5
src/common/mod.rs
Normal file
5
src/common/mod.rs
Normal file
@@ -0,0 +1,5 @@
|
||||
pub mod span;
|
||||
|
||||
pub mod prelude {
|
||||
pub use super::span::*;
|
||||
}
|
||||
67
src/common/span.rs
Normal file
67
src/common/span.rs
Normal file
@@ -0,0 +1,67 @@
|
||||
use std::cmp::Ordering;
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub struct Pos {
|
||||
pub source: usize,
|
||||
pub line: usize,
|
||||
pub col: usize,
|
||||
pub byte: usize,
|
||||
pub len: usize,
|
||||
}
|
||||
|
||||
impl Pos {
|
||||
pub fn new(source: usize, line: usize, col: usize, byte: usize, len: usize) -> Self {
|
||||
Pos {
|
||||
source,
|
||||
line,
|
||||
col,
|
||||
byte,
|
||||
len,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_char(c: char, source: usize, line: usize, col: usize, byte: usize) -> Self {
|
||||
Pos::new(source, line, col, byte, c.len_utf8())
|
||||
}
|
||||
|
||||
pub fn adv_char(self, c: char) -> Self {
|
||||
let mut next = self;
|
||||
next.byte += next.len;
|
||||
next.len = c.len_utf8();
|
||||
next.source += 1;
|
||||
next.col += 1;
|
||||
next
|
||||
}
|
||||
|
||||
pub fn adv_line(self) -> Self {
|
||||
let mut next = self;
|
||||
next.line += 1;
|
||||
next
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialOrd for Pos {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
||||
self.source.partial_cmp(&other.source)
|
||||
}
|
||||
}
|
||||
|
||||
impl Ord for Pos {
|
||||
fn cmp(&self, other: &Self) -> Ordering {
|
||||
self.partial_cmp(other).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub struct Span {
|
||||
start: Pos,
|
||||
end: Pos,
|
||||
}
|
||||
|
||||
impl Span {
|
||||
pub fn union(self, other: Span) -> Self {
|
||||
let start = self.start.min(other.start);
|
||||
let end = self.end.max(other.end);
|
||||
Span { start, end, }
|
||||
}
|
||||
}
|
||||
7
src/main.rs
Normal file
7
src/main.rs
Normal file
@@ -0,0 +1,7 @@
|
||||
mod common;
|
||||
//mod syn;
|
||||
mod vm;
|
||||
|
||||
fn main() {
|
||||
println!("Hello, world!");
|
||||
}
|
||||
2
src/syn/lexer.rs
Normal file
2
src/syn/lexer.rs
Normal file
@@ -0,0 +1,2 @@
|
||||
pub struct Lexer {
|
||||
}
|
||||
1
src/syn/mod.rs
Normal file
1
src/syn/mod.rs
Normal file
@@ -0,0 +1 @@
|
||||
pub mod lexer;
|
||||
8
src/vm/flags.rs
Normal file
8
src/vm/flags.rs
Normal file
@@ -0,0 +1,8 @@
|
||||
use bitflags::bitflags;
|
||||
|
||||
bitflags! {
|
||||
pub struct Flags: u64 {
|
||||
const HALT = 1;
|
||||
const COMPARE = 1 << 1;
|
||||
}
|
||||
}
|
||||
30
src/vm/inst.rs
Normal file
30
src/vm/inst.rs
Normal file
@@ -0,0 +1,30 @@
|
||||
use crate::vm::reg::*;
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
#[allow(dead_code)]
|
||||
pub enum Inst {
|
||||
Add(Reg, Reg),
|
||||
Mul(Reg, Reg),
|
||||
Div(Reg, Reg),
|
||||
Neg(Reg),
|
||||
And(Reg, Reg),
|
||||
Or(Reg, Reg),
|
||||
Xor(Reg, Reg),
|
||||
Shl(Reg, Reg),
|
||||
Shr(Reg, Reg),
|
||||
|
||||
CmpEq(Reg, Reg),
|
||||
CmpLt(Reg, Reg),
|
||||
Jz(Reg),
|
||||
Jnz(Reg),
|
||||
|
||||
Load(Reg, Reg),
|
||||
Store(Reg, Reg),
|
||||
StoreImm(Reg, u32),
|
||||
Copy(Reg, Reg),
|
||||
|
||||
Nop,
|
||||
Halt,
|
||||
}
|
||||
|
||||
// https://crates.io/crates/packed_struct
|
||||
5
src/vm/mod.rs
Normal file
5
src/vm/mod.rs
Normal file
@@ -0,0 +1,5 @@
|
||||
pub mod syn;
|
||||
pub mod inst;
|
||||
pub mod reg;
|
||||
pub mod vm;
|
||||
pub mod flags;
|
||||
83
src/vm/reg.rs
Normal file
83
src/vm/reg.rs
Normal file
@@ -0,0 +1,83 @@
|
||||
#[repr(u8)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
#[allow(dead_code)]
|
||||
pub enum Reg {
|
||||
// https://crates.io/crates/packed_struct
|
||||
// TODO : check this muffugin shit out!!
|
||||
|
||||
/// Instruction pointer
|
||||
Ip = 0,
|
||||
|
||||
/// Stack pointer
|
||||
Sp,
|
||||
|
||||
/// Frame pointer
|
||||
Fp,
|
||||
|
||||
/// Flags
|
||||
Flags,
|
||||
|
||||
Unused00,
|
||||
Unused01,
|
||||
Unused02,
|
||||
Unused03,
|
||||
Unused04,
|
||||
Unused05,
|
||||
Unused06,
|
||||
Unused07,
|
||||
Unused08,
|
||||
|
||||
/// General status code
|
||||
Status,
|
||||
|
||||
R00,
|
||||
R01,
|
||||
R02,
|
||||
R03,
|
||||
R04,
|
||||
R05,
|
||||
R06,
|
||||
R07,
|
||||
R08,
|
||||
R09,
|
||||
R10,
|
||||
R11,
|
||||
R12,
|
||||
R13,
|
||||
R14,
|
||||
R15,
|
||||
R16,
|
||||
R17,
|
||||
R18,
|
||||
R19,
|
||||
R20,
|
||||
R21,
|
||||
R22,
|
||||
R23,
|
||||
R24,
|
||||
R25,
|
||||
R26,
|
||||
R27,
|
||||
R28,
|
||||
R29,
|
||||
R30,
|
||||
R31,
|
||||
R32,
|
||||
R33,
|
||||
R34,
|
||||
R35,
|
||||
R36,
|
||||
R37,
|
||||
R38,
|
||||
R39,
|
||||
R40,
|
||||
R41,
|
||||
R42,
|
||||
R43,
|
||||
R44,
|
||||
R45,
|
||||
R46,
|
||||
R47,
|
||||
R48,
|
||||
R49,
|
||||
}
|
||||
46
src/vm/syn/ast.rs
Normal file
46
src/vm/syn/ast.rs
Normal file
@@ -0,0 +1,46 @@
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum Line {
|
||||
Section(Section),
|
||||
Inst(Inst),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum Section {
|
||||
Code,
|
||||
Data,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum InstOp {
|
||||
Add,
|
||||
Mul,
|
||||
Div,
|
||||
Neg,
|
||||
And,
|
||||
Or,
|
||||
Xor,
|
||||
Shl,
|
||||
Shr,
|
||||
CmpEq,
|
||||
CmpLt,
|
||||
Jz,
|
||||
Jnz,
|
||||
Load,
|
||||
Store,
|
||||
StoreImm,
|
||||
Copy,
|
||||
Nop,
|
||||
Halt,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct Inst {
|
||||
pub op: InstOp,
|
||||
pub args: Vec<Value>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum Value {
|
||||
Number(u64),
|
||||
Label(String),
|
||||
}
|
||||
4
src/vm/syn/mod.rs
Normal file
4
src/vm/syn/mod.rs
Normal file
@@ -0,0 +1,4 @@
|
||||
use lalrpop_util::lalrpop_mod;
|
||||
|
||||
lalrpop_mod!(pub parser, "/vm/syn/parser.rs");
|
||||
pub mod ast;
|
||||
73
src/vm/syn/parser.lalrpop
Normal file
73
src/vm/syn/parser.lalrpop
Normal file
@@ -0,0 +1,73 @@
|
||||
use std::str::FromStr;
|
||||
use crate::vm::{
|
||||
syn::ast::*,
|
||||
};
|
||||
|
||||
grammar;
|
||||
|
||||
// TODO : instkind
|
||||
InstOp: InstOp = {
|
||||
"add" => InstOp::Add,
|
||||
"mul" => InstOp::Mul,
|
||||
"div" => InstOp::Div,
|
||||
"neg" => InstOp::Neg,
|
||||
"and" => InstOp::And,
|
||||
"or" => InstOp::Or,
|
||||
"xor" => InstOp::Xor,
|
||||
"shl" => InstOp::Shl,
|
||||
"shr" => InstOp::Shr,
|
||||
"cmpeq" => InstOp::CmpEq,
|
||||
"cmplt" => InstOp::CmpLt,
|
||||
"jz" => InstOp::Jz,
|
||||
"jnz" => InstOp::Jnz,
|
||||
"load" => InstOp::Load,
|
||||
"store" => InstOp::Store,
|
||||
"storeimm" => InstOp::StoreImm,
|
||||
"copy" => InstOp::Copy,
|
||||
"nop" => InstOp::Nop,
|
||||
"halt" => InstOp::Halt,
|
||||
}
|
||||
|
||||
LabelDef: String = {
|
||||
<Label> ":" => <>
|
||||
}
|
||||
|
||||
pub Label: String = {
|
||||
"[a-zA-Z]+" => String::from(<>),
|
||||
}
|
||||
|
||||
pub Section: Section = {
|
||||
".code" => Section::Code,
|
||||
".data" => Section::Data,
|
||||
}
|
||||
|
||||
pub Inst: Inst = {
|
||||
<op:InstOp> <head:Value> <tail:("," <Value>)+> => {
|
||||
Inst {
|
||||
op,
|
||||
args: {
|
||||
let mut tail = tail;
|
||||
tail.insert(0, head);
|
||||
tail
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
<op:InstOp> <head:Value?> => {
|
||||
Inst { op, args: if let Some(head) = head { vec![head] } else { vec![] } }
|
||||
}
|
||||
}
|
||||
|
||||
pub Value: Value = {
|
||||
<Number> => Value::Number(<>),
|
||||
<Label> => Value::Label(<>),
|
||||
}
|
||||
|
||||
pub Number: u64 = {
|
||||
<s:r"[0-9]+"> => u64::from_str(s).unwrap()
|
||||
}
|
||||
|
||||
pub Line: Line = {
|
||||
<Section> => Line::Section(<>),
|
||||
<Inst> => Line::Inst(<>),
|
||||
}
|
||||
19
src/vm/syn/pass.rs
Normal file
19
src/vm/syn/pass.rs
Normal file
@@ -0,0 +1,19 @@
|
||||
use crate::vm::syn::ast::*;
|
||||
|
||||
pub trait Pass<In> {
|
||||
type Out;
|
||||
|
||||
fn pass(self, inval: In) -> Self::Out;
|
||||
}
|
||||
|
||||
/// Confirms the instructions and their validity.
|
||||
pub struct InstPass {
|
||||
}
|
||||
|
||||
impl Pass<Vec<Line>> for InstPass {
|
||||
type Out = Vec<Line>;
|
||||
|
||||
fn pass(self, inval: Vec<Line>) -> Self::Out {
|
||||
inval
|
||||
}
|
||||
}
|
||||
315
src/vm/vm.rs
Normal file
315
src/vm/vm.rs
Normal file
@@ -0,0 +1,315 @@
|
||||
use crate::vm::{
|
||||
reg::*,
|
||||
inst::*,
|
||||
flags::*,
|
||||
};
|
||||
use byteorder::{LE, ReadBytesExt, WriteBytesExt};
|
||||
use std::{
|
||||
mem,
|
||||
io::Cursor,
|
||||
};
|
||||
|
||||
pub type Word = u64;
|
||||
pub type HalfWord = u32;
|
||||
pub type Registers = [Word; 64];
|
||||
|
||||
pub struct Vm {
|
||||
code: Vec<Inst>,
|
||||
memory: Vec<u8>,
|
||||
registers: Registers,
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
impl Vm {
|
||||
pub fn new(code: Vec<Inst>, memory: Vec<u8>, registers: Registers) -> Self {
|
||||
Vm { code, memory, registers }
|
||||
}
|
||||
|
||||
pub fn run(&mut self) {
|
||||
while !self.is_halted() {
|
||||
self.tick();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn tick(&mut self) {
|
||||
let inst = self.decode();
|
||||
let ip = self.ip();
|
||||
self.set_reg(Reg::Ip, ip + 1);
|
||||
self.execute(inst);
|
||||
}
|
||||
|
||||
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: Word) -> Word {
|
||||
let mut reader = Cursor::new(&self.memory[(addr as usize)..]);
|
||||
reader.read_u64::<LE>()
|
||||
.expect("word outside of address range")
|
||||
}
|
||||
|
||||
pub fn get_halfword(&self, addr: Word) -> HalfWord {
|
||||
let mut reader = Cursor::new(&self.memory[(addr as usize)..]);
|
||||
reader.read_u32::<LE>()
|
||||
.expect("word outside of address range")
|
||||
}
|
||||
|
||||
pub fn get_byte(&self, addr: Word) -> u8 {
|
||||
let mut reader = Cursor::new(&self.memory[(addr as usize)..]);
|
||||
reader.read_u8()
|
||||
.expect("word outside of address range")
|
||||
}
|
||||
|
||||
pub fn set_word(&mut self, addr: Word, value: Word) {
|
||||
let mut writer = Cursor::new(&mut self.memory[(addr as usize)..]);
|
||||
writer.write_u64::<LE>(value)
|
||||
.expect("word outside of address range");
|
||||
}
|
||||
|
||||
pub fn set_halfword(&mut self, addr: Word, value: HalfWord) {
|
||||
let mut writer = Cursor::new(&mut self.memory[(addr as usize)..]);
|
||||
writer.write_u32::<LE>(value)
|
||||
.expect("word outside of address range");
|
||||
}
|
||||
|
||||
pub fn set_byte(&mut self, addr: Word, value: u8) {
|
||||
let mut writer = Cursor::new(&mut self.memory[(addr as usize)..]);
|
||||
writer.write_u8(value)
|
||||
.expect("word outside of address range");
|
||||
}
|
||||
|
||||
pub fn load(&self, reg: Reg) -> Word {
|
||||
self.get_word(self.get_reg(reg))
|
||||
}
|
||||
|
||||
pub fn store(&mut self, reg: Reg, value: Word) {
|
||||
let addr = self.get_reg(reg);
|
||||
self.set_word(addr, value);
|
||||
}
|
||||
|
||||
pub fn get_reg(&self, reg: Reg) -> Word {
|
||||
self.registers[reg as usize]
|
||||
}
|
||||
|
||||
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(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(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(Reg::Flags, flags.bits());
|
||||
}
|
||||
|
||||
pub fn map_flags<F>(&mut self, mapping: F)
|
||||
where F: FnOnce(Flags) -> Flags
|
||||
{
|
||||
let in_flags = self.flags();
|
||||
self.set_flags((mapping)(in_flags));
|
||||
}
|
||||
|
||||
pub fn decode(&self) -> Inst {
|
||||
self.code[self.ip() as usize]
|
||||
}
|
||||
|
||||
pub fn execute(&mut self, inst: Inst) {
|
||||
macro_rules! map {
|
||||
($r0:ident = $($tail:tt)*) => {{
|
||||
let w0 = map!($($tail)+);
|
||||
self.set_reg($r0, w0);
|
||||
}};
|
||||
|
||||
($r1:ident $sym:tt $r2:ident) => {{
|
||||
let w1 = self.get_reg($r1);
|
||||
let w2 = self.get_reg($r2);
|
||||
(w1 $sym w2)
|
||||
}}
|
||||
}
|
||||
|
||||
match inst {
|
||||
Inst::Add(r1, r2) => {
|
||||
let w1 = self.get_reg(r1);
|
||||
let w2 = self.get_reg(r2);
|
||||
let value = w1.wrapping_add(w2);
|
||||
self.set_reg(r1, value);
|
||||
}
|
||||
Inst::Mul(r1, r2) => {
|
||||
let w1 = self.get_reg(r1);
|
||||
let w2 = self.get_reg(r2);
|
||||
let value = w1.wrapping_mul(w2);
|
||||
self.set_reg(r1, value);
|
||||
}
|
||||
Inst::Div(r1, r2) => map!(r1 = r1 / r2),
|
||||
Inst::Neg(r1) => {
|
||||
let value = self.get_reg(r1);
|
||||
let ivalue = -i64::from_ne_bytes(value.to_ne_bytes());
|
||||
let uvalue = u64::from_ne_bytes(ivalue.to_ne_bytes());
|
||||
self.set_reg(r1, uvalue);
|
||||
}
|
||||
Inst::And(r1, r2) => map!(r1 = r1 & r2),
|
||||
Inst::Or(r1, r2) => map!(r1 = r1 | r2),
|
||||
Inst::Xor(r1, r2) => map!(r1 = r1 ^ r2),
|
||||
Inst::Shl(r1, r2) => map!(r1 = r1 << r2),
|
||||
Inst::Shr(r1, r2) => map!(r1 = r1 >> r2),
|
||||
Inst::CmpEq(r1, r2) => {
|
||||
if map!(r1 == r2) {
|
||||
self.insert_flags(Flags::COMPARE);
|
||||
} else {
|
||||
self.remove_flags(Flags::COMPARE);
|
||||
}
|
||||
}
|
||||
Inst::CmpLt(r1, r2) => {
|
||||
if map!(r1 < r2) {
|
||||
self.insert_flags(Flags::COMPARE);
|
||||
} else {
|
||||
self.remove_flags(Flags::COMPARE);
|
||||
}
|
||||
}
|
||||
Inst::Jz(r1) => {
|
||||
if !self.flags().contains(Flags::COMPARE) {
|
||||
let w1 = self.get_reg(r1);
|
||||
self.set_reg(Reg::Ip, w1);
|
||||
}
|
||||
}
|
||||
Inst::Jnz(r1) => {
|
||||
if self.flags().contains(Flags::COMPARE) {
|
||||
let w1 = self.get_reg(r1);
|
||||
self.set_reg(Reg::Ip, w1);
|
||||
}
|
||||
}
|
||||
Inst::Load(r1, r2) => {
|
||||
let value = self.load(r2);
|
||||
self.set_reg(r1, value);
|
||||
}
|
||||
Inst::Store(r1, r2) => {
|
||||
let value = self.get_reg(r1);
|
||||
self.store(r2, value);
|
||||
}
|
||||
Inst::StoreImm(r1, value) => {
|
||||
self.set_reg(r1, value as u64);
|
||||
}
|
||||
Inst::Copy(r1, r2) => {
|
||||
let w1 = self.load(r2);
|
||||
self.store(r1, w1);
|
||||
}
|
||||
Inst::Nop => {}
|
||||
Inst::Halt => {
|
||||
self.insert_flags(Flags::HALT);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn with_registers<F, B>(&self, r1: Reg, r2: Reg, map_fn: F) -> B
|
||||
where F: FnOnce(Word, Word) -> B
|
||||
{
|
||||
let w1 = self.get_reg(r1);
|
||||
let w2 = self.get_reg(r2);
|
||||
(map_fn)(w1, w2)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
macro_rules! itou {
|
||||
{ $expr:expr } => { u64::from_ne_bytes(($expr).to_ne_bytes()) };
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_arithmetic() {
|
||||
use Inst::*;
|
||||
use Reg::*;
|
||||
|
||||
let code = vec![
|
||||
StoreImm(R01, 25),
|
||||
|
||||
Add(R00, R01),
|
||||
Halt,
|
||||
|
||||
Mul(R00, R01),
|
||||
Halt,
|
||||
|
||||
Div(R00, R01),
|
||||
Halt,
|
||||
|
||||
StoreImm(R01, 15),
|
||||
And(R00, R01),
|
||||
Halt, // should be 9
|
||||
|
||||
// Storing large numbers
|
||||
StoreImm(R00, 0xABCD_EF98),
|
||||
StoreImm(R01, 32),
|
||||
Shl(R00, R01),
|
||||
Or(R00, R01),
|
||||
Halt,
|
||||
|
||||
Shr(R00, R01),
|
||||
Halt,
|
||||
|
||||
Xor(R00, R00),
|
||||
Halt,
|
||||
];
|
||||
|
||||
let mut vm = Vm::new(code, Default::default(), [0; 64]);
|
||||
|
||||
vm.run();
|
||||
assert_eq!(vm.get_reg(R00), 25);
|
||||
assert_eq!(vm.get_reg(R01), 25);
|
||||
vm.resume();
|
||||
|
||||
vm.run();
|
||||
assert_eq!(vm.get_reg(R00), 625);
|
||||
assert_eq!(vm.get_reg(R01), 25);
|
||||
vm.resume();
|
||||
|
||||
vm.run();
|
||||
assert_eq!(vm.get_reg(R00), 25);
|
||||
assert_eq!(vm.get_reg(R01), 25);
|
||||
vm.resume();
|
||||
|
||||
vm.run();
|
||||
assert_eq!(vm.get_reg(R00), 9);
|
||||
assert_eq!(vm.get_reg(R01), 15);
|
||||
vm.resume();
|
||||
|
||||
vm.run();
|
||||
assert_eq!(vm.get_reg(R00), 0xABCD_EF98_0000_0020);
|
||||
assert_eq!(vm.get_reg(R01), 32);
|
||||
vm.resume();
|
||||
|
||||
// TODO : signed instructions
|
||||
|
||||
vm.run();
|
||||
assert_eq!(vm.get_reg(R00), 0xABCD_EF98);
|
||||
vm.resume();
|
||||
|
||||
vm.run();
|
||||
assert_eq!(vm.get_reg(R00), 0);
|
||||
vm.resume();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user