From f28056da829737ec083cba6b89b3b5ad82f39fb4 Mon Sep 17 00:00:00 2001 From: Victor Mignot Date: Thu, 2 May 2024 13:33:33 +0200 Subject: [PATCH] Implement AddressSpace Add the first implementation to represent an address space (through the `AddressSpace` struct), that can be used by different objects which have their own inner memory map. The implementation use an AVLTree (right now it's just an ordinary BST, as rebalancing has not been implemented). --- src/core.rs | 2 + src/core/address_space.rs | 464 ++++++++++++++++++++++++++++++++++++++ src/main.rs | 10 +- 3 files changed, 475 insertions(+), 1 deletion(-) create mode 100644 src/core.rs create mode 100644 src/core/address_space.rs diff --git a/src/core.rs b/src/core.rs new file mode 100644 index 0000000..e03d830 --- /dev/null +++ b/src/core.rs @@ -0,0 +1,2 @@ +//! The core module contains each software component that is used to run the main features of a RISC-V hart and common devices. +pub mod address_space; diff --git a/src/core/address_space.rs b/src/core/address_space.rs new file mode 100644 index 0000000..d282369 --- /dev/null +++ b/src/core/address_space.rs @@ -0,0 +1,464 @@ +//! The address space module provides an allowing developpers and users to +//! provide the `AddressSpace` struct, used for devices and buses that have their own memory map. +//! Thus, they can have their own set of other devices mapped to their `AddressSpace`. + +use std::{cmp::max, sync::RwLock}; + +/// The potential errors that can be returned from operations within the address space. +pub enum AddressSpaceError { + /// Trying to map an object on an already use memory range. + RangeOverridingError, +} + +/// The trait that an object should implement to be mapped within the address space. +/// Its goal is to call callbacks functions when specific operations are done within the +/// range to which the object is mapped. +pub trait Addressable { + /// Get the size of the mapped memory range for the device. + fn memory_range_size(&self) -> u32; +} + +/// An elemental address range stored within the address space tree and the object mapped +/// to this range. +struct AddressRange +where + T: Addressable + ?Sized, +{ + /// The first address of the range. + start: u32, + /// The last address of the range. + end: u32, + /// A `RwLock` to the object mapped to the range. Allowing thus to deal with Read or Write operation within the Address Space. + mapped_object: RwLock>, +} + +/// The underlying structure storing all the devices mapped to an address space. +/// It's a self-balancing Binary Search Tree (more specifically an AVL Tree) where each node +/// represents an address range to which is mapped an object. +struct AddressSpaceRangesTree(Option>) +where + T: Addressable + ?Sized; + +impl AddressSpaceRangesTree +where + T: Addressable + ?Sized, +{ + /// Add the given `addressable` into the AddressSpace tree. + fn insert(&mut self, addressable: Box, address: u32) -> Result<(), AddressSpaceError> { + let new_node = AddressSpaceRangeNode::new(AddressRange { + start: address, + end: address + addressable.memory_range_size() - 1, + mapped_object: RwLock::new(addressable), + }); + + if let Some(root) = self.0.as_mut() { + root.insert(new_node) + } else { + self.0 = Some(new_node); + Ok(()) + } + } + + /// Returns the object mapped to the given `address`. + fn get_mapped_object_to_address(&self, address: u32) -> Option<(&RwLock>, u32)> { + if let Some(root_node) = self.0.as_ref() { + root_node.get_object_from_address(address) + } else { + None + } + } + + /// Create a new tree instance. + fn new() -> Self { + AddressSpaceRangesTree(None) + } +} + +/// Represent a node of an AddressSpaceRangesTree. +struct AddressSpaceRangeNode +where + T: Addressable + ?Sized, +{ + /// The given range that this node store. + range: AddressRange, + /// The left child of the node. + left: Option>>, + /// The right child of the node. + right: Option>>, +} + +impl AddressSpaceRangeNode +where + T: Addressable + ?Sized, +{ + /// Create a new node instance for an AddressSpaeRangesTree with the given `range`. + fn new(range: AddressRange) -> Self { + AddressSpaceRangeNode { + range, + left: None, + right: None, + } + } + + /// Return the height of the current node within the tree. + fn get_height(&self) -> usize { + if self.left.is_none() && self.right.is_none() { + 0 + } else if self.left.is_none() { + self.right.as_ref().unwrap().get_height() + 1 + } else if self.right.is_none() { + self.left.as_ref().unwrap().get_height() + 1 + } else { + max( + self.left.as_ref().unwrap().get_height(), + self.right.as_ref().unwrap().get_height(), + ) + 1 + } + } + + /// Take the subtree where this node is the root and return whether this subtree is balanced or not. + /// The subtree is unbalanced if the difference in height between the left and the right child is superior to one. + /// This method is mainly called to check if the tree should be rebalanced to optimize the next operations. + fn is_unbalanced(&self) -> bool { + match (self.left.as_ref(), self.right.as_ref()) { + (None, None) => false, + (None, Some(right)) => right.get_height() > 1, + (Some(left), None) => left.get_height() > 1, + (Some(left), Some(right)) => left.get_height().abs_diff(right.get_height()) > 1, + } + } + + /// Rebalance this node subtree if it's unbalanced. + fn rebalance_if_needed(&mut self) { + if !self.is_unbalanced() {} + + // TODO: Add tree rebalancing + } + + /// If this node has none, set the `new_node` as its left child. + /// Else, insert the `new_node` into the left child subtree. + fn insert_into_or_set_left_child(&mut self, new_node: Self) -> Result<(), AddressSpaceError> { + match self.left.as_mut() { + Some(node) => node.insert(new_node), + None => { + self.left = Some(Box::new(new_node)); + Ok(()) + } + } + } + + /// If this node has none, set the `new_node` as its right child. + /// Else, insert the `new_node` into the right child subtree. + fn insert_into_or_set_right_child(&mut self, new_node: Self) -> Result<(), AddressSpaceError> { + match self.right.as_mut() { + Some(node) => node.insert(new_node), + None => { + self.right = Some(Box::new(new_node)); + Ok(()) + } + } + } + + /// Insert the given `new_node` into the this node subtree. + fn insert(&mut self, new_node: Self) -> Result<(), AddressSpaceError> { + if self.range.start > new_node.range.end { + self.insert_into_or_set_left_child(new_node) + } else if self.range.end < new_node.range.start { + self.insert_into_or_set_right_child(new_node) + } else { + Err(AddressSpaceError::RangeOverridingError) + }?; + + self.rebalance_if_needed(); + + Ok(()) + } + + /// Return the object mapped to the given `address`. + fn get_object_from_address(&self, address: u32) -> Option<(&RwLock>, u32)> { + if address >= self.range.start && address <= self.range.end { + Some((&self.range.mapped_object, self.range.start)) + } else if address < self.range.start { + if let Some(left_child) = self.left.as_ref() { + left_child.get_object_from_address(address) + } else { + None + } + } else if address > self.range.end { + if let Some(right_child) = self.right.as_ref() { + right_child.get_object_from_address(address) + } else { + None + } + } else { + None + } + } +} + +/// Represent a 32-bit address space to which elements of different nature can be mapped to specific address ranges. +/// From there, one will be able to the device mappe to a specific address. +pub struct AddressSpace +where + T: Addressable + ?Sized, +{ + /// Data structure storing the device mapping. + memory_map: AddressSpaceRangesTree, +} + +impl AddressSpace +where + T: Addressable + ?Sized, +{ + /// Create a new address space object. + pub fn new() -> Self { + AddressSpace { + memory_map: AddressSpaceRangesTree::new(), + } + } + + /// Map the given memory-mappable `object` to the specified `address` and `range`. + pub fn map_object(&mut self, object: Box, address: u32) -> Result<(), AddressSpaceError> { + self.memory_map.insert(object, address) + } + + /// Return the object mapped to the given `address` on the address space. + pub fn get_mapped_object_to_address(&self, address: u32) -> Option<(&RwLock>, u32)> { + self.memory_map.get_mapped_object_to_address(address) + } +} + +impl Default for AddressSpace +where + T: Addressable + ?Sized, +{ + fn default() -> Self { + AddressSpace::new() + } +} + +#[cfg(test)] +mod tests { + use super::{AddressSpace, Addressable}; + + #[derive(Clone, Copy)] + struct TestObject { + size: u32, + } + + impl TestObject { + fn new(size: u32) -> Self { + TestObject { size } + } + } + + impl Addressable for TestObject { + fn memory_range_size(&self) -> u32 { + self.size + } + } + + #[test] + fn empty_is_none() { + let address_space: AddressSpace = AddressSpace::new(); + + assert!(address_space.memory_map.0.is_none()) + } + + #[test] + fn fetch_from_empty() { + let address_space: AddressSpace = AddressSpace::new(); + + assert!(address_space.get_mapped_object_to_address(0x100).is_none()); + } + + #[test] + fn insert_on_empty() { + let mut address_space: AddressSpace = AddressSpace::new(); + let object = TestObject::new(0x10); + let _ = address_space.map_object(Box::new(object), 0x100); + + assert!(address_space.memory_map.0.is_some()); + + for address in 0x100..0x10 { + let mapped_device = address_space.get_mapped_object_to_address(address); + assert!(mapped_device.is_some()); + assert_eq!( + mapped_device.unwrap().0.read().unwrap().memory_range_size(), + 0x10 + ); + } + } + + #[test] + fn overriding_insert() { + let mut address_space: AddressSpace = AddressSpace::new(); + let object1 = TestObject::new(0x10); + let object2 = TestObject::new(0x10); + + assert!(address_space.map_object(Box::new(object1), 0x100).is_ok()); + assert!(address_space.map_object(Box::new(object2), 0x105).is_err()); + } + + #[test] + fn left_insert() { + let mut address_space: AddressSpace = AddressSpace::new(); + + let object1 = TestObject::new(0x1); + let object2 = TestObject::new(0x1); + + let _ = address_space.map_object(Box::new(object1), 0x110); + let _ = address_space.map_object(Box::new(object2), 0x100); + + assert_eq!( + address_space.memory_map.0.as_ref().unwrap().range.start, + 0x110 + ); + assert!(address_space.memory_map.0.as_ref().unwrap().right.is_none()); + assert_eq!( + address_space + .memory_map + .0 + .unwrap() + .left + .unwrap() + .range + .start, + 0x100 + ); + } + + #[test] + fn right_insert() { + let mut address_space: AddressSpace = AddressSpace::new(); + + let object1 = TestObject::new(0x1); + let object2 = TestObject::new(0x1); + + let _ = address_space.map_object(Box::new(object1), 0x100); + let _ = address_space.map_object(Box::new(object2), 0x110); + + assert_eq!( + address_space.memory_map.0.as_ref().unwrap().range.start, + 0x100 + ); + assert!(address_space.memory_map.0.as_ref().unwrap().left.is_none()); + assert_eq!( + address_space + .memory_map + .0 + .unwrap() + .right + .unwrap() + .range + .start, + 0x110 + ); + } + + #[test] + fn linear_left_height() { + let mut address_space: AddressSpace = AddressSpace::new(); + + let object1 = TestObject::new(0x1); + let object2 = TestObject::new(0x1); + let object3 = TestObject::new(0x1); + + let _ = address_space.map_object(Box::new(object1), 0x200); + let _ = address_space.map_object(Box::new(object2), 0x150); + let _ = address_space.map_object(Box::new(object3), 0x100); + + assert_eq!(address_space.memory_map.0.unwrap().get_height(), 2); + } + + #[test] + fn linear_right_height() { + let mut address_space: AddressSpace = AddressSpace::new(); + + let object1 = TestObject::new(0x1); + let object2 = TestObject::new(0x1); + let object3 = TestObject::new(0x1); + + let _ = address_space.map_object(Box::new(object1), 0x100); + let _ = address_space.map_object(Box::new(object2), 0x150); + let _ = address_space.map_object(Box::new(object3), 0x200); + + assert_eq!(address_space.memory_map.0.unwrap().get_height(), 2); + } + + #[test] + fn balanced_tree_height() { + let mut address_space: AddressSpace = AddressSpace::new(); + + let object_root = TestObject::new(0x1); + let object_left = TestObject::new(0x1); + let object_right = TestObject::new(0x1); + let object_left_left = TestObject::new(0x1); + let object_right_right = TestObject::new(0x1); + + let _ = address_space.map_object(Box::new(object_root), 0x100); + let _ = address_space.map_object(Box::new(object_left), 0x50); + let _ = address_space.map_object(Box::new(object_right), 0x150); + let _ = address_space.map_object(Box::new(object_left_left), 0x0); + let _ = address_space.map_object(Box::new(object_right_right), 0x200); + + assert_eq!(address_space.memory_map.0.unwrap().get_height(), 2); + } + + #[test] + fn unbalanced_tree_height() { + let mut address_space: AddressSpace = AddressSpace::new(); + + let object_root = TestObject::new(0x1); + let object_left = TestObject::new(0x1); + let object_right = TestObject::new(0x1); + let object_left_left = TestObject::new(0x1); + let object_left_left_left = TestObject::new(0x1); + + let _ = address_space.map_object(Box::new(object_root), 0x100); + let _ = address_space.map_object(Box::new(object_left), 0x50); + let _ = address_space.map_object(Box::new(object_right), 0x150); + let _ = address_space.map_object(Box::new(object_left_left), 0x25); + let _ = address_space.map_object(Box::new(object_left_left_left), 0x0); + + assert_eq!(address_space.memory_map.0.unwrap().get_height(), 3); + } + + #[test] + fn unbalanced_check() { + let mut address_space: AddressSpace = AddressSpace::new(); + + let object_root = TestObject::new(0x1); + let object_left = TestObject::new(0x1); + let object_right = TestObject::new(0x1); + let object_left_left = TestObject::new(0x1); + let object_left_left_left = TestObject::new(0x1); + + let _ = address_space.map_object(Box::new(object_root), 0x100); + let _ = address_space.map_object(Box::new(object_left), 0x50); + let _ = address_space.map_object(Box::new(object_right), 0x150); + let _ = address_space.map_object(Box::new(object_left_left), 0x25); + let _ = address_space.map_object(Box::new(object_left_left_left), 0x0); + + assert!(address_space.memory_map.0.unwrap().is_unbalanced()) + } + + #[test] + fn balanced_check() { + let mut address_space: AddressSpace = AddressSpace::new(); + + let object_root = TestObject::new(0x1); + let object_left = TestObject::new(0x1); + let object_right = TestObject::new(0x1); + let object_left_left = TestObject::new(0x1); + let object_right_right = TestObject::new(0x1); + + let _ = address_space.map_object(Box::new(object_root), 0x100); + let _ = address_space.map_object(Box::new(object_left), 0x50); + let _ = address_space.map_object(Box::new(object_right), 0x150); + let _ = address_space.map_object(Box::new(object_left_left), 0x0); + let _ = address_space.map_object(Box::new(object_right_right), 0x200); + + assert!(!address_space.memory_map.0.unwrap().is_unbalanced()); + } +} diff --git a/src/main.rs b/src/main.rs index 23a2acd..3b35055 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,7 +1,15 @@ //! dremu is a RISC-V 32-bits CPU emulator. -#![deny(warnings)] +#![deny( + warnings, + missing_docs, + clippy::all, + clippy::missing_docs_in_private_items, + rustdoc::missing_crate_level_docs +)] +mod address_space; +pub mod core; fn main() { println!("Hello, world!"); }