Compare commits
67 Commits
vm-strings
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| bff9220fb1 | |||
| cb75bf59e0 | |||
| 28edfb6933 | |||
| 7c9d4fe908 | |||
| 85e5f7a2bf | |||
| 3da72874f5 | |||
| 551bd2c3f4 | |||
| 3522b3d0cd | |||
| 8c4a9991fd | |||
| 9e82663b2a | |||
| 219da3c3c2 | |||
| d0f8d93edf | |||
| 63c81f07f7 | |||
| 4ae060c68d | |||
| 65972fcbbe | |||
| b697b000a4 | |||
| 90155b0cb3 | |||
| 1f93732a7c | |||
| fbe2c529af | |||
| 34b1147fe6 | |||
| 1fb2a1df44 | |||
| 58262eab40 | |||
| b1f2de198e | |||
| bf83601cdf | |||
| ce352c000c | |||
| bdd30274ed | |||
| 711bfeb7f9 | |||
| bac1b413dc | |||
| 5ffca7bcf0 | |||
| 2b8663037f | |||
| 74139b7c65 | |||
| ef03ea9137 | |||
| ad7c22c168 | |||
| 782a2ea4e9 | |||
| 4b96902831 | |||
| 0eb394ddf5 | |||
| 381a59f5fe | |||
| 79d37a4e7b | |||
| 44dc34e52d | |||
| cd95011e7b | |||
| a4e1d43a74 | |||
| 7a6c2d80ab | |||
| bd34cdad63 | |||
| d11dbbcdf5 | |||
| 86d46b2a50 | |||
| 700ea6c54f | |||
| 461cf59bb0 | |||
| ef83cf7ef3 | |||
| b4637e2070 | |||
| 07abfc96b5 | |||
| bc4f59ecad | |||
| 362590292a | |||
| 145739aee2 | |||
| 0ff189bc1b | |||
| b8a769c80f | |||
| 1c05b3bb44 | |||
| aff2da591b | |||
| 795a890502 | |||
| 5619c9dc87 | |||
| bdd08c6c5b | |||
| 2413f9f362 | |||
| b0ef49bc2a | |||
| 0598bd1526 | |||
| bf6b0dfba9 | |||
| 2c4b56e362 | |||
| cf9ba376aa | |||
| 95d4eb0a60 |
986
Cargo.lock
generated
986
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
10
Cargo.toml
10
Cargo.toml
@@ -4,16 +4,12 @@ version = "0.1.0"
|
||||
authors = ["Alek Ratzloff <alekratz@gmail.com>"]
|
||||
edition = "2018"
|
||||
|
||||
build = "build.rs"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
bitflags = "1"
|
||||
byteorder = "1"
|
||||
lalrpop-util = "0.17.2"
|
||||
regex = "*"
|
||||
structopt = "0.3"
|
||||
snafu = "0.6.2"
|
||||
libvm = { path = "src/libvm" }
|
||||
|
||||
[build-dependencies]
|
||||
lalrpop = "0.17.2"
|
||||
rerun_except = "0.1"
|
||||
|
||||
4
build.rs
4
build.rs
@@ -1,5 +1,5 @@
|
||||
use lalrpop;
|
||||
use rerun_except::rerun_except;
|
||||
|
||||
fn main() {
|
||||
lalrpop::process_root().unwrap();
|
||||
rerun_except(&["*.asm"]).unwrap();
|
||||
}
|
||||
|
||||
25
run_asm_tests.sh
Executable file
25
run_asm_tests.sh
Executable file
@@ -0,0 +1,25 @@
|
||||
#!/bin/bash
|
||||
here="$(dirname "$0")"
|
||||
cd "$here"
|
||||
|
||||
tests="./tests"
|
||||
|
||||
RED="\e[31m"
|
||||
GREEN="\e[32m"
|
||||
RESET="\e[0m"
|
||||
|
||||
# Build project
|
||||
cargo build
|
||||
|
||||
find "$tests" -type f -name '*.asm' | \
|
||||
while read fname; do
|
||||
test_name="$(basename "${fname%.asm}")"
|
||||
printf "%s ... " "$test_name"
|
||||
cargo run --quiet "$fname"
|
||||
exit_code="$?"
|
||||
if (( "$exit_code" == 0 )); then
|
||||
echo -e "${GREEN}OK${RESET}"
|
||||
else
|
||||
echo -e "${RED}FAIL${RESET} ($exit_code)"
|
||||
fi
|
||||
done
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
use std::cmp::Ordering;
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)]
|
||||
pub struct Pos {
|
||||
pub source: usize,
|
||||
pub line: usize,
|
||||
@@ -22,23 +22,20 @@ impl Pos {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_char(c: char, source: usize, line: usize, col: usize, byte: usize) -> Self {
|
||||
Pos::new(source, line, col, byte, c.len_utf8())
|
||||
pub fn from_char(c: char) -> Self {
|
||||
Pos::new(0, 0, 0, 0, c.len_utf8())
|
||||
}
|
||||
|
||||
pub fn adv_char(self, c: char) -> Self {
|
||||
let mut next = self;
|
||||
next.byte += next.len;
|
||||
next.len = c.len_utf8();
|
||||
next.source += 1;
|
||||
next.col += 1;
|
||||
next
|
||||
pub fn adv_char(&mut self, c: char) {
|
||||
self.byte += self.len;
|
||||
self.len = c.len_utf8();
|
||||
self.source += 1;
|
||||
self.col += 1;
|
||||
}
|
||||
|
||||
pub fn adv_line(self) -> Self {
|
||||
let mut next = self;
|
||||
next.line += 1;
|
||||
next
|
||||
pub fn adv_line(&mut self) {
|
||||
self.line += 1;
|
||||
self.col = 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,8 +53,8 @@ impl Ord for Pos {
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub struct Span {
|
||||
start: Pos,
|
||||
end: Pos,
|
||||
pub start: Pos,
|
||||
pub end: Pos,
|
||||
}
|
||||
|
||||
impl Span {
|
||||
|
||||
2
src/libvm/.gitignore
vendored
Normal file
2
src/libvm/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
/target
|
||||
**/*.rs.bk
|
||||
851
src/libvm/Cargo.lock
generated
Normal file
851
src/libvm/Cargo.lock
generated
Normal file
@@ -0,0 +1,851 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
[[package]]
|
||||
name = "aho-corasick"
|
||||
version = "0.7.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"memchr 2.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ansi_term"
|
||||
version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "atty"
|
||||
version = "0.2.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"hermit-abi 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.67 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "autocfg"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "backtrace"
|
||||
version = "0.3.44"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"backtrace-sys 0.1.32 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.67 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rustc-demangle 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "backtrace-sys"
|
||||
version = "0.1.32"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"cc 1.0.50 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.67 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bincode"
|
||||
version = "1.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"byteorder 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "1.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "bstr"
|
||||
version = "0.2.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"memchr 2.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "byteorder"
|
||||
version = "1.3.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "cactus"
|
||||
version = "1.0.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.0.50"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "0.1.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "cfgrammar"
|
||||
version = "0.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"getopts 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"indexmap 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"num-traits 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"regex 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"vob 2.0.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "chrono"
|
||||
version = "0.4.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"num-integer 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"num-traits 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "2.33.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"atty 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"strsim 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"textwrap 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"unicode-width 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"vec_map 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-channel"
|
||||
version = "0.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"crossbeam-utils 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"maybe-uninit 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-utils"
|
||||
version = "0.7.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"autocfg 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "doc-comment"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "error-chain"
|
||||
version = "0.12.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"backtrace 0.3.44 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"version_check 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "filetime"
|
||||
version = "0.2.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.67 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fnv"
|
||||
version = "1.0.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "getopts"
|
||||
version = "0.2.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"unicode-width 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "getset"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"proc-macro-error 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"proc-macro2 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"syn 1.0.16 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "globset"
|
||||
version = "0.4.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"aho-corasick 0.7.9 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"bstr 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"fnv 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"regex 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "heck"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"unicode-segmentation 1.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hermit-abi"
|
||||
version = "0.1.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"libc 0.2.67 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ignore"
|
||||
version = "0.4.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"crossbeam-channel 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"globset 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"memchr 2.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"regex 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"same-file 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"thread_local 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"walkdir 2.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"winapi-util 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "indexmap"
|
||||
version = "1.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"autocfg 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lazy_static"
|
||||
version = "1.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.67"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "libvm"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"byteorder 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"cfgrammar 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"lrlex 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"lrpar 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"regex 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rerun_except 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"snafu 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"structopt 0.3.11 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
version = "0.4.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lrlex"
|
||||
version = "0.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"getopts 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"lrpar 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"num-traits 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"regex 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"try_from 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"typename 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lrpar"
|
||||
version = "0.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"bincode 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"cactus 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"cfgrammar 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"filetime 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"getopts 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"indexmap 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"lrtable 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"num-traits 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"packedvec 1.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"regex 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rmp-serde 0.14.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"static_assertions 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"typename 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"vergen 2.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"vob 2.0.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lrtable"
|
||||
version = "0.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"cfgrammar 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"fnv 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"getopts 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"macro-attr 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"newtype_derive 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"num-traits 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"sparsevec 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"static_assertions 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"try_from 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"vob 2.0.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "macro-attr"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "maybe-uninit"
|
||||
version = "2.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "newtype_derive"
|
||||
version = "0.1.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"rustc_version 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-integer"
|
||||
version = "0.1.42"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"autocfg 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"num-traits 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-traits"
|
||||
version = "0.2.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"autocfg 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "packedvec"
|
||||
version = "1.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"num-traits 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro-error"
|
||||
version = "0.4.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"proc-macro-error-attr 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"proc-macro2 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"syn 1.0.16 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"version_check 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro-error-attr"
|
||||
version = "0.4.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"proc-macro2 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"syn 1.0.16 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"syn-mid 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"version_check 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"proc-macro2 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "redox_syscall"
|
||||
version = "0.1.56"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "1.3.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"aho-corasick 0.7.9 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"memchr 2.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"regex-syntax 0.6.16 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"thread_local 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-syntax"
|
||||
version = "0.6.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "rerun_except"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"ignore 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rmp"
|
||||
version = "0.8.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"byteorder 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"num-traits 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rmp-serde"
|
||||
version = "0.14.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"byteorder 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rmp 0.8.9 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustc-demangle"
|
||||
version = "0.1.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "rustc_version"
|
||||
version = "0.1.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"semver 0.1.20 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustc_version"
|
||||
version = "0.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "same-file"
|
||||
version = "1.0.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"winapi-util 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "semver"
|
||||
version = "0.1.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "semver"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"semver-parser 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "semver-parser"
|
||||
version = "0.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.104"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"serde_derive 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.104"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"proc-macro2 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"syn 1.0.16 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "snafu"
|
||||
version = "0.6.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"doc-comment 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"snafu-derive 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "snafu-derive"
|
||||
version = "0.6.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"proc-macro2 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"syn 1.0.16 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sparsevec"
|
||||
version = "0.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"num-traits 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"packedvec 1.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"vob 2.0.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "static_assertions"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "strsim"
|
||||
version = "0.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "structopt"
|
||||
version = "0.3.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"structopt-derive 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "structopt-derive"
|
||||
version = "0.4.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"heck 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"proc-macro-error 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"proc-macro2 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"syn 1.0.16 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "1.0.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"proc-macro2 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syn-mid"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"proc-macro2 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"syn 1.0.16 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "textwrap"
|
||||
version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"unicode-width 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thread_local"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "time"
|
||||
version = "0.1.42"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"libc 0.2.67 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "try_from"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "typename"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"typename_derive 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "typename_derive"
|
||||
version = "0.1.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"syn 1.0.16 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-segmentation"
|
||||
version = "1.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-width"
|
||||
version = "0.1.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-xid"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "vec_map"
|
||||
version = "0.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "vergen"
|
||||
version = "2.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"chrono 0.4.10 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"error-chain 0.12.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"getset 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "version_check"
|
||||
version = "0.9.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "vob"
|
||||
version = "2.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"num-traits 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "walkdir"
|
||||
version = "2.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"same-file 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"winapi-util 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi"
|
||||
version = "0.3.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi-i686-pc-windows-gnu"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "winapi-util"
|
||||
version = "0.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi-x86_64-pc-windows-gnu"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[metadata]
|
||||
"checksum aho-corasick 0.7.9 (registry+https://github.com/rust-lang/crates.io-index)" = "d5e63fd144e18ba274ae7095c0197a870a7b9468abc801dd62f190d80817d2ec"
|
||||
"checksum ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b"
|
||||
"checksum atty 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)" = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
|
||||
"checksum autocfg 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f8aac770f1885fd7e387acedd76065302551364496e46b3dd00860b2f8359b9d"
|
||||
"checksum backtrace 0.3.44 (registry+https://github.com/rust-lang/crates.io-index)" = "e4036b9bf40f3cf16aba72a3d65e8a520fc4bafcdc7079aea8f848c58c5b5536"
|
||||
"checksum backtrace-sys 0.1.32 (registry+https://github.com/rust-lang/crates.io-index)" = "5d6575f128516de27e3ce99689419835fce9643a9b215a14d2b5b685be018491"
|
||||
"checksum bincode 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "5753e2a71534719bf3f4e57006c3a4f0d2c672a4b676eec84161f763eca87dbf"
|
||||
"checksum bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
|
||||
"checksum bstr 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "502ae1441a0a5adb8fbd38a5955a6416b9493e92b465de5e4a9bde6a539c2c48"
|
||||
"checksum byteorder 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "08c48aae112d48ed9f069b33538ea9e3e90aa263cfa3d1c24309612b1f7472de"
|
||||
"checksum cactus 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)" = "0713c9ecac2733e4c61a93157674eee620ba377d263714a6083d7a8be13468eb"
|
||||
"checksum cc 1.0.50 (registry+https://github.com/rust-lang/crates.io-index)" = "95e28fa049fda1c330bcf9d723be7663a899c4679724b34c81e9f5a326aab8cd"
|
||||
"checksum cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)" = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822"
|
||||
"checksum cfgrammar 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)" = "20f2d944e00cc7ce8af4feb1604b0b35d846d9d44f526de3b6eb754190e9a901"
|
||||
"checksum chrono 0.4.10 (registry+https://github.com/rust-lang/crates.io-index)" = "31850b4a4d6bae316f7a09e691c944c28299298837edc0a03f755618c23cbc01"
|
||||
"checksum clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5067f5bb2d80ef5d68b4c87db81601f0b75bca627bc2ef76b141d7b846a3c6d9"
|
||||
"checksum crossbeam-channel 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "cced8691919c02aac3cb0a1bc2e9b73d89e832bf9a06fc579d4e71b68a2da061"
|
||||
"checksum crossbeam-utils 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)" = "c3c7c73a2d1e9fc0886a08b93e98eb643461230d5f1925e4036204d5f2e261a8"
|
||||
"checksum doc-comment 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "923dea538cea0aa3025e8685b20d6ee21ef99c4f77e954a30febbaac5ec73a97"
|
||||
"checksum error-chain 0.12.2 (registry+https://github.com/rust-lang/crates.io-index)" = "d371106cc88ffdfb1eabd7111e432da544f16f3e2d7bf1dfe8bf575f1df045cd"
|
||||
"checksum filetime 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "1ff6d4dab0aa0c8e6346d46052e93b13a16cf847b54ed357087c35011048cc7d"
|
||||
"checksum fnv 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)" = "2fad85553e09a6f881f739c29f0b00b0f01357c743266d478b68951ce23285f3"
|
||||
"checksum getopts 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)" = "14dbbfd5c71d70241ecf9e6f13737f7b5ce823821063188d7e46c41d371eebd5"
|
||||
"checksum getset 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f62a139c59ae846c3964c392f12aac68f1997d1a40e9d3b40a89a4ab553e04a0"
|
||||
"checksum globset 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "925aa2cac82d8834e2b2a4415b6f6879757fb5c0928fc445ae76461a12eed8f2"
|
||||
"checksum heck 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "20564e78d53d2bb135c343b3f47714a56af2061f1c928fdb541dc7b9fdd94205"
|
||||
"checksum hermit-abi 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "1010591b26bbfe835e9faeabeb11866061cc7dcebffd56ad7d0942d0e61aefd8"
|
||||
"checksum ignore 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)" = "522daefc3b69036f80c7d2990b28ff9e0471c683bad05ca258e0a01dd22c5a1e"
|
||||
"checksum indexmap 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "076f042c5b7b98f31d205f1249267e12a6518c1481e9dae9764af19b707d2292"
|
||||
"checksum lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
|
||||
"checksum libc 0.2.67 (registry+https://github.com/rust-lang/crates.io-index)" = "eb147597cdf94ed43ab7a9038716637d2d1bf2bc571da995d0028dec06bd3018"
|
||||
"checksum log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)" = "14b6052be84e6b71ab17edffc2eeabf5c2c3ae1fdb464aae35ac50c67a44e1f7"
|
||||
"checksum lrlex 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)" = "205bebfb37f228ad711fc22944328a3f2b9d89acb30590cbae6005c3aa2153dd"
|
||||
"checksum lrpar 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)" = "5df95ed1526bb6a2d9e3890de679fc17178f5e68e930d921926d9e6e4e673638"
|
||||
"checksum lrtable 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)" = "35afa92a7b9135277668b9a696ad9811238196f0b5fb8bda79a6f343fe3ef044"
|
||||
"checksum macro-attr 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "00e51c6f0e2bf862b01b3d784fc32b02feb248a69062c51fb0b6d14cd526cc2a"
|
||||
"checksum maybe-uninit 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "60302e4db3a61da70c0cb7991976248362f30319e88850c487b9b95bbf059e00"
|
||||
"checksum memchr 2.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "3728d817d99e5ac407411fa471ff9800a778d88a24685968b36824eaf4bee400"
|
||||
"checksum newtype_derive 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "ac8cd24d9f185bb7223958d8c1ff7a961b74b1953fd05dba7cc568a63b3861ec"
|
||||
"checksum num-integer 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)" = "3f6ea62e9d81a77cd3ee9a2a5b9b609447857f3d358704331e4ef39eb247fcba"
|
||||
"checksum num-traits 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "c62be47e61d1842b9170f0fdeec8eba98e60e90e5446449a0545e5152acd7096"
|
||||
"checksum packedvec 1.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "1fce532538ba443dbd7e9d0dd16e687cec205dc1c13fa4e6cb225f3405d85cdd"
|
||||
"checksum proc-macro-error 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)" = "e7959c6467d962050d639361f7703b2051c43036d03493c36f01d440fdd3138a"
|
||||
"checksum proc-macro-error-attr 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)" = "e4002d9f55991d5e019fb940a90e1a95eb80c24e77cb2462dd4dc869604d543a"
|
||||
"checksum proc-macro2 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)" = "6c09721c6781493a2a492a96b5a5bf19b65917fe6728884e7c44dd0c60ca3435"
|
||||
"checksum quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "053a8c8bcc71fcce321828dc897a98ab9760bef03a4fc36693c231e5b3216cfe"
|
||||
"checksum redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)" = "2439c63f3f6139d1b57529d16bc3b8bb855230c8efcc5d3a896c8bea7c3b1e84"
|
||||
"checksum regex 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "322cf97724bea3ee221b78fe25ac9c46114ebb51747ad5babd51a2fc6a8235a8"
|
||||
"checksum regex-syntax 0.6.16 (registry+https://github.com/rust-lang/crates.io-index)" = "1132f845907680735a84409c3bebc64d1364a5683ffbce899550cd09d5eaefc1"
|
||||
"checksum rerun_except 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "f74370ce9660eea246c8c15aa4e30e836438a3ff9dc24af4ae1d3c6c6a0a3c1e"
|
||||
"checksum rmp 0.8.9 (registry+https://github.com/rust-lang/crates.io-index)" = "0f10b46df14cf1ee1ac7baa4d2fbc2c52c0622a4b82fa8740e37bc452ac0184f"
|
||||
"checksum rmp-serde 0.14.3 (registry+https://github.com/rust-lang/crates.io-index)" = "4c1ee98f14fe8b8e9c5ea13d25da7b2a1796169202c57a09d7288de90d56222b"
|
||||
"checksum rustc-demangle 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)" = "4c691c0e608126e00913e33f0ccf3727d5fc84573623b8d65b2df340b5201783"
|
||||
"checksum rustc_version 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "c5f5376ea5e30ce23c03eb77cbe4962b988deead10910c372b226388b594c084"
|
||||
"checksum rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a"
|
||||
"checksum same-file 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)" = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
|
||||
"checksum semver 0.1.20 (registry+https://github.com/rust-lang/crates.io-index)" = "d4f410fedcf71af0345d7607d246e7ad15faaadd49d240ee3b24e5dc21a820ac"
|
||||
"checksum semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403"
|
||||
"checksum semver-parser 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3"
|
||||
"checksum serde 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)" = "414115f25f818d7dfccec8ee535d76949ae78584fc4f79a6f45a904bf8ab4449"
|
||||
"checksum serde_derive 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)" = "128f9e303a5a29922045a830221b8f78ec74a5f544944f3d5984f8ec3895ef64"
|
||||
"checksum snafu 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "546db9181bce2aa22ed883c33d65603b76335b4c2533a98289f54265043de7a1"
|
||||
"checksum snafu-derive 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "bdc75da2e0323f297402fd9c8fdba709bb04e4c627cbe31d19a2c91fc8d9f0e2"
|
||||
"checksum sparsevec 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "a182a1fc36753f8a2e3eea04cc3cd28065d2949cbda1e3a453cd183dace42bbb"
|
||||
"checksum static_assertions 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
|
||||
"checksum strsim 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a"
|
||||
"checksum structopt 0.3.11 (registry+https://github.com/rust-lang/crates.io-index)" = "3fe43617218c0805c6eb37160119dc3c548110a67786da7218d1c6555212f073"
|
||||
"checksum structopt-derive 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "c6e79c80e0f4efd86ca960218d4e056249be189ff1c42824dcd9a7f51a56f0bd"
|
||||
"checksum syn 1.0.16 (registry+https://github.com/rust-lang/crates.io-index)" = "123bd9499cfb380418d509322d7a6d52e5315f064fe4b3ad18a53d6b92c07859"
|
||||
"checksum syn-mid 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7be3539f6c128a931cf19dcee741c1af532c7fd387baa739c03dd2e96479338a"
|
||||
"checksum textwrap 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060"
|
||||
"checksum thread_local 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d40c6d1b69745a6ec6fb1ca717914848da4b44ae29d9b3080cbee91d72a69b14"
|
||||
"checksum time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)" = "db8dcfca086c1143c9270ac42a2bbd8a7ee477b78ac8e45b19abfb0cbede4b6f"
|
||||
"checksum try_from 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "283d3b89e1368717881a9d51dad843cc435380d8109c9e47d38780a324698d8b"
|
||||
"checksum typename 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "c255dd1af8cf5cfb95f062266201778080d215e86294dba14c47bf3137c55419"
|
||||
"checksum typename_derive 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "c68acf2f6e8a32e2bad47e73ee177558583fb9dbb264a5c0569c7f9f80f79b0a"
|
||||
"checksum unicode-segmentation 1.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e83e153d1053cbb5a118eeff7fd5be06ed99153f00dbcd8ae310c5fb2b22edc0"
|
||||
"checksum unicode-width 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "caaa9d531767d1ff2150b9332433f32a24622147e5ebb1f26409d5da67afd479"
|
||||
"checksum unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c"
|
||||
"checksum vec_map 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "05c78687fb1a80548ae3250346c3db86a80a7cdd77bda190189f2d0a0987c81a"
|
||||
"checksum vergen 2.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "93fb2d57fbc535fcd45548c99b141d2d960995daaf04b864c4d9fe1ea011c819"
|
||||
"checksum version_check 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)" = "078775d0255232fb988e6fccf26ddc9d1ac274299aaedcedce21c6f72cc533ce"
|
||||
"checksum vob 2.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "5430ea8977e2f0ed62cd37239cd713415022b240095c2f9187c32823015ae52f"
|
||||
"checksum walkdir 2.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "777182bc735b6424e1a57516d35ed72cb8019d85c8c9bf536dccb3445c1a2f7d"
|
||||
"checksum winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)" = "8093091eeb260906a183e6ae1abdba2ef5ef2257a21801128899c3fc699229c6"
|
||||
"checksum winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
|
||||
"checksum winapi-util 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "4ccfbf554c6ad11084fb7517daca16cfdcaccbdadba4fc336f032a8b12c2ad80"
|
||||
"checksum winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
||||
27
src/libvm/Cargo.toml
Normal file
27
src/libvm/Cargo.toml
Normal file
@@ -0,0 +1,27 @@
|
||||
[package]
|
||||
name = "libvm"
|
||||
version = "0.1.0"
|
||||
authors = ["Alek Ratzloff <alekratz@gmail.com>"]
|
||||
edition = "2018"
|
||||
|
||||
build = "build.rs"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
bitflags = "1"
|
||||
byteorder = "1"
|
||||
lazy_static = "1"
|
||||
snafu = "0.6.2"
|
||||
|
||||
cfgrammar = "0.6"
|
||||
lrlex = "0.6"
|
||||
lrpar = "0.6"
|
||||
regex = "*"
|
||||
prettytable-rs = "0.8"
|
||||
|
||||
[build-dependencies]
|
||||
cfgrammar = "0.6"
|
||||
lrlex = "0.6"
|
||||
lrpar = "0.6"
|
||||
rerun_except = "0.1"
|
||||
15
src/libvm/build.rs
Normal file
15
src/libvm/build.rs
Normal file
@@ -0,0 +1,15 @@
|
||||
use cfgrammar::yacc::YaccKind;
|
||||
use lrlex::LexerBuilder;
|
||||
use lrpar::CTParserBuilder;
|
||||
use rerun_except::rerun_except;
|
||||
|
||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let lex_rule_ids_map = CTParserBuilder::new()
|
||||
.yacckind(YaccKind::Grmtools)
|
||||
.process_file_in_src("obj/syn/parser.y")?;
|
||||
LexerBuilder::new()
|
||||
.rule_ids_map(lex_rule_ids_map)
|
||||
.process_file_in_src("obj/syn/lexer.l")?;
|
||||
rerun_except(&["examples/*.asm", "tests/*.asm"]).unwrap();
|
||||
Ok(())
|
||||
}
|
||||
52
src/libvm/examples/deadbeef.asm
Normal file
52
src/libvm/examples/deadbeef.asm
Normal file
@@ -0,0 +1,52 @@
|
||||
.section data 0x1000 {
|
||||
dead: .u16 0xDEAD
|
||||
beef: .u16 0xBEEF
|
||||
|
||||
.export dead
|
||||
.export beef
|
||||
}
|
||||
|
||||
.section code 0x0 {
|
||||
main:
|
||||
mov %ivt, ivt
|
||||
or %flags, 0b100
|
||||
mov %r0, 0xDEAD
|
||||
shl %r0, 16
|
||||
; move 32 bits at 'beef' to %r01
|
||||
; TODO(syntax)
|
||||
mov %r1, (beef)u32
|
||||
or %r0, %r01
|
||||
cmpeq %r0, 0xDEADBEEF
|
||||
; jump to the address 'end'
|
||||
jnz end
|
||||
mov %status, 1
|
||||
end:
|
||||
int 0, 0
|
||||
halt
|
||||
.export main
|
||||
|
||||
.align u64
|
||||
dz:
|
||||
mov %status, 255
|
||||
halt
|
||||
|
||||
.export dz
|
||||
}
|
||||
|
||||
.section ivt 0x2000 .. 0x2800 {
|
||||
ivt:
|
||||
.interrupt 1, dz ; Divide by zero
|
||||
|
||||
.export ivt
|
||||
}
|
||||
|
||||
.section stack 0x4000 .. 0x5000 {
|
||||
stack_base:
|
||||
.export stack_base
|
||||
}
|
||||
|
||||
.meta {
|
||||
ip: main
|
||||
sp: stack_base
|
||||
ivt: ivt
|
||||
}
|
||||
28
src/libvm/examples/factorial.asm
Normal file
28
src/libvm/examples/factorial.asm
Normal file
@@ -0,0 +1,28 @@
|
||||
.section data 0x1000 {
|
||||
factorial:
|
||||
cmplt %r0, 2
|
||||
jnz factorial_one
|
||||
|
||||
push %r0
|
||||
sub %r0, 1
|
||||
call factorial
|
||||
pop %r0
|
||||
mul %status, %r0
|
||||
jmp factorial_end
|
||||
|
||||
factorial_one:
|
||||
mov %status, 1
|
||||
factorial_end:
|
||||
ret
|
||||
|
||||
main:
|
||||
mov %r0, 5
|
||||
call factorial
|
||||
halt
|
||||
|
||||
.export main
|
||||
}
|
||||
|
||||
.meta {
|
||||
ip: main
|
||||
}
|
||||
30
src/libvm/examples/strings.asm
Normal file
30
src/libvm/examples/strings.asm
Normal file
@@ -0,0 +1,30 @@
|
||||
.section data 0x1000 .. 0x1100 {
|
||||
zstr: .zstring "This is a string"
|
||||
str: .string "This is a string"
|
||||
|
||||
.export zstr
|
||||
.export str
|
||||
}
|
||||
|
||||
.section code 0x0 {
|
||||
; Take the length of a zstr without those fancy "function calls" and "stack frames"
|
||||
; %r0: the input string (in)
|
||||
; %r1: the return address (in)
|
||||
; %r2: the length of the string (out)
|
||||
|
||||
zstrlen:
|
||||
mov %r2, %r0
|
||||
ztrlen_loop:
|
||||
cmpeq (%r2)u8, 0
|
||||
zstrlen_end:
|
||||
jmp %r1
|
||||
|
||||
main:
|
||||
halt
|
||||
|
||||
.export main
|
||||
}
|
||||
|
||||
.meta {
|
||||
ip: main
|
||||
}
|
||||
73
src/libvm/src/addr.rs
Normal file
73
src/libvm/src/addr.rs
Normal file
@@ -0,0 +1,73 @@
|
||||
use std::{
|
||||
cmp::Ordering,
|
||||
fmt::{self, Formatter, LowerHex},
|
||||
ops::{Add, AddAssign},
|
||||
};
|
||||
|
||||
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub struct Addr(pub u64);
|
||||
|
||||
impl LowerHex for Addr {
|
||||
fn fmt(&self, fmt: &mut Formatter) -> fmt::Result {
|
||||
let Addr(v) = self;
|
||||
LowerHex::fmt(v, fmt)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Add<T> for Addr
|
||||
where
|
||||
T: Add<u64, Output = u64>,
|
||||
u64: Add<T, Output = u64>,
|
||||
{
|
||||
type Output = Addr;
|
||||
|
||||
fn add(self, rhs: T) -> Self::Output {
|
||||
Addr(self.0 + rhs)
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! impl_add_assign {
|
||||
($ty:ty) => {
|
||||
impl AddAssign<$ty> for Addr {
|
||||
fn add_assign(&mut self, rhs: $ty) {
|
||||
self.0 = self.0 + (rhs as u64);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
impl_add_assign!(usize);
|
||||
impl_add_assign!(u64);
|
||||
|
||||
macro_rules! impl_cmp {
|
||||
($ty:ty) => {
|
||||
impl PartialEq<$ty> for Addr {
|
||||
fn eq(&self, other: &$ty) -> bool {
|
||||
self.0 == (*other as u64)
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialOrd<$ty> for Addr {
|
||||
fn partial_cmp(&self, other: &$ty) -> Option<Ordering> {
|
||||
let other = *other as u64;
|
||||
self.0.partial_cmp(&other)
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
impl_cmp!(usize);
|
||||
impl_cmp!(u64);
|
||||
|
||||
macro_rules! impl_from {
|
||||
($ty:ty) => {
|
||||
impl From<$ty> for Addr {
|
||||
fn from(other: $ty) -> Self {
|
||||
Addr(other as u64)
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
impl_from!(usize);
|
||||
impl_from!(u64);
|
||||
@@ -1,4 +1,4 @@
|
||||
use crate::vm::{
|
||||
use crate::{
|
||||
error::*,
|
||||
inst::*,
|
||||
mem::MemCursor,
|
||||
43
src/libvm/src/error.rs
Normal file
43
src/libvm/src/error.rs
Normal file
@@ -0,0 +1,43 @@
|
||||
use crate::{addr::*, inst::InstOp, reg::Reg};
|
||||
use snafu::Snafu;
|
||||
|
||||
#[derive(Snafu, Debug, Clone)]
|
||||
pub enum VmError {
|
||||
#[snafu(display("object to load spans too much memory"))]
|
||||
ObjectTooLarge { object_size: usize, max_mem: usize },
|
||||
|
||||
#[snafu(display("an interrupt was not caught"))]
|
||||
Interrupt { source: InterruptError },
|
||||
|
||||
#[snafu(display("a triple fault occurred"))]
|
||||
TripleFault,
|
||||
}
|
||||
|
||||
impl From<InterruptError> for VmError {
|
||||
fn from(other: InterruptError) -> Self {
|
||||
VmError::Interrupt { source: other, }
|
||||
}
|
||||
}
|
||||
|
||||
/// The error type that is returned by the `State::tick()` function.
|
||||
///
|
||||
/// This error is meant to be caught and handled specifically, but can be passed up as a `VmError`.
|
||||
#[derive(Snafu, Debug, Clone)]
|
||||
pub enum InterruptError {
|
||||
#[snafu(display("illegal register: 0x{:02x}", reg))]
|
||||
IllegalReg { reg: Reg },
|
||||
|
||||
#[snafu(display("memory address out of bounds: 0x{:016x}", addr))]
|
||||
MemOutOfBounds { addr: Addr },
|
||||
|
||||
#[snafu(display("illegal instruction opcode: 0x{:04x}", op))]
|
||||
IllegalOpcode { op: InstOp },
|
||||
|
||||
#[snafu(display("illegal destination specification: 0b{:08b}", spec))]
|
||||
IllegalDestSpec { spec: u8 },
|
||||
|
||||
#[snafu(display("illegal source specification: 0b{:08b}", spec))]
|
||||
IllegalSourceSpec { spec: u8 },
|
||||
}
|
||||
|
||||
pub type Result<T, E = VmError> = std::result::Result<T, E>;
|
||||
@@ -4,5 +4,6 @@ bitflags! {
|
||||
pub struct Flags: u64 {
|
||||
const HALT = 1;
|
||||
const COMPARE = 1 << 1;
|
||||
const INTERRUPT_ENABLE = 1 << 2;
|
||||
}
|
||||
}
|
||||
268
src/libvm/src/inst.rs
Normal file
268
src/libvm/src/inst.rs
Normal file
@@ -0,0 +1,268 @@
|
||||
use crate::{addr::Addr, reg::Reg};
|
||||
|
||||
macro_rules! instructions {
|
||||
{
|
||||
$($variant:ident = $value:expr),* $(,)?
|
||||
} => {
|
||||
|
||||
$(
|
||||
#[allow(dead_code)]
|
||||
pub const $variant: InstOp = $value;
|
||||
)*
|
||||
|
||||
pub fn inst_name(op: InstOp) -> Option<&'static str> {
|
||||
match op {
|
||||
$(
|
||||
$value => Some(stringify!($variant)),
|
||||
)*
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
pub type InstOp = u16;
|
||||
|
||||
instructions! {
|
||||
ADD = 0x1000,
|
||||
SUB = 0x1001,
|
||||
MUL = 0x1002,
|
||||
DIV = 0x1003,
|
||||
IDIV = 0x1004,
|
||||
MOD = 0x1005,
|
||||
AND = 0x1006,
|
||||
OR = 0x1007,
|
||||
XOR = 0x1008,
|
||||
SHL = 0x1009,
|
||||
SHR = 0x100a,
|
||||
INEG = 0x100b,
|
||||
INV = 0x100c,
|
||||
NOT = 0x100d,
|
||||
CMPEQ = 0x2000,
|
||||
CMPLT = 0x2001,
|
||||
JMP = 0x2002,
|
||||
JZ = 0x2003,
|
||||
JNZ = 0x2004,
|
||||
CALL = 0x3000,
|
||||
RET = 0x3001,
|
||||
PUSH = 0x3002,
|
||||
POP = 0x3003,
|
||||
INT = 0x3004,
|
||||
IRET = 0x3005,
|
||||
MOV = 0x4000,
|
||||
HALT = 0xF000,
|
||||
NOP = 0xF001,
|
||||
DUMP = 0xF002,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub enum Inst {
|
||||
Add(Dest, Source),
|
||||
Sub(Dest, Source),
|
||||
Mul(Dest, Source),
|
||||
Div(Dest, Source),
|
||||
IDiv(Dest, Source),
|
||||
Mod(Dest, Source),
|
||||
And(Dest, Source),
|
||||
Or(Dest, Source),
|
||||
Xor(Dest, Source),
|
||||
Shl(Dest, Source),
|
||||
Shr(Dest, Source),
|
||||
INeg(Dest, Source),
|
||||
Inv(Dest, Source),
|
||||
Not(Dest, Source),
|
||||
CmpEq(Source, Source),
|
||||
CmpLt(Source, Source),
|
||||
Jmp(Source),
|
||||
Jz(Source),
|
||||
Jnz(Source),
|
||||
Call(Source),
|
||||
Ret,
|
||||
Push(Source),
|
||||
Pop(Dest),
|
||||
Int(Source, Source),
|
||||
IRet,
|
||||
Mov(Dest, Source),
|
||||
Halt,
|
||||
Nop,
|
||||
Dump,
|
||||
}
|
||||
|
||||
impl Inst {
|
||||
pub fn op(&self) -> InstOp {
|
||||
match self {
|
||||
Inst::Add(_, _) => ADD,
|
||||
Inst::Sub(_, _) => SUB,
|
||||
Inst::Mul(_, _) => MUL,
|
||||
Inst::Div(_, _) => DIV,
|
||||
Inst::IDiv(_, _) => IDIV,
|
||||
Inst::Mod(_, _) => MOD,
|
||||
Inst::And(_, _) => AND,
|
||||
Inst::Or(_, _) => OR,
|
||||
Inst::Xor(_, _) => XOR,
|
||||
Inst::Shl(_, _) => SHL,
|
||||
Inst::Shr(_, _) => SHL,
|
||||
Inst::INeg(_, _) => INEG,
|
||||
Inst::Inv(_, _) => INV,
|
||||
Inst::Not(_, _) => NOT,
|
||||
Inst::CmpEq(_, _) => CMPEQ,
|
||||
Inst::CmpLt(_, _) => CMPLT,
|
||||
Inst::Jmp(_) => JMP,
|
||||
Inst::Jz(_) => JZ,
|
||||
Inst::Jnz(_) => JNZ,
|
||||
Inst::Mov(_, _) => MOV,
|
||||
Inst::Call(_) => CALL,
|
||||
Inst::Ret => RET,
|
||||
Inst::Push(_) => PUSH,
|
||||
Inst::Pop(_) => POP,
|
||||
Inst::Int(_, _) => INT,
|
||||
Inst::IRet => IRET,
|
||||
Inst::Halt => HALT,
|
||||
Inst::Nop => NOP,
|
||||
Inst::Dump => DUMP,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn len(&self) -> usize {
|
||||
match self {
|
||||
Inst::Add(dest, source)
|
||||
| Inst::Sub(dest, source)
|
||||
| Inst::Mul(dest, source)
|
||||
| Inst::Div(dest, source)
|
||||
| Inst::IDiv(dest, source)
|
||||
| Inst::Mod(dest, source)
|
||||
| Inst::And(dest, source)
|
||||
| Inst::Or(dest, source)
|
||||
| Inst::Xor(dest, source)
|
||||
| Inst::Shl(dest, source)
|
||||
| Inst::Shr(dest, source)
|
||||
| Inst::INeg(dest, source)
|
||||
| Inst::Inv(dest, source)
|
||||
| Inst::Not(dest, source)
|
||||
| Inst::Mov(dest, source) => 3 + dest.len() + source.len(),
|
||||
Inst::CmpEq(s1, s2) | Inst::CmpLt(s1, s2) | Inst::Int(s1, s2) => {
|
||||
3 + s1.len() + s2.len()
|
||||
}
|
||||
Inst::Jmp(v) | Inst::Jz(v) | Inst::Jnz(v) | Inst::Call(v) | Inst::Push(v) => {
|
||||
3 + v.len()
|
||||
}
|
||||
Inst::Pop(v) => 3 + v.len(),
|
||||
Inst::Ret | Inst::Halt | Inst::Nop | Inst::Dump | Inst::IRet => 2,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum Source {
|
||||
Addr64(Addr),
|
||||
Addr32(Addr),
|
||||
Addr16(Addr),
|
||||
Addr8(Addr),
|
||||
RegAddr64(Reg),
|
||||
RegAddr32(Reg),
|
||||
RegAddr16(Reg),
|
||||
RegAddr8(Reg),
|
||||
Reg(Reg),
|
||||
Imm(u64),
|
||||
}
|
||||
|
||||
impl Source {
|
||||
/// The length of this source in its instruction.
|
||||
pub fn len(&self) -> usize {
|
||||
match self {
|
||||
Source::Addr64(_) | Source::Addr32(_) | Source::Addr16(_) | Source::Addr8(_) => 8,
|
||||
Source::RegAddr64(_)
|
||||
| Source::RegAddr32(_)
|
||||
| Source::RegAddr16(_)
|
||||
| Source::RegAddr8(_) => 1,
|
||||
Source::Reg(_) => 1,
|
||||
Source::Imm(_) => 8,
|
||||
}
|
||||
}
|
||||
|
||||
/// The length of the value that this source points to.
|
||||
pub fn value_len(&self) -> usize {
|
||||
match self {
|
||||
Source::Addr64(_) | Source::RegAddr64(_) | Source::Reg(_) | Source::Imm(_) => 8,
|
||||
Source::Addr32(_) | Source::RegAddr32(_) => 4,
|
||||
Source::Addr16(_) | Source::RegAddr16(_) => 2,
|
||||
Source::Addr8(_) | Source::RegAddr8(_) => 1,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Dest> for Source {
|
||||
fn from(other: Dest) -> Self {
|
||||
match other {
|
||||
Dest::Addr64(a) => Source::Addr64(a),
|
||||
Dest::Addr32(a) => Source::Addr32(a),
|
||||
Dest::Addr16(a) => Source::Addr16(a),
|
||||
Dest::Addr8(a) => Source::Addr8(a),
|
||||
Dest::RegAddr64(r) => Source::RegAddr64(r),
|
||||
Dest::RegAddr32(r) => Source::RegAddr32(r),
|
||||
Dest::RegAddr16(r) => Source::RegAddr16(r),
|
||||
Dest::RegAddr8(r) => Source::RegAddr8(r),
|
||||
Dest::Reg(r) => Source::Reg(r),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum Dest {
|
||||
Addr64(Addr),
|
||||
Addr32(Addr),
|
||||
Addr16(Addr),
|
||||
Addr8(Addr),
|
||||
RegAddr64(Reg),
|
||||
RegAddr32(Reg),
|
||||
RegAddr16(Reg),
|
||||
RegAddr8(Reg),
|
||||
Reg(Reg),
|
||||
}
|
||||
|
||||
impl Dest {
|
||||
/// The length of this destination in its instruction.
|
||||
pub fn len(&self) -> usize {
|
||||
match self {
|
||||
Dest::Addr64(_) | Dest::Addr32(_) | Dest::Addr16(_) | Dest::Addr8(_) => 8,
|
||||
Dest::RegAddr64(_) | Dest::RegAddr32(_) | Dest::RegAddr16(_) | Dest::RegAddr8(_) => 1,
|
||||
Dest::Reg(_) => 1,
|
||||
}
|
||||
}
|
||||
|
||||
/// The length of the value that this destination points to.
|
||||
pub fn value_len(&self) -> usize {
|
||||
match self {
|
||||
Dest::Addr64(_) | Dest::RegAddr64(_) | Dest::Reg(_) => 8,
|
||||
Dest::Addr32(_) | Dest::RegAddr32(_) => 4,
|
||||
Dest::Addr16(_) | Dest::RegAddr16(_) => 2,
|
||||
Dest::Addr8(_) | Dest::RegAddr8(_) => 1,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO : make this an enum
|
||||
|
||||
pub const DEST_ADDR64: u8 = 0b0000;
|
||||
pub const DEST_ADDR32: u8 = 0b0001;
|
||||
pub const DEST_ADDR16: u8 = 0b0010;
|
||||
pub const DEST_ADDR8: u8 = 0b0011;
|
||||
pub const DEST_REG_ADDR64: u8 = 0b0100;
|
||||
pub const DEST_REG_ADDR32: u8 = 0b0101;
|
||||
pub const DEST_REG_ADDR16: u8 = 0b0110;
|
||||
pub const DEST_REG_ADDR8: u8 = 0b0111;
|
||||
/* immediates - not used, invalid */
|
||||
pub const DEST_REG: u8 = 0b1100;
|
||||
|
||||
pub const SOURCE_ADDR64: u8 = 0b0000;
|
||||
pub const SOURCE_ADDR32: u8 = 0b0001;
|
||||
pub const SOURCE_ADDR16: u8 = 0b0010;
|
||||
pub const SOURCE_ADDR8: u8 = 0b0011;
|
||||
pub const SOURCE_REG_ADDR64: u8 = 0b0100;
|
||||
pub const SOURCE_REG_ADDR32: u8 = 0b0101;
|
||||
pub const SOURCE_REG_ADDR16: u8 = 0b0110;
|
||||
pub const SOURCE_REG_ADDR8: u8 = 0b0111;
|
||||
pub const SOURCE_IMM64: u8 = 0b1000;
|
||||
pub const SOURCE_IMM32: u8 = 0b1001;
|
||||
pub const SOURCE_IMM16: u8 = 0b1010;
|
||||
pub const SOURCE_IMM8: u8 = 0b1011;
|
||||
pub const SOURCE_REG: u8 = 0b1100;
|
||||
62
src/libvm/src/interrupt.rs
Normal file
62
src/libvm/src/interrupt.rs
Normal file
@@ -0,0 +1,62 @@
|
||||
use crate::addr::Addr;
|
||||
|
||||
macro_rules! interrupts {
|
||||
{
|
||||
$($const_name:ident = $value:expr),* $(,)?
|
||||
} => {
|
||||
pub type InterruptIndex = usize;
|
||||
|
||||
$(
|
||||
#[allow(dead_code)]
|
||||
pub const $const_name: InterruptIndex = $value;
|
||||
)*
|
||||
};
|
||||
}
|
||||
|
||||
interrupts! {
|
||||
// Error state exceptions
|
||||
|
||||
DOUBLE_FAULT = 0x00,
|
||||
ILLEGAL_INSTRUCTION = 0x01,
|
||||
ILLEGAL_MEMORY_ADDRESS = 0x02,
|
||||
DIVIDE_BY_ZERO = 0x03,
|
||||
|
||||
// General exceptions
|
||||
|
||||
//IO_EVENT = 0x80,
|
||||
}
|
||||
|
||||
pub const MAX_ERROR_INTERRUPT: usize = 0x7f;
|
||||
|
||||
pub const IVT_LENGTH: usize = 512;
|
||||
|
||||
#[derive(Debug, Clone, Copy, Default)]
|
||||
pub struct InterruptVector(u64);
|
||||
|
||||
const ENABLED_MASK: u64 = 0x8000_0000_0000_0000;
|
||||
const ADDR_MASK: u64 = 0x1fff_ffff_ffff_ffff;
|
||||
|
||||
impl InterruptVector {
|
||||
pub fn enabled(&self) -> bool {
|
||||
(self.0 & ENABLED_MASK) != 0
|
||||
}
|
||||
|
||||
pub fn set_enabled(&mut self, enabled: bool) {
|
||||
let enabled = (enabled as u64) << 63;
|
||||
self.0 |= enabled;
|
||||
}
|
||||
|
||||
pub fn addr(&self) -> Addr {
|
||||
Addr((self.0 & ADDR_MASK) << 3)
|
||||
}
|
||||
|
||||
pub fn set_addr(&mut self, addr: Addr) {
|
||||
let addr = (addr.0 >> 3) & ADDR_MASK;
|
||||
self.0 &= !ADDR_MASK;
|
||||
self.0 |= addr;
|
||||
}
|
||||
|
||||
pub fn to_vec(&self) -> Vec<u8> {
|
||||
self.0.to_le_bytes().to_vec()
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,11 @@
|
||||
pub mod disassemble;
|
||||
#![allow(dead_code)]
|
||||
|
||||
pub mod addr;
|
||||
pub mod error;
|
||||
pub mod flags;
|
||||
pub mod inst;
|
||||
pub mod interrupt;
|
||||
pub mod mem;
|
||||
pub mod obj;
|
||||
pub mod reg;
|
||||
mod tick;
|
||||
pub mod visit;
|
||||
pub mod vm;
|
||||
pub mod state;
|
||||
307
src/libvm/src/mem.rs
Normal file
307
src/libvm/src/mem.rs
Normal file
@@ -0,0 +1,307 @@
|
||||
use crate::{addr::*, error::*, inst::*, reg::*};
|
||||
use byteorder::{ReadBytesExt, WriteBytesExt, LE};
|
||||
use std::io::Cursor;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct MemCursor<T> {
|
||||
cursor: Cursor<T>,
|
||||
}
|
||||
|
||||
impl<T> MemCursor<T>
|
||||
where
|
||||
Cursor<T>: ReadBytesExt,
|
||||
T: AsRef<[u8]>,
|
||||
{
|
||||
pub fn new(mem: T) -> Self {
|
||||
MemCursor {
|
||||
cursor: Cursor::new(mem),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn position(&self) -> u64 {
|
||||
self.cursor.position()
|
||||
}
|
||||
|
||||
pub fn set_position<P: Into<Addr>>(&mut self, position: P) {
|
||||
self.cursor.set_position((position.into()).0)
|
||||
}
|
||||
|
||||
pub fn is_end(&self) -> bool {
|
||||
self.check_addr(self.position()).is_err()
|
||||
}
|
||||
|
||||
pub fn next_bytes(&mut self, count: usize) -> Result<&[u8]> {
|
||||
let start = self.position() as usize;
|
||||
let end = start + count;
|
||||
self.check_addr(end as u64 - 1)?;
|
||||
self.cursor.set_position(end as u64);
|
||||
Ok(&self.cursor.get_ref().as_ref()[start..end])
|
||||
}
|
||||
|
||||
pub fn next_u8_unchecked(&mut self) -> u8 {
|
||||
self.cursor.read_u8().unwrap()
|
||||
}
|
||||
|
||||
pub fn next_u8(&mut self) -> Result<u8> {
|
||||
self.check_addr(self.position())
|
||||
.map(|_| self.next_u8_unchecked())
|
||||
}
|
||||
|
||||
pub fn next_u16_unchecked(&mut self) -> u16 {
|
||||
self.cursor.read_u16::<LE>().unwrap()
|
||||
}
|
||||
|
||||
pub fn next_u16(&mut self) -> Result<u16> {
|
||||
self.check_addr(self.position() + 1)
|
||||
.map(|_| self.next_u16_unchecked())
|
||||
}
|
||||
|
||||
pub fn next_u32_unchecked(&mut self) -> u32 {
|
||||
self.cursor.read_u32::<LE>().unwrap()
|
||||
}
|
||||
|
||||
pub fn next_u32(&mut self) -> Result<u32> {
|
||||
self.check_addr(self.position() + 3)
|
||||
.map(|_| self.next_u32_unchecked())
|
||||
}
|
||||
|
||||
pub fn next_u64_unchecked(&mut self) -> u64 {
|
||||
self.cursor.read_u64::<LE>().unwrap()
|
||||
}
|
||||
|
||||
pub fn next_u64(&mut self) -> Result<u64> {
|
||||
self.check_addr(self.position() + 7)
|
||||
.map(|_| self.next_u64_unchecked())
|
||||
}
|
||||
|
||||
pub fn next_addr(&mut self) -> Result<Addr> {
|
||||
self.check_addr(self.position() + 7)
|
||||
.map(|_| self.next_addr_unchecked())
|
||||
}
|
||||
|
||||
pub fn next_addr_unchecked(&mut self) -> Addr {
|
||||
Addr(self.next_u64_unchecked())
|
||||
}
|
||||
|
||||
pub fn next_inst(&mut self) -> Result<Inst> {
|
||||
let start = self.position();
|
||||
let result = self.next_inst_inner();
|
||||
if result.is_err() {
|
||||
self.set_position(start);
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
fn next_inst_inner(&mut self) -> Result<Inst> {
|
||||
let start = self.position();
|
||||
let op = self.next_u16()?;
|
||||
|
||||
macro_rules! dest_source {
|
||||
($variant:ident) => {{
|
||||
let (d, s) = self.next_dest_source()?;
|
||||
Ok(Inst::$variant(d, s))
|
||||
}};
|
||||
}
|
||||
macro_rules! source_source {
|
||||
($variant:ident) => {{
|
||||
let (s1, s2) = self.next_source_source()?;
|
||||
Ok(Inst::$variant(s1, s2))
|
||||
}};
|
||||
}
|
||||
macro_rules! source {
|
||||
($variant:ident) => {{
|
||||
let spec = (self.next_u8()? & 0xF0) >> 4;
|
||||
let source = self.next_source(spec)?;
|
||||
Ok(Inst::$variant(source))
|
||||
}};
|
||||
}
|
||||
macro_rules! dest {
|
||||
($variant:ident) => {{
|
||||
let spec = (self.next_u8()? & 0xF0) >> 4;
|
||||
let dest = self.next_dest(spec)?;
|
||||
Ok(Inst::$variant(dest))
|
||||
}};
|
||||
}
|
||||
let inst = match op {
|
||||
ADD => dest_source!(Add),
|
||||
SUB => dest_source!(Sub),
|
||||
MUL => dest_source!(Mul),
|
||||
DIV => dest_source!(Div),
|
||||
IDIV => dest_source!(IDiv),
|
||||
MOD => dest_source!(Mod),
|
||||
AND => dest_source!(And),
|
||||
OR => dest_source!(Or),
|
||||
XOR => dest_source!(Xor),
|
||||
SHL => dest_source!(Shl),
|
||||
SHR => dest_source!(Shr),
|
||||
INEG => dest_source!(INeg),
|
||||
INV => dest_source!(Inv),
|
||||
NOT => dest_source!(Not),
|
||||
CMPEQ => source_source!(CmpEq),
|
||||
CMPLT => source_source!(CmpLt),
|
||||
JMP => source!(Jmp),
|
||||
JZ => source!(Jz),
|
||||
JNZ => source!(Jnz),
|
||||
CALL => source!(Call),
|
||||
RET => Ok(Inst::Ret),
|
||||
PUSH => source!(Push),
|
||||
POP => dest!(Pop),
|
||||
INT => source_source!(Int),
|
||||
IRET => Ok(Inst::IRet),
|
||||
MOV => dest_source!(Mov),
|
||||
HALT => Ok(Inst::Halt),
|
||||
NOP => Ok(Inst::Nop),
|
||||
DUMP => Ok(Inst::Dump),
|
||||
_ => Err(VmError::from(InterruptError::IllegalOpcode { op, })),
|
||||
}?;
|
||||
let end = self.position();
|
||||
let len = (end - start) as usize;
|
||||
assert_eq!(len, inst.len());
|
||||
Ok(inst)
|
||||
}
|
||||
|
||||
fn next_source_source(&mut self) -> Result<(Source, Source)> {
|
||||
let spec = self.next_u8()?;
|
||||
let s1_spec = (spec & 0xF0) >> 4;
|
||||
let s2_spec = spec & 0x0F;
|
||||
let s1 = self.next_source(s1_spec)?;
|
||||
let s2 = self.next_source(s2_spec)?;
|
||||
Ok((s1, s2))
|
||||
}
|
||||
|
||||
fn next_dest_source(&mut self) -> Result<(Dest, Source)> {
|
||||
let spec = self.next_u8()?;
|
||||
let dest_spec = (spec & 0xF0) >> 4;
|
||||
let source_spec = spec & 0x0F;
|
||||
let dest = self.next_dest(dest_spec)?;
|
||||
let source = self.next_source(source_spec)?;
|
||||
Ok((dest, source))
|
||||
}
|
||||
|
||||
fn next_dest(&mut self, spec: u8) -> Result<Dest> {
|
||||
match spec {
|
||||
DEST_ADDR64 => Ok(Dest::Addr64(self.next_addr()?)),
|
||||
DEST_ADDR32 => Ok(Dest::Addr32(self.next_addr()?)),
|
||||
DEST_ADDR16 => Ok(Dest::Addr16(self.next_addr()?)),
|
||||
DEST_ADDR8 => Ok(Dest::Addr8(self.next_addr()?)),
|
||||
DEST_REG_ADDR64 => Ok(Dest::RegAddr64(self.next_reg()?)),
|
||||
DEST_REG_ADDR32 => Ok(Dest::RegAddr32(self.next_reg()?)),
|
||||
DEST_REG_ADDR16 => Ok(Dest::RegAddr16(self.next_reg()?)),
|
||||
DEST_REG_ADDR8 => Ok(Dest::RegAddr8(self.next_reg()?)),
|
||||
DEST_REG => Ok(Dest::Reg(self.next_reg()?)),
|
||||
_ => Err(InterruptError::IllegalDestSpec { spec }.into()),
|
||||
}
|
||||
}
|
||||
|
||||
fn next_source(&mut self, spec: u8) -> Result<Source> {
|
||||
match spec {
|
||||
SOURCE_ADDR64 => Ok(Source::Addr64(self.next_addr()?)),
|
||||
SOURCE_ADDR32 => Ok(Source::Addr32(self.next_addr()?)),
|
||||
SOURCE_ADDR16 => Ok(Source::Addr16(self.next_addr()?)),
|
||||
SOURCE_ADDR8 => Ok(Source::Addr8(self.next_addr()?)),
|
||||
SOURCE_REG_ADDR64 => Ok(Source::RegAddr64(self.next_reg()?)),
|
||||
SOURCE_REG_ADDR32 => Ok(Source::RegAddr32(self.next_reg()?)),
|
||||
SOURCE_REG_ADDR16 => Ok(Source::RegAddr16(self.next_reg()?)),
|
||||
SOURCE_REG_ADDR8 => Ok(Source::RegAddr8(self.next_reg()?)),
|
||||
SOURCE_REG => Ok(Source::Reg(self.next_reg()?)),
|
||||
SOURCE_IMM64 => Ok(Source::Imm(self.next_u64()?)),
|
||||
SOURCE_IMM32 => Ok(Source::Imm(self.next_u32()? as u64)),
|
||||
SOURCE_IMM16 => Ok(Source::Imm(self.next_u16()? as u64)),
|
||||
SOURCE_IMM8 => Ok(Source::Imm(self.next_u8()? as u64)),
|
||||
_ => Err(InterruptError::IllegalSourceSpec { spec }.into()),
|
||||
}
|
||||
}
|
||||
|
||||
fn next_reg(&mut self) -> Result<Reg> {
|
||||
let reg = self.next_u8()?;
|
||||
if (reg as usize) >= NUM_REGS {
|
||||
Err(InterruptError::IllegalReg { reg }.into())
|
||||
} else {
|
||||
Ok(reg)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> MemCursor<T>
|
||||
where
|
||||
T: AsRef<[u8]>,
|
||||
{
|
||||
fn check_addr(&self, addr: u64) -> Result<()> {
|
||||
if addr >= (self.cursor.get_ref().as_ref().len() as u64) {
|
||||
Err(InterruptError::MemOutOfBounds { addr: Addr(addr) }.into())
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> MemCursor<T>
|
||||
where
|
||||
Cursor<T>: WriteBytesExt,
|
||||
T: AsRef<[u8]>,
|
||||
{
|
||||
pub fn write_u8_unchecked(&mut self, value: u8) {
|
||||
self.cursor.write_u8(value).unwrap();
|
||||
}
|
||||
|
||||
pub fn write_u8(&mut self, value: u8) -> Result<()> {
|
||||
self.check_addr(self.position())
|
||||
.map(|_| self.write_u8_unchecked(value))
|
||||
}
|
||||
|
||||
pub fn write_u16_unchecked(&mut self, value: u16) {
|
||||
self.cursor.write_u16::<LE>(value).unwrap();
|
||||
}
|
||||
|
||||
pub fn write_u16(&mut self, value: u16) -> Result<()> {
|
||||
self.check_addr(self.position())
|
||||
.map(|_| self.write_u16_unchecked(value))
|
||||
}
|
||||
|
||||
pub fn write_u32_unchecked(&mut self, value: u32) {
|
||||
self.cursor.write_u32::<LE>(value).unwrap();
|
||||
}
|
||||
|
||||
pub fn write_u32(&mut self, value: u32) -> Result<()> {
|
||||
self.check_addr(self.position())
|
||||
.map(|_| self.write_u32_unchecked(value))
|
||||
}
|
||||
|
||||
pub fn write_u64_unchecked(&mut self, value: u64) {
|
||||
self.cursor.write_u64::<LE>(value).unwrap();
|
||||
}
|
||||
|
||||
pub fn write_u64(&mut self, value: u64) -> Result<()> {
|
||||
self.check_addr(self.position())
|
||||
.map(|_| self.write_u64_unchecked(value))
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// Index impl
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
impl<T: AsRef<[u8]>> Index<usize> for MemCursor<T> {
|
||||
type Output = u8;
|
||||
|
||||
fn index(&self, addr: usize) -> &Self::Output {
|
||||
self.mem.as_ref().index(addr)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: AsRef<[u8]>> Index<u64> for MemCursor<T> {
|
||||
type Output = u8;
|
||||
|
||||
fn index(&self, addr: u64) -> &Self::Output {
|
||||
self.index(addr as usize)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: AsRef<[u8]>> Index<Addr> for MemCursor<T> {
|
||||
type Output = u8;
|
||||
|
||||
fn index(&self, addr: Addr) -> &Self::Output {
|
||||
self.index(addr.0)
|
||||
}
|
||||
}
|
||||
*/
|
||||
398
src/libvm/src/obj/assemble.rs
Normal file
398
src/libvm/src/obj/assemble.rs
Normal file
@@ -0,0 +1,398 @@
|
||||
pub mod error;
|
||||
mod includes;
|
||||
mod names;
|
||||
pub mod session;
|
||||
|
||||
use self::{error::*, session::AsmSession};
|
||||
use crate::{
|
||||
addr::Addr,
|
||||
inst,
|
||||
interrupt::InterruptVector,
|
||||
obj::{
|
||||
obj::{self, Object},
|
||||
syn::ast::*,
|
||||
},
|
||||
};
|
||||
use byteorder::{WriteBytesExt, LE};
|
||||
use std::{collections::HashMap, path::Path};
|
||||
|
||||
pub trait Asm {
|
||||
type Out;
|
||||
fn assemble(&self, asm: &mut AsmSession) -> Result<Self::Out>;
|
||||
}
|
||||
|
||||
pub fn assemble_path(path: impl AsRef<Path>) -> Result<Object> {
|
||||
let mut session = AsmSession::default();
|
||||
session.include_path(path)?;
|
||||
session.assemble()
|
||||
}
|
||||
|
||||
impl Asm for Vec<&'_ Directive> {
|
||||
type Out = obj::Object;
|
||||
fn assemble(&self, asm: &mut AsmSession) -> Result<Self::Out> {
|
||||
let sections = self
|
||||
.iter()
|
||||
.filter_map(|section| section.assemble(asm).transpose())
|
||||
.collect::<Result<_>>()?;
|
||||
Ok(obj::Object {
|
||||
version: obj::OBJ_VERSION,
|
||||
sections,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Asm for Directive {
|
||||
type Out = Option<obj::Section>;
|
||||
|
||||
fn assemble(&self, asm: &mut AsmSession) -> Result<Self::Out> {
|
||||
match self {
|
||||
Directive::Data(section) => Ok(Some(obj::Section::Data(section.assemble(asm)?))),
|
||||
Directive::Meta(section) => section.assemble(asm).map(Some),
|
||||
Directive::Include(_) => Ok(None),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Asm for DataSection {
|
||||
type Out = obj::DataSection;
|
||||
|
||||
fn assemble(&self, session: &mut AsmSession) -> Result<Self::Out> {
|
||||
let names = names::get_section_names(self)?;
|
||||
session.name_stack.push(names);
|
||||
|
||||
let content_len = self.len() as u64;
|
||||
let (start, end) = match self.org {
|
||||
SectionOrg::Start(start) => (start, start + content_len),
|
||||
SectionOrg::StartEnd(start, end) => (start, end),
|
||||
};
|
||||
session.pos = start;
|
||||
if start > end {
|
||||
return Err(AsmError::StartGreaterThanEnd { start, end });
|
||||
}
|
||||
let len = end - start;
|
||||
if content_len > len {
|
||||
return Err(AsmError::SectionTooShort {
|
||||
name: self.name.clone(),
|
||||
section_end: end,
|
||||
section_size: start + content_len,
|
||||
});
|
||||
}
|
||||
|
||||
let mut contents = Vec::with_capacity(content_len as usize);
|
||||
for (pos, line) in self.lines(start) {
|
||||
let expected_len = pos - (start as usize);
|
||||
assert!(
|
||||
expected_len >= contents.len(),
|
||||
"next line size would cause section to shrink"
|
||||
);
|
||||
// resize contents if necessary, this pads out aligned values
|
||||
contents.resize(expected_len, 0);
|
||||
contents.extend(line.assemble(session)?);
|
||||
session.pos = pos as u64;
|
||||
}
|
||||
assert_eq!(
|
||||
contents.len() as u64,
|
||||
content_len,
|
||||
"in section {}",
|
||||
self.name
|
||||
);
|
||||
assert_eq!(session.pos - start, content_len, "in section {}", self.name);
|
||||
|
||||
session.name_stack.pop();
|
||||
Ok(obj::DataSection {
|
||||
name: self.name.clone(),
|
||||
start,
|
||||
len: content_len,
|
||||
contents,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Asm for MetaSection {
|
||||
type Out = obj::Section;
|
||||
|
||||
fn assemble(&self, session: &mut AsmSession) -> Result<Self::Out> {
|
||||
let mut entries = HashMap::new();
|
||||
for line in self.lines.iter() {
|
||||
if entries.contains_key(&line.name) {
|
||||
return Err(AsmError::DuplicateMetaName {
|
||||
name: line.name.to_string(),
|
||||
});
|
||||
}
|
||||
let value = match &line.value {
|
||||
Value::Int(i) => *i,
|
||||
Value::Name(s) => {
|
||||
session
|
||||
.lookup_name(s.as_str())
|
||||
.ok_or_else(|| AsmError::UnknownName {
|
||||
name: s.to_string(),
|
||||
})?
|
||||
.addr
|
||||
.0
|
||||
}
|
||||
Value::Reg(_) | Value::Here | Value::Addr(_, _) => {
|
||||
return Err(AsmError::IllegalMetaValue {
|
||||
name: line.name.to_string(),
|
||||
value: line.value.clone(),
|
||||
})
|
||||
} // TODO :
|
||||
// * deref constexpr?
|
||||
// * pre-startup static init?
|
||||
};
|
||||
entries.insert(line.name.to_string(), value);
|
||||
}
|
||||
Ok(obj::Section::Meta(obj::MetaSection { entries }))
|
||||
}
|
||||
}
|
||||
|
||||
impl Asm for DataLine {
|
||||
type Out = Vec<u8>;
|
||||
|
||||
fn assemble(&self, session: &mut AsmSession) -> Result<Self::Out> {
|
||||
match self {
|
||||
DataLine::ValueDef(v) => v.assemble(session),
|
||||
DataLine::Inst(i) => i.assemble(session),
|
||||
DataLine::Export(_) | DataLine::Label(_) => Ok(Vec::new()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Asm for ValueDef {
|
||||
type Out = Vec<u8>;
|
||||
|
||||
fn assemble(&self, session: &mut AsmSession) -> Result<Self::Out> {
|
||||
match self {
|
||||
ValueDef::Int(x, s) => Ok(x.to_le_bytes().iter().copied().take(s.len()).collect()),
|
||||
ValueDef::String(s) => {
|
||||
let bytes = s.bytes();
|
||||
let mut out = s.len().to_le_bytes().to_vec();
|
||||
out.extend(bytes);
|
||||
Ok(out)
|
||||
}
|
||||
ValueDef::ZString(z) => {
|
||||
let bytes = z.bytes();
|
||||
let mut out = z.len().to_le_bytes().to_vec();
|
||||
out.extend(bytes);
|
||||
Ok(out)
|
||||
}
|
||||
ValueDef::Interrupt(e, a) => {
|
||||
use std::convert::TryInto;
|
||||
let mut interrupt = InterruptVector::default();
|
||||
interrupt.set_enabled(*e);
|
||||
let mut addr_bytes = a.assemble(session)?;
|
||||
addr_bytes.resize(8, 0);
|
||||
let addr_bytes: [u8; 8] = addr_bytes.as_slice().try_into().unwrap();
|
||||
let addr = u64::from_ne_bytes(addr_bytes);
|
||||
interrupt.set_addr(Addr(addr));
|
||||
Ok(interrupt.to_vec())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Asm for Inst {
|
||||
type Out = Vec<u8>;
|
||||
|
||||
fn assemble(&self, session: &mut AsmSession) -> Result<Self::Out> {
|
||||
match self {
|
||||
Inst::Add(v1, v2) => self.map_dest_source(session, inst::ADD, v1, v2),
|
||||
Inst::Sub(v1, v2) => self.map_dest_source(session, inst::SUB, v1, v2),
|
||||
Inst::Mul(v1, v2) => self.map_dest_source(session, inst::MUL, v1, v2),
|
||||
Inst::Div(v1, v2) => self.map_dest_source(session, inst::DIV, v1, v2),
|
||||
Inst::IDiv(v1, v2) => self.map_dest_source(session, inst::IDIV, v1, v2),
|
||||
Inst::Mod(v1, v2) => self.map_dest_source(session, inst::MOD, v1, v2),
|
||||
Inst::And(v1, v2) => self.map_dest_source(session, inst::AND, v1, v2),
|
||||
Inst::Or(v1, v2) => self.map_dest_source(session, inst::OR, v1, v2),
|
||||
Inst::Xor(v1, v2) => self.map_dest_source(session, inst::XOR, v1, v2),
|
||||
Inst::Shl(v1, v2) => self.map_dest_source(session, inst::SHL, v1, v2),
|
||||
Inst::Shr(v1, v2) => self.map_dest_source(session, inst::SHR, v1, v2),
|
||||
Inst::INeg(v1, v2) => self.map_dest_source(session, inst::INEG, v1, v2),
|
||||
Inst::Inv(v1, v2) => self.map_dest_source(session, inst::INV, v1, v2),
|
||||
Inst::Not(v1, v2) => self.map_dest_source(session, inst::NOT, v1, v2),
|
||||
Inst::CmpEq(v1, v2) => self.map_source_source(session, inst::CMPEQ, v1, v2),
|
||||
Inst::CmpLt(v1, v2) => self.map_source_source(session, inst::CMPLT, v1, v2),
|
||||
Inst::Jmp(v) => self.map_source(session, inst::JMP, v),
|
||||
Inst::Jz(v) => self.map_source(session, inst::JZ, v),
|
||||
Inst::Jnz(v) => self.map_source(session, inst::JNZ, v),
|
||||
Inst::Call(v) => self.map_source(session, inst::CALL, v),
|
||||
Inst::Ret => self.map_inst(inst::RET),
|
||||
Inst::Push(v) => self.map_source(session, inst::PUSH, v),
|
||||
Inst::Pop(dest) => {
|
||||
let len = self.len();
|
||||
let mut bytes = Vec::with_capacity(len);
|
||||
bytes.write_u16::<LE>(inst::POP).unwrap();
|
||||
let dest_encoding = dest.source_encoding() << 4;
|
||||
bytes.write_u8(dest_encoding).unwrap();
|
||||
bytes.extend(dest.assemble(session)?);
|
||||
assert_eq!(len, bytes.len(),);
|
||||
Ok(bytes)
|
||||
}
|
||||
Inst::Int(v1, v2) => self.map_source_source(session, inst::INT, v1, v2),
|
||||
Inst::IRet => self.map_inst(inst::IRET),
|
||||
Inst::Mov(v1, v2) => self.map_dest_source(session, inst::MOV, v1, v2),
|
||||
Inst::Halt => self.map_inst(inst::HALT),
|
||||
Inst::Nop => self.map_inst(inst::NOP),
|
||||
Inst::Dump => self.map_inst(inst::DUMP),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Inst {
|
||||
fn map_dest_source(
|
||||
&self,
|
||||
session: &mut AsmSession,
|
||||
op: inst::InstOp,
|
||||
dest: &Value,
|
||||
source: &Value,
|
||||
) -> Result<Vec<u8>> {
|
||||
let len = self.len();
|
||||
let mut bytes = Vec::with_capacity(len);
|
||||
bytes.write_u16::<LE>(op).unwrap();
|
||||
let dest_encoding = dest
|
||||
.dest_encoding()
|
||||
.ok_or_else(|| AsmError::IllegalDestValue {
|
||||
value: dest.clone(),
|
||||
})?;
|
||||
let source_encoding = source.source_encoding();
|
||||
bytes
|
||||
.write_u8((dest_encoding << 4) | source_encoding)
|
||||
.unwrap();
|
||||
bytes.extend(dest.assemble(session)?);
|
||||
bytes.extend(source.assemble(session)?);
|
||||
assert_eq!(len, bytes.len(),);
|
||||
Ok(bytes)
|
||||
}
|
||||
|
||||
fn map_source_source(
|
||||
&self,
|
||||
session: &mut AsmSession,
|
||||
op: inst::InstOp,
|
||||
s1: &Value,
|
||||
s2: &Value,
|
||||
) -> Result<Vec<u8>> {
|
||||
let len = self.len();
|
||||
let mut bytes = Vec::with_capacity(len);
|
||||
bytes.write_u16::<LE>(op).unwrap();
|
||||
let s1_encoding = s1.source_encoding();
|
||||
let s2_encoding = s2.source_encoding();
|
||||
bytes.write_u8((s1_encoding << 4) | s2_encoding).unwrap();
|
||||
bytes.extend(s1.assemble(session)?);
|
||||
bytes.extend(s2.assemble(session)?);
|
||||
assert_eq!(len, bytes.len(),);
|
||||
Ok(bytes)
|
||||
}
|
||||
|
||||
fn map_source(
|
||||
&self,
|
||||
session: &mut AsmSession,
|
||||
op: inst::InstOp,
|
||||
source: &Value,
|
||||
) -> Result<Vec<u8>> {
|
||||
let len = self.len();
|
||||
let mut bytes = Vec::with_capacity(len);
|
||||
bytes.write_u16::<LE>(op).unwrap();
|
||||
let source_encoding = source.source_encoding() << 4;
|
||||
bytes.write_u8(source_encoding).unwrap();
|
||||
bytes.extend(source.assemble(session)?);
|
||||
assert_eq!(len, bytes.len());
|
||||
Ok(bytes)
|
||||
}
|
||||
|
||||
fn map_inst(&self, op: inst::InstOp) -> Result<Vec<u8>> {
|
||||
let len = self.len();
|
||||
let mut bytes = Vec::with_capacity(len);
|
||||
bytes.write_u16::<LE>(op).unwrap();
|
||||
assert_eq!(len, bytes.len(),);
|
||||
Ok(bytes)
|
||||
}
|
||||
}
|
||||
|
||||
impl Asm for Value {
|
||||
type Out = Vec<u8>;
|
||||
|
||||
fn assemble(&self, session: &mut AsmSession) -> Result<Self::Out> {
|
||||
match self {
|
||||
Value::Int(i) => Ok(i.to_le_bytes().to_vec()),
|
||||
Value::Reg(r) => Ok(vec![*r]),
|
||||
Value::Name(name) => {
|
||||
let value =
|
||||
session
|
||||
.lookup_name(name.as_str())
|
||||
.ok_or_else(|| AsmError::UnknownName {
|
||||
name: name.to_string(),
|
||||
})?;
|
||||
Ok(value.addr.0.to_le_bytes().to_vec())
|
||||
}
|
||||
Value::Here => Ok(session.pos.to_le_bytes().to_vec()),
|
||||
Value::Addr(v, _) => {
|
||||
if let Value::Addr(_, _) = &**v {
|
||||
// double deref is not allowed
|
||||
todo!()
|
||||
} else {
|
||||
v.assemble(session)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_inst_len() {
|
||||
let mut session = AsmSession::default();
|
||||
//asm.names
|
||||
//.push(vec![("test".to_string(), Addr(0u64))].into_iter().collect());
|
||||
|
||||
macro_rules! assert_len {
|
||||
($inst:expr) => {{
|
||||
let inst = $inst;
|
||||
let asm_size = $inst.assemble(&mut session).unwrap().len();
|
||||
assert_eq!(inst.len(), asm_size, "Instruction {:?}.len() indicates it should be {} bytes long but was assembled as {} bytes", inst, inst.len(), asm_size);
|
||||
}}
|
||||
}
|
||||
|
||||
use Inst::*;
|
||||
|
||||
let dummy_dests = &[
|
||||
Value::Reg(0),
|
||||
Value::Addr(Box::new(Value::Reg(0)), IntSize::U8),
|
||||
Value::Addr(Box::new(Value::Here), IntSize::U16),
|
||||
//Value::Addr(Box::new(Value::Name("test".to_string())), IntSize::U32),
|
||||
Value::Addr(Box::new(Value::Int(0)), IntSize::U64),
|
||||
];
|
||||
|
||||
let dummy_sources = &[
|
||||
Value::Int(0),
|
||||
Value::Reg(0),
|
||||
//Value::Name("test".to_string()),
|
||||
Value::Here,
|
||||
Value::Addr(Box::new(Value::Reg(0)), IntSize::U8),
|
||||
Value::Addr(Box::new(Value::Here), IntSize::U16),
|
||||
//Value::Addr(Box::new(Value::Name("test".to_string())), IntSize::U32),
|
||||
Value::Addr(Box::new(Value::Int(0)), IntSize::U32),
|
||||
];
|
||||
|
||||
for v1 in dummy_dests {
|
||||
for v2 in dummy_sources {
|
||||
assert_len!(Add(v1.clone(), v2.clone()));
|
||||
assert_len!(Sub(v1.clone(), v2.clone()));
|
||||
assert_len!(Mul(v1.clone(), v2.clone()));
|
||||
assert_len!(Div(v1.clone(), v2.clone()));
|
||||
assert_len!(Mod(v1.clone(), v2.clone()));
|
||||
assert_len!(And(v1.clone(), v2.clone()));
|
||||
assert_len!(Or(v1.clone(), v2.clone()));
|
||||
assert_len!(Xor(v1.clone(), v2.clone()));
|
||||
assert_len!(Shl(v1.clone(), v2.clone()));
|
||||
assert_len!(Shr(v1.clone(), v2.clone()));
|
||||
assert_len!(INeg(v1.clone(), v2.clone()));
|
||||
assert_len!(Inv(v1.clone(), v2.clone()));
|
||||
assert_len!(Not(v1.clone(), v2.clone()));
|
||||
assert_len!(Mov(v1.clone(), v2.clone()));
|
||||
// TODO more length tests
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
114
src/libvm/src/obj/assemble/error.rs
Normal file
114
src/libvm/src/obj/assemble/error.rs
Normal file
@@ -0,0 +1,114 @@
|
||||
use crate::obj::syn::ast::*;
|
||||
use snafu::Snafu;
|
||||
use std::path::PathBuf;
|
||||
|
||||
pub type LexError = lrpar::LexError;
|
||||
pub type ParseError = lrpar::ParseError<u32>;
|
||||
pub type LexParseError = lrpar::LexParseError<u32>;
|
||||
|
||||
#[derive(Debug, Snafu)]
|
||||
pub enum AsmError {
|
||||
#[snafu(display("unknown name: {}", name))]
|
||||
UnknownName {
|
||||
name: String,
|
||||
},
|
||||
|
||||
#[snafu(display("unknown export name: {}", name))]
|
||||
UnknownExport {
|
||||
name: String,
|
||||
},
|
||||
|
||||
#[snafu(display("duplicate label definition: {}", name))]
|
||||
DuplicateLabel {
|
||||
name: String,
|
||||
},
|
||||
|
||||
#[snafu(display("duplicate meta entry name: {}", name))]
|
||||
DuplicateMetaName {
|
||||
name: String,
|
||||
},
|
||||
|
||||
#[snafu(display("illegal meta value for entry name {}: {:?}", name, value))]
|
||||
IllegalMetaValue {
|
||||
name: String,
|
||||
value: Value,
|
||||
},
|
||||
|
||||
#[snafu(display("duplicate exported name: {}", name))]
|
||||
DuplicateExport {
|
||||
name: String,
|
||||
},
|
||||
|
||||
#[snafu(display("section start ({:#x}) is greater than end ({:#x})", start, end))]
|
||||
StartGreaterThanEnd {
|
||||
start: u64,
|
||||
end: u64,
|
||||
},
|
||||
|
||||
#[snafu(display(
|
||||
"section end ({:#x}) too short for section content size ({:#x})",
|
||||
section_end,
|
||||
section_size
|
||||
))]
|
||||
SectionTooShort {
|
||||
name: String,
|
||||
section_end: u64,
|
||||
section_size: u64,
|
||||
},
|
||||
|
||||
#[snafu(display("illegal instruction destination value: {:?}", value))]
|
||||
IllegalDestValue {
|
||||
value: Value,
|
||||
},
|
||||
|
||||
#[snafu(display("deref of a deref value is not allowed"))]
|
||||
DoubleDeref {
|
||||
value: Value,
|
||||
},
|
||||
|
||||
// TODO(asm) : Path error wrapper for assembling things
|
||||
#[snafu(display("could not read path: {}", path.display()))]
|
||||
BadPath {
|
||||
path: PathBuf,
|
||||
},
|
||||
|
||||
Syntax {
|
||||
source: SyntaxError,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Debug, Snafu)]
|
||||
pub enum SyntaxError {
|
||||
Lex { source: LexError },
|
||||
Parse { source: ParseError },
|
||||
Multi { errors: Vec<LexParseError> },
|
||||
}
|
||||
|
||||
impl From<LexError> for SyntaxError {
|
||||
fn from(source: LexError) -> Self {
|
||||
SyntaxError::Lex { source }
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ParseError> for SyntaxError {
|
||||
fn from(source: ParseError) -> Self {
|
||||
SyntaxError::Parse { source }
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Vec<LexParseError>> for SyntaxError {
|
||||
fn from(errors: Vec<LexParseError>) -> Self {
|
||||
SyntaxError::Multi { errors }
|
||||
}
|
||||
}
|
||||
|
||||
impl From<LexParseError> for SyntaxError {
|
||||
fn from(source: LexParseError) -> Self {
|
||||
match source {
|
||||
LexParseError::LexError(e) => SyntaxError::Lex { source: e },
|
||||
LexParseError::ParseError(e) => SyntaxError::Parse { source: e },
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub type Result<T, E = AsmError> = std::result::Result<T, E>;
|
||||
68
src/libvm/src/obj/assemble/includes.rs
Normal file
68
src/libvm/src/obj/assemble/includes.rs
Normal file
@@ -0,0 +1,68 @@
|
||||
use crate::obj::{
|
||||
assemble::{error::*, session::AsmSession, Asm},
|
||||
syn::{ast::Directive, lexer, parser},
|
||||
};
|
||||
use std::{path::PathBuf, rc::Rc};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct GetIncludes {
|
||||
path: PathBuf,
|
||||
}
|
||||
|
||||
impl GetIncludes {
|
||||
pub fn assemble_from_path(path: PathBuf, session: &mut AsmSession) -> Result<()> {
|
||||
let path = path
|
||||
.canonicalize()
|
||||
.map_err(|_| AsmError::BadPath { path })?;
|
||||
GetIncludes { path }.assemble(session)
|
||||
}
|
||||
}
|
||||
|
||||
impl Asm for GetIncludes {
|
||||
type Out = ();
|
||||
|
||||
fn assemble(&self, session: &mut AsmSession) -> Result<Self::Out> {
|
||||
assert!(self.path.is_absolute());
|
||||
if session.includes.contains_key(&self.path) {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let text = std::fs::read_to_string(&self.path).map_err(|_| AsmError::BadPath {
|
||||
path: self.path.clone(),
|
||||
})?;
|
||||
|
||||
let lexerdef = lexer::lexerdef();
|
||||
let lexer = lexerdef.lexer(&text);
|
||||
let (res, errors) = parser::parse(&lexer);
|
||||
|
||||
if !errors.is_empty() {
|
||||
return Err(AsmError::Syntax {
|
||||
source: errors.into(),
|
||||
});
|
||||
}
|
||||
|
||||
// insert a dummy AST in the includes and replace it when we're done with the actual AST
|
||||
Rc::get_mut(&mut session.includes)
|
||||
.unwrap()
|
||||
.insert(self.path.clone(), Default::default());
|
||||
session.include_stack.push(self.path.clone());
|
||||
let ast = res.unwrap();
|
||||
for directive in ast.iter() {
|
||||
if let Directive::Include(include_path) = directive {
|
||||
GetIncludes::assemble_from_path(PathBuf::from(include_path), session)?;
|
||||
}
|
||||
}
|
||||
session.include_stack.pop();
|
||||
|
||||
let dummy = Rc::get_mut(&mut session.includes)
|
||||
.unwrap()
|
||||
.insert(self.path.clone(), ast)
|
||||
.expect("ast was removed after its inclusion (why did you do that?)");
|
||||
assert!(
|
||||
dummy.is_empty(),
|
||||
"ast was modified after its inclusion (why did you do that?)"
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
113
src/libvm/src/obj/assemble/names.rs
Normal file
113
src/libvm/src/obj/assemble/names.rs
Normal file
@@ -0,0 +1,113 @@
|
||||
use crate::{
|
||||
addr::Addr,
|
||||
obj::{
|
||||
assemble::{error::*, session::AsmSession},
|
||||
syn::ast::{DataLine, DataSection, Directive},
|
||||
},
|
||||
};
|
||||
use std::{
|
||||
collections::{HashMap, HashSet},
|
||||
rc::Rc,
|
||||
};
|
||||
|
||||
// TODO(asm) make custom Names type that has "merge" that will catch errors with duplicate names
|
||||
pub type Names = HashMap<String, Name>;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct Name {
|
||||
pub name: String,
|
||||
pub addr: Addr,
|
||||
pub export: bool,
|
||||
}
|
||||
|
||||
pub fn get_section_names(section: &DataSection) -> Result<Names> {
|
||||
let mut names = HashMap::new();
|
||||
let mut exports = HashSet::new();
|
||||
|
||||
// This isn't immediately straightforward in code, so this is what's happening.
|
||||
//
|
||||
// A name may be specified exactly once. It is added to the "names" mapping. If the name is
|
||||
// specified again, it is a duplicate error and handled as such. A name may be flagged as
|
||||
// being exported.
|
||||
//
|
||||
// Exports are gathered at the beginning into a set. Afterwards, as name definitions
|
||||
// are gathered, exports are removed from their set if they exist. Since duplicate names
|
||||
// cause an error, export names will only be removed once.
|
||||
//
|
||||
// At the end of name gathering, if any exports have not been removed, then we know those
|
||||
// are exports whose names are undefined and we can return an UnknownExport error.
|
||||
|
||||
let start = section.org.start();
|
||||
|
||||
// get exported names
|
||||
for (_, line) in section.lines(start) {
|
||||
if let DataLine::Export(name) = line {
|
||||
exports.insert(name);
|
||||
}
|
||||
}
|
||||
|
||||
// get names
|
||||
for (pos, line) in section.lines(start) {
|
||||
if let DataLine::Label(name) = line {
|
||||
if names.contains_key(name) {
|
||||
return Err(AsmError::DuplicateLabel { name: name.clone() });
|
||||
}
|
||||
let export = exports.remove(name);
|
||||
|
||||
names.insert(
|
||||
name.clone(),
|
||||
Name {
|
||||
name: name.clone(),
|
||||
addr: Addr(pos as u64),
|
||||
export,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// all exports map 1:1 with names
|
||||
if exports.is_empty() {
|
||||
Ok(names)
|
||||
} else {
|
||||
Err(AsmError::UnknownExport {
|
||||
name: exports.iter().next().unwrap().to_string(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_exports(session: &mut AsmSession) -> Result<Names> {
|
||||
let includes = Rc::clone(&session.includes);
|
||||
let mut all_exports = Vec::new();
|
||||
|
||||
// get *all* exports first
|
||||
for (_, ast) in includes.iter() {
|
||||
for directive in ast.iter() {
|
||||
if let Directive::Data(section) = directive {
|
||||
let names: HashMap<_, _> = get_section_names(section)?
|
||||
.into_iter()
|
||||
.filter(|(_, name)| name.export)
|
||||
.collect();
|
||||
all_exports.push(names);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut exports = HashSet::new();
|
||||
|
||||
// make a set of all export names, erroring on duplicate export
|
||||
for name_map in all_exports.iter() {
|
||||
let names_set: HashSet<_> = name_map.keys().collect();
|
||||
if let Some(dupe) = exports.intersection(&names_set).next() {
|
||||
return Err(AsmError::DuplicateExport {
|
||||
name: dupe.to_string(),
|
||||
});
|
||||
}
|
||||
exports.extend(names_set);
|
||||
}
|
||||
|
||||
// NOTE: this can probably be done with a fancy combinator chain
|
||||
Ok(all_exports.into_iter().fold(Names::new(), |mut acc, val| {
|
||||
acc.extend(val);
|
||||
acc
|
||||
}))
|
||||
}
|
||||
69
src/libvm/src/obj/assemble/session.rs
Normal file
69
src/libvm/src/obj/assemble/session.rs
Normal file
@@ -0,0 +1,69 @@
|
||||
use crate::obj::{
|
||||
assemble::{
|
||||
error::*,
|
||||
includes::GetIncludes,
|
||||
names::{self, Name, Names},
|
||||
Asm,
|
||||
},
|
||||
obj::Object,
|
||||
syn::ast::Ast,
|
||||
};
|
||||
use std::{
|
||||
collections::BTreeMap,
|
||||
path::{Path, PathBuf},
|
||||
rc::Rc,
|
||||
};
|
||||
|
||||
/// A shared session for the assembler.
|
||||
#[derive(Debug, Default)]
|
||||
pub struct AsmSession {
|
||||
pub(super) includes: Rc<BTreeMap<PathBuf, Ast>>,
|
||||
pub(super) include_search_paths: Vec<PathBuf>,
|
||||
pub(super) include_stack: Vec<PathBuf>,
|
||||
pub(super) name_stack: Vec<Names>,
|
||||
pub(super) pos: u64,
|
||||
}
|
||||
|
||||
impl AsmSession {
|
||||
pub fn assemble(&mut self) -> Result<Object> {
|
||||
let includes = Rc::clone(&self.includes);
|
||||
let sections: Vec<_> = includes.iter().flat_map(|(_, ast)| ast).collect();
|
||||
sections.assemble(self)
|
||||
}
|
||||
|
||||
pub fn include_path(&mut self, path: impl AsRef<Path>) -> Result<()> {
|
||||
self.include_stack.clear();
|
||||
let path = path.as_ref().to_path_buf();
|
||||
|
||||
GetIncludes::assemble_from_path(path, self)?;
|
||||
let exports = names::get_exports(self)?;
|
||||
// TODO(asm) - merge exports
|
||||
self.name_stack.push(exports);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(super) fn current_include_path(&self) -> Option<&Path> {
|
||||
self.include_stack.last().map(PathBuf::as_path)
|
||||
}
|
||||
|
||||
pub(super) fn resolve_include_path(&self, path: impl AsRef<Path>) -> Option<PathBuf> {
|
||||
let path = path.as_ref();
|
||||
self.current_include_path()
|
||||
.and_then(|last_path| last_path.parent())
|
||||
.map(|last_dir| last_dir.join(path))
|
||||
.or_else(|| {
|
||||
self.include_search_paths
|
||||
.iter()
|
||||
.filter_map(|include| include.join(path).canonicalize().ok())
|
||||
.next()
|
||||
})
|
||||
}
|
||||
|
||||
pub fn lookup_name(&self, name: &str) -> Option<&Name> {
|
||||
self.name_stack
|
||||
.iter()
|
||||
.rev()
|
||||
.filter_map(|names| names.get(name))
|
||||
.next()
|
||||
}
|
||||
}
|
||||
116
src/libvm/src/obj/disassemble.rs
Normal file
116
src/libvm/src/obj/disassemble.rs
Normal file
@@ -0,0 +1,116 @@
|
||||
use crate::{mem::MemCursor, obj::obj::*};
|
||||
use prettytable::{cell, row, Table};
|
||||
use std::io::{self, Write};
|
||||
|
||||
const SEP: &str =
|
||||
"================================================================================";
|
||||
|
||||
pub trait Disasm {
|
||||
fn disasm(&self, writer: &mut dyn Write) -> io::Result<()>;
|
||||
}
|
||||
|
||||
impl Disasm for Vec<u8> {
|
||||
fn disasm(&self, writer: &mut dyn Write) -> io::Result<()> {
|
||||
let obj = Object::from_bytes(self.as_slice()).expect("invalid object bytes");
|
||||
obj.disasm(writer)
|
||||
}
|
||||
}
|
||||
|
||||
impl Disasm for Object {
|
||||
fn disasm(&self, writer: &mut dyn Write) -> io::Result<()> {
|
||||
for section in self.sections.iter() {
|
||||
section.disasm(writer)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Disasm for Section {
|
||||
fn disasm(&self, writer: &mut dyn Write) -> io::Result<()> {
|
||||
match self {
|
||||
Section::Data(s) => s.disasm(writer),
|
||||
Section::Meta(s) => s.disasm(writer),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO :
|
||||
// Instruction decoding is borked and I don't know why
|
||||
// I think it has to do with the DataSection::lines() method, because that's the change that
|
||||
// introduced it. The 0xdeadbeef program is only getting the value "200" in %r0 when it goes to do
|
||||
// the comparison, so for some reason that value is being put in there. The disassembler is an
|
||||
// attempt to make things slightly more readable while I debug.
|
||||
//
|
||||
// It appears that instructions are being encoded correctly (at least, as far as instructions
|
||||
// themselves go) so we will have to look deeper.
|
||||
|
||||
impl Disasm for DataSection {
|
||||
fn disasm(&self, writer: &mut dyn Write) -> io::Result<()> {
|
||||
writeln!(writer, "{}", SEP)?;
|
||||
writeln!(writer, "= DATA SECTION - {}", self.name)?;
|
||||
writeln!(writer, "{}", SEP)?;
|
||||
let mut table = Table::new();
|
||||
table.add_row(row!["Address", "Bytes", "Info"]);
|
||||
let mut cursor = MemCursor::new(self.contents.as_slice());
|
||||
loop {
|
||||
if cursor.is_end() {
|
||||
break;
|
||||
}
|
||||
let cursor_pos = cursor.position();
|
||||
let pos = self.start + cursor_pos;
|
||||
if let Ok(inst) = cursor.next_inst() {
|
||||
let start = cursor_pos as usize;
|
||||
let end = start + inst.len();
|
||||
let data = &self.contents.as_slice()[start..end];
|
||||
table.add_row(row![
|
||||
format!("{:016x} <{}+{:x}>", pos, self.name, cursor_pos),
|
||||
bytes_hex(data),
|
||||
format!("{:?}", inst),
|
||||
]);
|
||||
} else {
|
||||
let mut count = 0;
|
||||
let mut lookahead = MemCursor::new(self.contents.as_slice());
|
||||
lookahead.set_position(cursor_pos);
|
||||
while !lookahead.is_end() && lookahead.next_inst().is_err() {
|
||||
count += 1;
|
||||
lookahead.next_u8_unchecked();
|
||||
}
|
||||
let bytes = cursor.next_bytes(count).unwrap();
|
||||
table.add_row(row![
|
||||
format!("{:016x} <{}+{:x}>", pos, self.name, cursor_pos),
|
||||
bytes_hex(bytes),
|
||||
"",
|
||||
]);
|
||||
}
|
||||
}
|
||||
table.print(writer)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Disasm for MetaSection {
|
||||
fn disasm(&self, writer: &mut dyn Write) -> io::Result<()> {
|
||||
let mut table = Table::new();
|
||||
writeln!(writer, "{}", SEP)?;
|
||||
writeln!(writer, "= META SECTION")?;
|
||||
writeln!(writer, "{}", SEP)?;
|
||||
table.add_row(row!["Name", "Value"]);
|
||||
for (name, value) in self.entries.iter() {
|
||||
table.add_row(row![name, format!("0x{:016x}", value)]);
|
||||
}
|
||||
table.print(writer)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn bytes_hex(bytes: &[u8]) -> String {
|
||||
let mut out = String::new();
|
||||
for b in bytes.iter() {
|
||||
out += &format!("{:02x} ", b);
|
||||
}
|
||||
if !out.is_empty() {
|
||||
out.pop();
|
||||
}
|
||||
out
|
||||
}
|
||||
@@ -1,3 +1,5 @@
|
||||
pub mod assemble;
|
||||
pub mod disassemble;
|
||||
pub mod error;
|
||||
pub mod obj;
|
||||
pub mod syn;
|
||||
271
src/libvm/src/obj/obj.rs
Normal file
271
src/libvm/src/obj/obj.rs
Normal file
@@ -0,0 +1,271 @@
|
||||
use crate::obj::error::{ParseError, Result};
|
||||
use byteorder::{ReadBytesExt, WriteBytesExt, LE};
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
convert::{TryFrom, TryInto},
|
||||
fmt::Debug,
|
||||
io::{Cursor, Read, Write},
|
||||
};
|
||||
|
||||
pub const MAGIC: u64 = 0xDEAD_BEA7_BA5E_BA11;
|
||||
pub const OBJ_VERSION: u32 = 0;
|
||||
const OBJECT_HEADER_LEN: usize = 16; // 8 + 4 + 4
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct Object {
|
||||
pub version: u32,
|
||||
pub sections: Vec<Section>,
|
||||
}
|
||||
|
||||
impl Object {
|
||||
pub fn to_bytes(&self) -> Vec<u8> {
|
||||
let mut cursor = Cursor::new(Vec::new());
|
||||
cursor.write_u64::<LE>(MAGIC).unwrap();
|
||||
cursor.write_u32::<LE>(OBJ_VERSION).unwrap();
|
||||
cursor.write_u32::<LE>(self.sections.len() as u32).unwrap();
|
||||
for section in self.sections.iter() {
|
||||
cursor.write(§ion.to_bytes()).unwrap();
|
||||
}
|
||||
cursor.into_inner()
|
||||
}
|
||||
|
||||
pub fn from_bytes(bytes: &[u8]) -> Result<Self> {
|
||||
let mut cursor = Cursor::new(bytes);
|
||||
let magic = cursor.read_u64::<LE>()?;
|
||||
if magic != MAGIC {
|
||||
return Err(ParseError::WrongMagic);
|
||||
}
|
||||
let version = cursor.read_u32::<LE>()?;
|
||||
let section_count = cursor.read_u32::<LE>()?;
|
||||
|
||||
let mut sections = Vec::new();
|
||||
for _ in 0..section_count {
|
||||
let start = cursor.position() as usize;
|
||||
let section = Section::from_bytes(&bytes[start..])?;
|
||||
cursor.set_position((start + section.len()) as u64);
|
||||
sections.push(section);
|
||||
}
|
||||
Ok(Object { version, sections })
|
||||
}
|
||||
|
||||
pub fn virtual_len(&self) -> usize {
|
||||
self.sections
|
||||
.iter()
|
||||
.map(|s| match s {
|
||||
Section::Data(DataSection { start, len, .. }) => (start + len) as usize,
|
||||
Section::Meta { .. } => 0,
|
||||
})
|
||||
.max()
|
||||
.unwrap_or(0)
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! section_kind {
|
||||
(
|
||||
pub enum $enum_name:ident {
|
||||
$($name:ident = $value:expr),* $(,)?
|
||||
}
|
||||
) => {
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
#[repr(u8)]
|
||||
pub enum $enum_name {
|
||||
$($name = $value),*
|
||||
}
|
||||
|
||||
impl TryFrom<u8> for SectionKind {
|
||||
type Error = ParseError;
|
||||
|
||||
fn try_from(other: u8) -> std::result::Result<Self, Self::Error> {
|
||||
match other {
|
||||
$(
|
||||
$value => Ok($enum_name::$name),
|
||||
)*
|
||||
_ => Err(ParseError::UnknownSectionKind { kind: other }),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<SectionKind> for u8 {
|
||||
fn from(other: SectionKind) -> Self {
|
||||
match other {
|
||||
$(
|
||||
$enum_name::$name => $value,
|
||||
)*
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
section_kind! {
|
||||
pub enum SectionKind {
|
||||
Data = 0x00,
|
||||
Meta = 0xFF,
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum Section {
|
||||
Data(DataSection),
|
||||
Meta(MetaSection),
|
||||
}
|
||||
|
||||
impl Section {
|
||||
pub fn len(&self) -> usize {
|
||||
match self {
|
||||
Section::Data(s) => 1 + 8 + s.len(),
|
||||
Section::Meta(s) => 1 + 8 + s.len(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_bytes(&self) -> Vec<u8> {
|
||||
let mut cursor = Cursor::new(Vec::new());
|
||||
let bytes = match self {
|
||||
Section::Data(s) => {
|
||||
cursor.write_u8(SectionKind::Data.into()).unwrap();
|
||||
s.to_bytes()
|
||||
}
|
||||
Section::Meta(s) => {
|
||||
cursor.write_u8(SectionKind::Meta.into()).unwrap();
|
||||
s.to_bytes()
|
||||
}
|
||||
};
|
||||
cursor.write_u64::<LE>(bytes.len() as u64).unwrap();
|
||||
cursor.write(&bytes).unwrap();
|
||||
cursor.into_inner()
|
||||
}
|
||||
|
||||
pub fn from_bytes(bytes: &[u8]) -> Result<Self> {
|
||||
let mut cursor = Cursor::new(bytes);
|
||||
let kind: SectionKind = cursor.read_u8()?.try_into()?;
|
||||
let len = cursor.read_u64::<LE>()?;
|
||||
let start = cursor.position() as usize;
|
||||
let end = start + len as usize;
|
||||
|
||||
let bytes = &cursor.get_ref()[start..end];
|
||||
match kind {
|
||||
SectionKind::Data => Ok(Section::Data(DataSection::from_bytes(bytes)?)),
|
||||
SectionKind::Meta => Ok(Section::Meta(MetaSection::from_bytes(bytes)?)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct DataSection {
|
||||
pub name: String,
|
||||
pub start: u64,
|
||||
pub len: u64,
|
||||
pub contents: Vec<u8>,
|
||||
}
|
||||
|
||||
impl DataSection {
|
||||
pub fn len(&self) -> usize {
|
||||
2 + self.name.as_bytes().len() + 8 + 8 + self.contents.len()
|
||||
}
|
||||
|
||||
pub fn to_bytes(&self) -> Vec<u8> {
|
||||
let mut cursor = Cursor::new(Vec::new());
|
||||
assert!(self.name.len() < u16::max_value() as usize);
|
||||
cursor.write_u16::<LE>(self.name.len() as u16).unwrap();
|
||||
cursor.write(self.name.as_bytes()).unwrap();
|
||||
cursor.write_u64::<LE>(self.start).unwrap();
|
||||
cursor.write_u64::<LE>(self.len).unwrap();
|
||||
cursor.write(&self.contents).unwrap();
|
||||
cursor.into_inner()
|
||||
}
|
||||
|
||||
pub fn from_bytes(bytes: &[u8]) -> Result<Self> {
|
||||
let mut cursor = Cursor::new(bytes);
|
||||
|
||||
let name_len = cursor.read_u16::<LE>()? as usize;
|
||||
let name_start = cursor.position() as usize;
|
||||
let name_end = name_start + name_len;
|
||||
let name_bytes = &bytes[name_start..name_end];
|
||||
let name_string = String::from_utf8(name_bytes.to_vec())?;
|
||||
cursor.set_position(name_end as u64);
|
||||
|
||||
let start = cursor.read_u64::<LE>()?;
|
||||
let len = cursor.read_u64::<LE>()?;
|
||||
|
||||
let contents = &bytes[cursor.position() as usize..];
|
||||
Ok(DataSection {
|
||||
name: name_string,
|
||||
start,
|
||||
len,
|
||||
contents: From::from(contents),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct MetaSection {
|
||||
pub entries: HashMap<String, u64>,
|
||||
}
|
||||
|
||||
impl MetaSection {
|
||||
pub fn len(&self) -> usize {
|
||||
8 + self
|
||||
.entries
|
||||
.iter()
|
||||
.map(|(k, _)| 8 + k.as_bytes().len() + 8)
|
||||
.sum::<usize>()
|
||||
}
|
||||
|
||||
pub fn to_bytes(&self) -> Vec<u8> {
|
||||
let mut cursor = Cursor::new(Vec::new());
|
||||
cursor.write_u64::<LE>(self.entries.len() as u64).unwrap();
|
||||
for (k, v) in self.entries.iter() {
|
||||
let key_len = k.as_bytes().len();
|
||||
cursor.write_u64::<LE>(key_len as u64).unwrap();
|
||||
cursor.write(k.as_bytes()).unwrap();
|
||||
cursor.write_u64::<LE>(*v).unwrap();
|
||||
}
|
||||
cursor.into_inner()
|
||||
}
|
||||
|
||||
pub fn from_bytes(bytes: &[u8]) -> Result<Self> {
|
||||
let mut cursor = Cursor::new(bytes);
|
||||
let entry_count = cursor.read_u64::<LE>()?;
|
||||
let mut entries = HashMap::new();
|
||||
for _ in 0..entry_count {
|
||||
// key
|
||||
let key_len = cursor.read_u64::<LE>()?;
|
||||
let mut key_bytes = vec![0u8; key_len as usize];
|
||||
cursor.read_exact(&mut key_bytes)?;
|
||||
let key = String::from_utf8(key_bytes)?;
|
||||
|
||||
// value
|
||||
let value = cursor.read_u64::<LE>()?;
|
||||
entries.insert(key, value);
|
||||
}
|
||||
Ok(MetaSection { entries })
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_to_from_bytes() {
|
||||
let obj = Object {
|
||||
version: OBJ_VERSION,
|
||||
sections: vec![
|
||||
Section::Data(DataSection {
|
||||
name: "data".to_string(),
|
||||
start: 0,
|
||||
len: 16,
|
||||
contents: vec![0u8; 16],
|
||||
}),
|
||||
Section::Meta(MetaSection {
|
||||
entries: Default::default(),
|
||||
}),
|
||||
],
|
||||
};
|
||||
|
||||
let obj_bytes = obj.to_bytes();
|
||||
let converted = Object::from_bytes(&obj_bytes).unwrap();
|
||||
assert_eq!(obj, converted);
|
||||
}
|
||||
}
|
||||
316
src/libvm/src/obj/syn/ast.rs
Normal file
316
src/libvm/src/obj/syn/ast.rs
Normal file
@@ -0,0 +1,316 @@
|
||||
use crate::{inst, interrupt::InterruptVector, reg::Reg};
|
||||
|
||||
pub type Ast = Vec<Directive>;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum Directive {
|
||||
Meta(MetaSection),
|
||||
Data(DataSection),
|
||||
Include(String),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct MetaSection {
|
||||
pub lines: Vec<MetaLine>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct MetaLine {
|
||||
pub name: String,
|
||||
pub value: Value,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct DataSection {
|
||||
pub name: String,
|
||||
pub org: SectionOrg,
|
||||
pub blocks: Vec<AlignedBlock>,
|
||||
}
|
||||
|
||||
impl DataSection {
|
||||
pub fn len(&self) -> usize {
|
||||
let start = self.org.start();
|
||||
let mut end = start;
|
||||
for (pos, _) in self.lines(start) {
|
||||
end = pos as u64;
|
||||
}
|
||||
(end - start) as usize
|
||||
}
|
||||
|
||||
pub fn lines<'a>(&'a self, start: u64) -> DataLines<'a> {
|
||||
DataLines::new(&self.blocks, start)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct DataLines<'a> {
|
||||
blocks: &'a Vec<AlignedBlock>,
|
||||
block_idx: usize,
|
||||
line_idx: usize,
|
||||
pos: usize,
|
||||
}
|
||||
|
||||
impl<'a> DataLines<'a> {
|
||||
fn new(blocks: &'a Vec<AlignedBlock>, start: u64) -> Self {
|
||||
DataLines {
|
||||
blocks,
|
||||
block_idx: 0,
|
||||
line_idx: 0,
|
||||
pos: start as usize,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Iterator for DataLines<'a> {
|
||||
type Item = (usize, &'a DataLine);
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
if self.block_idx >= self.blocks.len() {
|
||||
return None;
|
||||
}
|
||||
let block = &self.blocks[self.block_idx];
|
||||
if self.line_idx >= block.block.len() {
|
||||
// next block - advance the position by the padding amount
|
||||
self.block_idx += 1;
|
||||
self.line_idx = 0;
|
||||
// if there's a next block, update the padding
|
||||
if self.block_idx < self.blocks.len() {
|
||||
let block = &self.blocks[self.block_idx];
|
||||
self.pos += block.padding_for(self.pos);
|
||||
}
|
||||
self.next()
|
||||
} else {
|
||||
let pos = self.pos;
|
||||
let line = &block.block[self.line_idx];
|
||||
self.line_idx += 1;
|
||||
self.pos += line.len();
|
||||
Some((pos, line))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum SectionOrg {
|
||||
Start(u64),
|
||||
StartEnd(u64, u64),
|
||||
}
|
||||
|
||||
impl SectionOrg {
|
||||
pub fn start(&self) -> u64 {
|
||||
match self {
|
||||
SectionOrg::Start(start) | SectionOrg::StartEnd(start, _) => *start,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct AlignedBlock {
|
||||
pub alignment: IntSize,
|
||||
pub block: Vec<DataLine>,
|
||||
}
|
||||
|
||||
impl AlignedBlock {
|
||||
pub fn padding_for(&self, len: usize) -> usize {
|
||||
let align = self.alignment.len();
|
||||
let padding = len % align;
|
||||
if padding == 0 {
|
||||
padding
|
||||
} else {
|
||||
align - padding
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum DataLine {
|
||||
ValueDef(ValueDef),
|
||||
Inst(Inst),
|
||||
Export(String),
|
||||
Label(String),
|
||||
}
|
||||
|
||||
impl DataLine {
|
||||
pub fn len(&self) -> usize {
|
||||
match self {
|
||||
DataLine::ValueDef(v) => v.len(),
|
||||
DataLine::Inst(i) => i.len(),
|
||||
DataLine::Export(_) | DataLine::Label(_) => 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum ValueDef {
|
||||
Int(u64, IntSize),
|
||||
String(String),
|
||||
ZString(String),
|
||||
Interrupt(bool, Value),
|
||||
}
|
||||
|
||||
impl ValueDef {
|
||||
pub fn len(&self) -> usize {
|
||||
match self {
|
||||
ValueDef::Int(_, s) => s.len(),
|
||||
ValueDef::String(s) => 8 + s.as_bytes().len(),
|
||||
ValueDef::ZString(s) => s.as_bytes().len() + 1,
|
||||
ValueDef::Interrupt(_, _) => std::mem::size_of::<InterruptVector>(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum Value {
|
||||
// TODO : immediate int sizes
|
||||
// Int(u64, IntSize)
|
||||
Int(u64),
|
||||
Reg(Reg),
|
||||
Name(String),
|
||||
Here,
|
||||
Addr(Box<Value>, IntSize),
|
||||
}
|
||||
|
||||
impl Value {
|
||||
pub fn len(&self) -> usize {
|
||||
match self {
|
||||
Value::Int(_) => 8,
|
||||
Value::Reg(_) => 1,
|
||||
Value::Name(_) => 8,
|
||||
Value::Here => 8,
|
||||
Value::Addr(v, _) => v.len(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn dest_encoding(&self) -> Option<u8> {
|
||||
match self {
|
||||
Value::Int(_) | Value::Name(_) | Value::Here => None,
|
||||
Value::Reg(_) => Some(inst::DEST_REG),
|
||||
// TODO : check reg vs int value, and use dest_reg_addr8/16/32/64 values
|
||||
Value::Addr(v, size) => {
|
||||
if let Value::Reg(_) = &**v {
|
||||
match size {
|
||||
IntSize::U64 => Some(inst::DEST_REG_ADDR64),
|
||||
IntSize::U32 => Some(inst::DEST_REG_ADDR32),
|
||||
IntSize::U16 => Some(inst::DEST_REG_ADDR16),
|
||||
IntSize::U8 => Some(inst::DEST_REG_ADDR8),
|
||||
}
|
||||
} else {
|
||||
match size {
|
||||
IntSize::U64 => Some(inst::DEST_ADDR64),
|
||||
IntSize::U32 => Some(inst::DEST_ADDR32),
|
||||
IntSize::U16 => Some(inst::DEST_ADDR16),
|
||||
IntSize::U8 => Some(inst::DEST_ADDR8),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn source_encoding(&self) -> u8 {
|
||||
match self {
|
||||
Value::Int(_) => inst::SOURCE_IMM64,
|
||||
Value::Reg(_) => inst::SOURCE_REG,
|
||||
// TODO : check reg vs int value, and use source_reg_addr8/16/32/64 values
|
||||
Value::Name(_) | Value::Here => inst::SOURCE_IMM64,
|
||||
Value::Addr(v, size) => {
|
||||
if let Value::Reg(_) = &**v {
|
||||
match size {
|
||||
IntSize::U64 => inst::SOURCE_REG_ADDR64,
|
||||
IntSize::U32 => inst::SOURCE_REG_ADDR32,
|
||||
IntSize::U16 => inst::SOURCE_REG_ADDR16,
|
||||
IntSize::U8 => inst::SOURCE_REG_ADDR8,
|
||||
}
|
||||
} else {
|
||||
match size {
|
||||
IntSize::U64 => inst::SOURCE_ADDR64,
|
||||
IntSize::U32 => inst::SOURCE_ADDR32,
|
||||
IntSize::U16 => inst::SOURCE_ADDR16,
|
||||
IntSize::U8 => inst::SOURCE_ADDR8,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum IntSize {
|
||||
U8,
|
||||
U16,
|
||||
U32,
|
||||
U64,
|
||||
}
|
||||
|
||||
impl IntSize {
|
||||
pub fn len(&self) -> usize {
|
||||
match self {
|
||||
IntSize::U8 => 1,
|
||||
IntSize::U16 => 2,
|
||||
IntSize::U32 => 4,
|
||||
IntSize::U64 => 8,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum Inst {
|
||||
Add(Value, Value),
|
||||
Sub(Value, Value),
|
||||
Mul(Value, Value),
|
||||
Div(Value, Value),
|
||||
IDiv(Value, Value),
|
||||
Mod(Value, Value),
|
||||
And(Value, Value),
|
||||
Or(Value, Value),
|
||||
Xor(Value, Value),
|
||||
Shl(Value, Value),
|
||||
Shr(Value, Value),
|
||||
INeg(Value, Value),
|
||||
Inv(Value, Value),
|
||||
Not(Value, Value),
|
||||
CmpEq(Value, Value),
|
||||
CmpLt(Value, Value),
|
||||
Jmp(Value),
|
||||
Jz(Value),
|
||||
Jnz(Value),
|
||||
Call(Value),
|
||||
Ret,
|
||||
Push(Value),
|
||||
Pop(Value),
|
||||
Int(Value, Value),
|
||||
IRet,
|
||||
Mov(Value, Value),
|
||||
Halt,
|
||||
Nop,
|
||||
Dump,
|
||||
}
|
||||
|
||||
impl Inst {
|
||||
pub fn len(&self) -> usize {
|
||||
match self {
|
||||
Inst::Add(v1, v2)
|
||||
| Inst::Sub(v1, v2)
|
||||
| Inst::Mul(v1, v2)
|
||||
| Inst::Div(v1, v2)
|
||||
| Inst::IDiv(v1, v2)
|
||||
| Inst::Mod(v1, v2)
|
||||
| Inst::And(v1, v2)
|
||||
| Inst::Or(v1, v2)
|
||||
| Inst::Xor(v1, v2)
|
||||
| Inst::Shl(v1, v2)
|
||||
| Inst::Shr(v1, v2)
|
||||
| Inst::INeg(v1, v2)
|
||||
| Inst::Inv(v1, v2)
|
||||
| Inst::Not(v1, v2)
|
||||
| Inst::CmpEq(v1, v2)
|
||||
| Inst::CmpLt(v1, v2)
|
||||
| Inst::Int(v1, v2)
|
||||
| Inst::Mov(v1, v2) => 3 + v1.len() + v2.len(),
|
||||
Inst::Jmp(v)
|
||||
| Inst::Jz(v)
|
||||
| Inst::Jnz(v)
|
||||
| Inst::Call(v)
|
||||
| Inst::Push(v)
|
||||
| Inst::Pop(v) => 3 + v.len(),
|
||||
Inst::Ret | Inst::IRet | Inst::Halt | Inst::Nop | Inst::Dump => 2,
|
||||
}
|
||||
}
|
||||
}
|
||||
71
src/libvm/src/obj/syn/lexer.l
Normal file
71
src/libvm/src/obj/syn/lexer.l
Normal file
@@ -0,0 +1,71 @@
|
||||
%%
|
||||
[0-9]+ "DEC_INT"
|
||||
0[Xx][0-9a-fA-F]+ "HEX_INT"
|
||||
0[Bb][01]+ "BIN_INT"
|
||||
\.meta "DIR_META"
|
||||
\.section "DIR_SECTION"
|
||||
\.export "DIR_EXPORT"
|
||||
\.include "DIR_INCLUDE"
|
||||
\( "LPAREN"
|
||||
\) "RPAREN"
|
||||
\{ "LBRACE"
|
||||
\} "RBRACE"
|
||||
\[ "LBRACKET"
|
||||
\] "RBRACKET"
|
||||
\.\. "DOTDOT"
|
||||
: "COLON"
|
||||
, "COMMA"
|
||||
\$\$ "BUCKBUCK"
|
||||
u8 "U8"
|
||||
u16 "U16"
|
||||
u32 "U32"
|
||||
u64 "U64"
|
||||
\.u8 "U8_DEF"
|
||||
\.u16 "U16_DEF"
|
||||
\.u32 "U32_DEF"
|
||||
\.u64 "U64_DEF"
|
||||
\.string "STR_DEF"
|
||||
\.zstring "ZSTR_DEF"
|
||||
\.interrupt "INTERRUPT_DEF"
|
||||
\.align "ALIGN_DEF"
|
||||
"([^"]|\\[\\nt0"'])*" "STRING"
|
||||
add "ADD"
|
||||
sub "SUB"
|
||||
mul "MUL"
|
||||
div "DIV"
|
||||
idiv "IDIV"
|
||||
mod "MOD"
|
||||
and "AND"
|
||||
or "OR"
|
||||
xor "XOR"
|
||||
shl "SHL"
|
||||
shr "SHR"
|
||||
ineg "INEG"
|
||||
inv "INV"
|
||||
not "NOT"
|
||||
cmpeq "CMPEQ"
|
||||
cmplt "CMPLT"
|
||||
jmp "JMP"
|
||||
jz "JZ"
|
||||
jnz "JNZ"
|
||||
call "CALL"
|
||||
ret "RET"
|
||||
push "PUSH"
|
||||
pop "POP"
|
||||
int "INT"
|
||||
iret "IRET"
|
||||
mov "MOV"
|
||||
halt "HALT"
|
||||
nop "NOP"
|
||||
dump "DUMP"
|
||||
%ip "REG_IP"
|
||||
%sp "REG_SP"
|
||||
%fp "REG_FP"
|
||||
%flags "REG_FLAGS"
|
||||
%null "REG_NULL"
|
||||
%ivt "REG_IVT"
|
||||
%status "REG_STATUS"
|
||||
%r[0-9]{1,2} "REG_GENERAL"
|
||||
[a-zA-Z_][a-zA-Z0-9_]* "NAME"
|
||||
;[^\n]* ;
|
||||
[ \n\t]+ ;
|
||||
15
src/libvm/src/obj/syn/mod.rs
Normal file
15
src/libvm/src/obj/syn/mod.rs
Normal file
@@ -0,0 +1,15 @@
|
||||
pub mod ast;
|
||||
|
||||
pub mod parser {
|
||||
use lrpar::lrpar_mod;
|
||||
lrpar_mod!("obj/syn/parser.y");
|
||||
|
||||
pub use self::parser_y::*;
|
||||
}
|
||||
|
||||
pub mod lexer {
|
||||
use lrlex::lrlex_mod;
|
||||
lrlex_mod!("obj/syn/lexer.l");
|
||||
|
||||
pub use self::lexer_l::*;
|
||||
}
|
||||
249
src/libvm/src/obj/syn/parser.y
Normal file
249
src/libvm/src/obj/syn/parser.y
Normal file
@@ -0,0 +1,249 @@
|
||||
%start Top
|
||||
%%
|
||||
|
||||
Top -> Vec<Directive>:
|
||||
Top Directive { $1.push($2); $1 }
|
||||
| { Vec::new() }
|
||||
;
|
||||
|
||||
Directive -> Directive:
|
||||
'DIR_META' MetaBlock { Directive::Meta(MetaSection { lines: $2 }) }
|
||||
| 'DIR_SECTION' Name SectionOrg 'LBRACE' DataBlocks 'RBRACE' {
|
||||
Directive::Data(DataSection {
|
||||
name: $2,
|
||||
org: $3,
|
||||
blocks: $5,
|
||||
})
|
||||
}
|
||||
| 'DIR_INCLUDE' String { Directive::Include($2) }
|
||||
;
|
||||
|
||||
MetaBlock -> Vec<MetaLine>: 'LBRACE' MetaLines 'RBRACE' { $2 };
|
||||
|
||||
MetaLines -> Vec<MetaLine>:
|
||||
MetaLines MetaLine { $1.push($2); $1 }
|
||||
| { Vec::new() }
|
||||
;
|
||||
|
||||
|
||||
MetaLine -> MetaLine: Name 'COLON' Value { MetaLine { name: $1, value: $3 } };
|
||||
|
||||
SectionOrg -> SectionOrg:
|
||||
Int { SectionOrg::Start($1) }
|
||||
| Int 'DOTDOT' Int { SectionOrg::StartEnd($1, $3) }
|
||||
;
|
||||
|
||||
DataBlocks -> Vec<AlignedBlock>:
|
||||
DataLines AlignedBlocks {
|
||||
let front = AlignedBlock { alignment: IntSize::U8, block: $1 };
|
||||
$2.insert(0, front);
|
||||
$2
|
||||
}
|
||||
;
|
||||
|
||||
AlignedBlocks -> Vec<AlignedBlock>:
|
||||
AlignedBlocks AlignedBlock { $1.push($2); $1 }
|
||||
| { Vec::new() }
|
||||
;
|
||||
|
||||
AlignedBlock -> AlignedBlock:
|
||||
'ALIGN_DEF' IntSize DataLines { AlignedBlock { alignment: $2, block: $3 } }
|
||||
;
|
||||
|
||||
DataLines -> Vec<DataLine>:
|
||||
DataLines DataLine { $1.push($2); $1 }
|
||||
| { Vec::new() }
|
||||
;
|
||||
|
||||
DataLine -> DataLine:
|
||||
ValueDef { DataLine::ValueDef($1) }
|
||||
| Inst { DataLine::Inst($1) }
|
||||
| 'DIR_EXPORT' Name { DataLine::Export($2) }
|
||||
| Name 'COLON' { DataLine::Label($1) }
|
||||
;
|
||||
|
||||
ValueDef -> ValueDef:
|
||||
'U8_DEF' Int { ValueDef::Int($2, IntSize::U8) }
|
||||
| 'U16_DEF' Int { ValueDef::Int($2, IntSize::U16) }
|
||||
| 'U32_DEF' Int { ValueDef::Int($2, IntSize::U32) }
|
||||
| 'U64_DEF' Int { ValueDef::Int($2, IntSize::U64) }
|
||||
| 'STR_DEF' String { ValueDef::String($2) }
|
||||
| 'ZSTR_DEF' String { ValueDef::ZString($2) }
|
||||
| 'INTERRUPT_DEF' Int 'COMMA' ConstValue { ValueDef::Interrupt($2 != 0, $4) }
|
||||
//| 'ALIGN_DEF' IntSize { ValueDef::Align($2) }
|
||||
;
|
||||
|
||||
IntSize -> IntSize:
|
||||
'U8' { IntSize::U8 }
|
||||
| 'U16' { IntSize::U16 }
|
||||
| 'U32' { IntSize::U32 }
|
||||
| 'U64' { IntSize::U64 }
|
||||
;
|
||||
|
||||
Value -> Value:
|
||||
ConstValue { $1 }
|
||||
| 'LPAREN' Value 'RPAREN' { Value::Addr(Box::new($2), IntSize::U64) }
|
||||
| 'LPAREN' Value 'RPAREN' IntSize { Value::Addr(Box::new($2), $4) }
|
||||
//| 'LBRACKET' ArrayValues 'RBRACKET' { Value::Array($2) }
|
||||
;
|
||||
|
||||
ConstValue -> Value:
|
||||
Int { Value::Int($1) }
|
||||
| Reg { Value::Reg($1) }
|
||||
| Name { Value::Name($1) }
|
||||
| 'BUCKBUCK' { Value::Here }
|
||||
;
|
||||
|
||||
/*
|
||||
ArrayValues -> Vec<Value>:
|
||||
ArrayValues Value { $1.push($2); $1 }
|
||||
| { Vec::new() }
|
||||
;
|
||||
*/
|
||||
|
||||
Inst -> Inst:
|
||||
'ADD' Value 'COMMA' Value { Inst::Add($2, $4) }
|
||||
| 'SUB' Value 'COMMA' Value { Inst::Sub($2, $4) }
|
||||
| 'MUL' Value 'COMMA' Value { Inst::Mul($2, $4) }
|
||||
| 'DIV' Value 'COMMA' Value { Inst::Div($2, $4) }
|
||||
| 'IDIV' Value 'COMMA' Value { Inst::IDiv($2, $4) }
|
||||
| 'MOD' Value 'COMMA' Value { Inst::Mod($2, $4) }
|
||||
| 'AND' Value 'COMMA' Value { Inst::And($2, $4) }
|
||||
| 'OR' Value 'COMMA' Value { Inst::Or($2, $4) }
|
||||
| 'XOR' Value 'COMMA' Value { Inst::Xor($2, $4) }
|
||||
| 'SHL' Value 'COMMA' Value { Inst::Shl($2, $4) }
|
||||
| 'SHR' Value 'COMMA' Value { Inst::Shr($2, $4) }
|
||||
| 'INEG' Value 'COMMA' Value { Inst::INeg($2, $4) }
|
||||
| 'INV' Value 'COMMA' Value { Inst::Inv($2, $4) }
|
||||
| 'NOT' Value 'COMMA' Value { Inst::Not($2, $4) }
|
||||
| 'CMPEQ' Value 'COMMA' Value { Inst::CmpEq($2, $4) }
|
||||
| 'CMPLT' Value 'COMMA' Value { Inst::CmpLt($2, $4) }
|
||||
| 'JMP' Value { Inst::Jmp($2) }
|
||||
| 'JZ' Value { Inst::Jz($2) }
|
||||
| 'JNZ' Value { Inst::Jnz($2) }
|
||||
| 'CALL' Value { Inst::Call($2) }
|
||||
| 'RET' { Inst::Ret }
|
||||
| 'PUSH' Value { Inst::Push($2) }
|
||||
| 'POP' Value { Inst::Pop($2) }
|
||||
| 'INT' Value 'COMMA' Value { Inst::Int($2, $4) }
|
||||
| 'IRET' { Inst::IRet }
|
||||
| 'MOV' Value 'COMMA' Value { Inst::Mov($2, $4) }
|
||||
| 'HALT' { Inst::Halt }
|
||||
| 'NOP' { Inst::Nop }
|
||||
| 'DUMP' { Inst::Dump }
|
||||
;
|
||||
|
||||
Name -> String:
|
||||
'NAME' {
|
||||
let v = $1.expect("could not parse name");
|
||||
$lexer.span_str(v.span()).to_string()
|
||||
}
|
||||
;
|
||||
|
||||
Int -> u64:
|
||||
'DEC_INT' {
|
||||
let span = $1.expect("could not parse dec_int").span();
|
||||
let s = $lexer.span_str(span);
|
||||
s.parse().unwrap()
|
||||
}
|
||||
| 'HEX_INT' {
|
||||
let span = $1.expect("could not parse hex_int").span();
|
||||
let s = &$lexer.span_str(span)[2..];
|
||||
u64::from_str_radix(s, 16).unwrap()
|
||||
}
|
||||
| 'BIN_INT' {
|
||||
let span = $1.expect("could not parse bin_int").span();
|
||||
let s = &$lexer.span_str(span)[2..];
|
||||
u64::from_str_radix(s, 2).unwrap()
|
||||
}
|
||||
;
|
||||
|
||||
Reg -> Reg:
|
||||
'REG_IP' { IP }
|
||||
| 'REG_SP' { SP }
|
||||
| 'REG_FP' { FP }
|
||||
| 'REG_FLAGS' { FLAGS }
|
||||
| 'REG_NULL' { NULL }
|
||||
| 'REG_IVT' { IVT }
|
||||
| 'REG_STATUS' { STATUS }
|
||||
| 'REG_GENERAL' {
|
||||
let v = $1.expect("could not parse reg");
|
||||
parse_reg($lexer.span_str(v.span())).unwrap()
|
||||
}
|
||||
;
|
||||
|
||||
String -> String:
|
||||
'STRING' {
|
||||
let v = $1.expect("could not parse string");
|
||||
parse_string($lexer.span_str(v.span()))
|
||||
}
|
||||
;
|
||||
%%
|
||||
|
||||
use crate::{
|
||||
obj::syn::ast::*,
|
||||
reg::*,
|
||||
};
|
||||
|
||||
fn parse_string(input: &str) -> String {
|
||||
let mut s = String::new();
|
||||
let input = &input[1..input.bytes().len() - 1];
|
||||
let mut chars = input.chars();
|
||||
while let Some(c) = chars.next() {
|
||||
if c == '\\' {
|
||||
let next = chars.next().unwrap();
|
||||
let c = match next {
|
||||
'\\' => '\\',
|
||||
'n' => '\n',
|
||||
't' => '\t',
|
||||
'"' => '"',
|
||||
'\'' => '\'',
|
||||
'0' => '\0',
|
||||
_ => unreachable!(),
|
||||
};
|
||||
s.push(c);
|
||||
} else {
|
||||
s.push(c);
|
||||
}
|
||||
}
|
||||
s
|
||||
}
|
||||
|
||||
pub fn parse_reg(input: &str) -> Option<Reg> {
|
||||
use regex::Regex;
|
||||
use lazy_static::lazy_static;
|
||||
lazy_static! {
|
||||
static ref REG_RE: Regex = Regex::new(r"^%r([0-9]{1,2})$").unwrap();
|
||||
}
|
||||
let captures = REG_RE.captures(input)?;
|
||||
let reg_no: Reg = captures.get(1)?
|
||||
.as_str()
|
||||
.parse()
|
||||
.unwrap();
|
||||
let reg = R00 + reg_no;
|
||||
if reg > R31 {
|
||||
None
|
||||
} else {
|
||||
Some(reg)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use crate::reg::*;
|
||||
use super::parse_reg;
|
||||
#[test]
|
||||
fn test_parse_reg() {
|
||||
assert_eq!(parse_reg("%r00"), Some(R00));
|
||||
assert_eq!(parse_reg("%r0"), Some(R00));
|
||||
assert_eq!(parse_reg("%r1"), Some(R01));
|
||||
assert_eq!(parse_reg("%r01"), Some(R01));
|
||||
|
||||
assert_eq!(parse_reg("%r31"), Some(R31));
|
||||
assert_eq!(parse_reg("%r32"), None);
|
||||
assert_eq!(parse_reg("%r0000"), None);
|
||||
assert_eq!(parse_reg("%r9"), Some(R09));
|
||||
assert_eq!(parse_reg("%r"), None);
|
||||
assert_eq!(parse_reg("%r12"), Some(R12));
|
||||
}
|
||||
}
|
||||
80
src/libvm/src/reg.rs
Normal file
80
src/libvm/src/reg.rs
Normal file
@@ -0,0 +1,80 @@
|
||||
macro_rules! registers {
|
||||
{
|
||||
$($variant:ident = $value:expr),* $(,)?
|
||||
} => {
|
||||
pub type Reg = u8;
|
||||
|
||||
$(
|
||||
#[allow(dead_code)]
|
||||
pub const $variant: Reg = $value;
|
||||
)*
|
||||
|
||||
pub fn reg_name(reg: Reg) -> Option<&'static str> {
|
||||
match reg {
|
||||
$(
|
||||
$value => Some(stringify!($variant)),
|
||||
)*
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
registers! {
|
||||
// Instruction pointer
|
||||
IP = 0,
|
||||
|
||||
// Stack pointer
|
||||
SP = 1,
|
||||
|
||||
// Frame pointer
|
||||
FP = 2,
|
||||
|
||||
// Flags
|
||||
FLAGS = 3,
|
||||
|
||||
// Zero
|
||||
NULL = 4,
|
||||
|
||||
// Interrupt vector table pointer
|
||||
IVT = 5,
|
||||
|
||||
// General status code
|
||||
STATUS = 6,
|
||||
|
||||
R00 = 7,
|
||||
R01 = 8,
|
||||
R02 = 9,
|
||||
R03 = 10,
|
||||
R04 = 11,
|
||||
R05 = 12,
|
||||
R06 = 13,
|
||||
R07 = 14,
|
||||
R08 = 15,
|
||||
R09 = 16,
|
||||
R10 = 17,
|
||||
R11 = 18,
|
||||
R12 = 19,
|
||||
R13 = 20,
|
||||
R14 = 21,
|
||||
R15 = 22,
|
||||
R16 = 23,
|
||||
R17 = 24,
|
||||
R18 = 25,
|
||||
R19 = 26,
|
||||
R20 = 27,
|
||||
R21 = 28,
|
||||
R22 = 29,
|
||||
R23 = 30,
|
||||
R24 = 31,
|
||||
R25 = 32,
|
||||
R26 = 33,
|
||||
R27 = 34,
|
||||
R28 = 35,
|
||||
R29 = 36,
|
||||
R30 = 37,
|
||||
R31 = 38,
|
||||
}
|
||||
|
||||
pub const LAST_REG: Reg = 63;
|
||||
pub const NUM_REGS: usize = 64;
|
||||
587
src/libvm/src/state.rs
Normal file
587
src/libvm/src/state.rs
Normal file
@@ -0,0 +1,587 @@
|
||||
use crate::{
|
||||
addr::*,
|
||||
error::*,
|
||||
flags::*,
|
||||
inst::*,
|
||||
interrupt::*,
|
||||
mem::*,
|
||||
obj::{obj::*, syn::parser},
|
||||
reg::*,
|
||||
};
|
||||
use std::mem;
|
||||
|
||||
pub struct State {
|
||||
regs: [u64; NUM_REGS],
|
||||
mem: Vec<u8>,
|
||||
interrupt_stack: Vec<InterruptIndex>,
|
||||
}
|
||||
|
||||
impl State {
|
||||
pub fn new() -> Self {
|
||||
State {
|
||||
regs: [0; NUM_REGS],
|
||||
mem: Default::default(),
|
||||
interrupt_stack: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn load_object(&mut self, object: Object, max_mem: usize) -> Result<()> {
|
||||
// TODO : detecting section overlap
|
||||
let mem_len = object.virtual_len();
|
||||
if mem_len > max_mem {
|
||||
return Err(VmError::ObjectTooLarge {
|
||||
object_size: mem_len,
|
||||
max_mem,
|
||||
});
|
||||
}
|
||||
let mut mem = vec![0u8; max_mem];
|
||||
for section in object.sections {
|
||||
match section {
|
||||
Section::Data(DataSection {
|
||||
name: _,
|
||||
start,
|
||||
len,
|
||||
contents,
|
||||
}) => {
|
||||
for offset in 0..len {
|
||||
mem[(start + offset) as usize] = contents[offset as usize];
|
||||
}
|
||||
}
|
||||
Section::Meta(MetaSection { entries }) => {
|
||||
macro_rules! meta_addr {
|
||||
($($name:expr, $reg:expr);* $(;)?) => {{
|
||||
$(
|
||||
if let Some(value) = entries.get($name) {
|
||||
self.set_reg_unchecked($reg, *value);
|
||||
}
|
||||
)*
|
||||
}};
|
||||
}
|
||||
|
||||
meta_addr!("ip", IP);
|
||||
meta_addr!("sp", SP);
|
||||
meta_addr!("flags", FLAGS);
|
||||
meta_addr!("status", STATUS);
|
||||
meta_addr!("ivt", IVT);
|
||||
|
||||
if let Some(value) = entries.get("fp") {
|
||||
self.set_reg_unchecked(FP, *value);
|
||||
} else {
|
||||
self.set_reg_unchecked(FP, self.get_reg_unchecked(SP));
|
||||
}
|
||||
|
||||
for (reg, value) in entries.iter() {
|
||||
if let Some(reg) = parser::parse_reg(reg) {
|
||||
self.set_reg_unchecked(reg, *value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
self.mem = mem;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// Memory
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
pub fn mem_cursor(&self, addr: Addr) -> MemCursor<&[u8]> {
|
||||
let mut cursor = MemCursor::new(self.mem.as_slice());
|
||||
cursor.set_position(addr);
|
||||
cursor
|
||||
}
|
||||
|
||||
pub fn mem_cursor_mut(&mut self, addr: Addr) -> MemCursor<&mut [u8]> {
|
||||
let mut cursor = MemCursor::new(self.mem.as_mut_slice());
|
||||
cursor.set_position(addr);
|
||||
cursor
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// Registers
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
pub fn get_reg_unchecked(&self, reg: Reg) -> u64 {
|
||||
self.regs[reg as usize]
|
||||
}
|
||||
|
||||
pub fn get_reg(&self, reg: Reg) -> Result<u64> {
|
||||
if (reg as usize) >= NUM_REGS {
|
||||
Err(InterruptError::IllegalReg { reg }.into())
|
||||
} else {
|
||||
Ok(self.get_reg_unchecked(reg))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_reg_unchecked(&mut self, reg: Reg, value: u64) {
|
||||
self.regs[reg as usize] = value;
|
||||
}
|
||||
|
||||
pub fn set_reg(&mut self, reg: Reg, value: u64) -> Result<()> {
|
||||
if (reg as usize) >= NUM_REGS {
|
||||
Err(InterruptError::IllegalReg { reg }.into())
|
||||
} else {
|
||||
Ok(self.set_reg_unchecked(reg, value))
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// Registers
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
pub fn ip(&self) -> u64 {
|
||||
self.get_reg_unchecked(IP)
|
||||
}
|
||||
|
||||
pub fn sp(&self) -> u64 {
|
||||
self.get_reg_unchecked(SP)
|
||||
}
|
||||
|
||||
pub fn fp(&self) -> u64 {
|
||||
self.get_reg_unchecked(FP)
|
||||
}
|
||||
|
||||
pub fn status(&self) -> u64 {
|
||||
self.get_reg_unchecked(STATUS)
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// Flags
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
pub fn flags(&self) -> Flags {
|
||||
// this is safe because it's OK if there are random bits flipped - this shouldn't happen
|
||||
// anyway, but if it does, they're ignored
|
||||
unsafe { Flags::from_bits_unchecked(self.get_reg_unchecked(FLAGS)) }
|
||||
}
|
||||
|
||||
pub fn contains_flags(&self, flags: Flags) -> bool {
|
||||
self.flags().contains(flags)
|
||||
}
|
||||
|
||||
pub fn insert_flags(&mut self, flags: Flags) {
|
||||
let mut new_flags = self.flags();
|
||||
new_flags.insert(flags);
|
||||
self.set_flags(new_flags);
|
||||
}
|
||||
|
||||
pub fn remove_flags(&mut self, flags: Flags) {
|
||||
let mut new_flags = self.flags();
|
||||
new_flags.remove(flags);
|
||||
self.set_flags(new_flags);
|
||||
}
|
||||
|
||||
pub fn set_flags(&mut self, flags: Flags) {
|
||||
self.set_reg_unchecked(FLAGS, flags.bits());
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// Interrupts
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
pub fn ivt(&self) -> Result<&[InterruptVector]> {
|
||||
let ivt_addr = self.get_reg_unchecked(IVT);
|
||||
if ivt_addr % 64 != 0 {
|
||||
panic!(
|
||||
"illegal IVT address {:#x}; must be divisible by 64",
|
||||
ivt_addr
|
||||
);
|
||||
}
|
||||
|
||||
let start = ivt_addr as usize;
|
||||
let end = start + (IVT_LENGTH * mem::size_of::<InterruptVector>());
|
||||
|
||||
if end > self.mem.len() {
|
||||
return Err(InterruptError::MemOutOfBounds {
|
||||
addr: Addr(ivt_addr),
|
||||
}.into());
|
||||
}
|
||||
|
||||
// This is safe because we check the bounds above
|
||||
let slice_ptr = (&self.mem[start..]).as_ptr();
|
||||
let slice =
|
||||
unsafe { std::slice::from_raw_parts(slice_ptr as *const InterruptVector, IVT_LENGTH) };
|
||||
|
||||
Ok(slice)
|
||||
}
|
||||
|
||||
// stack size of 288 - yowza
|
||||
// Maybe we don't need 32 registers... this works out to 288
|
||||
const INTERRUPT_REG_SPACE: u64 = (R31 - R00 + 4) as u64 * 8;
|
||||
|
||||
/// Invoke an interrupt.
|
||||
pub fn interrupt(&mut self, index: InterruptIndex, aux: u64) -> Result<u64> {
|
||||
assert!(index < IVT_LENGTH, "invalid interrupt index");
|
||||
let interrupt = self.ivt()?[index];
|
||||
if !self.contains_flags(Flags::INTERRUPT_ENABLE) || !interrupt.enabled() {
|
||||
// get whether this is a double fault or an error interrupt
|
||||
// if a double fault is invoked while it is disabled, immediately triple-fault
|
||||
if index == DOUBLE_FAULT {
|
||||
return Err(VmError::TripleFault);
|
||||
} else if index <= MAX_ERROR_INTERRUPT {
|
||||
return self.interrupt(DOUBLE_FAULT, index as u64);
|
||||
} else {
|
||||
return Ok(self.ip());
|
||||
}
|
||||
}
|
||||
|
||||
// If this interrupt is an error, then check if a double or triple fault should be invoked.
|
||||
if index <= MAX_ERROR_INTERRUPT {
|
||||
if self.is_handling_double_fault() {
|
||||
return Err(VmError::TripleFault);
|
||||
} else if self.is_handling_error() {
|
||||
return self.interrupt(DOUBLE_FAULT, index as u64);
|
||||
}
|
||||
}
|
||||
// otherwise, handle it like normal
|
||||
|
||||
let fp = self.fp();
|
||||
let ip = self.ip();
|
||||
let flags = self.get_reg_unchecked(FLAGS);
|
||||
let status = self.status();
|
||||
|
||||
self.push(Source::Imm(fp))?;
|
||||
self.push(Source::Imm(ip))?;
|
||||
self.push(Source::Imm(flags))?;
|
||||
self.push(Source::Imm(status))?;
|
||||
|
||||
// push R0 .. R31
|
||||
for reg in R00..=R31 {
|
||||
self.push(Source::Reg(reg))?;
|
||||
}
|
||||
|
||||
let sp = self.sp();
|
||||
self.set_reg_unchecked(FP, sp - Self::INTERRUPT_REG_SPACE);
|
||||
self.set_reg_unchecked(R00, index as u64);
|
||||
self.set_reg_unchecked(R01, aux);
|
||||
self.interrupt_stack.push(index);
|
||||
|
||||
Ok(interrupt.addr().0)
|
||||
}
|
||||
|
||||
/// Exit/return from the current interrupt.
|
||||
pub fn exit_interrupt(&mut self) -> Result<u64> {
|
||||
self.interrupt_stack.pop().expect("mismatched interrupt stack");
|
||||
let fp = self.fp();
|
||||
let sp = fp + Self::INTERRUPT_REG_SPACE;
|
||||
|
||||
self.set_reg_unchecked(SP, sp);
|
||||
|
||||
// pop R31 .. R0
|
||||
for reg in R00..=R31 {
|
||||
let reg = R31 - reg + R00;
|
||||
self.pop(Dest::Reg(reg))?;
|
||||
}
|
||||
self.pop(Dest::Reg(STATUS))?;
|
||||
self.pop(Dest::Reg(FLAGS))?;
|
||||
self.pop(Dest::Reg(IP))?;
|
||||
self.pop(Dest::Reg(FP))?;
|
||||
Ok(self.ip())
|
||||
}
|
||||
|
||||
/// Get whether a double fault interrupt is currently being handled.
|
||||
fn is_handling_double_fault(&self) -> bool {
|
||||
self.interrupt_stack.contains(&DOUBLE_FAULT)
|
||||
}
|
||||
|
||||
/// Get whether an error interrupt is currently being handled.
|
||||
fn is_handling_error(&self) -> bool {
|
||||
self.interrupt_stack.iter()
|
||||
.copied()
|
||||
.any(|i| i <= MAX_ERROR_INTERRUPT)
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// Execution
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
pub fn halt(&mut self) {
|
||||
self.insert_flags(Flags::HALT);
|
||||
}
|
||||
|
||||
pub fn is_halted(&self) -> bool {
|
||||
self.contains_flags(Flags::HALT)
|
||||
}
|
||||
|
||||
pub fn exec(&mut self) -> Result<u64> {
|
||||
while !self.is_halted() {
|
||||
// TODO : better interrupt handling than this. Error-specific interrupts are a good
|
||||
// start, but catching multiple interrupts as they happen may be better.
|
||||
let tick = self.tick();
|
||||
if let Err(VmError::Interrupt { source: interrupt }) = tick {
|
||||
let next_ip = match interrupt {
|
||||
InterruptError::MemOutOfBounds { addr, } => {
|
||||
self.interrupt(ILLEGAL_MEMORY_ADDRESS, addr.0)?
|
||||
}
|
||||
InterruptError::IllegalReg { .. }
|
||||
| InterruptError::IllegalOpcode { .. }
|
||||
| InterruptError::IllegalDestSpec { .. }
|
||||
| InterruptError::IllegalSourceSpec { .. } => {
|
||||
self.interrupt(ILLEGAL_INSTRUCTION, self.ip())?
|
||||
}
|
||||
};
|
||||
self.set_reg_unchecked(IP, next_ip);
|
||||
} else {
|
||||
tick?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(self.get_reg_unchecked(STATUS))
|
||||
}
|
||||
|
||||
fn tick(&mut self) -> Result<()> {
|
||||
let mut cursor = self.mem_cursor(Addr(self.ip()));
|
||||
let inst = cursor.next_inst()?;
|
||||
let next_ip = self.ip() + (inst.len() as u64);
|
||||
self.set_reg_unchecked(IP, next_ip);
|
||||
match inst {
|
||||
Inst::Add(d, s) => {
|
||||
let value = self.load_dest(d)?.wrapping_add(self.load_source(s)?);
|
||||
self.store_dest(d, value)?;
|
||||
}
|
||||
Inst::Sub(d, s) => {
|
||||
let value = self.load_dest(d)?.wrapping_sub(self.load_source(s)?);
|
||||
self.store_dest(d, value)?;
|
||||
}
|
||||
Inst::Mul(d, s) => {
|
||||
let value = self.load_dest(d)?.wrapping_mul(self.load_source(s)?);
|
||||
self.store_dest(d, value)?;
|
||||
}
|
||||
Inst::Div(d, s) => {
|
||||
let src = self.load_source(s)?;
|
||||
if src == 0 {
|
||||
let next_ip = self.interrupt(DIVIDE_BY_ZERO, 0)?;
|
||||
self.set_reg_unchecked(IP, next_ip);
|
||||
} else {
|
||||
let value = self.load_dest(d)?.wrapping_div(src);
|
||||
self.store_dest(d, value)?;
|
||||
}
|
||||
}
|
||||
Inst::IDiv(d, s) => {
|
||||
let dest = self.load_dest(d)? as i64;
|
||||
let source = self.load_source(s)? as i64;
|
||||
if source == 0 {
|
||||
let next_ip = self.interrupt(DIVIDE_BY_ZERO, 0)?;
|
||||
self.set_reg_unchecked(IP, next_ip);
|
||||
} else {
|
||||
let value = dest.wrapping_div(source);
|
||||
self.store_dest(d, value as u64)?;
|
||||
}
|
||||
}
|
||||
Inst::Mod(d, s) => {
|
||||
let value = self.load_dest(d)? % self.load_source(s)?;
|
||||
self.store_dest(d, value)?;
|
||||
}
|
||||
Inst::And(d, s) => {
|
||||
let value = self.load_dest(d)? & self.load_source(s)?;
|
||||
self.store_dest(d, value)?;
|
||||
}
|
||||
Inst::Or(d, s) => {
|
||||
let value = self.load_dest(d)? | self.load_source(s)?;
|
||||
self.store_dest(d, value)?;
|
||||
}
|
||||
Inst::Xor(d, s) => {
|
||||
let value = self.load_dest(d)? ^ self.load_source(s)?;
|
||||
self.store_dest(d, value)?;
|
||||
}
|
||||
Inst::Shl(d, s) => {
|
||||
let value = self.load_dest(d)? << self.load_source(s)?;
|
||||
self.store_dest(d, value)?;
|
||||
}
|
||||
Inst::Shr(d, s) => {
|
||||
let value = self.load_dest(d)? >> self.load_source(s)?;
|
||||
self.store_dest(d, value)?;
|
||||
}
|
||||
Inst::INeg(d, s) => {
|
||||
let value = (!self.load_source(s)?).wrapping_add(1);
|
||||
self.store_dest(d, value)?;
|
||||
}
|
||||
Inst::Inv(d, s) => {
|
||||
let value = !self.load_source(s)?;
|
||||
self.store_dest(d, value)?;
|
||||
}
|
||||
Inst::Not(d, s) => {
|
||||
let value = (self.load_source(s)? == 0) as u64;
|
||||
self.store_dest(d, value)?;
|
||||
}
|
||||
Inst::CmpEq(s1, s2) => {
|
||||
let cmp = self.load_source(s1)? == self.load_source(s2)?;
|
||||
if cmp {
|
||||
self.insert_flags(Flags::COMPARE);
|
||||
} else {
|
||||
self.remove_flags(Flags::COMPARE);
|
||||
}
|
||||
}
|
||||
Inst::CmpLt(s1, s2) => {
|
||||
let cmp = self.load_source(s1)? < self.load_source(s2)?;
|
||||
if cmp {
|
||||
self.insert_flags(Flags::COMPARE);
|
||||
} else {
|
||||
self.remove_flags(Flags::COMPARE);
|
||||
}
|
||||
}
|
||||
Inst::Jmp(s) => {
|
||||
self.set_reg_unchecked(IP, self.load_source(s)?);
|
||||
}
|
||||
Inst::Jz(s) => {
|
||||
if !self.contains_flags(Flags::COMPARE) {
|
||||
self.set_reg_unchecked(IP, self.load_source(s)?);
|
||||
}
|
||||
}
|
||||
Inst::Jnz(s) => {
|
||||
if self.contains_flags(Flags::COMPARE) {
|
||||
self.set_reg_unchecked(IP, self.load_source(s)?);
|
||||
}
|
||||
}
|
||||
Inst::Call(s) => {
|
||||
{
|
||||
let fp = self.fp();
|
||||
let ip = next_ip;
|
||||
self.push(Source::Imm(fp))?;
|
||||
self.push(Source::Imm(ip))?;
|
||||
}
|
||||
|
||||
{
|
||||
let sp = self.sp();
|
||||
self.set_reg_unchecked(FP, sp - 16);
|
||||
|
||||
self.set_reg_unchecked(IP, self.load_source(s)?);
|
||||
}
|
||||
}
|
||||
Inst::Ret => {
|
||||
let fp = self.fp();
|
||||
let sp = fp + 16;
|
||||
self.set_reg_unchecked(SP, sp);
|
||||
|
||||
self.pop(Dest::Reg(IP))?;
|
||||
self.pop(Dest::Reg(FP))?;
|
||||
}
|
||||
Inst::Push(s) => {
|
||||
self.push(s)?;
|
||||
}
|
||||
Inst::Pop(d) => {
|
||||
self.pop(d)?;
|
||||
}
|
||||
Inst::Int(v, a) => {
|
||||
let index = self.load_source(v)?;
|
||||
let aux = self.load_source(a)?;
|
||||
let next_ip = self.interrupt(index as InterruptIndex, aux)?;
|
||||
self.set_reg_unchecked(IP, next_ip);
|
||||
}
|
||||
Inst::IRet => {
|
||||
let next_ip = self.exit_interrupt()?;
|
||||
self.set_reg_unchecked(IP, next_ip);
|
||||
}
|
||||
Inst::Mov(d, s) => {
|
||||
let value = self.load_source(s)?;
|
||||
self.store_dest(d, value)?;
|
||||
}
|
||||
Inst::Halt => {
|
||||
self.halt();
|
||||
}
|
||||
Inst::Nop => {}
|
||||
Inst::Dump => {
|
||||
// TODO - dump
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn push(&mut self, source: Source) -> Result<()> {
|
||||
let value = self.load_source(source)?;
|
||||
let mut stack_addr = self.sp();
|
||||
|
||||
// create a destination based on the size of the source
|
||||
let dest = match source {
|
||||
Source::Addr64(_) | Source::RegAddr64(_) | Source::Reg(_) | Source::Imm(_) => {
|
||||
Dest::Addr64(Addr(stack_addr))
|
||||
}
|
||||
Source::Addr32(_) | Source::RegAddr32(_) => Dest::Addr32(Addr(stack_addr)),
|
||||
Source::Addr16(_) | Source::RegAddr16(_) => Dest::Addr16(Addr(stack_addr)),
|
||||
Source::Addr8(_) | Source::RegAddr8(_) => Dest::Addr8(Addr(stack_addr)),
|
||||
};
|
||||
self.store_dest(dest, value)?;
|
||||
assert_eq!(source.value_len(), dest.value_len());
|
||||
|
||||
stack_addr += source.value_len() as u64;
|
||||
self.set_reg_unchecked(SP, stack_addr);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn pop(&mut self, dest: Dest) -> Result<()> {
|
||||
let sp = self.sp() - (dest.value_len() as u64);
|
||||
|
||||
let sp_source = match dest {
|
||||
Dest::Addr64(_) | Dest::RegAddr64(_) | Dest::Reg(_) => Source::Addr64(Addr(sp)),
|
||||
Dest::Addr32(_) | Dest::RegAddr32(_) => Source::Addr32(Addr(sp)),
|
||||
Dest::Addr16(_) | Dest::RegAddr16(_) => Source::Addr16(Addr(sp)),
|
||||
Dest::Addr8(_) | Dest::RegAddr8(_) => Source::Addr8(Addr(sp)),
|
||||
};
|
||||
|
||||
let value = self.load_source(sp_source)?;
|
||||
|
||||
// Set the SP first, because the destination may be the SP itself
|
||||
self.set_reg_unchecked(SP, sp);
|
||||
self.store_dest(dest, value)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn store_dest(&mut self, dest: Dest, value: u64) -> Result<()> {
|
||||
match dest {
|
||||
Dest::Addr64(a) => self.mem_cursor_mut(a).write_u64(value),
|
||||
Dest::Addr32(a) => self
|
||||
.mem_cursor_mut(a)
|
||||
.write_u32((value & 0xffff_ffff) as u32),
|
||||
Dest::Addr16(a) => self.mem_cursor_mut(a).write_u16((value & 0xffff) as u16),
|
||||
Dest::Addr8(a) => self.mem_cursor_mut(a).write_u8((value & 0xff) as u8),
|
||||
Dest::RegAddr64(r) => {
|
||||
let addr = Addr(self.get_reg(r)?);
|
||||
self.mem_cursor_mut(addr).write_u64(value)
|
||||
}
|
||||
Dest::RegAddr32(r) => {
|
||||
let addr = Addr(self.get_reg(r)?);
|
||||
self.mem_cursor_mut(addr)
|
||||
.write_u32((value & 0xffff_ffff) as u32)
|
||||
}
|
||||
Dest::RegAddr16(r) => {
|
||||
let addr = Addr(self.get_reg(r)?);
|
||||
self.mem_cursor_mut(addr).write_u16((value & 0xffff) as u16)
|
||||
}
|
||||
Dest::RegAddr8(r) => {
|
||||
let addr = Addr(self.get_reg(r)?);
|
||||
self.mem_cursor_mut(addr).write_u8((value & 0xff) as u8)
|
||||
}
|
||||
Dest::Reg(reg) => self.set_reg(reg, value),
|
||||
}
|
||||
}
|
||||
|
||||
fn load_source(&self, source: Source) -> Result<u64> {
|
||||
let value = match source {
|
||||
Source::Addr64(a) => self.mem_cursor(a).next_u64()?,
|
||||
Source::Addr32(a) => self.mem_cursor(a).next_u32()? as u64,
|
||||
Source::Addr16(a) => self.mem_cursor(a).next_u16()? as u64,
|
||||
Source::Addr8(a) => self.mem_cursor(a).next_u8()? as u64,
|
||||
Source::RegAddr64(r) => self.mem_cursor(Addr(self.get_reg(r)?)).next_u64()?,
|
||||
Source::RegAddr32(r) => self.mem_cursor(Addr(self.get_reg(r)?)).next_u32()? as u64,
|
||||
Source::RegAddr16(r) => self.mem_cursor(Addr(self.get_reg(r)?)).next_u16()? as u64,
|
||||
Source::RegAddr8(r) => self.mem_cursor(Addr(self.get_reg(r)?)).next_u8()? as u64,
|
||||
Source::Reg(reg) => self.get_reg(reg)?,
|
||||
Source::Imm(u) => u,
|
||||
};
|
||||
Ok(value)
|
||||
}
|
||||
|
||||
fn load_dest(&self, dest: Dest) -> Result<u64> {
|
||||
let value = match dest {
|
||||
Dest::Addr64(a) => self.mem_cursor(a).next_u64()?,
|
||||
Dest::Addr32(a) => self.mem_cursor(a).next_u32()? as u64,
|
||||
Dest::Addr16(a) => self.mem_cursor(a).next_u16()? as u64,
|
||||
Dest::Addr8(a) => self.mem_cursor(a).next_u8()? as u64,
|
||||
Dest::RegAddr64(r) => self.mem_cursor(Addr(self.get_reg(r)?)).next_u64()?,
|
||||
Dest::RegAddr32(r) => self.mem_cursor(Addr(self.get_reg(r)?)).next_u32()? as u64,
|
||||
Dest::RegAddr16(r) => self.mem_cursor(Addr(self.get_reg(r)?)).next_u16()? as u64,
|
||||
Dest::RegAddr8(r) => self.mem_cursor(Addr(self.get_reg(r)?)).next_u8()? as u64,
|
||||
Dest::Reg(reg) => self.get_reg(reg)?,
|
||||
};
|
||||
Ok(value)
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
use crate::vm::{error::*, flags::Flags, inst::*, reg::*, vm::*, visit::*, mem::MemCursor};
|
||||
use crate::{error::*, flags::Flags, inst::*, reg::*, vm::*, visit::*, mem::MemCursor};
|
||||
|
||||
impl Vm {
|
||||
pub fn tick(&mut self) -> Result<()> {
|
||||
177
src/main.rs
177
src/main.rs
@@ -1,60 +1,147 @@
|
||||
#![allow(dead_code)]
|
||||
|
||||
mod common;
|
||||
mod vm;
|
||||
|
||||
use std::{convert::TryFrom, env, fs, io, process};
|
||||
extern crate libvm as vm;
|
||||
|
||||
use snafu::Snafu;
|
||||
use std::{
|
||||
fs,
|
||||
io::{stdout, Write},
|
||||
path::{Path, PathBuf},
|
||||
process,
|
||||
};
|
||||
use structopt::StructOpt;
|
||||
|
||||
const DEFAULT_MAX_MEM: usize = 64 * 1024 * 1024;
|
||||
|
||||
#[derive(Snafu, Debug, Clone, PartialEq)]
|
||||
enum ArgParseError<'a> {
|
||||
#[snafu(display("invalid specified size: {} (sizes may end in G, M, or K)", text))]
|
||||
InvalidSize { text: &'a str },
|
||||
}
|
||||
|
||||
fn from_bytes_size<'a>(mut text: &'a str) -> std::result::Result<usize, ArgParseError<'a>> {
|
||||
if text.is_empty() {
|
||||
return Err(ArgParseError::InvalidSize { text });
|
||||
}
|
||||
let multiplier = match text.chars().last().unwrap() {
|
||||
'k' | 'K' => 1024,
|
||||
'm' | 'M' => 1024 * 1024,
|
||||
'g' | 'G' => 1024 * 1024 * 1024,
|
||||
_ => 1,
|
||||
};
|
||||
// trim the last character
|
||||
if multiplier != 1 {
|
||||
text = &text[0..text.len() - 1];
|
||||
}
|
||||
let value = text
|
||||
.parse::<usize>()
|
||||
.map_err(|_| ArgParseError::InvalidSize { text })?;
|
||||
Ok(value * multiplier)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_bytes_size() {
|
||||
assert_eq!(from_bytes_size("64G"), Ok(64 * 1024 * 1024 * 1024));
|
||||
assert_eq!(from_bytes_size("64M"), Ok(64 * 1024 * 1024));
|
||||
assert_eq!(from_bytes_size("64K"), Ok(64 * 1024));
|
||||
assert_eq!(from_bytes_size("64g"), Ok(64 * 1024 * 1024 * 1024));
|
||||
assert_eq!(from_bytes_size("64m"), Ok(64 * 1024 * 1024));
|
||||
assert_eq!(from_bytes_size("64k"), Ok(64 * 1024));
|
||||
assert_eq!(from_bytes_size("64"), Ok(64));
|
||||
|
||||
assert!(from_bytes_size("64B").is_err());
|
||||
assert!(from_bytes_size("64MB").is_err());
|
||||
assert!(from_bytes_size("64GB").is_err());
|
||||
assert!(from_bytes_size("64KB").is_err());
|
||||
assert!(from_bytes_size("64b").is_err());
|
||||
assert!(from_bytes_size("64mb").is_err());
|
||||
assert!(from_bytes_size("64gb").is_err());
|
||||
assert!(from_bytes_size("64kb").is_err());
|
||||
}
|
||||
|
||||
#[derive(StructOpt, Debug)]
|
||||
struct Options {
|
||||
// maybe some other options:
|
||||
// * debug
|
||||
/// The input file to work with.
|
||||
///
|
||||
/// By default, the file will be executed. If -c is passed, it will not run and only compile.
|
||||
/// Supplying - for the path will read from STDIN.
|
||||
#[structopt(name = "FILE", parse(from_os_str))]
|
||||
input: PathBuf,
|
||||
|
||||
/// The maximum amount of virtual memory allowed.
|
||||
#[structopt(long, parse(try_from_str = from_bytes_size))]
|
||||
max_mem: Option<usize>,
|
||||
|
||||
/// The output file for generated files (preprocessed, compiled, etc).
|
||||
///
|
||||
/// If not supplied, a name will be inferred from the input file. Supplying - for the path will
|
||||
/// output to STDOUT.
|
||||
#[structopt(short = "o", long)]
|
||||
out: Option<PathBuf>,
|
||||
|
||||
/// Disassemble object that would be passed to VM and exit before running it.
|
||||
#[structopt(short = "d", long)]
|
||||
disassemble: bool,
|
||||
|
||||
/// Only compile the input file to an object.
|
||||
#[structopt(short = "c", long)]
|
||||
compile_only: bool,
|
||||
}
|
||||
|
||||
type Result<T> = std::result::Result<T, Box<dyn std::error::Error>>;
|
||||
|
||||
fn get_input_string() -> io::Result<String> {
|
||||
let args: Vec<String> = env::args().collect();
|
||||
if args.len() <= 1 {
|
||||
println!(
|
||||
"usage: {} file.asm",
|
||||
args.get(0).map(String::as_str).unwrap_or("rasp")
|
||||
);
|
||||
process::exit(1);
|
||||
/*
|
||||
fn get_reader(path: impl AsRef<Path>) -> Result<Box<dyn Read>> {
|
||||
if let Some("-") = path.as_ref().to_str() {
|
||||
Ok(Box::new(stdin()))
|
||||
} else {
|
||||
fs::read_to_string(&args[1])
|
||||
Ok(Box::new(fs::File::open(path.as_ref())?))
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
fn get_writer(path: impl AsRef<Path>) -> Result<Box<dyn Write>> {
|
||||
if let Some("-") = path.as_ref().to_str() {
|
||||
Ok(Box::new(stdout()))
|
||||
} else {
|
||||
Ok(Box::new(fs::File::create(path.as_ref())?))
|
||||
}
|
||||
}
|
||||
|
||||
fn main() -> Result<()> {
|
||||
use vm::obj::syn::parser::SectionsParser;
|
||||
let contents = get_input_string()?;
|
||||
let ast = match SectionsParser::new().parse(&contents) {
|
||||
Ok(ast) => ast,
|
||||
Err(err) => {
|
||||
eprintln!("{}", err);
|
||||
process::exit(1);
|
||||
}
|
||||
use vm::{
|
||||
obj::{assemble, disassemble::Disasm},
|
||||
state::State,
|
||||
};
|
||||
let obj = vm::obj::obj::Object::try_from(&ast)?;
|
||||
//dump(&obj)?;
|
||||
let mut vm = vm::vm::Vm::new();
|
||||
vm.load_object(obj, 1024 * 1024 * 64)?; // 64mb
|
||||
let status = vm.run()?;
|
||||
println!("{}", status);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
let opt = Options::from_args();
|
||||
let object = assemble::assemble_path(&opt.input)?;
|
||||
|
||||
fn dump(obj: &vm::obj::obj::Object) -> Result<()> {
|
||||
use vm::obj::obj::Section;
|
||||
use vm::visit::VisitInst;
|
||||
let mut stdout = io::stdout();
|
||||
for section in &obj.sections {
|
||||
let mut disasm = match section {
|
||||
Section::Code { start, contents, .. } => {
|
||||
vm::disassemble::Disassemble::new(&mut stdout, contents, *start)
|
||||
}
|
||||
Section::Meta { .. } | Section::Data { .. } => continue,
|
||||
};
|
||||
while !disasm.is_done() {
|
||||
disasm.visit_inst()?;
|
||||
}
|
||||
println!();
|
||||
}
|
||||
if opt.compile_only {
|
||||
let outfile = opt.out.clone().unwrap_or_else(|| {
|
||||
let mut outfile = opt.input.clone();
|
||||
assert!(outfile.set_extension("obj"));
|
||||
outfile
|
||||
});
|
||||
let bytes = object.to_bytes();
|
||||
let mut writer = get_writer(&outfile)?;
|
||||
writer.write(&bytes)?;
|
||||
Ok(())
|
||||
} else if opt.disassemble {
|
||||
let outfile = opt
|
||||
.out
|
||||
.as_ref()
|
||||
.map(|p| p.as_path())
|
||||
.unwrap_or_else(|| Path::new("-"));
|
||||
let mut writer = get_writer(&outfile)?;
|
||||
object.disasm(&mut writer)?;
|
||||
Ok(())
|
||||
} else {
|
||||
let mut state = State::new();
|
||||
state.load_object(object, opt.max_mem.unwrap_or(DEFAULT_MAX_MEM))?;
|
||||
let status = state.exec()?;
|
||||
process::exit((status & 0xffff_ffff) as i32);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
use crate::vm::{inst::InstOp, reg::Reg, vm::*};
|
||||
use snafu::Snafu;
|
||||
|
||||
#[derive(Snafu, Debug, Clone)]
|
||||
pub enum VmError {
|
||||
#[snafu(display("illegal register: 0x{:02x}", reg))]
|
||||
IllegalReg { reg: Reg },
|
||||
#[snafu(display("memory address out of bounds: 0x{:016x}", addr))]
|
||||
MemOutOfBounds { addr: Addr },
|
||||
#[snafu(display("illegal instruction opcode: 0x{:04x}", op))]
|
||||
IllegalOp { op: InstOp },
|
||||
}
|
||||
|
||||
pub type Result<T, E = VmError> = std::result::Result<T, E>;
|
||||
@@ -1,62 +0,0 @@
|
||||
macro_rules! instructions {
|
||||
{
|
||||
$($variant:ident = $value:expr),* $(,)?
|
||||
} => {
|
||||
|
||||
$(
|
||||
pub const $variant: InstOp = $value;
|
||||
)*
|
||||
|
||||
pub fn inst_name(op: InstOp) -> Option<&'static str> {
|
||||
match op {
|
||||
$(
|
||||
$value => Some(stringify!($variant)),
|
||||
)*
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
pub type InstOp = u16;
|
||||
|
||||
instructions! {
|
||||
ADD = 0x0000,
|
||||
MUL = 0x0001,
|
||||
DIV = 0x0002,
|
||||
MOD = 0x0003,
|
||||
INEG = 0x0004,
|
||||
AND = 0x0005,
|
||||
OR = 0x0006,
|
||||
INV = 0x0007,
|
||||
NOT = 0x0008,
|
||||
XOR = 0x0009,
|
||||
SHL = 0x000a,
|
||||
SHR = 0x000b,
|
||||
CMPEQ = 0x1000,
|
||||
CMPLT = 0x1001,
|
||||
JMP = 0x1100,
|
||||
JZ = 0x1101,
|
||||
JNZ = 0x1102,
|
||||
LOAD = 0x2000,
|
||||
REGCOPY = 0x2001,
|
||||
STOREIMM64 = 0x2100,
|
||||
STOREIMM32 = 0x2101,
|
||||
MEMCOPY = 0x2200,
|
||||
STORE = 0x2201,
|
||||
HALT = 0xF000,
|
||||
NOP = 0xF001,
|
||||
}
|
||||
|
||||
pub fn inst_len(op: InstOp) -> usize {
|
||||
match op {
|
||||
// 2 bytes
|
||||
INEG | INV | NOT | HALT | NOP => 2,
|
||||
// 4 bytes
|
||||
ADD | MUL | DIV | MOD | AND | OR | XOR | SHL | SHR | CMPEQ | CMPLT | JMP | JZ | JNZ
|
||||
| LOAD | REGCOPY | MEMCOPY | STORE => 4,
|
||||
// Immediates - 4+ bytes
|
||||
STOREIMM64 => 16,
|
||||
STOREIMM32 => 8,
|
||||
_ => panic!("unknown instruction op 0x{:04x}", op),
|
||||
}
|
||||
}
|
||||
@@ -1,81 +0,0 @@
|
||||
use crate::vm::{error::*, reg::*};
|
||||
use byteorder::{ReadBytesExt, LE};
|
||||
use std::{
|
||||
io::Cursor,
|
||||
ops::{Deref, DerefMut},
|
||||
};
|
||||
|
||||
const R1_MASK: u16 = 0b1111_1100_0000_0000;
|
||||
const R2_MASK: u16 = 0b0000_0011_1111_0000;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct MemCursor<'mem> {
|
||||
cursor: Cursor<&'mem [u8]>,
|
||||
}
|
||||
|
||||
impl<'mem> MemCursor<'mem> {
|
||||
pub fn new(mem: &'mem [u8]) -> Self {
|
||||
MemCursor {
|
||||
cursor: Cursor::new(mem),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn cursor(&self) -> &Cursor<&'mem [u8]> {
|
||||
&self.cursor
|
||||
}
|
||||
|
||||
pub fn cursor_mut(&mut self) -> &mut Cursor<&'mem [u8]> {
|
||||
&mut self.cursor
|
||||
}
|
||||
|
||||
pub fn next_u8(&mut self) -> Result<u8> {
|
||||
self.read_u8().map_err(|_| VmError::MemOutOfBounds {
|
||||
addr: self.position(),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn next_u16(&mut self) -> Result<u16> {
|
||||
self.read_u16::<LE>().map_err(|_| VmError::MemOutOfBounds {
|
||||
addr: self.position(),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn next_u32(&mut self) -> Result<u32> {
|
||||
self.read_u32::<LE>().map_err(|_| VmError::MemOutOfBounds {
|
||||
addr: self.position(),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn next_u64(&mut self) -> Result<u64> {
|
||||
self.read_u64::<LE>().map_err(|_| VmError::MemOutOfBounds {
|
||||
addr: self.position(),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn next_regs(&mut self) -> Result<(Reg, Reg)> {
|
||||
let next16 = self.next_u16()?;
|
||||
let r1 = ((R1_MASK & next16) >> 10) as Reg;
|
||||
let r2 = ((R2_MASK & next16) >> 4) as Reg;
|
||||
Ok((r1, r2))
|
||||
}
|
||||
|
||||
pub fn next_reg(&mut self) -> Result<Reg> {
|
||||
let next16 = self.next_u16()?;
|
||||
let r1 = ((R1_MASK & next16) >> 10) as Reg;
|
||||
Ok(r1)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'mem> Deref for MemCursor<'mem> {
|
||||
type Target = Cursor<&'mem [u8]>;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
self.cursor()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'mem> DerefMut for MemCursor<'mem> {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
self.cursor_mut()
|
||||
}
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
use snafu::Snafu;
|
||||
use std::{fmt::Debug, io};
|
||||
|
||||
#[derive(Debug, Snafu)]
|
||||
pub enum AssembleError {
|
||||
#[snafu(display("IO error: {}", source))]
|
||||
Io { source: io::Error },
|
||||
|
||||
#[snafu(display("duplicate symbol name: {}", name))]
|
||||
DuplicateSymbol { name: String },
|
||||
|
||||
#[snafu(display("duplicate exported symbol name: {}", name))]
|
||||
DuplicateExportSymbol { name: String },
|
||||
}
|
||||
|
||||
pub type Result<T, E = AssembleError> = std::result::Result<T, E>;
|
||||
@@ -1,360 +0,0 @@
|
||||
pub mod error;
|
||||
|
||||
use crate::vm::{
|
||||
inst::*,
|
||||
obj::{assemble::error::*, obj::*, syn::ast::*},
|
||||
reg::Reg,
|
||||
};
|
||||
use byteorder::{WriteBytesExt, LE};
|
||||
use std::{
|
||||
collections::{HashMap, HashSet},
|
||||
convert::TryFrom,
|
||||
io::Cursor,
|
||||
mem,
|
||||
};
|
||||
|
||||
pub const LAYOUT_VERSION: u32 = 0;
|
||||
|
||||
impl TryFrom<&'_ Vec<SectionBlock>> for Object {
|
||||
type Error = AssembleError;
|
||||
|
||||
fn try_from(other: &Vec<SectionBlock>) -> Result<Self, Self::Error> {
|
||||
// Assemble an AST to an object
|
||||
Assemble::new(&other).assemble()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Assemble<'a> {
|
||||
ast: &'a Vec<SectionBlock>,
|
||||
symbols: SymbolTable,
|
||||
}
|
||||
|
||||
impl<'a> Assemble<'a> {
|
||||
pub fn new(ast: &'a Vec<SectionBlock>) -> Self {
|
||||
Assemble {
|
||||
ast,
|
||||
symbols: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn assemble(&mut self) -> Result<Object> {
|
||||
let mut sections = Vec::new();
|
||||
|
||||
// gather global symbols
|
||||
for block in self.ast.iter() {
|
||||
let exports = Self::gather_symbols(block, true)?;
|
||||
// check if there are any duplicated exports
|
||||
{
|
||||
let export_keys = exports.keys().collect::<HashSet<&String>>();
|
||||
let global_keys = self.symbols.globals().keys().collect::<HashSet<&String>>();
|
||||
if let Some(key) = export_keys.intersection(&global_keys).next() {
|
||||
return Err(AssembleError::DuplicateExportSymbol {
|
||||
name: key.to_string(),
|
||||
});
|
||||
}
|
||||
}
|
||||
self.symbols.globals_mut().extend(exports);
|
||||
}
|
||||
|
||||
for block in self.ast.iter() {
|
||||
let locals = Self::gather_symbols(block, false)?;
|
||||
self.symbols.replace_locals(locals);
|
||||
match block {
|
||||
SectionBlock::Data { org, body } | SectionBlock::Code { org, body } => {
|
||||
let mut bytes = Vec::new();
|
||||
for line in body {
|
||||
match line {
|
||||
Line::Inst(inst) => {
|
||||
bytes.extend(self.assemble_inst(inst));
|
||||
}
|
||||
Line::LabelDef(_) => { /* no-op */ }
|
||||
Line::ImmValue(value) => {
|
||||
let value =
|
||||
self.get_value(value).expect("TODO : value label not found");
|
||||
bytes.extend(&value.to_le_bytes());
|
||||
}
|
||||
Line::Export(_) => { /* no-op */ }
|
||||
}
|
||||
}
|
||||
let (start, end) = match org {
|
||||
SectionOrg::Start(start) => (*start, start + bytes.len() as u64),
|
||||
SectionOrg::Range(start, end) => (*start, *end),
|
||||
};
|
||||
|
||||
let section = match block {
|
||||
SectionBlock::Data { .. } => Section::Data {
|
||||
start,
|
||||
end,
|
||||
contents: bytes,
|
||||
},
|
||||
SectionBlock::Code { .. } => Section::Code {
|
||||
start,
|
||||
end,
|
||||
contents: bytes,
|
||||
},
|
||||
SectionBlock::Meta { .. } => unreachable!(),
|
||||
};
|
||||
sections.push(section);
|
||||
}
|
||||
SectionBlock::Meta { entries } => {
|
||||
let entries = entries
|
||||
.iter()
|
||||
.map(|(name, value)| {
|
||||
(
|
||||
name.to_string(),
|
||||
self.get_value(value).expect("TODO : value label not found"),
|
||||
)
|
||||
})
|
||||
.collect();
|
||||
sections.push(Section::Meta { entries });
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(Object {
|
||||
version: LAYOUT_VERSION,
|
||||
sections,
|
||||
})
|
||||
}
|
||||
|
||||
fn gather_symbols(block: &SectionBlock, export: bool) -> Result<HashMap<String, u64>> {
|
||||
match block {
|
||||
SectionBlock::Data { org, body, .. } | SectionBlock::Code { org, body, .. } => {
|
||||
let mut exports = HashSet::new();
|
||||
let mut labels = HashMap::new();
|
||||
let mut pos = match org {
|
||||
SectionOrg::Start(start) | SectionOrg::Range(start, _) => (*start) as usize,
|
||||
};
|
||||
for line in body.iter() {
|
||||
match line {
|
||||
Line::Inst(inst) => {
|
||||
pos += inst.len();
|
||||
}
|
||||
Line::LabelDef(label) => {
|
||||
if labels.contains_key(label) {
|
||||
return Err(AssembleError::DuplicateSymbol {
|
||||
name: label.to_string(),
|
||||
});
|
||||
} else {
|
||||
labels.insert(label.to_string(), pos as u64);
|
||||
}
|
||||
}
|
||||
Line::ImmValue(_) => {
|
||||
pos += 8;
|
||||
}
|
||||
Line::Export(name) => {
|
||||
if export {
|
||||
exports.insert(name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// TODO : make sure we aren't trying to export anything that doesn't exist
|
||||
|
||||
// only return exports if specified
|
||||
if export {
|
||||
labels.retain(|k, _| exports.contains(k));
|
||||
}
|
||||
Ok(labels)
|
||||
}
|
||||
SectionBlock::Meta { .. } => Ok(Default::default()),
|
||||
}
|
||||
}
|
||||
|
||||
fn get_value(&self, value: &ImmValue) -> Option<u64> {
|
||||
match value {
|
||||
ImmValue::Number(n) => Some(*n),
|
||||
ImmValue::Label(s) => self.symbols.get(s),
|
||||
}
|
||||
}
|
||||
|
||||
fn assemble_inst(&self, inst: &Inst) -> Vec<u8> {
|
||||
let mut builder = InstBuilder::default();
|
||||
builder = match inst {
|
||||
Inst::Add(r1, r2) => builder.op(ADD).r1(*r1).r2(*r2),
|
||||
Inst::Mul(r1, r2) => builder.op(MUL).r1(*r1).r2(*r2),
|
||||
Inst::Div(r1, r2) => builder.op(DIV).r1(*r1).r2(*r2),
|
||||
Inst::Mod(r1, r2) => builder.op(MOD).r1(*r1).r2(*r2),
|
||||
Inst::INeg(r1) => builder.op(INEG).r1(*r1),
|
||||
Inst::And(r1, r2) => builder.op(AND).r1(*r1).r2(*r2),
|
||||
Inst::Or(r1, r2) => builder.op(OR).r1(*r1).r2(*r2),
|
||||
Inst::Inv(r1) => builder.op(INV).r1(*r1),
|
||||
Inst::Not(r1) => builder.op(NOT).r1(*r1),
|
||||
Inst::Xor(r1, r2) => builder.op(XOR).r1(*r1).r2(*r2),
|
||||
Inst::Shl(r1, r2) => builder.op(SHL).r1(*r1).r2(*r2),
|
||||
Inst::Shr(r1, r2) => builder.op(SHR).r1(*r1).r2(*r2),
|
||||
Inst::CmpEq(r1, r2) => builder.op(CMPEQ).r1(*r1).r2(*r2),
|
||||
Inst::CmpLt(r1, r2) => builder.op(CMPLT).r1(*r1).r2(*r2),
|
||||
Inst::Jmp(r1) => builder.op(JMP).r1(*r1),
|
||||
Inst::Jz(r1) => builder.op(JZ).r1(*r1),
|
||||
Inst::Jnz(r1) => builder.op(JNZ).r1(*r1),
|
||||
Inst::Load(r1, r2) => builder.op(LOAD).r1(*r1).r2(*r2),
|
||||
Inst::Store(r1, r2) => builder.op(STORE).r1(*r1).r2(*r2),
|
||||
Inst::StoreImm(r1, imm) => match imm {
|
||||
ImmValue::Number(num) => {
|
||||
if *num > (u32::max_value() as u64) {
|
||||
builder.op(STOREIMM64).imm64(*num)
|
||||
} else {
|
||||
builder.op(STOREIMM32).imm32(*num as u32)
|
||||
}
|
||||
}
|
||||
ImmValue::Label(name) => {
|
||||
let imm = self.symbols.get(name).expect("TODO: value label not found");
|
||||
builder.op(STOREIMM64).imm64(imm)
|
||||
}
|
||||
}
|
||||
.r1(*r1),
|
||||
Inst::StoreImm32(r1, imm) => {
|
||||
let imm = self.get_value(imm).expect("TODO : value label not found");
|
||||
let imm_truncated = (imm & 0xFFFFFFFF) as u32;
|
||||
// TODO compile-time warnings
|
||||
if imm != (imm_truncated as u64) {
|
||||
eprintln!(
|
||||
"WARNING: immediate 32 bit value being truncated ({:#x} to {:#x})",
|
||||
imm, imm_truncated
|
||||
)
|
||||
}
|
||||
builder.op(STOREIMM32).imm32(imm_truncated).r1(*r1)
|
||||
}
|
||||
Inst::StoreImm64(r1, imm) => {
|
||||
let imm = self.get_value(imm).expect("TODO : value label not found");
|
||||
builder.op(STOREIMM64).imm64(imm).r1(*r1)
|
||||
}
|
||||
Inst::MemCopy(r1, r2) => builder.op(MEMCOPY).r1(*r1).r2(*r2),
|
||||
Inst::RegCopy(r1, r2) => builder.op(REGCOPY).r1(*r1).r2(*r2),
|
||||
Inst::Nop => builder.op(NOP),
|
||||
Inst::Halt => builder.op(HALT),
|
||||
};
|
||||
|
||||
builder.finish()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
struct InstBuilder {
|
||||
op: Option<InstOp>,
|
||||
r1: Option<Reg>,
|
||||
r2: Option<Reg>,
|
||||
imm32: Option<u32>,
|
||||
imm64: Option<u64>,
|
||||
}
|
||||
|
||||
impl InstBuilder {
|
||||
fn op(mut self, op: InstOp) -> Self {
|
||||
self.op = Some(op);
|
||||
self
|
||||
}
|
||||
|
||||
fn r1(mut self, r1: Reg) -> Self {
|
||||
self.r1 = Some(r1);
|
||||
self
|
||||
}
|
||||
|
||||
fn r2(mut self, r2: Reg) -> Self {
|
||||
self.r2 = Some(r2);
|
||||
self
|
||||
}
|
||||
|
||||
fn imm32(mut self, imm32: u32) -> Self {
|
||||
self.imm32 = Some(imm32);
|
||||
self
|
||||
}
|
||||
|
||||
fn imm64(mut self, imm64: u64) -> Self {
|
||||
self.imm64 = Some(imm64);
|
||||
self
|
||||
}
|
||||
|
||||
fn finish(self) -> Vec<u8> {
|
||||
let mut cursor = Cursor::new(Vec::new());
|
||||
let InstBuilder {
|
||||
op,
|
||||
r1,
|
||||
r2,
|
||||
imm32,
|
||||
imm64,
|
||||
} = self;
|
||||
|
||||
let op = op.expect("no op specified");
|
||||
cursor.write_u16::<LE>(op).unwrap();
|
||||
match (r1, r2, imm32, imm64) {
|
||||
(Some(r1), Some(r2), None, None) => {
|
||||
let tail = ((r1 as u16) << 10) | ((r2 as u16) << 4);
|
||||
cursor.write_u16::<LE>(tail).unwrap();
|
||||
}
|
||||
(Some(r1), None, None, None) => {
|
||||
let tail = (r1 as u16) << 10;
|
||||
cursor.write_u16::<LE>(tail).unwrap();
|
||||
}
|
||||
(Some(r1), None, Some(imm32), None) => {
|
||||
let tail = (r1 as u16) << 10;
|
||||
cursor.write_u16::<LE>(tail).unwrap();
|
||||
cursor.write_u32::<LE>(imm32).unwrap();
|
||||
}
|
||||
(Some(r1), None, None, Some(imm64)) => {
|
||||
let tail = (r1 as u16) << 10;
|
||||
cursor.write_u16::<LE>(tail).unwrap();
|
||||
cursor.write_u32::<LE>(0).unwrap();
|
||||
cursor.write_u64::<LE>(imm64).unwrap();
|
||||
}
|
||||
(_, _, _, _) if op == HALT || op == NOP => {}
|
||||
(_, _, _, _) => {
|
||||
panic!(
|
||||
r#"invalid instruction combo for opcode 0x{:04x}:
|
||||
r1 : {:?}
|
||||
r2 : {:?}
|
||||
imm32 : {:?}
|
||||
imm64 : {:?}"#,
|
||||
op, r1, r2, imm32, imm64
|
||||
);
|
||||
}
|
||||
}
|
||||
cursor.into_inner()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
struct SymbolTable {
|
||||
globals: HashMap<String, u64>,
|
||||
locals: HashMap<String, u64>,
|
||||
}
|
||||
|
||||
impl SymbolTable {
|
||||
pub fn new() -> Self {
|
||||
Default::default()
|
||||
}
|
||||
|
||||
pub fn globals(&self) -> &HashMap<String, u64> {
|
||||
&self.globals
|
||||
}
|
||||
|
||||
pub fn locals(&self) -> &HashMap<String, u64> {
|
||||
&self.locals
|
||||
}
|
||||
|
||||
pub fn globals_mut(&mut self) -> &mut HashMap<String, u64> {
|
||||
&mut self.globals
|
||||
}
|
||||
|
||||
pub fn locals_mut(&mut self) -> &mut HashMap<String, u64> {
|
||||
&mut self.locals
|
||||
}
|
||||
|
||||
pub fn insert_global(&mut self, name: String, value: u64) -> Option<u64> {
|
||||
self.globals_mut().insert(name, value)
|
||||
}
|
||||
|
||||
pub fn insert_local(&mut self, name: String, value: u64) -> Option<u64> {
|
||||
self.locals_mut().insert(name, value)
|
||||
}
|
||||
|
||||
pub fn replace_locals(&mut self, locals: HashMap<String, u64>) -> HashMap<String, u64> {
|
||||
mem::replace(self.locals_mut(), locals)
|
||||
}
|
||||
|
||||
pub fn get(&self, name: &String) -> Option<u64> {
|
||||
self.locals
|
||||
.get(name)
|
||||
.or_else(|| self.globals.get(name))
|
||||
.copied()
|
||||
}
|
||||
}
|
||||
@@ -1,147 +0,0 @@
|
||||
use crate::vm::obj::syn::error::{ParseError, Result};
|
||||
use byteorder::{ReadBytesExt, LE};
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
convert::{TryFrom, TryInto},
|
||||
fmt::Debug,
|
||||
io::{Cursor, Read},
|
||||
};
|
||||
|
||||
pub const MAGIC: u64 = 0xDEAD_BEA7_BA5E_BA11;
|
||||
const OBJECT_HEADER_LEN: usize = 16; // 8 + 4 + 4
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Object {
|
||||
pub version: u32,
|
||||
pub sections: Vec<Section>,
|
||||
}
|
||||
|
||||
impl Object {
|
||||
pub fn from_bytes(bytes: &[u8]) -> Result<Self> {
|
||||
let mut cursor = Cursor::new(bytes);
|
||||
let magic = cursor.read_u64::<LE>()?;
|
||||
if magic != magic {
|
||||
return Err(ParseError::WrongMagic);
|
||||
}
|
||||
let version = cursor.read_u32::<LE>()?;
|
||||
let section_count = cursor.read_u32::<LE>()?;
|
||||
|
||||
let mut sections = Vec::new();
|
||||
for _ in 0..section_count {
|
||||
let section = Section::from_bytes(&mut cursor)?;
|
||||
sections.push(section);
|
||||
}
|
||||
Ok(Object { version, sections })
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! section_kind {
|
||||
(
|
||||
pub enum $enum_name:ident {
|
||||
$($name:ident = $value:expr),* $(,)?
|
||||
}
|
||||
) => {
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
#[repr(u8)]
|
||||
pub enum $enum_name {
|
||||
$($name = $value),*
|
||||
}
|
||||
|
||||
impl TryFrom<u8> for SectionKind {
|
||||
type Error = ParseError;
|
||||
|
||||
fn try_from(other: u8) -> std::result::Result<Self, Self::Error> {
|
||||
match other {
|
||||
$(
|
||||
$value => Ok($enum_name::$name),
|
||||
)*
|
||||
_ => Err(ParseError::UnknownSectionKind { kind: other }),
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
section_kind! {
|
||||
pub enum SectionKind {
|
||||
Data = 0x00,
|
||||
Code = 0x10,
|
||||
Meta = 0xFF,
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum Section {
|
||||
Data {
|
||||
start: u64,
|
||||
end: u64,
|
||||
contents: Vec<u8>,
|
||||
},
|
||||
Code {
|
||||
start: u64,
|
||||
end: u64,
|
||||
contents: Vec<u8>,
|
||||
},
|
||||
Meta {
|
||||
entries: HashMap<String, u64>,
|
||||
},
|
||||
}
|
||||
|
||||
impl Section {
|
||||
fn from_bytes(cursor: &mut Cursor<&[u8]>) -> Result<Self> {
|
||||
let len = cursor.read_u64::<LE>()?;
|
||||
let start = cursor.position() as usize;
|
||||
let end = start + len as usize;
|
||||
|
||||
let bytes = &cursor.get_ref()[start..end];
|
||||
let kind: SectionKind = cursor.read_u8()?.try_into()?;
|
||||
match kind {
|
||||
SectionKind::Data => Section::data_section_from_bytes(bytes),
|
||||
SectionKind::Code => Section::code_section_from_bytes(bytes),
|
||||
SectionKind::Meta => Section::meta_section_from_bytes(bytes),
|
||||
}
|
||||
}
|
||||
|
||||
fn data_section_from_bytes(bytes: &[u8]) -> Result<Self> {
|
||||
let mut cursor = Cursor::new(bytes);
|
||||
let start = cursor.read_u64::<LE>()?;
|
||||
let end = cursor.read_u64::<LE>()?;
|
||||
let contents = &bytes[cursor.position() as usize..];
|
||||
Ok(Section::Data {
|
||||
start,
|
||||
end,
|
||||
contents: From::from(contents),
|
||||
})
|
||||
}
|
||||
|
||||
fn code_section_from_bytes(bytes: &[u8]) -> Result<Self> {
|
||||
let mut cursor = Cursor::new(bytes);
|
||||
let start = cursor.read_u64::<LE>()?;
|
||||
let end = cursor.read_u64::<LE>()?;
|
||||
let contents = &bytes[cursor.position() as usize..];
|
||||
Ok(Section::Code {
|
||||
start,
|
||||
end,
|
||||
contents: From::from(contents),
|
||||
})
|
||||
}
|
||||
|
||||
fn meta_section_from_bytes(bytes: &[u8]) -> Result<Self> {
|
||||
let mut cursor = Cursor::new(bytes);
|
||||
let entry_count = cursor.read_u64::<LE>()?;
|
||||
let mut entries = HashMap::new();
|
||||
for _ in 0..entry_count {
|
||||
// key
|
||||
let key_len = cursor.read_u64::<LE>()?;
|
||||
let mut key_bytes = vec![0u8; key_len as usize];
|
||||
cursor.read_exact(&mut key_bytes)?;
|
||||
let key = String::from_utf8(key_bytes)?;
|
||||
|
||||
// value
|
||||
let value = cursor.read_u64::<LE>()?;
|
||||
entries.insert(key, value);
|
||||
}
|
||||
Ok(Section::Meta { entries })
|
||||
}
|
||||
}
|
||||
@@ -1,118 +0,0 @@
|
||||
use crate::vm::{inst::*, reg::Reg};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum SectionBlock {
|
||||
Data {
|
||||
org: SectionOrg,
|
||||
body: Vec<Line>,
|
||||
},
|
||||
Code {
|
||||
org: SectionOrg,
|
||||
body: Vec<Line>,
|
||||
},
|
||||
Meta {
|
||||
entries: Vec<(String, ImmValue)>,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum SectionOrg {
|
||||
Start(u64),
|
||||
Range(u64, u64),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum Line {
|
||||
Inst(Inst),
|
||||
LabelDef(String),
|
||||
ImmValue(ImmValue),
|
||||
Export(String),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum ImmValue {
|
||||
Number(u64),
|
||||
Label(String),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum Inst {
|
||||
Add(Reg, Reg),
|
||||
Mul(Reg, Reg),
|
||||
Div(Reg, Reg),
|
||||
Mod(Reg, Reg),
|
||||
INeg(Reg),
|
||||
And(Reg, Reg),
|
||||
Or(Reg, Reg),
|
||||
Inv(Reg),
|
||||
Not(Reg),
|
||||
Xor(Reg, Reg),
|
||||
Shl(Reg, Reg),
|
||||
Shr(Reg, Reg),
|
||||
|
||||
CmpEq(Reg, Reg),
|
||||
CmpLt(Reg, Reg),
|
||||
Jmp(Reg),
|
||||
Jz(Reg),
|
||||
Jnz(Reg),
|
||||
|
||||
Load(Reg, Reg),
|
||||
Store(Reg, Reg),
|
||||
StoreImm(Reg, ImmValue),
|
||||
StoreImm32(Reg, ImmValue),
|
||||
StoreImm64(Reg, ImmValue),
|
||||
MemCopy(Reg, Reg),
|
||||
RegCopy(Reg, Reg),
|
||||
|
||||
Nop,
|
||||
Halt,
|
||||
}
|
||||
|
||||
impl Inst {
|
||||
pub fn op(&self) -> InstOp {
|
||||
match self {
|
||||
Inst::Add(_, _) => ADD,
|
||||
Inst::Mul(_, _) => MUL,
|
||||
Inst::Div(_, _) => DIV,
|
||||
Inst::Mod(_, _) => MOD,
|
||||
Inst::INeg(_) => INEG,
|
||||
Inst::And(_, _) => AND,
|
||||
Inst::Or(_, _) => OR,
|
||||
Inst::Inv(_) => INV,
|
||||
Inst::Not(_) => NOT,
|
||||
Inst::Xor(_, _) => XOR,
|
||||
Inst::Shl(_, _) => SHL,
|
||||
Inst::Shr(_, _) => SHR,
|
||||
|
||||
Inst::CmpEq(_, _) => CMPEQ,
|
||||
Inst::CmpLt(_, _) => CMPLT,
|
||||
Inst::Jmp(_) => JMP,
|
||||
Inst::Jz(_) => JZ,
|
||||
Inst::Jnz(_) => JNZ,
|
||||
|
||||
Inst::Load(_, _) => LOAD,
|
||||
Inst::Store(_, _) => STORE,
|
||||
Inst::StoreImm(_, imm) => {
|
||||
if let ImmValue::Number(imm) = imm {
|
||||
if *imm > (u32::max_value() as u64) {
|
||||
STOREIMM64
|
||||
} else {
|
||||
STOREIMM32
|
||||
}
|
||||
} else {
|
||||
STOREIMM64
|
||||
}
|
||||
}
|
||||
Inst::StoreImm32(_, _) => STOREIMM32,
|
||||
Inst::StoreImm64(_, _) => STOREIMM64,
|
||||
Inst::MemCopy(_, _) => MEMCOPY,
|
||||
Inst::RegCopy(_, _) => REGCOPY,
|
||||
|
||||
Inst::Nop => NOP,
|
||||
Inst::Halt => HALT,
|
||||
}
|
||||
}
|
||||
pub fn len(&self) -> usize {
|
||||
inst_len(self.op())
|
||||
}
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
use lalrpop_util::lalrpop_mod;
|
||||
|
||||
lalrpop_mod!(pub parser, "/vm/obj/syn/parser.rs");
|
||||
pub mod ast;
|
||||
pub mod error;
|
||||
@@ -1,103 +0,0 @@
|
||||
use std::str::FromStr;
|
||||
use crate::vm::{
|
||||
obj::syn::ast::*,
|
||||
reg::*,
|
||||
};
|
||||
|
||||
grammar;
|
||||
|
||||
LabelDef: String = {
|
||||
<Label> ":" => <>
|
||||
}
|
||||
|
||||
// TODO : Value (ImmValue, String)
|
||||
|
||||
ImmValue: ImmValue = {
|
||||
<Label> => ImmValue::Label(<>),
|
||||
<Number> => ImmValue::Number(<>),
|
||||
}
|
||||
|
||||
Label: String = {
|
||||
r"[a-zA-Z]+" => String::from(<>),
|
||||
}
|
||||
|
||||
Number: u64 = {
|
||||
<s:r"\$[0-9]+"> => u64::from_str(&s[1..]).unwrap(),
|
||||
<s:r"\$0x[0-9a-fA-F]+"> => u64::from_str_radix(&s[3..], 16).unwrap(),
|
||||
<s:r"\$0b[01]+"> => u64::from_str_radix(&s[3..], 2).unwrap(),
|
||||
}
|
||||
|
||||
Reg: Reg = {
|
||||
r"%ip" => IP,
|
||||
r"%sp" => SP,
|
||||
r"%fp" => FP,
|
||||
r"%flags" => FLAGS,
|
||||
r"%status" => STATUS,
|
||||
r"%r[0-9]{2}" => {
|
||||
let offset = (&<>[2..]).parse::<u8>().unwrap();
|
||||
let reg = R00 + offset;
|
||||
assert!(reg < LAST_REG, "invalid register");
|
||||
reg
|
||||
}
|
||||
}
|
||||
|
||||
Inst: Inst = {
|
||||
"add" <d:Reg> "," <s:Reg> => Inst::Add(d, s),
|
||||
"mul" <d:Reg> "," <s:Reg> => Inst::Mul(d, s),
|
||||
"div" <d:Reg> "," <s:Reg> =>Inst::Div(d, s),
|
||||
"mod" <d:Reg> "," <s:Reg> => Inst::Mod(d, s),
|
||||
"ineg" <d:Reg> => Inst::INeg(d),
|
||||
"and" <d:Reg> "," <s:Reg> => Inst::And(d, s),
|
||||
"or" <d:Reg> "," <s:Reg> => Inst::Or(d, s),
|
||||
"xor" <d:Reg> "," <s:Reg> => Inst::Xor(d, s),
|
||||
"shl" <d:Reg> "," <s:Reg> => Inst::Shl(d, s),
|
||||
"shr" <d:Reg> "," <s:Reg> => Inst::Shr(d, s),
|
||||
"cmpeq" <d:Reg> "," <s:Reg> => Inst::CmpEq(d, s),
|
||||
"cmplt" <d:Reg> "," <s:Reg> => Inst::CmpLt(d, s),
|
||||
"jmp" <d:Reg> => Inst::Jmp(d),
|
||||
"jz" <d:Reg> => Inst::Jz(d),
|
||||
"jnz" <d:Reg> => Inst::Jnz(d),
|
||||
"load" <d:Reg> "," <s:Reg> => Inst::Load(d, s),
|
||||
"store" <d:Reg> "," <s:Reg> => Inst::Store(d, s),
|
||||
"storeimm" <d:Reg> "," <s:ImmValue> => Inst::StoreImm(d, s),
|
||||
"storeimm32" <d:Reg> "," <s:ImmValue> => Inst::StoreImm32(d, s),
|
||||
"storeimm64" <d:Reg> "," <s:ImmValue> => Inst::StoreImm64(d, s),
|
||||
"memcopy" <d:Reg> "," <s:Reg> => Inst::MemCopy(d, s),
|
||||
"regcopy" <d:Reg> "," <s:Reg> => Inst::RegCopy(d, s),
|
||||
"nop" => Inst::Nop,
|
||||
"halt" => Inst::Halt,
|
||||
}
|
||||
|
||||
Line: Line = {
|
||||
<Inst> => Line::Inst(<>),
|
||||
<LabelDef> => Line::LabelDef(<>),
|
||||
<ImmValue> => Line::ImmValue(<>),
|
||||
r"\.export" <Label> => Line::Export(<>),
|
||||
}
|
||||
|
||||
MetaLine: (String, ImmValue) = {
|
||||
<name:Label> ":" <value:ImmValue> => (name, value),
|
||||
}
|
||||
|
||||
SectionOrg: SectionOrg = {
|
||||
<start:Number> => SectionOrg::Start(start),
|
||||
<start:Number> r"\.\." <end:Number> => SectionOrg::Range(start, end),
|
||||
}
|
||||
|
||||
Section: SectionBlock = {
|
||||
"data" <org:SectionOrg> "{" <body:Line*> "}" => {
|
||||
SectionBlock::Data { org, body }
|
||||
},
|
||||
|
||||
"code" <org:SectionOrg> "{" <body:Line*> "}" => {
|
||||
SectionBlock::Code { org, body }
|
||||
},
|
||||
|
||||
"meta" "{" <entries:MetaLine*> "}" => {
|
||||
SectionBlock::Meta { entries, }
|
||||
}
|
||||
}
|
||||
|
||||
pub Sections: Vec<SectionBlock> = {
|
||||
<Section*> => <>,
|
||||
}
|
||||
105
src/vm/reg.rs
105
src/vm/reg.rs
@@ -1,105 +0,0 @@
|
||||
macro_rules! registers {
|
||||
{
|
||||
$($variant:ident = $value:expr),* $(,)?
|
||||
} => {
|
||||
pub type Reg = u8;
|
||||
|
||||
$(
|
||||
pub const $variant: Reg = $value;
|
||||
)*
|
||||
|
||||
pub fn reg_name(reg: Reg) -> Option<&'static str> {
|
||||
match reg {
|
||||
$(
|
||||
$value => Some(stringify!($variant)),
|
||||
)*
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
registers! {
|
||||
// https://crates.io/crates/packed_struct
|
||||
// TODO : check this muffugin shit out!!
|
||||
|
||||
// Instruction pointer
|
||||
IP = 0,
|
||||
|
||||
// Stack pointer
|
||||
SP = 1,
|
||||
|
||||
// Frame pointer
|
||||
FP = 2,
|
||||
|
||||
// Flags
|
||||
FLAGS = 3,
|
||||
|
||||
// Zero
|
||||
NULL = 4,
|
||||
|
||||
UNUSED01 = 5,
|
||||
UNUSED02 = 6,
|
||||
UNUSED03 = 7,
|
||||
UNUSED04 = 8,
|
||||
UNUSED05 = 9,
|
||||
UNUSED06 = 10,
|
||||
UNUSED07 = 11,
|
||||
UNUSED08 = 12,
|
||||
|
||||
// General status code
|
||||
STATUS = 13,
|
||||
|
||||
R00 = 14,
|
||||
R01 = 15,
|
||||
R02 = 16,
|
||||
R03 = 17,
|
||||
R04 = 18,
|
||||
R05 = 19,
|
||||
R06 = 20,
|
||||
R07 = 21,
|
||||
R08 = 22,
|
||||
R09 = 23,
|
||||
R10 = 24,
|
||||
R11 = 25,
|
||||
R12 = 26,
|
||||
R13 = 27,
|
||||
R14 = 28,
|
||||
R15 = 29,
|
||||
R16 = 30,
|
||||
R17 = 31,
|
||||
R18 = 32,
|
||||
R19 = 33,
|
||||
R20 = 34,
|
||||
R21 = 35,
|
||||
R22 = 36,
|
||||
R23 = 37,
|
||||
R24 = 38,
|
||||
R25 = 39,
|
||||
R26 = 40,
|
||||
R27 = 41,
|
||||
R28 = 42,
|
||||
R29 = 43,
|
||||
R30 = 44,
|
||||
R31 = 45,
|
||||
R32 = 46,
|
||||
R33 = 47,
|
||||
R34 = 48,
|
||||
R35 = 49,
|
||||
R36 = 50,
|
||||
R37 = 51,
|
||||
R38 = 52,
|
||||
R39 = 53,
|
||||
R40 = 54,
|
||||
R41 = 55,
|
||||
R42 = 56,
|
||||
R43 = 57,
|
||||
R44 = 58,
|
||||
R45 = 59,
|
||||
R46 = 60,
|
||||
R47 = 61,
|
||||
R48 = 62,
|
||||
R49 = 63,
|
||||
}
|
||||
|
||||
pub const LAST_REG: Reg = R49;
|
||||
@@ -1,96 +0,0 @@
|
||||
use crate::vm::{
|
||||
error::*,
|
||||
inst::*,
|
||||
mem::MemCursor,
|
||||
reg::Reg,
|
||||
vm::{HalfWord, Word},
|
||||
};
|
||||
|
||||
pub trait VisitInst {
|
||||
type Out;
|
||||
|
||||
fn cursor(&self) -> MemCursor;
|
||||
fn add(&mut self, r1: Reg, r2: Reg) -> Result<Self::Out>;
|
||||
fn mul(&mut self, r1: Reg, r2: Reg) -> Result<Self::Out>;
|
||||
fn div(&mut self, r1: Reg, r2: Reg) -> Result<Self::Out>;
|
||||
fn mod_(&mut self, r1: Reg, r2: Reg) -> Result<Self::Out>;
|
||||
fn ineg(&mut self, r1: Reg) -> Result<Self::Out>;
|
||||
fn and(&mut self, r1: Reg, r2: Reg) -> Result<Self::Out>;
|
||||
fn or(&mut self, r1: Reg, r2: Reg) -> Result<Self::Out>;
|
||||
fn inv(&mut self, r1: Reg) -> Result<Self::Out>;
|
||||
fn not(&mut self, r1: Reg) -> Result<Self::Out>;
|
||||
fn xor(&mut self, r1: Reg, r2: Reg) -> Result<Self::Out>;
|
||||
fn shl(&mut self, r1: Reg, r2: Reg) -> Result<Self::Out>;
|
||||
fn shr(&mut self, r1: Reg, r2: Reg) -> Result<Self::Out>;
|
||||
fn cmpeq(&mut self, r1: Reg, r2: Reg) -> Result<Self::Out>;
|
||||
fn cmplt(&mut self, r1: Reg, r2: Reg) -> Result<Self::Out>;
|
||||
fn jmp(&mut self, r1: Reg) -> Result<Self::Out>;
|
||||
fn jz(&mut self, r1: Reg) -> Result<Self::Out>;
|
||||
fn jnz(&mut self, r1: Reg) -> Result<Self::Out>;
|
||||
fn load(&mut self, r1: Reg, r2: Reg) -> Result<Self::Out>;
|
||||
fn regcopy(&mut self, r1: Reg, r2: Reg) -> Result<Self::Out>;
|
||||
fn storeimm64(&mut self, r1: Reg, w1: Word) -> Result<Self::Out>;
|
||||
fn storeimm32(&mut self, r1: Reg, w1: HalfWord) -> Result<Self::Out>;
|
||||
fn memcopy(&mut self, r1: Reg, r2: Reg) -> Result<Self::Out>;
|
||||
fn store(&mut self, r1: Reg, r2: Reg) -> Result<Self::Out>;
|
||||
fn halt(&mut self) -> Result<Self::Out>;
|
||||
fn nop(&mut self) -> Result<Self::Out>;
|
||||
|
||||
fn visit_inst(&mut self) -> Result<Self::Out> {
|
||||
let mut cursor = self.cursor();
|
||||
//panic!("cursor pos: {}", cursor.position());
|
||||
let op = cursor.next_u16()?;
|
||||
|
||||
macro_rules! r1_r2_inst {
|
||||
($fun:ident) => {{
|
||||
let (r1, r2) = cursor.next_regs()?;
|
||||
self.$fun(r1, r2)
|
||||
}};
|
||||
}
|
||||
macro_rules! r1_inst {
|
||||
($fun:ident) => {{
|
||||
let r1 = cursor.next_reg()?;
|
||||
self.$fun(r1)
|
||||
}};
|
||||
}
|
||||
|
||||
match op {
|
||||
ADD => r1_r2_inst!(add),
|
||||
MUL => r1_r2_inst!(mul),
|
||||
DIV => r1_r2_inst!(div),
|
||||
MOD => r1_r2_inst!(mod_),
|
||||
INEG => r1_inst!(ineg),
|
||||
AND => r1_r2_inst!(and),
|
||||
OR => r1_r2_inst!(or),
|
||||
INV => r1_inst!(inv),
|
||||
NOT => r1_inst!(not),
|
||||
XOR => r1_r2_inst!(xor),
|
||||
SHL => r1_r2_inst!(shl),
|
||||
SHR => r1_r2_inst!(shr),
|
||||
CMPEQ => r1_r2_inst!(cmpeq),
|
||||
CMPLT => r1_r2_inst!(cmplt),
|
||||
JMP => r1_inst!(jmp),
|
||||
JZ => r1_inst!(jz),
|
||||
JNZ => r1_inst!(jnz),
|
||||
LOAD => r1_r2_inst!(load),
|
||||
REGCOPY => r1_r2_inst!(regcopy),
|
||||
STOREIMM64 => {
|
||||
let r1 = cursor.next_reg()?;
|
||||
// skip
|
||||
cursor.next_u32()?;
|
||||
let imm = cursor.next_u64()?;
|
||||
self.storeimm64(r1, imm)
|
||||
}
|
||||
STOREIMM32 => {
|
||||
let r1 = cursor.next_reg()?;
|
||||
let imm = cursor.next_u32()?;
|
||||
self.storeimm32(r1, imm)
|
||||
}
|
||||
MEMCOPY => r1_r2_inst!(memcopy),
|
||||
STORE => r1_r2_inst!(store),
|
||||
HALT => self.halt(),
|
||||
NOP => self.nop(),
|
||||
_ => Err(VmError::IllegalOp { op }),
|
||||
}
|
||||
}
|
||||
}
|
||||
205
src/vm/vm.rs
205
src/vm/vm.rs
@@ -1,205 +0,0 @@
|
||||
use crate::vm::{error::*, flags::*, inst::InstOp, mem::*, obj::obj::*, reg::*};
|
||||
use byteorder::{WriteBytesExt, LE};
|
||||
use std::{io::Cursor, mem};
|
||||
|
||||
pub type Word = u64;
|
||||
pub type HalfWord = u32;
|
||||
pub type Registers = [Word; 64];
|
||||
pub type Addr = u64;
|
||||
|
||||
pub struct Vm {
|
||||
pub(super) mem: Vec<u8>,
|
||||
pub(super) registers: Registers,
|
||||
}
|
||||
|
||||
impl Vm {
|
||||
pub fn new() -> Self {
|
||||
Vm {
|
||||
mem: Default::default(),
|
||||
registers: [0; 64],
|
||||
}
|
||||
}
|
||||
|
||||
/// Loads an object into this VM, clearing out all previous memory and resetting the registers.
|
||||
pub fn load_object(&mut self, object: Object, max_mem: usize) -> Result<()> {
|
||||
self.registers = [0; 64];
|
||||
// determine memory spread
|
||||
let mem_size = object
|
||||
.sections
|
||||
.iter()
|
||||
.filter_map(|s| match s {
|
||||
Section::Data { end, .. } | Section::Code { end, .. } => Some(*end),
|
||||
Section::Meta { .. } => None,
|
||||
})
|
||||
.max()
|
||||
.unwrap_or(0);
|
||||
if mem_size > (max_mem as u64) {
|
||||
todo!("raise max memory error");
|
||||
}
|
||||
self.mem = vec![0; mem_size as usize];
|
||||
|
||||
let mut entry = 0;
|
||||
// write sections to memory
|
||||
for section in object.sections.into_iter() {
|
||||
match section {
|
||||
Section::Data {
|
||||
start,
|
||||
contents,
|
||||
..
|
||||
}
|
||||
| Section::Code {
|
||||
start,
|
||||
contents,
|
||||
..
|
||||
} => {
|
||||
let start = start as usize;
|
||||
for (value, dest) in contents.into_iter().zip(&mut self.mem[start..])
|
||||
{
|
||||
*dest = value;
|
||||
}
|
||||
}
|
||||
Section::Meta { entries } => {
|
||||
if let Some(e) = entries.get("entry") {
|
||||
// set the entry point
|
||||
entry = *e;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
self.set_reg(IP, entry);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn mem_cursor(&self, at: usize) -> MemCursor {
|
||||
let mut cursor = MemCursor::new(&self.mem);
|
||||
cursor.set_position(at as u64);
|
||||
cursor
|
||||
}
|
||||
|
||||
pub fn run(&mut self) -> Result<u64> {
|
||||
while !self.is_halted() {
|
||||
self.tick()?;
|
||||
}
|
||||
Ok(self.get_reg(STATUS))
|
||||
}
|
||||
|
||||
pub fn resume(&mut self) {
|
||||
self.remove_flags(Flags::HALT);
|
||||
}
|
||||
|
||||
pub fn is_halted(&self) -> bool {
|
||||
self.flags().contains(Flags::HALT)
|
||||
}
|
||||
|
||||
pub fn get_word(&self, addr: Addr) -> Result<Word> {
|
||||
self.check_read(addr, 8)?;
|
||||
Ok(self.mem_cursor(addr as usize).next_u64().unwrap())
|
||||
}
|
||||
|
||||
pub fn get_halfword(&self, addr: Addr) -> Result<HalfWord> {
|
||||
self.check_read(addr, 4)?;
|
||||
Ok(self.mem_cursor(addr as usize).next_u32().unwrap())
|
||||
}
|
||||
|
||||
pub fn get_inst_op(&self, addr: Addr) -> Result<InstOp> {
|
||||
self.check_read(addr, 2)?;
|
||||
Ok(self.mem_cursor(addr as usize).next_u16().unwrap())
|
||||
}
|
||||
|
||||
pub fn get_byte(&self, addr: Addr) -> Result<u8> {
|
||||
self.check_addr(addr)?;
|
||||
Ok(self.mem_cursor(addr as usize).next_u8().unwrap())
|
||||
}
|
||||
|
||||
pub fn set_word(&mut self, addr: Addr, value: Word) -> Result<()> {
|
||||
self.check_read(addr, 8)?;
|
||||
let mut cursor = Cursor::new(&mut self.mem[addr as usize..]);
|
||||
Ok(cursor.write_u64::<LE>(value).unwrap())
|
||||
}
|
||||
|
||||
pub fn set_halfword(&mut self, addr: Addr, value: HalfWord) -> Result<()> {
|
||||
self.check_read(addr, 4)?;
|
||||
let mut cursor = Cursor::new(&mut self.mem[addr as usize..]);
|
||||
Ok(cursor.write_u32::<LE>(value).unwrap())
|
||||
}
|
||||
|
||||
pub fn set_byte(&mut self, addr: Addr, value: u8) -> Result<()> {
|
||||
self.check_addr(addr)?;
|
||||
let mut cursor = Cursor::new(&mut self.mem[addr as usize..]);
|
||||
Ok(cursor.write_u8(value).unwrap())
|
||||
}
|
||||
|
||||
pub fn load(&self, reg: Reg) -> Result<Word> {
|
||||
self.get_word(self.get_reg_checked(reg)?)
|
||||
}
|
||||
|
||||
pub fn store(&mut self, reg: Reg, value: Word) -> Result<()> {
|
||||
let addr = self.get_reg_checked(reg)?;
|
||||
self.set_word(addr, value)
|
||||
}
|
||||
|
||||
pub fn get_reg_checked(&self, reg: Reg) -> Result<Word> {
|
||||
self.check_reg(reg)?;
|
||||
Ok(self.get_reg(reg))
|
||||
}
|
||||
|
||||
pub fn get_reg(&self, reg: Reg) -> Word {
|
||||
self.registers[reg as usize]
|
||||
}
|
||||
|
||||
pub fn set_reg_checked(&mut self, reg: Reg, value: Word) -> Result<Word> {
|
||||
self.check_reg(reg)?;
|
||||
Ok(self.set_reg(reg, value))
|
||||
}
|
||||
|
||||
pub fn set_reg(&mut self, reg: Reg, value: Word) -> Word {
|
||||
mem::replace(&mut self.registers[reg as usize], value)
|
||||
}
|
||||
|
||||
pub fn ip(&self) -> Word {
|
||||
self.get_reg(IP)
|
||||
}
|
||||
|
||||
pub fn flags(&self) -> Flags {
|
||||
// this is safe because it's OK if there are random bits flipped - this shouldn't happen
|
||||
// anyway, but if it does, they're ignored
|
||||
unsafe { Flags::from_bits_unchecked(self.get_reg(FLAGS)) }
|
||||
}
|
||||
|
||||
pub fn insert_flags(&mut self, flags: Flags) {
|
||||
let mut new_flags = self.flags();
|
||||
new_flags.insert(flags);
|
||||
self.set_flags(new_flags);
|
||||
}
|
||||
|
||||
pub fn remove_flags(&mut self, flags: Flags) {
|
||||
let mut new_flags = self.flags();
|
||||
new_flags.remove(flags);
|
||||
self.set_flags(new_flags);
|
||||
}
|
||||
|
||||
pub fn set_flags(&mut self, flags: Flags) {
|
||||
self.set_reg(FLAGS, flags.bits());
|
||||
}
|
||||
|
||||
fn check_addr(&self, addr: Addr) -> Result<()> {
|
||||
if addr >= (self.mem.len() as u64) {
|
||||
Err(VmError::MemOutOfBounds { addr })
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn check_read(&self, addr: Addr, len: Word) -> Result<()> {
|
||||
self.check_addr(addr)
|
||||
.and_then(|_| self.check_addr(addr + len - 1))
|
||||
}
|
||||
|
||||
fn check_reg(&self, reg: Reg) -> Result<()> {
|
||||
if reg > LAST_REG {
|
||||
Err(VmError::IllegalReg { reg })
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
31
test.asm
31
test.asm
@@ -1,31 +0,0 @@
|
||||
code $0x0 {
|
||||
main:
|
||||
storeimm32 %r00, $0xDEAD
|
||||
storeimm64 %r01, $16
|
||||
|
||||
shl %r00, %r01
|
||||
|
||||
storeimm32 %r01, $0xBEEF
|
||||
or %r00, %r01
|
||||
|
||||
storeimm64 %r01, $0xDEADBEEF
|
||||
|
||||
cmpeq %r00, %r01
|
||||
storeimm32 %r00, failure
|
||||
storeimm64 %r01, ok
|
||||
|
||||
jz %r00
|
||||
|
||||
jmp %r01
|
||||
|
||||
failure:
|
||||
storeimm32 %status, $1
|
||||
|
||||
ok: halt
|
||||
|
||||
.export main
|
||||
}
|
||||
|
||||
meta {
|
||||
entry: main
|
||||
}
|
||||
114
tests/test_arithmetic.asm
Normal file
114
tests/test_arithmetic.asm
Normal file
@@ -0,0 +1,114 @@
|
||||
.section data 0x1000 {
|
||||
number: .u8 10
|
||||
|
||||
main:
|
||||
; Test addition
|
||||
mov %status, 1
|
||||
add %r0, 1
|
||||
cmpeq %r0, 1
|
||||
jz end
|
||||
|
||||
; Test addition from a register
|
||||
mov %status, 2
|
||||
add %r0, %r0
|
||||
cmpeq %r0, 2
|
||||
jz end
|
||||
|
||||
; Test addition from a u8 location
|
||||
mov %status, 3
|
||||
add %r0, (number)u8
|
||||
cmpeq %r0, 12
|
||||
jz end
|
||||
|
||||
; Test addition overflow
|
||||
mov %status, 4
|
||||
add %r0, 0xFFFFFFFFFFFFFFFF
|
||||
cmpeq %r0, 11
|
||||
jz end
|
||||
|
||||
; Test subtraction
|
||||
mov %status, 5
|
||||
mov %r0, (number)u8
|
||||
sub %r0, 1
|
||||
cmpeq %r0, 9
|
||||
jz end
|
||||
|
||||
; Test subtraction from a register
|
||||
mov %status, 6
|
||||
mov %r1, 2
|
||||
sub %r0, %r1
|
||||
cmpeq %r0, 7
|
||||
jz end
|
||||
|
||||
; Test subtraction overflow
|
||||
mov %status, 7
|
||||
sub %r0, 8
|
||||
cmpeq %r0, 0xFFFFFFFFFFFFFFFF
|
||||
jz end
|
||||
|
||||
; Test multiplication
|
||||
mov %status, 8
|
||||
mov %r0, (number)u8
|
||||
mul %r0, 1
|
||||
cmpeq %r0, 10
|
||||
jz end
|
||||
|
||||
; Test multiplication from a register
|
||||
mov %status, 9
|
||||
mov %r1, 2
|
||||
mul %r0, %r1
|
||||
cmpeq %r0, 20
|
||||
jz end
|
||||
|
||||
; Test negative multiplication
|
||||
mov %status, 10
|
||||
mul %r0, 0xFFFFFFFFFFFFFFFF ; -1
|
||||
cmpeq %r0, 0xFFFFFFFFFFFFFFEC ; -20
|
||||
jz end
|
||||
|
||||
; Test division
|
||||
mov %status, 11
|
||||
mov %r0, (number)u8
|
||||
div %r0, 2
|
||||
cmpeq %r0, 5
|
||||
jz end
|
||||
|
||||
; Test integer division
|
||||
mov %status, 12
|
||||
mov %r1, 2
|
||||
div %r0, %r1
|
||||
cmpeq %r0, 2
|
||||
jz end
|
||||
|
||||
; Test negative division with idiv
|
||||
mov %status, 13
|
||||
idiv %r0, 0xFFFFFFFFFFFFFFFF ; -1
|
||||
cmpeq %r0, 0xFFFFFFFFFFFFFFFE ; -2
|
||||
jz end
|
||||
|
||||
; Test modulo
|
||||
mov %status, 14
|
||||
mov %r0, (number)u8
|
||||
mod %r0, 4
|
||||
cmpeq %r0, 2
|
||||
jz end
|
||||
|
||||
; Test mod from a register
|
||||
mov %status, 15
|
||||
mod %r0, %r0
|
||||
cmpeq %r0, 0
|
||||
jz end
|
||||
|
||||
; TODO : Test mod and div by zero
|
||||
|
||||
mov %status, 0
|
||||
end:
|
||||
halt
|
||||
|
||||
.export main
|
||||
}
|
||||
|
||||
.meta {
|
||||
ip: main
|
||||
}
|
||||
|
||||
80
tests/test_bitwise.asm
Normal file
80
tests/test_bitwise.asm
Normal file
@@ -0,0 +1,80 @@
|
||||
.section data 0x0 {
|
||||
flags: .u8 0b1001
|
||||
|
||||
main:
|
||||
; Test bit and
|
||||
mov %status, 1
|
||||
mov %r0, 0b1000
|
||||
and %r0, (flags)u8
|
||||
cmpeq %r0, 0b1000
|
||||
jz end
|
||||
|
||||
; Test shift right
|
||||
mov %status, 2
|
||||
shr %r0, 2
|
||||
cmpeq %r0, 0b0010
|
||||
jz end
|
||||
|
||||
; Test bit and
|
||||
mov %status, 3
|
||||
and %r0, (flags)u8
|
||||
cmpeq %r0, 0
|
||||
jz end
|
||||
|
||||
; Test bit or
|
||||
mov %status, 4
|
||||
or %r0, (flags)u8
|
||||
cmpeq %r0, (flags)u8
|
||||
jz end
|
||||
|
||||
; Test bit or
|
||||
mov %status, 5
|
||||
or %r0, 0b10
|
||||
cmpeq %r0, 0b1011
|
||||
jz end
|
||||
|
||||
; Test shift left
|
||||
mov %status, 6
|
||||
shl %r0, 1
|
||||
cmpeq %r0, 0b10110
|
||||
jz end
|
||||
|
||||
; Test xor
|
||||
mov %status, 7
|
||||
xor %r0, %r0
|
||||
cmpeq %r0, 0
|
||||
jz end
|
||||
|
||||
; Test xor
|
||||
mov %status, 8
|
||||
mov %r0, 0b1000
|
||||
xor %r0, (flags)u8
|
||||
cmpeq %r0, 1
|
||||
jz end
|
||||
|
||||
; Test inv
|
||||
mov %status, 9
|
||||
; TODO : destination size - the line below inverts all 64 bits instead of the source 8 bits
|
||||
; inv %r0, (flags)u8
|
||||
inv (flags)u8, (flags)u8
|
||||
cmpeq (flags)u8, 0b11110110
|
||||
jz end
|
||||
|
||||
; Test inv again (reset flags to their previous value)
|
||||
mov %status, 10
|
||||
inv (flags)u8, (flags)u8
|
||||
cmpeq (flags)u8, 0b00001001
|
||||
jz end
|
||||
|
||||
mov %status, 0
|
||||
end:
|
||||
halt
|
||||
|
||||
.export main
|
||||
}
|
||||
|
||||
.meta {
|
||||
ip: main
|
||||
}
|
||||
|
||||
|
||||
36
tests/test_call.asm
Normal file
36
tests/test_call.asm
Normal file
@@ -0,0 +1,36 @@
|
||||
.section data 0x1000 {
|
||||
factorial:
|
||||
cmplt %r0, 2
|
||||
jnz factorial_one
|
||||
|
||||
push %r0
|
||||
sub %r0, 1
|
||||
call factorial
|
||||
pop %r0
|
||||
mul %status, %r0
|
||||
jmp factorial_end
|
||||
|
||||
factorial_one:
|
||||
mov %status, 1
|
||||
factorial_end:
|
||||
ret
|
||||
|
||||
main:
|
||||
mov %r0, 6
|
||||
call factorial
|
||||
cmpeq %status, 720
|
||||
jz fail
|
||||
mov %status, 0
|
||||
jmp end
|
||||
fail:
|
||||
mov %status, 1
|
||||
end:
|
||||
halt
|
||||
|
||||
.export main
|
||||
}
|
||||
|
||||
.meta {
|
||||
ip: main
|
||||
}
|
||||
|
||||
75
tests/test_interrupts.asm
Normal file
75
tests/test_interrupts.asm
Normal file
@@ -0,0 +1,75 @@
|
||||
.section code 0x1000 {
|
||||
main:
|
||||
; Test that interrupts will not called when enabled flag is not set
|
||||
; (using 0x80 because anything below it is an error interrupt, and
|
||||
; invoking an error interrupt when disabled will cascade to a triple
|
||||
; fault)
|
||||
mov %status, 1
|
||||
int 0x80, 0
|
||||
cmpeq (count), 0
|
||||
jz end
|
||||
|
||||
or %flags, 0b100
|
||||
|
||||
; Test divide by zero interrupts
|
||||
|
||||
; div
|
||||
add %status, 1
|
||||
mov %r0, 1
|
||||
div %r0, 0
|
||||
cmpeq (count), 1
|
||||
jz end
|
||||
|
||||
; idiv
|
||||
add %status, 1
|
||||
idiv %r0, 0
|
||||
cmpeq (count), 2
|
||||
jz end
|
||||
|
||||
; Test illegal memory address interrupts
|
||||
add %status, 1
|
||||
; TODO - fix this bug, this line breaks because it's trying to
|
||||
; calculate an address that's too large without using wrapping addition
|
||||
; functions
|
||||
;mov %status, (0xfffffffffffffff)
|
||||
mov %status, (0xffffffff)
|
||||
cmpeq (count), 3
|
||||
jz end
|
||||
|
||||
mov %status, 0
|
||||
end:
|
||||
halt
|
||||
|
||||
.align u64
|
||||
generic_handler:
|
||||
add (count), 1
|
||||
iret
|
||||
|
||||
.export main
|
||||
.export generic_handler
|
||||
}
|
||||
|
||||
.section ivt 0x1800 {
|
||||
ivt:
|
||||
.interrupt 0, 0
|
||||
.interrupt 0, 0
|
||||
.interrupt 1, generic_handler
|
||||
.interrupt 1, generic_handler
|
||||
.export ivt
|
||||
}
|
||||
|
||||
.section shared 0x2000 {
|
||||
count: .u64 0
|
||||
.export count
|
||||
}
|
||||
|
||||
.section stack 0x4000 .. 0x5000 {
|
||||
stack_base:
|
||||
.export stack_base
|
||||
}
|
||||
|
||||
.meta {
|
||||
ip: main
|
||||
sp: stack_base
|
||||
ivt: ivt
|
||||
}
|
||||
669
vm.md
669
vm.md
@@ -4,7 +4,7 @@ This is an outline of the VM that drives this language.
|
||||
|
||||
# Primitives
|
||||
|
||||
* Numbers may be big endian (BE) or little endian (LE) at the byte level. This guide will use LE.
|
||||
* Numbers are little endian (LE) at the byte level.
|
||||
* Addresses point to single bytes.
|
||||
* Signed numbers use two's complement.
|
||||
|
||||
@@ -23,10 +23,19 @@ CPU registers are addressed by a value between 0-63 (6 bits). All registers are
|
||||
* SP - Stack pointer
|
||||
* FP - Frame pointer
|
||||
* FLAGS - CPU flags
|
||||
* NULL - Always zero for reading and will never change after writing.
|
||||
* (8 unused registers)
|
||||
* STATUS - Generic status code
|
||||
* R0-R49
|
||||
* NIL - Always zero for reading and will never change after writing.
|
||||
* IVT - Interrupt vector table pointer
|
||||
* R0-R31
|
||||
* (25 reserved registers)
|
||||
|
||||
The following registers are caller-save (i.e., their value may change after a function call):
|
||||
|
||||
* FLAGS
|
||||
* STATUS
|
||||
* IVT
|
||||
|
||||
The rest are callee-save.
|
||||
|
||||
## CPU Flags
|
||||
|
||||
@@ -34,6 +43,7 @@ CPU flags are addressed by bit index, going from right to left.
|
||||
|
||||
* `00` - Halt flag
|
||||
* `01` - Compare flag
|
||||
* `02` - Enable interrupts
|
||||
|
||||
### Flag ideas
|
||||
|
||||
@@ -42,14 +52,75 @@ CPU flags are addressed by bit index, going from right to left.
|
||||
* Overwriting a register without its value being used
|
||||
* Mixing arithmetic with bit twiddling on the same target
|
||||
|
||||
## Register ideas
|
||||
|
||||
* Other possible names: Z, NIL
|
||||
|
||||
# Instructions
|
||||
|
||||
Instructions attempt to be as small as possible while conforming to 8-bit, 16-bit, 32-bit, or 64-bit
|
||||
alignment. All instructions have 16-bit opcodes.
|
||||
All instructions have 16-bit opcodes. There are three types of instructions:
|
||||
|
||||
* Those whose operations require a source and a destination.
|
||||
* Those whose operations require two sources
|
||||
* The sources of these instructions is implied by the instruction itself; e.g. the `CMPEQ`
|
||||
instruction implicitly sets a bit in the `FLAGS` register.
|
||||
* Those whose operations require a source, but no destination.
|
||||
* Those whose operations require a destination, but no source.
|
||||
* There aren't any of these instructions yet
|
||||
* Those whose operations require neither a source nor a destination.
|
||||
|
||||
Destinations may be:
|
||||
|
||||
* A 64-bit address pointing at a 64-bit or 8-bit value
|
||||
* A 6-bit register
|
||||
|
||||
Sources may be one of:
|
||||
|
||||
* A 64-bit address pointing at a 64-bit or 8-bit value
|
||||
* A 6-bit register
|
||||
* A 64-bit immediate value
|
||||
|
||||
Counting all source and destination value sizes as their own configuration, there are:
|
||||
|
||||
* 3 possible destination types
|
||||
* 4 possible source types
|
||||
|
||||
Instructions have different layouts depending on whether its operation takes a source and/or
|
||||
destination. For example, the `ADD` instruction takes a source and a destination, the `JMP`
|
||||
instruction takes a source, and the `NOP` instruction takes neither a source nor a destination.
|
||||
|
||||
For instructions that take neither a source nor a destination, they are simply 16 bits long and
|
||||
that's that. All other instructions are followed by a byte determining its source and/or
|
||||
destination.
|
||||
|
||||
An instruction that has a source and destination looks like this:
|
||||
|
||||
```
|
||||
| XXXXXXXX | XXXXXXXX | DDDDSSSS | ...source and destination |
|
||||
```
|
||||
|
||||
An instruction that has either a source or a destination (but not both) looks like this:
|
||||
|
||||
```
|
||||
| XXXXXXXX | XXXXXXXX | YYYY0000 | ...source or destination |
|
||||
```
|
||||
|
||||
An instruction that has neither a source nor a destination looks like this:
|
||||
|
||||
```
|
||||
| XXXXXXXX | XXXXXXXX |
|
||||
```
|
||||
|
||||
## Source/destination flags
|
||||
|
||||
| Bits | Source/destination |
|
||||
| - | - |
|
||||
| 0b0000 | Address (64 bit value) |
|
||||
| 0b0001 | Address (32 bit value) |
|
||||
| 0b0010 | Address (16 bit value) |
|
||||
| 0b0011 | Address (8 bit value) |
|
||||
| 0b0100 | 6-bit register |
|
||||
| 0b0101 | Immediate (64 bits, source only) |
|
||||
| 0b0110 | Immediate (32 bits, source only) |
|
||||
| 0b0111 | Immediate (16 bits, source only) |
|
||||
| 0b1000 | Immediate (8 bits, source only) |
|
||||
|
||||
|
||||
## Arithmetic
|
||||
|
||||
@@ -57,161 +128,44 @@ Arithmetic instructions store their result in the first register specified. Over
|
||||
wrapping around to 0.
|
||||
|
||||
* Add
|
||||
* Opcode: 0x0000
|
||||
* **Params**: REG1, REG2
|
||||
* `REG1 = REG1 + REG2`
|
||||
* Unsigned addition
|
||||
* ```
|
||||
32 16 10 4 0
|
||||
opcode reg1 reg2 unused
|
||||
/ / / /
|
||||
+-------------------------------------------+
|
||||
| 0000000000000000 | ...... | ...... | XXXX |
|
||||
+-------------------------------------------+
|
||||
```
|
||||
* Opcode: 0x1000
|
||||
* Params: Destination, source
|
||||
* Sub
|
||||
* Opcode: 0x1001
|
||||
* Params: Destination, source
|
||||
* Mul
|
||||
* Opcode: 0x0001
|
||||
* **Params**: REG1, REG2
|
||||
* `REG1 = REG1 * REG2`
|
||||
* Unsigned multiplication
|
||||
* ```
|
||||
32 16 10 4 0
|
||||
opcode reg1 reg2 unused
|
||||
/ / / /
|
||||
+-------------------------------------------+
|
||||
| 0000000000000001 | ...... | ...... | XXXX |
|
||||
+-------------------------------------------+
|
||||
```
|
||||
* Opcode: 0x1002
|
||||
* Params: Destination, source
|
||||
* Div
|
||||
* Opcode: 0x0002
|
||||
* **Params**: REG1, REG2
|
||||
* `REG1 = REG1 / REG2`
|
||||
* Unsigned division
|
||||
* ```
|
||||
32 16 10 4 0
|
||||
opcode reg1 reg2 unused
|
||||
/ / / /
|
||||
+-------------------------------------------+
|
||||
| 0000000000000010 | ...... | ...... | XXXX |
|
||||
+-------------------------------------------+
|
||||
```
|
||||
* Opcode: 0x1003
|
||||
* Params: Destination, source
|
||||
* Mod
|
||||
* Opcode: 0x0003
|
||||
* **Params**: REG1, REG2
|
||||
* `REG1 = REG1 % REG2` (exact semantics TBD)
|
||||
* ```
|
||||
32 16 10 4 0
|
||||
opcode reg1 reg2 unused
|
||||
/ / / /
|
||||
+-------------------------------------------+
|
||||
| 0000000000000011 | ...... | ...... | XXXX |
|
||||
+-------------------------------------------+
|
||||
```
|
||||
* INeg
|
||||
* Opcode: 0x0004
|
||||
* **Params**: REG1
|
||||
* `REG1 = REG1 * -1`
|
||||
* Signed negative
|
||||
* ```
|
||||
32 16 10 0
|
||||
opcode reg1 unused
|
||||
/ / /
|
||||
+----------------------------------------+
|
||||
| 0000000000000100 | ...... | XXXXXXXXXX |
|
||||
+----------------------------------------+
|
||||
```
|
||||
* Opcode: 0x1004
|
||||
* Params: Destination, source
|
||||
* And
|
||||
* Opcode: 0x0005
|
||||
* **Params**: REG1, REG2
|
||||
* `REG1 = REG1 & REG2`
|
||||
* ```
|
||||
32 16 10 4 0
|
||||
opcode reg1 reg2 unused
|
||||
/ / / /
|
||||
+-------------------------------------------+
|
||||
| 0000000000000101 | ...... | ...... | XXXX |
|
||||
+-------------------------------------------+
|
||||
```
|
||||
* Opcode: 0x1005
|
||||
* Params: Destination, source
|
||||
* Or
|
||||
* Opcode: 0x0006
|
||||
* **Params**: REG1, REG2
|
||||
* `REG1 = REG1 | REG2`
|
||||
* ```
|
||||
32 16 10 4 0
|
||||
opcode reg1 reg2 unused
|
||||
/ / / /
|
||||
+-------------------------------------------+
|
||||
| 0000000000000110 | ...... | ...... | XXXX |
|
||||
+-------------------------------------------+
|
||||
```
|
||||
* Inv
|
||||
* Opcode: 0x0007
|
||||
* **Params**: REG1
|
||||
* `REG1 = ~REG1`
|
||||
* ```
|
||||
32 16 10 0
|
||||
opcode reg1 unused
|
||||
/ / /
|
||||
+----------------------------------------+
|
||||
| 0000000000000111 | ...... | XXXXXXXXXX |
|
||||
+----------------------------------------+
|
||||
```
|
||||
* Not
|
||||
* Opcode: 0x0008
|
||||
* **Params**: REG1
|
||||
* ```
|
||||
if REG1 == 0 {
|
||||
REG1 = 0;
|
||||
} else {
|
||||
REG1 = 1;
|
||||
}
|
||||
```
|
||||
* Boolean NOT; equivalent of C's `!` unary operator
|
||||
* ```
|
||||
32 16 10 0
|
||||
opcode reg1 unused
|
||||
/ / /
|
||||
+----------------------------------------+
|
||||
| 0000000000001000 | ...... | XXXXXXXXXX |
|
||||
+----------------------------------------+
|
||||
```
|
||||
* Opcode: 0x1006
|
||||
* Params: Destination, source
|
||||
* Xor
|
||||
* Opcode: 0x0009
|
||||
* **Params**: REG1, REG2
|
||||
* `REG1 = REG1 ^ REG2`
|
||||
* ```
|
||||
32 16 10 4 0
|
||||
opcode reg1 reg2 unused
|
||||
/ / / /
|
||||
+-------------------------------------------+
|
||||
| 0000000000001001 | ...... | ...... | XXXX |
|
||||
+-------------------------------------------+
|
||||
```
|
||||
* Opcode: 0x1007
|
||||
* Params: Destination, source
|
||||
* Shl
|
||||
* Opcode: 0x000A
|
||||
* **Params**: REG1, REG2
|
||||
* `REG1 = REG1 << REG2`
|
||||
* ```
|
||||
32 16 10 4 0
|
||||
opcode reg1 reg2 unused
|
||||
/ / / /
|
||||
+-------------------------------------------+
|
||||
| 0000000000001010 | ...... | ...... | XXXX |
|
||||
+-------------------------------------------+
|
||||
```
|
||||
* Opcode: 0x1008
|
||||
* Params: Destination, source
|
||||
* Shr
|
||||
* Opcode: 0x000B
|
||||
* **Params**: REG1, REG2
|
||||
* `REG1 = REG1 >> REG2`
|
||||
* Does not sign extend
|
||||
* ```
|
||||
32 16 10 4 0
|
||||
opcode reg1 reg2 unused
|
||||
/ / / /
|
||||
+-------------------------------------------+
|
||||
| 0000000000001011 | ...... | ...... | XXXX |
|
||||
+-------------------------------------------+
|
||||
```
|
||||
* Opcode: 0x1009
|
||||
* Params: Destination, source
|
||||
* INeg
|
||||
* Opcode: 0x100a
|
||||
* Params: Destination, source
|
||||
* Inv
|
||||
* Opcode: 0x100b
|
||||
* Params: Destination, source
|
||||
* Not
|
||||
* Opcode: 0x100c
|
||||
* Params: Destination, source
|
||||
|
||||
### TODO
|
||||
|
||||
@@ -222,209 +176,196 @@ wrapping around to 0.
|
||||
## Control flow
|
||||
|
||||
* CmpEq
|
||||
* Opcode: 0x1000
|
||||
* **Params**: REG1, REG2
|
||||
* ```
|
||||
if REG1 == REG2 {
|
||||
FLAGS[1] = 1;
|
||||
} else {
|
||||
FLAGS[1] = 0;
|
||||
}
|
||||
```
|
||||
* Sets the COMPARE flag to 1 if REG1 == REG2
|
||||
* ```
|
||||
32 16 10 4 0
|
||||
opcode reg1 reg2 unused
|
||||
/ / / /
|
||||
+-------------------------------------------+
|
||||
| 0001000000000000 | ...... | ...... | XXXX |
|
||||
+-------------------------------------------+
|
||||
```
|
||||
* Opcode: 0x2000
|
||||
* Params: Source, source
|
||||
* CmpLt
|
||||
* Opcode: 0x1001
|
||||
* **Params**: REG1, REG2
|
||||
* ```
|
||||
if REG1 < REG2 {
|
||||
FLAGS[1] = 1;
|
||||
} else {
|
||||
FLAGS[1] = 0;
|
||||
}
|
||||
```
|
||||
* Sets the COMPARE flag to 1 if REG1 < REG2
|
||||
* ```
|
||||
32 16 10 4 0
|
||||
opcode reg1 reg2 unused
|
||||
/ / / /
|
||||
+-------------------------------------------+
|
||||
| 0001000000000001 | ...... | ...... | XXXX |
|
||||
+-------------------------------------------+
|
||||
```
|
||||
* Opcode: 0x2001
|
||||
* Params: Source, source
|
||||
* Jmp
|
||||
* Opcode: 0x1100
|
||||
* **Params**: REG1
|
||||
* `IP = REG1;`
|
||||
* Jumps to the address in REG1 unconditionally.
|
||||
* ```
|
||||
32 16 10 0
|
||||
opcode reg1 unused
|
||||
/ / /
|
||||
+----------------------------------------+
|
||||
| 0001000100000000 | ...... | XXXXXXXXXX |
|
||||
+----------------------------------------+
|
||||
```
|
||||
|
||||
* Opcode: 0x2002
|
||||
* Params: Source
|
||||
* Jz
|
||||
* Opcode: 0x1101
|
||||
* **Params**: REG1
|
||||
* ```
|
||||
if FLAGS[1] == 0 {
|
||||
IP = REG1;
|
||||
}
|
||||
```
|
||||
* Jumps to the address in REG1 if COMPARE flag is 0.
|
||||
* ```
|
||||
32 16 10 0
|
||||
opcode reg1 unused
|
||||
/ / /
|
||||
+----------------------------------------+
|
||||
| 0001000100000001 | ...... | XXXXXXXXXX |
|
||||
+----------------------------------------+
|
||||
```
|
||||
* Opcode: 0x2003
|
||||
* Params: Source
|
||||
* Jnz
|
||||
* Opcode: 0x1002
|
||||
* **Params**: REG1
|
||||
* ```
|
||||
if FLAGS[1] != 0 {
|
||||
IP = REG1;
|
||||
}
|
||||
```
|
||||
* Jumps to the address in REG1 if COMPARE flag is 1.
|
||||
* ```
|
||||
32 16 10 0
|
||||
opcode reg1 unused
|
||||
/ / /
|
||||
+----------------------------------------+
|
||||
| 0001000100000002 | ...... | XXXXXXXXXX |
|
||||
+----------------------------------------+
|
||||
```
|
||||
* Opcode: 0x2004
|
||||
* Params: Source
|
||||
|
||||
## Functions
|
||||
|
||||
* Call
|
||||
* Opcode: 0x3000
|
||||
* Params: Source
|
||||
* When this instruction is executed, these actions occur:
|
||||
* Push the current stack frame pointer
|
||||
* Push the IP of the next instruction
|
||||
* Update the IP to the value at the given source.
|
||||
* Update the frame pointer to the current stack pointer - 16
|
||||
* Ret
|
||||
* Opcode: 0x3001
|
||||
* When this instruction is executed, these actions occur:
|
||||
* Update the stack pointer to the current frame pointer + 16.
|
||||
* Pop the IP of the next instruction.
|
||||
* Pop the old stack frame.
|
||||
* Restore the last three values in an undefined order
|
||||
* Push
|
||||
* Opcode: 0x3002
|
||||
* Params: Source
|
||||
* When this instruction is executed, these actions occur:
|
||||
* Set the value in memory at the current stack pointer to the source value.
|
||||
* Increment the stack pointer by the size of value at the source.
|
||||
* Pop
|
||||
* Opcode: 0x3003
|
||||
* Params: Dest
|
||||
* When this instruction is executed, these actions occur:
|
||||
* Decrement the stack pointer by the size of value at the destination.
|
||||
* Copy the value at the stack pointer into the destination.
|
||||
* Int
|
||||
* Opcode: 0x3004
|
||||
* Params: Source, Source
|
||||
* When this instruction is executed, these actions occur:
|
||||
* Push the current stack frame pointer
|
||||
* Push the IP of the next instruction to be called
|
||||
* Push the FLAGS register
|
||||
* Push the STATUS register
|
||||
* Push the R0-R31 registers
|
||||
* Update the IP to the address of the given interrupt vector in the IVT
|
||||
* Update the R0 register to the value in the first parameter
|
||||
* Update the R1 register to the value in the second parameter
|
||||
* Update the frame pointer to the current stack pointer - 288
|
||||
* IRet
|
||||
* Opcode: 0x3005
|
||||
* When this instruction is executed, these actions occur:
|
||||
* Update the stack pointer to the current frame pointer + 288
|
||||
* Pop the old R31-R00 values
|
||||
* Pop the old STATUS value
|
||||
* Pop the old FLAGS value
|
||||
* Pop the IP of the next instruction
|
||||
* Pop the old stack frame
|
||||
|
||||
## Data movement
|
||||
|
||||
* Load
|
||||
* Opcode: 0x2000
|
||||
* **Params**: REG1, REG2
|
||||
* ```
|
||||
REG1 = MEM[REG2];
|
||||
```
|
||||
* Sets REG1 to the value at the memory address in REG2.
|
||||
* ```
|
||||
32 16 10 4 0
|
||||
opcode reg1 reg2 unused
|
||||
/ / / /
|
||||
+-------------------------------------------+
|
||||
| 0010000000000000 | ...... | ...... | XXXX |
|
||||
+-------------------------------------------+
|
||||
```
|
||||
* RegCopy
|
||||
* Opcode: 0x2001
|
||||
* **Params**: REG1, REG2
|
||||
* `REG1 = REG2`
|
||||
* Copies the value in REG2 into REG1.
|
||||
* ```
|
||||
32 16 10 4 0
|
||||
opcode reg1 reg2 unused
|
||||
/ / / /
|
||||
+-------------------------------------------+
|
||||
| 0010000000000001 | REG1.. | REG2.. | XXXX |
|
||||
+-------------------------------------------+
|
||||
```
|
||||
* StoreImm64
|
||||
* Opcode: 0x2100
|
||||
* **Params**: REG1, IMM_64
|
||||
* `REG1 = IMM_64`
|
||||
* Sets REG1 to the specified 64-bit number.
|
||||
* StoreImm32
|
||||
* Opcode: 0x2101
|
||||
* **Params**: REG1, IMM_32
|
||||
* `REG1 = IMM_32`
|
||||
* Sets REG1 to the specified 32-bit number.
|
||||
* ```
|
||||
64 48 42 36 32 0
|
||||
opcode reg1 reg2 unused
|
||||
/ / / / immediate 32 bit value
|
||||
/ / / / /
|
||||
+------------------------------------------------------------------------------+
|
||||
| 0010000100000001 | REG1.. | REG2.. | XXXX | IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII |
|
||||
+------------------------------------------------------------------------------+
|
||||
```
|
||||
* MemCopy
|
||||
* Opcode: 0x2200
|
||||
* **Params**: REG1, REG2
|
||||
* `MEM[REG1] = MEM[REG2]`
|
||||
* Copies the value at the memory address in REG2 to the memory address in REG1.
|
||||
* ```
|
||||
32 16 10 4 0
|
||||
opcode reg1 reg2 unused
|
||||
/ / / /
|
||||
+-------------------------------------------+
|
||||
| 0010001000000000 | REG1.. | REG2.. | XXXX |
|
||||
+-------------------------------------------+
|
||||
```
|
||||
* Store
|
||||
* Opcode: 0x2201
|
||||
* **Params**: REG1, REG2
|
||||
* ```
|
||||
MEM[REG2] = REG1;
|
||||
```
|
||||
* Sets the value at the memory address in REG2 to the value in REG1.
|
||||
* ```
|
||||
32 16 10 4 0
|
||||
opcode reg1 reg2 unused
|
||||
/ / / /
|
||||
+-------------------------------------------+
|
||||
| 0010001000000001 | REG1.. | REG2.. | XXXX |
|
||||
+-------------------------------------------+
|
||||
```
|
||||
* Mov
|
||||
* Opcode: 0x4000
|
||||
* Params: Source, Dest
|
||||
|
||||
## Miscellaneous
|
||||
|
||||
* Halt
|
||||
* Opcode: 0xF000
|
||||
* **Params**: (none)
|
||||
* `FLAGS[0] = 1`
|
||||
* Halts the machine
|
||||
* ```
|
||||
16
|
||||
opcode
|
||||
/
|
||||
+------------------+
|
||||
| 1111000000000000 |
|
||||
+------------------+
|
||||
```
|
||||
* Nop
|
||||
* Opcode: 0xF001
|
||||
* **Params**: (none)
|
||||
* Does nothing
|
||||
* ```
|
||||
16
|
||||
opcode
|
||||
/
|
||||
+------------------+
|
||||
| 1111000000000001 |
|
||||
+------------------+
|
||||
```
|
||||
* Dump
|
||||
* Opcode: 0xF002
|
||||
|
||||
## Other instructions TODO
|
||||
# Interrupts
|
||||
|
||||
* Call
|
||||
* Takes address and number of bytes on the stack that are for args(?)
|
||||
* Updates SP, FP, IP, storing previous values starting at the new FP
|
||||
* Ret
|
||||
* Uses FP to determine previous SP, FP, and IP and restores them
|
||||
* Push
|
||||
* Pop
|
||||
* More immediate stores?
|
||||
* Idea: Store42 (or whatever number of bits) that maximizes the usage of a 64-bit instruction
|
||||
Interrupts are signaled explicitly from software or from hardware signaling the CPU. When an
|
||||
interrupt signal is set, the CPU will finish whatever instruction it is executing, and then begin
|
||||
handling the interrupt whose signal was set. Software interrupts may be invoked using the `int`
|
||||
instruction, supplying the index of the interrupt to invoke. Hardware interrupts are invoked
|
||||
directly by a hardware event, e.g. a keypress. Hardware and software interrupts are treated equally
|
||||
in the CPU, and as such, they are all maskable.
|
||||
|
||||
An interrupt may be masked in two ways: either through its entry in the IVT, or through the "enable
|
||||
interrupts" CPU flag. If the "enabled" bit in the IVT is not set, that interrupt will not be handled
|
||||
when it is invoked. If the "enable interrupts" CPU flag is not set, *no* interrupts will be handled.
|
||||
|
||||
## Interrupt vector table
|
||||
|
||||
Interrupts are defined by the IVT register. The address stored in the IVT register must be a
|
||||
multiple of 64. The IVT always has 512 entries, with 8 bytes for each entry. Thus, the entire table
|
||||
is 512 * 8 = 4096 bytes, or one page.
|
||||
|
||||
## Interrupt table entries
|
||||
|
||||
Interrupt table entries make up the interrupt vector table, each entry being 64 bits (8 bytes) long.
|
||||
|
||||
* 1 bit - Enabled
|
||||
* 4 bits - Reserved, set to 0
|
||||
* 59 bits - Interrupt address, multiplied by 64 for the start address
|
||||
|
||||
## Interrupt handling
|
||||
|
||||
After an interrupt is signaled, the CPU looks up the index of the interrupt in the IVT, calculates
|
||||
its address, sets up the stack for the interrupt handler, and jumps to the interrupt handler's
|
||||
address.
|
||||
|
||||
The interrupt stack is structured similarly to a normal call stack, but since interrupts may be
|
||||
invoked at any time, it saves additional state. Interrupt handlers have two explicit arguments: the
|
||||
interrupt index itself, and an auxiliary 64-bit value or pointer specific to that interrupt. The
|
||||
index is stored in the R0 register, and the auxiliary value is stored in the R1 register. These
|
||||
registers, along with the FP, IP, FLAGS, and STATUS registers are saved on the stack before calling
|
||||
an interrupt handler.
|
||||
|
||||
Before an interrupt handler is called, these actions occur:
|
||||
|
||||
* Push the current stack frame pointer
|
||||
* Push the IP of the next instruction to be called
|
||||
* Push the FLAGS register
|
||||
* Push the STATUS register
|
||||
* Push the R0-R31 registers
|
||||
* Update the IP to the address of the given interrupt vector in the IVT
|
||||
* Update the R0 register to the value in the first parameter
|
||||
* Update the R1 register to the value in the second parameter
|
||||
* Update the frame pointer to the current stack pointer - 288
|
||||
|
||||
Interrupt handlers must be exited using the `iret` instruction. When an interrupt call is exited,
|
||||
the above actions occur in reverse:
|
||||
|
||||
* Update the stack pointer to the current frame pointer + 288
|
||||
* Pop the old R31-R00 values
|
||||
* Pop the old STATUS value
|
||||
* Pop the old FLAGS value
|
||||
* Pop the IP of the next instruction
|
||||
* Pop the old stack frame
|
||||
|
||||
## Exceptions
|
||||
|
||||
The first 256 interrupt vectors are reserved for CPU and I/O-sourced events - these are known as
|
||||
exceptions. Likewise, the first 128 exceptions are error state exceptions, with the remaining 128
|
||||
being used for general exceptions.
|
||||
|
||||
### Error state exceptions
|
||||
|
||||
Error state exceptions occur when an instruction attempts to perform illegal operation,
|
||||
such as attempting to read an out-of-bounds memory address or attempting to execute an invalid
|
||||
opcode. Error state exceptions may be caught and handled, just like any other interrupt.
|
||||
|
||||
#### Double fault and triple fault
|
||||
|
||||
If, while already handling an error state exception, and a second error state exception is raised, a
|
||||
double fault is invoked. You may handle a double fault like any exception and attempt to repair the
|
||||
situation. If yet another exception is raised, the CPU will invoke a triple fault. A triple fault
|
||||
will unconditionally halt the machine.
|
||||
|
||||
### Error state exceptions (vectors 0x00-0x0f)
|
||||
|
||||
* Double fault
|
||||
* Interrupt vector: 0x00
|
||||
* Auxiliary: The interrupt vector that was being invoked that caused the fault.
|
||||
* An error state interrupt occurred while already handling an error state interrupt
|
||||
* Illegal instruction
|
||||
* Interrupt vector: 0x01
|
||||
* Auxiliary: Memory address where illegal instruction is located
|
||||
* Attempted to execute a malformed instruction
|
||||
* Illegal memory address
|
||||
* Interrupt vector: 0x02
|
||||
* Auxiliary: Memory address causing the interrupt
|
||||
* Attempted to access a memory address in an illegal way - either it's out of bounds or is
|
||||
protected in some way.
|
||||
* Divide by zero
|
||||
* Interrupt vector: 0x03
|
||||
* Auxiliary: N/A
|
||||
* Invoked upon a divide-by-zero
|
||||
* Remaining error states below 0x80 are reserved for future use.
|
||||
|
||||
### General exceptions (vectors 0x80-0xff)
|
||||
* I/O event
|
||||
* Interrupt vector: 0x80
|
||||
* Auxiliary: Pointer to the I/O event structure.
|
||||
* An I/O device has an event that needs attention.
|
||||
* NOTE: This will probably be removed.
|
||||
|
||||
# Binary object format
|
||||
|
||||
@@ -435,7 +376,7 @@ the object.
|
||||
|
||||
The header is composed of:
|
||||
|
||||
* 64 bits - A magic number (0xDEAD_BEA7_BA5E_BA11).
|
||||
* 64 bits - A magic number (0xDEAD\_BEA7\_BA5E\_BA11).
|
||||
* 32 bits - Version of the file
|
||||
* 32 bits - The number of sections in the file
|
||||
* section descriptions detailed below
|
||||
@@ -449,7 +390,6 @@ the section contents.
|
||||
|
||||
* 8 bits - Section kind
|
||||
* 0x00 - Data
|
||||
* 0x10 - Code
|
||||
* 0xFF - Meta
|
||||
* 64 bits - Length of the section
|
||||
|
||||
@@ -457,17 +397,10 @@ the section contents.
|
||||
|
||||
The data section contains static data that is initialized to some known value.
|
||||
|
||||
* 16 bits - length of section name
|
||||
* N bits - section name
|
||||
* 64 bits - section load start - where in memory the content of this section begins
|
||||
* 64 bits - section load end - where in memory the content of this section ends
|
||||
|
||||
### Code section
|
||||
|
||||
The code section contains executable code.
|
||||
|
||||
* 64 bits - section load start - where in memory the content of this section begins
|
||||
* 64 bits - section load end - where in memory the content of this section ends
|
||||
|
||||
The remaining length of the section is the code itself.
|
||||
* 64 bits - section length - how long the memory content is
|
||||
|
||||
### Meta section
|
||||
|
||||
@@ -487,10 +420,28 @@ executing program. Data in the meta section is not copied to the program memory.
|
||||
|
||||
A VM must provide support for the following meta-values:
|
||||
|
||||
* `entry` - a 64-bit address for where the VM should begin executing code.
|
||||
* `ip` - the initial value for the instruction pointer (the entry point)
|
||||
* `fp` - the initial value for the stack frame pointer
|
||||
* If not set, its default value is the value of the stack pointer.
|
||||
* `sp` - the initial value for the stack pointer
|
||||
* `flags` - the initial CPU flags
|
||||
* `status` - the initial value for the status register
|
||||
* `ivt` - the initial value for the pointer to the IVT
|
||||
* `rXX` - the initial value for register XX (0-31)
|
||||
|
||||
# General TODO
|
||||
|
||||
* Interrupts
|
||||
* Memory permissions
|
||||
* MMIO regions
|
||||
* Paging?
|
||||
* Paging
|
||||
* Determine how address sizes are determined
|
||||
* source size <= dest size - zero extend source and copy
|
||||
* mov %r0, (label)u32
|
||||
* source size > dest size - truncate to dest size
|
||||
* mov (label)u32, %r0
|
||||
* source size with unknown dest size - use dest size == source size
|
||||
* mov %r0, (label)
|
||||
* unknown source size with dest size - use dest size == source size
|
||||
* mov (label), %r0
|
||||
* unknown source size with unknown dest size - 64 bits
|
||||
* mov (label), (%r0)
|
||||
|
||||
Reference in New Issue
Block a user