73 Commits

Author SHA1 Message Date
bff9220fb1 Extend how interrupts are reported to the main execution loop
Some things that were previously hard VM-level errors are now handled by
interrupts. While this is relatively easy to handle, I was wanting a
little more structure for the error types - so, errors that should
invoke an interrupt are passed along in their own structure in a
VmError::Interrupt variant. If an error is raised during the tick()
phase of execution that would cause an interrupt, that interrupt is
intercepted and the VM continues.

The State::interrupt() function also will catch double faults and triple
faults, with a triple fault being its own variant in the VmError
structure (so it cannot be intercepted as an interrupt by accident).

Signed-off-by: Alek Ratzloff <alekratz@gmail.com>
2020-03-12 16:56:20 -04:00
cb75bf59e0 Polish the VM exception definitions, add double faults, update exception order
* Exception definition is a little more comprehensive
* Add error state exceptions including double fault
* Exception numbers are now roughly in the order of their importance

Signed-off-by: Alek Ratzloff <alekratz@gmail.com>
2020-03-10 16:02:23 -04:00
28edfb6933 Remove dollar sign ($) from front of number tokens
Number tokens with a dollar sign are kind of cumbersome and don't really
serve a purpose, so I'm removing them.

Signed-off-by: Alek Ratzloff <alekratz@gmail.com>
2020-03-10 15:59:36 -04:00
7c9d4fe908 Update spec to reflect previous changes
This adds R00-R31 preservation to interrupts and adds meta section
required values.

Signed-off-by: Alek Ratzloff <alekratz@gmail.com>
2020-03-10 12:21:29 -04:00
85e5f7a2bf Add INTERRUPT_ENABLE flag, fix bug with register preservation
Signed-off-by: Alek Ratzloff <alekratz@gmail.com>
2020-03-10 12:20:59 -04:00
3da72874f5 Add syntax for previous commit
Signed-off-by: Alek Ratzloff <alekratz@gmail.com>
2020-03-10 12:20:15 -04:00
551bd2c3f4 Add initial register states to meta section; remove entry directive
It makes sense to allow users to set the initial register values. This
allows someone to e.g. enable interrupts once the VM has started, or set
the initial stack pointer value.

Signed-off-by: Alek Ratzloff <alekratz@gmail.com>
2020-03-10 12:18:31 -04:00
3522b3d0cd Update spec and register offset for interrupts
Signed-off-by: Alek Ratzloff <alekratz@gmail.com>
2020-03-09 18:42:31 -04:00
8c4a9991fd Fix dumb mistake in address calculation for interrupts, add more registers to interrupt call stack
Interrupts need to be on 8-byte (or 64-bit) bounds. I had mistakenly
decided to shift right by 5 (which isn't even dividing by 64, it's 32)
-but since this an address pointing at byte ranges, that should be
divided by 8, or >> 3.

Additionally, interrupts add all 32 general purpose registers to the
stack because otherwise they become useless.

Signed-off-by: Alek Ratzloff <alekratz@gmail.com>
2020-03-09 18:24:54 -04:00
9e82663b2a Rename tests/call.asm -> tests/test_call.asm
Signed-off-by: Alek Ratzloff <alekratz@gmail.com>
2020-03-09 18:24:19 -04:00
219da3c3c2 Run rustfmt
Signed-off-by: Alek Ratzloff <alekratz@gmail.com>
2020-03-09 16:47:38 -04:00
d0f8d93edf Run rustfmt
Signed-off-by: Alek Ratzloff <alekratz@gmail.com>
2020-03-09 16:47:30 -04:00
63c81f07f7 Fix alignment calculation and padding
Aligned values are a little funky, because the size of the line can't be
determined without knowing what address we're at. So the
DataLines iterator now takes a start address, so it is able to correctly
calculate alignment padding.

Also, the length of the data section uses the DataLines iterator now so
there isn't a complex re-implementation of the same logic.

Signed-off-by: Alek Ratzloff <alekratz@gmail.com>
2020-03-09 16:43:04 -04:00
4ae060c68d Add rerun_except to root crate
Cargo is finding more reasons to do unnecessary rebuilds, so I've added
the rerun_except crate to its build.rs as well to ignore .asm files
before recompiling.

Signed-off-by: Alek Ratzloff <alekratz@gmail.com>
2020-03-09 16:38:41 -04:00
65972fcbbe Change instruction encodings to avoid null instructions
Lots of null bytes in a row are a common thing, so the instructions
available can't start with a null pair of bytes anymore.

Signed-off-by: Alek Ratzloff <alekratz@gmail.com>
2020-03-09 15:11:32 -04:00
b697b000a4 Add updated Cargo.lock for new table generator package
Signed-off-by: Alek Ratzloff <alekratz@gmail.com>
2020-03-07 18:08:57 -05:00
90155b0cb3 Add alignment syntax, datasection pos/line iterator, disassembler
* Interrupts require functions being aligned on 64-bit boundaries.
  Alignment is now allowed via the .align <intsize> directive, aligning
  the current address to a new alignment.
* Datasection iteration doesn't require keeping track of the current
  position.
* Rudimentary disassembler for breaking down the contents of an object

Signed-off-by: Alek Ratzloff <alekratz@gmail.com>
2020-03-07 18:01:31 -05:00
1f93732a7c Reorganize instruction assembler logic (fixing a known bug), add interrupts to frontend
* Instruction assembler used to use macros for handling source,
  dest-source, source-source, etc instructions. It did not have a
  source-source implementation, however, so the two source-source
  instructions (cmpeq and cmplt) were treated as dest-source. This has
  been refactored into some local methods that handle this now.
* Syntax for interrupts and interrupt returns are implemented and appear
  to be working

