Skip to content

Implement a module system for external C modules. #3871

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
86 changes: 86 additions & 0 deletions docs/develop/cmodules.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
Extending MicroPython with C
============================

Some specialized code would be unacceptably slow or needs to access hardware in
a way that cannot be done from MicroPython. Therefore, it supports a way of
extending the language with custom modules written in C. But before you consider
writing a module in C, please take a look at :ref:`speed_python`.

`Unlike CPython <https://docs.python.org/3/extending/building.html>`_, these
modules are (currently) embedded directly in the program image instead of being
dynamically loaded. This requires a `custom build of MicroPython
<https://github.com/micropython/micropython/wiki/Getting-Started>`_.


Writing a module
----------------

A module is a directory with the following files:

* ``micropython.mk``, which contains the Makefile fragment for this module.
* All C files you would like included.

Put the required build commands in ``micropython.mk``. For a simple module, you
will only have to add the file paths to ``SRC_USERMOD``, which will include
these C files in the build:

.. highlight:: make
.. code::

# Add all C files to SRC_USERMOD.
SRC_USERMOD += example/example.c

This is a very bare bones module named ``example`` that provides
``example.double(x)``. Note that the name of the module must be equal to the
directory name and is also used in the name of the ``mp_obj_module_t`` object at
the bottom.

.. highlight:: c
.. code::

// Include required definitions first.
#include "py/obj.h"
#include "py/runtime.h"

// This is the function you will call using example.double(n).
STATIC mp_obj_t example_double(mp_obj_t x_obj) {
// Check input value and convert it to a C type.
if (!MP_OBJ_IS_SMALL_INT(x_obj)) {
mp_raise_ValueError("x is not a small int");
}
int x = mp_obj_int_get_truncated(x_obj);

// Calculate the double, and convert back to MicroPython object.
return mp_obj_new_int(x + x);
}
STATIC MP_DEFINE_CONST_FUN_OBJ_1(example_double_obj, example_double);

// Define all properties of the example module, which currently are the name (a
// string) and a function.
// All identifiers and strings are written as MP_QSTR_xxx and will be
// optimized to word-sized integers by the build system (interned strings).
STATIC const mp_rom_map_elem_t example_module_globals_table[] = {
{ MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_example) },
{ MP_ROM_QSTR(MP_QSTR_double), MP_ROM_PTR(&example_double_obj) },
};
STATIC MP_DEFINE_CONST_DICT(example_module_globals, example_module_globals_table);

// Define module object.
const mp_obj_module_t example_user_cmodule = {
.base = { &mp_type_module },
.globals = (mp_obj_dict_t*)&example_module_globals,
};


Using a module
--------------

To build such a module, compile MicroPython (see `getting started
<https://github.com/micropython/micropython/wiki/Getting-Started>`_) with an
extra ``make`` flag named ``USER_C_MODULES`` set to the directory containing
all modules you want included (not to the module itself!). For example:

.. highlight:: shell
.. code::

$ make USER_C_MODULES=path-to-modules-folder all
12 changes: 12 additions & 0 deletions docs/develop/index.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
Developing and building MicroPython
===================================

This chapter contains some documentation for how to extend MicroPython. Note
that it doesn't aim to be a complete guide for developing with MicroPython, see
`this getting started guide
<https://github.com/micropython/micropython/wiki/Getting-Started>`_ for that.

.. toctree::
:maxdepth: 1

cmodules.rst
1 change: 1 addition & 0 deletions docs/esp8266_index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,5 @@ MicroPython documentation and references
library/index.rst
reference/index.rst
genrst/index.rst
develop/index.rst
license.rst
1 change: 1 addition & 0 deletions docs/pyboard_index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,5 @@ MicroPython documentation and references
library/index.rst
reference/index.rst
genrst/index.rst
develop/index.rst
license.rst
2 changes: 2 additions & 0 deletions docs/reference/speed_python.rst
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
.. _speed_python:

Maximising MicroPython Speed
============================

Expand Down
1 change: 1 addition & 0 deletions docs/unix_index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,5 @@ MicroPython documentation and references
library/index.rst
reference/index.rst
genrst/index.rst
develop/index.rst
license.rst
1 change: 1 addition & 0 deletions docs/wipy_index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,5 @@ MicroPython documentation and references
library/index.rst
reference/index.rst
genrst/index.rst
develop/index.rst
license.rst
2 changes: 2 additions & 0 deletions py/mkenv.mk
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ RM = rm
ECHO = @echo
CP = cp
MKDIR = mkdir
LN = ln
SED = sed
PYTHON = python

Expand All @@ -61,6 +62,7 @@ endif
MAKE_FROZEN = $(PYTHON) $(TOP)/tools/make-frozen.py
MPY_CROSS = $(TOP)/mpy-cross/mpy-cross
MPY_TOOL = $(PYTHON) $(TOP)/tools/mpy-tool.py
GEN_CMODULES = $(PYTHON) $(TOP)/tools/gen-cmodules.py

