From 011f5d88edb9ef95428a625295206a8f16328928 Mon Sep 17 00:00:00 2001
From: Matthias van de Meent <boekewurm+postgres@gmail.com>
Date: Wed, 31 Jan 2024 01:27:06 +0100
Subject: [PATCH v15 2/6] btree: Introduce basic specialization

This introduces the basic infrastructure for specialization, and updates
call sites of the specialized functions to use the new infrastructure.

Note that actual meaningful code behaviour changes will happen in a later
commit, this only builds the scaffolds.

Specialized functions are written in _spec files, and their names are
defined as macros that produce either a specialized name token (when in a
specialized context), or an expression (which utilizes an in-scope
nbts_prep_ctx-prepared context) that evaluates to a function pointer which
points to the indicated function's variant which is most optimized for that
context's index key shape.
---
 contrib/amcheck/verify_nbtree.c               |  7 ++
 src/backend/access/nbtree/nbtdedup.c          |  4 +-
 src/backend/access/nbtree/nbtdedup_spec.c     |  2 +
 src/backend/access/nbtree/nbtinsert.c         |  6 +-
 src/backend/access/nbtree/nbtinsert_spec.c    |  3 +
 src/backend/access/nbtree/nbtpage.c           |  1 +
 src/backend/access/nbtree/nbtree.c            | 30 ++++++-
 src/backend/access/nbtree/nbtsearch.c         |  6 +-
 src/backend/access/nbtree/nbtsearch_spec.c    |  4 +
 src/backend/access/nbtree/nbtsort.c           |  5 +-
 src/backend/access/nbtree/nbtsort_spec.c      |  2 +
 src/backend/access/nbtree/nbtsplitloc.c       |  3 +
 src/backend/access/nbtree/nbtutils.c          |  3 +-
 src/backend/access/nbtree/nbtutils_spec.c     |  3 +
 src/backend/utils/sort/tuplesortvariants.c    |  6 +-
 .../utils/sort/tuplesortvariants_spec.c       |  3 +
 src/include/access/nbtree.h                   | 45 +++++++++-
 src/include/access/nbtree_spec.h              | 86 +++++++++++++++++++
 src/include/access/nbtree_specfuncs.h         | 16 ++++
 19 files changed, 226 insertions(+), 9 deletions(-)
 create mode 100644 src/include/access/nbtree_spec.h

diff --git a/contrib/amcheck/verify_nbtree.c b/contrib/amcheck/verify_nbtree.c
index 91caa53dd8..636ec1199e 100644
--- a/contrib/amcheck/verify_nbtree.c
+++ b/contrib/amcheck/verify_nbtree.c
@@ -1369,6 +1369,7 @@ bt_target_page_check(BtreeCheckState *state)
 	OffsetNumber offset;
 	OffsetNumber max;
 	BTPageOpaque topaque;
+	nbts_prep_ctx(state->rel);
 
 	/* last visible entry info for checking indexes with unique constraint */
 	int			lVis_i = -1;	/* the position of last visible item for
@@ -3066,6 +3067,7 @@ bt_rootdescend(BtreeCheckState *state, IndexTuple itup)
 	BTStack		stack;
 	Buffer		lbuf;
 	bool		exists;
+	nbts_prep_ctx(state->rel);
 
 	key = _bt_mkscankey(state->rel, itup);
 	Assert(key->heapkeyspace && key->scantid != NULL);
@@ -3164,6 +3166,7 @@ invariant_l_offset(BtreeCheckState *state, BTScanInsert key,
 {
 	ItemId		itemid;
 	int32		cmp;
+	nbts_prep_ctx(state->rel);
 
 	Assert(!key->nextkey && key->backward);
 
@@ -3226,6 +3229,7 @@ invariant_leq_offset(BtreeCheckState *state, BTScanInsert key,
 					 OffsetNumber upperbound)
 {
 	int32		cmp;
+	nbts_prep_ctx(state->rel);
 
 	Assert(!key->nextkey && key->backward);
 
@@ -3249,6 +3253,7 @@ invariant_g_offset(BtreeCheckState *state, BTScanInsert key,
 				   OffsetNumber lowerbound)
 {
 	int32		cmp;
+	nbts_prep_ctx(state->rel);
 
 	Assert(!key->nextkey && key->backward);
 
@@ -3287,6 +3292,7 @@ invariant_l_nontarget_offset(BtreeCheckState *state, BTScanInsert key,
 {
 	ItemId		itemid;
 	int32		cmp;
+	nbts_prep_ctx(state->rel);
 
 	Assert(!key->nextkey && key->backward);
 
@@ -3522,6 +3528,7 @@ static inline BTScanInsert
 bt_mkscankey_pivotsearch(Relation rel, IndexTuple itup)
 {
 	BTScanInsert skey;
+	nbts_prep_ctx(rel);
 
 	skey = _bt_mkscankey(rel, itup);
 	skey->backward = true;
diff --git a/src/backend/access/nbtree/nbtdedup.c b/src/backend/access/nbtree/nbtdedup.c
index c0410f1792..74e5dc0905 100644
--- a/src/backend/access/nbtree/nbtdedup.c
+++ b/src/backend/access/nbtree/nbtdedup.c
@@ -28,7 +28,8 @@ static void _bt_singleval_fillfactor(Page page, BTDedupState state,
 static bool _bt_posting_valid(IndexTuple posting);
 #endif
 
-#include "nbtdedup_spec.c"
+#define NBT_FILE "../../backend/access/nbtree/nbtdedup_spec.c"
+#include "access/nbtree_spec.h"
 
 /*
  * Perform bottom-up index deletion pass.
@@ -69,6 +70,7 @@ _bt_bottomupdel_pass(Relation rel, Buffer buf, Relation heapRel,
 	TM_IndexDeleteOp delstate;
 	bool		neverdedup;
 	int			nkeyatts = IndexRelationGetNumberOfKeyAttributes(rel);
+	nbts_prep_ctx(rel);
 
 	/* Passed-in newitemsz is MAXALIGNED but does not include line pointer */
 	newitemsz += sizeof(ItemIdData);
diff --git a/src/backend/access/nbtree/nbtdedup_spec.c b/src/backend/access/nbtree/nbtdedup_spec.c
index 056cf9908b..6db340dce6 100644
--- a/src/backend/access/nbtree/nbtdedup_spec.c
+++ b/src/backend/access/nbtree/nbtdedup_spec.c
@@ -15,6 +15,8 @@
  *-------------------------------------------------------------------------
  */
 
+#define _bt_do_singleval NBTS_FUNCTION(_bt_do_singleval)
+
 static bool _bt_do_singleval(Relation rel, Page page, BTDedupState state,
 							 OffsetNumber minoff, IndexTuple newitem);
 
diff --git a/src/backend/access/nbtree/nbtinsert.c b/src/backend/access/nbtree/nbtinsert.c
index 635ba48078..9665588443 100644
--- a/src/backend/access/nbtree/nbtinsert.c
+++ b/src/backend/access/nbtree/nbtinsert.c
@@ -67,7 +67,8 @@ static BlockNumber *_bt_deadblocks(Page page, OffsetNumber *deletable,
 								   int *nblocks);
 static inline int _bt_blk_cmp(const void *arg1, const void *arg2);
 
-#include "nbtinsert_spec.c"
+#define NBT_FILE "../../backend/access/nbtree/nbtinsert_spec.c"
+#include "access/nbtree_spec.h"
 
 /*
  *	_bt_check_unique() -- Check for violation of unique index constraint
@@ -111,6 +112,7 @@ _bt_check_unique(Relation rel, BTInsertState insertstate, Relation heapRel,
 	bool		inposting = false;
 	bool		prevalldead = true;
 	int			curposti = 0;
+	nbts_prep_ctx(rel);
 
 	/* Assume unique until we find a duplicate */
 	*is_unique = true;
