From 02557ad6c0d175958099f7dbb48df276cb05c13b Mon Sep 17 00:00:00 2001 From: matejcik Date: Tue, 6 Feb 2024 10:57:12 +0100 Subject: [PATCH] feat(core/rust): expose attrtuple to Rust --- core/embed/rust/build.rs | 1 + core/embed/rust/src/micropython/macros.rs | 33 +++++++++++++++++++++++ core/embed/rust/src/micropython/util.rs | 33 +++++++++++++++++++++++ 3 files changed, 67 insertions(+) diff --git a/core/embed/rust/build.rs b/core/embed/rust/build.rs index e3ceccb9a..0fbc13a90 100644 --- a/core/embed/rust/build.rs +++ b/core/embed/rust/build.rs @@ -183,6 +183,7 @@ fn generate_micropython_bindings() { .allowlist_function("mp_obj_new_bytes") .allowlist_function("mp_obj_new_str") .allowlist_function("mp_obj_new_tuple") + .allowlist_function("mp_obj_new_attrtuple") .allowlist_function("mp_obj_get_int_maybe") .allowlist_function("mp_obj_is_true") .allowlist_function("mp_call_function_n_kw") diff --git a/core/embed/rust/src/micropython/macros.rs b/core/embed/rust/src/micropython/macros.rs index 9dcdb0e95..daab06d6f 100644 --- a/core/embed/rust/src/micropython/macros.rs +++ b/core/embed/rust/src/micropython/macros.rs @@ -228,6 +228,39 @@ macro_rules! obj_module { }); } +macro_rules! attr_tuple { + (@append + fields: [$($fields:expr,)*], + values: [$($values:expr,)*], + rest: { + $field:expr => $val:expr, + $($rest:tt)* + } + ) => { + attr_tuple! { + @append + fields: [$($fields,)* $field,], + values: [$($values,)* $val,], + rest: {$($rest)*} + } + }; + (@append + fields: [$($fields:expr,)*], + values: [$($values:expr,)*], + rest: {} + ) => { + $crate::micropython::util::new_attrtuple(&[$($fields,)*], &[$($values,)*]) + }; + // version without trailing comma + ($($key:expr => $val:expr),*) => ({ + attr_tuple!(@append fields: [], values: [], rest: { $($key => $val,)* }) + }); + // version with trailing comma + ($($key:expr => $val:expr,)*) => ({ + attr_tuple!(@append fields: [], values: [], rest: { $($key => $val,)* }) + }); +} + /// Print arbitrary amounts of slices into a terminal. /// Does not include a newline at the end. /// Does not do anything when not in debugging mode. diff --git a/core/embed/rust/src/micropython/util.rs b/core/embed/rust/src/micropython/util.rs index 168c34ca2..a8f337cfe 100644 --- a/core/embed/rust/src/micropython/util.rs +++ b/core/embed/rust/src/micropython/util.rs @@ -7,6 +7,7 @@ use super::{ iter::IterBuf, map::{Map, MapElem}, obj::Obj, + qstr::Qstr, runtime::{catch_exception, raise_exception}, }; use crate::error::Error; @@ -92,6 +93,38 @@ pub fn new_tuple(args: &[Obj]) -> Result { Ok(obj) } +/// Create a new "attrtuple", which is essentially a namedtuple / ad-hoc object. +/// +/// It is recommended to use the attr_tuple! macro instead of this function: +/// ``` +/// let obj = attr_tuple! { +/// Qstr::MP_QSTR_language => header.language.try_into()?, +/// Qstr::MP_QSTR_version => util::new_tuple(&version_objs)?, +/// // ... +/// } +/// ``` +pub fn new_attrtuple(field_qstrs: &'static [Qstr], values: &[Obj]) -> Result { + if field_qstrs.len() != values.len() { + return Err(Error::TypeError); + } + // SAFETY: + // * `values` are copied into the tuple, but the `fields` array is stored as a + // pointer in the last tuple item. Hence the requirement that `fields` is + // 'static. See objattrtuple.c:79 + // * we cast `field_qstrs` to the required type `qstr`, which is internally + // usize. (py/qstr.h:48). This is valid for as long as Qstr is + // repr(transparent) and the only field is a usize. Check generated qstr.rs. + // EXCEPTION: Raises if allocation fails, does not return NULL. + let obj = catch_exception(|| unsafe { + ffi::mp_obj_new_attrtuple( + field_qstrs.as_ptr() as *const _, + values.len(), + values.as_ptr(), + ) + })?; + Ok(obj) +} + pub fn iter_into_array(iterable: Obj) -> Result<[T; N], Error> where T: TryFrom,