From 943060f91d3edcb1c94837a09dfb5566bf2daded Mon Sep 17 00:00:00 2001
From: Thomas Munro <thomas.munro@gmail.com>
Date: Mon, 2 Mar 2026 04:15:05 +1300
Subject: [PATCH v2 09/19] Use stack buffer in some access method code.

Mechanical changes including:
* temporary strings
* temporary datum, null, key lists etc
* WAL construction
---
 src/backend/access/brin/brin_tuple.c          | 19 +++++++++------
 src/backend/access/common/heaptuple.c         | 21 ++++++++++-------
 src/backend/access/gin/ginscan.c              |  7 ++++--
 src/backend/access/hash/hashfunc.c            | 12 ++++++----
 src/backend/access/heap/heapam.c              | 12 ++++++----
 src/backend/access/heap/heapam_handler.c      | 11 +++++----
 src/backend/access/index/genam.c              | 23 ++++++++++++-------
 src/backend/access/nbtree/nbtpreprocesskeys.c | 23 +++++++++++--------
 src/backend/access/nbtree/nbtxlog.c           |  9 +++++---
 9 files changed, 87 insertions(+), 50 deletions(-)

diff --git a/src/backend/access/brin/brin_tuple.c b/src/backend/access/brin/brin_tuple.c
index 69c233c62eb..13ff1d3f5d6 100644
--- a/src/backend/access/brin/brin_tuple.c
+++ b/src/backend/access/brin/brin_tuple.c
@@ -40,6 +40,7 @@
 #include "access/tupmacs.h"
 #include "utils/datum.h"
 #include "utils/memutils.h"
+#include "utils/stack_buffer.h"
 
 
 /*
@@ -117,14 +118,16 @@ brin_form_tuple(BrinDesc *brdesc, BlockNumber blkno, BrinMemTuple *tuple,
 	int			nuntoasted = 0;
 #endif
 
+	DECLARE_STACK_BUFFER();
+
 	Assert(brdesc->bd_totalstored > 0);
 
-	values = palloc_array(Datum, brdesc->bd_totalstored);
-	nulls = palloc0_array(bool, brdesc->bd_totalstored);
-	phony_nullbitmap = palloc_array(bits8, BITMAPLEN(brdesc->bd_totalstored));
+	values = stack_buffer_alloc_array(Datum, brdesc->bd_totalstored);
+	nulls = stack_buffer_alloc0_array(bool, brdesc->bd_totalstored);
+	phony_nullbitmap = stack_buffer_alloc_array(bits8, BITMAPLEN(brdesc->bd_totalstored));
 
 #ifdef TOAST_INDEX_HACK
-	untoasted_values = palloc_array(Datum, brdesc->bd_totalstored);
+	untoasted_values = stack_buffer_alloc_array(Datum, brdesc->bd_totalstored);
 #endif
 
 	/*
@@ -307,13 +310,15 @@ brin_form_tuple(BrinDesc *brdesc, BlockNumber blkno, BrinMemTuple *tuple,
 					phony_nullbitmap);
 
 	/* done with these */
-	pfree(values);
-	pfree(nulls);
-	pfree(phony_nullbitmap);
+	stack_buffer_free(values);
+	stack_buffer_free(nulls);
+	stack_buffer_free(phony_nullbitmap);
 
 #ifdef TOAST_INDEX_HACK
 	for (i = 0; i < nuntoasted; i++)
 		pfree(DatumGetPointer(untoasted_values[i]));
