You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
trezor-firmware/core/embed/rust/src/micropython/map.rs

212 lines
6.9 KiB

use core::{marker::PhantomData, mem::MaybeUninit, ops::Deref, ptr, slice};
use crate::{
error::Error,
micropython::{obj::Obj, qstr::Qstr},
};
use super::{ffi, runtime::catch_exception};
pub type Map = ffi::mp_map_t;
pub type MapElem = ffi::mp_map_elem_t;
impl Map {
pub const fn from_fixed_static<const N: usize>(table: &'static [MapElem; N]) -> Self {
// We can't use the regular accessors generated by bindgen for us, as they are
// not `const`. Instead, we build the bitfield manually.
// micropython/py/obj.h MP_DEFINE_CONST_DICT
// .all_keys_are_qstrs = 1,
// .is_fixed = 1,
// .is_ordered = 1,
// .used = MP_ARRAY_SIZE(table_name),
// .alloc = MP_ARRAY_SIZE(table_name),
let bits = 0b111 | N << 3;
let bitfield = ffi::__BindgenBitfieldUnit::new(bits.to_ne_bytes());
Self {
_bitfield_align_1: [],
_bitfield_1: bitfield,
alloc: N,
table: table.as_ptr() as *mut MapElem,
}
}
pub const fn at(key: Qstr, value: Obj) -> MapElem {
MapElem {
key: key.to_obj(),
value,
}
}
pub const EMPTY: Map = Self::from_fixed_static::<0>(&[]);
}
impl Map {
pub fn from_fixed(table: &[MapElem]) -> MapRef {
let mut map = MaybeUninit::uninit();
// SAFETY: `mp_map_init_fixed_table` completely initializes all fields of `map`.
unsafe {
// EXCEPTION: Does not raise.
ffi::mp_map_init_fixed_table(map.as_mut_ptr(), table.len(), table.as_ptr().cast());
MapRef::new(map.assume_init())
}
}
pub fn with_capacity(capacity: usize) -> Result<Self, Error> {
let mut map = MaybeUninit::uninit();
// SAFETY: `mp_map_init` completely initializes all fields of `map`, allocating
// the backing storage for `capacity` items on the heap.
unsafe {
// EXCEPTION: Will raise if allocation fails.
catch_exception(|| {
ffi::mp_map_init(map.as_mut_ptr(), capacity);
})?;
Ok(map.assume_init())
}
}
pub fn len(&self) -> usize {
self.used()
}
pub fn elems(&self) -> &[MapElem] {
// SAFETY: `self.table` should always point to an array of `MapElem` of
// `self.len()` items valid at least for the lifetime of `self`.
unsafe { slice::from_raw_parts(self.table, self.len()) }
}
pub fn contains_key(&self, index: impl Into<Obj>) -> bool {
self.get_obj(index.into()).is_ok()
}
pub fn get(&self, index: impl Into<Obj>) -> Result<Obj, Error> {
self.get_obj(index.into())
}
pub fn get_obj(&self, index: Obj) -> Result<Obj, Error> {
// SAFETY:
// - `mp_map_lookup` returns either NULL or a pointer to a `mp_map_elem_t`
// value with a lifetime valid for the whole lifetime of the passed immutable
// ref of `map`.
// - with `_mp_map_lookup_kind_t_MP_MAP_LOOKUP`, `map` stays unmodified and the
// cast to mut ptr is therefore safe.
unsafe {
let map = self as *const Self as *mut Self;
// EXCEPTION: Does not raise for MP_MAP_LOOKUP.
let elem = ffi::mp_map_lookup(map, index, ffi::_mp_map_lookup_kind_t_MP_MAP_LOOKUP)
.as_ref()
.ok_or(Error::KeyError(index))?;
Ok(elem.value)
}
}
pub fn get_or<T>(&self, index: impl Into<Obj>, default: T) -> Result<T, Error>
where
T: TryFrom<Obj, Error = Error>,
{
let res = self.get(index);
match res {
Ok(obj) => obj.try_into(),
Err(Error::KeyError(_)) => Ok(default),
Err(e) => Err(e),
}
}
pub fn set(&mut self, index: impl Into<Obj>, value: impl Into<Obj>) -> Result<(), Error> {
self.set_obj(index.into(), value.into())
}
pub fn set_obj(&mut self, index: Obj, value: Obj) -> Result<(), Error> {
// SAFETY:
// - `mp_map_lookup` with `_mp_map_lookup_kind_t_MP_MAP_LOOKUP_ADD_IF_NOT_FOUND
// returns a pointer to a `mp_map_elem_t` value with a lifetime valid for the
// whole lifetime of `&mut self`.
// - adding an element is an allocation, so it might raise.
// - the original `elem.value` might be an `Obj` that will get GCd when we
// replace it.
unsafe {
let map = self as *mut Self;
// EXCEPTION: Will raise if allocation fails.
let elem = catch_exception(|| {
ffi::mp_map_lookup(
map,
index,
ffi::_mp_map_lookup_kind_t_MP_MAP_LOOKUP_ADD_IF_NOT_FOUND,
)
})?
.as_mut()
.unwrap(); // `MP_MAP_LOOKUP_ADD_IF_NOT_FOUND` should always return a non-null pointer.
elem.value = value;
}
Ok(())
}
pub fn delete(&mut self, index: impl Into<Obj>) {
self.delete_obj(index.into())
}
pub fn delete_obj(&mut self, index: Obj) {
unsafe {
let map = self as *mut Self;
// EXCEPTION: Does not raise for MP_MAP_LOOKUP_REMOVE_IF_FOUND.
ffi::mp_map_lookup(
map,
index,
ffi::_mp_map_lookup_kind_t_MP_MAP_LOOKUP_REMOVE_IF_FOUND,
);
}
}
}
impl Map {
pub fn try_clone(&self) -> Result<Self, Error> {
let mut map = Self::with_capacity(self.len())?;
unsafe {
ptr::copy_nonoverlapping(self.table, map.table, self.len());
}
map.set_used(self.used());
map.set_all_keys_are_qstrs(self.all_keys_are_qstrs());
map.set_is_ordered(self.is_ordered());
map.set_is_fixed(0);
Ok(map)
}
}
// SAFETY: We are in a single-threaded environment.
unsafe impl Sync for Map {}
unsafe impl Sync for MapElem {}
/// Wrapper over a private `Map` value with element lifetime `'a`.
///
/// `Map` itself does not have any lifetime parameter, so it can effectively
/// only reference element tables with `'static` lifetime. `MapRef` is tying a
/// private `Map` value to a lifetime `'a`, exposing it only by-ref within the
/// `'a` lifetime. It's not possible to move the value out from the reference,
/// and because `Map` is not `Clone`, it is also impossible to clone it. All
/// this only holds for immutable ref, therefore we only impl `Deref`, not
/// `DerefMut`.
//
// TODO: Better solution would be to make Map a wrapper type instead of a type
// alias and add a lifetime parameter.
pub struct MapRef<'a> {
map: Map,
_phantom: PhantomData<&'a Map>,
}
impl<'a> MapRef<'a> {
fn new(map: Map) -> Self {
Self {
map,
_phantom: PhantomData,
}
}
}
impl<'a> Deref for MapRef<'a> {
type Target = Map;
fn deref(&self) -> &Self::Target {
&self.map
}
}