Valider is now generated from macro (not done yet)

This commit is contained in:
Erin 2023-07-25 23:43:06 +02:00
parent 92793dc93b
commit 759514686a
8 changed files with 205 additions and 131 deletions

View file

@ -19,25 +19,7 @@ pub struct Assembler {
// Implement both assembler and generate module for text-code-based one // Implement both assembler and generate module for text-code-based one
macros::impl_both!( hbbytecode::invoke_with_def!(macros::impl_all);
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],
);
impl Assembler { impl Assembler {
// Special-cased for text-assembler // Special-cased for text-assembler

View file

@ -1,5 +1,5 @@
//! And here the land of macros begin. //! And here the land of macros begin.
//! //!
//! They do not bite, really. Have you seen what Yandros is writing? //! They do not bite, really. Have you seen what Yandros is writing?
pub mod asm; pub mod asm;
@ -8,16 +8,16 @@ pub mod text;
#[allow(rustdoc::invalid_rust_codeblocks)] #[allow(rustdoc::invalid_rust_codeblocks)]
/// Generate code for both programmatic-interface assembler and /// Generate code for both programmatic-interface assembler and
/// textural interface. /// textural interface.
/// ///
/// Some people claim: /// Some people claim:
/// > Write programs to handle text streams, because that is a universal interface. /// > 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 /// We at AbleCorp believe that nice programatic API is nicer than piping some text
/// into a program. It's less error-prone and faster. /// into a program. It's less error-prone and faster.
/// ///
/// # Syntax /// # Syntax
/// ```no_run /// ```no_run
/// impl_both!( /// impl_all!(
/// INSTRUCTION_TYPE(p0: TYPE, p1: TYPE, …) /// INSTRUCTION_TYPE(p0: TYPE, p1: TYPE, …)
/// => [INSTRUCTION_A, INSTRUCTION_B, …], /// => [INSTRUCTION_A, INSTRUCTION_B, …],
/// … /// …
@ -30,7 +30,7 @@ pub mod text;
/// - R: Register (u8) /// - R: Register (u8)
/// - I: Immediate (implements [`crate::Imm`] trait) /// - I: Immediate (implements [`crate::Imm`] trait)
/// - Other types are identity-mapped /// - Other types are identity-mapped
/// ///
/// # Text assembler /// # Text assembler
/// Text assembler generated simply calls methods in the [`crate::Assembler`] type. /// Text assembler generated simply calls methods in the [`crate::Assembler`] type.
/// # Syntax /// # Syntax
@ -45,7 +45,7 @@ pub mod text;
/// - Labels are defined by their names followed by colon `label:` /// - Labels are defined by their names followed by colon `label:`
/// - Labels are referenced simply by their names /// - Labels are referenced simply by their names
/// - Immediates are numbers, can be negative, floats are not yet supported /// - Immediates are numbers, can be negative, floats are not yet supported
macro_rules! impl_both { macro_rules! impl_all {
($($tt:tt)*) => { ($($tt:tt)*) => {
impl Assembler { impl Assembler {
$crate::macros::asm::impl_asm!($($tt)*); $crate::macros::asm::impl_asm!($($tt)*);
@ -55,4 +55,4 @@ macro_rules! impl_both {
}; };
} }
pub(crate) use impl_both; pub(crate) use impl_all;

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

View file

@ -1,5 +1,7 @@
#![no_std] #![no_std]
mod gen_valider;
macro_rules! constmod { macro_rules! constmod {
($vis:vis $mname:ident($repr:ty) { ($vis:vis $mname:ident($repr:ty) {
$(#![doc = $mdoc:literal])? $(#![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) { constmod!(pub opcode(u8) {
//! Opcode constant module //! Opcode constant module

View file

@ -3,5 +3,4 @@
extern crate alloc; extern crate alloc;
pub mod validate;
pub mod vm; pub mod vm;

View file

@ -1,7 +1,8 @@
use hbvm::vm::mem::{HandlePageFault, Memory, MemoryAccessReason, PageSize}; use hbvm::vm::mem::{HandlePageFault, Memory, MemoryAccessReason, PageSize};
use { use {
hbvm::{validate::validate, vm::Vm}, hbbytecode::valider::validate,
hbvm::vm::Vm,
std::io::{stdin, Read}, std::io::{stdin, Read},
}; };

View file

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

View file

@ -12,9 +12,10 @@ pub mod value;
use { use {
self::{mem::HandlePageFault, value::ValueVariant}, self::{mem::HandlePageFault, value::ValueVariant},
crate::validate,
core::{cmp::Ordering, ops}, 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, mem::Memory,
value::Value, 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 /// 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> { pub fn new_validated(program: &'a [u8], traph: PfHandler) -> Result<Self, valider::Error> {
validate::validate(program)?; valider::validate(program)?;
Ok(unsafe { Self::new_unchecked(program, traph) }) Ok(unsafe { Self::new_unchecked(program, traph) })
} }
@ -249,6 +250,7 @@ impl<'a, PfHandler: HandlePageFault, const TIMER_QUOTIENT: usize>
BRC => { BRC => {
// Block register copy // Block register copy
let ParamBBB(src, dst, count) = self.decode(); let ParamBBB(src, dst, count) = self.decode();
extern crate std;
core::ptr::copy( core::ptr::copy(
self.registers.get_unchecked(usize::from(src)), self.registers.get_unchecked(usize::from(src)),
self.registers.get_unchecked_mut(usize::from(dst)), self.registers.get_unchecked_mut(usize::from(dst)),