+
+	stack_buffer_free(untoasted_values);
 #endif
 
 	/*
diff --git a/src/backend/access/common/heaptuple.c b/src/backend/access/common/heaptuple.c
index 11bec20e82e..12f820056ba 100644
--- a/src/backend/access/common/heaptuple.c
+++ b/src/backend/access/common/heaptuple.c
@@ -65,6 +65,7 @@
 #include "utils/expandeddatum.h"
 #include "utils/hsearch.h"
 #include "utils/memutils.h"
+#include "utils/stack_buffer.h"
 
 
 /*
@@ -1219,6 +1220,8 @@ heap_modify_tuple(HeapTuple tuple,
 	bool	   *isnull;
 	HeapTuple	newTuple;
 
+	DECLARE_STACK_BUFFER();
+
 	/*
 	 * allocate and fill values and isnull arrays from either the tuple or the
 	 * repl information, as appropriate.
@@ -1230,8 +1233,8 @@ heap_modify_tuple(HeapTuple tuple,
 	 * O(N^2) if there are many non-replaced columns, so it seems better to
 	 * err on the side of linear cost.
 	 */
-	values = palloc_array(Datum, numberOfAttributes);
-	isnull = palloc_array(bool, numberOfAttributes);
+	values = stack_buffer_alloc_array(Datum, numberOfAttributes);
+	isnull = stack_buffer_alloc_array(bool, numberOfAttributes);
 
 	heap_deform_tuple(tuple, tupleDesc, values, isnull);
 
@@ -1249,8 +1252,8 @@ heap_modify_tuple(HeapTuple tuple,
 	 */
 	newTuple = heap_form_tuple(tupleDesc, values, isnull);
 
-	pfree(values);
-	pfree(isnull);
+	stack_buffer_free(values);
+	stack_buffer_free(isnull);
 
 	/*
 	 * copy the identification info of the old tuple: t_ctid, t_self
@@ -1288,12 +1291,14 @@ heap_modify_tuple_by_cols(HeapTuple tuple,
 	HeapTuple	newTuple;
 	int			i;
 
+	DECLARE_STACK_BUFFER();
+
 	/*
 	 * allocate and fill values and isnull arrays from the tuple, then replace
 	 * selected columns from the input arrays.
 	 */
-	values = palloc_array(Datum, numberOfAttributes);
-	isnull = palloc_array(bool, numberOfAttributes);
+	values = stack_buffer_alloc_array(Datum, numberOfAttributes);
+	isnull = stack_buffer_alloc_array(bool, numberOfAttributes);
 
 	heap_deform_tuple(tuple, tupleDesc, values, isnull);
 
@@ -1312,8 +1317,8 @@ heap_modify_tuple_by_cols(HeapTuple tuple,
 	 */
 	newTuple = heap_form_tuple(tupleDesc, values, isnull);
 
-	pfree(values);
-	pfree(isnull);
+	stack_buffer_free(values);
+	stack_buffer_free(isnull);
 
 	/*
 	 * copy the identification info of the old tuple: t_ctid, t_self
diff --git a/src/backend/access/gin/ginscan.c b/src/backend/access/gin/ginscan.c
index fb929761ab7..6d3d2737080 100644
--- a/src/backend/access/gin/ginscan.c
+++ b/src/backend/access/gin/ginscan.c
@@ -20,6 +20,7 @@
 #include "pgstat.h"
 #include "utils/memutils.h"
 #include "utils/rel.h"
+#include "utils/stack_buffer.h"
 
 
 IndexScanDesc
@@ -420,10 +421,12 @@ ginNewScanKey(IndexScanDesc scan)
 		int			iNormalKey;
 		int			iExcludeOnly;
 
+		DECLARE_STACK_BUFFER();
+
 		/* We'd better have made at least one normal key */
 		Assert(numExcludeOnly < so->nkeys);
 		/* Make a temporary array to hold the re-ordered scan keys */
-		tmpkeys = (GinScanKey) palloc(so->nkeys * sizeof(GinScanKeyData));
+		tmpkeys = stack_buffer_alloc_array(GinScanKeyData, so->nkeys);
 		/* Re-order the keys ... */
 		iNormalKey = 0;
 		iExcludeOnly = so->nkeys - numExcludeOnly;
@@ -446,7 +449,7 @@ ginNewScanKey(IndexScanDesc scan)
 		Assert(iExcludeOnly == so->nkeys);
 		/* ... and copy them back to so->keys[] */
 		memcpy(so->keys, tmpkeys, so->nkeys * sizeof(GinScanKeyData));
-		pfree(tmpkeys);
+		stack_buffer_free(tmpkeys);
 	}
 
 	/*
diff --git a/src/backend/access/hash/hashfunc.c b/src/backend/access/hash/hashfunc.c
index 575342a21b6..cda9ce28780 100644
--- a/src/backend/access/hash/hashfunc.c
+++ b/src/backend/access/hash/hashfunc.c
@@ -31,6 +31,7 @@
 #include "utils/float.h"
 #include "utils/fmgrprotos.h"
 #include "utils/pg_locale.h"
+#include "utils/stack_buffer.h"
 #include "varatt.h"
 
 /*
@@ -295,9 +296,10 @@ hashtext(PG_FUNCTION_ARGS)
 		const char *keydata = VARDATA_ANY(key);
 		size_t		keylen = VARSIZE_ANY_EXHDR(key);
 
+		DECLARE_STACK_BUFFER();
 
 		bsize = pg_strnxfrm(NULL, 0, keydata, keylen, mylocale);
-		buf = palloc(bsize + 1);
+		buf = stack_buffer_alloc(bsize + 1);
 
 		rsize = pg_strnxfrm(buf, bsize + 1, keydata, keylen, mylocale);
 
@@ -312,7 +314,7 @@ hashtext(PG_FUNCTION_ARGS)
 		 */
 		result = hash_any((uint8_t *) buf, bsize + 1);
 
-		pfree(buf);
+		stack_buffer_free(buf);
 	}
 
 	/* Avoid leaking memory for toasted inputs */