Signed-off-by: Alek Ratzloff <alekratz@gmail.com>
2020-03-06 12:53:07 -05:00
fbe2c529af Add decoding of INT and IRET instructions
Signed-off-by: Alek Ratzloff <alekratz@gmail.com>
2020-03-06 12:52:46 -05:00
34b1147fe6 Add int and iret instruction definitions to the VM backend
Signed-off-by: Alek Ratzloff <alekratz@gmail.com>
2020-03-06 12:30:49 -05:00
1fb2a1df44 Add interrupts enabled flag to spec, add interrupt return actions to spec
Signed-off-by: Alek Ratzloff <alekratz@gmail.com>
2020-03-06 12:19:07 -05:00
58262eab40 Move examples to libvm source base and add symlink to root directory
Signed-off-by: Alek Ratzloff <alekratz@gmail.com>
2020-03-06 12:18:20 -05:00
b1f2de198e Add interrupt definition syntax and interrupt handling in VM execution state
Signed-off-by: Alek Ratzloff <alekratz@gmail.com>
2020-03-06 12:17:05 -05:00
bf83601cdf Add interrupt struct and IVT reader
Signed-off-by: Alek Ratzloff <alekratz@gmail.com>
2020-03-04 16:41:37 -05:00
ce352c000c Add IVT register and interrupt module
Signed-off-by: Alek Ratzloff <alekratz@gmail.com>
2020-03-04 14:37:00 -05:00
bdd30274ed Add interrupts draft to spec
Signed-off-by: Alek Ratzloff <alekratz@gmail.com>
2020-03-04 14:23:57 -05:00
711bfeb7f9 Squash some warnings
Signed-off-by: Alek Ratzloff <alekratz@gmail.com>
2020-03-03 19:34:41 -05:00
bac1b413dc Remove structopt where appropriate
Signed-off-by: Alek Ratzloff <alekratz@gmail.com>
2020-03-03 19:33:55 -05:00
5ffca7bcf0 Second part of VM lib transition
Signed-off-by: Alek Ratzloff <alekratz@gmail.com>
2020-03-03 19:07:35 -05:00
2b8663037f First part of libvm transition
Signed-off-by: Alek Ratzloff <alekratz@gmail.com>
2020-03-03 19:05:50 -05:00
74139b7c65 Remove defunct TODO from spec
Signed-off-by: Alek Ratzloff <alekratz@gmail.com>
2020-03-03 18:34:56 -05:00
ef03ea9137 Update assembler infrastructure to be more modular
Assembler has more discrete passes now, but things are kind of bare on
the error message front. But, everything is working as it did
previously, so we can move on to more interesting things.

Signed-off-by: Alek Ratzloff <alekratz@gmail.com>
2020-03-03 18:10:08 -05:00
ad7c22c168 Remove include_paths() methods from AsmSession
Signed-off-by: Alek Ratzloff <alekratz@gmail.com>
2020-02-28 15:08:17 -05:00
782a2ea4e9 Remove examples/constants.asm, update deadbeef.asm to not use it
Signed-off-by: Alek Ratzloff <alekratz@gmail.com>
2020-02-27 17:33:44 -05:00
4b96902831 Scrap preprocessor, add .include directive instead
Signed-off-by: Alek Ratzloff <alekratz@gmail.com>
2020-02-27 15:52:17 -05:00
0eb394ddf5 Update build.rs
Signed-off-by: Alek Ratzloff <alekratz@gmail.com>
2020-02-26 14:27:30 -05:00
381a59f5fe Clean up main.rs some, add preprocessor
* Preprocessor uses an LRPAR generated lexer and a custom parser to
  filter comments and set defines. Includes and conditional compilation
  will come next.

Signed-off-by: Alek Ratzloff <alekratz@gmail.com>
2020-02-26 14:24:30 -05:00
79d37a4e7b Add documentation to the Source and Dest value_len() and len() functions
Signed-off-by: Alek Ratzloff <alekratz@gmail.com>
2020-02-26 10:26:18 -05:00
44dc34e52d Update spec for push and pop operations
Signed-off-by: Alek Ratzloff <alekratz@gmail.com>
2020-02-26 10:25:46 -05:00
cd95011e7b Add function call test
Signed-off-by: Alek Ratzloff <alekratz@gmail.com>
2020-02-26 10:19:26 -05:00
a4e1d43a74 Add factorial asm example
Signed-off-by: Alek Ratzloff <alekratz@gmail.com>
2020-02-26 10:16:38 -05:00
7a6c2d80ab Add call/ret/push/pop instructions
* Call/ret/push/pop are implemented and appear to be working
* Call/ret/push/pop is specified in the 0x2000 block, replacing the
  mov instruction
* Mov instruction is now specified in the 0x3000 block

Signed-off-by: Alek Ratzloff <alekratz@gmail.com>
2020-02-26 10:14:48 -05:00
bd34cdad63 Add compile and compile_out options, and extended input/output options
* compile flag and compile_out argument to the driver allow the program
  to be compiled only
* if '-' is supplied to compile_out, it will output to STDOUT instead
* if '-' is supplied to input, it will read from STDIN instead

Signed-off-by: Alek Ratzloff <alekratz@gmail.com>
2020-02-25 18:12:28 -05:00
d11dbbcdf5 Add meta section len() and to_bytes()
Signed-off-by: Alek Ratzloff <alekratz@gmail.com>
2020-02-25 17:56:16 -05:00
86d46b2a50 Add data section names and conversion to object file format
* Data section names are encoded in the object file format
* Objects can be converted into their file format layouts
* Add writing/reading test to make sure that an object converted to
  bytes and then back from bytes is equal to hopefully catch major
  object translations

Signed-off-by: Alek Ratzloff <alekratz@gmail.com>
2020-02-25 16:39:32 -05:00
700ea6c54f Add section name to data section spec
Signed-off-by: Alek Ratzloff <alekratz@gmail.com>
2020-02-25 16:38:43 -05:00
461cf59bb0 Update intermediate object representation
Signed-off-by: Alek Ratzloff <alekratz@gmail.com>
2020-02-25 15:08:49 -05:00
ef83cf7ef3 Add max_mem and size parsing for arg parsing
Signed-off-by: Alek Ratzloff <alekratz@gmail.com>
2020-02-25 14:55:14 -05:00
b4637e2070 Comments in main.rs
Signed-off-by: Alek Ratzloff <alekratz@gmail.com>
2020-02-25 14:21:39 -05:00
07abfc96b5 Add structopt for arg parsing
Signed-off-by: Alek Ratzloff <alekratz@gmail.com>
2020-02-25 14:19:12 -05:00
bc4f59ecad Add bitwise tests, squash all arithmetic tests into test_arithmetic
Signed-off-by: Alek Ratzloff <alekratz@gmail.com>
2020-02-25 14:10:21 -05:00
362590292a Add some integration tests and runner
Signed-off-by: Alek Ratzloff <alekratz@gmail.com>
2020-02-25 13:48:03 -05:00
145739aee2 Add brackets to lexer in preparation for arrays
Signed-off-by: Alek Ratzloff <alekratz@gmail.com>
2020-02-25 13:47:28 -05:00
0ff189bc1b Minor code cleanup and surpress warnings for unused inst and register constants
Signed-off-by: Alek Ratzloff <alekratz@gmail.com>
2020-02-25 13:46:38 -05:00
b8a769c80f Add sized int value defs
Integer value definitions now respect their sizes (.u8, .u16, etc)

