From 607548f32d98d955775bc02d4fc2b33a79e860ee Mon Sep 17 00:00:00 2001 From: Jim Mussared Date: Mon, 29 May 2023 16:56:38 +1000 Subject: [PATCH] examples/natmod: Add features4 as a class definition example. Also provide a basic README.md for dynamic native modules. This work was funded through GitHub Sponsors. Signed-off-by: Jim Mussared --- examples/natmod/README.md | 74 +++++++++++++++++++++++++++ examples/natmod/features4/Makefile | 14 +++++ examples/natmod/features4/features4.c | 73 ++++++++++++++++++++++++++ tools/ci.sh | 1 + 4 files changed, 162 insertions(+) create mode 100644 examples/natmod/README.md create mode 100644 examples/natmod/features4/Makefile create mode 100644 examples/natmod/features4/features4.c diff --git a/examples/natmod/README.md b/examples/natmod/README.md new file mode 100644 index 000000000000..0cc4010ef42f --- /dev/null +++ b/examples/natmod/README.md @@ -0,0 +1,74 @@ +# Dynamic Native Modules + +Dynamic Native Modules are .mpy files that contain native machine code from a +language other than Python. For more info see [the documentation] +(https://docs.micropython.org/en/latest/develop/natmod.html). + +This should not be confused with [User C Modules] +(https://docs.micropython.org/en/latest/develop/cmodules.html) which are a +mechanism to add additional out-of-tree modules into the firmware build. + +## Examples + +This directory contains several examples of writing dynamic native modules, in +two main categories: + +1. Feature examples. + + * `features0` - A module containing a single "factorial" function which + demonstrates working with integers. + + * `features1` - A module that demonstrates some common tasks: + - defining simple functions exposed to Python + - defining local, helper C functions + - defining constant integers and strings exposed to Python + - getting and creating integer objects + - creating Python lists + - raising exceptions + - allocating memory + - BSS and constant data (rodata) + - relocated pointers in rodata + + * `features2` - This is a hybrid module containing both Python and C code, + and additionally the C code is spread over multiple files. It also + demonstrates using floating point (only when the target supports + hardware floating point). + + * `features3` - A module that shows how to use types, constant objects, + and creating dictionary instances. + + * `features4` - A module that demonstrates how to define a class. + +2. Dynamic version of existing built-ins. + + This provides a way to add missing functionality to firmware that doesn't + include certain built-in modules. See the `heapq`, `random`, `re`, + `deflate`, `btree`, and `framebuf` directories. + + So for example, if your firmware was compiled with `MICROPY_PY_FRAMEBUF` + disabled (e.g. to save flash space), then it would not include the + `framebuf` module. The `framebuf` native module provides a way to add the + `framebuf` module dynamically. + + The way these work is they define a dynamic native module which + `#include`'s the original module and then does the necessary + initialisation of the module's globals dict. + +## Build instructions + +To compile an example, you need to have the same toolchain available as +required for your target port. e.g. `arm-none-eabi-gcc` for any ARM Cortex M +target. See the port instructions for details. + +You also need to have the `pyelftools` Python package available, either via +your system package manager or installed from PyPI in a virtual environment +with `pip`. + +Each example provides a Makefile. You should specify the `ARCH` argument to +make (one of x86, x64, armv6m, armv7m, xtensa, xtensawin): + +``` +$ cd features0 +$ make ARCH=armv7m +$ mpremote cp features0.mpy : +``` diff --git a/examples/natmod/features4/Makefile b/examples/natmod/features4/Makefile new file mode 100644 index 000000000000..f76a31a7cc14 --- /dev/null +++ b/examples/natmod/features4/Makefile @@ -0,0 +1,14 @@ +# Location of top-level MicroPython directory +MPY_DIR = ../../.. + +# Name of module +MOD = features4 + +# Source files (.c or .py) +SRC = features4.c + +# Architecture to build for (x86, x64, armv7m, xtensa, xtensawin) +ARCH = x64 + +# Include to get the rules for compiling and linking the module +include $(MPY_DIR)/py/dynruntime.mk diff --git a/examples/natmod/features4/features4.c b/examples/natmod/features4/features4.c new file mode 100644 index 000000000000..336f4ecf6408 --- /dev/null +++ b/examples/natmod/features4/features4.c @@ -0,0 +1,73 @@ +/* + This example extends on features0 but demonstrates how to define a class. + + The Factorial class constructor takes an integer, and then the calculate + method can be called to get the factorial. + + >>> import features4 + >>> f = features4.Factorial(4) + >>> f.calculate() + 24 +*/ + +// Include the header file to get access to the MicroPython API +#include "py/dynruntime.h" + +// This is type(Factorial) +mp_obj_full_type_t mp_type_factorial; + +// This is the internal state of a Factorial instance. +typedef struct { + mp_obj_base_t base; + mp_int_t n; +} mp_obj_factorial_t; + +// Essentially Factorial.__new__ (but also kind of __init__). +// Takes a single argument (the number to find the factorial of) +STATIC mp_obj_t factorial_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *args_in) { + mp_arg_check_num(n_args, n_kw, 1, 1, false); + + mp_obj_factorial_t *o = mp_obj_malloc(mp_obj_factorial_t, type); + o->n = mp_obj_get_int(args_in[0]); + + return MP_OBJ_FROM_PTR(o); +} + +STATIC mp_int_t factorial_helper(mp_int_t x) { + if (x == 0) { + return 1; + } + return x * factorial_helper(x - 1); +} + +// Implements Factorial.calculate() +STATIC mp_obj_t factorial_calculate(mp_obj_t self_in) { + mp_obj_factorial_t *self = MP_OBJ_TO_PTR(self_in); + return mp_obj_new_int(factorial_helper(self->n)); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(factorial_calculate_obj, factorial_calculate); + +// Locals dict for the Factorial type (will have a single method, calculate, +// added in mpy_init). +mp_map_elem_t factorial_locals_dict_table[1]; +STATIC MP_DEFINE_CONST_DICT(factorial_locals_dict, factorial_locals_dict_table); + +// This is the entry point and is called when the module is imported +mp_obj_t mpy_init(mp_obj_fun_bc_t *self, size_t n_args, size_t n_kw, mp_obj_t *args) { + // This must be first, it sets up the globals dict and other things + MP_DYNRUNTIME_INIT_ENTRY + + // Initialise the type. + mp_type_factorial.base.type = (void*)&mp_type_type; + mp_type_factorial.flags = MP_TYPE_FLAG_NONE; + mp_type_factorial.name = MP_QSTR_Factorial; + MP_OBJ_TYPE_SET_SLOT(&mp_type_factorial, make_new, factorial_make_new, 0); + factorial_locals_dict_table[0] = (mp_map_elem_t){ MP_OBJ_NEW_QSTR(MP_QSTR_calculate), MP_OBJ_FROM_PTR(&factorial_calculate_obj) }; + MP_OBJ_TYPE_SET_SLOT(&mp_type_factorial, locals_dict, (void*)&factorial_locals_dict, 1); + + // Make the Factorial type available on the module. + mp_store_global(MP_QSTR_Factorial, MP_OBJ_FROM_PTR(&mp_type_factorial)); + + // This must be last, it restores the globals dict + MP_DYNRUNTIME_INIT_EXIT +} diff --git a/tools/ci.sh b/tools/ci.sh index 33dc58d6b8e1..5ce742b01bed 100755 --- a/tools/ci.sh +++ b/tools/ci.sh @@ -425,6 +425,7 @@ function ci_native_mpy_modules_build { make -C examples/natmod/features1 ARCH=$arch make -C examples/natmod/features2 ARCH=$arch make -C examples/natmod/features3 ARCH=$arch + make -C examples/natmod/features4 ARCH=$arch make -C examples/natmod/btree ARCH=$arch make -C examples/natmod/deflate ARCH=$arch make -C examples/natmod/framebuf ARCH=$arch