diff --git a/Cargo.toml b/Cargo.toml index 69da7d2..e9cd091 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,19 +7,20 @@ default-members = ["redox-terminal", "redox-core"] [workspace.dependencies] criterion = "0.5.1" enumflags2 = "0.7.10" -float-cmp = "0.9.0" -hashbrown = "0.14.5" -itertools = "0.13.0" +float-cmp = "0.10.0" +hashbrown = "0.15.2" +itertools = "0.14.0" num-traits = "0.2.19" num-derive = "0.4.2" prettytable = "0.10.0" -rand = "0.8.5" -rand_xoshiro = "0.6.0" +rand = "0.9.0" +rand_core = "0.9.0" +rand_xoshiro = "0.7.0" strum = "0.26.3" strum_macros = "0.26.4" -thiserror = "1.0.61" +thiserror = "2.0.11" unescape = "0.1.0" -unicode-segmentation = "1.11.0" +unicode-segmentation = "1.12.0" winres = "0.1.12" [profile.dev] diff --git a/README.md b/README.md index 4933a20..f4bf91c 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,2 @@ -# redox +# Redox A new toy virtual machine, assembler and compiler, written in Rust. diff --git a/redox-core/Cargo.toml b/redox-core/Cargo.toml index d0b0207..de2995c 100644 --- a/redox-core/Cargo.toml +++ b/redox-core/Cargo.toml @@ -4,8 +4,8 @@ version = "0.1.0" authors = ["Ryan Jones-Ward "] edition = "2021" readme = "README.md" +license = "LGPL-2.1-only" description = "A new toy virtual machine, assembler and compiler, written in Rust." -license_file = "LICENSE" repository = "https://github.com/sciguyryan/Redox" homepage = "https://github.com/sciguyryan/Redox" @@ -20,6 +20,7 @@ num-traits.workspace = true num-derive.workspace = true prettytable.workspace = true rand.workspace = true +rand_core.workspace = true rand_xoshiro.workspace = true strum.workspace = true strum_macros.workspace = true diff --git a/redox-core/src/com_bus/com_bus_io.rs b/redox-core/src/com_bus/com_bus_io.rs index 9c35ccb..d772b8f 100644 --- a/redox-core/src/com_bus/com_bus_io.rs +++ b/redox-core/src/com_bus/com_bus_io.rs @@ -2,11 +2,11 @@ use super::device_error::DeviceResult; pub trait ComBusIO { /// Read a u8 value from the device. - fn read_u8(&self) -> DeviceResult; + fn read_u8(&mut self) -> DeviceResult; /// Read a u32 value from the device. - fn read_u32(&self) -> DeviceResult; + fn read_u32(&mut self) -> DeviceResult; /// Read a uf32 value from the device. - fn read_f32(&self) -> DeviceResult; + fn read_f32(&mut self) -> DeviceResult; /// Write a u8 value to the device. fn write_u8(&mut self, value: u8) -> DeviceResult<()>; diff --git a/redox-core/src/com_bus/random_device.rs b/redox-core/src/com_bus/random_device.rs index 247d99a..d36185c 100644 --- a/redox-core/src/com_bus/random_device.rs +++ b/redox-core/src/com_bus/random_device.rs @@ -1,30 +1,28 @@ -use rand::{rngs::OsRng, Rng, SeedableRng}; +use rand::{rngs::OsRng, Rng, SeedableRng, TryRngCore}; use rand_xoshiro::Xoshiro256Plus; -use std::cell::RefCell; use super::{ com_bus_io::ComBusIO, device_error::{DeviceError, DeviceResult}, }; -thread_local! { - static PRNG: RefCell = RefCell::new(Xoshiro256Plus::from_entropy()); - static CRNG: RefCell = const { RefCell::new(OsRng) }; -} - +#[repr(u8)] #[derive(PartialEq, Eq)] enum RandomMode { Crypto, - Seeded, - Standard, + Pseudorandom, } -/// The seedable random generator chip! +/// The random number generator device! pub struct RandomDevice { /// Has the random number generator been initialized? is_initialized: bool, /// The random generator mode currently being used. mode: RandomMode, + /// The pseudorandom number generator. + prng: Xoshiro256Plus, + /// The Cryptographically secure RNG generator. + crng: OsRng, } impl RandomDevice { @@ -32,7 +30,9 @@ impl RandomDevice { Self { // By default we initialize the random number generator using entropy. is_initialized: true, - mode: RandomMode::Standard, + mode: RandomMode::Crypto, + prng: Xoshiro256Plus::from_os_rng(), + crng: OsRng, } } } @@ -44,39 +44,51 @@ impl Default for RandomDevice { } impl ComBusIO for RandomDevice { - fn read_u8(&self) -> DeviceResult { + fn read_u8(&mut self) -> DeviceResult { if !self.is_initialized { return DeviceResult::Err(DeviceError::Misconfigured); } Ok(match self.mode { - RandomMode::Crypto => CRNG.with(|r| r.borrow_mut().gen::()), - RandomMode::Seeded => PRNG.with(|r| r.borrow_mut().gen::()), - RandomMode::Standard => rand::thread_rng().gen::(), + RandomMode::Crypto => { + let mut arr: [u8; 1] = [0; 1]; + self.crng + .try_fill_bytes(&mut arr) + .expect("failed to get u8"); + arr[0] + } + RandomMode::Pseudorandom => self.prng.random::(), }) } - fn read_u32(&self) -> DeviceResult { + fn read_u32(&mut self) -> DeviceResult { if !self.is_initialized { return DeviceResult::Err(DeviceError::Misconfigured); } Ok(match self.mode { - RandomMode::Crypto => CRNG.with(|r| r.borrow_mut().gen::()), - RandomMode::Seeded => PRNG.with(|r| r.borrow_mut().gen::()), - RandomMode::Standard => rand::thread_rng().gen::(), + RandomMode::Crypto => self.crng.try_next_u32().expect("failed to get u32"), + RandomMode::Pseudorandom => self.prng.random::(), }) } - fn read_f32(&self) -> DeviceResult { + fn read_f32(&mut self) -> DeviceResult { if !self.is_initialized { return DeviceResult::Err(DeviceError::Misconfigured); } Ok(match self.mode { - RandomMode::Crypto => CRNG.with(|r| r.borrow_mut().gen::()), - RandomMode::Seeded => PRNG.with(|r| r.borrow_mut().gen::()), - RandomMode::Standard => rand::thread_rng().gen::(), + RandomMode::Crypto => { + // We want to weed out any potential infinities or NaN's, + // and we'll iterate until we find one. + let mut value = f32::INFINITY; + while !value.is_finite() { + let v = self.crng.try_next_u32().expect("failed to get u32"); + value = f32::from_bits(v); + } + value + } + RandomMode::Pseudorandom => self.prng.random::(), }) } @@ -86,19 +98,15 @@ impl ComBusIO for RandomDevice { // The instruction indicating the type of random number generator to be used. match value { 0 => { - // Standard mode - a PRNG derived from entropy. - PRNG.with(|r| *r.borrow_mut() = Xoshiro256Plus::from_entropy()); - self.mode = RandomMode::Standard; + // Cryptographic random mode - a cryptographically secure random number generator. + // This is the default. + self.mode = RandomMode::Crypto; self.is_initialized = true; } 1 => { - // Seeded random mode - the seed must be set via sending a u32 command after this one. - self.mode = RandomMode::Seeded; - self.is_initialized = false; - } - 2 => { - // Cryptographic random mode - a cryptographically secure random number generator. - self.mode = RandomMode::Crypto; + // Standard mode - a PRNG derived from entropy. + self.prng = Xoshiro256Plus::from_os_rng(); + self.mode = RandomMode::Pseudorandom; self.is_initialized = true; } _ => { @@ -112,8 +120,8 @@ impl ComBusIO for RandomDevice { fn write_u32(&mut self, value: u32) -> DeviceResult<()> { let mut result = DeviceResult::Ok(()); - if self.mode == RandomMode::Seeded { - PRNG.with(|r| *r.borrow_mut() = Xoshiro256Plus::seed_from_u64(value as u64)); + if self.mode == RandomMode::Pseudorandom { + self.prng = Xoshiro256Plus::seed_from_u64(value as u64); // Indicate that the PRNG has been seeded and initialized. self.is_initialized = true; @@ -131,21 +139,21 @@ impl ComBusIO for RandomDevice { #[cfg(test)] mod tests_random_device { - use crate::com_bus::com_bus_io::ComBusIO; + use crate::com_bus::{com_bus_io::ComBusIO, random_device::RandomMode}; use super::RandomDevice; - /// Test the PRNG to ensure it is non-repeating. + /// Test the cryptographic RNG to ensure it is non-repeating. #[test] - fn test_prng() { - // By default, the generator should use an entropy-seeded PRNG. - let device = RandomDevice::default(); + fn test_crng() { + // By default, the generator should use an entropy-seeded RNG. + let mut device = RandomDevice::default(); let mut sequence_1 = vec![]; for _ in 0..1000 { sequence_1.push(device.read_u32().expect("")); } - let device = RandomDevice::default(); + let mut device = RandomDevice::default(); let mut sequence_2 = vec![]; for _ in 0..1000 { sequence_2.push(device.read_u32().expect("")); @@ -158,7 +166,7 @@ mod tests_random_device { #[test] fn test_seeded_prng() { let mut device = RandomDevice::default(); - _ = device.write_u8(0x1); + _ = device.write_u8(RandomMode::Pseudorandom as u8); _ = device.write_u32(0xdeadbeef); let mut sequence_1 = vec![]; @@ -167,7 +175,7 @@ mod tests_random_device { } let mut device = RandomDevice::default(); - _ = device.write_u8(0x1); + _ = device.write_u8(RandomMode::Pseudorandom as u8); _ = device.write_u32(0xdeadbeef); let mut sequence_2 = vec![]; diff --git a/redox-core/src/com_bus/test_debug_device.rs b/redox-core/src/com_bus/test_debug_device.rs index eff5da9..4784a64 100644 --- a/redox-core/src/com_bus/test_debug_device.rs +++ b/redox-core/src/com_bus/test_debug_device.rs @@ -27,7 +27,7 @@ impl Default for TestDebugDevice { } impl ComBusIO for TestDebugDevice { - fn read_u8(&self) -> DeviceResult { + fn read_u8(&mut self) -> DeviceResult { if self.should_fail[0] { return DeviceResult::Err(DeviceError::OperationNotSupported); } @@ -35,7 +35,7 @@ impl ComBusIO for TestDebugDevice { Ok(u8::MAX) } - fn read_u32(&self) -> DeviceResult { + fn read_u32(&mut self) -> DeviceResult { if self.should_fail[1] { return DeviceResult::Err(DeviceError::OperationNotSupported); } @@ -43,7 +43,7 @@ impl ComBusIO for TestDebugDevice { Ok(u32::MAX) } - fn read_f32(&self) -> DeviceResult { + fn read_f32(&mut self) -> DeviceResult { if self.should_fail[2] { return DeviceResult::Err(DeviceError::OperationNotSupported); } diff --git a/redox-core/src/compiling/compiler.rs b/redox-core/src/compiling/compiler.rs index 104c0c8..f5e1685 100644 --- a/redox-core/src/compiling/compiler.rs +++ b/redox-core/src/compiling/compiler.rs @@ -27,6 +27,7 @@ impl Compiler { } } + #[inline] fn assert_label_does_not_exist(&self, label: &String) { assert!( !self.labels.contains_key(label), @@ -73,19 +74,16 @@ impl Compiler { // Now load the code labels. self.load_code_labels(&instructions); + //println!("labels = {:#?}", self.labels); + if optimize { // TODO - optimizations should go here. // TODO - an optimization here might be to use a short call/jump instruction // TODO - if the call address is close enough, for example. - // TODO - the calculate label positions method should be called here again. - // TODO - this is because the instructions may have changed, altering the relative positions of the labels. - } - - // TODO - do we care enough to do this? - if optimize { - // TODO - a second optimization pass should go here to optimize any labels - // TODO - related to the data section. + // TODO - the calculate code label positions method should be called here again. + // TODO - this is because the instructions may have changed, altering the relative + // TODO - positions of the code labels. } self.handle_labelled_instructions(&mut instructions); @@ -137,7 +135,7 @@ impl Compiler { self.labels.insert( d.label.clone(), - LabelEntry::new(LabelType::Code, d.label.clone(), position), + LabelEntry::new(LabelType::Data, d.label.clone(), position), ); position += d.bytes.len(); @@ -159,21 +157,18 @@ impl Compiler { // Attempt to get the label entry. // If a label has been used but has not been defined then that is a syntax error. - let label_entry = if let Some(l) = self.labels.get(label) { - l - } else { + let Some(label_entry) = self.labels.get(label) else { panic!("syntax error - no label with the name of {label} has been defined."); }; + let position = label_entry.position as u32; + match label_entry.label_type { LabelType::Code => { - *ins = match ins { - Instruction::CallAbsU32Imm(_, label) => Instruction::CallRelCSU32Offset( - label_entry.position as u32, - label.clone(), - ), - Instruction::CallRelCSU32Offset(_, _) => continue, - _ => unreachable!("unexpected labelled instruction instance - {ins}"), + if let Some(i) = + Compiler::maybe_rewrite_code_labelled_instruction(ins, position) + { + *ins = i; } } LabelType::Data => todo!(), @@ -182,6 +177,21 @@ impl Compiler { } } + #[inline] + fn maybe_rewrite_code_labelled_instruction( + ins: &Instruction, + label_position: u32, + ) -> Option { + match ins { + Instruction::CallAbsU32Imm(_, label) => Some(Instruction::CallRelCSU32Offset( + label_position, + label.clone(), + )), + Instruction::CallRelCSU32Offset(_, _) => None, + _ => unreachable!("unexpected labelled instruction - {ins}"), + } + } + /// Compile a single instruction into bytecode. /// /// # Arguments diff --git a/redox-core/src/cpu.rs b/redox-core/src/cpu.rs index a3c0fd9..1adca59 100644 --- a/redox-core/src/cpu.rs +++ b/redox-core/src/cpu.rs @@ -1753,9 +1753,9 @@ mod tests_cpu { // Did the test registers match their expected values? self.expected_changed_registers .iter() - .for_each(|(id, expected_value)| { + .for_each(|(id, &expected_value)| { let actual_value = vm.cpu.registers.read_reg_f32_unchecked(id); - if *expected_value != actual_value { + if expected_value != actual_value { table.add_row(row![ id, format!("{expected_value}"), @@ -1899,9 +1899,9 @@ mod tests_cpu { // Did the test registers match their expected values? self.expected_changed_registers .iter() - .for_each(|(id, expected_value)| { + .for_each(|(id, &expected_value)| { let actual_value = vm.cpu.registers.read_reg_u32_unchecked(id); - if *expected_value != actual_value { + if expected_value != actual_value { table.add_row(row![ id, format!("{expected_value}"), diff --git a/redox-core/src/ins/instruction.rs b/redox-core/src/ins/instruction.rs index 213e596..d78ec5d 100644 --- a/redox-core/src/ins/instruction.rs +++ b/redox-core/src/ins/instruction.rs @@ -289,16 +289,16 @@ impl Display for Instruction { format!("call &{reg}") } I::CallRelU32RegU32Offset(offset, reg) => { - format!("callr 0x{offset:08x}, &{reg}") + format!("call.r 0x{offset:08x}, &{reg}") } I::CallRelCSU32Offset(offset, label) => { if label.is_empty() { - format!("callr 0x{offset:08x}") + format!("call.r 0x{offset:08x}") } else { - format!("callr {label}") + format!("call.r {label}") } } - I::RetArgsU32 => String::from("iret"), + I::RetArgsU32 => String::from("ret.i"), I::Int(int_code) => { format!("int 0x{int_code:02x}") } diff --git a/redox-core/src/ins/jump_expression.rs b/redox-core/src/ins/jump_expression.rs deleted file mode 100644 index 5908ff1..0000000 --- a/redox-core/src/ins/jump_expression.rs +++ /dev/null @@ -1,9 +0,0 @@ -use crate::reg::registers::RegisterId; - -#[derive(Clone, Debug)] -pub struct JumpExpression { - /// The base register ID associated with this expression. - pub base_register: RegisterId, - /// The 32-bit value by which the value of the specific register should be offset in the jump. - pub offset: u32, -} diff --git a/redox-core/src/ins/mod.rs b/redox-core/src/ins/mod.rs index 82eabdc..a8d8d80 100644 --- a/redox-core/src/ins/mod.rs +++ b/redox-core/src/ins/mod.rs @@ -1,4 +1,3 @@ pub mod expression; pub mod instruction; -pub mod jump_expression; pub mod op_codes; diff --git a/redox-core/src/parsing/asm_parser.rs b/redox-core/src/parsing/asm_parser.rs index 019324e..3024ef1 100644 --- a/redox-core/src/parsing/asm_parser.rs +++ b/redox-core/src/parsing/asm_parser.rs @@ -82,33 +82,33 @@ impl FromStr for FileSection { /// Cheekily get the inner value of an enum. macro_rules! get_inner_arg { ($target:expr, $enum:path) => {{ - if let $enum(a) = $target { - a - } else { + let $enum(a) = $target else { unreachable!(); - } + }; + + a }}; } /// Cheekily get the inner value of an enum, with casting. macro_rules! get_inner_arg_and_cast { ($target:expr, $enum:path, $cast:ident) => {{ - if let $enum(a) = $target { - a as $cast - } else { + let $enum(a) = $target else { unreachable!(); - } + }; + + a as $cast }}; } /// Cheekily get and pack an inner enum expression. macro_rules! get_inner_expr_arg { ($target:expr) => {{ - if let Argument::Expression(expr) = &$target { - expr.pack() - } else { + let Argument::Expression(expr) = &$target else { unreachable!() - } + }; + + expr.pack() }}; } @@ -485,11 +485,11 @@ impl<'a> AsmParser<'a> { fn parse_section_line(line: &str) -> FileSection { assert!(line.len() >= 9); - if let Ok(section) = FileSection::from_str(&line[9..]) { - section - } else { + let Ok(section) = FileSection::from_str(&line[9..]) else { panic!("invalid assembly file section name - {}", &line[9..]) - } + }; + + section } /// Push a string buffer onto an string argument vector. @@ -1126,7 +1126,7 @@ impl<'a> AsmParser<'a> { } } -impl<'a> Default for AsmParser<'a> { +impl Default for AsmParser<'_> { fn default() -> Self { Self::new() } diff --git a/redox-core/src/parsing/type_hints.rs b/redox-core/src/parsing/type_hints.rs index c02fe27..19131bc 100644 --- a/redox-core/src/parsing/type_hints.rs +++ b/redox-core/src/parsing/type_hints.rs @@ -93,7 +93,7 @@ pub struct InstructionHints<'a> { pub hints: Vec>, } -impl<'a> InstructionHints<'a> { +impl InstructionHints<'_> { pub fn new() -> Self { use ArgTypeHint::*; use OpCode as O; @@ -146,12 +146,12 @@ impl<'a> InstructionHints<'a> { gen_hint!(vec!["call"], [U32Pointer], O::CallAbsU32Imm), gen_hint!(vec!["call"], [RegisterU32Pointer], O::CallAbsU32Reg), gen_hint!( - vec!["callr"], + vec!["call.r"], [U32, RegisterU32Pointer], O::CallRelU32RegU32Offset ), - gen_hint!(vec!["callr"], [U32], O::CallRelCSU32Offset), - gen_hint!(vec!["iret"], [], O::RetArgsU32), + gen_hint!(vec!["call.r"], [U32], O::CallRelCSU32Offset), + gen_hint!(vec!["ret.i"], [], O::RetArgsU32), gen_hint!(vec!["int"], [U8], O::Int), gen_hint!(vec!["intret"], [], O::IntRet), gen_hint!(vec!["jmp"], [U32Pointer], O::JumpAbsU32Imm), @@ -276,7 +276,7 @@ impl<'a> InstructionHints<'a> { } } -impl<'a> Default for InstructionHints<'a> { +impl Default for InstructionHints<'_> { fn default() -> Self { Self::new() } diff --git a/redox-terminal/Cargo.toml b/redox-terminal/Cargo.toml index c08c00e..0a76c6c 100644 --- a/redox-terminal/Cargo.toml +++ b/redox-terminal/Cargo.toml @@ -3,7 +3,7 @@ name = "redox_terminal" version = "0.1.0" authors = ["Ryan Jones-Ward "] edition = "2021" -readme = "README.md" +license = "LGPL-2.1-only" description = "A testing terminal for Redox" repository = "https://github.com/sciguyryan/Redox" homepage = "https://github.com/sciguyryan/Redox" diff --git a/redox-terminal/src/main.rs b/redox-terminal/src/main.rs index 6ec3234..892bac8 100644 --- a/redox-terminal/src/main.rs +++ b/redox-terminal/src/main.rs @@ -66,13 +66,15 @@ fn main() { panic!("currently unsupported"); } - let code = "section .text + let code = "section .data + test db 'bananas' + section .text push 0 call :LABEL_1 hlt :LABEL_1 mov 0xdeadbeef, EAX - iret"; + ret.i"; let mut compiler = Compiler::new(); let data = compiler.compile_assembly(code, true);