Signed-off-by: Alek Ratzloff <alekratz@gmail.com>
2020-02-25 13:45:41 -05:00
1c05b3bb44 Update main to return the given status instead of printing the status out when halted
Signed-off-by: Alek Ratzloff <alekratz@gmail.com>
2020-02-25 13:45:04 -05:00
aff2da591b Add rerun_except crate to prevent building when .asm files are changed
Signed-off-by: Alek Ratzloff <alekratz@gmail.com>
2020-02-25 13:44:15 -05:00
795a890502 Add IDiv instruction for signed integer division
Signed-off-by: Alek Ratzloff <alekratz@gmail.com>
2020-02-25 13:44:03 -05:00
5619c9dc87 Add address deref, syntax, and deref sizes
Signed-off-by: Alek Ratzloff <alekratz@gmail.com>
2020-02-25 12:07:24 -05:00
bdd08c6c5b Fix bug when generating single-source instructions
Single-source instructions need their source spec to be in the the top 4
bits of the source/dest spec, and it was incorrectly being set in the
bottom 4 bits. This affected jump instructions, as they would
be reading the instruction at the given address rather than jumping
to it.

Signed-off-by: Alek Ratzloff <alekratz@gmail.com>
2020-02-19 14:11:57 -05:00
2413f9f362 Remove LALRPOP parser
Signed-off-by: Alek Ratzloff <alekratz@gmail.com>
2020-02-18 17:55:11 -05:00
b0ef49bc2a Add assembler and execution logic
Most everything works, but there's one small bug with the execution
involving jumps - still have to figure that one out.

Signed-off-by: Alek Ratzloff <alekratz@gmail.com>
2020-02-18 17:44:41 -05:00
0598bd1526 Rename vm::common -> vm::addr
Signed-off-by: Alek Ratzloff <alekratz@gmail.com>
2020-02-17 16:22:15 -05:00
bf6b0dfba9 Remove vm::obj::assemble mod
Signed-off-by: Alek Ratzloff <alekratz@gmail.com>
2020-02-17 16:17:55 -05:00
2c4b56e362 Use lrpar for parsing, big 'ol syntax overhaul
Signed-off-by: Alek Ratzloff <alekratz@gmail.com>
2020-02-17 16:15:06 -05:00
cf9ba376aa Add value directives and strings
.string, .zstring, .u64, .u32, .u16, .u8 are used before an ImmValue to
determine how the memory should be laid out for that value.

Signed-off-by: Alek Ratzloff <alekratz@gmail.com>
2020-02-11 17:49:17 -05:00
95d4eb0a60 Move test.asm -> examples directory
Signed-off-by: Alek Ratzloff <alekratz@gmail.com>
2020-02-11 13:57:33 -05:00
6feeeea028 Add binary number parsing
Signed-off-by: Alek Ratzloff <alekratz@gmail.com>
2020-02-10 19:10:17 -05:00
0ea3406b71 Add storeimm32 and storeimm64 syntax
These explicitly allow usage of storeimm32 and storeimm64 in the
assembler syntax. It will also warn you if you try to store a value
that's too large using the storeimm32 instruction

Signed-off-by: Alek Ratzloff <alekratz@gmail.com>
2020-02-10 18:59:23 -05:00
15423502f3 Remove data section from test.asm example file
Signed-off-by: Alek Ratzloff <alekratz@gmail.com>
2020-02-10 18:03:50 -05:00
7504b81b2d Require assembler sections to specify where in virtual memory they begin
Signed-off-by: Alek Ratzloff <alekratz@gmail.com>
2020-02-10 18:02:44 -05:00
a4a37b5a27 Add Disassemble, fix bug in position calculation
* Disassemble structure can be used for dumping an object section
* Assembler position calculation was messing up, causing jump addresses
  to be wrong. This is fixed.

Signed-off-by: Alek Ratzloff <alekratz@gmail.com>
2020-02-10 16:31:08 -05:00
6c352396fa Add instruction visitor, which traverses memory and choosing instructions
Signed-off-by: Alek Ratzloff <alekratz@gmail.com>
2020-02-10 13:22:54 -05:00
59 changed files with 6147 additions and 2108 deletions

986
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -4,16 +4,12 @@ version = "0.1.0"
authors = ["Alek Ratzloff <alekratz@gmail.com>"] authors = ["Alek Ratzloff <alekratz@gmail.com>"]
edition = "2018" edition = "2018"
build = "build.rs"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies] [dependencies]
bitflags = "1" structopt = "0.3"
byteorder = "1"
lalrpop-util = "0.17.2"
regex = "*"
snafu = "0.6.2" snafu = "0.6.2"
libvm = { path = "src/libvm" }
[build-dependencies] [build-dependencies]
lalrpop = "0.17.2" rerun_except = "0.1"

View File

@@ -1,5 +1,5 @@
use lalrpop; use rerun_except::rerun_except;
fn main() { fn main() {
lalrpop::process_root().unwrap(); rerun_except(&["*.asm"]).unwrap();
} }

1
examples Symbolic link
View File

@@ -0,0 +1 @@
src/libvm/examples/

25
run_asm_tests.sh Executable file
View 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

View File

