Expand description
size-classes — const-built size-class tables + a compile-time-derived
O(1) size→class lookup + an alignment-divisibility classifier.
Every slab / pool / arena allocator reinvents the same trio: a table of
block sizes, an O(1) map from a requested byte size to the smallest class
that fits it, and a classifier that also honours alignment via stride
divisibility. This crate packages that trio as a const-evaluated,
no_std, zero-dependency, #![forbid(unsafe_code)] unit — the table
shape is a parameter, so a consumer can bake its own scheme and still
get the derived lookup and the alignment-aware classifier for free.
§The three pieces
build_table— aconst fnsorted-merge of a geometric progression (geo_countclasses, eachround_up(ceil(prev * num / den), min_block)) with a strictly increasing,min_block-multiple,>= min_blocklist of explicitextras(page-aligned classes, an exact size the geometric run skips, a feature-gated medium tier, …).build_size2class— derives the O(1)size→classlookup from a table at compile time with the monotone-pointer technique (O(buckets + classes)const-eval) and a compile-timeu8pin.SizeClasses::class_for— an O(1) fast path foralign <= min_blockand a provably-equivalent jump slow path for larger alignments: roundblockup to the next multiple ofalignvia a bitmask, re-seed through the lookup, and so skip whole runs of non-divisible classes instead of stepping by one. Without it, a request whosealignexceeds what the caller’s classifier happens to handle silently falls through to the caller’s whole-segment path — a real bug class in hand-rolled allocators (sefer-alloc’s own motivating case, the allocator this crate was extracted from:align >= 512). The classifier picks analign-divisible stride; seeSizeClasses::class_for’s# Preconditionsfor the separate base-address requirement this crate cannot check.SizeClasses::try_class_foris the checked twin – validatesaligninstead of assuming it. Use it unlessalignis already known-valid by construction (e.g. taken from acore::alloc::Layout).
§The huge threshold is a policy parameter
SizeClasses::is_huge compares against a caller-supplied
Params::huge_threshold. The crate has no notion of an OS segment size;
the consumer picks the threshold that separates “large” from “huge” for its
own segment policy.
§Deriving lengths
SizeClasses is generic over both the table length N (geo_count +
extras.len()) and the lookup length L (max_class / min_block + 1,
via size2class_len). Both are pure functions of the Params, but
L needs the built table’s LAST entry (max_class) — there is no
shortcut around building TABLE once to read it; SizeClasses::build
then builds the same table again internally, from the same Params,
so the two never drift apart:
const PARAMS: Params = Params::new(MIN_BLOCK, (5, 4), GEO_COUNT, EXTRAS, HUGE_THRESHOLD);
const N: usize = GEO_COUNT + EXTRAS.len();
const TABLE: [usize; N] = build_table::<N>(PARAMS);
const L: usize = size2class_len(TABLE[N - 1], MIN_BLOCK);
static SC: SizeClasses<N, L> = SizeClasses::build(PARAMS);(Runnable form with concrete values in crates/size-classes/README.md.)
Structs§
- Invalid
Align - The error
SizeClasses::try_class_forreturns whenalignis not a power of two (thecore::alloc::LayoutcontractSizeClasses::class_forassumes but – on its own hot path – onlydebug_assert!s). Carries the offending value for diagnostics. - Params
- Parameters for a size-class scheme, consumed by
build_table,build_size2classandSizeClasses::build. - Size
Classes - A const-built size-class scheme: the sorted class table, its derived O(1)
size→classlookup, and the policy constants needed to classify a request.
Functions§
- build_
size2class - Build the O(1)
size→classlookup from a table at compile time — so the lookup and the table cannot drift. The caller indexes it assize2class[(size - 1) >> log2(min_block)], so bucketkcovers every size in(k * min_block, (k + 1) * min_block];size2class[k]is the smallest class whoseblock_size >= (k + 1) * min_block– EXCEPT the top bucketL - 1, whose idealneed(L * min_blockmathematically – NOT guaranteed to fitusizeeven for a valid scheme, e.g.min_block = 1 << 62, L = 4; the builder computes it as(k + 1).checked_mul(min_block)and folds that same overflow into the clamp below, never evaluating the unrepresentable product) exceedstable[N - 1](the largest class), so no such class exists; that bucket is clamped totable[N - 1]itself instead. ForSizeClasses::class_forspecifically this is harmless: it never queries bucketL - 1for any in-rangesize(its own early-rejection guard catches every size that would land there), so THERE the clamped entry is an unreachable sentinel, not an observable answer. A caller driving this array directly (bypassingclass_for) can still observe it – and for a hand-builttablewhosesmall_maxis not a multiple ofmin_block, bucketL - 1need not even be a sentinel: it can be the correct, reachable answer for sizes in((L - 1) * min_block, small_max]. - build_
table - Build the size-class table at compile time: a geometric progression merged
with
params.extrasin sorted order, returned as[usize; N]whereNmust equalparams.geo_count + params.extras.len(). - size2class_
len - The
size2classarray length for a scheme whose largest class ismax_class: oneu8permin_block-sized bucket from0up to and includingmax_class. A consumer uses this in aconstexpression to pin theLgeneric ofSizeClasses.