Skip to content

Commit 4050d0d

Browse files
authored
[mypyc] Add float read/write functions to librt.strings (#20778)
Support both 32-bit and 64-bit float formats, and also support little endian and big endian (e.g. `write_f64_le`). I think the read/write function feature set now covers all the essential functionality. We can consider adding `u8` variants in the future, though they can already be supported using just `append` and get item. Follow-up to #20772. I used coding agent assist significantly.
1 parent 8a91c09 commit 4050d0d

6 files changed

Lines changed: 859 additions & 23 deletions

File tree

mypy/typeshed/stubs/librt/librt/strings.pyi

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,3 +32,11 @@ def write_i64_le(b: BytesWriter, n: i64, /) -> None: ...
3232
def write_i64_be(b: BytesWriter, n: i64, /) -> None: ...
3333
def read_i64_le(b: bytes, index: i64, /) -> i64: ...
3434
def read_i64_be(b: bytes, index: i64, /) -> i64: ...
35+
def write_f32_le(b: BytesWriter, n: float, /) -> None: ...
36+
def write_f32_be(b: BytesWriter, n: float, /) -> None: ...
37+
def read_f32_le(b: bytes, index: i64, /) -> float: ...
38+
def read_f32_be(b: bytes, index: i64, /) -> float: ...
39+
def write_f64_le(b: BytesWriter, n: float, /) -> None: ...
40+
def write_f64_be(b: BytesWriter, n: float, /) -> None: ...
41+
def read_f64_le(b: bytes, index: i64, /) -> float: ...
42+
def read_f64_be(b: bytes, index: i64, /) -> float: ...

mypyc/lib-rt/byteswriter_extra_ops.h

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,44 @@ CPyBytesWriter_WriteI64BE(PyObject *obj, int64_t value) {
119119
return CPY_NONE;
120120
}
121121

122+
// BytesWriter: Write float operations
123+
124+
static inline char
125+
CPyBytesWriter_WriteF32LE(PyObject *obj, double value) {
126+
BytesWriterObject *self = (BytesWriterObject *)obj;
127+
if (!CPyBytesWriter_EnsureSize(self, 4))
128+
return CPY_NONE_ERROR;
129+
BytesWriter_WriteF32LEUnsafe(self, (float)value);
130+
return CPY_NONE;
131+
}
132+
133+
static inline char
134+
CPyBytesWriter_WriteF32BE(PyObject *obj, double value) {
135+
BytesWriterObject *self = (BytesWriterObject *)obj;
136+
if (!CPyBytesWriter_EnsureSize(self, 4))
137+
return CPY_NONE_ERROR;
138+
BytesWriter_WriteF32BEUnsafe(self, (float)value);
139+
return CPY_NONE;
140+
}
141+
142+
static inline char
143+
CPyBytesWriter_WriteF64LE(PyObject *obj, double value) {
144+
BytesWriterObject *self = (BytesWriterObject *)obj;
145+
if (!CPyBytesWriter_EnsureSize(self, 8))
146+
return CPY_NONE_ERROR;
147+
BytesWriter_WriteF64LEUnsafe(self, value);
148+
return CPY_NONE;
149+
}
150+
151+
static inline char
152+
CPyBytesWriter_WriteF64BE(PyObject *obj, double value) {
153+
BytesWriterObject *self = (BytesWriterObject *)obj;
154+
if (!CPyBytesWriter_EnsureSize(self, 8))
155+
return CPY_NONE_ERROR;
156+
BytesWriter_WriteF64BEUnsafe(self, value);
157+
return CPY_NONE;
158+
}
159+
122160
// Bytes: Read integer operations
123161

124162
// 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) {
196234
return CPyBytes_ReadI64BEUnsafe(data + index);
197235
}
198236

