feat(core): Add Rust bindings to MicroPython and trezorhal

core: Remove dangling module decls

core: Use new Cargo feature resolver, use external MacOS debug info

core: Rust docs improvements

core: Upgrade bindgen

core: Add test target to Rust

ci: build rust sources

build(core): .ARM.exidx.text.__aeabi_ui2f in t1 firmware size

It's an unwind table for softfloat function inserted by rustc, probably
can be removed to save 8 bytes:
599c58db70/link.x.in (L175-L182)

scons: Remove dead code

core: Move Rust target to build/rust

core: Replace extern with a FFI version

core: Add some explanatory Rust comments

core: Use correct path for the Rust lib

core: Remove Buffer::as_mut()

Mutable buffer access needs MP_BUFFER_WRITE flag. TBD in the Protobuf PR.

core: Improve docs for micropython::Buffer

core: Minor Rust docs changes

core: Rewrite trezor_obj_get_ll_checked

core: Fix incorrect doc comment

core: Remove cc from deps

fixup! core: Rewrite trezor_obj_get_ll_checked

core: update safety comments
pull/1610/head
Jan Pochyla 3 years ago committed by matejcik
parent 77d00206ba
commit 6257584951

@ -2,25 +2,35 @@
, hardwareTest ? false
}:
# the last successful build of nixpkgs-unstable as of 2021-03-25
with import (builtins.fetchTarball {
url = "https://github.com/NixOS/nixpkgs/archive/c0e881852006b132236cbf0301bd1939bb50867e.tar.gz";
sha256 = "0fy7z7yxk5n7yslsvx5cyc6h21qwi4bhxf3awhirniszlbvaazy2";
})
{ };
let
moneroTests = fetchurl {
mozillaOverlay = import (builtins.fetchTarball {
url = "https://github.com/mozilla/nixpkgs-mozilla/archive/8c007b60731c07dd7a052cce508de3bb1ae849b4.tar.gz";
sha256 = "1zybp62zz0h077zm2zmqs2wcg3whg6jqaah9hcl1gv4x8af4zhs6";
});
# the last successful build of nixpkgs-unstable as of 2021-03-25
nixpkgs = import (builtins.fetchTarball {
url = "https://github.com/NixOS/nixpkgs/archive/c0e881852006b132236cbf0301bd1939bb50867e.tar.gz";
sha256 = "0fy7z7yxk5n7yslsvx5cyc6h21qwi4bhxf3awhirniszlbvaazy2";
}) { overlays = [ mozillaOverlay ]; };
moneroTests = nixpkgs.fetchurl {
url = "https://github.com/ph4r05/monero/releases/download/v0.17.1.9-tests/trezor_tests";
sha256 = "410bc4ff2ff1edc65e17f15b549bd1bf8a3776cf67abdea86aed52cf4bce8d9d";
};
moneroTestsPatched = runCommandCC "monero_trezor_tests" {} ''
moneroTestsPatched = nixpkgs.runCommandCC "monero_trezor_tests" {} ''
cp ${moneroTests} $out
chmod +wx $out
${patchelf}/bin/patchelf --set-interpreter "$(cat $NIX_CC/nix-support/dynamic-linker)" "$out"
${nixpkgs.patchelf}/bin/patchelf --set-interpreter "$(cat $NIX_CC/nix-support/dynamic-linker)" "$out"
chmod -w $out
'';
rustNightly = (nixpkgs.rustChannelOf { date = "2021-03-29"; channel = "nightly"; }).rust.override {
targets = [
"x86_64-unknown-linux-gnu" # emulator
"thumbv7em-none-eabihf" # TT
"thumbv7em-none-eabi" # T1
];
};
in
with nixpkgs;
stdenv.mkDerivation ({
name = "trezor-firmware-env";
buildInputs = lib.optionals fullDeps [
@ -38,6 +48,7 @@ stdenv.mkDerivation ({
bash
check
clang-tools
clang
editorconfig-checker
gcc
gcc-arm-embedded
@ -52,6 +63,8 @@ stdenv.mkDerivation ({
pkgconfig
poetry
protobuf3_6
rustfmt
rustNightly
wget
zlib
] ++ lib.optionals (!stdenv.isDarwin) [
@ -80,6 +93,8 @@ stdenv.mkDerivation ({
# Fix bdist-wheel problem by setting source date epoch to a more recent date
SOURCE_DATE_EPOCH = 1600000000;
# Used by rust bindgen
LIBCLANG_PATH = "${llvmPackages.libclang}/lib";
} // (lib.optionalAttrs fullDeps) {
TREZOR_MONERO_TESTS_PATH = moneroTestsPatched;
})

@ -0,0 +1 @@
Rust FFI for MicroPython.

@ -12,6 +12,7 @@ PRODTEST_BUILD_DIR = $(BUILD_DIR)/prodtest
REFLASH_BUILD_DIR = $(BUILD_DIR)/reflash
FIRMWARE_BUILD_DIR = $(BUILD_DIR)/firmware
UNIX_BUILD_DIR = $(BUILD_DIR)/unix
RUST_BUILD_DIR = $(BUILD_DIR)/rust
UNAME_S := $(shell uname -s)
UNIX_PORT_OPTS ?=
@ -179,10 +180,10 @@ clean_reflash: ## clean reflash build
rm -rf $(REFLASH_BUILD_DIR)
clean_firmware: ## clean firmware build
rm -rf $(FIRMWARE_BUILD_DIR)
rm -rf $(FIRMWARE_BUILD_DIR) $(RUST_BUILD_DIR)
clean_unix: ## clean unix build
rm -rf $(UNIX_BUILD_DIR)
rm -rf $(UNIX_BUILD_DIR) $(RUST_BUILD_DIR)
clean_cross: ## clean mpy-cross build
$(MAKE) -C vendor/micropython/mpy-cross clean $(CROSS_PORT_OPTS)

@ -1,4 +1,5 @@
# pylint: disable=E0602
# fmt: off
import os
@ -49,6 +50,7 @@ CPPDEFINES_MOD += [
('USE_EOS', '1' if EVERYTHING else '0'),
]
SOURCE_MOD += [
'embed/extmod/trezorobj.c',
'embed/extmod/modtrezorcrypto/crc.c',
'embed/extmod/modtrezorcrypto/modtrezorcrypto.c',
'embed/extmod/modtrezorcrypto/rand.c',
@ -394,11 +396,13 @@ if TREZOR_MODEL == 'T':
CPU_CCFLAGS = '-mthumb -mcpu=cortex-m4 -mfloat-abi=hard -mfpu=fpv4-sp-d16 -mtune=cortex-m4 '
CPU_MODEL = 'STM32F427xx'
LD_VARIANT = '_zkp' if FEATURE_FLAGS["SECP256K1_ZKP"] else ''
RUST_TARGET = 'thumbv7em-none-eabihf'
elif TREZOR_MODEL == '1':
CPU_ASFLAGS = '-mthumb -mcpu=cortex-m3 -mfloat-abi=soft'
CPU_CCFLAGS = '-mthumb -mtune=cortex-m3 -mcpu=cortex-m3 -mfloat-abi=soft '
CPU_MODEL = 'STM32F405xx'
LD_VARIANT = '' if EVERYTHING else '_min'
RUST_TARGET = 'thumbv7em-none-eabi'
else:
raise ValueError('Unknown Trezor model')
@ -416,6 +420,7 @@ env.Replace(
LINKFLAGS='-T embed/firmware/memory_${TREZOR_MODEL}%s.ld -Wl,--gc-sections -Wl,-Map=build/firmware/firmware.map -Wl,--warn-common' % LD_VARIANT,
CPPPATH=[
'.',
'embed/rust',
'embed/firmware',
'embed/trezorhal',
'embed/extmod/modtrezorui',
@ -616,6 +621,33 @@ if FEATURE_FLAGS["SECP256K1_ZKP"]:
action='cd ${SOURCE.dir}; ./gen_context',
)
#
# Rust library
#
RUST_PROFILE = 'release'
RUST_LIB = 'trezor_lib'
RUST_LIBDIR = f'build/rust/{RUST_TARGET}/{RUST_PROFILE}'
RUST_LIBPATH = f'{RUST_LIBDIR}/lib{RUST_LIB}.a'
def cargo_build():
# Determine the profile build flags.
if RUST_PROFILE == 'release':
profile = '--release'
else:
profile = ''
return f'cd embed/rust; cargo build {profile} --target={RUST_TARGET}'
rust = env.Command(
target=RUST_LIBPATH,
source='',
action=cargo_build(), )
env.Depends(rust, qstr_generated)
env.Append(LINKFLAGS=f' -L{RUST_LIBDIR}')
env.Append(LINKFLAGS=f' -l{RUST_LIB}')
#
# Program objects
#
@ -658,9 +690,11 @@ program_elf = env.Command(
target='firmware.elf',
source=obj_program,
action=
'$LINK -o $TARGET $CCFLAGS $CFLAGS $LINKFLAGS $SOURCES -lc_nano -lm -lgcc',
'$LINK -o $TARGET $CCFLAGS $CFLAGS $SOURCES $LINKFLAGS -lc_nano -lm -lgcc',
)
env.Depends(program_elf, rust)
if TREZOR_MODEL == 'T':
action_bin=[
'$OBJCOPY -O binary -j .vendorheader -j .header -j .flash -j .data --pad-to 0x08100000 $SOURCE ${TARGET}.p1',

@ -1,4 +1,5 @@
# pylint: disable=E0602
# fmt: off
import os
@ -48,6 +49,7 @@ CPPDEFINES_MOD += [
('USE_EOS', '1' if EVERYTHING else '0'),
]
SOURCE_MOD += [
'embed/extmod/trezorobj.c',
'embed/extmod/modtrezorcrypto/modtrezorcrypto.c',
'embed/extmod/modtrezorcrypto/crc.c',
'vendor/trezor-crypto/address.c',
@ -367,6 +369,7 @@ env.Replace(
LIBS=['m'],
CPPPATH=[
'.',
'embed/rust',
'embed/unix',
'embed/extmod/modtrezorui',
'vendor/micropython',
@ -567,6 +570,33 @@ if FEATURE_FLAGS["SECP256K1_ZKP"]:
action='cd ${SOURCE.dir}; ./gen_context',
)
#
# Rust library
#
RUST_PROFILE = 'release'
RUST_LIB = 'trezor_lib'
RUST_LIBDIR = f'build/rust/{RUST_PROFILE}'
RUST_LIBPATH = f'{RUST_LIBDIR}/lib{RUST_LIB}.a'
def cargo_build():
# Determine the profile build flags.
if RUST_PROFILE == 'release':
profile = '--release'
else:
profile = ''
return f'cd embed/rust; cargo build {profile}'
rust = env.Command(
target=RUST_LIBPATH,
source='',
action=cargo_build(), )
env.Depends(rust, qstr_generated)
env.Append(LINKFLAGS=f'-L{RUST_LIBDIR}')
env.Append(LINKFLAGS=f'-l{RUST_LIB}')
#
# Program objects
#
@ -588,3 +618,5 @@ program = env.Command(
target='trezor-emu-core',
source=obj_program,
action='$CC -o $TARGET $SOURCES $_LIBDIRFLAGS $_LIBFLAGS $LINKFLAGS', )
env.Depends(program, rust)

@ -0,0 +1,64 @@
/*
* This file is part of the Trezor project, https://trezor.io/
*
* Copyright (c) SatoshiLabs
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <string.h>
#include "memzero.h"
#include "py/objint.h"
static bool mpz_as_ll_checked(const mpz_t *i, long long *value) {
// Analogue of `mpz_as_int_checked` from mpz.c
unsigned long long val = 0;
mpz_dig_t *d = i->dig + i->len;
while (d-- > i->dig) {
if (val > (~0x8000000000000000 >> MPZ_DIG_SIZE)) {
// will overflow
*value = 0;
return false;
}
val = (val << MPZ_DIG_SIZE) | *d;
}
if (i->neg != 0) {
val = -val;
}
*value = val;
return true;
}
bool trezor_obj_get_ll_checked(mp_obj_t obj, long long *value) {
if (mp_obj_is_small_int(obj)) {
// Value is fitting in a small int range. Return it directly.
*value = MP_OBJ_SMALL_INT_VALUE(obj);
return true;
} else if (mp_obj_is_type(obj, &mp_type_int)) {
// Value is not fitting into small int range, but is an integer.
mp_obj_int_t *self = MP_OBJ_TO_PTR(obj);
// Try to get the long long value out of the MPZ struct.
return mpz_as_ll_checked(&self->mpz, value);
} else {
// Value is not integer.
*value = 0;
return false;
}
}

@ -75,4 +75,6 @@ static inline uint8_t trezor_obj_get_uint8(mp_obj_t obj) {
return u;
}
bool trezor_obj_get_ll_checked(mp_obj_t obj, long long *value);
#endif

@ -26,7 +26,8 @@ sram_end = ORIGIN(SRAM) + LENGTH(SRAM);
_ram_start = sram_start;
_ram_end = sram_end;
_codelen = SIZEOF(.flash) + SIZEOF(.data) + SIZEOF(.ARM.exidx);
/* .ARM.exidx.text.__aeabi_ui2f is probably not needed as long as we are using panic = "abort" */
_codelen = SIZEOF(.flash) + SIZEOF(.data) + SIZEOF(.ARM.exidx) + SIZEOF(.ARM.exidx.text.__aeabi_ui2f);
_flash_start = ORIGIN(FLASH);
_flash_end = ORIGIN(FLASH) + LENGTH(FLASH);
_heap_start = ADDR(.heap);

@ -26,7 +26,8 @@ sram_end = ORIGIN(SRAM) + LENGTH(SRAM);
_ram_start = sram_start;
_ram_end = sram_end;
_codelen = SIZEOF(.flash) + SIZEOF(.data);
/* .ARM.exidx.text.__aeabi_ui2f is probably not needed as long as we are using panic = "abort" */
_codelen = SIZEOF(.flash) + SIZEOF(.data) + SIZEOF(.ARM.exidx.text.__aeabi_ui2f);
_flash_start = ORIGIN(FLASH);
_flash_end = ORIGIN(FLASH) + LENGTH(FLASH);
_heap_start = ADDR(.heap);

@ -0,0 +1,2 @@
[build]
target-dir = "../../build/rust"

@ -0,0 +1,385 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "aho-corasick"
version = "0.7.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7404febffaa47dac81aa44dba71523c9d069b1bdc50a77db41195149e17f68e5"
dependencies = [
"memchr",
]
[[package]]
name = "ansi_term"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b"
dependencies = [
"winapi",
]
[[package]]
name = "atty"
version = "0.2.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
dependencies = [
"hermit-abi",
"libc",
"winapi",
]
[[package]]
name = "bindgen"
version = "0.58.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d5d2549a1ca679efa833b9d88a424955109722c9fd95418b95414c19fd6f4bda"
dependencies = [
"bitflags",
"cexpr",
"clang-sys",
"clap",
"env_logger",
"lazy_static",
"lazycell",
"log",
"peeking_take_while",
"proc-macro2",
"quote",
"regex",
"rustc-hash",
"shlex",
"which",
]
[[package]]
name = "bitflags"
version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
[[package]]
name = "cc"
version = "1.0.67"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e3c69b077ad434294d3ce9f1f6143a2a4b89a8a2d54ef813d85003a4fd1137fd"
[[package]]
name = "cexpr"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f4aedb84272dbe89af497cf81375129abda4fc0a9e7c5d317498c15cc30c0d27"
dependencies = [
"nom",
]
[[package]]
name = "cfg-if"
version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822"
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "clang-sys"
version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0659001ab56b791be01d4b729c44376edc6718cf389a502e579b77b758f3296c"
dependencies = [
"glob",
"libc",
"libloading",
]
[[package]]
name = "clap"
version = "2.33.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "37e58ac78573c40708d45522f0d80fa2f01cc4f9b4e2bf749807255454312002"
dependencies = [
"ansi_term",
"atty",
"bitflags",
"strsim",
"textwrap",
"unicode-width",
"vec_map",
]
[[package]]
name = "cstr_core"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "26bdf52fc09b421407bd990b4662a380420b6b6f61708bcba417731dca1b65ef"
dependencies = [
"cty",
"memchr",
]
[[package]]
name = "cty"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7313c0d620d0cb4dbd9d019e461a4beb501071ff46ec0ab933efb4daa76d73e3"
[[package]]
name = "env_logger"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f26ecb66b4bdca6c1409b40fb255eefc2bd4f6d135dab3c3124f80ffa2a9661e"
dependencies = [
"atty",
"humantime",
"log",
"regex",
"termcolor",
]
[[package]]
name = "glob"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574"
[[package]]
name = "hermit-abi"
version = "0.1.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "322f4de77956e22ed0e5032c359a0f1273f1f7f0d79bfa3b8ffbc730d7fbcc5c"
dependencies = [
"libc",
]
[[package]]
name = "humantime"
version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4"
[[package]]
name = "lazy_static"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]]
name = "lazycell"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55"
[[package]]
name = "libc"
version = "0.2.82"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "89203f3fba0a3795506acaad8ebce3c80c0af93f994d5a1d7a0b1eeb23271929"
[[package]]
name = "libloading"
version = "0.6.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "351a32417a12d5f7e82c368a66781e307834dae04c6ce0cd4456d52989229883"
dependencies = [
"cfg-if 1.0.0",
"winapi",
]
[[package]]
name = "log"
version = "0.4.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fcf3805d4480bb5b86070dcfeb9e2cb2ebc148adb753c5cca5f884d1d65a42b2"
dependencies = [
"cfg-if 0.1.10",
]
[[package]]
name = "memchr"
version = "2.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0ee1c47aaa256ecabcaea351eae4a9b01ef39ed810004e298d2511ed284b1525"
[[package]]
name = "nom"
version = "5.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ffb4262d26ed83a1c0a33a38fe2bb15797329c85770da05e6b828ddb782627af"
dependencies = [
"memchr",
"version_check",
]
[[package]]
name = "peeking_take_while"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099"
[[package]]
name = "proc-macro2"
version = "1.0.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e0704ee1a7e00d7bb417d0770ea303c1bccbabf0ef1667dae92b5967f5f8a71"
dependencies = [
"unicode-xid",
]
[[package]]
name = "quote"
version = "1.0.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "991431c3519a3f36861882da93630ce66b52918dcf1b8e2fd66b397fc96f28df"
dependencies = [
"proc-macro2",
]
[[package]]
name = "regex"
version = "1.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9251239e129e16308e70d853559389de218ac275b515068abc96829d05b948a"
dependencies = [
"aho-corasick",
"memchr",
"regex-syntax",
"thread_local",
]
[[package]]
name = "regex-syntax"
version = "0.6.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b5eb417147ba9860a96cfe72a0b93bf88fee1744b5636ec99ab20c1aa9376581"
[[package]]
name = "rustc-hash"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
[[package]]
name = "shlex"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42a568c8f2cd051a4d283bd6eb0343ac214c1b0f1ac19f93e1175b2dee38c73d"
[[package]]
name = "staticvec"
version = "0.10.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3c87f4be0fd89694157f3814ca88715ad8ba6010c453b1e89ca264aee04d70b9"
[[package]]
name = "strsim"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a"
[[package]]
name = "termcolor"
version = "1.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2dfed899f0eb03f32ee8c6a0aabdb8a7949659e3466561fc0adf54e26d88c5f4"
dependencies = [
"winapi-util",
]
[[package]]
name = "textwrap"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060"
dependencies = [
"unicode-width",
]
[[package]]
name = "thread_local"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bb9bc092d0d51e76b2b19d9d85534ffc9ec2db959a2523cdae0697e2972cd447"
dependencies = [
"lazy_static",
]
[[package]]
name = "trezor_lib"
version = "0.1.0"
dependencies = [
"bindgen",
"cc",
"cstr_core",
"cty",
"staticvec",
]
[[package]]
name = "unicode-width"
version = "0.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9337591893a19b88d8d87f2cec1e73fad5cdfd10e5a6f349f498ad6ea2ffb1e3"
[[package]]
name = "unicode-xid"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564"
[[package]]
name = "vec_map"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191"
[[package]]
name = "version_check"
version = "0.9.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b5a972e5669d67ba988ce3dc826706fb0a8b01471c088cb0b6110b805cc36aed"
[[package]]
name = "which"
version = "3.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d011071ae14a2f6671d0b74080ae0cd8ebf3a6f8c9589a2cd45f23126fe29724"
dependencies = [
"libc",
]
[[package]]
name = "winapi"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
dependencies = [
"winapi-i686-pc-windows-gnu",
"winapi-x86_64-pc-windows-gnu",
]
[[package]]
name = "winapi-i686-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
[[package]]
name = "winapi-util"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178"
dependencies = [
"winapi",
]
[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"

@ -0,0 +1,34 @@
[package]
name = "trezor_lib"
version = "0.1.0"
authors = ["SatoshiLabs <info@satoshilabs.com>"]
edition = "2018"
resolver = "2"
build = "build.rs"
[lib]
crate-type = ["staticlib"]
[profile.dev]
panic = "abort"
split-debuginfo = "unpacked"
[profile.release]
panic = "abort"
opt-level = "z"
lto = true
codegen-units = 1
[dependencies]
cty = "0.2.1"
[dependencies.staticvec]
version = "0.10.5"
default_features = false
[dependencies.cstr_core]
version = "0.2.2"
default_features = false
[build-dependencies]
bindgen = "0.58.0"

@ -0,0 +1,166 @@
use std::{env, path::PathBuf, process::Command};
fn main() {
generate_qstr_bindings();
generate_micropython_bindings();
}
/// Generates Rust module that exports QSTR constants used in firmware.
fn generate_qstr_bindings() {
let out_path = env::var("OUT_DIR").unwrap();
let target = env::var("TARGET").unwrap();
// Tell cargo to invalidate the built crate whenever the header changes.
println!("cargo:rerun-if-changed=qstr.h");
bindgen::Builder::default()
.header("qstr.h")
// Build the Qstr enum as a newtype so we can define method on it.
.default_enum_style(bindgen::EnumVariation::NewType { is_bitfield: false })
// Pass in correct include paths.
.clang_args(&[
"-I",
if target.starts_with("thumbv7em-none-eabi") {
"../../build/firmware"
} else {
"../../build/unix"
},
])
// Customize the standard types.
.use_core()
.ctypes_prefix("cty")
.size_t_is_usize(true)
// Tell cargo to invalidate the built crate whenever any of the
// included header files change.
.parse_callbacks(Box::new(bindgen::CargoCallbacks))
.generate()
.expect("Unable to generate Rust QSTR bindings")
.write_to_file(PathBuf::from(out_path).join("qstr.rs"))
.unwrap();
}
fn generate_micropython_bindings() {
let out_path = env::var("OUT_DIR").unwrap();
let target = env::var("TARGET").unwrap();
// Tell cargo to invalidate the built crate whenever the header changes.
println!("cargo:rerun-if-changed=micropython.h");
let mut bindings = bindgen::Builder::default()
.header("micropython.h")
// obj
.new_type_alias("mp_obj_t")
.allowlist_type("mp_obj_type_t")
.allowlist_type("mp_obj_base_t")
.allowlist_function("mp_obj_new_int")
.allowlist_function("mp_obj_new_int_from_ll")
.allowlist_function("mp_obj_new_int_from_ull")
.allowlist_function("mp_obj_new_int_from_uint")
.allowlist_function("mp_obj_new_bytes")
.allowlist_function("mp_obj_new_str")
.allowlist_function("mp_obj_get_int_maybe")
.allowlist_function("mp_obj_is_true")
.allowlist_function("mp_call_function_n_kw")
.allowlist_function("trezor_obj_get_ll_checked")
.allowlist_function("trezor_obj_get_ull_checked")
// buffer
.allowlist_function("mp_get_buffer")
.allowlist_var("MP_BUFFER_READ")
.allowlist_var("MP_BUFFER_WRITE")
.allowlist_var("MP_BUFFER_RW")
// dict
.allowlist_type("mp_obj_dict_t")
.allowlist_function("mp_obj_new_dict")
.allowlist_function("mp_obj_dict_store")
.allowlist_var("mp_type_dict")
// fun
.allowlist_type("mp_obj_fun_builtin_fixed_t")
.allowlist_var("mp_type_fun_builtin_1")
.allowlist_var("mp_type_fun_builtin_2")
.allowlist_var("mp_type_fun_builtin_3")
// gc
.allowlist_function("gc_alloc")
// iter
.allowlist_type("mp_obj_iter_buf_t")
.allowlist_function("mp_getiter")
.allowlist_function("mp_iternext")
// list
.allowlist_type("mp_obj_list_t")
.allowlist_function("mp_obj_new_list")
.allowlist_function("mp_obj_list_append")
.allowlist_var("mp_type_list")
// map
.allowlist_type("mp_map_elem_t")
.allowlist_type("mp_map_lookup_kind_t")
.allowlist_function("mp_map_init")
.allowlist_function("mp_map_init_fixed_table")
.allowlist_function("mp_map_lookup")
// runtime
.allowlist_function("mp_raise_ValueError")
// typ
.allowlist_var("mp_type_type");
// `ffi::mp_map_t` type is not allowed to be `Clone` or `Copy` because we tie it
// to the data lifetimes with the `MapRef` type, see `src/micropython/map.rs`.
// TODO: We should disable `Clone` and `Copy` for all types and only allow-list
// the specific cases we require.
bindings = bindings.no_copy("_mp_map_t");
// Pass in correct include paths and defines.
if target.starts_with("thumbv7em-none-eabi") {
bindings = bindings.clang_args(&[
"-nostdinc",
"-I../firmware",
"-I../trezorhal",
"-I../../build/firmware",
"-I../../vendor/micropython",
"-I../../vendor/micropython/lib/stm32lib/STM32F4xx_HAL_Driver/Inc",
"-I../../vendor/micropython/lib/stm32lib/CMSIS/STM32F4xx/Include",
"-I../../vendor/micropython/lib/cmsis/inc",
"-DTREZOR_MODEL=T",
"-DSTM32F405xx",
"-DUSE_HAL_DRIVER",
"-DSTM32_HAL_H=<stm32f4xx.h>",
]);
// Append gcc-arm-none-eabi's include paths.
let cc_output = Command::new("arm-none-eabi-gcc")
.arg("-E")
.arg("-Wp,-v")
.arg("-")
.output()
.expect("arm-none-eabi-gcc failed to execute");
if !cc_output.status.success() {
panic!("arm-none-eabi-gcc failed");
}
let include_paths =
String::from_utf8(cc_output.stderr).expect("arm-none-eabi-gcc returned invalid output");
let include_args = include_paths
.lines()
.skip_while(|s| !s.contains("search starts here:"))
.take_while(|s| !s.contains("End of search list."))
.filter(|s| s.starts_with(" "))
.map(|s| format!("-I{}", s.trim()));
bindings = bindings.clang_args(include_args);
} else {
bindings = bindings.clang_args(&[
"-I../unix",
"-I../../build/unix",
"-I../../vendor/micropython",
]);
}
bindings
// Customize the standard types.
.use_core()
.ctypes_prefix("cty")
.size_t_is_usize(true)
// Tell cargo to invalidate the built crate whenever any of the
// included header files change.
.parse_callbacks(Box::new(bindgen::CargoCallbacks))
// Write the bindings to a file in the OUR_DIR.
.generate()
.expect("Unable to generate Rust Micropython bindings")
.write_to_file(PathBuf::from(out_path).join("micropython.rs"))
.unwrap();
}

@ -0,0 +1,5 @@
#include "py/gc.h"
#include "py/obj.h"
#include "py/runtime.h"
#include "../extmod/trezorobj.h"

@ -0,0 +1,5 @@
enum Qstr {
#define QDEF(id, str) id,
#include "genhdr/qstrdefs.generated.h"
#undef QDEF
};

@ -0,0 +1,2 @@
wrap_comments = true

@ -0,0 +1,49 @@
use core::convert::Infallible;
use core::fmt;
use cstr_core::CStr;
pub enum Error {
Missing,
OutOfRange,
InvalidType,
NotBuffer,
NotInt,
}
impl Error {
pub fn as_cstr(&self) -> &'static CStr {
// SAFETY: Safe because we are passing in \0-terminated literals.
unsafe {
let cstr = |s: &'static str| CStr::from_bytes_with_nul_unchecked(s.as_bytes());
match self {
Error::Missing => cstr("Missing\0"),
Error::OutOfRange => cstr("OutOfRange\0"),
Error::InvalidType => cstr("InvalidType\0"),
Error::NotBuffer => cstr("NotBuffer\0"),
Error::NotInt => cstr("NotInt\0"),
}
}
}
}
impl From<Error> for &'static CStr {
fn from(val: Error) -> Self {
val.as_cstr()
}
}
impl fmt::Debug for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.as_cstr().fmt(f)
}
}
// Implements a conversion from `core::convert::Infallible` to `Error` to so
// that code generic over `TryFrom` can work with values covered by the blanket
// impl for `Into`: `https://doc.rust-lang.org/std/convert/enum.Infallible.html`
impl From<Infallible> for Error {
fn from(_: Infallible) -> Self {
unreachable!()
}
}

@ -0,0 +1,37 @@
#![cfg_attr(not(test), no_std)]
#![feature(never_type)]
#![feature(unsize)]
#![feature(coerce_unsized)]
#![feature(dispatch_from_dyn)]
#![deny(clippy::all)]
#![deny(unsafe_op_in_unsafe_fn)]
#![allow(dead_code)]
mod error;
#[macro_use]
mod micropython;
mod trezorhal;
mod util;
#[cfg(not(test))]
use core::panic::PanicInfo;
#[cfg(not(test))]
use cstr_core::CStr;
#[cfg(not(test))]
#[panic_handler]
fn panic(_info: &PanicInfo) -> ! {
// Although it would be ideal to use the original error message, ignoring it
// lets us avoid the `fmt` machinery and its code size and is also important for
// security reasons, as we do not always controls the message contents. We
// should also avoid printing "panic" or "rust" on the user screen to avoid any
// confusion.
// SAFETY: Safe because we are passing in \0-terminated literals.
let empty = unsafe { CStr::from_bytes_with_nul_unchecked("\0".as_bytes()) };
let msg = unsafe { CStr::from_bytes_with_nul_unchecked("rs\0".as_bytes()) };
// TODO: Ideally we would take the file and line info out of
// `PanicInfo::location()`.
trezorhal::common::fatal_error(&empty, &msg, &empty, 0, &empty);
}

@ -0,0 +1,66 @@
use core::{convert::TryFrom, ops::Deref, ptr, slice};
use crate::{error::Error, micropython::obj::Obj};
use super::ffi;
/// Represents an immutable slice of bytes stored on the MicroPython heap and
/// owned by values that obey the buffer protocol, such as `bytes`, `str`,
/// `bytearray` or `memoryview`.
///
/// # Safety
///
/// In most cases, it is unsound to store `Buffer` values in a GC-unreachable
/// location, such as static data.
pub struct Buffer {
ptr: *const u8,
len: usize,
}
impl TryFrom<Obj> for Buffer {
type Error = Error;
fn try_from(obj: Obj) -> Result<Self, Self::Error> {
let mut bufinfo = ffi::mp_buffer_info_t {
buf: ptr::null_mut(),
len: 0,
typecode: 0,
};
// SAFETY: We assume that if `ffi::mp_get_buffer` returns successfully,
// `bufinfo.buf` contains a pointer to data of `bufinfo.len` bytes. Here
// we consider this data either GC-allocated or effectively 'static, and
// store the pointer directly in `Buffer`. It is unsound to store
// `Buffer` values in a GC-unreachable location, such as static data.
if unsafe { ffi::mp_get_buffer(obj, &mut bufinfo, ffi::MP_BUFFER_READ as _) } {
Ok(Self {
ptr: bufinfo.buf as _,
len: bufinfo.len as _,
})
} else {
Err(Error::NotBuffer)
}
}
}
impl Deref for Buffer {
type Target = [u8];
fn deref(&self) -> &Self::Target {
self.as_ref()
}
}
impl AsRef<[u8]> for Buffer {
fn as_ref(&self) -> &[u8] {
if self.ptr.is_null() {
// `self.ptr` can be null if len == 0.
&[]
} else {
// SAFETY: We assume that `self.ptr` is pointing to memory:
// - without any mutable references,
// - immutable for the whole lifetime of `&self`,
// - with at least `self.len` bytes.
unsafe { slice::from_raw_parts(self.ptr, self.len) }
}
}
}

@ -0,0 +1,70 @@
use core::convert::TryFrom;
use crate::error::Error;
use super::{ffi, gc::Gc, map::Map, obj::Obj};
/// Insides of the MicroPython `dict` object.
pub type Dict = ffi::mp_obj_dict_t;
impl Dict {
/// Allocate a new dictionary on the GC heap, empty, but with a capacity of
/// `capacity` items.
pub fn alloc_with_capacity(capacity: usize) -> Gc<Self> {
unsafe {
// SAFETY: We expect that `ffi::mp_obj_new_dict` either returns a GC-allocated
// pointer to `ffi::mp_obj_dict_t` or raises (i.e. on allocation failure).
let ptr = ffi::mp_obj_new_dict(capacity);
Gc::from_raw(ptr.as_ptr().cast())
}
}
/// Constructs a dictionary definition by taking ownership of given [`Map`].
pub fn with_map(map: Map) -> Self {
unsafe {
Self {
base: ffi::mp_obj_base_t {
type_: &ffi::mp_type_dict,
},
map,
}
}
}
/// Returns a reference to the contained [`Map`].
pub fn map(&self) -> &Map {
&self.map
}
/// Returns a mutable reference to the contained [`Map`].
pub fn map_mut(&mut self) -> &mut Map {
&mut self.map
}
}
impl From<Gc<Dict>> for Obj {
fn from(value: Gc<Dict>) -> Self {
// SAFETY:
// - `value` is an object struct with a base and a type.
// - `value` is GC-allocated.
unsafe { Obj::from_ptr(Gc::into_raw(value).cast()) }
}
}
impl TryFrom<Obj> for Gc<Dict> {
type Error = Error;
fn try_from(value: Obj) -> Result<Self, Self::Error> {
if unsafe { ffi::mp_type_dict.is_type_of(value) } {
// SAFETY: We assume that if `value` is an object pointer with the correct type,
// it is managed by MicroPython GC (see `Gc::from_raw` for details).
let this = unsafe { Gc::from_raw(value.as_ptr().cast()) };
Ok(this)
} else {
Err(Error::InvalidType)
}
}
}
// SAFETY: We are in a single-threaded environment.
unsafe impl Sync for Dict {}

@ -0,0 +1,5 @@
#![allow(non_camel_case_types)]
#![allow(non_upper_case_globals)]
#![allow(dead_code)]
include!(concat!(env!("OUT_DIR"), "/micropython.rs"));

@ -0,0 +1,18 @@
use super::{ffi, obj::Obj};
pub type Func = ffi::mp_obj_fun_builtin_fixed_t;
impl Func {
/// Convert a "static const" function to a MicroPython object.
pub const fn to_obj(&'static self) -> Obj {
// SAFETY:
// - We are an object struct with a base and a type.
// - 'static lifetime holds us in place.
// - MicroPython is smart enough not to mutate `mp_obj_fun_builtin_fixed_t`
// objects.
unsafe { Obj::from_ptr(self as *const _ as *mut _) }
}
}
// SAFETY: We are in a single-threaded environment.
unsafe impl Sync for Func {}

@ -0,0 +1,81 @@
use core::{
alloc::Layout,
marker::Unsize,
ops::{CoerceUnsized, Deref, DispatchFromDyn},
ptr::{self, NonNull},
};
use super::ffi;
/// A pointer type for values on the garbage-collected heap.
///
/// Although a garbage-collected pointer type technically should implement
/// `Copy` and `Clone`, we avoid doing this until proven necessary.
pub struct Gc<T: ?Sized>(NonNull<T>);
impl<T: ?Sized + Unsize<U>, U: ?Sized> CoerceUnsized<Gc<U>> for Gc<T> {}
impl<T: ?Sized + Unsize<U>, U: ?Sized> DispatchFromDyn<Gc<U>> for Gc<T> {}
impl<T> Gc<T> {
/// Allocate memory on the heap managed by the MicroPython garbage collector
/// and then place `v` into it. `v` will _not_ get its destructor called.
pub fn new(v: T) -> Self {
let layout = Layout::for_value(&v);
// TODO: Assert that `layout.align()` is the same as the GC alignment.
// SAFETY:
// - Unfortunately we cannot respect `layout.align()` as MicroPython GC does
// not support custom alignment.
// - `ptr` is guaranteed to stay valid as long as it's reachable from the stack
// or the MicroPython heap.
unsafe {
let raw = ffi::gc_alloc(layout.size(), 0).cast();
ptr::write(raw, v);
Self::from_raw(raw)
}
}
/// Return a mutable reference to the value.
///
/// # Safety
///
/// `Gc` values can originate in the MicroPython interpreter, and these can
/// be both shared and mutable. Before calling this function, you have to
/// ensure that `this` is unique for the whole lifetime of the
/// returned mutable reference.
pub unsafe fn as_mut(this: &mut Self) -> &mut T {
// SAFETY: The caller must guarantee that `this` meets all the requirements for
// a mutable reference.
unsafe { this.0.as_mut() }
}
}
impl<T: ?Sized> Gc<T> {
/// Construct a `Gc` from a raw pointer.
///
/// # Safety
///
/// This function is unsafe because the caller has to guarantee that `ptr`
/// is pointing to a memory understood by the MicroPython GC, that is:
/// - previously allocated through `Gc::new()` or `gc_alloc()`, or
/// - through the MicroPython interpreter, or
/// - one of the GC roots (sys.argv, sys.modules, etc.).
pub unsafe fn from_raw(ptr: *mut T) -> Self {
// SAFETY: The caller must guarantee that `ptr` is something the MicroPython GC
// can reason about.
unsafe { Self(NonNull::new_unchecked(ptr)) }
}
/// Convert `this` into a raw pointer. This will _not_ drop the contained
/// value.
pub fn into_raw(this: Self) -> *mut T {
this.0.as_ptr()
}
}
impl<T: ?Sized> Deref for Gc<T> {
type Target = T;
fn deref(&self) -> &Self::Target {
unsafe { self.0.as_ref() }
}
}

@ -0,0 +1,69 @@
use core::ptr;
use crate::error::Error;
use crate::micropython::obj::Obj;
use super::ffi;
pub struct IterBuf {
iter_buf: ffi::mp_obj_iter_buf_t,
}
impl IterBuf {
pub fn new() -> Self {
Self {
iter_buf: ffi::mp_obj_iter_buf_t {
base: ffi::mp_obj_base_t {
type_: ptr::null_mut(),
},
buf: [Obj::const_null(), Obj::const_null(), Obj::const_null()],
},
}
}
}
pub struct Iter<'a> {
iter: Obj,
iter_buf: &'a mut IterBuf,
finished: bool,
}
impl<'a> Iter<'a> {
pub fn try_from_obj_with_buf(o: Obj, iter_buf: &'a mut IterBuf) -> Result<Self, Error> {
// SAFETY:
// - In the common case, `ffi::mp_getiter` does not heap-allocate, but instead
// uses memory from the passed `iter_buf`. We maintain this invariant by
// taking a mut ref to `IterBuf` and tying it to the lifetime of returned
// `Iter`.
// - Raises if `o` is not iterable and in other cases as well.
// - Returned obj is referencing into `iter_buf`.
// TODO: Use a fn that doesn't raise on non-iterable object.
let iter = unsafe { ffi::mp_getiter(o, &mut iter_buf.iter_buf) };
Ok(Self {
iter,
iter_buf,
finished: false,
})
}
}
impl<'a> Iterator for Iter<'a> {
type Item = Obj;
fn next(&mut self) -> Option<Self::Item> {
if self.finished {
return None;
}
// SAFETY:
// - We assume that `mp_iternext` returns objects without any lifetime
// invariants, i.e. heap-allocated, unlike `mp_getiter`.
// - Can raise.
let item = unsafe { ffi::mp_iternext(self.iter) };
if item == Obj::const_stop_iteration() {
self.finished = true;
None
} else {
Some(item)
}
}
}

@ -0,0 +1,50 @@
use core::convert::TryFrom;
use crate::error::Error;
use super::{ffi, gc::Gc, obj::Obj};
pub type List = ffi::mp_obj_list_t;
impl List {
pub fn alloc(values: &[Obj]) -> Gc<Self> {
// SAFETY: Altough `values` are copied into the new list and not mutated,
// `mp_obj_new_list` is taking them through a mut pointer.
unsafe {
let list = ffi::mp_obj_new_list(values.len(), values.as_ptr() as *mut Obj);
Gc::from_raw(list.as_ptr().cast())
}
}
pub fn append(&mut self, value: Obj) {
unsafe {
let ptr = self as *mut Self;
let list = Obj::from_ptr(ptr.cast());
ffi::mp_obj_list_append(list, value);
}
}
}
impl From<Gc<List>> for Obj {
fn from(value: Gc<List>) -> Self {
// SAFETY:
// - `value` is an object struct with a base and a type.
// - `value` is GC-allocated.
unsafe { Obj::from_ptr(Gc::into_raw(value).cast()) }
}
}
impl TryFrom<Obj> for Gc<List> {
type Error = Error;
fn try_from(value: Obj) -> Result<Self, Self::Error> {
if unsafe { ffi::mp_type_list.is_type_of(value) } {
// SAFETY: We assume that if `value` is an object pointer with the correct type,
// it is managed by MicroPython GC (see `Gc::from_raw` for details).
let this = unsafe { Gc::from_raw(value.as_ptr().cast()) };
Ok(this)
} else {
Err(Error::InvalidType)
}
}
}

@ -0,0 +1,132 @@
macro_rules! obj_fn_1 {
($f:expr) => {{
#[allow(unused_unsafe)]
unsafe {
use $crate::micropython::ffi;
ffi::mp_obj_fun_builtin_fixed_t {
base: ffi::mp_obj_base_t {
type_: &ffi::mp_type_fun_builtin_1,
},
fun: ffi::_mp_obj_fun_builtin_fixed_t__bindgen_ty_1 { _1: Some($f) },
}
}
}};
}
macro_rules! obj_fn_2 {
($f:expr) => {{
#[allow(unused_unsafe)]
unsafe {
use $crate::micropython::ffi;
ffi::mp_obj_fun_builtin_fixed_t {
base: ffi::mp_obj_base_t {
type_: &ffi::mp_type_fun_builtin_2,
},
fun: ffi::_mp_obj_fun_builtin_fixed_t__bindgen_ty_1 { _2: Some($f) },
}
}
}};
}
macro_rules! obj_fn_3 {
($f:expr) => {{
#[allow(unused_unsafe)]
unsafe {
use $crate::micropython::ffi;
ffi::mp_obj_fun_builtin_fixed_t {
base: ffi::mp_obj_base_t {
type_: &ffi::mp_type_fun_builtin_3,
},
fun: ffi::_mp_obj_fun_builtin_fixed_t__bindgen_ty_1 { _3: Some($f) },
}
}
}};
}
/// Construct fixed static const `Map` from `key` => `val` pairs.
macro_rules! obj_map {
($($key:expr => $val:expr),*) => ({
Map::from_fixed_static(&[
$(
Map::at($key, $val),
)*
])
});
($($key:expr => $val:expr),* ,) => ({
obj_map!($($key => $val),*)
});
}
/// Construct a `Dict` from the backing `Map`. See `obj_map` above.
macro_rules! obj_dict {
($map:expr) => {{
#[allow(unused_unsafe)]
unsafe {
use $crate::micropython::ffi;
ffi::mp_obj_dict_t {
base: ffi::mp_obj_base_t {
type_: &ffi::mp_type_dict,
},
map: $map,
}
}
}};
}
macro_rules! obj_type {
(name: $name:expr,
$(locals: $locals:expr,)?
$(attr_fn: $attr_fn:ident,)?
$(call_fn: $call_fn:ident,)?
) => {{
#[allow(unused_unsafe)]
unsafe {
use $crate::micropython::ffi;
let name = $name.to_u16();
#[allow(unused_mut)]
#[allow(unused_assignments)]
let mut attr: ffi::mp_attr_fun_t = None;
$(attr = Some($attr_fn);)?
#[allow(unused_mut)]
#[allow(unused_assignments)]
let mut call: ffi::mp_call_fun_t = None;
$(call = Some($call_fn);)?
// TODO: This is safe only if we pass in `Dict` with fixed `Map` (created by
// `Map::fixed()`), because only then will Micropython treat `locals_dict` as
// immutable, and make the mutable cast safe.
#[allow(unused_mut)]
#[allow(unused_assignments)]
let mut locals_dict = ::core::ptr::null_mut();
$(locals_dict = $locals as *const _ as *mut _;)?
ffi::mp_obj_type_t {
base: ffi::mp_obj_base_t {
type_: &ffi::mp_type_type,
},
flags: 0,
name,
print: None,
make_new: None,
call,
unary_op: None,
binary_op: None,
attr,
subscr: None,
getiter: None,
iternext: None,
buffer_p: ffi::mp_buffer_p_t { get_buffer: None },
protocol: ::core::ptr::null(),
parent: ::core::ptr::null(),
locals_dict,
}
}
}};
}

@ -0,0 +1,193 @@
use core::{marker::PhantomData, mem::MaybeUninit, ops::Deref, ptr, slice};
use crate::{
error::Error,
micropython::{obj::Obj, qstr::Qstr},
};
use super::ffi;
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,
}
}
}
impl Map {
pub fn from_fixed<'a>(table: &'a [MapElem]) -> MapRef<'a> {
let mut map = MaybeUninit::uninit();
// SAFETY: `mp_map_init` completely initializes all fields of `map`.
unsafe {
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) -> Self {
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 {
ffi::mp_map_init(map.as_mut_ptr(), capacity);
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;
let elem = ffi::mp_map_lookup(map, index, ffi::_mp_map_lookup_kind_t_MP_MAP_LOOKUP)
.as_ref()
.ok_or(Error::Missing)?;
Ok(elem.value)
}
}
pub fn set(&mut self, index: impl Into<Obj>, value: impl Into<Obj>) {
self.set_obj(index.into(), value.into())
}
pub fn set_obj(&mut self, index: Obj, value: Obj) {
// 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;
let elem = ffi::mp_map_lookup(
map,
index,
ffi::_mp_map_lookup_kind_t_MP_MAP_LOOKUP_ADD_IF_NOT_FOUND,
)
.as_mut()
.unwrap();
elem.value = value;
}
}
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;
ffi::mp_map_lookup(
map,
index,
ffi::_mp_map_lookup_kind_t_MP_MAP_LOOKUP_REMOVE_IF_FOUND,
);
}
}
}
impl Clone for Map {
fn clone(&self) -> Self {
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);
map
}
}
impl Default for Map {
fn default() -> Self {
Self::with_capacity(0)
}
}
// 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
}
}