@@ -351,8 +353,10 @@ hashtextextended(PG_FUNCTION_ARGS)
 		const char *keydata = VARDATA_ANY(key);
 		size_t		keylen = VARSIZE_ANY_EXHDR(key);
 
+		DECLARE_STACK_BUFFER();
+
 		bsize = pg_strnxfrm(NULL, 0, keydata, keylen, mylocale);
-		buf = palloc(bsize + 1);
+		buf = stack_buffer_alloc(bsize + 1);
 
 		rsize = pg_strnxfrm(buf, bsize + 1, keydata, keylen, mylocale);
 
@@ -368,7 +372,7 @@ hashtextextended(PG_FUNCTION_ARGS)
 		result = hash_any_extended((uint8_t *) buf, bsize + 1,
 								   PG_GETARG_INT64(1));
 
-		pfree(buf);
+		stack_buffer_free(buf);
 	}
 
 	PG_FREE_IF_COPY(key, 0);
diff --git a/src/backend/access/heap/heapam.c b/src/backend/access/heap/heapam.c
index 8f1c11a9350..13171df183c 100644
--- a/src/backend/access/heap/heapam.c
+++ b/src/backend/access/heap/heapam.c
@@ -53,6 +53,7 @@
 #include "utils/injection_point.h"
 #include "utils/inval.h"
 #include "utils/spccache.h"
+#include "utils/stack_buffer.h"
 #include "utils/syscache.h"
 
 
@@ -8768,11 +8769,13 @@ bottomup_sort_and_shrink(TM_IndexDeleteOp *delstate)
 	int			ncopied = 0;
 	int			nblocksfavorable = 0;
 
+	DECLARE_STACK_BUFFER();
+
 	Assert(delstate->bottomup);
 	Assert(delstate->ndeltids > 0);
 
 	/* Calculate per-heap-block count of TIDs */
