From d19faefba09e7daf3ca804b5fdd091da465d7136 Mon Sep 17 00:00:00 2001 From: Jukka Lehtosalo Date: Thu, 5 Feb 2026 13:36:52 +0000 Subject: [PATCH 01/12] Add i32 read/write --- mypy/typeshed/stubs/librt/librt/strings.pyi | 2 + mypyc/lib-rt/byteswriter_extra_ops.c | 3 +- mypyc/lib-rt/byteswriter_extra_ops.h | 23 +++- mypyc/lib-rt/strings/librt_strings.c | 65 +++++++++ mypyc/lib-rt/strings/librt_strings_common.h | 30 +++++ mypyc/primitives/librt_strings_ops.py | 20 +++ mypyc/test-data/irbuild-librt-strings.test | 40 ++++++ mypyc/test-data/run-librt-strings.test | 139 +++++++++++++++++++- 8 files changed, 318 insertions(+), 4 deletions(-) diff --git a/mypy/typeshed/stubs/librt/librt/strings.pyi b/mypy/typeshed/stubs/librt/librt/strings.pyi index feb348ce59c3..9e2acb90b7bf 100644 --- a/mypy/typeshed/stubs/librt/librt/strings.pyi +++ b/mypy/typeshed/stubs/librt/librt/strings.pyi @@ -22,3 +22,5 @@ class StringWriter: def write_i16_le(b: BytesWriter, n: i16, /) -> None: ... def read_i16_le(b: bytes, index: i64, /) -> i16: ... +def write_i32_le(b: BytesWriter, n: i32, /) -> None: ... +def read_i32_le(b: bytes, index: i64, /) -> i32: ... diff --git a/mypyc/lib-rt/byteswriter_extra_ops.c b/mypyc/lib-rt/byteswriter_extra_ops.c index 69e79cf05c71..0cd37fe5f945 100644 --- a/mypyc/lib-rt/byteswriter_extra_ops.c +++ b/mypyc/lib-rt/byteswriter_extra_ops.c @@ -32,7 +32,7 @@ char CPyBytesWriter_Write(PyObject *obj, PyObject *value) { return CPY_NONE; } -int16_t CPyBytes_ReadError(int64_t index, Py_ssize_t size) { +void CPyBytes_ReadError(int64_t index, Py_ssize_t size) { if (index < 0) { PyErr_SetString(PyExc_ValueError, "index must be non-negative"); } else { @@ -40,7 +40,6 @@ int16_t CPyBytes_ReadError(int64_t index, Py_ssize_t size) { "index %lld out of range for bytes of length %zd", (long long)index, size); } - return CPY_LL_INT_ERROR; } #endif // MYPYC_EXPERIMENTAL diff --git a/mypyc/lib-rt/byteswriter_extra_ops.h b/mypyc/lib-rt/byteswriter_extra_ops.h index 737a90271889..0c38f6794f03 100644 --- a/mypyc/lib-rt/byteswriter_extra_ops.h +++ b/mypyc/lib-rt/byteswriter_extra_ops.h @@ -44,10 +44,19 @@ CPyBytesWriter_WriteI16LE(PyObject *obj, int16_t value) { return CPY_NONE; } +static inline char +CPyBytesWriter_WriteI32LE(PyObject *obj, int32_t value) { + BytesWriterObject *self = (BytesWriterObject *)obj; + if (!CPyBytesWriter_EnsureSize(self, 4)) + return CPY_NONE_ERROR; + BytesWriter_write_i32_le_unchecked(self, value); + return CPY_NONE; +} + char CPyBytesWriter_Write(PyObject *obj, PyObject *value); // Helper function for bytes read error handling (negative index or out of range) -int16_t CPyBytes_ReadError(int64_t index, Py_ssize_t size); +void CPyBytes_ReadError(int64_t index, Py_ssize_t size); // If index is negative, convert to non-negative index (no range checking) static inline int64_t CPyBytesWriter_AdjustIndex(PyObject *obj, int64_t index) { @@ -81,6 +90,18 @@ CPyBytes_ReadI16LE(PyObject *bytes_obj, int64_t index) { return read_i16_le_unchecked(data + index); } +static inline int32_t +CPyBytes_ReadI32LE(PyObject *bytes_obj, int64_t index) { + // bytes_obj type is enforced by mypyc + Py_ssize_t size = PyBytes_GET_SIZE(bytes_obj); + if (unlikely(index < 0 || index > size - 4)) { + CPyBytes_ReadError(index, size); + return CPY_LL_INT_ERROR; + } + const unsigned char *data = (const unsigned char *)PyBytes_AS_STRING(bytes_obj); + return read_i32_le_unchecked(data + index); +} + #endif // MYPYC_EXPERIMENTAL #endif diff --git a/mypyc/lib-rt/strings/librt_strings.c b/mypyc/lib-rt/strings/librt_strings.c index 6019251515af..1175049b6047 100644 --- a/mypyc/lib-rt/strings/librt_strings.c +++ b/mypyc/lib-rt/strings/librt_strings.c @@ -890,6 +890,65 @@ read_i16_le(PyObject *module, PyObject *const *args, size_t nargs) { return PyLong_FromLong(value); } +static PyObject* +write_i32_le(PyObject *module, PyObject *const *args, size_t nargs) { + if (unlikely(nargs != 2)) { + PyErr_Format(PyExc_TypeError, + "write_i32_le() takes exactly 2 arguments (%zu given)", nargs); + return NULL; + } + PyObject *writer = args[0]; + if (!check_bytes_writer(writer)) { + return NULL; + } + PyObject *value = args[1]; + int32_t unboxed = CPyLong_AsInt32(value); + if (unlikely(unboxed == CPY_LL_INT_ERROR && PyErr_Occurred())) { + // Error already set by CPyLong_AsInt32 (ValueError for overflow, TypeError for wrong type) + return NULL; + } + BytesWriterObject *bw = (BytesWriterObject *)writer; + if (unlikely(!ensure_bytes_writer_size(bw, 4))) { + return NULL; + } + BytesWriter_write_i32_le_unchecked(bw, unboxed); + Py_INCREF(Py_None); + return Py_None; +} + +static PyObject* +read_i32_le(PyObject *module, PyObject *const *args, size_t nargs) { + if (unlikely(nargs != 2)) { + PyErr_Format(PyExc_TypeError, + "read_i32_le() takes exactly 2 arguments (%zu given)", nargs); + return NULL; + } + PyObject *bytes_obj = args[0]; + if (unlikely(!PyBytes_Check(bytes_obj))) { + PyErr_SetString(PyExc_TypeError, "read_i32_le() argument 1 must be bytes"); + return NULL; + } + PyObject *index_obj = args[1]; + int64_t index = CPyLong_AsInt64(index_obj); + if (unlikely(index == CPY_LL_INT_ERROR && PyErr_Occurred())) { + return NULL; + } + if (unlikely(index < 0)) { + PyErr_SetString(PyExc_ValueError, "index must be non-negative"); + return NULL; + } + Py_ssize_t size = PyBytes_GET_SIZE(bytes_obj); + if (unlikely(index > size - 4)) { + PyErr_Format(PyExc_IndexError, + "index %lld out of range for bytes of length %zd", + (long long)index, size); + return NULL; + } + const unsigned char *data = (const unsigned char *)PyBytes_AS_STRING(bytes_obj); + int32_t value = read_i32_le_unchecked(data + index); + return PyLong_FromLong(value); +} + #endif static PyMethodDef librt_strings_module_methods[] = { @@ -900,6 +959,12 @@ static PyMethodDef librt_strings_module_methods[] = { {"read_i16_le", (PyCFunction) read_i16_le, METH_FASTCALL, PyDoc_STR("Read a 16-bit signed integer from bytes in little-endian format") }, + {"write_i32_le", (PyCFunction) write_i32_le, METH_FASTCALL, + PyDoc_STR("Write a 32-bit signed integer to BytesWriter in little-endian format") + }, + {"read_i32_le", (PyCFunction) read_i32_le, METH_FASTCALL, + PyDoc_STR("Read a 32-bit signed integer from bytes in little-endian format") + }, #endif {NULL, NULL, 0, NULL} }; diff --git a/mypyc/lib-rt/strings/librt_strings_common.h b/mypyc/lib-rt/strings/librt_strings_common.h index 9f0dd07d3cdf..99bbeebbc612 100644 --- a/mypyc/lib-rt/strings/librt_strings_common.h +++ b/mypyc/lib-rt/strings/librt_strings_common.h @@ -43,4 +43,34 @@ read_i16_le_unchecked(const unsigned char *data) { return (int16_t)uval; } +// Write a 32-bit signed integer in little-endian format to BytesWriter. +// NOTE: This does NOT check buffer capacity - caller must ensure space is available. +static inline void +BytesWriter_write_i32_le_unchecked(BytesWriterObject *self, int32_t value) { + // Store len in local to help optimizer reduce struct member accesses + Py_ssize_t len = self->len; + unsigned char *p = (unsigned char *)(self->buf + len); + uint32_t uval = (uint32_t)value; + + // Write in little-endian format + // Modern compilers optimize this pattern well, often to a single store on LE systems + p[0] = (unsigned char)uval; + p[1] = (unsigned char)(uval >> 8); + p[2] = (unsigned char)(uval >> 16); + p[3] = (unsigned char)(uval >> 24); + + self->len = len + 4; +} + +// Read a 32-bit signed integer in little-endian format from bytes. +// NOTE: This does NOT check bounds - caller must ensure valid index. +static inline int32_t +read_i32_le_unchecked(const unsigned char *data) { + // Read in little-endian format + // Modern compilers optimize this pattern well, often to a single load on LE systems + uint32_t uval = (uint32_t)data[0] | ((uint32_t)data[1] << 8) | + ((uint32_t)data[2] << 16) | ((uint32_t)data[3] << 24); + return (int32_t)uval; +} + #endif // LIBRT_STRINGS_COMMON_H diff --git a/mypyc/primitives/librt_strings_ops.py b/mypyc/primitives/librt_strings_ops.py index 5eae6aa13e83..f677cb566faf 100644 --- a/mypyc/primitives/librt_strings_ops.py +++ b/mypyc/primitives/librt_strings_ops.py @@ -97,6 +97,26 @@ dependencies=[LIBRT_STRINGS, BYTES_WRITER_EXTRA_OPS], ) +function_op( + name="librt.strings.write_i32_le", + arg_types=[bytes_writer_rprimitive, int32_rprimitive], + return_type=none_rprimitive, + c_function_name="CPyBytesWriter_WriteI32LE", + error_kind=ERR_MAGIC, + experimental=True, + dependencies=[LIBRT_STRINGS, BYTES_WRITER_EXTRA_OPS], +) + +function_op( + name="librt.strings.read_i32_le", + arg_types=[bytes_rprimitive, int64_rprimitive], + return_type=int32_rprimitive, + c_function_name="CPyBytes_ReadI32LE", + error_kind=ERR_MAGIC, + experimental=True, + dependencies=[LIBRT_STRINGS, BYTES_WRITER_EXTRA_OPS], +) + function_op( name="builtins.len", arg_types=[bytes_writer_rprimitive], diff --git a/mypyc/test-data/irbuild-librt-strings.test b/mypyc/test-data/irbuild-librt-strings.test index d1b4fd7d2381..1da51d31d31f 100644 --- a/mypyc/test-data/irbuild-librt-strings.test +++ b/mypyc/test-data/irbuild-librt-strings.test @@ -140,6 +140,46 @@ L0: r0 = CPyBytes_ReadI16LE(b, 0) return r0 +[case testLibrtStrings_i32_le_experimental_64bit] +from librt.strings import BytesWriter, write_i32_le, read_i32_le +from mypy_extensions import i32, i64 + +def test_write_i32_le(b: BytesWriter, n: i32) -> None: + write_i32_le(b, n) +def test_write_i32_le_literal(b: BytesWriter) -> None: + write_i32_le(b, 12345678) +def test_read_i32_le(b: bytes, i: i64) -> i32: + return read_i32_le(b, i) +def test_read_i32_le_literal(b: bytes) -> i32: + return read_i32_le(b, 0) +[out] +def test_write_i32_le(b, n): + b :: librt.strings.BytesWriter + n :: i32 + r0 :: None +L0: + r0 = CPyBytesWriter_WriteI32LE(b, n) + return 1 +def test_write_i32_le_literal(b): + b :: librt.strings.BytesWriter + r0 :: None +L0: + r0 = CPyBytesWriter_WriteI32LE(b, 12345678) + return 1 +def test_read_i32_le(b, i): + b :: bytes + i :: i64 + r0 :: i32 +L0: + r0 = CPyBytes_ReadI32LE(b, i) + return r0 +def test_read_i32_le_literal(b): + b :: bytes + r0 :: i32 +L0: + r0 = CPyBytes_ReadI32LE(b, 0) + return r0 + [case testLibrtStrings_StringWriter_experimental_64bit] from librt.strings import StringWriter from mypy_extensions import i32, i64 diff --git a/mypyc/test-data/run-librt-strings.test b/mypyc/test-data/run-librt-strings.test index 621c6f323566..6cb20566ef91 100644 --- a/mypyc/test-data/run-librt-strings.test +++ b/mypyc/test-data/run-librt-strings.test @@ -3,8 +3,9 @@ from typing import Any import base64 import binascii import random +import struct -from librt.strings import BytesWriter, StringWriter, write_i16_le, read_i16_le +from librt.strings import BytesWriter, StringWriter, write_i16_le, read_i16_le, write_i32_le, read_i32_le from testutil import assertRaises @@ -224,6 +225,142 @@ def test_read_i16_le_via_any() -> None: with assertRaises(TypeError, "read_i16_le() argument 1 must be bytes"): read_i16_le(bytearray(b"\x00\x00"), 0 + int()) # type: ignore +def test_bytes_writer_write_i32_le() -> None: + # Test 1-byte values (0 to 127, and -128 to -1 in terms of low byte pattern) + w = BytesWriter() + values = [0, 1, 127, -1, -128] + for v in values: + write_i32_le(w, v) + assert w.getvalue() == b"".join(struct.pack(" None: + # Test write_i32_le via Any to ensure C extension wrapper works + # (tests fallback path when not using mypyc primitives) + w: Any = BytesWriter() + + # Test 8-bit and 32-bit operations + w.append(0x42) + write_i32_le(w, 0x12345678) + w.append(0xFF) + assert w.getvalue() == b"\x42" + struct.pack(" None: + # Test 1-byte values + values = [0, 1, 127, -1, -128] + data = b"".join(struct.pack(" None: + # Test read_i32_le via Any to ensure C extension wrapper works + data: Any = struct.pack(" None: w = BytesWriter() w.write(bytearray(b"foobar")) From b185f05ed79e355133f5c2d16df1b2a6eb8cf618 Mon Sep 17 00:00:00 2001 From: Jukka Lehtosalo Date: Thu, 5 Feb 2026 13:43:01 +0000 Subject: [PATCH 02/12] Read and write i164 --- mypy/typeshed/stubs/librt/librt/strings.pyi | 2 + mypyc/lib-rt/byteswriter_extra_ops.h | 21 +++ mypyc/lib-rt/strings/librt_strings.c | 64 ++++++++ mypyc/lib-rt/strings/librt_strings_common.h | 36 +++++ mypyc/primitives/librt_strings_ops.py | 20 +++ mypyc/test-data/irbuild-librt-strings.test | 39 +++++ mypyc/test-data/run-librt-strings.test | 162 +++++++++++++++++++- 7 files changed, 343 insertions(+), 1 deletion(-) diff --git a/mypy/typeshed/stubs/librt/librt/strings.pyi b/mypy/typeshed/stubs/librt/librt/strings.pyi index 9e2acb90b7bf..a0c0cb9efdeb 100644 --- a/mypy/typeshed/stubs/librt/librt/strings.pyi +++ b/mypy/typeshed/stubs/librt/librt/strings.pyi @@ -24,3 +24,5 @@ def write_i16_le(b: BytesWriter, n: i16, /) -> None: ... def read_i16_le(b: bytes, index: i64, /) -> i16: ... def write_i32_le(b: BytesWriter, n: i32, /) -> None: ... def read_i32_le(b: bytes, index: i64, /) -> i32: ... +def write_i64_le(b: BytesWriter, n: i64, /) -> None: ... +def read_i64_le(b: bytes, index: i64, /) -> i64: ... diff --git a/mypyc/lib-rt/byteswriter_extra_ops.h b/mypyc/lib-rt/byteswriter_extra_ops.h index 0c38f6794f03..d281449cabba 100644 --- a/mypyc/lib-rt/byteswriter_extra_ops.h +++ b/mypyc/lib-rt/byteswriter_extra_ops.h @@ -102,6 +102,27 @@ CPyBytes_ReadI32LE(PyObject *bytes_obj, int64_t index) { return read_i32_le_unchecked(data + index); } +static inline char +CPyBytesWriter_WriteI64LE(PyObject *obj, int64_t value) { + BytesWriterObject *self = (BytesWriterObject *)obj; + if (!CPyBytesWriter_EnsureSize(self, 8)) + return CPY_NONE_ERROR; + BytesWriter_write_i64_le_unchecked(self, value); + return CPY_NONE; +} + +static inline int64_t +CPyBytes_ReadI64LE(PyObject *bytes_obj, int64_t index) { + // bytes_obj type is enforced by mypyc + Py_ssize_t size = PyBytes_GET_SIZE(bytes_obj); + if (unlikely(index < 0 || index > size - 8)) { + CPyBytes_ReadError(index, size); + return CPY_LL_INT_ERROR; + } + const unsigned char *data = (const unsigned char *)PyBytes_AS_STRING(bytes_obj); + return read_i64_le_unchecked(data + index); +} + #endif // MYPYC_EXPERIMENTAL #endif diff --git a/mypyc/lib-rt/strings/librt_strings.c b/mypyc/lib-rt/strings/librt_strings.c index 1175049b6047..6b6f1d22117f 100644 --- a/mypyc/lib-rt/strings/librt_strings.c +++ b/mypyc/lib-rt/strings/librt_strings.c @@ -949,6 +949,64 @@ read_i32_le(PyObject *module, PyObject *const *args, size_t nargs) { return PyLong_FromLong(value); } +static PyObject* +write_i64_le(PyObject *module, PyObject *const *args, size_t nargs) { + if (unlikely(nargs != 2)) { + PyErr_Format(PyExc_TypeError, + "write_i64_le() takes exactly 2 arguments (%zu given)", nargs); + return NULL; + } + PyObject *writer = args[0]; + if (!check_bytes_writer(writer)) { + return NULL; + } + PyObject *value = args[1]; + int64_t unboxed = CPyLong_AsInt64(value); + if (unlikely(unboxed == CPY_LL_INT_ERROR && PyErr_Occurred())) { + return NULL; + } + BytesWriterObject *bw = (BytesWriterObject *)writer; + if (unlikely(!ensure_bytes_writer_size(bw, 8))) { + return NULL; + } + BytesWriter_write_i64_le_unchecked(bw, unboxed); + Py_INCREF(Py_None); + return Py_None; +} + +static PyObject* +read_i64_le(PyObject *module, PyObject *const *args, size_t nargs) { + if (unlikely(nargs != 2)) { + PyErr_Format(PyExc_TypeError, + "read_i64_le() takes exactly 2 arguments (%zu given)", nargs); + return NULL; + } + PyObject *bytes_obj = args[0]; + if (unlikely(!PyBytes_Check(bytes_obj))) { + PyErr_SetString(PyExc_TypeError, "read_i64_le() argument 1 must be bytes"); + return NULL; + } + PyObject *index_obj = args[1]; + int64_t index = CPyLong_AsInt64(index_obj); + if (unlikely(index == CPY_LL_INT_ERROR && PyErr_Occurred())) { + return NULL; + } + if (unlikely(index < 0)) { + PyErr_SetString(PyExc_ValueError, "index must be non-negative"); + return NULL; + } + Py_ssize_t size = PyBytes_GET_SIZE(bytes_obj); + if (unlikely(index > size - 8)) { + PyErr_Format(PyExc_IndexError, + "index %lld out of range for bytes of length %zd", + (long long)index, size); + return NULL; + } + const unsigned char *data = (const unsigned char *)PyBytes_AS_STRING(bytes_obj); + int64_t value = read_i64_le_unchecked(data + index); + return PyLong_FromLongLong(value); +} + #endif static PyMethodDef librt_strings_module_methods[] = { @@ -965,6 +1023,12 @@ static PyMethodDef librt_strings_module_methods[] = { {"read_i32_le", (PyCFunction) read_i32_le, METH_FASTCALL, PyDoc_STR("Read a 32-bit signed integer from bytes in little-endian format") }, + {"write_i64_le", (PyCFunction) write_i64_le, METH_FASTCALL, + PyDoc_STR("Write a 64-bit signed integer to BytesWriter in little-endian format") + }, + {"read_i64_le", (PyCFunction) read_i64_le, METH_FASTCALL, + PyDoc_STR("Read a 64-bit signed integer from bytes in little-endian format") + }, #endif {NULL, NULL, 0, NULL} }; diff --git a/mypyc/lib-rt/strings/librt_strings_common.h b/mypyc/lib-rt/strings/librt_strings_common.h index 99bbeebbc612..ee7c1010912d 100644 --- a/mypyc/lib-rt/strings/librt_strings_common.h +++ b/mypyc/lib-rt/strings/librt_strings_common.h @@ -73,4 +73,40 @@ read_i32_le_unchecked(const unsigned char *data) { return (int32_t)uval; } +// Write a 64-bit signed integer in little-endian format to BytesWriter. +// NOTE: This does NOT check buffer capacity - caller must ensure space is available. +static inline void +BytesWriter_write_i64_le_unchecked(BytesWriterObject *self, int64_t value) { + // Store len in local to help optimizer reduce struct member accesses + Py_ssize_t len = self->len; + unsigned char *p = (unsigned char *)(self->buf + len); + uint64_t uval = (uint64_t)value; + + // Write in little-endian format + // Modern compilers optimize this pattern well, often to a single store on LE systems + p[0] = (unsigned char)uval; + p[1] = (unsigned char)(uval >> 8); + p[2] = (unsigned char)(uval >> 16); + p[3] = (unsigned char)(uval >> 24); + p[4] = (unsigned char)(uval >> 32); + p[5] = (unsigned char)(uval >> 40); + p[6] = (unsigned char)(uval >> 48); + p[7] = (unsigned char)(uval >> 56); + + self->len = len + 8; +} + +// Read a 64-bit signed integer in little-endian format from bytes. +// NOTE: This does NOT check bounds - caller must ensure valid index. +static inline int64_t +read_i64_le_unchecked(const unsigned char *data) { + // Read in little-endian format + // Modern compilers optimize this pattern well, often to a single load on LE systems + uint64_t uval = (uint64_t)data[0] | ((uint64_t)data[1] << 8) | + ((uint64_t)data[2] << 16) | ((uint64_t)data[3] << 24) | + ((uint64_t)data[4] << 32) | ((uint64_t)data[5] << 40) | + ((uint64_t)data[6] << 48) | ((uint64_t)data[7] << 56); + return (int64_t)uval; +} + #endif // LIBRT_STRINGS_COMMON_H diff --git a/mypyc/primitives/librt_strings_ops.py b/mypyc/primitives/librt_strings_ops.py index f677cb566faf..2fc5152bce7f 100644 --- a/mypyc/primitives/librt_strings_ops.py +++ b/mypyc/primitives/librt_strings_ops.py @@ -117,6 +117,26 @@ dependencies=[LIBRT_STRINGS, BYTES_WRITER_EXTRA_OPS], ) +function_op( + name="librt.strings.write_i64_le", + arg_types=[bytes_writer_rprimitive, int64_rprimitive], + return_type=none_rprimitive, + c_function_name="CPyBytesWriter_WriteI64LE", + error_kind=ERR_MAGIC, + experimental=True, + dependencies=[LIBRT_STRINGS, BYTES_WRITER_EXTRA_OPS], +) + +function_op( + name="librt.strings.read_i64_le", + arg_types=[bytes_rprimitive, int64_rprimitive], + return_type=int64_rprimitive, + c_function_name="CPyBytes_ReadI64LE", + error_kind=ERR_MAGIC, + experimental=True, + dependencies=[LIBRT_STRINGS, BYTES_WRITER_EXTRA_OPS], +) + function_op( name="builtins.len", arg_types=[bytes_writer_rprimitive], diff --git a/mypyc/test-data/irbuild-librt-strings.test b/mypyc/test-data/irbuild-librt-strings.test index 1da51d31d31f..17c9e6597c9a 100644 --- a/mypyc/test-data/irbuild-librt-strings.test +++ b/mypyc/test-data/irbuild-librt-strings.test @@ -180,6 +180,45 @@ L0: r0 = CPyBytes_ReadI32LE(b, 0) return r0 +[case testLibrtStrings_i64_le_experimental_64bit] +from librt.strings import BytesWriter, write_i64_le, read_i64_le +from mypy_extensions import i64 + +def test_write_i64_le(b: BytesWriter, n: i64) -> None: + write_i64_le(b, n) +def test_write_i64_le_literal(b: BytesWriter) -> None: + write_i64_le(b, 1234567890123456789) +def test_read_i64_le(b: bytes, i: i64) -> i64: + return read_i64_le(b, i) +def test_read_i64_le_literal(b: bytes) -> i64: + return read_i64_le(b, 0) +[out] +def test_write_i64_le(b, n): + b :: librt.strings.BytesWriter + n :: i64 + r0 :: None +L0: + r0 = CPyBytesWriter_WriteI64LE(b, n) + return 1 +def test_write_i64_le_literal(b): + b :: librt.strings.BytesWriter + r0 :: None +L0: + r0 = CPyBytesWriter_WriteI64LE(b, 1234567890123456789) + return 1 +def test_read_i64_le(b, i): + b :: bytes + i, r0 :: i64 +L0: + r0 = CPyBytes_ReadI64LE(b, i) + return r0 +def test_read_i64_le_literal(b): + b :: bytes + r0 :: i64 +L0: + r0 = CPyBytes_ReadI64LE(b, 0) + return r0 + [case testLibrtStrings_StringWriter_experimental_64bit] from librt.strings import StringWriter from mypy_extensions import i32, i64 diff --git a/mypyc/test-data/run-librt-strings.test b/mypyc/test-data/run-librt-strings.test index 6cb20566ef91..20dca5ffd7ee 100644 --- a/mypyc/test-data/run-librt-strings.test +++ b/mypyc/test-data/run-librt-strings.test @@ -5,7 +5,7 @@ import binascii import random import struct -from librt.strings import BytesWriter, StringWriter, write_i16_le, read_i16_le, write_i32_le, read_i32_le +from librt.strings import BytesWriter, StringWriter, write_i16_le, read_i16_le, write_i32_le, read_i32_le, write_i64_le, read_i64_le from testutil import assertRaises @@ -361,6 +361,166 @@ def test_read_i32_le_via_any() -> None: with assertRaises(TypeError, "read_i32_le() argument 1 must be bytes"): read_i32_le(bytearray(b"\x00\x00\x00\x00"), 0 + int()) # type: ignore +def test_bytes_writer_write_i64_le() -> None: + # Test 1-byte values + w = BytesWriter() + values = [0, 1, 127, -1, -128] + for v in values: + write_i64_le(w, v) + assert w.getvalue() == b"".join(struct.pack(" None: + # Test write_i64_le via Any to ensure C extension wrapper works + w: Any = BytesWriter() + + w.append(0x42) + write_i64_le(w, 0x123456789ABCDEF0) + w.append(0xFF) + assert w.getvalue() == b"\x42" + struct.pack(" None: + # Test 1-byte values + values = [0, 1, 127, -1, -128] + data = b"".join(struct.pack(" None: + # Test read_i64_le via Any to ensure C extension wrapper works + data: Any = struct.pack(" None: w = BytesWriter() w.write(bytearray(b"foobar")) From efd3b734273ed9b26ec9c3b7b9504fed5ff2f280 Mon Sep 17 00:00:00 2001 From: Jukka Lehtosalo Date: Thu, 5 Feb 2026 14:15:31 +0000 Subject: [PATCH 03/12] Refactor argument processing --- mypyc/lib-rt/strings/librt_strings.c | 174 +++++++++------------------ 1 file changed, 58 insertions(+), 116 deletions(-) diff --git a/mypyc/lib-rt/strings/librt_strings.c b/mypyc/lib-rt/strings/librt_strings.c index 6b6f1d22117f..5868ec4c15a7 100644 --- a/mypyc/lib-rt/strings/librt_strings.c +++ b/mypyc/lib-rt/strings/librt_strings.c @@ -831,46 +831,37 @@ StringWriter_len_internal(PyObject *self) { // End of StringWriter -static PyObject* -write_i16_le(PyObject *module, PyObject *const *args, size_t nargs) { +// Helper for write_i*_le/be functions - validates args and returns BytesWriter +static inline BytesWriterObject * +parse_write_int_args(PyObject *const *args, size_t nargs, const char *func_name) { if (unlikely(nargs != 2)) { PyErr_Format(PyExc_TypeError, - "write_i16_le() takes exactly 2 arguments (%zu given)", nargs); + "%s() takes exactly 2 arguments (%zu given)", func_name, nargs); return NULL; } PyObject *writer = args[0]; if (!check_bytes_writer(writer)) { return NULL; } - PyObject *value = args[1]; - int16_t unboxed = CPyLong_AsInt16(value); - if (unlikely(unboxed == CPY_LL_INT_ERROR && PyErr_Occurred())) { - // Error already set by CPyLong_AsInt16 (ValueError for overflow, TypeError for wrong type) - return NULL; - } - BytesWriterObject *bw = (BytesWriterObject *)writer; - if (unlikely(!ensure_bytes_writer_size(bw, 2))) { - return NULL; - } - BytesWriter_write_i16_le_unchecked(bw, unboxed); - Py_INCREF(Py_None); - return Py_None; + return (BytesWriterObject *)writer; } -static PyObject* -read_i16_le(PyObject *module, PyObject *const *args, size_t nargs) { +// Helper for read_i*_le/be functions - validates args and returns data pointer +// Returns NULL on error, sets *out_index to the validated index on success +static inline const unsigned char * +parse_read_int_args(PyObject *const *args, size_t nargs, const char *func_name, + Py_ssize_t num_bytes, int64_t *out_index) { if (unlikely(nargs != 2)) { PyErr_Format(PyExc_TypeError, - "read_i16_le() takes exactly 2 arguments (%zu given)", nargs); + "%s() takes exactly 2 arguments (%zu given)", func_name, nargs); return NULL; } PyObject *bytes_obj = args[0]; if (unlikely(!PyBytes_Check(bytes_obj))) { - PyErr_SetString(PyExc_TypeError, "read_i16_le() argument 1 must be bytes"); + PyErr_Format(PyExc_TypeError, "%s() argument 1 must be bytes", func_name); return NULL; } - PyObject *index_obj = args[1]; - int64_t index = CPyLong_AsInt64(index_obj); + int64_t index = CPyLong_AsInt64(args[1]); if (unlikely(index == CPY_LL_INT_ERROR && PyErr_Occurred())) { return NULL; } @@ -879,132 +870,83 @@ read_i16_le(PyObject *module, PyObject *const *args, size_t nargs) { return NULL; } Py_ssize_t size = PyBytes_GET_SIZE(bytes_obj); - if (unlikely(index > size - 2)) { + if (unlikely(index > size - num_bytes)) { PyErr_Format(PyExc_IndexError, "index %lld out of range for bytes of length %zd", (long long)index, size); return NULL; } - const unsigned char *data = (const unsigned char *)PyBytes_AS_STRING(bytes_obj); - int16_t value = read_i16_le_unchecked(data + index); - return PyLong_FromLong(value); + *out_index = index; + return (const unsigned char *)PyBytes_AS_STRING(bytes_obj); } static PyObject* -write_i32_le(PyObject *module, PyObject *const *args, size_t nargs) { - if (unlikely(nargs != 2)) { - PyErr_Format(PyExc_TypeError, - "write_i32_le() takes exactly 2 arguments (%zu given)", nargs); +write_i16_le(PyObject *module, PyObject *const *args, size_t nargs) { + BytesWriterObject *bw = parse_write_int_args(args, nargs, "write_i16_le"); + if (bw == NULL) return NULL; - } - PyObject *writer = args[0]; - if (!check_bytes_writer(writer)) { + int16_t unboxed = CPyLong_AsInt16(args[1]); + if (unlikely(unboxed == CPY_LL_INT_ERROR && PyErr_Occurred())) return NULL; - } - PyObject *value = args[1]; - int32_t unboxed = CPyLong_AsInt32(value); - if (unlikely(unboxed == CPY_LL_INT_ERROR && PyErr_Occurred())) { - // Error already set by CPyLong_AsInt32 (ValueError for overflow, TypeError for wrong type) + if (unlikely(!ensure_bytes_writer_size(bw, 2))) return NULL; - } - BytesWriterObject *bw = (BytesWriterObject *)writer; - if (unlikely(!ensure_bytes_writer_size(bw, 4))) { - return NULL; - } - BytesWriter_write_i32_le_unchecked(bw, unboxed); - Py_INCREF(Py_None); - return Py_None; + BytesWriter_write_i16_le_unchecked(bw, unboxed); + Py_RETURN_NONE; } static PyObject* -read_i32_le(PyObject *module, PyObject *const *args, size_t nargs) { - if (unlikely(nargs != 2)) { - PyErr_Format(PyExc_TypeError, - "read_i32_le() takes exactly 2 arguments (%zu given)", nargs); +read_i16_le(PyObject *module, PyObject *const *args, size_t nargs) { + int64_t index; + const unsigned char *data = parse_read_int_args(args, nargs, "read_i16_le", 2, &index); + if (data == NULL) return NULL; - } - PyObject *bytes_obj = args[0]; - if (unlikely(!PyBytes_Check(bytes_obj))) { - PyErr_SetString(PyExc_TypeError, "read_i32_le() argument 1 must be bytes"); + return PyLong_FromLong(read_i16_le_unchecked(data + index)); +} + +static PyObject* +write_i32_le(PyObject *module, PyObject *const *args, size_t nargs) { + BytesWriterObject *bw = parse_write_int_args(args, nargs, "write_i32_le"); + if (bw == NULL) return NULL; - } - PyObject *index_obj = args[1]; - int64_t index = CPyLong_AsInt64(index_obj); - if (unlikely(index == CPY_LL_INT_ERROR && PyErr_Occurred())) { + int32_t unboxed = CPyLong_AsInt32(args[1]); + if (unlikely(unboxed == CPY_LL_INT_ERROR && PyErr_Occurred())) return NULL; - } - if (unlikely(index < 0)) { - PyErr_SetString(PyExc_ValueError, "index must be non-negative"); + if (unlikely(!ensure_bytes_writer_size(bw, 4))) return NULL; - } - Py_ssize_t size = PyBytes_GET_SIZE(bytes_obj); - if (unlikely(index > size - 4)) { - PyErr_Format(PyExc_IndexError, - "index %lld out of range for bytes of length %zd", - (long long)index, size); + BytesWriter_write_i32_le_unchecked(bw, unboxed); + Py_RETURN_NONE; +} + +static PyObject* +read_i32_le(PyObject *module, PyObject *const *args, size_t nargs) { + int64_t index; + const unsigned char *data = parse_read_int_args(args, nargs, "read_i32_le", 4, &index); + if (data == NULL) return NULL; - } - const unsigned char *data = (const unsigned char *)PyBytes_AS_STRING(bytes_obj); - int32_t value = read_i32_le_unchecked(data + index); - return PyLong_FromLong(value); + return PyLong_FromLong(read_i32_le_unchecked(data + index)); } static PyObject* write_i64_le(PyObject *module, PyObject *const *args, size_t nargs) { - if (unlikely(nargs != 2)) { - PyErr_Format(PyExc_TypeError, - "write_i64_le() takes exactly 2 arguments (%zu given)", nargs); + BytesWriterObject *bw = parse_write_int_args(args, nargs, "write_i64_le"); + if (bw == NULL) return NULL; - } - PyObject *writer = args[0]; - if (!check_bytes_writer(writer)) { + int64_t unboxed = CPyLong_AsInt64(args[1]); + if (unlikely(unboxed == CPY_LL_INT_ERROR && PyErr_Occurred())) return NULL; - } - PyObject *value = args[1]; - int64_t unboxed = CPyLong_AsInt64(value); - if (unlikely(unboxed == CPY_LL_INT_ERROR && PyErr_Occurred())) { + if (unlikely(!ensure_bytes_writer_size(bw, 8))) return NULL; - } - BytesWriterObject *bw = (BytesWriterObject *)writer; - if (unlikely(!ensure_bytes_writer_size(bw, 8))) { - return NULL; - } BytesWriter_write_i64_le_unchecked(bw, unboxed); - Py_INCREF(Py_None); - return Py_None; + Py_RETURN_NONE; } static PyObject* read_i64_le(PyObject *module, PyObject *const *args, size_t nargs) { - if (unlikely(nargs != 2)) { - PyErr_Format(PyExc_TypeError, - "read_i64_le() takes exactly 2 arguments (%zu given)", nargs); - return NULL; - } - PyObject *bytes_obj = args[0]; - if (unlikely(!PyBytes_Check(bytes_obj))) { - PyErr_SetString(PyExc_TypeError, "read_i64_le() argument 1 must be bytes"); - return NULL; - } - PyObject *index_obj = args[1]; - int64_t index = CPyLong_AsInt64(index_obj); - if (unlikely(index == CPY_LL_INT_ERROR && PyErr_Occurred())) { + int64_t index; + const unsigned char *data = parse_read_int_args(args, nargs, "read_i64_le", 8, &index); + if (data == NULL) return NULL; - } - if (unlikely(index < 0)) { - PyErr_SetString(PyExc_ValueError, "index must be non-negative"); - return NULL; - } - Py_ssize_t size = PyBytes_GET_SIZE(bytes_obj); - if (unlikely(index > size - 8)) { - PyErr_Format(PyExc_IndexError, - "index %lld out of range for bytes of length %zd", - (long long)index, size); - return NULL; - } - const unsigned char *data = (const unsigned char *)PyBytes_AS_STRING(bytes_obj); - int64_t value = read_i64_le_unchecked(data + index); - return PyLong_FromLongLong(value); + return PyLong_FromLongLong(read_i64_le_unchecked(data + index)); } #endif From 990b336286c4c95090c0d0f2d1aafc344d199d13 Mon Sep 17 00:00:00 2001 From: Jukka Lehtosalo Date: Thu, 5 Feb 2026 14:24:50 +0000 Subject: [PATCH 04/12] Use a more portable optimization --- mypyc/lib-rt/strings/librt_strings_common.h | 118 ++++++++++---------- 1 file changed, 61 insertions(+), 57 deletions(-) diff --git a/mypyc/lib-rt/strings/librt_strings_common.h b/mypyc/lib-rt/strings/librt_strings_common.h index ee7c1010912d..094b6a49262c 100644 --- a/mypyc/lib-rt/strings/librt_strings_common.h +++ b/mypyc/lib-rt/strings/librt_strings_common.h @@ -5,6 +5,22 @@ #include #include +// Byte-swap macros for big-endian support +#if PY_BIG_ENDIAN +# if defined(_MSC_VER) +# include +# define BSWAP16(x) _byteswap_ushort(x) +# define BSWAP32(x) _byteswap_ulong(x) +# define BSWAP64(x) _byteswap_uint64(x) +# elif defined(__GNUC__) || defined(__clang__) +# define BSWAP16(x) __builtin_bswap16(x) +# define BSWAP32(x) __builtin_bswap32(x) +# define BSWAP64(x) __builtin_bswap64(x) +# else +# error "Unsupported compiler for big-endian byte swapping" +# endif +#endif + // Length of the default buffer embedded directly in a BytesWriter object #define WRITER_EMBEDDED_BUF_LEN 256 @@ -20,93 +36,81 @@ typedef struct { // NOTE: This does NOT check buffer capacity - caller must ensure space is available. static inline void BytesWriter_write_i16_le_unchecked(BytesWriterObject *self, int16_t value) { - // Store len in local to help optimizer reduce struct member accesses - Py_ssize_t len = self->len; - unsigned char *p = (unsigned char *)(self->buf + len); - uint16_t uval = (uint16_t)value; - - // Write in little-endian format - // Modern compilers optimize this pattern well, often to a single store on LE systems - p[0] = (unsigned char)uval; - p[1] = (unsigned char)(uval >> 8); - - self->len = len + 2; + // memcpy is reliably optimized to a single store by GCC, Clang, and MSVC +#if PY_BIG_ENDIAN + uint16_t swapped = BSWAP16((uint16_t)value); + memcpy(self->buf + self->len, &swapped, 2); +#else + memcpy(self->buf + self->len, &value, 2); +#endif + self->len += 2; } // Read a 16-bit signed integer in little-endian format from bytes. // NOTE: This does NOT check bounds - caller must ensure valid index. static inline int16_t read_i16_le_unchecked(const unsigned char *data) { - // Read in little-endian format - // Modern compilers optimize this pattern well, often to a single load on LE systems - uint16_t uval = (uint16_t)data[0] | ((uint16_t)data[1] << 8); - return (int16_t)uval; + // memcpy is reliably optimized to a single load by GCC, Clang, and MSVC + uint16_t value; + memcpy(&value, data, 2); +#if PY_BIG_ENDIAN + value = BSWAP16(value); +#endif + return (int16_t)value; } // Write a 32-bit signed integer in little-endian format to BytesWriter. // NOTE: This does NOT check buffer capacity - caller must ensure space is available. static inline void BytesWriter_write_i32_le_unchecked(BytesWriterObject *self, int32_t value) { - // Store len in local to help optimizer reduce struct member accesses - Py_ssize_t len = self->len; - unsigned char *p = (unsigned char *)(self->buf + len); - uint32_t uval = (uint32_t)value; - - // Write in little-endian format - // Modern compilers optimize this pattern well, often to a single store on LE systems - p[0] = (unsigned char)uval; - p[1] = (unsigned char)(uval >> 8); - p[2] = (unsigned char)(uval >> 16); - p[3] = (unsigned char)(uval >> 24); - - self->len = len + 4; + // memcpy is reliably optimized to a single store by GCC, Clang, and MSVC +#if PY_BIG_ENDIAN + uint32_t swapped = BSWAP32((uint32_t)value); + memcpy(self->buf + self->len, &swapped, 4); +#else + memcpy(self->buf + self->len, &value, 4); +#endif + self->len += 4; } // Read a 32-bit signed integer in little-endian format from bytes. // NOTE: This does NOT check bounds - caller must ensure valid index. static inline int32_t read_i32_le_unchecked(const unsigned char *data) { - // Read in little-endian format - // Modern compilers optimize this pattern well, often to a single load on LE systems - uint32_t uval = (uint32_t)data[0] | ((uint32_t)data[1] << 8) | - ((uint32_t)data[2] << 16) | ((uint32_t)data[3] << 24); - return (int32_t)uval; + // memcpy is reliably optimized to a single load by GCC, Clang, and MSVC + uint32_t value; + memcpy(&value, data, 4); +#if PY_BIG_ENDIAN + value = BSWAP32(value); +#endif + return (int32_t)value; } // Write a 64-bit signed integer in little-endian format to BytesWriter. // NOTE: This does NOT check buffer capacity - caller must ensure space is available. static inline void BytesWriter_write_i64_le_unchecked(BytesWriterObject *self, int64_t value) { - // Store len in local to help optimizer reduce struct member accesses - Py_ssize_t len = self->len; - unsigned char *p = (unsigned char *)(self->buf + len); - uint64_t uval = (uint64_t)value; - - // Write in little-endian format - // Modern compilers optimize this pattern well, often to a single store on LE systems - p[0] = (unsigned char)uval; - p[1] = (unsigned char)(uval >> 8); - p[2] = (unsigned char)(uval >> 16); - p[3] = (unsigned char)(uval >> 24); - p[4] = (unsigned char)(uval >> 32); - p[5] = (unsigned char)(uval >> 40); - p[6] = (unsigned char)(uval >> 48); - p[7] = (unsigned char)(uval >> 56); - - self->len = len + 8; + // memcpy is reliably optimized to a single store by GCC, Clang, and MSVC +#if PY_BIG_ENDIAN + uint64_t swapped = BSWAP64((uint64_t)value); + memcpy(self->buf + self->len, &swapped, 8); +#else + memcpy(self->buf + self->len, &value, 8); +#endif + self->len += 8; } // Read a 64-bit signed integer in little-endian format from bytes. // NOTE: This does NOT check bounds - caller must ensure valid index. static inline int64_t read_i64_le_unchecked(const unsigned char *data) { - // Read in little-endian format - // Modern compilers optimize this pattern well, often to a single load on LE systems - uint64_t uval = (uint64_t)data[0] | ((uint64_t)data[1] << 8) | - ((uint64_t)data[2] << 16) | ((uint64_t)data[3] << 24) | - ((uint64_t)data[4] << 32) | ((uint64_t)data[5] << 40) | - ((uint64_t)data[6] << 48) | ((uint64_t)data[7] << 56); - return (int64_t)uval; + // memcpy is reliably optimized to a single load by GCC, Clang, and MSVC + uint64_t value; + memcpy(&value, data, 8); +#if PY_BIG_ENDIAN + value = BSWAP64(value); +#endif + return (int64_t)value; } #endif // LIBRT_STRINGS_COMMON_H From 3ba9f17fc495ca65ad04899e192d42eebc688143 Mon Sep 17 00:00:00 2001 From: Jukka Lehtosalo Date: Thu, 5 Feb 2026 14:30:10 +0000 Subject: [PATCH 05/12] Make more portable --- mypyc/lib-rt/strings/librt_strings_common.h | 23 +++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/mypyc/lib-rt/strings/librt_strings_common.h b/mypyc/lib-rt/strings/librt_strings_common.h index 094b6a49262c..67433f3b5631 100644 --- a/mypyc/lib-rt/strings/librt_strings_common.h +++ b/mypyc/lib-rt/strings/librt_strings_common.h @@ -5,7 +5,7 @@ #include #include -// Byte-swap macros for big-endian support +// Byte-swap functions for non-native endianness support #if PY_BIG_ENDIAN # if defined(_MSC_VER) # include @@ -17,7 +17,26 @@ # define BSWAP32(x) __builtin_bswap32(x) # define BSWAP64(x) __builtin_bswap64(x) # else -# error "Unsupported compiler for big-endian byte swapping" +// Fallback for other compilers (slower but portable) +static inline uint16_t BSWAP16(uint16_t x) { + return (uint16_t)((x >> 8) | (x << 8)); +} +static inline uint32_t BSWAP32(uint32_t x) { + return ((x >> 24) & 0xFFU) | + ((x >> 8) & 0xFF00U) | + ((x << 8) & 0xFF0000U) | + ((x << 24) & 0xFF000000U); +} +static inline uint64_t BSWAP64(uint64_t x) { + return ((x >> 56) & 0xFFULL) | + ((x >> 40) & 0xFF00ULL) | + ((x >> 24) & 0xFF0000ULL) | + ((x >> 8) & 0xFF000000ULL) | + ((x << 8) & 0xFF00000000ULL) | + ((x << 24) & 0xFF0000000000ULL) | + ((x << 40) & 0xFF000000000000ULL) | + ((x << 56) & 0xFF00000000000000ULL); +} # endif #endif From 5a8662d13af071335d2c71c9924290f7bc6932cc Mon Sep 17 00:00:00 2001 From: Jukka Lehtosalo Date: Thu, 5 Feb 2026 14:39:42 +0000 Subject: [PATCH 06/12] Make naming more consistent --- mypyc/lib-rt/byteswriter_extra_ops.h | 6 +++--- mypyc/lib-rt/strings/librt_strings.c | 6 +++--- mypyc/lib-rt/strings/librt_strings_common.h | 6 +++--- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/mypyc/lib-rt/byteswriter_extra_ops.h b/mypyc/lib-rt/byteswriter_extra_ops.h index d281449cabba..92afeb7798a9 100644 --- a/mypyc/lib-rt/byteswriter_extra_ops.h +++ b/mypyc/lib-rt/byteswriter_extra_ops.h @@ -87,7 +87,7 @@ CPyBytes_ReadI16LE(PyObject *bytes_obj, int64_t index) { return CPY_LL_INT_ERROR; } const unsigned char *data = (const unsigned char *)PyBytes_AS_STRING(bytes_obj); - return read_i16_le_unchecked(data + index); + return CPyBytes_ReadI16LEUnsafe(data + index); } static inline int32_t @@ -99,7 +99,7 @@ CPyBytes_ReadI32LE(PyObject *bytes_obj, int64_t index) { return CPY_LL_INT_ERROR; } const unsigned char *data = (const unsigned char *)PyBytes_AS_STRING(bytes_obj); - return read_i32_le_unchecked(data + index); + return CPyBytes_ReadI32LEUnsafe(data + index); } static inline char @@ -120,7 +120,7 @@ CPyBytes_ReadI64LE(PyObject *bytes_obj, int64_t index) { return CPY_LL_INT_ERROR; } const unsigned char *data = (const unsigned char *)PyBytes_AS_STRING(bytes_obj); - return read_i64_le_unchecked(data + index); + return CPyBytes_ReadI64LEUnsafe(data + index); } #endif // MYPYC_EXPERIMENTAL diff --git a/mypyc/lib-rt/strings/librt_strings.c b/mypyc/lib-rt/strings/librt_strings.c index 5868ec4c15a7..45077b01cb6d 100644 --- a/mypyc/lib-rt/strings/librt_strings.c +++ b/mypyc/lib-rt/strings/librt_strings.c @@ -900,7 +900,7 @@ read_i16_le(PyObject *module, PyObject *const *args, size_t nargs) { const unsigned char *data = parse_read_int_args(args, nargs, "read_i16_le", 2, &index); if (data == NULL) return NULL; - return PyLong_FromLong(read_i16_le_unchecked(data + index)); + return PyLong_FromLong(CPyBytes_ReadI16LEUnsafe(data + index)); } static PyObject* @@ -923,7 +923,7 @@ read_i32_le(PyObject *module, PyObject *const *args, size_t nargs) { const unsigned char *data = parse_read_int_args(args, nargs, "read_i32_le", 4, &index); if (data == NULL) return NULL; - return PyLong_FromLong(read_i32_le_unchecked(data + index)); + return PyLong_FromLong(CPyBytes_ReadI32LEUnsafe(data + index)); } static PyObject* @@ -946,7 +946,7 @@ read_i64_le(PyObject *module, PyObject *const *args, size_t nargs) { const unsigned char *data = parse_read_int_args(args, nargs, "read_i64_le", 8, &index); if (data == NULL) return NULL; - return PyLong_FromLongLong(read_i64_le_unchecked(data + index)); + return PyLong_FromLongLong(CPyBytes_ReadI64LEUnsafe(data + index)); } #endif diff --git a/mypyc/lib-rt/strings/librt_strings_common.h b/mypyc/lib-rt/strings/librt_strings_common.h index 67433f3b5631..180d7e67a5b9 100644 --- a/mypyc/lib-rt/strings/librt_strings_common.h +++ b/mypyc/lib-rt/strings/librt_strings_common.h @@ -68,7 +68,7 @@ BytesWriter_write_i16_le_unchecked(BytesWriterObject *self, int16_t value) { // Read a 16-bit signed integer in little-endian format from bytes. // NOTE: This does NOT check bounds - caller must ensure valid index. static inline int16_t -read_i16_le_unchecked(const unsigned char *data) { +CPyBytes_ReadI16LEUnsafe(const unsigned char *data) { // memcpy is reliably optimized to a single load by GCC, Clang, and MSVC uint16_t value; memcpy(&value, data, 2); @@ -95,7 +95,7 @@ BytesWriter_write_i32_le_unchecked(BytesWriterObject *self, int32_t value) { // Read a 32-bit signed integer in little-endian format from bytes. // NOTE: This does NOT check bounds - caller must ensure valid index. static inline int32_t -read_i32_le_unchecked(const unsigned char *data) { +CPyBytes_ReadI32LEUnsafe(const unsigned char *data) { // memcpy is reliably optimized to a single load by GCC, Clang, and MSVC uint32_t value; memcpy(&value, data, 4); @@ -122,7 +122,7 @@ BytesWriter_write_i64_le_unchecked(BytesWriterObject *self, int64_t value) { // Read a 64-bit signed integer in little-endian format from bytes. // NOTE: This does NOT check bounds - caller must ensure valid index. static inline int64_t -read_i64_le_unchecked(const unsigned char *data) { +CPyBytes_ReadI64LEUnsafe(const unsigned char *data) { // memcpy is reliably optimized to a single load by GCC, Clang, and MSVC uint64_t value; memcpy(&value, data, 8); From 953fd8d38a37e1cd7c0ae3f4a7d173caa9f1e666 Mon Sep 17 00:00:00 2001 From: Jukka Lehtosalo Date: Thu, 5 Feb 2026 14:42:35 +0000 Subject: [PATCH 07/12] Refactor more --- mypyc/lib-rt/byteswriter_extra_ops.h | 6 +++--- mypyc/lib-rt/strings/librt_strings.c | 6 +++--- mypyc/lib-rt/strings/librt_strings_common.h | 6 +++--- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/mypyc/lib-rt/byteswriter_extra_ops.h b/mypyc/lib-rt/byteswriter_extra_ops.h index 92afeb7798a9..6e0c6f0525ff 100644 --- a/mypyc/lib-rt/byteswriter_extra_ops.h +++ b/mypyc/lib-rt/byteswriter_extra_ops.h @@ -40,7 +40,7 @@ CPyBytesWriter_WriteI16LE(PyObject *obj, int16_t value) { BytesWriterObject *self = (BytesWriterObject *)obj; if (!CPyBytesWriter_EnsureSize(self, 2)) return CPY_NONE_ERROR; - BytesWriter_write_i16_le_unchecked(self, value); + BytesWriter_WriteI16LEUnsafe(self, value); return CPY_NONE; } @@ -49,7 +49,7 @@ CPyBytesWriter_WriteI32LE(PyObject *obj, int32_t value) { BytesWriterObject *self = (BytesWriterObject *)obj; if (!CPyBytesWriter_EnsureSize(self, 4)) return CPY_NONE_ERROR; - BytesWriter_write_i32_le_unchecked(self, value); + BytesWriter_WriteI32LEUnsafe(self, value); return CPY_NONE; } @@ -107,7 +107,7 @@ CPyBytesWriter_WriteI64LE(PyObject *obj, int64_t value) { BytesWriterObject *self = (BytesWriterObject *)obj; if (!CPyBytesWriter_EnsureSize(self, 8)) return CPY_NONE_ERROR; - BytesWriter_write_i64_le_unchecked(self, value); + BytesWriter_WriteI64LEUnsafe(self, value); return CPY_NONE; } diff --git a/mypyc/lib-rt/strings/librt_strings.c b/mypyc/lib-rt/strings/librt_strings.c index 45077b01cb6d..a06602793565 100644 --- a/mypyc/lib-rt/strings/librt_strings.c +++ b/mypyc/lib-rt/strings/librt_strings.c @@ -890,7 +890,7 @@ write_i16_le(PyObject *module, PyObject *const *args, size_t nargs) { return NULL; if (unlikely(!ensure_bytes_writer_size(bw, 2))) return NULL; - BytesWriter_write_i16_le_unchecked(bw, unboxed); + BytesWriter_WriteI16LEUnsafe(bw, unboxed); Py_RETURN_NONE; } @@ -913,7 +913,7 @@ write_i32_le(PyObject *module, PyObject *const *args, size_t nargs) { return NULL; if (unlikely(!ensure_bytes_writer_size(bw, 4))) return NULL; - BytesWriter_write_i32_le_unchecked(bw, unboxed); + BytesWriter_WriteI32LEUnsafe(bw, unboxed); Py_RETURN_NONE; } @@ -936,7 +936,7 @@ write_i64_le(PyObject *module, PyObject *const *args, size_t nargs) { return NULL; if (unlikely(!ensure_bytes_writer_size(bw, 8))) return NULL; - BytesWriter_write_i64_le_unchecked(bw, unboxed); + BytesWriter_WriteI64LEUnsafe(bw, unboxed); Py_RETURN_NONE; } diff --git a/mypyc/lib-rt/strings/librt_strings_common.h b/mypyc/lib-rt/strings/librt_strings_common.h index 180d7e67a5b9..a8cfb217d695 100644 --- a/mypyc/lib-rt/strings/librt_strings_common.h +++ b/mypyc/lib-rt/strings/librt_strings_common.h @@ -54,7 +54,7 @@ typedef struct { // Write a 16-bit signed integer in little-endian format to BytesWriter. // NOTE: This does NOT check buffer capacity - caller must ensure space is available. static inline void -BytesWriter_write_i16_le_unchecked(BytesWriterObject *self, int16_t value) { +BytesWriter_WriteI16LEUnsafe(BytesWriterObject *self, int16_t value) { // memcpy is reliably optimized to a single store by GCC, Clang, and MSVC #if PY_BIG_ENDIAN uint16_t swapped = BSWAP16((uint16_t)value); @@ -81,7 +81,7 @@ CPyBytes_ReadI16LEUnsafe(const unsigned char *data) { // Write a 32-bit signed integer in little-endian format to BytesWriter. // NOTE: This does NOT check buffer capacity - caller must ensure space is available. static inline void -BytesWriter_write_i32_le_unchecked(BytesWriterObject *self, int32_t value) { +BytesWriter_WriteI32LEUnsafe(BytesWriterObject *self, int32_t value) { // memcpy is reliably optimized to a single store by GCC, Clang, and MSVC #if PY_BIG_ENDIAN uint32_t swapped = BSWAP32((uint32_t)value); @@ -108,7 +108,7 @@ CPyBytes_ReadI32LEUnsafe(const unsigned char *data) { // Write a 64-bit signed integer in little-endian format to BytesWriter. // NOTE: This does NOT check buffer capacity - caller must ensure space is available. static inline void -BytesWriter_write_i64_le_unchecked(BytesWriterObject *self, int64_t value) { +BytesWriter_WriteI64LEUnsafe(BytesWriterObject *self, int64_t value) { // memcpy is reliably optimized to a single store by GCC, Clang, and MSVC #if PY_BIG_ENDIAN uint64_t swapped = BSWAP64((uint64_t)value); From e8bc8a422bdcfee0b6bb3f524ba38b20779e89a2 Mon Sep 17 00:00:00 2001 From: Jukka Lehtosalo Date: Thu, 5 Feb 2026 14:49:08 +0000 Subject: [PATCH 08/12] Refactor tests --- mypyc/test-data/run-librt-strings.test | 219 +++++++------------------ 1 file changed, 59 insertions(+), 160 deletions(-) diff --git a/mypyc/test-data/run-librt-strings.test b/mypyc/test-data/run-librt-strings.test index 20dca5ffd7ee..7c5e7e1ca1fc 100644 --- a/mypyc/test-data/run-librt-strings.test +++ b/mypyc/test-data/run-librt-strings.test @@ -9,6 +9,37 @@ from librt.strings import BytesWriter, StringWriter, write_i16_le, read_i16_le, from testutil import assertRaises +# Test values for i16 write/read operations, organized by byte size +I16_TEST_VALUES: list[int] = [ + # 1-byte values (0 to 127, and -128 to -1 in terms of low byte pattern) + 0, 1, 127, -1, -128, + # 2-byte values (128 to 32767, -32768 to -129) + 128, 255, 256, 0x1234, 32767, -129, -256, -32768, +] + +# Test values for i32 write/read operations: all i16 values plus 3-4 byte values +I32_TEST_VALUES: list[int] = I16_TEST_VALUES + [ + # 3-byte values (32768 to 8388607, -8388608 to -32769) + 32768, 65535, 65536, 8388607, -32769, -65536, -8388608, + # 4-byte values (8388608 to 2147483647, -2147483648 to -8388609) + 8388608, 16777215, 16777216, 2147483647, -8388609, -16777216, -2147483648, +] + +# Test values for i64 write/read operations: all i32 values plus 5-8 byte values +I64_TEST_VALUES: list[int] = I32_TEST_VALUES + [ + # 5-8 byte values (beyond i32 range) + 2147483648, # 2^31, just above i32 max + 4294967295, # 2^32 - 1 + 4294967296, # 2^32 + 1099511627775, # 2^40 - 1 + 281474976710655, # 2^48 - 1 + 72057594037927935, # 2^56 - 1 + 9223372036854775807, # max i64 + -2147483649, # just below i32 min + -4294967296, # -2^32 + -9223372036854775808, # min i64 +] + def test_bytes_writer_basics() -> None: w = BytesWriter() assert w.getvalue() == b"" @@ -120,14 +151,11 @@ def test_bytes_writer_truncate() -> None: b.truncate(-1) def test_bytes_writer_write_i16_le() -> None: - # Test basic values and multiple writes + # Test all i16 values from 1-byte to 2-byte range w = BytesWriter() - write_i16_le(w, 0x1234) - write_i16_le(w, -1) - write_i16_le(w, 0) - write_i16_le(w, -32768) # min i16 - write_i16_le(w, 32767) # max i16 - assert w.getvalue() == b"\x34\x12\xff\xff\x00\x00\x00\x80\xff\x7f" + for v in I16_TEST_VALUES: + write_i16_le(w, v) + assert w.getvalue() == b"".join(struct.pack(" None: write_i16_le(w3, 100000 + int()) def test_bytes_reader_read_i16_le() -> None: - # Test basic reads - data = b"\x34\x12\xff\xff\x00\x00\x00\x80\xff\x7f" - assert read_i16_le(data, 0) == 0x1234 - assert read_i16_le(data, 2) == -1 - assert read_i16_le(data, 4) == 0 - assert read_i16_le(data, 6) == -32768 # min i16 - assert read_i16_le(data, 8) == 32767 # max i16 + # Test all i16 values from 1-byte to 2-byte range + data = b"".join(struct.pack(" None: read_i16_le(bytearray(b"\x00\x00"), 0 + int()) # type: ignore def test_bytes_writer_write_i32_le() -> None: - # Test 1-byte values (0 to 127, and -128 to -1 in terms of low byte pattern) + # Test all i32 values from 1-byte to 4-byte range w = BytesWriter() - values = [0, 1, 127, -1, -128] - for v in values: + for v in I32_TEST_VALUES: write_i32_le(w, v) - assert w.getvalue() == b"".join(struct.pack(" None: write_i32_le(w3, 10000000000 + int()) def test_bytes_reader_read_i32_le() -> None: - # Test 1-byte values - values = [0, 1, 127, -1, -128] - data = b"".join(struct.pack(" None: read_i32_le(bytearray(b"\x00\x00\x00\x00"), 0 + int()) # type: ignore def test_bytes_writer_write_i64_le() -> None: - # Test 1-byte values + # Test all i64 values (includes all i32 values plus 5-8 byte values) w = BytesWriter() - values = [0, 1, 127, -1, -128] - for v in values: + for v in I64_TEST_VALUES: write_i64_le(w, v) - assert w.getvalue() == b"".join(struct.pack(" None: assert result[392:400] == struct.pack(" None: - # Test 1-byte values - values = [0, 1, 127, -1, -128] - data = b"".join(struct.pack(" Date: Fri, 6 Feb 2026 12:17:32 +0000 Subject: [PATCH 09/12] Refactor --- mypyc/lib-rt/byteswriter_extra_ops.h | 68 ++++++++++++++++------------ 1 file changed, 39 insertions(+), 29 deletions(-) diff --git a/mypyc/lib-rt/byteswriter_extra_ops.h b/mypyc/lib-rt/byteswriter_extra_ops.h index 6e0c6f0525ff..5465870357bf 100644 --- a/mypyc/lib-rt/byteswriter_extra_ops.h +++ b/mypyc/lib-rt/byteswriter_extra_ops.h @@ -9,6 +9,8 @@ #include "strings/librt_strings.h" #include "strings/librt_strings_common.h" +// BytesWriter: Length and capacity + static inline CPyTagged CPyBytesWriter_Len(PyObject *obj) { return (CPyTagged)((BytesWriterObject *)obj)->len << 1; @@ -23,6 +25,8 @@ CPyBytesWriter_EnsureSize(BytesWriterObject *data, Py_ssize_t n) { } } +// BytesWriter: Basic write operations + static inline char CPyBytesWriter_Append(PyObject *obj, uint8_t value) { BytesWriterObject *self = (BytesWriterObject *)obj; @@ -35,28 +39,9 @@ CPyBytesWriter_Append(PyObject *obj, uint8_t value) { return CPY_NONE; } -static inline char -CPyBytesWriter_WriteI16LE(PyObject *obj, int16_t value) { - BytesWriterObject *self = (BytesWriterObject *)obj; - if (!CPyBytesWriter_EnsureSize(self, 2)) - return CPY_NONE_ERROR; - BytesWriter_WriteI16LEUnsafe(self, value); - return CPY_NONE; -} - -static inline char -CPyBytesWriter_WriteI32LE(PyObject *obj, int32_t value) { - BytesWriterObject *self = (BytesWriterObject *)obj; - if (!CPyBytesWriter_EnsureSize(self, 4)) - return CPY_NONE_ERROR; - BytesWriter_WriteI32LEUnsafe(self, value); - return CPY_NONE; -} - char CPyBytesWriter_Write(PyObject *obj, PyObject *value); -// Helper function for bytes read error handling (negative index or out of range) -void CPyBytes_ReadError(int64_t index, Py_ssize_t size); +// BytesWriter: Indexing operations // If index is negative, convert to non-negative index (no range checking) static inline int64_t CPyBytesWriter_AdjustIndex(PyObject *obj, int64_t index) { @@ -78,6 +63,40 @@ static inline void CPyBytesWriter_SetItem(PyObject *obj, int64_t index, uint8_t (((BytesWriterObject *)obj)->buf)[index] = x; } +// BytesWriter: Write integer operations (little-endian) + +static inline char +CPyBytesWriter_WriteI16LE(PyObject *obj, int16_t value) { + BytesWriterObject *self = (BytesWriterObject *)obj; + if (!CPyBytesWriter_EnsureSize(self, 2)) + return CPY_NONE_ERROR; + BytesWriter_WriteI16LEUnsafe(self, value); + return CPY_NONE; +} + +static inline char +CPyBytesWriter_WriteI32LE(PyObject *obj, int32_t value) { + BytesWriterObject *self = (BytesWriterObject *)obj; + if (!CPyBytesWriter_EnsureSize(self, 4)) + return CPY_NONE_ERROR; + BytesWriter_WriteI32LEUnsafe(self, value); + return CPY_NONE; +} + +static inline char +CPyBytesWriter_WriteI64LE(PyObject *obj, int64_t value) { + BytesWriterObject *self = (BytesWriterObject *)obj; + if (!CPyBytesWriter_EnsureSize(self, 8)) + return CPY_NONE_ERROR; + BytesWriter_WriteI64LEUnsafe(self, value); + return CPY_NONE; +} + +// Bytes: Read integer operations (little-endian) + +// Helper function for bytes read error handling (negative index or out of range) +void CPyBytes_ReadError(int64_t index, Py_ssize_t size); + static inline int16_t CPyBytes_ReadI16LE(PyObject *bytes_obj, int64_t index) { // bytes_obj type is enforced by mypyc @@ -102,15 +121,6 @@ CPyBytes_ReadI32LE(PyObject *bytes_obj, int64_t index) { return CPyBytes_ReadI32LEUnsafe(data + index); } -static inline char -CPyBytesWriter_WriteI64LE(PyObject *obj, int64_t value) { - BytesWriterObject *self = (BytesWriterObject *)obj; - if (!CPyBytesWriter_EnsureSize(self, 8)) - return CPY_NONE_ERROR; - BytesWriter_WriteI64LEUnsafe(self, value); - return CPY_NONE; -} - static inline int64_t CPyBytes_ReadI64LE(PyObject *bytes_obj, int64_t index) { // bytes_obj type is enforced by mypyc From d7eb95aadcacab452aa782f597b7506811928db3 Mon Sep 17 00:00:00 2001 From: Jukka Lehtosalo Date: Fri, 6 Feb 2026 12:22:15 +0000 Subject: [PATCH 10/12] Fix overlapping error value --- mypyc/primitives/librt_strings_ops.py | 8 ++++---- mypyc/test-data/run-librt-strings.test | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/mypyc/primitives/librt_strings_ops.py b/mypyc/primitives/librt_strings_ops.py index 2fc5152bce7f..ac297c425f84 100644 --- a/mypyc/primitives/librt_strings_ops.py +++ b/mypyc/primitives/librt_strings_ops.py @@ -1,5 +1,5 @@ from mypyc.ir.deps import BYTES_WRITER_EXTRA_OPS, LIBRT_STRINGS, STRING_WRITER_EXTRA_OPS -from mypyc.ir.ops import ERR_MAGIC, ERR_NEVER +from mypyc.ir.ops import ERR_MAGIC, ERR_NEVER, ERR_MAGIC_OVERLAPPING from mypyc.ir.rtypes import ( bool_rprimitive, bytearray_rprimitive, @@ -92,7 +92,7 @@ arg_types=[bytes_rprimitive, int64_rprimitive], return_type=int16_rprimitive, c_function_name="CPyBytes_ReadI16LE", - error_kind=ERR_MAGIC, + error_kind=ERR_MAGIC_OVERLAPPING, experimental=True, dependencies=[LIBRT_STRINGS, BYTES_WRITER_EXTRA_OPS], ) @@ -112,7 +112,7 @@ arg_types=[bytes_rprimitive, int64_rprimitive], return_type=int32_rprimitive, c_function_name="CPyBytes_ReadI32LE", - error_kind=ERR_MAGIC, + error_kind=ERR_MAGIC_OVERLAPPING, experimental=True, dependencies=[LIBRT_STRINGS, BYTES_WRITER_EXTRA_OPS], ) @@ -132,7 +132,7 @@ arg_types=[bytes_rprimitive, int64_rprimitive], return_type=int64_rprimitive, c_function_name="CPyBytes_ReadI64LE", - error_kind=ERR_MAGIC, + error_kind=ERR_MAGIC_OVERLAPPING, experimental=True, dependencies=[LIBRT_STRINGS, BYTES_WRITER_EXTRA_OPS], ) diff --git a/mypyc/test-data/run-librt-strings.test b/mypyc/test-data/run-librt-strings.test index 7c5e7e1ca1fc..5d2a28b9c731 100644 --- a/mypyc/test-data/run-librt-strings.test +++ b/mypyc/test-data/run-librt-strings.test @@ -12,7 +12,7 @@ from testutil import assertRaises # Test values for i16 write/read operations, organized by byte size I16_TEST_VALUES: list[int] = [ # 1-byte values (0 to 127, and -128 to -1 in terms of low byte pattern) - 0, 1, 127, -1, -128, + 0, 1, 127, -1, -113, -128, # -113 is mypyc overlapping error value # 2-byte values (128 to 32767, -32768 to -129) 128, 255, 256, 0x1234, 32767, -129, -256, -32768, ] From 1b8f996c9cf490b6eea0fe589665d99852284b4a Mon Sep 17 00:00:00 2001 From: Jukka Lehtosalo Date: Fri, 6 Feb 2026 12:26:00 +0000 Subject: [PATCH 11/12] Update tests --- mypyc/test-data/run-librt-strings.test | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/mypyc/test-data/run-librt-strings.test b/mypyc/test-data/run-librt-strings.test index 5d2a28b9c731..4653a0ac986e 100644 --- a/mypyc/test-data/run-librt-strings.test +++ b/mypyc/test-data/run-librt-strings.test @@ -17,7 +17,7 @@ I16_TEST_VALUES: list[int] = [ 128, 255, 256, 0x1234, 32767, -129, -256, -32768, ] -# Test values for i32 write/read operations: all i16 values plus 3-4 byte values +# Test values for i32 write/read operations: i16 test values plus 3-4 byte values I32_TEST_VALUES: list[int] = I16_TEST_VALUES + [ # 3-byte values (32768 to 8388607, -8388608 to -32769) 32768, 65535, 65536, 8388607, -32769, -65536, -8388608, @@ -25,7 +25,7 @@ I32_TEST_VALUES: list[int] = I16_TEST_VALUES + [ 8388608, 16777215, 16777216, 2147483647, -8388609, -16777216, -2147483648, ] -# Test values for i64 write/read operations: all i32 values plus 5-8 byte values +# Test values for i64 write/read operations: i32 test values plus 5-8 byte values I64_TEST_VALUES: list[int] = I32_TEST_VALUES + [ # 5-8 byte values (beyond i32 range) 2147483648, # 2^31, just above i32 max @@ -151,7 +151,7 @@ def test_bytes_writer_truncate() -> None: b.truncate(-1) def test_bytes_writer_write_i16_le() -> None: - # Test all i16 values from 1-byte to 2-byte range + # Test various i16 values from 1-byte to 2-byte range w = BytesWriter() for v in I16_TEST_VALUES: write_i16_le(w, v) @@ -203,7 +203,7 @@ def test_write_i16_le_via_any() -> None: write_i16_le(w3, 100000 + int()) def test_bytes_reader_read_i16_le() -> None: - # Test all i16 values from 1-byte to 2-byte range + # Test various i16 values from 1-byte to 2-byte range data = b"".join(struct.pack(" None: for i, v in enumerate(I16_TEST_VALUES): assert read_i16_le(result, i * 2) == v - # Test various offsets + # Test unaligned offset data2 = b"\xFF" + b"\x34\x12" + b"\xFF" assert read_i16_le(data2, 1) == 0x1234 @@ -245,7 +245,7 @@ def test_read_i16_le_via_any() -> None: read_i16_le(bytearray(b"\x00\x00"), 0 + int()) # type: ignore def test_bytes_writer_write_i32_le() -> None: - # Test all i32 values from 1-byte to 4-byte range + # Test various i32 values from 1-byte to 4-byte range w = BytesWriter() for v in I32_TEST_VALUES: write_i32_le(w, v) @@ -297,7 +297,7 @@ def test_write_i32_le_via_any() -> None: write_i32_le(w3, 10000000000 + int()) def test_bytes_reader_read_i32_le() -> None: - # Test all i32 values from 1-byte to 4-byte range + # Test various i32 values from 1-byte to 4-byte range data = b"".join(struct.pack(" None: for i, v in enumerate(I32_TEST_VALUES): assert read_i32_le(result, i * 4) == v - # Test various offsets + # Test unaligned offset data2 = b"\xFF" + struct.pack(" None: for i, v in enumerate(I64_TEST_VALUES): assert read_i64_le(result, i * 8) == v - # Test various offsets + # Test unaligned offset data2 = b"\xFF" + struct.pack(" Date: Fri, 6 Feb 2026 12:26:42 +0000 Subject: [PATCH 12/12] Lint --- mypyc/primitives/librt_strings_ops.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mypyc/primitives/librt_strings_ops.py b/mypyc/primitives/librt_strings_ops.py index ac297c425f84..3e863e96d0df 100644 --- a/mypyc/primitives/librt_strings_ops.py +++ b/mypyc/primitives/librt_strings_ops.py @@ -1,5 +1,5 @@ from mypyc.ir.deps import BYTES_WRITER_EXTRA_OPS, LIBRT_STRINGS, STRING_WRITER_EXTRA_OPS -from mypyc.ir.ops import ERR_MAGIC, ERR_NEVER, ERR_MAGIC_OVERLAPPING +from mypyc.ir.ops import ERR_MAGIC, ERR_MAGIC_OVERLAPPING, ERR_NEVER from mypyc.ir.rtypes import ( bool_rprimitive, bytearray_rprimitive,