@@ -2,7 +2,7 @@
use std::cmp::Ordering; use std::cmp::Ordering;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] #[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)]
pub struct Pos { pub struct Pos {
pub source: usize, pub source: usize,
pub line: 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 { pub fn from_char(c: char) -> Self {
Pos::new(source, line, col, byte, c.len_utf8()) Pos::new(0, 0, 0, 0, c.len_utf8())
} }
pub fn adv_char(self, c: char) -> Self { pub fn adv_char(&mut self, c: char) {
let mut next = self; self.byte += self.len;
next.byte += next.len; self.len = c.len_utf8();
next.len = c.len_utf8(); self.source += 1;
next.source += 1; self.col += 1;
next.col += 1;
next
} }
pub fn adv_line(self) -> Self { pub fn adv_line(&mut self) {
let mut next = self; self.line += 1;
next.line += 1; self.col = 0;
next
} }
} }
@@ -56,8 +53,8 @@ impl Ord for Pos {
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct Span { pub struct Span {
start: Pos, pub start: Pos,
end: Pos, pub end: Pos,
} }
impl Span { impl Span {

2
src/libvm/.gitignore vendored Normal file
View File

@@ -0,0 +1,2 @@
/target
**/*.rs.bk

851
src/libvm/Cargo.lock generated Normal file
View 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
View 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
View 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(())
}

View 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
}

View 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
}

View 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
View 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);

View File

@@ -0,0 +1,294 @@
use crate::{
error::*,
inst::*,
mem::MemCursor,
reg::*,
visit::*,
vm::{Addr, HalfWord, Word},
};
use std::io::Write;
const WIDTH: usize = 60;
pub struct Disassemble<'w, 'o> {
writer: &'w mut dyn Write,
cursor: MemCursor<'o>,
addr_offset: Addr,
}
impl<'w, 'o> Disassemble<'w, 'o> {
pub fn new(writer: &'w mut dyn Write, content: &'o [u8], addr_offset: Addr) -> Self {
Disassemble {
writer,
cursor: MemCursor::new(content),
addr_offset,
}
}
pub fn is_done(&self) -> bool {
self.cursor.position() >= (self.cursor.get_ref().len() as u64)
}
fn adv(&mut self) -> Result<()> {
// note the () - this explicitly clones the cursor
let op = self.cursor().next_u16()?;
let next = self.cursor.position() + (inst_len(op) as u64);
self.cursor.set_position(next);
Ok(())
}
fn write_addr(&mut self, addr: Addr) {
write!(self.writer, "{:06x} | ", self.addr_offset + addr).unwrap();
}
fn write_bytes(&mut self, bytes: &[u8]) {
for b in bytes {
write!(self.writer, "{:02x} ", b).unwrap();
}
}
fn write_inst_bytes(&mut self, op: InstOp) {
let len = inst_len(op);
let start = self.cursor.position() as usize;
let end = start + len;
let bytes = &self.cursor.get_ref()[start..end];
self.write_bytes(bytes);
}
fn write_r1_r2_inst(&mut self, addr: Addr, op: InstOp, r1: Reg, r2: Reg) {
let len = inst_len(op);
let line_width = 6 + 3 + (3 * len);
let line_offset = WIDTH - line_width;
self.write_addr(addr);
self.write_inst_bytes(op);
let iname = inst_name(op).unwrap();
let r1name = reg_name(r1).unwrap().to_lowercase();
let r2name = reg_name(r2).unwrap().to_lowercase();
writeln!(
self.writer,
"{}| {:>10} %{} %{}",
" ".repeat(line_offset),
iname,
r1name,
r2name
)
.unwrap();
}
fn write_r1_inst(&mut self, addr: Addr, op: InstOp, r1: Reg) {
let line_width = 6 + 3 + (3 * inst_len(op));
let line_offset = WIDTH - line_width;
self.write_addr(addr);
self.write_inst_bytes(op);
let iname = inst_name(op).unwrap();
let r1name = reg_name(r1).unwrap().to_lowercase();
writeln!(
self.writer,
"{}| {:>10} %{}",
" ".repeat(line_offset),
iname,
r1name,
)
.unwrap();
}
fn write_r1_imm_inst(&mut self, addr: Addr, op: InstOp, r1: Reg, imm: Word) {
let line_width = 6 + 3 + (3 * inst_len(op));
let line_offset = WIDTH - line_width;
self.write_addr(addr);
self.write_inst_bytes(op);
let iname = inst_name(op).unwrap();
let r1name = reg_name(r1).unwrap().to_lowercase();
writeln!(
self.writer,
"{}| {:>10} %{} {:#X}",
" ".repeat(line_offset),
iname,
r1name,
imm,
)
.unwrap();
}
}
impl VisitInst for Disassemble<'_, '_> {
type Out = ();
fn cursor(&self) -> MemCursor {
self.cursor.clone()
}
fn add(&mut self, r1: Reg, r2: Reg) -> Result<Self::Out> {
self.write_r1_r2_inst(self.cursor.position(), ADD, r1, r2);
self.adv()?;
Ok(())
}
fn mul(&mut self, r1: Reg, r2: Reg) -> Result<Self::Out> {
self.write_r1_r2_inst(self.cursor.position(), MUL, r1, r2);
self.adv()?;
Ok(())
}
fn div(&mut self, r1: Reg, r2: Reg) -> Result<Self::Out> {
self.write_r1_r2_inst(self.cursor.position(), DIV, r1, r2);
self.adv()?;
Ok(())
}
fn mod_(&mut self, r1: Reg, r2: Reg) -> Result<Self::Out> {
self.write_r1_r2_inst(self.cursor.position(), MOD, r1, r2);
self.adv()?;
Ok(())
}
fn ineg(&mut self, r1: Reg) -> Result<Self::Out> {
self.write_r1_inst(self.cursor.position(), INEG, r1);
self.adv()?;
Ok(())
}
fn and(&mut self, r1: Reg, r2: Reg) -> Result<Self::Out> {
self.write_r1_r2_inst(self.cursor.position(), AND, r1, r2);
self.adv()?;
Ok(())
}
fn or(&mut self, r1: Reg, r2: Reg) -> Result<Self::Out> {
self.write_r1_r2_inst(self.cursor.position(), OR, r1, r2);
self.adv()?;
Ok(())
}
fn inv(&mut self, r1: Reg) -> Result<Self::Out> {
self.write_r1_inst(self.cursor.position(), INV, r1);
self.adv()?;
Ok(())
}
fn not(&mut self, r1: Reg) -> Result<Self::Out> {
self.write_r1_inst(self.cursor.position(), NOT, r1);
self.adv()?;
Ok(())
}
fn xor(&mut self, r1: Reg, r2: Reg) -> Result<Self::Out> {
self.write_r1_r2_inst(self.cursor.position(), XOR, r1, r2);
self.adv()?;
Ok(())
}
fn shl(&mut self, r1: Reg, r2: Reg) -> Result<Self::Out> {
self.write_r1_r2_inst(self.cursor.position(), SHL, r1, r2);
self.adv()?;
Ok(())
}
fn shr(&mut self, r1: Reg, r2: Reg) -> Result<Self::Out> {
self.write_r1_r2_inst(self.cursor.position(), SHR, r1, r2);
self.adv()?;
Ok(())
}
fn cmpeq(&mut self, r1: Reg, r2: Reg) -> Result<Self::Out> {
self.write_r1_r2_inst(self.cursor.position(), CMPEQ, r1, r2);
self.adv()?;
Ok(())
}
fn cmplt(&mut self, r1: Reg, r2: Reg) -> Result<Self::Out> {
self.write_r1_r2_inst(self.cursor.position(), CMPLT, r1, r2);
self.adv()?;
Ok(())
}
fn jmp(&mut self, r1: Reg) -> Result<Self::Out> {
self.write_r1_inst(self.cursor.position(), JMP, r1);
self.adv()?;
Ok(())
}
fn jz(&mut self, r1: Reg) -> Result<Self::Out> {
self.write_r1_inst(self.cursor.position(), JZ, r1);
self.adv()?;
Ok(())
}
fn jnz(&mut self, r1: Reg) -> Result<Self::Out> {
self.write_r1_inst(self.cursor.position(), JNZ, r1);
self.adv()?;
Ok(())
}
fn load(&mut self, r1: Reg, r2: Reg) -> Result<Self::Out> {
self.write_r1_r2_inst(self.cursor.position(), LOAD, r1, r2);
self.adv()?;
Ok(())
}
fn regcopy(&mut self, r1: Reg, r2: Reg) -> Result<Self::Out> {
self.write_r1_r2_inst(self.cursor.position(), REGCOPY, r1, r2);
self.adv()?;
Ok(())
}
fn storeimm64(&mut self, r1: Reg, w1: Word) -> Result<Self::Out> {
self.write_r1_imm_inst(self.cursor.position(), STOREIMM64, r1, w1);
self.adv()?;
Ok(())
}
fn storeimm32(&mut self, r1: Reg, w1: HalfWord) -> Result<Self::Out> {
self.write_r1_imm_inst(self.cursor.position(), STOREIMM32, r1, w1 as u64);
self.adv()?;
Ok(())
}
fn memcopy(&mut self, r1: Reg, r2: Reg) -> Result<Self::Out> {
self.write_r1_r2_inst(self.cursor.position(), MEMCOPY, r1, r2);
self.adv()?;
Ok(())
}
fn store(&mut self, r1: Reg, r2: Reg) -> Result<Self::Out> {
self.write_r1_r2_inst(self.cursor.position(), STORE, r1, r2);
self.adv()?;
Ok(())
}
fn halt(&mut self) -> Result<Self::Out> {
let line_width = 6 + 3 + 3 + 3;
let line_offset = WIDTH - line_width;
self.write_addr(self.cursor.position());
self.write_inst_bytes(HALT);
writeln!(
self.writer,
"{}| {:>10}",
" ".repeat(line_offset),
"HALT",
).unwrap();
self.adv()?;
Ok(())
}
fn nop(&mut self) -> Result<Self::Out> {
let line_width = 6 + 3 + 3 + 3;
let line_offset = WIDTH - line_width;
self.write_addr(self.cursor.position());
self.write_inst_bytes(NOP);
writeln!(
self.writer,
"{}| {:>10}",
" ".repeat(line_offset),
"NOP",
).unwrap();
self.adv()?;
Ok(())
}
}

43
src/libvm/src/error.rs Normal file
View 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>;

View File

@@ -4,5 +4,6 @@ bitflags! {
pub struct Flags: u64 { pub struct Flags: u64 {
const HALT = 1; const HALT = 1;
const COMPARE = 1 << 1; const COMPARE = 1 << 1;
const INTERRUPT_ENABLE = 1 << 2;
} }
} }

268
src/libvm/src/inst.rs Normal file
View 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;

View 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()
}
}

View File

@@ -1,8 +1,11 @@
#![allow(dead_code)]
pub mod addr;
pub mod error; pub mod error;
pub mod flags; pub mod flags;
pub mod inst; pub mod inst;
pub mod interrupt;
pub mod mem; pub mod mem;
pub mod obj; pub mod obj;
pub mod reg; pub mod reg;
mod tick; pub mod state;
pub mod vm;

307
src/libvm/src/mem.rs Normal file
View 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)
}
}
*/

View 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
}
}
}
}

