SpinLock

Struct SpinLock 

Source
pub struct SpinLock<T, G> { /* private fields */ }
Expand description

A spin lock.

§Guard behavior

The type `G’ specifies the guard behavior of the spin lock. While holding the lock,

  • if G is [PreemptDisabled], preemption is disabled;
  • if G is LocalIrqDisabled, local IRQs are disabled.

The G can also be provided by other crates other than ostd, if it behaves similar like [PreemptDisabled] or LocalIrqDisabled.

The guard behavior can be temporarily upgraded from [PreemptDisabled] to LocalIrqDisabled using the disable_irq method.

§Verified Properties

§Verification Design

To verify the correctness of spin lock, we use a ghost permission (i.e., not present in executable Rust). Only the owner of this permission can access the protected data in the cell. When lock or try_lock succeeds, the ghost permission is transferred to the lock guard and given to the user for accessing the protected data. When the lock guard is dropped, the ghost permission is transferred back to the spin lock.

§Invariant

The SpinLock is internally represented by a struct SpinLockInner that contains an AtomicBool and a PCell to hold the protected data. We present its formally verified version and invariant below.

The lock field is extended with a PointsTo<T> ghost permission to track the ownership of the protected data. This ghost permission is also checked by Rust’s ownership and borrowing rules and cannot be duplicated, thereby ensuring exclusive access to the protected data. The val field is a PCell<T>, which behaves like UnsafeCell<T> used in the Asterinas mainline, but only allows verified access through the ghost permission.

When the internal AtomicBool is true, the permission has been transferred to a SpinLockGuard and no one else can acquire the lock. When it is false, the permission to access the PCell<T> is stored in the lock, and it must match the val’s id.

struct_with_invariants! {
struct SpinLockInner<T> {
   lock: AtomicBool<_,Option<PointsTo<T>>,_>,
   val: PCell<T>,
}

closed spec fn wf(self) -> bool {
   invariant on lock with (val) is (v:bool, g:Option<PointsTo<T>>) {
       match g {
           None => v == true,
           Some(perm) => perm.id() == val.id() && !v
       }
   }
}
}

Note: The invariant is encapsulated in type_inv using the #[verifier::type_invariant] mechanism. It internally holds at all steps during the method executions and is NOT exposed in the public APIs’ pre- and post-conditions.

§Safety

There are no data races.

§Functional Correctness

  • At most one user can hold the lock at the same time.

Implementations§

Source§

impl<T, G> SpinLock<T, G>

Source

pub const exec fn new(val: T) -> Self

Creates a new spin lock.

§Verified Properties
§Safety

This function is written in safe Rust and there is no undefined behavior.

§Preconditions

None.

§Postconditions
  • The function will not panic.
  • The created spin lock satisfies the invariant.
Source§

impl<T, G> SpinLock<T, G>

Source

pub closed spec fn cell_id(self) -> CellId

Returns the unique CellId of the internal PCell<T>.

Source

pub closed spec fn type_inv(self) -> bool

Encapsulates the invariant described in the Invariant section of SpinLock.

Source§

impl<T, G: SpinGuardian> SpinLock<T, G>

Source

pub exec fn lock(&self) -> SpinLockGuard<'_, T, G>

Acquires the spin lock.

§Verified Properties
§Safety

There are no data races. The lock ensures exclusive access to the protected data.

§Preconditions

None. (The invariant of SpinLock always holds internally.)

§Postconditions

The returned SpinLockGuard satisfies its type invariant:

  • An exclusive permission to access the protected data is held by the guard.
  • The guard’s permission matches the lock’s internal cell ID.
§Key Verification Step

When the internal atomic compare-and-exchange operation in acquire_lock succeeds, the ghost permission is simultaneously extracted from the lock.

atomic_with_ghost!  {
   self.inner.lock => compare_exchange(false, true);
   returning res;
   ghost cell_perm => {
    // Extract the ghost permission when the lock is successfully acquired
    if res is Ok {
           perm = Some(cell_perm.tracked_take());
       }
   }
}.is_ok()
Source

pub exec fn try_lock(&self) -> Option<SpinLockGuard<'_, T, G>>

Tries acquiring the spin lock immediately.

§Verified Properties
§Safety

There are no data races. The lock ensures exclusive access to the protected data.

§Preconditions

None. (The invariant of SpinLock always holds internally.)

§Postconditions

If Some(guard) is returned, it satisfies its type invariant:

  • An exclusive permission to access the protected data is held by the guard.
  • The guard’s permission matches the lock’s internal cell ID.
Source

pub exec fn spec_with_example(&self) -> ret : u32

with
ghost_in1: Tracked<int>,
ghost_in2: Tracked<int>,
->
ghost_out: Ghost<(int, int)>,
requires
ghost_in1@ >= 0,
ensures
ghost_out@.0 == ghost_in1@,
ret == 0,

Note: This is NOT an OSTD API. It is only an example to show the doc generation output with the #[verus_spec] macro. It will be removed after our PR about #[verus_spec] doc generation is merged into Verus mainline.

Trait Implementations§

Source§

impl<T: Send, G> Send for SpinLock<T, G>

Source§

impl<T: Send, G> Sync for SpinLock<T, G>

Auto Trait Implementations§

§

impl<T, G> !Freeze for SpinLock<T, G>

§

impl<T, G> !RefUnwindSafe for SpinLock<T, G>

§

impl<T, G> Unpin for SpinLock<T, G>
where G: Unpin, T: Unpin,

§

impl<T, G> UnwindSafe for SpinLock<T, G>

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

§

impl<T, VERUS_SPEC__A> FromSpec<T> for VERUS_SPEC__A
where VERUS_SPEC__A: From<T>,

§

fn obeys_from_spec() -> bool

§

fn from_spec(v: T) -> VERUS_SPEC__A

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

§

impl<T, VERUS_SPEC__A> IntoSpec<T> for VERUS_SPEC__A
where VERUS_SPEC__A: Into<T>,

§

fn obeys_into_spec() -> bool

§

fn into_spec(self) -> T

§

impl<T, U> IntoSpecImpl<U> for T
where U: From<T>,

§

fn obeys_into_spec() -> bool

§

fn into_spec(self) -> U

Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
§

impl<T, VERUS_SPEC__A> TryFromSpec<T> for VERUS_SPEC__A
where VERUS_SPEC__A: TryFrom<T>,

§

fn obeys_try_from_spec() -> bool

§

fn try_from_spec( v: T, ) -> Result<VERUS_SPEC__A, <VERUS_SPEC__A as TryFrom<T>>::Error>

Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<T, VERUS_SPEC__A> TryIntoSpec<T> for VERUS_SPEC__A
where VERUS_SPEC__A: TryInto<T>,

§

fn obeys_try_into_spec() -> bool

§

fn try_into_spec(self) -> Result<T, <VERUS_SPEC__A as TryInto<T>>::Error>

§

impl<T, U> TryIntoSpecImpl<U> for T
where U: TryFrom<T>,

§

fn obeys_try_into_spec() -> bool

§

fn try_into_spec(self) -> Result<U, <U as TryFrom<T>>::Error>