Separate TBM[Shared|Private]Iterator and TBMIterateResult

Remove the TBMIterateResult member from the TBMPrivateIterator and
TBMSharedIterator and make tbm_[shared|private_]iterate() take a
TBMIterateResult as a parameter.

This allows tidbitmap API users to manage multiple TBMIterateResults per
scan. This is required for bitmap heap scan to use the read stream API,
with which there may be multiple I/Os in flight at once, each one with a
TBMIterateResult.

Reviewed-by: Tomas Vondra <tomas@vondra.me>
Discussion: https://postgr.es/m/d4bb26c9-fe07-439e-ac53-c0e244387e01%40vondra.me
This commit is contained in:
Melanie Plageman 2025-03-15 10:10:51 -04:00
parent 799959dc7c
commit 944e81bf99
7 changed files with 110 additions and 101 deletions

View File

@ -332,8 +332,8 @@ restartScanEntry:
entry->list = NULL; entry->list = NULL;
entry->nlist = 0; entry->nlist = 0;
entry->matchBitmap = NULL; entry->matchBitmap = NULL;
entry->matchResult = NULL;
entry->matchNtuples = -1; entry->matchNtuples = -1;
entry->matchResult.blockno = InvalidBlockNumber;
entry->reduceResult = false; entry->reduceResult = false;
entry->predictNumberResult = 0; entry->predictNumberResult = 0;
@ -827,20 +827,19 @@ entryGetItem(GinState *ginstate, GinScanEntry entry,
{ {
/* /*
* If we've exhausted all items on this block, move to next block * If we've exhausted all items on this block, move to next block
* in the bitmap. * in the bitmap. tbm_private_iterate() sets matchResult.blockno
* to InvalidBlockNumber when the bitmap is exhausted.
*/ */
while (entry->matchResult == NULL || while ((!BlockNumberIsValid(entry->matchResult.blockno)) ||
(!entry->matchResult->lossy && (!entry->matchResult.lossy &&
entry->offset >= entry->matchNtuples) || entry->offset >= entry->matchNtuples) ||
entry->matchResult->blockno < advancePastBlk || entry->matchResult.blockno < advancePastBlk ||
(ItemPointerIsLossyPage(&advancePast) && (ItemPointerIsLossyPage(&advancePast) &&
entry->matchResult->blockno == advancePastBlk)) entry->matchResult.blockno == advancePastBlk))
{ {
entry->matchResult = if (!tbm_private_iterate(entry->matchIterator, &entry->matchResult))
tbm_private_iterate(entry->matchIterator);
if (entry->matchResult == NULL)
{ {
Assert(!BlockNumberIsValid(entry->matchResult.blockno));
ItemPointerSetInvalid(&entry->curItem); ItemPointerSetInvalid(&entry->curItem);
tbm_end_private_iterate(entry->matchIterator); tbm_end_private_iterate(entry->matchIterator);
entry->matchIterator = NULL; entry->matchIterator = NULL;
@ -849,14 +848,14 @@ entryGetItem(GinState *ginstate, GinScanEntry entry,
} }
/* Exact pages need their tuple offsets extracted. */ /* Exact pages need their tuple offsets extracted. */
if (!entry->matchResult->lossy) if (!entry->matchResult.lossy)
entry->matchNtuples = tbm_extract_page_tuple(entry->matchResult, entry->matchNtuples = tbm_extract_page_tuple(&entry->matchResult,
entry->matchOffsets, entry->matchOffsets,
TBM_MAX_TUPLES_PER_PAGE); TBM_MAX_TUPLES_PER_PAGE);
/* /*
* Reset counter to the beginning of entry->matchResult. Note: * Reset counter to the beginning of entry->matchResult. Note:
* entry->offset is still greater than entry->matchNtuples if * entry->offset is still greater than matchResult.ntuples if
* matchResult is lossy. So, on next call we will get next * matchResult is lossy. So, on next call we will get next
* result from TIDBitmap. * result from TIDBitmap.
*/ */
@ -869,10 +868,10 @@ entryGetItem(GinState *ginstate, GinScanEntry entry,
* We're now on the first page after advancePast which has any * We're now on the first page after advancePast which has any
* items on it. If it's a lossy result, return that. * items on it. If it's a lossy result, return that.
*/ */
if (entry->matchResult->lossy) if (entry->matchResult.lossy)
{ {
ItemPointerSetLossyPage(&entry->curItem, ItemPointerSetLossyPage(&entry->curItem,
entry->matchResult->blockno); entry->matchResult.blockno);
/* /*
* We might as well fall out of the loop; we could not * We might as well fall out of the loop; we could not
@ -889,7 +888,7 @@ entryGetItem(GinState *ginstate, GinScanEntry entry,
Assert(entry->matchNtuples > -1); Assert(entry->matchNtuples > -1);
/* Skip over any offsets <= advancePast, and return that. */ /* Skip over any offsets <= advancePast, and return that. */
if (entry->matchResult->blockno == advancePastBlk) if (entry->matchResult.blockno == advancePastBlk)
{ {
Assert(entry->matchNtuples > 0); Assert(entry->matchNtuples > 0);
@ -910,7 +909,7 @@ entryGetItem(GinState *ginstate, GinScanEntry entry,
} }
ItemPointerSet(&entry->curItem, ItemPointerSet(&entry->curItem,
entry->matchResult->blockno, entry->matchResult.blockno,
entry->matchOffsets[entry->offset]); entry->matchOffsets[entry->offset]);
entry->offset++; entry->offset++;

View File

@ -111,7 +111,7 @@ ginFillScanEntry(GinScanOpaque so, OffsetNumber attnum,
ItemPointerSetMin(&scanEntry->curItem); ItemPointerSetMin(&scanEntry->curItem);
scanEntry->matchBitmap = NULL; scanEntry->matchBitmap = NULL;
scanEntry->matchIterator = NULL; scanEntry->matchIterator = NULL;
scanEntry->matchResult = NULL; scanEntry->matchResult.blockno = InvalidBlockNumber;
scanEntry->matchNtuples = -1; scanEntry->matchNtuples = -1;
scanEntry->list = NULL; scanEntry->list = NULL;
scanEntry->nlist = 0; scanEntry->nlist = 0;

View File

@ -2126,7 +2126,7 @@ heapam_scan_bitmap_next_block(TableScanDesc scan,
Buffer buffer; Buffer buffer;
Snapshot snapshot; Snapshot snapshot;
int ntup; int ntup;
TBMIterateResult *tbmres; TBMIterateResult tbmres;
OffsetNumber offsets[TBM_MAX_TUPLES_PER_PAGE]; OffsetNumber offsets[TBM_MAX_TUPLES_PER_PAGE];
int noffsets = -1; int noffsets = -1;
@ -2142,14 +2142,12 @@ heapam_scan_bitmap_next_block(TableScanDesc scan,
{ {
CHECK_FOR_INTERRUPTS(); CHECK_FOR_INTERRUPTS();
tbmres = tbm_iterate(&scan->st.rs_tbmiterator); if (!tbm_iterate(&scan->st.rs_tbmiterator, &tbmres))
if (tbmres == NULL)
return false; return false;
/* Exact pages need their tuple offsets extracted. */ /* Exact pages need their tuple offsets extracted. */
if (!tbmres->lossy) if (!tbmres.lossy)
noffsets = tbm_extract_page_tuple(tbmres, offsets, noffsets = tbm_extract_page_tuple(&tbmres, offsets,
TBM_MAX_TUPLES_PER_PAGE); TBM_MAX_TUPLES_PER_PAGE);
/* /*
@ -2161,11 +2159,11 @@ heapam_scan_bitmap_next_block(TableScanDesc scan,
* reachable by the index. * reachable by the index.
*/ */
} while (!IsolationIsSerializable() && } while (!IsolationIsSerializable() &&
tbmres->blockno >= hscan->rs_nblocks); tbmres.blockno >= hscan->rs_nblocks);
/* Got a valid block */ /* Got a valid block */
*blockno = tbmres->blockno; *blockno = tbmres.blockno;
*recheck = tbmres->recheck; *recheck = tbmres.recheck;
/* /*
* We can skip fetching the heap page if we don't need any fields from the * We can skip fetching the heap page if we don't need any fields from the
@ -2173,11 +2171,11 @@ heapam_scan_bitmap_next_block(TableScanDesc scan,
* page are visible to our transaction. * page are visible to our transaction.
*/ */
if (!(scan->rs_flags & SO_NEED_TUPLES) && if (!(scan->rs_flags & SO_NEED_TUPLES) &&
!tbmres->recheck && !tbmres.recheck &&
VM_ALL_VISIBLE(scan->rs_rd, tbmres->blockno, &bscan->rs_vmbuffer)) VM_ALL_VISIBLE(scan->rs_rd, tbmres.blockno, &bscan->rs_vmbuffer))
{ {
/* can't be lossy in the skip_fetch case */ /* can't be lossy in the skip_fetch case */
Assert(!tbmres->lossy); Assert(!tbmres.lossy);
Assert(bscan->rs_empty_tuples_pending >= 0); Assert(bscan->rs_empty_tuples_pending >= 0);
Assert(noffsets > -1); Assert(noffsets > -1);
@ -2186,7 +2184,7 @@ heapam_scan_bitmap_next_block(TableScanDesc scan,
return true; return true;
} }
block = tbmres->blockno; block = tbmres.blockno;
/* /*
* Acquire pin on the target heap page, trading in any pin we held before. * Acquire pin on the target heap page, trading in any pin we held before.
@ -2215,7 +2213,7 @@ heapam_scan_bitmap_next_block(TableScanDesc scan,
/* /*
* We need two separate strategies for lossy and non-lossy cases. * We need two separate strategies for lossy and non-lossy cases.
*/ */
if (!tbmres->lossy) if (!tbmres.lossy)
{ {
/* /*
* Bitmap is non-lossy, so we just look through the offsets listed in * Bitmap is non-lossy, so we just look through the offsets listed in
@ -2279,7 +2277,7 @@ heapam_scan_bitmap_next_block(TableScanDesc scan,
Assert(ntup <= MaxHeapTuplesPerPage); Assert(ntup <= MaxHeapTuplesPerPage);
hscan->rs_ntuples = ntup; hscan->rs_ntuples = ntup;
if (tbmres->lossy) if (tbmres.lossy)
(*lossy_pages)++; (*lossy_pages)++;
else else
(*exact_pages)++; (*exact_pages)++;

View File

@ -317,7 +317,7 @@ BitmapAdjustPrefetchIterator(BitmapHeapScanState *node)
{ {
#ifdef USE_PREFETCH #ifdef USE_PREFETCH
ParallelBitmapHeapState *pstate = node->pstate; ParallelBitmapHeapState *pstate = node->pstate;
TBMIterateResult *tbmpre; TBMIterateResult tbmpre;
if (pstate == NULL) if (pstate == NULL)
{ {
@ -330,9 +330,8 @@ BitmapAdjustPrefetchIterator(BitmapHeapScanState *node)
} }
else if (!tbm_exhausted(prefetch_iterator)) else if (!tbm_exhausted(prefetch_iterator))
{ {
tbmpre = tbm_iterate(prefetch_iterator); tbm_iterate(prefetch_iterator, &tbmpre);
node->prefetch_blockno = tbmpre ? tbmpre->blockno : node->prefetch_blockno = tbmpre.blockno;
InvalidBlockNumber;
} }
return; return;
} }
@ -371,9 +370,8 @@ BitmapAdjustPrefetchIterator(BitmapHeapScanState *node)
*/ */
if (!tbm_exhausted(prefetch_iterator)) if (!tbm_exhausted(prefetch_iterator))
{ {
tbmpre = tbm_iterate(prefetch_iterator); tbm_iterate(prefetch_iterator, &tbmpre);
node->prefetch_blockno = tbmpre ? tbmpre->blockno : node->prefetch_blockno = tbmpre.blockno;
InvalidBlockNumber;
} }
} }
} }
@ -441,17 +439,18 @@ BitmapPrefetch(BitmapHeapScanState *node, TableScanDesc scan)
{ {
while (node->prefetch_pages < node->prefetch_target) while (node->prefetch_pages < node->prefetch_target)
{ {
TBMIterateResult *tbmpre = tbm_iterate(prefetch_iterator); TBMIterateResult tbmpre;
bool skip_fetch; bool skip_fetch;
if (tbmpre == NULL) if (!tbm_iterate(prefetch_iterator, &tbmpre))
{ {
/* No more pages to prefetch */ /* No more pages to prefetch */
Assert(!BlockNumberIsValid(tbmpre.blockno));
tbm_end_iterate(prefetch_iterator); tbm_end_iterate(prefetch_iterator);
break; break;
} }
node->prefetch_pages++; node->prefetch_pages++;
node->prefetch_blockno = tbmpre->blockno; node->prefetch_blockno = tbmpre.blockno;
/* /*
* If we expect not to have to actually read this heap page, * If we expect not to have to actually read this heap page,
@ -460,13 +459,13 @@ BitmapPrefetch(BitmapHeapScanState *node, TableScanDesc scan)
* prefetch_pages?) * prefetch_pages?)
*/ */
skip_fetch = (!(scan->rs_flags & SO_NEED_TUPLES) && skip_fetch = (!(scan->rs_flags & SO_NEED_TUPLES) &&
!tbmpre->recheck && !tbmpre.recheck &&
VM_ALL_VISIBLE(node->ss.ss_currentRelation, VM_ALL_VISIBLE(node->ss.ss_currentRelation,
tbmpre->blockno, tbmpre.blockno,
&node->pvmbuffer)); &node->pvmbuffer));
if (!skip_fetch) if (!skip_fetch)
PrefetchBuffer(scan->rs_rd, MAIN_FORKNUM, tbmpre->blockno); PrefetchBuffer(scan->rs_rd, MAIN_FORKNUM, tbmpre.blockno);
} }
} }
@ -481,7 +480,7 @@ BitmapPrefetch(BitmapHeapScanState *node, TableScanDesc scan)
{ {
while (1) while (1)
{ {
TBMIterateResult *tbmpre; TBMIterateResult tbmpre;
bool do_prefetch = false; bool do_prefetch = false;
bool skip_fetch; bool skip_fetch;
@ -500,25 +499,25 @@ BitmapPrefetch(BitmapHeapScanState *node, TableScanDesc scan)
if (!do_prefetch) if (!do_prefetch)
return; return;
tbmpre = tbm_iterate(prefetch_iterator); if (!tbm_iterate(prefetch_iterator, &tbmpre))
if (tbmpre == NULL)
{ {
Assert(!BlockNumberIsValid(tbmpre.blockno));
/* No more pages to prefetch */ /* No more pages to prefetch */
tbm_end_iterate(prefetch_iterator); tbm_end_iterate(prefetch_iterator);
break; break;
} }
node->prefetch_blockno = tbmpre->blockno; node->prefetch_blockno = tbmpre.blockno;
/* As above, skip prefetch if we expect not to need page */ /* As above, skip prefetch if we expect not to need page */
skip_fetch = (!(scan->rs_flags & SO_NEED_TUPLES) && skip_fetch = (!(scan->rs_flags & SO_NEED_TUPLES) &&
!tbmpre->recheck && !tbmpre.recheck &&
VM_ALL_VISIBLE(node->ss.ss_currentRelation, VM_ALL_VISIBLE(node->ss.ss_currentRelation,
tbmpre->blockno, tbmpre.blockno,
&node->pvmbuffer)); &node->pvmbuffer));
if (!skip_fetch) if (!skip_fetch)
PrefetchBuffer(scan->rs_rd, MAIN_FORKNUM, tbmpre->blockno); PrefetchBuffer(scan->rs_rd, MAIN_FORKNUM, tbmpre.blockno);
} }
} }
} }

View File

@ -172,7 +172,6 @@ struct TBMPrivateIterator
int spageptr; /* next spages index */ int spageptr; /* next spages index */
int schunkptr; /* next schunks index */ int schunkptr; /* next schunks index */
int schunkbit; /* next bit to check in current schunk */ int schunkbit; /* next bit to check in current schunk */
TBMIterateResult output;
}; };
/* /*
@ -213,7 +212,6 @@ struct TBMSharedIterator
PTEntryArray *ptbase; /* pagetable element array */ PTEntryArray *ptbase; /* pagetable element array */
PTIterationArray *ptpages; /* sorted exact page index list */ PTIterationArray *ptpages; /* sorted exact page index list */
PTIterationArray *ptchunks; /* sorted lossy page index list */ PTIterationArray *ptchunks; /* sorted lossy page index list */
TBMIterateResult output;
}; };
/* Local function prototypes */ /* Local function prototypes */
@ -957,21 +955,28 @@ tbm_advance_schunkbit(PagetableEntry *chunk, int *schunkbitp)
/* /*
* tbm_private_iterate - scan through next page of a TIDBitmap * tbm_private_iterate - scan through next page of a TIDBitmap
* *
* Returns a TBMIterateResult representing one page, or NULL if there are * Caller must pass in a TBMIterateResult to be filled.
* no more pages to scan. Pages are guaranteed to be delivered in numerical *
* order. If lossy is true, then the bitmap is "lossy" and failed to * Pages are guaranteed to be delivered in numerical order.
* remember the exact tuples to look at on this page --- the caller must *
* examine all tuples on the page and check if they meet the intended * Returns false when there are no more pages to scan and true otherwise. When
* condition. result->ntuples is set to -1 when the bitmap is lossy. * there are no more pages to scan, tbmres->blockno is set to
* If result->recheck is true, only the indicated tuples need * InvalidBlockNumber.
* be examined, but the condition must be rechecked anyway. (For ease of *
* testing, recheck is always set true when lossy is true.) * If lossy is true, then the bitmap is "lossy" and failed to remember
* the exact tuples to look at on this page --- the caller must examine all
* tuples on the page and check if they meet the intended condition. If lossy
* is false, the caller must later extract the tuple offsets from the page
* pointed to by internal_page with tbm_extract_page_tuple.
*
* If tbmres->recheck is true, only the indicated tuples need be examined, but
* the condition must be rechecked anyway. (For ease of testing, recheck is
* always set true when lossy is true.)
*/ */
TBMIterateResult * bool
tbm_private_iterate(TBMPrivateIterator *iterator) tbm_private_iterate(TBMPrivateIterator *iterator, TBMIterateResult *tbmres)
{ {
TIDBitmap *tbm = iterator->tbm; TIDBitmap *tbm = iterator->tbm;
TBMIterateResult *output = &(iterator->output);
Assert(tbm->iterating == TBM_ITERATING_PRIVATE); Assert(tbm->iterating == TBM_ITERATING_PRIVATE);
@ -1009,12 +1014,12 @@ tbm_private_iterate(TBMPrivateIterator *iterator)
chunk_blockno < tbm->spages[iterator->spageptr]->blockno) chunk_blockno < tbm->spages[iterator->spageptr]->blockno)
{ {
/* Return a lossy page indicator from the chunk */ /* Return a lossy page indicator from the chunk */
output->blockno = chunk_blockno; tbmres->blockno = chunk_blockno;
output->lossy = true; tbmres->lossy = true;
output->recheck = true; tbmres->recheck = true;
output->internal_page = NULL; tbmres->internal_page = NULL;
iterator->schunkbit++; iterator->schunkbit++;
return output; return true;
} }
} }
@ -1028,16 +1033,17 @@ tbm_private_iterate(TBMPrivateIterator *iterator)
else else
page = tbm->spages[iterator->spageptr]; page = tbm->spages[iterator->spageptr];
output->internal_page = page; tbmres->internal_page = page;
output->blockno = page->blockno; tbmres->blockno = page->blockno;
output->lossy = false; tbmres->lossy = false;
output->recheck = page->recheck; tbmres->recheck = page->recheck;
iterator->spageptr++; iterator->spageptr++;
return output; return true;
} }
/* Nothing more in the bitmap */ /* Nothing more in the bitmap */
return NULL; tbmres->blockno = InvalidBlockNumber;
return false;
} }
/* /*
@ -1047,10 +1053,9 @@ tbm_private_iterate(TBMPrivateIterator *iterator)
* across multiple processes. We need to acquire the iterator LWLock, * across multiple processes. We need to acquire the iterator LWLock,
* before accessing the shared members. * before accessing the shared members.
*/ */
TBMIterateResult * bool
tbm_shared_iterate(TBMSharedIterator *iterator) tbm_shared_iterate(TBMSharedIterator *iterator, TBMIterateResult *tbmres)
{ {
TBMIterateResult *output = &iterator->output;
TBMSharedIteratorState *istate = iterator->state; TBMSharedIteratorState *istate = iterator->state;
PagetableEntry *ptbase = NULL; PagetableEntry *ptbase = NULL;
int *idxpages = NULL; int *idxpages = NULL;
@ -1101,14 +1106,14 @@ tbm_shared_iterate(TBMSharedIterator *iterator)
chunk_blockno < ptbase[idxpages[istate->spageptr]].blockno) chunk_blockno < ptbase[idxpages[istate->spageptr]].blockno)
{ {
/* Return a lossy page indicator from the chunk */ /* Return a lossy page indicator from the chunk */
output->blockno = chunk_blockno; tbmres->blockno = chunk_blockno;
output->lossy = true; tbmres->lossy = true;
output->recheck = true; tbmres->recheck = true;
output->internal_page = NULL; tbmres->internal_page = NULL;
istate->schunkbit++; istate->schunkbit++;
LWLockRelease(&istate->lock); LWLockRelease(&istate->lock);
return output; return true;
} }
} }
@ -1116,21 +1121,22 @@ tbm_shared_iterate(TBMSharedIterator *iterator)
{ {
PagetableEntry *page = &ptbase[idxpages[istate->spageptr]]; PagetableEntry *page = &ptbase[idxpages[istate->spageptr]];
output->internal_page = page; tbmres->internal_page = page;
output->blockno = page->blockno; tbmres->blockno = page->blockno;
output->lossy = false; tbmres->lossy = false;
output->recheck = page->recheck; tbmres->recheck = page->recheck;
istate->spageptr++; istate->spageptr++;
LWLockRelease(&istate->lock); LWLockRelease(&istate->lock);
return output; return true;
} }
LWLockRelease(&istate->lock); LWLockRelease(&istate->lock);
/* Nothing more in the bitmap */ /* Nothing more in the bitmap */
return NULL; tbmres->blockno = InvalidBlockNumber;
return false;
} }
/* /*
@ -1604,15 +1610,17 @@ tbm_end_iterate(TBMIterator *iterator)
} }
/* /*
* Get the next TBMIterateResult from the shared or private bitmap iterator. * Populate the next TBMIterateResult using the shared or private bitmap
* iterator. Returns false when there is nothing more to scan.
*/ */
TBMIterateResult * bool
tbm_iterate(TBMIterator *iterator) tbm_iterate(TBMIterator *iterator, TBMIterateResult *tbmres)
{ {
Assert(iterator); Assert(iterator);
Assert(tbmres);
if (iterator->shared) if (iterator->shared)
return tbm_shared_iterate(iterator->i.shared_iterator); return tbm_shared_iterate(iterator->i.shared_iterator, tbmres);
else else
return tbm_private_iterate(iterator->i.private_iterator); return tbm_private_iterate(iterator->i.private_iterator, tbmres);
} }