View 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>;

View 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(())
}
}

View 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
}))
}

View 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()
}
}

View 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
}

View File

@@ -1,3 +1,5 @@
pub mod assemble; pub mod assemble;
pub mod disassemble;
pub mod error;
pub mod obj; pub mod obj;
pub mod syn; pub mod syn;

271
src/libvm/src/obj/obj.rs Normal file
View 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(&section.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);
}
}

View 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,
}
}
}

View 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]+ ;

View 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::*;
}

View 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
View 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
View 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)
}
}

189
src/libvm/src/tick.rs Normal file
View File

@@ -0,0 +1,189 @@
use crate::{error::*, flags::Flags, inst::*, reg::*, vm::*, visit::*, mem::MemCursor};
impl Vm {
pub fn tick(&mut self) -> Result<()> {
let next_ip = self.visit_inst()?;
self.set_reg(IP, next_ip);
Ok(())
}
fn next_ip(&self) -> Result<Addr> {
let ip = self.ip();
let op = self.get_inst_op(ip)?;
Ok(ip + (inst_len(op) as u64))
}
fn with_regs<F, B>(&mut self, r1: Reg, r2: Reg, mapping: F) -> B
where F: FnOnce(Word, Word) -> B
{
let w1 = self.get_reg(r1);
let w2 = self.get_reg(r2);
(mapping)(w1, w2)
}
}
impl VisitInst for Vm {
type Out = Addr;
fn cursor(&self) -> MemCursor {
self.mem_cursor(self.ip() as usize)
}
fn add(&mut self, r1: Reg, r2: Reg) -> Result<Self::Out> {
let out = self.with_regs(r1, r2, |w1, w2| w1.wrapping_add(w2));
self.set_reg(r1, out);
self.next_ip()
}
fn mul(&mut self, r1: Reg, r2: Reg) -> Result<Self::Out> {
let out = self.with_regs(r1, r2, |w1, w2| w1.wrapping_mul(w2));
self.set_reg(r1, out);
self.next_ip()
}
fn div(&mut self, r1: Reg, r2: Reg) -> Result<Self::Out> {
// TODO : check w2 == 0 and throw error/exception
let out = self.with_regs(r1, r2, |w1, w2| w1 / w2);
self.set_reg(r1, out);
self.next_ip()
}
fn mod_(&mut self, r1: Reg, r2: Reg) -> Result<Self::Out> {
// TODO : check w2 == 0 and throw error/exception
let out = self.with_regs(r1, r2, |w1, w2| w1 % w2);
self.set_reg(r1, out);
self.next_ip()
}
fn ineg(&mut self, r1: Reg) -> Result<Self::Out> {
let w1 = self.get_reg(r1);
self.set_reg(r1, (!w1).wrapping_add(1));
self.next_ip()
}
fn and(&mut self, r1: Reg, r2: Reg) -> Result<Self::Out> {
let out = self.with_regs(r1, r2, |w1, w2| w1 & w2);
self.set_reg(r1, out);
self.next_ip()
}
fn or(&mut self, r1: Reg, r2: Reg) -> Result<Self::Out> {
let out = self.with_regs(r1, r2, |w1, w2| w1 | w2);
self.set_reg(r1, out);
self.next_ip()
}
fn inv(&mut self, r1: Reg) -> Result<Self::Out> {
let w1 = self.get_reg(r1);
self.set_reg(r1, !w1);
self.next_ip()
}
fn not(&mut self, r1: Reg) -> Result<Self::Out> {
let w1 = self.get_reg(r1);
self.set_reg(r1, (w1 == 0) as Word);
self.next_ip()
}
fn xor(&mut self, r1: Reg, r2: Reg) -> Result<Self::Out> {
let out = self.with_regs(r1, r2, |w1, w2| w1 ^ w2);
self.set_reg(r1, out);
self.next_ip()
}
fn shl(&mut self, r1: Reg, r2: Reg) -> Result<Self::Out> {
let out = self.with_regs(r1, r2, |w1, w2| w1 << w2);
self.set_reg(r1, out);
self.next_ip()
}
fn shr(&mut self, r1: Reg, r2: Reg) -> Result<Self::Out> {
let out = self.with_regs(r1, r2, |w1, w2| w1 >> w2);
self.set_reg(r1, out);
self.next_ip()
}
fn cmpeq(&mut self, r1: Reg, r2: Reg) -> Result<Self::Out> {
let cmp = self.with_regs(r1, r2, |w1, w2| w1 == w2);
if cmp {
self.insert_flags(Flags::COMPARE);
} else {
self.remove_flags(Flags::COMPARE);
}
self.next_ip()
}
fn cmplt(&mut self, r1: Reg, r2: Reg) -> Result<Self::Out> {
let cmp = self.with_regs(r1, r2, |w1, w2| w1 < w2);
if cmp {
self.insert_flags(Flags::COMPARE);
} else {
self.remove_flags(Flags::COMPARE);
}
self.next_ip()
}
fn jmp(&mut self, r1: Reg) -> Result<Self::Out> {
let addr = self.get_reg(r1);
Ok(addr)
}
fn jz(&mut self, r1: Reg) -> Result<Self::Out> {
if !self.flags().contains(Flags::COMPARE) {
Ok(self.get_reg(r1))
} else {
self.next_ip()
}
}
fn jnz(&mut self, r1: Reg) -> Result<Self::Out> {
if self.flags().contains(Flags::COMPARE) {
Ok(self.get_reg(r1))
} else {
self.next_ip()
}
}
fn load(&mut self, r1: Reg, r2: Reg) -> Result<Self::Out> {
let value = Vm::load(self, r2)?;
self.set_reg_checked(r1, value)?;
self.next_ip()
}
fn regcopy(&mut self, r1: Reg, r2: Reg) -> Result<Self::Out> {
let value = self.get_reg_checked(r2)?;
self.set_reg_checked(r1, value)?;
self.next_ip()
}
fn storeimm64(&mut self, r1: Reg, w1: Word) -> Result<Self::Out> {
self.set_reg_checked(r1, w1)?;
self.next_ip()
}
fn storeimm32(&mut self, r1: Reg, w1: HalfWord) -> Result<Self::Out> {
self.set_reg_checked(r1, w1 as Word)?;
self.next_ip()
}
fn memcopy(&mut self, r1: Reg, r2: Reg) -> Result<Self::Out> {
let value = Vm::load(self, r2)?;
self.store(r1, value)?;
self.next_ip()
}
fn store(&mut self, r1: Reg, r2: Reg) -> Result<Self::Out> {
let value = self.get_reg_checked(r1)?;
self.store(r2, value)?;
self.next_ip()
}
fn halt(&mut self) -> Result<Self::Out> {
self.insert_flags(Flags::HALT);
self.next_ip()
}
fn nop(&mut self) -> Result<Self::Out> {
self.next_ip()
}
}

