1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227
|
/*
* Copyright Redis Ltd. 2024 - present
*
* 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).
*
*
* WHAT IS MSTR (M-STRING)?
* ------------------------
* mstr stands for immutable string with optional metadata attached.
*
* sds string is widely used across the system and serves as a general purpose
* container to hold data. The need to optimize memory and aggregate strings
* along with metadata and store it into Redis data-structures as single bulk keep
* reoccur. One thought might be, why not to extend sds to support metadata. The
* answer is that sds is mutable string in its nature, with wide API (split, join,
* etc.). Pushing metadata logic into sds will make it very fragile, and complex
* to maintain.
*
* Another idea involved using a simple struct with flags and a dynamic buf[] at the
* end. While this could be viable, it introduces considerable complexity and would
* need maintenance across different contexts.
*
* As an alternative, we introduce a new implementation of immutable strings,
* with limited API, and with the option to attach metadata. The representation
* of the string, without any metadata, in its basic form, resembles SDS but
* without the API to manipulate the string. Only to attach metadata to it. The
* following diagram shows the memory layout of mstring (mstrhdr8) when no
* metadata is attached:
*
* +----------------------------------------------+
* | mstrhdr8 | c-string | |
* +--------------------------------+-------------+
* |8b |2b |1b |5b |?bytes |8b|
* | Len | Type |m-bit=0 | Unused | String |\0|
* +----------------------------------------------+
* ^
* |
* mstrNew() returns pointer to here --+
*
* If metadata-flag is set, depicted in diagram above as m-bit in the diagram,
* then the header will be preceded with additional 16 bits of metadata flags such
* that if i'th bit is set, then the i'th metadata structure is attached to the
* mstring. The metadata layout and their sizes are defined by mstrKind structure
* (More below).
*
* The following diagram shows the memory layout of mstr (mstrhdr8) when 3 bits in mFlags
* are set to indicate that 3 fields of metadata are attached to the mstring at the
* beginning.
*
* +-------------------------------------------------------------------------------+
* | METADATA FIELDS | mflags | mstrhdr8 | c-string | |
* +-----------------------+--------+--------------------------------+-------------+
* |?bytes |?bytes |?bytes |16b |8b |2b |1b |5b |?bytes |8b|
* | Meta3 | Meta2 | Meta0 | 0x1101 | Len | Type |m-bit=1 | Unused | String |\0|
* +-------------------------------------------------------------------------------+
* ^
* |
* mstrNewWithMeta() returns pointer to here --+
*
* mstr allows to define different kinds (groups) of mstrings, each with its
* own unique metadata layout. For example, in case of hash-fields, all instances of
* it can optionally have TTL metadata attached to it. This is achieved by first
* prototyping a single mstrKind structure that defines the metadata layout and sizes
* of this specific kind. Now each hash-field instance has still the freedom to
* attach or not attach the metadata to it, and metadata flags (mFlags) of the
* instance will reflect this decision.
*
* In the future, the keys of Redis keyspace can be another kind of mstring that
* has TTL, LRU or even dictEntry metadata embedded into. Unlike vptr in c++, this
* struct won't be attached to mstring but will be passed as yet another argument
* to API, to save memory. In addition, each instance of a given mstrkind can hold
* any subset of metadata and the 8 bits of metadata-flags will reflect it.
*
* The following example shows how to define mstrKind for possible future keyspace
* that aggregates several keyspace related metadata into one compact, singly
* allocated, mstring.
*
* typedef enum HkeyMetaFlags {
* HKEY_META_VAL_REF_COUNT = 0, // refcount
* HKEY_META_VAL_REF = 1, // Val referenced
* HKEY_META_EXPIRE = 2, // TTL and more
* HKEY_META_TYPE_ENC_LRU = 3, // TYPE + LRU + ENC
* HKEY_META_DICT_ENT_NEXT = 4, // Next dict entry
* // Following two must be together and in this order
* HKEY_META_VAL_EMBED8 = 5, // Val embedded, max 7 bytes
* HKEY_META_VAL_EMBED16 = 6, // Val embedded, max 15 bytes (23 with EMBED8)
* } HkeyMetaFlags;
*
* mstrKind hkeyKind = {
* .name = "hkey",
* .metaSize[HKEY_META_VAL_REF_COUNT] = 4,
* .metaSize[HKEY_META_VAL_REF] = 8,
* .metaSize[HKEY_META_EXPIRE] = sizeof(ExpireMeta),
* .metaSize[HKEY_META_TYPE_ENC_LRU] = 8,
* .metaSize[HKEY_META_DICT_ENT_NEXT] = 8,
* .metaSize[HKEY_META_VAL_EMBED8] = 8,
* .metaSize[HKEY_META_VAL_EMBED16] = 16,
* };
*
* MSTR-ALIGNMENT
* --------------
* There are two types of alignments to take into consideration:
* 1. Alignment of the metadata.
* 2. Alignment of returned mstr pointer
*
* 1) As the metadatas layout are reversed to their enumeration, it is recommended
* to put metadata with "better" alignment first in memory layout (enumerated
* last) and the worst, or those that simply don't require any alignment will be
* last in memory layout (enumerated first). This is similar the to the applied
* consideration when defining new struct in C. Note also that each metadata
* might either be attached to mstr or not which complicates the design phase
* of a new mstrKind a little.
*
* In the example above, HKEY_META_VAL_REF_COUNT, with worst alignment of 4
* bytes, is enumerated first, and therefore, will be last in memory layout.
*
* 2) Few optimizations in Redis rely on the fact that sds address is always an odd
* pointer. We can achieve the same with a little effort. It was already taken
* care that all headers of type mstrhdrX has odd size. With that in mind, if
* a new kind of mstr is required to be limited to odd addresses, then we must
* make sure that sizes of all related metadatas that are defined in mstrKind
* are even in size.
*/
#ifndef __MSTR_H
#define __MSTR_H
#include <sys/types.h>
#include <stdarg.h>
#include <stdint.h>
/* Selective copy of ifndef from server.h instead of including it */
#ifndef static_assert
#define static_assert(expr, lit) extern char __static_assert_failure[(expr) ? 1:-1]
#endif
#define MSTR_TYPE_5 0
#define MSTR_TYPE_8 1
#define MSTR_TYPE_16 2
#define MSTR_TYPE_64 3
#define MSTR_TYPE_MASK 3
#define MSTR_TYPE_BITS 2
#define MSTR_META_MASK 4
#define MSTR_HDR(T,s) ((struct mstrhdr##T *)((s)-(sizeof(struct mstrhdr##T))))
#define MSTR_HDR_VAR(T,s) struct mstrhdr##T *sh = (void*)((s)-(sizeof(struct mstrhdr##T)));
#define MSTR_META_BITS 1 /* is metadata attached? */
#define MSTR_TYPE_5_LEN(f) ((f) >> (MSTR_TYPE_BITS + MSTR_META_BITS))
#define CREATE_MSTR_INFO(len, ismeta, type) ( (((len<<MSTR_META_BITS) + ismeta) << (MSTR_TYPE_BITS)) | type )
/* mimic plain c-string */
typedef char *mstr;
/* Flags that can be set on mstring to indicate for attached metadata. It is
* */
typedef uint16_t mstrFlags;
struct __attribute__ ((__packed__)) mstrhdr5 {
unsigned char info; /* 2 lsb of type, 1 metadata, and 5 msb of string length */
char buf[];
};
struct __attribute__ ((__packed__)) mstrhdr8 {
uint8_t unused; /* To achieve odd size header (See comment above) */
uint8_t len;
unsigned char info; /* 2 lsb of type, 6 unused bits */
char buf[];
};
struct __attribute__ ((__packed__)) mstrhdr16 {
uint16_t len;
unsigned char info; /* 2 lsb of type, 6 unused bits */
char buf[];
};
struct __attribute__ ((__packed__)) mstrhdr64 {
uint64_t len;
unsigned char info; /* 2 lsb of type, 6 unused bits */
char buf[];
};
#define NUM_MSTR_FLAGS (sizeof(mstrFlags)*8)
/* mstrKind is used to define a kind (a group) of mstring with its own metadata layout */
typedef struct mstrKind {
const char *name;
int metaSize[NUM_MSTR_FLAGS];
} mstrKind;
mstr mstrNew(const char *initStr, size_t lenStr, int trymalloc);
mstr mstrNewWithMeta(struct mstrKind *kind, const char *initStr, size_t lenStr, mstrFlags flags, int trymalloc);
mstr mstrNewCopy(struct mstrKind *kind, mstr src, mstrFlags newFlags);
void *mstrGetAllocPtr(struct mstrKind *kind, mstr str);
void mstrFree(struct mstrKind *kind, mstr s);
mstrFlags *mstrFlagsRef(mstr s);
void *mstrMetaRef(mstr s, struct mstrKind *kind, int flagIdx);
size_t mstrlen(const mstr s);
/* return non-zero if metadata is attached to mstring */
static inline int mstrIsMetaAttached(mstr s) { return s[-1] & MSTR_META_MASK; }
/* return whether if a specific flag-index is set */
static inline int mstrGetFlag(mstr s, int flagIdx) { return *mstrFlagsRef(s) & (1 << flagIdx); }
/* DEBUG */
void mstrPrint(mstr s, struct mstrKind *kind, int verbose);
/* See comment above about MSTR-ALIGNMENT(2) */
static_assert(sizeof(struct mstrhdr5 ) % 2 == 1, "must be odd");
static_assert(sizeof(struct mstrhdr8 ) % 2 == 1, "must be odd");
static_assert(sizeof(struct mstrhdr16 ) % 2 == 1, "must be odd");
static_assert(sizeof(struct mstrhdr64 ) % 2 == 1, "must be odd");
static_assert(sizeof(mstrFlags ) % 2 == 0, "must be even to keep mstr pointer odd");
#ifdef REDIS_TEST
int mstrTest(int argc, char *argv[], int flags);
#endif
#endif
|