/* Generated by rbhash.cpppp using command cpppp -p 'max_bits=16' \ -p 'namespace=pollfd_rbhash' \ -p '@default_search_args=('\''int search_fd'\'')' \ -p 'default_search_cmp=search_fd - ((struct pollfd*)rbhash)[(int)node_id-1-(int)capacity].fd' \ rbhash.cpppp \ -o 'public=pollfd_rbhash.h' \ -o pollfd_rbhash.c */ #include "pollfd_rbhash.h" // A setting that disables all the runtime sanity checks and safeguards #ifndef POLLFD_RBHASH_RUN_WITH_SCISSORS #define POLLFD_RBHASH_RUN_WITH_SCISSORS 0 #endif #ifndef POLLFD_RBHASH_ASSERT /* The assertions of this library are fairly important since so much of the * implementation is exposed to the rest of the program, so only actually * remove the checks if RUN_WITH_SCISSORS is set. */ #if POLLFD_RBHASH_RUN_WITH_SCISSORS #define POLLFD_RBHASH_ASSERT(x) (void)0 #elif defined(NDEBUG) #define POLLFD_RBHASH_ASSERT(x) if (!(x)) return 0 #else #define POLLFD_RBHASH_ASSERT(x) assert(x) #endif #endif void pollfd_rbhash_path_8_init(struct pollfd_rbhash_path_8 *p); void pollfd_rbhash_path_16_init(struct pollfd_rbhash_path_16 *p); /* Only called when capacity is out of bounds. Used by the inline bit-selectors. */ extern size_t pollfd_rbhash_capacity_bounds_assertion(size_t capacity) { POLLFD_RBHASH_ASSERT(capacity <= POLLFD_RBHASH_MAX_ELEMENTS_16); return 0; } /* Find a node in the hash table, or tree. Returns the node_id, or 0 if no * nodes match. * * This is a simplified version of find_path that doesn't keep track of the * path through the tree, saving time but not facilitating inserts or deletes. */ size_t pollfd_rbhash_find( void *rbhash, size_t capacity, size_t bucket_idx, int search_fd ) { size_t node_id= 0; int cmp; if (capacity <= POLLFD_RBHASH_MAX_ELEMENTS_8) { node_id= ((uint8_t *)rbhash)[ POLLFD_RBHASH_TABLE_WORD_OFS(capacity) + bucket_idx ] >> 1; while (node_id && (cmp= search_fd - ((struct pollfd*)rbhash)[(int)node_id-1-(int)capacity].fd)) node_id= ((uint8_t *)rbhash)[ (node_id<<1) | (cmp < 0? 0 : 1) ] >> 1; } else if (capacity <= POLLFD_RBHASH_MAX_ELEMENTS_16) { node_id= ((uint16_t *)rbhash)[ POLLFD_RBHASH_TABLE_WORD_OFS(capacity) + bucket_idx ] >> 1; while (node_id && (cmp= search_fd - ((struct pollfd*)rbhash)[(int)node_id-1-(int)capacity].fd)) node_id= ((uint16_t *)rbhash)[ (node_id<<1) | (cmp < 0? 0 : 1) ] >> 1; } else { POLLFD_RBHASH_ASSERT(capacity <= POLLFD_RBHASH_MAX_ELEMENTS_16); } return node_id; } /* Find a node in the hash table, and record the path to arrive at the node * or the node pointer where it would exist. The path can be used for * inserting or deleting without re-comparing any elements. * * The path should already have been initialized using `pollfd_rbhash_path_init`. */ size_t pollfd_rbhash_find_path( void *rbhash, size_t capacity, pollfd_rbhash_path *path, size_t bucket_idx, int search_fd ) { size_t ref, node_id= 0; int cmp, p_i= 0, p_lim= path->lim; path->len= 0; // in case POLLFD_RBHASH_ASSERT calls 'return 0' POLLFD_RBHASH_ASSERT(p_lim > 0); if (capacity <= POLLFD_RBHASH_MAX_ELEMENTS_8) { uint8_t *rbhash_w= (uint8_t*) rbhash; path->refs[0]= POLLFD_RBHASH_TABLE_WORD_OFS(capacity) + bucket_idx; node_id= rbhash_w[ path->refs[0] ] >> 1; while (node_id && (cmp= search_fd - ((struct pollfd*)rbhash)[(int)node_id-1-(int)capacity].fd)) { ref= (node_id<<1) | (cmp < 0? 0 : 1); ++p_i; POLLFD_RBHASH_ASSERT(p_i < p_lim); path->refs[p_i]= ref; node_id= rbhash_w[ref] >> 1; } } else if (capacity <= POLLFD_RBHASH_MAX_ELEMENTS_16) { uint16_t *rbhash_w= (uint16_t*) rbhash; path->refs[0]= POLLFD_RBHASH_TABLE_WORD_OFS(capacity) + bucket_idx; node_id= rbhash_w[ path->refs[0] ] >> 1; while (node_id && (cmp= search_fd - ((struct pollfd*)rbhash)[(int)node_id-1-(int)capacity].fd)) { ref= (node_id<<1) | (cmp < 0? 0 : 1); ++p_i; POLLFD_RBHASH_ASSERT(p_i < p_lim); path->refs[p_i]= ref; node_id= rbhash_w[ref] >> 1; } } else { POLLFD_RBHASH_ASSERT(capacity <= POLLFD_RBHASH_MAX_ELEMENTS_16); } path->len= p_i+1; return node_id; } /* Insert a node into the hashtable, storing collisions in a tree. * If it finds a node with same key, it returns that index and does not insert * the new node, else it will insert and return your 'new_node' value. * If it returns node 0, you have a corrupted data structure. */ extern size_t pollfd_rbhash_insert( void *rbhash, size_t capacity, size_t at_node_id, size_t bucket_idx, int search_fd ) { size_t node_id= 0, ref= POLLFD_RBHASH_TABLE_WORD_OFS(capacity) + bucket_idx; int cmp, p_i= 0, p_lim; if (capacity <= POLLFD_RBHASH_MAX_ELEMENTS_8) { uint8_t *rbhash_w= (uint8_t*) rbhash; node_id= rbhash_w[ref] >> 1; if (!node_id) { rbhash_w[ref]= at_node_id << 1; return at_node_id; } else { struct pollfd_rbhash_path_8 path; pollfd_rbhash_path_8_init(&path); p_lim= path.lim; path.refs[0]= ref; do { if (!(cmp= search_fd - ((struct pollfd*)rbhash)[(int)node_id-1-(int)capacity].fd)) return node_id; ref= (node_id<<1) | (cmp < 0? 0 : 1); ++p_i; POLLFD_RBHASH_ASSERT(p_i < p_lim); path.refs[p_i]= ref; node_id= rbhash_w[ref] >> 1; } while (node_id); node_id= at_node_id; // Handle simple case of adding to black parent without invoking balance. if (!(rbhash_w[path.refs[p_i-1]] & 1)) { rbhash_w[ref]= (node_id << 1) | 1; return node_id; } path.len= p_i+1; return pollfd_rbhash_path_insert_8(rbhash_w, (pollfd_rbhash_path*) &path, node_id); } } else if (capacity <= POLLFD_RBHASH_MAX_ELEMENTS_16) { uint16_t *rbhash_w= (uint16_t*) rbhash; node_id= rbhash_w[ref] >> 1; if (!node_id) { rbhash_w[ref]= at_node_id << 1; return at_node_id; } else { struct pollfd_rbhash_path_16 path; pollfd_rbhash_path_16_init(&path); p_lim= path.lim; path.refs[0]= ref; do { if (!(cmp= search_fd - ((struct pollfd*)rbhash)[(int)node_id-1-(int)capacity].fd)) return node_id; ref= (node_id<<1) | (cmp < 0? 0 : 1); ++p_i; POLLFD_RBHASH_ASSERT(p_i < p_lim); path.refs[p_i]= ref; node_id= rbhash_w[ref] >> 1; } while (node_id); node_id= at_node_id; // Handle simple case of adding to black parent without invoking balance. if (!(rbhash_w[path.refs[p_i-1]] & 1)) { rbhash_w[ref]= (node_id << 1) | 1; return node_id; } path.len= p_i+1; return pollfd_rbhash_path_insert_16(rbhash_w, (pollfd_rbhash_path*) &path, node_id); } } POLLFD_RBHASH_ASSERT(capacity <= POLLFD_RBHASH_MAX_ELEMENTS_16); return 0; } /* Find and delete a node in the hashtable. If found, this returns the node_id * that was removed. If not found (or if the data structure is currupt) this * returns 0. It may also return 0 if an assertion fails and you have disabled * aborting on assertions. */ extern size_t pollfd_rbhash_delete( void *rbhash, size_t capacity, size_t bucket_idx, int search_fd ) { size_t cur= 0, ref= POLLFD_RBHASH_TABLE_WORD_OFS(capacity) + bucket_idx; int cmp, p_i= 0, p_lim; if (capacity <= POLLFD_RBHASH_MAX_ELEMENTS_8) { uint8_t *rbhash_w= (uint8_t*) rbhash; if ((cur= rbhash_w[ref])) { struct pollfd_rbhash_path_8 path; pollfd_rbhash_path_8_init(&path); p_lim= path.lim; path.refs[0]= ref; #define node_id (cur >> 1) while ((cmp= search_fd - ((struct pollfd*)rbhash)[(int)node_id-1-(int)capacity].fd)) { #undef node_id ref= (cur|1) ^ (cmp < 0? 1 : 0); cur= rbhash_w[ref]; if (!cur) return 0; ++p_i; POLLFD_RBHASH_ASSERT(p_i < p_lim); path.refs[p_i]= ref; } path.len= p_i+1; return pollfd_rbhash_path_delete_8(rbhash_w, (pollfd_rbhash_path*) &path); } } else if (capacity <= POLLFD_RBHASH_MAX_ELEMENTS_16) { uint16_t *rbhash_w= (uint16_t*) rbhash; if ((cur= rbhash_w[ref])) { struct pollfd_rbhash_path_16 path; pollfd_rbhash_path_16_init(&path); p_lim= path.lim; path.refs[0]= ref; #define node_id (cur >> 1) while ((cmp= search_fd - ((struct pollfd*)rbhash)[(int)node_id-1-(int)capacity].fd)) { #undef node_id ref= (cur|1) ^ (cmp < 0? 1 : 0); cur= rbhash_w[ref]; if (!cur) return 0; ++p_i; POLLFD_RBHASH_ASSERT(p_i < p_lim); path.refs[p_i]= ref; } path.len= p_i+1; return pollfd_rbhash_path_delete_16(rbhash_w, (pollfd_rbhash_path*) &path); } } POLLFD_RBHASH_ASSERT(capacity <= POLLFD_RBHASH_MAX_ELEMENTS_16); return 0; } /* Given a path to a node, make that path point to a new node assuming that * the new node contains the same search key that the old node used to. * This is used when moving array elements to a new index where the NodeID * would be different. * * This is a O(1) operation, though building the path probably took O(log N). * * Returns the NodeID that the path previously ended with, and the path has * been modified to end with new_node_id and is still valid. May return * 0 if an assertion fails and you have disabled assertions. */ extern size_t pollfd_rbhash_path_swap( void *rbhash, size_t capacity, pollfd_rbhash_path *path, size_t new_node_id ) { size_t ref; /* path must point to a node, and new_node_id must not be the sentinel */ POLLFD_RBHASH_ASSERT(path->len > 0 && new_node_id); if (capacity <= POLLFD_RBHASH_MAX_ELEMENTS_8) { uint8_t *rbhash_w= (uint8_t*) rbhash, prev; // It is an error if new_node_id is not already zeroed POLLFD_RBHASH_ASSERT(((uint16_t*) rbhash)[new_node_id] == 0); // Swap the references ref= path->refs[path->len-1]; prev= rbhash_w[ref]; rbhash_w[ref]= (new_node_id << 1) | (prev&1); ((uint16_t*) rbhash)[new_node_id]= ((uint16_t*) rbhash)[prev>>1]; // and clear out the 'prev' before returning it ((uint16_t*) rbhash)[prev>>1]= 0; return prev >> 1; } else if (capacity <= POLLFD_RBHASH_MAX_ELEMENTS_16) { uint16_t *rbhash_w= (uint16_t*) rbhash, prev; // It is an error if new_node_id is not already zeroed POLLFD_RBHASH_ASSERT(((uint32_t*) rbhash)[new_node_id] == 0); // Swap the references ref= path->refs[path->len-1]; prev= rbhash_w[ref]; rbhash_w[ref]= (new_node_id << 1) | (prev&1); ((uint32_t*) rbhash)[new_node_id]= ((uint32_t*) rbhash)[prev>>1]; // and clear out the 'prev' before returning it ((uint32_t*) rbhash)[prev>>1]= 0; return prev >> 1; } POLLFD_RBHASH_ASSERT(capacity <= POLLFD_RBHASH_MAX_ELEMENTS_16); return 0; } /* Insert a node_id at the end of a path which points to the Sentinel. * The final ref will be updated to point to node_id, and then the tree * will be balanced according to the Red/Black algorithm. The path is * destroyed in the process and should not be used after this call. * (the path could be updated during balance rotations, but would add * overhead and users are unlikely to need it afterward anyway) * * Returns the node_id on success, or 0 on an assertion failure if you * disabled assertions. */ extern size_t pollfd_rbhash_path_insert_8( uint8_t *rbhash, pollfd_rbhash_path *path, size_t node_id ) { /* Legend for deciphering crazy bitwise operations below: X_ref - the index within the rbhash array which holds X X= rbhash[X_ref] - an integer of ((node_id << 1) | red) i.e. if pos is like a pointer with embedded color information, pos_ref is like a pointer to that pointer. X & 1 - 1 = red, 0 = black X >> 1 - the node_id X is pointing to If X_ref is from a tree node (true for all path->refs[i > 0]) then the location of the ref also indicates which node it belongs to, by virtue of nodes being located at rbhash[node_id*2]. X_ref >> 1 - the node_id of the parent of X X_ref & 1 - true if X is the right-subtree of its parent X_ref ^ 1 - a ref to the sibling of X X | 1 - the ref to X's node_id's right subtree X >> 1 << 1 - the ref to X's node_id's left subtree (X|1) ^ 1 - same, maybe optimized if (X|1) is already in a register X ^ 1 - a shortcut for one of the above when the color is known */ int p_i= path->len - 1; // Empty paths or paths ending with a non-Sentinel reference are invalid. POLLFD_RBHASH_ASSERT(path->len > 0 && rbhash[path->refs[p_i]] == 0); // Add new_node to the final parent-ref of the path. // If p_i is 0, this will be altering the hash bucket. rbhash[path->refs[p_i--]]= (node_id << 1) | 1; // and make it red // 'pos' will be the parent node of that. while (p_i > 0) { uint8_t pos_ref= path->refs[p_i--]; uint8_t pos= rbhash[pos_ref]; uint8_t parent_ref= path->refs[p_i]; // if current is a black node, no rotations needed if (!(pos & 1)) break; // pos is red, its new child is red, and parent will be black. // if the sibling is also red, we can pull down the color black from the parent // if not, need a rotation. if (!(rbhash[pos_ref^1]&1)) { // Sibling is black, need a rotation // if the imbalanced child (red node) is on the same side as the parent, // need to rotate those lower nodes to the opposite side in preparation // for the rotation. // e.g. if pos_ref is leftward (even) and pos's rightward child (odd) is the red one... uint8_t child_ref= pos ^ (pos_ref&1); uint8_t child= rbhash[child_ref]; if (child&1) { // rotate pos toward [side] so parent's [side] now points to pos's [otherside] // set pos's child-ref to child's [otherside] ref uint8_t near_grandchild_ref= child ^ (child_ref&1); rbhash[child_ref]= rbhash[near_grandchild_ref]; // set child's [side] to pos rbhash[near_grandchild_ref]= pos; pos= child; // keep pos as a red node, soon to become black rbhash[pos_ref]= child; // parent's [side] has not been updated here, but is about to become 'child' child_ref= near_grandchild_ref^1; child= rbhash[child_ref]; } // Now we can rotate toward parent to balance the tree. rbhash[pos_ref]= child; rbhash[child_ref]= pos_ref|1; // = parent, colored red. simplification of ((pos_ref>>1)<<1)|1 rbhash[parent_ref]= pos^1; // also make pos black // rotation finished, exit. break; } rbhash[pos_ref^1] ^= 1; // toggle color of sibling rbhash[pos_ref]= pos^1; // toggle color of pos rbhash[parent_ref] ^= 1; // toggle color of parent // Now pos is black. // Jump twice up the tree so that once again, pos has one red child. p_i--; } // Root of tree is always black if (rbhash[path->refs[0]] & 1) rbhash[path->refs[0]] ^= 1; #if !POLLFD_RBHASH_RUN_WITH_SCISSORS // Path is no longer valid, because rotations may have destroyed it. path->len= 0; #endif return node_id; } extern size_t pollfd_rbhash_path_insert_16( uint16_t *rbhash, pollfd_rbhash_path *path, size_t node_id ) { /* Legend for deciphering crazy bitwise operations below: X_ref - the index within the rbhash array which holds X X= rbhash[X_ref] - an integer of ((node_id << 1) | red) i.e. if pos is like a pointer with embedded color information, pos_ref is like a pointer to that pointer. X & 1 - 1 = red, 0 = black X >> 1 - the node_id X is pointing to If X_ref is from a tree node (true for all path->refs[i > 0]) then the location of the ref also indicates which node it belongs to, by virtue of nodes being located at rbhash[node_id*2]. X_ref >> 1 - the node_id of the parent of X X_ref & 1 - true if X is the right-subtree of its parent X_ref ^ 1 - a ref to the sibling of X X | 1 - the ref to X's node_id's right subtree X >> 1 << 1 - the ref to X's node_id's left subtree (X|1) ^ 1 - same, maybe optimized if (X|1) is already in a register X ^ 1 - a shortcut for one of the above when the color is known */ int p_i= path->len - 1; // Empty paths or paths ending with a non-Sentinel reference are invalid. POLLFD_RBHASH_ASSERT(path->len > 0 && rbhash[path->refs[p_i]] == 0); // Add new_node to the final parent-ref of the path. // If p_i is 0, this will be altering the hash bucket. rbhash[path->refs[p_i--]]= (node_id << 1) | 1; // and make it red // 'pos' will be the parent node of that. while (p_i > 0) { uint16_t pos_ref= path->refs[p_i--]; uint16_t pos= rbhash[pos_ref]; uint16_t parent_ref= path->refs[p_i]; // if current is a black node, no rotations needed if (!(pos & 1)) break; // pos is red, its new child is red, and parent will be black. // if the sibling is also red, we can pull down the color black from the parent // if not, need a rotation. if (!(rbhash[pos_ref^1]&1)) { // Sibling is black, need a rotation // if the imbalanced child (red node) is on the same side as the parent, // need to rotate those lower nodes to the opposite side in preparation // for the rotation. // e.g. if pos_ref is leftward (even) and pos's rightward child (odd) is the red one... uint16_t child_ref= pos ^ (pos_ref&1); uint16_t child= rbhash[child_ref]; if (child&1) { // rotate pos toward [side] so parent's [side] now points to pos's [otherside] // set pos's child-ref to child's [otherside] ref uint16_t near_grandchild_ref= child ^ (child_ref&1); rbhash[child_ref]= rbhash[near_grandchild_ref]; // set child's [side] to pos rbhash[near_grandchild_ref]= pos; pos= child; // keep pos as a red node, soon to become black rbhash[pos_ref]= child; // parent's [side] has not been updated here, but is about to become 'child' child_ref= near_grandchild_ref^1; child= rbhash[child_ref]; } // Now we can rotate toward parent to balance the tree. rbhash[pos_ref]= child; rbhash[child_ref]= pos_ref|1; // = parent, colored red. simplification of ((pos_ref>>1)<<1)|1 rbhash[parent_ref]= pos^1; // also make pos black // rotation finished, exit. break; } rbhash[pos_ref^1] ^= 1; // toggle color of sibling rbhash[pos_ref]= pos^1; // toggle color of pos rbhash[parent_ref] ^= 1; // toggle color of parent // Now pos is black. // Jump twice up the tree so that once again, pos has one red child. p_i--; } // Root of tree is always black if (rbhash[path->refs[0]] & 1) rbhash[path->refs[0]] ^= 1; #if !POLLFD_RBHASH_RUN_WITH_SCISSORS // Path is no longer valid, because rotations may have destroyed it. path->len= 0; #endif return node_id; } /* Delete a node at the end of a path. The path must end with a non-Sentinel * reference, and must also be allocated to the maximum height of the tree, * because the node you want to delete might need to be replaced by a node * deeper in the tree. The tree will be re-balanced using the Red/Black * algorithm. If this is the last node in the tree, it clears the hash bucket. * * The path is destroyed in the process, as rotations and node-replacement * occur. You may not use the path afterward, even if the function fails. * (the path could be updated during balance rotations, but would add * overhead and users are unlikely to need it afterward anyway) * * Returns the deleted node_id on success, or 0 on an assertion failure if * you disabled assertions. */ extern size_t pollfd_rbhash_path_delete_8(uint8_t *rbhash, pollfd_rbhash_path *path) { // See pollfd_rbhash_path_insert for the notes on the bitwise operations uint8_t pos, ch1, ch2, sibling; int p_i= path->len-1, p_lim= path->lim; size_t *parent_refs= path->refs, ref, pos_ref; // Path should be at least 1 element (the bucket root ref) POLLFD_RBHASH_ASSERT(path->len >= 1); // Read the final ref to find 'pos_ref' and 'pos' pos_ref= parent_refs[p_i]; pos= rbhash[pos_ref]; // Path must point to a non-sentinel node POLLFD_RBHASH_ASSERT(pos != 0); // If pos has children, find a leaf to swap with. // Then delete this node in the leaf's position. // Note that normal red/black would delete the element first, then swap, but if we do that // a rotation could change the path->refs putting the node-to-delete somwhere else. ch1= rbhash[pos], ch2= rbhash[pos ^ 1]; if (ch1 || ch2) { if (ch1 && ch2) { int orig_p_i= p_i; uint8_t alt= pos, alt2; // Descend one level to the left. // The path should always have room for this additional reference if it // was allocated to max-tree-height and the tree is actually balanced. ++p_i; POLLFD_RBHASH_ASSERT(p_i < p_lim); parent_refs[p_i]= ref= (pos >> 1 << 1); // go left; alt= rbhash[ref]; // either ch1 or ch2, but now we know it's the left one // descend as many levels as possible to the right while ((alt= rbhash[ref= alt | 1])) { ++p_i; POLLFD_RBHASH_ASSERT(p_i < p_lim); parent_refs[p_i]= ref; } // 'alt' is the node we swap with. alt= rbhash[parent_refs[p_i]]; // is there one to the left? if ((alt2= rbhash[(alt >> 1 << 1)])) { POLLFD_RBHASH_ASSERT(alt2 & 1); // it is required to be a red leaf, so replace alt with it rbhash[parent_refs[p_i]]= alt2 ^ 1; ((uint16_t *)rbhash)[alt2 >> 1]= 0; // Now substitute this for pos and we're done. ((uint16_t *)rbhash)[alt >> 1]= ((uint16_t *)rbhash)[pos >> 1]; rbhash[pos_ref]= (alt >> 1 << 1) | (pos & 1); // preserve color of pos goto done; } else { // swap colors of alt and pos alt ^= pos & 1; pos ^= alt & 1; alt ^= pos & 1; ((uint16_t *)rbhash)[alt >> 1]= ((uint16_t *)rbhash)[pos >> 1]; rbhash[pos_ref]= alt; // the parent ref at orig_p_i+1 just changed address, so update that // (and this affects the next line if alt was a child of pos) parent_refs[orig_p_i + 1]= (alt >> 1 << 1); // was left branch at that point pos_ref= parent_refs[p_i]; } } else { // Node is black with one child. Swap with it. rbhash[pos_ref]= ((ch1 | ch2) >> 1 << 1); // and make it black goto done; } } // Remove it. rbhash[pos_ref]= 0; // It was a black node with no children. Now it gets interesting. if (!(pos & 1)) { // The tree must have the same number of black nodes along any path from root // to leaf. We want to remove a black node, disrupting the number of black // nodes along the path from the root to the current leaf. To correct this, // we must either reduce all other paths, or add a black node to the current // path. // Loop until the current node is red, or until we get to the root node. sibling= rbhash[pos_ref ^ 1]; --p_i; // p_i is now the index of the ref to the parent while (p_i >= 0) { size_t near_nephew_ref; uint8_t near_nephew; // If the sibling is red, we are unable to reduce the number of black // nodes in the sibling tree, and we can't increase the number of black // nodes in our tree.. Thus we must do a rotation from the sibling // tree to our tree to give us some extra (red) nodes to play with. // This is Case 1 from the text if (sibling & 1) { // Node is black and sibling is red. Get ref to sibling's near subtree near_nephew_ref= (sibling ^ 1) | (pos_ref & 1); // sibling is new parent, and now black. rbhash[parent_refs[p_i]]= sibling ^ 1; // move sibling's child under parent, becoming new sibling (which is black) sibling= rbhash[near_nephew_ref]; rbhash[pos_ref ^ 1]= sibling; rbhash[near_nephew_ref]= pos_ref | 1; // former sibling sameside tree = parent, now red ++p_i; POLLFD_RBHASH_ASSERT(p_i < p_lim); parent_refs[p_i] = near_nephew_ref; // insert new parent into list } // sibling will be black here // If the sibling is black and both children are black, we have to // reduce the black node count in the sibling's tree to match ours. // This is Case 2a from the text. near_nephew_ref= sibling | (pos_ref & 1); near_nephew= rbhash[near_nephew_ref]; if (!((near_nephew|rbhash[near_nephew_ref ^ 1]) & 1)) { POLLFD_RBHASH_ASSERT(sibling > 1); rbhash[pos_ref ^ 1] |= 1; // change sibling to red // Now we move one level up the tree to continue fixing the other branches if (p_i < 1) break; pos_ref= parent_refs[p_i--]; if (rbhash[pos_ref] & 1) { // Now, make the current node black (to fulfill Case 2b) rbhash[pos_ref] ^= 1; break; } sibling= rbhash[pos_ref ^ 1]; } else { // sibling will be black with 1 or 2 red children here // If one of the sibling's children are red, we again can't make the // sibling red to balance the tree at the parent, so we have to do a // rotation. If the "near" nephew is red and the "far" nephew is // black, we need to rotate that tree away before rotating the // parent toward. // After doing a rotation and rearranging a few colors, the effect is // that we maintain the same number of black nodes per path on the far // side of the parent, and we gain a black node on the current side, // so we are done. if (near_nephew & 1) { // Case 3 from the text, double rotation size_t tmp_ref= near_nephew ^ (pos_ref & 1); // near nephew's far child rbhash[near_nephew_ref]= rbhash[tmp_ref]; rbhash[pos_ref ^ 1]= near_nephew; rbhash[tmp_ref]= sibling; sibling= near_nephew ^ 1; // make it black near_nephew_ref= sibling | (pos_ref & 1); } else rbhash[near_nephew_ref ^ 1] ^= 1; // far nephew becomes black // now Case 4 from the text POLLFD_RBHASH_ASSERT(sibling > 1); rbhash[pos_ref ^ 1]= rbhash[near_nephew_ref]; // parent becomes black, balancing current path rbhash[near_nephew_ref]= (pos_ref >> 1 << 1); // Sibling assumes parent's color and position rbhash[parent_refs[p_i]]= sibling | (rbhash[parent_refs[p_i]] & 1); break; } } } done: // Ensure root-ref is black if (rbhash[parent_refs[0]] & 1) rbhash[parent_refs[0]] ^= 1; // clean the 'pos' node for future use ((uint16_t *)rbhash)[pos >> 1]= 0; #if !POLLFD_RBHASH_RUN_WITH_SCISSORS // Path is no longer valid, because rotations may have destroyed it. path->len= 0; #endif return pos >> 1; } extern size_t pollfd_rbhash_path_delete_16(uint16_t *rbhash, pollfd_rbhash_path *path) { // See pollfd_rbhash_path_insert for the notes on the bitwise operations uint16_t pos, ch1, ch2, sibling; int p_i= path->len-1, p_lim= path->lim; size_t *parent_refs= path->refs, ref, pos_ref; // Path should be at least 1 element (the bucket root ref) POLLFD_RBHASH_ASSERT(path->len >= 1); // Read the final ref to find 'pos_ref' and 'pos' pos_ref= parent_refs[p_i]; pos= rbhash[pos_ref]; // Path must point to a non-sentinel node POLLFD_RBHASH_ASSERT(pos != 0); // If pos has children, find a leaf to swap with. // Then delete this node in the leaf's position. // Note that normal red/black would delete the element first, then swap, but if we do that // a rotation could change the path->refs putting the node-to-delete somwhere else. ch1= rbhash[pos], ch2= rbhash[pos ^ 1]; if (ch1 || ch2) { if (ch1 && ch2) { int orig_p_i= p_i; uint16_t alt= pos, alt2; // Descend one level to the left. // The path should always have room for this additional reference if it // was allocated to max-tree-height and the tree is actually balanced. ++p_i; POLLFD_RBHASH_ASSERT(p_i < p_lim); parent_refs[p_i]= ref= (pos >> 1 << 1); // go left; alt= rbhash[ref]; // either ch1 or ch2, but now we know it's the left one // descend as many levels as possible to the right while ((alt= rbhash[ref= alt | 1])) { ++p_i; POLLFD_RBHASH_ASSERT(p_i < p_lim); parent_refs[p_i]= ref; } // 'alt' is the node we swap with. alt= rbhash[parent_refs[p_i]]; // is there one to the left? if ((alt2= rbhash[(alt >> 1 << 1)])) { POLLFD_RBHASH_ASSERT(alt2 & 1); // it is required to be a red leaf, so replace alt with it rbhash[parent_refs[p_i]]= alt2 ^ 1; ((uint32_t *)rbhash)[alt2 >> 1]= 0; // Now substitute this for pos and we're done. ((uint32_t *)rbhash)[alt >> 1]= ((uint32_t *)rbhash)[pos >> 1]; rbhash[pos_ref]= (alt >> 1 << 1) | (pos & 1); // preserve color of pos goto done; } else { // swap colors of alt and pos alt ^= pos & 1; pos ^= alt & 1; alt ^= pos & 1; ((uint32_t *)rbhash)[alt >> 1]= ((uint32_t *)rbhash)[pos >> 1]; rbhash[pos_ref]= alt; // the parent ref at orig_p_i+1 just changed address, so update that // (and this affects the next line if alt was a child of pos) parent_refs[orig_p_i + 1]= (alt >> 1 << 1); // was left branch at that point pos_ref= parent_refs[p_i]; } } else { // Node is black with one child. Swap with it. rbhash[pos_ref]= ((ch1 | ch2) >> 1 << 1); // and make it black goto done; } } // Remove it. rbhash[pos_ref]= 0; // It was a black node with no children. Now it gets interesting. if (!(pos & 1)) { // The tree must have the same number of black nodes along any path from root // to leaf. We want to remove a black node, disrupting the number of black // nodes along the path from the root to the current leaf. To correct this, // we must either reduce all other paths, or add a black node to the current // path. // Loop until the current node is red, or until we get to the root node. sibling= rbhash[pos_ref ^ 1]; --p_i; // p_i is now the index of the ref to the parent while (p_i >= 0) { size_t near_nephew_ref; uint16_t near_nephew; // If the sibling is red, we are unable to reduce the number of black // nodes in the sibling tree, and we can't increase the number of black // nodes in our tree.. Thus we must do a rotation from the sibling // tree to our tree to give us some extra (red) nodes to play with. // This is Case 1 from the text if (sibling & 1) { // Node is black and sibling is red. Get ref to sibling's near subtree near_nephew_ref= (sibling ^ 1) | (pos_ref & 1); // sibling is new parent, and now black. rbhash[parent_refs[p_i]]= sibling ^ 1; // move sibling's child under parent, becoming new sibling (which is black) sibling= rbhash[near_nephew_ref]; rbhash[pos_ref ^ 1]= sibling; rbhash[near_nephew_ref]= pos_ref | 1; // former sibling sameside tree = parent, now red ++p_i; POLLFD_RBHASH_ASSERT(p_i < p_lim); parent_refs[p_i] = near_nephew_ref; // insert new parent into list } // sibling will be black here // If the sibling is black and both children are black, we have to // reduce the black node count in the sibling's tree to match ours. // This is Case 2a from the text. near_nephew_ref= sibling | (pos_ref & 1); near_nephew= rbhash[near_nephew_ref]; if (!((near_nephew|rbhash[near_nephew_ref ^ 1]) & 1)) { POLLFD_RBHASH_ASSERT(sibling > 1); rbhash[pos_ref ^ 1] |= 1; // change sibling to red // Now we move one level up the tree to continue fixing the other branches if (p_i < 1) break; pos_ref= parent_refs[p_i--]; if (rbhash[pos_ref] & 1) { // Now, make the current node black (to fulfill Case 2b) rbhash[pos_ref] ^= 1; break; } sibling= rbhash[pos_ref ^ 1]; } else { // sibling will be black with 1 or 2 red children here // If one of the sibling's children are red, we again can't make the // sibling red to balance the tree at the parent, so we have to do a // rotation. If the "near" nephew is red and the "far" nephew is // black, we need to rotate that tree away before rotating the // parent toward. // After doing a rotation and rearranging a few colors, the effect is // that we maintain the same number of black nodes per path on the far // side of the parent, and we gain a black node on the current side, // so we are done. if (near_nephew & 1) { // Case 3 from the text, double rotation size_t tmp_ref= near_nephew ^ (pos_ref & 1); // near nephew's far child rbhash[near_nephew_ref]= rbhash[tmp_ref]; rbhash[pos_ref ^ 1]= near_nephew; rbhash[tmp_ref]= sibling; sibling= near_nephew ^ 1; // make it black near_nephew_ref= sibling | (pos_ref & 1); } else rbhash[near_nephew_ref ^ 1] ^= 1; // far nephew becomes black // now Case 4 from the text POLLFD_RBHASH_ASSERT(sibling > 1); rbhash[pos_ref ^ 1]= rbhash[near_nephew_ref]; // parent becomes black, balancing current path rbhash[near_nephew_ref]= (pos_ref >> 1 << 1); // Sibling assumes parent's color and position rbhash[parent_refs[p_i]]= sibling | (rbhash[parent_refs[p_i]] & 1); break; } } } done: // Ensure root-ref is black if (rbhash[parent_refs[0]] & 1) rbhash[parent_refs[0]] ^= 1; // clean the 'pos' node for future use ((uint32_t *)rbhash)[pos >> 1]= 0; #if !POLLFD_RBHASH_RUN_WITH_SCISSORS // Path is no longer valid, because rotations may have destroyed it. path->len= 0; #endif return pos >> 1; } // Handy for gdb: "p pollfd_rbhash_treeprint_8(rbhash, capacity, i, i, NULL, NULL, stdout)" static size_t pollfd_rbhash_print_tree_8( uint8_t *rbhash, uint8_t max_node, uint8_t node, uint8_t mark_node, void (*print_node)(void*,size_t,FILE*), void* userdata, FILE * out ) { uint8_t node_path[ 1+POLLFD_RBHASH_MAX_TREE_HEIGHT_8 ]; bool cycle; int i, pos, step= 0; size_t nodecount= 0; if (!node) { fputs("(empty tree)\n", out); return 0; } node_path[0]= 0; node_path[pos= 1]= node << 1; while (node && pos) { switch (step) { case 0: // Check for cycles cycle= false; for (i= 1; i < pos; i++) if ((node_path[i]>>1) == (node_path[pos]>>1)) cycle= true; // Proceed down right subtree if possible if (!cycle && pos < POLLFD_RBHASH_MAX_TREE_HEIGHT_8 && node <= max_node && rbhash[(node<<1)|1] ) { node= rbhash[(node<<1)|1] >> 1; node_path[++pos]= node << 1; continue; } case 1: // Print tree branches for nodes up until this one for (i= 2; i < pos; i++) fputs((node_path[i]&1) == (node_path[i+1]&1)? " " : " |", out); if (pos > 1) fputs((node_path[pos]&1)? " `" : " ,", out); // Print content of this node fprintf(out, "--%c%c%c #%ld%s ", (node == mark_node? '(' : '-'), (node > max_node? '!' : (rbhash[ (node_path[pos-1]|1) ^ (node_path[pos]&1) ]&1)? 'R':'B'), (node == mark_node? ')' : ' '), (long) node, cycle? " CYCLE DETECTED" : pos >= POLLFD_RBHASH_MAX_TREE_HEIGHT_8? " MAX DEPTH EXCEEDED" : node > max_node? " VALUE OUT OF BOUNDS" : "" ); if (print_node) print_node(userdata, node, out); fputs("\n", out); ++nodecount; // Proceed down left subtree if possible if (!cycle && pos < POLLFD_RBHASH_MAX_TREE_HEIGHT_8 && node <= max_node && rbhash[node<<1] ) { node= rbhash[node<<1] >> 1; node_path[++pos]= (node << 1) | 1; step= 0; continue; } case 2: // Return to parent step= (node_path[pos]&1) + 1; node= node_path[--pos] >> 1; cycle= false; } } return nodecount; } // Handy for gdb: "p pollfd_rbhash_treeprint_16(rbhash, capacity, i, i, NULL, NULL, stdout)" static size_t pollfd_rbhash_print_tree_16( uint16_t *rbhash, uint16_t max_node, uint16_t node, uint16_t mark_node, void (*print_node)(void*,size_t,FILE*), void* userdata, FILE * out ) { uint16_t node_path[ 1+POLLFD_RBHASH_MAX_TREE_HEIGHT_16 ]; bool cycle; int i, pos, step= 0; size_t nodecount= 0; if (!node) { fputs("(empty tree)\n", out); return 0; } node_path[0]= 0; node_path[pos= 1]= node << 1; while (node && pos) { switch (step) { case 0: // Check for cycles cycle= false; for (i= 1; i < pos; i++) if ((node_path[i]>>1) == (node_path[pos]>>1)) cycle= true; // Proceed down right subtree if possible if (!cycle && pos < POLLFD_RBHASH_MAX_TREE_HEIGHT_16 && node <= max_node && rbhash[(node<<1)|1] ) { node= rbhash[(node<<1)|1] >> 1; node_path[++pos]= node << 1; continue; } case 1: // Print tree branches for nodes up until this one for (i= 2; i < pos; i++) fputs((node_path[i]&1) == (node_path[i+1]&1)? " " : " |", out); if (pos > 1) fputs((node_path[pos]&1)? " `" : " ,", out); // Print content of this node fprintf(out, "--%c%c%c #%ld%s ", (node == mark_node? '(' : '-'), (node > max_node? '!' : (rbhash[ (node_path[pos-1]|1) ^ (node_path[pos]&1) ]&1)? 'R':'B'), (node == mark_node? ')' : ' '), (long) node, cycle? " CYCLE DETECTED" : pos >= POLLFD_RBHASH_MAX_TREE_HEIGHT_16? " MAX DEPTH EXCEEDED" : node > max_node? " VALUE OUT OF BOUNDS" : "" ); if (print_node) print_node(userdata, node, out); fputs("\n", out); ++nodecount; // Proceed down left subtree if possible if (!cycle && pos < POLLFD_RBHASH_MAX_TREE_HEIGHT_16 && node <= max_node && rbhash[node<<1] ) { node= rbhash[node<<1] >> 1; node_path[++pos]= (node << 1) | 1; step= 0; continue; } case 2: // Return to parent step= (node_path[pos]&1) + 1; node= node_path[--pos] >> 1; cycle= false; } } return nodecount; } void pollfd_rbhash_print( void *rbhash, size_t capacity, size_t n_buckets, void (*print_node)(void*,size_t,FILE*), void* userdata, FILE *out ) { size_t used= 0, collision= 0, empty=0, i; fprintf(out, "# rbhash for capacity=%ld: %ld hash buckets, %ld bytes\n" "--------------------\n", (long) capacity, (long) n_buckets, (long) POLLFD_RBHASH_SIZEOF(capacity, n_buckets)); if (capacity <= POLLFD_RBHASH_MAX_ELEMENTS_8) { uint8_t *nodes= (uint8_t*) rbhash; uint8_t *table= nodes + POLLFD_RBHASH_TABLE_WORD_OFS(capacity); for (i= 0; i < n_buckets; i++) { if (table[i]) { if (empty) { fprintf(out, "(%ld empty buckets)\n", (long) empty); empty= 0; } ++used; collision += pollfd_rbhash_print_tree_8(rbhash, capacity, table[i]>>1, 0, print_node, userdata, out) - 1; } else ++empty; } if (empty) { fprintf(out, "(%ld empty buckets)\n", (long) empty); empty= 0; } } else if (capacity <= POLLFD_RBHASH_MAX_ELEMENTS_16) { uint16_t *nodes= (uint16_t*) rbhash; uint16_t *table= nodes + POLLFD_RBHASH_TABLE_WORD_OFS(capacity); for (i= 0; i < n_buckets; i++) { if (table[i]) { if (empty) { fprintf(out, "(%ld empty buckets)\n", (long) empty); empty= 0; } ++used; collision += pollfd_rbhash_print_tree_16(rbhash, capacity, table[i]>>1, 0, print_node, userdata, out) - 1; } else ++empty; } if (empty) { fprintf(out, "(%ld empty buckets)\n", (long) empty); empty= 0; } } fprintf(out, "--------------------\n" "# used %ld/%ld buckets, %ld collisions\n", (long) used, (long) n_buckets, (long) collision); }