237+
// Bytes: Read float operations
238+
239+
static inline double
240+
CPyBytes_ReadF32LE(PyObject *bytes_obj, int64_t index) {
241+
// bytes_obj type is enforced by mypyc
242+
Py_ssize_t size = PyBytes_GET_SIZE(bytes_obj);
243+
if (unlikely(index < 0 || index > size - 4)) {
244+
CPyBytes_ReadError(index, size);
245+
return CPY_FLOAT_ERROR;
246+
}
247+
const unsigned char *data = (const unsigned char *)PyBytes_AS_STRING(bytes_obj);
248+
return (double)CPyBytes_ReadF32LEUnsafe(data + index);
249+
}
250+
251+
static inline double
252+
CPyBytes_ReadF32BE(PyObject *bytes_obj, int64_t index) {
253+
// bytes_obj type is enforced by mypyc
254+
Py_ssize_t size = PyBytes_GET_SIZE(bytes_obj);
255+
if (unlikely(index < 0 || index > size - 4)) {
256+
CPyBytes_ReadError(index, size);
257+
return CPY_FLOAT_ERROR;
258+
}
259+
const unsigned char *data = (const unsigned char *)PyBytes_AS_STRING(bytes_obj);
260+
return (double)CPyBytes_ReadF32BEUnsafe(data + index);
261+
}
262+
263+
static inline double
264+
CPyBytes_ReadF64LE(PyObject *bytes_obj, int64_t index) {
265+
// bytes_obj type is enforced by mypyc
266+
Py_ssize_t size = PyBytes_GET_SIZE(bytes_obj);
267+
if (unlikely(index < 0 || index > size - 8)) {
268+
CPyBytes_ReadError(index, size);
269+
return CPY_FLOAT_ERROR;
270+
}
271+
const unsigned char *data = (const unsigned char *)PyBytes_AS_STRING(bytes_obj);
272+
return CPyBytes_ReadF64LEUnsafe(data + index);
273+
}
274+
275+
static inline double
276+
CPyBytes_ReadF64BE(PyObject *bytes_obj, int64_t index) {
277+
// bytes_obj type is enforced by mypyc
278+
Py_ssize_t size = PyBytes_GET_SIZE(bytes_obj);
279+
if (unlikely(index < 0 || index > size - 8)) {
280+
CPyBytes_ReadError(index, size);
281+
return CPY_FLOAT_ERROR;
282+
}
283+
const unsigned char *data = (const unsigned char *)PyBytes_AS_STRING(bytes_obj);
284+
return CPyBytes_ReadF64BEUnsafe(data + index);
285+
}
286+
199287
#endif // MYPYC_EXPERIMENTAL
200288

201289
#endif

mypyc/lib-rt/strings/librt_strings.c