@ -0,0 +1,15 @@
#[macro_use]
pub mod macros;
pub mod buffer;
pub mod dict;
pub mod ffi;
pub mod func;
pub mod gc;
pub mod iter;
pub mod list;
pub mod map;
pub mod obj;
pub mod qstr;
pub mod runtime;
pub mod typ;

@ -0,0 +1,327 @@
use core::convert::{TryFrom, TryInto};
use core::num::TryFromIntError;
use crate::error::Error;
use super::ffi;
pub type Obj = ffi::mp_obj_t;
pub type ObjBase = ffi::mp_obj_base_t;
impl PartialEq for Obj {
fn eq(&self, other: &Self) -> bool {
self.as_bits() == other.as_bits()
}
}
impl Eq for Obj {}
impl Obj {
pub const unsafe fn from_ptr(ptr: *mut cty::c_void) -> Self {
Self(ptr)
}
pub const fn as_ptr(self) -> *mut cty::c_void {
self.0
}
pub const unsafe fn from_bits(bits: cty::uintptr_t) -> Self {
Self(bits as _)
}
pub fn as_bits(self) -> cty::uintptr_t {
self.0 as _
}
}
impl Obj {
pub fn is_small_int(self) -> bool {
// micropython/py/obj.h mp_obj_is_small_int
self.as_bits() & 1 != 0
}
pub fn is_qstr(self) -> bool {
// micropython/py/obj.h mp_obj_is_qstr
self.as_bits() & 7 == 2
}
pub fn is_immediate(self) -> bool {
// micropython/py/obj.h mp_obj_is_immediate_obj
self.as_bits() & 7 == 6
}
pub fn is_ptr(self) -> bool {
// micropython/py/obj.h mp_obj_is_obj
self.as_bits() & 3 == 0
}
}
impl Obj {
pub const fn const_null() -> Self {
// micropython/py/obj.h
// #define MP_OBJ_NULL (MP_OBJ_FROM_PTR((void *)0))
unsafe { Self::from_bits(0) }
}
pub const fn const_stop_iteration() -> Self {
// micropython/py/obj.h
// #define MP_OBJ_STOP_ITERATION (MP_OBJ_FROM_PTR((void *)0))
unsafe { Self::from_bits(0) }
}
pub const fn const_none() -> Self {
// micropython/py/obj.h
// #define mp_const_none MP_OBJ_NEW_IMMEDIATE_OBJ(0)
unsafe { Self::immediate(0) }
}
pub const fn const_false() -> Self {
// micropython/py/obj.h
// #define mp_const_false MP_OBJ_NEW_IMMEDIATE_OBJ(1)
unsafe { Self::immediate(1) }
}
pub const fn const_true() -> Self {
// micropython/py/obj.h
// #define mp_const_true MP_OBJ_NEW_IMMEDIATE_OBJ(3)
unsafe { Self::immediate(3) }
}
const unsafe fn immediate(val: usize) -> Self {
// SAFETY:
// - `val` is in `0..=3` range.
// - MicroPython compiled with `MICROPY_OBJ_REPR == MICROPY_OBJ_REPR_A`.
// - MicroPython compiled with `MICROPY_OBJ_IMMEDIATE_OBJS`.
// micropython/py/obj.h #define MP_OBJ_NEW_IMMEDIATE_OBJ(val)
// ((mp_obj_t)(((val) << 3) | 6))
unsafe { Self::from_bits((val << 3) | 6) }
}
}
impl Obj {
pub fn call_with_n_args(self, args: &[Obj]) -> Obj {
// SAFETY:
// - Can call python code and therefore raise.
// - Each of `args` has no lifetime bounds.
unsafe { ffi::mp_call_function_n_kw(self, args.len(), 0, args.as_ptr()) }
}
}
//
// # Converting `Obj` into plain data.
//
impl TryFrom<Obj> for bool {
type Error = Error;
fn try_from(obj: Obj) -> Result<Self, Self::Error> {
// TODO: Avoid type casts on the Python side.
// SAFETY:
// - Can call python code and therefore raise.
if unsafe { ffi::mp_obj_is_true(obj) } {
Ok(true)
} else {
Ok(false)
}
}
}
impl TryFrom<Obj> for i32 {
type Error = Error;
fn try_from(obj: Obj) -> Result<Self, Self::Error> {
let mut int: ffi::mp_int_t = 0;
// TODO: Avoid type casts on the Python side.
// SAFETY:
// - Can raise if `obj` is int but cannot fit into `cty::mp_int_t`.
if unsafe { ffi::mp_obj_get_int_maybe(obj.as_ptr(), &mut int) } {
let int = int.try_into()?;
Ok(int)
} else {
Err(Error::NotInt)
}
}
}
impl TryFrom<Obj> for i64 {
type Error = Error;
fn try_from(obj: Obj) -> Result<Self, Self::Error> {
let mut ll: cty::c_longlong = 0;
if unsafe { ffi::trezor_obj_get_ll_checked(obj, &mut ll) } {
Ok(ll)
} else {
Err(Error::NotInt)
}
}
}
//
// # Converting plain data into `Obj`.
//
impl From<bool> for Obj {
fn from(val: bool) -> Self {
if val {
Obj::const_true()
} else {
Obj::const_false()
}
}
}
impl From<i32> for Obj {
fn from(val: i32) -> Self {
// `mp_obj_new_int` accepts a `mp_int_t` argument, which is word-sized. We
// primarily target 32-bit architecture, and therefore keep the primary signed
// conversion type as `i32`, but convert through `into()` if the types differ.
// SAFETY:
// - Can raise if allocation fails.
unsafe { ffi::mp_obj_new_int(val.into()) }
}
}
impl From<i64> for Obj {
fn from(val: i64) -> Self {
// Because `mp_obj_new_int_from_ll` allocates even if `val` fits into small-int,
// we try to go through `mp_obj_new_int` first.
match i32::try_from(val) {
Ok(smaller_val) => smaller_val.into(),
// SAFETY:
// - Can raise if allocation fails.
Err(_) => unsafe { ffi::mp_obj_new_int_from_ll(val) },
}
}
}
impl From<u32> for Obj {
fn from(val: u32) -> Self {
// `mp_obj_new_int_from_uint` accepts a `mp_uint_t` argument, which is
// word-sized. We primarily target 32-bit architecture, and therefore keep
// the primary unsigned conversion type as `u32`, but convert through `into()`
// if the types differ.
// SAFETY:
// - Can raise if allocation fails.
unsafe { ffi::mp_obj_new_int_from_uint(val.into()) }
}
}
impl From<u64> for Obj {
fn from(val: u64) -> Self {
// Because `mp_obj_new_int_from_ull` allocates even if `val` fits into
// small-int, we try to go through `mp_obj_new_int_from_uint` first.
match u32::try_from(val) {
Ok(smaller_val) => smaller_val.into(),
// SAFETY:
// - Can raise if allocation fails.
Err(_) => unsafe { ffi::mp_obj_new_int_from_ull(val) },
}
}
}
/// Byte slices are converted into `bytes` MicroPython objects, by allocating
/// new space on the heap and copying.
impl From<&[u8]> for Obj {
fn from(val: &[u8]) -> Self {
// SAFETY:
// - Can raise if allocation fails.
unsafe { ffi::mp_obj_new_bytes(val.as_ptr(), val.len()) }
}
}
/// String slices are converted into `str` MicroPython objects. Strings that are
/// already interned will turn up as QSTRs, strings not found in the QSTR pool
/// will be allocated on the heap and copied.
impl From<&str> for Obj {
fn from(val: &str) -> Self {
// SAFETY:
// - Can raise if allocation fails.
// - `str` is guaranteed to be UTF-8.
unsafe { ffi::mp_obj_new_str(val.as_ptr().cast(), val.len()) }
}
}
//
// # Additional conversions based on the methods above.
//
impl From<u8> for Obj {
fn from(val: u8) -> Self {
u32::from(val).into()
}
}
impl From<u16> for Obj {
fn from(val: u16) -> Self {
u32::from(val).into()
}
}
impl From<usize> for Obj {
fn from(val: usize) -> Self {
// Willingly truncate the bits on 128-bit architectures.
(val as u64).into()
}
}
impl TryFrom<Obj> for u8 {
type Error = Error;
fn try_from(obj: Obj) -> Result<Self, Self::Error> {
let val = i32::try_from(obj)?;
let this = Self::try_from(val)?;
Ok(this)
}
}
impl TryFrom<Obj> for u16 {
type Error = Error;
fn try_from(obj: Obj) -> Result<Self, Self::Error> {
let val = i32::try_from(obj)?;
let this = Self::try_from(val)?;
Ok(this)
}
}
impl TryFrom<Obj> for u32 {
type Error = Error;
fn try_from(obj: Obj) -> Result<Self, Self::Error> {
let val = i64::try_from(obj)?;
let this = Self::try_from(val)?;
Ok(this)
}
}
impl TryFrom<Obj> for u64 {
type Error = Error;
fn try_from(obj: Obj) -> Result<Self, Self::Error> {
// TODO: Support full range.
let val = i64::try_from(obj)?;
let this = Self::try_from(val)?;
Ok(this)
}
}
impl TryFrom<Obj> for usize {
type Error = Error;
fn try_from(obj: Obj) -> Result<Self, Self::Error> {
// TODO: Support full range.
let val = i64::try_from(obj)?;
let this = Self::try_from(val)?;
Ok(this)
}
}
impl From<TryFromIntError> for Error {
fn from(_: TryFromIntError) -> Self {
Self::OutOfRange
}
}

