diff --git a/mypy/typeshed/stubs/librt/librt/strings.pyi b/mypy/typeshed/stubs/librt/librt/strings.pyi index feb348ce59c3..a0c0cb9efdeb 100644 --- a/mypy/typeshed/stubs/librt/librt/strings.pyi +++ b/mypy/typeshed/stubs/librt/librt/strings.pyi @@ -22,3 +22,7 @@ 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: ... +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.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..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,19 +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_write_i16_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); +// 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) { @@ -69,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 @@ -78,7 +106,31 @@ 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 +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 CPyBytes_ReadI32LEUnsafe(data + index); +} + +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 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 6019251515af..a06602793565 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,15 +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_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; + int16_t unboxed = CPyLong_AsInt16(args[1]); + if (unlikely(unboxed == CPY_LL_INT_ERROR && PyErr_Occurred())) + return NULL; + if (unlikely(!ensure_bytes_writer_size(bw, 2))) + return NULL; + BytesWriter_WriteI16LEUnsafe(bw, unboxed); + Py_RETURN_NONE; +} + +static PyObject* +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; + return PyLong_FromLong(CPyBytes_ReadI16LEUnsafe(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; + int32_t unboxed = CPyLong_AsInt32(args[1]); + if (unlikely(unboxed == CPY_LL_INT_ERROR && PyErr_Occurred())) + return NULL; + if (unlikely(!ensure_bytes_writer_size(bw, 4))) + return NULL; + BytesWriter_WriteI32LEUnsafe(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; + return PyLong_FromLong(CPyBytes_ReadI32LEUnsafe(data + index)); +} + +static PyObject* +write_i64_le(PyObject *module, PyObject *const *args, size_t nargs) { + BytesWriterObject *bw = parse_write_int_args(args, nargs, "write_i64_le"); + if (bw == NULL) + return NULL; + int64_t unboxed = CPyLong_AsInt64(args[1]); + if (unlikely(unboxed == CPY_LL_INT_ERROR && PyErr_Occurred())) + return NULL; + if (unlikely(!ensure_bytes_writer_size(bw, 8))) + return NULL; + BytesWriter_WriteI64LEUnsafe(bw, unboxed); + Py_RETURN_NONE; +} + +static PyObject* +read_i64_le(PyObject *module, PyObject *const *args, size_t nargs) { + int64_t index; + const unsigned char *data = parse_read_int_args(args, nargs, "read_i64_le", 8, &index); + if (data == NULL) + return NULL; + return PyLong_FromLongLong(CPyBytes_ReadI64LEUnsafe(data + index)); } #endif @@ -900,6 +959,18 @@ 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") + }, + {"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 9f0dd07d3cdf..a8cfb217d695 100644 --- a/mypyc/lib-rt/strings/librt_strings_common.h +++ b/mypyc/lib-rt/strings/librt_strings_common.h @@ -5,6 +5,41 @@ #include #include +// Byte-swap functions for non-native endianness 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 +// 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 + // Length of the default buffer embedded directly in a BytesWriter object #define WRITER_EMBEDDED_BUF_LEN 256 @@ -19,28 +54,82 @@ 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) { - // 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; +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); + 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; +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); +#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_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); + 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 +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); +#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_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); + 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 +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); +#if PY_BIG_ENDIAN + value = BSWAP64(value); +#endif + return (int64_t)value; } #endif // LIBRT_STRINGS_COMMON_H diff --git a/mypyc/primitives/librt_strings_ops.py b/mypyc/primitives/librt_strings_ops.py index 5eae6aa13e83..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 +from mypyc.ir.ops import ERR_MAGIC, ERR_MAGIC_OVERLAPPING, ERR_NEVER from mypyc.ir.rtypes import ( bool_rprimitive, bytearray_rprimitive, @@ -92,11 +92,51 @@ arg_types=[bytes_rprimitive, int64_rprimitive], return_type=int16_rprimitive, c_function_name="CPyBytes_ReadI16LE", + error_kind=ERR_MAGIC_OVERLAPPING, + experimental=True, + 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_OVERLAPPING, + experimental=True, + 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_OVERLAPPING, + 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..17c9e6597c9a 100644 --- a/mypyc/test-data/irbuild-librt-strings.test +++ b/mypyc/test-data/irbuild-librt-strings.test @@ -140,6 +140,85 @@ 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_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 621c6f323566..4653a0ac986e 100644 --- a/mypyc/test-data/run-librt-strings.test +++ b/mypyc/test-data/run-librt-strings.test @@ -3,11 +3,43 @@ 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, write_i64_le, read_i64_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, -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, +] + +# 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, + # 4-byte values (8388608 to 2147483647, -2147483648 to -8388609) + 8388608, 16777215, 16777216, 2147483647, -8388609, -16777216, -2147483648, +] + +# 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 + 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"" @@ -119,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 various 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 various i16 values from 1-byte to 2-byte range + data = b"".join(struct.pack(" 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 various i32 values from 1-byte to 4-byte range + w = BytesWriter() + for v in I32_TEST_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 various i32 values from 1-byte to 4-byte range + data = b"".join(struct.pack(" None: + # Test read_i32_le via Any to ensure C extension wrapper works + data: Any = struct.pack(" None: + # Test all i64 values (includes all i32 values plus 5-8 byte values) + w = BytesWriter() + for v in I64_TEST_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 all i64 values (includes all i32 values plus 5-8 byte values) + 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"))