Valider is now generated from macro (not done yet)
This commit is contained in:
parent
58310eb858
commit
74f98f610c
|
@ -19,25 +19,7 @@ pub struct Assembler {
|
|||
|
||||
|
||||
// Implement both assembler and generate module for text-code-based one
|
||||
macros::impl_both!(
|
||||
bbbb(p0: R, p1: R, p2: R, p3: R)
|
||||
=> [DIR, DIRF, FMAF],
|
||||
bbb(p0: R, p1: R, p2: R)
|
||||
=> [ADD, SUB, MUL, AND, OR, XOR, SL, SR, SRS, CMP, CMPU, /*BRC,*/ ADDF, SUBF, MULF],
|
||||
bbdh(p0: R, p1: R, p2: I, p3: L)
|
||||
=> [LD, ST],
|
||||
bbd(p0: R, p1: R, p2: I)
|
||||
=> [ADDI, MULI, ANDI, ORI, XORI, CMPI, CMPUI, BMC, JAL, JEQ, JNE, JLT, JGT, JLTU,
|
||||
JGTU, ADDFI, MULFI],
|
||||
bbw(p0: R, p1: R, p2: u32)
|
||||
=> [SLI, SRI, SRSI],
|
||||
bb(p0: R, p1: R)
|
||||
=> [NEG, NOT, CP, SWA, NEGF, ITF, FTI],
|
||||
bd(p0: R, p1: I)
|
||||
=> [LI],
|
||||
n()
|
||||
=> [UN, NOP, ECALL],
|
||||
);
|
||||
hbbytecode::invoke_with_def!(macros::impl_all);
|
||||
|
||||
impl Assembler {
|
||||
// Special-cased for text-assembler
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
//! And here the land of macros begin.
|
||||
//!
|
||||
//!
|
||||
//! They do not bite, really. Have you seen what Yandros is writing?
|
||||
|
||||
pub mod asm;
|
||||
|
@ -8,16 +8,16 @@ pub mod text;
|
|||
#[allow(rustdoc::invalid_rust_codeblocks)]
|
||||
/// Generate code for both programmatic-interface assembler and
|
||||
/// textural interface.
|
||||
///
|
||||
///
|
||||
/// Some people claim:
|
||||
/// > Write programs to handle text streams, because that is a universal interface.
|
||||
///
|
||||
///
|
||||
/// We at AbleCorp believe that nice programatic API is nicer than piping some text
|
||||
/// into a program. It's less error-prone and faster.
|
||||
///
|
||||
///
|
||||
/// # Syntax
|
||||
/// ```no_run
|
||||
/// impl_both!(
|
||||
/// impl_all!(
|
||||
/// INSTRUCTION_TYPE(p0: TYPE, p1: TYPE, …)
|
||||
/// => [INSTRUCTION_A, INSTRUCTION_B, …],
|
||||
/// …
|
||||
|
@ -30,7 +30,7 @@ pub mod text;
|
|||
/// - R: Register (u8)
|
||||
/// - I: Immediate (implements [`crate::Imm`] trait)
|
||||
/// - Other types are identity-mapped
|
||||
///
|
||||
///
|
||||
/// # Text assembler
|
||||
/// Text assembler generated simply calls methods in the [`crate::Assembler`] type.
|
||||
/// # Syntax
|
||||
|
@ -45,7 +45,7 @@ pub mod text;
|
|||
/// - Labels are defined by their names followed by colon `label:`
|
||||
/// - Labels are referenced simply by their names
|
||||
/// - Immediates are numbers, can be negative, floats are not yet supported
|
||||
macro_rules! impl_both {
|
||||
macro_rules! impl_all {
|
||||
($($tt:tt)*) => {
|
||||
impl Assembler {
|
||||
$crate::macros::asm::impl_asm!($($tt)*);
|
||||
|
@ -55,4 +55,4 @@ macro_rules! impl_both {
|
|||
};
|
||||
}
|
||||
|
||||
pub(crate) use impl_both;
|
||||
pub(crate) use impl_all;
|
||||
|
|
158
hbbytecode/src/gen_valider.rs
Normal file
158
hbbytecode/src/gen_valider.rs
Normal file
|
@ -0,0 +1,158 @@
|
|||
//! Generate HoleyBytes code validator
|
||||
|
||||
macro_rules! gen_valider {
|
||||
(
|
||||
$(
|
||||
$ityn:ident
|
||||
($($param_i:ident: $param_ty:ident),* $(,)?)
|
||||
=> [$($opcode:ident),* $(,)?],
|
||||
)*
|
||||
) => {
|
||||
#[allow(unreachable_code)]
|
||||
pub mod valider {
|
||||
//! Validate if program is sound to execute
|
||||
|
||||
/// Program validation error kind
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub enum ErrorKind {
|
||||
/// Unknown opcode
|
||||
InvalidInstruction,
|
||||
/// VM doesn't implement this valid opcode
|
||||
Unimplemented,
|
||||
/// Attempted to copy over register boundary
|
||||
RegisterArrayOverflow,
|
||||
/// Program is not validly terminated
|
||||
InvalidEnd,
|
||||
}
|
||||
|
||||
/// Error
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub struct Error {
|
||||
/// Kind
|
||||
pub kind: ErrorKind,
|
||||
/// Location in bytecode
|
||||
pub index: usize,
|
||||
}
|
||||
|
||||
/// Perform bytecode validation. If it passes, the program should be
|
||||
/// sound to execute.
|
||||
pub fn validate(mut program: &[u8]) -> Result<(), Error> {
|
||||
// Program has to end with 12 zeroes, if there is less than
|
||||
// 12 bytes, program is invalid.
|
||||
if program.len() < 12 {
|
||||
return Err(Error {
|
||||
kind: ErrorKind::InvalidEnd,
|
||||
index: 0,
|
||||
});
|
||||
}
|
||||
|
||||
// Verify that program ends with 12 zeroes
|
||||
for (index, item) in program.iter().enumerate().skip(program.len() - 12) {
|
||||
if *item != 0 {
|
||||
return Err(Error {
|
||||
kind: ErrorKind::InvalidEnd,
|
||||
index,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
let start = program;
|
||||
loop {
|
||||
use crate::opcode::*;
|
||||
program = match program {
|
||||
// End of program
|
||||
[] => return Ok(()),
|
||||
|
||||
// Memory load/store cannot go out-of-bounds register array
|
||||
[LD..=ST, reg, _, _, _, _, _, _, _, _, count_0, count_1, ..]
|
||||
if usize::from(*reg) * 8
|
||||
+ usize::from(u16::from_le_bytes([*count_1, *count_0]))
|
||||
> 2048 =>
|
||||
{
|
||||
return Err(Error {
|
||||
kind: ErrorKind::RegisterArrayOverflow,
|
||||
index: (program.as_ptr() as usize) - (start.as_ptr() as usize),
|
||||
});
|
||||
}
|
||||
|
||||
// Block register copy cannot go out-of-bounds register array
|
||||
[BRC, src, dst, count, ..]
|
||||
if src.checked_add(*count).is_none()
|
||||
|| dst.checked_add(*count).is_none() =>
|
||||
{
|
||||
return Err(Error {
|
||||
kind: ErrorKind::RegisterArrayOverflow,
|
||||
index: (program.as_ptr() as usize) - (start.as_ptr() as usize),
|
||||
});
|
||||
}
|
||||
|
||||
$(
|
||||
$crate::gen_valider::inst_chk!(
|
||||
rest, $ityn, $($opcode),*
|
||||
)
|
||||
)|* => rest,
|
||||
|
||||
// The plebs
|
||||
_ => {
|
||||
return Err(Error {
|
||||
kind: ErrorKind::InvalidInstruction,
|
||||
index: (program.as_ptr() as usize) - (start.as_ptr() as usize),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// Generate instruction check pattern
|
||||
macro_rules! inst_chk {
|
||||
// Sadly this has hardcoded instruction types,
|
||||
// as I cannot generate parts of patterns+
|
||||
|
||||
($rest:ident, bbbb, $($opcode:ident),*) => {
|
||||
// B B B B
|
||||
[$($opcode)|*, _, _, _, _, $rest @ ..]
|
||||
};
|
||||
|
||||
($rest:ident, bbb, $($opcode:ident),*) => {
|
||||
// B B B
|
||||
[$($opcode)|*, _, _, _, $rest @ ..]
|
||||
};
|
||||
|
||||
($rest:ident, bbdh, $($opcode:ident),*) => {
|
||||
// B B D1 D2 D3 D4 D5 D6 D7 D8 H1 H2
|
||||
[$($opcode)|*, _, _, _, _, _, _, _, _, _, _, _, _, $rest @ ..]
|
||||
};
|
||||
|
||||
($rest:ident, bbd, $($opcode:ident),*) => {
|
||||
// B B D1 D2 D3 D4 D5 D6 D7 D8
|
||||
[$($opcode)|*, _, _, _, _, _, _, _, _, _, _, $rest @ ..]
|
||||
};
|
||||
|
||||
($rest:ident, bbw, $($opcode:ident),*) => {
|
||||
// B B W1 W2 W3 W4
|
||||
[$($opcode)|*, _, _, _, _, _, _, $rest @ ..]
|
||||
};
|
||||
|
||||
($rest:ident, bb, $($opcode:ident),*) => {
|
||||
// B B
|
||||
[$($opcode)|*, _, _, $rest @ ..]
|
||||
};
|
||||
|
||||
($rest:ident, bd, $($opcode:ident),*) => {
|
||||
// B D1 D2 D3 D4 D5 D6 D7 D8
|
||||
[$($opcode)|*, _, _, _, _, _, _, _, _, _, $rest @ ..]
|
||||
};
|
||||
|
||||
($rest:ident, n, $($opcode:ident),*) => {
|
||||
[$($opcode)|*, $rest @ ..]
|
||||
};
|
||||
|
||||
($_0:ident, $($_1:ident),*) => {
|
||||
compile_error!("Invalid instruction type");
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) use {gen_valider, inst_chk};
|
|
@ -1,5 +1,7 @@
|
|||
#![no_std]
|
||||
|
||||
mod gen_valider;
|
||||
|
||||
macro_rules! constmod {
|
||||
($vis:vis $mname:ident($repr:ty) {
|
||||
$(#![doc = $mdoc:literal])?
|
||||
|
@ -15,6 +17,34 @@ macro_rules! constmod {
|
|||
};
|
||||
}
|
||||
|
||||
/// Invoke macro with bytecode definition format
|
||||
#[macro_export]
|
||||
macro_rules! invoke_with_def {
|
||||
($macro:path) => {
|
||||
$macro!(
|
||||
bbbb(p0: R, p1: R, p2: R, p3: R)
|
||||
=> [DIR, DIRF, FMAF],
|
||||
bbb(p0: R, p1: R, p2: R)
|
||||
=> [ADD, SUB, MUL, AND, OR, XOR, SL, SR, SRS, CMP, CMPU, /*BRC,*/ ADDF, SUBF, MULF],
|
||||
bbdh(p0: R, p1: R, p2: I, p3: L)
|
||||
=> [LD, ST],
|
||||
bbd(p0: R, p1: R, p2: I)
|
||||
=> [ADDI, MULI, ANDI, ORI, XORI, CMPI, CMPUI, BMC, JAL, JEQ, JNE, JLT, JGT, JLTU,
|
||||
JGTU, ADDFI, MULFI],
|
||||
bbw(p0: R, p1: R, p2: u32)
|
||||
=> [SLI, SRI, SRSI],
|
||||
bb(p0: R, p1: R)
|
||||
=> [NEG, NOT, CP, SWA, NEGF, ITF, FTI],
|
||||
bd(p0: R, p1: I)
|
||||
=> [LI],
|
||||
n()
|
||||
=> [UN, NOP, ECALL],
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
invoke_with_def!(gen_valider::gen_valider);
|
||||
|
||||
constmod!(pub opcode(u8) {
|
||||
//! Opcode constant module
|
||||
|
||||
|
|
|
@ -3,5 +3,4 @@
|
|||
|
||||
extern crate alloc;
|
||||
|
||||
pub mod validate;
|
||||
pub mod vm;
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
use hbvm::vm::mem::{HandlePageFault, Memory, MemoryAccessReason, PageSize};
|
||||
|
||||
use {
|
||||
hbvm::{validate::validate, vm::Vm},
|
||||
hbbytecode::valider::validate,
|
||||
hbvm::vm::Vm,
|
||||
std::io::{stdin, Read},
|
||||
};
|
||||
|
||||
|
|
|
@ -1,98 +0,0 @@
|
|||
//! Validate if program is sound to execute
|
||||
|
||||
/// Program validation error kind
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub enum ErrorKind {
|
||||
/// Unknown opcode
|
||||
InvalidInstruction,
|
||||
/// VM doesn't implement this valid opcode
|
||||
Unimplemented,
|
||||
/// Attempted to copy over register boundary
|
||||
RegisterArrayOverflow,
|
||||
/// Program is not validly terminated
|
||||
InvalidEnd,
|
||||
}
|
||||
|
||||
/// Error
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub struct Error {
|
||||
/// Kind
|
||||
pub kind: ErrorKind,
|
||||
/// Location in bytecode
|
||||
pub index: usize,
|
||||
}
|
||||
|
||||
/// Perform bytecode validation. If it passes, the program should be
|
||||
/// sound to execute.
|
||||
pub fn validate(mut program: &[u8]) -> Result<(), Error> {
|
||||
// Program has to end with 12 zeroes, if there is less than
|
||||
// 12 bytes, program is invalid.
|
||||
if program.len() < 12 {
|
||||
return Err(Error {
|
||||
kind: ErrorKind::InvalidEnd,
|
||||
index: 0,
|
||||
});
|
||||
}
|
||||
|
||||
// Verify that program ends with 12 zeroes
|
||||
for (index, item) in program.iter().enumerate().skip(program.len() - 12) {
|
||||
if *item != 0 {
|
||||
return Err(Error {
|
||||
kind: ErrorKind::InvalidEnd,
|
||||
index,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
let start = program;
|
||||
loop {
|
||||
use hbbytecode::opcode::*;
|
||||
// Match on instruction types and perform necessary checks
|
||||
program = match program {
|
||||
// End of program
|
||||
[] => return Ok(()),
|
||||
|
||||
// Memory load/store cannot go out-of-bounds register array
|
||||
[LD..=ST, reg, _, _, _, _, _, _, _, _, count_0, count_1, ..]
|
||||
if usize::from(*reg) * 8
|
||||
+ usize::from(u16::from_le_bytes([*count_1, *count_0]))
|
||||
> 2048 =>
|
||||
{
|
||||
return Err(Error {
|
||||
kind: ErrorKind::RegisterArrayOverflow,
|
||||
index: (program.as_ptr() as usize) - (start.as_ptr() as usize),
|
||||
})
|
||||
}
|
||||
|
||||
// Block register copy cannot go out-of-bounds register array
|
||||
[BRC, src, dst, count, ..]
|
||||
if src.checked_add(*count).is_none() || dst.checked_add(*count).is_none() =>
|
||||
{
|
||||
return Err(Error {
|
||||
kind: ErrorKind::RegisterArrayOverflow,
|
||||
index: (program.as_ptr() as usize) - (start.as_ptr() as usize),
|
||||
})
|
||||
}
|
||||
|
||||
// Valid instructions
|
||||
[DIR | DIRF | FMAF, _, _, _, _, rest @ ..] // BBBB
|
||||
| [ADD | SUB | MUL | AND | OR | XOR | SL | SR | SRS | CMP | CMPU | BRC | ADDF | SUBF | MULF, _, _, _, rest @ ..]
|
||||
| [LD | ST, _, _, _, _, _, _, _, _, _, _, _, rest @ ..] // BBDH
|
||||
| [
|
||||
ADDI | MULI | ANDI | ORI | XORI | CMPI | CMPUI | BMC | JAL | JEQ | JNE | JLT | JGT | JLTU | JGTU | ADDFI | MULFI, _, _, _, _, _, _, _, _, _, _, rest @ ..] // BBD
|
||||
| [SLI | SRI | SRSI, _, _, _, _, _, _, rest @ ..] // BBW
|
||||
| [NEG | NOT | CP | SWA | NEGF | ITF | FTI, _, _, rest @ ..] // BB
|
||||
| [LI, _, _, _, _, _, _, _, _, _, rest @ ..] // BD
|
||||
| [UN | NOP | ECALL, rest @ ..] // N
|
||||
=> rest,
|
||||
|
||||
// The rest
|
||||
_ => {
|
||||
return Err(Error {
|
||||
kind: ErrorKind::InvalidInstruction,
|
||||
index: (program.as_ptr() as usize) - (start.as_ptr() as usize),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -12,9 +12,10 @@ pub mod value;
|
|||
|
||||
use {
|
||||
self::{mem::HandlePageFault, value::ValueVariant},
|
||||
crate::validate,
|
||||
core::{cmp::Ordering, ops},
|
||||
hbbytecode::{OpParam, ParamBB, ParamBBB, ParamBBBB, ParamBBD, ParamBBDH, ParamBBW, ParamBD},
|
||||
hbbytecode::{
|
||||
valider, OpParam, ParamBB, ParamBBB, ParamBBBB, ParamBBD, ParamBBDH, ParamBBW, ParamBD,
|
||||
},
|
||||
mem::Memory,
|
||||
value::Value,
|
||||
};
|
||||
|
@ -66,8 +67,8 @@ impl<'a, PfHandler: HandlePageFault, const TIMER_QUOTIENT: usize>
|
|||
}
|
||||
|
||||
/// Create a new VM with program and trap handler only if it passes validation
|
||||
pub fn new_validated(program: &'a [u8], traph: PfHandler) -> Result<Self, validate::Error> {
|
||||
validate::validate(program)?;
|
||||
pub fn new_validated(program: &'a [u8], traph: PfHandler) -> Result<Self, valider::Error> {
|
||||
valider::validate(program)?;
|
||||
Ok(unsafe { Self::new_unchecked(program, traph) })
|
||||
}
|
||||
|
||||
|
@ -249,6 +250,7 @@ impl<'a, PfHandler: HandlePageFault, const TIMER_QUOTIENT: usize>
|
|||
BRC => {
|
||||
// Block register copy
|
||||
let ParamBBB(src, dst, count) = self.decode();
|
||||
extern crate std;
|
||||
core::ptr::copy(
|
||||
self.registers.get_unchecked(usize::from(src)),
|
||||
self.registers.get_unchecked_mut(usize::from(dst)),
|
||||
|
|
Loading…
Reference in a new issue