@ -0,0 +1,59 @@
#![allow(non_camel_case_types)]
#![allow(non_upper_case_globals)]
#![allow(dead_code)]
use core::convert::TryFrom;
use crate::{error::Error, micropython::obj::Obj};
impl Qstr {
pub const fn to_obj(self) -> Obj {
// SAFETY:
// - Micropython compiled with `MICROPY_OBJ_REPR == MICROPY_OBJ_REPR_A`.
// micropython/py/obj.h #define MP_OBJ_NEW_QSTR(qst)
// ((mp_obj_t)((((mp_uint_t)(qst)) << 3) | 2))
let bits = (self.0 << 3) | 2;
unsafe { Obj::from_bits(bits as usize) }
}
pub const fn from_obj_bits(bits: cty::uintptr_t) -> Self {
let bits = (bits >> 3) as u16; // See `Self::to_obj`.
Self::from_u16(bits)
}
pub const fn to_u16(self) -> u16 {
// TODO: Change the internal representation of Qstr to u16.
self.0 as _
}
pub const fn from_u16(val: u16) -> Self {
// TODO: Change the internal representation of Qstr to u16.
Self(val as _)
}
}
impl From<u16> for Qstr {
fn from(val: u16) -> Self {
Self::from_u16(val)
}
}
impl TryFrom<Obj> for Qstr {
type Error = Error;
fn try_from(value: Obj) -> Result<Self, Self::Error> {
if value.is_qstr() {
Ok(Self::from_obj_bits(value.as_bits()))
} else {
Err(Error::InvalidType)
}
}
}
impl From<Qstr> for Obj {
fn from(value: Qstr) -> Self {
value.to_obj()
}
}
include!(concat!(env!("OUT_DIR"), "/qstr.rs"));

