# HG changeset patch # User Mike Becker # Date 1734640425 -3600 # Node ID 3e4905241838602f7e8a16fc5aa30d496b037d33 # Parent b1373253e20219d56a814a485339b4b84644fc0e add copy-on-extend feature to UCX buffer - fixes #533 diff -r b1373253e202 -r 3e4905241838 CHANGELOG --- a/CHANGELOG Thu Dec 19 12:00:20 2024 +0100 +++ b/CHANGELOG Thu Dec 19 21:33:45 2024 +0100 @@ -9,7 +9,7 @@ * adds cx_array_reallocator() and cx_array_default_reallocator * adds several new array and list functions * adds cxBufferReset() - * adds CX_BUFFER_COPY_ON_WRITE flag + * adds CX_BUFFER_COPY_ON_WRITE and CX_BUFFER_COPY_ON_EXTEND flags * adds cx_cmp_ptr() * adds cx_sprintf() and several more variants * adds runtime constants to read out the actual SBO sizes diff -r b1373253e202 -r 3e4905241838 src/buffer.c --- a/src/buffer.c Thu Dec 19 12:00:20 2024 +0100 +++ b/src/buffer.c Thu Dec 19 21:33:45 2024 +0100 @@ -31,14 +31,12 @@ #include #include -static int buffer_copy_on_write(CxBuffer* buffer, size_t newcap) { +static int buffer_copy_on_write(CxBuffer* buffer) { if (0 == (buffer->flags & CX_BUFFER_COPY_ON_WRITE)) return 0; - if (newcap == 0) newcap = buffer->capacity; - void *newspace = cxMalloc(buffer->allocator, newcap); + void *newspace = cxMalloc(buffer->allocator, buffer->capacity); if (NULL == newspace) return -1; memcpy(newspace, buffer->space, buffer->size); buffer->space = newspace; - buffer->capacity = newcap; buffer->flags &= ~CX_BUFFER_COPY_ON_WRITE; buffer->flags |= CX_BUFFER_FREE_CONTENTS; return 0; @@ -54,6 +52,9 @@ if (allocator == NULL) { allocator = cxDefaultAllocator; } + if (flags & CX_BUFFER_COPY_ON_EXTEND) { + flags |= CX_BUFFER_AUTO_EXTEND; + } buffer->allocator = allocator; buffer->flags = flags; if (!space) { @@ -170,8 +171,16 @@ return 0; } - if (buffer->flags & CX_BUFFER_COPY_ON_WRITE) { - return buffer_copy_on_write(buffer, newcap); + const int force_copy_flags = CX_BUFFER_COPY_ON_WRITE | CX_BUFFER_COPY_ON_EXTEND; + if (buffer->flags & force_copy_flags) { + void *newspace = cxMalloc(buffer->allocator, newcap); + if (NULL == newspace) return -1; + memcpy(newspace, buffer->space, buffer->size); + buffer->space = newspace; + buffer->capacity = newcap; + buffer->flags &= ~force_copy_flags; + buffer->flags |= CX_BUFFER_FREE_CONTENTS; + return 0; } else if (cxReallocate(buffer->allocator, (void **) &buffer->bytes, newcap) == 0) { buffer->capacity = newcap; @@ -224,7 +233,7 @@ ) { // optimize for easy case if (size == 1 && (buffer->capacity - buffer->pos) >= nitems) { - if (buffer_copy_on_write(buffer, 0)) return 0; + if (buffer_copy_on_write(buffer)) return 0; memcpy(buffer->bytes + buffer->pos, ptr, nitems); buffer->pos += nitems; if (buffer->pos > buffer->size) { @@ -311,7 +320,7 @@ return cxBufferWrite(ptr, size, nitems, buffer); } } else { - if (buffer_copy_on_write(buffer, 0)) return 0; + if (buffer_copy_on_write(buffer)) return 0; memcpy(buffer->bytes + buffer->pos, ptr, len); buffer->pos += len; if (buffer->pos > buffer->size) { @@ -395,7 +404,7 @@ if (shift >= buffer->size) { buffer->pos = buffer->size = 0; } else { - if (buffer_copy_on_write(buffer, 0)) return -1; + if (buffer_copy_on_write(buffer)) return -1; memmove(buffer->bytes, buffer->bytes + shift, buffer->size - shift); buffer->size -= shift; @@ -430,7 +439,7 @@ } if (movebytes > 0) { - if (buffer_copy_on_write(buffer, 0)) return -1; + if (buffer_copy_on_write(buffer)) return -1; memmove(buffer->bytes + shift, buffer->bytes, movebytes); buffer->size = shift + movebytes; } diff -r b1373253e202 -r 3e4905241838 src/cx/buffer.h --- a/src/cx/buffer.h Thu Dec 19 12:00:20 2024 +0100 +++ b/src/cx/buffer.h Thu Dec 19 21:33:45 2024 +0100 @@ -79,6 +79,15 @@ */ #define CX_BUFFER_COPY_ON_WRITE 0x04 +/** + * If this flag is enabled, the buffer will copy its contents to a new memory area on reallocation. + * + * After performing the copy, the flag is automatically cleared. + * This flag has no effect on buffers which do not have #CX_BUFFER_AUTO_EXTEND set, which is why + * buffers automatically admit the auto-extend flag when initialized with copy-on-extend enabled. + */ +#define CX_BUFFER_COPY_ON_EXTEND 0x08 + /** Structure for the UCX buffer data. */ typedef struct { /** A pointer to the buffer contents. */ @@ -156,6 +165,11 @@ * you will need to cast the pointer, and you should set the * #CX_BUFFER_COPY_ON_WRITE flag. * + * When you specify stack memory as \p space and decide to use + * the auto-extension feature, you \em must use the + * #CX_BUFFER_COPY_ON_EXTEND flag, instead of the + * #CX_BUFFER_AUTO_EXTEND flag. + * * \note You may provide \c NULL as argument for \p space. * Then this function will allocate the space and enforce * the #CX_BUFFER_FREE_CONTENTS flag. In that case, specifying @@ -213,6 +227,10 @@ * You may also provide a read-only \p space, in which case * you will need to cast the pointer, and you should set the * #CX_BUFFER_COPY_ON_WRITE flag. + * When you specify stack memory as \p space and decide to use + * the auto-extension feature, you \em must use the + * #CX_BUFFER_COPY_ON_EXTEND flag, instead of the + * #CX_BUFFER_AUTO_EXTEND flag. * * \note You may provide \c NULL as argument for \p space. * Then this function will allocate the space and enforce diff -r b1373253e202 -r 3e4905241838 tests/test_buffer.c --- a/tests/test_buffer.c Thu Dec 19 12:00:20 2024 +0100 +++ b/tests/test_buffer.c Thu Dec 19 21:33:45 2024 +0100 @@ -933,7 +933,39 @@ cxBufferDestroy(&buf); } -CX_TEST(test_buffer_put_string_extend_copy_on_write) { +CX_TEST(test_buffer_put_string_copy_on_extend) { + CxTestingAllocator talloc; + cx_testing_allocator_init(&talloc); + const CxAllocator *alloc = &talloc.base; + CxBuffer buf; + char original[16] = "preparedXXXXXXX\0"; + CX_TEST_DO { + cxBufferInit(&buf, original, 16, alloc, CX_BUFFER_COPY_ON_EXTEND); + buf.capacity = 8; + buf.size = buf.pos = 4; + size_t written = cxBufferPutString(&buf, "test"); + CX_TEST_ASSERT(written == 4); + CX_TEST_ASSERT(buf.size == 8); + CX_TEST_ASSERT(buf.pos == 8); + CX_TEST_ASSERT(buf.capacity == 8); + CX_TEST_ASSERT(0 == memcmp(buf.space, "preptest", 8)); + CX_TEST_ASSERT(original == buf.space); + written = cxBufferPutString(&buf, "ing"); + CX_TEST_ASSERT(written == 3); + CX_TEST_ASSERT(buf.size == 11); + CX_TEST_ASSERT(buf.pos == 11); + CX_TEST_ASSERT(buf.capacity >= 11); + CX_TEST_ASSERT(0 == memcmp(buf.space, "preptesting", 11)); + CX_TEST_ASSERT(original != buf.space); + CX_TEST_ASSERT(0 == memcmp(original, "preptestXXXXXXX\0", 16)); + CX_TEST_ASSERT(!cx_testing_allocator_verify(&talloc)); + cxBufferDestroy(&buf); + CX_TEST_ASSERT(cx_testing_allocator_verify(&talloc)); + cx_testing_allocator_destroy(&talloc); + } +} + +CX_TEST(test_buffer_put_string_copy_on_write) { CxBuffer buf; char original[16] = "preparedXXXXXXX\0"; cxBufferInit(&buf, original, 16, cxDefaultAllocator, CX_BUFFER_COPY_ON_WRITE); @@ -1289,7 +1321,8 @@ cx_test_register(suite, test_buffer_put_string_fit); cx_test_register(suite, test_buffer_put_string_discard); cx_test_register(suite, test_buffer_put_string_extend); - cx_test_register(suite, test_buffer_put_string_extend_copy_on_write); + cx_test_register(suite, test_buffer_put_string_copy_on_extend); + cx_test_register(suite, test_buffer_put_string_copy_on_write); cx_test_register(suite, test_buffer_terminate); cx_test_register(suite, test_buffer_write_size_overflow); cx_test_register(suite, test_buffer_write_capacity_overflow);