-	blockgroups = palloc_array(IndexDeleteCounts, delstate->ndeltids);
+	blockgroups = stack_buffer_alloc_array(IndexDeleteCounts, delstate->ndeltids);
 	for (int i = 0; i < delstate->ndeltids; i++)
 	{
 		TM_IndexDelete *ideltid = &delstate->deltids[i];
@@ -8845,7 +8848,8 @@ bottomup_sort_and_shrink(TM_IndexDeleteOp *delstate)
 	/* Sort groups and rearrange caller's deltids array */
 	qsort(blockgroups, nblockgroups, sizeof(IndexDeleteCounts),
 		  bottomup_sort_and_shrink_cmp);
-	reordereddeltids = palloc(delstate->ndeltids * sizeof(TM_IndexDelete));
+	reordereddeltids = stack_buffer_alloc_array(TM_IndexDelete,
+												delstate->ndeltids);
 
 	nblockgroups = Min(BOTTOMUP_MAX_NBLOCKS, nblockgroups);
 	/* Determine number of favorable blocks at the start of final deltids */
@@ -8867,8 +8871,8 @@ bottomup_sort_and_shrink(TM_IndexDeleteOp *delstate)
 		   sizeof(TM_IndexDelete) * ncopied);
 	delstate->ndeltids = ncopied;
 
-	pfree(reordereddeltids);
-	pfree(blockgroups);
+	stack_buffer_free(reordereddeltids);
+	stack_buffer_free(blockgroups);
 
 	return nblocksfavorable;
 }
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 5137d2510ea..cfb06ab82ac 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -45,6 +45,7 @@
 #include "storage/smgr.h"
 #include "utils/builtins.h"
 #include "utils/rel.h"
+#include "utils/stack_buffer.h"
 
 static void reform_and_rewrite_tuple(HeapTuple tuple,
 									 Relation OldHeap, Relation NewHeap,
@@ -706,6 +707,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
 	BufferHeapTupleTableSlot *hslot;
 	BlockNumber prev_cblock = InvalidBlockNumber;
 
+	DECLARE_STACK_BUFFER();
+
 	/* Remember if it's a system catalog */
 	is_system_catalog = IsSystemRelation(OldHeap);
 
@@ -717,8 +720,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
 
 	/* Preallocate values/isnull arrays */
 	natts = newTupDesc->natts;
-	values = palloc_array(Datum, natts);
-	isnull = palloc_array(bool, natts);
+	values = stack_buffer_alloc_array(Datum, natts);
+	isnull = stack_buffer_alloc_array(bool, natts);
 
 	/* Initialize the rewrite operation */
 	rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin, *xid_cutoff,
@@ -1002,8 +1005,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
 	end_heap_rewrite(rwstate);
 
 	/* Clean up */
-	pfree(values);
-	pfree(isnull);
+	stack_buffer_free(values);
+	stack_buffer_free(isnull);
 }
 
 /*
diff --git a/src/backend/access/index/genam.c b/src/backend/access/index/genam.c
index 5e89b86a62c..30df0b8819b 100644
--- a/src/backend/access/index/genam.c
+++ b/src/backend/access/index/genam.c
@@ -36,6 +36,7 @@
 #include "utils/rls.h"
 #include "utils/ruleutils.h"
 #include "utils/snapmgr.h"
+#include "utils/stack_buffer.h"
 
 
 /* ----------------------------------------------------------------
@@ -303,6 +304,8 @@ index_compute_xid_horizon_for_tuples(Relation irel,
 	Page		ipage = BufferGetPage(ibuf);
 	IndexTuple	itup;
 
+	DECLARE_STACK_BUFFER();
+
 	Assert(nitems > 0);
 
 	delstate.irel = irel;
@@ -310,8 +313,8 @@ index_compute_xid_horizon_for_tuples(Relation irel,
 	delstate.bottomup = false;
 	delstate.bottomupfreespace = 0;
 	delstate.ndeltids = 0;
-	delstate.deltids = palloc_array(TM_IndexDelete, nitems);
-	delstate.status = palloc_array(TM_IndexStatus, nitems);
+	delstate.deltids = stack_buffer_alloc_array(TM_IndexDelete, nitems);
+	delstate.status = stack_buffer_alloc_array(TM_IndexStatus, nitems);
 
 	/* identify what the index tuples about to be deleted point to */
 	for (int i = 0; i < nitems; i++)