@ -0,0 +1,10 @@
use cstr_core::CStr;
use super::ffi;
pub fn raise_value_error(msg: &'static CStr) -> ! {
unsafe {
ffi::mp_raise_ValueError(msg.as_ptr());
}
panic!();
}

@ -0,0 +1,28 @@
use super::{
ffi,
obj::{Obj, ObjBase},
};
pub type Type = ffi::mp_obj_type_t;
impl Type {
pub fn is_type_of(&'static self, obj: Obj) -> bool {
if obj.is_ptr() {
// SAFETY: If `obj` is a pointer, it should always point to an object having
// `ObjBase` as the first field, making this cast safe.
unsafe {
let base = obj.as_ptr() as *const ObjBase;
(*base).type_ == self
}
} else {
false
}
}
pub fn to_base(&'static self) -> ObjBase {
ObjBase { type_: self }
}
}
// SAFETY: We are in a single-threaded environment.
unsafe impl Sync for Type {}

@ -0,0 +1,24 @@
use cstr_core::CStr;
extern "C" {
// trezorhal/common.c
fn __fatal_error(
expr: *const cty::c_char,
msg: *const cty::c_char,
file: *const cty::c_char,
line: i32,
func: *const cty::c_char,
) -> !;
}
pub fn fatal_error(expr: &CStr, msg: &CStr, file: &CStr, line: i32, func: &CStr) -> ! {
unsafe {
__fatal_error(
expr.as_ptr(),
msg.as_ptr(),
file.as_ptr(),
line,
func.as_ptr(),
);
}
}

