File: mstr.h

package info (click to toggle)
redis 5%3A8.0.4-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 21,916 kB
  • sloc: ansic: 217,142; tcl: 51,874; sh: 4,625; perl: 4,214; cpp: 3,568; python: 3,165; makefile: 2,055; ruby: 639; javascript: 30; csh: 7
file content (227 lines) | stat: -rw-r--r-- 10,032 bytes parent folder | download | duplicates (3)
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