From a7a4aa7d4c1056f74713e35953e919204df00706 Mon Sep 17 00:00:00 2001 From: Adrien Burgun Date: Sun, 3 Jul 2022 21:07:39 +0200 Subject: [PATCH] :sparkles: Use veccell, making Miri happy --- README.md | 5 +- stackline/Cargo.toml | 1 + stackline/src/context.rs | 153 ++++++++++++++++----------------------- stackline/src/pane.rs | 29 ++++++-- 4 files changed, 88 insertions(+), 100 deletions(-) diff --git a/README.md b/README.md index 506858f..6dee5fb 100644 --- a/README.md +++ b/README.md @@ -2,4 +2,7 @@ This is the successor of [stackline](https://github.com/adri326/stackline), an esoteric language inspired by [Wireworld](https://mathworld.wolfram.com/WireWorld.html) and [ORCA](https://github.com/hundredrabbits/Orca). -You can read the documentation here at [https://adri326.github.io/stackline2](https://adri326.github.io/stackline2). +Although it wasn't influenced by it, stackline has a lot in common with [Befunge](https://esolangs.org/wiki/Befunge). +[Befungell](https://github.com/zwade/Befungell)'s concurrency model acts in a very similar way to Stackline's, with the main difference being that Stackline's concurrency stems from a cellular automaton. + +Until the core of the language is stabilized and submitted to crates.io, its documentation will be rendered daily on [https://shadamethyst.xyz/stackline/](https://shadamethyst.xyz/stackline/). diff --git a/stackline/Cargo.toml b/stackline/Cargo.toml index ada9ccc..f39f60e 100755 --- a/stackline/Cargo.toml +++ b/stackline/Cargo.toml @@ -10,6 +10,7 @@ edition = "2021" palette = "0.6" enum_dispatch = "0.3" contracts = { version = "0.6.3", features = ["override_debug"] } +veccell = "0.1" [dev-dependencies] colored = "2.0" diff --git a/stackline/src/context.rs b/stackline/src/context.rs index 579cbfe..649bee9 100644 --- a/stackline/src/context.rs +++ b/stackline/src/context.rs @@ -1,5 +1,6 @@ use super::*; use std::ptr::NonNull; +use veccell::{VecRef, VecRefMut}; // TODO: write VecCell, to make miri happy @@ -55,15 +56,10 @@ use std::ptr::NonNull; } ``` - - # Safety - - Because [`Tile::update`] requires a `&mut self` reference, the current [`Tile`] cannot be accessed through [`UpdateContext::get`]. - This structure stores the [`State`] and [`Signal`] of the [`FullTile`] containing the current tile, so these can be accessed nonetheless, and it is still possible and safe to call [`UpdateContext::send`] on the current position. **/ pub struct UpdateContext<'a> { position: (usize, usize), - pane: NonNull, + pane: &'a Pane, state: State, signal: Option, commit: &'a mut UpdateCommit, @@ -82,7 +78,7 @@ impl<'a> UpdateContext<'a> { "Should return None if the tile was already updated" )] #[ensures( - old(pane.get(position).is_some() && pane.get(position).unwrap().get().is_none()) -> ret.is_none(), + old(pane.get(position).is_some() && (*pane.get(position).unwrap()).get().is_none()) -> ret.is_none(), "Should return None if the tile is empty" )] #[ensures( @@ -90,30 +86,28 @@ impl<'a> UpdateContext<'a> { "Should add an entry in self.commit.updates if result is Some" )] pub(crate) fn new( - pane: &'a mut Pane, + pane: &'a Pane, position: (usize, usize), commit: &'a mut UpdateCommit, - ) -> Option<(UpdateContext<'a>, &'a mut AnyTile)> { - let mut tile = pane.get_mut(position)?; + ) -> Option<(UpdateContext<'a>, VecRefMut<'a, FullTile>)> { + let mut tile = pane.borrow_mut(position)?; if tile.updated { return None; } tile.updated = true; // prevent duplicate updates commit.updates.push(position); - let ptr: *mut AnyTile = &mut **(tile.get_mut().as_mut()?); - let res = Self { position, state: tile.state(), signal: tile.take_signal(), - pane: unsafe { NonNull::new_unchecked(&mut *pane) }, + pane: pane, commit, }; // SAFETY: ptr is a valid pointer // SAFETY: aliasing is prevented by the invariants of UpdateContext - Some((res, unsafe { &mut *ptr })) + Some((res, tile)) } /// Returns the position of the currently updated tile. @@ -142,19 +136,13 @@ impl<'a> UpdateContext<'a> { /// Returns the [`width`](Pane::width) of the current [`Pane`]. #[inline] pub fn width(&self) -> NonZeroUsize { - unsafe { - // SAFETY: we only read self.pane.width - self.pane.as_ref().width() - } + self.pane.width() } /// Returns the [`height`](Pane::height) of the current [`Pane`]. #[inline] pub fn height(&self) -> NonZeroUsize { - unsafe { - // SAFETY: we only read self.pane.height - self.pane.as_ref().height() - } + self.pane.height() } /// Returns a reference to the [signal](crate::FullTile::signal) of the currently updated tile. @@ -239,46 +227,33 @@ impl<'a> UpdateContext<'a> { /// Returns an immutable reference to the [FullTile] at `pos` in the current [Pane]. /// Returns `None` if the tile is borrowed mutably, if it is the current tile or if it does not exist. #[inline] - #[ensures( - ret.is_some() -> ret.unwrap().get().is_some() -> - std::ptr::addr_of!(*ret.unwrap().get().unwrap()) != self.current_ptr() - )] - pub fn get<'b>(&'b self, pos: (usize, usize)) -> Option<&'b FullTile> + pub fn get<'b>(&'b self, pos: (usize, usize)) -> Option> where 'a: 'b, { if self.position == pos { None } else { - unsafe { - // SAFETY: pos != self.position, thus self.pane[self.position].cell cannot be accessed - self.pane.as_ref().get(pos) - } + self.pane.get(pos) } } /// Returns `Some((position.x + Δx, position.y + Δy))` iff `(x + Δx, y + Δy)` is inside the current pane. #[inline] pub fn offset(&self, offset: (i8, i8)) -> Option<(usize, usize)> { - unsafe { - // SAFETY: Pane::offset does not read `self.pane.cells` - self.pane.as_ref().offset(self.position, offset) - } + self.pane.offset(self.position, offset) } /// Returns `true` iff `(x, y)` is within the bounds of the current pane. #[inline] #[ensures(ret == true -> position.0 < self.width().get() && position.1 < self.height().get())] pub fn in_bounds(&self, position: (usize, usize)) -> bool { - unsafe { - // SAFETY: Pane::in_bounds does not read `self.pane.cells` - self.pane.as_ref().in_bounds(position) - } + self.pane.in_bounds(position) } /// Shortcut for calling both `ctx.offset(offset)` and `ctx.get(pos)` #[inline] - pub fn get_offset<'b>(&'b self, offset: (i8, i8)) -> Option<((usize, usize), &'b FullTile)> + pub fn get_offset<'b>(&'b self, offset: (i8, i8)) -> Option<((usize, usize), VecRef<'b, FullTile>)> where 'a: 'b, { @@ -289,7 +264,7 @@ impl<'a> UpdateContext<'a> { /// Returns whether or not the tile at `pos` accepts a signal coming from `direction`. /// If the tile does not exist, then this function will return `false`. #[inline] - #[ensures(ret == true -> self.get(pos).is_some() && self.get(pos).unwrap().get().is_some())] + #[ensures(ret == true -> self.get(pos).is_some() && (*self.get(pos).unwrap()).get().is_some())] pub fn accepts_signal(&self, pos: (usize, usize), direction: Direction) -> bool { match self.get(pos) { Some(tile) => tile.accepts_signal(direction), @@ -387,57 +362,51 @@ impl<'a> UpdateContext<'a> { } } - /// Stores the current signal back in the current tile, guaranteeing that it will stay there for - /// this update cycle. See [`take_signal`](UpdateContext::take_signal) for more information. - /// - /// This method differs from [`send`](UpdateContext::send), as it takes action immediately. - /// The signal may also not be modified, as it would otherwise break the guarantees of [`Pane::step`]. - /// - /// This function will [`std::mem::take`] the signal stored in `UpdateContext`, similar to [`take_signal`](UpdateContext::take_signal). - /// If you wish to modify or send copies of the signal, then you will need to call [`signal`](UpdateContext::signal) beforehand and make - /// clones of the signal before calling `keep`. - /// - /// If `take_signal` or `keep` are called before this functions, then it will do nothing. - /// - /// # Example - /// - /// ``` - /// # use stackline::prelude::*; - /// #[derive(Clone, Debug)] - /// pub struct StorageTile {}; - /// - /// impl Tile for StorageTile { - /// fn update<'b>(&'b mut self, mut ctx: UpdateContext<'b>) { - /// if ctx.signal().is_some() { - /// ctx.keep(); - /// } - /// // If we weren't to do this, then the signal would get dropped here - /// } - /// } - /// ``` - #[ensures(self.signal.is_none())] - pub fn keep(&mut self) { - if self.signal.is_none() { - return; - } - - unsafe { - // SAFETY: we only access self.pane[self.position].signal, not self.pane[self.position].cell - self.pane - .as_mut() - .get_mut(self.position) - .unwrap_or_else(|| unreachable!()) - .set_signal(std::mem::take(&mut self.signal)); - } - } - - /// Returns a pointer to self.pane[self.position].cell - fn current_ptr(&self) -> *const AnyTile { - let reference = unsafe { - self.pane.as_ref().get(self.position).unwrap() - }; - &*reference.get().unwrap() - } + // TODO: re-implement through UpdateCommit + + // /// Stores the current signal back in the current tile, guaranteeing that it will stay there for + // /// this update cycle. See [`take_signal`](UpdateContext::take_signal) for more information. + // /// + // /// This method differs from [`send`](UpdateContext::send), as it takes action immediately. + // /// The signal may also not be modified, as it would otherwise break the guarantees of [`Pane::step`]. + // /// + // /// This function will [`std::mem::take`] the signal stored in `UpdateContext`, similar to [`take_signal`](UpdateContext::take_signal). + // /// If you wish to modify or send copies of the signal, then you will need to call [`signal`](UpdateContext::signal) beforehand and make + // /// clones of the signal before calling `keep`. + // /// + // /// If `take_signal` or `keep` are called before this functions, then it will do nothing. + // /// + // /// # Example + // /// + // /// ``` + // /// # use stackline::prelude::*; + // /// #[derive(Clone, Debug)] + // /// pub struct StorageTile {}; + // /// + // /// impl Tile for StorageTile { + // /// fn update<'b>(&'b mut self, mut ctx: UpdateContext<'b>) { + // /// if ctx.signal().is_some() { + // /// ctx.keep(); + // /// } + // /// // If we weren't to do this, then the signal would get dropped here + // /// } + // /// } + // /// ``` + // #[ensures(self.signal.is_none())] + // pub fn keep(&mut self) { + // if self.signal.is_none() { + // return; + // } + + // unsafe { + // // SAFETY: we only access self.pane[self.position].signal, not self.pane[self.position].cell + // self.pane + // .as_mut() + // .get_mut(self.position) + // .unwrap_or_else(|| unreachable!()) + // .set_signal(std::mem::take(&mut self.signal)); + // } + // } } pub struct SendError(pub Signal); diff --git a/stackline/src/pane.rs b/stackline/src/pane.rs index c709914..8549f91 100644 --- a/stackline/src/pane.rs +++ b/stackline/src/pane.rs @@ -1,8 +1,9 @@ use super::*; +use veccell::{VecCell, VecRef, VecRefMut}; #[derive(Debug)] pub struct Pane { - tiles: Vec, + tiles: VecCell, width: NonZeroUsize, height: NonZeroUsize, @@ -37,11 +38,16 @@ impl Pane { pub fn empty(width: usize, height: usize) -> Option { // TODO: check that width * height is a valid usize let length = width.checked_mul(height)?; + let mut tiles = VecCell::with_capacity(length); + + for _ in 0..length { + tiles.push(FullTile::default()); + } Some(Self { width: width.try_into().ok()?, height: height.try_into().ok()?, - tiles: vec![FullTile::default(); length], + tiles, signals: Vec::new(), }) @@ -138,7 +144,7 @@ impl Pane { /// ``` #[inline] #[ensures(self.in_bounds(position) -> ret.is_some())] - pub fn get<'b>(&'b self, position: (usize, usize)) -> Option<&'b FullTile> { + pub fn get<'b>(&'b self, position: (usize, usize)) -> Option> { if !self.in_bounds(position) { return None; } @@ -172,6 +178,15 @@ impl Pane { .get_mut(position.1 * self.width.get() + position.0) } + pub(crate) fn borrow_mut<'b>(&'b self, position: (usize, usize)) -> Option> { + if !self.in_bounds(position) { + return None; + } + + self.tiles + .borrow_mut(position.1 * self.width.get() + position.0) + } + /// Sets the tile at `position` to `tile`. `T` must either implement [`Tile`] or be `()`. #[inline] #[ensures(self.in_bounds(position) -> ret.is_some())] @@ -240,7 +255,7 @@ impl Pane { /// assert!(pane.get((0, 0)).unwrap().signal().is_some()); /// ``` #[inline] - #[ensures(ret.is_some() -> self.in_bounds(position) && self.get(position).unwrap().get().is_some())] + #[ensures(ret.is_some() -> self.in_bounds(position) && (*self.get(position).unwrap()).get().is_some())] #[ensures(!self.in_bounds(position) -> ret.is_none())] pub fn set_signal(&mut self, position: (usize, usize), mut signal: Signal) -> Option<()> { signal.set_position(position); @@ -277,9 +292,9 @@ impl Pane { #[ensures(!self.in_bounds(position) -> ret.is_none())] fn update(&mut self, position: (usize, usize), commit: &mut UpdateCommit) -> Option<()> { // NOTE: Tiles will only be updated once as per UpdateContext::new - let (ctx, tile) = UpdateContext::new(self, position, commit)?; + let (ctx, mut tile) = UpdateContext::new(self, position, commit)?; - tile.update(ctx); + (*tile).get_mut()?.update(ctx); Some(()) } @@ -353,7 +368,7 @@ impl Pane { /// Returns an iterator over the tiles and their coordinates #[inline] - pub fn tiles<'b>(&'b self) -> impl Iterator + 'b { + pub fn tiles<'b>(&'b self) -> impl Iterator)> + 'b { self.tiles .iter() .enumerate()