@ -0,0 +1,69 @@
extern "C" {
// trezorhal/display.c
fn display_backlight(val: cty::c_int) -> cty::c_int;
fn display_text(
x: cty::c_int,
y: cty::c_int,
text: *const cty::c_char,
textlen: cty::c_int,
font: cty::c_int,
fgcolor: cty::uint16_t,
bgcolor: cty::uint16_t,
);
fn display_text_width(
text: *const cty::c_char,
textlen: cty::c_int,
font: cty::c_int,
) -> cty::c_int;
fn display_bar(x: cty::c_int, y: cty::c_int, w: cty::c_int, h: cty::c_int, c: cty::uint16_t);
fn display_bar_radius(
x: cty::c_int,
y: cty::c_int,
w: cty::c_int,
h: cty::c_int,
c: cty::uint16_t,
b: cty::uint16_t,
r: cty::uint8_t,
);
}
const WIDTH: i32 = 240;
const HEIGHT: i32 = 240;
pub fn width() -> i32 {
WIDTH
}
pub fn height() -> i32 {
HEIGHT
}
pub fn backlight(val: i32) -> i32 {
unsafe { display_backlight(val) }
}
pub fn text(baseline_x: i32, baseline_y: i32, text: &[u8], font: i32, fgcolor: u16, bgcolor: u16) {
unsafe {
display_text(
baseline_x,
baseline_y,
text.as_ptr() as _,
text.len() as _,
font,
fgcolor,
bgcolor,
)
}
}
pub fn text_width(text: &[u8], font: i32) -> i32 {
unsafe { display_text_width(text.as_ptr() as _, text.len() as _, font) }
}
pub fn bar(x: i32, y: i32, w: i32, h: i32, fgcolor: u16) {
unsafe { display_bar(x, y, w, h, fgcolor) }
}
pub fn bar_radius(x: i32, y: i32, w: i32, h: i32, fgcolor: u16, bgcolor: u16, radius: u8) {
unsafe { display_bar_radius(x, y, w, h, fgcolor, bgcolor, radius) }
}

