mirror of https://github.com/tongzx/nt5src
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1559 lines
67 KiB
1559 lines
67 KiB
/*
|
|
* handfact.c
|
|
*
|
|
* author: John R. Douceur
|
|
* date: 26 January 1998
|
|
*
|
|
* This source file provides functions that implement assignment, release, and
|
|
* dereferencing operations with a handle_factory. The code is object-oriented
|
|
* C, transliterated from a C++ implementation.
|
|
*
|
|
* The handle factory is a component that generates and validates handles. It
|
|
* is intended to be used in a software module that provides client software
|
|
* modules with means to refer to information structures contained within the
|
|
* provider. While such a means could simply be a pointer, this would not
|
|
* enable the deletion of the information structures without explicitly
|
|
* notifying the clients of such deletion. Unlike pointers, the handles
|
|
* generated by the handle factory can be examined (by the handle factory)
|
|
* to determine their validity.
|
|
*
|
|
* Handles can be invalidated in one of two ways. The handle can be released
|
|
* by calling the release_HF_handle() function, indicating to the handle
|
|
* factory that the handle is no longer necessary and that future requests
|
|
* to dereference this handle should be met with a null pointer. Alternately,
|
|
* the handle can be revoked by the handle factory; this will happen unter two
|
|
* circumstances. If a large number of handles (more than four billion) are
|
|
* issued and subsequently released, it becomes necessary to reuse portions of
|
|
* the handle space for future assignments; under these circumstances, very
|
|
* old handles will be revoked well before this recycling occurs, to give the
|
|
* holders of those handles ample opportunity to notice that their handles
|
|
* have become invalid and to request new handles. The other situation in
|
|
* which revokation can occur is if the amount of available memory becomes
|
|
* too small to allocate additional space to expand the handle database; then,
|
|
* if the assignment of a new handle is requested, the least-recently-assigned
|
|
* handle will be revoked to make room for the new request.
|
|
*
|
|
* Use of the handle factory in a multi-threaded environment requires a lock.
|
|
* This lock must be taken by a single thread for the execution of either
|
|
* assign_HF_handle() or release_HF_handle(). Use of dereference_HF_handle()
|
|
* does not require taking a lock, since synchronization is handled internally
|
|
* through careful sequencing of read and write operations.
|
|
*
|
|
* None of the code or comments in this file need to be understood by writers
|
|
* of client code; all explanatory information for clients is found in the
|
|
* associated header file, handfact.h.
|
|
*
|
|
*/
|
|
|
|
#include "gpcpre.h"
|
|
|
|
/*
|
|
* There are a number of aspects to the handle factory that must be understood
|
|
* by anyone wishing to modify this code. The description in this comment
|
|
* block is intended to provide a progressive overview of the handle factory.
|
|
*
|
|
* The basic system comprises a table of entries. Each assigned handle
|
|
* corresponds to a single, unique entry, as determined by the handle value
|
|
* modulo the table size. A handle is validated by comparing the handle value
|
|
* to the stored handle value in the entry. The unassigned entries are kept
|
|
* on a list; when an entry is released (or revoked), it is put on the tail of
|
|
* the list, and when an entry is needed for an assignment, it is taken from
|
|
* the head of the list.
|
|
*
|
|
* If there are no unassigned entries in the table when a new handle is
|
|
* requested, a new table of twice the size is allocated, and all assigned
|
|
* handles are relocated to the new table. All unassigned handles in the new
|
|
* table are placed on the unassigned list.
|
|
*
|
|
* As handles are released, the space required for handle entries is reduced.
|
|
* The table can be contracted into a table of half the size if no two assigned
|
|
* handles will yield the same entry address. Two handles which will yield
|
|
* the same entry address in a half-size table are called a pair, and the
|
|
* number of such pairs is tracked in the variable pair_count, which must be
|
|
* zero in order to contract the table. In order to minimize the number of
|
|
* pairs in the table, there are actually two lists of unassigned entries.
|
|
* Assigning an entry from the primary list will not increase the pair count,
|
|
* whereas assigning an entry from the secondary list will increase the pair
|
|
* count. Thus, assignments are always made from the primary list, if it is
|
|
* not empty.
|
|
*
|
|
* Assigned handles are also kept on a list, in order of assignment. If it
|
|
* becomes necessary to revoke a handle to make room for another, the oldest
|
|
* handle will be revoked, and it will be found at the head of this list.
|
|
*
|
|
*/
|
|
|
|
// This macro allocates an array of HFEntry structures. The size of the array
|
|
// is provided as an argument to the macro.
|
|
//
|
|
//#define NEW_HFEntry_array(array_size) \
|
|
// ((HFEntry *)malloc(array_size * sizeof(HFEntry)))
|
|
#define NEW_HFEntry_array(_a,array_size) \
|
|
GpcAllocMem(&_a,array_size * sizeof(HFEntry), HandleFactoryTag)
|
|
|
|
// This macro allocates an array of integers. The size of the array is
|
|
// provided as an argument to the macro.
|
|
//
|
|
//#define NEW_int_array(array_size) \
|
|
// ((int *)malloc(array_size * sizeof(int)))
|
|
#define NEW_int_array(_a,array_size) \
|
|
GpcAllocMem(&_a,array_size * sizeof(int), HandleFactoryTag)
|
|
|
|
/*
|
|
* Following are prototypes for static functions that are used internally by
|
|
* the handle factory routines.
|
|
*
|
|
*/
|
|
|
|
// This function doubles the size of the table in which the handles and pointers
|
|
// are stored. It is called by assign_HF_handle() when there is insufficient
|
|
// space in the table to assign the newly requested handle. If the expansion
|
|
// is successful, the function returns a value of 0. If the expansion fails
|
|
// (due, for example, to an inability to allocate memory), the function returns
|
|
// a value of 1.
|
|
//
|
|
int expand_HF_table(
|
|
HandleFactory *hfact);
|
|
|
|
// This function halves the size of the table in which the handles and pointers
|
|
// are stored. In order to reduce the amount of space consumed by the handle
|
|
// factory, this function is called called by release_HF_handle() and
|
|
// revoke_ancient_HF_handles() when they determine that the table can and should
|
|
// be contracted. The table can be contracted when pair_count == 0 and
|
|
// table_size > 2. However, the table may not be contracted then, because
|
|
// hysteresis is employed both to keep the mean assignment and release times
|
|
// constant and to minimize the allocation chatter of rapidly expanding and
|
|
// contracting the table. If the contraction is successful, the function
|
|
// returns a value of 0. If the contraction fails, the function returns a
|
|
// value of 1.
|
|
//
|
|
int contract_HF_table(
|
|
HandleFactory *hfact);
|
|
|
|
// This function revokes handles that are between handle_base and handle_base
|
|
// + 2 * HANDLE_RANGE_STEP - 1, inclusive. It then increments the value of
|
|
// handle_base by HANDLE_RANGE_STEP. Suspended handles will be revoked one
|
|
// revokation pass later than non-suspended handles.
|
|
//
|
|
void revoke_ancient_HF_handles(
|
|
HandleFactory *hfact);
|
|
|
|
// Every entry is on one of three lists, and the heads and tails of these lists
|
|
// are maintained in the entry_list[] array. The index of this array is given
|
|
// by the following three manifest constants.
|
|
//
|
|
#define LD_PRIMARY 0 // first list from which to select an entry to assign
|
|
#define LD_SECONDARY 1 // second list from which to select an entry to assign
|
|
#define LD_ASSIGNED 2 // list of assigned entries, in order of assignment age
|
|
|
|
// When the handle space is recycled, there is a danger of handle collisions.
|
|
// In order to substantially reduce the likelihood of these collisions, very
|
|
// old handles are revoked well before their recycling begins, to give the
|
|
// holders of these handles ample opportunity to notice that their handles
|
|
// have become invalid and to request new handles. Thus, handles are revoked
|
|
// when they become more than MAX_HANDLE_RANGE less than the currently generated
|
|
// handles. To reduce overhead, revokations are performed in batches of size
|
|
// determined by HANDLE_RANGE_STEP.
|
|
//
|
|
// A handle may be suspended by incrementing the handle value by
|
|
// HANDLE_RANGE_STEP. This causes the comparison in dereference_HF_handle() to
|
|
// fail, so the handle is judged to be invalid. To reinstate the handle, the
|
|
// handle value is decremented by HANDLE_RANGE_STEP, returning the handle to its
|
|
// original value. A handle that is suspended will be revoked one revokation
|
|
// pass later than it would have been if it hadn't been suspended.
|
|
//
|
|
#define HANDLE_RANGE_STEP ((HFHandle)0x20000000)
|
|
#define MAX_HANDLE_RANGE ((HFHandle)0x90000000)
|
|
|
|
// To keep the mean assignment and release times constant (and, indirectly, to
|
|
// minimize the allocation chatter of rapidly expanding and contracting the
|
|
// table), the table is not necessarily contracted as soon as possible.
|
|
// Hysteresis is employed to postpone the contraction until the computational
|
|
// cost of previous expansions and contractions is distributed over a sufficient
|
|
// number of assignment or release operations to maintain a constant cost per
|
|
// operation ratio. The cost of each expansion is equal to the overhead of
|
|
// memory allocation and deallocation plus the cost to split each entry into
|
|
// two entries. The cost of each contraction is equal to the overhead of
|
|
// memory allocation and deallocation plus the cost to merge each pair of
|
|
// entries into one entry. The cost of memory allocation and deallocation is
|
|
// equal to ALLOCATION_COST times the mean cost of a single split or merge
|
|
// operation. This value was determined by empirical measurement.
|
|
//
|
|
#define ALLOCATION_COST 12
|
|
|
|
// Since this is not C++, the HandleFactory structure is not self-constructing;
|
|
// therefore, the following constructor code must be called on the HandleFactory
|
|
// structure after it is allocated. If the construction is successful, the
|
|
// function returns a value of 0. If the construction fails (due, for example,
|
|
// to an inability to allocate memory), the function returns a value of 1.
|
|
//
|
|
int
|
|
constructHandleFactory(
|
|
HandleFactory *hfact)
|
|
{
|
|
// The table size is initially set to 2, and it will never be smaller.
|
|
hfact->table_size = 2;
|
|
// Allocate space for the initial table.
|
|
NEW_HFEntry_array(hfact->entries,hfact->table_size);
|
|
if (hfact->entries == 0)
|
|
{
|
|
// Memory could not be allocated for the array of entries created by
|
|
// the constructor. Therefore, we return an indication of failure to
|
|
// the client.
|
|
return 1;
|
|
}
|
|
hfact->verifier0 = 0; // the verifiers are initialized with the same value
|
|
hfact->verifier1 = 0; // the verifiers are initialized with the same value
|
|
hfact->handle_base = 0; // handles will start with 0
|
|
hfact->population = 0; // no handles initially assigned
|
|
hfact->pair_count = 0; // since no assigned handles, no pairs
|
|
hfact->hysteresis_debt = 0;
|
|
// Initialize the two entries that are initially allocated. Both are marked
|
|
// as unassigned; the larger value (2) is put on the secondary list, and the
|
|
// smaller value (1) on the secondary list. Record 0 contains an initial
|
|
// handle value of 2 instead of 0 because a handle value of 0 is reserved.
|
|
hfact->entries[0].handle = hfact->handle_base + hfact->table_size;
|
|
hfact->entries[0].next_handle = hfact->handle_base + hfact->table_size;
|
|
hfact->entries[0].reference = 0;
|
|
hfact->entries[0].next_entry = &hfact->entry_list[LD_SECONDARY];
|
|
hfact->entries[0].prev_entry = &hfact->entry_list[LD_SECONDARY];
|
|
hfact->entries[1].handle = hfact->handle_base + 1;
|
|
hfact->entries[1].next_handle = hfact->handle_base + 1;
|
|
hfact->entries[1].reference = 0;
|
|
hfact->entries[1].next_entry = &hfact->entry_list[LD_PRIMARY];
|
|
hfact->entries[1].prev_entry = &hfact->entry_list[LD_PRIMARY];
|
|
// Initialize the primary list. This list initially contains entry 1.
|
|
hfact->entry_list[LD_PRIMARY].handle = 0;
|
|
hfact->entry_list[LD_PRIMARY].next_handle = 0;
|
|
hfact->entry_list[LD_PRIMARY].reference = 0;
|
|
hfact->entry_list[LD_PRIMARY].next_entry = &hfact->entries[1];
|
|
hfact->entry_list[LD_PRIMARY].prev_entry = &hfact->entries[1];
|
|
// Initialize the secondary list. This list initially contains entry 0.
|
|
hfact->entry_list[LD_SECONDARY].handle = 0;
|
|
hfact->entry_list[LD_SECONDARY].next_handle = 0;
|
|
hfact->entry_list[LD_SECONDARY].reference = 0;
|
|
hfact->entry_list[LD_SECONDARY].next_entry = &hfact->entries[0];
|
|
hfact->entry_list[LD_SECONDARY].prev_entry = &hfact->entries[0];
|
|
// Initialize the assigned list. This list initially is empty.
|
|
hfact->entry_list[LD_ASSIGNED].handle = 0;
|
|
hfact->entry_list[LD_ASSIGNED].next_handle = 0;
|
|
hfact->entry_list[LD_ASSIGNED].reference = 0;
|
|
hfact->entry_list[LD_ASSIGNED].next_entry = &hfact->entry_list[LD_ASSIGNED];
|
|
hfact->entry_list[LD_ASSIGNED].prev_entry = &hfact->entry_list[LD_ASSIGNED];
|
|
// Reduce handle_base by HANDLE_RANGE_STEP so that suspended handles will
|
|
// not slip through revokation.
|
|
hfact->handle_base -= HANDLE_RANGE_STEP;
|
|
// return an indication of success to the client.
|
|
return 0;
|
|
}
|
|
|
|
// Since this is not C++, the HandleFactory structure is not self-destructing;
|
|
// therefore, the following destructor code must be called on the HandleFactory
|
|
// structure before it is deallocated.
|
|
//
|
|
void
|
|
destructHandleFactory(
|
|
HandleFactory *hfact)
|
|
{
|
|
// Free the space consumed by the table of handles.
|
|
GpcFreeMem(hfact->entries, HandleFactoryTag);
|
|
}
|
|
|
|
// This function generates a new handle value, associates the handle value with
|
|
// the provided reference pointer, and returns the handle value. Barring
|
|
// highly unusual circumstances, this handle will remain valid until it is
|
|
// explicitly released by a call to release_HF_handle(). However, there is no
|
|
// guarantee that the handle will persist for an arbitrary duration; it may
|
|
// become necessary for the handle factory to revoke the handle under some
|
|
// circumstances, particularly when the handle becomes very old or when memory
|
|
// becomes scarce.
|
|
//
|
|
// The assign_HF_handle() function will never return a handle value of zero.
|
|
// Thus, the client program is free to use a zero handle value as an escape
|
|
// indicator, if desired.
|
|
//
|
|
// In a multi-threaded environment, a single thread must take a lock prior to
|
|
// calling this function, and this must be the same lock taken before calling
|
|
// release_HF_handle().
|
|
//
|
|
HFHandle
|
|
assign_HF_handle(
|
|
HandleFactory *hfact,
|
|
void *reference)
|
|
{
|
|
int list;
|
|
HFEntry *entry;
|
|
volatile HFEntry *seq_entry; // volatile to ensure sequencing
|
|
HFHandle handle;
|
|
HFHandle handle_range;
|
|
|
|
if (hfact->population >= hfact->table_size)
|
|
{
|
|
// All entries in the table are assigned, so it is necessary to
|
|
// increase the table size.
|
|
int expansion_failure = expand_HF_table(hfact);
|
|
if (expansion_failure)
|
|
{
|
|
//
|
|
// just fail
|
|
//
|
|
|
|
return 0;
|
|
#if 0
|
|
|
|
// Expanding the table failed, presumably due to inability to
|
|
// allocate sufficient memory. So, instead, we revoke the least-
|
|
// recently assigned handle. First, remove the entry from the
|
|
// assigned list and place it on the secondary list.
|
|
entry = hfact->entry_list[LD_ASSIGNED].next_entry;
|
|
entry->next_entry->prev_entry = &hfact->entry_list[LD_ASSIGNED];
|
|
hfact->entry_list[LD_ASSIGNED].next_entry = entry->next_entry;
|
|
entry->next_entry = &hfact->entry_list[LD_SECONDARY];
|
|
entry->prev_entry = hfact->entry_list[LD_SECONDARY].prev_entry;
|
|
hfact->entry_list[LD_SECONDARY].prev_entry->next_entry = entry;
|
|
hfact->entry_list[LD_SECONDARY].prev_entry = entry;
|
|
// Then, invalidate the handle. The order of the operations is
|
|
// important to correct multi-threaded operation.
|
|
seq_entry = entry;
|
|
seq_entry->handle = entry->next_handle; // first invalidate handle
|
|
seq_entry->reference = 0; // then clear reference
|
|
// Decrement the pair count and population, so that when they are
|
|
// incremented in the code below, they will have correct values.
|
|
hfact->pair_count--;
|
|
hfact->population--;
|
|
#endif
|
|
}
|
|
}
|
|
// At this point, there is at least one available entry. If there is any
|
|
// entry on the primary list, it should be selected.
|
|
list = LD_PRIMARY;
|
|
if (hfact->entry_list[LD_PRIMARY].next_entry ==
|
|
&hfact->entry_list[LD_PRIMARY])
|
|
{
|
|
// The primary list is empty, so we take from the secondary list. By
|
|
// definition, this will increase the pair count.
|
|
list = LD_SECONDARY;
|
|
hfact->pair_count++;
|
|
}
|
|
// Remove the entry from the head of the appropriate list and place it on
|
|
// the assigned list.
|
|
entry = hfact->entry_list[list].next_entry;
|
|
handle = entry->handle;
|
|
entry->next_entry->prev_entry = entry->prev_entry;
|
|
entry->prev_entry->next_entry = entry->next_entry;
|
|
entry->next_entry = &hfact->entry_list[LD_ASSIGNED];
|
|
entry->prev_entry = hfact->entry_list[LD_ASSIGNED].prev_entry;
|
|
hfact->entry_list[LD_ASSIGNED].prev_entry->next_entry = entry;
|
|
hfact->entry_list[LD_ASSIGNED].prev_entry = entry;
|
|
// Set the reference pointer to that provided as an argument.
|
|
entry->reference = reference;
|
|
// The next handle for this entry will be greater by the table size. It
|
|
// is important to set this value in this routine because unequal values of
|
|
// handle and next_handle indicate an assigned entry.
|
|
entry->next_handle = handle + hfact->table_size;
|
|
if (entry->next_handle == 0)
|
|
{
|
|
// The handle value has wrapped around back to zero; however, zero is
|
|
// a reserved value, so we instead set the next handle to the subsequent
|
|
// legal value, which is the table size.
|
|
entry->next_handle = hfact->table_size;
|
|
}
|
|
// The population has increased by one.
|
|
hfact->population++;
|
|
// We're being tricky with unsigned integer math here. We revoke ancient
|
|
// handles if the value of the handle we are currently issuing is greater
|
|
// than the handle base by more than MAX_HANDLE_RANGE, modulo the size of
|
|
// the handle space. The modulo is implicit.
|
|
handle_range = handle - hfact->handle_base;
|
|
if (handle_range > MAX_HANDLE_RANGE)
|
|
{
|
|
revoke_ancient_HF_handles(hfact);
|
|
}
|
|
// This assignment operation decreases the hysteresis debt.
|
|
if (hfact->hysteresis_debt > 0)
|
|
{
|
|
hfact->hysteresis_debt--;
|
|
}
|
|
// Return the newly assigned handle.
|
|
return handle;
|
|
}
|
|
|
|
// This function releases a handle, indicating that further attempts to
|
|
// dereference the handle should result in a null pointer value rather than the
|
|
// pointer value that was originally assigned to the handle. The handle factory
|
|
// checks the validity of the handle and returns a corresponding status code.
|
|
// If the handle is currently assigned, then it is released, and the function
|
|
// returns a value of 0. If the handle is not currently assigned, the function
|
|
// aborts and returns a value of 1.
|
|
//
|
|
// In a multi-threaded environment, a single thread must take a lock prior to
|
|
// calling this function, and this must be the same lock taken before calling
|
|
// assign_HF_handle().
|
|
//
|
|
int
|
|
release_HF_handle(
|
|
HandleFactory *hfact,
|
|
HFHandle handle)
|
|
{
|
|
int entry_index;
|
|
HFEntry *entry;
|
|
HFEntry *other_entry;
|
|
int list;
|
|
HFHandle adjusted_next_handle;
|
|
HFHandle adjusted_other_next_handle;
|
|
volatile HFEntry *seq_entry; // volatile to ensure sequencing
|
|
|
|
// Compute the index of the entry by taking the handle value modulo the
|
|
// table size. Since the table size is a power of two, we can simply
|
|
// subtract one to produce a mask and then conjoin the mask with the
|
|
// handle value.
|
|
entry_index = handle & hfact->table_size - 1;
|
|
entry = &hfact->entries[entry_index];
|
|
if ((entry->handle != handle && entry->handle != handle + HANDLE_RANGE_STEP)
|
|
|| entry->handle == entry->next_handle)
|
|
{
|
|
// Either the indexed entry does not refer to the provided handle nor to
|
|
// the provided handle's suspension value, or the entry is unassigned.
|
|
// In any of these cases, abort and return an error code to the client.
|
|
return 1;
|
|
}
|
|
// The "other entry" is the entry that would have to be merged with the
|
|
// indexed entry if the table size were to be contracted in half.
|
|
other_entry = &hfact->entries[entry_index ^ hfact->table_size / 2];
|
|
if (other_entry->handle == other_entry->next_handle)
|
|
{
|
|
// We're being tricky with unsigned integer math here. Before comparing
|
|
// the two next handles, we subtract from each the value of handle_base,
|
|
// modulo the size of the handle space (the modulo is implicit). This
|
|
// allows the effective comparison of their logical acyclic values
|
|
// rather than their actual cyclic values.
|
|
adjusted_next_handle = entry->next_handle - hfact->handle_base;
|
|
adjusted_other_next_handle =
|
|
other_entry->next_handle - hfact->handle_base;
|
|
if (adjusted_other_next_handle < adjusted_next_handle)
|
|
{
|
|
// The other entry is unassigned and has a smaller handle value
|
|
// than the indexed entry. Thus, the other entry should be moved
|
|
// from the secondary list to the primary list, and the indexed
|
|
// entry should be placed on the secondary list.
|
|
other_entry->next_entry->prev_entry = other_entry->prev_entry;
|
|
other_entry->prev_entry->next_entry = other_entry->next_entry;
|
|
other_entry->next_entry = &hfact->entry_list[LD_PRIMARY];
|
|
other_entry->prev_entry = hfact->entry_list[LD_PRIMARY].prev_entry;
|
|
hfact->entry_list[LD_PRIMARY].prev_entry->next_entry = other_entry;
|
|
hfact->entry_list[LD_PRIMARY].prev_entry = other_entry;
|
|
list = LD_SECONDARY;
|
|
}
|
|
else
|
|
{
|
|
// The other entry is unassigned and has a larger handle value
|
|
// than the indexed entry. Thus, the indexed entry should be
|
|
// placed on the secondary list.
|
|
list = LD_PRIMARY;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// The other entry is assigned. Thus, the indexed entry should be
|
|
// placed on the secondary list. Also, since the two entries were
|
|
// both assigned, they formed a pair. Since we are releasing one of
|
|
// them, the pair count drops by one.
|
|
list = LD_SECONDARY;
|
|
hfact->pair_count--;
|
|
}
|
|
// Remove the entry from the assigned list and place it on the
|
|
// appropriate list.
|
|
entry->next_entry->prev_entry = entry->prev_entry;
|
|
entry->prev_entry->next_entry = entry->next_entry;
|
|
entry->next_entry = &hfact->entry_list[list];
|
|
entry->prev_entry = hfact->entry_list[list].prev_entry;
|
|
hfact->entry_list[list].prev_entry->next_entry = entry;
|
|
hfact->entry_list[list].prev_entry = entry;
|
|
// Invalidate the handle. The order of the operations is important to
|
|
// correct multi-threaded operation.
|
|
seq_entry = entry;
|
|
seq_entry->handle = entry->next_handle; // first invalidate handle
|
|
seq_entry->reference = 0; // then clear reference
|
|
// The population has decreased by one.
|
|
hfact->population--;
|
|
// This release operation decreases the hysteresis debt.
|
|
if (hfact->hysteresis_debt > 0)
|
|
{
|
|
hfact->hysteresis_debt--;
|
|
}
|
|
// To contract the table, there must be no pairs, because otherwise two
|
|
// assigned handles would yield the same entry index and thereby conflict.
|
|
// Furthermore, the table size must be greater than 2, because much of the
|
|
// handle factory code assumes that the table is at least of size 2. In
|
|
// addition to these strict requirements, hysteresis is employed both to
|
|
// keep the mean assignment and release times constant and to minimize the
|
|
// allocation chatter of rapidly expanding and contracting the table. Only
|
|
// if the hysteresis debt is zero will the table be contracted.
|
|
if (hfact->pair_count == 0 && hfact->table_size > 2 &&
|
|
hfact->hysteresis_debt == 0)
|
|
{
|
|
contract_HF_table(hfact);
|
|
// Note that we ignore the return code. If the contraction is
|
|
// unsuccessful, we just continue as usual. There is no real harm in
|
|
// not contracting the table, except that we consume more space than
|
|
// necessary.
|
|
}
|
|
// return an indication of success to the client.
|
|
return 0;
|
|
}
|
|
|
|
// This function suspends a handle, indicating that further attempts to
|
|
// dereference the handle should result in a null pointer value rather than the
|
|
// pointer value that was originally assigned to the handle, unless and until
|
|
// reinstate_HF_handle() is called on the handle value. The handle factory
|
|
// checks the validity of the handle and returns a corresponding status code.
|
|
// If the handle is currently assigned and not suspended, then it is suspended,
|
|
// and the function returns a value of 0. If the handle is not currently
|
|
// assigned or has already been suspended, the function aborts and returns a
|
|
// value of 1.
|
|
//
|
|
// In a multi-threaded environment, a single thread must take a lock prior to
|
|
// calling this function, and this must be the same lock taken before calling
|
|
// assign_HF_handle(), release_HF_handle(), and reinstate_HF_handle().
|
|
//
|
|
int
|
|
suspend_HF_handle(
|
|
HandleFactory *hfact,
|
|
HFHandle handle)
|
|
{
|
|
int entry_index;
|
|
HFEntry *entry;
|
|
|
|
// Compute the index of the entry by taking the handle value modulo the
|
|
// table size. Since the table size is a power of two, we can simply
|
|
// subtract one to produce a mask and then conjoin the mask with the
|
|
// handle value.
|
|
entry_index = handle & hfact->table_size - 1;
|
|
entry = &hfact->entries[entry_index];
|
|
if (entry->handle != handle || entry->handle == entry->next_handle)
|
|
{
|
|
// Either the indexed entry does not refer to the provided handle, or
|
|
// the entry is unassigned. In either case, abort and return an error
|
|
// code to the client.
|
|
return 1;
|
|
}
|
|
// Suspend the handle.
|
|
entry->handle += HANDLE_RANGE_STEP;
|
|
// This suspension operation decreases the hysteresis debt.
|
|
if (hfact->hysteresis_debt > 0)
|
|
{
|
|
hfact->hysteresis_debt--;
|
|
}
|
|
// return an indication of success to the client.
|
|
return 0;
|
|
}
|
|
|
|
// This function reinstates a suspended handle, indicating that further attempts
|
|
// to dereference the handle should result in the pointer value that was
|
|
// originally assigned to the handle, rather than the null pointer value to
|
|
// which a suspended handle dereferences. The handle factory checks the
|
|
// validity of the handle and returns a corresponding status code. If the handle
|
|
// is currently assigned and suspended, then it is reinstated, and the function
|
|
// returns a value of 0. If the handle is not currently assigned or is not
|
|
// suspended, the function aborts and returns a value of 1.
|
|
//
|
|
// In a multi-threaded environment, a single thread must take a lock prior to
|
|
// calling this function, and this must be the same lock taken before calling
|
|
// assign_HF_handle(), release_HF_handle(), and suspend_HF_handle().
|
|
//
|
|
int
|
|
reinstate_HF_handle(
|
|
HandleFactory *hfact,
|
|
HFHandle handle)
|
|
{
|
|
int entry_index;
|
|
HFEntry *entry;
|
|
|
|
// Compute the index of the entry by taking the handle value modulo the
|
|
// table size. Since the table size is a power of two, we can simply
|
|
// subtract one to produce a mask and then conjoin the mask with the
|
|
// handle value.
|
|
entry_index = handle & hfact->table_size - 1;
|
|
entry = &hfact->entries[entry_index];
|
|
if (entry->handle != handle + HANDLE_RANGE_STEP ||
|
|
entry->handle == entry->next_handle)
|
|
{
|
|
// Either the indexed entry does not refer to the provided handle's
|
|
// suspension value, or the entry is unassigned. In either case, abort
|
|
// and return an error code to the client.
|
|
return 1;
|
|
}
|
|
// Reinstate the handle.
|
|
entry->handle -= HANDLE_RANGE_STEP;
|
|
// This reinstatement operation decreases the hysteresis debt.
|
|
if (hfact->hysteresis_debt > 0)
|
|
{
|
|
hfact->hysteresis_debt--;
|
|
}
|
|
// return an indication of success to the client.
|
|
return 0;
|
|
}
|
|
|
|
// This function validates a handle and returns either the associated pointer
|
|
// (if the handle is valid) or a null pointer value (if the handle is invalid).
|
|
// If the handle has not been released but a null value is returned, then the
|
|
// handle has been revoked by the handle factory. This is expected to be a
|
|
// highly unusual occurrence; however, since it can happen, any program that
|
|
// employs the handle factory must have some auxiliary mechanism for retrieving
|
|
// the desired pointer information. Once the pointer is retrieved through this
|
|
// (presumably expensive) auxiliary means, a new handle can be reassigned to
|
|
// the pointer by another call to assign_HF_handle().
|
|
//
|
|
// Even in a multi-threaded environment, it is not necessary to take a lock
|
|
// prior to calling this function. Careful sequencing of read and write
|
|
// operations inside the handle factory code obviates the need to explicitly
|
|
// lock the data structure for dereferencing handles.
|
|
//
|
|
void *
|
|
dereference_HF_handle(
|
|
HandleFactory *hfact,
|
|
HFHandle handle)
|
|
{
|
|
HFHandle entry_handle;
|
|
void *reference;
|
|
//int verifier;
|
|
int entry_index;
|
|
/*volatile*/ HFEntry *entry; // volatile to ensure sequencing
|
|
|
|
// This loop spins until the verifier variables begin and end a pass of
|
|
// the loop with the same value. There is an extremely short sequence
|
|
// of instructions in the expand and contract routines that modifies the
|
|
// values of the entries and table_size variables, and these modifications
|
|
// are bracketed by increments of the verifier variables in the reverse
|
|
// order as they are here read. As long as the verifiers have the same
|
|
// value, then entries and table_size are in a consistent state. This loop
|
|
// should very rarely be executed more than once, since the modification in
|
|
// the other routines is so short.
|
|
do
|
|
{
|
|
//verifier = hfact->verifier1;
|
|
// Compute the index of the entry by taking the handle value modulo the
|
|
// table size. Since the table size is a power of two, we can simply
|
|
// subtract one to produce a mask and then conjoin the mask with the
|
|
// handle value.
|
|
entry_index = handle & hfact->table_size - 1;
|
|
entry = &hfact->entries[entry_index];
|
|
// Get local copies of the reference pointer and handle value. The
|
|
// order of the operations is important to correct multi-threaded
|
|
// operation.
|
|
reference = entry->reference; // first get reference
|
|
entry_handle = entry->handle; // then get handle to check validity
|
|
} while (0 /*verifier != hfact->verifier0*/);
|
|
if (entry_handle == handle)
|
|
{
|
|
// The stored handle matches the provided handle, so the latter is
|
|
// valid. We thus return the reference pointer.
|
|
return reference;
|
|
}
|
|
else
|
|
{
|
|
// The stored handle does not match the provided handle, so the latter
|
|
// is invalid. We thus return a null pointer.
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
void *
|
|
dereference_HF_handle_with_cb(
|
|
HandleFactory *hfact,
|
|
HFHandle handle,
|
|
ULONG offset)
|
|
{
|
|
HFHandle entry_handle;
|
|
void *reference;
|
|
//int verifier;
|
|
int entry_index;
|
|
/*volatile*/ HFEntry *entry; // volatile to ensure sequencing
|
|
|
|
// This loop spins until the verifier variables begin and end a pass of
|
|
// the loop with the same value. There is an extremely short sequence
|
|
// of instructions in the expand and contract routines that modifies the
|
|
// values of the entries and table_size variables, and these modifications
|
|
// are bracketed by increments of the verifier variables in the reverse
|
|
// order as they are here read. As long as the verifiers have the same
|
|
// value, then entries and table_size are in a consistent state. This loop
|
|
// should very rarely be executed more than once, since the modification in
|
|
// the other routines is so short.
|
|
do
|
|
{
|
|
//verifier = hfact->verifier1;
|
|
// Compute the index of the entry by taking the handle value modulo the
|
|
// table size. Since the table size is a power of two, we can simply
|
|
// subtract one to produce a mask and then conjoin the mask with the
|
|
// handle value.
|
|
entry_index = handle & hfact->table_size - 1;
|
|
entry = &hfact->entries[entry_index];
|
|
// Get local copies of the reference pointer and handle value. The
|
|
// order of the operations is important to correct multi-threaded
|
|
// operation.
|
|
|
|
if ((entry->reference) && (entry->handle == handle)) {
|
|
|
|
ASSERT(((PCLASSIFICATION_BLOCK)entry->reference)->NumberOfElements > offset);
|
|
reference = (void *)((PCLASSIFICATION_BLOCK)entry->reference)->arpBlobBlock[offset];
|
|
TRACE(CLASSIFY, entry->reference, reference, "dereference_HF_handle_with_cb");
|
|
|
|
return reference;
|
|
|
|
} else {
|
|
|
|
reference = 0;
|
|
return 0;
|
|
|
|
}
|
|
|
|
} while (0 /*verifier != hfact->verifier0*/);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#ifdef _TEST_HANDFACT
|
|
|
|
// This is a test routine that simply verifies the internal valididy of the
|
|
// handle factory's data structures. By defining the constant _TEST_HANDFACT,
|
|
// this routine will be compiled and available to the client code. It can be
|
|
// called at any time, unless running in a multi-threaded environment, in which
|
|
// case the caller must first take the same lock used for assign_HF_handle()
|
|
// and release_HF_handle. If the routine returns any value other than zero,
|
|
// then the internal lists of records are in an inconsistent state.
|
|
//
|
|
int
|
|
verify_HF_lists(
|
|
HandleFactory *hfact)
|
|
{
|
|
int entry_count[3];
|
|
int list;
|
|
HFEntry *entry;
|
|
for (list = 0; list < 3; list++)
|
|
{
|
|
entry_count[list] = 0;
|
|
entry = &hfact->entry_list[list];
|
|
do
|
|
{
|
|
entry_count[list]++;
|
|
if (entry->next_entry->prev_entry != entry)
|
|
{
|
|
return 1;
|
|
}
|
|
entry = entry->next_entry;
|
|
} while (entry != &hfact->entry_list[list]);
|
|
entry_count[list]--;
|
|
}
|
|
if (entry_count[2] != hfact->population)
|
|
{
|
|
return 2;
|
|
}
|
|
if (entry_count[0] + entry_count[2] - 2 * hfact->pair_count !=
|
|
entry_count[1])
|
|
{
|
|
return 3;
|
|
}
|
|
if (entry_count[0] + entry_count[1] + entry_count[2] != hfact->table_size)
|
|
{
|
|
return 4;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
#endif /* _TEST_HANDFACT */
|
|
|
|
// This function doubles the size of the table in which the handles and pointers
|
|
// are stored. It is called by assign_HF_handle() when there is insufficient
|
|
// space in the table to assign the newly requested handle. If the expansion
|
|
// is successful, the function returns a value of 0. If the expansion fails
|
|
// (due, for example, to an inability to allocate memory), the function returns
|
|
// a value of 1.
|
|
//
|
|
int expand_HF_table(
|
|
HandleFactory *hfact)
|
|
{
|
|
int double_size;
|
|
HFEntry *new_entries;
|
|
HFEntry *old_entries;
|
|
HFEntry *old_entry;
|
|
HFEntry *low_entry;
|
|
HFEntry *high_entry;
|
|
HFEntry *assigned_entry;
|
|
HFEntry *secondary_entry;
|
|
HFEntry *other_entry;
|
|
HFHandle handle;
|
|
HFHandle next_handle;
|
|
HFHandle other_handle;
|
|
void *reference;
|
|
int other_entry_index;
|
|
int index;
|
|
|
|
// Expanded table is double the size of the old table.
|
|
double_size = hfact->table_size * 2;
|
|
// Allocate space for the expanded table.
|
|
NEW_HFEntry_array(new_entries,double_size);
|
|
if (new_entries == 0)
|
|
{
|
|
// Memory could not be allocated for the new array of entries.
|
|
// Therefore, we return an indication of failure.
|
|
return 1;
|
|
}
|
|
// Since we are doubling the table size, we will be treating one more bit
|
|
// of each handle as a bit of the entry index. The value of this bit
|
|
// determines the index of the entry in the new table. For each entry,
|
|
// we have to determine the value of this bit and relocate the entry to
|
|
// the indicated location.
|
|
for (index = 0; index < hfact->table_size; index++)
|
|
{
|
|
old_entry = &hfact->entries[index];
|
|
low_entry = &new_entries[index];
|
|
high_entry = &new_entries[hfact->table_size + index];
|
|
handle = old_entry->handle;
|
|
next_handle = old_entry->next_handle;
|
|
reference = old_entry->reference;
|
|
// One of the two entries in the new table that correspond to the
|
|
// indexed entry in the old table will have a next handle value equal
|
|
// to the next handle value of the entry in the old table, and one will
|
|
// have a handle value equal to the indexed entry's next handle plus
|
|
// the old table size.
|
|
other_handle = next_handle + hfact->table_size;
|
|
if (other_handle == 0)
|
|
{
|
|
// The handle value has wrapped around back to zero; however, zero
|
|
// is a reserved value, so we instead set the next handle to the
|
|
// subsequent legal value, which is the new table size.
|
|
other_handle = double_size;
|
|
}
|
|
if ((handle & hfact->table_size) == 0)
|
|
{
|
|
// The handle of the old entry has a zero in its next bit, so the
|
|
// old entry will be located in the lower half of the new table.
|
|
if ((next_handle & hfact->table_size) == 0)
|
|
{
|
|
// The next handle of the old entry has a zero in its next bit,
|
|
// so this value will be the next handle for the lower entry
|
|
// and the other next handle value will be the next handle
|
|
// value for the higher entry. The high entry handle is set
|
|
// equal to its next handle because it is unassigned.
|
|
high_entry->handle = other_handle;
|
|
high_entry->next_handle = other_handle;
|
|
low_entry->next_handle = next_handle;
|
|
}
|
|
else
|
|
{
|
|
// The next handle of the old entry has a zero in its next bit,
|
|
// so this value will be the next handle for the higher entry
|
|
// and the other next handle value will be the next handle
|
|
// value for the lower entry. The high entry handle is set
|
|
// equal to its next handle because it is unassigned.
|
|
high_entry->handle = next_handle;
|
|
high_entry->next_handle = next_handle;
|
|
low_entry->next_handle = other_handle;
|
|
}
|
|
// The high entry is unassigned, so set its reference to null.
|
|
// Copy the information from the old entry to the low entry.
|
|
// Remove the old entry from the assigned list, and replace it
|
|
// with the low entry.
|
|
high_entry->reference = 0;
|
|
low_entry->handle = handle;
|
|
low_entry->reference = reference;
|
|
old_entry->next_entry->prev_entry = low_entry;
|
|
old_entry->prev_entry->next_entry = low_entry;
|
|
low_entry->next_entry = old_entry->next_entry;
|
|
low_entry->prev_entry = old_entry->prev_entry;
|
|
}
|
|
else
|
|
{
|
|
// The handle of the old entry has a one in its next bit, so the
|
|
// old entry will be located in the higher half of the new table.
|
|
if ((next_handle & hfact->table_size) == 0)
|
|
{
|
|
// The next handle of the old entry has a zero in its next bit,
|
|
// so this value will be the next handle for the lower entry
|
|
// and the other next handle value will be the next handle
|
|
// value for the higher entry. The low entry handle is set
|
|
// equal to its next handle because it is unassigned.
|
|
high_entry->next_handle = other_handle;
|
|
low_entry->handle = next_handle;
|
|
low_entry->next_handle = next_handle;
|
|
}
|
|
else
|
|
{
|
|
// The next handle of the old entry has a zero in its next bit,
|
|
// so this value will be the next handle for the higher entry
|
|
// and the other next handle value will be the next handle
|
|
// value for the lower entry. The low entry handle is set
|
|
// equal to its next handle because it is unassigned.
|
|
high_entry->next_handle = next_handle;
|
|
low_entry->handle = other_handle;
|
|
low_entry->next_handle = other_handle;
|
|
}
|
|
// The low entry is unassigned, so set its reference to null.
|
|
// Copy the information from the old entry to the high entry.
|
|
// Remove the old entry from the assigned list, and replace it
|
|
// with the high entry.
|
|
low_entry->reference = 0;
|
|
high_entry->handle = handle;
|
|
high_entry->reference = reference;
|
|
old_entry->next_entry->prev_entry = high_entry;
|
|
old_entry->prev_entry->next_entry = high_entry;
|
|
high_entry->next_entry = old_entry->next_entry;
|
|
high_entry->prev_entry = old_entry->prev_entry;
|
|
}
|
|
}
|
|
// All of the unassigned entries in the new table will be placed on the
|
|
// secondary list. We loop through the assigned list and place the
|
|
// unassigned entry corresponding each assigned entry onto the secondary
|
|
// list. Doing the list assignment in this manner tends to approximately
|
|
// sort the secondary list according to handle value, since the assigned
|
|
// list is sorted according to assignment order, and this approximately
|
|
// correlates to the handle value.
|
|
assigned_entry = hfact->entry_list[LD_ASSIGNED].next_entry;
|
|
secondary_entry = &hfact->entry_list[LD_SECONDARY];
|
|
while (assigned_entry != &hfact->entry_list[LD_ASSIGNED])
|
|
{
|
|
other_entry_index =
|
|
assigned_entry->handle + hfact->table_size & double_size - 1;
|
|
other_entry = &new_entries[other_entry_index];
|
|
secondary_entry->next_entry = other_entry;
|
|
other_entry->prev_entry = secondary_entry;
|
|
secondary_entry = other_entry;
|
|
assigned_entry = assigned_entry->next_entry;
|
|
}
|
|
// Wrap up lists by connecting in tails.
|
|
secondary_entry->next_entry = &hfact->entry_list[LD_SECONDARY];
|
|
hfact->entry_list[LD_SECONDARY].prev_entry = secondary_entry;
|
|
// This expansion increases the hysteresis debt by the cost of one set of
|
|
// allocation and deallocation operations plus the cost of splitting each
|
|
// entry into two entries.
|
|
hfact->hysteresis_debt += ALLOCATION_COST + hfact->table_size;
|
|
// Save a pointer to the old entry table so that it can be deallocated.
|
|
old_entries = hfact->entries;
|
|
// Note that we have not modified the handle, next_handle, or reference
|
|
// fields of any entries in the old table. Therefore, any calls to the
|
|
// dereference_HF_handle() routine that may have been made by other threads
|
|
// during the above operations would have been performed successfully.
|
|
// We are now about to increase the table size and update the entries
|
|
// variable to point to the new table. These operations must be performed
|
|
// atomically in order for the dereference routine to perform correctly.
|
|
// We thus bracket the operations with increments to the verifier variables.
|
|
// When the verifiers have the same value, the table_size and entries
|
|
// variables are in a consistent state. This is checked by the dereference
|
|
// routine.
|
|
hfact->verifier0++; // begin critical section
|
|
hfact->entries = new_entries;
|
|
hfact->table_size = double_size;
|
|
hfact->verifier1 = hfact->verifier0; // end critical section
|
|
// Deallocate the old table.
|
|
GpcFreeMem(old_entries, handleFactoryTag);
|
|
// Since the new table was created by expanding a half-size table, the pair
|
|
// count must be zero.
|
|
hfact->pair_count = 0;
|
|
// return an indication of success.
|
|
return 0;
|
|
}
|
|
|
|
// This function halves the size of the table in which the handles and pointers
|
|
// are stored. In order to reduce the amount of space consumed by the handle
|
|
// factory, this function is called called by release_HF_handle() and
|
|
// revoke_ancient_HF_handles() when they determine that the table can and should
|
|
// be contracted. The table can be contracted when pair_count == 0 and
|
|
// table_size > 2. However, the table may not be contracted then, because
|
|
// hysteresis is employed both to keep the mean assignment and release times
|
|
// constant and to minimize the allocation chatter of rapidly expanding and
|
|
// contracting the table. If the contraction is successful, the function
|
|
// returns a value of 0. If the contraction fails, the function returns a
|
|
// value of 1.
|
|
//
|
|
int contract_HF_table(
|
|
HandleFactory *hfact)
|
|
{
|
|
HFEntry *new_entries;
|
|
HFEntry *old_entries;
|
|
int *list;
|
|
int half_size;
|
|
int quarter_size;
|
|
int index;
|
|
HFEntry *high_entry1;
|
|
HFEntry *high_entry0;
|
|
HFEntry *low_entry1;
|
|
HFEntry *low_entry0;
|
|
HFEntry *new_entry1;
|
|
HFEntry *new_entry0;
|
|
HFHandle adjusted_high_next_handle1;
|
|
HFHandle adjusted_low_next_handle1;
|
|
HFHandle next_handle1;
|
|
HFHandle adjusted_high_next_handle0;
|
|
HFHandle adjusted_low_next_handle0;
|
|
HFHandle next_handle0;
|
|
HFHandle adjusted_new_handle0;
|
|
HFHandle adjusted_new_handle1;
|
|
HFEntry *entry;
|
|
HFEntry *primary_entry;
|
|
HFEntry *secondary_entry;
|
|
|
|
// Contracted table is half the size of the old table.
|
|
half_size = hfact->table_size / 2;
|
|
quarter_size = half_size / 2;
|
|
// Allocate space for the contracted table.
|
|
NEW_HFEntry_array(new_entries,half_size);
|
|
if (new_entries == 0)
|
|
{
|
|
// Memory could not be allocated for the new array of entries, so we
|
|
// are ironically prevented from reducing the amount of memory that
|
|
// the handle factory is consuming. Therefore, we return an indication
|
|
// of failure.
|
|
return 1;
|
|
}
|
|
// Allocate space for auxiliary array of list indicators
|
|
NEW_int_array(list,half_size);
|
|
if (list == 0)
|
|
{
|
|
// Memory could not be allocated for the auxiliary array, so again we
|
|
// are ironically prevented from reducing the amount of memory that
|
|
// the handle factory is consuming. Therefore, we return an indication
|
|
// of failure. First, however, we must free the memory allocated for
|
|
// the new array of entries above.
|
|
GpcFreeMem(new_entries, handleFactoryTag);
|
|
return 1;
|
|
}
|
|
// Since we are halving the size of the table, it might seem reasonable to
|
|
// loop through each index of the new table and merge the two corresponding
|
|
// entries from the old table. This is in fact what the following routine
|
|
// does; however, it does it by looping through only half of the new indices
|
|
// and processing two merges for each index. It does this so that it can
|
|
// then examine the two new entries to determine on which list to place each
|
|
// of them.
|
|
for (index = 0; index < quarter_size; index++)
|
|
{
|
|
// We're looking at four entries at once. First we merge high_entry1
|
|
// and low_entry1, and then we independently merge high_entry0 and
|
|
// low_entry0. After the two merges, we examine the results jointly.
|
|
high_entry1 = &hfact->entries[half_size + quarter_size + index];
|
|
high_entry0 = &hfact->entries[half_size + index];
|
|
low_entry1 = &hfact->entries[quarter_size + index];
|
|
low_entry0 = &hfact->entries[index];
|
|
new_entry1 = &new_entries[quarter_size + index];
|
|
new_entry0 = &new_entries[index];
|
|
// When merging two entries, the next handle value for the combined
|
|
// entry is equal to the larger next handle value of the two, minus
|
|
// the new table size. However, the determination of which is larger
|
|
// must be made with respect to their logical acyclic values rather
|
|
// than their actual cyclic values, so we subtract from each the value
|
|
// of handle_base, modulo the size of the handle space. The modulo is
|
|
// implicit.
|
|
adjusted_high_next_handle1 =
|
|
high_entry1->next_handle - hfact->handle_base;
|
|
adjusted_low_next_handle1 =
|
|
low_entry1->next_handle - hfact->handle_base;
|
|
next_handle1 = __max(adjusted_high_next_handle1,
|
|
adjusted_low_next_handle1) + hfact->handle_base - half_size;
|
|
// Since handle 1 is -- by definition -- in either the second or fourth
|
|
// quarter of the table, there is no need to check for the reserved
|
|
// value of zero.
|
|
if (high_entry1->handle != high_entry1->next_handle)
|
|
{
|
|
// The high entry is assigned, so we copy its handle value and
|
|
// reference pointer. Also, we remove it from the assigned list
|
|
// and replace it with the new entry.
|
|
new_entry1->handle = high_entry1->handle;
|
|
new_entry1->reference = high_entry1->reference;
|
|
high_entry1->next_entry->prev_entry = new_entry1;
|
|
high_entry1->prev_entry->next_entry = new_entry1;
|
|
new_entry1->next_entry = high_entry1->next_entry;
|
|
new_entry1->prev_entry = high_entry1->prev_entry;
|
|
}
|
|
else if (low_entry1->handle != low_entry1->next_handle)
|
|
{
|
|
// The low entry is assigned, so we copy its handle value and
|
|
// reference pointer. Also, we remove it from the assigned list
|
|
// and replace it with the new entry.
|
|
new_entry1->handle = low_entry1->handle;
|
|
new_entry1->reference = low_entry1->reference;
|
|
low_entry1->next_entry->prev_entry = new_entry1;
|
|
low_entry1->prev_entry->next_entry = new_entry1;
|
|
new_entry1->next_entry = low_entry1->next_entry;
|
|
new_entry1->prev_entry = low_entry1->prev_entry;
|
|
}
|
|
else
|
|
{
|
|
// Neither entry is assigned, so we indicate an unassigned condition
|
|
// in the new entry.
|
|
new_entry1->handle = next_handle1;
|
|
new_entry1->reference = 0;
|
|
if (adjusted_high_next_handle1 < adjusted_low_next_handle1)
|
|
{
|
|
// The high entry next handle has a lesser value than the low
|
|
// entry next handle, so the high entry must be on the primary
|
|
// list. We remove it from the primary list and replace it
|
|
// with the new entry.
|
|
high_entry1->next_entry->prev_entry = new_entry1;
|
|
high_entry1->prev_entry->next_entry = new_entry1;
|
|
new_entry1->next_entry = high_entry1->next_entry;
|
|
new_entry1->prev_entry = high_entry1->prev_entry;
|
|
}
|
|
else
|
|
{
|
|
// The low entry next handle has a lesser value than the high
|
|
// entry next handle, so the low entry must be on the primary
|
|
// list. We remove it from the primary list and replace it
|
|
// with the new entry.
|
|
low_entry1->next_entry->prev_entry = new_entry1;
|
|
low_entry1->prev_entry->next_entry = new_entry1;
|
|
new_entry1->next_entry = low_entry1->next_entry;
|
|
new_entry1->prev_entry = low_entry1->prev_entry;
|
|
}
|
|
}
|
|
// Set the next handle for the new entry.
|
|
new_entry1->next_handle = next_handle1;
|
|
// When merging two entries, the next handle value for the combined
|
|
// entry is equal to the larger next handle value of the two, minus
|
|
// the new table size. However, the determination of which is larger
|
|
// must be made with respect to their logical acyclic values rather
|
|
// than their actual cyclic values, so we subtract from each the value
|
|
// of handle_base, modulo the size of the handle space. The modulo is
|
|
// implicit.
|
|
adjusted_high_next_handle0 =
|
|
high_entry0->next_handle - hfact->handle_base;
|
|
adjusted_low_next_handle0 =
|
|
low_entry0->next_handle - hfact->handle_base;
|
|
next_handle0 = __max(adjusted_high_next_handle0,
|
|
adjusted_low_next_handle0) + hfact->handle_base - half_size;
|
|
if (next_handle0 == 0)
|
|
{
|
|
// The handle value has wrapped around back to zero; however, zero
|
|
// is a reserved value, so we instead set the next handle to the
|
|
// subsequent legal value, which is the new table size.
|
|
next_handle0 = half_size;
|
|
}
|
|
if (high_entry0->handle != high_entry0->next_handle)
|
|
{
|
|
// The high entry is assigned, so we copy its handle value and
|
|
// reference pointer. Also, we remove it from the assigned list
|
|
// and replace it with the new entry.
|
|
new_entry0->handle = high_entry0->handle;
|
|
new_entry0->reference = high_entry0->reference;
|
|
high_entry0->next_entry->prev_entry = new_entry0;
|
|
high_entry0->prev_entry->next_entry = new_entry0;
|
|
new_entry0->next_entry = high_entry0->next_entry;
|
|
new_entry0->prev_entry = high_entry0->prev_entry;
|
|
}
|
|
else if (low_entry0->handle != low_entry0->next_handle)
|
|
{
|
|
// The low entry is assigned, so we copy its handle value and
|
|
// reference pointer. Also, we remove it from the assigned list
|
|
// and replace it with the new entry.
|
|
new_entry0->handle = low_entry0->handle;
|
|
new_entry0->reference = low_entry0->reference;
|
|
low_entry0->next_entry->prev_entry = new_entry0;
|
|
low_entry0->prev_entry->next_entry = new_entry0;
|
|
new_entry0->next_entry = low_entry0->next_entry;
|
|
new_entry0->prev_entry = low_entry0->prev_entry;
|
|
}
|
|
else
|
|
{
|
|
// Neither entry is assigned, so we indicate an unassigned condition
|
|
// in the new entry.
|
|
new_entry0->handle = next_handle0;
|
|
new_entry0->reference = 0;
|
|
if (adjusted_high_next_handle0 < adjusted_low_next_handle0)
|
|
{
|
|
// The high entry next handle has a lesser value than the low
|
|
// entry next handle, so the high entry must be on the primary
|
|
// list. We remove it from the primary list and replace it
|
|
// with the new entry.
|
|
high_entry0->next_entry->prev_entry = new_entry0;
|
|
high_entry0->prev_entry->next_entry = new_entry0;
|
|
new_entry0->next_entry = high_entry0->next_entry;
|
|
new_entry0->prev_entry = high_entry0->prev_entry;
|
|
}
|
|
else
|
|
{
|
|
// The low entry next handle has a lesser value than the high
|
|
// entry next handle, so the low entry must be on the primary
|
|
// list. We remove it from the primary list and replace it
|
|
// with the new entry.
|
|
low_entry0->next_entry->prev_entry = new_entry0;
|
|
low_entry0->prev_entry->next_entry = new_entry0;
|
|
new_entry0->next_entry = low_entry0->next_entry;
|
|
new_entry0->prev_entry = low_entry0->prev_entry;
|
|
}
|
|
}
|
|
// Set the next handle for the new entry.
|
|
new_entry0->next_handle = next_handle0;
|
|
// Now that we have merged high_entry1 and low_entry1 into new_entry1,
|
|
// and independently merged high_entry0 and low_entry0 into new_entry0,
|
|
// we examine the two new entries to determine on which list to place
|
|
// each of them. Note that we do not actually manipulate the lists in
|
|
// this portion of the code; we merely make decisions and record these
|
|
// decisions for the future.
|
|
if (new_entry0->handle == new_entry0->next_handle &&
|
|
new_entry1->handle == new_entry1->next_handle)
|
|
{
|
|
// Both new_entry0 and new_entry1 are unassigned, so one of them
|
|
// belongs on the primary list and the other on the secondary list.
|
|
// Which goes on which is determined by a comparison of their handle
|
|
// values. We're being tricky with unsigned integer math here.
|
|
// Before comparing the two handles, we subtract from each the value
|
|
// of handle_base, modulo the size of the handle space (the modulo
|
|
// is implicit). This allows the effective comparison of their
|
|
// logical acyclic values rather than their actual cyclic values.
|
|
adjusted_new_handle0 = new_entry0->handle - hfact->handle_base;
|
|
adjusted_new_handle1 = new_entry1->handle - hfact->handle_base;
|
|
if (adjusted_new_handle0 < adjusted_new_handle1)
|
|
{
|
|
// The handle value for new_entry0 is lower, so new_entry0
|
|
// belongs on the primary list and new_entry1 on the secondary
|
|
// list. We indicate this decision in the list array.
|
|
list[index] = LD_PRIMARY;
|
|
list[quarter_size + index] = LD_SECONDARY;
|
|
}
|
|
else
|
|
{
|
|
// The handle value for new_entry1 is lower, so new_entry1
|
|
// belongs on the primary list and new_entry0 on the secondary
|
|
// list. We indicate this decision in the list array.
|
|
list[index] = LD_SECONDARY;
|
|
list[quarter_size + index] = LD_PRIMARY;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Either new_entry0 or new_entry1 (or both) is assigned, and it is
|
|
// therefore already on the assigned list. If one of the entries
|
|
// is not assigned, it belongs on the secondary list. We indicate
|
|
// this decision in both places of the list array, which is safe to
|
|
// do since the assigned entry's list indicator will never be
|
|
// examined.
|
|
list[index] = LD_SECONDARY;
|
|
list[quarter_size + index] = LD_SECONDARY;
|
|
}
|
|
if (new_entry0->handle != new_entry0->next_handle &&
|
|
new_entry1->handle != new_entry1->next_handle)
|
|
{
|
|
// Both new_entry0 and new_entry1 are assigned, so they form a pair.
|
|
// We thus increment the pair count. Note that we never set the
|
|
// pair count to zero above, but this was not necessary since the
|
|
// table could not be contracted unless the pair count was zero.
|
|
hfact->pair_count++;
|
|
}
|
|
}
|
|
// At this point, the table has been completely contracted except for the
|
|
// reassembly of the unassigned lists. In the code above, any entries that
|
|
// had previously been on the secondary list were merged with assigned
|
|
// entries, so they are no longer relevant. Only those entries that had
|
|
// previously been (and are still) on the primary list will still be
|
|
// unassigned. We now loop through the primary list and place each list
|
|
// element on the appropriate list, as indicated by the list array. Doing
|
|
// the list assignment in these two steps preserves the general order of
|
|
// the entries, which has some value since they will tend to be partially
|
|
// sorted.
|
|
entry = hfact->entry_list[LD_PRIMARY].next_entry;
|
|
primary_entry = &hfact->entry_list[LD_PRIMARY];
|
|
secondary_entry = &hfact->entry_list[LD_SECONDARY];
|
|
while (entry != &hfact->entry_list[LD_PRIMARY])
|
|
{
|
|
if (list[entry->handle & half_size - 1] == LD_PRIMARY)
|
|
{
|
|
// The list array indicates the primary list, so place the entry
|
|
// onto the primary list.
|
|
primary_entry->next_entry = entry;
|
|
entry->prev_entry = primary_entry;
|
|
primary_entry = entry;
|
|
}
|
|
else
|
|
{
|
|
// The list array indicates the secondary list, so place the entry
|
|
// onto the secondary list.
|
|
secondary_entry->next_entry = entry;
|
|
entry->prev_entry = secondary_entry;
|
|
secondary_entry = entry;
|
|
}
|
|
entry = entry->next_entry;
|
|
}
|
|
// Wrap up lists by connecting in tails.
|
|
primary_entry->next_entry = &hfact->entry_list[LD_PRIMARY];
|
|
hfact->entry_list[LD_PRIMARY].prev_entry = primary_entry;
|
|
secondary_entry->next_entry = &hfact->entry_list[LD_SECONDARY];
|
|
hfact->entry_list[LD_SECONDARY].prev_entry = secondary_entry;
|
|
// This contraction increases the hysteresis debt by the cost of one set of
|
|
// allocation and deallocation operations plus the cost of merging each
|
|
// pair of entries into a single entry.
|
|
hfact->hysteresis_debt += ALLOCATION_COST + half_size;
|
|
// Save a pointer to the old entry table so that it can be deallocated.
|
|
old_entries = hfact->entries;
|
|
// Note that we have not modified the handle, next_handle, or reference
|
|
// fields of any entries in the old table. Therefore, any calls to the
|
|
// dereference_HF_handle() routine that may have been made by other threads
|
|
// during the above operations would have been performed successfully.
|
|
// We are now about to decrease the table size and update the entries
|
|
// variable to point to the new table. These operations must be performed
|
|
// atomically in order for the dereference routine to perform correctly.
|
|
// We thus bracket the operations with increments to the verifier variables.
|
|
// When the verifiers have the same value, the table_size and entries
|
|
// variables are in a consistent state. This is checked by the dereference
|
|
// routine.
|
|
hfact->verifier0++; // begin critical section
|
|
hfact->table_size = half_size;
|
|
hfact->entries = new_entries;
|
|
hfact->verifier1 = hfact->verifier0; // end critical section
|
|
|
|
// Deallocate the old table and the auxiliary list indicator array.
|
|
GpcFreeMem(old_entries, handleFactoryTag);
|
|
GpcFreeMem(list, handleFactoryTag);
|
|
// return an indication of success.
|
|
return 0;
|
|
}
|
|
|
|
// This function revokes handles that are between handle_base and handle_base
|
|
// + 2 * HANDLE_RANGE_STEP - 1, inclusive. It then increments the value of
|
|
// handle_base by HANDLE_RANGE_STEP. Suspended handles will be revoked one
|
|
// revokation pass later than non-suspended handles.
|
|
//
|
|
void revoke_ancient_HF_handles(
|
|
HandleFactory *hfact)
|
|
{
|
|
HFHandle new_handle_base;
|
|
int half_size;
|
|
int index;
|
|
HFEntry *high_entry;
|
|
HFEntry *low_entry;
|
|
HFHandle adjusted_high_handle;
|
|
HFHandle adjusted_low_handle;
|
|
HFHandle adjusted_high_next_handle;
|
|
HFHandle adjusted_low_next_handle;
|
|
HFHandle handle;
|
|
volatile HFEntry *seq_entry; // volatile to ensure sequencing
|
|
|
|
// Compute new handle base.
|
|
new_handle_base = hfact->handle_base + HANDLE_RANGE_STEP;
|
|
// It might seem reasonable to loop through each index of the table and
|
|
// determine whether to revoke the handle of each entry. This is in fact
|
|
// what the following routine does; however, it does it by looping through
|
|
// only half of the indices and examining two entries for each index. It
|
|
// does this so that it can compare the two entries to determine on which
|
|
// list to place each of them.
|
|
half_size = hfact->table_size / 2;
|
|
for (index = 0; index < half_size; index++)
|
|
{
|
|
// We're looking at two entries at once.
|
|
high_entry = &hfact->entries[half_size + index];
|
|
low_entry = &hfact->entries[index];
|
|
// We're being tricky with unsigned integer math here. Before making
|
|
// comparisons on either handle, we subtract from it the value of
|
|
// handle_base, modulo the size of the handle space (the modulo is
|
|
// implicit). This allows the effective comparison of its logical
|
|
// acyclic value rather than its actual cyclic value.
|
|
adjusted_high_handle = high_entry->handle - hfact->handle_base;
|
|
adjusted_low_handle = low_entry->handle - hfact->handle_base;
|
|
if (adjusted_high_handle < 2 * HANDLE_RANGE_STEP ||
|
|
adjusted_low_handle < 2 * HANDLE_RANGE_STEP)
|
|
{
|
|
// At least one of the handles is less than twice HANDLE_RANGE_STEP
|
|
// more than the current handle base, so it will need to be updated.
|
|
// For the vast majority of cases, this test is expected to fail,
|
|
// and so all of the following work can be skipped.
|
|
if (high_entry->handle != high_entry->next_handle &&
|
|
low_entry->handle != low_entry->next_handle)
|
|
{
|
|
// Both of the entries are assigned, so, since at least one of
|
|
// them will be revoked, we will be losing one pair.
|
|
hfact->pair_count--;
|
|
}
|
|
if (high_entry->handle == high_entry->next_handle ||
|
|
adjusted_high_handle < 2 * HANDLE_RANGE_STEP)
|
|
{
|
|
// Either the high entry is unassigned or in need of revokation
|
|
// (after which it will be unassigned), so we remove it from
|
|
// whatever list it is on. We do this because all unassigned
|
|
// entries will be added to the appropriate list below.
|
|
high_entry->next_entry->prev_entry = high_entry->prev_entry;
|
|
high_entry->prev_entry->next_entry = high_entry->next_entry;
|
|
// Zeroing these pointers is unnecessary, but it will help to
|
|
// catch any mistakes made further down.
|
|
high_entry->next_entry = 0;
|
|
high_entry->prev_entry = 0;
|
|
}
|
|
if (adjusted_high_handle < 2 * HANDLE_RANGE_STEP)
|
|
{
|
|
// The high handle needs to be updated.
|
|
if (high_entry->handle != high_entry->next_handle)
|
|
{
|
|
// The high handle is assigned, so this updating will
|
|
// revoke the handle. Thus, we decrement the population.
|
|
hfact->population--;
|
|
}
|
|
// Compute the handle value as the maximum of (1) the next
|
|
// handle and (2) the new handle base plus the entry index.
|
|
// We're being tricky with unsigned integer math here. The
|
|
// maximum involves partial decomposition of the sums, from
|
|
// which we then subtract the value of handle_base, modulo the
|
|
// size of the handle space (the modulo is implicit). Thus,
|
|
// the maximum is taken with respect to the logical acyclic
|
|
// values rather than the actual cyclic values.
|
|
adjusted_high_next_handle =
|
|
high_entry->next_handle - hfact->handle_base;
|
|
handle = __max(adjusted_high_next_handle,
|
|
HANDLE_RANGE_STEP + half_size + index) + hfact->handle_base;
|
|
// Since the high handle is -- by definition -- in the upper
|
|
// half of the table, there is no need to check for the reserved
|
|
// value of zero.
|
|
// Update the handle value. Since this updating will invalidate
|
|
// the handle if it is currently assigned, the order of the
|
|
// operations is important to correct multi-threaded operation.
|
|
seq_entry = high_entry;
|
|
seq_entry->next_handle = handle;
|
|
seq_entry->handle = handle; // first invalidate handle
|
|
seq_entry->reference = 0; // then clear reference
|
|
}
|
|
if (low_entry->handle == low_entry->next_handle ||
|
|
adjusted_low_handle < 2 * HANDLE_RANGE_STEP)
|
|
{
|
|
// Either the low entry is unassigned or in need of revokation
|
|
// (after which it will be unassigned), so we remove it from
|
|
// whatever list it is on. We do this because all unassigned
|
|
// entries will be added to the appropriate list below.
|
|
low_entry->next_entry->prev_entry = low_entry->prev_entry;
|
|
low_entry->prev_entry->next_entry = low_entry->next_entry;
|
|
// Zeroing these pointers is unnecessary, but it will help to
|
|
// catch any mistakes made further down.
|
|
low_entry->next_entry = 0;
|
|
low_entry->prev_entry = 0;
|
|
}
|
|
if (adjusted_low_handle < 2 * HANDLE_RANGE_STEP)
|
|
{
|
|
// The low handle needs to be updated.
|
|
if (low_entry->handle != low_entry->next_handle)
|
|
{
|
|
// The low handle is assigned, so this updating will
|
|
// revoke the handle. Thus, we decrement the population.
|
|
hfact->population--;
|
|
}
|
|
// Compute the handle value as the maximum of (1) the next
|
|
// handle and (2) the new handle base plus the entry index.
|
|
// We're being tricky with unsigned integer math here. The
|
|
// maximum involves partial decomposition of the sums, from
|
|
// which we then subtract the value of handle_base, modulo the
|
|
// size of the handle space (the modulo is implicit). Thus,
|
|
// the maximum is taken with respect to the logical acyclic
|
|
// values rather than the actual cyclic values.
|
|
adjusted_low_next_handle =
|
|
low_entry->next_handle - hfact->handle_base;
|
|
handle = __max(adjusted_low_next_handle,
|
|
HANDLE_RANGE_STEP + index) + hfact->handle_base;
|
|
if (handle == 0)
|
|
{
|
|
// The handle value has wrapped around back to zero;
|
|
// however, zero is a reserved value, so we instead set the
|
|
// handle to the subsequent legal value, which is the table
|
|
// size.
|
|
handle = hfact->table_size;
|
|
}
|
|
// Update the handle value. Since this updating will invalidate
|
|
// the handle if it is currently assigned, the order of the
|
|
// operations is important to correct multi-threaded operation.
|
|
seq_entry = low_entry;
|
|
seq_entry->next_handle = handle;
|
|
seq_entry->handle = handle; // first invalidate handle
|
|
seq_entry->reference = 0; // then clear reference
|
|
}
|
|
if (high_entry->handle != high_entry->next_handle)
|
|
{
|
|
// The high entry is still assigned, so the low entry belongs
|
|
// on the secondary list.
|
|
low_entry->next_entry = &hfact->entry_list[LD_SECONDARY];
|
|
low_entry->prev_entry =
|
|
hfact->entry_list[LD_SECONDARY].prev_entry;
|
|
hfact->entry_list[LD_SECONDARY].prev_entry->next_entry =
|
|
low_entry;
|
|
hfact->entry_list[LD_SECONDARY].prev_entry = low_entry;
|
|
}
|
|
else if (low_entry->handle != low_entry->next_handle)
|
|
{
|
|
// The low entry is still assigned, so the high entry belongs
|
|
// on the secondary list.
|
|
high_entry->next_entry = &hfact->entry_list[LD_SECONDARY];
|
|
high_entry->prev_entry =
|
|
hfact->entry_list[LD_SECONDARY].prev_entry;
|
|
hfact->entry_list[LD_SECONDARY].prev_entry->next_entry =
|
|
high_entry;
|
|
hfact->entry_list[LD_SECONDARY].prev_entry = high_entry;
|
|
}
|
|
else
|
|
{
|
|
// Neither entry is still assigned, so one entry belongs on the
|
|
// primary list and one on the secondary list. Which goes on
|
|
// which is determined by a comparison of their handle values.
|
|
// We're being tricky with unsigned integer math here. Before
|
|
// comparing the two handles, we subtract from each the value
|
|
// of handle_base, modulo the size of the handle space (the
|
|
// modulo is implicit). This allows the effective comparison
|
|
// of their logical acyclic values rather than their actual
|
|
// cyclic values.
|
|
adjusted_high_next_handle =
|
|
high_entry->next_handle - new_handle_base;
|
|
adjusted_low_next_handle =
|
|
low_entry->next_handle - new_handle_base;
|
|
if (adjusted_low_next_handle < adjusted_high_next_handle)
|
|
{
|
|
// The handle value for the low entry is smaller, so it
|
|
// belongs on the primary list and the high entry on the
|
|
// secondary list.
|
|
high_entry->next_entry = &hfact->entry_list[LD_SECONDARY];
|
|
high_entry->prev_entry =
|
|
hfact->entry_list[LD_SECONDARY].prev_entry;
|
|
hfact->entry_list[LD_SECONDARY].prev_entry->next_entry =
|
|
high_entry;
|
|
hfact->entry_list[LD_SECONDARY].prev_entry = high_entry;
|
|
low_entry->next_entry = &hfact->entry_list[LD_PRIMARY];
|
|
low_entry->prev_entry =
|
|
hfact->entry_list[LD_PRIMARY].prev_entry;
|
|
hfact->entry_list[LD_PRIMARY].prev_entry->next_entry =
|
|
low_entry;
|
|
hfact->entry_list[LD_PRIMARY].prev_entry = low_entry;
|
|
}
|
|
else
|
|
{
|
|
// The handle value for the high entry is smaller, so it
|
|
// belongs on the primary list and the low entry on the
|
|
// secondary list.
|
|
high_entry->next_entry = &hfact->entry_list[LD_PRIMARY];
|
|
high_entry->prev_entry =
|
|
hfact->entry_list[LD_PRIMARY].prev_entry;
|
|
hfact->entry_list[LD_PRIMARY].prev_entry->next_entry =
|
|
high_entry;
|
|
hfact->entry_list[LD_PRIMARY].prev_entry = high_entry;
|
|
low_entry->next_entry = &hfact->entry_list[LD_SECONDARY];
|
|
low_entry->prev_entry =
|
|
hfact->entry_list[LD_SECONDARY].prev_entry;
|
|
hfact->entry_list[LD_SECONDARY].prev_entry->next_entry =
|
|
low_entry;
|
|
hfact->entry_list[LD_SECONDARY].prev_entry = low_entry;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
// Update the handle base with the new handle base.
|
|
hfact->handle_base = new_handle_base;
|
|
// To contract the table, there must be no pairs, because otherwise two
|
|
// assigned handles would yield the same entry index and thereby conflict.
|
|
// Furthermore, the table size must be greater than 2, because much of the
|
|
// handle factory code assumes that the table is at least of size 2. In
|
|
// addition to these strict requirements, hysteresis is employed both to
|
|
// keep the mean assignment and release times constant and to minimize the
|
|
// allocation chatter of rapidly expanding and contracting the table. Only
|
|
// if the hysteresis debt is zero will the table be contracted.
|
|
if (hfact->pair_count == 0 && hfact->table_size > 2 &&
|
|
hfact->hysteresis_debt == 0)
|
|
{
|
|
contract_HF_table(hfact);
|
|
// Note that we ignore the return code. If the contraction is
|
|
// unsuccessful, we just continue as usual. There is no real harm in
|
|
// not contracting the table, except that we consume more space than
|
|
// necessary.
|
|
}
|
|
}
|