diff --git a/src/Makefile b/src/Makefile index b3ebd13b8..c202a233d 100644 --- a/src/Makefile +++ b/src/Makefile @@ -382,7 +382,7 @@ endif REDIS_SERVER_NAME=redis-server$(PROG_SUFFIX) REDIS_SENTINEL_NAME=redis-sentinel$(PROG_SUFFIX) -REDIS_SERVER_OBJ=threads_mngr.o memory_prefetch.o adlist.o quicklist.o ae.o anet.o dict.o ebuckets.o eventnotifier.o iothread.o mstr.o entry.o kvstore.o fwtree.o estore.o server.o sds.o zmalloc.o lzf_c.o lzf_d.o pqsort.o zipmap.o sha1.o ziplist.o release.o networking.o util.o object.o db.o replication.o rdb.o t_string.o t_list.o t_set.o t_zset.o t_hash.o config.o aof.o pubsub.o multi.o debug.o sort.o intset.o syncio.o cluster.o cluster_asm.o cluster_legacy.o cluster_slot_stats.o crc16.o endianconv.o slowlog.o eval.o bio.o rio.o rand.o memtest.o syscheck.o crcspeed.o crccombine.o crc64.o bitops.o sentinel.o notify.o setproctitle.o blocked.o hyperloglog.o latency.o sparkline.o redis-check-rdb.o redis-check-aof.o geo.o lazyfree.o module.o evict.o expire.o geohash.o geohash_helper.o childinfo.o defrag.o siphash.o rax.o t_stream.o listpack.o localtime.o lolwut.o lolwut5.o lolwut6.o lolwut8.o acl.o tracking.o socket.o tls.o sha256.o timeout.o setcpuaffinity.o monotonic.o mt19937-64.o resp_parser.o call_reply.o script_lua.o script.o functions.o function_lua.o commands.o strl.o connection.o unix.o logreqres.o keymeta.o chk.o hotkeys.o gcra.o +REDIS_SERVER_OBJ=threads_mngr.o memory_prefetch.o adlist.o quicklist.o ae.o anet.o dict.o ebuckets.o eventnotifier.o iothread.o mstr.o entry.o kvstore.o fwtree.o estore.o server.o sds.o zmalloc.o lzf_c.o lzf_d.o pqsort.o zipmap.o sha1.o ziplist.o release.o networking.o util.o object.o db.o replication.o rdb.o t_string.o t_list.o t_set.o t_zset.o t_hash.o config.o aof.o pubsub.o multi.o debug.o sort.o intset.o syncio.o cluster.o cluster_asm.o cluster_legacy.o cluster_slot_stats.o crc16.o endianconv.o slowlog.o eval.o bio.o rio.o rand.o memtest.o syscheck.o crcspeed.o crccombine.o crc64.o bitops.o sentinel.o notify.o setproctitle.o blocked.o hyperloglog.o latency.o sparkline.o redis-check-rdb.o redis-check-aof.o geo.o lazyfree.o module.o evict.o expire.o geohash.o geohash_helper.o childinfo.o defrag.o siphash.o rax.o t_stream.o listpack.o localtime.o lolwut.o lolwut5.o lolwut6.o lolwut8.o acl.o tracking.o socket.o tls.o sha256.o timeout.o setcpuaffinity.o monotonic.o mt19937-64.o resp_parser.o call_reply.o script_lua.o script.o functions.o function_lua.o commands.o strl.o connection.o unix.o logreqres.o keymeta.o chk.o hotkeys.o gcra.o vector.o REDIS_CLI_NAME=redis-cli$(PROG_SUFFIX) REDIS_CLI_OBJ=anet.o adlist.o dict.o redis-cli.o zmalloc.o release.o ae.o redisassert.o crcspeed.o crccombine.o crc64.o siphash.o crc16.o monotonic.o cli_common.o mt19937-64.o strl.o cli_commands.o REDIS_BENCHMARK_NAME=redis-benchmark$(PROG_SUFFIX) diff --git a/src/server.c b/src/server.c index c7f415717..4b2d0191c 100644 --- a/src/server.c +++ b/src/server.c @@ -7797,6 +7797,7 @@ int __test_num = 0; typedef int redisTestProc(int argc, char **argv, int flags); int bitopsTest(int argc, char **argv, int flags); int zsetTest(int argc, char **argv, int flags); +int vectorTest(int argc, char **argv, int flags); struct redisTest { char *name; redisTestProc *proc; @@ -7820,6 +7821,7 @@ struct redisTest { {"fwtree", fwtreeTest}, {"estore", estoreTest}, {"ebuckets", ebucketsTest}, + {"vector", vectorTest}, {"bitmap", bitopsTest}, {"rax", raxTest}, {"zset", zsetTest}, diff --git a/src/vector.c b/src/vector.c new file mode 100644 index 000000000..e5809dabb --- /dev/null +++ b/src/vector.c @@ -0,0 +1,173 @@ +/* vector.c - Simple append-only vector implementation + * + * Copyright (c) 2026-Present, Redis Ltd. + * All rights reserved. + * + * Licensed under your choice of (a) the Redis Source Available License 2.0 + * (RSALv2); or (b) the Server Side Public License v1 (SSPLv1); or (c) the + * GNU Affero General Public License v3 (AGPLv3). + */ + +#include +#include +#include + +#include "vector.h" +#include "redisassert.h" +#include "zmalloc.h" + +#define VEC_DEFAULT_INITCAP 8 + +/* + * Vector initialization. + * + * Modes: + * - stack != NULL: use caller-provided storage for the first initcap items. + * - stack == NULL && initcap > 0: start heap-backed with an initial 'initcap' capacity. + * - stack == NULL && initcap == 0: start heap-backed with no initial storage. + */ +void vecInit(vec *v, void **stack, size_t initcap) { + /* If stack is provided, initcap must be > 0 and at the size of the stack */ + assert(initcap > 0 || stack == NULL); + + v->size = 0; + v->cap = initcap; + v->stack = stack; /* stack is NULL if not used */ + + /* now init data either stack, heap or NULL */ + v->data = (stack) ? stack : ((initcap > 0) ? zmalloc(initcap * sizeof(void *)) : NULL); +} + +/* Free only heap storage if any */ +void vecRelease(vec *v) { + /* if data is not stack-allocated and is not NULL, free it */ + if (v->data && v->data != v->stack) + zfree(v->data); + v->size = 0; + v->cap = 0; + v->data = NULL; + v->stack = NULL; +} + +/* Reset the logical length to zero while preserving allocated storage. */ +void vecClear(vec *v) { + v->size = 0; +} + +/* Return the number of elements in the vector. */ +size_t vecSize(const vec *v) { + return v->size; +} + +/* Get element at index. index must be < vecSize(v). */ +void *vecGet(const vec *v, size_t index) { + assert(index < v->size); + return v->data[index]; +} + +/* Return the contiguous backing array. */ +void **vecData(vec *v) { + return v->data; +} + +/* Ensure capacity is at least mincap. */ +void vecReserve(vec *v, size_t mincap) { + void **newdata; + + if (mincap <= v->cap) return; + + /* If no heap storage is used yet, allocate and copy from stack if needed. */ + if (v->data == v->stack) { + newdata = zmalloc(mincap * sizeof(void *)); + if (v->size) memcpy(newdata, v->data, v->size * sizeof(void *)); + } else { + newdata = zrealloc(v->data, mincap * sizeof(void *)); + } + + v->data = newdata; + v->cap = mincap; +} + +/* Append one element, growing storage as needed. */ +void vecPush(vec *v, void *value) { + if (v->size == v->cap) { + size_t newcap = (v->cap > 0) ? v->cap * 2 : VEC_DEFAULT_INITCAP; + vecReserve(v, newcap); + } + + v->data[v->size++] = value; +} + +#ifdef REDIS_TEST + +#include +#include + +#include "testhelp.h" + +#define UNUSED(x) (void)(x) + +int vectorTest(int argc, char **argv, int flags) +{ + UNUSED(argc); + UNUSED(argv); + UNUSED(flags); + + vec v; + void *vstack[2]; + int one = 1, two = 2, three = 3, four = 4, five = 5, six = 6; + + vecInit(&v, vstack, 2); + test_cond("vecInit() stack-backed size is 0", vecSize(&v) == 0); + test_cond("vecInit() uses stack buffer", vecData(&v) == vstack); + vecReserve(&v, 1); + test_cond("vecReserve() no-ops when capacity is already sufficient", + v.cap == 2 && vecData(&v) == vstack); + vecPush(&v, &one); + vecPush(&v, &two); + test_cond("vecPush() appends into stack storage", + vecSize(&v) == 2 && vecData(&v) == vstack && + vecGet(&v, 0) == &one && vecGet(&v, 1) == &two); + vecReserve(&v, 4); + test_cond("vecReserve() spills from stack to heap preserving values", + v.cap == 4 && vecData(&v) != vstack && + vecGet(&v, 0) == &one && vecGet(&v, 1) == &two); + vecPush(&v, &three); + test_cond("vecPush() spills from stack to heap preserving values", + vecSize(&v) == 3 && + vecData(&v) != vstack && vecGet(&v, 0) == &one && + vecGet(&v, 1) == &two && vecGet(&v, 2) == &three); + + void **heap_data = vecData(&v); + vecClear(&v); + test_cond("vecClear() resets size but preserves storage", + vecSize(&v) == 0 && vecData(&v) == heap_data); + vecRelease(&v); + test_cond("vecRelease() resets vector state", + vecSize(&v) == 0 && vecData(&v) == NULL && v.cap == 0); + + vecInit(&v, NULL, 4); + test_cond("vecInit() heap-backed hint allocates storage", + vecSize(&v) == 0 && vecData(&v) != NULL && v.cap == 4); + vecPush(&v, &four); + test_cond("vecPush() works in heap-backed mode", + vecGet(&v, 0) == &four); + vecReserve(&v, 8); + test_cond("vecReserve() grows heap-backed storage preserving values", + v.cap == 8 && vecGet(&v, 0) == &four); + vecRelease(&v); + + vecInit(&v, NULL, 0); + vecReserve(&v, 6); + test_cond("vecReserve() allocates heap storage from empty vector", + v.cap == 6 && vecData(&v) != NULL); + vecPush(&v, &five); + vecPush(&v, &six); + test_cond("vecPush() works after vecReserve() on empty vector", + vecSize(&v) == 2 && + vecGet(&v, 0) == &five && vecGet(&v, 1) == &six); + vecRelease(&v); + + return 0; +} +#endif diff --git a/src/vector.h b/src/vector.h new file mode 100644 index 000000000..a3ea28505 --- /dev/null +++ b/src/vector.h @@ -0,0 +1,92 @@ +#ifndef REDIS_VECTOR_H +#define REDIS_VECTOR_H + +#include + +/* + * Simple append-only vector (dynamic array) of void * elements. + * + * Design: + * -------- + * - Stores elements in a contiguous array (void **). + * - Supports append (vecPush) and read access. + * - Optionally uses caller-provided stack buffer to avoid heap allocations. + * - See also comment in vector.c of vecInit() for more details. + * + * Memory: + * ------- + * - vecRelease() frees heap memory if used. + * - Stack buffer is never freed. + * - Stored elements are never freed. + * + * Modes: + * ------- + * 1. Start On Stack (grow to heap): vec v; + * void *vstack[8]; + * ... + * vecInit(&v, vstack, 8); + * + * Start Embedded (grow to heap): typedef struct { + * vec v; + * void *vembedded[8]; + * } obj; + * ... + * vecInit(&obj->v, obj->vembedded, 8); + * + * 2. Heap only, init capacity 8: vec v; + * ... + * vecInit(&v, NULL, 8); + * + * Heap only, init capacity 0: vec v; + * ... + * vecInit(&v, NULL, 0); + * + * 3. Depends on var size: vec v; + * void *vstack[8]; + * vecInit(&v, vstack, 8); + * vecReserve(&v, varsize); // varsize <= 8 ? stack : heap + * + * Notes: + * ------ + * - Not thread-safe. + * - If stack == NULL and initcap > 0, initcap is treated as an initial + * heap-capacity hint. + * - When used in Redis core, the implementation should use the Redis allocator + * wrappers (zmalloc / zrealloc / zfree) rather than libc allocation APIs. + */ + +typedef struct vec { + size_t size; /* Number of elements in the vector. */ + size_t cap; /* Capacity of the vector. */ + void **data; /* Heap-allocated storage or refers to stack. */ + void **stack; /* Optional stack buffer. */ +} vec; + +/* Initialize a vector */ +void vecInit(vec *v, void **stack, size_t initcap); + +/* Free only heap storage if any */ +void vecRelease(vec *v); + +/* Reset the logical length to zero while preserving allocated storage. */ +void vecClear(vec *v); + +size_t vecSize(const vec *v); + +/* Requires index < vecSize(v). */ +void *vecGet(const vec *v, size_t index); + +/* Return the contiguous backing array. */ +void **vecData(vec *v); + +/* Ensure capacity is at least mincap. */ +void vecReserve(vec *v, size_t mincap); + +/* Append one element, growing storage as needed. */ +void vecPush(vec *v, void *value); + +#ifdef REDIS_TEST +int vectorTest(int argc, char **argv, int flags); +#endif + +#endif /* REDIS_VECTOR_H */