Path: blob/main/Include/internal/pycore_blocks_output_buffer.h
12 views
/*1_BlocksOutputBuffer is used to maintain an output buffer2that has unpredictable size. Suitable for compression/decompression3API (bz2/lzma/zlib) that has stream->next_out and stream->avail_out:45stream->next_out: point to the next output position.6stream->avail_out: the number of available bytes left in the buffer.78It maintains a list of bytes object, so there is no overhead of resizing9the buffer.1011Usage:12131, Initialize the struct instance like this:14_BlocksOutputBuffer buffer = {.list = NULL};15Set .list to NULL for _BlocksOutputBuffer_OnError()16172, Initialize the buffer use one of these functions:18_BlocksOutputBuffer_InitAndGrow()19_BlocksOutputBuffer_InitWithSize()20213, If (avail_out == 0), grow the buffer:22_BlocksOutputBuffer_Grow()23244, Get the current outputted data size:25_BlocksOutputBuffer_GetDataSize()26275, Finish the buffer, and return a bytes object:28_BlocksOutputBuffer_Finish()29306, Clean up the buffer when an error occurred:31_BlocksOutputBuffer_OnError()32*/3334#ifndef Py_INTERNAL_BLOCKS_OUTPUT_BUFFER_H35#define Py_INTERNAL_BLOCKS_OUTPUT_BUFFER_H36#ifdef __cplusplus37extern "C" {38#endif3940#include "Python.h"4142typedef struct {43// List of bytes objects44PyObject *list;45// Number of whole allocated size46Py_ssize_t allocated;47// Max length of the buffer, negative number means unlimited length.48Py_ssize_t max_length;49} _BlocksOutputBuffer;5051static const char unable_allocate_msg[] = "Unable to allocate output buffer.";5253/* In 32-bit build, the max block size should <= INT32_MAX. */54#define OUTPUT_BUFFER_MAX_BLOCK_SIZE (256*1024*1024)5556/* Block size sequence */57#define KB (1024)58#define MB (1024*1024)59static const Py_ssize_t BUFFER_BLOCK_SIZE[] =60{ 32*KB, 64*KB, 256*KB, 1*MB, 4*MB, 8*MB, 16*MB, 16*MB,6132*MB, 32*MB, 32*MB, 32*MB, 64*MB, 64*MB, 128*MB, 128*MB,62OUTPUT_BUFFER_MAX_BLOCK_SIZE };63#undef KB64#undef MB6566/* According to the block sizes defined by BUFFER_BLOCK_SIZE, the whole67allocated size growth step is:681 32 KB +32 KB692 96 KB +64 KB703 352 KB +256 KB714 1.34 MB +1 MB725 5.34 MB +4 MB736 13.34 MB +8 MB747 29.34 MB +16 MB758 45.34 MB +16 MB769 77.34 MB +32 MB7710 109.34 MB +32 MB7811 141.34 MB +32 MB7912 173.34 MB +32 MB8013 237.34 MB +64 MB8114 301.34 MB +64 MB8215 429.34 MB +128 MB8316 557.34 MB +128 MB8417 813.34 MB +256 MB8518 1069.34 MB +256 MB8619 1325.34 MB +256 MB8720 1581.34 MB +256 MB8821 1837.34 MB +256 MB8922 2093.34 MB +256 MB90...91*/9293/* Initialize the buffer, and grow the buffer.9495max_length: Max length of the buffer, -1 for unlimited length.9697On success, return allocated size (>=0)98On failure, return -199*/100static inline Py_ssize_t101_BlocksOutputBuffer_InitAndGrow(_BlocksOutputBuffer *buffer,102const Py_ssize_t max_length,103void **next_out)104{105PyObject *b;106Py_ssize_t block_size;107108// ensure .list was set to NULL109assert(buffer->list == NULL);110111// get block size112if (0 <= max_length && max_length < BUFFER_BLOCK_SIZE[0]) {113block_size = max_length;114} else {115block_size = BUFFER_BLOCK_SIZE[0];116}117118// the first block119b = PyBytes_FromStringAndSize(NULL, block_size);120if (b == NULL) {121return -1;122}123124// create the list125buffer->list = PyList_New(1);126if (buffer->list == NULL) {127Py_DECREF(b);128return -1;129}130PyList_SET_ITEM(buffer->list, 0, b);131132// set variables133buffer->allocated = block_size;134buffer->max_length = max_length;135136*next_out = PyBytes_AS_STRING(b);137return block_size;138}139140/* Initialize the buffer, with an initial size.141142Check block size limit in the outer wrapper function. For example, some libs143accept UINT32_MAX as the maximum block size, then init_size should <= it.144145On success, return allocated size (>=0)146On failure, return -1147*/148static inline Py_ssize_t149_BlocksOutputBuffer_InitWithSize(_BlocksOutputBuffer *buffer,150const Py_ssize_t init_size,151void **next_out)152{153PyObject *b;154155// ensure .list was set to NULL156assert(buffer->list == NULL);157158// the first block159b = PyBytes_FromStringAndSize(NULL, init_size);160if (b == NULL) {161PyErr_SetString(PyExc_MemoryError, unable_allocate_msg);162return -1;163}164165// create the list166buffer->list = PyList_New(1);167if (buffer->list == NULL) {168Py_DECREF(b);169return -1;170}171PyList_SET_ITEM(buffer->list, 0, b);172173// set variables174buffer->allocated = init_size;175buffer->max_length = -1;176177*next_out = PyBytes_AS_STRING(b);178return init_size;179}180181/* Grow the buffer. The avail_out must be 0, please check it before calling.182183On success, return allocated size (>=0)184On failure, return -1185*/186static inline Py_ssize_t187_BlocksOutputBuffer_Grow(_BlocksOutputBuffer *buffer,188void **next_out,189const Py_ssize_t avail_out)190{191PyObject *b;192const Py_ssize_t list_len = Py_SIZE(buffer->list);193Py_ssize_t block_size;194195// ensure no gaps in the data196if (avail_out != 0) {197PyErr_SetString(PyExc_SystemError,198"avail_out is non-zero in _BlocksOutputBuffer_Grow().");199return -1;200}201202// get block size203if (list_len < (Py_ssize_t) Py_ARRAY_LENGTH(BUFFER_BLOCK_SIZE)) {204block_size = BUFFER_BLOCK_SIZE[list_len];205} else {206block_size = BUFFER_BLOCK_SIZE[Py_ARRAY_LENGTH(BUFFER_BLOCK_SIZE) - 1];207}208209// check max_length210if (buffer->max_length >= 0) {211// if (rest == 0), should not grow the buffer.212Py_ssize_t rest = buffer->max_length - buffer->allocated;213assert(rest > 0);214215// block_size of the last block216if (block_size > rest) {217block_size = rest;218}219}220221// check buffer->allocated overflow222if (block_size > PY_SSIZE_T_MAX - buffer->allocated) {223PyErr_SetString(PyExc_MemoryError, unable_allocate_msg);224return -1;225}226227// create the block228b = PyBytes_FromStringAndSize(NULL, block_size);229if (b == NULL) {230PyErr_SetString(PyExc_MemoryError, unable_allocate_msg);231return -1;232}233if (PyList_Append(buffer->list, b) < 0) {234Py_DECREF(b);235return -1;236}237Py_DECREF(b);238239// set variables240buffer->allocated += block_size;241242*next_out = PyBytes_AS_STRING(b);243return block_size;244}245246/* Return the current outputted data size. */247static inline Py_ssize_t248_BlocksOutputBuffer_GetDataSize(_BlocksOutputBuffer *buffer,249const Py_ssize_t avail_out)250{251return buffer->allocated - avail_out;252}253254/* Finish the buffer.255256Return a bytes object on success257Return NULL on failure258*/259static inline PyObject *260_BlocksOutputBuffer_Finish(_BlocksOutputBuffer *buffer,261const Py_ssize_t avail_out)262{263PyObject *result, *block;264const Py_ssize_t list_len = Py_SIZE(buffer->list);265266// fast path for single block267if ((list_len == 1 && avail_out == 0) ||268(list_len == 2 && Py_SIZE(PyList_GET_ITEM(buffer->list, 1)) == avail_out))269{270block = PyList_GET_ITEM(buffer->list, 0);271Py_INCREF(block);272273Py_CLEAR(buffer->list);274return block;275}276277// final bytes object278result = PyBytes_FromStringAndSize(NULL, buffer->allocated - avail_out);279if (result == NULL) {280PyErr_SetString(PyExc_MemoryError, unable_allocate_msg);281return NULL;282}283284// memory copy285if (list_len > 0) {286char *posi = PyBytes_AS_STRING(result);287288// blocks except the last one289Py_ssize_t i = 0;290for (; i < list_len-1; i++) {291block = PyList_GET_ITEM(buffer->list, i);292memcpy(posi, PyBytes_AS_STRING(block), Py_SIZE(block));293posi += Py_SIZE(block);294}295// the last block296block = PyList_GET_ITEM(buffer->list, i);297memcpy(posi, PyBytes_AS_STRING(block), Py_SIZE(block) - avail_out);298} else {299assert(Py_SIZE(result) == 0);300}301302Py_CLEAR(buffer->list);303return result;304}305306/* Clean up the buffer when an error occurred. */307static inline void308_BlocksOutputBuffer_OnError(_BlocksOutputBuffer *buffer)309{310Py_CLEAR(buffer->list);311}312313#ifdef __cplusplus314}315#endif316#endif /* Py_INTERNAL_BLOCKS_OUTPUT_BUFFER_H */317318