@ -0,0 +1,3 @@
pub mod common;
pub mod display;
pub mod random;

@ -0,0 +1,16 @@
extern "C" {
// trezor-crypto/rand.h
fn random_uniform(n: u32) -> u32;
}
pub fn uniform(n: u32) -> u32 {
unsafe { random_uniform(n) }
}
pub fn shuffle<T>(slice: &mut [T]) {
// Fisher-Yates shuffle.
for i in (1..slice.len()).rev() {
let j = uniform(i as u32 + 1) as usize;
slice.swap(i, j);
}
}

@ -0,0 +1,63 @@
use core::slice;
use crate::{
error::Error,
micropython::{
map::{Map, MapElem},
obj::Obj,
runtime::raise_value_error,
},
};
pub fn try_or_raise(func: impl FnOnce() -> Result<Obj, Error>) -> Obj {
func().unwrap_or_else(|err| raise_value_error(err.as_cstr()))
}
pub fn try_with_kwargs(kwargs: *const Map, func: impl FnOnce(&Map) -> Result<Obj, Error>) -> Obj {
try_or_raise(|| {
let kwargs = unsafe { kwargs.as_ref() }.ok_or(Error::Missing)?;
func(kwargs)
})
}
pub fn try_with_args_and_kwargs(
n_args: usize,
args: *const Obj,
kwargs: *const Map,
func: impl FnOnce(&[Obj], &Map) -> Result<Obj, Error>,
) -> Obj {
try_or_raise(|| {
let args = if args.is_null() {
&[]
} else {
unsafe { slice::from_raw_parts(args, n_args) }
};
let kwargs = unsafe { kwargs.as_ref() }.ok_or(Error::Missing)?;
func(args, kwargs)
})
}
pub fn try_with_args_and_kwargs_inline(
n_args: usize,
n_kw: usize,
args: *const Obj,
func: impl FnOnce(&[Obj], &Map) -> Result<Obj, Error>,
) -> Obj {
try_or_raise(|| {
let args_slice: &[Obj];
let kwargs_slice: &[MapElem];
if args.is_null() {
args_slice = &[];
kwargs_slice = &[];
} else {
args_slice = unsafe { slice::from_raw_parts(args, n_args) };
kwargs_slice = unsafe { slice::from_raw_parts(args.add(n_args).cast(), n_kw) };
}
let kw_map = Map::from_fixed(kwargs_slice);
func(args_slice, &kw_map)
})
}
Loading…
Cancel
Save