View File

@ -354,7 +354,12 @@ typedef struct GinScanEntryData
/* for a partial-match or full-scan query, we accumulate all TIDs here */ /* for a partial-match or full-scan query, we accumulate all TIDs here */
TIDBitmap *matchBitmap; TIDBitmap *matchBitmap;
TBMPrivateIterator *matchIterator; TBMPrivateIterator *matchIterator;
TBMIterateResult *matchResult;
/*
* If blockno is InvalidBlockNumber, all of the other fields in the
* matchResult are meaningless.
*/
TBMIterateResult matchResult;
OffsetNumber matchOffsets[TBM_MAX_TUPLES_PER_PAGE]; OffsetNumber matchOffsets[TBM_MAX_TUPLES_PER_PAGE];
int matchNtuples; int matchNtuples;

View File

@ -101,8 +101,8 @@ extern bool tbm_is_empty(const TIDBitmap *tbm);
extern TBMPrivateIterator *tbm_begin_private_iterate(TIDBitmap *tbm); extern TBMPrivateIterator *tbm_begin_private_iterate(TIDBitmap *tbm);
extern dsa_pointer tbm_prepare_shared_iterate(TIDBitmap *tbm); extern dsa_pointer tbm_prepare_shared_iterate(TIDBitmap *tbm);
extern TBMIterateResult *tbm_private_iterate(TBMPrivateIterator *iterator); extern bool tbm_private_iterate(TBMPrivateIterator *iterator, TBMIterateResult *tbmres);
extern TBMIterateResult *tbm_shared_iterate(TBMSharedIterator *iterator); extern bool tbm_shared_iterate(TBMSharedIterator *iterator, TBMIterateResult *tbmres);
extern void tbm_end_private_iterate(TBMPrivateIterator *iterator); extern void tbm_end_private_iterate(TBMPrivateIterator *iterator);
extern void tbm_end_shared_iterate(TBMSharedIterator *iterator); extern void tbm_end_shared_iterate(TBMSharedIterator *iterator);
extern TBMSharedIterator *tbm_attach_shared_iterate(dsa_area *dsa, extern TBMSharedIterator *tbm_attach_shared_iterate(dsa_area *dsa,
@ -113,7 +113,7 @@ extern TBMIterator tbm_begin_iterate(TIDBitmap *tbm,
dsa_area *dsa, dsa_pointer dsp); dsa_area *dsa, dsa_pointer dsp);
extern void tbm_end_iterate(TBMIterator *iterator); extern void tbm_end_iterate(TBMIterator *iterator);
extern TBMIterateResult *tbm_iterate(TBMIterator *iterator); extern bool tbm_iterate(TBMIterator *iterator, TBMIterateResult *tbmres);
static inline bool static inline bool
tbm_exhausted(TBMIterator *iterator) tbm_exhausted(TBMIterator *iterator)