0001-pg_stack_alloc-Provide-API-for-stack-allocation.patch
text/x-patch
Filename: 0001-pg_stack_alloc-Provide-API-for-stack-allocation.patch
Type: text/x-patch
Part: 0
Patch
Same data as JSON:
GET /api/v1/attachments/:id/patch
the parsed metadata as JSON — format, series position, per-file stats; never the diff bytes.
API reference →
Format: format-patch
Series: patch 0001
Subject: pg_stack_alloc: Provide API for stack allocation.
| File | + | − |
|---|---|---|
| src/include/utils/pg_stack_alloc.h | 315 | 0 |
| src/test/regress/expected/internals.out | 12 | 0 |
| src/test/regress/parallel_schedule | 1 | 1 |
| src/test/regress/regress.c | 51 | 0 |
| src/test/regress/sql/internals.sql | 11 | 0 |
From 65ed1421fe1469bd7152db9009b3a22cf82a9ecb Mon Sep 17 00:00:00 2001
From: Thomas Munro <thomas.munro@gmail.com>
Date: Fri, 27 Feb 2026 22:21:36 +1300
Subject: [PATCH 1/2] pg_stack_alloc: Provide API for stack allocation.
Several places have long used a large array on the stack for temporary
objects of dynamic size, and switched to palloc()/pfree() when it was
full. This commit provides a centralized API for that pattern.
It is implemented the same way, but the interface looks like palloc.h's,
including the type-safety features added by commit 2016055a and later
developments. A small set of functions like strdup() is also provided,
for common operations.
Later patches will adopt the new API in various places.
Reviewed-by:
Discussion: https://postgr.es/m/CA%2BhUKG%2BixUUYOGRcuZpkk5pmJZaUQv6VCPAjdTZXFP5vA8jxcA%40mail.gmail.com
---
src/include/utils/pg_stack_alloc.h | 315 ++++++++++++++++++++++++
src/test/regress/expected/internals.out | 12 +
src/test/regress/parallel_schedule | 2 +-
src/test/regress/regress.c | 51 ++++
src/test/regress/sql/internals.sql | 11 +
5 files changed, 390 insertions(+), 1 deletion(-)
create mode 100644 src/include/utils/pg_stack_alloc.h
create mode 100644 src/test/regress/expected/internals.out
create mode 100644 src/test/regress/sql/internals.sql
diff --git a/src/include/utils/pg_stack_alloc.h b/src/include/utils/pg_stack_alloc.h
new file mode 100644
index 00000000000..bb8c58597b9
--- /dev/null
+++ b/src/include/utils/pg_stack_alloc.h
@@ -0,0 +1,315 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_stack_alloc.h
+ * Allocator for objects that don't escape the current function.
+ *
+ * A palloc()-like interface for allocating memory on the stack. The initial
+ * implementation uses an array declared statically.
+ *
+ * Once stack space is exhausted, allocations silently fall back to using
+ * palloc(). Memory should therefore still be freed explicitly with
+ * pg_stack_free() or MemoryContext-level cleanup. It is a no-op in the
+ * common case that pfree() doesn't need to be called.
+ *
+ * XXX A space-limited version of alloca() could be added.
+ *
+ * XXX It might be possible to use something like "defer" or equivalent
+ * compiler extensions to clean up palloc()'d memory automatically, in future
+ * work, and then pg_stack_free() would not be necessary.
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/utils/pg_stack_alloc.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PG_STACK_ALLOC_H
+#define PG_STACK_ALLOC_H
+
+#include "utils/elog.h"
+#include "utils/palloc.h"
+#include "miscadmin.h"
+
+#include <limits.h>
+#include <unistd.h>
+
+
+/* #define PG_STACK_USE_PALLOC_LOG "/tmp/pg_stack_alloc.csv" */
+
+/* Choose which implementation to use, if not already defined manually. */
+#if !defined(PG_STACK_USE_ARRAY) && \
+ !defined(PG_STACK_USE_PALLOC) && \
+ !defined(PG_STACK_USE_PALLOC_LOG)
+#define PG_STACK_USE_ARRAY
+#endif
+
+
+/*-------------------------------------------------------------------------
+ *
+ * Public API.
+ *
+ *-------------------------------------------------------------------------
+ */
+
+/* This ensures that "align" is a constant and can't cause overflow. */
+#define PG_STACK_MAX_ALIGN 4096
+
+/* Declare a stack allocator with a default size limit. */
+#define DECLARE_PG_STACK() \
+ DECLARE_PG_STACK_SIZE(128)
+
+/*
+ * As above, but with a caller-supplied limit on stack usage. The default
+ * should be preferred.
+ */
+#define DECLARE_PG_STACK_SIZE(size) \
+ bool pg_stack_maybe_pfree pg_attribute_unused() = false; \
+ size_t pg_stack_let_size; /* temp, avoids double eval */ \
+ DECLARE_PG_STACK_IMPL(size)
+
+/* Allocate memory, optionally with explicit alignment. */
+#define pg_stack_alloc(size) \
+ pg_stack_alloc_aligned((size), MAXIMUM_ALIGNOF)
+#define pg_stack_alloc_aligned(size, align) \
+ (pg_stack_sanity_checks(align), \
+ pg_stack_let_size = (size), \
+ pg_stack_alloc_aligned_impl(pg_stack_let_size, (align)))
+
+/* As above, but also zero the memory. */
+#define pg_stack_alloc0(size) \
+ pg_stack_alloc0_aligned((size), MAXIMUM_ALIGNOF)
+#define pg_stack_alloc0_aligned(size, align) \
+ (pg_stack_sanity_checks(align), \
+ pg_stack_let_size = (size), \
+ memset(pg_stack_alloc_aligned_impl(pg_stack_let_size, (align)), \
+ 0, \
+ pg_stack_let_size))
+
+/* As above, but for a given type T. */
+#define pg_stack_alloc_object(T) \
+ pg_stack_alloc_array(T, 1)
+#define pg_stack_alloc0_object(T) \
+ pg_stack_alloc0_array(T, 1)
+
+/* As above, but for an array of objects of size T. */
+#define pg_stack_alloc_array(T, n) \
+ (pg_stack_sanity_checks(alignof(T)), \
+ StaticAssertExpr(sizeof(n) <= sizeof(size_t), "n too wide"), \
+ pg_stack_let_size = pg_stack_T_mul_n(sizeof(T), sizeof(n), (n)), \
+ pg_stack_alloc_aligned_impl(pg_stack_let_size, alignof(T)))
+#define pg_stack_alloc0_array(T, n) \
+ (pg_stack_sanity_checks(alignof(T)), \
+ StaticAssertExpr(sizeof(n) <= sizeof(size_t), "n too wide"), \
+ pg_stack_let_size = pg_stack_T_mul_n(sizeof(T), sizeof(n), (n)), \
+ pg_stack_alloc0_aligned(pg_stack_let_size, (alignof(T))))
+
+/* Copy a string. */
+#define pg_stack_strdup(cstr) \
+ pg_stack_strdup_with_len((cstr), strlen(cstr))
+#define pg_stack_strndup(cstr, n) \
+ pg_stack_strdup_with_len((cstr), strnlen((cstr), (n)))
+#define pg_stack_strdup_with_len(data, size) \
+ (pg_stack_sanity_checks(1), \
+ pg_stack_let_size = (size), \
+ pg_stack_strdup_with_len_impl( \
+ pg_stack_alloc_aligned_impl(pg_stack_let_size + 1, \
+ alignof(char)), \
+ (data), \
+ pg_stack_let_size))
+#define pg_stack_text_to_cstring(text) \
+ pg_stack_strdup_with_len(VARDATA_ANY(text), VARSIZE_ANY_EXHDR(text))
+#define pg_stack_text_datum_to_cstring(datum) \
+ pg_stack_text_to_cstring((text *) DatumGetPointer(datum))
+
+/*
+ * Free memory allocated with the above interfaces. We don't expect to
+ * receive pointers allocated by palloc() directly and not via this API. That
+ * would break the pg_stack_maybe_pfree optimization, and might limit
+ * future implementation techniques.
+ */
+#define pg_stack_free(ptr) \
+ do \
+ { \
+ void *pg_stack_let_ptr = (ptr); \
+ Assert(pg_stack_ptr_p(pg_stack_let_ptr) || \
+ pg_stack_maybe_pfree); \
+ if (unlikely(pg_stack_maybe_pfree) && \
+ !pg_stack_ptr_p(pg_stack_let_ptr)) \
+ pfree(pg_stack_let_ptr); \
+ } \
+ while (0)
+
+
+/*-------------------------------------------------------------------------
+ *
+ * Private helper code common to all implementations.
+ *
+ *-------------------------------------------------------------------------
+ */
+
+/*
+ * Normal usage would have constant align values, so the first two checks
+ * should ideally be static assertions, but they are done this way to allow
+ * regress.c to loop through alignment sizes.
+ */
+#define pg_stack_sanity_checks(align) \
+ (AssertMacro((align) > 0 && (align) <= PG_STACK_MAX_ALIGN), \
+ AssertMacro(((align) & ((align) - 1)) == 0 /* power-of-two? */))
+
+/* For assertions. */
+static inline bool
+pg_stack_ptr_is_aligned_p(const void *p, size_t align)
+{
+ return (uintptr_t) p % align == 0;
+}
+
+/* Maximum value that can be stored in an unsigned integer of given size. */
+static inline size_t
+pg_stack_max_for_uint_size(size_t size)
+{
+ Assert(size <= sizeof(size_t));
+ return SIZE_MAX >> ((sizeof(size_t) * CHAR_BIT) - size * CHAR_BIT);
+}
+
+/* Post-allocation part of pg_stack_strdup_with_len(). */
+static inline char *
+pg_stack_strdup_with_len_impl(char *dst, const char *data, size_t size)
+{
+ memcpy(dst, data, size);
+ dst[size] = 0;
+ return dst;
+}
+
+/* Compute sizeof(T) * n. */
+static inline size_t
+pg_stack_T_mul_n(size_t sizeof_T, size_t sizeof_n, size_t n)
+{
+ /* XXX Could overflow, which might allow the stack to be corrupted. */
+ return sizeof_T * n;
+}
+
+/*
+ * Fall back to palloc() or palloc_aligned() due to lack of space. We waste a
+ * register remembering if we've ever had to do this, to generate better
+ * straight-line code for the case where we don't have to free anything.
+ */
+#define pg_stack_palloc_aligned(size, align) \
+ ((pg_stack_maybe_pfree = true), \
+ ((align) > MAXIMUM_ALIGNOF ? \
+ palloc_aligned((size), (align), 0) : \
+ palloc(size)))
+
+
+/*-------------------------------------------------------------------------
+ *
+ * Low-level implementations below this point supply the following macros:
+ *
+ * 1. DECLARE_PG_STACK_IMPL(size)
+ * 2. pg_stack_alloc_aligned(size, align)
+ * 3. pg_stack_ptr_p(ptr)
+ *
+ *-------------------------------------------------------------------------
+ */
+
+/*-------------------------------------------------------------------------
+ *
+ * Toy implementations for debugging.
+ *
+ *-------------------------------------------------------------------------
+ */
+
+/* Just use palloc. */
+#ifdef PG_STACK_USE_PALLOC
+#define DECLARE_PG_STACK_IMPL(size)
+#define pg_stack_alloc_aligned_impl(size, align) \
+ pg_stack_palloc_aligned((size), (align))
+#define pg_stack_ptr_p() false
+#endif
+
+/*
+ * Same, but log "location,function,size,depth" entries to a file, using the
+ * PG_STACK_USE_PALLOC_LOG macro's definition (a ""-quoted string literal) for
+ * the path.
+ */
+#ifdef PG_STACK_USE_PALLOC_LOG
+#define DECLARE_PG_STACK_IMPL(size) \
+ FILE *pg_stack_log \
+ __attribute__((cleanup(pg_stack_close_log))) = \
+ fopen(PG_STACK_USE_PALLOC_LOG, "a+")
+#define pg_stack_alloc_aligned_impl(size, align) \
+ (fprintf(pg_stack_log, \
+ "%s:%d,%s,%zu,%zu\n", \
+ __FILE__, \
+ __LINE__, \
+ __func__, \
+ (size_t) (size), \
+ ((const char *) stack_base_ptr - \
+ (const char *) __builtin_stack_address())), \
+ pg_stack_palloc_aligned((size), (align)))
+#define pg_stack_ptr_p(ptr) false
+static inline void
+pg_stack_close_log(FILE **f)
+{
+ fclose(*f);
+}
+#endif
+
+/*-------------------------------------------------------------------------
+ *
+ * Array-based implementation.
+ *
+ * This is entirely standard C requiring no compiler extensions, but it leaves
+ * a big hole in the stack when you call another function and has no ability
+ * to respect the total stack size limit.
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifdef PG_STACK_USE_ARRAY
+
+/* Required interface macros. */
+
+#define DECLARE_PG_STACK_IMPL(size) \
+ char pg_stack_array[(size)]; \
+ char *pg_stack_p = pg_stack_array + (size)
+
+#define pg_stack_alloc_aligned_impl(size, align) \
+ (pg_stack_alloc_aligned_from_array(&pg_stack_array[0], \
+ &pg_stack_p, \
+ (size), \
+ (align)) ? \
+ pg_stack_p : \
+ pg_stack_palloc_aligned(size, align))
+
+#define pg_stack_ptr_p(ptr) \
+ ((char *) (ptr) >= &pg_stack_array[0] && \
+ (char *) (ptr) <= &pg_stack_array[sizeof(pg_stack_array)])
+
+
+/* Implementation. */
+
+static inline bool
+pg_stack_alloc_aligned_from_array(char *array,
+ char **p,
+ size_t size,
+ size_t align)
+{
+ if (likely(size <= (uintptr_t) *p)) /* avoid overflow with huge size */
+ {
+ char *result = *p - size;
+
+ if (align > 1)
+ result = (char *) TYPEALIGN_DOWN(align, result);
+
+ if (likely(result >= array))
+ {
+ *p = result;
+ return true;
+ }
+ }
+ return false;
+}
+
+#endif
+
+#endif
diff --git a/src/test/regress/expected/internals.out b/src/test/regress/expected/internals.out
new file mode 100644
index 00000000000..532e9f791ed
--- /dev/null
+++ b/src/test/regress/expected/internals.out
@@ -0,0 +1,12 @@
+\getenv libdir PG_LIBDIR
+\getenv dlsuffix PG_DLSUFFIX
+\set regresslib :libdir '/regress' :dlsuffix
+CREATE FUNCTION test_pg_stack_alloc() RETURNS void
+ AS :'regresslib' LANGUAGE C STRICT;
+SELECT test_pg_stack_alloc();
+ test_pg_stack_alloc
+---------------------
+
+(1 row)
+
+DROP FUNCTION test_pg_stack_alloc();
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index 288e94dc408..37a97dbedc7 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -48,7 +48,7 @@ test: create_index create_index_spgist create_view index_including index_includi
# ----------
# Another group of parallel tests
# ----------
-test: create_aggregate create_function_sql create_cast constraints triggers select inherit typed_table vacuum drop_if_exists updatable_views roleattributes create_am hash_func errors infinite_recurse create_property_graph for_portion_of
+test: create_aggregate create_function_sql create_cast constraints triggers select inherit typed_table vacuum drop_if_exists updatable_views roleattributes create_am hash_func errors infinite_recurse create_property_graph for_portion_of internals
# ----------
# sanity_check does a vacuum, affecting the sort order of SELECT *
diff --git a/src/test/regress/regress.c b/src/test/regress/regress.c
index 2bcb5559a45..74fe6fdce9d 100644
--- a/src/test/regress/regress.c
+++ b/src/test/regress/regress.c
@@ -27,6 +27,7 @@
#include "catalog/pg_type.h"
#include "commands/sequence.h"
#include "commands/trigger.h"
+#include "common/int.h"
#include "common/pg_lzcompress.h"
#include "executor/executor.h"
#include "executor/functions.h"
@@ -47,6 +48,7 @@
#include "utils/builtins.h"
#include "utils/geo_decls.h"
#include "utils/memutils.h"
+#include "utils/pg_stack_alloc.h"
#include "utils/rel.h"
#include "utils/typcache.h"
@@ -1488,3 +1490,52 @@ test_pglz_decompress(PG_FUNCTION_ARGS)
SET_VARSIZE(result, dlen + VARHDRSZ);
PG_RETURN_BYTEA_P(result);
}
+
+PG_FUNCTION_INFO_V1(test_pg_stack_alloc);
+Datum
+test_pg_stack_alloc(PG_FUNCTION_ARGS)
+{
+ char *p;
+ char *p2 PG_USED_FOR_ASSERTS_ONLY;
+
+ /* Need extra space to run under ASAN. */
+ DECLARE_PG_STACK_SIZE(1024 * 8);
+
+ /* Too big for the stack. */
+ p = pg_stack_alloc(10000);
+ Assert(!pg_stack_ptr_p(p));
+ pg_stack_free(p);
+
+ /* Acceptable size. */
+ p = pg_stack_alloc(10);
+ Assert(pg_stack_ptr_p(p));
+
+ /* Addresses should move downwards. */
+ p2 = pg_stack_alloc(10);
+ Assert(pg_stack_ptr_p(p));
+ Assert(p2 < p);
+
+ /* A zero-sized allocation is identifiable as a stack address. */
+ p = pg_stack_alloc(0);
+ Assert(pg_stack_ptr_p(p));
+
+ /* Test a range of alignments. */
+#define TEST_ALIGN MAXIMUM_ALIGNOF
+ for (int i = 1; i <= TEST_ALIGN * 8; i *= 2)
+ {
+ /* Allocate and check alignment is as requested, when we use palloc(). */
+ p = pg_stack_alloc_aligned(1024 * 8, i);
+
+ Assert(!pg_stack_ptr_p(p));
+ Assert(pg_stack_ptr_is_aligned_p(p, i));
+ pg_stack_free(p);
+
+ /* Allocate and check alignment is as requested. */
+ p = pg_stack_alloc_aligned(i, i);
+
+ Assert(pg_stack_ptr_p(p));
+ Assert(pg_stack_ptr_is_aligned_p(p, i));
+ }
+
+ PG_RETURN_VOID();
+}
diff --git a/src/test/regress/sql/internals.sql b/src/test/regress/sql/internals.sql
new file mode 100644
index 00000000000..bea61780f8b
--- /dev/null
+++ b/src/test/regress/sql/internals.sql
@@ -0,0 +1,11 @@
+\getenv libdir PG_LIBDIR
+\getenv dlsuffix PG_DLSUFFIX
+
+\set regresslib :libdir '/regress' :dlsuffix
+
+CREATE FUNCTION test_pg_stack_alloc() RETURNS void
+ AS :'regresslib' LANGUAGE C STRICT;
+
+SELECT test_pg_stack_alloc();
+
+DROP FUNCTION test_pg_stack_alloc();
--
2.47.3