diff --git a/extmod/moductypes.c b/extmod/moductypes.c new file mode 100644 index 0000000000..f0aaa78b29 --- /dev/null +++ b/extmod/moductypes.c @@ -0,0 +1,630 @@ +#include +/* + * This file is part of the Micro Python project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2014 Paul Sokolovsky + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include +#include +#include +#include "mpconfig.h" +#include "misc.h" +#include "nlr.h" +#include "qstr.h" +#include "obj.h" +#include "runtime.h" +#include "objtuple.h" +#include "binary.h" + +#if MICROPY_PY_UCTYPES + +/// \module uctypes - Access data structures in memory +/// +/// The module allows to define layout of raw data structure (using terms +/// of C language), and then access memory buffers using this definition. +/// The module also provides convenience functions to access memory buffers +/// contained in Python objects or wrap memory buffers in Python objects. +/// \constant UINT8_1 - uint8_t value type + +/// \class struct - C-like structure +/// +/// Encapsulalation of in-memory data structure. This class doesn't define +/// any methods, only attribute access (for structure fields) and +/// indexing (for pointer and array fields). +/// +/// Usage: +/// +/// # Define layout of a structure with 2 fields +/// # 0 and 4 are byte offsets of fields from the beginning of struct +/// # they are logically ORed with field type +/// FOO_STRUCT = {"a": 0 | uctypes.UINT32, "b": 4 | uctypes.UINT8} +/// +/// # Example memory buffer to access (contained in bytes object) +/// buf = b"\x64\0\0\0\0x14" +/// +/// # Create structure object referring to address of +/// # the data in the buffer above +/// s = uctypes.struct(FOO_STRUCT, uctypes.addressof(buf)) +/// +/// # Access fields +/// print(s.a, s.b) +/// # Result: +/// # 100, 20 + +#define LAYOUT_LITTLE_ENDIAN (0) +#define LAYOUT_BIG_ENDIAN (1) +#define LAYOUT_NATIVE (2) + +#define VAL_TYPE_BITS 4 +#define BITF_LEN_BITS 5 +#define BITF_OFF_BITS 5 +#define OFFSET_BITS 17 +#if VAL_TYPE_BITS + BITF_LEN_BITS + BITF_OFF_BITS + OFFSET_BITS != 31 +#error Invalid encoding field length +#endif + +enum { + UINT8, INT8, UINT16, INT16, + UINT32, INT32, UINT64, INT64, + + BFUINT8, BFINT8, BFUINT16, BFINT16, + BFUINT32, BFINT32, + + FLOAT32, FLOAT64, +}; + +#define AGG_TYPE_BITS 2 + +enum { + STRUCT, PTR, ARRAY, BITFIELD, +}; + +// Here we need to set sign bit right +#define TYPE2SMALLINT(x, nbits) ((((int)x) << (32 - nbits)) >> 1) +#define GET_TYPE(x, nbits) (((x) >> (31 - nbits)) & ((1 << nbits) - 1)); +// Bit 0 is "is_signed" +#define GET_SCALAR_SIZE(val_type) (1 << ((val_type) >> 1)) +#define VALUE_MASK(type_nbits) ~((int)0x80000000 >> type_nbits) + +STATIC const mp_obj_type_t uctypes_struct_type; + +typedef struct _mp_obj_uctypes_struct_t { + mp_obj_base_t base; + mp_obj_t desc; + byte *addr; + uint32_t flags; +} mp_obj_uctypes_struct_t; + +STATIC NORETURN void syntax_error() { + nlr_raise(mp_obj_new_exception_msg(&mp_type_TypeError, "syntax error in uctypes descriptor")); +} + +STATIC mp_obj_t uctypes_struct_make_new(mp_obj_t type_in, uint n_args, uint n_kw, const mp_obj_t *args) { + if (n_args < 2 || n_args > 3) { + syntax_error(); + } + mp_obj_uctypes_struct_t *o = m_new_obj(mp_obj_uctypes_struct_t); + o->base.type = type_in; + o->desc = args[0]; + o->addr = (void*)mp_obj_get_int(args[1]); + o->flags = LAYOUT_NATIVE; + if (n_args == 3) { + o->flags = mp_obj_get_int(args[2]); + } + return o; +} + +STATIC void uctypes_struct_print(void (*print)(void *env, const char *fmt, ...), void *env, mp_obj_t self_in, mp_print_kind_t kind) { + mp_obj_uctypes_struct_t *self = self_in; + const char *typen = "unk"; + if (MP_OBJ_IS_TYPE(self->desc, &mp_type_dict)) { + typen = "STRUCT"; + } else if (MP_OBJ_IS_TYPE(self->desc, &mp_type_tuple)) { + mp_obj_tuple_t *t = (mp_obj_tuple_t*)self->desc; + mp_int_t offset = MP_OBJ_SMALL_INT_VALUE(t->items[0]); + uint agg_type = GET_TYPE(offset, AGG_TYPE_BITS); + switch (agg_type) { + case PTR: typen = "PTR"; break; + case ARRAY: typen = "ARRAY"; break; + } + } else { + typen = "ERROR"; + } + print(env, "", typen, self->addr); +} + +static inline mp_uint_t uctypes_struct_scalar_size(int val_type) { + if (val_type == FLOAT32) { + return 4; + } else { + return GET_SCALAR_SIZE(val_type & 7); + } +} + +STATIC mp_uint_t uctypes_struct_size(mp_obj_t desc_in, mp_uint_t *max_field_size) { + mp_obj_dict_t *d = desc_in; + mp_uint_t total_size = 0; + + if (!MP_OBJ_IS_TYPE(desc_in, &mp_type_dict)) { + syntax_error(); + } + + for (mp_uint_t i = 0; i < d->map.alloc; i++) { + if (MP_MAP_SLOT_IS_FILLED(&d->map, i)) { + mp_obj_t v = d->map.table[i].value; + if (MP_OBJ_IS_SMALL_INT(v)) { + mp_uint_t offset = MP_OBJ_SMALL_INT_VALUE(v); + mp_uint_t val_type = GET_TYPE(offset, VAL_TYPE_BITS); + offset &= VALUE_MASK(VAL_TYPE_BITS); + mp_uint_t s = uctypes_struct_scalar_size(val_type); + if (s > *max_field_size) { + *max_field_size = s; + } + if (offset + s > total_size) { + total_size = offset + s; + } + } else { + if (!MP_OBJ_IS_TYPE(v, &mp_type_tuple)) { + syntax_error(); + } + mp_obj_tuple_t *t = (mp_obj_tuple_t*)v; + mp_int_t offset = MP_OBJ_SMALL_INT_VALUE(t->items[0]); + mp_uint_t agg_type = GET_TYPE(offset, AGG_TYPE_BITS); + offset &= VALUE_MASK(AGG_TYPE_BITS); + + switch (agg_type) { + case STRUCT: { + mp_uint_t s = uctypes_struct_size(t->items[1], max_field_size); + if (offset + s > total_size) { + total_size = offset + s; + } + break; + } + case PTR: { + if (offset + sizeof(void*) > total_size) { + total_size = offset + sizeof(void*); + } + if (sizeof(void*) > *max_field_size) { + *max_field_size = sizeof(void*); + } + break; + } + case ARRAY: { + mp_int_t arr_sz = MP_OBJ_SMALL_INT_VALUE(t->items[1]); + uint val_type = GET_TYPE(arr_sz, VAL_TYPE_BITS); + arr_sz &= VALUE_MASK(VAL_TYPE_BITS); + mp_uint_t item_s; + if (t->len == 2) { + item_s = GET_SCALAR_SIZE(val_type); + if (item_s > *max_field_size) { + *max_field_size = item_s; + } + } else { + item_s = uctypes_struct_size(t->items[2], max_field_size); + } + + mp_uint_t byte_sz = item_s * arr_sz; + if (offset + byte_sz > total_size) { + total_size = offset + byte_sz; + } + break; + } + default: + assert(0); + } + } + } + } + + // Round size up to alignment of biggest field + total_size = (total_size + *max_field_size - 1) & ~(*max_field_size - 1); + return total_size; +} + +STATIC mp_obj_t uctypes_struct_sizeof(mp_obj_t obj_in) { + mp_uint_t max_field_size = 0; + if (MP_OBJ_IS_TYPE(obj_in, &uctypes_struct_type)) { + mp_obj_uctypes_struct_t *obj = obj_in; + obj_in = obj->desc; + } + mp_uint_t size = uctypes_struct_size(obj_in, &max_field_size); + return MP_OBJ_NEW_SMALL_INT(size); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(uctypes_struct_sizeof_obj, uctypes_struct_sizeof); + +STATIC inline mp_obj_t get_unaligned(uint val_type, void *p, int big_endian) { + mp_int_t val = mp_binary_get_int(GET_SCALAR_SIZE(val_type), val_type & 1, big_endian, p); + if (val_type == UINT32) { + return mp_obj_new_int_from_uint(val); + } else { + return mp_obj_new_int(val); + } +} + +STATIC inline void set_unaligned(uint val_type, void *p, int big_endian, mp_obj_t val) { + char struct_type = big_endian ? '>' : '<'; + static const char type2char[8] = "BbHhIiQq"; + mp_binary_set_val(struct_type, type2char[val_type], val, (byte**)&p); +} + +static inline mp_uint_t get_aligned_basic(uint val_type, void *p) { + switch (val_type) { + case UINT8: + return *(uint8_t*)p; + case UINT16: + return *(uint16_t*)p; + case UINT32: + return *(uint32_t*)p; + } + assert(0); + return 0; +} + +static inline void set_aligned_basic(uint val_type, void *p, mp_uint_t v) { + switch (val_type) { + case UINT8: + *(uint8_t*)p = (uint8_t)v; return; + case UINT16: + *(uint16_t*)p = (uint16_t)v; return; + case UINT32: + *(uint32_t*)p = (uint32_t)v; return; + } + assert(0); +} + +STATIC mp_obj_t get_aligned(uint val_type, void *p, mp_int_t index) { + switch (val_type) { + case UINT8: + return MP_OBJ_NEW_SMALL_INT((mp_int_t)((uint8_t*)p)[index]); + case INT8: + return MP_OBJ_NEW_SMALL_INT((mp_int_t)((int8_t*)p)[index]); + case UINT16: + return MP_OBJ_NEW_SMALL_INT((mp_int_t)((uint16_t*)p)[index]); + case INT16: + return MP_OBJ_NEW_SMALL_INT((mp_int_t)((int16_t*)p)[index]); + case UINT32: + return mp_obj_new_int_from_uint(((uint32_t*)p)[index]); + case INT32: + return mp_obj_new_int(((int32_t*)p)[index]); + case UINT64: + case INT64: + return mp_obj_new_int_from_ll(((int64_t*)p)[index]); + case FLOAT32: + return mp_obj_new_float(((float*)p)[index]); + case FLOAT64: + return mp_obj_new_float(((double*)p)[index]); + default: + assert(0); + } +} + +STATIC void set_aligned(uint val_type, void *p, mp_int_t index, mp_obj_t val) { + mp_int_t v = mp_obj_get_int(val); + switch (val_type) { + case UINT8: + ((uint8_t*)p)[index] = (uint8_t)v; return; + case INT8: + ((int8_t*)p)[index] = (int8_t)v; return; + case UINT16: + ((uint16_t*)p)[index] = (uint16_t)v; return; + case INT16: + ((int16_t*)p)[index] = (int16_t)v; return; + case UINT32: + ((uint32_t*)p)[index] = (uint32_t)v; return; + case INT32: + ((int32_t*)p)[index] = (int32_t)v; return; + default: + assert(0); + } +} + +STATIC mp_obj_t uctypes_struct_attr_op(mp_obj_t self_in, qstr attr, mp_obj_t set_val) { + mp_obj_uctypes_struct_t *self = self_in; + + // TODO: Support at least OrderedDict in addition + if (!MP_OBJ_IS_TYPE(self->desc, &mp_type_dict)) { + nlr_raise(mp_obj_new_exception_msg(&mp_type_TypeError, "struct: no fields")); + } + + mp_obj_t deref = mp_obj_dict_get(self->desc, MP_OBJ_NEW_QSTR(attr)); + if (MP_OBJ_IS_SMALL_INT(deref)) { + mp_int_t offset = MP_OBJ_SMALL_INT_VALUE(deref); + mp_uint_t val_type = GET_TYPE(offset, VAL_TYPE_BITS); + offset &= VALUE_MASK(VAL_TYPE_BITS); +//printf("scalar type=%d offset=%x\n", val_type, offset); + + if (val_type <= INT64) { +// printf("size=%d\n", GET_SCALAR_SIZE(val_type)); + if (self->flags == LAYOUT_NATIVE) { + if (set_val == MP_OBJ_NULL) { + return get_aligned(val_type, self->addr + offset, 0); + } else { + set_aligned(val_type, self->addr + offset, 0, set_val); + return set_val; // just !MP_OBJ_NULL + } + } else { + if (set_val == MP_OBJ_NULL) { + return get_unaligned(val_type, self->addr + offset, self->flags); + } else { + set_unaligned(val_type, self->addr + offset, self->flags, set_val); + return set_val; // just !MP_OBJ_NULL + } + } + } else if (val_type >= BFUINT8 && val_type <= BFINT32) { + uint bit_offset = (offset >> 17) & 31; + uint bit_len = (offset >> 22) & 31; + offset &= (1 << 17) - 1; + mp_uint_t val; + if (self->flags == LAYOUT_NATIVE) { + val = get_aligned_basic(val_type & 6, self->addr + offset); + } else { + val = mp_binary_get_int(GET_SCALAR_SIZE(val_type & 7), val_type & 1, self->flags, self->addr + offset); + } + if (set_val == MP_OBJ_NULL) { + val >>= bit_offset; + val &= (1 << bit_len) - 1; + // TODO: signed + assert((val_type & 1) == 0); + return mp_obj_new_int(val); + } else { + mp_uint_t set_val_int = (mp_uint_t)mp_obj_get_int(set_val); + mp_uint_t mask = (1 << bit_len) - 1; + set_val_int &= mask; + set_val_int <<= bit_offset; + mask <<= bit_offset; + val = (val & ~mask) | set_val_int; + + if (self->flags == LAYOUT_NATIVE) { + set_aligned_basic(val_type & 6, self->addr + offset, val); + } else { + mp_binary_set_int(GET_SCALAR_SIZE(val_type & 7), self->flags == LAYOUT_BIG_ENDIAN, + self->addr + offset, (byte*)&val); + } + return set_val; // just !MP_OBJ_NULL + } + } + + assert(0); + return MP_OBJ_NULL; + } + + if (!MP_OBJ_IS_TYPE(deref, &mp_type_tuple)) { + syntax_error(); + } + + if (set_val != MP_OBJ_NULL) { + // Cannot assign to aggregate + syntax_error(); + } + + mp_obj_tuple_t *sub = (mp_obj_tuple_t*)deref; + mp_int_t offset = MP_OBJ_SMALL_INT_VALUE(sub->items[0]); + mp_uint_t agg_type = GET_TYPE(offset, AGG_TYPE_BITS); + offset &= VALUE_MASK(AGG_TYPE_BITS); +//printf("agg type=%d offset=%x\n", agg_type, offset); + + switch (agg_type) { + case STRUCT: { + mp_obj_uctypes_struct_t *o = m_new_obj(mp_obj_uctypes_struct_t); + o->base.type = &uctypes_struct_type; + o->desc = sub->items[1]; + o->addr = self->addr + offset; + o->flags = self->flags; + return o; + } + case PTR: case ARRAY: { + mp_obj_uctypes_struct_t *o = m_new_obj(mp_obj_uctypes_struct_t); + o->base.type = &uctypes_struct_type; + o->desc = sub; + o->addr = self->addr + offset; + o->flags = self->flags; +//printf("PTR/ARR base addr=%p\n", o->addr); + return o; + } + } + + // Should be unreachable once all cases are handled + return MP_OBJ_NULL; +} + +STATIC void uctypes_struct_load_attr(mp_obj_t self_in, qstr attr, mp_obj_t *dest) { + mp_obj_t val = uctypes_struct_attr_op(self_in, attr, MP_OBJ_NULL); + *dest = val; +} + +STATIC bool uctypes_struct_store_attr(mp_obj_t self_in, qstr attr, mp_obj_t val) { + return uctypes_struct_attr_op(self_in, attr, val) != MP_OBJ_NULL; +} + +STATIC mp_obj_t uctypes_struct_subscr(mp_obj_t self_in, mp_obj_t index_in, mp_obj_t value) { + mp_obj_uctypes_struct_t *self = self_in; + + if (value == MP_OBJ_NULL) { + // delete + return MP_OBJ_NULL; // op not supported + } else if (value == MP_OBJ_SENTINEL) { + // load + if (!MP_OBJ_IS_TYPE(self->desc, &mp_type_tuple)) { + nlr_raise(mp_obj_new_exception_msg(&mp_type_TypeError, "struct: cannot index")); + } + + mp_obj_tuple_t *t = (mp_obj_tuple_t*)self->desc; + mp_int_t offset = MP_OBJ_SMALL_INT_VALUE(t->items[0]); + uint agg_type = GET_TYPE(offset, AGG_TYPE_BITS); + + mp_int_t index = MP_OBJ_SMALL_INT_VALUE(index_in); + + if (agg_type == ARRAY) { + mp_int_t arr_sz = MP_OBJ_SMALL_INT_VALUE(t->items[1]); + uint val_type = GET_TYPE(arr_sz, VAL_TYPE_BITS); + arr_sz &= VALUE_MASK(VAL_TYPE_BITS); + if (index >= arr_sz) { + nlr_raise(mp_obj_new_exception_msg(&mp_type_IndexError, "struct: index out of range")); + } + + if (t->len == 2) { + byte *p = self->addr + GET_SCALAR_SIZE(val_type) * index; + return get_unaligned(val_type, p, self->flags); + } else { + mp_uint_t dummy = 0; + mp_uint_t size = uctypes_struct_size(t->items[2], &dummy); + mp_obj_uctypes_struct_t *o = m_new_obj(mp_obj_uctypes_struct_t); + o->base.type = &uctypes_struct_type; + o->desc = t->items[2]; + o->addr = self->addr + size * index; + o->flags = self->flags; + return o; + } + } else if (agg_type == PTR) { + byte *p = *(void**)self->addr; + if (MP_OBJ_IS_SMALL_INT(t->items[1])) { + uint val_type = GET_TYPE(MP_OBJ_SMALL_INT_VALUE(t->items[1]), VAL_TYPE_BITS); + return get_aligned(val_type, p, index); + } else { + mp_uint_t dummy = 0; + mp_uint_t size = uctypes_struct_size(t->items[1], &dummy); + mp_obj_uctypes_struct_t *o = m_new_obj(mp_obj_uctypes_struct_t); + o->base.type = &uctypes_struct_type; + o->desc = t->items[1]; + o->addr = p + size * index; + o->flags = self->flags; + return o; + } + } + + assert(0); + } else { + // store + return MP_OBJ_NULL; // op not supported + } +} + +/// \function addressof() +/// Return address of object's data (applies to object providing buffer +/// interface). +mp_obj_t uctypes_struct_addressof(mp_obj_t buf) { + mp_buffer_info_t bufinfo; + mp_get_buffer_raise(buf, &bufinfo, MP_BUFFER_READ); + return mp_obj_new_int((mp_int_t)bufinfo.buf); +} +MP_DEFINE_CONST_FUN_OBJ_1(uctypes_struct_addressof_obj, uctypes_struct_addressof); + +/// \function bytearray_at() +/// Capture memory at given address of given size as bytearray. Memory is +/// captured by reference (and thus memory pointed by bytearray may change +/// or become invalid at later time). Use bytes_at() to capture by value. +mp_obj_t uctypes_struct_bytearray_at(mp_obj_t ptr, mp_obj_t size) { + return mp_obj_new_bytearray_by_ref(mp_obj_int_get(size), (void*)mp_obj_int_get(ptr)); +} +MP_DEFINE_CONST_FUN_OBJ_2(uctypes_struct_bytearray_at_obj, uctypes_struct_bytearray_at); + +/// \function bytes_at() +/// Capture memory at given address of given size as bytes. Memory is +/// captured by value, i.e. copied. Use bytearray_at() to capture by reference +/// ("zero copy"). +mp_obj_t uctypes_struct_bytes_at(mp_obj_t ptr, mp_obj_t size) { + return mp_obj_new_bytes((void*)mp_obj_int_get(ptr), mp_obj_int_get(size)); +} +MP_DEFINE_CONST_FUN_OBJ_2(uctypes_struct_bytes_at_obj, uctypes_struct_bytes_at); + + +STATIC const mp_obj_type_t uctypes_struct_type = { + { &mp_type_type }, + .name = MP_QSTR_struct, + .print = uctypes_struct_print, + .make_new = uctypes_struct_make_new, + .load_attr = uctypes_struct_load_attr, + .store_attr = uctypes_struct_store_attr, + .subscr = uctypes_struct_subscr, +}; + +STATIC const mp_map_elem_t mp_module_uctypes_globals_table[] = { + { MP_OBJ_NEW_QSTR(MP_QSTR___name__), MP_OBJ_NEW_QSTR(MP_QSTR_uctypes) }, + { MP_OBJ_NEW_QSTR(MP_QSTR_struct), (mp_obj_t)&uctypes_struct_type }, + { MP_OBJ_NEW_QSTR(MP_QSTR_sizeof), (mp_obj_t)&uctypes_struct_sizeof_obj }, + { MP_OBJ_NEW_QSTR(MP_QSTR_addressof), (mp_obj_t)&uctypes_struct_addressof_obj }, + { MP_OBJ_NEW_QSTR(MP_QSTR_bytes_at), (mp_obj_t)&uctypes_struct_bytes_at_obj }, + { MP_OBJ_NEW_QSTR(MP_QSTR_bytearray_at), (mp_obj_t)&uctypes_struct_bytearray_at_obj }, + + /// \moduleref uctypes + + /// \constant NATIVE - Native structure layout - native endianness, + /// platform-specific field alignment + { MP_OBJ_NEW_QSTR(MP_QSTR_NATIVE), MP_OBJ_NEW_SMALL_INT(LAYOUT_NATIVE) }, + /// \constant LITTLE_ENDIAN - Little-endian structure layout, tightly packed + /// (no alignment constraints) + { MP_OBJ_NEW_QSTR(MP_QSTR_LITTLE_ENDIAN), MP_OBJ_NEW_SMALL_INT(LAYOUT_LITTLE_ENDIAN) }, + /// \constant BIG_ENDIAN - Big-endian structure layout, tightly packed + /// (no alignment constraints) + { MP_OBJ_NEW_QSTR(MP_QSTR_BIG_ENDIAN), MP_OBJ_NEW_SMALL_INT(LAYOUT_BIG_ENDIAN) }, + + /// \constant VOID - void value type, may be used only as pointer target type. + { MP_OBJ_NEW_QSTR(MP_QSTR_VOID), MP_OBJ_NEW_SMALL_INT(TYPE2SMALLINT(UINT8, VAL_TYPE_BITS)) }, + + /// \constant UINT8 - uint8_t value type + { MP_OBJ_NEW_QSTR(MP_QSTR_UINT8), MP_OBJ_NEW_SMALL_INT(TYPE2SMALLINT(UINT8, 4)) }, + /// \constant INT8 - int8_t value type + { MP_OBJ_NEW_QSTR(MP_QSTR_INT8), MP_OBJ_NEW_SMALL_INT(TYPE2SMALLINT(INT8, 4)) }, + /// \constant UINT16 - uint16_t value type + { MP_OBJ_NEW_QSTR(MP_QSTR_UINT16), MP_OBJ_NEW_SMALL_INT(TYPE2SMALLINT(UINT16, 4)) }, + /// \constant INT16 - int16_t value type + { MP_OBJ_NEW_QSTR(MP_QSTR_INT16), MP_OBJ_NEW_SMALL_INT(TYPE2SMALLINT(INT16, 4)) }, + /// \constant UINT32 - uint32_t value type + { MP_OBJ_NEW_QSTR(MP_QSTR_UINT32), MP_OBJ_NEW_SMALL_INT(TYPE2SMALLINT(UINT32, 4)) }, + /// \constant INT32 - int32_t value type + { MP_OBJ_NEW_QSTR(MP_QSTR_INT32), MP_OBJ_NEW_SMALL_INT(TYPE2SMALLINT(INT32, 4)) }, + /// \constant UINT64 - uint64_t value type + { MP_OBJ_NEW_QSTR(MP_QSTR_UINT64), MP_OBJ_NEW_SMALL_INT(TYPE2SMALLINT(UINT64, 4)) }, + /// \constant INT64 - int64_t value type + { MP_OBJ_NEW_QSTR(MP_QSTR_INT64), MP_OBJ_NEW_SMALL_INT(TYPE2SMALLINT(INT64, 4)) }, + + { MP_OBJ_NEW_QSTR(MP_QSTR_BFUINT8), MP_OBJ_NEW_SMALL_INT(TYPE2SMALLINT(BFUINT8, 4)) }, + { MP_OBJ_NEW_QSTR(MP_QSTR_BFINT8), MP_OBJ_NEW_SMALL_INT(TYPE2SMALLINT(BFINT8, 4)) }, + { MP_OBJ_NEW_QSTR(MP_QSTR_BFUINT16), MP_OBJ_NEW_SMALL_INT(TYPE2SMALLINT(BFUINT16, 4)) }, + { MP_OBJ_NEW_QSTR(MP_QSTR_BFINT16), MP_OBJ_NEW_SMALL_INT(TYPE2SMALLINT(BFINT16, 4)) }, + { MP_OBJ_NEW_QSTR(MP_QSTR_BFUINT32), MP_OBJ_NEW_SMALL_INT(TYPE2SMALLINT(BFUINT32, 4)) }, + { MP_OBJ_NEW_QSTR(MP_QSTR_BFINT32), MP_OBJ_NEW_SMALL_INT(TYPE2SMALLINT(BFINT32, 4)) }, + + { MP_OBJ_NEW_QSTR(MP_QSTR_PTR), MP_OBJ_NEW_SMALL_INT(TYPE2SMALLINT(PTR, AGG_TYPE_BITS)) }, + { MP_OBJ_NEW_QSTR(MP_QSTR_ARRAY), MP_OBJ_NEW_SMALL_INT(TYPE2SMALLINT(ARRAY, AGG_TYPE_BITS)) }, +}; + +STATIC const mp_obj_dict_t mp_module_uctypes_globals = { + .base = {&mp_type_dict}, + .map = { + .all_keys_are_qstrs = 1, + .table_is_fixed_array = 1, + .used = MP_ARRAY_SIZE(mp_module_uctypes_globals_table), + .alloc = MP_ARRAY_SIZE(mp_module_uctypes_globals_table), + .table = (mp_map_elem_t*)mp_module_uctypes_globals_table, + }, +}; + +const mp_obj_module_t mp_module_uctypes = { + .base = { &mp_type_module }, + .name = MP_QSTR_uctypes, + .globals = (mp_obj_dict_t*)&mp_module_uctypes_globals, +}; + +#endif diff --git a/py/builtin.h b/py/builtin.h index 2e4d2023d0..361cef604e 100644 --- a/py/builtin.h +++ b/py/builtin.h @@ -80,3 +80,6 @@ extern const mp_obj_module_t mp_module_micropython; extern const mp_obj_module_t mp_module_struct; extern const mp_obj_module_t mp_module_sys; extern const mp_obj_module_t mp_module_gc; + +// extmod modules +extern const mp_obj_module_t mp_module_uctypes; diff --git a/py/builtintables.c b/py/builtintables.c index c42cdf89bb..ff530b93be 100644 --- a/py/builtintables.c +++ b/py/builtintables.c @@ -190,6 +190,12 @@ STATIC const mp_map_elem_t mp_builtin_module_table[] = { { MP_OBJ_NEW_QSTR(MP_QSTR_gc), (mp_obj_t)&mp_module_gc }, #endif + // extmod modules + +#if MICROPY_PY_UCTYPES + { MP_OBJ_NEW_QSTR(MP_QSTR_uctypes), (mp_obj_t)&mp_module_uctypes }, +#endif + // extra builtin modules as defined by a port MICROPY_PORT_BUILTIN_MODULES }; diff --git a/py/compile.c b/py/compile.c index c5f2166003..e89554a4fc 100644 --- a/py/compile.c +++ b/py/compile.c @@ -104,6 +104,9 @@ STATIC void compile_syntax_error(compiler_t *comp, mp_parse_node_t pn, const cha } STATIC const mp_map_elem_t mp_constants_table[] = { + #if MICROPY_PY_UCTYPES + { MP_OBJ_NEW_QSTR(MP_QSTR_uctypes), (mp_obj_t)&mp_module_uctypes }, + #endif // Extra constants as defined by a port MICROPY_PORT_CONSTANTS }; diff --git a/py/mpconfig.h b/py/mpconfig.h index c04c69c639..99d697f9ad 100644 --- a/py/mpconfig.h +++ b/py/mpconfig.h @@ -351,6 +351,12 @@ typedef double mp_float_t; #define MICROPY_PY_SYS_STDFILES (0) #endif + +// Extended modules +#ifndef MICROPY_PY_UCTYPES +#define MICROPY_PY_UCTYPES (0) +#endif + /*****************************************************************************/ /* Hooks for a port to add builtins */ diff --git a/py/py.mk b/py/py.mk index 549c35d320..1615c4bbaa 100644 --- a/py/py.mk +++ b/py/py.mk @@ -102,6 +102,7 @@ PY_O_BASENAME = \ repl.o \ smallint.o \ pfenv.o \ + ../extmod/moductypes.o # prepend the build destination prefix to the py object files PY_O = $(addprefix $(PY_BUILD)/, $(PY_O_BASENAME)) diff --git a/py/qstrdefs.h b/py/qstrdefs.h index 6fbfabde67..cb40bdb835 100644 --- a/py/qstrdefs.h +++ b/py/qstrdefs.h @@ -363,6 +363,43 @@ Q(pack) Q(unpack) #endif +#if MICROPY_PY_UCTYPES +Q(uctypes) +Q(sizeof) +Q(addressof) +Q(bytes_at) +Q(bytearray_at) + +Q(NATIVE) +Q(LITTLE_ENDIAN) +Q(BIG_ENDIAN) + +Q(VOID) + +Q(UINT8) +Q(INT8) +Q(UINT16) +Q(INT16) +Q(UINT32) +Q(INT32) +Q(UINT64) +Q(INT64) + +Q(BFUINT8) +Q(BFINT8) +Q(BFUINT16) +Q(BFINT16) +Q(BFUINT32) +Q(BFINT32) + +Q(FLOAT32) +Q(FLOAT64) + +Q(ARRAY) +Q(PTR) +//Q(BITFIELD) +#endif + #if MICROPY_PY_IO Q(_io) Q(readall) diff --git a/tests/extmod/uctypes_le.py b/tests/extmod/uctypes_le.py new file mode 100644 index 0000000000..0e3bd9a82d --- /dev/null +++ b/tests/extmod/uctypes_le.py @@ -0,0 +1,68 @@ +import uctypes + +desc = { + "s0": uctypes.UINT16 | 0, + "sub": (0, { + "b0": uctypes.UINT8 | 0, + "b1": uctypes.UINT8 | 1, + }), + "arr": (uctypes.ARRAY | 0, uctypes.UINT8 | 2), + "arr2": (uctypes.ARRAY | 0, 2, {"b": uctypes.UINT8 | 0}), + "bitf0": uctypes.BFUINT16 | 0 | 0 << 17 | 8 << 22, + "bitf1": uctypes.BFUINT16 | 0 | 8 << 17 | 8 << 22, + + "bf0": uctypes.BFUINT16 | 0 | 0 << 17 | 4 << 22, + "bf1": uctypes.BFUINT16 | 0 | 4 << 17 | 4 << 22, + "bf2": uctypes.BFUINT16 | 0 | 8 << 17 | 4 << 22, + "bf3": uctypes.BFUINT16 | 0 | 12 << 17 | 4 << 22, + + "ptr": (uctypes.PTR | 0, uctypes.UINT8), + "ptr2": (uctypes.PTR | 0, {"b": uctypes.UINT8 | 0}), +} + +data = bytearray(b"01") + +S = uctypes.struct(desc, uctypes.addressof(data), uctypes.LITTLE_ENDIAN) + +#print(S) +print(hex(S.s0)) +assert hex(S.s0) == "0x3130" +#print(S.sub.b0) +print(S.sub.b0, S.sub.b1) +assert S.sub.b0, S.sub.b1 == (0x30, 0x31) + +try: + S[0] + assert False, "Can't index struct" +except TypeError: + print("TypeError") + +print("arr:", S.arr[0], S.arr[1]) +assert (S.arr[0], S.arr[1]) == (0x30, 0x31) + +print("arr of struct:", S.arr2[0].b, S.arr2[1].b) +assert (S.arr2[0].b, S.arr2[1].b) == (0x30, 0x31) + + +try: + S.arr[2] + assert False, "Out of bounds index" +except IndexError: + print("IndexError") + +print("bf:", S.bitf0, S.bitf1) +assert (S.bitf0, S.bitf1) == (0x30, 0x31) + +print("bf 4bit:", S.bf3, S.bf2, S.bf1, S.bf0) +assert (S.bf3, S.bf2, S.bf1, S.bf0) == (3, 1, 3, 0) + + +# Write access + +S.sub.b0 = ord("2") +print(data) +assert bytes(data) == b"21" + +S.bf3 = 5 +print(data) +assert bytes(data) == b"2Q" diff --git a/tests/extmod/uctypes_le.py.exp b/tests/extmod/uctypes_le.py.exp new file mode 100644 index 0000000000..8efd7a6ea0 --- /dev/null +++ b/tests/extmod/uctypes_le.py.exp @@ -0,0 +1,10 @@ +0x3130 +48 49 +TypeError +arr: 48 49 +arr of struct: 48 49 +IndexError +bf: 48 49 +bf 4bit: 3 1 3 0 +bytearray(b'21') +bytearray(b'2Q') diff --git a/tests/extmod/uctypes_native_le.py b/tests/extmod/uctypes_native_le.py new file mode 100644 index 0000000000..7a5e38733a --- /dev/null +++ b/tests/extmod/uctypes_native_le.py @@ -0,0 +1,76 @@ +# This test is exactly like uctypes_le.py, but uses native structure layout. +# Codepaths for packed vs native structures are different. This test only works +# on little-endian machine (no matter if 32 or 64 bit). +import sys +import uctypes + +if sys.byteorder != "little": + print("SKIP") + sys.exit() + + +desc = { + "s0": uctypes.UINT16 | 0, + "sub": (0, { + "b0": uctypes.UINT8 | 0, + "b1": uctypes.UINT8 | 1, + }), + "arr": (uctypes.ARRAY | 0, uctypes.UINT8 | 2), + "arr2": (uctypes.ARRAY | 0, 2, {"b": uctypes.UINT8 | 0}), + "bitf0": uctypes.BFUINT16 | 0 | 0 << 17 | 8 << 22, + "bitf1": uctypes.BFUINT16 | 0 | 8 << 17 | 8 << 22, + + "bf0": uctypes.BFUINT16 | 0 | 0 << 17 | 4 << 22, + "bf1": uctypes.BFUINT16 | 0 | 4 << 17 | 4 << 22, + "bf2": uctypes.BFUINT16 | 0 | 8 << 17 | 4 << 22, + "bf3": uctypes.BFUINT16 | 0 | 12 << 17 | 4 << 22, + + "ptr": (uctypes.PTR | 0, uctypes.UINT8), + "ptr2": (uctypes.PTR | 0, {"b": uctypes.UINT8 | 0}), +} + +data = bytearray(b"01") + +S = uctypes.struct(desc, uctypes.addressof(data), uctypes.NATIVE) + +#print(S) +print(hex(S.s0)) +assert hex(S.s0) == "0x3130" +#print(S.sub.b0) +print(S.sub.b0, S.sub.b1) +assert S.sub.b0, S.sub.b1 == (0x30, 0x31) + +try: + S[0] + assert False, "Can't index struct" +except TypeError: + print("TypeError") + +print("arr:", S.arr[0], S.arr[1]) +assert (S.arr[0], S.arr[1]) == (0x30, 0x31) + +print("arr of struct:", S.arr2[0].b, S.arr2[1].b) +assert (S.arr2[0].b, S.arr2[1].b) == (0x30, 0x31) + + +try: + S.arr[2] + assert False, "Out of bounds index" +except IndexError: + print("IndexError") + +print("bf:", S.bitf0, S.bitf1) +assert (S.bitf0, S.bitf1) == (0x30, 0x31) + +print("bf 4bit:", S.bf3, S.bf2, S.bf1, S.bf0) +assert (S.bf3, S.bf2, S.bf1, S.bf0) == (3, 1, 3, 0) + +# Write access + +S.sub.b0 = ord("2") +print(data) +assert bytes(data) == b"21" + +S.bf3 = 5 +print(data) +assert bytes(data) == b"2Q" diff --git a/tests/extmod/uctypes_native_le.py.exp b/tests/extmod/uctypes_native_le.py.exp new file mode 100644 index 0000000000..8efd7a6ea0 --- /dev/null +++ b/tests/extmod/uctypes_native_le.py.exp @@ -0,0 +1,10 @@ +0x3130 +48 49 +TypeError +arr: 48 49 +arr of struct: 48 49 +IndexError +bf: 48 49 +bf 4bit: 3 1 3 0 +bytearray(b'21') +bytearray(b'2Q') diff --git a/tests/extmod/uctypes_ptr_le.py b/tests/extmod/uctypes_ptr_le.py new file mode 100644 index 0000000000..ef9890c3f1 --- /dev/null +++ b/tests/extmod/uctypes_ptr_le.py @@ -0,0 +1,25 @@ +import uctypes + +desc = { + "ptr": (uctypes.PTR | 0, uctypes.UINT8), + "ptr16": (uctypes.PTR | 0, uctypes.UINT16), + "ptr2": (uctypes.PTR | 0, {"b": uctypes.UINT8 | 0}), +} + +bytes = b"01" + +addr = uctypes.addressof(bytes) +buf = addr.to_bytes(4) + +S = uctypes.struct(desc, uctypes.addressof(buf), uctypes.LITTLE_ENDIAN) + +print(S.ptr[0]) +assert S.ptr[0] == ord("0") +print(S.ptr[1]) +assert S.ptr[1] == ord("1") +print(hex(S.ptr16[0])) +assert hex(S.ptr16[0]) == "0x3130" +print(S.ptr2[0].b, S.ptr2[1].b) +print (S.ptr2[0].b, S.ptr2[1].b) +print(hex(S.ptr16[0])) +assert (S.ptr2[0].b, S.ptr2[1].b) == (48, 49) diff --git a/tests/extmod/uctypes_ptr_le.py.exp b/tests/extmod/uctypes_ptr_le.py.exp new file mode 100644 index 0000000000..30d159edd1 --- /dev/null +++ b/tests/extmod/uctypes_ptr_le.py.exp @@ -0,0 +1,6 @@ +48 +49 +0x3130 +48 49 +48 49 +0x3130 diff --git a/tests/extmod/uctypes_ptr_native_le.py b/tests/extmod/uctypes_ptr_native_le.py new file mode 100644 index 0000000000..139ed3280c --- /dev/null +++ b/tests/extmod/uctypes_ptr_native_le.py @@ -0,0 +1,31 @@ +import sys +import uctypes + +if sys.byteorder != "little": + print("SKIP") + sys.exit() + + +desc = { + "ptr": (uctypes.PTR | 0, uctypes.UINT8), + "ptr16": (uctypes.PTR | 0, uctypes.UINT16), + "ptr2": (uctypes.PTR | 0, {"b": uctypes.UINT8 | 0}), +} + +bytes = b"01" + +addr = uctypes.addressof(bytes) +buf = addr.to_bytes(4) + +S = uctypes.struct(desc, uctypes.addressof(buf), uctypes.NATIVE) + +print(S.ptr[0]) +assert S.ptr[0] == ord("0") +print(S.ptr[1]) +assert S.ptr[1] == ord("1") +print(hex(S.ptr16[0])) +assert hex(S.ptr16[0]) == "0x3130" +print(S.ptr2[0].b, S.ptr2[1].b) +print (S.ptr2[0].b, S.ptr2[1].b) +print(hex(S.ptr16[0])) +assert (S.ptr2[0].b, S.ptr2[1].b) == (48, 49) diff --git a/tests/extmod/uctypes_ptr_native_le.py.exp b/tests/extmod/uctypes_ptr_native_le.py.exp new file mode 100644 index 0000000000..30d159edd1 --- /dev/null +++ b/tests/extmod/uctypes_ptr_native_le.py.exp @@ -0,0 +1,6 @@ +48 +49 +0x3130 +48 49 +48 49 +0x3130 diff --git a/tests/extmod/uctypes_sizeof_native.py b/tests/extmod/uctypes_sizeof_native.py new file mode 100644 index 0000000000..0dfbfa980a --- /dev/null +++ b/tests/extmod/uctypes_sizeof_native.py @@ -0,0 +1,53 @@ +import uctypes + +S1 = {} +assert uctypes.sizeof(S1) == 0 + +S2 = {"a": uctypes.UINT8 | 0} +assert uctypes.sizeof(S2) == 1 + +S3 = { + "a": uctypes.UINT8 | 0, + "b": uctypes.UINT8 | 1, +} +assert uctypes.sizeof(S3) == 2 + +S4 = { + "a": uctypes.UINT8 | 0, + "b": uctypes.UINT32 | 4, + "c": uctypes.UINT8 | 8, +} +assert uctypes.sizeof(S4) == 12 + +S5 = { + "a": uctypes.UINT8 | 0, + "b": uctypes.UINT32 | 4, + "c": uctypes.UINT8 | 8, + "d": uctypes.UINT32 | 0, + "sub": (4, { + "b0": uctypes.UINT8 | 0, + "b1": uctypes.UINT8 | 1, + }), +} + +assert uctypes.sizeof(S5) == 12 + +s5 = uctypes.struct(S5, 0) +assert uctypes.sizeof(s5) == 12 +assert uctypes.sizeof(s5.sub) == 2 + +S6 = { + "ptr": (uctypes.PTR | 0, uctypes.UINT8), +} +# As if there're no other arch bitnesses +assert uctypes.sizeof(S6) in (4, 8) + +S7 = { + "arr": (uctypes.ARRAY | 0, uctypes.UINT8 | 5), +} +assert uctypes.sizeof(S7) == 5 + +S8 = { + "arr": (uctypes.ARRAY | 0, 3, {"a": uctypes.UINT32 | 0, "b": uctypes.UINT8 | 4}), +} +assert uctypes.sizeof(S8) == 24 diff --git a/tests/extmod/uctypes_sizeof_native.py.exp b/tests/extmod/uctypes_sizeof_native.py.exp new file mode 100644 index 0000000000..e69de29bb2 diff --git a/unix/mpconfigport.h b/unix/mpconfigport.h index 98132ac6e7..73435863b7 100644 --- a/unix/mpconfigport.h +++ b/unix/mpconfigport.h @@ -50,6 +50,9 @@ #define MICROPY_PY_CMATH (1) #define MICROPY_PY_IO_FILEIO (1) #define MICROPY_PY_GC_COLLECT_RETVAL (1) + +#define MICROPY_PY_UCTYPES (1) + // Define to MICROPY_ERROR_REPORTING_DETAILED to get function, etc. // names in exception messages (may require more RAM). #define MICROPY_ERROR_REPORTING (MICROPY_ERROR_REPORTING_DETAILED)