View File

@@ -1,41 +1,147 @@
#![allow(dead_code)]
mod common; mod common;
//mod syn;
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>>; 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(); fn get_reader(path: impl AsRef<Path>) -> Result<Box<dyn Read>> {
if args.len() <= 1 { if let Some("-") = path.as_ref().to_str() {
println!( Ok(Box::new(stdin()))
"usage: {} file.asm",
args.get(0).map(String::as_str).unwrap_or("rasp")
);
process::exit(1);
} else { } 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<()> { fn main() -> Result<()> {
use vm::obj::syn::parser::SectionsParser; use vm::{
let contents = get_input_string()?; obj::{assemble, disassemble::Disasm},
let ast = match SectionsParser::new().parse(&contents) { state::State,
Ok(ast) => ast,
Err(err) => {
eprintln!("{}", err);
process::exit(1);
}
}; };
let obj = vm::obj::obj::Object::try_from(&ast)?;
//println!("{:#?}", obj); let opt = Options::from_args();
let mut vm = vm::vm::Vm::new(); let object = assemble::assemble_path(&opt.input)?;
vm.load_object(obj, 1024 * 1024 * 64)?; // 64mb
let status = vm.run()?; if opt.compile_only {
println!("status: {}", status); let outfile = opt.out.clone().unwrap_or_else(|| {
Ok(()) 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);
}
} }

View File

@@ -1,12 +0,0 @@
use crate::vm::{vm::*, reg::Reg};
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 },
}
pub type Result<T, E = VmError> = std::result::Result<T, E>;

View File

@@ -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),
}
}

View File

@@ -1,80 +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;
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()
}
}

View File

@@ -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>;

View File

@@ -1,345 +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 pos = 0;
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 {
Some(SectionOrg::Start(start)) => (*start, start + bytes.len() as u64),
Some(SectionOrg::Range(start, end)) => (*start, *end),
None => (pos, pos + bytes.len() as u64),
};
pos = 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 { body, .. } | SectionBlock::Code { body, .. } => {
let mut exports = HashSet::new();
let mut labels = HashMap::new();
let mut pos = 0;
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) => {
let imm = match imm {
ImmValue::Number(num) => *num,
ImmValue::Label(name) => {
self.symbols.get(name).expect("TODO: value label not found")
}
};
if imm <= (u32::max_value() as u64) {
builder.op(STOREIMM32).r1(*r1).imm32(imm as u32)
} else {
builder.op(STOREIMM64).r1(*r1).imm64(imm)
}
}
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()
}
}

View File

View File

@@ -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 })
}
}

View File

@@ -1,114 +0,0 @@
use crate::vm::{inst::*, reg::Reg};
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum SectionBlock {
Data {
org: Option<SectionOrg>,
body: Vec<Line>,
},
Code {
org: Option<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),
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::MemCopy(_, _) => MEMCOPY,
Inst::RegCopy(_, _) => REGCOPY,
Inst::Nop => NOP,
Inst::Halt => HALT,
}
}
pub fn len(&self) -> usize {
inst_len(self.op())
}
}

View File

@@ -1,5 +0,0 @@
use lalrpop_util::lalrpop_mod;
lalrpop_mod!(pub parser, "/vm/obj/syn/parser.rs");
pub mod ast;
pub mod error;

View File

@@ -1,100 +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()
}
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),
"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*> => <>,
}

View File

@@ -1,95 +0,0 @@
macro_rules! registers {
{
$($variant:ident = $value:expr),* $(,)?
} => {
pub type Reg = u8;
$(
pub const $variant: Reg = $value;
)*
};
}
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,
LAST_REG = R49,
}

View File

@@ -1,134 +0,0 @@
use crate::vm::{error::*, flags::Flags, inst::*, reg::*, vm::*};
use std::io::stdin;
impl Vm {
pub fn tick(&mut self) -> Result<()> {
let mut cursor = self.mem_cursor(self.ip() as usize);
let op = cursor.next_u16()?;
let next_ip;
macro_rules! math_inst {
($mapping:expr) => {{
let (r1, r2) = cursor.next_regs()?;
next_ip = cursor.position();
let value = ($mapping)(self.get_reg_checked(r1)?, self.get_reg_checked(r2)?);
self.set_reg(r1, value);
}};
}
match op {
ADD => math_inst!(|w1: u64, w2: u64| w1.wrapping_add(w2)),
MUL => math_inst!(|w1: u64, w2: u64| w1.wrapping_mul(w2)),
DIV => math_inst!(|w1: u64, w2: u64| w1.wrapping_div(w2)),
MOD => math_inst!(|w1: u64, w2: u64| w1 % w2),
INEG => todo!(),
AND => math_inst!(|w1: u64, w2: u64| w1 & w2),
OR => math_inst!(|w1: u64, w2: u64| w1 | w2),
INV => {
let r1 = cursor.next_reg()?;
next_ip = cursor.position();
let value = self.get_reg_checked(r1)?;
self.set_reg(r1, !value);
}
NOT => {
let r1 = cursor.next_reg()?;
next_ip = cursor.position();
let value = self.get_reg(r1);
self.set_reg(r1, (value == 0) as Word);
}
XOR => math_inst!(|w1: u64, w2: u64| w1 ^ w2),
SHL => math_inst!(|w1: u64, w2: u64| w1 << w2),
SHR => math_inst!(|w1: u64, w2: u64| w1 >> w2),
CMPEQ => {
let (r1, r2) = cursor.next_regs()?;
next_ip = cursor.position();
let cmp = self.get_reg(r1) == self.get_reg(r2);
if cmp {
self.insert_flags(Flags::COMPARE);
} else {
self.remove_flags(Flags::COMPARE);
}
}
CMPLT => {
let (r1, r2) = cursor.next_regs()?;
next_ip = cursor.position();
let cmp = self.get_reg(r1) < self.get_reg(r2);
if cmp {
self.insert_flags(Flags::COMPARE);
} else {
self.remove_flags(Flags::COMPARE);
}
}
JMP => {
let r1 = cursor.next_reg()?;
next_ip = self.get_reg(r1);
}
JZ => {
let r1 = cursor.next_reg()?;
if !self.flags().contains(Flags::COMPARE) {
next_ip = self.get_reg(r1);
} else {
next_ip = cursor.position();
}
}
JNZ => {
let r1 = cursor.next_reg()?;
if self.flags().contains(Flags::COMPARE) {
next_ip = self.get_reg(r1);
} else {
next_ip = cursor.position();
}
}
LOAD => {
let (r1, r2) = cursor.next_regs()?;
next_ip = cursor.position();
let value = self.load(r2)?;
self.set_reg_checked(r1, value)?;
}
REGCOPY => math_inst!(|_: u64, w2: u64| w2),
STOREIMM64 => {
let r1 = cursor.next_reg()?;
// skip
cursor.next_u32()?;
let imm = cursor.next_u64()?;
next_ip = cursor.position();
self.set_reg(r1, imm);
}
STOREIMM32 => {
let r1 = cursor.next_reg()?;
let imm = cursor.next_u32()?;
next_ip = cursor.position();
self.set_reg(r1, imm as u64);
}
MEMCOPY => {
let (r1, r2) = cursor.next_regs()?;
next_ip = cursor.position();
let value = self.load(r2)?;
self.store(r1, value)?;
}
STORE => {
let (r1, r2) = cursor.next_regs()?;
next_ip = cursor.position();
let value = self.get_reg(r1);
self.store(r2, value)?;
}
HALT => {
next_ip = cursor.position();
self.insert_flags(Flags::HALT);
}
NOP => {
next_ip = cursor.position();
}
_ => panic!("unknown instruction opcode: 0x{:04x}", op),
}
println!("op: {:04x} {}", op, inst_name(op).unwrap());
println!("ip: {:05x}", self.ip());
println!("next_ip: {:05x}", next_ip);
let mut _line = String::new();
stdin().read_line(&mut _line).unwrap();
self.set_reg(IP, next_ip);
Ok(())
}
}

View File

@@ -1,200 +0,0 @@
use crate::vm::{error::*, flags::*, 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_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(())
}
}
}

View File

@@ -1,34 +0,0 @@
data $0x1000 .. $0x2000 {
}
code $0x0 {
main:
storeimm %r00, $0xDEAD
storeimm %r01, $16
shl %r00, %r01
storeimm %r01, $0xBEEF
or %r00, %r01
storeimm %r01, $0xDEADBEEF
cmpeq %r00, %r01
storeimm %r00, failure
storeimm %r01, ok
jz %r00
jmp %r01
failure:
storeimm %status, $1
ok: halt
.export main
}
meta {
entry: main
}

114
tests/test_arithmetic.asm Normal file
View 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
View 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
View 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
View 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
View File

