File .mpy MicroPython

MicroPython mendefinisikan konsep file .mpy yaitu format file kontainer biner yang menyimpan kode yang telah dikompilasi sebelumnya, dan dapat diimpor seperti modul .py biasa. File foo.mpy dapat diimpor melalui import foo, selama foo.mpy dapat ditemukan dengan cara yang biasa oleh mesin impor. Biasanya, setiap direktori yang tercantum dalam sys.path dicari secara berurutan. Saat mencari direktori tertentu, foo.py dicari terlebih dahulu, dan jika tidak ditemukan maka foo.mpy dicari, kemudian pencarian dilanjutkan ke direktori berikutnya jika keduanya tidak ditemukan. Dengan demikian, foo.py akan lebih diprioritaskan daripada foo.mpy.

File-file .mpy ini dapat berisi bytecode yang biasanya dihasilkan dari file sumber Python (file .py) melalui program mpy-cross. Untuk beberapa arsitektur, file .mpy juga dapat berisi kode mesin native, yang dapat dihasilkan dengan berbagai cara, terutama dari kode sumber C.

Kompiler mpy-cross

mpy-cross adalah kompiler silang yang mengubah file sumber .py menjadi kontainer biner .mpy yang siap diimpor pada kamera. Ini merupakan bagian dari pohon sumber MicroPython (yang sama digunakan untuk membangun firmware kamera) dan juga diterbitkan sebagai paket pip untuk penggunaan sisi host tanpa checkout firmware lengkap:

$ pip install --user mpy-cross

Atau melalui pipx:

$ pipx install mpy-cross

Setelah terinstal, jalankan pada satu file sumber:

$ mpy-cross foo.py

Ini menghasilkan foo.mpy di direktori saat ini, siap disalin ke sistem file kamera bersama modul lain atau untuk dimasukkan ke dalam citra ROMFS.

Opsi baris perintah yang paling berguna:

  • -o <path> -- jalur keluaran untuk .mpy yang dihasilkan (defaultnya adalah nama file masukan dengan ekstensi yang diganti; -o - menulis ke stdout).

  • -O<n> -- tingkat optimisasi 0 hingga 3. Default 0 mempertahankan pernyataan assert dan lokasi sumber penuh; 3 menghapus assert dan docstring serta menulis ulang blok if __debug__. Tingkat ini mengontrol permukaan micropython.opt_level yang sama yang diekspos oleh runtime.

  • -march=<arch> -- arsitektur native target untuk fungsi yang didekorasi @native dan @viper. Diperlukan saat sumber menggunakan dekorator tersebut. Nilainya harus cocok dengan kelas MCU kamera: pilih dari daftar yang dicetak oleh mpy-cross --help, atau baca dari kamera saat runtime dengan sys.implementation._mpy.

  • -s <path> -- string jalur sumber yang disematkan dalam info debug .mpy. Berguna ketika jalur pada disk berbeda dari jalur impor yang seharusnya ditampilkan file dalam traceback.

  • -X emit=bytecode|native|viper -- pilih emiter default untuk seluruh modul (alternatif per-fungsi dari dekorator @native / @viper).

  • --version -- mencetak versi format .mpy yang dipancarkan oleh biner ini. Angka tersebut harus cocok dengan versi yang didukung oleh runtime kamera (lihat tabel rilis di bawah) atau impor akan memunculkan ValueError('incompatible .mpy file').

Jalankan mpy-cross --help untuk daftar flag lengkap.

Paket pip juga mengekspos API modul Python kecil sehingga skrip build dapat menjalankan kompiler secara in-process alih-alih memforking subprocess secara manual:

import mpy_cross

mpy_cross.compile('foo.py', dest='build/foo.mpy', opt=3,
                  march=mpy_cross.NATIVE_ARCH_ARMV7EMSP)

mpy_cross.compile, mpy_cross.run, dan mpy_cross.mpy_version adalah tiga titik masuk; mpy_cross.CrossCompileError membawa stderr kompiler saat ada yang salah. Konstanta arsitektur (NATIVE_ARCH_ARMV7EMSP, NATIVE_ARCH_ARMV7EMDP, dll.) cocok dengan string yang diterima oleh flag -march.

Versi dan kompatibilitas file .mpy

File .mpy tertentu mungkin atau mungkin tidak kompatibel dengan sistem MicroPython tertentu. Kompatibilitas didasarkan pada hal-hal berikut:

  • Versi file .mpy: versi file harus cocok dengan versi yang didukung oleh sistem yang memuatnya.

  • Sub-versi file .mpy: jika file .mpy berisi kode mesin native maka sub-versi file harus cocok dengan versi yang didukung oleh sistem yang memuatnya. Jika tidak, jika tidak ada kode mesin native dalam file .mpy, maka sub-versi diabaikan saat pemuatan.

  • Bit bilangan bulat kecil: file .mpy akan membutuhkan jumlah bit minimum dalam small integer dan sistem yang memuatnya harus mendukung setidaknya sebanyak bit ini.

  • Arsitektur native: jika file .mpy berisi kode mesin native maka akan menentukan arsitektur kode mesin tersebut dan sistem yang memuatnya harus mendukung eksekusi kode arsitektur tersebut.

