קובצי .mpy של MicroPython¶
MicroPython מגדירה את המושג של קובץ .mpy, שהוא פורמט מכל בינארי המכיל קוד מהודר מראש, וניתן לייבא אותו כמו מודול .py רגיל. את הקובץ foo.mpy ניתן לייבא באמצעות import foo, כל עוד foo.mpy נמצא בדרך הרגילה על ידי מנגנון הייבוא. בדרך כלל, כל ספרייה המופיעה ב-sys.path נסרקת לפי הסדר. בעת סריקת ספרייה מסוימת, מתבצע חיפוש תחילה אחר foo.py, ואם הוא לא נמצא אזי מתבצע חיפוש אחר foo.mpy, ואז החיפוש ממשיך לספרייה הבאה אם אף אחד מהם לא נמצא. לפיכך, ל-foo.py תהיה עדיפות על פני foo.mpy.
קובצי .mpy אלה יכולים להכיל bytecode המופק בדרך כלל מקובצי מקור של Python (קובצי .py) באמצעות התוכנית mpy-cross. עבור חלק מהארכיטקטורות קובץ .mpy יכול להכיל גם קוד מכונה נייטיב, שניתן להפיק במגוון דרכים, ובמיוחד מקוד מקור בשפת C.
המהדר mpy-cross¶
mpy-cross הוא המהדר הצולב שהופך קובץ מקור .py למכל בינארי .mpy המוכן לייבוא במצלמה. הוא חלק מעץ המקור של MicroPython (אותו עץ המשמש לבניית הקושחה של המצלמה) וגם מתפרסם כחבילת pip לשימוש בצד המארח ללא checkout מלא של הקושחה:
$ pip install --user mpy-cross
או באמצעות pipx:
$ pipx install mpy-cross
לאחר ההתקנה, הפעילו אותו על קובץ מקור בודד:
$ mpy-cross foo.py
זה מייצר את foo.mpy בספרייה הנוכחית, מוכן להעתקה אל מערכת הקבצים של המצלמה לצד מודולים אחרים או להזנה לתוך תמונת ROMFS.
אפשרויות שורת הפקודה השימושיות ביותר:
-o <path>– נתיב הפלט עבור ה-.mpyשנוצר (ברירת המחדל היא שם קובץ הקלט עם הסיומת המוחלפת;-o -כותב ל-stdout).-O<n>– רמת אופטימיזציה0עד3. ברירת המחדל0משמרת את ה-assertions ואת מיקומי המקור המלאים;3מסיר assertions ו-docstrings ומשכתב בלוקים שלif __debug__. הרמה שולטת באותו ממשקmicropython.opt_levelשזמן הריצה חושף.-march=<arch>– ארכיטקטורת היעד הנייטיב עבור פונקציות מעוטרות ב-@nativeוב-@viper. נדרש כאשר המקור משתמש בעטרים אלה. הערך חייב להתאים למחלקת ה-MCU של המצלמה: בחרו אותו מהרשימה ש-mpy-cross --helpמדפיס, או קראו אותו מהמצלמה בזמן הריצה באמצעותsys.implementation._mpy.-s <path>– מחרוזת נתיב המקור המוטמעת במידע ניפוי הבאגים של ה-.mpy. שימושי כאשר הנתיב בדיסק שונה מנתיב הייבוא שתחתיו הקובץ אמור להופיע ב-tracebacks.-X emit=bytecode|native|viper– בחרו את הפולט (emitter) שברירת המחדל עבור המודול כולו (חלופה ברמת הפונקציה לעטרים@native/@viper).--version– מדפיס את גרסת פורמט ה-.mpyשהבינארי הזה פולט. מספר זה חייב להתאים לגרסה שזמן הריצה של המצלמה תומך בה (ראו את טבלת הגרסאות שלהלן), אחרת הייבוא יעלהValueError('incompatible .mpy file').
הריצו mpy-cross --help לרשימת הדגלים המלאה.
חבילת ה-pip גם חושפת ממשק API קטן של מודול Python כך שסקריפטים של בנייה יכולים להפעיל את המהדר בתוך התהליך במקום ליצור תת-תהליך באופן ידני:
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 ו-mpy_cross.mpy_version הם שלוש נקודות הכניסה; mpy_cross.CrossCompileError נושא את ה-stderr של המהדר כאשר משהו משתבש. קבועי הארכיטקטורה (NATIVE_ARCH_ARMV7EMSP, NATIVE_ARCH_ARMV7EMDP וכו«) תואמים את המחרוזות שהדגל -march מקבל.
ניהול גרסאות ותאימות של קובצי .mpy¶
קובץ .mpy נתון עשוי להיות תואם או לא תואם למערכת MicroPython נתונה. התאימות מבוססת על הדברים הבאים:
גרסת קובץ ה-.mpy: גרסת הקובץ חייבת להתאים לגרסה הנתמכת על ידי המערכת הטוענת אותו.
תת-גרסת קובץ ה-.mpy: אם קובץ ה-.mpy מכיל קוד מכונה נייטיב אזי תת-הגרסה של הקובץ חייבת להתאים לגרסה הנתמכת על ידי המערכת הטוענת אותו. אחרת, אם אין קוד מכונה נייטיב בקובץ ה-.mpy, אזי תת-הגרסה מתעלמים ממנה בעת הטעינה.
ביטים של מספר שלם קטן: קובץ ה-.mpy ידרוש מספר מינימלי של ביטים ב-small integer, והמערכת הטוענת אותו חייבת לתמוך לפחות במספר ביטים זה.
ארכיטקטורה נייטיב: אם קובץ ה-.mpy מכיל קוד מכונה נייטיב אזי הוא יציין את הארכיטקטורה של קוד מכונה זה, והמערכת הטוענת אותו חייבת לתמוך בהרצת קוד של אותה ארכיטקטורה.
אם מערכת MicroPython תומכת בייבוא קובצי .mpy אזי השדה sys.implementation._mpy יתקיים ויחזיר מספר שלם המקודד את הגרסה (8 הביטים התחתונים), המאפיינים והארכיטקטורה הנייטיב.
ניסיון לייבא קובץ .mpy שנכשל באחד מארבעת הבדיקות הראשונות יעלה ValueError('incompatible .mpy file'). ניסיון לייבא קובץ .mpy שנכשל בבדיקת הארכיטקטורה הנייטיב (אם הוא מכיל קוד מכונה נייטיב) יעלה ValueError('incompatible .mpy arch').
אם ייבוא קובץ .mpy נכשל אזי נסו את הדברים הבאים:
קבעו את גרסת ה-.mpy ואת הדגלים הנתמכים על ידי מערכת MicroPython שלכם על ידי הרצת:
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()
בדקו את תקפות קובץ ה-.mpy על ידי בחינת שני הבייטים הראשונים של הקובץ. הבית הראשון אמור להיות »M« באות גדולה, והבית השני יהיה מספר הגרסה, שאמור להתאים לגרסת המערכת שלמעלה. אם הוא אינו תואם אזי בנו מחדש את קובץ ה-.mpy.
בדקו אם גרסת ה-.mpy של המערכת תואמת לגרסה שנפלטה על ידי
mpy-crossששימש לבניית קובץ ה-.mpy, הניתנת על ידיmpy-cross --version. אם היא אינה תואמת אזי הדרו מחדש אתmpy-crossממאגר ה-Git ש-checkout שלו בוצע ב-tag (או ה-hash) המדווח על ידיmpy-cross --version.ודאו שאתם משתמשים בדגלי
mpy-crossהנכונים, הניתנים על ידי הקוד שלמעלה, או על ידי בחינת משתנה ה-MakefileMPY_CROSS_FLAGSעבור ה-port שבו אתם משתמשים.אם הבית השלישי של קובץ ה-.mpy בעל ביט מס« 6 מוגדר, אזי בדקו אם ה-vuint של ביטי הדגל הספציפיים לארכיטקטורה המקודדים תואם ליעד שעליו אתם מייבאים את הקובץ.
הטבלה הבאה מציגה את ההתאמה בין גרסת ההפצה של MicroPython לבין גרסת ה-.mpy.
גרסת הפצה של MicroPython |
גרסת .mpy |
|---|---|
v1.23.0 ומעלה |
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 |
לשם השלמות, הטבלה הבאה מציגה את commit ה-Git של מאגר MicroPython הראשי שבו שונתה גרסת ה-.mpy.
שינוי גרסת .mpy |
commit של Git |
|---|---|
6.2 ל-6.3 |
bdbc869f9ea200c0d28b2bc7bfb60acd9d884e1b |
6.1 ל-6.2 |
6967ff3c581a66f73e9f3d78975f47528db39980 |
6 ל-6.1 |
d94141e1473aebae0d3c63aeaa8397651ad6fa01 |
5 ל-6 |
f2040bfc7ee033e48acef9f289790f3b4e6b74e5 |
4 ל-5 |
5716c5cf65e9b2cb46c2906f40302401bdd27517 |
3 ל-4 |
9a5f92ea72754c01cc03e5efcdfe94021120531e |
2 ל-3 |
ff93fd4f50321c6190e1659b19e64fef3045a484 |
1 ל-2 |
dd11af209d226b7d18d5148b239662e30ed60bad |
0 ל-1 |
6a11048af1d01c78bdacddadd1b72dc7ba7c6478 |
גרסה ראשונית 0 |
d8c834c95d506db979ec871417de90b7951edc30 |
קידוד בינארי של קובצי .mpy¶
קובצי .mpy של MicroPython הם פורמט מכל בינארי עם אובייקטי קוד (bytecode וקוד מכונה נייטיב) המאוחסנים פנימית בהיררכיה מקוננת. הקוד עבור המודול החיצוני מאוחסן ראשון, ולאחר מכן באים צאצאיו. לכל צאצא עשויים להיות צאצאים נוספים, למשל במקרה של מחלקה בעלת מתודות, או פונקציה המגדירה lambda או comprehension. כדי לשמור על קבצים קטנים תוך מתן טווח גדול של ערכים אפשריים, הוא משתמש במושג של מספר שלם לא-מסומן בקידוד משתנה (vuint) במקומות רבים. בדומה לקידוד UTF-8, קידוד זה מאחסן 7 ביטים לכל בית כאשר הביט ה-8 (MSB) מוגדר אם בית אחד או יותר באים אחריו. הביטים של המספר השלם הלא-מסומן מאוחסנים ב-vuint בצורת LSB.
הרמה העליונה של קובץ .mpy מורכבת משלושה חלקים:
הכותרת.
טבלאות ה-qstr הגלובליות וטבלאות הקבועים.
ה-raw-code עבור ה-scope החיצוני של המודול. scope חיצוני זה מורץ כאשר קובץ ה-.mpy מיובא.
ניתן לבחון את תוכן קובץ .mpy באמצעות mpy-tool.py, למשל (הרצה מהשורש של מאגר MicroPython הראשי):
$ ./tools/mpy-tool.py -xd myfile.mpy
הכותרת¶
כותרת ה-.mpy היא:
גודל |
שדה |
|---|---|
בית |
ערך 0x4d (ASCII »M«) |
בית |
מספר גרסה ראשי של .mpy |
בית |
דגלי מאפיינים, ארכיטקטורה נייטיב, מספר גרסה משני (היה דגלי מאפיינים בגרסאות ישנות יותר) |
בית |
מספר הביטים במספר שלם קטן |
הבית השלישי מחולק כדלקמן (MSB ראשון):
ביט |
משמעות |
|---|---|
7 |
שמור, חייב להיות 0 |
6 |
vuint של דגלים ספציפיים לארכיטקטורה בא אחרי הכותרת |
5..2 |
מספר ארכיטקטורה נייטיב |
1..0 |
מספר גרסה משני |
דגלים ספציפיים לארכיטקטורה¶
אם ביט מס« 6 של בית דגלי המאפיינים של הכותרת מוגדר, אזי vuint המכיל מידע אופציונלי הספציפי לארכיטקטורה יבוא אחרי הכותרת. תוכן מספר שלם זה תלוי באיזו ארכיטקטורה נייטיב הקובץ מיועד עבורה.
כיום נעשה בכך שימוש לאחסון אילו הרחבות מעבד RISC-V קובץ ה-MPY זקוק להן כדי לפעול כראוי מלבד I, M, C ו-Zicsr. גרסאות שונות של ArmV7 מזוהות לפי מספר הארכיטקטורה הנייטיב שלהן, אך שימוש חוזר במנגנון זה יסבך את העניינים עבור RV32 ו-RV64.
קובצי MPY המכוונים ל-RV32 או ל-RV64 שאינם זקוקים להרחבות מעבד מסוימות אינם צריכים לספק מספר שלם של דגלים (יחד עם הגדרת הביט המתאים בכותרת). היעדר ערך דגלים עבור קובצי MPY של RV32 ו-RV64 משמש לציון שאין צורך בהרחבות ספציפיות, וחוסך בית אחד בבינארי הפלט הסופי.
ראו גם את אפשרות שורת הפקודה -march-flags הן ב-mpy-tool.py והן ב-mpy-cross, ואת אפשרות שורת הפקודה --arch-flags ב-mpy_ld.py לקביעת ערך זה בעת יצירת קובצי MPY.
טבלאות ה-qstr הגלובליות וטבלאות הקבועים¶
קובץ .mpy מכיל טבלת qstr יחידה וטבלת אובייקטי קבועים יחידה. אלה גלובליות לקובץ ה-.mpy, ומופנים אליהן על ידי כל אובייקטי ה-raw-code המקוננים. טבלת ה-qstr ממפה מספר qstr פנימי (פנימי לקובץ ה-.mpy) למספר ה-qstr המומר של זמן הריצה שאליו קובץ ה-.mpy מיובא. זה מקשר את קובץ ה-.mpy עם שאר המערכת שבתוכה הוא מורץ. טבלת אובייקטי הקבועים מאוכלסת בהפניות לכל אובייקטי הקבועים שקובץ ה-.mpy זקוק להם.
גודל |
שדה |
|---|---|
vuint |
מספר ה-qstrs |
vuint |
מספר אובייקטי הקבועים |
… |
נתוני qstr |
… |
אובייקטי קבועים מקודדים |
רכיבי raw code¶
רכיב raw-code מכיל קוד, בין אם bytecode ובין אם קוד מכונה נייטיב. תוכנו הוא:
גודל |
שדה |
|---|---|
vuint |
סוג, גודל והאם יש רכיבי sub-raw-code |
… |
קוד (bytecode או קוד מכונה) |
vuint |
מספר רכיבי ה-sub-raw-code (רק אם שונה מאפס) |
… |
רכיבי sub-raw-code |
ה-vuint הראשון ברכיב raw-code מקודד את סוג הקוד המאוחסן ברכיב זה (שני הביטים הפחות משמעותיים), האם ל-raw-code זה יש צאצאים כלשהם (הביט השלישי הפחות משמעותי), ואת אורך הקוד שבא אחריו (כמות ה-RAM שיש להקצות עבורו).
לאחר ה-vuint בא הקוד עצמו. אלא אם סוג הקוד הוא קוד viper עם relocations, קוד זה הוא נתוני קבוע ואינו צריך שינוי.
אם ל-raw-code זה יש צאצאים כלשהם (כפי שמצוין על ידי ביט ב-vuint הראשון), לאחר הקוד בא vuint המונה את מספר רכיבי ה-sub-raw-code.
לבסוף, כל רכיבי ה-sub-raw-code מאוחסנים, באופן רקורסיבי.