ไฟล์ .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 สำหรับใช้งานฝั่งโฮสต์โดยไม่ต้องมีการเช็กเอาต์เฟิร์มแวร์แบบเต็ม:

$ 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 ยังเปิดเผย Python module API ขนาดเล็กเพื่อให้สคริปต์สร้างสามารถขับเคลื่อนคอมไพเลอร์ในกระบวนการแทนที่จะ fork subprocess ด้วยตนเอง:

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 ที่เช็กเอาต์ที่แท็ก (หรือแฮช) ที่รายงานโดย mpy-cross --version

  • ตรวจสอบให้แน่ใจว่าคุณใช้แฟล็ก mpy-cross ที่ถูกต้อง ซึ่งพบได้จากโค้ดด้านบน หรือโดยตรวจสอบตัวแปร Makefile MPY_CROSS_FLAGS สำหรับพอร์ตที่คุณใช้

  • หากไบต์ที่สามของไฟล์ .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

เพื่อความสมบูรณ์ ตารางถัดไปแสดง Git commit ของที่เก็บ MicroPython หลักซึ่งเวอร์ชัน .mpy ถูกเปลี่ยนแปลง

การเปลี่ยนแปลงเวอร์ชัน .mpy

Git commit

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 ประกอบด้วยสามส่วน:

  • ส่วนหัว (header)

  • ตาราง qstr และค่าคงที่ส่วนกลาง

  • raw-code สำหรับขอบเขตภายนอกของโมดูล ขอบเขตภายนอกนี้จะถูกรันเมื่อนำเข้าไฟล์ .mpy

คุณสามารถตรวจสอบเนื้อหาของไฟล์ .mpy โดยใช้ mpy-tool.py ตัวอย่างเช่น (รันจากรากของที่เก็บ MicroPython หลัก):

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

ส่วนหัว (header)

ส่วนหัว .mpy คือ:

ขนาด

ฟิลด์

ไบต์

ค่า 0x4d (ASCII 'M')

ไบต์

หมายเลขเวอร์ชันหลัก .mpy

ไบต์

แฟล็กลักษณะเด่น, arch เนทีฟ, หมายเลขเวอร์ชันรอง (เดิมเป็นแฟล็กลักษณะเด่นในเวอร์ชันเก่า)

ไบต์

จำนวนบิตใน small int

ไบต์ที่สามถูกแบ่งดังนี้ (MSB ก่อน):

บิต

ความหมาย

7

สำรอง ต้องเป็น 0

6

vuint ที่มีแฟล็กเฉพาะสถาปัตยกรรมตามหลังส่วนหัว

5..2

หมายเลข arch เนทีฟ

1..0

หมายเลขเวอร์ชันรอง

แฟล็กเฉพาะสถาปัตยกรรม

หากบิต #6 ของไบต์แฟล็กลักษณะเด่นของส่วนหัวถูกตั้งค่า vuint ที่บรรจุข้อมูลเฉพาะสถาปัตยกรรมแบบเสริมจะตามหลังส่วนหัว เนื้อหาของจำนวนเต็มนี้ขึ้นอยู่กับสถาปัตยกรรมเนทีฟที่ไฟล์มุ่งหมายสำหรับ

ปัจจุบันใช้เพื่อเก็บว่าส่วนขยายโปรเซสเซอร์ RISC-V ใดที่ไฟล์ MPY ต้องการเพื่อทำงานได้อย่างถูกต้องนอกเหนือจาก I, M, C และ Zicsr ArmV7 รสชาติต่าง ๆ ถูกระบุโดยหมายเลข arch เนทีฟของพวกมัน แต่การนำกลไกนั้นมาใช้ซ้ำจะทำให้สิ่งต่าง ๆ ซับซ้อนขึ้นสำหรับ 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 code ที่มีการย้ายที่อยู่ โค้ดนี้เป็นข้อมูลคงที่และไม่จำเป็นต้องถูกแก้ไข

หาก raw-code นี้มีลูก (ตามที่ระบุโดยบิตใน vuint แรก) ตาม vuint มาเป็น vuint ที่นับจำนวนองค์ประกอบ sub-raw-code

สุดท้ายองค์ประกอบ sub-raw-code ใด ๆ จะถูกเก็บแบบเรียกซ้ำ