(root)/
Python-3.12.0/
Include/
internal/
pycore_blocks_output_buffer.h
       1  /*
       2     _BlocksOutputBuffer is used to maintain an output buffer
       3     that has unpredictable size. Suitable for compression/decompression
       4     API (bz2/lzma/zlib) that has stream->next_out and stream->avail_out:
       5  
       6          stream->next_out:  point to the next output position.
       7          stream->avail_out: the number of available bytes left in the buffer.
       8  
       9     It maintains a list of bytes object, so there is no overhead of resizing
      10     the buffer.
      11  
      12     Usage:
      13  
      14     1, Initialize the struct instance like this:
      15          _BlocksOutputBuffer buffer = {.list = NULL};
      16        Set .list to NULL for _BlocksOutputBuffer_OnError()
      17  
      18     2, Initialize the buffer use one of these functions:
      19          _BlocksOutputBuffer_InitAndGrow()
      20          _BlocksOutputBuffer_InitWithSize()
      21  
      22     3, If (avail_out == 0), grow the buffer:
      23          _BlocksOutputBuffer_Grow()
      24  
      25     4, Get the current outputted data size:
      26          _BlocksOutputBuffer_GetDataSize()
      27  
      28     5, Finish the buffer, and return a bytes object:
      29          _BlocksOutputBuffer_Finish()
      30  
      31     6, Clean up the buffer when an error occurred:
      32          _BlocksOutputBuffer_OnError()
      33  */
      34  
      35  #ifndef Py_INTERNAL_BLOCKS_OUTPUT_BUFFER_H
      36  #define Py_INTERNAL_BLOCKS_OUTPUT_BUFFER_H
      37  #ifdef __cplusplus
      38  extern "C" {
      39  #endif
      40  
      41  #include "Python.h"
      42  
      43  typedef struct {
      44      // List of bytes objects
      45      PyObject *list;
      46      // Number of whole allocated size
      47      Py_ssize_t allocated;
      48      // Max length of the buffer, negative number means unlimited length.
      49      Py_ssize_t max_length;
      50  } _BlocksOutputBuffer;
      51  
      52  static const char unable_allocate_msg[] = "Unable to allocate output buffer.";
      53  
      54  /* In 32-bit build, the max block size should <= INT32_MAX. */
      55  #define OUTPUT_BUFFER_MAX_BLOCK_SIZE (256*1024*1024)
      56  
      57  /* Block size sequence */
      58  #define KB (1024)
      59  #define MB (1024*1024)
      60  static const Py_ssize_t BUFFER_BLOCK_SIZE[] =
      61      { 32*KB, 64*KB, 256*KB, 1*MB, 4*MB, 8*MB, 16*MB, 16*MB,
      62        32*MB, 32*MB, 32*MB, 32*MB, 64*MB, 64*MB, 128*MB, 128*MB,
      63        OUTPUT_BUFFER_MAX_BLOCK_SIZE };
      64  #undef KB
      65  #undef MB
      66  
      67  /* According to the block sizes defined by BUFFER_BLOCK_SIZE, the whole
      68     allocated size growth step is:
      69      1   32 KB       +32 KB
      70      2   96 KB       +64 KB
      71      3   352 KB      +256 KB
      72      4   1.34 MB     +1 MB
      73      5   5.34 MB     +4 MB
      74      6   13.34 MB    +8 MB
      75      7   29.34 MB    +16 MB
      76      8   45.34 MB    +16 MB
      77      9   77.34 MB    +32 MB
      78      10  109.34 MB   +32 MB
      79      11  141.34 MB   +32 MB
      80      12  173.34 MB   +32 MB
      81      13  237.34 MB   +64 MB
      82      14  301.34 MB   +64 MB
      83      15  429.34 MB   +128 MB
      84      16  557.34 MB   +128 MB
      85      17  813.34 MB   +256 MB
      86      18  1069.34 MB  +256 MB
      87      19  1325.34 MB  +256 MB
      88      20  1581.34 MB  +256 MB
      89      21  1837.34 MB  +256 MB
      90      22  2093.34 MB  +256 MB
      91      ...
      92  */
      93  
      94  /* Initialize the buffer, and grow the buffer.
      95  
      96     max_length: Max length of the buffer, -1 for unlimited length.
      97  
      98     On success, return allocated size (>=0)
      99     On failure, return -1
     100  */
     101  static inline Py_ssize_t
     102  _BlocksOutputBuffer_InitAndGrow(_BlocksOutputBuffer *buffer,
     103                                  const Py_ssize_t max_length,
     104                                  void **next_out)
     105  {
     106      PyObject *b;
     107      Py_ssize_t block_size;
     108  
     109      // ensure .list was set to NULL
     110      assert(buffer->list == NULL);
     111  
     112      // get block size
     113      if (0 <= max_length && max_length < BUFFER_BLOCK_SIZE[0]) {
     114          block_size = max_length;
     115      } else {
     116          block_size = BUFFER_BLOCK_SIZE[0];
     117      }
     118  
     119      // the first block
     120      b = PyBytes_FromStringAndSize(NULL, block_size);
     121      if (b == NULL) {
     122          return -1;
     123      }
     124  
     125      // create the list
     126      buffer->list = PyList_New(1);
     127      if (buffer->list == NULL) {
     128          Py_DECREF(b);
     129          return -1;
     130      }
     131      PyList_SET_ITEM(buffer->list, 0, b);
     132  
     133      // set variables
     134      buffer->allocated = block_size;
     135      buffer->max_length = max_length;
     136  
     137      *next_out = PyBytes_AS_STRING(b);
     138      return block_size;
     139  }
     140  
     141  /* Initialize the buffer, with an initial size.
     142  
     143     Check block size limit in the outer wrapper function. For example, some libs
     144     accept UINT32_MAX as the maximum block size, then init_size should <= it.
     145  
     146     On success, return allocated size (>=0)
     147     On failure, return -1
     148  */
     149  static inline Py_ssize_t
     150  _BlocksOutputBuffer_InitWithSize(_BlocksOutputBuffer *buffer,
     151                                   const Py_ssize_t init_size,
     152                                   void **next_out)
     153  {
     154      PyObject *b;
     155  
     156      // ensure .list was set to NULL
     157      assert(buffer->list == NULL);
     158  
     159      // the first block
     160      b = PyBytes_FromStringAndSize(NULL, init_size);
     161      if (b == NULL) {
     162          PyErr_SetString(PyExc_MemoryError, unable_allocate_msg);
     163          return -1;
     164      }
     165  
     166      // create the list
     167      buffer->list = PyList_New(1);
     168      if (buffer->list == NULL) {
     169          Py_DECREF(b);
     170          return -1;
     171      }
     172      PyList_SET_ITEM(buffer->list, 0, b);
     173  
     174      // set variables
     175      buffer->allocated = init_size;
     176      buffer->max_length = -1;
     177  
     178      *next_out = PyBytes_AS_STRING(b);
     179      return init_size;
     180  }
     181  
     182  /* Grow the buffer. The avail_out must be 0, please check it before calling.
     183  
     184     On success, return allocated size (>=0)
     185     On failure, return -1
     186  */
     187  static inline Py_ssize_t
     188  _BlocksOutputBuffer_Grow(_BlocksOutputBuffer *buffer,
     189                           void **next_out,
     190                           const Py_ssize_t avail_out)
     191  {
     192      PyObject *b;
     193      const Py_ssize_t list_len = Py_SIZE(buffer->list);
     194      Py_ssize_t block_size;
     195  
     196      // ensure no gaps in the data
     197      if (avail_out != 0) {
     198          PyErr_SetString(PyExc_SystemError,
     199                          "avail_out is non-zero in _BlocksOutputBuffer_Grow().");
     200          return -1;
     201      }
     202  
     203      // get block size
     204      if (list_len < (Py_ssize_t) Py_ARRAY_LENGTH(BUFFER_BLOCK_SIZE)) {
     205          block_size = BUFFER_BLOCK_SIZE[list_len];
     206      } else {
     207          block_size = BUFFER_BLOCK_SIZE[Py_ARRAY_LENGTH(BUFFER_BLOCK_SIZE) - 1];
     208      }
     209  
     210      // check max_length
     211      if (buffer->max_length >= 0) {
     212          // if (rest == 0), should not grow the buffer.
     213          Py_ssize_t rest = buffer->max_length - buffer->allocated;
     214          assert(rest > 0);
     215  
     216          // block_size of the last block
     217          if (block_size > rest) {
     218              block_size = rest;
     219          }
     220      }
     221  
     222      // check buffer->allocated overflow
     223      if (block_size > PY_SSIZE_T_MAX - buffer->allocated) {
     224          PyErr_SetString(PyExc_MemoryError, unable_allocate_msg);
     225          return -1;
     226      }
     227  
     228      // create the block
     229      b = PyBytes_FromStringAndSize(NULL, block_size);
     230      if (b == NULL) {
     231          PyErr_SetString(PyExc_MemoryError, unable_allocate_msg);
     232          return -1;
     233      }
     234      if (PyList_Append(buffer->list, b) < 0) {
     235          Py_DECREF(b);
     236          return -1;
     237      }
     238      Py_DECREF(b);
     239  
     240      // set variables
     241      buffer->allocated += block_size;
     242  
     243      *next_out = PyBytes_AS_STRING(b);
     244      return block_size;
     245  }
     246  
     247  /* Return the current outputted data size. */
     248  static inline Py_ssize_t
     249  _BlocksOutputBuffer_GetDataSize(_BlocksOutputBuffer *buffer,
     250                                  const Py_ssize_t avail_out)
     251  {
     252      return buffer->allocated - avail_out;
     253  }
     254  
     255  /* Finish the buffer.
     256  
     257     Return a bytes object on success
     258     Return NULL on failure
     259  */
     260  static inline PyObject *
     261  _BlocksOutputBuffer_Finish(_BlocksOutputBuffer *buffer,
     262                             const Py_ssize_t avail_out)
     263  {
     264      PyObject *result, *block;
     265      const Py_ssize_t list_len = Py_SIZE(buffer->list);
     266  
     267      // fast path for single block
     268      if ((list_len == 1 && avail_out == 0) ||
     269          (list_len == 2 && Py_SIZE(PyList_GET_ITEM(buffer->list, 1)) == avail_out))
     270      {
     271          block = PyList_GET_ITEM(buffer->list, 0);
     272          Py_INCREF(block);
     273  
     274          Py_CLEAR(buffer->list);
     275          return block;
     276      }
     277  
     278      // final bytes object
     279      result = PyBytes_FromStringAndSize(NULL, buffer->allocated - avail_out);
     280      if (result == NULL) {
     281          PyErr_SetString(PyExc_MemoryError, unable_allocate_msg);
     282          return NULL;
     283      }
     284  
     285      // memory copy
     286      if (list_len > 0) {
     287          char *posi = PyBytes_AS_STRING(result);
     288  
     289          // blocks except the last one
     290          Py_ssize_t i = 0;
     291          for (; i < list_len-1; i++) {
     292              block = PyList_GET_ITEM(buffer->list, i);
     293              memcpy(posi, PyBytes_AS_STRING(block), Py_SIZE(block));
     294              posi += Py_SIZE(block);
     295          }
     296          // the last block
     297          block = PyList_GET_ITEM(buffer->list, i);
     298          memcpy(posi, PyBytes_AS_STRING(block), Py_SIZE(block) - avail_out);
     299      } else {
     300          assert(Py_SIZE(result) == 0);
     301      }
     302  
     303      Py_CLEAR(buffer->list);
     304      return result;
     305  }
     306  
     307  /* Clean up the buffer when an error occurred. */
     308  static inline void
     309  _BlocksOutputBuffer_OnError(_BlocksOutputBuffer *buffer)
     310  {
     311      Py_CLEAR(buffer->list);
     312  }
     313  
     314  #ifdef __cplusplus
     315  }
     316  #endif
     317  #endif /* Py_INTERNAL_BLOCKS_OUTPUT_BUFFER_H */