util: power-of-2, blocking types Signed-off-by: Chris Frantz <cfrantz@google.com>
diff --git a/util/types/BUILD.bazel b/util/types/BUILD.bazel new file mode 100644 index 0000000..407a06a --- /dev/null +++ b/util/types/BUILD.bazel
@@ -0,0 +1,24 @@ +# Licensed under the Apache-2.0 license +# SPDX-License-Identifier: Apache-2.0 + +load("@rules_rust//rust:defs.bzl", "rust_library", "rust_test") + +rust_library( + name = "types", + srcs = [ + "lib.rs", + "opcode.rs", + "power_of_2.rs", + ], + crate_name = "util_types", + edition = "2024", + visibility = ["//visibility:public"], + deps = [ + "@rust_crates//:zerocopy", + ], +) + +rust_test( + name = "types_test", + crate = ":types", +)
diff --git a/util/types/README.md b/util/types/README.md new file mode 100644 index 0000000..c28d546 --- /dev/null +++ b/util/types/README.md
@@ -0,0 +1,40 @@ +# util_types + +A collection of common utility types. This crate is `#![no_std]` and suitable for embedded development. + +## Types + +### [`Blocking`](lib.rs) + +A trait for blocking on notifications. Typically implemented by mechanisms that need to wait for an event or notification (e.g., an interrupt). + +```rust +pub trait Blocking { + fn wait_for_notification(&self); +} +``` + +### [`Opcode`](opcode.rs) + +A 32-bit IPC opcode, typically represented as a 4-character ASCII string. It wraps a `u32` and implements `zerocopy` traits (`FromBytes`, `IntoBytes`, `Immutable`) for safe serialization/deserialization. + +```rust +pub struct Opcode(u32); + +impl Opcode { + pub const fn new(val: [u8; 4]) -> Self; +} +``` + +### [`PowerOf2Usize`](power_of_2.rs) + +A wrapper around `usize` that is guaranteed to be a power of two. This guarantee allows the compiler to optimize operations (e.g., replacing division with bitwise shifts). + +```rust +pub struct PowerOf2Usize(usize); + +impl PowerOf2Usize { + pub const fn new(val: usize) -> Option<Self>; + pub const fn get(self) -> usize; +} +```
diff --git a/util/types/lib.rs b/util/types/lib.rs new file mode 100644 index 0000000..1af4db9 --- /dev/null +++ b/util/types/lib.rs
@@ -0,0 +1,21 @@ +// Licensed under the Apache-2.0 license +// SPDX-License-Identifier: Apache-2.0 + +//! Common utility types. + +#![no_std] + +mod opcode; +mod power_of_2; + +pub use opcode::Opcode; +pub use power_of_2::PowerOf2Usize; + +/// A trait for blocking on notifications. +/// +/// This trait is typically implemented by mechanisms that need to wait for +/// an event or notification from another part of the system (e.g., an interrupt). +pub trait Blocking { + /// Waits until a notification is received. + fn wait_for_notification(&self); +}
diff --git a/util/types/opcode.rs b/util/types/opcode.rs new file mode 100644 index 0000000..b207bd5 --- /dev/null +++ b/util/types/opcode.rs
@@ -0,0 +1,25 @@ +// Licensed under the Apache-2.0 license +// SPDX-License-Identifier: Apache-2.0 + +//! IPC opcode definition. + +use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout}; + +/// A 32-bit IPC opcode. +/// +/// Opcodes are typically represented as 4-character ASCII strings. +#[derive(Clone, Copy, PartialEq, Eq, FromBytes, IntoBytes, Immutable, KnownLayout)] +pub struct Opcode(u32); + +impl Opcode { + /// Creates a new `Opcode` from a 4-byte array. + pub const fn new(val: [u8; 4]) -> Self { + Opcode(u32::from_le_bytes(val)) + } +} + +impl From<Opcode> for u32 { + fn from(op: Opcode) -> Self { + op.0 + } +}
diff --git a/util/types/power_of_2.rs b/util/types/power_of_2.rs new file mode 100644 index 0000000..dd41445 --- /dev/null +++ b/util/types/power_of_2.rs
@@ -0,0 +1,81 @@ +// Licensed under the Apache-2.0 license +// SPDX-License-Identifier: Apache-2.0 + +use core::hint::assert_unchecked; + +/// Represents a `usize` that is guaranteed to be a power-of-two. The compiler +/// can take advantage of this fact when optimizing (for example, using bitwise +/// arithmetic instead of division). +#[repr(transparent)] +#[derive(Clone, Copy, Debug, PartialEq, Eq, Ord, PartialOrd, Hash)] +pub struct PowerOf2Usize(usize); +impl PowerOf2Usize { + // WARNING: Do not add any functions or derives (such as + // zerocopy::FromBytes) that make it possible to modify this value without + // confirming that it is still a power-of-two. As the compiler is relying on + // the power-of-two assertion for safety, any such changes are unsound. + + /// Creates a new `PowerOf2Usize`. + /// + /// Returns `Some` if `val` is a power of two, `None` otherwise. + #[inline(always)] + pub const fn new(val: usize) -> Option<Self> { + if !val.is_power_of_two() { + return None; + } + Some(Self(val)) + } + + /// Returns the underlying `usize` value. + /// + /// This method asserts to the compiler that the value is a non-zero + /// power of two, which can enable certain optimizations. + #[inline(always)] + pub const fn get(self) -> usize { + // nosemgrep + unsafe { + // SAFETY: These assertions are safe because self.0 can only be set by + // Self::new, and we check for the same preconditions there. + // (LLVM is too stupid to realize that is_power_of_two() implies != 0) + assert_unchecked(self.0 != 0); + assert_unchecked(self.0.is_power_of_two()) + }; + self.0 + } +} + +#[cfg(test)] +mod tests { + use crate::PowerOf2Usize; + + #[test] + pub fn test() { + assert_eq!(PowerOf2Usize::new(0), None); + assert_eq!(PowerOf2Usize::new(1).unwrap().get(), 1); + assert_eq!(PowerOf2Usize::new(2).unwrap().get(), 2); + assert_eq!(PowerOf2Usize::new(3), None); + assert_eq!(PowerOf2Usize::new(4).unwrap().get(), 4); + assert_eq!(PowerOf2Usize::new(5), None); + assert_eq!(PowerOf2Usize::new(6), None); + assert_eq!(PowerOf2Usize::new(7), None); + assert_eq!(PowerOf2Usize::new(8).unwrap().get(), 8); + assert_eq!(PowerOf2Usize::new(9), None); + assert_eq!(PowerOf2Usize::new(0x7fff_ffff), None); + assert_eq!(PowerOf2Usize::new(0x8000_0000).unwrap().get(), 0x8000_0000); + assert_eq!(PowerOf2Usize::new(0x8000_0001), None); + assert_eq!(PowerOf2Usize::new(0xc000_0000), None); + assert_eq!(PowerOf2Usize::new(0xffff_ffff), None); + + #[cfg(target_pointer_width = "64")] + { + assert_eq!(PowerOf2Usize::new(0x7fff_ffff_ffff_ffff), None); + assert_eq!( + PowerOf2Usize::new(0x8000_0000_0000_0000).unwrap().get(), + 0x8000_0000_0000_0000 + ); + assert_eq!(PowerOf2Usize::new(0x8000_0000_0000_0001), None); + assert_eq!(PowerOf2Usize::new(0xc000_0000_0000_0000), None); + assert_eq!(PowerOf2Usize::new(0xffff_ffff_ffff_ffff), None); + } + } +}