Kode mesin native dalam file .mpy¶
Bagian ini menjelaskan cara membangun dan bekerja dengan file .mpy yang berisi kode mesin native dari bahasa selain Python. Ini memungkinkan Anda menulis kode dalam bahasa seperti C, mengompilasinya dan menautkannya ke dalam file .mpy, lalu mengimpor file ini seperti modul Python biasa. Ini dapat digunakan untuk mengimplementasikan fungsionalitas yang kritis terhadap performa, atau untuk menyertakan pustaka yang sudah ada yang ditulis dalam bahasa lain.
Salah satu keuntungan utama menggunakan file .mpy native adalah bahwa kode mesin native dapat diimpor oleh skrip secara dinamis, tanpa perlu membangun ulang firmware MicroPython utama. Ini berbeda dengan Modul C eksternal MicroPython yang juga memungkinkan mendefinisikan modul kustom dalam C tetapi harus dikompilasi ke dalam citra firmware utama.
Fokus di sini adalah pada penggunaan C untuk membangun modul native, tetapi pada prinsipnya bahasa apa pun yang dapat dikompilasi ke kode mesin mandiri dapat dimasukkan ke dalam file .mpy.
Modul .mpy native dibangun menggunakan alat mpy_ld.py, yang terdapat dalam direktori tools/ dari proyek. Alat ini mengambil sekumpulan file objek (.o files) dan menautkannya bersama untuk membuat file .mpy native. Ini membutuhkan CPython 3 dan pustaka pyelftools v0.25 atau lebih baru.
Fitur yang didukung dan keterbatasan¶
File .mpy dapat berisi bytecode MicroPython dan/atau kode mesin native. Jika berisi kode mesin native maka file .mpy memiliki arsitektur tertentu yang terkait dengannya. Arsitektur yang didukung saat ini adalah (ini adalah opsi yang valid untuk variabel ARCH, lihat di bawah):
x86(32 bit)x64(64 bit x86)armv6m(ARM Thumb, mis. Cortex-M0)armv7m(ARM Thumb 2, mis. Cortex-M3)armv7emsp(ARM Thumb 2, float presisi tunggal, mis. Cortex-M4F, Cortex-M7)armv7emdp(ARM Thumb 2, float presisi ganda, mis. Cortex-M7)xtensa(non-windowed, mis. ESP8266)xtensawin(windowed dengan ukuran window 8, mis. ESP32, ESP32S3)rv32imc(RISC-V 32 bit dengan instruksi terkompresi, mis. ESP32C3, ESP32C6)rv64imc(RISC-V 64 bit dengan instruksi terkompresi)
Jika platform yang dipilih mendukung flag arsitektur eksplisit dan Anda ingin membiarkan file .mpy keluaran membawa nilai flag tersebut, Anda harus meneruskannya ke variabel flag ARCH_FLAGS saat membangun file .mpy.
Saat mengompilasi dan menautkan file .mpy native, arsitektur harus dipilih dan file yang sesuai hanya dapat diimpor pada arsitektur tersebut (dan jika flag arsitektur ada, hanya jika cocok dengan kemampuan target). Untuk detail lebih lanjut tentang file .mpy lihat File .mpy MicroPython.
Kode native harus dikompilasi sebagai kode independen posisi (PIC) dan menggunakan tabel offset global (GOT), meskipun detailnya bervariasi dari satu arsitektur ke arsitektur lainnya. Saat mengimpor file .mpy dengan kode native, mekanisme impor mampu melakukan beberapa relokasi dasar kode native. Ini mencakup relokasi bagian teks, rodata, dan BSS.
Fitur yang didukung dari linker dan dynamic loader adalah:
kode yang dapat dieksekusi (teks)
data hanya-baca (rodata), termasuk string dan data konstan (array, struct, dll.)
data yang diinisialisasi nol (BSS)
pointer dalam teks ke teks, rodata, dan BSS
pointer dalam rodata ke teks, rodata, dan BSS
Keterbatasan yang diketahui adalah:
bagian data tidak didukung; solusi: gunakan data BSS dan inisialisasi nilai data secara eksplisit
variabel BSS statis tidak didukung; solusi: gunakan variabel BSS global
variabel penyimpanan thread-lokal tidak didukung pada rv32imc; solusi: gunakan variabel BSS global atau alokasikan ruang di heap untuk menyimpannya
Jadi, jika kode C Anda memiliki data yang dapat ditulis, pastikan data didefinisikan secara global, tanpa penginisialisasi, dan hanya ditulis di dalam fungsi.
Modul native tidak secara otomatis ditautkan terhadap pustaka statis standar seperti libm.a dan libgcc.a, yang dapat menyebabkan kesalahan undefined symbol. Anda dapat menautkan pustaka runtime dengan menetapkan LINK_RUNTIME = 1 di Makefile Anda. Pustaka statis kustom juga dapat ditautkan dengan menambahkan MPY_LD_FLAGS += -l path/to/library.a. Perhatikan bahwa ini ditautkan ke dalam modul native dan tidak akan dibagikan dengan modul lain atau sistem.
Keterbatasan linker: modul native tidak ditautkan terhadap tabel simbol firmware MicroPython penuh. Sebaliknya, ini ditautkan terhadap tabel eksplisit simbol yang diekspor yang ditemukan dalam mp_fun_table (di py/nativeglue.h), yang ditetapkan pada waktu pembangunan firmware. Oleh karena itu tidak mungkin untuk sekadar memanggil fungsi HAL/OS/RTOS/sistem tertentu, misalnya, kecuali jika berada di alamat tetap. Dalam hal tersebut, jalur dari linkerscript yang berisi serangkaian nama simbol dan alamat tetapnya dapat diteruskan ke mpy_ld.py melalui argumen baris perintah --externs. Dengan cara itu simbol yang muncul dalam linkerscript akan lebih diprioritaskan daripada yang disediakan dari file objek, tetapi saat ini implementasi file objek masih akan berada dalam file MPY akhir. Parser linkerscript terbatas kemampuannya, dan saat ini hanya digunakan untuk mengurai daftar simbol ROM port ESP8266 (lihat ports/esp8266/boards/eagle.rom.addr.v6.ld).
Simbol baru dapat ditambahkan ke akhir tabel dan firmware dibangun ulang. Simbol juga perlu ditambahkan ke dict fun_table milik tools/mpy_ld.py di lokasi yang sama. Ini memungkinkan mpy_ld.py untuk dapat mengambil simbol baru dan menyediakan relokasi untuk simbol tersebut saat mpy diimpor. Akhirnya, jika simbol adalah sebuah fungsi, makro atau stub harus ditambahkan ke py/dynruntime.h untuk memudahkan pemanggilan fungsi.
Mendefinisikan modul native¶
Modul .mpy native didefinisikan oleh sekumpulan file yang digunakan untuk membangun .mpy. Tata letak filesystem terdiri dari dua bagian utama, file sumber dan Makefile:
Dalam kasus paling sederhana hanya satu file sumber C yang diperlukan, yang berisi semua kode yang akan dikompilasi ke dalam modul .mpy. Kode sumber C ini harus menyertakan file
py/dynruntime.huntuk mengakses API dinamis MicroPython, dan setidaknya harus mendefinisikan sebuah fungsi bernamampy_init. Fungsi ini akan menjadi titik masuk modul, dipanggil saat modul diimpor.Modul dapat dibagi menjadi beberapa file sumber C jika diinginkan. Sebagian modul juga dapat diimplementasikan dalam Python. Semua file sumber harus dicantumkan dalam Makefile, dengan menambahkannya ke variabel
SRC(lihat di bawah). Ini mencakup file sumber C maupun file Python yang akan disertakan dalam file .mpy yang dihasilkan.Makefileberisi konfigurasi pembangunan untuk modul dan mencantumkan file sumber yang digunakan untuk membangun modul .mpy. Ini harus mendefinisikanMPY_DIRsebagai lokasi repositori MicroPython (untuk menemukan file header, fragmen Makefile yang relevan, dan alatmpy_ld.py),MODsebagai nama modul,SRCsebagai daftar file sumber, secara opsional menentukan arsitektur mesin viaARCH, beserta flag arsitektur mesin opsional yang ditentukan viaARCH_FLAGS, lalu menyertakanpy/dynruntime.mk.
Contoh minimal¶
Bagian ini menyediakan contoh yang sepenuhnya berfungsi dari modul sederhana bernama factorial. Modul ini menyediakan satu fungsi factorial.factorial(x) yang menghitung faktorial dari input dan mengembalikan hasilnya.
Tata letak direktori:
factorial/
├── factorial.c
└── Makefile
File factorial.c berisi:
// Include the header file to get access to the MicroPython API
#include "py/dynruntime.h"
// Helper function to compute factorial
static mp_int_t factorial_helper(mp_int_t x) {
if (x == 0) {
return 1;
}
return x * factorial_helper(x - 1);
}
// This is the function which will be called from Python, as factorial(x)
static mp_obj_t factorial(mp_obj_t x_obj) {
// Extract the integer from the MicroPython input object
mp_int_t x = mp_obj_get_int(x_obj);
// Calculate the factorial
mp_int_t result = factorial_helper(x);
// Convert the result to a MicroPython integer object and return it
return mp_obj_new_int(result);
}
// Define a Python reference to the function above
static MP_DEFINE_CONST_FUN_OBJ_1(factorial_obj, factorial);
// 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
// Make the function available in the module's namespace
mp_store_global(MP_QSTR_factorial, MP_OBJ_FROM_PTR(&factorial_obj));
// This must be last, it restores the globals dict
MP_DYNRUNTIME_INIT_EXIT
}
File Makefile berisi:
# Location of top-level MicroPython directory
MPY_DIR = ../../..
# Name of module
MOD = factorial
# Source files (.c or .py)
SRC = factorial.c
# Architecture to build for (x86, x64, armv6m, armv7m, xtensa, xtensawin, rv32imc, rv64imc)
ARCH = x64
# Include to get the rules for compiling and linking the module
include $(MPY_DIR)/py/dynruntime.mk
Mengompilasi modul¶
Alat prasyarat yang diperlukan untuk membangun file .mpy native adalah:
Repositori MicroPython (setidaknya direktori
py/dantools/).CPython 3, dan pustaka pyelftools (mis.
pip install 'pyelftools>=0.25').GNU make.
Kompilator C untuk arsitektur target (jika sumber C digunakan).
Opsional
mpy-cross, dibangun dari repositori MicroPython (jika sumber .py digunakan).
Pastikan untuk memilih ARCH yang benar untuk target yang akan dijalankan. Kemudian bangun dengan:
$ make
Tanpa memodifikasi Makefile Anda dapat menentukan arsitektur target via:
$ make ARCH=armv7m
Hal yang sama berlaku untuk flag arsitektur opsional via:
$ make ARCH=rv32imc ARCH_FLAGS=zba
Penggunaan modul dalam MicroPython¶
Setelah modul dibangun, seharusnya ada file bernama factorial.mpy. Salin file ini agar dapat diakses di filesystem sistem MicroPython Anda dan dapat ditemukan dalam jalur impor. Modul kini dapat diakses dalam Python seperti modul lainnya, contohnya:
import factorial
print(factorial.factorial(10))
# should display 3628800
Menggunakan Picolibc saat membangun modul¶
Menggunakan Picolibc sebagai pustaka standar C Anda tidak hanya didukung, tetapi sebenarnya ini adalah default untuk platform rv32imc dan rv64imc. Namun, ada beberapa hal yang perlu disebutkan untuk memastikan Anda tidak mengalami masalah nanti saat membangun kode.
Beberapa versi Picolibc yang sudah terpasang sebelumnya (misalnya, yang disediakan oleh Ubuntu Linux sebagai paket picolibc-arm-none-eabi, picolibc-riscv64-unknown-elf, dan picolibc-xtensa-lx106-elf) mengasumsikan penyimpanan thread-lokal (TLS) tersedia pada runtime, tetapi sayangnya modul MicroPython tidak mendukung hal itu pada beberapa arsitektur (yaitu rv32imc dan rv64imc). Ini berarti beberapa fungsionalitas yang disediakan oleh Picolibc akan default menggunakan TLS, mengembalikan kesalahan baik selama kompilasi maupun selama penautan.
Sebagai contoh bagaimana hal ini dapat memengaruhi Anda, modul contoh examples/natmod/btree berisi solusi untuk memastikan errno berfungsi (cari __PICOLIBC_ERRNO_FUNCTION di Makefile dan ikuti jejaknya dari sana).
Contoh lebih lanjut¶
Lihat examples/natmod/ untuk contoh lebih lanjut yang menunjukkan banyak fitur yang tersedia dari modul .mpy native. Fitur-fitur tersebut meliputi:
menggunakan beberapa file sumber C
menyertakan kode Python bersama kode C
data rodata dan BSS
alokasi memori
penggunaan floating point
penanganan pengecualian
menyertakan pustaka C eksternal