This commit is contained in:
bluepython508 committed 2026-10-22 12:27:19 +01:00
1 parent 3530afb854
commit 6903fcff27
2146 files changed
+112256 -190703

No files matched your search

+547
View File
@@ -0,0 +1,547 @@
// Code generated by generate_allottbl.go; DO NOT EDIT.
// Copyright (c) 2026 Karl Gaissmaier
// SPDX-License-Identifier: MIT
package allot
import "github.com/gaissmai/bart/internal/bitset"
// PfxRoutesLookupTbl is a lookup table of BitSet256 entries.
//
// It maps a baseIndex (1..255) to a bitset that contains all more
// specific baseIndices that are covered by the prefix at that index.
//
// The idea:
//
// If prefix P has baseIndex idx, then P covers all prefixes in the subtree
// rooted at idx. This table enumerates and encodes those indices.
//
// The table is split:
// - PfxRoutesLookupTbl holds prefixes up to /7
// - FringeRoutesLookupTbl holds /8 prefixes
//
// This structure allows for very fast set inclusion checks using simple bitwise AND.
//
//nolint:gochecknoglobals // Precomputed read‑only table used in hot paths.
var PfxRoutesLookupTbl = [256]bitset.BitSet256{
/* idx: 0 */ {0x0, 0x0, 0x0, 0x0}, // invalid
/* idx: 1 */ {0xfffffffffffffffe, 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff}, // [1, 2, 3, 4, 5, ... ]
/* idx: 2 */ {0xffff00ff0f34, 0xffffffff, 0xffffffffffffffff, 0x0}, // [2, 4, 5, 8, 9, ... ]
/* idx: 3 */ {0xffff0000ff00f0c8, 0xffffffff00000000, 0x0, 0xffffffffffffffff}, // [3, 6, 7, 12, 13, ... ]
/* idx: 4 */ {0xff000f0310, 0xffff, 0xffffffff, 0x0}, // [4, 8, 9, 16, 17, ... ]
/* idx: 5 */ {0xff0000f00c20, 0xffff0000, 0xffffffff00000000, 0x0}, // [5, 10, 11, 20, 21, ... ]
/* idx: 6 */ {0xff00000f003040, 0xffff00000000, 0x0, 0xffffffff}, // [6, 12, 13, 24, 25, ... ]
/* idx: 7 */ {0xff000000f000c080, 0xffff000000000000, 0x0, 0xffffffff00000000}, // [7, 14, 15, 28, 29, ... ]
/* idx: 8 */ {0xf00030100, 0xff, 0xffff, 0x0}, // [8, 16, 17, 32, 33, ... ]
/* idx: 9 */ {0xf0000c0200, 0xff00, 0xffff0000, 0x0}, // [9, 18, 19, 36, 37, ... ]
/* idx: 10 */ {0xf0000300400, 0xff0000, 0xffff00000000, 0x0}, // [10, 20, 21, 40, 41, ... ]
/* idx: 11 */ {0xf00000c00800, 0xff000000, 0xffff000000000000, 0x0}, // [11, 22, 23, 44, 45, ... ]
/* idx: 12 */ {0xf000003001000, 0xff00000000, 0x0, 0xffff}, // [12, 24, 25, 48, 49, ... ]
/* idx: 13 */ {0xf000000c002000, 0xff0000000000, 0x0, 0xffff0000}, // [13, 26, 27, 52, 53, ... ]
/* idx: 14 */ {0xf00000030004000, 0xff000000000000, 0x0, 0xffff00000000}, // [14, 28, 29, 56, 57, ... ]
/* idx: 15 */ {0xf0000000c0008000, 0xff00000000000000, 0x0, 0xffff000000000000}, // [15, 30, 31, 60, 61, ... ]
/* idx: 16 */ {0x300010000, 0xf, 0xff, 0x0}, // [16, 32, 33, 64, 65, ... ]
/* idx: 17 */ {0xc00020000, 0xf0, 0xff00, 0x0}, // [17, 34, 35, 68, 69, ... ]
/* idx: 18 */ {0x3000040000, 0xf00, 0xff0000, 0x0}, // [18, 36, 37, 72, 73, ... ]
/* idx: 19 */ {0xc000080000, 0xf000, 0xff000000, 0x0}, // [19, 38, 39, 76, 77, ... ]
/* idx: 20 */ {0x30000100000, 0xf0000, 0xff00000000, 0x0}, // [20, 40, 41, 80, 81, ... ]
/* idx: 21 */ {0xc0000200000, 0xf00000, 0xff0000000000, 0x0}, // [21, 42, 43, 84, 85, ... ]
/* idx: 22 */ {0x300000400000, 0xf000000, 0xff000000000000, 0x0}, // [22, 44, 45, 88, 89, ... ]
/* idx: 23 */ {0xc00000800000, 0xf0000000, 0xff00000000000000, 0x0}, // [23, 46, 47, 92, 93, ... ]
/* idx: 24 */ {0x3000001000000, 0xf00000000, 0x0, 0xff}, // [24, 48, 49, 96, 97, ... ]
/* idx: 25 */ {0xc000002000000, 0xf000000000, 0x0, 0xff00}, // [25, 50, 51, 100, 101, ... ]
/* idx: 26 */ {0x30000004000000, 0xf0000000000, 0x0, 0xff0000}, // [26, 52, 53, 104, 105, ... ]
/* idx: 27 */ {0xc0000008000000, 0xf00000000000, 0x0, 0xff000000}, // [27, 54, 55, 108, 109, ... ]
/* idx: 28 */ {0x300000010000000, 0xf000000000000, 0x0, 0xff00000000}, // [28, 56, 57, 112, 113, ... ]
/* idx: 29 */ {0xc00000020000000, 0xf0000000000000, 0x0, 0xff0000000000}, // [29, 58, 59, 116, 117, ... ]
/* idx: 30 */ {0x3000000040000000, 0xf00000000000000, 0x0, 0xff000000000000}, // [30, 60, 61, 120, 121, ... ]
/* idx: 31 */ {0xc000000080000000, 0xf000000000000000, 0x0, 0xff00000000000000}, // [31, 62, 63, 124, 125, ... ]
/* idx: 32 */ {0x100000000, 0x3, 0xf, 0x0}, // [32 64 65 128 129 130 131]
/* idx: 33 */ {0x200000000, 0xc, 0xf0, 0x0}, // [33 66 67 132 133 134 135]
/* idx: 34 */ {0x400000000, 0x30, 0xf00, 0x0}, // [34 68 69 136 137 138 139]
/* idx: 35 */ {0x800000000, 0xc0, 0xf000, 0x0}, // [35 70 71 140 141 142 143]
/* idx: 36 */ {0x1000000000, 0x300, 0xf0000, 0x0}, // [36 72 73 144 145 146 147]
/* idx: 37 */ {0x2000000000, 0xc00, 0xf00000, 0x0}, // [37 74 75 148 149 150 151]
/* idx: 38 */ {0x4000000000, 0x3000, 0xf000000, 0x0}, // [38 76 77 152 153 154 155]
/* idx: 39 */ {0x8000000000, 0xc000, 0xf0000000, 0x0}, // [39 78 79 156 157 158 159]
/* idx: 40 */ {0x10000000000, 0x30000, 0xf00000000, 0x0}, // [40 80 81 160 161 162 163]
/* idx: 41 */ {0x20000000000, 0xc0000, 0xf000000000, 0x0}, // [41 82 83 164 165 166 167]
/* idx: 42 */ {0x40000000000, 0x300000, 0xf0000000000, 0x0}, // [42 84 85 168 169 170 171]
/* idx: 43 */ {0x80000000000, 0xc00000, 0xf00000000000, 0x0}, // [43 86 87 172 173 174 175]
/* idx: 44 */ {0x100000000000, 0x3000000, 0xf000000000000, 0x0}, // [44 88 89 176 177 178 179]
/* idx: 45 */ {0x200000000000, 0xc000000, 0xf0000000000000, 0x0}, // [45 90 91 180 181 182 183]
/* idx: 46 */ {0x400000000000, 0x30000000, 0xf00000000000000, 0x0}, // [46 92 93 184 185 186 187]
/* idx: 47 */ {0x800000000000, 0xc0000000, 0xf000000000000000, 0x0}, // [47 94 95 188 189 190 191]
/* idx: 48 */ {0x1000000000000, 0x300000000, 0x0, 0xf}, // [48 96 97 192 193 194 195]
/* idx: 49 */ {0x2000000000000, 0xc00000000, 0x0, 0xf0}, // [49 98 99 196 197 198 199]
/* idx: 50 */ {0x4000000000000, 0x3000000000, 0x0, 0xf00}, // [50 100 101 200 201 202 203]
/* idx: 51 */ {0x8000000000000, 0xc000000000, 0x0, 0xf000}, // [51 102 103 204 205 206 207]
/* idx: 52 */ {0x10000000000000, 0x30000000000, 0x0, 0xf0000}, // [52 104 105 208 209 210 211]
/* idx: 53 */ {0x20000000000000, 0xc0000000000, 0x0, 0xf00000}, // [53 106 107 212 213 214 215]
/* idx: 54 */ {0x40000000000000, 0x300000000000, 0x0, 0xf000000}, // [54 108 109 216 217 218 219]
/* idx: 55 */ {0x80000000000000, 0xc00000000000, 0x0, 0xf0000000}, // [55 110 111 220 221 222 223]
/* idx: 56 */ {0x100000000000000, 0x3000000000000, 0x0, 0xf00000000}, // [56 112 113 224 225 226 227]
/* idx: 57 */ {0x200000000000000, 0xc000000000000, 0x0, 0xf000000000}, // [57 114 115 228 229 230 231]
/* idx: 58 */ {0x400000000000000, 0x30000000000000, 0x0, 0xf0000000000}, // [58 116 117 232 233 234 235]
/* idx: 59 */ {0x800000000000000, 0xc0000000000000, 0x0, 0xf00000000000}, // [59 118 119 236 237 238 239]
/* idx: 60 */ {0x1000000000000000, 0x300000000000000, 0x0, 0xf000000000000}, // [60 120 121 240 241 242 243]
/* idx: 61 */ {0x2000000000000000, 0xc00000000000000, 0x0, 0xf0000000000000}, // [61 122 123 244 245 246 247]
/* idx: 62 */ {0x4000000000000000, 0x3000000000000000, 0x0, 0xf00000000000000}, // [62 124 125 248 249 250 251]
/* idx: 63 */ {0x8000000000000000, 0xc000000000000000, 0x0, 0xf000000000000000}, // [63 126 127 252 253 254 255]
/* idx: 64 */ {0x0, 0x1, 0x3, 0x0}, // [64 128 129]
/* idx: 65 */ {0x0, 0x2, 0xc, 0x0}, // [65 130 131]
/* idx: 66 */ {0x0, 0x4, 0x30, 0x0}, // [66 132 133]
/* idx: 67 */ {0x0, 0x8, 0xc0, 0x0}, // [67 134 135]
/* idx: 68 */ {0x0, 0x10, 0x300, 0x0}, // [68 136 137]
/* idx: 69 */ {0x0, 0x20, 0xc00, 0x0}, // [69 138 139]
/* idx: 70 */ {0x0, 0x40, 0x3000, 0x0}, // [70 140 141]
/* idx: 71 */ {0x0, 0x80, 0xc000, 0x0}, // [71 142 143]
/* idx: 72 */ {0x0, 0x100, 0x30000, 0x0}, // [72 144 145]
/* idx: 73 */ {0x0, 0x200, 0xc0000, 0x0}, // [73 146 147]
/* idx: 74 */ {0x0, 0x400, 0x300000, 0x0}, // [74 148 149]
/* idx: 75 */ {0x0, 0x800, 0xc00000, 0x0}, // [75 150 151]
/* idx: 76 */ {0x0, 0x1000, 0x3000000, 0x0}, // [76 152 153]
/* idx: 77 */ {0x0, 0x2000, 0xc000000, 0x0}, // [77 154 155]
/* idx: 78 */ {0x0, 0x4000, 0x30000000, 0x0}, // [78 156 157]
/* idx: 79 */ {0x0, 0x8000, 0xc0000000, 0x0}, // [79 158 159]
/* idx: 80 */ {0x0, 0x10000, 0x300000000, 0x0}, // [80 160 161]
/* idx: 81 */ {0x0, 0x20000, 0xc00000000, 0x0}, // [81 162 163]
/* idx: 82 */ {0x0, 0x40000, 0x3000000000, 0x0}, // [82 164 165]
/* idx: 83 */ {0x0, 0x80000, 0xc000000000, 0x0}, // [83 166 167]
/* idx: 84 */ {0x0, 0x100000, 0x30000000000, 0x0}, // [84 168 169]
/* idx: 85 */ {0x0, 0x200000, 0xc0000000000, 0x0}, // [85 170 171]
/* idx: 86 */ {0x0, 0x400000, 0x300000000000, 0x0}, // [86 172 173]
/* idx: 87 */ {0x0, 0x800000, 0xc00000000000, 0x0}, // [87 174 175]
/* idx: 88 */ {0x0, 0x1000000, 0x3000000000000, 0x0}, // [88 176 177]
/* idx: 89 */ {0x0, 0x2000000, 0xc000000000000, 0x0}, // [89 178 179]
/* idx: 90 */ {0x0, 0x4000000, 0x30000000000000, 0x0}, // [90 180 181]
/* idx: 91 */ {0x0, 0x8000000, 0xc0000000000000, 0x0}, // [91 182 183]
/* idx: 92 */ {0x0, 0x10000000, 0x300000000000000, 0x0}, // [92 184 185]
/* idx: 93 */ {0x0, 0x20000000, 0xc00000000000000, 0x0}, // [93 186 187]
/* idx: 94 */ {0x0, 0x40000000, 0x3000000000000000, 0x0}, // [94 188 189]
/* idx: 95 */ {0x0, 0x80000000, 0xc000000000000000, 0x0}, // [95 190 191]
/* idx: 96 */ {0x0, 0x100000000, 0x0, 0x3}, // [96 192 193]
/* idx: 97 */ {0x0, 0x200000000, 0x0, 0xc}, // [97 194 195]
/* idx: 98 */ {0x0, 0x400000000, 0x0, 0x30}, // [98 196 197]
/* idx: 99 */ {0x0, 0x800000000, 0x0, 0xc0}, // [99 198 199]
/* idx: 100 */ {0x0, 0x1000000000, 0x0, 0x300}, // [100 200 201]
/* idx: 101 */ {0x0, 0x2000000000, 0x0, 0xc00}, // [101 202 203]
/* idx: 102 */ {0x0, 0x4000000000, 0x0, 0x3000}, // [102 204 205]
/* idx: 103 */ {0x0, 0x8000000000, 0x0, 0xc000}, // [103 206 207]
/* idx: 104 */ {0x0, 0x10000000000, 0x0, 0x30000}, // [104 208 209]
/* idx: 105 */ {0x0, 0x20000000000, 0x0, 0xc0000}, // [105 210 211]
/* idx: 106 */ {0x0, 0x40000000000, 0x0, 0x300000}, // [106 212 213]
/* idx: 107 */ {0x0, 0x80000000000, 0x0, 0xc00000}, // [107 214 215]
/* idx: 108 */ {0x0, 0x100000000000, 0x0, 0x3000000}, // [108 216 217]
/* idx: 109 */ {0x0, 0x200000000000, 0x0, 0xc000000}, // [109 218 219]
/* idx: 110 */ {0x0, 0x400000000000, 0x0, 0x30000000}, // [110 220 221]
/* idx: 111 */ {0x0, 0x800000000000, 0x0, 0xc0000000}, // [111 222 223]
/* idx: 112 */ {0x0, 0x1000000000000, 0x0, 0x300000000}, // [112 224 225]
/* idx: 113 */ {0x0, 0x2000000000000, 0x0, 0xc00000000}, // [113 226 227]
/* idx: 114 */ {0x0, 0x4000000000000, 0x0, 0x3000000000}, // [114 228 229]
/* idx: 115 */ {0x0, 0x8000000000000, 0x0, 0xc000000000}, // [115 230 231]
/* idx: 116 */ {0x0, 0x10000000000000, 0x0, 0x30000000000}, // [116 232 233]
/* idx: 117 */ {0x0, 0x20000000000000, 0x0, 0xc0000000000}, // [117 234 235]
/* idx: 118 */ {0x0, 0x40000000000000, 0x0, 0x300000000000}, // [118 236 237]
/* idx: 119 */ {0x0, 0x80000000000000, 0x0, 0xc00000000000}, // [119 238 239]
/* idx: 120 */ {0x0, 0x100000000000000, 0x0, 0x3000000000000}, // [120 240 241]
/* idx: 121 */ {0x0, 0x200000000000000, 0x0, 0xc000000000000}, // [121 242 243]
/* idx: 122 */ {0x0, 0x400000000000000, 0x0, 0x30000000000000}, // [122 244 245]
/* idx: 123 */ {0x0, 0x800000000000000, 0x0, 0xc0000000000000}, // [123 246 247]
/* idx: 124 */ {0x0, 0x1000000000000000, 0x0, 0x300000000000000}, // [124 248 249]
/* idx: 125 */ {0x0, 0x2000000000000000, 0x0, 0xc00000000000000}, // [125 250 251]
/* idx: 126 */ {0x0, 0x4000000000000000, 0x0, 0x3000000000000000}, // [126 252 253]
/* idx: 127 */ {0x0, 0x8000000000000000, 0x0, 0xc000000000000000}, // [127 254 255]
/* idx: 128 */ {0x0, 0x0, 0x1, 0x0}, // [128]
/* idx: 129 */ {0x0, 0x0, 0x2, 0x0}, // [129]
/* idx: 130 */ {0x0, 0x0, 0x4, 0x0}, // [130]
/* idx: 131 */ {0x0, 0x0, 0x8, 0x0}, // [131]
/* idx: 132 */ {0x0, 0x0, 0x10, 0x0}, // [132]
/* idx: 133 */ {0x0, 0x0, 0x20, 0x0}, // [133]
/* idx: 134 */ {0x0, 0x0, 0x40, 0x0}, // [134]
/* idx: 135 */ {0x0, 0x0, 0x80, 0x0}, // [135]
/* idx: 136 */ {0x0, 0x0, 0x100, 0x0}, // [136]
/* idx: 137 */ {0x0, 0x0, 0x200, 0x0}, // [137]
/* idx: 138 */ {0x0, 0x0, 0x400, 0x0}, // [138]
/* idx: 139 */ {0x0, 0x0, 0x800, 0x0}, // [139]
/* idx: 140 */ {0x0, 0x0, 0x1000, 0x0}, // [140]
/* idx: 141 */ {0x0, 0x0, 0x2000, 0x0}, // [141]
/* idx: 142 */ {0x0, 0x0, 0x4000, 0x0}, // [142]
/* idx: 143 */ {0x0, 0x0, 0x8000, 0x0}, // [143]
/* idx: 144 */ {0x0, 0x0, 0x10000, 0x0}, // [144]
/* idx: 145 */ {0x0, 0x0, 0x20000, 0x0}, // [145]
/* idx: 146 */ {0x0, 0x0, 0x40000, 0x0}, // [146]
/* idx: 147 */ {0x0, 0x0, 0x80000, 0x0}, // [147]
/* idx: 148 */ {0x0, 0x0, 0x100000, 0x0}, // [148]
/* idx: 149 */ {0x0, 0x0, 0x200000, 0x0}, // [149]
/* idx: 150 */ {0x0, 0x0, 0x400000, 0x0}, // [150]
/* idx: 151 */ {0x0, 0x0, 0x800000, 0x0}, // [151]
/* idx: 152 */ {0x0, 0x0, 0x1000000, 0x0}, // [152]
/* idx: 153 */ {0x0, 0x0, 0x2000000, 0x0}, // [153]
/* idx: 154 */ {0x0, 0x0, 0x4000000, 0x0}, // [154]
/* idx: 155 */ {0x0, 0x0, 0x8000000, 0x0}, // [155]
/* idx: 156 */ {0x0, 0x0, 0x10000000, 0x0}, // [156]
/* idx: 157 */ {0x0, 0x0, 0x20000000, 0x0}, // [157]
/* idx: 158 */ {0x0, 0x0, 0x40000000, 0x0}, // [158]
/* idx: 159 */ {0x0, 0x0, 0x80000000, 0x0}, // [159]
/* idx: 160 */ {0x0, 0x0, 0x100000000, 0x0}, // [160]
/* idx: 161 */ {0x0, 0x0, 0x200000000, 0x0}, // [161]
/* idx: 162 */ {0x0, 0x0, 0x400000000, 0x0}, // [162]
/* idx: 163 */ {0x0, 0x0, 0x800000000, 0x0}, // [163]
/* idx: 164 */ {0x0, 0x0, 0x1000000000, 0x0}, // [164]
/* idx: 165 */ {0x0, 0x0, 0x2000000000, 0x0}, // [165]
/* idx: 166 */ {0x0, 0x0, 0x4000000000, 0x0}, // [166]
/* idx: 167 */ {0x0, 0x0, 0x8000000000, 0x0}, // [167]
/* idx: 168 */ {0x0, 0x0, 0x10000000000, 0x0}, // [168]
/* idx: 169 */ {0x0, 0x0, 0x20000000000, 0x0}, // [169]
/* idx: 170 */ {0x0, 0x0, 0x40000000000, 0x0}, // [170]
/* idx: 171 */ {0x0, 0x0, 0x80000000000, 0x0}, // [171]
/* idx: 172 */ {0x0, 0x0, 0x100000000000, 0x0}, // [172]
/* idx: 173 */ {0x0, 0x0, 0x200000000000, 0x0}, // [173]
/* idx: 174 */ {0x0, 0x0, 0x400000000000, 0x0}, // [174]
/* idx: 175 */ {0x0, 0x0, 0x800000000000, 0x0}, // [175]
/* idx: 176 */ {0x0, 0x0, 0x1000000000000, 0x0}, // [176]
/* idx: 177 */ {0x0, 0x0, 0x2000000000000, 0x0}, // [177]
/* idx: 178 */ {0x0, 0x0, 0x4000000000000, 0x0}, // [178]
/* idx: 179 */ {0x0, 0x0, 0x8000000000000, 0x0}, // [179]
/* idx: 180 */ {0x0, 0x0, 0x10000000000000, 0x0}, // [180]
/* idx: 181 */ {0x0, 0x0, 0x20000000000000, 0x0}, // [181]
/* idx: 182 */ {0x0, 0x0, 0x40000000000000, 0x0}, // [182]
/* idx: 183 */ {0x0, 0x0, 0x80000000000000, 0x0}, // [183]
/* idx: 184 */ {0x0, 0x0, 0x100000000000000, 0x0}, // [184]
/* idx: 185 */ {0x0, 0x0, 0x200000000000000, 0x0}, // [185]
/* idx: 186 */ {0x0, 0x0, 0x400000000000000, 0x0}, // [186]
/* idx: 187 */ {0x0, 0x0, 0x800000000000000, 0x0}, // [187]
/* idx: 188 */ {0x0, 0x0, 0x1000000000000000, 0x0}, // [188]
/* idx: 189 */ {0x0, 0x0, 0x2000000000000000, 0x0}, // [189]
/* idx: 190 */ {0x0, 0x0, 0x4000000000000000, 0x0}, // [190]
/* idx: 191 */ {0x0, 0x0, 0x8000000000000000, 0x0}, // [191]
/* idx: 192 */ {0x0, 0x0, 0x0, 0x1}, // [192]
/* idx: 193 */ {0x0, 0x0, 0x0, 0x2}, // [193]
/* idx: 194 */ {0x0, 0x0, 0x0, 0x4}, // [194]
/* idx: 195 */ {0x0, 0x0, 0x0, 0x8}, // [195]
/* idx: 196 */ {0x0, 0x0, 0x0, 0x10}, // [196]
/* idx: 197 */ {0x0, 0x0, 0x0, 0x20}, // [197]
/* idx: 198 */ {0x0, 0x0, 0x0, 0x40}, // [198]
/* idx: 199 */ {0x0, 0x0, 0x0, 0x80}, // [199]
/* idx: 200 */ {0x0, 0x0, 0x0, 0x100}, // [200]
/* idx: 201 */ {0x0, 0x0, 0x0, 0x200}, // [201]
/* idx: 202 */ {0x0, 0x0, 0x0, 0x400}, // [202]
/* idx: 203 */ {0x0, 0x0, 0x0, 0x800}, // [203]
/* idx: 204 */ {0x0, 0x0, 0x0, 0x1000}, // [204]
/* idx: 205 */ {0x0, 0x0, 0x0, 0x2000}, // [205]
/* idx: 206 */ {0x0, 0x0, 0x0, 0x4000}, // [206]
/* idx: 207 */ {0x0, 0x0, 0x0, 0x8000}, // [207]
/* idx: 208 */ {0x0, 0x0, 0x0, 0x10000}, // [208]
/* idx: 209 */ {0x0, 0x0, 0x0, 0x20000}, // [209]
/* idx: 210 */ {0x0, 0x0, 0x0, 0x40000}, // [210]
/* idx: 211 */ {0x0, 0x0, 0x0, 0x80000}, // [211]
/* idx: 212 */ {0x0, 0x0, 0x0, 0x100000}, // [212]
/* idx: 213 */ {0x0, 0x0, 0x0, 0x200000}, // [213]
/* idx: 214 */ {0x0, 0x0, 0x0, 0x400000}, // [214]
/* idx: 215 */ {0x0, 0x0, 0x0, 0x800000}, // [215]
/* idx: 216 */ {0x0, 0x0, 0x0, 0x1000000}, // [216]
/* idx: 217 */ {0x0, 0x0, 0x0, 0x2000000}, // [217]
/* idx: 218 */ {0x0, 0x0, 0x0, 0x4000000}, // [218]
/* idx: 219 */ {0x0, 0x0, 0x0, 0x8000000}, // [219]
/* idx: 220 */ {0x0, 0x0, 0x0, 0x10000000}, // [220]
/* idx: 221 */ {0x0, 0x0, 0x0, 0x20000000}, // [221]
/* idx: 222 */ {0x0, 0x0, 0x0, 0x40000000}, // [222]
/* idx: 223 */ {0x0, 0x0, 0x0, 0x80000000}, // [223]
/* idx: 224 */ {0x0, 0x0, 0x0, 0x100000000}, // [224]
/* idx: 225 */ {0x0, 0x0, 0x0, 0x200000000}, // [225]
/* idx: 226 */ {0x0, 0x0, 0x0, 0x400000000}, // [226]
/* idx: 227 */ {0x0, 0x0, 0x0, 0x800000000}, // [227]
/* idx: 228 */ {0x0, 0x0, 0x0, 0x1000000000}, // [228]
/* idx: 229 */ {0x0, 0x0, 0x0, 0x2000000000}, // [229]
/* idx: 230 */ {0x0, 0x0, 0x0, 0x4000000000}, // [230]
/* idx: 231 */ {0x0, 0x0, 0x0, 0x8000000000}, // [231]
/* idx: 232 */ {0x0, 0x0, 0x0, 0x10000000000}, // [232]
/* idx: 233 */ {0x0, 0x0, 0x0, 0x20000000000}, // [233]
/* idx: 234 */ {0x0, 0x0, 0x0, 0x40000000000}, // [234]
/* idx: 235 */ {0x0, 0x0, 0x0, 0x80000000000}, // [235]
/* idx: 236 */ {0x0, 0x0, 0x0, 0x100000000000}, // [236]
/* idx: 237 */ {0x0, 0x0, 0x0, 0x200000000000}, // [237]
/* idx: 238 */ {0x0, 0x0, 0x0, 0x400000000000}, // [238]
/* idx: 239 */ {0x0, 0x0, 0x0, 0x800000000000}, // [239]
/* idx: 240 */ {0x0, 0x0, 0x0, 0x1000000000000}, // [240]
/* idx: 241 */ {0x0, 0x0, 0x0, 0x2000000000000}, // [241]
/* idx: 242 */ {0x0, 0x0, 0x0, 0x4000000000000}, // [242]
/* idx: 243 */ {0x0, 0x0, 0x0, 0x8000000000000}, // [243]
/* idx: 244 */ {0x0, 0x0, 0x0, 0x10000000000000}, // [244]
/* idx: 245 */ {0x0, 0x0, 0x0, 0x20000000000000}, // [245]
/* idx: 246 */ {0x0, 0x0, 0x0, 0x40000000000000}, // [246]
/* idx: 247 */ {0x0, 0x0, 0x0, 0x80000000000000}, // [247]
/* idx: 248 */ {0x0, 0x0, 0x0, 0x100000000000000}, // [248]
/* idx: 249 */ {0x0, 0x0, 0x0, 0x200000000000000}, // [249]
/* idx: 250 */ {0x0, 0x0, 0x0, 0x400000000000000}, // [250]
/* idx: 251 */ {0x0, 0x0, 0x0, 0x800000000000000}, // [251]
/* idx: 252 */ {0x0, 0x0, 0x0, 0x1000000000000000}, // [252]
/* idx: 253 */ {0x0, 0x0, 0x0, 0x2000000000000000}, // [253]
/* idx: 254 */ {0x0, 0x0, 0x0, 0x4000000000000000}, // [254]
/* idx: 255 */ {0x0, 0x0, 0x0, 0x8000000000000000}, // [255]
}
// FringeRoutesLookupTbl, the second 256 Bits, see also the PfxRoutesLookupTbl for the first 256 Bits
// we split 512 bits into 2×256 to leverage BitSet256 optimizations.
//
//nolint:gochecknoglobals // Precomputed read‑only table used in hot paths.
var FringeRoutesLookupTbl = [256]bitset.BitSet256{
/* idx: 0 */ {0x0, 0x0, 0x0, 0x0}, // invalid
/* idx: 1 */ {0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff}, // [0, 1, 2, 3, 4, ... ]
/* idx: 2 */ {0xffffffffffffffff, 0xffffffffffffffff, 0x0, 0x0}, // [0, 1, 2, 3, 4, ... ]
/* idx: 3 */ {0x0, 0x0, 0xffffffffffffffff, 0xffffffffffffffff}, // [128, 129, 130, 131, 132, ... ]
/* idx: 4 */ {0xffffffffffffffff, 0x0, 0x0, 0x0}, // [0, 1, 2, 3, 4, ... ]
/* idx: 5 */ {0x0, 0xffffffffffffffff, 0x0, 0x0}, // [64, 65, 66, 67, 68, ... ]
/* idx: 6 */ {0x0, 0x0, 0xffffffffffffffff, 0x0}, // [128, 129, 130, 131, 132, ... ]
/* idx: 7 */ {0x0, 0x0, 0x0, 0xffffffffffffffff}, // [192, 193, 194, 195, 196, ... ]
/* idx: 8 */ {0xffffffff, 0x0, 0x0, 0x0}, // [0, 1, 2, 3, 4, ... ]
/* idx: 9 */ {0xffffffff00000000, 0x0, 0x0, 0x0}, // [32, 33, 34, 35, 36, ... ]
/* idx: 10 */ {0x0, 0xffffffff, 0x0, 0x0}, // [64, 65, 66, 67, 68, ... ]
/* idx: 11 */ {0x0, 0xffffffff00000000, 0x0, 0x0}, // [96, 97, 98, 99, 100, ... ]
/* idx: 12 */ {0x0, 0x0, 0xffffffff, 0x0}, // [128, 129, 130, 131, 132, ... ]
/* idx: 13 */ {0x0, 0x0, 0xffffffff00000000, 0x0}, // [160, 161, 162, 163, 164, ... ]
/* idx: 14 */ {0x0, 0x0, 0x0, 0xffffffff}, // [192, 193, 194, 195, 196, ... ]
/* idx: 15 */ {0x0, 0x0, 0x0, 0xffffffff00000000}, // [224, 225, 226, 227, 228, ... ]
/* idx: 16 */ {0xffff, 0x0, 0x0, 0x0}, // [0, 1, 2, 3, 4, ... ]
/* idx: 17 */ {0xffff0000, 0x0, 0x0, 0x0}, // [16, 17, 18, 19, 20, ... ]
/* idx: 18 */ {0xffff00000000, 0x0, 0x0, 0x0}, // [32, 33, 34, 35, 36, ... ]
/* idx: 19 */ {0xffff000000000000, 0x0, 0x0, 0x0}, // [48, 49, 50, 51, 52, ... ]
/* idx: 20 */ {0x0, 0xffff, 0x0, 0x0}, // [64, 65, 66, 67, 68, ... ]
/* idx: 21 */ {0x0, 0xffff0000, 0x0, 0x0}, // [80, 81, 82, 83, 84, ... ]
/* idx: 22 */ {0x0, 0xffff00000000, 0x0, 0x0}, // [96, 97, 98, 99, 100, ... ]
/* idx: 23 */ {0x0, 0xffff000000000000, 0x0, 0x0}, // [112, 113, 114, 115, 116, ... ]
/* idx: 24 */ {0x0, 0x0, 0xffff, 0x0}, // [128, 129, 130, 131, 132, ... ]
/* idx: 25 */ {0x0, 0x0, 0xffff0000, 0x0}, // [144, 145, 146, 147, 148, ... ]
/* idx: 26 */ {0x0, 0x0, 0xffff00000000, 0x0}, // [160, 161, 162, 163, 164, ... ]
/* idx: 27 */ {0x0, 0x0, 0xffff000000000000, 0x0}, // [176, 177, 178, 179, 180, ... ]
/* idx: 28 */ {0x0, 0x0, 0x0, 0xffff}, // [192, 193, 194, 195, 196, ... ]
/* idx: 29 */ {0x0, 0x0, 0x0, 0xffff0000}, // [208, 209, 210, 211, 212, ... ]
/* idx: 30 */ {0x0, 0x0, 0x0, 0xffff00000000}, // [224, 225, 226, 227, 228, ... ]
/* idx: 31 */ {0x0, 0x0, 0x0, 0xffff000000000000}, // [240, 241, 242, 243, 244, ... ]
/* idx: 32 */ {0xff, 0x0, 0x0, 0x0}, // [0 1 2 3 4 5 6 7]
/* idx: 33 */ {0xff00, 0x0, 0x0, 0x0}, // [8 9 10 11 12 13 14 15]
/* idx: 34 */ {0xff0000, 0x0, 0x0, 0x0}, // [16 17 18 19 20 21 22 23]
/* idx: 35 */ {0xff000000, 0x0, 0x0, 0x0}, // [24 25 26 27 28 29 30 31]
/* idx: 36 */ {0xff00000000, 0x0, 0x0, 0x0}, // [32 33 34 35 36 37 38 39]
/* idx: 37 */ {0xff0000000000, 0x0, 0x0, 0x0}, // [40 41 42 43 44 45 46 47]
/* idx: 38 */ {0xff000000000000, 0x0, 0x0, 0x0}, // [48 49 50 51 52 53 54 55]
/* idx: 39 */ {0xff00000000000000, 0x0, 0x0, 0x0}, // [56 57 58 59 60 61 62 63]
/* idx: 40 */ {0x0, 0xff, 0x0, 0x0}, // [64 65 66 67 68 69 70 71]
/* idx: 41 */ {0x0, 0xff00, 0x0, 0x0}, // [72 73 74 75 76 77 78 79]
/* idx: 42 */ {0x0, 0xff0000, 0x0, 0x0}, // [80 81 82 83 84 85 86 87]
/* idx: 43 */ {0x0, 0xff000000, 0x0, 0x0}, // [88 89 90 91 92 93 94 95]
/* idx: 44 */ {0x0, 0xff00000000, 0x0, 0x0}, // [96 97 98 99 100 101 102 103]
/* idx: 45 */ {0x0, 0xff0000000000, 0x0, 0x0}, // [104 105 106 107 108 109 110 111]
/* idx: 46 */ {0x0, 0xff000000000000, 0x0, 0x0}, // [112 113 114 115 116 117 118 119]
/* idx: 47 */ {0x0, 0xff00000000000000, 0x0, 0x0}, // [120 121 122 123 124 125 126 127]
/* idx: 48 */ {0x0, 0x0, 0xff, 0x0}, // [128 129 130 131 132 133 134 135]
/* idx: 49 */ {0x0, 0x0, 0xff00, 0x0}, // [136 137 138 139 140 141 142 143]
/* idx: 50 */ {0x0, 0x0, 0xff0000, 0x0}, // [144 145 146 147 148 149 150 151]
/* idx: 51 */ {0x0, 0x0, 0xff000000, 0x0}, // [152 153 154 155 156 157 158 159]
/* idx: 52 */ {0x0, 0x0, 0xff00000000, 0x0}, // [160 161 162 163 164 165 166 167]
/* idx: 53 */ {0x0, 0x0, 0xff0000000000, 0x0}, // [168 169 170 171 172 173 174 175]
/* idx: 54 */ {0x0, 0x0, 0xff000000000000, 0x0}, // [176 177 178 179 180 181 182 183]
/* idx: 55 */ {0x0, 0x0, 0xff00000000000000, 0x0}, // [184 185 186 187 188 189 190 191]
/* idx: 56 */ {0x0, 0x0, 0x0, 0xff}, // [192 193 194 195 196 197 198 199]
/* idx: 57 */ {0x0, 0x0, 0x0, 0xff00}, // [200 201 202 203 204 205 206 207]
/* idx: 58 */ {0x0, 0x0, 0x0, 0xff0000}, // [208 209 210 211 212 213 214 215]
/* idx: 59 */ {0x0, 0x0, 0x0, 0xff000000}, // [216 217 218 219 220 221 222 223]
/* idx: 60 */ {0x0, 0x0, 0x0, 0xff00000000}, // [224 225 226 227 228 229 230 231]
/* idx: 61 */ {0x0, 0x0, 0x0, 0xff0000000000}, // [232 233 234 235 236 237 238 239]
/* idx: 62 */ {0x0, 0x0, 0x0, 0xff000000000000}, // [240 241 242 243 244 245 246 247]
/* idx: 63 */ {0x0, 0x0, 0x0, 0xff00000000000000}, // [248 249 250 251 252 253 254 255]
/* idx: 64 */ {0xf, 0x0, 0x0, 0x0}, // [0 1 2 3]
/* idx: 65 */ {0xf0, 0x0, 0x0, 0x0}, // [4 5 6 7]
/* idx: 66 */ {0xf00, 0x0, 0x0, 0x0}, // [8 9 10 11]
/* idx: 67 */ {0xf000, 0x0, 0x0, 0x0}, // [12 13 14 15]
/* idx: 68 */ {0xf0000, 0x0, 0x0, 0x0}, // [16 17 18 19]
/* idx: 69 */ {0xf00000, 0x0, 0x0, 0x0}, // [20 21 22 23]
/* idx: 70 */ {0xf000000, 0x0, 0x0, 0x0}, // [24 25 26 27]
/* idx: 71 */ {0xf0000000, 0x0, 0x0, 0x0}, // [28 29 30 31]
/* idx: 72 */ {0xf00000000, 0x0, 0x0, 0x0}, // [32 33 34 35]
/* idx: 73 */ {0xf000000000, 0x0, 0x0, 0x0}, // [36 37 38 39]
/* idx: 74 */ {0xf0000000000, 0x0, 0x0, 0x0}, // [40 41 42 43]
/* idx: 75 */ {0xf00000000000, 0x0, 0x0, 0x0}, // [44 45 46 47]
/* idx: 76 */ {0xf000000000000, 0x0, 0x0, 0x0}, // [48 49 50 51]
/* idx: 77 */ {0xf0000000000000, 0x0, 0x0, 0x0}, // [52 53 54 55]
/* idx: 78 */ {0xf00000000000000, 0x0, 0x0, 0x0}, // [56 57 58 59]
/* idx: 79 */ {0xf000000000000000, 0x0, 0x0, 0x0}, // [60 61 62 63]
/* idx: 80 */ {0x0, 0xf, 0x0, 0x0}, // [64 65 66 67]
/* idx: 81 */ {0x0, 0xf0, 0x0, 0x0}, // [68 69 70 71]
/* idx: 82 */ {0x0, 0xf00, 0x0, 0x0}, // [72 73 74 75]
/* idx: 83 */ {0x0, 0xf000, 0x0, 0x0}, // [76 77 78 79]
/* idx: 84 */ {0x0, 0xf0000, 0x0, 0x0}, // [80 81 82 83]
/* idx: 85 */ {0x0, 0xf00000, 0x0, 0x0}, // [84 85 86 87]
/* idx: 86 */ {0x0, 0xf000000, 0x0, 0x0}, // [88 89 90 91]
/* idx: 87 */ {0x0, 0xf0000000, 0x0, 0x0}, // [92 93 94 95]
/* idx: 88 */ {0x0, 0xf00000000, 0x0, 0x0}, // [96 97 98 99]
/* idx: 89 */ {0x0, 0xf000000000, 0x0, 0x0}, // [100 101 102 103]
/* idx: 90 */ {0x0, 0xf0000000000, 0x0, 0x0}, // [104 105 106 107]
/* idx: 91 */ {0x0, 0xf00000000000, 0x0, 0x0}, // [108 109 110 111]
/* idx: 92 */ {0x0, 0xf000000000000, 0x0, 0x0}, // [112 113 114 115]
/* idx: 93 */ {0x0, 0xf0000000000000, 0x0, 0x0}, // [116 117 118 119]
/* idx: 94 */ {0x0, 0xf00000000000000, 0x0, 0x0}, // [120 121 122 123]
/* idx: 95 */ {0x0, 0xf000000000000000, 0x0, 0x0}, // [124 125 126 127]
/* idx: 96 */ {0x0, 0x0, 0xf, 0x0}, // [128 129 130 131]
/* idx: 97 */ {0x0, 0x0, 0xf0, 0x0}, // [132 133 134 135]
/* idx: 98 */ {0x0, 0x0, 0xf00, 0x0}, // [136 137 138 139]
/* idx: 99 */ {0x0, 0x0, 0xf000, 0x0}, // [140 141 142 143]
/* idx: 100 */ {0x0, 0x0, 0xf0000, 0x0}, // [144 145 146 147]
/* idx: 101 */ {0x0, 0x0, 0xf00000, 0x0}, // [148 149 150 151]
/* idx: 102 */ {0x0, 0x0, 0xf000000, 0x0}, // [152 153 154 155]
/* idx: 103 */ {0x0, 0x0, 0xf0000000, 0x0}, // [156 157 158 159]
/* idx: 104 */ {0x0, 0x0, 0xf00000000, 0x0}, // [160 161 162 163]
/* idx: 105 */ {0x0, 0x0, 0xf000000000, 0x0}, // [164 165 166 167]
/* idx: 106 */ {0x0, 0x0, 0xf0000000000, 0x0}, // [168 169 170 171]
/* idx: 107 */ {0x0, 0x0, 0xf00000000000, 0x0}, // [172 173 174 175]
/* idx: 108 */ {0x0, 0x0, 0xf000000000000, 0x0}, // [176 177 178 179]
/* idx: 109 */ {0x0, 0x0, 0xf0000000000000, 0x0}, // [180 181 182 183]
/* idx: 110 */ {0x0, 0x0, 0xf00000000000000, 0x0}, // [184 185 186 187]
/* idx: 111 */ {0x0, 0x0, 0xf000000000000000, 0x0}, // [188 189 190 191]
/* idx: 112 */ {0x0, 0x0, 0x0, 0xf}, // [192 193 194 195]
/* idx: 113 */ {0x0, 0x0, 0x0, 0xf0}, // [196 197 198 199]
/* idx: 114 */ {0x0, 0x0, 0x0, 0xf00}, // [200 201 202 203]
/* idx: 115 */ {0x0, 0x0, 0x0, 0xf000}, // [204 205 206 207]
/* idx: 116 */ {0x0, 0x0, 0x0, 0xf0000}, // [208 209 210 211]
/* idx: 117 */ {0x0, 0x0, 0x0, 0xf00000}, // [212 213 214 215]
/* idx: 118 */ {0x0, 0x0, 0x0, 0xf000000}, // [216 217 218 219]
/* idx: 119 */ {0x0, 0x0, 0x0, 0xf0000000}, // [220 221 222 223]
/* idx: 120 */ {0x0, 0x0, 0x0, 0xf00000000}, // [224 225 226 227]
/* idx: 121 */ {0x0, 0x0, 0x0, 0xf000000000}, // [228 229 230 231]
/* idx: 122 */ {0x0, 0x0, 0x0, 0xf0000000000}, // [232 233 234 235]
/* idx: 123 */ {0x0, 0x0, 0x0, 0xf00000000000}, // [236 237 238 239]
/* idx: 124 */ {0x0, 0x0, 0x0, 0xf000000000000}, // [240 241 242 243]
/* idx: 125 */ {0x0, 0x0, 0x0, 0xf0000000000000}, // [244 245 246 247]
/* idx: 126 */ {0x0, 0x0, 0x0, 0xf00000000000000}, // [248 249 250 251]
/* idx: 127 */ {0x0, 0x0, 0x0, 0xf000000000000000}, // [252 253 254 255]
/* idx: 128 */ {0x3, 0x0, 0x0, 0x0}, // [0 1]
/* idx: 129 */ {0xc, 0x0, 0x0, 0x0}, // [2 3]
/* idx: 130 */ {0x30, 0x0, 0x0, 0x0}, // [4 5]
/* idx: 131 */ {0xc0, 0x0, 0x0, 0x0}, // [6 7]
/* idx: 132 */ {0x300, 0x0, 0x0, 0x0}, // [8 9]
/* idx: 133 */ {0xc00, 0x0, 0x0, 0x0}, // [10 11]
/* idx: 134 */ {0x3000, 0x0, 0x0, 0x0}, // [12 13]
/* idx: 135 */ {0xc000, 0x0, 0x0, 0x0}, // [14 15]
/* idx: 136 */ {0x30000, 0x0, 0x0, 0x0}, // [16 17]
/* idx: 137 */ {0xc0000, 0x0, 0x0, 0x0}, // [18 19]
/* idx: 138 */ {0x300000, 0x0, 0x0, 0x0}, // [20 21]
/* idx: 139 */ {0xc00000, 0x0, 0x0, 0x0}, // [22 23]
/* idx: 140 */ {0x3000000, 0x0, 0x0, 0x0}, // [24 25]
/* idx: 141 */ {0xc000000, 0x0, 0x0, 0x0}, // [26 27]
/* idx: 142 */ {0x30000000, 0x0, 0x0, 0x0}, // [28 29]
/* idx: 143 */ {0xc0000000, 0x0, 0x0, 0x0}, // [30 31]
/* idx: 144 */ {0x300000000, 0x0, 0x0, 0x0}, // [32 33]
/* idx: 145 */ {0xc00000000, 0x0, 0x0, 0x0}, // [34 35]
/* idx: 146 */ {0x3000000000, 0x0, 0x0, 0x0}, // [36 37]
/* idx: 147 */ {0xc000000000, 0x0, 0x0, 0x0}, // [38 39]
/* idx: 148 */ {0x30000000000, 0x0, 0x0, 0x0}, // [40 41]
/* idx: 149 */ {0xc0000000000, 0x0, 0x0, 0x0}, // [42 43]
/* idx: 150 */ {0x300000000000, 0x0, 0x0, 0x0}, // [44 45]
/* idx: 151 */ {0xc00000000000, 0x0, 0x0, 0x0}, // [46 47]
/* idx: 152 */ {0x3000000000000, 0x0, 0x0, 0x0}, // [48 49]
/* idx: 153 */ {0xc000000000000, 0x0, 0x0, 0x0}, // [50 51]
/* idx: 154 */ {0x30000000000000, 0x0, 0x0, 0x0}, // [52 53]
/* idx: 155 */ {0xc0000000000000, 0x0, 0x0, 0x0}, // [54 55]
/* idx: 156 */ {0x300000000000000, 0x0, 0x0, 0x0}, // [56 57]
/* idx: 157 */ {0xc00000000000000, 0x0, 0x0, 0x0}, // [58 59]
/* idx: 158 */ {0x3000000000000000, 0x0, 0x0, 0x0}, // [60 61]
/* idx: 159 */ {0xc000000000000000, 0x0, 0x0, 0x0}, // [62 63]
/* idx: 160 */ {0x0, 0x3, 0x0, 0x0}, // [64 65]
/* idx: 161 */ {0x0, 0xc, 0x0, 0x0}, // [66 67]
/* idx: 162 */ {0x0, 0x30, 0x0, 0x0}, // [68 69]
/* idx: 163 */ {0x0, 0xc0, 0x0, 0x0}, // [70 71]
/* idx: 164 */ {0x0, 0x300, 0x0, 0x0}, // [72 73]
/* idx: 165 */ {0x0, 0xc00, 0x0, 0x0}, // [74 75]
/* idx: 166 */ {0x0, 0x3000, 0x0, 0x0}, // [76 77]
/* idx: 167 */ {0x0, 0xc000, 0x0, 0x0}, // [78 79]
/* idx: 168 */ {0x0, 0x30000, 0x0, 0x0}, // [80 81]
/* idx: 169 */ {0x0, 0xc0000, 0x0, 0x0}, // [82 83]
/* idx: 170 */ {0x0, 0x300000, 0x0, 0x0}, // [84 85]
/* idx: 171 */ {0x0, 0xc00000, 0x0, 0x0}, // [86 87]
/* idx: 172 */ {0x0, 0x3000000, 0x0, 0x0}, // [88 89]
/* idx: 173 */ {0x0, 0xc000000, 0x0, 0x0}, // [90 91]
/* idx: 174 */ {0x0, 0x30000000, 0x0, 0x0}, // [92 93]
/* idx: 175 */ {0x0, 0xc0000000, 0x0, 0x0}, // [94 95]
/* idx: 176 */ {0x0, 0x300000000, 0x0, 0x0}, // [96 97]
/* idx: 177 */ {0x0, 0xc00000000, 0x0, 0x0}, // [98 99]
/* idx: 178 */ {0x0, 0x3000000000, 0x0, 0x0}, // [100 101]
/* idx: 179 */ {0x0, 0xc000000000, 0x0, 0x0}, // [102 103]
/* idx: 180 */ {0x0, 0x30000000000, 0x0, 0x0}, // [104 105]
/* idx: 181 */ {0x0, 0xc0000000000, 0x0, 0x0}, // [106 107]
/* idx: 182 */ {0x0, 0x300000000000, 0x0, 0x0}, // [108 109]
/* idx: 183 */ {0x0, 0xc00000000000, 0x0, 0x0}, // [110 111]
/* idx: 184 */ {0x0, 0x3000000000000, 0x0, 0x0}, // [112 113]
/* idx: 185 */ {0x0, 0xc000000000000, 0x0, 0x0}, // [114 115]
/* idx: 186 */ {0x0, 0x30000000000000, 0x0, 0x0}, // [116 117]
/* idx: 187 */ {0x0, 0xc0000000000000, 0x0, 0x0}, // [118 119]
/* idx: 188 */ {0x0, 0x300000000000000, 0x0, 0x0}, // [120 121]
/* idx: 189 */ {0x0, 0xc00000000000000, 0x0, 0x0}, // [122 123]
/* idx: 190 */ {0x0, 0x3000000000000000, 0x0, 0x0}, // [124 125]
/* idx: 191 */ {0x0, 0xc000000000000000, 0x0, 0x0}, // [126 127]
/* idx: 192 */ {0x0, 0x0, 0x3, 0x0}, // [128 129]
/* idx: 193 */ {0x0, 0x0, 0xc, 0x0}, // [130 131]
/* idx: 194 */ {0x0, 0x0, 0x30, 0x0}, // [132 133]
/* idx: 195 */ {0x0, 0x0, 0xc0, 0x0}, // [134 135]
/* idx: 196 */ {0x0, 0x0, 0x300, 0x0}, // [136 137]
/* idx: 197 */ {0x0, 0x0, 0xc00, 0x0}, // [138 139]
/* idx: 198 */ {0x0, 0x0, 0x3000, 0x0}, // [140 141]
/* idx: 199 */ {0x0, 0x0, 0xc000, 0x0}, // [142 143]
/* idx: 200 */ {0x0, 0x0, 0x30000, 0x0}, // [144 145]
/* idx: 201 */ {0x0, 0x0, 0xc0000, 0x0}, // [146 147]
/* idx: 202 */ {0x0, 0x0, 0x300000, 0x0}, // [148 149]
/* idx: 203 */ {0x0, 0x0, 0xc00000, 0x0}, // [150 151]
/* idx: 204 */ {0x0, 0x0, 0x3000000, 0x0}, // [152 153]
/* idx: 205 */ {0x0, 0x0, 0xc000000, 0x0}, // [154 155]
/* idx: 206 */ {0x0, 0x0, 0x30000000, 0x0}, // [156 157]
/* idx: 207 */ {0x0, 0x0, 0xc0000000, 0x0}, // [158 159]
/* idx: 208 */ {0x0, 0x0, 0x300000000, 0x0}, // [160 161]
/* idx: 209 */ {0x0, 0x0, 0xc00000000, 0x0}, // [162 163]
/* idx: 210 */ {0x0, 0x0, 0x3000000000, 0x0}, // [164 165]
/* idx: 211 */ {0x0, 0x0, 0xc000000000, 0x0}, // [166 167]
/* idx: 212 */ {0x0, 0x0, 0x30000000000, 0x0}, // [168 169]
/* idx: 213 */ {0x0, 0x0, 0xc0000000000, 0x0}, // [170 171]
/* idx: 214 */ {0x0, 0x0, 0x300000000000, 0x0}, // [172 173]
/* idx: 215 */ {0x0, 0x0, 0xc00000000000, 0x0}, // [174 175]
/* idx: 216 */ {0x0, 0x0, 0x3000000000000, 0x0}, // [176 177]
/* idx: 217 */ {0x0, 0x0, 0xc000000000000, 0x0}, // [178 179]
/* idx: 218 */ {0x0, 0x0, 0x30000000000000, 0x0}, // [180 181]
/* idx: 219 */ {0x0, 0x0, 0xc0000000000000, 0x0}, // [182 183]
/* idx: 220 */ {0x0, 0x0, 0x300000000000000, 0x0}, // [184 185]
/* idx: 221 */ {0x0, 0x0, 0xc00000000000000, 0x0}, // [186 187]
/* idx: 222 */ {0x0, 0x0, 0x3000000000000000, 0x0}, // [188 189]
/* idx: 223 */ {0x0, 0x0, 0xc000000000000000, 0x0}, // [190 191]
/* idx: 224 */ {0x0, 0x0, 0x0, 0x3}, // [192 193]
/* idx: 225 */ {0x0, 0x0, 0x0, 0xc}, // [194 195]
/* idx: 226 */ {0x0, 0x0, 0x0, 0x30}, // [196 197]
/* idx: 227 */ {0x0, 0x0, 0x0, 0xc0}, // [198 199]
/* idx: 228 */ {0x0, 0x0, 0x0, 0x300}, // [200 201]
/* idx: 229 */ {0x0, 0x0, 0x0, 0xc00}, // [202 203]
/* idx: 230 */ {0x0, 0x0, 0x0, 0x3000}, // [204 205]
/* idx: 231 */ {0x0, 0x0, 0x0, 0xc000}, // [206 207]
/* idx: 232 */ {0x0, 0x0, 0x0, 0x30000}, // [208 209]
/* idx: 233 */ {0x0, 0x0, 0x0, 0xc0000}, // [210 211]
/* idx: 234 */ {0x0, 0x0, 0x0, 0x300000}, // [212 213]
/* idx: 235 */ {0x0, 0x0, 0x0, 0xc00000}, // [214 215]
/* idx: 236 */ {0x0, 0x0, 0x0, 0x3000000}, // [216 217]
/* idx: 237 */ {0x0, 0x0, 0x0, 0xc000000}, // [218 219]
/* idx: 238 */ {0x0, 0x0, 0x0, 0x30000000}, // [220 221]
/* idx: 239 */ {0x0, 0x0, 0x0, 0xc0000000}, // [222 223]
/* idx: 240 */ {0x0, 0x0, 0x0, 0x300000000}, // [224 225]
/* idx: 241 */ {0x0, 0x0, 0x0, 0xc00000000}, // [226 227]
/* idx: 242 */ {0x0, 0x0, 0x0, 0x3000000000}, // [228 229]
/* idx: 243 */ {0x0, 0x0, 0x0, 0xc000000000}, // [230 231]
/* idx: 244 */ {0x0, 0x0, 0x0, 0x30000000000}, // [232 233]
/* idx: 245 */ {0x0, 0x0, 0x0, 0xc0000000000}, // [234 235]
/* idx: 246 */ {0x0, 0x0, 0x0, 0x300000000000}, // [236 237]
/* idx: 247 */ {0x0, 0x0, 0x0, 0xc00000000000}, // [238 239]
/* idx: 248 */ {0x0, 0x0, 0x0, 0x3000000000000}, // [240 241]
/* idx: 249 */ {0x0, 0x0, 0x0, 0xc000000000000}, // [242 243]
/* idx: 250 */ {0x0, 0x0, 0x0, 0x30000000000000}, // [244 245]
/* idx: 251 */ {0x0, 0x0, 0x0, 0xc0000000000000}, // [246 247]
/* idx: 252 */ {0x0, 0x0, 0x0, 0x300000000000000}, // [248 249]
/* idx: 253 */ {0x0, 0x0, 0x0, 0xc00000000000000}, // [250 251]
/* idx: 254 */ {0x0, 0x0, 0x0, 0x3000000000000000}, // [252 253]
/* idx: 255 */ {0x0, 0x0, 0x0, 0xc000000000000000}, // [254 255]
}
+236
View File
@@ -0,0 +1,236 @@
//go:build generate
//go:generate go run $GOFILE
// Copyright (c) 2026 Karl Gaissmaier
// SPDX-License-Identifier: MIT
// Package main generates lookup tables for route allocation in the BART routing table.
//
// This generator creates two precomputed lookup tables (PfxRoutesLookupTbl and
// FringeRoutesLookupTbl) that enable fast prefix coverage queries. Each table maps
// a base index (1..255) to a bitset containing all more specific indices covered
// by that prefix.
//
// The tables model a complete binary tree where:
// - Indices 1..255 represent prefixes up to /7
// - Indices 256..511 represent /8 prefixes (stored separately as "fringe")
package main
import (
"fmt"
"os"
"os/exec"
"strings"
"text/template"
"github.com/gaissmai/bart/internal/bitset"
)
var (
thisFname = os.Getenv("GOFILE")
outFname = "allotroutesgenerated.go"
)
var (
// prefixAllotTbl holds the bitsets for indices 1..255 (prefixes up to /7).
// For each index idx, the bitset contains all descendant indices in the
// range 1..255 that are covered by the prefix at idx.
prefixAllotTbl [256]bitset.BitSet256
// fringeAllotTbl holds the bitsets for indices 1..255, but tracks descendants
// in the range 256..511 (/8 prefixes). The bitset at index idx contains all
// /8 fringe indices (minus 256 offset) covered by the prefix at idx.
fringeAllotTbl [256]bitset.BitSet256
)
// data holds the template variables for code generation
var data struct {
File string
PrefixAllotTable string
FringeAllotTable string
}
// colored prefix for informational and error messages
const (
INFO = "\x1b[34mINFO:\x1b[0m"
DIE = "\x1b[31mERROR:\x1b[0m"
)
func main() {
outFile, err := os.Create(outFname)
if err != nil {
fmt.Fprintf(os.Stderr, "%s %v\n", DIE, err)
os.Exit(1)
}
// Generate both lookup tables using the stack-based algorithm
prefixAllotTbl, fringeAllotTbl = genAllotTables()
// Populate template data
data.File = thisFname
data.PrefixAllotTable = asString(prefixAllotTbl)
data.FringeAllotTable = asString(fringeAllotTbl)
// Parse the template ...
t := template.Must(template.New("foo").Parse(codeTemplate))
// ... and execute it with our data
err = t.Execute(outFile, data)
if err != nil {
fmt.Fprintf(os.Stderr, "%s %v\n", DIE, err)
_ = outFile.Close()
os.Exit(1)
}
// Ensure the file is properly closed before formatting
if err := outFile.Close(); err != nil {
fmt.Fprintf(os.Stderr, "%s %v\n", DIE, err)
os.Exit(1)
}
fmt.Fprintf(os.Stdout, "%s ✓ Generated %s\n", INFO, outFname)
// Run goimports to organize imports and format the code
goimports := exec.Command("goimports", "-w", outFname)
fmt.Fprintf(os.Stdout, "%s Running goimports on %s\n", INFO, outFname)
if out, err := goimports.CombinedOutput(); err != nil {
fmt.Fprintf(os.Stderr, "%s %s\n", DIE, string(out))
os.Exit(1)
}
fmt.Fprintf(os.Stdout, "%s ✓ goimports completed\n", INFO)
// Run gofumpt for additional formatting consistency
gofumpt := exec.Command("gofumpt", "-w", outFname)
fmt.Fprintf(os.Stdout, "%s Running gofumpt on %s\n", INFO, outFname)
if out, err := gofumpt.CombinedOutput(); err != nil {
fmt.Fprintf(os.Stderr, "%s %s\n", DIE, string(out))
os.Exit(1)
}
fmt.Fprintf(os.Stdout, "%s ✓ gofumpt completed\n", INFO)
fmt.Println()
}
// genAllotTables generates both prefix and fringe allocation lookup tables.
//
// For each base index idx (1..255), it performs a breadth-first traversal of the
// complete binary tree rooted at idx, collecting all descendant indices. The
// traversal uses a stack-based approach instead of recursion.
//
// The algorithm:
// 1. Start with the root index idx in the stack
// 2. For each index j in the stack:
// - If j < 256: add j to prefixAllotTbl[idx]
// - If j >= 256: add (j-256) to fringeAllotTbl[idx]
// - If j < 256: push children (2*j and 2*j+1) onto the stack
// 3. Continue until the stack is exhausted
//
// This builds a complete binary tree where each node knows all its descendants,
// split into two ranges: [1..255] and [256..511].
//
// Returns the prefix table (indices 1..255) and fringe table (indices 256..511).
func genAllotTables() (prefix, fringe [256]bitset.BitSet256) {
prefixAllotTbl := [256]bitset.BitSet256{}
fringeAllotTbl := [256]bitset.BitSet256{}
for idx := 1; idx < 256; idx++ {
// allot algorithm, stack based instead of recursion
stack := make([]int, 0, 512)
stack = append(stack, idx)
for i := 0; i < len(stack); i++ {
j := stack[i]
if j < 256 {
prefixAllotTbl[idx].Set(uint8(j))
} else {
fringeAllotTbl[idx].Set(uint8(j - 256))
}
// max j is 511, so stop the duplication at 256 and above
if j >= 256 {
continue
}
// build a complete binary tree
// left: j*2
// right: (j*2)+1
stack = append(stack, j<<1, (j<<1)+1)
}
}
return prefixAllotTbl, fringeAllotTbl
}
// asString converts a lookup table to a formatted Go source string.
//
// Each entry in the table is formatted as:
//
// /* idx: N */ BitSet256{...}, // [list of set bits]
//
// For bitsets with 10 or more set bits, the list is truncated with "..." to
// keep the generated code readable. Index 0 is marked as "invalid" since no
// valid prefix maps to base index 0 in the BART structure.
//
// The returned string is ready to be embedded into the generated Go file.
func asString(tbl [256]bitset.BitSet256) string {
builder := strings.Builder{}
// Index 0 is invalid (no valid prefix maps to 0)
fmt.Fprintf(&builder, " /* idx: %3d */ %#v, // %s\n", 0, tbl[0], "invalid")
// Format each valid index with its bitset and the list of set bits, e.g.
// /* idx: 13 */ {0x204a, 0x0, 0x0, 0x0}, // [1 3 6 13]
for idx := 1; idx < 256; idx++ {
fmt.Fprintf(&builder, " /* idx: %3d */ %#v, // ", idx, tbl[idx])
bits := tbl[idx].Bits()
if len(bits) < 10 {
fmt.Fprintf(&builder, "%v\n", bits)
} else {
fmt.Fprint(&builder, "[")
for _, bit := range bits[:5] {
fmt.Fprintf(&builder, "%d, ", bit)
}
fmt.Fprintln(&builder, "... ]")
}
}
return builder.String()
}
// codeTemplate is the Go source template for the generated allotRoutes_gen.go file.
// It embeds the precomputed lookup tables and includes documentation about
// their structure and usage.
const codeTemplate = `// Code generated by {{.File}}; DO NOT EDIT.
// Copyright (c) 2026 Karl Gaissmaier
// SPDX-License-Identifier: MIT
package allot
// PfxRoutesLookupTbl is a lookup table of BitSet256 entries.
//
// It maps a baseIndex (1..255) to a bitset that contains all more
// specific baseIndices that are covered by the prefix at that index.
//
// The idea:
//
// If prefix P has baseIndex idx, then P covers all prefixes in the subtree
// rooted at idx. This table enumerates and encodes those indices.
//
// The table is split:
// - PfxRoutesLookupTbl holds prefixes up to /7
// - FringeRoutesLookupTbl holds /8 prefixes
//
// This structure allows for very fast set inclusion checks using simple bitwise AND.
//
//nolint:gochecknoglobals // Precomputed read‑only table used in hot paths.
var PfxRoutesLookupTbl = [256]bitset.BitSet256{
{{.PrefixAllotTable}} }
// FringeRoutesLookupTbl, the second 256 Bits, see also the PfxRoutesLookupTbl for the first 256 Bits
// we split 512 bits into 2×256 to leverage BitSet256 optimizations.
//
//nolint:gochecknoglobals // Precomputed read‑only table used in hot paths.
var FringeRoutesLookupTbl = [256]bitset.BitSet256{
{{.FringeAllotTable}} }
`
+153
View File
@@ -0,0 +1,153 @@
// Copyright (c) 2026 Karl Gaissmaier
// SPDX-License-Identifier: MIT
// Package art summarizes the functions and inverse functions
// for mapping between a prefix and a baseIndex.
//
// can inline OctetToIdx with cost 6
// can inline PfxToIdx with cost 10
// can inline NetMask with cost 13
// can inline PfxBits with cost 21
// can inline IdxToPfx with cost 39
// can inline IdxToRange with cost 71
package art
import "math/bits"
// PfxToIdx maps 8bit prefixes to numbers. The prefixes range from 0/0 to 255/7
// The return values range from 1 to 255.
//
// [0x0000_00001 .. 0x1111_1111] = [1 .. 255]
//
// example: octet/pfxLen: 160/3 = 0b1010_0000/3 => IdxToPfx(160/3) => 13
//
// 0b1010_0000 => 0b0000_0101
// ^^^ >> (8-3) ^^^
//
// 0b0000_0001 => 0b0000_1000
// ^ << 3 ^
// + -----------------------
// 0b0000_1101 = 13
//
// ATTENTION: For pfxLen values > 7 the result is undefined!
func PfxToIdx(octet, pfxLen uint8) uint8 {
// use '|' instead of '+', maybe a little bit faster
return octet>>(8-pfxLen) | 1<<pfxLen
}
// OctetToIdx maps octet/8 prefixes from [256..511] => [128..255] to start
// with the parent (>>1) in the complete binary tree.
//
// Same formula as PfxToIdx, but for octet/8 and a shift (>>1).
//
// octet>>(8-pfxLen) + 1<<pfxLen
// octet>>(8-8) + 1<<8
// octet + 256
//
// got to parent idx:
// (octet+256)>>1 == octet>>1 + 128
func OctetToIdx(octet uint8) uint8 {
// use '|' instead of '+', maybe a little bit faster
return octet>>1 | 128
}
// IdxToPfx returns the octet and prefix len of baseIdx.
// It's the inverse to PfxToIdx.
func IdxToPfx(idx uint8) (octet, pfxLen uint8) {
if idx == 0 {
panic("IdxToPfx: invalid idx 0")
}
// The prefix length corresponds to the number of leading bits in idx.
// bits.Len8 returns the number of bits needed to represent idx as binary,
// so we subtract 1 to recover the prefix length (which is always >= 0).
// Invariant: idx==0 is invalid
//nolint:gosec //G115: integer overflow conversion int -> uint8 (gosec)
pfxLen = uint8(bits.Len8(idx)) - 1
// Compute the number of bits to shift back to obtain the original octet.
shiftBits := 8 - pfxLen
// Extract the original prefix bits from idx by masking out the low bits.
mask := uint8(0xff) >> shiftBits
// Shift the masked prefix bits back to their original position.
octet = (idx & mask) << shiftBits
return octet, pfxLen
}
// PfxBits returns the bit position of a prefix represented by a base index at a given trie depth.
//
// Each trie level represents an 8-bit stride (one octet).
// The base index contains enough information to recover the prefix length.
// This function returns the full bit offset of the prefix in the address space.
//
// For example:
//
// depth = 2 (i.e. third trie level)
// idx = 13 (which encodes a prefix of length 3 bits within that stride)
//
// => PfxBits = 2*8 + 3 = 19
func PfxBits(depth int, idx uint8) uint8 {
// bits.Len8(idx) gives the number of significant bits (i.e. prefix length + 1)
// subtract 1 to get the actual prefix length used in this stride
pfxLenInStride := bits.Len8(idx) - 1
// Each trie level represents 8 bits (an octet), max depth is 16
baseBits := depth << 3 // same as depth * 8
// Total prefix length in bits, [0..128]
//nolint:gosec // G115: integer overflow conversion int -> uint8
return uint8(baseBits + pfxLenInStride)
}
// IdxToRange returns the first and last octets covered by a base index.
//
// The base index encodes a prefix of up to 8 bits inside a single stride (octet).
// This function computes the numerical start and end of the value range for that prefix.
//
// For example:
//
// - A 0-bit prefix (idx == 1) covers the full range: 0..255
//
// - A 3-bit prefix like 0b101xxxx (idx == 13) covers: 160..191
//
// idx := PfxToIdx(0b10100000, 3) // 13
// first, last := IdxToRange(13) // 160, 191
//
// Internally, this function decodes (octet, prefixLength) via IdxToPfx,
// then computes the broadcast address (last octet) by masking all bits below the prefix length.
func IdxToRange(idx uint8) (first, last uint8) {
// Decode the prefix base (octet) and length (number of fixed bits)
first, pfxLen := IdxToPfx(idx)
// Compute the "broadcast" value by filling trailing (host) bits with 1s.
// This gives the maximum octet value that still matches the prefix.
last = first | ^NetMask(pfxLen)
return first, last
}
// NetMask returns an 8-bit left-aligned network mask for the given number of prefix bits.
//
// For example:
//
// bits = 0 -> 0b00000000
// bits = 1 -> 0b10000000
// bits = 2 -> 0b11000000
// bits = 3 -> 0b11100000
// ...
// bits = 8 -> 0b11111111
//
// This mask is used to extract or identify the fixed (prefix) portion of an octet.
// The rightmost (8 - bits) bits are cleared (set to zero), and the upper 'bits' are set to 1.
func NetMask(bits uint8) uint8 {
if bits > 8 {
panic("NetMask: invalid bits > 8")
}
// Use a full 8-bit mask and shift left to clear trailing (host) bits.
// Shifts 0..8 on a uint8 are well-defined; safe for bits in [0..8].
return ^uint8(0) << (8 - bits)
}
-326
View File
@@ -1,326 +0,0 @@
// Copyright (c) 2024 Karl Gaissmaier
// SPDX-License-Identifier: MIT
// Package bitset implements bitsets, a mapping
// between non-negative integers and boolean values.
//
// Studied [github.com/bits-and-blooms/bitset] inside out
// and rewrote needed parts from scratch for this project.
//
// This implementation is smaller and faster as the more
// general [github.com/bits-and-blooms/bitset].
//
// All functions can be inlined!
//
// can inline BitSet.Set with cost 63
// can inline BitSet.Clear with cost 24
// can inline BitSet.Test with cost 26
// can inline BitSet.Rank0 with cost 66
// can inline BitSet.Clone with cost 7
// can inline BitSet.Compact with cost 35
// can inline BitSet.FirstSet with cost 25
// can inline BitSet.NextSet with cost 71
// can inline BitSet.AsSlice with cost 50
// can inline BitSet.All with cost 62
// can inline BitSet.IntersectsAny with cost 42
// can inline BitSet.IntersectionTop with cost 56
// can inline BitSet.IntersectionCardinality with cost 35
// can inline (*BitSet).InPlaceIntersection with cost 71
// can inline (*BitSet).InPlaceUnion with cost 77
// can inline BitSet.Size with cost 16
// can inline popcount with cost 12
// can inline popcountAnd with cost 30
package bitset
import (
"math/bits"
)
// A BitSet is a slice of words. This is an internal package
// with a wide open public API.
type BitSet []uint64
// xIdx calculates the index of i in a []uint64
// func wIdx(i uint) int {
// return int(i >> 6) // like (i / 64) but faster
// }
// bIdx calculates the index of i in a `uint64`
// func bIdx(i uint) uint {
// return i & 63 // like (i % 64) but faster
// }
//
// just as an explanation of the expressions,
//
// i>>6 or i<<6 and i&63
//
// not factored out as functions to make most of the methods
// inlineable with minimal costs.
// Set bit i to 1, the capacity of the bitset is increased accordingly.
func (b BitSet) Set(i uint) BitSet {
// grow?
if i >= uint(len(b)<<6) {
words := int((i + 64) >> 6)
switch {
case b == nil:
b = make([]uint64, words)
case cap(b) >= words:
b = b[:words]
default:
// be exact, don't use append!
// max 512 prefixes/node (8*uint64), and a cache line has 64 Bytes
newset := make([]uint64, words)
copy(newset, b)
b = newset
}
}
b[i>>6] |= 1 << (i & 63)
return b
}
// Clear bit i to 0.
func (b BitSet) Clear(i uint) BitSet {
if x := int(i >> 6); x < len(b) {
b[x] &^= 1 << (i & 63)
}
return b
}
// Test if bit i is set.
func (b BitSet) Test(i uint) (ok bool) {
if x := int(i >> 6); x < len(b) {
return b[x]&(1<<(i&63)) != 0
}
return
}
// Clone this BitSet, returning a new BitSet that has the same bits set.
func (b BitSet) Clone() BitSet {
return append(b[:0:0], b...)
}
// Compact, preserve all set bits, while minimizing memory usage.
func (b BitSet) Compact() BitSet {
last := len(b) - 1
// find last word with at least one bit set.
for ; last >= 0; last-- {
if b[last] != 0 {
b = b[: last+1 : last+1]
return b
}
}
// BitSet was empty, shrink to nil
return nil
}
// FirstSet returns the first bit set along with an ok code.
func (b BitSet) FirstSet() (uint, bool) {
for x, word := range b {
if word != 0 {
return uint(x<<6 + bits.TrailingZeros64(word)), true
}
}
return 0, false
}
// NextSet returns the next bit set from the specified index,
// including possibly the current index along with an ok code.
func (b BitSet) NextSet(i uint) (uint, bool) {
x := int(i >> 6)
if x >= len(b) {
return 0, false
}
// process the first (maybe partial) word
first := b[x] >> (i & 63) // i % 64
if first != 0 {
return i + uint(bits.TrailingZeros64(first)), true
}
// process the following words until next bit is set
// x < len(b), no out-of-bounds panic in following slice expression
x++
for j, word := range b[x:] {
if word != 0 {
return uint((x+j)<<6 + bits.TrailingZeros64(word)), true
}
}
return 0, false
}
// AsSlice returns all set bits as slice of uint without
// heap allocations.
//
// This is faster than All, but also more dangerous,
// it panics if the capacity of buf is < b.Size()
func (b BitSet) AsSlice(buf []uint) []uint {
buf = buf[:cap(buf)] // len = cap
size := 0
for idx, word := range b {
for ; word != 0; size++ {
// panics if capacity of buf is exceeded.
buf[size] = uint(idx<<6 + bits.TrailingZeros64(word))
// clear the rightmost set bit
word &= word - 1
}
}
buf = buf[:size]
return buf
}
// All returns all set bits. This is simpler but slower than AsSlice.
func (b BitSet) All() []uint {
buf := make([]uint, b.Size())
slot := 0
for idx, word := range b {
for word != 0 {
buf[slot] = uint(idx<<6 + bits.TrailingZeros64(word))
slot++
// clear the rightmost set bit
word &= word - 1
}
}
return buf
}
// IntersectsAny returns true if the intersection of base set with the compare set
// is not the empty set.
func (b BitSet) IntersectsAny(c BitSet) bool {
i := min(len(b), len(c)) - 1
// bounds check eliminated (BCE)
for ; i >= 0 && i < len(b) && i < len(c); i-- {
if b[i]&c[i] != 0 {
return true
}
}
return false
}
// IntersectionTop computes the intersection of base set with the compare set.
// If the result set isn't empty, it returns the top most set bit and true.
func (b BitSet) IntersectionTop(c BitSet) (top uint, ok bool) {
i := min(len(b), len(c)) - 1
// bounds check eliminated (BCE)
for ; i >= 0 && i < len(b) && i < len(c); i-- {
if word := b[i] & c[i]; word != 0 {
return uint(i<<6+bits.Len64(word)) - 1, true
}
}
return
}
// IntersectionCardinality computes the popcount of the intersection.
func (b BitSet) IntersectionCardinality(c BitSet) int {
return popcntAnd(b, c)
}
// InPlaceIntersection overwrites and computes the intersection of
// base set with the compare set. This is the BitSet equivalent of & (and).
// If len(c) > len(b), new memory is allocated.
func (b *BitSet) InPlaceIntersection(c BitSet) {
// bounds check eliminated, range until minLen(b,c)
for i := 0; i < len(*b) && i < len(c); i++ {
(*b)[i] &= c[i]
}
// b >= c
if len(*b) >= len(c) {
// bounds check eliminated
for i := len(c); i < len(*b); i++ {
(*b)[i] = 0
}
return
}
// b < c
newset := make([]uint64, len(c))
copy(newset, *b)
*b = newset
}
// InPlaceUnion creates the destructive union of base set with compare set.
// This is the BitSet equivalent of | (or).
// If len(c) > len(b), new memory is allocated.
func (b *BitSet) InPlaceUnion(c BitSet) {
// b >= c
if len(*b) >= len(c) {
// bounds check eliminated
for i := 0; i < len(*b) && i < len(c); i++ {
(*b)[i] |= c[i]
}
return
}
// b < c
newset := make([]uint64, len(c))
copy(newset, *b)
*b = newset
// bounds check eliminated
for i := 0; i < len(*b) && i < len(c); i++ {
(*b)[i] |= c[i]
}
}
// Size (number of set bits).
func (b BitSet) Size() int {
return popcntSlice(b)
}
// Rank0 is equal to Rank(i) - 1
//
// With inlined popcount to make Rank0 itself inlineable.
func (b BitSet) Rank0(i uint) (rnk int) {
// Rank count is inclusive
i++
if wordIdx := int(i >> 6); wordIdx >= len(b) {
// inlined popcount, whole slice
for _, x := range b {
rnk += bits.OnesCount64(x)
}
} else {
// inlined popcount, partial slice ...
for _, x := range b[:wordIdx] {
rnk += bits.OnesCount64(x)
}
// ... plus partial word?
if bitsIdx := i & 63; bitsIdx != 0 {
rnk += bits.OnesCount64(b[wordIdx] << (64 - bitsIdx))
}
}
// correct for offset by one
return rnk - 1
}
// popcntSlice
func popcntSlice(s []uint64) (cnt int) {
for _, x := range s {
// count all the bits set in slice.
cnt += bits.OnesCount64(x)
}
return
}
// popcntAnd
func popcntAnd(s, m []uint64) (cnt int) {
for j := 0; j < len(s) && j < len(m); j++ {
// words are bitwise & followed by popcount.
cnt += bits.OnesCount64(s[j] & m[j])
}
return
}
+595
View File
@@ -0,0 +1,595 @@
// Copyright (c) 2026 Karl Gaissmaier
// SPDX-License-Identifier: MIT
// Package bitset provides a compact and efficient implementation of a fixed-length
// bitset for the range [0..255].
//
// This implementation is optimized for internal use in a compressed routing trie
// and prioritizes minimal allocation, performance, and inlining. It supports
// constant-time set/test operations, iteration over set bits, ranking, and masked
// intersections.
//
// Internally, the bitset is represented by four uint64 words, providing
// fast bit-level access through direct indexing and hardware-accelerated primitives.
//
// If Go eventually supports SIMD intrinsics, this can be further optimized.
//
// For external consumers, the API intentionally avoids dynamic allocation except
// when explicitly requested (via Bits()).
package bitset
// can inline (*BitSet256).AsSlice with cost 42
// can inline (*BitSet256).Bits with cost 47
// can inline (*BitSet256).Clear with cost 12
// can inline (*BitSet256).FirstSet with cost 79
// can inline (*BitSet256).Intersects with cost 48
// can inline (*BitSet256).Intersection with cost 53
// can inline (*BitSet256).IntersectionTop with cost 67
// can inline (*BitSet256).IsEmpty with cost 22
// can inline (*BitSet256).LastSet with cost 75
// can inline (*BitSet256).NextSet with cost 65
// can inline (*BitSet256).Rank with cost 57
// can inline (*BitSet256).Set with cost 12
// can inline (*BitSet256).Size with cost 33
// can inline (*BitSet256).Test with cost 15
// can inline (*BitSet256).Union with cost 36
import (
"math/bits"
)
// wordIdx calculates the wordIndex of bit i in a []uint64
// func wordIdx(i uint) int {
// return int(i >> 6) // like (i / 64) but faster
// }
// bitIdx calculates the bitIndex of i in an `uint64`
// func bitIdx(i uint) uint {
// return i & 63 // like (i % 64) but mostly faster
// }
//
// just as an explanation of the expressions,
//
// i>>6 or i<<6 and i&63
//
// not factored out as functions to make most of the methods
// inlineable with minimal costs.
// BitSet256 represents a fixed-size bitset for the range [0..255],
// stored as four uint64 words (256 bits total).
type BitSet256 [4]uint64
// Set sets the bit at position bit (0..255).
func (b *BitSet256) Set(bit uint8) {
b[bit>>6] |= 1 << (bit & 63)
}
// Clear clears the bit at position bit (0..255).
func (b *BitSet256) Clear(bit uint8) {
b[bit>>6] &^= 1 << (bit & 63)
}
// Test reports whether the bit at position bit (0..255) is set.
func (b *BitSet256) Test(bit uint8) (ok bool) {
return b[bit>>6]&(1<<(bit&63)) != 0
}
// FirstSet returns the index of the lowest (first) bit that is set in the BitSet256.
//
// It searches the 256-bit set in ascending order and returns the position of the
// first bit with value 1. If at least one bit is set, ok is true.
// If no bits are set, ok is false and first is undefined.
//
// Example:
//
// var bs BitSet256
// bs.Set(17)
// bs.Set(42)
// bs.Set(130)
// bs.Set(255)
// index, ok := bs.FirstSet() // index == 17, ok == true
//
// Note: This implementation avoids a for loop for optimal speed.
// On modern CPUs, computing all four trailing-zero counts up front allows
// the CPU to parallelize these operations internally (pipelining), avoiding
// branch misprediction and maximizing instruction throughput. This technique
// is especially effective for bitsets with known, fixed word count.
func (b *BitSet256) FirstSet() (first uint8, ok bool) {
// optimized for pipelining, can still inline with cost 79
x0 := bits.TrailingZeros64(b[0])
x1 := bits.TrailingZeros64(b[1])
x2 := bits.TrailingZeros64(b[2])
x3 := bits.TrailingZeros64(b[3])
if x0 != 64 {
//nolint:gosec // G115: integer overflow conversion int -> uint
return uint8(x0), true
}
if x1 != 64 {
//nolint:gosec // G115: integer overflow conversion int -> uint
return uint8(x1 + 64), true
}
if x2 != 64 {
//nolint:gosec // G115: integer overflow conversion int -> uint
return uint8(x2 + 128), true
}
if x3 != 64 {
//nolint:gosec // G115: integer overflow conversion int -> uint
return uint8(x3 + 192), true
}
return
}
// NextSet returns the index of the next set bit that is greater than or equal to bit.
//
// If such a bit exists, it returns its index as next and ok=true.
// Otherwise, ok is false and next is undefined.
//
// The search starts at the given bit index and proceeds toward higher indices, scanning
// across all four 64-bit segments of the internal bitset representation.
//
// Example:
//
// b.Set(5)
// b.Set(130)
// b.NextSet(0) -> 5, true
// b.NextSet(5) -> 5, true
// b.NextSet(6) -> 130, true
// b.NextSet(200) -> 0, false
func (b *BitSet256) NextSet(bit uint8) (next uint8, ok bool) {
wIdx := int(bit >> 6)
// process the first (maybe partial) word
first := b[wIdx] >> (bit & 63)
if first != 0 {
//nolint:gosec // G115: integer overflow conversion int -> uint
return bit + uint8(bits.TrailingZeros64(first)), true
}
// process the following words until next bit is set
for wIdx++; wIdx < 4; wIdx++ {
if next := b[wIdx]; next != 0 {
//nolint:gosec // G115: integer overflow conversion int -> uint
return uint8(wIdx<<6 + bits.TrailingZeros64(next)), true
}
}
return
}
// LastSet returns the index of the highest (last) bit that is set in the BitSet256.
//
// It searches the bitset in descending order and returns the position of the
// first bit (top bit) with value 1. If at least one bit is set, ok is true.
// If no bits are set, ok is false and last is 0.
//
// Example:
//
// var bs BitSet256
// bs.Set(2)
// bs.Set(130)
// bs.Set(214)
// index, ok := bs.LastSet() // index == 214, ok == true
func (b *BitSet256) LastSet() (last uint8, ok bool) {
// optimized by unrolling the loop.
// This enables compiler inlining and is ~20% faster.
if b[3] != 0 {
//nolint:gosec // G115: integer overflow conversion int -> uint
return uint8(bits.Len64(b[3]) + 191), true
}
if b[2] != 0 {
//nolint:gosec // G115: integer overflow conversion int -> uint
return uint8(bits.Len64(b[2]) + 127), true
}
if b[1] != 0 {
//nolint:gosec // G115: integer overflow conversion int -> uint
return uint8(bits.Len64(b[1]) + 63), true
}
if b[0] != 0 {
//nolint:gosec // G115: integer overflow conversion int -> uint
return uint8(bits.Len64(b[0]) - 1), true
}
return 0, false
}
// AsSlice extracts the indices of all set bits in the BitSet256, returning them
// as uint8 values in strictly ascending order.
//
// Performance Considerations:
// To guarantee zero heap allocations and enable compiler inlining, the caller must
// provide a pointer to a 256-byte array (`buf`) as backing storage. The method
// populates this array in-place and returns a sliced view (`[]uint8`) tailored to
// the actual number of set bits.
//
// Safety and Lifecycle:
// The returned slice directly shares the underlying storage of `buf` and is only
// valid until `buf` is modified or reused. This pattern is highly recommended for
// hot paths and performance-critical loops where heap churn must be avoided.
func (b *BitSet256) AsSlice(buf *[256]uint8) []uint8 {
size := 0
for wIdx, word := range b {
for ; word != 0; size++ {
//nolint:gosec // G115: integer overflow conversion int -> uint
buf[size] = uint8(wIdx<<6 + bits.TrailingZeros64(word))
word &= word - 1 // clear the rightmost set bit
}
}
// tailor to the actual number of set bits
return buf[:size]
}
// Bits returns a slice containing the indices of all set bits in strictly
// ascending order as uint8 values.
//
// Performance Considerations:
// Unlike [AsSlice], this method dynamically allocates a new slice on the
// heap to store the result. It is designed for convenience and APIs where the lifecycle
// of the returned slice needs to outlive the immediate caller's stack frame.
//
// Usage Guidance:
// Use Bits when convenience is preferred over raw performance, or when the result
// must be returned across boundaries where stack-allocated buffers cannot safely escape.
// For high-throughput or allocation-free processing, prefer [AsSlice].
func (b *BitSet256) Bits() []uint8 {
return b.AsSlice(&[256]uint8{})
}
// IntersectionTop computes the intersection of the receiver with c
// and returns the highest (top-most) set bit of the result.
// If the intersection is non-empty, it returns the top bit index and true.
// If the intersection is empty, ok is false and top is 0.
func (b *BitSet256) IntersectionTop(c *BitSet256) (top uint8, ok bool) {
// optimized by unrolling the first word check.
// This enables compiler inlining and is ~15% faster.
if w := b[3] & c[3]; w != 0 {
//nolint:gosec // G115: integer overflow conversion int -> uint8
return uint8(191 + bits.Len64(w)), true
}
for wIdx := 2; wIdx >= 0; wIdx-- {
if w := b[wIdx] & c[wIdx]; w != 0 {
//nolint:gosec // G115: integer overflow conversion int -> uint8
return uint8(wIdx<<6 + bits.Len64(w) - 1), true
}
}
return
}
// Rank returns the number of bits set (i.e., value 1) in the BitSet256
// up to and including the provided index.
//
// The rank is computed efficiently using precomputed bitmasks (rankMask),
// which mask out all bits above the index. For example:
//
// b.Set(3)
// b.Set(5)
// b.Set(120)
// b.Rank(5) -> 2 // only bits 3 and 5 are ≤ 5
// b.Rank(119) -> 2 // only bits 3 and 5 are ≤ 119
// b.Rank(120) -> 3 // bit 120 is included here
//
// Rank is particularly useful in prefix trees, indexing schemes,
// and data compression techniques where ordinal positions matter.
//
// Internally, the function performs four bitwise-and operations
// between the bitset words and a precomputed mask covering bits 0..idx,
// followed by popcount operations (via bits.OnesCount64).
//
// This avoids dynamic mask construction and enables branch-free, highly
// predictable performance.
func (b *BitSet256) Rank(idx uint8) (rnk int) {
rnk += bits.OnesCount64(b[0] & rankMask[idx][0])
rnk += bits.OnesCount64(b[1] & rankMask[idx][1])
rnk += bits.OnesCount64(b[2] & rankMask[idx][2])
rnk += bits.OnesCount64(b[3] & rankMask[idx][3])
return
}
// IsEmpty reports whether all 256 bits are zero.
func (b *BitSet256) IsEmpty() bool {
return b[0]|b[1]|b[2]|b[3] == 0
}
// Intersects reports whether the receiver and c have at least one bit in common.
func (b *BitSet256) Intersects(c *BitSet256) bool {
return b[0]&c[0] != 0 ||
b[1]&c[1] != 0 ||
b[2]&c[2] != 0 ||
b[3]&c[3] != 0
}
// Intersection returns a new BitSet256 containing only the bits
// that are set in both the receiver and c (bitwise AND).
func (b *BitSet256) Intersection(c *BitSet256) (bs BitSet256) {
bs[0] = b[0] & c[0]
bs[1] = b[1] & c[1]
bs[2] = b[2] & c[2]
bs[3] = b[3] & c[3]
return
}
// Union sets all bits in the receiver that are set in c (in-place bitwise OR).
func (b *BitSet256) Union(c *BitSet256) {
b[0] |= c[0]
b[1] |= c[1]
b[2] |= c[2]
b[3] |= c[3]
}
// Size returns the population count, i.e. the number of set bits.
func (b *BitSet256) Size() (cnt int) {
cnt += bits.OnesCount64(b[0])
cnt += bits.OnesCount64(b[1])
cnt += bits.OnesCount64(b[2])
cnt += bits.OnesCount64(b[3])
return
}
// rankMask is a table of bitmasks with all bits set to 1 up to and including a given bit position.
// It is used by BitSet256.Rank() to perform efficient popcount operations.
//
// This approach trades ~8KB of static memory for zero-allocation,
// fully branch-free, and cache-friendly Rank() calls with constant runtime.
//
// Used internally by the trie for position counting, CIDR ordering,
// and fast range-limited bit population counts.
//
//nolint:gochecknoglobals // Precomputed read‑only table used in hot paths.
var rankMask = [256]BitSet256{
/* 0 */ {0x1, 0x0, 0x0, 0x0},
/* 1 */ {0x3, 0x0, 0x0, 0x0},
/* 2 */ {0x7, 0x0, 0x0, 0x0},
/* 3 */ {0xf, 0x0, 0x0, 0x0},
/* 4 */ {0x1f, 0x0, 0x0, 0x0},
/* 5 */ {0x3f, 0x0, 0x0, 0x0},
/* 6 */ {0x7f, 0x0, 0x0, 0x0},
/* 7 */ {0xff, 0x0, 0x0, 0x0},
/* 8 */ {0x1ff, 0x0, 0x0, 0x0},
/* 9 */ {0x3ff, 0x0, 0x0, 0x0},
/* 10 */ {0x7ff, 0x0, 0x0, 0x0},
/* 11 */ {0xfff, 0x0, 0x0, 0x0},
/* 12 */ {0x1fff, 0x0, 0x0, 0x0},
/* 13 */ {0x3fff, 0x0, 0x0, 0x0},
/* 14 */ {0x7fff, 0x0, 0x0, 0x0},
/* 15 */ {0xffff, 0x0, 0x0, 0x0},
/* 16 */ {0x1ffff, 0x0, 0x0, 0x0},
/* 17 */ {0x3ffff, 0x0, 0x0, 0x0},
/* 18 */ {0x7ffff, 0x0, 0x0, 0x0},
/* 19 */ {0xfffff, 0x0, 0x0, 0x0},
/* 20 */ {0x1fffff, 0x0, 0x0, 0x0},
/* 21 */ {0x3fffff, 0x0, 0x0, 0x0},
/* 22 */ {0x7fffff, 0x0, 0x0, 0x0},
/* 23 */ {0xffffff, 0x0, 0x0, 0x0},
/* 24 */ {0x1ffffff, 0x0, 0x0, 0x0},
/* 25 */ {0x3ffffff, 0x0, 0x0, 0x0},
/* 26 */ {0x7ffffff, 0x0, 0x0, 0x0},
/* 27 */ {0xfffffff, 0x0, 0x0, 0x0},
/* 28 */ {0x1fffffff, 0x0, 0x0, 0x0},
/* 29 */ {0x3fffffff, 0x0, 0x0, 0x0},
/* 30 */ {0x7fffffff, 0x0, 0x0, 0x0},
/* 31 */ {0xffffffff, 0x0, 0x0, 0x0},
/* 32 */ {0x1ffffffff, 0x0, 0x0, 0x0},
/* 33 */ {0x3ffffffff, 0x0, 0x0, 0x0},
/* 34 */ {0x7ffffffff, 0x0, 0x0, 0x0},
/* 35 */ {0xfffffffff, 0x0, 0x0, 0x0},
/* 36 */ {0x1fffffffff, 0x0, 0x0, 0x0},
/* 37 */ {0x3fffffffff, 0x0, 0x0, 0x0},
/* 38 */ {0x7fffffffff, 0x0, 0x0, 0x0},
/* 39 */ {0xffffffffff, 0x0, 0x0, 0x0},
/* 40 */ {0x1ffffffffff, 0x0, 0x0, 0x0},
/* 41 */ {0x3ffffffffff, 0x0, 0x0, 0x0},
/* 42 */ {0x7ffffffffff, 0x0, 0x0, 0x0},
/* 43 */ {0xfffffffffff, 0x0, 0x0, 0x0},
/* 44 */ {0x1fffffffffff, 0x0, 0x0, 0x0},
/* 45 */ {0x3fffffffffff, 0x0, 0x0, 0x0},
/* 46 */ {0x7fffffffffff, 0x0, 0x0, 0x0},
/* 47 */ {0xffffffffffff, 0x0, 0x0, 0x0},
/* 48 */ {0x1ffffffffffff, 0x0, 0x0, 0x0},
/* 49 */ {0x3ffffffffffff, 0x0, 0x0, 0x0},
/* 50 */ {0x7ffffffffffff, 0x0, 0x0, 0x0},
/* 51 */ {0xfffffffffffff, 0x0, 0x0, 0x0},
/* 52 */ {0x1fffffffffffff, 0x0, 0x0, 0x0},
/* 53 */ {0x3fffffffffffff, 0x0, 0x0, 0x0},
/* 54 */ {0x7fffffffffffff, 0x0, 0x0, 0x0},
/* 55 */ {0xffffffffffffff, 0x0, 0x0, 0x0},
/* 56 */ {0x1ffffffffffffff, 0x0, 0x0, 0x0},
/* 57 */ {0x3ffffffffffffff, 0x0, 0x0, 0x0},
/* 58 */ {0x7ffffffffffffff, 0x0, 0x0, 0x0},
/* 59 */ {0xfffffffffffffff, 0x0, 0x0, 0x0},
/* 60 */ {0x1fffffffffffffff, 0x0, 0x0, 0x0},
/* 61 */ {0x3fffffffffffffff, 0x0, 0x0, 0x0},
/* 62 */ {0x7fffffffffffffff, 0x0, 0x0, 0x0},
/* 63 */ {0xffffffffffffffff, 0x0, 0x0, 0x0},
/* 64 */ {0xffffffffffffffff, 0x1, 0x0, 0x0},
/* 65 */ {0xffffffffffffffff, 0x3, 0x0, 0x0},
/* 66 */ {0xffffffffffffffff, 0x7, 0x0, 0x0},
/* 67 */ {0xffffffffffffffff, 0xf, 0x0, 0x0},
/* 68 */ {0xffffffffffffffff, 0x1f, 0x0, 0x0},
/* 69 */ {0xffffffffffffffff, 0x3f, 0x0, 0x0},
/* 70 */ {0xffffffffffffffff, 0x7f, 0x0, 0x0},
/* 71 */ {0xffffffffffffffff, 0xff, 0x0, 0x0},
/* 72 */ {0xffffffffffffffff, 0x1ff, 0x0, 0x0},
/* 73 */ {0xffffffffffffffff, 0x3ff, 0x0, 0x0},
/* 74 */ {0xffffffffffffffff, 0x7ff, 0x0, 0x0},
/* 75 */ {0xffffffffffffffff, 0xfff, 0x0, 0x0},
/* 76 */ {0xffffffffffffffff, 0x1fff, 0x0, 0x0},
/* 77 */ {0xffffffffffffffff, 0x3fff, 0x0, 0x0},
/* 78 */ {0xffffffffffffffff, 0x7fff, 0x0, 0x0},
/* 79 */ {0xffffffffffffffff, 0xffff, 0x0, 0x0},
/* 80 */ {0xffffffffffffffff, 0x1ffff, 0x0, 0x0},
/* 81 */ {0xffffffffffffffff, 0x3ffff, 0x0, 0x0},
/* 82 */ {0xffffffffffffffff, 0x7ffff, 0x0, 0x0},
/* 83 */ {0xffffffffffffffff, 0xfffff, 0x0, 0x0},
/* 84 */ {0xffffffffffffffff, 0x1fffff, 0x0, 0x0},
/* 85 */ {0xffffffffffffffff, 0x3fffff, 0x0, 0x0},
/* 86 */ {0xffffffffffffffff, 0x7fffff, 0x0, 0x0},
/* 87 */ {0xffffffffffffffff, 0xffffff, 0x0, 0x0},
/* 88 */ {0xffffffffffffffff, 0x1ffffff, 0x0, 0x0},
/* 89 */ {0xffffffffffffffff, 0x3ffffff, 0x0, 0x0},
/* 90 */ {0xffffffffffffffff, 0x7ffffff, 0x0, 0x0},
/* 91 */ {0xffffffffffffffff, 0xfffffff, 0x0, 0x0},
/* 92 */ {0xffffffffffffffff, 0x1fffffff, 0x0, 0x0},
/* 93 */ {0xffffffffffffffff, 0x3fffffff, 0x0, 0x0},
/* 94 */ {0xffffffffffffffff, 0x7fffffff, 0x0, 0x0},
/* 95 */ {0xffffffffffffffff, 0xffffffff, 0x0, 0x0},
/* 96 */ {0xffffffffffffffff, 0x1ffffffff, 0x0, 0x0},
/* 97 */ {0xffffffffffffffff, 0x3ffffffff, 0x0, 0x0},
/* 98 */ {0xffffffffffffffff, 0x7ffffffff, 0x0, 0x0},
/* 99 */ {0xffffffffffffffff, 0xfffffffff, 0x0, 0x0},
/* 100 */ {0xffffffffffffffff, 0x1fffffffff, 0x0, 0x0},
/* 101 */ {0xffffffffffffffff, 0x3fffffffff, 0x0, 0x0},
/* 102 */ {0xffffffffffffffff, 0x7fffffffff, 0x0, 0x0},
/* 103 */ {0xffffffffffffffff, 0xffffffffff, 0x0, 0x0},
/* 104 */ {0xffffffffffffffff, 0x1ffffffffff, 0x0, 0x0},
/* 105 */ {0xffffffffffffffff, 0x3ffffffffff, 0x0, 0x0},
/* 106 */ {0xffffffffffffffff, 0x7ffffffffff, 0x0, 0x0},
/* 107 */ {0xffffffffffffffff, 0xfffffffffff, 0x0, 0x0},
/* 108 */ {0xffffffffffffffff, 0x1fffffffffff, 0x0, 0x0},
/* 109 */ {0xffffffffffffffff, 0x3fffffffffff, 0x0, 0x0},
/* 110 */ {0xffffffffffffffff, 0x7fffffffffff, 0x0, 0x0},
/* 111 */ {0xffffffffffffffff, 0xffffffffffff, 0x0, 0x0},
/* 112 */ {0xffffffffffffffff, 0x1ffffffffffff, 0x0, 0x0},
/* 113 */ {0xffffffffffffffff, 0x3ffffffffffff, 0x0, 0x0},
/* 114 */ {0xffffffffffffffff, 0x7ffffffffffff, 0x0, 0x0},
/* 115 */ {0xffffffffffffffff, 0xfffffffffffff, 0x0, 0x0},
/* 116 */ {0xffffffffffffffff, 0x1fffffffffffff, 0x0, 0x0},
/* 117 */ {0xffffffffffffffff, 0x3fffffffffffff, 0x0, 0x0},
/* 118 */ {0xffffffffffffffff, 0x7fffffffffffff, 0x0, 0x0},
/* 119 */ {0xffffffffffffffff, 0xffffffffffffff, 0x0, 0x0},
/* 120 */ {0xffffffffffffffff, 0x1ffffffffffffff, 0x0, 0x0},
/* 121 */ {0xffffffffffffffff, 0x3ffffffffffffff, 0x0, 0x0},
/* 122 */ {0xffffffffffffffff, 0x7ffffffffffffff, 0x0, 0x0},
/* 123 */ {0xffffffffffffffff, 0xfffffffffffffff, 0x0, 0x0},
/* 124 */ {0xffffffffffffffff, 0x1fffffffffffffff, 0x0, 0x0},
/* 125 */ {0xffffffffffffffff, 0x3fffffffffffffff, 0x0, 0x0},
/* 126 */ {0xffffffffffffffff, 0x7fffffffffffffff, 0x0, 0x0},
/* 127 */ {0xffffffffffffffff, 0xffffffffffffffff, 0x0, 0x0},
/* 128 */ {0xffffffffffffffff, 0xffffffffffffffff, 0x1, 0x0},
/* 129 */ {0xffffffffffffffff, 0xffffffffffffffff, 0x3, 0x0},
/* 130 */ {0xffffffffffffffff, 0xffffffffffffffff, 0x7, 0x0},
/* 131 */ {0xffffffffffffffff, 0xffffffffffffffff, 0xf, 0x0},
/* 132 */ {0xffffffffffffffff, 0xffffffffffffffff, 0x1f, 0x0},
/* 133 */ {0xffffffffffffffff, 0xffffffffffffffff, 0x3f, 0x0},
/* 134 */ {0xffffffffffffffff, 0xffffffffffffffff, 0x7f, 0x0},
/* 135 */ {0xffffffffffffffff, 0xffffffffffffffff, 0xff, 0x0},
/* 136 */ {0xffffffffffffffff, 0xffffffffffffffff, 0x1ff, 0x0},
/* 137 */ {0xffffffffffffffff, 0xffffffffffffffff, 0x3ff, 0x0},
/* 138 */ {0xffffffffffffffff, 0xffffffffffffffff, 0x7ff, 0x0},
/* 139 */ {0xffffffffffffffff, 0xffffffffffffffff, 0xfff, 0x0},
/* 140 */ {0xffffffffffffffff, 0xffffffffffffffff, 0x1fff, 0x0},
/* 141 */ {0xffffffffffffffff, 0xffffffffffffffff, 0x3fff, 0x0},
/* 142 */ {0xffffffffffffffff, 0xffffffffffffffff, 0x7fff, 0x0},
/* 143 */ {0xffffffffffffffff, 0xffffffffffffffff, 0xffff, 0x0},
/* 144 */ {0xffffffffffffffff, 0xffffffffffffffff, 0x1ffff, 0x0},
/* 145 */ {0xffffffffffffffff, 0xffffffffffffffff, 0x3ffff, 0x0},
/* 146 */ {0xffffffffffffffff, 0xffffffffffffffff, 0x7ffff, 0x0},
/* 147 */ {0xffffffffffffffff, 0xffffffffffffffff, 0xfffff, 0x0},
/* 148 */ {0xffffffffffffffff, 0xffffffffffffffff, 0x1fffff, 0x0},
/* 149 */ {0xffffffffffffffff, 0xffffffffffffffff, 0x3fffff, 0x0},
/* 150 */ {0xffffffffffffffff, 0xffffffffffffffff, 0x7fffff, 0x0},
/* 151 */ {0xffffffffffffffff, 0xffffffffffffffff, 0xffffff, 0x0},
/* 152 */ {0xffffffffffffffff, 0xffffffffffffffff, 0x1ffffff, 0x0},
/* 153 */ {0xffffffffffffffff, 0xffffffffffffffff, 0x3ffffff, 0x0},
/* 154 */ {0xffffffffffffffff, 0xffffffffffffffff, 0x7ffffff, 0x0},
/* 155 */ {0xffffffffffffffff, 0xffffffffffffffff, 0xfffffff, 0x0},
/* 156 */ {0xffffffffffffffff, 0xffffffffffffffff, 0x1fffffff, 0x0},
/* 157 */ {0xffffffffffffffff, 0xffffffffffffffff, 0x3fffffff, 0x0},
/* 158 */ {0xffffffffffffffff, 0xffffffffffffffff, 0x7fffffff, 0x0},
/* 159 */ {0xffffffffffffffff, 0xffffffffffffffff, 0xffffffff, 0x0},
/* 160 */ {0xffffffffffffffff, 0xffffffffffffffff, 0x1ffffffff, 0x0},
/* 161 */ {0xffffffffffffffff, 0xffffffffffffffff, 0x3ffffffff, 0x0},
/* 162 */ {0xffffffffffffffff, 0xffffffffffffffff, 0x7ffffffff, 0x0},
/* 163 */ {0xffffffffffffffff, 0xffffffffffffffff, 0xfffffffff, 0x0},
/* 164 */ {0xffffffffffffffff, 0xffffffffffffffff, 0x1fffffffff, 0x0},
/* 165 */ {0xffffffffffffffff, 0xffffffffffffffff, 0x3fffffffff, 0x0},
/* 166 */ {0xffffffffffffffff, 0xffffffffffffffff, 0x7fffffffff, 0x0},
/* 167 */ {0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffff, 0x0},
/* 168 */ {0xffffffffffffffff, 0xffffffffffffffff, 0x1ffffffffff, 0x0},
/* 169 */ {0xffffffffffffffff, 0xffffffffffffffff, 0x3ffffffffff, 0x0},
/* 170 */ {0xffffffffffffffff, 0xffffffffffffffff, 0x7ffffffffff, 0x0},
/* 171 */ {0xffffffffffffffff, 0xffffffffffffffff, 0xfffffffffff, 0x0},
/* 172 */ {0xffffffffffffffff, 0xffffffffffffffff, 0x1fffffffffff, 0x0},
/* 173 */ {0xffffffffffffffff, 0xffffffffffffffff, 0x3fffffffffff, 0x0},
/* 174 */ {0xffffffffffffffff, 0xffffffffffffffff, 0x7fffffffffff, 0x0},
/* 175 */ {0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffff, 0x0},
/* 176 */ {0xffffffffffffffff, 0xffffffffffffffff, 0x1ffffffffffff, 0x0},
/* 177 */ {0xffffffffffffffff, 0xffffffffffffffff, 0x3ffffffffffff, 0x0},
/* 178 */ {0xffffffffffffffff, 0xffffffffffffffff, 0x7ffffffffffff, 0x0},
/* 179 */ {0xffffffffffffffff, 0xffffffffffffffff, 0xfffffffffffff, 0x0},
/* 180 */ {0xffffffffffffffff, 0xffffffffffffffff, 0x1fffffffffffff, 0x0},
/* 181 */ {0xffffffffffffffff, 0xffffffffffffffff, 0x3fffffffffffff, 0x0},
/* 182 */ {0xffffffffffffffff, 0xffffffffffffffff, 0x7fffffffffffff, 0x0},
/* 183 */ {0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffff, 0x0},
/* 184 */ {0xffffffffffffffff, 0xffffffffffffffff, 0x1ffffffffffffff, 0x0},
/* 185 */ {0xffffffffffffffff, 0xffffffffffffffff, 0x3ffffffffffffff, 0x0},
/* 186 */ {0xffffffffffffffff, 0xffffffffffffffff, 0x7ffffffffffffff, 0x0},
/* 187 */ {0xffffffffffffffff, 0xffffffffffffffff, 0xfffffffffffffff, 0x0},
/* 188 */ {0xffffffffffffffff, 0xffffffffffffffff, 0x1fffffffffffffff, 0x0},
/* 189 */ {0xffffffffffffffff, 0xffffffffffffffff, 0x3fffffffffffffff, 0x0},
/* 190 */ {0xffffffffffffffff, 0xffffffffffffffff, 0x7fffffffffffffff, 0x0},
/* 191 */ {0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0x0},
/* 192 */ {0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0x1},
/* 193 */ {0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0x3},
/* 194 */ {0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0x7},
/* 195 */ {0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0xf},
/* 196 */ {0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0x1f},
/* 197 */ {0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0x3f},
/* 198 */ {0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0x7f},
/* 199 */ {0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0xff},
/* 200 */ {0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0x1ff},
/* 201 */ {0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0x3ff},
/* 202 */ {0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0x7ff},
/* 203 */ {0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0xfff},
/* 204 */ {0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0x1fff},
/* 205 */ {0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0x3fff},
/* 206 */ {0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0x7fff},
/* 207 */ {0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0xffff},
/* 208 */ {0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0x1ffff},
/* 209 */ {0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0x3ffff},
/* 210 */ {0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0x7ffff},
/* 211 */ {0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0xfffff},
/* 212 */ {0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0x1fffff},
/* 213 */ {0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0x3fffff},
/* 214 */ {0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0x7fffff},
/* 215 */ {0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0xffffff},
/* 216 */ {0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0x1ffffff},
/* 217 */ {0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0x3ffffff},
/* 218 */ {0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0x7ffffff},
/* 219 */ {0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0xfffffff},
/* 220 */ {0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0x1fffffff},
/* 221 */ {0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0x3fffffff},
/* 222 */ {0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0x7fffffff},
/* 223 */ {0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffff},
/* 224 */ {0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0x1ffffffff},
/* 225 */ {0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0x3ffffffff},
/* 226 */ {0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0x7ffffffff},
/* 227 */ {0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0xfffffffff},
/* 228 */ {0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0x1fffffffff},
/* 229 */ {0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0x3fffffffff},
/* 230 */ {0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0x7fffffffff},
/* 231 */ {0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffff},
/* 232 */ {0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0x1ffffffffff},
/* 233 */ {0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0x3ffffffffff},
/* 234 */ {0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0x7ffffffffff},
/* 235 */ {0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0xfffffffffff},
/* 236 */ {0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0x1fffffffffff},
/* 237 */ {0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0x3fffffffffff},
/* 238 */ {0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0x7fffffffffff},
/* 239 */ {0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffff},
/* 240 */ {0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0x1ffffffffffff},
/* 241 */ {0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0x3ffffffffffff},
/* 242 */ {0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0x7ffffffffffff},
/* 243 */ {0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0xfffffffffffff},
/* 244 */ {0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0x1fffffffffffff},
/* 245 */ {0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0x3fffffffffffff},
/* 246 */ {0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0x7fffffffffffff},
/* 247 */ {0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffff},
/* 248 */ {0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0x1ffffffffffffff},
/* 249 */ {0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0x3ffffffffffffff},
/* 250 */ {0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0x7ffffffffffffff},
/* 251 */ {0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0xfffffffffffffff},
/* 252 */ {0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0x1fffffffffffffff},
/* 253 */ {0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0x3fffffffffffffff},
/* 254 */ {0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0x7fffffffffffffff},
/* 255 */ {0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff},
}
+242
View File
@@ -0,0 +1,242 @@
//go:build generate
//go:generate go run $GOFILE
// Copyright (c) 2026 Karl Gaissmaier
// SPDX-License-Identifier: MIT
// Package main generates a lookup table for longest-prefix-match (LPM) operations
// in the BART routing table.
//
// This generator creates a precomputed lookup table (LookupTbl) that enables fast
// prefix matching by replacing iterative backtracking with a single bitset intersection.
//
// The core idea:
//
// In a complete binary tree representation of prefixes, finding the longest matching
// prefix requires checking a node and all its ancestors (parent, grandparent, etc.).
// Instead of iterating through ancestors at runtime (idx >>= 1 in a loop), this table
// precomputes all ancestors for each index.
//
// Binary tree structure:
// - Each index i has a parent at i>>1 (integer division by 2)
// - Index 1 is the root (represents the default route)
// - Indices 2-255 form a 7-level binary tree
//
// Example: Index 13 (binary: 0b1101) has ancestors:
// - 13 itself (0b1101)
// - 6 = 13>>1 (0b0110) - parent
// - 3 = 6>>1 (0b0011) - grandparent
// - 1 = 3>>1 (0b0001) - root
//
// With this table, checking if any ancestor exists in a node's prefix set becomes:
//
// node.prefixes.Intersects(&LookupTbl[idx])
//
// instead of a loop with multiple Test() calls.
package main
import (
"fmt"
"os"
"os/exec"
"strings"
"text/template"
"github.com/gaissmai/bart/internal/bitset"
)
var (
thisFname = os.Getenv("GOFILE")
outFname = "lookuptblgenerated.go"
)
// data holds the template variables for code generation.
// The LookupTbl field is populated immediately with the generated table string
var data = struct {
File string
LookupTbl string
}{
File: thisFname,
LookupTbl: genLookupTbl(),
}
// colored prefix for informational and error messages
const (
INFO = "\x1b[34mINFO:\x1b[0m"
DIE = "\x1b[31mERROR:\x1b[0m"
)
func main() {
outFile, err := os.Create(outFname)
if err != nil {
fmt.Fprintf(os.Stderr, "%s %v\n", DIE, err)
os.Exit(1)
}
// Parse the template ...
t := template.Must(template.New("foo").Parse(codeTemplate))
// ... and execute it with our data
err = t.Execute(outFile, data)
if err != nil {
fmt.Fprintf(os.Stderr, "%s %v\n", DIE, err)
_ = outFile.Close()
os.Exit(1)
}
// Ensure the file is properly closed before formatting
if err := outFile.Close(); err != nil {
fmt.Fprintf(os.Stderr, "%s %v\n", DIE, err)
os.Exit(1)
}
fmt.Fprintf(os.Stdout, "%s ✓ Generated %s\n", INFO, outFname)
// Run goimports to organize imports and format the code
goimports := exec.Command("goimports", "-w", outFname)
fmt.Fprintf(os.Stdout, "%s Running goimports on %s\n", INFO, outFname)
if out, err := goimports.CombinedOutput(); err != nil {
fmt.Fprintf(os.Stderr, "%s %s\n", DIE, string(out))
os.Exit(1)
}
fmt.Fprintf(os.Stdout, "%s ✓ goimports completed\n", INFO)
// Run gofumpt for additional formatting consistency
gofumpt := exec.Command("gofumpt", "-w", outFname)
fmt.Fprintf(os.Stdout, "%s Running gofumpt on %s\n", INFO, outFname)
if out, err := gofumpt.CombinedOutput(); err != nil {
fmt.Fprintf(os.Stderr, "%s %s\n", DIE, string(out))
os.Exit(1)
}
fmt.Fprintf(os.Stdout, "%s ✓ gofumpt completed\n", INFO)
fmt.Println()
}
// genLookupTbl generates the lookup table as a formatted string.
//
// For each index i (1-255), the table stores a bitset containing:
// - i itself
// - all binary ancestors of i (i>>1, i>>2, i>>3, ...)
//
// Example: For index 13 (binary: 1101):
// - Set bit 13 (1101)
// - Set bit 6 (0110) = 13>>1
// - Set bit 3 (0011) = 6>>1
// - Set bit 1 (0001) = 3>>1
//
// This precomputation allows replacing a loop of bitset tests
// with a single bitset intersection operation during longest-prefix-match.
// genLookupTbl generates the lookup table as a formatted string.
//
// For each index i (1-255), the table stores a BitSet256 containing:
// - i itself (the current node)
// - all binary ancestors of i (obtained by repeatedly right-shifting: i>>1, i>>2, ...)
//
// The algorithm walks up the binary tree from each leaf to the root (index 1).
// Each right-shift operation (i >>= 1) moves from a child to its parent node:
// - Even indices (LSB=0) are left children: parent = i/2
// - Odd indices (LSB=1) are right children: parent = (i-1)/2
// - Both cases are handled by integer division via right-shift
//
// Example walkthrough for index 13 (binary: 0b00001101):
//
// Iteration 1: i=13 (0b00001101) => Set bit 13
// Iteration 2: i=6 (0b00000110) => Set bit 6 (parent of 13)
// Iteration 3: i=3 (0b00000011) => Set bit 3 (parent of 6)
// Iteration 4: i=1 (0b00000001) => Set bit 1 (root)
// Iteration 5: i=0 → Loop terminates
//
// Result: BitSet256 with bits {1, 3, 6, 13} set
//
// This allows longest-prefix-match to be computed with a single bitset intersection:
//
// // Fast O(1) check using precomputed ancestors
// return node.prefixes.Intersects(&LookupTbl[idx])
//
// instead of iterative backtracking:
//
// // Slow O(log n) loop checking each ancestor individually
// for ; idx > 0; idx >>= 1 {
// if node.prefixes.Test(idx) {
// return true
// }
// }
//
// The generated string is formatted for embedding into Go source code.
func genLookupTbl() string {
// Initialize the lookup table with 256 entries (one per uint8 value)
lookupTbl := [256]bitset.BitSet256{}
for idx := 1; idx <= 255; idx++ {
// Walk up the binary tree by repeatedly right-shifting
// Each iteration moves to the parent node (idx >> 1)
for i := idx; i > 0; i >>= 1 {
lookupTbl[idx].Set(uint8(i))
}
}
// Convert the lookup table to a formatted string for the generated code
builder := strings.Builder{}
// Index 0 is invalid (no valid prefix maps to 0)
fmt.Fprintf(&builder, " /* idx: %3d */ %#v, // %s\n", 0, lookupTbl[0], "invalid")
// Format each valid index with its bitset and the list of set bits, e.g.
// /* idx: 13 */ {0x204a, 0x0, 0x0, 0x0}, // [1 3 6 13]
for idx := 1; idx < 256; idx++ {
fmt.Fprintf(&builder, " /* idx: %3d */ %#v, // %v\n", idx, lookupTbl[idx], lookupTbl[idx].Bits())
}
return builder.String()
}
// codeTemplate is the Go source template for the generated lookuptbl_gen.go file.
// It embeds the precomputed lookup table and includes comprehensive documentation
// about its structure, usage, and performance benefits.
//
// The template defines:
// - Package-level documentation explaining the LPM optimization
// - The LookupTbl variable with all 256 precomputed bitsets
// - Usage examples comparing the fast (Intersects) vs slow (loop) approach
// - Warnings about not mutating the read-only table
const codeTemplate = `// Code generated by {{.File}}; DO NOT EDIT.
// Copyright (c) 2026 Karl Gaissmaier
// SPDX-License-Identifier: MIT
// Package lpm (longest-prefix-match) contains the lookup table with which
// the backtracking for the lpm in the complete binary tree of the prefixes
// can be replaced by a fast bitset operation.
package lpm
// LookupTbl is a precomputed read‑only table used in hot paths.
//
// It allows a one-shot bitset intersection algorithm:
// Each entry i encodes i and all its binary ancestors (i>>1, i>>2, ...).
//
// idx must be the uint8 produced by art.OctetToIdx or art.PfxToIdx (0 is invalid).
//
// Usage:
//
// func (n *bartNode[V]) contains(idx uint8) bool {
// return n.prefixes.Intersects(&lpm.LookupTbl[idx])
// }
//
// instead of a sequence of single bitset tests:
//
// func (n *bartNode[V]) contains(idx uint8) bool {
// for ; idx > 0; idx >>= 1 {
// if n.prefixes.Test(idx) {
// return true
// }
// }
// return false
// }
//
// DO NOT MUTATE: Precomputed read‑only table used in hot paths.
//
//nolint:gochecknoglobals
var LookupTbl = [256]bitset.BitSet256{
{{.LookupTbl}} }`
+297
View File
@@ -0,0 +1,297 @@
// Code generated by generate_lookuptbl.go; DO NOT EDIT.
// Copyright (c) 2026 Karl Gaissmaier
// SPDX-License-Identifier: MIT
// Package lpm (longest-prefix-match) contains the lookup table with which
// the backtracking for the lpm in the complete binary tree of the prefixes
// can be replaced by a fast bitset operation.
package lpm
import "github.com/gaissmai/bart/internal/bitset"
// LookupTbl is a precomputed read‑only table used in hot paths.
//
// It allows a one-shot bitset intersection algorithm:
// Each entry i encodes i and all its binary ancestors (i>>1, i>>2, ...).
//
// idx must be the uint8 produced by art.OctetToIdx or art.PfxToIdx (0 is invalid).
//
// Usage:
//
// func (n *bartNode[V]) contains(idx uint8) bool {
// return n.prefixes.Intersects(&lpm.LookupTbl[idx])
// }
//
// instead of a sequence of single bitset tests:
//
// func (n *bartNode[V]) contains(idx uint8) bool {
// for ; idx > 0; idx >>= 1 {
// if n.prefixes.Test(idx) {
// return true
// }
// }
// return false
// }
//
// DO NOT MUTATE: Precomputed read‑only table used in hot paths.
//
//nolint:gochecknoglobals
var LookupTbl = [256]bitset.BitSet256{
/* idx: 0 */ {0x0, 0x0, 0x0, 0x0}, // invalid
/* idx: 1 */ {0x2, 0x0, 0x0, 0x0}, // [1]
/* idx: 2 */ {0x6, 0x0, 0x0, 0x0}, // [1 2]
/* idx: 3 */ {0xa, 0x0, 0x0, 0x0}, // [1 3]
/* idx: 4 */ {0x16, 0x0, 0x0, 0x0}, // [1 2 4]
/* idx: 5 */ {0x26, 0x0, 0x0, 0x0}, // [1 2 5]
/* idx: 6 */ {0x4a, 0x0, 0x0, 0x0}, // [1 3 6]
/* idx: 7 */ {0x8a, 0x0, 0x0, 0x0}, // [1 3 7]
/* idx: 8 */ {0x116, 0x0, 0x0, 0x0}, // [1 2 4 8]
/* idx: 9 */ {0x216, 0x0, 0x0, 0x0}, // [1 2 4 9]
/* idx: 10 */ {0x426, 0x0, 0x0, 0x0}, // [1 2 5 10]
/* idx: 11 */ {0x826, 0x0, 0x0, 0x0}, // [1 2 5 11]
/* idx: 12 */ {0x104a, 0x0, 0x0, 0x0}, // [1 3 6 12]
/* idx: 13 */ {0x204a, 0x0, 0x0, 0x0}, // [1 3 6 13]
/* idx: 14 */ {0x408a, 0x0, 0x0, 0x0}, // [1 3 7 14]
/* idx: 15 */ {0x808a, 0x0, 0x0, 0x0}, // [1 3 7 15]
/* idx: 16 */ {0x10116, 0x0, 0x0, 0x0}, // [1 2 4 8 16]
/* idx: 17 */ {0x20116, 0x0, 0x0, 0x0}, // [1 2 4 8 17]
/* idx: 18 */ {0x40216, 0x0, 0x0, 0x0}, // [1 2 4 9 18]
/* idx: 19 */ {0x80216, 0x0, 0x0, 0x0}, // [1 2 4 9 19]
/* idx: 20 */ {0x100426, 0x0, 0x0, 0x0}, // [1 2 5 10 20]
/* idx: 21 */ {0x200426, 0x0, 0x0, 0x0}, // [1 2 5 10 21]
/* idx: 22 */ {0x400826, 0x0, 0x0, 0x0}, // [1 2 5 11 22]
/* idx: 23 */ {0x800826, 0x0, 0x0, 0x0}, // [1 2 5 11 23]
/* idx: 24 */ {0x100104a, 0x0, 0x0, 0x0}, // [1 3 6 12 24]
/* idx: 25 */ {0x200104a, 0x0, 0x0, 0x0}, // [1 3 6 12 25]
/* idx: 26 */ {0x400204a, 0x0, 0x0, 0x0}, // [1 3 6 13 26]
/* idx: 27 */ {0x800204a, 0x0, 0x0, 0x0}, // [1 3 6 13 27]
/* idx: 28 */ {0x1000408a, 0x0, 0x0, 0x0}, // [1 3 7 14 28]
/* idx: 29 */ {0x2000408a, 0x0, 0x0, 0x0}, // [1 3 7 14 29]
/* idx: 30 */ {0x4000808a, 0x0, 0x0, 0x0}, // [1 3 7 15 30]
/* idx: 31 */ {0x8000808a, 0x0, 0x0, 0x0}, // [1 3 7 15 31]
/* idx: 32 */ {0x100010116, 0x0, 0x0, 0x0}, // [1 2 4 8 16 32]
/* idx: 33 */ {0x200010116, 0x0, 0x0, 0x0}, // [1 2 4 8 16 33]
/* idx: 34 */ {0x400020116, 0x0, 0x0, 0x0}, // [1 2 4 8 17 34]
/* idx: 35 */ {0x800020116, 0x0, 0x0, 0x0}, // [1 2 4 8 17 35]
/* idx: 36 */ {0x1000040216, 0x0, 0x0, 0x0}, // [1 2 4 9 18 36]
/* idx: 37 */ {0x2000040216, 0x0, 0x0, 0x0}, // [1 2 4 9 18 37]
/* idx: 38 */ {0x4000080216, 0x0, 0x0, 0x0}, // [1 2 4 9 19 38]
/* idx: 39 */ {0x8000080216, 0x0, 0x0, 0x0}, // [1 2 4 9 19 39]
/* idx: 40 */ {0x10000100426, 0x0, 0x0, 0x0}, // [1 2 5 10 20 40]
/* idx: 41 */ {0x20000100426, 0x0, 0x0, 0x0}, // [1 2 5 10 20 41]
/* idx: 42 */ {0x40000200426, 0x0, 0x0, 0x0}, // [1 2 5 10 21 42]
/* idx: 43 */ {0x80000200426, 0x0, 0x0, 0x0}, // [1 2 5 10 21 43]
/* idx: 44 */ {0x100000400826, 0x0, 0x0, 0x0}, // [1 2 5 11 22 44]
/* idx: 45 */ {0x200000400826, 0x0, 0x0, 0x0}, // [1 2 5 11 22 45]
/* idx: 46 */ {0x400000800826, 0x0, 0x0, 0x0}, // [1 2 5 11 23 46]
/* idx: 47 */ {0x800000800826, 0x0, 0x0, 0x0}, // [1 2 5 11 23 47]
/* idx: 48 */ {0x100000100104a, 0x0, 0x0, 0x0}, // [1 3 6 12 24 48]
/* idx: 49 */ {0x200000100104a, 0x0, 0x0, 0x0}, // [1 3 6 12 24 49]
/* idx: 50 */ {0x400000200104a, 0x0, 0x0, 0x0}, // [1 3 6 12 25 50]
/* idx: 51 */ {0x800000200104a, 0x0, 0x0, 0x0}, // [1 3 6 12 25 51]
/* idx: 52 */ {0x1000000400204a, 0x0, 0x0, 0x0}, // [1 3 6 13 26 52]
/* idx: 53 */ {0x2000000400204a, 0x0, 0x0, 0x0}, // [1 3 6 13 26 53]
/* idx: 54 */ {0x4000000800204a, 0x0, 0x0, 0x0}, // [1 3 6 13 27 54]
/* idx: 55 */ {0x8000000800204a, 0x0, 0x0, 0x0}, // [1 3 6 13 27 55]
/* idx: 56 */ {0x10000001000408a, 0x0, 0x0, 0x0}, // [1 3 7 14 28 56]
/* idx: 57 */ {0x20000001000408a, 0x0, 0x0, 0x0}, // [1 3 7 14 28 57]
/* idx: 58 */ {0x40000002000408a, 0x0, 0x0, 0x0}, // [1 3 7 14 29 58]
/* idx: 59 */ {0x80000002000408a, 0x0, 0x0, 0x0}, // [1 3 7 14 29 59]
/* idx: 60 */ {0x100000004000808a, 0x0, 0x0, 0x0}, // [1 3 7 15 30 60]
/* idx: 61 */ {0x200000004000808a, 0x0, 0x0, 0x0}, // [1 3 7 15 30 61]
/* idx: 62 */ {0x400000008000808a, 0x0, 0x0, 0x0}, // [1 3 7 15 31 62]
/* idx: 63 */ {0x800000008000808a, 0x0, 0x0, 0x0}, // [1 3 7 15 31 63]
/* idx: 64 */ {0x100010116, 0x1, 0x0, 0x0}, // [1 2 4 8 16 32 64]
/* idx: 65 */ {0x100010116, 0x2, 0x0, 0x0}, // [1 2 4 8 16 32 65]
/* idx: 66 */ {0x200010116, 0x4, 0x0, 0x0}, // [1 2 4 8 16 33 66]
/* idx: 67 */ {0x200010116, 0x8, 0x0, 0x0}, // [1 2 4 8 16 33 67]
/* idx: 68 */ {0x400020116, 0x10, 0x0, 0x0}, // [1 2 4 8 17 34 68]
/* idx: 69 */ {0x400020116, 0x20, 0x0, 0x0}, // [1 2 4 8 17 34 69]
/* idx: 70 */ {0x800020116, 0x40, 0x0, 0x0}, // [1 2 4 8 17 35 70]
/* idx: 71 */ {0x800020116, 0x80, 0x0, 0x0}, // [1 2 4 8 17 35 71]
/* idx: 72 */ {0x1000040216, 0x100, 0x0, 0x0}, // [1 2 4 9 18 36 72]
/* idx: 73 */ {0x1000040216, 0x200, 0x0, 0x0}, // [1 2 4 9 18 36 73]
/* idx: 74 */ {0x2000040216, 0x400, 0x0, 0x0}, // [1 2 4 9 18 37 74]
/* idx: 75 */ {0x2000040216, 0x800, 0x0, 0x0}, // [1 2 4 9 18 37 75]
/* idx: 76 */ {0x4000080216, 0x1000, 0x0, 0x0}, // [1 2 4 9 19 38 76]
/* idx: 77 */ {0x4000080216, 0x2000, 0x0, 0x0}, // [1 2 4 9 19 38 77]
/* idx: 78 */ {0x8000080216, 0x4000, 0x0, 0x0}, // [1 2 4 9 19 39 78]
/* idx: 79 */ {0x8000080216, 0x8000, 0x0, 0x0}, // [1 2 4 9 19 39 79]
/* idx: 80 */ {0x10000100426, 0x10000, 0x0, 0x0}, // [1 2 5 10 20 40 80]
/* idx: 81 */ {0x10000100426, 0x20000, 0x0, 0x0}, // [1 2 5 10 20 40 81]
/* idx: 82 */ {0x20000100426, 0x40000, 0x0, 0x0}, // [1 2 5 10 20 41 82]
/* idx: 83 */ {0x20000100426, 0x80000, 0x0, 0x0}, // [1 2 5 10 20 41 83]
/* idx: 84 */ {0x40000200426, 0x100000, 0x0, 0x0}, // [1 2 5 10 21 42 84]
/* idx: 85 */ {0x40000200426, 0x200000, 0x0, 0x0}, // [1 2 5 10 21 42 85]
/* idx: 86 */ {0x80000200426, 0x400000, 0x0, 0x0}, // [1 2 5 10 21 43 86]
/* idx: 87 */ {0x80000200426, 0x800000, 0x0, 0x0}, // [1 2 5 10 21 43 87]
/* idx: 88 */ {0x100000400826, 0x1000000, 0x0, 0x0}, // [1 2 5 11 22 44 88]
/* idx: 89 */ {0x100000400826, 0x2000000, 0x0, 0x0}, // [1 2 5 11 22 44 89]
/* idx: 90 */ {0x200000400826, 0x4000000, 0x0, 0x0}, // [1 2 5 11 22 45 90]
/* idx: 91 */ {0x200000400826, 0x8000000, 0x0, 0x0}, // [1 2 5 11 22 45 91]
/* idx: 92 */ {0x400000800826, 0x10000000, 0x0, 0x0}, // [1 2 5 11 23 46 92]
/* idx: 93 */ {0x400000800826, 0x20000000, 0x0, 0x0}, // [1 2 5 11 23 46 93]
/* idx: 94 */ {0x800000800826, 0x40000000, 0x0, 0x0}, // [1 2 5 11 23 47 94]
/* idx: 95 */ {0x800000800826, 0x80000000, 0x0, 0x0}, // [1 2 5 11 23 47 95]
/* idx: 96 */ {0x100000100104a, 0x100000000, 0x0, 0x0}, // [1 3 6 12 24 48 96]
/* idx: 97 */ {0x100000100104a, 0x200000000, 0x0, 0x0}, // [1 3 6 12 24 48 97]
/* idx: 98 */ {0x200000100104a, 0x400000000, 0x0, 0x0}, // [1 3 6 12 24 49 98]
/* idx: 99 */ {0x200000100104a, 0x800000000, 0x0, 0x0}, // [1 3 6 12 24 49 99]
/* idx: 100 */ {0x400000200104a, 0x1000000000, 0x0, 0x0}, // [1 3 6 12 25 50 100]
/* idx: 101 */ {0x400000200104a, 0x2000000000, 0x0, 0x0}, // [1 3 6 12 25 50 101]
/* idx: 102 */ {0x800000200104a, 0x4000000000, 0x0, 0x0}, // [1 3 6 12 25 51 102]
/* idx: 103 */ {0x800000200104a, 0x8000000000, 0x0, 0x0}, // [1 3 6 12 25 51 103]
/* idx: 104 */ {0x1000000400204a, 0x10000000000, 0x0, 0x0}, // [1 3 6 13 26 52 104]
/* idx: 105 */ {0x1000000400204a, 0x20000000000, 0x0, 0x0}, // [1 3 6 13 26 52 105]
/* idx: 106 */ {0x2000000400204a, 0x40000000000, 0x0, 0x0}, // [1 3 6 13 26 53 106]
/* idx: 107 */ {0x2000000400204a, 0x80000000000, 0x0, 0x0}, // [1 3 6 13 26 53 107]
/* idx: 108 */ {0x4000000800204a, 0x100000000000, 0x0, 0x0}, // [1 3 6 13 27 54 108]
/* idx: 109 */ {0x4000000800204a, 0x200000000000, 0x0, 0x0}, // [1 3 6 13 27 54 109]
/* idx: 110 */ {0x8000000800204a, 0x400000000000, 0x0, 0x0}, // [1 3 6 13 27 55 110]
/* idx: 111 */ {0x8000000800204a, 0x800000000000, 0x0, 0x0}, // [1 3 6 13 27 55 111]
/* idx: 112 */ {0x10000001000408a, 0x1000000000000, 0x0, 0x0}, // [1 3 7 14 28 56 112]
/* idx: 113 */ {0x10000001000408a, 0x2000000000000, 0x0, 0x0}, // [1 3 7 14 28 56 113]
/* idx: 114 */ {0x20000001000408a, 0x4000000000000, 0x0, 0x0}, // [1 3 7 14 28 57 114]
/* idx: 115 */ {0x20000001000408a, 0x8000000000000, 0x0, 0x0}, // [1 3 7 14 28 57 115]
/* idx: 116 */ {0x40000002000408a, 0x10000000000000, 0x0, 0x0}, // [1 3 7 14 29 58 116]
/* idx: 117 */ {0x40000002000408a, 0x20000000000000, 0x0, 0x0}, // [1 3 7 14 29 58 117]
/* idx: 118 */ {0x80000002000408a, 0x40000000000000, 0x0, 0x0}, // [1 3 7 14 29 59 118]
/* idx: 119 */ {0x80000002000408a, 0x80000000000000, 0x0, 0x0}, // [1 3 7 14 29 59 119]
/* idx: 120 */ {0x100000004000808a, 0x100000000000000, 0x0, 0x0}, // [1 3 7 15 30 60 120]
/* idx: 121 */ {0x100000004000808a, 0x200000000000000, 0x0, 0x0}, // [1 3 7 15 30 60 121]
/* idx: 122 */ {0x200000004000808a, 0x400000000000000, 0x0, 0x0}, // [1 3 7 15 30 61 122]
/* idx: 123 */ {0x200000004000808a, 0x800000000000000, 0x0, 0x0}, // [1 3 7 15 30 61 123]
/* idx: 124 */ {0x400000008000808a, 0x1000000000000000, 0x0, 0x0}, // [1 3 7 15 31 62 124]
/* idx: 125 */ {0x400000008000808a, 0x2000000000000000, 0x0, 0x0}, // [1 3 7 15 31 62 125]
/* idx: 126 */ {0x800000008000808a, 0x4000000000000000, 0x0, 0x0}, // [1 3 7 15 31 63 126]
/* idx: 127 */ {0x800000008000808a, 0x8000000000000000, 0x0, 0x0}, // [1 3 7 15 31 63 127]
/* idx: 128 */ {0x100010116, 0x1, 0x1, 0x0}, // [1 2 4 8 16 32 64 128]
/* idx: 129 */ {0x100010116, 0x1, 0x2, 0x0}, // [1 2 4 8 16 32 64 129]
/* idx: 130 */ {0x100010116, 0x2, 0x4, 0x0}, // [1 2 4 8 16 32 65 130]
/* idx: 131 */ {0x100010116, 0x2, 0x8, 0x0}, // [1 2 4 8 16 32 65 131]
/* idx: 132 */ {0x200010116, 0x4, 0x10, 0x0}, // [1 2 4 8 16 33 66 132]
/* idx: 133 */ {0x200010116, 0x4, 0x20, 0x0}, // [1 2 4 8 16 33 66 133]
/* idx: 134 */ {0x200010116, 0x8, 0x40, 0x0}, // [1 2 4 8 16 33 67 134]
/* idx: 135 */ {0x200010116, 0x8, 0x80, 0x0}, // [1 2 4 8 16 33 67 135]
/* idx: 136 */ {0x400020116, 0x10, 0x100, 0x0}, // [1 2 4 8 17 34 68 136]
/* idx: 137 */ {0x400020116, 0x10, 0x200, 0x0}, // [1 2 4 8 17 34 68 137]
/* idx: 138 */ {0x400020116, 0x20, 0x400, 0x0}, // [1 2 4 8 17 34 69 138]
/* idx: 139 */ {0x400020116, 0x20, 0x800, 0x0}, // [1 2 4 8 17 34 69 139]
/* idx: 140 */ {0x800020116, 0x40, 0x1000, 0x0}, // [1 2 4 8 17 35 70 140]
/* idx: 141 */ {0x800020116, 0x40, 0x2000, 0x0}, // [1 2 4 8 17 35 70 141]
/* idx: 142 */ {0x800020116, 0x80, 0x4000, 0x0}, // [1 2 4 8 17 35 71 142]
/* idx: 143 */ {0x800020116, 0x80, 0x8000, 0x0}, // [1 2 4 8 17 35 71 143]
/* idx: 144 */ {0x1000040216, 0x100, 0x10000, 0x0}, // [1 2 4 9 18 36 72 144]
/* idx: 145 */ {0x1000040216, 0x100, 0x20000, 0x0}, // [1 2 4 9 18 36 72 145]
/* idx: 146 */ {0x1000040216, 0x200, 0x40000, 0x0}, // [1 2 4 9 18 36 73 146]
/* idx: 147 */ {0x1000040216, 0x200, 0x80000, 0x0}, // [1 2 4 9 18 36 73 147]
/* idx: 148 */ {0x2000040216, 0x400, 0x100000, 0x0}, // [1 2 4 9 18 37 74 148]
/* idx: 149 */ {0x2000040216, 0x400, 0x200000, 0x0}, // [1 2 4 9 18 37 74 149]
/* idx: 150 */ {0x2000040216, 0x800, 0x400000, 0x0}, // [1 2 4 9 18 37 75 150]
/* idx: 151 */ {0x2000040216, 0x800, 0x800000, 0x0}, // [1 2 4 9 18 37 75 151]
/* idx: 152 */ {0x4000080216, 0x1000, 0x1000000, 0x0}, // [1 2 4 9 19 38 76 152]
/* idx: 153 */ {0x4000080216, 0x1000, 0x2000000, 0x0}, // [1 2 4 9 19 38 76 153]
/* idx: 154 */ {0x4000080216, 0x2000, 0x4000000, 0x0}, // [1 2 4 9 19 38 77 154]
/* idx: 155 */ {0x4000080216, 0x2000, 0x8000000, 0x0}, // [1 2 4 9 19 38 77 155]
/* idx: 156 */ {0x8000080216, 0x4000, 0x10000000, 0x0}, // [1 2 4 9 19 39 78 156]
/* idx: 157 */ {0x8000080216, 0x4000, 0x20000000, 0x0}, // [1 2 4 9 19 39 78 157]
/* idx: 158 */ {0x8000080216, 0x8000, 0x40000000, 0x0}, // [1 2 4 9 19 39 79 158]
/* idx: 159 */ {0x8000080216, 0x8000, 0x80000000, 0x0}, // [1 2 4 9 19 39 79 159]
/* idx: 160 */ {0x10000100426, 0x10000, 0x100000000, 0x0}, // [1 2 5 10 20 40 80 160]
/* idx: 161 */ {0x10000100426, 0x10000, 0x200000000, 0x0}, // [1 2 5 10 20 40 80 161]
/* idx: 162 */ {0x10000100426, 0x20000, 0x400000000, 0x0}, // [1 2 5 10 20 40 81 162]
/* idx: 163 */ {0x10000100426, 0x20000, 0x800000000, 0x0}, // [1 2 5 10 20 40 81 163]
/* idx: 164 */ {0x20000100426, 0x40000, 0x1000000000, 0x0}, // [1 2 5 10 20 41 82 164]
/* idx: 165 */ {0x20000100426, 0x40000, 0x2000000000, 0x0}, // [1 2 5 10 20 41 82 165]
/* idx: 166 */ {0x20000100426, 0x80000, 0x4000000000, 0x0}, // [1 2 5 10 20 41 83 166]
/* idx: 167 */ {0x20000100426, 0x80000, 0x8000000000, 0x0}, // [1 2 5 10 20 41 83 167]
/* idx: 168 */ {0x40000200426, 0x100000, 0x10000000000, 0x0}, // [1 2 5 10 21 42 84 168]
/* idx: 169 */ {0x40000200426, 0x100000, 0x20000000000, 0x0}, // [1 2 5 10 21 42 84 169]
/* idx: 170 */ {0x40000200426, 0x200000, 0x40000000000, 0x0}, // [1 2 5 10 21 42 85 170]
/* idx: 171 */ {0x40000200426, 0x200000, 0x80000000000, 0x0}, // [1 2 5 10 21 42 85 171]
/* idx: 172 */ {0x80000200426, 0x400000, 0x100000000000, 0x0}, // [1 2 5 10 21 43 86 172]
/* idx: 173 */ {0x80000200426, 0x400000, 0x200000000000, 0x0}, // [1 2 5 10 21 43 86 173]
/* idx: 174 */ {0x80000200426, 0x800000, 0x400000000000, 0x0}, // [1 2 5 10 21 43 87 174]
/* idx: 175 */ {0x80000200426, 0x800000, 0x800000000000, 0x0}, // [1 2 5 10 21 43 87 175]
/* idx: 176 */ {0x100000400826, 0x1000000, 0x1000000000000, 0x0}, // [1 2 5 11 22 44 88 176]
/* idx: 177 */ {0x100000400826, 0x1000000, 0x2000000000000, 0x0}, // [1 2 5 11 22 44 88 177]
/* idx: 178 */ {0x100000400826, 0x2000000, 0x4000000000000, 0x0}, // [1 2 5 11 22 44 89 178]
/* idx: 179 */ {0x100000400826, 0x2000000, 0x8000000000000, 0x0}, // [1 2 5 11 22 44 89 179]
/* idx: 180 */ {0x200000400826, 0x4000000, 0x10000000000000, 0x0}, // [1 2 5 11 22 45 90 180]
/* idx: 181 */ {0x200000400826, 0x4000000, 0x20000000000000, 0x0}, // [1 2 5 11 22 45 90 181]
/* idx: 182 */ {0x200000400826, 0x8000000, 0x40000000000000, 0x0}, // [1 2 5 11 22 45 91 182]
/* idx: 183 */ {0x200000400826, 0x8000000, 0x80000000000000, 0x0}, // [1 2 5 11 22 45 91 183]
/* idx: 184 */ {0x400000800826, 0x10000000, 0x100000000000000, 0x0}, // [1 2 5 11 23 46 92 184]
/* idx: 185 */ {0x400000800826, 0x10000000, 0x200000000000000, 0x0}, // [1 2 5 11 23 46 92 185]
/* idx: 186 */ {0x400000800826, 0x20000000, 0x400000000000000, 0x0}, // [1 2 5 11 23 46 93 186]
/* idx: 187 */ {0x400000800826, 0x20000000, 0x800000000000000, 0x0}, // [1 2 5 11 23 46 93 187]
/* idx: 188 */ {0x800000800826, 0x40000000, 0x1000000000000000, 0x0}, // [1 2 5 11 23 47 94 188]
/* idx: 189 */ {0x800000800826, 0x40000000, 0x2000000000000000, 0x0}, // [1 2 5 11 23 47 94 189]
/* idx: 190 */ {0x800000800826, 0x80000000, 0x4000000000000000, 0x0}, // [1 2 5 11 23 47 95 190]
/* idx: 191 */ {0x800000800826, 0x80000000, 0x8000000000000000, 0x0}, // [1 2 5 11 23 47 95 191]
/* idx: 192 */ {0x100000100104a, 0x100000000, 0x0, 0x1}, // [1 3 6 12 24 48 96 192]
/* idx: 193 */ {0x100000100104a, 0x100000000, 0x0, 0x2}, // [1 3 6 12 24 48 96 193]
/* idx: 194 */ {0x100000100104a, 0x200000000, 0x0, 0x4}, // [1 3 6 12 24 48 97 194]
/* idx: 195 */ {0x100000100104a, 0x200000000, 0x0, 0x8}, // [1 3 6 12 24 48 97 195]
/* idx: 196 */ {0x200000100104a, 0x400000000, 0x0, 0x10}, // [1 3 6 12 24 49 98 196]
/* idx: 197 */ {0x200000100104a, 0x400000000, 0x0, 0x20}, // [1 3 6 12 24 49 98 197]
/* idx: 198 */ {0x200000100104a, 0x800000000, 0x0, 0x40}, // [1 3 6 12 24 49 99 198]
/* idx: 199 */ {0x200000100104a, 0x800000000, 0x0, 0x80}, // [1 3 6 12 24 49 99 199]
/* idx: 200 */ {0x400000200104a, 0x1000000000, 0x0, 0x100}, // [1 3 6 12 25 50 100 200]
/* idx: 201 */ {0x400000200104a, 0x1000000000, 0x0, 0x200}, // [1 3 6 12 25 50 100 201]
/* idx: 202 */ {0x400000200104a, 0x2000000000, 0x0, 0x400}, // [1 3 6 12 25 50 101 202]
/* idx: 203 */ {0x400000200104a, 0x2000000000, 0x0, 0x800}, // [1 3 6 12 25 50 101 203]
/* idx: 204 */ {0x800000200104a, 0x4000000000, 0x0, 0x1000}, // [1 3 6 12 25 51 102 204]
/* idx: 205 */ {0x800000200104a, 0x4000000000, 0x0, 0x2000}, // [1 3 6 12 25 51 102 205]
/* idx: 206 */ {0x800000200104a, 0x8000000000, 0x0, 0x4000}, // [1 3 6 12 25 51 103 206]
/* idx: 207 */ {0x800000200104a, 0x8000000000, 0x0, 0x8000}, // [1 3 6 12 25 51 103 207]
/* idx: 208 */ {0x1000000400204a, 0x10000000000, 0x0, 0x10000}, // [1 3 6 13 26 52 104 208]
/* idx: 209 */ {0x1000000400204a, 0x10000000000, 0x0, 0x20000}, // [1 3 6 13 26 52 104 209]
/* idx: 210 */ {0x1000000400204a, 0x20000000000, 0x0, 0x40000}, // [1 3 6 13 26 52 105 210]
/* idx: 211 */ {0x1000000400204a, 0x20000000000, 0x0, 0x80000}, // [1 3 6 13 26 52 105 211]
/* idx: 212 */ {0x2000000400204a, 0x40000000000, 0x0, 0x100000}, // [1 3 6 13 26 53 106 212]
/* idx: 213 */ {0x2000000400204a, 0x40000000000, 0x0, 0x200000}, // [1 3 6 13 26 53 106 213]
/* idx: 214 */ {0x2000000400204a, 0x80000000000, 0x0, 0x400000}, // [1 3 6 13 26 53 107 214]
/* idx: 215 */ {0x2000000400204a, 0x80000000000, 0x0, 0x800000}, // [1 3 6 13 26 53 107 215]
/* idx: 216 */ {0x4000000800204a, 0x100000000000, 0x0, 0x1000000}, // [1 3 6 13 27 54 108 216]
/* idx: 217 */ {0x4000000800204a, 0x100000000000, 0x0, 0x2000000}, // [1 3 6 13 27 54 108 217]
/* idx: 218 */ {0x4000000800204a, 0x200000000000, 0x0, 0x4000000}, // [1 3 6 13 27 54 109 218]
/* idx: 219 */ {0x4000000800204a, 0x200000000000, 0x0, 0x8000000}, // [1 3 6 13 27 54 109 219]
/* idx: 220 */ {0x8000000800204a, 0x400000000000, 0x0, 0x10000000}, // [1 3 6 13 27 55 110 220]
/* idx: 221 */ {0x8000000800204a, 0x400000000000, 0x0, 0x20000000}, // [1 3 6 13 27 55 110 221]
/* idx: 222 */ {0x8000000800204a, 0x800000000000, 0x0, 0x40000000}, // [1 3 6 13 27 55 111 222]
/* idx: 223 */ {0x8000000800204a, 0x800000000000, 0x0, 0x80000000}, // [1 3 6 13 27 55 111 223]
/* idx: 224 */ {0x10000001000408a, 0x1000000000000, 0x0, 0x100000000}, // [1 3 7 14 28 56 112 224]
/* idx: 225 */ {0x10000001000408a, 0x1000000000000, 0x0, 0x200000000}, // [1 3 7 14 28 56 112 225]
/* idx: 226 */ {0x10000001000408a, 0x2000000000000, 0x0, 0x400000000}, // [1 3 7 14 28 56 113 226]
/* idx: 227 */ {0x10000001000408a, 0x2000000000000, 0x0, 0x800000000}, // [1 3 7 14 28 56 113 227]
/* idx: 228 */ {0x20000001000408a, 0x4000000000000, 0x0, 0x1000000000}, // [1 3 7 14 28 57 114 228]
/* idx: 229 */ {0x20000001000408a, 0x4000000000000, 0x0, 0x2000000000}, // [1 3 7 14 28 57 114 229]
/* idx: 230 */ {0x20000001000408a, 0x8000000000000, 0x0, 0x4000000000}, // [1 3 7 14 28 57 115 230]
/* idx: 231 */ {0x20000001000408a, 0x8000000000000, 0x0, 0x8000000000}, // [1 3 7 14 28 57 115 231]
/* idx: 232 */ {0x40000002000408a, 0x10000000000000, 0x0, 0x10000000000}, // [1 3 7 14 29 58 116 232]
/* idx: 233 */ {0x40000002000408a, 0x10000000000000, 0x0, 0x20000000000}, // [1 3 7 14 29 58 116 233]
/* idx: 234 */ {0x40000002000408a, 0x20000000000000, 0x0, 0x40000000000}, // [1 3 7 14 29 58 117 234]
/* idx: 235 */ {0x40000002000408a, 0x20000000000000, 0x0, 0x80000000000}, // [1 3 7 14 29 58 117 235]
/* idx: 236 */ {0x80000002000408a, 0x40000000000000, 0x0, 0x100000000000}, // [1 3 7 14 29 59 118 236]
/* idx: 237 */ {0x80000002000408a, 0x40000000000000, 0x0, 0x200000000000}, // [1 3 7 14 29 59 118 237]
/* idx: 238 */ {0x80000002000408a, 0x80000000000000, 0x0, 0x400000000000}, // [1 3 7 14 29 59 119 238]
/* idx: 239 */ {0x80000002000408a, 0x80000000000000, 0x0, 0x800000000000}, // [1 3 7 14 29 59 119 239]
/* idx: 240 */ {0x100000004000808a, 0x100000000000000, 0x0, 0x1000000000000}, // [1 3 7 15 30 60 120 240]
/* idx: 241 */ {0x100000004000808a, 0x100000000000000, 0x0, 0x2000000000000}, // [1 3 7 15 30 60 120 241]
/* idx: 242 */ {0x100000004000808a, 0x200000000000000, 0x0, 0x4000000000000}, // [1 3 7 15 30 60 121 242]
/* idx: 243 */ {0x100000004000808a, 0x200000000000000, 0x0, 0x8000000000000}, // [1 3 7 15 30 60 121 243]
/* idx: 244 */ {0x200000004000808a, 0x400000000000000, 0x0, 0x10000000000000}, // [1 3 7 15 30 61 122 244]
/* idx: 245 */ {0x200000004000808a, 0x400000000000000, 0x0, 0x20000000000000}, // [1 3 7 15 30 61 122 245]
/* idx: 246 */ {0x200000004000808a, 0x800000000000000, 0x0, 0x40000000000000}, // [1 3 7 15 30 61 123 246]
/* idx: 247 */ {0x200000004000808a, 0x800000000000000, 0x0, 0x80000000000000}, // [1 3 7 15 30 61 123 247]
/* idx: 248 */ {0x400000008000808a, 0x1000000000000000, 0x0, 0x100000000000000}, // [1 3 7 15 31 62 124 248]
/* idx: 249 */ {0x400000008000808a, 0x1000000000000000, 0x0, 0x200000000000000}, // [1 3 7 15 31 62 124 249]
/* idx: 250 */ {0x400000008000808a, 0x2000000000000000, 0x0, 0x400000000000000}, // [1 3 7 15 31 62 125 250]
/* idx: 251 */ {0x400000008000808a, 0x2000000000000000, 0x0, 0x800000000000000}, // [1 3 7 15 31 62 125 251]
/* idx: 252 */ {0x800000008000808a, 0x4000000000000000, 0x0, 0x1000000000000000}, // [1 3 7 15 31 63 126 252]
/* idx: 253 */ {0x800000008000808a, 0x4000000000000000, 0x0, 0x2000000000000000}, // [1 3 7 15 31 63 126 253]
/* idx: 254 */ {0x800000008000808a, 0x8000000000000000, 0x0, 0x4000000000000000}, // [1 3 7 15 31 63 127 254]
/* idx: 255 */ {0x800000008000808a, 0x8000000000000000, 0x0, 0x8000000000000000}, // [1 3 7 15 31 63 127 255]
}
+265
View File
@@ -0,0 +1,265 @@
// Copyright (c) 2026 Karl Gaissmaier
// SPDX-License-Identifier: MIT
package nodes
import (
"iter"
"github.com/gaissmai/bart/internal/lpm"
"github.com/gaissmai/bart/internal/sparse"
)
// BartNode represents a single trie level in the multibit routing table.
//
// Unlike the original ART algorithm, this implementation uses popcount-compressed
// sparse arrays instead of fixed-size allocations. Insertions and lookups rely on
// fast bitset operations and precomputed lookup tables to maximize CPU pipelining
// and cache efficiency.
//
// Each BartNode maintains two distinct sparse arrays:
//
// 1. Prefixes: Stores routing entries (prefix -> value) for the current stride.
// These are laid out as a complete binary tree using the baseIndex()
// function mapping from the ART algorithm. Prefixes that match exactly at
// the maximum trie depth are always stored here.
//
// 2. Children: Holds pointers to the next logical levels with a branching
// factor of 256 (8 bits per stride).
//
// A slot in the Children array may contain one of three types:
// - *BartNode[V]: An internal intermediate node for further trie traversal.
// - *FringeNode[V]: A path-compressed node if it qualifies as fringe [IsFringe]
// - *LeafNode[V]: A path-compressed node otherwise.
//
// Note: LeafNode and FringeNode are created through path compression and
// are automatically split into regular BartNodes when a more specific prefix
// is inserted that requires further branching.
type BartNode[V any] struct {
Prefixes sparse.Array256[V]
Children sparse.Array256[any]
}
// IsEmpty returns true if the node contains no routing entries (prefixes)
// and no child nodes. Empty nodes are candidates for compression or removal
// during trie optimization.
func (n *BartNode[V]) IsEmpty() bool {
if n == nil {
return true
}
return n.Prefixes.Len() == 0 && n.Children.Len() == 0
}
// PrefixCount returns the number of prefixes stored in this node.
func (n *BartNode[V]) PrefixCount() int {
return n.Prefixes.Len()
}
// ChildCount returns the number of slots used in this node.
func (n *BartNode[V]) ChildCount() int {
return n.Children.Len()
}
// InsertPrefix adds or updates a routing entry at the specified index with the given value.
// It returns true if a prefix already existed at that index (indicating an update),
// false if this is a new insertion.
func (n *BartNode[V]) InsertPrefix(idx uint8, val V) (exists bool) {
_, exists = n.Prefixes.InsertAt(idx, val)
return
}
// GetPrefix retrieves the value associated with the prefix at the given index.
// Returns the value and true if found, or zero value and false if not present.
func (n *BartNode[V]) GetPrefix(idx uint8) (val V, exists bool) {
return n.Prefixes.Get(idx)
}
// MustGetPrefix retrieves the value at the specified index, panicking if not found.
// This method should only be used when the caller is certain the index exists.
func (n *BartNode[V]) MustGetPrefix(idx uint8) (val V) {
return n.Prefixes.MustGet(idx)
}
// AllIndices returns an iterator over all prefix entries.
// Each iteration yields the prefix index (uint8) and its associated value (V).
func (n *BartNode[V]) AllIndices() iter.Seq2[uint8, V] {
return func(yield func(uint8, V) bool) {
var buf [256]uint8
for i, idx := range n.Prefixes.AsSlice(&buf) {
if !yield(idx, n.Prefixes.Items[i]) {
return
}
}
}
}
// DeletePrefix removes the prefix at the specified index.
// Returns true if the prefix existed, otherwise false.
func (n *BartNode[V]) DeletePrefix(idx uint8) (exists bool) {
_, exists = n.Prefixes.DeleteAt(idx)
return exists
}
// InsertChild adds a child node at the specified address (0-255).
// The child can be a *BartNode[V], *LeafNode[V], or *FringeNode[V].
// Returns true if a child already existed at that address.
func (n *BartNode[V]) InsertChild(addr uint8, child any) (exists bool) {
_, exists = n.Children.InsertAt(addr, child)
return
}
// GetChild retrieves the child node at the specified address.
// Returns the child and true if found, or nil and false if not present.
func (n *BartNode[V]) GetChild(addr uint8) (any, bool) {
return n.Children.Get(addr)
}
// MustGetChild retrieves the child at the specified address, panicking if not found.
// This method should only be used when the caller is certain the child exists.
func (n *BartNode[V]) MustGetChild(addr uint8) any {
return n.Children.MustGet(addr)
}
// AllChildren returns an iterator over all child nodes.
// Each iteration yields the child's address (uint8) and the child node (any).
func (n *BartNode[V]) AllChildren() iter.Seq2[uint8, any] {
return func(yield func(addr uint8, child any) bool) {
var buf [256]uint8
addrs := n.Children.AsSlice(&buf)
for i, addr := range addrs {
if !yield(addr, n.Children.Items[i]) {
return
}
}
}
}
// DeleteChild removes the child node at the specified address.
// This operation is idempotent - removing a non-existent child is safe.
func (n *BartNode[V]) DeleteChild(addr uint8) (exists bool) {
_, exists = n.Children.DeleteAt(addr)
return exists
}
// Contains returns true if an index (idx) has any matching longest-prefix
// in the current node’s prefix table.
//
// This function performs a presence check without retrieving the associated value.
// It is faster than a full lookup, as it only tests for intersection with the
// backtracking bitset for the given index.
//
// The prefix table is structured as a complete binary tree (CBT), and LPM testing
// is done via a bitset operation that maps the traversal path from the given index
// toward its possible ancestors.
func (n *BartNode[V]) Contains(idx uint8) bool {
return n.Prefixes.Intersects(&lpm.LookupTbl[idx])
}
// LookupIdx performs a longest-prefix match (LPM) lookup for the given index (idx)
// within the 8-bit stride-based prefix table at this trie depth.
//
// The function returns the matched base index, associated value, and true if a
// matching prefix exists at this level; otherwise, ok is false.
//
// Internally, the prefix table is organized as a complete binary tree (CBT) indexed
// via the baseIndex function. Unlike the original ART algorithm, this implementation
// does not use an allotment-based approach. Instead, it performs CBT backtracking
// using a bitset-based operation with a precomputed backtracking pattern specific to idx.
func (n *BartNode[V]) LookupIdx(idx uint8) (top uint8, val V, ok bool) {
// top is the idx of the longest-prefix-match
if top, ok = n.Prefixes.IntersectionTop(&lpm.LookupTbl[idx]); ok {
return top, n.MustGetPrefix(top), true
}
return top, val, ok
}
// Lookup is just a simple wrapper for LookupIdx.
func (n *BartNode[V]) Lookup(idx uint8) (val V, ok bool) {
_, val, ok = n.LookupIdx(idx)
return val, ok
}
// CloneFlat returns a shallow copy of the current node, optionally performing deep copies of values.
//
// If cloneFn is nil, the stored values in prefixes are copied directly without modification.
// Otherwise, cloneFn is applied to each stored value for deep cloning.
// Child nodes are cloned shallowly: LeafNode and FringeNode children are cloned via their clone methods,
// but child nodes of type *BartNode[V] (subnodes) are assigned as-is without recursive cloning.
// This method does not recursively clone descendants beyond the immediate children.
//
// Note: The returned node is a new instance with copied slices but only shallow copies of nested nodes,
// except for LeafNode and FringeNode children which are cloned according to cloneFn.
func (n *BartNode[V]) CloneFlat(cloneFn func(V) V) *BartNode[V] {
if n == nil {
return nil
}
c := new(BartNode[V])
if n.IsEmpty() {
return c
}
// copy ...
c.Prefixes = *(n.Prefixes.Copy())
// ... and clone the values
if cloneFn != nil {
for i, v := range c.Prefixes.Items {
c.Prefixes.Items[i] = cloneFn(v)
}
}
// copy ...
c.Children = *(n.Children.Copy())
// Iterate over children to flat clone leaf/fringe nodes;
// for *BartNode[V] children, keep shallow references (no recursive clone)
for i, anyKid := range c.Children.Items {
switch kid := anyKid.(type) {
case *BartNode[V]:
// Shallow copy
case *LeafNode[V]:
// Clone leaf nodes, applying cloneFn as needed
c.Children.Items[i] = kid.CloneLeaf(cloneFn)
case *FringeNode[V]:
// Clone fringe nodes, applying cloneFn as needed
c.Children.Items[i] = kid.CloneFringe(cloneFn)
default:
panic("logic error, wrong node type")
}
}
return c
}
// CloneRec performs a recursive deep copy of the node and all its descendants.
//
// If cloneFn is nil, the stored values are copied directly without modification.
// Otherwise cloneFn is applied to each stored value for deep cloning.
//
// This method first creates a shallow clone of the current node using CloneFlat,
// applying cloneFn to values as described there. Then it recursively clones all
// child nodes of type *BartNode[V], performing a full deep clone down the subtree.
//
// Child nodes of type *LeafNode[V] and *FringeNode[V] are already cloned
// by CloneFlat.
//
// Returns a new instance of BartNode[V] which is a complete deep clone of the
// receiver node with all descendants.
func (n *BartNode[V]) CloneRec(cloneFn func(V) V) *BartNode[V] {
if n == nil {
return nil
}
// Perform a flat clone of the current node.
c := n.CloneFlat(cloneFn)
// Recursively clone all child nodes of type *BartNode[V]
for i, kidAny := range c.Children.Items {
if kid, ok := kidAny.(*BartNode[V]); ok {
c.Children.Items[i] = kid.CloneRec(cloneFn)
}
}
return c
}
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
+286
View File
@@ -0,0 +1,286 @@
// Copyright (c) 2026 Karl Gaissmaier
// SPDX-License-Identifier: MIT
package nodes
import (
"iter"
"github.com/gaissmai/bart/internal/lpm"
"github.com/gaissmai/bart/internal/sparse"
)
// FastNode is based on [BartNode], but it also uses a cache ([256]uint8)
// per node to speed up traversal of the multi-bit trie.
// Lookups become faster, but this requires more memory per prefix,
// and updates (insertions/deletions) also become slower due to the
// overhead of managing the cache.
type FastNode[V any] struct {
Prefixes sparse.Array256[V]
Children sparse.Array256[any]
// map addr to slice idx (aka rank)
childRankCache [256]uint8
}
// IsEmpty returns true if the node contains no routing entries (prefixes)
// and no child nodes. Empty nodes are candidates for compression or removal
// during trie optimization.
func (n *FastNode[V]) IsEmpty() bool {
if n == nil {
return true
}
return n.Prefixes.Len() == 0 && n.Children.Len() == 0
}
// PrefixCount returns the number of prefixes stored in this node.
func (n *FastNode[V]) PrefixCount() int {
return n.Prefixes.Len()
}
// ChildCount returns the number of slots used in this node.
func (n *FastNode[V]) ChildCount() int {
return n.Children.Len()
}
// InsertPrefix adds or updates a routing entry at the specified index with the given value.
// It returns true if a prefix already existed at that index (indicating an update),
// false if this is a new insertion.
func (n *FastNode[V]) InsertPrefix(idx uint8, val V) (exists bool) {
_, exists = n.Prefixes.InsertAt(idx, val)
return
}
// GetPrefix retrieves the value associated with the prefix at the given index.
// Returns the value and true if found, or zero value and false if not present.
func (n *FastNode[V]) GetPrefix(idx uint8) (val V, exists bool) {
return n.Prefixes.Get(idx)
}
// MustGetPrefix retrieves the value at the specified index, panicking if not found.
// This method should only be used when the caller is certain the index exists.
func (n *FastNode[V]) MustGetPrefix(idx uint8) (val V) {
return n.Prefixes.MustGet(idx)
}
// AllIndices returns an iterator over all prefix entries.
// Each iteration yields the prefix index (uint8) and its associated value (V).
func (n *FastNode[V]) AllIndices() iter.Seq2[uint8, V] {
return func(yield func(uint8, V) bool) {
var buf [256]uint8
for i, idx := range n.Prefixes.AsSlice(&buf) {
if !yield(idx, n.Prefixes.Items[i]) {
return
}
}
}
}
// DeletePrefix removes the prefix at the specified index.
// Returns true if the prefix existed, otherwise false.
func (n *FastNode[V]) DeletePrefix(idx uint8) (exists bool) {
_, exists = n.Prefixes.DeleteAt(idx)
return exists
}
// InsertChild adds a child node at the specified address (0-255).
// The child can be a *FastNode[V], *LeafNode[V], or *FringeNode[V].
// Returns true if a child already existed at that address.
func (n *FastNode[V]) InsertChild(addr uint8, child any) (exists bool) {
var rank0 int
rank0, exists = n.Children.InsertAt(addr, child)
if exists {
// Update only: the value at addr is overwritten in-place by InsertAt.
// childRankCache[addr] is not refreshed because the rank is unchanged
// the position of addr in the sparse slice does not shift on an update.
return
}
// new child inserted? cache the rank value for this addr
//nolint:gosec // G115: integer overflow conversion int -> uint8
n.childRankCache[addr] = uint8(rank0)
// increment all cached ranks after addr
for i := 255; i > int(addr); i-- {
n.childRankCache[i]++
}
return
}
// DeleteChild removes the child node at the specified address.
// This operation is idempotent - removing a non-existent child is safe.
func (n *FastNode[V]) DeleteChild(addr uint8) (exists bool) {
_, exists = n.Children.DeleteAt(addr)
// nothing deleted
if !exists {
return exists
}
// decrement all cached ranks after addr
for i := 255; i > int(addr); i-- {
n.childRankCache[i]--
}
return exists
}
// GetChild retrieves the child node at the specified address.
// Returns the child and true if found, or nil and false if not present.
func (n *FastNode[V]) GetChild(addr uint8) (any, bool) {
if n.Children.Test(addr) {
rank0 := n.childRankCache[addr]
return n.Children.Items[rank0], true
}
return nil, false
}
// MustGetChild retrieves the child at addr using the pre-cached rank stored in
// childRankCache[addr] for a direct O(1) array access without an existence check.
//
// The caller must guarantee that addr is present (Children.Test(addr) == true).
// If addr is absent, childRankCache[addr] contains the number of occupied
// addresses less than addr (maintained by InsertChild/DeleteChild), so the
// behaviour is undefined: either a wrong child is returned silently, or the
// call panics with an index-out-of-range error.
func (n *FastNode[V]) MustGetChild(addr uint8) any {
rank0 := n.childRankCache[addr]
return n.Children.Items[rank0]
}
// AllChildren returns an iterator over all child nodes.
// Each iteration yields the child's address (uint8) and the child node (any).
func (n *FastNode[V]) AllChildren() iter.Seq2[uint8, any] {
return func(yield func(addr uint8, child any) bool) {
var buf [256]uint8
addrs := n.Children.AsSlice(&buf)
for i, addr := range addrs {
if !yield(addr, n.Children.Items[i]) {
return
}
}
}
}
// Contains returns true if an index (idx) has any matching longest-prefix
// in the current node’s prefix table.
//
// This function performs a presence check without retrieving the associated value.
// It is faster than a full lookup, as it only tests for intersection with the
// backtracking bitset for the given index.
//
// The prefix table is structured as a complete binary tree (CBT), and LPM testing
// is done via a bitset operation that maps the traversal path from the given index
// toward its possible ancestors.
func (n *FastNode[V]) Contains(idx uint8) bool {
return n.Prefixes.Intersects(&lpm.LookupTbl[idx])
}
// LookupIdx performs a longest-prefix match (LPM) lookup for the given index (idx)
// within the 8-bit stride-based prefix table at this trie depth.
//
// The function returns the matched base index, associated value, and true if a
// matching prefix exists at this level; otherwise, ok is false.
//
// Internally, the prefix table is organized as a complete binary tree (CBT) indexed
// via the baseIndex function. Unlike the original ART algorithm, this implementation
// does not use an allotment-based approach. Instead, it performs CBT backtracking
// using a bitset-based operation with a precomputed backtracking pattern specific to idx.
func (n *FastNode[V]) LookupIdx(idx uint8) (top uint8, val V, ok bool) {
// top is the idx of the longest-prefix-match
if top, ok = n.Prefixes.IntersectionTop(&lpm.LookupTbl[idx]); ok {
return top, n.MustGetPrefix(top), true
}
return top, val, ok
}
// Lookup is just a simple wrapper for LookupIdx.
func (n *FastNode[V]) Lookup(idx uint8) (val V, ok bool) {
_, val, ok = n.LookupIdx(idx)
return val, ok
}
// CloneFlat returns a shallow copy of the current node, optionally performing deep copies of values.
//
// If cloneFn is nil, the stored values in prefixes are copied directly without modification.
// Otherwise, cloneFn is applied to each stored value for deep cloning.
// Child nodes are cloned shallowly: LeafNode and FringeNode children are cloned via their clone methods,
// but child nodes of type *FastNode[V] (subnodes) are assigned as-is without recursive cloning.
// This method does not recursively clone descendants beyond the immediate children.
//
// Note: The returned node is a new instance with copied slices but only shallow copies of nested nodes,
// except for LeafNode and FringeNode children which are cloned according to cloneFn.
func (n *FastNode[V]) CloneFlat(cloneFn func(V) V) *FastNode[V] {
if n == nil {
return nil
}
c := new(FastNode[V])
if n.IsEmpty() {
return c
}
// copy ...
c.Prefixes = *(n.Prefixes.Copy())
// ... and clone the values
if cloneFn != nil {
for i, v := range c.Prefixes.Items {
c.Prefixes.Items[i] = cloneFn(v)
}
}
// copy ...
c.childRankCache = n.childRankCache
c.Children = *(n.Children.Copy())
// Iterate over children to flat clone leaf/fringe nodes;
// for *FastNode[V] children, keep shallow references (no recursive clone)
for i, anyKid := range c.Children.Items {
switch kid := anyKid.(type) {
case *FastNode[V]:
// Shallow copy
case *LeafNode[V]:
// Clone leaf nodes, applying cloneFn as needed
c.Children.Items[i] = kid.CloneLeaf(cloneFn)
case *FringeNode[V]:
// Clone fringe nodes, applying cloneFn as needed
c.Children.Items[i] = kid.CloneFringe(cloneFn)
default:
panic("logic error, wrong node type")
}
}
return c
}
// CloneRec performs a recursive deep copy of the node and all its descendants.
//
// If cloneFn is nil, the stored values are copied directly without modification.
// Otherwise cloneFn is applied to each stored value for deep cloning.
//
// This method first creates a shallow clone of the current node using CloneFlat,
// applying cloneFn to values as described there. Then it recursively clones all
// child nodes of type *FastNode[V], performing a full deep clone down the subtree.
//
// Child nodes of type *LeafNode[V] and *FringeNode[V] are already cloned
// by CloneFlat.
//
// Returns a new instance of FastNode[V] which is a complete deep clone of the
// receiver node with all descendants.
func (n *FastNode[V]) CloneRec(cloneFn func(V) V) *FastNode[V] {
if n == nil {
return nil
}
// Perform a flat clone of the current node.
c := n.CloneFlat(cloneFn)
// Recursively clone all child nodes of type *FastNode[V]
for i, kidAny := range c.Children.Items {
if kid, ok := kidAny.(*FastNode[V]); ok {
c.Children.Items[i] = kid.CloneRec(cloneFn)
}
}
return c
}
File diff suppressed because it is too large. Load diff
+221
View File
@@ -0,0 +1,221 @@
// Copyright (c) 2026 Karl Gaissmaier
// SPDX-License-Identifier: MIT
package nodes
import (
"iter"
"github.com/gaissmai/bart/internal/bitset"
"github.com/gaissmai/bart/internal/lpm"
"github.com/gaissmai/bart/internal/sparse"
)
// LiteNode is a space-optimized version of [BartNode] that tracks prefix existence
// without storing associated values.
type LiteNode[V any] struct {
Children sparse.Array256[any]
Prefixes struct {
// BitSet256 tracks the presence of prefixes at this level.
bitset.BitSet256
// Count maintains the current number of set bits, updated on modification
// to avoid expensive population counting.
Count uint16
}
}
// IsEmpty returns true if the node contains no routing entries (prefixes)
// and no child nodes. Empty nodes are candidates for compression or removal
// during trie optimization.
func (n *LiteNode[V]) IsEmpty() bool {
if n == nil {
return true
}
return n.Prefixes.Count == 0 && n.Children.Len() == 0
}
// PrefixCount returns the number of prefixes stored in this node.
func (n *LiteNode[V]) PrefixCount() int {
return int(n.Prefixes.Count)
}
// ChildCount returns the number of slots used in this node.
func (n *LiteNode[V]) ChildCount() int {
return n.Children.Len()
}
// InsertPrefix adds a routing entry at the specified index.
// It returns true if a prefix already existed at that index,
// false if this is a new insertion.
func (n *LiteNode[V]) InsertPrefix(idx uint8, _ V) (exists bool) {
if exists = n.Prefixes.Test(idx); exists {
return exists
}
n.Prefixes.Set(idx)
n.Prefixes.Count++
return exists
}
func (n *LiteNode[V]) GetPrefix(idx uint8) (_ V, exists bool) {
// no docstring by intention
exists = n.Prefixes.Test(idx)
return
}
func (n *LiteNode[V]) MustGetPrefix(idx uint8) (_ V) {
// no docstring by intention
return
}
// AllIndices returns an iterator over all prefix entries.
// Each iteration yields the prefix index (uint8) and its associated value (V).
func (n *LiteNode[V]) AllIndices() iter.Seq2[uint8, V] {
var zero V
return func(yield func(uint8, V) bool) {
var buf [256]uint8
for _, idx := range n.Prefixes.AsSlice(&buf) {
if !yield(idx, zero) {
return
}
}
}
}
// DeletePrefix removes the prefix at the specified index.
// Returns true if the prefix existed, and false otherwise.
func (n *LiteNode[V]) DeletePrefix(idx uint8) (exists bool) {
if exists = n.Prefixes.Test(idx); !exists {
return false
}
n.Prefixes.Clear(idx)
n.Prefixes.Count--
return true
}
// InsertChild adds a child node at the specified address (0-255).
// The child can be a *LiteNode[V], *LeafNode, or *FringeNode.
// Returns true if a child already existed at that address.
func (n *LiteNode[V]) InsertChild(addr uint8, child any) (exists bool) {
_, exists = n.Children.InsertAt(addr, child)
return
}
// GetChild retrieves the child node at the specified address.
// Returns the child and true if found, or nil and false if not present.
func (n *LiteNode[V]) GetChild(addr uint8) (any, bool) {
return n.Children.Get(addr)
}
// MustGetChild retrieves the child at the specified address, panicking if not found.
// This method should only be used when the caller is certain the child exists.
func (n *LiteNode[V]) MustGetChild(addr uint8) any {
return n.Children.MustGet(addr)
}
// AllChildren returns an iterator over all child nodes.
// Each iteration yields the child's address (uint8) and the child node (any).
func (n *LiteNode[V]) AllChildren() iter.Seq2[uint8, any] {
return func(yield func(addr uint8, child any) bool) {
var buf [256]uint8
addrs := n.Children.AsSlice(&buf)
for i, addr := range addrs {
if !yield(addr, n.Children.Items[i]) {
return
}
}
}
}
// DeleteChild removes the child node at the specified address.
// This operation is idempotent - removing a non-existent child is safe.
func (n *LiteNode[V]) DeleteChild(addr uint8) (exists bool) {
_, exists = n.Children.DeleteAt(addr)
return exists
}
// Contains returns true if an index (idx) has any matching longest-prefix
// in the current node’s prefix table.
//
// This function performs a presence check.
//
// The prefix table is structured as a complete binary tree (CBT), and LPM testing
// is done via a bitset operation that maps the traversal path from the given index
// toward its possible ancestors.
func (n *LiteNode[V]) Contains(idx uint8) bool {
return n.Prefixes.Intersects(&lpm.LookupTbl[idx])
}
// LookupIdx performs a longest-prefix match (LPM) lookup for the given index (idx)
// within the 8-bit stride-based prefix table at this trie depth.
//
// The function returns the matched index and whether a matching prefix
// exists at this level. The value type parameter exists only to satisfy interfaces.
//
// Internally, the prefix table is organized as a complete binary tree (CBT) indexed
// via the baseIndex function. Unlike the original ART algorithm, this implementation
// does not use an allotment-based approach. Instead, it performs CBT backtracking
// using a bitset-based operation with a precomputed backtracking pattern specific to idx.
func (n *LiteNode[V]) LookupIdx(idx uint8) (top uint8, _ V, ok bool) {
top, ok = n.Prefixes.IntersectionTop(&lpm.LookupTbl[idx])
return
}
// Lookup is just a simple wrapper for LookupIdx.
func (n *LiteNode[V]) Lookup(idx uint8) (_ V, ok bool) {
_, _, ok = n.LookupIdx(idx)
return
}
// CloneFlat returns a shallow copy of the current node.
//
// CloneFn is only used for interface satisfaction.
func (n *LiteNode[V]) CloneFlat(_ func(V) V) *LiteNode[V] {
if n == nil {
return nil
}
c := new(LiteNode[V])
if n.IsEmpty() {
return c
}
// copy simple values
c.Prefixes = n.Prefixes
// sparse array
c.Children = *(n.Children.Copy())
// no values to copy
return c
}
// CloneRec performs a recursive deep copy of the node and all its descendants.
//
// cloneFn is only used for interface satisfaction.
//
// It first creates a shallow clone of the current node using CloneFlat.
// Then it recursively clones all child nodes of type *LiteNode[V],
// performing a full deep clone down the subtree.
//
// Child nodes of type *LeafNode and *FringeNode are already copied
// by CloneFlat.
//
// Returns a new instance of LiteNode[V] which is a complete deep clone of the
// receiver node with all descendants.
func (n *LiteNode[V]) CloneRec(_ func(V) V) *LiteNode[V] {
if n == nil {
return nil
}
// Perform a flat clone of the current node.
c := n.CloneFlat(nil)
// Recursively clone all child nodes of type *LiteNode[V]
for i, kidAny := range c.Children.Items {
if kid, ok := kidAny.(*LiteNode[V]); ok {
c.Children.Items[i] = kid.CloneRec(nil)
}
}
return c
}
File diff suppressed because it is too large. Load diff
+337
View File
@@ -0,0 +1,337 @@
// Copyright (c) 2026 Karl Gaissmaier
// SPDX-License-Identifier: MIT
package nodes
import (
"cmp"
"fmt"
"net/netip"
"strconv"
"strings"
"github.com/gaissmai/bart/internal/art"
)
// strideLen represents the byte stride length for the multibit trie.
// Each stride processes 8 bits (1 byte) at a time.
const strideLen = 8
// MaxItems defines the maximum number of prefixes or children that can be stored in a single node.
// This corresponds to 256 possible values for an 8-bit stride.
const MaxItems = 256
// MaxTreeDepth represents the maximum depth of the trie structure.
// For IPv6 addresses, this allows up to 16 bytes of depth.
const MaxTreeDepth = 16
// DepthMask is used for bounds check elimination (BCE) when accessing depth-indexed arrays.
const DepthMask = MaxTreeDepth - 1
// StridePath represents a path through the trie, with a maximum depth of 16 octets for IPv6.
type StridePath [MaxTreeDepth]uint8
// TrieItem, a node has no path information about its predecessors,
// we collect this during the recursive descent.
type TrieItem[V any] struct {
// for traversing, Path/Depth/Idx is needed to get the CIDR back from the trie.
Node any // BartNode, FastNode, LiteNode
Is4 bool
Path StridePath
Depth int
Idx uint8
// for printing
Cidr netip.Prefix
Val V
}
// StatsT, only used for dump, tests and benchmarks
type StatsT struct {
Prefixes int
Children int
SubNodes int
Leaves int
Fringes int
}
type nodeType byte
const (
nullNode nodeType = iota // empty node
fullNode // prefixes and children or path-compressed prefixes
halfNode // no prefixes, only children and path-compressed prefixes
pathNode // only children, no prefix nor path-compressed prefixes
stopNode // no children, only prefixes or path-compressed prefixes
)
// String implements Stringer for nodeType.
func (nt nodeType) String() string {
switch nt {
case nullNode:
return "NULL"
case fullNode:
return "FULL"
case halfNode:
return "HALF"
case pathNode:
return "PATH"
case stopNode:
return "STOP"
default:
return "unreachable"
}
}
// addrFmt, different format strings for IPv4 and IPv6, decimal versus hex.
func addrFmt(addr byte, is4 bool) string {
if is4 {
return fmt.Sprintf("%d", addr)
}
return fmt.Sprintf("0x%02x", addr)
}
// ip stride path, different formats for IPv4 and IPv6, dotted decimal or hex.
//
// 127.0.0
// 2001:0d
func ipStridePath(path StridePath, depth int, is4 bool) string {
buf := new(strings.Builder)
if is4 {
for i, b := range path[:depth] {
if i != 0 {
buf.WriteString(".")
}
buf.WriteString(strconv.Itoa(int(b)))
}
return buf.String()
}
for i, b := range path[:depth] {
if i != 0 && i%2 == 0 {
buf.WriteString(":")
}
fmt.Fprintf(buf, "%02x", b)
}
return buf.String()
}
// CmpPrefix, helper function, compare func for prefix sort,
// all cidrs are already normalized
func CmpPrefix(a, b netip.Prefix) int {
if cmpAddr := a.Addr().Compare(b.Addr()); cmpAddr != 0 {
return cmpAddr
}
return cmp.Compare(a.Bits(), b.Bits())
}
// LeafNode represents a path-compressed routing entry that stores both prefix and value.
// Leaf nodes are used when a prefix doesn't align with trie stride boundaries
// and needs to be stored as a compressed path to save memory.
type LeafNode[V any] struct {
Value V
Prefix netip.Prefix
}
// NewLeafNode creates a new leaf node with the specified prefix and value.
func NewLeafNode[V any](pfx netip.Prefix, val V) *LeafNode[V] {
return &LeafNode[V]{Prefix: pfx, Value: val}
}
// FringeNode represents a path-compressed routing entry that stores only a value.
// The prefix is implicitly defined by the node's position in the trie.
// Fringe nodes are used for prefixes that align exactly with stride boundaries
// (/8, /16, /24, etc.) to save memory by not storing redundant prefix information.
type FringeNode[V any] struct {
Value V
}
// NewFringeNode creates a new fringe node with the specified value.
func NewFringeNode[V any](val V) *FringeNode[V] {
return &FringeNode[V]{Value: val}
}
// IsFringe determines whether a prefix qualifies as a "FringeNode".
// Only prefixes that are stride-aligned (i.e., /8, /16, ..., /128)
// can be fringe-compressed. If these prefixes are inserted at a position
// where depth == (strideCount-1), they are treated as FringeNodes;
// at positions where depth < (strideCount-1), they are treated as LeafNodes.
//
// Example for a stride-aligned prefix like 192.168.1.0/24 (strideCount = 3, modBits = 0):
//
// depth = 3, depth == strideCount : A direct prefix with 0/0 (default route for subtrie).
// depth = 2, depth == (strideCount-1) : A path-compressed fringe.
// depth < 2, depth < (strideCount-1) : A path-compressed leaf.
func IsFringe(depth int, pfxLen int) bool {
strideCount, modBits := DivMod8(pfxLen)
return depth == strideCount-1 && modBits == 0
}
// CmpIndexRank, sort indexes in prefix sort order.
func CmpIndexRank(aIdx, bIdx uint8) int {
// convert idx [1..255] to prefix
aOctet, aBits := art.IdxToPfx(aIdx)
bOctet, bBits := art.IdxToPfx(bIdx)
// cmp the prefixes, first by address and then by bits
if aOctet == bOctet {
return cmp.Compare(aBits, bBits)
}
return cmp.Compare(aOctet, bOctet)
}
// CidrFromPath reconstructs a CIDR prefix from a stride path, depth, and index.
// The prefix is determined by the node's position in the trie and the base index
// from the ART algorithm's complete binary tree representation.
//
// Parameters:
// - path: The stride path through the trie
// - depth: Current depth in the trie
// - is4: True for IPv4 processing, false for IPv6
// - idx: The base index from the prefix table
//
// Returns the reconstructed netip.Prefix.
func CidrFromPath(path StridePath, depth int, is4 bool, idx uint8) netip.Prefix {
depth &= DepthMask // BCE
// retrieve the last octet and pfxLen
octet, pfxLen := art.IdxToPfx(idx)
// set byte in path at depth with last octet
path[depth] = octet
// canonicalize
clear(path[depth+1:])
// make ip addr from octets
var ip netip.Addr
if is4 {
ip = netip.AddrFrom4([4]byte(path[:4]))
} else {
ip = netip.AddrFrom16(path)
}
// calc bits with pathLen and pfxLen
bits := depth<<3 + int(pfxLen)
// PrefixFrom does not allocate and does not mask off the host bits of ip.
// With the clear(), the non-canonical bytes have already been removed.
return netip.PrefixFrom(ip, bits)
}
// CidrForFringe reconstructs a CIDR prefix for a fringe node from the traversal path.
// Since fringe nodes don't store their prefix explicitly, it's derived entirely
// from the node's position in the trie and its final byte value.
//
// Parameters:
// - octets: The path of previous bytes leading up to the fringe.
// - depth: Current depth in the trie (which equals strideCount - 1).
// - is4: True for IPv4 processing, false for IPv6.
// - fringeByte: The actual 8-bit value (0-255) of the prefix at this final stride.
//
// Returns the reconstructed netip.Prefix for the fringe.
func CidrForFringe(octets []byte, depth int, is4 bool, fringeByte uint8) netip.Prefix {
depth &= DepthMask // BCE
var path StridePath
copy(path[:], octets)
path[depth] = fringeByte
// canonicalize, fringe bit boundaries are always a multiple of a byte
clear(path[depth+1:])
// make ip addr from octets
var ip netip.Addr
if is4 {
ip = netip.AddrFrom4([4]byte(path[:4]))
} else {
ip = netip.AddrFrom16(path)
}
// it's a fringe, bits are always aligned on stride boundaries (/8, /16, /24, ...)
bits := (depth + 1) << 3
// PrefixFrom does not allocate and does not mask off the host bits of ip.
// With the clear(), the non-canonical bytes have already been removed.
return netip.PrefixFrom(ip, bits)
}
// DivMod8 returns the count of full 8‑bit strides (bits/8)
// and the remaining bits in the final stride (bits%8) for pfxLen.
//
// ATTENTION: Split the IP prefixes at 8-bit borders, count from 0.
//
// /7, /15, /23, /31, ..., /127
//
// BitPos: [0-7],[8-15],[16-23],[24-31],[32]
// BitPos: [0-7],[8-15],[16-23],[24-31],[32-39],[40-47],[48-55],[56-63],...,[120-127],[128]
//
// 0.0.0.0/0 => strideCount: 0, modBits: 0 (default route)
// 0.0.0.0/7 => strideCount: 0, modBits: 7
// 0.0.0.0/8 => strideCount: 1, modBits: 0 (fringe candidate)
// 10.0.0.0/8 => strideCount: 1, modBits: 0 (fringe candidate)
// 10.0.0.0/22 => strideCount: 2, modBits: 6
// 10.0.0.0/29 => strideCount: 3, modBits: 5
// 10.0.0.0/32 => strideCount: 4, modBits: 0 (fringe candidate)
//
// ::/0 => strideCount: 0, modBits: 0 (default route)
// ::1/128 => strideCount: 16, modBits: 0 (fringe candidate)
// 2001:db8::/42 => strideCount: 5, modBits: 2
// 2001:db8::/56 => strideCount: 7, modBits: 0 (fringe candidate)
//
// /32 and /128 prefixes are special, they never form a new node,
// At the end of the trie (IPv4: depth 4, IPv6: depth 16) they are always
// inserted as a path‑compressed fringe.
//
// We are not splitting at /8, /16, ..., because this would mean that the
// first node would have 512 prefixes, 9 bits from [0-8]. All remaining nodes
// would then only have 8 bits from [9-16], [17-24], [25..32], ...
// but the algorithm would then require a variable length bitset
// or imply a double-sized bitset.
//
// If you can commit to a fixed size of [4]uint64, then the algorithm is
// much faster due to modern CPUs.
//
// Perhaps a future Go version that supports SIMD instructions for the [4]uint64 vectors
// will make the algorithm even faster on suitable hardware.
func DivMod8(pfxLen int) (strideCount int, modBits uint8) {
// strideCount: range from 0..4 or 0..16
// modBits: range from 0..7
return pfxLen >> 3, uint8(pfxLen & 7)
}
// CloneLeaf creates and returns a copy of the leafNode receiver.
// If cloneFn is nil, the value is copied directly without modification.
// Otherwise, cloneFn is applied to the value for deep cloning.
// The prefix field is always copied as is.
func (l *LeafNode[V]) CloneLeaf(cloneFn func(V) V) *LeafNode[V] {
if l == nil {
return nil
}
if cloneFn == nil {
return &LeafNode[V]{Prefix: l.Prefix, Value: l.Value}
}
return &LeafNode[V]{Prefix: l.Prefix, Value: cloneFn(l.Value)}
}
// CloneFringe creates and returns a copy of the FringeNode receiver.
// If cloneFn is nil, the value is copied directly without modification.
// Otherwise, cloneFn is applied to the value for deep cloning.
func (l *FringeNode[V]) CloneFringe(cloneFn func(V) V) *FringeNode[V] {
if l == nil {
return nil
}
if cloneFn == nil {
return &FringeNode[V]{Value: l.Value}
}
return &FringeNode[V]{Value: cloneFn(l.Value)}
}
-160
View File
@@ -1,160 +0,0 @@
// Copyright (c) 2024 Karl Gaissmaier
// SPDX-License-Identifier: MIT
// package sparse implements a generic sparse array
// with popcount compression.
package sparse
import (
"github.com/gaissmai/bart/internal/bitset"
)
// Array, a generic implementation of a sparse array
// with popcount compression and payload T.
type Array[T any] struct {
bitset.BitSet
Items []T
}
// Get the value at i from sparse array.
//
// example: Array.Get(5) -> Array.Items[1]
//
// ⬇
// BitSet: [0|0|1|0|0|1|0|1|...] <- 3 bits set
// Items: [*|*|*] <- len(Items) = 3
// ⬆
//
// BitSet.Test(5): true
// BitSet.popcount(5): 2, for interval [0,5]
// BitSet.Rank0(5): 1, equal popcount(5)-1
func (s *Array[T]) Get(i uint) (value T, ok bool) {
if s.Test(i) {
return s.Items[s.Rank0(i)], true
}
return
}
// MustGet, use it only after a successful test
// or the behavior is undefined, maybe it panics.
func (s *Array[T]) MustGet(i uint) T {
return s.Items[s.Rank0(i)]
}
// UpdateAt or set the value at i via callback. The new value is returned
// and true if the value was already present.
func (s *Array[T]) UpdateAt(i uint, cb func(T, bool) T) (newValue T, wasPresent bool) {
var rank0 int
// if already set, get current value
var oldValue T
if wasPresent = s.Test(i); wasPresent {
rank0 = s.Rank0(i)
oldValue = s.Items[rank0]
}
// callback function to get updated or new value
newValue = cb(oldValue, wasPresent)
// already set, update and return value
if wasPresent {
s.Items[rank0] = newValue
return newValue, wasPresent
}
// new value, insert into bitset ...
s.BitSet = s.Set(i)
// bitset has changed, recalc rank
rank0 = s.Rank0(i)
// ... and insert value into slice
s.insertItem(rank0, newValue)
return newValue, wasPresent
}
// Len returns the number of items in sparse array.
func (s *Array[T]) Len() int {
return len(s.Items)
}
// Copy returns a shallow copy of the Array.
// The elements are copied using assignment, this is no deep clone.
func (s *Array[T]) Copy() *Array[T] {
if s == nil {
return nil
}
return &Array[T]{
BitSet: s.BitSet.Clone(),
Items: append(s.Items[:0:0], s.Items...),
}
}
// InsertAt a value at i into the sparse array.
// If the value already exists, overwrite it with val and return true.
func (s *Array[T]) InsertAt(i uint, value T) (exists bool) {
// slot exists, overwrite value
if s.Len() != 0 && s.Test(i) {
s.Items[s.Rank0(i)] = value
return true
}
// new, insert into bitset ...
s.BitSet = s.Set(i)
// ... and slice
s.insertItem(s.Rank0(i), value)
return false
}
// DeleteAt a value at i from the sparse array, zeroes the tail.
func (s *Array[T]) DeleteAt(i uint) (value T, exists bool) {
if s.Len() == 0 || !s.Test(i) {
return
}
rank0 := s.Rank0(i)
value = s.Items[rank0]
// delete from slice
s.deleteItem(rank0)
// delete from bitset
s.BitSet = s.Clear(i)
return value, true
}
// insertItem inserts the item at index i, shift the rest one pos right
//
// It panics if i is out of range.
func (s *Array[T]) insertItem(i int, item T) {
if len(s.Items) < cap(s.Items) {
s.Items = s.Items[:len(s.Items)+1] // fast resize, no alloc
} else {
var zero T
s.Items = append(s.Items, zero) // append one item, mostly enlarge cap by more than one item
}
copy(s.Items[i+1:], s.Items[i:])
s.Items[i] = item
}
// deleteItem at index i, shift the rest one pos left and clears the tail item
//
// It panics if i is out of range.
func (s *Array[T]) deleteItem(i int) {
var zero T
nl := len(s.Items) - 1 // new len
copy(s.Items[i:], s.Items[i+1:]) // overwrite item at [i]
s.Items[nl] = zero // clear the tail item
s.Items = s.Items[:nl] // new len, keep cap is unchanged
}
+220
View File
@@ -0,0 +1,220 @@
// Copyright (c) 2026 Karl Gaissmaier
// SPDX-License-Identifier: MIT
// Package sparse provides a compact and efficient sparse array
// implementation for addressable key ranges from [0..255].
//
// It is optimized for use in radix tries and lookup structures
// where only a small subset of possible keys contain actual data.
//
// Internally, an Array256 combines a fixed-size BitSet256 with
// a compact packed Items slice. The bitset tracks which key slots
// are occupied and enables fast index mapping via popcount (Rank),
// while the Items slice stores the associated payloads.
//
// Lookup, insertion, and deletion operate in O(1) time (worst-case O(n)
// only for shifting/copying in extremely full arrays).
//
// This module avoids memory allocations where possible and provides
// predictable performance even under heavy insert/delete workloads.
package sparse
import (
"github.com/gaissmai/bart/internal/bitset"
)
// Array256 is a popcount-compressed sparse array for up to 256 item slots.
//
// Internally, it consists of:
// - a BitSet256, where each set bit marks a present slot (i.e., key i used)
// - a compact Items slice, which stores the payloads only for the set bits
//
// Each occupied index i ∈ [0..255] can be tested using .Test(i).
// The actual index within the Items slice is computed via .Rank(i)-1.
//
// All insert/delete operations update both the bitset and item slice
// to preserve this mapping invariant. Direct mutation of the underlying
// bitset (via Set or Clear) is forbidden and will panic.
//
// This structure allows extremely fast and compact prefix-based routing,
// highly efficient child-node containers in prefix tries, or memory-safe
// alternatives to map[uint8]T with deterministic iteration.
//
// Example layout:
//
// BitSet256: [0 1 0 0 0 1 ...] // bits 1 and 5 set
// Items: [T1, T2] // a.Items[0] ↔ key 1, a.Items[1] ↔ key 5
type Array256[T any] struct {
bitset.BitSet256
Items []T
}
// Set panics. The bitset is internally coupled with Items[].
// Use InsertAt to add or overwrite at index i.
func (a *Array256[T]) Set(uint8) {
panic("forbidden, use InsertAt")
}
// Clear panics. The bitset is internally coupled with Items[].
func (a *Array256[T]) Clear(uint8) {
panic("forbidden, use DeleteAt")
}
// Get returns the value at index i and whether it exists.
//
// If the bit for i is not set, ok is false and value is the zero-value of T.
//
// example: a.Get(5) -> a.Items[1]
//
// ⬇
// BitSet256: [0|0|1|0|0|1|0|...|1] <- 3 bits set
// Items: [*|*|*] <- len(Items) = 3
// ⬆
//
// BitSet256.Test(5): true
// BitSet256.Rank(5): 2
func (a *Array256[T]) Get(i uint8) (value T, ok bool) {
if a.Test(i) {
return a.Items[a.Rank(i)-1], true
}
return
}
// MustGet returns the value at index i without checking if it exists.
//
// Use only after ensuring i is set (via Test(i)); otherwise it may return
// an incorrect value or panic. Intended only for tight, validated loops.
func (a *Array256[T]) MustGet(i uint8) T {
return a.Items[a.Rank(i)-1]
}
// Len returns the number of items in sparse array.
func (a *Array256[T]) Len() int {
return len(a.Items)
}
// Copy returns a shallow copy of the Array.
// The elements are copied using assignment, this is no deep clone.
func (a *Array256[T]) Copy() *Array256[T] {
if a == nil {
return nil
}
c := &Array256[T]{
BitSet256: a.BitSet256,
Items: make([]T, len(a.Items)),
}
copy(c.Items, a.Items)
return c
}
// InsertAt inserts or overwrites the value at sparse index i.
//
// If the slot at i already exists, its value is overwritten in-place and
// (rank0, true) is returned, where rank0 = Rank(i)-1 is the 0-based index
// into Items[].
//
// If the slot is new, the bit for i is set, the value is inserted into Items[]
// at the correct packed position, and (rank0, false) is returned.
//
// rank0 can be cached by the caller to directly access Items[rank0]
// without a second Rank() call.
func (a *Array256[T]) InsertAt(i uint8, value T) (rank0 int, exists bool) {
// slot exists, just overwrite value
if a.Test(i) {
rank0 = a.Rank(i) - 1
a.Items[rank0] = value
return rank0, true
}
// Since i is not set yet, Rank(i) before Set(i) is exactly
// the index where the new item should be inserted (equivalent to Rank(i)-1 after Set(i)).
rank0 = a.Rank(i)
// new, insert into bitset ...
a.BitSet256.Set(i)
// ... and insert value into slice
a.insertItem(rank0, value)
return rank0, false
}
// DeleteAt removes the value at index i from the sparse array,
// shifting remaining items down in the slice and clearing the bit.
//
// If the entry exists, it is returned together with true.
// If i is not present, the zero value and false are returned.
func (a *Array256[T]) DeleteAt(i uint8) (value T, exists bool) {
if a.Len() == 0 || !a.Test(i) {
return value, exists
}
rank0 := a.Rank(i) - 1
value = a.Items[rank0]
// delete from slice
a.deleteItem(rank0)
// delete from bitset
a.BitSet256.Clear(i)
return value, true
}
// insertItem inserts a new element at the given index position i in the Items slice,
// shifting all following elements one position to the right to make space.
//
// This method must be called with the correct insertion index - that is,
// the rank-0 value of the corresponding bit index i in BitSet256 (calculated before Set(i)).
//
// The slice will be extended by one element. If the capacity allows, this is done
// without reallocation (fast path); otherwise slice growth occurs (slow path).
//
// Example (inserting at rank0 == 2):
//
// Items before: [A B C D]
// After insertItem(2, X): [A B X C D]
//
// Panics if i is out of range (i < 0 or i > len(Items)).
func (a *Array256[T]) insertItem(i int, item T) {
if len(a.Items) < cap(a.Items) {
a.Items = a.Items[:len(a.Items)+1] // fast resize, no alloc
} else {
var zero T
a.Items = append(a.Items, zero) // append one item, mostly enlarge cap by more than one item
}
_ = a.Items[i] // BCE
copy(a.Items[i+1:], a.Items[i:]) // shift one slot right, starting at [i]
a.Items[i] = item // insert new item at [i]
}
// deleteItem removes the item at index i from the Items slice,
// shifting all subsequent items one position to the left,
// and clearing the final (now duplicate) slot.
//
// This is used to remove Items[i] in response to a BitSet256.Clear(i)
// operation, where i is guaranteed to be set and valid.
//
// Example (deleting rank0 == 1):
//
// Items before: [A B C D]
// After deleteItem(1): [A C D]
//
// The tail item is explicitly cleared (assigned the zero-value of T)
// to avoid holding references (important for GC with pointer types).
//
// Panics if i is out of range (i < 0 or i >= len(Items)).
func (a *Array256[T]) deleteItem(i int) {
var zero T
_ = a.Items[i] // BCE
copy(a.Items[i:], a.Items[i+1:]) // shift left, overwrite item at [i]
nl := len(a.Items) - 1 // new len
a.Items[nl] = zero // clear the tail item
a.Items = a.Items[:nl] // new len, cap is unchanged
}
+207
View File
@@ -0,0 +1,207 @@
// Copyright (c) 2026 Karl Gaissmaier
// SPDX-License-Identifier: MIT
package golden
import (
"cmp"
"fmt"
"net/netip"
"slices"
)
// Table is a simple and slow route table, implemented as a slice of prefixes
// and values as a golden reference for bart.
type Table[V any] []TableItem[V]
type TableItem[V any] struct {
Pfx netip.Prefix
Val V
}
func (g TableItem[V]) String() string {
return fmt.Sprintf("(%s, %v)", g.Pfx, g.Val)
}
func (t *Table[V]) Insert(pfx netip.Prefix, val V) {
pfx = pfx.Masked()
for i, item := range *t {
if item.Pfx == pfx {
(*t)[i].Val = val // de-dupe
return
}
}
*t = append(*t, TableItem[V]{pfx, val})
}
func (t *Table[V]) Delete(pfx netip.Prefix) (exists bool) {
pfx = pfx.Masked()
for i, item := range *t {
if item.Pfx == pfx {
*t = slices.Delete(*t, i, i+1)
return true
}
}
return false
}
func (t Table[V]) AllSorted() []netip.Prefix {
var result []netip.Prefix
for _, item := range t {
result = append(result, item.Pfx)
}
slices.SortFunc(result, cmpPrefix)
return result
}
func (t Table[V]) Get(pfx netip.Prefix) (val V, ok bool) {
pfx = pfx.Masked()
for _, item := range t {
if item.Pfx == pfx {
return item.Val, true
}
}
return val, false
}
func (t *Table[V]) Update(pfx netip.Prefix, cb func(V, bool) V) (val V) {
pfx = pfx.Masked()
for i, item := range *t {
if item.Pfx == pfx {
// update val
val = cb(item.Val, true)
(*t)[i].Val = val
return val
}
}
// new val
val = cb(val, false)
*t = append(*t, TableItem[V]{pfx, val})
return val
}
func (ta *Table[V]) Union(tb *Table[V]) {
for _, bItem := range *tb {
var match bool
for i, aItem := range *ta {
if aItem.Pfx == bItem.Pfx {
(*ta)[i] = bItem
match = true
break
}
}
if !match {
*ta = append(*ta, bItem)
}
}
}
func (t Table[V]) Lookup(addr netip.Addr) (val V, ok bool) {
bestLen := -1
for _, item := range t {
if item.Pfx.Contains(addr) && item.Pfx.Bits() > bestLen {
val = item.Val
ok = true
bestLen = item.Pfx.Bits()
}
}
return val, ok
}
func (t Table[V]) LookupPrefix(pfx netip.Prefix) (val V, ok bool) {
pfx = pfx.Masked()
bestLen := -1
for _, item := range t {
if item.Pfx.Overlaps(pfx) && item.Pfx.Bits() <= pfx.Bits() && item.Pfx.Bits() > bestLen {
val = item.Val
ok = true
bestLen = item.Pfx.Bits()
}
}
return val, ok
}
func (t Table[V]) LookupPrefixLPM(pfx netip.Prefix) (lpm netip.Prefix, val V, ok bool) {
pfx = pfx.Masked()
bestLen := -1
for _, item := range t {
if item.Pfx.Overlaps(pfx) && item.Pfx.Bits() <= pfx.Bits() && item.Pfx.Bits() > bestLen {
val = item.Val
lpm = item.Pfx
ok = true
bestLen = item.Pfx.Bits()
}
}
return lpm, val, ok
}
func (t Table[V]) Subnets(pfx netip.Prefix) []netip.Prefix {
pfx = pfx.Masked()
var result []netip.Prefix
for _, item := range t {
if pfx.Overlaps(item.Pfx) && pfx.Bits() <= item.Pfx.Bits() {
result = append(result, item.Pfx)
}
}
slices.SortFunc(result, cmpPrefix)
return result
}
func (t Table[V]) Supernets(pfx netip.Prefix) []netip.Prefix {
pfx = pfx.Masked()
var result []netip.Prefix
for _, item := range t {
if item.Pfx.Overlaps(pfx) && item.Pfx.Bits() <= pfx.Bits() {
result = append(result, item.Pfx)
}
}
slices.SortFunc(result, cmpPrefix)
slices.Reverse(result)
return result
}
func (t Table[V]) OverlapsPrefix(pfx netip.Prefix) bool {
pfx = pfx.Masked()
for _, p := range t {
if p.Pfx.Overlaps(pfx) {
return true
}
}
return false
}
func (ta *Table[V]) Overlaps(tb *Table[V]) bool {
for _, aItem := range *ta {
for _, bItem := range *tb {
if aItem.Pfx.Overlaps(bItem.Pfx) {
return true
}
}
}
return false
}
// Sort, inplace by netip.Prefix, all prefixes are in normalized form
func (t *Table[V]) Sort() {
slices.SortFunc(*t, func(a, b TableItem[V]) int {
return cmpPrefix(a.Pfx, b.Pfx)
})
}
// cmpPrefix, helper function, compare func for prefix sort,
// all cidrs are already normalized
func cmpPrefix(a, b netip.Prefix) int {
if cmpAddr := a.Addr().Compare(b.Addr()); cmpAddr != 0 {
return cmpAddr
}
return cmp.Compare(a.Bits(), b.Bits())
}
+129
View File
@@ -0,0 +1,129 @@
// Copyright (c) 2026 Karl Gaissmaier
// SPDX-License-Identifier: MIT
package random
import (
"fmt"
"math/rand/v2"
"net/netip"
)
// mpp is a abbreviation and panics on non masked prefixes.
//
//nolint:gochecknoglobals
var mpp = func(s string) netip.Prefix {
pfx := netip.MustParsePrefix(s)
if pfx == pfx.Masked() {
return pfx
}
panic(fmt.Sprintf("%s is not canonicalized as %s", s, pfx.Masked()))
}
// Prefix returns a randomly generated prefix
func Prefix(prng *rand.Rand) netip.Prefix {
if prng.IntN(2) == 1 {
return Prefix4(prng)
}
return Prefix6(prng)
}
func Prefix4(prng *rand.Rand) netip.Prefix {
bits := prng.IntN(33)
return netip.PrefixFrom(IP4(prng), bits).Masked()
}
func Prefix6(prng *rand.Rand) netip.Prefix {
bits := prng.IntN(129)
return netip.PrefixFrom(IP6(prng), bits).Masked()
}
func IP4(prng *rand.Rand) netip.Addr {
var b [4]byte
for i := range b {
//nolint:gosec // G115: integer overflow conversion uint -> byte
b[i] = byte(prng.UintN(256))
}
return netip.AddrFrom4(b)
}
func IP6(prng *rand.Rand) netip.Addr {
var b [16]byte
for i := range b {
//nolint:gosec // G115: integer overflow conversion uint -> byte
b[i] = byte(prng.UintN(256))
}
return netip.AddrFrom16(b)
}
func IP(prng *rand.Rand) netip.Addr {
if prng.IntN(2) == 1 {
return IP4(prng)
}
return IP6(prng)
}
func RealWorldPrefixes4(prng *rand.Rand, n int) []netip.Prefix {
set := make(map[netip.Prefix]struct{})
pfxs := make([]netip.Prefix, 0, n)
for len(set) < n {
pfx := Prefix4(prng)
// skip too small or too big masks
if pfx.Bits() < 8 || pfx.Bits() > 28 {
continue
}
// skip reserved/experimental ranges (e.g., 240.0.0.0/8)
if pfx.Overlaps(mpp("240.0.0.0/8")) {
continue
}
if _, ok := set[pfx]; !ok {
set[pfx] = struct{}{}
pfxs = append(pfxs, pfx)
}
}
return pfxs
}
func RealWorldPrefixes6(prng *rand.Rand, n int) []netip.Prefix {
set := make(map[netip.Prefix]struct{})
pfxs := make([]netip.Prefix, 0, n)
for len(set) < n {
pfx := Prefix6(prng)
// skip too small or too big masks
if pfx.Bits() < 16 || pfx.Bits() > 56 {
continue
}
// skip non global routes seen in the real world
if !pfx.Overlaps(mpp("2000::/3")) {
continue
}
if pfx.Addr().Compare(mpp("2c0f::/16").Addr()) == 1 {
continue
}
if _, ok := set[pfx]; !ok {
set[pfx] = struct{}{}
pfxs = append(pfxs, pfx)
}
}
return pfxs
}
func RealWorldPrefixes(prng *rand.Rand, n int) []netip.Prefix {
pfxs := make([]netip.Prefix, 0, n)
pfxs = append(pfxs, RealWorldPrefixes4(prng, n/2)...)
pfxs = append(pfxs, RealWorldPrefixes6(prng, n-len(pfxs))...)
prng.Shuffle(len(pfxs), func(i, j int) {
pfxs[i], pfxs[j] = pfxs[j], pfxs[i]
})
return pfxs
}
+77
View File
@@ -0,0 +1,77 @@
// Copyright (c) 2026 Karl Gaissmaier
// SPDX-License-Identifier: MIT
// Package value provides runtime utilities for working with generic type
// parameters as payload values in bart data structures.
//
// The package offers three main categories of utilities:
//
// # V as type struct{} Detection
//
// IsEmptyStruct[V] detects whether a type parameter V is a `struct{}`. These
// types carry no information in their values. Omitting them from dumps
// and prints reduces line noise and improves readability.
//
// # Value Equality
//
// The [Equal] function enables custom equality logic for payload values.
// When V implements an `Equal(V) bool` method, [Equal] uses that implementation,
// avoiding the potentially expensive [reflect.DeepEqual] fallback.
//
// # Value Cloning
//
// The [CloneFnFactory] supports copying of payload values for persistent
// operations. When V implements a `Clone() V` method,
// bart methods like InsertPersist, DeletePersist, and UnionPersist use the
// generated clone function to create independent copies.
//
// This is an internal package used by the bart data structure implementation.
package value
// IsEmptyStruct reports whether type V is the unnamed empty struct type `struct{}`.
func IsEmptyStruct[V any]() bool {
var zero V
_, ok := any(zero).(struct{})
return ok
}
// Equal compares two values of type V for equality.
//
// If V implements an `Equal(V) bool` method, its custom equality logic is used.
// Otherwise, values are compared directly using the == operator.
//
// If V is not comparable at runtime (such as a slice or map without an Equal
// method), a runtime panic will occur.
//
// Note: If V implements Equal(V) bool with a pointer receiver, the Equal
// method should handle nil receivers gracefully.
func Equal[V any](v1, v2 V) bool {
if eq, ok := any(v1).(interface{ Equal(V) bool }); ok {
return eq.Equal(v2)
}
return any(v1) == any(v2)
}
// CloneFnFactory returns a function that takes a value of type V and returns
// a copy by calling its `Clone` method.
//
// If V does not implement a `Clone() V` method, it returns nil.
//
// Note: If V implements `Clone() V` with a pointer receiver, the `Clone`
// method should handle nil receivers gracefully.
func CloneFnFactory[V any]() func(V) V {
var zero V
// Safely check if V implements the clone method using an inline interface.
if _, ok := any(zero).(interface{ Clone() V }); ok {
// Return an anonymous closure directly.
// Since we already proved V implements the interface above,
// the direct type assertion here is guaranteed to succeed.
return func(val V) V {
return any(val).(interface{ Clone() V }).Clone()
}
}
return nil
}