From 14531d3634c688ceef6cc6034eb4762b48b85b50 Mon Sep 17 00:00:00 2001 From: Armin Brauns Date: Fri, 14 Jul 2023 10:06:27 +0000 Subject: [PATCH 1/5] tests: Regenerate frozentest.mpy. Generated using: make -C mpy-cross clean && make -C mpy-cross cd tests/frozen/ ../../mpy-cross/build/mpy-cross frozentest.py For some reason, ordering of interned strings has changed, and the file still used the old "feature flags" in the header instead of the native arch and subversion combination. Signed-off-by: Armin Brauns --- tests/frozen/frozentest.mpy | Bin 196 -> 196 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/tests/frozen/frozentest.mpy b/tests/frozen/frozentest.mpy index 99581617ac3d13e416722a63852da14088ff8db7..9cef1a2f12a2dabdd3ba6a28d5f675a559b85ccc 100644 GIT binary patch delta 98 zcmX@Yc!W{bmyJQ5m02pSD8DK-uOzj&M6aNdfuF53ppv1ne4=c&4YL4~31g%nvoZsO mVuVl-L8V>qXNdv@8k~H>#aINH1zCWc#;4&e*PZnv1Cs%p3>YN< delta 98 zcmX@Yc!W{bmyJoDm02pSD8DK-uOzj&M6aNdfxoewtu&yLVWMod4U+(~31g%nlQIK@ nVuVmkU`lD%`&ptuR)dpIxEPBdlOPL_)A%&J<+`(eWMDD?omLnk From 83db674e971eb89450455398b8e4c86ae8a5e944 Mon Sep 17 00:00:00 2001 From: Armin Brauns Date: Thu, 13 Jul 2023 14:39:04 +0000 Subject: [PATCH 2/5] py/objint: Add mp_obj_int_max_bytes_needed_impl(). This allows users of mp_obj_int_to_bytes_impl() to allocate a buffer of the correct size. Signed-off-by: Armin Brauns --- py/objint.h | 1 + py/objint_longlong.c | 5 +++++ py/objint_mpz.c | 7 +++++++ 3 files changed, 13 insertions(+) diff --git a/py/objint.h b/py/objint.h index 5eed87705dedb..8919bd07ba234 100644 --- a/py/objint.h +++ b/py/objint.h @@ -55,6 +55,7 @@ char *mp_obj_int_formatted_impl(char **buf, size_t *buf_size, size_t *fmt_size, int base, const char *prefix, char base_char, char comma); mp_int_t mp_obj_int_hash(mp_obj_t self_in); mp_obj_t mp_obj_int_from_bytes_impl(bool big_endian, size_t len, const byte *buf); +size_t mp_obj_int_max_bytes_needed_impl(mp_obj_t self_in); void mp_obj_int_to_bytes_impl(mp_obj_t self_in, bool big_endian, size_t len, byte *buf); int mp_obj_int_sign(mp_obj_t self_in); mp_obj_t mp_obj_int_unary_op(mp_unary_op_t op, mp_obj_t o_in); diff --git a/py/objint_longlong.c b/py/objint_longlong.c index ee499e0265b32..9e737663a372c 100644 --- a/py/objint_longlong.c +++ b/py/objint_longlong.c @@ -57,6 +57,11 @@ mp_obj_t mp_obj_int_from_bytes_impl(bool big_endian, size_t len, const byte *buf return mp_obj_new_int_from_ll(value); } +size_t mp_obj_int_max_bytes_needed_impl(mp_obj_t self_in) { + assert(mp_obj_is_exact_type(self_in, &mp_type_int)); + return sizeof(mp_longint_impl_t); +} + void mp_obj_int_to_bytes_impl(mp_obj_t self_in, bool big_endian, size_t len, byte *buf) { assert(mp_obj_is_exact_type(self_in, &mp_type_int)); mp_obj_int_t *self = self_in; diff --git a/py/objint_mpz.c b/py/objint_mpz.c index 8078441d66a0b..a9f79bf1a67e9 100644 --- a/py/objint_mpz.c +++ b/py/objint_mpz.c @@ -112,6 +112,13 @@ mp_obj_t mp_obj_int_from_bytes_impl(bool big_endian, size_t len, const byte *buf return MP_OBJ_FROM_PTR(o); } +size_t mp_obj_int_max_bytes_needed_impl(mp_obj_t self_in) { + assert(mp_obj_is_exact_type(self_in, &mp_type_int)); + mp_obj_int_t *self = MP_OBJ_TO_PTR(self_in); + // two's complement may require one more bit than the magnitude itself + return (mpz_max_num_bits(&self->mpz) + 1 + 7) / 8; +} + void mp_obj_int_to_bytes_impl(mp_obj_t self_in, bool big_endian, size_t len, byte *buf) { assert(mp_obj_is_exact_type(self_in, &mp_type_int)); mp_obj_int_t *self = MP_OBJ_TO_PTR(self_in); From 4e53e49d7ea7d323ef15c6d3ef95dcfe0cd63e50 Mon Sep 17 00:00:00 2001 From: Armin Brauns Date: Mon, 17 Jul 2023 12:21:19 +0000 Subject: [PATCH 3/5] py/mpz: Allow reading signed bigint from bytes. Previously, the bytes would always be interpreted as unsigned. Signed-off-by: Armin Brauns --- py/mpz.c | 30 ++++++++++++++++++++++++------ py/mpz.h | 2 +- py/objint.c | 2 +- py/objint.h | 2 +- py/objint_longlong.c | 2 +- py/objint_mpz.c | 4 ++-- 6 files changed, 30 insertions(+), 12 deletions(-) diff --git a/py/mpz.c b/py/mpz.c index b61997e2fd4ed..65bb3af182c66 100644 --- a/py/mpz.c +++ b/py/mpz.c @@ -850,25 +850,43 @@ size_t mpz_set_from_str(mpz_t *z, const char *str, size_t len, bool neg, unsigne return cur - str; } -void mpz_set_from_bytes(mpz_t *z, bool big_endian, size_t len, const byte *buf) { +void mpz_set_from_bytes(mpz_t *z, bool is_signed, bool big_endian, size_t len, const byte *buf) { int delta = 1; if (big_endian) { + z->neg = is_signed && (buf[0] & 0x80); buf += len - 1; delta = -1; + } else { + z->neg = is_signed && (buf[len - 1] & 0x80); } mpz_need_dig(z, (len * 8 + DIG_SIZE - 1) / DIG_SIZE); mpz_dig_t d = 0; + byte carry = 1; int num_bits = 0; - z->neg = 0; z->len = 0; while (len) { - while (len && num_bits < DIG_SIZE) { - d |= *buf << num_bits; + while (num_bits < DIG_SIZE) { + byte b; + if (len) { + b = *buf; + buf += delta; + len--; + } else if (z->neg) { + // sign-extend missing bytes + b = 0xff; + } else { + b = 0; + } + + if (z->neg) { + b = (~b) + carry; + carry &= b == 0; + } + + d |= b << num_bits; num_bits += 8; - buf += delta; - len--; } z->dig[z->len++] = d & DIG_MASK; // Need this #if because it's C undefined behavior to do: uint32_t >> 32 diff --git a/py/mpz.h b/py/mpz.h index d27f5724047ae..b266bd9817ead 100644 --- a/py/mpz.h +++ b/py/mpz.h @@ -114,7 +114,7 @@ void mpz_set_from_ll(mpz_t *z, long long i, bool is_signed); void mpz_set_from_float(mpz_t *z, mp_float_t src); #endif size_t mpz_set_from_str(mpz_t *z, const char *str, size_t len, bool neg, unsigned int base); -void mpz_set_from_bytes(mpz_t *z, bool big_endian, size_t len, const byte *buf); +void mpz_set_from_bytes(mpz_t *z, bool is_signed, bool big_endian, size_t len, const byte *buf); static inline bool mpz_is_zero(const mpz_t *z) { return z->len == 0; diff --git a/py/objint.c b/py/objint.c index be5f4653a7dec..9275b82d83992 100644 --- a/py/objint.c +++ b/py/objint.c @@ -409,7 +409,7 @@ STATIC mp_obj_t int_from_bytes(size_t n_args, const mp_obj_t *args) { #if MICROPY_LONGINT_IMPL != MICROPY_LONGINT_IMPL_NONE if (value > (MP_SMALL_INT_MAX >> 8)) { // Result will overflow a small-int so construct a big-int - return mp_obj_int_from_bytes_impl(args[2] != MP_OBJ_NEW_QSTR(MP_QSTR_little), bufinfo.len, bufinfo.buf); + return mp_obj_int_from_bytes_impl(false, args[2] != MP_OBJ_NEW_QSTR(MP_QSTR_little), bufinfo.len, bufinfo.buf); } #endif value = (value << 8) | *buf; diff --git a/py/objint.h b/py/objint.h index 8919bd07ba234..560aa2263d7fe 100644 --- a/py/objint.h +++ b/py/objint.h @@ -54,7 +54,7 @@ char *mp_obj_int_formatted(char **buf, size_t *buf_size, size_t *fmt_size, mp_co char *mp_obj_int_formatted_impl(char **buf, size_t *buf_size, size_t *fmt_size, mp_const_obj_t self_in, int base, const char *prefix, char base_char, char comma); mp_int_t mp_obj_int_hash(mp_obj_t self_in); -mp_obj_t mp_obj_int_from_bytes_impl(bool big_endian, size_t len, const byte *buf); +mp_obj_t mp_obj_int_from_bytes_impl(bool is_signed, bool big_endian, size_t len, const byte *buf); size_t mp_obj_int_max_bytes_needed_impl(mp_obj_t self_in); void mp_obj_int_to_bytes_impl(mp_obj_t self_in, bool big_endian, size_t len, byte *buf); int mp_obj_int_sign(mp_obj_t self_in); diff --git a/py/objint_longlong.c b/py/objint_longlong.c index 9e737663a372c..f3fc2fa9c8dfe 100644 --- a/py/objint_longlong.c +++ b/py/objint_longlong.c @@ -43,7 +43,7 @@ const mp_obj_int_t mp_sys_maxsize_obj = {{&mp_type_int}, MP_SSIZE_MAX}; #endif -mp_obj_t mp_obj_int_from_bytes_impl(bool big_endian, size_t len, const byte *buf) { +mp_obj_t mp_obj_int_from_bytes_impl(bool is_signed, bool big_endian, size_t len, const byte *buf) { int delta = 1; if (!big_endian) { buf += len - 1; diff --git a/py/objint_mpz.c b/py/objint_mpz.c index a9f79bf1a67e9..11d44a23e5b44 100644 --- a/py/objint_mpz.c +++ b/py/objint_mpz.c @@ -106,9 +106,9 @@ char *mp_obj_int_formatted_impl(char **buf, size_t *buf_size, size_t *fmt_size, return str; } -mp_obj_t mp_obj_int_from_bytes_impl(bool big_endian, size_t len, const byte *buf) { +mp_obj_t mp_obj_int_from_bytes_impl(bool is_signed, bool big_endian, size_t len, const byte *buf) { mp_obj_int_t *o = mp_obj_int_new_mpz(); - mpz_set_from_bytes(&o->mpz, big_endian, len, buf); + mpz_set_from_bytes(&o->mpz, is_signed, big_endian, len, buf); return MP_OBJ_FROM_PTR(o); } From f2b6c80f5ab616144f1e64e1967a1f175f096d91 Mon Sep 17 00:00:00 2001 From: Armin Brauns Date: Thu, 13 Jul 2023 15:08:43 +0000 Subject: [PATCH 4/5] py/persistentcode: Store constants more efficiently. Instead of being stored as strings: - Floats are stored as little-endian bytes - Integers are stored as a length byte and the minimum number of little-endian bytes required. This also necessitates a bump of the mpy version. Signed-off-by: Armin Brauns --- py/persistentcode.c | 222 ++++++++++++++++++--- py/persistentcode.h | 8 +- tests/micropython/import_mpy_native.py | 4 +- tests/perf_bench/core_import_mpy_multi.py | 2 +- tests/perf_bench/core_import_mpy_single.py | 2 +- tools/mpy-tool.py | 51 ++++- tools/mpy_ld.py | 2 +- 7 files changed, 242 insertions(+), 49 deletions(-) diff --git a/py/persistentcode.c b/py/persistentcode.c index fdc87d5cc8c3a..3d41fddb0098e 100644 --- a/py/persistentcode.c +++ b/py/persistentcode.c @@ -24,17 +24,22 @@ * THE SOFTWARE. */ +#include #include #include #include #include +#include "py/binary.h" +#include "py/obj.h" +#include "py/objint.h" #include "py/reader.h" #include "py/nativeglue.h" #include "py/persistentcode.h" #include "py/bc0.h" #include "py/objstr.h" #include "py/mpthread.h" +#include "py/misc.h" #if MICROPY_PERSISTENT_CODE_LOAD || MICROPY_PERSISTENT_CODE_SAVE @@ -170,6 +175,49 @@ STATIC qstr load_qstr(mp_reader_t *reader) { return qst; } +#if MICROPY_FLOAT_IMPL != MICROPY_FLOAT_IMPL_NONE +STATIC mp_float_t mp_read_float_binary(mp_reader_t *reader, bool is_double) { + size_t len = is_double ? 8 : 4; + // native-endian buffer + byte buf[8]; + + #if MP_ENDIANNESS_LITTLE + read_bytes(reader, buf, len); + #else + for (int i = len - 1; i >= 0; i--) { + const byte b = read_byte(reader); + buf[i] = b; + } + #endif + + if (is_double) { + union double_int_union { + double f; + int64_t i; + } u; + memcpy(&u.i, buf, len); + + #if MICROPY_FLOAT_IMPL == MICROPY_FLOAT_IMPL_DOUBLE + return u.f; + #elif MICROPY_FLOAT_IMPL == MICROPY_FLOAT_IMPL_FLOAT + return (mp_float_t)u.f; + #endif + } else { + union float_int_union { + float f; + int32_t i; + } u; + memcpy(&u.i, buf, len); + + #if MICROPY_FLOAT_IMPL == MICROPY_FLOAT_IMPL_DOUBLE + return (mp_float_t)u.f; + #elif MICROPY_FLOAT_IMPL == MICROPY_FLOAT_IMPL_FLOAT + return u.f; + #endif + } +} +#endif + STATIC mp_obj_t load_obj(mp_reader_t *reader) { byte obj_type = read_byte(reader); #if MICROPY_EMIT_MACHINE_CODE @@ -185,7 +233,47 @@ STATIC mp_obj_t load_obj(mp_reader_t *reader) { return mp_const_true; } else if (obj_type == MP_PERSISTENT_OBJ_ELLIPSIS) { return MP_OBJ_FROM_PTR(&mp_const_ellipsis_obj); - } else { + } else if (obj_type == MP_PERSISTENT_OBJ_INT) { + size_t len_neg = read_uint(reader); + size_t len = len_neg >> 1; + bool is_negative = len_neg & 1; + + vstr_t vstr; + vstr_init_len(&vstr, len); + read_bytes(reader, (byte *)vstr.buf, len); + + if (len > sizeof(mp_int_t)) { + // definitely too big for small int + return mp_obj_int_from_bytes_impl(is_negative, false, len, (byte *)vstr.buf); + } + + mp_int_t val = mp_binary_get_int(len, is_negative, false, (byte *)vstr.buf); + + if (!MP_SMALL_INT_FITS(val) || (!is_negative && val < 0)) { + // still didn't fit in small int + return mp_obj_int_from_bytes_impl(is_negative, false, len, (byte *)vstr.buf); + } else { + return MP_OBJ_NEW_SMALL_INT(val); + } + } + #if MICROPY_FLOAT_IMPL != MICROPY_FLOAT_IMPL_NONE + #if MICROPY_PY_BUILTINS_COMPLEX + else if (obj_type == MP_PERSISTENT_OBJ_COMPLEX_FLOAT || + obj_type == MP_PERSISTENT_OBJ_COMPLEX_DOUBLE) { + bool is_double = obj_type == MP_PERSISTENT_OBJ_COMPLEX_DOUBLE; + mp_float_t real = mp_read_float_binary(reader, is_double); + mp_float_t imag = mp_read_float_binary(reader, is_double); + return mp_obj_new_complex(real, imag); + } + #endif + else if (obj_type == MP_PERSISTENT_OBJ_FP_FLOAT || + obj_type == MP_PERSISTENT_OBJ_FP_DOUBLE) { + bool is_double = obj_type == MP_PERSISTENT_OBJ_FP_DOUBLE; + mp_float_t f = mp_read_float_binary(reader, is_double); + return mp_obj_new_float(f); + } + #endif + else { size_t len = read_uint(reader); if (len == 0 && obj_type == MP_PERSISTENT_OBJ_BYTES) { read_byte(reader); // skip null terminator @@ -197,21 +285,16 @@ STATIC mp_obj_t load_obj(mp_reader_t *reader) { } return MP_OBJ_FROM_PTR(tuple); } + + assert(obj_type == MP_PERSISTENT_OBJ_STR || obj_type == MP_PERSISTENT_OBJ_BYTES); vstr_t vstr; vstr_init_len(&vstr, len); read_bytes(reader, (byte *)vstr.buf, len); - if (obj_type == MP_PERSISTENT_OBJ_STR || obj_type == MP_PERSISTENT_OBJ_BYTES) { - read_byte(reader); // skip null terminator - if (obj_type == MP_PERSISTENT_OBJ_STR) { - return mp_obj_new_str_from_utf8_vstr(&vstr); - } else { - return mp_obj_new_bytes_from_vstr(&vstr); - } - } else if (obj_type == MP_PERSISTENT_OBJ_INT) { - return mp_parse_num_integer(vstr.buf, vstr.len, 10, NULL); + read_byte(reader); // skip null terminator + if (obj_type == MP_PERSISTENT_OBJ_STR) { + return mp_obj_new_str_from_utf8_vstr(&vstr); } else { - assert(obj_type == MP_PERSISTENT_OBJ_FLOAT || obj_type == MP_PERSISTENT_OBJ_COMPLEX); - return mp_parse_num_float(vstr.buf, vstr.len, obj_type == MP_PERSISTENT_OBJ_COMPLEX, NULL); + return mp_obj_new_bytes_from_vstr(&vstr); } } } @@ -488,6 +571,23 @@ STATIC void save_qstr(mp_print_t *print, qstr qst) { mp_print_bytes(print, str, len + 1); // +1 to store null terminator } +#if MICROPY_FLOAT_IMPL != MICROPY_FLOAT_IMPL_NONE +STATIC void mp_print_float_binary(mp_print_t *print, mp_float_t f) { + mp_float_union_t fu = { .f = f }; + mp_float_uint_t f_int_val = fu.i; + + #if MP_ENDIANNESS_LITTLE + mp_print_bytes(print, (const byte *)&f_int_val, sizeof(f_int_val)); + #else + for (int i = 0; i < sizeof(f_int_val); i++) { + const byte b = f_int_val & 0xff; + mp_print_bytes(print, &b, 1); + f_int_val >>= 8; + } + #endif +} +#endif + STATIC void save_obj(mp_print_t *print, mp_obj_t o) { #if MICROPY_EMIT_MACHINE_CODE if (o == MP_OBJ_FROM_PTR(&mp_fun_table)) { @@ -529,29 +629,91 @@ STATIC void save_obj(mp_print_t *print, mp_obj_t o) { for (size_t i = 0; i < len; ++i) { save_obj(print, items[i]); } - } else { - // we save numbers using a simplistic text representation - // TODO could be improved - byte obj_type; - if (mp_obj_is_int(o)) { - obj_type = MP_PERSISTENT_OBJ_INT; - #if MICROPY_PY_BUILTINS_COMPLEX - } else if (mp_obj_is_type(o, &mp_type_complex)) { - obj_type = MP_PERSISTENT_OBJ_COMPLEX; - #endif + } else if (mp_obj_is_int(o)) { + // Integers are saved as a metadata byte followed by the little-endian bytes of the integer. + // The metadata is the length shifted left by one, ORed with a bit specifying whether the + // integer is negative (1) or positive (0). + vstr_t vstr = {0}; + byte smallint_buf[sizeof(mp_int_t)]; + + size_t len; + byte *buf; + bool is_negative = mp_obj_int_sign(o) == -1; + + if (mp_obj_is_small_int(o)) { + mp_int_t val = MP_OBJ_SMALL_INT_VALUE(o); + + len = sizeof(mp_int_t); + mp_binary_set_int(len, false, smallint_buf, val); + buf = smallint_buf; } else { - assert(mp_obj_is_float(o)); - obj_type = MP_PERSISTENT_OBJ_FLOAT; + len = mp_obj_int_max_bytes_needed_impl(o); + vstr_init(&vstr, len); + mp_obj_int_to_bytes_impl(o, false, len, (byte *)vstr.buf); + buf = (byte *)vstr.buf; } - vstr_t vstr; - mp_print_t pr; - vstr_init_print(&vstr, 10, &pr); - mp_obj_print_helper(&pr, o, PRINT_REPR); + + // Try to chop off as many bytes as possible (starting from MSB). Either chop off 0xff + // bytes if the resulting number's MSB is still 1 (is_negative=true), or chop off zero + // bytes (is_negative=false) + size_t leading_unneeded_bytes = 0; + for (int i = len - 1; i >= 0; i--) { + if (is_negative) { + // Try to remove a 0xff byte + if (i > 0 && buf[i] == 0xff && buf[i - 1] & 0x80) { + // The current byte is all 1s, and the MSB of the next byte is 1, so this + // byte can be chopped off. + leading_unneeded_bytes++; + } else { + // byte is required, end. + break; + } + } else if (buf[i] == 0) { + // Remove a zero byte + leading_unneeded_bytes++; + } else { + // byte is required, end. + break; + } + } + len -= leading_unneeded_bytes; + + byte obj_type = MP_PERSISTENT_OBJ_INT; mp_print_bytes(print, &obj_type, 1); - mp_print_uint(print, vstr.len); - mp_print_bytes(print, (const byte *)vstr.buf, vstr.len); + mp_print_uint(print, (len << 1) | is_negative); + mp_print_bytes(print, buf, len); + vstr_clear(&vstr); } + #if MICROPY_FLOAT_IMPL != MICROPY_FLOAT_IMPL_NONE + #if MICROPY_PY_BUILTINS_COMPLEX + else if (mp_obj_is_type(o, &mp_type_complex)) { + mp_float_t real, imag; + mp_obj_complex_get(o, &real, &imag); + + #if MICROPY_FLOAT_IMPL == MICROPY_FLOAT_IMPL_DOUBLE + byte obj_type = MP_PERSISTENT_OBJ_COMPLEX_DOUBLE; + #elif MICROPY_FLOAT_IMPL == MICROPY_FLOAT_IMPL_FLOAT + byte obj_type = MP_PERSISTENT_OBJ_COMPLEX_FLOAT; + #endif + mp_print_bytes(print, &obj_type, 1); + mp_print_float_binary(print, real); + mp_print_float_binary(print, imag); + } + #endif + else if (mp_obj_is_float(o)) { + #if MICROPY_FLOAT_IMPL == MICROPY_FLOAT_IMPL_DOUBLE + byte obj_type = MP_PERSISTENT_OBJ_FP_DOUBLE; + #elif MICROPY_FLOAT_IMPL == MICROPY_FLOAT_IMPL_FLOAT + byte obj_type = MP_PERSISTENT_OBJ_FP_FLOAT; + #endif + mp_print_bytes(print, &obj_type, 1); + mp_print_float_binary(print, mp_obj_float_get(o)); + } + #endif + else { + mp_raise_ValueError(MP_ERROR_TEXT("Unhandled constant type")); + } } STATIC void save_raw_code(mp_print_t *print, const mp_raw_code_t *rc) { diff --git a/py/persistentcode.h b/py/persistentcode.h index d363f544ad558..1241d9a4a48c0 100644 --- a/py/persistentcode.h +++ b/py/persistentcode.h @@ -34,7 +34,7 @@ // 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. -#define MPY_VERSION 6 +#define MPY_VERSION 7 #define MPY_SUB_VERSION 1 // Macros to encode/decode sub-version to/from the feature byte. This replaces @@ -106,8 +106,10 @@ enum { MP_PERSISTENT_OBJ_STR, MP_PERSISTENT_OBJ_BYTES, MP_PERSISTENT_OBJ_INT, - MP_PERSISTENT_OBJ_FLOAT, - MP_PERSISTENT_OBJ_COMPLEX, + MP_PERSISTENT_OBJ_FP_FLOAT, + MP_PERSISTENT_OBJ_FP_DOUBLE, + MP_PERSISTENT_OBJ_COMPLEX_FLOAT, + MP_PERSISTENT_OBJ_COMPLEX_DOUBLE, MP_PERSISTENT_OBJ_TUPLE, }; diff --git a/tests/micropython/import_mpy_native.py b/tests/micropython/import_mpy_native.py index da20746b225de..94cbf3ae8a535 100644 --- a/tests/micropython/import_mpy_native.py +++ b/tests/micropython/import_mpy_native.py @@ -52,11 +52,11 @@ def open(self, path, mode): # these are the test .mpy files -valid_header = bytes([77, 6, mpy_arch, 31]) +valid_header = bytes([77, 7, mpy_arch, 31]) # fmt: off user_files = { # bad architecture (mpy_arch needed for sub-version) - '/mod0.mpy': bytes([77, 6, 0xfc | mpy_arch, 31]), + '/mod0.mpy': bytes([77, 7, 0xfc | mpy_arch, 31]), # test loading of viper and asm '/mod1.mpy': valid_header + ( diff --git a/tests/perf_bench/core_import_mpy_multi.py b/tests/perf_bench/core_import_mpy_multi.py index 364c325042843..950bec7e4d271 100644 --- a/tests/perf_bench/core_import_mpy_multi.py +++ b/tests/perf_bench/core_import_mpy_multi.py @@ -23,7 +23,7 @@ def f(): x = ("const tuple", None, False, True, 1, 2, 3) result = 123 """ -file_data = b'M\x06\x00\x1f\x14\x03\x0etest.py\x00\x0f\x02A\x00\x02f\x00\x0cresult\x00/-5#\x82I\x81{\x81w\x82/\x81\x05\x81\x17Iom\x82\x13\x06arg\x00\x05\x1cthis will be a string object\x00\x06\x1bthis will be a bytes object\x00\n\x07\x05\x0bconst tuple\x00\x01\x02\x03\x07\x011\x07\x012\x07\x013\x81\\\x10\n\x01\x89\x07d`T2\x00\x10\x024\x02\x16\x022\x01\x16\x03"\x80{\x16\x04Qc\x02\x81d\x00\x08\x02(DD\x11\x05\x16\x06\x10\x02\x16\x072\x00\x16\x082\x01\x16\t2\x02\x16\nQc\x03`\x1a\x08\x08\x12\x13@\xb1\xb0\x18\x13Qc@\t\x08\t\x12` Qc@\t\x08\n\x12``Qc\x82@ \x0e\x03\x80\x08+)##\x12\x0b\x12\x0c\x12\r\x12\x0e*\x04Y\x12\x0f\x12\x10\x12\x11*\x03Y#\x00\xc0#\x01\xc0#\x02\xc0Qc' +file_data = b'M\x07\x00\x1f\x14\x03\x0etest.py\x00\x0f\x02A\x00\x02f\x00#\x06arg\x00\x82I\x81{\x0cresult\x00/-5\x81w\x82/\x81\x05\x81\x17Iom\x82\x13\x05\x1cthis will be a string object\x00\x06\x1bthis will be a bytes object\x00\x0c\x07\x05\x0bconst tuple\x00\x01\x02\x03\x07\x02\x01\x07\x02\x02\x07\x02\x03\x81\\\x10\n\x01\x89\x07d`T2\x00\x10\x024\x02\x16\x022\x01\x16\x03"\x80{\x16\x08Qc\x02\x81d\x00\x08\x02(DD\x11\t\x16\n\x10\x02\x16\x0b2\x00\x16\x042\x01\x16\x062\x02\x16\x07Qc\x03`\x1a\x08\x04\x13\x05@\xb1\xb0\x18\x05Qc@\t\x08\x06\x13` Qc@\t\x08\x07\x13``Qc\x82@ \x0e\x03\x80\x08+)##\x12\x0c\x12\r\x12\x0e\x12\x0f*\x04Y\x12\x10\x12\x11\x12\x12*\x03Y#\x00\xc0#\x01\xc0#\x02\xc0Qc' class File(io.IOBase): diff --git a/tests/perf_bench/core_import_mpy_single.py b/tests/perf_bench/core_import_mpy_single.py index 5757c3eaf1fdf..2454bd693eaa8 100644 --- a/tests/perf_bench/core_import_mpy_single.py +++ b/tests/perf_bench/core_import_mpy_single.py @@ -78,7 +78,7 @@ def f1(): x = ("const tuple 9", None, False, True, 1, 2, 3) result = 123 """ -file_data = b"M\x06\x00\x1f\x81=\x1e\x0etest.py\x00\x0f\x04A0\x00\x04A1\x00\x04f0\x00\x04f1\x00\x0cresult\x00/-5\x04a0\x00\x04a1\x00\x04a2\x00\x04a3\x00\x13\x15\x17\x19\x1b\x1d\x1f!#%')+1379;=?ACEGIKMOQSUWY[]_acegikmoqsuwy{}\x7f\x81\x01\x81\x03\x81\x05\x81\x07\x81\t\x81\x0b\x81\r\x81\x0f\x81\x11\x81\x13\x81\x15\x81\x17\x81\x19\x81\x1b\x81\x1d\x81\x1f\x81!\x81#\x81%\x81'\x81)\x81+\x81-\x81/\x811\x813\x815\x817\x819\x81;\x81=\x81?\x81A\x81C\x81E\x81G\x81I\x81K\x81M\x81O\x81Q\x81S\x81U\x81W\x81Y\x81[\x81]\x81_\x81a\x81c\x81e\x81g\x81i\x81k\x81m\x81o\x81q\x81s\x81u\x81w\x81y\x81{\x81}\x81\x7f\x82\x01\x82\x03\x82\x05\x82\x07\x82\t\x82\x0b\x82\r\x82\x0f\x82\x11\x82\x13\x82\x15\x82\x17\x82\x19\x82\x1b\x82\x1d\x82\x1f\x82!\x82#\x82%\x82'\x82)\x82+\x82-\x82/\x821\x823\x825\x827\x829\x82;\x82=\x82?\x82A\x82E\x82G\x82I\x82K\nname0\x00\nname1\x00\nname2\x00\nname3\x00\nname4\x00\nname5\x00\nname6\x00\nname7\x00\nname8\x00\nname9\x00$quite_a_long_name0\x00$quite_a_long_name1\x00$quite_a_long_name2\x00$quite_a_long_name3\x00$quite_a_long_name4\x00$quite_a_long_name5\x00$quite_a_long_name6\x00$quite_a_long_name7\x00$quite_a_long_name8\x00$quite_a_long_name9\x00&quite_a_long_name10\x00&quite_a_long_name11\x00\x05\x1ethis will be a string object 0\x00\x05\x1ethis will be a string object 1\x00\x05\x1ethis will be a string object 2\x00\x05\x1ethis will be a string object 3\x00\x05\x1ethis will be a string object 4\x00\x05\x1ethis will be a string object 5\x00\x05\x1ethis will be a string object 6\x00\x05\x1ethis will be a string object 7\x00\x05\x1ethis will be a string object 8\x00\x05\x1ethis will be a string object 9\x00\x06\x1dthis will be a bytes object 0\x00\x06\x1dthis will be a bytes object 1\x00\x06\x1dthis will be a bytes object 2\x00\x06\x1dthis will be a bytes object 3\x00\x06\x1dthis will be a bytes object 4\x00\x06\x1dthis will be a bytes object 5\x00\x06\x1dthis will be a bytes object 6\x00\x06\x1dthis will be a bytes object 7\x00\x06\x1dthis will be a bytes object 8\x00\x06\x1dthis will be a bytes object 9\x00\n\x07\x05\rconst tuple 0\x00\x01\x02\x03\x07\x011\x07\x012\x07\x013\n\x07\x05\rconst tuple 1\x00\x01\x02\x03\x07\x011\x07\x012\x07\x013\n\x07\x05\rconst tuple 2\x00\x01\x02\x03\x07\x011\x07\x012\x07\x013\n\x07\x05\rconst tuple 3\x00\x01\x02\x03\x07\x011\x07\x012\x07\x013\n\x07\x05\rconst tuple 4\x00\x01\x02\x03\x07\x011\x07\x012\x07\x013\n\x07\x05\rconst tuple 5\x00\x01\x02\x03\x07\x011\x07\x012\x07\x013\n\x07\x05\rconst tuple 6\x00\x01\x02\x03\x07\x011\x07\x012\x07\x013\n\x07\x05\rconst tuple 7\x00\x01\x02\x03\x07\x011\x07\x012\x07\x013\n\x07\x05\rconst tuple 8\x00\x01\x02\x03\x07\x011\x07\x012\x07\x013\n\x07\x05\rconst tuple 9\x00\x01\x02\x03\x07\x011\x07\x012\x07\x013\x82d\x10\x12\x01i@i@\x84\x18\x84\x1fT2\x00\x10\x024\x02\x16\x02T2\x01\x10\x034\x02\x16\x032\x02\x16\x042\x03\x16\x05\"\x80{\x16\x06Qc\x04\x82\x0c\x00\n\x02($$$\x11\x07\x16\x08\x10\x02\x16\t2\x00\x16\n2\x01\x16\x0b2\x02\x16\x0c2\x03\x16\rQc\x04@\t\x08\n\x81\x0b Qc@\t\x08\x0b\x81\x0b@Qc@\t\x08\x0c\x81\x0b`QcH\t\n\r\x81\x0b` Qc\x82\x14\x00\x0c\x03h`$$$\x11\x07\x16\x08\x10\x03\x16\t2\x00\x16\n2\x01\x16\x0b2\x02\x16\x0c2\x03\x16\rQc\x04H\t\n\n\x81\x0b``QcH\t\n\x0b\x81\x0b\x80\x07QcH\t\n\x0c\x81\x0b\x80\x08QcH\t\n\r\x81\x0b\x80\tQc\xa08P:\x04\x80\x0b13///---997799<\x1f%\x1f\"\x1f%)\x1f\"//\x12\x0e\x12\x0f\x12\x10\x12\x11\x12\x12\x12\x13\x12\x14*\x07Y\x12\x15\x12\x16\x12\x17\x12\x18\x12\x19\x12\x1a\x12\x08\x12\x07*\x08Y\x12\x1b\x12\x1c\x12\t\x12\x1d\x12\x1e\x12\x1f*\x06Y\x12 \x12!\x12\"\x12#\x12$\x12%*\x06Y\x12&\x12'\x12(\x12)\x12*\x12+*\x06Y\x12,\x12-\x12.\x12/\x120*\x05Y\x121\x122\x123\x124\x125*\x05Y\x126\x127\x128\x129\x12:*\x05Y\x12;\x12<\x12=\x12>\x12?\x12@\x12A\x12B\x12C\x12D\x12E*\x0bY\x12F\x12G\x12H\x12I\x12J\x12K\x12L\x12M\x12N\x12O\x12P*\x0bY\x12Q\x12R\x12S\x12T\x12U\x12V\x12W\x12X\x12Y\x12Z*\nY\x12[\x12\\\x12]\x12^\x12_\x12`\x12a\x12b\x12c\x12d*\nY\x12e\x12f\x12g\x12h\x12i\x12j\x12k\x12l\x12m\x12n\x12o*\x0bY\x12p\x12q\x12r\x12s\x12t\x12u\x12v\x12w\x12x\x12y\x12z*\x0bY\x12{\x12|\x12}\x12~\x12\x7f\x12\x81\x00\x12\x81\x01\x12\x81\x02\x12\x81\x03\x12\x81\x04*\nY\x12\x81\x05\x12\x81\x06\x12\x81\x07\x12\x81\x08\x12\x81\t\x12\x81\n\x12\x81\x0b\x12\x81\x0c\x12\x81\r\x12\x81\x0e\x12\x81\x0f*\x0bY\x12\x81\x10\x12\x81\x11\x12\x81\x12\x12\x81\x13\x12\x81\x14\x12\x81\x15\x12\x81\x16\x12\x81\x17\x12\x81\x18\x12\x81\x19*\nY\x12\x81\x1a\x12\x81\x1b\x12\x81\x1c\x12\x81\x1d\x12\x81\x1e\x12\x81\x1f\x12\x81 \x12\x81!\x12\x81\"\x12\x81#\x12\x81$*\x0bY\x12\x81%\x12\x81&*\x02Y\x12\x81'\x12\x81(\x12\x81)\x12\x81*\x12\x81+\x12\x81,\x12\x81-\x12\x81.\x12\x81/\x12\x810*\nY\x12\x811\x12\x812\x12\x813\x12\x814*\x04Y\x12\x815\x12\x816\x12\x817\x12\x818*\x04Y\x12\x819\x12\x81:\x12\x81;\x12\x81<*\x04YQc\x87p\x08@\x05\x80###############################\x00\xc0#\x01\xc0#\x02\xc0#\x03\xc0#\x04\xc0#\x05\xc0#\x06\xc0#\x07\xc0#\x08\xc0#\t\xc0#\n\xc0#\x0b\xc0#\x0c\xc0#\r\xc0#\x0e\xc0#\x0f\xc0#\x10\xc0#\x11\xc0#\x12\xc0#\x13\xc0#\x14\xc0#\x15\xc0#\x16\xc0#\x17\xc0#\x18\xc0#\x19\xc0#\x1a\xc0#\x1b\xc0#\x1c\xc0#\x1d\xc0Qc" +file_data = b"M\x07\x00\x1f\x81=\x1e\x0etest.py\x00\x0f\x04A0\x00\x04A1\x00\x04f0\x00\x04f1\x00\x04a0\x00\x04a1\x00\x04a2\x00\x04a3\x00\x0cresult\x00/-5\x13\x15\x17\x19\x1b\x1d\x1f!#%')+1379;=?ACEGIKMOQSUWY[]_acegikmoqsuwy{}\x7f\x81\x01\x81\x03\x81\x05\x81\x07\x81\t\x81\x0b\x81\r\x81\x0f\x81\x11\x81\x13\x81\x15\x81\x17\x81\x19\x81\x1b\x81\x1d\x81\x1f\x81!\x81#\x81%\x81'\x81)\x81+\x81-\x81/\x811\x813\x815\x817\x819\x81;\x81=\x81?\x81A\x81C\x81E\x81G\x81I\x81K\x81M\x81O\x81Q\x81S\x81U\x81W\x81Y\x81[\x81]\x81_\x81a\x81c\x81e\x81g\x81i\x81k\x81m\x81o\x81q\x81s\x81u\x81w\x81y\x81{\x81}\x81\x7f\x82\x01\x82\x03\x82\x05\x82\x07\x82\t\x82\x0b\x82\r\x82\x0f\x82\x11\x82\x13\x82\x15\x82\x17\x82\x19\x82\x1b\x82\x1d\x82\x1f\x82!\x82#\x82%\x82'\x82)\x82+\x82-\x82/\x821\x823\x825\x827\x829\x82;\x82=\x82?\x82A\x82E\x82G\x82I\x82K\nname0\x00\nname1\x00\nname2\x00\nname3\x00\nname4\x00\nname5\x00\nname6\x00\nname7\x00\nname8\x00\nname9\x00$quite_a_long_name0\x00$quite_a_long_name1\x00$quite_a_long_name2\x00$quite_a_long_name3\x00$quite_a_long_name4\x00$quite_a_long_name5\x00$quite_a_long_name6\x00$quite_a_long_name7\x00$quite_a_long_name8\x00$quite_a_long_name9\x00&quite_a_long_name10\x00&quite_a_long_name11\x00\x05\x1ethis will be a string object 0\x00\x05\x1ethis will be a string object 1\x00\x05\x1ethis will be a string object 2\x00\x05\x1ethis will be a string object 3\x00\x05\x1ethis will be a string object 4\x00\x05\x1ethis will be a string object 5\x00\x05\x1ethis will be a string object 6\x00\x05\x1ethis will be a string object 7\x00\x05\x1ethis will be a string object 8\x00\x05\x1ethis will be a string object 9\x00\x06\x1dthis will be a bytes object 0\x00\x06\x1dthis will be a bytes object 1\x00\x06\x1dthis will be a bytes object 2\x00\x06\x1dthis will be a bytes object 3\x00\x06\x1dthis will be a bytes object 4\x00\x06\x1dthis will be a bytes object 5\x00\x06\x1dthis will be a bytes object 6\x00\x06\x1dthis will be a bytes object 7\x00\x06\x1dthis will be a bytes object 8\x00\x06\x1dthis will be a bytes object 9\x00\x0c\x07\x05\rconst tuple 0\x00\x01\x02\x03\x07\x02\x01\x07\x02\x02\x07\x02\x03\x0c\x07\x05\rconst tuple 1\x00\x01\x02\x03\x07\x02\x01\x07\x02\x02\x07\x02\x03\x0c\x07\x05\rconst tuple 2\x00\x01\x02\x03\x07\x02\x01\x07\x02\x02\x07\x02\x03\x0c\x07\x05\rconst tuple 3\x00\x01\x02\x03\x07\x02\x01\x07\x02\x02\x07\x02\x03\x0c\x07\x05\rconst tuple 4\x00\x01\x02\x03\x07\x02\x01\x07\x02\x02\x07\x02\x03\x0c\x07\x05\rconst tuple 5\x00\x01\x02\x03\x07\x02\x01\x07\x02\x02\x07\x02\x03\x0c\x07\x05\rconst tuple 6\x00\x01\x02\x03\x07\x02\x01\x07\x02\x02\x07\x02\x03\x0c\x07\x05\rconst tuple 7\x00\x01\x02\x03\x07\x02\x01\x07\x02\x02\x07\x02\x03\x0c\x07\x05\rconst tuple 8\x00\x01\x02\x03\x07\x02\x01\x07\x02\x02\x07\x02\x03\x0c\x07\x05\rconst tuple 9\x00\x01\x02\x03\x07\x02\x01\x07\x02\x02\x07\x02\x03\x82d\x10\x12\x01i@i@\x84\x18\x84\x1fT2\x00\x10\x024\x02\x16\x02T2\x01\x10\x034\x02\x16\x032\x02\x16\x042\x03\x16\x05\"\x80{\x16\nQc\x04\x82\x0c\x00\n\x02($$$\x11\x0b\x16\x0c\x10\x02\x16\r2\x00\x16\x062\x01\x16\x072\x02\x16\x082\x03\x16\tQc\x04@\t\x08\x06\x81\x0b Qc@\t\x08\x07\x81\x0b@Qc@\t\x08\x08\x81\x0b`QcH\t\n\t\x81\x0b` Qc\x82\x14\x00\x0c\x03h`$$$\x11\x0b\x16\x0c\x10\x03\x16\r2\x00\x16\x062\x01\x16\x072\x02\x16\x082\x03\x16\tQc\x04H\t\n\x06\x81\x0b``QcH\t\n\x07\x81\x0b\x80\x07QcH\t\n\x08\x81\x0b\x80\x08QcH\t\n\t\x81\x0b\x80\tQc\xa08P:\x04\x80\x0b13///---997799<\x1f%\x1f\"\x1f%)\x1f\"//\x12\x0e\x12\x0f\x12\x10\x12\x11\x12\x12\x12\x13\x12\x14*\x07Y\x12\x15\x12\x16\x12\x17\x12\x18\x12\x19\x12\x1a\x12\x0c\x12\x0b*\x08Y\x12\x1b\x12\x1c\x12\r\x12\x1d\x12\x1e\x12\x1f*\x06Y\x12 \x12!\x12\"\x12#\x12$\x12%*\x06Y\x12&\x12'\x12(\x12)\x12*\x12+*\x06Y\x12,\x12-\x12.\x12/\x120*\x05Y\x121\x122\x123\x124\x125*\x05Y\x126\x127\x128\x129\x12:*\x05Y\x12;\x12<\x12=\x12>\x12?\x12@\x12A\x12B\x12C\x12D\x12E*\x0bY\x12F\x12G\x12H\x12I\x12J\x12K\x12L\x12M\x12N\x12O\x12P*\x0bY\x12Q\x12R\x12S\x12T\x12U\x12V\x12W\x12X\x12Y\x12Z*\nY\x12[\x12\\\x12]\x12^\x12_\x12`\x12a\x12b\x12c\x12d*\nY\x12e\x12f\x12g\x12h\x12i\x12j\x12k\x12l\x12m\x12n\x12o*\x0bY\x12p\x12q\x12r\x12s\x12t\x12u\x12v\x12w\x12x\x12y\x12z*\x0bY\x12{\x12|\x12}\x12~\x12\x7f\x12\x81\x00\x12\x81\x01\x12\x81\x02\x12\x81\x03\x12\x81\x04*\nY\x12\x81\x05\x12\x81\x06\x12\x81\x07\x12\x81\x08\x12\x81\t\x12\x81\n\x12\x81\x0b\x12\x81\x0c\x12\x81\r\x12\x81\x0e\x12\x81\x0f*\x0bY\x12\x81\x10\x12\x81\x11\x12\x81\x12\x12\x81\x13\x12\x81\x14\x12\x81\x15\x12\x81\x16\x12\x81\x17\x12\x81\x18\x12\x81\x19*\nY\x12\x81\x1a\x12\x81\x1b\x12\x81\x1c\x12\x81\x1d\x12\x81\x1e\x12\x81\x1f\x12\x81 \x12\x81!\x12\x81\"\x12\x81#\x12\x81$*\x0bY\x12\x81%\x12\x81&*\x02Y\x12\x81'\x12\x81(\x12\x81)\x12\x81*\x12\x81+\x12\x81,\x12\x81-\x12\x81.\x12\x81/\x12\x810*\nY\x12\x811\x12\x812\x12\x813\x12\x814*\x04Y\x12\x815\x12\x816\x12\x817\x12\x818*\x04Y\x12\x819\x12\x81:\x12\x81;\x12\x81<*\x04YQc\x87p\x08@\x05\x80###############################\x00\xc0#\x01\xc0#\x02\xc0#\x03\xc0#\x04\xc0#\x05\xc0#\x06\xc0#\x07\xc0#\x08\xc0#\t\xc0#\n\xc0#\x0b\xc0#\x0c\xc0#\r\xc0#\x0e\xc0#\x0f\xc0#\x10\xc0#\x11\xc0#\x12\xc0#\x13\xc0#\x14\xc0#\x15\xc0#\x16\xc0#\x17\xc0#\x18\xc0#\x19\xc0#\x1a\xc0#\x1b\xc0#\x1c\xc0#\x1d\xc0Qc" class File(io.IOBase): diff --git a/tools/mpy-tool.py b/tools/mpy-tool.py index 7f581d0b1345c..4edcebf0e4d94 100755 --- a/tools/mpy-tool.py +++ b/tools/mpy-tool.py @@ -87,7 +87,7 @@ def __str__(self): class Config: - MPY_VERSION = 6 + MPY_VERSION = 7 MPY_SUB_VERSION = 1 MICROPY_LONGINT_IMPL_NONE = 0 MICROPY_LONGINT_IMPL_LONGLONG = 1 @@ -122,9 +122,11 @@ class Config: MP_PERSISTENT_OBJ_STR = 5 MP_PERSISTENT_OBJ_BYTES = 6 MP_PERSISTENT_OBJ_INT = 7 -MP_PERSISTENT_OBJ_FLOAT = 8 -MP_PERSISTENT_OBJ_COMPLEX = 9 -MP_PERSISTENT_OBJ_TUPLE = 10 +MP_PERSISTENT_OBJ_FP_FLOAT = 8 +MP_PERSISTENT_OBJ_FP_DOUBLE = 9 +MP_PERSISTENT_OBJ_COMPLEX_FLOAT = 10 +MP_PERSISTENT_OBJ_COMPLEX_DOUBLE = 11 +MP_PERSISTENT_OBJ_TUPLE = 12 MP_SCOPE_FLAG_VIPERRELOC = 0x10 MP_SCOPE_FLAG_VIPERRODATA = 0x20 @@ -1206,7 +1208,11 @@ def read_qstr(reader, segments): def read_obj(reader, segments): + def append_seg(obj): + segments.append(MPYSegment(MPYSegment.OBJ, obj, start_pos, reader.tell())) + obj_type = reader.read_byte() + start_pos = reader.tell() if obj_type == MP_PERSISTENT_OBJ_FUN_TABLE: return MPFunTable() elif obj_type == MP_PERSISTENT_OBJ_NONE: @@ -1220,6 +1226,35 @@ def read_obj(reader, segments): elif obj_type == MP_PERSISTENT_OBJ_TUPLE: ln = reader.read_uint() return tuple(read_obj(reader, segments) for _ in range(ln)) + elif obj_type == MP_PERSISTENT_OBJ_INT: + ln_neg = reader.read_uint() + ln = ln_neg >> 1 + is_negative = ln_neg & 1 + + start_pos = reader.tell() + obj = int.from_bytes(reader.read_bytes(ln), byteorder="little", signed=is_negative) + append_seg(obj) + return obj + elif obj_type == MP_PERSISTENT_OBJ_FP_FLOAT: + (obj,) = struct.unpack(" Date: Fri, 14 Jul 2023 10:15:39 +0000 Subject: [PATCH 5/5] tests: Regenerate frozentest.mpy. The mpy version has changed. Signed-off-by: Armin Brauns --- tests/frozen/frozentest.mpy | Bin 196 -> 196 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/tests/frozen/frozentest.mpy b/tests/frozen/frozentest.mpy index 9cef1a2f12a2dabdd3ba6a28d5f675a559b85ccc..06fee65c285cd2bb02320a625362fad70d89b700 100644 GIT binary patch delta 10 RcmX@Yc!ZJ3mwh7BApjBt12zBv delta 10 RcmX@Yc!ZJ3mu(``ApjBp12q5u pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy