agora inbox for [email protected]  
help / color / mirror / Atom feed
[PATCH v14 6/7] Restructure TupleTableSlot to allow tuples other than HeapTuple
114+ messages / 2 participants
[nested] [flat]

* [PATCH v14 6/7] Restructure TupleTableSlot to allow tuples other than HeapTuple
@ 2018-11-13 20:58 Andres Freund <[email protected]>
  0 siblings, 0 replies; 114+ messages in thread

From: Andres Freund @ 2018-11-13 20:58 UTC (permalink / raw)

Author: Andres Freund and  Ashutosh Bapat, with changes by Amit Khandekar
Discussion: https://postgr.es/m/[email protected]
---
 src/backend/access/common/heaptuple.c  |  184 +--
 src/backend/catalog/index.c            |    4 +-
 src/backend/executor/execCurrent.c     |   18 +-
 src/backend/executor/execExpr.c        |    5 +-
 src/backend/executor/execExprInterp.c  |   13 +-
 src/backend/executor/execGrouping.c    |    5 +-
 src/backend/executor/execReplication.c |   41 +-
 src/backend/executor/execScan.c        |    4 +-
 src/backend/executor/execTuples.c      | 1665 ++++++++++++++++--------
 src/backend/executor/nodeAgg.c         |    5 +-
 src/backend/executor/nodeHashjoin.c    |   17 +-
 src/backend/executor/nodeModifyTable.c |   13 +
 src/backend/executor/nodeSubplan.c     |    1 +
 src/backend/jit/llvm/llvmjit.c         |    8 +-
 src/backend/jit/llvm/llvmjit_deform.c  |   52 +-
 src/backend/jit/llvm/llvmjit_expr.c    |    3 +-
 src/backend/jit/llvm/llvmjit_types.c   |    4 +-
 src/include/access/htup_details.h      |    2 -
 src/include/executor/executor.h        |    1 +
 src/include/executor/tuptable.h        |  305 ++++-
 src/include/jit/llvmjit.h              |    7 +-
 src/include/nodes/execnodes.h          |    1 +
 22 files changed, 1562 insertions(+), 796 deletions(-)

diff --git a/src/backend/access/common/heaptuple.c b/src/backend/access/common/heaptuple.c
index 28127b311f5..ccb69bdd616 100644
--- a/src/backend/access/common/heaptuple.c
+++ b/src/backend/access/common/heaptuple.c
@@ -71,6 +71,8 @@
 #define VARLENA_ATT_IS_PACKABLE(att) \
 	((att)->attstorage != 'p')
 
+static Datum getmissingattr(TupleDesc tupleDesc, int attnum, bool *isnull);
+
 
 /* ----------------------------------------------------------------
  *						misc support routines
@@ -80,7 +82,7 @@
 /*
  * Return the missing value of an attribute, or NULL if there isn't one.
  */
-Datum
+static Datum
 getmissingattr(TupleDesc tupleDesc,
 			   int attnum, bool *isnull)
 {
@@ -1350,186 +1352,6 @@ heap_deform_tuple(HeapTuple tuple, TupleDesc tupleDesc,
 		values[attnum] = getmissingattr(tupleDesc, attnum + 1, &isnull[attnum]);
 }
 
-/*
- * slot_deform_tuple
- *		Given a TupleTableSlot, extract data from the slot's physical tuple
- *		into its Datum/isnull arrays.  Data is extracted up through the
- *		natts'th column (caller must ensure this is a legal column number).
- *
- *		This is essentially an incremental version of heap_deform_tuple:
- *		on each call we extract attributes up to the one needed, without
- *		re-computing information about previously extracted attributes.
- *		slot->tts_nvalid is the number of attributes already extracted.
- */
-void
-slot_deform_tuple(TupleTableSlot *slot, int natts)
-{
-	HeapTuple	tuple = slot->tts_tuple;
-	TupleDesc	tupleDesc = slot->tts_tupleDescriptor;
-	Datum	   *values = slot->tts_values;
-	bool	   *isnull = slot->tts_isnull;
-	HeapTupleHeader tup = tuple->t_data;
-	bool		hasnulls = HeapTupleHasNulls(tuple);
-	int			attnum;
-	char	   *tp;				/* ptr to tuple data */
-	uint32		off;			/* offset in tuple data */
-	bits8	   *bp = tup->t_bits;	/* ptr to null bitmap in tuple */
-	bool		slow;			/* can we use/set attcacheoff? */
-
-	/*
-	 * Check whether the first call for this tuple, and initialize or restore
-	 * loop state.
-	 */
-	attnum = slot->tts_nvalid;
-	if (attnum == 0)
-	{
-		/* Start from the first attribute */
-		off = 0;
-		slow = false;
-	}
-	else
-	{
-		/* Restore state from previous execution */
-		off = slot->tts_off;
-		slow = TTS_SLOW(slot);
-	}
-
-	tp = (char *) tup + tup->t_hoff;
-
-	for (; attnum < natts; attnum++)
-	{
-		Form_pg_attribute thisatt = TupleDescAttr(tupleDesc, attnum);
-
-		if (hasnulls && att_isnull(attnum, bp))
-		{
-			values[attnum] = (Datum) 0;
-			isnull[attnum] = true;
-			slow = true;		/* can't use attcacheoff anymore */
-			continue;
-		}
-
-		isnull[attnum] = false;
-
-		if (!slow && thisatt->attcacheoff >= 0)
-			off = thisatt->attcacheoff;
-		else if (thisatt->attlen == -1)
-		{
-			/*
-			 * We can only cache the offset for a varlena attribute if the
-			 * offset is already suitably aligned, so that there would be no
-			 * pad bytes in any case: then the offset will be valid for either
-			 * an aligned or unaligned value.
-			 */
-			if (!slow &&
-				off == att_align_nominal(off, thisatt->attalign))
-				thisatt->attcacheoff = off;
-			else
-			{
-				off = att_align_pointer(off, thisatt->attalign, -1,
-										tp + off);
-				slow = true;
-			}
-		}
-		else
-		{
-			/* not varlena, so safe to use att_align_nominal */
-			off = att_align_nominal(off, thisatt->attalign);
-
-			if (!slow)
-				thisatt->attcacheoff = off;
-		}
-
-		values[attnum] = fetchatt(thisatt, tp + off);
-
-		off = att_addlength_pointer(off, thisatt->attlen, tp + off);
-
-		if (thisatt->attlen <= 0)
-			slow = true;		/* can't use attcacheoff anymore */
-	}
-
-	/*
-	 * Save state for next execution
-	 */
-	slot->tts_nvalid = attnum;
-	slot->tts_off = off;
-	if (slow)
-		slot->tts_flags |= TTS_FLAG_SLOW;
-	else
-		slot->tts_flags &= ~TTS_FLAG_SLOW;
-}
-
-/*
- * slot_attisnull
- *		Detect whether an attribute of the slot is null, without
- *		actually fetching it.
- */
-bool
-slot_attisnull(TupleTableSlot *slot, int attnum)
-{
-	HeapTuple	tuple = slot->tts_tuple;
-	TupleDesc	tupleDesc = slot->tts_tupleDescriptor;
-
-	/*
-	 * system attributes are handled by heap_attisnull
-	 */
-	if (attnum <= 0)
-	{
-		if (tuple == NULL)		/* internal error */
-			elog(ERROR, "cannot extract system attribute from virtual tuple");
-		if (tuple == &(slot->tts_minhdr))	/* internal error */
-			elog(ERROR, "cannot extract system attribute from minimal tuple");
-		return heap_attisnull(tuple, attnum, tupleDesc);
-	}
-
-	/*
-	 * fast path if desired attribute already cached
-	 */
-	if (attnum <= slot->tts_nvalid)
-		return slot->tts_isnull[attnum - 1];
-
-	/*
-	 * return NULL if attnum is out of range according to the tupdesc
-	 */
-	if (attnum > tupleDesc->natts)
-		return true;
-
-	/*
-	 * otherwise we had better have a physical tuple (tts_nvalid should equal
-	 * natts in all virtual-tuple cases)
-	 */
-	if (tuple == NULL)			/* internal error */
-		elog(ERROR, "cannot extract attribute from empty tuple slot");
-
-	/* and let the tuple tell it */
-	return heap_attisnull(tuple, attnum, tupleDesc);
-}
-
-/*
- * slot_getsysattr
- *		This function fetches a system attribute of the slot's current tuple.
- *		Unlike slot_getattr, if the slot does not contain system attributes,
- *		this will return false (with a NULL attribute value) instead of
- *		throwing an error.
- */
-bool
-slot_getsysattr(TupleTableSlot *slot, int attnum,
-				Datum *value, bool *isnull)
-{
-	HeapTuple	tuple = slot->tts_tuple;
-
-	Assert(attnum < 0);			/* else caller error */
-	if (tuple == NULL ||
-		tuple == &(slot->tts_minhdr))
-	{
-		/* No physical tuple, or minimal tuple, so fail */
-		*value = (Datum) 0;
-		*isnull = true;
-		return false;
-	}
-	*value = heap_getsysattr(tuple, attnum, slot->tts_tupleDescriptor, isnull);
-	return true;
-}
-
 /*
  * heap_freetuple
  */
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 21bdf794da6..a980202a7b1 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -2041,7 +2041,9 @@ FormIndexDatum(IndexInfo *indexInfo,
 		Datum		iDatum;
 		bool		isNull;
 
-		if (keycol != 0)
+		if (keycol < 0)
+			iDatum = slot_getsysattr(slot, keycol, &isNull);
+		else if (keycol != 0)
 		{
 			/*
 			 * Plain index column; get the value we need directly from the
diff --git a/src/backend/executor/execCurrent.c b/src/backend/executor/execCurrent.c
index aadf7493827..39c462a4e59 100644
--- a/src/backend/executor/execCurrent.c
+++ b/src/backend/executor/execCurrent.c
@@ -218,27 +218,25 @@ execCurrentOf(CurrentOfExpr *cexpr,
 			ItemPointer tuple_tid;
 
 #ifdef USE_ASSERT_CHECKING
-			if (!slot_getsysattr(scanstate->ss_ScanTupleSlot,
-								 TableOidAttributeNumber,
-								 &ldatum,
-								 &lisnull))
+			ldatum = slot_getsysattr(scanstate->ss_ScanTupleSlot,
+									 TableOidAttributeNumber,
+									 &lisnull);
+			if (lisnull)
 				ereport(ERROR,
 						(errcode(ERRCODE_INVALID_CURSOR_STATE),
 						 errmsg("cursor \"%s\" is not a simply updatable scan of table \"%s\"",
 								cursor_name, table_name)));
-			Assert(!lisnull);
 			Assert(DatumGetObjectId(ldatum) == table_oid);
 #endif
 
-			if (!slot_getsysattr(scanstate->ss_ScanTupleSlot,
-								 SelfItemPointerAttributeNumber,
-								 &ldatum,
-								 &lisnull))
+			ldatum = slot_getsysattr(scanstate->ss_ScanTupleSlot,
+									 SelfItemPointerAttributeNumber,
+									 &lisnull);
+			if (lisnull)
 				ereport(ERROR,
 						(errcode(ERRCODE_INVALID_CURSOR_STATE),
 						 errmsg("cursor \"%s\" is not a simply updatable scan of table \"%s\"",
 								cursor_name, table_name)));
-			Assert(!lisnull);
 			tuple_tid = (ItemPointer) DatumGetPointer(ldatum);
 
 			*current_tid = *tuple_tid;
diff --git a/src/backend/executor/execExpr.c b/src/backend/executor/execExpr.c
index a4099c23176..914d04186bd 100644
--- a/src/backend/executor/execExpr.c
+++ b/src/backend/executor/execExpr.c
@@ -3313,6 +3313,7 @@ ExecBuildAggTransCall(ExprState *state, AggState *aggstate,
  */
 ExprState *
 ExecBuildGroupingEqual(TupleDesc ldesc, TupleDesc rdesc,
+					   const TupleTableSlotOps *lops, const TupleTableSlotOps *rops,
 					   int numCols,
 					   AttrNumber *keyColIdx,
 					   Oid *eqfunctions,
@@ -3354,7 +3355,7 @@ ExecBuildGroupingEqual(TupleDesc ldesc, TupleDesc rdesc,
 	scratch.d.fetch.last_var = maxatt;
 	scratch.d.fetch.fixed = false;
 	scratch.d.fetch.known_desc = ldesc;
-	scratch.d.fetch.kind = NULL;
+	scratch.d.fetch.kind = lops;
 	ExecComputeSlotInfo(state, &scratch);
 	ExprEvalPushStep(state, &scratch);
 
@@ -3362,7 +3363,7 @@ ExecBuildGroupingEqual(TupleDesc ldesc, TupleDesc rdesc,
 	scratch.d.fetch.last_var = maxatt;
 	scratch.d.fetch.fixed = false;
 	scratch.d.fetch.known_desc = rdesc;
-	scratch.d.fetch.kind = NULL;
+	scratch.d.fetch.kind = rops;
 	ExecComputeSlotInfo(state, &scratch);
 	ExprEvalPushStep(state, &scratch);
 
diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor/execExprInterp.c
index 357eae41cc3..dbf2445a21d 100644
--- a/src/backend/executor/execExprInterp.c
+++ b/src/backend/executor/execExprInterp.c
@@ -428,7 +428,6 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
 		{
 			CheckOpSlotCompatibility(op, innerslot);
 
-			/* XXX: worthwhile to check tts_nvalid inline first? */
 			slot_getsomeattrs(innerslot, op->d.fetch.last_var);
 
 			EEO_NEXT();
@@ -4026,15 +4025,15 @@ void
 ExecEvalSysVar(ExprState *state, ExprEvalStep *op, ExprContext *econtext,
 			   TupleTableSlot *slot)
 {
-	bool success;
+	Datum d;
 
 	/* slot_getsysattr has sufficient defenses against bad attnums */
-	success = slot_getsysattr(slot,
-							  op->d.var.attnum,
-							  op->resvalue,
-							  op->resnull);
+	d = slot_getsysattr(slot,
+						op->d.var.attnum,
+						op->resnull);
+	*op->resvalue = d;
 	/* this ought to be unreachable, but it's cheap enough to check */
-	if (unlikely(!success))
+	if (unlikely(*op->resnull))
 		elog(ERROR, "failed to fetch attribute from slot");
 }
 
diff --git a/src/backend/executor/execGrouping.c b/src/backend/executor/execGrouping.c
index 5bc200e4dc3..abce1e95cb6 100644
--- a/src/backend/executor/execGrouping.c
+++ b/src/backend/executor/execGrouping.c
@@ -75,7 +75,8 @@ execTuplesMatchPrepare(TupleDesc desc,
 		eqFunctions[i] = get_opcode(eqOperators[i]);
 
 	/* build actual expression */
-	expr = ExecBuildGroupingEqual(desc, desc, numCols, keyColIdx, eqFunctions,
+	expr = ExecBuildGroupingEqual(desc, desc, NULL, NULL,
+								  numCols, keyColIdx, eqFunctions,
 								  parent);
 
 	return expr;
@@ -206,7 +207,9 @@ BuildTupleHashTable(PlanState *parent,
 													&TTSOpsMinimalTuple);
 
 	/* build comparator for all columns */
+	/* XXX: should we support non-minimal tuples for the inputslot? */
 	hashtable->tab_eq_func = ExecBuildGroupingEqual(inputDesc, inputDesc,
+													&TTSOpsMinimalTuple, &TTSOpsMinimalTuple,
 													numCols,
 													keyColIdx, eqfuncoids,
 													parent);
diff --git a/src/backend/executor/execReplication.c b/src/backend/executor/execReplication.c
index 071ba8762d4..727770eab56 100644
--- a/src/backend/executor/execReplication.c
+++ b/src/backend/executor/execReplication.c
@@ -170,8 +170,11 @@ retry:
 		HeapUpdateFailureData hufd;
 		HTSU_Result res;
 		HeapTupleData locktup;
+		HeapTupleTableSlot *hslot = (HeapTupleTableSlot *)outslot;
 
-		ItemPointerCopy(&outslot->tts_tuple->t_self, &locktup.t_self);
+		/* Only a heap tuple has item pointers. */
+		Assert(TTS_IS_HEAPTUPLE(outslot) || TTS_IS_BUFFERTUPLE(outslot));
+		ItemPointerCopy(&hslot->tuple->t_self, &locktup.t_self);
 
 		PushActiveSnapshot(GetLatestSnapshot());
 
@@ -334,8 +337,12 @@ retry:
 		HeapUpdateFailureData hufd;
 		HTSU_Result res;
 		HeapTupleData locktup;
+		HeapTupleTableSlot *hslot = (HeapTupleTableSlot *)outslot;
 
-		ItemPointerCopy(&outslot->tts_tuple->t_self, &locktup.t_self);
+		/* Only a heap tuple has item pointers. */
+		Assert(TTS_IS_HEAPTUPLE(outslot) || TTS_IS_BUFFERTUPLE(outslot));
+
+		ItemPointerCopy(&hslot->tuple->t_self, &locktup.t_self);
 
 		PushActiveSnapshot(GetLatestSnapshot());
 
@@ -456,6 +463,12 @@ ExecSimpleRelationUpdate(EState *estate, EPQState *epqstate,
 	HeapTuple	tuple;
 	ResultRelInfo *resultRelInfo = estate->es_result_relation_info;
 	Relation	rel = resultRelInfo->ri_RelationDesc;
+	HeapTupleTableSlot *hsearchslot = (HeapTupleTableSlot *)searchslot;
+	HeapTupleTableSlot *hslot = (HeapTupleTableSlot *)slot;
+
+	/* We expect both searchslot and the slot to contain a heap tuple. */
+	Assert(TTS_IS_HEAPTUPLE(searchslot) || TTS_IS_BUFFERTUPLE(searchslot));
+	Assert(TTS_IS_HEAPTUPLE(slot) || TTS_IS_BUFFERTUPLE(slot));
 
 	/* For now we support only tables. */
 	Assert(rel->rd_rel->relkind == RELKIND_RELATION);
@@ -467,8 +480,7 @@ ExecSimpleRelationUpdate(EState *estate, EPQState *epqstate,
 		resultRelInfo->ri_TrigDesc->trig_update_before_row)
 	{
 		slot = ExecBRUpdateTriggers(estate, epqstate, resultRelInfo,
-									&searchslot->tts_tuple->t_self,
-									NULL, slot);
+									&hsearchslot->tuple->t_self, NULL, slot);
 
 		if (slot == NULL)		/* "do nothing" */
 			skip_tuple = true;
@@ -488,19 +500,18 @@ ExecSimpleRelationUpdate(EState *estate, EPQState *epqstate,
 		tuple = ExecFetchSlotHeapTuple(slot, true, NULL);
 
 		/* OK, update the tuple and index entries for it */
-		simple_heap_update(rel, &searchslot->tts_tuple->t_self,
-						   slot->tts_tuple);
+		simple_heap_update(rel, &hsearchslot->tuple->t_self, hslot->tuple);
 
 		if (resultRelInfo->ri_NumIndices > 0 &&
-			!HeapTupleIsHeapOnly(slot->tts_tuple))
+			!HeapTupleIsHeapOnly(hslot->tuple))
 			recheckIndexes = ExecInsertIndexTuples(slot, &(tuple->t_self),
 												   estate, false, NULL,
 												   NIL);
 
 		/* AFTER ROW UPDATE Triggers */
 		ExecARUpdateTriggers(estate, resultRelInfo,
-							 &searchslot->tts_tuple->t_self,
-							 NULL, tuple, recheckIndexes, NULL);
+							 &hsearchslot->tuple->t_self, NULL, tuple,
+							 recheckIndexes, NULL);
 
 		list_free(recheckIndexes);
 	}
@@ -519,9 +530,11 @@ ExecSimpleRelationDelete(EState *estate, EPQState *epqstate,
 	bool		skip_tuple = false;
 	ResultRelInfo *resultRelInfo = estate->es_result_relation_info;
 	Relation	rel = resultRelInfo->ri_RelationDesc;
+	HeapTupleTableSlot *hsearchslot = (HeapTupleTableSlot *)searchslot;
 
-	/* For now we support only tables. */
+	/* For now we support only tables and heap tuples. */
 	Assert(rel->rd_rel->relkind == RELKIND_RELATION);
+	Assert(TTS_IS_HEAPTUPLE(searchslot) || TTS_IS_BUFFERTUPLE(searchslot));
 
 	CheckCmdReplicaIdentity(rel, CMD_DELETE);
 
@@ -530,8 +543,8 @@ ExecSimpleRelationDelete(EState *estate, EPQState *epqstate,
 		resultRelInfo->ri_TrigDesc->trig_delete_before_row)
 	{
 		skip_tuple = !ExecBRDeleteTriggers(estate, epqstate, resultRelInfo,
-										   &searchslot->tts_tuple->t_self,
-										   NULL, NULL);
+										   &hsearchslot->tuple->t_self, NULL,
+										   NULL);
 	}
 
 	if (!skip_tuple)
@@ -539,11 +552,11 @@ ExecSimpleRelationDelete(EState *estate, EPQState *epqstate,
 		List	   *recheckIndexes = NIL;
 
 		/* OK, delete the tuple */
-		simple_heap_delete(rel, &searchslot->tts_tuple->t_self);
+		simple_heap_delete(rel, &hsearchslot->tuple->t_self);
 
 		/* AFTER ROW DELETE Triggers */
 		ExecARDeleteTriggers(estate, resultRelInfo,
-							 &searchslot->tts_tuple->t_self, NULL, NULL);
+							 &hsearchslot->tuple->t_self, NULL, NULL);
 
 		list_free(recheckIndexes);
 	}
diff --git a/src/backend/executor/execScan.c b/src/backend/executor/execScan.c
index 233cc280608..d90bb16b570 100644
--- a/src/backend/executor/execScan.c
+++ b/src/backend/executor/execScan.c
@@ -78,8 +78,8 @@ ExecScanFetch(ScanState *node,
 				return ExecClearTuple(slot);
 
 			/* Store test tuple in the plan node's scan slot */
-			ExecStoreHeapTuple(estate->es_epqTuple[scanrelid - 1],
-							   slot, false);
+			ExecForceStoreHeapTuple(estate->es_epqTuple[scanrelid - 1],
+									slot);
 
 			/* Check if it meets the access-method conditions */
 			if (!(*recheckMtd) (node, slot))
diff --git a/src/backend/executor/execTuples.c b/src/backend/executor/execTuples.c
index bb618e94f5d..bf49c4789cb 100644
--- a/src/backend/executor/execTuples.c
+++ b/src/backend/executor/execTuples.c
@@ -71,12 +71,1024 @@
 
 static TupleDesc ExecTypeFromTLInternal(List *targetList,
 					   bool hasoid, bool skipjunk);
+static void tts_buffer_heap_store_tuple(TupleTableSlot *slot, HeapTuple tuple, Buffer buffer);
 
 const TupleTableSlotOps TTSOpsVirtual;
 const TupleTableSlotOps TTSOpsHeapTuple;
 const TupleTableSlotOps TTSOpsMinimalTuple;
 const TupleTableSlotOps TTSOpsBufferTuple;
 
+/*
+ * slot_deform_heap_tuple
+ *		Given a TupleTableSlot, extract data from the slot's physical tuple
+ *		into its Datum/isnull arrays.  Data is extracted up through the
+ *		natts'th column (caller must ensure this is a legal column number).
+ *
+ *		This is essentially an incremental version of heap_deform_tuple:
+ *		on each call we extract attributes up to the one needed, without
+ *		re-computing information about previously extracted attributes.
+ *		slot->tts_nvalid is the number of attributes already extracted.
+ */
+static inline void __attribute__((always_inline))
+slot_deform_heap_tuple(TupleTableSlot *slot, HeapTuple tuple, uint32 *offp,
+					   int natts)
+{
+	TupleDesc	tupleDesc = slot->tts_tupleDescriptor;
+	Datum	   *values = slot->tts_values;
+	bool	   *isnull = slot->tts_isnull;
+	HeapTupleHeader tup = tuple->t_data;
+	bool		hasnulls = HeapTupleHasNulls(tuple);
+	int			attnum;
+	char	   *tp;				/* ptr to tuple data */
+	uint32		off;			/* offset in tuple data */
+	bits8	   *bp = tup->t_bits;	/* ptr to null bitmap in tuple */
+	bool		slow;			/* can we use/set attcacheoff? */
+
+	/* We can only fetch as many attributes as the tuple has. */
+	natts = Min(HeapTupleHeaderGetNatts(tuple->t_data), natts);
+
+	/*
+	 * Check whether the first call for this tuple, and initialize or restore
+	 * loop state.
+	 */
+	attnum = slot->tts_nvalid;
+	if (attnum == 0)
+	{
+		/* Start from the first attribute */
+		off = 0;
+		slow = false;
+	}
+	else
+	{
+		/* Restore state from previous execution */
+		off = *offp;
+		slow = TTS_SLOW(slot);
+	}
+
+	tp = (char *) tup + tup->t_hoff;
+
+	for (; attnum < natts; attnum++)
+	{
+		Form_pg_attribute thisatt = TupleDescAttr(tupleDesc, attnum);
+
+		if (hasnulls && att_isnull(attnum, bp))
+		{
+			values[attnum] = (Datum) 0;
+			isnull[attnum] = true;
+			slow = true;		/* can't use attcacheoff anymore */
+			continue;
+		}
+
+		isnull[attnum] = false;
+
+		if (!slow && thisatt->attcacheoff >= 0)
+			off = thisatt->attcacheoff;
+		else if (thisatt->attlen == -1)
+		{
+			/*
+			 * We can only cache the offset for a varlena attribute if the
+			 * offset is already suitably aligned, so that there would be no
+			 * pad bytes in any case: then the offset will be valid for either
+			 * an aligned or unaligned value.
+			 */
+			if (!slow &&
+				off == att_align_nominal(off, thisatt->attalign))
+				thisatt->attcacheoff = off;
+			else
+			{
+				off = att_align_pointer(off, thisatt->attalign, -1,
+										tp + off);
+				slow = true;
+			}
+		}
+		else
+		{
+			/* not varlena, so safe to use att_align_nominal */
+			off = att_align_nominal(off, thisatt->attalign);
+
+			if (!slow)
+				thisatt->attcacheoff = off;
+		}
+
+		values[attnum] = fetchatt(thisatt, tp + off);
+
+		off = att_addlength_pointer(off, thisatt->attlen, tp + off);
+
+		if (thisatt->attlen <= 0)
+			slow = true;		/* can't use attcacheoff anymore */
+	}
+
+	/*
+	 * Save state for next execution
+	 */
+	slot->tts_nvalid = attnum;
+	*offp = off;
+	if (slow)
+		slot->tts_flags |= TTS_FLAG_SLOW;
+	else
+		slot->tts_flags &= ~TTS_FLAG_SLOW;
+}
+
+/*
+ * TupleTableSlotOps implementations.
+ */
+
+/*
+ * TupleTableSlotOps implementation for VirtualTupleTableSlot.
+ */
+static void
+tts_virtual_init(TupleTableSlot *slot)
+{
+}
+
+static void
+tts_virtual_release(TupleTableSlot *slot)
+{
+}
+
+static void
+tts_virtual_clear(TupleTableSlot *slot)
+{
+	if (unlikely(TTS_SHOULDFREE(slot)))
+	{
+		VirtualTupleTableSlot *vslot = (VirtualTupleTableSlot *) slot;
+
+		pfree(vslot->data);
+		vslot->data = NULL;
+
+		slot->tts_flags = ~TTS_FLAG_SHOULDFREE;
+	}
+
+	slot->tts_nvalid = 0;
+	slot->tts_flags |= TTS_FLAG_EMPTY;
+}
+
+/*
+ * Attribute values are readily available in tts_values and tts_isnull array
+ * in a VirtualTupleTableSlot. So there should be no need to call either of the
+ * following two functions.
+ */
+static void
+tts_virtual_getsomeattrs(TupleTableSlot *slot, int natts)
+{
+	elog(ERROR, "getsomeattrs is not required to be called on a virtual tuple table slot");
+}
+
+static Datum
+tts_virtual_getsysattr(TupleTableSlot *slot, int attnum, bool *isnull)
+{
+	elog(ERROR, "virtual tuple table slot does not have system atttributes");
+}
+
+/*
+ * To materialize a virtual slot all the datums that aren't passed by value
+ * have to be copied into the slot's memory context.  To do so, count the
+ * required size, and allocate enough memory to store all attributes.  That
+ * both gains efficiency, and makes it easier to release all the memory at
+ * once.
+ */
+static void
+tts_virtual_materialize(TupleTableSlot *slot)
+{
+	VirtualTupleTableSlot *vslot = (VirtualTupleTableSlot *) slot;
+	TupleDesc	desc = slot->tts_tupleDescriptor;
+	Size		sz = 0;
+	char	   *data;
+
+	/* already materialized */
+	if (TTS_SHOULDFREE(slot))
+		return;
+
+	/* compute size of memory required */
+	for (int natt = 0; natt < desc->natts; natt++)
+	{
+		Form_pg_attribute att = TupleDescAttr(desc, natt);
+		Datum val;
+
+		if (att->attbyval || slot->tts_isnull[natt])
+			continue;
+
+		val = slot->tts_values[natt];
+
+		if (att->attlen == -1 &&
+			VARATT_IS_EXTERNAL_EXPANDED(DatumGetPointer(val)))
+		{
+			/*
+			 * We want to flatten the expanded value so that the materialized
+			 * slot doesn't depend on it.
+			 */
+			sz = att_align_nominal(sz, att->attalign);
+			sz += EOH_get_flat_size(DatumGetEOHP(val));
+		}
+		else
+		{
+			sz = att_align_nominal(sz, att->attalign);
+			sz = att_addlength_datum(sz, att->attlen, val);
+		}
+
+		/* FIXME: any reason to expand default args? */
+	}
+
+	/* all data is byval */
+	if (sz == 0)
+		return;
+
+	/* allocate memory */
+	vslot->data = data = MemoryContextAlloc(slot->tts_mcxt, sz);
+	slot->tts_flags |= TTS_FLAG_SHOULDFREE;
+
+	/* and copy all attributes into the pre-allocated space */
+	for (int natt = 0; natt < desc->natts; natt++)
+	{
+		Form_pg_attribute att = TupleDescAttr(desc, natt);
+		Datum val;
+
+		if (att->attbyval || slot->tts_isnull[natt])
+			continue;
+
+		val = slot->tts_values[natt];
+
+		if (att->attlen == -1 &&
+			VARATT_IS_EXTERNAL_EXPANDED(DatumGetPointer(val)))
+		{
+			Size data_length;
+
+			/*
+			 * We want to flatten the expanded value so that the materialized
+			 * slot doesn't depend on it.
+			 */
+			ExpandedObjectHeader *eoh = DatumGetEOHP(val);
+
+			data = (char *) att_align_nominal(data,
+											  att->attalign);
+			data_length = EOH_get_flat_size(eoh);
+			EOH_flatten_into(eoh, data, data_length);
+
+			slot->tts_values[natt] = PointerGetDatum(data);
+			data += data_length;
+		}
+		else
+		{
+			Size data_length = 0;
+
+			data = (char *) att_align_nominal(data, att->attalign);
+			data_length = att_addlength_datum(data_length, att->attlen, val);
+
+			memcpy(data, DatumGetPointer(val), data_length);
+
+			slot->tts_values[natt] = PointerGetDatum(data);
+			data += data_length;
+		}
+		/* FIXME: any reason to expand default args? */
+	}
+}
+
+/*
+ * A virtual tuple table slot can not copy the datums into its own storage. So
+ * it can not copy contents of the other slot into its own memory context.
+ */
+static void
+tts_virtual_copyslot(TupleTableSlot *dstslot, TupleTableSlot *srcslot)
+{
+	TupleDesc	srcdesc = dstslot->tts_tupleDescriptor;
+
+	Assert(srcdesc->natts <= dstslot->tts_tupleDescriptor->natts);
+
+	tts_virtual_clear(dstslot);
+
+	slot_getallattrs(srcslot);
+
+	for (int natt = 0; natt < srcdesc->natts; natt++)
+	{
+		dstslot->tts_values[natt] = srcslot->tts_values[natt];
+		dstslot->tts_isnull[natt] = srcslot->tts_isnull[natt];
+	}
+
+	dstslot->tts_nvalid = srcdesc->natts;
+	dstslot->tts_flags &= ~TTS_FLAG_EMPTY;
+
+	/* make sure storage doesn't depend on external memory */
+	tts_virtual_materialize(dstslot);
+}
+
+static HeapTuple
+tts_virtual_copy_heap_tuple(TupleTableSlot *slot)
+{
+	Assert(!TTS_EMPTY(slot));
+
+	return heap_form_tuple(slot->tts_tupleDescriptor,
+						   slot->tts_values,
+						   slot->tts_isnull);
+
+}
+
+static MinimalTuple
+tts_virtual_copy_minimal_tuple(TupleTableSlot *slot)
+{
+	Assert(!TTS_EMPTY(slot));
+
+	return heap_form_minimal_tuple(slot->tts_tupleDescriptor,
+								   slot->tts_values,
+								   slot->tts_isnull);
+}
+
+/*
+ * TupleTableSlotOps implementation for HeapTupleTableSlot.
+ */
+
+static void
+tts_heap_init(TupleTableSlot *slot)
+{
+}
+
+static void
+tts_heap_release(TupleTableSlot *slot)
+{
+}
+
+static void
+tts_heap_clear(TupleTableSlot *slot)
+{
+	HeapTupleTableSlot *hslot = (HeapTupleTableSlot *) slot;
+
+	/* Free the memory for the heap tuple if it's allowed. */
+	if (TTS_SHOULDFREE(slot))
+	{
+		heap_freetuple(hslot->tuple);
+		slot->tts_flags &= ~TTS_FLAG_SHOULDFREE;
+	}
+
+	slot->tts_nvalid = 0;
+	slot->tts_flags |= TTS_FLAG_EMPTY;
+	hslot->off = 0;
+	hslot->tuple = NULL;
+}
+
+static void
+tts_heap_getsomeattrs(TupleTableSlot *slot, int natts)
+{
+	HeapTupleTableSlot *hslot = (HeapTupleTableSlot *) slot;
+
+	Assert(!TTS_EMPTY(slot));
+
+	slot_deform_heap_tuple(slot, hslot->tuple, &hslot->off, natts);
+}
+
+static Datum
+tts_heap_getsysattr(TupleTableSlot *slot, int attnum, bool *isnull)
+{
+	HeapTupleTableSlot *hslot = (HeapTupleTableSlot *) slot;
+
+	return heap_getsysattr(hslot->tuple, attnum,
+						   slot->tts_tupleDescriptor, isnull);
+}
+
+/*
+ * Materialize the heap tuple contained in the given slot into its own memory
+ * context.
+ */
+static void
+tts_heap_materialize(TupleTableSlot *slot)
+{
+	HeapTupleTableSlot *hslot = (HeapTupleTableSlot *) slot;
+	MemoryContext oldContext;
+
+	Assert(!TTS_EMPTY(slot));
+
+	/* This slot has it's tuple already materialized. Nothing to do. */
+	if (TTS_SHOULDFREE(slot))
+		return;
+
+	slot->tts_flags |= TTS_FLAG_SHOULDFREE;
+
+	oldContext = MemoryContextSwitchTo(slot->tts_mcxt);
+
+	if (!hslot->tuple)
+		hslot->tuple = heap_form_tuple(slot->tts_tupleDescriptor,
+									   slot->tts_values,
+									   slot->tts_isnull);
+	else
+	{
+		/*
+		 * The tuple contained in this slot is not allocated in the memory
+		 * context of the given slot (else it would have TTS_SHOULDFREE set).
+		 * Copy the tuple into the given slot's memory context.
+		 */
+		hslot->tuple = heap_copytuple(hslot->tuple);
+	}
+
+	slot->tts_nvalid = 0;
+	hslot->off = 0;
+
+	MemoryContextSwitchTo(oldContext);
+}
+
+/*
+ * Copy contents of the srcslot into dstslot, on which this callback has been
+ * invoked, in dstslot's memory context. We do this by creating a heap tuple
+ * from the source slot's contents in the memory context of the destination
+ * slot and then storing that heap tuple in the destination slot.
+ */
+static void
+tts_heap_copyslot(TupleTableSlot *dstslot, TupleTableSlot *srcslot)
+{
+	HeapTuple tuple;
+	MemoryContext oldcontext;
+
+	oldcontext = MemoryContextSwitchTo(dstslot->tts_mcxt);
+	tuple = ExecCopySlotTuple(srcslot);
+	MemoryContextSwitchTo(oldcontext);
+
+	ExecStoreHeapTuple(tuple, dstslot, true);
+}
+
+/*
+ * Return the heap tuple in the slot as is if it contains one. Otherwise,
+ * materialize a heap tuple using contents of the slot and return it.
+ */
+static HeapTuple
+tts_heap_get_heap_tuple(TupleTableSlot *slot)
+{
+	HeapTupleTableSlot *hslot = (HeapTupleTableSlot *) slot;
+
+	Assert(!TTS_EMPTY(slot));
+	if (!hslot->tuple)
+		tts_heap_materialize(slot);
+
+	return hslot->tuple;
+}
+
+/*
+ * Return a copy of heap tuple contained in the slot, materialising one if
+ * necessary.
+ */
+static HeapTuple
+tts_heap_copy_heap_tuple(TupleTableSlot *slot)
+{
+	HeapTupleTableSlot *hslot = (HeapTupleTableSlot *) slot;
+
+	Assert(!TTS_EMPTY(slot));
+	if (!hslot->tuple)
+		tts_heap_materialize(slot);
+
+	return heap_copytuple(hslot->tuple);
+}
+
+/*
+ * Return a minimal tuple constructed from the contents of the slot.
+ *
+ * We always return a new minimal tuple so no copy, per say, is needed.
+ */
+static MinimalTuple
+tts_heap_copy_minimal_tuple(TupleTableSlot *slot)
+{
+	HeapTupleTableSlot *hslot = (HeapTupleTableSlot *) slot;
+
+	if (!hslot->tuple)
+		tts_heap_materialize(slot);
+
+	return minimal_tuple_from_heap_tuple(hslot->tuple);
+}
+
+/*
+ * Store the given tuple into the given HeapTupleTableSlot. If the slot
+ * already contains a tuple, we will free the memory for the same before
+ * storing a new one there.
+ */
+static void
+tts_heap_store_tuple(TupleTableSlot *slot, HeapTuple tuple, bool shouldFree)
+{
+	HeapTupleTableSlot *hslot = (HeapTupleTableSlot *) slot;
+
+	tts_heap_clear(slot);
+
+	slot->tts_nvalid = 0;
+	hslot->tuple = tuple;
+	hslot->off = 0;
+	slot->tts_flags &= ~TTS_FLAG_EMPTY;
+
+	if (shouldFree)
+		slot->tts_flags |= TTS_FLAG_SHOULDFREE;
+}
+
+
+static void
+tts_minimal_init(TupleTableSlot *slot)
+{
+	MinimalTupleTableSlot *mslot = (MinimalTupleTableSlot *) slot;
+
+	/*
+	 * Initialize the heap tuple pointer to access attributes of the minimal
+	 * tuple contained in the slot as if its a heap tuple.
+	 */
+	mslot->tuple = &mslot->minhdr;
+}
+
+static void
+tts_minimal_release(TupleTableSlot *slot)
+{
+}
+
+static void
+tts_minimal_clear(TupleTableSlot *slot)
+{
+	MinimalTupleTableSlot *mslot = (MinimalTupleTableSlot *) slot;
+
+	if (TTS_SHOULDFREE(slot))
+	{
+		heap_free_minimal_tuple(mslot->mintuple);
+		slot->tts_flags &= ~TTS_FLAG_SHOULDFREE;
+	}
+
+	slot->tts_nvalid = 0;
+	slot->tts_flags |= TTS_FLAG_EMPTY;
+	mslot->off = 0;
+	mslot->mintuple = NULL;
+}
+
+static void
+tts_minimal_getsomeattrs(TupleTableSlot *slot, int natts)
+{
+	MinimalTupleTableSlot *mslot = (MinimalTupleTableSlot *) slot;
+
+	Assert(!TTS_EMPTY(slot));
+
+	slot_deform_heap_tuple(slot, mslot->tuple, &mslot->off, natts);
+}
+
+static Datum
+tts_minimal_getsysattr(TupleTableSlot *slot, int attnum, bool *isnull)
+{
+	elog(ERROR, "minimal tuple table slot does not have system atttributes");
+}
+
+/*
+ * Materialize the minimal tuple contained in the given slot into its own
+ * memory context.
+ */
+static void
+tts_minimal_materialize(TupleTableSlot *slot)
+{
+	MinimalTupleTableSlot *mslot = (MinimalTupleTableSlot *) slot;
+	MemoryContext oldContext;
+
+	Assert(!TTS_EMPTY(slot));
+
+	/* This slot has it's tuple already materialized. Nothing to do. */
+	if (TTS_SHOULDFREE(slot))
+		return;
+
+	slot->tts_flags |= TTS_FLAG_SHOULDFREE;
+	oldContext = MemoryContextSwitchTo(slot->tts_mcxt);
+
+	if (!mslot->mintuple)
+		mslot->mintuple = heap_form_minimal_tuple(slot->tts_tupleDescriptor,
+												  slot->tts_values,
+												  slot->tts_isnull);
+	else
+	{
+		/*
+		 * The minimal tuple contained in this slot is not allocated in the
+		 * memory context of the given slot (else it would have TTS_SHOULDFREE
+		 * set).  Copy the minimal tuple into the given slot's memory context.
+		 */
+		mslot->mintuple = heap_copy_minimal_tuple(mslot->mintuple);
+	}
+
+	Assert(mslot->tuple == &mslot->minhdr);
+
+	mslot->minhdr.t_len = mslot->mintuple->t_len + MINIMAL_TUPLE_OFFSET;
+	mslot->minhdr.t_data = (HeapTupleHeader) ((char *) mslot->mintuple - MINIMAL_TUPLE_OFFSET);
+
+	MemoryContextSwitchTo(oldContext);
+
+	slot->tts_nvalid = 0;
+	mslot->off = 0;
+}
+
+/*
+ * Copy contents of the srcslot into dstslot, on which this callback has been
+ * invoked, in dstslot's memory context. We do this by creating a minimal tuple
+ * from the source slot's contents in the memory context of the destination
+ * slot and then storing that minimal tuple in the destination slot.
+ */
+static void
+tts_minimal_copyslot(TupleTableSlot *dstslot, TupleTableSlot *srcslot)
+{
+	MemoryContext oldcontext;
+	MinimalTuple mintuple;
+
+	oldcontext = MemoryContextSwitchTo(dstslot->tts_mcxt);
+	mintuple = ExecCopySlotMinimalTuple(srcslot);
+	MemoryContextSwitchTo(oldcontext);
+
+	ExecStoreMinimalTuple(mintuple, dstslot, true);
+}
+
+/*
+ * Return the minimal tuple if slot contains one. Otherwise materialize the
+ * contents of slot into a minimal tuple and return that.
+ */
+static MinimalTuple
+tts_minimal_get_minimal_tuple(TupleTableSlot *slot)
+{
+	MinimalTupleTableSlot *mslot = (MinimalTupleTableSlot *) slot;
+
+	Assert(!TTS_EMPTY(slot));
+
+	if (!mslot->mintuple)
+		tts_minimal_materialize(slot);
+
+	return mslot->mintuple;
+}
+
+/*
+ * Return a heap tuple constructed from the minimal tuple contained in the slot.
+ *
+ * We always construct a new heap tuple, so there is nothing to copy as such.
+ */
+static HeapTuple
+tts_minimal_copy_heap_tuple(TupleTableSlot *slot)
+{
+	MinimalTupleTableSlot *mslot = (MinimalTupleTableSlot *) slot;
+
+	/* Materialize the minimal tuple if not already present. */
+	if (!mslot->mintuple)
+		tts_minimal_materialize(slot);
+
+	return heap_tuple_from_minimal_tuple(mslot->mintuple);
+}
+
+/*
+ * Return a copy of minimal tuple contained in the slot, materializing one if
+ * necessary.
+ */
+static MinimalTuple
+tts_minimal_copy_minimal_tuple(TupleTableSlot *slot)
+{
+	MinimalTupleTableSlot *mslot = (MinimalTupleTableSlot *) slot;
+
+	if (!mslot->mintuple)
+		tts_minimal_materialize(slot);
+
+	return heap_copy_minimal_tuple(mslot->mintuple);
+}
+
+/*
+ * Store the given minimal tuple into the given MinimalTupleTableSlot. If the
+ * slot already contains a tuple, we will free the memory for the same before
+ * storing a new one there.
+ */
+static void
+tts_minimal_store_tuple(TupleTableSlot *slot, MinimalTuple mtup, bool shouldFree)
+{
+	MinimalTupleTableSlot *mslot = (MinimalTupleTableSlot *) slot;
+
+	tts_minimal_clear(slot);
+
+	Assert(!TTS_SHOULDFREE(slot));
+	Assert(TTS_EMPTY(slot));
+
+	slot->tts_flags &= ~TTS_FLAG_EMPTY;
+	slot->tts_nvalid = 0;
+	mslot->off = 0;
+
+	mslot->mintuple = mtup;
+	Assert(mslot->tuple == &mslot->minhdr);
+	mslot->minhdr.t_len = mtup->t_len + MINIMAL_TUPLE_OFFSET;
+	mslot->minhdr.t_data = (HeapTupleHeader) ((char *) mtup - MINIMAL_TUPLE_OFFSET);
+	/* no need to set t_self or t_tableOid since we won't allow access */
+
+	if (shouldFree)
+		slot->tts_flags |= TTS_FLAG_SHOULDFREE;
+	else
+		Assert(!TTS_SHOULDFREE(slot));
+}
+
+/*
+ * TupleTableSlotOps implementation for BufferHeapTupleTableSlot.
+ */
+
+static void
+tts_buffer_heap_init(TupleTableSlot *slot)
+{
+}
+
+static void
+tts_buffer_heap_release(TupleTableSlot *slot)
+{
+}
+
+static void
+tts_buffer_heap_clear(TupleTableSlot *slot)
+{
+	BufferHeapTupleTableSlot *bslot = (BufferHeapTupleTableSlot *) slot;
+
+	/*
+	 * Free the memory for heap tuple if allowed. A tuple coming from buffer
+	 * can never be freed. But we may have materialized a tuple from buffer.
+	 * Such a tuple can be freed.
+	 */
+	if (TTS_SHOULDFREE(slot))
+	{
+		/* We should have unpinned the buffer while materializing the tuple. */
+		Assert(!BufferIsValid(bslot->buffer));
+
+		heap_freetuple(bslot->base.tuple);
+		slot->tts_flags &= ~TTS_FLAG_SHOULDFREE;
+
+		Assert(!BufferIsValid(bslot->buffer));
+	}
+
+	if (BufferIsValid(bslot->buffer))
+		ReleaseBuffer(bslot->buffer);
+
+	slot->tts_nvalid = 0;
+	slot->tts_flags |= TTS_FLAG_EMPTY;
+	bslot->base.tuple = NULL;
+	bslot->base.off = 0;
+	bslot->buffer = InvalidBuffer;
+}
+
+static void
+tts_buffer_heap_getsomeattrs(TupleTableSlot *slot, int natts)
+{
+	BufferHeapTupleTableSlot *bslot = (BufferHeapTupleTableSlot *) slot;
+
+	Assert(!TTS_EMPTY(slot));
+
+	slot_deform_heap_tuple(slot, bslot->base.tuple, &bslot->base.off, natts);
+}
+
+static Datum
+tts_buffer_heap_getsysattr(TupleTableSlot *slot, int attnum, bool *isnull)
+{
+	BufferHeapTupleTableSlot *bslot = (BufferHeapTupleTableSlot *) slot;
+
+	return heap_getsysattr(bslot->base.tuple, attnum,
+						   slot->tts_tupleDescriptor, isnull);
+}
+
+/*
+ * Materialize the heap tuple contained in the given slot into its own memory
+ * context.
+ */
+static void
+tts_buffer_heap_materialize(TupleTableSlot *slot)
+{
+	BufferHeapTupleTableSlot *bslot = (BufferHeapTupleTableSlot *) slot;
+	MemoryContext oldContext;
+
+	Assert(!TTS_EMPTY(slot));
+
+	/* If already materialized nothing to do. */
+	if (TTS_SHOULDFREE(slot))
+		return;
+
+	slot->tts_flags |= TTS_FLAG_SHOULDFREE;
+	oldContext = MemoryContextSwitchTo(slot->tts_mcxt);
+
+	if (!bslot->base.tuple)
+	{
+		/*
+		 * A heap tuple stored in a BufferHeapTupleTableSlot should have a
+		 * buffer associated with it, unless it's materialized. Thus it will
+		 * never happen that there is no heap tuple stored in this slot but
+		 * tts_values and tts_isnull is set. IOW, we should never require to construct a
+		 * tuple from tts_values and tts_isnull in this slot.
+		 */
+		elog(ERROR, "a buffer tuple table slot should never require to construct a tuple from datums");
+	}
+	bslot->base.tuple = heap_copytuple(bslot->base.tuple);
+	MemoryContextSwitchTo(oldContext);
+
+	/*
+	 * A heap tuple stored in a BufferHeapTupleTableSlot should have a buffer
+	 * associated with it, unless it's materialized.
+	 */
+	Assert(BufferIsValid(bslot->buffer));
+	ReleaseBuffer(bslot->buffer);
+	bslot->buffer = InvalidBuffer;
+
+	bslot->base.off = 0;
+	slot->tts_nvalid = 0;
+}
+
+/*
+ * A buffer tuple table slot is used to store an on-disk tuple in it, at least
+ * to start with. So a need to copy into a buffer tuple table slot should not
+ * arise. May be we could support copying from a buffer tuple table slot, but
+ * it is not clear why it's required to do that.
+ */
+static void
+tts_buffer_heap_copyslot(TupleTableSlot *dstslot, TupleTableSlot *srcslot)
+{
+	BufferHeapTupleTableSlot *bsrcslot = (BufferHeapTupleTableSlot *) srcslot;
+	BufferHeapTupleTableSlot *bdstslot = (BufferHeapTupleTableSlot *) dstslot;
+
+	Assert(dstslot->tts_cb == srcslot->tts_cb);
+
+	if (TTS_SHOULDFREE(srcslot))
+	{
+		MemoryContext oldContext;
+
+		ExecClearTuple(dstslot);
+		dstslot->tts_flags |= TTS_FLAG_SHOULDFREE;
+		dstslot->tts_flags &= ~TTS_FLAG_EMPTY;
+		oldContext = MemoryContextSwitchTo(dstslot->tts_mcxt);
+		bdstslot->base.tuple = heap_copytuple(bsrcslot->base.tuple);
+		MemoryContextSwitchTo(oldContext);
+	}
+	else
+	{
+		tts_buffer_heap_store_tuple(dstslot, bsrcslot->base.tuple, bsrcslot->buffer);
+		tts_buffer_heap_materialize(dstslot);
+	}
+}
+
+/*
+ * Return the heap tuple in the slot as is if it contains one. Otherwise,
+ * materialize a heap tuple using contents of the slot and return it.
+ */
+static HeapTuple
+tts_buffer_heap_get_heap_tuple(TupleTableSlot *slot)
+{
+	BufferHeapTupleTableSlot *bslot = (BufferHeapTupleTableSlot *) slot;
+
+	Assert(!TTS_EMPTY(slot));
+
+	/* Is this even possible for a buffer tuple? */
+	if (!bslot->base.tuple)
+		tts_buffer_heap_materialize(slot);
+
+	return bslot->base.tuple;
+}
+
+/*
+ * Return a copy of heap tuple contained in the slot, materialising one if
+ * necessary.
+ */
+static HeapTuple
+tts_buffer_heap_copy_heap_tuple(TupleTableSlot *slot)
+{
+	BufferHeapTupleTableSlot *bslot = (BufferHeapTupleTableSlot *) slot;
+
+	Assert(!TTS_EMPTY(slot));
+
+	/* Is this even possible for a buffer tuple? */
+	if (!bslot->base.tuple)
+		tts_buffer_heap_materialize(slot);
+
+	return heap_copytuple(bslot->base.tuple);
+}
+
+/*
+ * Return a minimal tuple constructed from the contents of the slot.
+ *
+ * We always return a new minimal tuple so no copy, per say, is needed.
+ */
+static MinimalTuple
+tts_buffer_heap_copy_minimal_tuple(TupleTableSlot *slot)
+{
+	BufferHeapTupleTableSlot *bslot = (BufferHeapTupleTableSlot *) slot;
+
+	Assert(!TTS_EMPTY(slot));
+
+	/* Is this even possible for a buffer tuple? */
+	if (!bslot->base.tuple)
+		tts_buffer_heap_materialize(slot);
+
+	return minimal_tuple_from_heap_tuple(bslot->base.tuple);
+}
+
+/*
+ * Store the given tuple into the given BufferHeapTupleTableSlot and pin the
+ * given buffer. If the tuple already contained in the slot can be freed free
+ * it.
+ */
+static void
+tts_buffer_heap_store_tuple(TupleTableSlot *slot, HeapTuple tuple, Buffer buffer)
+{
+	BufferHeapTupleTableSlot *bslot = (BufferHeapTupleTableSlot *) slot;
+
+	if (TTS_SHOULDFREE(slot))
+	{
+		/*
+		 * A heap tuple stored in a BufferHeapTupleTableSlot should have a
+		 * buffer associated with it, unless it's materialized.
+		 */
+		Assert(!BufferIsValid(bslot->buffer));
+
+		heap_freetuple(bslot->base.tuple);
+		slot->tts_flags &= ~TTS_FLAG_SHOULDFREE;
+	}
+
+	slot->tts_flags &= ~TTS_FLAG_EMPTY;
+	slot->tts_nvalid = 0;
+	bslot->base.tuple = tuple;
+	bslot->base.off = 0;
+
+	/*
+	 * If tuple is on a disk page, keep the page pinned as long as we hold a
+	 * pointer into it.  We assume the caller already has such a pin.
+	 *
+	 * This is coded to optimize the case where the slot previously held a
+	 * tuple on the same disk page: in that case releasing and re-acquiring
+	 * the pin is a waste of cycles.  This is a common situation during
+	 * seqscans, so it's worth troubling over.
+	 */
+	if (bslot->buffer != buffer)
+	{
+		if (BufferIsValid(bslot->buffer))
+			ReleaseBuffer(bslot->buffer);
+		bslot->buffer = buffer;
+		IncrBufferRefCount(buffer);
+	}
+}
+
+/*
+ * TupleTableSlotOps for each of TupleTableSlotTypes. These are used to
+ * identify the type of slot.
+ */
+const TupleTableSlotOps TTSOpsVirtual = {
+	.base_slot_size = sizeof(VirtualTupleTableSlot),
+	.init = tts_virtual_init,
+	.release = tts_virtual_release,
+	.clear = tts_virtual_clear,
+	.getsomeattrs = tts_virtual_getsomeattrs,
+	.getsysattr = tts_virtual_getsysattr,
+	.materialize = tts_virtual_materialize,
+	.copyslot = tts_virtual_copyslot,
+
+	/*
+	 * A virtual tuple table slot can not "own" a heap tuple or a minimal
+	 * tuple.
+	 */
+	.get_heap_tuple = NULL,
+	.get_minimal_tuple = NULL,
+	.copy_heap_tuple = tts_virtual_copy_heap_tuple,
+	.copy_minimal_tuple = tts_virtual_copy_minimal_tuple
+};
+
+const TupleTableSlotOps TTSOpsHeapTuple = {
+	.base_slot_size = sizeof(HeapTupleTableSlot),
+	.init = tts_heap_init,
+	.release = tts_heap_release,
+	.clear = tts_heap_clear,
+	.getsomeattrs = tts_heap_getsomeattrs,
+	.getsysattr = tts_heap_getsysattr,
+	.materialize = tts_heap_materialize,
+	.copyslot = tts_heap_copyslot,
+	.get_heap_tuple = tts_heap_get_heap_tuple,
+
+	/* A heap tuple table slot can not "own" a minimal tuple. */
+	.get_minimal_tuple = NULL,
+	.copy_heap_tuple = tts_heap_copy_heap_tuple,
+	.copy_minimal_tuple = tts_heap_copy_minimal_tuple
+};
+
+const TupleTableSlotOps TTSOpsMinimalTuple = {
+	.base_slot_size = sizeof(MinimalTupleTableSlot),
+	.init = tts_minimal_init,
+	.release = tts_minimal_release,
+	.clear = tts_minimal_clear,
+	.getsomeattrs = tts_minimal_getsomeattrs,
+	.getsysattr = tts_minimal_getsysattr,
+	.materialize = tts_minimal_materialize,
+	.copyslot = tts_minimal_copyslot,
+
+	/*
+	 * A minimal tuple table slot can not "own" a heap tuple. As mentioned in
+	 * the prologue of MinimalTupleData, a minimal tuple may be presented as a
+	 * heap tuple, but such a representation does not have memory to hold
+	 * system attributes in it and should be used for accessing/manipulating
+	 * tuple through a slot. Hence we do not return heap tuple representation
+	 * crafted from a minimal tuple to be returned in get_heap_tuple callback
+	 * of this TupleTableSlotType.
+	 */
+	.get_heap_tuple = NULL,
+	.get_minimal_tuple = tts_minimal_get_minimal_tuple,
+	.copy_heap_tuple = tts_minimal_copy_heap_tuple,
+	.copy_minimal_tuple = tts_minimal_copy_minimal_tuple
+};
+
+const TupleTableSlotOps TTSOpsBufferTuple = {
+	.base_slot_size = sizeof(BufferHeapTupleTableSlot),
+	.init = tts_buffer_heap_init,
+	.release = tts_buffer_heap_release,
+	.clear = tts_buffer_heap_clear,
+	.getsomeattrs = tts_buffer_heap_getsomeattrs,
+	.getsysattr = tts_buffer_heap_getsysattr,
+	.materialize = tts_buffer_heap_materialize,
+	.copyslot = tts_buffer_heap_copyslot,
+	.get_heap_tuple = tts_buffer_heap_get_heap_tuple,
+
+	/* A buffer heap tuple table slot can not "own" a minimal tuple. */
+	.get_minimal_tuple = NULL,
+	.copy_heap_tuple = tts_buffer_heap_copy_heap_tuple,
+	.copy_minimal_tuple = tts_buffer_heap_copy_minimal_tuple
+};
+
 /* ----------------------------------------------------------------
  *				  tuple table create/delete functions
  * ----------------------------------------------------------------
@@ -85,58 +1097,60 @@ const TupleTableSlotOps TTSOpsBufferTuple;
 /* --------------------------------
  *		MakeTupleTableSlot
  *
- *		Basic routine to make an empty TupleTableSlot. If tupleDesc is
- *		specified the slot's descriptor is fixed for it's lifetime, gaining
- *		some efficiency. If that's undesirable, pass NULL.
+ *		Basic routine to make an empty TupleTableSlot of given
+ *		TupleTableSlotType. If tupleDesc is specified the slot's descriptor is
+ *		fixed for it's lifetime, gaining some efficiency. If that's
+ *		undesirable, pass NULL.
  * --------------------------------
  */
 TupleTableSlot *
 MakeTupleTableSlot(TupleDesc tupleDesc,
 				   const TupleTableSlotOps *tts_cb)
 {
-	Size		sz;
+	Size		basesz, allocsz;
 	TupleTableSlot *slot;
+	basesz = tts_cb->base_slot_size;
 
 	/*
 	 * When a fixed descriptor is specified, we can reduce overhead by
 	 * allocating the entire slot in one go.
 	 */
 	if (tupleDesc)
-		sz = MAXALIGN(sizeof(TupleTableSlot)) +
+		allocsz = MAXALIGN(basesz) +
 			MAXALIGN(tupleDesc->natts * sizeof(Datum)) +
 			MAXALIGN(tupleDesc->natts * sizeof(bool));
 	else
-		sz = sizeof(TupleTableSlot);
+		allocsz = basesz;
 
-	slot = palloc0(sz);
+	slot = palloc0(allocsz);
 	/* const for optimization purposes, OK to modify at allocation time */
 	*((const TupleTableSlotOps **) &slot->tts_cb) = tts_cb;
 	slot->type = T_TupleTableSlot;
 	slot->tts_flags |= TTS_FLAG_EMPTY;
 	if (tupleDesc != NULL)
 		slot->tts_flags |= TTS_FLAG_FIXED;
-	slot->tts_tuple = NULL;
 	slot->tts_tupleDescriptor = tupleDesc;
 	slot->tts_mcxt = CurrentMemoryContext;
-	slot->tts_buffer = InvalidBuffer;
 	slot->tts_nvalid = 0;
-	slot->tts_values = NULL;
-	slot->tts_isnull = NULL;
-	slot->tts_mintuple = NULL;
 
 	if (tupleDesc != NULL)
 	{
 		slot->tts_values = (Datum *)
 			(((char *) slot)
-			 + MAXALIGN(sizeof(TupleTableSlot)));
+			 + MAXALIGN(basesz));
 		slot->tts_isnull = (bool *)
 			(((char *) slot)
-			 + MAXALIGN(sizeof(TupleTableSlot))
+			 + MAXALIGN(basesz)
 			 + MAXALIGN(tupleDesc->natts * sizeof(Datum)));
 
 		PinTupleDesc(tupleDesc);
 	}
 
+	/*
+	 * And allow slot type specific initialization.
+	 */
+	slot->tts_cb->init(slot);
+
 	return slot;
 }
 
@@ -178,6 +1192,7 @@ ExecResetTupleTable(List *tupleTable,	/* tuple table */
 
 		/* Always release resources and reset the slot to empty */
 		ExecClearTuple(slot);
+		slot->tts_cb->release(slot);
 		if (slot->tts_tupleDescriptor)
 		{
 			ReleaseTupleDesc(slot->tts_tupleDescriptor);
@@ -234,6 +1249,7 @@ ExecDropSingleTupleTableSlot(TupleTableSlot *slot)
 	/* This should match ExecResetTupleTable's processing of one slot */
 	Assert(IsA(slot, TupleTableSlot));
 	ExecClearTuple(slot);
+	slot->tts_cb->release(slot);
 	if (slot->tts_tupleDescriptor)
 		ReleaseTupleDesc(slot->tts_tupleDescriptor);
 	if (!TTS_FIXED(slot))
@@ -334,36 +1350,9 @@ ExecStoreHeapTuple(HeapTuple tuple,
 	Assert(slot != NULL);
 	Assert(slot->tts_tupleDescriptor != NULL);
 
-	/*
-	 * Free any old physical tuple belonging to the slot.
-	 */
-	if (TTS_SHOULDFREE(slot))
-	{
-		heap_freetuple(slot->tts_tuple);
-		slot->tts_flags &= ~TTS_FLAG_SHOULDFREE;
-	}
-	if (TTS_SHOULDFREEMIN(slot))
-	{
-		heap_free_minimal_tuple(slot->tts_mintuple);
-		slot->tts_flags &= ~TTS_FLAG_SHOULDFREEMIN;
-	}
-
-	/*
-	 * Store the new tuple into the specified slot.
-	 */
-	slot->tts_flags &= ~TTS_FLAG_EMPTY;
-	if (shouldFree)
-		slot->tts_flags |= TTS_FLAG_SHOULDFREE;
-	slot->tts_tuple = tuple;
-	slot->tts_mintuple = NULL;
-
-	/* Mark extracted state invalid */
-	slot->tts_nvalid = 0;
-
-	/* Unpin any buffer pinned by the slot. */
-	if (BufferIsValid(slot->tts_buffer))
-		ReleaseBuffer(slot->tts_buffer);
-	slot->tts_buffer = InvalidBuffer;
+	if (!TTS_IS_HEAPTUPLE(slot))
+		elog(ERROR, "trying to store a heap tuple into wrong type of slot");
+	tts_heap_store_tuple(slot, tuple, shouldFree);
 
 	return slot;
 }
@@ -397,46 +1386,9 @@ ExecStoreBufferHeapTuple(HeapTuple tuple,
 	Assert(slot->tts_tupleDescriptor != NULL);
 	Assert(BufferIsValid(buffer));
 
-	/*
-	 * Free any old physical tuple belonging to the slot.
-	 */
-	if (TTS_SHOULDFREE(slot))
-	{
-		heap_freetuple(slot->tts_tuple);
-		slot->tts_flags &= ~TTS_FLAG_SHOULDFREE;
-	}
-	if (TTS_SHOULDFREEMIN(slot))
-	{
-		heap_free_minimal_tuple(slot->tts_mintuple);
-		slot->tts_flags &= ~TTS_FLAG_SHOULDFREEMIN;
-	}
-
-	/*
-	 * Store the new tuple into the specified slot.
-	 */
-	slot->tts_flags &= ~TTS_FLAG_EMPTY;
-	slot->tts_tuple = tuple;
-	slot->tts_mintuple = NULL;
-
-	/* Mark extracted state invalid */
-	slot->tts_nvalid = 0;
-
-	/*
-	 * Keep the disk page containing the given tuple pinned as long as we hold
-	 * a pointer into it.  We assume the caller already has such a pin.
-	 *
-	 * This is coded to optimize the case where the slot previously held a
-	 * tuple on the same disk page: in that case releasing and re-acquiring the
-	 * pin is a waste of cycles.  This is a common situation during seqscans,
-	 * so it's worth troubling over.
-	 */
-	if (slot->tts_buffer != buffer)
-	{
-		if (BufferIsValid(slot->tts_buffer))
-			ReleaseBuffer(slot->tts_buffer);
-		slot->tts_buffer = buffer;
-		IncrBufferRefCount(buffer);
-	}
+	if (!TTS_IS_BUFFERTUPLE(slot))
+		elog(ERROR, "trying to store an on-disk heap tuple into wrong type of slot");
+	tts_buffer_heap_store_tuple(slot, tuple, buffer);
 
 	return slot;
 }
@@ -461,93 +1413,9 @@ ExecStoreMinimalTuple(MinimalTuple mtup,
 	Assert(slot != NULL);
 	Assert(slot->tts_tupleDescriptor != NULL);
 
-	/*
-	 * Free any old physical tuple belonging to the slot.
-	 */
-	if (TTS_SHOULDFREE(slot))
-	{
-		heap_freetuple(slot->tts_tuple);
-		slot->tts_flags &= ~TTS_FLAG_SHOULDFREE;
-	}
-	if (TTS_SHOULDFREEMIN(slot))
-	{
-		heap_free_minimal_tuple(slot->tts_mintuple);
-		slot->tts_flags &= ~TTS_FLAG_SHOULDFREEMIN;
-	}
-
-	/*
-	 * Drop the pin on the referenced buffer, if there is one.
-	 */
-	if (BufferIsValid(slot->tts_buffer))
-		ReleaseBuffer(slot->tts_buffer);
-
-	slot->tts_buffer = InvalidBuffer;
-
-	/*
-	 * Store the new tuple into the specified slot.
-	 */
-	slot->tts_flags &= ~TTS_FLAG_EMPTY;
-	if (shouldFree)
-		slot->tts_flags |= TTS_FLAG_SHOULDFREEMIN;
-	slot->tts_tuple = &slot->tts_minhdr;
-	slot->tts_mintuple = mtup;
-
-	slot->tts_minhdr.t_len = mtup->t_len + MINIMAL_TUPLE_OFFSET;
-	slot->tts_minhdr.t_data = (HeapTupleHeader) ((char *) mtup - MINIMAL_TUPLE_OFFSET);
-	/* no need to set t_self or t_tableOid since we won't allow access */
-
-	/* Mark extracted state invalid */
-	slot->tts_nvalid = 0;
-
-	return slot;
-}
-
-/* --------------------------------
- *		ExecClearTuple
- *
- *		This function is used to clear out a slot in the tuple table.
- *
- *		NB: only the tuple is cleared, not the tuple descriptor (if any).
- * --------------------------------
- */
-TupleTableSlot *				/* return: slot passed */
-ExecClearTuple(TupleTableSlot *slot)	/* slot in which to store tuple */
-{
-	/*
-	 * sanity checks
-	 */
-	Assert(slot != NULL);
-
-	/*
-	 * Free the old physical tuple if necessary.
-	 */
-	if (TTS_SHOULDFREE(slot))
-	{
-		heap_freetuple(slot->tts_tuple);
-		slot->tts_flags &= ~TTS_FLAG_SHOULDFREE;
-	}
-	if (TTS_SHOULDFREEMIN(slot))
-	{
-		heap_free_minimal_tuple(slot->tts_mintuple);
-		slot->tts_flags &= ~TTS_FLAG_SHOULDFREEMIN;
-	}
-
-	slot->tts_tuple = NULL;
-	slot->tts_mintuple = NULL;
-
-	/*
-	 * Drop the pin on the referenced buffer, if there is one.
-	 */
-	if (BufferIsValid(slot->tts_buffer))
-		ReleaseBuffer(slot->tts_buffer);
-
-	slot->tts_buffer = InvalidBuffer;
-
-	/*
-	 * Mark it empty.
-	 */
-	slot->tts_flags |= TTS_FLAG_EMPTY;
-	slot->tts_nvalid = 0;
+	if (!TTS_IS_MINIMALTUPLE(slot))
+		elog(ERROR, "trying to store a minimal tuple into wrong type of slot");
+	tts_minimal_store_tuple(slot, mtup, shouldFree);
 
 	return slot;
 }
@@ -611,79 +1479,19 @@ ExecStoreAllNullTuple(TupleTableSlot *slot)
 }
 
 /* --------------------------------
- *		ExecCopySlotTuple
- *			Obtain a copy of a slot's regular physical tuple.  The copy is
- *			palloc'd in the current memory context.
- *			The slot itself is undisturbed.
+ *		ExecFetchSlotTuple
+ *			Fetch the slot's regular physical tuple.
  *
- *		This works even if the slot contains a virtual or minimal tuple;
- *		however the "system columns" of the result will not be meaningful.
+ *		This is a thin wrapper around TupleTableSlotType specific
+ *		get_heap_tuple callback. The callback is expected to return a heap
+ *		tuple as is if it holds one and continue to have ownership of the heap
+ *		tuple. If materialize is true, the function calls TupleTableSlotType
+ *		specific materialize() callback which is expected to materialize the
+ *		tuple within the slot so that the slot "owns" it. If materialize is
+ *		false, the returned tuple is not guaranteed to have storage local to
+ *		the slot and hence should be treated as read-only.
  * --------------------------------
  */
-HeapTuple
-ExecCopySlotTuple(TupleTableSlot *slot)
-{
-	/*
-	 * sanity checks
-	 */
-	Assert(slot != NULL);
-	Assert(!TTS_EMPTY(slot));
-
-	/*
-	 * If we have a physical tuple (either format) then just copy it.
-	 */
-	if (TTS_HAS_PHYSICAL_TUPLE(slot))
-		return heap_copytuple(slot->tts_tuple);
-	if (slot->tts_mintuple)
-		return heap_tuple_from_minimal_tuple(slot->tts_mintuple);
-
-	/*
-	 * Otherwise we need to build a tuple from the Datum array.
-	 */
-	return heap_form_tuple(slot->tts_tupleDescriptor,
-						   slot->tts_values,
-						   slot->tts_isnull);
-}
-
-/* --------------------------------
- *		ExecCopySlotMinimalTuple
- *			Obtain a copy of a slot's minimal physical tuple.  The copy is
- *			palloc'd in the current memory context.
- *			The slot itself is undisturbed.
- * --------------------------------
- */
-MinimalTuple
-ExecCopySlotMinimalTuple(TupleTableSlot *slot)
-{
-	/*
-	 * sanity checks
-	 */
-	Assert(slot != NULL);
-	Assert(!TTS_EMPTY(slot));
-
-	/*
-	 * If we have a physical tuple then just copy it.  Prefer to copy
-	 * tts_mintuple since that's a tad cheaper.
-	 */
-	if (slot->tts_mintuple)
-		return heap_copy_minimal_tuple(slot->tts_mintuple);
-	if (slot->tts_tuple)
-	{
-		if (HeapTupleHeaderGetNatts(slot->tts_tuple->t_data)
-			< slot->tts_tupleDescriptor->natts)
-			return minimal_expand_tuple(slot->tts_tuple,
-										slot->tts_tupleDescriptor);
-		else
-			return minimal_tuple_from_heap_tuple(slot->tts_tuple);
-	}
-
-	/*
-	 * Otherwise we need to build a tuple from the Datum array.
-	 */
-	return heap_form_minimal_tuple(slot->tts_tupleDescriptor,
-								   slot->tts_values,
-								   slot->tts_isnull);
-}
 
 /*
  * ExecFetchSlotHeapTuple - fetch HeapTuple representing the slot's content
@@ -713,89 +1521,67 @@ ExecFetchSlotHeapTuple(TupleTableSlot *slot, bool materialize, bool *shouldFree)
 	Assert(slot != NULL);
 	Assert(!TTS_EMPTY(slot));
 
-	if (shouldFree)
-		*shouldFree = false;
+	/* Materialize the tuple so that the slot "owns" it, if requested. */
+	if (materialize)
+		slot->tts_cb->materialize(slot);
 
-	/*
-	 * If we have a regular physical tuple then just return it.
-	 */
-	if (TTS_HAS_PHYSICAL_TUPLE(slot))
+	if (slot->tts_cb->get_heap_tuple == NULL)
 	{
-		if (HeapTupleHeaderGetNatts(slot->tts_tuple->t_data) <
-			slot->tts_tupleDescriptor->natts)
-		{
-			HeapTuple	tuple;
-			MemoryContext oldContext = MemoryContextSwitchTo(slot->tts_mcxt);
-
-			tuple = heap_expand_tuple(slot->tts_tuple,
-									  slot->tts_tupleDescriptor);
-			MemoryContextSwitchTo(oldContext);
-			slot = ExecStoreHeapTuple(tuple, slot, true);
-		}
-		return slot->tts_tuple;
+		if (shouldFree)
+			*shouldFree = true;
+		return slot->tts_cb->copy_heap_tuple(slot);
+	}
+	else
+	{
+		if (shouldFree)
+			*shouldFree = false;
+		return slot->tts_cb->get_heap_tuple(slot);
 	}
-
-	/*
-	 * Otherwise materialize the slot...
-	 */
-	ExecMaterializeSlot(slot);
-
-	return slot->tts_tuple;
 }
 
 /* --------------------------------
  *		ExecFetchSlotMinimalTuple
  *			Fetch the slot's minimal physical tuple.
  *
- *		If the slot contains a virtual tuple, we convert it to minimal
- *		physical form.  The slot retains ownership of the minimal tuple.
- *		If it contains a regular tuple we convert to minimal form and store
- *		that in addition to the regular tuple (not instead of, because
- *		callers may hold pointers to Datums within the regular tuple).
+ *		If the given tuple table slot can hold a minimal tuple, indicated by a
+ *		non-NULL get_minimal_tuple callback, the function returns the minimal
+ *		tuple returned by that callback. It assumes that the minimal tuple
+ *		returned by the callback is "owned" by the slot i.e. the slot is
+ *		responsible for freeing the memory consumed by the tuple. Hence it sets
+ *		*shouldFree to false, indicating that the caller should not free the
+ *		memory consumed by the minimal tuple. In this case the returned minimal
+ *		tuple should be considered as read-only.
  *
- * As above, the result must be treated as read-only.
+ *		If that callback is not supported, it calls copy_minimal_tuple callback
+ *		which is expected to return a copy of minimal tuple represnting the
+ *		contents of the slot. In this case *shouldFree is set to true,
+ *		indicating the caller that it should free the memory consumed by the
+ *		minimal tuple. In this case the returned minimal tuple may be written
+ *		up.
  * --------------------------------
  */
 MinimalTuple
-ExecFetchSlotMinimalTuple(TupleTableSlot *slot, bool *shouldFree)
+ExecFetchSlotMinimalTuple(TupleTableSlot *slot,
+						  bool *shouldFree)
 {
-	MemoryContext oldContext;
-
 	/*
 	 * sanity checks
 	 */
 	Assert(slot != NULL);
 	Assert(!TTS_EMPTY(slot));
 
-	if (shouldFree)
-		*shouldFree = false;
-
-	/*
-	 * If we have a minimal physical tuple (local or not) then just return it.
-	 */
-	if (slot->tts_mintuple)
-		return slot->tts_mintuple;
-
-	/*
-	 * Otherwise, copy or build a minimal tuple, and store it into the slot.
-	 *
-	 * We may be called in a context that is shorter-lived than the tuple
-	 * slot, but we have to ensure that the materialized tuple will survive
-	 * anyway.
-	 */
-	oldContext = MemoryContextSwitchTo(slot->tts_mcxt);
-	slot->tts_mintuple = ExecCopySlotMinimalTuple(slot);
-	slot->tts_flags |= TTS_FLAG_SHOULDFREEMIN;
-	MemoryContextSwitchTo(oldContext);
-
-	/*
-	 * Note: we may now have a situation where we have a local minimal tuple
-	 * attached to a virtual or non-local physical tuple.  There seems no harm
-	 * in that at the moment, but if any materializes, we should change this
-	 * function to force the slot into minimal-tuple-only state.
-	 */
-
-	return slot->tts_mintuple;
+	if (slot->tts_cb->get_minimal_tuple)
+	{
+		if (shouldFree)
+			*shouldFree = false;
+		return slot->tts_cb->get_minimal_tuple(slot);
+	}
+	else
+	{
+		if (shouldFree)
+			*shouldFree = true;
+		return slot->tts_cb->copy_minimal_tuple(slot);
+	}
 }
 
 /* --------------------------------
@@ -826,103 +1612,58 @@ ExecFetchSlotHeapTupleDatum(TupleTableSlot *slot)
 	return ret;
 }
 
-/* ExecMaterializeSlot - force a slot into the "materialized" state.
- *
- * This causes the slot's tuple to be a local copy not dependent on any
- * external storage (i.e. pointing into a Buffer, or having allocations in
- * another memory context).
- *
- * A typical use for this operation is to prepare a computed tuple for being
- * stored on disk.  The original data may or may not be virtual, but in any
- * case we need a private copy for heap_insert to scribble on.
- */
 void
-ExecMaterializeSlot(TupleTableSlot *slot)
+ExecForceStoreHeapTuple(HeapTuple tuple,
+						TupleTableSlot *slot)
 {
-	MemoryContext oldContext;
+	if (TTS_IS_HEAPTUPLE(slot))
+	{
+		ExecStoreHeapTuple(tuple, slot, false);
+	}
+	else if (TTS_IS_BUFFERTUPLE(slot))
+	{
+		MemoryContext oldContext;
+		BufferHeapTupleTableSlot *bslot = (BufferHeapTupleTableSlot *) slot;
 
-	/*
-	 * sanity checks
-	 */
-	Assert(slot != NULL);
-	Assert(!TTS_EMPTY(slot));
-
-	/*
-	 * If we have a regular physical tuple, and it's locally palloc'd, we have
-	 * nothing to do.
-	 */
-	if (slot->tts_tuple && TTS_SHOULDFREE(slot))
-		return;
-
-	/*
-	 * Otherwise, copy or build a physical tuple, and store it into the slot.
-	 *
-	 * We may be called in a context that is shorter-lived than the tuple
-	 * slot, but we have to ensure that the materialized tuple will survive
-	 * anyway.
-	 */
-	oldContext = MemoryContextSwitchTo(slot->tts_mcxt);
-	slot->tts_tuple = ExecCopySlotTuple(slot);
-	slot->tts_flags |= TTS_FLAG_SHOULDFREE;
-	MemoryContextSwitchTo(oldContext);
-
-	/*
-	 * Drop the pin on the referenced buffer, if there is one.
-	 */
-	if (BufferIsValid(slot->tts_buffer))
-		ReleaseBuffer(slot->tts_buffer);
-
-	slot->tts_buffer = InvalidBuffer;
-
-	/*
-	 * Mark extracted state invalid.  This is important because the slot is
-	 * not supposed to depend any more on the previous external data; we
-	 * mustn't leave any dangling pass-by-reference datums in tts_values.
-	 * However, we have not actually invalidated any such datums, if there
-	 * happen to be any previously fetched from the slot.  (Note in particular
-	 * that we have not pfree'd tts_mintuple, if there is one.)
-	 */
-	slot->tts_nvalid = 0;
-
-	/*
-	 * On the same principle of not depending on previous remote storage,
-	 * forget the mintuple if it's not local storage.  (If it is local
-	 * storage, we must not pfree it now, since callers might have already
-	 * fetched datum pointers referencing it.)
-	 */
-	if (!TTS_SHOULDFREEMIN(slot))
-		slot->tts_mintuple = NULL;
+		ExecClearTuple(slot);
+		slot->tts_flags |= TTS_FLAG_SHOULDFREE;
+		slot->tts_flags &= ~TTS_FLAG_EMPTY;
+		oldContext = MemoryContextSwitchTo(slot->tts_mcxt);
+		bslot->base.tuple = heap_copytuple(tuple);
+		MemoryContextSwitchTo(oldContext);
+	}
+	else
+	{
+		ExecClearTuple(slot);
+		heap_deform_tuple(tuple, slot->tts_tupleDescriptor,
+						  slot->tts_values, slot->tts_isnull);
+		ExecStoreVirtualTuple(slot);
+	}
 }
 
-/* --------------------------------
- *		ExecCopySlot
- *			Copy the source slot's contents into the destination slot.
- *
- *		The destination acquires a private copy that will not go away
- *		if the source is cleared.
- *
- *		The caller must ensure the slots have compatible tupdescs.
- * --------------------------------
- */
-TupleTableSlot *
-ExecCopySlot(TupleTableSlot *dstslot, TupleTableSlot *srcslot)
+void
+ExecForceStoreMinimalTuple(MinimalTuple mtup,
+						   TupleTableSlot *slot,
+						   bool shouldFree)
 {
-	HeapTuple	newTuple;
-	MemoryContext oldContext;
+	if (TTS_IS_MINIMALTUPLE(slot))
+	{
+		tts_minimal_store_tuple(slot, mtup, shouldFree);
+	}
+	else
+	{
+		HeapTupleData htup;
 
-	/*
-	 * There might be ways to optimize this when the source is virtual, but
-	 * for now just always build a physical copy.  Make sure it is in the
-	 * right context.
-	 */
-	oldContext = MemoryContextSwitchTo(dstslot->tts_mcxt);
-	newTuple = ExecCopySlotTuple(srcslot);
-	MemoryContextSwitchTo(oldContext);
+		ExecClearTuple(slot);
 
-	return ExecStoreHeapTuple(newTuple, dstslot, true);
+		htup.t_len = mtup->t_len + MINIMAL_TUPLE_OFFSET;
+		htup.t_data = (HeapTupleHeader) ((char *) mtup - MINIMAL_TUPLE_OFFSET);
+		heap_deform_tuple(&htup, slot->tts_tupleDescriptor,
+						  slot->tts_values, slot->tts_isnull);
+		ExecStoreVirtualTuple(slot);
+	}
 }
 
-
 /* ----------------------------------------------------------------
  *				convenience initialization routines
  * ----------------------------------------------------------------
@@ -1062,7 +1803,6 @@ void
 slot_getmissingattrs(TupleTableSlot *slot, int startAttNum, int lastAttNum)
 {
 	AttrMissing *attrmiss = NULL;
-	int			missattnum;
 
 	if (slot->tts_tupleDescriptor->constr)
 		attrmiss = slot->tts_tupleDescriptor->constr->missing;
@@ -1077,6 +1817,8 @@ slot_getmissingattrs(TupleTableSlot *slot, int startAttNum, int lastAttNum)
 	}
 	else
 	{
+		int			missattnum;
+
 		/* if there is a missing values array we must process them one by one */
 		for (missattnum = startAttNum;
 			 missattnum < lastAttNum;
@@ -1085,143 +1827,36 @@ slot_getmissingattrs(TupleTableSlot *slot, int startAttNum, int lastAttNum)
 			slot->tts_values[missattnum] = attrmiss[missattnum].am_value;
 			slot->tts_isnull[missattnum] = !attrmiss[missattnum].am_present;
 		}
+
 	}
 }
 
 /*
- * slot_getattr
- *		This function fetches an attribute of the slot's current tuple.
- *		It is functionally equivalent to heap_getattr, but fetches of
- *		multiple attributes of the same tuple will be optimized better,
- *		because we avoid O(N^2) behavior from multiple calls of
- *		nocachegetattr(), even when attcacheoff isn't usable.
+ * slot_getsomeattrs_int
  *
- *		A difference from raw heap_getattr is that attnums beyond the
- *		slot's tupdesc's last attribute will be considered NULL even
- *		when the physical tuple is longer than the tupdesc.
- */
-Datum
-slot_getattr(TupleTableSlot *slot, int attnum, bool *isnull)
-{
-	HeapTuple	tuple = slot->tts_tuple;
-	TupleDesc	tupleDesc = slot->tts_tupleDescriptor;
-	HeapTupleHeader tup;
-
-	/*
-	 * system attributes are handled by heap_getsysattr
-	 */
-	if (attnum <= 0)
-	{
-		if (tuple == NULL)		/* internal error */
-			elog(ERROR, "cannot extract system attribute from virtual tuple");
-		if (tuple == &(slot->tts_minhdr))	/* internal error */
-			elog(ERROR, "cannot extract system attribute from minimal tuple");
-		return heap_getsysattr(tuple, attnum, tupleDesc, isnull);
-	}
-
-	/*
-	 * fast path if desired attribute already cached
-	 */
-	if (attnum <= slot->tts_nvalid)
-	{
-		*isnull = slot->tts_isnull[attnum - 1];
-		return slot->tts_values[attnum - 1];
-	}
-
-	/*
-	 * return NULL if attnum is out of range according to the tupdesc
-	 */
-	if (attnum > tupleDesc->natts)
-	{
-		*isnull = true;
-		return (Datum) 0;
-	}
-
-	/*
-	 * otherwise we had better have a physical tuple (tts_nvalid should equal
-	 * natts in all virtual-tuple cases)
-	 */
-	if (tuple == NULL)			/* internal error */
-		elog(ERROR, "cannot extract attribute from empty tuple slot");
-
-	/*
-	 * return NULL or missing value if attnum is out of range according to the
-	 * tuple
-	 *
-	 * (We have to check this separately because of various inheritance and
-	 * table-alteration scenarios: the tuple could be either longer or shorter
-	 * than the tupdesc.)
-	 */
-	tup = tuple->t_data;
-	if (attnum > HeapTupleHeaderGetNatts(tup))
-		return getmissingattr(slot->tts_tupleDescriptor, attnum, isnull);
-
-	/*
-	 * check if target attribute is null: no point in groveling through tuple
-	 */
-	if (HeapTupleHasNulls(tuple) && att_isnull(attnum - 1, tup->t_bits))
-	{
-		*isnull = true;
-		return (Datum) 0;
-	}
-
-	/*
-	 * Extract the attribute, along with any preceding attributes.
-	 */
-	slot_deform_tuple(slot, attnum);
-
-	/*
-	 * The result is acquired from tts_values array.
-	 */
-	*isnull = slot->tts_isnull[attnum - 1];
-	return slot->tts_values[attnum - 1];
-}
-
-/*
- * slot_getsomeattrs
- *		This function forces the entries of the slot's Datum/isnull
- *		arrays to be valid at least up through the attnum'th entry.
  */
 void
-slot_getsomeattrs(TupleTableSlot *slot, int attnum)
+slot_getsomeattrs_int(TupleTableSlot *slot, int attnum)
 {
-	HeapTuple	tuple;
-	int			attno;
+	/* Check for caller errors */
+	Assert(slot->tts_nvalid < attnum); /* slot_getsomeattr checked */
+	Assert(attnum > 0);
 
-	/* Quick out if we have 'em all already */
-	if (slot->tts_nvalid >= attnum)
-		return;
-
-	/* Check for caller error */
-	if (attnum <= 0 || attnum > slot->tts_tupleDescriptor->natts)
+	if (unlikely(attnum > slot->tts_tupleDescriptor->natts))
 		elog(ERROR, "invalid attribute number %d", attnum);
 
-	/*
-	 * otherwise we had better have a physical tuple (tts_nvalid should equal
-	 * natts in all virtual-tuple cases)
-	 */
-	tuple = slot->tts_tuple;
-	if (tuple == NULL)			/* internal error */
-		elog(ERROR, "cannot extract attribute from empty tuple slot");
+	/* Fetch as many attributes as possible from the underlying tuple. */
+	slot->tts_cb->getsomeattrs(slot, attnum);
 
 	/*
-	 * load up any slots available from physical tuple
+	 * If the underlying tuple doesn't have enough attributes, tuple descriptor
+	 * must have the missing attributes.
 	 */
-	attno = HeapTupleHeaderGetNatts(tuple->t_data);
-	attno = Min(attno, attnum);
-
-	slot_deform_tuple(slot, attno);
-
-	attno = slot->tts_nvalid;
-
-	/*
-	 * If tuple doesn't have all the atts indicated by attnum, read the rest
-	 * as NULLs or missing values
-	 */
-	if (attno < attnum)
-		slot_getmissingattrs(slot, attno, attnum);
-
-	slot->tts_nvalid = attnum;
+	if (unlikely(slot->tts_nvalid < attnum))
+	{
+		slot_getmissingattrs(slot, slot->tts_nvalid, attnum);
+		slot->tts_nvalid = attnum;
+	}
 }
 
 /* ----------------------------------------------------------------
diff --git a/src/backend/executor/nodeAgg.c b/src/backend/executor/nodeAgg.c
index 20d6d8e9cbb..2a1a6123d3b 100644
--- a/src/backend/executor/nodeAgg.c
+++ b/src/backend/executor/nodeAgg.c
@@ -1800,9 +1800,8 @@ agg_retrieve_direct(AggState *aggstate)
 				 * reserved for it.  The tuple will be deleted when it is
 				 * cleared from the slot.
 				 */
-				ExecStoreHeapTuple(aggstate->grp_firstTuple,
-								   firstSlot,
-								   true);
+				ExecForceStoreHeapTuple(aggstate->grp_firstTuple,
+								   firstSlot);
 				aggstate->grp_firstTuple = NULL;	/* don't keep two pointers */
 
 				/* set up for first advance_aggregates call */
diff --git a/src/backend/executor/nodeHashjoin.c b/src/backend/executor/nodeHashjoin.c
index d9afd0bdded..8fb477f45c3 100644
--- a/src/backend/executor/nodeHashjoin.c
+++ b/src/backend/executor/nodeHashjoin.c
@@ -929,9 +929,10 @@ ExecParallelHashJoinOuterGetTuple(PlanState *outerNode,
 									   hashvalue);
 		if (tuple != NULL)
 		{
-			slot = ExecStoreMinimalTuple(tuple,
-										 hjstate->hj_OuterTupleSlot,
-										 false);
+			ExecForceStoreMinimalTuple(tuple,
+									   hjstate->hj_OuterTupleSlot,
+									   false);
+			slot = hjstate->hj_OuterTupleSlot;
 			return slot;
 		}
 		else
@@ -1158,9 +1159,10 @@ ExecParallelHashJoinNewBatch(HashJoinState *hjstate)
 					while ((tuple = sts_parallel_scan_next(inner_tuples,
 														   &hashvalue)))
 					{
-						slot = ExecStoreMinimalTuple(tuple,
-													 hjstate->hj_HashTupleSlot,
-													 false);
+						ExecForceStoreMinimalTuple(tuple,
+												   hjstate->hj_HashTupleSlot,
+												   false);
+						slot = hjstate->hj_HashTupleSlot;
 						ExecParallelHashTableInsertCurrentBatch(hashtable, slot,
 																hashvalue);
 					}
@@ -1294,7 +1296,8 @@ ExecHashJoinGetSavedTuple(HashJoinState *hjstate,
 		ereport(ERROR,
 				(errcode_for_file_access(),
 				 errmsg("could not read from hash-join temporary file: %m")));
-	return ExecStoreMinimalTuple(tuple, tupleSlot, true);
+	ExecForceStoreMinimalTuple(tuple, tupleSlot, true);
+	return tupleSlot;
 }
 
 
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 9cc499d6533..d29162bab79 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -2059,6 +2059,15 @@ ExecModifyTable(PlanState *pstate)
 				break;
 		}
 
+		/*
+		 * Ensure input tuple is the right format for the target relation.
+		 */
+		if (node->mt_scans[node->mt_whichplan]->tts_cb != planSlot->tts_cb)
+		{
+			ExecCopySlot(node->mt_scans[node->mt_whichplan], planSlot);
+			planSlot = node->mt_scans[node->mt_whichplan];
+		}
+
 		/*
 		 * If resultRelInfo->ri_usesFdwDirectModify is true, all we need to do
 		 * here is compute the RETURNING expressions.
@@ -2242,6 +2251,7 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 
 	mtstate->mt_plans = (PlanState **) palloc0(sizeof(PlanState *) * nplans);
 	mtstate->resultRelInfo = estate->es_result_relations + node->resultRelIndex;
+	mtstate->mt_scans = (TupleTableSlot **) palloc0(sizeof(TupleTableSlot *) * nplans);
 
 	/* If modifying a partitioned table, initialize the root table info */
 	if (node->rootResultRelIndex >= 0)
@@ -2308,6 +2318,9 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 		/* Now init the plan for this result rel */
 		estate->es_result_relation_info = resultRelInfo;
 		mtstate->mt_plans[i] = ExecInitNode(subplan, estate, eflags);
+		mtstate->mt_scans[i] =
+			ExecInitExtraTupleSlot(mtstate->ps.state, ExecGetResultType(mtstate->mt_plans[i]),
+								   &TTSOpsHeapTuple);
 
 		/* Also let FDWs init themselves for foreign-table result rels */
 		if (!resultRelInfo->ri_usesFdwDirectModify &&
diff --git a/src/backend/executor/nodeSubplan.c b/src/backend/executor/nodeSubplan.c
index 62811ed0302..b42e60576e3 100644
--- a/src/backend/executor/nodeSubplan.c
+++ b/src/backend/executor/nodeSubplan.c
@@ -988,6 +988,7 @@ ExecInitSubPlan(SubPlan *subplan, PlanState *parent)
 		 * across-type comparison).
 		 */
 		sstate->cur_eq_comp = ExecBuildGroupingEqual(tupDescLeft, tupDescRight,
+													 &TTSOpsVirtual, &TTSOpsMinimalTuple,
 													 ncols,
 													 sstate->keyColIdx,
 													 sstate->tab_eq_funcoids,
diff --git a/src/backend/jit/llvm/llvmjit.c b/src/backend/jit/llvm/llvmjit.c
index 168072afd2f..162d1be89bd 100644
--- a/src/backend/jit/llvm/llvmjit.c
+++ b/src/backend/jit/llvm/llvmjit.c
@@ -65,6 +65,8 @@ LLVMTypeRef StructFormPgAttribute;
 LLVMTypeRef StructTupleConstr;
 LLVMTypeRef StructtupleDesc;
 LLVMTypeRef StructTupleTableSlot;
+LLVMTypeRef StructHeapTupleTableSlot;
+LLVMTypeRef StructMinimalTupleTableSlot;
 LLVMTypeRef StructMemoryContextData;
 LLVMTypeRef StructPGFinfoRecord;
 LLVMTypeRef StructFmgrInfo;
@@ -79,7 +81,7 @@ LLVMTypeRef StructAggStatePerTransData;
 LLVMValueRef AttributeTemplate;
 LLVMValueRef FuncStrlen;
 LLVMValueRef FuncVarsizeAny;
-LLVMValueRef FuncSlotGetsomeattrs;
+LLVMValueRef FuncSlotGetsomeattrsInt;
 LLVMValueRef FuncSlotGetmissingattrs;
 LLVMValueRef FuncMakeExpandedObjectReadOnlyInternal;
 LLVMValueRef FuncExecEvalArrayRefSubscript;
@@ -811,6 +813,8 @@ llvm_create_types(void)
 	StructFunctionCallInfoData = load_type(mod, "StructFunctionCallInfoData");
 	StructMemoryContextData = load_type(mod, "StructMemoryContextData");
 	StructTupleTableSlot = load_type(mod, "StructTupleTableSlot");
+	StructHeapTupleTableSlot = load_type(mod, "StructHeapTupleTableSlot");
+	StructMinimalTupleTableSlot = load_type(mod, "StructMinimalTupleTableSlot");
 	StructHeapTupleData = load_type(mod, "StructHeapTupleData");
 	StructtupleDesc = load_type(mod, "StructtupleDesc");
 	StructAggState = load_type(mod, "StructAggState");
@@ -820,7 +824,7 @@ llvm_create_types(void)
 	AttributeTemplate = LLVMGetNamedFunction(mod, "AttributeTemplate");
 	FuncStrlen = LLVMGetNamedFunction(mod, "strlen");
 	FuncVarsizeAny = LLVMGetNamedFunction(mod, "varsize_any");
-	FuncSlotGetsomeattrs = LLVMGetNamedFunction(mod, "slot_getsomeattrs");
+	FuncSlotGetsomeattrsInt = LLVMGetNamedFunction(mod, "slot_getsomeattrs_int");
 	FuncSlotGetmissingattrs = LLVMGetNamedFunction(mod, "slot_getmissingattrs");
 	FuncMakeExpandedObjectReadOnlyInternal = LLVMGetNamedFunction(mod, "MakeExpandedObjectReadOnlyInternal");
 	FuncExecEvalArrayRefSubscript = LLVMGetNamedFunction(mod, "ExecEvalArrayRefSubscript");
diff --git a/src/backend/jit/llvm/llvmjit_deform.c b/src/backend/jit/llvm/llvmjit_deform.c
index 59e38d2d955..e430cd9eda4 100644
--- a/src/backend/jit/llvm/llvmjit_deform.c
+++ b/src/backend/jit/llvm/llvmjit_deform.c
@@ -31,7 +31,7 @@
  * Create a function that deforms a tuple of type desc up to natts columns.
  */
 LLVMValueRef
-slot_compile_deform(LLVMJitContext *context, TupleDesc desc, int natts)
+slot_compile_deform(LLVMJitContext *context, TupleDesc desc, const TupleTableSlotOps *ops, int natts)
 {
 	char	   *funcname;
 
@@ -88,6 +88,16 @@ slot_compile_deform(LLVMJitContext *context, TupleDesc desc, int natts)
 
 	int			attnum;
 
+	if (ops != &TTSOpsHeapTuple && ops != &TTSOpsBufferTuple &&
+		ops != &TTSOpsMinimalTuple)
+	{
+		/*
+		 * Decline to JIT for slot types we don't know to handle, or don't
+		 * want to handle (say virtual slots).
+		 */
+		return NULL;
+	}
+
 	mod = llvm_mutable_module(context);
 
 	funcname = llvm_expand_funcname(context, "deform");
@@ -166,14 +176,44 @@ slot_compile_deform(LLVMJitContext *context, TupleDesc desc, int natts)
 	v_tts_nulls =
 		l_load_struct_gep(b, v_slot, FIELDNO_TUPLETABLESLOT_ISNULL,
 						  "tts_ISNULL");
-
-	v_slotoffp = LLVMBuildStructGEP(b, v_slot, FIELDNO_TUPLETABLESLOT_OFF, "");
 	v_flagsp = LLVMBuildStructGEP(b, v_slot, FIELDNO_TUPLETABLESLOT_FLAGS, "");
 	v_nvalidp = LLVMBuildStructGEP(b, v_slot, FIELDNO_TUPLETABLESLOT_NVALID, "");
 
-	v_tupleheaderp =
-		l_load_struct_gep(b, v_slot, FIELDNO_TUPLETABLESLOT_TUPLE,
-						  "tupleheader");
+	if (ops == &TTSOpsHeapTuple || ops == &TTSOpsBufferTuple)
+	{
+		LLVMValueRef v_heapslot;
+
+		v_heapslot =
+			LLVMBuildBitCast(b,
+							 v_slot,
+							 l_ptr(StructHeapTupleTableSlot),
+							 "heapslot");
+		v_slotoffp = LLVMBuildStructGEP(b, v_heapslot, FIELDNO_HEAPTUPLETABLESLOT_OFF, "");
+		v_tupleheaderp =
+			l_load_struct_gep(b, v_heapslot, FIELDNO_HEAPTUPLETABLESLOT_TUPLE,
+							  "tupleheader");
+
+	}
+	else if (ops == &TTSOpsMinimalTuple)
+	{
+		LLVMValueRef v_minimalslot;
+
+		v_minimalslot =
+			LLVMBuildBitCast(b,
+							 v_slot,
+							 l_ptr(StructMinimalTupleTableSlot),
+							 "minimalslotslot");
+		v_slotoffp = LLVMBuildStructGEP(b, v_minimalslot, FIELDNO_MINIMALTUPLETABLESLOT_OFF, "");
+		v_tupleheaderp =
+			l_load_struct_gep(b, v_minimalslot, FIELDNO_MINIMALTUPLETABLESLOT_TUPLE,
+							  "tupleheader");
+	}
+	else
+	{
+		/* should've returned at the start of the function */
+		pg_unreachable();
+	}
+
 	v_tuplep =
 		l_load_struct_gep(b, v_tupleheaderp, FIELDNO_HEAPTUPLEDATA_DATA,
 						  "tuple");
diff --git a/src/backend/jit/llvm/llvmjit_expr.c b/src/backend/jit/llvm/llvmjit_expr.c
index 63b921517c6..f3414ef9caf 100644
--- a/src/backend/jit/llvm/llvmjit_expr.c
+++ b/src/backend/jit/llvm/llvmjit_expr.c
@@ -324,6 +324,7 @@ llvm_compile_expr(ExprState *state)
 					{
 						l_jit_deform =
 							slot_compile_deform(context, desc,
+												tts_cb,
 												op->d.fetch.last_var);
 					}
 
@@ -344,7 +345,7 @@ llvm_compile_expr(ExprState *state)
 						params[1] = l_int32_const(op->d.fetch.last_var);
 
 						LLVMBuildCall(b,
-									  llvm_get_decl(mod, FuncSlotGetsomeattrs),
+									  llvm_get_decl(mod, FuncSlotGetsomeattrsInt),
 									  params, lengthof(params), "");
 					}
 
diff --git a/src/backend/jit/llvm/llvmjit_types.c b/src/backend/jit/llvm/llvmjit_types.c
index 855a6977ee5..2df1882b750 100644
--- a/src/backend/jit/llvm/llvmjit_types.c
+++ b/src/backend/jit/llvm/llvmjit_types.c
@@ -59,6 +59,8 @@ FunctionCallInfoData StructFunctionCallInfoData;
 HeapTupleData StructHeapTupleData;
 MemoryContextData StructMemoryContextData;
 TupleTableSlot StructTupleTableSlot;
+HeapTupleTableSlot StructHeapTupleTableSlot;
+MinimalTupleTableSlot StructMinimalTupleTableSlot;
 struct tupleDesc StructtupleDesc;
 
 
@@ -97,7 +99,7 @@ void	   *referenced_functions[] =
 {
 	strlen,
 	varsize_any,
-	slot_getsomeattrs,
+	slot_getsomeattrs_int,
 	slot_getmissingattrs,
 	MakeExpandedObjectReadOnlyInternal,
 	ExecEvalArrayRefSubscript,
diff --git a/src/include/access/htup_details.h b/src/include/access/htup_details.h
index 97d240fdbb0..1867a70f6f3 100644
--- a/src/include/access/htup_details.h
+++ b/src/include/access/htup_details.h
@@ -835,7 +835,5 @@ extern MinimalTuple minimal_tuple_from_heap_tuple(HeapTuple htup);
 extern size_t varsize_any(void *p);
 extern HeapTuple heap_expand_tuple(HeapTuple sourceTuple, TupleDesc tupleDesc);
 extern MinimalTuple minimal_expand_tuple(HeapTuple sourceTuple, TupleDesc tupleDesc);
-struct TupleTableSlot;
-extern void slot_deform_tuple(struct TupleTableSlot *slot, int natts);
 
 #endif							/* HTUP_DETAILS_H */
diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h
index 4f156f4a5e7..8b48a23d13e 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -249,6 +249,7 @@ extern List *ExecInitExprList(List *nodes, PlanState *parent);
 extern ExprState *ExecBuildAggTrans(AggState *aggstate, struct AggStatePerPhaseData *phase,
 				  bool doSort, bool doHash);
 extern ExprState *ExecBuildGroupingEqual(TupleDesc ldesc, TupleDesc rdesc,
+					   const TupleTableSlotOps *lops, const TupleTableSlotOps *rops,
 					   int numCols,
 					   AttrNumber *keyColIdx,
 					   Oid *eqfunctions,
diff --git a/src/include/executor/tuptable.h b/src/include/executor/tuptable.h
index a6f6084f0a9..f78853488a9 100644
--- a/src/include/executor/tuptable.h
+++ b/src/include/executor/tuptable.h
@@ -65,7 +65,7 @@
  * ie, only as needed.  This serves to avoid repeated extraction of data
  * from the physical tuple.
  *
- * A TupleTableSlot can also be "empty", indicated by flag TTS_EMPTY set in
+ * A TupleTableSlot can also be "empty", indicated by flag TTS_FLAG_EMPTY set in
  * tts_flags, holding no valid data.  This is the only valid state for a
  * freshly-created slot that has not yet had a tuple descriptor assigned to it.
  * In this state, TTS_SHOULDFREE should not be set in tts_flag, tts_tuple must
@@ -116,25 +116,22 @@
 #define			TTS_FLAG_EMPTY			(1 << 1)
 #define TTS_EMPTY(slot)	(((slot)->tts_flags & TTS_FLAG_EMPTY) != 0)
 
-/* should pfree tts_tuple? */
+/* should pfree tuple "owned" by the slot? */
 #define			TTS_FLAG_SHOULDFREE		(1 << 2)
 #define TTS_SHOULDFREE(slot) (((slot)->tts_flags & TTS_FLAG_SHOULDFREE) != 0)
 
-/* should pfree tts_mintuple? */
-#define			TTS_FLAG_SHOULDFREEMIN	(1 << 3)
-#define TTS_SHOULDFREEMIN(slot) (((slot)->tts_flags & TTS_FLAG_SHOULDFREEMIN) != 0)
-
 /* saved state for slot_deform_tuple */
-#define			TTS_FLAG_SLOW		(1 << 4)
+#define			TTS_FLAG_SLOW		(1 << 3)
 #define TTS_SLOW(slot) (((slot)->tts_flags & TTS_FLAG_SLOW) != 0)
 
 /* fixed tuple descriptor */
-#define			TTS_FLAG_FIXED		(1 << 5)
+#define			TTS_FLAG_FIXED		(1 << 4)
 #define TTS_FIXED(slot) (((slot)->tts_flags & TTS_FLAG_FIXED) != 0)
 
 struct TupleTableSlotOps;
 typedef struct TupleTableSlotOps TupleTableSlotOps;
 
+/* virtual or base type */
 typedef struct TupleTableSlot
 {
 	NodeTag		type;
@@ -142,26 +139,98 @@ typedef struct TupleTableSlot
 	uint16		tts_flags;		/* Boolean states */
 #define FIELDNO_TUPLETABLESLOT_NVALID 2
 	AttrNumber	tts_nvalid;		/* # of valid values in tts_values */
-#define FIELDNO_TUPLETABLESLOT_TUPLE 3
-	HeapTuple	tts_tuple;		/* physical tuple, or NULL if virtual */
 	const TupleTableSlotOps *const tts_cb; /* implementation of slot */
-#define FIELDNO_TUPLETABLESLOT_TUPLEDESCRIPTOR 5
+#define FIELDNO_TUPLETABLESLOT_TUPLEDESCRIPTOR 4
 	TupleDesc	tts_tupleDescriptor;	/* slot's tuple descriptor */
-	MemoryContext tts_mcxt;		/* slot itself is in this context */
-	Buffer		tts_buffer;		/* tuple's buffer, or InvalidBuffer */
-#define FIELDNO_TUPLETABLESLOT_OFF 8
-	uint32		tts_off;		/* saved state for slot_deform_tuple */
-#define FIELDNO_TUPLETABLESLOT_VALUES 9
+#define FIELDNO_TUPLETABLESLOT_VALUES 5
 	Datum	   *tts_values;		/* current per-attribute values */
-#define FIELDNO_TUPLETABLESLOT_ISNULL 10
+#define FIELDNO_TUPLETABLESLOT_ISNULL 6
 	bool	   *tts_isnull;		/* current per-attribute isnull flags */
-	MinimalTuple tts_mintuple;	/* minimal tuple, or NULL if none */
-	HeapTupleData tts_minhdr;	/* workspace for minimal-tuple-only case */
+	MemoryContext tts_mcxt;		/* slot itself is in this context */
 } TupleTableSlot;
 
 /* routines for a TupleTableSlot implementation */
 struct TupleTableSlotOps
 {
+	/* Minimum size of the slot */
+	size_t			base_slot_size;
+
+	/* Initialization. */
+	void (*init)(TupleTableSlot *slot);
+
+	/* Destruction. */
+	void (*release)(TupleTableSlot *slot);
+
+	/*
+	 * Clear the contents of the slot. Only the contents are expected to be
+	 * cleared and not the tuple descriptor. Typically an implementation of
+	 * this callback should free the memory allocated for the tuple contained
+	 * in the slot.
+	 */
+	void (*clear)(TupleTableSlot *slot);
+
+	/*
+	 * Fill up first natts entries of tts_values and tts_isnull arrays with
+	 * values from the tuple contained in the slot. The function may be called
+	 * with natts more than the number of attributes available in the tuple,
+	 * in which case it should set tts_nvalid to the number of returned
+	 * columns.
+	 */
+	void (*getsomeattrs)(TupleTableSlot *slot, int natts);
+
+	/*
+	 * Returns value of the given system attribute as a datum and sets isnull
+	 * to false, if it's not NULL. Throws an error if the slot type does not
+	 * support system attributes.
+	 */
+	Datum (*getsysattr)(TupleTableSlot *slot, int attnum, bool *isnull);
+
+	/*
+	 * Make the contents of the slot solely depend on the slot, and not on
+	 * underlying resources (like another memory context, buffers, etc).
+	 */
+	void (*materialize)(TupleTableSlot *slot);
+
+	/*
+	 * Copy the contents of the source slot into the destination slot's own
+	 * context. Invoked using callback of the destination slot.
+	 */
+	void (*copyslot) (TupleTableSlot *dstslot, TupleTableSlot *srcslot);
+
+	/*
+	 * Return a heap tuple "owned" by the slot. It is slot's responsibility to
+	 * free the memory consumed by the heap tuple. If the slot can not "own" a
+	 * heap tuple, it should not implement this callback and should set it as
+	 * NULL.
+	 */
+	HeapTuple (*get_heap_tuple)(TupleTableSlot *slot);
+
+	/*
+	 * Return a minimal tuple "owned" by the slot. It is slot's responsibility
+	 * to free the memory consumed by the minimal tuple. If the slot can not
+	 * "own" a minimal tuple, it should not implement this callback and should
+	 * set it as NULL.
+	 */
+	MinimalTuple (*get_minimal_tuple)(TupleTableSlot *slot);
+
+	/*
+	 * Return a copy of heap tuple representing the contents of the slot. The
+	 * returned heap tuple should be writable. The copy should be palloc'd in
+	 * the current memory context. The slot itself is expected to remain
+	 * undisturbed. It is *not* expected to have meaningful "system columns"
+	 * in the copy. The copy is not be "owned" by the slot i.e. the caller has
+	 * to take responsibilty to free memory consumed by the slot.
+	 */
+	HeapTuple (*copy_heap_tuple)(TupleTableSlot *slot);
+
+	/*
+	 * Return a copy of minimal tuple representing the contents of the slot.
+	 * The returned minimal tuple should be writable. The copy should be
+	 * palloc'd in the current memory context. The slot itself is expected to
+	 * remain undisturbed. The copy is not be "owned" by the slot i.e. the
+	 * caller has to take responsibilty to free memory consumed by the slot.
+	 */
+	MinimalTuple (*copy_minimal_tuple)(TupleTableSlot *slot);
 };
 
 /*
@@ -173,8 +242,44 @@ extern PGDLLIMPORT const TupleTableSlotOps TTSOpsHeapTuple;
 extern PGDLLIMPORT const TupleTableSlotOps TTSOpsMinimalTuple;
 extern PGDLLIMPORT const TupleTableSlotOps TTSOpsBufferTuple;
 
-#define TTS_HAS_PHYSICAL_TUPLE(slot)  \
-	((slot)->tts_tuple != NULL && (slot)->tts_tuple != &((slot)->tts_minhdr))
+#define TTS_IS_VIRTUAL(slot) ((slot)->tts_cb == &TTSOpsVirtual)
+#define TTS_IS_HEAPTUPLE(slot) ((slot)->tts_cb == &TTSOpsHeapTuple)
+#define TTS_IS_MINIMALTUPLE(slot) ((slot)->tts_cb == &TTSOpsMinimalTuple)
+#define TTS_IS_BUFFERTUPLE(slot) ((slot)->tts_cb == &TTSOpsBufferTuple)
+
+
+typedef struct VirtualTupleTableSlot
+{
+	TupleTableSlot base;
+	char	   *data;		/* data for materialized slots */
+} VirtualTupleTableSlot;
+
+typedef struct HeapTupleTableSlot
+{
+	TupleTableSlot base;
+#define FIELDNO_HEAPTUPLETABLESLOT_TUPLE 1
+	HeapTuple	tuple;		/* physical tuple */
+#define FIELDNO_HEAPTUPLETABLESLOT_OFF 2
+	uint32		off;		/* saved state for slot_deform_tuple */
+} HeapTupleTableSlot;
+
+/* heap tuple residing in a buffer */
+typedef struct BufferHeapTupleTableSlot
+{
+	HeapTupleTableSlot base;
+	Buffer		buffer;		/* tuple's buffer, or InvalidBuffer */
+} BufferHeapTupleTableSlot;
+
+typedef struct MinimalTupleTableSlot
+{
+	TupleTableSlot base;
+#define FIELDNO_MINIMALTUPLETABLESLOT_TUPLE 1
+	HeapTuple	tuple;		/* tuple wrapper */
+	MinimalTuple mintuple;	/* minimal tuple, or NULL if none */
+	HeapTupleData minhdr;	/* workspace for minimal-tuple-only case */
+#define FIELDNO_MINIMALTUPLETABLESLOT_OFF 4
+	uint32		off;		/* saved state for slot_deform_tuple */
+} MinimalTupleTableSlot;
 
 /*
  * TupIsNull -- is a TupleTableSlot empty?
@@ -195,39 +300,38 @@ extern void ExecSetSlotDescriptor(TupleTableSlot *slot, TupleDesc tupdesc);
 extern TupleTableSlot *ExecStoreHeapTuple(HeapTuple tuple,
 				   TupleTableSlot *slot,
 				   bool shouldFree);
+extern void ExecForceStoreHeapTuple(HeapTuple tuple, TupleTableSlot *slot);
 extern TupleTableSlot *ExecStoreBufferHeapTuple(HeapTuple tuple,
 						 TupleTableSlot *slot,
 						 Buffer buffer);
 extern TupleTableSlot *ExecStoreMinimalTuple(MinimalTuple mtup,
 					  TupleTableSlot *slot,
 					  bool shouldFree);
-extern TupleTableSlot *ExecClearTuple(TupleTableSlot *slot);
+extern void ExecForceStoreMinimalTuple(MinimalTuple mtup, TupleTableSlot *slot,
+									   bool shouldFree);
 extern TupleTableSlot *ExecStoreVirtualTuple(TupleTableSlot *slot);
 extern TupleTableSlot *ExecStoreAllNullTuple(TupleTableSlot *slot);
-extern HeapTuple ExecCopySlotTuple(TupleTableSlot *slot);
-extern MinimalTuple ExecCopySlotMinimalTuple(TupleTableSlot *slot);
-extern HeapTuple ExecFetchSlotHeapTuple(TupleTableSlot *slot, bool materialize, bool *shoulFree);
+extern HeapTuple ExecFetchSlotHeapTuple(TupleTableSlot *slot, bool materialize, bool *shouldFree);
 extern MinimalTuple ExecFetchSlotMinimalTuple(TupleTableSlot *slot,
 						  bool *shouldFree);
 extern Datum ExecFetchSlotHeapTupleDatum(TupleTableSlot *slot);
-extern void ExecMaterializeSlot(TupleTableSlot *slot);
-extern TupleTableSlot *ExecCopySlot(TupleTableSlot *dstslot,
-			 TupleTableSlot *srcslot);
 extern void slot_getmissingattrs(TupleTableSlot *slot, int startAttNum,
 					 int lastAttNum);
-extern Datum slot_getattr(TupleTableSlot *slot, int attnum,
-			 bool *isnull);
-extern void slot_getsomeattrs(TupleTableSlot *slot, int attnum);
-
-/* in access/common/heaptuple.c */
-extern bool slot_attisnull(TupleTableSlot *slot, int attnum);
-extern bool slot_getsysattr(TupleTableSlot *slot, int attnum,
-				Datum *value, bool *isnull);
-extern Datum getmissingattr(TupleDesc tupleDesc,
-			   int attnum, bool *isnull);
+extern void slot_getsomeattrs_int(TupleTableSlot *slot, int attnum);
 
 #ifndef FRONTEND
 
+/*
+ * This function forces the entries of the slot's Datum/isnull arrays to be
+ * valid at least up through the attnum'th entry.
+ */
+static inline void
+slot_getsomeattrs(TupleTableSlot *slot, int attnum)
+{
+	if (slot->tts_nvalid < attnum)
+		slot_getsomeattrs_int(slot, attnum);
+}
+
 /*
  * slot_getallattrs
  *		This function forces all the entries of the slot's Datum/isnull
@@ -240,6 +344,129 @@ slot_getallattrs(TupleTableSlot *slot)
 	slot_getsomeattrs(slot, slot->tts_tupleDescriptor->natts);
 }
 
-#endif
+
+/*
+ * slot_attisnull
+ *
+ * Detect whether an attribute of the slot is null, without actually fetching
+ * it.
+ */
+static inline bool
+slot_attisnull(TupleTableSlot *slot, int attnum)
+{
+	AssertArg(attnum > 0);
+
+	if (attnum > slot->tts_nvalid)
+		slot_getsomeattrs(slot, attnum);
+
+	return slot->tts_isnull[attnum - 1];
+}
+
+static inline Datum
+slot_getattr(TupleTableSlot *slot, int attnum,
+			 bool *isnull)
+{
+	AssertArg(attnum > 0);
+
+	if (attnum > slot->tts_nvalid)
+		slot_getsomeattrs(slot, attnum);
+
+	*isnull = slot->tts_isnull[attnum - 1];
+
+	return slot->tts_values[attnum - 1];
+}
+
+/*
+ * slot_getsysattr
+ *		This function fetches a system attribute of the slot's current tuple.
+ *		If the slot type does not contain system attributes, this will throw
+ *		an error.  Hence before calling this function, callers should make sure
+ *		that the slot type is the one that supports system attributes, namely,
+ *		heap tuple slot and buffer tuple slot.
+ */
+static inline Datum
+slot_getsysattr(TupleTableSlot *slot, int attnum, bool *isnull)
+{
+	AssertArg(attnum < 0);		/* caller error */
+
+	/* Fetch the system attribute from the underlying tuple. */
+	return slot->tts_cb->getsysattr(slot, attnum, isnull);
+}
+
+/*
+ * ExecClearTuple
+ *
+ * A thin wrapper around calling TupleTableSlotType specific clear() method.
+ */
+static inline TupleTableSlot *
+ExecClearTuple(TupleTableSlot *slot)
+{
+	slot->tts_cb->clear(slot);
+
+	return slot;
+}
+
+/* ExecMaterializeSlot - force a slot into the "materialized" state.
+ *
+ * This causes the slot's tuple to be a local copy not dependent on any
+ * external storage (i.e. pointing into a Buffer, or having allocations in
+ * another memory context).
+ *
+ * A typical use for this operation is to prepare a computed tuple for being
+ * stored on disk.  The original data may or may not be virtual, but in any
+ * case we need a private copy for heap_insert to scribble on.
+ */
+static inline void
+ExecMaterializeSlot(TupleTableSlot *slot)
+{
+	slot->tts_cb->materialize(slot);
+}
+
+/*
+ * ExecCopySlotTuple
+ *
+ * A thin wrapper calling TupleTableSlotType specific copy_heap_tuple()
+ * method.
+ */
+static inline HeapTuple
+ExecCopySlotTuple(TupleTableSlot *slot)
+{
+	/*
+	 * sanity checks
+	 */
+	Assert(slot != NULL);
+	Assert(!TTS_EMPTY(slot));
+
+	return slot->tts_cb->copy_heap_tuple(slot);
+}
+
+/*
+ * ExecCopySlotMinimalTuple
+ *
+ * A thin wrapper around calling TupleTableSlotType specific
+ * copy_minimal_tuple() method.
+ */
+static inline MinimalTuple
+ExecCopySlotMinimalTuple(TupleTableSlot *slot)
+{
+	return slot->tts_cb->copy_minimal_tuple(slot);
+}
+
+/*
+ * ExecCopySlot
+ *
+ * A thin wrapper calling TupleTableSlotType specific copyslot callback.
+ *
+ * The caller must ensure the slots have compatible tupdescs.
+ */
+static inline TupleTableSlot *
+ExecCopySlot(TupleTableSlot *dstslot, TupleTableSlot *srcslot)
+{
+	dstslot->tts_cb->copyslot(dstslot, srcslot);
+
+	return dstslot;
+}
+
+#endif							/* FRONTEND */
 
 #endif							/* TUPTABLE_H */
diff --git a/src/include/jit/llvmjit.h b/src/include/jit/llvmjit.h
index f3ea2492835..b4b0a16d1ae 100644
--- a/src/include/jit/llvmjit.h
+++ b/src/include/jit/llvmjit.h
@@ -65,6 +65,8 @@ extern LLVMTypeRef TypeStorageBool;
 extern LLVMTypeRef StructtupleDesc;
 extern LLVMTypeRef StructHeapTupleData;
 extern LLVMTypeRef StructTupleTableSlot;
+extern LLVMTypeRef StructHeapTupleTableSlot;
+extern LLVMTypeRef StructMinimalTupleTableSlot;
 extern LLVMTypeRef StructMemoryContextData;
 extern LLVMTypeRef StructFunctionCallInfoData;
 extern LLVMTypeRef StructExprContext;
@@ -77,7 +79,7 @@ extern LLVMTypeRef StructAggStatePerGroupData;
 extern LLVMValueRef AttributeTemplate;
 extern LLVMValueRef FuncStrlen;
 extern LLVMValueRef FuncVarsizeAny;
-extern LLVMValueRef FuncSlotGetsomeattrs;
+extern LLVMValueRef FuncSlotGetsomeattrsInt;
 extern LLVMValueRef FuncSlotGetmissingattrs;
 extern LLVMValueRef FuncMakeExpandedObjectReadOnlyInternal;
 extern LLVMValueRef FuncExecEvalArrayRefSubscript;
@@ -111,7 +113,8 @@ extern void llvm_inline(LLVMModuleRef mod);
  ****************************************************************************
  */
 extern bool llvm_compile_expr(struct ExprState *state);
-extern LLVMValueRef slot_compile_deform(struct LLVMJitContext *context, TupleDesc desc, int natts);
+struct TupleTableSlotOps;
+extern LLVMValueRef slot_compile_deform(struct LLVMJitContext *context, TupleDesc desc, const struct TupleTableSlotOps *ops, int natts);
 
 /*
  ****************************************************************************
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 59fb3c15833..149916093a2 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -1100,6 +1100,7 @@ typedef struct ModifyTableState
 	PlanState **mt_plans;		/* subplans (one per target rel) */
 	int			mt_nplans;		/* number of plans in the array */
 	int			mt_whichplan;	/* which one is being executed (0..n-1) */
+	TupleTableSlot** mt_scans;	/* input tuple for underlying plan */
 	ResultRelInfo *resultRelInfo;	/* per-subplan target relations */
 	ResultRelInfo *rootResultRelInfo;	/* root target relation (partitioned
 										 * table root) */
-- 
2.18.0.rc2.dirty


--ll5lqoiis2grgqzk
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
 filename="v14-0007-Rationalize-expression-context-reset-in-ExecModi.patch"



^ permalink  raw  reply  [nested|flat] 114+ messages in thread

* [PATCH v2 1/4] Make stack depth check work with asan's use-after-return
@ 2026-02-07 19:04 Andres Freund <[email protected]>
  0 siblings, 0 replies; 114+ messages in thread

From: Andres Freund @ 2026-02-07 19:04 UTC (permalink / raw)

With address sanitizer's stack-use-after-return check, stack variables are
moved to heap allocations, to allow to detect references to the memory at a
later time. That broke our stack-depth check, which is why we had to disable
detect_stack_use_after_return in CI. Luckily __builtin_frame_address() works
correctly, even under asan, so use that.

Author:
Reviewed-by:
Discussion: https://postgr.es/m/
Backpatch:
---
 src/backend/utils/misc/stack_depth.c | 17 ++++++++++++++++-
 .cirrus.tasks.yml                    |  2 +-
 2 files changed, 17 insertions(+), 2 deletions(-)

diff --git a/src/backend/utils/misc/stack_depth.c b/src/backend/utils/misc/stack_depth.c
index 61a07cf824e..9955e28c176 100644
--- a/src/backend/utils/misc/stack_depth.c
+++ b/src/backend/utils/misc/stack_depth.c
@@ -108,13 +108,28 @@ check_stack_depth(void)
 bool
 stack_is_too_deep(void)
 {
+#ifndef HAVE__BUILTIN_FRAME_ADDRESS
 	char		stack_top_loc;
+#endif
 	ssize_t		stack_depth;
+	char	   *stack_address;
+
+	/*
+	 * With address sanitizer's stack-use-after-return check, stack variables
+	 * are moved to heap allocations, to allow to detect references to the
+	 * memory at a later time. That would break our stack-depth check. Luckily
+	 * __builtin_frame_address() works correctly, even under asan.
+	 */
+#ifndef HAVE__BUILTIN_FRAME_ADDRESS
+	stack_address = &stack_top_loc;
+#else
+	stack_address = (char *) __builtin_frame_address(0);
+#endif
 
 	/*
 	 * Compute distance from reference point to my local variables
 	 */
-	stack_depth = (ssize_t) (stack_base_ptr - &stack_top_loc);
+	stack_depth = (ssize_t) (stack_base_ptr - stack_address);
 
 	/*
 	 * Take abs value, since stacks grow up on some machines, down on others
diff --git a/.cirrus.tasks.yml b/.cirrus.tasks.yml
index a22cef063f3..8683d1ae9c7 100644
--- a/.cirrus.tasks.yml
+++ b/.cirrus.tasks.yml
@@ -447,7 +447,7 @@ task:
     # print_stacktraces=1,verbosity=2, duh
     # detect_leaks=0: too many uninteresting leak errors in short-lived binaries
     UBSAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:verbosity=2
-    ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0:detect_stack_use_after_return=0
+    ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0
 
     # SANITIZER_FLAGS is set in the tasks below
     CFLAGS: -Og -ggdb -fno-sanitize-recover=all $SANITIZER_FLAGS
-- 
2.53.0.1.gb2826b52eb


--7e2sypi5gxvkgog6--





^ permalink  raw  reply  [nested|flat] 114+ messages in thread

* [PATCH v2 1/4] Make stack depth check work with asan's use-after-return
@ 2026-02-07 19:04 Andres Freund <[email protected]>
  0 siblings, 0 replies; 114+ messages in thread

From: Andres Freund @ 2026-02-07 19:04 UTC (permalink / raw)

With address sanitizer's stack-use-after-return check, stack variables are
moved to heap allocations, to allow to detect references to the memory at a
later time. That broke our stack-depth check, which is why we had to disable
detect_stack_use_after_return in CI. Luckily __builtin_frame_address() works
correctly, even under asan, so use that.

Author:
Reviewed-by:
Discussion: https://postgr.es/m/
Backpatch:
---
 src/backend/utils/misc/stack_depth.c | 17 ++++++++++++++++-
 .cirrus.tasks.yml                    |  2 +-
 2 files changed, 17 insertions(+), 2 deletions(-)

diff --git a/src/backend/utils/misc/stack_depth.c b/src/backend/utils/misc/stack_depth.c
index 61a07cf824e..9955e28c176 100644
--- a/src/backend/utils/misc/stack_depth.c
+++ b/src/backend/utils/misc/stack_depth.c
@@ -108,13 +108,28 @@ check_stack_depth(void)
 bool
 stack_is_too_deep(void)
 {
+#ifndef HAVE__BUILTIN_FRAME_ADDRESS
 	char		stack_top_loc;
+#endif
 	ssize_t		stack_depth;
+	char	   *stack_address;
+
+	/*
+	 * With address sanitizer's stack-use-after-return check, stack variables
+	 * are moved to heap allocations, to allow to detect references to the
+	 * memory at a later time. That would break our stack-depth check. Luckily
+	 * __builtin_frame_address() works correctly, even under asan.
+	 */
+#ifndef HAVE__BUILTIN_FRAME_ADDRESS
+	stack_address = &stack_top_loc;
+#else
+	stack_address = (char *) __builtin_frame_address(0);
+#endif
 
 	/*
 	 * Compute distance from reference point to my local variables
 	 */
-	stack_depth = (ssize_t) (stack_base_ptr - &stack_top_loc);
+	stack_depth = (ssize_t) (stack_base_ptr - stack_address);
 
 	/*
 	 * Take abs value, since stacks grow up on some machines, down on others
diff --git a/.cirrus.tasks.yml b/.cirrus.tasks.yml
index a22cef063f3..8683d1ae9c7 100644
--- a/.cirrus.tasks.yml
+++ b/.cirrus.tasks.yml
@@ -447,7 +447,7 @@ task:
     # print_stacktraces=1,verbosity=2, duh
     # detect_leaks=0: too many uninteresting leak errors in short-lived binaries
     UBSAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:verbosity=2
-    ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0:detect_stack_use_after_return=0
+    ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0
 
     # SANITIZER_FLAGS is set in the tasks below
     CFLAGS: -Og -ggdb -fno-sanitize-recover=all $SANITIZER_FLAGS
-- 
2.53.0.1.gb2826b52eb


--7e2sypi5gxvkgog6--





^ permalink  raw  reply  [nested|flat] 114+ messages in thread

* [PATCH v2 1/4] Make stack depth check work with asan's use-after-return
@ 2026-02-07 19:04 Andres Freund <[email protected]>
  0 siblings, 0 replies; 114+ messages in thread

From: Andres Freund @ 2026-02-07 19:04 UTC (permalink / raw)

With address sanitizer's stack-use-after-return check, stack variables are
moved to heap allocations, to allow to detect references to the memory at a
later time. That broke our stack-depth check, which is why we had to disable
detect_stack_use_after_return in CI. Luckily __builtin_frame_address() works
correctly, even under asan, so use that.

Author:
Reviewed-by:
Discussion: https://postgr.es/m/
Backpatch:
---
 src/backend/utils/misc/stack_depth.c | 17 ++++++++++++++++-
 .cirrus.tasks.yml                    |  2 +-
 2 files changed, 17 insertions(+), 2 deletions(-)

diff --git a/src/backend/utils/misc/stack_depth.c b/src/backend/utils/misc/stack_depth.c
index 61a07cf824e..9955e28c176 100644
--- a/src/backend/utils/misc/stack_depth.c
+++ b/src/backend/utils/misc/stack_depth.c
@@ -108,13 +108,28 @@ check_stack_depth(void)
 bool
 stack_is_too_deep(void)
 {
+#ifndef HAVE__BUILTIN_FRAME_ADDRESS
 	char		stack_top_loc;
+#endif
 	ssize_t		stack_depth;
+	char	   *stack_address;
+
+	/*
+	 * With address sanitizer's stack-use-after-return check, stack variables
+	 * are moved to heap allocations, to allow to detect references to the
+	 * memory at a later time. That would break our stack-depth check. Luckily
+	 * __builtin_frame_address() works correctly, even under asan.
+	 */
+#ifndef HAVE__BUILTIN_FRAME_ADDRESS
+	stack_address = &stack_top_loc;
+#else
+	stack_address = (char *) __builtin_frame_address(0);
+#endif
 
 	/*
 	 * Compute distance from reference point to my local variables
 	 */
-	stack_depth = (ssize_t) (stack_base_ptr - &stack_top_loc);
+	stack_depth = (ssize_t) (stack_base_ptr - stack_address);
 
 	/*
 	 * Take abs value, since stacks grow up on some machines, down on others
diff --git a/.cirrus.tasks.yml b/.cirrus.tasks.yml
index a22cef063f3..8683d1ae9c7 100644
--- a/.cirrus.tasks.yml
+++ b/.cirrus.tasks.yml
@@ -447,7 +447,7 @@ task:
     # print_stacktraces=1,verbosity=2, duh
     # detect_leaks=0: too many uninteresting leak errors in short-lived binaries
     UBSAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:verbosity=2
-    ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0:detect_stack_use_after_return=0
+    ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0
 
     # SANITIZER_FLAGS is set in the tasks below
     CFLAGS: -Og -ggdb -fno-sanitize-recover=all $SANITIZER_FLAGS
-- 
2.53.0.1.gb2826b52eb


--7e2sypi5gxvkgog6--





^ permalink  raw  reply  [nested|flat] 114+ messages in thread

* [PATCH v2 1/4] Make stack depth check work with asan's use-after-return
@ 2026-02-07 19:04 Andres Freund <[email protected]>
  0 siblings, 0 replies; 114+ messages in thread

From: Andres Freund @ 2026-02-07 19:04 UTC (permalink / raw)

With address sanitizer's stack-use-after-return check, stack variables are
moved to heap allocations, to allow to detect references to the memory at a
later time. That broke our stack-depth check, which is why we had to disable
detect_stack_use_after_return in CI. Luckily __builtin_frame_address() works
correctly, even under asan, so use that.

Author:
Reviewed-by:
Discussion: https://postgr.es/m/
Backpatch:
---
 src/backend/utils/misc/stack_depth.c | 17 ++++++++++++++++-
 .cirrus.tasks.yml                    |  2 +-
 2 files changed, 17 insertions(+), 2 deletions(-)

diff --git a/src/backend/utils/misc/stack_depth.c b/src/backend/utils/misc/stack_depth.c
index 61a07cf824e..9955e28c176 100644
--- a/src/backend/utils/misc/stack_depth.c
+++ b/src/backend/utils/misc/stack_depth.c
@@ -108,13 +108,28 @@ check_stack_depth(void)
 bool
 stack_is_too_deep(void)
 {
+#ifndef HAVE__BUILTIN_FRAME_ADDRESS
 	char		stack_top_loc;
+#endif
 	ssize_t		stack_depth;
+	char	   *stack_address;
+
+	/*
+	 * With address sanitizer's stack-use-after-return check, stack variables
+	 * are moved to heap allocations, to allow to detect references to the
+	 * memory at a later time. That would break our stack-depth check. Luckily
+	 * __builtin_frame_address() works correctly, even under asan.
+	 */
+#ifndef HAVE__BUILTIN_FRAME_ADDRESS
+	stack_address = &stack_top_loc;
+#else
+	stack_address = (char *) __builtin_frame_address(0);
+#endif
 
 	/*
 	 * Compute distance from reference point to my local variables
 	 */
-	stack_depth = (ssize_t) (stack_base_ptr - &stack_top_loc);
+	stack_depth = (ssize_t) (stack_base_ptr - stack_address);
 
 	/*
 	 * Take abs value, since stacks grow up on some machines, down on others
diff --git a/.cirrus.tasks.yml b/.cirrus.tasks.yml
index a22cef063f3..8683d1ae9c7 100644
--- a/.cirrus.tasks.yml
+++ b/.cirrus.tasks.yml
@@ -447,7 +447,7 @@ task:
     # print_stacktraces=1,verbosity=2, duh
     # detect_leaks=0: too many uninteresting leak errors in short-lived binaries
     UBSAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:verbosity=2
-    ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0:detect_stack_use_after_return=0
+    ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0
 
     # SANITIZER_FLAGS is set in the tasks below
     CFLAGS: -Og -ggdb -fno-sanitize-recover=all $SANITIZER_FLAGS
-- 
2.53.0.1.gb2826b52eb


--7e2sypi5gxvkgog6--





^ permalink  raw  reply  [nested|flat] 114+ messages in thread

* [PATCH v2 1/4] Make stack depth check work with asan's use-after-return
@ 2026-02-07 19:04 Andres Freund <[email protected]>
  0 siblings, 0 replies; 114+ messages in thread

From: Andres Freund @ 2026-02-07 19:04 UTC (permalink / raw)

With address sanitizer's stack-use-after-return check, stack variables are
moved to heap allocations, to allow to detect references to the memory at a
later time. That broke our stack-depth check, which is why we had to disable
detect_stack_use_after_return in CI. Luckily __builtin_frame_address() works
correctly, even under asan, so use that.

Author:
Reviewed-by:
Discussion: https://postgr.es/m/
Backpatch:
---
 src/backend/utils/misc/stack_depth.c | 17 ++++++++++++++++-
 .cirrus.tasks.yml                    |  2 +-
 2 files changed, 17 insertions(+), 2 deletions(-)

diff --git a/src/backend/utils/misc/stack_depth.c b/src/backend/utils/misc/stack_depth.c
index 61a07cf824e..9955e28c176 100644
--- a/src/backend/utils/misc/stack_depth.c
+++ b/src/backend/utils/misc/stack_depth.c
@@ -108,13 +108,28 @@ check_stack_depth(void)
 bool
 stack_is_too_deep(void)
 {
+#ifndef HAVE__BUILTIN_FRAME_ADDRESS
 	char		stack_top_loc;
+#endif
 	ssize_t		stack_depth;
+	char	   *stack_address;
+
+	/*
+	 * With address sanitizer's stack-use-after-return check, stack variables
+	 * are moved to heap allocations, to allow to detect references to the
+	 * memory at a later time. That would break our stack-depth check. Luckily
+	 * __builtin_frame_address() works correctly, even under asan.
+	 */
+#ifndef HAVE__BUILTIN_FRAME_ADDRESS
+	stack_address = &stack_top_loc;
+#else
+	stack_address = (char *) __builtin_frame_address(0);
+#endif
 
 	/*
 	 * Compute distance from reference point to my local variables
 	 */
-	stack_depth = (ssize_t) (stack_base_ptr - &stack_top_loc);
+	stack_depth = (ssize_t) (stack_base_ptr - stack_address);
 
 	/*
 	 * Take abs value, since stacks grow up on some machines, down on others
diff --git a/.cirrus.tasks.yml b/.cirrus.tasks.yml
index a22cef063f3..8683d1ae9c7 100644
--- a/.cirrus.tasks.yml
+++ b/.cirrus.tasks.yml
@@ -447,7 +447,7 @@ task:
     # print_stacktraces=1,verbosity=2, duh
     # detect_leaks=0: too many uninteresting leak errors in short-lived binaries
     UBSAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:verbosity=2
-    ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0:detect_stack_use_after_return=0
+    ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0
 
     # SANITIZER_FLAGS is set in the tasks below
     CFLAGS: -Og -ggdb -fno-sanitize-recover=all $SANITIZER_FLAGS
-- 
2.53.0.1.gb2826b52eb


--7e2sypi5gxvkgog6--





^ permalink  raw  reply  [nested|flat] 114+ messages in thread

* [PATCH v2 1/4] Make stack depth check work with asan's use-after-return
@ 2026-02-07 19:04 Andres Freund <[email protected]>
  0 siblings, 0 replies; 114+ messages in thread

From: Andres Freund @ 2026-02-07 19:04 UTC (permalink / raw)

With address sanitizer's stack-use-after-return check, stack variables are
moved to heap allocations, to allow to detect references to the memory at a
later time. That broke our stack-depth check, which is why we had to disable
detect_stack_use_after_return in CI. Luckily __builtin_frame_address() works
correctly, even under asan, so use that.

Author:
Reviewed-by:
Discussion: https://postgr.es/m/
Backpatch:
---
 src/backend/utils/misc/stack_depth.c | 17 ++++++++++++++++-
 .cirrus.tasks.yml                    |  2 +-
 2 files changed, 17 insertions(+), 2 deletions(-)

diff --git a/src/backend/utils/misc/stack_depth.c b/src/backend/utils/misc/stack_depth.c
index 61a07cf824e..9955e28c176 100644
--- a/src/backend/utils/misc/stack_depth.c
+++ b/src/backend/utils/misc/stack_depth.c
@@ -108,13 +108,28 @@ check_stack_depth(void)
 bool
 stack_is_too_deep(void)
 {
+#ifndef HAVE__BUILTIN_FRAME_ADDRESS
 	char		stack_top_loc;
+#endif
 	ssize_t		stack_depth;
+	char	   *stack_address;
+
+	/*
+	 * With address sanitizer's stack-use-after-return check, stack variables
+	 * are moved to heap allocations, to allow to detect references to the
+	 * memory at a later time. That would break our stack-depth check. Luckily
+	 * __builtin_frame_address() works correctly, even under asan.
+	 */
+#ifndef HAVE__BUILTIN_FRAME_ADDRESS
+	stack_address = &stack_top_loc;
+#else
+	stack_address = (char *) __builtin_frame_address(0);
+#endif
 
 	/*
 	 * Compute distance from reference point to my local variables
 	 */
-	stack_depth = (ssize_t) (stack_base_ptr - &stack_top_loc);
+	stack_depth = (ssize_t) (stack_base_ptr - stack_address);
 
 	/*
 	 * Take abs value, since stacks grow up on some machines, down on others
diff --git a/.cirrus.tasks.yml b/.cirrus.tasks.yml
index a22cef063f3..8683d1ae9c7 100644
--- a/.cirrus.tasks.yml
+++ b/.cirrus.tasks.yml
@@ -447,7 +447,7 @@ task:
     # print_stacktraces=1,verbosity=2, duh
     # detect_leaks=0: too many uninteresting leak errors in short-lived binaries
     UBSAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:verbosity=2
-    ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0:detect_stack_use_after_return=0
+    ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0
 
     # SANITIZER_FLAGS is set in the tasks below
     CFLAGS: -Og -ggdb -fno-sanitize-recover=all $SANITIZER_FLAGS
-- 
2.53.0.1.gb2826b52eb


--7e2sypi5gxvkgog6--





^ permalink  raw  reply  [nested|flat] 114+ messages in thread

* [PATCH v2 1/4] Make stack depth check work with asan's use-after-return
@ 2026-02-07 19:04 Andres Freund <[email protected]>
  0 siblings, 0 replies; 114+ messages in thread

From: Andres Freund @ 2026-02-07 19:04 UTC (permalink / raw)

With address sanitizer's stack-use-after-return check, stack variables are
moved to heap allocations, to allow to detect references to the memory at a
later time. That broke our stack-depth check, which is why we had to disable
detect_stack_use_after_return in CI. Luckily __builtin_frame_address() works
correctly, even under asan, so use that.

Author:
Reviewed-by:
Discussion: https://postgr.es/m/
Backpatch:
---
 src/backend/utils/misc/stack_depth.c | 17 ++++++++++++++++-
 .cirrus.tasks.yml                    |  2 +-
 2 files changed, 17 insertions(+), 2 deletions(-)

diff --git a/src/backend/utils/misc/stack_depth.c b/src/backend/utils/misc/stack_depth.c
index 61a07cf824e..9955e28c176 100644
--- a/src/backend/utils/misc/stack_depth.c
+++ b/src/backend/utils/misc/stack_depth.c
@@ -108,13 +108,28 @@ check_stack_depth(void)
 bool
 stack_is_too_deep(void)
 {
+#ifndef HAVE__BUILTIN_FRAME_ADDRESS
 	char		stack_top_loc;
+#endif
 	ssize_t		stack_depth;
+	char	   *stack_address;
+
+	/*
+	 * With address sanitizer's stack-use-after-return check, stack variables
+	 * are moved to heap allocations, to allow to detect references to the
+	 * memory at a later time. That would break our stack-depth check. Luckily
+	 * __builtin_frame_address() works correctly, even under asan.
+	 */
+#ifndef HAVE__BUILTIN_FRAME_ADDRESS
+	stack_address = &stack_top_loc;
+#else
+	stack_address = (char *) __builtin_frame_address(0);
+#endif
 
 	/*
 	 * Compute distance from reference point to my local variables
 	 */
-	stack_depth = (ssize_t) (stack_base_ptr - &stack_top_loc);
+	stack_depth = (ssize_t) (stack_base_ptr - stack_address);
 
 	/*
 	 * Take abs value, since stacks grow up on some machines, down on others
diff --git a/.cirrus.tasks.yml b/.cirrus.tasks.yml
index a22cef063f3..8683d1ae9c7 100644
--- a/.cirrus.tasks.yml
+++ b/.cirrus.tasks.yml
@@ -447,7 +447,7 @@ task:
     # print_stacktraces=1,verbosity=2, duh
     # detect_leaks=0: too many uninteresting leak errors in short-lived binaries
     UBSAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:verbosity=2
-    ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0:detect_stack_use_after_return=0
+    ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0
 
     # SANITIZER_FLAGS is set in the tasks below
     CFLAGS: -Og -ggdb -fno-sanitize-recover=all $SANITIZER_FLAGS
-- 
2.53.0.1.gb2826b52eb


--7e2sypi5gxvkgog6--





^ permalink  raw  reply  [nested|flat] 114+ messages in thread

* [PATCH v2 1/4] Make stack depth check work with asan's use-after-return
@ 2026-02-07 19:04 Andres Freund <[email protected]>
  0 siblings, 0 replies; 114+ messages in thread

From: Andres Freund @ 2026-02-07 19:04 UTC (permalink / raw)

With address sanitizer's stack-use-after-return check, stack variables are
moved to heap allocations, to allow to detect references to the memory at a
later time. That broke our stack-depth check, which is why we had to disable
detect_stack_use_after_return in CI. Luckily __builtin_frame_address() works
correctly, even under asan, so use that.

Author:
Reviewed-by:
Discussion: https://postgr.es/m/
Backpatch:
---
 src/backend/utils/misc/stack_depth.c | 17 ++++++++++++++++-
 .cirrus.tasks.yml                    |  2 +-
 2 files changed, 17 insertions(+), 2 deletions(-)

diff --git a/src/backend/utils/misc/stack_depth.c b/src/backend/utils/misc/stack_depth.c
index 61a07cf824e..9955e28c176 100644
--- a/src/backend/utils/misc/stack_depth.c
+++ b/src/backend/utils/misc/stack_depth.c
@@ -108,13 +108,28 @@ check_stack_depth(void)
 bool
 stack_is_too_deep(void)
 {
+#ifndef HAVE__BUILTIN_FRAME_ADDRESS
 	char		stack_top_loc;
+#endif
 	ssize_t		stack_depth;
+	char	   *stack_address;
+
+	/*
+	 * With address sanitizer's stack-use-after-return check, stack variables
+	 * are moved to heap allocations, to allow to detect references to the
+	 * memory at a later time. That would break our stack-depth check. Luckily
+	 * __builtin_frame_address() works correctly, even under asan.
+	 */
+#ifndef HAVE__BUILTIN_FRAME_ADDRESS
+	stack_address = &stack_top_loc;
+#else
+	stack_address = (char *) __builtin_frame_address(0);
+#endif
 
 	/*
 	 * Compute distance from reference point to my local variables
 	 */
-	stack_depth = (ssize_t) (stack_base_ptr - &stack_top_loc);
+	stack_depth = (ssize_t) (stack_base_ptr - stack_address);
 
 	/*
 	 * Take abs value, since stacks grow up on some machines, down on others
diff --git a/.cirrus.tasks.yml b/.cirrus.tasks.yml
index a22cef063f3..8683d1ae9c7 100644
--- a/.cirrus.tasks.yml
+++ b/.cirrus.tasks.yml
@@ -447,7 +447,7 @@ task:
     # print_stacktraces=1,verbosity=2, duh
     # detect_leaks=0: too many uninteresting leak errors in short-lived binaries
     UBSAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:verbosity=2
-    ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0:detect_stack_use_after_return=0
+    ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0
 
     # SANITIZER_FLAGS is set in the tasks below
     CFLAGS: -Og -ggdb -fno-sanitize-recover=all $SANITIZER_FLAGS
-- 
2.53.0.1.gb2826b52eb


--7e2sypi5gxvkgog6--





^ permalink  raw  reply  [nested|flat] 114+ messages in thread

* [PATCH v2 1/4] Make stack depth check work with asan's use-after-return
@ 2026-02-07 19:04 Andres Freund <[email protected]>
  0 siblings, 0 replies; 114+ messages in thread

From: Andres Freund @ 2026-02-07 19:04 UTC (permalink / raw)

With address sanitizer's stack-use-after-return check, stack variables are
moved to heap allocations, to allow to detect references to the memory at a
later time. That broke our stack-depth check, which is why we had to disable
detect_stack_use_after_return in CI. Luckily __builtin_frame_address() works
correctly, even under asan, so use that.

Author:
Reviewed-by:
Discussion: https://postgr.es/m/
Backpatch:
---
 src/backend/utils/misc/stack_depth.c | 17 ++++++++++++++++-
 .cirrus.tasks.yml                    |  2 +-
 2 files changed, 17 insertions(+), 2 deletions(-)

diff --git a/src/backend/utils/misc/stack_depth.c b/src/backend/utils/misc/stack_depth.c
index 61a07cf824e..9955e28c176 100644
--- a/src/backend/utils/misc/stack_depth.c
+++ b/src/backend/utils/misc/stack_depth.c
@@ -108,13 +108,28 @@ check_stack_depth(void)
 bool
 stack_is_too_deep(void)
 {
+#ifndef HAVE__BUILTIN_FRAME_ADDRESS
 	char		stack_top_loc;
+#endif
 	ssize_t		stack_depth;
+	char	   *stack_address;
+
+	/*
+	 * With address sanitizer's stack-use-after-return check, stack variables
+	 * are moved to heap allocations, to allow to detect references to the
+	 * memory at a later time. That would break our stack-depth check. Luckily
+	 * __builtin_frame_address() works correctly, even under asan.
+	 */
+#ifndef HAVE__BUILTIN_FRAME_ADDRESS
+	stack_address = &stack_top_loc;
+#else
+	stack_address = (char *) __builtin_frame_address(0);
+#endif
 
 	/*
 	 * Compute distance from reference point to my local variables
 	 */
-	stack_depth = (ssize_t) (stack_base_ptr - &stack_top_loc);
+	stack_depth = (ssize_t) (stack_base_ptr - stack_address);
 
 	/*
 	 * Take abs value, since stacks grow up on some machines, down on others
diff --git a/.cirrus.tasks.yml b/.cirrus.tasks.yml
index a22cef063f3..8683d1ae9c7 100644
--- a/.cirrus.tasks.yml
+++ b/.cirrus.tasks.yml
@@ -447,7 +447,7 @@ task:
     # print_stacktraces=1,verbosity=2, duh
     # detect_leaks=0: too many uninteresting leak errors in short-lived binaries
     UBSAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:verbosity=2
-    ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0:detect_stack_use_after_return=0
+    ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0
 
     # SANITIZER_FLAGS is set in the tasks below
     CFLAGS: -Og -ggdb -fno-sanitize-recover=all $SANITIZER_FLAGS
-- 
2.53.0.1.gb2826b52eb


--7e2sypi5gxvkgog6--





^ permalink  raw  reply  [nested|flat] 114+ messages in thread

* [PATCH v2 1/4] Make stack depth check work with asan's use-after-return
@ 2026-02-07 19:04 Andres Freund <[email protected]>
  0 siblings, 0 replies; 114+ messages in thread

From: Andres Freund @ 2026-02-07 19:04 UTC (permalink / raw)

With address sanitizer's stack-use-after-return check, stack variables are
moved to heap allocations, to allow to detect references to the memory at a
later time. That broke our stack-depth check, which is why we had to disable
detect_stack_use_after_return in CI. Luckily __builtin_frame_address() works
correctly, even under asan, so use that.

Author:
Reviewed-by:
Discussion: https://postgr.es/m/
Backpatch:
---
 src/backend/utils/misc/stack_depth.c | 17 ++++++++++++++++-
 .cirrus.tasks.yml                    |  2 +-
 2 files changed, 17 insertions(+), 2 deletions(-)

diff --git a/src/backend/utils/misc/stack_depth.c b/src/backend/utils/misc/stack_depth.c
index 61a07cf824e..9955e28c176 100644
--- a/src/backend/utils/misc/stack_depth.c
+++ b/src/backend/utils/misc/stack_depth.c
@@ -108,13 +108,28 @@ check_stack_depth(void)
 bool
 stack_is_too_deep(void)
 {
+#ifndef HAVE__BUILTIN_FRAME_ADDRESS
 	char		stack_top_loc;
+#endif
 	ssize_t		stack_depth;
+	char	   *stack_address;
+
+	/*
+	 * With address sanitizer's stack-use-after-return check, stack variables
+	 * are moved to heap allocations, to allow to detect references to the
+	 * memory at a later time. That would break our stack-depth check. Luckily
+	 * __builtin_frame_address() works correctly, even under asan.
+	 */
+#ifndef HAVE__BUILTIN_FRAME_ADDRESS
+	stack_address = &stack_top_loc;
+#else
+	stack_address = (char *) __builtin_frame_address(0);
+#endif
 
 	/*
 	 * Compute distance from reference point to my local variables
 	 */
-	stack_depth = (ssize_t) (stack_base_ptr - &stack_top_loc);
+	stack_depth = (ssize_t) (stack_base_ptr - stack_address);
 
 	/*
 	 * Take abs value, since stacks grow up on some machines, down on others
diff --git a/.cirrus.tasks.yml b/.cirrus.tasks.yml
index a22cef063f3..8683d1ae9c7 100644
--- a/.cirrus.tasks.yml
+++ b/.cirrus.tasks.yml
@@ -447,7 +447,7 @@ task:
     # print_stacktraces=1,verbosity=2, duh
     # detect_leaks=0: too many uninteresting leak errors in short-lived binaries
     UBSAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:verbosity=2
-    ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0:detect_stack_use_after_return=0
+    ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0
 
     # SANITIZER_FLAGS is set in the tasks below
     CFLAGS: -Og -ggdb -fno-sanitize-recover=all $SANITIZER_FLAGS
-- 
2.53.0.1.gb2826b52eb


--7e2sypi5gxvkgog6--





^ permalink  raw  reply  [nested|flat] 114+ messages in thread

* [PATCH v2 1/4] Make stack depth check work with asan's use-after-return
@ 2026-02-07 19:04 Andres Freund <[email protected]>
  0 siblings, 0 replies; 114+ messages in thread

From: Andres Freund @ 2026-02-07 19:04 UTC (permalink / raw)

With address sanitizer's stack-use-after-return check, stack variables are
moved to heap allocations, to allow to detect references to the memory at a
later time. That broke our stack-depth check, which is why we had to disable
detect_stack_use_after_return in CI. Luckily __builtin_frame_address() works
correctly, even under asan, so use that.

Author:
Reviewed-by:
Discussion: https://postgr.es/m/
Backpatch:
---
 src/backend/utils/misc/stack_depth.c | 17 ++++++++++++++++-
 .cirrus.tasks.yml                    |  2 +-
 2 files changed, 17 insertions(+), 2 deletions(-)

diff --git a/src/backend/utils/misc/stack_depth.c b/src/backend/utils/misc/stack_depth.c
index 61a07cf824e..9955e28c176 100644
--- a/src/backend/utils/misc/stack_depth.c
+++ b/src/backend/utils/misc/stack_depth.c
@@ -108,13 +108,28 @@ check_stack_depth(void)
 bool
 stack_is_too_deep(void)
 {
+#ifndef HAVE__BUILTIN_FRAME_ADDRESS
 	char		stack_top_loc;
+#endif
 	ssize_t		stack_depth;
+	char	   *stack_address;
+
+	/*
+	 * With address sanitizer's stack-use-after-return check, stack variables
+	 * are moved to heap allocations, to allow to detect references to the
+	 * memory at a later time. That would break our stack-depth check. Luckily
+	 * __builtin_frame_address() works correctly, even under asan.
+	 */
+#ifndef HAVE__BUILTIN_FRAME_ADDRESS
+	stack_address = &stack_top_loc;
+#else
+	stack_address = (char *) __builtin_frame_address(0);
+#endif
 
 	/*
 	 * Compute distance from reference point to my local variables
 	 */
-	stack_depth = (ssize_t) (stack_base_ptr - &stack_top_loc);
+	stack_depth = (ssize_t) (stack_base_ptr - stack_address);
 
 	/*
 	 * Take abs value, since stacks grow up on some machines, down on others
diff --git a/.cirrus.tasks.yml b/.cirrus.tasks.yml
index a22cef063f3..8683d1ae9c7 100644
--- a/.cirrus.tasks.yml
+++ b/.cirrus.tasks.yml
@@ -447,7 +447,7 @@ task:
     # print_stacktraces=1,verbosity=2, duh
     # detect_leaks=0: too many uninteresting leak errors in short-lived binaries
     UBSAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:verbosity=2
-    ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0:detect_stack_use_after_return=0
+    ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0
 
     # SANITIZER_FLAGS is set in the tasks below
     CFLAGS: -Og -ggdb -fno-sanitize-recover=all $SANITIZER_FLAGS
-- 
2.53.0.1.gb2826b52eb


--7e2sypi5gxvkgog6--





^ permalink  raw  reply  [nested|flat] 114+ messages in thread

* [PATCH v2 1/4] Make stack depth check work with asan's use-after-return
@ 2026-02-07 19:04 Andres Freund <[email protected]>
  0 siblings, 0 replies; 114+ messages in thread

From: Andres Freund @ 2026-02-07 19:04 UTC (permalink / raw)

With address sanitizer's stack-use-after-return check, stack variables are
moved to heap allocations, to allow to detect references to the memory at a
later time. That broke our stack-depth check, which is why we had to disable
detect_stack_use_after_return in CI. Luckily __builtin_frame_address() works
correctly, even under asan, so use that.

Author:
Reviewed-by:
Discussion: https://postgr.es/m/
Backpatch:
---
 src/backend/utils/misc/stack_depth.c | 17 ++++++++++++++++-
 .cirrus.tasks.yml                    |  2 +-
 2 files changed, 17 insertions(+), 2 deletions(-)

diff --git a/src/backend/utils/misc/stack_depth.c b/src/backend/utils/misc/stack_depth.c
index 61a07cf824e..9955e28c176 100644
--- a/src/backend/utils/misc/stack_depth.c
+++ b/src/backend/utils/misc/stack_depth.c
@@ -108,13 +108,28 @@ check_stack_depth(void)
 bool
 stack_is_too_deep(void)
 {
+#ifndef HAVE__BUILTIN_FRAME_ADDRESS
 	char		stack_top_loc;
+#endif
 	ssize_t		stack_depth;
+	char	   *stack_address;
+
+	/*
+	 * With address sanitizer's stack-use-after-return check, stack variables
+	 * are moved to heap allocations, to allow to detect references to the
+	 * memory at a later time. That would break our stack-depth check. Luckily
+	 * __builtin_frame_address() works correctly, even under asan.
+	 */
+#ifndef HAVE__BUILTIN_FRAME_ADDRESS
+	stack_address = &stack_top_loc;
+#else
+	stack_address = (char *) __builtin_frame_address(0);
+#endif
 
 	/*
 	 * Compute distance from reference point to my local variables
 	 */
-	stack_depth = (ssize_t) (stack_base_ptr - &stack_top_loc);
+	stack_depth = (ssize_t) (stack_base_ptr - stack_address);
 
 	/*
 	 * Take abs value, since stacks grow up on some machines, down on others
diff --git a/.cirrus.tasks.yml b/.cirrus.tasks.yml
index a22cef063f3..8683d1ae9c7 100644
--- a/.cirrus.tasks.yml
+++ b/.cirrus.tasks.yml
@@ -447,7 +447,7 @@ task:
     # print_stacktraces=1,verbosity=2, duh
     # detect_leaks=0: too many uninteresting leak errors in short-lived binaries
     UBSAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:verbosity=2
-    ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0:detect_stack_use_after_return=0
+    ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0
 
     # SANITIZER_FLAGS is set in the tasks below
     CFLAGS: -Og -ggdb -fno-sanitize-recover=all $SANITIZER_FLAGS
-- 
2.53.0.1.gb2826b52eb


--7e2sypi5gxvkgog6--





^ permalink  raw  reply  [nested|flat] 114+ messages in thread

* [PATCH v2 1/4] Make stack depth check work with asan's use-after-return
@ 2026-02-07 19:04 Andres Freund <[email protected]>
  0 siblings, 0 replies; 114+ messages in thread

From: Andres Freund @ 2026-02-07 19:04 UTC (permalink / raw)

With address sanitizer's stack-use-after-return check, stack variables are
moved to heap allocations, to allow to detect references to the memory at a
later time. That broke our stack-depth check, which is why we had to disable
detect_stack_use_after_return in CI. Luckily __builtin_frame_address() works
correctly, even under asan, so use that.

Author:
Reviewed-by:
Discussion: https://postgr.es/m/
Backpatch:
---
 src/backend/utils/misc/stack_depth.c | 17 ++++++++++++++++-
 .cirrus.tasks.yml                    |  2 +-
 2 files changed, 17 insertions(+), 2 deletions(-)

diff --git a/src/backend/utils/misc/stack_depth.c b/src/backend/utils/misc/stack_depth.c
index 61a07cf824e..9955e28c176 100644
--- a/src/backend/utils/misc/stack_depth.c
+++ b/src/backend/utils/misc/stack_depth.c
@@ -108,13 +108,28 @@ check_stack_depth(void)
 bool
 stack_is_too_deep(void)
 {
+#ifndef HAVE__BUILTIN_FRAME_ADDRESS
 	char		stack_top_loc;
+#endif
 	ssize_t		stack_depth;
+	char	   *stack_address;
+
+	/*
+	 * With address sanitizer's stack-use-after-return check, stack variables
+	 * are moved to heap allocations, to allow to detect references to the
+	 * memory at a later time. That would break our stack-depth check. Luckily
+	 * __builtin_frame_address() works correctly, even under asan.
+	 */
+#ifndef HAVE__BUILTIN_FRAME_ADDRESS
+	stack_address = &stack_top_loc;
+#else
+	stack_address = (char *) __builtin_frame_address(0);
+#endif
 
 	/*
 	 * Compute distance from reference point to my local variables
 	 */
-	stack_depth = (ssize_t) (stack_base_ptr - &stack_top_loc);
+	stack_depth = (ssize_t) (stack_base_ptr - stack_address);
 
 	/*
 	 * Take abs value, since stacks grow up on some machines, down on others
diff --git a/.cirrus.tasks.yml b/.cirrus.tasks.yml
index a22cef063f3..8683d1ae9c7 100644
--- a/.cirrus.tasks.yml
+++ b/.cirrus.tasks.yml
@@ -447,7 +447,7 @@ task:
     # print_stacktraces=1,verbosity=2, duh
     # detect_leaks=0: too many uninteresting leak errors in short-lived binaries
     UBSAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:verbosity=2
-    ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0:detect_stack_use_after_return=0
+    ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0
 
     # SANITIZER_FLAGS is set in the tasks below
     CFLAGS: -Og -ggdb -fno-sanitize-recover=all $SANITIZER_FLAGS
-- 
2.53.0.1.gb2826b52eb


--7e2sypi5gxvkgog6--





^ permalink  raw  reply  [nested|flat] 114+ messages in thread

* [PATCH v2 1/4] Make stack depth check work with asan's use-after-return
@ 2026-02-07 19:04 Andres Freund <[email protected]>
  0 siblings, 0 replies; 114+ messages in thread

From: Andres Freund @ 2026-02-07 19:04 UTC (permalink / raw)

With address sanitizer's stack-use-after-return check, stack variables are
moved to heap allocations, to allow to detect references to the memory at a
later time. That broke our stack-depth check, which is why we had to disable
detect_stack_use_after_return in CI. Luckily __builtin_frame_address() works
correctly, even under asan, so use that.

Author:
Reviewed-by:
Discussion: https://postgr.es/m/
Backpatch:
---
 src/backend/utils/misc/stack_depth.c | 17 ++++++++++++++++-
 .cirrus.tasks.yml                    |  2 +-
 2 files changed, 17 insertions(+), 2 deletions(-)

diff --git a/src/backend/utils/misc/stack_depth.c b/src/backend/utils/misc/stack_depth.c
index 61a07cf824e..9955e28c176 100644
--- a/src/backend/utils/misc/stack_depth.c
+++ b/src/backend/utils/misc/stack_depth.c
@@ -108,13 +108,28 @@ check_stack_depth(void)
 bool
 stack_is_too_deep(void)
 {
+#ifndef HAVE__BUILTIN_FRAME_ADDRESS
 	char		stack_top_loc;
+#endif
 	ssize_t		stack_depth;
+	char	   *stack_address;
+
+	/*
+	 * With address sanitizer's stack-use-after-return check, stack variables
+	 * are moved to heap allocations, to allow to detect references to the
+	 * memory at a later time. That would break our stack-depth check. Luckily
+	 * __builtin_frame_address() works correctly, even under asan.
+	 */
+#ifndef HAVE__BUILTIN_FRAME_ADDRESS
+	stack_address = &stack_top_loc;
+#else
+	stack_address = (char *) __builtin_frame_address(0);
+#endif
 
 	/*
 	 * Compute distance from reference point to my local variables
 	 */
-	stack_depth = (ssize_t) (stack_base_ptr - &stack_top_loc);
+	stack_depth = (ssize_t) (stack_base_ptr - stack_address);
 
 	/*
 	 * Take abs value, since stacks grow up on some machines, down on others
diff --git a/.cirrus.tasks.yml b/.cirrus.tasks.yml
index a22cef063f3..8683d1ae9c7 100644
--- a/.cirrus.tasks.yml
+++ b/.cirrus.tasks.yml
@@ -447,7 +447,7 @@ task:
     # print_stacktraces=1,verbosity=2, duh
     # detect_leaks=0: too many uninteresting leak errors in short-lived binaries
     UBSAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:verbosity=2
-    ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0:detect_stack_use_after_return=0
+    ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0
 
     # SANITIZER_FLAGS is set in the tasks below
     CFLAGS: -Og -ggdb -fno-sanitize-recover=all $SANITIZER_FLAGS
-- 
2.53.0.1.gb2826b52eb


--7e2sypi5gxvkgog6--





^ permalink  raw  reply  [nested|flat] 114+ messages in thread

* [PATCH v2 1/4] Make stack depth check work with asan's use-after-return
@ 2026-02-07 19:04 Andres Freund <[email protected]>
  0 siblings, 0 replies; 114+ messages in thread

From: Andres Freund @ 2026-02-07 19:04 UTC (permalink / raw)

With address sanitizer's stack-use-after-return check, stack variables are
moved to heap allocations, to allow to detect references to the memory at a
later time. That broke our stack-depth check, which is why we had to disable
detect_stack_use_after_return in CI. Luckily __builtin_frame_address() works
correctly, even under asan, so use that.

Author:
Reviewed-by:
Discussion: https://postgr.es/m/
Backpatch:
---
 src/backend/utils/misc/stack_depth.c | 17 ++++++++++++++++-
 .cirrus.tasks.yml                    |  2 +-
 2 files changed, 17 insertions(+), 2 deletions(-)

diff --git a/src/backend/utils/misc/stack_depth.c b/src/backend/utils/misc/stack_depth.c
index 61a07cf824e..9955e28c176 100644
--- a/src/backend/utils/misc/stack_depth.c
+++ b/src/backend/utils/misc/stack_depth.c
@@ -108,13 +108,28 @@ check_stack_depth(void)
 bool
 stack_is_too_deep(void)
 {
+#ifndef HAVE__BUILTIN_FRAME_ADDRESS
 	char		stack_top_loc;
+#endif
 	ssize_t		stack_depth;
+	char	   *stack_address;
+
+	/*
+	 * With address sanitizer's stack-use-after-return check, stack variables
+	 * are moved to heap allocations, to allow to detect references to the
+	 * memory at a later time. That would break our stack-depth check. Luckily
+	 * __builtin_frame_address() works correctly, even under asan.
+	 */
+#ifndef HAVE__BUILTIN_FRAME_ADDRESS
+	stack_address = &stack_top_loc;
+#else
+	stack_address = (char *) __builtin_frame_address(0);
+#endif
 
 	/*
 	 * Compute distance from reference point to my local variables
 	 */
-	stack_depth = (ssize_t) (stack_base_ptr - &stack_top_loc);
+	stack_depth = (ssize_t) (stack_base_ptr - stack_address);
 
 	/*
 	 * Take abs value, since stacks grow up on some machines, down on others
diff --git a/.cirrus.tasks.yml b/.cirrus.tasks.yml
index a22cef063f3..8683d1ae9c7 100644
--- a/.cirrus.tasks.yml
+++ b/.cirrus.tasks.yml
@@ -447,7 +447,7 @@ task:
     # print_stacktraces=1,verbosity=2, duh
     # detect_leaks=0: too many uninteresting leak errors in short-lived binaries
     UBSAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:verbosity=2
-    ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0:detect_stack_use_after_return=0
+    ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0
 
     # SANITIZER_FLAGS is set in the tasks below
     CFLAGS: -Og -ggdb -fno-sanitize-recover=all $SANITIZER_FLAGS
-- 
2.53.0.1.gb2826b52eb


--7e2sypi5gxvkgog6--





^ permalink  raw  reply  [nested|flat] 114+ messages in thread

* [PATCH v2 1/4] Make stack depth check work with asan's use-after-return
@ 2026-02-07 19:04 Andres Freund <[email protected]>
  0 siblings, 0 replies; 114+ messages in thread

From: Andres Freund @ 2026-02-07 19:04 UTC (permalink / raw)

With address sanitizer's stack-use-after-return check, stack variables are
moved to heap allocations, to allow to detect references to the memory at a
later time. That broke our stack-depth check, which is why we had to disable
detect_stack_use_after_return in CI. Luckily __builtin_frame_address() works
correctly, even under asan, so use that.

Author:
Reviewed-by:
Discussion: https://postgr.es/m/
Backpatch:
---
 src/backend/utils/misc/stack_depth.c | 17 ++++++++++++++++-
 .cirrus.tasks.yml                    |  2 +-
 2 files changed, 17 insertions(+), 2 deletions(-)

diff --git a/src/backend/utils/misc/stack_depth.c b/src/backend/utils/misc/stack_depth.c
index 61a07cf824e..9955e28c176 100644
--- a/src/backend/utils/misc/stack_depth.c
+++ b/src/backend/utils/misc/stack_depth.c
@@ -108,13 +108,28 @@ check_stack_depth(void)
 bool
 stack_is_too_deep(void)
 {
+#ifndef HAVE__BUILTIN_FRAME_ADDRESS
 	char		stack_top_loc;
+#endif
 	ssize_t		stack_depth;
+	char	   *stack_address;
+
+	/*
+	 * With address sanitizer's stack-use-after-return check, stack variables
+	 * are moved to heap allocations, to allow to detect references to the
+	 * memory at a later time. That would break our stack-depth check. Luckily
+	 * __builtin_frame_address() works correctly, even under asan.
+	 */
+#ifndef HAVE__BUILTIN_FRAME_ADDRESS
+	stack_address = &stack_top_loc;
+#else
+	stack_address = (char *) __builtin_frame_address(0);
+#endif
 
 	/*
 	 * Compute distance from reference point to my local variables
 	 */
-	stack_depth = (ssize_t) (stack_base_ptr - &stack_top_loc);
+	stack_depth = (ssize_t) (stack_base_ptr - stack_address);
 
 	/*
 	 * Take abs value, since stacks grow up on some machines, down on others
diff --git a/.cirrus.tasks.yml b/.cirrus.tasks.yml
index a22cef063f3..8683d1ae9c7 100644
--- a/.cirrus.tasks.yml
+++ b/.cirrus.tasks.yml
@@ -447,7 +447,7 @@ task:
     # print_stacktraces=1,verbosity=2, duh
     # detect_leaks=0: too many uninteresting leak errors in short-lived binaries
     UBSAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:verbosity=2
-    ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0:detect_stack_use_after_return=0
+    ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0
 
     # SANITIZER_FLAGS is set in the tasks below
     CFLAGS: -Og -ggdb -fno-sanitize-recover=all $SANITIZER_FLAGS
-- 
2.53.0.1.gb2826b52eb


--7e2sypi5gxvkgog6--





^ permalink  raw  reply  [nested|flat] 114+ messages in thread

* [PATCH v2 1/4] Make stack depth check work with asan's use-after-return
@ 2026-02-07 19:04 Andres Freund <[email protected]>
  0 siblings, 0 replies; 114+ messages in thread

From: Andres Freund @ 2026-02-07 19:04 UTC (permalink / raw)

With address sanitizer's stack-use-after-return check, stack variables are
moved to heap allocations, to allow to detect references to the memory at a
later time. That broke our stack-depth check, which is why we had to disable
detect_stack_use_after_return in CI. Luckily __builtin_frame_address() works
correctly, even under asan, so use that.

Author:
Reviewed-by:
Discussion: https://postgr.es/m/
Backpatch:
---
 src/backend/utils/misc/stack_depth.c | 17 ++++++++++++++++-
 .cirrus.tasks.yml                    |  2 +-
 2 files changed, 17 insertions(+), 2 deletions(-)

diff --git a/src/backend/utils/misc/stack_depth.c b/src/backend/utils/misc/stack_depth.c
index 61a07cf824e..9955e28c176 100644
--- a/src/backend/utils/misc/stack_depth.c
+++ b/src/backend/utils/misc/stack_depth.c
@@ -108,13 +108,28 @@ check_stack_depth(void)
 bool
 stack_is_too_deep(void)
 {
+#ifndef HAVE__BUILTIN_FRAME_ADDRESS
 	char		stack_top_loc;
+#endif
 	ssize_t		stack_depth;
+	char	   *stack_address;
+
+	/*
+	 * With address sanitizer's stack-use-after-return check, stack variables
+	 * are moved to heap allocations, to allow to detect references to the
+	 * memory at a later time. That would break our stack-depth check. Luckily
+	 * __builtin_frame_address() works correctly, even under asan.
+	 */
+#ifndef HAVE__BUILTIN_FRAME_ADDRESS
+	stack_address = &stack_top_loc;
+#else
+	stack_address = (char *) __builtin_frame_address(0);
+#endif
 
 	/*
 	 * Compute distance from reference point to my local variables
 	 */
-	stack_depth = (ssize_t) (stack_base_ptr - &stack_top_loc);
+	stack_depth = (ssize_t) (stack_base_ptr - stack_address);
 
 	/*
 	 * Take abs value, since stacks grow up on some machines, down on others
diff --git a/.cirrus.tasks.yml b/.cirrus.tasks.yml
index a22cef063f3..8683d1ae9c7 100644
--- a/.cirrus.tasks.yml
+++ b/.cirrus.tasks.yml
@@ -447,7 +447,7 @@ task:
     # print_stacktraces=1,verbosity=2, duh
     # detect_leaks=0: too many uninteresting leak errors in short-lived binaries
     UBSAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:verbosity=2
-    ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0:detect_stack_use_after_return=0
+    ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0
 
     # SANITIZER_FLAGS is set in the tasks below
     CFLAGS: -Og -ggdb -fno-sanitize-recover=all $SANITIZER_FLAGS
-- 
2.53.0.1.gb2826b52eb


--7e2sypi5gxvkgog6--





^ permalink  raw  reply  [nested|flat] 114+ messages in thread

* [PATCH v2 1/4] Make stack depth check work with asan's use-after-return
@ 2026-02-07 19:04 Andres Freund <[email protected]>
  0 siblings, 0 replies; 114+ messages in thread

From: Andres Freund @ 2026-02-07 19:04 UTC (permalink / raw)

With address sanitizer's stack-use-after-return check, stack variables are
moved to heap allocations, to allow to detect references to the memory at a
later time. That broke our stack-depth check, which is why we had to disable
detect_stack_use_after_return in CI. Luckily __builtin_frame_address() works
correctly, even under asan, so use that.

Author:
Reviewed-by:
Discussion: https://postgr.es/m/
Backpatch:
---
 src/backend/utils/misc/stack_depth.c | 17 ++++++++++++++++-
 .cirrus.tasks.yml                    |  2 +-
 2 files changed, 17 insertions(+), 2 deletions(-)

diff --git a/src/backend/utils/misc/stack_depth.c b/src/backend/utils/misc/stack_depth.c
index 61a07cf824e..9955e28c176 100644
--- a/src/backend/utils/misc/stack_depth.c
+++ b/src/backend/utils/misc/stack_depth.c
@@ -108,13 +108,28 @@ check_stack_depth(void)
 bool
 stack_is_too_deep(void)
 {
+#ifndef HAVE__BUILTIN_FRAME_ADDRESS
 	char		stack_top_loc;
+#endif
 	ssize_t		stack_depth;
+	char	   *stack_address;
+
+	/*
+	 * With address sanitizer's stack-use-after-return check, stack variables
+	 * are moved to heap allocations, to allow to detect references to the
+	 * memory at a later time. That would break our stack-depth check. Luckily
+	 * __builtin_frame_address() works correctly, even under asan.
+	 */
+#ifndef HAVE__BUILTIN_FRAME_ADDRESS
+	stack_address = &stack_top_loc;
+#else
+	stack_address = (char *) __builtin_frame_address(0);
+#endif
 
 	/*
 	 * Compute distance from reference point to my local variables
 	 */
-	stack_depth = (ssize_t) (stack_base_ptr - &stack_top_loc);
+	stack_depth = (ssize_t) (stack_base_ptr - stack_address);
 
 	/*
 	 * Take abs value, since stacks grow up on some machines, down on others
diff --git a/.cirrus.tasks.yml b/.cirrus.tasks.yml
index a22cef063f3..8683d1ae9c7 100644
--- a/.cirrus.tasks.yml
+++ b/.cirrus.tasks.yml
@@ -447,7 +447,7 @@ task:
     # print_stacktraces=1,verbosity=2, duh
     # detect_leaks=0: too many uninteresting leak errors in short-lived binaries
     UBSAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:verbosity=2
-    ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0:detect_stack_use_after_return=0
+    ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0
 
     # SANITIZER_FLAGS is set in the tasks below
     CFLAGS: -Og -ggdb -fno-sanitize-recover=all $SANITIZER_FLAGS
-- 
2.53.0.1.gb2826b52eb


--7e2sypi5gxvkgog6--





^ permalink  raw  reply  [nested|flat] 114+ messages in thread

* [PATCH v2 1/4] Make stack depth check work with asan's use-after-return
@ 2026-02-07 19:04 Andres Freund <[email protected]>
  0 siblings, 0 replies; 114+ messages in thread

From: Andres Freund @ 2026-02-07 19:04 UTC (permalink / raw)

With address sanitizer's stack-use-after-return check, stack variables are
moved to heap allocations, to allow to detect references to the memory at a
later time. That broke our stack-depth check, which is why we had to disable
detect_stack_use_after_return in CI. Luckily __builtin_frame_address() works
correctly, even under asan, so use that.

Author:
Reviewed-by:
Discussion: https://postgr.es/m/
Backpatch:
---
 src/backend/utils/misc/stack_depth.c | 17 ++++++++++++++++-
 .cirrus.tasks.yml                    |  2 +-
 2 files changed, 17 insertions(+), 2 deletions(-)

diff --git a/src/backend/utils/misc/stack_depth.c b/src/backend/utils/misc/stack_depth.c
index 61a07cf824e..9955e28c176 100644
--- a/src/backend/utils/misc/stack_depth.c
+++ b/src/backend/utils/misc/stack_depth.c
@@ -108,13 +108,28 @@ check_stack_depth(void)
 bool
 stack_is_too_deep(void)
 {
+#ifndef HAVE__BUILTIN_FRAME_ADDRESS
 	char		stack_top_loc;
+#endif
 	ssize_t		stack_depth;
+	char	   *stack_address;
+
+	/*
+	 * With address sanitizer's stack-use-after-return check, stack variables
+	 * are moved to heap allocations, to allow to detect references to the
+	 * memory at a later time. That would break our stack-depth check. Luckily
+	 * __builtin_frame_address() works correctly, even under asan.
+	 */
+#ifndef HAVE__BUILTIN_FRAME_ADDRESS
+	stack_address = &stack_top_loc;
+#else
+	stack_address = (char *) __builtin_frame_address(0);
+#endif
 
 	/*
 	 * Compute distance from reference point to my local variables
 	 */
-	stack_depth = (ssize_t) (stack_base_ptr - &stack_top_loc);
+	stack_depth = (ssize_t) (stack_base_ptr - stack_address);
 
 	/*
 	 * Take abs value, since stacks grow up on some machines, down on others
diff --git a/.cirrus.tasks.yml b/.cirrus.tasks.yml
index a22cef063f3..8683d1ae9c7 100644
--- a/.cirrus.tasks.yml
+++ b/.cirrus.tasks.yml
@@ -447,7 +447,7 @@ task:
     # print_stacktraces=1,verbosity=2, duh
     # detect_leaks=0: too many uninteresting leak errors in short-lived binaries
     UBSAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:verbosity=2
-    ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0:detect_stack_use_after_return=0
+    ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0
 
     # SANITIZER_FLAGS is set in the tasks below
     CFLAGS: -Og -ggdb -fno-sanitize-recover=all $SANITIZER_FLAGS
-- 
2.53.0.1.gb2826b52eb


--7e2sypi5gxvkgog6--





^ permalink  raw  reply  [nested|flat] 114+ messages in thread

* [PATCH v2 1/4] Make stack depth check work with asan's use-after-return
@ 2026-02-07 19:04 Andres Freund <[email protected]>
  0 siblings, 0 replies; 114+ messages in thread

From: Andres Freund @ 2026-02-07 19:04 UTC (permalink / raw)

With address sanitizer's stack-use-after-return check, stack variables are
moved to heap allocations, to allow to detect references to the memory at a
later time. That broke our stack-depth check, which is why we had to disable
detect_stack_use_after_return in CI. Luckily __builtin_frame_address() works
correctly, even under asan, so use that.

Author:
Reviewed-by:
Discussion: https://postgr.es/m/
Backpatch:
---
 src/backend/utils/misc/stack_depth.c | 17 ++++++++++++++++-
 .cirrus.tasks.yml                    |  2 +-
 2 files changed, 17 insertions(+), 2 deletions(-)

diff --git a/src/backend/utils/misc/stack_depth.c b/src/backend/utils/misc/stack_depth.c
index 61a07cf824e..9955e28c176 100644
--- a/src/backend/utils/misc/stack_depth.c
+++ b/src/backend/utils/misc/stack_depth.c
@@ -108,13 +108,28 @@ check_stack_depth(void)
 bool
 stack_is_too_deep(void)
 {
+#ifndef HAVE__BUILTIN_FRAME_ADDRESS
 	char		stack_top_loc;
+#endif
 	ssize_t		stack_depth;
+	char	   *stack_address;
+
+	/*
+	 * With address sanitizer's stack-use-after-return check, stack variables
+	 * are moved to heap allocations, to allow to detect references to the
+	 * memory at a later time. That would break our stack-depth check. Luckily
+	 * __builtin_frame_address() works correctly, even under asan.
+	 */
+#ifndef HAVE__BUILTIN_FRAME_ADDRESS
+	stack_address = &stack_top_loc;
+#else
+	stack_address = (char *) __builtin_frame_address(0);
+#endif
 
 	/*
 	 * Compute distance from reference point to my local variables
 	 */
-	stack_depth = (ssize_t) (stack_base_ptr - &stack_top_loc);
+	stack_depth = (ssize_t) (stack_base_ptr - stack_address);
 
 	/*
 	 * Take abs value, since stacks grow up on some machines, down on others
diff --git a/.cirrus.tasks.yml b/.cirrus.tasks.yml
index a22cef063f3..8683d1ae9c7 100644
--- a/.cirrus.tasks.yml
+++ b/.cirrus.tasks.yml
@@ -447,7 +447,7 @@ task:
     # print_stacktraces=1,verbosity=2, duh
     # detect_leaks=0: too many uninteresting leak errors in short-lived binaries
     UBSAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:verbosity=2
-    ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0:detect_stack_use_after_return=0
+    ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0
 
     # SANITIZER_FLAGS is set in the tasks below
     CFLAGS: -Og -ggdb -fno-sanitize-recover=all $SANITIZER_FLAGS
-- 
2.53.0.1.gb2826b52eb


--7e2sypi5gxvkgog6--





^ permalink  raw  reply  [nested|flat] 114+ messages in thread

* [PATCH v2 1/4] Make stack depth check work with asan's use-after-return
@ 2026-02-07 19:04 Andres Freund <[email protected]>
  0 siblings, 0 replies; 114+ messages in thread

From: Andres Freund @ 2026-02-07 19:04 UTC (permalink / raw)

With address sanitizer's stack-use-after-return check, stack variables are
moved to heap allocations, to allow to detect references to the memory at a
later time. That broke our stack-depth check, which is why we had to disable
detect_stack_use_after_return in CI. Luckily __builtin_frame_address() works
correctly, even under asan, so use that.

Author:
Reviewed-by:
Discussion: https://postgr.es/m/
Backpatch:
---
 src/backend/utils/misc/stack_depth.c | 17 ++++++++++++++++-
 .cirrus.tasks.yml                    |  2 +-
 2 files changed, 17 insertions(+), 2 deletions(-)

diff --git a/src/backend/utils/misc/stack_depth.c b/src/backend/utils/misc/stack_depth.c
index 61a07cf824e..9955e28c176 100644
--- a/src/backend/utils/misc/stack_depth.c
+++ b/src/backend/utils/misc/stack_depth.c
@@ -108,13 +108,28 @@ check_stack_depth(void)
 bool
 stack_is_too_deep(void)
 {
+#ifndef HAVE__BUILTIN_FRAME_ADDRESS
 	char		stack_top_loc;
+#endif
 	ssize_t		stack_depth;
+	char	   *stack_address;
+
+	/*
+	 * With address sanitizer's stack-use-after-return check, stack variables
+	 * are moved to heap allocations, to allow to detect references to the
+	 * memory at a later time. That would break our stack-depth check. Luckily
+	 * __builtin_frame_address() works correctly, even under asan.
+	 */
+#ifndef HAVE__BUILTIN_FRAME_ADDRESS
+	stack_address = &stack_top_loc;
+#else
+	stack_address = (char *) __builtin_frame_address(0);
+#endif
 
 	/*
 	 * Compute distance from reference point to my local variables
 	 */
-	stack_depth = (ssize_t) (stack_base_ptr - &stack_top_loc);
+	stack_depth = (ssize_t) (stack_base_ptr - stack_address);
 
 	/*
 	 * Take abs value, since stacks grow up on some machines, down on others
diff --git a/.cirrus.tasks.yml b/.cirrus.tasks.yml
index a22cef063f3..8683d1ae9c7 100644
--- a/.cirrus.tasks.yml
+++ b/.cirrus.tasks.yml
@@ -447,7 +447,7 @@ task:
     # print_stacktraces=1,verbosity=2, duh
     # detect_leaks=0: too many uninteresting leak errors in short-lived binaries
     UBSAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:verbosity=2
-    ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0:detect_stack_use_after_return=0
+    ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0
 
     # SANITIZER_FLAGS is set in the tasks below
     CFLAGS: -Og -ggdb -fno-sanitize-recover=all $SANITIZER_FLAGS
-- 
2.53.0.1.gb2826b52eb


--7e2sypi5gxvkgog6--





^ permalink  raw  reply  [nested|flat] 114+ messages in thread

* [PATCH v2 1/4] Make stack depth check work with asan's use-after-return
@ 2026-02-07 19:04 Andres Freund <[email protected]>
  0 siblings, 0 replies; 114+ messages in thread

From: Andres Freund @ 2026-02-07 19:04 UTC (permalink / raw)

With address sanitizer's stack-use-after-return check, stack variables are
moved to heap allocations, to allow to detect references to the memory at a
later time. That broke our stack-depth check, which is why we had to disable
detect_stack_use_after_return in CI. Luckily __builtin_frame_address() works
correctly, even under asan, so use that.

Author:
Reviewed-by:
Discussion: https://postgr.es/m/
Backpatch:
---
 src/backend/utils/misc/stack_depth.c | 17 ++++++++++++++++-
 .cirrus.tasks.yml                    |  2 +-
 2 files changed, 17 insertions(+), 2 deletions(-)

diff --git a/src/backend/utils/misc/stack_depth.c b/src/backend/utils/misc/stack_depth.c
index 61a07cf824e..9955e28c176 100644
--- a/src/backend/utils/misc/stack_depth.c
+++ b/src/backend/utils/misc/stack_depth.c
@@ -108,13 +108,28 @@ check_stack_depth(void)
 bool
 stack_is_too_deep(void)
 {
+#ifndef HAVE__BUILTIN_FRAME_ADDRESS
 	char		stack_top_loc;
+#endif
 	ssize_t		stack_depth;
+	char	   *stack_address;
+
+	/*
+	 * With address sanitizer's stack-use-after-return check, stack variables
+	 * are moved to heap allocations, to allow to detect references to the
+	 * memory at a later time. That would break our stack-depth check. Luckily
+	 * __builtin_frame_address() works correctly, even under asan.
+	 */
+#ifndef HAVE__BUILTIN_FRAME_ADDRESS
+	stack_address = &stack_top_loc;
+#else
+	stack_address = (char *) __builtin_frame_address(0);
+#endif
 
 	/*
 	 * Compute distance from reference point to my local variables
 	 */
-	stack_depth = (ssize_t) (stack_base_ptr - &stack_top_loc);
+	stack_depth = (ssize_t) (stack_base_ptr - stack_address);
 
 	/*
 	 * Take abs value, since stacks grow up on some machines, down on others
diff --git a/.cirrus.tasks.yml b/.cirrus.tasks.yml
index a22cef063f3..8683d1ae9c7 100644
--- a/.cirrus.tasks.yml
+++ b/.cirrus.tasks.yml
@@ -447,7 +447,7 @@ task:
     # print_stacktraces=1,verbosity=2, duh
     # detect_leaks=0: too many uninteresting leak errors in short-lived binaries
     UBSAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:verbosity=2
-    ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0:detect_stack_use_after_return=0
+    ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0
 
     # SANITIZER_FLAGS is set in the tasks below
     CFLAGS: -Og -ggdb -fno-sanitize-recover=all $SANITIZER_FLAGS
-- 
2.53.0.1.gb2826b52eb


--7e2sypi5gxvkgog6--





^ permalink  raw  reply  [nested|flat] 114+ messages in thread

* [PATCH v2 1/4] Make stack depth check work with asan's use-after-return
@ 2026-02-07 19:04 Andres Freund <[email protected]>
  0 siblings, 0 replies; 114+ messages in thread

From: Andres Freund @ 2026-02-07 19:04 UTC (permalink / raw)

With address sanitizer's stack-use-after-return check, stack variables are
moved to heap allocations, to allow to detect references to the memory at a
later time. That broke our stack-depth check, which is why we had to disable
detect_stack_use_after_return in CI. Luckily __builtin_frame_address() works
correctly, even under asan, so use that.

Author:
Reviewed-by:
Discussion: https://postgr.es/m/
Backpatch:
---
 src/backend/utils/misc/stack_depth.c | 17 ++++++++++++++++-
 .cirrus.tasks.yml                    |  2 +-
 2 files changed, 17 insertions(+), 2 deletions(-)

diff --git a/src/backend/utils/misc/stack_depth.c b/src/backend/utils/misc/stack_depth.c
index 61a07cf824e..9955e28c176 100644
--- a/src/backend/utils/misc/stack_depth.c
+++ b/src/backend/utils/misc/stack_depth.c
@@ -108,13 +108,28 @@ check_stack_depth(void)
 bool
 stack_is_too_deep(void)
 {
+#ifndef HAVE__BUILTIN_FRAME_ADDRESS
 	char		stack_top_loc;
+#endif
 	ssize_t		stack_depth;
+	char	   *stack_address;
+
+	/*
+	 * With address sanitizer's stack-use-after-return check, stack variables
+	 * are moved to heap allocations, to allow to detect references to the
+	 * memory at a later time. That would break our stack-depth check. Luckily
+	 * __builtin_frame_address() works correctly, even under asan.
+	 */
+#ifndef HAVE__BUILTIN_FRAME_ADDRESS
+	stack_address = &stack_top_loc;
+#else
+	stack_address = (char *) __builtin_frame_address(0);
+#endif
 
 	/*
 	 * Compute distance from reference point to my local variables
 	 */
-	stack_depth = (ssize_t) (stack_base_ptr - &stack_top_loc);
+	stack_depth = (ssize_t) (stack_base_ptr - stack_address);
 
 	/*
 	 * Take abs value, since stacks grow up on some machines, down on others
diff --git a/.cirrus.tasks.yml b/.cirrus.tasks.yml
index a22cef063f3..8683d1ae9c7 100644
--- a/.cirrus.tasks.yml
+++ b/.cirrus.tasks.yml
@@ -447,7 +447,7 @@ task:
     # print_stacktraces=1,verbosity=2, duh
     # detect_leaks=0: too many uninteresting leak errors in short-lived binaries
     UBSAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:verbosity=2
-    ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0:detect_stack_use_after_return=0
+    ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0
 
     # SANITIZER_FLAGS is set in the tasks below
     CFLAGS: -Og -ggdb -fno-sanitize-recover=all $SANITIZER_FLAGS
-- 
2.53.0.1.gb2826b52eb


--7e2sypi5gxvkgog6--





^ permalink  raw  reply  [nested|flat] 114+ messages in thread

* [PATCH v2 1/4] Make stack depth check work with asan's use-after-return
@ 2026-02-07 19:04 Andres Freund <[email protected]>
  0 siblings, 0 replies; 114+ messages in thread

From: Andres Freund @ 2026-02-07 19:04 UTC (permalink / raw)

With address sanitizer's stack-use-after-return check, stack variables are
moved to heap allocations, to allow to detect references to the memory at a
later time. That broke our stack-depth check, which is why we had to disable
detect_stack_use_after_return in CI. Luckily __builtin_frame_address() works
correctly, even under asan, so use that.

Author:
Reviewed-by:
Discussion: https://postgr.es/m/
Backpatch:
---
 src/backend/utils/misc/stack_depth.c | 17 ++++++++++++++++-
 .cirrus.tasks.yml                    |  2 +-
 2 files changed, 17 insertions(+), 2 deletions(-)

diff --git a/src/backend/utils/misc/stack_depth.c b/src/backend/utils/misc/stack_depth.c
index 61a07cf824e..9955e28c176 100644
--- a/src/backend/utils/misc/stack_depth.c
+++ b/src/backend/utils/misc/stack_depth.c
@@ -108,13 +108,28 @@ check_stack_depth(void)
 bool
 stack_is_too_deep(void)
 {
+#ifndef HAVE__BUILTIN_FRAME_ADDRESS
 	char		stack_top_loc;
+#endif
 	ssize_t		stack_depth;
+	char	   *stack_address;
+
+	/*
+	 * With address sanitizer's stack-use-after-return check, stack variables
+	 * are moved to heap allocations, to allow to detect references to the
+	 * memory at a later time. That would break our stack-depth check. Luckily
+	 * __builtin_frame_address() works correctly, even under asan.
+	 */
+#ifndef HAVE__BUILTIN_FRAME_ADDRESS
+	stack_address = &stack_top_loc;
+#else
+	stack_address = (char *) __builtin_frame_address(0);
+#endif
 
 	/*
 	 * Compute distance from reference point to my local variables
 	 */
-	stack_depth = (ssize_t) (stack_base_ptr - &stack_top_loc);
+	stack_depth = (ssize_t) (stack_base_ptr - stack_address);
 
 	/*
 	 * Take abs value, since stacks grow up on some machines, down on others
diff --git a/.cirrus.tasks.yml b/.cirrus.tasks.yml
index a22cef063f3..8683d1ae9c7 100644
--- a/.cirrus.tasks.yml
+++ b/.cirrus.tasks.yml
@@ -447,7 +447,7 @@ task:
     # print_stacktraces=1,verbosity=2, duh
     # detect_leaks=0: too many uninteresting leak errors in short-lived binaries
     UBSAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:verbosity=2
-    ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0:detect_stack_use_after_return=0
+    ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0
 
     # SANITIZER_FLAGS is set in the tasks below
     CFLAGS: -Og -ggdb -fno-sanitize-recover=all $SANITIZER_FLAGS
-- 
2.53.0.1.gb2826b52eb


--7e2sypi5gxvkgog6--





^ permalink  raw  reply  [nested|flat] 114+ messages in thread

* [PATCH v2 1/4] Make stack depth check work with asan's use-after-return
@ 2026-02-07 19:04 Andres Freund <[email protected]>
  0 siblings, 0 replies; 114+ messages in thread

From: Andres Freund @ 2026-02-07 19:04 UTC (permalink / raw)

With address sanitizer's stack-use-after-return check, stack variables are
moved to heap allocations, to allow to detect references to the memory at a
later time. That broke our stack-depth check, which is why we had to disable
detect_stack_use_after_return in CI. Luckily __builtin_frame_address() works
correctly, even under asan, so use that.

Author:
Reviewed-by:
Discussion: https://postgr.es/m/
Backpatch:
---
 src/backend/utils/misc/stack_depth.c | 17 ++++++++++++++++-
 .cirrus.tasks.yml                    |  2 +-
 2 files changed, 17 insertions(+), 2 deletions(-)

diff --git a/src/backend/utils/misc/stack_depth.c b/src/backend/utils/misc/stack_depth.c
index 61a07cf824e..9955e28c176 100644
--- a/src/backend/utils/misc/stack_depth.c
+++ b/src/backend/utils/misc/stack_depth.c
@@ -108,13 +108,28 @@ check_stack_depth(void)
 bool
 stack_is_too_deep(void)
 {
+#ifndef HAVE__BUILTIN_FRAME_ADDRESS
 	char		stack_top_loc;
+#endif
 	ssize_t		stack_depth;
+	char	   *stack_address;
+
+	/*
+	 * With address sanitizer's stack-use-after-return check, stack variables
+	 * are moved to heap allocations, to allow to detect references to the
+	 * memory at a later time. That would break our stack-depth check. Luckily
+	 * __builtin_frame_address() works correctly, even under asan.
+	 */
+#ifndef HAVE__BUILTIN_FRAME_ADDRESS
+	stack_address = &stack_top_loc;
+#else
+	stack_address = (char *) __builtin_frame_address(0);
+#endif
 
 	/*
 	 * Compute distance from reference point to my local variables
 	 */
-	stack_depth = (ssize_t) (stack_base_ptr - &stack_top_loc);
+	stack_depth = (ssize_t) (stack_base_ptr - stack_address);
 
 	/*
 	 * Take abs value, since stacks grow up on some machines, down on others
diff --git a/.cirrus.tasks.yml b/.cirrus.tasks.yml
index a22cef063f3..8683d1ae9c7 100644
--- a/.cirrus.tasks.yml
+++ b/.cirrus.tasks.yml
@@ -447,7 +447,7 @@ task:
     # print_stacktraces=1,verbosity=2, duh
     # detect_leaks=0: too many uninteresting leak errors in short-lived binaries
     UBSAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:verbosity=2
-    ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0:detect_stack_use_after_return=0
+    ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0
 
     # SANITIZER_FLAGS is set in the tasks below
     CFLAGS: -Og -ggdb -fno-sanitize-recover=all $SANITIZER_FLAGS
-- 
2.53.0.1.gb2826b52eb


--7e2sypi5gxvkgog6--





^ permalink  raw  reply  [nested|flat] 114+ messages in thread

* [PATCH v2 1/4] Make stack depth check work with asan's use-after-return
@ 2026-02-07 19:04 Andres Freund <[email protected]>
  0 siblings, 0 replies; 114+ messages in thread

From: Andres Freund @ 2026-02-07 19:04 UTC (permalink / raw)

With address sanitizer's stack-use-after-return check, stack variables are
moved to heap allocations, to allow to detect references to the memory at a
later time. That broke our stack-depth check, which is why we had to disable
detect_stack_use_after_return in CI. Luckily __builtin_frame_address() works
correctly, even under asan, so use that.

Author:
Reviewed-by:
Discussion: https://postgr.es/m/
Backpatch:
---
 src/backend/utils/misc/stack_depth.c | 17 ++++++++++++++++-
 .cirrus.tasks.yml                    |  2 +-
 2 files changed, 17 insertions(+), 2 deletions(-)

diff --git a/src/backend/utils/misc/stack_depth.c b/src/backend/utils/misc/stack_depth.c
index 61a07cf824e..9955e28c176 100644
--- a/src/backend/utils/misc/stack_depth.c
+++ b/src/backend/utils/misc/stack_depth.c
@@ -108,13 +108,28 @@ check_stack_depth(void)
 bool
 stack_is_too_deep(void)
 {
+#ifndef HAVE__BUILTIN_FRAME_ADDRESS
 	char		stack_top_loc;
+#endif
 	ssize_t		stack_depth;
+	char	   *stack_address;
+
+	/*
+	 * With address sanitizer's stack-use-after-return check, stack variables
+	 * are moved to heap allocations, to allow to detect references to the
+	 * memory at a later time. That would break our stack-depth check. Luckily
+	 * __builtin_frame_address() works correctly, even under asan.
+	 */
+#ifndef HAVE__BUILTIN_FRAME_ADDRESS
+	stack_address = &stack_top_loc;
+#else
+	stack_address = (char *) __builtin_frame_address(0);
+#endif
 
 	/*
 	 * Compute distance from reference point to my local variables
 	 */
-	stack_depth = (ssize_t) (stack_base_ptr - &stack_top_loc);
+	stack_depth = (ssize_t) (stack_base_ptr - stack_address);
 
 	/*
 	 * Take abs value, since stacks grow up on some machines, down on others
diff --git a/.cirrus.tasks.yml b/.cirrus.tasks.yml
index a22cef063f3..8683d1ae9c7 100644
--- a/.cirrus.tasks.yml
+++ b/.cirrus.tasks.yml
@@ -447,7 +447,7 @@ task:
     # print_stacktraces=1,verbosity=2, duh
     # detect_leaks=0: too many uninteresting leak errors in short-lived binaries
     UBSAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:verbosity=2
-    ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0:detect_stack_use_after_return=0
+    ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0
 
     # SANITIZER_FLAGS is set in the tasks below
     CFLAGS: -Og -ggdb -fno-sanitize-recover=all $SANITIZER_FLAGS
-- 
2.53.0.1.gb2826b52eb


--7e2sypi5gxvkgog6--





^ permalink  raw  reply  [nested|flat] 114+ messages in thread

* [PATCH v2 1/4] Make stack depth check work with asan's use-after-return
@ 2026-02-07 19:04 Andres Freund <[email protected]>
  0 siblings, 0 replies; 114+ messages in thread

From: Andres Freund @ 2026-02-07 19:04 UTC (permalink / raw)

With address sanitizer's stack-use-after-return check, stack variables are
moved to heap allocations, to allow to detect references to the memory at a
later time. That broke our stack-depth check, which is why we had to disable
detect_stack_use_after_return in CI. Luckily __builtin_frame_address() works
correctly, even under asan, so use that.

Author:
Reviewed-by:
Discussion: https://postgr.es/m/
Backpatch:
---
 src/backend/utils/misc/stack_depth.c | 17 ++++++++++++++++-
 .cirrus.tasks.yml                    |  2 +-
 2 files changed, 17 insertions(+), 2 deletions(-)

diff --git a/src/backend/utils/misc/stack_depth.c b/src/backend/utils/misc/stack_depth.c
index 61a07cf824e..9955e28c176 100644
--- a/src/backend/utils/misc/stack_depth.c
+++ b/src/backend/utils/misc/stack_depth.c
@@ -108,13 +108,28 @@ check_stack_depth(void)
 bool
 stack_is_too_deep(void)
 {
+#ifndef HAVE__BUILTIN_FRAME_ADDRESS
 	char		stack_top_loc;
+#endif
 	ssize_t		stack_depth;
+	char	   *stack_address;
+
+	/*
+	 * With address sanitizer's stack-use-after-return check, stack variables
+	 * are moved to heap allocations, to allow to detect references to the
+	 * memory at a later time. That would break our stack-depth check. Luckily
+	 * __builtin_frame_address() works correctly, even under asan.
+	 */
+#ifndef HAVE__BUILTIN_FRAME_ADDRESS
+	stack_address = &stack_top_loc;
+#else
+	stack_address = (char *) __builtin_frame_address(0);
+#endif
 
 	/*
 	 * Compute distance from reference point to my local variables
 	 */
-	stack_depth = (ssize_t) (stack_base_ptr - &stack_top_loc);
+	stack_depth = (ssize_t) (stack_base_ptr - stack_address);
 
 	/*
 	 * Take abs value, since stacks grow up on some machines, down on others
diff --git a/.cirrus.tasks.yml b/.cirrus.tasks.yml
index a22cef063f3..8683d1ae9c7 100644
--- a/.cirrus.tasks.yml
+++ b/.cirrus.tasks.yml
@@ -447,7 +447,7 @@ task:
     # print_stacktraces=1,verbosity=2, duh
     # detect_leaks=0: too many uninteresting leak errors in short-lived binaries
     UBSAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:verbosity=2
-    ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0:detect_stack_use_after_return=0
+    ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0
 
     # SANITIZER_FLAGS is set in the tasks below
     CFLAGS: -Og -ggdb -fno-sanitize-recover=all $SANITIZER_FLAGS
-- 
2.53.0.1.gb2826b52eb


--7e2sypi5gxvkgog6--





^ permalink  raw  reply  [nested|flat] 114+ messages in thread

* [PATCH v2 1/4] Make stack depth check work with asan's use-after-return
@ 2026-02-07 19:04 Andres Freund <[email protected]>
  0 siblings, 0 replies; 114+ messages in thread

From: Andres Freund @ 2026-02-07 19:04 UTC (permalink / raw)

With address sanitizer's stack-use-after-return check, stack variables are
moved to heap allocations, to allow to detect references to the memory at a
later time. That broke our stack-depth check, which is why we had to disable
detect_stack_use_after_return in CI. Luckily __builtin_frame_address() works
correctly, even under asan, so use that.

Author:
Reviewed-by:
Discussion: https://postgr.es/m/
Backpatch:
---
 src/backend/utils/misc/stack_depth.c | 17 ++++++++++++++++-
 .cirrus.tasks.yml                    |  2 +-
 2 files changed, 17 insertions(+), 2 deletions(-)

diff --git a/src/backend/utils/misc/stack_depth.c b/src/backend/utils/misc/stack_depth.c
index 61a07cf824e..9955e28c176 100644
--- a/src/backend/utils/misc/stack_depth.c
+++ b/src/backend/utils/misc/stack_depth.c
@@ -108,13 +108,28 @@ check_stack_depth(void)
 bool
 stack_is_too_deep(void)
 {
+#ifndef HAVE__BUILTIN_FRAME_ADDRESS
 	char		stack_top_loc;
+#endif
 	ssize_t		stack_depth;
+	char	   *stack_address;
+
+	/*
+	 * With address sanitizer's stack-use-after-return check, stack variables
+	 * are moved to heap allocations, to allow to detect references to the
+	 * memory at a later time. That would break our stack-depth check. Luckily
+	 * __builtin_frame_address() works correctly, even under asan.
+	 */
+#ifndef HAVE__BUILTIN_FRAME_ADDRESS
+	stack_address = &stack_top_loc;
+#else
+	stack_address = (char *) __builtin_frame_address(0);
+#endif
 
 	/*
 	 * Compute distance from reference point to my local variables
 	 */
-	stack_depth = (ssize_t) (stack_base_ptr - &stack_top_loc);
+	stack_depth = (ssize_t) (stack_base_ptr - stack_address);
 
 	/*
 	 * Take abs value, since stacks grow up on some machines, down on others
diff --git a/.cirrus.tasks.yml b/.cirrus.tasks.yml
index a22cef063f3..8683d1ae9c7 100644
--- a/.cirrus.tasks.yml
+++ b/.cirrus.tasks.yml
@@ -447,7 +447,7 @@ task:
     # print_stacktraces=1,verbosity=2, duh
     # detect_leaks=0: too many uninteresting leak errors in short-lived binaries
     UBSAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:verbosity=2
-    ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0:detect_stack_use_after_return=0
+    ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0
 
     # SANITIZER_FLAGS is set in the tasks below
     CFLAGS: -Og -ggdb -fno-sanitize-recover=all $SANITIZER_FLAGS
-- 
2.53.0.1.gb2826b52eb


--7e2sypi5gxvkgog6--





^ permalink  raw  reply  [nested|flat] 114+ messages in thread

* [PATCH v2 1/4] Make stack depth check work with asan's use-after-return
@ 2026-02-07 19:04 Andres Freund <[email protected]>
  0 siblings, 0 replies; 114+ messages in thread

From: Andres Freund @ 2026-02-07 19:04 UTC (permalink / raw)

With address sanitizer's stack-use-after-return check, stack variables are
moved to heap allocations, to allow to detect references to the memory at a
later time. That broke our stack-depth check, which is why we had to disable
detect_stack_use_after_return in CI. Luckily __builtin_frame_address() works
correctly, even under asan, so use that.

Author:
Reviewed-by:
Discussion: https://postgr.es/m/
Backpatch:
---
 src/backend/utils/misc/stack_depth.c | 17 ++++++++++++++++-
 .cirrus.tasks.yml                    |  2 +-
 2 files changed, 17 insertions(+), 2 deletions(-)

diff --git a/src/backend/utils/misc/stack_depth.c b/src/backend/utils/misc/stack_depth.c
index 61a07cf824e..9955e28c176 100644
--- a/src/backend/utils/misc/stack_depth.c
+++ b/src/backend/utils/misc/stack_depth.c
@@ -108,13 +108,28 @@ check_stack_depth(void)
 bool
 stack_is_too_deep(void)
 {
+#ifndef HAVE__BUILTIN_FRAME_ADDRESS
 	char		stack_top_loc;
+#endif
 	ssize_t		stack_depth;
+	char	   *stack_address;
+
+	/*
+	 * With address sanitizer's stack-use-after-return check, stack variables
+	 * are moved to heap allocations, to allow to detect references to the
+	 * memory at a later time. That would break our stack-depth check. Luckily
+	 * __builtin_frame_address() works correctly, even under asan.
+	 */
+#ifndef HAVE__BUILTIN_FRAME_ADDRESS
+	stack_address = &stack_top_loc;
+#else
+	stack_address = (char *) __builtin_frame_address(0);
+#endif
 
 	/*
 	 * Compute distance from reference point to my local variables
 	 */
-	stack_depth = (ssize_t) (stack_base_ptr - &stack_top_loc);
+	stack_depth = (ssize_t) (stack_base_ptr - stack_address);
 
 	/*
 	 * Take abs value, since stacks grow up on some machines, down on others
diff --git a/.cirrus.tasks.yml b/.cirrus.tasks.yml
index a22cef063f3..8683d1ae9c7 100644
--- a/.cirrus.tasks.yml
+++ b/.cirrus.tasks.yml
@@ -447,7 +447,7 @@ task:
     # print_stacktraces=1,verbosity=2, duh
     # detect_leaks=0: too many uninteresting leak errors in short-lived binaries
     UBSAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:verbosity=2
-    ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0:detect_stack_use_after_return=0
+    ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0
 
     # SANITIZER_FLAGS is set in the tasks below
     CFLAGS: -Og -ggdb -fno-sanitize-recover=all $SANITIZER_FLAGS
-- 
2.53.0.1.gb2826b52eb


--7e2sypi5gxvkgog6--





^ permalink  raw  reply  [nested|flat] 114+ messages in thread

* [PATCH v2 1/4] Make stack depth check work with asan's use-after-return
@ 2026-02-07 19:04 Andres Freund <[email protected]>
  0 siblings, 0 replies; 114+ messages in thread

From: Andres Freund @ 2026-02-07 19:04 UTC (permalink / raw)

With address sanitizer's stack-use-after-return check, stack variables are
moved to heap allocations, to allow to detect references to the memory at a
later time. That broke our stack-depth check, which is why we had to disable
detect_stack_use_after_return in CI. Luckily __builtin_frame_address() works
correctly, even under asan, so use that.

Author:
Reviewed-by:
Discussion: https://postgr.es/m/
Backpatch:
---
 src/backend/utils/misc/stack_depth.c | 17 ++++++++++++++++-
 .cirrus.tasks.yml                    |  2 +-
 2 files changed, 17 insertions(+), 2 deletions(-)

diff --git a/src/backend/utils/misc/stack_depth.c b/src/backend/utils/misc/stack_depth.c
index 61a07cf824e..9955e28c176 100644
--- a/src/backend/utils/misc/stack_depth.c
+++ b/src/backend/utils/misc/stack_depth.c
@@ -108,13 +108,28 @@ check_stack_depth(void)
 bool
 stack_is_too_deep(void)
 {
+#ifndef HAVE__BUILTIN_FRAME_ADDRESS
 	char		stack_top_loc;
+#endif
 	ssize_t		stack_depth;
+	char	   *stack_address;
+
+	/*
+	 * With address sanitizer's stack-use-after-return check, stack variables
+	 * are moved to heap allocations, to allow to detect references to the
+	 * memory at a later time. That would break our stack-depth check. Luckily
+	 * __builtin_frame_address() works correctly, even under asan.
+	 */
+#ifndef HAVE__BUILTIN_FRAME_ADDRESS
+	stack_address = &stack_top_loc;
+#else
+	stack_address = (char *) __builtin_frame_address(0);
+#endif
 
 	/*
 	 * Compute distance from reference point to my local variables
 	 */
-	stack_depth = (ssize_t) (stack_base_ptr - &stack_top_loc);
+	stack_depth = (ssize_t) (stack_base_ptr - stack_address);
 
 	/*
 	 * Take abs value, since stacks grow up on some machines, down on others
diff --git a/.cirrus.tasks.yml b/.cirrus.tasks.yml
index a22cef063f3..8683d1ae9c7 100644
--- a/.cirrus.tasks.yml
+++ b/.cirrus.tasks.yml
@@ -447,7 +447,7 @@ task:
     # print_stacktraces=1,verbosity=2, duh
     # detect_leaks=0: too many uninteresting leak errors in short-lived binaries
     UBSAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:verbosity=2
-    ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0:detect_stack_use_after_return=0
+    ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0
 
     # SANITIZER_FLAGS is set in the tasks below
     CFLAGS: -Og -ggdb -fno-sanitize-recover=all $SANITIZER_FLAGS
-- 
2.53.0.1.gb2826b52eb


--7e2sypi5gxvkgog6--





^ permalink  raw  reply  [nested|flat] 114+ messages in thread

* [PATCH v2 1/4] Make stack depth check work with asan's use-after-return
@ 2026-02-07 19:04 Andres Freund <[email protected]>
  0 siblings, 0 replies; 114+ messages in thread

From: Andres Freund @ 2026-02-07 19:04 UTC (permalink / raw)

With address sanitizer's stack-use-after-return check, stack variables are
moved to heap allocations, to allow to detect references to the memory at a
later time. That broke our stack-depth check, which is why we had to disable
detect_stack_use_after_return in CI. Luckily __builtin_frame_address() works
correctly, even under asan, so use that.

Author:
Reviewed-by:
Discussion: https://postgr.es/m/
Backpatch:
---
 src/backend/utils/misc/stack_depth.c | 17 ++++++++++++++++-
 .cirrus.tasks.yml                    |  2 +-
 2 files changed, 17 insertions(+), 2 deletions(-)

diff --git a/src/backend/utils/misc/stack_depth.c b/src/backend/utils/misc/stack_depth.c
index 61a07cf824e..9955e28c176 100644
--- a/src/backend/utils/misc/stack_depth.c
+++ b/src/backend/utils/misc/stack_depth.c
@@ -108,13 +108,28 @@ check_stack_depth(void)
 bool
 stack_is_too_deep(void)
 {
+#ifndef HAVE__BUILTIN_FRAME_ADDRESS
 	char		stack_top_loc;
+#endif
 	ssize_t		stack_depth;
+	char	   *stack_address;
+
+	/*
+	 * With address sanitizer's stack-use-after-return check, stack variables
+	 * are moved to heap allocations, to allow to detect references to the
+	 * memory at a later time. That would break our stack-depth check. Luckily
+	 * __builtin_frame_address() works correctly, even under asan.
+	 */
+#ifndef HAVE__BUILTIN_FRAME_ADDRESS
+	stack_address = &stack_top_loc;
+#else
+	stack_address = (char *) __builtin_frame_address(0);
+#endif
 
 	/*
 	 * Compute distance from reference point to my local variables
 	 */
-	stack_depth = (ssize_t) (stack_base_ptr - &stack_top_loc);
+	stack_depth = (ssize_t) (stack_base_ptr - stack_address);
 
 	/*
 	 * Take abs value, since stacks grow up on some machines, down on others
diff --git a/.cirrus.tasks.yml b/.cirrus.tasks.yml
index a22cef063f3..8683d1ae9c7 100644
--- a/.cirrus.tasks.yml
+++ b/.cirrus.tasks.yml
@@ -447,7 +447,7 @@ task:
     # print_stacktraces=1,verbosity=2, duh
     # detect_leaks=0: too many uninteresting leak errors in short-lived binaries
     UBSAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:verbosity=2
-    ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0:detect_stack_use_after_return=0
+    ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0
 
     # SANITIZER_FLAGS is set in the tasks below
     CFLAGS: -Og -ggdb -fno-sanitize-recover=all $SANITIZER_FLAGS
-- 
2.53.0.1.gb2826b52eb


--7e2sypi5gxvkgog6--





^ permalink  raw  reply  [nested|flat] 114+ messages in thread

* [PATCH v2 1/4] Make stack depth check work with asan's use-after-return
@ 2026-02-07 19:04 Andres Freund <[email protected]>
  0 siblings, 0 replies; 114+ messages in thread

From: Andres Freund @ 2026-02-07 19:04 UTC (permalink / raw)

With address sanitizer's stack-use-after-return check, stack variables are
moved to heap allocations, to allow to detect references to the memory at a
later time. That broke our stack-depth check, which is why we had to disable
detect_stack_use_after_return in CI. Luckily __builtin_frame_address() works
correctly, even under asan, so use that.

Author:
Reviewed-by:
Discussion: https://postgr.es/m/
Backpatch:
---
 src/backend/utils/misc/stack_depth.c | 17 ++++++++++++++++-
 .cirrus.tasks.yml                    |  2 +-
 2 files changed, 17 insertions(+), 2 deletions(-)

diff --git a/src/backend/utils/misc/stack_depth.c b/src/backend/utils/misc/stack_depth.c
index 61a07cf824e..9955e28c176 100644
--- a/src/backend/utils/misc/stack_depth.c
+++ b/src/backend/utils/misc/stack_depth.c
@@ -108,13 +108,28 @@ check_stack_depth(void)
 bool
 stack_is_too_deep(void)
 {
+#ifndef HAVE__BUILTIN_FRAME_ADDRESS
 	char		stack_top_loc;
+#endif
 	ssize_t		stack_depth;
+	char	   *stack_address;
+
+	/*
+	 * With address sanitizer's stack-use-after-return check, stack variables
+	 * are moved to heap allocations, to allow to detect references to the
+	 * memory at a later time. That would break our stack-depth check. Luckily
+	 * __builtin_frame_address() works correctly, even under asan.
+	 */
+#ifndef HAVE__BUILTIN_FRAME_ADDRESS
+	stack_address = &stack_top_loc;
+#else
+	stack_address = (char *) __builtin_frame_address(0);
+#endif
 
 	/*
 	 * Compute distance from reference point to my local variables
 	 */
-	stack_depth = (ssize_t) (stack_base_ptr - &stack_top_loc);
+	stack_depth = (ssize_t) (stack_base_ptr - stack_address);
 
 	/*
 	 * Take abs value, since stacks grow up on some machines, down on others
diff --git a/.cirrus.tasks.yml b/.cirrus.tasks.yml
index a22cef063f3..8683d1ae9c7 100644
--- a/.cirrus.tasks.yml
+++ b/.cirrus.tasks.yml
@@ -447,7 +447,7 @@ task:
     # print_stacktraces=1,verbosity=2, duh
     # detect_leaks=0: too many uninteresting leak errors in short-lived binaries
     UBSAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:verbosity=2
-    ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0:detect_stack_use_after_return=0
+    ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0
 
     # SANITIZER_FLAGS is set in the tasks below
     CFLAGS: -Og -ggdb -fno-sanitize-recover=all $SANITIZER_FLAGS
-- 
2.53.0.1.gb2826b52eb


--7e2sypi5gxvkgog6--





^ permalink  raw  reply  [nested|flat] 114+ messages in thread

* [PATCH v2 1/4] Make stack depth check work with asan's use-after-return
@ 2026-02-07 19:04 Andres Freund <[email protected]>
  0 siblings, 0 replies; 114+ messages in thread

From: Andres Freund @ 2026-02-07 19:04 UTC (permalink / raw)

With address sanitizer's stack-use-after-return check, stack variables are
moved to heap allocations, to allow to detect references to the memory at a
later time. That broke our stack-depth check, which is why we had to disable
detect_stack_use_after_return in CI. Luckily __builtin_frame_address() works
correctly, even under asan, so use that.

Author:
Reviewed-by:
Discussion: https://postgr.es/m/
Backpatch:
---
 src/backend/utils/misc/stack_depth.c | 17 ++++++++++++++++-
 .cirrus.tasks.yml                    |  2 +-
 2 files changed, 17 insertions(+), 2 deletions(-)

diff --git a/src/backend/utils/misc/stack_depth.c b/src/backend/utils/misc/stack_depth.c
index 61a07cf824e..9955e28c176 100644
--- a/src/backend/utils/misc/stack_depth.c
+++ b/src/backend/utils/misc/stack_depth.c
@@ -108,13 +108,28 @@ check_stack_depth(void)
 bool
 stack_is_too_deep(void)
 {
+#ifndef HAVE__BUILTIN_FRAME_ADDRESS
 	char		stack_top_loc;
+#endif
 	ssize_t		stack_depth;
+	char	   *stack_address;
+
+	/*
+	 * With address sanitizer's stack-use-after-return check, stack variables
+	 * are moved to heap allocations, to allow to detect references to the
+	 * memory at a later time. That would break our stack-depth check. Luckily
+	 * __builtin_frame_address() works correctly, even under asan.
+	 */
+#ifndef HAVE__BUILTIN_FRAME_ADDRESS
+	stack_address = &stack_top_loc;
+#else
+	stack_address = (char *) __builtin_frame_address(0);
+#endif
 
 	/*
 	 * Compute distance from reference point to my local variables
 	 */
-	stack_depth = (ssize_t) (stack_base_ptr - &stack_top_loc);
+	stack_depth = (ssize_t) (stack_base_ptr - stack_address);
 
 	/*
 	 * Take abs value, since stacks grow up on some machines, down on others
diff --git a/.cirrus.tasks.yml b/.cirrus.tasks.yml
index a22cef063f3..8683d1ae9c7 100644
--- a/.cirrus.tasks.yml
+++ b/.cirrus.tasks.yml
@@ -447,7 +447,7 @@ task:
     # print_stacktraces=1,verbosity=2, duh
     # detect_leaks=0: too many uninteresting leak errors in short-lived binaries
     UBSAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:verbosity=2
-    ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0:detect_stack_use_after_return=0
+    ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0
 
     # SANITIZER_FLAGS is set in the tasks below
     CFLAGS: -Og -ggdb -fno-sanitize-recover=all $SANITIZER_FLAGS
-- 
2.53.0.1.gb2826b52eb


--7e2sypi5gxvkgog6--





^ permalink  raw  reply  [nested|flat] 114+ messages in thread

* [PATCH v2 1/4] Make stack depth check work with asan's use-after-return
@ 2026-02-07 19:04 Andres Freund <[email protected]>
  0 siblings, 0 replies; 114+ messages in thread

From: Andres Freund @ 2026-02-07 19:04 UTC (permalink / raw)

With address sanitizer's stack-use-after-return check, stack variables are
moved to heap allocations, to allow to detect references to the memory at a
later time. That broke our stack-depth check, which is why we had to disable
detect_stack_use_after_return in CI. Luckily __builtin_frame_address() works
correctly, even under asan, so use that.

Author:
Reviewed-by:
Discussion: https://postgr.es/m/
Backpatch:
---
 src/backend/utils/misc/stack_depth.c | 17 ++++++++++++++++-
 .cirrus.tasks.yml                    |  2 +-
 2 files changed, 17 insertions(+), 2 deletions(-)

diff --git a/src/backend/utils/misc/stack_depth.c b/src/backend/utils/misc/stack_depth.c
index 61a07cf824e..9955e28c176 100644
--- a/src/backend/utils/misc/stack_depth.c
+++ b/src/backend/utils/misc/stack_depth.c
@@ -108,13 +108,28 @@ check_stack_depth(void)
 bool
 stack_is_too_deep(void)
 {
+#ifndef HAVE__BUILTIN_FRAME_ADDRESS
 	char		stack_top_loc;
+#endif
 	ssize_t		stack_depth;
+	char	   *stack_address;
+
+	/*
+	 * With address sanitizer's stack-use-after-return check, stack variables
+	 * are moved to heap allocations, to allow to detect references to the
+	 * memory at a later time. That would break our stack-depth check. Luckily
+	 * __builtin_frame_address() works correctly, even under asan.
+	 */
+#ifndef HAVE__BUILTIN_FRAME_ADDRESS
+	stack_address = &stack_top_loc;
+#else
+	stack_address = (char *) __builtin_frame_address(0);
+#endif
 
 	/*
 	 * Compute distance from reference point to my local variables
 	 */
-	stack_depth = (ssize_t) (stack_base_ptr - &stack_top_loc);
+	stack_depth = (ssize_t) (stack_base_ptr - stack_address);
 
 	/*
 	 * Take abs value, since stacks grow up on some machines, down on others
diff --git a/.cirrus.tasks.yml b/.cirrus.tasks.yml
index a22cef063f3..8683d1ae9c7 100644
--- a/.cirrus.tasks.yml
+++ b/.cirrus.tasks.yml
@@ -447,7 +447,7 @@ task:
     # print_stacktraces=1,verbosity=2, duh
     # detect_leaks=0: too many uninteresting leak errors in short-lived binaries
     UBSAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:verbosity=2
-    ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0:detect_stack_use_after_return=0
+    ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0
 
     # SANITIZER_FLAGS is set in the tasks below
     CFLAGS: -Og -ggdb -fno-sanitize-recover=all $SANITIZER_FLAGS
-- 
2.53.0.1.gb2826b52eb


--7e2sypi5gxvkgog6--





^ permalink  raw  reply  [nested|flat] 114+ messages in thread

* [PATCH v2 1/4] Make stack depth check work with asan's use-after-return
@ 2026-02-07 19:04 Andres Freund <[email protected]>
  0 siblings, 0 replies; 114+ messages in thread

From: Andres Freund @ 2026-02-07 19:04 UTC (permalink / raw)

With address sanitizer's stack-use-after-return check, stack variables are
moved to heap allocations, to allow to detect references to the memory at a
later time. That broke our stack-depth check, which is why we had to disable
detect_stack_use_after_return in CI. Luckily __builtin_frame_address() works
correctly, even under asan, so use that.

Author:
Reviewed-by:
Discussion: https://postgr.es/m/
Backpatch:
---
 src/backend/utils/misc/stack_depth.c | 17 ++++++++++++++++-
 .cirrus.tasks.yml                    |  2 +-
 2 files changed, 17 insertions(+), 2 deletions(-)

diff --git a/src/backend/utils/misc/stack_depth.c b/src/backend/utils/misc/stack_depth.c
index 61a07cf824e..9955e28c176 100644
--- a/src/backend/utils/misc/stack_depth.c
+++ b/src/backend/utils/misc/stack_depth.c
@@ -108,13 +108,28 @@ check_stack_depth(void)
 bool
 stack_is_too_deep(void)
 {
+#ifndef HAVE__BUILTIN_FRAME_ADDRESS
 	char		stack_top_loc;
+#endif
 	ssize_t		stack_depth;
+	char	   *stack_address;
+
+	/*
+	 * With address sanitizer's stack-use-after-return check, stack variables
+	 * are moved to heap allocations, to allow to detect references to the
+	 * memory at a later time. That would break our stack-depth check. Luckily
+	 * __builtin_frame_address() works correctly, even under asan.
+	 */
+#ifndef HAVE__BUILTIN_FRAME_ADDRESS
+	stack_address = &stack_top_loc;
+#else
+	stack_address = (char *) __builtin_frame_address(0);
+#endif
 
 	/*
 	 * Compute distance from reference point to my local variables
 	 */
-	stack_depth = (ssize_t) (stack_base_ptr - &stack_top_loc);
+	stack_depth = (ssize_t) (stack_base_ptr - stack_address);
 
 	/*
 	 * Take abs value, since stacks grow up on some machines, down on others
diff --git a/.cirrus.tasks.yml b/.cirrus.tasks.yml
index a22cef063f3..8683d1ae9c7 100644
--- a/.cirrus.tasks.yml
+++ b/.cirrus.tasks.yml
@@ -447,7 +447,7 @@ task:
     # print_stacktraces=1,verbosity=2, duh
     # detect_leaks=0: too many uninteresting leak errors in short-lived binaries
     UBSAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:verbosity=2
-    ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0:detect_stack_use_after_return=0
+    ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0
 
     # SANITIZER_FLAGS is set in the tasks below
     CFLAGS: -Og -ggdb -fno-sanitize-recover=all $SANITIZER_FLAGS
-- 
2.53.0.1.gb2826b52eb


--7e2sypi5gxvkgog6--





^ permalink  raw  reply  [nested|flat] 114+ messages in thread

* [PATCH v2 1/4] Make stack depth check work with asan's use-after-return
@ 2026-02-07 19:04 Andres Freund <[email protected]>
  0 siblings, 0 replies; 114+ messages in thread

From: Andres Freund @ 2026-02-07 19:04 UTC (permalink / raw)

With address sanitizer's stack-use-after-return check, stack variables are
moved to heap allocations, to allow to detect references to the memory at a
later time. That broke our stack-depth check, which is why we had to disable
detect_stack_use_after_return in CI. Luckily __builtin_frame_address() works
correctly, even under asan, so use that.

Author:
Reviewed-by:
Discussion: https://postgr.es/m/
Backpatch:
---
 src/backend/utils/misc/stack_depth.c | 17 ++++++++++++++++-
 .cirrus.tasks.yml                    |  2 +-
 2 files changed, 17 insertions(+), 2 deletions(-)

diff --git a/src/backend/utils/misc/stack_depth.c b/src/backend/utils/misc/stack_depth.c
index 61a07cf824e..9955e28c176 100644
--- a/src/backend/utils/misc/stack_depth.c
+++ b/src/backend/utils/misc/stack_depth.c
@@ -108,13 +108,28 @@ check_stack_depth(void)
 bool
 stack_is_too_deep(void)
 {
+#ifndef HAVE__BUILTIN_FRAME_ADDRESS
 	char		stack_top_loc;
+#endif
 	ssize_t		stack_depth;
+	char	   *stack_address;
+
+	/*
+	 * With address sanitizer's stack-use-after-return check, stack variables
+	 * are moved to heap allocations, to allow to detect references to the
+	 * memory at a later time. That would break our stack-depth check. Luckily
+	 * __builtin_frame_address() works correctly, even under asan.
+	 */
+#ifndef HAVE__BUILTIN_FRAME_ADDRESS
+	stack_address = &stack_top_loc;
+#else
+	stack_address = (char *) __builtin_frame_address(0);
+#endif
 
 	/*
 	 * Compute distance from reference point to my local variables
 	 */
-	stack_depth = (ssize_t) (stack_base_ptr - &stack_top_loc);
+	stack_depth = (ssize_t) (stack_base_ptr - stack_address);
 
 	/*
 	 * Take abs value, since stacks grow up on some machines, down on others
diff --git a/.cirrus.tasks.yml b/.cirrus.tasks.yml
index a22cef063f3..8683d1ae9c7 100644
--- a/.cirrus.tasks.yml
+++ b/.cirrus.tasks.yml
@@ -447,7 +447,7 @@ task:
     # print_stacktraces=1,verbosity=2, duh
     # detect_leaks=0: too many uninteresting leak errors in short-lived binaries
     UBSAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:verbosity=2
-    ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0:detect_stack_use_after_return=0
+    ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0
 
     # SANITIZER_FLAGS is set in the tasks below
     CFLAGS: -Og -ggdb -fno-sanitize-recover=all $SANITIZER_FLAGS
-- 
2.53.0.1.gb2826b52eb


--7e2sypi5gxvkgog6--





^ permalink  raw  reply  [nested|flat] 114+ messages in thread

* [PATCH v2 1/4] Make stack depth check work with asan's use-after-return
@ 2026-02-07 19:04 Andres Freund <[email protected]>
  0 siblings, 0 replies; 114+ messages in thread

From: Andres Freund @ 2026-02-07 19:04 UTC (permalink / raw)

With address sanitizer's stack-use-after-return check, stack variables are
moved to heap allocations, to allow to detect references to the memory at a
later time. That broke our stack-depth check, which is why we had to disable
detect_stack_use_after_return in CI. Luckily __builtin_frame_address() works
correctly, even under asan, so use that.

Author:
Reviewed-by:
Discussion: https://postgr.es/m/
Backpatch:
---
 src/backend/utils/misc/stack_depth.c | 17 ++++++++++++++++-
 .cirrus.tasks.yml                    |  2 +-
 2 files changed, 17 insertions(+), 2 deletions(-)

diff --git a/src/backend/utils/misc/stack_depth.c b/src/backend/utils/misc/stack_depth.c
index 61a07cf824e..9955e28c176 100644
--- a/src/backend/utils/misc/stack_depth.c
+++ b/src/backend/utils/misc/stack_depth.c
@@ -108,13 +108,28 @@ check_stack_depth(void)
 bool
 stack_is_too_deep(void)
 {
+#ifndef HAVE__BUILTIN_FRAME_ADDRESS
 	char		stack_top_loc;
+#endif
 	ssize_t		stack_depth;
+	char	   *stack_address;
+
+	/*
+	 * With address sanitizer's stack-use-after-return check, stack variables
+	 * are moved to heap allocations, to allow to detect references to the
+	 * memory at a later time. That would break our stack-depth check. Luckily
+	 * __builtin_frame_address() works correctly, even under asan.
+	 */
+#ifndef HAVE__BUILTIN_FRAME_ADDRESS
+	stack_address = &stack_top_loc;
+#else
+	stack_address = (char *) __builtin_frame_address(0);
+#endif
 
 	/*
 	 * Compute distance from reference point to my local variables
 	 */
-	stack_depth = (ssize_t) (stack_base_ptr - &stack_top_loc);
+	stack_depth = (ssize_t) (stack_base_ptr - stack_address);
 
 	/*
 	 * Take abs value, since stacks grow up on some machines, down on others
diff --git a/.cirrus.tasks.yml b/.cirrus.tasks.yml
index a22cef063f3..8683d1ae9c7 100644
--- a/.cirrus.tasks.yml
+++ b/.cirrus.tasks.yml
@@ -447,7 +447,7 @@ task:
     # print_stacktraces=1,verbosity=2, duh
     # detect_leaks=0: too many uninteresting leak errors in short-lived binaries
     UBSAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:verbosity=2
-    ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0:detect_stack_use_after_return=0
+    ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0
 
     # SANITIZER_FLAGS is set in the tasks below
     CFLAGS: -Og -ggdb -fno-sanitize-recover=all $SANITIZER_FLAGS
-- 
2.53.0.1.gb2826b52eb


--7e2sypi5gxvkgog6--





^ permalink  raw  reply  [nested|flat] 114+ messages in thread

* [PATCH v2 1/4] Make stack depth check work with asan's use-after-return
@ 2026-02-07 19:04 Andres Freund <[email protected]>
  0 siblings, 0 replies; 114+ messages in thread

From: Andres Freund @ 2026-02-07 19:04 UTC (permalink / raw)

With address sanitizer's stack-use-after-return check, stack variables are
moved to heap allocations, to allow to detect references to the memory at a
later time. That broke our stack-depth check, which is why we had to disable
detect_stack_use_after_return in CI. Luckily __builtin_frame_address() works
correctly, even under asan, so use that.

Author:
Reviewed-by:
Discussion: https://postgr.es/m/
Backpatch:
---
 src/backend/utils/misc/stack_depth.c | 17 ++++++++++++++++-
 .cirrus.tasks.yml                    |  2 +-
 2 files changed, 17 insertions(+), 2 deletions(-)

diff --git a/src/backend/utils/misc/stack_depth.c b/src/backend/utils/misc/stack_depth.c
index 61a07cf824e..9955e28c176 100644
--- a/src/backend/utils/misc/stack_depth.c
+++ b/src/backend/utils/misc/stack_depth.c
@@ -108,13 +108,28 @@ check_stack_depth(void)
 bool
 stack_is_too_deep(void)
 {
+#ifndef HAVE__BUILTIN_FRAME_ADDRESS
 	char		stack_top_loc;
+#endif
 	ssize_t		stack_depth;
+	char	   *stack_address;
+
+	/*
+	 * With address sanitizer's stack-use-after-return check, stack variables
+	 * are moved to heap allocations, to allow to detect references to the
+	 * memory at a later time. That would break our stack-depth check. Luckily
+	 * __builtin_frame_address() works correctly, even under asan.
+	 */
+#ifndef HAVE__BUILTIN_FRAME_ADDRESS
+	stack_address = &stack_top_loc;
+#else
+	stack_address = (char *) __builtin_frame_address(0);
+#endif
 
 	/*
 	 * Compute distance from reference point to my local variables
 	 */
-	stack_depth = (ssize_t) (stack_base_ptr - &stack_top_loc);
+	stack_depth = (ssize_t) (stack_base_ptr - stack_address);
 
 	/*
 	 * Take abs value, since stacks grow up on some machines, down on others
diff --git a/.cirrus.tasks.yml b/.cirrus.tasks.yml
index a22cef063f3..8683d1ae9c7 100644
--- a/.cirrus.tasks.yml
+++ b/.cirrus.tasks.yml
@@ -447,7 +447,7 @@ task:
     # print_stacktraces=1,verbosity=2, duh
     # detect_leaks=0: too many uninteresting leak errors in short-lived binaries
     UBSAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:verbosity=2
-    ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0:detect_stack_use_after_return=0
+    ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0
 
     # SANITIZER_FLAGS is set in the tasks below
     CFLAGS: -Og -ggdb -fno-sanitize-recover=all $SANITIZER_FLAGS
-- 
2.53.0.1.gb2826b52eb


--7e2sypi5gxvkgog6--





^ permalink  raw  reply  [nested|flat] 114+ messages in thread

* [PATCH v2 1/4] Make stack depth check work with asan's use-after-return
@ 2026-02-07 19:04 Andres Freund <[email protected]>
  0 siblings, 0 replies; 114+ messages in thread

From: Andres Freund @ 2026-02-07 19:04 UTC (permalink / raw)

With address sanitizer's stack-use-after-return check, stack variables are
moved to heap allocations, to allow to detect references to the memory at a
later time. That broke our stack-depth check, which is why we had to disable
detect_stack_use_after_return in CI. Luckily __builtin_frame_address() works
correctly, even under asan, so use that.

Author:
Reviewed-by:
Discussion: https://postgr.es/m/
Backpatch:
---
 src/backend/utils/misc/stack_depth.c | 17 ++++++++++++++++-
 .cirrus.tasks.yml                    |  2 +-
 2 files changed, 17 insertions(+), 2 deletions(-)

diff --git a/src/backend/utils/misc/stack_depth.c b/src/backend/utils/misc/stack_depth.c
index 61a07cf824e..9955e28c176 100644
--- a/src/backend/utils/misc/stack_depth.c
+++ b/src/backend/utils/misc/stack_depth.c
@@ -108,13 +108,28 @@ check_stack_depth(void)
 bool
 stack_is_too_deep(void)
 {
+#ifndef HAVE__BUILTIN_FRAME_ADDRESS
 	char		stack_top_loc;
+#endif
 	ssize_t		stack_depth;
+	char	   *stack_address;
+
+	/*
+	 * With address sanitizer's stack-use-after-return check, stack variables
+	 * are moved to heap allocations, to allow to detect references to the
+	 * memory at a later time. That would break our stack-depth check. Luckily
+	 * __builtin_frame_address() works correctly, even under asan.
+	 */
+#ifndef HAVE__BUILTIN_FRAME_ADDRESS
+	stack_address = &stack_top_loc;
+#else
+	stack_address = (char *) __builtin_frame_address(0);
+#endif
 
 	/*
 	 * Compute distance from reference point to my local variables
 	 */
-	stack_depth = (ssize_t) (stack_base_ptr - &stack_top_loc);
+	stack_depth = (ssize_t) (stack_base_ptr - stack_address);
 
 	/*
 	 * Take abs value, since stacks grow up on some machines, down on others
diff --git a/.cirrus.tasks.yml b/.cirrus.tasks.yml
index a22cef063f3..8683d1ae9c7 100644
--- a/.cirrus.tasks.yml
+++ b/.cirrus.tasks.yml
@@ -447,7 +447,7 @@ task:
     # print_stacktraces=1,verbosity=2, duh
     # detect_leaks=0: too many uninteresting leak errors in short-lived binaries
     UBSAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:verbosity=2
-    ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0:detect_stack_use_after_return=0
+    ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0
 
     # SANITIZER_FLAGS is set in the tasks below
     CFLAGS: -Og -ggdb -fno-sanitize-recover=all $SANITIZER_FLAGS
-- 
2.53.0.1.gb2826b52eb


--7e2sypi5gxvkgog6--





^ permalink  raw  reply  [nested|flat] 114+ messages in thread

* [PATCH v2 1/4] Make stack depth check work with asan's use-after-return
@ 2026-02-07 19:04 Andres Freund <[email protected]>
  0 siblings, 0 replies; 114+ messages in thread

From: Andres Freund @ 2026-02-07 19:04 UTC (permalink / raw)

With address sanitizer's stack-use-after-return check, stack variables are
moved to heap allocations, to allow to detect references to the memory at a
later time. That broke our stack-depth check, which is why we had to disable
detect_stack_use_after_return in CI. Luckily __builtin_frame_address() works
correctly, even under asan, so use that.

Author:
Reviewed-by:
Discussion: https://postgr.es/m/
Backpatch:
---
 src/backend/utils/misc/stack_depth.c | 17 ++++++++++++++++-
 .cirrus.tasks.yml                    |  2 +-
 2 files changed, 17 insertions(+), 2 deletions(-)

diff --git a/src/backend/utils/misc/stack_depth.c b/src/backend/utils/misc/stack_depth.c
index 61a07cf824e..9955e28c176 100644
--- a/src/backend/utils/misc/stack_depth.c
+++ b/src/backend/utils/misc/stack_depth.c
@@ -108,13 +108,28 @@ check_stack_depth(void)
 bool
 stack_is_too_deep(void)
 {
+#ifndef HAVE__BUILTIN_FRAME_ADDRESS
 	char		stack_top_loc;
+#endif
 	ssize_t		stack_depth;
+	char	   *stack_address;
+
+	/*
+	 * With address sanitizer's stack-use-after-return check, stack variables
+	 * are moved to heap allocations, to allow to detect references to the
+	 * memory at a later time. That would break our stack-depth check. Luckily
+	 * __builtin_frame_address() works correctly, even under asan.
+	 */
+#ifndef HAVE__BUILTIN_FRAME_ADDRESS
+	stack_address = &stack_top_loc;
+#else
+	stack_address = (char *) __builtin_frame_address(0);
+#endif
 
 	/*
 	 * Compute distance from reference point to my local variables
 	 */
-	stack_depth = (ssize_t) (stack_base_ptr - &stack_top_loc);
+	stack_depth = (ssize_t) (stack_base_ptr - stack_address);
 
 	/*
 	 * Take abs value, since stacks grow up on some machines, down on others
diff --git a/.cirrus.tasks.yml b/.cirrus.tasks.yml
index a22cef063f3..8683d1ae9c7 100644
--- a/.cirrus.tasks.yml
+++ b/.cirrus.tasks.yml
@@ -447,7 +447,7 @@ task:
     # print_stacktraces=1,verbosity=2, duh
     # detect_leaks=0: too many uninteresting leak errors in short-lived binaries
     UBSAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:verbosity=2
-    ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0:detect_stack_use_after_return=0
+    ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0
 
     # SANITIZER_FLAGS is set in the tasks below
     CFLAGS: -Og -ggdb -fno-sanitize-recover=all $SANITIZER_FLAGS
-- 
2.53.0.1.gb2826b52eb


--7e2sypi5gxvkgog6--





^ permalink  raw  reply  [nested|flat] 114+ messages in thread

* [PATCH v2 1/4] Make stack depth check work with asan's use-after-return
@ 2026-02-07 19:04 Andres Freund <[email protected]>
  0 siblings, 0 replies; 114+ messages in thread

From: Andres Freund @ 2026-02-07 19:04 UTC (permalink / raw)

With address sanitizer's stack-use-after-return check, stack variables are
moved to heap allocations, to allow to detect references to the memory at a
later time. That broke our stack-depth check, which is why we had to disable
detect_stack_use_after_return in CI. Luckily __builtin_frame_address() works
correctly, even under asan, so use that.

Author:
Reviewed-by:
Discussion: https://postgr.es/m/
Backpatch:
---
 src/backend/utils/misc/stack_depth.c | 17 ++++++++++++++++-
 .cirrus.tasks.yml                    |  2 +-
 2 files changed, 17 insertions(+), 2 deletions(-)

diff --git a/src/backend/utils/misc/stack_depth.c b/src/backend/utils/misc/stack_depth.c
index 61a07cf824e..9955e28c176 100644
--- a/src/backend/utils/misc/stack_depth.c
+++ b/src/backend/utils/misc/stack_depth.c
@@ -108,13 +108,28 @@ check_stack_depth(void)
 bool
 stack_is_too_deep(void)
 {
+#ifndef HAVE__BUILTIN_FRAME_ADDRESS
 	char		stack_top_loc;
+#endif
 	ssize_t		stack_depth;
+	char	   *stack_address;
+
+	/*
+	 * With address sanitizer's stack-use-after-return check, stack variables
+	 * are moved to heap allocations, to allow to detect references to the
+	 * memory at a later time. That would break our stack-depth check. Luckily
+	 * __builtin_frame_address() works correctly, even under asan.
+	 */
+#ifndef HAVE__BUILTIN_FRAME_ADDRESS
+	stack_address = &stack_top_loc;
+#else
+	stack_address = (char *) __builtin_frame_address(0);
+#endif
 
 	/*
 	 * Compute distance from reference point to my local variables
 	 */
-	stack_depth = (ssize_t) (stack_base_ptr - &stack_top_loc);
+	stack_depth = (ssize_t) (stack_base_ptr - stack_address);
 
 	/*
 	 * Take abs value, since stacks grow up on some machines, down on others
diff --git a/.cirrus.tasks.yml b/.cirrus.tasks.yml
index a22cef063f3..8683d1ae9c7 100644
--- a/.cirrus.tasks.yml
+++ b/.cirrus.tasks.yml
@@ -447,7 +447,7 @@ task:
     # print_stacktraces=1,verbosity=2, duh
     # detect_leaks=0: too many uninteresting leak errors in short-lived binaries
     UBSAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:verbosity=2
-    ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0:detect_stack_use_after_return=0
+    ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0
 
     # SANITIZER_FLAGS is set in the tasks below
     CFLAGS: -Og -ggdb -fno-sanitize-recover=all $SANITIZER_FLAGS
-- 
2.53.0.1.gb2826b52eb


--7e2sypi5gxvkgog6--





^ permalink  raw  reply  [nested|flat] 114+ messages in thread

* [PATCH v2 1/4] Make stack depth check work with asan's use-after-return
@ 2026-02-07 19:04 Andres Freund <[email protected]>
  0 siblings, 0 replies; 114+ messages in thread

From: Andres Freund @ 2026-02-07 19:04 UTC (permalink / raw)

With address sanitizer's stack-use-after-return check, stack variables are
moved to heap allocations, to allow to detect references to the memory at a
later time. That broke our stack-depth check, which is why we had to disable
detect_stack_use_after_return in CI. Luckily __builtin_frame_address() works
correctly, even under asan, so use that.

Author:
Reviewed-by:
Discussion: https://postgr.es/m/
Backpatch:
---
 src/backend/utils/misc/stack_depth.c | 17 ++++++++++++++++-
 .cirrus.tasks.yml                    |  2 +-
 2 files changed, 17 insertions(+), 2 deletions(-)

diff --git a/src/backend/utils/misc/stack_depth.c b/src/backend/utils/misc/stack_depth.c
index 61a07cf824e..9955e28c176 100644
--- a/src/backend/utils/misc/stack_depth.c
+++ b/src/backend/utils/misc/stack_depth.c
@@ -108,13 +108,28 @@ check_stack_depth(void)
 bool
 stack_is_too_deep(void)
 {
+#ifndef HAVE__BUILTIN_FRAME_ADDRESS
 	char		stack_top_loc;
+#endif
 	ssize_t		stack_depth;
+	char	   *stack_address;
+
+	/*
+	 * With address sanitizer's stack-use-after-return check, stack variables
+	 * are moved to heap allocations, to allow to detect references to the
+	 * memory at a later time. That would break our stack-depth check. Luckily
+	 * __builtin_frame_address() works correctly, even under asan.
+	 */
+#ifndef HAVE__BUILTIN_FRAME_ADDRESS
+	stack_address = &stack_top_loc;
+#else
+	stack_address = (char *) __builtin_frame_address(0);
+#endif
 
 	/*
 	 * Compute distance from reference point to my local variables
 	 */
-	stack_depth = (ssize_t) (stack_base_ptr - &stack_top_loc);
+	stack_depth = (ssize_t) (stack_base_ptr - stack_address);
 
 	/*
 	 * Take abs value, since stacks grow up on some machines, down on others
diff --git a/.cirrus.tasks.yml b/.cirrus.tasks.yml
index a22cef063f3..8683d1ae9c7 100644
--- a/.cirrus.tasks.yml
+++ b/.cirrus.tasks.yml
@@ -447,7 +447,7 @@ task:
     # print_stacktraces=1,verbosity=2, duh
     # detect_leaks=0: too many uninteresting leak errors in short-lived binaries
     UBSAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:verbosity=2
-    ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0:detect_stack_use_after_return=0
+    ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0
 
     # SANITIZER_FLAGS is set in the tasks below
     CFLAGS: -Og -ggdb -fno-sanitize-recover=all $SANITIZER_FLAGS
-- 
2.53.0.1.gb2826b52eb


--7e2sypi5gxvkgog6--





^ permalink  raw  reply  [nested|flat] 114+ messages in thread

* [PATCH v2 1/4] Make stack depth check work with asan's use-after-return
@ 2026-02-07 19:04 Andres Freund <[email protected]>
  0 siblings, 0 replies; 114+ messages in thread

From: Andres Freund @ 2026-02-07 19:04 UTC (permalink / raw)

With address sanitizer's stack-use-after-return check, stack variables are
moved to heap allocations, to allow to detect references to the memory at a
later time. That broke our stack-depth check, which is why we had to disable
detect_stack_use_after_return in CI. Luckily __builtin_frame_address() works
correctly, even under asan, so use that.

Author:
Reviewed-by:
Discussion: https://postgr.es/m/
Backpatch:
---
 src/backend/utils/misc/stack_depth.c | 17 ++++++++++++++++-
 .cirrus.tasks.yml                    |  2 +-
 2 files changed, 17 insertions(+), 2 deletions(-)

diff --git a/src/backend/utils/misc/stack_depth.c b/src/backend/utils/misc/stack_depth.c
index 61a07cf824e..9955e28c176 100644
--- a/src/backend/utils/misc/stack_depth.c
+++ b/src/backend/utils/misc/stack_depth.c
@@ -108,13 +108,28 @@ check_stack_depth(void)
 bool
 stack_is_too_deep(void)
 {
+#ifndef HAVE__BUILTIN_FRAME_ADDRESS
 	char		stack_top_loc;
+#endif
 	ssize_t		stack_depth;
+	char	   *stack_address;
+
+	/*
+	 * With address sanitizer's stack-use-after-return check, stack variables
+	 * are moved to heap allocations, to allow to detect references to the
+	 * memory at a later time. That would break our stack-depth check. Luckily
+	 * __builtin_frame_address() works correctly, even under asan.
+	 */
+#ifndef HAVE__BUILTIN_FRAME_ADDRESS
+	stack_address = &stack_top_loc;
+#else
+	stack_address = (char *) __builtin_frame_address(0);
+#endif
 
 	/*
 	 * Compute distance from reference point to my local variables
 	 */
-	stack_depth = (ssize_t) (stack_base_ptr - &stack_top_loc);
+	stack_depth = (ssize_t) (stack_base_ptr - stack_address);
 
 	/*
 	 * Take abs value, since stacks grow up on some machines, down on others
diff --git a/.cirrus.tasks.yml b/.cirrus.tasks.yml
index a22cef063f3..8683d1ae9c7 100644
--- a/.cirrus.tasks.yml
+++ b/.cirrus.tasks.yml
@@ -447,7 +447,7 @@ task:
     # print_stacktraces=1,verbosity=2, duh
     # detect_leaks=0: too many uninteresting leak errors in short-lived binaries
     UBSAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:verbosity=2
-    ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0:detect_stack_use_after_return=0
+    ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0
 
     # SANITIZER_FLAGS is set in the tasks below
     CFLAGS: -Og -ggdb -fno-sanitize-recover=all $SANITIZER_FLAGS
-- 
2.53.0.1.gb2826b52eb


--7e2sypi5gxvkgog6--





^ permalink  raw  reply  [nested|flat] 114+ messages in thread

* [PATCH v2 1/4] Make stack depth check work with asan's use-after-return
@ 2026-02-07 19:04 Andres Freund <[email protected]>
  0 siblings, 0 replies; 114+ messages in thread

From: Andres Freund @ 2026-02-07 19:04 UTC (permalink / raw)

With address sanitizer's stack-use-after-return check, stack variables are
moved to heap allocations, to allow to detect references to the memory at a
later time. That broke our stack-depth check, which is why we had to disable
detect_stack_use_after_return in CI. Luckily __builtin_frame_address() works
correctly, even under asan, so use that.

Author:
Reviewed-by:
Discussion: https://postgr.es/m/
Backpatch:
---
 src/backend/utils/misc/stack_depth.c | 17 ++++++++++++++++-
 .cirrus.tasks.yml                    |  2 +-
 2 files changed, 17 insertions(+), 2 deletions(-)

diff --git a/src/backend/utils/misc/stack_depth.c b/src/backend/utils/misc/stack_depth.c
index 61a07cf824e..9955e28c176 100644
--- a/src/backend/utils/misc/stack_depth.c
+++ b/src/backend/utils/misc/stack_depth.c
@@ -108,13 +108,28 @@ check_stack_depth(void)
 bool
 stack_is_too_deep(void)
 {
+#ifndef HAVE__BUILTIN_FRAME_ADDRESS
 	char		stack_top_loc;
+#endif
 	ssize_t		stack_depth;
+	char	   *stack_address;
+
+	/*
+	 * With address sanitizer's stack-use-after-return check, stack variables
+	 * are moved to heap allocations, to allow to detect references to the
+	 * memory at a later time. That would break our stack-depth check. Luckily
+	 * __builtin_frame_address() works correctly, even under asan.
+	 */
+#ifndef HAVE__BUILTIN_FRAME_ADDRESS
+	stack_address = &stack_top_loc;
+#else
+	stack_address = (char *) __builtin_frame_address(0);
+#endif
 
 	/*
 	 * Compute distance from reference point to my local variables
 	 */
-	stack_depth = (ssize_t) (stack_base_ptr - &stack_top_loc);
+	stack_depth = (ssize_t) (stack_base_ptr - stack_address);
 
 	/*
 	 * Take abs value, since stacks grow up on some machines, down on others
diff --git a/.cirrus.tasks.yml b/.cirrus.tasks.yml
index a22cef063f3..8683d1ae9c7 100644
--- a/.cirrus.tasks.yml
+++ b/.cirrus.tasks.yml
@@ -447,7 +447,7 @@ task:
     # print_stacktraces=1,verbosity=2, duh
     # detect_leaks=0: too many uninteresting leak errors in short-lived binaries
     UBSAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:verbosity=2
-    ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0:detect_stack_use_after_return=0
+    ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0
 
     # SANITIZER_FLAGS is set in the tasks below
     CFLAGS: -Og -ggdb -fno-sanitize-recover=all $SANITIZER_FLAGS
-- 
2.53.0.1.gb2826b52eb


--7e2sypi5gxvkgog6--





^ permalink  raw  reply  [nested|flat] 114+ messages in thread

* [PATCH v2 1/4] Make stack depth check work with asan's use-after-return
@ 2026-02-07 19:04 Andres Freund <[email protected]>
  0 siblings, 0 replies; 114+ messages in thread

From: Andres Freund @ 2026-02-07 19:04 UTC (permalink / raw)

With address sanitizer's stack-use-after-return check, stack variables are
moved to heap allocations, to allow to detect references to the memory at a
later time. That broke our stack-depth check, which is why we had to disable
detect_stack_use_after_return in CI. Luckily __builtin_frame_address() works
correctly, even under asan, so use that.

Author:
Reviewed-by:
Discussion: https://postgr.es/m/
Backpatch:
---
 src/backend/utils/misc/stack_depth.c | 17 ++++++++++++++++-
 .cirrus.tasks.yml                    |  2 +-
 2 files changed, 17 insertions(+), 2 deletions(-)

diff --git a/src/backend/utils/misc/stack_depth.c b/src/backend/utils/misc/stack_depth.c
index 61a07cf824e..9955e28c176 100644
--- a/src/backend/utils/misc/stack_depth.c
+++ b/src/backend/utils/misc/stack_depth.c
@@ -108,13 +108,28 @@ check_stack_depth(void)
 bool
 stack_is_too_deep(void)
 {
+#ifndef HAVE__BUILTIN_FRAME_ADDRESS
 	char		stack_top_loc;
+#endif
 	ssize_t		stack_depth;
+	char	   *stack_address;
+
+	/*
+	 * With address sanitizer's stack-use-after-return check, stack variables
+	 * are moved to heap allocations, to allow to detect references to the
+	 * memory at a later time. That would break our stack-depth check. Luckily
+	 * __builtin_frame_address() works correctly, even under asan.
+	 */
+#ifndef HAVE__BUILTIN_FRAME_ADDRESS
+	stack_address = &stack_top_loc;
+#else
+	stack_address = (char *) __builtin_frame_address(0);
+#endif
 
 	/*
 	 * Compute distance from reference point to my local variables
 	 */
-	stack_depth = (ssize_t) (stack_base_ptr - &stack_top_loc);
+	stack_depth = (ssize_t) (stack_base_ptr - stack_address);
 
 	/*
 	 * Take abs value, since stacks grow up on some machines, down on others
diff --git a/.cirrus.tasks.yml b/.cirrus.tasks.yml
index a22cef063f3..8683d1ae9c7 100644
--- a/.cirrus.tasks.yml
+++ b/.cirrus.tasks.yml
@@ -447,7 +447,7 @@ task:
     # print_stacktraces=1,verbosity=2, duh
     # detect_leaks=0: too many uninteresting leak errors in short-lived binaries
     UBSAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:verbosity=2
-    ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0:detect_stack_use_after_return=0
+    ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0
 
     # SANITIZER_FLAGS is set in the tasks below
     CFLAGS: -Og -ggdb -fno-sanitize-recover=all $SANITIZER_FLAGS
-- 
2.53.0.1.gb2826b52eb


--7e2sypi5gxvkgog6--





^ permalink  raw  reply  [nested|flat] 114+ messages in thread

* [PATCH v2 1/4] Make stack depth check work with asan's use-after-return
@ 2026-02-07 19:04 Andres Freund <[email protected]>
  0 siblings, 0 replies; 114+ messages in thread

From: Andres Freund @ 2026-02-07 19:04 UTC (permalink / raw)

With address sanitizer's stack-use-after-return check, stack variables are
moved to heap allocations, to allow to detect references to the memory at a
later time. That broke our stack-depth check, which is why we had to disable
detect_stack_use_after_return in CI. Luckily __builtin_frame_address() works
correctly, even under asan, so use that.

Author:
Reviewed-by:
Discussion: https://postgr.es/m/
Backpatch:
---
 src/backend/utils/misc/stack_depth.c | 17 ++++++++++++++++-
 .cirrus.tasks.yml                    |  2 +-
 2 files changed, 17 insertions(+), 2 deletions(-)

diff --git a/src/backend/utils/misc/stack_depth.c b/src/backend/utils/misc/stack_depth.c
index 61a07cf824e..9955e28c176 100644
--- a/src/backend/utils/misc/stack_depth.c
+++ b/src/backend/utils/misc/stack_depth.c
@@ -108,13 +108,28 @@ check_stack_depth(void)
 bool
 stack_is_too_deep(void)
 {
+#ifndef HAVE__BUILTIN_FRAME_ADDRESS
 	char		stack_top_loc;
+#endif
 	ssize_t		stack_depth;
+	char	   *stack_address;
+
+	/*
+	 * With address sanitizer's stack-use-after-return check, stack variables
+	 * are moved to heap allocations, to allow to detect references to the
+	 * memory at a later time. That would break our stack-depth check. Luckily
+	 * __builtin_frame_address() works correctly, even under asan.
+	 */
+#ifndef HAVE__BUILTIN_FRAME_ADDRESS
+	stack_address = &stack_top_loc;
+#else
+	stack_address = (char *) __builtin_frame_address(0);
+#endif
 
 	/*
 	 * Compute distance from reference point to my local variables
 	 */
-	stack_depth = (ssize_t) (stack_base_ptr - &stack_top_loc);
+	stack_depth = (ssize_t) (stack_base_ptr - stack_address);
 
 	/*
 	 * Take abs value, since stacks grow up on some machines, down on others
diff --git a/.cirrus.tasks.yml b/.cirrus.tasks.yml
index a22cef063f3..8683d1ae9c7 100644
--- a/.cirrus.tasks.yml
+++ b/.cirrus.tasks.yml
@@ -447,7 +447,7 @@ task:
     # print_stacktraces=1,verbosity=2, duh
     # detect_leaks=0: too many uninteresting leak errors in short-lived binaries
     UBSAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:verbosity=2
-    ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0:detect_stack_use_after_return=0
+    ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0
 
     # SANITIZER_FLAGS is set in the tasks below
     CFLAGS: -Og -ggdb -fno-sanitize-recover=all $SANITIZER_FLAGS
-- 
2.53.0.1.gb2826b52eb


--7e2sypi5gxvkgog6--





^ permalink  raw  reply  [nested|flat] 114+ messages in thread

* [PATCH v2 1/4] Make stack depth check work with asan's use-after-return
@ 2026-02-07 19:04 Andres Freund <[email protected]>
  0 siblings, 0 replies; 114+ messages in thread

From: Andres Freund @ 2026-02-07 19:04 UTC (permalink / raw)

With address sanitizer's stack-use-after-return check, stack variables are
moved to heap allocations, to allow to detect references to the memory at a
later time. That broke our stack-depth check, which is why we had to disable
detect_stack_use_after_return in CI. Luckily __builtin_frame_address() works
correctly, even under asan, so use that.

Author:
Reviewed-by:
Discussion: https://postgr.es/m/
Backpatch:
---
 src/backend/utils/misc/stack_depth.c | 17 ++++++++++++++++-
 .cirrus.tasks.yml                    |  2 +-
 2 files changed, 17 insertions(+), 2 deletions(-)

diff --git a/src/backend/utils/misc/stack_depth.c b/src/backend/utils/misc/stack_depth.c
index 61a07cf824e..9955e28c176 100644
--- a/src/backend/utils/misc/stack_depth.c
+++ b/src/backend/utils/misc/stack_depth.c
@@ -108,13 +108,28 @@ check_stack_depth(void)
 bool
 stack_is_too_deep(void)
 {
+#ifndef HAVE__BUILTIN_FRAME_ADDRESS
 	char		stack_top_loc;
+#endif
 	ssize_t		stack_depth;
+	char	   *stack_address;
+
+	/*
+	 * With address sanitizer's stack-use-after-return check, stack variables
+	 * are moved to heap allocations, to allow to detect references to the
+	 * memory at a later time. That would break our stack-depth check. Luckily
+	 * __builtin_frame_address() works correctly, even under asan.
+	 */
+#ifndef HAVE__BUILTIN_FRAME_ADDRESS
+	stack_address = &stack_top_loc;
+#else
+	stack_address = (char *) __builtin_frame_address(0);
+#endif
 
 	/*
 	 * Compute distance from reference point to my local variables
 	 */
-	stack_depth = (ssize_t) (stack_base_ptr - &stack_top_loc);
+	stack_depth = (ssize_t) (stack_base_ptr - stack_address);
 
 	/*
 	 * Take abs value, since stacks grow up on some machines, down on others
diff --git a/.cirrus.tasks.yml b/.cirrus.tasks.yml
index a22cef063f3..8683d1ae9c7 100644
--- a/.cirrus.tasks.yml
+++ b/.cirrus.tasks.yml
@@ -447,7 +447,7 @@ task:
     # print_stacktraces=1,verbosity=2, duh
     # detect_leaks=0: too many uninteresting leak errors in short-lived binaries
     UBSAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:verbosity=2
-    ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0:detect_stack_use_after_return=0
+    ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0
 
     # SANITIZER_FLAGS is set in the tasks below
     CFLAGS: -Og -ggdb -fno-sanitize-recover=all $SANITIZER_FLAGS
-- 
2.53.0.1.gb2826b52eb


--7e2sypi5gxvkgog6--





^ permalink  raw  reply  [nested|flat] 114+ messages in thread

* [PATCH v2 1/4] Make stack depth check work with asan's use-after-return
@ 2026-02-07 19:04 Andres Freund <[email protected]>
  0 siblings, 0 replies; 114+ messages in thread

From: Andres Freund @ 2026-02-07 19:04 UTC (permalink / raw)

With address sanitizer's stack-use-after-return check, stack variables are
moved to heap allocations, to allow to detect references to the memory at a
later time. That broke our stack-depth check, which is why we had to disable
detect_stack_use_after_return in CI. Luckily __builtin_frame_address() works
correctly, even under asan, so use that.

Author:
Reviewed-by:
Discussion: https://postgr.es/m/
Backpatch:
---
 src/backend/utils/misc/stack_depth.c | 17 ++++++++++++++++-
 .cirrus.tasks.yml                    |  2 +-
 2 files changed, 17 insertions(+), 2 deletions(-)

diff --git a/src/backend/utils/misc/stack_depth.c b/src/backend/utils/misc/stack_depth.c
index 61a07cf824e..9955e28c176 100644
--- a/src/backend/utils/misc/stack_depth.c
+++ b/src/backend/utils/misc/stack_depth.c
@@ -108,13 +108,28 @@ check_stack_depth(void)
 bool
 stack_is_too_deep(void)
 {
+#ifndef HAVE__BUILTIN_FRAME_ADDRESS
 	char		stack_top_loc;
+#endif
 	ssize_t		stack_depth;
+	char	   *stack_address;
+
+	/*
+	 * With address sanitizer's stack-use-after-return check, stack variables
+	 * are moved to heap allocations, to allow to detect references to the
+	 * memory at a later time. That would break our stack-depth check. Luckily
+	 * __builtin_frame_address() works correctly, even under asan.
+	 */
+#ifndef HAVE__BUILTIN_FRAME_ADDRESS
+	stack_address = &stack_top_loc;
+#else
+	stack_address = (char *) __builtin_frame_address(0);
+#endif
 
 	/*
 	 * Compute distance from reference point to my local variables
 	 */
-	stack_depth = (ssize_t) (stack_base_ptr - &stack_top_loc);
+	stack_depth = (ssize_t) (stack_base_ptr - stack_address);
 
 	/*
 	 * Take abs value, since stacks grow up on some machines, down on others
diff --git a/.cirrus.tasks.yml b/.cirrus.tasks.yml
index a22cef063f3..8683d1ae9c7 100644
--- a/.cirrus.tasks.yml
+++ b/.cirrus.tasks.yml
@@ -447,7 +447,7 @@ task:
     # print_stacktraces=1,verbosity=2, duh
     # detect_leaks=0: too many uninteresting leak errors in short-lived binaries
     UBSAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:verbosity=2
-    ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0:detect_stack_use_after_return=0
+    ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0
 
     # SANITIZER_FLAGS is set in the tasks below
     CFLAGS: -Og -ggdb -fno-sanitize-recover=all $SANITIZER_FLAGS
-- 
2.53.0.1.gb2826b52eb


--7e2sypi5gxvkgog6--





^ permalink  raw  reply  [nested|flat] 114+ messages in thread

* [PATCH v2 1/4] Make stack depth check work with asan's use-after-return
@ 2026-02-07 19:04 Andres Freund <[email protected]>
  0 siblings, 0 replies; 114+ messages in thread

From: Andres Freund @ 2026-02-07 19:04 UTC (permalink / raw)

With address sanitizer's stack-use-after-return check, stack variables are
moved to heap allocations, to allow to detect references to the memory at a
later time. That broke our stack-depth check, which is why we had to disable
detect_stack_use_after_return in CI. Luckily __builtin_frame_address() works
correctly, even under asan, so use that.

Author:
Reviewed-by:
Discussion: https://postgr.es/m/
Backpatch:
---
 src/backend/utils/misc/stack_depth.c | 17 ++++++++++++++++-
 .cirrus.tasks.yml                    |  2 +-
 2 files changed, 17 insertions(+), 2 deletions(-)

diff --git a/src/backend/utils/misc/stack_depth.c b/src/backend/utils/misc/stack_depth.c
index 61a07cf824e..9955e28c176 100644
--- a/src/backend/utils/misc/stack_depth.c
+++ b/src/backend/utils/misc/stack_depth.c
@@ -108,13 +108,28 @@ check_stack_depth(void)
 bool
 stack_is_too_deep(void)
 {
+#ifndef HAVE__BUILTIN_FRAME_ADDRESS
 	char		stack_top_loc;
+#endif
 	ssize_t		stack_depth;
+	char	   *stack_address;
+
+	/*
+	 * With address sanitizer's stack-use-after-return check, stack variables
+	 * are moved to heap allocations, to allow to detect references to the
+	 * memory at a later time. That would break our stack-depth check. Luckily
+	 * __builtin_frame_address() works correctly, even under asan.
+	 */
+#ifndef HAVE__BUILTIN_FRAME_ADDRESS
+	stack_address = &stack_top_loc;
+#else
+	stack_address = (char *) __builtin_frame_address(0);
+#endif
 
 	/*
 	 * Compute distance from reference point to my local variables
 	 */
-	stack_depth = (ssize_t) (stack_base_ptr - &stack_top_loc);
+	stack_depth = (ssize_t) (stack_base_ptr - stack_address);
 
 	/*
 	 * Take abs value, since stacks grow up on some machines, down on others
diff --git a/.cirrus.tasks.yml b/.cirrus.tasks.yml
index a22cef063f3..8683d1ae9c7 100644
--- a/.cirrus.tasks.yml
+++ b/.cirrus.tasks.yml
@@ -447,7 +447,7 @@ task:
     # print_stacktraces=1,verbosity=2, duh
     # detect_leaks=0: too many uninteresting leak errors in short-lived binaries
     UBSAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:verbosity=2
-    ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0:detect_stack_use_after_return=0
+    ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0
 
     # SANITIZER_FLAGS is set in the tasks below
     CFLAGS: -Og -ggdb -fno-sanitize-recover=all $SANITIZER_FLAGS
-- 
2.53.0.1.gb2826b52eb


--7e2sypi5gxvkgog6--





^ permalink  raw  reply  [nested|flat] 114+ messages in thread

* [PATCH v2 1/4] Make stack depth check work with asan's use-after-return
@ 2026-02-07 19:04 Andres Freund <[email protected]>
  0 siblings, 0 replies; 114+ messages in thread

From: Andres Freund @ 2026-02-07 19:04 UTC (permalink / raw)

With address sanitizer's stack-use-after-return check, stack variables are
moved to heap allocations, to allow to detect references to the memory at a
later time. That broke our stack-depth check, which is why we had to disable
detect_stack_use_after_return in CI. Luckily __builtin_frame_address() works
correctly, even under asan, so use that.

Author:
Reviewed-by:
Discussion: https://postgr.es/m/
Backpatch:
---
 src/backend/utils/misc/stack_depth.c | 17 ++++++++++++++++-
 .cirrus.tasks.yml                    |  2 +-
 2 files changed, 17 insertions(+), 2 deletions(-)

diff --git a/src/backend/utils/misc/stack_depth.c b/src/backend/utils/misc/stack_depth.c
index 61a07cf824e..9955e28c176 100644
--- a/src/backend/utils/misc/stack_depth.c
+++ b/src/backend/utils/misc/stack_depth.c
@@ -108,13 +108,28 @@ check_stack_depth(void)
 bool
 stack_is_too_deep(void)
 {
+#ifndef HAVE__BUILTIN_FRAME_ADDRESS
 	char		stack_top_loc;
+#endif
 	ssize_t		stack_depth;
+	char	   *stack_address;
+
+	/*
+	 * With address sanitizer's stack-use-after-return check, stack variables
+	 * are moved to heap allocations, to allow to detect references to the
+	 * memory at a later time. That would break our stack-depth check. Luckily
+	 * __builtin_frame_address() works correctly, even under asan.
+	 */
+#ifndef HAVE__BUILTIN_FRAME_ADDRESS
+	stack_address = &stack_top_loc;
+#else
+	stack_address = (char *) __builtin_frame_address(0);
+#endif
 
 	/*
 	 * Compute distance from reference point to my local variables
 	 */
-	stack_depth = (ssize_t) (stack_base_ptr - &stack_top_loc);
+	stack_depth = (ssize_t) (stack_base_ptr - stack_address);
 
 	/*
 	 * Take abs value, since stacks grow up on some machines, down on others
diff --git a/.cirrus.tasks.yml b/.cirrus.tasks.yml
index a22cef063f3..8683d1ae9c7 100644
--- a/.cirrus.tasks.yml
+++ b/.cirrus.tasks.yml
@@ -447,7 +447,7 @@ task:
     # print_stacktraces=1,verbosity=2, duh
     # detect_leaks=0: too many uninteresting leak errors in short-lived binaries
     UBSAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:verbosity=2
-    ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0:detect_stack_use_after_return=0
+    ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0
 
     # SANITIZER_FLAGS is set in the tasks below
     CFLAGS: -Og -ggdb -fno-sanitize-recover=all $SANITIZER_FLAGS
-- 
2.53.0.1.gb2826b52eb


--7e2sypi5gxvkgog6--





^ permalink  raw  reply  [nested|flat] 114+ messages in thread

* [PATCH v2 1/4] Make stack depth check work with asan's use-after-return
@ 2026-02-07 19:04 Andres Freund <[email protected]>
  0 siblings, 0 replies; 114+ messages in thread

From: Andres Freund @ 2026-02-07 19:04 UTC (permalink / raw)

With address sanitizer's stack-use-after-return check, stack variables are
moved to heap allocations, to allow to detect references to the memory at a
later time. That broke our stack-depth check, which is why we had to disable
detect_stack_use_after_return in CI. Luckily __builtin_frame_address() works
correctly, even under asan, so use that.

Author:
Reviewed-by:
Discussion: https://postgr.es/m/
Backpatch:
---
 src/backend/utils/misc/stack_depth.c | 17 ++++++++++++++++-
 .cirrus.tasks.yml                    |  2 +-
 2 files changed, 17 insertions(+), 2 deletions(-)

diff --git a/src/backend/utils/misc/stack_depth.c b/src/backend/utils/misc/stack_depth.c
index 61a07cf824e..9955e28c176 100644
--- a/src/backend/utils/misc/stack_depth.c
+++ b/src/backend/utils/misc/stack_depth.c
@@ -108,13 +108,28 @@ check_stack_depth(void)
 bool
 stack_is_too_deep(void)
 {
+#ifndef HAVE__BUILTIN_FRAME_ADDRESS
 	char		stack_top_loc;
+#endif
 	ssize_t		stack_depth;
+	char	   *stack_address;
+
+	/*
+	 * With address sanitizer's stack-use-after-return check, stack variables
+	 * are moved to heap allocations, to allow to detect references to the
+	 * memory at a later time. That would break our stack-depth check. Luckily
+	 * __builtin_frame_address() works correctly, even under asan.
+	 */
+#ifndef HAVE__BUILTIN_FRAME_ADDRESS
+	stack_address = &stack_top_loc;
+#else
+	stack_address = (char *) __builtin_frame_address(0);
+#endif
 
 	/*
 	 * Compute distance from reference point to my local variables
 	 */
-	stack_depth = (ssize_t) (stack_base_ptr - &stack_top_loc);
+	stack_depth = (ssize_t) (stack_base_ptr - stack_address);
 
 	/*
 	 * Take abs value, since stacks grow up on some machines, down on others
diff --git a/.cirrus.tasks.yml b/.cirrus.tasks.yml
index a22cef063f3..8683d1ae9c7 100644
--- a/.cirrus.tasks.yml
+++ b/.cirrus.tasks.yml
@@ -447,7 +447,7 @@ task:
     # print_stacktraces=1,verbosity=2, duh
     # detect_leaks=0: too many uninteresting leak errors in short-lived binaries
     UBSAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:verbosity=2
-    ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0:detect_stack_use_after_return=0
+    ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0
 
     # SANITIZER_FLAGS is set in the tasks below
     CFLAGS: -Og -ggdb -fno-sanitize-recover=all $SANITIZER_FLAGS
-- 
2.53.0.1.gb2826b52eb


--7e2sypi5gxvkgog6--





^ permalink  raw  reply  [nested|flat] 114+ messages in thread

* [PATCH v2 1/4] Make stack depth check work with asan's use-after-return
@ 2026-02-07 19:04 Andres Freund <[email protected]>
  0 siblings, 0 replies; 114+ messages in thread

From: Andres Freund @ 2026-02-07 19:04 UTC (permalink / raw)

With address sanitizer's stack-use-after-return check, stack variables are
moved to heap allocations, to allow to detect references to the memory at a
later time. That broke our stack-depth check, which is why we had to disable
detect_stack_use_after_return in CI. Luckily __builtin_frame_address() works
correctly, even under asan, so use that.

Author:
Reviewed-by:
Discussion: https://postgr.es/m/
Backpatch:
---
 src/backend/utils/misc/stack_depth.c | 17 ++++++++++++++++-
 .cirrus.tasks.yml                    |  2 +-
 2 files changed, 17 insertions(+), 2 deletions(-)

diff --git a/src/backend/utils/misc/stack_depth.c b/src/backend/utils/misc/stack_depth.c
index 61a07cf824e..9955e28c176 100644
--- a/src/backend/utils/misc/stack_depth.c
+++ b/src/backend/utils/misc/stack_depth.c
@@ -108,13 +108,28 @@ check_stack_depth(void)
 bool
 stack_is_too_deep(void)
 {
+#ifndef HAVE__BUILTIN_FRAME_ADDRESS
 	char		stack_top_loc;
+#endif
 	ssize_t		stack_depth;
+	char	   *stack_address;
+
+	/*
+	 * With address sanitizer's stack-use-after-return check, stack variables
+	 * are moved to heap allocations, to allow to detect references to the
+	 * memory at a later time. That would break our stack-depth check. Luckily
+	 * __builtin_frame_address() works correctly, even under asan.
+	 */
+#ifndef HAVE__BUILTIN_FRAME_ADDRESS
+	stack_address = &stack_top_loc;
+#else
+	stack_address = (char *) __builtin_frame_address(0);
+#endif
 
 	/*
 	 * Compute distance from reference point to my local variables
 	 */
-	stack_depth = (ssize_t) (stack_base_ptr - &stack_top_loc);
+	stack_depth = (ssize_t) (stack_base_ptr - stack_address);
 
 	/*
 	 * Take abs value, since stacks grow up on some machines, down on others
diff --git a/.cirrus.tasks.yml b/.cirrus.tasks.yml
index a22cef063f3..8683d1ae9c7 100644
--- a/.cirrus.tasks.yml
+++ b/.cirrus.tasks.yml
@@ -447,7 +447,7 @@ task:
     # print_stacktraces=1,verbosity=2, duh
     # detect_leaks=0: too many uninteresting leak errors in short-lived binaries
     UBSAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:verbosity=2
-    ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0:detect_stack_use_after_return=0
+    ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0
 
     # SANITIZER_FLAGS is set in the tasks below
     CFLAGS: -Og -ggdb -fno-sanitize-recover=all $SANITIZER_FLAGS
-- 
2.53.0.1.gb2826b52eb


--7e2sypi5gxvkgog6--





^ permalink  raw  reply  [nested|flat] 114+ messages in thread

* [PATCH v2 1/4] Make stack depth check work with asan's use-after-return
@ 2026-02-07 19:04 Andres Freund <[email protected]>
  0 siblings, 0 replies; 114+ messages in thread

From: Andres Freund @ 2026-02-07 19:04 UTC (permalink / raw)

With address sanitizer's stack-use-after-return check, stack variables are
moved to heap allocations, to allow to detect references to the memory at a
later time. That broke our stack-depth check, which is why we had to disable
detect_stack_use_after_return in CI. Luckily __builtin_frame_address() works
correctly, even under asan, so use that.

Author:
Reviewed-by:
Discussion: https://postgr.es/m/
Backpatch:
---
 src/backend/utils/misc/stack_depth.c | 17 ++++++++++++++++-
 .cirrus.tasks.yml                    |  2 +-
 2 files changed, 17 insertions(+), 2 deletions(-)

diff --git a/src/backend/utils/misc/stack_depth.c b/src/backend/utils/misc/stack_depth.c
index 61a07cf824e..9955e28c176 100644
--- a/src/backend/utils/misc/stack_depth.c
+++ b/src/backend/utils/misc/stack_depth.c
@@ -108,13 +108,28 @@ check_stack_depth(void)
 bool
 stack_is_too_deep(void)
 {
+#ifndef HAVE__BUILTIN_FRAME_ADDRESS
 	char		stack_top_loc;
+#endif
 	ssize_t		stack_depth;
+	char	   *stack_address;
+
+	/*
+	 * With address sanitizer's stack-use-after-return check, stack variables
+	 * are moved to heap allocations, to allow to detect references to the
+	 * memory at a later time. That would break our stack-depth check. Luckily
+	 * __builtin_frame_address() works correctly, even under asan.
+	 */
+#ifndef HAVE__BUILTIN_FRAME_ADDRESS
+	stack_address = &stack_top_loc;
+#else
+	stack_address = (char *) __builtin_frame_address(0);
+#endif
 
 	/*
 	 * Compute distance from reference point to my local variables
 	 */
-	stack_depth = (ssize_t) (stack_base_ptr - &stack_top_loc);
+	stack_depth = (ssize_t) (stack_base_ptr - stack_address);
 
 	/*
 	 * Take abs value, since stacks grow up on some machines, down on others
diff --git a/.cirrus.tasks.yml b/.cirrus.tasks.yml
index a22cef063f3..8683d1ae9c7 100644
--- a/.cirrus.tasks.yml
+++ b/.cirrus.tasks.yml
@@ -447,7 +447,7 @@ task:
     # print_stacktraces=1,verbosity=2, duh
     # detect_leaks=0: too many uninteresting leak errors in short-lived binaries
     UBSAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:verbosity=2
-    ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0:detect_stack_use_after_return=0
+    ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0
 
     # SANITIZER_FLAGS is set in the tasks below
     CFLAGS: -Og -ggdb -fno-sanitize-recover=all $SANITIZER_FLAGS
-- 
2.53.0.1.gb2826b52eb


--7e2sypi5gxvkgog6--





^ permalink  raw  reply  [nested|flat] 114+ messages in thread

* [PATCH v2 1/4] Make stack depth check work with asan's use-after-return
@ 2026-02-07 19:04 Andres Freund <[email protected]>
  0 siblings, 0 replies; 114+ messages in thread

From: Andres Freund @ 2026-02-07 19:04 UTC (permalink / raw)

With address sanitizer's stack-use-after-return check, stack variables are
moved to heap allocations, to allow to detect references to the memory at a
later time. That broke our stack-depth check, which is why we had to disable
detect_stack_use_after_return in CI. Luckily __builtin_frame_address() works
correctly, even under asan, so use that.

Author:
Reviewed-by:
Discussion: https://postgr.es/m/
Backpatch:
---
 src/backend/utils/misc/stack_depth.c | 17 ++++++++++++++++-
 .cirrus.tasks.yml                    |  2 +-
 2 files changed, 17 insertions(+), 2 deletions(-)

diff --git a/src/backend/utils/misc/stack_depth.c b/src/backend/utils/misc/stack_depth.c
index 61a07cf824e..9955e28c176 100644
--- a/src/backend/utils/misc/stack_depth.c
+++ b/src/backend/utils/misc/stack_depth.c
@@ -108,13 +108,28 @@ check_stack_depth(void)
 bool
 stack_is_too_deep(void)
 {
+#ifndef HAVE__BUILTIN_FRAME_ADDRESS
 	char		stack_top_loc;
+#endif
 	ssize_t		stack_depth;
+	char	   *stack_address;
+
+	/*
+	 * With address sanitizer's stack-use-after-return check, stack variables
+	 * are moved to heap allocations, to allow to detect references to the
+	 * memory at a later time. That would break our stack-depth check. Luckily
+	 * __builtin_frame_address() works correctly, even under asan.
+	 */
+#ifndef HAVE__BUILTIN_FRAME_ADDRESS
+	stack_address = &stack_top_loc;
+#else
+	stack_address = (char *) __builtin_frame_address(0);
+#endif
 
 	/*
 	 * Compute distance from reference point to my local variables
 	 */
-	stack_depth = (ssize_t) (stack_base_ptr - &stack_top_loc);
+	stack_depth = (ssize_t) (stack_base_ptr - stack_address);
 
 	/*
 	 * Take abs value, since stacks grow up on some machines, down on others
diff --git a/.cirrus.tasks.yml b/.cirrus.tasks.yml
index a22cef063f3..8683d1ae9c7 100644
--- a/.cirrus.tasks.yml
+++ b/.cirrus.tasks.yml
@@ -447,7 +447,7 @@ task:
     # print_stacktraces=1,verbosity=2, duh
     # detect_leaks=0: too many uninteresting leak errors in short-lived binaries
     UBSAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:verbosity=2
-    ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0:detect_stack_use_after_return=0
+    ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0
 
     # SANITIZER_FLAGS is set in the tasks below
     CFLAGS: -Og -ggdb -fno-sanitize-recover=all $SANITIZER_FLAGS
-- 
2.53.0.1.gb2826b52eb


--7e2sypi5gxvkgog6--





^ permalink  raw  reply  [nested|flat] 114+ messages in thread

* [PATCH v2 1/4] Make stack depth check work with asan's use-after-return
@ 2026-02-07 19:04 Andres Freund <[email protected]>
  0 siblings, 0 replies; 114+ messages in thread

From: Andres Freund @ 2026-02-07 19:04 UTC (permalink / raw)

With address sanitizer's stack-use-after-return check, stack variables are
moved to heap allocations, to allow to detect references to the memory at a
later time. That broke our stack-depth check, which is why we had to disable
detect_stack_use_after_return in CI. Luckily __builtin_frame_address() works
correctly, even under asan, so use that.

Author:
Reviewed-by:
Discussion: https://postgr.es/m/
Backpatch:
---
 src/backend/utils/misc/stack_depth.c | 17 ++++++++++++++++-
 .cirrus.tasks.yml                    |  2 +-
 2 files changed, 17 insertions(+), 2 deletions(-)

diff --git a/src/backend/utils/misc/stack_depth.c b/src/backend/utils/misc/stack_depth.c
index 61a07cf824e..9955e28c176 100644
--- a/src/backend/utils/misc/stack_depth.c
+++ b/src/backend/utils/misc/stack_depth.c
@@ -108,13 +108,28 @@ check_stack_depth(void)
 bool
 stack_is_too_deep(void)
 {
+#ifndef HAVE__BUILTIN_FRAME_ADDRESS
 	char		stack_top_loc;
+#endif
 	ssize_t		stack_depth;
+	char	   *stack_address;
+
+	/*
+	 * With address sanitizer's stack-use-after-return check, stack variables
+	 * are moved to heap allocations, to allow to detect references to the
+	 * memory at a later time. That would break our stack-depth check. Luckily
+	 * __builtin_frame_address() works correctly, even under asan.
+	 */
+#ifndef HAVE__BUILTIN_FRAME_ADDRESS
+	stack_address = &stack_top_loc;
+#else
+	stack_address = (char *) __builtin_frame_address(0);
+#endif
 
 	/*
 	 * Compute distance from reference point to my local variables
 	 */
-	stack_depth = (ssize_t) (stack_base_ptr - &stack_top_loc);
+	stack_depth = (ssize_t) (stack_base_ptr - stack_address);
 
 	/*
 	 * Take abs value, since stacks grow up on some machines, down on others
diff --git a/.cirrus.tasks.yml b/.cirrus.tasks.yml
index a22cef063f3..8683d1ae9c7 100644
--- a/.cirrus.tasks.yml
+++ b/.cirrus.tasks.yml
@@ -447,7 +447,7 @@ task:
     # print_stacktraces=1,verbosity=2, duh
     # detect_leaks=0: too many uninteresting leak errors in short-lived binaries
     UBSAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:verbosity=2
-    ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0:detect_stack_use_after_return=0
+    ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0
 
     # SANITIZER_FLAGS is set in the tasks below
     CFLAGS: -Og -ggdb -fno-sanitize-recover=all $SANITIZER_FLAGS
-- 
2.53.0.1.gb2826b52eb


--7e2sypi5gxvkgog6--





^ permalink  raw  reply  [nested|flat] 114+ messages in thread

* [PATCH v2 1/4] Make stack depth check work with asan's use-after-return
@ 2026-02-07 19:04 Andres Freund <[email protected]>
  0 siblings, 0 replies; 114+ messages in thread

From: Andres Freund @ 2026-02-07 19:04 UTC (permalink / raw)

With address sanitizer's stack-use-after-return check, stack variables are
moved to heap allocations, to allow to detect references to the memory at a
later time. That broke our stack-depth check, which is why we had to disable
detect_stack_use_after_return in CI. Luckily __builtin_frame_address() works
correctly, even under asan, so use that.

Author:
Reviewed-by:
Discussion: https://postgr.es/m/
Backpatch:
---
 src/backend/utils/misc/stack_depth.c | 17 ++++++++++++++++-
 .cirrus.tasks.yml                    |  2 +-
 2 files changed, 17 insertions(+), 2 deletions(-)

diff --git a/src/backend/utils/misc/stack_depth.c b/src/backend/utils/misc/stack_depth.c
index 61a07cf824e..9955e28c176 100644
--- a/src/backend/utils/misc/stack_depth.c
+++ b/src/backend/utils/misc/stack_depth.c
@@ -108,13 +108,28 @@ check_stack_depth(void)
 bool
 stack_is_too_deep(void)
 {
+#ifndef HAVE__BUILTIN_FRAME_ADDRESS
 	char		stack_top_loc;
+#endif
 	ssize_t		stack_depth;
+	char	   *stack_address;
+
+	/*
+	 * With address sanitizer's stack-use-after-return check, stack variables
+	 * are moved to heap allocations, to allow to detect references to the
+	 * memory at a later time. That would break our stack-depth check. Luckily
+	 * __builtin_frame_address() works correctly, even under asan.
+	 */
+#ifndef HAVE__BUILTIN_FRAME_ADDRESS
+	stack_address = &stack_top_loc;
+#else
+	stack_address = (char *) __builtin_frame_address(0);
+#endif
 
 	/*
 	 * Compute distance from reference point to my local variables
 	 */
-	stack_depth = (ssize_t) (stack_base_ptr - &stack_top_loc);
+	stack_depth = (ssize_t) (stack_base_ptr - stack_address);
 
 	/*
 	 * Take abs value, since stacks grow up on some machines, down on others
diff --git a/.cirrus.tasks.yml b/.cirrus.tasks.yml
index a22cef063f3..8683d1ae9c7 100644
--- a/.cirrus.tasks.yml
+++ b/.cirrus.tasks.yml
@@ -447,7 +447,7 @@ task:
     # print_stacktraces=1,verbosity=2, duh
     # detect_leaks=0: too many uninteresting leak errors in short-lived binaries
     UBSAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:verbosity=2
-    ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0:detect_stack_use_after_return=0
+    ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0
 
     # SANITIZER_FLAGS is set in the tasks below
     CFLAGS: -Og -ggdb -fno-sanitize-recover=all $SANITIZER_FLAGS
-- 
2.53.0.1.gb2826b52eb


--7e2sypi5gxvkgog6--





^ permalink  raw  reply  [nested|flat] 114+ messages in thread

* [PATCH v2 1/4] Make stack depth check work with asan's use-after-return
@ 2026-02-07 19:04 Andres Freund <[email protected]>
  0 siblings, 0 replies; 114+ messages in thread

From: Andres Freund @ 2026-02-07 19:04 UTC (permalink / raw)

With address sanitizer's stack-use-after-return check, stack variables are
moved to heap allocations, to allow to detect references to the memory at a
later time. That broke our stack-depth check, which is why we had to disable
detect_stack_use_after_return in CI. Luckily __builtin_frame_address() works
correctly, even under asan, so use that.

Author:
Reviewed-by:
Discussion: https://postgr.es/m/
Backpatch:
---
 src/backend/utils/misc/stack_depth.c | 17 ++++++++++++++++-
 .cirrus.tasks.yml                    |  2 +-
 2 files changed, 17 insertions(+), 2 deletions(-)

diff --git a/src/backend/utils/misc/stack_depth.c b/src/backend/utils/misc/stack_depth.c
index 61a07cf824e..9955e28c176 100644
--- a/src/backend/utils/misc/stack_depth.c
+++ b/src/backend/utils/misc/stack_depth.c
@@ -108,13 +108,28 @@ check_stack_depth(void)
 bool
 stack_is_too_deep(void)
 {
+#ifndef HAVE__BUILTIN_FRAME_ADDRESS
 	char		stack_top_loc;
+#endif
 	ssize_t		stack_depth;
+	char	   *stack_address;
+
+	/*
+	 * With address sanitizer's stack-use-after-return check, stack variables
+	 * are moved to heap allocations, to allow to detect references to the
+	 * memory at a later time. That would break our stack-depth check. Luckily
+	 * __builtin_frame_address() works correctly, even under asan.
+	 */
+#ifndef HAVE__BUILTIN_FRAME_ADDRESS
+	stack_address = &stack_top_loc;
+#else
+	stack_address = (char *) __builtin_frame_address(0);
+#endif
 
 	/*
 	 * Compute distance from reference point to my local variables
 	 */
-	stack_depth = (ssize_t) (stack_base_ptr - &stack_top_loc);
+	stack_depth = (ssize_t) (stack_base_ptr - stack_address);
 
 	/*
 	 * Take abs value, since stacks grow up on some machines, down on others
diff --git a/.cirrus.tasks.yml b/.cirrus.tasks.yml
index a22cef063f3..8683d1ae9c7 100644
--- a/.cirrus.tasks.yml
+++ b/.cirrus.tasks.yml
@@ -447,7 +447,7 @@ task:
     # print_stacktraces=1,verbosity=2, duh
     # detect_leaks=0: too many uninteresting leak errors in short-lived binaries
     UBSAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:verbosity=2
-    ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0:detect_stack_use_after_return=0
+    ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0
 
     # SANITIZER_FLAGS is set in the tasks below
     CFLAGS: -Og -ggdb -fno-sanitize-recover=all $SANITIZER_FLAGS
-- 
2.53.0.1.gb2826b52eb


--7e2sypi5gxvkgog6--





^ permalink  raw  reply  [nested|flat] 114+ messages in thread

* [PATCH v2 1/4] Make stack depth check work with asan's use-after-return
@ 2026-02-07 19:04 Andres Freund <[email protected]>
  0 siblings, 0 replies; 114+ messages in thread

From: Andres Freund @ 2026-02-07 19:04 UTC (permalink / raw)

With address sanitizer's stack-use-after-return check, stack variables are
moved to heap allocations, to allow to detect references to the memory at a
later time. That broke our stack-depth check, which is why we had to disable
detect_stack_use_after_return in CI. Luckily __builtin_frame_address() works
correctly, even under asan, so use that.

Author:
Reviewed-by:
Discussion: https://postgr.es/m/
Backpatch:
---
 src/backend/utils/misc/stack_depth.c | 17 ++++++++++++++++-
 .cirrus.tasks.yml                    |  2 +-
 2 files changed, 17 insertions(+), 2 deletions(-)

diff --git a/src/backend/utils/misc/stack_depth.c b/src/backend/utils/misc/stack_depth.c
index 61a07cf824e..9955e28c176 100644
--- a/src/backend/utils/misc/stack_depth.c
+++ b/src/backend/utils/misc/stack_depth.c
@@ -108,13 +108,28 @@ check_stack_depth(void)
 bool
 stack_is_too_deep(void)
 {
+#ifndef HAVE__BUILTIN_FRAME_ADDRESS
 	char		stack_top_loc;
+#endif
 	ssize_t		stack_depth;
+	char	   *stack_address;
+
+	/*
+	 * With address sanitizer's stack-use-after-return check, stack variables
+	 * are moved to heap allocations, to allow to detect references to the
+	 * memory at a later time. That would break our stack-depth check. Luckily
+	 * __builtin_frame_address() works correctly, even under asan.
+	 */
+#ifndef HAVE__BUILTIN_FRAME_ADDRESS
+	stack_address = &stack_top_loc;
+#else
+	stack_address = (char *) __builtin_frame_address(0);
+#endif
 
 	/*
 	 * Compute distance from reference point to my local variables
 	 */
-	stack_depth = (ssize_t) (stack_base_ptr - &stack_top_loc);
+	stack_depth = (ssize_t) (stack_base_ptr - stack_address);
 
 	/*
 	 * Take abs value, since stacks grow up on some machines, down on others
diff --git a/.cirrus.tasks.yml b/.cirrus.tasks.yml
index a22cef063f3..8683d1ae9c7 100644
--- a/.cirrus.tasks.yml
+++ b/.cirrus.tasks.yml
@@ -447,7 +447,7 @@ task:
     # print_stacktraces=1,verbosity=2, duh
     # detect_leaks=0: too many uninteresting leak errors in short-lived binaries
     UBSAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:verbosity=2
-    ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0:detect_stack_use_after_return=0
+    ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0
 
     # SANITIZER_FLAGS is set in the tasks below
     CFLAGS: -Og -ggdb -fno-sanitize-recover=all $SANITIZER_FLAGS
-- 
2.53.0.1.gb2826b52eb


--7e2sypi5gxvkgog6--





^ permalink  raw  reply  [nested|flat] 114+ messages in thread

* [PATCH v2 1/4] Make stack depth check work with asan's use-after-return
@ 2026-02-07 19:04 Andres Freund <[email protected]>
  0 siblings, 0 replies; 114+ messages in thread

From: Andres Freund @ 2026-02-07 19:04 UTC (permalink / raw)

With address sanitizer's stack-use-after-return check, stack variables are
moved to heap allocations, to allow to detect references to the memory at a
later time. That broke our stack-depth check, which is why we had to disable
detect_stack_use_after_return in CI. Luckily __builtin_frame_address() works
correctly, even under asan, so use that.

Author:
Reviewed-by:
Discussion: https://postgr.es/m/
Backpatch:
---
 src/backend/utils/misc/stack_depth.c | 17 ++++++++++++++++-
 .cirrus.tasks.yml                    |  2 +-
 2 files changed, 17 insertions(+), 2 deletions(-)

diff --git a/src/backend/utils/misc/stack_depth.c b/src/backend/utils/misc/stack_depth.c
index 61a07cf824e..9955e28c176 100644
--- a/src/backend/utils/misc/stack_depth.c
+++ b/src/backend/utils/misc/stack_depth.c
@@ -108,13 +108,28 @@ check_stack_depth(void)
 bool
 stack_is_too_deep(void)
 {
+#ifndef HAVE__BUILTIN_FRAME_ADDRESS
 	char		stack_top_loc;
+#endif
 	ssize_t		stack_depth;
+	char	   *stack_address;
+
+	/*
+	 * With address sanitizer's stack-use-after-return check, stack variables
+	 * are moved to heap allocations, to allow to detect references to the
+	 * memory at a later time. That would break our stack-depth check. Luckily
+	 * __builtin_frame_address() works correctly, even under asan.
+	 */
+#ifndef HAVE__BUILTIN_FRAME_ADDRESS
+	stack_address = &stack_top_loc;
+#else
+	stack_address = (char *) __builtin_frame_address(0);
+#endif
 
 	/*
 	 * Compute distance from reference point to my local variables
 	 */
-	stack_depth = (ssize_t) (stack_base_ptr - &stack_top_loc);
+	stack_depth = (ssize_t) (stack_base_ptr - stack_address);
 
 	/*
 	 * Take abs value, since stacks grow up on some machines, down on others
diff --git a/.cirrus.tasks.yml b/.cirrus.tasks.yml
index a22cef063f3..8683d1ae9c7 100644
--- a/.cirrus.tasks.yml
+++ b/.cirrus.tasks.yml
@@ -447,7 +447,7 @@ task:
     # print_stacktraces=1,verbosity=2, duh
     # detect_leaks=0: too many uninteresting leak errors in short-lived binaries
     UBSAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:verbosity=2
-    ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0:detect_stack_use_after_return=0
+    ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0
 
     # SANITIZER_FLAGS is set in the tasks below
     CFLAGS: -Og -ggdb -fno-sanitize-recover=all $SANITIZER_FLAGS
-- 
2.53.0.1.gb2826b52eb


--7e2sypi5gxvkgog6--





^ permalink  raw  reply  [nested|flat] 114+ messages in thread

* [PATCH v2 1/4] Make stack depth check work with asan's use-after-return
@ 2026-02-07 19:04 Andres Freund <[email protected]>
  0 siblings, 0 replies; 114+ messages in thread

From: Andres Freund @ 2026-02-07 19:04 UTC (permalink / raw)

With address sanitizer's stack-use-after-return check, stack variables are
moved to heap allocations, to allow to detect references to the memory at a
later time. That broke our stack-depth check, which is why we had to disable
detect_stack_use_after_return in CI. Luckily __builtin_frame_address() works
correctly, even under asan, so use that.

Author:
Reviewed-by:
Discussion: https://postgr.es/m/
Backpatch:
---
 src/backend/utils/misc/stack_depth.c | 17 ++++++++++++++++-
 .cirrus.tasks.yml                    |  2 +-
 2 files changed, 17 insertions(+), 2 deletions(-)

diff --git a/src/backend/utils/misc/stack_depth.c b/src/backend/utils/misc/stack_depth.c
index 61a07cf824e..9955e28c176 100644
--- a/src/backend/utils/misc/stack_depth.c
+++ b/src/backend/utils/misc/stack_depth.c
@@ -108,13 +108,28 @@ check_stack_depth(void)
 bool
 stack_is_too_deep(void)
 {
+#ifndef HAVE__BUILTIN_FRAME_ADDRESS
 	char		stack_top_loc;
+#endif
 	ssize_t		stack_depth;
+	char	   *stack_address;
+
+	/*
+	 * With address sanitizer's stack-use-after-return check, stack variables
+	 * are moved to heap allocations, to allow to detect references to the
+	 * memory at a later time. That would break our stack-depth check. Luckily
+	 * __builtin_frame_address() works correctly, even under asan.
+	 */
+#ifndef HAVE__BUILTIN_FRAME_ADDRESS
+	stack_address = &stack_top_loc;
+#else
+	stack_address = (char *) __builtin_frame_address(0);
+#endif
 
 	/*
 	 * Compute distance from reference point to my local variables
 	 */
-	stack_depth = (ssize_t) (stack_base_ptr - &stack_top_loc);
+	stack_depth = (ssize_t) (stack_base_ptr - stack_address);
 
 	/*
 	 * Take abs value, since stacks grow up on some machines, down on others
diff --git a/.cirrus.tasks.yml b/.cirrus.tasks.yml
index a22cef063f3..8683d1ae9c7 100644
--- a/.cirrus.tasks.yml
+++ b/.cirrus.tasks.yml
@@ -447,7 +447,7 @@ task:
     # print_stacktraces=1,verbosity=2, duh
     # detect_leaks=0: too many uninteresting leak errors in short-lived binaries
     UBSAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:verbosity=2
-    ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0:detect_stack_use_after_return=0
+    ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0
 
     # SANITIZER_FLAGS is set in the tasks below
     CFLAGS: -Og -ggdb -fno-sanitize-recover=all $SANITIZER_FLAGS
-- 
2.53.0.1.gb2826b52eb


--7e2sypi5gxvkgog6--





^ permalink  raw  reply  [nested|flat] 114+ messages in thread

* [PATCH v2 1/4] Make stack depth check work with asan's use-after-return
@ 2026-02-07 19:04 Andres Freund <[email protected]>
  0 siblings, 0 replies; 114+ messages in thread

From: Andres Freund @ 2026-02-07 19:04 UTC (permalink / raw)

With address sanitizer's stack-use-after-return check, stack variables are
moved to heap allocations, to allow to detect references to the memory at a
later time. That broke our stack-depth check, which is why we had to disable
detect_stack_use_after_return in CI. Luckily __builtin_frame_address() works
correctly, even under asan, so use that.

Author:
Reviewed-by:
Discussion: https://postgr.es/m/
Backpatch:
---
 src/backend/utils/misc/stack_depth.c | 17 ++++++++++++++++-
 .cirrus.tasks.yml                    |  2 +-
 2 files changed, 17 insertions(+), 2 deletions(-)

diff --git a/src/backend/utils/misc/stack_depth.c b/src/backend/utils/misc/stack_depth.c
index 61a07cf824e..9955e28c176 100644
--- a/src/backend/utils/misc/stack_depth.c
+++ b/src/backend/utils/misc/stack_depth.c
@@ -108,13 +108,28 @@ check_stack_depth(void)
 bool
 stack_is_too_deep(void)
 {
+#ifndef HAVE__BUILTIN_FRAME_ADDRESS
 	char		stack_top_loc;
+#endif
 	ssize_t		stack_depth;
+	char	   *stack_address;
+
+	/*
+	 * With address sanitizer's stack-use-after-return check, stack variables
+	 * are moved to heap allocations, to allow to detect references to the
+	 * memory at a later time. That would break our stack-depth check. Luckily
+	 * __builtin_frame_address() works correctly, even under asan.
+	 */
+#ifndef HAVE__BUILTIN_FRAME_ADDRESS
+	stack_address = &stack_top_loc;
+#else
+	stack_address = (char *) __builtin_frame_address(0);
+#endif
 
 	/*
 	 * Compute distance from reference point to my local variables
 	 */
-	stack_depth = (ssize_t) (stack_base_ptr - &stack_top_loc);
+	stack_depth = (ssize_t) (stack_base_ptr - stack_address);
 
 	/*
 	 * Take abs value, since stacks grow up on some machines, down on others
diff --git a/.cirrus.tasks.yml b/.cirrus.tasks.yml
index a22cef063f3..8683d1ae9c7 100644
--- a/.cirrus.tasks.yml
+++ b/.cirrus.tasks.yml
@@ -447,7 +447,7 @@ task:
     # print_stacktraces=1,verbosity=2, duh
     # detect_leaks=0: too many uninteresting leak errors in short-lived binaries
     UBSAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:verbosity=2
-    ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0:detect_stack_use_after_return=0
+    ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0
 
     # SANITIZER_FLAGS is set in the tasks below
     CFLAGS: -Og -ggdb -fno-sanitize-recover=all $SANITIZER_FLAGS
-- 
2.53.0.1.gb2826b52eb


--7e2sypi5gxvkgog6--





^ permalink  raw  reply  [nested|flat] 114+ messages in thread

* [PATCH v2 1/4] Make stack depth check work with asan's use-after-return
@ 2026-02-07 19:04 Andres Freund <[email protected]>
  0 siblings, 0 replies; 114+ messages in thread

From: Andres Freund @ 2026-02-07 19:04 UTC (permalink / raw)

With address sanitizer's stack-use-after-return check, stack variables are
moved to heap allocations, to allow to detect references to the memory at a
later time. That broke our stack-depth check, which is why we had to disable
detect_stack_use_after_return in CI. Luckily __builtin_frame_address() works
correctly, even under asan, so use that.

Author:
Reviewed-by:
Discussion: https://postgr.es/m/
Backpatch:
---
 src/backend/utils/misc/stack_depth.c | 17 ++++++++++++++++-
 .cirrus.tasks.yml                    |  2 +-
 2 files changed, 17 insertions(+), 2 deletions(-)

diff --git a/src/backend/utils/misc/stack_depth.c b/src/backend/utils/misc/stack_depth.c
index 61a07cf824e..9955e28c176 100644
--- a/src/backend/utils/misc/stack_depth.c
+++ b/src/backend/utils/misc/stack_depth.c
@@ -108,13 +108,28 @@ check_stack_depth(void)
 bool
 stack_is_too_deep(void)
 {
+#ifndef HAVE__BUILTIN_FRAME_ADDRESS
 	char		stack_top_loc;
+#endif
 	ssize_t		stack_depth;
+	char	   *stack_address;
+
+	/*
+	 * With address sanitizer's stack-use-after-return check, stack variables
+	 * are moved to heap allocations, to allow to detect references to the
+	 * memory at a later time. That would break our stack-depth check. Luckily
+	 * __builtin_frame_address() works correctly, even under asan.
+	 */
+#ifndef HAVE__BUILTIN_FRAME_ADDRESS
+	stack_address = &stack_top_loc;
+#else
+	stack_address = (char *) __builtin_frame_address(0);
+#endif
 
 	/*
 	 * Compute distance from reference point to my local variables
 	 */
-	stack_depth = (ssize_t) (stack_base_ptr - &stack_top_loc);
+	stack_depth = (ssize_t) (stack_base_ptr - stack_address);
 
 	/*
 	 * Take abs value, since stacks grow up on some machines, down on others
diff --git a/.cirrus.tasks.yml b/.cirrus.tasks.yml
index a22cef063f3..8683d1ae9c7 100644
--- a/.cirrus.tasks.yml
+++ b/.cirrus.tasks.yml
@@ -447,7 +447,7 @@ task:
     # print_stacktraces=1,verbosity=2, duh
     # detect_leaks=0: too many uninteresting leak errors in short-lived binaries
     UBSAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:verbosity=2
-    ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0:detect_stack_use_after_return=0
+    ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0
 
     # SANITIZER_FLAGS is set in the tasks below
     CFLAGS: -Og -ggdb -fno-sanitize-recover=all $SANITIZER_FLAGS
-- 
2.53.0.1.gb2826b52eb


--7e2sypi5gxvkgog6--





^ permalink  raw  reply  [nested|flat] 114+ messages in thread

* [PATCH v2 1/4] Make stack depth check work with asan's use-after-return
@ 2026-02-07 19:04 Andres Freund <[email protected]>
  0 siblings, 0 replies; 114+ messages in thread

From: Andres Freund @ 2026-02-07 19:04 UTC (permalink / raw)

With address sanitizer's stack-use-after-return check, stack variables are
moved to heap allocations, to allow to detect references to the memory at a
later time. That broke our stack-depth check, which is why we had to disable
detect_stack_use_after_return in CI. Luckily __builtin_frame_address() works
correctly, even under asan, so use that.

Author:
Reviewed-by:
Discussion: https://postgr.es/m/
Backpatch:
---
 src/backend/utils/misc/stack_depth.c | 17 ++++++++++++++++-
 .cirrus.tasks.yml                    |  2 +-
 2 files changed, 17 insertions(+), 2 deletions(-)

diff --git a/src/backend/utils/misc/stack_depth.c b/src/backend/utils/misc/stack_depth.c
index 61a07cf824e..9955e28c176 100644
--- a/src/backend/utils/misc/stack_depth.c
+++ b/src/backend/utils/misc/stack_depth.c
@@ -108,13 +108,28 @@ check_stack_depth(void)
 bool
 stack_is_too_deep(void)
 {
+#ifndef HAVE__BUILTIN_FRAME_ADDRESS
 	char		stack_top_loc;
+#endif
 	ssize_t		stack_depth;
+	char	   *stack_address;
+
+	/*
+	 * With address sanitizer's stack-use-after-return check, stack variables
+	 * are moved to heap allocations, to allow to detect references to the
+	 * memory at a later time. That would break our stack-depth check. Luckily
+	 * __builtin_frame_address() works correctly, even under asan.
+	 */
+#ifndef HAVE__BUILTIN_FRAME_ADDRESS
+	stack_address = &stack_top_loc;
+#else
+	stack_address = (char *) __builtin_frame_address(0);
+#endif
 
 	/*
 	 * Compute distance from reference point to my local variables
 	 */
-	stack_depth = (ssize_t) (stack_base_ptr - &stack_top_loc);
+	stack_depth = (ssize_t) (stack_base_ptr - stack_address);
 
 	/*
 	 * Take abs value, since stacks grow up on some machines, down on others
diff --git a/.cirrus.tasks.yml b/.cirrus.tasks.yml
index a22cef063f3..8683d1ae9c7 100644
--- a/.cirrus.tasks.yml
+++ b/.cirrus.tasks.yml
@@ -447,7 +447,7 @@ task:
     # print_stacktraces=1,verbosity=2, duh
     # detect_leaks=0: too many uninteresting leak errors in short-lived binaries
     UBSAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:verbosity=2
-    ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0:detect_stack_use_after_return=0
+    ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0
 
     # SANITIZER_FLAGS is set in the tasks below
     CFLAGS: -Og -ggdb -fno-sanitize-recover=all $SANITIZER_FLAGS
-- 
2.53.0.1.gb2826b52eb


--7e2sypi5gxvkgog6--





^ permalink  raw  reply  [nested|flat] 114+ messages in thread

* [PATCH v2 1/4] Make stack depth check work with asan's use-after-return
@ 2026-02-07 19:04 Andres Freund <[email protected]>
  0 siblings, 0 replies; 114+ messages in thread

From: Andres Freund @ 2026-02-07 19:04 UTC (permalink / raw)

With address sanitizer's stack-use-after-return check, stack variables are
moved to heap allocations, to allow to detect references to the memory at a
later time. That broke our stack-depth check, which is why we had to disable
detect_stack_use_after_return in CI. Luckily __builtin_frame_address() works
correctly, even under asan, so use that.

Author:
Reviewed-by:
Discussion: https://postgr.es/m/
Backpatch:
---
 src/backend/utils/misc/stack_depth.c | 17 ++++++++++++++++-
 .cirrus.tasks.yml                    |  2 +-
 2 files changed, 17 insertions(+), 2 deletions(-)

diff --git a/src/backend/utils/misc/stack_depth.c b/src/backend/utils/misc/stack_depth.c
index 61a07cf824e..9955e28c176 100644
--- a/src/backend/utils/misc/stack_depth.c
+++ b/src/backend/utils/misc/stack_depth.c
@@ -108,13 +108,28 @@ check_stack_depth(void)
 bool
 stack_is_too_deep(void)
 {
+#ifndef HAVE__BUILTIN_FRAME_ADDRESS
 	char		stack_top_loc;
+#endif
 	ssize_t		stack_depth;
+	char	   *stack_address;
+
+	/*
+	 * With address sanitizer's stack-use-after-return check, stack variables
+	 * are moved to heap allocations, to allow to detect references to the
+	 * memory at a later time. That would break our stack-depth check. Luckily
+	 * __builtin_frame_address() works correctly, even under asan.
+	 */
+#ifndef HAVE__BUILTIN_FRAME_ADDRESS
+	stack_address = &stack_top_loc;
+#else
+	stack_address = (char *) __builtin_frame_address(0);
+#endif
 
 	/*
 	 * Compute distance from reference point to my local variables
 	 */
-	stack_depth = (ssize_t) (stack_base_ptr - &stack_top_loc);
+	stack_depth = (ssize_t) (stack_base_ptr - stack_address);
 
 	/*
 	 * Take abs value, since stacks grow up on some machines, down on others
diff --git a/.cirrus.tasks.yml b/.cirrus.tasks.yml
index a22cef063f3..8683d1ae9c7 100644
--- a/.cirrus.tasks.yml
+++ b/.cirrus.tasks.yml
@@ -447,7 +447,7 @@ task:
     # print_stacktraces=1,verbosity=2, duh
     # detect_leaks=0: too many uninteresting leak errors in short-lived binaries
     UBSAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:verbosity=2
-    ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0:detect_stack_use_after_return=0
+    ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0
 
     # SANITIZER_FLAGS is set in the tasks below
     CFLAGS: -Og -ggdb -fno-sanitize-recover=all $SANITIZER_FLAGS
-- 
2.53.0.1.gb2826b52eb


--7e2sypi5gxvkgog6--





^ permalink  raw  reply  [nested|flat] 114+ messages in thread

* [PATCH v2 1/4] Make stack depth check work with asan's use-after-return
@ 2026-02-07 19:04 Andres Freund <[email protected]>
  0 siblings, 0 replies; 114+ messages in thread

From: Andres Freund @ 2026-02-07 19:04 UTC (permalink / raw)

With address sanitizer's stack-use-after-return check, stack variables are
moved to heap allocations, to allow to detect references to the memory at a
later time. That broke our stack-depth check, which is why we had to disable
detect_stack_use_after_return in CI. Luckily __builtin_frame_address() works
correctly, even under asan, so use that.

Author:
Reviewed-by:
Discussion: https://postgr.es/m/
Backpatch:
---
 src/backend/utils/misc/stack_depth.c | 17 ++++++++++++++++-
 .cirrus.tasks.yml                    |  2 +-
 2 files changed, 17 insertions(+), 2 deletions(-)

diff --git a/src/backend/utils/misc/stack_depth.c b/src/backend/utils/misc/stack_depth.c
index 61a07cf824e..9955e28c176 100644
--- a/src/backend/utils/misc/stack_depth.c
+++ b/src/backend/utils/misc/stack_depth.c
@@ -108,13 +108,28 @@ check_stack_depth(void)
 bool
 stack_is_too_deep(void)
 {
+#ifndef HAVE__BUILTIN_FRAME_ADDRESS
 	char		stack_top_loc;
+#endif
 	ssize_t		stack_depth;
+	char	   *stack_address;
+
+	/*
+	 * With address sanitizer's stack-use-after-return check, stack variables
+	 * are moved to heap allocations, to allow to detect references to the
+	 * memory at a later time. That would break our stack-depth check. Luckily
+	 * __builtin_frame_address() works correctly, even under asan.
+	 */
+#ifndef HAVE__BUILTIN_FRAME_ADDRESS
+	stack_address = &stack_top_loc;
+#else
+	stack_address = (char *) __builtin_frame_address(0);
+#endif
 
 	/*
 	 * Compute distance from reference point to my local variables
 	 */
-	stack_depth = (ssize_t) (stack_base_ptr - &stack_top_loc);
+	stack_depth = (ssize_t) (stack_base_ptr - stack_address);
 
 	/*
 	 * Take abs value, since stacks grow up on some machines, down on others
diff --git a/.cirrus.tasks.yml b/.cirrus.tasks.yml
index a22cef063f3..8683d1ae9c7 100644
--- a/.cirrus.tasks.yml
+++ b/.cirrus.tasks.yml
@@ -447,7 +447,7 @@ task:
     # print_stacktraces=1,verbosity=2, duh
     # detect_leaks=0: too many uninteresting leak errors in short-lived binaries
     UBSAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:verbosity=2
-    ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0:detect_stack_use_after_return=0
+    ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0
 
     # SANITIZER_FLAGS is set in the tasks below
     CFLAGS: -Og -ggdb -fno-sanitize-recover=all $SANITIZER_FLAGS
-- 
2.53.0.1.gb2826b52eb


--7e2sypi5gxvkgog6--





^ permalink  raw  reply  [nested|flat] 114+ messages in thread

* [PATCH v2 1/4] Make stack depth check work with asan's use-after-return
@ 2026-02-07 19:04 Andres Freund <[email protected]>
  0 siblings, 0 replies; 114+ messages in thread

From: Andres Freund @ 2026-02-07 19:04 UTC (permalink / raw)

With address sanitizer's stack-use-after-return check, stack variables are
moved to heap allocations, to allow to detect references to the memory at a
later time. That broke our stack-depth check, which is why we had to disable
detect_stack_use_after_return in CI. Luckily __builtin_frame_address() works
correctly, even under asan, so use that.

Author:
Reviewed-by:
Discussion: https://postgr.es/m/
Backpatch:
---
 src/backend/utils/misc/stack_depth.c | 17 ++++++++++++++++-
 .cirrus.tasks.yml                    |  2 +-
 2 files changed, 17 insertions(+), 2 deletions(-)

diff --git a/src/backend/utils/misc/stack_depth.c b/src/backend/utils/misc/stack_depth.c
index 61a07cf824e..9955e28c176 100644
--- a/src/backend/utils/misc/stack_depth.c
+++ b/src/backend/utils/misc/stack_depth.c
@@ -108,13 +108,28 @@ check_stack_depth(void)
 bool
 stack_is_too_deep(void)
 {
+#ifndef HAVE__BUILTIN_FRAME_ADDRESS
 	char		stack_top_loc;
+#endif
 	ssize_t		stack_depth;
+	char	   *stack_address;
+
+	/*
+	 * With address sanitizer's stack-use-after-return check, stack variables
+	 * are moved to heap allocations, to allow to detect references to the
+	 * memory at a later time. That would break our stack-depth check. Luckily
+	 * __builtin_frame_address() works correctly, even under asan.
+	 */
+#ifndef HAVE__BUILTIN_FRAME_ADDRESS
+	stack_address = &stack_top_loc;
+#else
+	stack_address = (char *) __builtin_frame_address(0);
+#endif
 
 	/*
 	 * Compute distance from reference point to my local variables
 	 */
-	stack_depth = (ssize_t) (stack_base_ptr - &stack_top_loc);
+	stack_depth = (ssize_t) (stack_base_ptr - stack_address);
 
 	/*
 	 * Take abs value, since stacks grow up on some machines, down on others
diff --git a/.cirrus.tasks.yml b/.cirrus.tasks.yml
index a22cef063f3..8683d1ae9c7 100644
--- a/.cirrus.tasks.yml
+++ b/.cirrus.tasks.yml
@@ -447,7 +447,7 @@ task:
     # print_stacktraces=1,verbosity=2, duh
     # detect_leaks=0: too many uninteresting leak errors in short-lived binaries
     UBSAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:verbosity=2
-    ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0:detect_stack_use_after_return=0
+    ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0
 
     # SANITIZER_FLAGS is set in the tasks below
     CFLAGS: -Og -ggdb -fno-sanitize-recover=all $SANITIZER_FLAGS
-- 
2.53.0.1.gb2826b52eb


--7e2sypi5gxvkgog6--





^ permalink  raw  reply  [nested|flat] 114+ messages in thread

* [PATCH v2 1/4] Make stack depth check work with asan's use-after-return
@ 2026-02-07 19:04 Andres Freund <[email protected]>
  0 siblings, 0 replies; 114+ messages in thread

From: Andres Freund @ 2026-02-07 19:04 UTC (permalink / raw)

With address sanitizer's stack-use-after-return check, stack variables are
moved to heap allocations, to allow to detect references to the memory at a
later time. That broke our stack-depth check, which is why we had to disable
detect_stack_use_after_return in CI. Luckily __builtin_frame_address() works
correctly, even under asan, so use that.

Author:
Reviewed-by:
Discussion: https://postgr.es/m/
Backpatch:
---
 src/backend/utils/misc/stack_depth.c | 17 ++++++++++++++++-
 .cirrus.tasks.yml                    |  2 +-
 2 files changed, 17 insertions(+), 2 deletions(-)

diff --git a/src/backend/utils/misc/stack_depth.c b/src/backend/utils/misc/stack_depth.c
index 61a07cf824e..9955e28c176 100644
--- a/src/backend/utils/misc/stack_depth.c
+++ b/src/backend/utils/misc/stack_depth.c
@@ -108,13 +108,28 @@ check_stack_depth(void)
 bool
 stack_is_too_deep(void)
 {
+#ifndef HAVE__BUILTIN_FRAME_ADDRESS
 	char		stack_top_loc;
+#endif
 	ssize_t		stack_depth;
+	char	   *stack_address;
+
+	/*
+	 * With address sanitizer's stack-use-after-return check, stack variables
+	 * are moved to heap allocations, to allow to detect references to the
+	 * memory at a later time. That would break our stack-depth check. Luckily
+	 * __builtin_frame_address() works correctly, even under asan.
+	 */
+#ifndef HAVE__BUILTIN_FRAME_ADDRESS
+	stack_address = &stack_top_loc;
+#else
+	stack_address = (char *) __builtin_frame_address(0);
+#endif
 
 	/*
 	 * Compute distance from reference point to my local variables
 	 */
-	stack_depth = (ssize_t) (stack_base_ptr - &stack_top_loc);
+	stack_depth = (ssize_t) (stack_base_ptr - stack_address);
 
 	/*
 	 * Take abs value, since stacks grow up on some machines, down on others
diff --git a/.cirrus.tasks.yml b/.cirrus.tasks.yml
index a22cef063f3..8683d1ae9c7 100644
--- a/.cirrus.tasks.yml
+++ b/.cirrus.tasks.yml
@@ -447,7 +447,7 @@ task:
     # print_stacktraces=1,verbosity=2, duh
     # detect_leaks=0: too many uninteresting leak errors in short-lived binaries
     UBSAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:verbosity=2
-    ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0:detect_stack_use_after_return=0
+    ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0
 
     # SANITIZER_FLAGS is set in the tasks below
     CFLAGS: -Og -ggdb -fno-sanitize-recover=all $SANITIZER_FLAGS
-- 
2.53.0.1.gb2826b52eb


--7e2sypi5gxvkgog6--





^ permalink  raw  reply  [nested|flat] 114+ messages in thread

* [PATCH v2 1/4] Make stack depth check work with asan's use-after-return
@ 2026-02-07 19:04 Andres Freund <[email protected]>
  0 siblings, 0 replies; 114+ messages in thread

From: Andres Freund @ 2026-02-07 19:04 UTC (permalink / raw)

With address sanitizer's stack-use-after-return check, stack variables are
moved to heap allocations, to allow to detect references to the memory at a
later time. That broke our stack-depth check, which is why we had to disable
detect_stack_use_after_return in CI. Luckily __builtin_frame_address() works
correctly, even under asan, so use that.

Author:
Reviewed-by:
Discussion: https://postgr.es/m/
Backpatch:
---
 src/backend/utils/misc/stack_depth.c | 17 ++++++++++++++++-
 .cirrus.tasks.yml                    |  2 +-
 2 files changed, 17 insertions(+), 2 deletions(-)

diff --git a/src/backend/utils/misc/stack_depth.c b/src/backend/utils/misc/stack_depth.c
index 61a07cf824e..9955e28c176 100644
--- a/src/backend/utils/misc/stack_depth.c
+++ b/src/backend/utils/misc/stack_depth.c
@@ -108,13 +108,28 @@ check_stack_depth(void)
 bool
 stack_is_too_deep(void)
 {
+#ifndef HAVE__BUILTIN_FRAME_ADDRESS
 	char		stack_top_loc;
+#endif
 	ssize_t		stack_depth;
+	char	   *stack_address;
+
+	/*
+	 * With address sanitizer's stack-use-after-return check, stack variables
+	 * are moved to heap allocations, to allow to detect references to the
+	 * memory at a later time. That would break our stack-depth check. Luckily
+	 * __builtin_frame_address() works correctly, even under asan.
+	 */
+#ifndef HAVE__BUILTIN_FRAME_ADDRESS
+	stack_address = &stack_top_loc;
+#else
+	stack_address = (char *) __builtin_frame_address(0);
+#endif
 
 	/*
 	 * Compute distance from reference point to my local variables
 	 */
-	stack_depth = (ssize_t) (stack_base_ptr - &stack_top_loc);
+	stack_depth = (ssize_t) (stack_base_ptr - stack_address);
 
 	/*
 	 * Take abs value, since stacks grow up on some machines, down on others
diff --git a/.cirrus.tasks.yml b/.cirrus.tasks.yml
index a22cef063f3..8683d1ae9c7 100644
--- a/.cirrus.tasks.yml
+++ b/.cirrus.tasks.yml
@@ -447,7 +447,7 @@ task:
     # print_stacktraces=1,verbosity=2, duh
     # detect_leaks=0: too many uninteresting leak errors in short-lived binaries
     UBSAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:verbosity=2
-    ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0:detect_stack_use_after_return=0
+    ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0
 
     # SANITIZER_FLAGS is set in the tasks below
     CFLAGS: -Og -ggdb -fno-sanitize-recover=all $SANITIZER_FLAGS
-- 
2.53.0.1.gb2826b52eb


--7e2sypi5gxvkgog6--





^ permalink  raw  reply  [nested|flat] 114+ messages in thread

* [PATCH v2 1/4] Make stack depth check work with asan's use-after-return
@ 2026-02-07 19:04 Andres Freund <[email protected]>
  0 siblings, 0 replies; 114+ messages in thread

From: Andres Freund @ 2026-02-07 19:04 UTC (permalink / raw)

With address sanitizer's stack-use-after-return check, stack variables are
moved to heap allocations, to allow to detect references to the memory at a
later time. That broke our stack-depth check, which is why we had to disable
detect_stack_use_after_return in CI. Luckily __builtin_frame_address() works
correctly, even under asan, so use that.

Author:
Reviewed-by:
Discussion: https://postgr.es/m/
Backpatch:
---
 src/backend/utils/misc/stack_depth.c | 17 ++++++++++++++++-
 .cirrus.tasks.yml                    |  2 +-
 2 files changed, 17 insertions(+), 2 deletions(-)

diff --git a/src/backend/utils/misc/stack_depth.c b/src/backend/utils/misc/stack_depth.c
index 61a07cf824e..9955e28c176 100644
--- a/src/backend/utils/misc/stack_depth.c
+++ b/src/backend/utils/misc/stack_depth.c
@@ -108,13 +108,28 @@ check_stack_depth(void)
 bool
 stack_is_too_deep(void)
 {
+#ifndef HAVE__BUILTIN_FRAME_ADDRESS
 	char		stack_top_loc;
+#endif
 	ssize_t		stack_depth;
+	char	   *stack_address;
+
+	/*
+	 * With address sanitizer's stack-use-after-return check, stack variables
+	 * are moved to heap allocations, to allow to detect references to the
+	 * memory at a later time. That would break our stack-depth check. Luckily
+	 * __builtin_frame_address() works correctly, even under asan.
+	 */
+#ifndef HAVE__BUILTIN_FRAME_ADDRESS
+	stack_address = &stack_top_loc;
+#else
+	stack_address = (char *) __builtin_frame_address(0);
+#endif
 
 	/*
 	 * Compute distance from reference point to my local variables
 	 */
-	stack_depth = (ssize_t) (stack_base_ptr - &stack_top_loc);
+	stack_depth = (ssize_t) (stack_base_ptr - stack_address);
 
 	/*
 	 * Take abs value, since stacks grow up on some machines, down on others
diff --git a/.cirrus.tasks.yml b/.cirrus.tasks.yml
index a22cef063f3..8683d1ae9c7 100644
--- a/.cirrus.tasks.yml
+++ b/.cirrus.tasks.yml
@@ -447,7 +447,7 @@ task:
     # print_stacktraces=1,verbosity=2, duh
     # detect_leaks=0: too many uninteresting leak errors in short-lived binaries
     UBSAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:verbosity=2
-    ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0:detect_stack_use_after_return=0
+    ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0
 
     # SANITIZER_FLAGS is set in the tasks below
     CFLAGS: -Og -ggdb -fno-sanitize-recover=all $SANITIZER_FLAGS
-- 
2.53.0.1.gb2826b52eb


--7e2sypi5gxvkgog6--





^ permalink  raw  reply  [nested|flat] 114+ messages in thread

* [PATCH v2 1/4] Make stack depth check work with asan's use-after-return
@ 2026-02-07 19:04 Andres Freund <[email protected]>
  0 siblings, 0 replies; 114+ messages in thread

From: Andres Freund @ 2026-02-07 19:04 UTC (permalink / raw)

With address sanitizer's stack-use-after-return check, stack variables are
moved to heap allocations, to allow to detect references to the memory at a
later time. That broke our stack-depth check, which is why we had to disable
detect_stack_use_after_return in CI. Luckily __builtin_frame_address() works
correctly, even under asan, so use that.

Author:
Reviewed-by:
Discussion: https://postgr.es/m/
Backpatch:
---
 src/backend/utils/misc/stack_depth.c | 17 ++++++++++++++++-
 .cirrus.tasks.yml                    |  2 +-
 2 files changed, 17 insertions(+), 2 deletions(-)

diff --git a/src/backend/utils/misc/stack_depth.c b/src/backend/utils/misc/stack_depth.c
index 61a07cf824e..9955e28c176 100644
--- a/src/backend/utils/misc/stack_depth.c
+++ b/src/backend/utils/misc/stack_depth.c
@@ -108,13 +108,28 @@ check_stack_depth(void)
 bool
 stack_is_too_deep(void)
 {
+#ifndef HAVE__BUILTIN_FRAME_ADDRESS
 	char		stack_top_loc;
+#endif
 	ssize_t		stack_depth;
+	char	   *stack_address;
+
+	/*
+	 * With address sanitizer's stack-use-after-return check, stack variables
+	 * are moved to heap allocations, to allow to detect references to the
+	 * memory at a later time. That would break our stack-depth check. Luckily
+	 * __builtin_frame_address() works correctly, even under asan.
+	 */
+#ifndef HAVE__BUILTIN_FRAME_ADDRESS
+	stack_address = &stack_top_loc;
+#else
+	stack_address = (char *) __builtin_frame_address(0);
+#endif
 
 	/*
 	 * Compute distance from reference point to my local variables
 	 */
-	stack_depth = (ssize_t) (stack_base_ptr - &stack_top_loc);
+	stack_depth = (ssize_t) (stack_base_ptr - stack_address);
 
 	/*
 	 * Take abs value, since stacks grow up on some machines, down on others
diff --git a/.cirrus.tasks.yml b/.cirrus.tasks.yml
index a22cef063f3..8683d1ae9c7 100644
--- a/.cirrus.tasks.yml
+++ b/.cirrus.tasks.yml
@@ -447,7 +447,7 @@ task:
     # print_stacktraces=1,verbosity=2, duh
     # detect_leaks=0: too many uninteresting leak errors in short-lived binaries
     UBSAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:verbosity=2
-    ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0:detect_stack_use_after_return=0
+    ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0
 
     # SANITIZER_FLAGS is set in the tasks below
     CFLAGS: -Og -ggdb -fno-sanitize-recover=all $SANITIZER_FLAGS
-- 
2.53.0.1.gb2826b52eb


--7e2sypi5gxvkgog6--





^ permalink  raw  reply  [nested|flat] 114+ messages in thread

* [PATCH v2 1/4] Make stack depth check work with asan's use-after-return
@ 2026-02-07 19:04 Andres Freund <[email protected]>
  0 siblings, 0 replies; 114+ messages in thread

From: Andres Freund @ 2026-02-07 19:04 UTC (permalink / raw)

With address sanitizer's stack-use-after-return check, stack variables are
moved to heap allocations, to allow to detect references to the memory at a
later time. That broke our stack-depth check, which is why we had to disable
detect_stack_use_after_return in CI. Luckily __builtin_frame_address() works
correctly, even under asan, so use that.

Author:
Reviewed-by:
Discussion: https://postgr.es/m/
Backpatch:
---
 src/backend/utils/misc/stack_depth.c | 17 ++++++++++++++++-
 .cirrus.tasks.yml                    |  2 +-
 2 files changed, 17 insertions(+), 2 deletions(-)

diff --git a/src/backend/utils/misc/stack_depth.c b/src/backend/utils/misc/stack_depth.c
index 61a07cf824e..9955e28c176 100644
--- a/src/backend/utils/misc/stack_depth.c
+++ b/src/backend/utils/misc/stack_depth.c
@@ -108,13 +108,28 @@ check_stack_depth(void)
 bool
 stack_is_too_deep(void)
 {
+#ifndef HAVE__BUILTIN_FRAME_ADDRESS
 	char		stack_top_loc;
+#endif
 	ssize_t		stack_depth;
+	char	   *stack_address;
+
+	/*
+	 * With address sanitizer's stack-use-after-return check, stack variables
+	 * are moved to heap allocations, to allow to detect references to the
+	 * memory at a later time. That would break our stack-depth check. Luckily
+	 * __builtin_frame_address() works correctly, even under asan.
+	 */
+#ifndef HAVE__BUILTIN_FRAME_ADDRESS
+	stack_address = &stack_top_loc;
+#else
+	stack_address = (char *) __builtin_frame_address(0);
+#endif
 
 	/*
 	 * Compute distance from reference point to my local variables
 	 */
-	stack_depth = (ssize_t) (stack_base_ptr - &stack_top_loc);
+	stack_depth = (ssize_t) (stack_base_ptr - stack_address);
 
 	/*
 	 * Take abs value, since stacks grow up on some machines, down on others
diff --git a/.cirrus.tasks.yml b/.cirrus.tasks.yml
index a22cef063f3..8683d1ae9c7 100644
--- a/.cirrus.tasks.yml
+++ b/.cirrus.tasks.yml
@@ -447,7 +447,7 @@ task:
     # print_stacktraces=1,verbosity=2, duh
     # detect_leaks=0: too many uninteresting leak errors in short-lived binaries
     UBSAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:verbosity=2
-    ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0:detect_stack_use_after_return=0
+    ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0
 
     # SANITIZER_FLAGS is set in the tasks below
     CFLAGS: -Og -ggdb -fno-sanitize-recover=all $SANITIZER_FLAGS
-- 
2.53.0.1.gb2826b52eb


--7e2sypi5gxvkgog6--





^ permalink  raw  reply  [nested|flat] 114+ messages in thread

* [PATCH v2 1/4] Make stack depth check work with asan's use-after-return
@ 2026-02-07 19:04 Andres Freund <[email protected]>
  0 siblings, 0 replies; 114+ messages in thread

From: Andres Freund @ 2026-02-07 19:04 UTC (permalink / raw)

With address sanitizer's stack-use-after-return check, stack variables are
moved to heap allocations, to allow to detect references to the memory at a
later time. That broke our stack-depth check, which is why we had to disable
detect_stack_use_after_return in CI. Luckily __builtin_frame_address() works
correctly, even under asan, so use that.

Author:
Reviewed-by:
Discussion: https://postgr.es/m/
Backpatch:
---
 src/backend/utils/misc/stack_depth.c | 17 ++++++++++++++++-
 .cirrus.tasks.yml                    |  2 +-
 2 files changed, 17 insertions(+), 2 deletions(-)

diff --git a/src/backend/utils/misc/stack_depth.c b/src/backend/utils/misc/stack_depth.c
index 61a07cf824e..9955e28c176 100644
--- a/src/backend/utils/misc/stack_depth.c
+++ b/src/backend/utils/misc/stack_depth.c
@@ -108,13 +108,28 @@ check_stack_depth(void)
 bool
 stack_is_too_deep(void)
 {
+#ifndef HAVE__BUILTIN_FRAME_ADDRESS
 	char		stack_top_loc;
+#endif
 	ssize_t		stack_depth;
+	char	   *stack_address;
+
+	/*
+	 * With address sanitizer's stack-use-after-return check, stack variables
+	 * are moved to heap allocations, to allow to detect references to the
+	 * memory at a later time. That would break our stack-depth check. Luckily
+	 * __builtin_frame_address() works correctly, even under asan.
+	 */
+#ifndef HAVE__BUILTIN_FRAME_ADDRESS
+	stack_address = &stack_top_loc;
+#else
+	stack_address = (char *) __builtin_frame_address(0);
+#endif
 
 	/*
 	 * Compute distance from reference point to my local variables
 	 */
-	stack_depth = (ssize_t) (stack_base_ptr - &stack_top_loc);
+	stack_depth = (ssize_t) (stack_base_ptr - stack_address);
 
 	/*
 	 * Take abs value, since stacks grow up on some machines, down on others
diff --git a/.cirrus.tasks.yml b/.cirrus.tasks.yml
index a22cef063f3..8683d1ae9c7 100644
--- a/.cirrus.tasks.yml
+++ b/.cirrus.tasks.yml
@@ -447,7 +447,7 @@ task:
     # print_stacktraces=1,verbosity=2, duh
     # detect_leaks=0: too many uninteresting leak errors in short-lived binaries
     UBSAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:verbosity=2
-    ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0:detect_stack_use_after_return=0
+    ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0
 
     # SANITIZER_FLAGS is set in the tasks below
     CFLAGS: -Og -ggdb -fno-sanitize-recover=all $SANITIZER_FLAGS
-- 
2.53.0.1.gb2826b52eb


--7e2sypi5gxvkgog6--





^ permalink  raw  reply  [nested|flat] 114+ messages in thread

* [PATCH v2 1/4] Make stack depth check work with asan's use-after-return
@ 2026-02-07 19:04 Andres Freund <[email protected]>
  0 siblings, 0 replies; 114+ messages in thread

From: Andres Freund @ 2026-02-07 19:04 UTC (permalink / raw)

With address sanitizer's stack-use-after-return check, stack variables are
moved to heap allocations, to allow to detect references to the memory at a
later time. That broke our stack-depth check, which is why we had to disable
detect_stack_use_after_return in CI. Luckily __builtin_frame_address() works
correctly, even under asan, so use that.

Author:
Reviewed-by:
Discussion: https://postgr.es/m/
Backpatch:
---
 src/backend/utils/misc/stack_depth.c | 17 ++++++++++++++++-
 .cirrus.tasks.yml                    |  2 +-
 2 files changed, 17 insertions(+), 2 deletions(-)

diff --git a/src/backend/utils/misc/stack_depth.c b/src/backend/utils/misc/stack_depth.c
index 61a07cf824e..9955e28c176 100644
--- a/src/backend/utils/misc/stack_depth.c
+++ b/src/backend/utils/misc/stack_depth.c
@@ -108,13 +108,28 @@ check_stack_depth(void)
 bool
 stack_is_too_deep(void)
 {
+#ifndef HAVE__BUILTIN_FRAME_ADDRESS
 	char		stack_top_loc;
+#endif
 	ssize_t		stack_depth;
+	char	   *stack_address;
+
+	/*
+	 * With address sanitizer's stack-use-after-return check, stack variables
+	 * are moved to heap allocations, to allow to detect references to the
+	 * memory at a later time. That would break our stack-depth check. Luckily
+	 * __builtin_frame_address() works correctly, even under asan.
+	 */
+#ifndef HAVE__BUILTIN_FRAME_ADDRESS
+	stack_address = &stack_top_loc;
+#else
+	stack_address = (char *) __builtin_frame_address(0);
+#endif
 
 	/*
 	 * Compute distance from reference point to my local variables
 	 */
-	stack_depth = (ssize_t) (stack_base_ptr - &stack_top_loc);
+	stack_depth = (ssize_t) (stack_base_ptr - stack_address);
 
 	/*
 	 * Take abs value, since stacks grow up on some machines, down on others
diff --git a/.cirrus.tasks.yml b/.cirrus.tasks.yml
index a22cef063f3..8683d1ae9c7 100644
--- a/.cirrus.tasks.yml
+++ b/.cirrus.tasks.yml
@@ -447,7 +447,7 @@ task:
     # print_stacktraces=1,verbosity=2, duh
     # detect_leaks=0: too many uninteresting leak errors in short-lived binaries
     UBSAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:verbosity=2
-    ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0:detect_stack_use_after_return=0
+    ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0
 
     # SANITIZER_FLAGS is set in the tasks below
     CFLAGS: -Og -ggdb -fno-sanitize-recover=all $SANITIZER_FLAGS
-- 
2.53.0.1.gb2826b52eb


--7e2sypi5gxvkgog6--





^ permalink  raw  reply  [nested|flat] 114+ messages in thread

* [PATCH v2 1/4] Make stack depth check work with asan's use-after-return
@ 2026-02-07 19:04 Andres Freund <[email protected]>
  0 siblings, 0 replies; 114+ messages in thread

From: Andres Freund @ 2026-02-07 19:04 UTC (permalink / raw)

With address sanitizer's stack-use-after-return check, stack variables are
moved to heap allocations, to allow to detect references to the memory at a
later time. That broke our stack-depth check, which is why we had to disable
detect_stack_use_after_return in CI. Luckily __builtin_frame_address() works
correctly, even under asan, so use that.

Author:
Reviewed-by:
Discussion: https://postgr.es/m/
Backpatch:
---
 src/backend/utils/misc/stack_depth.c | 17 ++++++++++++++++-
 .cirrus.tasks.yml                    |  2 +-
 2 files changed, 17 insertions(+), 2 deletions(-)

diff --git a/src/backend/utils/misc/stack_depth.c b/src/backend/utils/misc/stack_depth.c
index 61a07cf824e..9955e28c176 100644
--- a/src/backend/utils/misc/stack_depth.c
+++ b/src/backend/utils/misc/stack_depth.c
@@ -108,13 +108,28 @@ check_stack_depth(void)
 bool
 stack_is_too_deep(void)
 {
+#ifndef HAVE__BUILTIN_FRAME_ADDRESS
 	char		stack_top_loc;
+#endif
 	ssize_t		stack_depth;
+	char	   *stack_address;
+
+	/*
+	 * With address sanitizer's stack-use-after-return check, stack variables
+	 * are moved to heap allocations, to allow to detect references to the
+	 * memory at a later time. That would break our stack-depth check. Luckily
+	 * __builtin_frame_address() works correctly, even under asan.
+	 */
+#ifndef HAVE__BUILTIN_FRAME_ADDRESS
+	stack_address = &stack_top_loc;
+#else
+	stack_address = (char *) __builtin_frame_address(0);
+#endif
 
 	/*
 	 * Compute distance from reference point to my local variables
 	 */
-	stack_depth = (ssize_t) (stack_base_ptr - &stack_top_loc);
+	stack_depth = (ssize_t) (stack_base_ptr - stack_address);
 
 	/*
 	 * Take abs value, since stacks grow up on some machines, down on others
diff --git a/.cirrus.tasks.yml b/.cirrus.tasks.yml
index a22cef063f3..8683d1ae9c7 100644
--- a/.cirrus.tasks.yml
+++ b/.cirrus.tasks.yml
@@ -447,7 +447,7 @@ task:
     # print_stacktraces=1,verbosity=2, duh
     # detect_leaks=0: too many uninteresting leak errors in short-lived binaries
     UBSAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:verbosity=2
-    ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0:detect_stack_use_after_return=0
+    ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0
 
     # SANITIZER_FLAGS is set in the tasks below
     CFLAGS: -Og -ggdb -fno-sanitize-recover=all $SANITIZER_FLAGS
-- 
2.53.0.1.gb2826b52eb


--7e2sypi5gxvkgog6--





^ permalink  raw  reply  [nested|flat] 114+ messages in thread

* [PATCH v2 1/4] Make stack depth check work with asan's use-after-return
@ 2026-02-07 19:04 Andres Freund <[email protected]>
  0 siblings, 0 replies; 114+ messages in thread

From: Andres Freund @ 2026-02-07 19:04 UTC (permalink / raw)

With address sanitizer's stack-use-after-return check, stack variables are
moved to heap allocations, to allow to detect references to the memory at a
later time. That broke our stack-depth check, which is why we had to disable
detect_stack_use_after_return in CI. Luckily __builtin_frame_address() works
correctly, even under asan, so use that.

Author:
Reviewed-by:
Discussion: https://postgr.es/m/
Backpatch:
---
 src/backend/utils/misc/stack_depth.c | 17 ++++++++++++++++-
 .cirrus.tasks.yml                    |  2 +-
 2 files changed, 17 insertions(+), 2 deletions(-)

diff --git a/src/backend/utils/misc/stack_depth.c b/src/backend/utils/misc/stack_depth.c
index 61a07cf824e..9955e28c176 100644
--- a/src/backend/utils/misc/stack_depth.c
+++ b/src/backend/utils/misc/stack_depth.c
@@ -108,13 +108,28 @@ check_stack_depth(void)
 bool
 stack_is_too_deep(void)
 {
+#ifndef HAVE__BUILTIN_FRAME_ADDRESS
 	char		stack_top_loc;
+#endif
 	ssize_t		stack_depth;
+	char	   *stack_address;
+
+	/*
+	 * With address sanitizer's stack-use-after-return check, stack variables
+	 * are moved to heap allocations, to allow to detect references to the
+	 * memory at a later time. That would break our stack-depth check. Luckily
+	 * __builtin_frame_address() works correctly, even under asan.
+	 */
+#ifndef HAVE__BUILTIN_FRAME_ADDRESS
+	stack_address = &stack_top_loc;
+#else
+	stack_address = (char *) __builtin_frame_address(0);
+#endif
 
 	/*
 	 * Compute distance from reference point to my local variables
 	 */
-	stack_depth = (ssize_t) (stack_base_ptr - &stack_top_loc);
+	stack_depth = (ssize_t) (stack_base_ptr - stack_address);
 
 	/*
 	 * Take abs value, since stacks grow up on some machines, down on others
diff --git a/.cirrus.tasks.yml b/.cirrus.tasks.yml
index a22cef063f3..8683d1ae9c7 100644
--- a/.cirrus.tasks.yml
+++ b/.cirrus.tasks.yml
@@ -447,7 +447,7 @@ task:
     # print_stacktraces=1,verbosity=2, duh
     # detect_leaks=0: too many uninteresting leak errors in short-lived binaries
     UBSAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:verbosity=2
-    ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0:detect_stack_use_after_return=0
+    ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0
 
     # SANITIZER_FLAGS is set in the tasks below
     CFLAGS: -Og -ggdb -fno-sanitize-recover=all $SANITIZER_FLAGS
-- 
2.53.0.1.gb2826b52eb


--7e2sypi5gxvkgog6--





^ permalink  raw  reply  [nested|flat] 114+ messages in thread

* [PATCH v2 1/4] Make stack depth check work with asan's use-after-return
@ 2026-02-07 19:04 Andres Freund <[email protected]>
  0 siblings, 0 replies; 114+ messages in thread

From: Andres Freund @ 2026-02-07 19:04 UTC (permalink / raw)

With address sanitizer's stack-use-after-return check, stack variables are
moved to heap allocations, to allow to detect references to the memory at a
later time. That broke our stack-depth check, which is why we had to disable
detect_stack_use_after_return in CI. Luckily __builtin_frame_address() works
correctly, even under asan, so use that.

Author:
Reviewed-by:
Discussion: https://postgr.es/m/
Backpatch:
---
 src/backend/utils/misc/stack_depth.c | 17 ++++++++++++++++-
 .cirrus.tasks.yml                    |  2 +-
 2 files changed, 17 insertions(+), 2 deletions(-)

diff --git a/src/backend/utils/misc/stack_depth.c b/src/backend/utils/misc/stack_depth.c
index 61a07cf824e..9955e28c176 100644
--- a/src/backend/utils/misc/stack_depth.c
+++ b/src/backend/utils/misc/stack_depth.c
@@ -108,13 +108,28 @@ check_stack_depth(void)
 bool
 stack_is_too_deep(void)
 {
+#ifndef HAVE__BUILTIN_FRAME_ADDRESS
 	char		stack_top_loc;
+#endif
 	ssize_t		stack_depth;
+	char	   *stack_address;
+
+	/*
+	 * With address sanitizer's stack-use-after-return check, stack variables
+	 * are moved to heap allocations, to allow to detect references to the
+	 * memory at a later time. That would break our stack-depth check. Luckily
+	 * __builtin_frame_address() works correctly, even under asan.
+	 */
+#ifndef HAVE__BUILTIN_FRAME_ADDRESS
+	stack_address = &stack_top_loc;
+#else
+	stack_address = (char *) __builtin_frame_address(0);
+#endif
 
 	/*
 	 * Compute distance from reference point to my local variables
 	 */
-	stack_depth = (ssize_t) (stack_base_ptr - &stack_top_loc);
+	stack_depth = (ssize_t) (stack_base_ptr - stack_address);
 
 	/*
 	 * Take abs value, since stacks grow up on some machines, down on others
diff --git a/.cirrus.tasks.yml b/.cirrus.tasks.yml
index a22cef063f3..8683d1ae9c7 100644
--- a/.cirrus.tasks.yml
+++ b/.cirrus.tasks.yml
@@ -447,7 +447,7 @@ task:
     # print_stacktraces=1,verbosity=2, duh
     # detect_leaks=0: too many uninteresting leak errors in short-lived binaries
     UBSAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:verbosity=2
-    ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0:detect_stack_use_after_return=0
+    ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0
 
     # SANITIZER_FLAGS is set in the tasks below
     CFLAGS: -Og -ggdb -fno-sanitize-recover=all $SANITIZER_FLAGS
-- 
2.53.0.1.gb2826b52eb


--7e2sypi5gxvkgog6--





^ permalink  raw  reply  [nested|flat] 114+ messages in thread

* [PATCH v2 1/4] Make stack depth check work with asan's use-after-return
@ 2026-02-07 19:04 Andres Freund <[email protected]>
  0 siblings, 0 replies; 114+ messages in thread

From: Andres Freund @ 2026-02-07 19:04 UTC (permalink / raw)

With address sanitizer's stack-use-after-return check, stack variables are
moved to heap allocations, to allow to detect references to the memory at a
later time. That broke our stack-depth check, which is why we had to disable
detect_stack_use_after_return in CI. Luckily __builtin_frame_address() works
correctly, even under asan, so use that.

Author:
Reviewed-by:
Discussion: https://postgr.es/m/
Backpatch:
---
 src/backend/utils/misc/stack_depth.c | 17 ++++++++++++++++-
 .cirrus.tasks.yml                    |  2 +-
 2 files changed, 17 insertions(+), 2 deletions(-)

diff --git a/src/backend/utils/misc/stack_depth.c b/src/backend/utils/misc/stack_depth.c
index 61a07cf824e..9955e28c176 100644
--- a/src/backend/utils/misc/stack_depth.c
+++ b/src/backend/utils/misc/stack_depth.c
@@ -108,13 +108,28 @@ check_stack_depth(void)
 bool
 stack_is_too_deep(void)
 {
+#ifndef HAVE__BUILTIN_FRAME_ADDRESS
 	char		stack_top_loc;
+#endif
 	ssize_t		stack_depth;
+	char	   *stack_address;
+
+	/*
+	 * With address sanitizer's stack-use-after-return check, stack variables
+	 * are moved to heap allocations, to allow to detect references to the
+	 * memory at a later time. That would break our stack-depth check. Luckily
+	 * __builtin_frame_address() works correctly, even under asan.
+	 */
+#ifndef HAVE__BUILTIN_FRAME_ADDRESS
+	stack_address = &stack_top_loc;
+#else
+	stack_address = (char *) __builtin_frame_address(0);
+#endif
 
 	/*
 	 * Compute distance from reference point to my local variables
 	 */
-	stack_depth = (ssize_t) (stack_base_ptr - &stack_top_loc);
+	stack_depth = (ssize_t) (stack_base_ptr - stack_address);
 
 	/*
 	 * Take abs value, since stacks grow up on some machines, down on others
diff --git a/.cirrus.tasks.yml b/.cirrus.tasks.yml
index a22cef063f3..8683d1ae9c7 100644
--- a/.cirrus.tasks.yml
+++ b/.cirrus.tasks.yml
@@ -447,7 +447,7 @@ task:
     # print_stacktraces=1,verbosity=2, duh
     # detect_leaks=0: too many uninteresting leak errors in short-lived binaries
     UBSAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:verbosity=2
-    ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0:detect_stack_use_after_return=0
+    ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0
 
     # SANITIZER_FLAGS is set in the tasks below
     CFLAGS: -Og -ggdb -fno-sanitize-recover=all $SANITIZER_FLAGS
-- 
2.53.0.1.gb2826b52eb


--7e2sypi5gxvkgog6--





^ permalink  raw  reply  [nested|flat] 114+ messages in thread

* [PATCH v2 1/4] Make stack depth check work with asan's use-after-return
@ 2026-02-07 19:04 Andres Freund <[email protected]>
  0 siblings, 0 replies; 114+ messages in thread

From: Andres Freund @ 2026-02-07 19:04 UTC (permalink / raw)

With address sanitizer's stack-use-after-return check, stack variables are
moved to heap allocations, to allow to detect references to the memory at a
later time. That broke our stack-depth check, which is why we had to disable
detect_stack_use_after_return in CI. Luckily __builtin_frame_address() works
correctly, even under asan, so use that.

Author:
Reviewed-by:
Discussion: https://postgr.es/m/
Backpatch:
---
 src/backend/utils/misc/stack_depth.c | 17 ++++++++++++++++-
 .cirrus.tasks.yml                    |  2 +-
 2 files changed, 17 insertions(+), 2 deletions(-)

diff --git a/src/backend/utils/misc/stack_depth.c b/src/backend/utils/misc/stack_depth.c
index 61a07cf824e..9955e28c176 100644
--- a/src/backend/utils/misc/stack_depth.c
+++ b/src/backend/utils/misc/stack_depth.c
@@ -108,13 +108,28 @@ check_stack_depth(void)
 bool
 stack_is_too_deep(void)
 {
+#ifndef HAVE__BUILTIN_FRAME_ADDRESS
 	char		stack_top_loc;
+#endif
 	ssize_t		stack_depth;
+	char	   *stack_address;
+
+	/*
+	 * With address sanitizer's stack-use-after-return check, stack variables
+	 * are moved to heap allocations, to allow to detect references to the
+	 * memory at a later time. That would break our stack-depth check. Luckily
+	 * __builtin_frame_address() works correctly, even under asan.
+	 */
+#ifndef HAVE__BUILTIN_FRAME_ADDRESS
+	stack_address = &stack_top_loc;
+#else
+	stack_address = (char *) __builtin_frame_address(0);
+#endif
 
 	/*
 	 * Compute distance from reference point to my local variables
 	 */
-	stack_depth = (ssize_t) (stack_base_ptr - &stack_top_loc);
+	stack_depth = (ssize_t) (stack_base_ptr - stack_address);
 
 	/*
 	 * Take abs value, since stacks grow up on some machines, down on others
diff --git a/.cirrus.tasks.yml b/.cirrus.tasks.yml
index a22cef063f3..8683d1ae9c7 100644
--- a/.cirrus.tasks.yml
+++ b/.cirrus.tasks.yml
@@ -447,7 +447,7 @@ task:
     # print_stacktraces=1,verbosity=2, duh
     # detect_leaks=0: too many uninteresting leak errors in short-lived binaries
     UBSAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:verbosity=2
-    ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0:detect_stack_use_after_return=0
+    ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0
 
     # SANITIZER_FLAGS is set in the tasks below
     CFLAGS: -Og -ggdb -fno-sanitize-recover=all $SANITIZER_FLAGS
-- 
2.53.0.1.gb2826b52eb


--7e2sypi5gxvkgog6--





^ permalink  raw  reply  [nested|flat] 114+ messages in thread

* [PATCH v2 1/4] Make stack depth check work with asan's use-after-return
@ 2026-02-07 19:04 Andres Freund <[email protected]>
  0 siblings, 0 replies; 114+ messages in thread

From: Andres Freund @ 2026-02-07 19:04 UTC (permalink / raw)

With address sanitizer's stack-use-after-return check, stack variables are
moved to heap allocations, to allow to detect references to the memory at a
later time. That broke our stack-depth check, which is why we had to disable
detect_stack_use_after_return in CI. Luckily __builtin_frame_address() works
correctly, even under asan, so use that.

Author:
Reviewed-by:
Discussion: https://postgr.es/m/
Backpatch:
---
 src/backend/utils/misc/stack_depth.c | 17 ++++++++++++++++-
 .cirrus.tasks.yml                    |  2 +-
 2 files changed, 17 insertions(+), 2 deletions(-)

diff --git a/src/backend/utils/misc/stack_depth.c b/src/backend/utils/misc/stack_depth.c
index 61a07cf824e..9955e28c176 100644
--- a/src/backend/utils/misc/stack_depth.c
+++ b/src/backend/utils/misc/stack_depth.c
@@ -108,13 +108,28 @@ check_stack_depth(void)
 bool
 stack_is_too_deep(void)
 {
+#ifndef HAVE__BUILTIN_FRAME_ADDRESS
 	char		stack_top_loc;
+#endif
 	ssize_t		stack_depth;
+	char	   *stack_address;
+
+	/*
+	 * With address sanitizer's stack-use-after-return check, stack variables
+	 * are moved to heap allocations, to allow to detect references to the
+	 * memory at a later time. That would break our stack-depth check. Luckily
+	 * __builtin_frame_address() works correctly, even under asan.
+	 */
+#ifndef HAVE__BUILTIN_FRAME_ADDRESS
+	stack_address = &stack_top_loc;
+#else
+	stack_address = (char *) __builtin_frame_address(0);
+#endif
 
 	/*
 	 * Compute distance from reference point to my local variables
 	 */
-	stack_depth = (ssize_t) (stack_base_ptr - &stack_top_loc);
+	stack_depth = (ssize_t) (stack_base_ptr - stack_address);
 
 	/*
 	 * Take abs value, since stacks grow up on some machines, down on others
diff --git a/.cirrus.tasks.yml b/.cirrus.tasks.yml
index a22cef063f3..8683d1ae9c7 100644
--- a/.cirrus.tasks.yml
+++ b/.cirrus.tasks.yml
@@ -447,7 +447,7 @@ task:
     # print_stacktraces=1,verbosity=2, duh
     # detect_leaks=0: too many uninteresting leak errors in short-lived binaries
     UBSAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:verbosity=2
-    ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0:detect_stack_use_after_return=0
+    ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0
 
     # SANITIZER_FLAGS is set in the tasks below
     CFLAGS: -Og -ggdb -fno-sanitize-recover=all $SANITIZER_FLAGS
-- 
2.53.0.1.gb2826b52eb


--7e2sypi5gxvkgog6--





^ permalink  raw  reply  [nested|flat] 114+ messages in thread

* [PATCH v2 1/4] Make stack depth check work with asan's use-after-return
@ 2026-02-07 19:04 Andres Freund <[email protected]>
  0 siblings, 0 replies; 114+ messages in thread

From: Andres Freund @ 2026-02-07 19:04 UTC (permalink / raw)

With address sanitizer's stack-use-after-return check, stack variables are
moved to heap allocations, to allow to detect references to the memory at a
later time. That broke our stack-depth check, which is why we had to disable
detect_stack_use_after_return in CI. Luckily __builtin_frame_address() works
correctly, even under asan, so use that.

Author:
Reviewed-by:
Discussion: https://postgr.es/m/
Backpatch:
---
 src/backend/utils/misc/stack_depth.c | 17 ++++++++++++++++-
 .cirrus.tasks.yml                    |  2 +-
 2 files changed, 17 insertions(+), 2 deletions(-)

diff --git a/src/backend/utils/misc/stack_depth.c b/src/backend/utils/misc/stack_depth.c
index 61a07cf824e..9955e28c176 100644
--- a/src/backend/utils/misc/stack_depth.c
+++ b/src/backend/utils/misc/stack_depth.c
@@ -108,13 +108,28 @@ check_stack_depth(void)
 bool
 stack_is_too_deep(void)
 {
+#ifndef HAVE__BUILTIN_FRAME_ADDRESS
 	char		stack_top_loc;
+#endif
 	ssize_t		stack_depth;
+	char	   *stack_address;
+
+	/*
+	 * With address sanitizer's stack-use-after-return check, stack variables
+	 * are moved to heap allocations, to allow to detect references to the
+	 * memory at a later time. That would break our stack-depth check. Luckily
+	 * __builtin_frame_address() works correctly, even under asan.
+	 */
+#ifndef HAVE__BUILTIN_FRAME_ADDRESS
+	stack_address = &stack_top_loc;
+#else
+	stack_address = (char *) __builtin_frame_address(0);
+#endif
 
 	/*
 	 * Compute distance from reference point to my local variables
 	 */
-	stack_depth = (ssize_t) (stack_base_ptr - &stack_top_loc);
+	stack_depth = (ssize_t) (stack_base_ptr - stack_address);
 
 	/*
 	 * Take abs value, since stacks grow up on some machines, down on others
diff --git a/.cirrus.tasks.yml b/.cirrus.tasks.yml
index a22cef063f3..8683d1ae9c7 100644
--- a/.cirrus.tasks.yml
+++ b/.cirrus.tasks.yml
@@ -447,7 +447,7 @@ task:
     # print_stacktraces=1,verbosity=2, duh
     # detect_leaks=0: too many uninteresting leak errors in short-lived binaries
     UBSAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:verbosity=2
-    ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0:detect_stack_use_after_return=0
+    ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0
 
     # SANITIZER_FLAGS is set in the tasks below
     CFLAGS: -Og -ggdb -fno-sanitize-recover=all $SANITIZER_FLAGS
-- 
2.53.0.1.gb2826b52eb


--7e2sypi5gxvkgog6--





^ permalink  raw  reply  [nested|flat] 114+ messages in thread

* [PATCH v2 1/4] Make stack depth check work with asan's use-after-return
@ 2026-02-07 19:04 Andres Freund <[email protected]>
  0 siblings, 0 replies; 114+ messages in thread

From: Andres Freund @ 2026-02-07 19:04 UTC (permalink / raw)

With address sanitizer's stack-use-after-return check, stack variables are
moved to heap allocations, to allow to detect references to the memory at a
later time. That broke our stack-depth check, which is why we had to disable
detect_stack_use_after_return in CI. Luckily __builtin_frame_address() works
correctly, even under asan, so use that.

Author:
Reviewed-by:
Discussion: https://postgr.es/m/
Backpatch:
---
 src/backend/utils/misc/stack_depth.c | 17 ++++++++++++++++-
 .cirrus.tasks.yml                    |  2 +-
 2 files changed, 17 insertions(+), 2 deletions(-)

diff --git a/src/backend/utils/misc/stack_depth.c b/src/backend/utils/misc/stack_depth.c
index 61a07cf824e..9955e28c176 100644
--- a/src/backend/utils/misc/stack_depth.c
+++ b/src/backend/utils/misc/stack_depth.c
@@ -108,13 +108,28 @@ check_stack_depth(void)
 bool
 stack_is_too_deep(void)
 {
+#ifndef HAVE__BUILTIN_FRAME_ADDRESS
 	char		stack_top_loc;
+#endif
 	ssize_t		stack_depth;
+	char	   *stack_address;
+
+	/*
+	 * With address sanitizer's stack-use-after-return check, stack variables
+	 * are moved to heap allocations, to allow to detect references to the
+	 * memory at a later time. That would break our stack-depth check. Luckily
+	 * __builtin_frame_address() works correctly, even under asan.
+	 */
+#ifndef HAVE__BUILTIN_FRAME_ADDRESS
+	stack_address = &stack_top_loc;
+#else
+	stack_address = (char *) __builtin_frame_address(0);
+#endif
 
 	/*
 	 * Compute distance from reference point to my local variables
 	 */
-	stack_depth = (ssize_t) (stack_base_ptr - &stack_top_loc);
+	stack_depth = (ssize_t) (stack_base_ptr - stack_address);
 
 	/*
 	 * Take abs value, since stacks grow up on some machines, down on others
diff --git a/.cirrus.tasks.yml b/.cirrus.tasks.yml
index a22cef063f3..8683d1ae9c7 100644
--- a/.cirrus.tasks.yml
+++ b/.cirrus.tasks.yml
@@ -447,7 +447,7 @@ task:
     # print_stacktraces=1,verbosity=2, duh
     # detect_leaks=0: too many uninteresting leak errors in short-lived binaries
     UBSAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:verbosity=2
-    ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0:detect_stack_use_after_return=0
+    ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0
 
     # SANITIZER_FLAGS is set in the tasks below
     CFLAGS: -Og -ggdb -fno-sanitize-recover=all $SANITIZER_FLAGS
-- 
2.53.0.1.gb2826b52eb


--7e2sypi5gxvkgog6--





^ permalink  raw  reply  [nested|flat] 114+ messages in thread

* [PATCH v2 1/4] Make stack depth check work with asan's use-after-return
@ 2026-02-07 19:04 Andres Freund <[email protected]>
  0 siblings, 0 replies; 114+ messages in thread

From: Andres Freund @ 2026-02-07 19:04 UTC (permalink / raw)

With address sanitizer's stack-use-after-return check, stack variables are
moved to heap allocations, to allow to detect references to the memory at a
later time. That broke our stack-depth check, which is why we had to disable
detect_stack_use_after_return in CI. Luckily __builtin_frame_address() works
correctly, even under asan, so use that.

Author:
Reviewed-by:
Discussion: https://postgr.es/m/
Backpatch:
---
 src/backend/utils/misc/stack_depth.c | 17 ++++++++++++++++-
 .cirrus.tasks.yml                    |  2 +-
 2 files changed, 17 insertions(+), 2 deletions(-)

diff --git a/src/backend/utils/misc/stack_depth.c b/src/backend/utils/misc/stack_depth.c
index 61a07cf824e..9955e28c176 100644
--- a/src/backend/utils/misc/stack_depth.c
+++ b/src/backend/utils/misc/stack_depth.c
@@ -108,13 +108,28 @@ check_stack_depth(void)
 bool
 stack_is_too_deep(void)
 {
+#ifndef HAVE__BUILTIN_FRAME_ADDRESS
 	char		stack_top_loc;
+#endif
 	ssize_t		stack_depth;
+	char	   *stack_address;
+
+	/*
+	 * With address sanitizer's stack-use-after-return check, stack variables
+	 * are moved to heap allocations, to allow to detect references to the
+	 * memory at a later time. That would break our stack-depth check. Luckily
+	 * __builtin_frame_address() works correctly, even under asan.
+	 */
+#ifndef HAVE__BUILTIN_FRAME_ADDRESS
+	stack_address = &stack_top_loc;
+#else
+	stack_address = (char *) __builtin_frame_address(0);
+#endif
 
 	/*
 	 * Compute distance from reference point to my local variables
 	 */
-	stack_depth = (ssize_t) (stack_base_ptr - &stack_top_loc);
+	stack_depth = (ssize_t) (stack_base_ptr - stack_address);
 
 	/*
 	 * Take abs value, since stacks grow up on some machines, down on others
diff --git a/.cirrus.tasks.yml b/.cirrus.tasks.yml
index a22cef063f3..8683d1ae9c7 100644
--- a/.cirrus.tasks.yml
+++ b/.cirrus.tasks.yml
@@ -447,7 +447,7 @@ task:
     # print_stacktraces=1,verbosity=2, duh
     # detect_leaks=0: too many uninteresting leak errors in short-lived binaries
     UBSAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:verbosity=2
-    ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0:detect_stack_use_after_return=0
+    ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0
 
     # SANITIZER_FLAGS is set in the tasks below
     CFLAGS: -Og -ggdb -fno-sanitize-recover=all $SANITIZER_FLAGS
-- 
2.53.0.1.gb2826b52eb


--7e2sypi5gxvkgog6--





^ permalink  raw  reply  [nested|flat] 114+ messages in thread

* [PATCH v2 1/4] Make stack depth check work with asan's use-after-return
@ 2026-02-07 19:04 Andres Freund <[email protected]>
  0 siblings, 0 replies; 114+ messages in thread

From: Andres Freund @ 2026-02-07 19:04 UTC (permalink / raw)

With address sanitizer's stack-use-after-return check, stack variables are
moved to heap allocations, to allow to detect references to the memory at a
later time. That broke our stack-depth check, which is why we had to disable
detect_stack_use_after_return in CI. Luckily __builtin_frame_address() works
correctly, even under asan, so use that.

Author:
Reviewed-by:
Discussion: https://postgr.es/m/
Backpatch:
---
 src/backend/utils/misc/stack_depth.c | 17 ++++++++++++++++-
 .cirrus.tasks.yml                    |  2 +-
 2 files changed, 17 insertions(+), 2 deletions(-)

diff --git a/src/backend/utils/misc/stack_depth.c b/src/backend/utils/misc/stack_depth.c
index 61a07cf824e..9955e28c176 100644
--- a/src/backend/utils/misc/stack_depth.c
+++ b/src/backend/utils/misc/stack_depth.c
@@ -108,13 +108,28 @@ check_stack_depth(void)
 bool
 stack_is_too_deep(void)
 {
+#ifndef HAVE__BUILTIN_FRAME_ADDRESS
 	char		stack_top_loc;
+#endif
 	ssize_t		stack_depth;
+	char	   *stack_address;
+
+	/*
+	 * With address sanitizer's stack-use-after-return check, stack variables
+	 * are moved to heap allocations, to allow to detect references to the
+	 * memory at a later time. That would break our stack-depth check. Luckily
+	 * __builtin_frame_address() works correctly, even under asan.
+	 */
+#ifndef HAVE__BUILTIN_FRAME_ADDRESS
+	stack_address = &stack_top_loc;
+#else
+	stack_address = (char *) __builtin_frame_address(0);
+#endif
 
 	/*
 	 * Compute distance from reference point to my local variables
 	 */
-	stack_depth = (ssize_t) (stack_base_ptr - &stack_top_loc);
+	stack_depth = (ssize_t) (stack_base_ptr - stack_address);
 
 	/*
 	 * Take abs value, since stacks grow up on some machines, down on others
diff --git a/.cirrus.tasks.yml b/.cirrus.tasks.yml
index a22cef063f3..8683d1ae9c7 100644
--- a/.cirrus.tasks.yml
+++ b/.cirrus.tasks.yml
@@ -447,7 +447,7 @@ task:
     # print_stacktraces=1,verbosity=2, duh
     # detect_leaks=0: too many uninteresting leak errors in short-lived binaries
     UBSAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:verbosity=2
-    ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0:detect_stack_use_after_return=0
+    ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0
 
     # SANITIZER_FLAGS is set in the tasks below
     CFLAGS: -Og -ggdb -fno-sanitize-recover=all $SANITIZER_FLAGS
-- 
2.53.0.1.gb2826b52eb


--7e2sypi5gxvkgog6--





^ permalink  raw  reply  [nested|flat] 114+ messages in thread

* [PATCH v2 1/4] Make stack depth check work with asan's use-after-return
@ 2026-02-07 19:04 Andres Freund <[email protected]>
  0 siblings, 0 replies; 114+ messages in thread

From: Andres Freund @ 2026-02-07 19:04 UTC (permalink / raw)

With address sanitizer's stack-use-after-return check, stack variables are
moved to heap allocations, to allow to detect references to the memory at a
later time. That broke our stack-depth check, which is why we had to disable
detect_stack_use_after_return in CI. Luckily __builtin_frame_address() works
correctly, even under asan, so use that.

Author:
Reviewed-by:
Discussion: https://postgr.es/m/
Backpatch:
---
 src/backend/utils/misc/stack_depth.c | 17 ++++++++++++++++-
 .cirrus.tasks.yml                    |  2 +-
 2 files changed, 17 insertions(+), 2 deletions(-)

diff --git a/src/backend/utils/misc/stack_depth.c b/src/backend/utils/misc/stack_depth.c
index 61a07cf824e..9955e28c176 100644
--- a/src/backend/utils/misc/stack_depth.c
+++ b/src/backend/utils/misc/stack_depth.c
@@ -108,13 +108,28 @@ check_stack_depth(void)
 bool
 stack_is_too_deep(void)
 {
+#ifndef HAVE__BUILTIN_FRAME_ADDRESS
 	char		stack_top_loc;
+#endif
 	ssize_t		stack_depth;
+	char	   *stack_address;
+
+	/*
+	 * With address sanitizer's stack-use-after-return check, stack variables
+	 * are moved to heap allocations, to allow to detect references to the
+	 * memory at a later time. That would break our stack-depth check. Luckily
+	 * __builtin_frame_address() works correctly, even under asan.
+	 */
+#ifndef HAVE__BUILTIN_FRAME_ADDRESS
+	stack_address = &stack_top_loc;
+#else
+	stack_address = (char *) __builtin_frame_address(0);
+#endif
 
 	/*
 	 * Compute distance from reference point to my local variables
 	 */
-	stack_depth = (ssize_t) (stack_base_ptr - &stack_top_loc);
+	stack_depth = (ssize_t) (stack_base_ptr - stack_address);
 
 	/*
 	 * Take abs value, since stacks grow up on some machines, down on others
diff --git a/.cirrus.tasks.yml b/.cirrus.tasks.yml
index a22cef063f3..8683d1ae9c7 100644
--- a/.cirrus.tasks.yml
+++ b/.cirrus.tasks.yml
@@ -447,7 +447,7 @@ task:
     # print_stacktraces=1,verbosity=2, duh
     # detect_leaks=0: too many uninteresting leak errors in short-lived binaries
     UBSAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:verbosity=2
-    ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0:detect_stack_use_after_return=0
+    ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0
 
     # SANITIZER_FLAGS is set in the tasks below
     CFLAGS: -Og -ggdb -fno-sanitize-recover=all $SANITIZER_FLAGS
-- 
2.53.0.1.gb2826b52eb


--7e2sypi5gxvkgog6--





^ permalink  raw  reply  [nested|flat] 114+ messages in thread

* [PATCH v2 1/4] Make stack depth check work with asan's use-after-return
@ 2026-02-07 19:04 Andres Freund <[email protected]>
  0 siblings, 0 replies; 114+ messages in thread

From: Andres Freund @ 2026-02-07 19:04 UTC (permalink / raw)

With address sanitizer's stack-use-after-return check, stack variables are
moved to heap allocations, to allow to detect references to the memory at a
later time. That broke our stack-depth check, which is why we had to disable
detect_stack_use_after_return in CI. Luckily __builtin_frame_address() works
correctly, even under asan, so use that.

Author:
Reviewed-by:
Discussion: https://postgr.es/m/
Backpatch:
---
 src/backend/utils/misc/stack_depth.c | 17 ++++++++++++++++-
 .cirrus.tasks.yml                    |  2 +-
 2 files changed, 17 insertions(+), 2 deletions(-)

diff --git a/src/backend/utils/misc/stack_depth.c b/src/backend/utils/misc/stack_depth.c
index 61a07cf824e..9955e28c176 100644
--- a/src/backend/utils/misc/stack_depth.c
+++ b/src/backend/utils/misc/stack_depth.c
@@ -108,13 +108,28 @@ check_stack_depth(void)
 bool
 stack_is_too_deep(void)
 {
+#ifndef HAVE__BUILTIN_FRAME_ADDRESS
 	char		stack_top_loc;
+#endif
 	ssize_t		stack_depth;
+	char	   *stack_address;
+
+	/*
+	 * With address sanitizer's stack-use-after-return check, stack variables
+	 * are moved to heap allocations, to allow to detect references to the
+	 * memory at a later time. That would break our stack-depth check. Luckily
+	 * __builtin_frame_address() works correctly, even under asan.
+	 */
+#ifndef HAVE__BUILTIN_FRAME_ADDRESS
+	stack_address = &stack_top_loc;
+#else
+	stack_address = (char *) __builtin_frame_address(0);
+#endif
 
 	/*
 	 * Compute distance from reference point to my local variables
 	 */
-	stack_depth = (ssize_t) (stack_base_ptr - &stack_top_loc);
+	stack_depth = (ssize_t) (stack_base_ptr - stack_address);
 
 	/*
 	 * Take abs value, since stacks grow up on some machines, down on others
diff --git a/.cirrus.tasks.yml b/.cirrus.tasks.yml
index a22cef063f3..8683d1ae9c7 100644
--- a/.cirrus.tasks.yml
+++ b/.cirrus.tasks.yml
@@ -447,7 +447,7 @@ task:
     # print_stacktraces=1,verbosity=2, duh
     # detect_leaks=0: too many uninteresting leak errors in short-lived binaries
     UBSAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:verbosity=2
-    ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0:detect_stack_use_after_return=0
+    ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0
 
     # SANITIZER_FLAGS is set in the tasks below
     CFLAGS: -Og -ggdb -fno-sanitize-recover=all $SANITIZER_FLAGS
-- 
2.53.0.1.gb2826b52eb


--7e2sypi5gxvkgog6--





^ permalink  raw  reply  [nested|flat] 114+ messages in thread

* [PATCH v2 1/4] Make stack depth check work with asan's use-after-return
@ 2026-02-07 19:04 Andres Freund <[email protected]>
  0 siblings, 0 replies; 114+ messages in thread

From: Andres Freund @ 2026-02-07 19:04 UTC (permalink / raw)

With address sanitizer's stack-use-after-return check, stack variables are
moved to heap allocations, to allow to detect references to the memory at a
later time. That broke our stack-depth check, which is why we had to disable
detect_stack_use_after_return in CI. Luckily __builtin_frame_address() works
correctly, even under asan, so use that.

Author:
Reviewed-by:
Discussion: https://postgr.es/m/
Backpatch:
---
 src/backend/utils/misc/stack_depth.c | 17 ++++++++++++++++-
 .cirrus.tasks.yml                    |  2 +-
 2 files changed, 17 insertions(+), 2 deletions(-)

diff --git a/src/backend/utils/misc/stack_depth.c b/src/backend/utils/misc/stack_depth.c
index 61a07cf824e..9955e28c176 100644
--- a/src/backend/utils/misc/stack_depth.c
+++ b/src/backend/utils/misc/stack_depth.c
@@ -108,13 +108,28 @@ check_stack_depth(void)
 bool
 stack_is_too_deep(void)
 {
+#ifndef HAVE__BUILTIN_FRAME_ADDRESS
 	char		stack_top_loc;
+#endif
 	ssize_t		stack_depth;
+	char	   *stack_address;
+
+	/*
+	 * With address sanitizer's stack-use-after-return check, stack variables
+	 * are moved to heap allocations, to allow to detect references to the
+	 * memory at a later time. That would break our stack-depth check. Luckily
+	 * __builtin_frame_address() works correctly, even under asan.
+	 */
+#ifndef HAVE__BUILTIN_FRAME_ADDRESS
+	stack_address = &stack_top_loc;
+#else
+	stack_address = (char *) __builtin_frame_address(0);
+#endif
 
 	/*
 	 * Compute distance from reference point to my local variables
 	 */
-	stack_depth = (ssize_t) (stack_base_ptr - &stack_top_loc);
+	stack_depth = (ssize_t) (stack_base_ptr - stack_address);
 
 	/*
 	 * Take abs value, since stacks grow up on some machines, down on others
diff --git a/.cirrus.tasks.yml b/.cirrus.tasks.yml
index a22cef063f3..8683d1ae9c7 100644
--- a/.cirrus.tasks.yml
+++ b/.cirrus.tasks.yml
@@ -447,7 +447,7 @@ task:
     # print_stacktraces=1,verbosity=2, duh
     # detect_leaks=0: too many uninteresting leak errors in short-lived binaries
     UBSAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:verbosity=2
-    ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0:detect_stack_use_after_return=0
+    ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0
 
     # SANITIZER_FLAGS is set in the tasks below
     CFLAGS: -Og -ggdb -fno-sanitize-recover=all $SANITIZER_FLAGS
-- 
2.53.0.1.gb2826b52eb


--7e2sypi5gxvkgog6--





^ permalink  raw  reply  [nested|flat] 114+ messages in thread

* [PATCH v2 1/4] Make stack depth check work with asan's use-after-return
@ 2026-02-07 19:04 Andres Freund <[email protected]>
  0 siblings, 0 replies; 114+ messages in thread

From: Andres Freund @ 2026-02-07 19:04 UTC (permalink / raw)

With address sanitizer's stack-use-after-return check, stack variables are
moved to heap allocations, to allow to detect references to the memory at a
later time. That broke our stack-depth check, which is why we had to disable
detect_stack_use_after_return in CI. Luckily __builtin_frame_address() works
correctly, even under asan, so use that.

Author:
Reviewed-by:
Discussion: https://postgr.es/m/
Backpatch:
---
 src/backend/utils/misc/stack_depth.c | 17 ++++++++++++++++-
 .cirrus.tasks.yml                    |  2 +-
 2 files changed, 17 insertions(+), 2 deletions(-)

diff --git a/src/backend/utils/misc/stack_depth.c b/src/backend/utils/misc/stack_depth.c
index 61a07cf824e..9955e28c176 100644
--- a/src/backend/utils/misc/stack_depth.c
+++ b/src/backend/utils/misc/stack_depth.c
@@ -108,13 +108,28 @@ check_stack_depth(void)
 bool
 stack_is_too_deep(void)
 {
+#ifndef HAVE__BUILTIN_FRAME_ADDRESS
 	char		stack_top_loc;
+#endif
 	ssize_t		stack_depth;
+	char	   *stack_address;
+
+	/*
+	 * With address sanitizer's stack-use-after-return check, stack variables
+	 * are moved to heap allocations, to allow to detect references to the
+	 * memory at a later time. That would break our stack-depth check. Luckily
+	 * __builtin_frame_address() works correctly, even under asan.
+	 */
+#ifndef HAVE__BUILTIN_FRAME_ADDRESS
+	stack_address = &stack_top_loc;
+#else
+	stack_address = (char *) __builtin_frame_address(0);
+#endif
 
 	/*
 	 * Compute distance from reference point to my local variables
 	 */
-	stack_depth = (ssize_t) (stack_base_ptr - &stack_top_loc);
+	stack_depth = (ssize_t) (stack_base_ptr - stack_address);
 
 	/*
 	 * Take abs value, since stacks grow up on some machines, down on others
diff --git a/.cirrus.tasks.yml b/.cirrus.tasks.yml
index a22cef063f3..8683d1ae9c7 100644
--- a/.cirrus.tasks.yml
+++ b/.cirrus.tasks.yml
@@ -447,7 +447,7 @@ task:
     # print_stacktraces=1,verbosity=2, duh
     # detect_leaks=0: too many uninteresting leak errors in short-lived binaries
     UBSAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:verbosity=2
-    ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0:detect_stack_use_after_return=0
+    ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0
 
     # SANITIZER_FLAGS is set in the tasks below
     CFLAGS: -Og -ggdb -fno-sanitize-recover=all $SANITIZER_FLAGS
-- 
2.53.0.1.gb2826b52eb


--7e2sypi5gxvkgog6--





^ permalink  raw  reply  [nested|flat] 114+ messages in thread

* [PATCH v2 1/4] Make stack depth check work with asan's use-after-return
@ 2026-02-07 19:04 Andres Freund <[email protected]>
  0 siblings, 0 replies; 114+ messages in thread

From: Andres Freund @ 2026-02-07 19:04 UTC (permalink / raw)

With address sanitizer's stack-use-after-return check, stack variables are
moved to heap allocations, to allow to detect references to the memory at a
later time. That broke our stack-depth check, which is why we had to disable
detect_stack_use_after_return in CI. Luckily __builtin_frame_address() works
correctly, even under asan, so use that.

Author:
Reviewed-by:
Discussion: https://postgr.es/m/
Backpatch:
---
 src/backend/utils/misc/stack_depth.c | 17 ++++++++++++++++-
 .cirrus.tasks.yml                    |  2 +-
 2 files changed, 17 insertions(+), 2 deletions(-)

diff --git a/src/backend/utils/misc/stack_depth.c b/src/backend/utils/misc/stack_depth.c
index 61a07cf824e..9955e28c176 100644
--- a/src/backend/utils/misc/stack_depth.c
+++ b/src/backend/utils/misc/stack_depth.c
@@ -108,13 +108,28 @@ check_stack_depth(void)
 bool
 stack_is_too_deep(void)
 {
+#ifndef HAVE__BUILTIN_FRAME_ADDRESS
 	char		stack_top_loc;
+#endif
 	ssize_t		stack_depth;
+	char	   *stack_address;
+
+	/*
+	 * With address sanitizer's stack-use-after-return check, stack variables
+	 * are moved to heap allocations, to allow to detect references to the
+	 * memory at a later time. That would break our stack-depth check. Luckily
+	 * __builtin_frame_address() works correctly, even under asan.
+	 */
+#ifndef HAVE__BUILTIN_FRAME_ADDRESS
+	stack_address = &stack_top_loc;
+#else
+	stack_address = (char *) __builtin_frame_address(0);
+#endif
 
 	/*
 	 * Compute distance from reference point to my local variables
 	 */
-	stack_depth = (ssize_t) (stack_base_ptr - &stack_top_loc);
+	stack_depth = (ssize_t) (stack_base_ptr - stack_address);
 
 	/*
 	 * Take abs value, since stacks grow up on some machines, down on others
diff --git a/.cirrus.tasks.yml b/.cirrus.tasks.yml
index a22cef063f3..8683d1ae9c7 100644
--- a/.cirrus.tasks.yml
+++ b/.cirrus.tasks.yml
@@ -447,7 +447,7 @@ task:
     # print_stacktraces=1,verbosity=2, duh
     # detect_leaks=0: too many uninteresting leak errors in short-lived binaries
     UBSAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:verbosity=2
-    ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0:detect_stack_use_after_return=0
+    ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0
 
     # SANITIZER_FLAGS is set in the tasks below
     CFLAGS: -Og -ggdb -fno-sanitize-recover=all $SANITIZER_FLAGS
-- 
2.53.0.1.gb2826b52eb


--7e2sypi5gxvkgog6--





^ permalink  raw  reply  [nested|flat] 114+ messages in thread

* [PATCH v2 1/4] Make stack depth check work with asan's use-after-return
@ 2026-02-07 19:04 Andres Freund <[email protected]>
  0 siblings, 0 replies; 114+ messages in thread

From: Andres Freund @ 2026-02-07 19:04 UTC (permalink / raw)

With address sanitizer's stack-use-after-return check, stack variables are
moved to heap allocations, to allow to detect references to the memory at a
later time. That broke our stack-depth check, which is why we had to disable
detect_stack_use_after_return in CI. Luckily __builtin_frame_address() works
correctly, even under asan, so use that.

Author:
Reviewed-by:
Discussion: https://postgr.es/m/
Backpatch:
---
 src/backend/utils/misc/stack_depth.c | 17 ++++++++++++++++-
 .cirrus.tasks.yml                    |  2 +-
 2 files changed, 17 insertions(+), 2 deletions(-)

diff --git a/src/backend/utils/misc/stack_depth.c b/src/backend/utils/misc/stack_depth.c
index 61a07cf824e..9955e28c176 100644
--- a/src/backend/utils/misc/stack_depth.c
+++ b/src/backend/utils/misc/stack_depth.c
@@ -108,13 +108,28 @@ check_stack_depth(void)
 bool
 stack_is_too_deep(void)
 {
+#ifndef HAVE__BUILTIN_FRAME_ADDRESS
 	char		stack_top_loc;
+#endif
 	ssize_t		stack_depth;
+	char	   *stack_address;
+
+	/*
+	 * With address sanitizer's stack-use-after-return check, stack variables
+	 * are moved to heap allocations, to allow to detect references to the
+	 * memory at a later time. That would break our stack-depth check. Luckily
+	 * __builtin_frame_address() works correctly, even under asan.
+	 */
+#ifndef HAVE__BUILTIN_FRAME_ADDRESS
+	stack_address = &stack_top_loc;
+#else
+	stack_address = (char *) __builtin_frame_address(0);
+#endif
 
 	/*
 	 * Compute distance from reference point to my local variables
 	 */
-	stack_depth = (ssize_t) (stack_base_ptr - &stack_top_loc);
+	stack_depth = (ssize_t) (stack_base_ptr - stack_address);
 
 	/*
 	 * Take abs value, since stacks grow up on some machines, down on others
diff --git a/.cirrus.tasks.yml b/.cirrus.tasks.yml
index a22cef063f3..8683d1ae9c7 100644
--- a/.cirrus.tasks.yml
+++ b/.cirrus.tasks.yml
@@ -447,7 +447,7 @@ task:
     # print_stacktraces=1,verbosity=2, duh
     # detect_leaks=0: too many uninteresting leak errors in short-lived binaries
     UBSAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:verbosity=2
-    ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0:detect_stack_use_after_return=0
+    ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0
 
     # SANITIZER_FLAGS is set in the tasks below
     CFLAGS: -Og -ggdb -fno-sanitize-recover=all $SANITIZER_FLAGS
-- 
2.53.0.1.gb2826b52eb


--7e2sypi5gxvkgog6--





^ permalink  raw  reply  [nested|flat] 114+ messages in thread

* [PATCH v2 1/4] Make stack depth check work with asan's use-after-return
@ 2026-02-07 19:04 Andres Freund <[email protected]>
  0 siblings, 0 replies; 114+ messages in thread

From: Andres Freund @ 2026-02-07 19:04 UTC (permalink / raw)

With address sanitizer's stack-use-after-return check, stack variables are
moved to heap allocations, to allow to detect references to the memory at a
later time. That broke our stack-depth check, which is why we had to disable
detect_stack_use_after_return in CI. Luckily __builtin_frame_address() works
correctly, even under asan, so use that.

Author:
Reviewed-by:
Discussion: https://postgr.es/m/
Backpatch:
---
 src/backend/utils/misc/stack_depth.c | 17 ++++++++++++++++-
 .cirrus.tasks.yml                    |  2 +-
 2 files changed, 17 insertions(+), 2 deletions(-)

diff --git a/src/backend/utils/misc/stack_depth.c b/src/backend/utils/misc/stack_depth.c
index 61a07cf824e..9955e28c176 100644
--- a/src/backend/utils/misc/stack_depth.c
+++ b/src/backend/utils/misc/stack_depth.c
@@ -108,13 +108,28 @@ check_stack_depth(void)
 bool
 stack_is_too_deep(void)
 {
+#ifndef HAVE__BUILTIN_FRAME_ADDRESS
 	char		stack_top_loc;
+#endif
 	ssize_t		stack_depth;
+	char	   *stack_address;
+
+	/*
+	 * With address sanitizer's stack-use-after-return check, stack variables
+	 * are moved to heap allocations, to allow to detect references to the
+	 * memory at a later time. That would break our stack-depth check. Luckily
+	 * __builtin_frame_address() works correctly, even under asan.
+	 */
+#ifndef HAVE__BUILTIN_FRAME_ADDRESS
+	stack_address = &stack_top_loc;
+#else
+	stack_address = (char *) __builtin_frame_address(0);
+#endif
 
 	/*
 	 * Compute distance from reference point to my local variables
 	 */
-	stack_depth = (ssize_t) (stack_base_ptr - &stack_top_loc);
+	stack_depth = (ssize_t) (stack_base_ptr - stack_address);
 
 	/*
 	 * Take abs value, since stacks grow up on some machines, down on others
diff --git a/.cirrus.tasks.yml b/.cirrus.tasks.yml
index a22cef063f3..8683d1ae9c7 100644
--- a/.cirrus.tasks.yml
+++ b/.cirrus.tasks.yml
@@ -447,7 +447,7 @@ task:
     # print_stacktraces=1,verbosity=2, duh
     # detect_leaks=0: too many uninteresting leak errors in short-lived binaries
     UBSAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:verbosity=2
-    ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0:detect_stack_use_after_return=0
+    ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0
 
     # SANITIZER_FLAGS is set in the tasks below
     CFLAGS: -Og -ggdb -fno-sanitize-recover=all $SANITIZER_FLAGS
-- 
2.53.0.1.gb2826b52eb


--7e2sypi5gxvkgog6--





^ permalink  raw  reply  [nested|flat] 114+ messages in thread

* [PATCH v2 1/4] Make stack depth check work with asan's use-after-return
@ 2026-02-07 19:04 Andres Freund <[email protected]>
  0 siblings, 0 replies; 114+ messages in thread

From: Andres Freund @ 2026-02-07 19:04 UTC (permalink / raw)

With address sanitizer's stack-use-after-return check, stack variables are
moved to heap allocations, to allow to detect references to the memory at a
later time. That broke our stack-depth check, which is why we had to disable
detect_stack_use_after_return in CI. Luckily __builtin_frame_address() works
correctly, even under asan, so use that.

Author:
Reviewed-by:
Discussion: https://postgr.es/m/
Backpatch:
---
 src/backend/utils/misc/stack_depth.c | 17 ++++++++++++++++-
 .cirrus.tasks.yml                    |  2 +-
 2 files changed, 17 insertions(+), 2 deletions(-)

diff --git a/src/backend/utils/misc/stack_depth.c b/src/backend/utils/misc/stack_depth.c
index 61a07cf824e..9955e28c176 100644
--- a/src/backend/utils/misc/stack_depth.c
+++ b/src/backend/utils/misc/stack_depth.c
@@ -108,13 +108,28 @@ check_stack_depth(void)
 bool
 stack_is_too_deep(void)
 {
+#ifndef HAVE__BUILTIN_FRAME_ADDRESS
 	char		stack_top_loc;
+#endif
 	ssize_t		stack_depth;
+	char	   *stack_address;
+
+	/*
+	 * With address sanitizer's stack-use-after-return check, stack variables
+	 * are moved to heap allocations, to allow to detect references to the
+	 * memory at a later time. That would break our stack-depth check. Luckily
+	 * __builtin_frame_address() works correctly, even under asan.
+	 */
+#ifndef HAVE__BUILTIN_FRAME_ADDRESS
+	stack_address = &stack_top_loc;
+#else
+	stack_address = (char *) __builtin_frame_address(0);
+#endif
 
 	/*
 	 * Compute distance from reference point to my local variables
 	 */
-	stack_depth = (ssize_t) (stack_base_ptr - &stack_top_loc);
+	stack_depth = (ssize_t) (stack_base_ptr - stack_address);
 
 	/*
 	 * Take abs value, since stacks grow up on some machines, down on others
diff --git a/.cirrus.tasks.yml b/.cirrus.tasks.yml
index a22cef063f3..8683d1ae9c7 100644
--- a/.cirrus.tasks.yml
+++ b/.cirrus.tasks.yml
@@ -447,7 +447,7 @@ task:
     # print_stacktraces=1,verbosity=2, duh
     # detect_leaks=0: too many uninteresting leak errors in short-lived binaries
     UBSAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:verbosity=2
-    ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0:detect_stack_use_after_return=0
+    ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0
 
     # SANITIZER_FLAGS is set in the tasks below
     CFLAGS: -Og -ggdb -fno-sanitize-recover=all $SANITIZER_FLAGS
-- 
2.53.0.1.gb2826b52eb


--7e2sypi5gxvkgog6--





^ permalink  raw  reply  [nested|flat] 114+ messages in thread

* [PATCH v2 1/4] Make stack depth check work with asan's use-after-return
@ 2026-02-07 19:04 Andres Freund <[email protected]>
  0 siblings, 0 replies; 114+ messages in thread

From: Andres Freund @ 2026-02-07 19:04 UTC (permalink / raw)

With address sanitizer's stack-use-after-return check, stack variables are
moved to heap allocations, to allow to detect references to the memory at a
later time. That broke our stack-depth check, which is why we had to disable
detect_stack_use_after_return in CI. Luckily __builtin_frame_address() works
correctly, even under asan, so use that.

Author:
Reviewed-by:
Discussion: https://postgr.es/m/
Backpatch:
---
 src/backend/utils/misc/stack_depth.c | 17 ++++++++++++++++-
 .cirrus.tasks.yml                    |  2 +-
 2 files changed, 17 insertions(+), 2 deletions(-)

diff --git a/src/backend/utils/misc/stack_depth.c b/src/backend/utils/misc/stack_depth.c
index 61a07cf824e..9955e28c176 100644
--- a/src/backend/utils/misc/stack_depth.c
+++ b/src/backend/utils/misc/stack_depth.c
@@ -108,13 +108,28 @@ check_stack_depth(void)
 bool
 stack_is_too_deep(void)
 {
+#ifndef HAVE__BUILTIN_FRAME_ADDRESS
 	char		stack_top_loc;
+#endif
 	ssize_t		stack_depth;
+	char	   *stack_address;
+
+	/*
+	 * With address sanitizer's stack-use-after-return check, stack variables
+	 * are moved to heap allocations, to allow to detect references to the
+	 * memory at a later time. That would break our stack-depth check. Luckily
+	 * __builtin_frame_address() works correctly, even under asan.
+	 */
+#ifndef HAVE__BUILTIN_FRAME_ADDRESS
+	stack_address = &stack_top_loc;
+#else
+	stack_address = (char *) __builtin_frame_address(0);
+#endif
 
 	/*
 	 * Compute distance from reference point to my local variables
 	 */
-	stack_depth = (ssize_t) (stack_base_ptr - &stack_top_loc);
+	stack_depth = (ssize_t) (stack_base_ptr - stack_address);
 
 	/*
 	 * Take abs value, since stacks grow up on some machines, down on others
diff --git a/.cirrus.tasks.yml b/.cirrus.tasks.yml
index a22cef063f3..8683d1ae9c7 100644
--- a/.cirrus.tasks.yml
+++ b/.cirrus.tasks.yml
@@ -447,7 +447,7 @@ task:
     # print_stacktraces=1,verbosity=2, duh
     # detect_leaks=0: too many uninteresting leak errors in short-lived binaries
     UBSAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:verbosity=2
-    ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0:detect_stack_use_after_return=0
+    ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0
 
     # SANITIZER_FLAGS is set in the tasks below
     CFLAGS: -Og -ggdb -fno-sanitize-recover=all $SANITIZER_FLAGS
-- 
2.53.0.1.gb2826b52eb


--7e2sypi5gxvkgog6--





^ permalink  raw  reply  [nested|flat] 114+ messages in thread

* [PATCH v2 1/4] Make stack depth check work with asan's use-after-return
@ 2026-02-07 19:04 Andres Freund <[email protected]>
  0 siblings, 0 replies; 114+ messages in thread

From: Andres Freund @ 2026-02-07 19:04 UTC (permalink / raw)

With address sanitizer's stack-use-after-return check, stack variables are
moved to heap allocations, to allow to detect references to the memory at a
later time. That broke our stack-depth check, which is why we had to disable
detect_stack_use_after_return in CI. Luckily __builtin_frame_address() works
correctly, even under asan, so use that.

Author:
Reviewed-by:
Discussion: https://postgr.es/m/
Backpatch:
---
 src/backend/utils/misc/stack_depth.c | 17 ++++++++++++++++-
 .cirrus.tasks.yml                    |  2 +-
 2 files changed, 17 insertions(+), 2 deletions(-)

diff --git a/src/backend/utils/misc/stack_depth.c b/src/backend/utils/misc/stack_depth.c
index 61a07cf824e..9955e28c176 100644
--- a/src/backend/utils/misc/stack_depth.c
+++ b/src/backend/utils/misc/stack_depth.c
@@ -108,13 +108,28 @@ check_stack_depth(void)
 bool
 stack_is_too_deep(void)
 {
+#ifndef HAVE__BUILTIN_FRAME_ADDRESS
 	char		stack_top_loc;
+#endif
 	ssize_t		stack_depth;
+	char	   *stack_address;
+
+	/*
+	 * With address sanitizer's stack-use-after-return check, stack variables
+	 * are moved to heap allocations, to allow to detect references to the
+	 * memory at a later time. That would break our stack-depth check. Luckily
+	 * __builtin_frame_address() works correctly, even under asan.
+	 */
+#ifndef HAVE__BUILTIN_FRAME_ADDRESS
+	stack_address = &stack_top_loc;
+#else
+	stack_address = (char *) __builtin_frame_address(0);
+#endif
 
 	/*
 	 * Compute distance from reference point to my local variables
 	 */
-	stack_depth = (ssize_t) (stack_base_ptr - &stack_top_loc);
+	stack_depth = (ssize_t) (stack_base_ptr - stack_address);
 
 	/*
 	 * Take abs value, since stacks grow up on some machines, down on others
diff --git a/.cirrus.tasks.yml b/.cirrus.tasks.yml
index a22cef063f3..8683d1ae9c7 100644
--- a/.cirrus.tasks.yml
+++ b/.cirrus.tasks.yml
@@ -447,7 +447,7 @@ task:
     # print_stacktraces=1,verbosity=2, duh
     # detect_leaks=0: too many uninteresting leak errors in short-lived binaries
     UBSAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:verbosity=2
-    ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0:detect_stack_use_after_return=0
+    ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0
 
     # SANITIZER_FLAGS is set in the tasks below
     CFLAGS: -Og -ggdb -fno-sanitize-recover=all $SANITIZER_FLAGS
-- 
2.53.0.1.gb2826b52eb


--7e2sypi5gxvkgog6--





^ permalink  raw  reply  [nested|flat] 114+ messages in thread

* [PATCH v2 1/4] Make stack depth check work with asan's use-after-return
@ 2026-02-07 19:04 Andres Freund <[email protected]>
  0 siblings, 0 replies; 114+ messages in thread

From: Andres Freund @ 2026-02-07 19:04 UTC (permalink / raw)

With address sanitizer's stack-use-after-return check, stack variables are
moved to heap allocations, to allow to detect references to the memory at a
later time. That broke our stack-depth check, which is why we had to disable
detect_stack_use_after_return in CI. Luckily __builtin_frame_address() works
correctly, even under asan, so use that.

Author:
Reviewed-by:
Discussion: https://postgr.es/m/
Backpatch:
---
 src/backend/utils/misc/stack_depth.c | 17 ++++++++++++++++-
 .cirrus.tasks.yml                    |  2 +-
 2 files changed, 17 insertions(+), 2 deletions(-)

diff --git a/src/backend/utils/misc/stack_depth.c b/src/backend/utils/misc/stack_depth.c
index 61a07cf824e..9955e28c176 100644
--- a/src/backend/utils/misc/stack_depth.c
+++ b/src/backend/utils/misc/stack_depth.c
@@ -108,13 +108,28 @@ check_stack_depth(void)
 bool
 stack_is_too_deep(void)
 {
+#ifndef HAVE__BUILTIN_FRAME_ADDRESS
 	char		stack_top_loc;
+#endif
 	ssize_t		stack_depth;
+	char	   *stack_address;
+
+	/*
+	 * With address sanitizer's stack-use-after-return check, stack variables
+	 * are moved to heap allocations, to allow to detect references to the
+	 * memory at a later time. That would break our stack-depth check. Luckily
+	 * __builtin_frame_address() works correctly, even under asan.
+	 */
+#ifndef HAVE__BUILTIN_FRAME_ADDRESS
+	stack_address = &stack_top_loc;
+#else
+	stack_address = (char *) __builtin_frame_address(0);
+#endif
 
 	/*
 	 * Compute distance from reference point to my local variables
 	 */
-	stack_depth = (ssize_t) (stack_base_ptr - &stack_top_loc);
+	stack_depth = (ssize_t) (stack_base_ptr - stack_address);
 
 	/*
 	 * Take abs value, since stacks grow up on some machines, down on others
diff --git a/.cirrus.tasks.yml b/.cirrus.tasks.yml
index a22cef063f3..8683d1ae9c7 100644
--- a/.cirrus.tasks.yml
+++ b/.cirrus.tasks.yml
@@ -447,7 +447,7 @@ task:
     # print_stacktraces=1,verbosity=2, duh
     # detect_leaks=0: too many uninteresting leak errors in short-lived binaries
     UBSAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:verbosity=2
-    ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0:detect_stack_use_after_return=0
+    ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0
 
     # SANITIZER_FLAGS is set in the tasks below
     CFLAGS: -Og -ggdb -fno-sanitize-recover=all $SANITIZER_FLAGS
-- 
2.53.0.1.gb2826b52eb


--7e2sypi5gxvkgog6--





^ permalink  raw  reply  [nested|flat] 114+ messages in thread

* [PATCH v2 1/4] Make stack depth check work with asan's use-after-return
@ 2026-02-07 19:04 Andres Freund <[email protected]>
  0 siblings, 0 replies; 114+ messages in thread

From: Andres Freund @ 2026-02-07 19:04 UTC (permalink / raw)

With address sanitizer's stack-use-after-return check, stack variables are
moved to heap allocations, to allow to detect references to the memory at a
later time. That broke our stack-depth check, which is why we had to disable
detect_stack_use_after_return in CI. Luckily __builtin_frame_address() works
correctly, even under asan, so use that.

Author:
Reviewed-by:
Discussion: https://postgr.es/m/
Backpatch:
---
 src/backend/utils/misc/stack_depth.c | 17 ++++++++++++++++-
 .cirrus.tasks.yml                    |  2 +-
 2 files changed, 17 insertions(+), 2 deletions(-)

diff --git a/src/backend/utils/misc/stack_depth.c b/src/backend/utils/misc/stack_depth.c
index 61a07cf824e..9955e28c176 100644
--- a/src/backend/utils/misc/stack_depth.c
+++ b/src/backend/utils/misc/stack_depth.c
@@ -108,13 +108,28 @@ check_stack_depth(void)
 bool
 stack_is_too_deep(void)
 {
+#ifndef HAVE__BUILTIN_FRAME_ADDRESS
 	char		stack_top_loc;
+#endif
 	ssize_t		stack_depth;
+	char	   *stack_address;
+
+	/*
+	 * With address sanitizer's stack-use-after-return check, stack variables
+	 * are moved to heap allocations, to allow to detect references to the
+	 * memory at a later time. That would break our stack-depth check. Luckily
+	 * __builtin_frame_address() works correctly, even under asan.
+	 */
+#ifndef HAVE__BUILTIN_FRAME_ADDRESS
+	stack_address = &stack_top_loc;
+#else
+	stack_address = (char *) __builtin_frame_address(0);
+#endif
 
 	/*
 	 * Compute distance from reference point to my local variables
 	 */
-	stack_depth = (ssize_t) (stack_base_ptr - &stack_top_loc);
+	stack_depth = (ssize_t) (stack_base_ptr - stack_address);
 
 	/*
 	 * Take abs value, since stacks grow up on some machines, down on others
diff --git a/.cirrus.tasks.yml b/.cirrus.tasks.yml
index a22cef063f3..8683d1ae9c7 100644
--- a/.cirrus.tasks.yml
+++ b/.cirrus.tasks.yml
@@ -447,7 +447,7 @@ task:
     # print_stacktraces=1,verbosity=2, duh
     # detect_leaks=0: too many uninteresting leak errors in short-lived binaries
     UBSAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:verbosity=2
-    ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0:detect_stack_use_after_return=0
+    ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0
 
     # SANITIZER_FLAGS is set in the tasks below
     CFLAGS: -Og -ggdb -fno-sanitize-recover=all $SANITIZER_FLAGS
-- 
2.53.0.1.gb2826b52eb


--7e2sypi5gxvkgog6--





^ permalink  raw  reply  [nested|flat] 114+ messages in thread

* [PATCH v2 1/4] Make stack depth check work with asan's use-after-return
@ 2026-02-07 19:04 Andres Freund <[email protected]>
  0 siblings, 0 replies; 114+ messages in thread

From: Andres Freund @ 2026-02-07 19:04 UTC (permalink / raw)

With address sanitizer's stack-use-after-return check, stack variables are
moved to heap allocations, to allow to detect references to the memory at a
later time. That broke our stack-depth check, which is why we had to disable
detect_stack_use_after_return in CI. Luckily __builtin_frame_address() works
correctly, even under asan, so use that.

Author:
Reviewed-by:
Discussion: https://postgr.es/m/
Backpatch:
---
 src/backend/utils/misc/stack_depth.c | 17 ++++++++++++++++-
 .cirrus.tasks.yml                    |  2 +-
 2 files changed, 17 insertions(+), 2 deletions(-)

diff --git a/src/backend/utils/misc/stack_depth.c b/src/backend/utils/misc/stack_depth.c
index 61a07cf824e..9955e28c176 100644
--- a/src/backend/utils/misc/stack_depth.c
+++ b/src/backend/utils/misc/stack_depth.c
@@ -108,13 +108,28 @@ check_stack_depth(void)
 bool
 stack_is_too_deep(void)
 {
+#ifndef HAVE__BUILTIN_FRAME_ADDRESS
 	char		stack_top_loc;
+#endif
 	ssize_t		stack_depth;
+	char	   *stack_address;
+
+	/*
+	 * With address sanitizer's stack-use-after-return check, stack variables
+	 * are moved to heap allocations, to allow to detect references to the
+	 * memory at a later time. That would break our stack-depth check. Luckily
+	 * __builtin_frame_address() works correctly, even under asan.
+	 */
+#ifndef HAVE__BUILTIN_FRAME_ADDRESS
+	stack_address = &stack_top_loc;
+#else
+	stack_address = (char *) __builtin_frame_address(0);
+#endif
 
 	/*
 	 * Compute distance from reference point to my local variables
 	 */
-	stack_depth = (ssize_t) (stack_base_ptr - &stack_top_loc);
+	stack_depth = (ssize_t) (stack_base_ptr - stack_address);
 
 	/*
 	 * Take abs value, since stacks grow up on some machines, down on others
diff --git a/.cirrus.tasks.yml b/.cirrus.tasks.yml
index a22cef063f3..8683d1ae9c7 100644
--- a/.cirrus.tasks.yml
+++ b/.cirrus.tasks.yml
@@ -447,7 +447,7 @@ task:
     # print_stacktraces=1,verbosity=2, duh
     # detect_leaks=0: too many uninteresting leak errors in short-lived binaries
     UBSAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:verbosity=2
-    ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0:detect_stack_use_after_return=0
+    ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0
 
     # SANITIZER_FLAGS is set in the tasks below
     CFLAGS: -Og -ggdb -fno-sanitize-recover=all $SANITIZER_FLAGS
-- 
2.53.0.1.gb2826b52eb


--7e2sypi5gxvkgog6--





^ permalink  raw  reply  [nested|flat] 114+ messages in thread

* [PATCH v2 1/4] Make stack depth check work with asan's use-after-return
@ 2026-02-07 19:04 Andres Freund <[email protected]>
  0 siblings, 0 replies; 114+ messages in thread

From: Andres Freund @ 2026-02-07 19:04 UTC (permalink / raw)

With address sanitizer's stack-use-after-return check, stack variables are
moved to heap allocations, to allow to detect references to the memory at a
later time. That broke our stack-depth check, which is why we had to disable
detect_stack_use_after_return in CI. Luckily __builtin_frame_address() works
correctly, even under asan, so use that.

Author:
Reviewed-by:
Discussion: https://postgr.es/m/
Backpatch:
---
 src/backend/utils/misc/stack_depth.c | 17 ++++++++++++++++-
 .cirrus.tasks.yml                    |  2 +-
 2 files changed, 17 insertions(+), 2 deletions(-)

diff --git a/src/backend/utils/misc/stack_depth.c b/src/backend/utils/misc/stack_depth.c
index 61a07cf824e..9955e28c176 100644
--- a/src/backend/utils/misc/stack_depth.c
+++ b/src/backend/utils/misc/stack_depth.c
@@ -108,13 +108,28 @@ check_stack_depth(void)
 bool
 stack_is_too_deep(void)
 {
+#ifndef HAVE__BUILTIN_FRAME_ADDRESS
 	char		stack_top_loc;
+#endif
 	ssize_t		stack_depth;
+	char	   *stack_address;
+
+	/*
+	 * With address sanitizer's stack-use-after-return check, stack variables
+	 * are moved to heap allocations, to allow to detect references to the
+	 * memory at a later time. That would break our stack-depth check. Luckily
+	 * __builtin_frame_address() works correctly, even under asan.
+	 */
+#ifndef HAVE__BUILTIN_FRAME_ADDRESS
+	stack_address = &stack_top_loc;
+#else
+	stack_address = (char *) __builtin_frame_address(0);
+#endif
 
 	/*
 	 * Compute distance from reference point to my local variables
 	 */
-	stack_depth = (ssize_t) (stack_base_ptr - &stack_top_loc);
+	stack_depth = (ssize_t) (stack_base_ptr - stack_address);
 
 	/*
 	 * Take abs value, since stacks grow up on some machines, down on others
diff --git a/.cirrus.tasks.yml b/.cirrus.tasks.yml
index a22cef063f3..8683d1ae9c7 100644
--- a/.cirrus.tasks.yml
+++ b/.cirrus.tasks.yml
@@ -447,7 +447,7 @@ task:
     # print_stacktraces=1,verbosity=2, duh
     # detect_leaks=0: too many uninteresting leak errors in short-lived binaries
     UBSAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:verbosity=2
-    ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0:detect_stack_use_after_return=0
+    ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0
 
     # SANITIZER_FLAGS is set in the tasks below
     CFLAGS: -Og -ggdb -fno-sanitize-recover=all $SANITIZER_FLAGS
-- 
2.53.0.1.gb2826b52eb


--7e2sypi5gxvkgog6--





^ permalink  raw  reply  [nested|flat] 114+ messages in thread

* [PATCH v2 1/4] Make stack depth check work with asan's use-after-return
@ 2026-02-07 19:04 Andres Freund <[email protected]>
  0 siblings, 0 replies; 114+ messages in thread

From: Andres Freund @ 2026-02-07 19:04 UTC (permalink / raw)

With address sanitizer's stack-use-after-return check, stack variables are
moved to heap allocations, to allow to detect references to the memory at a
later time. That broke our stack-depth check, which is why we had to disable
detect_stack_use_after_return in CI. Luckily __builtin_frame_address() works
correctly, even under asan, so use that.

Author:
Reviewed-by:
Discussion: https://postgr.es/m/
Backpatch:
---
 src/backend/utils/misc/stack_depth.c | 17 ++++++++++++++++-
 .cirrus.tasks.yml                    |  2 +-
 2 files changed, 17 insertions(+), 2 deletions(-)

diff --git a/src/backend/utils/misc/stack_depth.c b/src/backend/utils/misc/stack_depth.c
index 61a07cf824e..9955e28c176 100644
--- a/src/backend/utils/misc/stack_depth.c
+++ b/src/backend/utils/misc/stack_depth.c
@@ -108,13 +108,28 @@ check_stack_depth(void)
 bool
 stack_is_too_deep(void)
 {
+#ifndef HAVE__BUILTIN_FRAME_ADDRESS
 	char		stack_top_loc;
+#endif
 	ssize_t		stack_depth;
+	char	   *stack_address;
+
+	/*
+	 * With address sanitizer's stack-use-after-return check, stack variables
+	 * are moved to heap allocations, to allow to detect references to the
+	 * memory at a later time. That would break our stack-depth check. Luckily
+	 * __builtin_frame_address() works correctly, even under asan.
+	 */
+#ifndef HAVE__BUILTIN_FRAME_ADDRESS
+	stack_address = &stack_top_loc;
+#else
+	stack_address = (char *) __builtin_frame_address(0);
+#endif
 
 	/*
 	 * Compute distance from reference point to my local variables
 	 */
-	stack_depth = (ssize_t) (stack_base_ptr - &stack_top_loc);
+	stack_depth = (ssize_t) (stack_base_ptr - stack_address);
 
 	/*
 	 * Take abs value, since stacks grow up on some machines, down on others
diff --git a/.cirrus.tasks.yml b/.cirrus.tasks.yml
index a22cef063f3..8683d1ae9c7 100644
--- a/.cirrus.tasks.yml
+++ b/.cirrus.tasks.yml
@@ -447,7 +447,7 @@ task:
     # print_stacktraces=1,verbosity=2, duh
     # detect_leaks=0: too many uninteresting leak errors in short-lived binaries
     UBSAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:verbosity=2
-    ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0:detect_stack_use_after_return=0
+    ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0
 
     # SANITIZER_FLAGS is set in the tasks below
     CFLAGS: -Og -ggdb -fno-sanitize-recover=all $SANITIZER_FLAGS
-- 
2.53.0.1.gb2826b52eb


--7e2sypi5gxvkgog6--





^ permalink  raw  reply  [nested|flat] 114+ messages in thread

* [PATCH v2 1/4] Make stack depth check work with asan's use-after-return
@ 2026-02-07 19:04 Andres Freund <[email protected]>
  0 siblings, 0 replies; 114+ messages in thread

From: Andres Freund @ 2026-02-07 19:04 UTC (permalink / raw)

With address sanitizer's stack-use-after-return check, stack variables are
moved to heap allocations, to allow to detect references to the memory at a
later time. That broke our stack-depth check, which is why we had to disable
detect_stack_use_after_return in CI. Luckily __builtin_frame_address() works
correctly, even under asan, so use that.

Author:
Reviewed-by:
Discussion: https://postgr.es/m/
Backpatch:
---
 src/backend/utils/misc/stack_depth.c | 17 ++++++++++++++++-
 .cirrus.tasks.yml                    |  2 +-
 2 files changed, 17 insertions(+), 2 deletions(-)

diff --git a/src/backend/utils/misc/stack_depth.c b/src/backend/utils/misc/stack_depth.c
index 61a07cf824e..9955e28c176 100644
--- a/src/backend/utils/misc/stack_depth.c
+++ b/src/backend/utils/misc/stack_depth.c
@@ -108,13 +108,28 @@ check_stack_depth(void)
 bool
 stack_is_too_deep(void)
 {
+#ifndef HAVE__BUILTIN_FRAME_ADDRESS
 	char		stack_top_loc;
+#endif
 	ssize_t		stack_depth;
+	char	   *stack_address;
+
+	/*
+	 * With address sanitizer's stack-use-after-return check, stack variables
+	 * are moved to heap allocations, to allow to detect references to the
+	 * memory at a later time. That would break our stack-depth check. Luckily
+	 * __builtin_frame_address() works correctly, even under asan.
+	 */
+#ifndef HAVE__BUILTIN_FRAME_ADDRESS
+	stack_address = &stack_top_loc;
+#else
+	stack_address = (char *) __builtin_frame_address(0);
+#endif
 
 	/*
 	 * Compute distance from reference point to my local variables
 	 */
-	stack_depth = (ssize_t) (stack_base_ptr - &stack_top_loc);
+	stack_depth = (ssize_t) (stack_base_ptr - stack_address);
 
 	/*
 	 * Take abs value, since stacks grow up on some machines, down on others
diff --git a/.cirrus.tasks.yml b/.cirrus.tasks.yml
index a22cef063f3..8683d1ae9c7 100644
--- a/.cirrus.tasks.yml
+++ b/.cirrus.tasks.yml
@@ -447,7 +447,7 @@ task:
     # print_stacktraces=1,verbosity=2, duh
     # detect_leaks=0: too many uninteresting leak errors in short-lived binaries
     UBSAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:verbosity=2
-    ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0:detect_stack_use_after_return=0
+    ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0
 
     # SANITIZER_FLAGS is set in the tasks below
     CFLAGS: -Og -ggdb -fno-sanitize-recover=all $SANITIZER_FLAGS
-- 
2.53.0.1.gb2826b52eb


--7e2sypi5gxvkgog6--





^ permalink  raw  reply  [nested|flat] 114+ messages in thread

* [PATCH v2 1/4] Make stack depth check work with asan's use-after-return
@ 2026-02-07 19:04 Andres Freund <[email protected]>
  0 siblings, 0 replies; 114+ messages in thread

From: Andres Freund @ 2026-02-07 19:04 UTC (permalink / raw)

With address sanitizer's stack-use-after-return check, stack variables are
moved to heap allocations, to allow to detect references to the memory at a
later time. That broke our stack-depth check, which is why we had to disable
detect_stack_use_after_return in CI. Luckily __builtin_frame_address() works
correctly, even under asan, so use that.

Author:
Reviewed-by:
Discussion: https://postgr.es/m/
Backpatch:
---
 src/backend/utils/misc/stack_depth.c | 17 ++++++++++++++++-
 .cirrus.tasks.yml                    |  2 +-
 2 files changed, 17 insertions(+), 2 deletions(-)

diff --git a/src/backend/utils/misc/stack_depth.c b/src/backend/utils/misc/stack_depth.c
index 61a07cf824e..9955e28c176 100644
--- a/src/backend/utils/misc/stack_depth.c
+++ b/src/backend/utils/misc/stack_depth.c
@@ -108,13 +108,28 @@ check_stack_depth(void)
 bool
 stack_is_too_deep(void)
 {
+#ifndef HAVE__BUILTIN_FRAME_ADDRESS
 	char		stack_top_loc;
+#endif
 	ssize_t		stack_depth;
+	char	   *stack_address;
+
+	/*
+	 * With address sanitizer's stack-use-after-return check, stack variables
+	 * are moved to heap allocations, to allow to detect references to the
+	 * memory at a later time. That would break our stack-depth check. Luckily
+	 * __builtin_frame_address() works correctly, even under asan.
+	 */
+#ifndef HAVE__BUILTIN_FRAME_ADDRESS
+	stack_address = &stack_top_loc;
+#else
+	stack_address = (char *) __builtin_frame_address(0);
+#endif
 
 	/*
 	 * Compute distance from reference point to my local variables
 	 */
-	stack_depth = (ssize_t) (stack_base_ptr - &stack_top_loc);
+	stack_depth = (ssize_t) (stack_base_ptr - stack_address);
 
 	/*
 	 * Take abs value, since stacks grow up on some machines, down on others
diff --git a/.cirrus.tasks.yml b/.cirrus.tasks.yml
index a22cef063f3..8683d1ae9c7 100644
--- a/.cirrus.tasks.yml
+++ b/.cirrus.tasks.yml
@@ -447,7 +447,7 @@ task:
     # print_stacktraces=1,verbosity=2, duh
     # detect_leaks=0: too many uninteresting leak errors in short-lived binaries
     UBSAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:verbosity=2
-    ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0:detect_stack_use_after_return=0
+    ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0
 
     # SANITIZER_FLAGS is set in the tasks below
     CFLAGS: -Og -ggdb -fno-sanitize-recover=all $SANITIZER_FLAGS
-- 
2.53.0.1.gb2826b52eb


--7e2sypi5gxvkgog6--





^ permalink  raw  reply  [nested|flat] 114+ messages in thread

* [PATCH v2 1/4] Make stack depth check work with asan's use-after-return
@ 2026-02-07 19:04 Andres Freund <[email protected]>
  0 siblings, 0 replies; 114+ messages in thread

From: Andres Freund @ 2026-02-07 19:04 UTC (permalink / raw)

With address sanitizer's stack-use-after-return check, stack variables are
moved to heap allocations, to allow to detect references to the memory at a
later time. That broke our stack-depth check, which is why we had to disable
detect_stack_use_after_return in CI. Luckily __builtin_frame_address() works
correctly, even under asan, so use that.

Author:
Reviewed-by:
Discussion: https://postgr.es/m/
Backpatch:
---
 src/backend/utils/misc/stack_depth.c | 17 ++++++++++++++++-
 .cirrus.tasks.yml                    |  2 +-
 2 files changed, 17 insertions(+), 2 deletions(-)

diff --git a/src/backend/utils/misc/stack_depth.c b/src/backend/utils/misc/stack_depth.c
index 61a07cf824e..9955e28c176 100644
--- a/src/backend/utils/misc/stack_depth.c
+++ b/src/backend/utils/misc/stack_depth.c
@@ -108,13 +108,28 @@ check_stack_depth(void)
 bool
 stack_is_too_deep(void)
 {
+#ifndef HAVE__BUILTIN_FRAME_ADDRESS
 	char		stack_top_loc;
+#endif
 	ssize_t		stack_depth;
+	char	   *stack_address;
+
+	/*
+	 * With address sanitizer's stack-use-after-return check, stack variables
+	 * are moved to heap allocations, to allow to detect references to the
+	 * memory at a later time. That would break our stack-depth check. Luckily
+	 * __builtin_frame_address() works correctly, even under asan.
+	 */
+#ifndef HAVE__BUILTIN_FRAME_ADDRESS
+	stack_address = &stack_top_loc;
+#else
+	stack_address = (char *) __builtin_frame_address(0);
+#endif
 
 	/*
 	 * Compute distance from reference point to my local variables
 	 */
-	stack_depth = (ssize_t) (stack_base_ptr - &stack_top_loc);
+	stack_depth = (ssize_t) (stack_base_ptr - stack_address);
 
 	/*
 	 * Take abs value, since stacks grow up on some machines, down on others
diff --git a/.cirrus.tasks.yml b/.cirrus.tasks.yml
index a22cef063f3..8683d1ae9c7 100644
--- a/.cirrus.tasks.yml
+++ b/.cirrus.tasks.yml
@@ -447,7 +447,7 @@ task:
     # print_stacktraces=1,verbosity=2, duh
     # detect_leaks=0: too many uninteresting leak errors in short-lived binaries
     UBSAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:verbosity=2
-    ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0:detect_stack_use_after_return=0
+    ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0
 
     # SANITIZER_FLAGS is set in the tasks below
     CFLAGS: -Og -ggdb -fno-sanitize-recover=all $SANITIZER_FLAGS
-- 
2.53.0.1.gb2826b52eb


--7e2sypi5gxvkgog6--





^ permalink  raw  reply  [nested|flat] 114+ messages in thread

* [PATCH v2 1/4] Make stack depth check work with asan's use-after-return
@ 2026-02-07 19:04 Andres Freund <[email protected]>
  0 siblings, 0 replies; 114+ messages in thread

From: Andres Freund @ 2026-02-07 19:04 UTC (permalink / raw)

With address sanitizer's stack-use-after-return check, stack variables are
moved to heap allocations, to allow to detect references to the memory at a
later time. That broke our stack-depth check, which is why we had to disable
detect_stack_use_after_return in CI. Luckily __builtin_frame_address() works
correctly, even under asan, so use that.

Author:
Reviewed-by:
Discussion: https://postgr.es/m/
Backpatch:
---
 src/backend/utils/misc/stack_depth.c | 17 ++++++++++++++++-
 .cirrus.tasks.yml                    |  2 +-
 2 files changed, 17 insertions(+), 2 deletions(-)

diff --git a/src/backend/utils/misc/stack_depth.c b/src/backend/utils/misc/stack_depth.c
index 61a07cf824e..9955e28c176 100644
--- a/src/backend/utils/misc/stack_depth.c
+++ b/src/backend/utils/misc/stack_depth.c
@@ -108,13 +108,28 @@ check_stack_depth(void)
 bool
 stack_is_too_deep(void)
 {
+#ifndef HAVE__BUILTIN_FRAME_ADDRESS
 	char		stack_top_loc;
+#endif
 	ssize_t		stack_depth;
+	char	   *stack_address;
+
+	/*
+	 * With address sanitizer's stack-use-after-return check, stack variables
+	 * are moved to heap allocations, to allow to detect references to the
+	 * memory at a later time. That would break our stack-depth check. Luckily
+	 * __builtin_frame_address() works correctly, even under asan.
+	 */
+#ifndef HAVE__BUILTIN_FRAME_ADDRESS
+	stack_address = &stack_top_loc;
+#else
+	stack_address = (char *) __builtin_frame_address(0);
+#endif
 
 	/*
 	 * Compute distance from reference point to my local variables
 	 */
-	stack_depth = (ssize_t) (stack_base_ptr - &stack_top_loc);
+	stack_depth = (ssize_t) (stack_base_ptr - stack_address);
 
 	/*
 	 * Take abs value, since stacks grow up on some machines, down on others
diff --git a/.cirrus.tasks.yml b/.cirrus.tasks.yml
index a22cef063f3..8683d1ae9c7 100644
--- a/.cirrus.tasks.yml
+++ b/.cirrus.tasks.yml
@@ -447,7 +447,7 @@ task:
     # print_stacktraces=1,verbosity=2, duh
     # detect_leaks=0: too many uninteresting leak errors in short-lived binaries
     UBSAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:verbosity=2
-    ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0:detect_stack_use_after_return=0
+    ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0
 
     # SANITIZER_FLAGS is set in the tasks below
     CFLAGS: -Og -ggdb -fno-sanitize-recover=all $SANITIZER_FLAGS
-- 
2.53.0.1.gb2826b52eb


--7e2sypi5gxvkgog6--





^ permalink  raw  reply  [nested|flat] 114+ messages in thread

* [PATCH v2 1/4] Make stack depth check work with asan's use-after-return
@ 2026-02-07 19:04 Andres Freund <[email protected]>
  0 siblings, 0 replies; 114+ messages in thread

From: Andres Freund @ 2026-02-07 19:04 UTC (permalink / raw)

With address sanitizer's stack-use-after-return check, stack variables are
moved to heap allocations, to allow to detect references to the memory at a
later time. That broke our stack-depth check, which is why we had to disable
detect_stack_use_after_return in CI. Luckily __builtin_frame_address() works
correctly, even under asan, so use that.

Author:
Reviewed-by:
Discussion: https://postgr.es/m/
Backpatch:
---
 src/backend/utils/misc/stack_depth.c | 17 ++++++++++++++++-
 .cirrus.tasks.yml                    |  2 +-
 2 files changed, 17 insertions(+), 2 deletions(-)

diff --git a/src/backend/utils/misc/stack_depth.c b/src/backend/utils/misc/stack_depth.c
index 61a07cf824e..9955e28c176 100644
--- a/src/backend/utils/misc/stack_depth.c
+++ b/src/backend/utils/misc/stack_depth.c
@@ -108,13 +108,28 @@ check_stack_depth(void)
 bool
 stack_is_too_deep(void)
 {
+#ifndef HAVE__BUILTIN_FRAME_ADDRESS
 	char		stack_top_loc;
+#endif
 	ssize_t		stack_depth;
+	char	   *stack_address;
+
+	/*
+	 * With address sanitizer's stack-use-after-return check, stack variables
+	 * are moved to heap allocations, to allow to detect references to the
+	 * memory at a later time. That would break our stack-depth check. Luckily
+	 * __builtin_frame_address() works correctly, even under asan.
+	 */
+#ifndef HAVE__BUILTIN_FRAME_ADDRESS
+	stack_address = &stack_top_loc;
+#else
+	stack_address = (char *) __builtin_frame_address(0);
+#endif
 
 	/*
 	 * Compute distance from reference point to my local variables
 	 */
-	stack_depth = (ssize_t) (stack_base_ptr - &stack_top_loc);
+	stack_depth = (ssize_t) (stack_base_ptr - stack_address);
 
 	/*
 	 * Take abs value, since stacks grow up on some machines, down on others
diff --git a/.cirrus.tasks.yml b/.cirrus.tasks.yml
index a22cef063f3..8683d1ae9c7 100644
--- a/.cirrus.tasks.yml
+++ b/.cirrus.tasks.yml
@@ -447,7 +447,7 @@ task:
     # print_stacktraces=1,verbosity=2, duh
     # detect_leaks=0: too many uninteresting leak errors in short-lived binaries
     UBSAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:verbosity=2
-    ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0:detect_stack_use_after_return=0
+    ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0
 
     # SANITIZER_FLAGS is set in the tasks below
     CFLAGS: -Og -ggdb -fno-sanitize-recover=all $SANITIZER_FLAGS
-- 
2.53.0.1.gb2826b52eb


--7e2sypi5gxvkgog6--





^ permalink  raw  reply  [nested|flat] 114+ messages in thread

* [PATCH v2 1/4] Make stack depth check work with asan's use-after-return
@ 2026-02-07 19:04 Andres Freund <[email protected]>
  0 siblings, 0 replies; 114+ messages in thread

From: Andres Freund @ 2026-02-07 19:04 UTC (permalink / raw)

With address sanitizer's stack-use-after-return check, stack variables are
moved to heap allocations, to allow to detect references to the memory at a
later time. That broke our stack-depth check, which is why we had to disable
detect_stack_use_after_return in CI. Luckily __builtin_frame_address() works
correctly, even under asan, so use that.

Author:
Reviewed-by:
Discussion: https://postgr.es/m/
Backpatch:
---
 src/backend/utils/misc/stack_depth.c | 17 ++++++++++++++++-
 .cirrus.tasks.yml                    |  2 +-
 2 files changed, 17 insertions(+), 2 deletions(-)

diff --git a/src/backend/utils/misc/stack_depth.c b/src/backend/utils/misc/stack_depth.c
index 61a07cf824e..9955e28c176 100644
--- a/src/backend/utils/misc/stack_depth.c
+++ b/src/backend/utils/misc/stack_depth.c
@@ -108,13 +108,28 @@ check_stack_depth(void)
 bool
 stack_is_too_deep(void)
 {
+#ifndef HAVE__BUILTIN_FRAME_ADDRESS
 	char		stack_top_loc;
+#endif
 	ssize_t		stack_depth;
+	char	   *stack_address;
+
+	/*
+	 * With address sanitizer's stack-use-after-return check, stack variables
+	 * are moved to heap allocations, to allow to detect references to the
+	 * memory at a later time. That would break our stack-depth check. Luckily
+	 * __builtin_frame_address() works correctly, even under asan.
+	 */
+#ifndef HAVE__BUILTIN_FRAME_ADDRESS
+	stack_address = &stack_top_loc;
+#else
+	stack_address = (char *) __builtin_frame_address(0);
+#endif
 
 	/*
 	 * Compute distance from reference point to my local variables
 	 */
-	stack_depth = (ssize_t) (stack_base_ptr - &stack_top_loc);
+	stack_depth = (ssize_t) (stack_base_ptr - stack_address);
 
 	/*
 	 * Take abs value, since stacks grow up on some machines, down on others
diff --git a/.cirrus.tasks.yml b/.cirrus.tasks.yml
index a22cef063f3..8683d1ae9c7 100644
--- a/.cirrus.tasks.yml
+++ b/.cirrus.tasks.yml
@@ -447,7 +447,7 @@ task:
     # print_stacktraces=1,verbosity=2, duh
     # detect_leaks=0: too many uninteresting leak errors in short-lived binaries
     UBSAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:verbosity=2
-    ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0:detect_stack_use_after_return=0
+    ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0
 
     # SANITIZER_FLAGS is set in the tasks below
     CFLAGS: -Og -ggdb -fno-sanitize-recover=all $SANITIZER_FLAGS
-- 
2.53.0.1.gb2826b52eb


--7e2sypi5gxvkgog6--





^ permalink  raw  reply  [nested|flat] 114+ messages in thread

* [PATCH v2 1/4] Make stack depth check work with asan's use-after-return
@ 2026-02-07 19:04 Andres Freund <[email protected]>
  0 siblings, 0 replies; 114+ messages in thread

From: Andres Freund @ 2026-02-07 19:04 UTC (permalink / raw)

With address sanitizer's stack-use-after-return check, stack variables are
moved to heap allocations, to allow to detect references to the memory at a
later time. That broke our stack-depth check, which is why we had to disable
detect_stack_use_after_return in CI. Luckily __builtin_frame_address() works
correctly, even under asan, so use that.

Author:
Reviewed-by:
Discussion: https://postgr.es/m/
Backpatch:
---
 src/backend/utils/misc/stack_depth.c | 17 ++++++++++++++++-
 .cirrus.tasks.yml                    |  2 +-
 2 files changed, 17 insertions(+), 2 deletions(-)

diff --git a/src/backend/utils/misc/stack_depth.c b/src/backend/utils/misc/stack_depth.c
index 61a07cf824e..9955e28c176 100644
--- a/src/backend/utils/misc/stack_depth.c
+++ b/src/backend/utils/misc/stack_depth.c
@@ -108,13 +108,28 @@ check_stack_depth(void)
 bool
 stack_is_too_deep(void)
 {
+#ifndef HAVE__BUILTIN_FRAME_ADDRESS
 	char		stack_top_loc;
+#endif
 	ssize_t		stack_depth;
+	char	   *stack_address;
+
+	/*
+	 * With address sanitizer's stack-use-after-return check, stack variables
+	 * are moved to heap allocations, to allow to detect references to the
+	 * memory at a later time. That would break our stack-depth check. Luckily
+	 * __builtin_frame_address() works correctly, even under asan.
+	 */
+#ifndef HAVE__BUILTIN_FRAME_ADDRESS
+	stack_address = &stack_top_loc;
+#else
+	stack_address = (char *) __builtin_frame_address(0);
+#endif
 
 	/*
 	 * Compute distance from reference point to my local variables
 	 */
-	stack_depth = (ssize_t) (stack_base_ptr - &stack_top_loc);
+	stack_depth = (ssize_t) (stack_base_ptr - stack_address);
 
 	/*
 	 * Take abs value, since stacks grow up on some machines, down on others
diff --git a/.cirrus.tasks.yml b/.cirrus.tasks.yml
index a22cef063f3..8683d1ae9c7 100644
--- a/.cirrus.tasks.yml
+++ b/.cirrus.tasks.yml
@@ -447,7 +447,7 @@ task:
     # print_stacktraces=1,verbosity=2, duh
     # detect_leaks=0: too many uninteresting leak errors in short-lived binaries
     UBSAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:verbosity=2
-    ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0:detect_stack_use_after_return=0
+    ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0
 
     # SANITIZER_FLAGS is set in the tasks below
     CFLAGS: -Og -ggdb -fno-sanitize-recover=all $SANITIZER_FLAGS
-- 
2.53.0.1.gb2826b52eb


--7e2sypi5gxvkgog6--





^ permalink  raw  reply  [nested|flat] 114+ messages in thread

* [PATCH v2 1/4] Make stack depth check work with asan's use-after-return
@ 2026-02-07 19:04 Andres Freund <[email protected]>
  0 siblings, 0 replies; 114+ messages in thread

From: Andres Freund @ 2026-02-07 19:04 UTC (permalink / raw)

With address sanitizer's stack-use-after-return check, stack variables are
moved to heap allocations, to allow to detect references to the memory at a
later time. That broke our stack-depth check, which is why we had to disable
detect_stack_use_after_return in CI. Luckily __builtin_frame_address() works
correctly, even under asan, so use that.

Author:
Reviewed-by:
Discussion: https://postgr.es/m/
Backpatch:
---
 src/backend/utils/misc/stack_depth.c | 17 ++++++++++++++++-
 .cirrus.tasks.yml                    |  2 +-
 2 files changed, 17 insertions(+), 2 deletions(-)

diff --git a/src/backend/utils/misc/stack_depth.c b/src/backend/utils/misc/stack_depth.c
index 61a07cf824e..9955e28c176 100644
--- a/src/backend/utils/misc/stack_depth.c
+++ b/src/backend/utils/misc/stack_depth.c
@@ -108,13 +108,28 @@ check_stack_depth(void)
 bool
 stack_is_too_deep(void)
 {
+#ifndef HAVE__BUILTIN_FRAME_ADDRESS
 	char		stack_top_loc;
+#endif
 	ssize_t		stack_depth;
+	char	   *stack_address;
+
+	/*
+	 * With address sanitizer's stack-use-after-return check, stack variables
+	 * are moved to heap allocations, to allow to detect references to the
+	 * memory at a later time. That would break our stack-depth check. Luckily
+	 * __builtin_frame_address() works correctly, even under asan.
+	 */
+#ifndef HAVE__BUILTIN_FRAME_ADDRESS
+	stack_address = &stack_top_loc;
+#else
+	stack_address = (char *) __builtin_frame_address(0);
+#endif
 
 	/*
 	 * Compute distance from reference point to my local variables
 	 */
-	stack_depth = (ssize_t) (stack_base_ptr - &stack_top_loc);
+	stack_depth = (ssize_t) (stack_base_ptr - stack_address);
 
 	/*
 	 * Take abs value, since stacks grow up on some machines, down on others
diff --git a/.cirrus.tasks.yml b/.cirrus.tasks.yml
index a22cef063f3..8683d1ae9c7 100644
--- a/.cirrus.tasks.yml
+++ b/.cirrus.tasks.yml
@@ -447,7 +447,7 @@ task:
     # print_stacktraces=1,verbosity=2, duh
     # detect_leaks=0: too many uninteresting leak errors in short-lived binaries
     UBSAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:verbosity=2
-    ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0:detect_stack_use_after_return=0
+    ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0
 
     # SANITIZER_FLAGS is set in the tasks below
     CFLAGS: -Og -ggdb -fno-sanitize-recover=all $SANITIZER_FLAGS
-- 
2.53.0.1.gb2826b52eb


--7e2sypi5gxvkgog6--





^ permalink  raw  reply  [nested|flat] 114+ messages in thread

* [PATCH v2 1/4] Make stack depth check work with asan's use-after-return
@ 2026-02-07 19:04 Andres Freund <[email protected]>
  0 siblings, 0 replies; 114+ messages in thread

From: Andres Freund @ 2026-02-07 19:04 UTC (permalink / raw)

With address sanitizer's stack-use-after-return check, stack variables are
moved to heap allocations, to allow to detect references to the memory at a
later time. That broke our stack-depth check, which is why we had to disable
detect_stack_use_after_return in CI. Luckily __builtin_frame_address() works
correctly, even under asan, so use that.

Author:
Reviewed-by:
Discussion: https://postgr.es/m/
Backpatch:
---
 src/backend/utils/misc/stack_depth.c | 17 ++++++++++++++++-
 .cirrus.tasks.yml                    |  2 +-
 2 files changed, 17 insertions(+), 2 deletions(-)

diff --git a/src/backend/utils/misc/stack_depth.c b/src/backend/utils/misc/stack_depth.c
index 61a07cf824e..9955e28c176 100644
--- a/src/backend/utils/misc/stack_depth.c
+++ b/src/backend/utils/misc/stack_depth.c
@@ -108,13 +108,28 @@ check_stack_depth(void)
 bool
 stack_is_too_deep(void)
 {
+#ifndef HAVE__BUILTIN_FRAME_ADDRESS
 	char		stack_top_loc;
+#endif
 	ssize_t		stack_depth;
+	char	   *stack_address;
+
+	/*
+	 * With address sanitizer's stack-use-after-return check, stack variables
+	 * are moved to heap allocations, to allow to detect references to the
+	 * memory at a later time. That would break our stack-depth check. Luckily
+	 * __builtin_frame_address() works correctly, even under asan.
+	 */
+#ifndef HAVE__BUILTIN_FRAME_ADDRESS
+	stack_address = &stack_top_loc;
+#else
+	stack_address = (char *) __builtin_frame_address(0);
+#endif
 
 	/*
 	 * Compute distance from reference point to my local variables
 	 */
-	stack_depth = (ssize_t) (stack_base_ptr - &stack_top_loc);
+	stack_depth = (ssize_t) (stack_base_ptr - stack_address);
 
 	/*
 	 * Take abs value, since stacks grow up on some machines, down on others
diff --git a/.cirrus.tasks.yml b/.cirrus.tasks.yml
index a22cef063f3..8683d1ae9c7 100644
--- a/.cirrus.tasks.yml
+++ b/.cirrus.tasks.yml
@@ -447,7 +447,7 @@ task:
     # print_stacktraces=1,verbosity=2, duh
     # detect_leaks=0: too many uninteresting leak errors in short-lived binaries
     UBSAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:verbosity=2
-    ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0:detect_stack_use_after_return=0
+    ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0
 
     # SANITIZER_FLAGS is set in the tasks below
     CFLAGS: -Og -ggdb -fno-sanitize-recover=all $SANITIZER_FLAGS
-- 
2.53.0.1.gb2826b52eb


--7e2sypi5gxvkgog6--





^ permalink  raw  reply  [nested|flat] 114+ messages in thread

* [PATCH v2 1/4] Make stack depth check work with asan's use-after-return
@ 2026-02-07 19:04 Andres Freund <[email protected]>
  0 siblings, 0 replies; 114+ messages in thread

From: Andres Freund @ 2026-02-07 19:04 UTC (permalink / raw)

With address sanitizer's stack-use-after-return check, stack variables are
moved to heap allocations, to allow to detect references to the memory at a
later time. That broke our stack-depth check, which is why we had to disable
detect_stack_use_after_return in CI. Luckily __builtin_frame_address() works
correctly, even under asan, so use that.

Author:
Reviewed-by:
Discussion: https://postgr.es/m/
Backpatch:
---
 src/backend/utils/misc/stack_depth.c | 17 ++++++++++++++++-
 .cirrus.tasks.yml                    |  2 +-
 2 files changed, 17 insertions(+), 2 deletions(-)

diff --git a/src/backend/utils/misc/stack_depth.c b/src/backend/utils/misc/stack_depth.c
index 61a07cf824e..9955e28c176 100644
--- a/src/backend/utils/misc/stack_depth.c
+++ b/src/backend/utils/misc/stack_depth.c
@@ -108,13 +108,28 @@ check_stack_depth(void)
 bool
 stack_is_too_deep(void)
 {
+#ifndef HAVE__BUILTIN_FRAME_ADDRESS
 	char		stack_top_loc;
+#endif
 	ssize_t		stack_depth;
+	char	   *stack_address;
+
+	/*
+	 * With address sanitizer's stack-use-after-return check, stack variables
+	 * are moved to heap allocations, to allow to detect references to the
+	 * memory at a later time. That would break our stack-depth check. Luckily
+	 * __builtin_frame_address() works correctly, even under asan.
+	 */
+#ifndef HAVE__BUILTIN_FRAME_ADDRESS
+	stack_address = &stack_top_loc;
+#else
+	stack_address = (char *) __builtin_frame_address(0);
+#endif
 
 	/*
 	 * Compute distance from reference point to my local variables
 	 */
-	stack_depth = (ssize_t) (stack_base_ptr - &stack_top_loc);
+	stack_depth = (ssize_t) (stack_base_ptr - stack_address);
 
 	/*
 	 * Take abs value, since stacks grow up on some machines, down on others
diff --git a/.cirrus.tasks.yml b/.cirrus.tasks.yml
index a22cef063f3..8683d1ae9c7 100644
--- a/.cirrus.tasks.yml
+++ b/.cirrus.tasks.yml
@@ -447,7 +447,7 @@ task:
     # print_stacktraces=1,verbosity=2, duh
     # detect_leaks=0: too many uninteresting leak errors in short-lived binaries
     UBSAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:verbosity=2
-    ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0:detect_stack_use_after_return=0
+    ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0
 
     # SANITIZER_FLAGS is set in the tasks below
     CFLAGS: -Og -ggdb -fno-sanitize-recover=all $SANITIZER_FLAGS
-- 
2.53.0.1.gb2826b52eb


--7e2sypi5gxvkgog6--





^ permalink  raw  reply  [nested|flat] 114+ messages in thread

* [PATCH v2 1/4] Make stack depth check work with asan's use-after-return
@ 2026-02-07 19:04 Andres Freund <[email protected]>
  0 siblings, 0 replies; 114+ messages in thread

From: Andres Freund @ 2026-02-07 19:04 UTC (permalink / raw)

With address sanitizer's stack-use-after-return check, stack variables are
moved to heap allocations, to allow to detect references to the memory at a
later time. That broke our stack-depth check, which is why we had to disable
detect_stack_use_after_return in CI. Luckily __builtin_frame_address() works
correctly, even under asan, so use that.

Author:
Reviewed-by:
Discussion: https://postgr.es/m/
Backpatch:
---
 src/backend/utils/misc/stack_depth.c | 17 ++++++++++++++++-
 .cirrus.tasks.yml                    |  2 +-
 2 files changed, 17 insertions(+), 2 deletions(-)

diff --git a/src/backend/utils/misc/stack_depth.c b/src/backend/utils/misc/stack_depth.c
index 61a07cf824e..9955e28c176 100644
--- a/src/backend/utils/misc/stack_depth.c
+++ b/src/backend/utils/misc/stack_depth.c
@@ -108,13 +108,28 @@ check_stack_depth(void)
 bool
 stack_is_too_deep(void)
 {
+#ifndef HAVE__BUILTIN_FRAME_ADDRESS
 	char		stack_top_loc;
+#endif
 	ssize_t		stack_depth;
+	char	   *stack_address;
+
+	/*
+	 * With address sanitizer's stack-use-after-return check, stack variables
+	 * are moved to heap allocations, to allow to detect references to the
+	 * memory at a later time. That would break our stack-depth check. Luckily
+	 * __builtin_frame_address() works correctly, even under asan.
+	 */
+#ifndef HAVE__BUILTIN_FRAME_ADDRESS
+	stack_address = &stack_top_loc;
+#else
+	stack_address = (char *) __builtin_frame_address(0);
+#endif
 
 	/*
 	 * Compute distance from reference point to my local variables
 	 */
-	stack_depth = (ssize_t) (stack_base_ptr - &stack_top_loc);
+	stack_depth = (ssize_t) (stack_base_ptr - stack_address);
 
 	/*
 	 * Take abs value, since stacks grow up on some machines, down on others
diff --git a/.cirrus.tasks.yml b/.cirrus.tasks.yml
index a22cef063f3..8683d1ae9c7 100644
--- a/.cirrus.tasks.yml
+++ b/.cirrus.tasks.yml
@@ -447,7 +447,7 @@ task:
     # print_stacktraces=1,verbosity=2, duh
     # detect_leaks=0: too many uninteresting leak errors in short-lived binaries
     UBSAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:verbosity=2
-    ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0:detect_stack_use_after_return=0
+    ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0
 
     # SANITIZER_FLAGS is set in the tasks below
     CFLAGS: -Og -ggdb -fno-sanitize-recover=all $SANITIZER_FLAGS
-- 
2.53.0.1.gb2826b52eb


--7e2sypi5gxvkgog6--





^ permalink  raw  reply  [nested|flat] 114+ messages in thread

* [PATCH v2 1/4] Make stack depth check work with asan's use-after-return
@ 2026-02-07 19:04 Andres Freund <[email protected]>
  0 siblings, 0 replies; 114+ messages in thread

From: Andres Freund @ 2026-02-07 19:04 UTC (permalink / raw)

With address sanitizer's stack-use-after-return check, stack variables are
moved to heap allocations, to allow to detect references to the memory at a
later time. That broke our stack-depth check, which is why we had to disable
detect_stack_use_after_return in CI. Luckily __builtin_frame_address() works
correctly, even under asan, so use that.

Author:
Reviewed-by:
Discussion: https://postgr.es/m/
Backpatch:
---
 src/backend/utils/misc/stack_depth.c | 17 ++++++++++++++++-
 .cirrus.tasks.yml                    |  2 +-
 2 files changed, 17 insertions(+), 2 deletions(-)

diff --git a/src/backend/utils/misc/stack_depth.c b/src/backend/utils/misc/stack_depth.c
index 61a07cf824e..9955e28c176 100644
--- a/src/backend/utils/misc/stack_depth.c
+++ b/src/backend/utils/misc/stack_depth.c
@@ -108,13 +108,28 @@ check_stack_depth(void)
 bool
 stack_is_too_deep(void)
 {
+#ifndef HAVE__BUILTIN_FRAME_ADDRESS
 	char		stack_top_loc;
+#endif
 	ssize_t		stack_depth;
+	char	   *stack_address;
+
+	/*
+	 * With address sanitizer's stack-use-after-return check, stack variables
+	 * are moved to heap allocations, to allow to detect references to the
+	 * memory at a later time. That would break our stack-depth check. Luckily
+	 * __builtin_frame_address() works correctly, even under asan.
+	 */
+#ifndef HAVE__BUILTIN_FRAME_ADDRESS
+	stack_address = &stack_top_loc;
+#else
+	stack_address = (char *) __builtin_frame_address(0);
+#endif
 
 	/*
 	 * Compute distance from reference point to my local variables
 	 */
-	stack_depth = (ssize_t) (stack_base_ptr - &stack_top_loc);
+	stack_depth = (ssize_t) (stack_base_ptr - stack_address);
 
 	/*
 	 * Take abs value, since stacks grow up on some machines, down on others
diff --git a/.cirrus.tasks.yml b/.cirrus.tasks.yml
index a22cef063f3..8683d1ae9c7 100644
--- a/.cirrus.tasks.yml
+++ b/.cirrus.tasks.yml
@@ -447,7 +447,7 @@ task:
     # print_stacktraces=1,verbosity=2, duh
     # detect_leaks=0: too many uninteresting leak errors in short-lived binaries
     UBSAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:verbosity=2
-    ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0:detect_stack_use_after_return=0
+    ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0
 
     # SANITIZER_FLAGS is set in the tasks below
     CFLAGS: -Og -ggdb -fno-sanitize-recover=all $SANITIZER_FLAGS
-- 
2.53.0.1.gb2826b52eb


--7e2sypi5gxvkgog6--





^ permalink  raw  reply  [nested|flat] 114+ messages in thread

* [PATCH v2 1/4] Make stack depth check work with asan's use-after-return
@ 2026-02-07 19:04 Andres Freund <[email protected]>
  0 siblings, 0 replies; 114+ messages in thread

From: Andres Freund @ 2026-02-07 19:04 UTC (permalink / raw)

With address sanitizer's stack-use-after-return check, stack variables are
moved to heap allocations, to allow to detect references to the memory at a
later time. That broke our stack-depth check, which is why we had to disable
detect_stack_use_after_return in CI. Luckily __builtin_frame_address() works
correctly, even under asan, so use that.

Author:
Reviewed-by:
Discussion: https://postgr.es/m/
Backpatch:
---
 src/backend/utils/misc/stack_depth.c | 17 ++++++++++++++++-
 .cirrus.tasks.yml                    |  2 +-
 2 files changed, 17 insertions(+), 2 deletions(-)

diff --git a/src/backend/utils/misc/stack_depth.c b/src/backend/utils/misc/stack_depth.c
index 61a07cf824e..9955e28c176 100644
--- a/src/backend/utils/misc/stack_depth.c
+++ b/src/backend/utils/misc/stack_depth.c
@@ -108,13 +108,28 @@ check_stack_depth(void)
 bool
 stack_is_too_deep(void)
 {
+#ifndef HAVE__BUILTIN_FRAME_ADDRESS
 	char		stack_top_loc;
+#endif
 	ssize_t		stack_depth;
+	char	   *stack_address;
+
+	/*
+	 * With address sanitizer's stack-use-after-return check, stack variables
+	 * are moved to heap allocations, to allow to detect references to the
+	 * memory at a later time. That would break our stack-depth check. Luckily
+	 * __builtin_frame_address() works correctly, even under asan.
+	 */
+#ifndef HAVE__BUILTIN_FRAME_ADDRESS
+	stack_address = &stack_top_loc;
+#else
+	stack_address = (char *) __builtin_frame_address(0);
+#endif
 
 	/*
 	 * Compute distance from reference point to my local variables
 	 */
-	stack_depth = (ssize_t) (stack_base_ptr - &stack_top_loc);
+	stack_depth = (ssize_t) (stack_base_ptr - stack_address);
 
 	/*
 	 * Take abs value, since stacks grow up on some machines, down on others
diff --git a/.cirrus.tasks.yml b/.cirrus.tasks.yml
index a22cef063f3..8683d1ae9c7 100644
--- a/.cirrus.tasks.yml
+++ b/.cirrus.tasks.yml
@@ -447,7 +447,7 @@ task:
     # print_stacktraces=1,verbosity=2, duh
     # detect_leaks=0: too many uninteresting leak errors in short-lived binaries
     UBSAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:verbosity=2
-    ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0:detect_stack_use_after_return=0
+    ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0
 
     # SANITIZER_FLAGS is set in the tasks below
     CFLAGS: -Og -ggdb -fno-sanitize-recover=all $SANITIZER_FLAGS
-- 
2.53.0.1.gb2826b52eb


--7e2sypi5gxvkgog6--





^ permalink  raw  reply  [nested|flat] 114+ messages in thread

* [PATCH v2 1/4] Make stack depth check work with asan's use-after-return
@ 2026-02-07 19:04 Andres Freund <[email protected]>
  0 siblings, 0 replies; 114+ messages in thread

From: Andres Freund @ 2026-02-07 19:04 UTC (permalink / raw)

With address sanitizer's stack-use-after-return check, stack variables are
moved to heap allocations, to allow to detect references to the memory at a
later time. That broke our stack-depth check, which is why we had to disable
detect_stack_use_after_return in CI. Luckily __builtin_frame_address() works
correctly, even under asan, so use that.

Author:
Reviewed-by:
Discussion: https://postgr.es/m/
Backpatch:
---
 src/backend/utils/misc/stack_depth.c | 17 ++++++++++++++++-
 .cirrus.tasks.yml                    |  2 +-
 2 files changed, 17 insertions(+), 2 deletions(-)

diff --git a/src/backend/utils/misc/stack_depth.c b/src/backend/utils/misc/stack_depth.c
index 61a07cf824e..9955e28c176 100644
--- a/src/backend/utils/misc/stack_depth.c
+++ b/src/backend/utils/misc/stack_depth.c
@@ -108,13 +108,28 @@ check_stack_depth(void)
 bool
 stack_is_too_deep(void)
 {
+#ifndef HAVE__BUILTIN_FRAME_ADDRESS
 	char		stack_top_loc;
+#endif
 	ssize_t		stack_depth;
+	char	   *stack_address;
+
+	/*
+	 * With address sanitizer's stack-use-after-return check, stack variables
+	 * are moved to heap allocations, to allow to detect references to the
+	 * memory at a later time. That would break our stack-depth check. Luckily
+	 * __builtin_frame_address() works correctly, even under asan.
+	 */
+#ifndef HAVE__BUILTIN_FRAME_ADDRESS
+	stack_address = &stack_top_loc;
+#else
+	stack_address = (char *) __builtin_frame_address(0);
+#endif
 
 	/*
 	 * Compute distance from reference point to my local variables
 	 */
-	stack_depth = (ssize_t) (stack_base_ptr - &stack_top_loc);
+	stack_depth = (ssize_t) (stack_base_ptr - stack_address);
 
 	/*
 	 * Take abs value, since stacks grow up on some machines, down on others
diff --git a/.cirrus.tasks.yml b/.cirrus.tasks.yml
index a22cef063f3..8683d1ae9c7 100644
--- a/.cirrus.tasks.yml
+++ b/.cirrus.tasks.yml
@@ -447,7 +447,7 @@ task:
     # print_stacktraces=1,verbosity=2, duh
     # detect_leaks=0: too many uninteresting leak errors in short-lived binaries
     UBSAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:verbosity=2
-    ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0:detect_stack_use_after_return=0
+    ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0
 
     # SANITIZER_FLAGS is set in the tasks below
     CFLAGS: -Og -ggdb -fno-sanitize-recover=all $SANITIZER_FLAGS
-- 
2.53.0.1.gb2826b52eb


--7e2sypi5gxvkgog6--





^ permalink  raw  reply  [nested|flat] 114+ messages in thread

* [PATCH 4/8] Use multiple snapshots to copy the data.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 114+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

REPACK (CONCURRENTLY) does not prevent applications from using the table that
is being processed, however it can prevent the xmin horizon from advancing and
thus restrict VACUUM for the whole database. This patch adds the ability to
use particular snapshot only for certain range of pages. Each time that range
is processed, a new snapshot is built, which supposedly has its xmin higher
than the previous snapshot.

Note that we still use the same XID throughout the REPACK execution, and that
also prevents the xmin horizon from advancing. The following part of this
series will fix the problem.

To use multiple snapshots, the data copying works as follows:

  1. Have the logical decoding system build a snapshot S0 for range R0 at
     LSN0. This snapshot sees all the data changes whose commit records have
     LSN < LSN0.

  2. Copy the pages in that range to the new relation. The changes not visible
     to the snapshot (because their transactions are still running from the
     POV of the snapshot) will appear in the output of the logical decoding
     system as soon as their commit records are decoded.

  3. Perform logical decoding of all changes we find in WAL for the table
     we're repacking, but only apply those that affect the range R0 in the old
     relation. (Naturally, we cannot apply ones that belong to other pages
     because it's impossible to UPDATE / DELETE a row in the new relation if
     it hasn't been copied yet.) Then consider LSN1 to be the position of the
     end of the last WAL record decoded.

  4. Build a new snapshot S1 at position LSN1, i.e. one that sees all the data
     whose commit records are at WAL positions < LSN1. Use this snapshot to
     copy the range of pages R1.

  5. Perform logical decoding like in step 3, but out of this next set, only
     apply changes belonging to ranges R0 *and* R1 in the old table.

  6. etc

Special attention needs to be paid to UPDATES that span page ranges. For
example, if the old tuple is in range R0, but the new tuple is in R1, and R1
hasn't been copied yet, we only DELETE the old version from the new
relation. The new version will be handled during processing of range R1. The
snapshot S1 will be based on WAL position following that UPDATE, so it'll see
the new tuple if its transaction's commit record is at WAL position lower than
the position where we built the snapshot. On the other hand, if the commit
record appears at higher position than the that of the snapshot, the insertion
of the new tuple will be decoded and replayed later (after the copying of
range R1 has completed).

Likewise, if the old tuple is in range R1 (not yet copied) but the new tuple
is in R0, we only perform INSERT on the new relation. The deletion of the old
version will either be visible to the snapshot S1 (i.e. the snapshot won't see
the old version), or replayed later.

Due to these cross-range UPDATEs, we must apply the changes pertaining to
given range before processing of the next range starts. Specifically, if
UPDATE becomes DELETE for specific range, that DELETE must be replayed soon
enough so that we don't see both old and new tuple when building the identity
index. The problem is that if the UPDATE does not change the identity key,
we'd end up with duplicate key values.

Even if the USING INDEX clause is specified, a sequential scan is used to
retrieve the tuples from the old relation: the approach described above
requires that the tuples are in CTID order. For sorting we use a regular table
("auxiliary table"), on which we create the clustering index and scan it. The
scan output is inserted into the new relation. Tuplesort is not appropriate
here because it has no identity index, so it's not possible to apply the
decoded changes to it "eagerly", as explained above.

A new GUC repack_snapshot_after can be used to set the number of pages per
snapshot. It's currently classified as DEVELOPER_OPTIONS and may be replaced
by a constant after enough evaluation is done.
---
 src/backend/access/heap/heapam_handler.c      | 238 ++++-
 src/backend/commands/repack.c                 | 861 +++++++++++++-----
 src/backend/commands/repack_worker.c          |  97 +-
 src/backend/replication/logical/decode.c      |  83 +-
 src/backend/replication/logical/logical.c     |  30 +-
 .../replication/logical/reorderbuffer.c       |  40 +
 src/backend/replication/logical/snapbuild.c   |  30 +-
 src/backend/replication/pgrepack/pgrepack.c   |  24 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +
 src/backend/utils/misc/guc_tables.c           |   1 +
 src/include/access/tableam.h                  |  14 +-
 src/include/commands/repack.h                 |  64 +-
 src/include/commands/repack_internal.h        |  13 +-
 src/include/replication/logical.h             |   2 +-
 src/include/replication/reorderbuffer.h       |   7 +
 src/test/modules/injection_points/Makefile    |   1 +
 .../expected/repack_snapshots.out             | 393 ++++++++
 src/test/modules/injection_points/meson.build |   1 +
 .../specs/repack_snapshots.spec               | 235 +++++
 19 files changed, 1850 insertions(+), 294 deletions(-)
 create mode 100644 src/test/modules/injection_points/expected/repack_snapshots.out
 create mode 100644 src/test/modules/injection_points/specs/repack_snapshots.spec

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 2866a5e696e..d402e51e6a1 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -43,12 +43,16 @@
 #include "storage/lmgr.h"
 #include "storage/lock.h"
 #include "storage/predicate.h"
+#include "storage/proc.h"
 #include "storage/procarray.h"
 #include "storage/smgr.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/rel.h"
 #include "utils/tuplesort.h"
 
+static Snapshot finalize_block_range(ChangeContext *chgcxt, BlockNumber cur,
+									 BlockNumber start, BlockNumber *end_p);
 static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform,
 									 RewriteState rwstate);
 
@@ -589,15 +593,14 @@ static void
 heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
 								 Relation OldIndex, bool use_sort,
 								 TransactionId OldestXmin,
-								 Snapshot snapshot,
 								 TransactionId *xid_cutoff,
 								 MultiXactId *multi_cutoff,
 								 double *num_tuples,
 								 double *tups_vacuumed,
-								 double *tups_recently_dead)
+								 double *tups_recently_dead,
+								 void *tableam_data)
 {
 	RewriteState rwstate;
-	BulkInsertState bistate;
 	IndexScanDesc indexScan;
 	TableScanDesc tableScan;
 	HeapScanDesc heapScan;
@@ -608,7 +611,11 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
 	TupleTableSlot *reform_slot;
 	BufferHeapTupleTableSlot *hslot;
 	BlockNumber prev_cblock = InvalidBlockNumber;
-	bool		concurrent = snapshot != NULL;
+	ChangeContext *chgcxt = (ChangeContext *) tableam_data;
+	bool		concurrent = chgcxt != NULL;
+	Snapshot	snapshot = NULL;
+	BlockNumber range_start = InvalidBlockNumber;
+	BlockNumber range_end = InvalidBlockNumber;
 
 	/* Remember if it's a system catalog */
 	is_system_catalog = IsSystemRelation(OldHeap);
@@ -629,14 +636,11 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
 	else
 		rwstate = NULL;
 
-	/* In concurrent mode, prepare for bulk-insert operation. */
-	if (concurrent)
-		bistate = GetBulkInsertState();
-	else
-		bistate = NULL;
-
-	/* Set up sorting if wanted */
-	if (use_sort)
+	/*
+	 * Set up sorting if wanted. CONCURRENTLY sorts the tuple w/o tuplesort,
+	 * see below.
+	 */
+	if (use_sort && !concurrent)
 		tuplesort = tuplesort_begin_cluster(oldTupDesc, OldIndex,
 											maintenance_work_mem,
 											NULL, TUPLESORT_NONE);
@@ -648,8 +652,11 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
 	 * that still need to be copied, we scan with SnapshotAny and use
 	 * HeapTupleSatisfiesVacuum for the visibility test.
 	 *
-	 * In the CONCURRENTLY case, we do regular MVCC visibility tests, using
-	 * the snapshot passed by the caller.
+	 * In the CONCURRENTLY case, we do regular MVCC visibility tests. The
+	 * snapshot changes several times during the scan so that we do not block
+	 * the progress of the xmin horizon for VACUUM too much.  Index scan
+	 * should not be used because it returns tuples in random order, which
+	 * makes it impossible to split the scan into block ranges.
 	 */
 	if (OldIndex != NULL && !use_sort)
 	{
@@ -659,6 +666,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
 		};
 		int64		ci_val[2];
 
+		Assert(!concurrent);
+
 		/* Set phase and OIDOldIndex to columns */
 		ci_val[0] = PROGRESS_REPACK_PHASE_INDEX_SCAN_HEAP;
 		ci_val[1] = RelationGetRelid(OldIndex);
@@ -666,10 +675,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
 
 		tableScan = NULL;
 		heapScan = NULL;
-		indexScan = index_beginscan(OldHeap, OldIndex,
-									snapshot ? snapshot : SnapshotAny,
-									NULL, 0, 0,
-									SO_NONE);
+		indexScan = index_beginscan(OldHeap, OldIndex, SnapshotAny, NULL, 0,
+									0, SO_NONE);
 		index_rescan(indexScan, NULL, 0, NULL, 0);
 	}
 	else
@@ -678,16 +685,28 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
 		pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 									 PROGRESS_REPACK_PHASE_SEQ_SCAN_HEAP);
 
-		tableScan = table_beginscan(OldHeap,
-									snapshot ? snapshot : SnapshotAny,
-									0, (ScanKey) NULL,
+		tableScan = table_beginscan(OldHeap, SnapshotAny, 0, (ScanKey) NULL,
 									SO_NONE);
 		heapScan = (HeapScanDesc) tableScan;
+
+		/*
+		 * In CONCURRENTLY mode we scan the table by ranges of blocks and the
+		 * algorithm below expects forward direction. (No other direction
+		 * should be set here regardless concurrently anyway.)
+		 */
+		Assert(heapScan->rs_dir == ForwardScanDirection || !concurrent);
 		indexScan = NULL;
 
 		/* Set total heap blocks */
 		pgstat_progress_update_param(PROGRESS_REPACK_TOTAL_HEAP_BLKS,
 									 heapScan->rs_nblocks);
+
+		/* Setup the first range. */
+		if (concurrent)
+		{
+			range_start = heapScan->rs_startblock;
+			range_end = range_start + repack_pages_per_snapshot;
+		}
 	}
 
 	slot = table_slot_create(OldHeap, NULL);
@@ -695,6 +714,30 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
 	reform_slot = MakeSingleTupleTableSlot(RelationGetDescr(OldHeap),
 										   &TTSOpsVirtual);
 
+	if (concurrent)
+	{
+		/*
+		 * Do not block the progress of xmin horizons.
+		 */
+		PopActiveSnapshot();
+		InvalidateCatalogSnapshot();
+
+		/*
+		 * As there is no snapshot, our xmin should be invalid now.
+		 *
+		 * XXX xid can still be valid. The next patches in the series fix
+		 * that.
+		 */
+		Assert(!TransactionIdIsValid(MyProc->xmin));
+
+		/*
+		 * Wait until the worker has the initial snapshot and retrieve it.
+		 */
+		snapshot = repack_get_snapshot(chgcxt);
+
+		PushActiveSnapshot(snapshot);
+	}
+
 	/*
 	 * Scan through the OldHeap, either in OldIndex order or sequentially;
 	 * copy each tuple into the NewHeap, or transiently to the tuplesort
@@ -711,6 +754,9 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
 
 		if (indexScan != NULL)
 		{
+			/* See above. */
+			Assert(!concurrent);
+
 			if (!index_getnext_slot(indexScan, ForwardScanDirection, slot))
 				break;
 
@@ -732,6 +778,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
 				 */
 				pgstat_progress_update_param(PROGRESS_REPACK_HEAP_BLKS_SCANNED,
 											 heapScan->rs_nblocks);
+
 				break;
 			}
 
@@ -848,10 +895,39 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
 				continue;
 			}
 		}
+		else
+		{
+			BlockNumber blkno;
+			bool		visible;
+
+			/*
+			 * With CONCURRENTLY, we use each snapshot only for certain range
+			 * of pages, so that VACUUM does not get blocked for too long. So
+			 * first check if the tuple falls into the current range.
+			 */
+			blkno = BufferGetBlockNumber(buf);
+
+			Assert(BlockNumberIsValid(range_end));
+
+			/* End of the current range or wraparound? */
+			if (blkno >= range_end || blkno < range_start)
+				snapshot = finalize_block_range(chgcxt, blkno, range_start,
+												&range_end);
+
+			/* Finally check the tuple visibility. */
+			LockBuffer(buf, BUFFER_LOCK_SHARE);
+			visible = HeapTupleSatisfiesVisibility(tuple, snapshot, buf);
+			LockBuffer(buf, BUFFER_LOCK_UNLOCK);
+
+			if (!visible)
+				continue;
+		}
 
 		*num_tuples += 1;
 		if (tuplesort != NULL)
 		{
+			Assert(!concurrent);
+
 			tuplesort_putheaptuple(tuplesort, tuple);
 
 			/*
@@ -872,7 +948,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
 			if (!concurrent)
 				reform_and_rewrite_tuple(slot, reform_slot, rwstate);
 			else
-				heap_insert_for_repack(NewHeap, slot, reform_slot, bistate);
+				heap_insert_for_repack(chgcxt, slot, reform_slot);
 
 			/*
 			 * In indexscan mode and also VACUUM FULL, report increase in
@@ -884,6 +960,28 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
 		}
 	}
 
+	if (concurrent)
+	{
+		XLogRecPtr	end_of_wal;
+
+		/*
+		 * Process the changes belonging to the last range.
+		 */
+		end_of_wal = GetFlushRecPtr(NULL);
+		repack_process_concurrent_changes(chgcxt, end_of_wal,
+										  InvalidBlockNumber,
+										  InvalidBlockNumber,
+										  false, false);
+
+		/*
+		 * There was an active transaction snapshot on entry, so push one
+		 * before return.
+		 */
+		PopActiveSnapshot();
+		PushActiveSnapshot(GetTransactionSnapshot());
+
+	}
+
 	if (indexScan != NULL)
 		index_endscan(indexScan);
 	if (tableScan != NULL)
@@ -929,10 +1027,13 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
 			ExecStoreHeapTuple(tuple, slot, false);
 
 			n_tuples += 1;
-			if (!concurrent)
-				reform_and_rewrite_tuple(slot, reform_slot, rwstate);
-			else
-				heap_insert_for_repack(NewHeap, slot, reform_slot, bistate);
+
+			/*
+			 * The CONCURRENTLY mode uses auxiliary tables rather than
+			 * tuplesort.
+			 */
+			Assert(!concurrent);
+			reform_and_rewrite_tuple(slot, reform_slot, rwstate);
 
 			/* Report n_tuples */
 			pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
@@ -948,8 +1049,89 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
 	/* Write out any remaining tuples, and fsync if needed */
 	if (rwstate)
 		end_heap_rewrite(rwstate);
-	if (bistate)
-		FreeBulkInsertState(bistate);
+}
+
+/*
+ * Finalize processing of the current block range.
+ *
+ * 'cur' is the current block, 'start' is the first block of the current
+ * range.
+ *
+ * '*end_p': on entry, the first block beyond the current range, on exit, the
+ * first block beyond the new range.
+ *
+ * Return the snapshot for the scan of the new range.
+ */
+static Snapshot
+finalize_block_range(ChangeContext *chgcxt, BlockNumber cur,
+					 BlockNumber start, BlockNumber *end_p)
+{
+	BlockNumber end = *end_p;
+	XLogRecPtr	end_of_wal;
+	Snapshot	snapshot;
+
+	/*
+	 * Wait here when testing how snapshot is changed at page boundary.
+	 */
+	INJECTION_POINT("repack-concurrently-new-range", NULL);
+
+	/*
+	 * Decode all the concurrent data changes committed so far before
+	 * requesting the next snapshot - these changes are applicable on top of
+	 * the current snapshot. Since we only copied part of the table so far,
+	 * only changes applicable to that part can be applied.
+	 *
+	 * It's important to apply the changes before we start copying the next
+	 * range of blocks. Without that, in case of concurrent UPDATE, we could
+	 * end up with both old and new tuple present in the new table: the old
+	 * still visible in the current range and the new already visible in the
+	 * following range (for which we'll use more recent snapshot). Thus it'd
+	 * be non-trivial to apply the UPDATE later. By replaying it now, we get
+	 * rid of the old tuple in the current range.
+	 */
+	end_of_wal = GetFlushRecPtr(NULL);
+	repack_process_concurrent_changes(chgcxt, end_of_wal, start, end, true,
+									  false);
+
+	/*
+	 * A new snapshot will be pushed below. Note that it's important to not do
+	 * this earlier, because - while processing the concurrent data changes -
+	 * we might have needed to fetch TOASTed values from the old relation -
+	 * see the UPDATE-to-INSERT conversion in apply_concurrent_changes(). As
+	 * this snapshot protects the data copied from VACUUM, it should also
+	 * protect the TOAST values referenced by the consequent UPDATE
+	 * statements.
+	 */
+	PopActiveSnapshot();
+	InvalidateCatalogSnapshot();
+
+	/* See above. */
+	Assert(!TransactionIdIsValid(MyProc->xmin));
+
+	/*
+	 * XXX It might be worth Assert(CatalogSnapshot == NULL) here, however
+	 * that symbol is not external.
+	 */
+
+	/*
+	 * Compute the end of the new range by aligning 'cur' to a multiple of
+	 * range boundary. This accounts for the possibility that some block
+	 * numbers could have been skipped (due to pages being empty) or that the
+	 * block number could have wrapped around.
+	 */
+	end = cur + repack_pages_per_snapshot - (cur % repack_pages_per_snapshot);
+	*end_p = end;
+
+	/*
+	 * Get the snapshot for the next range - it should have been built at the
+	 * position right after the last change decoded. Data present in the next
+	 * range of blocks will either be visible to the snapshot or appear in the
+	 * next batch of decoded changes.
+	 */
+	snapshot = repack_get_snapshot(chgcxt);
+	PushActiveSnapshot(snapshot);
+
+	return snapshot;
 }
 
 /*
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 04d39ad8c86..2debadd86ed 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -33,6 +33,7 @@
 #include "postgres.h"
 
 #include "access/amapi.h"
+#include "access/detoast.h"
 #include "access/heapam.h"
 #include "access/multixact.h"
 #include "access/relscan.h"
@@ -93,6 +94,12 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+/*
+ * When REPACK (CONCURRENTLY) copies data to the new heap, a new snapshot is
+ * built after processing this many pages. XXX Tune the value.
+ */
+int			repack_pages_per_snapshot = 1024;
+
 /*
  * Backend-local information to control the decoding worker.
  */
@@ -126,11 +133,11 @@ static void check_concurrent_repack_requirements(Relation rel,
 static void rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 							 Oid ident_idx);
 static void copy_table_data(Relation NewHeap, Relation OldHeap, Relation OldIndex,
-							Snapshot snapshot,
 							bool verbose,
 							bool *pSwapToastByContent,
 							TransactionId *pFreezeXid,
-							MultiXactId *pCutoffMulti);
+							MultiXactId *pCutoffMulti,
+							ChangeContext *chgcxt);
 static List *get_tables_to_repack(RepackCommand cmd, bool usingindex,
 								  MemoryContext permcxt);
 static List *get_tables_to_repack_partitioned(RepackCommand cmd,
@@ -139,23 +146,26 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
 
-static void apply_concurrent_changes(ChangeContext *chgcxt);
+static void apply_concurrent_changes(ChangeContext *chgcxt,
+									 BlockNumber range_start,
+									 BlockNumber range_end);
 static void apply_concurrent_insert(RepackDest *dest, TupleTableSlot *slot);
 static void apply_concurrent_update(RepackDest *dest,
 									TupleTableSlot *spilled_tuple,
 									TupleTableSlot *ondisk_tuple);
 static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
 static void restore_tuple(BufFile *file, Relation relation,
-						  TupleTableSlot *slot);
+						  TupleTableSlot *slot, BlockNumber *block_nr_p,
+						  BlockNumber *old_block_nr_p);
 static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
 								  TupleTableSlot *src);
+static bool is_block_in_range(BlockNumber blknum, BlockNumber start,
+							  BlockNumber end);
 static bool find_target_tuple(RepackDest *dest, TupleTableSlot *locator,
 							  TupleTableSlot *retrieved);
-static bool identity_key_equal(RepackDest *dest, TupleTableSlot *locator,
+static bool identity_key_equal(RepackDest *dest,
+							   TupleTableSlot *locator,
 							   TupleTableSlot *candidate);
-static void process_concurrent_changes(XLogRecPtr end_of_wal,
-									   ChangeContext *chgcxt,
-									   bool done);
 static void initialize_change_context(ChangeContext *chgcxt,
 									  Relation relation,
 									  Oid ident_index_id);
@@ -166,8 +176,12 @@ static void release_change_dest(RepackDest *dest);
 static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 											   Oid identIdx,
 											   TransactionId frozenXid,
-											   MultiXactId cutoffMulti);
+											   MultiXactId cutoffMulti,
+											   ChangeContext *chgcxt);
+static void process_auxiliary_table(ChangeContext *chgcxt, Relation OldHeap,
+									Oid identIdx);
 static List *build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes);
+static Oid	build_new_index(Relation NewHeap, Relation OldHeap, Oid oldindex);
 static void copy_index_constraints(Relation old_index, Oid new_index_id,
 								   Oid new_heap_id);
 static Relation process_single_relation(RepackStmt *stmt,
@@ -180,7 +194,6 @@ static Oid	determine_clustered_index(Relation rel, bool usingindex,
 static void start_repack_decoding_worker(Oid relid);
 static void stop_repack_decoding_worker(void);
 static void stop_repack_decoding_worker_cb(int code, Datum arg);
-static Snapshot get_initial_snapshot(DecodingWorker *worker);
 
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
@@ -947,6 +960,14 @@ check_concurrent_repack_requirements(Relation rel, Oid *ident_idx_p)
 						RelationGetRelationName(rel)));
 	}
 
+	/*
+	 * In the CONCURRENTLY mode we don't want to use the same snapshot
+	 * throughout the whole processing, as it could block the progress of xmin
+	 * horizon. Assert should be ok as we already disallow transaction block
+	 * in the CONCURRENTLY case.
+	 */
+	Assert(!IsolationUsesXactSnapshot());
+
 	*ident_idx_p = ident_idx;
 }
 
@@ -983,7 +1004,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 	TransactionId frozenXid;
 	MultiXactId cutoffMulti;
 	bool		concurrent = OidIsValid(ident_idx);
-	Snapshot	snapshot = NULL;
+	ChangeContext *chgcxt = NULL;
 #if USE_ASSERT_CHECKING
 	LOCKMODE	lmode;
 
@@ -1020,13 +1041,6 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * REPACK CONCURRENTLY.
 		 */
 		start_repack_decoding_worker(tableOid);
-
-		/*
-		 * Wait until the worker has the initial snapshot and retrieve it.
-		 */
-		snapshot = get_initial_snapshot(decoding_worker);
-
-		PushActiveSnapshot(snapshot);
 	}
 
 	/* for CLUSTER or REPACK USING INDEX, mark the index as the one to use */
@@ -1049,19 +1063,101 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 	Assert(CheckRelationOidLockedByMe(OIDNewHeap, AccessExclusiveLock, false));
 	NewHeap = table_open(OIDNewHeap, NoLock);
 
+	if (concurrent)
+	{
+		bool		need_aux_rel;
+
+		/*
+		 * Auxiliary table is needed for clustering in the CONCURRENTLY mode,
+		 * see comments in ChangeContext. FIXME Non-btree indexes are allowed
+		 * historically, but in general, these can hardly define any useful
+		 * order. We ignore them here.
+		 */
+		need_aux_rel = index != NULL && index->rd_rel->relam == BTREE_AM_OID;
+
+		/* Gather information to apply concurrent changes. */
+		chgcxt = palloc0_object(ChangeContext);
+
+		if (!need_aux_rel)
+		{
+			Oid			ident_idx_new;
+
+			/*
+			 * Create the identity index. We will need it during data copying
+			 * so that we can apply the data changes at the appropriate time -
+			 * see comments around the call of
+			 * repack_process_concurrent_changes() with block range specified.
+			 *
+			 * XXX NewHeap is empty - should we pass INDEX_CREATE_SKIP_BUILD?
+			 */
+			ident_idx_new = build_new_index(NewHeap, OldHeap, ident_idx);
+
+			initialize_change_context(chgcxt, NewHeap, ident_idx_new);
+		}
+		else
+		{
+			Oid			aux_oid;
+			Relation	aux_rel;
+			Oid			aux_ident_idx;
+
+			/*
+			 * As the concurrent data changes will be applied to the auxiliary
+			 * heap, the new heap does not need the identity index yet. We'll
+			 * build it after having copied the data from the auxiliary heap.
+			 * (Bulk insert should be more efficient.)
+			 */
+			initialize_change_context(chgcxt, NewHeap, InvalidOid);
+
+			/*
+			 * Like above, but only temporary - no other backend should need
+			 * it.
+			 */
+			aux_oid = make_new_heap(tableOid, tableSpace, accessMethod,
+									RELPERSISTENCE_TEMP, NoLock);
+			Assert(CheckRelationOidLockedByMe(aux_oid, AccessExclusiveLock,
+											  false));
+			aux_rel = table_open(aux_oid, NoLock);
+
+			/*
+			 * The same for identity index. (The additional
+			 * ShareUpdateExclusiveLock on ident_idx is not a problem, it'll
+			 * be released at the end of transaction.)
+			 */
+			aux_ident_idx = build_new_index(aux_rel, OldHeap, ident_idx);
+
+			/*
+			 * Make the relation ready for use.
+			 */
+			chgcxt->cc_dest_aux = palloc0_object(RepackDest);
+			initialize_change_dest(chgcxt->cc_dest_aux, aux_rel,
+								   aux_ident_idx);
+
+			/*
+			 * Set OID of the old relation's clustering index if it's
+			 * different from the identity index. Otherwise set InvalidOid to
+			 * indicate that the identity index should be used for clustering.
+			 */
+			if (RelationGetRelid(index) != ident_idx)
+				chgcxt->cc_clustering_index = RelationGetRelid(index);
+			else
+				chgcxt->cc_clustering_index = InvalidOid;
+		}
+	}
+
 	/* Copy the heap data into the new table in the desired order */
-	copy_table_data(NewHeap, OldHeap, index, snapshot, verbose,
-					&swap_toast_by_content, &frozenXid, &cutoffMulti);
+	copy_table_data(NewHeap, OldHeap, index, verbose,
+					&swap_toast_by_content, &frozenXid, &cutoffMulti,
+					chgcxt);
 
 	/* The historic snapshot won't be needed anymore. */
-	if (snapshot)
+	if (concurrent)
 	{
-		PopActiveSnapshot();
+		/*
+		 * Make sure the active snapshot can see the data copied, so the rows
+		 * can be updated / deleted.
+		 */
 		UpdateActiveSnapshotCommandId();
-	}
 
-	if (concurrent)
-	{
 		Assert(!swap_toast_by_content);
 
 		/*
@@ -1072,10 +1168,16 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 			index_close(index, NoLock);
 
 		rebuild_relation_finish_concurrent(NewHeap, OldHeap, ident_idx,
-										   frozenXid, cutoffMulti);
+										   frozenXid, cutoffMulti, chgcxt);
 
 		pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 									 PROGRESS_REPACK_PHASE_FINAL_CLEANUP);
+
+		/*
+		 * REPACK (CONCURRENTLY) launches separate transaction(s) so it
+		 * shouldn't rely on the current portal to pop the active snapshot.
+		 */
+		PopActiveSnapshot();
 	}
 	else
 	{
@@ -1238,10 +1340,9 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
  * Insert tuple when processing REPACK CONCURRENTLY.
  *
  * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
- * difficult to do the same in the catch-up phase (as the logical
- * decoding does not provide us with sufficient visibility
- * information). Thus we must use heap_insert() both during the
- * catch-up and here.
+ * difficult to do the same in the catch-up phase (as the logical decoding
+ * does not provide us with sufficient visibility information). Thus we must
+ * use heap_insert() both during the catch-up and here.
  *
  * 'reform' is a slot to use for tuple "reforming", typically to get set
  * values of dropped columns to NULL.
@@ -1249,20 +1350,27 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
  * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
  * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
  * this relation, so no logical replication subscription should need the data.
- *
- * BulkInsertState is used because many tuples are inserted in the typical
- * case.
  */
 void
-heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-					   TupleTableSlot *reform, BulkInsertStateData *bistate)
+heap_insert_for_repack(ChangeContext *chgcxt, TupleTableSlot *src,
+					   TupleTableSlot *reform)
 {
 	HeapTuple	tuple;
 	bool		shouldFree;
 	TupleTableSlot *slot;
+	RepackDest *dest;
+
+	/*
+	 * Use the current auxiliary table as output if one is active, otherwise
+	 * insert the tuple into the actual destination table.
+	 */
+	if (chgcxt->cc_dest_aux)
+		dest = chgcxt->cc_dest_aux;
+	else
+		dest = &chgcxt->cc_dest;
 
 	tuple = ExecFetchSlotHeapTuple(src, false, &shouldFree);
-	if (tuple_needs_reform(tuple, src->tts_tupleDescriptor))
+	if (reform != NULL && tuple_needs_reform(tuple, src->tts_tupleDescriptor))
 	{
 		clear_dropped_attributes(tuple, reform);
 		slot = reform;
@@ -1277,8 +1385,16 @@ heap_insert_for_repack(Relation rel, TupleTableSlot *src,
 	if (shouldFree)
 		heap_freetuple(tuple);
 
-	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
-					   TABLE_INSERT_NO_LOGICAL, bistate);
+	table_tuple_insert(dest->rel, slot, GetCurrentCommandId(true),
+					   TABLE_INSERT_NO_LOGICAL, dest->bistate);
+
+	/*
+	 * Insert the tuple into the identity index. initialize_change_context()
+	 * may skip opening of indexes if the identity index is not needed
+	 * immediately.
+	 */
+	if (dest->rri)
+		ExecInsertIndexTuples(dest->rri, dest->estate, 0, slot, NIL, NULL);
 }
 
 bool
@@ -1328,9 +1444,6 @@ clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
 /*
  * Do the physical copying of table data.
  *
- * 'snapshot' and 'decoding_ctx': see table_relation_copy_for_cluster(). Pass
- * iff concurrent processing is required.
- *
  * There are three output parameters:
  * *pSwapToastByContent is set true if toast tables must be swapped by content.
  * *pFreezeXid receives the TransactionId used as freeze cutoff point.
@@ -1338,8 +1451,9 @@ clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform)
  */
 static void
 copy_table_data(Relation NewHeap, Relation OldHeap, Relation OldIndex,
-				Snapshot snapshot, bool verbose, bool *pSwapToastByContent,
-				TransactionId *pFreezeXid, MultiXactId *pCutoffMulti)
+				bool verbose, bool *pSwapToastByContent,
+				TransactionId *pFreezeXid, MultiXactId *pCutoffMulti,
+				ChangeContext *chgcxt)
 {
 	Relation	relRelation;
 	HeapTuple	reltup;
@@ -1356,7 +1470,7 @@ copy_table_data(Relation NewHeap, Relation OldHeap, Relation OldIndex,
 	int			elevel = verbose ? INFO : DEBUG2;
 	PGRUsage	ru0;
 	char	   *nspname;
-	bool		concurrent = snapshot != NULL;
+	bool		concurrent = chgcxt != NULL;
 	LOCKMODE	lmode;
 
 	lmode = RepackLockLevel(concurrent);
@@ -1460,18 +1574,28 @@ copy_table_data(Relation NewHeap, Relation OldHeap, Relation OldIndex,
 			cutoffs.MultiXactCutoff = relminmxid;
 	}
 
-	/*
-	 * Decide whether to use an indexscan or seqscan-and-optional-sort to scan
-	 * the OldHeap.  We know how to use a sort to duplicate the ordering of a
-	 * btree index, and will use seqscan-and-sort for that case if the planner
-	 * tells us it's cheaper.  Otherwise, always indexscan if an index is
-	 * provided, else plain seqscan.
-	 */
-	if (OldIndex != NULL && OldIndex->rd_rel->relam == BTREE_AM_OID)
-		use_sort = plan_cluster_use_sort(RelationGetRelid(OldHeap),
-										 RelationGetRelid(OldIndex));
+	if (!concurrent)
+	{
+		/*
+		 * Decide whether to use an indexscan or seqscan-and-optional-sort to
+		 * scan the OldHeap.  We know how to use a sort to duplicate the
+		 * ordering of a btree index, and will use seqscan-and-sort for that
+		 * case if the planner tells us it's cheaper.  Otherwise, always
+		 * indexscan if an index is provided, else plain seqscan.
+		 */
+		if (OldIndex != NULL && OldIndex->rd_rel->relam == BTREE_AM_OID)
+			use_sort = plan_cluster_use_sort(RelationGetRelid(OldHeap),
+											 RelationGetRelid(OldIndex));
+		else
+			use_sort = false;
+	}
 	else
-		use_sort = false;
+	{
+		/*
+		 * To use multiple snapshots, we need to read the table sequentially.
+		 */
+		use_sort = true;
+	}
 
 	/* Log what we're doing */
 	if (OldIndex != NULL && !use_sort)
@@ -1498,11 +1622,11 @@ copy_table_data(Relation NewHeap, Relation OldHeap, Relation OldIndex,
 	 * values (e.g. because the AM doesn't use freezing).
 	 */
 	table_relation_copy_for_cluster(OldHeap, NewHeap, OldIndex, use_sort,
-									cutoffs.OldestXmin, snapshot,
+									cutoffs.OldestXmin,
 									&cutoffs.FreezeLimit,
 									&cutoffs.MultiXactCutoff,
 									&num_tuples, &tups_vacuumed,
-									&tups_recently_dead);
+									&tups_recently_dead, chgcxt);
 
 	/* return selected values to caller, get set as relfrozenxid/minmxid */
 	*pFreezeXid = cutoffs.FreezeLimit;
@@ -2428,6 +2552,8 @@ repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
  * instead return the opened and locked relcache entry, so that caller can
  * process the partitions using the multiple-table handling code.  In this
  * case, if an index name is given, it's up to the caller to resolve it.
+ *
+ * A new transaction is started in either case.
  */
 static Relation
 process_single_relation(RepackStmt *stmt, LOCKMODE lockmode, bool isTopLevel,
@@ -2440,6 +2566,31 @@ process_single_relation(RepackStmt *stmt, LOCKMODE lockmode, bool isTopLevel,
 	Assert(stmt->command == REPACK_COMMAND_CLUSTER ||
 		   stmt->command == REPACK_COMMAND_REPACK);
 
+	if (params->options & CLUOPT_CONCURRENT)
+	{
+		/*
+		 * Since REPACK (CONCURRENTLY) pops the active snapshot during the
+		 * processing (it creates and pushes snapshots on its own), and since
+		 * that snapshot can be referenced by the current portal, we need to
+		 * make sure that the portal has no dangling pointer to the snapshot.
+		 * Starting a new transaction seems to be the simplest way.
+		 *
+		 * XXX The following patches in the series make this unnecessary, as
+		 * they start new transactions for other reasons elsewhere.
+		 */
+		PopActiveSnapshot();
+		CommitTransactionCommand();
+
+		/* Start a new transaction. */
+		StartTransactionCommand();
+
+		/*
+		 * Functions in indexes may want a snapshot set. Note that the portal
+		 * is not aware of this one, so the caller needs to pop it explicitly.
+		 */
+		PushActiveSnapshot(GetTransactionSnapshot());
+	}
+
 	/*
 	 * Make sure ANALYZE is specified if a column list is present.
 	 */
@@ -2579,10 +2730,11 @@ RepackCommandAsString(RepackCommand cmd)
 }
 
 /*
- * Apply all the changes provided by decoding worker.
+ * Apply data changes that affect pages in given range.
  */
 static void
-apply_concurrent_changes(ChangeContext *chgcxt)
+apply_concurrent_changes(ChangeContext *chgcxt, BlockNumber range_start,
+						 BlockNumber range_end)
 {
 	ConcurrentChangeKind kind = '\0';
 	RepackDest *dest;
@@ -2591,18 +2743,31 @@ apply_concurrent_changes(ChangeContext *chgcxt)
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
 	bool		have_old_tuple = false;
+	bool		check_range;
 	MemoryContext oldcxt;
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
 	BufFile    *file;
 
-	dest = &chgcxt->cc_dest;
+	/*
+	 * Use the auxiliary table if one exists, otherwise the "final"
+	 * destination table.
+	 */
+	dest = chgcxt->cc_dest_aux ? chgcxt->cc_dest_aux : &chgcxt->cc_dest;
 	rel = dest->rel;
 
+	/*
+	 * Range needs to be checked if the bounds are specified. Expect either
+	 * both or none.
+	 */
+	Assert(BlockNumberIsValid(range_start) == BlockNumberIsValid(range_end));
+	check_range = BlockNumberIsValid(range_start);
+
 	shared = (DecodingWorkerShared *) dsm_segment_address(decoding_worker->seg);
 
 	/* Open the file containing the changes. */
-	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq_changes,
+						   false);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
@@ -2618,6 +2783,9 @@ apply_concurrent_changes(ChangeContext *chgcxt)
 	{
 		size_t		nread;
 		ConcurrentChangeKind prevkind = kind;
+		BlockNumber block,
+					old_block;
+		BlockNumber *old_block_p;
 
 		CHECK_FOR_INTERRUPTS();
 
@@ -2632,7 +2800,7 @@ apply_concurrent_changes(ChangeContext *chgcxt)
 		 */
 		if (kind == CHANGE_UPDATE_OLD)
 		{
-			restore_tuple(file, rel, old_update_tuple);
+			restore_tuple(file, rel, old_update_tuple, NULL, NULL);
 			have_old_tuple = true;
 			continue;
 		}
@@ -2656,22 +2824,39 @@ apply_concurrent_changes(ChangeContext *chgcxt)
 
 		/*
 		 * Now restore the tuple into the slot and execute the change.
+		 *
+		 * old_block is only stored with UPDATE_NEW.
 		 */
-		restore_tuple(file, rel, spilled_tuple);
+		old_block_p = kind == CHANGE_UPDATE_NEW ? &old_block : NULL;
+		restore_tuple(file, rel, spilled_tuple, &block, old_block_p);
 
 		if (kind == CHANGE_INSERT)
 		{
-			apply_concurrent_insert(dest, spilled_tuple);
+			/*
+			 * Only insert the tuple if it fits into the current range (or if
+			 * range does not matter).
+			 */
+			if (!check_range ||
+				is_block_in_range(block, range_start, range_end))
+				apply_concurrent_insert(dest, spilled_tuple);
 		}
 		else if (kind == CHANGE_DELETE)
 		{
-			bool		found;
+			/*
+			 * Only delete the tuple if it fits into the current range (or if
+			 * range does not matter).
+			 */
+			if (!check_range ||
+				is_block_in_range(block, range_start, range_end))
+			{
+				bool		found;
 
-			/* Find the tuple to be deleted */
-			found = find_target_tuple(dest, spilled_tuple, ondisk_tuple);
-			if (!found)
-				elog(ERROR, "could not find target tuple");
-			apply_concurrent_delete(rel, ondisk_tuple);
+				/* Find the tuple to be deleted */
+				found = find_target_tuple(dest, spilled_tuple, ondisk_tuple);
+				if (!found)
+					elog(ERROR, "could not find target tuple");
+				apply_concurrent_delete(rel, ondisk_tuple);
+			}
 		}
 		else if (kind == CHANGE_UPDATE_NEW)
 		{
@@ -2683,21 +2868,71 @@ apply_concurrent_changes(ChangeContext *chgcxt)
 			else
 				key = spilled_tuple;
 
-			/* Find the tuple to be updated or deleted. */
-			found = find_target_tuple(dest, key, ondisk_tuple);
-			if (!found)
-				elog(ERROR, "could not find target tuple");
-
 			/*
-			 * If 'tup' contains TOAST pointers, they point to the old
-			 * relation's toast. Copy the corresponding TOAST pointers for the
-			 * new relation from the existing tuple. (The fact that we
-			 * received a TOAST pointer here implies that the attribute hasn't
-			 * changed.)
+			 * Perform normal update if both old and new version are in the
+			 * current range.
 			 */
-			adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
+			if (!check_range ||
+				(is_block_in_range(old_block, range_start, range_end) &&
+				 is_block_in_range(block, range_start, range_end)))
+			{
+				/* Find the tuple to be updated or deleted. */
+				found = find_target_tuple(dest, key, ondisk_tuple);
+				if (!found)
+					elog(ERROR, "could not find target tuple");
+
+				/*
+				 * If 'spilled_tuple' contains TOAST pointers, they point to
+				 * the old relation's toast. Copy the corresponding TOAST
+				 * pointers for the new relation from the existing tuple. (The
+				 * fact that we received a TOAST pointer here implies that the
+				 * attribute hasn't changed.)
+				 */
+				adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
+
+				apply_concurrent_update(dest, spilled_tuple, ondisk_tuple);
+			}
+			else
+			{
+				Assert(check_range);
+
+				if (is_block_in_range(block, range_start, range_end))
+				{
+					/*
+					 * The old key is in another range, so only insert the new
+					 * one into the current range. The old version should not
+					 * be visible to the snapshot that we'll use to copy the
+					 * other range.
+					 *
+					 * Unlike UPDATE, there's no old tuple to copy the TOAST
+					 * pointers from. Therefore pass NULL for the source
+					 * tuple, to enforce detoasting of the TOAST pointers in
+					 * 'spilled_tuple'.
+					 */
+					adjust_toast_pointers(rel, spilled_tuple, NULL);
+
+					apply_concurrent_insert(dest, spilled_tuple);
+				}
+				else if (is_block_in_range(old_block, range_start, range_end))
+				{
+					found = find_target_tuple(dest, key, ondisk_tuple);
+					if (!found)
+						elog(ERROR, "could not find target tuple");
+
+					/*
+					 * The new key is in another range, so only delete the old
+					 * one from the current range. The new version should be
+					 * visible to the snapshot that we'll use to copy the
+					 * other range.
+					 */
+					apply_concurrent_delete(rel, ondisk_tuple);
+				}
 
-			apply_concurrent_update(dest, spilled_tuple, ondisk_tuple);
+				/*
+				 * Otherwise, both tuple versions belong to another range, so
+				 * there's nothing to do here.
+				 */
+			}
 
 			ExecClearTuple(old_update_tuple);
 			have_old_tuple = false;
@@ -2819,7 +3054,8 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
  * smaller than MaxAllocSize but the whole tuple is bigger.
  */
 static void
-restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
+restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot,
+			  BlockNumber *block_nr_p, BlockNumber *old_block_nr_p)
 {
 	uint32		t_len;
 	HeapTuple	tup;
@@ -2831,7 +3067,6 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	tup->t_data = (HeapTupleHeader) ((char *) tup + HEAPTUPLESIZE);
 	BufFileReadExact(file, tup->t_data, t_len);
 	tup->t_len = t_len;
-	ItemPointerSetInvalid(&tup->t_self);
 	tup->t_tableOid = RelationGetRelid(relation);
 
 	/*
@@ -2840,6 +3075,12 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 	 */
 	ExecForceStoreHeapTuple(tup, slot, false);
 
+	/* Handle TID separate because not all tuple slots care about it. */
+	if (block_nr_p)
+		*block_nr_p = ItemPointerGetBlockNumber(&tup->t_data->t_ctid);
+	if (old_block_nr_p)
+		BufFileReadExact(file, old_block_nr_p, sizeof(BlockNumber));
+
 	/*
 	 * Next, read any attributes we stored separately into the tts_values
 	 * array elements expecting them, if any.  This matches
@@ -2886,10 +3127,12 @@ restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
 
 /*
  * Adjust 'dest' replacing any EXTERNAL_ONDISK toast pointers with the
- * corresponding ones from 'src'.
+ * corresponding ones from 'src'. If 'src' is NULL, replace the toast pointer
+ * with the actual value.
  */
 static void
-adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *src)
+adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
+					  TupleTableSlot *src)
 {
 	TupleDesc	desc = dest->tts_tupleDescriptor;
 
@@ -2910,9 +3153,44 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *s
 		varlena_dst = (varlena *) DatumGetPointer(dest->tts_values[i]);
 		if (!VARATT_IS_EXTERNAL_ONDISK(varlena_dst))
 			continue;
-		slot_getsomeattrs(src, i + 1);
 
-		dest->tts_values[i] = src->tts_values[i];
+		/*
+		 * Ideally we just copy the value, but if there is no source tuple, we
+		 * need to detoast the value.
+		 */
+		if (src)
+		{
+			slot_getsomeattrs(src, i + 1);
+			dest->tts_values[i] = src->tts_values[i];
+		}
+		else
+		{
+			varlena    *detoasted;
+
+			detoasted = detoast_external_attr(varlena_dst);
+			dest->tts_values[i] = PointerGetDatum(detoasted);
+		}
+	}
+}
+
+/*
+ * Check if tuple originates from given range of blocks that have already been
+ * copied.
+ */
+static bool
+is_block_in_range(BlockNumber blknum, BlockNumber start, BlockNumber end)
+{
+	Assert(BlockNumberIsValid(start) && BlockNumberIsValid(end));
+	Assert(BlockNumberIsValid(blknum));
+
+	if (start < end)
+		return blknum >= start && blknum < end;
+	else
+	{
+		/* Has the scan position wrapped around? */
+		Assert(start > end);
+
+		return blknum >= start || blknum < end;
 	}
 }
 
@@ -3008,75 +3286,19 @@ identity_key_equal(RepackDest *dest, TupleTableSlot *locator,
 }
 
 /*
- * Decode and apply concurrent changes, up to (and including) the record whose
- * LSN is 'end_of_wal'.
- *
- * XXX the names "process_concurrent_changes" and "apply_concurrent_changes"
- * are far too similar to each other.
+ * Initialize the ChangeContext struct for the given relation.
  */
 static void
-process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
-{
-	DecodingWorkerShared *shared;
-	char		fname[MAXPGPATH];
-	BufFile    *file;
-
-	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
-								 PROGRESS_REPACK_PHASE_CATCH_UP);
-
-	/* Ask the worker for the file. */
-	shared = (DecodingWorkerShared *) dsm_segment_address(decoding_worker->seg);
-	SpinLockAcquire(&shared->mutex);
-	shared->lsn_upto = end_of_wal;
-	shared->done = done;
-	SpinLockRelease(&shared->mutex);
-
-	/*
-	 * The worker needs to finish processing of the current WAL record. Even
-	 * if it's idle, it'll need to close the output file. Thus we're likely to
-	 * wait, so prepare for sleep.
-	 */
-	ConditionVariablePrepareToSleep(&shared->cv);
-	for (;;)
-	{
-		int			last_exported;
-
-		SpinLockAcquire(&shared->mutex);
-		last_exported = shared->last_exported;
-		SpinLockRelease(&shared->mutex);
-
-		/*
-		 * Has the worker exported the file we are waiting for?
-		 */
-		if (last_exported == chgcxt->cc_file_seq)
-			break;
-
-		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
-	}
-	ConditionVariableCancelSleep();
-
-	/* Open the file. */
-	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
-	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
-	apply_concurrent_changes(chgcxt);
-
-	BufFileClose(file);
-
-	/* Get ready for the next file. */
-	chgcxt->cc_file_seq++;
-}
-
-/*
- * Initialize the ChangeContext struct for the given relation, with
- * the given index as identity index.
- */
-static void
-initialize_change_context(ChangeContext *chgcxt,
-						  Relation relation, Oid ident_index_id)
+initialize_change_context(ChangeContext *chgcxt, Relation relation,
+						  Oid ident_index_id)
 {
 	initialize_change_dest(&chgcxt->cc_dest, relation, ident_index_id);
 
-	chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
+	chgcxt->cc_file_seq_snapshot = 0;
+	chgcxt->cc_file_seq_changes = 0;
+
+	chgcxt->cc_dest_aux = NULL;
+	chgcxt->cc_clustering_index = InvalidOid;
 }
 
 /*
@@ -3086,6 +3308,8 @@ static void
 release_change_context(ChangeContext *chgcxt)
 {
 	release_change_dest(&chgcxt->cc_dest);
+	if (chgcxt->cc_dest_aux)
+		release_change_dest(chgcxt->cc_dest_aux);
 }
 
 /*
@@ -3100,6 +3324,10 @@ initialize_change_dest(RepackDest *dest, Relation relation,
 	dest->rel = relation;
 	dest->bistate = GetBulkInsertState();
 
+	/* If there's no identity index yet, there should be no indexes at all. */
+	if (!OidIsValid(ident_index_id))
+		return;
+
 	/* Only initialize fields needed by ExecInsertIndexTuples(). */
 	dest->estate = CreateExecutorState();
 
@@ -3185,6 +3413,11 @@ static void
 release_change_dest(RepackDest *dest)
 {
 	FreeBulkInsertState(dest->bistate);
+
+	/* It's possible that no indexes were opened during initialization. */
+	if (dest->rri == NULL)
+		return;
+
 	ExecCloseIndices(dest->rri);
 	FreeExecutorState(dest->estate);
 	/* XXX are these pfrees necessary? */
@@ -3203,7 +3436,8 @@ release_change_dest(RepackDest *dest)
 static void
 rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 								   Oid identIdx, TransactionId frozenXid,
-								   MultiXactId cutoffMulti)
+								   MultiXactId cutoffMulti,
+								   ChangeContext *chgcxt)
 {
 	List	   *ind_oids_new;
 	Oid			old_table_oid = RelationGetRelid(OldHeap);
@@ -3213,14 +3447,17 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 			   *lc2;
 	char		relpersistence;
 	bool		is_system_catalog;
-	Oid			ident_idx_new;
 	XLogRecPtr	end_of_wal;
 	List	   *indexrels;
-	ChangeContext chgcxt;
+	List	   *inds_tmp = NIL;
 
 	Assert(CheckRelationLockedByMe(OldHeap, ShareUpdateExclusiveLock, false));
 	Assert(CheckRelationLockedByMe(NewHeap, AccessExclusiveLock, false));
 
+	/* If we have the auxiliary table, this is the moment we should use it. */
+	if (chgcxt->cc_dest_aux)
+		process_auxiliary_table(chgcxt, OldHeap, identIdx);
+
 	/*
 	 * Unlike the exclusive case, we build new indexes for the new relation
 	 * rather than swapping the storage and reindexing the old relation. The
@@ -3236,32 +3473,26 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * might not be enough for commands like ALTER INDEX ... SET ... (Those
 	 * are not necessarily dangerous, but can make user confused if the
 	 * changes they do get lost due to REPACK.)
+	 *
+	 * As the identity index already had to be built, skip it here. XXX
+	 * Consider if the retail inserts during data copying (in the case w/o
+	 * auxiliary table) can be a problem in terms of index layout. Shouldn't
+	 * we drop the identity index and build it using bulk insert too?
 	 */
+	foreach_oid(ind_oid, ind_oids_old)
+	{
+		if (ind_oid != identIdx)
+			inds_tmp = lappend_oid(inds_tmp, ind_oid);
+	}
+	ind_oids_old = inds_tmp;
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
 	/*
-	 * The identity index in the new relation appears in the same relative
-	 * position as the corresponding index in the old relation.  Find it.
+	 * The identity index will be involved in the following processing.
 	 */
-	ident_idx_new = InvalidOid;
-	foreach_oid(ind_old, ind_oids_old)
-	{
-		if (identIdx == ind_old)
-		{
-			int			pos = foreach_current_index(ind_old);
-
-			if (list_length(ind_oids_new) <= pos)
-				elog(ERROR, "list of new indexes too short");
-			ident_idx_new = list_nth_oid(ind_oids_new, pos);
-			break;
-		}
-	}
-	if (!OidIsValid(ident_idx_new))
-		elog(ERROR, "could not find index matching \"%s\" at the new relation",
-			 get_rel_name(identIdx));
-
-	/* Gather information to apply concurrent changes. */
-	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
+	ind_oids_old = lappend_oid(ind_oids_old, identIdx);
+	ind_oids_new = lappend_oid(ind_oids_new,
+							   RelationGetRelid(chgcxt->cc_dest.ident_index));
 
 	/*
 	 * During testing, wait for another backend to perform concurrent data
@@ -3278,11 +3509,13 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
-	 * Apply concurrent changes first time, to minimize the time we need to
-	 * hold AccessExclusiveLock. (Quite some amount of WAL could have been
+	 * Decode and apply concurrent changes again, to minimize the time we need
+	 * to hold AccessExclusiveLock. (Quite some amount of WAL could have been
 	 * written during the data copying and index creation.)
 	 */
-	process_concurrent_changes(end_of_wal, &chgcxt, false);
+	repack_process_concurrent_changes(chgcxt, end_of_wal,
+									  InvalidBlockNumber, InvalidBlockNumber,
+									  false, false);
 
 	/*
 	 * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
@@ -3336,10 +3569,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
-	 * Apply the concurrent changes again. Indicate that the decoding worker
-	 * won't be needed anymore.
+	 * Decode and apply the concurrent changes again. Indicate that the
+	 * decoding worker won't be needed anymore.
 	 */
-	process_concurrent_changes(end_of_wal, &chgcxt, true);
+	repack_process_concurrent_changes(chgcxt, end_of_wal,
+									  InvalidBlockNumber, InvalidBlockNumber,
+									  false, true);
 
 	/* Remember info about rel before closing OldHeap */
 	relpersistence = OldHeap->rd_rel->relpersistence;
@@ -3387,7 +3622,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	table_close(NewHeap, NoLock);
 
 	/* Cleanup what we don't need anymore. (And close the identity index.) */
-	release_change_context(&chgcxt);
+	release_change_context(chgcxt);
 
 	/*
 	 * Swap the relations and their TOAST relations and TOAST indexes. This
@@ -3406,6 +3641,103 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 					 relpersistence);
 }
 
+/*
+ * Copy the contents of the auxiliary table to the new table in the desired
+ * order, then drop the auxiliary table.
+ */
+static void
+process_auxiliary_table(ChangeContext *chgcxt, Relation OldHeap, Oid identIdx)
+{
+	RepackDest *dest = chgcxt->cc_dest_aux;
+	Oid			ident_idx_new;
+	Relation	clustering_index;
+	IndexScanDesc scan;
+	TupleTableSlot *slot;
+	Oid			aux_oid;
+	ObjectAddress object;
+	Relation	rel;
+
+	/*
+	 * First, make sure the clustering index exists.
+	 */
+	if (OidIsValid(chgcxt->cc_clustering_index))
+	{
+		Oid			cl_ind_oid;
+
+		/*
+		 * Create it according to the clustering index on the old relation.
+		 */
+		cl_ind_oid = build_new_index(dest->rel, OldHeap,
+									 chgcxt->cc_clustering_index);
+		clustering_index = index_open(cl_ind_oid, NoLock);
+	}
+	else
+	{
+		/* The identity index is also the clustering index. */
+		clustering_index = dest->ident_index;
+	}
+
+	/*
+	 * Now do the copying. Before starting, clear ->cc_dest_aux so that
+	 * insertions go to the final table, rather than the auxiliary one.
+	 */
+	chgcxt->cc_dest_aux = NULL;
+	slot = table_slot_create(dest->rel, NULL);
+
+	/*
+	 * Note: the current active snapshot blocks the progress of xmin
+	 * horizon(s). The next patches in the series should fix this by using a
+	 * new kind of snapshot (which we can use here because there are no
+	 * transaction aborts in the auxiliary table).
+	 */
+	scan = index_beginscan(dest->rel, clustering_index, GetActiveSnapshot(),
+						   NULL, 0, 0, SO_NONE);
+	index_rescan(scan, NULL, 0, NULL, 0);
+	for (;;)
+	{
+		CHECK_FOR_INTERRUPTS();
+
+		if (!index_getnext_slot(scan, ForwardScanDirection, slot))
+			break;
+
+		/*
+		 * Reforming should have been performed during insertions into the
+		 * auxiliary table.
+		 */
+		heap_insert_for_repack(chgcxt, slot, NULL);
+	}
+	index_endscan(scan);
+	ExecDropSingleTupleTableSlot(slot);
+
+	/*
+	 * Close the relation, its identity index and clustering index if we had
+	 * to open it above. Lock will be released on commit.
+	 */
+	aux_oid = RelationGetRelid(dest->rel);
+	table_close(dest->rel, NoLock);
+	if (OidIsValid(chgcxt->cc_clustering_index))
+		index_close(clustering_index, NoLock);
+	/* Here we close the other indexes. */
+	release_change_dest(dest);
+
+	/* Drop the auxiliary table. */
+	object.classId = RelationRelationId;
+	object.objectId = aux_oid;
+	object.objectSubId = 0;
+	performDeletion(&object, DROP_RESTRICT, PERFORM_DELETION_INTERNAL);
+
+	/* Build the identity index on the new relation. */
+	ident_idx_new = build_new_index(chgcxt->cc_dest.rel, OldHeap, identIdx);
+
+	/*
+	 * Make the new heap ready to use the index for future replaying of
+	 * concurrent changes.
+	 */
+	rel = chgcxt->cc_dest.rel;
+	release_change_dest(&chgcxt->cc_dest);
+	initialize_change_dest(&chgcxt->cc_dest, rel, ident_idx_new);
+}
+
 /*
  * Build indexes on NewHeap according to those on OldHeap.
  *
@@ -3421,34 +3753,48 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 {
 	List	   *result = NIL;
 
-	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
-								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
-
 	foreach_oid(oldindex, OldIndexes)
 	{
 		Oid			newindex;
-		char	   *newName;
-		Relation	ind;
-
-		ind = index_open(oldindex, ShareUpdateExclusiveLock);
-
-		newName = ChooseRelationName(get_rel_name(oldindex),
-									 NULL,
-									 "repacknew",
-									 get_rel_namespace(ind->rd_index->indrelid),
-									 false);
-		newindex = index_create_copy(NewHeap, INDEX_CREATE_SUPPRESS_PROGRESS,
-									 oldindex, ind->rd_rel->reltablespace,
-									 newName);
-		copy_index_constraints(ind, newindex, RelationGetRelid(NewHeap));
-		result = lappend_oid(result, newindex);
 
-		index_close(ind, NoLock);
+		newindex = build_new_index(NewHeap, OldHeap, oldindex);
+		result = lappend_oid(result, newindex);
 	}
 
 	return result;
 }
 
+/*
+ * Subroutine of build_new_indexes().
+ */
+static Oid
+build_new_index(Relation NewHeap, Relation OldHeap, Oid oldindex)
+{
+	Oid			newindex;
+	char	   *newName;
+	Relation	ind;
+
+	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
+								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
+
+	ind = index_open(oldindex, ShareUpdateExclusiveLock);
+
+	newName = ChooseRelationName(get_rel_name(oldindex),
+								 NULL,
+								 "repacknew",
+								 get_rel_namespace(ind->rd_index->indrelid),
+								 false);
+	/* Functions in indexes may want a snapshot set. */
+	Assert(ActiveSnapshotSet());
+	newindex = index_create_copy(NewHeap, INDEX_CREATE_SUPPRESS_PROGRESS,
+								 oldindex, ind->rd_rel->reltablespace,
+								 newName);
+	copy_index_constraints(ind, newindex, RelationGetRelid(NewHeap));
+	index_close(ind, NoLock);
+
+	return newindex;
+}
+
 /*
  * Create a transient copy of a constraint -- supported by a transient
  * copy of the index that supports the original constraint.
@@ -3545,10 +3891,13 @@ start_repack_decoding_worker(Oid relid)
 
 	shared = (DecodingWorkerShared *) dsm_segment_address(decoding_worker->seg);
 	shared->initialized = false;
+	/* Snapshot is the first thing we need from the worker. */
+	shared->snapshot_requested = true;
 	shared->lsn_upto = InvalidXLogRecPtr;
 	shared->done = false;
 	SharedFileSetInit(&shared->sfs, decoding_worker->seg);
-	shared->last_exported = -1;
+	shared->last_exported_snapshot = -1;
+	shared->last_exported_changes = -1;
 	SpinLockInit(&shared->mutex);
 	shared->dbid = MyDatabaseId;
 
@@ -3670,10 +4019,10 @@ stop_repack_decoding_worker_cb(int code, Datum arg)
 }
 
 /*
- * Get the initial snapshot from the decoding worker.
+ * Get snapshot from the decoding worker.
  */
-static Snapshot
-get_initial_snapshot(DecodingWorker *worker)
+Snapshot
+repack_get_snapshot(ChangeContext *chgcxt)
 {
 	DecodingWorkerShared *shared;
 	char		fname[MAXPGPATH];
@@ -3682,12 +4031,13 @@ get_initial_snapshot(DecodingWorker *worker)
 	char	   *snap_space;
 	Snapshot	snapshot;
 
-	shared = (DecodingWorkerShared *) dsm_segment_address(worker->seg);
+	shared = (DecodingWorkerShared *) dsm_segment_address(decoding_worker->seg);
 
 	/*
-	 * The worker needs to initialize the logical decoding, which usually
-	 * takes some time. Therefore it makes sense to prepare for the sleep
-	 * first.
+	 * For the first snapshot request, the worker needs to initialize the
+	 * logical decoding, which usually takes some time. Therefore it makes
+	 * sense to prepare for the sleep first. Does it make sense to skip the
+	 * preparation on the next requests?
 	 */
 	ConditionVariablePrepareToSleep(&shared->cv);
 	for (;;)
@@ -3695,13 +4045,13 @@ get_initial_snapshot(DecodingWorker *worker)
 		int			last_exported;
 
 		SpinLockAcquire(&shared->mutex);
-		last_exported = shared->last_exported;
+		last_exported = shared->last_exported_snapshot;
 		SpinLockRelease(&shared->mutex);
 
 		/*
 		 * Has the worker exported the file we are waiting for?
 		 */
-		if (last_exported == WORKER_FILE_SNAPSHOT)
+		if (last_exported == chgcxt->cc_file_seq_snapshot)
 			break;
 
 		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
@@ -3709,20 +4059,98 @@ get_initial_snapshot(DecodingWorker *worker)
 	ConditionVariableCancelSleep();
 
 	/* Read the snapshot from a file. */
-	DecodingWorkerFileName(fname, shared->relid, WORKER_FILE_SNAPSHOT);
+	DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq_snapshot,
+						   true);
 	file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
 	BufFileReadExact(file, &snap_size, sizeof(snap_size));
 	snap_space = (char *) palloc(snap_size);
 	BufFileReadExact(file, snap_space, snap_size);
 	BufFileClose(file);
 
+#ifdef USE_ASSERT_CHECKING
+	SpinLockAcquire(&shared->mutex);
+	Assert(!shared->snapshot_requested);
+	shared->snapshot_requested = false;
+	SpinLockRelease(&shared->mutex);
+#endif
+
 	/* Restore it. */
 	snapshot = RestoreSnapshot(snap_space);
 	pfree(snap_space);
 
+	/* Get ready for the next snapshot. */
+	chgcxt->cc_file_seq_snapshot++;
+
 	return snapshot;
 }
 
+/*
+ * Get concurrent changes, up to (and including) the record whose LSN is
+ * 'end_of_wal', from the decoding worker, and apply them to the new table. If
+ * block range is specified, only apply changes related to that range.
+ *
+ * If 'request_snapshot' is true, the snapshot built at LSN following the last
+ * data change needs to be exported too.
+ */
+void
+repack_process_concurrent_changes(ChangeContext *chgcxt,
+								  XLogRecPtr end_of_wal,
+								  BlockNumber range_start,
+								  BlockNumber range_end,
+								  bool request_snapshot, bool done)
+{
+	DecodingWorkerShared *shared;
+
+	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
+								 PROGRESS_REPACK_PHASE_CATCH_UP);
+
+	/* Ask the worker for the file. */
+	shared = (DecodingWorkerShared *) dsm_segment_address(decoding_worker->seg);
+	SpinLockAcquire(&shared->mutex);
+	shared->lsn_upto = end_of_wal;
+	Assert(!shared->snapshot_requested);
+	shared->snapshot_requested = request_snapshot;
+	shared->done = done;
+	SpinLockRelease(&shared->mutex);
+
+	/*
+	 * The worker needs to finish processing of the current WAL record. Even
+	 * if it's idle, it'll need to close the output file. Thus we're likely to
+	 * wait, so prepare for sleep.
+	 */
+	ConditionVariablePrepareToSleep(&shared->cv);
+	for (;;)
+	{
+		int			last_exported;
+
+		SpinLockAcquire(&shared->mutex);
+		last_exported = shared->last_exported_changes;
+		SpinLockRelease(&shared->mutex);
+
+		/*
+		 * Has the worker exported the file we are waiting for?
+		 */
+		if (last_exported == chgcxt->cc_file_seq_changes)
+			break;
+
+		ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
+	}
+	ConditionVariableCancelSleep();
+
+#ifdef USE_ASSERT_CHECKING
+	/* No file is exported until the worker exports the next one. */
+	SpinLockAcquire(&shared->mutex);
+	Assert(XLogRecPtrIsInvalid(shared->lsn_upto));
+	SpinLockRelease(&shared->mutex);
+#endif
+
+	/* Apply the changes to the new table. */
+	apply_concurrent_changes(chgcxt, range_start, range_end);
+
+	/* Get ready for the next set of changes. */
+	chgcxt->cc_file_seq_changes++;
+}
+
 /*
  * Generate worker's file name into 'fname', which must be of size MAXPGPATH.
  * If relations of the same 'relid' happen to be processed at the same time,
@@ -3730,10 +4158,13 @@ get_initial_snapshot(DecodingWorker *worker)
  * be involved.
  */
 void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq, bool snapshot)
 {
 	/* The PID is already present in the fileset name, so we needn't add it */
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+	if (!snapshot)
+		snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+	else
+		snprintf(fname, MAXPGPATH, "%u-%u-snapshot", relid, seq);
 }
 
 /*
diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index db9ff057cc6..461d60ec0ca 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -33,8 +33,7 @@
 static void RepackWorkerShutdown(int code, Datum arg);
 static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
 static void repack_cleanup_logical_decoding(LogicalDecodingContext *ctx);
-static void export_initial_snapshot(Snapshot snapshot,
-									DecodingWorkerShared *shared);
+static void export_snapshot(Snapshot snapshot, DecodingWorkerShared *shared);
 static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
 									  DecodingWorkerShared *shared);
 
@@ -65,6 +64,8 @@ RepackWorkerMain(Datum main_arg)
 	shm_mq_handle *mqh;
 	LogicalDecodingContext *decoding_ctx;
 	SharedFileSet *sfs;
+	RepackDecodingState *dstate;
+	MemoryContext oldcxt;
 	Snapshot	snapshot;
 
 	am_repack_worker = true;
@@ -118,7 +119,9 @@ RepackWorkerMain(Datum main_arg)
 	 * anything in the shared memory until we have serialized the snapshot.
 	 */
 	SpinLockAcquire(&shared->mutex);
-	Assert(!XLogRecPtrIsValid(shared->lsn_upto));
+	/* Initially we're expected to provide a snapshot and only that. */
+	Assert(shared->snapshot_requested &&
+		   XLogRecPtrIsInvalid(shared->lsn_upto));
 	sfs = &shared->sfs;
 	SpinLockRelease(&shared->mutex);
 
@@ -139,9 +142,25 @@ RepackWorkerMain(Datum main_arg)
 	XactIsoLevel = XACT_REPEATABLE_READ;
 	XactReadOnly = true;
 
-	/* Build the initial snapshot and export it. */
+	/*
+	 * Build the initial snapshot and export it.
+	 *
+	 * Since there is no API to free the "external snapshot", and since such
+	 * snapshot is not guaranteed to be flat (i.e. pfree() is not appropriate)
+	 * the easiest way to clean it up is to use a separate memory context for
+	 * it.
+	 */
+	dstate = (RepackDecodingState *) decoding_ctx->output_writer_private;
+	MemoryContextReset(dstate->snapshot_cxt);
+	oldcxt = MemoryContextSwitchTo(dstate->snapshot_cxt);
 	snapshot = SnapBuildInitialSnapshot(decoding_ctx->snapshot_builder);
-	export_initial_snapshot(snapshot, shared);
+	MemoryContextSwitchTo(oldcxt);
+	export_snapshot(snapshot, shared);
+
+	/*
+	 * Adjust the replication slot's xmin so that VACUUM can do more work.
+	 */
+	LogicalIncreaseXminForSlot(InvalidXLogRecPtr, snapshot->xmin, false);
 
 	/*
 	 * Only historic snapshots should be used now. Do not let us restrict the
@@ -307,7 +326,7 @@ repack_cleanup_logical_decoding(LogicalDecodingContext *ctx)
  * Make snapshot available to the backend that launched the decoding worker.
  */
 static void
-export_initial_snapshot(Snapshot snapshot, DecodingWorkerShared *shared)
+export_snapshot(Snapshot snapshot, DecodingWorkerShared *shared)
 {
 	char		fname[MAXPGPATH];
 	BufFile    *file;
@@ -318,7 +337,9 @@ export_initial_snapshot(Snapshot snapshot, DecodingWorkerShared *shared)
 	snap_space = (char *) palloc(snap_size);
 	SerializeSnapshot(snapshot, snap_space);
 
-	DecodingWorkerFileName(fname, shared->relid, shared->last_exported + 1);
+	DecodingWorkerFileName(fname, shared->relid,
+						   shared->last_exported_snapshot + 1,
+						   true);
 	file = BufFileCreateFileSet(&shared->sfs.fs, fname);
 	/* To make restoration easier, write the snapshot size first. */
 	BufFileWrite(file, &snap_size, sizeof(snap_size));
@@ -328,7 +349,8 @@ export_initial_snapshot(Snapshot snapshot, DecodingWorkerShared *shared)
 
 	/* Increase the counter to tell the backend that the file is available. */
 	SpinLockAcquire(&shared->mutex);
-	shared->last_exported++;
+	shared->last_exported_snapshot++;
+	shared->snapshot_requested = false;
 	SpinLockRelease(&shared->mutex);
 	ConditionVariableSignal(&shared->cv);
 }
@@ -343,6 +365,7 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
 						  DecodingWorkerShared *shared)
 {
 	RepackDecodingState *dstate;
+	bool		snapshot_requested;
 	XLogRecPtr	lsn_upto;
 	bool		done;
 	char		fname[MAXPGPATH];
@@ -350,11 +373,14 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
 	dstate = (RepackDecodingState *) ctx->output_writer_private;
 
 	/* Open the output file. */
-	DecodingWorkerFileName(fname, shared->relid, shared->last_exported + 1);
+	DecodingWorkerFileName(fname, shared->relid,
+						   shared->last_exported_changes + 1,
+						   false);
 	dstate->file = BufFileCreateFileSet(&shared->sfs.fs, fname);
 
 	SpinLockAcquire(&shared->mutex);
 	lsn_upto = shared->lsn_upto;
+	snapshot_requested = shared->snapshot_requested;
 	done = shared->done;
 	SpinLockRelease(&shared->mutex);
 
@@ -437,6 +463,7 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
 		{
 			SpinLockAcquire(&shared->mutex);
 			lsn_upto = shared->lsn_upto;
+			snapshot_requested = shared->snapshot_requested;
 			/* 'done' should be set at the same time as 'lsn_upto' */
 			done = shared->done;
 			SpinLockRelease(&shared->mutex);
@@ -483,9 +510,59 @@ decode_concurrent_changes(LogicalDecodingContext *ctx,
 	 */
 	BufFileClose(dstate->file);
 	dstate->file = NULL;
+
+	/*
+	 * Before publishing the data changes, export the snapshot too if
+	 * requested. Publishing both at once makes sense because both are needed
+	 * at the same time, and it's simpler.
+	 */
+	if (snapshot_requested)
+	{
+		Snapshot	snapshot;
+		MemoryContext oldcxt;
+
+		/* See comments about memory context in RepackWorkerMain(). */
+		MemoryContextReset(dstate->snapshot_cxt);
+		oldcxt = MemoryContextSwitchTo(dstate->snapshot_cxt);
+
+		/*
+		 * SnapBuildInitialSnapshot() assumes invalid XID, so set it. We do
+		 * not use the snapshot, so it's ok.
+		 */
+		MyProc->xmin = InvalidTransactionId;
+		snapshot = SnapBuildInitialSnapshot(ctx->snapshot_builder);
+		MemoryContextSwitchTo(oldcxt);
+		export_snapshot(snapshot, shared);
+
+		/*
+		 * Adjust the replication slot's xmin so that VACUUM can do more work.
+		 */
+		LogicalIncreaseXminForSlot(InvalidXLogRecPtr, snapshot->xmin, false);
+	}
+	else
+	{
+		/*
+		 * If data changes were requested but no following snapshot, we don't
+		 * care about xmin horizon because the heap copying should be done by
+		 * now.
+		 */
+		LogicalIncreaseXminForSlot(InvalidXLogRecPtr, InvalidTransactionId,
+								   false);
+
+	}
+
+	/*
+	 * Make sure the xmin of our slot is taken into account when computing new
+	 * VACUUM horizons.
+	 */
+	ReplicationSlotsComputeRequiredXmin(false);
+
+	/*
+	 * Now increase the counter(s) to announce that the output is available.
+	 */
 	SpinLockAcquire(&shared->mutex);
+	shared->last_exported_changes++;
 	shared->lsn_upto = InvalidXLogRecPtr;
-	shared->last_exported++;
 	SpinLockRelease(&shared->mutex);
 	ConditionVariableSignal(&shared->cv);
 
diff --git a/src/backend/replication/logical/decode.c b/src/backend/replication/logical/decode.c
index c944be4ac83..c3722b5c623 100644
--- a/src/backend/replication/logical/decode.c
+++ b/src/backend/replication/logical/decode.c
@@ -921,6 +921,7 @@ DecodeInsert(LogicalDecodingContext *ctx, XLogRecordBuffer *buf)
 	xl_heap_insert *xlrec;
 	ReorderBufferChange *change;
 	RelFileLocator target_locator;
+	BlockNumber blknum;
 
 	xlrec = (xl_heap_insert *) XLogRecGetData(r);
 
@@ -932,7 +933,7 @@ DecodeInsert(LogicalDecodingContext *ctx, XLogRecordBuffer *buf)
 		return;
 
 	/* only interested in our database */
-	XLogRecGetBlockTag(r, 0, &target_locator, NULL, NULL);
+	XLogRecGetBlockTag(r, 0, &target_locator, NULL, &blknum);
 	if (target_locator.dbOid != ctx->slot->data.database)
 		return;
 
@@ -947,7 +948,8 @@ DecodeInsert(LogicalDecodingContext *ctx, XLogRecordBuffer *buf)
 		change->action = REORDER_BUFFER_CHANGE_INTERNAL_SPEC_INSERT;
 	change->origin_id = XLogRecGetOrigin(r);
 
-	memcpy(&change->data.tp.rlocator, &target_locator, sizeof(RelFileLocator));
+	memcpy(&change->data.tp.rlocator, &target_locator,
+		   sizeof(RelFileLocator));
 
 	tupledata = XLogRecGetBlockData(r, 0, &datalen);
 	tuplelen = datalen - SizeOfHeapHeader;
@@ -957,6 +959,20 @@ DecodeInsert(LogicalDecodingContext *ctx, XLogRecordBuffer *buf)
 
 	DecodeXLogTuple(tupledata, datalen, change->data.tp.newtuple);
 
+	/*
+	 * REPACK (CONCURRENTLY) needs block number to check if the corresponding
+	 * part of the table was already copied.  XXX Should we only do this if
+	 * AmRepackWorker()? It might save a few cycles, but not sure it's good to
+	 * leave the fields unset in other cases.
+	 */
+	{
+		HeapTupleHeader header;
+
+		header = change->data.tp.newtuple->t_data;
+		/* offnum is not really needed, but let's set valid pointer. */
+		ItemPointerSet(&header->t_ctid, blknum, xlrec->offnum);
+	}
+
 	change->data.tp.clear_toast_afterwards = true;
 
 	ReorderBufferQueueChange(ctx->reorder, XLogRecGetXid(r), buf->origptr,
@@ -978,11 +994,13 @@ DecodeUpdate(LogicalDecodingContext *ctx, XLogRecordBuffer *buf)
 	ReorderBufferChange *change;
 	char	   *data;
 	RelFileLocator target_locator;
+	BlockNumber new_blknum,
+				old_blknum;
 
 	xlrec = (xl_heap_update *) XLogRecGetData(r);
 
 	/* only interested in our database */
-	XLogRecGetBlockTag(r, 0, &target_locator, NULL, NULL);
+	XLogRecGetBlockTag(r, 0, &target_locator, NULL, &new_blknum);
 	if (target_locator.dbOid != ctx->slot->data.database)
 		return;
 
@@ -990,6 +1008,11 @@ DecodeUpdate(LogicalDecodingContext *ctx, XLogRecordBuffer *buf)
 	if (FilterByOrigin(ctx, XLogRecGetOrigin(r)))
 		return;
 
+	if (XLogRecHasBlockRef(r, 1))
+		XLogRecGetBlockTag(r, 1, NULL, NULL, &old_blknum);
+	else
+		old_blknum = new_blknum;
+
 	change = ReorderBufferAllocChange(ctx->reorder);
 	change->action = REORDER_BUFFER_CHANGE_UPDATE;
 	change->origin_id = XLogRecGetOrigin(r);
@@ -1008,6 +1031,20 @@ DecodeUpdate(LogicalDecodingContext *ctx, XLogRecordBuffer *buf)
 			ReorderBufferAllocTupleBuf(ctx->reorder, tuplelen);
 
 		DecodeXLogTuple(data, datalen, change->data.tp.newtuple);
+
+		/*
+		 * REPACK (CONCURRENTLY) needs block numbers to check if the
+		 * corresponding part of the table was already copied. XXX Do this
+		 * only if AmRepackWorker()?
+		 */
+		{
+			HeapTupleHeader header;
+
+			header = change->data.tp.newtuple->t_data;
+			/* offnum is not really needed, but let's set valid pointer. */
+			ItemPointerSet(&header->t_ctid, new_blknum, xlrec->new_offnum);
+			change->data.tp.old_blknum = old_blknum;
+		}
 	}
 
 	if (xlrec->flags & XLH_UPDATE_CONTAINS_OLD)
@@ -1044,6 +1081,7 @@ DecodeDelete(LogicalDecodingContext *ctx, XLogRecordBuffer *buf)
 	xl_heap_delete *xlrec;
 	ReorderBufferChange *change;
 	RelFileLocator target_locator;
+	BlockNumber blknum;
 
 	xlrec = (xl_heap_delete *) XLogRecGetData(r);
 
@@ -1057,7 +1095,7 @@ DecodeDelete(LogicalDecodingContext *ctx, XLogRecordBuffer *buf)
 		return;
 
 	/* only interested in our database */
-	XLogRecGetBlockTag(r, 0, &target_locator, NULL, NULL);
+	XLogRecGetBlockTag(r, 0, &target_locator, NULL, &blknum);
 	if (target_locator.dbOid != ctx->slot->data.database)
 		return;
 
@@ -1089,6 +1127,19 @@ DecodeDelete(LogicalDecodingContext *ctx, XLogRecordBuffer *buf)
 
 		DecodeXLogTuple((char *) xlrec + SizeOfHeapDelete,
 						datalen, change->data.tp.oldtuple);
+
+		/*
+		 * REPACK (CONCURRENTLY) needs block number to check if the
+		 * corresponding part of the table was already copied. XXX Do this
+		 * only if AmRepackWorker()?
+		 */
+		{
+			HeapTupleHeader header;
+
+			header = change->data.tp.oldtuple->t_data;
+			/* offnum is not really needed, but let's set valid pointer. */
+			ItemPointerSet(&header->t_ctid, blknum, xlrec->offnum);
+		}
 	}
 
 	change->data.tp.clear_toast_afterwards = true;
@@ -1148,8 +1199,11 @@ DecodeMultiInsert(LogicalDecodingContext *ctx, XLogRecordBuffer *buf)
 	char	   *tupledata;
 	Size		tuplelen;
 	RelFileLocator rlocator;
+	BlockNumber blknum;
+	bool		isinit;
 
 	xlrec = (xl_heap_multi_insert *) XLogRecGetData(r);
+	isinit = (XLogRecGetInfo(r) & XLOG_HEAP_INIT_PAGE) != 0;
 
 	/*
 	 * Ignore insert records without new tuples.  This happens when a
@@ -1159,7 +1213,7 @@ DecodeMultiInsert(LogicalDecodingContext *ctx, XLogRecordBuffer *buf)
 		return;
 
 	/* only interested in our database */
-	XLogRecGetBlockTag(r, 0, &rlocator, NULL, NULL);
+	XLogRecGetBlockTag(r, 0, &rlocator, NULL, &blknum);
 	if (rlocator.dbOid != ctx->slot->data.database)
 		return;
 
@@ -1227,6 +1281,25 @@ DecodeMultiInsert(LogicalDecodingContext *ctx, XLogRecordBuffer *buf)
 		else
 			change->data.tp.clear_toast_afterwards = false;
 
+		/*
+		 * REPACK (CONCURRENTLY) needs block number to check if the
+		 * corresponding part of the table was already copied.
+		 */
+		if (AmRepackWorker())
+		{
+			OffsetNumber offnum;
+
+			/*
+			 * offnum is not really needed, but let's set valid pointer. (It
+			 * will be invalid anyway if the page was initially empty.)
+			 */
+			if (isinit)
+				offnum = FirstOffsetNumber + i;
+			else
+				offnum = xlrec->offsets[i];
+			ItemPointerSet(&header->t_ctid, blknum, offnum);
+		}
+
 		ReorderBufferQueueChange(ctx->reorder, XLogRecGetXid(r),
 								 buf->origptr, change, false);
 
diff --git a/src/backend/replication/logical/logical.c b/src/backend/replication/logical/logical.c
index 3541fc793e4..fdcaa5036b4 100644
--- a/src/backend/replication/logical/logical.c
+++ b/src/backend/replication/logical/logical.c
@@ -1659,14 +1659,17 @@ update_progress_txn_cb_wrapper(ReorderBuffer *cache, ReorderBufferTXN *txn,
 
 /*
  * Set the required catalog xmin horizon for historic snapshots in the current
- * replication slot.
+ * replication slot if catalog is TRUE, or xmin if catalog is FALSE.
  *
  * Note that in the most cases, we won't be able to immediately use the xmin
  * to increase the xmin horizon: we need to wait till the client has confirmed
- * receiving current_lsn with LogicalConfirmReceivedLocation().
+ * receiving current_lsn with LogicalConfirmReceivedLocation(). However,
+ * catalog=FALSE is only allowed for temporary replication slots, so the
+ * horizon is applied immediately.
  */
 void
-LogicalIncreaseXminForSlot(XLogRecPtr current_lsn, TransactionId xmin)
+LogicalIncreaseXminForSlot(XLogRecPtr current_lsn, TransactionId xmin,
+						   bool catalog)
 {
 	bool		updated_xmin = false;
 	ReplicationSlot *slot;
@@ -1677,6 +1680,27 @@ LogicalIncreaseXminForSlot(XLogRecPtr current_lsn, TransactionId xmin)
 	Assert(slot != NULL);
 
 	SpinLockAcquire(&slot->mutex);
+	if (!catalog)
+	{
+		/*
+		 * The non-catalog horizon can only advance in temporary slots, so
+		 * update it in the shared memory immediately (w/o requiring prior
+		 * saving to disk).
+		 */
+		Assert(slot->data.persistency == RS_TEMPORARY);
+
+		/*
+		 * The horizon must not go backwards, however it's o.k. to become
+		 * invalid.
+		 */
+		Assert(!TransactionIdIsValid(slot->effective_xmin) ||
+			   !TransactionIdIsValid(xmin) ||
+			   TransactionIdFollowsOrEquals(xmin, slot->effective_xmin));
+
+		slot->effective_xmin = xmin;
+		SpinLockRelease(&slot->mutex);
+		return;
+	}
 
 	/*
 	 * don't overwrite if we already have a newer xmin. This can happen if we
diff --git a/src/backend/replication/logical/reorderbuffer.c b/src/backend/replication/logical/reorderbuffer.c
index 682d13c9f22..06f31e02b9a 100644
--- a/src/backend/replication/logical/reorderbuffer.c
+++ b/src/backend/replication/logical/reorderbuffer.c
@@ -3732,6 +3732,40 @@ ReorderBufferXidHasCatalogChanges(ReorderBuffer *rb, TransactionId xid)
 	return rbtxn_has_catalog_changes(txn);
 }
 
+/*
+ * Check if a transaction (or its subtransaction) contains a heap change.
+ */
+bool
+ReorderBufferXidHasHeapChanges(ReorderBuffer *rb, TransactionId xid)
+{
+	ReorderBufferTXN *txn;
+	dlist_iter	iter;
+
+	txn = ReorderBufferTXNByXid(rb, xid, false, NULL, InvalidXLogRecPtr,
+								false);
+	if (txn == NULL)
+		return false;
+
+	dlist_foreach(iter, &txn->changes)
+	{
+		ReorderBufferChange *change;
+
+		change = dlist_container(ReorderBufferChange, node, iter.cur);
+
+		switch (change->action)
+		{
+			case REORDER_BUFFER_CHANGE_INSERT:
+			case REORDER_BUFFER_CHANGE_UPDATE:
+			case REORDER_BUFFER_CHANGE_DELETE:
+				return true;
+			default:
+				break;
+		}
+	}
+
+	return false;
+}
+
 /*
  * ReorderBufferXidHasBaseSnapshot
  *		Have we already set the base snapshot for the given txn/subtxn?
@@ -5229,6 +5263,12 @@ ReorderBufferToastReplace(ReorderBuffer *rb, ReorderBufferTXN *txn,
 	Assert(newtup->t_len <= MaxHeapTupleSize);
 	Assert(newtup->t_data == (HeapTupleHeader) ((char *) newtup + HEAPTUPLESIZE));
 
+	/*
+	 * Preserve TID - REPACK relies on it when dealing with block ranges. XXX
+	 * Shouldn't we add a new field to ReorderBufferChange instead?
+	 */
+	tmphtup->t_data->t_ctid = newtup->t_data->t_ctid;
+
 	memcpy(newtup->t_data, tmphtup->t_data, tmphtup->t_len);
 	newtup->t_len = tmphtup->t_len;
 
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index b8992234924..254f0e1aa9d 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -128,6 +128,7 @@
 #include "access/heapam_xlog.h"
 #include "access/transam.h"
 #include "access/xact.h"
+#include "commands/repack.h"
 #include "common/file_utils.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -982,6 +983,13 @@ SnapBuildCommitTxn(SnapBuild *builder, XLogRecPtr lsn, TransactionId xid,
 		}
 	}
 
+	/*
+	 * REPACK decoding worker may need timetravel anytime. It takes
+	 * responsibility for tracking transaction commits, see below.
+	 */
+	else if (AmRepackWorker())
+		needs_timetravel = true;
+
 	for (nxact = 0; nxact < nsubxacts; nxact++)
 	{
 		TransactionId subxid = subxacts[nxact];
@@ -989,8 +997,12 @@ SnapBuildCommitTxn(SnapBuild *builder, XLogRecPtr lsn, TransactionId xid,
 		/*
 		 * Add subtransaction to base snapshot if catalog modifying, we don't
 		 * distinguish to toplevel transactions there.
+		 *
+		 * See comments on REPACK worker below.
 		 */
-		if (SnapBuildXidHasCatalogChanges(builder, subxid, xinfo))
+		if (SnapBuildXidHasCatalogChanges(builder, subxid, xinfo) ||
+			(AmRepackWorker() &&
+			 ReorderBufferXidHasHeapChanges(builder->reorder, xid)))
 		{
 			sub_needs_timetravel = true;
 			needs_snapshot = true;
@@ -1018,8 +1030,18 @@ SnapBuildCommitTxn(SnapBuild *builder, XLogRecPtr lsn, TransactionId xid,
 		}
 	}
 
-	/* if top-level modified catalog, it'll need a snapshot */
-	if (SnapBuildXidHasCatalogChanges(builder, xid, xinfo))
+	/*
+	 * If top-level modified catalog, it'll need a snapshot.
+	 *
+	 * If we're decoding changes on behalf of REPACK (CONCURRENTLY), only
+	 * changes of the relation being processed are decoded - see
+	 * heap_decode(). Thus any heap change we find here must belong to that
+	 * relation. Add the transaction so that we can keep building snapshots to
+	 * scan that relation.
+	 */
+	if (SnapBuildXidHasCatalogChanges(builder, xid, xinfo) ||
+		(AmRepackWorker() &&
+		 ReorderBufferXidHasHeapChanges(builder->reorder, xid)))
 	{
 		elog(DEBUG2, "found top level transaction %u, with catalog changes",
 			 xid);
@@ -1187,7 +1209,7 @@ SnapBuildProcessRunningXacts(SnapBuild *builder, XLogRecPtr lsn, xl_running_xact
 		xmin = running->oldestRunningXid;
 	elog(DEBUG3, "xmin: %u, xmax: %u, oldest running: %u, oldest xmin: %u",
 		 builder->xmin, builder->xmax, running->oldestRunningXid, xmin);
-	LogicalIncreaseXminForSlot(lsn, xmin);
+	LogicalIncreaseXminForSlot(lsn, xmin, true);
 
 	/*
 	 * Also tell the slot where we can restart decoding from. We don't want to
diff --git a/src/backend/replication/pgrepack/pgrepack.c b/src/backend/replication/pgrepack/pgrepack.c
index 959551f5724..4b205bba241 100644
--- a/src/backend/replication/pgrepack/pgrepack.c
+++ b/src/backend/replication/pgrepack/pgrepack.c
@@ -30,7 +30,8 @@ static void repack_commit_txn(LogicalDecodingContext *ctx,
 static void repack_process_change(LogicalDecodingContext *ctx, ReorderBufferTXN *txn,
 								  Relation relation, ReorderBufferChange *change);
 static void repack_store_change(LogicalDecodingContext *ctx, Relation relation,
-								ConcurrentChangeKind kind, HeapTuple tuple);
+								ConcurrentChangeKind kind, HeapTuple tuple,
+								BlockNumber old_blknum);
 
 void
 _PG_output_plugin_init(OutputPluginCallbacks *cb)
@@ -64,6 +65,9 @@ repack_startup(LogicalDecodingContext *ctx, OutputPluginOptions *opt,
 	dstate->change_cxt = AllocSetContextCreate(ctx->context,
 											   "REPACK - change",
 											   ALLOCSET_DEFAULT_SIZES);
+	dstate->snapshot_cxt = AllocSetContextCreate(ctx->context,
+												 "REPACK - snapshot",
+												 ALLOCSET_DEFAULT_SIZES);
 	/* repack_setup_logical_decoding fills in the rest */
 	ctx->output_writer_private = dstate;
 
@@ -133,7 +137,8 @@ repack_process_change(LogicalDecodingContext *ctx, ReorderBufferTXN *txn,
 				if (newtuple == NULL)
 					elog(ERROR, "incomplete insert info");
 
-				repack_store_change(ctx, relation, CHANGE_INSERT, newtuple);
+				repack_store_change(ctx, relation, CHANGE_INSERT, newtuple,
+									InvalidBlockNumber);
 			}
 			break;
 		case REORDER_BUFFER_CHANGE_UPDATE:
@@ -148,9 +153,11 @@ repack_process_change(LogicalDecodingContext *ctx, ReorderBufferTXN *txn,
 					elog(ERROR, "incomplete update info");
 
 				if (oldtuple != NULL)
-					repack_store_change(ctx, relation, CHANGE_UPDATE_OLD, oldtuple);
+					repack_store_change(ctx, relation, CHANGE_UPDATE_OLD, oldtuple,
+										InvalidBlockNumber);
 
-				repack_store_change(ctx, relation, CHANGE_UPDATE_NEW, newtuple);
+				repack_store_change(ctx, relation, CHANGE_UPDATE_NEW, newtuple,
+									change->data.tp.old_blknum);
 			}
 			break;
 		case REORDER_BUFFER_CHANGE_DELETE:
@@ -162,7 +169,8 @@ repack_process_change(LogicalDecodingContext *ctx, ReorderBufferTXN *txn,
 				if (oldtuple == NULL)
 					elog(ERROR, "incomplete delete info");
 
-				repack_store_change(ctx, relation, CHANGE_DELETE, oldtuple);
+				repack_store_change(ctx, relation, CHANGE_DELETE, oldtuple,
+									InvalidBlockNumber);
 			}
 			break;
 		default:
@@ -189,7 +197,8 @@ repack_process_change(LogicalDecodingContext *ctx, ReorderBufferTXN *txn,
  */
 static void
 repack_store_change(LogicalDecodingContext *ctx, Relation relation,
-					ConcurrentChangeKind kind, HeapTuple tuple)
+					ConcurrentChangeKind kind, HeapTuple tuple,
+					BlockNumber old_blknum)
 {
 	RepackDecodingState *dstate;
 	MemoryContext oldcxt;
@@ -285,6 +294,9 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 	 */
 	BufFileWrite(file, &tuple->t_len, sizeof(tuple->t_len));
 	BufFileWrite(file, tuple->t_data, tuple->t_len);
+	/* If old_blknum is specified, write it too. */
+	if (old_blknum != InvalidBlockNumber)
+		BufFileWrite(file, &old_blknum, sizeof(old_blknum));
 
 	/* Then, write the number of external attributes we found. */
 	natt_ext = list_length(attrs_ext);
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index afaa058b046..59dec018de9 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2528,6 +2528,16 @@
   boot_val => 'true',
 },
 
+# TODO Tune boot_val, 1024 is probably too low.
+{ name => 'repack_snapshot_after', type => 'int', context => 'PGC_USERSET', group => 'DEVELOPER_OPTIONS',
+  short_desc => 'Number of pages REPACK (CONCURRENTLY) can read using a single snapshot.',
+  flags => 'GUC_UNIT_BLOCKS | GUC_NOT_IN_SAMPLE',
+  variable => 'repack_pages_per_snapshot',
+  boot_val => '1024',
+  min => '1',
+  max => 'INT_MAX',
+}
+
 { name => 'reserved_connections', type => 'int', context => 'PGC_POSTMASTER', group => 'CONN_AUTH_SETTINGS',
   short_desc => 'Sets the number of connection slots reserved for roles with privileges of pg_use_reserved_connections.',
   variable => 'ReservedConnections',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 290ccbc543e..8a48172ea57 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -44,6 +44,7 @@
 #include "commands/async.h"
 #include "commands/extension.h"
 #include "commands/event_trigger.h"
+#include "commands/repack.h"
 #include "commands/tablespace.h"
 #include "commands/trigger.h"
 #include "commands/user.h"
diff --git a/src/include/access/tableam.h b/src/include/access/tableam.h
index f2c36696bca..132248c5d43 100644
--- a/src/include/access/tableam.h
+++ b/src/include/access/tableam.h
@@ -666,12 +666,12 @@ typedef struct TableAmRoutine
 											  Relation OldIndex,
 											  bool use_sort,
 											  TransactionId OldestXmin,
-											  Snapshot snapshot,
 											  TransactionId *xid_cutoff,
 											  MultiXactId *multi_cutoff,
 											  double *num_tuples,
 											  double *tups_vacuumed,
-											  double *tups_recently_dead);
+											  double *tups_recently_dead,
+											  void *tableam_data);
 
 	/*
 	 * React to VACUUM command on the relation. The VACUUM can be triggered by
@@ -1733,8 +1733,6 @@ table_relation_copy_data(Relation rel, const RelFileLocator *newrlocator)
  *   not needed for the relation's AM
  * - *xid_cutoff - ditto
  * - *multi_cutoff - ditto
- * - snapshot - if != NULL, ignore data changes done by transactions that this
- *	 (MVCC) snapshot considers still in-progress or in the future.
  *
  * Output parameters:
  * - *xid_cutoff - rel's new relfrozenxid value, may be invalid
@@ -1747,19 +1745,19 @@ table_relation_copy_for_cluster(Relation OldTable, Relation NewTable,
 								Relation OldIndex,
 								bool use_sort,
 								TransactionId OldestXmin,
-								Snapshot snapshot,
 								TransactionId *xid_cutoff,
 								MultiXactId *multi_cutoff,
 								double *num_tuples,
 								double *tups_vacuumed,
-								double *tups_recently_dead)
+								double *tups_recently_dead,
+								void *tableam_data)
 {
 	OldTable->rd_tableam->relation_copy_for_cluster(OldTable, NewTable, OldIndex,
 													use_sort, OldestXmin,
-													snapshot,
 													xid_cutoff, multi_cutoff,
 													num_tuples, tups_vacuumed,
-													tups_recently_dead);
+													tups_recently_dead,
+													tableam_data);
 }
 
 /*
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 07f887e99f6..8af73f8c81f 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -17,11 +17,14 @@
 
 #include "access/hio.h"
 #include "access/skey.h"
+#include "access/xlogdefs.h"
 #include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
+#include "storage/block.h"
 #include "storage/lockdefs.h"
 #include "utils/relcache.h"
+#include "utils/snapshot.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -66,13 +69,14 @@ typedef struct RepackDest
 
 	/* The latest column we need to deform to have the tuple identity */
 	AttrNumber	last_key_attno;
-} RepackDest;
 
-/*
- * The first file exported by the decoding worker must contain a snapshot, the
- * following ones contain the data changes.
- */
-#define WORKER_FILE_SNAPSHOT	0
+	/*
+	 * Range of blocks in the old table the contents of this table comes from.
+	 * Note that range_end is the first block of the next range.
+	 */
+	BlockNumber range_start;
+	BlockNumber range_end;
+} RepackDest;
 
 /*
  * Information needed to apply concurrent data changes.
@@ -84,10 +88,42 @@ typedef struct ChangeContext
 	/* The destination table. */
 	RepackDest	cc_dest;
 
-	/* Sequential number of the file containing the changes. */
-	int			cc_file_seq;
+	/* Sequential number of the file containing snapshot. */
+	int			cc_file_seq_snapshot;
+	/* Sequential number of the file containing data changes. */
+	int			cc_file_seq_changes;
+
+	/*
+	 * Auxiliary table to store ordered tuples temporarily.
+	 *
+	 * When the new relation needs to be clustered, we use this table instead
+	 * of tuplesort. The problem with a tuplesort is that data changes need to
+	 * be applied at range boundary (see heapam_relation_copy_for_cluster()
+	 * for more information), however it's not possible to look-up and change
+	 * tuples in tuplestore.
+	 *
+	 * Once the contents of the REPACKed table has been copied into the
+	 * auxiliary table, we build the clustering index (unless it's the same as
+	 * the identity index) and scan it to get the tuple in the desired order.
+	 * XXX Is it worth putting the contents into a tuplestore and sorting it?
+	 * Not sure, it'd require disk space for one more copy and the copying
+	 * itself is not free.
+	 *
+	 * TODO 1) make the tables unlogged, 2) if REPACK locks the TOAST relation
+	 * too (not sure it does) try to preserve TOAST pointers, instead of
+	 * storing them to TOAST relations of these tables, 3) Check that the
+	 * tables are dropped on transaction abort.
+	 */
+	RepackDest *cc_dest_aux;
+
+	/*
+	 * The index that defines ordering of the old table.
+	 */
+	Oid			cc_clustering_index;
 } ChangeContext;
 
+extern PGDLLIMPORT int repack_pages_per_snapshot;
+
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
@@ -98,9 +134,8 @@ extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
 
 extern Oid	make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
 						  char relpersistence, LOCKMODE lockmode);
-extern void heap_insert_for_repack(Relation rel, TupleTableSlot *src,
-								   TupleTableSlot *reform,
-								   BulkInsertStateData *bistate);
+extern void heap_insert_for_repack(ChangeContext *chgcxt, TupleTableSlot *src,
+								   TupleTableSlot *reform);
 extern bool tuple_needs_reform(HeapTuple tuple, TupleDesc tupDesc);
 extern void clear_dropped_attributes(HeapTuple tuple, TupleTableSlot *reform);
 extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
@@ -112,7 +147,12 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 TransactionId frozenXid,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
-
+extern Snapshot repack_get_snapshot(ChangeContext *chgcxt);
+extern void repack_process_concurrent_changes(ChangeContext *chgcxt,
+											  XLogRecPtr end_of_wal,
+											  BlockNumber range_start,
+											  BlockNumber range_end,
+											  bool request_snapshot, bool done);
 extern void HandleRepackMessageInterrupt(void);
 extern void ProcessRepackMessages(void);
 
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
index 42111aa4ae3..8003e999864 100644
--- a/src/include/commands/repack_internal.h
+++ b/src/include/commands/repack_internal.h
@@ -44,6 +44,8 @@ typedef struct RepackDecodingState
 
 	/* Per-change memory context. */
 	MemoryContext change_cxt;
+	/* Per-snapshot memory context. */
+	MemoryContext snapshot_cxt;
 
 	/* A tuple slot used to pass tuples back and forth */
 	TupleTableSlot *slot;
@@ -67,6 +69,9 @@ typedef struct DecodingWorkerShared
 	/* Is the decoding initialized? */
 	bool		initialized;
 
+	/* Set to request a snapshot. */
+	bool		snapshot_requested;
+
 	/*
 	 * Once the worker has reached this LSN, it should close the current
 	 * output file and either create a new one or exit, according to the field
@@ -74,6 +79,8 @@ typedef struct DecodingWorkerShared
 	 * the WAL available and keep checking this field. It is ok if the worker
 	 * had already decoded records whose LSN is >= lsn_upto before this field
 	 * has been set.
+	 *
+	 * Set a valid LSN to request data changes.
 	 */
 	XLogRecPtr	lsn_upto;
 
@@ -84,7 +91,8 @@ typedef struct DecodingWorkerShared
 	SharedFileSet sfs;
 
 	/* Number of the last file exported by the worker. */
-	int			last_exported;
+	int			last_exported_snapshot;
+	int			last_exported_changes;
 
 	/* Synchronize access to the fields above. */
 	slock_t		mutex;
@@ -116,7 +124,8 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-extern void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+extern void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq,
+								   bool snapshot);
 
 
 #endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/logical.h b/src/include/replication/logical.h
index 6e0b7628001..37315a424e0 100644
--- a/src/include/replication/logical.h
+++ b/src/include/replication/logical.h
@@ -138,7 +138,7 @@ extern bool DecodingContextReady(LogicalDecodingContext *ctx);
 extern void FreeDecodingContext(LogicalDecodingContext *ctx);
 
 extern void LogicalIncreaseXminForSlot(XLogRecPtr current_lsn,
-									   TransactionId xmin);
+									   TransactionId xmin, bool catalog);
 extern void LogicalIncreaseRestartDecodingForSlot(XLogRecPtr current_lsn,
 												  XLogRecPtr restart_lsn);
 extern void LogicalConfirmReceivedLocation(XLogRecPtr lsn);
diff --git a/src/include/replication/reorderbuffer.h b/src/include/replication/reorderbuffer.h
index ff825e4b7b2..cdefc4808df 100644
--- a/src/include/replication/reorderbuffer.h
+++ b/src/include/replication/reorderbuffer.h
@@ -104,6 +104,12 @@ typedef struct ReorderBufferChange
 			HeapTuple	oldtuple;
 			/* valid for INSERT || UPDATE */
 			HeapTuple	newtuple;
+
+			/*
+			 * valid for UPDATE - this is the physical location of the old
+			 * tuple version, valid even if 'oldtuple' is NULL.
+			 */
+			BlockNumber old_blknum;
 		}			tp;
 
 		/*
@@ -763,6 +769,7 @@ extern void ReorderBufferProcessXid(ReorderBuffer *rb, TransactionId xid, XLogRe
 
 extern void ReorderBufferXidSetCatalogChanges(ReorderBuffer *rb, TransactionId xid, XLogRecPtr lsn);
 extern bool ReorderBufferXidHasCatalogChanges(ReorderBuffer *rb, TransactionId xid);
+extern bool ReorderBufferXidHasHeapChanges(ReorderBuffer *rb, TransactionId xid);
 extern bool ReorderBufferXidHasBaseSnapshot(ReorderBuffer *rb, TransactionId xid);
 
 extern bool ReorderBufferRememberPrepareInfo(ReorderBuffer *rb, TransactionId xid,
diff --git a/src/test/modules/injection_points/Makefile b/src/test/modules/injection_points/Makefile
index c01d2fb095c..9c942599c49 100644
--- a/src/test/modules/injection_points/Makefile
+++ b/src/test/modules/injection_points/Makefile
@@ -15,6 +15,7 @@ REGRESS_OPTS = --dlpath=$(top_builddir)/src/test/regress
 ISOLATION = basic \
 	    inplace \
 	    repack \
+	    repack_snapshots \
 	    repack_temporal \
 	    repack_temporal_multirange \
 	    repack_toast \
diff --git a/src/test/modules/injection_points/expected/repack_snapshots.out b/src/test/modules/injection_points/expected/repack_snapshots.out
new file mode 100644
index 00000000000..e247b1e1946
--- /dev/null
+++ b/src/test/modules/injection_points/expected/repack_snapshots.out
@@ -0,0 +1,393 @@
+Parsed test spec with 2 sessions
+
+starting permutation: load repack change_new_beyond change_old_beyond check2 wakeup_new_range check1
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step load: 
+	SELECT load(1);
+
+load
+----
+    
+(1 row)
+
+step repack: 
+	REPACK (CONCURRENTLY) repack_test;
+ <waiting ...>
+step change_new_beyond: 
+	INSERT INTO repack_test
+	SELECT max(i) + 1, gen_external()
+	FROM repack_test
+	RETURNING tid_block(ctid);
+
+	UPDATE repack_test SET j = gen_external()
+	WHERE ctid=(SELECT min(ctid) FROM repack_test)
+	RETURNING tid_block(OLD.ctid), tid_block(NEW.ctid);
+
+tid_block
+---------
+        1
+(1 row)
+
+tid_block|tid_block
+---------+---------
+        0|        1
+(1 row)
+
+step change_old_beyond: 
+	DELETE FROM repack_test
+	WHERE ctid=(SELECT max(ctid) FROM repack_test)
+	RETURNING tid_block(ctid);
+
+	UPDATE repack_test SET j = gen_external()
+	WHERE ctid=(SELECT max(ctid) FROM repack_test)
+	RETURNING tid_block(OLD.ctid), tid_block(NEW.ctid);
+
+tid_block
+---------
+        1
+(1 row)
+
+tid_block|tid_block
+---------+---------
+        1|        1
+(1 row)
+
+step check2: 
+	INSERT INTO data_s2(i, j)
+	SELECT i, j FROM repack_test;
+
+step wakeup_new_range: 
+	SELECT injection_points_wakeup('repack-concurrently-new-range');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step repack: <... completed>
+step check1: 
+	INSERT INTO data_s1(i, j)
+	SELECT i, j FROM repack_test;
+
+	SELECT count(*) > 0 FROM repack_test;
+
+	SELECT count(*)
+	FROM data_s1 d1 FULL JOIN data_s2 d2 USING (i, j)
+	WHERE d1.i ISNULL OR d2.i ISNULL;
+
+?column?
+--------
+t       
+(1 row)
+
+count
+-----
+    0
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+
+starting permutation: load2 load2_vacuum repack change_old_beyond2 check2 wakeup_new_range wakeup_new_range check1
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step load2: 
+	SELECT load(2);
+
+	DELETE FROM repack_test WHERE i < 100;
+
+load
+----
+    
+(1 row)
+
+step load2_vacuum: 
+	VACUUM repack_test;
+
+step repack: 
+	REPACK (CONCURRENTLY) repack_test;
+ <waiting ...>
+step change_old_beyond2: 
+	UPDATE repack_test SET j = gen_external()
+	WHERE ctid=(SELECT max(ctid) FROM repack_test WHERE tid_block(ctid) = 1)
+	RETURNING tid_block(OLD.ctid), tid_block(NEW.ctid);
+
+tid_block|tid_block
+---------+---------
+        1|        0
+(1 row)
+
+step check2: 
+	INSERT INTO data_s2(i, j)
+	SELECT i, j FROM repack_test;
+
+step wakeup_new_range: 
+	SELECT injection_points_wakeup('repack-concurrently-new-range');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step wakeup_new_range: 
+	SELECT injection_points_wakeup('repack-concurrently-new-range');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step repack: <... completed>
+step check1: 
+	INSERT INTO data_s1(i, j)
+	SELECT i, j FROM repack_test;
+
+	SELECT count(*) > 0 FROM repack_test;
+
+	SELECT count(*)
+	FROM data_s1 d1 FULL JOIN data_s2 d2 USING (i, j)
+	WHERE d1.i ISNULL OR d2.i ISNULL;
+
+?column?
+--------
+t       
+(1 row)
+
+count
+-----
+    0
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+
+starting permutation: load repack_pkey change_new_beyond change_old_beyond check2 wakeup_new_range check1 check1_order_asc
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step load: 
+	SELECT load(1);
+
+load
+----
+    
+(1 row)
+
+step repack_pkey: 
+	REPACK (CONCURRENTLY) repack_test USING INDEX repack_test_pkey;
+ <waiting ...>
+step change_new_beyond: 
+	INSERT INTO repack_test
+	SELECT max(i) + 1, gen_external()
+	FROM repack_test
+	RETURNING tid_block(ctid);
+
+	UPDATE repack_test SET j = gen_external()
+	WHERE ctid=(SELECT min(ctid) FROM repack_test)
+	RETURNING tid_block(OLD.ctid), tid_block(NEW.ctid);
+
+tid_block
+---------
+        1
+(1 row)
+
+tid_block|tid_block
+---------+---------
+        0|        1
+(1 row)
+
+step change_old_beyond: 
+	DELETE FROM repack_test
+	WHERE ctid=(SELECT max(ctid) FROM repack_test)
+	RETURNING tid_block(ctid);
+
+	UPDATE repack_test SET j = gen_external()
+	WHERE ctid=(SELECT max(ctid) FROM repack_test)
+	RETURNING tid_block(OLD.ctid), tid_block(NEW.ctid);
+
+tid_block
+---------
+        1
+(1 row)
+
+tid_block|tid_block
+---------+---------
+        1|        1
+(1 row)
+
+step check2: 
+	INSERT INTO data_s2(i, j)
+	SELECT i, j FROM repack_test;
+
+step wakeup_new_range: 
+	SELECT injection_points_wakeup('repack-concurrently-new-range');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step repack_pkey: <... completed>
+step check1: 
+	INSERT INTO data_s1(i, j)
+	SELECT i, j FROM repack_test;
+
+	SELECT count(*) > 0 FROM repack_test;
+
+	SELECT count(*)
+	FROM data_s1 d1 FULL JOIN data_s2 d2 USING (i, j)
+	WHERE d1.i ISNULL OR d2.i ISNULL;
+
+?column?
+--------
+t       
+(1 row)
+
+count
+-----
+    0
+(1 row)
+
+step check1_order_asc: 
+	SELECT i FROM repack_test LIMIT 10;
+
+ i
+--
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+(10 rows)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+
+starting permutation: load repack_other_index change_new_beyond change_old_beyond check2 wakeup_new_range check1 check1_order_desc
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step load: 
+	SELECT load(1);
+
+load
+----
+    
+(1 row)
+
+step repack_other_index: 
+	REPACK (CONCURRENTLY) repack_test USING INDEX repack_test_i_idx;
+ <waiting ...>
+step change_new_beyond: 
+	INSERT INTO repack_test
+	SELECT max(i) + 1, gen_external()
+	FROM repack_test
+	RETURNING tid_block(ctid);
+
+	UPDATE repack_test SET j = gen_external()
+	WHERE ctid=(SELECT min(ctid) FROM repack_test)
+	RETURNING tid_block(OLD.ctid), tid_block(NEW.ctid);
+
+tid_block
+---------
+        1
+(1 row)
+
+tid_block|tid_block
+---------+---------
+        0|        1
+(1 row)
+
+step change_old_beyond: 
+	DELETE FROM repack_test
+	WHERE ctid=(SELECT max(ctid) FROM repack_test)
+	RETURNING tid_block(ctid);
+
+	UPDATE repack_test SET j = gen_external()
+	WHERE ctid=(SELECT max(ctid) FROM repack_test)
+	RETURNING tid_block(OLD.ctid), tid_block(NEW.ctid);
+
+tid_block
+---------
+        1
+(1 row)
+
+tid_block|tid_block
+---------+---------
+        1|        1
+(1 row)
+
+step check2: 
+	INSERT INTO data_s2(i, j)
+	SELECT i, j FROM repack_test;
+
+step wakeup_new_range: 
+	SELECT injection_points_wakeup('repack-concurrently-new-range');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step repack_other_index: <... completed>
+step check1: 
+	INSERT INTO data_s1(i, j)
+	SELECT i, j FROM repack_test;
+
+	SELECT count(*) > 0 FROM repack_test;
+
+	SELECT count(*)
+	FROM data_s1 d1 FULL JOIN data_s2 d2 USING (i, j)
+	WHERE d1.i ISNULL OR d2.i ISNULL;
+
+?column?
+--------
+t       
+(1 row)
+
+count
+-----
+    0
+(1 row)
+
+step check1_order_desc: 
+	WITH tmp(diff) as (
+		SELECT i - lag(i, 1, 10000) OVER (ORDER BY ctid)
+		FROM repack_test
+		LIMIT 10)
+	SELECT * FROM tmp WHERE diff > -1;
+
+diff
+----
+(0 rows)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/src/test/modules/injection_points/meson.build b/src/test/modules/injection_points/meson.build
index 59dba1cb023..d432a6b8f76 100644
--- a/src/test/modules/injection_points/meson.build
+++ b/src/test/modules/injection_points/meson.build
@@ -46,6 +46,7 @@ tests += {
       'basic',
       'inplace',
       'repack',
+      'repack_snapshots',
       'repack_temporal',
       'repack_temporal_multirange',
       'repack_toast',
diff --git a/src/test/modules/injection_points/specs/repack_snapshots.spec b/src/test/modules/injection_points/specs/repack_snapshots.spec
new file mode 100644
index 00000000000..0706e25a6e7
--- /dev/null
+++ b/src/test/modules/injection_points/specs/repack_snapshots.spec
@@ -0,0 +1,235 @@
+# REPACK (CONCURRENTLY) - use one snapshot per block range.
+setup
+{
+	CREATE EXTENSION injection_points;
+
+	CREATE TABLE repack_test(i int PRIMARY KEY, j text);
+	CREATE INDEX ON repack_test(i DESC);
+	CREATE TABLE relfilenodes(node oid);
+
+	CREATE TABLE data_s1(i int, j text);
+	CREATE TABLE data_s2(i int, j text);
+
+	-- Keep inserting tuples into repack_test until several tuples need to
+        -- be inserted into block number last_block.
+	CREATE FUNCTION load(last_block int)
+	RETURNS void
+	LANGUAGE 'plpgsql'
+	AS $$
+	    DECLARE
+		cnt	int;
+	    BEGIN
+		INSERT INTO repack_test VALUES (1, gen_external());
+
+		LOOP
+		    WITH max(m) AS (SELECT max(i) FROM repack_test)
+		    INSERT INTO repack_test(i, j)
+		    SELECT m + x, gen_external()
+		    FROM generate_series(1, 100) s(x), max;
+
+		    SELECT count(*)
+		    FROM repack_test WHERE tid_block(ctid) = last_block
+		    INTO cnt;
+
+		    IF cnt >= 10 THEN
+			EXIT;
+		    END IF;
+		END LOOP;
+	    END;
+	$$;
+
+	-- Generate a string of random characters that is not likely to be
+	-- compressed, but is big enough to be stored externally.
+	CREATE FUNCTION gen_external()
+	RETURNS text
+	LANGUAGE sql as $$
+		SELECT string_agg(chr(65 + trunc(25 * random())::int), '')
+		FROM generate_series(1, 2048) s(x);
+	$$;
+}
+
+teardown
+{
+	DROP TABLE repack_test;
+	DROP EXTENSION injection_points;
+
+	DROP TABLE relfilenodes;
+	DROP TABLE data_s1;
+	DROP TABLE data_s2;
+
+	DROP FUNCTION load(int);
+	DROP FUNCTION gen_external();
+}
+
+session s1
+setup
+{
+	SET repack_snapshot_after = 1;
+
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('repack-concurrently-new-range', 'wait');
+}
+# The most practical way to test the corner cases is to set range size to 1
+# block. To initialize, insert new tuples until we have several tuples in the
+# 2nd block.
+step load
+{
+	SELECT load(1);
+}
+# Start the initial load and wait when the first range has been completed.
+step repack
+{
+	REPACK (CONCURRENTLY) repack_test;
+}
+# The same, but with clustering.
+step repack_pkey
+{
+	REPACK (CONCURRENTLY) repack_test USING INDEX repack_test_pkey;
+}
+# Clustering by other than the identity index.
+step repack_other_index
+{
+	REPACK (CONCURRENTLY) repack_test USING INDEX repack_test_i_idx;
+}
+# Check the table from the perspective of s1.
+step check1
+{
+	INSERT INTO data_s1(i, j)
+	SELECT i, j FROM repack_test;
+
+	SELECT count(*) > 0 FROM repack_test;
+
+	SELECT count(*)
+	FROM data_s1 d1 FULL JOIN data_s2 d2 USING (i, j)
+	WHERE d1.i ISNULL OR d2.i ISNULL;
+}
+# Check the ordering where appropriate. We don't know the exact number of
+# rows, so just check a sample. (The replayed concurrent changes are not
+# ordered, but those shouldn't fit into the first 10 rows.)
+step check1_order_asc
+{
+	SELECT i FROM repack_test LIMIT 10;
+}
+# Due to the special way of loading the data (see the load() function above)
+# we don't know the maximum value. To make the test output deterministic,
+# check for cases where the current row is not lower than the previous row.
+step check1_order_desc
+{
+	WITH tmp(diff) as (
+		SELECT i - lag(i, 1, 10000) OVER (ORDER BY ctid)
+		FROM repack_test
+		LIMIT 10)
+	SELECT * FROM tmp WHERE diff > -1;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-new-range');
+}
+
+session s2
+# Test processing of changes such that the new tuple is beyond the current
+# range. Specifically for UPDATE, the old tuple should be in the current
+# range. So when applying it, we have to convert it to DELETE.
+step change_new_beyond
+{
+	INSERT INTO repack_test
+	SELECT max(i) + 1, gen_external()
+	FROM repack_test
+	RETURNING tid_block(ctid);
+
+	UPDATE repack_test SET j = gen_external()
+	WHERE ctid=(SELECT min(ctid) FROM repack_test)
+	RETURNING tid_block(OLD.ctid), tid_block(NEW.ctid);
+}
+# Test processing of changes such that the old tuple is beyond the current
+# range. The UPDATE puts also the new tuple beyond the current range.
+step change_old_beyond
+{
+	DELETE FROM repack_test
+	WHERE ctid=(SELECT max(ctid) FROM repack_test)
+	RETURNING tid_block(ctid);
+
+	UPDATE repack_test SET j = gen_external()
+	WHERE ctid=(SELECT max(ctid) FROM repack_test)
+	RETURNING tid_block(OLD.ctid), tid_block(NEW.ctid);
+}
+# Arrange for UPDATE to put the new tuples into block 0. In particular, fill
+# the block 1 and delete some tuples from block 1.
+step load2
+{
+	SELECT load(2);
+
+	DELETE FROM repack_test WHERE i < 100;
+}
+# Logically this belongs to the previous step, however VACUUM cannot run
+# inside a transaction block.
+step load2_vacuum
+{
+	VACUUM repack_test;
+}
+# This UPDATE should put the new tuple into block 0 (the current range). So
+# when replaying it, we have to convert it to INSERT. That includes fetching
+# the old tuple's TOAST from the TOAST table because the old tuple is not
+# available during the replay.
+step change_old_beyond2
+{
+	UPDATE repack_test SET j = gen_external()
+	WHERE ctid=(SELECT max(ctid) FROM repack_test WHERE tid_block(ctid) = 1)
+	RETURNING tid_block(OLD.ctid), tid_block(NEW.ctid);
+}
+# Check the table from the perspective of s4.
+step check2
+{
+	INSERT INTO data_s2(i, j)
+	SELECT i, j FROM repack_test;
+}
+step wakeup_new_range
+{
+	SELECT injection_points_wakeup('repack-concurrently-new-range');
+}
+
+# Test if snapshots are used correctly to scan block ranges.
+permutation
+	load
+	repack
+	change_new_beyond
+	change_old_beyond
+	check2
+	wakeup_new_range
+	check1
+
+# Special attention is needed to update tuple in block 1 so that the new tuple
+# appears in block 0. The preparation includes VACUUM, which in turn cannot
+# proceed while REPACK is in progress. That's why we need a separate
+# permutation. Note that two wake-ups are needed as we have two range
+# boundaries now.
+permutation
+	load2
+	load2_vacuum
+	repack
+	change_old_beyond2
+	check2
+	wakeup_new_range
+	wakeup_new_range
+	check1
+
+# The first permutation with identity index as the clustering index.
+permutation
+	load
+	repack_pkey
+	change_new_beyond
+	change_old_beyond
+	check2
+	wakeup_new_range
+	check1
+	check1_order_asc
+# The first permutation with another clustering index.
+permutation
+	load
+	repack_other_index
+	change_new_beyond
+	change_old_beyond
+	check2
+	wakeup_new_range
+	check1
+	check1_order_desc
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0005-Simplify-the-way-restrictions-are-imposed-on-index-f.patch



^ permalink  raw  reply  [nested|flat] 114+ messages in thread


end of thread, other threads:[~2026-06-16 11:54 UTC | newest]

Thread overview: 114+ messages (download: mbox mbox.gz follow: Atom feed)
-- links below jump to the message on this page --
2018-11-13 20:58 [PATCH v14 6/7] Restructure TupleTableSlot to allow tuples other than HeapTuple Andres Freund <[email protected]>
2026-02-07 19:04 [PATCH v2 1/4] Make stack depth check work with asan's use-after-return Andres Freund <[email protected]>
2026-02-07 19:04 [PATCH v2 1/4] Make stack depth check work with asan's use-after-return Andres Freund <[email protected]>
2026-02-07 19:04 [PATCH v2 1/4] Make stack depth check work with asan's use-after-return Andres Freund <[email protected]>
2026-02-07 19:04 [PATCH v2 1/4] Make stack depth check work with asan's use-after-return Andres Freund <[email protected]>
2026-02-07 19:04 [PATCH v2 1/4] Make stack depth check work with asan's use-after-return Andres Freund <[email protected]>
2026-02-07 19:04 [PATCH v2 1/4] Make stack depth check work with asan's use-after-return Andres Freund <[email protected]>
2026-02-07 19:04 [PATCH v2 1/4] Make stack depth check work with asan's use-after-return Andres Freund <[email protected]>
2026-02-07 19:04 [PATCH v2 1/4] Make stack depth check work with asan's use-after-return Andres Freund <[email protected]>
2026-02-07 19:04 [PATCH v2 1/4] Make stack depth check work with asan's use-after-return Andres Freund <[email protected]>
2026-02-07 19:04 [PATCH v2 1/4] Make stack depth check work with asan's use-after-return Andres Freund <[email protected]>
2026-02-07 19:04 [PATCH v2 1/4] Make stack depth check work with asan's use-after-return Andres Freund <[email protected]>
2026-02-07 19:04 [PATCH v2 1/4] Make stack depth check work with asan's use-after-return Andres Freund <[email protected]>
2026-02-07 19:04 [PATCH v2 1/4] Make stack depth check work with asan's use-after-return Andres Freund <[email protected]>
2026-02-07 19:04 [PATCH v2 1/4] Make stack depth check work with asan's use-after-return Andres Freund <[email protected]>
2026-02-07 19:04 [PATCH v2 1/4] Make stack depth check work with asan's use-after-return Andres Freund <[email protected]>
2026-02-07 19:04 [PATCH v2 1/4] Make stack depth check work with asan's use-after-return Andres Freund <[email protected]>
2026-02-07 19:04 [PATCH v2 1/4] Make stack depth check work with asan's use-after-return Andres Freund <[email protected]>
2026-02-07 19:04 [PATCH v2 1/4] Make stack depth check work with asan's use-after-return Andres Freund <[email protected]>
2026-02-07 19:04 [PATCH v2 1/4] Make stack depth check work with asan's use-after-return Andres Freund <[email protected]>
2026-02-07 19:04 [PATCH v2 1/4] Make stack depth check work with asan's use-after-return Andres Freund <[email protected]>
2026-02-07 19:04 [PATCH v2 1/4] Make stack depth check work with asan's use-after-return Andres Freund <[email protected]>
2026-02-07 19:04 [PATCH v2 1/4] Make stack depth check work with asan's use-after-return Andres Freund <[email protected]>
2026-02-07 19:04 [PATCH v2 1/4] Make stack depth check work with asan's use-after-return Andres Freund <[email protected]>
2026-02-07 19:04 [PATCH v2 1/4] Make stack depth check work with asan's use-after-return Andres Freund <[email protected]>
2026-02-07 19:04 [PATCH v2 1/4] Make stack depth check work with asan's use-after-return Andres Freund <[email protected]>
2026-02-07 19:04 [PATCH v2 1/4] Make stack depth check work with asan's use-after-return Andres Freund <[email protected]>
2026-02-07 19:04 [PATCH v2 1/4] Make stack depth check work with asan's use-after-return Andres Freund <[email protected]>
2026-02-07 19:04 [PATCH v2 1/4] Make stack depth check work with asan's use-after-return Andres Freund <[email protected]>
2026-02-07 19:04 [PATCH v2 1/4] Make stack depth check work with asan's use-after-return Andres Freund <[email protected]>
2026-02-07 19:04 [PATCH v2 1/4] Make stack depth check work with asan's use-after-return Andres Freund <[email protected]>
2026-02-07 19:04 [PATCH v2 1/4] Make stack depth check work with asan's use-after-return Andres Freund <[email protected]>
2026-02-07 19:04 [PATCH v2 1/4] Make stack depth check work with asan's use-after-return Andres Freund <[email protected]>
2026-02-07 19:04 [PATCH v2 1/4] Make stack depth check work with asan's use-after-return Andres Freund <[email protected]>
2026-02-07 19:04 [PATCH v2 1/4] Make stack depth check work with asan's use-after-return Andres Freund <[email protected]>
2026-02-07 19:04 [PATCH v2 1/4] Make stack depth check work with asan's use-after-return Andres Freund <[email protected]>
2026-02-07 19:04 [PATCH v2 1/4] Make stack depth check work with asan's use-after-return Andres Freund <[email protected]>
2026-02-07 19:04 [PATCH v2 1/4] Make stack depth check work with asan's use-after-return Andres Freund <[email protected]>
2026-02-07 19:04 [PATCH v2 1/4] Make stack depth check work with asan's use-after-return Andres Freund <[email protected]>
2026-02-07 19:04 [PATCH v2 1/4] Make stack depth check work with asan's use-after-return Andres Freund <[email protected]>
2026-02-07 19:04 [PATCH v2 1/4] Make stack depth check work with asan's use-after-return Andres Freund <[email protected]>
2026-02-07 19:04 [PATCH v2 1/4] Make stack depth check work with asan's use-after-return Andres Freund <[email protected]>
2026-02-07 19:04 [PATCH v2 1/4] Make stack depth check work with asan's use-after-return Andres Freund <[email protected]>
2026-02-07 19:04 [PATCH v2 1/4] Make stack depth check work with asan's use-after-return Andres Freund <[email protected]>
2026-02-07 19:04 [PATCH v2 1/4] Make stack depth check work with asan's use-after-return Andres Freund <[email protected]>
2026-02-07 19:04 [PATCH v2 1/4] Make stack depth check work with asan's use-after-return Andres Freund <[email protected]>
2026-02-07 19:04 [PATCH v2 1/4] Make stack depth check work with asan's use-after-return Andres Freund <[email protected]>
2026-02-07 19:04 [PATCH v2 1/4] Make stack depth check work with asan's use-after-return Andres Freund <[email protected]>
2026-02-07 19:04 [PATCH v2 1/4] Make stack depth check work with asan's use-after-return Andres Freund <[email protected]>
2026-02-07 19:04 [PATCH v2 1/4] Make stack depth check work with asan's use-after-return Andres Freund <[email protected]>
2026-02-07 19:04 [PATCH v2 1/4] Make stack depth check work with asan's use-after-return Andres Freund <[email protected]>
2026-02-07 19:04 [PATCH v2 1/4] Make stack depth check work with asan's use-after-return Andres Freund <[email protected]>
2026-02-07 19:04 [PATCH v2 1/4] Make stack depth check work with asan's use-after-return Andres Freund <[email protected]>
2026-02-07 19:04 [PATCH v2 1/4] Make stack depth check work with asan's use-after-return Andres Freund <[email protected]>
2026-02-07 19:04 [PATCH v2 1/4] Make stack depth check work with asan's use-after-return Andres Freund <[email protected]>
2026-02-07 19:04 [PATCH v2 1/4] Make stack depth check work with asan's use-after-return Andres Freund <[email protected]>
2026-02-07 19:04 [PATCH v2 1/4] Make stack depth check work with asan's use-after-return Andres Freund <[email protected]>
2026-02-07 19:04 [PATCH v2 1/4] Make stack depth check work with asan's use-after-return Andres Freund <[email protected]>
2026-02-07 19:04 [PATCH v2 1/4] Make stack depth check work with asan's use-after-return Andres Freund <[email protected]>
2026-02-07 19:04 [PATCH v2 1/4] Make stack depth check work with asan's use-after-return Andres Freund <[email protected]>
2026-02-07 19:04 [PATCH v2 1/4] Make stack depth check work with asan's use-after-return Andres Freund <[email protected]>
2026-02-07 19:04 [PATCH v2 1/4] Make stack depth check work with asan's use-after-return Andres Freund <[email protected]>
2026-02-07 19:04 [PATCH v2 1/4] Make stack depth check work with asan's use-after-return Andres Freund <[email protected]>
2026-02-07 19:04 [PATCH v2 1/4] Make stack depth check work with asan's use-after-return Andres Freund <[email protected]>
2026-02-07 19:04 [PATCH v2 1/4] Make stack depth check work with asan's use-after-return Andres Freund <[email protected]>
2026-02-07 19:04 [PATCH v2 1/4] Make stack depth check work with asan's use-after-return Andres Freund <[email protected]>
2026-02-07 19:04 [PATCH v2 1/4] Make stack depth check work with asan's use-after-return Andres Freund <[email protected]>
2026-02-07 19:04 [PATCH v2 1/4] Make stack depth check work with asan's use-after-return Andres Freund <[email protected]>
2026-02-07 19:04 [PATCH v2 1/4] Make stack depth check work with asan's use-after-return Andres Freund <[email protected]>
2026-02-07 19:04 [PATCH v2 1/4] Make stack depth check work with asan's use-after-return Andres Freund <[email protected]>
2026-02-07 19:04 [PATCH v2 1/4] Make stack depth check work with asan's use-after-return Andres Freund <[email protected]>
2026-02-07 19:04 [PATCH v2 1/4] Make stack depth check work with asan's use-after-return Andres Freund <[email protected]>
2026-02-07 19:04 [PATCH v2 1/4] Make stack depth check work with asan's use-after-return Andres Freund <[email protected]>
2026-02-07 19:04 [PATCH v2 1/4] Make stack depth check work with asan's use-after-return Andres Freund <[email protected]>
2026-02-07 19:04 [PATCH v2 1/4] Make stack depth check work with asan's use-after-return Andres Freund <[email protected]>
2026-02-07 19:04 [PATCH v2 1/4] Make stack depth check work with asan's use-after-return Andres Freund <[email protected]>
2026-02-07 19:04 [PATCH v2 1/4] Make stack depth check work with asan's use-after-return Andres Freund <[email protected]>
2026-02-07 19:04 [PATCH v2 1/4] Make stack depth check work with asan's use-after-return Andres Freund <[email protected]>
2026-02-07 19:04 [PATCH v2 1/4] Make stack depth check work with asan's use-after-return Andres Freund <[email protected]>
2026-02-07 19:04 [PATCH v2 1/4] Make stack depth check work with asan's use-after-return Andres Freund <[email protected]>
2026-02-07 19:04 [PATCH v2 1/4] Make stack depth check work with asan's use-after-return Andres Freund <[email protected]>
2026-02-07 19:04 [PATCH v2 1/4] Make stack depth check work with asan's use-after-return Andres Freund <[email protected]>
2026-02-07 19:04 [PATCH v2 1/4] Make stack depth check work with asan's use-after-return Andres Freund <[email protected]>
2026-02-07 19:04 [PATCH v2 1/4] Make stack depth check work with asan's use-after-return Andres Freund <[email protected]>
2026-02-07 19:04 [PATCH v2 1/4] Make stack depth check work with asan's use-after-return Andres Freund <[email protected]>
2026-02-07 19:04 [PATCH v2 1/4] Make stack depth check work with asan's use-after-return Andres Freund <[email protected]>
2026-02-07 19:04 [PATCH v2 1/4] Make stack depth check work with asan's use-after-return Andres Freund <[email protected]>
2026-02-07 19:04 [PATCH v2 1/4] Make stack depth check work with asan's use-after-return Andres Freund <[email protected]>
2026-02-07 19:04 [PATCH v2 1/4] Make stack depth check work with asan's use-after-return Andres Freund <[email protected]>
2026-02-07 19:04 [PATCH v2 1/4] Make stack depth check work with asan's use-after-return Andres Freund <[email protected]>
2026-02-07 19:04 [PATCH v2 1/4] Make stack depth check work with asan's use-after-return Andres Freund <[email protected]>
2026-02-07 19:04 [PATCH v2 1/4] Make stack depth check work with asan's use-after-return Andres Freund <[email protected]>
2026-02-07 19:04 [PATCH v2 1/4] Make stack depth check work with asan's use-after-return Andres Freund <[email protected]>
2026-02-07 19:04 [PATCH v2 1/4] Make stack depth check work with asan's use-after-return Andres Freund <[email protected]>
2026-02-07 19:04 [PATCH v2 1/4] Make stack depth check work with asan's use-after-return Andres Freund <[email protected]>
2026-02-07 19:04 [PATCH v2 1/4] Make stack depth check work with asan's use-after-return Andres Freund <[email protected]>
2026-02-07 19:04 [PATCH v2 1/4] Make stack depth check work with asan's use-after-return Andres Freund <[email protected]>
2026-02-07 19:04 [PATCH v2 1/4] Make stack depth check work with asan's use-after-return Andres Freund <[email protected]>
2026-02-07 19:04 [PATCH v2 1/4] Make stack depth check work with asan's use-after-return Andres Freund <[email protected]>
2026-02-07 19:04 [PATCH v2 1/4] Make stack depth check work with asan's use-after-return Andres Freund <[email protected]>
2026-02-07 19:04 [PATCH v2 1/4] Make stack depth check work with asan's use-after-return Andres Freund <[email protected]>
2026-02-07 19:04 [PATCH v2 1/4] Make stack depth check work with asan's use-after-return Andres Freund <[email protected]>
2026-02-07 19:04 [PATCH v2 1/4] Make stack depth check work with asan's use-after-return Andres Freund <[email protected]>
2026-02-07 19:04 [PATCH v2 1/4] Make stack depth check work with asan's use-after-return Andres Freund <[email protected]>
2026-02-07 19:04 [PATCH v2 1/4] Make stack depth check work with asan's use-after-return Andres Freund <[email protected]>
2026-02-07 19:04 [PATCH v2 1/4] Make stack depth check work with asan's use-after-return Andres Freund <[email protected]>
2026-02-07 19:04 [PATCH v2 1/4] Make stack depth check work with asan's use-after-return Andres Freund <[email protected]>
2026-02-07 19:04 [PATCH v2 1/4] Make stack depth check work with asan's use-after-return Andres Freund <[email protected]>
2026-02-07 19:04 [PATCH v2 1/4] Make stack depth check work with asan's use-after-return Andres Freund <[email protected]>
2026-02-07 19:04 [PATCH v2 1/4] Make stack depth check work with asan's use-after-return Andres Freund <[email protected]>
2026-02-07 19:04 [PATCH v2 1/4] Make stack depth check work with asan's use-after-return Andres Freund <[email protected]>
2026-02-07 19:04 [PATCH v2 1/4] Make stack depth check work with asan's use-after-return Andres Freund <[email protected]>
2026-02-07 19:04 [PATCH v2 1/4] Make stack depth check work with asan's use-after-return Andres Freund <[email protected]>
2026-06-16 11:54 [PATCH 4/8] Use multiple snapshots to copy the data. Antonin Houska <[email protected]>

This inbox is served by agora; see mirroring instructions
for how to clone and mirror all data and code used for this inbox