From 1a14b099737825bd091e09432d46195dc32412a0 Mon Sep 17 00:00:00 2001 From: animalize Date: Fri, 16 Oct 2020 15:44:03 +0800 Subject: [PATCH 01/16] 1. Add _BlocksOutputBuffer --- Doc/whatsnew/3.10.rst | 5 + .../2020-10-16-15-34-30.bpo-41486.Mu9Iit.rst | 4 + Modules/blocks_output_buffer.h | 344 ++++++++++++++++++ 3 files changed, 353 insertions(+) create mode 100644 Misc/NEWS.d/next/Library/2020-10-16-15-34-30.bpo-41486.Mu9Iit.rst create mode 100644 Modules/blocks_output_buffer.h diff --git a/Doc/whatsnew/3.10.rst b/Doc/whatsnew/3.10.rst index 96892ba3d37e10..a2bb7785d498bd 100644 --- a/Doc/whatsnew/3.10.rst +++ b/Doc/whatsnew/3.10.rst @@ -587,6 +587,11 @@ Optimizations for more details. (Contributed by Victor Stinner and Pablo Galindo in :issue:`38980`.) +* Use a new output buffer management code for :mod:`bz2` / :mod:`lzma` / + :mod:`zlib` modules, and add ``.readall()`` function to + ``_compression.DecompressReader`` class. bz2 decompression 1.09x ~ 1.17x + faster, lzma decompression 1.20x ~ 1.32x faster, ``GzipFile.read(-1)`` 1.11x + ~ 1.18x faster. (Contributed by Ma Lin in :issue:`41486`) * Function parameters and their annotations are no longer computed at runtime, but rather at compilation time. They are stored as a tuple of strings at the diff --git a/Misc/NEWS.d/next/Library/2020-10-16-15-34-30.bpo-41486.Mu9Iit.rst b/Misc/NEWS.d/next/Library/2020-10-16-15-34-30.bpo-41486.Mu9Iit.rst new file mode 100644 index 00000000000000..75de9f672ea5d3 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2020-10-16-15-34-30.bpo-41486.Mu9Iit.rst @@ -0,0 +1,4 @@ +Use a new output buffer management code for :mod:`bz2` / :mod:`lzma` / +:mod:`zlib` modules, and add ``.readall()`` function to +``_compression.DecompressReader`` class. These bring some performance +improvements. Patch by Ma Lin. diff --git a/Modules/blocks_output_buffer.h b/Modules/blocks_output_buffer.h new file mode 100644 index 00000000000000..746ca0eb5e2cea --- /dev/null +++ b/Modules/blocks_output_buffer.h @@ -0,0 +1,344 @@ +/* + _BlocksOutputBuffer is used to maintain an output buffer + that has unpredictable size. Suitable for compression/decompression + API (bz2/lzma/zlib) that has stream->next_out and stream->avail_out: + + stream->next_out: point to the next output position. + stream->avail_out: the number of available bytes left in the buffer. + + It maintains a list of bytes object, so there is no overhead + of resizing the buffer. + + Usage: + + 1, Define BOB_BUFFER_TYPE and BOB_SIZE_TYPE, and include this file: + #define BOB_BUFFER_TYPE uint8_t // type of next_out pointer + #define BOB_SIZE_TYPE size_t // type of avail_out + #include "blocks_output_buffer.h" + + 2, Initialize the buffer use one of these functions: + _BlocksOutputBuffer_Init() + _BlocksOutputBuffer_InitAndGrow() + _BlocksOutputBuffer_InitWithSize() + + 3, If (avail_out == 0), grow the buffer: + _BlocksOutputBuffer_Grow() + + 4, Get the current outputted data size: + _BlocksOutputBuffer_GetDataSize() + + 5, Finish the buffer, and return a bytes object: + _BlocksOutputBuffer_Finish() + + 6, Clean up the buffer when an error occurred: + _BlocksOutputBuffer_OnError() +*/ + +typedef struct { + // List of bytes objects + PyObject *list; + // Number of whole allocated size + Py_ssize_t allocated; + // Max length of the buffer, negative number means unlimited length. + Py_ssize_t max_length; +} _BlocksOutputBuffer; + +#if defined(__GNUC__) +# define UNUSED_ATTR __attribute__((unused)) +#else +# define UNUSED_ATTR +#endif + +#if BOB_SIZE_TYPE == uint32_t + #define BOB_SIZE_MAX UINT32_MAX +#elif BOB_SIZE_TYPE == size_t + #define BOB_SIZE_MAX UINTPTR_MAX +#else + #error Please define BOB_SIZE_MAX to BOB_SIZE_TYPE's max value +#endif + +const char unable_allocate_msg[] = "Unable to allocate output buffer."; + +/* Block size sequence. + In 32-bit build Py_ssize_t is int, so the type is int. Below functions + assume the type is int. +*/ +#define KB (1024) +#define MB (1024*1024) +const int BUFFER_BLOCK_SIZE[] = + { 32*KB, 64*KB, 256*KB, 1*MB, 4*MB, 8*MB, 16*MB, 16*MB, + 32*MB, 32*MB, 32*MB, 32*MB, 64*MB, 64*MB, 128*MB, 128*MB, + 256*MB }; +#undef KB +#undef MB + +/* According to the block sizes defined by BUFFER_BLOCK_SIZE, the whole + allocated size growth step is: + 1 32 KB +32 KB + 2 96 KB +64 KB + 3 352 KB +256 KB + 4 1.34 MB +1 MB + 5 5.34 MB +4 MB + 6 13.34 MB +8 MB + 7 29.34 MB +16 MB + 8 45.34 MB +16 MB + 9 77.34 MB +32 MB + 10 109.34 MB +32 MB + 11 141.34 MB +32 MB + 12 173.34 MB +32 MB + 13 237.34 MB +64 MB + 14 301.34 MB +64 MB + 15 429.34 MB +128 MB + 16 557.34 MB +128 MB + 17 813.34 MB +256 MB + 18 1069.34 MB +256 MB + 19 1325.34 MB +256 MB + 20 1581.34 MB +256 MB + 21 1837.34 MB +256 MB + 22 2093.34 MB +256 MB + ... +*/ + +/* Initialize the buffer. + + max_length: Max length of the buffer, -1 for unlimited length. + + Return 0 on success + Return -1 on failure +*/ +static UNUSED_ATTR int +_BlocksOutputBuffer_Init(_BlocksOutputBuffer *buffer, Py_ssize_t max_length, + BOB_BUFFER_TYPE **next_out, BOB_SIZE_TYPE *avail_out) +{ + buffer->list = PyList_New(0); + if (buffer->list == NULL) { + return -1; + } + buffer->allocated = 0; + buffer->max_length = max_length; + + *next_out = (BOB_BUFFER_TYPE*) 1; // some libs don't accept NULL + *avail_out = 0; + return 0; +} + +/* Initialize the buffer, and grow the buffer. + + max_length: Max length of the buffer, -1 for unlimited length. + + Return 0 on success + Return -1 on failure +*/ +static UNUSED_ATTR int +_BlocksOutputBuffer_InitAndGrow(_BlocksOutputBuffer *buffer, Py_ssize_t max_length, + BOB_BUFFER_TYPE **next_out, BOB_SIZE_TYPE *avail_out) +{ + PyObject *b; + int block_size; + + // set & check max_length + buffer->max_length = max_length; + if (0 <= max_length && max_length < BUFFER_BLOCK_SIZE[0]) { + block_size = (int) max_length; + } else { + block_size = BUFFER_BLOCK_SIZE[0]; + } + + // the first block + b = PyBytes_FromStringAndSize(NULL, block_size); + if (b == NULL) { + buffer->list = NULL; // for _BlocksOutputBuffer_OnError() + return -1; + } + + // create the list + buffer->list = PyList_New(1); + if (buffer->list == NULL) { + Py_DECREF(b); + return -1; + } + PyList_SET_ITEM(buffer->list, 0, b); + + // set variables + buffer->allocated = block_size; + + *next_out = (BOB_BUFFER_TYPE*) PyBytes_AS_STRING(b); + *avail_out = block_size; + return 0; +} + +/* Initialize the buffer, with an initial size. + + Return 0 on success + Return -1 on failure +*/ +static UNUSED_ATTR int +_BlocksOutputBuffer_InitWithSize(_BlocksOutputBuffer *buffer, Py_ssize_t init_size, + BOB_BUFFER_TYPE **next_out, BOB_SIZE_TYPE *avail_out) +{ + PyObject *b; + + // check block size limit + if (init_size > BOB_SIZE_MAX) { + buffer->list = NULL; // for _BlocksOutputBuffer_OnError() + + PyErr_SetString(PyExc_ValueError, "Initial buffer size is too large."); + return -1; + } + + // the first block + b = PyBytes_FromStringAndSize(NULL, init_size); + if (b == NULL) { + buffer->list = NULL; // for OutputBuffer_OnError() + + PyErr_SetString(PyExc_MemoryError, unable_allocate_msg); + return -1; + } + + // create the list + buffer->list = PyList_New(1); + if (buffer->list == NULL) { + Py_DECREF(b); + return -1; + } + PyList_SET_ITEM(buffer->list, 0, b); + + // set variables + buffer->allocated = init_size; + buffer->max_length = -1; + + *next_out = (BOB_BUFFER_TYPE*) PyBytes_AS_STRING(b); + *avail_out = (BOB_SIZE_TYPE) init_size; + return 0; +} + +/* Grow the buffer. The avail_out must be 0, please check it before calling. + + Return 0 on success + Return -1 on failure +*/ +static int +_BlocksOutputBuffer_Grow(_BlocksOutputBuffer *buffer, + BOB_BUFFER_TYPE **next_out, BOB_SIZE_TYPE *avail_out) +{ + PyObject *b; + const Py_ssize_t list_len = Py_SIZE(buffer->list); + int block_size; + + // ensure no gaps in the data + assert(*avail_out == 0); + + // get block size + if (list_len < (Py_ssize_t) Py_ARRAY_LENGTH(BUFFER_BLOCK_SIZE)) { + block_size = BUFFER_BLOCK_SIZE[list_len]; + } else { + block_size = BUFFER_BLOCK_SIZE[Py_ARRAY_LENGTH(BUFFER_BLOCK_SIZE) - 1]; + } + + // check max_length + if (buffer->max_length >= 0) { + // If (rest == 0), should not grow the buffer. + Py_ssize_t rest = buffer->max_length - buffer->allocated; + assert(rest > 0); + + // block_size of the last block + if (block_size > rest) { + block_size = (int) rest; + } + } + + // check buffer->allocated overflow + if (block_size > PY_SSIZE_T_MAX - buffer->allocated) { + PyErr_SetString(PyExc_MemoryError, unable_allocate_msg); + return -1; + } + + // create the block + b = PyBytes_FromStringAndSize(NULL, block_size); + if (b == NULL) { + PyErr_SetString(PyExc_MemoryError, unable_allocate_msg); + return -1; + } + if (PyList_Append(buffer->list, b) < 0) { + Py_DECREF(b); + return -1; + } + Py_DECREF(b); + + // set variables + buffer->allocated += block_size; + + *next_out = (BOB_BUFFER_TYPE*) PyBytes_AS_STRING(b); + *avail_out = block_size; + return 0; +} + +/* Return the current outputted data size. */ +static inline Py_ssize_t +_BlocksOutputBuffer_GetDataSize(_BlocksOutputBuffer *buffer, BOB_SIZE_TYPE avail_out) +{ + return buffer->allocated - avail_out; +} + +/* Finish the buffer. + After it succeeds, don't call OutputBuffer_OnError() function. + + Return a bytes object on success + Return NULL on failure +*/ +static PyObject * +_BlocksOutputBuffer_Finish(_BlocksOutputBuffer *buffer, BOB_SIZE_TYPE avail_out) +{ + PyObject *result, *block; + const Py_ssize_t list_len = Py_SIZE(buffer->list); + + // fast path for single block + if ( (list_len == 1 && avail_out == 0) || + (list_len == 2 && Py_SIZE(PyList_GET_ITEM(buffer->list, 1)) == (Py_ssize_t) avail_out)) { + block = PyList_GET_ITEM(buffer->list, 0); + Py_INCREF(block); + + Py_DECREF(buffer->list); + return block; + } + + // final bytes object + result = PyBytes_FromStringAndSize(NULL, buffer->allocated - avail_out); + if (result == NULL) { + PyErr_SetString(PyExc_MemoryError, unable_allocate_msg); + return NULL; + } + + // memory copy + if (list_len > 0) { + char *posi = PyBytes_AS_STRING(result); + + // blocks except the last one + Py_ssize_t i = 0; + for (; i < list_len-1; i++) { + block = PyList_GET_ITEM(buffer->list, i); + memcpy(posi, PyBytes_AS_STRING(block), Py_SIZE(block)); + posi += Py_SIZE(block); + } + // the last block + block = PyList_GET_ITEM(buffer->list, i); + memcpy(posi, PyBytes_AS_STRING(block), Py_SIZE(block) - avail_out); + } else { + assert(Py_SIZE(result) == 0); + } + + Py_DECREF(buffer->list); + return result; +} + +/* Clean up the buffer when an error occurred. + Don't call this function after OutputBuffer_Finish() function succeeds. */ +static inline void +_BlocksOutputBuffer_OnError(_BlocksOutputBuffer *buffer) +{ + Py_XDECREF(buffer->list); +} + +#undef BOB_BUFFER_TYPE +#undef BOB_SIZE_TYPE +#undef BOB_SIZE_MAX From 8b6dd9a685b29e06bd343b8bac9289d764813bea Mon Sep 17 00:00:00 2001 From: animalize Date: Fri, 16 Oct 2020 16:02:23 +0800 Subject: [PATCH 02/16] 2. lzma module --- Modules/_lzmamodule.c | 97 +++++++++++++++---------------------------- 1 file changed, 34 insertions(+), 63 deletions(-) diff --git a/Modules/_lzmamodule.c b/Modules/_lzmamodule.c index b01f6300098136..bd6978793b2ebc 100644 --- a/Modules/_lzmamodule.c +++ b/Modules/_lzmamodule.c @@ -15,6 +15,13 @@ #include +// _BlocksOutputBuffer +#define BOB_BUFFER_TYPE uint8_t // type of next_out pointer +#define BOB_SIZE_TYPE size_t // type of avail_out +#include "blocks_output_buffer.h" +#define OutputBuffer(F) _BlocksOutputBuffer_##F + + #define ACQUIRE_LOCK(obj) do { \ if (!PyThread_acquire_lock((obj)->lock, 0)) { \ Py_BEGIN_ALLOW_THREADS \ @@ -128,25 +135,6 @@ PyLzma_Free(void *opaque, void *ptr) PyMem_RawFree(ptr); } -#if BUFSIZ < 8192 -#define INITIAL_BUFFER_SIZE 8192 -#else -#define INITIAL_BUFFER_SIZE BUFSIZ -#endif - -static int -grow_buffer(PyObject **buf, Py_ssize_t max_length) -{ - Py_ssize_t size = PyBytes_GET_SIZE(*buf); - Py_ssize_t newsize = size + (size >> 3) + 6; - - if (max_length > 0 && newsize > max_length) { - newsize = max_length; - } - - return _PyBytes_Resize(buf, newsize); -} - /* Some custom type conversions for PyArg_ParseTupleAndKeywords(), since the predefined conversion specifiers do not suit our needs: @@ -510,29 +498,27 @@ class lzma_filter_converter(CConverter): static PyObject * compress(Compressor *c, uint8_t *data, size_t len, lzma_action action) { - Py_ssize_t data_size = 0; PyObject *result; + _BlocksOutputBuffer buffer; _lzma_state *state = PyType_GetModuleState(Py_TYPE(c)); assert(state != NULL); - result = PyBytes_FromStringAndSize(NULL, INITIAL_BUFFER_SIZE); - if (result == NULL) { - return NULL; + if (OutputBuffer(InitAndGrow)(&buffer, -1, &c->lzs.next_out, &c->lzs.avail_out) < 0) { + goto error; } c->lzs.next_in = data; c->lzs.avail_in = len; - c->lzs.next_out = (uint8_t *)PyBytes_AS_STRING(result); - c->lzs.avail_out = PyBytes_GET_SIZE(result); + for (;;) { lzma_ret lzret; Py_BEGIN_ALLOW_THREADS lzret = lzma_code(&c->lzs, action); - data_size = (char *)c->lzs.next_out - PyBytes_AS_STRING(result); + Py_END_ALLOW_THREADS + if (lzret == LZMA_BUF_ERROR && len == 0 && c->lzs.avail_out > 0) { lzret = LZMA_OK; /* That wasn't a real error */ } - Py_END_ALLOW_THREADS if (catch_lzma_error(state, lzret)) { goto error; } @@ -540,20 +526,19 @@ compress(Compressor *c, uint8_t *data, size_t len, lzma_action action) (action == LZMA_FINISH && lzret == LZMA_STREAM_END)) { break; } else if (c->lzs.avail_out == 0) { - if (grow_buffer(&result, -1) == -1) + if (OutputBuffer(Grow)(&buffer, &c->lzs.next_out, &c->lzs.avail_out) < 0) { goto error; - c->lzs.next_out = (uint8_t *)PyBytes_AS_STRING(result) + data_size; - c->lzs.avail_out = PyBytes_GET_SIZE(result) - data_size; + } } } - if (data_size != PyBytes_GET_SIZE(result)) - if (_PyBytes_Resize(&result, data_size) == -1) { - goto error; - } - return result; + + result = OutputBuffer(Finish)(&buffer, c->lzs.avail_out); + if (result != NULL) { + return result; + } error: - Py_XDECREF(result); + OutputBuffer(OnError)(&buffer); return NULL; } @@ -896,36 +881,26 @@ static PyType_Spec lzma_compressor_type_spec = { static PyObject* decompress_buf(Decompressor *d, Py_ssize_t max_length) { - Py_ssize_t data_size = 0; PyObject *result; lzma_stream *lzs = &d->lzs; + _BlocksOutputBuffer buffer; _lzma_state *state = PyType_GetModuleState(Py_TYPE(d)); assert(state != NULL); - if (max_length < 0 || max_length >= INITIAL_BUFFER_SIZE) { - result = PyBytes_FromStringAndSize(NULL, INITIAL_BUFFER_SIZE); - } - else { - result = PyBytes_FromStringAndSize(NULL, max_length); - } - if (result == NULL) { - return NULL; + if (OutputBuffer(InitAndGrow)(&buffer, max_length, &lzs->next_out, &lzs->avail_out) < 0) { + goto error; } - lzs->next_out = (uint8_t *)PyBytes_AS_STRING(result); - lzs->avail_out = PyBytes_GET_SIZE(result); - for (;;) { lzma_ret lzret; Py_BEGIN_ALLOW_THREADS lzret = lzma_code(lzs, LZMA_RUN); - data_size = (char *)lzs->next_out - PyBytes_AS_STRING(result); + Py_END_ALLOW_THREADS + if (lzret == LZMA_BUF_ERROR && lzs->avail_in == 0 && lzs->avail_out > 0) { lzret = LZMA_OK; /* That wasn't a real error */ } - Py_END_ALLOW_THREADS - if (catch_lzma_error(state, lzret)) { goto error; } @@ -940,28 +915,24 @@ decompress_buf(Decompressor *d, Py_ssize_t max_length) Maybe lzs's internal state still have a few bytes can be output, grow the output buffer and continue if max_lengh < 0. */ - if (data_size == max_length) { + if (OutputBuffer(GetDataSize)(&buffer, lzs->avail_out) == max_length) { break; } - if (grow_buffer(&result, max_length) == -1) { + if (OutputBuffer(Grow)(&buffer, &lzs->next_out, &lzs->avail_out) < 0) { goto error; } - lzs->next_out = (uint8_t *)PyBytes_AS_STRING(result) + data_size; - lzs->avail_out = PyBytes_GET_SIZE(result) - data_size; } else if (lzs->avail_in == 0) { break; } } - if (data_size != PyBytes_GET_SIZE(result)) { - if (_PyBytes_Resize(&result, data_size) == -1) { - goto error; - } - } - return result; + result = OutputBuffer(Finish)(&buffer, lzs->avail_out); + if (result != NULL) { + return result; + } error: - Py_XDECREF(result); + OutputBuffer(OnError)(&buffer); return NULL; } @@ -1042,7 +1013,7 @@ decompress(Decompressor *d, uint8_t *data, size_t len, Py_ssize_t max_length) be output, try to output them next time. */ d->needs_input = 0; - /* if max_length < 0, lzs->avail_out always > 0 */ + /* If max_length < 0, lzs->avail_out always > 0 */ assert(max_length >= 0); } else { /* Input buffer exhausted, output buffer has space. */ From 65f227bc3c412dfd1fc9c5f95b9a5554a6c7ade1 Mon Sep 17 00:00:00 2001 From: animalize Date: Fri, 16 Oct 2020 16:08:38 +0800 Subject: [PATCH 03/16] 3. bz2 module --- Modules/_bz2module.c | 107 ++++++++++++++----------------------------- 1 file changed, 35 insertions(+), 72 deletions(-) diff --git a/Modules/_bz2module.c b/Modules/_bz2module.c index bfcdac692461f1..dbc4cc8330c1ea 100644 --- a/Modules/_bz2module.c +++ b/Modules/_bz2module.c @@ -8,6 +8,12 @@ #include #include +// _BlocksOutputBuffer +#define BOB_BUFFER_TYPE char // type of next_out pointer +#define BOB_SIZE_TYPE uint32_t // type of avail_out +#include "blocks_output_buffer.h" +#define OutputBuffer(F) _BlocksOutputBuffer_##F + #ifndef BZ_CONFIG_ERROR #define BZ2_bzCompress bzCompress @@ -115,52 +121,22 @@ catch_bz2_error(int bzerror) } } -#if BUFSIZ < 8192 -#define INITIAL_BUFFER_SIZE 8192 -#else -#define INITIAL_BUFFER_SIZE BUFSIZ -#endif - -static int -grow_buffer(PyObject **buf, Py_ssize_t max_length) -{ - /* Expand the buffer by an amount proportional to the current size, - giving us amortized linear-time behavior. Use a less-than-double - growth factor to avoid excessive allocation. */ - size_t size = PyBytes_GET_SIZE(*buf); - size_t new_size = size + (size >> 3) + 6; - - if (max_length > 0 && new_size > (size_t) max_length) - new_size = (size_t) max_length; - - if (new_size > size) { - return _PyBytes_Resize(buf, new_size); - } else { /* overflow */ - PyErr_SetString(PyExc_OverflowError, - "Unable to allocate buffer - output too large"); - return -1; - } -} - /* BZ2Compressor class. */ static PyObject * compress(BZ2Compressor *c, char *data, size_t len, int action) { - size_t data_size = 0; PyObject *result; + _BlocksOutputBuffer buffer; - result = PyBytes_FromStringAndSize(NULL, INITIAL_BUFFER_SIZE); - if (result == NULL) - return NULL; - + if (OutputBuffer(InitAndGrow)(&buffer, -1, &c->bzs.next_out, &c->bzs.avail_out) < 0) { + goto error; + } c->bzs.next_in = data; c->bzs.avail_in = 0; - c->bzs.next_out = PyBytes_AS_STRING(result); - c->bzs.avail_out = INITIAL_BUFFER_SIZE; + for (;;) { - char *this_out; int bzerror; /* On a 64-bit system, len might not fit in avail_in (an unsigned int). @@ -175,21 +151,15 @@ compress(BZ2Compressor *c, char *data, size_t len, int action) break; if (c->bzs.avail_out == 0) { - size_t buffer_left = PyBytes_GET_SIZE(result) - data_size; - if (buffer_left == 0) { - if (grow_buffer(&result, -1) < 0) - goto error; - c->bzs.next_out = PyBytes_AS_STRING(result) + data_size; - buffer_left = PyBytes_GET_SIZE(result) - data_size; + if (OutputBuffer(Grow)(&buffer, &c->bzs.next_out, &c->bzs.avail_out) < 0) { + goto error; } - c->bzs.avail_out = (unsigned int)Py_MIN(buffer_left, UINT_MAX); } Py_BEGIN_ALLOW_THREADS - this_out = c->bzs.next_out; bzerror = BZ2_bzCompress(&c->bzs, action); - data_size += c->bzs.next_out - this_out; Py_END_ALLOW_THREADS + if (catch_bz2_error(bzerror)) goto error; @@ -197,13 +167,14 @@ compress(BZ2Compressor *c, char *data, size_t len, int action) if (action == BZ_FINISH && bzerror == BZ_STREAM_END) break; } - if (data_size != (size_t)PyBytes_GET_SIZE(result)) - if (_PyBytes_Resize(&result, data_size) < 0) - goto error; - return result; + + result = OutputBuffer(Finish)(&buffer, c->bzs.avail_out); + if (result != NULL) { + return result; + } error: - Py_XDECREF(result); + OutputBuffer(OnError)(&buffer); return NULL; } @@ -420,36 +391,29 @@ decompress_buf(BZ2Decompressor *d, Py_ssize_t max_length) /* data_size is strictly positive, but because we repeatedly have to compare against max_length and PyBytes_GET_SIZE we declare it as signed */ - Py_ssize_t data_size = 0; PyObject *result; + _BlocksOutputBuffer buffer; bz_stream *bzs = &d->bzs; - if (max_length < 0 || max_length >= INITIAL_BUFFER_SIZE) - result = PyBytes_FromStringAndSize(NULL, INITIAL_BUFFER_SIZE); - else - result = PyBytes_FromStringAndSize(NULL, max_length); - if (result == NULL) - return NULL; + if (OutputBuffer(InitAndGrow)(&buffer, max_length, &bzs->next_out, &bzs->avail_out) < 0) { + goto error; + } - bzs->next_out = PyBytes_AS_STRING(result); for (;;) { int bzret; - size_t avail; - /* On a 64-bit system, buffer length might not fit in avail_out, so we do decompression in chunks of no more than UINT_MAX bytes each. Note that the expression for `avail` is guaranteed to be positive, so the cast is safe. */ - avail = (size_t) (PyBytes_GET_SIZE(result) - data_size); - bzs->avail_out = (unsigned int)Py_MIN(avail, UINT_MAX); bzs->avail_in = (unsigned int)Py_MIN(d->bzs_avail_in_real, UINT_MAX); d->bzs_avail_in_real -= bzs->avail_in; Py_BEGIN_ALLOW_THREADS bzret = BZ2_bzDecompress(bzs); - data_size = bzs->next_out - PyBytes_AS_STRING(result); - d->bzs_avail_in_real += bzs->avail_in; Py_END_ALLOW_THREADS + + d->bzs_avail_in_real += bzs->avail_in; + if (catch_bz2_error(bzret)) goto error; if (bzret == BZ_STREAM_END) { @@ -458,22 +422,21 @@ decompress_buf(BZ2Decompressor *d, Py_ssize_t max_length) } else if (d->bzs_avail_in_real == 0) { break; } else if (bzs->avail_out == 0) { - if (data_size == max_length) + if (OutputBuffer(GetDataSize)(&buffer, bzs->avail_out) == max_length) break; - if (data_size == PyBytes_GET_SIZE(result) && - grow_buffer(&result, max_length) == -1) + if (OutputBuffer(Grow)(&buffer, &bzs->next_out, &bzs->avail_out) < 0) { goto error; - bzs->next_out = PyBytes_AS_STRING(result) + data_size; + } } } - if (data_size != PyBytes_GET_SIZE(result)) - if (_PyBytes_Resize(&result, data_size) == -1) - goto error; - return result; + result = OutputBuffer(Finish)(&buffer, bzs->avail_out); + if (result != NULL) { + return result; + } error: - Py_XDECREF(result); + OutputBuffer(OnError)(&buffer); return NULL; } From db8d0f2c21b8f136b1a5ad3af4eaaf08a18d0bd8 Mon Sep 17 00:00:00 2001 From: animalize Date: Fri, 16 Oct 2020 16:10:48 +0800 Subject: [PATCH 04/16] 4. zlib: #include --- Modules/zlibmodule.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Modules/zlibmodule.c b/Modules/zlibmodule.c index a537087d19d835..86306d6c1876cf 100644 --- a/Modules/zlibmodule.c +++ b/Modules/zlibmodule.c @@ -9,6 +9,12 @@ #include "structmember.h" // PyMemberDef #include "zlib.h" +// _BlocksOutputBuffer +#define BOB_BUFFER_TYPE Bytef // type of next_out pointer +#define BOB_SIZE_TYPE uint32_t // type of avail_out +#include "blocks_output_buffer.h" +#define OutputBuffer(F) _BlocksOutputBuffer_##F + #define ENTER_ZLIB(obj) \ Py_BEGIN_ALLOW_THREADS; \ From de3cfb26fa5828f4b275e2d89a0bd5c22099c9b2 Mon Sep 17 00:00:00 2001 From: animalize Date: Fri, 16 Oct 2020 16:14:58 +0800 Subject: [PATCH 05/16] 5. zlib: zlib_compress_impl() --- Modules/zlibmodule.c | 26 +++++++++++++++++--------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/Modules/zlibmodule.c b/Modules/zlibmodule.c index 86306d6c1876cf..63db517b2cfd8d 100644 --- a/Modules/zlibmodule.c +++ b/Modules/zlibmodule.c @@ -219,16 +219,22 @@ static PyObject * zlib_compress_impl(PyObject *module, Py_buffer *data, int level) /*[clinic end generated code: output=d80906d73f6294c8 input=638d54b6315dbed3]*/ { - PyObject *RetVal = NULL; - Py_ssize_t obuflen = DEF_BUF_SIZE; + PyObject *RetVal; int flush; z_stream zst; + _BlocksOutputBuffer buffer; zlibstate *state = get_zlib_state(module); Byte *ibuf = data->buf; Py_ssize_t ibuflen = data->len; + // OutputBuffer(OnError)(&buffer) is after `error` label, + // so initialize the buffer before any `goto error` statement. + if (OutputBuffer(InitAndGrow)(&buffer, -1, &zst.next_out, &zst.avail_out) < 0) { + goto error; + } + zst.opaque = NULL; zst.zalloc = PyZlib_Malloc; zst.zfree = PyZlib_Free; @@ -256,10 +262,11 @@ zlib_compress_impl(PyObject *module, Py_buffer *data, int level) flush = ibuflen == 0 ? Z_FINISH : Z_NO_FLUSH; do { - obuflen = arrange_output_buffer(&zst, &RetVal, obuflen); - if (obuflen < 0) { - deflateEnd(&zst); - goto error; + if (zst.avail_out == 0) { + if (OutputBuffer(Grow)(&buffer, &zst.next_out, &zst.avail_out) < 0) { + deflateEnd(&zst); + goto error; + } } Py_BEGIN_ALLOW_THREADS @@ -280,15 +287,16 @@ zlib_compress_impl(PyObject *module, Py_buffer *data, int level) err = deflateEnd(&zst); if (err == Z_OK) { - if (_PyBytes_Resize(&RetVal, zst.next_out - - (Byte *)PyBytes_AS_STRING(RetVal)) < 0) + RetVal = OutputBuffer(Finish)(&buffer, zst.avail_out); + if (RetVal == NULL) { goto error; + } return RetVal; } else zlib_error(state, zst, err, "while finishing compression"); error: - Py_XDECREF(RetVal); + OutputBuffer(OnError)(&buffer); return NULL; } From dbb750db947780a1c74dab82e82b5e41ba050387 Mon Sep 17 00:00:00 2001 From: animalize Date: Fri, 16 Oct 2020 16:21:34 +0800 Subject: [PATCH 06/16] 6. zlib_decompress_impl --- Modules/zlibmodule.c | 29 ++++++++++++++++++----------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/Modules/zlibmodule.c b/Modules/zlibmodule.c index 63db517b2cfd8d..40af7d37bda6f7 100644 --- a/Modules/zlibmodule.c +++ b/Modules/zlibmodule.c @@ -319,11 +319,12 @@ zlib_decompress_impl(PyObject *module, Py_buffer *data, int wbits, Py_ssize_t bufsize) /*[clinic end generated code: output=77c7e35111dc8c42 input=a9ac17beff1f893f]*/ { - PyObject *RetVal = NULL; + PyObject *RetVal; Byte *ibuf; Py_ssize_t ibuflen; int err, flush; z_stream zst; + _BlocksOutputBuffer buffer; zlibstate *state = get_zlib_state(module); @@ -334,6 +335,12 @@ zlib_decompress_impl(PyObject *module, Py_buffer *data, int wbits, bufsize = 1; } + // OutputBuffer(OnError)(&buffer) is after `error` label, + // so initialize the buffer before any `goto error` statement. + if (OutputBuffer(InitWithSize)(&buffer, bufsize, &zst.next_out, &zst.avail_out) < 0) { + goto error; + } + ibuf = data->buf; ibuflen = data->len; @@ -362,10 +369,11 @@ zlib_decompress_impl(PyObject *module, Py_buffer *data, int wbits, flush = ibuflen == 0 ? Z_FINISH : Z_NO_FLUSH; do { - bufsize = arrange_output_buffer(&zst, &RetVal, bufsize); - if (bufsize < 0) { - inflateEnd(&zst); - goto error; + if (zst.avail_out == 0) { + if (OutputBuffer(Grow)(&buffer, &zst.next_out, &zst.avail_out) < 0) { + inflateEnd(&zst); + goto error; + } } Py_BEGIN_ALLOW_THREADS @@ -405,14 +413,13 @@ zlib_decompress_impl(PyObject *module, Py_buffer *data, int wbits, goto error; } - if (_PyBytes_Resize(&RetVal, zst.next_out - - (Byte *)PyBytes_AS_STRING(RetVal)) < 0) - goto error; - - return RetVal; + RetVal = OutputBuffer(Finish)(&buffer, zst.avail_out); + if (RetVal != NULL) { + return RetVal; + } error: - Py_XDECREF(RetVal); + OutputBuffer(OnError)(&buffer); return NULL; } From 262116f404612337e18d637c7ff39a93d1778083 Mon Sep 17 00:00:00 2001 From: animalize Date: Fri, 16 Oct 2020 16:39:12 +0800 Subject: [PATCH 07/16] 7. zlib: zlib_Compress_compress_impl() --- Modules/zlibmodule.c | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/Modules/zlibmodule.c b/Modules/zlibmodule.c index 40af7d37bda6f7..c06e0a36776dd6 100644 --- a/Modules/zlibmodule.c +++ b/Modules/zlibmodule.c @@ -652,9 +652,9 @@ zlib_Compress_compress_impl(compobject *self, PyTypeObject *cls, Py_buffer *data) /*[clinic end generated code: output=6731b3f0ff357ca6 input=04d00f65ab01d260]*/ { - PyObject *RetVal = NULL; - Py_ssize_t obuflen = DEF_BUF_SIZE; + PyObject *RetVal; int err; + _BlocksOutputBuffer buffer; zlibstate *state = PyType_GetModuleState(cls); @@ -663,13 +663,18 @@ zlib_Compress_compress_impl(compobject *self, PyTypeObject *cls, ENTER_ZLIB(self); + if (OutputBuffer(InitAndGrow)(&buffer, -1, &self->zst.next_out, &self->zst.avail_out) < 0) { + goto error; + } + do { arrange_input_buffer(&self->zst, &ibuflen); do { - obuflen = arrange_output_buffer(&self->zst, &RetVal, obuflen); - if (obuflen < 0) - goto error; + if (self->zst.avail_out == 0) { + if (OutputBuffer(Grow)(&buffer, &self->zst.next_out, &self->zst.avail_out) < 0) + goto error; + } Py_BEGIN_ALLOW_THREADS err = deflate(&self->zst, Z_NO_FLUSH); @@ -685,12 +690,14 @@ zlib_Compress_compress_impl(compobject *self, PyTypeObject *cls, } while (ibuflen != 0); - if (_PyBytes_Resize(&RetVal, self->zst.next_out - - (Byte *)PyBytes_AS_STRING(RetVal)) == 0) + RetVal = OutputBuffer(Finish)(&buffer, self->zst.avail_out); + if (RetVal != NULL) { goto success; + } error: - Py_CLEAR(RetVal); + OutputBuffer(OnError)(&buffer); + RetVal = NULL; success: LEAVE_ZLIB(self); return RetVal; From 06bac139977bad0131f76c2161ffc941a9493fdd Mon Sep 17 00:00:00 2001 From: animalize Date: Fri, 16 Oct 2020 16:42:07 +0800 Subject: [PATCH 08/16] 8. zlib: zlib_Compress_flush_impl() --- Modules/zlibmodule.c | 31 ++++++++++++++++++------------- 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/Modules/zlibmodule.c b/Modules/zlibmodule.c index c06e0a36776dd6..e18a1d07b37c61 100644 --- a/Modules/zlibmodule.c +++ b/Modules/zlibmodule.c @@ -885,8 +885,8 @@ zlib_Compress_flush_impl(compobject *self, PyTypeObject *cls, int mode) /*[clinic end generated code: output=c7efd13efd62add2 input=286146e29442eb6c]*/ { int err; - Py_ssize_t length = DEF_BUF_SIZE; - PyObject *RetVal = NULL; + PyObject *RetVal; + _BlocksOutputBuffer buffer; zlibstate *state = PyType_GetModuleState(cls); /* Flushing with Z_NO_FLUSH is a no-op, so there's no point in @@ -899,11 +899,15 @@ zlib_Compress_flush_impl(compobject *self, PyTypeObject *cls, int mode) self->zst.avail_in = 0; + if (OutputBuffer(InitAndGrow)(&buffer, -1, &self->zst.next_out, &self->zst.avail_out) < 0) { + goto error; + } + do { - length = arrange_output_buffer(&self->zst, &RetVal, length); - if (length < 0) { - Py_CLEAR(RetVal); - goto error; + if (self->zst.avail_out == 0) { + if (OutputBuffer(Grow)(&buffer, &self->zst.next_out, &self->zst.avail_out) < 0) { + goto error; + } } Py_BEGIN_ALLOW_THREADS @@ -912,7 +916,6 @@ zlib_Compress_flush_impl(compobject *self, PyTypeObject *cls, int mode) if (err == Z_STREAM_ERROR) { zlib_error(state, self->zst, err, "while flushing"); - Py_CLEAR(RetVal); goto error; } } while (self->zst.avail_out == 0); @@ -925,7 +928,6 @@ zlib_Compress_flush_impl(compobject *self, PyTypeObject *cls, int mode) err = deflateEnd(&self->zst); if (err != Z_OK) { zlib_error(state, self->zst, err, "while finishing compression"); - Py_CLEAR(RetVal); goto error; } else @@ -937,15 +939,18 @@ zlib_Compress_flush_impl(compobject *self, PyTypeObject *cls, int mode) */ } else if (err != Z_OK && err != Z_BUF_ERROR) { zlib_error(state, self->zst, err, "while flushing"); - Py_CLEAR(RetVal); goto error; } - if (_PyBytes_Resize(&RetVal, self->zst.next_out - - (Byte *)PyBytes_AS_STRING(RetVal)) < 0) - Py_CLEAR(RetVal); + RetVal = OutputBuffer(Finish)(&buffer, self->zst.avail_out); + if (RetVal != NULL) { + goto success; + } - error: +error: + OutputBuffer(OnError)(&buffer); + RetVal = NULL; +success: LEAVE_ZLIB(self); return RetVal; } From 1d24d9b68e67f87e6767519faa999383f30f2bf7 Mon Sep 17 00:00:00 2001 From: animalize Date: Fri, 16 Oct 2020 16:47:47 +0800 Subject: [PATCH 09/16] 9. zlib: zlib_Decompress_decompress_impl() --- Modules/zlibmodule.c | 38 ++++++++++++++++++-------------------- 1 file changed, 18 insertions(+), 20 deletions(-) diff --git a/Modules/zlibmodule.c b/Modules/zlibmodule.c index e18a1d07b37c61..baa5b9dc4c78da 100644 --- a/Modules/zlibmodule.c +++ b/Modules/zlibmodule.c @@ -773,8 +773,9 @@ zlib_Decompress_decompress_impl(compobject *self, PyTypeObject *cls, /*[clinic end generated code: output=b024a93c2c922d57 input=bfb37b3864cfb606]*/ { int err = Z_OK; - Py_ssize_t ibuflen, obuflen = DEF_BUF_SIZE, hard_limit; - PyObject *RetVal = NULL; + Py_ssize_t ibuflen; + PyObject *RetVal; + _BlocksOutputBuffer buffer; PyObject *module = PyType_GetModule(cls); if (module == NULL) @@ -785,33 +786,28 @@ zlib_Decompress_decompress_impl(compobject *self, PyTypeObject *cls, PyErr_SetString(PyExc_ValueError, "max_length must be non-negative"); return NULL; } else if (max_length == 0) - hard_limit = PY_SSIZE_T_MAX; - else - hard_limit = max_length; + max_length = -1; self->zst.next_in = data->buf; ibuflen = data->len; - /* limit amount of data allocated to max_length */ - if (max_length && obuflen > max_length) - obuflen = max_length; - ENTER_ZLIB(self); + if (OutputBuffer(InitAndGrow)(&buffer, max_length, &self->zst.next_out, &self->zst.avail_out) < 0) { + goto abort; + } + do { arrange_input_buffer(&self->zst, &ibuflen); do { - obuflen = arrange_output_buffer_with_maximum(&self->zst, &RetVal, - obuflen, hard_limit); - if (obuflen == -2) { - if (max_length > 0) { + if (self->zst.avail_out == 0) { + if (OutputBuffer(GetDataSize)(&buffer, self->zst.avail_out) == max_length) { goto save; } - PyErr_NoMemory(); - } - if (obuflen < 0) { - goto abort; + if (OutputBuffer(Grow)(&buffer, &self->zst.next_out, &self->zst.avail_out) < 0) { + goto abort; + } } Py_BEGIN_ALLOW_THREADS @@ -855,12 +851,14 @@ zlib_Decompress_decompress_impl(compobject *self, PyTypeObject *cls, goto abort; } - if (_PyBytes_Resize(&RetVal, self->zst.next_out - - (Byte *)PyBytes_AS_STRING(RetVal)) == 0) + RetVal = OutputBuffer(Finish)(&buffer, self->zst.avail_out); + if (RetVal != NULL) { goto success; + } abort: - Py_CLEAR(RetVal); + OutputBuffer(OnError)(&buffer); + RetVal = NULL; success: LEAVE_ZLIB(self); return RetVal; From d73c83097055a894445aa136115f956eb0b49008 Mon Sep 17 00:00:00 2001 From: animalize Date: Fri, 16 Oct 2020 17:01:03 +0800 Subject: [PATCH 10/16] 10. zlib: zlib_Decompress_flush_impl() --- Modules/zlibmodule.c | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/Modules/zlibmodule.c b/Modules/zlibmodule.c index baa5b9dc4c78da..cc77f9ce9adbbd 100644 --- a/Modules/zlibmodule.c +++ b/Modules/zlibmodule.c @@ -1150,8 +1150,9 @@ zlib_Decompress_flush_impl(compobject *self, PyTypeObject *cls, { int err, flush; Py_buffer data; - PyObject *RetVal = NULL; + PyObject *RetVal; Py_ssize_t ibuflen; + _BlocksOutputBuffer buffer; PyObject *module = PyType_GetModule(cls); if (module == NULL) { @@ -1174,14 +1175,19 @@ zlib_Decompress_flush_impl(compobject *self, PyTypeObject *cls, self->zst.next_in = data.buf; ibuflen = data.len; + if (OutputBuffer(InitWithSize)(&buffer, length, &self->zst.next_out, &self->zst.avail_out) < 0) { + goto abort; + } + do { arrange_input_buffer(&self->zst, &ibuflen); flush = ibuflen == 0 ? Z_FINISH : Z_NO_FLUSH; do { - length = arrange_output_buffer(&self->zst, &RetVal, length); - if (length < 0) - goto abort; + if (self->zst.avail_out == 0) { + if (OutputBuffer(Grow)(&buffer, &self->zst.next_out, &self->zst.avail_out) < 0) + goto abort; + } Py_BEGIN_ALLOW_THREADS err = inflate(&self->zst, flush); @@ -1223,13 +1229,14 @@ zlib_Decompress_flush_impl(compobject *self, PyTypeObject *cls, } } - if (_PyBytes_Resize(&RetVal, self->zst.next_out - - (Byte *)PyBytes_AS_STRING(RetVal)) == 0) { + RetVal = OutputBuffer(Finish)(&buffer, self->zst.avail_out); + if (RetVal != NULL) { goto success; } abort: - Py_CLEAR(RetVal); + OutputBuffer(OnError)(&buffer); + RetVal = NULL; success: PyBuffer_Release(&data); LEAVE_ZLIB(self); From e9856470de8cc7d3ced558ba81d2aa8e8e8260f3 Mon Sep 17 00:00:00 2001 From: animalize Date: Fri, 16 Oct 2020 17:03:01 +0800 Subject: [PATCH 11/16] 11. zlib: remove old functions --- Modules/zlibmodule.c | 50 -------------------------------------------- 1 file changed, 50 deletions(-) diff --git a/Modules/zlibmodule.c b/Modules/zlibmodule.c index cc77f9ce9adbbd..f7b7523fb2c6a4 100644 --- a/Modules/zlibmodule.c +++ b/Modules/zlibmodule.c @@ -153,56 +153,6 @@ arrange_input_buffer(z_stream *zst, Py_ssize_t *remains) *remains -= zst->avail_in; } -static Py_ssize_t -arrange_output_buffer_with_maximum(z_stream *zst, PyObject **buffer, - Py_ssize_t length, - Py_ssize_t max_length) -{ - Py_ssize_t occupied; - - if (*buffer == NULL) { - if (!(*buffer = PyBytes_FromStringAndSize(NULL, length))) - return -1; - occupied = 0; - } - else { - occupied = zst->next_out - (Byte *)PyBytes_AS_STRING(*buffer); - - if (length == occupied) { - Py_ssize_t new_length; - assert(length <= max_length); - /* can not scale the buffer over max_length */ - if (length == max_length) - return -2; - if (length <= (max_length >> 1)) - new_length = length << 1; - else - new_length = max_length; - if (_PyBytes_Resize(buffer, new_length) < 0) - return -1; - length = new_length; - } - } - - zst->avail_out = (uInt)Py_MIN((size_t)(length - occupied), UINT_MAX); - zst->next_out = (Byte *)PyBytes_AS_STRING(*buffer) + occupied; - - return length; -} - -static Py_ssize_t -arrange_output_buffer(z_stream *zst, PyObject **buffer, Py_ssize_t length) -{ - Py_ssize_t ret; - - ret = arrange_output_buffer_with_maximum(zst, buffer, length, - PY_SSIZE_T_MAX); - if (ret == -2) - PyErr_NoMemory(); - - return ret; -} - /*[clinic input] zlib.compress From 8ad64f0605efe07d0df522e14692235fb541f114 Mon Sep 17 00:00:00 2001 From: animalize Date: Fri, 16 Oct 2020 17:03:38 +0800 Subject: [PATCH 12/16] 12. add .readall() to DecompressReader --- Lib/_compression.py | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/Lib/_compression.py b/Lib/_compression.py index b00f31b400c3f3..3a10458fe46537 100644 --- a/Lib/_compression.py +++ b/Lib/_compression.py @@ -1,7 +1,7 @@ """Internal classes used by the gzip, lzma and bz2 modules""" import io - +import sys BUFFER_SIZE = io.DEFAULT_BUFFER_SIZE # Compressed data read chunk size @@ -110,6 +110,18 @@ def read(self, size=-1): self._pos += len(data) return data + def readall(self): + chunks = [] + while True: + # sys.maxsize means the max length of output buffer is unlimited, + # so that the whole input buffer can be decompressed within one + # .decompress() call. + data = self.read(sys.maxsize) + if not data: + break + chunks.append(data) + return b''.join(chunks) + # Rewind the file to the beginning of the data stream. def _rewind(self): self._fp.seek(0) From c8f0819b813a006240034ff142600b1562f624bd Mon Sep 17 00:00:00 2001 From: animalize Date: Mon, 12 Apr 2021 09:33:11 +0800 Subject: [PATCH 13/16] address review comments 1 --- Doc/whatsnew/3.10.rst | 2 +- Lib/_compression.py | 14 ++++++-------- Modules/blocks_output_buffer.h | 18 +++++++++++------- 3 files changed, 18 insertions(+), 16 deletions(-) diff --git a/Doc/whatsnew/3.10.rst b/Doc/whatsnew/3.10.rst index 3cb4310a582581..77a2ad39676208 100644 --- a/Doc/whatsnew/3.10.rst +++ b/Doc/whatsnew/3.10.rst @@ -998,7 +998,7 @@ Optimizations :mod:`zlib` modules, and add ``.readall()`` function to ``_compression.DecompressReader`` class. bz2 decompression 1.09x ~ 1.17x faster, lzma decompression 1.20x ~ 1.32x faster, ``GzipFile.read(-1)`` 1.11x - ~ 1.18x faster. (Contributed by Ma Lin in :issue:`41486`) + ~ 1.18x faster. (Contributed by Ma Lin, reviewed by Gregory P. Smith, in :issue:`41486`) * Function parameters and their annotations are no longer computed at runtime, but rather at compilation time. They are stored as a tuple of strings at the diff --git a/Lib/_compression.py b/Lib/_compression.py index 3a10458fe46537..e8b70aa0a3e680 100644 --- a/Lib/_compression.py +++ b/Lib/_compression.py @@ -112,15 +112,13 @@ def read(self, size=-1): def readall(self): chunks = [] - while True: - # sys.maxsize means the max length of output buffer is unlimited, - # so that the whole input buffer can be decompressed within one - # .decompress() call. - data = self.read(sys.maxsize) - if not data: - break + # sys.maxsize means the max length of output buffer is unlimited, + # so that the whole input buffer can be decompressed within one + # .decompress() call. + while data := self.read(sys.maxsize): chunks.append(data) - return b''.join(chunks) + + return b"".join(chunks) # Rewind the file to the beginning of the data stream. def _rewind(self): diff --git a/Modules/blocks_output_buffer.h b/Modules/blocks_output_buffer.h index 746ca0eb5e2cea..62b3dc08fe8728 100644 --- a/Modules/blocks_output_buffer.h +++ b/Modules/blocks_output_buffer.h @@ -54,7 +54,7 @@ typedef struct { #elif BOB_SIZE_TYPE == size_t #define BOB_SIZE_MAX UINTPTR_MAX #else - #error Please define BOB_SIZE_MAX to BOB_SIZE_TYPE's max value + #error "Please define BOB_SIZE_MAX to BOB_SIZE_TYPE's max value" #endif const char unable_allocate_msg[] = "Unable to allocate output buffer."; @@ -226,7 +226,11 @@ _BlocksOutputBuffer_Grow(_BlocksOutputBuffer *buffer, int block_size; // ensure no gaps in the data - assert(*avail_out == 0); + if (*avail_out != 0) { + PyErr_SetString(PyExc_SystemError, + "*avail_out is non-zero in _BlocksOutputBuffer_Grow()."); + return -1; + } // get block size if (list_len < (Py_ssize_t) Py_ARRAY_LENGTH(BUFFER_BLOCK_SIZE)) { @@ -281,7 +285,6 @@ _BlocksOutputBuffer_GetDataSize(_BlocksOutputBuffer *buffer, BOB_SIZE_TYPE avail } /* Finish the buffer. - After it succeeds, don't call OutputBuffer_OnError() function. Return a bytes object on success Return NULL on failure @@ -294,11 +297,12 @@ _BlocksOutputBuffer_Finish(_BlocksOutputBuffer *buffer, BOB_SIZE_TYPE avail_out) // fast path for single block if ( (list_len == 1 && avail_out == 0) || - (list_len == 2 && Py_SIZE(PyList_GET_ITEM(buffer->list, 1)) == (Py_ssize_t) avail_out)) { + (list_len == 2 && Py_SIZE(PyList_GET_ITEM(buffer->list, 1)) == (Py_ssize_t) avail_out)) + { block = PyList_GET_ITEM(buffer->list, 0); Py_INCREF(block); - Py_DECREF(buffer->list); + Py_CLEAR(buffer->list); return block; } @@ -327,7 +331,7 @@ _BlocksOutputBuffer_Finish(_BlocksOutputBuffer *buffer, BOB_SIZE_TYPE avail_out) assert(Py_SIZE(result) == 0); } - Py_DECREF(buffer->list); + Py_CLEAR(buffer->list); return result; } @@ -336,7 +340,7 @@ _BlocksOutputBuffer_Finish(_BlocksOutputBuffer *buffer, BOB_SIZE_TYPE avail_out) static inline void _BlocksOutputBuffer_OnError(_BlocksOutputBuffer *buffer) { - Py_XDECREF(buffer->list); + Py_CLEAR(buffer->list); } #undef BOB_BUFFER_TYPE From 867da952230046540423be455b6c5b8a099950c5 Mon Sep 17 00:00:00 2001 From: animalize Date: Fri, 16 Apr 2021 10:14:58 +0800 Subject: [PATCH 14/16] address review comments 2 --- Modules/blocks_output_buffer.h | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/Modules/blocks_output_buffer.h b/Modules/blocks_output_buffer.h index 62b3dc08fe8728..1073f0c582c8af 100644 --- a/Modules/blocks_output_buffer.h +++ b/Modules/blocks_output_buffer.h @@ -241,7 +241,7 @@ _BlocksOutputBuffer_Grow(_BlocksOutputBuffer *buffer, // check max_length if (buffer->max_length >= 0) { - // If (rest == 0), should not grow the buffer. + // if (rest == 0), should not grow the buffer. Py_ssize_t rest = buffer->max_length - buffer->allocated; assert(rest > 0); @@ -321,7 +321,7 @@ _BlocksOutputBuffer_Finish(_BlocksOutputBuffer *buffer, BOB_SIZE_TYPE avail_out) Py_ssize_t i = 0; for (; i < list_len-1; i++) { block = PyList_GET_ITEM(buffer->list, i); - memcpy(posi, PyBytes_AS_STRING(block), Py_SIZE(block)); + memcpy(posi, PyBytes_AS_STRING(block), Py_SIZE(block)); posi += Py_SIZE(block); } // the last block @@ -335,8 +335,7 @@ _BlocksOutputBuffer_Finish(_BlocksOutputBuffer *buffer, BOB_SIZE_TYPE avail_out) return result; } -/* Clean up the buffer when an error occurred. - Don't call this function after OutputBuffer_Finish() function succeeds. */ +/* Clean up the buffer when an error occurred. */ static inline void _BlocksOutputBuffer_OnError(_BlocksOutputBuffer *buffer) { From 55705f6dc28ff4dc6183e0eb57312c885d19090a Mon Sep 17 00:00:00 2001 From: animalize Date: Sat, 24 Apr 2021 18:55:32 +0800 Subject: [PATCH 15/16] split core code and wrappers --- .../internal/pycore_blocks_output_buffer.h | 161 +++++++++--------- Modules/_bz2module.c | 80 +++++++-- Modules/_lzmamodule.c | 78 +++++++-- Modules/zlibmodule.c | 145 +++++++++++----- 4 files changed, 309 insertions(+), 155 deletions(-) rename Modules/blocks_output_buffer.h => Include/internal/pycore_blocks_output_buffer.h (67%) diff --git a/Modules/blocks_output_buffer.h b/Include/internal/pycore_blocks_output_buffer.h similarity index 67% rename from Modules/blocks_output_buffer.h rename to Include/internal/pycore_blocks_output_buffer.h index 1073f0c582c8af..b775d05b40fb10 100644 --- a/Modules/blocks_output_buffer.h +++ b/Include/internal/pycore_blocks_output_buffer.h @@ -6,15 +6,14 @@ stream->next_out: point to the next output position. stream->avail_out: the number of available bytes left in the buffer. - It maintains a list of bytes object, so there is no overhead - of resizing the buffer. + It maintains a list of bytes object, so there is no overhead of resizing + the buffer. Usage: - 1, Define BOB_BUFFER_TYPE and BOB_SIZE_TYPE, and include this file: - #define BOB_BUFFER_TYPE uint8_t // type of next_out pointer - #define BOB_SIZE_TYPE size_t // type of avail_out - #include "blocks_output_buffer.h" + 1, Initialize the struct instance like this: + _BlocksOutputBuffer buffer = {.list = NULL}; + Set .list to NULL for _BlocksOutputBuffer_OnError() 2, Initialize the buffer use one of these functions: _BlocksOutputBuffer_Init() @@ -34,6 +33,14 @@ _BlocksOutputBuffer_OnError() */ +#ifndef Py_INTERNAL_BLOCKS_OUTPUT_BUFFER_H +#define Py_INTERNAL_BLOCKS_OUTPUT_BUFFER_H +#ifdef __cplusplus +extern "C" { +#endif + +#include "Python.h" + typedef struct { // List of bytes objects PyObject *list; @@ -43,32 +50,18 @@ typedef struct { Py_ssize_t max_length; } _BlocksOutputBuffer; -#if defined(__GNUC__) -# define UNUSED_ATTR __attribute__((unused)) -#else -# define UNUSED_ATTR -#endif +static const char unable_allocate_msg[] = "Unable to allocate output buffer."; -#if BOB_SIZE_TYPE == uint32_t - #define BOB_SIZE_MAX UINT32_MAX -#elif BOB_SIZE_TYPE == size_t - #define BOB_SIZE_MAX UINTPTR_MAX -#else - #error "Please define BOB_SIZE_MAX to BOB_SIZE_TYPE's max value" -#endif - -const char unable_allocate_msg[] = "Unable to allocate output buffer."; +/* In 32-bit build, the max block size should <= INT32_MAX. */ +#define OUTPUT_BUFFER_MAX_BLOCK_SIZE (256*1024*1024) -/* Block size sequence. - In 32-bit build Py_ssize_t is int, so the type is int. Below functions - assume the type is int. -*/ +/* Block size sequence */ #define KB (1024) #define MB (1024*1024) -const int BUFFER_BLOCK_SIZE[] = +const Py_ssize_t BUFFER_BLOCK_SIZE[] = { 32*KB, 64*KB, 256*KB, 1*MB, 4*MB, 8*MB, 16*MB, 16*MB, 32*MB, 32*MB, 32*MB, 32*MB, 64*MB, 64*MB, 128*MB, 128*MB, - 256*MB }; + OUTPUT_BUFFER_MAX_BLOCK_SIZE }; #undef KB #undef MB @@ -103,13 +96,17 @@ const int BUFFER_BLOCK_SIZE[] = max_length: Max length of the buffer, -1 for unlimited length. - Return 0 on success - Return -1 on failure + On success, return allocated size (>=0) + On failure, return -1 */ -static UNUSED_ATTR int -_BlocksOutputBuffer_Init(_BlocksOutputBuffer *buffer, Py_ssize_t max_length, - BOB_BUFFER_TYPE **next_out, BOB_SIZE_TYPE *avail_out) +static inline Py_ssize_t +_BlocksOutputBuffer_Init(_BlocksOutputBuffer *buffer, + const Py_ssize_t max_length, + void **next_out) { + // ensure .list was set to NULL + assert(buffer->list == NULL); + buffer->list = PyList_New(0); if (buffer->list == NULL) { return -1; @@ -117,8 +114,7 @@ _BlocksOutputBuffer_Init(_BlocksOutputBuffer *buffer, Py_ssize_t max_length, buffer->allocated = 0; buffer->max_length = max_length; - *next_out = (BOB_BUFFER_TYPE*) 1; // some libs don't accept NULL - *avail_out = 0; + *next_out = (void*) 1; // some libs don't accept NULL return 0; } @@ -126,20 +122,23 @@ _BlocksOutputBuffer_Init(_BlocksOutputBuffer *buffer, Py_ssize_t max_length, max_length: Max length of the buffer, -1 for unlimited length. - Return 0 on success - Return -1 on failure + On success, return allocated size (>=0) + On failure, return -1 */ -static UNUSED_ATTR int -_BlocksOutputBuffer_InitAndGrow(_BlocksOutputBuffer *buffer, Py_ssize_t max_length, - BOB_BUFFER_TYPE **next_out, BOB_SIZE_TYPE *avail_out) +static inline Py_ssize_t +_BlocksOutputBuffer_InitAndGrow(_BlocksOutputBuffer *buffer, + const Py_ssize_t max_length, + void **next_out) { PyObject *b; - int block_size; + Py_ssize_t block_size; - // set & check max_length - buffer->max_length = max_length; + // ensure .list was set to NULL + assert(buffer->list == NULL); + + // get block size if (0 <= max_length && max_length < BUFFER_BLOCK_SIZE[0]) { - block_size = (int) max_length; + block_size = max_length; } else { block_size = BUFFER_BLOCK_SIZE[0]; } @@ -147,7 +146,6 @@ _BlocksOutputBuffer_InitAndGrow(_BlocksOutputBuffer *buffer, Py_ssize_t max_leng // the first block b = PyBytes_FromStringAndSize(NULL, block_size); if (b == NULL) { - buffer->list = NULL; // for _BlocksOutputBuffer_OnError() return -1; } @@ -161,36 +159,33 @@ _BlocksOutputBuffer_InitAndGrow(_BlocksOutputBuffer *buffer, Py_ssize_t max_leng // set variables buffer->allocated = block_size; + buffer->max_length = max_length; - *next_out = (BOB_BUFFER_TYPE*) PyBytes_AS_STRING(b); - *avail_out = block_size; - return 0; + *next_out = PyBytes_AS_STRING(b); + return block_size; } /* Initialize the buffer, with an initial size. - Return 0 on success - Return -1 on failure + Check block size limit in the outer wrapper function. For example, some libs + accept UINT32_MAX as the maximum block size, then init_size should <= it. + + On success, return allocated size (>=0) + On failure, return -1 */ -static UNUSED_ATTR int -_BlocksOutputBuffer_InitWithSize(_BlocksOutputBuffer *buffer, Py_ssize_t init_size, - BOB_BUFFER_TYPE **next_out, BOB_SIZE_TYPE *avail_out) +static inline Py_ssize_t +_BlocksOutputBuffer_InitWithSize(_BlocksOutputBuffer *buffer, + const Py_ssize_t init_size, + void **next_out) { PyObject *b; - // check block size limit - if (init_size > BOB_SIZE_MAX) { - buffer->list = NULL; // for _BlocksOutputBuffer_OnError() - - PyErr_SetString(PyExc_ValueError, "Initial buffer size is too large."); - return -1; - } + // ensure .list was set to NULL + assert(buffer->list == NULL); // the first block b = PyBytes_FromStringAndSize(NULL, init_size); if (b == NULL) { - buffer->list = NULL; // for OutputBuffer_OnError() - PyErr_SetString(PyExc_MemoryError, unable_allocate_msg); return -1; } @@ -207,28 +202,28 @@ _BlocksOutputBuffer_InitWithSize(_BlocksOutputBuffer *buffer, Py_ssize_t init_si buffer->allocated = init_size; buffer->max_length = -1; - *next_out = (BOB_BUFFER_TYPE*) PyBytes_AS_STRING(b); - *avail_out = (BOB_SIZE_TYPE) init_size; - return 0; + *next_out = PyBytes_AS_STRING(b); + return init_size; } /* Grow the buffer. The avail_out must be 0, please check it before calling. - Return 0 on success - Return -1 on failure + On success, return allocated size (>=0) + On failure, return -1 */ -static int +static inline Py_ssize_t _BlocksOutputBuffer_Grow(_BlocksOutputBuffer *buffer, - BOB_BUFFER_TYPE **next_out, BOB_SIZE_TYPE *avail_out) + void **next_out, + const Py_ssize_t avail_out) { PyObject *b; const Py_ssize_t list_len = Py_SIZE(buffer->list); - int block_size; + Py_ssize_t block_size; // ensure no gaps in the data - if (*avail_out != 0) { + if (avail_out != 0) { PyErr_SetString(PyExc_SystemError, - "*avail_out is non-zero in _BlocksOutputBuffer_Grow()."); + "avail_out is non-zero in _BlocksOutputBuffer_Grow()."); return -1; } @@ -247,7 +242,7 @@ _BlocksOutputBuffer_Grow(_BlocksOutputBuffer *buffer, // block_size of the last block if (block_size > rest) { - block_size = (int) rest; + block_size = rest; } } @@ -272,14 +267,14 @@ _BlocksOutputBuffer_Grow(_BlocksOutputBuffer *buffer, // set variables buffer->allocated += block_size; - *next_out = (BOB_BUFFER_TYPE*) PyBytes_AS_STRING(b); - *avail_out = block_size; - return 0; + *next_out = PyBytes_AS_STRING(b); + return block_size; } /* Return the current outputted data size. */ static inline Py_ssize_t -_BlocksOutputBuffer_GetDataSize(_BlocksOutputBuffer *buffer, BOB_SIZE_TYPE avail_out) +_BlocksOutputBuffer_GetDataSize(_BlocksOutputBuffer *buffer, + const Py_ssize_t avail_out) { return buffer->allocated - avail_out; } @@ -289,15 +284,16 @@ _BlocksOutputBuffer_GetDataSize(_BlocksOutputBuffer *buffer, BOB_SIZE_TYPE avail Return a bytes object on success Return NULL on failure */ -static PyObject * -_BlocksOutputBuffer_Finish(_BlocksOutputBuffer *buffer, BOB_SIZE_TYPE avail_out) +static inline PyObject * +_BlocksOutputBuffer_Finish(_BlocksOutputBuffer *buffer, + const Py_ssize_t avail_out) { PyObject *result, *block; const Py_ssize_t list_len = Py_SIZE(buffer->list); // fast path for single block - if ( (list_len == 1 && avail_out == 0) || - (list_len == 2 && Py_SIZE(PyList_GET_ITEM(buffer->list, 1)) == (Py_ssize_t) avail_out)) + if ((list_len == 1 && avail_out == 0) || + (list_len == 2 && Py_SIZE(PyList_GET_ITEM(buffer->list, 1)) == avail_out)) { block = PyList_GET_ITEM(buffer->list, 0); Py_INCREF(block); @@ -342,6 +338,7 @@ _BlocksOutputBuffer_OnError(_BlocksOutputBuffer *buffer) Py_CLEAR(buffer->list); } -#undef BOB_BUFFER_TYPE -#undef BOB_SIZE_TYPE -#undef BOB_SIZE_MAX +#ifdef __cplusplus +} +#endif +#endif /* Py_INTERNAL_BLOCKS_OUTPUT_BUFFER_H */ \ No newline at end of file diff --git a/Modules/_bz2module.c b/Modules/_bz2module.c index dbc4cc8330c1ea..1fbf27be1d04a8 100644 --- a/Modules/_bz2module.c +++ b/Modules/_bz2module.c @@ -8,11 +8,57 @@ #include #include -// _BlocksOutputBuffer -#define BOB_BUFFER_TYPE char // type of next_out pointer -#define BOB_SIZE_TYPE uint32_t // type of avail_out -#include "blocks_output_buffer.h" -#define OutputBuffer(F) _BlocksOutputBuffer_##F +// Blocks output buffer wrappers +#include "pycore_blocks_output_buffer.h" + +/* On success, return value >= 0 + On failure, return -1 */ +static inline Py_ssize_t +Buffer_InitAndGrow(_BlocksOutputBuffer *buffer, Py_ssize_t max_length, + char **next_out, uint32_t *avail_out) +{ + Py_ssize_t allocated; + + // The maximum block size accepted by the lib is UINT32_MAX + assert(OUTPUT_BUFFER_MAX_BLOCK_SIZE <= UINT32_MAX); + + allocated = _BlocksOutputBuffer_InitAndGrow( + buffer, max_length, (void**) next_out); + *avail_out = (uint32_t) allocated; + return allocated; +} + +/* On success, return value >= 0 + On failure, return -1 */ +static inline Py_ssize_t +Buffer_Grow(_BlocksOutputBuffer *buffer, + char **next_out, uint32_t *avail_out) +{ + Py_ssize_t allocated; + + allocated = _BlocksOutputBuffer_Grow( + buffer, (void**) next_out, (Py_ssize_t) *avail_out); + *avail_out = (uint32_t) allocated; + return allocated; +} + +static inline Py_ssize_t +Buffer_GetDataSize(_BlocksOutputBuffer *buffer, uint32_t avail_out) +{ + return _BlocksOutputBuffer_GetDataSize(buffer, (Py_ssize_t) avail_out); +} + +static inline PyObject * +Buffer_Finish(_BlocksOutputBuffer *buffer, uint32_t avail_out) +{ + return _BlocksOutputBuffer_Finish(buffer, (Py_ssize_t) avail_out); +} + +static inline void +Buffer_OnError(_BlocksOutputBuffer *buffer) +{ + _BlocksOutputBuffer_OnError(buffer); +} #ifndef BZ_CONFIG_ERROR @@ -128,9 +174,9 @@ static PyObject * compress(BZ2Compressor *c, char *data, size_t len, int action) { PyObject *result; - _BlocksOutputBuffer buffer; + _BlocksOutputBuffer buffer = {.list = NULL}; - if (OutputBuffer(InitAndGrow)(&buffer, -1, &c->bzs.next_out, &c->bzs.avail_out) < 0) { + if (Buffer_InitAndGrow(&buffer, -1, &c->bzs.next_out, &c->bzs.avail_out) < 0) { goto error; } c->bzs.next_in = data; @@ -151,7 +197,7 @@ compress(BZ2Compressor *c, char *data, size_t len, int action) break; if (c->bzs.avail_out == 0) { - if (OutputBuffer(Grow)(&buffer, &c->bzs.next_out, &c->bzs.avail_out) < 0) { + if (Buffer_Grow(&buffer, &c->bzs.next_out, &c->bzs.avail_out) < 0) { goto error; } } @@ -168,13 +214,13 @@ compress(BZ2Compressor *c, char *data, size_t len, int action) break; } - result = OutputBuffer(Finish)(&buffer, c->bzs.avail_out); + result = Buffer_Finish(&buffer, c->bzs.avail_out); if (result != NULL) { return result; } error: - OutputBuffer(OnError)(&buffer); + Buffer_OnError(&buffer); return NULL; } @@ -392,10 +438,10 @@ decompress_buf(BZ2Decompressor *d, Py_ssize_t max_length) compare against max_length and PyBytes_GET_SIZE we declare it as signed */ PyObject *result; - _BlocksOutputBuffer buffer; + _BlocksOutputBuffer buffer = {.list = NULL}; bz_stream *bzs = &d->bzs; - if (OutputBuffer(InitAndGrow)(&buffer, max_length, &bzs->next_out, &bzs->avail_out) < 0) { + if (Buffer_InitAndGrow(&buffer, max_length, &bzs->next_out, &bzs->avail_out) < 0) { goto error; } @@ -422,21 +468,21 @@ decompress_buf(BZ2Decompressor *d, Py_ssize_t max_length) } else if (d->bzs_avail_in_real == 0) { break; } else if (bzs->avail_out == 0) { - if (OutputBuffer(GetDataSize)(&buffer, bzs->avail_out) == max_length) + if (Buffer_GetDataSize(&buffer, bzs->avail_out) == max_length) break; - if (OutputBuffer(Grow)(&buffer, &bzs->next_out, &bzs->avail_out) < 0) { + if (Buffer_Grow(&buffer, &bzs->next_out, &bzs->avail_out) < 0) { goto error; } } } - result = OutputBuffer(Finish)(&buffer, bzs->avail_out); + result = Buffer_Finish(&buffer, bzs->avail_out); if (result != NULL) { return result; } error: - OutputBuffer(OnError)(&buffer); + Buffer_OnError(&buffer); return NULL; } @@ -631,7 +677,7 @@ static int _bz2_BZ2Decompressor___init__(PyObject *self, PyObject *args, PyObject *kwargs) { int return_value = -1; - + if (!_PyArg_NoPositional("BZ2Decompressor", args)) { goto exit; } diff --git a/Modules/_lzmamodule.c b/Modules/_lzmamodule.c index bd6978793b2ebc..30c10d222c20ef 100644 --- a/Modules/_lzmamodule.c +++ b/Modules/_lzmamodule.c @@ -15,11 +15,57 @@ #include -// _BlocksOutputBuffer -#define BOB_BUFFER_TYPE uint8_t // type of next_out pointer -#define BOB_SIZE_TYPE size_t // type of avail_out -#include "blocks_output_buffer.h" -#define OutputBuffer(F) _BlocksOutputBuffer_##F +// Blocks output buffer wrappers +#include "pycore_blocks_output_buffer.h" + +/* On success, return value >= 0 + On failure, return -1 */ +static inline Py_ssize_t +Buffer_InitAndGrow(_BlocksOutputBuffer *buffer, Py_ssize_t max_length, + uint8_t **next_out, size_t *avail_out) +{ + Py_ssize_t allocated; + + // The maximum block size accepted by the lib is SIZE_MAX + assert(OUTPUT_BUFFER_MAX_BLOCK_SIZE <= SIZE_MAX); + + allocated = _BlocksOutputBuffer_InitAndGrow( + buffer, max_length, (void**) next_out); + *avail_out = (size_t) allocated; + return allocated; +} + +/* On success, return value >= 0 + On failure, return -1 */ +static inline Py_ssize_t +Buffer_Grow(_BlocksOutputBuffer *buffer, + uint8_t **next_out, size_t *avail_out) +{ + Py_ssize_t allocated; + + allocated = _BlocksOutputBuffer_Grow( + buffer, (void**) next_out, (Py_ssize_t) *avail_out); + *avail_out = (size_t) allocated; + return allocated; +} + +static inline Py_ssize_t +Buffer_GetDataSize(_BlocksOutputBuffer *buffer, size_t avail_out) +{ + return _BlocksOutputBuffer_GetDataSize(buffer, (Py_ssize_t) avail_out); +} + +static inline PyObject * +Buffer_Finish(_BlocksOutputBuffer *buffer, size_t avail_out) +{ + return _BlocksOutputBuffer_Finish(buffer, (Py_ssize_t) avail_out); +} + +static inline void +Buffer_OnError(_BlocksOutputBuffer *buffer) +{ + _BlocksOutputBuffer_OnError(buffer); +} #define ACQUIRE_LOCK(obj) do { \ @@ -499,11 +545,11 @@ static PyObject * compress(Compressor *c, uint8_t *data, size_t len, lzma_action action) { PyObject *result; - _BlocksOutputBuffer buffer; + _BlocksOutputBuffer buffer = {.list = NULL}; _lzma_state *state = PyType_GetModuleState(Py_TYPE(c)); assert(state != NULL); - if (OutputBuffer(InitAndGrow)(&buffer, -1, &c->lzs.next_out, &c->lzs.avail_out) < 0) { + if (Buffer_InitAndGrow(&buffer, -1, &c->lzs.next_out, &c->lzs.avail_out) < 0) { goto error; } c->lzs.next_in = data; @@ -526,19 +572,19 @@ compress(Compressor *c, uint8_t *data, size_t len, lzma_action action) (action == LZMA_FINISH && lzret == LZMA_STREAM_END)) { break; } else if (c->lzs.avail_out == 0) { - if (OutputBuffer(Grow)(&buffer, &c->lzs.next_out, &c->lzs.avail_out) < 0) { + if (Buffer_Grow(&buffer, &c->lzs.next_out, &c->lzs.avail_out) < 0) { goto error; } } } - result = OutputBuffer(Finish)(&buffer, c->lzs.avail_out); + result = Buffer_Finish(&buffer, c->lzs.avail_out); if (result != NULL) { return result; } error: - OutputBuffer(OnError)(&buffer); + Buffer_OnError(&buffer); return NULL; } @@ -883,11 +929,11 @@ decompress_buf(Decompressor *d, Py_ssize_t max_length) { PyObject *result; lzma_stream *lzs = &d->lzs; - _BlocksOutputBuffer buffer; + _BlocksOutputBuffer buffer = {.list = NULL}; _lzma_state *state = PyType_GetModuleState(Py_TYPE(d)); assert(state != NULL); - if (OutputBuffer(InitAndGrow)(&buffer, max_length, &lzs->next_out, &lzs->avail_out) < 0) { + if (Buffer_InitAndGrow(&buffer, max_length, &lzs->next_out, &lzs->avail_out) < 0) { goto error; } @@ -915,10 +961,10 @@ decompress_buf(Decompressor *d, Py_ssize_t max_length) Maybe lzs's internal state still have a few bytes can be output, grow the output buffer and continue if max_lengh < 0. */ - if (OutputBuffer(GetDataSize)(&buffer, lzs->avail_out) == max_length) { + if (Buffer_GetDataSize(&buffer, lzs->avail_out) == max_length) { break; } - if (OutputBuffer(Grow)(&buffer, &lzs->next_out, &lzs->avail_out) < 0) { + if (Buffer_Grow(&buffer, &lzs->next_out, &lzs->avail_out) < 0) { goto error; } } else if (lzs->avail_in == 0) { @@ -926,13 +972,13 @@ decompress_buf(Decompressor *d, Py_ssize_t max_length) } } - result = OutputBuffer(Finish)(&buffer, lzs->avail_out); + result = Buffer_Finish(&buffer, lzs->avail_out); if (result != NULL) { return result; } error: - OutputBuffer(OnError)(&buffer); + Buffer_OnError(&buffer); return NULL; } diff --git a/Modules/zlibmodule.c b/Modules/zlibmodule.c index f7b7523fb2c6a4..480bc1ca2afc04 100644 --- a/Modules/zlibmodule.c +++ b/Modules/zlibmodule.c @@ -9,11 +9,80 @@ #include "structmember.h" // PyMemberDef #include "zlib.h" -// _BlocksOutputBuffer -#define BOB_BUFFER_TYPE Bytef // type of next_out pointer -#define BOB_SIZE_TYPE uint32_t // type of avail_out -#include "blocks_output_buffer.h" -#define OutputBuffer(F) _BlocksOutputBuffer_##F +// Blocks output buffer wrappers +#include "pycore_blocks_output_buffer.h" + +/* On success, return value >= 0 + On failure, return -1 */ +static inline Py_ssize_t +Buffer_InitAndGrow(_BlocksOutputBuffer *buffer, Py_ssize_t max_length, + Bytef **next_out, uint32_t *avail_out) +{ + Py_ssize_t allocated; + + // The maximum block size accepted by the lib is UINT32_MAX + assert(OUTPUT_BUFFER_MAX_BLOCK_SIZE <= UINT32_MAX); + + allocated = _BlocksOutputBuffer_InitAndGrow( + buffer, max_length, (void**) next_out); + *avail_out = (uint32_t) allocated; + return allocated; +} + +/* On success, return value >= 0 + On failure, return -1 */ +static inline Py_ssize_t +Buffer_InitWithSize(_BlocksOutputBuffer *buffer, Py_ssize_t init_size, + Bytef **next_out, uint32_t *avail_out) +{ + Py_ssize_t allocated; + + // The maximum block size accepted by the lib is UINT32_MAX + assert(OUTPUT_BUFFER_MAX_BLOCK_SIZE <= UINT32_MAX); + + if (init_size < 0 || init_size > UINT32_MAX) { + PyErr_SetString(PyExc_ValueError, + "Initial buffer size should (0 <= size <= UINT32_MAX)"); + return -1; + } + + allocated = _BlocksOutputBuffer_InitWithSize( + buffer, init_size, (void**) next_out); + *avail_out = (uint32_t) allocated; + return allocated; +} + +/* On success, return value >= 0 + On failure, return -1 */ +static inline Py_ssize_t +Buffer_Grow(_BlocksOutputBuffer *buffer, + Bytef **next_out, uint32_t *avail_out) +{ + Py_ssize_t allocated; + + allocated = _BlocksOutputBuffer_Grow( + buffer, (void**) next_out, (Py_ssize_t) *avail_out); + *avail_out = (uint32_t) allocated; + return allocated; +} + +static inline Py_ssize_t +Buffer_GetDataSize(_BlocksOutputBuffer *buffer, uint32_t avail_out) +{ + return _BlocksOutputBuffer_GetDataSize(buffer, (Py_ssize_t) avail_out); +} + +static inline PyObject * +Buffer_Finish(_BlocksOutputBuffer *buffer, uint32_t avail_out) +{ + return _BlocksOutputBuffer_Finish(buffer, (Py_ssize_t) avail_out); +} + +static inline void +Buffer_OnError(_BlocksOutputBuffer *buffer) +{ + _BlocksOutputBuffer_OnError(buffer); +} #define ENTER_ZLIB(obj) \ @@ -172,16 +241,14 @@ zlib_compress_impl(PyObject *module, Py_buffer *data, int level) PyObject *RetVal; int flush; z_stream zst; - _BlocksOutputBuffer buffer; + _BlocksOutputBuffer buffer = {.list = NULL}; zlibstate *state = get_zlib_state(module); Byte *ibuf = data->buf; Py_ssize_t ibuflen = data->len; - // OutputBuffer(OnError)(&buffer) is after `error` label, - // so initialize the buffer before any `goto error` statement. - if (OutputBuffer(InitAndGrow)(&buffer, -1, &zst.next_out, &zst.avail_out) < 0) { + if (Buffer_InitAndGrow(&buffer, -1, &zst.next_out, &zst.avail_out) < 0) { goto error; } @@ -213,7 +280,7 @@ zlib_compress_impl(PyObject *module, Py_buffer *data, int level) do { if (zst.avail_out == 0) { - if (OutputBuffer(Grow)(&buffer, &zst.next_out, &zst.avail_out) < 0) { + if (Buffer_Grow(&buffer, &zst.next_out, &zst.avail_out) < 0) { deflateEnd(&zst); goto error; } @@ -237,7 +304,7 @@ zlib_compress_impl(PyObject *module, Py_buffer *data, int level) err = deflateEnd(&zst); if (err == Z_OK) { - RetVal = OutputBuffer(Finish)(&buffer, zst.avail_out); + RetVal = Buffer_Finish(&buffer, zst.avail_out); if (RetVal == NULL) { goto error; } @@ -246,7 +313,7 @@ zlib_compress_impl(PyObject *module, Py_buffer *data, int level) else zlib_error(state, zst, err, "while finishing compression"); error: - OutputBuffer(OnError)(&buffer); + Buffer_OnError(&buffer); return NULL; } @@ -274,7 +341,7 @@ zlib_decompress_impl(PyObject *module, Py_buffer *data, int wbits, Py_ssize_t ibuflen; int err, flush; z_stream zst; - _BlocksOutputBuffer buffer; + _BlocksOutputBuffer buffer = {.list = NULL}; zlibstate *state = get_zlib_state(module); @@ -285,9 +352,7 @@ zlib_decompress_impl(PyObject *module, Py_buffer *data, int wbits, bufsize = 1; } - // OutputBuffer(OnError)(&buffer) is after `error` label, - // so initialize the buffer before any `goto error` statement. - if (OutputBuffer(InitWithSize)(&buffer, bufsize, &zst.next_out, &zst.avail_out) < 0) { + if (Buffer_InitWithSize(&buffer, bufsize, &zst.next_out, &zst.avail_out) < 0) { goto error; } @@ -320,7 +385,7 @@ zlib_decompress_impl(PyObject *module, Py_buffer *data, int wbits, do { if (zst.avail_out == 0) { - if (OutputBuffer(Grow)(&buffer, &zst.next_out, &zst.avail_out) < 0) { + if (Buffer_Grow(&buffer, &zst.next_out, &zst.avail_out) < 0) { inflateEnd(&zst); goto error; } @@ -363,13 +428,13 @@ zlib_decompress_impl(PyObject *module, Py_buffer *data, int wbits, goto error; } - RetVal = OutputBuffer(Finish)(&buffer, zst.avail_out); + RetVal = Buffer_Finish(&buffer, zst.avail_out); if (RetVal != NULL) { return RetVal; } error: - OutputBuffer(OnError)(&buffer); + Buffer_OnError(&buffer); return NULL; } @@ -604,7 +669,7 @@ zlib_Compress_compress_impl(compobject *self, PyTypeObject *cls, { PyObject *RetVal; int err; - _BlocksOutputBuffer buffer; + _BlocksOutputBuffer buffer = {.list = NULL}; zlibstate *state = PyType_GetModuleState(cls); @@ -613,7 +678,7 @@ zlib_Compress_compress_impl(compobject *self, PyTypeObject *cls, ENTER_ZLIB(self); - if (OutputBuffer(InitAndGrow)(&buffer, -1, &self->zst.next_out, &self->zst.avail_out) < 0) { + if (Buffer_InitAndGrow(&buffer, -1, &self->zst.next_out, &self->zst.avail_out) < 0) { goto error; } @@ -622,7 +687,7 @@ zlib_Compress_compress_impl(compobject *self, PyTypeObject *cls, do { if (self->zst.avail_out == 0) { - if (OutputBuffer(Grow)(&buffer, &self->zst.next_out, &self->zst.avail_out) < 0) + if (Buffer_Grow(&buffer, &self->zst.next_out, &self->zst.avail_out) < 0) goto error; } @@ -640,13 +705,13 @@ zlib_Compress_compress_impl(compobject *self, PyTypeObject *cls, } while (ibuflen != 0); - RetVal = OutputBuffer(Finish)(&buffer, self->zst.avail_out); + RetVal = Buffer_Finish(&buffer, self->zst.avail_out); if (RetVal != NULL) { goto success; } error: - OutputBuffer(OnError)(&buffer); + Buffer_OnError(&buffer); RetVal = NULL; success: LEAVE_ZLIB(self); @@ -725,7 +790,7 @@ zlib_Decompress_decompress_impl(compobject *self, PyTypeObject *cls, int err = Z_OK; Py_ssize_t ibuflen; PyObject *RetVal; - _BlocksOutputBuffer buffer; + _BlocksOutputBuffer buffer = {.list = NULL}; PyObject *module = PyType_GetModule(cls); if (module == NULL) @@ -743,7 +808,7 @@ zlib_Decompress_decompress_impl(compobject *self, PyTypeObject *cls, ENTER_ZLIB(self); - if (OutputBuffer(InitAndGrow)(&buffer, max_length, &self->zst.next_out, &self->zst.avail_out) < 0) { + if (Buffer_InitAndGrow(&buffer, max_length, &self->zst.next_out, &self->zst.avail_out) < 0) { goto abort; } @@ -752,10 +817,10 @@ zlib_Decompress_decompress_impl(compobject *self, PyTypeObject *cls, do { if (self->zst.avail_out == 0) { - if (OutputBuffer(GetDataSize)(&buffer, self->zst.avail_out) == max_length) { + if (Buffer_GetDataSize(&buffer, self->zst.avail_out) == max_length) { goto save; } - if (OutputBuffer(Grow)(&buffer, &self->zst.next_out, &self->zst.avail_out) < 0) { + if (Buffer_Grow(&buffer, &self->zst.next_out, &self->zst.avail_out) < 0) { goto abort; } } @@ -801,13 +866,13 @@ zlib_Decompress_decompress_impl(compobject *self, PyTypeObject *cls, goto abort; } - RetVal = OutputBuffer(Finish)(&buffer, self->zst.avail_out); + RetVal = Buffer_Finish(&buffer, self->zst.avail_out); if (RetVal != NULL) { goto success; } abort: - OutputBuffer(OnError)(&buffer); + Buffer_OnError(&buffer); RetVal = NULL; success: LEAVE_ZLIB(self); @@ -834,7 +899,7 @@ zlib_Compress_flush_impl(compobject *self, PyTypeObject *cls, int mode) { int err; PyObject *RetVal; - _BlocksOutputBuffer buffer; + _BlocksOutputBuffer buffer = {.list = NULL}; zlibstate *state = PyType_GetModuleState(cls); /* Flushing with Z_NO_FLUSH is a no-op, so there's no point in @@ -847,13 +912,13 @@ zlib_Compress_flush_impl(compobject *self, PyTypeObject *cls, int mode) self->zst.avail_in = 0; - if (OutputBuffer(InitAndGrow)(&buffer, -1, &self->zst.next_out, &self->zst.avail_out) < 0) { + if (Buffer_InitAndGrow(&buffer, -1, &self->zst.next_out, &self->zst.avail_out) < 0) { goto error; } do { if (self->zst.avail_out == 0) { - if (OutputBuffer(Grow)(&buffer, &self->zst.next_out, &self->zst.avail_out) < 0) { + if (Buffer_Grow(&buffer, &self->zst.next_out, &self->zst.avail_out) < 0) { goto error; } } @@ -890,13 +955,13 @@ zlib_Compress_flush_impl(compobject *self, PyTypeObject *cls, int mode) goto error; } - RetVal = OutputBuffer(Finish)(&buffer, self->zst.avail_out); + RetVal = Buffer_Finish(&buffer, self->zst.avail_out); if (RetVal != NULL) { goto success; } error: - OutputBuffer(OnError)(&buffer); + Buffer_OnError(&buffer); RetVal = NULL; success: LEAVE_ZLIB(self); @@ -1102,7 +1167,7 @@ zlib_Decompress_flush_impl(compobject *self, PyTypeObject *cls, Py_buffer data; PyObject *RetVal; Py_ssize_t ibuflen; - _BlocksOutputBuffer buffer; + _BlocksOutputBuffer buffer = {.list = NULL}; PyObject *module = PyType_GetModule(cls); if (module == NULL) { @@ -1125,7 +1190,7 @@ zlib_Decompress_flush_impl(compobject *self, PyTypeObject *cls, self->zst.next_in = data.buf; ibuflen = data.len; - if (OutputBuffer(InitWithSize)(&buffer, length, &self->zst.next_out, &self->zst.avail_out) < 0) { + if (Buffer_InitWithSize(&buffer, length, &self->zst.next_out, &self->zst.avail_out) < 0) { goto abort; } @@ -1135,7 +1200,7 @@ zlib_Decompress_flush_impl(compobject *self, PyTypeObject *cls, do { if (self->zst.avail_out == 0) { - if (OutputBuffer(Grow)(&buffer, &self->zst.next_out, &self->zst.avail_out) < 0) + if (Buffer_Grow(&buffer, &self->zst.next_out, &self->zst.avail_out) < 0) goto abort; } @@ -1179,13 +1244,13 @@ zlib_Decompress_flush_impl(compobject *self, PyTypeObject *cls, } } - RetVal = OutputBuffer(Finish)(&buffer, self->zst.avail_out); + RetVal = Buffer_Finish(&buffer, self->zst.avail_out); if (RetVal != NULL) { goto success; } abort: - OutputBuffer(OnError)(&buffer); + Buffer_OnError(&buffer); RetVal = NULL; success: PyBuffer_Release(&data); From 2964198f7f4b8833e6378d11964a8c944127900e Mon Sep 17 00:00:00 2001 From: animalize Date: Wed, 28 Apr 2021 13:28:21 +0800 Subject: [PATCH 16/16] address review comments 3 --- .../internal/pycore_blocks_output_buffer.h | 27 ------------------- Modules/_bz2module.c | 7 ++--- Modules/_lzmamodule.c | 7 ++--- Modules/zlibmodule.c | 12 ++++----- 4 files changed, 13 insertions(+), 40 deletions(-) diff --git a/Include/internal/pycore_blocks_output_buffer.h b/Include/internal/pycore_blocks_output_buffer.h index b775d05b40fb10..22546e9a32a80b 100644 --- a/Include/internal/pycore_blocks_output_buffer.h +++ b/Include/internal/pycore_blocks_output_buffer.h @@ -16,7 +16,6 @@ Set .list to NULL for _BlocksOutputBuffer_OnError() 2, Initialize the buffer use one of these functions: - _BlocksOutputBuffer_Init() _BlocksOutputBuffer_InitAndGrow() _BlocksOutputBuffer_InitWithSize() @@ -92,32 +91,6 @@ const Py_ssize_t BUFFER_BLOCK_SIZE[] = ... */ -/* Initialize the buffer. - - max_length: Max length of the buffer, -1 for unlimited length. - - On success, return allocated size (>=0) - On failure, return -1 -*/ -static inline Py_ssize_t -_BlocksOutputBuffer_Init(_BlocksOutputBuffer *buffer, - const Py_ssize_t max_length, - void **next_out) -{ - // ensure .list was set to NULL - assert(buffer->list == NULL); - - buffer->list = PyList_New(0); - if (buffer->list == NULL) { - return -1; - } - buffer->allocated = 0; - buffer->max_length = max_length; - - *next_out = (void*) 1; // some libs don't accept NULL - return 0; -} - /* Initialize the buffer, and grow the buffer. max_length: Max length of the buffer, -1 for unlimited length. diff --git a/Modules/_bz2module.c b/Modules/_bz2module.c index 1fbf27be1d04a8..9893a637262915 100644 --- a/Modules/_bz2module.c +++ b/Modules/_bz2module.c @@ -11,6 +11,10 @@ // Blocks output buffer wrappers #include "pycore_blocks_output_buffer.h" +#if OUTPUT_BUFFER_MAX_BLOCK_SIZE > UINT32_MAX + #error "The maximum block size accepted by libbzip2 is UINT32_MAX." +#endif + /* On success, return value >= 0 On failure, return -1 */ static inline Py_ssize_t @@ -19,9 +23,6 @@ Buffer_InitAndGrow(_BlocksOutputBuffer *buffer, Py_ssize_t max_length, { Py_ssize_t allocated; - // The maximum block size accepted by the lib is UINT32_MAX - assert(OUTPUT_BUFFER_MAX_BLOCK_SIZE <= UINT32_MAX); - allocated = _BlocksOutputBuffer_InitAndGrow( buffer, max_length, (void**) next_out); *avail_out = (uint32_t) allocated; diff --git a/Modules/_lzmamodule.c b/Modules/_lzmamodule.c index 30c10d222c20ef..0d6231953a3e78 100644 --- a/Modules/_lzmamodule.c +++ b/Modules/_lzmamodule.c @@ -18,6 +18,10 @@ // Blocks output buffer wrappers #include "pycore_blocks_output_buffer.h" +#if OUTPUT_BUFFER_MAX_BLOCK_SIZE > SIZE_MAX + #error "The maximum block size accepted by liblzma is SIZE_MAX." +#endif + /* On success, return value >= 0 On failure, return -1 */ static inline Py_ssize_t @@ -26,9 +30,6 @@ Buffer_InitAndGrow(_BlocksOutputBuffer *buffer, Py_ssize_t max_length, { Py_ssize_t allocated; - // The maximum block size accepted by the lib is SIZE_MAX - assert(OUTPUT_BUFFER_MAX_BLOCK_SIZE <= SIZE_MAX); - allocated = _BlocksOutputBuffer_InitAndGrow( buffer, max_length, (void**) next_out); *avail_out = (size_t) allocated; diff --git a/Modules/zlibmodule.c b/Modules/zlibmodule.c index f904f992af3696..8a20dfcbf9e27a 100644 --- a/Modules/zlibmodule.c +++ b/Modules/zlibmodule.c @@ -12,6 +12,10 @@ // Blocks output buffer wrappers #include "pycore_blocks_output_buffer.h" +#if OUTPUT_BUFFER_MAX_BLOCK_SIZE > UINT32_MAX + #error "The maximum block size accepted by zlib is UINT32_MAX." +#endif + /* On success, return value >= 0 On failure, return -1 */ static inline Py_ssize_t @@ -20,9 +24,6 @@ Buffer_InitAndGrow(_BlocksOutputBuffer *buffer, Py_ssize_t max_length, { Py_ssize_t allocated; - // The maximum block size accepted by the lib is UINT32_MAX - assert(OUTPUT_BUFFER_MAX_BLOCK_SIZE <= UINT32_MAX); - allocated = _BlocksOutputBuffer_InitAndGrow( buffer, max_length, (void**) next_out); *avail_out = (uint32_t) allocated; @@ -37,10 +38,7 @@ Buffer_InitWithSize(_BlocksOutputBuffer *buffer, Py_ssize_t init_size, { Py_ssize_t allocated; - // The maximum block size accepted by the lib is UINT32_MAX - assert(OUTPUT_BUFFER_MAX_BLOCK_SIZE <= UINT32_MAX); - - if (init_size < 0 || init_size > UINT32_MAX) { + if (init_size < 0 || (size_t)init_size > UINT32_MAX) { PyErr_SetString(PyExc_ValueError, "Initial buffer size should (0 <= size <= UINT32_MAX)"); return -1;