Lines changed: 132 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -831,9 +831,9 @@ StringWriter_len_internal(PyObject *self) {
831831

832832
// End of StringWriter
833833

834-
// Helper for write_i*_le/be functions - validates args and returns BytesWriter
834+
// Helper for write_*_le/be functions - validates args and returns BytesWriter
835835
static inline BytesWriterObject *
836-
parse_write_int_args(PyObject *const *args, size_t nargs, const char *func_name) {
836+
parse_write_args(PyObject *const *args, size_t nargs, const char *func_name) {
837837
if (unlikely(nargs != 2)) {
838838
PyErr_Format(PyExc_TypeError,
839839
"%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)
846846
return (BytesWriterObject *)writer;
847847
}
848848

849-
// Helper for read_i*_le/be functions - validates args and returns data pointer
849+
// Helper for read_*_le/be functions - validates args and returns data pointer
850850
// Returns NULL on error, sets *out_index to the validated index on success
851851
static inline const unsigned char *
852-
parse_read_int_args(PyObject *const *args, size_t nargs, const char *func_name,
852+
parse_read_args(PyObject *const *args, size_t nargs, const char *func_name,
853853
Py_ssize_t num_bytes, int64_t *out_index) {
854854
if (unlikely(nargs != 2)) {
855855
PyErr_Format(PyExc_TypeError,
@@ -882,7 +882,7 @@ parse_read_int_args(PyObject *const *args, size_t nargs, const char *func_name,
882882

883883
static PyObject*
884884
write_i16_le(PyObject *module, PyObject *const *args, size_t nargs) {
885-
BytesWriterObject *bw = parse_write_int_args(args, nargs, "write_i16_le");
885+
BytesWriterObject *bw = parse_write_args(args, nargs, "write_i16_le");
886886
if (bw == NULL)
887887
return NULL;
888888
int16_t unboxed = CPyLong_AsInt16(args[1]);
@@ -896,7 +896,7 @@ write_i16_le(PyObject *module, PyObject *const *args, size_t nargs) {
896896

897897
static PyObject*
898898
write_i16_be(PyObject *module, PyObject *const *args, size_t nargs) {
899-
BytesWriterObject *bw = parse_write_int_args(args, nargs, "write_i16_be");
899+
BytesWriterObject *bw = parse_write_args(args, nargs, "write_i16_be");
900900
if (bw == NULL)
901901
return NULL;
902902
int16_t unboxed = CPyLong_AsInt16(args[1]);
@@ -911,7 +911,7 @@ write_i16_be(PyObject *module, PyObject *const *args, size_t nargs) {
911911
static PyObject*
912912
read_i16_le(PyObject *module, PyObject *const *args, size_t nargs) {
913913
int64_t index;
914-
const unsigned char *data = parse_read_int_args(args, nargs, "read_i16_le", 2, &index);
914+
const unsigned char *data = parse_read_args(args, nargs, "read_i16_le", 2, &index);
915915
if (data == NULL)
916916
return NULL;
917917
return PyLong_FromLong(CPyBytes_ReadI16LEUnsafe(data + index));
@@ -920,15 +920,15 @@ read_i16_le(PyObject *module, PyObject *const *args, size_t nargs) {
920920
static PyObject*
921921
read_i16_be(PyObject *module, PyObject *const *args, size_t nargs) {
922922
int64_t index;
923-
const unsigned char *data = parse_read_int_args(args, nargs, "read_i16_be", 2, &index);
923+
const unsigned char *data = parse_read_args(args, nargs, "read_i16_be", 2, &index);
924924
if (data == NULL)
925925
return NULL;
926926
return PyLong_FromLong(CPyBytes_ReadI16BEUnsafe(data + index));
927927
}
928928

929929
static PyObject*
930930
write_i32_le(PyObject *module, PyObject *const *args, size_t nargs) {
931-
BytesWriterObject *bw = parse_write_int_args(args, nargs, "write_i32_le");
931+
BytesWriterObject *bw = parse_write_args(args, nargs, "write_i32_le");
932932
if (bw == NULL)
933933
return NULL;
934934
int32_t unboxed = CPyLong_AsInt32(args[1]);
@@ -942,7 +942,7 @@ write_i32_le(PyObject *module, PyObject *const *args, size_t nargs) {
942942

943943
static PyObject*
944944
write_i32_be(PyObject *module, PyObject *const *args, size_t nargs) {
945-
BytesWriterObject *bw = parse_write_int_args(args, nargs, "write_i32_be");
945+
BytesWriterObject *bw = parse_write_args(args, nargs, "write_i32_be");
946946
if (bw == NULL)
947947
return NULL;
948948
int32_t unboxed = CPyLong_AsInt32(args[1]);
@@ -957,7 +957,7 @@ write_i32_be(PyObject *module, PyObject *const *args, size_t nargs) {
957957
static PyObject*
958958
read_i32_le(PyObject *module, PyObject *const *args, size_t nargs) {
959959
int64_t index;
960-
const unsigned char *data = parse_read_int_args(args, nargs, "read_i32_le", 4, &index);
960+
const unsigned char *data = parse_read_args(args, nargs, "read_i32_le", 4, &index);
961961
if (data == NULL)
962962
return NULL;
963963
return PyLong_FromLong(CPyBytes_ReadI32LEUnsafe(data + index));
@@ -966,15 +966,15 @@ read_i32_le(PyObject *module, PyObject *const *args, size_t nargs) {
966966
static PyObject*
967967
read_i32_be(PyObject *module, PyObject *const *args, size_t nargs) {
968968
int64_t index;
969-
const unsigned char *data = parse_read_int_args(args, nargs, "read_i32_be", 4, &index);
969+
const unsigned char *data = parse_read_args(args, nargs, "read_i32_be", 4, &index);
970970
if (data == NULL)
971971
return NULL;
972972
return PyLong_FromLong(CPyBytes_ReadI32BEUnsafe(data + index));
973973
}
974974

975975
static PyObject*
976976
write_i64_le(PyObject *module, PyObject *const *args, size_t nargs) {
977-
BytesWriterObject *bw = parse_write_int_args(args, nargs, "write_i64_le");
977+
BytesWriterObject *bw = parse_write_args(args, nargs, "write_i64_le");
978978
if (bw == NULL)
979979
return NULL;
980980
int64_t unboxed = CPyLong_AsInt64(args[1]);
@@ -988,7 +988,7 @@ write_i64_le(PyObject *module, PyObject *const *args, size_t nargs) {
988988

989989
static PyObject*
990990
write_i64_be(PyObject *module, PyObject *const *args, size_t nargs) {
991-
BytesWriterObject *bw = parse_write_int_args(args, nargs, "write_i64_be");
991+
BytesWriterObject *bw = parse_write_args(args, nargs, "write_i64_be");
992992
if (bw == NULL)
993993
return NULL;
994994
int64_t unboxed = CPyLong_AsInt64(args[1]);
@@ -1003,7 +1003,7 @@ write_i64_be(PyObject *module, PyObject *const *args, size_t nargs) {
10031003
static PyObject*
10041004
read_i64_le(PyObject *module, PyObject *const *args, size_t nargs) {
10051005
int64_t index;
1006-
const unsigned char *data = parse_read_int_args(args, nargs, "read_i64_le", 8, &index);
1006+
const unsigned char *data = parse_read_args(args, nargs, "read_i64_le", 8, &index);
10071007
if (data == NULL)
10081008
return NULL;
10091009
return PyLong_FromLongLong(CPyBytes_ReadI64LEUnsafe(data + index));
@@ -1012,12 +1012,104 @@ read_i64_le(PyObject *module, PyObject *const *args, size_t nargs) {
10121012
static PyObject*
10131013
read_i64_be(PyObject *module, PyObject *const *args, size_t nargs) {
10141014
int64_t index;
1015-
const unsigned char *data = parse_read_int_args(args, nargs, "read_i64_be", 8, &index);
1015+
const unsigned char *data = parse_read_args(args, nargs, "read_i64_be", 8, &index);
10161016
if (data == NULL)
10171017
return NULL;
10181018
return PyLong_FromLongLong(CPyBytes_ReadI64BEUnsafe(data + index));
10191019
}
10201020

1021+
static PyObject*
1022+
write_f32_le(PyObject *module, PyObject *const *args, size_t nargs) {
1023+
BytesWriterObject *bw = parse_write_args(args, nargs, "write_f32_le");
1024+
if (bw == NULL)
1025+
return NULL;
1026+
double unboxed = PyFloat_AsDouble(args[1]);
1027+
if (unlikely(unboxed == -1.0 && PyErr_Occurred()))
1028+
return NULL;
1029+
if (unlikely(!ensure_bytes_writer_size(bw, 4)))
1030+
return NULL;
1031+
BytesWriter_WriteF32LEUnsafe(bw, (float)unboxed);
1032+
Py_RETURN_NONE;
1033+
}
1034+
1035+
static PyObject*
1036+
write_f32_be(PyObject *module, PyObject *const *args, size_t nargs) {
1037+
BytesWriterObject *bw = parse_write_args(args, nargs, "write_f32_be");
1038+
if (bw == NULL)
1039+
return NULL;
1040+
double unboxed = PyFloat_AsDouble(args[1]);
1041+
if (unlikely(unboxed == -1.0 && PyErr_Occurred()))
1042+
return NULL;
1043+
if (unlikely(!ensure_bytes_writer_size(bw, 4)))
1044+
return NULL;
1045+
BytesWriter_WriteF32BEUnsafe(bw, (float)unboxed);
1046+
Py_RETURN_NONE;
1047+
}
1048+
1049+
static PyObject*
1050+
read_f32_le(PyObject *module, PyObject *const *args, size_t nargs) {
1051+
int64_t index;
1052+
const unsigned char *data = parse_read_args(args, nargs, "read_f32_le", 4, &index);
1053+
if (data == NULL)
1054+
return NULL;
1055+
return PyFloat_FromDouble((double)CPyBytes_ReadF32LEUnsafe(data + index));
1056+
}
1057+
1058+
static PyObject*
1059+
read_f32_be(PyObject *module, PyObject *const *args, size_t nargs) {
1060+
int64_t index;
1061+
const unsigned char *data = parse_read_args(args, nargs, "read_f32_be", 4, &index);
1062+
if (data == NULL)
1063+
return NULL;
1064+
return PyFloat_FromDouble((double)CPyBytes_ReadF32BEUnsafe(data + index));
1065+
}
1066+
1067+
static PyObject*
1068+
write_f64_le(PyObject *module, PyObject *const *args, size_t nargs) {
1069+
BytesWriterObject *bw = parse_write_args(args, nargs, "write_f64_le");
1070+
if (bw == NULL)
1071+
return NULL;
1072+
double unboxed = PyFloat_AsDouble(args[1]);
1073+
if (unlikely(unboxed == -1.0 && PyErr_Occurred()))
1074+
return NULL;
1075+
if (unlikely(!ensure_bytes_writer_size(bw, 8)))
1076+
return NULL;
1077+
BytesWriter_WriteF64LEUnsafe(bw, unboxed);
1078+
Py_RETURN_NONE;
1079+
}
1080+
1081+
static PyObject*
1082+
write_f64_be(PyObject *module, PyObject *const *args, size_t nargs) {
1083+
BytesWriterObject *bw = parse_write_args(args, nargs, "write_f64_be");
1084+
if (bw == NULL)
1085+
return NULL;
1086+
double unboxed = PyFloat_AsDouble(args[1]);
1087+
if (unlikely(unboxed == -1.0 && PyErr_Occurred()))
1088+
return NULL;
1089+
if (unlikely(!ensure_bytes_writer_size(bw, 8)))
1090+
return NULL;
1091+
BytesWriter_WriteF64BEUnsafe(bw, unboxed);
1092+
Py_RETURN_NONE;
1093+
}
1094+
1095+
static PyObject*
1096+
read_f64_le(PyObject *module, PyObject *const *args, size_t nargs) {
1097+
int64_t index;
1098+
const unsigned char *data = parse_read_args(args, nargs, "read_f64_le", 8, &index);
1099+
if (data == NULL)
1100+
return NULL;
1101+
return PyFloat_FromDouble(CPyBytes_ReadF64LEUnsafe(data + index));
1102+
}
1103+
1104+
static PyObject*
1105+
read_f64_be(PyObject *module, PyObject *const *args, size_t nargs) {
1106+
int64_t index;
1107+
const unsigned char *data = parse_read_args(args, nargs, "read_f64_be", 8, &index);
1108+
if (data == NULL)
1109+
return NULL;
1110+
return PyFloat_FromDouble(CPyBytes_ReadF64BEUnsafe(data + index));
1111+
}
1112+
10211113
#endif
10221114

10231115
static PyMethodDef librt_strings_module_methods[] = {
@@ -1058,6 +1150,30 @@ static PyMethodDef librt_strings_module_methods[] = {
10581150
{"read_i64_be", (PyCFunction) read_i64_be, METH_FASTCALL,
10591151
PyDoc_STR("Read a 64-bit signed integer from bytes in big-endian format")
10601152
},
1153+
{"write_f32_le", (PyCFunction) write_f32_le, METH_FASTCALL,
1154+
PyDoc_STR("Write a 32-bit float to BytesWriter in little-endian format")
1155+
},
1156+
{"write_f32_be", (PyCFunction) write_f32_be, METH_FASTCALL,
1157+
PyDoc_STR("Write a 32-bit float to BytesWriter in big-endian format")
1158+
},
1159+
{"read_f32_le", (PyCFunction) read_f32_le, METH_FASTCALL,
1160+
PyDoc_STR("Read a 32-bit float from bytes in little-endian format")
1161+
},
1162+
{"read_f32_be", (PyCFunction) read_f32_be, METH_FASTCALL,
1163+
PyDoc_STR("Read a 32-bit float from bytes in big-endian format")
1164+
},
1165+
{"write_f64_le", (PyCFunction) write_f64_le, METH_FASTCALL,
1166+
PyDoc_STR("Write a 64-bit float to BytesWriter in little-endian format")
1167+
},
1168+
{"write_f64_be", (PyCFunction) write_f64_be, METH_FASTCALL,
1169+
PyDoc_STR("Write a 64-bit float to BytesWriter in big-endian format")
1170+
},
1171+
{"read_f64_le", (PyCFunction) read_f64_le, METH_FASTCALL,
1172+
PyDoc_STR("Read a 64-bit float from bytes in little-endian format")
1173+
},
1174+
{"read_f64_be", (PyCFunction) read_f64_be, METH_FASTCALL,
1175+
PyDoc_STR("Read a 64-bit float from bytes in big-endian format")
1176+
},
10611177
#endif
10621178
{NULL, NULL, 0, NULL}
10631179
};

0 commit comments

Comments
 (0)