diff --git a/mypy/typeshed/stubs/librt/librt/strings.pyi b/mypy/typeshed/stubs/librt/librt/strings.pyi index 51f04c680a56..711e52c2e370 100644 --- a/mypy/typeshed/stubs/librt/librt/strings.pyi +++ b/mypy/typeshed/stubs/librt/librt/strings.pyi @@ -32,3 +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_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: ... diff --git a/mypyc/lib-rt/byteswriter_extra_ops.h b/mypyc/lib-rt/byteswriter_extra_ops.h index 404078bfb611..43f33d986226 100644 --- a/mypyc/lib-rt/byteswriter_extra_ops.h +++ b/mypyc/lib-rt/byteswriter_extra_ops.h @@ -119,6 +119,44 @@ CPyBytesWriter_WriteI64BE(PyObject *obj, int64_t value) { return CPY_NONE; } +// 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; + if (!CPyBytesWriter_EnsureSize(self, 8)) + return CPY_NONE_ERROR; + BytesWriter_WriteF64LEUnsafe(self, 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) @@ -196,6 +234,56 @@ CPyBytes_ReadI64BE(PyObject *bytes_obj, int64_t index) { return CPyBytes_ReadI64BEUnsafe(data + 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 + 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); +} + +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 f1d6fd91d604..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,12 +1012,104 @@ 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)); } +static PyObject* +write_f32_le(PyObject *module, PyObject *const *args, size_t nargs) { + BytesWriterObject *bw = parse_write_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_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* +read_f32_le(PyObject *module, PyObject *const *args, size_t nargs) { + int64_t 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)); +} + +static PyObject* +read_f32_be(PyObject *module, PyObject *const *args, size_t nargs) { + int64_t 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)); +} + +static PyObject* +write_f64_le(PyObject *module, PyObject *const *args, size_t nargs) { + BytesWriterObject *bw = parse_write_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* +write_f64_be(PyObject *module, PyObject *const *args, size_t nargs) { + BytesWriterObject *bw = parse_write_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_f64_le(PyObject *module, PyObject *const *args, size_t nargs) { + int64_t 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)); +} + +static PyObject* +read_f64_be(PyObject *module, PyObject *const *args, size_t nargs) { + int64_t 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)); +} + #endif static PyMethodDef librt_strings_module_methods[] = { @@ -1058,6 +1150,30 @@ 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_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") + }, + {"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") + }, + {"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 d2ea605aea78..66045ffdedad 100644 --- a/mypyc/lib-rt/strings/librt_strings_common.h +++ b/mypyc/lib-rt/strings/librt_strings_common.h @@ -217,4 +217,136 @@ 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; +} + +// 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 +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; +} + +// 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 +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; +} + +// 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 9cbfc4e82ced..ebd967bdd880 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,90 @@ 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], +) + +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], +) + +# 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], +) + +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], +) + +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], +) + +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 cea23186ea69..a0dcc57732d8 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, 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 @@ -40,6 +40,37 @@ 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'), +] + +# 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"" @@ -268,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 @@ -434,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) @@ -565,9 +600,381 @@ 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): + bad2: Any = bytearray(b"\x00" * 8) + read_func(bad2, 0 + int()) + +def test_bytes_writer_write_f64_le() -> None: + # 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: + # 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 + + # Test mixing with other operations and buffer growth + w = BytesWriter() + w.append(0xFF) + for i in range(200): + write_f64_be(w, float(i) * 1.5) + w.append(0xEE) + result = w.getvalue() + assert len(result) == 1602 + assert result[0] == 0xFF + assert result[-1] == 0xEE + assert result[1:9] == 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_bytes_reader_read_f64_le() -> None: + import math + # Test various f64 values + data = b"".join(struct.pack(" None: + import math + # Test various f64 values + data = b"".join(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 + + # 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 + 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): + bad: Any = "not bytes" + read_func(bad, 0 + int()) + with assertRaises(TypeError): + bad2: Any = bytearray(b"\x00" * 8) + read_func(bad2, 0 + int()) + +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): + bad: Any = "not a float" + str() + write_func(w4, bad) + +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): + bad: Any = "not a float" + str() + write_func(w4, bad) + +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 + + # 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 + 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): + 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" * 4) + read_func(bad2, 0 + int()) def test_write_bytearray() -> None: w = BytesWriter()