|
1 | 1 | /*-------------------------------------------------------------------------
|
2 | 2 | *
|
3 | 3 | * list.c
|
4 |
| - * implementation for PostgreSQL generic linked list package |
| 4 | + * implementation for PostgreSQL generic list package |
5 | 5 | *
|
6 | 6 | *
|
7 |
| - * Portions Copyright (c) 1996-2016, PostgreSQL Global Development Group |
| 7 | + * Portions Copyright (c) 1996-2020, PostgreSQL Global Development Group |
8 | 8 | * Portions Copyright (c) 1994, Regents of the University of California
|
9 | 9 | *
|
10 | 10 | *
|
|
13 | 13 | *
|
14 | 14 | *-------------------------------------------------------------------------
|
15 | 15 | */
|
16 |
| -#define _GNU_SOURCE |
17 | 16 | #include "postgres.h"
|
| 17 | + |
18 | 18 | #include "nodes/pg_list.h"
|
19 | 19 |
|
| 20 | +#if PG_VERSION_NUM < 130000 |
20 | 21 |
|
21 | 22 | #define IsPointerList(l) ((l) == NIL || IsA((l), List))
|
22 | 23 | #define IsIntegerList(l) ((l) == NIL || IsA((l), IntList))
|
@@ -141,3 +142,306 @@ lcons(void *datum, List *list)
|
141 | 142 | return list;
|
142 | 143 |
|
143 | 144 | }
|
| 145 | + |
| 146 | +#else /* PG_VERSION_NUM >= 130000 */ |
| 147 | + |
| 148 | +/*------------------------------------------------------------------------- |
| 149 | + * |
| 150 | + * This was taken from src/backend/nodes/list.c PostgreSQL-13 source code. |
| 151 | + * We only need lappend() and lcons() and their dependencies. |
| 152 | + * There is one change: we use palloc() instead MemoryContextAlloc() in |
| 153 | + * enlarge_list() (see #defines). |
| 154 | + * |
| 155 | + *------------------------------------------------------------------------- |
| 156 | + */ |
| 157 | +#include "port/pg_bitutils.h" |
| 158 | +#include "utils/memdebug.h" |
| 159 | +#include "utils/memutils.h" |
| 160 | + |
| 161 | +#define MemoryContextAlloc(c, s) palloc(s) |
| 162 | +#define GetMemoryChunkContext(l) 0 |
| 163 | + |
| 164 | +/* |
| 165 | + * The previous List implementation, since it used a separate palloc chunk |
| 166 | + * for each cons cell, had the property that adding or deleting list cells |
| 167 | + * did not move the storage of other existing cells in the list. Quite a |
| 168 | + * bit of existing code depended on that, by retaining ListCell pointers |
| 169 | + * across such operations on a list. There is no such guarantee in this |
| 170 | + * implementation, so instead we have debugging support that is meant to |
| 171 | + * help flush out now-broken assumptions. Defining DEBUG_LIST_MEMORY_USAGE |
| 172 | + * while building this file causes the List operations to forcibly move |
| 173 | + * all cells in a list whenever a cell is added or deleted. In combination |
| 174 | + * with MEMORY_CONTEXT_CHECKING and/or Valgrind, this can usually expose |
| 175 | + * broken code. It's a bit expensive though, as there's many more palloc |
| 176 | + * cycles and a lot more data-copying than in a default build. |
| 177 | + * |
| 178 | + * By default, we enable this when building for Valgrind. |
| 179 | + */ |
| 180 | +#ifdef USE_VALGRIND |
| 181 | +#define DEBUG_LIST_MEMORY_USAGE |
| 182 | +#endif |
| 183 | + |
| 184 | +/* Overhead for the fixed part of a List header, measured in ListCells */ |
| 185 | +#define LIST_HEADER_OVERHEAD \ |
| 186 | + ((int) ((offsetof(List, initial_elements) - 1) / sizeof(ListCell) + 1)) |
| 187 | + |
| 188 | +/* |
| 189 | + * Macros to simplify writing assertions about the type of a list; a |
| 190 | + * NIL list is considered to be an empty list of any type. |
| 191 | + */ |
| 192 | +#define IsPointerList(l) ((l) == NIL || IsA((l), List)) |
| 193 | +#define IsIntegerList(l) ((l) == NIL || IsA((l), IntList)) |
| 194 | +#define IsOidList(l) ((l) == NIL || IsA((l), OidList)) |
| 195 | + |
| 196 | +#ifdef USE_ASSERT_CHECKING |
| 197 | +/* |
| 198 | + * Check that the specified List is valid (so far as we can tell). |
| 199 | + */ |
| 200 | +static void |
| 201 | +check_list_invariants(const List *list) |
| 202 | +{ |
| 203 | + if (list == NIL) |
| 204 | + return; |
| 205 | + |
| 206 | + Assert(list->length > 0); |
| 207 | + Assert(list->length <= list->max_length); |
| 208 | + Assert(list->elements != NULL); |
| 209 | + |
| 210 | + Assert(list->type == T_List || |
| 211 | + list->type == T_IntList || |
| 212 | + list->type == T_OidList); |
| 213 | +} |
| 214 | +#else |
| 215 | +#define check_list_invariants(l) ((void) 0) |
| 216 | +#endif /* USE_ASSERT_CHECKING */ |
| 217 | + |
| 218 | +/* |
| 219 | + * Return a freshly allocated List with room for at least min_size cells. |
| 220 | + * |
| 221 | + * Since empty non-NIL lists are invalid, new_list() sets the initial length |
| 222 | + * to min_size, effectively marking that number of cells as valid; the caller |
| 223 | + * is responsible for filling in their data. |
| 224 | + */ |
| 225 | +static List * |
| 226 | +new_list(NodeTag type, int min_size) |
| 227 | +{ |
| 228 | + List *newlist; |
| 229 | + int max_size; |
| 230 | + |
| 231 | + Assert(min_size > 0); |
| 232 | + |
| 233 | + /* |
| 234 | + * We allocate all the requested cells, and possibly some more, as part of |
| 235 | + * the same palloc request as the List header. This is a big win for the |
| 236 | + * typical case of short fixed-length lists. It can lose if we allocate a |
| 237 | + * moderately long list and then it gets extended; we'll be wasting more |
| 238 | + * initial_elements[] space than if we'd made the header small. However, |
| 239 | + * rounding up the request as we do in the normal code path provides some |
| 240 | + * defense against small extensions. |
| 241 | + */ |
| 242 | + |
| 243 | +#ifndef DEBUG_LIST_MEMORY_USAGE |
| 244 | + |
| 245 | + /* |
| 246 | + * Normally, we set up a list with some extra cells, to allow it to grow |
| 247 | + * without a repalloc. Prefer cell counts chosen to make the total |
| 248 | + * allocation a power-of-2, since palloc would round it up to that anyway. |
| 249 | + * (That stops being true for very large allocations, but very long lists |
| 250 | + * are infrequent, so it doesn't seem worth special logic for such cases.) |
| 251 | + * |
| 252 | + * The minimum allocation is 8 ListCell units, providing either 4 or 5 |
| 253 | + * available ListCells depending on the machine's word width. Counting |
| 254 | + * palloc's overhead, this uses the same amount of space as a one-cell |
| 255 | + * list did in the old implementation, and less space for any longer list. |
| 256 | + * |
| 257 | + * We needn't worry about integer overflow; no caller passes min_size |
| 258 | + * that's more than twice the size of an existing list, so the size limits |
| 259 | + * within palloc will ensure that we don't overflow here. |
| 260 | + */ |
| 261 | + max_size = pg_nextpower2_32(Max(8, min_size + LIST_HEADER_OVERHEAD)); |
| 262 | + max_size -= LIST_HEADER_OVERHEAD; |
| 263 | +#else |
| 264 | + |
| 265 | + /* |
| 266 | + * For debugging, don't allow any extra space. This forces any cell |
| 267 | + * addition to go through enlarge_list() and thus move the existing data. |
| 268 | + */ |
| 269 | + max_size = min_size; |
| 270 | +#endif |
| 271 | + |
| 272 | + newlist = (List *) palloc(offsetof(List, initial_elements) + |
| 273 | + max_size * sizeof(ListCell)); |
| 274 | + newlist->type = type; |
| 275 | + newlist->length = min_size; |
| 276 | + newlist->max_length = max_size; |
| 277 | + newlist->elements = newlist->initial_elements; |
| 278 | + |
| 279 | + return newlist; |
| 280 | +} |
| 281 | + |
| 282 | +/* |
| 283 | + * Enlarge an existing non-NIL List to have room for at least min_size cells. |
| 284 | + * |
| 285 | + * This does *not* update list->length, as some callers would find that |
| 286 | + * inconvenient. (list->length had better be the correct number of existing |
| 287 | + * valid cells, though.) |
| 288 | + */ |
| 289 | +static void |
| 290 | +enlarge_list(List *list, int min_size) |
| 291 | +{ |
| 292 | + int new_max_len; |
| 293 | + |
| 294 | + Assert(min_size > list->max_length); /* else we shouldn't be here */ |
| 295 | + |
| 296 | +#ifndef DEBUG_LIST_MEMORY_USAGE |
| 297 | + |
| 298 | + /* |
| 299 | + * As above, we prefer power-of-two total allocations; but here we need |
| 300 | + * not account for list header overhead. |
| 301 | + */ |
| 302 | + |
| 303 | + /* clamp the minimum value to 16, a semi-arbitrary small power of 2 */ |
| 304 | + new_max_len = pg_nextpower2_32(Max(16, min_size)); |
| 305 | + |
| 306 | +#else |
| 307 | + /* As above, don't allocate anything extra */ |
| 308 | + new_max_len = min_size; |
| 309 | +#endif |
| 310 | + |
| 311 | + if (list->elements == list->initial_elements) |
| 312 | + { |
| 313 | + /* |
| 314 | + * Replace original in-line allocation with a separate palloc block. |
| 315 | + * Ensure it is in the same memory context as the List header. (The |
| 316 | + * previous List implementation did not offer any guarantees about |
| 317 | + * keeping all list cells in the same context, but it seems reasonable |
| 318 | + * to create such a guarantee now.) |
| 319 | + */ |
| 320 | + list->elements = (ListCell *) |
| 321 | + MemoryContextAlloc(GetMemoryChunkContext(list), |
| 322 | + new_max_len * sizeof(ListCell)); |
| 323 | + memcpy(list->elements, list->initial_elements, |
| 324 | + list->length * sizeof(ListCell)); |
| 325 | + |
| 326 | + /* |
| 327 | + * We must not move the list header, so it's unsafe to try to reclaim |
| 328 | + * the initial_elements[] space via repalloc. In debugging builds, |
| 329 | + * however, we can clear that space and/or mark it inaccessible. |
| 330 | + * (wipe_mem includes VALGRIND_MAKE_MEM_NOACCESS.) |
| 331 | + */ |
| 332 | +#ifdef CLOBBER_FREED_MEMORY |
| 333 | + wipe_mem(list->initial_elements, |
| 334 | + list->max_length * sizeof(ListCell)); |
| 335 | +#else |
| 336 | + VALGRIND_MAKE_MEM_NOACCESS(list->initial_elements, |
| 337 | + list->max_length * sizeof(ListCell)); |
| 338 | +#endif |
| 339 | + } |
| 340 | + else |
| 341 | + { |
| 342 | +#ifndef DEBUG_LIST_MEMORY_USAGE |
| 343 | + /* Normally, let repalloc deal with enlargement */ |
| 344 | + list->elements = (ListCell *) repalloc(list->elements, |
| 345 | + new_max_len * sizeof(ListCell)); |
| 346 | +#else |
| 347 | + /* |
| 348 | + * repalloc() might enlarge the space in-place, which we don't want |
| 349 | + * for debugging purposes, so forcibly move the data somewhere else. |
| 350 | + */ |
| 351 | + ListCell *newelements; |
| 352 | + |
| 353 | + newelements = (ListCell *) |
| 354 | + MemoryContextAlloc(GetMemoryChunkContext(list), |
| 355 | + new_max_len * sizeof(ListCell)); |
| 356 | + memcpy(newelements, list->elements, |
| 357 | + list->length * sizeof(ListCell)); |
| 358 | + pfree(list->elements); |
| 359 | + list->elements = newelements; |
| 360 | +#endif |
| 361 | + } |
| 362 | + |
| 363 | + list->max_length = new_max_len; |
| 364 | +} |
| 365 | + |
| 366 | +/* |
| 367 | + * Make room for a new head cell in the given (non-NIL) list. |
| 368 | + * |
| 369 | + * The data in the new head cell is undefined; the caller should be |
| 370 | + * sure to fill it in |
| 371 | + */ |
| 372 | +static void |
| 373 | +new_head_cell(List *list) |
| 374 | +{ |
| 375 | + /* Enlarge array if necessary */ |
| 376 | + if (list->length >= list->max_length) |
| 377 | + enlarge_list(list, list->length + 1); |
| 378 | + /* Now shove the existing data over */ |
| 379 | + memmove(&list->elements[1], &list->elements[0], |
| 380 | + list->length * sizeof(ListCell)); |
| 381 | + list->length++; |
| 382 | +} |
| 383 | + |
| 384 | +/* |
| 385 | + * Make room for a new tail cell in the given (non-NIL) list. |
| 386 | + * |
| 387 | + * The data in the new tail cell is undefined; the caller should be |
| 388 | + * sure to fill it in |
| 389 | + */ |
| 390 | +static void |
| 391 | +new_tail_cell(List *list) |
| 392 | +{ |
| 393 | + /* Enlarge array if necessary */ |
| 394 | + if (list->length >= list->max_length) |
| 395 | + enlarge_list(list, list->length + 1); |
| 396 | + list->length++; |
| 397 | +} |
| 398 | + |
| 399 | +/* |
| 400 | + * Append a pointer to the list. A pointer to the modified list is |
| 401 | + * returned. Note that this function may or may not destructively |
| 402 | + * modify the list; callers should always use this function's return |
| 403 | + * value, rather than continuing to use the pointer passed as the |
| 404 | + * first argument. |
| 405 | + */ |
| 406 | +List * |
| 407 | +lappend(List *list, void *datum) |
| 408 | +{ |
| 409 | + Assert(IsPointerList(list)); |
| 410 | + |
| 411 | + if (list == NIL) |
| 412 | + list = new_list(T_List, 1); |
| 413 | + else |
| 414 | + new_tail_cell(list); |
| 415 | + |
| 416 | + lfirst(list_tail(list)) = datum; |
| 417 | + check_list_invariants(list); |
| 418 | + return list; |
| 419 | +} |
| 420 | + |
| 421 | +/* |
| 422 | + * Prepend a new element to the list. A pointer to the modified list |
| 423 | + * is returned. Note that this function may or may not destructively |
| 424 | + * modify the list; callers should always use this function's return |
| 425 | + * value, rather than continuing to use the pointer passed as the |
| 426 | + * second argument. |
| 427 | + * |
| 428 | + * Caution: before Postgres 8.0, the original List was unmodified and |
| 429 | + * could be considered to retain its separate identity. This is no longer |
| 430 | + * the case. |
| 431 | + */ |
| 432 | +List * |
| 433 | +lcons(void *datum, List *list) |
| 434 | +{ |
| 435 | + Assert(IsPointerList(list)); |
| 436 | + |
| 437 | + if (list == NIL) |
| 438 | + list = new_list(T_List, 1); |
| 439 | + else |
| 440 | + new_head_cell(list); |
| 441 | + |
| 442 | + lfirst(list_head(list)) = datum; |
| 443 | + check_list_invariants(list); |
| 444 | + return list; |
| 445 | +} |
| 446 | + |
| 447 | +#endif /* PG_VERSION_NUM */ |
0 commit comments