all:
.PHONY: all
Expand Down
11 changes: 10 additions & 1 deletion py/mkrules.mk
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ endif
ifneq ($(FROZEN_MPY_DIR),)
# to build the MicroPython cross compiler
$(TOP)/mpy-cross/mpy-cross: $(TOP)/py/*.[ch] $(TOP)/mpy-cross/*.[ch] $(TOP)/ports/windows/fmode.c
$(Q)$(MAKE) -C $(TOP)/mpy-cross
$(Q)$(MAKE) -C $(TOP)/mpy-cross USER_C_MODULES=

# make a list of all the .py files that need compiling and freezing
FROZEN_MPY_PY_FILES := $(shell find -L $(FROZEN_MPY_DIR) -type f -name '*.py' | $(SED) -e 's=^$(FROZEN_MPY_DIR)/==')
Expand All @@ -123,6 +123,15 @@ $(BUILD)/frozen_mpy.c: $(FROZEN_MPY_MPY_FILES) $(BUILD)/genhdr/qstrdefs.generate
$(Q)$(MPY_TOOL) -f -q $(BUILD)/genhdr/qstrdefs.preprocessed.h $(FROZEN_MPY_MPY_FILES) > $@
endif

# to build a list of modules for py/objmodule.c.
$(BUILD)/genhdr/cmodules.h: | $(HEADER_BUILD)/mpversion.h
@$(ECHO) "GEN $@"
$(Q)$(GEN_CMODULES) $(USER_C_MODULES) > $@

# make sure C modules are put in the build directory
$(BUILD)/cmodules:
@$(LN) -sf "$(realpath $(USER_C_MODULES))" $(BUILD)/cmodules

ifneq ($(PROG),)
# Build a standalone executable (unix does this)

Expand Down
9 changes: 9 additions & 0 deletions py/objmodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@
#include <stdlib.h>
#include <assert.h>

#ifdef MICROPY_CMODULES_INCLUDE_H
#include MICROPY_CMODULES_INCLUDE_H
#endif

#include "py/objmodule.h"
#include "py/runtime.h"
#include "py/builtin.h"
Expand Down Expand Up @@ -220,6 +224,11 @@ STATIC const mp_rom_map_elem_t mp_builtin_module_table[] = {

// extra builtin modules as defined by a port
MICROPY_PORT_BUILTIN_MODULES

#ifdef MICROPY_EXTRA_BUILTIN_MODULES
// extra builtin modules from USER_C_MODULES
MICROPY_EXTRA_BUILTIN_MODULES
#endif
};

MP_DEFINE_CONST_MAP(mp_builtin_module_map, mp_builtin_module_table);
Expand Down
11 changes: 10 additions & 1 deletion py/py.mk
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,15 @@ $(BUILD)/$(BTREE_DIR)/%.o: CFLAGS += -Wno-old-style-definition -Wno-sign-compare
$(BUILD)/extmod/modbtree.o: CFLAGS += $(BTREE_DEFS)
endif

# External modules written in C.
ifneq ($(USER_C_MODULES),)
CFLAGS_MOD += -DMICROPY_CMODULES_INCLUDE_H='"genhdr/cmodules.h"'
include $(USER_C_MODULES)/*/micropython.mk
SRC_MOD += $(addprefix $(BUILD)/cmodules/, $(SRC_USERMOD))
$(SRC_MOD) : | $(BUILD)/cmodules
SRC_QSTR += $(BUILD)/genhdr/cmodules.h
endif

# py object files
PY_CORE_O_BASENAME = $(addprefix py/,\
mpstate.o \
Expand Down Expand Up @@ -274,7 +283,7 @@ endif

# Sources that may contain qstrings
SRC_QSTR_IGNORE = py/nlr%
SRC_QSTR = $(SRC_MOD) $(filter-out $(SRC_QSTR_IGNORE),$(PY_CORE_O_BASENAME:.o=.c)) $(PY_EXTMOD_O_BASENAME:.o=.c)
SRC_QSTR += $(SRC_MOD) $(filter-out $(SRC_QSTR_IGNORE),$(PY_CORE_O_BASENAME:.o=.c)) $(PY_EXTMOD_O_BASENAME:.o=.c)

# Anything that depends on FORCE will be considered out-of-date
FORCE:
Expand Down
28 changes: 28 additions & 0 deletions tools/gen-cmodules.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
#!/usr/bin/env python

# Generate genhdr/cmodules.h for inclusion in py/objmodule.c.

from __future__ import print_function

import sys
import os
from glob import glob

def update_modules(path):
modules = []
for module in sorted(os.listdir(path)):
if not os.path.isfile('%s/%s/micropython.mk' % (path, module)):
continue # not a module
modules.append(module)

# Print header file for all external modules.
print('// Automatically generated by genmodules.py.\n')
for module in modules:
print('extern const struct _mp_obj_module_t %s_user_cmodule;' % module)
print('\n#define MICROPY_EXTRA_BUILTIN_MODULES \\')
for module in modules:
print(' { MP_ROM_QSTR(MP_QSTR_%s), MP_ROM_PTR(&%s_user_cmodule) }, \\' % (module, module))
print()

if __name__ == '__main__':
update_modules(sys.argv[1])