moductypes: Add types.

This is an experiment. I want the usual Python guarantee that
you can't just footgun yourself by passing the wrong thing through
a pointer argument, at least as much as is practical.

But a uctypes struct doesn't have a type of its own, like Point
or Rect; you can only check that the *size* matches some
underlying expected size.

This change allows a type like Rect to be defined in the core,
and of course if it's a distinct Python type it can be checked
in the core as well.

Restore the unix tests so we can test the added core functionality.

Signed-off-by: Jeff Epler <jepler@gmail.com>
This commit is contained in:
Jeff Epler 2025-07-10 18:29:23 +01:00
parent a80f00bac2
commit 13fbd43610
6 changed files with 606 additions and 87 deletions

298
.github/workflows/ports_unix.yml vendored Normal file
View file

@ -0,0 +1,298 @@
name: unix port
on:
push:
pull_request:
paths:
- '.github/workflows/*.yml'
- 'tools/**'
- 'py/**'
- 'extmod/**'
- 'shared/**'
- 'lib/**'
- 'examples/**'
- 'mpy-cross/**'
- 'ports/unix/**'
- 'tests/**'
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
minimal:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Build
run: source tools/ci.sh && ci_unix_minimal_build
- name: Run main test suite
run: source tools/ci.sh && ci_unix_minimal_run_tests
- name: Print failures
if: failure()
run: tests/run-tests.py --print-failures
reproducible:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Build with reproducible date
run: source tools/ci.sh && ci_unix_minimal_build
env:
SOURCE_DATE_EPOCH: 1234567890
- name: Check reproducible build date
run: echo | ports/unix/build-minimal/micropython -i | grep 'on 2009-02-13;'
standard:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Build
run: source tools/ci.sh && ci_unix_standard_build
- name: Run main test suite
run: source tools/ci.sh && ci_unix_standard_run_tests
- name: Print failures
if: failure()
run: tests/run-tests.py --print-failures
standard_v2:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Build
run: source tools/ci.sh && ci_unix_standard_v2_build
- name: Run main test suite
run: source tools/ci.sh && ci_unix_standard_v2_run_tests
- name: Print failures
if: failure()
run: tests/run-tests.py --print-failures
coverage:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
# Python 3.12 is the default for ubuntu-24.04, but that has compatibility issues with settrace tests.
# Can remove this step when ubuntu-latest uses a more recent Python 3.x as the default.
with:
python-version: '3.11'
- name: Install packages
run: source tools/ci.sh && ci_unix_coverage_setup
- name: Build
run: source tools/ci.sh && ci_unix_coverage_build
- name: Run main test suite
run: source tools/ci.sh && ci_unix_coverage_run_tests
- name: Test merging .mpy files
run: source tools/ci.sh && ci_unix_coverage_run_mpy_merge_tests
- name: Build native mpy modules
run: source tools/ci.sh && ci_native_mpy_modules_build
- name: Test importing .mpy generated by mpy_ld.py
run: source tools/ci.sh && ci_unix_coverage_run_native_mpy_tests
- name: Run gcov coverage analysis
run: |
(cd ports/unix && gcov -o build-coverage/py ../../py/*.c || true)
(cd ports/unix && gcov -o build-coverage/extmod ../../extmod/*.c || true)
- name: Print failures
if: failure()
run: tests/run-tests.py --print-failures
coverage_32bit:
runs-on: ubuntu-22.04 # use 22.04 to get libffi-dev:i386
steps:
- uses: actions/checkout@v4
- name: Install packages
run: source tools/ci.sh && ci_unix_32bit_setup
- name: Build
run: source tools/ci.sh && ci_unix_coverage_32bit_build
- name: Run main test suite
run: source tools/ci.sh && ci_unix_coverage_32bit_run_tests
- name: Build native mpy modules
run: source tools/ci.sh && ci_native_mpy_modules_32bit_build
- name: Test importing .mpy generated by mpy_ld.py
run: source tools/ci.sh && ci_unix_coverage_32bit_run_native_mpy_tests
- name: Print failures
if: failure()
run: tests/run-tests.py --print-failures
nanbox:
runs-on: ubuntu-22.04 # use 22.04 to get python2, and libffi-dev:i386
steps:
- uses: actions/checkout@v4
- name: Install packages
run: source tools/ci.sh && ci_unix_32bit_setup
- name: Build
run: source tools/ci.sh && ci_unix_nanbox_build
- name: Run main test suite
run: source tools/ci.sh && ci_unix_nanbox_run_tests
- name: Print failures
if: failure()
run: tests/run-tests.py --print-failures
float:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install packages
run: source tools/ci.sh && ci_unix_float_setup
- name: Build
run: source tools/ci.sh && ci_unix_float_build
- name: Run main test suite
run: source tools/ci.sh && ci_unix_float_run_tests
- name: Print failures
if: failure()
run: tests/run-tests.py --print-failures
stackless_clang:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install packages
run: source tools/ci.sh && ci_unix_clang_setup
- name: Build
run: source tools/ci.sh && ci_unix_stackless_clang_build
- name: Run main test suite
run: source tools/ci.sh && ci_unix_stackless_clang_run_tests
- name: Print failures
if: failure()
run: tests/run-tests.py --print-failures
float_clang:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install packages
run: source tools/ci.sh && ci_unix_clang_setup
- name: Build
run: source tools/ci.sh && ci_unix_float_clang_build
- name: Run main test suite
run: source tools/ci.sh && ci_unix_float_clang_run_tests
- name: Print failures
if: failure()
run: tests/run-tests.py --print-failures
settrace_stackless:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
# Python 3.12 is the default for ubuntu-24.04, but that has compatibility issues with settrace tests.
# Can remove this step when ubuntu-latest uses a more recent Python 3.x as the default.
with:
python-version: '3.11'
- name: Build
run: source tools/ci.sh && ci_unix_settrace_stackless_build
- name: Run main test suite
run: source tools/ci.sh && ci_unix_settrace_stackless_run_tests
- name: Print failures
if: failure()
run: tests/run-tests.py --print-failures
macos:
runs-on: macos-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: '3.8'
- name: Build
run: source tools/ci.sh && ci_unix_macos_build
- name: Run tests
run: source tools/ci.sh && ci_unix_macos_run_tests
- name: Print failures
if: failure()
run: tests/run-tests.py --print-failures
qemu_mips:
# ubuntu-22.04 is needed for older libffi.
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v4
- name: Install packages
run: source tools/ci.sh && ci_unix_qemu_mips_setup
- name: Build
run: source tools/ci.sh && ci_unix_qemu_mips_build
- name: Run main test suite
run: source tools/ci.sh && ci_unix_qemu_mips_run_tests
- name: Print failures
if: failure()
run: tests/run-tests.py --print-failures
qemu_arm:
# ubuntu-22.04 is needed for older libffi.
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v4
- name: Install packages
run: source tools/ci.sh && ci_unix_qemu_arm_setup
- name: Build
run: source tools/ci.sh && ci_unix_qemu_arm_build
- name: Run main test suite
run: source tools/ci.sh && ci_unix_qemu_arm_run_tests
- name: Print failures
if: failure()
run: tests/run-tests.py --print-failures
qemu_riscv64:
# ubuntu-22.04 is needed for older libffi.
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v4
- name: Install packages
run: source tools/ci.sh && ci_unix_qemu_riscv64_setup
- name: Build
run: source tools/ci.sh && ci_unix_qemu_riscv64_build
- name: Run main test suite
run: source tools/ci.sh && ci_unix_qemu_riscv64_run_tests
- name: Print failures
if: failure()
run: tests/run-tests.py --print-failures
sanitize_address:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
# Python 3.12 is the default for ubuntu-24.04, but that has compatibility issues with settrace tests.
# Can remove this step when ubuntu-latest uses a more recent Python 3.x as the default.
with:
python-version: '3.11'
- name: Install packages
run: source tools/ci.sh && ci_unix_coverage_setup
- name: Build
run: source tools/ci.sh && ci_unix_sanitize_address_build
- name: Run main test suite
run: source tools/ci.sh && ci_unix_sanitize_address_run_tests
- name: Test merging .mpy files
run: source tools/ci.sh && ci_unix_coverage_run_mpy_merge_tests
- name: Build native mpy modules
run: source tools/ci.sh && ci_native_mpy_modules_build
- name: Test importing .mpy generated by mpy_ld.py
run: source tools/ci.sh && ci_unix_coverage_run_native_mpy_tests
- name: Print failures
if: failure()
run: tests/run-tests.py --print-failures
sanitize_undefined:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
# Python 3.12 is the default for ubuntu-24.04, but that has compatibility issues with settrace tests.
# Can remove this step when ubuntu-latest uses a more recent Python 3.x as the default.
with:
python-version: '3.11'
- name: Install packages
run: source tools/ci.sh && ci_unix_coverage_setup
- name: Build
run: source tools/ci.sh && ci_unix_sanitize_undefined_build
- name: Run main test suite
run: source tools/ci.sh && ci_unix_sanitize_undefined_run_tests
- name: Test merging .mpy files
run: source tools/ci.sh && ci_unix_coverage_run_mpy_merge_tests
- name: Build native mpy modules
run: source tools/ci.sh && ci_native_mpy_modules_build
- name: Test importing .mpy generated by mpy_ld.py
run: source tools/ci.sh && ci_unix_coverage_run_native_mpy_tests
- name: Print failures
if: failure()
run: tests/run-tests.py --print-failures

View file

@ -31,49 +31,12 @@
#include "py/runtime.h"
#include "py/objtuple.h"
#include "py/binary.h"
#include "extmod/moductypes.h"
#if MICROPY_PY_UCTYPES
// The uctypes module allows defining the layout of a raw data structure (using
// terms of the 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.
#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
#define LEN_BITS (OFFSET_BITS + BITF_OFF_BITS)
#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,
};
// 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)
#define IS_SCALAR_ARRAY(tuple_desc) ((tuple_desc)->len == 2)
// We cannot apply the below to INT8, as their range [-128, 127]
@ -82,6 +45,9 @@ enum {
// "struct" in uctypes context means "structural", i.e. aggregate, type.
static const mp_obj_type_t uctypes_struct_type;
// Get size of any type descriptor
static mp_uint_t uctypes_struct_size(mp_obj_t desc_in, int layout_type, mp_uint_t *max_field_size);
typedef struct _mp_obj_uctypes_struct_t {
mp_obj_base_t base;
mp_obj_t desc;
@ -93,19 +59,106 @@ static MP_NORETURN void syntax_error(void) {
mp_raise_TypeError(MP_ERROR_TEXT("syntax error in uctypes descriptor"));
}
static mp_obj_t uctypes_struct_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *args) {
mp_arg_check_num(n_args, n_kw, 2, 3, false);
mp_obj_uctypes_struct_t *o = mp_obj_malloc(mp_obj_uctypes_struct_t, type);
o->addr = (void *)(uintptr_t)mp_obj_get_int_truncated(args[0]);
o->desc = args[1];
o->flags = LAYOUT_NATIVE;
if (n_args == 3) {
o->flags = mp_obj_get_int(args[2]);
static bool is_struct_type(mp_obj_t obj_in) {
if (!mp_obj_is_type(obj_in, &mp_type_type)) {
return false;
}
mp_make_new_fun_t make_new = MP_OBJ_TYPE_GET_SLOT_OR_NULL((mp_obj_type_t *)MP_OBJ_TO_PTR(obj_in), make_new);
return make_new == uctypes_struct_type_make_new;
}
mp_obj_t uctypes_struct_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *args) {
mp_arg_check_num(n_args, n_kw, 2, 3, false);
mp_obj_t desc = args[1];
int flags = LAYOUT_NATIVE;
if (is_struct_type(desc)) {
type = MP_OBJ_TO_PTR(desc);
mp_obj_ctypes_struct_type_t *struct_type = (mp_obj_ctypes_struct_type_t *)type;
desc = struct_type->desc;
} else if (n_args == 3) {
flags = mp_obj_get_int(args[2]);
}
mp_buffer_info_t bufinfo;
mp_obj_uctypes_struct_t *o = mp_obj_malloc(mp_obj_uctypes_struct_t, type);
if (mp_get_buffer(args[0], &bufinfo, MP_BUFFER_READ)) {
o->addr = bufinfo.buf;
} else {
o->addr = (void *)(uintptr_t)mp_obj_get_int_truncated(args[0]);
}
o->desc = desc;
o->flags = flags;
return MP_OBJ_FROM_PTR(o);
}
static void uctypes_struct_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) {
static bool is_ptr(mp_obj_t desc) {
if (!mp_obj_is_type(desc, &mp_type_tuple)) {
return false;
}
mp_obj_tuple_t *t = MP_OBJ_TO_PTR(desc);
mp_int_t offset = MP_OBJ_SMALL_INT_VALUE(t->items[0]);
uint agg_type = GET_TYPE(offset, AGG_TYPE_BITS);
return agg_type == PTR;
}
mp_obj_t uctypes_struct_type_make_new(const mp_obj_type_t *type_in, size_t n_args, size_t n_kw, const mp_obj_t *args) {
mp_obj_ctypes_struct_type_t *type = (mp_obj_ctypes_struct_type_t *)type_in;
mp_obj_t desc = type->desc;
mp_uint_t max_field_size = 0;
mp_uint_t size = uctypes_struct_size(desc, type->struct_flags, &max_field_size);
mp_buffer_info_t bufinfo;
mp_obj_t bytearray = mp_obj_new_bytearray(size, NULL);
mp_get_buffer_raise(bytearray, &bufinfo, MP_BUFFER_WRITE);
if (is_ptr(desc)) {
if (n_args != 1) {
mp_raise_TypeError(NULL);
}
if (mp_obj_is_int(args[0])) {
void *ptr = (void *)(uintptr_t)mp_obj_get_int_truncated(args[0]);
*(void **)bufinfo.buf = ptr;
} else {
mp_buffer_info_t pointee;
mp_get_buffer_raise(args[0], &pointee, MP_BUFFER_WRITE);
*(void **)bufinfo.buf = pointee.buf;
}
}
mp_obj_t args1[] = {mp_obj_new_int((intptr_t)bufinfo.buf), desc, mp_obj_new_int(type->struct_flags)};
mp_obj_t result = uctypes_struct_make_new(type_in, 3, 0, args1);
if (mp_obj_is_type(desc, &mp_type_tuple)) {
mp_obj_tuple_t *t = MP_OBJ_TO_PTR(desc);
mp_int_t offset = MP_OBJ_SMALL_INT_VALUE(t->items[0]);
uint agg_type = GET_TYPE(offset, AGG_TYPE_BITS);
if (agg_type == ARRAY) {
for (size_t i = 0; i < n_args; i++) {
mp_obj_subscr(result, mp_obj_new_int(i), args[i]);
}
} else if (agg_type != PTR) {
syntax_error();
}
} else {
mp_obj_dict_t *d = MP_OBJ_TO_PTR(desc);
// only for packed ROM tables..
assert(d->map.alloc == d->map.used);
assert(d->map.all_keys_are_qstrs);
if (n_args > d->map.alloc) {
mp_raise_msg(&mp_type_IndexError, MP_ERROR_TEXT("struct: index out of range"));
}
for (size_t i = 0; i < n_args; i++) {
mp_store_attr(result, mp_obj_str_get_qstr(d->map.table[i].key), args[i]);
}
}
args += n_args;
for (size_t i = 0; i < n_kw; i += 2) {
mp_store_attr(result, mp_obj_str_get_qstr(args[i]), args[i + 1]);
}
return result;
}
#define mp_obj_get_type_qstr(o_in) ((qstr)(mp_obj_get_type((o_in))->name))
void uctypes_struct_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) {
(void)kind;
mp_obj_uctypes_struct_t *self = MP_OBJ_TO_PTR(self_in);
const char *typen = "unk";
@ -126,11 +179,9 @@ static void uctypes_struct_print(const mp_print_t *print, mp_obj_t self_in, mp_p
} else {
typen = "ERROR";
}
mp_printf(print, "<struct %s %p>", typen, self->addr);
}
// Get size of any type descriptor
static mp_uint_t uctypes_struct_size(mp_obj_t desc_in, int layout_type, mp_uint_t *max_field_size);
mp_printf(print, "<%q %s %p>", (qstr)mp_obj_get_type_qstr(self_in), typen, self->addr);
}
// Get size of scalar type descriptor
static inline mp_uint_t uctypes_struct_scalar_size(int val_type) {
@ -195,6 +246,10 @@ static mp_uint_t uctypes_struct_agg_size(mp_obj_tuple_t *t, int layout_type, mp_
}
static mp_uint_t uctypes_struct_size(mp_obj_t desc_in, int layout_type, mp_uint_t *max_field_size) {
if (is_struct_type(desc_in)) {
mp_obj_ctypes_struct_type_t *struct_type = MP_OBJ_TO_PTR(desc_in);
desc_in = struct_type->desc;
}
if (!mp_obj_is_dict_or_ordereddict(desc_in)) {
if (mp_obj_is_type(desc_in, &mp_type_tuple)) {
return uctypes_struct_agg_size((mp_obj_tuple_t *)MP_OBJ_TO_PTR(desc_in), layout_type, max_field_size);
@ -253,20 +308,22 @@ static mp_uint_t uctypes_struct_size(mp_obj_t desc_in, int layout_type, mp_uint_
static mp_obj_t uctypes_struct_sizeof(size_t n_args, const mp_obj_t *args) {
mp_obj_t obj_in = args[0];
mp_uint_t max_field_size = 0;
if (mp_obj_is_type(obj_in, &mp_type_bytearray)) {
return mp_obj_len(obj_in);
}
int layout_type = LAYOUT_NATIVE;
// We can apply sizeof either to structure definition (a dict)
// or to instantiated structure
if (mp_obj_is_type(obj_in, &uctypes_struct_type)) {
mp_buffer_info_t bufinfo;
if (mp_get_buffer(obj_in, &bufinfo, MP_BUFFER_READ)) {
if (n_args != 1) {
mp_raise_TypeError(NULL);
}
// Extract structure definition
mp_obj_uctypes_struct_t *obj = MP_OBJ_TO_PTR(obj_in);
return MP_OBJ_NEW_SMALL_INT(bufinfo.len);
}
int layout_type = LAYOUT_NATIVE;
// We can apply sizeof to structure definition (a dict) or a "struct type" type
if (is_struct_type(obj_in)) {
if (n_args != 1) {
mp_raise_TypeError(NULL);
}
mp_obj_ctypes_struct_type_t *obj = MP_OBJ_TO_PTR(obj_in);
obj_in = obj->desc;
layout_type = obj->flags;
layout_type = obj->struct_flags;
} else {
if (n_args == 2) {
layout_type = mp_obj_get_int(args[1]);
@ -277,6 +334,15 @@ static mp_obj_t uctypes_struct_sizeof(size_t n_args, const mp_obj_t *args) {
}
static MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(uctypes_struct_sizeof_obj, 1, 2, uctypes_struct_sizeof);
static mp_obj_t uctypes_struct_desc(mp_obj_t arg) {
if (!is_struct_type(arg)) {
mp_raise_TypeError(NULL);
}
mp_obj_ctypes_struct_type_t *struct_type = MP_OBJ_TO_PTR(arg);
return struct_type->desc;
}
MP_DEFINE_CONST_FUN_OBJ_1(uctypes_struct_desc_obj, uctypes_struct_desc);
static const char type2char[16] = {
'B', 'b', 'H', 'h', 'I', 'i', 'Q', 'q',
'-', '-', '-', '-', '-', '-', 'f', 'd'
@ -478,11 +544,8 @@ static mp_obj_t uctypes_struct_attr_op(mp_obj_t self_in, qstr attr, mp_obj_t set
switch (agg_type) {
case STRUCT: {
mp_obj_uctypes_struct_t *o = mp_obj_malloc(mp_obj_uctypes_struct_t, &uctypes_struct_type);
o->desc = sub->items[1];
o->addr = self->addr + offset;
o->flags = self->flags;
return MP_OBJ_FROM_PTR(o);
mp_obj_t args[] = { mp_obj_new_int((mp_uint_t)(uintptr_t)self->addr + offset), sub->items[1], mp_obj_new_int(self->flags) };
return uctypes_struct_make_new(&uctypes_struct_type, MP_ARRAY_SIZE(args), 0, args);
}
case ARRAY: {
mp_uint_t dummy;
@ -493,11 +556,8 @@ static mp_obj_t uctypes_struct_attr_op(mp_obj_t self_in, qstr attr, mp_obj_t set
MP_FALLTHROUGH
}
case PTR: {
mp_obj_uctypes_struct_t *o = mp_obj_malloc(mp_obj_uctypes_struct_t, &uctypes_struct_type);
o->desc = MP_OBJ_FROM_PTR(sub);
o->addr = self->addr + offset;
o->flags = self->flags;
return MP_OBJ_FROM_PTR(o);
mp_obj_t args[] = { mp_obj_new_int((mp_uint_t)(uintptr_t)self->addr + offset), deref, mp_obj_new_int(self->flags) };
return uctypes_struct_make_new(&uctypes_struct_type, MP_ARRAY_SIZE(args), 0, args);
}
}
@ -505,7 +565,7 @@ static mp_obj_t uctypes_struct_attr_op(mp_obj_t self_in, qstr attr, mp_obj_t set
return MP_OBJ_NULL;
}
static void uctypes_struct_attr(mp_obj_t self_in, qstr attr, mp_obj_t *dest) {
void uctypes_struct_attr(mp_obj_t self_in, qstr attr, mp_obj_t *dest) {
if (dest[0] == MP_OBJ_NULL) {
// load attribute
mp_obj_t val = uctypes_struct_attr_op(self_in, attr, MP_OBJ_NULL);
@ -518,7 +578,7 @@ static void uctypes_struct_attr(mp_obj_t self_in, qstr attr, mp_obj_t *dest) {
}
}
static mp_obj_t uctypes_struct_subscr(mp_obj_t self_in, mp_obj_t index_in, mp_obj_t value) {
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 = MP_OBJ_TO_PTR(self_in);
if (value == MP_OBJ_NULL) {
@ -565,11 +625,8 @@ static mp_obj_t uctypes_struct_subscr(mp_obj_t self_in, mp_obj_t index_in, mp_ob
} else if (value == MP_OBJ_SENTINEL) {
mp_uint_t dummy = 0;
mp_uint_t size = uctypes_struct_size(t->items[2], self->flags, &dummy);
mp_obj_uctypes_struct_t *o = mp_obj_malloc(mp_obj_uctypes_struct_t, &uctypes_struct_type);
o->desc = t->items[2];
o->addr = self->addr + size * index;
o->flags = self->flags;
return MP_OBJ_FROM_PTR(o);
mp_obj_t args[] = { mp_obj_new_int((mp_uint_t)(uintptr_t)self->addr + size * index), t->items[2], mp_obj_new_int(self->flags) };
return uctypes_struct_make_new(&uctypes_struct_type, MP_ARRAY_SIZE(args), 0, args);
} else {
return MP_OBJ_NULL; // op not supported
}
@ -578,15 +635,17 @@ static mp_obj_t uctypes_struct_subscr(mp_obj_t self_in, mp_obj_t index_in, mp_ob
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);
if (value == MP_OBJ_SENTINEL) {
return get_aligned(val_type, p, index);
} else {
set_aligned(val_type, p, index, value);
return value; // just !MP_OBJ_NULL
}
} else {
mp_uint_t dummy = 0;
mp_uint_t size = uctypes_struct_size(t->items[1], self->flags, &dummy);
mp_obj_uctypes_struct_t *o = mp_obj_malloc(mp_obj_uctypes_struct_t, &uctypes_struct_type);
o->desc = t->items[1];
o->addr = p + size * index;
o->flags = self->flags;
return MP_OBJ_FROM_PTR(o);
mp_obj_t args[] = { mp_obj_new_int((mp_uint_t)(uintptr_t)p + size * index), t->items[1], mp_obj_new_int(self->flags) };
return uctypes_struct_make_new(&uctypes_struct_type, MP_ARRAY_SIZE(args), 0, args);
}
}
@ -595,7 +654,7 @@ static mp_obj_t uctypes_struct_subscr(mp_obj_t self_in, mp_obj_t index_in, mp_ob
}
}
static mp_obj_t uctypes_struct_unary_op(mp_unary_op_t op, mp_obj_t self_in) {
mp_obj_t uctypes_struct_unary_op(mp_unary_op_t op, mp_obj_t self_in) {
mp_obj_uctypes_struct_t *self = MP_OBJ_TO_PTR(self_in);
switch (op) {
case MP_UNARY_OP_INT_MAYBE:
@ -615,7 +674,7 @@ static mp_obj_t uctypes_struct_unary_op(mp_unary_op_t op, mp_obj_t self_in) {
}
}
static mp_int_t uctypes_get_buffer(mp_obj_t self_in, mp_buffer_info_t *bufinfo, mp_uint_t flags) {
mp_int_t uctypes_get_buffer(mp_obj_t self_in, mp_buffer_info_t *bufinfo, mp_uint_t flags) {
(void)flags;
mp_obj_uctypes_struct_t *self = MP_OBJ_TO_PTR(self_in);
mp_uint_t max_field_size = 0;
@ -669,6 +728,7 @@ static const mp_rom_map_elem_t mp_module_uctypes_globals_table[] = {
{ MP_ROM_QSTR(MP_QSTR_addressof), MP_ROM_PTR(&uctypes_struct_addressof_obj) },
{ MP_ROM_QSTR(MP_QSTR_bytes_at), MP_ROM_PTR(&uctypes_struct_bytes_at_obj) },
{ MP_ROM_QSTR(MP_QSTR_bytearray_at), MP_ROM_PTR(&uctypes_struct_bytearray_at_obj) },
{ MP_ROM_QSTR(MP_QSTR_get_descr), MP_ROM_PTR(&uctypes_struct_desc_obj) },
{ MP_ROM_QSTR(MP_QSTR_NATIVE), MP_ROM_INT(LAYOUT_NATIVE) },
{ MP_ROM_QSTR(MP_QSTR_LITTLE_ENDIAN), MP_ROM_INT(LAYOUT_LITTLE_ENDIAN) },

136
extmod/moductypes.h Normal file
View file

@ -0,0 +1,136 @@
/*
* This file is part of the MicroPython project, http://micropython.org/
*
* The MIT License (MIT)
*
* Copyright (c) 2014-2018 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.
*/
#ifndef MICROPY_INCLUDED_PY_MODUCTYPES_H
#define MICROPY_INCLUDED_PY_MODUCTYPES_H
#if MICROPY_PY_UCTYPES
#include "py/obj.h"
typedef struct _mp_obj_ctypes_struct_type_t {
// This is a mp_obj_type_t with six slots.
mp_obj_empty_type_t base;
void *slots[6];
mp_obj_t desc;
uint32_t struct_flags;
} mp_obj_ctypes_struct_type_t;
void uctypes_struct_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind);
void uctypes_struct_attr(mp_obj_t self_in, qstr attr, mp_obj_t *dest);
mp_obj_t uctypes_struct_subscr(mp_obj_t self_in, mp_obj_t index_in, mp_obj_t value);
mp_obj_t uctypes_struct_unary_op(mp_unary_op_t op, mp_obj_t self_in);
mp_int_t uctypes_get_buffer(mp_obj_t self_in, mp_buffer_info_t *bufinfo, mp_uint_t flags);
mp_obj_t uctypes_struct_type_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *args);
mp_obj_t uctypes_struct_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *args);
#define MP_DECLARE_CTYPES_STRUCT(type_name) \
extern mp_obj_ctypes_struct_type_t type_name;
#define MP_DEFINE_CTYPES_STRUCT(type_name, name_, desc_, flags_) \
mp_obj_ctypes_struct_type_t type_name = { \
.base = { \
.base = { &mp_type_type }, \
.flags = MP_TYPE_FLAG_NONE, \
.name = name_, \
.slot_index_make_new = 1, \
.slot_index_print = 2, \
.slot_index_attr = 3, \
.slot_index_subscr = 4, \
.slot_index_unary_op = 5, \
.slot_index_buffer = 6, \
}, \
.slots = { \
uctypes_struct_type_make_new, \
uctypes_struct_print, \
uctypes_struct_attr, \
uctypes_struct_subscr, \
uctypes_struct_unary_op, \
uctypes_get_buffer \
}, \
.desc = desc_, \
.struct_flags = flags_, \
}
// The uctypes module allows defining the layout of a raw data structure (using
// terms of the 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.
#define LAYOUT_LITTLE_ENDIAN (0)
#define LAYOUT_BIG_ENDIAN (1)
#define LAYOUT_NATIVE (2)
#define LEN_BITS (OFFSET_BITS + BITF_OFF_BITS)
#if MICROPY_OBJ_REPR == MICROPY_OBJ_REPR_B
#define VAL_TYPE_BITS 4
#define BITF_LEN_BITS 5
#define BITF_OFF_BITS 5
#define OFFSET_BITS 16
#if VAL_TYPE_BITS + BITF_LEN_BITS + BITF_OFF_BITS + OFFSET_BITS != 30
#error Invalid encoding field length
#endif
// Here we need to set sign bit right
#define TYPE2SMALLINT(x, nbits) ((((int)x) << (32 - nbits)) >> 2)
#define GET_TYPE(x, nbits) (((x) >> (30 - nbits)) & ((1 << nbits) - 1))
#define VALUE_MASK(type_nbits) (~((int)0xc0000000 >> type_nbits))
#else
#define VAL_TYPE_BITS 4
#define BITF_LEN_BITS 5
#define BITF_OFF_BITS 5
#define OFFSET_BITS 17
#define LEN_BITS (OFFSET_BITS + BITF_OFF_BITS)
#if VAL_TYPE_BITS + BITF_LEN_BITS + BITF_OFF_BITS + OFFSET_BITS != 31
#error Invalid encoding field length
#endif
// Here we need to set sign bit right
#define TYPE2SMALLINT(x, nbits) ((((mp_int_t)x) << (32 - nbits)) >> 1)
#define GET_TYPE(x, nbits) (((x) >> (31 - nbits)) & ((1 << nbits) - 1))
#define VALUE_MASK(type_nbits) (~((int)0x80000000 >> type_nbits))
#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,
};
// Here we need to set sign bit right
#define UCTYPE_TYPE(t) (TYPE2SMALLINT((t), VAL_TYPE_BITS))
#define UCTYPE_AGG(a) (TYPE2SMALLINT((a), AGG_TYPE_BITS))
#define TYPE_AND_OFFSET(t, o) (UCTYPE_TYPE((t)) | (o))
#endif
#endif

View file

@ -10,7 +10,7 @@ data = bytearray(b"01234567")
# first argument isn't an integer
try:
uctypes.struct(data, {})
uctypes.struct([], {})
except TypeError:
print("TypeError")

View file

@ -0,0 +1,19 @@
try:
Rect
except NameError:
print("SKIP")
raise SystemExit
import uctypes
r = Rect()
print(f"{repr(r)[:13]!r}")
print(r.top, r.left, r.bottom, r.right)
r = Rect(1, 2, 3, 4)
print(r.top, r.left, r.bottom, r.right)
r = Rect(1, 2, right=4)
print(r.top, r.left, r.bottom, r.right)
r = uctypes.struct(uctypes.addressof(r), Rect)
print(f"{repr(r)[:13]!r}")
print(r.top, r.left, r.bottom, r.right)

View file

@ -0,0 +1,6 @@
'<Rect STRUCT '
0 0 0 0
1 2 3 4
1 2 0 4
'<Rect STRUCT '
1 2 0 4