From 750e163107da641dd8aa77c19d932faa5f8a0fb4 Mon Sep 17 00:00:00 2001 From: Jukka Lehtosalo Date: Fri, 6 Feb 2026 13:50:47 +0000 Subject: [PATCH 01/12] write_f64_le --- mypy/typeshed/stubs/librt/librt/strings.pyi | 1 + mypyc/lib-rt/byteswriter_extra_ops.h | 11 +++ mypyc/lib-rt/strings/librt_strings.c | 17 +++++ mypyc/lib-rt/strings/librt_strings_common.h | 16 +++++ mypyc/primitives/librt_strings_ops.py | 13 ++++ mypyc/test-data/run-librt-strings.test | 78 ++++++++++++++++++++- 6 files changed, 135 insertions(+), 1 deletion(-) diff --git a/mypy/typeshed/stubs/librt/librt/strings.pyi b/mypy/typeshed/stubs/librt/librt/strings.pyi index 51f04c680a56..aa9a59c55fae 100644 --- a/mypy/typeshed/stubs/librt/librt/strings.pyi +++ b/mypy/typeshed/stubs/librt/librt/strings.pyi @@ -32,3 +32,4 @@ def write_i64_le(b: BytesWriter, n: i64, /) -> None: ... def write_i64_be(b: BytesWriter, n: i64, /) -> None: ... def read_i64_le(b: bytes, index: i64, /) -> i64: ... def read_i64_be(b: bytes, index: i64, /) -> i64: ... +def write_f64_le(b: BytesWriter, n: float, /) -> None: ... diff --git a/mypyc/lib-rt/byteswriter_extra_ops.h b/mypyc/lib-rt/byteswriter_extra_ops.h index 404078bfb611..b3860b5f4f4e 100644 --- a/mypyc/lib-rt/byteswriter_extra_ops.h +++ b/mypyc/lib-rt/byteswriter_extra_ops.h @@ -119,6 +119,17 @@ CPyBytesWriter_WriteI64BE(PyObject *obj, int64_t value) { return CPY_NONE; } +// BytesWriter: Write float operations + +static inline char +CPyBytesWriter_WriteF64LE(PyObject *obj, double value) { + BytesWriterObject *self = (BytesWriterObject *)obj; + if (!CPyBytesWriter_EnsureSize(self, 8)) + return CPY_NONE_ERROR; + BytesWriter_WriteF64LEUnsafe(self, value); + return CPY_NONE; +} + // Bytes: Read integer operations // Helper function for bytes read error handling (negative index or out of range) diff --git a/mypyc/lib-rt/strings/librt_strings.c b/mypyc/lib-rt/strings/librt_strings.c index f1d6fd91d604..d9bc8e90c2f2 100644 --- a/mypyc/lib-rt/strings/librt_strings.c +++ b/mypyc/lib-rt/strings/librt_strings.c @@ -1009,6 +1009,20 @@ read_i64_le(PyObject *module, PyObject *const *args, size_t nargs) { return PyLong_FromLongLong(CPyBytes_ReadI64LEUnsafe(data + index)); } +static PyObject* +write_f64_le(PyObject *module, PyObject *const *args, size_t nargs) { + BytesWriterObject *bw = parse_write_int_args(args, nargs, "write_f64_le"); + if (bw == NULL) + return NULL; + double unboxed = PyFloat_AsDouble(args[1]); + if (unlikely(unboxed == -1.0 && PyErr_Occurred())) + return NULL; + if (unlikely(!ensure_bytes_writer_size(bw, 8))) + return NULL; + BytesWriter_WriteF64LEUnsafe(bw, unboxed); + Py_RETURN_NONE; +} + static PyObject* read_i64_be(PyObject *module, PyObject *const *args, size_t nargs) { int64_t index; @@ -1058,6 +1072,9 @@ static PyMethodDef librt_strings_module_methods[] = { {"read_i64_be", (PyCFunction) read_i64_be, METH_FASTCALL, PyDoc_STR("Read a 64-bit signed integer from bytes in big-endian format") }, + {"write_f64_le", (PyCFunction) write_f64_le, METH_FASTCALL, + PyDoc_STR("Write a 64-bit float to BytesWriter 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 d2ea605aea78..37ea319bc757 100644 --- a/mypyc/lib-rt/strings/librt_strings_common.h +++ b/mypyc/lib-rt/strings/librt_strings_common.h @@ -217,4 +217,20 @@ CPyBytes_ReadI64BEUnsafe(const unsigned char *data) { return (int64_t)value; } +// Write a 64-bit float (double) in little-endian format to BytesWriter. +// NOTE: This does NOT check buffer capacity - caller must ensure space is available. +static inline void +BytesWriter_WriteF64LEUnsafe(BytesWriterObject *self, double value) { + // memcpy is reliably optimized to a single store by GCC, Clang, and MSVC +#if PY_BIG_ENDIAN + uint64_t bits; + memcpy(&bits, &value, 8); + bits = BSWAP64(bits); + memcpy(self->buf + self->len, &bits, 8); +#else + memcpy(self->buf + self->len, &value, 8); +#endif + self->len += 8; +} + #endif // LIBRT_STRINGS_COMMON_H diff --git a/mypyc/primitives/librt_strings_ops.py b/mypyc/primitives/librt_strings_ops.py index 9cbfc4e82ced..6cea217565b2 100644 --- a/mypyc/primitives/librt_strings_ops.py +++ b/mypyc/primitives/librt_strings_ops.py @@ -5,6 +5,7 @@ bytearray_rprimitive, bytes_rprimitive, bytes_writer_rprimitive, + float_rprimitive, int16_rprimitive, int32_rprimitive, int64_rprimitive, @@ -257,6 +258,18 @@ dependencies=[LIBRT_STRINGS, BYTES_WRITER_EXTRA_OPS], ) +# f64 write/read functions + +function_op( + name="librt.strings.write_f64_le", + arg_types=[bytes_writer_rprimitive, float_rprimitive], + return_type=none_rprimitive, + c_function_name="CPyBytesWriter_WriteF64LE", + error_kind=ERR_MAGIC, + experimental=True, + dependencies=[LIBRT_STRINGS, BYTES_WRITER_EXTRA_OPS], +) + # StringWriter operations function_op( diff --git a/mypyc/test-data/run-librt-strings.test b/mypyc/test-data/run-librt-strings.test index cea23186ea69..6834745c24b2 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, write_i16_be, read_i16_le, read_i16_be, write_i32_le, write_i32_be, read_i32_le, read_i32_be, write_i64_le, write_i64_be, read_i64_le, read_i64_be +from librt.strings import BytesWriter, StringWriter, write_i16_le, write_i16_be, read_i16_le, read_i16_be, write_i32_le, write_i32_be, read_i32_le, read_i32_be, write_i64_le, write_i64_be, read_i64_le, read_i64_be, write_f64_le from testutil import assertRaises @@ -40,6 +40,22 @@ I64_TEST_VALUES: list[int] = I32_TEST_VALUES + [ -9223372036854775808, # min i64 ] +# Test values for f64 write/read operations +F64_TEST_VALUES: list[float] = [ + 0.0, -0.0, 1.0, -1.0, + -113.0, # mypyc overlapping error value + 0.5, -0.5, 0.1, -0.1, + 1.5, 255.0, 256.0, + 1e10, -1e10, 1e100, -1e100, + 1e-10, 1e-100, + 1.7976931348623157e+308, # max float + -1.7976931348623157e+308, # min float + 2.2250738585072014e-308, # min positive normal + 5e-324, # min positive subnormal + float('inf'), float('-inf'), + float('nan'), +] + def test_bytes_writer_basics() -> None: w = BytesWriter() assert w.getvalue() == b"" @@ -569,6 +585,66 @@ def test_read_i64_via_any() -> None: with assertRaises(TypeError): read_func(bytearray(b"\x00" * 8), 0 + int()) # type: ignore +def test_bytes_writer_write_f64_le() -> None: + import math + # Test various f64 values + w = BytesWriter() + for v in F64_TEST_VALUES: + write_f64_le(w, v) + result = w.getvalue() + expected = b"".join(struct.pack(" None: + import math + # Test write_f64_le via Any to ensure C extension wrapper works + w: Any = BytesWriter() + + w.append(0x42) + write_f64_le(w, 1.5) + w.append(0xFF) + assert w.getvalue() == b"\x42" + struct.pack(" None: w = BytesWriter() w.write(bytearray(b"foobar")) From 2b35f3a02fd40d421081036d5ba9612752b4f022 Mon Sep 17 00:00:00 2001 From: Jukka Lehtosalo Date: Fri, 6 Feb 2026 13:54:09 +0000 Subject: [PATCH 02/12] write_f64_be --- mypy/typeshed/stubs/librt/librt/strings.pyi | 1 + mypyc/lib-rt/byteswriter_extra_ops.h | 9 ++ mypyc/lib-rt/strings/librt_strings.c | 17 ++++ mypyc/lib-rt/strings/librt_strings_common.h | 16 ++++ mypyc/primitives/librt_strings_ops.py | 10 +++ mypyc/test-data/run-librt-strings.test | 92 +++++++++++++-------- 6 files changed, 111 insertions(+), 34 deletions(-) diff --git a/mypy/typeshed/stubs/librt/librt/strings.pyi b/mypy/typeshed/stubs/librt/librt/strings.pyi index aa9a59c55fae..35596aef4d9c 100644 --- a/mypy/typeshed/stubs/librt/librt/strings.pyi +++ b/mypy/typeshed/stubs/librt/librt/strings.pyi @@ -33,3 +33,4 @@ def write_i64_be(b: BytesWriter, n: i64, /) -> None: ... def read_i64_le(b: bytes, index: i64, /) -> i64: ... def read_i64_be(b: bytes, index: i64, /) -> i64: ... def write_f64_le(b: BytesWriter, n: float, /) -> None: ... +def write_f64_be(b: BytesWriter, n: float, /) -> None: ... diff --git a/mypyc/lib-rt/byteswriter_extra_ops.h b/mypyc/lib-rt/byteswriter_extra_ops.h index b3860b5f4f4e..52f5a1b5eed2 100644 --- a/mypyc/lib-rt/byteswriter_extra_ops.h +++ b/mypyc/lib-rt/byteswriter_extra_ops.h @@ -130,6 +130,15 @@ CPyBytesWriter_WriteF64LE(PyObject *obj, double value) { return CPY_NONE; } +static inline char +CPyBytesWriter_WriteF64BE(PyObject *obj, double value) { + BytesWriterObject *self = (BytesWriterObject *)obj; + if (!CPyBytesWriter_EnsureSize(self, 8)) + return CPY_NONE_ERROR; + BytesWriter_WriteF64BEUnsafe(self, value); + return CPY_NONE; +} + // Bytes: Read integer operations // Helper function for bytes read error handling (negative index or out of range) diff --git a/mypyc/lib-rt/strings/librt_strings.c b/mypyc/lib-rt/strings/librt_strings.c index d9bc8e90c2f2..d1a153f1a4cf 100644 --- a/mypyc/lib-rt/strings/librt_strings.c +++ b/mypyc/lib-rt/strings/librt_strings.c @@ -1023,6 +1023,20 @@ write_f64_le(PyObject *module, PyObject *const *args, size_t nargs) { Py_RETURN_NONE; } +static PyObject* +write_f64_be(PyObject *module, PyObject *const *args, size_t nargs) { + BytesWriterObject *bw = parse_write_int_args(args, nargs, "write_f64_be"); + if (bw == NULL) + return NULL; + double unboxed = PyFloat_AsDouble(args[1]); + if (unlikely(unboxed == -1.0 && PyErr_Occurred())) + return NULL; + if (unlikely(!ensure_bytes_writer_size(bw, 8))) + return NULL; + BytesWriter_WriteF64BEUnsafe(bw, unboxed); + Py_RETURN_NONE; +} + static PyObject* read_i64_be(PyObject *module, PyObject *const *args, size_t nargs) { int64_t index; @@ -1075,6 +1089,9 @@ static PyMethodDef librt_strings_module_methods[] = { {"write_f64_le", (PyCFunction) write_f64_le, METH_FASTCALL, PyDoc_STR("Write a 64-bit float to BytesWriter in little-endian format") }, + {"write_f64_be", (PyCFunction) write_f64_be, METH_FASTCALL, + PyDoc_STR("Write a 64-bit float to BytesWriter in big-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 37ea319bc757..39795469a9cf 100644 --- a/mypyc/lib-rt/strings/librt_strings_common.h +++ b/mypyc/lib-rt/strings/librt_strings_common.h @@ -233,4 +233,20 @@ BytesWriter_WriteF64LEUnsafe(BytesWriterObject *self, double value) { self->len += 8; } +// Write a 64-bit float (double) in big-endian format to BytesWriter. +// NOTE: This does NOT check buffer capacity - caller must ensure space is available. +static inline void +BytesWriter_WriteF64BEUnsafe(BytesWriterObject *self, double value) { + // memcpy is reliably optimized to a single store by GCC, Clang, and MSVC +#if PY_BIG_ENDIAN + memcpy(self->buf + self->len, &value, 8); +#else + uint64_t bits; + memcpy(&bits, &value, 8); + bits = BSWAP64(bits); + memcpy(self->buf + self->len, &bits, 8); +#endif + self->len += 8; +} + #endif // LIBRT_STRINGS_COMMON_H diff --git a/mypyc/primitives/librt_strings_ops.py b/mypyc/primitives/librt_strings_ops.py index 6cea217565b2..a4475a4e0e5e 100644 --- a/mypyc/primitives/librt_strings_ops.py +++ b/mypyc/primitives/librt_strings_ops.py @@ -270,6 +270,16 @@ dependencies=[LIBRT_STRINGS, BYTES_WRITER_EXTRA_OPS], ) +function_op( + name="librt.strings.write_f64_be", + arg_types=[bytes_writer_rprimitive, float_rprimitive], + return_type=none_rprimitive, + c_function_name="CPyBytesWriter_WriteF64BE", + error_kind=ERR_MAGIC, + experimental=True, + dependencies=[LIBRT_STRINGS, BYTES_WRITER_EXTRA_OPS], +) + # StringWriter operations function_op( diff --git a/mypyc/test-data/run-librt-strings.test b/mypyc/test-data/run-librt-strings.test index 6834745c24b2..e634f468c59c 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, write_i16_be, read_i16_le, read_i16_be, write_i32_le, write_i32_be, read_i32_le, read_i32_be, write_i64_le, write_i64_be, read_i64_le, read_i64_be, write_f64_le +from librt.strings import BytesWriter, StringWriter, write_i16_le, write_i16_be, read_i16_le, read_i16_be, write_i32_le, write_i32_be, read_i32_le, read_i32_be, write_i64_le, write_i64_be, read_i64_le, read_i64_be, write_f64_le, write_f64_be from testutil import assertRaises @@ -586,7 +586,6 @@ def test_read_i64_via_any() -> None: read_func(bytearray(b"\x00" * 8), 0 + int()) # type: ignore def test_bytes_writer_write_f64_le() -> None: - import math # Test various f64 values w = BytesWriter() for v in F64_TEST_VALUES: @@ -610,40 +609,65 @@ def test_bytes_writer_write_f64_le() -> None: assert result[9:17] == struct.pack(" None: - import math - # Test write_f64_le via Any to ensure C extension wrapper works - w: Any = BytesWriter() +def test_bytes_writer_write_f64_be() -> None: + # Test various f64 values + w = BytesWriter() + for v in F64_TEST_VALUES: + write_f64_be(w, v) + result = w.getvalue() + expected = b"".join(struct.pack(">d", v) for v in F64_TEST_VALUES) + # Compare byte-by-byte (NaN != NaN but bytes should match) + assert result == expected - w.append(0x42) - write_f64_le(w, 1.5) + # Test mixing with other operations and buffer growth + w = BytesWriter() w.append(0xFF) - assert w.getvalue() == b"\x42" + struct.pack("d", 0.0) + assert result[9:17] == struct.pack(">d", 1.5) + assert result[1593:1601] == struct.pack(">d", 199.0 * 1.5) + +def test_write_f64_via_any() -> None: + import math + # Test write_f64_le/be via Any to ensure C extension wrapper works + for write_func, fmt in zip((write_f64_le, write_f64_be), ("d")): + w: Any = BytesWriter() + + w.append(0x42) + write_func(w, 1.5) + w.append(0xFF) + assert w.getvalue() == b"\x42" + struct.pack(fmt, 1.5) + b"\xFF" + + # Test buffer growth + w2: Any = BytesWriter() + for i in range(50): + write_func(w2, float(i) * 0.25) + result = w2.getvalue() + assert len(result) == 400 + assert result[0:8] == struct.pack(fmt, 0.0) + assert result[8:16] == struct.pack(fmt, 0.25) + assert result[392:400] == struct.pack(fmt, 49.0 * 0.25) + + # Test special values + w3: Any = BytesWriter() + write_func(w3, float('inf')) + write_func(w3, float('-inf')) + write_func(w3, float('nan')) + result = w3.getvalue() + assert result[0:8] == struct.pack(fmt, float('inf')) + assert result[8:16] == struct.pack(fmt, float('-inf')) + assert math.isnan(struct.unpack(fmt, result[16:24])[0]) + + # Test wrong type + w4: Any = BytesWriter() + with assertRaises(TypeError): + write_func(w4, "not a float" + str()) # type: ignore def test_write_bytearray() -> None: w = BytesWriter() From e11763e7118451ce4fa07823b5286f53c9f29194 Mon Sep 17 00:00:00 2001 From: Jukka Lehtosalo Date: Fri, 6 Feb 2026 14:27:26 +0000 Subject: [PATCH 03/12] read_f64_le --- mypy/typeshed/stubs/librt/librt/strings.pyi | 1 + mypyc/lib-rt/byteswriter_extra_ops.h | 14 +++++ mypyc/lib-rt/strings/librt_strings.c | 12 +++++ mypyc/lib-rt/strings/librt_strings_common.h | 17 ++++++ mypyc/primitives/librt_strings_ops.py | 10 ++++ mypyc/test-data/run-librt-strings.test | 59 ++++++++++++++++++++- 6 files changed, 112 insertions(+), 1 deletion(-) diff --git a/mypy/typeshed/stubs/librt/librt/strings.pyi b/mypy/typeshed/stubs/librt/librt/strings.pyi index 35596aef4d9c..b8a506cbeebc 100644 --- a/mypy/typeshed/stubs/librt/librt/strings.pyi +++ b/mypy/typeshed/stubs/librt/librt/strings.pyi @@ -33,4 +33,5 @@ def write_i64_be(b: BytesWriter, n: i64, /) -> None: ... def read_i64_le(b: bytes, index: i64, /) -> i64: ... def read_i64_be(b: bytes, index: i64, /) -> i64: ... def write_f64_le(b: BytesWriter, n: float, /) -> None: ... +def read_f64_le(b: bytes, index: i64, /) -> float: ... def write_f64_be(b: BytesWriter, n: float, /) -> None: ... diff --git a/mypyc/lib-rt/byteswriter_extra_ops.h b/mypyc/lib-rt/byteswriter_extra_ops.h index 52f5a1b5eed2..5ed02ca5d33e 100644 --- a/mypyc/lib-rt/byteswriter_extra_ops.h +++ b/mypyc/lib-rt/byteswriter_extra_ops.h @@ -216,6 +216,20 @@ CPyBytes_ReadI64BE(PyObject *bytes_obj, int64_t index) { return CPyBytes_ReadI64BEUnsafe(data + index); } +// Bytes: Read float operations + +static inline double +CPyBytes_ReadF64LE(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_FLOAT_ERROR; + } + const unsigned char *data = (const unsigned char *)PyBytes_AS_STRING(bytes_obj); + return CPyBytes_ReadF64LEUnsafe(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 d1a153f1a4cf..04b4ab3561c4 100644 --- a/mypyc/lib-rt/strings/librt_strings.c +++ b/mypyc/lib-rt/strings/librt_strings.c @@ -1046,6 +1046,15 @@ read_i64_be(PyObject *module, PyObject *const *args, size_t nargs) { return PyLong_FromLongLong(CPyBytes_ReadI64BEUnsafe(data + index)); } +static PyObject* +read_f64_le(PyObject *module, PyObject *const *args, size_t nargs) { + int64_t index; + const unsigned char *data = parse_read_int_args(args, nargs, "read_f64_le", 8, &index); + if (data == NULL) + return NULL; + return PyFloat_FromDouble(CPyBytes_ReadF64LEUnsafe(data + index)); +} + #endif static PyMethodDef librt_strings_module_methods[] = { @@ -1092,6 +1101,9 @@ static PyMethodDef librt_strings_module_methods[] = { {"write_f64_be", (PyCFunction) write_f64_be, METH_FASTCALL, PyDoc_STR("Write a 64-bit float to BytesWriter in big-endian format") }, + {"read_f64_le", (PyCFunction) read_f64_le, METH_FASTCALL, + PyDoc_STR("Read a 64-bit float 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 39795469a9cf..2f6e0bec2b20 100644 --- a/mypyc/lib-rt/strings/librt_strings_common.h +++ b/mypyc/lib-rt/strings/librt_strings_common.h @@ -233,6 +233,23 @@ BytesWriter_WriteF64LEUnsafe(BytesWriterObject *self, double value) { self->len += 8; } +// Read a 64-bit float (double) in little-endian format from bytes. +// NOTE: This does NOT check bounds - caller must ensure valid index. +static inline double +CPyBytes_ReadF64LEUnsafe(const unsigned char *data) { + // memcpy is reliably optimized to a single load by GCC, Clang, and MSVC + double value; +#if PY_BIG_ENDIAN + uint64_t bits; + memcpy(&bits, data, 8); + bits = BSWAP64(bits); + memcpy(&value, &bits, 8); +#else + memcpy(&value, data, 8); +#endif + return value; +} + // Write a 64-bit float (double) in big-endian format to BytesWriter. // NOTE: This does NOT check buffer capacity - caller must ensure space is available. static inline void diff --git a/mypyc/primitives/librt_strings_ops.py b/mypyc/primitives/librt_strings_ops.py index a4475a4e0e5e..8935c509f302 100644 --- a/mypyc/primitives/librt_strings_ops.py +++ b/mypyc/primitives/librt_strings_ops.py @@ -280,6 +280,16 @@ dependencies=[LIBRT_STRINGS, BYTES_WRITER_EXTRA_OPS], ) +function_op( + name="librt.strings.read_f64_le", + arg_types=[bytes_rprimitive, int64_rprimitive], + return_type=float_rprimitive, + c_function_name="CPyBytes_ReadF64LE", + error_kind=ERR_MAGIC_OVERLAPPING, + experimental=True, + dependencies=[LIBRT_STRINGS, BYTES_WRITER_EXTRA_OPS], +) + # StringWriter operations function_op( diff --git a/mypyc/test-data/run-librt-strings.test b/mypyc/test-data/run-librt-strings.test index e634f468c59c..bf4e22645f67 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, write_i16_be, read_i16_le, read_i16_be, write_i32_le, write_i32_be, read_i32_le, read_i32_be, write_i64_le, write_i64_be, read_i64_le, read_i64_be, write_f64_le, write_f64_be +from librt.strings import BytesWriter, StringWriter, write_i16_le, write_i16_be, read_i16_le, read_i16_be, write_i32_le, write_i32_be, read_i32_le, read_i32_be, write_i64_le, write_i64_be, read_i64_le, read_i64_be, write_f64_le, read_f64_le, write_f64_be from testutil import assertRaises @@ -633,6 +633,63 @@ def test_bytes_writer_write_f64_be() -> None: assert result[9:17] == struct.pack(">d", 1.5) assert result[1593:1601] == struct.pack(">d", 199.0 * 1.5) +def test_bytes_reader_read_f64_le() -> None: + import math + # Test various f64 values + data = b"".join(struct.pack(" None: + import math + # Test read_f64_le via Any to ensure C extension wrapper works + data: Any = struct.pack(" None: import math # Test write_f64_le/be via Any to ensure C extension wrapper works From 905ed8c56294fca00602803ae0a06529aa6decde Mon Sep 17 00:00:00 2001 From: Jukka Lehtosalo Date: Fri, 6 Feb 2026 14:36:48 +0000 Subject: [PATCH 04/12] read_f64_be --- mypy/typeshed/stubs/librt/librt/strings.pyi | 1 + mypyc/lib-rt/byteswriter_extra_ops.h | 12 +++ mypyc/lib-rt/strings/librt_strings.c | 12 +++ mypyc/lib-rt/strings/librt_strings_common.h | 17 ++++ mypyc/primitives/librt_strings_ops.py | 10 +++ mypyc/test-data/run-librt-strings.test | 86 ++++++++++++++------- 6 files changed, 109 insertions(+), 29 deletions(-) diff --git a/mypy/typeshed/stubs/librt/librt/strings.pyi b/mypy/typeshed/stubs/librt/librt/strings.pyi index b8a506cbeebc..3322d790870c 100644 --- a/mypy/typeshed/stubs/librt/librt/strings.pyi +++ b/mypy/typeshed/stubs/librt/librt/strings.pyi @@ -35,3 +35,4 @@ def read_i64_be(b: bytes, index: i64, /) -> i64: ... def write_f64_le(b: BytesWriter, n: float, /) -> None: ... def read_f64_le(b: bytes, index: i64, /) -> float: ... def write_f64_be(b: BytesWriter, n: float, /) -> None: ... +def read_f64_be(b: bytes, index: i64, /) -> float: ... diff --git a/mypyc/lib-rt/byteswriter_extra_ops.h b/mypyc/lib-rt/byteswriter_extra_ops.h index 5ed02ca5d33e..fa9684598f5b 100644 --- a/mypyc/lib-rt/byteswriter_extra_ops.h +++ b/mypyc/lib-rt/byteswriter_extra_ops.h @@ -230,6 +230,18 @@ CPyBytes_ReadF64LE(PyObject *bytes_obj, int64_t index) { return CPyBytes_ReadF64LEUnsafe(data + index); } +static inline double +CPyBytes_ReadF64BE(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_FLOAT_ERROR; + } + const unsigned char *data = (const unsigned char *)PyBytes_AS_STRING(bytes_obj); + return CPyBytes_ReadF64BEUnsafe(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 04b4ab3561c4..8aeb9aaedfd8 100644 --- a/mypyc/lib-rt/strings/librt_strings.c +++ b/mypyc/lib-rt/strings/librt_strings.c @@ -1055,6 +1055,15 @@ read_f64_le(PyObject *module, PyObject *const *args, size_t nargs) { return PyFloat_FromDouble(CPyBytes_ReadF64LEUnsafe(data + index)); } +static PyObject* +read_f64_be(PyObject *module, PyObject *const *args, size_t nargs) { + int64_t index; + const unsigned char *data = parse_read_int_args(args, nargs, "read_f64_be", 8, &index); + if (data == NULL) + return NULL; + return PyFloat_FromDouble(CPyBytes_ReadF64BEUnsafe(data + index)); +} + #endif static PyMethodDef librt_strings_module_methods[] = { @@ -1104,6 +1113,9 @@ static PyMethodDef librt_strings_module_methods[] = { {"read_f64_le", (PyCFunction) read_f64_le, METH_FASTCALL, PyDoc_STR("Read a 64-bit float from bytes in little-endian format") }, + {"read_f64_be", (PyCFunction) read_f64_be, METH_FASTCALL, + PyDoc_STR("Read a 64-bit float from bytes in big-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 2f6e0bec2b20..f0f5768ecbd2 100644 --- a/mypyc/lib-rt/strings/librt_strings_common.h +++ b/mypyc/lib-rt/strings/librt_strings_common.h @@ -266,4 +266,21 @@ BytesWriter_WriteF64BEUnsafe(BytesWriterObject *self, double value) { self->len += 8; } +// Read a 64-bit float (double) in big-endian format from bytes. +// NOTE: This does NOT check bounds - caller must ensure valid index. +static inline double +CPyBytes_ReadF64BEUnsafe(const unsigned char *data) { + // memcpy is reliably optimized to a single load by GCC, Clang, and MSVC + double value; +#if PY_BIG_ENDIAN + memcpy(&value, data, 8); +#else + uint64_t bits; + memcpy(&bits, data, 8); + bits = BSWAP64(bits); + memcpy(&value, &bits, 8); +#endif + return value; +} + #endif // LIBRT_STRINGS_COMMON_H diff --git a/mypyc/primitives/librt_strings_ops.py b/mypyc/primitives/librt_strings_ops.py index 8935c509f302..daf69d3fee95 100644 --- a/mypyc/primitives/librt_strings_ops.py +++ b/mypyc/primitives/librt_strings_ops.py @@ -290,6 +290,16 @@ dependencies=[LIBRT_STRINGS, BYTES_WRITER_EXTRA_OPS], ) +function_op( + name="librt.strings.read_f64_be", + arg_types=[bytes_rprimitive, int64_rprimitive], + return_type=float_rprimitive, + c_function_name="CPyBytes_ReadF64BE", + error_kind=ERR_MAGIC_OVERLAPPING, + experimental=True, + dependencies=[LIBRT_STRINGS, BYTES_WRITER_EXTRA_OPS], +) + # StringWriter operations function_op( diff --git a/mypyc/test-data/run-librt-strings.test b/mypyc/test-data/run-librt-strings.test index bf4e22645f67..8f6c7d033a10 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, write_i16_be, read_i16_le, read_i16_be, write_i32_le, write_i32_be, read_i32_le, read_i32_be, write_i64_le, write_i64_be, read_i64_le, read_i64_be, write_f64_le, read_f64_le, write_f64_be +from librt.strings import BytesWriter, StringWriter, write_i16_le, write_i16_be, read_i16_le, read_i16_be, write_i32_le, write_i32_be, read_i32_le, read_i32_be, write_i64_le, write_i64_be, read_i64_le, read_i64_be, write_f64_le, read_f64_le, write_f64_be, read_f64_be from testutil import assertRaises @@ -660,35 +660,63 @@ def test_bytes_reader_read_f64_le() -> None: data2 = b"\xFF" + struct.pack(" None: +def test_bytes_reader_read_f64_be() -> None: import math - # Test read_f64_le via Any to ensure C extension wrapper works - data: Any = struct.pack("d", v) for v in F64_TEST_VALUES) + for i, v in enumerate(F64_TEST_VALUES): + result = read_f64_be(data, i * 8) + if math.isnan(v): + assert math.isnan(result) + else: + assert result == v + + # Test round-trip with write_f64_be + w = BytesWriter() + for v in F64_TEST_VALUES: + write_f64_be(w, v) + result_bytes = w.getvalue() + for i, v in enumerate(F64_TEST_VALUES): + result = read_f64_be(result_bytes, i * 8) + if math.isnan(v): + assert math.isnan(result) + else: + assert result == v + + # Test unaligned offset + data2 = b"\xFF" + struct.pack(">d", 1.5) + b"\xFF" + assert read_f64_be(data2, 1) == 1.5 + +def test_read_f64_via_any() -> None: + import math + # Test read_f64_le/be via Any to ensure C extension wrapper works + for read_func, fmt in zip((read_f64_le, read_f64_be), ("d")): + data: Any = struct.pack(fmt, 1.5) + struct.pack(fmt, -113.0) + struct.pack(fmt, 0.0) + assert read_func(data, 0) == 1.5 + assert read_func(data, 8) == -113.0 + assert read_func(data, 16) == 0.0 + + # Test special values + data2: Any = struct.pack(fmt, float('inf')) + struct.pack(fmt, float('nan')) + assert read_func(data2, 0) == float('inf') + assert math.isnan(read_func(data2, 8)) + + # Test error cases + # Index out of range + with assertRaises(IndexError, "index 30 out of range for bytes of length 24"): + read_func(data, 30 + int()) + with assertRaises(IndexError, "index 17 out of range for bytes of length 24"): + read_func(data, 17 + int()) # Not enough bytes for f64 + + # Negative index + with assertRaises(ValueError, "index must be non-negative"): + read_func(data, -1 + int()) + + # Wrong type for bytes argument + with assertRaises(TypeError): + read_func("not bytes", 0 + int()) # type: ignore + with assertRaises(TypeError): + read_func(bytearray(b"\x00" * 8), 0 + int()) # type: ignore def test_write_f64_via_any() -> None: import math From f5fc9380df479beefb272ef68fcb200c5f961b6f Mon Sep 17 00:00:00 2001 From: Jukka Lehtosalo Date: Fri, 6 Feb 2026 14:44:06 +0000 Subject: [PATCH 05/12] write_f32_le and write_f32_be --- mypy/typeshed/stubs/librt/librt/strings.pyi | 2 + mypyc/lib-rt/byteswriter_extra_ops.h | 18 ++++ mypyc/lib-rt/strings/librt_strings.c | 34 +++++++ mypyc/lib-rt/strings/librt_strings_common.h | 32 ++++++ mypyc/primitives/librt_strings_ops.py | 22 +++++ mypyc/test-data/run-librt-strings.test | 103 +++++++++++++++++++- 6 files changed, 210 insertions(+), 1 deletion(-) diff --git a/mypy/typeshed/stubs/librt/librt/strings.pyi b/mypy/typeshed/stubs/librt/librt/strings.pyi index 3322d790870c..580c3d96db60 100644 --- a/mypy/typeshed/stubs/librt/librt/strings.pyi +++ b/mypy/typeshed/stubs/librt/librt/strings.pyi @@ -36,3 +36,5 @@ def write_f64_le(b: BytesWriter, n: float, /) -> None: ... def read_f64_le(b: bytes, index: i64, /) -> float: ... def write_f64_be(b: BytesWriter, n: float, /) -> None: ... def read_f64_be(b: bytes, index: i64, /) -> float: ... +def write_f32_le(b: BytesWriter, n: float, /) -> None: ... +def write_f32_be(b: BytesWriter, n: float, /) -> None: ... diff --git a/mypyc/lib-rt/byteswriter_extra_ops.h b/mypyc/lib-rt/byteswriter_extra_ops.h index fa9684598f5b..779a397d79e8 100644 --- a/mypyc/lib-rt/byteswriter_extra_ops.h +++ b/mypyc/lib-rt/byteswriter_extra_ops.h @@ -121,6 +121,24 @@ CPyBytesWriter_WriteI64BE(PyObject *obj, int64_t value) { // BytesWriter: Write float operations +static inline char +CPyBytesWriter_WriteF32LE(PyObject *obj, double value) { + BytesWriterObject *self = (BytesWriterObject *)obj; + if (!CPyBytesWriter_EnsureSize(self, 4)) + return CPY_NONE_ERROR; + BytesWriter_WriteF32LEUnsafe(self, (float)value); + return CPY_NONE; +} + +static inline char +CPyBytesWriter_WriteF32BE(PyObject *obj, double value) { + BytesWriterObject *self = (BytesWriterObject *)obj; + if (!CPyBytesWriter_EnsureSize(self, 4)) + return CPY_NONE_ERROR; + BytesWriter_WriteF32BEUnsafe(self, (float)value); + return CPY_NONE; +} + static inline char CPyBytesWriter_WriteF64LE(PyObject *obj, double value) { BytesWriterObject *self = (BytesWriterObject *)obj; diff --git a/mypyc/lib-rt/strings/librt_strings.c b/mypyc/lib-rt/strings/librt_strings.c index 8aeb9aaedfd8..1de79e7dfd83 100644 --- a/mypyc/lib-rt/strings/librt_strings.c +++ b/mypyc/lib-rt/strings/librt_strings.c @@ -1009,6 +1009,34 @@ read_i64_le(PyObject *module, PyObject *const *args, size_t nargs) { return PyLong_FromLongLong(CPyBytes_ReadI64LEUnsafe(data + index)); } +static PyObject* +write_f32_le(PyObject *module, PyObject *const *args, size_t nargs) { + BytesWriterObject *bw = parse_write_int_args(args, nargs, "write_f32_le"); + if (bw == NULL) + return NULL; + double unboxed = PyFloat_AsDouble(args[1]); + if (unlikely(unboxed == -1.0 && PyErr_Occurred())) + return NULL; + if (unlikely(!ensure_bytes_writer_size(bw, 4))) + return NULL; + BytesWriter_WriteF32LEUnsafe(bw, (float)unboxed); + Py_RETURN_NONE; +} + +static PyObject* +write_f32_be(PyObject *module, PyObject *const *args, size_t nargs) { + BytesWriterObject *bw = parse_write_int_args(args, nargs, "write_f32_be"); + if (bw == NULL) + return NULL; + double unboxed = PyFloat_AsDouble(args[1]); + if (unlikely(unboxed == -1.0 && PyErr_Occurred())) + return NULL; + if (unlikely(!ensure_bytes_writer_size(bw, 4))) + return NULL; + BytesWriter_WriteF32BEUnsafe(bw, (float)unboxed); + Py_RETURN_NONE; +} + static PyObject* write_f64_le(PyObject *module, PyObject *const *args, size_t nargs) { BytesWriterObject *bw = parse_write_int_args(args, nargs, "write_f64_le"); @@ -1116,6 +1144,12 @@ static PyMethodDef librt_strings_module_methods[] = { {"read_f64_be", (PyCFunction) read_f64_be, METH_FASTCALL, PyDoc_STR("Read a 64-bit float from bytes in big-endian format") }, + {"write_f32_le", (PyCFunction) write_f32_le, METH_FASTCALL, + PyDoc_STR("Write a 32-bit float to BytesWriter in little-endian format") + }, + {"write_f32_be", (PyCFunction) write_f32_be, METH_FASTCALL, + PyDoc_STR("Write a 32-bit float to BytesWriter in big-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 f0f5768ecbd2..eca3259acfa0 100644 --- a/mypyc/lib-rt/strings/librt_strings_common.h +++ b/mypyc/lib-rt/strings/librt_strings_common.h @@ -217,6 +217,38 @@ CPyBytes_ReadI64BEUnsafe(const unsigned char *data) { return (int64_t)value; } +// Write a 32-bit float in little-endian format to BytesWriter. +// NOTE: This does NOT check buffer capacity - caller must ensure space is available. +static inline void +BytesWriter_WriteF32LEUnsafe(BytesWriterObject *self, float value) { + // memcpy is reliably optimized to a single store by GCC, Clang, and MSVC +#if PY_BIG_ENDIAN + uint32_t bits; + memcpy(&bits, &value, 4); + bits = BSWAP32(bits); + memcpy(self->buf + self->len, &bits, 4); +#else + memcpy(self->buf + self->len, &value, 4); +#endif + self->len += 4; +} + +// Write a 32-bit float in big-endian format to BytesWriter. +// NOTE: This does NOT check buffer capacity - caller must ensure space is available. +static inline void +BytesWriter_WriteF32BEUnsafe(BytesWriterObject *self, float value) { + // memcpy is reliably optimized to a single store by GCC, Clang, and MSVC +#if PY_BIG_ENDIAN + memcpy(self->buf + self->len, &value, 4); +#else + uint32_t bits; + memcpy(&bits, &value, 4); + bits = BSWAP32(bits); + memcpy(self->buf + self->len, &bits, 4); +#endif + self->len += 4; +} + // Write a 64-bit float (double) in little-endian format to BytesWriter. // NOTE: This does NOT check buffer capacity - caller must ensure space is available. static inline void diff --git a/mypyc/primitives/librt_strings_ops.py b/mypyc/primitives/librt_strings_ops.py index daf69d3fee95..740d46c8b667 100644 --- a/mypyc/primitives/librt_strings_ops.py +++ b/mypyc/primitives/librt_strings_ops.py @@ -300,6 +300,28 @@ dependencies=[LIBRT_STRINGS, BYTES_WRITER_EXTRA_OPS], ) +# f32 write/read functions + +function_op( + name="librt.strings.write_f32_le", + arg_types=[bytes_writer_rprimitive, float_rprimitive], + return_type=none_rprimitive, + c_function_name="CPyBytesWriter_WriteF32LE", + error_kind=ERR_MAGIC, + experimental=True, + dependencies=[LIBRT_STRINGS, BYTES_WRITER_EXTRA_OPS], +) + +function_op( + name="librt.strings.write_f32_be", + arg_types=[bytes_writer_rprimitive, float_rprimitive], + return_type=none_rprimitive, + c_function_name="CPyBytesWriter_WriteF32BE", + error_kind=ERR_MAGIC, + experimental=True, + dependencies=[LIBRT_STRINGS, BYTES_WRITER_EXTRA_OPS], +) + # StringWriter operations function_op( diff --git a/mypyc/test-data/run-librt-strings.test b/mypyc/test-data/run-librt-strings.test index 8f6c7d033a10..44e4fce430a7 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, write_i16_be, read_i16_le, read_i16_be, write_i32_le, write_i32_be, read_i32_le, read_i32_be, write_i64_le, write_i64_be, read_i64_le, read_i64_be, write_f64_le, read_f64_le, write_f64_be, read_f64_be +from librt.strings import BytesWriter, StringWriter, write_i16_le, write_i16_be, read_i16_le, read_i16_be, write_i32_le, write_i32_be, read_i32_le, read_i32_be, write_i64_le, write_i64_be, read_i64_le, read_i64_be, write_f64_le, read_f64_le, write_f64_be, read_f64_be, write_f32_le, write_f32_be from testutil import assertRaises @@ -56,6 +56,21 @@ F64_TEST_VALUES: list[float] = [ float('nan'), ] +# Test values for f32 write/read operations (values that fit in 32-bit float) +F32_TEST_VALUES: list[float] = [ + 0.0, -0.0, 1.0, -1.0, + -113.0, # mypyc overlapping error value + 0.5, -0.5, 0.1, -0.1, + 1.5, 255.0, 256.0, + 1e10, -1e10, + 1e-10, + 3.4028235e+38, # max float32 + -3.4028235e+38, # min float32 + 1.1754944e-38, # min positive normal float32 + float('inf'), float('-inf'), + float('nan'), +] + def test_bytes_writer_basics() -> None: w = BytesWriter() assert w.getvalue() == b"" @@ -754,6 +769,92 @@ def test_write_f64_via_any() -> None: with assertRaises(TypeError): write_func(w4, "not a float" + str()) # type: ignore +def test_bytes_writer_write_f32_le() -> None: + import struct as s + # Test various f32 values + w = BytesWriter() + for v in F32_TEST_VALUES: + write_f32_le(w, v) + result = w.getvalue() + expected = b"".join(s.pack(" None: + import struct as s + # Test various f32 values + w = BytesWriter() + for v in F32_TEST_VALUES: + write_f32_be(w, v) + result = w.getvalue() + expected = b"".join(s.pack(">f", v) for v in F32_TEST_VALUES) + # Compare byte-by-byte (NaN != NaN but bytes should match) + assert result == expected + + # Test mixing with other operations and buffer growth + w = BytesWriter() + w.append(0xFF) + for i in range(200): + write_f32_be(w, float(i) * 1.5) + w.append(0xEE) + result = w.getvalue() + assert len(result) == 802 + assert result[0] == 0xFF + assert result[-1] == 0xEE + assert result[1:5] == s.pack(">f", 0.0) + assert result[5:9] == s.pack(">f", 1.5) + assert result[797:801] == s.pack(">f", 199.0 * 1.5) + +def test_write_f32_via_any() -> None: + import math + # Test write_f32_le/be via Any to ensure C extension wrapper works + for write_func, fmt in zip((write_f32_le, write_f32_be), ("f")): + w: Any = BytesWriter() + + w.append(0x42) + write_func(w, 1.5) + w.append(0xFF) + assert w.getvalue() == b"\x42" + struct.pack(fmt, 1.5) + b"\xFF" + + # Test buffer growth + w2: Any = BytesWriter() + for i in range(100): + write_func(w2, float(i) * 0.25) + result = w2.getvalue() + assert len(result) == 400 + assert result[0:4] == struct.pack(fmt, 0.0) + assert result[4:8] == struct.pack(fmt, 0.25) + assert result[396:400] == struct.pack(fmt, 99.0 * 0.25) + + # Test special values + w3: Any = BytesWriter() + write_func(w3, float('inf')) + write_func(w3, float('-inf')) + write_func(w3, float('nan')) + result = w3.getvalue() + assert result[0:4] == struct.pack(fmt, float('inf')) + assert result[4:8] == struct.pack(fmt, float('-inf')) + assert math.isnan(struct.unpack(fmt, result[8:12])[0]) + + # Test wrong type + w4: Any = BytesWriter() + with assertRaises(TypeError): + write_func(w4, "not a float" + str()) # type: ignore + def test_write_bytearray() -> None: w = BytesWriter() w.write(bytearray(b"foobar")) From 8bd2c17089af6b5251df8bc8c05a14bd9cfe8d8d Mon Sep 17 00:00:00 2001 From: Jukka Lehtosalo Date: Fri, 6 Feb 2026 15:57:07 +0000 Subject: [PATCH 06/12] read_f32_le and read_f32_be --- mypy/typeshed/stubs/librt/librt/strings.pyi | 2 + mypyc/lib-rt/byteswriter_extra_ops.h | 24 ++++++ mypyc/lib-rt/strings/librt_strings.c | 24 ++++++ mypyc/lib-rt/strings/librt_strings_common.h | 34 ++++++++ mypyc/primitives/librt_strings_ops.py | 20 +++++ mypyc/test-data/run-librt-strings.test | 95 ++++++++++++++++++++- 6 files changed, 198 insertions(+), 1 deletion(-) diff --git a/mypy/typeshed/stubs/librt/librt/strings.pyi b/mypy/typeshed/stubs/librt/librt/strings.pyi index 580c3d96db60..a6225845db23 100644 --- a/mypy/typeshed/stubs/librt/librt/strings.pyi +++ b/mypy/typeshed/stubs/librt/librt/strings.pyi @@ -38,3 +38,5 @@ def write_f64_be(b: BytesWriter, n: float, /) -> None: ... def read_f64_be(b: bytes, index: i64, /) -> float: ... def write_f32_le(b: BytesWriter, n: float, /) -> None: ... def write_f32_be(b: BytesWriter, n: float, /) -> None: ... +def read_f32_le(b: bytes, index: i64, /) -> float: ... +def read_f32_be(b: bytes, index: i64, /) -> float: ... diff --git a/mypyc/lib-rt/byteswriter_extra_ops.h b/mypyc/lib-rt/byteswriter_extra_ops.h index 779a397d79e8..43f33d986226 100644 --- a/mypyc/lib-rt/byteswriter_extra_ops.h +++ b/mypyc/lib-rt/byteswriter_extra_ops.h @@ -236,6 +236,30 @@ CPyBytes_ReadI64BE(PyObject *bytes_obj, int64_t index) { // Bytes: Read float operations +static inline double +CPyBytes_ReadF32LE(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_FLOAT_ERROR; + } + const unsigned char *data = (const unsigned char *)PyBytes_AS_STRING(bytes_obj); + return (double)CPyBytes_ReadF32LEUnsafe(data + index); +} + +static inline double +CPyBytes_ReadF32BE(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_FLOAT_ERROR; + } + const unsigned char *data = (const unsigned char *)PyBytes_AS_STRING(bytes_obj); + return (double)CPyBytes_ReadF32BEUnsafe(data + index); +} + static inline double CPyBytes_ReadF64LE(PyObject *bytes_obj, int64_t index) { // bytes_obj type is enforced by mypyc diff --git a/mypyc/lib-rt/strings/librt_strings.c b/mypyc/lib-rt/strings/librt_strings.c index 1de79e7dfd83..94b4f29f3cf9 100644 --- a/mypyc/lib-rt/strings/librt_strings.c +++ b/mypyc/lib-rt/strings/librt_strings.c @@ -1092,6 +1092,24 @@ read_f64_be(PyObject *module, PyObject *const *args, size_t nargs) { return PyFloat_FromDouble(CPyBytes_ReadF64BEUnsafe(data + index)); } +static PyObject* +read_f32_le(PyObject *module, PyObject *const *args, size_t nargs) { + int64_t index; + const unsigned char *data = parse_read_int_args(args, nargs, "read_f32_le", 4, &index); + if (data == NULL) + return NULL; + return PyFloat_FromDouble((double)CPyBytes_ReadF32LEUnsafe(data + index)); +} + +static PyObject* +read_f32_be(PyObject *module, PyObject *const *args, size_t nargs) { + int64_t index; + const unsigned char *data = parse_read_int_args(args, nargs, "read_f32_be", 4, &index); + if (data == NULL) + return NULL; + return PyFloat_FromDouble((double)CPyBytes_ReadF32BEUnsafe(data + index)); +} + #endif static PyMethodDef librt_strings_module_methods[] = { @@ -1150,6 +1168,12 @@ static PyMethodDef librt_strings_module_methods[] = { {"write_f32_be", (PyCFunction) write_f32_be, METH_FASTCALL, PyDoc_STR("Write a 32-bit float to BytesWriter in big-endian format") }, + {"read_f32_le", (PyCFunction) read_f32_le, METH_FASTCALL, + PyDoc_STR("Read a 32-bit float from bytes in little-endian format") + }, + {"read_f32_be", (PyCFunction) read_f32_be, METH_FASTCALL, + PyDoc_STR("Read a 32-bit float from bytes in big-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 eca3259acfa0..5bec3475784e 100644 --- a/mypyc/lib-rt/strings/librt_strings_common.h +++ b/mypyc/lib-rt/strings/librt_strings_common.h @@ -249,6 +249,40 @@ BytesWriter_WriteF32BEUnsafe(BytesWriterObject *self, float value) { self->len += 4; } +// Read a 32-bit float in little-endian format from bytes. +// NOTE: This does NOT check bounds - caller must ensure valid index. +static inline float +CPyBytes_ReadF32LEUnsafe(const unsigned char *data) { + // memcpy is reliably optimized to a single load by GCC, Clang, and MSVC + float value; +#if PY_BIG_ENDIAN + uint32_t bits; + memcpy(&bits, data, 4); + bits = BSWAP32(bits); + memcpy(&value, &bits, 4); +#else + memcpy(&value, data, 4); +#endif + return value; +} + +// Read a 32-bit float in big-endian format from bytes. +// NOTE: This does NOT check bounds - caller must ensure valid index. +static inline float +CPyBytes_ReadF32BEUnsafe(const unsigned char *data) { + // memcpy is reliably optimized to a single load by GCC, Clang, and MSVC + float value; +#if PY_BIG_ENDIAN + memcpy(&value, data, 4); +#else + uint32_t bits; + memcpy(&bits, data, 4); + bits = BSWAP32(bits); + memcpy(&value, &bits, 4); +#endif + return value; +} + // Write a 64-bit float (double) in little-endian format to BytesWriter. // NOTE: This does NOT check buffer capacity - caller must ensure space is available. static inline void diff --git a/mypyc/primitives/librt_strings_ops.py b/mypyc/primitives/librt_strings_ops.py index 740d46c8b667..2ef5a53de3ce 100644 --- a/mypyc/primitives/librt_strings_ops.py +++ b/mypyc/primitives/librt_strings_ops.py @@ -322,6 +322,26 @@ dependencies=[LIBRT_STRINGS, BYTES_WRITER_EXTRA_OPS], ) +function_op( + name="librt.strings.read_f32_le", + arg_types=[bytes_rprimitive, int64_rprimitive], + return_type=float_rprimitive, + c_function_name="CPyBytes_ReadF32LE", + error_kind=ERR_MAGIC_OVERLAPPING, + experimental=True, + dependencies=[LIBRT_STRINGS, BYTES_WRITER_EXTRA_OPS], +) + +function_op( + name="librt.strings.read_f32_be", + arg_types=[bytes_rprimitive, int64_rprimitive], + return_type=float_rprimitive, + c_function_name="CPyBytes_ReadF32BE", + error_kind=ERR_MAGIC_OVERLAPPING, + experimental=True, + dependencies=[LIBRT_STRINGS, BYTES_WRITER_EXTRA_OPS], +) + # StringWriter operations function_op( diff --git a/mypyc/test-data/run-librt-strings.test b/mypyc/test-data/run-librt-strings.test index 44e4fce430a7..003eccb3ec43 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, write_i16_be, read_i16_le, read_i16_be, write_i32_le, write_i32_be, read_i32_le, read_i32_be, write_i64_le, write_i64_be, read_i64_le, read_i64_be, write_f64_le, read_f64_le, write_f64_be, read_f64_be, write_f32_le, write_f32_be +from librt.strings import BytesWriter, StringWriter, write_i16_le, write_i16_be, read_i16_le, read_i16_be, write_i32_le, write_i32_be, read_i32_le, read_i32_be, write_i64_le, write_i64_be, read_i64_le, read_i64_be, write_f64_le, read_f64_le, write_f64_be, read_f64_be, write_f32_le, write_f32_be, read_f32_le, read_f32_be from testutil import assertRaises @@ -855,6 +855,99 @@ def test_write_f32_via_any() -> None: with assertRaises(TypeError): write_func(w4, "not a float" + str()) # type: ignore +def test_bytes_reader_read_f32_le() -> None: + import math + import struct as s + # Test various f32 values (compare against f32-rounded expected value, + # since some f64 values like 0.1 lose precision when stored as f32) + data = b"".join(s.pack(" None: + import math + import struct as s + # Test various f32 values (compare against f32-rounded expected value, + # since some f64 values like 0.1 lose precision when stored as f32) + data = b"".join(s.pack(">f", v) for v in F32_TEST_VALUES) + for i, v in enumerate(F32_TEST_VALUES): + result = read_f32_be(data, i * 4) + expected = s.unpack(">f", s.pack(">f", v))[0] + if math.isnan(v): + assert math.isnan(result) + else: + assert result == expected + + # Test round-trip with write_f32_be + w = BytesWriter() + for v in F32_TEST_VALUES: + write_f32_be(w, v) + result_bytes = w.getvalue() + for i, v in enumerate(F32_TEST_VALUES): + result = read_f32_be(result_bytes, i * 4) + expected = s.unpack(">f", s.pack(">f", v))[0] + if math.isnan(v): + assert math.isnan(result) + else: + assert result == expected + + # Test unaligned offset + data2 = b"\xFF" + s.pack(">f", 1.5) + b"\xFF" + assert read_f32_be(data2, 1) == 1.5 + +def test_read_f32_via_any() -> None: + import math + # Test read_f32_le/be via Any to ensure C extension wrapper works + for read_func, fmt in zip((read_f32_le, read_f32_be), ("f")): + data: Any = struct.pack(fmt, 1.5) + struct.pack(fmt, -113.0) + struct.pack(fmt, 0.0) + assert read_func(data, 0) == 1.5 + assert read_func(data, 4) == -113.0 + assert read_func(data, 8) == 0.0 + + # Test special values + data2: Any = struct.pack(fmt, float('inf')) + struct.pack(fmt, float('nan')) + assert read_func(data2, 0) == float('inf') + assert math.isnan(read_func(data2, 4)) + + # Test error cases + # Index out of range + with assertRaises(IndexError, "index 20 out of range for bytes of length 12"): + read_func(data, 20 + int()) + with assertRaises(IndexError, "index 9 out of range for bytes of length 12"): + read_func(data, 9 + int()) # Not enough bytes for f32 + + # Negative index + with assertRaises(ValueError, "index must be non-negative"): + read_func(data, -1 + int()) + + # Wrong type for bytes argument + with assertRaises(TypeError): + read_func("not bytes", 0 + int()) # type: ignore + with assertRaises(TypeError): + read_func(bytearray(b"\x00" * 4), 0 + int()) # type: ignore + def test_write_bytearray() -> None: w = BytesWriter() w.write(bytearray(b"foobar")) From 0885d1810d8cdd1aadc309f8c292d0c2c410d5fb Mon Sep 17 00:00:00 2001 From: Jukka Lehtosalo Date: Mon, 9 Feb 2026 17:01:43 +0000 Subject: [PATCH 07/12] Update tests --- mypyc/test-data/run-librt-strings.test | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/mypyc/test-data/run-librt-strings.test b/mypyc/test-data/run-librt-strings.test index 003eccb3ec43..0f36c0547cab 100644 --- a/mypyc/test-data/run-librt-strings.test +++ b/mypyc/test-data/run-librt-strings.test @@ -675,6 +675,10 @@ def test_bytes_reader_read_f64_le() -> None: data2 = b"\xFF" + struct.pack(" None: import math # Test various f64 values @@ -702,6 +706,10 @@ def test_bytes_reader_read_f64_be() -> None: data2 = b"\xFF" + struct.pack(">d", 1.5) + b"\xFF" assert read_f64_be(data2, 1) == 1.5 + # Test barely out of bounds (only 7 bytes available, need 8) + with assertRaises(IndexError): + read_f64_be(data, len(data) - 7) + def test_read_f64_via_any() -> None: import math # Test read_f64_le/be via Any to ensure C extension wrapper works @@ -886,6 +894,10 @@ def test_bytes_reader_read_f32_le() -> None: data2 = b"\xFF" + s.pack(" None: import math import struct as s @@ -917,6 +929,10 @@ def test_bytes_reader_read_f32_be() -> None: data2 = b"\xFF" + s.pack(">f", 1.5) + b"\xFF" assert read_f32_be(data2, 1) == 1.5 + # Test barely out of bounds (only 3 bytes available, need 4) + with assertRaises(IndexError): + read_f32_be(data, len(data) - 3) + def test_read_f32_via_any() -> None: import math # Test read_f32_le/be via Any to ensure C extension wrapper works From 7dc4933bab81d7ecd3d2d2777abe8c93cdc8c421 Mon Sep 17 00:00:00 2001 From: Jukka Lehtosalo Date: Tue, 10 Feb 2026 11:22:43 +0000 Subject: [PATCH 08/12] Make primitive order more consistent --- mypyc/primitives/librt_strings_ops.py | 36 +++++++++++++-------------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/mypyc/primitives/librt_strings_ops.py b/mypyc/primitives/librt_strings_ops.py index 2ef5a53de3ce..ebd967bdd880 100644 --- a/mypyc/primitives/librt_strings_ops.py +++ b/mypyc/primitives/librt_strings_ops.py @@ -258,85 +258,85 @@ dependencies=[LIBRT_STRINGS, BYTES_WRITER_EXTRA_OPS], ) -# f64 write/read functions +# f32 write/read functions function_op( - name="librt.strings.write_f64_le", + name="librt.strings.write_f32_le", arg_types=[bytes_writer_rprimitive, float_rprimitive], return_type=none_rprimitive, - c_function_name="CPyBytesWriter_WriteF64LE", + c_function_name="CPyBytesWriter_WriteF32LE", error_kind=ERR_MAGIC, experimental=True, dependencies=[LIBRT_STRINGS, BYTES_WRITER_EXTRA_OPS], ) function_op( - name="librt.strings.write_f64_be", + name="librt.strings.write_f32_be", arg_types=[bytes_writer_rprimitive, float_rprimitive], return_type=none_rprimitive, - c_function_name="CPyBytesWriter_WriteF64BE", + c_function_name="CPyBytesWriter_WriteF32BE", error_kind=ERR_MAGIC, experimental=True, dependencies=[LIBRT_STRINGS, BYTES_WRITER_EXTRA_OPS], ) function_op( - name="librt.strings.read_f64_le", + name="librt.strings.read_f32_le", arg_types=[bytes_rprimitive, int64_rprimitive], return_type=float_rprimitive, - c_function_name="CPyBytes_ReadF64LE", + c_function_name="CPyBytes_ReadF32LE", error_kind=ERR_MAGIC_OVERLAPPING, experimental=True, dependencies=[LIBRT_STRINGS, BYTES_WRITER_EXTRA_OPS], ) function_op( - name="librt.strings.read_f64_be", + name="librt.strings.read_f32_be", arg_types=[bytes_rprimitive, int64_rprimitive], return_type=float_rprimitive, - c_function_name="CPyBytes_ReadF64BE", + c_function_name="CPyBytes_ReadF32BE", error_kind=ERR_MAGIC_OVERLAPPING, experimental=True, dependencies=[LIBRT_STRINGS, BYTES_WRITER_EXTRA_OPS], ) -# f32 write/read functions +# f64 write/read functions function_op( - name="librt.strings.write_f32_le", + name="librt.strings.write_f64_le", arg_types=[bytes_writer_rprimitive, float_rprimitive], return_type=none_rprimitive, - c_function_name="CPyBytesWriter_WriteF32LE", + c_function_name="CPyBytesWriter_WriteF64LE", error_kind=ERR_MAGIC, experimental=True, dependencies=[LIBRT_STRINGS, BYTES_WRITER_EXTRA_OPS], ) function_op( - name="librt.strings.write_f32_be", + name="librt.strings.write_f64_be", arg_types=[bytes_writer_rprimitive, float_rprimitive], return_type=none_rprimitive, - c_function_name="CPyBytesWriter_WriteF32BE", + c_function_name="CPyBytesWriter_WriteF64BE", error_kind=ERR_MAGIC, experimental=True, dependencies=[LIBRT_STRINGS, BYTES_WRITER_EXTRA_OPS], ) function_op( - name="librt.strings.read_f32_le", + name="librt.strings.read_f64_le", arg_types=[bytes_rprimitive, int64_rprimitive], return_type=float_rprimitive, - c_function_name="CPyBytes_ReadF32LE", + c_function_name="CPyBytes_ReadF64LE", error_kind=ERR_MAGIC_OVERLAPPING, experimental=True, dependencies=[LIBRT_STRINGS, BYTES_WRITER_EXTRA_OPS], ) function_op( - name="librt.strings.read_f32_be", + name="librt.strings.read_f64_be", arg_types=[bytes_rprimitive, int64_rprimitive], return_type=float_rprimitive, - c_function_name="CPyBytes_ReadF32BE", + c_function_name="CPyBytes_ReadF64BE", error_kind=ERR_MAGIC_OVERLAPPING, experimental=True, dependencies=[LIBRT_STRINGS, BYTES_WRITER_EXTRA_OPS], From 3db8fb9b3419a85ca1e4950a12da3f3bd2b89014 Mon Sep 17 00:00:00 2001 From: Jukka Lehtosalo Date: Tue, 10 Feb 2026 11:29:33 +0000 Subject: [PATCH 09/12] Make ordering more consistent --- mypyc/lib-rt/strings/librt_strings.c | 78 ++++++++++----------- mypyc/lib-rt/strings/librt_strings_common.h | 32 ++++----- 2 files changed, 55 insertions(+), 55 deletions(-) diff --git a/mypyc/lib-rt/strings/librt_strings.c b/mypyc/lib-rt/strings/librt_strings.c index 94b4f29f3cf9..9944624b566a 100644 --- a/mypyc/lib-rt/strings/librt_strings.c +++ b/mypyc/lib-rt/strings/librt_strings.c @@ -1009,6 +1009,15 @@ read_i64_le(PyObject *module, PyObject *const *args, size_t nargs) { return PyLong_FromLongLong(CPyBytes_ReadI64LEUnsafe(data + index)); } +static PyObject* +read_i64_be(PyObject *module, PyObject *const *args, size_t nargs) { + int64_t index; + const unsigned char *data = parse_read_int_args(args, nargs, "read_i64_be", 8, &index); + if (data == NULL) + return NULL; + return PyLong_FromLongLong(CPyBytes_ReadI64BEUnsafe(data + index)); +} + static PyObject* write_f32_le(PyObject *module, PyObject *const *args, size_t nargs) { BytesWriterObject *bw = parse_write_int_args(args, nargs, "write_f32_le"); @@ -1037,6 +1046,24 @@ write_f32_be(PyObject *module, PyObject *const *args, size_t nargs) { Py_RETURN_NONE; } +static PyObject* +read_f32_le(PyObject *module, PyObject *const *args, size_t nargs) { + int64_t index; + const unsigned char *data = parse_read_int_args(args, nargs, "read_f32_le", 4, &index); + if (data == NULL) + return NULL; + return PyFloat_FromDouble((double)CPyBytes_ReadF32LEUnsafe(data + index)); +} + +static PyObject* +read_f32_be(PyObject *module, PyObject *const *args, size_t nargs) { + int64_t index; + const unsigned char *data = parse_read_int_args(args, nargs, "read_f32_be", 4, &index); + if (data == NULL) + return NULL; + return PyFloat_FromDouble((double)CPyBytes_ReadF32BEUnsafe(data + index)); +} + static PyObject* write_f64_le(PyObject *module, PyObject *const *args, size_t nargs) { BytesWriterObject *bw = parse_write_int_args(args, nargs, "write_f64_le"); @@ -1065,15 +1092,6 @@ write_f64_be(PyObject *module, PyObject *const *args, size_t nargs) { Py_RETURN_NONE; } -static PyObject* -read_i64_be(PyObject *module, PyObject *const *args, size_t nargs) { - int64_t index; - const unsigned char *data = parse_read_int_args(args, nargs, "read_i64_be", 8, &index); - if (data == NULL) - return NULL; - return PyLong_FromLongLong(CPyBytes_ReadI64BEUnsafe(data + index)); -} - static PyObject* read_f64_le(PyObject *module, PyObject *const *args, size_t nargs) { int64_t index; @@ -1092,24 +1110,6 @@ read_f64_be(PyObject *module, PyObject *const *args, size_t nargs) { return PyFloat_FromDouble(CPyBytes_ReadF64BEUnsafe(data + index)); } -static PyObject* -read_f32_le(PyObject *module, PyObject *const *args, size_t nargs) { - int64_t index; - const unsigned char *data = parse_read_int_args(args, nargs, "read_f32_le", 4, &index); - if (data == NULL) - return NULL; - return PyFloat_FromDouble((double)CPyBytes_ReadF32LEUnsafe(data + index)); -} - -static PyObject* -read_f32_be(PyObject *module, PyObject *const *args, size_t nargs) { - int64_t index; - const unsigned char *data = parse_read_int_args(args, nargs, "read_f32_be", 4, &index); - if (data == NULL) - return NULL; - return PyFloat_FromDouble((double)CPyBytes_ReadF32BEUnsafe(data + index)); -} - #endif static PyMethodDef librt_strings_module_methods[] = { @@ -1150,18 +1150,6 @@ static PyMethodDef librt_strings_module_methods[] = { {"read_i64_be", (PyCFunction) read_i64_be, METH_FASTCALL, PyDoc_STR("Read a 64-bit signed integer from bytes in big-endian format") }, - {"write_f64_le", (PyCFunction) write_f64_le, METH_FASTCALL, - PyDoc_STR("Write a 64-bit float to BytesWriter in little-endian format") - }, - {"write_f64_be", (PyCFunction) write_f64_be, METH_FASTCALL, - PyDoc_STR("Write a 64-bit float to BytesWriter in big-endian format") - }, - {"read_f64_le", (PyCFunction) read_f64_le, METH_FASTCALL, - PyDoc_STR("Read a 64-bit float from bytes in little-endian format") - }, - {"read_f64_be", (PyCFunction) read_f64_be, METH_FASTCALL, - PyDoc_STR("Read a 64-bit float from bytes in big-endian format") - }, {"write_f32_le", (PyCFunction) write_f32_le, METH_FASTCALL, PyDoc_STR("Write a 32-bit float to BytesWriter in little-endian format") }, @@ -1174,6 +1162,18 @@ static PyMethodDef librt_strings_module_methods[] = { {"read_f32_be", (PyCFunction) read_f32_be, METH_FASTCALL, PyDoc_STR("Read a 32-bit float from bytes in big-endian format") }, + {"write_f64_le", (PyCFunction) write_f64_le, METH_FASTCALL, + PyDoc_STR("Write a 64-bit float to BytesWriter in little-endian format") + }, + {"write_f64_be", (PyCFunction) write_f64_be, METH_FASTCALL, + PyDoc_STR("Write a 64-bit float to BytesWriter in big-endian format") + }, + {"read_f64_le", (PyCFunction) read_f64_le, METH_FASTCALL, + PyDoc_STR("Read a 64-bit float from bytes in little-endian format") + }, + {"read_f64_be", (PyCFunction) read_f64_be, METH_FASTCALL, + PyDoc_STR("Read a 64-bit float from bytes in big-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 5bec3475784e..66045ffdedad 100644 --- a/mypyc/lib-rt/strings/librt_strings_common.h +++ b/mypyc/lib-rt/strings/librt_strings_common.h @@ -299,6 +299,22 @@ BytesWriter_WriteF64LEUnsafe(BytesWriterObject *self, double value) { self->len += 8; } +// Write a 64-bit float (double) in big-endian format to BytesWriter. +// NOTE: This does NOT check buffer capacity - caller must ensure space is available. +static inline void +BytesWriter_WriteF64BEUnsafe(BytesWriterObject *self, double value) { + // memcpy is reliably optimized to a single store by GCC, Clang, and MSVC +#if PY_BIG_ENDIAN + memcpy(self->buf + self->len, &value, 8); +#else + uint64_t bits; + memcpy(&bits, &value, 8); + bits = BSWAP64(bits); + memcpy(self->buf + self->len, &bits, 8); +#endif + self->len += 8; +} + // Read a 64-bit float (double) in little-endian format from bytes. // NOTE: This does NOT check bounds - caller must ensure valid index. static inline double @@ -316,22 +332,6 @@ CPyBytes_ReadF64LEUnsafe(const unsigned char *data) { return value; } -// Write a 64-bit float (double) in big-endian format to BytesWriter. -// NOTE: This does NOT check buffer capacity - caller must ensure space is available. -static inline void -BytesWriter_WriteF64BEUnsafe(BytesWriterObject *self, double value) { - // memcpy is reliably optimized to a single store by GCC, Clang, and MSVC -#if PY_BIG_ENDIAN - memcpy(self->buf + self->len, &value, 8); -#else - uint64_t bits; - memcpy(&bits, &value, 8); - bits = BSWAP64(bits); - memcpy(self->buf + self->len, &bits, 8); -#endif - self->len += 8; -} - // Read a 64-bit float (double) in big-endian format from bytes. // NOTE: This does NOT check bounds - caller must ensure valid index. static inline double From 1e711d8cd8909751eec3fecfacb6e70d19ace52e Mon Sep 17 00:00:00 2001 From: Jukka Lehtosalo Date: Tue, 10 Feb 2026 11:30:32 +0000 Subject: [PATCH 10/12] Make ordering more consistent --- mypy/typeshed/stubs/librt/librt/strings.pyi | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/mypy/typeshed/stubs/librt/librt/strings.pyi b/mypy/typeshed/stubs/librt/librt/strings.pyi index a6225845db23..711e52c2e370 100644 --- a/mypy/typeshed/stubs/librt/librt/strings.pyi +++ b/mypy/typeshed/stubs/librt/librt/strings.pyi @@ -32,11 +32,11 @@ def write_i64_le(b: BytesWriter, n: i64, /) -> None: ... def write_i64_be(b: BytesWriter, n: i64, /) -> None: ... def read_i64_le(b: bytes, index: i64, /) -> i64: ... def read_i64_be(b: bytes, index: i64, /) -> i64: ... -def write_f64_le(b: BytesWriter, n: float, /) -> None: ... -def read_f64_le(b: bytes, index: i64, /) -> float: ... -def write_f64_be(b: BytesWriter, n: float, /) -> None: ... -def read_f64_be(b: bytes, index: i64, /) -> float: ... def write_f32_le(b: BytesWriter, n: float, /) -> None: ... def write_f32_be(b: BytesWriter, n: float, /) -> None: ... def read_f32_le(b: bytes, index: i64, /) -> float: ... def read_f32_be(b: bytes, index: i64, /) -> float: ... +def write_f64_le(b: BytesWriter, n: float, /) -> None: ... +def write_f64_be(b: BytesWriter, n: float, /) -> None: ... +def read_f64_le(b: bytes, index: i64, /) -> float: ... +def read_f64_be(b: bytes, index: i64, /) -> float: ... From 933ebebc11938a303a4a24a1e4c6378e45f5d0a0 Mon Sep 17 00:00:00 2001 From: Jukka Lehtosalo Date: Tue, 10 Feb 2026 11:34:58 +0000 Subject: [PATCH 11/12] Refactor --- mypyc/lib-rt/strings/librt_strings.c | 48 ++++++++++++++-------------- 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/mypyc/lib-rt/strings/librt_strings.c b/mypyc/lib-rt/strings/librt_strings.c index 9944624b566a..fa0a43f848eb 100644 --- a/mypyc/lib-rt/strings/librt_strings.c +++ b/mypyc/lib-rt/strings/librt_strings.c @@ -831,9 +831,9 @@ StringWriter_len_internal(PyObject *self) { // End of StringWriter -// Helper for write_i*_le/be functions - validates args and returns BytesWriter +// Helper for write_*_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) { +parse_write_args(PyObject *const *args, size_t nargs, const char *func_name) { if (unlikely(nargs != 2)) { PyErr_Format(PyExc_TypeError, "%s() takes exactly 2 arguments (%zu given)", func_name, nargs); @@ -846,10 +846,10 @@ parse_write_int_args(PyObject *const *args, size_t nargs, const char *func_name) return (BytesWriterObject *)writer; } -// Helper for read_i*_le/be functions - validates args and returns data pointer +// Helper for read_*_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, +parse_read_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, @@ -882,7 +882,7 @@ parse_read_int_args(PyObject *const *args, size_t nargs, const char *func_name, static PyObject* write_i16_le(PyObject *module, PyObject *const *args, size_t nargs) { - BytesWriterObject *bw = parse_write_int_args(args, nargs, "write_i16_le"); + BytesWriterObject *bw = parse_write_args(args, nargs, "write_i16_le"); if (bw == NULL) return NULL; int16_t unboxed = CPyLong_AsInt16(args[1]); @@ -896,7 +896,7 @@ write_i16_le(PyObject *module, PyObject *const *args, size_t nargs) { static PyObject* write_i16_be(PyObject *module, PyObject *const *args, size_t nargs) { - BytesWriterObject *bw = parse_write_int_args(args, nargs, "write_i16_be"); + BytesWriterObject *bw = parse_write_args(args, nargs, "write_i16_be"); if (bw == NULL) return NULL; int16_t unboxed = CPyLong_AsInt16(args[1]); @@ -911,7 +911,7 @@ write_i16_be(PyObject *module, PyObject *const *args, size_t nargs) { 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); + const unsigned char *data = parse_read_args(args, nargs, "read_i16_le", 2, &index); if (data == NULL) return NULL; return PyLong_FromLong(CPyBytes_ReadI16LEUnsafe(data + index)); @@ -920,7 +920,7 @@ read_i16_le(PyObject *module, PyObject *const *args, size_t nargs) { static PyObject* read_i16_be(PyObject *module, PyObject *const *args, size_t nargs) { int64_t index; - const unsigned char *data = parse_read_int_args(args, nargs, "read_i16_be", 2, &index); + const unsigned char *data = parse_read_args(args, nargs, "read_i16_be", 2, &index); if (data == NULL) return NULL; return PyLong_FromLong(CPyBytes_ReadI16BEUnsafe(data + index)); @@ -928,7 +928,7 @@ read_i16_be(PyObject *module, PyObject *const *args, size_t nargs) { static PyObject* write_i32_le(PyObject *module, PyObject *const *args, size_t nargs) { - BytesWriterObject *bw = parse_write_int_args(args, nargs, "write_i32_le"); + BytesWriterObject *bw = parse_write_args(args, nargs, "write_i32_le"); if (bw == NULL) return NULL; int32_t unboxed = CPyLong_AsInt32(args[1]); @@ -942,7 +942,7 @@ write_i32_le(PyObject *module, PyObject *const *args, size_t nargs) { static PyObject* write_i32_be(PyObject *module, PyObject *const *args, size_t nargs) { - BytesWriterObject *bw = parse_write_int_args(args, nargs, "write_i32_be"); + BytesWriterObject *bw = parse_write_args(args, nargs, "write_i32_be"); if (bw == NULL) return NULL; int32_t unboxed = CPyLong_AsInt32(args[1]); @@ -957,7 +957,7 @@ write_i32_be(PyObject *module, PyObject *const *args, size_t nargs) { 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); + const unsigned char *data = parse_read_args(args, nargs, "read_i32_le", 4, &index); if (data == NULL) return NULL; return PyLong_FromLong(CPyBytes_ReadI32LEUnsafe(data + index)); @@ -966,7 +966,7 @@ read_i32_le(PyObject *module, PyObject *const *args, size_t nargs) { static PyObject* read_i32_be(PyObject *module, PyObject *const *args, size_t nargs) { int64_t index; - const unsigned char *data = parse_read_int_args(args, nargs, "read_i32_be", 4, &index); + const unsigned char *data = parse_read_args(args, nargs, "read_i32_be", 4, &index); if (data == NULL) return NULL; return PyLong_FromLong(CPyBytes_ReadI32BEUnsafe(data + index)); @@ -974,7 +974,7 @@ read_i32_be(PyObject *module, PyObject *const *args, size_t nargs) { static PyObject* write_i64_le(PyObject *module, PyObject *const *args, size_t nargs) { - BytesWriterObject *bw = parse_write_int_args(args, nargs, "write_i64_le"); + BytesWriterObject *bw = parse_write_args(args, nargs, "write_i64_le"); if (bw == NULL) return NULL; int64_t unboxed = CPyLong_AsInt64(args[1]); @@ -988,7 +988,7 @@ write_i64_le(PyObject *module, PyObject *const *args, size_t nargs) { static PyObject* write_i64_be(PyObject *module, PyObject *const *args, size_t nargs) { - BytesWriterObject *bw = parse_write_int_args(args, nargs, "write_i64_be"); + BytesWriterObject *bw = parse_write_args(args, nargs, "write_i64_be"); if (bw == NULL) return NULL; int64_t unboxed = CPyLong_AsInt64(args[1]); @@ -1003,7 +1003,7 @@ write_i64_be(PyObject *module, PyObject *const *args, size_t nargs) { 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); + const unsigned char *data = parse_read_args(args, nargs, "read_i64_le", 8, &index); if (data == NULL) return NULL; return PyLong_FromLongLong(CPyBytes_ReadI64LEUnsafe(data + index)); @@ -1012,7 +1012,7 @@ read_i64_le(PyObject *module, PyObject *const *args, size_t nargs) { static PyObject* read_i64_be(PyObject *module, PyObject *const *args, size_t nargs) { int64_t index; - const unsigned char *data = parse_read_int_args(args, nargs, "read_i64_be", 8, &index); + const unsigned char *data = parse_read_args(args, nargs, "read_i64_be", 8, &index); if (data == NULL) return NULL; return PyLong_FromLongLong(CPyBytes_ReadI64BEUnsafe(data + index)); @@ -1020,7 +1020,7 @@ read_i64_be(PyObject *module, PyObject *const *args, size_t nargs) { static PyObject* write_f32_le(PyObject *module, PyObject *const *args, size_t nargs) { - BytesWriterObject *bw = parse_write_int_args(args, nargs, "write_f32_le"); + BytesWriterObject *bw = parse_write_args(args, nargs, "write_f32_le"); if (bw == NULL) return NULL; double unboxed = PyFloat_AsDouble(args[1]); @@ -1034,7 +1034,7 @@ write_f32_le(PyObject *module, PyObject *const *args, size_t nargs) { static PyObject* write_f32_be(PyObject *module, PyObject *const *args, size_t nargs) { - BytesWriterObject *bw = parse_write_int_args(args, nargs, "write_f32_be"); + BytesWriterObject *bw = parse_write_args(args, nargs, "write_f32_be"); if (bw == NULL) return NULL; double unboxed = PyFloat_AsDouble(args[1]); @@ -1049,7 +1049,7 @@ write_f32_be(PyObject *module, PyObject *const *args, size_t nargs) { static PyObject* read_f32_le(PyObject *module, PyObject *const *args, size_t nargs) { int64_t index; - const unsigned char *data = parse_read_int_args(args, nargs, "read_f32_le", 4, &index); + const unsigned char *data = parse_read_args(args, nargs, "read_f32_le", 4, &index); if (data == NULL) return NULL; return PyFloat_FromDouble((double)CPyBytes_ReadF32LEUnsafe(data + index)); @@ -1058,7 +1058,7 @@ read_f32_le(PyObject *module, PyObject *const *args, size_t nargs) { static PyObject* read_f32_be(PyObject *module, PyObject *const *args, size_t nargs) { int64_t index; - const unsigned char *data = parse_read_int_args(args, nargs, "read_f32_be", 4, &index); + const unsigned char *data = parse_read_args(args, nargs, "read_f32_be", 4, &index); if (data == NULL) return NULL; return PyFloat_FromDouble((double)CPyBytes_ReadF32BEUnsafe(data + index)); @@ -1066,7 +1066,7 @@ read_f32_be(PyObject *module, PyObject *const *args, size_t nargs) { static PyObject* write_f64_le(PyObject *module, PyObject *const *args, size_t nargs) { - BytesWriterObject *bw = parse_write_int_args(args, nargs, "write_f64_le"); + BytesWriterObject *bw = parse_write_args(args, nargs, "write_f64_le"); if (bw == NULL) return NULL; double unboxed = PyFloat_AsDouble(args[1]); @@ -1080,7 +1080,7 @@ write_f64_le(PyObject *module, PyObject *const *args, size_t nargs) { static PyObject* write_f64_be(PyObject *module, PyObject *const *args, size_t nargs) { - BytesWriterObject *bw = parse_write_int_args(args, nargs, "write_f64_be"); + BytesWriterObject *bw = parse_write_args(args, nargs, "write_f64_be"); if (bw == NULL) return NULL; double unboxed = PyFloat_AsDouble(args[1]); @@ -1095,7 +1095,7 @@ write_f64_be(PyObject *module, PyObject *const *args, size_t nargs) { static PyObject* read_f64_le(PyObject *module, PyObject *const *args, size_t nargs) { int64_t index; - const unsigned char *data = parse_read_int_args(args, nargs, "read_f64_le", 8, &index); + const unsigned char *data = parse_read_args(args, nargs, "read_f64_le", 8, &index); if (data == NULL) return NULL; return PyFloat_FromDouble(CPyBytes_ReadF64LEUnsafe(data + index)); @@ -1104,7 +1104,7 @@ read_f64_le(PyObject *module, PyObject *const *args, size_t nargs) { static PyObject* read_f64_be(PyObject *module, PyObject *const *args, size_t nargs) { int64_t index; - const unsigned char *data = parse_read_int_args(args, nargs, "read_f64_be", 8, &index); + const unsigned char *data = parse_read_args(args, nargs, "read_f64_be", 8, &index); if (data == NULL) return NULL; return PyFloat_FromDouble(CPyBytes_ReadF64BEUnsafe(data + index)); From d51195a4150b107dbd6cb905867eb7b14519eb88 Mon Sep 17 00:00:00 2001 From: Jukka Lehtosalo Date: Tue, 10 Feb 2026 11:38:18 +0000 Subject: [PATCH 12/12] Avoid type ignore comments --- mypyc/test-data/run-librt-strings.test | 36 +++++++++++++++++--------- 1 file changed, 24 insertions(+), 12 deletions(-) diff --git a/mypyc/test-data/run-librt-strings.test b/mypyc/test-data/run-librt-strings.test index 0f36c0547cab..a0dcc57732d8 100644 --- a/mypyc/test-data/run-librt-strings.test +++ b/mypyc/test-data/run-librt-strings.test @@ -299,9 +299,11 @@ def test_read_i16_via_any() -> None: # Wrong type for bytes argument with assertRaises(TypeError): - read_func("not bytes", 0 + int()) # type: ignore + bad: Any = "not bytes" + read_func(bad, 0 + int()) with assertRaises(TypeError): - read_func(bytearray(b"\x00\x00"), 0 + int()) # type: ignore + bad2: Any = bytearray(b"\x00\x00") + read_func(bad2, 0 + int()) def test_bytes_writer_write_i16_be() -> None: # Test various i16 values from 1-byte to 2-byte range @@ -465,9 +467,11 @@ def test_read_i32_via_any() -> None: # Wrong type for bytes argument with assertRaises(TypeError): - read_func("not bytes", 0 + int()) # type: ignore + bad: Any = "not bytes" + read_func(bad, 0 + int()) with assertRaises(TypeError): - read_func(bytearray(b"\x00\x00\x00\x00"), 0 + int()) # type: ignore + bad2: Any = bytearray(b"\x00\x00\x00\x00") + read_func(bad2, 0 + int()) def test_bytes_writer_write_i64_le() -> None: # Test all i64 values (includes all i32 values plus 5-8 byte values) @@ -596,9 +600,11 @@ def test_read_i64_via_any() -> None: # Wrong type for bytes argument with assertRaises(TypeError): - read_func("not bytes", 0 + int()) # type: ignore + bad: Any = "not bytes" + read_func(bad, 0 + int()) with assertRaises(TypeError): - read_func(bytearray(b"\x00" * 8), 0 + int()) # type: ignore + bad2: Any = bytearray(b"\x00" * 8) + read_func(bad2, 0 + int()) def test_bytes_writer_write_f64_le() -> None: # Test various f64 values @@ -737,9 +743,11 @@ def test_read_f64_via_any() -> None: # Wrong type for bytes argument with assertRaises(TypeError): - read_func("not bytes", 0 + int()) # type: ignore + bad: Any = "not bytes" + read_func(bad, 0 + int()) with assertRaises(TypeError): - read_func(bytearray(b"\x00" * 8), 0 + int()) # type: ignore + bad2: Any = bytearray(b"\x00" * 8) + read_func(bad2, 0 + int()) def test_write_f64_via_any() -> None: import math @@ -775,7 +783,8 @@ def test_write_f64_via_any() -> None: # Test wrong type w4: Any = BytesWriter() with assertRaises(TypeError): - write_func(w4, "not a float" + str()) # type: ignore + bad: Any = "not a float" + str() + write_func(w4, bad) def test_bytes_writer_write_f32_le() -> None: import struct as s @@ -861,7 +870,8 @@ def test_write_f32_via_any() -> None: # Test wrong type w4: Any = BytesWriter() with assertRaises(TypeError): - write_func(w4, "not a float" + str()) # type: ignore + bad: Any = "not a float" + str() + write_func(w4, bad) def test_bytes_reader_read_f32_le() -> None: import math @@ -960,9 +970,11 @@ def test_read_f32_via_any() -> None: # Wrong type for bytes argument with assertRaises(TypeError): - read_func("not bytes", 0 + int()) # type: ignore + bad: Any = "not bytes" + read_func(bad, 0 + int()) with assertRaises(TypeError): - read_func(bytearray(b"\x00" * 4), 0 + int()) # type: ignore + bad2: Any = bytearray(b"\x00" * 4) + read_func(bad2, 0 + int()) def test_write_bytearray() -> None: w = BytesWriter()