@@ -340,8 +343,8 @@ index_compute_xid_horizon_for_tuples(Relation irel,
 	/* assert tableam agrees that all items are deletable */
 	Assert(delstate.ndeltids == nitems);
 
-	pfree(delstate.deltids);
-	pfree(delstate.status);
+	stack_buffer_free(delstate.deltids);
+	stack_buffer_free(delstate.status);
 
 	return snapshotConflictHorizon;
 }
@@ -433,7 +436,9 @@ systable_beginscan(Relation heapRelation,
 		int			i;
 		ScanKey		idxkey;
 
-		idxkey = palloc_array(ScanKeyData, nkeys);
+		DECLARE_STACK_BUFFER();
+
+		idxkey = stack_buffer_alloc_array(ScanKeyData, nkeys);
 
 		/* Convert attribute numbers to be index column numbers. */
 		for (i = 0; i < nkeys; i++)
@@ -459,7 +464,7 @@ systable_beginscan(Relation heapRelation,
 		index_rescan(sysscan->iscan, idxkey, nkeys, NULL, 0);
 		sysscan->scan = NULL;
 
-		pfree(idxkey);
+		stack_buffer_free(idxkey);
 	}
 	else
 	{
@@ -656,6 +661,8 @@ systable_beginscan_ordered(Relation heapRelation,
 	int			i;
 	ScanKey		idxkey;
 
+	DECLARE_STACK_BUFFER();
+
 	/* REINDEX can probably be a hard error here ... */
 	if (ReindexIsProcessingIndex(RelationGetRelid(indexRelation)))
 		ereport(ERROR,
@@ -686,7 +693,7 @@ systable_beginscan_ordered(Relation heapRelation,
 		sysscan->snapshot = NULL;
 	}
 
-	idxkey = palloc_array(ScanKeyData, nkeys);
+	idxkey = stack_buffer_alloc_array(ScanKeyData, nkeys);
 
 	/* Convert attribute numbers to be index column numbers. */
 	for (i = 0; i < nkeys; i++)
@@ -720,7 +727,7 @@ systable_beginscan_ordered(Relation heapRelation,
 	index_rescan(sysscan->iscan, idxkey, nkeys, NULL, 0);
 	sysscan->scan = NULL;
 
-	pfree(idxkey);
+	stack_buffer_free(idxkey);
 
 	return sysscan;
 }
diff --git a/src/backend/access/nbtree/nbtpreprocesskeys.c b/src/backend/access/nbtree/nbtpreprocesskeys.c
index 39c0a5d610f..b62e6892345 100644
--- a/src/backend/access/nbtree/nbtpreprocesskeys.c
+++ b/src/backend/access/nbtree/nbtpreprocesskeys.c
@@ -23,6 +23,7 @@
 #include "utils/lsyscache.h"
 #include "utils/memutils.h"
 #include "utils/rel.h"
+#include "utils/stack_buffer.h"
 
 typedef struct BTScanKeyPreproc
 {
@@ -1557,6 +1558,8 @@ _bt_unmark_keys(IndexScanDesc scan, int *keyDataMap)
 				haveReqForward,
 				haveReqBackward;
 
+	DECLARE_STACK_BUFFER();
+
 	/*
 	 * Do an initial pass over so->keyData[] that determines which keys to
 	 * keep as required.  We expect so->keyData[] to still be in attribute
@@ -1568,7 +1571,7 @@ _bt_unmark_keys(IndexScanDesc scan, int *keyDataMap)
 	 * Any requiredness markings that we might leave on later keys/attributes
 	 * are predicated on there being required = keys on all prior columns.
 	 */
-	unmarkikey = palloc0(so->numberOfKeys * sizeof(bool));
+	unmarkikey = stack_buffer_alloc0_array(bool, so->numberOfKeys);
 	nunmark = 0;
 
 	/* Set things up for first key's attribute */
@@ -1653,14 +1656,14 @@ _bt_unmark_keys(IndexScanDesc scan, int *keyDataMap)
 	 * Next, allocate temp arrays: one for required keys that'll remain
 	 * required, the other for all remaining keys
 	 */
-	unmarkKeys = palloc(nunmark * sizeof(ScanKeyData));
-	keepKeys = palloc((so->numberOfKeys - nunmark) * sizeof(ScanKeyData));
+	unmarkKeys = stack_buffer_alloc_array(ScanKeyData, nunmark);
+	keepKeys = stack_buffer_alloc_array(ScanKeyData, so->numberOfKeys - nunmark);
 	nunmarked = 0;
 	nkept = 0;
 	if (so->numArrayKeys)
 	{
-		unmarkOrderProcs = palloc(nunmark * sizeof(FmgrInfo));
-		keepOrderProcs = palloc((so->numberOfKeys - nunmark) * sizeof(FmgrInfo));
+		unmarkOrderProcs = stack_buffer_alloc_array(FmgrInfo, nunmark);
+		keepOrderProcs = stack_buffer_alloc_array(FmgrInfo, so->numberOfKeys - nunmark);
 	}
 
 	/*
@@ -1751,9 +1754,9 @@ _bt_unmark_keys(IndexScanDesc scan, int *keyDataMap)
 	memcpy(so->keyData + nkept, unmarkKeys, sizeof(ScanKeyData) * nunmarked);
 
 	/* Done with temp arrays */
-	pfree(unmarkikey);
-	pfree(keepKeys);
-	pfree(unmarkKeys);
+	stack_buffer_free(unmarkikey);
+	stack_buffer_free(keepKeys);
+	stack_buffer_free(unmarkKeys);
 
 	/*
 	 * Now copy so->orderProcs[] temp entries needed by scans with = array
@@ -1781,8 +1784,8 @@ _bt_unmark_keys(IndexScanDesc scan, int *keyDataMap)
 			  _bt_reorder_array_cmp);
 
 		/* Done with temp arrays */
-		pfree(unmarkOrderProcs);
-		pfree(keepOrderProcs);
+		stack_buffer_free(unmarkOrderProcs);
+		stack_buffer_free(keepOrderProcs);
 	}
 }
 
diff --git a/src/backend/access/nbtree/nbtxlog.c b/src/backend/access/nbtree/nbtxlog.c
index dff7d286fc8..14bd0fd2fd3 100644
--- a/src/backend/access/nbtree/nbtxlog.c
+++ b/src/backend/access/nbtree/nbtxlog.c
@@ -21,6 +21,7 @@
 #include "access/xlogutils.h"
 #include "storage/standby.h"
 #include "utils/memutils.h"
+#include "utils/stack_buffer.h"
 
 static MemoryContext opCtx;		/* working memory for operations */
 
@@ -551,13 +552,15 @@ btree_xlog_updates(Page page, OffsetNumber *updatedoffsets,
 	ItemId		itemid;
 	Size		itemsz;
 
+	DECLARE_STACK_BUFFER();
+
 	for (int i = 0; i < nupdated; i++)
 	{
 		itemid = PageGetItemId(page, updatedoffsets[i]);
 		origtuple = (IndexTuple) PageGetItem(page, itemid);
 
-		vacposting = palloc(offsetof(BTVacuumPostingData, deletetids) +
-							updates->ndeletedtids * sizeof(uint16));
+		vacposting = stack_buffer_alloc(offsetof(BTVacuumPostingData, deletetids) +
+										updates->ndeletedtids * sizeof(uint16));
 		vacposting->updatedoffset = updatedoffsets[i];
 		vacposting->itup = origtuple;
 		vacposting->ndeletedtids = updates->ndeletedtids;
@@ -573,7 +576,7 @@ btree_xlog_updates(Page page, OffsetNumber *updatedoffsets,
 			elog(PANIC, "failed to update partially dead item");
 
 		pfree(vacposting->itup);
-		pfree(vacposting);
+		stack_buffer_free(vacposting);
 
 		/* advance to next xl_btree_update from array */
 		updates = (xl_btree_update *)
-- 
2.53.0

