Implemented some portion of VM, missing validation
This commit is contained in:
parent
a4b22e2053
commit
e67d512f89
7
Cargo.lock
generated
7
Cargo.lock
generated
|
@ -38,6 +38,7 @@ version = "0.1.0"
|
|||
dependencies = [
|
||||
"hashbrown",
|
||||
"log",
|
||||
"static_assertions",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -55,6 +56,12 @@ version = "1.17.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3"
|
||||
|
||||
[[package]]
|
||||
name = "static_assertions"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
|
||||
|
||||
[[package]]
|
||||
name = "version_check"
|
||||
version = "0.9.4"
|
||||
|
|
|
@ -3,6 +3,10 @@ name = "hbvm"
|
|||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[profile.release]
|
||||
lto = true
|
||||
|
||||
[dependencies]
|
||||
log = "*"
|
||||
hashbrown = "0.13.2"
|
||||
static_assertions = "1.0"
|
||||
|
|
56
hbvm/src/bytecode.rs
Normal file
56
hbvm/src/bytecode.rs
Normal file
|
@ -0,0 +1,56 @@
|
|||
pub const REG_COUNT: usize = 60;
|
||||
|
||||
macro_rules! constmod {
|
||||
($vis:vis $mname:ident($repr:ty) { $($cname:ident = $val:expr),* $(,)? }) => {
|
||||
$vis mod $mname {
|
||||
$(pub const $cname: $repr = $val;)*
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
constmod!(pub opcode(u8) {
|
||||
NOP = 0, // _
|
||||
ADD = 1, // RRR
|
||||
SUB = 2, // RRR
|
||||
MUL = 3, // RRR
|
||||
DIV = 4, // RRR
|
||||
REM = 5, // RRR
|
||||
AND = 6, // RRR
|
||||
OR = 7, // RRR
|
||||
XOR = 8, // RRR
|
||||
NOT = 9, // RR
|
||||
|
||||
// TODO: Add instruction for integer and float
|
||||
// reg ← reg + imm instructions
|
||||
|
||||
ADDF = 10, // RRR
|
||||
SUBF = 11, // RRR
|
||||
MULF = 12, // RRR
|
||||
DIVF = 13, // RRR
|
||||
|
||||
LI = 14, // RI
|
||||
LD = 15, // RI
|
||||
ST = 16, // RI
|
||||
|
||||
MAPPAGE = 17, // ?
|
||||
UNMAPPAGE = 18, // ?
|
||||
|
||||
JMP = 100, // I
|
||||
JMPCOND = 101, // I
|
||||
RET = 103, // _
|
||||
ECALL = 255, // _
|
||||
});
|
||||
|
||||
#[repr(packed)] pub struct ParamRRR(pub u8, pub u8, pub u8);
|
||||
#[repr(packed)] pub struct ParamRR(pub u8, pub u8);
|
||||
|
||||
#[repr(packed)] pub struct ParamRI(pub u8, pub u64);
|
||||
|
||||
/// # Safety
|
||||
/// TODO.
|
||||
pub unsafe trait OpParam {}
|
||||
unsafe impl OpParam for ParamRRR {}
|
||||
unsafe impl OpParam for ParamRR {}
|
||||
unsafe impl OpParam for ParamRI {}
|
||||
unsafe impl OpParam for u64 {}
|
||||
unsafe impl OpParam for () {}
|
|
@ -1,39 +0,0 @@
|
|||
mod validate;
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
#[repr(u8)]
|
||||
pub enum Instruction {
|
||||
Nop = 0,
|
||||
Add(Register, Register, Register),
|
||||
Sub(Register, Register, Register) = 2,
|
||||
Mul(Register, Register, Register) = 3,
|
||||
Div(Register, Register, Register) = 4,
|
||||
Mod(Register, Register, Register) = 5,
|
||||
|
||||
And(Register, Register, Register) = 6,
|
||||
Or(Register, Register, Register) = 7,
|
||||
Xor(Register, Register, Register) = 8,
|
||||
Not(Register, Register) = 9,
|
||||
|
||||
Li(Register, u64) = 10,
|
||||
Ld(Register, u64) = 15,
|
||||
St(Register, u64) = 16,
|
||||
|
||||
MapPage = 17,
|
||||
UnmapPage = 18,
|
||||
|
||||
Jmp = 100,
|
||||
JumpCond = 101,
|
||||
Ret = 103,
|
||||
Ecall = 255,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
#[repr(u8)]
|
||||
#[rustfmt::skip]
|
||||
pub enum Register {
|
||||
X0, X1, X2, X3, X4, X5, X6, X7, X8, X9, X10, X11, X12, X13, X14, X15, X16, X17, X18, X19, X20,
|
||||
X21, X22, X23, X24, X25, X26, X27, X28, X29, X30, X31, X32, X33, X34, X35, X36, X37, X38, X39,
|
||||
X40, X41, X42, X43, X44, X45, X46, X47, X48, X49, X50, X51, X52, X53, X54, X55, X56, X57, X58,
|
||||
X59,
|
||||
}
|
|
@ -1,6 +0,0 @@
|
|||
use alloc::vec::Vec;
|
||||
|
||||
pub type CallStack = Vec<FnCall>;
|
||||
pub struct FnCall {
|
||||
pub ret: usize,
|
||||
}
|
|
@ -1,13 +0,0 @@
|
|||
pub struct EngineConfig {
|
||||
pub call_stack_depth: usize,
|
||||
pub quantum: u32,
|
||||
}
|
||||
|
||||
impl EngineConfig {
|
||||
pub fn default() -> Self {
|
||||
Self {
|
||||
call_stack_depth: 32,
|
||||
quantum: 0,
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,3 +0,0 @@
|
|||
use super::Engine;
|
||||
|
||||
pub type EnviromentCall = fn(&mut Engine) -> Result<&mut Engine, u64>;
|
|
@ -1,102 +0,0 @@
|
|||
use log::info;
|
||||
|
||||
pub mod call_stack;
|
||||
pub mod config;
|
||||
pub mod enviroment_calls;
|
||||
pub mod regs;
|
||||
#[cfg(test)]
|
||||
pub mod tests;
|
||||
|
||||
use {
|
||||
self::call_stack::CallStack,
|
||||
crate::{engine::enviroment_calls::EnviromentCall, memory, HaltStatus, RuntimeErrors},
|
||||
alloc::vec::Vec,
|
||||
config::EngineConfig,
|
||||
log::trace,
|
||||
regs::Registers,
|
||||
};
|
||||
|
||||
// pub const PAGE_SIZE: usize = 8192;
|
||||
|
||||
pub struct RealPage {
|
||||
pub ptr: *mut u8,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct VMPage {
|
||||
pub data: [u8; 8192],
|
||||
}
|
||||
impl Default for VMPage {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
data: [0; 4096 * 2],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub enum Page {
|
||||
VMPage(VMPage),
|
||||
RealPage(RealPage),
|
||||
}
|
||||
impl Page {
|
||||
pub fn data(&self) -> [u8; 4096 * 2] {
|
||||
match self {
|
||||
Page::VMPage(vmpage) => vmpage.data,
|
||||
Page::RealPage(_) => {
|
||||
unimplemented!("Memmapped hw page not yet supported")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn empty_enviroment_call(engine: &mut Engine) -> Result<&mut Engine, u64> {
|
||||
trace!("Registers {:?}", engine.registers);
|
||||
Err(0)
|
||||
}
|
||||
|
||||
pub struct Engine {
|
||||
pub pc: usize,
|
||||
pub program: Vec<u8>,
|
||||
pub registers: Registers,
|
||||
pub config: EngineConfig,
|
||||
|
||||
/// BUG: This DOES NOT account for overflowing
|
||||
pub last_timer_count: u32,
|
||||
pub timer_callback: Option<fn() -> u32>,
|
||||
pub memory: memory::Memory,
|
||||
pub enviroment_call_table: [Option<EnviromentCall>; 256],
|
||||
pub call_stack: CallStack,
|
||||
}
|
||||
|
||||
impl Engine {
|
||||
pub fn set_timer_callback(&mut self, func: fn() -> u32) {
|
||||
self.timer_callback = Some(func);
|
||||
}
|
||||
}
|
||||
|
||||
impl Engine {
|
||||
pub fn new(program: Vec<u8>) -> Self {
|
||||
let mut mem = memory::Memory::new();
|
||||
for (addr, byte) in program.clone().into_iter().enumerate() {
|
||||
let _ = mem.set_addr8(addr as u64, byte);
|
||||
}
|
||||
trace!("{:?}", mem.read_addr8(0));
|
||||
let ecall_table: [Option<EnviromentCall>; 256] = [None; 256];
|
||||
Self {
|
||||
pc: 0,
|
||||
program,
|
||||
registers: Registers::default(),
|
||||
config: EngineConfig::default(),
|
||||
last_timer_count: 0,
|
||||
timer_callback: None,
|
||||
enviroment_call_table: ecall_table,
|
||||
memory: mem,
|
||||
call_stack: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn dump(&self) {}
|
||||
pub fn run(&mut self) -> Result<HaltStatus, RuntimeErrors> {
|
||||
Ok(HaltStatus::Running)
|
||||
}
|
||||
}
|
|
@ -1,125 +0,0 @@
|
|||
use {
|
||||
super::Engine,
|
||||
crate::{HaltStatus, RuntimeErrors},
|
||||
alloc::vec,
|
||||
RuntimeErrors::*,
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn invalid_program() {
|
||||
let prog = vec![1, 0];
|
||||
let mut eng = Engine::new(prog);
|
||||
let ret = eng.run();
|
||||
assert_eq!(ret, Err(InvalidOpcodePair(1, 0)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn empty_program() {
|
||||
let prog = vec![];
|
||||
let mut eng = Engine::new(prog);
|
||||
let ret = eng.run();
|
||||
assert_eq!(ret, Ok(HaltStatus::Halted));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn max_quantum_reached() {
|
||||
let prog = vec![0, 0, 0, 0];
|
||||
let mut eng = Engine::new(prog);
|
||||
eng.set_timer_callback(|| {
|
||||
return 1;
|
||||
});
|
||||
eng.config.quantum = 1;
|
||||
let ret = eng.run();
|
||||
assert_eq!(ret, Ok(HaltStatus::Running));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn jump_out_of_bounds() {
|
||||
use crate::bytecode::ops::Operations::JUMP;
|
||||
let prog = vec![JUMP as u8, 0, 0, 0, 0, 0, 0, 1, 0];
|
||||
let mut eng = Engine::new(prog);
|
||||
let ret = eng.run();
|
||||
assert_eq!(ret, Err(InvalidJumpAddress(256)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn invalid_system_call() {
|
||||
let prog = vec![255, 0];
|
||||
let mut eng = Engine::new(prog);
|
||||
let ret = eng.run();
|
||||
assert_eq!(ret, Err(InvalidSystemCall(0)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn add_u8() {
|
||||
use crate::bytecode::ops::{MathOpSides::ConstantConstant, Operations::ADD};
|
||||
|
||||
let prog = vec![ADD as u8, ConstantConstant as u8, 100, 98, 0xA0];
|
||||
let mut eng = Engine::new(prog);
|
||||
let _ = eng.run();
|
||||
assert_eq!(eng.registers.a0, 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sub_u8() {
|
||||
use crate::bytecode::ops::Operations::SUB;
|
||||
|
||||
let prog = vec![SUB as u8];
|
||||
let mut eng = Engine::new(prog);
|
||||
let _ = eng.run();
|
||||
assert_eq!(eng.registers.a0, 1);
|
||||
}
|
||||
#[test]
|
||||
fn mul_u8() {
|
||||
use crate::bytecode::ops::{MathOpSides::ConstantConstant, Operations::MUL};
|
||||
|
||||
let prog = vec![MUL as u8, ConstantConstant as u8, 1, 2, 0xA0];
|
||||
let mut eng = Engine::new(prog);
|
||||
let _ = eng.run();
|
||||
assert_eq!(eng.registers.a0, 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn div_u8() {
|
||||
use crate::bytecode::ops::Operations::DIV;
|
||||
|
||||
let prog = vec![DIV as u8];
|
||||
let mut eng = Engine::new(prog);
|
||||
let _ = eng.run();
|
||||
assert_eq!(eng.registers.a0, 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn set_register() {
|
||||
let prog = alloc::vec![];
|
||||
let mut eng = Engine::new(prog);
|
||||
eng.set_register(0xA0, 1);
|
||||
assert_eq!(eng.registers.a0, 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn load_u8() {
|
||||
use crate::bytecode::ops::{Operations::LOAD, RWSubTypes::AddrToReg};
|
||||
|
||||
let prog = vec![LOAD as u8, AddrToReg as u8, 0, 0, 0, 0, 0, 0, 1, 0, 0xA0];
|
||||
let mut eng = Engine::new(prog);
|
||||
let ret = eng.memory.set_addr8(256, 1);
|
||||
assert_eq!(ret, Ok(()));
|
||||
let _ = eng.run();
|
||||
assert_eq!(eng.registers.a0, 1);
|
||||
}
|
||||
#[test]
|
||||
fn set_memory_8() {
|
||||
let prog = vec![];
|
||||
let mut eng = Engine::new(prog);
|
||||
let ret = eng.memory.set_addr8(256, 1);
|
||||
assert_eq!(ret, Ok(()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn set_memory_64() {
|
||||
let prog = vec![];
|
||||
let mut eng = Engine::new(prog);
|
||||
let ret = eng.memory.set_addr64(256, 1);
|
||||
assert_eq!(ret, Ok(()));
|
||||
}
|
|
@ -2,8 +2,8 @@
|
|||
extern crate alloc;
|
||||
|
||||
pub mod bytecode;
|
||||
pub mod engine;
|
||||
pub mod memory;
|
||||
pub mod validate;
|
||||
pub mod vm;
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum RuntimeErrors {
|
||||
|
|
|
@ -1,30 +1,17 @@
|
|||
use hbvm::{
|
||||
bytecode::Instruction,
|
||||
engine::Engine,
|
||||
RuntimeErrors, HaltStatus,
|
||||
};
|
||||
use hbvm::{vm::Vm, RuntimeErrors};
|
||||
|
||||
fn main() -> Result<(), RuntimeErrors> {
|
||||
// TODO: Grab program from cmdline
|
||||
#[rustfmt::skip]
|
||||
let prog: Vec<u8> = vec![
|
||||
];
|
||||
|
||||
let mut eng = Engine::new(prog);
|
||||
// eng.set_timer_callback(time);
|
||||
eng.enviroment_call_table[10] = Some(print_fn);
|
||||
while eng.run()? != HaltStatus::Halted {}
|
||||
eng.dump();
|
||||
println!("{:#?}", eng.registers);
|
||||
let prog = &[];
|
||||
|
||||
unsafe {
|
||||
let mut vm = Vm::new_unchecked(prog);
|
||||
vm.run();
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn time() -> u32 {
|
||||
9
|
||||
}
|
||||
|
||||
pub fn print_fn(engine: &mut Engine) -> Result<&mut Engine, u64> {
|
||||
println!("hello");
|
||||
Ok(engine)
|
||||
}
|
||||
|
|
|
@ -1,70 +0,0 @@
|
|||
use crate::engine::VMPage;
|
||||
|
||||
use {
|
||||
crate::{engine::Page, RuntimeErrors},
|
||||
alloc::vec::Vec,
|
||||
hashbrown::HashMap,
|
||||
log::trace,
|
||||
};
|
||||
|
||||
pub struct Memory {
|
||||
inner: HashMap<u64, Page>,
|
||||
}
|
||||
|
||||
impl Memory {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
inner: HashMap::new(),
|
||||
}
|
||||
//
|
||||
}
|
||||
|
||||
pub fn map_vec(&mut self, address: u64, vec: Vec<u8>) {
|
||||
panic!("Mapping vectors into pages is not supported yet");
|
||||
}
|
||||
}
|
||||
|
||||
impl Memory {
|
||||
pub fn read_addr8(&mut self, address: u64) -> Result<u8, RuntimeErrors> {
|
||||
let (page, offset) = addr_to_page(address);
|
||||
trace!("page {} offset {}", page, offset);
|
||||
match self.inner.get(&page) {
|
||||
Some(page) => {
|
||||
let val = page.data()[offset as usize];
|
||||
trace!("Value {}", val);
|
||||
Ok(val)
|
||||
}
|
||||
None => {
|
||||
trace!("page not mapped");
|
||||
Err(RuntimeErrors::PageNotMapped(page))
|
||||
}
|
||||
}
|
||||
}
|
||||
pub fn read_addr64(&mut self, address: u64) -> u64 {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
pub fn set_addr8(&mut self, address: u64, value: u8) -> Result<(), RuntimeErrors> {
|
||||
let (page, offset) = addr_to_page(address);
|
||||
let ret: Option<(&u64, &mut Page)> = self.inner.get_key_value_mut(&page);
|
||||
match ret {
|
||||
Some((_, page)) => {
|
||||
page.data()[offset as usize] = value;
|
||||
}
|
||||
None => {
|
||||
let mut pg = VMPage::default();
|
||||
pg.data[offset as usize] = value;
|
||||
self.inner.insert(page, Page::VMPage(pg));
|
||||
trace!("Mapped page {}", page);
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
pub fn set_addr64(&mut self, address: u64, value: u64) -> Result<(), RuntimeErrors> {
|
||||
unimplemented!()
|
||||
}
|
||||
}
|
||||
|
||||
fn addr_to_page(addr: u64) -> (u64, u64) {
|
||||
(addr / 8192, addr % 8192)
|
||||
}
|
95
hbvm/src/vm/mod.rs
Normal file
95
hbvm/src/vm/mod.rs
Normal file
|
@ -0,0 +1,95 @@
|
|||
mod value;
|
||||
|
||||
use {
|
||||
crate::bytecode::{OpParam, ParamRI, ParamRR, ParamRRR},
|
||||
core::ops,
|
||||
static_assertions::assert_impl_one,
|
||||
value::Value,
|
||||
};
|
||||
|
||||
macro_rules! param {
|
||||
($self:expr, $ty:ty) => {{
|
||||
assert_impl_one!($ty: OpParam);
|
||||
let data = $self.program.as_ptr()
|
||||
.add($self.pc + 1)
|
||||
.cast::<$ty>()
|
||||
.read();
|
||||
$self.pc += 1 + core::mem::size_of::<$ty>();
|
||||
data
|
||||
}};
|
||||
}
|
||||
|
||||
macro_rules! binary_op {
|
||||
($self:expr, $ty:ident, $handler:expr) => {{
|
||||
let ParamRRR(tg, a0, a1) = param!($self, ParamRRR);
|
||||
*$self.reg_mut(tg) = $handler(Value::$ty($self.reg(a0)), Value::$ty($self.reg(a1))).into();
|
||||
}};
|
||||
}
|
||||
|
||||
pub struct Vm<'a> {
|
||||
pub registers: [Value; 60],
|
||||
pc: usize,
|
||||
program: &'a [u8],
|
||||
}
|
||||
|
||||
impl<'a> Vm<'a> {
|
||||
/// # Safety
|
||||
/// Program code has to be validated
|
||||
pub unsafe fn new_unchecked(program: &'a [u8]) -> Self {
|
||||
Self {
|
||||
registers: [Value::from(0); 60],
|
||||
pc: 0,
|
||||
program,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn run(&mut self) {
|
||||
use crate::bytecode::opcode::*;
|
||||
loop {
|
||||
let Some(&opcode) = self.program.get(self.pc)
|
||||
else { return };
|
||||
|
||||
unsafe {
|
||||
match opcode {
|
||||
NOP => param!(self, ()),
|
||||
ADD => binary_op!(self, int, u64::wrapping_add),
|
||||
SUB => binary_op!(self, int, u64::wrapping_sub),
|
||||
MUL => binary_op!(self, int, u64::wrapping_mul),
|
||||
DIV => binary_op!(self, int, u64::wrapping_div),
|
||||
REM => binary_op!(self, int, u64::wrapping_rem),
|
||||
AND => binary_op!(self, int, ops::BitAnd::bitand),
|
||||
OR => binary_op!(self, int, ops::BitOr::bitor),
|
||||
XOR => binary_op!(self, int, ops::BitXor::bitxor),
|
||||
NOT => {
|
||||
let param = param!(self, ParamRR);
|
||||
*self.reg_mut(param.0) = (!self.reg(param.1).int()).into();
|
||||
}
|
||||
ADDF => binary_op!(self, float, ops::Add::add),
|
||||
SUBF => binary_op!(self, float, ops::Sub::sub),
|
||||
MULF => binary_op!(self, float, ops::Mul::mul),
|
||||
DIVF => binary_op!(self, float, ops::Div::div),
|
||||
LI => {
|
||||
let param = param!(self, ParamRI);
|
||||
*self.reg_mut(param.0) = param.1.into();
|
||||
}
|
||||
// TODO: LD, ST
|
||||
JMP => {
|
||||
self.pc =
|
||||
self.program.as_ptr().add(self.pc + 1).cast::<u64>().read() as usize;
|
||||
}
|
||||
_ => core::hint::unreachable_unchecked(),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
unsafe fn reg(&self, n: u8) -> &Value {
|
||||
self.registers.get_unchecked(n as usize)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
unsafe fn reg_mut(&mut self, n: u8) -> &mut Value {
|
||||
self.registers.get_unchecked_mut(n as usize)
|
||||
}
|
||||
}
|
|
@ -3,31 +3,6 @@ use core::{
|
|||
ops::{Index, IndexMut},
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct Registers([Value; 60]);
|
||||
|
||||
impl Index<u8> for Registers {
|
||||
type Output = Value;
|
||||
|
||||
#[inline]
|
||||
fn index(&self, index: u8) -> &Self::Output {
|
||||
&self.0[index as usize]
|
||||
}
|
||||
}
|
||||
|
||||
impl IndexMut<u8> for Registers {
|
||||
#[inline]
|
||||
fn index_mut(&mut self, index: u8) -> &mut Self::Output {
|
||||
&mut self.0[index as usize]
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Registers {
|
||||
fn default() -> Self {
|
||||
Self([Value { i: 0 }; 60])
|
||||
}
|
||||
}
|
||||
|
||||
/// # Safety
|
||||
/// The macro invoker shall make sure that byte reinterpret-cast
|
||||
/// won't cause undefined behaviour.
|
Loading…
Reference in a new issue