@@ -943,6 +945,7 @@ _bt_split(Relation rel, Relation heaprel, BTScanInsert itup_key, Buffer buf,
 	bool		newitemonleft,
 				isleaf,
 				isrightmost;
+	nbts_prep_ctx(rel);
 
 	/*
 	 * origpage is the original page to be split.  leftpage is a temporary
@@ -2143,6 +2146,7 @@ _bt_delete_or_dedup_one_page(Relation rel, Relation heapRel,
 	BTScanInsert itup_key = insertstate->itup_key;
 	Page		page = BufferGetPage(buffer);
 	BTPageOpaque opaque = BTPageGetOpaque(page);
+	nbts_prep_ctx(rel);
 
 	Assert(P_ISLEAF(opaque));
 	Assert(simpleonly || itup_key->heapkeyspace);
diff --git a/src/backend/access/nbtree/nbtinsert_spec.c b/src/backend/access/nbtree/nbtinsert_spec.c
index b1f65ab1e6..64f62b41b8 100644
--- a/src/backend/access/nbtree/nbtinsert_spec.c
+++ b/src/backend/access/nbtree/nbtinsert_spec.c
@@ -16,6 +16,9 @@
  *-------------------------------------------------------------------------
  */
 
+#define _bt_search_insert NBTS_FUNCTION(_bt_search_insert)
+#define _bt_findinsertloc NBTS_FUNCTION(_bt_findinsertloc)
+
 static BTStack _bt_search_insert(Relation rel, Relation heaprel,
 								 BTInsertState insertstate);
 static OffsetNumber _bt_findinsertloc(Relation rel,
diff --git a/src/backend/access/nbtree/nbtpage.c b/src/backend/access/nbtree/nbtpage.c
index 567bade9f4..a2be1c2da3 100644
--- a/src/backend/access/nbtree/nbtpage.c
+++ b/src/backend/access/nbtree/nbtpage.c
@@ -1810,6 +1810,7 @@ _bt_pagedel(Relation rel, Buffer leafbuf, BTVacState *vstate)
 	bool		rightsib_empty;
 	Page		page;
 	BTPageOpaque opaque;
+	nbts_prep_ctx(rel);
 
 	/*
 	 * Save original leafbuf block number from caller.  Only deleted blocks
diff --git a/src/backend/access/nbtree/nbtree.c b/src/backend/access/nbtree/nbtree.c
index b29534a7f3..68048d9ef3 100644
--- a/src/backend/access/nbtree/nbtree.c
+++ b/src/backend/access/nbtree/nbtree.c
@@ -87,7 +87,8 @@ static BTVacuumPosting btreevacuumposting(BTVacState *vstate,
 										  OffsetNumber updatedoffset,
 										  int *nremaining);
 
-#include "nbtree_spec.c"
+#define NBT_FILE "../../backend/access/nbtree/nbtree_spec.c"
+#include "access/nbtree_spec.h"
 
 /*
  * Btree handler function: return IndexAmRoutine with access method parameters
@@ -123,7 +124,7 @@ bthandler(PG_FUNCTION_ARGS)
 
 	amroutine->ambuild = btbuild;
 	amroutine->ambuildempty = btbuildempty;
-	amroutine->aminsert = btinsert;
+	amroutine->aminsert = _btinsert_dispatch;
 	amroutine->aminsertcleanup = NULL;
 	amroutine->ambulkdelete = btbulkdelete;
 	amroutine->amvacuumcleanup = btvacuumcleanup;
@@ -158,6 +159,8 @@ btbuildempty(Relation index)
 	Buffer		metabuf;
 	Page		metapage;
 
+	nbt_opt_specialize(index);
+
 	/*
 	 * Initialize the metapage.
 	 *
@@ -183,6 +186,27 @@ btbuildempty(Relation index)
 	ReleaseBuffer(metabuf);
 }
 
+/*
+ *	_btinsert_dispatch() -- dispatcher for specialized btinsert functions.
+ *
+ *		Descend the tree recursively, find the appropriate location for our
+ *		new tuple, and put it there.
+ */
+bool
+_btinsert_dispatch(Relation rel, Datum *values, bool *isnull,
+				   ItemPointer ht_ctid, Relation heapRel,
+				   IndexUniqueCheck checkUnique,
+				   bool indexUnchanged,
+				   IndexInfo *indexInfo)
+{
+	nbts_prep_ctx(rel);
+
+	_bt_specialize(rel);
+
+	return btinsert(rel, values, isnull, ht_ctid, heapRel, checkUnique,
+					indexUnchanged, indexInfo);
+}
+
 /*
  *	btgettuple() -- Get the next tuple in the scan.
  */
@@ -323,6 +347,7 @@ btbeginscan(Relation rel, int nkeys, int norderbys)
 {
 	IndexScanDesc scan;
 	BTScanOpaque so;
+	nbt_opt_specialize(rel);
 
 	/* no order by operators allowed */
 	Assert(norderbys == 0);
@@ -767,6 +792,7 @@ btbulkdelete(IndexVacuumInfo *info, IndexBulkDeleteResult *stats,
 {
 	Relation	rel = info->index;
 	BTCycleId	cycleid;
+	nbt_opt_specialize(rel);
 
 	/* allocate stats if first time through, else re-use existing struct */
 	if (stats == NULL)
diff --git a/src/backend/access/nbtree/nbtsearch.c b/src/backend/access/nbtree/nbtsearch.c
index e46ed8f377..2adb73aa0d 100644
--- a/src/backend/access/nbtree/nbtsearch.c
+++ b/src/backend/access/nbtree/nbtsearch.c
@@ -44,7 +44,8 @@ static Buffer _bt_walk_left(Relation rel, Buffer buf);
 static bool _bt_endpoint(IndexScanDesc scan, ScanDirection dir);
 static inline void _bt_initialize_more_data(BTScanOpaque so, ScanDirection dir);
 
-#include "nbtsearch_spec.c"
+#define NBT_FILE "../../backend/access/nbtree/nbtsearch_spec.c"
+#include "access/nbtree_spec.h"
 
 /*
  *	_bt_drop_lock_and_maybe_pin()
@@ -174,6 +175,7 @@ _bt_first(IndexScanDesc scan, ScanDirection dir)
 	StrategyNumber strat_total;
 	BTScanPosItem *currItem;
 	BlockNumber blkno;
+	nbts_prep_ctx(rel);
 
 	Assert(!BTScanPosIsValid(so->currPos));
 
@@ -987,6 +989,7 @@ _bt_readnextpage(IndexScanDesc scan, BlockNumber blkno, ScanDirection dir)
 	Page		page;
 	BTPageOpaque opaque;
 	bool		status;
+	nbts_prep_ctx(rel);
 
 	if (ScanDirectionIsForward(dir))
 	{
@@ -1377,6 +1380,7 @@ _bt_endpoint(IndexScanDesc scan, ScanDirection dir)
 	BTPageOpaque opaque;
 	OffsetNumber start;
 	BTScanPosItem *currItem;
+	nbts_prep_ctx(rel);
 
 	/*
 	 * Scan down to the leftmost or rightmost leaf page.  This is a simplified
diff --git a/src/backend/access/nbtree/nbtsearch_spec.c b/src/backend/access/nbtree/nbtsearch_spec.c
index a42aa4bb11..d65765ca23 100644
--- a/src/backend/access/nbtree/nbtsearch_spec.c
+++ b/src/backend/access/nbtree/nbtsearch_spec.c
@@ -16,6 +16,10 @@
  *-------------------------------------------------------------------------
  */
 
+#define _bt_binsrch		NBTS_FUNCTION(_bt_binsrch)
+#define _bt_moveright	NBTS_FUNCTION(_bt_moveright)
+#define _bt_readpage	NBTS_FUNCTION(_bt_readpage)
+
 static OffsetNumber _bt_binsrch(Relation rel, BTScanInsert key, Buffer buf);
 static Buffer _bt_moveright(Relation rel, Relation heaprel, BTScanInsert key,
 							Buffer buf, bool forupdate, BTStack stack,
diff --git a/src/backend/access/nbtree/nbtsort.c b/src/backend/access/nbtree/nbtsort.c
index 7455d099dd..0bd0c5ffcf 100644
--- a/src/backend/access/nbtree/nbtsort.c
+++ b/src/backend/access/nbtree/nbtsort.c
@@ -291,7 +291,8 @@ static void _bt_parallel_scan_and_sort(BTSpool *btspool, BTSpool *btspool2,
 									   Sharedsort *sharedsort2, int sortmem,
 									   bool progress);
 
-#include "nbtsort_spec.c"
+#define NBT_FILE "../../backend/access/nbtree/nbtsort_spec.c"
+#include "access/nbtree_spec.h"
 
 /*
  *	btbuild() -- build a new btree index.
@@ -543,6 +544,7 @@ static void
 _bt_leafbuild(BTSpool *btspool, BTSpool *btspool2)
 {
 	BTWriteState wstate;
+	nbts_prep_ctx(btspool->index);
 
 #ifdef BTREE_BUILD_STATS
 	if (log_btree_build_stats)
@@ -845,6 +847,7 @@ _bt_buildadd(BTWriteState *wstate, BTPageState *state, IndexTuple itup,
 	Size		pgspc;
 	Size		itupsz;
 	bool		isleaf;
+	nbts_prep_ctx(wstate->index);
 
 	/*
 	 * This is a handy place to check for cancel interrupts during the btree
diff --git a/src/backend/access/nbtree/nbtsort_spec.c b/src/backend/access/nbtree/nbtsort_spec.c
index 044cb4f853..3ad997c332 100644
--- a/src/backend/access/nbtree/nbtsort_spec.c
+++ b/src/backend/access/nbtree/nbtsort_spec.c
@@ -16,6 +16,8 @@
  *-------------------------------------------------------------------------
  */
 
+#define _bt_load NBTS_FUNCTION(_bt_load)
+
 static void _bt_load(BTWriteState *wstate,
 					 BTSpool *btspool, BTSpool *btspool2);
 
diff --git a/src/backend/access/nbtree/nbtsplitloc.c b/src/backend/access/nbtree/nbtsplitloc.c
index d0b1d82578..fb8b75ee0a 100644
--- a/src/backend/access/nbtree/nbtsplitloc.c
+++ b/src/backend/access/nbtree/nbtsplitloc.c
@@ -639,6 +639,7 @@ _bt_afternewitemoff(FindSplitData *state, OffsetNumber maxoff,
 	ItemId		itemid;
 	IndexTuple	tup;
 	int			keepnatts;
+	nbts_prep_ctx(state->rel);
 
 	Assert(state->is_leaf && !state->is_rightmost);
 
@@ -945,6 +946,7 @@ _bt_strategy(FindSplitData *state, SplitPoint *leftpage,
 			   *rightinterval;
 	int			perfectpenalty;
 	int			indnkeyatts = IndexRelationGetNumberOfKeyAttributes(state->rel);
+	nbts_prep_ctx(state->rel);
 
 	/* Assume that alternative strategy won't be used for now */
 	*strategy = SPLIT_DEFAULT;
@@ -1137,6 +1139,7 @@ _bt_split_penalty(FindSplitData *state, SplitPoint *split)
 {
 	IndexTuple	lastleft;
 	IndexTuple	firstright;
+	nbts_prep_ctx(state->rel);
 
 	if (!state->is_leaf)
 	{
diff --git a/src/backend/access/nbtree/nbtutils.c b/src/backend/access/nbtree/nbtutils.c
index befd34b5df..22b9f04b0e 100644
--- a/src/backend/access/nbtree/nbtutils.c
+++ b/src/backend/access/nbtree/nbtutils.c
@@ -51,7 +51,8 @@ static bool _bt_compare_scankey_args(IndexScanDesc scan, ScanKey op,
 static bool _bt_fix_scankey_strategy(ScanKey skey, int16 *indoption);
 static void _bt_mark_scankey_required(ScanKey skey);
 
-#include "nbtutils_spec.c"
+#define NBT_FILE "../../backend/access/nbtree/nbtutils_spec.c"
+#include "access/nbtree_spec.h"
 
 /*
  * free a retracement stack made by _bt_search.
diff --git a/src/backend/access/nbtree/nbtutils_spec.c b/src/backend/access/nbtree/nbtutils_spec.c
index 29a9ce3664..38e27f1757 100644
--- a/src/backend/access/nbtree/nbtutils_spec.c
+++ b/src/backend/access/nbtree/nbtutils_spec.c
@@ -16,6 +16,9 @@
  *-------------------------------------------------------------------------
  */
 
+#define _bt_check_rowcompare	NBTS_FUNCTION(_bt_check_rowcompare)
+#define _bt_keep_natts			NBTS_FUNCTION(_bt_keep_natts)
+
 static bool _bt_check_rowcompare(ScanKey skey,
 								 IndexTuple tuple, int tupnatts, TupleDesc tupdesc,
 								 ScanDirection dir, bool *continuescan);
diff --git a/src/backend/utils/sort/tuplesortvariants.c b/src/backend/utils/sort/tuplesortvariants.c
index 6da7d7fd58..dfd3c31d47 100644
--- a/src/backend/utils/sort/tuplesortvariants.c
+++ b/src/backend/utils/sort/tuplesortvariants.c
@@ -159,7 +159,8 @@ typedef struct BrinSortTuple
 /* Size of the BrinSortTuple, given length of the BrinTuple. */
 #define BRINSORTTUPLE_SIZE(len)		(offsetof(BrinSortTuple, tuple) + (len))
 
-#include "tuplesortvariants_spec.c"
+#define NBT_FILE "../../backend/utils/sort/tuplesortvariants_spec.c"
+#include "access/nbtree_spec.h"
 
 Tuplesortstate *
 tuplesort_begin_heap(TupleDesc tupDesc,
@@ -249,6 +250,7 @@ tuplesort_begin_cluster(TupleDesc tupDesc,
 	MemoryContext oldcontext;
 	TuplesortClusterArg *arg;
 	int			i;
+	nbts_prep_ctx(indexRel);
 
 	Assert(indexRel->rd_rel->relam == BTREE_AM_OID);
 
@@ -361,6 +363,7 @@ tuplesort_begin_index_btree(Relation heapRel,
 	TuplesortIndexBTreeArg *arg;
 	MemoryContext oldcontext;
 	int			i;
+	nbts_prep_ctx(indexRel);
 
 	oldcontext = MemoryContextSwitchTo(base->maincontext);
 	arg = (TuplesortIndexBTreeArg *) palloc(sizeof(TuplesortIndexBTreeArg));
@@ -496,6 +499,7 @@ tuplesort_begin_index_gist(Relation heapRel,
 	MemoryContext oldcontext;
 	TuplesortIndexBTreeArg *arg;
 	int			i;
+	nbts_prep_ctx(indexRel);
 
 	oldcontext = MemoryContextSwitchTo(base->maincontext);
 	arg = (TuplesortIndexBTreeArg *) palloc(sizeof(TuplesortIndexBTreeArg));
diff --git a/src/backend/utils/sort/tuplesortvariants_spec.c b/src/backend/utils/sort/tuplesortvariants_spec.c
index 76cabb207e..705da09329 100644
--- a/src/backend/utils/sort/tuplesortvariants_spec.c
+++ b/src/backend/utils/sort/tuplesortvariants_spec.c
@@ -16,6 +16,9 @@
  *-------------------------------------------------------------------------
  */
 
+#define comparetup_index_btree NBTS_FUNCTION(comparetup_index_btree)
+#define comparetup_index_btree_tiebreak NBTS_FUNCTION(comparetup_index_btree_tiebreak)
+
 static int	comparetup_index_btree(const SortTuple *a, const SortTuple *b,
 								   Tuplesortstate *state);
 static int	comparetup_index_btree_tiebreak(const SortTuple *a, const SortTuple *b,
diff --git a/src/include/access/nbtree.h b/src/include/access/nbtree.h
index 63e245dcbc..e1e80a8830 100644
--- a/src/include/access/nbtree.h
+++ b/src/include/access/nbtree.h
@@ -1118,7 +1118,50 @@ typedef struct BTOptions
 #define PROGRESS_BTREE_PHASE_PERFORMSORT_2				4
 #define PROGRESS_BTREE_PHASE_LEAF_LOAD					5
 
-#include "access/nbtree_specfuncs.h"
+#define NBTS_TYPE_CACHED CACHED
+
+/* Generate the specialized function headers */
+#define NBT_FILE "access/nbtree_specfuncs.h"
+#include "access/nbtree_spec.h"
+
+/*
+ * Further specialization helpers and definitions
+ */
+
+typedef enum NBTS_CTX {
+	NBTS_CTX_CACHED = 1, /* Equivalent to unspecialized code */
+} NBTS_CTX;
+
+static inline NBTS_CTX _nbt_spec_context(Relation irel)
+{
+	return NBTS_CTX_CACHED;
+}
+
+#define NBTS_CTX_NAME __nbts_ctx
+
+/* contextual specialization macros */
+#define NBTS_MAKE_CTX(rel) const NBTS_CTX NBTS_CTX_NAME PG_USED_FOR_ASSERTS_ONLY = _nbt_spec_context(rel)
+#define NBTS_SPECIALIZE_NAME(name) ( \
+	AssertMacro((NBTS_CTX_NAME) == NBTS_CTX_CACHED), \
+	NBTS_MAKE_NAME(name, NBTS_TYPE_CACHED) \
+)
+
+extern bool _btinsert_dispatch(Relation rel, Datum *values, bool *isnull,
+							   ItemPointer ht_ctid, Relation heapRel,
+							   IndexUniqueCheck checkUnique,
+							   bool indexUnchanged,
+							   struct IndexInfo *indexInfo);
+
+static inline
+void nbt_opt_specialize(Relation rel)
+{
+	Assert(PointerIsValid(rel));
+	if (unlikely((rel)->rd_indam->aminsert == _btinsert_dispatch))
+	{
+		nbts_prep_ctx(rel);
+		_bt_specialize(rel);
+	}
+}
 
 /*
  * external entry points for btree, in nbtree.c
diff --git a/src/include/access/nbtree_spec.h b/src/include/access/nbtree_spec.h
new file mode 100644
index 0000000000..731f2fb946
--- /dev/null
+++ b/src/include/access/nbtree_spec.h
@@ -0,0 +1,86 @@
+/*-------------------------------------------------------------------------
+ *
+ * nbtree_specialize.h
+ *	  header file for postgres btree access method implementation.
+ *
+ *
+ * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/access/nbtree_specialize.h
+ *
+ *-------------------------------------------------------------------------
+ *
+ * Specialize key-accessing functions and the hot code around those. The file
+ * that's marked for specialization is either
+ * - NBTS_HEADER, a quoted header inclusion path; or
+ * - NBTS_FILE, the include path of the to-be-specialized code relative to
+ *		the access/nbtree_spec.h file.
+ *
+ * Specialized files define their functions like usual, with an additional
+ * requirement that they #define their function name at the top, with
+ * #define funcname NBTS_FUNCTION(funcname).
+ */
+
+#if defined(NBT_FILE)
+#define NBT_SPECIALIZE_FILE NBT_FILE
+#else
+#error "No specializable file defined"
+#endif
+
+/* how do we make names? */
+#define NBTS_MAKE_PREFIX(a) CppConcat(a,_)
+#define NBTS_MAKE_NAME_(a,b) CppConcat(a,b)
+#define NBTS_MAKE_NAME(a,b) NBTS_MAKE_NAME_(NBTS_MAKE_PREFIX(a),b)
+
+
+/*
+ * Protections against multiple inclusions - the definition of this macro is
+ * different for files included by this file as template, versus those that
+ * include this file for the definitions, so we redefine these macros at top and
+ * bottom.
+ */
+#ifdef NBTS_FUNCTION
+#undef NBTS_FUNCTION
+#endif
+#define NBTS_FUNCTION(name) NBTS_MAKE_NAME(name, NBTS_TYPE)
+
+/* In a specialized file, specialization contexts are meaningless */
+#ifdef nbts_prep_ctx
+#undef nbts_prep_ctx
+#endif
+
+/*
+ * Specialization 1: CACHED
+ *
+ * Multiple key columns, optimized access for attcacheoff -cacheable offsets.
+ */
+#define NBTS_TYPE NBTS_TYPE_CACHED
+
+#include NBT_SPECIALIZE_FILE
+
+#undef NBTS_TYPE
+
+/*
+ * We're done with templating, so restore or create the macros which can be
+ * used in non-template sources.
+ */
+#define nbts_prep_ctx(rel) NBTS_MAKE_CTX(rel)
+
+/*
+ * from here on all NBTS_FUNCTIONs are from specialized function names that
+ * are being called. Change the result of those macros from a direct call
+ * call to a conditional call to the right place, depending on the correct
+ * context.
+ */
+#undef NBTS_FUNCTION
+#define NBTS_FUNCTION(name) NBTS_SPECIALIZE_NAME(name)
+
+/* cleanup unwanted definitions from file inclusion */
+#undef NBT_SPECIALIZE_FILE
+#ifdef NBT_HEADERS
+#undef NBT_HEADERS
+#endif
+#ifdef NBT_FILE
+#undef NBT_FILE
+#endif
diff --git a/src/include/access/nbtree_specfuncs.h b/src/include/access/nbtree_specfuncs.h
index d2ca62cb3b..00b718324e 100644
--- a/src/include/access/nbtree_specfuncs.h
+++ b/src/include/access/nbtree_specfuncs.h
@@ -1,3 +1,19 @@
+/*
+ * prototypes for functions that are included in nbtree.h
+ */
+
+#define _bt_specialize		NBTS_FUNCTION(_bt_specialize)
+#define btinsert			NBTS_FUNCTION(btinsert)
+#define _bt_dedup_pass		NBTS_FUNCTION(_bt_dedup_pass)
+#define _bt_doinsert		NBTS_FUNCTION(_bt_doinsert)
+#define _bt_search			NBTS_FUNCTION(_bt_search)
+#define _bt_binsrch_insert	NBTS_FUNCTION(_bt_binsrch_insert)
+#define _bt_compare			NBTS_FUNCTION(_bt_compare)
+#define _bt_mkscankey		NBTS_FUNCTION(_bt_mkscankey)
+#define _bt_checkkeys		NBTS_FUNCTION(_bt_checkkeys)
+#define _bt_truncate		NBTS_FUNCTION(_bt_truncate)
+#define _bt_keep_natts_fast	NBTS_FUNCTION(_bt_keep_natts_fast)
+
 /*
  * prototypes for functions in nbtree_spec.h
  */
-- 
2.42.1