Jika sistem MicroPython mendukung impor file .mpy maka field sys.implementation._mpy akan ada dan mengembalikan bilangan bulat yang meng-encode versi (8 bit bawah), fitur, dan arsitektur native.

Mencoba mengimpor file .mpy yang gagal pada salah satu dari empat uji pertama akan memunculkan ValueError('incompatible .mpy file'). Mencoba mengimpor file .mpy yang gagal pada uji arsitektur native (jika berisi kode mesin native) akan memunculkan ValueError('incompatible .mpy arch').

Jika mengimpor file .mpy gagal, coba langkah-langkah berikut:

  • Tentukan versi .mpy dan flag yang didukung oleh sistem MicroPython Anda dengan mengeksekusi:

    import sys
    sys_mpy = sys.implementation._mpy
    arch = [None, 'x86', 'x64',
        'armv6', 'armv6m', 'armv7m', 'armv7em', 'armv7emsp', 'armv7emdp',
        'xtensa', 'xtensawin', 'rv32imc', 'rv64imc'][(sys_mpy >> 10) & 0x0F]
    print('mpy version:', sys_mpy & 0xff)
    print('mpy sub-version:', sys_mpy >> 8 & 3)
    print('mpy flags:', end='')
    if arch:
        print(' -march=' + arch, end='')
    if (sys_mpy >> 16) != 0:
        print(' -march-flags=' + (sys_mpy >> 16), end='')
    print()
    
  • Periksa validitas file .mpy dengan memeriksa dua byte pertama file. Byte pertama harus berupa huruf besar 'M' dan byte kedua akan menjadi nomor versi, yang harus cocok dengan versi sistem di atas. Jika tidak cocok maka bangun ulang file .mpy.

  • Periksa apakah versi .mpy sistem cocok dengan versi yang dipancarkan oleh mpy-cross yang digunakan untuk membangun file .mpy, yang ditemukan dengan mpy-cross --version. Jika tidak cocok maka kompilasi ulang mpy-cross dari repositori Git yang di-checkout pada tag (atau hash) yang dilaporkan oleh mpy-cross --version.

  • Pastikan Anda menggunakan flag mpy-cross yang benar, yang ditemukan oleh kode di atas, atau dengan memeriksa variabel Makefile MPY_CROSS_FLAGS untuk port yang Anda gunakan.

  • Jika byte ketiga dari file .mpy memiliki bit #6 yang diset, maka periksa apakah bit vuint flag khusus arsitektur yang di-encode kompatibel dengan target tempat Anda mengimpor file.

Tabel berikut menunjukkan korespondensi antara rilis MicroPython dan versi .mpy.

Rilis MicroPython

Versi .mpy

v1.23.0 dan ke atas

6.3

v1.22.x

6.2

v1.20 - v1.21.0

6.1

v1.19.x

6

v1.12 - v1.18

5

v1.11

4

v1.9.3 - v1.10

3

v1.9 - v1.9.2

2

v1.5.1 - v1.8.7

0

Untuk kelengkapan, tabel berikutnya menunjukkan commit Git dari repositori MicroPython utama saat versi .mpy diubah.

Perubahan versi .mpy

Commit Git

6.2 ke 6.3

bdbc869f9ea200c0d28b2bc7bfb60acd9d884e1b

6.1 ke 6.2

6967ff3c581a66f73e9f3d78975f47528db39980

6 ke 6.1

d94141e1473aebae0d3c63aeaa8397651ad6fa01

5 ke 6

f2040bfc7ee033e48acef9f289790f3b4e6b74e5

4 ke 5

5716c5cf65e9b2cb46c2906f40302401bdd27517

3 ke 4

9a5f92ea72754c01cc03e5efcdfe94021120531e

2 ke 3

ff93fd4f50321c6190e1659b19e64fef3045a484

1 ke 2

dd11af209d226b7d18d5148b239662e30ed60bad

0 ke 1

6a11048af1d01c78bdacddadd1b72dc7ba7c6478

versi awal 0

d8c834c95d506db979ec871417de90b7951edc30

Enkoding biner file .mpy

