Skip to content

Commit d94141e

Browse files
jimmodpgeorge
authored andcommitted
py/persistentcode: Introduce .mpy sub-version.
The intent is to allow us to make breaking changes to the native ABI (e.g. changes to dynruntime.h) without needing the bytecode version to increment. With this commit the two bits previously used for the feature flags (but now unused as of .mpy version 6) encode a sub-version. A bytecode-only .mpy file can be loaded as long as MPY_VERSION matches, but a native .mpy (i.e. one with an arch set) must also match MPY_SUB_VERSION. This allows 3 additional updates to the native ABI per bytecode revision. The sub-version is set to 1 because the previous commits that changed the layout of mp_obj_type_t have changed the native ABI. Signed-off-by: Jim Mussared <jim.mussared@gmail.com> Signed-off-by: Damien George <damien@micropython.org>
1 parent b41aaaa commit d94141e

File tree

8 files changed

+46
-27
lines changed

8 files changed

+46
-27
lines changed

docs/reference/mpyfiles.rst

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,11 @@ Compatibility is based on the following:
2727
* Version of the .mpy file: the version of the file must match the version
2828
supported by the system loading it.
2929

30+
* Sub-version of the .mpy file: if the .mpy file contains native machine code
31+
then the sub-version of the file must match the version support by the
32+
system loading it. Otherwise, if there is no native machine code in the .mpy
33+
file, then the sub-version is ignored when loading.
34+
3035
* Small integer bits: the .mpy file will require a minimum number of bits in
3136
a small integer and the system loading it must support at least this many
3237
bits.
@@ -55,6 +60,7 @@ If importing an .mpy file fails then try the following:
5560
'armv6', 'armv6m', 'armv7m', 'armv7em', 'armv7emsp', 'armv7emdp',
5661
'xtensa', 'xtensawin'][sys_mpy >> 10]
5762
print('mpy version:', sys_mpy & 0xff)
63+
print('mpy sub-version:', sys_mpy >> 8 & 3)
5864
print('mpy flags:', end='')
5965
if arch:
6066
print(' -march=' + arch, end='')

