Skip to content

Commit 490222f

Browse files
committed
pythongh-129813: Add PyBytesWriter C API
* Replace usage of the old private _PyBytesWriter with the new public PyBytesWriter C API. * Remove the old private _PyBytesWriter C API. * Add a freelist for PyBytesWriter_Create(). * TODO: write doc * TODO: document new functions in What's New and Changelog
1 parent a93a5a3 commit 490222f

File tree

14 files changed

+749
-592
lines changed

14 files changed

+749
-592
lines changed

Include/cpython/bytesobject.h

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,3 +40,34 @@ _PyBytes_Join(PyObject *sep, PyObject *iterable)
4040
{
4141
return PyBytes_Join(sep, iterable);
4242
}
43+
44+
45+
// --- PyBytesWriter API -----------------------------------------------------
46+
47+
typedef struct PyBytesWriter PyBytesWriter;
48+
49+
PyAPI_FUNC(void*) PyBytesWriter_Create(
50+
PyBytesWriter **writer,
51+
Py_ssize_t alloc);
52+
PyAPI_FUNC(void) PyBytesWriter_Discard(
53+
PyBytesWriter *writer);
54+
PyAPI_FUNC(PyObject*) PyBytesWriter_Finish(
55+
PyBytesWriter *writer,
56+
void *buf);
57+
58+
PyAPI_FUNC(Py_ssize_t) PyBytesWriter_GetAllocated(
59+
PyBytesWriter *writer);
60+
PyAPI_FUNC(void*) PyBytesWriter_Extend(
61+
PyBytesWriter *writer,
62+
void *buf,
63+
Py_ssize_t extend);
64+
PyAPI_FUNC(void*) PyBytesWriter_WriteBytes(
65+
PyBytesWriter *writer,
66+
void *buf,
67+
const char *bytes,
68+
Py_ssize_t size);
69+
PyAPI_FUNC(void*) PyBytesWriter_Format(
70+
PyBytesWriter *writer,
71+
void *buf,
72+
const char *format,
73+
...);

Include/internal/pycore_bytesobject.h

Lines changed: 1 addition & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -59,88 +59,7 @@ PyAPI_FUNC(void)
5959
_PyBytes_Repeat(char* dest, Py_ssize_t len_dest,
6060
const char* src, Py_ssize_t len_src);
6161

62-
/* --- _PyBytesWriter ----------------------------------------------------- */
63-
64-
/* The _PyBytesWriter structure is big: it contains an embedded "stack buffer".
65-
A _PyBytesWriter variable must be declared at the end of variables in a
66-
function to optimize the memory allocation on the stack. */
67-
typedef struct {
68-
/* bytes, bytearray or NULL (when the small buffer is used) */
69-
PyObject *buffer;
70-
71-
/* Number of allocated size. */
72-
Py_ssize_t allocated;
73-
74-
/* Minimum number of allocated bytes,
75-
incremented by _PyBytesWriter_Prepare() */
76-
Py_ssize_t min_size;
77-
78-
/* If non-zero, use a bytearray instead of a bytes object for buffer. */
79-
int use_bytearray;
80-
81-
/* If non-zero, overallocate the buffer (default: 0).
82-
This flag must be zero if use_bytearray is non-zero. */
83-
int overallocate;
84-
85-
/* Stack buffer */
86-
int use_small_buffer;
87-
char small_buffer[512];
88-
} _PyBytesWriter;
89-
90-
/* Initialize a bytes writer
91-
92-
By default, the overallocation is disabled. Set the overallocate attribute
93-
to control the allocation of the buffer.
94-
95-
Export _PyBytesWriter API for '_pickle' shared extension. */
96-
PyAPI_FUNC(void) _PyBytesWriter_Init(_PyBytesWriter *writer);
97-
98-
/* Get the buffer content and reset the writer.
99-
Return a bytes object, or a bytearray object if use_bytearray is non-zero.
100-
Raise an exception and return NULL on error. */
101-
PyAPI_FUNC(PyObject *) _PyBytesWriter_Finish(_PyBytesWriter *writer,
102-
void *str);
103-
104-
/* Deallocate memory of a writer (clear its internal buffer). */
105-
PyAPI_FUNC(void) _PyBytesWriter_Dealloc(_PyBytesWriter *writer);
106-
107-
/* Allocate the buffer to write size bytes.
108-
Return the pointer to the beginning of buffer data.
109-
Raise an exception and return NULL on error. */
110-
PyAPI_FUNC(void*) _PyBytesWriter_Alloc(_PyBytesWriter *writer,
111-
Py_ssize_t size);
112-
113-
/* Ensure that the buffer is large enough to write *size* bytes.
114-
Add size to the writer minimum size (min_size attribute).
115-
116-
str is the current pointer inside the buffer.
117-
Return the updated current pointer inside the buffer.
118-
Raise an exception and return NULL on error. */
119-
PyAPI_FUNC(void*) _PyBytesWriter_Prepare(_PyBytesWriter *writer,
120-
void *str,
121-
Py_ssize_t size);
122-
123-
/* Resize the buffer to make it larger.
124-
The new buffer may be larger than size bytes because of overallocation.
125-
Return the updated current pointer inside the buffer.
126-
Raise an exception and return NULL on error.
127-
128-
Note: size must be greater than the number of allocated bytes in the writer.
129-
130-
This function doesn't use the writer minimum size (min_size attribute).
131-
132-
See also _PyBytesWriter_Prepare().
133-
*/
134-
PyAPI_FUNC(void*) _PyBytesWriter_Resize(_PyBytesWriter *writer,
135-
void *str,
136-
Py_ssize_t size);
137-
138-
/* Write bytes.
139-
Raise an exception and return NULL on error. */
140-
PyAPI_FUNC(void*) _PyBytesWriter_WriteBytes(_PyBytesWriter *writer,
141-
void *str,
142-
const void *bytes,
143-
Py_ssize_t size);
62+
extern char* _PyBytesWriter_Start(PyBytesWriter *writer);
14463

