From 141c5f524f19f8afdbcc9d6cfb0e7e7fd6558d8a Mon Sep 17 00:00:00 2001 From: Erin Date: Thu, 13 Jul 2023 11:05:41 +0200 Subject: [PATCH 001/116] Added UN instruction and fixed UB --- Cargo.lock | 33 ++++++------- hbasm/src/lib.rs | 7 ++- hbasm/src/main.rs | 1 + hbbytecode/src/lib.rs | 103 ++++++++++++++++++++-------------------- hbvm/src/validate.rs | 20 +++++++- hbvm/src/vm/mod.rs | 24 ++++++---- spec.md | 106 +++++++++++++++++++++--------------------- 7 files changed, 163 insertions(+), 131 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0c533694..007c080d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -165,12 +165,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.17" +version = "0.4.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" -dependencies = [ - "cfg-if", -] +checksum = "b06a4cde4c0f271a446782e3eff8de789548ce57dbc8eca9292c27f4a42004b4" [[package]] name = "logos" @@ -192,7 +189,7 @@ dependencies = [ "proc-macro2", "quote", "regex-syntax", - "syn 2.0.18", + "syn 2.0.25", ] [[package]] @@ -206,30 +203,30 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.17.1" +version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3" +checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" [[package]] name = "paste" -version = "1.0.12" +version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f746c4065a8fa3fe23974dd82f15431cc8d40779821001404d10d2e79ca7d79" +checksum = "b4b27ab7be369122c218afc2079489cdcb4b517c0a3fc386ff11e1fedfcc2b35" [[package]] name = "proc-macro2" -version = "1.0.59" +version = "1.0.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6aeca18b86b413c660b781aa319e4e2648a3e6f9eadc9b47e9038e6fe9f3451b" +checksum = "78803b62cbf1f46fde80d7c0e803111524b9877184cfe7c3033659490ac7a7da" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.28" +version = "1.0.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b9ab9c7eadfd8df19006f1cf1a4aed13540ed5cbc047010ece5826e10825488" +checksum = "573015e8ab27661678357f27dc26460738fd2b6c86e46f386fde94cb5d913105" dependencies = [ "proc-macro2", ] @@ -274,9 +271,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.18" +version = "2.0.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32d41677bcbe24c20c52e7c70b0d8db04134c5d1066bf98662e2871ad200ea3e" +checksum = "15e3fc8c0c74267e2df136e5e5fb656a464158aa57624053375eb9c8c6e25ae2" dependencies = [ "proc-macro2", "quote", @@ -285,9 +282,9 @@ dependencies = [ [[package]] name = "unicode-ident" -version = "1.0.9" +version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b15811caf2415fb889178633e7724bad2509101cde276048e013b9def5e51fa0" +checksum = "22049a19f4a68748a168c0fc439f9516686aa045927ff767eca0a85101fb6e73" [[package]] name = "unicode-width" diff --git a/hbasm/src/lib.rs b/hbasm/src/lib.rs index 391b2514..a9cecc75 100644 --- a/hbasm/src/lib.rs +++ b/hbasm/src/lib.rs @@ -27,7 +27,7 @@ macros::impl_both!( bd(p0: R, p1: I) => [LI], n() - => [NOP, ECALL], + => [UN, NOP, ECALL], ); impl Assembler { @@ -36,6 +36,11 @@ impl Assembler { pub fn i_brc(&mut self, p0: u8, p1: u8, p2: u8) { self.i_param_bbb(hbbytecode::opcode::BRC, p0, p1, p2) } + + /// Append 12 zeroes (UN) at the end + pub fn finalise(&mut self) { + self.buf.extend([0; 12]); + } } pub trait Imm { diff --git a/hbasm/src/main.rs b/hbasm/src/main.rs index 844f2d6f..8ce4f57e 100644 --- a/hbasm/src/main.rs +++ b/hbasm/src/main.rs @@ -48,6 +48,7 @@ fn main() -> Result<(), Box> { .eprint(("engine_internal", Source::from(&code))) .unwrap(); } else { + assembler.finalise(); std::io::stdout().lock().write_all(&assembler.buf).unwrap(); } diff --git a/hbbytecode/src/lib.rs b/hbbytecode/src/lib.rs index 9b4f60bf..400cab1a 100644 --- a/hbbytecode/src/lib.rs +++ b/hbbytecode/src/lib.rs @@ -18,62 +18,63 @@ macro_rules! constmod { constmod!(pub opcode(u8) { //! Opcode constant module - NOP = 0, "N; Do nothing"; + UN = 0, "N; Raises a trap"; + NOP = 1, "N; Do nothing"; + + ADD = 2, "BBB; #0 ← #1 + #2"; + SUB = 3, "BBB; #0 ← #1 - #2"; + MUL = 4, "BBB; #0 ← #1 × #2"; + AND = 5, "BBB; #0 ← #1 & #2"; + OR = 6, "BBB; #0 ← #1 | #2"; + XOR = 7, "BBB; #0 ← #1 ^ #2"; + SL = 8, "BBB; #0 ← #1 « #2"; + SR = 9, "BBB; #0 ← #1 » #2"; + SRS = 10, "BBB; #0 ← #1 » #2 (signed)"; + CMP = 11, "BBB; #0 ← #1 <=> #2"; + CMPU = 12, "BBB; #0 ← #1 <=> #2 (unsigned)"; + DIR = 13, "BBBB; #0 ← #2 / #3, #1 ← #2 % #3"; + NEG = 14, "BB; #0 ← -#1"; + NOT = 15, "BB; #0 ← !#1"; - ADD = 1, "BBB; #0 ← #1 + #2"; - SUB = 2, "BBB; #0 ← #1 - #2"; - MUL = 3, "BBB; #0 ← #1 × #2"; - AND = 4, "BBB; #0 ← #1 & #2"; - OR = 5, "BBB; #0 ← #1 | #2"; - XOR = 6, "BBB; #0 ← #1 ^ #2"; - SL = 7, "BBB; #0 ← #1 « #2"; - SR = 8, "BBB; #0 ← #1 » #2"; - SRS = 9, "BBB; #0 ← #1 » #2 (signed)"; - CMP = 10, "BBB; #0 ← #1 <=> #2"; - CMPU = 11, "BBB; #0 ← #1 <=> #2 (unsigned)"; - DIR = 12, "BBBB; #0 ← #2 / #3, #1 ← #2 % #3"; - NEG = 13, "BB; #0 ← -#1"; - NOT = 14, "BB; #0 ← !#1"; + ADDI = 16, "BBD; #0 ← #1 + imm #2"; + MULI = 17, "BBD; #0 ← #1 × imm #2"; + ANDI = 18, "BBD; #0 ← #1 & imm #2"; + ORI = 19, "BBD; #0 ← #1 | imm #2"; + XORI = 20, "BBD; #0 ← #1 ^ imm #2"; + SLI = 21, "BBD; #0 ← #1 « imm #2"; + SRI = 22, "BBD; #0 ← #1 » imm #2"; + SRSI = 23, "BBD; #0 ← #1 » imm #2 (signed)"; + CMPI = 24, "BBD; #0 ← #1 <=> imm #2"; + CMPUI = 25, "BBD; #0 ← #1 <=> imm #2 (unsigned)"; - ADDI = 15, "BBD; #0 ← #1 + imm #2"; - MULI = 16, "BBD; #0 ← #1 × imm #2"; - ANDI = 17, "BBD; #0 ← #1 & imm #2"; - ORI = 18, "BBD; #0 ← #1 | imm #2"; - XORI = 19, "BBD; #0 ← #1 ^ imm #2"; - SLI = 20, "BBD; #0 ← #1 « imm #2"; - SRI = 21, "BBD; #0 ← #1 » imm #2"; - SRSI = 22, "BBD; #0 ← #1 » imm #2 (signed)"; - CMPI = 23, "BBD; #0 ← #1 <=> imm #2"; - CMPUI = 24, "BBD; #0 ← #1 <=> imm #2 (unsigned)"; + CP = 26, "BB; Copy #0 ← #1"; + SWA = 27, "BB; Swap #0 and #1"; + LI = 28, "BD; #0 ← imm #1"; + LD = 29, "BBDB; #0 ← [#1 + imm #3], imm #4 bytes, overflowing"; + ST = 30, "BBDB; [#1 + imm #3] ← #0, imm #4 bytes, overflowing"; + BMC = 31, "BBD; [#0] ← [#1], imm #2 bytes"; + BRC = 32, "BBB; #0 ← #1, imm #2 registers"; - CP = 25, "BB; Copy #0 ← #1"; - SWA = 26, "BB; Swap #0 and #1"; - LI = 27, "BD; #0 ← imm #1"; - LD = 28, "BBDB; #0 ← [#1 + imm #3], imm #4 bytes, overflowing"; - ST = 29, "BBDB; [#1 + imm #3] ← #0, imm #4 bytes, overflowing"; - BMC = 30, "BBD; [#0] ← [#1], imm #2 bytes"; - BRC = 31, "BBB; #0 ← #1, imm #2 registers"; + JAL = 33, "BD; Copy PC to #0 and unconditional jump [#1 + imm #2]"; + JEQ = 34, "BBD; if #0 = #1 → jump imm #2"; + JNE = 35, "BBD; if #0 ≠ #1 → jump imm #2"; + JLT = 36, "BBD; if #0 < #1 → jump imm #2"; + JGT = 37, "BBD; if #0 > #1 → jump imm #2"; + JLTU = 38, "BBD; if #0 < #1 → jump imm #2 (unsigned)"; + JGTU = 39, "BBD; if #0 > #1 → jump imm #2 (unsigned)"; + ECALL = 40, "N; Issue system call"; - JAL = 32, "BD; Copy PC to #0 and unconditional jump [#1 + imm #2]"; - JEQ = 33, "BBD; if #0 = #1 → jump imm #2"; - JNE = 34, "BBD; if #0 ≠ #1 → jump imm #2"; - JLT = 35, "BBD; if #0 < #1 → jump imm #2"; - JGT = 36, "BBD; if #0 > #1 → jump imm #2"; - JLTU = 37, "BBD; if #0 < #1 → jump imm #2 (unsigned)"; - JGTU = 38, "BBD; if #0 > #1 → jump imm #2 (unsigned)"; - ECALL = 39, "N; Issue system call"; + ADDF = 41, "BBB; #0 ← #1 +. #2"; + SUBF = 42, "BBB; #0 ← #1 -. #2"; + MULF = 43, "BBB; #0 ← #1 +. #2"; + DIRF = 44, "BBBB; #0 ← #2 / #3, #1 ← #2 % #3"; + FMAF = 45, "BBBB; #0 ← (#1 * #2) + #3"; + NEGF = 46, "BB; #0 ← -#1"; + ITF = 47, "BB; #0 ← #1 as float"; + FTI = 48, "BB; #0 ← #1 as int"; - ADDF = 40, "BBB; #0 ← #1 +. #2"; - SUBF = 41, "BBB; #0 ← #1 -. #2"; - MULF = 42, "BBB; #0 ← #1 +. #2"; - DIRF = 43, "BBBB; #0 ← #2 / #3, #1 ← #2 % #3"; - FMAF = 44, "BBBB; #0 ← (#1 * #2) + #3"; - NEGF = 45, "BB; #0 ← -#1"; - ITF = 46, "BB; #0 ← #1 as float"; - FTI = 47, "BB; #0 ← #1 as int"; - - ADDFI = 48, "BBD; #0 ← #1 +. imm #2"; - MULFI = 49, "BBD; #0 ← #1 *. imm #2"; + ADDFI = 49, "BBD; #0 ← #1 +. imm #2"; + MULFI = 50, "BBD; #0 ← #1 *. imm #2"; }); #[repr(packed)] diff --git a/hbvm/src/validate.rs b/hbvm/src/validate.rs index d75306bf..41b16367 100644 --- a/hbvm/src/validate.rs +++ b/hbvm/src/validate.rs @@ -9,6 +9,8 @@ pub enum ErrorKind { Unimplemented, /// Attempted to copy over register boundary RegisterArrayOverflow, + /// Program is not validly terminated + InvalidEnd, } /// Error @@ -25,6 +27,22 @@ pub struct Error { pub fn validate(mut program: &[u8]) -> Result<(), Error> { use hbbytecode::opcode::*; + if program.len() < 12 { + return Err(Error { + kind: ErrorKind::InvalidEnd, + index: 0, + }); + } + + 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 { // Match on instruction types and perform necessary checks @@ -46,7 +64,7 @@ pub fn validate(mut program: &[u8]) -> Result<(), Error> { index: (program.as_ptr() as usize) - (start.as_ptr() as usize), }) } - [NOP | ECALL, rest @ ..] + [UN | NOP | ECALL, rest @ ..] | [DIR | DIRF, _, _, _, _, rest @ ..] | [ADD..=CMPU | BRC | ADDF..=MULF, _, _, _, rest @ ..] | [NEG..=NOT | CP..=SWA | NEGF..=FTI, _, _, rest @ ..] diff --git a/hbvm/src/vm/mod.rs b/hbvm/src/vm/mod.rs index 1fcee80d..bf12d701 100644 --- a/hbvm/src/vm/mod.rs +++ b/hbvm/src/vm/mod.rs @@ -6,10 +6,6 @@ // - Validation has to assure there is 256 registers (r0 - r255) // - Instructions have to be valid as specified (values and sizes) // - Mapped pages should be at least 4 KiB -// - Yes, I am aware of the UB when jumping in-mid of instruction where -// the read byte corresponds to an instruction whose lenght exceets the -// program size. If you are (rightfully) worried about the UB, for now just -// append your program with 11 zeroes. use self::mem::HandlePageFault; @@ -97,6 +93,9 @@ pub struct Vm<'a, PfHandler, const TIMER_QUOTIENT: usize> { /// Program program: &'a [u8], + /// Cached program length (without unreachable end) + program_len: usize, + /// Program timer timer: usize, } @@ -114,6 +113,7 @@ impl<'a, PfHandler: HandlePageFault, const TIMER_QUOTIENT: usize> memory: Default::default(), pfhandler: traph, pc: 0, + program_len: program.len() - 12, program, timer: 0, } @@ -131,13 +131,18 @@ impl<'a, PfHandler: HandlePageFault, const TIMER_QUOTIENT: usize> pub fn run(&mut self) -> Result { use hbbytecode::opcode::*; loop { - // Fetch instruction - let Some(&opcode) = self.program.get(self.pc) - else { return Ok(VmRunOk::End) }; + // Check instruction boundary + if self.pc >= self.program_len { + return Ok(VmRunOk::End); + } // Big match unsafe { - match opcode { + match *self.program.get_unchecked(self.pc) { + UN => { + param!(self, ()); + return Err(VmRunError::Unreachable); + } NOP => param!(self, ()), ADD => binary_op!(self, as_u64, u64::wrapping_add), SUB => binary_op!(self, as_u64, u64::wrapping_sub), @@ -352,6 +357,9 @@ pub enum VmRunError { /// Unhandled store access exception StoreAccessEx(u64), + + /// Reached unreachable code + Unreachable, } /// Virtual machine halt ok diff --git a/spec.md b/spec.md index 55c3aaa4..8fc02019 100644 --- a/spec.md +++ b/spec.md @@ -4,6 +4,7 @@ - All numbers are encoded little-endian - There is 256 registers, they are represented by a byte - Immediate values are 64 bit +- Program is by spec required to be terminated with 12 zero bytes ### Instruction encoding - Instruction parameters are packed (no alignment) @@ -34,9 +35,10 @@ ## No-op - N type -| Opcode | Name | Action | -|:------:|:----:|:----------:| -| 0 | NOP | Do nothing | +| Opcode | Name | Action | +|:------:|:----:|:-----------------------------:| +| 0 | UN | Trigger unreachable code trap | +| 1 | NOP | Do nothing | ## Integer binary ops. - BBB type @@ -44,21 +46,21 @@ | Opcode | Name | Action | |:------:|:----:|:-----------------------:| -| 1 | ADD | Wrapping addition | -| 2 | SUB | Wrapping subtraction | -| 3 | MUL | Wrapping multiplication | -| 4 | AND | Bitand | -| 5 | OR | Bitor | -| 6 | XOR | Bitxor | -| 7 | SL | Unsigned left bitshift | -| 8 | SR | Unsigned right bitshift | -| 9 | SRS | Signed right bitshift | +| 2 | ADD | Wrapping addition | +| 3 | SUB | Wrapping subtraction | +| 4 | MUL | Wrapping multiplication | +| 5 | AND | Bitand | +| 6 | OR | Bitor | +| 7 | XOR | Bitxor | +| 8 | SL | Unsigned left bitshift | +| 9 | SR | Unsigned right bitshift | +| 10 | SRS | Signed right bitshift | ### Comparsion | Opcode | Name | Action | |:------:|:----:|:-------------------:| -| 10 | CMP | Signed comparsion | -| 11 | CMPU | Unsigned comparsion | +| 11 | CMP | Signed comparsion | +| 12 | CMPU | Unsigned comparsion | #### Comparsion table | #1 *op* #2 | Result | @@ -75,7 +77,7 @@ | Opcode | Name | Action | |:------:|:----:|:-------------------------------:| -| 12 | DIR | Divide and remainder combinated | +| 13 | DIR | Divide and remainder combinated | ### Negations - Type BB @@ -83,8 +85,8 @@ | Opcode | Name | Action | |:------:|:----:|:----------------:| -| 13 | NEG | Bit negation | -| 14 | NOT | Logical negation | +| 14 | NEG | Bit negation | +| 15 | NOT | Logical negation | ## Integer immediate binary ops. - Type BBD @@ -92,22 +94,22 @@ | Opcode | Name | Action | |:------:|:----:|:-----------------------:| -| 15 | ADDI | Wrapping addition | -| 16 | MULI | Wrapping subtraction | -| 17 | ANDI | Bitand | -| 18 | ORI | Bitor | -| 19 | XORI | Bitxor | -| 20 | SLI | Unsigned left bitshift | -| 21 | SRI | Unsigned right bitshift | -| 22 | SRSI | Signed right bitshift | +| 16 | ADDI | Wrapping addition | +| 17 | MULI | Wrapping subtraction | +| 18 | ANDI | Bitand | +| 19 | ORI | Bitor | +| 20 | XORI | Bitxor | +| 21 | SLI | Unsigned left bitshift | +| 22 | SRI | Unsigned right bitshift | +| 23 | SRSI | Signed right bitshift | ### Comparsion - Comparsion is the same as when RRR type | Opcode | Name | Action | |:------:|:-----:|:-------------------:| -| 23 | CMPI | Signed comparsion | -| 24 | CMPUI | Unsigned comparsion | +| 24 | CMPI | Signed comparsion | +| 25 | CMPUI | Unsigned comparsion | ## Register value set / copy @@ -117,7 +119,7 @@ | Opcode | Name | Action | |:------:|:----:|:------:| -| 25 | CP | Copy | +| 26 | CP | Copy | ### Swap - Type BB @@ -125,7 +127,7 @@ | Opcode | Name | Action | |:------:|:----:|:------:| -| 26 | SWA | Swap | +| 27 | SWA | Swap | ### Load immediate - Type BD @@ -133,7 +135,7 @@ | Opcode | Name | Action | |:------:|:----:|:--------------:| -| 27 | LI | Load immediate | +| 28 | LI | Load immediate | ## Memory operations - Type BBDH @@ -142,8 +144,8 @@ ### Load / Store | Opcode | Name | Action | |:------:|:----:|:---------------------------------------:| -| 28 | LD | `#0 ← [#1 + imm #3], copy imm #4 bytes` | -| 29 | ST | `[#1 + imm #3] ← #0, copy imm #4 bytes` | +| 29 | LD | `#0 ← [#1 + imm #3], copy imm #4 bytes` | +| 30 | ST | `[#1 + imm #3] ← #0, copy imm #4 bytes` | ## Block copy - Block copy source and target can overlap @@ -153,7 +155,7 @@ | Opcode | Name | Action | |:------:|:----:|:--------------------------------:| -| 30 | BMC | `[#1] ← [#0], copy imm #2 bytes` | +| 31 | BMC | `[#1] ← [#0], copy imm #2 bytes` | ### Register copy - Type BBB @@ -161,7 +163,7 @@ | Opcode | Name | Action | |:------:|:----:|:--------------------------------:| -| 31 | BRC | `#1 ← #0, copy imm #2 registers` | +| 32 | BRC | `#1 ← #0, copy imm #2 registers` | ## Control flow @@ -170,7 +172,7 @@ | Opcode | Name | Action | |:------:|:----:|:-------------------------------------------------:| -| 32 | JAL | Save current PC to `#0` and jump at `#1 + imm #2` | +| 33 | JAL | Save current PC to `#0` and jump at `#1 + imm #2` | ### Conditional jumps - Type BBD @@ -178,19 +180,19 @@ | Opcode | Name | Comparsion | |:------:|:----:|:------------:| -| 33 | JEQ | = | -| 34 | JNE | ≠ | -| 35 | JLT | < (signed) | -| 36 | JGT | > (signed) | -| 37 | JLTU | < (unsigned) | -| 38 | JGTU | > (unsigned) | +| 34 | JEQ | = | +| 35 | JNE | ≠ | +| 36 | JLT | < (signed) | +| 37 | JGT | > (signed) | +| 38 | JLTU | < (unsigned) | +| 39 | JGTU | > (unsigned) | ### Environment call - Type N | Opcode | Name | Action | |:------:|:-----:|:-------------------------------------:| -| 39 | ECALL | Cause an trap to the host environment | +| 40 | ECALL | Cause an trap to the host environment | ## Floating point operations - Type BBB @@ -198,29 +200,29 @@ | Opcode | Name | Action | |:------:|:----:|:--------------:| -| 40 | ADDF | Addition | -| 41 | SUBF | Subtraction | -| 42 | MULF | Multiplication | +| 41 | ADDF | Addition | +| 42 | SUBF | Subtraction | +| 43 | MULF | Multiplication | ### Division-remainder - Type BBBB | Opcode | Name | Action | |:------:|:----:|:-------------------------:| -| 43 | DIRF | Same as for integer `DIR` | +| 44 | DIRF | Same as for integer `DIR` | ### Fused Multiply-Add - Type BBBB | Opcode | Name | Action | |:------:|:----:|:---------------------:| -| 44 | FMAF | `#0 ← (#1 * #2) + #3` | +| 45 | FMAF | `#0 ← (#1 * #2) + #3` | ### Negation - Type BB | Opcode | Name | Action | |:------:|:----:|:----------:| -| 45 | NEGF | `#0 ← -#1` | +| 46 | NEGF | `#0 ← -#1` | ### Conversion - Type BB @@ -229,8 +231,8 @@ | Opcode | Name | Action | |:------:|:----:|:------------:| -| 46 | ITF | Int to Float | -| 47 | FTI | Float to Int | +| 47 | ITF | Int to Float | +| 48 | FTI | Float to Int | ## Floating point immediate operations - Type BBD @@ -238,8 +240,8 @@ | Opcode | Name | Action | |:------:|:-----:|:--------------:| -| 48 | ADDFI | Addition | -| 49 | MULFI | Multiplication | +| 49 | ADDFI | Addition | +| 50 | MULFI | Multiplication | # Registers - There is 255 registers + one zero register (with index 0) From 78333347132672bb7f9ba87ddcdec0306f9fdb07 Mon Sep 17 00:00:00 2001 From: Erin Date: Thu, 13 Jul 2023 11:11:35 +0200 Subject: [PATCH 002/116] Update spec --- spec.md | 1 + 1 file changed, 1 insertion(+) diff --git a/spec.md b/spec.md index 8fc02019..46df17b1 100644 --- a/spec.md +++ b/spec.md @@ -279,6 +279,7 @@ Program should at least implement these traps: - Invalid instruction exception - Load address exception - Store address exception +- Unreachable instruction and executing environment should be able to get information about them, like the opcode of invalid instruction or attempted address to load/store. From 3534b26946fd321b58a3e4cefb443336f97e9611 Mon Sep 17 00:00:00 2001 From: able Date: Thu, 13 Jul 2023 04:23:00 -0500 Subject: [PATCH 003/116] Add some example code for hbasm --- hbasm/assets/inf_loop.hbasm | 2 ++ hbvm/assets/inf_loop.hb | Bin 10 -> 11 bytes 2 files changed, 2 insertions(+) create mode 100644 hbasm/assets/inf_loop.hbasm diff --git a/hbasm/assets/inf_loop.hbasm b/hbasm/assets/inf_loop.hbasm new file mode 100644 index 00000000..dc56a416 --- /dev/null +++ b/hbasm/assets/inf_loop.hbasm @@ -0,0 +1,2 @@ +L: + jal r0, r0, L \ No newline at end of file diff --git a/hbvm/assets/inf_loop.hb b/hbvm/assets/inf_loop.hb index 65168fa85d41f53973238f9cc5d07ae0beba9373..2896284bd9325d7cce67b69b4bc1c574cd53d540 100644 GIT binary patch literal 11 KcmY#jfCB&lYXBht literal 10 LcmZQbVSoYv1C9V? From ac7fdc7688032d5001cba454f9d7ca4a9e786cfd Mon Sep 17 00:00:00 2001 From: able Date: Sat, 15 Jul 2023 06:27:11 -0500 Subject: [PATCH 004/116] code and stufd --- hbasm/assets/ecall.hbasm | 11 +++++++++++ hbvm/assets/ecall.hb | Bin 0 -> 112 bytes hbvm/assets/inf_loop.hb | Bin 11 -> 23 bytes 3 files changed, 11 insertions(+) create mode 100644 hbasm/assets/ecall.hbasm create mode 100644 hbvm/assets/ecall.hb diff --git a/hbasm/assets/ecall.hbasm b/hbasm/assets/ecall.hbasm new file mode 100644 index 00000000..7743b53a --- /dev/null +++ b/hbasm/assets/ecall.hbasm @@ -0,0 +1,11 @@ +addi r1, r0, 1024 +addi r2, r1, 1024 +addi r3, r2, 1024 +addi r4, r3, 1024 +addi r5, r4, 1024 +addi r6, r5, 1024 +addi r7, r6, 1024 +addi r8, r7, 1024 +addi r9, r8, 1024 + +ecall \ No newline at end of file diff --git a/hbvm/assets/ecall.hb b/hbvm/assets/ecall.hb new file mode 100644 index 0000000000000000000000000000000000000000..3ecd89a82ad402cc2f2d05840c3c5ee47f3b3b2e GIT binary patch literal 112 ycmWe&WME)n00RLgMmUq13C?6;hBH}N;7m4FIFp?X&g5W+GdVe6Obv+f5E1}Ep8+la literal 0 HcmV?d00001 diff --git a/hbvm/assets/inf_loop.hb b/hbvm/assets/inf_loop.hb index 2896284bd9325d7cce67b69b4bc1c574cd53d540..0a4fe615153afee88ff9cd9eca80ebf75c40c218 100644 GIT binary patch literal 23 KcmY#nzzP5Z4gewm literal 11 KcmY#jfCB&lYXBht From 6fe1fd91bf78a9e3421e9c9a591e13aed9694bb6 Mon Sep 17 00:00:00 2001 From: Erin Date: Thu, 20 Jul 2023 20:47:50 +0200 Subject: [PATCH 005/116] Mapping + bye bye memory leaks --- hbvm/src/main.rs | 22 ++++++- hbvm/src/vm/mem/mod.rs | 120 +++++++++++++++++++++++++++++++++----- hbvm/src/vm/mem/paging.rs | 84 +++----------------------- 3 files changed, 134 insertions(+), 92 deletions(-) diff --git a/hbvm/src/main.rs b/hbvm/src/main.rs index 79ced044..a680dd83 100644 --- a/hbvm/src/main.rs +++ b/hbvm/src/main.rs @@ -15,9 +15,29 @@ fn main() -> Result<(), Box> { } else { unsafe { let mut vm = Vm::<_, 0>::new_unchecked(&prog, TestTrapHandler); - vm.memory.insert_test_page(); + let data = { + let ptr = std::alloc::alloc_zeroed(std::alloc::Layout::from_size_align_unchecked( + 4096, 4096, + )); + if ptr.is_null() { + panic!("Alloc error tbhl"); + } + ptr + }; + + vm.memory + .map( + data, + 0, + hbvm::vm::mem::paging::Permission::Write, + PageSize::Size4K, + ) + .unwrap(); + println!("Program interrupt: {:?}", vm.run()); println!("{:?}", vm.registers); + std::alloc::dealloc(data, std::alloc::Layout::from_size_align_unchecked(4096, 4096)); + vm.memory.unmap(0).unwrap(); } } Ok(()) diff --git a/hbvm/src/vm/mem/mod.rs b/hbvm/src/vm/mem/mod.rs index 86095153..6b0ece4e 100644 --- a/hbvm/src/vm/mem/mod.rs +++ b/hbvm/src/vm/mem/mod.rs @@ -56,11 +56,11 @@ impl Memory { for _ in 0..4 { let mut pt = Box::::default(); - pt[0] = entry; + pt.table[0] = entry; entry = PtEntry::new(Box::into_raw(pt) as _, Permission::Node); } - (*self.root_pt)[0] = entry; + (*self.root_pt).table[0] = entry; } } @@ -70,17 +70,95 @@ impl Memory { /// Who knows. pub unsafe fn map( &mut self, - mut host: *mut u8, - target: usize, + host: *mut u8, + target: u64, + perm: Permission, pagesize: PageSize, - count: usize, - ) { - todo!() + ) -> Result<(), MapError> { + let mut current_pt = self.root_pt; + + let lookup_depth = match pagesize { + PageSize::Size4K => 4, + PageSize::Size2M => 3, + PageSize::Size1G => 2, + }; + + // Lookup pagetable above + for lvl in (0..lookup_depth).rev() { + let entry = (*current_pt) + .table + .get_unchecked_mut(addr_extract_index(target, lvl)); + + let ptr = entry.ptr(); + match entry.permission() { + Permission::Empty => { + (*current_pt).childen += 1; + let table = Box::into_raw(Box::new(paging::PtPointedData { + pt: PageTable::default(), + })); + + core::ptr::write(entry, PtEntry::new(table, Permission::Node)); + current_pt = table as _; + } + Permission::Node => current_pt = ptr as _, + _ => return Err(MapError::AlreadyMapped), + } + } + + // Write entry + (*current_pt).childen += 1; + core::ptr::write( + (*current_pt) + .table + .get_unchecked_mut(addr_extract_index(target, 4 - lookup_depth)), + PtEntry::new(host.cast(), perm), + ); + + Ok(()) } /// Unmaps pages from VM's memory - pub fn unmap(&mut self, addr: usize, count: usize) { - todo!() + pub fn unmap(&mut self, addr: u64) -> Result<(), NothingToUnmap> { + let mut current_pt = self.root_pt; + let mut page_tables = [core::ptr::null_mut(); 5]; + + for lvl in (0..5).rev() { + let entry = unsafe { + (*current_pt) + .table + .get_unchecked_mut(addr_extract_index(addr, lvl)) + }; + + let ptr = entry.ptr(); + match entry.permission() { + Permission::Empty => return Err(NothingToUnmap), + Permission::Node => { + page_tables[lvl as usize] = entry; + current_pt = ptr as _ + } + _ => unsafe { + core::ptr::write(entry, Default::default()); + }, + } + } + + for entry in page_tables.into_iter() { + if entry.is_null() { + continue; + } + + unsafe { + let children = &mut (*(*entry).ptr()).pt.childen; + *children -= 1; + if *children == 0 { + core::mem::drop(Box::from_raw((*entry).ptr() as *mut PageTable)); + } + + core::ptr::write(entry, Default::default()); + } + } + + Ok(()) } /// Load value from an address @@ -338,10 +416,9 @@ impl Iterator for AddrPageLookuper { for lvl in (0..5).rev() { // Get an entry unsafe { - let entry = (*current_pt).get_unchecked( - usize::try_from((self.addr >> (lvl * 9 + 12)) & ((1 << 9) - 1)) - .expect("?conradluget a better CPU"), - ); + let entry = (*current_pt) + .table + .get_unchecked(addr_extract_index(self.addr, lvl)); let ptr = entry.ptr(); match entry.permission() { @@ -356,7 +433,7 @@ impl Iterator for AddrPageLookuper { // Node → proceed waking Permission::Node => current_pt = ptr as _, - // Leaft → return relevant data + // Leaf → return relevant data perm => { break 'a ( // Pointer in host memory @@ -386,6 +463,11 @@ impl Iterator for AddrPageLookuper { } } +fn addr_extract_index(addr: u64, lvl: u8) -> usize { + debug_assert!(lvl <= 4); + usize::try_from((addr >> (lvl * 9 + 12)) & ((1 << 9) - 1)).expect("?conradluget a better CPU") +} + /// Page size #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum PageSize { @@ -401,7 +483,7 @@ pub enum PageSize { impl PageSize { /// Convert page table level to size of page - fn from_lvl(lvl: u8) -> Option { + const fn from_lvl(lvl: u8) -> Option { match lvl { 0 => Some(PageSize::Size4K), 1 => Some(PageSize::Size2M), @@ -419,6 +501,9 @@ pub struct LoadError(u64); #[derive(Clone, Copy, Display, Debug, PartialEq, Eq)] pub struct StoreError(u64); +#[derive(Clone, Copy, Display, Debug)] +pub struct NothingToUnmap; + #[derive(Clone, Copy, Display, Debug, PartialEq, Eq)] pub enum MemoryAccessReason { Load, @@ -451,3 +536,8 @@ impl From for VmRunError { Self::StoreAccessEx(value.0) } } + +#[derive(Clone, Copy, Display, Debug, PartialEq, Eq)] +pub enum MapError { + AlreadyMapped, +} diff --git a/hbvm/src/vm/mem/paging.rs b/hbvm/src/vm/mem/paging.rs index c621ab4b..05511537 100644 --- a/hbvm/src/vm/mem/paging.rs +++ b/hbvm/src/vm/mem/paging.rs @@ -1,14 +1,6 @@ //! Page table and associated structures implementation -use { - core::{ - fmt::Debug, - mem::MaybeUninit, - ops::{Index, IndexMut}, - slice::SliceIndex, - }, - delegate::delegate, -}; +use core::{fmt::Debug, mem::MaybeUninit}; /// Page entry permission #[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] @@ -66,78 +58,18 @@ impl Debug for PtEntry { /// Page table #[derive(Clone, Copy, Debug, PartialEq, Eq)] #[repr(align(4096))] -pub struct PageTable([PtEntry; 512]); - -impl PageTable { - delegate!(to self.0 { - /// Returns a reference to an element or subslice depending on the type of - /// index. - /// - /// - If given a position, returns a reference to the element at that - /// position or `None` if out of bounds. - /// - If given a range, returns the subslice corresponding to that range, - /// or `None` if out of bounds. - /// - pub fn get(&self, ix: I) -> Option<&I::Output> - where I: SliceIndex<[PtEntry]>; - - /// Returns a mutable reference to an element or subslice depending on the - /// type of index (see [`get`]) or `None` if the index is out of bounds. - pub fn get_mut(&mut self, ix: I) -> Option<&mut I::Output> - where I: SliceIndex<[PtEntry]>; - - /// Returns a reference to an element or subslice, without doing bounds - /// checking. - /// - /// For a safe alternative see [`get`]. - /// - /// # Safety - /// - /// Calling this method with an out-of-bounds index is *[undefined behavior]* - /// even if the resulting reference is not used. - pub unsafe fn get_unchecked(&self, index: I) -> &I::Output - where I: SliceIndex<[PtEntry]>; - - /// Returns a mutable reference to an element or subslice, without doing - /// bounds checking. - /// - /// For a safe alternative see [`get_mut`]. - /// - /// # Safety - /// - /// Calling this method with an out-of-bounds index is *[undefined behavior]* - /// even if the resulting reference is not used. - pub unsafe fn get_unchecked_mut(&mut self, index: I) -> &mut I::Output - where I: SliceIndex<[PtEntry]>; - }); -} - -impl Index for PageTable -where - Idx: SliceIndex<[PtEntry]>, -{ - type Output = Idx::Output; - - #[inline(always)] - fn index(&self, index: Idx) -> &Self::Output { - &self.0[index] - } -} - -impl IndexMut for PageTable -where - Idx: SliceIndex<[PtEntry]>, -{ - #[inline(always)] - fn index_mut(&mut self, index: Idx) -> &mut Self::Output { - &mut self.0[index] - } +pub struct PageTable { + pub childen: u8, + pub table: [PtEntry; 256], } impl Default for PageTable { fn default() -> Self { // SAFETY: It's fine, zeroed page table entry is valid (= empty) - Self(unsafe { MaybeUninit::zeroed().assume_init() }) + Self { + childen: 0, + table: unsafe { MaybeUninit::zeroed().assume_init() }, + } } } From 1a53c80a621b60de5cc3b8ebdf1f9640bcfe2451 Mon Sep 17 00:00:00 2001 From: Erin Date: Sat, 22 Jul 2023 00:46:30 +0200 Subject: [PATCH 006/116] Fixed bug + spec update --- hbvm/src/vm/mod.rs | 38 ++++++++++++++++++++++++++++++++------ spec.md | 7 +++++-- 2 files changed, 37 insertions(+), 8 deletions(-) diff --git a/hbvm/src/vm/mod.rs b/hbvm/src/vm/mod.rs index bf12d701..ff81de4e 100644 --- a/hbvm/src/vm/mod.rs +++ b/hbvm/src/vm/mod.rs @@ -154,6 +154,11 @@ impl<'a, PfHandler: HandlePageFault, const TIMER_QUOTIENT: usize> SR => binary_op!(self, as_u64, ops::Shr::shr), SRS => binary_op!(self, as_i64, ops::Shr::shr), CMP => { + // Compare a0 <=> a1 + // < → -1 + // > → 1 + // = → 0 + let ParamBBB(tg, a0, a1) = param!(self, ParamBBB); self.write_reg( tg, @@ -161,6 +166,7 @@ impl<'a, PfHandler: HandlePageFault, const TIMER_QUOTIENT: usize> ); } CMPU => { + // Unsigned comparsion let ParamBBB(tg, a0, a1) = param!(self, ParamBBB); self.write_reg( tg, @@ -168,10 +174,12 @@ impl<'a, PfHandler: HandlePageFault, const TIMER_QUOTIENT: usize> ); } NOT => { + // Logical negation let param = param!(self, ParamBB); self.write_reg(param.0, !self.read_reg(param.1).as_u64()); } NEG => { + // Bitwise negation let param = param!(self, ParamBB); self.write_reg( param.0, @@ -182,6 +190,7 @@ impl<'a, PfHandler: HandlePageFault, const TIMER_QUOTIENT: usize> ); } DIR => { + // Fused Division-Remainder let ParamBBBB(dt, rt, a0, a1) = param!(self, ParamBBBB); let a0 = self.read_reg(a0).as_u64(); let a1 = self.read_reg(a1).as_u64(); @@ -212,12 +221,17 @@ impl<'a, PfHandler: HandlePageFault, const TIMER_QUOTIENT: usize> self.write_reg(param.0, self.read_reg(param.1)); } SWA => { - let ParamBB(src, dst) = param!(self, ParamBB); - if src + dst != 0 { - core::ptr::swap( - self.registers.get_unchecked_mut(usize::from(src)), - self.registers.get_unchecked_mut(usize::from(dst)), - ); + // Swap registers + let ParamBB(r0, r1) = param!(self, ParamBB); + match (r0, r1) { + (0, 0) => (), + (dst, 0) | (0, dst) => self.write_reg(dst, 0_u64), + (r0, r1) => { + core::ptr::swap( + self.registers.get_unchecked_mut(usize::from(r0)), + self.registers.get_unchecked_mut(usize::from(r1)), + ); + } } } LI => { @@ -225,6 +239,7 @@ impl<'a, PfHandler: HandlePageFault, const TIMER_QUOTIENT: usize> self.write_reg(param.0, param.1); } LD => { + // Load. If loading more than register size, continue on adjecent registers let ParamBBDH(dst, base, off, count) = param!(self, ParamBBDH); let n: usize = match dst { 0 => 1, @@ -239,6 +254,7 @@ impl<'a, PfHandler: HandlePageFault, const TIMER_QUOTIENT: usize> )?; } ST => { + // Store. Same rules apply as to LD let ParamBBDH(dst, base, off, count) = param!(self, ParamBBDH); self.memory.store( self.read_reg(base).as_u64() + off, @@ -248,6 +264,7 @@ impl<'a, PfHandler: HandlePageFault, const TIMER_QUOTIENT: usize> )?; } BMC => { + // Block memory copy let ParamBBD(src, dst, count) = param!(self, ParamBBD); self.memory.block_copy( self.read_reg(src).as_u64(), @@ -257,6 +274,7 @@ impl<'a, PfHandler: HandlePageFault, const TIMER_QUOTIENT: usize> )?; } BRC => { + // Block register copy let ParamBBB(src, dst, count) = param!(self, ParamBBB); core::ptr::copy( self.registers.get_unchecked(usize::from(src)), @@ -265,10 +283,13 @@ impl<'a, PfHandler: HandlePageFault, const TIMER_QUOTIENT: usize> ); } JAL => { + // Jump and link. Save PC after this instruction to + // specified register and jump to reg + offset. let ParamBBD(save, reg, offset) = param!(self, ParamBBD); self.write_reg(save, self.pc as u64); self.pc = (self.read_reg(reg).as_u64() + offset) as usize; } + // Conditional jumps, jump only to immediates JEQ => cond_jump!(self, int, Equal), JNE => { let ParamBBD(a0, a1, jt) = param!(self, ParamBBD); @@ -282,6 +303,11 @@ impl<'a, PfHandler: HandlePageFault, const TIMER_QUOTIENT: usize> JGTU => cond_jump!(self, sint, Greater), ECALL => { param!(self, ()); + + // So we don't get timer interrupt after ECALL + if TIMER_QUOTIENT != 0 { + self.timer = self.timer.wrapping_add(1); + } return Ok(VmRunOk::Ecall); } ADDF => binary_op!(self, as_f64, ops::Add::add), diff --git a/spec.md b/spec.md index 46df17b1..ddf17672 100644 --- a/spec.md +++ b/spec.md @@ -124,6 +124,9 @@ ### Swap - Type BB - Swap #0 and #1 +- Zero register rules: + - Both: no-op + - One: Copy zero to the non-zero register | Opcode | Name | Action | |:------:|:----:|:------:| @@ -263,8 +266,8 @@ # Program execution - The way of program execution is implementation defined -- The order of instruction is arbitrary, as long all observable - effects are applied in the program's order +- The execution is arbitrary, as long all effects are obervable + in the way as program was executed literally, in order. # Program validation - Invalid program should cause runtime error: From ec7053a2898047fd7879d808234eca01bc41cdc1 Mon Sep 17 00:00:00 2001 From: Erin Date: Sat, 22 Jul 2023 01:03:09 +0200 Subject: [PATCH 007/116] Zero alloc BMC! --- hbvm/src/vm/mem/mod.rs | 121 ++++++++++++++++++++--------------------- 1 file changed, 58 insertions(+), 63 deletions(-) diff --git a/hbvm/src/vm/mem/mod.rs b/hbvm/src/vm/mem/mod.rs index 6b0ece4e..ea94238f 100644 --- a/hbvm/src/vm/mem/mod.rs +++ b/hbvm/src/vm/mem/mod.rs @@ -76,7 +76,7 @@ impl Memory { pagesize: PageSize, ) -> Result<(), MapError> { let mut current_pt = self.root_pt; - + let lookup_depth = match pagesize { PageSize::Size4K => 4, PageSize::Size2M => 3, @@ -220,81 +220,76 @@ impl Memory { /// - Addr-san claims it's fine but who knows is she isn't lying :ferrisSus: pub unsafe fn block_copy( &mut self, - src: u64, - dst: u64, + mut src: u64, + mut dst: u64, count: usize, traph: &mut impl HandlePageFault, ) -> Result<(), BlkCopyError> { // Yea, i know it is possible to do this more efficiently, but I am too lazy. - const STACK_BUFFER_SIZE: usize = 512; + impl Memory { + #[inline] + unsafe fn act( + &mut self, + src: u64, + dst: u64, + buf: *mut u8, + count: usize, + traph: &mut impl HandlePageFault, + ) -> Result<(), BlkCopyError> { + self.memory_access( + MemoryAccessReason::Load, + src, + buf, + STACK_BUFFER_SIZE, + |perm| { + matches!( + perm, + Permission::Readonly | Permission::Write | Permission::Exec + ) + }, + |src, dst, count| core::ptr::copy(src, dst, count), + traph, + ) + .map_err(|addr| BlkCopyError { + access_reason: MemoryAccessReason::Load, + addr, + })?; + + self.memory_access( + MemoryAccessReason::Store, + dst, + buf, + count, + |perm| perm == Permission::Write, + |dst, src, count| core::ptr::copy(src, dst, count), + traph, + ) + .map_err(|addr| BlkCopyError { + access_reason: MemoryAccessReason::Store, + addr, + })?; + + Ok(()) + } + } + + const STACK_BUFFER_SIZE: usize = 4096; // Decide if to use stack-allocated buffer or to heap allocate // Deallocation is again decided on size at the end of the function let mut buf = MaybeUninit::<[u8; STACK_BUFFER_SIZE]>::uninit(); - let buf = if count <= STACK_BUFFER_SIZE { - buf.as_mut_ptr().cast() - } else { - unsafe { - let layout = core::alloc::Layout::from_size_align_unchecked(count, 1); - let ptr = alloc::alloc::alloc(layout); - if ptr.is_null() { - alloc::alloc::handle_alloc_error(layout); - } - ptr - } - }; + let n_buffers = count / STACK_BUFFER_SIZE; + let rem = count % STACK_BUFFER_SIZE; - // Perform memory block transfer - let status = (|| { - // Load to buffer - self.memory_access( - MemoryAccessReason::Load, - src, - buf, - count, - |perm| { - matches!( - perm, - Permission::Readonly | Permission::Write | Permission::Exec - ) - }, - |src, dst, count| core::ptr::copy(src, dst, count), - traph, - ) - .map_err(|addr| BlkCopyError { - access_reason: MemoryAccessReason::Load, - addr, - })?; - - // Store from buffer - self.memory_access( - MemoryAccessReason::Store, - dst, - buf, - count, - |perm| perm == Permission::Write, - |dst, src, count| core::ptr::copy(src, dst, count), - traph, - ) - .map_err(|addr| BlkCopyError { - access_reason: MemoryAccessReason::Store, - addr, - })?; - - Ok::<_, BlkCopyError>(()) - })(); - - // Deallocate if used heap-allocated array - if count > STACK_BUFFER_SIZE { - alloc::alloc::dealloc( - buf, - core::alloc::Layout::from_size_align_unchecked(count, 1), - ); + for _ in 0..n_buffers { + self.act(src, dst, buf.as_mut_ptr().cast(), STACK_BUFFER_SIZE, traph)?; + src += STACK_BUFFER_SIZE as u64; + dst += STACK_BUFFER_SIZE as u64; } - status + self.act(src, dst, buf.as_mut_ptr().cast(), rem, traph) } /// Split address to pages, check their permissions and feed pointers with offset From afbf6dd2e41cba429c2d376167a126d5fe051293 Mon Sep 17 00:00:00 2001 From: Erin Date: Sat, 22 Jul 2023 01:06:41 +0200 Subject: [PATCH 008/116] Removed pagetable hack --- hbvm/src/vm/mem/mod.rs | 28 ---------------------------- 1 file changed, 28 deletions(-) diff --git a/hbvm/src/vm/mem/mod.rs b/hbvm/src/vm/mem/mod.rs index ea94238f..55af7f23 100644 --- a/hbvm/src/vm/mem/mod.rs +++ b/hbvm/src/vm/mem/mod.rs @@ -36,34 +36,6 @@ impl Drop for Memory { } impl Memory { - // HACK: Just for allocation testing, will be removed when proper memory interfaces - // implemented. - pub fn insert_test_page(&mut self) { - unsafe { - let mut entry = PtEntry::new( - { - let layout = alloc::alloc::Layout::from_size_align_unchecked(4096, 4096); - let ptr = alloc::alloc::alloc_zeroed(layout); - if ptr.is_null() { - alloc::alloc::handle_alloc_error(layout); - } - - core::ptr::write_bytes(ptr, 69, 10); - ptr.cast() - }, - Permission::Write, - ); - - for _ in 0..4 { - let mut pt = Box::::default(); - pt.table[0] = entry; - entry = PtEntry::new(Box::into_raw(pt) as _, Permission::Node); - } - - (*self.root_pt).table[0] = entry; - } - } - /// Maps host's memory into VM's memory /// /// # Safety From 3833beb17db06b4844edb0a4649493055e5e2e34 Mon Sep 17 00:00:00 2001 From: Erin Date: Sat, 22 Jul 2023 02:26:03 +0200 Subject: [PATCH 009/116] More comments --- hbasm/src/lib.rs | 40 +++++++- hbasm/src/macros/asm.rs | 29 ++++-- hbasm/src/macros/mod.rs | 44 +++++++++ hbasm/src/macros/text.rs | 53 ++++++++++- hbvm/src/vm/mem/mod.rs | 179 +++++++++++++++++++++++++---------- hbvm/src/vm/mem/pfhandler.rs | 4 + spec.md | 6 +- 7 files changed, 290 insertions(+), 65 deletions(-) diff --git a/hbasm/src/lib.rs b/hbasm/src/lib.rs index a9cecc75..ca8f9aff 100644 --- a/hbasm/src/lib.rs +++ b/hbasm/src/lib.rs @@ -6,12 +6,19 @@ mod macros; use {alloc::vec::Vec, hashbrown::HashSet}; +/// Assembler +/// +/// - Opcode-generic, instruction-type-specific methods are named `i_param_` +/// - You likely won't need to use them, but they are here, just in case :) +/// - Instruction-specific methods are named `i_` #[derive(Default)] pub struct Assembler { pub buf: Vec, pub sub: HashSet, } + +// 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], @@ -31,7 +38,9 @@ macros::impl_both!( ); impl Assembler { - // Special-cased + // Special-cased for text-assembler + // + // `p2` is not a register, but the instruction is still BBB #[inline(always)] pub fn i_brc(&mut self, p0: u8, p1: u8, p2: u8) { self.i_param_bbb(hbbytecode::opcode::BRC, p0, p1, p2) @@ -39,20 +48,49 @@ impl Assembler { /// Append 12 zeroes (UN) at the end pub fn finalise(&mut self) { + // HBVM lore: + // + // In reference HBVM implementation checks are done in + // a separate phase before execution. + // + // This way execution will be much faster as they have to + // be done only once. + // + // There was an issue. You cannot statically check register values and + // `JAL` instruction could hop at the end of program to some byte, which + // will be interpreted as opcode and VM in attempt to decode the instruction + // performed out-of-bounds read which leads to undefined behaviour. + // + // Several options were considered to overcome this, but inserting some data at + // program's end which when executed would lead to undesired behaviour, though + // not undefined behaviour. + // + // Newly created `UN` (as UNreachable) was chosen as + // - It was a good idea to add some equivalent to `ud2` anyways + // - Its zeroes + // - What if you somehow reached that code, it will appropriately bail :) self.buf.extend([0; 12]); } } +/// Immediate value +/// +/// # Implementor notice +/// It should insert exactly 8 bytes, otherwise output will be malformed. +/// This is not checked in any way pub trait Imm { + /// Insert immediate value fn insert(&self, asm: &mut Assembler); } +/// Implement immediate values macro_rules! impl_imm_le_bytes { ($($ty:ty),* $(,)?) => { $( impl Imm for $ty { #[inline(always)] fn insert(&self, asm: &mut Assembler) { + // Convert to little-endian bytes, insert. asm.buf.extend(self.to_le_bytes()); } } diff --git a/hbasm/src/macros/asm.rs b/hbasm/src/macros/asm.rs index fc8366ca..74e0e8f7 100644 --- a/hbasm/src/macros/asm.rs +++ b/hbasm/src/macros/asm.rs @@ -1,5 +1,9 @@ +//! Macros to generate [`crate::Assembler`] + +/// Incremental token-tree muncher to implement specific instruction +/// functions based on generic function for instruction type macro_rules! impl_asm_opcodes { - ( + ( // End case $generic:ident ($($param_i:ident: $param_ty:ty),*) => [] @@ -10,6 +14,7 @@ macro_rules! impl_asm_opcodes { ($($param_i:ident: $param_ty:ty),*) => [$opcode:ident, $($rest:tt)*] ) => { + // Instruction-specific function paste::paste! { #[inline(always)] pub fn [](&mut self, $($param_i: $param_ty),*) { @@ -17,6 +22,7 @@ macro_rules! impl_asm_opcodes { } } + // And recurse! macros::asm::impl_asm_opcodes!( $generic($($param_i: $param_ty),*) => [$($rest)*] @@ -24,16 +30,21 @@ macro_rules! impl_asm_opcodes { }; } +/// Numeric value insert macro_rules! impl_asm_insert { + // Immediate - this is trait-based, + // the insertion is delegated to its implementation ($self:expr, $id:ident, I) => { Imm::insert(&$id, $self) }; + // Other numbers, just insert their bytes, little endian ($self:expr, $id:ident, $_:ident) => { $self.buf.extend($id.to_le_bytes()) }; } +/// Implement assembler macro_rules! impl_asm { ( $( @@ -44,11 +55,13 @@ macro_rules! impl_asm { ) => { paste::paste! { $( - fn [](&mut self, opcode: u8, $($param_i: macros::asm::ident_map_ty!($param_ty)),*) { + // Opcode-generic functions specific for instruction types + pub fn [](&mut self, opcode: u8, $($param_i: macros::asm::ident_map_ty!($param_ty)),*) { self.buf.push(opcode); $(macros::asm::impl_asm_insert!(self, $param_i, $param_ty);)* } + // Generate opcode-specific functions calling the opcode-generic ones macros::asm::impl_asm_opcodes!( []($($param_i: macros::asm::ident_map_ty!($param_ty)),*) => [$($opcode,)*] @@ -58,14 +71,12 @@ macro_rules! impl_asm { }; } +/// Map operand type to Rust type #[rustfmt::skip] macro_rules! ident_map_ty { - (R) => { u8 }; - (I) => { impl Imm }; - ($id:ident) => { $id }; + (R) => { u8 }; // Register is just u8 + (I) => { impl Imm }; // Immediate is anything implementing the trait + ($id:ident) => { $id }; // Anything else → identity map } -pub(crate) use {ident_map_ty, impl_asm, impl_asm_opcodes}; - -#[allow(clippy::single_component_path_imports)] -pub(crate) use impl_asm_insert; +pub(crate) use {ident_map_ty, impl_asm, impl_asm_insert, impl_asm_opcodes}; diff --git a/hbasm/src/macros/mod.rs b/hbasm/src/macros/mod.rs index 6fd1e589..55d770b7 100644 --- a/hbasm/src/macros/mod.rs +++ b/hbasm/src/macros/mod.rs @@ -1,6 +1,50 @@ +//! And here the land of macros begin. +//! +//! They do not bite, really. Have you seen what Yandros is writing? + pub mod asm; 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!( +/// INSTRUCTION_TYPE(p0: TYPE, p1: TYPE, …) +/// => [INSTRUCTION_A, INSTRUCTION_B, …], +/// … +/// ); +/// ``` +/// - Instruction type determines opcode-generic, instruction-type-specific +/// function. Name: `i_param_INSTRUCTION_TYPE` +/// - Per-instructions there will be generated opcode-specific functions calling the generic ones +/// - Operand types +/// - 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 +/// ```text +/// instruction op1, op2, … +/// … +/// ``` +/// - Opcode names are lowercase +/// - Registers are prefixed with `r` followed by number +/// - Operands are separated by `,` +/// - Instructions are separated by either line feed or `;` (αυτό δεν είναι ερωτηματικό!) +/// - 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 { ($($tt:tt)*) => { impl Assembler { diff --git a/hbasm/src/macros/text.rs b/hbasm/src/macros/text.rs index 93acfae4..3234228f 100644 --- a/hbasm/src/macros/text.rs +++ b/hbasm/src/macros/text.rs @@ -1,3 +1,7 @@ +//! Macros to generate text-code assembler at [`crate::text`] +// Refering in module which generates a module to that module — is that even legal? :D + +/// Generate text code based assembler macro_rules! gen_text { ( $( @@ -6,6 +10,7 @@ macro_rules! gen_text { => [$($opcode:ident),* $(,)?], )* ) => { + /// Text code based assembler pub mod text { use { crate::{ @@ -18,6 +23,7 @@ macro_rules! gen_text { }; paste::paste!(literify::literify! { + /// Assembly token #[derive(Clone, Copy, Debug, PartialEq, Eq, Logos)] #[logos(extras = Rodeo)] #[logos(skip r"[ \t\t]+")] @@ -59,6 +65,7 @@ macro_rules! gen_text { } }); + /// Type of error #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub enum ErrorKind { UnexpectedToken, @@ -67,12 +74,14 @@ macro_rules! gen_text { InvalidSymbol, } + /// Text assembly error #[derive(Clone, Debug, PartialEq, Eq)] pub struct Error { pub kind: ErrorKind, pub span: Span, } + /// Parse code and insert instructions pub fn assemble(asm: &mut Assembler, code: &str) -> Result<(), Error> { pub struct TextAsm<'a> { asm: &'a mut Assembler, @@ -93,8 +102,10 @@ macro_rules! gen_text { fn run(&mut self) -> Result<(), ErrorKind> { loop { match self.lexer.next() { + // Got an opcode Some(Ok(Token::Opcode(op))) => { match op { + // Take all the opcodes and match them to their corresponding functions $( $(hbbytecode::opcode::$opcode)|* => paste::paste!({ param_extract_itm!(self, $($param_i: $param_ty),*); @@ -112,12 +123,16 @@ macro_rules! gen_text { self.asm.i_param_bbb(op, p0, p1, p2); } + // Already matched in Logos, should not be able to obtain + // invalid opcode. _ => unreachable!(), } } + // Insert label to table Some(Ok(Token::Label(lbl))) => { self.symloc.insert(lbl, self.asm.buf.len()); } + // Instruction separator (LF, ;) Some(Ok(Token::ISep)) => (), Some(Ok(_)) => return Err(ErrorKind::UnexpectedToken), Some(Err(())) => return Err(ErrorKind::InvalidToken), @@ -136,15 +151,20 @@ macro_rules! gen_text { asm.run() .map_err(|kind| Error { kind, span: asm.lexer.span() })?; + // Walk table and substitute labels + // for their addresses for &loc in &asm.asm.sub { + // Extract indices from the code and get addresses from table let val = asm.symloc .get( - &Spur::try_from_usize(bytemuck::pod_read_unaligned::(&asm.asm.buf[loc..loc+core::mem::size_of::()]) as _) - .unwrap() + &Spur::try_from_usize(bytemuck::pod_read_unaligned::( + &asm.asm.buf[loc..loc + core::mem::size_of::()]) as _ + ).unwrap() ) .ok_or(Error { kind: ErrorKind::InvalidSymbol, span: 0..0 })? .to_le_bytes(); + // New address asm.asm.buf[loc..] .iter_mut() .zip(val) @@ -154,6 +174,13 @@ macro_rules! gen_text { Ok(()) } + // Fun fact: this is a little hack + // It may slow the things a little bit down, but + // it made the macro to be made pretty nice. + // + // If you have any idea how to get rid of this, + // contributions are welcome :) + // I *likely* won't try anymore. enum InternalImm { Const(u64), Named(Spur), @@ -163,9 +190,14 @@ macro_rules! gen_text { #[inline] fn insert(&self, asm: &mut Assembler) { match self { + // Constant immediate, just put it in Self::Const(a) => a.insert(asm), + // Label Self::Named(a) => { + // Insert to the sub table that substitution will be + // requested asm.sub.insert(asm.buf.len()); + // Insert value from interner in place asm.buf.extend((a.into_usize() as u64).to_le_bytes()); }, } @@ -175,42 +207,57 @@ macro_rules! gen_text { }; } +/// Extract item by pattern, otherwise return [`ErrorKind::UnexpectedToken`] macro_rules! extract_pat { ($self:expr, $pat:pat) => { let $pat = $self.next()? - else { return Err(ErrorKind::UnexpectedToken) }; + else { return Err(ErrorKind::UnexpectedToken) }; }; } +/// Extract operand from code macro_rules! extract { + // Register (require prefixing with r) ($self:expr, R, $id:ident) => { extract_pat!($self, Token::Register($id)); }; + // Immediate ($self:expr, I, $id:ident) => { let $id = match $self.next()? { + // Either straight up integer Token::Integer(a) => InternalImm::Const(a), + // …or a label Token::Symbol(a) => InternalImm::Named(a), _ => return Err(ErrorKind::UnexpectedToken), }; }; + // Get u8, if not fitting, the token is claimed invalid ($self:expr, u8, $id:ident) => { extract_pat!($self, Token::Integer($id)); let $id = u8::try_from($id).map_err(|_| ErrorKind::InvalidToken)?; }; + // Get u16, if not fitting, the token is claimed invalid ($self:expr, u16, $id:ident) => { extract_pat!($self, Token::Integer($id)); let $id = u16::try_from($id).map_err(|_| ErrorKind::InvalidToken)?; }; } +/// Parameter extract incremental token-tree muncher +/// +/// What else would it mean? macro_rules! param_extract_itm { ($self:expr, $($id:ident: $ty:ident)? $(, $($tt:tt)*)?) => { + // Extract pattern $(extract!($self, $ty, $id);)? $( + // Require operand separator extract_pat!($self, Token::PSep); + // And go to the next (recursive) + // …munch munch… yummy token trees. param_extract_itm!($self, $($tt)*); )? }; diff --git a/hbvm/src/vm/mem/mod.rs b/hbvm/src/vm/mem/mod.rs index 55af7f23..df146826 100644 --- a/hbvm/src/vm/mem/mod.rs +++ b/hbvm/src/vm/mem/mod.rs @@ -39,7 +39,9 @@ impl Memory { /// Maps host's memory into VM's memory /// /// # Safety - /// Who knows. + /// - Your faith in the gods of UB + /// - Addr-san claims it's fine but who knows is she isn't lying :ferrisSus: + /// - Alright, Miri-sama is also fine with this, who knows why pub unsafe fn map( &mut self, host: *mut u8, @@ -49,13 +51,14 @@ impl Memory { ) -> Result<(), MapError> { let mut current_pt = self.root_pt; + // Decide on what level depth are we going let lookup_depth = match pagesize { PageSize::Size4K => 4, PageSize::Size2M => 3, PageSize::Size1G => 2, }; - // Lookup pagetable above + // Walk pagetable levels for lvl in (0..lookup_depth).rev() { let entry = (*current_pt) .table @@ -63,8 +66,12 @@ impl Memory { let ptr = entry.ptr(); match entry.permission() { + // Still not on target and already seeing empty entry? + // No worries! Let's create one (allocates). Permission::Empty => { + // Increase children count (*current_pt).childen += 1; + let table = Box::into_raw(Box::new(paging::PtPointedData { pt: PageTable::default(), })); @@ -72,28 +79,39 @@ impl Memory { core::ptr::write(entry, PtEntry::new(table, Permission::Node)); current_pt = table as _; } + // Continue walking Permission::Node => current_pt = ptr as _, - _ => return Err(MapError::AlreadyMapped), + + // There is some entry on place of node + _ => return Err(MapError::PageOnNode), } } + let node = (*current_pt) + .table + .get_unchecked_mut(addr_extract_index(target, 4 - lookup_depth)); + + // Check if node is not mapped + if node.permission() != Permission::Empty { + return Err(MapError::AlreadyMapped); + } + // Write entry (*current_pt).childen += 1; - core::ptr::write( - (*current_pt) - .table - .get_unchecked_mut(addr_extract_index(target, 4 - lookup_depth)), - PtEntry::new(host.cast(), perm), - ); + core::ptr::write(node, PtEntry::new(host.cast(), perm)); Ok(()) } /// Unmaps pages from VM's memory + /// + /// If errors, it only means there is no entry to unmap and in most cases + /// just should be ignored. pub fn unmap(&mut self, addr: u64) -> Result<(), NothingToUnmap> { let mut current_pt = self.root_pt; let mut page_tables = [core::ptr::null_mut(); 5]; + // Walk page table in reverse for lvl in (0..5).rev() { let entry = unsafe { (*current_pt) @@ -103,30 +121,42 @@ impl Memory { let ptr = entry.ptr(); match entry.permission() { + // Nothing is there, throw an error, not critical! Permission::Empty => return Err(NothingToUnmap), + // Node – Save to visited pagetables and continue walking Permission::Node => { page_tables[lvl as usize] = entry; current_pt = ptr as _ } + // Page entry – zero it out! + // Zero page entry is completely valid entry with + // empty permission - no UB here! _ => unsafe { - core::ptr::write(entry, Default::default()); + core::ptr::write_bytes(entry, 0, 1); }, } } + // Now walk in order visited page tables for entry in page_tables.into_iter() { + // Level not visited, skip. if entry.is_null() { continue; } unsafe { let children = &mut (*(*entry).ptr()).pt.childen; - *children -= 1; - if *children == 0 { - core::mem::drop(Box::from_raw((*entry).ptr() as *mut PageTable)); - } - core::ptr::write(entry, Default::default()); + // Decrease children count + *children -= 1; + + // If there are no children, deallocate. + if *children == 0 { + let _ = Box::from_raw((*entry).ptr() as *mut PageTable); + + // Zero visited entry + core::ptr::write_bytes(entry, 0, 1); + } } } @@ -149,12 +179,7 @@ impl Memory { addr, target, count, - |perm| { - matches!( - perm, - Permission::Readonly | Permission::Write | Permission::Exec - ) - }, + perm_check::readable, |src, dst, count| core::ptr::copy_nonoverlapping(src, dst, count), traph, ) @@ -177,7 +202,7 @@ impl Memory { addr, source.cast_mut(), count, - |perm| perm == Permission::Write, + perm_check::writable, |dst, src, count| core::ptr::copy_nonoverlapping(src, dst, count), traph, ) @@ -188,8 +213,7 @@ impl Memory { /// /// # Safety /// - Same as for [`Self::load`] and [`Self::store`] - /// - Your faith in the gods of UB - /// - Addr-san claims it's fine but who knows is she isn't lying :ferrisSus: + /// - This function has been rewritten and is now pretty much boring pub unsafe fn block_copy( &mut self, mut src: u64, @@ -209,17 +233,13 @@ impl Memory { count: usize, traph: &mut impl HandlePageFault, ) -> Result<(), BlkCopyError> { + // Load to buffer self.memory_access( MemoryAccessReason::Load, src, buf, - STACK_BUFFER_SIZE, - |perm| { - matches!( - perm, - Permission::Readonly | Permission::Write | Permission::Exec - ) - }, + count, + perm_check::readable, |src, dst, count| core::ptr::copy(src, dst, count), traph, ) @@ -228,12 +248,13 @@ impl Memory { addr, })?; + // Store from buffer self.memory_access( MemoryAccessReason::Store, dst, buf, count, - |perm| perm == Permission::Write, + perm_check::writable, |dst, src, count| core::ptr::copy(src, dst, count), traph, ) @@ -246,24 +267,37 @@ impl Memory { } } - const STACK_BUFFER_SIZE: usize = 4096; + // Buffer size (defaults to 4 KiB, a smallest page size on most platforms) + const BUF_SIZE: usize = 4096; - // Decide if to use stack-allocated buffer or to heap allocate - // Deallocation is again decided on size at the end of the function - let mut buf = MaybeUninit::<[u8; STACK_BUFFER_SIZE]>::uninit(); + // This should be equal to `BUF_SIZE` + #[repr(align(4096))] + struct AlignedBuf([MaybeUninit; BUF_SIZE]); - let n_buffers = count / STACK_BUFFER_SIZE; - let rem = count % STACK_BUFFER_SIZE; + // Safety: Assuming uninit of array of MaybeUninit is sound + let mut buf = AlignedBuf(MaybeUninit::uninit().assume_init()); + // Calculate how many times we need to copy buffer-sized blocks if any and the rest. + let n_buffers = count / BUF_SIZE; + let rem = count % BUF_SIZE; + + // Copy buffer-sized blocks for _ in 0..n_buffers { - self.act(src, dst, buf.as_mut_ptr().cast(), STACK_BUFFER_SIZE, traph)?; - src += STACK_BUFFER_SIZE as u64; - dst += STACK_BUFFER_SIZE as u64; + self.act(src, dst, buf.0.as_mut_ptr().cast(), BUF_SIZE, traph)?; + src += BUF_SIZE as u64; + dst += BUF_SIZE as u64; } - self.act(src, dst, buf.as_mut_ptr().cast(), rem, traph) + // Copy the rest (if any) + if rem != 0 { + self.act(src, dst, buf.0.as_mut_ptr().cast(), rem, traph)?; + } + + Ok(()) } + // Everyone behold, the holy function, the god of HBVM memory accesses! + /// Split address to pages, check their permissions and feed pointers with offset /// to a specified function. /// @@ -279,10 +313,11 @@ impl Memory { action: fn(*mut u8, *mut u8, usize), traph: &mut impl HandlePageFault, ) -> Result<(), u64> { + // Create new splitter let mut pspl = AddrPageLookuper::new(src, len, self.root_pt); loop { match pspl.next() { - // Page found + // Page is found Some(Ok(AddrPageLookupOk { vaddr, ptr, @@ -293,12 +328,13 @@ impl Memory { return Err(vaddr); } - // Perform memory action and bump dst pointer + // Perform specified memory action and bump destination pointer action(ptr, dst, size); dst = unsafe { dst.add(size) }; } + // No page found Some(Err(AddrPageLookupError { addr, size })) => { - // Execute page fault handler + // Attempt to execute page fault handler if traph.page_fault(reason, self, addr, size, dst) { // Shift the splitter address pspl.bump(size); @@ -306,16 +342,17 @@ impl Memory { // Bump dst pointer dst = unsafe { dst.add(size as _) }; } else { - return Err(addr); // Unhandleable + return Err(addr); // Unhandleable, VM will yield. } } + // No remaining pages, we are done! None => return Ok(()), } } } } -/// Result from address split +/// Good result from address split struct AddrPageLookupOk { /// Virtual address vaddr: u64, @@ -330,6 +367,7 @@ struct AddrPageLookupOk { perm: Permission, } +/// Errornous address split result struct AddrPageLookupError { /// Address of failure addr: u64, @@ -351,7 +389,7 @@ struct AddrPageLookuper { } impl AddrPageLookuper { - /// Create a new page splitter + /// Create a new page lookuper pub const fn new(addr: u64, size: usize, pagetable: *const PageTable) -> Self { Self { addr, @@ -430,7 +468,11 @@ impl Iterator for AddrPageLookuper { } } -fn addr_extract_index(addr: u64, lvl: u8) -> usize { +/// Extract index in page table on specified level +/// +/// The level shall not be larger than 4, otherwise +/// the output of the function is unspecified (yes, it can also panic :) +pub fn addr_extract_index(addr: u64, lvl: u8) -> usize { debug_assert!(lvl <= 4); usize::try_from((addr >> (lvl * 9 + 12)) & ((1 << 9) - 1)).expect("?conradluget a better CPU") } @@ -462,24 +504,36 @@ impl PageSize { /// Unhandled load access trap #[derive(Clone, Copy, Display, Debug, PartialEq, Eq)] +#[display(fmt = "Load access error at address {_0:#x}")] pub struct LoadError(u64); /// Unhandled store access trap #[derive(Clone, Copy, Display, Debug, PartialEq, Eq)] +#[display(fmt = "Store access error at address {_0:#x}")] pub struct StoreError(u64); +/// There was no entry in page table to unmap +/// +/// No worry, don't panic, nothing bad has happened, +/// but if you are 120% sure there should be something, +/// double-check your addresses. #[derive(Clone, Copy, Display, Debug)] +#[display(fmt = "There was no entry to unmap")] pub struct NothingToUnmap; +/// Reason to access memory #[derive(Clone, Copy, Display, Debug, PartialEq, Eq)] pub enum MemoryAccessReason { Load, Store, } +/// Error occured when copying a block of memory #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct BlkCopyError { + /// Kind of access access_reason: MemoryAccessReason, + /// VM Address addr: u64, } @@ -504,7 +558,34 @@ impl From for VmRunError { } } +/// Error mapping #[derive(Clone, Copy, Display, Debug, PartialEq, Eq)] pub enum MapError { + /// Entry was already mapped + #[display(fmt = "There is already a page mapped on specified address")] AlreadyMapped, + /// When walking a page entry was + /// encounterd. + #[display(fmt = "There was a page mapped on the way instead of node")] + PageOnNode, +} + +/// Permisison checks +pub mod perm_check { + use super::paging::Permission; + + /// Page is readable + #[inline(always)] + pub fn readable(perm: Permission) -> bool { + matches!( + perm, + Permission::Readonly | Permission::Write | Permission::Exec + ) + } + + /// Page is writable + #[inline(always)] + pub fn writable(perm: Permission) -> bool { + perm == Permission::Write + } } diff --git a/hbvm/src/vm/mem/pfhandler.rs b/hbvm/src/vm/mem/pfhandler.rs index 580e44e0..fb0cb229 100644 --- a/hbvm/src/vm/mem/pfhandler.rs +++ b/hbvm/src/vm/mem/pfhandler.rs @@ -5,6 +5,10 @@ use super::{Memory, MemoryAccessReason, PageSize}; /// Handle VM traps pub trait HandlePageFault { /// Handle page fault + /// + /// Return true if handling was sucessful, + /// otherwise the program will be interrupted and will + /// yield an error. fn page_fault( &mut self, reason: MemoryAccessReason, diff --git a/spec.md b/spec.md index ddf17672..b9909e67 100644 --- a/spec.md +++ b/spec.md @@ -173,9 +173,9 @@ ### Unconditional jump - Type BBD -| Opcode | Name | Action | -|:------:|:----:|:-------------------------------------------------:| -| 33 | JAL | Save current PC to `#0` and jump at `#1 + imm #2` | +| Opcode | Name | Action | +|:------:|:----:|:--------------------------------------------------:| +| 33 | JAL | Save PC past JAL to `#0` and jump at `#1 + imm #2` | ### Conditional jumps - Type BBD From 47323e140c5bf8c151347353aa474ae0d84318fe Mon Sep 17 00:00:00 2001 From: Erin Date: Sat, 22 Jul 2023 02:26:29 +0200 Subject: [PATCH 010/116] added notice. --- hbasm/src/lib.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/hbasm/src/lib.rs b/hbasm/src/lib.rs index ca8f9aff..639383b7 100644 --- a/hbasm/src/lib.rs +++ b/hbasm/src/lib.rs @@ -69,6 +69,8 @@ impl Assembler { // - It was a good idea to add some equivalent to `ud2` anyways // - Its zeroes // - What if you somehow reached that code, it will appropriately bail :) + // + // Why 12 bytes? That's the size of largest instruction parameter part. self.buf.extend([0; 12]); } } From 3892a719eb941532c54d83a3ac15203deca7bbcd Mon Sep 17 00:00:00 2001 From: Erin Date: Sat, 22 Jul 2023 02:27:03 +0200 Subject: [PATCH 011/116] A --- hbasm/src/lib.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/hbasm/src/lib.rs b/hbasm/src/lib.rs index 639383b7..3235f5bf 100644 --- a/hbasm/src/lib.rs +++ b/hbasm/src/lib.rs @@ -67,8 +67,9 @@ impl Assembler { // // Newly created `UN` (as UNreachable) was chosen as // - It was a good idea to add some equivalent to `ud2` anyways - // - Its zeroes + // - It was chosen to be zero // - What if you somehow reached that code, it will appropriately bail :) + // - (yes, originally NOP was considered) // // Why 12 bytes? That's the size of largest instruction parameter part. self.buf.extend([0; 12]); From 8d5d22eae19b710cf1c0f9719059ff66ae2b769c Mon Sep 17 00:00:00 2001 From: Erin Date: Sat, 22 Jul 2023 02:28:05 +0200 Subject: [PATCH 012/116] Moved lore --- hbasm/src/lib.rs | 49 ++++++++++++++++++++++++------------------------ 1 file changed, 25 insertions(+), 24 deletions(-) diff --git a/hbasm/src/lib.rs b/hbasm/src/lib.rs index 3235f5bf..b7799370 100644 --- a/hbasm/src/lib.rs +++ b/hbasm/src/lib.rs @@ -47,31 +47,32 @@ impl Assembler { } /// Append 12 zeroes (UN) at the end + /// + /// # HBVM lore + /// + /// In reference HBVM implementation checks are done in + /// a separate phase before execution. + /// + /// This way execution will be much faster as they have to + /// be done only once. + /// + /// There was an issue. You cannot statically check register values and + /// `JAL` instruction could hop at the end of program to some byte, which + /// will be interpreted as opcode and VM in attempt to decode the instruction + /// performed out-of-bounds read which leads to undefined behaviour. + /// + /// Several options were considered to overcome this, but inserting some data at + /// program's end which when executed would lead to undesired behaviour, though + /// not undefined behaviour. + /// + /// Newly created `UN` (as UNreachable) was chosen as + /// - It was a good idea to add some equivalent to `ud2` anyways + /// - It was chosen to be zero + /// - What if you somehow reached that code, it will appropriately bail :) + /// - (yes, originally `NOP` was considered) + /// + /// Why 12 bytes? That's the size of largest instruction parameter part. pub fn finalise(&mut self) { - // HBVM lore: - // - // In reference HBVM implementation checks are done in - // a separate phase before execution. - // - // This way execution will be much faster as they have to - // be done only once. - // - // There was an issue. You cannot statically check register values and - // `JAL` instruction could hop at the end of program to some byte, which - // will be interpreted as opcode and VM in attempt to decode the instruction - // performed out-of-bounds read which leads to undefined behaviour. - // - // Several options were considered to overcome this, but inserting some data at - // program's end which when executed would lead to undesired behaviour, though - // not undefined behaviour. - // - // Newly created `UN` (as UNreachable) was chosen as - // - It was a good idea to add some equivalent to `ud2` anyways - // - It was chosen to be zero - // - What if you somehow reached that code, it will appropriately bail :) - // - (yes, originally NOP was considered) - // - // Why 12 bytes? That's the size of largest instruction parameter part. self.buf.extend([0; 12]); } } From c621a5c71d7e91fd4901256fdea5a200c1e8d94a Mon Sep 17 00:00:00 2001 From: Erin Date: Sat, 22 Jul 2023 02:29:05 +0200 Subject: [PATCH 013/116] Edits. --- hbasm/src/lib.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/hbasm/src/lib.rs b/hbasm/src/lib.rs index b7799370..cd137e4b 100644 --- a/hbasm/src/lib.rs +++ b/hbasm/src/lib.rs @@ -58,8 +58,8 @@ impl Assembler { /// /// There was an issue. You cannot statically check register values and /// `JAL` instruction could hop at the end of program to some byte, which - /// will be interpreted as opcode and VM in attempt to decode the instruction - /// performed out-of-bounds read which leads to undefined behaviour. + /// will be interpreted as some valid opcode and VM in attempt to decode + /// the instruction performed out-of-bounds read which leads to undefined behaviour. /// /// Several options were considered to overcome this, but inserting some data at /// program's end which when executed would lead to undesired behaviour, though From 8eebbacb91b6a9522d9621073cd1cd08b707080e Mon Sep 17 00:00:00 2001 From: Erin Date: Sat, 22 Jul 2023 02:34:41 +0200 Subject: [PATCH 014/116] Name correction --- hbasm/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hbasm/src/lib.rs b/hbasm/src/lib.rs index cd137e4b..dfdda485 100644 --- a/hbasm/src/lib.rs +++ b/hbasm/src/lib.rs @@ -48,7 +48,7 @@ impl Assembler { /// Append 12 zeroes (UN) at the end /// - /// # HBVM lore + /// # HoleyBytes lore /// /// In reference HBVM implementation checks are done in /// a separate phase before execution. From 1532c501a6ab2a8b685212fbdc89d56ce9b274f8 Mon Sep 17 00:00:00 2001 From: Erin Date: Sat, 22 Jul 2023 02:42:21 +0200 Subject: [PATCH 015/116] added contribution guide to instructions --- hbvm/src/vm/mod.rs | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/hbvm/src/vm/mod.rs b/hbvm/src/vm/mod.rs index ff81de4e..4e466ac4 100644 --- a/hbvm/src/vm/mod.rs +++ b/hbvm/src/vm/mod.rs @@ -137,6 +137,24 @@ impl<'a, PfHandler: HandlePageFault, const TIMER_QUOTIENT: usize> } // Big match + // + // Contribution guide: + // - Zero register shall never be overwitten. It's value has to always be 0. + // - Prefer `Self::read_reg` and `Self::write_reg` functions + // - Extract parameters using `param!` macro + // - Prioritise speed over code size + // - Memory is cheap, CPUs not that much + // - Do not heap allocate at any cost + // - Yes, user-provided trap handler may allocate, + // but that is not our »fault«. + // - Unsafe is kinda must, but be sure you have validated everything + // - Your contributions have to pass sanitizers and Miri + // - Strictly follow the spec + // - The spec does not specify how you perform actions, in what order, + // just that the observable effects have to be performed in order and + // correctly. + // - Yes, we assume you run 64 bit CPU. Else ?conradluget a better CPU + // sorry 8 bit fans, HBVM won't run on your Speccy :( unsafe { match *self.program.get_unchecked(self.pc) { UN => { From dcfd51999d2b5675ff18d4f8b9384c13c600c687 Mon Sep 17 00:00:00 2001 From: Erin Date: Sat, 22 Jul 2023 02:42:43 +0200 Subject: [PATCH 016/116] Fixed missing / --- hbvm/src/vm/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hbvm/src/vm/mod.rs b/hbvm/src/vm/mod.rs index 4e466ac4..7b378149 100644 --- a/hbvm/src/vm/mod.rs +++ b/hbvm/src/vm/mod.rs @@ -87,7 +87,7 @@ pub struct Vm<'a, PfHandler, const TIMER_QUOTIENT: usize> { /// Trap handler pub pfhandler: PfHandler, - // Program counter + /// Program counter pc: usize, /// Program From 4ca4e81ac3c66feeaa229d0f4d45b57715ab7638 Mon Sep 17 00:00:00 2001 From: Erin Date: Mon, 24 Jul 2023 16:37:37 +0200 Subject: [PATCH 017/116] Fixed panic on shift outta bounds - Pointed out by 5225225 --- hbvm/src/vm/mod.rs | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/hbvm/src/vm/mod.rs b/hbvm/src/vm/mod.rs index 7b378149..33fdd605 100644 --- a/hbvm/src/vm/mod.rs +++ b/hbvm/src/vm/mod.rs @@ -50,6 +50,20 @@ macro_rules! binary_op { }}; } +/// Parform bitshift operations +macro_rules! binary_op_sh { + ($self:expr, $ty:ident, $handler:expr) => {{ + let ParamBBB(tg, a0, a1) = param!($self, ParamBBB); + $self.write_reg( + tg, + $handler( + Value::$ty(&$self.read_reg(a0)), + $self.read_reg(a1).as_u64() as u32, + ) + ) + }}; +} + /// Perform binary operation with immediate `#0 ← #1 OP imm #2` macro_rules! binary_op_imm { ($self:expr, $ty:ident, $handler:expr) => {{ @@ -168,9 +182,9 @@ impl<'a, PfHandler: HandlePageFault, const TIMER_QUOTIENT: usize> AND => binary_op!(self, as_u64, ops::BitAnd::bitand), OR => binary_op!(self, as_u64, ops::BitOr::bitor), XOR => binary_op!(self, as_u64, ops::BitXor::bitxor), - SL => binary_op!(self, as_u64, ops::Shl::shl), - SR => binary_op!(self, as_u64, ops::Shr::shr), - SRS => binary_op!(self, as_i64, ops::Shr::shr), + SL => binary_op_sh!(self, as_u64, u64::wrapping_shl), + SR => binary_op_sh!(self, as_u64, u64::wrapping_shr), + SRS => binary_op_sh!(self, as_i64, i64::wrapping_shr), CMP => { // Compare a0 <=> a1 // < → -1 From fce3fa5210fb5b343725cee23408976bee0a8f04 Mon Sep 17 00:00:00 2001 From: Erin Date: Mon, 24 Jul 2023 16:48:13 +0200 Subject: [PATCH 018/116] fixed imm shl/r --- hbvm/src/vm/mod.rs | 31 ++++++++++++++++++------------- 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/hbvm/src/vm/mod.rs b/hbvm/src/vm/mod.rs index 33fdd605..ce1096eb 100644 --- a/hbvm/src/vm/mod.rs +++ b/hbvm/src/vm/mod.rs @@ -48,19 +48,16 @@ macro_rules! binary_op { ), ); }}; -} -/// Parform bitshift operations -macro_rules! binary_op_sh { - ($self:expr, $ty:ident, $handler:expr) => {{ + ($self:expr, $ty:ident, $handler:expr, $con:ty) => {{ let ParamBBB(tg, a0, a1) = param!($self, ParamBBB); $self.write_reg( tg, $handler( Value::$ty(&$self.read_reg(a0)), - $self.read_reg(a1).as_u64() as u32, - ) - ) + Value::$ty(&$self.read_reg(a1)) as $con, + ), + ); }}; } @@ -73,6 +70,14 @@ macro_rules! binary_op_imm { $handler(Value::$ty(&$self.read_reg(a0)), Value::$ty(&imm.into())), ); }}; + + ($self:expr, $ty:ident, $handler:expr, $con:ty) => {{ + let ParamBBD(tg, a0, imm) = param!($self, ParamBBD); + $self.write_reg( + tg, + $handler(Value::$ty(&$self.read_reg(a0)), Value::$ty(&imm.into()) as $con), + ); + }}; } /// Jump at `#3` if ordering on `#0 <=> #1` is equal to expected @@ -182,9 +187,9 @@ impl<'a, PfHandler: HandlePageFault, const TIMER_QUOTIENT: usize> AND => binary_op!(self, as_u64, ops::BitAnd::bitand), OR => binary_op!(self, as_u64, ops::BitOr::bitor), XOR => binary_op!(self, as_u64, ops::BitXor::bitxor), - SL => binary_op_sh!(self, as_u64, u64::wrapping_shl), - SR => binary_op_sh!(self, as_u64, u64::wrapping_shr), - SRS => binary_op_sh!(self, as_i64, i64::wrapping_shr), + SL => binary_op!(self, as_u64, u64::wrapping_shl, u32), + SR => binary_op!(self, as_u64, u64::wrapping_shr, u32), + SRS => binary_op!(self, as_i64, i64::wrapping_shr, u32), CMP => { // Compare a0 <=> a1 // < → -1 @@ -234,9 +239,9 @@ impl<'a, PfHandler: HandlePageFault, const TIMER_QUOTIENT: usize> ANDI => binary_op_imm!(self, as_u64, ops::BitAnd::bitand), ORI => binary_op_imm!(self, as_u64, ops::BitOr::bitor), XORI => binary_op_imm!(self, as_u64, ops::BitXor::bitxor), - SLI => binary_op_imm!(self, as_u64, ops::Shl::shl), - SRI => binary_op_imm!(self, as_u64, ops::Shr::shr), - SRSI => binary_op_imm!(self, as_i64, ops::Shr::shr), + SLI => binary_op_imm!(self, as_u64, u64::wrapping_shl, u32), + SRI => binary_op_imm!(self, as_u64, u64::wrapping_shr, u32), + SRSI => binary_op_imm!(self, as_i64, i64::wrapping_shr, u32), CMPI => { let ParamBBD(tg, a0, imm) = param!(self, ParamBBD); self.write_reg( From 193be0bd5ad8a04d7f212c8baca57f8626c33d40 Mon Sep 17 00:00:00 2001 From: Erin Date: Mon, 24 Jul 2023 18:48:42 +0200 Subject: [PATCH 019/116] Removed some macros --- Cargo.lock | 19 +++ hbasm/src/lib.rs | 6 +- hbasm/src/macros/text.rs | 54 ++++---- hbbytecode/hbbytecode.h | 5 + hbbytecode/src/lib.rs | 12 +- hbvm/Cargo.toml | 1 + hbvm/src/validate.rs | 3 +- hbvm/src/vm/mod.rs | 289 +++++++++++++++++++-------------------- hbvm/src/vm/value.rs | 40 ++++-- spec.md | 6 + 10 files changed, 238 insertions(+), 197 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 007c080d..708a8680 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -130,9 +130,16 @@ dependencies = [ "hbbytecode", "log", "paste", + "sealed", "static_assertions", ] +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + [[package]] name = "lasso" version = "0.7.2" @@ -246,6 +253,18 @@ dependencies = [ "semver", ] +[[package]] +name = "sealed" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4a8caec23b7800fb97971a1c6ae365b6239aaeddfb934d6265f8505e795699d" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn 2.0.25", +] + [[package]] name = "semver" version = "1.0.17" diff --git a/hbasm/src/lib.rs b/hbasm/src/lib.rs index dfdda485..022f8d07 100644 --- a/hbasm/src/lib.rs +++ b/hbasm/src/lib.rs @@ -27,8 +27,10 @@ macros::impl_both!( bbdh(p0: R, p1: R, p2: I, p3: u16) => [LD, ST], bbd(p0: R, p1: R, p2: I) - => [ADDI, MULI, ANDI, ORI, XORI, SLI, SRI, SRSI, CMPI, CMPUI, - BMC, JAL, JEQ, JNE, JLT, JGT, JLTU, JGTU, ADDFI, MULFI], + => [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) diff --git a/hbasm/src/macros/text.rs b/hbasm/src/macros/text.rs index 3234228f..d26e7e1f 100644 --- a/hbasm/src/macros/text.rs +++ b/hbasm/src/macros/text.rs @@ -215,37 +215,39 @@ macro_rules! extract_pat { }; } -/// Extract operand from code -macro_rules! extract { - // Register (require prefixing with r) - ($self:expr, R, $id:ident) => { - extract_pat!($self, Token::Register($id)); - }; +/// Generate extract macro +macro_rules! gen_extract { + // Integer types have same body + ($($int:ident),* $(,)?) => { + /// Extract operand from code + macro_rules! extract { + // Register (require prefixing with r) + ($self:expr, R, $id:ident) => { + extract_pat!($self, Token::Register($id)); + }; - // Immediate - ($self:expr, I, $id:ident) => { - let $id = match $self.next()? { - // Either straight up integer - Token::Integer(a) => InternalImm::Const(a), - // …or a label - Token::Symbol(a) => InternalImm::Named(a), - _ => return Err(ErrorKind::UnexpectedToken), - }; - }; + // Immediate + ($self:expr, I, $id:ident) => { + let $id = match $self.next()? { + // Either straight up integer + Token::Integer(a) => InternalImm::Const(a), + // …or a label + Token::Symbol(a) => InternalImm::Named(a), + _ => return Err(ErrorKind::UnexpectedToken), + }; + }; - // Get u8, if not fitting, the token is claimed invalid - ($self:expr, u8, $id:ident) => { - extract_pat!($self, Token::Integer($id)); - let $id = u8::try_from($id).map_err(|_| ErrorKind::InvalidToken)?; - }; - - // Get u16, if not fitting, the token is claimed invalid - ($self:expr, u16, $id:ident) => { - extract_pat!($self, Token::Integer($id)); - let $id = u16::try_from($id).map_err(|_| ErrorKind::InvalidToken)?; + // Get $int, if not fitting, the token is claimed invalid + $(($self:expr, $int, $id:ident) => { + extract_pat!($self, Token::Integer($id)); + let $id = $int::try_from($id).map_err(|_| ErrorKind::InvalidToken)?; + });*; + } }; } +gen_extract!(u8, u16, u32); + /// Parameter extract incremental token-tree muncher /// /// What else would it mean? diff --git a/hbbytecode/hbbytecode.h b/hbbytecode/hbbytecode.h index 307ca927..5694afac 100644 --- a/hbbytecode/hbbytecode.h +++ b/hbbytecode/hbbytecode.h @@ -44,6 +44,11 @@ struct hbbc_ParamBBD typedef hbbc_ParamBBD; static_assert(sizeof(hbbc_ParamBBD) == 80 / 8); +struct hbbc_ParamBBW + { uint8_t _0; uint8_t _1; uint32_t _2; } + typedef hbbc_ParamBBW; + static_assert(sizeof(hbbc_ParamBBW) == 48 / 8); + struct hbbc_ParamBB { uint8_t _0; uint8_t _1; } typedef hbbc_ParamBB; diff --git a/hbbytecode/src/lib.rs b/hbbytecode/src/lib.rs index 400cab1a..1cf0b21d 100644 --- a/hbbytecode/src/lib.rs +++ b/hbbytecode/src/lib.rs @@ -20,7 +20,7 @@ constmod!(pub opcode(u8) { UN = 0, "N; Raises a trap"; NOP = 1, "N; Do nothing"; - + ADD = 2, "BBB; #0 ← #1 + #2"; SUB = 3, "BBB; #0 ← #1 - #2"; MUL = 4, "BBB; #0 ← #1 × #2"; @@ -41,9 +41,9 @@ constmod!(pub opcode(u8) { ANDI = 18, "BBD; #0 ← #1 & imm #2"; ORI = 19, "BBD; #0 ← #1 | imm #2"; XORI = 20, "BBD; #0 ← #1 ^ imm #2"; - SLI = 21, "BBD; #0 ← #1 « imm #2"; - SRI = 22, "BBD; #0 ← #1 » imm #2"; - SRSI = 23, "BBD; #0 ← #1 » imm #2 (signed)"; + SLI = 21, "BBW; #0 ← #1 « imm #2"; + SRI = 22, "BBW; #0 ← #1 » imm #2"; + SRSI = 23, "BBW; #0 ← #1 » imm #2 (signed)"; CMPI = 24, "BBD; #0 ← #1 <=> imm #2"; CMPUI = 25, "BBD; #0 ← #1 <=> imm #2 (unsigned)"; @@ -89,6 +89,9 @@ pub struct ParamBBDH(pub u8, pub u8, pub u64, pub u16); #[repr(packed)] pub struct ParamBBD(pub u8, pub u8, pub u64); +#[repr(packed)] +pub struct ParamBBW(pub u8, pub u8, pub u32); + #[repr(packed)] pub struct ParamBB(pub u8, pub u8); @@ -102,6 +105,7 @@ unsafe impl OpParam for ParamBBBB {} unsafe impl OpParam for ParamBBB {} unsafe impl OpParam for ParamBBDH {} unsafe impl OpParam for ParamBBD {} +unsafe impl OpParam for ParamBBW {} unsafe impl OpParam for ParamBB {} unsafe impl OpParam for ParamBD {} unsafe impl OpParam for u64 {} diff --git a/hbvm/Cargo.toml b/hbvm/Cargo.toml index a6b6a506..0346a40e 100644 --- a/hbvm/Cargo.toml +++ b/hbvm/Cargo.toml @@ -13,4 +13,5 @@ hashbrown = "0.13" hbbytecode.path = "../hbbytecode" log = "0.4" paste = "1.0" +sealed = "0.5" static_assertions = "1.0" diff --git a/hbvm/src/validate.rs b/hbvm/src/validate.rs index 41b16367..103a2d85 100644 --- a/hbvm/src/validate.rs +++ b/hbvm/src/validate.rs @@ -69,7 +69,8 @@ pub fn validate(mut program: &[u8]) -> Result<(), Error> { | [ADD..=CMPU | BRC | ADDF..=MULF, _, _, _, rest @ ..] | [NEG..=NOT | CP..=SWA | NEGF..=FTI, _, _, rest @ ..] | [LI, _, _, _, _, _, _, _, _, _, rest @ ..] - | [ADDI..=CMPUI | BMC | JAL..=JGTU | ADDFI..=MULFI, _, _, _, _, _, _, _, _, _, _, rest @ ..] + | [ADDI..=XORI | CMPI..=CMPUI | BMC | JAL..=JGTU | ADDFI..=MULFI, _, _, _, _, _, _, _, _, _, _, rest @ ..] + | [SLI..=SRSI, _, _, _, _, rest @ ..] | [LD..=ST, _, _, _, _, _, _, _, _, _, _, _, _, rest @ ..] => rest, _ => { return Err(Error { diff --git a/hbvm/src/vm/mod.rs b/hbvm/src/vm/mod.rs index ce1096eb..797d396f 100644 --- a/hbvm/src/vm/mod.rs +++ b/hbvm/src/vm/mod.rs @@ -7,91 +7,18 @@ // - Instructions have to be valid as specified (values and sizes) // - Mapped pages should be at least 4 KiB -use self::mem::HandlePageFault; - pub mod mem; pub mod value; use { + self::{mem::HandlePageFault, value::ValueVariant}, crate::validate, - core::ops, - hbbytecode::{OpParam, ParamBB, ParamBBB, ParamBBBB, ParamBBD, ParamBBDH, ParamBD}, + core::{cmp::Ordering, ops}, + hbbytecode::{OpParam, ParamBB, ParamBBB, ParamBBBB, ParamBBD, ParamBBDH, ParamBBW, ParamBD}, mem::Memory, - static_assertions::assert_impl_one, value::Value, }; -/// Extract a parameter from program -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 - }}; -} - -/// Perform binary operation `#0 ← #1 OP #2` -macro_rules! binary_op { - ($self:expr, $ty:ident, $handler:expr) => {{ - let ParamBBB(tg, a0, a1) = param!($self, ParamBBB); - $self.write_reg( - tg, - $handler( - Value::$ty(&$self.read_reg(a0)), - Value::$ty(&$self.read_reg(a1)), - ), - ); - }}; - - ($self:expr, $ty:ident, $handler:expr, $con:ty) => {{ - let ParamBBB(tg, a0, a1) = param!($self, ParamBBB); - $self.write_reg( - tg, - $handler( - Value::$ty(&$self.read_reg(a0)), - Value::$ty(&$self.read_reg(a1)) as $con, - ), - ); - }}; -} - -/// Perform binary operation with immediate `#0 ← #1 OP imm #2` -macro_rules! binary_op_imm { - ($self:expr, $ty:ident, $handler:expr) => {{ - let ParamBBD(tg, a0, imm) = param!($self, ParamBBD); - $self.write_reg( - tg, - $handler(Value::$ty(&$self.read_reg(a0)), Value::$ty(&imm.into())), - ); - }}; - - ($self:expr, $ty:ident, $handler:expr, $con:ty) => {{ - let ParamBBD(tg, a0, imm) = param!($self, ParamBBD); - $self.write_reg( - tg, - $handler(Value::$ty(&$self.read_reg(a0)), Value::$ty(&imm.into()) as $con), - ); - }}; -} - -/// Jump at `#3` if ordering on `#0 <=> #1` is equal to expected -macro_rules! cond_jump { - ($self:expr, $ty:ident, $expected:ident) => {{ - let ParamBBD(a0, a1, jt) = param!($self, ParamBBD); - if core::cmp::Ord::cmp(&$self.read_reg(a0).as_u64(), &$self.read_reg(a1).as_u64()) - == core::cmp::Ordering::$expected - { - $self.pc = jt as usize; - } - }}; -} - /// HoleyBytes Virtual Machine pub struct Vm<'a, PfHandler, const TIMER_QUOTIENT: usize> { /// Holds 256 registers @@ -107,7 +34,7 @@ pub struct Vm<'a, PfHandler, const TIMER_QUOTIENT: usize> { pub pfhandler: PfHandler, /// Program counter - pc: usize, + pub pc: usize, /// Program program: &'a [u8], @@ -177,50 +104,56 @@ impl<'a, PfHandler: HandlePageFault, const TIMER_QUOTIENT: usize> unsafe { match *self.program.get_unchecked(self.pc) { UN => { - param!(self, ()); + self.decode::<()>(); return Err(VmRunError::Unreachable); } - NOP => param!(self, ()), - ADD => binary_op!(self, as_u64, u64::wrapping_add), - SUB => binary_op!(self, as_u64, u64::wrapping_sub), - MUL => binary_op!(self, as_u64, u64::wrapping_mul), - AND => binary_op!(self, as_u64, ops::BitAnd::bitand), - OR => binary_op!(self, as_u64, ops::BitOr::bitor), - XOR => binary_op!(self, as_u64, ops::BitXor::bitxor), - SL => binary_op!(self, as_u64, u64::wrapping_shl, u32), - SR => binary_op!(self, as_u64, u64::wrapping_shr, u32), - SRS => binary_op!(self, as_i64, i64::wrapping_shr, u32), + NOP => self.decode::<()>(), + ADD => self.binary_op(u64::wrapping_add), + SUB => self.binary_op(u64::wrapping_sub), + MUL => self.binary_op(u64::wrapping_mul), + AND => self.binary_op::(ops::BitAnd::bitand), + OR => self.binary_op::(ops::BitOr::bitor), + XOR => self.binary_op::(ops::BitXor::bitxor), + SL => self.binary_op(|l, r| u64::wrapping_shl(l, r as u32)), + SR => self.binary_op(|l, r| u64::wrapping_shr(l, r as u32)), + SRS => self.binary_op(|l, r| i64::wrapping_shl(l, r as u32)), CMP => { // Compare a0 <=> a1 // < → -1 // > → 1 // = → 0 - let ParamBBB(tg, a0, a1) = param!(self, ParamBBB); + let ParamBBB(tg, a0, a1) = self.decode(); self.write_reg( tg, - self.read_reg(a0).as_i64().cmp(&self.read_reg(a1).as_i64()) as i64, + self.read_reg(a0) + .cast::() + .cmp(&self.read_reg(a1).cast::()) + as i64, ); } CMPU => { // Unsigned comparsion - let ParamBBB(tg, a0, a1) = param!(self, ParamBBB); + let ParamBBB(tg, a0, a1) = self.decode(); self.write_reg( tg, - self.read_reg(a0).as_u64().cmp(&self.read_reg(a1).as_u64()) as i64, + self.read_reg(a0) + .cast::() + .cmp(&self.read_reg(a1).cast::()) + as i64, ); } NOT => { // Logical negation - let param = param!(self, ParamBB); - self.write_reg(param.0, !self.read_reg(param.1).as_u64()); + let ParamBB(tg, a0) = self.decode(); + self.write_reg(tg, !self.read_reg(a0).cast::()); } NEG => { // Bitwise negation - let param = param!(self, ParamBB); + let ParamBB(tg, a0) = self.decode(); self.write_reg( - param.0, - match self.read_reg(param.1).as_u64() { + tg, + match self.read_reg(a0).cast::() { 0 => 1_u64, _ => 0, }, @@ -228,38 +161,41 @@ impl<'a, PfHandler: HandlePageFault, const TIMER_QUOTIENT: usize> } DIR => { // Fused Division-Remainder - let ParamBBBB(dt, rt, a0, a1) = param!(self, ParamBBBB); - let a0 = self.read_reg(a0).as_u64(); - let a1 = self.read_reg(a1).as_u64(); + let ParamBBBB(dt, rt, a0, a1) = self.decode(); + let a0 = self.read_reg(a0).cast::(); + let a1 = self.read_reg(a1).cast::(); self.write_reg(dt, a0.checked_div(a1).unwrap_or(u64::MAX)); self.write_reg(rt, a0.checked_rem(a1).unwrap_or(u64::MAX)); } - ADDI => binary_op_imm!(self, as_u64, ops::Add::add), - MULI => binary_op_imm!(self, as_u64, ops::Mul::mul), - ANDI => binary_op_imm!(self, as_u64, ops::BitAnd::bitand), - ORI => binary_op_imm!(self, as_u64, ops::BitOr::bitor), - XORI => binary_op_imm!(self, as_u64, ops::BitXor::bitxor), - SLI => binary_op_imm!(self, as_u64, u64::wrapping_shl, u32), - SRI => binary_op_imm!(self, as_u64, u64::wrapping_shr, u32), - SRSI => binary_op_imm!(self, as_i64, i64::wrapping_shr, u32), + ADDI => self.binary_op_imm(u64::wrapping_add), + MULI => self.binary_op_imm(u64::wrapping_sub), + ANDI => self.binary_op_imm::(ops::BitAnd::bitand), + ORI => self.binary_op_imm::(ops::BitOr::bitor), + XORI => self.binary_op_imm::(ops::BitXor::bitxor), + SLI => self.binary_op_ims(u64::wrapping_shl), + SRI => self.binary_op_ims(u64::wrapping_shr), + SRSI => self.binary_op_ims(i64::wrapping_shr), CMPI => { - let ParamBBD(tg, a0, imm) = param!(self, ParamBBD); + let ParamBBD(tg, a0, imm) = self.decode(); self.write_reg( tg, - self.read_reg(a0).as_i64().cmp(&Value::from(imm).as_i64()) as i64, + self.read_reg(a0) + .cast::() + .cmp(&Value::from(imm).cast::()) + as i64, ); } CMPUI => { - let ParamBBD(tg, a0, imm) = param!(self, ParamBBD); - self.write_reg(tg, self.read_reg(a0).as_u64().cmp(&imm) as i64); + let ParamBBD(tg, a0, imm) = self.decode(); + self.write_reg(tg, self.read_reg(a0).cast::().cmp(&imm) as i64); } CP => { - let param = param!(self, ParamBB); - self.write_reg(param.0, self.read_reg(param.1)); + let ParamBB(tg, a0) = self.decode(); + self.write_reg(tg, self.read_reg(a0)); } SWA => { // Swap registers - let ParamBB(r0, r1) = param!(self, ParamBB); + let ParamBB(r0, r1) = self.decode(); match (r0, r1) { (0, 0) => (), (dst, 0) | (0, dst) => self.write_reg(dst, 0_u64), @@ -272,19 +208,19 @@ impl<'a, PfHandler: HandlePageFault, const TIMER_QUOTIENT: usize> } } LI => { - let param = param!(self, ParamBD); - self.write_reg(param.0, param.1); + let ParamBD(tg, imm) = self.decode(); + self.write_reg(tg, imm); } LD => { // Load. If loading more than register size, continue on adjecent registers - let ParamBBDH(dst, base, off, count) = param!(self, ParamBBDH); + let ParamBBDH(dst, base, off, count) = self.decode(); let n: usize = match dst { 0 => 1, _ => 0, }; self.memory.load( - self.read_reg(base).as_u64() + off + n as u64, + self.read_reg(base).cast::() + off + n as u64, self.registers.as_mut_ptr().add(usize::from(dst) + n).cast(), usize::from(count).saturating_sub(n), &mut self.pfhandler, @@ -292,9 +228,9 @@ impl<'a, PfHandler: HandlePageFault, const TIMER_QUOTIENT: usize> } ST => { // Store. Same rules apply as to LD - let ParamBBDH(dst, base, off, count) = param!(self, ParamBBDH); + let ParamBBDH(dst, base, off, count) = self.decode(); self.memory.store( - self.read_reg(base).as_u64() + off, + self.read_reg(base).cast::() + off, self.registers.as_ptr().add(usize::from(dst)).cast(), count.into(), &mut self.pfhandler, @@ -302,17 +238,17 @@ impl<'a, PfHandler: HandlePageFault, const TIMER_QUOTIENT: usize> } BMC => { // Block memory copy - let ParamBBD(src, dst, count) = param!(self, ParamBBD); + let ParamBBD(src, dst, count) = self.decode(); self.memory.block_copy( - self.read_reg(src).as_u64(), - self.read_reg(dst).as_u64(), + self.read_reg(src).cast::(), + self.read_reg(dst).cast::(), count as _, &mut self.pfhandler, )?; } BRC => { // Block register copy - let ParamBBB(src, dst, count) = param!(self, ParamBBB); + let ParamBBB(src, dst, count) = self.decode(); core::ptr::copy( self.registers.get_unchecked(usize::from(src)), self.registers.get_unchecked_mut(usize::from(dst)), @@ -322,24 +258,24 @@ impl<'a, PfHandler: HandlePageFault, const TIMER_QUOTIENT: usize> JAL => { // Jump and link. Save PC after this instruction to // specified register and jump to reg + offset. - let ParamBBD(save, reg, offset) = param!(self, ParamBBD); + let ParamBBD(save, reg, offset) = self.decode(); self.write_reg(save, self.pc as u64); - self.pc = (self.read_reg(reg).as_u64() + offset) as usize; + self.pc = (self.read_reg(reg).cast::() + offset) as usize; } // Conditional jumps, jump only to immediates - JEQ => cond_jump!(self, int, Equal), + JEQ => self.cond_jmp::(Ordering::Equal), JNE => { - let ParamBBD(a0, a1, jt) = param!(self, ParamBBD); - if self.read_reg(a0).as_u64() != self.read_reg(a1).as_u64() { + let ParamBBD(a0, a1, jt) = self.decode(); + if self.read_reg(a0).cast::() != self.read_reg(a1).cast::() { self.pc = jt as usize; } } - JLT => cond_jump!(self, int, Less), - JGT => cond_jump!(self, int, Greater), - JLTU => cond_jump!(self, sint, Less), - JGTU => cond_jump!(self, sint, Greater), + JLT => self.cond_jmp::(Ordering::Less), + JGT => self.cond_jmp::(Ordering::Greater), + JLTU => self.cond_jmp::(Ordering::Less), + JGTU => self.cond_jmp::(Ordering::Greater), ECALL => { - param!(self, ()); + self.decode::<()>(); // So we don't get timer interrupt after ECALL if TIMER_QUOTIENT != 0 { @@ -347,38 +283,38 @@ impl<'a, PfHandler: HandlePageFault, const TIMER_QUOTIENT: usize> } return Ok(VmRunOk::Ecall); } - ADDF => binary_op!(self, as_f64, ops::Add::add), - SUBF => binary_op!(self, as_f64, ops::Sub::sub), - MULF => binary_op!(self, as_f64, ops::Mul::mul), + ADDF => self.binary_op::(ops::Add::add), + SUBF => self.binary_op::(ops::Sub::sub), + MULF => self.binary_op::(ops::Mul::mul), DIRF => { - let ParamBBBB(dt, rt, a0, a1) = param!(self, ParamBBBB); - let a0 = self.read_reg(a0).as_f64(); - let a1 = self.read_reg(a1).as_f64(); + let ParamBBBB(dt, rt, a0, a1) = self.decode(); + let a0 = self.read_reg(a0).cast::(); + let a1 = self.read_reg(a1).cast::(); self.write_reg(dt, a0 / a1); self.write_reg(rt, a0 % a1); } FMAF => { - let ParamBBBB(dt, a0, a1, a2) = param!(self, ParamBBBB); + let ParamBBBB(dt, a0, a1, a2) = self.decode(); self.write_reg( dt, - self.read_reg(a0).as_f64() * self.read_reg(a1).as_f64() - + self.read_reg(a2).as_f64(), + self.read_reg(a0).cast::() * self.read_reg(a1).cast::() + + self.read_reg(a2).cast::(), ); } NEGF => { - let ParamBB(dt, a0) = param!(self, ParamBB); - self.write_reg(dt, -self.read_reg(a0).as_f64()); + let ParamBB(dt, a0) = self.decode(); + self.write_reg(dt, -self.read_reg(a0).cast::()); } ITF => { - let ParamBB(dt, a0) = param!(self, ParamBB); - self.write_reg(dt, self.read_reg(a0).as_i64() as f64); + let ParamBB(dt, a0) = self.decode(); + self.write_reg(dt, self.read_reg(a0).cast::() as f64); } FTI => { - let ParamBB(dt, a0) = param!(self, ParamBB); - self.write_reg(dt, self.read_reg(a0).as_f64() as i64); + let ParamBB(dt, a0) = self.decode(); + self.write_reg(dt, self.read_reg(a0).cast::() as i64); } - ADDFI => binary_op_imm!(self, as_f64, ops::Add::add), - MULFI => binary_op_imm!(self, as_f64, ops::Mul::mul), + ADDFI => self.binary_op_imm::(ops::Add::add), + MULFI => self.binary_op_imm::(ops::Mul::mul), op => return Err(VmRunError::InvalidOpcode(op)), } } @@ -392,6 +328,55 @@ impl<'a, PfHandler: HandlePageFault, const TIMER_QUOTIENT: usize> } } + /// Decode instruction operands + #[inline] + unsafe fn decode(&mut self) -> T { + let data = self.program.as_ptr().add(self.pc + 1).cast::().read(); + self.pc += 1 + core::mem::size_of::(); + data + } + + /// Perform binary operating over two registers + #[inline] + unsafe fn binary_op(&mut self, op: impl Fn(T, T) -> T) { + let ParamBBB(tg, a0, a1) = self.decode(); + self.write_reg( + tg, + op(self.read_reg(a0).cast::(), self.read_reg(a1).cast::()), + ); + } + + /// Perform binary operation over register and immediate + #[inline] + unsafe fn binary_op_imm(&mut self, op: impl Fn(T, T) -> T) { + let ParamBBD(tg, reg, imm) = self.decode(); + self.write_reg( + tg, + op(self.read_reg(reg).cast::(), Value::from(imm).cast::()), + ); + } + + /// Perform binary operation over register and shift immediate + #[inline] + unsafe fn binary_op_ims(&mut self, op: impl Fn(T, u32) -> T) { + let ParamBBW(tg, reg, imm) = self.decode(); + self.write_reg(tg, op(self.read_reg(reg).cast::(), imm)); + } + + /// Jump at `#3` if ordering on `#0 <=> #1` is equal to expected + #[inline] + unsafe fn cond_jmp(&mut self, expected: Ordering) { + let ParamBBD(a0, a1, ja) = self.decode(); + if self + .read_reg(a0) + .cast::() + .cmp(&self.read_reg(a1).cast::()) + == expected + { + self.pc = ja as usize; + } + } + /// Read register #[inline] unsafe fn read_reg(&self, n: u8) -> Value { diff --git a/hbvm/src/vm/value.rs b/hbvm/src/vm/value.rs index ffc069c1..fb8738e3 100644 --- a/hbvm/src/vm/value.rs +++ b/hbvm/src/vm/value.rs @@ -1,6 +1,6 @@ //! HoleyBytes register value definition -use core::fmt::Debug; +use sealed::sealed; /// Define [`Value`] union /// @@ -16,15 +16,6 @@ macro_rules! value_def { $(pub $ty: $ty),* } - paste::paste! { - impl Value {$( - #[doc = "Byte-reinterpret [`Value`] as [`" $ty "`]"] - #[inline] - pub fn [](&self) -> $ty { - unsafe { self.$ty } - } - )*} - } $( impl From<$ty> for Value { @@ -33,16 +24,41 @@ macro_rules! value_def { Self { $ty: value } } } + + static_assertions::const_assert_eq!( + core::mem::size_of::<$ty>(), + core::mem::size_of::(), + ); + + #[sealed] + unsafe impl ValueVariant for $ty {} )* }; } +impl Value { + #[inline] + pub fn cast(self) -> Variant { + union Transmute { + src: Value, + variant: Variant, + } + + unsafe { Transmute { src: self }.variant } + } +} + +/// # Safety +/// - N/A, not to be implemented manually +#[sealed] +pub unsafe trait ValueVariant: Copy + Into {} + value_def!(u64, i64, f64); static_assertions::const_assert_eq!(core::mem::size_of::(), 8); -impl Debug for Value { +impl core::fmt::Debug for Value { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { // Print formatted as hexadecimal, unsigned integer - write!(f, "{:x}", self.as_u64()) + write!(f, "{:x}", self.cast::()) } } diff --git a/spec.md b/spec.md index b9909e67..67094ea2 100644 --- a/spec.md +++ b/spec.md @@ -21,6 +21,7 @@ | BBB | 24 bits | | BBDH | 96 bits | | BBD | 80 bits | +| BBW | 48 bits | | BB | 16 bits | | BD | 72 bits | | D | 64 bits | @@ -99,6 +100,11 @@ | 18 | ANDI | Bitand | | 19 | ORI | Bitor | | 20 | XORI | Bitxor | + +### Bitshifts +- Type BBW +| Opcode | Name | Action | +|:------:|:----:|:-----------------------:| | 21 | SLI | Unsigned left bitshift | | 22 | SRI | Unsigned right bitshift | | 23 | SRSI | Signed right bitshift | From d20447dd15d82f186f6f3efe5a8bd54157548c0f Mon Sep 17 00:00:00 2001 From: Erin Date: Mon, 24 Jul 2023 20:41:10 +0200 Subject: [PATCH 020/116] Kekw --- hbasm/src/lib.rs | 2 +- hbasm/src/macros/asm.rs | 7 +++++++ hbasm/src/macros/text.rs | 9 +++++++++ hbvm/src/validate.rs | 6 ++++-- 4 files changed, 21 insertions(+), 3 deletions(-) diff --git a/hbasm/src/lib.rs b/hbasm/src/lib.rs index 022f8d07..62d13717 100644 --- a/hbasm/src/lib.rs +++ b/hbasm/src/lib.rs @@ -24,7 +24,7 @@ macros::impl_both!( => [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: u16) + 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, diff --git a/hbasm/src/macros/asm.rs b/hbasm/src/macros/asm.rs index 74e0e8f7..98db69f0 100644 --- a/hbasm/src/macros/asm.rs +++ b/hbasm/src/macros/asm.rs @@ -38,6 +38,12 @@ macro_rules! impl_asm_insert { Imm::insert(&$id, $self) }; + // Length - cannot be more than 2048 + ($self:expr, $id:ident, L) => {{ + assert!($id <= 2048); + $self.buf.extend($id.to_le_bytes()) + }}; + // Other numbers, just insert their bytes, little endian ($self:expr, $id:ident, $_:ident) => { $self.buf.extend($id.to_le_bytes()) @@ -76,6 +82,7 @@ macro_rules! impl_asm { macro_rules! ident_map_ty { (R) => { u8 }; // Register is just u8 (I) => { impl Imm }; // Immediate is anything implementing the trait + (L) => { u16 }; // Copy count ($id:ident) => { $id }; // Anything else → identity map } diff --git a/hbasm/src/macros/text.rs b/hbasm/src/macros/text.rs index d26e7e1f..22f82061 100644 --- a/hbasm/src/macros/text.rs +++ b/hbasm/src/macros/text.rs @@ -226,6 +226,15 @@ macro_rules! gen_extract { extract_pat!($self, Token::Register($id)); }; + ($self:expr, L, $id:ident) => { + extract_pat!($self, Token::Integer($id)); + if $id > 2048 { + return Err(ErrorKind::InvalidToken); + } + + let $id = u16::try_from($id).unwrap(); + }; + // Immediate ($self:expr, I, $id:ident) => { let $id = match $self.next()? { diff --git a/hbvm/src/validate.rs b/hbvm/src/validate.rs index 103a2d85..81f9a0a8 100644 --- a/hbvm/src/validate.rs +++ b/hbvm/src/validate.rs @@ -48,8 +48,10 @@ pub fn validate(mut program: &[u8]) -> Result<(), Error> { // Match on instruction types and perform necessary checks program = match program { [] => return Ok(()), - [LD..=ST, reg, _, _, _, _, _, _, _, _, _, count, ..] - if usize::from(*reg) * 8 + usize::from(*count) > 2048 => + [LD..=ST, reg, _, _, _, _, _, _, _, _, count_0, count_1, ..] + if usize::from(*reg) * 8 + + usize::from(u16::from_le_bytes([*count_0, *count_1])) + > 2048 => { return Err(Error { kind: ErrorKind::RegisterArrayOverflow, From b3a6c42af343ef0c8c871e0f07e5d32586a269fd Mon Sep 17 00:00:00 2001 From: Erin Date: Tue, 25 Jul 2023 14:41:54 +0200 Subject: [PATCH 021/116] Added notice --- hbbytecode/hbbytecode.h | 3 +++ 1 file changed, 3 insertions(+) diff --git a/hbbytecode/hbbytecode.h b/hbbytecode/hbbytecode.h index 5694afac..0dabc8aa 100644 --- a/hbbytecode/hbbytecode.h +++ b/hbbytecode/hbbytecode.h @@ -1,5 +1,8 @@ /* HoleyBytes Bytecode representation in C * Requires C23 compiler or better + * + * Uses MSVC pack pragma extension, + * proved to work with Clang and GNU® GCC™. */ #pragma once From a82686ec074a0d75cd18c808c84878eb340bd9c9 Mon Sep 17 00:00:00 2001 From: Erin Date: Tue, 25 Jul 2023 19:10:00 +0200 Subject: [PATCH 022/116] Fixed endian stuffs --- hbvm/src/validate.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hbvm/src/validate.rs b/hbvm/src/validate.rs index 81f9a0a8..d81b2d58 100644 --- a/hbvm/src/validate.rs +++ b/hbvm/src/validate.rs @@ -50,7 +50,7 @@ pub fn validate(mut program: &[u8]) -> Result<(), Error> { [] => return Ok(()), [LD..=ST, reg, _, _, _, _, _, _, _, _, count_0, count_1, ..] if usize::from(*reg) * 8 - + usize::from(u16::from_le_bytes([*count_0, *count_1])) + + usize::from(u16::from_le_bytes([*count_1, *count_0])) > 2048 => { return Err(Error { From f4c55ae3cc9b62e34f19a3108d5854917d2bcf05 Mon Sep 17 00:00:00 2001 From: able Date: Tue, 25 Jul 2023 12:20:35 -0500 Subject: [PATCH 023/116] changes I GUESS --- hbasm/assets/memory.hbasm | 3 +++ hbvm/assets/memory.hb | Bin 0 -> 46 bytes hbvm/src/main.rs | 7 ++++++- 3 files changed, 9 insertions(+), 1 deletion(-) create mode 100644 hbasm/assets/memory.hbasm create mode 100644 hbvm/assets/memory.hb diff --git a/hbasm/assets/memory.hbasm b/hbasm/assets/memory.hbasm new file mode 100644 index 00000000..133def5f --- /dev/null +++ b/hbasm/assets/memory.hbasm @@ -0,0 +1,3 @@ +li r20, 1010 +st r20, r24, 0, 1 +addi r24, r0, 10 \ No newline at end of file diff --git a/hbvm/assets/memory.hb b/hbvm/assets/memory.hb new file mode 100644 index 0000000000000000000000000000000000000000..9fe357520153a5f5ccf15f5a6793ae05728d0d3f GIT binary patch literal 46 ecmb0U`NYfs267@25C#JSBZGhh0~d^kL<0apSOKE| literal 0 HcmV?d00001 diff --git a/hbvm/src/main.rs b/hbvm/src/main.rs index a680dd83..28ec8ced 100644 --- a/hbvm/src/main.rs +++ b/hbvm/src/main.rs @@ -36,7 +36,12 @@ fn main() -> Result<(), Box> { println!("Program interrupt: {:?}", vm.run()); println!("{:?}", vm.registers); - std::alloc::dealloc(data, std::alloc::Layout::from_size_align_unchecked(4096, 4096)); + + println!("{:?}", core::slice::from_raw_parts(data, 4096)); + std::alloc::dealloc( + data, + std::alloc::Layout::from_size_align_unchecked(4096, 4096), + ); vm.memory.unmap(0).unwrap(); } } From ac149a5319cbeefe7ab15780c4cb751ba3e88190 Mon Sep 17 00:00:00 2001 From: Erin Date: Tue, 25 Jul 2023 22:44:08 +0200 Subject: [PATCH 024/116] Commented valider --- hbvm/src/validate.rs | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/hbvm/src/validate.rs b/hbvm/src/validate.rs index d81b2d58..24c042cd 100644 --- a/hbvm/src/validate.rs +++ b/hbvm/src/validate.rs @@ -25,8 +25,8 @@ pub struct Error { /// Perform bytecode validation. If it passes, the program should be /// sound to execute. pub fn validate(mut program: &[u8]) -> Result<(), Error> { - use hbbytecode::opcode::*; - + // 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, @@ -34,6 +34,7 @@ pub fn validate(mut program: &[u8]) -> Result<(), Error> { }); } + // Verify that program ends with 12 zeroes for (index, item) in program.iter().enumerate().skip(program.len() - 12) { if *item != 0 { return Err(Error { @@ -45,9 +46,14 @@ pub fn validate(mut program: &[u8]) -> Result<(), Error> { 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])) @@ -58,6 +64,8 @@ pub fn validate(mut program: &[u8]) -> Result<(), Error> { 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() => { @@ -66,6 +74,8 @@ pub fn validate(mut program: &[u8]) -> Result<(), Error> { index: (program.as_ptr() as usize) - (start.as_ptr() as usize), }) } + + // Valid instructions [UN | NOP | ECALL, rest @ ..] | [DIR | DIRF, _, _, _, _, rest @ ..] | [ADD..=CMPU | BRC | ADDF..=MULF, _, _, _, rest @ ..] @@ -74,6 +84,8 @@ pub fn validate(mut program: &[u8]) -> Result<(), Error> { | [ADDI..=XORI | CMPI..=CMPUI | BMC | JAL..=JGTU | ADDFI..=MULFI, _, _, _, _, _, _, _, _, _, _, rest @ ..] | [SLI..=SRSI, _, _, _, _, rest @ ..] | [LD..=ST, _, _, _, _, _, _, _, _, _, _, _, _, rest @ ..] => rest, + + // The rest _ => { return Err(Error { kind: ErrorKind::InvalidInstruction, From 92793dc93b069b26139bb2d89aadbaf0b40dc8f6 Mon Sep 17 00:00:00 2001 From: Erin Date: Tue, 25 Jul 2023 23:03:06 +0200 Subject: [PATCH 025/116] Quick valider fix --- hbvm/src/validate.rs | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/hbvm/src/validate.rs b/hbvm/src/validate.rs index 24c042cd..3bc7d81d 100644 --- a/hbvm/src/validate.rs +++ b/hbvm/src/validate.rs @@ -47,7 +47,6 @@ pub fn validate(mut program: &[u8]) -> Result<(), Error> { let start = program; loop { use hbbytecode::opcode::*; - // Match on instruction types and perform necessary checks program = match program { // End of program @@ -76,14 +75,16 @@ pub fn validate(mut program: &[u8]) -> Result<(), Error> { } // Valid instructions - [UN | NOP | ECALL, rest @ ..] - | [DIR | DIRF, _, _, _, _, rest @ ..] - | [ADD..=CMPU | BRC | ADDF..=MULF, _, _, _, rest @ ..] - | [NEG..=NOT | CP..=SWA | NEGF..=FTI, _, _, rest @ ..] - | [LI, _, _, _, _, _, _, _, _, _, rest @ ..] - | [ADDI..=XORI | CMPI..=CMPUI | BMC | JAL..=JGTU | ADDFI..=MULFI, _, _, _, _, _, _, _, _, _, _, rest @ ..] - | [SLI..=SRSI, _, _, _, _, rest @ ..] - | [LD..=ST, _, _, _, _, _, _, _, _, _, _, _, _, rest @ ..] => rest, + [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 _ => { From 759514686aacde377ee3f8b394bda2d02f1c4aa4 Mon Sep 17 00:00:00 2001 From: Erin Date: Tue, 25 Jul 2023 23:43:06 +0200 Subject: [PATCH 026/116] Valider is now generated from macro (not done yet) --- hbasm/src/lib.rs | 20 +---- hbasm/src/macros/mod.rs | 16 ++-- hbbytecode/src/gen_valider.rs | 158 ++++++++++++++++++++++++++++++++++ hbbytecode/src/lib.rs | 30 +++++++ hbvm/src/lib.rs | 1 - hbvm/src/main.rs | 3 +- hbvm/src/validate.rs | 98 --------------------- hbvm/src/vm/mod.rs | 10 ++- 8 files changed, 205 insertions(+), 131 deletions(-) create mode 100644 hbbytecode/src/gen_valider.rs delete mode 100644 hbvm/src/validate.rs diff --git a/hbasm/src/lib.rs b/hbasm/src/lib.rs index 62d13717..11b1937d 100644 --- a/hbasm/src/lib.rs +++ b/hbasm/src/lib.rs @@ -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 diff --git a/hbasm/src/macros/mod.rs b/hbasm/src/macros/mod.rs index 55d770b7..db4ca008 100644 --- a/hbasm/src/macros/mod.rs +++ b/hbasm/src/macros/mod.rs @@ -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; diff --git a/hbbytecode/src/gen_valider.rs b/hbbytecode/src/gen_valider.rs new file mode 100644 index 00000000..42e053f5 --- /dev/null +++ b/hbbytecode/src/gen_valider.rs @@ -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}; diff --git a/hbbytecode/src/lib.rs b/hbbytecode/src/lib.rs index 1cf0b21d..08cb345a 100644 --- a/hbbytecode/src/lib.rs +++ b/hbbytecode/src/lib.rs @@ -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 diff --git a/hbvm/src/lib.rs b/hbvm/src/lib.rs index 82559847..1feaa738 100644 --- a/hbvm/src/lib.rs +++ b/hbvm/src/lib.rs @@ -3,5 +3,4 @@ extern crate alloc; -pub mod validate; pub mod vm; diff --git a/hbvm/src/main.rs b/hbvm/src/main.rs index 28ec8ced..b9d7a241 100644 --- a/hbvm/src/main.rs +++ b/hbvm/src/main.rs @@ -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}, }; diff --git a/hbvm/src/validate.rs b/hbvm/src/validate.rs deleted file mode 100644 index 3bc7d81d..00000000 --- a/hbvm/src/validate.rs +++ /dev/null @@ -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), - }) - } - } - } -} diff --git a/hbvm/src/vm/mod.rs b/hbvm/src/vm/mod.rs index 797d396f..1da55c95 100644 --- a/hbvm/src/vm/mod.rs +++ b/hbvm/src/vm/mod.rs @@ -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 { - validate::validate(program)?; + pub fn new_validated(program: &'a [u8], traph: PfHandler) -> Result { + 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)), From 668b324cc88dced28c1905ff006fa006c9481975 Mon Sep 17 00:00:00 2001 From: Erin Date: Tue, 25 Jul 2023 23:47:51 +0200 Subject: [PATCH 027/116] Valider --- hbbytecode/src/gen_valider.rs | 4 +++- hbbytecode/src/lib.rs | 3 +++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/hbbytecode/src/gen_valider.rs b/hbbytecode/src/gen_valider.rs index 42e053f5..a1223c12 100644 --- a/hbbytecode/src/gen_valider.rs +++ b/hbbytecode/src/gen_valider.rs @@ -90,7 +90,9 @@ macro_rules! gen_valider { $crate::gen_valider::inst_chk!( rest, $ityn, $($opcode),* ) - )|* => rest, + )|*| $crate::gen_valider::inst_chk!( + rest, bbb, BRC + ) => rest, // The plebs _ => { diff --git a/hbbytecode/src/lib.rs b/hbbytecode/src/lib.rs index 08cb345a..4919eba8 100644 --- a/hbbytecode/src/lib.rs +++ b/hbbytecode/src/lib.rs @@ -18,6 +18,9 @@ macro_rules! constmod { } /// Invoke macro with bytecode definition format +/// +/// Keep in mind BRC instruction is special-cased and you have to implement +/// it manually. #[macro_export] macro_rules! invoke_with_def { ($macro:path) => { From 0f5d78bb27d5748ea1da6ab17e2ce3f792ead503 Mon Sep 17 00:00:00 2001 From: Erin Date: Tue, 25 Jul 2023 23:48:59 +0200 Subject: [PATCH 028/116] whoops, fixed builds. --- hbvm/src/vm/mod.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/hbvm/src/vm/mod.rs b/hbvm/src/vm/mod.rs index 1da55c95..b1808f89 100644 --- a/hbvm/src/vm/mod.rs +++ b/hbvm/src/vm/mod.rs @@ -250,7 +250,6 @@ 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)), From 5055626968d5fc19f81e1391216b411edd3348f3 Mon Sep 17 00:00:00 2001 From: Erin Date: Wed, 26 Jul 2023 00:01:25 +0200 Subject: [PATCH 029/116] Added runtime bound checking --- hbbytecode/src/gen_valider.rs | 6 ++++-- hbvm/src/vm/mod.rs | 21 +++++++++++++++++++++ 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/hbbytecode/src/gen_valider.rs b/hbbytecode/src/gen_valider.rs index a1223c12..f82a0172 100644 --- a/hbbytecode/src/gen_valider.rs +++ b/hbbytecode/src/gen_valider.rs @@ -59,14 +59,16 @@ macro_rules! gen_valider { let start = program; loop { use crate::opcode::*; + extern crate std; 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, ..] + // B B D1 D2 D3 D4 D5 D6 D7 D8 H1 H2 + [LD..=ST, reg, _, _, _, _, _, _, _, _, _, count_0, count_1, ..] if usize::from(*reg) * 8 - + usize::from(u16::from_le_bytes([*count_1, *count_0])) + + usize::from(u16::from_le_bytes([*count_0, *count_1])) > 2048 => { return Err(Error { diff --git a/hbvm/src/vm/mod.rs b/hbvm/src/vm/mod.rs index b1808f89..fcbfacb6 100644 --- a/hbvm/src/vm/mod.rs +++ b/hbvm/src/vm/mod.rs @@ -215,6 +215,8 @@ impl<'a, PfHandler: HandlePageFault, const TIMER_QUOTIENT: usize> LD => { // Load. If loading more than register size, continue on adjecent registers let ParamBBDH(dst, base, off, count) = self.decode(); + ldst_bound_check(dst, count)?; + let n: usize = match dst { 0 => 1, _ => 0, @@ -230,6 +232,8 @@ impl<'a, PfHandler: HandlePageFault, const TIMER_QUOTIENT: usize> ST => { // Store. Same rules apply as to LD let ParamBBDH(dst, base, off, count) = self.decode(); + ldst_bound_check(dst, count)?; + self.memory.store( self.read_reg(base).cast::() + off, self.registers.as_ptr().add(usize::from(dst)).cast(), @@ -250,6 +254,10 @@ impl<'a, PfHandler: HandlePageFault, const TIMER_QUOTIENT: usize> BRC => { // Block register copy let ParamBBB(src, dst, count) = self.decode(); + if src.checked_add(count).is_none() || dst.checked_add(count).is_none() { + return Err(VmRunError::RegOutOfBounds); + } + core::ptr::copy( self.registers.get_unchecked(usize::from(src)), self.registers.get_unchecked_mut(usize::from(dst)), @@ -394,6 +402,16 @@ impl<'a, PfHandler: HandlePageFault, const TIMER_QUOTIENT: usize> } } +/// Load/Store target/source register range bound checking +#[inline] +fn ldst_bound_check(reg: u8, size: u16) -> Result<(), VmRunError> { + if usize::from(reg) * 8 + usize::from(size) > 2048 { + Err(VmRunError::RegOutOfBounds) + } else { + Ok(()) + } +} + /// Virtual machine halt error #[derive(Copy, Clone, Debug, PartialEq, Eq)] #[repr(u8)] @@ -407,6 +425,9 @@ pub enum VmRunError { /// Unhandled store access exception StoreAccessEx(u64), + /// Register out-of-bounds access + RegOutOfBounds, + /// Reached unreachable code Unreachable, } From 077da50787871a38e5f865f142a643355763d693 Mon Sep 17 00:00:00 2001 From: Erin Date: Wed, 26 Jul 2023 00:12:50 +0200 Subject: [PATCH 030/116] Reworked macros --- hbasm/src/lib.rs | 23 +++++++++------- hbasm/src/macros/mod.rs | 52 ----------------------------------- hbasm/src/macros/text.rs | 36 +++++++++++++++++------- hbbytecode/src/gen_valider.rs | 4 +-- hbbytecode/src/lib.rs | 21 +++++++++++--- 5 files changed, 57 insertions(+), 79 deletions(-) diff --git a/hbasm/src/lib.rs b/hbasm/src/lib.rs index 11b1937d..cf7da9e0 100644 --- a/hbasm/src/lib.rs +++ b/hbasm/src/lib.rs @@ -1,3 +1,14 @@ +//! Holey Bytes Assembler +//! +//! 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. +//! +//! So this crate contains both assembleer with API for programs and a text assembler +//! for humans to write + #![no_std] extern crate alloc; @@ -17,18 +28,10 @@ pub struct Assembler { pub sub: HashSet, } - -// Implement both assembler and generate module for text-code-based one -hbbytecode::invoke_with_def!(macros::impl_all); +hbbytecode::invoke_with_def!(macros::text::gen_text); impl Assembler { - // Special-cased for text-assembler - // - // `p2` is not a register, but the instruction is still BBB - #[inline(always)] - pub fn i_brc(&mut self, p0: u8, p1: u8, p2: u8) { - self.i_param_bbb(hbbytecode::opcode::BRC, p0, p1, p2) - } + hbbytecode::invoke_with_def!(macros::asm::impl_asm); /// Append 12 zeroes (UN) at the end /// diff --git a/hbasm/src/macros/mod.rs b/hbasm/src/macros/mod.rs index db4ca008..509f6be5 100644 --- a/hbasm/src/macros/mod.rs +++ b/hbasm/src/macros/mod.rs @@ -4,55 +4,3 @@ pub mod asm; 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_all!( -/// INSTRUCTION_TYPE(p0: TYPE, p1: TYPE, …) -/// => [INSTRUCTION_A, INSTRUCTION_B, …], -/// … -/// ); -/// ``` -/// - Instruction type determines opcode-generic, instruction-type-specific -/// function. Name: `i_param_INSTRUCTION_TYPE` -/// - Per-instructions there will be generated opcode-specific functions calling the generic ones -/// - Operand types -/// - 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 -/// ```text -/// instruction op1, op2, … -/// … -/// ``` -/// - Opcode names are lowercase -/// - Registers are prefixed with `r` followed by number -/// - Operands are separated by `,` -/// - Instructions are separated by either line feed or `;` (αυτό δεν είναι ερωτηματικό!) -/// - 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_all { - ($($tt:tt)*) => { - impl Assembler { - $crate::macros::asm::impl_asm!($($tt)*); - } - - $crate::macros::text::gen_text!($($tt)*); - }; -} - -pub(crate) use impl_all; diff --git a/hbasm/src/macros/text.rs b/hbasm/src/macros/text.rs index 22f82061..89b360b2 100644 --- a/hbasm/src/macros/text.rs +++ b/hbasm/src/macros/text.rs @@ -10,7 +10,21 @@ macro_rules! gen_text { => [$($opcode:ident),* $(,)?], )* ) => { - /// Text code based assembler + /// # Text assembler + /// Text assembler generated simply calls methods in the [`crate::Assembler`] type. + /// + /// # Syntax + /// ```text + /// instruction op1, op2, … + /// … + /// ``` + /// - Opcode names are lowercase + /// - Registers are prefixed with `r` followed by number + /// - Operands are separated by `,` + /// - Instructions are separated by either line feed or `;` (αυτό δεν είναι ερωτηματικό!) + /// - 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 pub mod text { use { crate::{ @@ -30,7 +44,6 @@ macro_rules! gen_text { #[logos(skip r"-- .*")] pub enum Token { $($(#[token(~([<$opcode:lower>]), |_| hbbytecode::opcode::[<$opcode:upper>])])*)* - #[token("brc", |_| hbbytecode::opcode::BRC)] // Special-cased Opcode(u8), #[regex("[0-9]+", |l| l.slice().parse().ok())] @@ -105,13 +118,6 @@ macro_rules! gen_text { // Got an opcode Some(Ok(Token::Opcode(op))) => { match op { - // Take all the opcodes and match them to their corresponding functions - $( - $(hbbytecode::opcode::$opcode)|* => paste::paste!({ - param_extract_itm!(self, $($param_i: $param_ty),*); - self.asm.[](op, $($param_i),*); - }), - )* // Special-cased hbbytecode::opcode::BRC => { param_extract_itm!( @@ -122,7 +128,17 @@ macro_rules! gen_text { ); self.asm.i_param_bbb(op, p0, p1, p2); - } + }, + + // Take all the opcodes and match them to their corresponding functions + $( + #[allow(unreachable_patterns)] + $(hbbytecode::opcode::$opcode)|* => paste::paste!({ + param_extract_itm!(self, $($param_i: $param_ty),*); + self.asm.[](op, $($param_i),*); + }), + )* + // Already matched in Logos, should not be able to obtain // invalid opcode. _ => unreachable!(), diff --git a/hbbytecode/src/gen_valider.rs b/hbbytecode/src/gen_valider.rs index f82a0172..a3f01936 100644 --- a/hbbytecode/src/gen_valider.rs +++ b/hbbytecode/src/gen_valider.rs @@ -92,9 +92,7 @@ macro_rules! gen_valider { $crate::gen_valider::inst_chk!( rest, $ityn, $($opcode),* ) - )|*| $crate::gen_valider::inst_chk!( - rest, bbb, BRC - ) => rest, + )|* => rest, // The plebs _ => { diff --git a/hbbytecode/src/lib.rs b/hbbytecode/src/lib.rs index 4919eba8..0c896730 100644 --- a/hbbytecode/src/lib.rs +++ b/hbbytecode/src/lib.rs @@ -18,9 +18,22 @@ macro_rules! constmod { } /// Invoke macro with bytecode definition format -/// -/// Keep in mind BRC instruction is special-cased and you have to implement -/// it manually. +/// # Input syntax +/// ```no_run +/// macro!( +/// INSTRUCTION_TYPE(p0: TYPE, p1: TYPE, …) +/// => [INSTRUCTION_A, INSTRUCTION_B, …], +/// … +/// ); +/// ``` +/// - Instruction type determines opcode-generic, instruction-type-specific +/// function. Name: `i_param_INSTRUCTION_TYPE` +/// - Per-instructions there will be generated opcode-specific functions calling the generic ones +/// - Operand types +/// - R: Register (u8) +/// - I: Immediate (implements [`crate::Imm`] trait) +/// - L: Memory load / store size (u16) +/// - Other types are identity-mapped #[macro_export] macro_rules! invoke_with_def { ($macro:path) => { @@ -28,7 +41,7 @@ macro_rules! invoke_with_def { 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], + => [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) From 66f634a70fb8e651039b4d41ec3b67199aa242cf Mon Sep 17 00:00:00 2001 From: Erin Date: Wed, 26 Jul 2023 00:16:50 +0200 Subject: [PATCH 031/116] Added warning --- hbbytecode/src/lib.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/hbbytecode/src/lib.rs b/hbbytecode/src/lib.rs index 0c896730..6ea444f7 100644 --- a/hbbytecode/src/lib.rs +++ b/hbbytecode/src/lib.rs @@ -34,6 +34,13 @@ macro_rules! constmod { /// - I: Immediate (implements [`crate::Imm`] trait) /// - L: Memory load / store size (u16) /// - Other types are identity-mapped +/// +/// # BRC special-case +/// BRC's 3rd operand is plain byte, not a register. Encoding is the same, but for some cases it may matter. +/// +/// Please, if you distinguish in your API between byte and register, special case this one. +/// +/// Sorry for that :( #[macro_export] macro_rules! invoke_with_def { ($macro:path) => { From 95c979cb83c5eef01016a602f92ecca1b9747bf9 Mon Sep 17 00:00:00 2001 From: Erin Date: Wed, 26 Jul 2023 00:17:10 +0200 Subject: [PATCH 032/116] a --- hbbytecode/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hbbytecode/src/lib.rs b/hbbytecode/src/lib.rs index 6ea444f7..36c3a370 100644 --- a/hbbytecode/src/lib.rs +++ b/hbbytecode/src/lib.rs @@ -17,7 +17,7 @@ macro_rules! constmod { }; } -/// Invoke macro with bytecode definition format +/// Invoke macro with bytecode definition /// # Input syntax /// ```no_run /// macro!( From f2ec9a3034862ab502e2c29ecfd110be2f457e29 Mon Sep 17 00:00:00 2001 From: Erin Date: Wed, 26 Jul 2023 01:01:53 +0200 Subject: [PATCH 033/116] Added fuzzy tests --- hbvm/fuzz/.gitignore | 5 +++++ hbvm/fuzz/Cargo.toml | 27 +++++++++++++++++++++++++++ hbvm/fuzz/fuzz_targets/vm.rs | 29 +++++++++++++++++++++++++++++ 3 files changed, 61 insertions(+) create mode 100644 hbvm/fuzz/.gitignore create mode 100644 hbvm/fuzz/Cargo.toml create mode 100644 hbvm/fuzz/fuzz_targets/vm.rs diff --git a/hbvm/fuzz/.gitignore b/hbvm/fuzz/.gitignore new file mode 100644 index 00000000..615384a8 --- /dev/null +++ b/hbvm/fuzz/.gitignore @@ -0,0 +1,5 @@ +target +artifacts +corpus +coverage +Cargo.lock \ No newline at end of file diff --git a/hbvm/fuzz/Cargo.toml b/hbvm/fuzz/Cargo.toml new file mode 100644 index 00000000..f6bc6164 --- /dev/null +++ b/hbvm/fuzz/Cargo.toml @@ -0,0 +1,27 @@ +[package] +name = "hbvm-fuzz" +version = "0.0.0" +publish = false +edition = "2021" + +[package.metadata] +cargo-fuzz = true + +[dependencies] +libfuzzer-sys = "0.4" + +[dependencies.hbvm] +path = ".." + +# Prevent this from interfering with workspaces +[workspace] +members = ["."] + +[profile.release] +debug = 1 + +[[bin]] +name = "vm" +path = "fuzz_targets/vm.rs" +test = false +doc = false diff --git a/hbvm/fuzz/fuzz_targets/vm.rs b/hbvm/fuzz/fuzz_targets/vm.rs new file mode 100644 index 00000000..13719030 --- /dev/null +++ b/hbvm/fuzz/fuzz_targets/vm.rs @@ -0,0 +1,29 @@ +#![no_main] + +use { + hbvm::vm::{ + mem::{HandlePageFault, Memory, MemoryAccessReason, PageSize}, + Vm, + }, + libfuzzer_sys::fuzz_target, +}; + +fuzz_target!(|data: &[u8]| { + if let Ok(mut vm) = Vm::<_, 0>::new_validated(data, TestTrapHandler) { + let _ = vm.run(); + } +}); + +struct TestTrapHandler; +impl HandlePageFault for TestTrapHandler { + fn page_fault( + &mut self, + _: MemoryAccessReason, + _: &mut Memory, + _: u64, + _: PageSize, + _: *mut u8, + ) -> bool { + false + } +} From 65888377694451f9fd79ed1f690a2f17ab8d0e16 Mon Sep 17 00:00:00 2001 From: Erin Date: Wed, 26 Jul 2023 01:11:21 +0200 Subject: [PATCH 034/116] restruct + no-alloc support --- Cargo.lock | 1 - hbvm/Cargo.toml | 5 +- hbvm/fuzz/fuzz_targets/vm.rs | 4 +- hbvm/src/lib.rs | 456 ++++++++++++++++++++++++++++- hbvm/src/main.rs | 11 +- hbvm/src/{vm => }/mem/mod.rs | 14 +- hbvm/src/{vm => }/mem/paging.rs | 0 hbvm/src/{vm => }/mem/pfhandler.rs | 0 hbvm/src/{vm => }/value.rs | 0 hbvm/src/vm/mod.rs | 446 ---------------------------- 10 files changed, 476 insertions(+), 461 deletions(-) rename hbvm/src/{vm => }/mem/mod.rs (98%) rename hbvm/src/{vm => }/mem/paging.rs (100%) rename hbvm/src/{vm => }/mem/pfhandler.rs (100%) rename hbvm/src/{vm => }/value.rs (100%) delete mode 100644 hbvm/src/vm/mod.rs diff --git a/Cargo.lock b/Cargo.lock index 708a8680..4cb5d256 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -126,7 +126,6 @@ version = "0.1.0" dependencies = [ "delegate", "derive_more", - "hashbrown 0.13.2", "hbbytecode", "log", "paste", diff --git a/hbvm/Cargo.toml b/hbvm/Cargo.toml index 0346a40e..87b74fa2 100644 --- a/hbvm/Cargo.toml +++ b/hbvm/Cargo.toml @@ -6,10 +6,13 @@ edition = "2021" [profile.release] lto = true +[features] +default = ["alloc"] +alloc = [] + [dependencies] delegate = "0.9" derive_more = "0.99" -hashbrown = "0.13" hbbytecode.path = "../hbbytecode" log = "0.4" paste = "1.0" diff --git a/hbvm/fuzz/fuzz_targets/vm.rs b/hbvm/fuzz/fuzz_targets/vm.rs index 13719030..72e6da50 100644 --- a/hbvm/fuzz/fuzz_targets/vm.rs +++ b/hbvm/fuzz/fuzz_targets/vm.rs @@ -1,7 +1,7 @@ #![no_main] use { - hbvm::vm::{ + hbvm::{ mem::{HandlePageFault, Memory, MemoryAccessReason, PageSize}, Vm, }, @@ -9,7 +9,7 @@ use { }; fuzz_target!(|data: &[u8]| { - if let Ok(mut vm) = Vm::<_, 0>::new_validated(data, TestTrapHandler) { + if let Ok(mut vm) = Vm::<_, 0>::new_validated(data, TestTrapHandler, Default::default()) { let _ = vm.run(); } }); diff --git a/hbvm/src/lib.rs b/hbvm/src/lib.rs index 1feaa738..4fdf7a0f 100644 --- a/hbvm/src/lib.rs +++ b/hbvm/src/lib.rs @@ -1,6 +1,458 @@ -#![doc = include_str!("../README.md")] +//! HoleyBytes Virtual Machine +//! +//! # Alloc feature +//! - Enabled by default +//! - Provides [`mem::Memory`] mapping / unmapping, as well as +//! [`Default`] and [`Drop`] implementation + +// # General safety notice: +// - Validation has to assure there is 256 registers (r0 - r255) +// - Instructions have to be valid as specified (values and sizes) +// - Mapped pages should be at least 4 KiB + #![no_std] +#[cfg(feature = "alloc")] extern crate alloc; -pub mod vm; +pub mod mem; +pub mod value; + +use { + self::{mem::HandlePageFault, value::ValueVariant}, + core::{cmp::Ordering, ops}, + hbbytecode::{ + valider, OpParam, ParamBB, ParamBBB, ParamBBBB, ParamBBD, ParamBBDH, ParamBBW, ParamBD, + }, + mem::Memory, + value::Value, +}; + +/// HoleyBytes Virtual Machine +pub struct Vm<'a, PfHandler, const TIMER_QUOTIENT: usize> { + /// Holds 256 registers + /// + /// Writing to register 0 is considered undefined behaviour + /// in terms of HoleyBytes program execution + pub registers: [Value; 256], + + /// Memory implementation + pub memory: Memory, + + /// Trap handler + pub pfhandler: PfHandler, + + /// Program counter + pub pc: usize, + + /// Program + program: &'a [u8], + + /// Cached program length (without unreachable end) + program_len: usize, + + /// Program timer + timer: usize, +} + +impl<'a, PfHandler: HandlePageFault, const TIMER_QUOTIENT: usize> + Vm<'a, PfHandler, TIMER_QUOTIENT> +{ + /// Create a new VM with program and trap handler + /// + /// # Safety + /// Program code has to be validated + pub unsafe fn new_unchecked(program: &'a [u8], traph: PfHandler, memory: Memory) -> Self { + Self { + registers: [Value::from(0_u64); 256], + memory, + pfhandler: traph, + pc: 0, + program_len: program.len() - 12, + program, + timer: 0, + } + } + + /// Create a new VM with program and trap handler only if it passes validation + pub fn new_validated( + program: &'a [u8], + traph: PfHandler, + memory: Memory, + ) -> Result { + valider::validate(program)?; + Ok(unsafe { Self::new_unchecked(program, traph, memory) }) + } + + /// Execute program + /// + /// Program can return [`VmRunError`] if a trap handling failed + pub fn run(&mut self) -> Result { + use hbbytecode::opcode::*; + loop { + // Check instruction boundary + if self.pc >= self.program_len { + return Ok(VmRunOk::End); + } + + // Big match + // + // Contribution guide: + // - Zero register shall never be overwitten. It's value has to always be 0. + // - Prefer `Self::read_reg` and `Self::write_reg` functions + // - Extract parameters using `param!` macro + // - Prioritise speed over code size + // - Memory is cheap, CPUs not that much + // - Do not heap allocate at any cost + // - Yes, user-provided trap handler may allocate, + // but that is not our »fault«. + // - Unsafe is kinda must, but be sure you have validated everything + // - Your contributions have to pass sanitizers and Miri + // - Strictly follow the spec + // - The spec does not specify how you perform actions, in what order, + // just that the observable effects have to be performed in order and + // correctly. + // - Yes, we assume you run 64 bit CPU. Else ?conradluget a better CPU + // sorry 8 bit fans, HBVM won't run on your Speccy :( + unsafe { + match *self.program.get_unchecked(self.pc) { + UN => { + self.decode::<()>(); + return Err(VmRunError::Unreachable); + } + NOP => self.decode::<()>(), + ADD => self.binary_op(u64::wrapping_add), + SUB => self.binary_op(u64::wrapping_sub), + MUL => self.binary_op(u64::wrapping_mul), + AND => self.binary_op::(ops::BitAnd::bitand), + OR => self.binary_op::(ops::BitOr::bitor), + XOR => self.binary_op::(ops::BitXor::bitxor), + SL => self.binary_op(|l, r| u64::wrapping_shl(l, r as u32)), + SR => self.binary_op(|l, r| u64::wrapping_shr(l, r as u32)), + SRS => self.binary_op(|l, r| i64::wrapping_shl(l, r as u32)), + CMP => { + // Compare a0 <=> a1 + // < → -1 + // > → 1 + // = → 0 + + let ParamBBB(tg, a0, a1) = self.decode(); + self.write_reg( + tg, + self.read_reg(a0) + .cast::() + .cmp(&self.read_reg(a1).cast::()) + as i64, + ); + } + CMPU => { + // Unsigned comparsion + let ParamBBB(tg, a0, a1) = self.decode(); + self.write_reg( + tg, + self.read_reg(a0) + .cast::() + .cmp(&self.read_reg(a1).cast::()) + as i64, + ); + } + NOT => { + // Logical negation + let ParamBB(tg, a0) = self.decode(); + self.write_reg(tg, !self.read_reg(a0).cast::()); + } + NEG => { + // Bitwise negation + let ParamBB(tg, a0) = self.decode(); + self.write_reg( + tg, + match self.read_reg(a0).cast::() { + 0 => 1_u64, + _ => 0, + }, + ); + } + DIR => { + // Fused Division-Remainder + let ParamBBBB(dt, rt, a0, a1) = self.decode(); + let a0 = self.read_reg(a0).cast::(); + let a1 = self.read_reg(a1).cast::(); + self.write_reg(dt, a0.checked_div(a1).unwrap_or(u64::MAX)); + self.write_reg(rt, a0.checked_rem(a1).unwrap_or(u64::MAX)); + } + ADDI => self.binary_op_imm(u64::wrapping_add), + MULI => self.binary_op_imm(u64::wrapping_sub), + ANDI => self.binary_op_imm::(ops::BitAnd::bitand), + ORI => self.binary_op_imm::(ops::BitOr::bitor), + XORI => self.binary_op_imm::(ops::BitXor::bitxor), + SLI => self.binary_op_ims(u64::wrapping_shl), + SRI => self.binary_op_ims(u64::wrapping_shr), + SRSI => self.binary_op_ims(i64::wrapping_shr), + CMPI => { + let ParamBBD(tg, a0, imm) = self.decode(); + self.write_reg( + tg, + self.read_reg(a0) + .cast::() + .cmp(&Value::from(imm).cast::()) + as i64, + ); + } + CMPUI => { + let ParamBBD(tg, a0, imm) = self.decode(); + self.write_reg(tg, self.read_reg(a0).cast::().cmp(&imm) as i64); + } + CP => { + let ParamBB(tg, a0) = self.decode(); + self.write_reg(tg, self.read_reg(a0)); + } + SWA => { + // Swap registers + let ParamBB(r0, r1) = self.decode(); + match (r0, r1) { + (0, 0) => (), + (dst, 0) | (0, dst) => self.write_reg(dst, 0_u64), + (r0, r1) => { + core::ptr::swap( + self.registers.get_unchecked_mut(usize::from(r0)), + self.registers.get_unchecked_mut(usize::from(r1)), + ); + } + } + } + LI => { + let ParamBD(tg, imm) = self.decode(); + self.write_reg(tg, imm); + } + LD => { + // Load. If loading more than register size, continue on adjecent registers + let ParamBBDH(dst, base, off, count) = self.decode(); + ldst_bound_check(dst, count)?; + + let n: usize = match dst { + 0 => 1, + _ => 0, + }; + + self.memory.load( + self.read_reg(base).cast::() + off + n as u64, + self.registers.as_mut_ptr().add(usize::from(dst) + n).cast(), + usize::from(count).saturating_sub(n), + &mut self.pfhandler, + )?; + } + ST => { + // Store. Same rules apply as to LD + let ParamBBDH(dst, base, off, count) = self.decode(); + ldst_bound_check(dst, count)?; + + self.memory.store( + self.read_reg(base).cast::() + off, + self.registers.as_ptr().add(usize::from(dst)).cast(), + count.into(), + &mut self.pfhandler, + )?; + } + BMC => { + // Block memory copy + let ParamBBD(src, dst, count) = self.decode(); + self.memory.block_copy( + self.read_reg(src).cast::(), + self.read_reg(dst).cast::(), + count as _, + &mut self.pfhandler, + )?; + } + BRC => { + // Block register copy + let ParamBBB(src, dst, count) = self.decode(); + if src.checked_add(count).is_none() || dst.checked_add(count).is_none() { + return Err(VmRunError::RegOutOfBounds); + } + + core::ptr::copy( + self.registers.get_unchecked(usize::from(src)), + self.registers.get_unchecked_mut(usize::from(dst)), + usize::from(count), + ); + } + JAL => { + // Jump and link. Save PC after this instruction to + // specified register and jump to reg + offset. + let ParamBBD(save, reg, offset) = self.decode(); + self.write_reg(save, self.pc as u64); + self.pc = (self.read_reg(reg).cast::() + offset) as usize; + } + // Conditional jumps, jump only to immediates + JEQ => self.cond_jmp::(Ordering::Equal), + JNE => { + let ParamBBD(a0, a1, jt) = self.decode(); + if self.read_reg(a0).cast::() != self.read_reg(a1).cast::() { + self.pc = jt as usize; + } + } + JLT => self.cond_jmp::(Ordering::Less), + JGT => self.cond_jmp::(Ordering::Greater), + JLTU => self.cond_jmp::(Ordering::Less), + JGTU => self.cond_jmp::(Ordering::Greater), + ECALL => { + self.decode::<()>(); + + // So we don't get timer interrupt after ECALL + if TIMER_QUOTIENT != 0 { + self.timer = self.timer.wrapping_add(1); + } + return Ok(VmRunOk::Ecall); + } + ADDF => self.binary_op::(ops::Add::add), + SUBF => self.binary_op::(ops::Sub::sub), + MULF => self.binary_op::(ops::Mul::mul), + DIRF => { + let ParamBBBB(dt, rt, a0, a1) = self.decode(); + let a0 = self.read_reg(a0).cast::(); + let a1 = self.read_reg(a1).cast::(); + self.write_reg(dt, a0 / a1); + self.write_reg(rt, a0 % a1); + } + FMAF => { + let ParamBBBB(dt, a0, a1, a2) = self.decode(); + self.write_reg( + dt, + self.read_reg(a0).cast::() * self.read_reg(a1).cast::() + + self.read_reg(a2).cast::(), + ); + } + NEGF => { + let ParamBB(dt, a0) = self.decode(); + self.write_reg(dt, -self.read_reg(a0).cast::()); + } + ITF => { + let ParamBB(dt, a0) = self.decode(); + self.write_reg(dt, self.read_reg(a0).cast::() as f64); + } + FTI => { + let ParamBB(dt, a0) = self.decode(); + self.write_reg(dt, self.read_reg(a0).cast::() as i64); + } + ADDFI => self.binary_op_imm::(ops::Add::add), + MULFI => self.binary_op_imm::(ops::Mul::mul), + op => return Err(VmRunError::InvalidOpcode(op)), + } + } + + if TIMER_QUOTIENT != 0 { + self.timer = self.timer.wrapping_add(1); + if self.timer % TIMER_QUOTIENT == 0 { + return Ok(VmRunOk::Timer); + } + } + } + } + + /// Decode instruction operands + #[inline] + unsafe fn decode(&mut self) -> T { + let data = self.program.as_ptr().add(self.pc + 1).cast::().read(); + self.pc += 1 + core::mem::size_of::(); + data + } + + /// Perform binary operating over two registers + #[inline] + unsafe fn binary_op(&mut self, op: impl Fn(T, T) -> T) { + let ParamBBB(tg, a0, a1) = self.decode(); + self.write_reg( + tg, + op(self.read_reg(a0).cast::(), self.read_reg(a1).cast::()), + ); + } + + /// Perform binary operation over register and immediate + #[inline] + unsafe fn binary_op_imm(&mut self, op: impl Fn(T, T) -> T) { + let ParamBBD(tg, reg, imm) = self.decode(); + self.write_reg( + tg, + op(self.read_reg(reg).cast::(), Value::from(imm).cast::()), + ); + } + + /// Perform binary operation over register and shift immediate + #[inline] + unsafe fn binary_op_ims(&mut self, op: impl Fn(T, u32) -> T) { + let ParamBBW(tg, reg, imm) = self.decode(); + self.write_reg(tg, op(self.read_reg(reg).cast::(), imm)); + } + + /// Jump at `#3` if ordering on `#0 <=> #1` is equal to expected + #[inline] + unsafe fn cond_jmp(&mut self, expected: Ordering) { + let ParamBBD(a0, a1, ja) = self.decode(); + if self + .read_reg(a0) + .cast::() + .cmp(&self.read_reg(a1).cast::()) + == expected + { + self.pc = ja as usize; + } + } + + /// Read register + #[inline] + unsafe fn read_reg(&self, n: u8) -> Value { + *self.registers.get_unchecked(n as usize) + } + + /// Write a register. + /// Writing to register 0 is no-op. + #[inline] + unsafe fn write_reg(&mut self, n: u8, value: impl Into) { + if n != 0 { + *self.registers.get_unchecked_mut(n as usize) = value.into(); + } + } +} + +/// Load/Store target/source register range bound checking +#[inline] +fn ldst_bound_check(reg: u8, size: u16) -> Result<(), VmRunError> { + if usize::from(reg) * 8 + usize::from(size) > 2048 { + Err(VmRunError::RegOutOfBounds) + } else { + Ok(()) + } +} + +/// Virtual machine halt error +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +#[repr(u8)] +pub enum VmRunError { + /// Tried to execute invalid instruction + InvalidOpcode(u8), + + /// Unhandled load access exception + LoadAccessEx(u64), + + /// Unhandled store access exception + StoreAccessEx(u64), + + /// Register out-of-bounds access + RegOutOfBounds, + + /// Reached unreachable code + Unreachable, +} + +/// Virtual machine halt ok +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub enum VmRunOk { + /// Program has eached its end + End, + + /// Program was interrupted by a timer + Timer, + + /// Environment call + Ecall, +} diff --git a/hbvm/src/main.rs b/hbvm/src/main.rs index b9d7a241..f3cc2d0c 100644 --- a/hbvm/src/main.rs +++ b/hbvm/src/main.rs @@ -1,8 +1,9 @@ -use hbvm::vm::mem::{HandlePageFault, Memory, MemoryAccessReason, PageSize}; - use { hbbytecode::valider::validate, - hbvm::vm::Vm, + hbvm::{ + mem::{HandlePageFault, Memory, MemoryAccessReason, PageSize}, + Vm, + }, std::io::{stdin, Read}, }; @@ -15,7 +16,7 @@ fn main() -> Result<(), Box> { return Ok(()); } else { unsafe { - let mut vm = Vm::<_, 0>::new_unchecked(&prog, TestTrapHandler); + let mut vm = Vm::<_, 0>::new_unchecked(&prog, TestTrapHandler, Default::default()); let data = { let ptr = std::alloc::alloc_zeroed(std::alloc::Layout::from_size_align_unchecked( 4096, 4096, @@ -30,7 +31,7 @@ fn main() -> Result<(), Box> { .map( data, 0, - hbvm::vm::mem::paging::Permission::Write, + hbvm::mem::paging::Permission::Write, PageSize::Size4K, ) .unwrap(); diff --git a/hbvm/src/vm/mem/mod.rs b/hbvm/src/mem/mod.rs similarity index 98% rename from hbvm/src/vm/mem/mod.rs rename to hbvm/src/mem/mod.rs index df146826..acc190db 100644 --- a/hbvm/src/vm/mem/mod.rs +++ b/hbvm/src/mem/mod.rs @@ -7,28 +7,32 @@ mod pfhandler; pub use pfhandler::HandlePageFault; use { - self::paging::{PageTable, Permission, PtEntry}, super::VmRunError, - alloc::boxed::Box, core::mem::MaybeUninit, derive_more::Display, + paging::{PageTable, Permission}, }; +#[cfg(feature = "alloc")] +use {alloc::boxed::Box, paging::PtEntry}; + /// HoleyBytes virtual memory #[derive(Clone, Debug)] pub struct Memory { /// Root page table - root_pt: *mut PageTable, + pub root_pt: *mut PageTable, } +#[cfg(feature = "alloc")] impl Default for Memory { fn default() -> Self { Self { - root_pt: Box::into_raw(Box::default()), + root_pt: Box::into_raw(Default::default()), } } } +#[cfg(feature = "alloc")] impl Drop for Memory { fn drop(&mut self) { let _ = unsafe { Box::from_raw(self.root_pt) }; @@ -42,6 +46,7 @@ impl Memory { /// - Your faith in the gods of UB /// - Addr-san claims it's fine but who knows is she isn't lying :ferrisSus: /// - Alright, Miri-sama is also fine with this, who knows why + #[cfg(feature = "alloc")] pub unsafe fn map( &mut self, host: *mut u8, @@ -107,6 +112,7 @@ impl Memory { /// /// If errors, it only means there is no entry to unmap and in most cases /// just should be ignored. + #[cfg(feature = "alloc")] pub fn unmap(&mut self, addr: u64) -> Result<(), NothingToUnmap> { let mut current_pt = self.root_pt; let mut page_tables = [core::ptr::null_mut(); 5]; diff --git a/hbvm/src/vm/mem/paging.rs b/hbvm/src/mem/paging.rs similarity index 100% rename from hbvm/src/vm/mem/paging.rs rename to hbvm/src/mem/paging.rs diff --git a/hbvm/src/vm/mem/pfhandler.rs b/hbvm/src/mem/pfhandler.rs similarity index 100% rename from hbvm/src/vm/mem/pfhandler.rs rename to hbvm/src/mem/pfhandler.rs diff --git a/hbvm/src/vm/value.rs b/hbvm/src/value.rs similarity index 100% rename from hbvm/src/vm/value.rs rename to hbvm/src/value.rs diff --git a/hbvm/src/vm/mod.rs b/hbvm/src/vm/mod.rs deleted file mode 100644 index fcbfacb6..00000000 --- a/hbvm/src/vm/mod.rs +++ /dev/null @@ -1,446 +0,0 @@ -//! HoleyBytes Virtual Machine -//! -//! All unsafe code here should be sound, if input bytecode passes validation. - -// # General safety notice: -// - Validation has to assure there is 256 registers (r0 - r255) -// - Instructions have to be valid as specified (values and sizes) -// - Mapped pages should be at least 4 KiB - -pub mod mem; -pub mod value; - -use { - self::{mem::HandlePageFault, value::ValueVariant}, - core::{cmp::Ordering, ops}, - hbbytecode::{ - valider, OpParam, ParamBB, ParamBBB, ParamBBBB, ParamBBD, ParamBBDH, ParamBBW, ParamBD, - }, - mem::Memory, - value::Value, -}; - -/// HoleyBytes Virtual Machine -pub struct Vm<'a, PfHandler, const TIMER_QUOTIENT: usize> { - /// Holds 256 registers - /// - /// Writing to register 0 is considered undefined behaviour - /// in terms of HoleyBytes program execution - pub registers: [Value; 256], - - /// Memory implementation - pub memory: Memory, - - /// Trap handler - pub pfhandler: PfHandler, - - /// Program counter - pub pc: usize, - - /// Program - program: &'a [u8], - - /// Cached program length (without unreachable end) - program_len: usize, - - /// Program timer - timer: usize, -} - -impl<'a, PfHandler: HandlePageFault, const TIMER_QUOTIENT: usize> - Vm<'a, PfHandler, TIMER_QUOTIENT> -{ - /// Create a new VM with program and trap handler - /// - /// # Safety - /// Program code has to be validated - pub unsafe fn new_unchecked(program: &'a [u8], traph: PfHandler) -> Self { - Self { - registers: [Value::from(0_u64); 256], - memory: Default::default(), - pfhandler: traph, - pc: 0, - program_len: program.len() - 12, - program, - timer: 0, - } - } - - /// Create a new VM with program and trap handler only if it passes validation - pub fn new_validated(program: &'a [u8], traph: PfHandler) -> Result { - valider::validate(program)?; - Ok(unsafe { Self::new_unchecked(program, traph) }) - } - - /// Execute program - /// - /// Program can return [`VmRunError`] if a trap handling failed - pub fn run(&mut self) -> Result { - use hbbytecode::opcode::*; - loop { - // Check instruction boundary - if self.pc >= self.program_len { - return Ok(VmRunOk::End); - } - - // Big match - // - // Contribution guide: - // - Zero register shall never be overwitten. It's value has to always be 0. - // - Prefer `Self::read_reg` and `Self::write_reg` functions - // - Extract parameters using `param!` macro - // - Prioritise speed over code size - // - Memory is cheap, CPUs not that much - // - Do not heap allocate at any cost - // - Yes, user-provided trap handler may allocate, - // but that is not our »fault«. - // - Unsafe is kinda must, but be sure you have validated everything - // - Your contributions have to pass sanitizers and Miri - // - Strictly follow the spec - // - The spec does not specify how you perform actions, in what order, - // just that the observable effects have to be performed in order and - // correctly. - // - Yes, we assume you run 64 bit CPU. Else ?conradluget a better CPU - // sorry 8 bit fans, HBVM won't run on your Speccy :( - unsafe { - match *self.program.get_unchecked(self.pc) { - UN => { - self.decode::<()>(); - return Err(VmRunError::Unreachable); - } - NOP => self.decode::<()>(), - ADD => self.binary_op(u64::wrapping_add), - SUB => self.binary_op(u64::wrapping_sub), - MUL => self.binary_op(u64::wrapping_mul), - AND => self.binary_op::(ops::BitAnd::bitand), - OR => self.binary_op::(ops::BitOr::bitor), - XOR => self.binary_op::(ops::BitXor::bitxor), - SL => self.binary_op(|l, r| u64::wrapping_shl(l, r as u32)), - SR => self.binary_op(|l, r| u64::wrapping_shr(l, r as u32)), - SRS => self.binary_op(|l, r| i64::wrapping_shl(l, r as u32)), - CMP => { - // Compare a0 <=> a1 - // < → -1 - // > → 1 - // = → 0 - - let ParamBBB(tg, a0, a1) = self.decode(); - self.write_reg( - tg, - self.read_reg(a0) - .cast::() - .cmp(&self.read_reg(a1).cast::()) - as i64, - ); - } - CMPU => { - // Unsigned comparsion - let ParamBBB(tg, a0, a1) = self.decode(); - self.write_reg( - tg, - self.read_reg(a0) - .cast::() - .cmp(&self.read_reg(a1).cast::()) - as i64, - ); - } - NOT => { - // Logical negation - let ParamBB(tg, a0) = self.decode(); - self.write_reg(tg, !self.read_reg(a0).cast::()); - } - NEG => { - // Bitwise negation - let ParamBB(tg, a0) = self.decode(); - self.write_reg( - tg, - match self.read_reg(a0).cast::() { - 0 => 1_u64, - _ => 0, - }, - ); - } - DIR => { - // Fused Division-Remainder - let ParamBBBB(dt, rt, a0, a1) = self.decode(); - let a0 = self.read_reg(a0).cast::(); - let a1 = self.read_reg(a1).cast::(); - self.write_reg(dt, a0.checked_div(a1).unwrap_or(u64::MAX)); - self.write_reg(rt, a0.checked_rem(a1).unwrap_or(u64::MAX)); - } - ADDI => self.binary_op_imm(u64::wrapping_add), - MULI => self.binary_op_imm(u64::wrapping_sub), - ANDI => self.binary_op_imm::(ops::BitAnd::bitand), - ORI => self.binary_op_imm::(ops::BitOr::bitor), - XORI => self.binary_op_imm::(ops::BitXor::bitxor), - SLI => self.binary_op_ims(u64::wrapping_shl), - SRI => self.binary_op_ims(u64::wrapping_shr), - SRSI => self.binary_op_ims(i64::wrapping_shr), - CMPI => { - let ParamBBD(tg, a0, imm) = self.decode(); - self.write_reg( - tg, - self.read_reg(a0) - .cast::() - .cmp(&Value::from(imm).cast::()) - as i64, - ); - } - CMPUI => { - let ParamBBD(tg, a0, imm) = self.decode(); - self.write_reg(tg, self.read_reg(a0).cast::().cmp(&imm) as i64); - } - CP => { - let ParamBB(tg, a0) = self.decode(); - self.write_reg(tg, self.read_reg(a0)); - } - SWA => { - // Swap registers - let ParamBB(r0, r1) = self.decode(); - match (r0, r1) { - (0, 0) => (), - (dst, 0) | (0, dst) => self.write_reg(dst, 0_u64), - (r0, r1) => { - core::ptr::swap( - self.registers.get_unchecked_mut(usize::from(r0)), - self.registers.get_unchecked_mut(usize::from(r1)), - ); - } - } - } - LI => { - let ParamBD(tg, imm) = self.decode(); - self.write_reg(tg, imm); - } - LD => { - // Load. If loading more than register size, continue on adjecent registers - let ParamBBDH(dst, base, off, count) = self.decode(); - ldst_bound_check(dst, count)?; - - let n: usize = match dst { - 0 => 1, - _ => 0, - }; - - self.memory.load( - self.read_reg(base).cast::() + off + n as u64, - self.registers.as_mut_ptr().add(usize::from(dst) + n).cast(), - usize::from(count).saturating_sub(n), - &mut self.pfhandler, - )?; - } - ST => { - // Store. Same rules apply as to LD - let ParamBBDH(dst, base, off, count) = self.decode(); - ldst_bound_check(dst, count)?; - - self.memory.store( - self.read_reg(base).cast::() + off, - self.registers.as_ptr().add(usize::from(dst)).cast(), - count.into(), - &mut self.pfhandler, - )?; - } - BMC => { - // Block memory copy - let ParamBBD(src, dst, count) = self.decode(); - self.memory.block_copy( - self.read_reg(src).cast::(), - self.read_reg(dst).cast::(), - count as _, - &mut self.pfhandler, - )?; - } - BRC => { - // Block register copy - let ParamBBB(src, dst, count) = self.decode(); - if src.checked_add(count).is_none() || dst.checked_add(count).is_none() { - return Err(VmRunError::RegOutOfBounds); - } - - core::ptr::copy( - self.registers.get_unchecked(usize::from(src)), - self.registers.get_unchecked_mut(usize::from(dst)), - usize::from(count), - ); - } - JAL => { - // Jump and link. Save PC after this instruction to - // specified register and jump to reg + offset. - let ParamBBD(save, reg, offset) = self.decode(); - self.write_reg(save, self.pc as u64); - self.pc = (self.read_reg(reg).cast::() + offset) as usize; - } - // Conditional jumps, jump only to immediates - JEQ => self.cond_jmp::(Ordering::Equal), - JNE => { - let ParamBBD(a0, a1, jt) = self.decode(); - if self.read_reg(a0).cast::() != self.read_reg(a1).cast::() { - self.pc = jt as usize; - } - } - JLT => self.cond_jmp::(Ordering::Less), - JGT => self.cond_jmp::(Ordering::Greater), - JLTU => self.cond_jmp::(Ordering::Less), - JGTU => self.cond_jmp::(Ordering::Greater), - ECALL => { - self.decode::<()>(); - - // So we don't get timer interrupt after ECALL - if TIMER_QUOTIENT != 0 { - self.timer = self.timer.wrapping_add(1); - } - return Ok(VmRunOk::Ecall); - } - ADDF => self.binary_op::(ops::Add::add), - SUBF => self.binary_op::(ops::Sub::sub), - MULF => self.binary_op::(ops::Mul::mul), - DIRF => { - let ParamBBBB(dt, rt, a0, a1) = self.decode(); - let a0 = self.read_reg(a0).cast::(); - let a1 = self.read_reg(a1).cast::(); - self.write_reg(dt, a0 / a1); - self.write_reg(rt, a0 % a1); - } - FMAF => { - let ParamBBBB(dt, a0, a1, a2) = self.decode(); - self.write_reg( - dt, - self.read_reg(a0).cast::() * self.read_reg(a1).cast::() - + self.read_reg(a2).cast::(), - ); - } - NEGF => { - let ParamBB(dt, a0) = self.decode(); - self.write_reg(dt, -self.read_reg(a0).cast::()); - } - ITF => { - let ParamBB(dt, a0) = self.decode(); - self.write_reg(dt, self.read_reg(a0).cast::() as f64); - } - FTI => { - let ParamBB(dt, a0) = self.decode(); - self.write_reg(dt, self.read_reg(a0).cast::() as i64); - } - ADDFI => self.binary_op_imm::(ops::Add::add), - MULFI => self.binary_op_imm::(ops::Mul::mul), - op => return Err(VmRunError::InvalidOpcode(op)), - } - } - - if TIMER_QUOTIENT != 0 { - self.timer = self.timer.wrapping_add(1); - if self.timer % TIMER_QUOTIENT == 0 { - return Ok(VmRunOk::Timer); - } - } - } - } - - /// Decode instruction operands - #[inline] - unsafe fn decode(&mut self) -> T { - let data = self.program.as_ptr().add(self.pc + 1).cast::().read(); - self.pc += 1 + core::mem::size_of::(); - data - } - - /// Perform binary operating over two registers - #[inline] - unsafe fn binary_op(&mut self, op: impl Fn(T, T) -> T) { - let ParamBBB(tg, a0, a1) = self.decode(); - self.write_reg( - tg, - op(self.read_reg(a0).cast::(), self.read_reg(a1).cast::()), - ); - } - - /// Perform binary operation over register and immediate - #[inline] - unsafe fn binary_op_imm(&mut self, op: impl Fn(T, T) -> T) { - let ParamBBD(tg, reg, imm) = self.decode(); - self.write_reg( - tg, - op(self.read_reg(reg).cast::(), Value::from(imm).cast::()), - ); - } - - /// Perform binary operation over register and shift immediate - #[inline] - unsafe fn binary_op_ims(&mut self, op: impl Fn(T, u32) -> T) { - let ParamBBW(tg, reg, imm) = self.decode(); - self.write_reg(tg, op(self.read_reg(reg).cast::(), imm)); - } - - /// Jump at `#3` if ordering on `#0 <=> #1` is equal to expected - #[inline] - unsafe fn cond_jmp(&mut self, expected: Ordering) { - let ParamBBD(a0, a1, ja) = self.decode(); - if self - .read_reg(a0) - .cast::() - .cmp(&self.read_reg(a1).cast::()) - == expected - { - self.pc = ja as usize; - } - } - - /// Read register - #[inline] - unsafe fn read_reg(&self, n: u8) -> Value { - *self.registers.get_unchecked(n as usize) - } - - /// Write a register. - /// Writing to register 0 is no-op. - #[inline] - unsafe fn write_reg(&mut self, n: u8, value: impl Into) { - if n != 0 { - *self.registers.get_unchecked_mut(n as usize) = value.into(); - } - } -} - -/// Load/Store target/source register range bound checking -#[inline] -fn ldst_bound_check(reg: u8, size: u16) -> Result<(), VmRunError> { - if usize::from(reg) * 8 + usize::from(size) > 2048 { - Err(VmRunError::RegOutOfBounds) - } else { - Ok(()) - } -} - -/// Virtual machine halt error -#[derive(Copy, Clone, Debug, PartialEq, Eq)] -#[repr(u8)] -pub enum VmRunError { - /// Tried to execute invalid instruction - InvalidOpcode(u8), - - /// Unhandled load access exception - LoadAccessEx(u64), - - /// Unhandled store access exception - StoreAccessEx(u64), - - /// Register out-of-bounds access - RegOutOfBounds, - - /// Reached unreachable code - Unreachable, -} - -/// Virtual machine halt ok -#[derive(Copy, Clone, Debug, PartialEq, Eq)] -pub enum VmRunOk { - /// Program has eached its end - End, - - /// Program was interrupted by a timer - Timer, - - /// Environment call - Ecall, -} From bdda987da9db132fd64aad60ca99e9ff94fbf37e Mon Sep 17 00:00:00 2001 From: Erin Date: Wed, 26 Jul 2023 02:04:26 +0200 Subject: [PATCH 035/116] BMC is now interruptable --- hbvm/fuzz/fuzz_targets/vm.rs | 2 +- hbvm/src/lib.rs | 55 ++++++++++--- hbvm/src/mem/bmc.rs | 152 +++++++++++++++++++++++++++++++++++ hbvm/src/mem/mod.rs | 107 +----------------------- rustfmt.toml | 4 +- 5 files changed, 199 insertions(+), 121 deletions(-) create mode 100644 hbvm/src/mem/bmc.rs diff --git a/hbvm/fuzz/fuzz_targets/vm.rs b/hbvm/fuzz/fuzz_targets/vm.rs index 72e6da50..82df8738 100644 --- a/hbvm/fuzz/fuzz_targets/vm.rs +++ b/hbvm/fuzz/fuzz_targets/vm.rs @@ -9,7 +9,7 @@ use { }; fuzz_target!(|data: &[u8]| { - if let Ok(mut vm) = Vm::<_, 0>::new_validated(data, TestTrapHandler, Default::default()) { + if let Ok(mut vm) = Vm::<_, 100>::new_validated(data, TestTrapHandler, Default::default()) { let _ = vm.run(); } }); diff --git a/hbvm/src/lib.rs b/hbvm/src/lib.rs index 4fdf7a0f..c2c28a4c 100644 --- a/hbvm/src/lib.rs +++ b/hbvm/src/lib.rs @@ -19,13 +19,12 @@ pub mod mem; pub mod value; use { - self::{mem::HandlePageFault, value::ValueVariant}, - core::{cmp::Ordering, ops}, + core::{cmp::Ordering, mem::size_of, ops}, hbbytecode::{ valider, OpParam, ParamBB, ParamBBB, ParamBBBB, ParamBBD, ParamBBDH, ParamBBW, ParamBD, }, - mem::Memory, - value::Value, + mem::{bmc::BlockCopier, HandlePageFault, Memory}, + value::{Value, ValueVariant}, }; /// HoleyBytes Virtual Machine @@ -53,6 +52,9 @@ pub struct Vm<'a, PfHandler, const TIMER_QUOTIENT: usize> { /// Program timer timer: usize, + + /// Saved block copier + copier: Option, } impl<'a, PfHandler: HandlePageFault, const TIMER_QUOTIENT: usize> @@ -71,6 +73,7 @@ impl<'a, PfHandler: HandlePageFault, const TIMER_QUOTIENT: usize> program_len: program.len() - 12, program, timer: 0, + copier: None, } } @@ -255,13 +258,41 @@ impl<'a, PfHandler: HandlePageFault, const TIMER_QUOTIENT: usize> } BMC => { // Block memory copy - let ParamBBD(src, dst, count) = self.decode(); - self.memory.block_copy( - self.read_reg(src).cast::(), - self.read_reg(dst).cast::(), - count as _, - &mut self.pfhandler, - )?; + match if let Some(copier) = &mut self.copier { + // There is some copier, poll. + copier.poll(&mut self.memory, &mut self.pfhandler) + } else { + // There is none, make one! + let ParamBBD(src, dst, count) = self.decode(); + + // So we are still on BMC on next cycle + self.pc -= size_of::() + 1; + + self.copier = Some(BlockCopier::new( + self.read_reg(src).cast(), + self.read_reg(dst).cast(), + count as _, + )); + + self.copier + .as_mut() + .unwrap_unchecked() // SAFETY: We just assigned there + .poll(&mut self.memory, &mut self.pfhandler) + } { + // We are done, shift program counter + core::task::Poll::Ready(Ok(())) => { + self.copier = None; + self.pc += size_of::() + 1; + } + // Error, shift program counter (for consistency) + // and yield error + core::task::Poll::Ready(Err(e)) => { + self.pc += size_of::() + 1; + return Err(e.into()); + } + // Not done yet, proceed to next cycle + core::task::Poll::Pending => (), + } } BRC => { // Block register copy @@ -353,7 +384,7 @@ impl<'a, PfHandler: HandlePageFault, const TIMER_QUOTIENT: usize> #[inline] unsafe fn decode(&mut self) -> T { let data = self.program.as_ptr().add(self.pc + 1).cast::().read(); - self.pc += 1 + core::mem::size_of::(); + self.pc += 1 + size_of::(); data } diff --git a/hbvm/src/mem/bmc.rs b/hbvm/src/mem/bmc.rs new file mode 100644 index 00000000..b561b907 --- /dev/null +++ b/hbvm/src/mem/bmc.rs @@ -0,0 +1,152 @@ +use { + super::MemoryAccessReason, + crate::{ + mem::{perm_check, HandlePageFault, Memory}, + VmRunError, + }, + core::{mem::MaybeUninit, task::Poll}, +}; + +// Buffer size (defaults to 4 KiB, a smallest page size on most platforms) +const BUF_SIZE: usize = 4096; + +// This should be equal to `BUF_SIZE` +#[repr(align(4096))] +struct AlignedBuf([MaybeUninit; BUF_SIZE]); + +pub struct BlockCopier { + /// Source address + src: u64, + /// Destination address + dst: u64, + /// How many buffer sizes to copy? + n_buffers: usize, + /// …and what remainds after? + rem: usize, +} + +impl BlockCopier { + pub fn new(src: u64, dst: u64, count: usize) -> Self { + Self { + src, + dst, + n_buffers: count / BUF_SIZE, + rem: count % BUF_SIZE, + } + } + + /// Copy one block + /// + /// # Safety + /// - Same as for [`Memory::load`] and [`Memory::store`] + pub unsafe fn poll( + &mut self, + memory: &mut Memory, + traph: &mut impl HandlePageFault, + ) -> Poll> { + // Safety: Assuming uninit of array of MaybeUninit is sound + let mut buf = AlignedBuf(MaybeUninit::uninit().assume_init()); + + if self.n_buffers != 0 { + if let Err(e) = act( + memory, + self.src, + self.dst, + buf.0.as_mut_ptr().cast(), + BUF_SIZE, + traph, + ) { + return Poll::Ready(Err(e)); + } + + self.src += BUF_SIZE as u64; + self.dst += BUF_SIZE as u64; + self.n_buffers -= 1; + + return if self.n_buffers + self.rem == 0 { + // If there is nothing left, we are done + Poll::Ready(Ok(())) + } else { + // Otherwise let's advice to run it again + Poll::Pending + }; + } + + if self.rem != 0 { + if let Err(e) = act( + memory, + self.src, + self.dst, + buf.0.as_mut_ptr().cast(), + self.rem, + traph, + ) { + return Poll::Ready(Err(e)); + } + } + + Poll::Ready(Ok(())) + } +} + +#[inline] +unsafe fn act( + memory: &mut Memory, + src: u64, + dst: u64, + buf: *mut u8, + count: usize, + traph: &mut impl HandlePageFault, +) -> Result<(), BlkCopyError> { + // Load to buffer + memory + .memory_access( + MemoryAccessReason::Load, + src, + buf, + count, + perm_check::readable, + |src, dst, count| core::ptr::copy(src, dst, count), + traph, + ) + .map_err(|addr| BlkCopyError { + access_reason: MemoryAccessReason::Load, + addr, + })?; + + // Store from buffer + memory + .memory_access( + MemoryAccessReason::Store, + dst, + buf, + count, + perm_check::writable, + |dst, src, count| core::ptr::copy(src, dst, count), + traph, + ) + .map_err(|addr| BlkCopyError { + access_reason: MemoryAccessReason::Store, + addr, + })?; + + Ok(()) +} + +/// Error occured when copying a block of memory +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct BlkCopyError { + /// Kind of access + access_reason: MemoryAccessReason, + /// VM Address + addr: u64, +} + +impl From for VmRunError { + fn from(value: BlkCopyError) -> Self { + match value.access_reason { + MemoryAccessReason::Load => Self::LoadAccessEx(value.addr), + MemoryAccessReason::Store => Self::StoreAccessEx(value.addr), + } + } +} diff --git a/hbvm/src/mem/mod.rs b/hbvm/src/mem/mod.rs index acc190db..493de635 100644 --- a/hbvm/src/mem/mod.rs +++ b/hbvm/src/mem/mod.rs @@ -1,6 +1,7 @@ //! Program memory implementation pub mod paging; +pub mod bmc; mod pfhandler; @@ -8,7 +9,6 @@ pub use pfhandler::HandlePageFault; use { super::VmRunError, - core::mem::MaybeUninit, derive_more::Display, paging::{PageTable, Permission}, }; @@ -215,93 +215,6 @@ impl Memory { .map_err(StoreError) } - /// Copy a block of memory - /// - /// # Safety - /// - Same as for [`Self::load`] and [`Self::store`] - /// - This function has been rewritten and is now pretty much boring - pub unsafe fn block_copy( - &mut self, - mut src: u64, - mut dst: u64, - count: usize, - traph: &mut impl HandlePageFault, - ) -> Result<(), BlkCopyError> { - // Yea, i know it is possible to do this more efficiently, but I am too lazy. - - impl Memory { - #[inline] - unsafe fn act( - &mut self, - src: u64, - dst: u64, - buf: *mut u8, - count: usize, - traph: &mut impl HandlePageFault, - ) -> Result<(), BlkCopyError> { - // Load to buffer - self.memory_access( - MemoryAccessReason::Load, - src, - buf, - count, - perm_check::readable, - |src, dst, count| core::ptr::copy(src, dst, count), - traph, - ) - .map_err(|addr| BlkCopyError { - access_reason: MemoryAccessReason::Load, - addr, - })?; - - // Store from buffer - self.memory_access( - MemoryAccessReason::Store, - dst, - buf, - count, - perm_check::writable, - |dst, src, count| core::ptr::copy(src, dst, count), - traph, - ) - .map_err(|addr| BlkCopyError { - access_reason: MemoryAccessReason::Store, - addr, - })?; - - Ok(()) - } - } - - // Buffer size (defaults to 4 KiB, a smallest page size on most platforms) - const BUF_SIZE: usize = 4096; - - // This should be equal to `BUF_SIZE` - #[repr(align(4096))] - struct AlignedBuf([MaybeUninit; BUF_SIZE]); - - // Safety: Assuming uninit of array of MaybeUninit is sound - let mut buf = AlignedBuf(MaybeUninit::uninit().assume_init()); - - // Calculate how many times we need to copy buffer-sized blocks if any and the rest. - let n_buffers = count / BUF_SIZE; - let rem = count % BUF_SIZE; - - // Copy buffer-sized blocks - for _ in 0..n_buffers { - self.act(src, dst, buf.0.as_mut_ptr().cast(), BUF_SIZE, traph)?; - src += BUF_SIZE as u64; - dst += BUF_SIZE as u64; - } - - // Copy the rest (if any) - if rem != 0 { - self.act(src, dst, buf.0.as_mut_ptr().cast(), rem, traph)?; - } - - Ok(()) - } - // Everyone behold, the holy function, the god of HBVM memory accesses! /// Split address to pages, check their permissions and feed pointers with offset @@ -534,24 +447,6 @@ pub enum MemoryAccessReason { Store, } -/// Error occured when copying a block of memory -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub struct BlkCopyError { - /// Kind of access - access_reason: MemoryAccessReason, - /// VM Address - addr: u64, -} - -impl From for VmRunError { - fn from(value: BlkCopyError) -> Self { - match value.access_reason { - MemoryAccessReason::Load => Self::LoadAccessEx(value.addr), - MemoryAccessReason::Store => Self::StoreAccessEx(value.addr), - } - } -} - impl From for VmRunError { fn from(value: LoadError) -> Self { Self::LoadAccessEx(value.0) diff --git a/rustfmt.toml b/rustfmt.toml index 90a36c78..907ba344 100644 --- a/rustfmt.toml +++ b/rustfmt.toml @@ -1,4 +1,4 @@ hex_literal_case = "Upper" imports_granularity = "One" -struct_field_align_threshold = 5 -enum_discrim_align_threshold = 5 \ No newline at end of file +struct_field_align_threshold = 8 +enum_discrim_align_threshold = 8 \ No newline at end of file From 83436507dfaa36613b15deed0be9f0dbb25648c3 Mon Sep 17 00:00:00 2001 From: Erin Date: Wed, 26 Jul 2023 02:28:14 +0200 Subject: [PATCH 036/116] Fixed few overflows --- hbvm/src/lib.rs | 52 +++++++++++++++++++++++++++++---------------- hbvm/src/mem/bmc.rs | 46 +++++++++++++++++++++++++++------------ 2 files changed, 67 insertions(+), 31 deletions(-) diff --git a/hbvm/src/lib.rs b/hbvm/src/lib.rs index c2c28a4c..e9a01e3d 100644 --- a/hbvm/src/lib.rs +++ b/hbvm/src/lib.rs @@ -230,27 +230,26 @@ impl<'a, PfHandler: HandlePageFault, const TIMER_QUOTIENT: usize> LD => { // Load. If loading more than register size, continue on adjecent registers let ParamBBDH(dst, base, off, count) = self.decode(); - ldst_bound_check(dst, count)?; - - let n: usize = match dst { + let n: u8 = match dst { 0 => 1, _ => 0, }; self.memory.load( - self.read_reg(base).cast::() + off + n as u64, - self.registers.as_mut_ptr().add(usize::from(dst) + n).cast(), - usize::from(count).saturating_sub(n), + self.ldst_addr_uber(dst, base, off, count, n)?, + self.registers + .as_mut_ptr() + .add(usize::from(dst) + usize::from(n)) + .cast(), + usize::from(count).saturating_sub(n.into()), &mut self.pfhandler, )?; } ST => { // Store. Same rules apply as to LD let ParamBBDH(dst, base, off, count) = self.decode(); - ldst_bound_check(dst, count)?; - self.memory.store( - self.read_reg(base).cast::() + off, + self.ldst_addr_uber(dst, base, off, count, 0)?, self.registers.as_ptr().add(usize::from(dst)).cast(), count.into(), &mut self.pfhandler, @@ -312,7 +311,8 @@ impl<'a, PfHandler: HandlePageFault, const TIMER_QUOTIENT: usize> // specified register and jump to reg + offset. let ParamBBD(save, reg, offset) = self.decode(); self.write_reg(save, self.pc as u64); - self.pc = (self.read_reg(reg).cast::() + offset) as usize; + self.pc = + (self.read_reg(reg).cast::().saturating_add(offset)) as usize; } // Conditional jumps, jump only to immediates JEQ => self.cond_jmp::(Ordering::Equal), @@ -443,15 +443,28 @@ impl<'a, PfHandler: HandlePageFault, const TIMER_QUOTIENT: usize> *self.registers.get_unchecked_mut(n as usize) = value.into(); } } -} -/// Load/Store target/source register range bound checking -#[inline] -fn ldst_bound_check(reg: u8, size: u16) -> Result<(), VmRunError> { - if usize::from(reg) * 8 + usize::from(size) > 2048 { - Err(VmRunError::RegOutOfBounds) - } else { - Ok(()) + /// Load / Store Address check-computation überfunction + #[inline] + unsafe fn ldst_addr_uber( + &self, + dst: u8, + base: u8, + offset: u64, + size: u16, + adder: u8, + ) -> Result { + let reg = dst.checked_add(adder).ok_or(VmRunError::RegOutOfBounds)?; + + if usize::from(reg) * 8 + usize::from(size) > 2048 { + Err(VmRunError::RegOutOfBounds) + } else { + self.read_reg(base) + .cast::() + .checked_add(offset) + .and_then(|x| x.checked_add(adder.into())) + .ok_or(VmRunError::AddrOutOfBounds) + } } } @@ -471,6 +484,9 @@ pub enum VmRunError { /// Register out-of-bounds access RegOutOfBounds, + /// Address out-of-bounds + AddrOutOfBounds, + /// Reached unreachable code Unreachable, } diff --git a/hbvm/src/mem/bmc.rs b/hbvm/src/mem/bmc.rs index b561b907..fd43d733 100644 --- a/hbvm/src/mem/bmc.rs +++ b/hbvm/src/mem/bmc.rs @@ -36,7 +36,7 @@ impl BlockCopier { } /// Copy one block - /// + /// /// # Safety /// - Same as for [`Memory::load`] and [`Memory::store`] pub unsafe fn poll( @@ -59,8 +59,16 @@ impl BlockCopier { return Poll::Ready(Err(e)); } - self.src += BUF_SIZE as u64; - self.dst += BUF_SIZE as u64; + match self.src.checked_add(BUF_SIZE as u64) { + Some(n) => self.src = n, + None => return Poll::Ready(Err(BlkCopyError::OutOfBounds)), + }; + + match self.dst.checked_add(BUF_SIZE as u64) { + Some(n) => self.dst = n, + None => return Poll::Ready(Err(BlkCopyError::OutOfBounds)), + }; + self.n_buffers -= 1; return if self.n_buffers + self.rem == 0 { @@ -109,7 +117,7 @@ unsafe fn act( |src, dst, count| core::ptr::copy(src, dst, count), traph, ) - .map_err(|addr| BlkCopyError { + .map_err(|addr| BlkCopyError::Access { access_reason: MemoryAccessReason::Load, addr, })?; @@ -125,7 +133,7 @@ unsafe fn act( |dst, src, count| core::ptr::copy(src, dst, count), traph, ) - .map_err(|addr| BlkCopyError { + .map_err(|addr| BlkCopyError::Access { access_reason: MemoryAccessReason::Store, addr, })?; @@ -135,18 +143,30 @@ unsafe fn act( /// Error occured when copying a block of memory #[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub struct BlkCopyError { - /// Kind of access - access_reason: MemoryAccessReason, - /// VM Address - addr: u64, +pub enum BlkCopyError { + /// Memory access error + Access { + /// Kind of access + access_reason: MemoryAccessReason, + /// VM Address + addr: u64, + }, + /// Address out of bounds + OutOfBounds, } impl From for VmRunError { fn from(value: BlkCopyError) -> Self { - match value.access_reason { - MemoryAccessReason::Load => Self::LoadAccessEx(value.addr), - MemoryAccessReason::Store => Self::StoreAccessEx(value.addr), + match value { + BlkCopyError::Access { + access_reason: MemoryAccessReason::Load, + addr, + } => Self::LoadAccessEx(addr), + BlkCopyError::Access { + access_reason: MemoryAccessReason::Store, + addr, + } => Self::StoreAccessEx(addr), + BlkCopyError::OutOfBounds => Self::AddrOutOfBounds, } } } From c4e062e742db8a2d5454a91907f4b6e7069d4ccc Mon Sep 17 00:00:00 2001 From: Erin Date: Wed, 26 Jul 2023 02:30:22 +0200 Subject: [PATCH 037/116] Increased timeout --- hbvm/fuzz/fuzz_targets/vm.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hbvm/fuzz/fuzz_targets/vm.rs b/hbvm/fuzz/fuzz_targets/vm.rs index 82df8738..b0b9fbd8 100644 --- a/hbvm/fuzz/fuzz_targets/vm.rs +++ b/hbvm/fuzz/fuzz_targets/vm.rs @@ -9,7 +9,7 @@ use { }; fuzz_target!(|data: &[u8]| { - if let Ok(mut vm) = Vm::<_, 100>::new_validated(data, TestTrapHandler, Default::default()) { + if let Ok(mut vm) = Vm::<_, 4096>::new_validated(data, TestTrapHandler, Default::default()) { let _ = vm.run(); } }); From cfe3203ef1b8ff526f0f51c0c15d7469e74c2226 Mon Sep 17 00:00:00 2001 From: Erin Date: Wed, 26 Jul 2023 02:31:06 +0200 Subject: [PATCH 038/116] Increased timeout --- hbvm/fuzz/fuzz_targets/vm.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hbvm/fuzz/fuzz_targets/vm.rs b/hbvm/fuzz/fuzz_targets/vm.rs index b0b9fbd8..d6867467 100644 --- a/hbvm/fuzz/fuzz_targets/vm.rs +++ b/hbvm/fuzz/fuzz_targets/vm.rs @@ -9,7 +9,7 @@ use { }; fuzz_target!(|data: &[u8]| { - if let Ok(mut vm) = Vm::<_, 4096>::new_validated(data, TestTrapHandler, Default::default()) { + if let Ok(mut vm) = Vm::<_, 65536>::new_validated(data, TestTrapHandler, Default::default()) { let _ = vm.run(); } }); From e07bfb2301c5cadd2b17c822f6c3337df6200212 Mon Sep 17 00:00:00 2001 From: Erin Date: Wed, 26 Jul 2023 02:35:27 +0200 Subject: [PATCH 039/116] Decreased timeout --- hbvm/fuzz/fuzz_targets/vm.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hbvm/fuzz/fuzz_targets/vm.rs b/hbvm/fuzz/fuzz_targets/vm.rs index d6867467..4f27fbb7 100644 --- a/hbvm/fuzz/fuzz_targets/vm.rs +++ b/hbvm/fuzz/fuzz_targets/vm.rs @@ -9,7 +9,7 @@ use { }; fuzz_target!(|data: &[u8]| { - if let Ok(mut vm) = Vm::<_, 65536>::new_validated(data, TestTrapHandler, Default::default()) { + if let Ok(mut vm) = Vm::<_, 16384>::new_validated(data, TestTrapHandler, Default::default()) { let _ = vm.run(); } }); From 19df4538d78e8759dd9ee77e039918a6452e9be3 Mon Sep 17 00:00:00 2001 From: Erin Date: Wed, 26 Jul 2023 03:27:31 +0200 Subject: [PATCH 040/116] Fixed page size, fuzzer now does memory. --- hbvm/fuzz/fuzz_targets/vm.rs | 24 ++++++++++++++++++++++++ hbvm/src/mem/mod.rs | 4 ++-- 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/hbvm/fuzz/fuzz_targets/vm.rs b/hbvm/fuzz/fuzz_targets/vm.rs index 4f27fbb7..3a30dac0 100644 --- a/hbvm/fuzz/fuzz_targets/vm.rs +++ b/hbvm/fuzz/fuzz_targets/vm.rs @@ -10,10 +10,34 @@ use { fuzz_target!(|data: &[u8]| { if let Ok(mut vm) = Vm::<_, 16384>::new_validated(data, TestTrapHandler, Default::default()) { + let page = Box::into_raw(Box::::default()); + + unsafe { + vm.memory + .map( + page.cast(), + 0, + hbvm::mem::paging::Permission::Write, + PageSize::Size4K, + ) + .unwrap() + }; + let _ = vm.run(); + + vm.memory.unmap(0).unwrap(); + let _ = unsafe { Box::from_raw(page) }; } }); +#[repr(align(4096))] +struct Page([u8; 4096]); +impl Default for Page { + fn default() -> Self { + unsafe { std::mem::MaybeUninit::zeroed().assume_init() } + } +} + struct TestTrapHandler; impl HandlePageFault for TestTrapHandler { fn page_fault( diff --git a/hbvm/src/mem/mod.rs b/hbvm/src/mem/mod.rs index 493de635..76809bfb 100644 --- a/hbvm/src/mem/mod.rs +++ b/hbvm/src/mem/mod.rs @@ -365,7 +365,7 @@ impl Iterator for AddrPageLookuper { perm, PageSize::from_lvl(lvl)?, // In-page offset - self.addr as usize & ((1 << (lvl * 9 + 12)) - 1), + addr_extract_index(self.addr, lvl), ); } } @@ -393,7 +393,7 @@ impl Iterator for AddrPageLookuper { /// the output of the function is unspecified (yes, it can also panic :) pub fn addr_extract_index(addr: u64, lvl: u8) -> usize { debug_assert!(lvl <= 4); - usize::try_from((addr >> (lvl * 9 + 12)) & ((1 << 9) - 1)).expect("?conradluget a better CPU") + usize::try_from((addr >> (lvl * 8 + 12)) & ((1 << 8) - 1)).expect("?conradluget a better CPU") } /// Page size From 1ed153a9a2fe44afe8deafef8e9ed5a4cf4c134e Mon Sep 17 00:00:00 2001 From: Erin Date: Wed, 26 Jul 2023 12:22:28 +0200 Subject: [PATCH 041/116] Fixed memory (un)mapping --- hbvm/fuzz/fuzz_targets/vm.rs | 44 ++++++++++++++++++++++++------------ hbvm/src/mem/mod.rs | 17 +++++++------- 2 files changed, 39 insertions(+), 22 deletions(-) diff --git a/hbvm/fuzz/fuzz_targets/vm.rs b/hbvm/fuzz/fuzz_targets/vm.rs index 3a30dac0..e1d42664 100644 --- a/hbvm/fuzz/fuzz_targets/vm.rs +++ b/hbvm/fuzz/fuzz_targets/vm.rs @@ -10,26 +10,42 @@ use { fuzz_target!(|data: &[u8]| { if let Ok(mut vm) = Vm::<_, 16384>::new_validated(data, TestTrapHandler, Default::default()) { - let page = Box::into_raw(Box::::default()); - - unsafe { - vm.memory - .map( - page.cast(), - 0, - hbvm::mem::paging::Permission::Write, - PageSize::Size4K, - ) - .unwrap() - }; + // Alloc and map some memory + let pages = [ + alloc_and_map(&mut vm.memory, 0), + alloc_and_map(&mut vm.memory, 4096), + ]; + // Run VM let _ = vm.run(); - vm.memory.unmap(0).unwrap(); - let _ = unsafe { Box::from_raw(page) }; + // Unmap and dealloc the memory + for (i, page) in pages.into_iter().enumerate() { + unmap_and_dealloc(&mut vm.memory, page, i as u64 * 4096); + } } }); +fn alloc_and_map(memory: &mut Memory, at: u64) -> *mut u8 { + let ptr = Box::into_raw(Box::::default()).cast(); + unsafe { + memory + .map( + ptr, + at, + hbvm::mem::paging::Permission::Write, + PageSize::Size4K, + ) + .unwrap() + }; + ptr +} + +fn unmap_and_dealloc(memory: &mut Memory, ptr: *mut u8, from: u64) { + memory.unmap(from).unwrap(); + let _ = unsafe { Box::from_raw(ptr.cast::()) }; +} + #[repr(align(4096))] struct Page([u8; 4096]); impl Default for Page { diff --git a/hbvm/src/mem/mod.rs b/hbvm/src/mem/mod.rs index 76809bfb..d93ef58c 100644 --- a/hbvm/src/mem/mod.rs +++ b/hbvm/src/mem/mod.rs @@ -1,7 +1,7 @@ //! Program memory implementation -pub mod paging; pub mod bmc; +pub mod paging; mod pfhandler; @@ -58,13 +58,13 @@ impl Memory { // Decide on what level depth are we going let lookup_depth = match pagesize { - PageSize::Size4K => 4, - PageSize::Size2M => 3, + PageSize::Size4K => 0, + PageSize::Size2M => 1, PageSize::Size1G => 2, }; // Walk pagetable levels - for lvl in (0..lookup_depth).rev() { + for lvl in (lookup_depth..5).rev() { let entry = (*current_pt) .table .get_unchecked_mut(addr_extract_index(target, lvl)); @@ -94,7 +94,7 @@ impl Memory { let node = (*current_pt) .table - .get_unchecked_mut(addr_extract_index(target, 4 - lookup_depth)); + .get_unchecked_mut(addr_extract_index(target, lookup_depth)); // Check if node is not mapped if node.permission() != Permission::Empty { @@ -114,6 +114,7 @@ impl Memory { /// just should be ignored. #[cfg(feature = "alloc")] pub fn unmap(&mut self, addr: u64) -> Result<(), NothingToUnmap> { + extern crate std; let mut current_pt = self.root_pt; let mut page_tables = [core::ptr::null_mut(); 5]; @@ -152,9 +153,7 @@ impl Memory { unsafe { let children = &mut (*(*entry).ptr()).pt.childen; - - // Decrease children count - *children -= 1; + *children -= 1; // Decrease children count // If there are no children, deallocate. if *children == 0 { @@ -162,6 +161,8 @@ impl Memory { // Zero visited entry core::ptr::write_bytes(entry, 0, 1); + } else { + break; } } } From 2480a659475a1a6003f0130ab5dc3a067b16d081 Mon Sep 17 00:00:00 2001 From: Erin Date: Wed, 26 Jul 2023 12:41:18 +0200 Subject: [PATCH 042/116] Whoops, this is 5-level paging, not 6-level paging --- hbvm/src/mem/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hbvm/src/mem/mod.rs b/hbvm/src/mem/mod.rs index d93ef58c..c8656f52 100644 --- a/hbvm/src/mem/mod.rs +++ b/hbvm/src/mem/mod.rs @@ -64,7 +64,7 @@ impl Memory { }; // Walk pagetable levels - for lvl in (lookup_depth..5).rev() { + for lvl in (lookup_depth..4).rev() { let entry = (*current_pt) .table .get_unchecked_mut(addr_extract_index(target, lvl)); From 10f9907c09c0f1c224ee48ffcc80f4c523690488 Mon Sep 17 00:00:00 2001 From: Erin Date: Wed, 26 Jul 2023 13:04:58 +0200 Subject: [PATCH 043/116] Fixed mapping problems --- hbvm/src/mem/mod.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/hbvm/src/mem/mod.rs b/hbvm/src/mem/mod.rs index c8656f52..1c230c2b 100644 --- a/hbvm/src/mem/mod.rs +++ b/hbvm/src/mem/mod.rs @@ -64,7 +64,7 @@ impl Memory { }; // Walk pagetable levels - for lvl in (lookup_depth..4).rev() { + for lvl in (lookup_depth + 1..5).rev() { let entry = (*current_pt) .table .get_unchecked_mut(addr_extract_index(target, lvl)); @@ -114,7 +114,6 @@ impl Memory { /// just should be ignored. #[cfg(feature = "alloc")] pub fn unmap(&mut self, addr: u64) -> Result<(), NothingToUnmap> { - extern crate std; let mut current_pt = self.root_pt; let mut page_tables = [core::ptr::null_mut(); 5]; @@ -140,6 +139,7 @@ impl Memory { // empty permission - no UB here! _ => unsafe { core::ptr::write_bytes(entry, 0, 1); + break; }, } } From 37a1923f1e74ab4c77c3a1f8d41941b0e4add05f Mon Sep 17 00:00:00 2001 From: Erin Date: Wed, 26 Jul 2023 20:49:23 +0200 Subject: [PATCH 044/116] Added some comments --- hbvm/src/mem/bmc.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/hbvm/src/mem/bmc.rs b/hbvm/src/mem/bmc.rs index fd43d733..7c4551ae 100644 --- a/hbvm/src/mem/bmc.rs +++ b/hbvm/src/mem/bmc.rs @@ -47,6 +47,7 @@ impl BlockCopier { // Safety: Assuming uninit of array of MaybeUninit is sound let mut buf = AlignedBuf(MaybeUninit::uninit().assume_init()); + // We have at least one buffer size to copy if self.n_buffers != 0 { if let Err(e) = act( memory, @@ -59,6 +60,9 @@ impl BlockCopier { return Poll::Ready(Err(e)); } + // Bump source and destination address + // + // If we are over the address space, bail. match self.src.checked_add(BUF_SIZE as u64) { Some(n) => self.src = n, None => return Poll::Ready(Err(BlkCopyError::OutOfBounds)), From 582c7164451cf22131fe205e4e75cac8c928f23d Mon Sep 17 00:00:00 2001 From: Erin Date: Wed, 26 Jul 2023 20:54:24 +0200 Subject: [PATCH 045/116] Nightly opts --- hbvm/Cargo.toml | 1 + hbvm/src/lib.rs | 3 +++ hbvm/src/mem/bmc.rs | 1 + hbvm/src/mem/mod.rs | 1 + 4 files changed, 6 insertions(+) diff --git a/hbvm/Cargo.toml b/hbvm/Cargo.toml index 87b74fa2..98d80517 100644 --- a/hbvm/Cargo.toml +++ b/hbvm/Cargo.toml @@ -9,6 +9,7 @@ lto = true [features] default = ["alloc"] alloc = [] +nightly = [] [dependencies] delegate = "0.9" diff --git a/hbvm/src/lib.rs b/hbvm/src/lib.rs index e9a01e3d..8e07bb55 100644 --- a/hbvm/src/lib.rs +++ b/hbvm/src/lib.rs @@ -12,6 +12,8 @@ #![no_std] +#![cfg_attr(feature = "nightly", feature(fn_align))] + #[cfg(feature = "alloc")] extern crate alloc; @@ -90,6 +92,7 @@ impl<'a, PfHandler: HandlePageFault, const TIMER_QUOTIENT: usize> /// Execute program /// /// Program can return [`VmRunError`] if a trap handling failed + #[cfg_attr(feature = "nightly", repr(align(4096)))] pub fn run(&mut self) -> Result { use hbbytecode::opcode::*; loop { diff --git a/hbvm/src/mem/bmc.rs b/hbvm/src/mem/bmc.rs index 7c4551ae..35d282db 100644 --- a/hbvm/src/mem/bmc.rs +++ b/hbvm/src/mem/bmc.rs @@ -26,6 +26,7 @@ pub struct BlockCopier { } impl BlockCopier { + #[inline] pub fn new(src: u64, dst: u64, count: usize) -> Self { Self { src, diff --git a/hbvm/src/mem/mod.rs b/hbvm/src/mem/mod.rs index 1c230c2b..8ef07b3d 100644 --- a/hbvm/src/mem/mod.rs +++ b/hbvm/src/mem/mod.rs @@ -310,6 +310,7 @@ struct AddrPageLookuper { impl AddrPageLookuper { /// Create a new page lookuper + #[inline] pub const fn new(addr: u64, size: usize, pagetable: *const PageTable) -> Self { Self { addr, From a667c36d6cde27cb24fca9e7a7e6030ae050c898 Mon Sep 17 00:00:00 2001 From: Erin Date: Wed, 26 Jul 2023 21:23:03 +0200 Subject: [PATCH 046/116] Link fix --- hbbytecode/src/gen_valider.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/hbbytecode/src/gen_valider.rs b/hbbytecode/src/gen_valider.rs index a3f01936..9149650f 100644 --- a/hbbytecode/src/gen_valider.rs +++ b/hbbytecode/src/gen_valider.rs @@ -59,7 +59,6 @@ macro_rules! gen_valider { let start = program; loop { use crate::opcode::*; - extern crate std; program = match program { // End of program [] => return Ok(()), From 1a5f101719b9dd57f131f09f8c8022db4bf270f3 Mon Sep 17 00:00:00 2001 From: Erin Date: Tue, 1 Aug 2023 22:13:22 +0200 Subject: [PATCH 047/116] Added magic --- hbasm/src/lib.rs | 28 ++++++++++++++++++++-------- hbbytecode/src/gen_valider.rs | 11 +++++++++++ hbvm/src/lib.rs | 2 +- spec.md | 1 + 4 files changed, 33 insertions(+), 9 deletions(-) diff --git a/hbasm/src/lib.rs b/hbasm/src/lib.rs index cf7da9e0..0b301b96 100644 --- a/hbasm/src/lib.rs +++ b/hbasm/src/lib.rs @@ -1,11 +1,11 @@ //! Holey Bytes Assembler -//! +//! //! 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. -//! +//! //! So this crate contains both assembleer with API for programs and a text assembler //! for humans to write @@ -15,26 +15,37 @@ extern crate alloc; mod macros; -use {alloc::vec::Vec, hashbrown::HashSet}; +use { + alloc::{vec, vec::Vec}, + hashbrown::HashSet, +}; /// Assembler -/// +/// /// - Opcode-generic, instruction-type-specific methods are named `i_param_` /// - You likely won't need to use them, but they are here, just in case :) /// - Instruction-specific methods are named `i_` -#[derive(Default)] pub struct Assembler { pub buf: Vec, pub sub: HashSet, } +impl Default for Assembler { + fn default() -> Self { + Self { + buf: vec![0; 3], + sub: Default::default(), + } + } +} + hbbytecode::invoke_with_def!(macros::text::gen_text); impl Assembler { hbbytecode::invoke_with_def!(macros::asm::impl_asm); - /// Append 12 zeroes (UN) at the end - /// + /// Append 12 zeroes (UN) at the end and add magic to the begining + /// /// # HoleyBytes lore /// /// In reference HBVM implementation checks are done in @@ -61,11 +72,12 @@ impl Assembler { /// Why 12 bytes? That's the size of largest instruction parameter part. pub fn finalise(&mut self) { self.buf.extend([0; 12]); + self.buf[0..3].copy_from_slice(&[0xAB, 0x1E, 0x0B]); } } /// Immediate value -/// +/// /// # Implementor notice /// It should insert exactly 8 bytes, otherwise output will be malformed. /// This is not checked in any way diff --git a/hbbytecode/src/gen_valider.rs b/hbbytecode/src/gen_valider.rs index 9149650f..c9d9ab73 100644 --- a/hbbytecode/src/gen_valider.rs +++ b/hbbytecode/src/gen_valider.rs @@ -23,6 +23,8 @@ macro_rules! gen_valider { RegisterArrayOverflow, /// Program is not validly terminated InvalidEnd, + /// Program misses magic + MissingMagic } /// Error @@ -37,6 +39,14 @@ macro_rules! gen_valider { /// Perform bytecode validation. If it passes, the program should be /// sound to execute. pub fn validate(mut program: &[u8]) -> Result<(), Error> { + // Validate magic + if program.get(0..3) != Some(&[0xAB, 0x1E, 0x0B]) { + return Err(Error { + kind: ErrorKind::MissingMagic, + index: 0, + }); + } + // Program has to end with 12 zeroes, if there is less than // 12 bytes, program is invalid. if program.len() < 12 { @@ -57,6 +67,7 @@ macro_rules! gen_valider { } let start = program; + program = &program[3..]; loop { use crate::opcode::*; program = match program { diff --git a/hbvm/src/lib.rs b/hbvm/src/lib.rs index 8e07bb55..67b1dda1 100644 --- a/hbvm/src/lib.rs +++ b/hbvm/src/lib.rs @@ -73,7 +73,7 @@ impl<'a, PfHandler: HandlePageFault, const TIMER_QUOTIENT: usize> pfhandler: traph, pc: 0, program_len: program.len() - 12, - program, + program: &program[3..], timer: 0, copier: None, } diff --git a/spec.md b/spec.md index 67094ea2..b5cbcc2d 100644 --- a/spec.md +++ b/spec.md @@ -1,6 +1,7 @@ # HoleyBytes ISA Specification # Bytecode format +- Holey Bytes program should start with following magic: `0xAB1E0B` - All numbers are encoded little-endian - There is 256 registers, they are represented by a byte - Immediate values are 64 bit From 73b998911ce9188c9c47cf0b2125eda704603243 Mon Sep 17 00:00:00 2001 From: Erin Date: Tue, 1 Aug 2023 22:17:20 +0200 Subject: [PATCH 048/116] a --- spec.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec.md b/spec.md index b5cbcc2d..1fdbfd7e 100644 --- a/spec.md +++ b/spec.md @@ -1,7 +1,7 @@ # HoleyBytes ISA Specification # Bytecode format -- Holey Bytes program should start with following magic: `0xAB1E0B` +- Holey Bytes program should start with following magic: `[0xAB, 0x1E, 0x0B]` - All numbers are encoded little-endian - There is 256 registers, they are represented by a byte - Immediate values are 64 bit From 8287b1bdc1b1af7d6cba79c246ca3c2562449b16 Mon Sep 17 00:00:00 2001 From: Erin Date: Tue, 1 Aug 2023 22:20:11 +0200 Subject: [PATCH 049/116] Changed magic --- hbasm/src/lib.rs | 2 +- hbbytecode/src/gen_valider.rs | 4 ++-- hbvm/src/lib.rs | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/hbasm/src/lib.rs b/hbasm/src/lib.rs index 0b301b96..f7690c05 100644 --- a/hbasm/src/lib.rs +++ b/hbasm/src/lib.rs @@ -72,7 +72,7 @@ impl Assembler { /// Why 12 bytes? That's the size of largest instruction parameter part. pub fn finalise(&mut self) { self.buf.extend([0; 12]); - self.buf[0..3].copy_from_slice(&[0xAB, 0x1E, 0x0B]); + self.buf[0..4].copy_from_slice(&0xAB1E0B_u32.to_le_bytes()); } } diff --git a/hbbytecode/src/gen_valider.rs b/hbbytecode/src/gen_valider.rs index c9d9ab73..d1e078f9 100644 --- a/hbbytecode/src/gen_valider.rs +++ b/hbbytecode/src/gen_valider.rs @@ -40,7 +40,7 @@ macro_rules! gen_valider { /// sound to execute. pub fn validate(mut program: &[u8]) -> Result<(), Error> { // Validate magic - if program.get(0..3) != Some(&[0xAB, 0x1E, 0x0B]) { + if program.get(0..4) != Some(&0xAB1E0B_u32.to_le_bytes()) { return Err(Error { kind: ErrorKind::MissingMagic, index: 0, @@ -67,7 +67,7 @@ macro_rules! gen_valider { } let start = program; - program = &program[3..]; + program = &program[4..]; loop { use crate::opcode::*; program = match program { diff --git a/hbvm/src/lib.rs b/hbvm/src/lib.rs index 67b1dda1..5c1021ad 100644 --- a/hbvm/src/lib.rs +++ b/hbvm/src/lib.rs @@ -73,7 +73,7 @@ impl<'a, PfHandler: HandlePageFault, const TIMER_QUOTIENT: usize> pfhandler: traph, pc: 0, program_len: program.len() - 12, - program: &program[3..], + program: &program[4..], timer: 0, copier: None, } From 629fc969c29a872d2c5fa7bbba181a380d4e32ad Mon Sep 17 00:00:00 2001 From: Erin Date: Mon, 7 Aug 2023 01:41:26 +0200 Subject: [PATCH 050/116] Spec update --- spec.md | 1 + 1 file changed, 1 insertion(+) diff --git a/spec.md b/spec.md index 1fdbfd7e..8c353b18 100644 --- a/spec.md +++ b/spec.md @@ -260,6 +260,7 @@ # Memory - Addresses are 64 bit +- Program should be in the same address space as all other data - Memory implementation is arbitrary - In case of accessing invalid address: - Program shall trap (LoadAccessEx, StoreAccessEx) with parameter of accessed address From aa186b35c25e9c888efc7b7a7f43668041bb5603 Mon Sep 17 00:00:00 2001 From: Erin Date: Mon, 7 Aug 2023 01:43:29 +0200 Subject: [PATCH 051/116] Spec update --- spec.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec.md b/spec.md index 8c353b18..e98852ff 100644 --- a/spec.md +++ b/spec.md @@ -261,13 +261,13 @@ # Memory - Addresses are 64 bit - Program should be in the same address space as all other data +- Address `0x0` is invalid and acessing it traps - Memory implementation is arbitrary - In case of accessing invalid address: - Program shall trap (LoadAccessEx, StoreAccessEx) with parameter of accessed address - Value of register when trapped is undefined ## Recommendations -- Leave address `0x0` as invalid - If paging used: - Leave first page invalid - Pages should be at least 4 KiB From f130a27685e701ca615a3fdf143ee2934c0ff9df Mon Sep 17 00:00:00 2001 From: Erin Date: Mon, 7 Aug 2023 01:50:21 +0200 Subject: [PATCH 052/116] Shrunk --- hbvm/src/lib.rs | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/hbvm/src/lib.rs b/hbvm/src/lib.rs index 5c1021ad..f71aeb95 100644 --- a/hbvm/src/lib.rs +++ b/hbvm/src/lib.rs @@ -11,9 +11,10 @@ // - Mapped pages should be at least 4 KiB #![no_std] - #![cfg_attr(feature = "nightly", feature(fn_align))] +use core::marker::PhantomData; + #[cfg(feature = "alloc")] extern crate alloc; @@ -47,11 +48,14 @@ pub struct Vm<'a, PfHandler, const TIMER_QUOTIENT: usize> { pub pc: usize, /// Program - program: &'a [u8], + program: *const u8, /// Cached program length (without unreachable end) program_len: usize, + /// Program lifetime + _program_lt: PhantomData<&'a [u8]>, + /// Program timer timer: usize, @@ -73,7 +77,8 @@ impl<'a, PfHandler: HandlePageFault, const TIMER_QUOTIENT: usize> pfhandler: traph, pc: 0, program_len: program.len() - 12, - program: &program[4..], + program: program[4..].as_ptr(), + _program_lt: Default::default(), timer: 0, copier: None, } @@ -121,7 +126,7 @@ impl<'a, PfHandler: HandlePageFault, const TIMER_QUOTIENT: usize> // - Yes, we assume you run 64 bit CPU. Else ?conradluget a better CPU // sorry 8 bit fans, HBVM won't run on your Speccy :( unsafe { - match *self.program.get_unchecked(self.pc) { + match *self.program.add(self.pc) { UN => { self.decode::<()>(); return Err(VmRunError::Unreachable); @@ -386,7 +391,7 @@ impl<'a, PfHandler: HandlePageFault, const TIMER_QUOTIENT: usize> /// Decode instruction operands #[inline] unsafe fn decode(&mut self) -> T { - let data = self.program.as_ptr().add(self.pc + 1).cast::().read(); + let data = self.program.add(self.pc + 1).cast::().read(); self.pc += 1 + size_of::(); data } From cdee99598ebcbe7764cd85e7efaacda57d608423 Mon Sep 17 00:00:00 2001 From: Erin Date: Tue, 8 Aug 2023 01:44:33 +0200 Subject: [PATCH 053/116] const perm check --- hbvm/src/mem/mod.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/hbvm/src/mem/mod.rs b/hbvm/src/mem/mod.rs index 8ef07b3d..f2d9ac71 100644 --- a/hbvm/src/mem/mod.rs +++ b/hbvm/src/mem/mod.rs @@ -479,7 +479,7 @@ pub mod perm_check { /// Page is readable #[inline(always)] - pub fn readable(perm: Permission) -> bool { + pub const fn readable(perm: Permission) -> bool { matches!( perm, Permission::Readonly | Permission::Write | Permission::Exec @@ -488,7 +488,7 @@ pub mod perm_check { /// Page is writable #[inline(always)] - pub fn writable(perm: Permission) -> bool { - perm == Permission::Write + pub const fn writable(perm: Permission) -> bool { + matches!(perm, Permission::Write) } } From 5264576274e4228c0a927d7eb7d2d5905effbdbf Mon Sep 17 00:00:00 2001 From: Erin Date: Tue, 8 Aug 2023 02:06:15 +0200 Subject: [PATCH 054/116] Reimplemented BMC --- hbvm/src/mem/bmc.rs | 28 ++++++---------------------- hbvm/src/mem/mod.rs | 4 ++-- 2 files changed, 8 insertions(+), 24 deletions(-) diff --git a/hbvm/src/mem/bmc.rs b/hbvm/src/mem/bmc.rs index 35d282db..0d47566a 100644 --- a/hbvm/src/mem/bmc.rs +++ b/hbvm/src/mem/bmc.rs @@ -1,7 +1,7 @@ use { super::MemoryAccessReason, crate::{ - mem::{perm_check, HandlePageFault, Memory}, + mem::{HandlePageFault, Memory}, VmRunError, }, core::{mem::MaybeUninit, task::Poll}, @@ -68,7 +68,7 @@ impl BlockCopier { Some(n) => self.src = n, None => return Poll::Ready(Err(BlkCopyError::OutOfBounds)), }; - + match self.dst.checked_add(BUF_SIZE as u64) { Some(n) => self.dst = n, None => return Poll::Ready(Err(BlkCopyError::OutOfBounds)), @@ -113,32 +113,16 @@ unsafe fn act( ) -> Result<(), BlkCopyError> { // Load to buffer memory - .memory_access( - MemoryAccessReason::Load, - src, - buf, - count, - perm_check::readable, - |src, dst, count| core::ptr::copy(src, dst, count), - traph, - ) - .map_err(|addr| BlkCopyError::Access { + .load(src, buf, count, traph) + .map_err(|super::LoadError(addr)| BlkCopyError::Access { access_reason: MemoryAccessReason::Load, addr, })?; // Store from buffer memory - .memory_access( - MemoryAccessReason::Store, - dst, - buf, - count, - perm_check::writable, - |dst, src, count| core::ptr::copy(src, dst, count), - traph, - ) - .map_err(|addr| BlkCopyError::Access { + .store(dst, buf, count, traph) + .map_err(|super::StoreError(addr)| BlkCopyError::Access { access_reason: MemoryAccessReason::Store, addr, })?; diff --git a/hbvm/src/mem/mod.rs b/hbvm/src/mem/mod.rs index f2d9ac71..38b99012 100644 --- a/hbvm/src/mem/mod.rs +++ b/hbvm/src/mem/mod.rs @@ -426,12 +426,12 @@ impl PageSize { /// Unhandled load access trap #[derive(Clone, Copy, Display, Debug, PartialEq, Eq)] #[display(fmt = "Load access error at address {_0:#x}")] -pub struct LoadError(u64); +pub struct LoadError(pub u64); /// Unhandled store access trap #[derive(Clone, Copy, Display, Debug, PartialEq, Eq)] #[display(fmt = "Store access error at address {_0:#x}")] -pub struct StoreError(u64); +pub struct StoreError(pub u64); /// There was no entry in page table to unmap /// From 82f23ec2e2decf960ae5d1052ec1010c558d532b Mon Sep 17 00:00:00 2001 From: Erin Date: Tue, 8 Aug 2023 02:48:47 +0200 Subject: [PATCH 055/116] Abstraction of memory --- hbvm/src/lib.rs | 29 +- hbvm/src/main.rs | 15 +- hbvm/src/mem/bmc.rs | 20 +- hbvm/src/mem/mod.rs | 460 +----------------------- hbvm/src/mem/pfhandler.rs | 20 -- hbvm/src/mem/softpaged/mod.rs | 475 +++++++++++++++++++++++++ hbvm/src/mem/{ => softpaged}/paging.rs | 0 7 files changed, 508 insertions(+), 511 deletions(-) delete mode 100644 hbvm/src/mem/pfhandler.rs create mode 100644 hbvm/src/mem/softpaged/mod.rs rename hbvm/src/mem/{ => softpaged}/paging.rs (100%) diff --git a/hbvm/src/lib.rs b/hbvm/src/lib.rs index f71aeb95..ed352300 100644 --- a/hbvm/src/lib.rs +++ b/hbvm/src/lib.rs @@ -26,12 +26,12 @@ use { hbbytecode::{ valider, OpParam, ParamBB, ParamBBB, ParamBBBB, ParamBBD, ParamBBDH, ParamBBW, ParamBD, }, - mem::{bmc::BlockCopier, HandlePageFault, Memory}, + mem::bmc::BlockCopier, value::{Value, ValueVariant}, }; /// HoleyBytes Virtual Machine -pub struct Vm<'a, PfHandler, const TIMER_QUOTIENT: usize> { +pub struct Vm<'a, Memory, const TIMER_QUOTIENT: usize> { /// Holds 256 registers /// /// Writing to register 0 is considered undefined behaviour @@ -41,9 +41,6 @@ pub struct Vm<'a, PfHandler, const TIMER_QUOTIENT: usize> { /// Memory implementation pub memory: Memory, - /// Trap handler - pub pfhandler: PfHandler, - /// Program counter pub pc: usize, @@ -63,18 +60,18 @@ pub struct Vm<'a, PfHandler, const TIMER_QUOTIENT: usize> { copier: Option, } -impl<'a, PfHandler: HandlePageFault, const TIMER_QUOTIENT: usize> - Vm<'a, PfHandler, TIMER_QUOTIENT> +impl<'a, Memory, const TIMER_QUOTIENT: usize> Vm<'a, Memory, TIMER_QUOTIENT> +where + Memory: mem::Memory, { /// Create a new VM with program and trap handler /// /// # Safety /// Program code has to be validated - pub unsafe fn new_unchecked(program: &'a [u8], traph: PfHandler, memory: Memory) -> Self { + pub unsafe fn new_unchecked(program: &'a [u8], memory: Memory) -> Self { Self { registers: [Value::from(0_u64); 256], memory, - pfhandler: traph, pc: 0, program_len: program.len() - 12, program: program[4..].as_ptr(), @@ -85,13 +82,9 @@ 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, - memory: Memory, - ) -> Result { + pub fn new_validated(program: &'a [u8], memory: Memory) -> Result { valider::validate(program)?; - Ok(unsafe { Self::new_unchecked(program, traph, memory) }) + Ok(unsafe { Self::new_unchecked(program, memory) }) } /// Execute program @@ -250,7 +243,6 @@ impl<'a, PfHandler: HandlePageFault, const TIMER_QUOTIENT: usize> .add(usize::from(dst) + usize::from(n)) .cast(), usize::from(count).saturating_sub(n.into()), - &mut self.pfhandler, )?; } ST => { @@ -260,14 +252,13 @@ impl<'a, PfHandler: HandlePageFault, const TIMER_QUOTIENT: usize> self.ldst_addr_uber(dst, base, off, count, 0)?, self.registers.as_ptr().add(usize::from(dst)).cast(), count.into(), - &mut self.pfhandler, )?; } BMC => { // Block memory copy match if let Some(copier) = &mut self.copier { // There is some copier, poll. - copier.poll(&mut self.memory, &mut self.pfhandler) + copier.poll(&mut self.memory) } else { // There is none, make one! let ParamBBD(src, dst, count) = self.decode(); @@ -284,7 +275,7 @@ impl<'a, PfHandler: HandlePageFault, const TIMER_QUOTIENT: usize> self.copier .as_mut() .unwrap_unchecked() // SAFETY: We just assigned there - .poll(&mut self.memory, &mut self.pfhandler) + .poll(&mut self.memory) } { // We are done, shift program counter core::task::Poll::Ready(Ok(())) => { diff --git a/hbvm/src/main.rs b/hbvm/src/main.rs index f3cc2d0c..fd46a3e5 100644 --- a/hbvm/src/main.rs +++ b/hbvm/src/main.rs @@ -1,9 +1,8 @@ +use hbvm::mem::softpaged::{HandlePageFault, PageSize, SoftPagedMem}; + use { hbbytecode::valider::validate, - hbvm::{ - mem::{HandlePageFault, Memory, MemoryAccessReason, PageSize}, - Vm, - }, + hbvm::{mem::MemoryAccessReason, Vm}, std::io::{stdin, Read}, }; @@ -16,7 +15,8 @@ fn main() -> Result<(), Box> { return Ok(()); } else { unsafe { - let mut vm = Vm::<_, 0>::new_unchecked(&prog, TestTrapHandler, Default::default()); + let mut vm = + Vm::<_, 0>::new_unchecked(&prog, SoftPagedMem::::default()); let data = { let ptr = std::alloc::alloc_zeroed(std::alloc::Layout::from_size_align_unchecked( 4096, 4096, @@ -31,7 +31,7 @@ fn main() -> Result<(), Box> { .map( data, 0, - hbvm::mem::paging::Permission::Write, + hbvm::mem::softpaged::paging::Permission::Write, PageSize::Size4K, ) .unwrap(); @@ -54,12 +54,13 @@ pub fn time() -> u32 { 9 } +#[derive(Default)] struct TestTrapHandler; impl HandlePageFault for TestTrapHandler { fn page_fault( &mut self, _: MemoryAccessReason, - _: &mut Memory, + //_: &mut Memory, _: u64, _: PageSize, _: *mut u8, diff --git a/hbvm/src/mem/bmc.rs b/hbvm/src/mem/bmc.rs index 0d47566a..4155ea15 100644 --- a/hbvm/src/mem/bmc.rs +++ b/hbvm/src/mem/bmc.rs @@ -1,9 +1,6 @@ use { super::MemoryAccessReason, - crate::{ - mem::{HandlePageFault, Memory}, - VmRunError, - }, + crate::{mem::Memory, VmRunError}, core::{mem::MaybeUninit, task::Poll}, }; @@ -40,11 +37,7 @@ impl BlockCopier { /// /// # Safety /// - Same as for [`Memory::load`] and [`Memory::store`] - pub unsafe fn poll( - &mut self, - memory: &mut Memory, - traph: &mut impl HandlePageFault, - ) -> Poll> { + pub unsafe fn poll(&mut self, memory: &mut impl Memory) -> Poll> { // Safety: Assuming uninit of array of MaybeUninit is sound let mut buf = AlignedBuf(MaybeUninit::uninit().assume_init()); @@ -56,7 +49,6 @@ impl BlockCopier { self.dst, buf.0.as_mut_ptr().cast(), BUF_SIZE, - traph, ) { return Poll::Ready(Err(e)); } @@ -92,7 +84,6 @@ impl BlockCopier { self.dst, buf.0.as_mut_ptr().cast(), self.rem, - traph, ) { return Poll::Ready(Err(e)); } @@ -104,16 +95,15 @@ impl BlockCopier { #[inline] unsafe fn act( - memory: &mut Memory, + memory: &mut impl Memory, src: u64, dst: u64, buf: *mut u8, count: usize, - traph: &mut impl HandlePageFault, ) -> Result<(), BlkCopyError> { // Load to buffer memory - .load(src, buf, count, traph) + .load(src, buf, count) .map_err(|super::LoadError(addr)| BlkCopyError::Access { access_reason: MemoryAccessReason::Load, addr, @@ -121,7 +111,7 @@ unsafe fn act( // Store from buffer memory - .store(dst, buf, count, traph) + .store(dst, buf, count) .map_err(|super::StoreError(addr)| BlkCopyError::Access { access_reason: MemoryAccessReason::Store, addr, diff --git a/hbvm/src/mem/mod.rs b/hbvm/src/mem/mod.rs index 38b99012..2391a3bf 100644 --- a/hbvm/src/mem/mod.rs +++ b/hbvm/src/mem/mod.rs @@ -1,426 +1,27 @@ //! Program memory implementation pub mod bmc; -pub mod paging; +pub mod softpaged; -mod pfhandler; +use {super::VmRunError, derive_more::Display}; -pub use pfhandler::HandlePageFault; - -use { - super::VmRunError, - derive_more::Display, - paging::{PageTable, Permission}, -}; - -#[cfg(feature = "alloc")] -use {alloc::boxed::Box, paging::PtEntry}; - -/// HoleyBytes virtual memory -#[derive(Clone, Debug)] -pub struct Memory { - /// Root page table - pub root_pt: *mut PageTable, -} - -#[cfg(feature = "alloc")] -impl Default for Memory { - fn default() -> Self { - Self { - root_pt: Box::into_raw(Default::default()), - } - } -} - -#[cfg(feature = "alloc")] -impl Drop for Memory { - fn drop(&mut self) { - let _ = unsafe { Box::from_raw(self.root_pt) }; - } -} - -impl Memory { - /// Maps host's memory into VM's memory +pub trait Memory { + /// Load data from memory on address /// /// # Safety - /// - Your faith in the gods of UB - /// - Addr-san claims it's fine but who knows is she isn't lying :ferrisSus: - /// - Alright, Miri-sama is also fine with this, who knows why - #[cfg(feature = "alloc")] - pub unsafe fn map( - &mut self, - host: *mut u8, - target: u64, - perm: Permission, - pagesize: PageSize, - ) -> Result<(), MapError> { - let mut current_pt = self.root_pt; + /// - Shall not overrun the buffer + unsafe fn load(&mut self, addr: u64, target: *mut u8, count: usize) -> Result<(), LoadError>; - // Decide on what level depth are we going - let lookup_depth = match pagesize { - PageSize::Size4K => 0, - PageSize::Size2M => 1, - PageSize::Size1G => 2, - }; - - // Walk pagetable levels - for lvl in (lookup_depth + 1..5).rev() { - let entry = (*current_pt) - .table - .get_unchecked_mut(addr_extract_index(target, lvl)); - - let ptr = entry.ptr(); - match entry.permission() { - // Still not on target and already seeing empty entry? - // No worries! Let's create one (allocates). - Permission::Empty => { - // Increase children count - (*current_pt).childen += 1; - - let table = Box::into_raw(Box::new(paging::PtPointedData { - pt: PageTable::default(), - })); - - core::ptr::write(entry, PtEntry::new(table, Permission::Node)); - current_pt = table as _; - } - // Continue walking - Permission::Node => current_pt = ptr as _, - - // There is some entry on place of node - _ => return Err(MapError::PageOnNode), - } - } - - let node = (*current_pt) - .table - .get_unchecked_mut(addr_extract_index(target, lookup_depth)); - - // Check if node is not mapped - if node.permission() != Permission::Empty { - return Err(MapError::AlreadyMapped); - } - - // Write entry - (*current_pt).childen += 1; - core::ptr::write(node, PtEntry::new(host.cast(), perm)); - - Ok(()) - } - - /// Unmaps pages from VM's memory - /// - /// If errors, it only means there is no entry to unmap and in most cases - /// just should be ignored. - #[cfg(feature = "alloc")] - pub fn unmap(&mut self, addr: u64) -> Result<(), NothingToUnmap> { - let mut current_pt = self.root_pt; - let mut page_tables = [core::ptr::null_mut(); 5]; - - // Walk page table in reverse - for lvl in (0..5).rev() { - let entry = unsafe { - (*current_pt) - .table - .get_unchecked_mut(addr_extract_index(addr, lvl)) - }; - - let ptr = entry.ptr(); - match entry.permission() { - // Nothing is there, throw an error, not critical! - Permission::Empty => return Err(NothingToUnmap), - // Node – Save to visited pagetables and continue walking - Permission::Node => { - page_tables[lvl as usize] = entry; - current_pt = ptr as _ - } - // Page entry – zero it out! - // Zero page entry is completely valid entry with - // empty permission - no UB here! - _ => unsafe { - core::ptr::write_bytes(entry, 0, 1); - break; - }, - } - } - - // Now walk in order visited page tables - for entry in page_tables.into_iter() { - // Level not visited, skip. - if entry.is_null() { - continue; - } - - unsafe { - let children = &mut (*(*entry).ptr()).pt.childen; - *children -= 1; // Decrease children count - - // If there are no children, deallocate. - if *children == 0 { - let _ = Box::from_raw((*entry).ptr() as *mut PageTable); - - // Zero visited entry - core::ptr::write_bytes(entry, 0, 1); - } else { - break; - } - } - } - - Ok(()) - } - - /// Load value from an address + /// Store data to memory on address /// /// # Safety - /// Applies same conditions as for [`core::ptr::copy_nonoverlapping`] - pub unsafe fn load( - &mut self, - addr: u64, - target: *mut u8, - count: usize, - traph: &mut impl HandlePageFault, - ) -> Result<(), LoadError> { - self.memory_access( - MemoryAccessReason::Load, - addr, - target, - count, - perm_check::readable, - |src, dst, count| core::ptr::copy_nonoverlapping(src, dst, count), - traph, - ) - .map_err(LoadError) - } - - /// Store value to an address - /// - /// # Safety - /// Applies same conditions as for [`core::ptr::copy_nonoverlapping`] - pub unsafe fn store( + /// - Shall not overrun the buffer + unsafe fn store( &mut self, addr: u64, source: *const u8, count: usize, - traph: &mut impl HandlePageFault, - ) -> Result<(), StoreError> { - self.memory_access( - MemoryAccessReason::Store, - addr, - source.cast_mut(), - count, - perm_check::writable, - |dst, src, count| core::ptr::copy_nonoverlapping(src, dst, count), - traph, - ) - .map_err(StoreError) - } - - // Everyone behold, the holy function, the god of HBVM memory accesses! - - /// Split address to pages, check their permissions and feed pointers with offset - /// to a specified function. - /// - /// If page is not found, execute page fault trap handler. - #[allow(clippy::too_many_arguments)] // Silence peasant - fn memory_access( - &mut self, - reason: MemoryAccessReason, - src: u64, - mut dst: *mut u8, - len: usize, - permission_check: fn(Permission) -> bool, - action: fn(*mut u8, *mut u8, usize), - traph: &mut impl HandlePageFault, - ) -> Result<(), u64> { - // Create new splitter - let mut pspl = AddrPageLookuper::new(src, len, self.root_pt); - loop { - match pspl.next() { - // Page is found - Some(Ok(AddrPageLookupOk { - vaddr, - ptr, - size, - perm, - })) => { - if !permission_check(perm) { - return Err(vaddr); - } - - // Perform specified memory action and bump destination pointer - action(ptr, dst, size); - dst = unsafe { dst.add(size) }; - } - // No page found - Some(Err(AddrPageLookupError { addr, size })) => { - // Attempt to execute page fault handler - if traph.page_fault(reason, self, addr, size, dst) { - // Shift the splitter address - pspl.bump(size); - - // Bump dst pointer - dst = unsafe { dst.add(size as _) }; - } else { - return Err(addr); // Unhandleable, VM will yield. - } - } - // No remaining pages, we are done! - None => return Ok(()), - } - } - } -} - -/// Good result from address split -struct AddrPageLookupOk { - /// Virtual address - vaddr: u64, - - /// Pointer to the start for perform operation - ptr: *mut u8, - - /// Size to the end of page / end of desired size - size: usize, - - /// Page permission - perm: Permission, -} - -/// Errornous address split result -struct AddrPageLookupError { - /// Address of failure - addr: u64, - - /// Requested page size - size: PageSize, -} - -/// Address splitter into pages -struct AddrPageLookuper { - /// Current address - addr: u64, - - /// Size left - size: usize, - - /// Page table - pagetable: *const PageTable, -} - -impl AddrPageLookuper { - /// Create a new page lookuper - #[inline] - pub const fn new(addr: u64, size: usize, pagetable: *const PageTable) -> Self { - Self { - addr, - size, - pagetable, - } - } - - /// Bump address by size X - fn bump(&mut self, page_size: PageSize) { - self.addr += page_size as u64; - self.size = self.size.saturating_sub(page_size as _); - } -} - -impl Iterator for AddrPageLookuper { - type Item = Result; - - fn next(&mut self) -> Option { - // The end, everything is fine - if self.size == 0 { - return None; - } - - let (base, perm, size, offset) = 'a: { - let mut current_pt = self.pagetable; - - // Walk the page table - for lvl in (0..5).rev() { - // Get an entry - unsafe { - let entry = (*current_pt) - .table - .get_unchecked(addr_extract_index(self.addr, lvl)); - - let ptr = entry.ptr(); - match entry.permission() { - // No page → page fault - Permission::Empty => { - return Some(Err(AddrPageLookupError { - addr: self.addr, - size: PageSize::from_lvl(lvl)?, - })) - } - - // Node → proceed waking - Permission::Node => current_pt = ptr as _, - - // Leaf → return relevant data - perm => { - break 'a ( - // Pointer in host memory - ptr as *mut u8, - perm, - PageSize::from_lvl(lvl)?, - // In-page offset - addr_extract_index(self.addr, lvl), - ); - } - } - } - } - return None; // Reached the end (should not happen) - }; - - // Get available byte count in the selected page with offset - let avail = (size as usize - offset).clamp(0, self.size); - self.bump(size); - - Some(Ok(AddrPageLookupOk { - vaddr: self.addr, - ptr: unsafe { base.add(offset) }, // Return pointer to the start of region - size: avail, - perm, - })) - } -} - -/// Extract index in page table on specified level -/// -/// The level shall not be larger than 4, otherwise -/// the output of the function is unspecified (yes, it can also panic :) -pub fn addr_extract_index(addr: u64, lvl: u8) -> usize { - debug_assert!(lvl <= 4); - usize::try_from((addr >> (lvl * 8 + 12)) & ((1 << 8) - 1)).expect("?conradluget a better CPU") -} - -/// Page size -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub enum PageSize { - /// 4 KiB page (on level 0) - Size4K = 4096, - - /// 2 MiB page (on level 1) - Size2M = 1024 * 1024 * 2, - - /// 1 GiB page (on level 2) - Size1G = 1024 * 1024 * 1024, -} - -impl PageSize { - /// Convert page table level to size of page - const fn from_lvl(lvl: u8) -> Option { - match lvl { - 0 => Some(PageSize::Size4K), - 1 => Some(PageSize::Size2M), - 2 => Some(PageSize::Size1G), - _ => None, - } - } + ) -> Result<(), StoreError>; } /// Unhandled load access trap @@ -433,15 +34,6 @@ pub struct LoadError(pub u64); #[display(fmt = "Store access error at address {_0:#x}")] pub struct StoreError(pub u64); -/// There was no entry in page table to unmap -/// -/// No worry, don't panic, nothing bad has happened, -/// but if you are 120% sure there should be something, -/// double-check your addresses. -#[derive(Clone, Copy, Display, Debug)] -#[display(fmt = "There was no entry to unmap")] -pub struct NothingToUnmap; - /// Reason to access memory #[derive(Clone, Copy, Display, Debug, PartialEq, Eq)] pub enum MemoryAccessReason { @@ -460,35 +52,3 @@ impl From for VmRunError { Self::StoreAccessEx(value.0) } } - -/// Error mapping -#[derive(Clone, Copy, Display, Debug, PartialEq, Eq)] -pub enum MapError { - /// Entry was already mapped - #[display(fmt = "There is already a page mapped on specified address")] - AlreadyMapped, - /// When walking a page entry was - /// encounterd. - #[display(fmt = "There was a page mapped on the way instead of node")] - PageOnNode, -} - -/// Permisison checks -pub mod perm_check { - use super::paging::Permission; - - /// Page is readable - #[inline(always)] - pub const fn readable(perm: Permission) -> bool { - matches!( - perm, - Permission::Readonly | Permission::Write | Permission::Exec - ) - } - - /// Page is writable - #[inline(always)] - pub const fn writable(perm: Permission) -> bool { - matches!(perm, Permission::Write) - } -} diff --git a/hbvm/src/mem/pfhandler.rs b/hbvm/src/mem/pfhandler.rs deleted file mode 100644 index fb0cb229..00000000 --- a/hbvm/src/mem/pfhandler.rs +++ /dev/null @@ -1,20 +0,0 @@ -//! Program trap handling interfaces - -use super::{Memory, MemoryAccessReason, PageSize}; - -/// Handle VM traps -pub trait HandlePageFault { - /// Handle page fault - /// - /// Return true if handling was sucessful, - /// otherwise the program will be interrupted and will - /// yield an error. - fn page_fault( - &mut self, - reason: MemoryAccessReason, - memory: &mut Memory, - vaddr: u64, - size: PageSize, - dataptr: *mut u8, - ) -> bool; -} diff --git a/hbvm/src/mem/softpaged/mod.rs b/hbvm/src/mem/softpaged/mod.rs new file mode 100644 index 00000000..2ec72e57 --- /dev/null +++ b/hbvm/src/mem/softpaged/mod.rs @@ -0,0 +1,475 @@ +//! Platform independent, software paged memory implementation + +pub mod paging; + +use { + super::{LoadError, Memory, MemoryAccessReason, StoreError}, + derive_more::Display, + paging::{PageTable, Permission}, +}; + +#[cfg(feature = "alloc")] +use {alloc::boxed::Box, paging::PtEntry}; + +/// HoleyBytes software paged memory +#[derive(Clone, Debug)] +pub struct SoftPagedMem { + /// Root page table + pub root_pt: *mut PageTable, + /// Page fault handler + pub pf_handler: PfHandler, +} + +impl Memory for SoftPagedMem { + /// Load value from an address + /// + /// # Safety + /// Applies same conditions as for [`core::ptr::copy_nonoverlapping`] + unsafe fn load(&mut self, addr: u64, target: *mut u8, count: usize) -> Result<(), LoadError> { + self.memory_access( + MemoryAccessReason::Load, + addr, + target, + count, + perm_check::readable, + |src, dst, count| core::ptr::copy_nonoverlapping(src, dst, count), + ) + .map_err(LoadError) + } + + /// Store value to an address + /// + /// # Safety + /// Applies same conditions as for [`core::ptr::copy_nonoverlapping`] + unsafe fn store( + &mut self, + addr: u64, + source: *const u8, + count: usize, + ) -> Result<(), StoreError> { + self.memory_access( + MemoryAccessReason::Store, + addr, + source.cast_mut(), + count, + perm_check::writable, + |dst, src, count| core::ptr::copy_nonoverlapping(src, dst, count), + ) + .map_err(StoreError) + } +} + +impl SoftPagedMem { + // Everyone behold, the holy function, the god of HBVM memory accesses! + + /// Split address to pages, check their permissions and feed pointers with offset + /// to a specified function. + /// + /// If page is not found, execute page fault trap handler. + #[allow(clippy::too_many_arguments)] // Silence peasant + fn memory_access( + &mut self, + reason: MemoryAccessReason, + src: u64, + mut dst: *mut u8, + len: usize, + permission_check: fn(Permission) -> bool, + action: fn(*mut u8, *mut u8, usize), + ) -> Result<(), u64> { + // Create new splitter + let mut pspl = AddrPageLookuper::new(src, len, self.root_pt); + loop { + match pspl.next() { + // Page is found + Some(Ok(AddrPageLookupOk { + vaddr, + ptr, + size, + perm, + })) => { + if !permission_check(perm) { + return Err(vaddr); + } + + // Perform specified memory action and bump destination pointer + action(ptr, dst, size); + dst = unsafe { dst.add(size) }; + } + // No page found + Some(Err(AddrPageLookupError { addr, size })) => { + // Attempt to execute page fault handler + if self.pf_handler.page_fault(reason, addr, size, dst) { + // Shift the splitter address + pspl.bump(size); + + // Bump dst pointer + dst = unsafe { dst.add(size as _) }; + } else { + return Err(addr); // Unhandleable, VM will yield. + } + } + // No remaining pages, we are done! + None => return Ok(()), + } + } + } +} + +/// Good result from address split +struct AddrPageLookupOk { + /// Virtual address + vaddr: u64, + + /// Pointer to the start for perform operation + ptr: *mut u8, + + /// Size to the end of page / end of desired size + size: usize, + + /// Page permission + perm: Permission, +} + +/// Errornous address split result +struct AddrPageLookupError { + /// Address of failure + addr: u64, + + /// Requested page size + size: PageSize, +} + +/// Address splitter into pages +struct AddrPageLookuper { + /// Current address + addr: u64, + + /// Size left + size: usize, + + /// Page table + pagetable: *const PageTable, +} + +impl AddrPageLookuper { + /// Create a new page lookuper + #[inline] + pub const fn new(addr: u64, size: usize, pagetable: *const PageTable) -> Self { + Self { + addr, + size, + pagetable, + } + } + + /// Bump address by size X + fn bump(&mut self, page_size: PageSize) { + self.addr += page_size as u64; + self.size = self.size.saturating_sub(page_size as _); + } +} + +impl Iterator for AddrPageLookuper { + type Item = Result; + + fn next(&mut self) -> Option { + // The end, everything is fine + if self.size == 0 { + return None; + } + + let (base, perm, size, offset) = 'a: { + let mut current_pt = self.pagetable; + + // Walk the page table + for lvl in (0..5).rev() { + // Get an entry + unsafe { + let entry = (*current_pt) + .table + .get_unchecked(addr_extract_index(self.addr, lvl)); + + let ptr = entry.ptr(); + match entry.permission() { + // No page → page fault + Permission::Empty => { + return Some(Err(AddrPageLookupError { + addr: self.addr, + size: PageSize::from_lvl(lvl)?, + })) + } + + // Node → proceed waking + Permission::Node => current_pt = ptr as _, + + // Leaf → return relevant data + perm => { + break 'a ( + // Pointer in host memory + ptr as *mut u8, + perm, + PageSize::from_lvl(lvl)?, + // In-page offset + addr_extract_index(self.addr, lvl), + ); + } + } + } + } + return None; // Reached the end (should not happen) + }; + + // Get available byte count in the selected page with offset + let avail = (size as usize - offset).clamp(0, self.size); + self.bump(size); + + Some(Ok(AddrPageLookupOk { + vaddr: self.addr, + ptr: unsafe { base.add(offset) }, // Return pointer to the start of region + size: avail, + perm, + })) + } +} + +#[cfg(feature = "alloc")] +impl Default for SoftPagedMem { + fn default() -> Self { + Self { + root_pt: Box::into_raw(Default::default()), + pf_handler: Default::default(), + } + } +} + +#[cfg(feature = "alloc")] +impl Drop for SoftPagedMem { + fn drop(&mut self) { + let _ = unsafe { Box::from_raw(self.root_pt) }; + } +} + +#[cfg(feature = "alloc")] +impl SoftPagedMem { + /// Maps host's memory into VM's memory + /// + /// # Safety + /// - Your faith in the gods of UB + /// - Addr-san claims it's fine but who knows is she isn't lying :ferrisSus: + /// - Alright, Miri-sama is also fine with this, who knows why + pub unsafe fn map( + &mut self, + host: *mut u8, + target: u64, + perm: Permission, + pagesize: PageSize, + ) -> Result<(), MapError> { + let mut current_pt = self.root_pt; + + // Decide on what level depth are we going + let lookup_depth = match pagesize { + PageSize::Size4K => 0, + PageSize::Size2M => 1, + PageSize::Size1G => 2, + }; + + // Walk pagetable levels + for lvl in (lookup_depth + 1..5).rev() { + let entry = (*current_pt) + .table + .get_unchecked_mut(addr_extract_index(target, lvl)); + + let ptr = entry.ptr(); + match entry.permission() { + // Still not on target and already seeing empty entry? + // No worries! Let's create one (allocates). + Permission::Empty => { + // Increase children count + (*current_pt).childen += 1; + + let table = Box::into_raw(Box::new(paging::PtPointedData { + pt: PageTable::default(), + })); + + core::ptr::write(entry, PtEntry::new(table, Permission::Node)); + current_pt = table as _; + } + // Continue walking + Permission::Node => current_pt = ptr as _, + + // There is some entry on place of node + _ => return Err(MapError::PageOnNode), + } + } + + let node = (*current_pt) + .table + .get_unchecked_mut(addr_extract_index(target, lookup_depth)); + + // Check if node is not mapped + if node.permission() != Permission::Empty { + return Err(MapError::AlreadyMapped); + } + + // Write entry + (*current_pt).childen += 1; + core::ptr::write(node, PtEntry::new(host.cast(), perm)); + + Ok(()) + } + + /// Unmaps pages from VM's memory + /// + /// If errors, it only means there is no entry to unmap and in most cases + /// just should be ignored. + pub fn unmap(&mut self, addr: u64) -> Result<(), NothingToUnmap> { + let mut current_pt = self.root_pt; + let mut page_tables = [core::ptr::null_mut(); 5]; + + // Walk page table in reverse + for lvl in (0..5).rev() { + let entry = unsafe { + (*current_pt) + .table + .get_unchecked_mut(addr_extract_index(addr, lvl)) + }; + + let ptr = entry.ptr(); + match entry.permission() { + // Nothing is there, throw an error, not critical! + Permission::Empty => return Err(NothingToUnmap), + // Node – Save to visited pagetables and continue walking + Permission::Node => { + page_tables[lvl as usize] = entry; + current_pt = ptr as _ + } + // Page entry – zero it out! + // Zero page entry is completely valid entry with + // empty permission - no UB here! + _ => unsafe { + core::ptr::write_bytes(entry, 0, 1); + break; + }, + } + } + + // Now walk in order visited page tables + for entry in page_tables.into_iter() { + // Level not visited, skip. + if entry.is_null() { + continue; + } + + unsafe { + let children = &mut (*(*entry).ptr()).pt.childen; + *children -= 1; // Decrease children count + + // If there are no children, deallocate. + if *children == 0 { + let _ = Box::from_raw((*entry).ptr() as *mut PageTable); + + // Zero visited entry + core::ptr::write_bytes(entry, 0, 1); + } else { + break; + } + } + } + + Ok(()) + } +} + +/// Extract index in page table on specified level +/// +/// The level shall not be larger than 4, otherwise +/// the output of the function is unspecified (yes, it can also panic :) +pub fn addr_extract_index(addr: u64, lvl: u8) -> usize { + debug_assert!(lvl <= 4); + usize::try_from((addr >> (lvl * 8 + 12)) & ((1 << 8) - 1)).expect("?conradluget a better CPU") +} + +/// Page size +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum PageSize { + /// 4 KiB page (on level 0) + Size4K = 4096, + + /// 2 MiB page (on level 1) + Size2M = 1024 * 1024 * 2, + + /// 1 GiB page (on level 2) + Size1G = 1024 * 1024 * 1024, +} + +impl PageSize { + /// Convert page table level to size of page + const fn from_lvl(lvl: u8) -> Option { + match lvl { + 0 => Some(PageSize::Size4K), + 1 => Some(PageSize::Size2M), + 2 => Some(PageSize::Size1G), + _ => None, + } + } +} + +/// Error mapping +#[derive(Clone, Copy, Display, Debug, PartialEq, Eq)] +pub enum MapError { + /// Entry was already mapped + #[display(fmt = "There is already a page mapped on specified address")] + AlreadyMapped, + /// When walking a page entry was + /// encounterd. + #[display(fmt = "There was a page mapped on the way instead of node")] + PageOnNode, +} + +/// There was no entry in page table to unmap +/// +/// No worry, don't panic, nothing bad has happened, +/// but if you are 120% sure there should be something, +/// double-check your addresses. +#[derive(Clone, Copy, Display, Debug)] +#[display(fmt = "There was no entry to unmap")] +pub struct NothingToUnmap; + +/// Permisison checks +pub mod perm_check { + use super::paging::Permission; + + /// Page is readable + #[inline(always)] + pub const fn readable(perm: Permission) -> bool { + matches!( + perm, + Permission::Readonly | Permission::Write | Permission::Exec + ) + } + + /// Page is writable + #[inline(always)] + pub const fn writable(perm: Permission) -> bool { + matches!(perm, Permission::Write) + } +} + +/// Handle VM traps +pub trait HandlePageFault { + /// Handle page fault + /// + /// Return true if handling was sucessful, + /// otherwise the program will be interrupted and will + /// yield an error. + fn page_fault( + &mut self, + reason: MemoryAccessReason, + // memory: &mut SoftPagedMem, TODO: Make work + vaddr: u64, + size: PageSize, + dataptr: *mut u8, + ) -> bool + where + Self: Sized; +} diff --git a/hbvm/src/mem/paging.rs b/hbvm/src/mem/softpaged/paging.rs similarity index 100% rename from hbvm/src/mem/paging.rs rename to hbvm/src/mem/softpaged/paging.rs From bf50bcb2035c64976db23fd3eededc2640bddb5a Mon Sep 17 00:00:00 2001 From: Erin Date: Tue, 8 Aug 2023 03:03:15 +0200 Subject: [PATCH 056/116] Changed stuff aroud --- hbbytecode/src/lib.rs | 3 +- hbvm/src/{mem => }/bmc.rs | 12 ++- hbvm/src/lib.rs | 75 ++++++++++++++++--- hbvm/src/main.rs | 9 ++- hbvm/src/mem/mod.rs | 54 ------------- hbvm/src/{mem/softpaged => softpaging}/mod.rs | 0 .../{mem/softpaged => softpaging}/paging.rs | 2 + hbvm/src/value.rs | 19 ++++- 8 files changed, 99 insertions(+), 75 deletions(-) rename hbvm/src/{mem => }/bmc.rs (92%) delete mode 100644 hbvm/src/mem/mod.rs rename hbvm/src/{mem/softpaged => softpaging}/mod.rs (100%) rename hbvm/src/{mem/softpaged => softpaging}/paging.rs (97%) diff --git a/hbbytecode/src/lib.rs b/hbbytecode/src/lib.rs index 36c3a370..a1c9862c 100644 --- a/hbbytecode/src/lib.rs +++ b/hbbytecode/src/lib.rs @@ -17,6 +17,7 @@ macro_rules! constmod { }; } +#[allow(rustdoc::invalid_rust_codeblocks)] /// Invoke macro with bytecode definition /// # Input syntax /// ```no_run @@ -31,7 +32,7 @@ macro_rules! constmod { /// - Per-instructions there will be generated opcode-specific functions calling the generic ones /// - Operand types /// - R: Register (u8) -/// - I: Immediate (implements [`crate::Imm`] trait) +/// - I: Immediate /// - L: Memory load / store size (u16) /// - Other types are identity-mapped /// diff --git a/hbvm/src/mem/bmc.rs b/hbvm/src/bmc.rs similarity index 92% rename from hbvm/src/mem/bmc.rs rename to hbvm/src/bmc.rs index 4155ea15..391579f6 100644 --- a/hbvm/src/mem/bmc.rs +++ b/hbvm/src/bmc.rs @@ -1,16 +1,18 @@ +//! Block memory copier state machine + use { - super::MemoryAccessReason, - crate::{mem::Memory, VmRunError}, + super::{Memory, MemoryAccessReason, VmRunError}, core::{mem::MaybeUninit, task::Poll}, }; -// Buffer size (defaults to 4 KiB, a smallest page size on most platforms) +/// Buffer size (defaults to 4 KiB, a smallest page size on most platforms) const BUF_SIZE: usize = 4096; -// This should be equal to `BUF_SIZE` +/// Buffer of possibly uninitialised bytes, aligned to [`BUF_SIZE`] #[repr(align(4096))] struct AlignedBuf([MaybeUninit; BUF_SIZE]); +/// State for block memory copy pub struct BlockCopier { /// Source address src: u64, @@ -23,6 +25,7 @@ pub struct BlockCopier { } impl BlockCopier { + /// Construct a new one #[inline] pub fn new(src: u64, dst: u64, count: usize) -> Self { Self { @@ -93,6 +96,7 @@ impl BlockCopier { } } +/// Load to buffer and store from buffer #[inline] unsafe fn act( memory: &mut impl Memory, diff --git a/hbvm/src/lib.rs b/hbvm/src/lib.rs index ed352300..84447d98 100644 --- a/hbvm/src/lib.rs +++ b/hbvm/src/lib.rs @@ -2,8 +2,8 @@ //! //! # Alloc feature //! - Enabled by default -//! - Provides [`mem::Memory`] mapping / unmapping, as well as -//! [`Default`] and [`Drop`] implementation +//! - Provides mapping / unmapping, as well as [`Default`] and [`Drop`] +//! implementations for soft-paged memory implementation // # General safety notice: // - Validation has to assure there is 256 registers (r0 - r255) @@ -12,26 +12,30 @@ #![no_std] #![cfg_attr(feature = "nightly", feature(fn_align))] +#![warn(missing_docs, clippy::missing_docs_in_private_items)] use core::marker::PhantomData; #[cfg(feature = "alloc")] extern crate alloc; -pub mod mem; +pub mod softpaging; pub mod value; +mod bmc; + use { + bmc::BlockCopier, core::{cmp::Ordering, mem::size_of, ops}, + derive_more::Display, hbbytecode::{ valider, OpParam, ParamBB, ParamBBB, ParamBBBB, ParamBBD, ParamBBDH, ParamBBW, ParamBD, }, - mem::bmc::BlockCopier, value::{Value, ValueVariant}, }; /// HoleyBytes Virtual Machine -pub struct Vm<'a, Memory, const TIMER_QUOTIENT: usize> { +pub struct Vm<'a, Mem, const TIMER_QUOTIENT: usize> { /// Holds 256 registers /// /// Writing to register 0 is considered undefined behaviour @@ -39,7 +43,7 @@ pub struct Vm<'a, Memory, const TIMER_QUOTIENT: usize> { pub registers: [Value; 256], /// Memory implementation - pub memory: Memory, + pub memory: Mem, /// Program counter pub pc: usize, @@ -60,15 +64,15 @@ pub struct Vm<'a, Memory, const TIMER_QUOTIENT: usize> { copier: Option, } -impl<'a, Memory, const TIMER_QUOTIENT: usize> Vm<'a, Memory, TIMER_QUOTIENT> +impl<'a, Mem, const TIMER_QUOTIENT: usize> Vm<'a, Mem, TIMER_QUOTIENT> where - Memory: mem::Memory, + Mem: Memory, { /// Create a new VM with program and trap handler /// /// # Safety /// Program code has to be validated - pub unsafe fn new_unchecked(program: &'a [u8], memory: Memory) -> Self { + pub unsafe fn new_unchecked(program: &'a [u8], memory: Mem) -> Self { Self { registers: [Value::from(0_u64); 256], memory, @@ -82,7 +86,7 @@ where } /// Create a new VM with program and trap handler only if it passes validation - pub fn new_validated(program: &'a [u8], memory: Memory) -> Result { + pub fn new_validated(program: &'a [u8], memory: Mem) -> Result { valider::validate(program)?; Ok(unsafe { Self::new_unchecked(program, memory) }) } @@ -502,3 +506,54 @@ pub enum VmRunOk { /// Environment call Ecall, } + +/// Load-store memory access +pub trait Memory { + /// Load data from memory on address + /// + /// # Safety + /// - Shall not overrun the buffer + unsafe fn load(&mut self, addr: u64, target: *mut u8, count: usize) -> Result<(), LoadError>; + + /// Store data to memory on address + /// + /// # Safety + /// - Shall not overrun the buffer + unsafe fn store( + &mut self, + addr: u64, + source: *const u8, + count: usize, + ) -> Result<(), StoreError>; +} + +/// Unhandled load access trap +#[derive(Clone, Copy, Display, Debug, PartialEq, Eq)] +#[display(fmt = "Load access error at address {_0:#x}")] +pub struct LoadError(pub u64); + +/// Unhandled store access trap +#[derive(Clone, Copy, Display, Debug, PartialEq, Eq)] +#[display(fmt = "Store access error at address {_0:#x}")] +pub struct StoreError(pub u64); + +/// Reason to access memory +#[derive(Clone, Copy, Display, Debug, PartialEq, Eq)] +pub enum MemoryAccessReason { + /// Memory was accessed for load (read) + Load, + /// Memory was accessed for store (write) + Store, +} + +impl From for VmRunError { + fn from(value: LoadError) -> Self { + Self::LoadAccessEx(value.0) + } +} + +impl From for VmRunError { + fn from(value: StoreError) -> Self { + Self::StoreAccessEx(value.0) + } +} diff --git a/hbvm/src/main.rs b/hbvm/src/main.rs index fd46a3e5..05d23c7f 100644 --- a/hbvm/src/main.rs +++ b/hbvm/src/main.rs @@ -1,8 +1,9 @@ -use hbvm::mem::softpaged::{HandlePageFault, PageSize, SoftPagedMem}; - use { hbbytecode::valider::validate, - hbvm::{mem::MemoryAccessReason, Vm}, + hbvm::{ + softpaging::{HandlePageFault, PageSize, SoftPagedMem}, + MemoryAccessReason, Vm, + }, std::io::{stdin, Read}, }; @@ -31,7 +32,7 @@ fn main() -> Result<(), Box> { .map( data, 0, - hbvm::mem::softpaged::paging::Permission::Write, + hbvm::softpaging::paging::Permission::Write, PageSize::Size4K, ) .unwrap(); diff --git a/hbvm/src/mem/mod.rs b/hbvm/src/mem/mod.rs deleted file mode 100644 index 2391a3bf..00000000 --- a/hbvm/src/mem/mod.rs +++ /dev/null @@ -1,54 +0,0 @@ -//! Program memory implementation - -pub mod bmc; -pub mod softpaged; - -use {super::VmRunError, derive_more::Display}; - -pub trait Memory { - /// Load data from memory on address - /// - /// # Safety - /// - Shall not overrun the buffer - unsafe fn load(&mut self, addr: u64, target: *mut u8, count: usize) -> Result<(), LoadError>; - - /// Store data to memory on address - /// - /// # Safety - /// - Shall not overrun the buffer - unsafe fn store( - &mut self, - addr: u64, - source: *const u8, - count: usize, - ) -> Result<(), StoreError>; -} - -/// Unhandled load access trap -#[derive(Clone, Copy, Display, Debug, PartialEq, Eq)] -#[display(fmt = "Load access error at address {_0:#x}")] -pub struct LoadError(pub u64); - -/// Unhandled store access trap -#[derive(Clone, Copy, Display, Debug, PartialEq, Eq)] -#[display(fmt = "Store access error at address {_0:#x}")] -pub struct StoreError(pub u64); - -/// Reason to access memory -#[derive(Clone, Copy, Display, Debug, PartialEq, Eq)] -pub enum MemoryAccessReason { - Load, - Store, -} - -impl From for VmRunError { - fn from(value: LoadError) -> Self { - Self::LoadAccessEx(value.0) - } -} - -impl From for VmRunError { - fn from(value: StoreError) -> Self { - Self::StoreAccessEx(value.0) - } -} diff --git a/hbvm/src/mem/softpaged/mod.rs b/hbvm/src/softpaging/mod.rs similarity index 100% rename from hbvm/src/mem/softpaged/mod.rs rename to hbvm/src/softpaging/mod.rs diff --git a/hbvm/src/mem/softpaged/paging.rs b/hbvm/src/softpaging/paging.rs similarity index 97% rename from hbvm/src/mem/softpaged/paging.rs rename to hbvm/src/softpaging/paging.rs index 05511537..c1ab8426 100644 --- a/hbvm/src/mem/softpaged/paging.rs +++ b/hbvm/src/softpaging/paging.rs @@ -59,7 +59,9 @@ impl Debug for PtEntry { #[derive(Clone, Copy, Debug, PartialEq, Eq)] #[repr(align(4096))] pub struct PageTable { + /// How much entries are in use pub childen: u8, + /// Entries pub table: [PtEntry; 256], } diff --git a/hbvm/src/value.rs b/hbvm/src/value.rs index fb8738e3..50d36d1e 100644 --- a/hbvm/src/value.rs +++ b/hbvm/src/value.rs @@ -13,7 +13,10 @@ macro_rules! value_def { #[derive(Copy, Clone)] #[repr(packed)] pub union Value { - $(pub $ty: $ty),* + $( + #[doc = concat!(stringify!($ty), " type")] + pub $ty: $ty + ),* } @@ -37,10 +40,22 @@ macro_rules! value_def { } impl Value { + /// Byte reinterpret value to target variant #[inline] - pub fn cast(self) -> Variant { + pub fn cast(self) -> V { + /// Evil. + /// + /// Transmute cannot be performed with generic type + /// as size is unknown, so union is used. + /// + /// # Safety + /// If [`ValueVariant`] implemented correctly, it's fine :) + /// + /// :ferrisClueless: union Transmute { + /// Self src: Value, + /// Target variant variant: Variant, } From 2d2978eec70f767370aefebe069db100d8d7f74a Mon Sep 17 00:00:00 2001 From: Erin Date: Tue, 8 Aug 2023 03:10:11 +0200 Subject: [PATCH 057/116] Added inner memory access --- hbvm/src/main.rs | 4 +++- hbvm/src/softpaging/mod.rs | 10 ++++++++-- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/hbvm/src/main.rs b/hbvm/src/main.rs index 05d23c7f..f5cc6136 100644 --- a/hbvm/src/main.rs +++ b/hbvm/src/main.rs @@ -1,3 +1,5 @@ +use hbvm::softpaging::paging::PageTable; + use { hbbytecode::valider::validate, hbvm::{ @@ -61,7 +63,7 @@ impl HandlePageFault for TestTrapHandler { fn page_fault( &mut self, _: MemoryAccessReason, - //_: &mut Memory, + _: &mut PageTable, _: u64, _: PageSize, _: *mut u8, diff --git a/hbvm/src/softpaging/mod.rs b/hbvm/src/softpaging/mod.rs index 2ec72e57..96005a41 100644 --- a/hbvm/src/softpaging/mod.rs +++ b/hbvm/src/softpaging/mod.rs @@ -98,7 +98,13 @@ impl SoftPagedMem { // No page found Some(Err(AddrPageLookupError { addr, size })) => { // Attempt to execute page fault handler - if self.pf_handler.page_fault(reason, addr, size, dst) { + if self.pf_handler.page_fault( + reason, + unsafe { &mut *self.root_pt }, + addr, + size, + dst, + ) { // Shift the splitter address pspl.bump(size); @@ -465,7 +471,7 @@ pub trait HandlePageFault { fn page_fault( &mut self, reason: MemoryAccessReason, - // memory: &mut SoftPagedMem, TODO: Make work + pagetable: &mut PageTable, vaddr: u64, size: PageSize, dataptr: *mut u8, From 4530ff049e3acc128ec5e933d417569f071d8541 Mon Sep 17 00:00:00 2001 From: Erin Date: Tue, 8 Aug 2023 03:10:23 +0200 Subject: [PATCH 058/116] fmt --- hbvm/src/main.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/hbvm/src/main.rs b/hbvm/src/main.rs index f5cc6136..f2a23aba 100644 --- a/hbvm/src/main.rs +++ b/hbvm/src/main.rs @@ -1,9 +1,7 @@ -use hbvm::softpaging::paging::PageTable; - use { hbbytecode::valider::validate, hbvm::{ - softpaging::{HandlePageFault, PageSize, SoftPagedMem}, + softpaging::{paging::PageTable, HandlePageFault, PageSize, SoftPagedMem}, MemoryAccessReason, Vm, }, std::io::{stdin, Read}, From eadf9e0a1fc29ae43d69ee13b917973333bc99ce Mon Sep 17 00:00:00 2001 From: Erin Date: Wed, 9 Aug 2023 01:24:13 +0200 Subject: [PATCH 059/116] Termination instruction --- hbasm/src/lib.rs | 2 +- hbbytecode/hbbytecode.h | 18 +++---- hbbytecode/src/lib.rs | 103 +++++++++++++++++++------------------- hbvm/src/lib.rs | 6 ++- hbvm/src/main.rs | 1 + spec.md | 107 ++++++++++++++++++++-------------------- 6 files changed, 122 insertions(+), 115 deletions(-) diff --git a/hbasm/src/lib.rs b/hbasm/src/lib.rs index f7690c05..a3eb6716 100644 --- a/hbasm/src/lib.rs +++ b/hbasm/src/lib.rs @@ -33,7 +33,7 @@ pub struct Assembler { impl Default for Assembler { fn default() -> Self { Self { - buf: vec![0; 3], + buf: vec![0; 4], sub: Default::default(), } } diff --git a/hbbytecode/hbbytecode.h b/hbbytecode/hbbytecode.h index 0dabc8aa..c5c78afc 100644 --- a/hbbytecode/hbbytecode.h +++ b/hbbytecode/hbbytecode.h @@ -13,15 +13,15 @@ static_assert(CHAR_BIT == 8, "Cursed architectures are not supported"); enum hbbc_Opcode: uint8_t { - hbbc_Op_NOP , hbbc_Op_ADD , hbbc_Op_SUB , hbbc_Op_MUL , hbbc_Op_AND , hbbc_Op_OR , - hbbc_Op_XOR , hbbc_Op_SL , hbbc_Op_SR , hbbc_Op_SRS , hbbc_Op_CMP , hbbc_Op_CMPU , - hbbc_Op_DIR , hbbc_Op_NEG , hbbc_Op_NOT , hbbc_Op_ADDI , hbbc_Op_MULI , hbbc_Op_ANDI , - hbbc_Op_ORI , hbbc_Op_XORI , hbbc_Op_SLI , hbbc_Op_SRI , hbbc_Op_SRSI , hbbc_Op_CMPI , - hbbc_Op_CMPUI , hbbc_Op_CP , hbbc_Op_SWA , hbbc_Op_LI , hbbc_Op_LD , hbbc_Op_ST , - hbbc_Op_BMC , hbbc_Op_BRC , hbbc_Op_JMP , hbbc_Op_JEQ , hbbc_Op_JNE , hbbc_Op_JLT , - hbbc_Op_JGT , hbbc_Op_JLTU , hbbc_Op_JGTU , hbbc_Op_ECALL , hbbc_Op_ADDF , hbbc_Op_SUBF , - hbbc_Op_MULF , hbbc_Op_DIRF , hbbc_Op_FMAF , hbbc_Op_NEGF , hbbc_Op_ITF , hbbc_Op_FTI , - hbbc_Op_ADDFI , hbbc_Op_MULFI , + hbbc_Op_UN , hbbc_Op_TX , hbbc_Op_NOP , hbbc_Op_ADD , hbbc_Op_SUB , hbbc_Op_MUL , + hbbc_Op_AND , hbbc_Op_OR , hbbc_Op_XOR , hbbc_Op_SL , hbbc_Op_SR , hbbc_Op_SRS , + hbbc_Op_CMP , hbbc_Op_CMPU , hbbc_Op_DIR , hbbc_Op_NEG , hbbc_Op_NOT , hbbc_Op_ADDI , + hbbc_Op_MULI , hbbc_Op_ANDI , hbbc_Op_ORI , hbbc_Op_XORI , hbbc_Op_SLI , hbbc_Op_SRI , + hbbc_Op_SRSI , hbbc_Op_CMPI , hbbc_Op_CMPUI , hbbc_Op_CP , hbbc_Op_SWA , hbbc_Op_LI , + hbbc_Op_LD , hbbc_Op_ST , hbbc_Op_BMC , hbbc_Op_BRC , hbbc_Op_JMP , hbbc_Op_JEQ , + hbbc_Op_JNE , hbbc_Op_JLT , hbbc_Op_JGT , hbbc_Op_JLTU , hbbc_Op_JGTU , hbbc_Op_ECALL , + hbbc_Op_ADDF , hbbc_Op_SUBF , hbbc_Op_MULF , hbbc_Op_DIRF , hbbc_Op_FMAF , hbbc_Op_NEGF , + hbbc_Op_ITF , hbbc_Op_FTI , hbbc_Op_ADDFI , hbbc_Op_MULFI , } typedef hbbc_Opcode; static_assert(sizeof(hbbc_Opcode) == 1); diff --git a/hbbytecode/src/lib.rs b/hbbytecode/src/lib.rs index a1c9862c..6dda4da4 100644 --- a/hbbytecode/src/lib.rs +++ b/hbbytecode/src/lib.rs @@ -62,7 +62,7 @@ macro_rules! invoke_with_def { bd(p0: R, p1: I) => [LI], n() - => [UN, NOP, ECALL], + => [UN, TX, NOP, ECALL], ); }; } @@ -73,62 +73,63 @@ constmod!(pub opcode(u8) { //! Opcode constant module UN = 0, "N; Raises a trap"; - NOP = 1, "N; Do nothing"; + TX = 1, "N; Terminate execution"; + NOP = 2, "N; Do nothing"; - ADD = 2, "BBB; #0 ← #1 + #2"; - SUB = 3, "BBB; #0 ← #1 - #2"; - MUL = 4, "BBB; #0 ← #1 × #2"; - AND = 5, "BBB; #0 ← #1 & #2"; - OR = 6, "BBB; #0 ← #1 | #2"; - XOR = 7, "BBB; #0 ← #1 ^ #2"; - SL = 8, "BBB; #0 ← #1 « #2"; - SR = 9, "BBB; #0 ← #1 » #2"; - SRS = 10, "BBB; #0 ← #1 » #2 (signed)"; - CMP = 11, "BBB; #0 ← #1 <=> #2"; - CMPU = 12, "BBB; #0 ← #1 <=> #2 (unsigned)"; - DIR = 13, "BBBB; #0 ← #2 / #3, #1 ← #2 % #3"; - NEG = 14, "BB; #0 ← -#1"; - NOT = 15, "BB; #0 ← !#1"; + ADD = 3, "BBB; #0 ← #1 + #2"; + SUB = 4, "BBB; #0 ← #1 - #2"; + MUL = 5, "BBB; #0 ← #1 × #2"; + AND = 6, "BBB; #0 ← #1 & #2"; + OR = 7, "BBB; #0 ← #1 | #2"; + XOR = 8, "BBB; #0 ← #1 ^ #2"; + SL = 9, "BBB; #0 ← #1 « #2"; + SR = 10, "BBB; #0 ← #1 » #2"; + SRS = 11, "BBB; #0 ← #1 » #2 (signed)"; + CMP = 12, "BBB; #0 ← #1 <=> #2"; + CMPU = 13, "BBB; #0 ← #1 <=> #2 (unsigned)"; + DIR = 14, "BBBB; #0 ← #2 / #3, #1 ← #2 % #3"; + NEG = 15, "BB; #0 ← -#1"; + NOT = 16, "BB; #0 ← !#1"; - ADDI = 16, "BBD; #0 ← #1 + imm #2"; - MULI = 17, "BBD; #0 ← #1 × imm #2"; - ANDI = 18, "BBD; #0 ← #1 & imm #2"; - ORI = 19, "BBD; #0 ← #1 | imm #2"; - XORI = 20, "BBD; #0 ← #1 ^ imm #2"; - SLI = 21, "BBW; #0 ← #1 « imm #2"; - SRI = 22, "BBW; #0 ← #1 » imm #2"; - SRSI = 23, "BBW; #0 ← #1 » imm #2 (signed)"; - CMPI = 24, "BBD; #0 ← #1 <=> imm #2"; - CMPUI = 25, "BBD; #0 ← #1 <=> imm #2 (unsigned)"; + ADDI = 17, "BBD; #0 ← #1 + imm #2"; + MULI = 18, "BBD; #0 ← #1 × imm #2"; + ANDI = 19, "BBD; #0 ← #1 & imm #2"; + ORI = 20, "BBD; #0 ← #1 | imm #2"; + XORI = 21, "BBD; #0 ← #1 ^ imm #2"; + SLI = 22, "BBW; #0 ← #1 « imm #2"; + SRI = 23, "BBW; #0 ← #1 » imm #2"; + SRSI = 24, "BBW; #0 ← #1 » imm #2 (signed)"; + CMPI = 25, "BBD; #0 ← #1 <=> imm #2"; + CMPUI = 26, "BBD; #0 ← #1 <=> imm #2 (unsigned)"; - CP = 26, "BB; Copy #0 ← #1"; - SWA = 27, "BB; Swap #0 and #1"; - LI = 28, "BD; #0 ← imm #1"; - LD = 29, "BBDB; #0 ← [#1 + imm #3], imm #4 bytes, overflowing"; - ST = 30, "BBDB; [#1 + imm #3] ← #0, imm #4 bytes, overflowing"; - BMC = 31, "BBD; [#0] ← [#1], imm #2 bytes"; - BRC = 32, "BBB; #0 ← #1, imm #2 registers"; + CP = 27, "BB; Copy #0 ← #1"; + SWA = 28, "BB; Swap #0 and #1"; + LI = 29, "BD; #0 ← imm #1"; + LD = 30, "BBDB; #0 ← [#1 + imm #3], imm #4 bytes, overflowing"; + ST = 31, "BBDB; [#1 + imm #3] ← #0, imm #4 bytes, overflowing"; + BMC = 32, "BBD; [#0] ← [#1], imm #2 bytes"; + BRC = 33, "BBB; #0 ← #1, imm #2 registers"; - JAL = 33, "BD; Copy PC to #0 and unconditional jump [#1 + imm #2]"; - JEQ = 34, "BBD; if #0 = #1 → jump imm #2"; - JNE = 35, "BBD; if #0 ≠ #1 → jump imm #2"; - JLT = 36, "BBD; if #0 < #1 → jump imm #2"; - JGT = 37, "BBD; if #0 > #1 → jump imm #2"; - JLTU = 38, "BBD; if #0 < #1 → jump imm #2 (unsigned)"; - JGTU = 39, "BBD; if #0 > #1 → jump imm #2 (unsigned)"; - ECALL = 40, "N; Issue system call"; + JAL = 34, "BD; Copy PC to #0 and unconditional jump [#1 + imm #2]"; + JEQ = 35, "BBD; if #0 = #1 → jump imm #2"; + JNE = 36, "BBD; if #0 ≠ #1 → jump imm #2"; + JLT = 37, "BBD; if #0 < #1 → jump imm #2"; + JGT = 38, "BBD; if #0 > #1 → jump imm #2"; + JLTU = 39, "BBD; if #0 < #1 → jump imm #2 (unsigned)"; + JGTU = 40, "BBD; if #0 > #1 → jump imm #2 (unsigned)"; + ECALL = 41, "N; Issue system call"; - ADDF = 41, "BBB; #0 ← #1 +. #2"; - SUBF = 42, "BBB; #0 ← #1 -. #2"; - MULF = 43, "BBB; #0 ← #1 +. #2"; - DIRF = 44, "BBBB; #0 ← #2 / #3, #1 ← #2 % #3"; - FMAF = 45, "BBBB; #0 ← (#1 * #2) + #3"; - NEGF = 46, "BB; #0 ← -#1"; - ITF = 47, "BB; #0 ← #1 as float"; - FTI = 48, "BB; #0 ← #1 as int"; + ADDF = 42, "BBB; #0 ← #1 +. #2"; + SUBF = 43, "BBB; #0 ← #1 -. #2"; + MULF = 44, "BBB; #0 ← #1 +. #2"; + DIRF = 45, "BBBB; #0 ← #2 / #3, #1 ← #2 % #3"; + FMAF = 46, "BBBB; #0 ← (#1 * #2) + #3"; + NEGF = 47, "BB; #0 ← -#1"; + ITF = 48, "BB; #0 ← #1 as float"; + FTI = 49, "BB; #0 ← #1 as int"; - ADDFI = 49, "BBD; #0 ← #1 +. imm #2"; - MULFI = 50, "BBD; #0 ← #1 *. imm #2"; + ADDFI = 50, "BBD; #0 ← #1 +. imm #2"; + MULFI = 51, "BBD; #0 ← #1 *. imm #2"; }); #[repr(packed)] diff --git a/hbvm/src/lib.rs b/hbvm/src/lib.rs index 84447d98..0cad1cd7 100644 --- a/hbvm/src/lib.rs +++ b/hbvm/src/lib.rs @@ -100,7 +100,7 @@ where loop { // Check instruction boundary if self.pc >= self.program_len { - return Ok(VmRunOk::End); + return Err(VmRunError::AddrOutOfBounds); } // Big match @@ -128,6 +128,10 @@ where self.decode::<()>(); return Err(VmRunError::Unreachable); } + TX => { + self.decode::<()>(); + return Ok(VmRunOk::End); + } NOP => self.decode::<()>(), ADD => self.binary_op(u64::wrapping_add), SUB => self.binary_op(u64::wrapping_sub), diff --git a/hbvm/src/main.rs b/hbvm/src/main.rs index f2a23aba..a2325b25 100644 --- a/hbvm/src/main.rs +++ b/hbvm/src/main.rs @@ -10,6 +10,7 @@ use { fn main() -> Result<(), Box> { let mut prog = vec![]; stdin().read_to_end(&mut prog)?; + println!("{prog:?}"); if let Err(e) = validate(&prog) { eprintln!("Program validation error: {e:?}"); diff --git a/spec.md b/spec.md index e98852ff..138ac84f 100644 --- a/spec.md +++ b/spec.md @@ -34,13 +34,14 @@ - `P ← V`: Set register P to value V - `[x]`: Address x -## No-op +## Program execution control - N type | Opcode | Name | Action | |:------:|:----:|:-----------------------------:| | 0 | UN | Trigger unreachable code trap | -| 1 | NOP | Do nothing | +| 1 | TX | Terminate execution | +| 2 | NOP | Do nothing | ## Integer binary ops. - BBB type @@ -48,21 +49,21 @@ | Opcode | Name | Action | |:------:|:----:|:-----------------------:| -| 2 | ADD | Wrapping addition | -| 3 | SUB | Wrapping subtraction | -| 4 | MUL | Wrapping multiplication | -| 5 | AND | Bitand | -| 6 | OR | Bitor | -| 7 | XOR | Bitxor | -| 8 | SL | Unsigned left bitshift | -| 9 | SR | Unsigned right bitshift | -| 10 | SRS | Signed right bitshift | +| 3 | ADD | Wrapping addition | +| 4 | SUB | Wrapping subtraction | +| 5 | MUL | Wrapping multiplication | +| 6 | AND | Bitand | +| 7 | OR | Bitor | +| 8 | XOR | Bitxor | +| 9 | SL | Unsigned left bitshift | +| 10 | SR | Unsigned right bitshift | +| 11 | SRS | Signed right bitshift | ### Comparsion | Opcode | Name | Action | |:------:|:----:|:-------------------:| -| 11 | CMP | Signed comparsion | -| 12 | CMPU | Unsigned comparsion | +| 12 | CMP | Signed comparsion | +| 13 | CMPU | Unsigned comparsion | #### Comparsion table | #1 *op* #2 | Result | @@ -79,7 +80,7 @@ | Opcode | Name | Action | |:------:|:----:|:-------------------------------:| -| 13 | DIR | Divide and remainder combinated | +| 14 | DIR | Divide and remainder combinated | ### Negations - Type BB @@ -87,36 +88,36 @@ | Opcode | Name | Action | |:------:|:----:|:----------------:| -| 14 | NEG | Bit negation | -| 15 | NOT | Logical negation | +| 15 | NEG | Bit negation | +| 16 | NOT | Logical negation | ## Integer immediate binary ops. - Type BBD - `#0 ← #1 imm #2` -| Opcode | Name | Action | -|:------:|:----:|:-----------------------:| -| 16 | ADDI | Wrapping addition | -| 17 | MULI | Wrapping subtraction | -| 18 | ANDI | Bitand | -| 19 | ORI | Bitor | -| 20 | XORI | Bitxor | +| Opcode | Name | Action | +|:------:|:----:|:--------------------:| +| 17 | ADDI | Wrapping addition | +| 18 | MULI | Wrapping subtraction | +| 19 | ANDI | Bitand | +| 20 | ORI | Bitor | +| 21 | XORI | Bitxor | ### Bitshifts - Type BBW | Opcode | Name | Action | |:------:|:----:|:-----------------------:| -| 21 | SLI | Unsigned left bitshift | -| 22 | SRI | Unsigned right bitshift | -| 23 | SRSI | Signed right bitshift | +| 22 | SLI | Unsigned left bitshift | +| 23 | SRI | Unsigned right bitshift | +| 24 | SRSI | Signed right bitshift | ### Comparsion - Comparsion is the same as when RRR type | Opcode | Name | Action | |:------:|:-----:|:-------------------:| -| 24 | CMPI | Signed comparsion | -| 25 | CMPUI | Unsigned comparsion | +| 25 | CMPI | Signed comparsion | +| 26 | CMPUI | Unsigned comparsion | ## Register value set / copy @@ -126,7 +127,7 @@ | Opcode | Name | Action | |:------:|:----:|:------:| -| 26 | CP | Copy | +| 27 | CP | Copy | ### Swap - Type BB @@ -137,7 +138,7 @@ | Opcode | Name | Action | |:------:|:----:|:------:| -| 27 | SWA | Swap | +| 28 | SWA | Swap | ### Load immediate - Type BD @@ -145,7 +146,7 @@ | Opcode | Name | Action | |:------:|:----:|:--------------:| -| 28 | LI | Load immediate | +| 29 | LI | Load immediate | ## Memory operations - Type BBDH @@ -154,8 +155,8 @@ ### Load / Store | Opcode | Name | Action | |:------:|:----:|:---------------------------------------:| -| 29 | LD | `#0 ← [#1 + imm #3], copy imm #4 bytes` | -| 30 | ST | `[#1 + imm #3] ← #0, copy imm #4 bytes` | +| 30 | LD | `#0 ← [#1 + imm #3], copy imm #4 bytes` | +| 31 | ST | `[#1 + imm #3] ← #0, copy imm #4 bytes` | ## Block copy - Block copy source and target can overlap @@ -165,7 +166,7 @@ | Opcode | Name | Action | |:------:|:----:|:--------------------------------:| -| 31 | BMC | `[#1] ← [#0], copy imm #2 bytes` | +| 32 | BMC | `[#1] ← [#0], copy imm #2 bytes` | ### Register copy - Type BBB @@ -173,7 +174,7 @@ | Opcode | Name | Action | |:------:|:----:|:--------------------------------:| -| 32 | BRC | `#1 ← #0, copy imm #2 registers` | +| 33 | BRC | `#1 ← #0, copy imm #2 registers` | ## Control flow @@ -182,7 +183,7 @@ | Opcode | Name | Action | |:------:|:----:|:--------------------------------------------------:| -| 33 | JAL | Save PC past JAL to `#0` and jump at `#1 + imm #2` | +| 34 | JAL | Save PC past JAL to `#0` and jump at `#1 + imm #2` | ### Conditional jumps - Type BBD @@ -190,19 +191,19 @@ | Opcode | Name | Comparsion | |:------:|:----:|:------------:| -| 34 | JEQ | = | -| 35 | JNE | ≠ | -| 36 | JLT | < (signed) | -| 37 | JGT | > (signed) | -| 38 | JLTU | < (unsigned) | -| 39 | JGTU | > (unsigned) | +| 35 | JEQ | = | +| 36 | JNE | ≠ | +| 37 | JLT | < (signed) | +| 38 | JGT | > (signed) | +| 39 | JLTU | < (unsigned) | +| 40 | JGTU | > (unsigned) | ### Environment call - Type N | Opcode | Name | Action | |:------:|:-----:|:-------------------------------------:| -| 40 | ECALL | Cause an trap to the host environment | +| 41 | ECALL | Cause an trap to the host environment | ## Floating point operations - Type BBB @@ -210,29 +211,29 @@ | Opcode | Name | Action | |:------:|:----:|:--------------:| -| 41 | ADDF | Addition | -| 42 | SUBF | Subtraction | -| 43 | MULF | Multiplication | +| 42 | ADDF | Addition | +| 43 | SUBF | Subtraction | +| 44 | MULF | Multiplication | ### Division-remainder - Type BBBB | Opcode | Name | Action | |:------:|:----:|:-------------------------:| -| 44 | DIRF | Same as for integer `DIR` | +| 45 | DIRF | Same as for integer `DIR` | ### Fused Multiply-Add - Type BBBB | Opcode | Name | Action | |:------:|:----:|:---------------------:| -| 45 | FMAF | `#0 ← (#1 * #2) + #3` | +| 46 | FMAF | `#0 ← (#1 * #2) + #3` | ### Negation - Type BB | Opcode | Name | Action | |:------:|:----:|:----------:| -| 46 | NEGF | `#0 ← -#1` | +| 47 | NEGF | `#0 ← -#1` | ### Conversion - Type BB @@ -241,8 +242,8 @@ | Opcode | Name | Action | |:------:|:----:|:------------:| -| 47 | ITF | Int to Float | -| 48 | FTI | Float to Int | +| 48 | ITF | Int to Float | +| 49 | FTI | Float to Int | ## Floating point immediate operations - Type BBD @@ -250,8 +251,8 @@ | Opcode | Name | Action | |:------:|:-----:|:--------------:| -| 49 | ADDFI | Addition | -| 50 | MULFI | Multiplication | +| 50 | ADDFI | Addition | +| 51 | MULFI | Multiplication | # Registers - There is 255 registers + one zero register (with index 0) From 430ccd170dc9d06e96c636fc628d2e5afd7970c9 Mon Sep 17 00:00:00 2001 From: Erin Date: Wed, 9 Aug 2023 02:33:03 +0200 Subject: [PATCH 060/116] Von-Neumann? --- hbvm/fuzz/Cargo.toml | 3 + hbvm/fuzz/fuzz_targets/vm.rs | 34 +++++++---- hbvm/src/lib.rs | 107 +++++++++++++++++++++-------------- hbvm/src/main.rs | 15 +++-- hbvm/src/softpaging/mod.rs | 43 ++++++++------ 5 files changed, 126 insertions(+), 76 deletions(-) diff --git a/hbvm/fuzz/Cargo.toml b/hbvm/fuzz/Cargo.toml index f6bc6164..42566bd3 100644 --- a/hbvm/fuzz/Cargo.toml +++ b/hbvm/fuzz/Cargo.toml @@ -13,6 +13,9 @@ libfuzzer-sys = "0.4" [dependencies.hbvm] path = ".." +[dependencies.hbbytecode] +path = "../../hbbytecode" + # Prevent this from interfering with workspaces [workspace] members = ["."] diff --git a/hbvm/fuzz/fuzz_targets/vm.rs b/hbvm/fuzz/fuzz_targets/vm.rs index e1d42664..b10c6d96 100644 --- a/hbvm/fuzz/fuzz_targets/vm.rs +++ b/hbvm/fuzz/fuzz_targets/vm.rs @@ -1,15 +1,30 @@ #![no_main] use { + hbbytecode::valider::validate, hbvm::{ - mem::{HandlePageFault, Memory, MemoryAccessReason, PageSize}, - Vm, + softpaging::{ + paging::{PageTable, Permission}, + HandlePageFault, PageSize, SoftPagedMem, + }, + MemoryAccessReason, Vm, }, libfuzzer_sys::fuzz_target, }; fuzz_target!(|data: &[u8]| { - if let Ok(mut vm) = Vm::<_, 16384>::new_validated(data, TestTrapHandler, Default::default()) { + if validate(data).is_ok() { + let mut vm = unsafe { + Vm::<_, 16384>::new( + SoftPagedMem { + pf_handler: TestTrapHandler, + program: data, + root_pt: Box::into_raw(Default::default()), + }, + 0, + ) + }; + // Alloc and map some memory let pages = [ alloc_and_map(&mut vm.memory, 0), @@ -26,22 +41,17 @@ fuzz_target!(|data: &[u8]| { } }); -fn alloc_and_map(memory: &mut Memory, at: u64) -> *mut u8 { +fn alloc_and_map(memory: &mut SoftPagedMem, at: u64) -> *mut u8 { let ptr = Box::into_raw(Box::::default()).cast(); unsafe { memory - .map( - ptr, - at, - hbvm::mem::paging::Permission::Write, - PageSize::Size4K, - ) + .map(ptr, at, Permission::Write, PageSize::Size4K) .unwrap() }; ptr } -fn unmap_and_dealloc(memory: &mut Memory, ptr: *mut u8, from: u64) { +fn unmap_and_dealloc(memory: &mut SoftPagedMem, ptr: *mut u8, from: u64) { memory.unmap(from).unwrap(); let _ = unsafe { Box::from_raw(ptr.cast::()) }; } @@ -59,7 +69,7 @@ impl HandlePageFault for TestTrapHandler { fn page_fault( &mut self, _: MemoryAccessReason, - _: &mut Memory, + _: &mut PageTable, _: u64, _: PageSize, _: *mut u8, diff --git a/hbvm/src/lib.rs b/hbvm/src/lib.rs index 0cad1cd7..752bf515 100644 --- a/hbvm/src/lib.rs +++ b/hbvm/src/lib.rs @@ -14,8 +14,6 @@ #![cfg_attr(feature = "nightly", feature(fn_align))] #![warn(missing_docs, clippy::missing_docs_in_private_items)] -use core::marker::PhantomData; - #[cfg(feature = "alloc")] extern crate alloc; @@ -26,16 +24,14 @@ mod bmc; use { bmc::BlockCopier, - core::{cmp::Ordering, mem::size_of, ops}, + core::{cmp::Ordering, mem::size_of, ops, slice::SliceIndex}, derive_more::Display, - hbbytecode::{ - valider, OpParam, ParamBB, ParamBBB, ParamBBBB, ParamBBD, ParamBBDH, ParamBBW, ParamBD, - }, + hbbytecode::{OpParam, ParamBB, ParamBBB, ParamBBBB, ParamBBD, ParamBBDH, ParamBBW, ParamBD}, value::{Value, ValueVariant}, }; /// HoleyBytes Virtual Machine -pub struct Vm<'a, Mem, const TIMER_QUOTIENT: usize> { +pub struct Vm { /// Holds 256 registers /// /// Writing to register 0 is considered undefined behaviour @@ -48,15 +44,6 @@ pub struct Vm<'a, Mem, const TIMER_QUOTIENT: usize> { /// Program counter pub pc: usize, - /// Program - program: *const u8, - - /// Cached program length (without unreachable end) - program_len: usize, - - /// Program lifetime - _program_lt: PhantomData<&'a [u8]>, - /// Program timer timer: usize, @@ -64,7 +51,7 @@ pub struct Vm<'a, Mem, const TIMER_QUOTIENT: usize> { copier: Option, } -impl<'a, Mem, const TIMER_QUOTIENT: usize> Vm<'a, Mem, TIMER_QUOTIENT> +impl Vm where Mem: Memory, { @@ -72,25 +59,16 @@ where /// /// # Safety /// Program code has to be validated - pub unsafe fn new_unchecked(program: &'a [u8], memory: Mem) -> Self { + pub unsafe fn new(memory: Mem, entry: u64) -> Self { Self { registers: [Value::from(0_u64); 256], memory, - pc: 0, - program_len: program.len() - 12, - program: program[4..].as_ptr(), - _program_lt: Default::default(), + pc: entry as _, timer: 0, copier: None, } } - /// Create a new VM with program and trap handler only if it passes validation - pub fn new_validated(program: &'a [u8], memory: Mem) -> Result { - valider::validate(program)?; - Ok(unsafe { Self::new_unchecked(program, memory) }) - } - /// Execute program /// /// Program can return [`VmRunError`] if a trap handling failed @@ -98,11 +76,6 @@ where pub fn run(&mut self) -> Result { use hbbytecode::opcode::*; loop { - // Check instruction boundary - if self.pc >= self.program_len { - return Err(VmRunError::AddrOutOfBounds); - } - // Big match // // Contribution guide: @@ -123,7 +96,11 @@ where // - Yes, we assume you run 64 bit CPU. Else ?conradluget a better CPU // sorry 8 bit fans, HBVM won't run on your Speccy :( unsafe { - match *self.program.add(self.pc) { + match *self + .memory + .load_prog(self.pc) + .ok_or(VmRunError::ProgramFetchLoadEx(self.pc as _))? + { UN => { self.decode::<()>(); return Err(VmRunError::Unreachable); @@ -388,15 +365,22 @@ where } /// Decode instruction operands - #[inline] + #[inline(always)] unsafe fn decode(&mut self) -> T { - let data = self.program.add(self.pc + 1).cast::().read(); + let pc1 = self.pc + 1; + let data = self + .memory + .load_prog_unchecked(pc1..pc1 + size_of::()) + .as_ptr() + .cast::() + .read(); + self.pc += 1 + size_of::(); data } /// Perform binary operating over two registers - #[inline] + #[inline(always)] unsafe fn binary_op(&mut self, op: impl Fn(T, T) -> T) { let ParamBBB(tg, a0, a1) = self.decode(); self.write_reg( @@ -406,7 +390,7 @@ where } /// Perform binary operation over register and immediate - #[inline] + #[inline(always)] unsafe fn binary_op_imm(&mut self, op: impl Fn(T, T) -> T) { let ParamBBD(tg, reg, imm) = self.decode(); self.write_reg( @@ -416,14 +400,14 @@ where } /// Perform binary operation over register and shift immediate - #[inline] + #[inline(always)] unsafe fn binary_op_ims(&mut self, op: impl Fn(T, u32) -> T) { let ParamBBW(tg, reg, imm) = self.decode(); self.write_reg(tg, op(self.read_reg(reg).cast::(), imm)); } /// Jump at `#3` if ordering on `#0 <=> #1` is equal to expected - #[inline] + #[inline(always)] unsafe fn cond_jmp(&mut self, expected: Ordering) { let ParamBBD(a0, a1, ja) = self.decode(); if self @@ -437,14 +421,14 @@ where } /// Read register - #[inline] + #[inline(always)] unsafe fn read_reg(&self, n: u8) -> Value { *self.registers.get_unchecked(n as usize) } /// Write a register. /// Writing to register 0 is no-op. - #[inline] + #[inline(always)] unsafe fn write_reg(&mut self, n: u8, value: impl Into) { if n != 0 { *self.registers.get_unchecked_mut(n as usize) = value.into(); @@ -452,7 +436,7 @@ where } /// Load / Store Address check-computation überfunction - #[inline] + #[inline(always)] unsafe fn ldst_addr_uber( &self, dst: u8, @@ -485,6 +469,9 @@ pub enum VmRunError { /// Unhandled load access exception LoadAccessEx(u64), + /// Unhandled instruction load access exception + ProgramFetchLoadEx(u64), + /// Unhandled store access exception StoreAccessEx(u64), @@ -529,6 +516,40 @@ pub trait Memory { source: *const u8, count: usize, ) -> Result<(), StoreError>; + + /// Fetch bytes from program section + /// + /// # Why? + /// Even Holey Bytes programs operate with + /// single address space, the actual implementation + /// may be different, so for these reasons there is a + /// separate function. + /// + /// Also if your memory implementation differentiates between + /// readable and executable memory, this is the way to distinguish + /// the loads. + /// + /// # Notice for implementors + /// This is a hot function. This is called on each opcode fetch + /// and instruction decode. Inlining the implementation is highly + /// recommended! + /// + /// If you utilise some more heavy memory implementation, consider + /// performing caching as HBVM does not do that for you. + /// + /// Has to return all the requested data. If cannot fetch data of requested + /// length, return [`None`]. + fn load_prog(&mut self, index: I) -> Option<&I::Output> + where + I: SliceIndex<[u8]>; + + /// Fetch bytes from program section, unchecked. + /// + /// # Safety + /// You really have to be sure you get the bytes, got me? + unsafe fn load_prog_unchecked(&mut self, index: I) -> &I::Output + where + I: SliceIndex<[u8]>; } /// Unhandled load access trap diff --git a/hbvm/src/main.rs b/hbvm/src/main.rs index a2325b25..11e08dde 100644 --- a/hbvm/src/main.rs +++ b/hbvm/src/main.rs @@ -10,15 +10,20 @@ use { fn main() -> Result<(), Box> { let mut prog = vec![]; stdin().read_to_end(&mut prog)?; - println!("{prog:?}"); if let Err(e) = validate(&prog) { eprintln!("Program validation error: {e:?}"); return Ok(()); } else { unsafe { - let mut vm = - Vm::<_, 0>::new_unchecked(&prog, SoftPagedMem::::default()); + let mut vm = Vm::<_, 0>::new( + SoftPagedMem { + pf_handler: TestTrapHandler, + program: &prog, + root_pt: Box::into_raw(Default::default()), + }, + 0, + ); let data = { let ptr = std::alloc::alloc_zeroed(std::alloc::Layout::from_size_align_unchecked( 4096, 4096, @@ -32,7 +37,7 @@ fn main() -> Result<(), Box> { vm.memory .map( data, - 0, + 8192, hbvm::softpaging::paging::Permission::Write, PageSize::Size4K, ) @@ -46,7 +51,7 @@ fn main() -> Result<(), Box> { data, std::alloc::Layout::from_size_align_unchecked(4096, 4096), ); - vm.memory.unmap(0).unwrap(); + vm.memory.unmap(8192).unwrap(); } } Ok(()) diff --git a/hbvm/src/softpaging/mod.rs b/hbvm/src/softpaging/mod.rs index 96005a41..ca2d3cd2 100644 --- a/hbvm/src/softpaging/mod.rs +++ b/hbvm/src/softpaging/mod.rs @@ -4,6 +4,7 @@ pub mod paging; use { super::{LoadError, Memory, MemoryAccessReason, StoreError}, + core::slice::SliceIndex, derive_more::Display, paging::{PageTable, Permission}, }; @@ -13,14 +14,16 @@ use {alloc::boxed::Box, paging::PtEntry}; /// HoleyBytes software paged memory #[derive(Clone, Debug)] -pub struct SoftPagedMem { +pub struct SoftPagedMem<'p, PfH> { /// Root page table pub root_pt: *mut PageTable, /// Page fault handler - pub pf_handler: PfHandler, + pub pf_handler: PfH, + /// Program memory segment + pub program: &'p [u8], } -impl Memory for SoftPagedMem { +impl<'p, PfH: HandlePageFault> Memory for SoftPagedMem<'p, PfH> { /// Load value from an address /// /// # Safety @@ -57,9 +60,27 @@ impl Memory for SoftPagedMem { ) .map_err(StoreError) } + + /// Fetch slice from program memory section + #[inline(always)] + fn load_prog(&mut self, index: I) -> Option<&I::Output> + where + I: SliceIndex<[u8]>, + { + self.program.get(index) + } + + /// Fetch slice from program memory section, unchecked! + #[inline(always)] + unsafe fn load_prog_unchecked(&mut self, index: I) -> &I::Output + where + I: SliceIndex<[u8]>, + { + self.program.get_unchecked(index) + } } -impl SoftPagedMem { +impl<'p, PfH: HandlePageFault> SoftPagedMem<'p, PfH> { // Everyone behold, the holy function, the god of HBVM memory accesses! /// Split address to pages, check their permissions and feed pointers with offset @@ -239,24 +260,14 @@ impl Iterator for AddrPageLookuper { } #[cfg(feature = "alloc")] -impl Default for SoftPagedMem { - fn default() -> Self { - Self { - root_pt: Box::into_raw(Default::default()), - pf_handler: Default::default(), - } - } -} - -#[cfg(feature = "alloc")] -impl Drop for SoftPagedMem { +impl<'p, A> Drop for SoftPagedMem<'p, A> { fn drop(&mut self) { let _ = unsafe { Box::from_raw(self.root_pt) }; } } #[cfg(feature = "alloc")] -impl SoftPagedMem { +impl<'p, A> SoftPagedMem<'p, A> { /// Maps host's memory into VM's memory /// /// # Safety From 06d66289bc7159db656c8526473c7dea594634e8 Mon Sep 17 00:00:00 2001 From: Erin Date: Wed, 9 Aug 2023 02:53:55 +0200 Subject: [PATCH 061/116] Now finally, leaving Hardvard! --- hbvm/src/main.rs | 2 +- hbvm/src/softpaging/mod.rs | 20 ++++++++++++++++++++ 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/hbvm/src/main.rs b/hbvm/src/main.rs index 11e08dde..74202713 100644 --- a/hbvm/src/main.rs +++ b/hbvm/src/main.rs @@ -22,7 +22,7 @@ fn main() -> Result<(), Box> { program: &prog, root_pt: Box::into_raw(Default::default()), }, - 0, + 4, ); let data = { let ptr = std::alloc::alloc_zeroed(std::alloc::Layout::from_size_align_unchecked( diff --git a/hbvm/src/softpaging/mod.rs b/hbvm/src/softpaging/mod.rs index ca2d3cd2..3db62486 100644 --- a/hbvm/src/softpaging/mod.rs +++ b/hbvm/src/softpaging/mod.rs @@ -97,6 +97,26 @@ impl<'p, PfH: HandlePageFault> SoftPagedMem<'p, PfH> { permission_check: fn(Permission) -> bool, action: fn(*mut u8, *mut u8, usize), ) -> Result<(), u64> { + let (src, len) = if src < self.program.len() as _ { + let to_copy = len.clamp(0, self.program.len().saturating_sub(src as _)); + action( + unsafe { self.program.as_ptr().add(src as _).cast_mut() }, + dst, + to_copy, + ); + + ( + src.saturating_add(to_copy as _), + len.saturating_sub(to_copy), + ) + } else { + (src, len) + }; + + if len == 0 { + return Ok(()); + } + // Create new splitter let mut pspl = AddrPageLookuper::new(src, len, self.root_pt); loop { From 3ac80a2e3d513c2ea9c1cf35a3e16b6f29a19817 Mon Sep 17 00:00:00 2001 From: Erin Date: Wed, 9 Aug 2023 02:57:25 +0200 Subject: [PATCH 062/116] Forbid store --- hbvm/src/softpaging/mod.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/hbvm/src/softpaging/mod.rs b/hbvm/src/softpaging/mod.rs index 3db62486..bf35ff13 100644 --- a/hbvm/src/softpaging/mod.rs +++ b/hbvm/src/softpaging/mod.rs @@ -98,6 +98,10 @@ impl<'p, PfH: HandlePageFault> SoftPagedMem<'p, PfH> { action: fn(*mut u8, *mut u8, usize), ) -> Result<(), u64> { let (src, len) = if src < self.program.len() as _ { + if reason != MemoryAccessReason::Load { + return Err(src); + } + let to_copy = len.clamp(0, self.program.len().saturating_sub(src as _)); action( unsafe { self.program.as_ptr().add(src as _).cast_mut() }, From 529fbdaed45402c76a6a0bd484070bb930760816 Mon Sep 17 00:00:00 2001 From: Erin Date: Wed, 9 Aug 2023 02:59:11 +0200 Subject: [PATCH 063/116] Comments --- hbvm/src/softpaging/mod.rs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/hbvm/src/softpaging/mod.rs b/hbvm/src/softpaging/mod.rs index bf35ff13..c33bc112 100644 --- a/hbvm/src/softpaging/mod.rs +++ b/hbvm/src/softpaging/mod.rs @@ -97,26 +97,33 @@ impl<'p, PfH: HandlePageFault> SoftPagedMem<'p, PfH> { permission_check: fn(Permission) -> bool, action: fn(*mut u8, *mut u8, usize), ) -> Result<(), u64> { + // Memory load from program section let (src, len) = if src < self.program.len() as _ { + // Allow only loads if reason != MemoryAccessReason::Load { return Err(src); } + // Determine how much data to copy from here let to_copy = len.clamp(0, self.program.len().saturating_sub(src as _)); + + // Perform action action( unsafe { self.program.as_ptr().add(src as _).cast_mut() }, dst, to_copy, ); + // Return shifted from what we've already copied ( src.saturating_add(to_copy as _), len.saturating_sub(to_copy), ) } else { - (src, len) + (src, len) // Nothing weird! }; + // Nothing to copy? Don't bother doing anything, bail. if len == 0 { return Ok(()); } From 1460a7a23075297ae463aa48e32615af7b83eea6 Mon Sep 17 00:00:00 2001 From: Erin Date: Wed, 9 Aug 2023 03:01:42 +0200 Subject: [PATCH 064/116] Edit 0x0 --- spec.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/spec.md b/spec.md index 138ac84f..b9aad2aa 100644 --- a/spec.md +++ b/spec.md @@ -262,8 +262,9 @@ # Memory - Addresses are 64 bit - Program should be in the same address space as all other data -- Address `0x0` is invalid and acessing it traps - Memory implementation is arbitrary + - Address `0x0` may or may not be valid. Count with compilers + considering it invalid! - In case of accessing invalid address: - Program shall trap (LoadAccessEx, StoreAccessEx) with parameter of accessed address - Value of register when trapped is undefined From 97eaae1c764fee8c8927e80d191047d9d600a6d6 Mon Sep 17 00:00:00 2001 From: Erin Date: Wed, 9 Aug 2023 03:12:09 +0200 Subject: [PATCH 065/116] bai --- Cargo.lock | 7 ------- hbvm/Cargo.toml | 1 - 2 files changed, 8 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4cb5d256..56e88bb3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -127,7 +127,6 @@ dependencies = [ "delegate", "derive_more", "hbbytecode", - "log", "paste", "sealed", "static_assertions", @@ -169,12 +168,6 @@ dependencies = [ "proc-macro2", ] -[[package]] -name = "log" -version = "0.4.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b06a4cde4c0f271a446782e3eff8de789548ce57dbc8eca9292c27f4a42004b4" - [[package]] name = "logos" version = "0.13.0" diff --git a/hbvm/Cargo.toml b/hbvm/Cargo.toml index 98d80517..ce3b5651 100644 --- a/hbvm/Cargo.toml +++ b/hbvm/Cargo.toml @@ -15,7 +15,6 @@ nightly = [] delegate = "0.9" derive_more = "0.99" hbbytecode.path = "../hbbytecode" -log = "0.4" paste = "1.0" sealed = "0.5" static_assertions = "1.0" From 6609bd10c5d4b901e8facabe48c7fb30b80cc40f Mon Sep 17 00:00:00 2001 From: Erin Date: Wed, 9 Aug 2023 20:19:12 +0200 Subject: [PATCH 066/116] executable --- hbvm/src/softpaging/mod.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/hbvm/src/softpaging/mod.rs b/hbvm/src/softpaging/mod.rs index c33bc112..07e05a54 100644 --- a/hbvm/src/softpaging/mod.rs +++ b/hbvm/src/softpaging/mod.rs @@ -501,6 +501,12 @@ pub mod perm_check { pub const fn writable(perm: Permission) -> bool { matches!(perm, Permission::Write) } + + /// Page is executable + #[inline(always)] + pub const fn executable(perm: Permission) -> bool { + matches!(perm, Permission::Exec) + } } /// Handle VM traps From 770c2ebcf0cd64330ef34a4daf631936fe6b4db4 Mon Sep 17 00:00:00 2001 From: Erin Date: Thu, 10 Aug 2023 12:39:03 +0200 Subject: [PATCH 067/116] move --- hbvm/src/softpaging/lookup.rs | 124 ++++++++++++++ hbvm/src/softpaging/mapping.rs | 163 +++++++++++++++++++ hbvm/src/softpaging/mod.rs | 286 +-------------------------------- 3 files changed, 293 insertions(+), 280 deletions(-) create mode 100644 hbvm/src/softpaging/lookup.rs create mode 100644 hbvm/src/softpaging/mapping.rs diff --git a/hbvm/src/softpaging/lookup.rs b/hbvm/src/softpaging/lookup.rs new file mode 100644 index 00000000..91113905 --- /dev/null +++ b/hbvm/src/softpaging/lookup.rs @@ -0,0 +1,124 @@ +//! Address lookup + +use super::{ + addr_extract_index, + paging::{PageTable, Permission}, + PageSize, +}; + +/// Good result from address split +pub struct AddrPageLookupOk { + /// Virtual address + pub vaddr: u64, + + /// Pointer to the start for perform operation + pub ptr: *mut u8, + + /// Size to the end of page / end of desired size + pub size: usize, + + /// Page permission + pub perm: Permission, +} + +/// Errornous address split result +pub struct AddrPageLookupError { + /// Address of failure + pub addr: u64, + + /// Requested page size + pub size: PageSize, +} + +/// Address splitter into pages +pub struct AddrPageLookuper { + /// Current address + addr: u64, + + /// Size left + size: usize, + + /// Page table + pagetable: *const PageTable, +} + +impl AddrPageLookuper { + /// Create a new page lookuper + #[inline] + pub const fn new(addr: u64, size: usize, pagetable: *const PageTable) -> Self { + Self { + addr, + size, + pagetable, + } + } + + /// Bump address by size X + pub fn bump(&mut self, page_size: PageSize) { + self.addr += page_size as u64; + self.size = self.size.saturating_sub(page_size as _); + } +} + +impl Iterator for AddrPageLookuper { + type Item = Result; + + fn next(&mut self) -> Option { + // The end, everything is fine + if self.size == 0 { + return None; + } + + let (base, perm, size, offset) = 'a: { + let mut current_pt = self.pagetable; + + // Walk the page table + for lvl in (0..5).rev() { + // Get an entry + unsafe { + let entry = (*current_pt) + .table + .get_unchecked(addr_extract_index(self.addr, lvl)); + + let ptr = entry.ptr(); + match entry.permission() { + // No page → page fault + Permission::Empty => { + return Some(Err(AddrPageLookupError { + addr: self.addr, + size: PageSize::from_lvl(lvl)?, + })) + } + + // Node → proceed waking + Permission::Node => current_pt = ptr as _, + + // Leaf → return relevant data + perm => { + break 'a ( + // Pointer in host memory + ptr as *mut u8, + perm, + PageSize::from_lvl(lvl)?, + // In-page offset + addr_extract_index(self.addr, lvl), + ); + } + } + } + } + return None; // Reached the end (should not happen) + }; + + // Get available byte count in the selected page with offset + let avail = (size as usize - offset).clamp(0, self.size); + self.bump(size); + + Some(Ok(AddrPageLookupOk { + vaddr: self.addr, + ptr: unsafe { base.add(offset) }, // Return pointer to the start of region + size: avail, + perm, + })) + } +} diff --git a/hbvm/src/softpaging/mapping.rs b/hbvm/src/softpaging/mapping.rs new file mode 100644 index 00000000..9c556de5 --- /dev/null +++ b/hbvm/src/softpaging/mapping.rs @@ -0,0 +1,163 @@ +//! Automatic memory mapping + +use { + super::{ + addr_extract_index, + paging::{PageTable, Permission, PtEntry, PtPointedData}, + PageSize, SoftPagedMem, + }, + alloc::boxed::Box, + derive_more::Display, +}; + +#[cfg(feature = "alloc")] +impl<'p, A> SoftPagedMem<'p, A> { + /// Maps host's memory into VM's memory + /// + /// # Safety + /// - Your faith in the gods of UB + /// - Addr-san claims it's fine but who knows is she isn't lying :ferrisSus: + /// - Alright, Miri-sama is also fine with this, who knows why + pub unsafe fn map( + &mut self, + host: *mut u8, + target: u64, + perm: Permission, + pagesize: PageSize, + ) -> Result<(), MapError> { + let mut current_pt = self.root_pt; + + // Decide on what level depth are we going + let lookup_depth = match pagesize { + PageSize::Size4K => 0, + PageSize::Size2M => 1, + PageSize::Size1G => 2, + }; + + // Walk pagetable levels + for lvl in (lookup_depth + 1..5).rev() { + let entry = (*current_pt) + .table + .get_unchecked_mut(addr_extract_index(target, lvl)); + + let ptr = entry.ptr(); + match entry.permission() { + // Still not on target and already seeing empty entry? + // No worries! Let's create one (allocates). + Permission::Empty => { + // Increase children count + (*current_pt).childen += 1; + + let table = Box::into_raw(Box::new(PtPointedData { + pt: PageTable::default(), + })); + + core::ptr::write(entry, PtEntry::new(table, Permission::Node)); + current_pt = table as _; + } + // Continue walking + Permission::Node => current_pt = ptr as _, + + // There is some entry on place of node + _ => return Err(MapError::PageOnNode), + } + } + + let node = (*current_pt) + .table + .get_unchecked_mut(addr_extract_index(target, lookup_depth)); + + // Check if node is not mapped + if node.permission() != Permission::Empty { + return Err(MapError::AlreadyMapped); + } + + // Write entry + (*current_pt).childen += 1; + core::ptr::write(node, PtEntry::new(host.cast(), perm)); + + Ok(()) + } + + /// Unmaps pages from VM's memory + /// + /// If errors, it only means there is no entry to unmap and in most cases + /// just should be ignored. + pub fn unmap(&mut self, addr: u64) -> Result<(), NothingToUnmap> { + let mut current_pt = self.root_pt; + let mut page_tables = [core::ptr::null_mut(); 5]; + + // Walk page table in reverse + for lvl in (0..5).rev() { + let entry = unsafe { + (*current_pt) + .table + .get_unchecked_mut(addr_extract_index(addr, lvl)) + }; + + let ptr = entry.ptr(); + match entry.permission() { + // Nothing is there, throw an error, not critical! + Permission::Empty => return Err(NothingToUnmap), + // Node – Save to visited pagetables and continue walking + Permission::Node => { + page_tables[lvl as usize] = entry; + current_pt = ptr as _ + } + // Page entry – zero it out! + // Zero page entry is completely valid entry with + // empty permission - no UB here! + _ => unsafe { + core::ptr::write_bytes(entry, 0, 1); + break; + }, + } + } + + // Now walk in order visited page tables + for entry in page_tables.into_iter() { + // Level not visited, skip. + if entry.is_null() { + continue; + } + + unsafe { + let children = &mut (*(*entry).ptr()).pt.childen; + *children -= 1; // Decrease children count + + // If there are no children, deallocate. + if *children == 0 { + let _ = Box::from_raw((*entry).ptr() as *mut PageTable); + + // Zero visited entry + core::ptr::write_bytes(entry, 0, 1); + } else { + break; + } + } + } + + Ok(()) + } +} + +/// Error mapping +#[derive(Clone, Copy, Display, Debug, PartialEq, Eq)] +pub enum MapError { + /// Entry was already mapped + #[display(fmt = "There is already a page mapped on specified address")] + AlreadyMapped, + /// When walking a page entry was + /// encounterd. + #[display(fmt = "There was a page mapped on the way instead of node")] + PageOnNode, +} + +/// There was no entry in page table to unmap +/// +/// No worry, don't panic, nothing bad has happened, +/// but if you are 120% sure there should be something, +/// double-check your addresses. +#[derive(Clone, Copy, Display, Debug)] +#[display(fmt = "There was no entry to unmap")] +pub struct NothingToUnmap; diff --git a/hbvm/src/softpaging/mod.rs b/hbvm/src/softpaging/mod.rs index 07e05a54..67c030cf 100644 --- a/hbvm/src/softpaging/mod.rs +++ b/hbvm/src/softpaging/mod.rs @@ -1,17 +1,19 @@ //! Platform independent, software paged memory implementation +use self::lookup::{AddrPageLookupError, AddrPageLookupOk, AddrPageLookuper}; + +pub mod lookup; pub mod paging; +#[cfg(feature = "alloc")] +pub mod mapping; + use { super::{LoadError, Memory, MemoryAccessReason, StoreError}, core::slice::SliceIndex, - derive_more::Display, paging::{PageTable, Permission}, }; -#[cfg(feature = "alloc")] -use {alloc::boxed::Box, paging::PtEntry}; - /// HoleyBytes software paged memory #[derive(Clone, Debug)] pub struct SoftPagedMem<'p, PfH> { @@ -173,261 +175,6 @@ impl<'p, PfH: HandlePageFault> SoftPagedMem<'p, PfH> { } } -/// Good result from address split -struct AddrPageLookupOk { - /// Virtual address - vaddr: u64, - - /// Pointer to the start for perform operation - ptr: *mut u8, - - /// Size to the end of page / end of desired size - size: usize, - - /// Page permission - perm: Permission, -} - -/// Errornous address split result -struct AddrPageLookupError { - /// Address of failure - addr: u64, - - /// Requested page size - size: PageSize, -} - -/// Address splitter into pages -struct AddrPageLookuper { - /// Current address - addr: u64, - - /// Size left - size: usize, - - /// Page table - pagetable: *const PageTable, -} - -impl AddrPageLookuper { - /// Create a new page lookuper - #[inline] - pub const fn new(addr: u64, size: usize, pagetable: *const PageTable) -> Self { - Self { - addr, - size, - pagetable, - } - } - - /// Bump address by size X - fn bump(&mut self, page_size: PageSize) { - self.addr += page_size as u64; - self.size = self.size.saturating_sub(page_size as _); - } -} - -impl Iterator for AddrPageLookuper { - type Item = Result; - - fn next(&mut self) -> Option { - // The end, everything is fine - if self.size == 0 { - return None; - } - - let (base, perm, size, offset) = 'a: { - let mut current_pt = self.pagetable; - - // Walk the page table - for lvl in (0..5).rev() { - // Get an entry - unsafe { - let entry = (*current_pt) - .table - .get_unchecked(addr_extract_index(self.addr, lvl)); - - let ptr = entry.ptr(); - match entry.permission() { - // No page → page fault - Permission::Empty => { - return Some(Err(AddrPageLookupError { - addr: self.addr, - size: PageSize::from_lvl(lvl)?, - })) - } - - // Node → proceed waking - Permission::Node => current_pt = ptr as _, - - // Leaf → return relevant data - perm => { - break 'a ( - // Pointer in host memory - ptr as *mut u8, - perm, - PageSize::from_lvl(lvl)?, - // In-page offset - addr_extract_index(self.addr, lvl), - ); - } - } - } - } - return None; // Reached the end (should not happen) - }; - - // Get available byte count in the selected page with offset - let avail = (size as usize - offset).clamp(0, self.size); - self.bump(size); - - Some(Ok(AddrPageLookupOk { - vaddr: self.addr, - ptr: unsafe { base.add(offset) }, // Return pointer to the start of region - size: avail, - perm, - })) - } -} - -#[cfg(feature = "alloc")] -impl<'p, A> Drop for SoftPagedMem<'p, A> { - fn drop(&mut self) { - let _ = unsafe { Box::from_raw(self.root_pt) }; - } -} - -#[cfg(feature = "alloc")] -impl<'p, A> SoftPagedMem<'p, A> { - /// Maps host's memory into VM's memory - /// - /// # Safety - /// - Your faith in the gods of UB - /// - Addr-san claims it's fine but who knows is she isn't lying :ferrisSus: - /// - Alright, Miri-sama is also fine with this, who knows why - pub unsafe fn map( - &mut self, - host: *mut u8, - target: u64, - perm: Permission, - pagesize: PageSize, - ) -> Result<(), MapError> { - let mut current_pt = self.root_pt; - - // Decide on what level depth are we going - let lookup_depth = match pagesize { - PageSize::Size4K => 0, - PageSize::Size2M => 1, - PageSize::Size1G => 2, - }; - - // Walk pagetable levels - for lvl in (lookup_depth + 1..5).rev() { - let entry = (*current_pt) - .table - .get_unchecked_mut(addr_extract_index(target, lvl)); - - let ptr = entry.ptr(); - match entry.permission() { - // Still not on target and already seeing empty entry? - // No worries! Let's create one (allocates). - Permission::Empty => { - // Increase children count - (*current_pt).childen += 1; - - let table = Box::into_raw(Box::new(paging::PtPointedData { - pt: PageTable::default(), - })); - - core::ptr::write(entry, PtEntry::new(table, Permission::Node)); - current_pt = table as _; - } - // Continue walking - Permission::Node => current_pt = ptr as _, - - // There is some entry on place of node - _ => return Err(MapError::PageOnNode), - } - } - - let node = (*current_pt) - .table - .get_unchecked_mut(addr_extract_index(target, lookup_depth)); - - // Check if node is not mapped - if node.permission() != Permission::Empty { - return Err(MapError::AlreadyMapped); - } - - // Write entry - (*current_pt).childen += 1; - core::ptr::write(node, PtEntry::new(host.cast(), perm)); - - Ok(()) - } - - /// Unmaps pages from VM's memory - /// - /// If errors, it only means there is no entry to unmap and in most cases - /// just should be ignored. - pub fn unmap(&mut self, addr: u64) -> Result<(), NothingToUnmap> { - let mut current_pt = self.root_pt; - let mut page_tables = [core::ptr::null_mut(); 5]; - - // Walk page table in reverse - for lvl in (0..5).rev() { - let entry = unsafe { - (*current_pt) - .table - .get_unchecked_mut(addr_extract_index(addr, lvl)) - }; - - let ptr = entry.ptr(); - match entry.permission() { - // Nothing is there, throw an error, not critical! - Permission::Empty => return Err(NothingToUnmap), - // Node – Save to visited pagetables and continue walking - Permission::Node => { - page_tables[lvl as usize] = entry; - current_pt = ptr as _ - } - // Page entry – zero it out! - // Zero page entry is completely valid entry with - // empty permission - no UB here! - _ => unsafe { - core::ptr::write_bytes(entry, 0, 1); - break; - }, - } - } - - // Now walk in order visited page tables - for entry in page_tables.into_iter() { - // Level not visited, skip. - if entry.is_null() { - continue; - } - - unsafe { - let children = &mut (*(*entry).ptr()).pt.childen; - *children -= 1; // Decrease children count - - // If there are no children, deallocate. - if *children == 0 { - let _ = Box::from_raw((*entry).ptr() as *mut PageTable); - - // Zero visited entry - core::ptr::write_bytes(entry, 0, 1); - } else { - break; - } - } - } - - Ok(()) - } -} - /// Extract index in page table on specified level /// /// The level shall not be larger than 4, otherwise @@ -462,27 +209,6 @@ impl PageSize { } } -/// Error mapping -#[derive(Clone, Copy, Display, Debug, PartialEq, Eq)] -pub enum MapError { - /// Entry was already mapped - #[display(fmt = "There is already a page mapped on specified address")] - AlreadyMapped, - /// When walking a page entry was - /// encounterd. - #[display(fmt = "There was a page mapped on the way instead of node")] - PageOnNode, -} - -/// There was no entry in page table to unmap -/// -/// No worry, don't panic, nothing bad has happened, -/// but if you are 120% sure there should be something, -/// double-check your addresses. -#[derive(Clone, Copy, Display, Debug)] -#[display(fmt = "There was no entry to unmap")] -pub struct NothingToUnmap; - /// Permisison checks pub mod perm_check { use super::paging::Permission; From 96b749060d9ecb3a270e851d21e6122c4b457a1f Mon Sep 17 00:00:00 2001 From: Erin Date: Thu, 10 Aug 2023 12:39:18 +0200 Subject: [PATCH 068/116] h --- hbvm/src/softpaging/mapping.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/hbvm/src/softpaging/mapping.rs b/hbvm/src/softpaging/mapping.rs index 9c556de5..0d6858ee 100644 --- a/hbvm/src/softpaging/mapping.rs +++ b/hbvm/src/softpaging/mapping.rs @@ -10,7 +10,6 @@ use { derive_more::Display, }; -#[cfg(feature = "alloc")] impl<'p, A> SoftPagedMem<'p, A> { /// Maps host's memory into VM's memory /// From af1a7d3bfab2453a2f300e4b1ed04cf430998def Mon Sep 17 00:00:00 2001 From: Erin Date: Tue, 15 Aug 2023 16:32:59 +0200 Subject: [PATCH 069/116] Move stuff, deprecate softpage --- hbvm/fuzz/fuzz_targets/vm.rs | 2 +- hbvm/src/lib.rs | 2 +- hbvm/src/main.rs | 4 ++-- hbvm/src/mem/mod.rs | 3 +++ hbvm/src/{ => mem}/softpaging/lookup.rs | 0 hbvm/src/{ => mem}/softpaging/mapping.rs | 0 hbvm/src/{ => mem}/softpaging/mod.rs | 6 +++--- hbvm/src/{ => mem}/softpaging/paging.rs | 0 8 files changed, 10 insertions(+), 7 deletions(-) create mode 100644 hbvm/src/mem/mod.rs rename hbvm/src/{ => mem}/softpaging/lookup.rs (100%) rename hbvm/src/{ => mem}/softpaging/mapping.rs (100%) rename hbvm/src/{ => mem}/softpaging/mod.rs (97%) rename hbvm/src/{ => mem}/softpaging/paging.rs (100%) diff --git a/hbvm/fuzz/fuzz_targets/vm.rs b/hbvm/fuzz/fuzz_targets/vm.rs index b10c6d96..e79679fa 100644 --- a/hbvm/fuzz/fuzz_targets/vm.rs +++ b/hbvm/fuzz/fuzz_targets/vm.rs @@ -3,7 +3,7 @@ use { hbbytecode::valider::validate, hbvm::{ - softpaging::{ + mem::softpaging::{ paging::{PageTable, Permission}, HandlePageFault, PageSize, SoftPagedMem, }, diff --git a/hbvm/src/lib.rs b/hbvm/src/lib.rs index 752bf515..3594a1d4 100644 --- a/hbvm/src/lib.rs +++ b/hbvm/src/lib.rs @@ -17,7 +17,7 @@ #[cfg(feature = "alloc")] extern crate alloc; -pub mod softpaging; +pub mod mem; pub mod value; mod bmc; diff --git a/hbvm/src/main.rs b/hbvm/src/main.rs index 74202713..d61d329f 100644 --- a/hbvm/src/main.rs +++ b/hbvm/src/main.rs @@ -1,7 +1,7 @@ use { hbbytecode::valider::validate, hbvm::{ - softpaging::{paging::PageTable, HandlePageFault, PageSize, SoftPagedMem}, + mem::softpaging::{paging::PageTable, HandlePageFault, PageSize, SoftPagedMem}, MemoryAccessReason, Vm, }, std::io::{stdin, Read}, @@ -38,7 +38,7 @@ fn main() -> Result<(), Box> { .map( data, 8192, - hbvm::softpaging::paging::Permission::Write, + hbvm::mem::softpaging::paging::Permission::Write, PageSize::Size4K, ) .unwrap(); diff --git a/hbvm/src/mem/mod.rs b/hbvm/src/mem/mod.rs new file mode 100644 index 00000000..f6cea2a2 --- /dev/null +++ b/hbvm/src/mem/mod.rs @@ -0,0 +1,3 @@ +//! Memory implementations + +pub mod softpaging; diff --git a/hbvm/src/softpaging/lookup.rs b/hbvm/src/mem/softpaging/lookup.rs similarity index 100% rename from hbvm/src/softpaging/lookup.rs rename to hbvm/src/mem/softpaging/lookup.rs diff --git a/hbvm/src/softpaging/mapping.rs b/hbvm/src/mem/softpaging/mapping.rs similarity index 100% rename from hbvm/src/softpaging/mapping.rs rename to hbvm/src/mem/softpaging/mapping.rs diff --git a/hbvm/src/softpaging/mod.rs b/hbvm/src/mem/softpaging/mod.rs similarity index 97% rename from hbvm/src/softpaging/mod.rs rename to hbvm/src/mem/softpaging/mod.rs index 67c030cf..6a111bbd 100644 --- a/hbvm/src/softpaging/mod.rs +++ b/hbvm/src/mem/softpaging/mod.rs @@ -1,7 +1,5 @@ //! Platform independent, software paged memory implementation -use self::lookup::{AddrPageLookupError, AddrPageLookupOk, AddrPageLookuper}; - pub mod lookup; pub mod paging; @@ -9,12 +7,14 @@ pub mod paging; pub mod mapping; use { - super::{LoadError, Memory, MemoryAccessReason, StoreError}, + crate::{LoadError, Memory, MemoryAccessReason, StoreError}, core::slice::SliceIndex, + lookup::{AddrPageLookupError, AddrPageLookupOk, AddrPageLookuper}, paging::{PageTable, Permission}, }; /// HoleyBytes software paged memory +#[deprecated] #[derive(Clone, Debug)] pub struct SoftPagedMem<'p, PfH> { /// Root page table diff --git a/hbvm/src/softpaging/paging.rs b/hbvm/src/mem/softpaging/paging.rs similarity index 100% rename from hbvm/src/softpaging/paging.rs rename to hbvm/src/mem/softpaging/paging.rs From a071a4a7ae472cb2e3414093ea61ada18bd6c133 Mon Sep 17 00:00:00 2001 From: Erin Date: Tue, 15 Aug 2023 16:33:56 +0200 Subject: [PATCH 070/116] Notice --- hbvm/src/mem/softpaging/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hbvm/src/mem/softpaging/mod.rs b/hbvm/src/mem/softpaging/mod.rs index 6a111bbd..f8c66dd3 100644 --- a/hbvm/src/mem/softpaging/mod.rs +++ b/hbvm/src/mem/softpaging/mod.rs @@ -14,7 +14,7 @@ use { }; /// HoleyBytes software paged memory -#[deprecated] +#[deprecated = "Use platform-specific memory implementation"] #[derive(Clone, Debug)] pub struct SoftPagedMem<'p, PfH> { /// Root page table From 3decd01619f241e79d48aa4f6bf7b2ce485abe7f Mon Sep 17 00:00:00 2001 From: Erin Date: Tue, 15 Aug 2023 17:05:10 +0200 Subject: [PATCH 071/116] Modified memory interface I have no idea what I am doing rn --- hbvm/src/lib.rs | 54 ++++++++-------------------------- hbvm/src/mem/softpaging/mod.rs | 23 ++++++--------- 2 files changed, 22 insertions(+), 55 deletions(-) diff --git a/hbvm/src/lib.rs b/hbvm/src/lib.rs index 3594a1d4..a87366b8 100644 --- a/hbvm/src/lib.rs +++ b/hbvm/src/lib.rs @@ -24,7 +24,7 @@ mod bmc; use { bmc::BlockCopier, - core::{cmp::Ordering, mem::size_of, ops, slice::SliceIndex}, + core::{cmp::Ordering, mem::size_of, ops}, derive_more::Display, hbbytecode::{OpParam, ParamBB, ParamBBB, ParamBBBB, ParamBBD, ParamBBDH, ParamBBW, ParamBD}, value::{Value, ValueVariant}, @@ -96,9 +96,9 @@ where // - Yes, we assume you run 64 bit CPU. Else ?conradluget a better CPU // sorry 8 bit fans, HBVM won't run on your Speccy :( unsafe { - match *self + match self .memory - .load_prog(self.pc) + .prog_read::(self.pc as _) .ok_or(VmRunError::ProgramFetchLoadEx(self.pc as _))? { UN => { @@ -368,13 +368,7 @@ where #[inline(always)] unsafe fn decode(&mut self) -> T { let pc1 = self.pc + 1; - let data = self - .memory - .load_prog_unchecked(pc1..pc1 + size_of::()) - .as_ptr() - .cast::() - .read(); - + let data = self.memory.prog_read_unchecked::(pc1 as _); self.pc += 1 + size_of::(); data } @@ -517,39 +511,17 @@ pub trait Memory { count: usize, ) -> Result<(), StoreError>; - /// Fetch bytes from program section - /// - /// # Why? - /// Even Holey Bytes programs operate with - /// single address space, the actual implementation - /// may be different, so for these reasons there is a - /// separate function. - /// - /// Also if your memory implementation differentiates between - /// readable and executable memory, this is the way to distinguish - /// the loads. - /// - /// # Notice for implementors - /// This is a hot function. This is called on each opcode fetch - /// and instruction decode. Inlining the implementation is highly - /// recommended! - /// - /// If you utilise some more heavy memory implementation, consider - /// performing caching as HBVM does not do that for you. - /// - /// Has to return all the requested data. If cannot fetch data of requested - /// length, return [`None`]. - fn load_prog(&mut self, index: I) -> Option<&I::Output> - where - I: SliceIndex<[u8]>; - - /// Fetch bytes from program section, unchecked. + /// Read from program memory to execute /// /// # Safety - /// You really have to be sure you get the bytes, got me? - unsafe fn load_prog_unchecked(&mut self, index: I) -> &I::Output - where - I: SliceIndex<[u8]>; + /// - Data read have to be valid + unsafe fn prog_read(&mut self, addr: u64) -> Option; + + /// Read from program memory to exectue + /// + /// # Safety + /// - You have to be really sure that these bytes are there, understand? + unsafe fn prog_read_unchecked(&mut self, addr: u64) -> T; } /// Unhandled load access trap diff --git a/hbvm/src/mem/softpaging/mod.rs b/hbvm/src/mem/softpaging/mod.rs index f8c66dd3..b50640e6 100644 --- a/hbvm/src/mem/softpaging/mod.rs +++ b/hbvm/src/mem/softpaging/mod.rs @@ -1,5 +1,7 @@ //! Platform independent, software paged memory implementation +use core::mem::size_of; + pub mod lookup; pub mod paging; @@ -63,22 +65,15 @@ impl<'p, PfH: HandlePageFault> Memory for SoftPagedMem<'p, PfH> { .map_err(StoreError) } - /// Fetch slice from program memory section - #[inline(always)] - fn load_prog(&mut self, index: I) -> Option<&I::Output> - where - I: SliceIndex<[u8]>, - { - self.program.get(index) + unsafe fn prog_read(&mut self, addr: u64) -> Option { + let addr = addr as usize; + self.program + .get(addr..addr + size_of::()) + .map(|x| x.as_ptr().cast::().read()) } - /// Fetch slice from program memory section, unchecked! - #[inline(always)] - unsafe fn load_prog_unchecked(&mut self, index: I) -> &I::Output - where - I: SliceIndex<[u8]>, - { - self.program.get_unchecked(index) + unsafe fn prog_read_unchecked(&mut self, addr: u64) -> T { + self.program.as_ptr().add(addr as _).cast::().read() } } From 0deeaf3a7e09ef460273155c6f30cabf6995d16a Mon Sep 17 00:00:00 2001 From: Erin Date: Tue, 15 Aug 2023 17:21:55 +0200 Subject: [PATCH 072/116] SPID --- hbvm/src/mem/softpaging/mod.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/hbvm/src/mem/softpaging/mod.rs b/hbvm/src/mem/softpaging/mod.rs index b50640e6..db06a4eb 100644 --- a/hbvm/src/mem/softpaging/mod.rs +++ b/hbvm/src/mem/softpaging/mod.rs @@ -65,6 +65,7 @@ impl<'p, PfH: HandlePageFault> Memory for SoftPagedMem<'p, PfH> { .map_err(StoreError) } + #[inline(always)] unsafe fn prog_read(&mut self, addr: u64) -> Option { let addr = addr as usize; self.program @@ -72,6 +73,7 @@ impl<'p, PfH: HandlePageFault> Memory for SoftPagedMem<'p, PfH> { .map(|x| x.as_ptr().cast::().read()) } + #[inline(always)] unsafe fn prog_read_unchecked(&mut self, addr: u64) -> T { self.program.as_ptr().add(addr as _).cast::().read() } From 600528434be52e383d280b9376709459724f2234 Mon Sep 17 00:00:00 2001 From: Erin Date: Thu, 17 Aug 2023 01:37:53 +0200 Subject: [PATCH 073/116] nope. --- hbvm/src/mem/softpaging/mod.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/hbvm/src/mem/softpaging/mod.rs b/hbvm/src/mem/softpaging/mod.rs index db06a4eb..6ed66dc4 100644 --- a/hbvm/src/mem/softpaging/mod.rs +++ b/hbvm/src/mem/softpaging/mod.rs @@ -10,13 +10,11 @@ pub mod mapping; use { crate::{LoadError, Memory, MemoryAccessReason, StoreError}, - core::slice::SliceIndex, lookup::{AddrPageLookupError, AddrPageLookupOk, AddrPageLookuper}, paging::{PageTable, Permission}, }; /// HoleyBytes software paged memory -#[deprecated = "Use platform-specific memory implementation"] #[derive(Clone, Debug)] pub struct SoftPagedMem<'p, PfH> { /// Root page table From d282b3d111e75413a47fabf6166288e6c5d06307 Mon Sep 17 00:00:00 2001 From: Erin Date: Fri, 18 Aug 2023 01:28:02 +0200 Subject: [PATCH 074/116] Softpage improvements --- Cargo.toml | 3 +- hbbytecode/src/lib.rs | 33 ++++----- hbvm/fuzz/fuzz_targets/vm.rs | 7 +- hbvm/src/lib.rs | 10 +-- hbvm/src/main.rs | 5 +- hbvm/src/mem/softpaging/icache.rs | 103 +++++++++++++++++++++++++++++ hbvm/src/mem/softpaging/lookup.rs | 2 +- hbvm/src/mem/softpaging/mapping.rs | 2 +- hbvm/src/mem/softpaging/mod.rs | 28 +++++++- 9 files changed, 161 insertions(+), 32 deletions(-) create mode 100644 hbvm/src/mem/softpaging/icache.rs diff --git a/Cargo.toml b/Cargo.toml index 7fc1adec..1c77a263 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,2 +1,3 @@ [workspace] -members = ["hbasm", "hbbytecode", "hbvm"] +resolver = "2" +members = ["hbasm", "hbbytecode", "hbvm"] diff --git a/hbbytecode/src/lib.rs b/hbbytecode/src/lib.rs index 6dda4da4..3f1c14b0 100644 --- a/hbbytecode/src/lib.rs +++ b/hbbytecode/src/lib.rs @@ -35,12 +35,12 @@ macro_rules! constmod { /// - I: Immediate /// - L: Memory load / store size (u16) /// - Other types are identity-mapped -/// +/// /// # BRC special-case /// BRC's 3rd operand is plain byte, not a register. Encoding is the same, but for some cases it may matter. -/// +/// /// Please, if you distinguish in your API between byte and register, special case this one. -/// +/// /// Sorry for that :( #[macro_export] macro_rules! invoke_with_def { @@ -134,34 +134,29 @@ constmod!(pub opcode(u8) { #[repr(packed)] pub struct ParamBBBB(pub u8, pub u8, pub u8, pub u8); - #[repr(packed)] pub struct ParamBBB(pub u8, pub u8, pub u8); - #[repr(packed)] pub struct ParamBBDH(pub u8, pub u8, pub u64, pub u16); - #[repr(packed)] pub struct ParamBBD(pub u8, pub u8, pub u64); - #[repr(packed)] pub struct ParamBBW(pub u8, pub u8, pub u32); - #[repr(packed)] pub struct ParamBB(pub u8, pub u8); - #[repr(packed)] pub struct ParamBD(pub u8, pub u64); /// # Safety /// Has to be valid to be decoded from bytecode. -pub unsafe trait OpParam {} -unsafe impl OpParam for ParamBBBB {} -unsafe impl OpParam for ParamBBB {} -unsafe impl OpParam for ParamBBDH {} -unsafe impl OpParam for ParamBBD {} -unsafe impl OpParam for ParamBBW {} -unsafe impl OpParam for ParamBB {} -unsafe impl OpParam for ParamBD {} -unsafe impl OpParam for u64 {} -unsafe impl OpParam for () {} +pub unsafe trait ProgramVal {} +unsafe impl ProgramVal for ParamBBBB {} +unsafe impl ProgramVal for ParamBBB {} +unsafe impl ProgramVal for ParamBBDH {} +unsafe impl ProgramVal for ParamBBD {} +unsafe impl ProgramVal for ParamBBW {} +unsafe impl ProgramVal for ParamBB {} +unsafe impl ProgramVal for ParamBD {} +unsafe impl ProgramVal for u64 {} +unsafe impl ProgramVal for u8 {} // Opcode +unsafe impl ProgramVal for () {} diff --git a/hbvm/fuzz/fuzz_targets/vm.rs b/hbvm/fuzz/fuzz_targets/vm.rs index e79679fa..f7336000 100644 --- a/hbvm/fuzz/fuzz_targets/vm.rs +++ b/hbvm/fuzz/fuzz_targets/vm.rs @@ -16,10 +16,11 @@ fuzz_target!(|data: &[u8]| { if validate(data).is_ok() { let mut vm = unsafe { Vm::<_, 16384>::new( - SoftPagedMem { + SoftPagedMem::<_, true> { pf_handler: TestTrapHandler, program: data, root_pt: Box::into_raw(Default::default()), + icache: Default::default(), }, 0, ) @@ -31,6 +32,8 @@ fuzz_target!(|data: &[u8]| { alloc_and_map(&mut vm.memory, 4096), ]; + unsafe { vm.memory.write() }; + // Run VM let _ = vm.run(); @@ -38,6 +41,8 @@ fuzz_target!(|data: &[u8]| { for (i, page) in pages.into_iter().enumerate() { unmap_and_dealloc(&mut vm.memory, page, i as u64 * 4096); } + + let _ = unsafe { Box::from_raw(vm.memory.root_pt) }; } }); diff --git a/hbvm/src/lib.rs b/hbvm/src/lib.rs index a87366b8..22d39fe7 100644 --- a/hbvm/src/lib.rs +++ b/hbvm/src/lib.rs @@ -26,7 +26,9 @@ use { bmc::BlockCopier, core::{cmp::Ordering, mem::size_of, ops}, derive_more::Display, - hbbytecode::{OpParam, ParamBB, ParamBBB, ParamBBBB, ParamBBD, ParamBBDH, ParamBBW, ParamBD}, + hbbytecode::{ + ParamBB, ParamBBB, ParamBBBB, ParamBBD, ParamBBDH, ParamBBW, ParamBD, ProgramVal, + }, value::{Value, ValueVariant}, }; @@ -366,7 +368,7 @@ where /// Decode instruction operands #[inline(always)] - unsafe fn decode(&mut self) -> T { + unsafe fn decode(&mut self) -> T { let pc1 = self.pc + 1; let data = self.memory.prog_read_unchecked::(pc1 as _); self.pc += 1 + size_of::(); @@ -515,13 +517,13 @@ pub trait Memory { /// /// # Safety /// - Data read have to be valid - unsafe fn prog_read(&mut self, addr: u64) -> Option; + unsafe fn prog_read(&mut self, addr: u64) -> Option; /// Read from program memory to exectue /// /// # Safety /// - You have to be really sure that these bytes are there, understand? - unsafe fn prog_read_unchecked(&mut self, addr: u64) -> T; + unsafe fn prog_read_unchecked(&mut self, addr: u64) -> T; } /// Unhandled load access trap diff --git a/hbvm/src/main.rs b/hbvm/src/main.rs index d61d329f..04d654dd 100644 --- a/hbvm/src/main.rs +++ b/hbvm/src/main.rs @@ -17,10 +17,11 @@ fn main() -> Result<(), Box> { } else { unsafe { let mut vm = Vm::<_, 0>::new( - SoftPagedMem { + SoftPagedMem::<_, true> { pf_handler: TestTrapHandler, program: &prog, root_pt: Box::into_raw(Default::default()), + icache: Default::default(), }, 4, ); @@ -46,12 +47,12 @@ fn main() -> Result<(), Box> { println!("Program interrupt: {:?}", vm.run()); println!("{:?}", vm.registers); - println!("{:?}", core::slice::from_raw_parts(data, 4096)); std::alloc::dealloc( data, std::alloc::Layout::from_size_align_unchecked(4096, 4096), ); vm.memory.unmap(8192).unwrap(); + let _ = Box::from_raw(vm.memory.root_pt); } } Ok(()) diff --git a/hbvm/src/mem/softpaging/icache.rs b/hbvm/src/mem/softpaging/icache.rs new file mode 100644 index 00000000..049d26b0 --- /dev/null +++ b/hbvm/src/mem/softpaging/icache.rs @@ -0,0 +1,103 @@ +//! Program instruction cache + +use { + super::{lookup::AddrPageLookuper, paging::PageTable, PageSize}, + core::{ + mem::{size_of, MaybeUninit}, + ptr::{copy_nonoverlapping, NonNull}, + }, +}; + +/// Instruction cache +#[derive(Clone, Debug)] +pub struct ICache { + /// Current page address base + base: u64, + /// Curent page pointer + data: Option>, + /// Current page size + size: PageSize, + /// Address mask + mask: u64, +} + +impl Default for ICache { + fn default() -> Self { + Self { + base: Default::default(), + data: Default::default(), + size: PageSize::Size4K, + mask: Default::default(), + } + } +} + +impl ICache { + /// Fetch instruction from cache + /// + /// # Safety + /// `T` should be valid to read from instruction memory + pub(super) unsafe fn fetch(&mut self, addr: u64, root_pt: *const PageTable) -> Option { + let mut ret = MaybeUninit::::uninit(); + + let pbase = self + .data + .or_else(|| self.fetch_page(self.base.checked_add(self.size as _)?, root_pt))?; + + // Get address base + let base = addr & self.mask; + + // Base not matching, fetch anew + if base != self.base { + self.fetch_page(base, root_pt)?; + }; + + let offset = addr & !self.mask; + let requ_size = size_of::(); + + // Page overflow + let rem = (offset as usize) + .saturating_add(requ_size) + .saturating_sub(self.size as _); + let first_copy = requ_size.saturating_sub(rem); + + // Copy non-overflowing part + copy_nonoverlapping(pbase.as_ptr(), ret.as_mut_ptr().cast::(), first_copy); + + // Copy overflow + if rem != 0 { + let pbase = self.fetch_page(self.base.checked_add(self.size as _)?, root_pt)?; + + // Unlikely, unsupported scenario + if rem > self.size as _ { + return None; + } + + copy_nonoverlapping( + pbase.as_ptr(), + ret.as_mut_ptr().cast::().add(first_copy), + rem, + ); + } + + Some(ret.assume_init()) + } + + /// Fetch a page + unsafe fn fetch_page(&mut self, addr: u64, pt: *const PageTable) -> Option> { + let res = AddrPageLookuper::new(addr, 0, pt).next()?.ok()?; + if !super::perm_check::executable(res.perm) { + return None; + } + + (self.size, self.mask) = match res.size { + 4096 => (PageSize::Size4K, !((1 << 8) - 1)), + 2097152 => (PageSize::Size2M, !((1 << (8 * 2)) - 1)), + 1073741824 => (PageSize::Size1G, !((1 << (8 * 3)) - 1)), + _ => return None, + }; + self.data = Some(NonNull::new(res.ptr)?); + self.base = addr & self.mask; + self.data + } +} diff --git a/hbvm/src/mem/softpaging/lookup.rs b/hbvm/src/mem/softpaging/lookup.rs index 91113905..1e9c3e31 100644 --- a/hbvm/src/mem/softpaging/lookup.rs +++ b/hbvm/src/mem/softpaging/lookup.rs @@ -111,7 +111,7 @@ impl Iterator for AddrPageLookuper { }; // Get available byte count in the selected page with offset - let avail = (size as usize - offset).clamp(0, self.size); + let avail = (size as usize).saturating_sub(offset).clamp(0, self.size); self.bump(size); Some(Ok(AddrPageLookupOk { diff --git a/hbvm/src/mem/softpaging/mapping.rs b/hbvm/src/mem/softpaging/mapping.rs index 0d6858ee..8f406a4a 100644 --- a/hbvm/src/mem/softpaging/mapping.rs +++ b/hbvm/src/mem/softpaging/mapping.rs @@ -10,7 +10,7 @@ use { derive_more::Display, }; -impl<'p, A> SoftPagedMem<'p, A> { +impl<'p, A, const OUT_PROG_EXEC: bool> SoftPagedMem<'p, A, OUT_PROG_EXEC> { /// Maps host's memory into VM's memory /// /// # Safety diff --git a/hbvm/src/mem/softpaging/mod.rs b/hbvm/src/mem/softpaging/mod.rs index 6ed66dc4..11fa4658 100644 --- a/hbvm/src/mem/softpaging/mod.rs +++ b/hbvm/src/mem/softpaging/mod.rs @@ -2,6 +2,9 @@ use core::mem::size_of; +use self::icache::ICache; + +pub mod icache; pub mod lookup; pub mod paging; @@ -15,17 +18,25 @@ use { }; /// HoleyBytes software paged memory +/// +/// - `OUT_PROG_EXEC`: set to `false` to disable executing program +/// not contained in initially provided program, even the pages +/// are executable #[derive(Clone, Debug)] -pub struct SoftPagedMem<'p, PfH> { +pub struct SoftPagedMem<'p, PfH, const OUT_PROG_EXEC: bool = true> { /// Root page table pub root_pt: *mut PageTable, /// Page fault handler pub pf_handler: PfH, /// Program memory segment pub program: &'p [u8], + /// Program instruction cache + pub icache: ICache, } -impl<'p, PfH: HandlePageFault> Memory for SoftPagedMem<'p, PfH> { +impl<'p, PfH: HandlePageFault, const OUT_PROG_EXEC: bool> Memory + for SoftPagedMem<'p, PfH, OUT_PROG_EXEC> +{ /// Load value from an address /// /// # Safety @@ -65,6 +76,10 @@ impl<'p, PfH: HandlePageFault> Memory for SoftPagedMem<'p, PfH> { #[inline(always)] unsafe fn prog_read(&mut self, addr: u64) -> Option { + if OUT_PROG_EXEC && addr as usize > self.program.len() { + return self.icache.fetch::(addr, self.root_pt); + } + let addr = addr as usize; self.program .get(addr..addr + size_of::()) @@ -73,11 +88,18 @@ impl<'p, PfH: HandlePageFault> Memory for SoftPagedMem<'p, PfH> { #[inline(always)] unsafe fn prog_read_unchecked(&mut self, addr: u64) -> T { + if OUT_PROG_EXEC && addr as usize > self.program.len() { + return self + .icache + .fetch::(addr as _, self.root_pt) + .unwrap_or_else(|| core::mem::zeroed()); + } + self.program.as_ptr().add(addr as _).cast::().read() } } -impl<'p, PfH: HandlePageFault> SoftPagedMem<'p, PfH> { +impl<'p, PfH: HandlePageFault, const OUT_PROG_EXEC: bool> SoftPagedMem<'p, PfH, OUT_PROG_EXEC> { // Everyone behold, the holy function, the god of HBVM memory accesses! /// Split address to pages, check their permissions and feed pointers with offset From 30070818ae8b76c63c6c3f76963b18bf8a2ef049 Mon Sep 17 00:00:00 2001 From: Erin Date: Fri, 18 Aug 2023 01:41:05 +0200 Subject: [PATCH 075/116] Move --- hbvm/src/bmc.rs | 6 +- hbvm/src/lib.rs | 459 +-------------------------------- hbvm/src/main.rs | 7 +- hbvm/src/mem/mod.rs | 65 +++++ hbvm/src/mem/softpaging/mod.rs | 4 +- hbvm/src/vmrun.rs | 404 +++++++++++++++++++++++++++++ 6 files changed, 483 insertions(+), 462 deletions(-) create mode 100644 hbvm/src/vmrun.rs diff --git a/hbvm/src/bmc.rs b/hbvm/src/bmc.rs index 391579f6..ef0d9cdd 100644 --- a/hbvm/src/bmc.rs +++ b/hbvm/src/bmc.rs @@ -1,7 +1,7 @@ //! Block memory copier state machine use { - super::{Memory, MemoryAccessReason, VmRunError}, + super::{Memory, mem::MemoryAccessReason, VmRunError}, core::{mem::MaybeUninit, task::Poll}, }; @@ -108,7 +108,7 @@ unsafe fn act( // Load to buffer memory .load(src, buf, count) - .map_err(|super::LoadError(addr)| BlkCopyError::Access { + .map_err(|super::mem::LoadError(addr)| BlkCopyError::Access { access_reason: MemoryAccessReason::Load, addr, })?; @@ -116,7 +116,7 @@ unsafe fn act( // Store from buffer memory .store(dst, buf, count) - .map_err(|super::StoreError(addr)| BlkCopyError::Access { + .map_err(|super::mem::StoreError(addr)| BlkCopyError::Access { access_reason: MemoryAccessReason::Store, addr, })?; diff --git a/hbvm/src/lib.rs b/hbvm/src/lib.rs index 22d39fe7..b0ee1437 100644 --- a/hbvm/src/lib.rs +++ b/hbvm/src/lib.rs @@ -14,6 +14,8 @@ #![cfg_attr(feature = "nightly", feature(fn_align))] #![warn(missing_docs, clippy::missing_docs_in_private_items)] +use mem::Memory; + #[cfg(feature = "alloc")] extern crate alloc; @@ -21,16 +23,9 @@ pub mod mem; pub mod value; mod bmc; +mod vmrun; -use { - bmc::BlockCopier, - core::{cmp::Ordering, mem::size_of, ops}, - derive_more::Display, - hbbytecode::{ - ParamBB, ParamBBB, ParamBBBB, ParamBBD, ParamBBDH, ParamBBW, ParamBD, ProgramVal, - }, - value::{Value, ValueVariant}, -}; +use {bmc::BlockCopier, value::Value}; /// HoleyBytes Virtual Machine pub struct Vm { @@ -70,389 +65,6 @@ where copier: None, } } - - /// Execute program - /// - /// Program can return [`VmRunError`] if a trap handling failed - #[cfg_attr(feature = "nightly", repr(align(4096)))] - pub fn run(&mut self) -> Result { - use hbbytecode::opcode::*; - loop { - // Big match - // - // Contribution guide: - // - Zero register shall never be overwitten. It's value has to always be 0. - // - Prefer `Self::read_reg` and `Self::write_reg` functions - // - Extract parameters using `param!` macro - // - Prioritise speed over code size - // - Memory is cheap, CPUs not that much - // - Do not heap allocate at any cost - // - Yes, user-provided trap handler may allocate, - // but that is not our »fault«. - // - Unsafe is kinda must, but be sure you have validated everything - // - Your contributions have to pass sanitizers and Miri - // - Strictly follow the spec - // - The spec does not specify how you perform actions, in what order, - // just that the observable effects have to be performed in order and - // correctly. - // - Yes, we assume you run 64 bit CPU. Else ?conradluget a better CPU - // sorry 8 bit fans, HBVM won't run on your Speccy :( - unsafe { - match self - .memory - .prog_read::(self.pc as _) - .ok_or(VmRunError::ProgramFetchLoadEx(self.pc as _))? - { - UN => { - self.decode::<()>(); - return Err(VmRunError::Unreachable); - } - TX => { - self.decode::<()>(); - return Ok(VmRunOk::End); - } - NOP => self.decode::<()>(), - ADD => self.binary_op(u64::wrapping_add), - SUB => self.binary_op(u64::wrapping_sub), - MUL => self.binary_op(u64::wrapping_mul), - AND => self.binary_op::(ops::BitAnd::bitand), - OR => self.binary_op::(ops::BitOr::bitor), - XOR => self.binary_op::(ops::BitXor::bitxor), - SL => self.binary_op(|l, r| u64::wrapping_shl(l, r as u32)), - SR => self.binary_op(|l, r| u64::wrapping_shr(l, r as u32)), - SRS => self.binary_op(|l, r| i64::wrapping_shl(l, r as u32)), - CMP => { - // Compare a0 <=> a1 - // < → -1 - // > → 1 - // = → 0 - - let ParamBBB(tg, a0, a1) = self.decode(); - self.write_reg( - tg, - self.read_reg(a0) - .cast::() - .cmp(&self.read_reg(a1).cast::()) - as i64, - ); - } - CMPU => { - // Unsigned comparsion - let ParamBBB(tg, a0, a1) = self.decode(); - self.write_reg( - tg, - self.read_reg(a0) - .cast::() - .cmp(&self.read_reg(a1).cast::()) - as i64, - ); - } - NOT => { - // Logical negation - let ParamBB(tg, a0) = self.decode(); - self.write_reg(tg, !self.read_reg(a0).cast::()); - } - NEG => { - // Bitwise negation - let ParamBB(tg, a0) = self.decode(); - self.write_reg( - tg, - match self.read_reg(a0).cast::() { - 0 => 1_u64, - _ => 0, - }, - ); - } - DIR => { - // Fused Division-Remainder - let ParamBBBB(dt, rt, a0, a1) = self.decode(); - let a0 = self.read_reg(a0).cast::(); - let a1 = self.read_reg(a1).cast::(); - self.write_reg(dt, a0.checked_div(a1).unwrap_or(u64::MAX)); - self.write_reg(rt, a0.checked_rem(a1).unwrap_or(u64::MAX)); - } - ADDI => self.binary_op_imm(u64::wrapping_add), - MULI => self.binary_op_imm(u64::wrapping_sub), - ANDI => self.binary_op_imm::(ops::BitAnd::bitand), - ORI => self.binary_op_imm::(ops::BitOr::bitor), - XORI => self.binary_op_imm::(ops::BitXor::bitxor), - SLI => self.binary_op_ims(u64::wrapping_shl), - SRI => self.binary_op_ims(u64::wrapping_shr), - SRSI => self.binary_op_ims(i64::wrapping_shr), - CMPI => { - let ParamBBD(tg, a0, imm) = self.decode(); - self.write_reg( - tg, - self.read_reg(a0) - .cast::() - .cmp(&Value::from(imm).cast::()) - as i64, - ); - } - CMPUI => { - let ParamBBD(tg, a0, imm) = self.decode(); - self.write_reg(tg, self.read_reg(a0).cast::().cmp(&imm) as i64); - } - CP => { - let ParamBB(tg, a0) = self.decode(); - self.write_reg(tg, self.read_reg(a0)); - } - SWA => { - // Swap registers - let ParamBB(r0, r1) = self.decode(); - match (r0, r1) { - (0, 0) => (), - (dst, 0) | (0, dst) => self.write_reg(dst, 0_u64), - (r0, r1) => { - core::ptr::swap( - self.registers.get_unchecked_mut(usize::from(r0)), - self.registers.get_unchecked_mut(usize::from(r1)), - ); - } - } - } - LI => { - let ParamBD(tg, imm) = self.decode(); - self.write_reg(tg, imm); - } - LD => { - // Load. If loading more than register size, continue on adjecent registers - let ParamBBDH(dst, base, off, count) = self.decode(); - let n: u8 = match dst { - 0 => 1, - _ => 0, - }; - - self.memory.load( - self.ldst_addr_uber(dst, base, off, count, n)?, - self.registers - .as_mut_ptr() - .add(usize::from(dst) + usize::from(n)) - .cast(), - usize::from(count).saturating_sub(n.into()), - )?; - } - ST => { - // Store. Same rules apply as to LD - let ParamBBDH(dst, base, off, count) = self.decode(); - self.memory.store( - self.ldst_addr_uber(dst, base, off, count, 0)?, - self.registers.as_ptr().add(usize::from(dst)).cast(), - count.into(), - )?; - } - BMC => { - // Block memory copy - match if let Some(copier) = &mut self.copier { - // There is some copier, poll. - copier.poll(&mut self.memory) - } else { - // There is none, make one! - let ParamBBD(src, dst, count) = self.decode(); - - // So we are still on BMC on next cycle - self.pc -= size_of::() + 1; - - self.copier = Some(BlockCopier::new( - self.read_reg(src).cast(), - self.read_reg(dst).cast(), - count as _, - )); - - self.copier - .as_mut() - .unwrap_unchecked() // SAFETY: We just assigned there - .poll(&mut self.memory) - } { - // We are done, shift program counter - core::task::Poll::Ready(Ok(())) => { - self.copier = None; - self.pc += size_of::() + 1; - } - // Error, shift program counter (for consistency) - // and yield error - core::task::Poll::Ready(Err(e)) => { - self.pc += size_of::() + 1; - return Err(e.into()); - } - // Not done yet, proceed to next cycle - core::task::Poll::Pending => (), - } - } - BRC => { - // Block register copy - let ParamBBB(src, dst, count) = self.decode(); - if src.checked_add(count).is_none() || dst.checked_add(count).is_none() { - return Err(VmRunError::RegOutOfBounds); - } - - core::ptr::copy( - self.registers.get_unchecked(usize::from(src)), - self.registers.get_unchecked_mut(usize::from(dst)), - usize::from(count), - ); - } - JAL => { - // Jump and link. Save PC after this instruction to - // specified register and jump to reg + offset. - let ParamBBD(save, reg, offset) = self.decode(); - self.write_reg(save, self.pc as u64); - self.pc = - (self.read_reg(reg).cast::().saturating_add(offset)) as usize; - } - // Conditional jumps, jump only to immediates - JEQ => self.cond_jmp::(Ordering::Equal), - JNE => { - let ParamBBD(a0, a1, jt) = self.decode(); - if self.read_reg(a0).cast::() != self.read_reg(a1).cast::() { - self.pc = jt as usize; - } - } - JLT => self.cond_jmp::(Ordering::Less), - JGT => self.cond_jmp::(Ordering::Greater), - JLTU => self.cond_jmp::(Ordering::Less), - JGTU => self.cond_jmp::(Ordering::Greater), - ECALL => { - self.decode::<()>(); - - // So we don't get timer interrupt after ECALL - if TIMER_QUOTIENT != 0 { - self.timer = self.timer.wrapping_add(1); - } - return Ok(VmRunOk::Ecall); - } - ADDF => self.binary_op::(ops::Add::add), - SUBF => self.binary_op::(ops::Sub::sub), - MULF => self.binary_op::(ops::Mul::mul), - DIRF => { - let ParamBBBB(dt, rt, a0, a1) = self.decode(); - let a0 = self.read_reg(a0).cast::(); - let a1 = self.read_reg(a1).cast::(); - self.write_reg(dt, a0 / a1); - self.write_reg(rt, a0 % a1); - } - FMAF => { - let ParamBBBB(dt, a0, a1, a2) = self.decode(); - self.write_reg( - dt, - self.read_reg(a0).cast::() * self.read_reg(a1).cast::() - + self.read_reg(a2).cast::(), - ); - } - NEGF => { - let ParamBB(dt, a0) = self.decode(); - self.write_reg(dt, -self.read_reg(a0).cast::()); - } - ITF => { - let ParamBB(dt, a0) = self.decode(); - self.write_reg(dt, self.read_reg(a0).cast::() as f64); - } - FTI => { - let ParamBB(dt, a0) = self.decode(); - self.write_reg(dt, self.read_reg(a0).cast::() as i64); - } - ADDFI => self.binary_op_imm::(ops::Add::add), - MULFI => self.binary_op_imm::(ops::Mul::mul), - op => return Err(VmRunError::InvalidOpcode(op)), - } - } - - if TIMER_QUOTIENT != 0 { - self.timer = self.timer.wrapping_add(1); - if self.timer % TIMER_QUOTIENT == 0 { - return Ok(VmRunOk::Timer); - } - } - } - } - - /// Decode instruction operands - #[inline(always)] - unsafe fn decode(&mut self) -> T { - let pc1 = self.pc + 1; - let data = self.memory.prog_read_unchecked::(pc1 as _); - self.pc += 1 + size_of::(); - data - } - - /// Perform binary operating over two registers - #[inline(always)] - unsafe fn binary_op(&mut self, op: impl Fn(T, T) -> T) { - let ParamBBB(tg, a0, a1) = self.decode(); - self.write_reg( - tg, - op(self.read_reg(a0).cast::(), self.read_reg(a1).cast::()), - ); - } - - /// Perform binary operation over register and immediate - #[inline(always)] - unsafe fn binary_op_imm(&mut self, op: impl Fn(T, T) -> T) { - let ParamBBD(tg, reg, imm) = self.decode(); - self.write_reg( - tg, - op(self.read_reg(reg).cast::(), Value::from(imm).cast::()), - ); - } - - /// Perform binary operation over register and shift immediate - #[inline(always)] - unsafe fn binary_op_ims(&mut self, op: impl Fn(T, u32) -> T) { - let ParamBBW(tg, reg, imm) = self.decode(); - self.write_reg(tg, op(self.read_reg(reg).cast::(), imm)); - } - - /// Jump at `#3` if ordering on `#0 <=> #1` is equal to expected - #[inline(always)] - unsafe fn cond_jmp(&mut self, expected: Ordering) { - let ParamBBD(a0, a1, ja) = self.decode(); - if self - .read_reg(a0) - .cast::() - .cmp(&self.read_reg(a1).cast::()) - == expected - { - self.pc = ja as usize; - } - } - - /// Read register - #[inline(always)] - unsafe fn read_reg(&self, n: u8) -> Value { - *self.registers.get_unchecked(n as usize) - } - - /// Write a register. - /// Writing to register 0 is no-op. - #[inline(always)] - unsafe fn write_reg(&mut self, n: u8, value: impl Into) { - if n != 0 { - *self.registers.get_unchecked_mut(n as usize) = value.into(); - } - } - - /// Load / Store Address check-computation überfunction - #[inline(always)] - unsafe fn ldst_addr_uber( - &self, - dst: u8, - base: u8, - offset: u64, - size: u16, - adder: u8, - ) -> Result { - let reg = dst.checked_add(adder).ok_or(VmRunError::RegOutOfBounds)?; - - if usize::from(reg) * 8 + usize::from(size) > 2048 { - Err(VmRunError::RegOutOfBounds) - } else { - self.read_reg(base) - .cast::() - .checked_add(offset) - .and_then(|x| x.checked_add(adder.into())) - .ok_or(VmRunError::AddrOutOfBounds) - } - } } /// Virtual machine halt error @@ -493,66 +105,3 @@ pub enum VmRunOk { /// Environment call Ecall, } - -/// Load-store memory access -pub trait Memory { - /// Load data from memory on address - /// - /// # Safety - /// - Shall not overrun the buffer - unsafe fn load(&mut self, addr: u64, target: *mut u8, count: usize) -> Result<(), LoadError>; - - /// Store data to memory on address - /// - /// # Safety - /// - Shall not overrun the buffer - unsafe fn store( - &mut self, - addr: u64, - source: *const u8, - count: usize, - ) -> Result<(), StoreError>; - - /// Read from program memory to execute - /// - /// # Safety - /// - Data read have to be valid - unsafe fn prog_read(&mut self, addr: u64) -> Option; - - /// Read from program memory to exectue - /// - /// # Safety - /// - You have to be really sure that these bytes are there, understand? - unsafe fn prog_read_unchecked(&mut self, addr: u64) -> T; -} - -/// Unhandled load access trap -#[derive(Clone, Copy, Display, Debug, PartialEq, Eq)] -#[display(fmt = "Load access error at address {_0:#x}")] -pub struct LoadError(pub u64); - -/// Unhandled store access trap -#[derive(Clone, Copy, Display, Debug, PartialEq, Eq)] -#[display(fmt = "Store access error at address {_0:#x}")] -pub struct StoreError(pub u64); - -/// Reason to access memory -#[derive(Clone, Copy, Display, Debug, PartialEq, Eq)] -pub enum MemoryAccessReason { - /// Memory was accessed for load (read) - Load, - /// Memory was accessed for store (write) - Store, -} - -impl From for VmRunError { - fn from(value: LoadError) -> Self { - Self::LoadAccessEx(value.0) - } -} - -impl From for VmRunError { - fn from(value: StoreError) -> Self { - Self::StoreAccessEx(value.0) - } -} diff --git a/hbvm/src/main.rs b/hbvm/src/main.rs index 04d654dd..b28173fa 100644 --- a/hbvm/src/main.rs +++ b/hbvm/src/main.rs @@ -1,8 +1,11 @@ use { hbbytecode::valider::validate, hbvm::{ - mem::softpaging::{paging::PageTable, HandlePageFault, PageSize, SoftPagedMem}, - MemoryAccessReason, Vm, + mem::{ + softpaging::{paging::PageTable, HandlePageFault, PageSize, SoftPagedMem}, + MemoryAccessReason, + }, + Vm, }, std::io::{stdin, Read}, }; diff --git a/hbvm/src/mem/mod.rs b/hbvm/src/mem/mod.rs index f6cea2a2..318f8e3d 100644 --- a/hbvm/src/mem/mod.rs +++ b/hbvm/src/mem/mod.rs @@ -1,3 +1,68 @@ //! Memory implementations +use {derive_more::Display, hbbytecode::ProgramVal}; + pub mod softpaging; + +/// Load-store memory access +pub trait Memory { + /// Load data from memory on address + /// + /// # Safety + /// - Shall not overrun the buffer + unsafe fn load(&mut self, addr: u64, target: *mut u8, count: usize) -> Result<(), LoadError>; + + /// Store data to memory on address + /// + /// # Safety + /// - Shall not overrun the buffer + unsafe fn store( + &mut self, + addr: u64, + source: *const u8, + count: usize, + ) -> Result<(), StoreError>; + + /// Read from program memory to execute + /// + /// # Safety + /// - Data read have to be valid + unsafe fn prog_read(&mut self, addr: u64) -> Option; + + /// Read from program memory to exectue + /// + /// # Safety + /// - You have to be really sure that these bytes are there, understand? + unsafe fn prog_read_unchecked(&mut self, addr: u64) -> T; +} + +/// Unhandled load access trap +#[derive(Clone, Copy, Display, Debug, PartialEq, Eq)] +#[display(fmt = "Load access error at address {_0:#x}")] +pub struct LoadError(pub u64); + +/// Unhandled store access trap +#[derive(Clone, Copy, Display, Debug, PartialEq, Eq)] +#[display(fmt = "Store access error at address {_0:#x}")] +pub struct StoreError(pub u64); + +/// Reason to access memory +#[derive(Clone, Copy, Display, Debug, PartialEq, Eq)] +pub enum MemoryAccessReason { + /// Memory was accessed for load (read) + Load, + /// Memory was accessed for store (write) + Store, +} + +impl From for crate::VmRunError { + fn from(value: LoadError) -> Self { + Self::LoadAccessEx(value.0) + } +} + +impl From for crate::VmRunError { + fn from(value: StoreError) -> Self { + Self::StoreAccessEx(value.0) + } +} diff --git a/hbvm/src/mem/softpaging/mod.rs b/hbvm/src/mem/softpaging/mod.rs index 11fa4658..bec6d761 100644 --- a/hbvm/src/mem/softpaging/mod.rs +++ b/hbvm/src/mem/softpaging/mod.rs @@ -12,13 +12,13 @@ pub mod paging; pub mod mapping; use { - crate::{LoadError, Memory, MemoryAccessReason, StoreError}, + super::{LoadError, Memory, MemoryAccessReason, StoreError}, lookup::{AddrPageLookupError, AddrPageLookupOk, AddrPageLookuper}, paging::{PageTable, Permission}, }; /// HoleyBytes software paged memory -/// +/// /// - `OUT_PROG_EXEC`: set to `false` to disable executing program /// not contained in initially provided program, even the pages /// are executable diff --git a/hbvm/src/vmrun.rs b/hbvm/src/vmrun.rs new file mode 100644 index 00000000..c0e78fde --- /dev/null +++ b/hbvm/src/vmrun.rs @@ -0,0 +1,404 @@ +//! Welcome to the land of The Great Dispatch Loop +//! +//! Have fun + +use { + super::{ + bmc::BlockCopier, + mem::Memory, + value::{Value, ValueVariant}, + Vm, VmRunError, VmRunOk, + }, + core::{cmp::Ordering, mem::size_of, ops}, + hbbytecode::{ + ParamBB, ParamBBB, ParamBBBB, ParamBBD, ParamBBDH, ParamBBW, ParamBD, ProgramVal, + }, +}; + +impl Vm +where + Mem: Memory, +{ + /// Execute program + /// + /// Program can return [`VmRunError`] if a trap handling failed + #[cfg_attr(feature = "nightly", repr(align(4096)))] + pub fn run(&mut self) -> Result { + use hbbytecode::opcode::*; + loop { + // Big match + // + // Contribution guide: + // - Zero register shall never be overwitten. It's value has to always be 0. + // - Prefer `Self::read_reg` and `Self::write_reg` functions + // - Extract parameters using `param!` macro + // - Prioritise speed over code size + // - Memory is cheap, CPUs not that much + // - Do not heap allocate at any cost + // - Yes, user-provided trap handler may allocate, + // but that is not our »fault«. + // - Unsafe is kinda must, but be sure you have validated everything + // - Your contributions have to pass sanitizers and Miri + // - Strictly follow the spec + // - The spec does not specify how you perform actions, in what order, + // just that the observable effects have to be performed in order and + // correctly. + // - Yes, we assume you run 64 bit CPU. Else ?conradluget a better CPU + // sorry 8 bit fans, HBVM won't run on your Speccy :( + unsafe { + match self + .memory + .prog_read::(self.pc as _) + .ok_or(VmRunError::ProgramFetchLoadEx(self.pc as _))? + { + UN => { + self.decode::<()>(); + return Err(VmRunError::Unreachable); + } + TX => { + self.decode::<()>(); + return Ok(VmRunOk::End); + } + NOP => self.decode::<()>(), + ADD => self.binary_op(u64::wrapping_add), + SUB => self.binary_op(u64::wrapping_sub), + MUL => self.binary_op(u64::wrapping_mul), + AND => self.binary_op::(ops::BitAnd::bitand), + OR => self.binary_op::(ops::BitOr::bitor), + XOR => self.binary_op::(ops::BitXor::bitxor), + SL => self.binary_op(|l, r| u64::wrapping_shl(l, r as u32)), + SR => self.binary_op(|l, r| u64::wrapping_shr(l, r as u32)), + SRS => self.binary_op(|l, r| i64::wrapping_shl(l, r as u32)), + CMP => { + // Compare a0 <=> a1 + // < → -1 + // > → 1 + // = → 0 + + let ParamBBB(tg, a0, a1) = self.decode(); + self.write_reg( + tg, + self.read_reg(a0) + .cast::() + .cmp(&self.read_reg(a1).cast::()) + as i64, + ); + } + CMPU => { + // Unsigned comparsion + let ParamBBB(tg, a0, a1) = self.decode(); + self.write_reg( + tg, + self.read_reg(a0) + .cast::() + .cmp(&self.read_reg(a1).cast::()) + as i64, + ); + } + NOT => { + // Logical negation + let ParamBB(tg, a0) = self.decode(); + self.write_reg(tg, !self.read_reg(a0).cast::()); + } + NEG => { + // Bitwise negation + let ParamBB(tg, a0) = self.decode(); + self.write_reg( + tg, + match self.read_reg(a0).cast::() { + 0 => 1_u64, + _ => 0, + }, + ); + } + DIR => { + // Fused Division-Remainder + let ParamBBBB(dt, rt, a0, a1) = self.decode(); + let a0 = self.read_reg(a0).cast::(); + let a1 = self.read_reg(a1).cast::(); + self.write_reg(dt, a0.checked_div(a1).unwrap_or(u64::MAX)); + self.write_reg(rt, a0.checked_rem(a1).unwrap_or(u64::MAX)); + } + ADDI => self.binary_op_imm(u64::wrapping_add), + MULI => self.binary_op_imm(u64::wrapping_sub), + ANDI => self.binary_op_imm::(ops::BitAnd::bitand), + ORI => self.binary_op_imm::(ops::BitOr::bitor), + XORI => self.binary_op_imm::(ops::BitXor::bitxor), + SLI => self.binary_op_ims(u64::wrapping_shl), + SRI => self.binary_op_ims(u64::wrapping_shr), + SRSI => self.binary_op_ims(i64::wrapping_shr), + CMPI => { + let ParamBBD(tg, a0, imm) = self.decode(); + self.write_reg( + tg, + self.read_reg(a0) + .cast::() + .cmp(&Value::from(imm).cast::()) + as i64, + ); + } + CMPUI => { + let ParamBBD(tg, a0, imm) = self.decode(); + self.write_reg(tg, self.read_reg(a0).cast::().cmp(&imm) as i64); + } + CP => { + let ParamBB(tg, a0) = self.decode(); + self.write_reg(tg, self.read_reg(a0)); + } + SWA => { + // Swap registers + let ParamBB(r0, r1) = self.decode(); + match (r0, r1) { + (0, 0) => (), + (dst, 0) | (0, dst) => self.write_reg(dst, 0_u64), + (r0, r1) => { + core::ptr::swap( + self.registers.get_unchecked_mut(usize::from(r0)), + self.registers.get_unchecked_mut(usize::from(r1)), + ); + } + } + } + LI => { + let ParamBD(tg, imm) = self.decode(); + self.write_reg(tg, imm); + } + LD => { + // Load. If loading more than register size, continue on adjecent registers + let ParamBBDH(dst, base, off, count) = self.decode(); + let n: u8 = match dst { + 0 => 1, + _ => 0, + }; + + self.memory.load( + self.ldst_addr_uber(dst, base, off, count, n)?, + self.registers + .as_mut_ptr() + .add(usize::from(dst) + usize::from(n)) + .cast(), + usize::from(count).saturating_sub(n.into()), + )?; + } + ST => { + // Store. Same rules apply as to LD + let ParamBBDH(dst, base, off, count) = self.decode(); + self.memory.store( + self.ldst_addr_uber(dst, base, off, count, 0)?, + self.registers.as_ptr().add(usize::from(dst)).cast(), + count.into(), + )?; + } + BMC => { + // Block memory copy + match if let Some(copier) = &mut self.copier { + // There is some copier, poll. + copier.poll(&mut self.memory) + } else { + // There is none, make one! + let ParamBBD(src, dst, count) = self.decode(); + + // So we are still on BMC on next cycle + self.pc -= size_of::() + 1; + + self.copier = Some(BlockCopier::new( + self.read_reg(src).cast(), + self.read_reg(dst).cast(), + count as _, + )); + + self.copier + .as_mut() + .unwrap_unchecked() // SAFETY: We just assigned there + .poll(&mut self.memory) + } { + // We are done, shift program counter + core::task::Poll::Ready(Ok(())) => { + self.copier = None; + self.pc += size_of::() + 1; + } + // Error, shift program counter (for consistency) + // and yield error + core::task::Poll::Ready(Err(e)) => { + self.pc += size_of::() + 1; + return Err(e.into()); + } + // Not done yet, proceed to next cycle + core::task::Poll::Pending => (), + } + } + BRC => { + // Block register copy + let ParamBBB(src, dst, count) = self.decode(); + if src.checked_add(count).is_none() || dst.checked_add(count).is_none() { + return Err(VmRunError::RegOutOfBounds); + } + + core::ptr::copy( + self.registers.get_unchecked(usize::from(src)), + self.registers.get_unchecked_mut(usize::from(dst)), + usize::from(count), + ); + } + JAL => { + // Jump and link. Save PC after this instruction to + // specified register and jump to reg + offset. + let ParamBBD(save, reg, offset) = self.decode(); + self.write_reg(save, self.pc as u64); + self.pc = + (self.read_reg(reg).cast::().saturating_add(offset)) as usize; + } + // Conditional jumps, jump only to immediates + JEQ => self.cond_jmp::(Ordering::Equal), + JNE => { + let ParamBBD(a0, a1, jt) = self.decode(); + if self.read_reg(a0).cast::() != self.read_reg(a1).cast::() { + self.pc = jt as usize; + } + } + JLT => self.cond_jmp::(Ordering::Less), + JGT => self.cond_jmp::(Ordering::Greater), + JLTU => self.cond_jmp::(Ordering::Less), + JGTU => self.cond_jmp::(Ordering::Greater), + ECALL => { + self.decode::<()>(); + + // So we don't get timer interrupt after ECALL + if TIMER_QUOTIENT != 0 { + self.timer = self.timer.wrapping_add(1); + } + return Ok(VmRunOk::Ecall); + } + ADDF => self.binary_op::(ops::Add::add), + SUBF => self.binary_op::(ops::Sub::sub), + MULF => self.binary_op::(ops::Mul::mul), + DIRF => { + let ParamBBBB(dt, rt, a0, a1) = self.decode(); + let a0 = self.read_reg(a0).cast::(); + let a1 = self.read_reg(a1).cast::(); + self.write_reg(dt, a0 / a1); + self.write_reg(rt, a0 % a1); + } + FMAF => { + let ParamBBBB(dt, a0, a1, a2) = self.decode(); + self.write_reg( + dt, + self.read_reg(a0).cast::() * self.read_reg(a1).cast::() + + self.read_reg(a2).cast::(), + ); + } + NEGF => { + let ParamBB(dt, a0) = self.decode(); + self.write_reg(dt, -self.read_reg(a0).cast::()); + } + ITF => { + let ParamBB(dt, a0) = self.decode(); + self.write_reg(dt, self.read_reg(a0).cast::() as f64); + } + FTI => { + let ParamBB(dt, a0) = self.decode(); + self.write_reg(dt, self.read_reg(a0).cast::() as i64); + } + ADDFI => self.binary_op_imm::(ops::Add::add), + MULFI => self.binary_op_imm::(ops::Mul::mul), + op => return Err(VmRunError::InvalidOpcode(op)), + } + } + + if TIMER_QUOTIENT != 0 { + self.timer = self.timer.wrapping_add(1); + if self.timer % TIMER_QUOTIENT == 0 { + return Ok(VmRunOk::Timer); + } + } + } + } + + /// Decode instruction operands + #[inline(always)] + unsafe fn decode(&mut self) -> T { + let pc1 = self.pc + 1; + let data = self.memory.prog_read_unchecked::(pc1 as _); + self.pc += 1 + size_of::(); + data + } + + /// Perform binary operating over two registers + #[inline(always)] + unsafe fn binary_op(&mut self, op: impl Fn(T, T) -> T) { + let ParamBBB(tg, a0, a1) = self.decode(); + self.write_reg( + tg, + op(self.read_reg(a0).cast::(), self.read_reg(a1).cast::()), + ); + } + + /// Perform binary operation over register and immediate + #[inline(always)] + unsafe fn binary_op_imm(&mut self, op: impl Fn(T, T) -> T) { + let ParamBBD(tg, reg, imm) = self.decode(); + self.write_reg( + tg, + op(self.read_reg(reg).cast::(), Value::from(imm).cast::()), + ); + } + + /// Perform binary operation over register and shift immediate + #[inline(always)] + unsafe fn binary_op_ims(&mut self, op: impl Fn(T, u32) -> T) { + let ParamBBW(tg, reg, imm) = self.decode(); + self.write_reg(tg, op(self.read_reg(reg).cast::(), imm)); + } + + /// Jump at `#3` if ordering on `#0 <=> #1` is equal to expected + #[inline(always)] + unsafe fn cond_jmp(&mut self, expected: Ordering) { + let ParamBBD(a0, a1, ja) = self.decode(); + if self + .read_reg(a0) + .cast::() + .cmp(&self.read_reg(a1).cast::()) + == expected + { + self.pc = ja as usize; + } + } + + /// Read register + #[inline(always)] + unsafe fn read_reg(&self, n: u8) -> Value { + *self.registers.get_unchecked(n as usize) + } + + /// Write a register. + /// Writing to register 0 is no-op. + #[inline(always)] + unsafe fn write_reg(&mut self, n: u8, value: impl Into) { + if n != 0 { + *self.registers.get_unchecked_mut(n as usize) = value.into(); + } + } + + /// Load / Store Address check-computation überfunction + #[inline(always)] + unsafe fn ldst_addr_uber( + &self, + dst: u8, + base: u8, + offset: u64, + size: u16, + adder: u8, + ) -> Result { + let reg = dst.checked_add(adder).ok_or(VmRunError::RegOutOfBounds)?; + + if usize::from(reg) * 8 + usize::from(size) > 2048 { + Err(VmRunError::RegOutOfBounds) + } else { + self.read_reg(base) + .cast::() + .checked_add(offset) + .and_then(|x| x.checked_add(adder.into())) + .ok_or(VmRunError::AddrOutOfBounds) + } + } +} From 3034469e89089b8ce7dc62cc2a5e3af6be863a14 Mon Sep 17 00:00:00 2001 From: Erin Date: Fri, 18 Aug 2023 02:31:49 +0200 Subject: [PATCH 076/116] Address type, changed behaviour on address overflow --- hbvm/fuzz/fuzz_targets/vm.rs | 21 +++--- hbvm/src/bmc.rs | 58 +++++----------- hbvm/src/lib.rs | 16 ++--- hbvm/src/main.rs | 10 +-- hbvm/src/mem/addr.rs | 108 +++++++++++++++++++++++++++++ hbvm/src/mem/mod.rs | 28 +++++--- hbvm/src/mem/softpaging/icache.rs | 24 ++++--- hbvm/src/mem/softpaging/lookup.rs | 12 ++-- hbvm/src/mem/softpaging/mapping.rs | 8 ++- hbvm/src/mem/softpaging/mod.rs | 68 ++++++++++++------ hbvm/src/vmrun.rs | 19 ++--- 11 files changed, 254 insertions(+), 118 deletions(-) create mode 100644 hbvm/src/mem/addr.rs diff --git a/hbvm/fuzz/fuzz_targets/vm.rs b/hbvm/fuzz/fuzz_targets/vm.rs index f7336000..5cd2d2a3 100644 --- a/hbvm/fuzz/fuzz_targets/vm.rs +++ b/hbvm/fuzz/fuzz_targets/vm.rs @@ -3,11 +3,14 @@ use { hbbytecode::valider::validate, hbvm::{ - mem::softpaging::{ - paging::{PageTable, Permission}, - HandlePageFault, PageSize, SoftPagedMem, + mem::{ + softpaging::{ + paging::{PageTable, Permission}, + HandlePageFault, PageSize, SoftPagedMem, + }, + Address, MemoryAccessReason, }, - MemoryAccessReason, Vm, + Vm, }, libfuzzer_sys::fuzz_target, }; @@ -22,7 +25,7 @@ fuzz_target!(|data: &[u8]| { root_pt: Box::into_raw(Default::default()), icache: Default::default(), }, - 0, + Address::new(4), ) }; @@ -32,8 +35,6 @@ fuzz_target!(|data: &[u8]| { alloc_and_map(&mut vm.memory, 4096), ]; - unsafe { vm.memory.write() }; - // Run VM let _ = vm.run(); @@ -50,14 +51,14 @@ fn alloc_and_map(memory: &mut SoftPagedMem, at: u64) -> *mut u8 let ptr = Box::into_raw(Box::::default()).cast(); unsafe { memory - .map(ptr, at, Permission::Write, PageSize::Size4K) + .map(ptr, Address::new(at), Permission::Write, PageSize::Size4K) .unwrap() }; ptr } fn unmap_and_dealloc(memory: &mut SoftPagedMem, ptr: *mut u8, from: u64) { - memory.unmap(from).unwrap(); + memory.unmap(Address::new(from)).unwrap(); let _ = unsafe { Box::from_raw(ptr.cast::()) }; } @@ -75,7 +76,7 @@ impl HandlePageFault for TestTrapHandler { &mut self, _: MemoryAccessReason, _: &mut PageTable, - _: u64, + _: Address, _: PageSize, _: *mut u8, ) -> bool { diff --git a/hbvm/src/bmc.rs b/hbvm/src/bmc.rs index ef0d9cdd..21d30b63 100644 --- a/hbvm/src/bmc.rs +++ b/hbvm/src/bmc.rs @@ -1,7 +1,8 @@ //! Block memory copier state machine use { - super::{Memory, mem::MemoryAccessReason, VmRunError}, + super::{mem::MemoryAccessReason, Memory, VmRunError}, + crate::mem::Address, core::{mem::MaybeUninit, task::Poll}, }; @@ -15,9 +16,9 @@ struct AlignedBuf([MaybeUninit; BUF_SIZE]); /// State for block memory copy pub struct BlockCopier { /// Source address - src: u64, + src: Address, /// Destination address - dst: u64, + dst: Address, /// How many buffer sizes to copy? n_buffers: usize, /// …and what remainds after? @@ -27,7 +28,7 @@ pub struct BlockCopier { impl BlockCopier { /// Construct a new one #[inline] - pub fn new(src: u64, dst: u64, count: usize) -> Self { + pub fn new(src: Address, dst: Address, count: usize) -> Self { Self { src, dst, @@ -57,17 +58,8 @@ impl BlockCopier { } // Bump source and destination address - // - // If we are over the address space, bail. - match self.src.checked_add(BUF_SIZE as u64) { - Some(n) => self.src = n, - None => return Poll::Ready(Err(BlkCopyError::OutOfBounds)), - }; - - match self.dst.checked_add(BUF_SIZE as u64) { - Some(n) => self.dst = n, - None => return Poll::Ready(Err(BlkCopyError::OutOfBounds)), - }; + self.src += BUF_SIZE; + self.dst += BUF_SIZE; self.n_buffers -= 1; @@ -100,15 +92,15 @@ impl BlockCopier { #[inline] unsafe fn act( memory: &mut impl Memory, - src: u64, - dst: u64, + src: Address, + dst: Address, buf: *mut u8, count: usize, ) -> Result<(), BlkCopyError> { // Load to buffer memory .load(src, buf, count) - .map_err(|super::mem::LoadError(addr)| BlkCopyError::Access { + .map_err(|super::mem::LoadError(addr)| BlkCopyError { access_reason: MemoryAccessReason::Load, addr, })?; @@ -116,7 +108,7 @@ unsafe fn act( // Store from buffer memory .store(dst, buf, count) - .map_err(|super::mem::StoreError(addr)| BlkCopyError::Access { + .map_err(|super::mem::StoreError(addr)| BlkCopyError { access_reason: MemoryAccessReason::Store, addr, })?; @@ -126,30 +118,18 @@ unsafe fn act( /// Error occured when copying a block of memory #[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub enum BlkCopyError { - /// Memory access error - Access { - /// Kind of access - access_reason: MemoryAccessReason, - /// VM Address - addr: u64, - }, - /// Address out of bounds - OutOfBounds, +pub struct BlkCopyError { + /// Kind of access + access_reason: MemoryAccessReason, + /// VM Address + addr: Address, } impl From for VmRunError { fn from(value: BlkCopyError) -> Self { - match value { - BlkCopyError::Access { - access_reason: MemoryAccessReason::Load, - addr, - } => Self::LoadAccessEx(addr), - BlkCopyError::Access { - access_reason: MemoryAccessReason::Store, - addr, - } => Self::StoreAccessEx(addr), - BlkCopyError::OutOfBounds => Self::AddrOutOfBounds, + match value.access_reason { + MemoryAccessReason::Load => Self::LoadAccessEx(value.addr), + MemoryAccessReason::Store => Self::StoreAccessEx(value.addr), } } } diff --git a/hbvm/src/lib.rs b/hbvm/src/lib.rs index b0ee1437..9651262a 100644 --- a/hbvm/src/lib.rs +++ b/hbvm/src/lib.rs @@ -12,9 +12,9 @@ #![no_std] #![cfg_attr(feature = "nightly", feature(fn_align))] -#![warn(missing_docs, clippy::missing_docs_in_private_items)] +#![warn(missing_docs)] -use mem::Memory; +use mem::{Memory, Address}; #[cfg(feature = "alloc")] extern crate alloc; @@ -39,7 +39,7 @@ pub struct Vm { pub memory: Mem, /// Program counter - pub pc: usize, + pub pc: Address, /// Program timer timer: usize, @@ -56,11 +56,11 @@ where /// /// # Safety /// Program code has to be validated - pub unsafe fn new(memory: Mem, entry: u64) -> Self { + pub unsafe fn new(memory: Mem, entry: Address) -> Self { Self { registers: [Value::from(0_u64); 256], memory, - pc: entry as _, + pc: entry, timer: 0, copier: None, } @@ -75,13 +75,13 @@ pub enum VmRunError { InvalidOpcode(u8), /// Unhandled load access exception - LoadAccessEx(u64), + LoadAccessEx(Address), /// Unhandled instruction load access exception - ProgramFetchLoadEx(u64), + ProgramFetchLoadEx(Address), /// Unhandled store access exception - StoreAccessEx(u64), + StoreAccessEx(Address), /// Register out-of-bounds access RegOutOfBounds, diff --git a/hbvm/src/main.rs b/hbvm/src/main.rs index b28173fa..d23e994e 100644 --- a/hbvm/src/main.rs +++ b/hbvm/src/main.rs @@ -1,3 +1,5 @@ +use hbvm::mem::Address; + use { hbbytecode::valider::validate, hbvm::{ @@ -26,7 +28,7 @@ fn main() -> Result<(), Box> { root_pt: Box::into_raw(Default::default()), icache: Default::default(), }, - 4, + Address::new(4), ); let data = { let ptr = std::alloc::alloc_zeroed(std::alloc::Layout::from_size_align_unchecked( @@ -41,7 +43,7 @@ fn main() -> Result<(), Box> { vm.memory .map( data, - 8192, + Address::new(8192), hbvm::mem::softpaging::paging::Permission::Write, PageSize::Size4K, ) @@ -54,7 +56,7 @@ fn main() -> Result<(), Box> { data, std::alloc::Layout::from_size_align_unchecked(4096, 4096), ); - vm.memory.unmap(8192).unwrap(); + vm.memory.unmap(Address::new(8192)).unwrap(); let _ = Box::from_raw(vm.memory.root_pt); } } @@ -72,7 +74,7 @@ impl HandlePageFault for TestTrapHandler { &mut self, _: MemoryAccessReason, _: &mut PageTable, - _: u64, + _: Address, _: PageSize, _: *mut u8, ) -> bool { diff --git a/hbvm/src/mem/addr.rs b/hbvm/src/mem/addr.rs new file mode 100644 index 00000000..cd0bbd39 --- /dev/null +++ b/hbvm/src/mem/addr.rs @@ -0,0 +1,108 @@ +//! Virtual(?) memory address + +use { + core::{fmt::Debug, ops}, + derive_more::Display, +}; + +/// Memory address +#[derive(Clone, Copy, Display, PartialEq, Eq, PartialOrd, Ord)] +#[display(fmt = "{_0:x}")] +pub struct Address(u64); +impl Address { + /// A null address + pub const NULL: Self = Self(0); + + /// Saturating integer addition. Computes self + rhs, saturating at the numeric bounds instead of overflowing. + #[inline] + pub fn saturating_add(self, rhs: T) -> Self { + Self(self.0.saturating_add(rhs.cast_u64())) + } + + /// Saturating integer subtraction. Computes self - rhs, saturating at the numeric bounds instead of overflowing. + #[inline] + pub fn saturating_sub(self, rhs: T) -> Self { + Self(self.0.saturating_sub(rhs.cast_u64())) + } + + /// Cast or if smaller, truncate to [`usize`] + pub fn truncate_usize(self) -> usize { + self.0 as _ + } + + /// Get inner value + #[inline(always)] + pub fn get(self) -> u64 { + self.0 + } + + /// Construct new address + #[inline(always)] + pub fn new(val: u64) -> Self { + Self(val) + } + + /// Do something with inner value + #[inline(always)] + pub fn map(self, f: impl Fn(u64) -> u64) -> Self { + Self(f(self.0)) + } +} + +impl ops::Add for Address { + type Output = Self; + + #[inline] + fn add(self, rhs: T) -> Self::Output { + Self(self.0.wrapping_add(rhs.cast_u64())) + } +} + +impl ops::Sub for Address { + type Output = Self; + + #[inline] + fn sub(self, rhs: T) -> Self::Output { + Self(self.0.wrapping_sub(rhs.cast_u64())) + } +} + +impl ops::AddAssign for Address { + fn add_assign(&mut self, rhs: T) { + self.0 = self.0.wrapping_add(rhs.cast_u64()) + } +} + +impl ops::SubAssign for Address { + fn sub_assign(&mut self, rhs: T) { + self.0 = self.0.wrapping_sub(rhs.cast_u64()) + } +} + +impl From
for u64 { + #[inline(always)] + fn from(value: Address) -> Self { + value.0 + } +} + +impl Debug for Address { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "[{:0x}]", self.0) + } +} + +/// Can perform address operations with +pub trait AddressOp { + /// Cast to u64, truncating or extending + fn cast_u64(self) -> u64; +} + +macro_rules! impl_address_ops(($($ty:ty),* $(,)?) => { + $(impl AddressOp for $ty { + #[inline(always)] + fn cast_u64(self) -> u64 { self as _ } + })* +}); + +impl_address_ops!(u8, u16, u32, u64, usize); diff --git a/hbvm/src/mem/mod.rs b/hbvm/src/mem/mod.rs index 318f8e3d..0d9522be 100644 --- a/hbvm/src/mem/mod.rs +++ b/hbvm/src/mem/mod.rs @@ -1,16 +1,24 @@ //! Memory implementations -use {derive_more::Display, hbbytecode::ProgramVal}; - pub mod softpaging; +mod addr; + +pub use addr::Address; +use {derive_more::Display, hbbytecode::ProgramVal}; + /// Load-store memory access pub trait Memory { /// Load data from memory on address /// /// # Safety /// - Shall not overrun the buffer - unsafe fn load(&mut self, addr: u64, target: *mut u8, count: usize) -> Result<(), LoadError>; + unsafe fn load( + &mut self, + addr: Address, + target: *mut u8, + count: usize, + ) -> Result<(), LoadError>; /// Store data to memory on address /// @@ -18,7 +26,7 @@ pub trait Memory { /// - Shall not overrun the buffer unsafe fn store( &mut self, - addr: u64, + addr: Address, source: *const u8, count: usize, ) -> Result<(), StoreError>; @@ -27,24 +35,24 @@ pub trait Memory { /// /// # Safety /// - Data read have to be valid - unsafe fn prog_read(&mut self, addr: u64) -> Option; + unsafe fn prog_read(&mut self, addr: Address) -> Option; /// Read from program memory to exectue /// /// # Safety /// - You have to be really sure that these bytes are there, understand? - unsafe fn prog_read_unchecked(&mut self, addr: u64) -> T; + unsafe fn prog_read_unchecked(&mut self, addr: Address) -> T; } /// Unhandled load access trap #[derive(Clone, Copy, Display, Debug, PartialEq, Eq)] -#[display(fmt = "Load access error at address {_0:#x}")] -pub struct LoadError(pub u64); +#[display(fmt = "Load access error at address {_0}")] +pub struct LoadError(pub Address); /// Unhandled store access trap #[derive(Clone, Copy, Display, Debug, PartialEq, Eq)] -#[display(fmt = "Store access error at address {_0:#x}")] -pub struct StoreError(pub u64); +#[display(fmt = "Store access error at address {_0}")] +pub struct StoreError(pub Address); /// Reason to access memory #[derive(Clone, Copy, Display, Debug, PartialEq, Eq)] diff --git a/hbvm/src/mem/softpaging/icache.rs b/hbvm/src/mem/softpaging/icache.rs index 049d26b0..963970ab 100644 --- a/hbvm/src/mem/softpaging/icache.rs +++ b/hbvm/src/mem/softpaging/icache.rs @@ -1,5 +1,7 @@ //! Program instruction cache +use crate::mem::Address; + use { super::{lookup::AddrPageLookuper, paging::PageTable, PageSize}, core::{ @@ -12,7 +14,7 @@ use { #[derive(Clone, Debug)] pub struct ICache { /// Current page address base - base: u64, + base: Address, /// Curent page pointer data: Option>, /// Current page size @@ -24,7 +26,7 @@ pub struct ICache { impl Default for ICache { fn default() -> Self { Self { - base: Default::default(), + base: Address::NULL, data: Default::default(), size: PageSize::Size4K, mask: Default::default(), @@ -37,22 +39,26 @@ impl ICache { /// /// # Safety /// `T` should be valid to read from instruction memory - pub(super) unsafe fn fetch(&mut self, addr: u64, root_pt: *const PageTable) -> Option { + pub(super) unsafe fn fetch( + &mut self, + addr: Address, + root_pt: *const PageTable, + ) -> Option { let mut ret = MaybeUninit::::uninit(); let pbase = self .data - .or_else(|| self.fetch_page(self.base.checked_add(self.size as _)?, root_pt))?; + .or_else(|| self.fetch_page(self.base + self.size, root_pt))?; // Get address base - let base = addr & self.mask; + let base = addr.map(|x| x & self.mask); // Base not matching, fetch anew if base != self.base { self.fetch_page(base, root_pt)?; }; - let offset = addr & !self.mask; + let offset = addr.get() & !self.mask; let requ_size = size_of::(); // Page overflow @@ -66,7 +72,7 @@ impl ICache { // Copy overflow if rem != 0 { - let pbase = self.fetch_page(self.base.checked_add(self.size as _)?, root_pt)?; + let pbase = self.fetch_page(self.base + self.size, root_pt)?; // Unlikely, unsupported scenario if rem > self.size as _ { @@ -84,7 +90,7 @@ impl ICache { } /// Fetch a page - unsafe fn fetch_page(&mut self, addr: u64, pt: *const PageTable) -> Option> { + unsafe fn fetch_page(&mut self, addr: Address, pt: *const PageTable) -> Option> { let res = AddrPageLookuper::new(addr, 0, pt).next()?.ok()?; if !super::perm_check::executable(res.perm) { return None; @@ -97,7 +103,7 @@ impl ICache { _ => return None, }; self.data = Some(NonNull::new(res.ptr)?); - self.base = addr & self.mask; + self.base = addr.map(|x| x & self.mask); self.data } } diff --git a/hbvm/src/mem/softpaging/lookup.rs b/hbvm/src/mem/softpaging/lookup.rs index 1e9c3e31..04b17d2d 100644 --- a/hbvm/src/mem/softpaging/lookup.rs +++ b/hbvm/src/mem/softpaging/lookup.rs @@ -1,5 +1,7 @@ //! Address lookup +use crate::mem::addr::Address; + use super::{ addr_extract_index, paging::{PageTable, Permission}, @@ -9,7 +11,7 @@ use super::{ /// Good result from address split pub struct AddrPageLookupOk { /// Virtual address - pub vaddr: u64, + pub vaddr: Address, /// Pointer to the start for perform operation pub ptr: *mut u8, @@ -24,7 +26,7 @@ pub struct AddrPageLookupOk { /// Errornous address split result pub struct AddrPageLookupError { /// Address of failure - pub addr: u64, + pub addr: Address, /// Requested page size pub size: PageSize, @@ -33,7 +35,7 @@ pub struct AddrPageLookupError { /// Address splitter into pages pub struct AddrPageLookuper { /// Current address - addr: u64, + addr: Address, /// Size left size: usize, @@ -45,7 +47,7 @@ pub struct AddrPageLookuper { impl AddrPageLookuper { /// Create a new page lookuper #[inline] - pub const fn new(addr: u64, size: usize, pagetable: *const PageTable) -> Self { + pub const fn new(addr: Address, size: usize, pagetable: *const PageTable) -> Self { Self { addr, size, @@ -55,7 +57,7 @@ impl AddrPageLookuper { /// Bump address by size X pub fn bump(&mut self, page_size: PageSize) { - self.addr += page_size as u64; + self.addr += page_size; self.size = self.size.saturating_sub(page_size as _); } } diff --git a/hbvm/src/mem/softpaging/mapping.rs b/hbvm/src/mem/softpaging/mapping.rs index 8f406a4a..e07c0dd3 100644 --- a/hbvm/src/mem/softpaging/mapping.rs +++ b/hbvm/src/mem/softpaging/mapping.rs @@ -1,5 +1,7 @@ //! Automatic memory mapping +use crate::mem::addr::Address; + use { super::{ addr_extract_index, @@ -10,7 +12,7 @@ use { derive_more::Display, }; -impl<'p, A, const OUT_PROG_EXEC: bool> SoftPagedMem<'p, A, OUT_PROG_EXEC> { +impl<'p, A, const OUT_PROG_EXEC: bool> SoftPagedMem<'p, A, OUT_PROG_EXEC> { /// Maps host's memory into VM's memory /// /// # Safety @@ -20,7 +22,7 @@ impl<'p, A, const OUT_PROG_EXEC: bool> SoftPagedMem<'p, A, OUT_PROG_EXEC> { pub unsafe fn map( &mut self, host: *mut u8, - target: u64, + target: Address, perm: Permission, pagesize: PageSize, ) -> Result<(), MapError> { @@ -82,7 +84,7 @@ impl<'p, A, const OUT_PROG_EXEC: bool> SoftPagedMem<'p, A, OUT_PROG_EXEC> { /// /// If errors, it only means there is no entry to unmap and in most cases /// just should be ignored. - pub fn unmap(&mut self, addr: u64) -> Result<(), NothingToUnmap> { + pub fn unmap(&mut self, addr: Address) -> Result<(), NothingToUnmap> { let mut current_pt = self.root_pt; let mut page_tables = [core::ptr::null_mut(); 5]; diff --git a/hbvm/src/mem/softpaging/mod.rs b/hbvm/src/mem/softpaging/mod.rs index bec6d761..331ad4d7 100644 --- a/hbvm/src/mem/softpaging/mod.rs +++ b/hbvm/src/mem/softpaging/mod.rs @@ -1,9 +1,5 @@ //! Platform independent, software paged memory implementation -use core::mem::size_of; - -use self::icache::ICache; - pub mod icache; pub mod lookup; pub mod paging; @@ -12,7 +8,9 @@ pub mod paging; pub mod mapping; use { - super::{LoadError, Memory, MemoryAccessReason, StoreError}, + super::{addr::Address, LoadError, Memory, MemoryAccessReason, StoreError}, + core::mem::size_of, + icache::ICache, lookup::{AddrPageLookupError, AddrPageLookupOk, AddrPageLookuper}, paging::{PageTable, Permission}, }; @@ -41,7 +39,12 @@ impl<'p, PfH: HandlePageFault, const OUT_PROG_EXEC: bool> Memory /// /// # Safety /// Applies same conditions as for [`core::ptr::copy_nonoverlapping`] - unsafe fn load(&mut self, addr: u64, target: *mut u8, count: usize) -> Result<(), LoadError> { + unsafe fn load( + &mut self, + addr: Address, + target: *mut u8, + count: usize, + ) -> Result<(), LoadError> { self.memory_access( MemoryAccessReason::Load, addr, @@ -59,7 +62,7 @@ impl<'p, PfH: HandlePageFault, const OUT_PROG_EXEC: bool> Memory /// Applies same conditions as for [`core::ptr::copy_nonoverlapping`] unsafe fn store( &mut self, - addr: u64, + addr: Address, source: *const u8, count: usize, ) -> Result<(), StoreError> { @@ -75,27 +78,31 @@ impl<'p, PfH: HandlePageFault, const OUT_PROG_EXEC: bool> Memory } #[inline(always)] - unsafe fn prog_read(&mut self, addr: u64) -> Option { - if OUT_PROG_EXEC && addr as usize > self.program.len() { + unsafe fn prog_read(&mut self, addr: Address) -> Option { + if OUT_PROG_EXEC && addr.truncate_usize() > self.program.len() { return self.icache.fetch::(addr, self.root_pt); } - let addr = addr as usize; + let addr = addr.truncate_usize(); self.program .get(addr..addr + size_of::()) .map(|x| x.as_ptr().cast::().read()) } #[inline(always)] - unsafe fn prog_read_unchecked(&mut self, addr: u64) -> T { - if OUT_PROG_EXEC && addr as usize > self.program.len() { + unsafe fn prog_read_unchecked(&mut self, addr: Address) -> T { + if OUT_PROG_EXEC && addr.truncate_usize() > self.program.len() { return self .icache - .fetch::(addr as _, self.root_pt) + .fetch::(addr, self.root_pt) .unwrap_or_else(|| core::mem::zeroed()); } - self.program.as_ptr().add(addr as _).cast::().read() + self.program + .as_ptr() + .add(addr.truncate_usize()) + .cast::() + .read() } } @@ -110,32 +117,32 @@ impl<'p, PfH: HandlePageFault, const OUT_PROG_EXEC: bool> SoftPagedMem<'p, PfH, fn memory_access( &mut self, reason: MemoryAccessReason, - src: u64, + src: Address, mut dst: *mut u8, len: usize, permission_check: fn(Permission) -> bool, action: fn(*mut u8, *mut u8, usize), - ) -> Result<(), u64> { + ) -> Result<(), Address> { // Memory load from program section - let (src, len) = if src < self.program.len() as _ { + let (src, len) = if src.truncate_usize() < self.program.len() as _ { // Allow only loads if reason != MemoryAccessReason::Load { return Err(src); } // Determine how much data to copy from here - let to_copy = len.clamp(0, self.program.len().saturating_sub(src as _)); + let to_copy = len.clamp(0, self.program.len().saturating_sub(src.truncate_usize())); // Perform action action( - unsafe { self.program.as_ptr().add(src as _).cast_mut() }, + unsafe { self.program.as_ptr().add(src.truncate_usize()).cast_mut() }, dst, to_copy, ); // Return shifted from what we've already copied ( - src.saturating_add(to_copy as _), + src.saturating_add(to_copy as u64), len.saturating_sub(to_copy), ) } else { @@ -196,8 +203,9 @@ impl<'p, PfH: HandlePageFault, const OUT_PROG_EXEC: bool> SoftPagedMem<'p, PfH, /// /// The level shall not be larger than 4, otherwise /// the output of the function is unspecified (yes, it can also panic :) -pub fn addr_extract_index(addr: u64, lvl: u8) -> usize { +pub fn addr_extract_index(addr: Address, lvl: u8) -> usize { debug_assert!(lvl <= 4); + let addr = addr.get(); usize::try_from((addr >> (lvl * 8 + 12)) & ((1 << 8) - 1)).expect("?conradluget a better CPU") } @@ -226,6 +234,22 @@ impl PageSize { } } +impl core::ops::Add for Address { + type Output = Self; + + #[inline(always)] + fn add(self, rhs: PageSize) -> Self::Output { + self + (rhs as u64) + } +} + +impl core::ops::AddAssign for Address { + #[inline(always)] + fn add_assign(&mut self, rhs: PageSize) { + *self = Self::new(self.get().wrapping_add(rhs as u64)); + } +} + /// Permisison checks pub mod perm_check { use super::paging::Permission; @@ -263,7 +287,7 @@ pub trait HandlePageFault { &mut self, reason: MemoryAccessReason, pagetable: &mut PageTable, - vaddr: u64, + vaddr: Address, size: PageSize, dataptr: *mut u8, ) -> bool diff --git a/hbvm/src/vmrun.rs b/hbvm/src/vmrun.rs index c0e78fde..ecbeb124 100644 --- a/hbvm/src/vmrun.rs +++ b/hbvm/src/vmrun.rs @@ -2,6 +2,8 @@ //! //! Have fun +use crate::mem::Address; + use { super::{ bmc::BlockCopier, @@ -202,8 +204,8 @@ where self.pc -= size_of::() + 1; self.copier = Some(BlockCopier::new( - self.read_reg(src).cast(), - self.read_reg(dst).cast(), + Address::new(self.read_reg(src).cast()), + Address::new(self.read_reg(dst).cast()), count as _, )); @@ -244,16 +246,16 @@ where // Jump and link. Save PC after this instruction to // specified register and jump to reg + offset. let ParamBBD(save, reg, offset) = self.decode(); - self.write_reg(save, self.pc as u64); + self.write_reg(save, self.pc.get()); self.pc = - (self.read_reg(reg).cast::().saturating_add(offset)) as usize; + Address::new(self.read_reg(reg).cast::().saturating_add(offset)); } // Conditional jumps, jump only to immediates JEQ => self.cond_jmp::(Ordering::Equal), JNE => { let ParamBBD(a0, a1, jt) = self.decode(); if self.read_reg(a0).cast::() != self.read_reg(a1).cast::() { - self.pc = jt as usize; + self.pc = Address::new(jt); } } JLT => self.cond_jmp::(Ordering::Less), @@ -317,7 +319,7 @@ where /// Decode instruction operands #[inline(always)] unsafe fn decode(&mut self) -> T { - let pc1 = self.pc + 1; + let pc1 = self.pc + 1_u64; let data = self.memory.prog_read_unchecked::(pc1 as _); self.pc += 1 + size_of::(); data @@ -360,7 +362,7 @@ where .cmp(&self.read_reg(a1).cast::()) == expected { - self.pc = ja as usize; + self.pc = Address::new(ja); } } @@ -388,7 +390,7 @@ where offset: u64, size: u16, adder: u8, - ) -> Result { + ) -> Result { let reg = dst.checked_add(adder).ok_or(VmRunError::RegOutOfBounds)?; if usize::from(reg) * 8 + usize::from(size) > 2048 { @@ -399,6 +401,7 @@ where .checked_add(offset) .and_then(|x| x.checked_add(adder.into())) .ok_or(VmRunError::AddrOutOfBounds) + .map(Address::new) } } } From 006dcca309c5e7c640b6be7d647effab8da2fdba Mon Sep 17 00:00:00 2001 From: Erin Date: Sat, 19 Aug 2023 23:24:31 +0200 Subject: [PATCH 077/116] cleaned up deps --- Cargo.lock | 62 ++++++++++++----------------------------------- hbvm/Cargo.toml | 3 --- hbvm/src/value.rs | 17 +++++++------ 3 files changed, 24 insertions(+), 58 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 56e88bb3..47ef3c9d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -15,9 +15,9 @@ dependencies = [ [[package]] name = "allocator-api2" -version = "0.2.15" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56fc6cf8dc8c4158eed8649f9b8b0ea1518eb62b544fe9490d66fa0b349eafe9" +checksum = "0942ffc6dcaadf03badf6e6a2d0228460359d5e34b57ccdc720b7382dfbd5ec5" [[package]] name = "ariadne" @@ -53,17 +53,6 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" -[[package]] -name = "delegate" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d358e0ec5c59a5e1603b933def447096886121660fc680dc1e64a0753981fe3c" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", -] - [[package]] name = "derive_more" version = "0.99.17" @@ -124,20 +113,11 @@ version = "0.1.0" name = "hbvm" version = "0.1.0" dependencies = [ - "delegate", "derive_more", "hbbytecode", - "paste", - "sealed", "static_assertions", ] -[[package]] -name = "heck" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" - [[package]] name = "lasso" version = "0.7.2" @@ -188,7 +168,7 @@ dependencies = [ "proc-macro2", "quote", "regex-syntax", - "syn 2.0.25", + "syn 2.0.29", ] [[package]] @@ -208,24 +188,24 @@ checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" [[package]] name = "paste" -version = "1.0.13" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4b27ab7be369122c218afc2079489cdcb4b517c0a3fc386ff11e1fedfcc2b35" +checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c" [[package]] name = "proc-macro2" -version = "1.0.64" +version = "1.0.66" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78803b62cbf1f46fde80d7c0e803111524b9877184cfe7c3033659490ac7a7da" +checksum = "18fb31db3f9bddb2ea821cde30a9f70117e3f119938b5ee630b7403aa6e2ead9" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.29" +version = "1.0.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "573015e8ab27661678357f27dc26460738fd2b6c86e46f386fde94cb5d913105" +checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" dependencies = [ "proc-macro2", ] @@ -245,23 +225,11 @@ dependencies = [ "semver", ] -[[package]] -name = "sealed" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4a8caec23b7800fb97971a1c6ae365b6239aaeddfb934d6265f8505e795699d" -dependencies = [ - "heck", - "proc-macro2", - "quote", - "syn 2.0.25", -] - [[package]] name = "semver" -version = "1.0.17" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bebd363326d05ec3e2f532ab7660680f3b02130d780c299bca73469d521bc0ed" +checksum = "b0293b4b29daaf487284529cc2f5675b8e57c61f70167ba415a463651fd6a918" [[package]] name = "static_assertions" @@ -282,9 +250,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.25" +version = "2.0.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15e3fc8c0c74267e2df136e5e5fb656a464158aa57624053375eb9c8c6e25ae2" +checksum = "c324c494eba9d92503e6f1ef2e6df781e78f6a7705a0202d9801b198807d518a" dependencies = [ "proc-macro2", "quote", @@ -293,9 +261,9 @@ dependencies = [ [[package]] name = "unicode-ident" -version = "1.0.10" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22049a19f4a68748a168c0fc439f9516686aa045927ff767eca0a85101fb6e73" +checksum = "301abaae475aa91687eb82514b328ab47a211a533026cb25fc3e519b86adfc3c" [[package]] name = "unicode-width" diff --git a/hbvm/Cargo.toml b/hbvm/Cargo.toml index ce3b5651..d9c87cbf 100644 --- a/hbvm/Cargo.toml +++ b/hbvm/Cargo.toml @@ -12,9 +12,6 @@ alloc = [] nightly = [] [dependencies] -delegate = "0.9" derive_more = "0.99" hbbytecode.path = "../hbbytecode" -paste = "1.0" -sealed = "0.5" static_assertions = "1.0" diff --git a/hbvm/src/value.rs b/hbvm/src/value.rs index 50d36d1e..7401057e 100644 --- a/hbvm/src/value.rs +++ b/hbvm/src/value.rs @@ -1,7 +1,5 @@ //! HoleyBytes register value definition -use sealed::sealed; - /// Define [`Value`] union /// /// # Safety @@ -33,7 +31,7 @@ macro_rules! value_def { core::mem::size_of::(), ); - #[sealed] + impl private::Sealed for $ty {} unsafe impl ValueVariant for $ty {} )* }; @@ -44,13 +42,13 @@ impl Value { #[inline] pub fn cast(self) -> V { /// Evil. - /// + /// /// Transmute cannot be performed with generic type /// as size is unknown, so union is used. - /// + /// /// # Safety /// If [`ValueVariant`] implemented correctly, it's fine :) - /// + /// /// :ferrisClueless: union Transmute { /// Self @@ -65,8 +63,11 @@ impl Value { /// # Safety /// - N/A, not to be implemented manually -#[sealed] -pub unsafe trait ValueVariant: Copy + Into {} +pub unsafe trait ValueVariant: private::Sealed + Copy + Into {} + +mod private { + pub trait Sealed {} +} value_def!(u64, i64, f64); static_assertions::const_assert_eq!(core::mem::size_of::(), 8); From 26105bab70f61a0622eedf60304eab6041a9e552 Mon Sep 17 00:00:00 2001 From: Erin Date: Sat, 19 Aug 2023 23:46:47 +0200 Subject: [PATCH 078/116] bye stuff --- Cargo.lock | 55 +----------------------------- hbvm/Cargo.toml | 4 +-- hbvm/src/lib.rs | 1 + hbvm/src/mem/addr.rs | 14 ++++---- hbvm/src/mem/mod.rs | 22 ++++++++---- hbvm/src/mem/softpaging/mapping.rs | 16 +++++---- hbvm/src/utils.rs | 38 +++++++++++++++++++++ hbvm/src/value.rs | 4 +-- 8 files changed, 76 insertions(+), 78 deletions(-) create mode 100644 hbvm/src/utils.rs diff --git a/Cargo.lock b/Cargo.lock index 47ef3c9d..fd50d298 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -47,25 +47,6 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" -[[package]] -name = "convert_case" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" - -[[package]] -name = "derive_more" -version = "0.99.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321" -dependencies = [ - "convert_case", - "proc-macro2", - "quote", - "rustc_version", - "syn 1.0.109", -] - [[package]] name = "fnv" version = "1.0.7" @@ -113,9 +94,7 @@ version = "0.1.0" name = "hbvm" version = "0.1.0" dependencies = [ - "derive_more", "hbbytecode", - "static_assertions", ] [[package]] @@ -168,7 +147,7 @@ dependencies = [ "proc-macro2", "quote", "regex-syntax", - "syn 2.0.29", + "syn", ] [[package]] @@ -216,38 +195,6 @@ version = "0.6.29" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" -[[package]] -name = "rustc_version" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" -dependencies = [ - "semver", -] - -[[package]] -name = "semver" -version = "1.0.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0293b4b29daaf487284529cc2f5675b8e57c61f70167ba415a463651fd6a918" - -[[package]] -name = "static_assertions" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" - -[[package]] -name = "syn" -version = "1.0.109" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - [[package]] name = "syn" version = "2.0.29" diff --git a/hbvm/Cargo.toml b/hbvm/Cargo.toml index d9c87cbf..e8302d24 100644 --- a/hbvm/Cargo.toml +++ b/hbvm/Cargo.toml @@ -12,6 +12,4 @@ alloc = [] nightly = [] [dependencies] -derive_more = "0.99" -hbbytecode.path = "../hbbytecode" -static_assertions = "1.0" +hbbytecode.path = "../hbbytecode" diff --git a/hbvm/src/lib.rs b/hbvm/src/lib.rs index 9651262a..5da24b1d 100644 --- a/hbvm/src/lib.rs +++ b/hbvm/src/lib.rs @@ -24,6 +24,7 @@ pub mod value; mod bmc; mod vmrun; +mod utils; use {bmc::BlockCopier, value::Value}; diff --git a/hbvm/src/mem/addr.rs b/hbvm/src/mem/addr.rs index cd0bbd39..ea95ac7a 100644 --- a/hbvm/src/mem/addr.rs +++ b/hbvm/src/mem/addr.rs @@ -1,13 +1,11 @@ //! Virtual(?) memory address -use { - core::{fmt::Debug, ops}, - derive_more::Display, -}; +use core::{fmt::Debug, ops}; + +use crate::utils::impl_display; /// Memory address -#[derive(Clone, Copy, Display, PartialEq, Eq, PartialOrd, Ord)] -#[display(fmt = "{_0:x}")] +#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] pub struct Address(u64); impl Address { /// A null address @@ -49,6 +47,10 @@ impl Address { } } +impl_display!(for Address => + |Address(a)| "{a:0x}" +); + impl ops::Add for Address { type Output = Self; diff --git a/hbvm/src/mem/mod.rs b/hbvm/src/mem/mod.rs index 0d9522be..e134706a 100644 --- a/hbvm/src/mem/mod.rs +++ b/hbvm/src/mem/mod.rs @@ -5,7 +5,8 @@ pub mod softpaging; mod addr; pub use addr::Address; -use {derive_more::Display, hbbytecode::ProgramVal}; + +use {crate::utils::impl_display, hbbytecode::ProgramVal}; /// Load-store memory access pub trait Memory { @@ -45,17 +46,21 @@ pub trait Memory { } /// Unhandled load access trap -#[derive(Clone, Copy, Display, Debug, PartialEq, Eq)] -#[display(fmt = "Load access error at address {_0}")] +#[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct LoadError(pub Address); +impl_display!(for LoadError => + |LoadError(a)| "Load access error at address {a}", +); /// Unhandled store access trap -#[derive(Clone, Copy, Display, Debug, PartialEq, Eq)] -#[display(fmt = "Store access error at address {_0}")] +#[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct StoreError(pub Address); +impl_display!(for StoreError => + |StoreError(a)| "Load access error at address {a}", +); /// Reason to access memory -#[derive(Clone, Copy, Display, Debug, PartialEq, Eq)] +#[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum MemoryAccessReason { /// Memory was accessed for load (read) Load, @@ -63,6 +68,11 @@ pub enum MemoryAccessReason { Store, } +impl_display!(for MemoryAccessReason => match { + Self::Load => "Load"; + Self::Store => "Store"; +}); + impl From for crate::VmRunError { fn from(value: LoadError) -> Self { Self::LoadAccessEx(value.0) diff --git a/hbvm/src/mem/softpaging/mapping.rs b/hbvm/src/mem/softpaging/mapping.rs index e07c0dd3..67f65a1e 100644 --- a/hbvm/src/mem/softpaging/mapping.rs +++ b/hbvm/src/mem/softpaging/mapping.rs @@ -1,6 +1,6 @@ //! Automatic memory mapping -use crate::mem::addr::Address; +use crate::{mem::addr::Address, utils::impl_display}; use { super::{ @@ -9,7 +9,6 @@ use { PageSize, SoftPagedMem, }, alloc::boxed::Box, - derive_more::Display, }; impl<'p, A, const OUT_PROG_EXEC: bool> SoftPagedMem<'p, A, OUT_PROG_EXEC> { @@ -143,22 +142,25 @@ impl<'p, A, const OUT_PROG_EXEC: bool> SoftPagedMem<'p, A, OUT_PROG_EXEC> { } /// Error mapping -#[derive(Clone, Copy, Display, Debug, PartialEq, Eq)] +#[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum MapError { /// Entry was already mapped - #[display(fmt = "There is already a page mapped on specified address")] AlreadyMapped, /// When walking a page entry was /// encounterd. - #[display(fmt = "There was a page mapped on the way instead of node")] PageOnNode, } +impl_display!(for MapError => match { + Self::AlreadyMapped => "There is already a page mapped on specified address"; + Self::PageOnNode => "There was a page mapped on the way instead of node"; +}); + /// There was no entry in page table to unmap /// /// No worry, don't panic, nothing bad has happened, /// but if you are 120% sure there should be something, /// double-check your addresses. -#[derive(Clone, Copy, Display, Debug)] -#[display(fmt = "There was no entry to unmap")] +#[derive(Clone, Copy, Debug)] pub struct NothingToUnmap; +impl_display!(for NothingToUnmap => "There is no entry to unmap"); diff --git a/hbvm/src/utils.rs b/hbvm/src/utils.rs new file mode 100644 index 00000000..3350c1da --- /dev/null +++ b/hbvm/src/utils.rs @@ -0,0 +1,38 @@ +macro_rules! impl_display { + (for $ty:ty => $(|$selfty:pat_param|)? $fmt:literal $(, $($param:expr),+)? $(,)?) => { + impl ::core::fmt::Display for $ty { + fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result { + $(let $selfty = self;)? + write!(f, $fmt, $($param),*) + } + } + }; + + (for $ty:ty => $str:literal) => { + impl ::core::fmt::Display for $ty { + fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result { + f.write_str($str) + } + } + }; + + (for $ty:ty => match {$( + $bind:pat => $fmt:literal $(,$($param:expr),* $(,)?)?; + )*}) => { + impl ::core::fmt::Display for $ty { + fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result { + match self { + $( + $bind => write!(f, $fmt, $($($param),*)?) + ),* + } + } + } + } +} + +macro_rules! static_assert_eq(($l:expr, $r:expr $(,)?) => { + const _: [(); ($l != $r) as usize] = []; +}); + +pub(crate) use {impl_display, static_assert_eq}; diff --git a/hbvm/src/value.rs b/hbvm/src/value.rs index 7401057e..b72b72b5 100644 --- a/hbvm/src/value.rs +++ b/hbvm/src/value.rs @@ -26,7 +26,7 @@ macro_rules! value_def { } } - static_assertions::const_assert_eq!( + crate::utils::static_assert_eq!( core::mem::size_of::<$ty>(), core::mem::size_of::(), ); @@ -70,7 +70,7 @@ mod private { } value_def!(u64, i64, f64); -static_assertions::const_assert_eq!(core::mem::size_of::(), 8); +crate::utils::static_assert_eq!(core::mem::size_of::(), 8); impl core::fmt::Debug for Value { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { From 0d2949024c9061ed935ae93fd64b70c5e0b61c3d Mon Sep 17 00:00:00 2001 From: Erin Date: Sat, 19 Aug 2023 23:57:48 +0200 Subject: [PATCH 079/116] updated macro --- hbvm/src/mem/mod.rs | 4 ++-- hbvm/src/utils.rs | 19 +++++++++++++++++-- 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/hbvm/src/mem/mod.rs b/hbvm/src/mem/mod.rs index e134706a..bba5d134 100644 --- a/hbvm/src/mem/mod.rs +++ b/hbvm/src/mem/mod.rs @@ -69,8 +69,8 @@ pub enum MemoryAccessReason { } impl_display!(for MemoryAccessReason => match { - Self::Load => "Load"; - Self::Store => "Store"; + Self::Load => const "Load"; + Self::Store => const "Store"; }); impl From for crate::VmRunError { diff --git a/hbvm/src/utils.rs b/hbvm/src/utils.rs index 3350c1da..74c4ef09 100644 --- a/hbvm/src/utils.rs +++ b/hbvm/src/utils.rs @@ -17,13 +17,13 @@ macro_rules! impl_display { }; (for $ty:ty => match {$( - $bind:pat => $fmt:literal $(,$($param:expr),* $(,)?)?; + $bind:pat => $($const:ident)? $fmt:literal $(,$($params:tt)*)?; )*}) => { impl ::core::fmt::Display for $ty { fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result { match self { $( - $bind => write!(f, $fmt, $($($param),*)?) + $bind => $crate::utils::private::impl_display_match_fragment!($($const,)? f, $fmt $(, $($params)*)?) ),* } } @@ -31,6 +31,21 @@ macro_rules! impl_display { } } +#[doc(hidden)] +pub(crate) mod private { + macro_rules! impl_display_match_fragment { + (const, $f:expr, $lit:literal) => { + $f.write_str($lit) + }; + + ($f:expr, $fmt:literal $(, $($params:tt)*)?) => { + write!($f, $fmt, $($($params)*)?) + }; + } + + pub(crate) use impl_display_match_fragment; +} + macro_rules! static_assert_eq(($l:expr, $r:expr $(,)?) => { const _: [(); ($l != $r) as usize] = []; }); From 9b8a4a718e3468823016945a3f1aaccd9f5d7207 Mon Sep 17 00:00:00 2001 From: Erin Date: Sun, 20 Aug 2023 00:24:27 +0200 Subject: [PATCH 080/116] a --- hbvm/src/utils.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/hbvm/src/utils.rs b/hbvm/src/utils.rs index 74c4ef09..19830dbf 100644 --- a/hbvm/src/utils.rs +++ b/hbvm/src/utils.rs @@ -23,7 +23,7 @@ macro_rules! impl_display { fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result { match self { $( - $bind => $crate::utils::private::impl_display_match_fragment!($($const,)? f, $fmt $(, $($params)*)?) + $bind => $crate::utils::internal::impl_display_match_fragment!($($const,)? f, $fmt $(, $($params)*)?) ),* } } @@ -32,7 +32,7 @@ macro_rules! impl_display { } #[doc(hidden)] -pub(crate) mod private { +pub(crate) mod internal { macro_rules! impl_display_match_fragment { (const, $f:expr, $lit:literal) => { $f.write_str($lit) From 42be580425a76e5ce53de85544706b3af556e765 Mon Sep 17 00:00:00 2001 From: Erin Date: Fri, 8 Sep 2023 10:46:41 +0200 Subject: [PATCH 081/116] ABI proposal part 1 --- c-abi.md | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 c-abi.md diff --git a/c-abi.md b/c-abi.md new file mode 100644 index 00000000..4a830e01 --- /dev/null +++ b/c-abi.md @@ -0,0 +1,29 @@ +# C ABI (proposal) + +## C datatypes +| C Type | Description | Size (B) | +|:------------|:-------------------------|-------------:| +| char | Character / byte | 8 | +| short | Short integer | 16 | +| int | Integer | 32 | +| long | Long integer | 64 | +| long long | Long long integer | 64 | +| T* | Pointer | 64 | +| float | Single-precision float | 32 | +| double | Double-precision float | 64 | +| long double | Extended-precision float | **Bikeshed** | + +## Registers +| Register | ABI Name | Description | Saver | +|:---------|:---------|:---------------|:-------| +| `r0` | — | Zero register | N/A | +| `r1` | `ra` | Return address | Caller | +| `r2` | `sp` | Stack pointer | Callee | +| `r3` | `tp` | Thread pointer | N/A | + +**TODO:** Parameters + +**TODO:** Saved + +**TODO:** Temp + From e7aa306e5d182fa05c53d3e513a6b09f48a9c6a0 Mon Sep 17 00:00:00 2001 From: able Date: Tue, 12 Sep 2023 01:38:32 -0500 Subject: [PATCH 082/116] examples --- hbasm/assets/add.hbasm | 12 ++++++++++++ hbasm/assets/celsius_converter.hbasm | 16 ++++++++++++++++ hbasm/assets/ecall.hbasm | 23 +++++++++++++---------- hbasm/assets/memory.hbasm | 3 ++- hbvm/assets/add.hb | Bin 0 -> 48 bytes hbvm/assets/celsius_converter.hb | Bin 0 -> 70 bytes hbvm/assets/ecall.hb | Bin 112 -> 90 bytes 7 files changed, 43 insertions(+), 11 deletions(-) create mode 100644 hbasm/assets/add.hbasm create mode 100644 hbasm/assets/celsius_converter.hbasm create mode 100644 hbvm/assets/add.hb create mode 100644 hbvm/assets/celsius_converter.hb diff --git a/hbasm/assets/add.hbasm b/hbasm/assets/add.hbasm new file mode 100644 index 00000000..b21dc5a0 --- /dev/null +++ b/hbasm/assets/add.hbasm @@ -0,0 +1,12 @@ +-- Add two numbers +-- A + B = C + +-- r1 A +li r1, 2 +-- r2 Result +li r2, 0 +-- B = 4 +addi r2, r1, 4 + +-- terminate execution +tx \ No newline at end of file diff --git a/hbasm/assets/celsius_converter.hbasm b/hbasm/assets/celsius_converter.hbasm new file mode 100644 index 00000000..46c6c48b --- /dev/null +++ b/hbasm/assets/celsius_converter.hbasm @@ -0,0 +1,16 @@ +-- r1 will be the temp in fahrenheit +-- r2 temp in celsius +-- r3/r4/r5 will be used by constants + +-- (f - 32) * 5 / 9 + +li r1, 100 + +li r3, 32 +li r4, 5 +li r5, 9 + +sub r2, r1, r3 +mul r2, r2, r4 +dir r2, r0, r2, r5 +tx \ No newline at end of file diff --git a/hbasm/assets/ecall.hbasm b/hbasm/assets/ecall.hbasm index 7743b53a..565655d9 100644 --- a/hbasm/assets/ecall.hbasm +++ b/hbasm/assets/ecall.hbasm @@ -1,11 +1,14 @@ -addi r1, r0, 1024 -addi r2, r1, 1024 -addi r3, r2, 1024 -addi r4, r3, 1024 -addi r5, r4, 1024 -addi r6, r5, 1024 -addi r7, r6, 1024 -addi r8, r7, 1024 -addi r9, r8, 1024 +li r255, 0 +ecall -ecall \ No newline at end of file +li r255, 1 +li r254, 1 +li r253, 100 +ecall + +li r255, 2 +li r254, 0 +li r253, 0 +ecall + +tx \ No newline at end of file diff --git a/hbasm/assets/memory.hbasm b/hbasm/assets/memory.hbasm index 133def5f..0a311455 100644 --- a/hbasm/assets/memory.hbasm +++ b/hbasm/assets/memory.hbasm @@ -1,3 +1,4 @@ li r20, 1010 st r20, r24, 0, 1 -addi r24, r0, 10 \ No newline at end of file +addi r24, r0, 10 +tx \ No newline at end of file diff --git a/hbvm/assets/add.hb b/hbvm/assets/add.hb new file mode 100644 index 0000000000000000000000000000000000000000..321efa58b58e844add54029067e38ef2658721b7 GIT binary patch literal 48 fcmd;@j{+9} literal 0 HcmV?d00001 diff --git a/hbvm/assets/ecall.hb b/hbvm/assets/ecall.hb index 3ecd89a82ad402cc2f2d05840c3c5ee47f3b3b2e..918327121a7e679f599e215b98623f0187efefaa 100644 GIT binary patch literal 90 scmd;=jtiLG;X(qTdl))hT7sk?r=>Q7=02R0icK`qY literal 112 ycmWe&WME)n00RLgMmUq13C?6;hBH}N;7m4FIFp?X&g5W+GdVe6Obv+f5E1}Ep8+la From 35f90e94a87a639904db7848f6da22227c6c2848 Mon Sep 17 00:00:00 2001 From: Erin Date: Fri, 15 Sep 2023 08:42:56 +0200 Subject: [PATCH 083/116] Changed CMP handling and added simple JMP --- hbbytecode/hbbytecode.h | 18 +++++++-------- hbbytecode/src/lib.rs | 37 +++++++++++++++--------------- hbvm/src/vmrun.rs | 10 +++++---- spec.md | 50 +++++++++++++++++++++++------------------ 4 files changed, 62 insertions(+), 53 deletions(-) diff --git a/hbbytecode/hbbytecode.h b/hbbytecode/hbbytecode.h index c5c78afc..d59e002e 100644 --- a/hbbytecode/hbbytecode.h +++ b/hbbytecode/hbbytecode.h @@ -13,15 +13,15 @@ static_assert(CHAR_BIT == 8, "Cursed architectures are not supported"); enum hbbc_Opcode: uint8_t { - hbbc_Op_UN , hbbc_Op_TX , hbbc_Op_NOP , hbbc_Op_ADD , hbbc_Op_SUB , hbbc_Op_MUL , - hbbc_Op_AND , hbbc_Op_OR , hbbc_Op_XOR , hbbc_Op_SL , hbbc_Op_SR , hbbc_Op_SRS , - hbbc_Op_CMP , hbbc_Op_CMPU , hbbc_Op_DIR , hbbc_Op_NEG , hbbc_Op_NOT , hbbc_Op_ADDI , - hbbc_Op_MULI , hbbc_Op_ANDI , hbbc_Op_ORI , hbbc_Op_XORI , hbbc_Op_SLI , hbbc_Op_SRI , - hbbc_Op_SRSI , hbbc_Op_CMPI , hbbc_Op_CMPUI , hbbc_Op_CP , hbbc_Op_SWA , hbbc_Op_LI , - hbbc_Op_LD , hbbc_Op_ST , hbbc_Op_BMC , hbbc_Op_BRC , hbbc_Op_JMP , hbbc_Op_JEQ , - hbbc_Op_JNE , hbbc_Op_JLT , hbbc_Op_JGT , hbbc_Op_JLTU , hbbc_Op_JGTU , hbbc_Op_ECALL , - hbbc_Op_ADDF , hbbc_Op_SUBF , hbbc_Op_MULF , hbbc_Op_DIRF , hbbc_Op_FMAF , hbbc_Op_NEGF , - hbbc_Op_ITF , hbbc_Op_FTI , hbbc_Op_ADDFI , hbbc_Op_MULFI , + hbbc_Op_UN , hbbc_Op_TX , hbbc_Op_NOP , hbbc_Op_ADD , hbbc_Op_SUB , hbbc_Op_MUL , + hbbc_Op_AND , hbbc_Op_OR , hbbc_Op_XOR , hbbc_Op_SL , hbbc_Op_SR , hbbc_Op_SRS , + hbbc_Op_CMP , hbbc_Op_CMPU , hbbc_Op_DIR , hbbc_Op_NEG , hbbc_Op_NOT , hbbc_Op_ADDI , + hbbc_Op_MULI , hbbc_Op_ANDI , hbbc_Op_ORI , hbbc_Op_XORI , hbbc_Op_SLI , hbbc_Op_SRI , + hbbc_Op_SRSI , hbbc_Op_CMPI , hbbc_Op_CMPUI , hbbc_Op_CP , hbbc_Op_SWA , hbbc_Op_LI , + hbbc_Op_LD , hbbc_Op_ST , hbbc_Op_BMC , hbbc_Op_BRC , hbbc_Op_JMP , hbbc_Op_JAL , + hbbc_Op_JEQ , hbbc_Op_JNE , hbbc_Op_JLT , hbbc_Op_JGT , hbbc_Op_JLTU , hbbc_Op_JGTU , + hbbc_Op_ECALL , hbbc_Op_ADDF , hbbc_Op_SUBF , hbbc_Op_MULF , hbbc_Op_DIRF , hbbc_Op_FMAF , + hbbc_Op_NEGF , hbbc_Op_ITF , hbbc_Op_FTI , hbbc_Op_ADDFI , hbbc_Op_MULFI , } typedef hbbc_Opcode; static_assert(sizeof(hbbc_Opcode) == 1); diff --git a/hbbytecode/src/lib.rs b/hbbytecode/src/lib.rs index 3f1c14b0..fe59ee28 100644 --- a/hbbytecode/src/lib.rs +++ b/hbbytecode/src/lib.rs @@ -110,26 +110,27 @@ constmod!(pub opcode(u8) { BMC = 32, "BBD; [#0] ← [#1], imm #2 bytes"; BRC = 33, "BBB; #0 ← #1, imm #2 registers"; - JAL = 34, "BD; Copy PC to #0 and unconditional jump [#1 + imm #2]"; - JEQ = 35, "BBD; if #0 = #1 → jump imm #2"; - JNE = 36, "BBD; if #0 ≠ #1 → jump imm #2"; - JLT = 37, "BBD; if #0 < #1 → jump imm #2"; - JGT = 38, "BBD; if #0 > #1 → jump imm #2"; - JLTU = 39, "BBD; if #0 < #1 → jump imm #2 (unsigned)"; - JGTU = 40, "BBD; if #0 > #1 → jump imm #2 (unsigned)"; - ECALL = 41, "N; Issue system call"; + JMP = 34, "D; Unconditional, non-linking absolute jump"; + JAL = 35, "BD; Copy PC to #0 and unconditional jump [#1 + imm #2]"; + JEQ = 36, "BBD; if #0 = #1 → jump imm #2"; + JNE = 37, "BBD; if #0 ≠ #1 → jump imm #2"; + JLT = 38, "BBD; if #0 < #1 → jump imm #2"; + JGT = 39, "BBD; if #0 > #1 → jump imm #2"; + JLTU = 40, "BBD; if #0 < #1 → jump imm #2 (unsigned)"; + JGTU = 41, "BBD; if #0 > #1 → jump imm #2 (unsigned)"; + ECALL = 42, "N; Issue system call"; - ADDF = 42, "BBB; #0 ← #1 +. #2"; - SUBF = 43, "BBB; #0 ← #1 -. #2"; - MULF = 44, "BBB; #0 ← #1 +. #2"; - DIRF = 45, "BBBB; #0 ← #2 / #3, #1 ← #2 % #3"; - FMAF = 46, "BBBB; #0 ← (#1 * #2) + #3"; - NEGF = 47, "BB; #0 ← -#1"; - ITF = 48, "BB; #0 ← #1 as float"; - FTI = 49, "BB; #0 ← #1 as int"; + ADDF = 43, "BBB; #0 ← #1 +. #2"; + SUBF = 44, "BBB; #0 ← #1 -. #2"; + MULF = 45, "BBB; #0 ← #1 +. #2"; + DIRF = 46, "BBBB; #0 ← #2 / #3, #1 ← #2 % #3"; + FMAF = 47, "BBBB; #0 ← (#1 * #2) + #3"; + NEGF = 48, "BB; #0 ← -#1"; + ITF = 49, "BB; #0 ← #1 as float"; + FTI = 50, "BB; #0 ← #1 as int"; - ADDFI = 50, "BBD; #0 ← #1 +. imm #2"; - MULFI = 51, "BBD; #0 ← #1 *. imm #2"; + ADDFI = 51, "BBD; #0 ← #1 +. imm #2"; + MULFI = 52, "BBD; #0 ← #1 *. imm #2"; }); #[repr(packed)] diff --git a/hbvm/src/vmrun.rs b/hbvm/src/vmrun.rs index ecbeb124..10f60578 100644 --- a/hbvm/src/vmrun.rs +++ b/hbvm/src/vmrun.rs @@ -73,9 +73,9 @@ where SRS => self.binary_op(|l, r| i64::wrapping_shl(l, r as u32)), CMP => { // Compare a0 <=> a1 - // < → -1 + // < → 0 // > → 1 - // = → 0 + // = → 2 let ParamBBB(tg, a0, a1) = self.decode(); self.write_reg( @@ -83,7 +83,8 @@ where self.read_reg(a0) .cast::() .cmp(&self.read_reg(a1).cast::()) - as i64, + as i64 + + 1, ); } CMPU => { @@ -94,7 +95,8 @@ where self.read_reg(a0) .cast::() .cmp(&self.read_reg(a1).cast::()) - as i64, + as i64 + + 1, ); } NOT => { diff --git a/spec.md b/spec.md index b9aad2aa..6aee9211 100644 --- a/spec.md +++ b/spec.md @@ -56,7 +56,7 @@ | 7 | OR | Bitor | | 8 | XOR | Bitxor | | 9 | SL | Unsigned left bitshift | -| 10 | SR | Unsigned right bitshift | +| 10 | SR | Unsigned right bitshift | | 11 | SRS | Signed right bitshift | ### Comparsion @@ -68,9 +68,9 @@ #### Comparsion table | #1 *op* #2 | Result | |:----------:|:------:| -| < | -1 | -| = | 0 | -| > | 1 | +| < | 0 | +| = | 1 | +| > | 2 | ### Division-remainder - Type BBBB @@ -179,11 +179,17 @@ ## Control flow ### Unconditional jump +- Type D +| Opcode | Name | Action | +|:------:|:----:|:-------------------------------:| +| 34 | JMP | Unconditional, non-linking jump | + +### Unconditional linking jump - Type BBD | Opcode | Name | Action | |:------:|:----:|:--------------------------------------------------:| -| 34 | JAL | Save PC past JAL to `#0` and jump at `#1 + imm #2` | +| 35 | JAL | Save PC past JAL to `#0` and jump at `#1 + imm #2` | ### Conditional jumps - Type BBD @@ -191,19 +197,19 @@ | Opcode | Name | Comparsion | |:------:|:----:|:------------:| -| 35 | JEQ | = | -| 36 | JNE | ≠ | -| 37 | JLT | < (signed) | -| 38 | JGT | > (signed) | -| 39 | JLTU | < (unsigned) | -| 40 | JGTU | > (unsigned) | +| 36 | JEQ | = | +| 37 | JNE | ≠ | +| 38 | JLT | < (signed) | +| 39 | JGT | > (signed) | +| 40 | JLTU | < (unsigned) | +| 41 | JGTU | > (unsigned) | ### Environment call - Type N | Opcode | Name | Action | |:------:|:-----:|:-------------------------------------:| -| 41 | ECALL | Cause an trap to the host environment | +| 42 | ECALL | Cause an trap to the host environment | ## Floating point operations - Type BBB @@ -211,29 +217,29 @@ | Opcode | Name | Action | |:------:|:----:|:--------------:| -| 42 | ADDF | Addition | -| 43 | SUBF | Subtraction | -| 44 | MULF | Multiplication | +| 43 | ADDF | Addition | +| 44 | SUBF | Subtraction | +| 45 | MULF | Multiplication | ### Division-remainder - Type BBBB | Opcode | Name | Action | |:------:|:----:|:-------------------------:| -| 45 | DIRF | Same as for integer `DIR` | +| 46 | DIRF | Same as for integer `DIR` | ### Fused Multiply-Add - Type BBBB | Opcode | Name | Action | |:------:|:----:|:---------------------:| -| 46 | FMAF | `#0 ← (#1 * #2) + #3` | +| 47 | FMAF | `#0 ← (#1 * #2) + #3` | ### Negation - Type BB | Opcode | Name | Action | |:------:|:----:|:----------:| -| 47 | NEGF | `#0 ← -#1` | +| 48 | NEGF | `#0 ← -#1` | ### Conversion - Type BB @@ -242,8 +248,8 @@ | Opcode | Name | Action | |:------:|:----:|:------------:| -| 48 | ITF | Int to Float | -| 49 | FTI | Float to Int | +| 49 | ITF | Int to Float | +| 50 | FTI | Float to Int | ## Floating point immediate operations - Type BBD @@ -251,8 +257,8 @@ | Opcode | Name | Action | |:------:|:-----:|:--------------:| -| 50 | ADDFI | Addition | -| 51 | MULFI | Multiplication | +| 51 | ADDFI | Addition | +| 52 | MULFI | Multiplication | # Registers - There is 255 registers + one zero register (with index 0) From 8c8c708279bdfb68364bc6021d72768df82338bb Mon Sep 17 00:00:00 2001 From: Erin Date: Sat, 16 Sep 2023 00:57:37 +0200 Subject: [PATCH 084/116] JMP impl --- hbvm/src/vmrun.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/hbvm/src/vmrun.rs b/hbvm/src/vmrun.rs index 10f60578..b0b79d1a 100644 --- a/hbvm/src/vmrun.rs +++ b/hbvm/src/vmrun.rs @@ -244,6 +244,7 @@ where usize::from(count), ); } + JMP => self.pc = Address::new(self.decode::()), JAL => { // Jump and link. Save PC after this instruction to // specified register and jump to reg + offset. From b1bdbea991e3a898ca13b48ba0e74e84a263edc9 Mon Sep 17 00:00:00 2001 From: Erin Date: Sat, 16 Sep 2023 01:02:14 +0200 Subject: [PATCH 085/116] JMP --- hbbytecode/src/gen_valider.rs | 5 +++++ hbbytecode/src/lib.rs | 2 ++ 2 files changed, 7 insertions(+) diff --git a/hbbytecode/src/gen_valider.rs b/hbbytecode/src/gen_valider.rs index d1e078f9..e047aa65 100644 --- a/hbbytecode/src/gen_valider.rs +++ b/hbbytecode/src/gen_valider.rs @@ -158,6 +158,11 @@ macro_rules! inst_chk { [$($opcode)|*, _, _, _, _, _, _, _, _, _, $rest @ ..] }; + ($rest:ident, d, $($opcode:ident),*) => { + // D1 D2 D3 D4 D5 D6 D7 D8 + [$($opcode)|*, _, _, _, _, _, _, _, _, $rest @ ..] + }; + ($rest:ident, n, $($opcode:ident),*) => { [$($opcode)|*, $rest @ ..] }; diff --git a/hbbytecode/src/lib.rs b/hbbytecode/src/lib.rs index fe59ee28..6e63b95a 100644 --- a/hbbytecode/src/lib.rs +++ b/hbbytecode/src/lib.rs @@ -61,6 +61,8 @@ macro_rules! invoke_with_def { => [NEG, NOT, CP, SWA, NEGF, ITF, FTI], bd(p0: R, p1: I) => [LI], + d(p0: I) + => [JMP], n() => [UN, TX, NOP, ECALL], ); From 3e4095da6f0496994af8bd36652d66c3176c2f99 Mon Sep 17 00:00:00 2001 From: Erin Date: Tue, 26 Sep 2023 23:36:27 +0200 Subject: [PATCH 086/116] Lottsa things changed --- Cargo.lock | 202 +++--------------- Cargo.toml | 2 +- hbasm/Cargo.toml | 22 -- hbasm/assets/add.hbasm | 12 -- hbasm/assets/celsius_converter.hbasm | 16 -- hbasm/assets/ecall.hbasm | 14 -- hbasm/assets/inf_loop.hbasm | 2 - hbasm/assets/memory.hbasm | 4 - hbasm/assets/serial_driver.hbasm | 18 -- hbasm/src/lib.rs | 104 ---------- hbasm/src/macros/asm.rs | 89 -------- hbasm/src/macros/mod.rs | 6 - hbasm/src/macros/text.rs | 293 --------------------------- hbasm/src/main.rs | 56 ----- hbbytecode/Cargo.toml | 3 +- hbbytecode/hbbytecode.h | 15 +- hbbytecode/instructions.in | 64 ++++++ hbbytecode/src/gen_valider.rs | 175 ---------------- hbbytecode/src/lib.rs | 238 ++++++++-------------- hbvm/src/lib.rs | 4 +- hbvm/src/main.rs | 83 -------- hbvm/src/mem/addr.rs | 12 ++ hbvm/src/mem/mod.rs | 8 +- hbvm/src/vmrun.rs | 151 +++++++++----- hbxrt/Cargo.toml | 7 + hbxrt/src/main.rs | 11 + spec.md | 73 ++++--- 27 files changed, 362 insertions(+), 1322 deletions(-) delete mode 100644 hbasm/Cargo.toml delete mode 100644 hbasm/assets/add.hbasm delete mode 100644 hbasm/assets/celsius_converter.hbasm delete mode 100644 hbasm/assets/ecall.hbasm delete mode 100644 hbasm/assets/inf_loop.hbasm delete mode 100644 hbasm/assets/memory.hbasm delete mode 100644 hbasm/assets/serial_driver.hbasm delete mode 100644 hbasm/src/lib.rs delete mode 100644 hbasm/src/macros/asm.rs delete mode 100644 hbasm/src/macros/mod.rs delete mode 100644 hbasm/src/macros/text.rs delete mode 100644 hbasm/src/main.rs create mode 100644 hbbytecode/instructions.in delete mode 100644 hbbytecode/src/gen_valider.rs delete mode 100644 hbvm/src/main.rs create mode 100644 hbxrt/Cargo.toml create mode 100644 hbxrt/src/main.rs diff --git a/Cargo.lock b/Cargo.lock index fd50d298..24aae341 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,93 +2,12 @@ # It is not intended for manual editing. version = 3 -[[package]] -name = "ahash" -version = "0.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c99f64d1e06488f620f932677e24bc6e2897582980441ae90a671415bd7ec2f" -dependencies = [ - "cfg-if", - "once_cell", - "version_check", -] - -[[package]] -name = "allocator-api2" -version = "0.2.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0942ffc6dcaadf03badf6e6a2d0228460359d5e34b57ccdc720b7382dfbd5ec5" - -[[package]] -name = "ariadne" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72fe02fc62033df9ba41cba57ee19acf5e742511a140c7dbc3a873e19a19a1bd" -dependencies = [ - "unicode-width", - "yansi", -] - -[[package]] -name = "beef" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a8241f3ebb85c056b509d4327ad0358fbbba6ffb340bf388f26350aeda225b1" - -[[package]] -name = "bytemuck" -version = "1.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17febce684fd15d89027105661fec94afb475cb995fbc59d2865198446ba2eea" - -[[package]] -name = "cfg-if" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" - -[[package]] -name = "fnv" -version = "1.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" - -[[package]] -name = "hashbrown" -version = "0.13.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e" -dependencies = [ - "ahash", -] - -[[package]] -name = "hashbrown" -version = "0.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c6201b9ff9fd90a5a3bac2e56a830d0caa509576f0e503818ee82c181b3437a" -dependencies = [ - "ahash", - "allocator-api2", -] - -[[package]] -name = "hbasm" -version = "0.1.0" -dependencies = [ - "ariadne", - "bytemuck", - "hashbrown 0.14.0", - "hbbytecode", - "lasso", - "literify", - "logos", - "paste", -] - [[package]] name = "hbbytecode" version = "0.1.0" +dependencies = [ + "with_builtin_macros", +] [[package]] name = "hbvm" @@ -98,84 +17,17 @@ dependencies = [ ] [[package]] -name = "lasso" -version = "0.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4644821e1c3d7a560fe13d842d13f587c07348a1a05d3a797152d41c90c56df2" -dependencies = [ - "ahash", - "hashbrown 0.13.2", -] - -[[package]] -name = "literify" +name = "hbxrt" version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54e4d365df794ed78b4ce1061886f82eae7afa8e3a98ce4c4b0bfd0c777b1175" dependencies = [ - "litrs", - "proc-macro2", - "quote", + "hbvm", ] -[[package]] -name = "litrs" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f17c3668f3cc1132437cdadc93dab05e52d592f06948d3f64828430c36e4a70" -dependencies = [ - "proc-macro2", -] - -[[package]] -name = "logos" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c000ca4d908ff18ac99b93a062cb8958d331c3220719c52e77cb19cc6ac5d2c1" -dependencies = [ - "logos-derive", -] - -[[package]] -name = "logos-codegen" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc487311295e0002e452025d6b580b77bb17286de87b57138f3b5db711cded68" -dependencies = [ - "beef", - "fnv", - "proc-macro2", - "quote", - "regex-syntax", - "syn", -] - -[[package]] -name = "logos-derive" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbfc0d229f1f42d790440136d941afd806bc9e949e2bcb8faa813b0f00d1267e" -dependencies = [ - "logos-codegen", -] - -[[package]] -name = "once_cell" -version = "1.18.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" - -[[package]] -name = "paste" -version = "1.0.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c" - [[package]] name = "proc-macro2" -version = "1.0.66" +version = "1.0.67" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18fb31db3f9bddb2ea821cde30a9f70117e3f119938b5ee630b7403aa6e2ead9" +checksum = "3d433d9f1a3e8c1263d9456598b16fec66f4acc9a74dacffd35c7bb09b3a1328" dependencies = [ "unicode-ident", ] @@ -189,17 +41,11 @@ dependencies = [ "proc-macro2", ] -[[package]] -name = "regex-syntax" -version = "0.6.29" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" - [[package]] name = "syn" -version = "2.0.29" +version = "1.0.109" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c324c494eba9d92503e6f1ef2e6df781e78f6a7705a0202d9801b198807d518a" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" dependencies = [ "proc-macro2", "quote", @@ -208,24 +54,26 @@ dependencies = [ [[package]] name = "unicode-ident" -version = "1.0.11" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "301abaae475aa91687eb82514b328ab47a211a533026cb25fc3e519b86adfc3c" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" [[package]] -name = "unicode-width" -version = "0.1.10" +name = "with_builtin_macros" +version = "0.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" +checksum = "a59d55032495429b87f9d69954c6c8602e4d3f3e0a747a12dea6b0b23de685da" +dependencies = [ + "with_builtin_macros-proc_macros", +] [[package]] -name = "version_check" -version = "0.9.4" +name = "with_builtin_macros-proc_macros" +version = "0.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" - -[[package]] -name = "yansi" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec" +checksum = "15bd7679c15e22924f53aee34d4e448c45b674feb6129689af88593e129f8f42" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] diff --git a/Cargo.toml b/Cargo.toml index 1c77a263..a648dd73 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,3 +1,3 @@ [workspace] resolver = "2" -members = ["hbasm", "hbbytecode", "hbvm"] +members = ["hbbytecode", "hbvm", "hbxrt"] diff --git a/hbasm/Cargo.toml b/hbasm/Cargo.toml deleted file mode 100644 index bd9e98eb..00000000 --- a/hbasm/Cargo.toml +++ /dev/null @@ -1,22 +0,0 @@ -[package] -name = "hbasm" -version = "0.1.0" -edition = "2021" - -[dependencies] -ariadne = "0.3" -bytemuck = "1.13" -hashbrown = "0.14" -hbbytecode = { path = "../hbbytecode" } -literify = "0.1" -paste = "1.0" - -[dependencies.lasso] -version = "0.7" -default-features = false -features = ["no-std"] - -[dependencies.logos] -version = "0.13" -default-features = false -features = ["export_derive"] diff --git a/hbasm/assets/add.hbasm b/hbasm/assets/add.hbasm deleted file mode 100644 index b21dc5a0..00000000 --- a/hbasm/assets/add.hbasm +++ /dev/null @@ -1,12 +0,0 @@ --- Add two numbers --- A + B = C - --- r1 A -li r1, 2 --- r2 Result -li r2, 0 --- B = 4 -addi r2, r1, 4 - --- terminate execution -tx \ No newline at end of file diff --git a/hbasm/assets/celsius_converter.hbasm b/hbasm/assets/celsius_converter.hbasm deleted file mode 100644 index 46c6c48b..00000000 --- a/hbasm/assets/celsius_converter.hbasm +++ /dev/null @@ -1,16 +0,0 @@ --- r1 will be the temp in fahrenheit --- r2 temp in celsius --- r3/r4/r5 will be used by constants - --- (f - 32) * 5 / 9 - -li r1, 100 - -li r3, 32 -li r4, 5 -li r5, 9 - -sub r2, r1, r3 -mul r2, r2, r4 -dir r2, r0, r2, r5 -tx \ No newline at end of file diff --git a/hbasm/assets/ecall.hbasm b/hbasm/assets/ecall.hbasm deleted file mode 100644 index 565655d9..00000000 --- a/hbasm/assets/ecall.hbasm +++ /dev/null @@ -1,14 +0,0 @@ -li r255, 0 -ecall - -li r255, 1 -li r254, 1 -li r253, 100 -ecall - -li r255, 2 -li r254, 0 -li r253, 0 -ecall - -tx \ No newline at end of file diff --git a/hbasm/assets/inf_loop.hbasm b/hbasm/assets/inf_loop.hbasm deleted file mode 100644 index dc56a416..00000000 --- a/hbasm/assets/inf_loop.hbasm +++ /dev/null @@ -1,2 +0,0 @@ -L: - jal r0, r0, L \ No newline at end of file diff --git a/hbasm/assets/memory.hbasm b/hbasm/assets/memory.hbasm deleted file mode 100644 index 0a311455..00000000 --- a/hbasm/assets/memory.hbasm +++ /dev/null @@ -1,4 +0,0 @@ -li r20, 1010 -st r20, r24, 0, 1 -addi r24, r0, 10 -tx \ No newline at end of file diff --git a/hbasm/assets/serial_driver.hbasm b/hbasm/assets/serial_driver.hbasm deleted file mode 100644 index 13bcb3a1..00000000 --- a/hbasm/assets/serial_driver.hbasm +++ /dev/null @@ -1,18 +0,0 @@ -jmp r0, start -start: - jmp r0, init_serial_port - --- Uses r20 to set the port -init_serial_port: - add r20, r30, r10 - li r20, 00 - - -- outb(PORT + 1, 0x00); // Disable all interrupts - -- outb(PORT + 3, 0x80); // Enable DLAB (set baud rate divisor) - -- outb(PORT + 0, 0x03); // Set divisor to 3 (lo byte) 38400 baud - -- outb(PORT + 1, 0x00); // (hi byte) - -- outb(PORT + 3, 0x03); // 8 bits, no parity, one stop bit - -- outb(PORT + 2, 0xC7); // Enable FIFO, clear them, with 14-byte threshold - -- outb(PORT + 4, 0x0B); // IRQs enabled, RTS/DSR set - -- outb(PORT + 4, 0x1E); // Set in loopback mode, test the serial chip - -- outb(PORT + 0, 0xAE); // Test serial chip (send byte 0xAE and check if serial returns same byte) \ No newline at end of file diff --git a/hbasm/src/lib.rs b/hbasm/src/lib.rs deleted file mode 100644 index a3eb6716..00000000 --- a/hbasm/src/lib.rs +++ /dev/null @@ -1,104 +0,0 @@ -//! Holey Bytes Assembler -//! -//! 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. -//! -//! So this crate contains both assembleer with API for programs and a text assembler -//! for humans to write - -#![no_std] - -extern crate alloc; - -mod macros; - -use { - alloc::{vec, vec::Vec}, - hashbrown::HashSet, -}; - -/// Assembler -/// -/// - Opcode-generic, instruction-type-specific methods are named `i_param_` -/// - You likely won't need to use them, but they are here, just in case :) -/// - Instruction-specific methods are named `i_` -pub struct Assembler { - pub buf: Vec, - pub sub: HashSet, -} - -impl Default for Assembler { - fn default() -> Self { - Self { - buf: vec![0; 4], - sub: Default::default(), - } - } -} - -hbbytecode::invoke_with_def!(macros::text::gen_text); - -impl Assembler { - hbbytecode::invoke_with_def!(macros::asm::impl_asm); - - /// Append 12 zeroes (UN) at the end and add magic to the begining - /// - /// # HoleyBytes lore - /// - /// In reference HBVM implementation checks are done in - /// a separate phase before execution. - /// - /// This way execution will be much faster as they have to - /// be done only once. - /// - /// There was an issue. You cannot statically check register values and - /// `JAL` instruction could hop at the end of program to some byte, which - /// will be interpreted as some valid opcode and VM in attempt to decode - /// the instruction performed out-of-bounds read which leads to undefined behaviour. - /// - /// Several options were considered to overcome this, but inserting some data at - /// program's end which when executed would lead to undesired behaviour, though - /// not undefined behaviour. - /// - /// Newly created `UN` (as UNreachable) was chosen as - /// - It was a good idea to add some equivalent to `ud2` anyways - /// - It was chosen to be zero - /// - What if you somehow reached that code, it will appropriately bail :) - /// - (yes, originally `NOP` was considered) - /// - /// Why 12 bytes? That's the size of largest instruction parameter part. - pub fn finalise(&mut self) { - self.buf.extend([0; 12]); - self.buf[0..4].copy_from_slice(&0xAB1E0B_u32.to_le_bytes()); - } -} - -/// Immediate value -/// -/// # Implementor notice -/// It should insert exactly 8 bytes, otherwise output will be malformed. -/// This is not checked in any way -pub trait Imm { - /// Insert immediate value - fn insert(&self, asm: &mut Assembler); -} - -/// Implement immediate values -macro_rules! impl_imm_le_bytes { - ($($ty:ty),* $(,)?) => { - $( - impl Imm for $ty { - #[inline(always)] - fn insert(&self, asm: &mut Assembler) { - // Convert to little-endian bytes, insert. - asm.buf.extend(self.to_le_bytes()); - } - } - )* - }; -} - -impl_imm_le_bytes!(u64, i64, f64); diff --git a/hbasm/src/macros/asm.rs b/hbasm/src/macros/asm.rs deleted file mode 100644 index 98db69f0..00000000 --- a/hbasm/src/macros/asm.rs +++ /dev/null @@ -1,89 +0,0 @@ -//! Macros to generate [`crate::Assembler`] - -/// Incremental token-tree muncher to implement specific instruction -/// functions based on generic function for instruction type -macro_rules! impl_asm_opcodes { - ( // End case - $generic:ident - ($($param_i:ident: $param_ty:ty),*) - => [] - ) => {}; - - ( - $generic:ident - ($($param_i:ident: $param_ty:ty),*) - => [$opcode:ident, $($rest:tt)*] - ) => { - // Instruction-specific function - paste::paste! { - #[inline(always)] - pub fn [](&mut self, $($param_i: $param_ty),*) { - self.$generic(hbbytecode::opcode::$opcode, $($param_i),*) - } - } - - // And recurse! - macros::asm::impl_asm_opcodes!( - $generic($($param_i: $param_ty),*) - => [$($rest)*] - ); - }; -} - -/// Numeric value insert -macro_rules! impl_asm_insert { - // Immediate - this is trait-based, - // the insertion is delegated to its implementation - ($self:expr, $id:ident, I) => { - Imm::insert(&$id, $self) - }; - - // Length - cannot be more than 2048 - ($self:expr, $id:ident, L) => {{ - assert!($id <= 2048); - $self.buf.extend($id.to_le_bytes()) - }}; - - // Other numbers, just insert their bytes, little endian - ($self:expr, $id:ident, $_:ident) => { - $self.buf.extend($id.to_le_bytes()) - }; -} - -/// Implement assembler -macro_rules! impl_asm { - ( - $( - $ityn:ident - ($($param_i:ident: $param_ty:ident),* $(,)?) - => [$($opcode:ident),* $(,)?], - )* - ) => { - paste::paste! { - $( - // Opcode-generic functions specific for instruction types - pub fn [](&mut self, opcode: u8, $($param_i: macros::asm::ident_map_ty!($param_ty)),*) { - self.buf.push(opcode); - $(macros::asm::impl_asm_insert!(self, $param_i, $param_ty);)* - } - - // Generate opcode-specific functions calling the opcode-generic ones - macros::asm::impl_asm_opcodes!( - []($($param_i: macros::asm::ident_map_ty!($param_ty)),*) - => [$($opcode,)*] - ); - )* - } - }; -} - -/// Map operand type to Rust type -#[rustfmt::skip] -macro_rules! ident_map_ty { - (R) => { u8 }; // Register is just u8 - (I) => { impl Imm }; // Immediate is anything implementing the trait - (L) => { u16 }; // Copy count - ($id:ident) => { $id }; // Anything else → identity map -} - -pub(crate) use {ident_map_ty, impl_asm, impl_asm_insert, impl_asm_opcodes}; diff --git a/hbasm/src/macros/mod.rs b/hbasm/src/macros/mod.rs deleted file mode 100644 index 509f6be5..00000000 --- a/hbasm/src/macros/mod.rs +++ /dev/null @@ -1,6 +0,0 @@ -//! And here the land of macros begin. -//! -//! They do not bite, really. Have you seen what Yandros is writing? - -pub mod asm; -pub mod text; diff --git a/hbasm/src/macros/text.rs b/hbasm/src/macros/text.rs deleted file mode 100644 index 89b360b2..00000000 --- a/hbasm/src/macros/text.rs +++ /dev/null @@ -1,293 +0,0 @@ -//! Macros to generate text-code assembler at [`crate::text`] -// Refering in module which generates a module to that module — is that even legal? :D - -/// Generate text code based assembler -macro_rules! gen_text { - ( - $( - $ityn:ident - ($($param_i:ident: $param_ty:ident),* $(,)?) - => [$($opcode:ident),* $(,)?], - )* - ) => { - /// # Text assembler - /// Text assembler generated simply calls methods in the [`crate::Assembler`] type. - /// - /// # Syntax - /// ```text - /// instruction op1, op2, … - /// … - /// ``` - /// - Opcode names are lowercase - /// - Registers are prefixed with `r` followed by number - /// - Operands are separated by `,` - /// - Instructions are separated by either line feed or `;` (αυτό δεν είναι ερωτηματικό!) - /// - 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 - pub mod text { - use { - crate::{ - Assembler, - macros::text::*, - }, - hashbrown::HashMap, - lasso::{Key, Rodeo, Spur}, - logos::{Lexer, Logos, Span}, - }; - - paste::paste!(literify::literify! { - /// Assembly token - #[derive(Clone, Copy, Debug, PartialEq, Eq, Logos)] - #[logos(extras = Rodeo)] - #[logos(skip r"[ \t\t]+")] - #[logos(skip r"-- .*")] - pub enum Token { - $($(#[token(~([<$opcode:lower>]), |_| hbbytecode::opcode::[<$opcode:upper>])])*)* - Opcode(u8), - - #[regex("[0-9]+", |l| l.slice().parse().ok())] - #[regex( - "-[0-9]+", - |lexer| { - Some(u64::from_ne_bytes(lexer.slice().parse::().ok()?.to_ne_bytes())) - }, - )] Integer(u64), - - #[regex( - "r[0-9]+", - |lexer| match lexer.slice()[1..].parse() { - Ok(n) => Some(n), - _ => None - }, - )] Register(u8), - - #[regex( - r"\p{XID_Start}\p{XID_Continue}*:", - |lexer| lexer.extras.get_or_intern(&lexer.slice()[..lexer.slice().len() - 1]), - )] Label(Spur), - - #[regex( - r"\p{XID_Start}\p{XID_Continue}*", - |lexer| lexer.extras.get_or_intern(lexer.slice()), - )] Symbol(Spur), - - #[token("\n")] - #[token(";")] ISep, - #[token(",")] PSep, - } - }); - - /// Type of error - #[derive(Copy, Clone, Debug, PartialEq, Eq)] - pub enum ErrorKind { - UnexpectedToken, - InvalidToken, - UnexpectedEnd, - InvalidSymbol, - } - - /// Text assembly error - #[derive(Clone, Debug, PartialEq, Eq)] - pub struct Error { - pub kind: ErrorKind, - pub span: Span, - } - - /// Parse code and insert instructions - pub fn assemble(asm: &mut Assembler, code: &str) -> Result<(), Error> { - pub struct TextAsm<'a> { - asm: &'a mut Assembler, - lexer: Lexer<'a, Token>, - symloc: HashMap, - } - - impl<'a> TextAsm<'a> { - fn next(&mut self) -> Result { - match self.lexer.next() { - Some(Ok(t)) => Ok(t), - Some(Err(())) => Err(ErrorKind::InvalidToken), - None => Err(ErrorKind::UnexpectedEnd), - } - } - - #[inline(always)] - fn run(&mut self) -> Result<(), ErrorKind> { - loop { - match self.lexer.next() { - // Got an opcode - Some(Ok(Token::Opcode(op))) => { - match op { - // Special-cased - hbbytecode::opcode::BRC => { - param_extract_itm!( - self, - p0: R, - p1: R, - p2: u8 - ); - - self.asm.i_param_bbb(op, p0, p1, p2); - }, - - // Take all the opcodes and match them to their corresponding functions - $( - #[allow(unreachable_patterns)] - $(hbbytecode::opcode::$opcode)|* => paste::paste!({ - param_extract_itm!(self, $($param_i: $param_ty),*); - self.asm.[](op, $($param_i),*); - }), - )* - - // Already matched in Logos, should not be able to obtain - // invalid opcode. - _ => unreachable!(), - } - } - // Insert label to table - Some(Ok(Token::Label(lbl))) => { - self.symloc.insert(lbl, self.asm.buf.len()); - } - // Instruction separator (LF, ;) - Some(Ok(Token::ISep)) => (), - Some(Ok(_)) => return Err(ErrorKind::UnexpectedToken), - Some(Err(())) => return Err(ErrorKind::InvalidToken), - None => return Ok(()), - } - } - } - } - - let mut asm = TextAsm { - asm, - lexer: Token::lexer(code), - symloc: HashMap::default(), - }; - - asm.run() - .map_err(|kind| Error { kind, span: asm.lexer.span() })?; - - // Walk table and substitute labels - // for their addresses - for &loc in &asm.asm.sub { - // Extract indices from the code and get addresses from table - let val = asm.symloc - .get( - &Spur::try_from_usize(bytemuck::pod_read_unaligned::( - &asm.asm.buf[loc..loc + core::mem::size_of::()]) as _ - ).unwrap() - ) - .ok_or(Error { kind: ErrorKind::InvalidSymbol, span: 0..0 })? - .to_le_bytes(); - - // New address - asm.asm.buf[loc..] - .iter_mut() - .zip(val) - .for_each(|(dst, src)| *dst = src); - } - - Ok(()) - } - - // Fun fact: this is a little hack - // It may slow the things a little bit down, but - // it made the macro to be made pretty nice. - // - // If you have any idea how to get rid of this, - // contributions are welcome :) - // I *likely* won't try anymore. - enum InternalImm { - Const(u64), - Named(Spur), - } - - impl $crate::Imm for InternalImm { - #[inline] - fn insert(&self, asm: &mut Assembler) { - match self { - // Constant immediate, just put it in - Self::Const(a) => a.insert(asm), - // Label - Self::Named(a) => { - // Insert to the sub table that substitution will be - // requested - asm.sub.insert(asm.buf.len()); - // Insert value from interner in place - asm.buf.extend((a.into_usize() as u64).to_le_bytes()); - }, - } - } - } - } - }; -} - -/// Extract item by pattern, otherwise return [`ErrorKind::UnexpectedToken`] -macro_rules! extract_pat { - ($self:expr, $pat:pat) => { - let $pat = $self.next()? - else { return Err(ErrorKind::UnexpectedToken) }; - }; -} - -/// Generate extract macro -macro_rules! gen_extract { - // Integer types have same body - ($($int:ident),* $(,)?) => { - /// Extract operand from code - macro_rules! extract { - // Register (require prefixing with r) - ($self:expr, R, $id:ident) => { - extract_pat!($self, Token::Register($id)); - }; - - ($self:expr, L, $id:ident) => { - extract_pat!($self, Token::Integer($id)); - if $id > 2048 { - return Err(ErrorKind::InvalidToken); - } - - let $id = u16::try_from($id).unwrap(); - }; - - // Immediate - ($self:expr, I, $id:ident) => { - let $id = match $self.next()? { - // Either straight up integer - Token::Integer(a) => InternalImm::Const(a), - // …or a label - Token::Symbol(a) => InternalImm::Named(a), - _ => return Err(ErrorKind::UnexpectedToken), - }; - }; - - // Get $int, if not fitting, the token is claimed invalid - $(($self:expr, $int, $id:ident) => { - extract_pat!($self, Token::Integer($id)); - let $id = $int::try_from($id).map_err(|_| ErrorKind::InvalidToken)?; - });*; - } - }; -} - -gen_extract!(u8, u16, u32); - -/// Parameter extract incremental token-tree muncher -/// -/// What else would it mean? -macro_rules! param_extract_itm { - ($self:expr, $($id:ident: $ty:ident)? $(, $($tt:tt)*)?) => { - // Extract pattern - $(extract!($self, $ty, $id);)? - $( - // Require operand separator - extract_pat!($self, Token::PSep); - // And go to the next (recursive) - // …munch munch… yummy token trees. - param_extract_itm!($self, $($tt)*); - )? - }; -} - -pub(crate) use {extract, extract_pat, gen_text, param_extract_itm}; diff --git a/hbasm/src/main.rs b/hbasm/src/main.rs deleted file mode 100644 index 8ce4f57e..00000000 --- a/hbasm/src/main.rs +++ /dev/null @@ -1,56 +0,0 @@ -use std::io::Write; - -use hbasm::Assembler; - -use { - ariadne::{ColorGenerator, Label, Report, ReportKind, Source}, - std::{ - error::Error, - io::{stdin, Read}, - }, -}; - -fn main() -> Result<(), Box> { - let mut code = String::new(); - stdin().read_to_string(&mut code)?; - - let mut assembler = Assembler::default(); - if let Err(e) = hbasm::text::assemble(&mut assembler, &code) { - let mut colors = ColorGenerator::new(); - - let e_code = match e.kind { - hbasm::text::ErrorKind::UnexpectedToken => 1, - hbasm::text::ErrorKind::InvalidToken => 2, - hbasm::text::ErrorKind::UnexpectedEnd => 3, - hbasm::text::ErrorKind::InvalidSymbol => 4, - }; - let message = match e.kind { - hbasm::text::ErrorKind::UnexpectedToken => "This token is not expected!", - hbasm::text::ErrorKind::InvalidToken => "The token is not valid!", - hbasm::text::ErrorKind::UnexpectedEnd => { - "The assembler reached the end of input unexpectedly!" - } - hbasm::text::ErrorKind::InvalidSymbol => { - "This referenced symbol doesn't have a corresponding label!" - } - }; - let a = colors.next(); - - Report::build(ReportKind::Error, "engine_internal", e.span.clone().start) - .with_code(e_code) - .with_message(format!("{:?}", e.kind)) - .with_label( - Label::new(("engine_internal", e.span)) - .with_message(message) - .with_color(a), - ) - .finish() - .eprint(("engine_internal", Source::from(&code))) - .unwrap(); - } else { - assembler.finalise(); - std::io::stdout().lock().write_all(&assembler.buf).unwrap(); - } - - Ok(()) -} diff --git a/hbbytecode/Cargo.toml b/hbbytecode/Cargo.toml index fa9f9707..ecfb1dbc 100644 --- a/hbbytecode/Cargo.toml +++ b/hbbytecode/Cargo.toml @@ -1,6 +1,7 @@ [package] name = "hbbytecode" version = "0.1.0" -edition = "2021" +edition = "2018" [dependencies] +with_builtin_macros = "0.0.3" diff --git a/hbbytecode/hbbytecode.h b/hbbytecode/hbbytecode.h index d59e002e..5ede0443 100644 --- a/hbbytecode/hbbytecode.h +++ b/hbbytecode/hbbytecode.h @@ -18,10 +18,11 @@ enum hbbc_Opcode: uint8_t { hbbc_Op_CMP , hbbc_Op_CMPU , hbbc_Op_DIR , hbbc_Op_NEG , hbbc_Op_NOT , hbbc_Op_ADDI , hbbc_Op_MULI , hbbc_Op_ANDI , hbbc_Op_ORI , hbbc_Op_XORI , hbbc_Op_SLI , hbbc_Op_SRI , hbbc_Op_SRSI , hbbc_Op_CMPI , hbbc_Op_CMPUI , hbbc_Op_CP , hbbc_Op_SWA , hbbc_Op_LI , - hbbc_Op_LD , hbbc_Op_ST , hbbc_Op_BMC , hbbc_Op_BRC , hbbc_Op_JMP , hbbc_Op_JAL , - hbbc_Op_JEQ , hbbc_Op_JNE , hbbc_Op_JLT , hbbc_Op_JGT , hbbc_Op_JLTU , hbbc_Op_JGTU , - hbbc_Op_ECALL , hbbc_Op_ADDF , hbbc_Op_SUBF , hbbc_Op_MULF , hbbc_Op_DIRF , hbbc_Op_FMAF , - hbbc_Op_NEGF , hbbc_Op_ITF , hbbc_Op_FTI , hbbc_Op_ADDFI , hbbc_Op_MULFI , + hhbc_Op_LRA , hbbc_Op_LD , hbbc_Op_ST , hbbc_Op_LDR , hhbc_Op_STR , hbbc_Op_BMC , + hbbc_Op_BRC , hbbc_Op_JMP , hbbc_Op_JMPR , hbbc_Op_JAL , hbbc_Op_JALR , hbbc_Op_JEQ , + hbbc_Op_JNE , hbbc_Op_JLT , hbbc_Op_JGT , hbbc_Op_JLTU , hbbc_Op_JGTU , hbbc_Op_ECALL , + hbbc_Op_ADDF , hbbc_Op_SUBF , hbbc_Op_MULF , hbbc_Op_DIRF , hbbc_Op_FMAF , hbbc_Op_NEGF , + hbbc_Op_ITF , hbbc_Op_FTI , hbbc_Op_ADDFI , hbbc_Op_MULFI , } typedef hbbc_Opcode; static_assert(sizeof(hbbc_Opcode) == 1); @@ -42,6 +43,12 @@ struct hbbc_ParamBBDH typedef hbbc_ParamBBDH; static_assert(sizeof(hbbc_ParamBBDH) == 96 / 8); +struct hbbc_ParamBBWH + { uint8_t _0; uint8_t _1; uint32_t _2; uint16_t _3; } + typedef hbbc_ParamBBWH; + static_assert(sizeof(hbbc_ParamBBWH) == 64 / 8); + + struct hbbc_ParamBBD { uint8_t _0; uint8_t _1; uint64_t _2; } typedef hbbc_ParamBBD; diff --git a/hbbytecode/instructions.in b/hbbytecode/instructions.in new file mode 100644 index 00000000..f9edb07d --- /dev/null +++ b/hbbytecode/instructions.in @@ -0,0 +1,64 @@ +// OPCODE, MNEMONIC, TYPE, DOC; + + 0, UN, N, "Cause an unreachable code trap" ; + 1, TX, N, "Termiante execution" ; + 2, NOP, N, "Do nothing" ; + + 3, ADD, RRR, "Addition" ; + 4, SUB, RRR, "Subtraction" ; + 5, MUL, RRR, "Multiplication" ; + 6, AND, RRR, "Bitand" ; + 7, OR, RRR, "Bitor" ; + 8, XOR, RRR, "Bitxor" ; + 9, SL, RRR, "Unsigned left bitshift" ; +10, SR, RRR, "Unsigned right bitshift" ; +11, SRS, RRR, "Signed right bitshift" ; +12, CMP, RRR, "Signed comparsion" ; +13, CMPU, RRR, "Unsigned comparsion" ; +14, DIR, RRRR, "Merged divide-remainder" ; +15, NOT, RR, "Logical negation" ; +16, ADDI, RRD, "Addition with immediate" ; +17, MULI, RRD, "Multiplication with immediate" ; +18, ANDI, RRD, "Bitand with immediate" ; +19, ORI, RRD, "Bitor with immediate" ; +20, XORI, RRD, "Bitxor with immediate" ; +21, SLI, RRW, "Unsigned left bitshift with immedidate"; +22, SRI, RRW, "Unsigned right bitshift with immediate"; +23, SRSI, RRW, "Signed right bitshift with immediate" ; +24, CMPI, RRD, "Signed compare with immediate" ; +25, CMPUI, RRD, "Unsigned compare with immediate" ; + +26, CP, RR, "Copy register" ; +27, SWA, RR, "Swap registers" ; +28, LI, RD, "Load immediate" ; +29, LRA, RRO, "Load relative address" ; +30, LD, RRAH, "Load from absolute address" ; +31, ST, RRAH, "Store to absolute address" ; +32, LDR, RROH, "Load from relative address" ; +33, STR, RROH, "Store to absolute address" ; +34, BMC, RRH, "Copy block of memory" ; +35, BRC, RRB, "Copy register block" ; + +36, JMP, A, "Absolute jump" ; +37, JMPR, O, "Relative jump" ; +38, JAL, RRA, "Linking absolute jump" ; +39, JALR, RRO, "Linking relative jump" ; +40, JEQ, RRP, "Branch on equal" ; +41, JNE, RRP, "Branch on nonequal" ; +42, JLT, RRP, "Branch on lesser-than (signed)" ; +43, JGT, RRP, "Branch on greater-than (signed)" ; +44, JLTU, RRP, "Branch on lesser-than (unsigned)" ; +45, JGTU, RRP, "Branch on greater-than (unsigned)" ; +46, ECALL, N, "Issue ecall trap" ; + +47, ADDF, RRR, "Floating addition" ; +48, SUBF, RRR, "Floating subtraction" ; +49, MULF, RRR, "Floating multiply" ; +50, DIRF, RRRR, "Merged floating divide-remainder" ; +51, FMAF, RRRR, "Fused floating multiply-add" ; +52, NEGF, RR, "Floating sign negation" ; +53, ITF, RR, "Int to float" ; +54, FTI, RR, "Float to int" ; + +55, ADDFI, RRD, "Floating addition with immediate" ; +56, MULFI, RRD, "Floating multiplication with immediate"; diff --git a/hbbytecode/src/gen_valider.rs b/hbbytecode/src/gen_valider.rs deleted file mode 100644 index e047aa65..00000000 --- a/hbbytecode/src/gen_valider.rs +++ /dev/null @@ -1,175 +0,0 @@ -//! 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, - /// Program misses magic - MissingMagic - } - - /// 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> { - // Validate magic - if program.get(0..4) != Some(&0xAB1E0B_u32.to_le_bytes()) { - return Err(Error { - kind: ErrorKind::MissingMagic, - index: 0, - }); - } - - // 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; - program = &program[4..]; - loop { - use crate::opcode::*; - program = match program { - // End of program - [] => return Ok(()), - - // Memory load/store cannot go out-of-bounds register array - // B B D1 D2 D3 D4 D5 D6 D7 D8 H1 H2 - [LD..=ST, reg, _, _, _, _, _, _, _, _, _, count_0, count_1, ..] - if usize::from(*reg) * 8 - + usize::from(u16::from_le_bytes([*count_0, *count_1])) - > 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, d, $($opcode:ident),*) => { - // 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}; diff --git a/hbbytecode/src/lib.rs b/hbbytecode/src/lib.rs index 6e63b95a..06414f89 100644 --- a/hbbytecode/src/lib.rs +++ b/hbbytecode/src/lib.rs @@ -1,165 +1,91 @@ #![no_std] -mod gen_valider; +pub type OpR = u8; -macro_rules! constmod { - ($vis:vis $mname:ident($repr:ty) { - $(#![doc = $mdoc:literal])? - $($cname:ident = $val:expr $(,$doc:literal)?;)* - }) => { - $(#[doc = $mdoc])? - $vis mod $mname { +pub type OpA = u64; +pub type OpO = u32; +pub type OpP = u16; + +pub type OpB = u8; +pub type OpH = u16; +pub type OpW = u32; +pub type OpD = u64; + +/// # Safety +/// Has to be valid to be decoded from bytecode. +pub unsafe trait BytecodeItem {} +macro_rules! define_items { + ($($name:ident ($($item:ident),* $(,)?)),* $(,)?) => { + $( + #[repr(packed)] + pub struct $name($(pub $item),*); + unsafe impl BytecodeItem for $name {} + )* + }; +} + +define_items! { + OpsRR (OpR, OpR ), + OpsRRR (OpR, OpR, OpR ), + OpsRRRR (OpR, OpR, OpR, OpR), + OpsRRB (OpR, OpR, OpB ), + OpsRRH (OpR, OpR, OpH ), + OpsRRW (OpR, OpR, OpW ), + OpsRD (OpR, OpD ), + OpsRRD (OpR, OpR, OpD ), + OpsRRAH (OpR, OpR, OpA, OpH), + OpsRROH (OpR, OpR, OpO, OpH), + OpsRRO (OpR, OpR, OpO ), + OpsRRP (OpR, OpR, OpP ), +} + +unsafe impl BytecodeItem for OpA {} +unsafe impl BytecodeItem for OpB {} +unsafe impl BytecodeItem for OpO {} +unsafe impl BytecodeItem for () {} + +::with_builtin_macros::with_builtin! { + let $spec = include_from_root!("instructions.in") in { + /// Invoke macro with bytecode definition + /// + /// # Format + /// ```text + /// Opcode, Mnemonic, Type, Docstring; + /// ``` + /// + /// # Type + /// ```text + /// Types consist of letters meaning a single field + /// | Type | Size (B) | Meaning | + /// |:-----|:---------|:------------------------| + /// | N | 0 | Empty | + /// | R | 1 | Register | + /// | A | 8 | Absolute address | + /// | O | 4 | Relative address offset | + /// | P | 2 | Relative address offset | + /// | B | 1 | Immediate | + /// | H | 2 | Immediate | + /// | W | 4 | Immediate | + /// | D | 8 | Immediate | + /// ``` + #[macro_export] + macro_rules! invoke_with_def { + ($macro:path) => { + $macro! { $spec } + }; + } + } +} + +macro_rules! gen_opcodes { + ($($opcode:expr, $mnemonic:ident, $_ty:ident, $doc:literal;)*) => { + pub mod opcode { $( - $(#[doc = $doc])? - pub const $cname: $repr = $val; + #[doc = $doc] + pub const $mnemonic: u8 = $opcode; )* } }; } -#[allow(rustdoc::invalid_rust_codeblocks)] -/// Invoke macro with bytecode definition -/// # Input syntax -/// ```no_run -/// macro!( -/// INSTRUCTION_TYPE(p0: TYPE, p1: TYPE, …) -/// => [INSTRUCTION_A, INSTRUCTION_B, …], -/// … -/// ); -/// ``` -/// - Instruction type determines opcode-generic, instruction-type-specific -/// function. Name: `i_param_INSTRUCTION_TYPE` -/// - Per-instructions there will be generated opcode-specific functions calling the generic ones -/// - Operand types -/// - R: Register (u8) -/// - I: Immediate -/// - L: Memory load / store size (u16) -/// - Other types are identity-mapped -/// -/// # BRC special-case -/// BRC's 3rd operand is plain byte, not a register. Encoding is the same, but for some cases it may matter. -/// -/// Please, if you distinguish in your API between byte and register, special case this one. -/// -/// Sorry for that :( -#[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], - d(p0: I) - => [JMP], - n() - => [UN, TX, NOP, ECALL], - ); - }; -} - -invoke_with_def!(gen_valider::gen_valider); - -constmod!(pub opcode(u8) { - //! Opcode constant module - - UN = 0, "N; Raises a trap"; - TX = 1, "N; Terminate execution"; - NOP = 2, "N; Do nothing"; - - ADD = 3, "BBB; #0 ← #1 + #2"; - SUB = 4, "BBB; #0 ← #1 - #2"; - MUL = 5, "BBB; #0 ← #1 × #2"; - AND = 6, "BBB; #0 ← #1 & #2"; - OR = 7, "BBB; #0 ← #1 | #2"; - XOR = 8, "BBB; #0 ← #1 ^ #2"; - SL = 9, "BBB; #0 ← #1 « #2"; - SR = 10, "BBB; #0 ← #1 » #2"; - SRS = 11, "BBB; #0 ← #1 » #2 (signed)"; - CMP = 12, "BBB; #0 ← #1 <=> #2"; - CMPU = 13, "BBB; #0 ← #1 <=> #2 (unsigned)"; - DIR = 14, "BBBB; #0 ← #2 / #3, #1 ← #2 % #3"; - NEG = 15, "BB; #0 ← -#1"; - NOT = 16, "BB; #0 ← !#1"; - - ADDI = 17, "BBD; #0 ← #1 + imm #2"; - MULI = 18, "BBD; #0 ← #1 × imm #2"; - ANDI = 19, "BBD; #0 ← #1 & imm #2"; - ORI = 20, "BBD; #0 ← #1 | imm #2"; - XORI = 21, "BBD; #0 ← #1 ^ imm #2"; - SLI = 22, "BBW; #0 ← #1 « imm #2"; - SRI = 23, "BBW; #0 ← #1 » imm #2"; - SRSI = 24, "BBW; #0 ← #1 » imm #2 (signed)"; - CMPI = 25, "BBD; #0 ← #1 <=> imm #2"; - CMPUI = 26, "BBD; #0 ← #1 <=> imm #2 (unsigned)"; - - CP = 27, "BB; Copy #0 ← #1"; - SWA = 28, "BB; Swap #0 and #1"; - LI = 29, "BD; #0 ← imm #1"; - LD = 30, "BBDB; #0 ← [#1 + imm #3], imm #4 bytes, overflowing"; - ST = 31, "BBDB; [#1 + imm #3] ← #0, imm #4 bytes, overflowing"; - BMC = 32, "BBD; [#0] ← [#1], imm #2 bytes"; - BRC = 33, "BBB; #0 ← #1, imm #2 registers"; - - JMP = 34, "D; Unconditional, non-linking absolute jump"; - JAL = 35, "BD; Copy PC to #0 and unconditional jump [#1 + imm #2]"; - JEQ = 36, "BBD; if #0 = #1 → jump imm #2"; - JNE = 37, "BBD; if #0 ≠ #1 → jump imm #2"; - JLT = 38, "BBD; if #0 < #1 → jump imm #2"; - JGT = 39, "BBD; if #0 > #1 → jump imm #2"; - JLTU = 40, "BBD; if #0 < #1 → jump imm #2 (unsigned)"; - JGTU = 41, "BBD; if #0 > #1 → jump imm #2 (unsigned)"; - ECALL = 42, "N; Issue system call"; - - ADDF = 43, "BBB; #0 ← #1 +. #2"; - SUBF = 44, "BBB; #0 ← #1 -. #2"; - MULF = 45, "BBB; #0 ← #1 +. #2"; - DIRF = 46, "BBBB; #0 ← #2 / #3, #1 ← #2 % #3"; - FMAF = 47, "BBBB; #0 ← (#1 * #2) + #3"; - NEGF = 48, "BB; #0 ← -#1"; - ITF = 49, "BB; #0 ← #1 as float"; - FTI = 50, "BB; #0 ← #1 as int"; - - ADDFI = 51, "BBD; #0 ← #1 +. imm #2"; - MULFI = 52, "BBD; #0 ← #1 *. imm #2"; -}); - -#[repr(packed)] -pub struct ParamBBBB(pub u8, pub u8, pub u8, pub u8); -#[repr(packed)] -pub struct ParamBBB(pub u8, pub u8, pub u8); -#[repr(packed)] -pub struct ParamBBDH(pub u8, pub u8, pub u64, pub u16); -#[repr(packed)] -pub struct ParamBBD(pub u8, pub u8, pub u64); -#[repr(packed)] -pub struct ParamBBW(pub u8, pub u8, pub u32); -#[repr(packed)] -pub struct ParamBB(pub u8, pub u8); -#[repr(packed)] -pub struct ParamBD(pub u8, pub u64); - -/// # Safety -/// Has to be valid to be decoded from bytecode. -pub unsafe trait ProgramVal {} -unsafe impl ProgramVal for ParamBBBB {} -unsafe impl ProgramVal for ParamBBB {} -unsafe impl ProgramVal for ParamBBDH {} -unsafe impl ProgramVal for ParamBBD {} -unsafe impl ProgramVal for ParamBBW {} -unsafe impl ProgramVal for ParamBB {} -unsafe impl ProgramVal for ParamBD {} -unsafe impl ProgramVal for u64 {} -unsafe impl ProgramVal for u8 {} // Opcode -unsafe impl ProgramVal for () {} +invoke_with_def!(gen_opcodes); diff --git a/hbvm/src/lib.rs b/hbvm/src/lib.rs index 5da24b1d..2c4391ca 100644 --- a/hbvm/src/lib.rs +++ b/hbvm/src/lib.rs @@ -14,7 +14,7 @@ #![cfg_attr(feature = "nightly", feature(fn_align))] #![warn(missing_docs)] -use mem::{Memory, Address}; +use mem::{Address, Memory}; #[cfg(feature = "alloc")] extern crate alloc; @@ -23,8 +23,8 @@ pub mod mem; pub mod value; mod bmc; -mod vmrun; mod utils; +mod vmrun; use {bmc::BlockCopier, value::Value}; diff --git a/hbvm/src/main.rs b/hbvm/src/main.rs deleted file mode 100644 index d23e994e..00000000 --- a/hbvm/src/main.rs +++ /dev/null @@ -1,83 +0,0 @@ -use hbvm::mem::Address; - -use { - hbbytecode::valider::validate, - hbvm::{ - mem::{ - softpaging::{paging::PageTable, HandlePageFault, PageSize, SoftPagedMem}, - MemoryAccessReason, - }, - Vm, - }, - std::io::{stdin, Read}, -}; - -fn main() -> Result<(), Box> { - let mut prog = vec![]; - stdin().read_to_end(&mut prog)?; - - if let Err(e) = validate(&prog) { - eprintln!("Program validation error: {e:?}"); - return Ok(()); - } else { - unsafe { - let mut vm = Vm::<_, 0>::new( - SoftPagedMem::<_, true> { - pf_handler: TestTrapHandler, - program: &prog, - root_pt: Box::into_raw(Default::default()), - icache: Default::default(), - }, - Address::new(4), - ); - let data = { - let ptr = std::alloc::alloc_zeroed(std::alloc::Layout::from_size_align_unchecked( - 4096, 4096, - )); - if ptr.is_null() { - panic!("Alloc error tbhl"); - } - ptr - }; - - vm.memory - .map( - data, - Address::new(8192), - hbvm::mem::softpaging::paging::Permission::Write, - PageSize::Size4K, - ) - .unwrap(); - - println!("Program interrupt: {:?}", vm.run()); - println!("{:?}", vm.registers); - - std::alloc::dealloc( - data, - std::alloc::Layout::from_size_align_unchecked(4096, 4096), - ); - vm.memory.unmap(Address::new(8192)).unwrap(); - let _ = Box::from_raw(vm.memory.root_pt); - } - } - Ok(()) -} - -pub fn time() -> u32 { - 9 -} - -#[derive(Default)] -struct TestTrapHandler; -impl HandlePageFault for TestTrapHandler { - fn page_fault( - &mut self, - _: MemoryAccessReason, - _: &mut PageTable, - _: Address, - _: PageSize, - _: *mut u8, - ) -> bool { - false - } -} diff --git a/hbvm/src/mem/addr.rs b/hbvm/src/mem/addr.rs index ea95ac7a..0191417d 100644 --- a/hbvm/src/mem/addr.rs +++ b/hbvm/src/mem/addr.rs @@ -23,6 +23,18 @@ impl Address { Self(self.0.saturating_sub(rhs.cast_u64())) } + /// Wrapping integer addition. Computes self + rhs, wrapping the numeric bounds. + #[inline] + pub fn wrapping_add(self, rhs: T) -> Self { + Self(self.0.wrapping_add(rhs.cast_u64())) + } + + /// Wrapping integer subtraction. Computes self + rhs, wrapping the numeric bounds. + #[inline] + pub fn wrapping_sub(self, rhs: T) -> Self { + Self(self.0.wrapping_sub(rhs.cast_u64())) + } + /// Cast or if smaller, truncate to [`usize`] pub fn truncate_usize(self) -> usize { self.0 as _ diff --git a/hbvm/src/mem/mod.rs b/hbvm/src/mem/mod.rs index bba5d134..f8d010dc 100644 --- a/hbvm/src/mem/mod.rs +++ b/hbvm/src/mem/mod.rs @@ -2,11 +2,11 @@ pub mod softpaging; -mod addr; +pub(crate) mod addr; pub use addr::Address; -use {crate::utils::impl_display, hbbytecode::ProgramVal}; +use {crate::utils::impl_display, hbbytecode::BytecodeItem}; /// Load-store memory access pub trait Memory { @@ -36,13 +36,13 @@ pub trait Memory { /// /// # Safety /// - Data read have to be valid - unsafe fn prog_read(&mut self, addr: Address) -> Option; + unsafe fn prog_read(&mut self, addr: Address) -> Option; /// Read from program memory to exectue /// /// # Safety /// - You have to be really sure that these bytes are there, understand? - unsafe fn prog_read_unchecked(&mut self, addr: Address) -> T; + unsafe fn prog_read_unchecked(&mut self, addr: Address) -> T; } /// Unhandled load access trap diff --git a/hbvm/src/vmrun.rs b/hbvm/src/vmrun.rs index b0b79d1a..76c2bb4b 100644 --- a/hbvm/src/vmrun.rs +++ b/hbvm/src/vmrun.rs @@ -2,8 +2,6 @@ //! //! Have fun -use crate::mem::Address; - use { super::{ bmc::BlockCopier, @@ -11,9 +9,11 @@ use { value::{Value, ValueVariant}, Vm, VmRunError, VmRunOk, }, + crate::mem::{addr::AddressOp, Address}, core::{cmp::Ordering, mem::size_of, ops}, hbbytecode::{ - ParamBB, ParamBBB, ParamBBBB, ParamBBD, ParamBBDH, ParamBBW, ParamBD, ProgramVal, + BytecodeItem, OpA, OpO, OpsRD, OpsRR, OpsRRAH, OpsRRB, OpsRRD, OpsRRH, OpsRRO, OpsRROH, + OpsRRP, OpsRRR, OpsRRRR, OpsRRW, }, }; @@ -70,14 +70,14 @@ where XOR => self.binary_op::(ops::BitXor::bitxor), SL => self.binary_op(|l, r| u64::wrapping_shl(l, r as u32)), SR => self.binary_op(|l, r| u64::wrapping_shr(l, r as u32)), - SRS => self.binary_op(|l, r| i64::wrapping_shl(l, r as u32)), + SRS => self.binary_op(|l: u64, r| i64::wrapping_shl(l as i64, r as u32) as u64), CMP => { // Compare a0 <=> a1 // < → 0 // > → 1 // = → 2 - let ParamBBB(tg, a0, a1) = self.decode(); + let OpsRRR(tg, a0, a1) = self.decode(); self.write_reg( tg, self.read_reg(a0) @@ -89,7 +89,7 @@ where } CMPU => { // Unsigned comparsion - let ParamBBB(tg, a0, a1) = self.decode(); + let OpsRRR(tg, a0, a1) = self.decode(); self.write_reg( tg, self.read_reg(a0) @@ -101,23 +101,12 @@ where } NOT => { // Logical negation - let ParamBB(tg, a0) = self.decode(); + let OpsRR(tg, a0) = self.decode(); self.write_reg(tg, !self.read_reg(a0).cast::()); } - NEG => { - // Bitwise negation - let ParamBB(tg, a0) = self.decode(); - self.write_reg( - tg, - match self.read_reg(a0).cast::() { - 0 => 1_u64, - _ => 0, - }, - ); - } DIR => { // Fused Division-Remainder - let ParamBBBB(dt, rt, a0, a1) = self.decode(); + let OpsRRRR(dt, rt, a0, a1) = self.decode(); let a0 = self.read_reg(a0).cast::(); let a1 = self.read_reg(a1).cast::(); self.write_reg(dt, a0.checked_div(a1).unwrap_or(u64::MAX)); @@ -132,7 +121,7 @@ where SRI => self.binary_op_ims(u64::wrapping_shr), SRSI => self.binary_op_ims(i64::wrapping_shr), CMPI => { - let ParamBBD(tg, a0, imm) = self.decode(); + let OpsRRD(tg, a0, imm) = self.decode(); self.write_reg( tg, self.read_reg(a0) @@ -142,16 +131,16 @@ where ); } CMPUI => { - let ParamBBD(tg, a0, imm) = self.decode(); + let OpsRRD(tg, a0, imm) = self.decode(); self.write_reg(tg, self.read_reg(a0).cast::().cmp(&imm) as i64); } CP => { - let ParamBB(tg, a0) = self.decode(); + let OpsRR(tg, a0) = self.decode(); self.write_reg(tg, self.read_reg(a0)); } SWA => { // Swap registers - let ParamBB(r0, r1) = self.decode(); + let OpsRR(r0, r1) = self.decode(); match (r0, r1) { (0, 0) => (), (dst, 0) | (0, dst) => self.write_reg(dst, 0_u64), @@ -164,12 +153,16 @@ where } } LI => { - let ParamBD(tg, imm) = self.decode(); + let OpsRD(tg, imm) = self.decode(); self.write_reg(tg, imm); } + LRA => { + let OpsRRO(tg, reg, imm) = self.decode(); + self.write_reg(tg, self.rel_addr(reg, imm).get()); + } LD => { // Load. If loading more than register size, continue on adjecent registers - let ParamBBDH(dst, base, off, count) = self.decode(); + let OpsRRAH(dst, base, off, count) = self.decode(); let n: u8 = match dst { 0 => 1, _ => 0, @@ -181,29 +174,67 @@ where .as_mut_ptr() .add(usize::from(dst) + usize::from(n)) .cast(), - usize::from(count).saturating_sub(n.into()), + usize::from(count).wrapping_sub(n.into()), )?; } ST => { // Store. Same rules apply as to LD - let ParamBBDH(dst, base, off, count) = self.decode(); + let OpsRRAH(dst, base, off, count) = self.decode(); self.memory.store( self.ldst_addr_uber(dst, base, off, count, 0)?, self.registers.as_ptr().add(usize::from(dst)).cast(), count.into(), )?; } + LDR => { + let OpsRROH(dst, base, off, count) = self.decode(); + let n: u8 = match dst { + 0 => 1, + _ => 0, + }; + + self.memory.load( + self.ldst_addr_uber( + dst, + base, + u64::from(off).wrapping_add(self.pc.get()), + count, + n, + )?, + self.registers + .as_mut_ptr() + .add(usize::from(dst) + usize::from(n)) + .cast(), + usize::from(count).wrapping_sub(n.into()), + )?; + } + STR => { + let OpsRROH(dst, base, off, count) = self.decode(); + self.memory.store( + self.ldst_addr_uber( + dst, + base, + u64::from(off).wrapping_add(self.pc.get()), + count, + 0, + )?, + self.registers.as_ptr().add(usize::from(dst)).cast(), + count.into(), + )?; + } BMC => { + const INS_SIZE: usize = size_of::() + 1; + // Block memory copy match if let Some(copier) = &mut self.copier { // There is some copier, poll. copier.poll(&mut self.memory) } else { // There is none, make one! - let ParamBBD(src, dst, count) = self.decode(); + let OpsRRH(src, dst, count) = self.decode(); // So we are still on BMC on next cycle - self.pc -= size_of::() + 1; + self.pc -= INS_SIZE; self.copier = Some(BlockCopier::new( Address::new(self.read_reg(src).cast()), @@ -219,12 +250,12 @@ where // We are done, shift program counter core::task::Poll::Ready(Ok(())) => { self.copier = None; - self.pc += size_of::() + 1; + self.pc += INS_SIZE; } // Error, shift program counter (for consistency) // and yield error core::task::Poll::Ready(Err(e)) => { - self.pc += size_of::() + 1; + self.pc += INS_SIZE; return Err(e.into()); } // Not done yet, proceed to next cycle @@ -233,7 +264,7 @@ where } BRC => { // Block register copy - let ParamBBB(src, dst, count) = self.decode(); + let OpsRRB(src, dst, count) = self.decode(); if src.checked_add(count).is_none() || dst.checked_add(count).is_none() { return Err(VmRunError::RegOutOfBounds); } @@ -244,21 +275,25 @@ where usize::from(count), ); } - JMP => self.pc = Address::new(self.decode::()), + JMP => self.pc = Address::new(self.decode::()), + JMPR => self.pc = self.pc.wrapping_add(self.decode::()), JAL => { // Jump and link. Save PC after this instruction to // specified register and jump to reg + offset. - let ParamBBD(save, reg, offset) = self.decode(); + let OpsRRW(save, reg, offset) = self.decode(); self.write_reg(save, self.pc.get()); - self.pc = - Address::new(self.read_reg(reg).cast::().saturating_add(offset)); + self.pc = Address::new( + self.read_reg(reg).cast::().wrapping_add(offset.into()), + ); } // Conditional jumps, jump only to immediates JEQ => self.cond_jmp::(Ordering::Equal), JNE => { - let ParamBBD(a0, a1, jt) = self.decode(); + let OpsRRP(a0, a1, ja) = self.decode(); if self.read_reg(a0).cast::() != self.read_reg(a1).cast::() { - self.pc = Address::new(jt); + self.pc = Address::new( + ((self.pc.get() as i64).wrapping_add(ja as i64)) as u64, + ) } } JLT => self.cond_jmp::(Ordering::Less), @@ -278,14 +313,14 @@ where SUBF => self.binary_op::(ops::Sub::sub), MULF => self.binary_op::(ops::Mul::mul), DIRF => { - let ParamBBBB(dt, rt, a0, a1) = self.decode(); + let OpsRRRR(dt, rt, a0, a1) = self.decode(); let a0 = self.read_reg(a0).cast::(); let a1 = self.read_reg(a1).cast::(); self.write_reg(dt, a0 / a1); self.write_reg(rt, a0 % a1); } FMAF => { - let ParamBBBB(dt, a0, a1, a2) = self.decode(); + let OpsRRRR(dt, a0, a1, a2) = self.decode(); self.write_reg( dt, self.read_reg(a0).cast::() * self.read_reg(a1).cast::() @@ -293,15 +328,15 @@ where ); } NEGF => { - let ParamBB(dt, a0) = self.decode(); + let OpsRR(dt, a0) = self.decode(); self.write_reg(dt, -self.read_reg(a0).cast::()); } ITF => { - let ParamBB(dt, a0) = self.decode(); + let OpsRR(dt, a0) = self.decode(); self.write_reg(dt, self.read_reg(a0).cast::() as f64); } FTI => { - let ParamBB(dt, a0) = self.decode(); + let OpsRR(dt, a0) = self.decode(); self.write_reg(dt, self.read_reg(a0).cast::() as i64); } ADDFI => self.binary_op_imm::(ops::Add::add), @@ -321,7 +356,7 @@ where /// Decode instruction operands #[inline(always)] - unsafe fn decode(&mut self) -> T { + unsafe fn decode(&mut self) -> T { let pc1 = self.pc + 1_u64; let data = self.memory.prog_read_unchecked::(pc1 as _); self.pc += 1 + size_of::(); @@ -331,7 +366,7 @@ where /// Perform binary operating over two registers #[inline(always)] unsafe fn binary_op(&mut self, op: impl Fn(T, T) -> T) { - let ParamBBB(tg, a0, a1) = self.decode(); + let OpsRRR(tg, a0, a1) = self.decode(); self.write_reg( tg, op(self.read_reg(a0).cast::(), self.read_reg(a1).cast::()), @@ -341,7 +376,7 @@ where /// Perform binary operation over register and immediate #[inline(always)] unsafe fn binary_op_imm(&mut self, op: impl Fn(T, T) -> T) { - let ParamBBD(tg, reg, imm) = self.decode(); + let OpsRRD(tg, reg, imm) = self.decode(); self.write_reg( tg, op(self.read_reg(reg).cast::(), Value::from(imm).cast::()), @@ -351,36 +386,44 @@ where /// Perform binary operation over register and shift immediate #[inline(always)] unsafe fn binary_op_ims(&mut self, op: impl Fn(T, u32) -> T) { - let ParamBBW(tg, reg, imm) = self.decode(); + let OpsRRW(tg, reg, imm) = self.decode(); self.write_reg(tg, op(self.read_reg(reg).cast::(), imm)); } - /// Jump at `#3` if ordering on `#0 <=> #1` is equal to expected + /// Compute address relative to program counter an register value + #[inline(always)] + fn rel_addr(&self, reg: u8, imm: impl AddressOp) -> Address { + self.pc + .wrapping_add(self.read_reg(reg).cast::()) + .wrapping_add(imm) + } + + /// Jump at `PC + #3` if ordering on `#0 <=> #1` is equal to expected #[inline(always)] unsafe fn cond_jmp(&mut self, expected: Ordering) { - let ParamBBD(a0, a1, ja) = self.decode(); + let OpsRRP(a0, a1, ja) = self.decode(); if self .read_reg(a0) .cast::() .cmp(&self.read_reg(a1).cast::()) == expected { - self.pc = Address::new(ja); + self.pc = Address::new(((self.pc.get() as i64).wrapping_add(ja as i64)) as u64); } } /// Read register #[inline(always)] - unsafe fn read_reg(&self, n: u8) -> Value { - *self.registers.get_unchecked(n as usize) + fn read_reg(&self, n: u8) -> Value { + unsafe { *self.registers.get_unchecked(n as usize) } } /// Write a register. /// Writing to register 0 is no-op. #[inline(always)] - unsafe fn write_reg(&mut self, n: u8, value: impl Into) { + fn write_reg(&mut self, n: u8, value: impl Into) { if n != 0 { - *self.registers.get_unchecked_mut(n as usize) = value.into(); + unsafe { *self.registers.get_unchecked_mut(n as usize) = value.into() }; } } diff --git a/hbxrt/Cargo.toml b/hbxrt/Cargo.toml new file mode 100644 index 00000000..d6b1792b --- /dev/null +++ b/hbxrt/Cargo.toml @@ -0,0 +1,7 @@ +[package] +name = "hbxrt" +version = "0.1.0" +edition = "2021" + +[dependencies] +hbvm.path = "../hbvm" diff --git a/hbxrt/src/main.rs b/hbxrt/src/main.rs new file mode 100644 index 00000000..8f5a0216 --- /dev/null +++ b/hbxrt/src/main.rs @@ -0,0 +1,11 @@ +use std::io::{stdin, Read}; + +/// Holey Bytes Experimental Runtime + +fn main() -> Result<(), Box> { + let mut prog = vec![]; + stdin().read_to_end(&mut prog)?; + + eprintln!("WARNING! Bytecode valider has not been yet implemented and running program can lead to undefiend behaviour."); + Ok(()) +} diff --git a/spec.md b/spec.md index 6aee9211..1fc7dd6a 100644 --- a/spec.md +++ b/spec.md @@ -148,6 +148,12 @@ |:------:|:----:|:--------------:| | 29 | LI | Load immediate | +### Load relative address +- Type BBW +| Opcode | Name | Action | +|:------:|:----:|:-----------------------:| +| 30 | LRA | `#0 ← #1 + imm #2 + PC` | + ## Memory operations - Type BBDH - If loaded/store value exceeds one register size, continue accessing following registers @@ -155,8 +161,15 @@ ### Load / Store | Opcode | Name | Action | |:------:|:----:|:---------------------------------------:| -| 30 | LD | `#0 ← [#1 + imm #3], copy imm #4 bytes` | -| 31 | ST | `[#1 + imm #3] ← #0, copy imm #4 bytes` | +| 31 | LD | `#0 ← [#1 + imm #2], copy imm #3 bytes` | +| 32 | ST | `[#1 + imm #2] ← #0, copy imm #3 bytes` | + +### PC relative Load / Store +- Type BBDW +| Opcode | Name | Action | +|:------:|:----:|:--------------------------------------------:| +| 33 | LDR | `#0 ← [#1 + imm #2 + PC], copy imm #3 bytes` | +| 34 | STR | `[#1 + imm #2 + PC] ← #0, copy imm #3 bytes` | ## Block copy - Block copy source and target can overlap @@ -166,7 +179,7 @@ | Opcode | Name | Action | |:------:|:----:|:--------------------------------:| -| 32 | BMC | `[#1] ← [#0], copy imm #2 bytes` | +| 35 | BMC | `[#1] ← [#0], copy imm #2 bytes` | ### Register copy - Type BBB @@ -174,42 +187,44 @@ | Opcode | Name | Action | |:------:|:----:|:--------------------------------:| -| 33 | BRC | `#1 ← #0, copy imm #2 registers` | +| 36 | BRC | `#1 ← #0, copy imm #2 registers` | ## Control flow ### Unconditional jump - Type D -| Opcode | Name | Action | -|:------:|:----:|:-------------------------------:| -| 34 | JMP | Unconditional, non-linking jump | +| Opcode | Name | Action | +|:------:|:----:|:-------------------------------------------:| +| 37 | JMP | Unconditional, non-linking jump | +| 38 | JMPR | Jump at address relative to program counter | ### Unconditional linking jump - Type BBD -| Opcode | Name | Action | -|:------:|:----:|:--------------------------------------------------:| -| 35 | JAL | Save PC past JAL to `#0` and jump at `#1 + imm #2` | +| Opcode | Name | Action | +|:------:|:----:|:-------------------------------------------------------:| +| 39 | JAL | Save PC past JAL to `#0` and jump at `#1 + imm #2` | +| 40 | JALR | Save PC past JAL to `#0` and jump at `#1 + imm #2 + PC` | ### Conditional jumps -- Type BBD -- Jump at `imm #2` if `#0 #1` +- Type BBH +- Jump at `PC + imm #2` if `#0 #1` | Opcode | Name | Comparsion | |:------:|:----:|:------------:| -| 36 | JEQ | = | -| 37 | JNE | ≠ | -| 38 | JLT | < (signed) | -| 39 | JGT | > (signed) | -| 40 | JLTU | < (unsigned) | -| 41 | JGTU | > (unsigned) | +| 41 | JEQ | = | +| 42 | JNE | ≠ | +| 43 | JLT | < (signed) | +| 44 | JGT | > (signed) | +| 45 | JLTU | < (unsigned) | +| 46 | JGTU | > (unsigned) | ### Environment call - Type N | Opcode | Name | Action | |:------:|:-----:|:-------------------------------------:| -| 42 | ECALL | Cause an trap to the host environment | +| 47 | ECALL | Cause an trap to the host environment | ## Floating point operations - Type BBB @@ -217,29 +232,29 @@ | Opcode | Name | Action | |:------:|:----:|:--------------:| -| 43 | ADDF | Addition | -| 44 | SUBF | Subtraction | -| 45 | MULF | Multiplication | +| 48 | ADDF | Addition | +| 49 | SUBF | Subtraction | +| 50 | MULF | Multiplication | ### Division-remainder - Type BBBB | Opcode | Name | Action | |:------:|:----:|:-------------------------:| -| 46 | DIRF | Same as for integer `DIR` | +| 51 | DIRF | Same as for integer `DIR` | ### Fused Multiply-Add - Type BBBB | Opcode | Name | Action | |:------:|:----:|:---------------------:| -| 47 | FMAF | `#0 ← (#1 * #2) + #3` | +| 52 | FMAF | `#0 ← (#1 * #2) + #3` | ### Negation - Type BB | Opcode | Name | Action | |:------:|:----:|:----------:| -| 48 | NEGF | `#0 ← -#1` | +| 53 | NEGF | `#0 ← -#1` | ### Conversion - Type BB @@ -248,8 +263,8 @@ | Opcode | Name | Action | |:------:|:----:|:------------:| -| 49 | ITF | Int to Float | -| 50 | FTI | Float to Int | +| 54 | ITF | Int to Float | +| 55 | FTI | Float to Int | ## Floating point immediate operations - Type BBD @@ -257,8 +272,8 @@ | Opcode | Name | Action | |:------:|:-----:|:--------------:| -| 51 | ADDFI | Addition | -| 52 | MULFI | Multiplication | +| 56 | ADDFI | Addition | +| 57 | MULFI | Multiplication | # Registers - There is 255 registers + one zero register (with index 0) From 2f8612c6d2a5ae138ec7f70ddbea533db637ca00 Mon Sep 17 00:00:00 2001 From: Erin Date: Fri, 29 Sep 2023 09:10:36 +0200 Subject: [PATCH 087/116] Added relaxed relative 16 bit instructions --- hbbytecode/instructions.in | 119 +++++++++++++++++++------------------ hbbytecode/src/lib.rs | 2 + hbvm/src/vmrun.rs | 112 ++++++++++++++++++---------------- 3 files changed, 126 insertions(+), 107 deletions(-) diff --git a/hbbytecode/instructions.in b/hbbytecode/instructions.in index f9edb07d..6bf97e05 100644 --- a/hbbytecode/instructions.in +++ b/hbbytecode/instructions.in @@ -1,64 +1,69 @@ // OPCODE, MNEMONIC, TYPE, DOC; - 0, UN, N, "Cause an unreachable code trap" ; - 1, TX, N, "Termiante execution" ; - 2, NOP, N, "Do nothing" ; + 0, UN, N, "Cause an unreachable code trap" ; + 1, TX, N, "Termiante execution" ; + 2, NOP, N, "Do nothing" ; - 3, ADD, RRR, "Addition" ; - 4, SUB, RRR, "Subtraction" ; - 5, MUL, RRR, "Multiplication" ; - 6, AND, RRR, "Bitand" ; - 7, OR, RRR, "Bitor" ; - 8, XOR, RRR, "Bitxor" ; - 9, SL, RRR, "Unsigned left bitshift" ; -10, SR, RRR, "Unsigned right bitshift" ; -11, SRS, RRR, "Signed right bitshift" ; -12, CMP, RRR, "Signed comparsion" ; -13, CMPU, RRR, "Unsigned comparsion" ; -14, DIR, RRRR, "Merged divide-remainder" ; -15, NOT, RR, "Logical negation" ; -16, ADDI, RRD, "Addition with immediate" ; -17, MULI, RRD, "Multiplication with immediate" ; -18, ANDI, RRD, "Bitand with immediate" ; -19, ORI, RRD, "Bitor with immediate" ; -20, XORI, RRD, "Bitxor with immediate" ; -21, SLI, RRW, "Unsigned left bitshift with immedidate"; -22, SRI, RRW, "Unsigned right bitshift with immediate"; -23, SRSI, RRW, "Signed right bitshift with immediate" ; -24, CMPI, RRD, "Signed compare with immediate" ; -25, CMPUI, RRD, "Unsigned compare with immediate" ; + 3, ADD, RRR, "Addition" ; + 4, SUB, RRR, "Subtraction" ; + 5, MUL, RRR, "Multiplication" ; + 6, AND, RRR, "Bitand" ; + 7, OR, RRR, "Bitor" ; + 8, XOR, RRR, "Bitxor" ; + 9, SL, RRR, "Unsigned left bitshift" ; +10, SR, RRR, "Unsigned right bitshift" ; +11, SRS, RRR, "Signed right bitshift" ; +12, CMP, RRR, "Signed comparsion" ; +13, CMPU, RRR, "Unsigned comparsion" ; +14, DIR, RRRR, "Merged divide-remainder" ; +15, NOT, RR, "Logical negation" ; +16, ADDI, RRD, "Addition with immediate" ; +17, MULI, RRD, "Multiplication with immediate" ; +18, ANDI, RRD, "Bitand with immediate" ; +19, ORI, RRD, "Bitor with immediate" ; +20, XORI, RRD, "Bitxor with immediate" ; +21, SLI, RRW, "Unsigned left bitshift with immedidate"; +22, SRI, RRW, "Unsigned right bitshift with immediate"; +23, SRSI, RRW, "Signed right bitshift with immediate" ; +24, CMPI, RRD, "Signed compare with immediate" ; +25, CMPUI, RRD, "Unsigned compare with immediate" ; -26, CP, RR, "Copy register" ; -27, SWA, RR, "Swap registers" ; -28, LI, RD, "Load immediate" ; -29, LRA, RRO, "Load relative address" ; -30, LD, RRAH, "Load from absolute address" ; -31, ST, RRAH, "Store to absolute address" ; -32, LDR, RROH, "Load from relative address" ; -33, STR, RROH, "Store to absolute address" ; -34, BMC, RRH, "Copy block of memory" ; -35, BRC, RRB, "Copy register block" ; +26, CP, RR, "Copy register" ; +27, SWA, RR, "Swap registers" ; +28, LI, RD, "Load immediate" ; +29, LRA, RRO, "Load relative address" ; +30, LD, RRAH, "Load from absolute address" ; +31, ST, RRAH, "Store to absolute address" ; +32, LDR, RROH, "Load from relative address" ; +33, STR, RROH, "Store to relative address" ; +34, BMC, RRH, "Copy block of memory" ; +35, BRC, RRB, "Copy register block" ; -36, JMP, A, "Absolute jump" ; -37, JMPR, O, "Relative jump" ; -38, JAL, RRA, "Linking absolute jump" ; -39, JALR, RRO, "Linking relative jump" ; -40, JEQ, RRP, "Branch on equal" ; -41, JNE, RRP, "Branch on nonequal" ; -42, JLT, RRP, "Branch on lesser-than (signed)" ; -43, JGT, RRP, "Branch on greater-than (signed)" ; -44, JLTU, RRP, "Branch on lesser-than (unsigned)" ; -45, JGTU, RRP, "Branch on greater-than (unsigned)" ; -46, ECALL, N, "Issue ecall trap" ; +36, JMP, A, "Absolute jump" ; +37, JMPR, O, "Relative jump" ; +38, JAL, RRA, "Linking absolute jump" ; +39, JALR, RRO, "Linking relative jump" ; +40, JEQ, RRP, "Branch on equal" ; +41, JNE, RRP, "Branch on nonequal" ; +42, JLT, RRP, "Branch on lesser-than (signed)" ; +43, JGT, RRP, "Branch on greater-than (signed)" ; +44, JLTU, RRP, "Branch on lesser-than (unsigned)" ; +45, JGTU, RRP, "Branch on greater-than (unsigned)" ; +46, ECALL, N, "Issue ecall trap" ; -47, ADDF, RRR, "Floating addition" ; -48, SUBF, RRR, "Floating subtraction" ; -49, MULF, RRR, "Floating multiply" ; -50, DIRF, RRRR, "Merged floating divide-remainder" ; -51, FMAF, RRRR, "Fused floating multiply-add" ; -52, NEGF, RR, "Floating sign negation" ; -53, ITF, RR, "Int to float" ; -54, FTI, RR, "Float to int" ; +47, ADDF, RRR, "Floating addition" ; +48, SUBF, RRR, "Floating subtraction" ; +49, MULF, RRR, "Floating multiply" ; +50, DIRF, RRRR, "Merged floating divide-remainder" ; +51, FMAF, RRRR, "Fused floating multiply-add" ; +52, NEGF, RR, "Floating sign negation" ; +53, ITF, RR, "Int to float" ; +54, FTI, RR, "Float to int" ; -55, ADDFI, RRD, "Floating addition with immediate" ; -56, MULFI, RRD, "Floating multiplication with immediate"; +55, ADDFI, RRD, "Floating addition with immediate" ; +56, MULFI, RRD, "Floating multiplication with immediate"; + +57, LRA16 , RRP, "Load relative immediate (16 bit)" ; +58, LDR16 , RRPH, "Load from relative address (16 bit)" ; +59, STR16 , RRPH, "Store to relative address (16 bit)" ; +60, JMPR16, P, "Relative jump (16 bit)" ; diff --git a/hbbytecode/src/lib.rs b/hbbytecode/src/lib.rs index 06414f89..45ed510d 100644 --- a/hbbytecode/src/lib.rs +++ b/hbbytecode/src/lib.rs @@ -35,6 +35,7 @@ define_items! { OpsRRD (OpR, OpR, OpD ), OpsRRAH (OpR, OpR, OpA, OpH), OpsRROH (OpR, OpR, OpO, OpH), + OpsRRPH (OpR, OpR, OpP, OpH), OpsRRO (OpR, OpR, OpO ), OpsRRP (OpR, OpR, OpP ), } @@ -42,6 +43,7 @@ define_items! { unsafe impl BytecodeItem for OpA {} unsafe impl BytecodeItem for OpB {} unsafe impl BytecodeItem for OpO {} +unsafe impl BytecodeItem for OpP {} unsafe impl BytecodeItem for () {} ::with_builtin_macros::with_builtin! { diff --git a/hbvm/src/vmrun.rs b/hbvm/src/vmrun.rs index 76c2bb4b..da69627b 100644 --- a/hbvm/src/vmrun.rs +++ b/hbvm/src/vmrun.rs @@ -12,8 +12,8 @@ use { crate::mem::{addr::AddressOp, Address}, core::{cmp::Ordering, mem::size_of, ops}, hbbytecode::{ - BytecodeItem, OpA, OpO, OpsRD, OpsRR, OpsRRAH, OpsRRB, OpsRRD, OpsRRH, OpsRRO, OpsRROH, - OpsRRP, OpsRRR, OpsRRRR, OpsRRW, + BytecodeItem, OpA, OpO, OpP, OpsRD, OpsRR, OpsRRAH, OpsRRB, OpsRRD, OpsRRH, OpsRRO, + OpsRROH, OpsRRP, OpsRRPH, OpsRRR, OpsRRRR, OpsRRW, }, }; @@ -163,64 +163,20 @@ where LD => { // Load. If loading more than register size, continue on adjecent registers let OpsRRAH(dst, base, off, count) = self.decode(); - let n: u8 = match dst { - 0 => 1, - _ => 0, - }; - - self.memory.load( - self.ldst_addr_uber(dst, base, off, count, n)?, - self.registers - .as_mut_ptr() - .add(usize::from(dst) + usize::from(n)) - .cast(), - usize::from(count).wrapping_sub(n.into()), - )?; + self.load(dst, base, off, count)?; } ST => { // Store. Same rules apply as to LD let OpsRRAH(dst, base, off, count) = self.decode(); - self.memory.store( - self.ldst_addr_uber(dst, base, off, count, 0)?, - self.registers.as_ptr().add(usize::from(dst)).cast(), - count.into(), - )?; + self.store(dst, base, off, count)?; } LDR => { let OpsRROH(dst, base, off, count) = self.decode(); - let n: u8 = match dst { - 0 => 1, - _ => 0, - }; - - self.memory.load( - self.ldst_addr_uber( - dst, - base, - u64::from(off).wrapping_add(self.pc.get()), - count, - n, - )?, - self.registers - .as_mut_ptr() - .add(usize::from(dst) + usize::from(n)) - .cast(), - usize::from(count).wrapping_sub(n.into()), - )?; + self.load(dst, base, u64::from(off).wrapping_add(self.pc.get()), count)?; } STR => { let OpsRROH(dst, base, off, count) = self.decode(); - self.memory.store( - self.ldst_addr_uber( - dst, - base, - u64::from(off).wrapping_add(self.pc.get()), - count, - 0, - )?, - self.registers.as_ptr().add(usize::from(dst)).cast(), - count.into(), - )?; + self.store(dst, base, u64::from(off).wrapping_add(self.pc.get()), count)?; } BMC => { const INS_SIZE: usize = size_of::() + 1; @@ -341,6 +297,19 @@ where } ADDFI => self.binary_op_imm::(ops::Add::add), MULFI => self.binary_op_imm::(ops::Mul::mul), + LRA16 => { + let OpsRRP(tg, reg, imm) = self.decode(); + self.write_reg(tg, self.rel_addr(reg, imm).get()); + } + LDR16 => { + let OpsRRPH(dst, base, off, count) = self.decode(); + self.load(dst, base, u64::from(off).wrapping_add(self.pc.get()), count)?; + } + STR16 => { + let OpsRRPH(dst, base, off, count) = self.decode(); + self.store(dst, base, u64::from(off).wrapping_add(self.pc.get()), count)?; + } + JMPR16 => self.pc = self.pc.wrapping_add(self.decode::()), op => return Err(VmRunError::InvalidOpcode(op)), } } @@ -363,6 +332,49 @@ where data } + /// Load + #[inline(always)] + unsafe fn load( + &mut self, + dst: u8, + base: u8, + offset: u64, + count: u16, + ) -> Result<(), VmRunError> { + let n: u8 = match dst { + 0 => 1, + _ => 0, + }; + + self.memory.load( + self.ldst_addr_uber(dst, base, offset, count, n)?, + self.registers + .as_mut_ptr() + .add(usize::from(dst) + usize::from(n)) + .cast(), + usize::from(count).wrapping_sub(n.into()), + )?; + + Ok(()) + } + + /// Store + #[inline(always)] + unsafe fn store( + &mut self, + dst: u8, + base: u8, + offset: u64, + count: u16, + ) -> Result<(), VmRunError> { + self.memory.store( + self.ldst_addr_uber(dst, base, offset, count, 0)?, + self.registers.as_ptr().add(usize::from(dst)).cast(), + count.into(), + )?; + Ok(()) + } + /// Perform binary operating over two registers #[inline(always)] unsafe fn binary_op(&mut self, op: impl Fn(T, T) -> T) { From 441356b6f290d9af9d98f996787c42ee6f19df5c Mon Sep 17 00:00:00 2001 From: Erin Date: Sun, 1 Oct 2023 01:51:51 +0200 Subject: [PATCH 088/116] Sus stuff --- hbbytecode/instructions.in | 97 +++++++++++++++++++------------------- hbvm/src/lib.rs | 3 ++ hbvm/src/vmrun.rs | 16 +++++-- spec.md | 26 +++++----- 4 files changed, 77 insertions(+), 65 deletions(-) diff --git a/hbbytecode/instructions.in b/hbbytecode/instructions.in index 6bf97e05..398a4e38 100644 --- a/hbbytecode/instructions.in +++ b/hbbytecode/instructions.in @@ -1,46 +1,46 @@ // OPCODE, MNEMONIC, TYPE, DOC; - - 0, UN, N, "Cause an unreachable code trap" ; - 1, TX, N, "Termiante execution" ; - 2, NOP, N, "Do nothing" ; - 3, ADD, RRR, "Addition" ; - 4, SUB, RRR, "Subtraction" ; - 5, MUL, RRR, "Multiplication" ; - 6, AND, RRR, "Bitand" ; - 7, OR, RRR, "Bitor" ; - 8, XOR, RRR, "Bitxor" ; - 9, SL, RRR, "Unsigned left bitshift" ; +0, UN, N, "Cause an unreachable code trap" ; +1, TX, N, "Termiante execution" ; +2, NOP, N, "Do nothing" ; + +3, ADD, RRR, "Addition" ; +4, SUB, RRR, "Subtraction" ; +5, MUL, RRR, "Multiplication" ; +6, AND, RRR, "Bitand" ; +7, OR, RRR, "Bitor" ; +8, XOR, RRR, "Bitxor" ; +9, SL, RRR, "Unsigned left bitshift" ; 10, SR, RRR, "Unsigned right bitshift" ; 11, SRS, RRR, "Signed right bitshift" ; 12, CMP, RRR, "Signed comparsion" ; 13, CMPU, RRR, "Unsigned comparsion" ; 14, DIR, RRRR, "Merged divide-remainder" ; +15, NEG, RR, "Bit negation" ; 15, NOT, RR, "Logical negation" ; -16, ADDI, RRD, "Addition with immediate" ; -17, MULI, RRD, "Multiplication with immediate" ; -18, ANDI, RRD, "Bitand with immediate" ; -19, ORI, RRD, "Bitor with immediate" ; -20, XORI, RRD, "Bitxor with immediate" ; -21, SLI, RRW, "Unsigned left bitshift with immedidate"; -22, SRI, RRW, "Unsigned right bitshift with immediate"; -23, SRSI, RRW, "Signed right bitshift with immediate" ; -24, CMPI, RRD, "Signed compare with immediate" ; -25, CMPUI, RRD, "Unsigned compare with immediate" ; +17, ADDI, RRD, "Addition with immediate" ; +18, MULI, RRD, "Multiplication with immediate" ; +19, ANDI, RRD, "Bitand with immediate" ; +20, ORI, RRD, "Bitor with immediate" ; +21, XORI, RRD, "Bitxor with immediate" ; +22, SLI, RRW, "Unsigned left bitshift with immedidate"; +23, SRI, RRW, "Unsigned right bitshift with immediate"; +24, SRSI, RRW, "Signed right bitshift with immediate" ; +25, CMPI, RRD, "Signed compare with immediate" ; +26, CMPUI, RRD, "Unsigned compare with immediate" ; -26, CP, RR, "Copy register" ; -27, SWA, RR, "Swap registers" ; -28, LI, RD, "Load immediate" ; -29, LRA, RRO, "Load relative address" ; -30, LD, RRAH, "Load from absolute address" ; -31, ST, RRAH, "Store to absolute address" ; -32, LDR, RROH, "Load from relative address" ; -33, STR, RROH, "Store to relative address" ; -34, BMC, RRH, "Copy block of memory" ; -35, BRC, RRB, "Copy register block" ; +27, CP, RR, "Copy register" ; +28, SWA, RR, "Swap registers" ; +29, LI, RD, "Load immediate" ; +30, LRA, RRO, "Load relative address" ; +31, LD, RRAH, "Load from absolute address" ; +32, ST, RRAH, "Store to absolute address" ; +33, LDR, RROH, "Load from relative address" ; +34, STR, RROH, "Store to relative address" ; +35, BMC, RRH, "Copy block of memory" ; +36, BRC, RRB, "Copy register block" ; -36, JMP, A, "Absolute jump" ; -37, JMPR, O, "Relative jump" ; +37, JMP, O, "Relative jump" ; 38, JAL, RRA, "Linking absolute jump" ; 39, JALR, RRO, "Linking relative jump" ; 40, JEQ, RRP, "Branch on equal" ; @@ -49,21 +49,22 @@ 43, JGT, RRP, "Branch on greater-than (signed)" ; 44, JLTU, RRP, "Branch on lesser-than (unsigned)" ; 45, JGTU, RRP, "Branch on greater-than (unsigned)" ; -46, ECALL, N, "Issue ecall trap" ; +46, ECA, N, "Environment call trap" ; +47, EBP, N, "Environment breakpoint" ; -47, ADDF, RRR, "Floating addition" ; -48, SUBF, RRR, "Floating subtraction" ; -49, MULF, RRR, "Floating multiply" ; -50, DIRF, RRRR, "Merged floating divide-remainder" ; -51, FMAF, RRRR, "Fused floating multiply-add" ; -52, NEGF, RR, "Floating sign negation" ; -53, ITF, RR, "Int to float" ; -54, FTI, RR, "Float to int" ; +48, ADDF, RRR, "Floating addition" ; +49, SUBF, RRR, "Floating subtraction" ; +50, MULF, RRR, "Floating multiply" ; +51, DIRF, RRRR, "Merged floating divide-remainder" ; +52, FMAF, RRRR, "Fused floating multiply-add" ; +53, NEGF, RR, "Floating sign negation" ; +54, ITF, RR, "Int to float" ; +55, FTI, RR, "Float to int" ; -55, ADDFI, RRD, "Floating addition with immediate" ; -56, MULFI, RRD, "Floating multiplication with immediate"; +56, ADDFI, RRD, "Floating addition with immediate" ; +57, MULFI, RRD, "Floating multiplication with immediate"; -57, LRA16 , RRP, "Load relative immediate (16 bit)" ; -58, LDR16 , RRPH, "Load from relative address (16 bit)" ; -59, STR16 , RRPH, "Store to relative address (16 bit)" ; -60, JMPR16, P, "Relative jump (16 bit)" ; +58, LRA16 , RRP, "Load relative immediate (16 bit)" ; +59, LDR16 , RRPH, "Load from relative address (16 bit)" ; +60, STR16 , RRPH, "Store to relative address (16 bit)" ; +61, JMPR16, P, "Relative jump (16 bit)" ; diff --git a/hbvm/src/lib.rs b/hbvm/src/lib.rs index 2c4391ca..3da37de7 100644 --- a/hbvm/src/lib.rs +++ b/hbvm/src/lib.rs @@ -105,4 +105,7 @@ pub enum VmRunOk { /// Environment call Ecall, + + /// Breakpoint + Breakpoint, } diff --git a/hbvm/src/vmrun.rs b/hbvm/src/vmrun.rs index da69627b..a3d886f0 100644 --- a/hbvm/src/vmrun.rs +++ b/hbvm/src/vmrun.rs @@ -99,10 +99,15 @@ where + 1, ); } + NEG => { + // Bit negation + let OpsRR(tg, a0) = self.decode(); + self.write_reg(tg, !self.read_reg(a0).cast::()) + } NOT => { // Logical negation let OpsRR(tg, a0) = self.decode(); - self.write_reg(tg, !self.read_reg(a0).cast::()); + self.write_reg(tg, u64::from(self.read_reg(a0).cast::() == 0)); } DIR => { // Fused Division-Remainder @@ -231,8 +236,7 @@ where usize::from(count), ); } - JMP => self.pc = Address::new(self.decode::()), - JMPR => self.pc = self.pc.wrapping_add(self.decode::()), + JMP => self.pc = self.pc.wrapping_add(self.decode::()), JAL => { // Jump and link. Save PC after this instruction to // specified register and jump to reg + offset. @@ -256,7 +260,7 @@ where JGT => self.cond_jmp::(Ordering::Greater), JLTU => self.cond_jmp::(Ordering::Less), JGTU => self.cond_jmp::(Ordering::Greater), - ECALL => { + ECA => { self.decode::<()>(); // So we don't get timer interrupt after ECALL @@ -265,6 +269,10 @@ where } return Ok(VmRunOk::Ecall); } + EBP => { + self.decode::<()>(); + return Ok(VmRunOk::Breakpoint); + } ADDF => self.binary_op::(ops::Add::add), SUBF => self.binary_op::(ops::Sub::sub), MULF => self.binary_op::(ops::Mul::mul), diff --git a/spec.md b/spec.md index 1fc7dd6a..bb807804 100644 --- a/spec.md +++ b/spec.md @@ -195,16 +195,15 @@ - Type D | Opcode | Name | Action | |:------:|:----:|:-------------------------------------------:| -| 37 | JMP | Unconditional, non-linking jump | -| 38 | JMPR | Jump at address relative to program counter | +| 37 | JMPR | Jump at address relative to program counter | ### Unconditional linking jump - Type BBD | Opcode | Name | Action | |:------:|:----:|:-------------------------------------------------------:| -| 39 | JAL | Save PC past JAL to `#0` and jump at `#1 + imm #2` | -| 40 | JALR | Save PC past JAL to `#0` and jump at `#1 + imm #2 + PC` | +| 38 | JAL | Save PC past JAL to `#0` and jump at `#1 + imm #2` | +| 39 | JALR | Save PC past JAL to `#0` and jump at `#1 + imm #2 + PC` | ### Conditional jumps - Type BBH @@ -212,19 +211,20 @@ | Opcode | Name | Comparsion | |:------:|:----:|:------------:| -| 41 | JEQ | = | -| 42 | JNE | ≠ | -| 43 | JLT | < (signed) | -| 44 | JGT | > (signed) | -| 45 | JLTU | < (unsigned) | -| 46 | JGTU | > (unsigned) | +| 40 | JEQ | = | +| 41 | JNE | ≠ | +| 42 | JLT | < (signed) | +| 43 | JGT | > (signed) | +| 44 | JLTU | < (unsigned) | +| 45 | JGTU | > (unsigned) | ### Environment call - Type N -| Opcode | Name | Action | -|:------:|:-----:|:-------------------------------------:| -| 47 | ECALL | Cause an trap to the host environment | +| Opcode | Name | Action | +|:------:|:----:|:-------------------------------------:| +| 46 | ECA | Cause an trap to the host environment | +| 47 | EBP | Cause breakproint trap to environment | ## Floating point operations - Type BBB From 59be9068359d86edd7de7dd696d28a6ec7006300 Mon Sep 17 00:00:00 2001 From: Erin Date: Sun, 1 Oct 2023 01:52:26 +0200 Subject: [PATCH 089/116] Sus2 --- hbbytecode/instructions.in | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/hbbytecode/instructions.in b/hbbytecode/instructions.in index 398a4e38..fb125dd7 100644 --- a/hbbytecode/instructions.in +++ b/hbbytecode/instructions.in @@ -1,23 +1,23 @@ // OPCODE, MNEMONIC, TYPE, DOC; -0, UN, N, "Cause an unreachable code trap" ; -1, TX, N, "Termiante execution" ; -2, NOP, N, "Do nothing" ; +0, UN, N, "Cause an unreachable code trap" ; +1, TX, N, "Termiante execution" ; +2, NOP, N, "Do nothing" ; -3, ADD, RRR, "Addition" ; -4, SUB, RRR, "Subtraction" ; -5, MUL, RRR, "Multiplication" ; -6, AND, RRR, "Bitand" ; -7, OR, RRR, "Bitor" ; -8, XOR, RRR, "Bitxor" ; -9, SL, RRR, "Unsigned left bitshift" ; +3, ADD, RRR, "Addition" ; +4, SUB, RRR, "Subtraction" ; +5, MUL, RRR, "Multiplication" ; +6, AND, RRR, "Bitand" ; +7, OR, RRR, "Bitor" ; +8, XOR, RRR, "Bitxor" ; +9, SL, RRR, "Unsigned left bitshift" ; 10, SR, RRR, "Unsigned right bitshift" ; 11, SRS, RRR, "Signed right bitshift" ; 12, CMP, RRR, "Signed comparsion" ; 13, CMPU, RRR, "Unsigned comparsion" ; 14, DIR, RRRR, "Merged divide-remainder" ; 15, NEG, RR, "Bit negation" ; -15, NOT, RR, "Logical negation" ; +16, NOT, RR, "Logical negation" ; 17, ADDI, RRD, "Addition with immediate" ; 18, MULI, RRD, "Multiplication with immediate" ; 19, ANDI, RRD, "Bitand with immediate" ; From 889aefe87adb57129d958ff999d17e9ddd322c71 Mon Sep 17 00:00:00 2001 From: Erin Date: Sun, 1 Oct 2023 16:02:06 +0200 Subject: [PATCH 090/116] Changed relative addressing --- hbbytecode/src/lib.rs | 25 +++-- hbvm/src/vmrun.rs | 215 +++++++++++++++++++++--------------------- 2 files changed, 117 insertions(+), 123 deletions(-) diff --git a/hbbytecode/src/lib.rs b/hbbytecode/src/lib.rs index 45ed510d..2c463ecb 100644 --- a/hbbytecode/src/lib.rs +++ b/hbbytecode/src/lib.rs @@ -1,15 +1,15 @@ #![no_std] -pub type OpR = u8; +type OpR = u8; -pub type OpA = u64; -pub type OpO = u32; -pub type OpP = u16; +type OpA = u64; +type OpO = u32; +type OpP = u16; -pub type OpB = u8; -pub type OpH = u16; -pub type OpW = u32; -pub type OpD = u64; +type OpB = u8; +type OpH = u16; +type OpW = u32; +type OpD = u64; /// # Safety /// Has to be valid to be decoded from bytecode. @@ -38,13 +38,12 @@ define_items! { OpsRRPH (OpR, OpR, OpP, OpH), OpsRRO (OpR, OpR, OpO ), OpsRRP (OpR, OpR, OpP ), + OpsO (OpO, ), + OpsP (OpP, ), + OpsN ( ), } -unsafe impl BytecodeItem for OpA {} -unsafe impl BytecodeItem for OpB {} -unsafe impl BytecodeItem for OpO {} -unsafe impl BytecodeItem for OpP {} -unsafe impl BytecodeItem for () {} +unsafe impl BytecodeItem for u8 {} ::with_builtin_macros::with_builtin! { let $spec = include_from_root!("instructions.in") in { diff --git a/hbvm/src/vmrun.rs b/hbvm/src/vmrun.rs index a3d886f0..e633e24b 100644 --- a/hbvm/src/vmrun.rs +++ b/hbvm/src/vmrun.rs @@ -2,6 +2,8 @@ //! //! Have fun +use hbbytecode::OpsN; + use { super::{ bmc::BlockCopier, @@ -9,14 +11,22 @@ use { value::{Value, ValueVariant}, Vm, VmRunError, VmRunOk, }, - crate::mem::{addr::AddressOp, Address}, - core::{cmp::Ordering, mem::size_of, ops}, + crate::mem::Address, + core::{cmp::Ordering, ops}, hbbytecode::{ - BytecodeItem, OpA, OpO, OpP, OpsRD, OpsRR, OpsRRAH, OpsRRB, OpsRRD, OpsRRH, OpsRRO, - OpsRROH, OpsRRP, OpsRRPH, OpsRRR, OpsRRRR, OpsRRW, + BytecodeItem, OpsO, OpsP, OpsRD, OpsRR, OpsRRAH, OpsRRB, OpsRRD, OpsRRH, OpsRRO, OpsRROH, + OpsRRP, OpsRRPH, OpsRRR, OpsRRRR, OpsRRW, }, }; +macro_rules! handler { + ($self:expr, |$ty:ident ($($ident:pat),* $(,)?)| $expr:expr) => {{ + let $ty($($ident),*) = $self.decode::<$ty>(); + #[allow(clippy::no_effect)] $expr; + $self.bump_pc::<$ty>(); + }}; +} + impl Vm where Mem: Memory, @@ -54,14 +64,14 @@ where .ok_or(VmRunError::ProgramFetchLoadEx(self.pc as _))? { UN => { - self.decode::<()>(); + self.bump_pc::(); return Err(VmRunError::Unreachable); } TX => { - self.decode::<()>(); + self.bump_pc::(); return Ok(VmRunOk::End); } - NOP => self.decode::<()>(), + NOP => handler!(self, |OpsN()| ()), ADD => self.binary_op(u64::wrapping_add), SUB => self.binary_op(u64::wrapping_sub), MUL => self.binary_op(u64::wrapping_mul), @@ -71,13 +81,12 @@ where SL => self.binary_op(|l, r| u64::wrapping_shl(l, r as u32)), SR => self.binary_op(|l, r| u64::wrapping_shr(l, r as u32)), SRS => self.binary_op(|l: u64, r| i64::wrapping_shl(l as i64, r as u32) as u64), - CMP => { + CMP => handler!(self, |OpsRRR(tg, a0, a1)| { // Compare a0 <=> a1 // < → 0 // > → 1 // = → 2 - let OpsRRR(tg, a0, a1) = self.decode(); self.write_reg( tg, self.read_reg(a0) @@ -86,10 +95,9 @@ where as i64 + 1, ); - } - CMPU => { + }), + CMPU => handler!(self, |OpsRRR(tg, a0, a1)| { // Unsigned comparsion - let OpsRRR(tg, a0, a1) = self.decode(); self.write_reg( tg, self.read_reg(a0) @@ -98,25 +106,22 @@ where as i64 + 1, ); - } - NEG => { + }), + NEG => handler!(self, |OpsRR(tg, a0)| { // Bit negation - let OpsRR(tg, a0) = self.decode(); self.write_reg(tg, !self.read_reg(a0).cast::()) - } - NOT => { + }), + NOT => handler!(self, |OpsRR(tg, a0)| { // Logical negation - let OpsRR(tg, a0) = self.decode(); self.write_reg(tg, u64::from(self.read_reg(a0).cast::() == 0)); - } - DIR => { + }), + DIR => handler!(self, |OpsRRRR(dt, rt, a0, a1)| { // Fused Division-Remainder - let OpsRRRR(dt, rt, a0, a1) = self.decode(); let a0 = self.read_reg(a0).cast::(); let a1 = self.read_reg(a1).cast::(); self.write_reg(dt, a0.checked_div(a1).unwrap_or(u64::MAX)); self.write_reg(rt, a0.checked_rem(a1).unwrap_or(u64::MAX)); - } + }), ADDI => self.binary_op_imm(u64::wrapping_add), MULI => self.binary_op_imm(u64::wrapping_sub), ANDI => self.binary_op_imm::(ops::BitAnd::bitand), @@ -125,8 +130,7 @@ where SLI => self.binary_op_ims(u64::wrapping_shl), SRI => self.binary_op_ims(u64::wrapping_shr), SRSI => self.binary_op_ims(i64::wrapping_shr), - CMPI => { - let OpsRRD(tg, a0, imm) = self.decode(); + CMPI => handler!(self, |OpsRRD(tg, a0, imm)| { self.write_reg( tg, self.read_reg(a0) @@ -134,18 +138,15 @@ where .cmp(&Value::from(imm).cast::()) as i64, ); - } - CMPUI => { - let OpsRRD(tg, a0, imm) = self.decode(); + }), + CMPUI => handler!(self, |OpsRRD(tg, a0, imm)| { self.write_reg(tg, self.read_reg(a0).cast::().cmp(&imm) as i64); - } - CP => { - let OpsRR(tg, a0) = self.decode(); + }), + CP => handler!(self, |OpsRR(tg, a0)| { self.write_reg(tg, self.read_reg(a0)); - } - SWA => { + }), + SWA => handler!(self, |OpsRR(r0, r1)| { // Swap registers - let OpsRR(r0, r1) = self.decode(); match (r0, r1) { (0, 0) => (), (dst, 0) | (0, dst) => self.write_reg(dst, 0_u64), @@ -156,36 +157,41 @@ where ); } } - } - LI => { - let OpsRD(tg, imm) = self.decode(); + }), + LI => handler!(self, |OpsRD(tg, imm)| { self.write_reg(tg, imm); - } - LRA => { - let OpsRRO(tg, reg, imm) = self.decode(); - self.write_reg(tg, self.rel_addr(reg, imm).get()); - } - LD => { + }), + LRA => handler!(self, |OpsRRO(tg, reg, imm)| { + self.write_reg( + tg, + (self.pc + self.read_reg(reg).cast::() + imm + 3_u16).get(), + ); + }), + LD => handler!(self, |OpsRRAH(dst, base, off, count)| { // Load. If loading more than register size, continue on adjecent registers - let OpsRRAH(dst, base, off, count) = self.decode(); self.load(dst, base, off, count)?; - } - ST => { + }), + ST => handler!(self, |OpsRRAH(dst, base, off, count)| { // Store. Same rules apply as to LD - let OpsRRAH(dst, base, off, count) = self.decode(); self.store(dst, base, off, count)?; - } - LDR => { - let OpsRROH(dst, base, off, count) = self.decode(); - self.load(dst, base, u64::from(off).wrapping_add(self.pc.get()), count)?; - } - STR => { - let OpsRROH(dst, base, off, count) = self.decode(); - self.store(dst, base, u64::from(off).wrapping_add(self.pc.get()), count)?; - } + }), + LDR => handler!(self, |OpsRROH(dst, base, off, count)| { + self.load( + dst, + base, + u64::from(off).wrapping_add((self.pc + 3_u64).get()), + count, + )?; + }), + STR => handler!(self, |OpsRROH(dst, base, off, count)| { + self.store( + dst, + base, + u64::from(off).wrapping_add((self.pc + 3_u64).get()), + count, + )?; + }), BMC => { - const INS_SIZE: usize = size_of::() + 1; - // Block memory copy match if let Some(copier) = &mut self.copier { // There is some copier, poll. @@ -194,9 +200,6 @@ where // There is none, make one! let OpsRRH(src, dst, count) = self.decode(); - // So we are still on BMC on next cycle - self.pc -= INS_SIZE; - self.copier = Some(BlockCopier::new( Address::new(self.read_reg(src).cast()), Address::new(self.read_reg(dst).cast()), @@ -211,21 +214,19 @@ where // We are done, shift program counter core::task::Poll::Ready(Ok(())) => { self.copier = None; - self.pc += INS_SIZE; + self.bump_pc::(); } // Error, shift program counter (for consistency) // and yield error core::task::Poll::Ready(Err(e)) => { - self.pc += INS_SIZE; return Err(e.into()); } // Not done yet, proceed to next cycle core::task::Poll::Pending => (), } } - BRC => { + BRC => handler!(self, |OpsRRB(src, dst, count)| { // Block register copy - let OpsRRB(src, dst, count) = self.decode(); if src.checked_add(count).is_none() || dst.checked_add(count).is_none() { return Err(VmRunError::RegOutOfBounds); } @@ -235,89 +236,83 @@ where self.registers.get_unchecked_mut(usize::from(dst)), usize::from(count), ); - } - JMP => self.pc = self.pc.wrapping_add(self.decode::()), - JAL => { + }), + JMP => handler!(self, |OpsO(off)| self.pc = self.pc.wrapping_add(off)), + JAL => handler!(self, |OpsRRW(save, reg, offset)| { // Jump and link. Save PC after this instruction to // specified register and jump to reg + offset. - let OpsRRW(save, reg, offset) = self.decode(); self.write_reg(save, self.pc.get()); self.pc = Address::new( self.read_reg(reg).cast::().wrapping_add(offset.into()), ); - } + }), // Conditional jumps, jump only to immediates JEQ => self.cond_jmp::(Ordering::Equal), - JNE => { - let OpsRRP(a0, a1, ja) = self.decode(); + JNE => handler!(self, |OpsRRP(a0, a1, ja)| { if self.read_reg(a0).cast::() != self.read_reg(a1).cast::() { self.pc = Address::new( ((self.pc.get() as i64).wrapping_add(ja as i64)) as u64, ) } - } + }), JLT => self.cond_jmp::(Ordering::Less), JGT => self.cond_jmp::(Ordering::Greater), JLTU => self.cond_jmp::(Ordering::Less), JGTU => self.cond_jmp::(Ordering::Greater), ECA => { - self.decode::<()>(); - // So we don't get timer interrupt after ECALL if TIMER_QUOTIENT != 0 { self.timer = self.timer.wrapping_add(1); } + + self.bump_pc::(); return Ok(VmRunOk::Ecall); } EBP => { - self.decode::<()>(); + self.bump_pc::(); return Ok(VmRunOk::Breakpoint); } ADDF => self.binary_op::(ops::Add::add), SUBF => self.binary_op::(ops::Sub::sub), MULF => self.binary_op::(ops::Mul::mul), - DIRF => { - let OpsRRRR(dt, rt, a0, a1) = self.decode(); + DIRF => handler!(self, |OpsRRRR(dt, rt, a0, a1)| { let a0 = self.read_reg(a0).cast::(); let a1 = self.read_reg(a1).cast::(); self.write_reg(dt, a0 / a1); self.write_reg(rt, a0 % a1); - } - FMAF => { - let OpsRRRR(dt, a0, a1, a2) = self.decode(); + }), + FMAF => handler!(self, |OpsRRRR(dt, a0, a1, a2)| { self.write_reg( dt, self.read_reg(a0).cast::() * self.read_reg(a1).cast::() + self.read_reg(a2).cast::(), ); - } - NEGF => { - let OpsRR(dt, a0) = self.decode(); + }), + NEGF => handler!(self, |OpsRR(dt, a0)| { self.write_reg(dt, -self.read_reg(a0).cast::()); - } - ITF => { - let OpsRR(dt, a0) = self.decode(); + }), + ITF => handler!(self, |OpsRR(dt, a0)| { self.write_reg(dt, self.read_reg(a0).cast::() as f64); - } + }), FTI => { let OpsRR(dt, a0) = self.decode(); self.write_reg(dt, self.read_reg(a0).cast::() as i64); } ADDFI => self.binary_op_imm::(ops::Add::add), MULFI => self.binary_op_imm::(ops::Mul::mul), - LRA16 => { - let OpsRRP(tg, reg, imm) = self.decode(); - self.write_reg(tg, self.rel_addr(reg, imm).get()); - } - LDR16 => { - let OpsRRPH(dst, base, off, count) = self.decode(); + LRA16 => handler!(self, |OpsRRP(tg, reg, imm)| { + self.write_reg( + tg, + (self.pc + self.read_reg(reg).cast::() + imm + 3_u16).get(), + ); + }), + LDR16 => handler!(self, |OpsRRPH(dst, base, off, count)| { self.load(dst, base, u64::from(off).wrapping_add(self.pc.get()), count)?; - } - STR16 => { - let OpsRRPH(dst, base, off, count) = self.decode(); + }), + STR16 => handler!(self, |OpsRRPH(dst, base, off, count)| { self.store(dst, base, u64::from(off).wrapping_add(self.pc.get()), count)?; - } - JMPR16 => self.pc = self.pc.wrapping_add(self.decode::()), + }), + JMPR16 => handler!(self, |OpsP(off)| self.pc = self.pc.wrapping_add(off)), op => return Err(VmRunError::InvalidOpcode(op)), } } @@ -331,13 +326,16 @@ where } } + /// Bump instruction pointer + #[inline(always)] + fn bump_pc(&mut self) { + self.pc = self.pc.wrapping_add(core::mem::size_of::() + 1); + } + /// Decode instruction operands #[inline(always)] unsafe fn decode(&mut self) -> T { - let pc1 = self.pc + 1_u64; - let data = self.memory.prog_read_unchecked::(pc1 as _); - self.pc += 1 + size_of::(); - data + self.memory.prog_read_unchecked::(self.pc + 1_u64) } /// Load @@ -391,6 +389,7 @@ where tg, op(self.read_reg(a0).cast::(), self.read_reg(a1).cast::()), ); + self.bump_pc::(); } /// Perform binary operation over register and immediate @@ -401,6 +400,7 @@ where tg, op(self.read_reg(reg).cast::(), Value::from(imm).cast::()), ); + self.bump_pc::(); } /// Perform binary operation over register and shift immediate @@ -408,14 +408,7 @@ where unsafe fn binary_op_ims(&mut self, op: impl Fn(T, u32) -> T) { let OpsRRW(tg, reg, imm) = self.decode(); self.write_reg(tg, op(self.read_reg(reg).cast::(), imm)); - } - - /// Compute address relative to program counter an register value - #[inline(always)] - fn rel_addr(&self, reg: u8, imm: impl AddressOp) -> Address { - self.pc - .wrapping_add(self.read_reg(reg).cast::()) - .wrapping_add(imm) + self.bump_pc::(); } /// Jump at `PC + #3` if ordering on `#0 <=> #1` is equal to expected @@ -430,6 +423,8 @@ where { self.pc = Address::new(((self.pc.get() as i64).wrapping_add(ja as i64)) as u64); } + + self.bump_pc::(); } /// Read register From 0cb20d5727b83ca6685cc6d49200ff42f849e0e2 Mon Sep 17 00:00:00 2001 From: Erin Date: Wed, 18 Oct 2023 12:14:24 +0200 Subject: [PATCH 091/116] New float instructions, Linux runtime added, some other stuff I forgor --- .cargo/config.toml | 2 + Cargo.lock | 317 ++++++++++++++++++++++++++++++++- Cargo.toml | 2 +- examples/addition.hbf | Bin 0 -> 15 bytes examples/linux-hello.hbf | Bin 0 -> 33 bytes hbbytecode/instructions.in | 190 ++++++++++++-------- hbbytecode/src/lib.rs | 26 +++ hbvm/src/float/aarch64.rs | 66 +++++++ hbvm/src/float/mod.rs | 17 ++ hbvm/src/float/riscv64.rs | 59 ++++++ hbvm/src/float/x86_64.rs | 71 ++++++++ hbvm/src/lib.rs | 8 +- hbvm/src/mem/mod.rs | 10 +- hbvm/src/mem/softpaging/mod.rs | 24 +-- hbvm/src/utils.rs | 6 +- hbvm/src/value.rs | 74 ++++---- hbvm/src/vmrun.rs | 237 +++++++++++++++++------- hbxrt/Cargo.toml | 8 +- hbxrt/src/main.rs | 68 ++++++- hbxrt/src/mem.rs | 31 ++++ xtask/Cargo.toml | 9 + xtask/src/fmt.rs | 93 ++++++++++ xtask/src/main.rs | 25 +++ xtask/src/utils.rs | 19 ++ 24 files changed, 1154 insertions(+), 208 deletions(-) create mode 100644 .cargo/config.toml create mode 100644 examples/addition.hbf create mode 100644 examples/linux-hello.hbf create mode 100644 hbvm/src/float/aarch64.rs create mode 100644 hbvm/src/float/mod.rs create mode 100644 hbvm/src/float/riscv64.rs create mode 100644 hbvm/src/float/x86_64.rs create mode 100644 hbxrt/src/mem.rs create mode 100644 xtask/Cargo.toml create mode 100644 xtask/src/fmt.rs create mode 100644 xtask/src/main.rs create mode 100644 xtask/src/utils.rs diff --git a/.cargo/config.toml b/.cargo/config.toml new file mode 100644 index 00000000..2279e15c --- /dev/null +++ b/.cargo/config.toml @@ -0,0 +1,2 @@ +[alias] +xtask = "r -p xtask --" diff --git a/Cargo.lock b/Cargo.lock index 24aae341..750936be 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,131 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "addr2line" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + +[[package]] +name = "argh" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7af5ba06967ff7214ce4c7419c7d185be7ecd6cc4965a8f6e1d8ce0398aad219" +dependencies = [ + "argh_derive", + "argh_shared", +] + +[[package]] +name = "argh_derive" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56df0aeedf6b7a2fc67d06db35b09684c3e8da0c95f8f27685cb17e08413d87a" +dependencies = [ + "argh_shared", + "proc-macro2", + "quote", + "syn 2.0.38", +] + +[[package]] +name = "argh_shared" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5693f39141bda5760ecc4111ab08da40565d1771038c4a0250f03457ec707531" +dependencies = [ + "serde", +] + +[[package]] +name = "backtrace" +version = "0.3.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837" +dependencies = [ + "addr2line", + "cc", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", +] + +[[package]] +name = "bitflags" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07" + +[[package]] +name = "cc" +version = "1.0.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" +dependencies = [ + "libc", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "color-eyre" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a667583cca8c4f8436db8de46ea8233c42a7d9ae424a82d338f2e4675229204" +dependencies = [ + "backtrace", + "color-spantrace", + "eyre", + "indenter", + "once_cell", + "owo-colors", + "tracing-error", +] + +[[package]] +name = "color-spantrace" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ba75b3d9449ecdccb27ecbc479fdc0b87fa2dd43d2f8298f9bf0e59aacc8dce" +dependencies = [ + "once_cell", + "owo-colors", + "tracing-core", + "tracing-error", +] + +[[package]] +name = "eyre" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c2b6b5a29c02cdc822728b7d7b8ae1bab3e3b05d44522770ddd49722eeac7eb" +dependencies = [ + "indenter", + "once_cell", +] + +[[package]] +name = "gimli" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fb8d784f27acf97159b40fc4db5ecd8aa23b9ad5ef69cdd136d3bc80665f0c0" + [[package]] name = "hbbytecode" version = "0.1.0" @@ -21,13 +146,85 @@ name = "hbxrt" version = "0.1.0" dependencies = [ "hbvm", + "nix", ] [[package]] -name = "proc-macro2" -version = "1.0.67" +name = "indenter" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d433d9f1a3e8c1263d9456598b16fec66f4acc9a74dacffd35c7bb09b3a1328" +checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683" + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "libc" +version = "0.2.149" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a08173bc88b7955d1b3145aa561539096c421ac8debde8cbc3612ec635fee29b" + +[[package]] +name = "memchr" +version = "2.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167" + +[[package]] +name = "miniz_oxide" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" +dependencies = [ + "adler", +] + +[[package]] +name = "nix" +version = "0.27.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2eb04e9c688eff1c89d72b407f168cf79bb9e867a9d3323ed6c01519eb9cc053" +dependencies = [ + "bitflags", + "cfg-if", + "libc", +] + +[[package]] +name = "object" +version = "0.32.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9cf5f9dd3933bd50a9e1f149ec995f39ae2c496d31fd772c1fd45ebc27e902b0" +dependencies = [ + "memchr", +] + +[[package]] +name = "once_cell" +version = "1.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" + +[[package]] +name = "owo-colors" +version = "3.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1b04fb49957986fdce4d6ee7a65027d55d4b6d2265e5848bbb507b58ccfdb6f" + +[[package]] +name = "pin-project-lite" +version = "0.2.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" + +[[package]] +name = "proc-macro2" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "134c189feb4956b20f6f547d2cf727d4c0fe06722b20a0eec87ed445a97f92da" dependencies = [ "unicode-ident", ] @@ -41,6 +238,41 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "rustc-demangle" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" + +[[package]] +name = "serde" +version = "1.0.189" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e422a44e74ad4001bdc8eede9a4570ab52f71190e9c076d14369f38b9200537" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.189" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e48d1f918009ce3145511378cf68d613e3b3d9137d67272562080d68a2b32d5" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.38", +] + +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + [[package]] name = "syn" version = "1.0.109" @@ -52,12 +284,80 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "syn" +version = "2.0.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e96b79aaa137db8f61e26363a0c9b47d8b4ec75da28b7d1d614c2303e232408b" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "thread_local" +version = "1.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fdd6f064ccff2d6567adcb3873ca630700f00b5ad3f060c25b5dcfd9a4ce152" +dependencies = [ + "cfg-if", + "once_cell", +] + +[[package]] +name = "tracing" +version = "0.1.39" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee2ef2af84856a50c1d430afce2fdded0a4ec7eda868db86409b4543df0797f9" +dependencies = [ + "pin-project-lite", + "tracing-core", +] + +[[package]] +name = "tracing-core" +version = "0.1.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-error" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d686ec1c0f384b1277f097b2f279a2ecc11afe8c133c1aabf036a27cb4cd206e" +dependencies = [ + "tracing", + "tracing-subscriber", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30a651bc37f915e81f087d86e62a18eec5f79550c7faff886f7090b4ea757c77" +dependencies = [ + "sharded-slab", + "thread_local", + "tracing-core", +] + [[package]] name = "unicode-ident" version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" +[[package]] +name = "valuable" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" + [[package]] name = "with_builtin_macros" version = "0.0.3" @@ -75,5 +375,14 @@ checksum = "15bd7679c15e22924f53aee34d4e448c45b674feb6129689af88593e129f8f42" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", +] + +[[package]] +name = "xtask" +version = "0.1.0" +dependencies = [ + "argh", + "color-eyre", + "once_cell", ] diff --git a/Cargo.toml b/Cargo.toml index a648dd73..8d81c807 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,3 +1,3 @@ [workspace] resolver = "2" -members = ["hbbytecode", "hbvm", "hbxrt"] +members = ["hbbytecode", "hbvm", "hbxrt", "xtask"] diff --git a/examples/addition.hbf b/examples/addition.hbf new file mode 100644 index 0000000000000000000000000000000000000000..38ffdea5a26a0a5fc31c062d7a0bebeb23a5214b GIT binary patch literal 15 RcmeZZ%w&K7HYP?!MgR%c0KNbK literal 0 HcmV?d00001 diff --git a/examples/linux-hello.hbf b/examples/linux-hello.hbf new file mode 100644 index 0000000000000000000000000000000000000000..d1d8616efb7c432a926c4507458053d9f4ad4cb6 GIT binary patch literal 33 ocmeZZWb|NSEM{h4XYgPVjA8Uh&B@8vQE*JkNp;RIDp2GC0AcC}KmY&$ literal 0 HcmV?d00001 diff --git a/hbbytecode/instructions.in b/hbbytecode/instructions.in index fb125dd7..8e0d1406 100644 --- a/hbbytecode/instructions.in +++ b/hbbytecode/instructions.in @@ -1,70 +1,120 @@ -// OPCODE, MNEMONIC, TYPE, DOC; - -0, UN, N, "Cause an unreachable code trap" ; -1, TX, N, "Termiante execution" ; -2, NOP, N, "Do nothing" ; - -3, ADD, RRR, "Addition" ; -4, SUB, RRR, "Subtraction" ; -5, MUL, RRR, "Multiplication" ; -6, AND, RRR, "Bitand" ; -7, OR, RRR, "Bitor" ; -8, XOR, RRR, "Bitxor" ; -9, SL, RRR, "Unsigned left bitshift" ; -10, SR, RRR, "Unsigned right bitshift" ; -11, SRS, RRR, "Signed right bitshift" ; -12, CMP, RRR, "Signed comparsion" ; -13, CMPU, RRR, "Unsigned comparsion" ; -14, DIR, RRRR, "Merged divide-remainder" ; -15, NEG, RR, "Bit negation" ; -16, NOT, RR, "Logical negation" ; -17, ADDI, RRD, "Addition with immediate" ; -18, MULI, RRD, "Multiplication with immediate" ; -19, ANDI, RRD, "Bitand with immediate" ; -20, ORI, RRD, "Bitor with immediate" ; -21, XORI, RRD, "Bitxor with immediate" ; -22, SLI, RRW, "Unsigned left bitshift with immedidate"; -23, SRI, RRW, "Unsigned right bitshift with immediate"; -24, SRSI, RRW, "Signed right bitshift with immediate" ; -25, CMPI, RRD, "Signed compare with immediate" ; -26, CMPUI, RRD, "Unsigned compare with immediate" ; - -27, CP, RR, "Copy register" ; -28, SWA, RR, "Swap registers" ; -29, LI, RD, "Load immediate" ; -30, LRA, RRO, "Load relative address" ; -31, LD, RRAH, "Load from absolute address" ; -32, ST, RRAH, "Store to absolute address" ; -33, LDR, RROH, "Load from relative address" ; -34, STR, RROH, "Store to relative address" ; -35, BMC, RRH, "Copy block of memory" ; -36, BRC, RRB, "Copy register block" ; - -37, JMP, O, "Relative jump" ; -38, JAL, RRA, "Linking absolute jump" ; -39, JALR, RRO, "Linking relative jump" ; -40, JEQ, RRP, "Branch on equal" ; -41, JNE, RRP, "Branch on nonequal" ; -42, JLT, RRP, "Branch on lesser-than (signed)" ; -43, JGT, RRP, "Branch on greater-than (signed)" ; -44, JLTU, RRP, "Branch on lesser-than (unsigned)" ; -45, JGTU, RRP, "Branch on greater-than (unsigned)" ; -46, ECA, N, "Environment call trap" ; -47, EBP, N, "Environment breakpoint" ; - -48, ADDF, RRR, "Floating addition" ; -49, SUBF, RRR, "Floating subtraction" ; -50, MULF, RRR, "Floating multiply" ; -51, DIRF, RRRR, "Merged floating divide-remainder" ; -52, FMAF, RRRR, "Fused floating multiply-add" ; -53, NEGF, RR, "Floating sign negation" ; -54, ITF, RR, "Int to float" ; -55, FTI, RR, "Float to int" ; - -56, ADDFI, RRD, "Floating addition with immediate" ; -57, MULFI, RRD, "Floating multiplication with immediate"; - -58, LRA16 , RRP, "Load relative immediate (16 bit)" ; -59, LDR16 , RRPH, "Load from relative address (16 bit)" ; -60, STR16 , RRPH, "Store to relative address (16 bit)" ; -61, JMPR16, P, "Relative jump (16 bit)" ; +0x00, UN, N, "Cause an unreachable code trap" ; +0x01, TX, N, "Termiante execution" ; +0x02, NOP, N, "Do nothing" ; +0x03, ADD8, RRR, "Addition (8b)" ; +0x04, ADD16, RRR, "Addition (16b)" ; +0x05, ADD32, RRR, "Addition (32b)" ; +0x06, ADD64, RRR, "Addition (64b)" ; +0x07, SUB8, RRR, "Subtraction (8b)" ; +0x08, SUB16, RRR, "Subtraction (16b)" ; +0x09, SUB32, RRR, "Subtraction (32b)" ; +0x0A, SUB64, RRR, "Subtraction (64b)" ; +0x0B, MUL8, RRR, "Multiplication (8b)" ; +0x0C, MUL16, RRR, "Multiplication (16b)" ; +0x0D, MUL32, RRR, "Multiplication (32b)" ; +0x0E, MUL64, RRR, "Multiplication (64b)" ; +0x0F, AND, RRR, "Bitand" ; +0x10, OR, RRR, "Bitor" ; +0x11, XOR, RRR, "Bitxor" ; +0x12, SLU8, RRR, "Unsigned left bitshift (8b)" ; +0x13, SLU16, RRR, "Unsigned left bitshift (16b)" ; +0x14, SLU32, RRR, "Unsigned left bitshift (32b)" ; +0x15, SLU64, RRR, "Unsigned left bitshift (64b)" ; +0x16, SRU8, RRR, "Unsigned right bitshift (8b)" ; +0x17, SRU16, RRR, "Unsigned right bitshift (16b)" ; +0x18, SRU32, RRR, "Unsigned right bitshift (32b)" ; +0x19, SRU64, RRR, "Unsigned right bitshift (64b)" ; +0x1A, SRS8, RRR, "Signed right bitshift (8b)" ; +0x1B, SRS16, RRR, "Signed right bitshift (16b)" ; +0x1C, SRS32, RRR, "Signed right bitshift (32b)" ; +0x1D, SRS64, RRR, "Signed right bitshift (64b)" ; +0x1E, CMP, RRR, "Signed comparsion" ; +0x1F, CMPU, RRR, "Unsigned comparsion" ; +0x20, DIRU8, RRRR, "Merged divide-remainder (unsigned 8b)" ; +0x21, DIRU16, RRRR, "Merged divide-remainder (unsigned 16b)" ; +0x22, DIRU32, RRRR, "Merged divide-remainder (unsigned 32b)" ; +0x23, DIRU64, RRRR, "Merged divide-remainder (unsigned 64b)" ; +0x24, DIRS8, RRRR, "Merged divide-remainder (signed 8b)" ; +0x25, DIRS16, RRRR, "Merged divide-remainder (signed 16b)" ; +0x26, DIRS32, RRRR, "Merged divide-remainder (signed 32b)" ; +0x27, DIRS64, RRRR, "Merged divide-remainder (signed 64b)" ; +0x28, NEG, RR, "Bit negation" ; +0x29, NOT, RR, "Logical negation" ; +0x2A, SXT8, RR, "Sign extend 8b to 64b" ; +0x2B, SXT16, RR, "Sign extend 16b to 64b" ; +0x2C, SXT32, RR, "Sign extend 32b to 64b" ; +0x2D, ADDI8, RRB, "Addition with immediate (8b)" ; +0x2E, ADDI16, RRH, "Addition with immediate (16b)" ; +0x2F, ADDI32, RRW, "Addition with immediate (32b)" ; +0x30, ADDI64, RRD, "Addition with immediate (64b)" ; +0x31, MULI8, RRW, "Multiplication with immediate (8b)" ; +0x32, MULI16, RRH, "Multiplication with immediate (16b)" ; +0x33, MULI32, RRW, "Multiplication with immediate (32b)" ; +0x34, MULI64, RRD, "Multiplication with immediate (64b)" ; +0x35, ANDI, RRD, "Bitand with immediate" ; +0x36, ORI, RRD, "Bitor with immediate" ; +0x37, XORI, RRD, "Bitxor with immediate" ; +0x38, SLUI8, RRW, "Unsigned left bitshift with immedidate (8b)" ; +0x39, SLUI16, RRW, "Unsigned left bitshift with immedidate (16b)"; +0x3A, SLUI32, RRW, "Unsigned left bitshift with immedidate (32b)"; +0x3B, SLUI64, RRW, "Unsigned left bitshift with immedidate (64b)"; +0x3C, SRUI8, RRW, "Unsigned right bitshift with immediate (8b)" ; +0x3D, SRUI16, RRW, "Unsigned right bitshift with immediate (16b)"; +0x3E, SRUI32, RRW, "Unsigned right bitshift with immediate (32b)"; +0x3F, SRUI64, RRW, "Unsigned right bitshift with immediate (64b)"; +0x40, SRSI8, RRW, "Signed right bitshift with immediate" ; +0x41, SRSI16, RRW, "Signed right bitshift with immediate" ; +0x42, SRSI32, RRW, "Signed right bitshift with immediate" ; +0x43, SRSI64, RRW, "Signed right bitshift with immediate" ; +0x44, CMPI, RRD, "Signed compare with immediate" ; +0x45, CMPUI, RRD, "Unsigned compare with immediate" ; +0x46, CP, RR, "Copy register" ; +0x47, SWA, RR, "Swap registers" ; +0x48, LI8, RB, "Load immediate (8b)" ; +0x49, LI16, RH, "Load immediate (16b)" ; +0x4A, LI32, RW, "Load immediate (32b)" ; +0x4B, LI64, RD, "Load immediate (64b)" ; +0x4C, LRA, RRO, "Load relative address" ; +0x4D, LD, RRAH, "Load from absolute address" ; +0x4E, ST, RRAH, "Store to absolute address" ; +0x4F, LDR, RROH, "Load from relative address" ; +0x50, STR, RROH, "Store to relative address" ; +0x51, BMC, RRH, "Copy block of memory" ; +0x52, BRC, RRB, "Copy register block" ; +0x53, JMP, O, "Relative jump" ; +0x54, JAL, RRA, "Linking absolute jump" ; +0x55, JALR, RRO, "Linking relative jump" ; +0x56, JEQ, RRP, "Branch on equal" ; +0x57, JNE, RRP, "Branch on nonequal" ; +0x58, JLT, RRP, "Branch on lesser-than (signed)" ; +0x59, JGT, RRP, "Branch on greater-than (signed)" ; +0x5A, JLTU, RRP, "Branch on lesser-than (unsigned)" ; +0x5B, JGTU, RRP, "Branch on greater-than (unsigned)" ; +0x5C, ECA, N, "Environment call trap" ; +0x5D, EBP, N, "Environment breakpoint" ; +0x5E, FADD32, RRR, "Floating point addition (32b)" ; +0x5F, FADD64, RRR, "Floating point addition (64b)" ; +0x60, FSUB32, RRR, "Floating point subtraction (32b)" ; +0x61, FSUB64, RRR, "Floating point subtraction (64b)" ; +0x62, FMUL32, RRR, "Floating point multiply (32b)" ; +0x63, FMUL64, RRR, "Floating point multiply (64b)" ; +0x64, FDIV32, RRR, "Floating point division (32b)" ; +0x65, FDIV64, RRR, "Floating point division (64b)" ; +0x66, FMA32, RRR, "Float fused multiply-add (32b)" ; +0x67, FMA64, RRR, "Float fused multiply-add (64b)" ; +0x68, FINV32, RR, "Float reciprocal (32b)" ; +0x69, FINV64, RR, "Float reciprocal (64b)" ; +0x6A, FCMPLT32, RRR, "Flaot compare less than (32b)" ; +0x6B, FCMPLT64, RRR, "Flaot compare less than (64b)" ; +0x6C, FCMPGT32, RRR, "Flaot compare greater than (32b)" ; +0x6D, FCMPGT64, RRR, "Flaot compare greater than (64b)" ; +0x6E, ITF32, RR, "Int to 32 bit float" ; +0x6F, ITF64, RR, "Int to 64 bit float" ; +0x70, FTI32, RRB, "Float 32 to int" ; +0x71, FTI64, RRB, "Float 64 to int" ; +0x72, FC32T64, RR, "Float 64 to Float 32" ; +0x73, FC64T32, RRB, "Float 32 to Float 64" ; +0x74, LRA16, RRP, "Load relative immediate (16 bit)" ; +0x75, LDR16, RRPH, "Load from relative address (16 bit)" ; +0x76, STR16, RRPH, "Store to relative address (16 bit)" ; +0x77, JMP16, P, "Relative jump (16 bit)" ; diff --git a/hbbytecode/src/lib.rs b/hbbytecode/src/lib.rs index 2c463ecb..21a29c68 100644 --- a/hbbytecode/src/lib.rs +++ b/hbbytecode/src/lib.rs @@ -1,5 +1,7 @@ #![no_std] +use core::convert::TryFrom; + type OpR = u8; type OpA = u64; @@ -17,6 +19,7 @@ pub unsafe trait BytecodeItem {} macro_rules! define_items { ($($name:ident ($($item:ident),* $(,)?)),* $(,)?) => { $( + #[derive(Clone, Copy, Debug)] #[repr(packed)] pub struct $name($(pub $item),*); unsafe impl BytecodeItem for $name {} @@ -31,6 +34,9 @@ define_items! { OpsRRB (OpR, OpR, OpB ), OpsRRH (OpR, OpR, OpH ), OpsRRW (OpR, OpR, OpW ), + OpsRB (OpR, OpB ), + OpsRH (OpR, OpH ), + OpsRW (OpR, OpW ), OpsRD (OpR, OpD ), OpsRRD (OpR, OpR, OpD ), OpsRRAH (OpR, OpR, OpA, OpH), @@ -89,4 +95,24 @@ macro_rules! gen_opcodes { }; } +/// Rounding mode +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +#[repr(u8)] +pub enum RoundingMode { + NearestEven = 0, + Truncate = 1, + Up = 2, + Down = 3, +} + +impl TryFrom for RoundingMode { + type Error = (); + + fn try_from(value: u8) -> Result { + (value >= 3) + .then(|| unsafe { core::mem::transmute(value) }) + .ok_or(()) + } +} + invoke_with_def!(gen_opcodes); diff --git a/hbvm/src/float/aarch64.rs b/hbvm/src/float/aarch64.rs new file mode 100644 index 00000000..034105d3 --- /dev/null +++ b/hbvm/src/float/aarch64.rs @@ -0,0 +1,66 @@ +use {core::arch::asm, hbbytecode::RoundingMode}; + +macro_rules! fnsdef { + {$( + $(#[$attr:meta])* + $vis:vis fn $name:ident[$inreg:ident -> $outreg:ident]($from:ident -> $to:ident): $ins:literal; + )*} => {$( + $(#[$attr])* + $vis fn $name(val: $from, mode: RoundingMode) -> $to { + let result: $to; + unsafe { + set_rounding_mode(mode); + asm!( + $ins, + out($outreg) result, + in($inreg) val, + ); + default_rounding_mode(); + } + result + } + )*}; +} + +fnsdef! { + /// Convert [`f64`] to [`f32`] with chosen rounding mode + pub fn conv64to32[vreg -> vreg](f64 -> f32): "fcvt {:s}, {:d}"; + + /// Convert [`f32`] to [`i64`] with chosen rounding mode + pub fn f32toint[vreg -> reg](f32 -> i64): "fcvtzs {}, {:s}"; + + /// Convert [`f64`] to [`i64`] with chosen rounding mode + pub fn f64toint[vreg -> reg](f64 -> i64): "fcvtzs {}, {:d}"; +} + +/// Set rounding mode +/// +/// # Safety +/// - Do not call if rounding mode isn't [`RoundingMode::NearestEven`] +/// - Do not perform any Rust FP operations until reset using +/// [`default_rounding_mode`], you have to rely on inline assembly +#[inline(always)] +unsafe fn set_rounding_mode(mode: RoundingMode) { + if mode == RoundingMode::NearestEven { + return; + } + + let fpcr: u64; + asm!("mrs {}, fpcr", out(reg) fpcr); + + let fpcr = fpcr & !(0b11 << 22) + | (match mode { + RoundingMode::NearestEven => 0b00, + RoundingMode::Truncate => 0b11, + RoundingMode::Up => 0b01, + RoundingMode::Down => 0b10, + }) << 22; + + asm!("msr fpcr, {}", in(reg) fpcr); +} + +#[inline(always)] +unsafe fn default_rounding_mode() { + // I hope so much it gets optimised + set_rounding_mode(RoundingMode::NearestEven); +} diff --git a/hbvm/src/float/mod.rs b/hbvm/src/float/mod.rs new file mode 100644 index 00000000..f8b3ea63 --- /dev/null +++ b/hbvm/src/float/mod.rs @@ -0,0 +1,17 @@ +macro_rules! arch_specific { + { + $({$($cfg:tt)*} : $mod:ident;)* + } => {$( + #[cfg($($cfg)*)] + mod $mod; + + #[cfg($($cfg)*)] + pub use $mod::*; + )*}; +} + +arch_specific! { + {target_arch = "x86_64" }: x86_64; + {target_arch = "riscv64"}: riscv64; + {target_arch = "aarch64"}: aarch64; +} diff --git a/hbvm/src/float/riscv64.rs b/hbvm/src/float/riscv64.rs new file mode 100644 index 00000000..79ba8ef7 --- /dev/null +++ b/hbvm/src/float/riscv64.rs @@ -0,0 +1,59 @@ +use {core::arch::asm, hbbytecode::RoundingMode}; + +macro_rules! roundm_op_litmode_internal { + ($ins:literal, $in:expr, $out:expr => $outy:ident, $mode:literal $(,)?) => { + asm!( + concat!($ins, " {}, {}, ", $mode), + out($outy) $out, + in(freg) $in, + ) + }; +} + +macro_rules! gen_roundm_op_litmode { + [$($ty:ident => $reg:ident),* $(,)?] => { + macro_rules! roundm_op_litmode { + $( + ($ins:literal, $in:expr, $out:expr => $ty, $mode:literal) => { + roundm_op_litmode_internal!($ins, $in, $out => $reg, $mode) + }; + )* + } + }; +} + +gen_roundm_op_litmode![ + f32 => freg, + f64 => freg, + i64 => reg, +]; + +macro_rules! fnsdef { + {$( + $(#[$attr:meta])* + $vis:vis fn $name:ident($from:ident -> $to:ident): $ins:literal; + )*} => {$( + $(#[$attr])* + $vis fn $name(val: $from, mode: RoundingMode) -> $to { + let result: $to; + unsafe { + match mode { + RoundingMode::NearestEven => roundm_op_litmode!($ins, val, result => $to, "rne"), + RoundingMode::Truncate => roundm_op_litmode!($ins, val, result => $to, "rtz"), + RoundingMode::Up => roundm_op_litmode!($ins, val, result => $to, "rup"), + RoundingMode::Down => roundm_op_litmode!($ins, val, result => $to, "rdn"), + } + } + result + } + )*}; +} + +fnsdef! { + /// Convert [`f64`] to [`f32`] with chosen rounding mode + pub fn conv64to32(f64 -> f32): "fcvt.s.d"; + /// Convert [`f32`] to [`i64`] with chosen rounding mode + pub fn f32toint(f32 -> i64): "fcvt.l.s"; + /// Convert [`f64`] to [`i64`] with chosen rounding mode + pub fn f64toint(f64 -> i64): "fcvt.l.d"; +} diff --git a/hbvm/src/float/x86_64.rs b/hbvm/src/float/x86_64.rs new file mode 100644 index 00000000..fd83bf01 --- /dev/null +++ b/hbvm/src/float/x86_64.rs @@ -0,0 +1,71 @@ +use { + core::arch::{asm, x86_64 as arin}, + hbbytecode::RoundingMode, +}; + +macro_rules! gen_op { + [$($ty:ident => $reg:ident),* $(,)?] => { + macro_rules! op { + $( + ($ins:literal, $in:expr, $out:expr => $ty) => { + asm!(concat!($ins, " {}, {}"), out($reg) $out, in(xmm_reg) $in) + }; + )* + } + }; +} + +gen_op![ + f32 => xmm_reg, + f64 => xmm_reg, + i64 => reg, +]; + +macro_rules! fnsdef { + {$( + $(#[$attr:meta])* + $vis:vis fn $name:ident($from:ident -> $to:ident): $ins:literal; + )*} => {$( + $(#[$attr])* + $vis fn $name(val: $from, mode: RoundingMode) -> $to { + let result: $to; + unsafe { + set_rounding_mode(mode); + op!($ins, val, result => $to); + default_rounding_mode(); + } + result + } + )*}; +} + +fnsdef! { + /// Convert [`f64`] to [`f32`] with chosen rounding mode + pub fn conv64to32(f64 -> f32): "cvtsd2ss"; + /// Convert [`f32`] to [`i64`] with chosen rounding mode + pub fn f32toint(f32 -> i64): "cvttss2si"; + /// Convert [`f64`] to [`i64`] with chosen rounding mode + pub fn f64toint(f64 -> i64): "cvttsd2si"; +} + +/// Set rounding mode +/// +/// # Safety +/// - Do not call if rounding mode isn't [`RoundingMode::NearestEven`] +/// - Do not perform any Rust FP operations until reset using +/// [`default_rounding_mode`], you have to rely on inline assembly +#[inline(always)] +unsafe fn set_rounding_mode(mode: RoundingMode) { + arin::_MM_SET_ROUNDING_MODE(match mode { + RoundingMode::NearestEven => return, + RoundingMode::Truncate => arin::_MM_ROUND_TOWARD_ZERO, + RoundingMode::Up => arin::_MM_ROUND_UP, + RoundingMode::Down => arin::_MM_ROUND_DOWN, + }) +} + +#[inline(always)] +fn default_rounding_mode() { + // SAFETY: This is said to be the default mode, do not trust me. + unsafe { arin::_MM_SET_ROUNDING_MODE(arin::_MM_ROUND_NEAREST) }; +} diff --git a/hbvm/src/lib.rs b/hbvm/src/lib.rs index 3da37de7..5b90f83f 100644 --- a/hbvm/src/lib.rs +++ b/hbvm/src/lib.rs @@ -12,7 +12,6 @@ #![no_std] #![cfg_attr(feature = "nightly", feature(fn_align))] -#![warn(missing_docs)] use mem::{Address, Memory}; @@ -23,6 +22,7 @@ pub mod mem; pub mod value; mod bmc; +mod float; mod utils; mod vmrun; @@ -92,6 +92,12 @@ pub enum VmRunError { /// Reached unreachable code Unreachable, + + /// Invalid operand + InvalidOperand, + + /// Unimplemented feature + Unimplemented, } /// Virtual machine halt ok diff --git a/hbvm/src/mem/mod.rs b/hbvm/src/mem/mod.rs index f8d010dc..7387abee 100644 --- a/hbvm/src/mem/mod.rs +++ b/hbvm/src/mem/mod.rs @@ -6,7 +6,7 @@ pub(crate) mod addr; pub use addr::Address; -use {crate::utils::impl_display, hbbytecode::BytecodeItem}; +use crate::utils::impl_display; /// Load-store memory access pub trait Memory { @@ -36,13 +36,7 @@ pub trait Memory { /// /// # Safety /// - Data read have to be valid - unsafe fn prog_read(&mut self, addr: Address) -> Option; - - /// Read from program memory to exectue - /// - /// # Safety - /// - You have to be really sure that these bytes are there, understand? - unsafe fn prog_read_unchecked(&mut self, addr: Address) -> T; + unsafe fn prog_read(&mut self, addr: Address) -> T; } /// Unhandled load access trap diff --git a/hbvm/src/mem/softpaging/mod.rs b/hbvm/src/mem/softpaging/mod.rs index 331ad4d7..3e1f6de9 100644 --- a/hbvm/src/mem/softpaging/mod.rs +++ b/hbvm/src/mem/softpaging/mod.rs @@ -78,31 +78,19 @@ impl<'p, PfH: HandlePageFault, const OUT_PROG_EXEC: bool> Memory } #[inline(always)] - unsafe fn prog_read(&mut self, addr: Address) -> Option { + unsafe fn prog_read(&mut self, addr: Address) -> T { if OUT_PROG_EXEC && addr.truncate_usize() > self.program.len() { - return self.icache.fetch::(addr, self.root_pt); + return self + .icache + .fetch::(addr, self.root_pt) + .unwrap_or_else(|| unsafe { core::mem::zeroed() }); } let addr = addr.truncate_usize(); self.program .get(addr..addr + size_of::()) .map(|x| x.as_ptr().cast::().read()) - } - - #[inline(always)] - unsafe fn prog_read_unchecked(&mut self, addr: Address) -> T { - if OUT_PROG_EXEC && addr.truncate_usize() > self.program.len() { - return self - .icache - .fetch::(addr, self.root_pt) - .unwrap_or_else(|| core::mem::zeroed()); - } - - self.program - .as_ptr() - .add(addr.truncate_usize()) - .cast::() - .read() + .unwrap_or_else(|| unsafe { core::mem::zeroed() }) } } diff --git a/hbvm/src/utils.rs b/hbvm/src/utils.rs index 19830dbf..fbd43af9 100644 --- a/hbvm/src/utils.rs +++ b/hbvm/src/utils.rs @@ -46,8 +46,8 @@ pub(crate) mod internal { pub(crate) use impl_display_match_fragment; } -macro_rules! static_assert_eq(($l:expr, $r:expr $(,)?) => { - const _: [(); ($l != $r) as usize] = []; +macro_rules! static_assert(($expr:expr $(,)?) => { + const _: [(); !$expr as usize] = []; }); -pub(crate) use {impl_display, static_assert_eq}; +pub(crate) use {impl_display, static_assert}; diff --git a/hbvm/src/value.rs b/hbvm/src/value.rs index b72b72b5..fd74b7b6 100644 --- a/hbvm/src/value.rs +++ b/hbvm/src/value.rs @@ -1,35 +1,32 @@ //! HoleyBytes register value definition -/// Define [`Value`] union +use crate::utils::static_assert; + +/// Define [`Value`] »union« (it's fake) /// /// # Safety -/// Union variants have to be sound to byte-reinterpretate +/// Its variants have to be sound to byte-reinterpretate /// between each other. Otherwise the behaviour is undefined. macro_rules! value_def { ($($ty:ident),* $(,)?) => { /// HBVM register value #[derive(Copy, Clone)] - #[repr(packed)] - pub union Value { - $( - #[doc = concat!(stringify!($ty), " type")] - pub $ty: $ty - ),* - } - + #[repr(transparent)] + pub struct Value(pub u64); $( impl From<$ty> for Value { #[inline] fn from(value: $ty) -> Self { - Self { $ty: value } + let mut new = core::mem::MaybeUninit::::zeroed(); + unsafe { + new.as_mut_ptr().cast::<$ty>().write(value); + Self(new.assume_init()) + } } } - crate::utils::static_assert_eq!( - core::mem::size_of::<$ty>(), - core::mem::size_of::(), - ); + static_assert!(core::mem::size_of::<$ty>() <= core::mem::size_of::()); impl private::Sealed for $ty {} unsafe impl ValueVariant for $ty {} @@ -41,23 +38,7 @@ impl Value { /// Byte reinterpret value to target variant #[inline] pub fn cast(self) -> V { - /// Evil. - /// - /// Transmute cannot be performed with generic type - /// as size is unknown, so union is used. - /// - /// # Safety - /// If [`ValueVariant`] implemented correctly, it's fine :) - /// - /// :ferrisClueless: - union Transmute { - /// Self - src: Value, - /// Target variant - variant: Variant, - } - - unsafe { Transmute { src: self }.variant } + unsafe { core::mem::transmute_copy(&self.0) } } } @@ -69,8 +50,8 @@ mod private { pub trait Sealed {} } -value_def!(u64, i64, f64); -crate::utils::static_assert_eq!(core::mem::size_of::(), 8); +value_def!(u8, u16, u32, u64, i8, i16, i32, i64, f32, f64); +static_assert!(core::mem::size_of::() == 8); impl core::fmt::Debug for Value { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { @@ -78,3 +59,28 @@ impl core::fmt::Debug for Value { write!(f, "{:x}", self.cast::()) } } + +pub(crate) trait CheckedDivRem { + fn checked_div(self, other: Self) -> Option + where + Self: Sized; + fn checked_rem(self, other: Self) -> Option + where + Self: Sized; +} + +macro_rules! impl_checked_div_rem { + ($($ty:ty),* $(,)?) => { + $(impl CheckedDivRem for $ty { + #[inline(always)] + fn checked_div(self, another: Self) -> Option + { self.checked_div(another) } + + #[inline(always)] + fn checked_rem(self, another: Self) -> Option + { self.checked_rem(another) } + })* + }; +} + +impl_checked_div_rem!(u8, u16, u32, u64, i8, i16, i32, i64); diff --git a/hbvm/src/vmrun.rs b/hbvm/src/vmrun.rs index e633e24b..965ebab0 100644 --- a/hbvm/src/vmrun.rs +++ b/hbvm/src/vmrun.rs @@ -2,7 +2,7 @@ //! //! Have fun -use hbbytecode::OpsN; +use hbbytecode::RoundingMode; use { super::{ @@ -11,19 +11,20 @@ use { value::{Value, ValueVariant}, Vm, VmRunError, VmRunOk, }, - crate::mem::Address, + crate::{mem::Address, value::CheckedDivRem}, core::{cmp::Ordering, ops}, hbbytecode::{ - BytecodeItem, OpsO, OpsP, OpsRD, OpsRR, OpsRRAH, OpsRRB, OpsRRD, OpsRRH, OpsRRO, OpsRROH, - OpsRRP, OpsRRPH, OpsRRR, OpsRRRR, OpsRRW, + OpsN, OpsO, OpsP, OpsRB, OpsRD, OpsRH, OpsRR, OpsRRAH, OpsRRB, OpsRRD, OpsRRH, OpsRRO, + OpsRROH, OpsRRP, OpsRRPH, OpsRRR, OpsRRRR, OpsRRW, OpsRW, }, }; macro_rules! handler { ($self:expr, |$ty:ident ($($ident:pat),* $(,)?)| $expr:expr) => {{ let $ty($($ident),*) = $self.decode::<$ty>(); - #[allow(clippy::no_effect)] $expr; + #[allow(clippy::no_effect)] let e = $expr; $self.bump_pc::<$ty>(); + e }}; } @@ -58,11 +59,7 @@ where // - Yes, we assume you run 64 bit CPU. Else ?conradluget a better CPU // sorry 8 bit fans, HBVM won't run on your Speccy :( unsafe { - match self - .memory - .prog_read::(self.pc as _) - .ok_or(VmRunError::ProgramFetchLoadEx(self.pc as _))? - { + match self.memory.prog_read::(self.pc as _) { UN => { self.bump_pc::(); return Err(VmRunError::Unreachable); @@ -72,15 +69,32 @@ where return Ok(VmRunOk::End); } NOP => handler!(self, |OpsN()| ()), - ADD => self.binary_op(u64::wrapping_add), - SUB => self.binary_op(u64::wrapping_sub), - MUL => self.binary_op(u64::wrapping_mul), + ADD8 => self.binary_op(u8::wrapping_add), + ADD16 => self.binary_op(u16::wrapping_add), + ADD32 => self.binary_op(u32::wrapping_add), + ADD64 => self.binary_op(u64::wrapping_add), + SUB8 => self.binary_op(u8::wrapping_sub), + SUB16 => self.binary_op(u16::wrapping_sub), + SUB32 => self.binary_op(u32::wrapping_sub), + SUB64 => self.binary_op(u64::wrapping_sub), + MUL8 => self.binary_op(u8::wrapping_mul), + MUL16 => self.binary_op(u16::wrapping_mul), + MUL32 => self.binary_op(u32::wrapping_mul), + MUL64 => self.binary_op(u64::wrapping_mul), AND => self.binary_op::(ops::BitAnd::bitand), OR => self.binary_op::(ops::BitOr::bitor), XOR => self.binary_op::(ops::BitXor::bitxor), - SL => self.binary_op(|l, r| u64::wrapping_shl(l, r as u32)), - SR => self.binary_op(|l, r| u64::wrapping_shr(l, r as u32)), - SRS => self.binary_op(|l: u64, r| i64::wrapping_shl(l as i64, r as u32) as u64), + SLU8 => self.binary_op(|l, r| u8::wrapping_shl(l, r as u32)), + SLU16 => self.binary_op(|l, r| u16::wrapping_shl(l, r as u32)), + SLU32 => self.binary_op(u32::wrapping_shl), + SLU64 => self.binary_op(|l, r| u64::wrapping_shl(l, r as u32)), + SRU8 => self.binary_op(|l, r| u8::wrapping_shr(l, r as u32)), + SRU16 => self.binary_op(|l, r| u16::wrapping_shr(l, r as u32)), + SRU32 => self.binary_op(u32::wrapping_shr), + SRS8 => self.binary_op(|l: i8, r| i8::wrapping_shl(l, r as u32)), + SRS16 => self.binary_op(|l: i16, r| i16::wrapping_shl(l, r as u32)), + SRS32 => self.binary_op(|l: i32, r| i32::wrapping_shl(l, r as u32)), + SRS64 => self.binary_op(|l: i64, r| i64::wrapping_shl(l, r as u32)), CMP => handler!(self, |OpsRRR(tg, a0, a1)| { // Compare a0 <=> a1 // < → 0 @@ -107,6 +121,14 @@ where + 1, ); }), + DIRU8 => self.dir::(), + DIRU16 => self.dir::(), + DIRU32 => self.dir::(), + DIRU64 => self.dir::(), + DIRS8 => self.dir::(), + DIRS16 => self.dir::(), + DIRS32 => self.dir::(), + DIRS64 => self.dir::(), NEG => handler!(self, |OpsRR(tg, a0)| { // Bit negation self.write_reg(tg, !self.read_reg(a0).cast::()) @@ -115,21 +137,38 @@ where // Logical negation self.write_reg(tg, u64::from(self.read_reg(a0).cast::() == 0)); }), - DIR => handler!(self, |OpsRRRR(dt, rt, a0, a1)| { - // Fused Division-Remainder - let a0 = self.read_reg(a0).cast::(); - let a1 = self.read_reg(a1).cast::(); - self.write_reg(dt, a0.checked_div(a1).unwrap_or(u64::MAX)); - self.write_reg(rt, a0.checked_rem(a1).unwrap_or(u64::MAX)); + SXT8 => handler!(self, |OpsRR(tg, a0)| { + self.write_reg(tg, self.read_reg(a0).cast::() as i64) }), - ADDI => self.binary_op_imm(u64::wrapping_add), - MULI => self.binary_op_imm(u64::wrapping_sub), + SXT16 => handler!(self, |OpsRR(tg, a0)| { + self.write_reg(tg, self.read_reg(a0).cast::() as i64) + }), + SXT32 => handler!(self, |OpsRR(tg, a0)| { + self.write_reg(tg, self.read_reg(a0).cast::() as i64) + }), + ADDI8 => self.binary_op_imm(u8::wrapping_add), + ADDI16 => self.binary_op_imm(u16::wrapping_add), + ADDI32 => self.binary_op_imm(u32::wrapping_add), + ADDI64 => self.binary_op_imm(u64::wrapping_add), + MULI8 => self.binary_op_imm(u8::wrapping_sub), + MULI16 => self.binary_op_imm(u16::wrapping_sub), + MULI32 => self.binary_op_imm(u32::wrapping_sub), + MULI64 => self.binary_op_imm(u64::wrapping_sub), ANDI => self.binary_op_imm::(ops::BitAnd::bitand), ORI => self.binary_op_imm::(ops::BitOr::bitor), XORI => self.binary_op_imm::(ops::BitXor::bitxor), - SLI => self.binary_op_ims(u64::wrapping_shl), - SRI => self.binary_op_ims(u64::wrapping_shr), - SRSI => self.binary_op_ims(i64::wrapping_shr), + SLUI8 => self.binary_op_ims::(ops::Shl::shl), + SLUI16 => self.binary_op_ims::(ops::Shl::shl), + SLUI32 => self.binary_op_ims::(ops::Shl::shl), + SLUI64 => self.binary_op_ims::(ops::Shl::shl), + SRUI8 => self.binary_op_ims::(ops::Shr::shr), + SRUI16 => self.binary_op_ims::(ops::Shr::shr), + SRUI32 => self.binary_op_ims::(ops::Shr::shr), + SRUI64 => self.binary_op_ims::(ops::Shr::shr), + SRSI8 => self.binary_op_ims::(ops::Shr::shr), + SRSI16 => self.binary_op_ims::(ops::Shr::shr), + SRSI32 => self.binary_op_ims::(ops::Shr::shr), + SRSI64 => self.binary_op_ims::(ops::Shr::shr), CMPI => handler!(self, |OpsRRD(tg, a0, imm)| { self.write_reg( tg, @@ -158,9 +197,10 @@ where } } }), - LI => handler!(self, |OpsRD(tg, imm)| { - self.write_reg(tg, imm); - }), + LI8 => handler!(self, |OpsRB(tg, imm)| self.write_reg(tg, imm)), + LI16 => handler!(self, |OpsRH(tg, imm)| self.write_reg(tg, imm)), + LI32 => handler!(self, |OpsRW(tg, imm)| self.write_reg(tg, imm)), + LI64 => handler!(self, |OpsRD(tg, imm)| self.write_reg(tg, imm)), LRA => handler!(self, |OpsRRO(tg, reg, imm)| { self.write_reg( tg, @@ -272,34 +312,65 @@ where self.bump_pc::(); return Ok(VmRunOk::Breakpoint); } - ADDF => self.binary_op::(ops::Add::add), - SUBF => self.binary_op::(ops::Sub::sub), - MULF => self.binary_op::(ops::Mul::mul), - DIRF => handler!(self, |OpsRRRR(dt, rt, a0, a1)| { - let a0 = self.read_reg(a0).cast::(); - let a1 = self.read_reg(a1).cast::(); - self.write_reg(dt, a0 / a1); - self.write_reg(rt, a0 % a1); + FADD32 => self.binary_op::(ops::Add::add), + FADD64 => self.binary_op::(ops::Add::add), + FSUB32 => self.binary_op::(ops::Sub::sub), + FSUB64 => self.binary_op::(ops::Sub::sub), + FMUL32 => self.binary_op::(ops::Mul::mul), + FMUL64 => self.binary_op::(ops::Mul::mul), + FDIV32 => self.binary_op::(ops::Div::div), + FDIV64 => self.binary_op::(ops::Div::div), + FMA32 => self.fma::(), + FMA64 => self.fma::(), + FINV32 => handler!(self, |OpsRR(tg, reg)| { + self.write_reg(tg, 1. / self.read_reg(reg).cast::()) }), - FMAF => handler!(self, |OpsRRRR(dt, a0, a1, a2)| { + FINV64 => handler!(self, |OpsRR(tg, reg)| { + self.write_reg(tg, 1. / self.read_reg(reg).cast::()) + }), + FCMPLT32 => self.fcmp::(Ordering::Less), + FCMPLT64 => self.fcmp::(Ordering::Less), + FCMPGT32 => self.fcmp::(Ordering::Greater), + FCMPGT64 => self.fcmp::(Ordering::Greater), + ITF32 => handler!(self, |OpsRR(tg, reg)| { + self.write_reg(tg, self.read_reg(reg).cast::() as f32); + }), + ITF64 => handler!(self, |OpsRR(tg, reg)| { + self.write_reg(tg, self.read_reg(reg).cast::() as f64); + }), + FTI32 => handler!(self, |OpsRRB(tg, reg, mode)| { self.write_reg( - dt, - self.read_reg(a0).cast::() * self.read_reg(a1).cast::() - + self.read_reg(a2).cast::(), + tg, + crate::float::f32toint( + self.read_reg(reg).cast::(), + RoundingMode::try_from(mode) + .map_err(|()| VmRunError::InvalidOperand)?, + ), ); }), - NEGF => handler!(self, |OpsRR(dt, a0)| { - self.write_reg(dt, -self.read_reg(a0).cast::()); + FTI64 => handler!(self, |OpsRRB(tg, reg, mode)| { + self.write_reg( + tg, + crate::float::f64toint( + self.read_reg(reg).cast::(), + RoundingMode::try_from(mode) + .map_err(|()| VmRunError::InvalidOperand)?, + ), + ); }), - ITF => handler!(self, |OpsRR(dt, a0)| { - self.write_reg(dt, self.read_reg(a0).cast::() as f64); + FC32T64 => handler!(self, |OpsRR(tg, reg)| { + self.write_reg(tg, self.read_reg(reg).cast::() as f64); + }), + FC64T32 => handler!(self, |OpsRRB(tg, reg, mode)| { + self.write_reg( + tg, + crate::float::conv64to32( + self.read_reg(reg).cast(), + RoundingMode::try_from(mode) + .map_err(|()| VmRunError::InvalidOperand)?, + ), + ) }), - FTI => { - let OpsRR(dt, a0) = self.decode(); - self.write_reg(dt, self.read_reg(a0).cast::() as i64); - } - ADDFI => self.binary_op_imm::(ops::Add::add), - MULFI => self.binary_op_imm::(ops::Mul::mul), LRA16 => handler!(self, |OpsRRP(tg, reg, imm)| { self.write_reg( tg, @@ -312,7 +383,7 @@ where STR16 => handler!(self, |OpsRRPH(dst, base, off, count)| { self.store(dst, base, u64::from(off).wrapping_add(self.pc.get()), count)?; }), - JMPR16 => handler!(self, |OpsP(off)| self.pc = self.pc.wrapping_add(off)), + JMP16 => handler!(self, |OpsP(off)| self.pc = self.pc.wrapping_add(off)), op => return Err(VmRunError::InvalidOpcode(op)), } } @@ -328,14 +399,14 @@ where /// Bump instruction pointer #[inline(always)] - fn bump_pc(&mut self) { + fn bump_pc(&mut self) { self.pc = self.pc.wrapping_add(core::mem::size_of::() + 1); } /// Decode instruction operands #[inline(always)] - unsafe fn decode(&mut self) -> T { - self.memory.prog_read_unchecked::(self.pc + 1_u64) + unsafe fn decode(&mut self) -> T { + self.memory.prog_read::(self.pc + 1_u64) } /// Load @@ -395,12 +466,11 @@ where /// Perform binary operation over register and immediate #[inline(always)] unsafe fn binary_op_imm(&mut self, op: impl Fn(T, T) -> T) { - let OpsRRD(tg, reg, imm) = self.decode(); - self.write_reg( - tg, - op(self.read_reg(reg).cast::(), Value::from(imm).cast::()), - ); + let OpsRR(tg, reg) = self.decode(); + let imm: T = self.decode(); + self.write_reg(tg, op(self.read_reg(reg).cast::(), imm)); self.bump_pc::(); + self.bump_pc::(); } /// Perform binary operation over register and shift immediate @@ -411,6 +481,51 @@ where self.bump_pc::(); } + /// Fused division-remainder + #[inline(always)] + unsafe fn dir(&mut self) { + handler!(self, |OpsRRRR(td, tr, a0, a1)| { + let a0 = self.read_reg(a0).cast::(); + let a1 = self.read_reg(a1).cast::(); + + if let Some(div) = a0.checked_div(a1) { + self.write_reg(td, div); + } else { + self.write_reg(td, -1_i64); + } + + if let Some(rem) = a0.checked_rem(a1) { + self.write_reg(tr, rem); + } else { + self.write_reg(tr, a0); + } + }); + } + + /// Fused multiply-add + #[inline(always)] + unsafe fn fma(&mut self) + where + T: ValueVariant + core::ops::Mul + core::ops::Add, + { + handler!(self, |OpsRRRR(tg, a0, a1, a2)| { + let a0 = self.read_reg(a0).cast::(); + let a1 = self.read_reg(a1).cast::(); + let a2 = self.read_reg(a2).cast::(); + self.write_reg(tg, a0 * a1 + a2) + }); + } + + /// Float comparsion + #[inline(always)] + unsafe fn fcmp(&mut self, nan: Ordering) { + handler!(self, |OpsRRR(tg, a0, a1)| { + let a0 = self.read_reg(a0).cast::(); + let a1 = self.read_reg(a1).cast::(); + self.write_reg(tg, (a0.partial_cmp(&a1).unwrap_or(nan) as i8 + 1) as u8) + }); + } + /// Jump at `PC + #3` if ordering on `#0 <=> #1` is equal to expected #[inline(always)] unsafe fn cond_jmp(&mut self, expected: Ordering) { diff --git a/hbxrt/Cargo.toml b/hbxrt/Cargo.toml index d6b1792b..0dbf35ea 100644 --- a/hbxrt/Cargo.toml +++ b/hbxrt/Cargo.toml @@ -1,7 +1,9 @@ [package] -name = "hbxrt" -version = "0.1.0" -edition = "2021" +name = "hbxrt" +version = "0.1.0" +edition = "2021" +default-run = "hbxrt" [dependencies] hbvm.path = "../hbvm" +nix = { version = "0.27", features = ["mman"] } diff --git a/hbxrt/src/main.rs b/hbxrt/src/main.rs index 8f5a0216..9be88add 100644 --- a/hbxrt/src/main.rs +++ b/hbxrt/src/main.rs @@ -1,11 +1,69 @@ -use std::io::{stdin, Read}; +//! Holey Bytes Experimental Runtime -/// Holey Bytes Experimental Runtime +mod mem; + +use { + hbvm::{mem::Address, Vm, VmRunOk}, + nix::sys::mman::{mmap, MapFlags, ProtFlags}, + std::{env::args, fs::File, num::NonZeroUsize, process::exit}, +}; fn main() -> Result<(), Box> { - let mut prog = vec![]; - stdin().read_to_end(&mut prog)?; + eprintln!("== HB×RT (Holey Bytes Linux Runtime) v0.1 =="); + eprintln!("[W] Currently supporting only flat images"); + + let Some(image_path) = args().nth(1) else { + eprintln!("[E] Missing image path"); + exit(1); + }; + + // Load program + eprintln!("[I] Loading image from \"{image_path}\""); + let file = File::open(image_path)?; + let ptr = unsafe { + mmap( + None, + NonZeroUsize::new(file.metadata()?.len() as usize).ok_or("File is empty")?, + ProtFlags::PROT_READ, + MapFlags::MAP_PRIVATE, + Some(&file), + 0, + )? + }; + + eprintln!("[I] Image loaded at {ptr:p}"); + + // Execute program + let mut vm = unsafe { Vm::<_, 0>::new(mem::HostMemory, Address::new(ptr as u64)) }; + let stat = loop { + match vm.run() { + Ok(VmRunOk::Breakpoint) => eprintln!( + "[I] Hit breakpoint\nIP: {}\n== Registers ==\n{:?}", + vm.pc, vm.registers + ), + Ok(VmRunOk::Timer) => (), + Ok(VmRunOk::Ecall) => unsafe { + std::arch::asm!( + "syscall", + inlateout("rax") vm.registers[1].0, + in("rdi") vm.registers[2].0, + in("rsi") vm.registers[3].0, + in("rdx") vm.registers[4].0, + in("r10") vm.registers[5].0, + in("r8") vm.registers[6].0, + in("r9") vm.registers[7].0, + ) + }, + Ok(VmRunOk::End) => break Ok(()), + Err(e) => break Err(e), + } + }; + + eprintln!("\n== Registers ==\n{:?}", vm.registers); + if let Err(e) = stat { + eprintln!("\n[E] Runtime error: {e:?}"); + exit(2); + } - eprintln!("WARNING! Bytecode valider has not been yet implemented and running program can lead to undefiend behaviour."); Ok(()) } diff --git a/hbxrt/src/mem.rs b/hbxrt/src/mem.rs new file mode 100644 index 00000000..e1687d31 --- /dev/null +++ b/hbxrt/src/mem.rs @@ -0,0 +1,31 @@ +use hbvm::mem::{Address, LoadError, Memory, StoreError}; + +pub struct HostMemory; +impl Memory for HostMemory { + #[inline] + unsafe fn load( + &mut self, + addr: Address, + target: *mut u8, + count: usize, + ) -> Result<(), LoadError> { + unsafe { core::ptr::copy(addr.get() as *const u8, target, count) } + Ok(()) + } + + #[inline] + unsafe fn store( + &mut self, + addr: Address, + source: *const u8, + count: usize, + ) -> Result<(), StoreError> { + unsafe { core::ptr::copy(source, addr.get() as *mut u8, count) } + Ok(()) + } + + #[inline] + unsafe fn prog_read(&mut self, addr: Address) -> T { + core::ptr::read(addr.get() as *const T) + } +} diff --git a/xtask/Cargo.toml b/xtask/Cargo.toml new file mode 100644 index 00000000..cde8bef2 --- /dev/null +++ b/xtask/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "xtask" +version = "0.1.0" +edition = "2021" + +[dependencies] +argh = "0.1" +color-eyre = "0.6" +once_cell = "1.18" diff --git a/xtask/src/fmt.rs b/xtask/src/fmt.rs new file mode 100644 index 00000000..4cce8d73 --- /dev/null +++ b/xtask/src/fmt.rs @@ -0,0 +1,93 @@ +use { + crate::{utils::IterExt, ROOT}, + argh::FromArgs, + color_eyre::{eyre::eyre, Result}, + std::{ + fs::File, + io::{BufRead, BufReader, BufWriter, Seek, Write}, + }, +}; + +/// Format `instructions.in` +#[derive(Debug, FromArgs, PartialEq)] +#[argh(subcommand, name = "fmt")] +pub struct Command { + /// renumber instructions in their definition order + #[argh(switch, short = 'r')] + renumber: bool, +} + +pub fn command(args: Command) -> Result<()> { + let mut file = File::options() + .read(true) + .write(true) + .open(ROOT.join("hbbytecode/instructions.in"))?; + + // Extract records + let reader = BufReader::new(&file); + let mut recs = vec![]; + let mut lens = [0_usize; 4]; + + for rec in reader.split(b';').filter_map(|r| { + r.map(|ln| { + let s = String::from_utf8_lossy(&ln); + let s = s.trim_matches('\n'); + if s.is_empty() { + return None; + } + + s.split(',') + .map(|s| Box::::from(s.trim())) + .collect_array::<4>() + .map(Ok::<_, ()>) + }) + .transpose() + }) { + let rec = rec?.map_err(|_| eyre!("Invalid record format"))?; + for (current, next) in lens.iter_mut().zip(rec.iter()) { + *current = (*current).max(next.len()); + } + + recs.push(rec); + } + + // Clear file! + file.set_len(0)?; + file.seek(std::io::SeekFrom::Start(0))?; + + let mut writer = BufWriter::new(file); + + let ord_opco_len = digit_count(recs.len()) as usize; + for (n, rec) in recs.iter().enumerate() { + // Write opcode number + if args.renumber { + let n = format!("{n:#04X}"); + write!(writer, "{n}, {}", padding(ord_opco_len, &n))?; + } else { + write!(writer, "{}, {}", rec[0], padding(lens[0], &rec[0]))?; + } + + // Write other fields + writeln!( + writer, + "{}, {}{},{} {}{};", + rec[1], + padding(lens[1], &rec[1]), + rec[2], + padding(lens[2], &rec[2]), + rec[3], + padding(lens[3], &rec[3]), + )?; + } + + Ok(()) +} + +fn padding(req: usize, s: &str) -> Box { + " ".repeat(req.saturating_sub(s.len())).into() +} + +#[inline] +fn digit_count(n: usize) -> u32 { + n.checked_ilog10().unwrap_or(0) + 1 +} diff --git a/xtask/src/main.rs b/xtask/src/main.rs new file mode 100644 index 00000000..8468a8fc --- /dev/null +++ b/xtask/src/main.rs @@ -0,0 +1,25 @@ +mod fmt; +mod utils; + +use {argh::FromArgs, color_eyre::Result, once_cell::sync::Lazy, std::path::Path}; + +static ROOT: Lazy<&Path> = Lazy::new(|| Path::new(env!("CARGO_MANIFEST_DIR")).parent().unwrap()); + +/// xTask for Holey Bytes project +#[derive(FromArgs)] +struct Command { + #[argh(subcommand)] + subcom: Subcommands, +} + +#[derive(FromArgs)] +#[argh(subcommand)] +enum Subcommands { + Format(fmt::Command), +} + +fn main() -> Result<()> { + match argh::from_env::().subcom { + Subcommands::Format(com) => fmt::command(com), + } +} diff --git a/xtask/src/utils.rs b/xtask/src/utils.rs new file mode 100644 index 00000000..8ad0081e --- /dev/null +++ b/xtask/src/utils.rs @@ -0,0 +1,19 @@ +use std::mem::MaybeUninit; + +pub trait IterExt: Iterator { + fn collect_array(&mut self) -> Option<[Self::Item; N]> + where + Self: Sized, + { + let mut array: [MaybeUninit; N] = + unsafe { MaybeUninit::uninit().assume_init() }; + + for item in &mut array { + item.write(self.next()?); + } + + Some(array.map(|item| unsafe { item.assume_init() })) + } +} + +impl IterExt for T {} From 0e701e31b5cd266f58b1b7dd4666aad52bee0e04 Mon Sep 17 00:00:00 2001 From: Erin Date: Fri, 20 Oct 2023 00:12:32 +0200 Subject: [PATCH 092/116] Updated runtime stuff --- Cargo.lock | 324 ++++++++++++++++++++++++++++++++++++++- examples/linux-hello.hbf | Bin 33 -> 33 bytes hbvm/src/vmrun.rs | 26 ++-- hbxrt/Cargo.toml | 3 +- hbxrt/src/main.rs | 66 +++++++- 5 files changed, 399 insertions(+), 20 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 750936be..ec874025 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -17,6 +17,24 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" +[[package]] +name = "aho-corasick" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0" +dependencies = [ + "memchr", +] + +[[package]] +name = "ansi_term" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2" +dependencies = [ + "winapi", +] + [[package]] name = "argh" version = "0.1.12" @@ -48,6 +66,17 @@ dependencies = [ "serde", ] +[[package]] +name = "atty" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +dependencies = [ + "hermit-abi", + "libc", + "winapi", +] + [[package]] name = "backtrace" version = "0.3.69" @@ -56,13 +85,43 @@ checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837" dependencies = [ "addr2line", "cc", - "cfg-if", + "cfg-if 1.0.0", "libc", "miniz_oxide", "object", "rustc-demangle", ] +[[package]] +name = "bindgen" +version = "0.53.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c72a978d268b1d70b0e963217e60fdabd9523a941457a6c42a7315d15c7e89e5" +dependencies = [ + "bitflags 1.3.2", + "cexpr", + "cfg-if 0.1.10", + "clang-sys", + "clap", + "env_logger", + "lazy_static", + "lazycell", + "log", + "peeking_take_while", + "proc-macro2", + "quote", + "regex", + "rustc-hash", + "shlex", + "which", +] + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + [[package]] name = "bitflags" version = "2.4.1" @@ -78,12 +137,53 @@ dependencies = [ "libc", ] +[[package]] +name = "cexpr" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4aedb84272dbe89af497cf81375129abda4fc0a9e7c5d317498c15cc30c0d27" +dependencies = [ + "nom", +] + +[[package]] +name = "cfg-if" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" + [[package]] name = "cfg-if" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "clang-sys" +version = "0.29.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe6837df1d5cba2397b835c8530f51723267e16abbf83892e9e5af4f0e5dd10a" +dependencies = [ + "glob", + "libc", + "libloading", +] + +[[package]] +name = "clap" +version = "2.34.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0610544180c38b88101fecf2dd634b174a62eef6946f84dfc6a7127512b381c" +dependencies = [ + "ansi_term", + "atty", + "bitflags 1.3.2", + "strsim", + "textwrap", + "unicode-width", + "vec_map", +] + [[package]] name = "color-eyre" version = "0.6.2" @@ -111,6 +211,19 @@ dependencies = [ "tracing-error", ] +[[package]] +name = "env_logger" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44533bbbb3bb3c1fa17d9f2e4e38bbbaf8396ba82193c4cb1b6445d711445d36" +dependencies = [ + "atty", + "humantime", + "log", + "regex", + "termcolor", +] + [[package]] name = "eyre" version = "0.6.8" @@ -127,6 +240,12 @@ version = "0.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6fb8d784f27acf97159b40fc4db5ecd8aa23b9ad5ef69cdd136d3bc80665f0c0" +[[package]] +name = "glob" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" + [[package]] name = "hbbytecode" version = "0.1.0" @@ -147,6 +266,25 @@ version = "0.1.0" dependencies = [ "hbvm", "nix", + "setjmp", +] + +[[package]] +name = "hermit-abi" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +dependencies = [ + "libc", +] + +[[package]] +name = "humantime" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df004cfca50ef23c36850aaaa59ad52cc70d0e90243c3c7737a4dd32dc7a3c4f" +dependencies = [ + "quick-error", ] [[package]] @@ -161,12 +299,34 @@ version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +[[package]] +name = "lazycell" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" + [[package]] name = "libc" version = "0.2.149" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a08173bc88b7955d1b3145aa561539096c421ac8debde8cbc3612ec635fee29b" +[[package]] +name = "libloading" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2b111a074963af1d37a139918ac6d49ad1d0d5e47f72fd55388619691a7d753" +dependencies = [ + "cc", + "winapi", +] + +[[package]] +name = "log" +version = "0.4.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" + [[package]] name = "memchr" version = "2.6.4" @@ -188,11 +348,21 @@ version = "0.27.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2eb04e9c688eff1c89d72b407f168cf79bb9e867a9d3323ed6c01519eb9cc053" dependencies = [ - "bitflags", - "cfg-if", + "bitflags 2.4.1", + "cfg-if 1.0.0", "libc", ] +[[package]] +name = "nom" +version = "5.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08959a387a676302eebf4ddbcbc611da04285579f76f88ee0506c63b1a61dd4b" +dependencies = [ + "memchr", + "version_check", +] + [[package]] name = "object" version = "0.32.1" @@ -214,6 +384,12 @@ version = "3.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c1b04fb49957986fdce4d6ee7a65027d55d4b6d2265e5848bbb507b58ccfdb6f" +[[package]] +name = "peeking_take_while" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099" + [[package]] name = "pin-project-lite" version = "0.2.13" @@ -229,6 +405,12 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "quick-error" +version = "1.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" + [[package]] name = "quote" version = "1.0.33" @@ -238,12 +420,47 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "regex" +version = "1.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "380b951a9c5e80ddfd6136919eef32310721aa4aacd4889a8d39124b026ab343" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f804c7828047e88b2d32e2d7fe5a105da8ee3264f01902f796c8e067dc2483f" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" + [[package]] name = "rustc-demangle" version = "0.1.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + [[package]] name = "serde" version = "1.0.189" @@ -264,6 +481,17 @@ dependencies = [ "syn 2.0.38", ] +[[package]] +name = "setjmp" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9bce8e042e9b4349ccf7ce5caeb5c9b7ee6007ff543b494733ae8ed4ea083c73" +dependencies = [ + "bindgen", + "clang-sys", + "libc", +] + [[package]] name = "sharded-slab" version = "0.1.7" @@ -273,6 +501,18 @@ dependencies = [ "lazy_static", ] +[[package]] +name = "shlex" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fdf1b9db47230893d76faad238fd6097fd6d6a9245cd7a4d90dbd639536bbd2" + +[[package]] +name = "strsim" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" + [[package]] name = "syn" version = "1.0.109" @@ -295,13 +535,31 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "termcolor" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6093bad37da69aab9d123a8091e4be0aa4a03e4d601ec641c327398315f62b64" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "textwrap" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" +dependencies = [ + "unicode-width", +] + [[package]] name = "thread_local" version = "1.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3fdd6f064ccff2d6567adcb3873ca630700f00b5ad3f060c25b5dcfd9a4ce152" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", "once_cell", ] @@ -352,12 +610,70 @@ version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" +[[package]] +name = "unicode-width" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85" + [[package]] name = "valuable" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" +[[package]] +name = "vec_map" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "which" +version = "3.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d011071ae14a2f6671d0b74080ae0cd8ebf3a6f8c9589a2cd45f23126fe29724" +dependencies = [ + "libc", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596" +dependencies = [ + "winapi", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + [[package]] name = "with_builtin_macros" version = "0.0.3" diff --git a/examples/linux-hello.hbf b/examples/linux-hello.hbf index d1d8616efb7c432a926c4507458053d9f4ad4cb6..1b962c96655799cbe8bf9774e988c69ef0d9ffab 100644 GIT binary patch literal 33 ocmeZZWb|NSEMaC~XYgPVjA8Uh&B@8vQE*JkNp;RIDp2GC0Ad9PK>z>% literal 33 ocmeZZWb|NSEM{h4XYgPVjA8Uh&B@8vQE*JkNp;RIDp2GC0AcC}KmY&$ diff --git a/hbvm/src/vmrun.rs b/hbvm/src/vmrun.rs index 965ebab0..37b6db8f 100644 --- a/hbvm/src/vmrun.rs +++ b/hbvm/src/vmrun.rs @@ -23,7 +23,7 @@ macro_rules! handler { ($self:expr, |$ty:ident ($($ident:pat),* $(,)?)| $expr:expr) => {{ let $ty($($ident),*) = $self.decode::<$ty>(); #[allow(clippy::no_effect)] let e = $expr; - $self.bump_pc::<$ty>(); + $self.bump_pc::<$ty, true>(); e }}; } @@ -61,11 +61,11 @@ where unsafe { match self.memory.prog_read::(self.pc as _) { UN => { - self.bump_pc::(); + self.bump_pc::(); return Err(VmRunError::Unreachable); } TX => { - self.bump_pc::(); + self.bump_pc::(); return Ok(VmRunOk::End); } NOP => handler!(self, |OpsN()| ()), @@ -254,7 +254,7 @@ where // We are done, shift program counter core::task::Poll::Ready(Ok(())) => { self.copier = None; - self.bump_pc::(); + self.bump_pc::(); } // Error, shift program counter (for consistency) // and yield error @@ -305,11 +305,11 @@ where self.timer = self.timer.wrapping_add(1); } - self.bump_pc::(); + self.bump_pc::(); return Ok(VmRunOk::Ecall); } EBP => { - self.bump_pc::(); + self.bump_pc::(); return Ok(VmRunOk::Breakpoint); } FADD32 => self.binary_op::(ops::Add::add), @@ -399,8 +399,8 @@ where /// Bump instruction pointer #[inline(always)] - fn bump_pc(&mut self) { - self.pc = self.pc.wrapping_add(core::mem::size_of::() + 1); + fn bump_pc(&mut self) { + self.pc = self.pc.wrapping_add(core::mem::size_of::() + PAST_OP as usize); } /// Decode instruction operands @@ -460,7 +460,7 @@ where tg, op(self.read_reg(a0).cast::(), self.read_reg(a1).cast::()), ); - self.bump_pc::(); + self.bump_pc::(); } /// Perform binary operation over register and immediate @@ -469,8 +469,8 @@ where let OpsRR(tg, reg) = self.decode(); let imm: T = self.decode(); self.write_reg(tg, op(self.read_reg(reg).cast::(), imm)); - self.bump_pc::(); - self.bump_pc::(); + self.bump_pc::(); + self.bump_pc::(); } /// Perform binary operation over register and shift immediate @@ -478,7 +478,7 @@ where unsafe fn binary_op_ims(&mut self, op: impl Fn(T, u32) -> T) { let OpsRRW(tg, reg, imm) = self.decode(); self.write_reg(tg, op(self.read_reg(reg).cast::(), imm)); - self.bump_pc::(); + self.bump_pc::(); } /// Fused division-remainder @@ -539,7 +539,7 @@ where self.pc = Address::new(((self.pc.get() as i64).wrapping_add(ja as i64)) as u64); } - self.bump_pc::(); + self.bump_pc::(); } /// Read register diff --git a/hbxrt/Cargo.toml b/hbxrt/Cargo.toml index 0dbf35ea..6831f3ec 100644 --- a/hbxrt/Cargo.toml +++ b/hbxrt/Cargo.toml @@ -6,4 +6,5 @@ default-run = "hbxrt" [dependencies] hbvm.path = "../hbvm" -nix = { version = "0.27", features = ["mman"] } +nix = { version = "0.27", features = ["mman", "signal"] } +setjmp = "0.1" diff --git a/hbxrt/src/main.rs b/hbxrt/src/main.rs index 9be88add..e10194b3 100644 --- a/hbxrt/src/main.rs +++ b/hbxrt/src/main.rs @@ -1,11 +1,13 @@ //! Holey Bytes Experimental Runtime - mod mem; use { hbvm::{mem::Address, Vm, VmRunOk}, nix::sys::mman::{mmap, MapFlags, ProtFlags}, - std::{env::args, fs::File, num::NonZeroUsize, process::exit}, + setjmp::sigjmp_buf, + std::{ + cell::UnsafeCell, env::args, fs::File, mem::MaybeUninit, num::NonZeroUsize, process::exit, + }, }; fn main() -> Result<(), Box> { @@ -35,6 +37,44 @@ fn main() -> Result<(), Box> { // Execute program let mut vm = unsafe { Vm::<_, 0>::new(mem::HostMemory, Address::new(ptr as u64)) }; + + // Memory access fault handling + unsafe { + use nix::sys::signal; + + static JMP_BUF: SyncUnsafeCell> = + SyncUnsafeCell::new(MaybeUninit::uninit()); + + extern "C" fn action( + _: std::ffi::c_int, + info: *mut nix::libc::siginfo_t, + _: *mut std::ffi::c_void, + ) { + unsafe { + eprintln!("[E] Memory access fault at {:p}", (*info).si_addr()); + setjmp::siglongjmp((*JMP_BUF.get()).as_mut_ptr(), 1); + } + } + + if setjmp::sigsetjmp((*JMP_BUF.get()).as_mut_ptr(), 0) > 0 { + eprintln!( + " Program counter: {:#x}\n\n== Registers ==\n{:?}", + vm.pc.get(), + vm.registers + ); + exit(3); + } + + signal::sigaction( + signal::Signal::SIGSEGV, + &nix::sys::signal::SigAction::new( + signal::SigHandler::SigAction(action), + signal::SaFlags::SA_NODEFER, + nix::sys::signalfd::SigSet::empty(), + ), + )?; + } + let stat = loop { match vm.run() { Ok(VmRunOk::Breakpoint) => eprintln!( @@ -67,3 +107,25 @@ fn main() -> Result<(), Box> { Ok(()) } + +#[repr(transparent)] +struct SyncUnsafeCell { + value: UnsafeCell, +} + +unsafe impl Sync for SyncUnsafeCell {} +impl SyncUnsafeCell { + #[inline(always)] + pub const fn new(value: T) -> Self { + Self { + value: UnsafeCell::new(value), + } + } +} + +impl SyncUnsafeCell { + #[inline(always)] + pub const fn get(&self) -> *mut T { + self.value.get() + } +} From a944a145ed6b16ac25eb692b6de409ad297cd7ec Mon Sep 17 00:00:00 2001 From: Erin Date: Fri, 20 Oct 2023 00:42:45 +0200 Subject: [PATCH 093/116] relative JAL --- hbbytecode/instructions.in | 4 ++-- hbvm/src/vmrun.rs | 15 +++++++++++++-- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/hbbytecode/instructions.in b/hbbytecode/instructions.in index 8e0d1406..7a74d294 100644 --- a/hbbytecode/instructions.in +++ b/hbbytecode/instructions.in @@ -82,8 +82,8 @@ 0x51, BMC, RRH, "Copy block of memory" ; 0x52, BRC, RRB, "Copy register block" ; 0x53, JMP, O, "Relative jump" ; -0x54, JAL, RRA, "Linking absolute jump" ; -0x55, JALR, RRO, "Linking relative jump" ; +0x54, JAL, RRO, "Linking relative jump" ; +0x55, JALA, RRA, "Linking absolute jump" ; 0x56, JEQ, RRP, "Branch on equal" ; 0x57, JNE, RRP, "Branch on nonequal" ; 0x58, JLT, RRP, "Branch on lesser-than (signed)" ; diff --git a/hbvm/src/vmrun.rs b/hbvm/src/vmrun.rs index 37b6db8f..98e549c8 100644 --- a/hbvm/src/vmrun.rs +++ b/hbvm/src/vmrun.rs @@ -280,7 +280,16 @@ where JMP => handler!(self, |OpsO(off)| self.pc = self.pc.wrapping_add(off)), JAL => handler!(self, |OpsRRW(save, reg, offset)| { // Jump and link. Save PC after this instruction to - // specified register and jump to reg + offset. + // specified register and jump to reg + relative offset. + self.write_reg(save, self.pc.get()); + self.pc = self + .pc + .wrapping_add(self.read_reg(reg).cast::()) + .wrapping_add(offset); + }), + JALA => handler!(self, |OpsRRW(save, reg, offset)| { + // Jump and link. Save PC after this instruction to + // specified register and jump to reg self.write_reg(save, self.pc.get()); self.pc = Address::new( self.read_reg(reg).cast::().wrapping_add(offset.into()), @@ -400,7 +409,9 @@ where /// Bump instruction pointer #[inline(always)] fn bump_pc(&mut self) { - self.pc = self.pc.wrapping_add(core::mem::size_of::() + PAST_OP as usize); + self.pc = self + .pc + .wrapping_add(core::mem::size_of::() + PAST_OP as usize); } /// Decode instruction operands From 4b45407a70e9032b5431c700a5027af66f6de0e5 Mon Sep 17 00:00:00 2001 From: Erin Date: Fri, 20 Oct 2023 02:05:00 +0200 Subject: [PATCH 094/116] Added test example --- examples/asm/hello-name.hba | 100 ++++++++++++++++++++++++ examples/{ => bytecode}/addition.hbf | Bin examples/{ => bytecode}/linux-hello.hbf | Bin 3 files changed, 100 insertions(+) create mode 100644 examples/asm/hello-name.hba rename examples/{ => bytecode}/addition.hbf (100%) rename examples/{ => bytecode}/linux-hello.hbf (100%) diff --git a/examples/asm/hello-name.hba b/examples/asm/hello-name.hba new file mode 100644 index 00000000..ee5f92a4 --- /dev/null +++ b/examples/asm/hello-name.hba @@ -0,0 +1,100 @@ +jmp entry + +puts: +-- Write string to console +-- r2: [IN] *const u8 String pointer +-- r3: [IN] usize String length + + li8 r1, 0x1 -- Write syscall + brc r2, r3, 2 -- Copy parameters + li8 r2, 0x1 -- STDOUT + eca + + jal r0, r31, 0 + +gets: +-- Read string until end of buffer or LF +-- r2: [IN] *mut u8 Buffer +-- r3: [IN] usize Buffer length + + -- Register allocations: + -- r33: *mut u8 Buffer end + -- r34: u8 Immediate char + -- r35: u8 Const [0x0A = LF] + + li8 r35, 0x0A + add64 r33, r2, r3 + + -- Setup syscall + li8 r2, 0x1 -- Stdin + cp r3, r2 + li8 r4, 0x1 -- Read one char + + jeq r3, r33, end + loop: + li8 r1, 0x1 -- Read syscall + eca + addi64 r3, r3, 1 + ld r34, r3, 0, 1 + jeq r34, r35, end + jne r3, r33, loop + + end: + -- Set copied amount + sub64 r1, r33, r3 + addi64 r1, -1 + jal r0, r31, 0 + +alloc-pages: +-- Allocate pages +-- r1: [OUT] *mut u8 Pointer to page +-- r2: [IN] u16 Page count + + muli16 r3, r2, 4096 -- page count + li8 r1, 0x9 -- mmap syscall + li8 r2, 0x0 -- no address set, kernel chosen + li8 r4, 0x2 -- PROT_WRITE + li8 r5, 0x20 -- MAP_ANONYMOUS + li64 r6, -1 -- Doesn't map file + li8 r7, 0x0 -- Doesn't map file + eca + + jal r0, r31, 0 + +entry: +-- Program entrypoint + + -- Register allocations: + -- r32: *mut u8 Buffer + -- r36: usize Read buffer length + + -- Allocate one page (4096 KiB) + li8 r2, 1 + jal r31, 0, alloc-pages + cp r32, r1 + + -- Print message + lra16 r2, r0, #enter-your-name + li8 r3, 17 + jal r31, r0, puts + + -- Read name + cp r2, r32 + li16 r3, 4096 + jal r31, r0, gets + cp r36, r1 + + -- Print your name is + lra16 r2, r0, #your-name-is + li8 r3, 15 + jal r31, r0, puts + + -- And now print the name + cp r2, r32 + cp r3, r36 + jal r31, r0, puts + + tx + +#enter-your-name: "Enter your name: " +#your-name-is : "\nYour name is: " \ No newline at end of file diff --git a/examples/addition.hbf b/examples/bytecode/addition.hbf similarity index 100% rename from examples/addition.hbf rename to examples/bytecode/addition.hbf diff --git a/examples/linux-hello.hbf b/examples/bytecode/linux-hello.hbf similarity index 100% rename from examples/linux-hello.hbf rename to examples/bytecode/linux-hello.hbf From eab47db4d6aae10f2e3c0dba97a283d906e1dde4 Mon Sep 17 00:00:00 2001 From: Erin Date: Fri, 20 Oct 2023 12:35:45 +0200 Subject: [PATCH 095/116] chore: cargo update --- Cargo.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ec874025..4b395248 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -565,9 +565,9 @@ dependencies = [ [[package]] name = "tracing" -version = "0.1.39" +version = "0.1.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee2ef2af84856a50c1d430afce2fdded0a4ec7eda868db86409b4543df0797f9" +checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" dependencies = [ "pin-project-lite", "tracing-core", From b4923cfb953b203412f25823b2c5dbf4338b7730 Mon Sep 17 00:00:00 2001 From: Erin Date: Sun, 22 Oct 2023 04:21:45 +0200 Subject: [PATCH 096/116] Removed UB --- Cargo.lock | 324 +--------------------------------------------- hbxrt/Cargo.toml | 1 - hbxrt/src/main.rs | 40 +----- 3 files changed, 5 insertions(+), 360 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4b395248..4faacd13 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -17,24 +17,6 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" -[[package]] -name = "aho-corasick" -version = "1.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0" -dependencies = [ - "memchr", -] - -[[package]] -name = "ansi_term" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2" -dependencies = [ - "winapi", -] - [[package]] name = "argh" version = "0.1.12" @@ -66,17 +48,6 @@ dependencies = [ "serde", ] -[[package]] -name = "atty" -version = "0.2.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" -dependencies = [ - "hermit-abi", - "libc", - "winapi", -] - [[package]] name = "backtrace" version = "0.3.69" @@ -85,43 +56,13 @@ checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837" dependencies = [ "addr2line", "cc", - "cfg-if 1.0.0", + "cfg-if", "libc", "miniz_oxide", "object", "rustc-demangle", ] -[[package]] -name = "bindgen" -version = "0.53.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c72a978d268b1d70b0e963217e60fdabd9523a941457a6c42a7315d15c7e89e5" -dependencies = [ - "bitflags 1.3.2", - "cexpr", - "cfg-if 0.1.10", - "clang-sys", - "clap", - "env_logger", - "lazy_static", - "lazycell", - "log", - "peeking_take_while", - "proc-macro2", - "quote", - "regex", - "rustc-hash", - "shlex", - "which", -] - -[[package]] -name = "bitflags" -version = "1.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" - [[package]] name = "bitflags" version = "2.4.1" @@ -137,53 +78,12 @@ dependencies = [ "libc", ] -[[package]] -name = "cexpr" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4aedb84272dbe89af497cf81375129abda4fc0a9e7c5d317498c15cc30c0d27" -dependencies = [ - "nom", -] - -[[package]] -name = "cfg-if" -version = "0.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" - [[package]] name = "cfg-if" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" -[[package]] -name = "clang-sys" -version = "0.29.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe6837df1d5cba2397b835c8530f51723267e16abbf83892e9e5af4f0e5dd10a" -dependencies = [ - "glob", - "libc", - "libloading", -] - -[[package]] -name = "clap" -version = "2.34.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0610544180c38b88101fecf2dd634b174a62eef6946f84dfc6a7127512b381c" -dependencies = [ - "ansi_term", - "atty", - "bitflags 1.3.2", - "strsim", - "textwrap", - "unicode-width", - "vec_map", -] - [[package]] name = "color-eyre" version = "0.6.2" @@ -211,19 +111,6 @@ dependencies = [ "tracing-error", ] -[[package]] -name = "env_logger" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44533bbbb3bb3c1fa17d9f2e4e38bbbaf8396ba82193c4cb1b6445d711445d36" -dependencies = [ - "atty", - "humantime", - "log", - "regex", - "termcolor", -] - [[package]] name = "eyre" version = "0.6.8" @@ -240,12 +127,6 @@ version = "0.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6fb8d784f27acf97159b40fc4db5ecd8aa23b9ad5ef69cdd136d3bc80665f0c0" -[[package]] -name = "glob" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" - [[package]] name = "hbbytecode" version = "0.1.0" @@ -266,25 +147,6 @@ version = "0.1.0" dependencies = [ "hbvm", "nix", - "setjmp", -] - -[[package]] -name = "hermit-abi" -version = "0.1.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" -dependencies = [ - "libc", -] - -[[package]] -name = "humantime" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df004cfca50ef23c36850aaaa59ad52cc70d0e90243c3c7737a4dd32dc7a3c4f" -dependencies = [ - "quick-error", ] [[package]] @@ -299,34 +161,12 @@ version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" -[[package]] -name = "lazycell" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" - [[package]] name = "libc" version = "0.2.149" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a08173bc88b7955d1b3145aa561539096c421ac8debde8cbc3612ec635fee29b" -[[package]] -name = "libloading" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2b111a074963af1d37a139918ac6d49ad1d0d5e47f72fd55388619691a7d753" -dependencies = [ - "cc", - "winapi", -] - -[[package]] -name = "log" -version = "0.4.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" - [[package]] name = "memchr" version = "2.6.4" @@ -348,21 +188,11 @@ version = "0.27.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2eb04e9c688eff1c89d72b407f168cf79bb9e867a9d3323ed6c01519eb9cc053" dependencies = [ - "bitflags 2.4.1", - "cfg-if 1.0.0", + "bitflags", + "cfg-if", "libc", ] -[[package]] -name = "nom" -version = "5.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08959a387a676302eebf4ddbcbc611da04285579f76f88ee0506c63b1a61dd4b" -dependencies = [ - "memchr", - "version_check", -] - [[package]] name = "object" version = "0.32.1" @@ -384,12 +214,6 @@ version = "3.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c1b04fb49957986fdce4d6ee7a65027d55d4b6d2265e5848bbb507b58ccfdb6f" -[[package]] -name = "peeking_take_while" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099" - [[package]] name = "pin-project-lite" version = "0.2.13" @@ -405,12 +229,6 @@ dependencies = [ "unicode-ident", ] -[[package]] -name = "quick-error" -version = "1.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" - [[package]] name = "quote" version = "1.0.33" @@ -420,47 +238,12 @@ dependencies = [ "proc-macro2", ] -[[package]] -name = "regex" -version = "1.10.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "380b951a9c5e80ddfd6136919eef32310721aa4aacd4889a8d39124b026ab343" -dependencies = [ - "aho-corasick", - "memchr", - "regex-automata", - "regex-syntax", -] - -[[package]] -name = "regex-automata" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f804c7828047e88b2d32e2d7fe5a105da8ee3264f01902f796c8e067dc2483f" -dependencies = [ - "aho-corasick", - "memchr", - "regex-syntax", -] - -[[package]] -name = "regex-syntax" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" - [[package]] name = "rustc-demangle" version = "0.1.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" -[[package]] -name = "rustc-hash" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" - [[package]] name = "serde" version = "1.0.189" @@ -481,17 +264,6 @@ dependencies = [ "syn 2.0.38", ] -[[package]] -name = "setjmp" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9bce8e042e9b4349ccf7ce5caeb5c9b7ee6007ff543b494733ae8ed4ea083c73" -dependencies = [ - "bindgen", - "clang-sys", - "libc", -] - [[package]] name = "sharded-slab" version = "0.1.7" @@ -501,18 +273,6 @@ dependencies = [ "lazy_static", ] -[[package]] -name = "shlex" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fdf1b9db47230893d76faad238fd6097fd6d6a9245cd7a4d90dbd639536bbd2" - -[[package]] -name = "strsim" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" - [[package]] name = "syn" version = "1.0.109" @@ -535,31 +295,13 @@ dependencies = [ "unicode-ident", ] -[[package]] -name = "termcolor" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6093bad37da69aab9d123a8091e4be0aa4a03e4d601ec641c327398315f62b64" -dependencies = [ - "winapi-util", -] - -[[package]] -name = "textwrap" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" -dependencies = [ - "unicode-width", -] - [[package]] name = "thread_local" version = "1.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3fdd6f064ccff2d6567adcb3873ca630700f00b5ad3f060c25b5dcfd9a4ce152" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", "once_cell", ] @@ -610,70 +352,12 @@ version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" -[[package]] -name = "unicode-width" -version = "0.1.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85" - [[package]] name = "valuable" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" -[[package]] -name = "vec_map" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" - -[[package]] -name = "version_check" -version = "0.9.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" - -[[package]] -name = "which" -version = "3.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d011071ae14a2f6671d0b74080ae0cd8ebf3a6f8c9589a2cd45f23126fe29724" -dependencies = [ - "libc", -] - -[[package]] -name = "winapi" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" -dependencies = [ - "winapi-i686-pc-windows-gnu", - "winapi-x86_64-pc-windows-gnu", -] - -[[package]] -name = "winapi-i686-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" - -[[package]] -name = "winapi-util" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596" -dependencies = [ - "winapi", -] - -[[package]] -name = "winapi-x86_64-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" - [[package]] name = "with_builtin_macros" version = "0.0.3" diff --git a/hbxrt/Cargo.toml b/hbxrt/Cargo.toml index 6831f3ec..2fd08a92 100644 --- a/hbxrt/Cargo.toml +++ b/hbxrt/Cargo.toml @@ -7,4 +7,3 @@ default-run = "hbxrt" [dependencies] hbvm.path = "../hbvm" nix = { version = "0.27", features = ["mman", "signal"] } -setjmp = "0.1" diff --git a/hbxrt/src/main.rs b/hbxrt/src/main.rs index e10194b3..ac0c0226 100644 --- a/hbxrt/src/main.rs +++ b/hbxrt/src/main.rs @@ -4,10 +4,7 @@ mod mem; use { hbvm::{mem::Address, Vm, VmRunOk}, nix::sys::mman::{mmap, MapFlags, ProtFlags}, - setjmp::sigjmp_buf, - std::{ - cell::UnsafeCell, env::args, fs::File, mem::MaybeUninit, num::NonZeroUsize, process::exit, - }, + std::{env::args, fs::File, num::NonZeroUsize, process::exit}, }; fn main() -> Result<(), Box> { @@ -42,9 +39,6 @@ fn main() -> Result<(), Box> { unsafe { use nix::sys::signal; - static JMP_BUF: SyncUnsafeCell> = - SyncUnsafeCell::new(MaybeUninit::uninit()); - extern "C" fn action( _: std::ffi::c_int, info: *mut nix::libc::siginfo_t, @@ -52,19 +46,9 @@ fn main() -> Result<(), Box> { ) { unsafe { eprintln!("[E] Memory access fault at {:p}", (*info).si_addr()); - setjmp::siglongjmp((*JMP_BUF.get()).as_mut_ptr(), 1); } } - if setjmp::sigsetjmp((*JMP_BUF.get()).as_mut_ptr(), 0) > 0 { - eprintln!( - " Program counter: {:#x}\n\n== Registers ==\n{:?}", - vm.pc.get(), - vm.registers - ); - exit(3); - } - signal::sigaction( signal::Signal::SIGSEGV, &nix::sys::signal::SigAction::new( @@ -107,25 +91,3 @@ fn main() -> Result<(), Box> { Ok(()) } - -#[repr(transparent)] -struct SyncUnsafeCell { - value: UnsafeCell, -} - -unsafe impl Sync for SyncUnsafeCell {} -impl SyncUnsafeCell { - #[inline(always)] - pub const fn new(value: T) -> Self { - Self { - value: UnsafeCell::new(value), - } - } -} - -impl SyncUnsafeCell { - #[inline(always)] - pub const fn get(&self) -> *mut T { - self.value.get() - } -} From 83563fff84b392d0110205081d30c3f8dc69da24 Mon Sep 17 00:00:00 2001 From: Erin Date: Sun, 22 Oct 2023 14:46:45 +0200 Subject: [PATCH 097/116] Maybe fixed relative addressing bugs --- hbbytecode/src/lib.rs | 4 ++-- hbvm/src/mem/addr.rs | 13 +++++++++++-- hbvm/src/vmrun.rs | 43 +++++++++++++++++++++---------------------- 3 files changed, 34 insertions(+), 26 deletions(-) diff --git a/hbbytecode/src/lib.rs b/hbbytecode/src/lib.rs index 21a29c68..40be3c8f 100644 --- a/hbbytecode/src/lib.rs +++ b/hbbytecode/src/lib.rs @@ -5,8 +5,8 @@ use core::convert::TryFrom; type OpR = u8; type OpA = u64; -type OpO = u32; -type OpP = u16; +type OpO = i32; +type OpP = i16; type OpB = u8; type OpH = u16; diff --git a/hbvm/src/mem/addr.rs b/hbvm/src/mem/addr.rs index 0191417d..fc7b57b2 100644 --- a/hbvm/src/mem/addr.rs +++ b/hbvm/src/mem/addr.rs @@ -112,11 +112,20 @@ pub trait AddressOp { fn cast_u64(self) -> u64; } -macro_rules! impl_address_ops(($($ty:ty),* $(,)?) => { +macro_rules! impl_address_ops_u(($($ty:ty),* $(,)?) => { $(impl AddressOp for $ty { #[inline(always)] fn cast_u64(self) -> u64 { self as _ } })* }); -impl_address_ops!(u8, u16, u32, u64, usize); +macro_rules! impl_address_ops_i(($($ty:ty),* $(,)?) => { + $(impl AddressOp for $ty { + #[inline(always)] + fn cast_u64(self) -> u64 { self as i64 as u64 } + })* +}); + + +impl_address_ops_u!(u8, u16, u32, u64, usize); +impl_address_ops_i!(i8, i16, i32, i64, isize); diff --git a/hbvm/src/vmrun.rs b/hbvm/src/vmrun.rs index 98e549c8..8abc3b9b 100644 --- a/hbvm/src/vmrun.rs +++ b/hbvm/src/vmrun.rs @@ -4,6 +4,8 @@ use hbbytecode::RoundingMode; +use crate::mem::addr::AddressOp; + use { super::{ bmc::BlockCopier, @@ -201,10 +203,12 @@ where LI16 => handler!(self, |OpsRH(tg, imm)| self.write_reg(tg, imm)), LI32 => handler!(self, |OpsRW(tg, imm)| self.write_reg(tg, imm)), LI64 => handler!(self, |OpsRD(tg, imm)| self.write_reg(tg, imm)), - LRA => handler!(self, |OpsRRO(tg, reg, imm)| { + LRA => handler!(self, |OpsRRO(tg, reg, off)| { self.write_reg( tg, - (self.pc + self.read_reg(reg).cast::() + imm + 3_u16).get(), + self.pcrel(off, 3) + .wrapping_add(self.read_reg(reg).cast::()) + .get(), ); }), LD => handler!(self, |OpsRRAH(dst, base, off, count)| { @@ -216,20 +220,10 @@ where self.store(dst, base, off, count)?; }), LDR => handler!(self, |OpsRROH(dst, base, off, count)| { - self.load( - dst, - base, - u64::from(off).wrapping_add((self.pc + 3_u64).get()), - count, - )?; + self.load(dst, base, self.pcrel(off, 3).get(), count)?; }), STR => handler!(self, |OpsRROH(dst, base, off, count)| { - self.store( - dst, - base, - u64::from(off).wrapping_add((self.pc + 3_u64).get()), - count, - )?; + self.store(dst, base, self.pcrel(off, 3).get(), count)?; }), BMC => { // Block memory copy @@ -278,14 +272,13 @@ where ); }), JMP => handler!(self, |OpsO(off)| self.pc = self.pc.wrapping_add(off)), - JAL => handler!(self, |OpsRRW(save, reg, offset)| { + JAL => handler!(self, |OpsRRO(save, reg, offset)| { // Jump and link. Save PC after this instruction to // specified register and jump to reg + relative offset. self.write_reg(save, self.pc.get()); self.pc = self - .pc - .wrapping_add(self.read_reg(reg).cast::()) - .wrapping_add(offset); + .pcrel(offset, 3) + .wrapping_add(self.read_reg(reg).cast::()); }), JALA => handler!(self, |OpsRRW(save, reg, offset)| { // Jump and link. Save PC after this instruction to @@ -387,12 +380,12 @@ where ); }), LDR16 => handler!(self, |OpsRRPH(dst, base, off, count)| { - self.load(dst, base, u64::from(off).wrapping_add(self.pc.get()), count)?; + self.load(dst, base, self.pcrel(off, 3).get(), count)?; }), STR16 => handler!(self, |OpsRRPH(dst, base, off, count)| { - self.store(dst, base, u64::from(off).wrapping_add(self.pc.get()), count)?; + self.store(dst, base, self.pcrel(off, 3).get(), count)?; }), - JMP16 => handler!(self, |OpsP(off)| self.pc = self.pc.wrapping_add(off)), + JMP16 => handler!(self, |OpsP(off)| self.pc = self.pcrel(off, 1)), op => return Err(VmRunError::InvalidOpcode(op)), } } @@ -537,6 +530,12 @@ where }); } + /// Calculate pc-relative address + #[inline(always)] + fn pcrel(&self, offset: impl AddressOp, pos: u8) -> Address { + self.pc.wrapping_add(pos).wrapping_add(offset) + } + /// Jump at `PC + #3` if ordering on `#0 <=> #1` is equal to expected #[inline(always)] unsafe fn cond_jmp(&mut self, expected: Ordering) { @@ -547,7 +546,7 @@ where .cmp(&self.read_reg(a1).cast::()) == expected { - self.pc = Address::new(((self.pc.get() as i64).wrapping_add(ja as i64)) as u64); + self.pc = self.pcrel(ja, 3); } self.bump_pc::(); From 8182abca980149f35a5ce35fa40f75bd9ac6f516 Mon Sep 17 00:00:00 2001 From: Erin Date: Sun, 22 Oct 2023 15:08:45 +0200 Subject: [PATCH 098/116] Added assembler - its a bit cursed, prolly broken, but at least something --- Cargo.lock | 312 +++++++++++++++++++++++++++++++++++++++++ Cargo.toml | 2 +- hbasm/Cargo.toml | 12 ++ hbasm/assets/exit.lua | 3 + hbasm/assets/label.lua | 8 ++ hbasm/src/arraylist.rs | 53 +++++++ hbasm/src/ins.rs | 194 +++++++++++++++++++++++++ hbasm/src/label.rs | 91 ++++++++++++ hbasm/src/linker.rs | 44 ++++++ hbasm/src/main.rs | 54 +++++++ hbbytecode/src/lib.rs | 1 + hbvm/src/vmrun.rs | 29 ++-- 12 files changed, 792 insertions(+), 11 deletions(-) create mode 100644 hbasm/Cargo.toml create mode 100644 hbasm/assets/exit.lua create mode 100644 hbasm/assets/label.lua create mode 100644 hbasm/src/arraylist.rs create mode 100644 hbasm/src/ins.rs create mode 100644 hbasm/src/label.rs create mode 100644 hbasm/src/linker.rs create mode 100644 hbasm/src/main.rs diff --git a/Cargo.lock b/Cargo.lock index 4faacd13..ab773b4e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -17,6 +17,15 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" +[[package]] +name = "aho-corasick" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0" +dependencies = [ + "memchr", +] + [[package]] name = "argh" version = "0.1.12" @@ -48,6 +57,12 @@ dependencies = [ "serde", ] +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + [[package]] name = "backtrace" version = "0.3.69" @@ -69,6 +84,16 @@ version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07" +[[package]] +name = "bstr" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c79ad7fb2dd38f3dabd76b09c6a5a20c038fc0213ef1e9afd30eb777f120f019" +dependencies = [ + "memchr", + "serde", +] + [[package]] name = "cc" version = "1.0.83" @@ -111,6 +136,22 @@ dependencies = [ "tracing-error", ] +[[package]] +name = "either" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" + +[[package]] +name = "errno" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3e13f66a2f95e32a39eaa81f6b95d42878ca0e1db0c7543723dfe12557e860" +dependencies = [ + "libc", + "windows-sys", +] + [[package]] name = "eyre" version = "0.6.8" @@ -127,6 +168,15 @@ version = "0.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6fb8d784f27acf97159b40fc4db5ecd8aa23b9ad5ef69cdd136d3bc80665f0c0" +[[package]] +name = "hbasm" +version = "0.1.0" +dependencies = [ + "mlua", + "paste", + "with_builtin_macros", +] + [[package]] name = "hbbytecode" version = "0.1.0" @@ -149,12 +199,30 @@ dependencies = [ "nix", ] +[[package]] +name = "home" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5444c27eef6923071f7ebcc33e3444508466a76f7a2b93da00ed6e19f30c1ddb" +dependencies = [ + "windows-sys", +] + [[package]] name = "indenter" version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683" +[[package]] +name = "itertools" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57" +dependencies = [ + "either", +] + [[package]] name = "lazy_static" version = "1.4.0" @@ -167,6 +235,31 @@ version = "0.2.149" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a08173bc88b7955d1b3145aa561539096c421ac8debde8cbc3612ec635fee29b" +[[package]] +name = "linux-raw-sys" +version = "0.4.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da2479e8c062e40bf0066ffa0bc823de0a9368974af99c9f6df941d2c231e03f" + +[[package]] +name = "lua-src" +version = "546.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c26d4af78361e025a3d03a2b964cd1592aff7495f4d4f7947218c084c6fdca8" +dependencies = [ + "cc", +] + +[[package]] +name = "luajit-src" +version = "210.4.8+resty107baaf" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e05167e8b2a2185758d83ed23541e5bd8bce37072e4204e0ef2c9b322bc87c4e" +dependencies = [ + "cc", + "which", +] + [[package]] name = "memchr" version = "2.6.4" @@ -182,6 +275,48 @@ dependencies = [ "adler", ] +[[package]] +name = "mlua" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c3a7a7ff4481ec91b951a733390211a8ace1caba57266ccb5f4d4966704e560" +dependencies = [ + "bstr", + "mlua-sys", + "mlua_derive", + "num-traits", + "once_cell", + "rustc-hash", +] + +[[package]] +name = "mlua-sys" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ec8b54eddb76093069cce9eeffb4c7b3a1a0fe66962d7bd44c4867928149ca3" +dependencies = [ + "cc", + "cfg-if", + "lua-src", + "luajit-src", + "pkg-config", +] + +[[package]] +name = "mlua_derive" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f359220f24e6452dd82a3f50d7242d4aab822b5594798048e953d7a9e0314c6" +dependencies = [ + "itertools", + "once_cell", + "proc-macro-error", + "proc-macro2", + "quote", + "regex", + "syn 2.0.38", +] + [[package]] name = "nix" version = "0.27.1" @@ -193,6 +328,15 @@ dependencies = [ "libc", ] +[[package]] +name = "num-traits" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c" +dependencies = [ + "autocfg", +] + [[package]] name = "object" version = "0.32.1" @@ -214,12 +358,48 @@ version = "3.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c1b04fb49957986fdce4d6ee7a65027d55d4b6d2265e5848bbb507b58ccfdb6f" +[[package]] +name = "paste" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c" + [[package]] name = "pin-project-lite" version = "0.2.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" +[[package]] +name = "pkg-config" +version = "0.3.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" + +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn 1.0.109", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + [[package]] name = "proc-macro2" version = "1.0.69" @@ -238,12 +418,60 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "regex" +version = "1.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "380b951a9c5e80ddfd6136919eef32310721aa4aacd4889a8d39124b026ab343" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f804c7828047e88b2d32e2d7fe5a105da8ee3264f01902f796c8e067dc2483f" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" + [[package]] name = "rustc-demangle" version = "0.1.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + +[[package]] +name = "rustix" +version = "0.38.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67ce50cb2e16c2903e30d1cbccfd8387a74b9d4c938b6a4c5ec6cc7556f7a8a0" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys", + "windows-sys", +] + [[package]] name = "serde" version = "1.0.189" @@ -358,6 +586,90 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "which" +version = "4.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87ba24419a2078cd2b0f2ede2691b6c66d8e47836da3b6db8265ebad47afbfc7" +dependencies = [ + "either", + "home", + "once_cell", + "rustix", +] + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + [[package]] name = "with_builtin_macros" version = "0.0.3" diff --git a/Cargo.toml b/Cargo.toml index 8d81c807..91a11baa 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,3 +1,3 @@ [workspace] resolver = "2" -members = ["hbbytecode", "hbvm", "hbxrt", "xtask"] +members = ["hbasm", "hbbytecode", "hbvm", "hbxrt", "xtask"] diff --git a/hbasm/Cargo.toml b/hbasm/Cargo.toml new file mode 100644 index 00000000..3fafd654 --- /dev/null +++ b/hbasm/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "hbasm" +version = "0.1.0" +edition = "2021" + +[dependencies] +paste = "1.0" +with_builtin_macros = "0.0.3" + +[dependencies.mlua] +version = "0.9" +features = ["luajit52", "macros", "vendored"] diff --git a/hbasm/assets/exit.lua b/hbasm/assets/exit.lua new file mode 100644 index 00000000..e563181a --- /dev/null +++ b/hbasm/assets/exit.lua @@ -0,0 +1,3 @@ +li8(r1, 60) +li8(r2, 69) +eca() \ No newline at end of file diff --git a/hbasm/assets/label.lua b/hbasm/assets/label.lua new file mode 100644 index 00000000..a88879fc --- /dev/null +++ b/hbasm/assets/label.lua @@ -0,0 +1,8 @@ +label "label" -- set named label +local a = label {} -- unassigned label + +jmp16("label") +a:here() -- assign label +jmp16(a) + +addi8(r3, r4, 5) \ No newline at end of file diff --git a/hbasm/src/arraylist.rs b/hbasm/src/arraylist.rs new file mode 100644 index 00000000..439984c9 --- /dev/null +++ b/hbasm/src/arraylist.rs @@ -0,0 +1,53 @@ +use mlua::prelude::*; + +#[derive(Clone, Debug, Default, FromLua)] +pub struct ArrayList(pub Vec); +impl LuaUserData for ArrayList +where + T: for<'lua> FromLua<'lua> + for<'lua> IntoLua<'lua> + Clone + std::fmt::Debug, +{ + fn add_methods<'lua, M: LuaUserDataMethods<'lua, Self>>(methods: &mut M) { + methods.add_meta_method( + LuaMetaMethod::Index, + |lua, this, index: LuaInteger| match this.0.get( + (index as usize) + .checked_sub(1) + .ok_or_else(|| mlua::Error::runtime("Invalid index: 0"))?, + ) { + Some(i) => i.clone().into_lua(lua), + None => Ok(LuaValue::Nil), + }, + ); + + methods.add_meta_method_mut( + LuaMetaMethod::NewIndex, + |_, this, (index, val): (LuaInteger, T)| match this.0.get_mut( + (index as usize) + .checked_sub(1) + .ok_or_else(|| mlua::Error::runtime("Invalid index: 0"))?, + ) { + Some(x) => { + *x = val; + Ok(()) + } + None => Err(mlua::Error::runtime(format!( + "Index out of bounds: length = {}, index = {index}", + this.0.len() + ))), + }, + ); + + methods.add_meta_method(LuaMetaMethod::Len, |_, this, ()| Ok(this.0.len())); + methods.add_meta_method(LuaMetaMethod::ToString, |_, this, ()| { + Ok(format!("{this:?}")) + }); + methods.add_method_mut("push", |_, this, val: T| { + this.0.push(val); + Ok(()) + }); + methods.add_method_mut("pop", |lua, this, ()| match this.0.pop() { + Some(val) => val.into_lua(lua), + None => Ok(LuaValue::Nil), + }); + } +} diff --git a/hbasm/src/ins.rs b/hbasm/src/ins.rs new file mode 100644 index 00000000..f20bd180 --- /dev/null +++ b/hbasm/src/ins.rs @@ -0,0 +1,194 @@ +use { + crate::arraylist::ArrayList, + mlua::{prelude::*, Result, Scope}, +}; + +mod opsty { + pub type R = i8; + pub type B = i8; + pub type H = i16; + pub type W = i32; + pub type D = i64; + pub type A = i64; + pub type O<'lua> = super::LuaValue<'lua>; + pub type P<'lua> = super::LuaValue<'lua>; +} + +macro_rules! gen_insert { + ($($plain:ident),* $(,)?) => { + macro_rules! insert { + $( + ($label:expr, $lua:expr, $symrt:expr, $code:expr, $plain) => { + $code.0.extend($label.to_le_bytes()); + }; + )* + + ($label:expr, $lua:expr, $symrt:expr, $code:expr, O) => { + insert_label_ref::<4>( + $label, + $lua, + &$symrt, + &mut $code.0, + )?; + }; + + ($label:expr, $lua:expr, $symrt:expr, $code:expr, P) => { + insert_label_ref::<2>( + $label, + $lua, + &$symrt, + &mut $code.0, + )?; + }; + } + }; +} + +gen_insert!(R, B, H, W, D, A); + +macro_rules! generic_ins { + { + ($lua:expr, $scope:expr); + $($name:ident($($param_i:ident : $param_ty:ident),*);)* + } => {{ + let lua = $lua; + let scope = $scope; + + let code = $lua.globals() + .get::<_, LuaTable>("_CODE")? + .get::<_, LuaAnyUserData>("text")?; + + let symrt = $lua.globals() + .get::<_, LuaTable>("_SYM_REPLS")?; + + lua.globals().set( + "_GENERIC", + lua.create_table_from([$(( + stringify!($name), + #[allow(unused)] + { + let code = code.clone(); + let symrt = symrt.clone(); + scope.create_function_mut(move |lua, (opcode, $($param_i),*): (u8, $(opsty::$param_ty),*)| { + let mut code = code.borrow_mut::>()?; + code.0.push(opcode); + $(insert!($param_i, lua, symrt, code, $param_ty);)* + Ok(()) + })? + } + )),*])? + )?; + }}; +} + +macro_rules! ins { + { + $lua:expr; + {$($opcode:expr, $mnemonic:ident, $ty:ident, $_doc:literal;)*} + } => {{ + use std::fmt::Write; + + let lua = $lua; + let mut code = String::new(); + + $({ + paste::paste! { + let name = match stringify!([<$mnemonic:lower>]) { + "and" => "and_", + "or" => "or_", + "not" => "not_", + name => name, + }; + } + + writeln!( + code, + "function {name}(...) _GENERIC.{ty}({opcode}, ...) end", + ty = stringify!($ty).to_lowercase(), + opcode = $opcode, + ).unwrap(); + + })* + + lua.load(code).exec()?; + }}; +} + +pub fn setup<'lua, 'scope>(lua: &'lua Lua, scope: &Scope<'lua, 'scope>) -> Result<()> +where + 'lua: 'scope, +{ + generic_ins! { + (lua, scope); + rr (o0: R, o1: R); + rrr (o0: R, o1: R, o2: R); + rrrr(o0: R, o1: R, o2: R, o3: R); + rrb (o0: R, o1: R, o2: B); + rrh (o0: R, o1: R, o2: H); + rrw (o0: R, o1: R, o2: W); + rrd (o0: R, o1: R, o2: D); + rb (o0: R, o1: B); + rh (o0: R, o1: H); + rw (o0: R, o1: W); + rd (o0: R, o1: D); + rrah(o0: R, o1: R, o2: A, o3: H); + rroh(o0: R, o1: R, o2: O, o3: H); + rrph(o0: R, o1: R, o2: P, o3: H); + rro (o0: R, o1: R, o2: O); + rrp (o0: R, o1: R, r2: P); + o (o0: O); + p (o0: P); + n (); + } + + with_builtin_macros::with_builtin! { + let $spec = include_from_root!("../hbbytecode/instructions.in") in { + ins!(lua; { $spec }); + } + } + + Ok(()) +} + +fn insert_label_ref( + label: LuaValue, + lua: &Lua, + symrt: &LuaTable, + code: &mut Vec, +) -> Result<()> { + match label { + LuaValue::Integer(offset) => { + if match SIZE { + 2 => i16::try_from(offset).map(|o| code.extend(o.to_le_bytes())), + 4 => i32::try_from(offset).map(|o| code.extend(o.to_le_bytes())), + s => { + return Err(mlua::Error::runtime(format!( + "Invalid offset size (expected 2 or 4 bytes, got {s})" + ))); + } + } + .is_err() + { + return Err(mlua::Error::runtime("Failed to cast offset")); + } + return Ok(()); + } + LuaValue::UserData(ud) => { + symrt.set( + code.len() + 1, + lua.create_table_from([("label", ud.get("id")?), ("size", SIZE)])?, + )?; + code.extend([0; SIZE]); + } + LuaValue::String(_) => { + symrt.set( + code.len() + 1, + lua.create_table_from([("label", label), ("size", SIZE.into_lua(lua)?)])?, + )?; + code.extend([0; SIZE]); + } + _ => return Err(mlua::Error::runtime("Invalid label type")), + } + + Ok(()) +} diff --git a/hbasm/src/label.rs b/hbasm/src/label.rs new file mode 100644 index 00000000..da0c1cdc --- /dev/null +++ b/hbasm/src/label.rs @@ -0,0 +1,91 @@ +use { + crate::{arraylist::ArrayList, Labels}, + mlua::{Result, prelude::*}, +}; + +#[derive(Clone, Copy, Debug, PartialEq, Eq, FromLua)] +pub struct UnassignedLabel(pub usize); +impl LuaUserData for UnassignedLabel { + fn add_fields<'lua, F: LuaUserDataFields<'lua, Self>>(fields: &mut F) { + fields.add_field_method_get("id", |_, this| Ok(this.0)); + } + + fn add_methods<'lua, M: LuaUserDataMethods<'lua, Self>>(methods: &mut M) { + methods.add_method("here", |lua, this, ()| { + match lua + .globals() + .get::<_, LuaUserDataRefMut>("_LABELS")? + .0 + .get_mut( + this.0 + .checked_sub(1) + .ok_or_else(|| mlua::Error::runtime("Invalid label"))?, + ) { + Some(entry) => { + *entry = Some( + lua.globals() + .get::<_, LuaTable>("_CODE")? + .get::<_, LuaUserDataRef>>("text")? + .0 + .len(), + ); + + Ok(()) + } + None => Err(mlua::Error::runtime("Invalid label")), + } + }); + } +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq, FromLua)] +pub struct Label(pub usize); +impl LuaUserData for Label { + fn add_fields<'lua, F: LuaUserDataFields<'lua, Self>>(fields: &mut F) { + fields.add_field_method_get("id", |_, this| Ok(this.0)); + } +} + +pub fn label<'lua>(lua: &'lua Lua, val: LuaValue<'lua>) -> Result> { + let globals = lua.globals(); + let mut labels = globals.get::<_, LuaUserDataRefMut>("_LABELS")?; + + let code_ix = globals + .get::<_, LuaTable>("_CODE")? + .get::<_, LuaUserDataRefMut>>("text")? + .0 + .len(); + + match val { + LuaValue::Table(_) => { + labels.0.push(None); + Ok(LuaValue::UserData( + lua.create_userdata(UnassignedLabel(labels.0.len()))?, + )) + } + LuaValue::String(str) => { + labels.0.push(Some(code_ix + 1)); + globals + .get::<_, LuaTable>("_SYM_TABLE")? + .set(str, labels.0.len())?; + + Ok(LuaValue::UserData( + lua.create_userdata(Label(labels.0.len()))?, + )) + } + LuaNil => { + labels.0.push(Some(code_ix + 1)); + Ok(LuaValue::UserData( + lua.create_userdata(Label(labels.0.len()))?, + )) + } + _ => Err(mlua::Error::BadArgument { + to: Some("label".into()), + pos: 1, + name: None, + cause: std::sync::Arc::new(mlua::Error::runtime( + "Unsupported type (nil and string are only supported)", + )), + }), + } +} diff --git a/hbasm/src/linker.rs b/hbasm/src/linker.rs new file mode 100644 index 00000000..566c2dfa --- /dev/null +++ b/hbasm/src/linker.rs @@ -0,0 +1,44 @@ +use mlua::prelude::*; + +pub fn link( + symrt: LuaTable, + symtab: LuaTable, + labels: &[Option], + code: &mut [u8], + out: &mut impl std::io::Write, +) -> mlua::Result<()> { + for item in symrt.pairs::() { + let (loc, val) = item?; + let size: usize = val.get("size")?; + let dest = labels + .get( + match val.get::<_, LuaValue>("label")? { + LuaValue::Integer(i) => i, + LuaValue::String(s) => symtab.get(s)?, + _ => { + return Err(mlua::Error::runtime( + "Invalid symbol type (int or string expected)", + )) + } + } as usize + - 1, + ) + .copied() + .flatten() + .ok_or_else(|| mlua::Error::runtime("Invalid label"))?; + + let loc = loc - 1; + let dest = dest - 1; + + let offset = dest.wrapping_sub(loc); + match size { + 4 => code[loc..loc + size].copy_from_slice(&(offset as u32).to_le_bytes()), + 2 => code[loc..loc + size].copy_from_slice(&(offset as u16).to_le_bytes()), + _ => return Err(mlua::Error::runtime("Invalid symbol")), + } + } + + dbg!(&code); + out.write_all(code)?; + Ok(()) +} diff --git a/hbasm/src/main.rs b/hbasm/src/main.rs new file mode 100644 index 00000000..a9a0f9a1 --- /dev/null +++ b/hbasm/src/main.rs @@ -0,0 +1,54 @@ +mod arraylist; +mod ins; +mod label; +mod linker; + +use {arraylist::ArrayList, mlua::{Result, prelude::*}, std::io::Read}; + +pub type Labels = ArrayList>; + +fn main() -> Result<()> { + let mut code = vec![]; + std::io::stdin().read_to_end(&mut code)?; + + let lua = Lua::new(); + lua.scope(|scope| { + // Global values + let globals = lua.globals(); + globals.set( + "_CODE", + lua.create_table_from([ + ("text", ArrayList::::default()), + ("data", ArrayList::::default()), + ])?, + )?; + + globals.set("_LABELS", Labels::default())?; + globals.set("_SYM_TABLE", lua.create_table()?)?; + globals.set("_SYM_REPLS", lua.create_table()?)?; + + // Functions + globals.set("label", lua.create_function(label::label)?)?; + ins::setup(&lua, scope)?; + + // Register symbols + for n in 0..255 { + globals.set(format!("r{n}"), n)?; + } + + lua.load(code).exec()?; + + linker::link( + globals.get("_SYM_REPLS")?, + globals.get("_SYM_TABLE")?, + &globals.get::<_, Labels>("_LABELS")?.0, + &mut globals + .get::<_, LuaTable>("_CODE")? + .get::<_, LuaUserDataRefMut>>("text")? + .0, + &mut std::io::stdout(), + ) + })?; + + Ok(()) +} diff --git a/hbbytecode/src/lib.rs b/hbbytecode/src/lib.rs index 40be3c8f..4fe81bad 100644 --- a/hbbytecode/src/lib.rs +++ b/hbbytecode/src/lib.rs @@ -39,6 +39,7 @@ define_items! { OpsRW (OpR, OpW ), OpsRD (OpR, OpD ), OpsRRD (OpR, OpR, OpD ), + OpsRRA (OpR, OpR, OpA ), OpsRRAH (OpR, OpR, OpA, OpH), OpsRROH (OpR, OpR, OpO, OpH), OpsRRPH (OpR, OpR, OpP, OpH), diff --git a/hbvm/src/vmrun.rs b/hbvm/src/vmrun.rs index 8abc3b9b..047e17ce 100644 --- a/hbvm/src/vmrun.rs +++ b/hbvm/src/vmrun.rs @@ -2,7 +2,7 @@ //! //! Have fun -use hbbytecode::RoundingMode; +use hbbytecode::{OpsRRA, RoundingMode}; use crate::mem::addr::AddressOp; @@ -271,23 +271,29 @@ where usize::from(count), ); }), - JMP => handler!(self, |OpsO(off)| self.pc = self.pc.wrapping_add(off)), - JAL => handler!(self, |OpsRRO(save, reg, offset)| { + JMP => { + let OpsO(off) = self.decode(); + self.pc = self.pc.wrapping_add(off); + } + JAL => { // Jump and link. Save PC after this instruction to // specified register and jump to reg + relative offset. + let OpsRRO(save, reg, offset) = self.decode(); + self.write_reg(save, self.pc.get()); self.pc = self .pcrel(offset, 3) .wrapping_add(self.read_reg(reg).cast::()); - }), - JALA => handler!(self, |OpsRRW(save, reg, offset)| { + } + JALA => { // Jump and link. Save PC after this instruction to // specified register and jump to reg + let OpsRRA(save, reg, offset) = self.decode(); + self.write_reg(save, self.pc.get()); - self.pc = Address::new( - self.read_reg(reg).cast::().wrapping_add(offset.into()), - ); - }), + self.pc = + Address::new(self.read_reg(reg).cast::().wrapping_add(offset)); + } // Conditional jumps, jump only to immediates JEQ => self.cond_jmp::(Ordering::Equal), JNE => handler!(self, |OpsRRP(a0, a1, ja)| { @@ -385,7 +391,10 @@ where STR16 => handler!(self, |OpsRRPH(dst, base, off, count)| { self.store(dst, base, self.pcrel(off, 3).get(), count)?; }), - JMP16 => handler!(self, |OpsP(off)| self.pc = self.pcrel(off, 1)), + JMP16 => { + let OpsP(off) = self.decode(); + self.pc = self.pcrel(off, 1); + } op => return Err(VmRunError::InvalidOpcode(op)), } } From 2715bc9107f378940837b6ca4ac15a91aa1920cc Mon Sep 17 00:00:00 2001 From: Erin Date: Sun, 22 Oct 2023 16:17:51 +0200 Subject: [PATCH 099/116] Some fmt --- hbvm/src/vmrun.rs | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/hbvm/src/vmrun.rs b/hbvm/src/vmrun.rs index 047e17ce..de89fee7 100644 --- a/hbvm/src/vmrun.rs +++ b/hbvm/src/vmrun.rs @@ -2,10 +2,6 @@ //! //! Have fun -use hbbytecode::{OpsRRA, RoundingMode}; - -use crate::mem::addr::AddressOp; - use { super::{ bmc::BlockCopier, @@ -13,11 +9,14 @@ use { value::{Value, ValueVariant}, Vm, VmRunError, VmRunOk, }, - crate::{mem::Address, value::CheckedDivRem}, + crate::{ + mem::{addr::AddressOp, Address}, + value::CheckedDivRem, + }, core::{cmp::Ordering, ops}, hbbytecode::{ - OpsN, OpsO, OpsP, OpsRB, OpsRD, OpsRH, OpsRR, OpsRRAH, OpsRRB, OpsRRD, OpsRRH, OpsRRO, - OpsRROH, OpsRRP, OpsRRPH, OpsRRR, OpsRRRR, OpsRRW, OpsRW, + OpsN, OpsO, OpsP, OpsRB, OpsRD, OpsRH, OpsRR, OpsRRA, OpsRRAH, OpsRRB, OpsRRD, OpsRRH, + OpsRRO, OpsRROH, OpsRRP, OpsRRPH, OpsRRR, OpsRRRR, OpsRRW, OpsRW, RoundingMode, }, }; From cb557d136102bd743ca74264d2f492cce8ffaf08 Mon Sep 17 00:00:00 2001 From: Erin Date: Sun, 22 Oct 2023 18:18:50 +0200 Subject: [PATCH 100/116] Updated spec! --- hbbytecode/instructions.in | 20 +- hbvm/src/vmrun.rs | 222 ++++++------- spec.md | 628 ++++++++++++++++++++++--------------- 3 files changed, 475 insertions(+), 395 deletions(-) diff --git a/hbbytecode/instructions.in b/hbbytecode/instructions.in index 7a74d294..e6a61fa4 100644 --- a/hbbytecode/instructions.in +++ b/hbbytecode/instructions.in @@ -28,8 +28,8 @@ 0x1B, SRS16, RRR, "Signed right bitshift (16b)" ; 0x1C, SRS32, RRR, "Signed right bitshift (32b)" ; 0x1D, SRS64, RRR, "Signed right bitshift (64b)" ; -0x1E, CMP, RRR, "Signed comparsion" ; -0x1F, CMPU, RRR, "Unsigned comparsion" ; +0x1E, CMPU, RRR, "Unsigned comparsion" ; +0x1F, CMPS, RRR, "Signed comparsion" ; 0x20, DIRU8, RRRR, "Merged divide-remainder (unsigned 8b)" ; 0x21, DIRU16, RRRR, "Merged divide-remainder (unsigned 16b)" ; 0x22, DIRU32, RRRR, "Merged divide-remainder (unsigned 32b)" ; @@ -66,8 +66,8 @@ 0x41, SRSI16, RRW, "Signed right bitshift with immediate" ; 0x42, SRSI32, RRW, "Signed right bitshift with immediate" ; 0x43, SRSI64, RRW, "Signed right bitshift with immediate" ; -0x44, CMPI, RRD, "Signed compare with immediate" ; -0x45, CMPUI, RRD, "Unsigned compare with immediate" ; +0x44, CMPUI, RRD, "Unsigned compare with immediate" ; +0x45, CMPSI, RRD, "Signed compare with immediate" ; 0x46, CP, RR, "Copy register" ; 0x47, SWA, RR, "Swap registers" ; 0x48, LI8, RB, "Load immediate (8b)" ; @@ -86,10 +86,10 @@ 0x55, JALA, RRA, "Linking absolute jump" ; 0x56, JEQ, RRP, "Branch on equal" ; 0x57, JNE, RRP, "Branch on nonequal" ; -0x58, JLT, RRP, "Branch on lesser-than (signed)" ; -0x59, JGT, RRP, "Branch on greater-than (signed)" ; -0x5A, JLTU, RRP, "Branch on lesser-than (unsigned)" ; -0x5B, JGTU, RRP, "Branch on greater-than (unsigned)" ; +0x58, JLTU, RRP, "Branch on lesser-than (unsigned)" ; +0x59, JGTU, RRP, "Branch on greater-than (unsigned)" ; +0x5A, JLTS, RRP, "Branch on lesser-than (signed)" ; +0x5B, JGTS, RRP, "Branch on greater-than (signed)" ; 0x5C, ECA, N, "Environment call trap" ; 0x5D, EBP, N, "Environment breakpoint" ; 0x5E, FADD32, RRR, "Floating point addition (32b)" ; @@ -100,8 +100,8 @@ 0x63, FMUL64, RRR, "Floating point multiply (64b)" ; 0x64, FDIV32, RRR, "Floating point division (32b)" ; 0x65, FDIV64, RRR, "Floating point division (64b)" ; -0x66, FMA32, RRR, "Float fused multiply-add (32b)" ; -0x67, FMA64, RRR, "Float fused multiply-add (64b)" ; +0x66, FMA32, RRRR, "Float fused multiply-add (32b)" ; +0x67, FMA64, RRRR, "Float fused multiply-add (64b)" ; 0x68, FINV32, RR, "Float reciprocal (32b)" ; 0x69, FINV64, RR, "Float reciprocal (64b)" ; 0x6A, FCMPLT32, RRR, "Flaot compare less than (32b)" ; diff --git a/hbvm/src/vmrun.rs b/hbvm/src/vmrun.rs index de89fee7..70d68778 100644 --- a/hbvm/src/vmrun.rs +++ b/hbvm/src/vmrun.rs @@ -96,32 +96,16 @@ where SRS16 => self.binary_op(|l: i16, r| i16::wrapping_shl(l, r as u32)), SRS32 => self.binary_op(|l: i32, r| i32::wrapping_shl(l, r as u32)), SRS64 => self.binary_op(|l: i64, r| i64::wrapping_shl(l, r as u32)), - CMP => handler!(self, |OpsRRR(tg, a0, a1)| { - // Compare a0 <=> a1 - // < → 0 - // > → 1 - // = → 2 - - self.write_reg( - tg, - self.read_reg(a0) - .cast::() - .cmp(&self.read_reg(a1).cast::()) - as i64 - + 1, - ); - }), - CMPU => handler!(self, |OpsRRR(tg, a0, a1)| { - // Unsigned comparsion - self.write_reg( - tg, - self.read_reg(a0) - .cast::() - .cmp(&self.read_reg(a1).cast::()) - as i64 - + 1, - ); - }), + CMPU => handler!(self, |OpsRRR(tg, a0, a1)| self.cmp( + tg, + a0, + self.read_reg(a1).cast::() + )), + CMPS => handler!(self, |OpsRRR(tg, a0, a1)| self.cmp( + tg, + a0, + self.read_reg(a1).cast::() + )), DIRU8 => self.dir::(), DIRU16 => self.dir::(), DIRU32 => self.dir::(), @@ -170,21 +154,9 @@ where SRSI16 => self.binary_op_ims::(ops::Shr::shr), SRSI32 => self.binary_op_ims::(ops::Shr::shr), SRSI64 => self.binary_op_ims::(ops::Shr::shr), - CMPI => handler!(self, |OpsRRD(tg, a0, imm)| { - self.write_reg( - tg, - self.read_reg(a0) - .cast::() - .cmp(&Value::from(imm).cast::()) - as i64, - ); - }), - CMPUI => handler!(self, |OpsRRD(tg, a0, imm)| { - self.write_reg(tg, self.read_reg(a0).cast::().cmp(&imm) as i64); - }), - CP => handler!(self, |OpsRR(tg, a0)| { - self.write_reg(tg, self.read_reg(a0)); - }), + CMPUI => handler!(self, |OpsRRD(tg, a0, imm)| { self.cmp(tg, a0, imm) }), + CMPSI => handler!(self, |OpsRRD(tg, a0, imm)| { self.cmp(tg, a0, imm as i64) }), + CP => handler!(self, |OpsRR(tg, a0)| self.write_reg(tg, self.read_reg(a0))), SWA => handler!(self, |OpsRR(r0, r1)| { // Swap registers match (r0, r1) { @@ -202,28 +174,30 @@ where LI16 => handler!(self, |OpsRH(tg, imm)| self.write_reg(tg, imm)), LI32 => handler!(self, |OpsRW(tg, imm)| self.write_reg(tg, imm)), LI64 => handler!(self, |OpsRD(tg, imm)| self.write_reg(tg, imm)), - LRA => handler!(self, |OpsRRO(tg, reg, off)| { - self.write_reg( - tg, - self.pcrel(off, 3) - .wrapping_add(self.read_reg(reg).cast::()) - .get(), - ); - }), - LD => handler!(self, |OpsRRAH(dst, base, off, count)| { - // Load. If loading more than register size, continue on adjecent registers - self.load(dst, base, off, count)?; - }), - ST => handler!(self, |OpsRRAH(dst, base, off, count)| { - // Store. Same rules apply as to LD - self.store(dst, base, off, count)?; - }), - LDR => handler!(self, |OpsRROH(dst, base, off, count)| { - self.load(dst, base, self.pcrel(off, 3).get(), count)?; - }), - STR => handler!(self, |OpsRROH(dst, base, off, count)| { - self.store(dst, base, self.pcrel(off, 3).get(), count)?; - }), + LRA => handler!(self, |OpsRRO(tg, reg, off)| self.write_reg( + tg, + self.pcrel(off, 3) + .wrapping_add(self.read_reg(reg).cast::()) + .get(), + )), + // Load. If loading more than register size, continue on adjecent registers + LD => handler!(self, |OpsRRAH(dst, base, off, count)| self + .load(dst, base, off, count)?), + // Store. Same rules apply as to LD + ST => handler!(self, |OpsRRAH(dst, base, off, count)| self + .store(dst, base, off, count)?), + LDR => handler!(self, |OpsRROH(dst, base, off, count)| self.load( + dst, + base, + self.pcrel(off, 3).get(), + count + )?), + STR => handler!(self, |OpsRROH(dst, base, off, count)| self.store( + dst, + base, + self.pcrel(off, 3).get(), + count + )?), BMC => { // Block memory copy match if let Some(copier) = &mut self.copier { @@ -295,13 +269,12 @@ where } // Conditional jumps, jump only to immediates JEQ => self.cond_jmp::(Ordering::Equal), - JNE => handler!(self, |OpsRRP(a0, a1, ja)| { + JNE => { + let OpsRRP(a0, a1, ja) = self.decode(); if self.read_reg(a0).cast::() != self.read_reg(a1).cast::() { - self.pc = Address::new( - ((self.pc.get() as i64).wrapping_add(ja as i64)) as u64, - ) + self.pc = self.pcrel(ja, 3); } - }), + } JLT => self.cond_jmp::(Ordering::Less), JGT => self.cond_jmp::(Ordering::Greater), JLTU => self.cond_jmp::(Ordering::Less), @@ -329,67 +302,60 @@ where FDIV64 => self.binary_op::(ops::Div::div), FMA32 => self.fma::(), FMA64 => self.fma::(), - FINV32 => handler!(self, |OpsRR(tg, reg)| { - self.write_reg(tg, 1. / self.read_reg(reg).cast::()) - }), - FINV64 => handler!(self, |OpsRR(tg, reg)| { - self.write_reg(tg, 1. / self.read_reg(reg).cast::()) - }), + FINV32 => handler!(self, |OpsRR(tg, reg)| self + .write_reg(tg, 1. / self.read_reg(reg).cast::())), + FINV64 => handler!(self, |OpsRR(tg, reg)| self + .write_reg(tg, 1. / self.read_reg(reg).cast::())), FCMPLT32 => self.fcmp::(Ordering::Less), FCMPLT64 => self.fcmp::(Ordering::Less), FCMPGT32 => self.fcmp::(Ordering::Greater), FCMPGT64 => self.fcmp::(Ordering::Greater), - ITF32 => handler!(self, |OpsRR(tg, reg)| { - self.write_reg(tg, self.read_reg(reg).cast::() as f32); - }), - ITF64 => handler!(self, |OpsRR(tg, reg)| { - self.write_reg(tg, self.read_reg(reg).cast::() as f64); - }), - FTI32 => handler!(self, |OpsRRB(tg, reg, mode)| { - self.write_reg( - tg, - crate::float::f32toint( - self.read_reg(reg).cast::(), - RoundingMode::try_from(mode) - .map_err(|()| VmRunError::InvalidOperand)?, - ), - ); - }), - FTI64 => handler!(self, |OpsRRB(tg, reg, mode)| { - self.write_reg( - tg, - crate::float::f64toint( - self.read_reg(reg).cast::(), - RoundingMode::try_from(mode) - .map_err(|()| VmRunError::InvalidOperand)?, - ), - ); - }), - FC32T64 => handler!(self, |OpsRR(tg, reg)| { - self.write_reg(tg, self.read_reg(reg).cast::() as f64); - }), - FC64T32 => handler!(self, |OpsRRB(tg, reg, mode)| { - self.write_reg( - tg, - crate::float::conv64to32( - self.read_reg(reg).cast(), - RoundingMode::try_from(mode) - .map_err(|()| VmRunError::InvalidOperand)?, - ), - ) - }), - LRA16 => handler!(self, |OpsRRP(tg, reg, imm)| { - self.write_reg( - tg, - (self.pc + self.read_reg(reg).cast::() + imm + 3_u16).get(), - ); - }), - LDR16 => handler!(self, |OpsRRPH(dst, base, off, count)| { - self.load(dst, base, self.pcrel(off, 3).get(), count)?; - }), - STR16 => handler!(self, |OpsRRPH(dst, base, off, count)| { - self.store(dst, base, self.pcrel(off, 3).get(), count)?; - }), + ITF32 => handler!(self, |OpsRR(tg, reg)| self + .write_reg(tg, self.read_reg(reg).cast::() as f32)), + ITF64 => handler!(self, |OpsRR(tg, reg)| self + .write_reg(tg, self.read_reg(reg).cast::() as f64)), + FTI32 => handler!(self, |OpsRRB(tg, reg, mode)| self.write_reg( + tg, + crate::float::f32toint( + self.read_reg(reg).cast::(), + RoundingMode::try_from(mode) + .map_err(|()| VmRunError::InvalidOperand)?, + ), + )), + FTI64 => handler!(self, |OpsRRB(tg, reg, mode)| self.write_reg( + tg, + crate::float::f64toint( + self.read_reg(reg).cast::(), + RoundingMode::try_from(mode) + .map_err(|()| VmRunError::InvalidOperand)?, + ), + )), + FC32T64 => handler!(self, |OpsRR(tg, reg)| self + .write_reg(tg, self.read_reg(reg).cast::() as f64)), + FC64T32 => handler!(self, |OpsRRB(tg, reg, mode)| self.write_reg( + tg, + crate::float::conv64to32( + self.read_reg(reg).cast(), + RoundingMode::try_from(mode) + .map_err(|()| VmRunError::InvalidOperand)?, + ), + )), + LRA16 => handler!(self, |OpsRRP(tg, reg, imm)| self.write_reg( + tg, + (self.pc + self.read_reg(reg).cast::() + imm + 3_u16).get(), + )), + LDR16 => handler!(self, |OpsRRPH(dst, base, off, count)| self.load( + dst, + base, + self.pcrel(off, 3).get(), + count + )?), + STR16 => handler!(self, |OpsRRPH(dst, base, off, count)| self.store( + dst, + base, + self.pcrel(off, 3).get(), + count + )?), JMP16 => { let OpsP(off) = self.decode(); self.pc = self.pcrel(off, 1); @@ -464,6 +430,12 @@ where Ok(()) } + /// Three-way comparsion + #[inline(always)] + unsafe fn cmp(&mut self, to: u8, reg: u8, val: T) { + self.write_reg(to, self.read_reg(reg).cast::().cmp(&val) as i64); + } + /// Perform binary operating over two registers #[inline(always)] unsafe fn binary_op(&mut self, op: impl Fn(T, T) -> T) { diff --git a/spec.md b/spec.md index bb807804..37cd6f93 100644 --- a/spec.md +++ b/spec.md @@ -1,332 +1,440 @@ # HoleyBytes ISA Specification # Bytecode format -- Holey Bytes program should start with following magic: `[0xAB, 0x1E, 0x0B]` +- Image format is not specified, though ELF is recommended - All numbers are encoded little-endian - There is 256 registers, they are represented by a byte -- Immediate values are 64 bit -- Program is by spec required to be terminated with 12 zero bytes +- Immediate values are 8, 16, 32 or 64 bit -### Instruction encoding -- Instruction parameters are packed (no alignment) -- [opcode, …parameters…] +## Instruction encoding +- Instruction operands are packed (no alignment) +- [opcode, operand 0, operand 1, …] -### Instruction parameter types -- B = Byte -- D = Doubleword (64 bits) -- H = Halfword (16 bits) +## Instruction parameter types +- `R`: Register (8 bits) +- Relative program-counter offset immediates: + - `O`: 32 bit (Si32) + - `P`: 16 bit (Si16) +- Immediates: + - `B`: Byte, 8 bit (Xi8) + - `H`: Half-word, 16 bit (Xi16) + - `W`: Word, 32 bit (Xi32) + - `D`: Double-word, 64 bit (Xi64) +- `A`: Absolute address immediate, 64 bit (Ui64) -| Name | Size | -|:----:|:--------| -| BBBB | 32 bits | -| BBB | 24 bits | -| BBDH | 96 bits | -| BBD | 80 bits | -| BBW | 48 bits | -| BB | 16 bits | -| BD | 72 bits | -| D | 64 bits | -| N | 0 bits | +## Types +- Si*n*: Signed integer of size *n* bits (Si8, Si16, Si32, Si64) +- Ui*n*: Unsigned integer of size *n* bits (Ui8, Ui16, Ui32, Ui64) +- Xi*n*: Sign-agnostic integer of size *n* bits (Xi8, Xi16, Xi32, Xi64) +- Fl*n*: Floating point number of size *n* bits (Fl32, Fl64) + +# Behaviours +- Integer operations are always wrapping, including signed numbers +- Two's complement +- Floats as specified by IEEE 754 + +## Relative addressing +Relative addresses are computed from address of the first byte +of offset in the code. Not from the beginning of current or following instruction. + +## Zero register +- Register 0 +- Cannot be clobbered + - Write is no-op +- Load always yields 0 + +## Rounding modes +| Rounding mode | Value | +|:-------------------------|:------| +| To nearest, ties to even | 0b00 | +| Towards 0 (truncate) | 0b01 | +| Towards +∞ (up) | 0b10 | +| Towards -∞ (down) | 0b11 | # Instructions - `#n`: register in parameter *n* -- `imm #n`: for immediate in parameter *n* -- `P ← V`: Set register P to value V +- `$n`: for immediate in parameter *n* +- `#P ← V`: Set register P to value V - `[x]`: Address x +- `XY`: X bytes from location Y +- `pc`: Program counter +- ``: Placeholder +- `Type(X)`: Cast ## Program execution control -- N type +- Type `N` -| Opcode | Name | Action | -|:------:|:----:|:-----------------------------:| -| 0 | UN | Trigger unreachable code trap | -| 1 | TX | Terminate execution | -| 2 | NOP | Do nothing | +| Opcode | Mnemonic | Action | +|:-------|:---------|:--------------------------------------------| +| 0x00 | UN | Throw unreachable code exception | +| 0x01 | TX | Terminate execution (eg. on end of program) | +| 0x02 | NOP | Do nothing | -## Integer binary ops. -- BBB type -- `#0 ← #1 #2` +## Binary register-immediate ops +- Type `RR` +- Action: `#0 ← #1 #2` -| Opcode | Name | Action | -|:------:|:----:|:-----------------------:| -| 3 | ADD | Wrapping addition | -| 4 | SUB | Wrapping subtraction | -| 5 | MUL | Wrapping multiplication | -| 6 | AND | Bitand | -| 7 | OR | Bitor | -| 8 | XOR | Bitxor | -| 9 | SL | Unsigned left bitshift | -| 10 | SR | Unsigned right bitshift | -| 11 | SRS | Signed right bitshift | +## Addition (`+`) +| Opcode | Mnemonic | Type | +|:-------|:---------|:-----| +| 0x03 | ADD8 | Xi8 | +| 0x04 | ADD16 | Xi16 | +| 0x05 | ADD32 | Xi32 | +| 0x06 | ADD64 | Xi64 | -### Comparsion -| Opcode | Name | Action | -|:------:|:----:|:-------------------:| -| 12 | CMP | Signed comparsion | -| 13 | CMPU | Unsigned comparsion | +## Subtraction (`-`) +| Opcode | Mnemonic | Type | +|:-------|:---------|:-----| +| 0x07 | SUB8 | Xi8 | +| 0x08 | SUB16 | Xi16 | +| 0x09 | SUB32 | Xi32 | +| 0x0A | SUB64 | Xi64 | -#### Comparsion table -| #1 *op* #2 | Result | -|:----------:|:------:| -| < | 0 | -| = | 1 | -| > | 2 | +## Multiplication (`*`) +| Opcode | Mnemonic | Type | +|:-------|:---------|:-----| +| 0x0B | MUL8 | Xi8 | +| 0x0C | MUL16 | Xi16 | +| 0x0D | MUL32 | Xi32 | +| 0x0E | MUL64 | Xi64 | -### Division-remainder -- Type BBBB -- In case of `#3` is zero, the resulting value is all-ones -- `#0 ← #2 ÷ #3` -- `#1 ← #2 % #3` +## Bitwise ops (type: Xi64) +| Opcode | Mnemonic | Operation | +|:-------|:---------|:--------------------| +| 0x0F | AND | Conjunction (&) | +| 0x10 | OR | Disjunction (\|) | +| 0x11 | XOR | Non-equivalence (^) | -| Opcode | Name | Action | -|:------:|:----:|:-------------------------------:| -| 14 | DIR | Divide and remainder combinated | +## Unsigned left bitshift (`<<`) +| Opcode | Mnemonic | Type | +|:-------|:---------|:-----| +| 0x12 | SLU8 | Ui8 | +| 0x13 | SLU16 | Ui16 | +| 0x14 | SLU32 | Ui32 | +| 0x15 | SLU64 | Ui64 | -### Negations -- Type BB -- `#0 ← #1 #2` +## Unsigned right bitshift (`>>`) +| Opcode | Mnemonic | Type | +|:-------|:---------|:-----| +| 0x16 | SRU8 | Ui8 | +| 0x17 | SRU16 | Ui16 | +| 0x18 | SRU32 | Ui32 | +| 0x19 | SRU64 | Ui64 | -| Opcode | Name | Action | -|:------:|:----:|:----------------:| -| 15 | NEG | Bit negation | -| 16 | NOT | Logical negation | +## Signed right bitshift (`>>`) +| Opcode | Mnemonic | Type | +|:-------|:---------|:-----| +| 0x1A | SRS8 | Si8 | +| 0x1B | SRS16 | Si16 | +| 0x1C | SRS32 | Si32 | +| 0x1D | SRS64 | Si64 | -## Integer immediate binary ops. -- Type BBD -- `#0 ← #1 imm #2` +## Comparsion +- Compares two numbers, saves result to register +- Operation: `#0 ← #1 <=> #2` -| Opcode | Name | Action | -|:------:|:----:|:--------------------:| -| 17 | ADDI | Wrapping addition | -| 18 | MULI | Wrapping subtraction | -| 19 | ANDI | Bitand | -| 20 | ORI | Bitor | -| 21 | XORI | Bitxor | +| Ordering | Number | +|:---------|:-------| +| < | -1 | +| = | 0 | +| > | 1 | -### Bitshifts -- Type BBW -| Opcode | Name | Action | -|:------:|:----:|:-----------------------:| -| 22 | SLI | Unsigned left bitshift | -| 23 | SRI | Unsigned right bitshift | -| 24 | SRSI | Signed right bitshift | +| Opcode | Mnemonic | Type | +|:-------|:---------|:-----| +| 0x1E | CMPU | Ui64 | +| 0x1F | CMPS | Si64 | -### Comparsion -- Comparsion is the same as when RRR type +# Merged divide-remainder +- Type `RRRR` +- Operation: + - `#0 ← #2 / #3` + - `#1 ← #2 % #3` -| Opcode | Name | Action | -|:------:|:-----:|:-------------------:| -| 25 | CMPI | Signed comparsion | -| 26 | CMPUI | Unsigned comparsion | +- If dividing by zero: + - `#0 ← Ui64(-1)` + - `#1 ← #2` -## Register value set / copy +| Opcode | Mnemonic | Type | +|:-------|:---------|:-----| +| 0x20 | DIRU8 | Ui8 | +| 0x21 | DIRU16 | Ui16 | +| 0x22 | DIRU32 | Ui32 | +| 0x23 | DIRU64 | Ui64 | +| 0x24 | DIRS8 | Si8 | +| 0x25 | DIRS16 | Si16 | +| 0x26 | DIRS32 | Si32 | +| 0x27 | DIRS64 | Si64 | -### Copy -- Type BB -- `#0 ← #1` +# Unary register operations (type: Xi64) +- Type: `RR` +- Operation: `#0 ← #1` -| Opcode | Name | Action | -|:------:|:----:|:------:| -| 27 | CP | Copy | +| Opcode | Mnemonic | Operation | +|:-------|:---------|:-------------------------| +| 0x28 | NEG | Bitwise complement (`~`) | +| 0x29 | NOT | Logical negation (`!`) | -### Swap -- Type BB -- Swap #0 and #1 -- Zero register rules: - - Both: no-op - - One: Copy zero to the non-zero register +## Sign extensions +- Operation: `#0 ← Si64(#1)` -| Opcode | Name | Action | -|:------:|:----:|:------:| -| 28 | SWA | Swap | +| Opcode | Mnemonic | Source type | +|:-------|:---------|:------------| +| 0x2A | SXT8 | Si8 | +| 0x2B | SXT16 | Si16 | +| 0x2C | SXT32 | Si32 | -### Load immediate -- Type BD -- `#0 ← #1` +# Binary register-immediate operations +- Type: `RR` +- Operation: `#0 ← #1 $2` -| Opcode | Name | Action | -|:------:|:----:|:--------------:| -| 29 | LI | Load immediate | +## Addition (`+`) +| Opcode | Mnemonic | Type | +|:-------|:---------|:-----| +| 0x2D | ADDI8 | Xi8 | +| 0x2E | ADDI16 | Xi16 | +| 0x2F | ADDI32 | Xi32 | +| 0x30 | ADDI64 | Xi64 | -### Load relative address -- Type BBW -| Opcode | Name | Action | -|:------:|:----:|:-----------------------:| -| 30 | LRA | `#0 ← #1 + imm #2 + PC` | +## Multiplication (`*`) +| Opcode | Mnemonic | Type | +|:-------|:---------|:-----| +| 0x31 | MULI8 | Xi8 | +| 0x32 | MULI16 | Xi16 | +| 0x33 | MULI32 | Xi32 | +| 0x34 | MULI64 | Xi64 | -## Memory operations -- Type BBDH -- If loaded/store value exceeds one register size, continue accessing following registers +## Bitwise ops (type: Xi64) +| Opcode | Mnemonic | Operation | +|:-------|:---------|:--------------------| +| 0x35 | ANDI | Conjunction (&) | +| 0x36 | ORI | Disjunction (\|) | +| 0x37 | XORI | Non-equivalence (^) | -### Load / Store -| Opcode | Name | Action | -|:------:|:----:|:---------------------------------------:| -| 31 | LD | `#0 ← [#1 + imm #2], copy imm #3 bytes` | -| 32 | ST | `[#1 + imm #2] ← #0, copy imm #3 bytes` | +## Unsigned left bitshift (`<<`) +| Opcode | Mnemonic | Type | +|:-------|:---------|:-----| +| 0x38 | SLUI8 | Ui8 | +| 0x39 | SLUI16 | Ui16 | +| 0x3A | SLUI32 | Ui32 | +| 0x3B | SLUI64 | Ui64 | -### PC relative Load / Store -- Type BBDW -| Opcode | Name | Action | -|:------:|:----:|:--------------------------------------------:| -| 33 | LDR | `#0 ← [#1 + imm #2 + PC], copy imm #3 bytes` | -| 34 | STR | `[#1 + imm #2 + PC] ← #0, copy imm #3 bytes` | +## Unsigned right bitshift (`>>`) +| Opcode | Mnemonic | Type | +|:-------|:---------|:-----| +| 0x3C | SRUI8 | Ui8 | +| 0x3D | SRUI16 | Ui16 | +| 0x3E | SRUI32 | Ui32 | +| 0x3F | SRUI64 | Ui64 | -## Block copy -- Block copy source and target can overlap +## Signed right bitshift (`>>`) +| Opcode | Mnemonic | Type | +|:-------|:---------|:-----| +| 0x40 | SRSI8 | Si8 | +| 0x41 | SRSI16 | Si16 | +| 0x42 | SRSI32 | Si32 | +| 0x43 | SRSI64 | Si64 | -### Memory copy -- Type BBD +## Comparsion +- Compares two numbers, saves result to register +- Operation: `#0 ← #1 <=> $2` +- Comparsion table same for register-register one -| Opcode | Name | Action | -|:------:|:----:|:--------------------------------:| -| 35 | BMC | `[#1] ← [#0], copy imm #2 bytes` | +| Opcode | Mnemonic | Type | +|:-------|:---------|:-----| +| 0x44 | CMPUI | Ui64 | +| 0x45 | CMPSI | Si64 | -### Register copy -- Type BBB -- Copy a block a register to another location (again, overflowing to following registers) +# Register copies +- Type: `RR` -| Opcode | Name | Action | -|:------:|:----:|:--------------------------------:| -| 36 | BRC | `#1 ← #0, copy imm #2 registers` | +| Opcode | Mnemonic | Operation | +|:-------|:---------|:---------------------------------| +| 0x46 | CP | Copy register value (`#0 ← #1`) | +| 0x47 | SWA | Swap register values (`#0 ⇆ #1`) | -## Control flow +# Load immediate +- Load immediate value from code to register +- Type: `R` +- Operation: `#0 ← $1` -### Unconditional jump -- Type D -| Opcode | Name | Action | -|:------:|:----:|:-------------------------------------------:| -| 37 | JMPR | Jump at address relative to program counter | +| Opcode | Mnemonic | Type | +|:-------|:---------|:-----| +| 0x48 | LI8 | Xi8 | +| 0x49 | LI16 | Xi16 | +| 0x4A | Li32 | Xi32 | +| 0x4B | Li64 | Xi64 | -### Unconditional linking jump -- Type BBD +# Load relative address +- Compute value from program counter, register value and offset +- Type: `RRO` +- Operation: `#0 ← pc + #1 + $2` -| Opcode | Name | Action | -|:------:|:----:|:-------------------------------------------------------:| -| 38 | JAL | Save PC past JAL to `#0` and jump at `#1 + imm #2` | -| 39 | JALR | Save PC past JAL to `#0` and jump at `#1 + imm #2 + PC` | +| Opcode | Mnemonic | +|:-------|:---------| +| 0x4C | LRA | -### Conditional jumps -- Type BBH -- Jump at `PC + imm #2` if `#0 #1` +# Memory access operations +- Immediate `$3` specifies size +- If size is greater than register size, + it overflows to adjecent register + (eg. copying 16 bytes to register `r1` copies first 8 bytes to it + and the remaining to `r2`) -| Opcode | Name | Comparsion | -|:------:|:----:|:------------:| -| 40 | JEQ | = | -| 41 | JNE | ≠ | -| 42 | JLT | < (signed) | -| 43 | JGT | > (signed) | -| 44 | JLTU | < (unsigned) | -| 45 | JGTU | > (unsigned) | +## Absolute addressing +- Type: `RRAH` +- Computes address from base register and absolute offset -### Environment call -- Type N +| Opcode | Mnemonic | Operation | +|:-------|:---------|:-------------------| +| 0x4D | LD | `#0 ← $3[#1 + $2]` | +| 0x4E | ST | `$3[#1 + $2] ← #0` | -| Opcode | Name | Action | -|:------:|:----:|:-------------------------------------:| -| 46 | ECA | Cause an trap to the host environment | -| 47 | EBP | Cause breakproint trap to environment | +## Relative addressing +- Type: `RROH` +- Computes address from register and offset from program counter -## Floating point operations -- Type BBB -- `#0 ← #1 #2` +| Opcode | Mnemonic | Operation | +|:-------|:---------|:------------------------| +| 0x4F | LDR | `#0 ← $3[pc + #1 + $2]` | +| 0x50 | STR | `$3[pc + #1 + $2] ← #0` | -| Opcode | Name | Action | -|:------:|:----:|:--------------:| -| 48 | ADDF | Addition | -| 49 | SUBF | Subtraction | -| 50 | MULF | Multiplication | +# Block memory copy +- Type: `RRH` +- Copies block of `$3` bytes from memory location on address on `#0` to `#1` -### Division-remainder -- Type BBBB +| Opcode | Mnemonic | Operation | +|:-------|:---------|:------------------| +| 0x51 | BMC | `$3[#1] ← $3[x0]` | -| Opcode | Name | Action | -|:------:|:----:|:-------------------------:| -| 51 | DIRF | Same as for integer `DIR` | +# Block register copy +- Type: `RRB` +- Copy block of `$3` registers starting with `#0` to `#1` +- Copying over the 256 registers causes an exception -### Fused Multiply-Add -- Type BBBB +| Opcode | Mnemonic | Operation | +|:-------|:---------|:--------------| +| 0x52 | BRC | `$3#1 ← $3#0` | -| Opcode | Name | Action | -|:------:|:----:|:---------------------:| -| 52 | FMAF | `#0 ← (#1 * #2) + #3` | +# Relative jump +- Type: `O` -### Negation -- Type BB -| Opcode | Name | Action | -|:------:|:----:|:----------:| -| 53 | NEGF | `#0 ← -#1` | +| Opcode | Mnemonic | Operation | +|:-------|:---------|:---------------| +| 0x53 | JMP | `pc ← pc + $0` | -### Conversion -- Type BB -- Signed -- `#0 ← #1 as _` +# Linking jump +- Operation: + - Save address of following instruction to `#0` + - `#0 ← pc+` + - Jump to specified address -| Opcode | Name | Action | -|:------:|:----:|:------------:| -| 54 | ITF | Int to Float | -| 55 | FTI | Float to Int | +| Opcode | Mnemonic | Instruction type | Address | +|:-------|:---------|:------------------|:-------------------------| +| 0x54 | JAL | RRO (size = 6 B) | Relative, `pc + #1 + $2` | +| 0x55 | JALA | RRA (size = 10 B) | Absolute, `#1 + $2` | -## Floating point immediate operations -- Type BBD -- `#0 ← #1 imm #2` +# Conditional jump +- Perform comparsion, if operation met, jump to relative address +- Type: `RRP` +- Operation: `if #0 #1 { pc ← pc + $2 }` -| Opcode | Name | Action | -|:------:|:-----:|:--------------:| -| 56 | ADDFI | Addition | -| 57 | MULFI | Multiplication | +| Opcode | Mnemonic | Condition | Type | +|:-------|:---------|:-------------------|:-----| +| 0x56 | JEQ | Equals (`=`) | Xi64 | +| 0x57 | JNE | Not-equals (`≠`) | Xi64 | +| 0x58 | JLTU | Less-than (`<`) | Ui64 | +| 0x59 | JGTU | Greater-than (`>`) | Ui64 | +| 0x5A | JLTS | Less-than (`<`) | Si64 | +| 0x5B | JGTS | Greater-than (`>`) | Si64 | -# Registers -- There is 255 registers + one zero register (with index 0) -- Reading from zero register yields zero -- Writing to zero register is a no-op +# Environment traps +- Traps to the environment +- Type: `N` -# Memory -- Addresses are 64 bit -- Program should be in the same address space as all other data -- Memory implementation is arbitrary - - Address `0x0` may or may not be valid. Count with compilers - considering it invalid! -- In case of accessing invalid address: - - Program shall trap (LoadAccessEx, StoreAccessEx) with parameter of accessed address - - Value of register when trapped is undefined +| Opcode | Mnemonic | Trap type | +|:-------|:---------|:-----------------| +| 0x5C | ECA | Environment call | +| 0x5D | EBP | Breakpoint | -## Recommendations -- If paging used: - - Leave first page invalid - - Pages should be at least 4 KiB +# Floating point binary operations +- Type: `RRR` +- Operation: `#0 ← #1 #2` -# Program execution -- The way of program execution is implementation defined -- The execution is arbitrary, as long all effects are obervable - in the way as program was executed literally, in order. +| Opcode | Mnemonic | Operation | Type | +|:-------|:---------|:---------------------|:-----| +| 0x5E | FADD32 | Addition (`+`) | Fl32 | +| 0x5F | FADD64 | Addition (`+`) | Fl64 | +| 0x60 | FSUB32 | Subtraction (`-`) | Fl32 | +| 0x61 | FSUB64 | Subtraction (`-`) | Fl64 | +| 0x62 | FMUL32 | Multiplication (`*`) | Fl32 | +| 0x63 | FMUL64 | Multiplication (`*`) | Fl64 | +| 0x64 | FDIV32 | Division (`/`) | Fl32 | +| 0x65 | FDIV64 | Division (`/`) | Fl64 | -# Program validation -- Invalid program should cause runtime error: - - The form of error is arbitrary. Can be a trap or an interpreter-specified error - - It shall not be handleable from within the program -- Executing invalid opcode should trap -- Program can be validaded either before execution or when executing +# Fused multiply-add +- Type: `RRRR` +- Operation: `#0 ← (#1 * #2) + #3` -# Traps -Program should at least implement these traps: -- Environment call -- Invalid instruction exception -- Load address exception -- Store address exception -- Unreachable instruction +| Opcode | Mnemonic | Type | +|:-------|:---------|:-----| +| 0x66 | FMA32 | Fl32 | +| 0x67 | FMA64 | Fl64 | -and executing environment should be able to get information about them, -like the opcode of invalid instruction or attempted address to load/store. -Details about these are left as an implementation detail. +# Comparsions +- Type: `RRR` +- Operation: `#0 ← #1 <=> #2` +- Comparsion table same as for `CMPx`/`CMPxI` +- NaN is less-than/greater-than depends on variant -# Assembly -HoleyBytes assembly format is not defined, this is just a weak description -of `hbasm` syntax. +| Opcode | Mnemonic | Type | NaN is | +|:-------|:---------|:-----|:-------| +| 0x6A | FCMPLT32 | Fl32 | < | +| 0x6B | FCMPLT64 | Fl64 | < | +| 0x6C | FCMPGT32 | Fl32 | > | +| 0x6D | FCMPGT64 | Fl64 | > | -- Opcode names correspond to specified opcode names, lowercase (`nop`) -- Parameters are separated by comma (`addi r0, r0, 1`) -- Instructions are separated by either line feed or semicolon -- Registers are represented by `r` followed by the number (`r10`) -- Labels are defined by label name followed with colon (`loop:`) -- Labels are references simply by their name (`print`) -- Immediates are entered plainly. Negative numbers supported. \ No newline at end of file +# Int to float +- Type: `RR` +- Converts from `Si64` +- Operation: `#0 ← Fl(#1)` + +| Opcode | Mnemonic | Type | +|:-------|:---------|:-----| +| 0x6E | ITF32 | Fl32 | +| 0x6F | ITF64 | Fl64 | + +# Float to int +- Type: `RRB` +- Operation: `#0 ← Si64(#1)` +- Immediate `$2` specifies rounding mode + +| Opcode | Mnemonic | Type | +|:-------|:---------|:-----| +| 0x70 | FTI32 | Fl32 | +| 0x71 | FTI64 | Fl64 | + +# Fl32 to Fl64 +- Type: `RR` +- Operation: `#0 ← Fl64(#1)` + +| Opcode | Mnemonic | +|:-------|:---------| +| 0x72 | FC32T64 | + +# Fl64 to Fl32 +- Type: `RRB` +- Operation: `#0 ← Fl32(#1)` +- Immediate `$2` specified rounding mode + +| Opcode | Mnemonic | +|:-------|:---------| +| 0x73 | FC64T32 | + +# 16-bit relative address instruction variants + +| Opcode | Mnemonic | Type | Variant of | +|:-------|:---------|:-----|:-----------| +| 0x74 | LRA16 | RRP | LRA | +| 0x75 | LDR16 | RRPH | LDR | +| 0x76 | STR16 | RRPH | STR | +| 0x77 | JMP16 | P | JMP | From fc4118938e235be286523d5f0607dfe5dd4b5232 Mon Sep 17 00:00:00 2001 From: Erin Date: Sun, 22 Oct 2023 18:36:32 +0200 Subject: [PATCH 101/116] Spec update --- hbbytecode/instructions.in | 24 ++++++++++++------------ hbvm/src/vmrun.rs | 26 +++++++++++++------------- spec.md | 30 ++++++++++++++++++++++++++++-- 3 files changed, 53 insertions(+), 27 deletions(-) diff --git a/hbbytecode/instructions.in b/hbbytecode/instructions.in index e6a61fa4..f57ce9f0 100644 --- a/hbbytecode/instructions.in +++ b/hbbytecode/instructions.in @@ -54,18 +54,18 @@ 0x35, ANDI, RRD, "Bitand with immediate" ; 0x36, ORI, RRD, "Bitor with immediate" ; 0x37, XORI, RRD, "Bitxor with immediate" ; -0x38, SLUI8, RRW, "Unsigned left bitshift with immedidate (8b)" ; -0x39, SLUI16, RRW, "Unsigned left bitshift with immedidate (16b)"; -0x3A, SLUI32, RRW, "Unsigned left bitshift with immedidate (32b)"; -0x3B, SLUI64, RRW, "Unsigned left bitshift with immedidate (64b)"; -0x3C, SRUI8, RRW, "Unsigned right bitshift with immediate (8b)" ; -0x3D, SRUI16, RRW, "Unsigned right bitshift with immediate (16b)"; -0x3E, SRUI32, RRW, "Unsigned right bitshift with immediate (32b)"; -0x3F, SRUI64, RRW, "Unsigned right bitshift with immediate (64b)"; -0x40, SRSI8, RRW, "Signed right bitshift with immediate" ; -0x41, SRSI16, RRW, "Signed right bitshift with immediate" ; -0x42, SRSI32, RRW, "Signed right bitshift with immediate" ; -0x43, SRSI64, RRW, "Signed right bitshift with immediate" ; +0x38, SLUI8, RRB, "Unsigned left bitshift with immedidate (8b)" ; +0x39, SLUI16, RRB, "Unsigned left bitshift with immedidate (16b)"; +0x3A, SLUI32, RRB, "Unsigned left bitshift with immedidate (32b)"; +0x3B, SLUI64, RRB, "Unsigned left bitshift with immedidate (64b)"; +0x3C, SRUI8, RRB, "Unsigned right bitshift with immediate (8b)" ; +0x3D, SRUI16, RRB, "Unsigned right bitshift with immediate (16b)"; +0x3E, SRUI32, RRB, "Unsigned right bitshift with immediate (32b)"; +0x3F, SRUI64, RRB, "Unsigned right bitshift with immediate (64b)"; +0x40, SRSI8, RRB, "Signed right bitshift with immediate" ; +0x41, SRSI16, RRB, "Signed right bitshift with immediate" ; +0x42, SRSI32, RRB, "Signed right bitshift with immediate" ; +0x43, SRSI64, RRB, "Signed right bitshift with immediate" ; 0x44, CMPUI, RRD, "Unsigned compare with immediate" ; 0x45, CMPSI, RRD, "Signed compare with immediate" ; 0x46, CP, RR, "Copy register" ; diff --git a/hbvm/src/vmrun.rs b/hbvm/src/vmrun.rs index 70d68778..6abc6787 100644 --- a/hbvm/src/vmrun.rs +++ b/hbvm/src/vmrun.rs @@ -85,17 +85,17 @@ where AND => self.binary_op::(ops::BitAnd::bitand), OR => self.binary_op::(ops::BitOr::bitor), XOR => self.binary_op::(ops::BitXor::bitxor), - SLU8 => self.binary_op(|l, r| u8::wrapping_shl(l, r as u32)), - SLU16 => self.binary_op(|l, r| u16::wrapping_shl(l, r as u32)), - SLU32 => self.binary_op(u32::wrapping_shl), - SLU64 => self.binary_op(|l, r| u64::wrapping_shl(l, r as u32)), - SRU8 => self.binary_op(|l, r| u8::wrapping_shr(l, r as u32)), - SRU16 => self.binary_op(|l, r| u16::wrapping_shr(l, r as u32)), - SRU32 => self.binary_op(u32::wrapping_shr), - SRS8 => self.binary_op(|l: i8, r| i8::wrapping_shl(l, r as u32)), - SRS16 => self.binary_op(|l: i16, r| i16::wrapping_shl(l, r as u32)), - SRS32 => self.binary_op(|l: i32, r| i32::wrapping_shl(l, r as u32)), - SRS64 => self.binary_op(|l: i64, r| i64::wrapping_shl(l, r as u32)), + SLU8 => self.binary_op::(ops::Shl::shl), + SLU16 => self.binary_op::(ops::Shl::shl), + SLU32 => self.binary_op::(ops::Shl::shl), + SLU64 => self.binary_op::(ops::Shl::shl), + SRU8 => self.binary_op::(ops::Shr::shr), + SRU16 => self.binary_op::(ops::Shr::shr), + SRU32 => self.binary_op::(ops::Shr::shr), + SRS8 => self.binary_op::(ops::Shr::shr), + SRS16 => self.binary_op::(ops::Shr::shr), + SRS32 => self.binary_op::(ops::Shr::shr), + SRS64 => self.binary_op::(ops::Shr::shr), CMPU => handler!(self, |OpsRRR(tg, a0, a1)| self.cmp( tg, a0, @@ -459,8 +459,8 @@ where /// Perform binary operation over register and shift immediate #[inline(always)] - unsafe fn binary_op_ims(&mut self, op: impl Fn(T, u32) -> T) { - let OpsRRW(tg, reg, imm) = self.decode(); + unsafe fn binary_op_ims(&mut self, op: impl Fn(T, u8) -> T) { + let OpsRRB(tg, reg, imm) = self.decode(); self.write_reg(tg, op(self.read_reg(reg).cast::(), imm)); self.bump_pc::(); } diff --git a/spec.md b/spec.md index 37cd6f93..772373c7 100644 --- a/spec.md +++ b/spec.md @@ -28,10 +28,13 @@ - Xi*n*: Sign-agnostic integer of size *n* bits (Xi8, Xi16, Xi32, Xi64) - Fl*n*: Floating point number of size *n* bits (Fl32, Fl64) -# Behaviours -- Integer operations are always wrapping, including signed numbers +# Behaviour +- Integer operations are wrapping, including signed numbers + - Bitshifts are truncating - Two's complement - Floats as specified by IEEE 754 +- Execution model is implementation defined as long all observable + effects are performed in correct order ## Relative addressing Relative addresses are computed from address of the first byte @@ -51,6 +54,25 @@ of offset in the code. Not from the beginning of current or following instructio | Towards +∞ (up) | 0b10 | | Towards -∞ (down) | 0b11 | +- Remaining values in the byte traps with invalid operand exception + +# Memory +- Memory implementation is implementation-defined +- Zero address (`0x0`) is considered invalid + +# Traps +- Environment call +- Environment breakpoint + +Program counter goes to the following instruction + +## Exceptions +- Memory access fault +- Invalid operand +- Unknown opcode + +Program counter stays on the currently executed instruction + # Instructions - `#n`: register in parameter *n* - `$n`: for immediate in parameter *n* @@ -210,6 +232,10 @@ of offset in the code. Not from the beginning of current or following instructio | 0x36 | ORI | Disjunction (\|) | | 0x37 | XORI | Non-equivalence (^) | +# Register-immediate bitshifts +- Type: `RRB` +- Operation: `#0 ← #1 $2` + ## Unsigned left bitshift (`<<`) | Opcode | Mnemonic | Type | |:-------|:---------|:-----| From 84aeac0b2af53cce1b6c26ab4cf2e2d1c7e5bcd6 Mon Sep 17 00:00:00 2001 From: Erin Date: Sun, 22 Oct 2023 18:43:44 +0200 Subject: [PATCH 102/116] Hints of ABI --- spec.md | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/spec.md b/spec.md index 772373c7..70fdf052 100644 --- a/spec.md +++ b/spec.md @@ -29,6 +29,8 @@ - Fl*n*: Floating point number of size *n* bits (Fl32, Fl64) # Behaviour +- There is only one type of register, a general-purpose one. + Used for both integers and floats. - Integer operations are wrapping, including signed numbers - Bitshifts are truncating - Two's complement @@ -464,3 +466,32 @@ Program counter stays on the currently executed instruction | 0x75 | LDR16 | RRPH | LDR | | 0x76 | STR16 | RRPH | STR | | 0x77 | JMP16 | P | JMP | + +# psABI +## C datatypes and alignment +- One byte is 8 bits + +| C Type | Description | Byte sizes | +|:------------|:-------------------------|:-----------| +| char | Character / byte | 1 | +| short | SShort integer | 2 | +| int | Integer | 4 | +| long | Long integer | 8 | +| long long | Long long integer | 8 | +| float | Single-precision float | 4 | +| double | Double-precision float | 8 | +| long double | Extended-precision float | TBD | + +## Call convention +- Registers r1 – r30 are caller saved +- Registers r31 – r255 are callee saved + +| Register | Description | Saver | +|:---------|:--------------------|:-------| +| r0 | Hard-wired zero | N/A | +| r1 - r2 | Return values | Caller | +| r2 - r11 | Function parameters | Caller | +| r30 | Return address | Caller | + +If return value is too big to fit one register, r2 is also used. +TODO: Stack pointer, Thread pointer, ... From 9b823ef66047bbcbef33a32c16481536a1b09285 Mon Sep 17 00:00:00 2001 From: Erin Date: Sun, 22 Oct 2023 18:44:08 +0200 Subject: [PATCH 103/116] Untypo'd --- spec.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec.md b/spec.md index 70fdf052..a30e50e9 100644 --- a/spec.md +++ b/spec.md @@ -474,7 +474,7 @@ Program counter stays on the currently executed instruction | C Type | Description | Byte sizes | |:------------|:-------------------------|:-----------| | char | Character / byte | 1 | -| short | SShort integer | 2 | +| short | Short integer | 2 | | int | Integer | 4 | | long | Long integer | 8 | | long long | Long long integer | 8 | From 3a6d0fdd2d0dd38cbb329f48655493d76dbe7baf Mon Sep 17 00:00:00 2001 From: Erin Date: Sun, 22 Oct 2023 23:59:27 +0200 Subject: [PATCH 104/116] fixed match --- hbvm/src/vmrun.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/hbvm/src/vmrun.rs b/hbvm/src/vmrun.rs index 6abc6787..debe4236 100644 --- a/hbvm/src/vmrun.rs +++ b/hbvm/src/vmrun.rs @@ -275,8 +275,8 @@ where self.pc = self.pcrel(ja, 3); } } - JLT => self.cond_jmp::(Ordering::Less), - JGT => self.cond_jmp::(Ordering::Greater), + JLTS => self.cond_jmp::(Ordering::Less), + JGTS => self.cond_jmp::(Ordering::Greater), JLTU => self.cond_jmp::(Ordering::Less), JGTU => self.cond_jmp::(Ordering::Greater), ECA => { From d6243fa99f8076c18a5d94360f4cf7318d17e967 Mon Sep 17 00:00:00 2001 From: Erin Date: Sat, 28 Oct 2023 03:29:02 +0200 Subject: [PATCH 105/116] Squashed assembler --- Cargo.lock | 363 ++++++++++-------------------- Cargo.toml | 2 +- hbasm/Cargo.toml | 5 +- hbasm/assets/exit.lua | 3 - hbasm/assets/label.lua | 8 - hbasm/examples/ableos/main.rhai | 13 ++ hbasm/examples/ableos/std.rhai | 24 ++ hbasm/examples/hello-linux.rhai | 9 + hbasm/examples/macros.rhai | 33 +++ hbasm/src/arraylist.rs | 53 ----- hbasm/src/data.rs | 85 +++++++ hbasm/src/ins.rs | 386 +++++++++++++++++--------------- hbasm/src/label.rs | 149 ++++++------ hbasm/src/lib.rs | 45 ++++ hbasm/src/linker.rs | 67 +++--- hbasm/src/main.rs | 56 +---- hbasm/src/object.rs | 77 +++++++ hbbytecode/src/lib.rs | 2 +- hbvm/src/vmrun.rs | 8 +- hbvm_aos_on_linux/Cargo.toml | 9 + hbvm_aos_on_linux/readme.md | 3 + hbvm_aos_on_linux/src/main.rs | 96 ++++++++ hbvm_aos_on_linux/src/mem.rs | 31 +++ 23 files changed, 863 insertions(+), 664 deletions(-) delete mode 100644 hbasm/assets/exit.lua delete mode 100644 hbasm/assets/label.lua create mode 100644 hbasm/examples/ableos/main.rhai create mode 100644 hbasm/examples/ableos/std.rhai create mode 100644 hbasm/examples/hello-linux.rhai create mode 100644 hbasm/examples/macros.rhai delete mode 100644 hbasm/src/arraylist.rs create mode 100644 hbasm/src/data.rs create mode 100644 hbasm/src/lib.rs create mode 100644 hbasm/src/object.rs create mode 100644 hbvm_aos_on_linux/Cargo.toml create mode 100644 hbvm_aos_on_linux/readme.md create mode 100644 hbvm_aos_on_linux/src/main.rs create mode 100644 hbvm_aos_on_linux/src/mem.rs diff --git a/Cargo.lock b/Cargo.lock index ab773b4e..d6b4d512 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -18,12 +18,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" [[package]] -name = "aho-corasick" -version = "1.1.2" +name = "ahash" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0" +checksum = "cd7d5a2cecb58716e47d67d5703a249964b14c7be1ec3cad3affc295b2d1c35d" dependencies = [ - "memchr", + "cfg-if", + "const-random", + "getrandom", + "once_cell", + "version_check", + "zerocopy", ] [[package]] @@ -84,16 +89,6 @@ version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07" -[[package]] -name = "bstr" -version = "1.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c79ad7fb2dd38f3dabd76b09c6a5a20c038fc0213ef1e9afd30eb777f120f019" -dependencies = [ - "memchr", - "serde", -] - [[package]] name = "cc" version = "1.0.83" @@ -137,21 +132,31 @@ dependencies = [ ] [[package]] -name = "either" -version = "1.9.0" +name = "const-random" +version = "0.1.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" +checksum = "11df32a13d7892ec42d51d3d175faba5211ffe13ed25d4fb348ac9e9ce835593" +dependencies = [ + "const-random-macro", +] [[package]] -name = "errno" -version = "0.3.5" +name = "const-random-macro" +version = "0.1.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3e13f66a2f95e32a39eaa81f6b95d42878ca0e1db0c7543723dfe12557e860" +checksum = "f9d839f2a20b0aee515dc581a6172f2321f96cab76c1a38a4c584a194955390e" dependencies = [ - "libc", - "windows-sys", + "getrandom", + "once_cell", + "tiny-keccak", ] +[[package]] +name = "crunchy" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" + [[package]] name = "eyre" version = "0.6.8" @@ -162,6 +167,17 @@ dependencies = [ "once_cell", ] +[[package]] +name = "getrandom" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + [[package]] name = "gimli" version = "0.28.0" @@ -172,8 +188,8 @@ checksum = "6fb8d784f27acf97159b40fc4db5ecd8aa23b9ad5ef69cdd136d3bc80665f0c0" name = "hbasm" version = "0.1.0" dependencies = [ - "mlua", "paste", + "rhai", "with_builtin_macros", ] @@ -192,7 +208,7 @@ dependencies = [ ] [[package]] -name = "hbxrt" +name = "hbvm_aos_on_linux" version = "0.1.0" dependencies = [ "hbvm", @@ -200,12 +216,11 @@ dependencies = [ ] [[package]] -name = "home" -version = "0.5.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5444c27eef6923071f7ebcc33e3444508466a76f7a2b93da00ed6e19f30c1ddb" +name = "hbxrt" +version = "0.1.0" dependencies = [ - "windows-sys", + "hbvm", + "nix", ] [[package]] @@ -215,12 +230,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683" [[package]] -name = "itertools" -version = "0.11.0" +name = "instant" +version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57" +checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" dependencies = [ - "either", + "cfg-if", ] [[package]] @@ -235,31 +250,6 @@ version = "0.2.149" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a08173bc88b7955d1b3145aa561539096c421ac8debde8cbc3612ec635fee29b" -[[package]] -name = "linux-raw-sys" -version = "0.4.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da2479e8c062e40bf0066ffa0bc823de0a9368974af99c9f6df941d2c231e03f" - -[[package]] -name = "lua-src" -version = "546.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c26d4af78361e025a3d03a2b964cd1592aff7495f4d4f7947218c084c6fdca8" -dependencies = [ - "cc", -] - -[[package]] -name = "luajit-src" -version = "210.4.8+resty107baaf" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e05167e8b2a2185758d83ed23541e5bd8bce37072e4204e0ef2c9b322bc87c4e" -dependencies = [ - "cc", - "which", -] - [[package]] name = "memchr" version = "2.6.4" @@ -275,48 +265,6 @@ dependencies = [ "adler", ] -[[package]] -name = "mlua" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c3a7a7ff4481ec91b951a733390211a8ace1caba57266ccb5f4d4966704e560" -dependencies = [ - "bstr", - "mlua-sys", - "mlua_derive", - "num-traits", - "once_cell", - "rustc-hash", -] - -[[package]] -name = "mlua-sys" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ec8b54eddb76093069cce9eeffb4c7b3a1a0fe66962d7bd44c4867928149ca3" -dependencies = [ - "cc", - "cfg-if", - "lua-src", - "luajit-src", - "pkg-config", -] - -[[package]] -name = "mlua_derive" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f359220f24e6452dd82a3f50d7242d4aab822b5594798048e953d7a9e0314c6" -dependencies = [ - "itertools", - "once_cell", - "proc-macro-error", - "proc-macro2", - "quote", - "regex", - "syn 2.0.38", -] - [[package]] name = "nix" version = "0.27.1" @@ -370,36 +318,6 @@ version = "0.2.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" -[[package]] -name = "pkg-config" -version = "0.3.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" - -[[package]] -name = "proc-macro-error" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" -dependencies = [ - "proc-macro-error-attr", - "proc-macro2", - "quote", - "syn 1.0.109", - "version_check", -] - -[[package]] -name = "proc-macro-error-attr" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" -dependencies = [ - "proc-macro2", - "quote", - "version_check", -] - [[package]] name = "proc-macro2" version = "1.0.69" @@ -419,59 +337,38 @@ dependencies = [ ] [[package]] -name = "regex" -version = "1.10.2" +name = "rhai" +version = "1.16.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "380b951a9c5e80ddfd6136919eef32310721aa4aacd4889a8d39124b026ab343" +checksum = "206cee941730eaf90a22c84235b25193df661393688162e15551164f92f09eca" dependencies = [ - "aho-corasick", - "memchr", - "regex-automata", - "regex-syntax", + "ahash", + "bitflags", + "instant", + "num-traits", + "once_cell", + "rhai_codegen", + "smallvec", + "smartstring", ] [[package]] -name = "regex-automata" -version = "0.4.3" +name = "rhai_codegen" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f804c7828047e88b2d32e2d7fe5a105da8ee3264f01902f796c8e067dc2483f" +checksum = "853977598f084a492323fe2f7896b4100a86284ee8473612de60021ea341310f" dependencies = [ - "aho-corasick", - "memchr", - "regex-syntax", + "proc-macro2", + "quote", + "syn 2.0.38", ] -[[package]] -name = "regex-syntax" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" - [[package]] name = "rustc-demangle" version = "0.1.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" -[[package]] -name = "rustc-hash" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" - -[[package]] -name = "rustix" -version = "0.38.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67ce50cb2e16c2903e30d1cbccfd8387a74b9d4c938b6a4c5ec6cc7556f7a8a0" -dependencies = [ - "bitflags", - "errno", - "libc", - "linux-raw-sys", - "windows-sys", -] - [[package]] name = "serde" version = "1.0.189" @@ -501,6 +398,29 @@ dependencies = [ "lazy_static", ] +[[package]] +name = "smallvec" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "942b4a808e05215192e39f4ab80813e599068285906cc91aa64f923db842bd5a" + +[[package]] +name = "smartstring" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fb72c633efbaa2dd666986505016c32c3044395ceaf881518399d2f4127ee29" +dependencies = [ + "autocfg", + "static_assertions", + "version_check", +] + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + [[package]] name = "syn" version = "1.0.109" @@ -533,6 +453,15 @@ dependencies = [ "once_cell", ] +[[package]] +name = "tiny-keccak" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237" +dependencies = [ + "crunchy", +] + [[package]] name = "tracing" version = "0.1.40" @@ -593,82 +522,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" [[package]] -name = "which" -version = "4.4.2" +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87ba24419a2078cd2b0f2ede2691b6c66d8e47836da3b6db8265ebad47afbfc7" -dependencies = [ - "either", - "home", - "once_cell", - "rustix", -] - -[[package]] -name = "windows-sys" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" -dependencies = [ - "windows-targets", -] - -[[package]] -name = "windows-targets" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" -dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", -] - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" - -[[package]] -name = "windows_i686_gnu" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" - -[[package]] -name = "windows_i686_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "with_builtin_macros" @@ -698,3 +555,23 @@ dependencies = [ "color-eyre", "once_cell", ] + +[[package]] +name = "zerocopy" +version = "0.7.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ffc046c9f849405a42c87e82e2f2f861d1f0a06b855910c76c2bd1e87be900c" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "246c000cfc5f942bac7ff99fde24a9ebb589d92e024bc758c6c733c15a02a73e" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.38", +] diff --git a/Cargo.toml b/Cargo.toml index 91a11baa..02f53508 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,3 +1,3 @@ [workspace] resolver = "2" -members = ["hbasm", "hbbytecode", "hbvm", "hbxrt", "xtask"] +members = ["hbasm", "hbbytecode", "hbvm", "hbvm_aos_on_linux", "hbxrt", "xtask"] diff --git a/hbasm/Cargo.toml b/hbasm/Cargo.toml index 3fafd654..3cac5660 100644 --- a/hbasm/Cargo.toml +++ b/hbasm/Cargo.toml @@ -5,8 +5,5 @@ edition = "2021" [dependencies] paste = "1.0" +rhai = "1.16" with_builtin_macros = "0.0.3" - -[dependencies.mlua] -version = "0.9" -features = ["luajit52", "macros", "vendored"] diff --git a/hbasm/assets/exit.lua b/hbasm/assets/exit.lua deleted file mode 100644 index e563181a..00000000 --- a/hbasm/assets/exit.lua +++ /dev/null @@ -1,3 +0,0 @@ -li8(r1, 60) -li8(r2, 69) -eca() \ No newline at end of file diff --git a/hbasm/assets/label.lua b/hbasm/assets/label.lua deleted file mode 100644 index a88879fc..00000000 --- a/hbasm/assets/label.lua +++ /dev/null @@ -1,8 +0,0 @@ -label "label" -- set named label -local a = label {} -- unassigned label - -jmp16("label") -a:here() -- assign label -jmp16(a) - -addi8(r3, r4, 5) \ No newline at end of file diff --git a/hbasm/examples/ableos/main.rhai b/hbasm/examples/ableos/main.rhai new file mode 100644 index 00000000..0302a2ce --- /dev/null +++ b/hbasm/examples/ableos/main.rhai @@ -0,0 +1,13 @@ +import "hbasm/examples/ableos/std" as std; + +fn main(){ + std::Error(":+)"); + std::Warn("Your mom fell in a well!"); + std::Info("Hello, world!"); + std::Debug("ABC"); + std::Trace("Trace Deez"); + + tx(); +} + +main(); \ No newline at end of file diff --git a/hbasm/examples/ableos/std.rhai b/hbasm/examples/ableos/std.rhai new file mode 100644 index 00000000..5ff9deeb --- /dev/null +++ b/hbasm/examples/ableos/std.rhai @@ -0,0 +1,24 @@ +fn ipc_send(buffer_id, mem_addr, length){ + // set the ecall + li8(r1, 3); + // Set the buffer ID to be the BufferID + li64(r2, buffer_id); + lra(r3, r0, mem_addr); + // set the length + li64(r4, length); + // ecall + eca(); +} + +private fn log(log_level, string){ + let str = data::str(string); + ipc_send(1, str, str.len); +} + +fn Error(string) {log(0, string);} +fn Warn(string) {log(1, string);} +fn Info(string) {log(2, string);} +// Due to rhai limitations this cannot be debug +// because of this all of the log levels are upper case +fn Debug(string) {log(3, string);} +fn Trace(string) {log(4, string);} diff --git a/hbasm/examples/hello-linux.rhai b/hbasm/examples/hello-linux.rhai new file mode 100644 index 00000000..326a80a4 --- /dev/null +++ b/hbasm/examples/hello-linux.rhai @@ -0,0 +1,9 @@ +let hello = data::str("Hello, world!"); + +li8 (r1, 1); // Write syscall +li8 (r2, 1); // Stdout FD +lra16 (r3, r0, hello); // String buffer +li8 (r4, hello.len); // String length +eca (); // System call + +tx (); // End program \ No newline at end of file diff --git a/hbasm/examples/macros.rhai b/hbasm/examples/macros.rhai new file mode 100644 index 00000000..afd7bd2f --- /dev/null +++ b/hbasm/examples/macros.rhai @@ -0,0 +1,33 @@ +li8(r1, 69); +li8(r2, 0); + +if_eq(r1, r2, + || puts("Equals!"), + || puts("Not equals!"), +); + + +tx(); // END OF MAIN + +/// Inline function – write text to stdout +fn puts(string) { + let d = data::str(string); + li8 (r1, 1); // Write syscall + li8 (r2, 1); // Stdout handle + lra16 (r3, r0, d); + li64 (r4, d.len); + eca (); +} + +fn if_eq(a, b, thenblk, elseblk) { + let elselbl = declabel(); + let endlbl = declabel(); + + jne(a, b, elselbl); + thenblk.call(); + jmp16(endlbl); + + elselbl.here(); + elseblk.call(); + endlbl.here(); +} \ No newline at end of file diff --git a/hbasm/src/arraylist.rs b/hbasm/src/arraylist.rs deleted file mode 100644 index 439984c9..00000000 --- a/hbasm/src/arraylist.rs +++ /dev/null @@ -1,53 +0,0 @@ -use mlua::prelude::*; - -#[derive(Clone, Debug, Default, FromLua)] -pub struct ArrayList(pub Vec); -impl LuaUserData for ArrayList -where - T: for<'lua> FromLua<'lua> + for<'lua> IntoLua<'lua> + Clone + std::fmt::Debug, -{ - fn add_methods<'lua, M: LuaUserDataMethods<'lua, Self>>(methods: &mut M) { - methods.add_meta_method( - LuaMetaMethod::Index, - |lua, this, index: LuaInteger| match this.0.get( - (index as usize) - .checked_sub(1) - .ok_or_else(|| mlua::Error::runtime("Invalid index: 0"))?, - ) { - Some(i) => i.clone().into_lua(lua), - None => Ok(LuaValue::Nil), - }, - ); - - methods.add_meta_method_mut( - LuaMetaMethod::NewIndex, - |_, this, (index, val): (LuaInteger, T)| match this.0.get_mut( - (index as usize) - .checked_sub(1) - .ok_or_else(|| mlua::Error::runtime("Invalid index: 0"))?, - ) { - Some(x) => { - *x = val; - Ok(()) - } - None => Err(mlua::Error::runtime(format!( - "Index out of bounds: length = {}, index = {index}", - this.0.len() - ))), - }, - ); - - methods.add_meta_method(LuaMetaMethod::Len, |_, this, ()| Ok(this.0.len())); - methods.add_meta_method(LuaMetaMethod::ToString, |_, this, ()| { - Ok(format!("{this:?}")) - }); - methods.add_method_mut("push", |_, this, val: T| { - this.0.push(val); - Ok(()) - }); - methods.add_method_mut("pop", |lua, this, ()| match this.0.pop() { - Some(val) => val.into_lua(lua), - None => Ok(LuaValue::Nil), - }); - } -} diff --git a/hbasm/src/data.rs b/hbasm/src/data.rs new file mode 100644 index 00000000..a95c2558 --- /dev/null +++ b/hbasm/src/data.rs @@ -0,0 +1,85 @@ +use rhai::{CustomType, Engine, ImmutableString}; + +use { + crate::{object::SymbolRef, SharedObject}, + rhai::Module, +}; + +macro_rules! gen_data_insertions { + ($module:expr, $obj:expr, [$($ty:ident),* $(,)?] $(,)?) => {{ + let (module, obj) = ($module, $obj); + $({ + let obj = ::std::rc::Rc::clone(obj); + let hash = module.set_native_fn(stringify!($ty), move |arr: ::rhai::Array| { + let obj = &mut *obj.borrow_mut(); + let symbol = obj.symbol($crate::object::Section::Data); + + obj.sections + .data + .reserve(arr.len() * ::std::mem::size_of::<$ty>()); + + for item in arr { + obj.sections.data.extend( + match item.as_int() { + Ok(num) => $ty::try_from(num).map_err(|_| "i64".to_owned()), + Err(ty) => Err(ty.to_owned()), + } + .map_err(|err| { + ::rhai::EvalAltResult::ErrorMismatchDataType( + stringify!($ty).to_owned(), + err, + ::rhai::Position::NONE, + ) + })? + .to_le_bytes(), + ); + } + + Ok(DataRef { + symbol, + len: obj.sections.data.len() - symbol.0, + }) + }); + + module.update_fn_namespace(hash, ::rhai::FnNamespace::Global); + })* + }}; +} + +#[derive(Clone, Copy, Debug)] +pub struct DataRef { + pub symbol: SymbolRef, + pub len: usize, +} + +impl CustomType for DataRef { + fn build(mut builder: rhai::TypeBuilder) { + builder + .with_name("DataRef") + .with_get("symbol", |this: &mut Self| this.symbol) + .with_get("len", |this: &mut Self| this.len as u64 as i64); + } +} + +pub fn module(engine: &mut Engine, obj: SharedObject) -> Module { + let mut module = Module::new(); + gen_data_insertions!(&mut module, &obj, [i8, i16, i32, i64]); + + { + let hash = module.set_native_fn("str", move |s: ImmutableString| { + let obj = &mut *obj.borrow_mut(); + let symbol = obj.symbol(crate::object::Section::Data); + + obj.sections.data.extend(s.as_bytes()); + Ok(DataRef { + symbol, + len: s.len(), + }) + }); + + module.update_fn_namespace(hash, rhai::FnNamespace::Global); + } + + engine.build_type::(); + module +} diff --git a/hbasm/src/ins.rs b/hbasm/src/ins.rs index f20bd180..590e23dd 100644 --- a/hbasm/src/ins.rs +++ b/hbasm/src/ins.rs @@ -1,194 +1,226 @@ use { - crate::arraylist::ArrayList, - mlua::{prelude::*, Result, Scope}, + crate::object::Object, + rhai::{FnNamespace, Module}, + std::{cell::RefCell, rc::Rc}, }; -mod opsty { - pub type R = i8; +mod optypes { + use { + crate::{ + label::UnboundLabel, + object::{Object, RelocKey, RelocType, SymbolRef}, + }, + rhai::{Dynamic, EvalAltResult, ImmutableString, Position}, + }; + + pub type R = u8; pub type B = i8; pub type H = i16; pub type W = i32; pub type D = i64; - pub type A = i64; - pub type O<'lua> = super::LuaValue<'lua>; - pub type P<'lua> = super::LuaValue<'lua>; -} -macro_rules! gen_insert { - ($($plain:ident),* $(,)?) => { - macro_rules! insert { - $( - ($label:expr, $lua:expr, $symrt:expr, $code:expr, $plain) => { - $code.0.extend($label.to_le_bytes()); - }; - )* + pub type A = Dynamic; + pub type O = Dynamic; + pub type P = Dynamic; - ($label:expr, $lua:expr, $symrt:expr, $code:expr, O) => { - insert_label_ref::<4>( - $label, - $lua, - &$symrt, - &mut $code.0, - )?; - }; - - ($label:expr, $lua:expr, $symrt:expr, $code:expr, P) => { - insert_label_ref::<2>( - $label, - $lua, - &$symrt, - &mut $code.0, - )?; - }; - } - }; -} - -gen_insert!(R, B, H, W, D, A); - -macro_rules! generic_ins { - { - ($lua:expr, $scope:expr); - $($name:ident($($param_i:ident : $param_ty:ident),*);)* - } => {{ - let lua = $lua; - let scope = $scope; - - let code = $lua.globals() - .get::<_, LuaTable>("_CODE")? - .get::<_, LuaAnyUserData>("text")?; - - let symrt = $lua.globals() - .get::<_, LuaTable>("_SYM_REPLS")?; - - lua.globals().set( - "_GENERIC", - lua.create_table_from([$(( - stringify!($name), - #[allow(unused)] - { - let code = code.clone(); - let symrt = symrt.clone(); - scope.create_function_mut(move |lua, (opcode, $($param_i),*): (u8, $(opsty::$param_ty),*)| { - let mut code = code.borrow_mut::>()?; - code.0.push(opcode); - $(insert!($param_i, lua, symrt, code, $param_ty);)* - Ok(()) - })? - } - )),*])? - )?; - }}; -} - -macro_rules! ins { - { - $lua:expr; - {$($opcode:expr, $mnemonic:ident, $ty:ident, $_doc:literal;)*} - } => {{ - use std::fmt::Write; - - let lua = $lua; - let mut code = String::new(); - - $({ - paste::paste! { - let name = match stringify!([<$mnemonic:lower>]) { - "and" => "and_", - "or" => "or_", - "not" => "not_", - name => name, - }; + pub fn insert_reloc( + obj: &mut Object, + ty: RelocType, + val: &Dynamic, + ) -> Result<(), EvalAltResult> { + match () { + _ if val.is::() => { + obj.relocation(RelocKey::Symbol(val.clone_cast::().0), ty) } - - writeln!( - code, - "function {name}(...) _GENERIC.{ty}({opcode}, ...) end", - ty = stringify!($ty).to_lowercase(), - opcode = $opcode, - ).unwrap(); - - })* - - lua.load(code).exec()?; - }}; -} - -pub fn setup<'lua, 'scope>(lua: &'lua Lua, scope: &Scope<'lua, 'scope>) -> Result<()> -where - 'lua: 'scope, -{ - generic_ins! { - (lua, scope); - rr (o0: R, o1: R); - rrr (o0: R, o1: R, o2: R); - rrrr(o0: R, o1: R, o2: R, o3: R); - rrb (o0: R, o1: R, o2: B); - rrh (o0: R, o1: R, o2: H); - rrw (o0: R, o1: R, o2: W); - rrd (o0: R, o1: R, o2: D); - rb (o0: R, o1: B); - rh (o0: R, o1: H); - rw (o0: R, o1: W); - rd (o0: R, o1: D); - rrah(o0: R, o1: R, o2: A, o3: H); - rroh(o0: R, o1: R, o2: O, o3: H); - rrph(o0: R, o1: R, o2: P, o3: H); - rro (o0: R, o1: R, o2: O); - rrp (o0: R, o1: R, r2: P); - o (o0: O); - p (o0: P); - n (); - } - - with_builtin_macros::with_builtin! { - let $spec = include_from_root!("../hbbytecode/instructions.in") in { - ins!(lua; { $spec }); - } - } - - Ok(()) -} - -fn insert_label_ref( - label: LuaValue, - lua: &Lua, - symrt: &LuaTable, - code: &mut Vec, -) -> Result<()> { - match label { - LuaValue::Integer(offset) => { - if match SIZE { - 2 => i16::try_from(offset).map(|o| code.extend(o.to_le_bytes())), - 4 => i32::try_from(offset).map(|o| code.extend(o.to_le_bytes())), - s => { - return Err(mlua::Error::runtime(format!( - "Invalid offset size (expected 2 or 4 bytes, got {s})" - ))); + _ if val.is::() => { + obj.relocation(RelocKey::Symbol(val.clone_cast::().0), ty) + } + _ if val.is::() => { + obj.relocation(RelocKey::Symbol(val.clone_cast::().symbol.0), ty) + } + _ if val.is_string() => { + obj.relocation(RelocKey::Label(val.clone_cast::()), ty) + } + _ if val.is_int() => { + let int = val.clone_cast::(); + match ty { + RelocType::Rel32 => obj.sections.text.extend((int as i32).to_le_bytes()), + RelocType::Rel16 => obj.sections.text.extend((int as i16).to_le_bytes()), + RelocType::Abs64 => obj.sections.text.extend(int.to_le_bytes()), } } - .is_err() - { - return Err(mlua::Error::runtime("Failed to cast offset")); + _ => { + return Err(EvalAltResult::ErrorMismatchDataType( + "SybolRef, UnboundLabel, String or Int".to_owned(), + val.type_name().to_owned(), + Position::NONE, + )) } - return Ok(()); } - LuaValue::UserData(ud) => { - symrt.set( - code.len() + 1, - lua.create_table_from([("label", ud.get("id")?), ("size", SIZE)])?, - )?; - code.extend([0; SIZE]); - } - LuaValue::String(_) => { - symrt.set( - code.len() + 1, - lua.create_table_from([("label", label), ("size", SIZE.into_lua(lua)?)])?, - )?; - code.extend([0; SIZE]); - } - _ => return Err(mlua::Error::runtime("Invalid label type")), + + Ok(()) } - Ok(()) + macro_rules! gen_insert { + (le_bytes: [$($lety:ident),* $(,)?]) => { + macro_rules! insert { + $(($thing:expr, $obj: expr, $lety) => { + $obj.sections.text.extend($thing.to_le_bytes()); + };)* + + ($thing:expr, $obj:expr, A) => { + $crate::ins::optypes::insert_reloc( + $obj, + $crate::object::RelocType::Abs64, + $thing + )? + }; + ($thing:expr, $obj:expr, O) => { + $crate::ins::optypes::insert_reloc( + $obj, + $crate::object::RelocType::Rel32, + $thing + )? + }; + ($thing:expr, $obj:expr, P) => { + $crate::ins::optypes::insert_reloc( + $obj, + $crate::object::RelocType::Rel16, + $thing + )? + }; + } + }; + } + + gen_insert!(le_bytes: [R, B, H, W, D]); + + #[allow(clippy::single_component_path_imports)] + pub(super) use insert; + + use crate::data::DataRef; +} + +mod rity { + pub use super::optypes::{A, O, P, R}; + pub type B = i64; + pub type H = i64; + pub type W = i64; + pub type D = i64; +} + +mod generic { + use {crate::object::Object, rhai::EvalAltResult}; + + pub(super) fn convert_op(from: A) -> Result + where + B: TryFrom, + >::Error: std::error::Error + Sync + Send + 'static, + { + B::try_from(from).map_err(|e| { + EvalAltResult::ErrorSystem("Data conversion error".to_owned(), Box::new(e)) + }) + } + + macro_rules! gen_ins { + ($($($name:ident : $ty:ty),*;)*) => { + paste::paste! { + $(#[inline] + pub fn [<$($ty:lower)*>]( + obj: &mut Object, + opcode: u8, + $($name: $crate::ins::optypes::$ty),*, + ) -> Result<(), EvalAltResult> { + obj.sections.text.push(opcode); + $($crate::ins::optypes::insert!(&$name, obj, $ty);)* + Ok(()) + })* + + macro_rules! gen_ins_fn { + $(($obj:expr, $opcode:expr, [<$($ty)*>]) => { + move |$($name: $crate::ins::rity::$ty),*| { + $crate::ins::generic::[<$($ty:lower)*>]( + &mut *$obj.borrow_mut(), + $opcode, + $( + $crate::ins::generic::convert_op::< + _, + $crate::ins::optypes::$ty + >($name)? + ),* + )?; + Ok(()) + } + };)* + + ($obj:expr, $opcode:expr, N) => { + move || { + $crate::ins::generic::n(&mut *$obj.borrow_mut(), $opcode); + Ok(()) + } + }; + } + } + }; + } + + #[inline] + pub fn n(obj: &mut Object, opcode: u8) { + obj.sections.text.push(opcode); + } + + gen_ins! { + o0: R, o1: R; + o0: R, o1: R, o2: R; + o0: R, o1: R, o2: R, o3: R; + o0: R, o1: R, o2: B; + o0: R, o1: R, o2: H; + o0: R, o1: R, o2: W; + o0: R, o1: R, o2: D; + o0: R, o1: B; + o0: R, o1: H; + o0: R, o1: W; + o0: R, o1: D; + o0: R, o1: R, o2: A; + o0: R, o1: R, o2: A, o3: H; + o0: R, o1: R, o2: O, o3: H; + o0: R, o1: R, o2: P, o3: H; + o0: R, o1: R, o2: O; + o0: R, o1: R, o2: P; + o0: O; + o0: P; + } + + #[allow(clippy::single_component_path_imports)] + pub(super) use gen_ins_fn; +} + +macro_rules! instructions { + ( + ($module:expr, $obj:expr $(,)?) + { $($opcode:expr, $mnemonic:ident, $ops:ident, $doc:literal;)* } + ) => {{ + let (module, obj) = ($module, $obj); + $({ + let obj = Rc::clone(&obj); + let hash = module.set_native_fn( + paste::paste!(stringify!([<$mnemonic:lower>])), + generic::gen_ins_fn!( + obj, + $opcode, + $ops + ) + ); + + module.update_fn_namespace(hash, FnNamespace::Global); + })* + }}; +} + +pub fn setup(module: &mut Module, obj: Rc>) { + with_builtin_macros::with_builtin! { + let $spec = include_from_root!("../hbbytecode/instructions.in") in { + instructions!((module, obj) { $spec }); + } + } } diff --git a/hbasm/src/label.rs b/hbasm/src/label.rs index da0c1cdc..d1547373 100644 --- a/hbasm/src/label.rs +++ b/hbasm/src/label.rs @@ -1,91 +1,76 @@ use { - crate::{arraylist::ArrayList, Labels}, - mlua::{Result, prelude::*}, + crate::SharedObject, + rhai::{Engine, ImmutableString, Module}, }; -#[derive(Clone, Copy, Debug, PartialEq, Eq, FromLua)] -pub struct UnassignedLabel(pub usize); -impl LuaUserData for UnassignedLabel { - fn add_fields<'lua, F: LuaUserDataFields<'lua, Self>>(fields: &mut F) { - fields.add_field_method_get("id", |_, this| Ok(this.0)); +#[derive(Clone, Copy, Debug)] +pub struct UnboundLabel(pub usize); + +pub fn setup(engine: &mut Engine, module: &mut Module, object: SharedObject) { + { + let object = SharedObject::clone(&object); + let hash = module.set_native_fn("label", move || { + let mut obj = object.borrow_mut(); + let symbol = obj.symbol(crate::object::Section::Text); + Ok(symbol) + }); + + module.update_fn_namespace(hash, rhai::FnNamespace::Global); } - fn add_methods<'lua, M: LuaUserDataMethods<'lua, Self>>(methods: &mut M) { - methods.add_method("here", |lua, this, ()| { - match lua - .globals() - .get::<_, LuaUserDataRefMut>("_LABELS")? - .0 - .get_mut( - this.0 - .checked_sub(1) - .ok_or_else(|| mlua::Error::runtime("Invalid label"))?, - ) { - Some(entry) => { - *entry = Some( - lua.globals() - .get::<_, LuaTable>("_CODE")? - .get::<_, LuaUserDataRef>>("text")? - .0 - .len(), - ); + { + let object = SharedObject::clone(&object); + let hash = module.set_native_fn("label", move |label: ImmutableString| { + let mut obj = object.borrow_mut(); + let symbol = obj.symbol(crate::object::Section::Text); + obj.labels.insert(label, symbol.0); - Ok(()) - } - None => Err(mlua::Error::runtime("Invalid label")), - } + Ok(symbol) + }); + + module.update_fn_namespace(hash, rhai::FnNamespace::Global); + } + + { + let object = SharedObject::clone(&object); + let hash = module.set_native_fn("declabel", move || { + let mut obj = object.borrow_mut(); + + let index = obj.symbols.len(); + obj.symbols.push(None); + + Ok(UnboundLabel(index)) + }); + + module.update_fn_namespace(hash, rhai::FnNamespace::Global); + } + + { + let object = SharedObject::clone(&object); + let hash = module.set_native_fn("declabel", move |label: ImmutableString| { + let mut obj = object.borrow_mut(); + + let index = obj.symbols.len(); + obj.symbols.push(None); + obj.labels.insert(label, index); + + Ok(UnboundLabel(index)) + }); + + module.update_fn_namespace(hash, rhai::FnNamespace::Global); + } + + { + module.set_native_fn("here", move |label: UnboundLabel| { + let mut obj = object.borrow_mut(); + obj.symbols[label.0] = Some(crate::object::SymbolEntry { + location: crate::object::Section::Text, + offset: obj.sections.text.len(), + }); + + Ok(()) }); } -} - -#[derive(Clone, Copy, Debug, PartialEq, Eq, FromLua)] -pub struct Label(pub usize); -impl LuaUserData for Label { - fn add_fields<'lua, F: LuaUserDataFields<'lua, Self>>(fields: &mut F) { - fields.add_field_method_get("id", |_, this| Ok(this.0)); - } -} - -pub fn label<'lua>(lua: &'lua Lua, val: LuaValue<'lua>) -> Result> { - let globals = lua.globals(); - let mut labels = globals.get::<_, LuaUserDataRefMut>("_LABELS")?; - - let code_ix = globals - .get::<_, LuaTable>("_CODE")? - .get::<_, LuaUserDataRefMut>>("text")? - .0 - .len(); - - match val { - LuaValue::Table(_) => { - labels.0.push(None); - Ok(LuaValue::UserData( - lua.create_userdata(UnassignedLabel(labels.0.len()))?, - )) - } - LuaValue::String(str) => { - labels.0.push(Some(code_ix + 1)); - globals - .get::<_, LuaTable>("_SYM_TABLE")? - .set(str, labels.0.len())?; - - Ok(LuaValue::UserData( - lua.create_userdata(Label(labels.0.len()))?, - )) - } - LuaNil => { - labels.0.push(Some(code_ix + 1)); - Ok(LuaValue::UserData( - lua.create_userdata(Label(labels.0.len()))?, - )) - } - _ => Err(mlua::Error::BadArgument { - to: Some("label".into()), - pos: 1, - name: None, - cause: std::sync::Arc::new(mlua::Error::runtime( - "Unsupported type (nil and string are only supported)", - )), - }), - } + + engine.register_type_with_name::("UnboundLabel"); } diff --git a/hbasm/src/lib.rs b/hbasm/src/lib.rs new file mode 100644 index 00000000..c4a7c875 --- /dev/null +++ b/hbasm/src/lib.rs @@ -0,0 +1,45 @@ +mod data; +mod ins; +mod label; +mod linker; +mod object; + +use { + object::Object, + rhai::{Engine, Module}, + std::{cell::RefCell, rc::Rc}, +}; + +type SharedObject = Rc>; + +pub fn assembler( + linkout: &mut impl std::io::Write, + loader: impl FnOnce(&mut Engine) -> Result<(), Box>, +) -> Result<(), Box> { + let mut engine = Engine::new(); + let mut module = Module::new(); + let obj = Rc::new(RefCell::new(Object::default())); + ins::setup(&mut module, Rc::clone(&obj)); + label::setup(&mut engine, &mut module, Rc::clone(&obj)); + + // Registers + for n in 0_u8..255 { + module.set_var(format!("r{n}"), n); + } + + module.set_native_fn("reg", |n: i64| { + Ok(u8::try_from(n).map_err(|_| { + rhai::EvalAltResult::ErrorRuntime("Invalid register value".into(), rhai::Position::NONE) + })?) + }); + + module.set_native_fn("as_i64", |n: u8| Ok(n as i64)); + + let datamod = Rc::new(data::module(&mut engine, SharedObject::clone(&obj))); + engine.register_global_module(Rc::new(module)); + engine.register_static_module("data", datamod); + engine.register_type_with_name::("SymbolRef"); + loader(&mut engine)?; + linker::link(obj, linkout)?; + Ok(()) +} diff --git a/hbasm/src/linker.rs b/hbasm/src/linker.rs index 566c2dfa..d31c73b2 100644 --- a/hbasm/src/linker.rs +++ b/hbasm/src/linker.rs @@ -1,44 +1,35 @@ -use mlua::prelude::*; +use { + crate::{ + object::{RelocKey, RelocType, Section}, + SharedObject, + }, + std::io::Write, +}; -pub fn link( - symrt: LuaTable, - symtab: LuaTable, - labels: &[Option], - code: &mut [u8], - out: &mut impl std::io::Write, -) -> mlua::Result<()> { - for item in symrt.pairs::() { - let (loc, val) = item?; - let size: usize = val.get("size")?; - let dest = labels - .get( - match val.get::<_, LuaValue>("label")? { - LuaValue::Integer(i) => i, - LuaValue::String(s) => symtab.get(s)?, - _ => { - return Err(mlua::Error::runtime( - "Invalid symbol type (int or string expected)", - )) - } - } as usize - - 1, - ) - .copied() - .flatten() - .ok_or_else(|| mlua::Error::runtime("Invalid label"))?; +pub fn link(object: SharedObject, out: &mut impl Write) -> std::io::Result<()> { + let obj = &mut *object.borrow_mut(); + for (&loc, entry) in &obj.relocs { + let value = match &entry.key { + RelocKey::Symbol(sym) => obj.symbols[*sym], + RelocKey::Label(label) => obj.symbols[obj.labels[label]], + } + .ok_or_else(|| std::io::Error::other("Invalid symbol"))?; - let loc = loc - 1; - let dest = dest - 1; - - let offset = dest.wrapping_sub(loc); - match size { - 4 => code[loc..loc + size].copy_from_slice(&(offset as u32).to_le_bytes()), - 2 => code[loc..loc + size].copy_from_slice(&(offset as u16).to_le_bytes()), - _ => return Err(mlua::Error::runtime("Invalid symbol")), + let offset = match value.location { + Section::Text => value.offset, + Section::Data => value.offset + obj.sections.text.len(), + }; + + match entry.ty { + RelocType::Rel32 => obj.sections.text[loc..loc + 4] + .copy_from_slice(&((offset as isize - loc as isize) as i32).to_le_bytes()), + RelocType::Rel16 => obj.sections.text[loc..loc + 2] + .copy_from_slice(&((offset as isize - loc as isize) as i16).to_le_bytes()), + RelocType::Abs64 => obj.sections.text[loc..loc + 8] + .copy_from_slice(&(offset as isize - loc as isize).to_le_bytes()), } } - dbg!(&code); - out.write_all(code)?; - Ok(()) + out.write_all(&obj.sections.text)?; + out.write_all(&obj.sections.data) } diff --git a/hbasm/src/main.rs b/hbasm/src/main.rs index a9a0f9a1..ca9a2585 100644 --- a/hbasm/src/main.rs +++ b/hbasm/src/main.rs @@ -1,54 +1,8 @@ -mod arraylist; -mod ins; -mod label; -mod linker; - -use {arraylist::ArrayList, mlua::{Result, prelude::*}, std::io::Read}; - -pub type Labels = ArrayList>; - -fn main() -> Result<()> { - let mut code = vec![]; - std::io::stdin().read_to_end(&mut code)?; - - let lua = Lua::new(); - lua.scope(|scope| { - // Global values - let globals = lua.globals(); - globals.set( - "_CODE", - lua.create_table_from([ - ("text", ArrayList::::default()), - ("data", ArrayList::::default()), - ])?, - )?; - - globals.set("_LABELS", Labels::default())?; - globals.set("_SYM_TABLE", lua.create_table()?)?; - globals.set("_SYM_REPLS", lua.create_table()?)?; - - // Functions - globals.set("label", lua.create_function(label::label)?)?; - ins::setup(&lua, scope)?; - - // Register symbols - for n in 0..255 { - globals.set(format!("r{n}"), n)?; - } - - lua.load(code).exec()?; - - linker::link( - globals.get("_SYM_REPLS")?, - globals.get("_SYM_TABLE")?, - &globals.get::<_, Labels>("_LABELS")?.0, - &mut globals - .get::<_, LuaTable>("_CODE")? - .get::<_, LuaUserDataRefMut>>("text")? - .0, - &mut std::io::stdout(), - ) - })?; +use std::{io::stdout, path::PathBuf}; +fn main() -> Result<(), Box> { + let path = PathBuf::from(std::env::args().nth(1).ok_or("Missing path")?); + hbasm::assembler(&mut stdout(), |engine| engine.run_file(path))?; + Ok(()) } diff --git a/hbasm/src/object.rs b/hbasm/src/object.rs new file mode 100644 index 00000000..bae43df0 --- /dev/null +++ b/hbasm/src/object.rs @@ -0,0 +1,77 @@ +use {rhai::ImmutableString, std::collections::HashMap}; + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum Section { + Text, + Data, +} + +#[derive(Clone, Copy, Debug)] +pub struct SymbolEntry { + pub location: Section, + pub offset: usize, +} + +#[derive(Clone, Debug)] +pub enum RelocKey { + Symbol(usize), + Label(ImmutableString), +} + +#[derive(Clone, Copy, Debug)] +pub enum RelocType { + Rel32, + Rel16, + Abs64, +} + +#[derive(Clone, Debug)] +pub struct RelocEntry { + pub key: RelocKey, + pub ty: RelocType, +} + +#[derive(Clone, Debug, Default)] +pub struct Sections { + pub text: Vec, + pub data: Vec, +} + +#[derive(Clone, Debug, Default)] +pub struct Object { + pub sections: Sections, + pub symbols: Vec>, + pub labels: HashMap, + pub relocs: HashMap, +} + +#[derive(Clone, Copy, Debug)] +#[repr(transparent)] +pub struct SymbolRef(pub usize); + +impl Object { + pub fn symbol(&mut self, section: Section) -> SymbolRef { + let section_buf = match section { + Section::Text => &mut self.sections.text, + Section::Data => &mut self.sections.data, + }; + + self.symbols.push(Some(SymbolEntry { + location: section, + offset: section_buf.len(), + })); + + SymbolRef(self.symbols.len() - 1) + } + + pub fn relocation(&mut self, key: RelocKey, ty: RelocType) { + self.relocs + .insert(self.sections.text.len(), RelocEntry { key, ty }); + + self.sections.text.extend(match ty { + RelocType::Rel32 => &[0_u8; 4] as &[u8], + RelocType::Rel16 => &[0; 2], + RelocType::Abs64 => &[0; 8], + }); + } +} diff --git a/hbbytecode/src/lib.rs b/hbbytecode/src/lib.rs index 4fe81bad..488ebe73 100644 --- a/hbbytecode/src/lib.rs +++ b/hbbytecode/src/lib.rs @@ -34,11 +34,11 @@ define_items! { OpsRRB (OpR, OpR, OpB ), OpsRRH (OpR, OpR, OpH ), OpsRRW (OpR, OpR, OpW ), + OpsRRD (OpR, OpR, OpD ), OpsRB (OpR, OpB ), OpsRH (OpR, OpH ), OpsRW (OpR, OpW ), OpsRD (OpR, OpD ), - OpsRRD (OpR, OpR, OpD ), OpsRRA (OpR, OpR, OpA ), OpsRRAH (OpR, OpR, OpA, OpH), OpsRROH (OpR, OpR, OpO, OpH), diff --git a/hbvm/src/vmrun.rs b/hbvm/src/vmrun.rs index debe4236..fbf7f3d7 100644 --- a/hbvm/src/vmrun.rs +++ b/hbvm/src/vmrun.rs @@ -273,6 +273,8 @@ where let OpsRRP(a0, a1, ja) = self.decode(); if self.read_reg(a0).cast::() != self.read_reg(a1).cast::() { self.pc = self.pcrel(ja, 3); + } else { + self.bump_pc::(); } } JLTS => self.cond_jmp::(Ordering::Less), @@ -453,7 +455,7 @@ where let OpsRR(tg, reg) = self.decode(); let imm: T = self.decode(); self.write_reg(tg, op(self.read_reg(reg).cast::(), imm)); - self.bump_pc::(); + self.bump_pc::(); self.bump_pc::(); } @@ -527,9 +529,9 @@ where == expected { self.pc = self.pcrel(ja, 3); + } else { + self.bump_pc::(); } - - self.bump_pc::(); } /// Read register diff --git a/hbvm_aos_on_linux/Cargo.toml b/hbvm_aos_on_linux/Cargo.toml new file mode 100644 index 00000000..a82122dd --- /dev/null +++ b/hbvm_aos_on_linux/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "hbvm_aos_on_linux" +version = "0.1.0" +edition = "2021" +default-run = "hbvm_aos_on_linux" + +[dependencies] +hbvm.path = "../hbvm" +nix = { version = "0.27", features = ["mman", "signal"] } diff --git a/hbvm_aos_on_linux/readme.md b/hbvm_aos_on_linux/readme.md new file mode 100644 index 00000000..24d919f9 --- /dev/null +++ b/hbvm_aos_on_linux/readme.md @@ -0,0 +1,3 @@ +As close to the AbleOS runtime as possible + +useful for me to spec out things on my laptop \ No newline at end of file diff --git a/hbvm_aos_on_linux/src/main.rs b/hbvm_aos_on_linux/src/main.rs new file mode 100644 index 00000000..38dea08b --- /dev/null +++ b/hbvm_aos_on_linux/src/main.rs @@ -0,0 +1,96 @@ +//! Holey Bytes Experimental Runtime +mod mem; + +use { + hbvm::{mem::Address, Vm, VmRunOk}, + nix::sys::mman::{mmap, MapFlags, ProtFlags}, + std::{env::args, fs::File, num::NonZeroUsize, process::exit}, +}; + +fn main() -> Result<(), Box> { + eprintln!("== HB×RT (Holey Bytes Linux Runtime) v0.1 =="); + eprintln!("[W] Currently supporting only flat images"); + + let Some(image_path) = args().nth(1) else { + eprintln!("[E] Missing image path"); + exit(1); + }; + + // Load program + eprintln!("[I] Loading image from \"{image_path}\""); + let file = File::open(image_path)?; + let ptr = unsafe { + mmap( + None, + NonZeroUsize::new(file.metadata()?.len() as usize).ok_or("File is empty")?, + ProtFlags::PROT_READ, + MapFlags::MAP_PRIVATE, + Some(&file), + 0, + )? + }; + + eprintln!("[I] Image loaded at {ptr:p}"); + + // Execute program + let mut vm = unsafe { Vm::<_, 0>::new(mem::HostMemory, Address::new(ptr as u64)) }; + + // Memory access fault handling + unsafe { + use nix::sys::signal; + + extern "C" fn action( + _: std::ffi::c_int, + info: *mut nix::libc::siginfo_t, + _: *mut std::ffi::c_void, + ) { + unsafe { + eprintln!("[E] Memory access fault at {:p}", (*info).si_addr()); + } + } + + signal::sigaction( + signal::Signal::SIGSEGV, + &nix::sys::signal::SigAction::new( + signal::SigHandler::SigAction(action), + signal::SaFlags::SA_NODEFER, + nix::sys::signalfd::SigSet::empty(), + ), + )?; + } + + let stat = loop { + match vm.run() { + Ok(VmRunOk::Breakpoint) => eprintln!( + "[I] Hit breakpoint\nIP: {}\n== Registers ==\n{:?}", + vm.pc, vm.registers + ), + Ok(VmRunOk::Timer) => (), + Ok(VmRunOk::Ecall) => { + + // unsafe { + // std::arch::asm!( + // "syscall", + // inlateout("rax") vm.registers[1].0, + // in("rdi") vm.registers[2].0, + // in("rsi") vm.registers[3].0, + // in("rdx") vm.registers[4].0, + // in("r10") vm.registers[5].0, + // in("r8") vm.registers[6].0, + // in("r9") vm.registers[7].0, + // ) + // } + } + Ok(VmRunOk::End) => break Ok(()), + Err(e) => break Err(e), + } + }; + + eprintln!("\n== Registers ==\n{:?}", vm.registers); + if let Err(e) = stat { + eprintln!("\n[E] Runtime error: {e:?}"); + exit(2); + } + + Ok(()) +} diff --git a/hbvm_aos_on_linux/src/mem.rs b/hbvm_aos_on_linux/src/mem.rs new file mode 100644 index 00000000..e1687d31 --- /dev/null +++ b/hbvm_aos_on_linux/src/mem.rs @@ -0,0 +1,31 @@ +use hbvm::mem::{Address, LoadError, Memory, StoreError}; + +pub struct HostMemory; +impl Memory for HostMemory { + #[inline] + unsafe fn load( + &mut self, + addr: Address, + target: *mut u8, + count: usize, + ) -> Result<(), LoadError> { + unsafe { core::ptr::copy(addr.get() as *const u8, target, count) } + Ok(()) + } + + #[inline] + unsafe fn store( + &mut self, + addr: Address, + source: *const u8, + count: usize, + ) -> Result<(), StoreError> { + unsafe { core::ptr::copy(source, addr.get() as *mut u8, count) } + Ok(()) + } + + #[inline] + unsafe fn prog_read(&mut self, addr: Address) -> T { + core::ptr::read(addr.get() as *const T) + } +} From 57f30109c8d5ccd3945076b2e86ed6d9add47ea1 Mon Sep 17 00:00:00 2001 From: Erin Date: Sun, 29 Oct 2023 20:38:57 +0100 Subject: [PATCH 106/116] Fixed smaller units --- hbvm/src/value.rs | 2 ++ hbvm/src/vmrun.rs | 10 ++++++++-- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/hbvm/src/value.rs b/hbvm/src/value.rs index fd74b7b6..b3d24e8f 100644 --- a/hbvm/src/value.rs +++ b/hbvm/src/value.rs @@ -45,6 +45,8 @@ impl Value { /// # Safety /// - N/A, not to be implemented manually pub unsafe trait ValueVariant: private::Sealed + Copy + Into {} +impl private::Sealed for Value {} +unsafe impl ValueVariant for Value {} mod private { pub trait Sealed {} diff --git a/hbvm/src/vmrun.rs b/hbvm/src/vmrun.rs index fbf7f3d7..13bcbc3f 100644 --- a/hbvm/src/vmrun.rs +++ b/hbvm/src/vmrun.rs @@ -543,9 +543,15 @@ where /// Write a register. /// Writing to register 0 is no-op. #[inline(always)] - fn write_reg(&mut self, n: u8, value: impl Into) { + fn write_reg(&mut self, n: u8, value: T) { if n != 0 { - unsafe { *self.registers.get_unchecked_mut(n as usize) = value.into() }; + unsafe { + core::ptr::copy_nonoverlapping( + (&value as *const T).cast::(), + self.registers.as_mut_ptr().add(n.into()).cast::(), + core::mem::size_of::(), + ); + }; } } From 9ee3e9cb5f4587e9f7bf2c8e197f79b197553fc5 Mon Sep 17 00:00:00 2001 From: Erin Date: Thu, 2 Nov 2023 16:53:44 +0100 Subject: [PATCH 107/116] Public accessors --- hbvm/src/lib.rs | 26 +++++++++++++++++++++++++- hbvm/src/vmrun.rs | 28 +--------------------------- 2 files changed, 26 insertions(+), 28 deletions(-) diff --git a/hbvm/src/lib.rs b/hbvm/src/lib.rs index 5b90f83f..c520aeaf 100644 --- a/hbvm/src/lib.rs +++ b/hbvm/src/lib.rs @@ -13,7 +13,10 @@ #![no_std] #![cfg_attr(feature = "nightly", feature(fn_align))] -use mem::{Address, Memory}; +use { + mem::{Address, Memory}, + value::ValueVariant, +}; #[cfg(feature = "alloc")] extern crate alloc; @@ -66,6 +69,27 @@ where copier: None, } } + + /// Read register + #[inline(always)] + pub fn read_reg(&self, n: u8) -> Value { + unsafe { *self.registers.get_unchecked(n as usize) } + } + + /// Write a register. + /// Writing to register 0 is no-op. + #[inline(always)] + pub fn write_reg(&mut self, n: u8, value: T) { + if n != 0 { + unsafe { + core::ptr::copy_nonoverlapping( + (&value as *const T).cast::(), + self.registers.as_mut_ptr().add(n.into()).cast::(), + core::mem::size_of::(), + ); + }; + } + } } /// Virtual machine halt error diff --git a/hbvm/src/vmrun.rs b/hbvm/src/vmrun.rs index 13bcbc3f..152a992e 100644 --- a/hbvm/src/vmrun.rs +++ b/hbvm/src/vmrun.rs @@ -3,12 +3,7 @@ //! Have fun use { - super::{ - bmc::BlockCopier, - mem::Memory, - value::{Value, ValueVariant}, - Vm, VmRunError, VmRunOk, - }, + super::{bmc::BlockCopier, mem::Memory, value::ValueVariant, Vm, VmRunError, VmRunOk}, crate::{ mem::{addr::AddressOp, Address}, value::CheckedDivRem, @@ -534,27 +529,6 @@ where } } - /// Read register - #[inline(always)] - fn read_reg(&self, n: u8) -> Value { - unsafe { *self.registers.get_unchecked(n as usize) } - } - - /// Write a register. - /// Writing to register 0 is no-op. - #[inline(always)] - fn write_reg(&mut self, n: u8, value: T) { - if n != 0 { - unsafe { - core::ptr::copy_nonoverlapping( - (&value as *const T).cast::(), - self.registers.as_mut_ptr().add(n.into()).cast::(), - core::mem::size_of::(), - ); - }; - } - } - /// Load / Store Address check-computation überfunction #[inline(always)] unsafe fn ldst_addr_uber( From b45d235312c5402581c418bab42f12e1820995ad Mon Sep 17 00:00:00 2001 From: Erin Date: Thu, 2 Nov 2023 19:53:03 +0100 Subject: [PATCH 108/116] Exit --- hbxrt/src/main.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/hbxrt/src/main.rs b/hbxrt/src/main.rs index ac0c0226..0afb807d 100644 --- a/hbxrt/src/main.rs +++ b/hbxrt/src/main.rs @@ -46,6 +46,7 @@ fn main() -> Result<(), Box> { ) { unsafe { eprintln!("[E] Memory access fault at {:p}", (*info).si_addr()); + exit(2); } } From 6b3a132451b7693660ab9b5223ae17f9920f3dc9 Mon Sep 17 00:00:00 2001 From: Erin Date: Fri, 3 Nov 2023 09:01:26 +0100 Subject: [PATCH 109/116] fmt --- hbvm/src/lib.rs | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/hbvm/src/lib.rs b/hbvm/src/lib.rs index c520aeaf..9a6c0343 100644 --- a/hbvm/src/lib.rs +++ b/hbvm/src/lib.rs @@ -13,11 +13,6 @@ #![no_std] #![cfg_attr(feature = "nightly", feature(fn_align))] -use { - mem::{Address, Memory}, - value::ValueVariant, -}; - #[cfg(feature = "alloc")] extern crate alloc; @@ -29,7 +24,11 @@ mod float; mod utils; mod vmrun; -use {bmc::BlockCopier, value::Value}; +use { + bmc::BlockCopier, + mem::{Address, Memory}, + value::{Value, ValueVariant}, +}; /// HoleyBytes Virtual Machine pub struct Vm { From 37711809096a3d9297fca23b609b4bd3515da18b Mon Sep 17 00:00:00 2001 From: Erin Date: Fri, 3 Nov 2023 09:19:41 +0100 Subject: [PATCH 110/116] more fmt --- hbvm/src/mem/addr.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/hbvm/src/mem/addr.rs b/hbvm/src/mem/addr.rs index fc7b57b2..2a162afc 100644 --- a/hbvm/src/mem/addr.rs +++ b/hbvm/src/mem/addr.rs @@ -1,8 +1,9 @@ //! Virtual(?) memory address -use core::{fmt::Debug, ops}; - -use crate::utils::impl_display; +use { + crate::utils::impl_display, + core::{fmt::Debug, ops}, +}; /// Memory address #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] @@ -126,6 +127,5 @@ macro_rules! impl_address_ops_i(($($ty:ty),* $(,)?) => { })* }); - impl_address_ops_u!(u8, u16, u32, u64, usize); impl_address_ops_i!(i8, i16, i32, i64, isize); From 949dd3ba6190bcf7ac667eec7057d89d7cd41e9f Mon Sep 17 00:00:00 2001 From: Erin Date: Fri, 3 Nov 2023 09:43:08 +0100 Subject: [PATCH 111/116] Fixed rounding mode --- hbbytecode/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hbbytecode/src/lib.rs b/hbbytecode/src/lib.rs index 488ebe73..80960a01 100644 --- a/hbbytecode/src/lib.rs +++ b/hbbytecode/src/lib.rs @@ -110,7 +110,7 @@ impl TryFrom for RoundingMode { type Error = (); fn try_from(value: u8) -> Result { - (value >= 3) + (value <= 3) .then(|| unsafe { core::mem::transmute(value) }) .ok_or(()) } From a7c4379976c7b60261224130f35e10724154a0b9 Mon Sep 17 00:00:00 2001 From: Erin Date: Fri, 3 Nov 2023 09:49:42 +0100 Subject: [PATCH 112/116] =?UTF-8?q?=C2=BBfixed=C2=AB=20fuzzer?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- hbvm/fuzz/fuzz_targets/vm.rs | 49 +++++++++++++++++------------------- 1 file changed, 23 insertions(+), 26 deletions(-) diff --git a/hbvm/fuzz/fuzz_targets/vm.rs b/hbvm/fuzz/fuzz_targets/vm.rs index 5cd2d2a3..aa6d7ccf 100644 --- a/hbvm/fuzz/fuzz_targets/vm.rs +++ b/hbvm/fuzz/fuzz_targets/vm.rs @@ -1,7 +1,6 @@ #![no_main] use { - hbbytecode::valider::validate, hbvm::{ mem::{ softpaging::{ @@ -16,35 +15,33 @@ use { }; fuzz_target!(|data: &[u8]| { - if validate(data).is_ok() { - let mut vm = unsafe { - Vm::<_, 16384>::new( - SoftPagedMem::<_, true> { - pf_handler: TestTrapHandler, - program: data, - root_pt: Box::into_raw(Default::default()), - icache: Default::default(), - }, - Address::new(4), - ) - }; + let mut vm = unsafe { + Vm::<_, 16384>::new( + SoftPagedMem::<_, true> { + pf_handler: TestTrapHandler, + program: data, + root_pt: Box::into_raw(Default::default()), + icache: Default::default(), + }, + Address::new(4), + ) + }; - // Alloc and map some memory - let pages = [ - alloc_and_map(&mut vm.memory, 0), - alloc_and_map(&mut vm.memory, 4096), - ]; + // Alloc and map some memory + let pages = [ + alloc_and_map(&mut vm.memory, 0), + alloc_and_map(&mut vm.memory, 4096), + ]; - // Run VM - let _ = vm.run(); + // Run VM + let _ = vm.run(); - // Unmap and dealloc the memory - for (i, page) in pages.into_iter().enumerate() { - unmap_and_dealloc(&mut vm.memory, page, i as u64 * 4096); - } - - let _ = unsafe { Box::from_raw(vm.memory.root_pt) }; + // Unmap and dealloc the memory + for (i, page) in pages.into_iter().enumerate() { + unmap_and_dealloc(&mut vm.memory, page, i as u64 * 4096); } + + let _ = unsafe { Box::from_raw(vm.memory.root_pt) }; }); fn alloc_and_map(memory: &mut SoftPagedMem, at: u64) -> *mut u8 { From 398687d8bf9b0868a090e0244a0e2eb4a85765e1 Mon Sep 17 00:00:00 2001 From: Erin Date: Wed, 8 Nov 2023 12:38:14 +0100 Subject: [PATCH 113/116] Fixed some panics and some UB --- hbvm/src/vmrun.rs | 67 +++++++++++++++++++++++++++++------------------ 1 file changed, 41 insertions(+), 26 deletions(-) diff --git a/hbvm/src/vmrun.rs b/hbvm/src/vmrun.rs index 152a992e..5dac0a69 100644 --- a/hbvm/src/vmrun.rs +++ b/hbvm/src/vmrun.rs @@ -80,17 +80,18 @@ where AND => self.binary_op::(ops::BitAnd::bitand), OR => self.binary_op::(ops::BitOr::bitor), XOR => self.binary_op::(ops::BitXor::bitxor), - SLU8 => self.binary_op::(ops::Shl::shl), - SLU16 => self.binary_op::(ops::Shl::shl), - SLU32 => self.binary_op::(ops::Shl::shl), - SLU64 => self.binary_op::(ops::Shl::shl), - SRU8 => self.binary_op::(ops::Shr::shr), - SRU16 => self.binary_op::(ops::Shr::shr), - SRU32 => self.binary_op::(ops::Shr::shr), - SRS8 => self.binary_op::(ops::Shr::shr), - SRS16 => self.binary_op::(ops::Shr::shr), - SRS32 => self.binary_op::(ops::Shr::shr), - SRS64 => self.binary_op::(ops::Shr::shr), + SLU8 => self.binary_op_shift::(u8::wrapping_shl), + SLU16 => self.binary_op_shift::(u16::wrapping_shl), + SLU32 => self.binary_op_shift::(u32::wrapping_shl), + SLU64 => self.binary_op_shift::(u64::wrapping_shl), + SRU8 => self.binary_op_shift::(u8::wrapping_shr), + SRU16 => self.binary_op_shift::(u16::wrapping_shr), + SRU32 => self.binary_op_shift::(u32::wrapping_shr), + SRU64 => self.binary_op_shift::(u64::wrapping_shr), + SRS8 => self.binary_op_shift::(i8::wrapping_shr), + SRS16 => self.binary_op_shift::(i16::wrapping_shr), + SRS32 => self.binary_op_shift::(i32::wrapping_shr), + SRS64 => self.binary_op_shift::(i64::wrapping_shr), CMPU => handler!(self, |OpsRRR(tg, a0, a1)| self.cmp( tg, a0, @@ -137,18 +138,18 @@ where ANDI => self.binary_op_imm::(ops::BitAnd::bitand), ORI => self.binary_op_imm::(ops::BitOr::bitor), XORI => self.binary_op_imm::(ops::BitXor::bitxor), - SLUI8 => self.binary_op_ims::(ops::Shl::shl), - SLUI16 => self.binary_op_ims::(ops::Shl::shl), - SLUI32 => self.binary_op_ims::(ops::Shl::shl), - SLUI64 => self.binary_op_ims::(ops::Shl::shl), - SRUI8 => self.binary_op_ims::(ops::Shr::shr), - SRUI16 => self.binary_op_ims::(ops::Shr::shr), - SRUI32 => self.binary_op_ims::(ops::Shr::shr), - SRUI64 => self.binary_op_ims::(ops::Shr::shr), - SRSI8 => self.binary_op_ims::(ops::Shr::shr), - SRSI16 => self.binary_op_ims::(ops::Shr::shr), - SRSI32 => self.binary_op_ims::(ops::Shr::shr), - SRSI64 => self.binary_op_ims::(ops::Shr::shr), + SLUI8 => self.binary_op_ims::(u8::wrapping_shl), + SLUI16 => self.binary_op_ims::(u16::wrapping_shl), + SLUI32 => self.binary_op_ims::(u32::wrapping_shl), + SLUI64 => self.binary_op_ims::(u64::wrapping_shl), + SRUI8 => self.binary_op_ims::(u8::wrapping_shr), + SRUI16 => self.binary_op_ims::(u16::wrapping_shr), + SRUI32 => self.binary_op_ims::(u32::wrapping_shr), + SRUI64 => self.binary_op_ims::(u64::wrapping_shr), + SRSI8 => self.binary_op_ims::(i8::wrapping_shr), + SRSI16 => self.binary_op_ims::(i16::wrapping_shr), + SRSI32 => self.binary_op_ims::(i32::wrapping_shr), + SRSI64 => self.binary_op_ims::(i64::wrapping_shr), CMPUI => handler!(self, |OpsRRD(tg, a0, imm)| { self.cmp(tg, a0, imm) }), CMPSI => handler!(self, |OpsRRD(tg, a0, imm)| { self.cmp(tg, a0, imm as i64) }), CP => handler!(self, |OpsRR(tg, a0)| self.write_reg(tg, self.read_reg(a0))), @@ -404,7 +405,7 @@ where .as_mut_ptr() .add(usize::from(dst) + usize::from(n)) .cast(), - usize::from(count).wrapping_sub(n.into()), + usize::from(count).saturating_sub(n.into()), )?; Ok(()) @@ -456,9 +457,23 @@ where /// Perform binary operation over register and shift immediate #[inline(always)] - unsafe fn binary_op_ims(&mut self, op: impl Fn(T, u8) -> T) { + unsafe fn binary_op_shift(&mut self, op: impl Fn(T, u32) -> T) { + let OpsRRR(tg, a0, a1) = self.decode(); + self.write_reg( + tg, + op( + self.read_reg(a0).cast::(), + self.read_reg(a1).cast::(), + ), + ); + self.bump_pc::(); + } + + /// Perform binary operation over register and shift immediate + #[inline(always)] + unsafe fn binary_op_ims(&mut self, op: impl Fn(T, u32) -> T) { let OpsRRB(tg, reg, imm) = self.decode(); - self.write_reg(tg, op(self.read_reg(reg).cast::(), imm)); + self.write_reg(tg, op(self.read_reg(reg).cast::(), imm.into())); self.bump_pc::(); } From aca8045a987715d836235ebfcc06d39269937bc3 Mon Sep 17 00:00:00 2001 From: Erin Date: Fri, 10 Nov 2023 09:51:01 +0100 Subject: [PATCH 114/116] Fixed assembler register symbols --- hbasm/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hbasm/src/lib.rs b/hbasm/src/lib.rs index c4a7c875..440de14a 100644 --- a/hbasm/src/lib.rs +++ b/hbasm/src/lib.rs @@ -23,7 +23,7 @@ pub fn assembler( label::setup(&mut engine, &mut module, Rc::clone(&obj)); // Registers - for n in 0_u8..255 { + for n in 0_u8..=255 { module.set_var(format!("r{n}"), n); } From c5c8d23470be6bb85c80c85967ba230c47e9a197 Mon Sep 17 00:00:00 2001 From: Erin Date: Mon, 13 Nov 2023 00:12:18 +0100 Subject: [PATCH 115/116] Fixed immediate ops --- hbvm/src/vmrun.rs | 38 +++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/hbvm/src/vmrun.rs b/hbvm/src/vmrun.rs index 5dac0a69..21525a91 100644 --- a/hbvm/src/vmrun.rs +++ b/hbvm/src/vmrun.rs @@ -19,7 +19,7 @@ macro_rules! handler { ($self:expr, |$ty:ident ($($ident:pat),* $(,)?)| $expr:expr) => {{ let $ty($($ident),*) = $self.decode::<$ty>(); #[allow(clippy::no_effect)] let e = $expr; - $self.bump_pc::<$ty, true>(); + $self.bump_pc::<$ty>(); e }}; } @@ -57,11 +57,11 @@ where unsafe { match self.memory.prog_read::(self.pc as _) { UN => { - self.bump_pc::(); + self.bump_pc::(); return Err(VmRunError::Unreachable); } TX => { - self.bump_pc::(); + self.bump_pc::(); return Ok(VmRunOk::End); } NOP => handler!(self, |OpsN()| ()), @@ -217,7 +217,7 @@ where // We are done, shift program counter core::task::Poll::Ready(Ok(())) => { self.copier = None; - self.bump_pc::(); + self.bump_pc::(); } // Error, shift program counter (for consistency) // and yield error @@ -270,7 +270,7 @@ where if self.read_reg(a0).cast::() != self.read_reg(a1).cast::() { self.pc = self.pcrel(ja, 3); } else { - self.bump_pc::(); + self.bump_pc::(); } } JLTS => self.cond_jmp::(Ordering::Less), @@ -283,11 +283,11 @@ where self.timer = self.timer.wrapping_add(1); } - self.bump_pc::(); + self.bump_pc::(); return Ok(VmRunOk::Ecall); } EBP => { - self.bump_pc::(); + self.bump_pc::(); return Ok(VmRunOk::Breakpoint); } FADD32 => self.binary_op::(ops::Add::add), @@ -373,10 +373,8 @@ where /// Bump instruction pointer #[inline(always)] - fn bump_pc(&mut self) { - self.pc = self - .pc - .wrapping_add(core::mem::size_of::() + PAST_OP as usize); + fn bump_pc(&mut self) { + self.pc = self.pc.wrapping_add(core::mem::size_of::()); } /// Decode instruction operands @@ -442,17 +440,19 @@ where tg, op(self.read_reg(a0).cast::(), self.read_reg(a1).cast::()), ); - self.bump_pc::(); + self.bump_pc::(); } /// Perform binary operation over register and immediate #[inline(always)] unsafe fn binary_op_imm(&mut self, op: impl Fn(T, T) -> T) { - let OpsRR(tg, reg) = self.decode(); - let imm: T = self.decode(); + #[derive(Clone, Copy)] + #[repr(packed)] + struct OpsRRImm(OpsRR, I); + + let OpsRRImm::(OpsRR(tg, reg), imm) = self.decode(); self.write_reg(tg, op(self.read_reg(reg).cast::(), imm)); - self.bump_pc::(); - self.bump_pc::(); + self.bump_pc::>(); } /// Perform binary operation over register and shift immediate @@ -466,7 +466,7 @@ where self.read_reg(a1).cast::(), ), ); - self.bump_pc::(); + self.bump_pc::(); } /// Perform binary operation over register and shift immediate @@ -474,7 +474,7 @@ where unsafe fn binary_op_ims(&mut self, op: impl Fn(T, u32) -> T) { let OpsRRB(tg, reg, imm) = self.decode(); self.write_reg(tg, op(self.read_reg(reg).cast::(), imm.into())); - self.bump_pc::(); + self.bump_pc::(); } /// Fused division-remainder @@ -540,7 +540,7 @@ where { self.pc = self.pcrel(ja, 3); } else { - self.bump_pc::(); + self.bump_pc::(); } } From d25596712569a77903a05fbca67d363cab62d9e9 Mon Sep 17 00:00:00 2001 From: Erin Date: Mon, 13 Nov 2023 00:21:12 +0100 Subject: [PATCH 116/116] Ehm? --- hbvm/src/vmrun.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hbvm/src/vmrun.rs b/hbvm/src/vmrun.rs index 21525a91..56e7f429 100644 --- a/hbvm/src/vmrun.rs +++ b/hbvm/src/vmrun.rs @@ -242,7 +242,7 @@ where }), JMP => { let OpsO(off) = self.decode(); - self.pc = self.pc.wrapping_add(off); + self.pc = self.pc.wrapping_add(off).wrapping_add(1); } JAL => { // Jump and link. Save PC after this instruction to