@@ -4,7 +4,7 @@ This is an outline of the VM that drives this language.
# Primitives # 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. * Addresses point to single bytes.
* Signed numbers use two's complement. * 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 * SP - Stack pointer
* FP - Frame pointer * FP - Frame pointer
* FLAGS - CPU flags * FLAGS - CPU flags
* NULL - Always zero for reading and will never change after writing.
* (8 unused registers)
* STATUS - Generic status code * 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 ## CPU Flags
@@ -34,6 +43,7 @@ CPU flags are addressed by bit index, going from right to left.
* `00` - Halt flag * `00` - Halt flag
* `01` - Compare flag * `01` - Compare flag
* `02` - Enable interrupts
### Flag ideas ### 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 * Overwriting a register without its value being used
* Mixing arithmetic with bit twiddling on the same target * Mixing arithmetic with bit twiddling on the same target
## Register ideas
* Other possible names: Z, NIL
# Instructions # Instructions
Instructions attempt to be as small as possible while conforming to 8-bit, 16-bit, 32-bit, or 64-bit All instructions have 16-bit opcodes. There are three types of instructions:
alignment. All instructions have 16-bit opcodes.
* 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 ## Arithmetic
@@ -57,161 +128,44 @@ Arithmetic instructions store their result in the first register specified. Over
wrapping around to 0. wrapping around to 0.
* Add * Add
* Opcode: 0x0000 * Opcode: 0x1000
* **Params**: REG1, REG2 * Params: Destination, source
* `REG1 = REG1 + REG2` * Sub
* Unsigned addition * Opcode: 0x1001
* ``` * Params: Destination, source
32 16 10 4 0
opcode reg1 reg2 unused
/ / / /
+-------------------------------------------+
| 0000000000000000 | ...... | ...... | XXXX |
+-------------------------------------------+
```
* Mul * Mul
* Opcode: 0x0001 * Opcode: 0x1002
* **Params**: REG1, REG2 * Params: Destination, source
* `REG1 = REG1 * REG2`
* Unsigned multiplication
* ```
32 16 10 4 0
opcode reg1 reg2 unused
/ / / /
+-------------------------------------------+
| 0000000000000001 | ...... | ...... | XXXX |
+-------------------------------------------+
```
* Div * Div
* Opcode: 0x0002 * Opcode: 0x1003
* **Params**: REG1, REG2 * Params: Destination, source
* `REG1 = REG1 / REG2`
* Unsigned division
* ```
32 16 10 4 0
opcode reg1 reg2 unused
/ / / /
+-------------------------------------------+
| 0000000000000010 | ...... | ...... | XXXX |
+-------------------------------------------+
```
* Mod * Mod
* Opcode: 0x0003 * Opcode: 0x1004
* **Params**: REG1, REG2 * Params: Destination, source
* `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 |
+----------------------------------------+
```
* And * And
* Opcode: 0x0005 * Opcode: 0x1005
* **Params**: REG1, REG2 * Params: Destination, source
* `REG1 = REG1 & REG2`
* ```
32 16 10 4 0
opcode reg1 reg2 unused
/ / / /
+-------------------------------------------+
| 0000000000000101 | ...... | ...... | XXXX |
+-------------------------------------------+
```
* Or * Or
* Opcode: 0x0006 * Opcode: 0x1006
* **Params**: REG1, REG2 * Params: Destination, source
* `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 |
+----------------------------------------+
```
* Xor * Xor
* Opcode: 0x0009 * Opcode: 0x1007
* **Params**: REG1, REG2 * Params: Destination, source
* `REG1 = REG1 ^ REG2`
* ```
32 16 10 4 0
opcode reg1 reg2 unused
/ / / /
+-------------------------------------------+
| 0000000000001001 | ...... | ...... | XXXX |
+-------------------------------------------+
```
* Shl * Shl
* Opcode: 0x000A * Opcode: 0x1008
* **Params**: REG1, REG2 * Params: Destination, source
* `REG1 = REG1 << REG2`
* ```
32 16 10 4 0
opcode reg1 reg2 unused
/ / / /
+-------------------------------------------+
| 0000000000001010 | ...... | ...... | XXXX |
+-------------------------------------------+
```
* Shr * Shr
* Opcode: 0x000B * Opcode: 0x1009
* **Params**: REG1, REG2 * Params: Destination, source
* `REG1 = REG1 >> REG2` * INeg
* Does not sign extend * Opcode: 0x100a
* ``` * Params: Destination, source
32 16 10 4 0 * Inv
opcode reg1 reg2 unused * Opcode: 0x100b
/ / / / * Params: Destination, source
+-------------------------------------------+ * Not
| 0000000000001011 | ...... | ...... | XXXX | * Opcode: 0x100c
+-------------------------------------------+ * Params: Destination, source
```
### TODO ### TODO
@@ -222,209 +176,196 @@ wrapping around to 0.
## Control flow ## Control flow
* CmpEq * CmpEq
* Opcode: 0x1000 * Opcode: 0x2000
* **Params**: REG1, REG2 * Params: Source, source
* ```
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 |
+-------------------------------------------+
```
* CmpLt * CmpLt
* Opcode: 0x1001 * Opcode: 0x2001
* **Params**: REG1, REG2 * Params: Source, source
* ```
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 |
+-------------------------------------------+
```
* Jmp * Jmp
* Opcode: 0x1100 * Opcode: 0x2002
* **Params**: REG1 * Params: Source
* `IP = REG1;`
* Jumps to the address in REG1 unconditionally.
* ```
32 16 10 0
opcode reg1 unused
/ / /
+----------------------------------------+
| 0001000100000000 | ...... | XXXXXXXXXX |
+----------------------------------------+
```
* Jz * Jz
* Opcode: 0x1101 * Opcode: 0x2003
* **Params**: REG1 * Params: Source
* ```
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 |
+----------------------------------------+
```
* Jnz * Jnz
* Opcode: 0x1002 * Opcode: 0x2004
* **Params**: REG1 * Params: Source
* ```
if FLAGS[1] != 0 { ## Functions
IP = REG1;
} * Call
``` * Opcode: 0x3000
* Jumps to the address in REG1 if COMPARE flag is 1. * Params: Source
* ``` * When this instruction is executed, these actions occur:
32 16 10 0 * Push the current stack frame pointer
opcode reg1 unused * 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
| 0001000100000002 | ...... | XXXXXXXXXX | * 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 ## Data movement
* Load * Mov
* Opcode: 0x2000 * Opcode: 0x4000
* **Params**: REG1, REG2 * Params: Source, Dest
* ```
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 |
+-------------------------------------------+
```
## Miscellaneous ## Miscellaneous
* Halt * Halt
* Opcode: 0xF000 * Opcode: 0xF000
* **Params**: (none)
* `FLAGS[0] = 1`
* Halts the machine
* ```
16
opcode
/
+------------------+
| 1111000000000000 |
+------------------+
```
* Nop * Nop
* Opcode: 0xF001 * Opcode: 0xF001
* **Params**: (none) * Dump
* Does nothing * Opcode: 0xF002
* ```
16
opcode
/
+------------------+
| 1111000000000001 |
+------------------+
```
## Other instructions TODO # Interrupts
* Call Interrupts are signaled explicitly from software or from hardware signaling the CPU. When an
* Takes address and number of bytes on the stack that are for args(?) interrupt signal is set, the CPU will finish whatever instruction it is executing, and then begin
* Updates SP, FP, IP, storing previous values starting at the new FP handling the interrupt whose signal was set. Software interrupts may be invoked using the `int`
* Ret instruction, supplying the index of the interrupt to invoke. Hardware interrupts are invoked
* Uses FP to determine previous SP, FP, and IP and restores them directly by a hardware event, e.g. a keypress. Hardware and software interrupts are treated equally
* Push in the CPU, and as such, they are all maskable.
* Pop
* More immediate stores? An interrupt may be masked in two ways: either through its entry in the IVT, or through the "enable
* Idea: Store42 (or whatever number of bits) that maximizes the usage of a 64-bit instruction 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 # Binary object format
@@ -435,7 +376,7 @@ the object.
The header is composed of: 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 - Version of the file
* 32 bits - The number of sections in the file * 32 bits - The number of sections in the file
* section descriptions detailed below * section descriptions detailed below
@@ -449,7 +390,6 @@ the section contents.
* 8 bits - Section kind * 8 bits - Section kind
* 0x00 - Data * 0x00 - Data
* 0x10 - Code
* 0xFF - Meta * 0xFF - Meta
* 64 bits - Length of the section * 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. 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 start - where in memory the content of this section begins
* 64 bits - section load end - where in memory the content of this section ends * 64 bits - section length - how long the memory content is
### 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.
### Meta section ### 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: 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 # General TODO
* Interrupts * Memory permissions
* MMIO regions * 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)