/* * Trie index for efficient trie storage * * (c) 2019 Maria Matejka * (c) 2019 CZ.NIC z.s.p.o. * * Can be freely distributed and used under the terms of the GNU GPL. */ #include "nest/bird.h" #include "lib/idm.h" #include "lib/tindex.h" #undef LOCAL_DEBUG #define LOCAL_DEBUG #define TI_MIN_UNIT_SIZE 4 #define TI_MIN_ADDRESS_SIZE 6 union tindex_data { u32 data4[0]; u16 data6[0]; u64 data8[0]; u32 data12[0]; }; struct tindex { union tindex_data *index_data; pool *p; struct idm idm; u8 unit_size; u8 address_size; }; struct tindex * tindex_new(pool *p) { struct tindex *ti = mb_allocz(p, sizeof(struct tindex)); ti->p = p; ti->unit_size = TI_MIN_UNIT_SIZE; ti->address_size = TI_MIN_ADDRESS_SIZE; ti->index_data = mb_allocz(p, ti->unit_size * (1 << ti->address_size)); idm_init(&(ti->idm), p, (1 << ti->address_size)); u32 rootnode = idm_alloc(&(ti->idm)); ASSERT(rootnode == 1); return ti; } static inline u64 tindex_data(const struct tindex *ti, u64 asize, u64 usize, u64 dsize, u64 dshift, u64 idx, uint *len) { u64 data; switch (usize) { case 4: data = ti->index_data->data4[idx] >> dshift; break; case 6: data = ((u64)(ti->index_data->data6[idx * 3] >> asize) << (dshift * 2)) | ((u64)(ti->index_data->data6[idx * 3 + 1] >> asize) << (dshift)) | (u64)(ti->index_data->data6[idx * 3 + 2] >> asize); break; case 8: data = ti->index_data->data8[idx] >> dshift; break; case 12: data = ((u64)(ti->index_data->data12[idx * 3] >> asize) << (dshift * 2)) | ((u64)(ti->index_data->data12[idx * 3 + 1] >> asize) << (dshift)) | (u64)(ti->index_data->data12[idx * 3 + 2] >> asize); break; default: bug("This shall never happen"); } u64 out = u64_var_decode(data, len); if (*len == 64) *len = 0; else *len = dsize - *len; return out; } static inline u64 tindex_left(const struct tindex *ti, u64 idx, u64 usize, u64 asize, u64 addrmask) { switch (usize) { case 4: return (ti->index_data->data4[idx] >> (asize * 2)) & addrmask; case 6: return ti->index_data->data6[idx * 3] & addrmask; case 8: return (ti->index_data->data8[idx] >> (asize * 2)) & addrmask; case 12: return ti->index_data->data12[idx * 3] & addrmask; default: bug("This shall never happen"); } } static inline u64 tindex_right(const struct tindex *ti, u64 idx, u64 usize, u64 asize, u64 addrmask) { switch (usize) { case 4: return (ti->index_data->data4[idx] >> (asize)) & addrmask; case 6: return ti->index_data->data6[idx * 3 + 1] & addrmask; case 8: return (ti->index_data->data8[idx] >> (asize)) & addrmask; case 12: return ti->index_data->data12[idx * 3 + 1] & addrmask; default: bug("This shall never happen"); } } static inline u64 tindex_up(const struct tindex *ti, u64 idx, u64 usize, u64 addrmask) { switch (usize) { case 4: return ti->index_data->data4[idx] & addrmask; case 6: return ti->index_data->data6[idx * 3 + 2] & addrmask; case 8: return ti->index_data->data8[idx] & addrmask; case 12: return ti->index_data->data12[idx * 3 + 2] & addrmask; default: bug("This shall never happen"); } } static inline void tindex_put(struct tindex *ti, u64 idx, u64 usize, u64 asize, u64 dsize, u64 dshift, u64 data, uint dlen, u64 left, u64 right, u64 up) { const u64 dsmask = (1LL << dshift) - 1; data = u64_var_encode(data, dsize - dlen); switch (usize) { case 4: ti->index_data->data4[idx] = (data << dshift) | (left << (asize * 2)) | (right << asize) | up; return; case 6: ti->index_data->data6[idx * 3 ] = left | ((data >> (2 * dshift)) << asize); ti->index_data->data6[idx * 3 + 1] = right | (((data >> dshift) & dsmask) << asize); ti->index_data->data6[idx * 3 + 2] = up | ((data & dsmask) << asize); return; case 8: ti->index_data->data8[idx] = (data << dshift) | (left << (asize * 2)) | (right << asize) | up; return; case 12: ti->index_data->data12[idx * 3 ] = left | ((data >> (2 * dshift)) << asize); ti->index_data->data12[idx * 3 + 1] = right | (((data >> dshift) & dsmask) << asize); ti->index_data->data12[idx * 3 + 2] = up | ((data & dsmask) << asize); return; default: bug("This shall never happen"); } } static inline void tindex_left_clear(struct tindex *ti, u64 idx, u64 usize, u64 asize, u64 addrmask) { switch (usize) { case 4: ti->index_data->data4[idx] &= ~(addrmask << (asize * 2)); break; case 6: ti->index_data->data6[idx * 3] &= ~addrmask; break; case 8: ti->index_data->data8[idx] &= ~(addrmask << (asize * 2)); break; case 12: ti->index_data->data6[idx * 3] &= ~addrmask; break; } } static inline void tindex_right_clear(struct tindex *ti, u64 idx, u64 usize, u64 asize, u64 addrmask) { switch (usize) { case 4: ti->index_data->data4[idx] &= ~(addrmask << asize); break; case 6: ti->index_data->data6[idx * 3 + 1] &= ~addrmask; break; case 8: ti->index_data->data8[idx] &= ~(addrmask << asize); break; case 12: ti->index_data->data6[idx * 3 + 1] &= ~addrmask; break; } } static inline void tindex_left_set(struct tindex *ti, u64 idx, u64 usize, u64 asize, u64 nidx) { /* The left child must have been zero before */ switch (usize) { case 4: ti->index_data->data4[idx] |= nidx << (asize * 2); break; case 6: ti->index_data->data6[idx * 3] |= nidx; break; case 8: ti->index_data->data8[idx] |= nidx << (asize * 2); break; case 12: ti->index_data->data6[idx * 3] |= nidx; break; } } static inline void tindex_right_set(struct tindex *ti, u64 idx, u64 usize, u64 asize, u64 nidx) { /* The right child must have been zero before */ switch (usize) { case 4: ti->index_data->data4[idx] |= nidx << asize; break; case 6: ti->index_data->data6[idx * 3 + 1] |= nidx; break; case 8: ti->index_data->data8[idx] |= nidx << asize; break; case 12: ti->index_data->data6[idx * 3 + 1] |= nidx; break; } } static inline void tindex_child_update(struct tindex *ti, u64 idx, u64 usize, u64 asize, u64 addrmask, u64 oidx, u64 nidx) { if (oidx == tindex_left(ti, idx, usize, asize, addrmask)) { tindex_left_clear(ti, idx, usize, asize, addrmask); tindex_left_set(ti, idx, usize, asize, nidx); } else { ASSERT(oidx == tindex_right(ti, idx, usize, asize, addrmask)); tindex_right_clear(ti, idx, usize, asize, addrmask); tindex_right_set(ti, idx, usize, asize, nidx); } } static inline uint tindex_input_bits(const u64 *bits_in, const uint blen, uint *bpos, const uint dlen, u64 *bits) { uint bmax = blen - *bpos; /* How much remains in the input */ uint ilen = MIN(bmax, dlen); /* How much we really take */ if (ilen == 0) { /* End of input */ *bits = 0; return 0; } ASSERT(ilen <= 64); /* The limit of output bit count is 64 */ uint bend = *bpos + ilen - 1; /* The last bit, inclusive (!) */ /* Crop the bits at the end */ *bits = (bits_in[bend / 64] >> (63 - (bend % 64))); /* Prepend bits from the previous item if the range goes over */ if (bend / 64 > *bpos / 64) *bits |= bits_in[*bpos / 64] << (1 + bend % 64); else ASSERT(bend / 64 == *bpos / 64); /* Advance the bit pointer */ *bpos += ilen; /* Return the wanted bits */ *bits &= ((1 << ilen) - 1); return ilen; } const char dump_indent[] = " "; #define INDENT (dump_indent + sizeof(dump_indent) - depth - 1) static void _tindex_dump(const struct tindex *ti, u64 idx, uint depth, uint bit) { const uint asize = ti->address_size; const uint usize = ti->unit_size; const uint dsize = usize * 8 - asize * 3; const uint dshift = (usize % 3) ? (asize * 3) : (dsize / 3); const u64 addrmask = (1ULL << ti->address_size) - 1; /* Validate unit size */ switch (usize) { case 4: case 6: case 8: case 12: break; default: bug("This shall never happen"); } uint dlen; u64 data = tindex_data(ti, asize, usize, dsize, dshift, idx, &dlen); if (depth && bit) data |= 1ULL << dlen; if (depth) dlen++; debug("%s0x%x/%u (%lu)\n", INDENT, data, dlen, idx); u64 left = tindex_left(ti, idx, usize, asize, addrmask); if (left) _tindex_dump(ti, left, depth+1, 0); u64 right = tindex_right(ti, idx, usize, asize, addrmask); if (right) _tindex_dump(ti, right, depth+1, 1); } void tindex_dump(const struct tindex *ti) { _tindex_dump(ti, 1, 0, 0); } u64 tindex_find(struct tindex *ti, const u64 *bits_in, const uint blen, const int create) { const uint asize = ti->address_size; const uint usize = ti->unit_size; const uint dsize = usize * 8 - asize * 3; const uint dshift = (usize % 3) ? (asize * 3) : (dsize / 3); const u64 addrmask = (1ULL << ti->address_size) - 1; /* Validate unit size */ switch (usize) { case 4: case 6: case 8: case 12: break; default: bug("This shall never happen"); } u64 idx = 1; /* The root node is always 1 */ u64 uidx = 0; /* Parent node is 0 on beginning */ uint bpos = 0; while (1) { /* Get data from trie */ uint dlen; u64 data = tindex_data(ti, asize, usize, dsize, dshift, idx, &dlen); /* Get data from input */ u64 bits; uint ilen = tindex_input_bits(bits_in, blen, &bpos, dlen, &bits); /* Check whether this node matches the data */ int match = ((ilen == dlen) && (bits == data)); /* Doesn't match and we are just traversing */ if (!create && !match) return 0; /* The bit strings match */ if (match) { /* Get one more bit */ ilen = tindex_input_bits(bits_in, blen, &bpos, 1, &bits); /* No more bits, we're done */ if (!ilen) return idx; /* Just one bit, to be sure */ ASSERT(bits < 2); /* Go left or right? */ u64 nidx = bits ? tindex_right(ti, idx, usize, asize, addrmask) : tindex_left(ti, idx, usize, asize, addrmask); /* There is a path, we'll follow it. */ if (nidx) { uidx = idx; idx = nidx; continue; } /* There is no path and we shan't create it. */ if (!create) return 0; /* So there will be a new node on path. */ nidx = idm_alloc(&(ti->idm)); /* Left or right? */ if (bits) tindex_right_set(ti, idx, usize, asize, nidx); else tindex_left_set(ti, idx, usize, asize, nidx); /* Go there. */ uidx = idx; idx = nidx; /* And now we shall continue by the brand new node. */ break; } /* Move the bits to same places */ u64 shorter = dlen - ilen; bits <<= shorter; /* What is the common part? */ u64 diflen = u64_log2(bits ^ data) + 1; /* To be sure that the split is right. */ ASSERT((bits >> diflen) == (data >> diflen)); ASSERT(((bits >> (diflen - 1)) ^ (data >> (diflen - 1))) == 1); /* Get the common part */ u64 common = data >> diflen; u64 comlen = dlen - diflen; /* Return the differing part to the input buffer (if there is some) */ int split = (ilen - comlen > 0); if (split) bpos -= ilen - comlen - 1; /* Split out the first different bit */ u64 dataright = !!(data & (1 << (diflen - 1))); dlen = diflen - 1; data &= (1 << dlen) - 1; /* Allocate the splitting index */ u64 midx = idm_alloc(&(ti->idm)); /* Allocate the new node if it shall exist */ u64 nidx = split ? idm_alloc(&(ti->idm)) : 0; /* Relink idx -> midx in the parent node */ if (uidx) tindex_child_update(ti, uidx, usize, asize, addrmask, idx, midx); /* Setup the splitting index (midx) */ tindex_put(ti, midx, usize, asize, dsize, dshift, common, comlen, dataright ? nidx : idx, dataright ? idx : nidx, uidx); /* Update the existing index (idx) */ tindex_put(ti, idx, usize, asize, dsize, dshift, data, dlen, tindex_left(ti, idx, usize, asize, addrmask), tindex_right(ti, idx, usize, asize, addrmask), midx); /* Go down to the child */ uidx = idx; idx = nidx; /* Grow there a branch if it has to be grown, otherwise return */ if (split) break; else return midx; } /* Growing a new branch */ while (1) { /* Get more data from input */ u64 data; uint ilen = tindex_input_bits(bits_in, blen, &bpos, dsize - 1, &data); /* For the single bit */ u64 dataright = ~0; /* End of input data */ if ((ilen < dsize - 1) || !tindex_input_bits(bits_in, blen, &bpos, 1, &dataright)) { tindex_put(ti, idx, usize, asize, dsize, dshift, data, ilen, 0, 0, uidx); return idx; } /* Just one bit. */ ASSERT(dataright < 2); /* Create a new node */ uint nidx = idm_alloc(&(ti->idm)); /* Link it into the trie */ tindex_put(ti, idx, usize, asize, dsize, dshift, data, ilen, dataright ? 0 : nidx, dataright ? nidx : 0, uidx); /* And continue there */ uidx = idx; idx = nidx; } }