14564
#ifdef __cplusplus
14665
}

Include/internal/pycore_freelist_state.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ extern "C" {
2424
# define Py_futureiters_MAXFREELIST 255
2525
# define Py_object_stack_chunks_MAXFREELIST 4
2626
# define Py_unicode_writers_MAXFREELIST 1
27+
# define Py_bytes_writers_MAXFREELIST 1
2728
# define Py_pymethodobjects_MAXFREELIST 20
2829

2930
// A generic freelist of either PyObjects or other data structures.
@@ -53,6 +54,7 @@ struct _Py_freelists {
5354
struct _Py_freelist futureiters;
5455
struct _Py_freelist object_stack_chunks;
5556
struct _Py_freelist unicode_writers;
57+
struct _Py_freelist bytes_writers;
5658
struct _Py_freelist pymethodobjects;
5759
};
5860

Include/internal/pycore_long.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,7 @@ extern int _PyLong_FormatWriter(
136136
int alternate);
137137

138138
extern char* _PyLong_FormatBytesWriter(
139-
_PyBytesWriter *writer,
139+
PyBytesWriter *writer,
140140
char *str,
141141
PyObject *obj,
142142
int base,

Lib/test/test_capi/test_bytes.py

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -290,5 +290,64 @@ def test_join(self):
290290
bytes_join(b'', NULL)
291291

292292

293+
class PyBytesWriterTest(unittest.TestCase):
294+
def create_writer(self, alloc):
295+
return _testcapi.PyBytesWriter(alloc)
296+
297+
def test_empty(self):
298+
# Test PyBytesWriter_Create()
299+
writer = self.create_writer(0)
300+
self.assertEqual(writer.finish(), b'')
301+
302+
def test_write_bytes(self):
303+
# Test PyBytesWriter_WriteBytes()
304+
305+
writer = self.create_writer(0)
306+
writer.write_bytes(b'Hello World!', -1)
307+
self.assertEqual(writer.finish(), b'Hello World!')
308+
309+
writer = self.create_writer(0)
310+
writer.write_bytes(b'Hello ', -1)
311+
writer.write_bytes(b'World! <truncated>', 6)
312+
self.assertEqual(writer.finish(), b'Hello World!')
313+
314+
def test_extend(self):
315+
# Test PyBytesWriter_Extend()
316+
317+
writer = self.create_writer(0)
318+
writer.extend(20, b'number=123456')
319+
writer.extend(0, b'')
320+
self.assertEqual(writer.finish(), b'number=123456')
321+
322+
writer = self.create_writer(0)
323+
writer.extend(0, b'')
324+
writer.extend(20, b'number=123456')
325+
self.assertEqual(writer.finish(), b'number=123456')
326+
327+
writer = self.create_writer(0)
328+
writer.extend(10, b'number=')
329+
writer.extend(10, b'123456')
330+
self.assertEqual(writer.finish(), b'number=123456')
331+
332+
writer = self.create_writer(0)
333+
writer.extend(10, b'number=')
334+
writer.extend(0, b'')
335+
writer.extend(10, b'123456')
336+
self.assertEqual(writer.finish(), b'number=123456')
337+
338+
writer = self.create_writer(0)
339+
writer.extend(10, b'number')
340+
writer.extend(10, b'=')
341+
writer.extend(10, b'123')
342+
writer.extend(10, b'456')
343+
self.assertEqual(writer.finish(), b'number=123456')
344+
345+
def test_format(self):
346+
# Test PyBytesWriter_Format()
347+
writer = self.create_writer(0)
348+
writer.format_i(123456)
349+
self.assertEqual(writer.finish(), b'123456')
350+
351+
293352
if __name__ == "__main__":
294353
unittest.main()

Modules/_pickle.c

Lines changed: 13 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -2601,29 +2601,22 @@ save_picklebuffer(PickleState *st, PicklerObject *self, PyObject *obj)
26012601
static PyObject *
26022602
raw_unicode_escape(PyObject *obj)
26032603
{
2604-
char *p;
2605-
Py_ssize_t i, size;
2606-
const void *data;
2607-
int kind;
2608-
_PyBytesWriter writer;
2604+
Py_ssize_t size = PyUnicode_GET_LENGTH(obj);
2605+
const void *data = PyUnicode_DATA(obj);
2606+
int kind = PyUnicode_KIND(obj);
26092607

2610-
_PyBytesWriter_Init(&writer);
2611-
2612-
size = PyUnicode_GET_LENGTH(obj);
2613-
data = PyUnicode_DATA(obj);
2614-
kind = PyUnicode_KIND(obj);
2615-
2616-
p = _PyBytesWriter_Alloc(&writer, size);
2617-
if (p == NULL)
2618-
goto error;
2619-
writer.overallocate = 1;
2608+
PyBytesWriter *writer;
2609+
char *p = PyBytesWriter_Create(&writer, size);
2610+
if (p == NULL) {
2611+
return NULL;
2612+
}
26202613

2621-
for (i=0; i < size; i++) {
2614+
for (Py_ssize_t i=0; i < size; i++) {
26222615
Py_UCS4 ch = PyUnicode_READ(kind, data, i);
26232616
/* Map 32-bit characters to '\Uxxxxxxxx' */
26242617
if (ch >= 0x10000) {
26252618
/* -1: subtract 1 preallocated byte */
2626-
p = _PyBytesWriter_Prepare(&writer, p, 10-1);
2619+
p = PyBytesWriter_Extend(writer, p, 10-1);
26272620
if (p == NULL)
26282621
goto error;
26292622

@@ -2644,7 +2637,7 @@ raw_unicode_escape(PyObject *obj)
26442637
ch == 0x1a)
26452638
{
26462639
/* -1: subtract 1 preallocated byte */
2647-
p = _PyBytesWriter_Prepare(&writer, p, 6-1);
2640+
p = PyBytesWriter_Extend(writer, p, 6-1);
26482641
if (p == NULL)
26492642
goto error;
26502643

@@ -2660,10 +2653,10 @@ raw_unicode_escape(PyObject *obj)
26602653
*p++ = (char) ch;
26612654
}
26622655

2663-
return _PyBytesWriter_Finish(&writer, p);
2656+
return PyBytesWriter_Finish(writer, p);
26642657

26652658
error:
2666-
_PyBytesWriter_Dealloc(&writer);
2659+
PyBytesWriter_Discard(writer);
26672660
return NULL;
26682661
}
26692662

Modules/_struct.c

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2259,7 +2259,6 @@ strings.");
22592259
static PyObject *
22602260
s_pack(PyObject *self, PyObject *const *args, Py_ssize_t nargs)
22612261
{
2262-
char *buf;
22632262
PyStructObject *soself;
22642263
_structmodulestate *state = get_struct_state_structinst(self);
22652264

@@ -2275,21 +2274,23 @@ s_pack(PyObject *self, PyObject *const *args, Py_ssize_t nargs)
22752274
}
22762275

22772276
/* Allocate a new string */
2278-
_PyBytesWriter writer;
2279-
_PyBytesWriter_Init(&writer);
2280-
buf = _PyBytesWriter_Alloc(&writer, soself->s_size);
2277+
PyBytesWriter *writer;
2278+
char *buf = PyBytesWriter_Create(&writer, soself->s_size);
22812279
if (buf == NULL) {
2282-
_PyBytesWriter_Dealloc(&writer);
2283-
return NULL;
2280+
goto error;
22842281
}
22852282

22862283
/* Call the guts */
2287-
if ( s_pack_internal(soself, args, 0, buf, state) != 0 ) {
2288-
_PyBytesWriter_Dealloc(&writer);
2289-
return NULL;
2284+
if (s_pack_internal(soself, args, 0, buf, state) != 0) {
2285+
goto error;
22902286
}
2287+
buf += soself->s_size;
22912288

2292-
return _PyBytesWriter_Finish(&writer, buf + soself->s_size);
2289+
return PyBytesWriter_Finish(writer, buf);
2290+
2291+
error:
2292+
PyBytesWriter_Discard(writer);
2293+
return NULL;
22932294
}
22942295

22952296
PyDoc_STRVAR(s_pack_into__doc__,

0 commit comments

Comments
 (0)