File .mpy MicroPython adalah format kontainer biner dengan objek kode (bytecode dan kode mesin native) yang disimpan secara internal dalam hierarki bersarang. Kode untuk modul luar disimpan terlebih dahulu, kemudian turunannya mengikuti. Setiap anak mungkin memiliki turunan lebih lanjut, misalnya dalam kasus kelas yang memiliki metode, atau fungsi yang mendefinisikan lambda atau comprehension. Untuk menjaga file tetap kecil sekaligus menyediakan rentang nilai yang besar, digunakan konsep variably-encoded-unsigned-integer (vuint) di banyak tempat. Mirip dengan enkoding UTF-8, enkoding ini menyimpan 7 bit per byte dengan bit ke-8 (MSB) diset jika satu atau lebih byte mengikuti. Bit-bit dari bilangan bulat tidak bertanda disimpan dalam vuint dalam bentuk LSB.

Tingkat atas file .mpy terdiri dari tiga bagian:

  • Header.

  • Tabel qstr global dan tabel konstanta.

  • Kode mentah untuk cakupan luar modul. Cakupan luar ini dieksekusi saat file .mpy diimpor.

Anda dapat memeriksa isi file .mpy dengan menggunakan mpy-tool.py, misalnya (dijalankan dari root repositori MicroPython utama):

$ ./tools/mpy-tool.py -xd myfile.mpy

Header

Header .mpy adalah:

ukuran

field

byte

nilai 0x4d (ASCII 'M')

byte

nomor versi mayor .mpy

byte

flag fitur, arsitektur native, nomor versi minor (sebelumnya berupa flag fitur pada versi lama)

byte

jumlah bit dalam small int

Byte ketiga dibagi sebagai berikut (MSB terlebih dahulu):

bit

makna

7

dicadangkan, harus 0

6

vuint flag khusus arsitektur mengikuti header

5..2

nomor arsitektur native

1..0

nomor versi minor

Flag khusus arsitektur

Jika bit #6 dari byte flag fitur header diset, maka vuint yang berisi informasi opsional khusus arsitektur akan mengikuti header. Isi bilangan bulat ini bergantung pada arsitektur native yang dimaksud oleh file.

Ini saat ini digunakan untuk menyimpan ekstensi prosesor RISC-V yang diperlukan file MPY untuk beroperasi dengan benar selain I, M, C, dan Zicsr. Berbagai varian ArmV7 diidentifikasi berdasarkan nomor arsitektur native-nya, tetapi menggunakan kembali mekanisme tersebut akan memperumit hal-hal untuk RV32 dan RV64.

File MPY yang menargetkan RV32 atau RV64 yang tidak memerlukan ekstensi prosesor tertentu tidak perlu menyediakan bilangan bulat flag (beserta pengaturan bit yang sesuai di header). Tidak adanya nilai flag untuk file MPY RV32 dan RV64 digunakan untuk menunjukkan bahwa tidak ada ekstensi khusus yang diperlukan, dan menghemat satu byte dalam output biner akhir.

Lihat juga opsi baris perintah -march-flags dalam mpy-tool.py maupun mpy-cross, dan opsi baris perintah --arch-flags dalam mpy_ld.py untuk mengatur nilai ini saat membuat file MPY.

Tabel qstr global dan tabel konstanta

File .mpy berisi satu tabel qstr, dan satu tabel objek konstanta. Keduanya bersifat global terhadap file .mpy, direferensikan oleh semua objek kode mentah bersarang. Tabel qstr memetakan nomor qstr internal (internal terhadap file .mpy) ke nomor qstr yang diselesaikan dari runtime tempat file .mpy diimpor. Ini menghubungkan file .mpy dengan sisa sistem tempatnya dieksekusi. Tabel objek konstanta diisi dengan referensi ke semua objek konstanta yang diperlukan file .mpy.

ukuran

field

vuint

jumlah qstr

vuint

jumlah objek konstanta

...

data qstr

...

objek konstanta yang di-encode

Elemen kode mentah

Elemen kode mentah berisi kode, baik bytecode maupun kode mesin native. Isinya adalah:

ukuran

field

vuint

tipe, ukuran, dan apakah ada elemen sub-kode-mentah

...

kode (bytecode atau kode mesin)

vuint

jumlah elemen sub-kode-mentah (hanya jika bukan nol)

...

elemen sub-kode-mentah

Vuint pertama dalam elemen kode mentah meng-encode tipe kode yang disimpan dalam elemen ini (dua bit paling tidak signifikan), apakah kode mentah ini memiliki turunan (bit ketiga paling tidak signifikan), dan panjang kode yang mengikutinya (jumlah RAM yang dialokasikan untuknya).

Mengikuti vuint adalah kode itu sendiri. Kecuali tipe kode adalah kode viper dengan relokasi, kode ini adalah data konstan dan tidak perlu dimodifikasi.

Jika kode mentah ini memiliki turunan (seperti yang ditunjukkan oleh bit dalam vuint pertama), setelah kode datang vuint yang menghitung jumlah elemen sub-kode-mentah.

Akhirnya semua elemen sub-kode-mentah disimpan secara rekursif.