From 244a83e463fe618b477e37eab71357bb46d5b9ac Mon Sep 17 00:00:00 2001 From: Jianfeng Jiang Date: Wed, 30 Nov 2022 14:20:06 +0800 Subject: [PATCH] implement vmar apis --- .../jinux-std/src/process/signal/c_types.rs | 2 +- .../libs/jinux-std/src/vm/vmar/dyn_cap.rs | 12 +- .../libs/jinux-std/src/vm/vmar/mod.rs | 336 +++++++++++++++++- .../libs/jinux-std/src/vm/vmar/options.rs | 53 ++- .../libs/jinux-std/src/vm/vmar/static_cap.rs | 13 +- src/services/libs/jinux-std/src/vm/vmo/mod.rs | 2 +- .../libs/jinux-std/src/vm/vmo/options.rs | 50 ++- .../libs/typeflags-util/src/extend.rs | 15 + src/services/libs/typeflags-util/src/lib.rs | 2 + src/services/libs/typeflags-util/src/set.rs | 2 + src/services/libs/typeflags/src/flag_set.rs | 26 ++ src/services/libs/typeflags/src/lib.rs | 4 + src/services/libs/typeflags/src/type_flag.rs | 3 +- src/services/libs/typeflags/src/util.rs | 57 ++- 14 files changed, 505 insertions(+), 72 deletions(-) create mode 100644 src/services/libs/typeflags-util/src/extend.rs diff --git a/src/services/libs/jinux-std/src/process/signal/c_types.rs b/src/services/libs/jinux-std/src/process/signal/c_types.rs index 91beecc3..1012248d 100644 --- a/src/services/libs/jinux-std/src/process/signal/c_types.rs +++ b/src/services/libs/jinux-std/src/process/signal/c_types.rs @@ -1,7 +1,7 @@ #![allow(non_camel_case_types)] use core::mem; -use jinux_frame::{cpu::GpRegs, offset_of}; +use jinux_frame::cpu::GpRegs; use jinux_util::{read_union_fields, union_read_ptr::UnionReadPtr}; use crate::{prelude::*, process::Pid}; diff --git a/src/services/libs/jinux-std/src/vm/vmar/dyn_cap.rs b/src/services/libs/jinux-std/src/vm/vmar/dyn_cap.rs index a68fc58a..172be3a4 100644 --- a/src/services/libs/jinux-std/src/vm/vmar/dyn_cap.rs +++ b/src/services/libs/jinux-std/src/vm/vmar/dyn_cap.rs @@ -1,8 +1,6 @@ -use core::ops::Range; - use alloc::sync::Arc; -use jinux_frame::prelude::Result; -use jinux_frame::{vm::VmIo, Error}; +use core::ops::Range; +use jinux_frame::{vm::VmIo, Error, Result}; use crate::{rights::Rights, vm::vmo::Vmo}; @@ -13,8 +11,8 @@ use super::{ impl Vmar { /// Creates a root VMAR. - pub fn new() -> Result { - let inner = Arc::new(Vmar_::new()?); + pub fn new_root() -> Result { + let inner = Arc::new(Vmar_::new_root()?); let rights = Rights::all(); let new_self = Self(inner, rights); Ok(new_self) @@ -135,7 +133,7 @@ impl Vmar { /// The method requires the Dup right. pub fn dup(&self) -> Result { self.check_rights(Rights::DUP)?; - todo!() + Ok(Vmar(self.0.clone(), self.1.clone())) } /// Returns the access rights. diff --git a/src/services/libs/jinux-std/src/vm/vmar/mod.rs b/src/services/libs/jinux-std/src/vm/vmar/mod.rs index 3bb65cb8..1987bfb1 100644 --- a/src/services/libs/jinux-std/src/vm/vmar/mod.rs +++ b/src/services/libs/jinux-std/src/vm/vmar/mod.rs @@ -5,15 +5,24 @@ mod options; mod static_cap; use crate::rights::Rights; +use alloc::collections::BTreeMap; use alloc::sync::Arc; +use alloc::sync::Weak; +use alloc::vec::Vec; use bitflags::bitflags; +use jinux_frame::config::PAGE_SIZE; +// use jinux_frame::vm::VmPerm; use core::ops::Range; -use jinux_frame::prelude::Result; use jinux_frame::vm::Vaddr; +use jinux_frame::vm::VmIo; +// use jinux_frame::vm::VmPerm; use jinux_frame::vm::VmSpace; -use jinux_frame::Error; +use jinux_frame::AlignExt; +use jinux_frame::{Error, Result}; use spin::Mutex; +use super::vmo::Vmo; + /// Virtual Memory Address Regions (VMARs) are a type of capability that manages /// user address spaces. /// @@ -46,27 +55,109 @@ pub struct Vmar(Arc, R); // TODO: how page faults can be delivered to and handled by the current VMAR. struct Vmar_ { - inner: Mutex, - // The offset relative to the root VMAR + /// vmar inner + inner: Mutex, + /// The offset relative to the root VMAR base: Vaddr, - parent: Option>, + /// The total size of the VMAR in bytes + size: usize, + /// The attached vmspace + vm_space: Arc, + /// The parent vmar. If points to none, this is a root vmar + parent: Weak, } -struct Inner { +/// FIXME: How can a vmar have its child vmar and vmos with its rights? +struct VmarInner { + /// Whether the vmar is destroyed is_destroyed: bool, - vm_space: VmSpace, - //... + /// The child vmars. The key is offset relative to root VMAR + child_vmar_s: BTreeMap>, + /// The mapped vmos. The key is offset relative to root VMAR + mapped_vmos: BTreeMap>, + /// Free ranges that can be used for creating child vmar or mapping vmos + free_regions: BTreeMap, } +pub const ROOT_VMAR_HIGHEST_ADDR: Vaddr = 0x1000_0000_0000; + impl Vmar_ { - pub fn new() -> Result { - todo!() + pub fn new_root() -> Result { + let mut free_regions = BTreeMap::new(); + let root_region = FreeRegion::new(0..ROOT_VMAR_HIGHEST_ADDR); + free_regions.insert(root_region.start(), root_region); + let vmar_inner = VmarInner { + is_destroyed: false, + child_vmar_s: BTreeMap::new(), + mapped_vmos: BTreeMap::new(), + free_regions, + }; + let vmar_ = Vmar_ { + inner: Mutex::new(vmar_inner), + vm_space: Arc::new(VmSpace::new()), + base: 0, + size: ROOT_VMAR_HIGHEST_ADDR, + parent: Weak::new(), + }; + Ok(vmar_) } pub fn protect(&self, perms: VmPerms, range: Range) -> Result<()> { - todo!() + assert!(range.start % PAGE_SIZE == 0); + assert!(range.end % PAGE_SIZE == 0); + self.check_protected_range(&range)?; + self.do_protect_inner(perms, range)?; + Ok(()) } + // do real protect. The protected range is ensured to be mapped. + fn do_protect_inner(&self, perms: VmPerms, range: Range) -> Result<()> { + for (vmo_base, mapped_vmo) in &self.inner.lock().mapped_vmos { + let vmo_range = *vmo_base..(*vmo_base + mapped_vmo.size()); + if is_intersected(&range, &vmo_range) { + let intersected_range = get_intersected_range(&range, &vmo_range); + // TODO: How to protect a mapped vmo? + todo!() + } + } + + for (_, child_vmar_) in &self.inner.lock().child_vmar_s { + let child_vmar_range = child_vmar_.range(); + if is_intersected(&range, &child_vmar_range) { + let intersected_range = get_intersected_range(&range, &child_vmar_range); + child_vmar_.do_protect_inner(perms, intersected_range)?; + } + } + + Ok(()) + } + + /// ensure the whole protected range is mapped, that is to say, backed up by a VMO. + /// Internally, we check whether the range intersects any free region recursively. + /// If so, the range is not fully mapped. + fn check_protected_range(&self, protected_range: &Range) -> Result<()> { + // The protected range should be in self's range + assert!(self.base <= protected_range.start); + assert!(protected_range.end <= self.base + self.size); + + // The protected range should not interstect with any free region + for (_, free_region) in &self.inner.lock().free_regions { + if is_intersected(&free_region.range, &protected_range) { + return Err(Error::InvalidArgs); + } + } + + // if the protected range intersects with child vmar_, child vmar_ is responsible to do the check. + for (_, child_vmar_) in &self.inner.lock().child_vmar_s { + let child_range = child_vmar_.range(); + if is_intersected(&child_range, &protected_range) { + let intersected_range = get_intersected_range(&child_range, &protected_range); + child_vmar_.check_protected_range(&intersected_range)?; + } + } + + Ok(()) + } pub fn destroy_all(&self) -> Result<()> { todo!() } @@ -76,11 +167,146 @@ impl Vmar_ { } pub fn read(&self, offset: usize, buf: &mut [u8]) -> Result<()> { - todo!() + let read_start = self.base + offset; + let read_end = buf.len() + read_start; + // if the read range is in child vmar + for (child_vmar_base, child_vmar) in &self.inner.lock().child_vmar_s { + let child_vmar_end = *child_vmar_base + child_vmar.size; + if *child_vmar_base <= read_start && read_end <= child_vmar_end { + let child_offset = read_start - *child_vmar_base; + return child_vmar.read(child_offset, buf); + } + } + // if the read range is in mapped vmo + for (vmo_base, vmo) in &self.inner.lock().mapped_vmos { + let vmo_end = *vmo_base + vmo.size(); + if *vmo_base <= read_start && read_end <= vmo_end { + let vmo_offset = read_start - *vmo_base; + return vmo.read_bytes(vmo_offset, buf); + } + } + // FIXME: should be read the free range? + // for (_, free_region) in &self.inner.lock().free_regions { + // let (region_start, region_end) = free_region.range(); + // if region_start <= read_start && read_end <= region_end { + // return self.vm_space.read_bytes(read_start, buf); + // } + // } + + // FIXME: If the read range is across different vmos or child vmars, should we directly return error? + Err(Error::AccessDenied) } pub fn write(&self, offset: usize, buf: &[u8]) -> Result<()> { - todo!() + let write_start = self.base + offset; + let write_end = buf.len() + write_start; + // if the write range is in child vmar + for (child_vmar_base, child_vmar) in &self.inner.lock().child_vmar_s { + let child_vmar_end = *child_vmar_base + child_vmar.size; + if *child_vmar_base <= write_start && write_end <= child_vmar_end { + let child_offset = write_start - *child_vmar_base; + return child_vmar.write(child_offset, buf); + } + } + // if the write range is in mapped vmo + for (vmo_base, vmo) in &self.inner.lock().mapped_vmos { + let vmo_end = *vmo_base + vmo.size(); + if *vmo_base <= write_start && write_end <= vmo_end { + let vmo_offset = write_start - *vmo_base; + return vmo.write_bytes(vmo_offset, buf); + } + } + // if the write range is in free region + // FIXME: should we write the free region? + // for (_, free_region) in &self.inner.lock().free_regions { + // let (region_start, region_end) = free_region.range(); + // if region_start <= write_start && write_end <= region_end { + // return self.vm_space.write_bytes(write_start, buf); + // } + // } + + // FIXME: If the write range is across different vmos or child vmars, should we directly return error? + Err(Error::AccessDenied) + } + + /// allocate a child vmar_. + pub fn alloc_child_vmar( + self: &Arc, + child_vmar_offset: Option, + child_vmar_size: usize, + align: usize, + ) -> Result> { + match self.find_free_region_for_child_vmar(child_vmar_offset, child_vmar_size, align) { + None => return Err(Error::InvalidArgs), + Some((region_base, child_vmar_offset)) => { + // This unwrap should never fails + let free_region = self.inner.lock().free_regions.remove(®ion_base).unwrap(); + let child_range = child_vmar_offset..(child_vmar_offset + child_vmar_size); + let regions_after_allocation = free_region.allocate_range(child_range.clone()); + regions_after_allocation.into_iter().for_each(|region| { + self.inner + .lock() + .free_regions + .insert(region.start(), region); + }); + let child_region = FreeRegion::new(child_range); + let mut child_regions = BTreeMap::new(); + child_regions.insert(child_region.start(), child_region); + let child_vmar_inner = VmarInner { + is_destroyed: false, + child_vmar_s: BTreeMap::new(), + mapped_vmos: BTreeMap::new(), + free_regions: child_regions, + }; + let child_vmar_ = Arc::new(Vmar_ { + inner: Mutex::new(child_vmar_inner), + base: child_vmar_offset, + size: child_vmar_size, + vm_space: self.vm_space.clone(), + parent: Arc::downgrade(self), + }); + self.inner + .lock() + .child_vmar_s + .insert(child_vmar_.base, child_vmar_.clone()); + Ok(child_vmar_) + } + } + } + + /// returns (region base addr, child real offset) + fn find_free_region_for_child_vmar( + &self, + child_vmar_offset: Option, + child_vmar_size: usize, + align: usize, + ) -> Option<(Vaddr, Vaddr)> { + for (region_base, free_region) in &self.inner.lock().free_regions { + if let Some(child_vmar_offset) = child_vmar_offset { + // if the offset is set, we should find a free region can satisfy both the offset and size + if *region_base <= child_vmar_offset + && (child_vmar_offset + child_vmar_size) <= (free_region.end()) + { + return Some((*region_base, child_vmar_offset)); + } + } else { + // else, we find a free region that can satisfy the length and align requirement. + // Here, we use a simple brute-force algorithm to find the first free range that can satisfy. + // FIXME: A randomized algorithm may be more efficient. + let region_start = free_region.start(); + let region_end = free_region.end(); + let child_vmar_real_start = region_start.align_up(align); + let child_vmar_real_end = child_vmar_real_start + child_vmar_size; + if region_start <= child_vmar_real_start && child_vmar_real_end <= region_end { + return Some((*region_base, child_vmar_real_start)); + } + } + } + None + } + + fn range(&self) -> Range { + self.base..(self.base + self.size) } } @@ -91,6 +317,11 @@ impl Vmar { pub fn base(&self) -> Vaddr { self.0.base } + + /// The size of the vmar in bytes. + pub fn size(&self) -> usize { + self.0.size + } } bitflags! { @@ -107,12 +338,87 @@ bitflags! { impl From for VmPerms { fn from(rights: Rights) -> VmPerms { - todo!() + let mut vm_perm = VmPerms::empty(); + if rights.contains(Rights::READ) { + vm_perm |= VmPerms::READ; + } + if rights.contains(Rights::WRITE) { + vm_perm |= VmPerms::WRITE; + } + if rights.contains(Rights::EXEC) { + vm_perm |= VmPerms::EXEC; + } + vm_perm } } impl From for Rights { fn from(vm_perms: VmPerms) -> Rights { - todo!() + let mut rights = Rights::empty(); + if vm_perms.contains(VmPerms::READ) { + rights |= Rights::READ; + } + if vm_perms.contains(VmPerms::WRITE) { + rights |= Rights::WRITE; + } + if vm_perms.contains(VmPerms::EXEC) { + rights |= Rights::EXEC; + } + rights } } + +pub struct FreeRegion { + range: Range, +} + +impl FreeRegion { + pub fn new(range: Range) -> Self { + Self { range } + } + + pub fn start(&self) -> Vaddr { + self.range.start + } + + pub fn end(&self) -> Vaddr { + self.range.end + } + + pub fn size(&self) -> usize { + self.range.end - self.range.start + } + + pub fn range(&self) -> &Range { + &self.range + } + + /// allocate a range in this free region. + /// The range is ensured to be contained in current region before call this function. + /// The return vector contains regions that are not allocated. Since the allocate_range can be + /// in the middle of a free region, the original region may be split as at most two regions. + pub fn allocate_range(&self, allocate_range: Range) -> Vec { + let mut res = Vec::new(); + if self.range.start < allocate_range.start { + let free_region = FreeRegion::new(self.range.start..allocate_range.start); + res.push(free_region); + } + if allocate_range.end < self.range.end { + let free_region = FreeRegion::new(allocate_range.end..self.range.end); + res.push(free_region); + } + res + } +} + +/// determine whether two ranges are intersected. +fn is_intersected(range1: &Range, range2: &Range) -> bool { + range1.start.max(range2.start) < range1.end.min(range2.end) +} + +/// get the intersection range of two ranges. +/// The two ranges should be ensured to be intersected. +fn get_intersected_range(range1: &Range, range2: &Range) -> Range { + debug_assert!(is_intersected(range1, range2)); + range1.start.max(range2.start)..range1.end.min(range2.end) +} diff --git a/src/services/libs/jinux-std/src/vm/vmar/options.rs b/src/services/libs/jinux-std/src/vm/vmar/options.rs index d6dbd003..e11e595d 100644 --- a/src/services/libs/jinux-std/src/vm/vmar/options.rs +++ b/src/services/libs/jinux-std/src/vm/vmar/options.rs @@ -1,7 +1,7 @@ //! Options for allocating child VMARs and creating mappings. -use jinux_frame::prelude::Result; use jinux_frame::{config::PAGE_SIZE, vm::Vaddr}; +use jinux_frame::{Error, Result}; use crate::vm::vmo::Vmo; @@ -27,7 +27,7 @@ use super::{VmPerms, Vmar}; /// assert!(child_vmar.size() == child_size); /// ``` /// -/// A child VMO created from a parent VMO of _static_ capability is also a +/// A child VMAR created from a parent VMAR of _static_ capability is also a /// _static_ capability. /// ``` /// use jinux_std::prelude::*; @@ -45,8 +45,8 @@ use super::{VmPerms, Vmar}; pub struct VmarChildOptions { parent: Vmar, size: usize, - offset: usize, - align: usize, + offset: Option, + align: Option, } impl VmarChildOptions { @@ -58,8 +58,8 @@ impl VmarChildOptions { Self { parent, size, - offset: 0, - align: PAGE_SIZE, + offset: None, + align: None, } } @@ -69,7 +69,8 @@ impl VmarChildOptions { /// /// The alignment must be a power of two and a multiple of the page size. pub fn align(mut self, align: usize) -> Self { - todo!() + self.align = Some(align); + self } /// Sets the offset of the child VMAR. @@ -84,7 +85,8 @@ impl VmarChildOptions { /// /// The offset must be page-aligned. pub fn offset(mut self, offset: usize) -> Self { - todo!() + self.offset = Some(offset); + self } /// Allocates the child VMAR according to the specified options. @@ -94,8 +96,39 @@ impl VmarChildOptions { /// # Access rights /// /// The child VMAR is initially assigned all the parent's access rights. - pub fn alloc(mut self) -> Result> { - todo!() + pub fn alloc(self) -> Result> { + // check align + let align = if let Some(align) = self.align { + if align % PAGE_SIZE != 0 || !align.is_power_of_two() { + return Err(Error::InvalidArgs); + } + align + } else { + PAGE_SIZE + }; + // check size + if self.size % align != 0 { + return Err(Error::InvalidArgs); + } + // check offset + let root_vmar_offset = if let Some(offset) = self.offset { + if offset % PAGE_SIZE != 0 { + return Err(Error::InvalidArgs); + } + let root_vmar_offset = offset + self.parent.base(); + if root_vmar_offset % align != 0 { + return Err(Error::InvalidArgs); + } + Some(root_vmar_offset) + } else { + None + }; + let child_vmar_ = self + .parent + .0 + .alloc_child_vmar(root_vmar_offset, self.size, align)?; + let child_vmar = Vmar(child_vmar_, self.parent.1); + Ok(child_vmar) } } diff --git a/src/services/libs/jinux-std/src/vm/vmar/static_cap.rs b/src/services/libs/jinux-std/src/vm/vmar/static_cap.rs index 6a9a9391..5f84e8eb 100644 --- a/src/services/libs/jinux-std/src/vm/vmar/static_cap.rs +++ b/src/services/libs/jinux-std/src/vm/vmar/static_cap.rs @@ -1,8 +1,7 @@ use core::ops::Range; use alloc::sync::Arc; -use jinux_frame::prelude::Result; -use jinux_frame::{vm::VmIo, Error}; +use jinux_frame::{vm::VmIo, Error, Result}; use jinux_rights_proc::require; use crate::{ @@ -21,8 +20,8 @@ impl Vmar { /// # Access rights /// /// A root VMAR is initially given full access rights. - pub fn new() -> Result { - let inner = Arc::new(Vmar_::new()?); + pub fn new_root() -> Result { + let inner = Arc::new(Vmar_::new_root()?); let rights = R::new(); let new_self = Self(inner, rights); Ok(new_self) @@ -141,13 +140,13 @@ impl Vmar { /// The method requires the Dup right. #[require(R > Dup)] pub fn dup(&self) -> Result { - todo!() + Ok(Vmar(self.0.clone(), self.1)) } /// Strict the access rights. #[require(R > R1)] - pub fn restrict(mut self) -> Vmo { - todo!() + pub fn restrict(self) -> Vmar { + Vmar(self.0, R1::new()) } /// Returns the access rights. diff --git a/src/services/libs/jinux-std/src/vm/vmo/mod.rs b/src/services/libs/jinux-std/src/vm/vmo/mod.rs index 6d104554..c7f0c53a 100644 --- a/src/services/libs/jinux-std/src/vm/vmo/mod.rs +++ b/src/services/libs/jinux-std/src/vm/vmo/mod.rs @@ -71,7 +71,7 @@ use spin::Mutex; /// `Vmo` is easier to use (by offering more powerful APIs) and /// harder to misuse (thanks to its nature of being capability). /// -pub struct Vmo(Arc, R); +pub struct Vmo(Arc, R); bitflags! { /// VMO flags. diff --git a/src/services/libs/jinux-std/src/vm/vmo/options.rs b/src/services/libs/jinux-std/src/vm/vmo/options.rs index 17ccaed0..4298fd9e 100644 --- a/src/services/libs/jinux-std/src/vm/vmo/options.rs +++ b/src/services/libs/jinux-std/src/vm/vmo/options.rs @@ -3,12 +3,12 @@ use core::marker::PhantomData; use core::ops::Range; -use alloc::sync::Arc; -use jinux_frame::prelude::Result; +use crate::prelude::*; use jinux_frame::vm::Paddr; use jinux_rights_proc::require; +use typeflags_util::{SetExtend, SetExtendOp}; -use crate::rights::{Dup, Rights, TRights}; +use crate::rights::{Dup, Rights, TRights, Write}; use super::{Pager, Vmo, VmoFlags}; @@ -50,8 +50,8 @@ pub struct VmoOptions { size: usize, paddr: Option, flags: VmoFlags, - rights: R, - // supplier: Option>, + rights: Option, + pager: Option>, } impl VmoOptions { @@ -60,7 +60,13 @@ impl VmoOptions { /// /// The size of the VMO will be rounded up to align with the page size. pub fn new(size: usize) -> Self { - todo!() + Self { + size, + paddr: None, + flags: VmoFlags::empty(), + rights: None, + pager: None, + } } /// Sets the starting physical address of the VMO. @@ -70,7 +76,9 @@ impl VmoOptions { /// If this option is set, then the underlying pages of VMO must be contiguous. /// So `VmoFlags::IS_CONTIGUOUS` will be set automatically. pub fn paddr(mut self, paddr: Paddr) -> Self { - todo!() + self.paddr = Some(paddr); + self.flags |= VmoFlags::CONTIGUOUS; + self } /// Sets the VMO flags. @@ -79,12 +87,14 @@ impl VmoOptions { /// /// For more information about the flags, see `VmoFlags`. pub fn flags(mut self, flags: VmoFlags) -> Self { - todo!() + self.flags = flags; + self } /// Sets the pager of the VMO. pub fn pager(mut self, pager: Arc) -> Self { - todo!() + self.pager = Some(pager); + self } } @@ -181,7 +191,7 @@ impl VmoOptions { /// Note that a slice VMO child and its parent cannot not be resizable. /// /// ```rust -/// use _std::vm::{PAGE_SIZE, VmoOptions}; +/// use jinux_std::vm::{PAGE_SIZE, VmoOptions}; /// /// let parent_vmo = VmoOptions::new(PAGE_SIZE) /// .alloc() @@ -313,28 +323,12 @@ impl VmoChildOptions { /// /// The child VMO is initially assigned all the parent's access rights /// plus the Write right. - pub fn alloc(mut self) -> Result> + pub fn alloc(mut self) -> Result>> where - R1: TRights, // TODO: R1 must contain the Write right. To do so at the type level, - // we need to implement a type-level operator - // (say, `TRightsExtend(L, F)`) - // that may extend a list (`L`) of type-level flags with an extra flag `F`. - // TRightsExtend + R: SetExtend, { todo!() } - - // original: - // pub fn alloc(mut self) -> Result> - // where - // // TODO: R1 must contain the Write right. To do so at the type level, - // // we need to implement a type-level operator - // // (say, `TRightsExtend(L, F)`) - // // that may extend a list (`L`) of type-level flags with an extra flag `F`. - // R1: R // TRightsExtend - // { - // todo!() - // } } /// A type to specify the "type" of a child, which is either a slice or a COW. diff --git a/src/services/libs/typeflags-util/src/extend.rs b/src/services/libs/typeflags-util/src/extend.rs new file mode 100644 index 00000000..3f579c77 --- /dev/null +++ b/src/services/libs/typeflags-util/src/extend.rs @@ -0,0 +1,15 @@ +use crate::{Cons, Nil}; + +/// This trait will extend a set with another item. +/// If the set already contains the item, it will return the original set. +/// Otherwise, it will return the new set with the new item. +/// The implementation should care about the item orders when extending set. +pub trait SetExtend { + type Output; +} + +pub type SetExtendOp = >::Output; + +impl SetExtend for Nil { + type Output = Cons; +} diff --git a/src/services/libs/typeflags-util/src/lib.rs b/src/services/libs/typeflags-util/src/lib.rs index 22a82375..df8f37a1 100644 --- a/src/services/libs/typeflags-util/src/lib.rs +++ b/src/services/libs/typeflags-util/src/lib.rs @@ -5,11 +5,13 @@ #![no_std] pub mod assert; pub mod bool; +pub mod extend; pub mod if_; pub mod same; pub mod set; pub use crate::bool::{And, AndOp, False, IsFalse, IsTrue, Not, NotOp, Or, OrOp, True}; +pub use crate::extend::{SetExtend, SetExtendOp}; pub use crate::same::{SameAs, SameAsOp}; pub use crate::set::{Cons, Nil, Set, SetContain, SetContainOp, SetInclude, SetIncludeOp}; pub use assert::AssertTypeSame; diff --git a/src/services/libs/typeflags-util/src/set.rs b/src/services/libs/typeflags-util/src/set.rs index bd89bed6..a9ee0e21 100644 --- a/src/services/libs/typeflags-util/src/set.rs +++ b/src/services/libs/typeflags-util/src/set.rs @@ -13,6 +13,7 @@ use core::ops::BitOr as Or; pub trait Set {} /// An non-empty type-level set. +#[derive(Debug, Clone, Copy)] pub struct Cons(PhantomData<(T, S)>); impl Cons { @@ -22,6 +23,7 @@ impl Cons { } /// An empty type-level set. +#[derive(Debug, Clone, Copy)] pub struct Nil; impl Set for Cons {} diff --git a/src/services/libs/typeflags/src/flag_set.rs b/src/services/libs/typeflags/src/flag_set.rs index 05708eee..1045fdde 100644 --- a/src/services/libs/typeflags/src/flag_set.rs +++ b/src/services/libs/typeflags/src/flag_set.rs @@ -11,6 +11,7 @@ const SET_NAME: &'static str = "::typeflags_util::Cons"; /// A flagSet represent the combination of differnt flag item. /// e.g. [Read, Write], [Read], [] are all flag sets. /// The order of flagItem does not matters. So flag sets with same sets of items should be viewed as the same set. +#[derive(Debug)] pub struct FlagSet { items: Vec, } @@ -107,6 +108,23 @@ impl FlagSet { } } + pub fn contains_type(&self, type_ident: &Ident) -> bool { + let type_name = type_ident.to_string(); + self.items + .iter() + .position(|item| item.ident.to_string() == type_name) + .is_some() + } + + pub fn contains_set(&self, other_set: &FlagSet) -> bool { + for item in &other_set.items { + if !self.contains_type(&item.ident) { + return false; + } + } + return true; + } + /// The token stream inside macro definition. We will generate a token stream for each permutation of items /// since the user may use arbitrary order of items in macro. pub fn macro_item_tokens(&self) -> Vec { @@ -135,6 +153,14 @@ pub struct FlagItem { val: Expr, } +impl core::fmt::Debug for FlagItem { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("FlagItem") + .field("ident", &self.ident.to_string()) + .finish() + } +} + /// generate all possible flag sets pub fn generate_flag_sets(type_flag_def: &TypeFlagDef) -> Vec { let flag_items = type_flag_def diff --git a/src/services/libs/typeflags/src/lib.rs b/src/services/libs/typeflags/src/lib.rs index 1bf64b3e..2aff5b9d 100644 --- a/src/services/libs/typeflags/src/lib.rs +++ b/src/services/libs/typeflags/src/lib.rs @@ -43,6 +43,10 @@ //! assert_type_same!(SetIncludeOp, False); //! assert_type_same!(SetIncludeOp, True); //! assert_type_same!(SetIncludeOp, False); +//! assert_type_same!(SetExtendOp, R); +//! assert_type_same!(SetExtendOp, RW); +//! assert_type_same!(SetExtendOp, R); +//! assert_type_same!(SetExtendOp, RW); //! ``` #![feature(proc_macro_diagnostic)] diff --git a/src/services/libs/typeflags/src/type_flag.rs b/src/services/libs/typeflags/src/type_flag.rs index d7c54d35..e5f4dda7 100644 --- a/src/services/libs/typeflags/src/type_flag.rs +++ b/src/services/libs/typeflags/src/type_flag.rs @@ -77,7 +77,7 @@ impl TypeFlagDef { let type_ = self.type_.clone(); quote!( #(#attributes)* - #vis trait #ident : Sync + Send{ + #vis trait #ident : Sync + Send + Copy + Clone{ const BITS: #type_; fn new() -> Self; @@ -128,6 +128,7 @@ impl TypeFlagItem { let ident = self.ident.clone(); quote!( #(#attributes)* + #[derive(Copy, Clone, Debug)] #vis struct #ident {} ) } diff --git a/src/services/libs/typeflags/src/util.rs b/src/services/libs/typeflags/src/util.rs index b1d1c7d0..4b644fde 100644 --- a/src/services/libs/typeflags/src/util.rs +++ b/src/services/libs/typeflags/src/util.rs @@ -1,8 +1,8 @@ -use proc_macro2::TokenStream; +use proc_macro2::{Ident, TokenStream}; use quote::{quote, TokenStreamExt}; use crate::{ - flag_set::{generate_flag_sets, FlagSet}, + flag_set::{self, generate_flag_sets, FlagSet}, type_flag::TypeFlagDef, }; @@ -23,6 +23,9 @@ pub fn expand_type_flag(type_flags_def: &TypeFlagDef) -> TokenStream { all_tokens.append_all(impl_main_trait_tokens); }); + let impl_set_entend_tokens = impl_set_extend(type_flags_def, &flag_sets); + all_tokens.append_all(impl_set_entend_tokens); + let export_declarive_macro_tokens = export_declarive_macro(type_flags_def, &flag_sets); all_tokens.append_all(export_declarive_macro_tokens); @@ -73,6 +76,56 @@ pub fn impl_same_as(type_flags_def: &TypeFlagDef) -> TokenStream { all_tokens } +pub fn impl_set_extend(type_flags_def: &TypeFlagDef, flag_sets: &[FlagSet]) -> TokenStream { + let mut all_tokens = TokenStream::new(); + let type_idents: Vec<_> = type_flags_def + .items_iter() + .map(|type_flag_item| type_flag_item.ident()) + .collect(); + + for flag_set in flag_sets { + // We don't need to impl set extend trait for Nil + if flag_set.len() == 0 { + continue; + } + for type_ident in &type_idents { + let type_ident = type_ident.clone(); + let flag_set_tokens = flag_set.type_name_tokens(); + if flag_set.contains_type(&type_ident) { + // the flagset contains the type + let impl_extend_tokens = quote!( + impl ::typeflags_util::SetExtend<#type_ident> for #flag_set_tokens { + type Output = #flag_set_tokens; + } + ); + all_tokens.append_all(impl_extend_tokens) + } else { + // the flagset does not contains the type + let output_set = extent_one_type(&type_ident, flag_set, flag_sets).unwrap(); + let output_set_tokens = output_set.type_name_tokens(); + let impl_extend_tokens = quote!( + impl ::typeflags_util::SetExtend<#type_ident> for #flag_set_tokens { + type Output = #output_set_tokens; + } + ); + all_tokens.append_all(impl_extend_tokens); + } + } + } + + all_tokens +} + +fn extent_one_type<'a>( + type_ident: &Ident, + flag_set: &'a FlagSet, + sets: &'a [FlagSet], +) -> Option<&'a FlagSet> { + sets.iter().find(|bigger_set| { + bigger_set.contains_type(type_ident) && bigger_set.contains_set(flag_set) + }) +} + /// export the declarive macro pub fn export_declarive_macro(type_flags_def: &TypeFlagDef, flag_sets: &[FlagSet]) -> TokenStream { let macro_ident = type_flags_def.trait_ident();