mpy-cross/main.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -228,7 +228,7 @@ MP_NOINLINE int main_(int argc, char **argv) {
228228
a += 1;
229229
} else if (strcmp(argv[a], "--version") == 0) {
230230
printf("MicroPython " MICROPY_GIT_TAG " on " MICROPY_BUILD_DATE
231-
"; mpy-cross emitting mpy v" MP_STRINGIFY(MPY_VERSION) "\n");
231+
"; mpy-cross emitting mpy v" MP_STRINGIFY(MPY_VERSION) "." MP_STRINGIFY(MPY_SUB_VERSION) "\n");
232232
return 0;
233233
} else if (strcmp(argv[a], "-v") == 0) {
234234
mp_verbose_flag++;

py/persistentcode.c

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -393,14 +393,14 @@ STATIC mp_raw_code_t *load_raw_code(mp_reader_t *reader, mp_module_context_t *co
393393
mp_compiled_module_t mp_raw_code_load(mp_reader_t *reader, mp_module_context_t *context) {
394394
byte header[4];
395395
read_bytes(reader, header, sizeof(header));
396+
byte arch = MPY_FEATURE_DECODE_ARCH(header[2]);
396397
if (header[0] != 'M'
397398
|| header[1] != MPY_VERSION
398-
|| MPY_FEATURE_DECODE_FLAGS(header[2]) != MPY_FEATURE_FLAGS
399+
|| (arch != MP_NATIVE_ARCH_NONE && MPY_FEATURE_DECODE_SUB_VERSION(header[2]) != MPY_SUB_VERSION)
399400
|| header[3] > MP_SMALL_INT_BITS) {
400401
mp_raise_ValueError(MP_ERROR_TEXT("incompatible .mpy file"));
401402
}
402403
if (MPY_FEATURE_DECODE_ARCH(header[2]) != MP_NATIVE_ARCH_NONE) {
403-
byte arch = MPY_FEATURE_DECODE_ARCH(header[2]);
404404
if (!MPY_FEATURE_ARCH_TEST(arch)) {
405405
if (MPY_FEATURE_ARCH_TEST(MP_NATIVE_ARCH_NONE)) {
406406
// On supported ports this can be resolved by enabling feature, eg
@@ -596,7 +596,7 @@ void mp_raw_code_save(mp_compiled_module_t *cm, mp_print_t *print) {
596596
byte header[4] = {
597597
'M',
598598
MPY_VERSION,
599-
MPY_FEATURE_ENCODE_FLAGS(MPY_FEATURE_FLAGS_DYNAMIC),
599+
MPY_FEATURE_ENCODE_SUB_VERSION(MPY_SUB_VERSION),
600600
#if MICROPY_DYNAMIC_COMPILER
601601
mp_dynamic_compiler.small_int_bits,
602602
#else

py/persistentcode.h

Lines changed: 11 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -30,24 +30,23 @@
3030
#include "py/reader.h"
3131
#include "py/emitglue.h"
3232

33-
// The current version of .mpy files
33+
// The current version of .mpy files. A bytecode-only .mpy file can be loaded
34+
// as long as MPY_VERSION matches, but a native .mpy (i.e. one with an arch
35+
// set) must also match MPY_SUB_VERSION. This allows 3 additional updates to
36+
// the native ABI per bytecode revision.
3437
#define MPY_VERSION 6
38+
#define MPY_SUB_VERSION 1
3539

36-
// Macros to encode/decode flags to/from the feature byte
37-
#define MPY_FEATURE_ENCODE_FLAGS(flags) (flags)
38-
#define MPY_FEATURE_DECODE_FLAGS(feat) ((feat) & 3)
40+
// Macros to encode/decode sub-version to/from the feature byte. This replaces
41+
// the bits previously used to encode the flags (map caching and unicode)
42+
// which are no longer used starting at .mpy version 6.
43+
#define MPY_FEATURE_ENCODE_SUB_VERSION(version) (version)
44+
#define MPY_FEATURE_DECODE_SUB_VERSION(feat) ((feat) & 3)
3945

4046
// Macros to encode/decode native architecture to/from the feature byte
4147
#define MPY_FEATURE_ENCODE_ARCH(arch) ((arch) << 2)
4248
#define MPY_FEATURE_DECODE_ARCH(feat) ((feat) >> 2)
4349

44-
// The feature flag bits encode the compile-time config options that affect
45-
// the generate bytecode. Note: no longer used.
46-
// (formerly MICROPY_OPT_CACHE_MAP_LOOKUP_IN_BYTECODE and MICROPY_PY_BUILTINS_STR_UNICODE).
47-
#define MPY_FEATURE_FLAGS (0)
48-
// This is a version of the flags that can be configured at runtime.
49-
#define MPY_FEATURE_FLAGS_DYNAMIC (0)
50-
5150
// Define the host architecture
5251
#if MICROPY_EMIT_X86
5352
#define MPY_FEATURE_ARCH (MP_NATIVE_ARCH_X86)
@@ -82,7 +81,7 @@
8281

8382
// 16-bit little-endian integer with the second and third bytes of supported .mpy files
8483
#define MPY_FILE_HEADER_INT (MPY_VERSION \
85-
| (MPY_FEATURE_ENCODE_FLAGS(MPY_FEATURE_FLAGS) | MPY_FEATURE_ENCODE_ARCH(MPY_FEATURE_ARCH)) << 8)
84+
| (MPY_FEATURE_ENCODE_SUB_VERSION(MPY_SUB_VERSION) | MPY_FEATURE_ENCODE_ARCH(MPY_FEATURE_ARCH)) << 8)
8685

8786
enum {
8887
MP_NATIVE_ARCH_NONE = 0,

tests/micropython/import_mpy_native.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@
1111
raise SystemExit
1212

1313
mpy_arch = usys.implementation._mpy >> 8
14-
if mpy_arch == 0:
14+
if mpy_arch >> 2 == 0:
15+
# This system does not support .mpy files containing native code
1516
print("SKIP")
1617
raise SystemExit
1718

@@ -54,8 +55,8 @@ def open(self, path, mode):
5455
valid_header = bytes([77, 6, mpy_arch, 31])
5556
# fmt: off
5657
user_files = {
57-
# bad architecture
58-
'/mod0.mpy': b'M\x06\xfc\x1f',
58+
# bad architecture (mpy_arch needed for sub-version)
59+
'/mod0.mpy': bytes([77, 6, 0xfc | mpy_arch, 31]),
5960

6061
# test loading of viper and asm
6162
'/mod1.mpy': valid_header + (

tests/micropython/import_mpy_native_gc.py

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -46,24 +46,26 @@ def open(self, path, mode):
4646

4747

4848
# Pre-compiled examples/natmod/features0 example for various architectures, keyed
49-
# by the required value of sys.implementation._mpy.
49+
# by the required value of sys.implementation._mpy (without sub-version).
5050
features0_file_contents = {
5151
# -march=x64
52-
0x806: b'M\x06\x08\x1f\x02\x004build/features0.native.mpy\x00\x12factorial\x00\x8a\x02\xe9/\x00\x00\x00SH\x8b\x1d\x83\x00\x00\x00\xbe\x02\x00\x00\x00\xffS\x18\xbf\x01\x00\x00\x00H\x85\xc0u\x0cH\x8bC \xbe\x02\x00\x00\x00[\xff\xe0H\x0f\xaf\xf8H\xff\xc8\xeb\xe6ATUSH\x8b\x1dQ\x00\x00\x00H\x8bG\x08L\x8bc(H\x8bx\x08A\xff\xd4H\x8d5+\x00\x00\x00H\x89\xc5H\x8b\x059\x00\x00\x00\x0f\xb7x\x02\xffShH\x89\xefA\xff\xd4H\x8b\x03[]A\\\xc3\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x05\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x10\x11$\r&\xa3 \x01"\xff',
52+
0x806: b'M\x06\x09\x1f\x02\x004build/features0.native.mpy\x00\x12factorial\x00\x8a\x02\xe9/\x00\x00\x00SH\x8b\x1d\x83\x00\x00\x00\xbe\x02\x00\x00\x00\xffS\x18\xbf\x01\x00\x00\x00H\x85\xc0u\x0cH\x8bC \xbe\x02\x00\x00\x00[\xff\xe0H\x0f\xaf\xf8H\xff\xc8\xeb\xe6ATUSH\x8b\x1dQ\x00\x00\x00H\x8bG\x08L\x8bc(H\x8bx\x08A\xff\xd4H\x8d5+\x00\x00\x00H\x89\xc5H\x8b\x059\x00\x00\x00\x0f\xb7x\x02\xffShH\x89\xefA\xff\xd4H\x8b\x03[]A\\\xc3\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x05\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x10\x11$\r&\xa3 \x01"\xff',
5353
# -march=armv6m
54-
0x1006: b"M\x06\x10\x1f\x02\x004build/features0.native.mpy\x00\x12factorial\x00\x88\x02\x18\xe0\x00\x00\x10\xb5\tK\tJ{D\x9cX\x02!\xe3h\x98G\x03\x00\x01 \x00+\x02\xd0XC\x01;\xfa\xe7\x02!#i\x98G\x10\xbd\xc0Fj\x00\x00\x00\x00\x00\x00\x00\xf8\xb5\nN\nK~D\xf4XChgiXh\xb8G\x05\x00\x07K\x08I\xf3XyDX\x88ck\x98G(\x00\xb8G h\xf8\xbd\xc0F:\x00\x00\x00\x00\x00\x00\x00\x04\x00\x00\x00\x1e\x00\x00\x00\x00\x00\x00\x00\x05\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x10\x11<\r>\xa38\x01:\xff",
54+
0x1006: b"M\x06\x11\x1f\x02\x004build/features0.native.mpy\x00\x12factorial\x00\x88\x02\x18\xe0\x00\x00\x10\xb5\tK\tJ{D\x9cX\x02!\xe3h\x98G\x03\x00\x01 \x00+\x02\xd0XC\x01;\xfa\xe7\x02!#i\x98G\x10\xbd\xc0Fj\x00\x00\x00\x00\x00\x00\x00\xf8\xb5\nN\nK~D\xf4XChgiXh\xb8G\x05\x00\x07K\x08I\xf3XyDX\x88ck\x98G(\x00\xb8G h\xf8\xbd\xc0F:\x00\x00\x00\x00\x00\x00\x00\x04\x00\x00\x00\x1e\x00\x00\x00\x00\x00\x00\x00\x05\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x10\x11<\r>\xa38\x01:\xff",
5555
}
5656

5757
# Populate armv7m-derived archs based on armv6m.
5858
for arch in (0x1406, 0x1806, 0x1C06, 0x2006):
5959
features0_file_contents[arch] = features0_file_contents[0x1006]
6060

61-
if sys.implementation._mpy not in features0_file_contents:
61+
# Check that a .mpy exists for the target (ignore sub-version in lookup).
62+
sys_implementation_mpy = sys.implementation._mpy & ~(3 << 8)
63+
if sys_implementation_mpy not in features0_file_contents:
6264
print("SKIP")
6365
raise SystemExit
6466

6567
# These are the test .mpy files.
66-
user_files = {"/features0.mpy": features0_file_contents[sys.implementation._mpy]}
68+
user_files = {"/features0.mpy": features0_file_contents[sys_implementation_mpy]}
6769

6870
# Create and mount a user filesystem.
6971
uos.mount(UserFS(user_files), "/userfs")

tools/mpy-tool.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@ def __str__(self):
8888

8989
class Config:
9090
MPY_VERSION = 6
91+
MPY_SUB_VERSION = 1
9192
MICROPY_LONGINT_IMPL_NONE = 0
9293
MICROPY_LONGINT_IMPL_LONGLONG = 1
9394
MICROPY_LONGINT_IMPL_MPZ = 2
@@ -1335,6 +1336,9 @@ def read_mpy(filename):
13351336
feature_byte = header[2]
13361337
mpy_native_arch = feature_byte >> 2
13371338
if mpy_native_arch != MP_NATIVE_ARCH_NONE:
1339+
mpy_sub_version = feature_byte & 3
1340+
if mpy_sub_version != config.MPY_SUB_VERSION:
1341+
raise MPYReadError(filename, "incompatible .mpy sub-version")
13381342
if config.native_arch == MP_NATIVE_ARCH_NONE:
13391343
config.native_arch = mpy_native_arch
13401344
elif config.native_arch != mpy_native_arch:
@@ -1658,7 +1662,9 @@ def merge_mpy(compiled_modules, output_file):
16581662
else:
16591663
main_cm_idx = None
16601664
for idx, cm in enumerate(compiled_modules):
1661-
if cm.header[2]:
1665+
feature_byte = cm.header[2]
1666+
mpy_native_arch = feature_byte >> 2
1667+
if mpy_native_arch:
16621668
# Must use qstr_table and obj_table from this raw_code
16631669
if main_cm_idx is not None:
16641670
raise Exception("can't merge files when more than one contains native code")
@@ -1670,7 +1676,7 @@ def merge_mpy(compiled_modules, output_file):
16701676
header = bytearray(4)
16711677
header[0] = ord("M")
16721678
header[1] = config.MPY_VERSION
1673-
header[2] = config.native_arch << 2
1679+
header[2] = config.native_arch << 2 | config.MPY_SUB_VERSION
16741680
header[3] = config.mp_small_int_bits
16751681
merged_mpy.extend(header)
16761682

tools/mpy_ld.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636

3737
# MicroPython constants
3838
MPY_VERSION = 6
39+
MPY_SUB_VERSION = 1
3940
MP_CODE_BYTECODE = 2
4041
MP_CODE_NATIVE_VIPER = 4
4142
MP_NATIVE_ARCH_X86 = 1
@@ -917,7 +918,11 @@ def build_mpy(env, entry_offset, fmpy, native_qstr_vals, native_qstr_objs):
917918
out.open(fmpy)
918919

919920
# MPY: header
920-
out.write_bytes(bytearray([ord("M"), MPY_VERSION, env.arch.mpy_feature, MP_SMALL_INT_BITS]))
921+
out.write_bytes(
922+
bytearray(
923+
[ord("M"), MPY_VERSION, env.arch.mpy_feature | MPY_SUB_VERSION, MP_SMALL_INT_BITS]
924+
)
925+
)
921926

922927
# MPY: n_qstr
923928
out.write_uint(1 + len(native_qstr_vals))

0 commit comments

Comments
 (0)