tigerbeetle-node 0.11.12 → 0.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +212 -196
- package/dist/bin/aarch64-linux-gnu/client.node +0 -0
- package/dist/bin/aarch64-linux-musl/client.node +0 -0
- package/dist/bin/aarch64-macos/client.node +0 -0
- package/dist/bin/x86_64-linux-gnu/client.node +0 -0
- package/dist/bin/x86_64-linux-musl/client.node +0 -0
- package/dist/bin/x86_64-macos/client.node +0 -0
- package/dist/index.js +33 -1
- package/dist/index.js.map +1 -1
- package/package-lock.json +66 -0
- package/package.json +8 -17
- package/src/index.ts +56 -1
- package/src/node.zig +10 -9
- package/dist/.client.node.sha256 +0 -1
- package/scripts/build_lib.sh +0 -61
- package/scripts/download_node_headers.sh +0 -32
- package/src/tigerbeetle/scripts/benchmark.bat +0 -48
- package/src/tigerbeetle/scripts/benchmark.sh +0 -66
- package/src/tigerbeetle/scripts/confirm_image.sh +0 -44
- package/src/tigerbeetle/scripts/fuzz_loop.sh +0 -15
- package/src/tigerbeetle/scripts/fuzz_unique_errors.sh +0 -7
- package/src/tigerbeetle/scripts/install.bat +0 -7
- package/src/tigerbeetle/scripts/install.sh +0 -21
- package/src/tigerbeetle/scripts/install_zig.bat +0 -113
- package/src/tigerbeetle/scripts/install_zig.sh +0 -90
- package/src/tigerbeetle/scripts/lint.zig +0 -199
- package/src/tigerbeetle/scripts/pre-commit.sh +0 -9
- package/src/tigerbeetle/scripts/scripts/benchmark.bat +0 -48
- package/src/tigerbeetle/scripts/scripts/benchmark.sh +0 -66
- package/src/tigerbeetle/scripts/scripts/confirm_image.sh +0 -44
- package/src/tigerbeetle/scripts/scripts/fuzz_loop.sh +0 -15
- package/src/tigerbeetle/scripts/scripts/fuzz_unique_errors.sh +0 -7
- package/src/tigerbeetle/scripts/scripts/install.bat +0 -7
- package/src/tigerbeetle/scripts/scripts/install.sh +0 -21
- package/src/tigerbeetle/scripts/scripts/install_zig.bat +0 -113
- package/src/tigerbeetle/scripts/scripts/install_zig.sh +0 -90
- package/src/tigerbeetle/scripts/scripts/lint.zig +0 -199
- package/src/tigerbeetle/scripts/scripts/pre-commit.sh +0 -9
- package/src/tigerbeetle/scripts/scripts/shellcheck.sh +0 -5
- package/src/tigerbeetle/scripts/scripts/tests_on_alpine.sh +0 -10
- package/src/tigerbeetle/scripts/scripts/tests_on_ubuntu.sh +0 -14
- package/src/tigerbeetle/scripts/scripts/upgrade_ubuntu_kernel.sh +0 -48
- package/src/tigerbeetle/scripts/scripts/validate_docs.sh +0 -23
- package/src/tigerbeetle/scripts/scripts/vr_state_enumerate +0 -46
- package/src/tigerbeetle/scripts/shellcheck.sh +0 -5
- package/src/tigerbeetle/scripts/tests_on_alpine.sh +0 -10
- package/src/tigerbeetle/scripts/tests_on_ubuntu.sh +0 -14
- package/src/tigerbeetle/scripts/upgrade_ubuntu_kernel.sh +0 -48
- package/src/tigerbeetle/scripts/validate_docs.sh +0 -23
- package/src/tigerbeetle/scripts/vr_state_enumerate +0 -46
- package/src/tigerbeetle/src/benchmark.zig +0 -314
- package/src/tigerbeetle/src/config.zig +0 -234
- package/src/tigerbeetle/src/constants.zig +0 -436
- package/src/tigerbeetle/src/ewah.zig +0 -286
- package/src/tigerbeetle/src/ewah_benchmark.zig +0 -120
- package/src/tigerbeetle/src/ewah_fuzz.zig +0 -130
- package/src/tigerbeetle/src/fifo.zig +0 -120
- package/src/tigerbeetle/src/io/benchmark.zig +0 -213
- package/src/tigerbeetle/src/io/darwin.zig +0 -814
- package/src/tigerbeetle/src/io/linux.zig +0 -1062
- package/src/tigerbeetle/src/io/test.zig +0 -643
- package/src/tigerbeetle/src/io/windows.zig +0 -1183
- package/src/tigerbeetle/src/io.zig +0 -34
- package/src/tigerbeetle/src/iops.zig +0 -107
- package/src/tigerbeetle/src/lsm/README.md +0 -308
- package/src/tigerbeetle/src/lsm/binary_search.zig +0 -341
- package/src/tigerbeetle/src/lsm/bloom_filter.zig +0 -125
- package/src/tigerbeetle/src/lsm/compaction.zig +0 -603
- package/src/tigerbeetle/src/lsm/composite_key.zig +0 -77
- package/src/tigerbeetle/src/lsm/direction.zig +0 -11
- package/src/tigerbeetle/src/lsm/eytzinger.zig +0 -587
- package/src/tigerbeetle/src/lsm/eytzinger_benchmark.zig +0 -330
- package/src/tigerbeetle/src/lsm/forest.zig +0 -204
- package/src/tigerbeetle/src/lsm/forest_fuzz.zig +0 -401
- package/src/tigerbeetle/src/lsm/grid.zig +0 -573
- package/src/tigerbeetle/src/lsm/groove.zig +0 -972
- package/src/tigerbeetle/src/lsm/k_way_merge.zig +0 -474
- package/src/tigerbeetle/src/lsm/level_iterator.zig +0 -332
- package/src/tigerbeetle/src/lsm/manifest.zig +0 -617
- package/src/tigerbeetle/src/lsm/manifest_level.zig +0 -877
- package/src/tigerbeetle/src/lsm/manifest_log.zig +0 -789
- package/src/tigerbeetle/src/lsm/manifest_log_fuzz.zig +0 -691
- package/src/tigerbeetle/src/lsm/merge_iterator.zig +0 -106
- package/src/tigerbeetle/src/lsm/node_pool.zig +0 -235
- package/src/tigerbeetle/src/lsm/posted_groove.zig +0 -378
- package/src/tigerbeetle/src/lsm/segmented_array.zig +0 -1328
- package/src/tigerbeetle/src/lsm/segmented_array_benchmark.zig +0 -148
- package/src/tigerbeetle/src/lsm/segmented_array_fuzz.zig +0 -9
- package/src/tigerbeetle/src/lsm/set_associative_cache.zig +0 -850
- package/src/tigerbeetle/src/lsm/table.zig +0 -1031
- package/src/tigerbeetle/src/lsm/table_immutable.zig +0 -203
- package/src/tigerbeetle/src/lsm/table_iterator.zig +0 -340
- package/src/tigerbeetle/src/lsm/table_mutable.zig +0 -220
- package/src/tigerbeetle/src/lsm/test.zig +0 -438
- package/src/tigerbeetle/src/lsm/tree.zig +0 -1193
- package/src/tigerbeetle/src/lsm/tree_fuzz.zig +0 -474
- package/src/tigerbeetle/src/message_bus.zig +0 -1012
- package/src/tigerbeetle/src/message_pool.zig +0 -156
- package/src/tigerbeetle/src/ring_buffer.zig +0 -399
- package/src/tigerbeetle/src/simulator.zig +0 -569
- package/src/tigerbeetle/src/state_machine/auditor.zig +0 -577
- package/src/tigerbeetle/src/state_machine/workload.zig +0 -883
- package/src/tigerbeetle/src/state_machine.zig +0 -1881
- package/src/tigerbeetle/src/static_allocator.zig +0 -65
- package/src/tigerbeetle/src/stdx.zig +0 -162
- package/src/tigerbeetle/src/storage.zig +0 -393
- package/src/tigerbeetle/src/testing/cluster/message_bus.zig +0 -82
- package/src/tigerbeetle/src/testing/cluster/network.zig +0 -237
- package/src/tigerbeetle/src/testing/cluster/state_checker.zig +0 -169
- package/src/tigerbeetle/src/testing/cluster/storage_checker.zig +0 -202
- package/src/tigerbeetle/src/testing/cluster.zig +0 -443
- package/src/tigerbeetle/src/testing/fuzz.zig +0 -140
- package/src/tigerbeetle/src/testing/hash_log.zig +0 -66
- package/src/tigerbeetle/src/testing/id.zig +0 -99
- package/src/tigerbeetle/src/testing/packet_simulator.zig +0 -364
- package/src/tigerbeetle/src/testing/priority_queue.zig +0 -645
- package/src/tigerbeetle/src/testing/reply_sequence.zig +0 -139
- package/src/tigerbeetle/src/testing/state_machine.zig +0 -249
- package/src/tigerbeetle/src/testing/storage.zig +0 -757
- package/src/tigerbeetle/src/testing/table.zig +0 -247
- package/src/tigerbeetle/src/testing/time.zig +0 -84
- package/src/tigerbeetle/src/tigerbeetle.zig +0 -227
- package/src/tigerbeetle/src/time.zig +0 -112
- package/src/tigerbeetle/src/tracer.zig +0 -529
- package/src/tigerbeetle/src/unit_tests.zig +0 -42
- package/src/tigerbeetle/src/vopr.zig +0 -495
- package/src/tigerbeetle/src/vsr/README.md +0 -209
- package/src/tigerbeetle/src/vsr/client.zig +0 -544
- package/src/tigerbeetle/src/vsr/clock.zig +0 -853
- package/src/tigerbeetle/src/vsr/journal.zig +0 -2413
- package/src/tigerbeetle/src/vsr/journal_format_fuzz.zig +0 -111
- package/src/tigerbeetle/src/vsr/marzullo.zig +0 -309
- package/src/tigerbeetle/src/vsr/replica.zig +0 -6381
- package/src/tigerbeetle/src/vsr/replica_format.zig +0 -219
- package/src/tigerbeetle/src/vsr/superblock.zig +0 -1631
- package/src/tigerbeetle/src/vsr/superblock_client_table.zig +0 -256
- package/src/tigerbeetle/src/vsr/superblock_free_set.zig +0 -929
- package/src/tigerbeetle/src/vsr/superblock_free_set_fuzz.zig +0 -334
- package/src/tigerbeetle/src/vsr/superblock_fuzz.zig +0 -390
- package/src/tigerbeetle/src/vsr/superblock_manifest.zig +0 -615
- package/src/tigerbeetle/src/vsr/superblock_quorums.zig +0 -394
- package/src/tigerbeetle/src/vsr/superblock_quorums_fuzz.zig +0 -314
- package/src/tigerbeetle/src/vsr.zig +0 -1352
|
@@ -1,929 +0,0 @@
|
|
|
1
|
-
const std = @import("std");
|
|
2
|
-
const assert = std.debug.assert;
|
|
3
|
-
const mem = std.mem;
|
|
4
|
-
|
|
5
|
-
const DynamicBitSetUnmanaged = std.bit_set.DynamicBitSetUnmanaged;
|
|
6
|
-
const MaskInt = DynamicBitSetUnmanaged.MaskInt;
|
|
7
|
-
|
|
8
|
-
const constants = @import("../constants.zig");
|
|
9
|
-
|
|
10
|
-
const ewah = @import("../ewah.zig").ewah(usize);
|
|
11
|
-
const div_ceil = @import("../stdx.zig").div_ceil;
|
|
12
|
-
|
|
13
|
-
/// This is logically a range of addresses within the FreeSet, but its actual fields are block
|
|
14
|
-
/// indexes for ease of calculation.
|
|
15
|
-
///
|
|
16
|
-
/// A reservation covers a range of both free and acquired blocks — when it is first created,
|
|
17
|
-
/// it is guaranteed to cover exactly as many free blocks as were requested by `reserve()`.
|
|
18
|
-
pub const Reservation = struct {
|
|
19
|
-
block_base: usize,
|
|
20
|
-
block_count: usize,
|
|
21
|
-
/// An identifer for each reservation cycle, to verify that old reservations are not reused.
|
|
22
|
-
session: usize,
|
|
23
|
-
};
|
|
24
|
-
|
|
25
|
-
/// The 0 address is reserved for usage as a sentinel and will never be returned by acquire().
|
|
26
|
-
///
|
|
27
|
-
/// Concurrent callers must reserve free blocks before acquiring them to ensure that
|
|
28
|
-
/// acquisition order is deterministic despite concurrent jobs acquiring blocks in
|
|
29
|
-
/// nondeterministic order.
|
|
30
|
-
///
|
|
31
|
-
/// The reservation lifecycle is:
|
|
32
|
-
///
|
|
33
|
-
/// 1. Reserve: In deterministic order, each job (e.g. compaction) calls `reserve()` to
|
|
34
|
-
/// reserve the upper bound of blocks that it may need to acquire to complete.
|
|
35
|
-
/// 2. Acquire: The jobs run concurrently. Each job acquires blocks only from its respective
|
|
36
|
-
/// reservation (via `acquire()`).
|
|
37
|
-
/// 3. Forfeit: When a job finishes, it calls `forfeit()` to drop its reservation.
|
|
38
|
-
/// 4. Done: When all pending reservations are forfeited, the reserved (but unacquired) space
|
|
39
|
-
/// is reclaimed.
|
|
40
|
-
///
|
|
41
|
-
pub const FreeSet = struct {
|
|
42
|
-
/// If a shard has any free blocks, the corresponding index bit is zero.
|
|
43
|
-
/// If a shard has no free blocks, the corresponding index bit is one.
|
|
44
|
-
index: DynamicBitSetUnmanaged,
|
|
45
|
-
|
|
46
|
-
/// Set bits indicate allocated blocks; unset bits indicate free blocks.
|
|
47
|
-
blocks: DynamicBitSetUnmanaged,
|
|
48
|
-
|
|
49
|
-
/// Set bits indicate blocks to be released at the next checkpoint.
|
|
50
|
-
staging: DynamicBitSetUnmanaged,
|
|
51
|
-
|
|
52
|
-
/// The number of blocks that are reserved, counting both acquired and free blocks
|
|
53
|
-
/// from the start of `blocks`.
|
|
54
|
-
/// Alternatively, the index of the first non-reserved block in `blocks`.
|
|
55
|
-
reservation_blocks: usize = 0,
|
|
56
|
-
|
|
57
|
-
/// The number of active reservations.
|
|
58
|
-
reservation_count: usize = 0,
|
|
59
|
-
|
|
60
|
-
/// Verify that when the caller transitions from creating reservations to forfeiting them,
|
|
61
|
-
/// all reservations must be forfeited before additional reservations are made.
|
|
62
|
-
reservation_state: enum {
|
|
63
|
-
reserving,
|
|
64
|
-
forfeiting,
|
|
65
|
-
} = .reserving,
|
|
66
|
-
|
|
67
|
-
/// Verifies that reservations are not allocated from or forefeited when they should not be.
|
|
68
|
-
reservation_session: usize = 1,
|
|
69
|
-
|
|
70
|
-
// Each shard is 8 cache lines because the CPU line fill buffer can fetch 10 lines in parallel.
|
|
71
|
-
// And 8 is fast for division when computing the shard of a block.
|
|
72
|
-
// Since the shard is scanned sequentially, the prefetching amortizes the cost of the single
|
|
73
|
-
// cache miss. It also reduces the size of the index.
|
|
74
|
-
//
|
|
75
|
-
// e.g. 10TiB disk ÷ 64KiB/block ÷ 512*8 blocks/shard ÷ 8 shards/byte = 5120B index
|
|
76
|
-
const shard_cache_lines = 8;
|
|
77
|
-
pub const shard_bits = shard_cache_lines * constants.cache_line_size * @bitSizeOf(u8);
|
|
78
|
-
comptime {
|
|
79
|
-
assert(shard_bits == 4096);
|
|
80
|
-
assert(@bitSizeOf(MaskInt) == 64);
|
|
81
|
-
// Ensure there are no wasted padding bits at the end of the index.
|
|
82
|
-
assert(shard_bits % @bitSizeOf(MaskInt) == 0);
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
pub fn init(allocator: mem.Allocator, blocks_count: usize) !FreeSet {
|
|
86
|
-
assert(blocks_count % shard_bits == 0);
|
|
87
|
-
assert(blocks_count % @bitSizeOf(usize) == 0);
|
|
88
|
-
|
|
89
|
-
// Every block bit is covered by exactly one index bit.
|
|
90
|
-
const shards_count = @divExact(blocks_count, shard_bits);
|
|
91
|
-
var index = try DynamicBitSetUnmanaged.initEmpty(allocator, shards_count);
|
|
92
|
-
errdefer index.deinit(allocator);
|
|
93
|
-
|
|
94
|
-
var blocks = try DynamicBitSetUnmanaged.initEmpty(allocator, blocks_count);
|
|
95
|
-
errdefer blocks.deinit(allocator);
|
|
96
|
-
|
|
97
|
-
var staging = try DynamicBitSetUnmanaged.initEmpty(allocator, blocks_count);
|
|
98
|
-
errdefer staging.deinit(allocator);
|
|
99
|
-
|
|
100
|
-
assert(index.count() == 0);
|
|
101
|
-
assert(blocks.count() == 0);
|
|
102
|
-
assert(staging.count() == 0);
|
|
103
|
-
|
|
104
|
-
return FreeSet{
|
|
105
|
-
.index = index,
|
|
106
|
-
.blocks = blocks,
|
|
107
|
-
.staging = staging,
|
|
108
|
-
};
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
pub fn deinit(set: *FreeSet, allocator: mem.Allocator) void {
|
|
112
|
-
set.index.deinit(allocator);
|
|
113
|
-
set.blocks.deinit(allocator);
|
|
114
|
-
set.staging.deinit(allocator);
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
/// Returns the number of active reservations.
|
|
118
|
-
pub fn count_reservations(set: FreeSet) usize {
|
|
119
|
-
return set.reservation_count;
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
/// Returns the number of free blocks.
|
|
123
|
-
pub fn count_free(set: FreeSet) usize {
|
|
124
|
-
return set.blocks.capacity() - set.blocks.count();
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
/// Returns the number of free blocks in the reservation.
|
|
128
|
-
pub fn count_free_reserved(set: FreeSet, reservation: Reservation) usize {
|
|
129
|
-
assert(set.reservation_count > 0);
|
|
130
|
-
assert(reservation.block_count > 0);
|
|
131
|
-
assert(reservation.block_base < set.reservation_blocks);
|
|
132
|
-
assert(reservation.block_base + reservation.block_count <= set.reservation_blocks);
|
|
133
|
-
|
|
134
|
-
var count: u64 = 0;
|
|
135
|
-
var i: u64 = 0;
|
|
136
|
-
while (i < reservation.block_count) : (i += 1) {
|
|
137
|
-
if (!set.blocks.isSet(reservation.block_base + i)) count += 1;
|
|
138
|
-
}
|
|
139
|
-
return count;
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
/// Returns the number of acquired blocks.
|
|
143
|
-
pub fn count_acquired(set: FreeSet) usize {
|
|
144
|
-
return set.blocks.count();
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
/// Returns the address of the highest acquired block.
|
|
148
|
-
pub fn highest_address_acquired(set: FreeSet) ?u64 {
|
|
149
|
-
var it = set.blocks.iterator(.{
|
|
150
|
-
.kind = .set,
|
|
151
|
-
.direction = .reverse,
|
|
152
|
-
});
|
|
153
|
-
|
|
154
|
-
if (it.next()) |block| {
|
|
155
|
-
const address = block + 1;
|
|
156
|
-
return address;
|
|
157
|
-
} else {
|
|
158
|
-
// All blocks are free.
|
|
159
|
-
assert(set.blocks.count() == 0);
|
|
160
|
-
return null;
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
/// Reserve `reserve_count` free blocks. The blocks are not acquired yet.
|
|
165
|
-
///
|
|
166
|
-
/// Invariants:
|
|
167
|
-
///
|
|
168
|
-
/// - If a reservation is returned, it covers exactly `reserve_count` free blocks, along with
|
|
169
|
-
/// any interleaved already-acquired blocks.
|
|
170
|
-
/// - Active reservations are exclusive (i.e. disjoint).
|
|
171
|
-
/// (A reservation is active until `forfeit()` is called.)
|
|
172
|
-
///
|
|
173
|
-
/// Returns null if there are not enough blocks free and vacant.
|
|
174
|
-
/// Returns a reservation which can be used with `acquire()`:
|
|
175
|
-
/// - The caller should consider the returned Reservation as opaque and immutable.
|
|
176
|
-
/// - Each `reserve()` call which returns a non-null Reservation must correspond to exactly one
|
|
177
|
-
/// `forfeit()` call.
|
|
178
|
-
pub fn reserve(set: *FreeSet, reserve_count: usize) ?Reservation {
|
|
179
|
-
assert(set.reservation_state == .reserving);
|
|
180
|
-
assert(reserve_count > 0);
|
|
181
|
-
|
|
182
|
-
var shard_start = find_bit(
|
|
183
|
-
set.index,
|
|
184
|
-
@divFloor(set.reservation_blocks, shard_bits),
|
|
185
|
-
set.index.bit_length,
|
|
186
|
-
.unset,
|
|
187
|
-
) orelse return null;
|
|
188
|
-
|
|
189
|
-
// The reservation may cover (and ignore) already-acquired blocks due to fragmentation.
|
|
190
|
-
var block = std.math.max(shard_start * shard_bits, set.reservation_blocks);
|
|
191
|
-
var reserved: usize = 0;
|
|
192
|
-
while (reserved < reserve_count) : (reserved += 1) {
|
|
193
|
-
block = 1 + (find_bit(
|
|
194
|
-
set.blocks,
|
|
195
|
-
block,
|
|
196
|
-
set.blocks.bit_length,
|
|
197
|
-
.unset,
|
|
198
|
-
) orelse return null);
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
const block_base = set.reservation_blocks;
|
|
202
|
-
const block_count = block - set.reservation_blocks;
|
|
203
|
-
set.reservation_blocks += block_count;
|
|
204
|
-
set.reservation_count += 1;
|
|
205
|
-
|
|
206
|
-
return Reservation{
|
|
207
|
-
.block_base = block_base,
|
|
208
|
-
.block_count = block_count,
|
|
209
|
-
.session = set.reservation_session,
|
|
210
|
-
};
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
/// After invoking `forfeit()`, the reservation must never be used again.
|
|
214
|
-
pub fn forfeit(set: *FreeSet, reservation: Reservation) void {
|
|
215
|
-
assert(set.reservation_session == reservation.session);
|
|
216
|
-
|
|
217
|
-
set.reservation_count -= 1;
|
|
218
|
-
if (set.reservation_count == 0) {
|
|
219
|
-
// All reservations have been dropped.
|
|
220
|
-
set.reservation_blocks = 0;
|
|
221
|
-
set.reservation_session +%= 1;
|
|
222
|
-
set.reservation_state = .reserving;
|
|
223
|
-
} else {
|
|
224
|
-
set.reservation_state = .forfeiting;
|
|
225
|
-
}
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
/// Marks a free block from the reservation as allocated, and returns the address.
|
|
229
|
-
/// The reservation must not have been forfeited yet.
|
|
230
|
-
/// The reservation must belong to the current cycle of reservations.
|
|
231
|
-
///
|
|
232
|
-
/// Invariants:
|
|
233
|
-
///
|
|
234
|
-
/// - An acquired block cannot be acquired again until it has been released and the release
|
|
235
|
-
/// has been checkpointed.
|
|
236
|
-
///
|
|
237
|
-
/// Returns null if no free block is available in the reservation.
|
|
238
|
-
pub fn acquire(set: *FreeSet, reservation: Reservation) ?u64 {
|
|
239
|
-
assert(set.reservation_count > 0);
|
|
240
|
-
assert(reservation.block_count > 0);
|
|
241
|
-
assert(reservation.block_base < set.reservation_blocks);
|
|
242
|
-
assert(reservation.block_base + reservation.block_count <= set.reservation_blocks);
|
|
243
|
-
assert(reservation.session == set.reservation_session);
|
|
244
|
-
|
|
245
|
-
const shard = find_bit(
|
|
246
|
-
set.index,
|
|
247
|
-
@divFloor(reservation.block_base, shard_bits),
|
|
248
|
-
div_ceil(reservation.block_base + reservation.block_count, shard_bits),
|
|
249
|
-
.unset,
|
|
250
|
-
) orelse return null;
|
|
251
|
-
assert(!set.index.isSet(shard));
|
|
252
|
-
|
|
253
|
-
const reservation_start = std.math.max(
|
|
254
|
-
shard * shard_bits,
|
|
255
|
-
reservation.block_base,
|
|
256
|
-
);
|
|
257
|
-
const reservation_end = reservation.block_base + reservation.block_count;
|
|
258
|
-
const block = find_bit(
|
|
259
|
-
set.blocks,
|
|
260
|
-
reservation_start,
|
|
261
|
-
reservation_end,
|
|
262
|
-
.unset,
|
|
263
|
-
) orelse return null;
|
|
264
|
-
assert(block >= reservation.block_base);
|
|
265
|
-
assert(block <= reservation.block_base + reservation.block_count);
|
|
266
|
-
assert(!set.blocks.isSet(block));
|
|
267
|
-
assert(!set.staging.isSet(block));
|
|
268
|
-
|
|
269
|
-
set.blocks.set(block);
|
|
270
|
-
// Update the index when every block in the shard is allocated.
|
|
271
|
-
if (set.find_free_block_in_shard(shard) == null) set.index.set(shard);
|
|
272
|
-
|
|
273
|
-
const address = block + 1;
|
|
274
|
-
return address;
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
fn find_free_block_in_shard(set: FreeSet, shard: usize) ?usize {
|
|
278
|
-
const shard_start = shard * shard_bits;
|
|
279
|
-
const shard_end = shard_start + shard_bits;
|
|
280
|
-
assert(shard_start < set.blocks.bit_length);
|
|
281
|
-
|
|
282
|
-
return find_bit(set.blocks, shard_start, shard_end, .unset);
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
pub fn is_free(set: FreeSet, address: u64) bool {
|
|
286
|
-
const block = address - 1;
|
|
287
|
-
return !set.blocks.isSet(block);
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
/// Leave the address allocated for now, but free it at the next checkpoint.
|
|
291
|
-
/// This ensures that it will not be overwritten during the current checkpoint — the block may
|
|
292
|
-
/// still be needed if we crash and recover from the current checkpoint.
|
|
293
|
-
/// (TODO) If the block was created since the last checkpoint then it's safe to free immediately.
|
|
294
|
-
/// This may reduce space amplification, especially for smaller datasets.
|
|
295
|
-
/// (Note: This must be careful not to release while any reservations are held
|
|
296
|
-
/// to avoid making the reservation's acquire()s nondeterministic).
|
|
297
|
-
pub fn release(set: *FreeSet, address: u64) void {
|
|
298
|
-
const block = address - 1;
|
|
299
|
-
assert(set.blocks.isSet(block));
|
|
300
|
-
assert(!set.staging.isSet(block));
|
|
301
|
-
|
|
302
|
-
set.staging.set(block);
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
/// Given the address, marks an allocated block as free.
|
|
306
|
-
fn release_now(set: *FreeSet, address: u64) void {
|
|
307
|
-
const block = address - 1;
|
|
308
|
-
assert(set.blocks.isSet(block));
|
|
309
|
-
assert(!set.staging.isSet(block));
|
|
310
|
-
assert(set.reservation_count == 0);
|
|
311
|
-
assert(set.reservation_blocks == 0);
|
|
312
|
-
|
|
313
|
-
set.index.unset(@divFloor(block, shard_bits));
|
|
314
|
-
set.blocks.unset(block);
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
/// Free all staged blocks.
|
|
318
|
-
/// Checkpoint must not be called while there are outstanding reservations.
|
|
319
|
-
pub fn checkpoint(set: *FreeSet) void {
|
|
320
|
-
assert(set.reservation_count == 0);
|
|
321
|
-
assert(set.reservation_blocks == 0);
|
|
322
|
-
|
|
323
|
-
var it = set.staging.iterator(.{ .kind = .set });
|
|
324
|
-
while (it.next()) |block| {
|
|
325
|
-
set.staging.unset(block);
|
|
326
|
-
const address = block + 1;
|
|
327
|
-
set.release_now(address);
|
|
328
|
-
}
|
|
329
|
-
assert(set.staging.count() == 0);
|
|
330
|
-
}
|
|
331
|
-
|
|
332
|
-
/// Temporarily marks staged blocks as free.
|
|
333
|
-
/// Amortizes the cost of toggling staged blocks when encoding and getting the highest address.
|
|
334
|
-
/// Does not update the index and MUST therefore be paired immediately with exclude_staging().
|
|
335
|
-
pub fn include_staging(set: *FreeSet) void {
|
|
336
|
-
const free = set.count_free();
|
|
337
|
-
|
|
338
|
-
set.blocks.toggleSet(set.staging);
|
|
339
|
-
|
|
340
|
-
// We expect the free count to increase now that staging has been included:
|
|
341
|
-
assert(set.count_free() == free + set.staging.count());
|
|
342
|
-
}
|
|
343
|
-
|
|
344
|
-
pub fn exclude_staging(set: *FreeSet) void {
|
|
345
|
-
const free = set.count_free();
|
|
346
|
-
|
|
347
|
-
set.blocks.toggleSet(set.staging);
|
|
348
|
-
|
|
349
|
-
// We expect the free count to decrease now that staging has been excluded:
|
|
350
|
-
assert(set.count_free() == free - set.staging.count());
|
|
351
|
-
}
|
|
352
|
-
|
|
353
|
-
/// Decodes the compressed bitset in `source` into `set`.
|
|
354
|
-
/// Panics if the `source` encoding is invalid.
|
|
355
|
-
pub fn decode(set: *FreeSet, source: []align(@alignOf(usize)) const u8) void {
|
|
356
|
-
// Verify that this FreeSet is entirely unallocated.
|
|
357
|
-
assert(set.index.count() == 0);
|
|
358
|
-
assert(set.blocks.count() == 0);
|
|
359
|
-
assert(set.staging.count() == 0);
|
|
360
|
-
assert(set.reservation_count == 0);
|
|
361
|
-
assert(set.reservation_blocks == 0);
|
|
362
|
-
|
|
363
|
-
const words_decoded = ewah.decode(source, bit_set_masks(set.blocks));
|
|
364
|
-
assert(words_decoded * @bitSizeOf(MaskInt) <= set.blocks.bit_length);
|
|
365
|
-
|
|
366
|
-
var shard: usize = 0;
|
|
367
|
-
while (shard < set.index.bit_length) : (shard += 1) {
|
|
368
|
-
if (set.find_free_block_in_shard(shard) == null) set.index.set(shard);
|
|
369
|
-
}
|
|
370
|
-
}
|
|
371
|
-
|
|
372
|
-
/// Returns the maximum number of bytes that `blocks_count` blocks need to be encoded.
|
|
373
|
-
pub fn encode_size_max(blocks_count: usize) usize {
|
|
374
|
-
assert(blocks_count % shard_bits == 0);
|
|
375
|
-
assert(blocks_count % @bitSizeOf(usize) == 0);
|
|
376
|
-
|
|
377
|
-
return ewah.encode_size_max(@divExact(blocks_count, @bitSizeOf(usize)));
|
|
378
|
-
}
|
|
379
|
-
|
|
380
|
-
/// Returns the number of bytes written to `target`.
|
|
381
|
-
/// The encoded data does *not* include staged changes.
|
|
382
|
-
pub fn encode(set: FreeSet, target: []align(@alignOf(usize)) u8) usize {
|
|
383
|
-
assert(target.len == FreeSet.encode_size_max(set.blocks.bit_length));
|
|
384
|
-
assert(set.reservation_count == 0);
|
|
385
|
-
assert(set.reservation_blocks == 0);
|
|
386
|
-
|
|
387
|
-
return ewah.encode(bit_set_masks(set.blocks), target);
|
|
388
|
-
}
|
|
389
|
-
|
|
390
|
-
/// Returns `blocks_count` rounded down to the nearest multiple of shard and word bit count.
|
|
391
|
-
/// Ensures that the result is acceptable to `FreeSet.init()`.
|
|
392
|
-
pub fn blocks_count_floor(blocks_count: usize) usize {
|
|
393
|
-
assert(blocks_count > 0);
|
|
394
|
-
assert(blocks_count >= shard_bits);
|
|
395
|
-
|
|
396
|
-
const floor = @divFloor(blocks_count, shard_bits) * shard_bits;
|
|
397
|
-
|
|
398
|
-
// We assume that shard_bits is itself a multiple of word bit count.
|
|
399
|
-
assert(floor % @bitSizeOf(usize) == 0);
|
|
400
|
-
|
|
401
|
-
return floor;
|
|
402
|
-
}
|
|
403
|
-
};
|
|
404
|
-
|
|
405
|
-
fn bit_set_masks(bit_set: DynamicBitSetUnmanaged) []MaskInt {
|
|
406
|
-
const len = div_ceil(bit_set.bit_length, @bitSizeOf(MaskInt));
|
|
407
|
-
return bit_set.masks[0..len];
|
|
408
|
-
}
|
|
409
|
-
|
|
410
|
-
test "FreeSet block shard count" {
|
|
411
|
-
if (constants.block_size != 64 * 1024) return;
|
|
412
|
-
const blocks_in_tb = @divExact(1 << 40, constants.block_size);
|
|
413
|
-
try test_block_shards_count(5120 * 8, 10 * blocks_in_tb);
|
|
414
|
-
try test_block_shards_count(5120 * 8 - 1, 10 * blocks_in_tb - FreeSet.shard_bits);
|
|
415
|
-
try test_block_shards_count(1, FreeSet.shard_bits); // Must be at least one index bit.
|
|
416
|
-
}
|
|
417
|
-
|
|
418
|
-
fn test_block_shards_count(expect_shards_count: usize, blocks_count: usize) !void {
|
|
419
|
-
var set = try FreeSet.init(std.testing.allocator, blocks_count);
|
|
420
|
-
defer set.deinit(std.testing.allocator);
|
|
421
|
-
|
|
422
|
-
try std.testing.expectEqual(expect_shards_count, set.index.bit_length);
|
|
423
|
-
}
|
|
424
|
-
|
|
425
|
-
test "FreeSet highest_address_acquired" {
|
|
426
|
-
const expectEqual = std.testing.expectEqual;
|
|
427
|
-
const blocks_count = FreeSet.shard_bits;
|
|
428
|
-
var set = try FreeSet.init(std.testing.allocator, blocks_count);
|
|
429
|
-
defer set.deinit(std.testing.allocator);
|
|
430
|
-
|
|
431
|
-
{
|
|
432
|
-
const reservation = set.reserve(6).?;
|
|
433
|
-
defer set.forfeit(reservation);
|
|
434
|
-
|
|
435
|
-
try expectEqual(@as(?u64, null), set.highest_address_acquired());
|
|
436
|
-
try expectEqual(@as(?u64, 1), set.acquire(reservation));
|
|
437
|
-
try expectEqual(@as(?u64, 2), set.acquire(reservation));
|
|
438
|
-
try expectEqual(@as(?u64, 3), set.acquire(reservation));
|
|
439
|
-
}
|
|
440
|
-
|
|
441
|
-
try expectEqual(@as(?u64, 3), set.highest_address_acquired());
|
|
442
|
-
set.release_now(2);
|
|
443
|
-
try expectEqual(@as(?u64, 3), set.highest_address_acquired());
|
|
444
|
-
set.release_now(3);
|
|
445
|
-
try expectEqual(@as(?u64, 1), set.highest_address_acquired());
|
|
446
|
-
set.release_now(1);
|
|
447
|
-
try expectEqual(@as(?u64, null), set.highest_address_acquired());
|
|
448
|
-
|
|
449
|
-
{
|
|
450
|
-
const reservation = set.reserve(6).?;
|
|
451
|
-
defer set.forfeit(reservation);
|
|
452
|
-
|
|
453
|
-
try expectEqual(@as(?u64, 1), set.acquire(reservation));
|
|
454
|
-
try expectEqual(@as(?u64, 2), set.acquire(reservation));
|
|
455
|
-
try expectEqual(@as(?u64, 3), set.acquire(reservation));
|
|
456
|
-
}
|
|
457
|
-
|
|
458
|
-
{
|
|
459
|
-
set.release(3);
|
|
460
|
-
try expectEqual(@as(?u64, 3), set.highest_address_acquired());
|
|
461
|
-
|
|
462
|
-
set.include_staging();
|
|
463
|
-
try expectEqual(@as(?u64, 2), set.highest_address_acquired());
|
|
464
|
-
set.exclude_staging();
|
|
465
|
-
|
|
466
|
-
try expectEqual(@as(?u64, 3), set.highest_address_acquired());
|
|
467
|
-
set.checkpoint();
|
|
468
|
-
try expectEqual(@as(?u64, 2), set.highest_address_acquired());
|
|
469
|
-
}
|
|
470
|
-
}
|
|
471
|
-
|
|
472
|
-
test "FreeSet acquire/release" {
|
|
473
|
-
try test_acquire_release(FreeSet.shard_bits);
|
|
474
|
-
try test_acquire_release(2 * FreeSet.shard_bits);
|
|
475
|
-
try test_acquire_release(63 * FreeSet.shard_bits);
|
|
476
|
-
try test_acquire_release(64 * FreeSet.shard_bits);
|
|
477
|
-
try test_acquire_release(65 * FreeSet.shard_bits);
|
|
478
|
-
}
|
|
479
|
-
|
|
480
|
-
fn test_acquire_release(blocks_count: usize) !void {
|
|
481
|
-
const expectEqual = std.testing.expectEqual;
|
|
482
|
-
// Acquire everything, then release, then acquire again.
|
|
483
|
-
var set = try FreeSet.init(std.testing.allocator, blocks_count);
|
|
484
|
-
defer set.deinit(std.testing.allocator);
|
|
485
|
-
|
|
486
|
-
var empty = try FreeSet.init(std.testing.allocator, blocks_count);
|
|
487
|
-
defer empty.deinit(std.testing.allocator);
|
|
488
|
-
|
|
489
|
-
{
|
|
490
|
-
const reservation = set.reserve(blocks_count).?;
|
|
491
|
-
defer set.forfeit(reservation);
|
|
492
|
-
|
|
493
|
-
var i: usize = 0;
|
|
494
|
-
while (i < blocks_count) : (i += 1) {
|
|
495
|
-
try expectEqual(@as(?u64, i + 1), set.acquire(reservation));
|
|
496
|
-
}
|
|
497
|
-
try expectEqual(@as(?u64, null), set.acquire(reservation));
|
|
498
|
-
}
|
|
499
|
-
|
|
500
|
-
try expectEqual(@as(u64, set.blocks.bit_length), set.count_acquired());
|
|
501
|
-
try expectEqual(@as(u64, 0), set.count_free());
|
|
502
|
-
|
|
503
|
-
{
|
|
504
|
-
var i: usize = 0;
|
|
505
|
-
while (i < blocks_count) : (i += 1) set.release_now(@as(u64, i + 1));
|
|
506
|
-
try expect_free_set_equal(empty, set);
|
|
507
|
-
}
|
|
508
|
-
|
|
509
|
-
try expectEqual(@as(u64, 0), set.count_acquired());
|
|
510
|
-
try expectEqual(@as(u64, set.blocks.bit_length), set.count_free());
|
|
511
|
-
|
|
512
|
-
{
|
|
513
|
-
const reservation = set.reserve(blocks_count).?;
|
|
514
|
-
defer set.forfeit(reservation);
|
|
515
|
-
|
|
516
|
-
var i: usize = 0;
|
|
517
|
-
while (i < blocks_count) : (i += 1) {
|
|
518
|
-
try expectEqual(@as(?u64, i + 1), set.acquire(reservation));
|
|
519
|
-
}
|
|
520
|
-
try expectEqual(@as(?u64, null), set.acquire(reservation));
|
|
521
|
-
}
|
|
522
|
-
}
|
|
523
|
-
|
|
524
|
-
test "FreeSet.reserve/acquire" {
|
|
525
|
-
const blocks_count_total = 4096;
|
|
526
|
-
var set = try FreeSet.init(std.testing.allocator, blocks_count_total);
|
|
527
|
-
defer set.deinit(std.testing.allocator);
|
|
528
|
-
|
|
529
|
-
// At most `blocks_count_total` blocks are initially available for reservation.
|
|
530
|
-
try std.testing.expectEqual(set.reserve(blocks_count_total + 1), null);
|
|
531
|
-
const r1 = set.reserve(blocks_count_total - 1);
|
|
532
|
-
const r2 = set.reserve(1);
|
|
533
|
-
try std.testing.expectEqual(set.reserve(1), null);
|
|
534
|
-
set.forfeit(r1.?);
|
|
535
|
-
set.forfeit(r2.?);
|
|
536
|
-
|
|
537
|
-
var address: usize = 1; // Start at 1 because addresses are >0.
|
|
538
|
-
{
|
|
539
|
-
const reservation = set.reserve(2).?;
|
|
540
|
-
defer set.forfeit(reservation);
|
|
541
|
-
|
|
542
|
-
try std.testing.expectEqual(set.count_free_reserved(reservation), 2);
|
|
543
|
-
try std.testing.expectEqual(set.acquire(reservation), address + 0);
|
|
544
|
-
try std.testing.expectEqual(set.count_free_reserved(reservation), 1);
|
|
545
|
-
try std.testing.expectEqual(set.acquire(reservation), address + 1);
|
|
546
|
-
try std.testing.expectEqual(set.count_free_reserved(reservation), 0);
|
|
547
|
-
try std.testing.expectEqual(set.acquire(reservation), null);
|
|
548
|
-
}
|
|
549
|
-
address += 2;
|
|
550
|
-
|
|
551
|
-
{
|
|
552
|
-
// Blocks are acquired from the target reservation.
|
|
553
|
-
const reservation_1 = set.reserve(2).?;
|
|
554
|
-
const reservation_2 = set.reserve(2).?;
|
|
555
|
-
defer set.forfeit(reservation_1);
|
|
556
|
-
defer set.forfeit(reservation_2);
|
|
557
|
-
|
|
558
|
-
try std.testing.expectEqual(set.acquire(reservation_1), address + 0);
|
|
559
|
-
try std.testing.expectEqual(set.acquire(reservation_2), address + 2);
|
|
560
|
-
try std.testing.expectEqual(set.acquire(reservation_1), address + 1);
|
|
561
|
-
try std.testing.expectEqual(set.acquire(reservation_1), null);
|
|
562
|
-
try std.testing.expectEqual(set.acquire(reservation_2), address + 3);
|
|
563
|
-
try std.testing.expectEqual(set.acquire(reservation_2), null);
|
|
564
|
-
}
|
|
565
|
-
address += 4;
|
|
566
|
-
}
|
|
567
|
-
|
|
568
|
-
test "FreeSet checkpoint" {
|
|
569
|
-
const expectEqual = std.testing.expectEqual;
|
|
570
|
-
const blocks_count = FreeSet.shard_bits;
|
|
571
|
-
var set = try FreeSet.init(std.testing.allocator, blocks_count);
|
|
572
|
-
defer set.deinit(std.testing.allocator);
|
|
573
|
-
|
|
574
|
-
var empty = try FreeSet.init(std.testing.allocator, blocks_count);
|
|
575
|
-
defer empty.deinit(std.testing.allocator);
|
|
576
|
-
|
|
577
|
-
var full = try FreeSet.init(std.testing.allocator, blocks_count);
|
|
578
|
-
defer full.deinit(std.testing.allocator);
|
|
579
|
-
|
|
580
|
-
{
|
|
581
|
-
// Acquire all of `full`'s blocks.
|
|
582
|
-
const reservation = full.reserve(blocks_count).?;
|
|
583
|
-
defer full.forfeit(reservation);
|
|
584
|
-
|
|
585
|
-
var i: usize = 0;
|
|
586
|
-
while (i < full.blocks.bit_length) : (i += 1) {
|
|
587
|
-
try expectEqual(@as(?u64, i + 1), full.acquire(reservation));
|
|
588
|
-
}
|
|
589
|
-
}
|
|
590
|
-
|
|
591
|
-
{
|
|
592
|
-
// Acquire & stage-release every block.
|
|
593
|
-
const reservation = set.reserve(blocks_count).?;
|
|
594
|
-
defer set.forfeit(reservation);
|
|
595
|
-
|
|
596
|
-
var i: usize = 0;
|
|
597
|
-
while (i < set.blocks.bit_length) : (i += 1) {
|
|
598
|
-
try expectEqual(@as(?u64, i + 1), set.acquire(reservation));
|
|
599
|
-
set.release(i + 1);
|
|
600
|
-
|
|
601
|
-
// These count functions treat staged blocks as allocated.
|
|
602
|
-
try expectEqual(@as(u64, i + 1), set.count_acquired());
|
|
603
|
-
try expectEqual(@as(u64, set.blocks.bit_length - i - 1), set.count_free());
|
|
604
|
-
}
|
|
605
|
-
// All blocks are still allocated, though staged to release at the next checkpoint.
|
|
606
|
-
try expectEqual(@as(?u64, null), set.acquire(reservation));
|
|
607
|
-
}
|
|
608
|
-
|
|
609
|
-
// Free all the blocks.
|
|
610
|
-
set.checkpoint();
|
|
611
|
-
try expect_free_set_equal(empty, set);
|
|
612
|
-
try expectEqual(@as(usize, 0), set.staging.count());
|
|
613
|
-
|
|
614
|
-
// Redundant checkpointing is a noop (but safe).
|
|
615
|
-
set.checkpoint();
|
|
616
|
-
|
|
617
|
-
{
|
|
618
|
-
// Allocate & stage-release all blocks again.
|
|
619
|
-
const reservation = set.reserve(blocks_count).?;
|
|
620
|
-
defer set.forfeit(reservation);
|
|
621
|
-
var i: usize = 0;
|
|
622
|
-
while (i < set.blocks.bit_length) : (i += 1) {
|
|
623
|
-
try expectEqual(@as(?u64, i + 1), set.acquire(reservation));
|
|
624
|
-
set.release(i + 1);
|
|
625
|
-
}
|
|
626
|
-
}
|
|
627
|
-
|
|
628
|
-
var set_encoded = try std.testing.allocator.alignedAlloc(
|
|
629
|
-
u8,
|
|
630
|
-
@alignOf(usize),
|
|
631
|
-
FreeSet.encode_size_max(set.blocks.bit_length),
|
|
632
|
-
);
|
|
633
|
-
defer std.testing.allocator.free(set_encoded);
|
|
634
|
-
|
|
635
|
-
{
|
|
636
|
-
// `encode` encodes staged blocks as free.
|
|
637
|
-
set.include_staging();
|
|
638
|
-
defer set.exclude_staging();
|
|
639
|
-
|
|
640
|
-
const set_encoded_length = set.encode(set_encoded);
|
|
641
|
-
var set_decoded = try FreeSet.init(std.testing.allocator, blocks_count);
|
|
642
|
-
defer set_decoded.deinit(std.testing.allocator);
|
|
643
|
-
|
|
644
|
-
set_decoded.decode(set_encoded[0..set_encoded_length]);
|
|
645
|
-
try expect_free_set_equal(empty, set_decoded);
|
|
646
|
-
}
|
|
647
|
-
|
|
648
|
-
{
|
|
649
|
-
// `encode` encodes staged blocks as still allocated.
|
|
650
|
-
const set_encoded_length = set.encode(set_encoded);
|
|
651
|
-
var set_decoded = try FreeSet.init(std.testing.allocator, blocks_count);
|
|
652
|
-
defer set_decoded.deinit(std.testing.allocator);
|
|
653
|
-
|
|
654
|
-
set_decoded.decode(set_encoded[0..set_encoded_length]);
|
|
655
|
-
try expect_free_set_equal(full, set_decoded);
|
|
656
|
-
}
|
|
657
|
-
}
|
|
658
|
-
|
|
659
|
-
test "FreeSet encode, decode, encode" {
|
|
660
|
-
const shard_bits = FreeSet.shard_bits / @bitSizeOf(usize);
|
|
661
|
-
// Uniform.
|
|
662
|
-
try test_encode(&.{.{ .fill = .uniform_ones, .words = shard_bits }});
|
|
663
|
-
try test_encode(&.{.{ .fill = .uniform_zeros, .words = shard_bits }});
|
|
664
|
-
try test_encode(&.{.{ .fill = .literal, .words = shard_bits }});
|
|
665
|
-
try test_encode(&.{.{ .fill = .uniform_ones, .words = std.math.maxInt(u16) + 1 }});
|
|
666
|
-
|
|
667
|
-
// Mixed.
|
|
668
|
-
try test_encode(&.{
|
|
669
|
-
.{ .fill = .uniform_ones, .words = shard_bits / 4 },
|
|
670
|
-
.{ .fill = .uniform_zeros, .words = shard_bits / 4 },
|
|
671
|
-
.{ .fill = .literal, .words = shard_bits / 4 },
|
|
672
|
-
.{ .fill = .uniform_ones, .words = shard_bits / 4 },
|
|
673
|
-
});
|
|
674
|
-
|
|
675
|
-
// Random.
|
|
676
|
-
var seed: u64 = undefined;
|
|
677
|
-
try std.os.getrandom(mem.asBytes(&seed));
|
|
678
|
-
|
|
679
|
-
var prng = std.rand.DefaultPrng.init(seed);
|
|
680
|
-
const random = prng.random();
|
|
681
|
-
|
|
682
|
-
const fills = [_]TestPatternFill{ .uniform_ones, .uniform_zeros, .literal };
|
|
683
|
-
var t: usize = 0;
|
|
684
|
-
while (t < 10) : (t += 1) {
|
|
685
|
-
var patterns = std.ArrayList(TestPattern).init(std.testing.allocator);
|
|
686
|
-
defer patterns.deinit();
|
|
687
|
-
|
|
688
|
-
var i: usize = 0;
|
|
689
|
-
while (i < shard_bits) : (i += 1) {
|
|
690
|
-
try patterns.append(.{
|
|
691
|
-
.fill = fills[random.uintLessThan(usize, fills.len)],
|
|
692
|
-
.words = 1,
|
|
693
|
-
});
|
|
694
|
-
}
|
|
695
|
-
try test_encode(patterns.items);
|
|
696
|
-
}
|
|
697
|
-
}
|
|
698
|
-
|
|
699
|
-
const TestPattern = struct {
|
|
700
|
-
fill: TestPatternFill,
|
|
701
|
-
words: usize,
|
|
702
|
-
};
|
|
703
|
-
|
|
704
|
-
const TestPatternFill = enum { uniform_ones, uniform_zeros, literal };
|
|
705
|
-
|
|
706
|
-
fn test_encode(patterns: []const TestPattern) !void {
|
|
707
|
-
var seed: u64 = undefined;
|
|
708
|
-
try std.os.getrandom(mem.asBytes(&seed));
|
|
709
|
-
|
|
710
|
-
var prng = std.rand.DefaultPrng.init(seed);
|
|
711
|
-
const random = prng.random();
|
|
712
|
-
|
|
713
|
-
var blocks_count: usize = 0;
|
|
714
|
-
for (patterns) |pattern| blocks_count += pattern.words * @bitSizeOf(usize);
|
|
715
|
-
|
|
716
|
-
var decoded_expect = try FreeSet.init(std.testing.allocator, blocks_count);
|
|
717
|
-
defer decoded_expect.deinit(std.testing.allocator);
|
|
718
|
-
|
|
719
|
-
{
|
|
720
|
-
// The `index` will start out one-filled. Every pattern containing a zero will update the
|
|
721
|
-
// corresponding index bit with a zero (probably multiple times) to ensure it ends up synced
|
|
722
|
-
// with `blocks`.
|
|
723
|
-
decoded_expect.index.toggleAll();
|
|
724
|
-
assert(decoded_expect.index.count() == decoded_expect.index.capacity());
|
|
725
|
-
|
|
726
|
-
// Fill the bitset according to the patterns.
|
|
727
|
-
var blocks = bit_set_masks(decoded_expect.blocks);
|
|
728
|
-
var blocks_offset: usize = 0;
|
|
729
|
-
for (patterns) |pattern| {
|
|
730
|
-
var i: usize = 0;
|
|
731
|
-
while (i < pattern.words) : (i += 1) {
|
|
732
|
-
blocks[blocks_offset] = switch (pattern.fill) {
|
|
733
|
-
.uniform_ones => ~@as(usize, 0),
|
|
734
|
-
.uniform_zeros => 0,
|
|
735
|
-
.literal => random.intRangeLessThan(usize, 1, std.math.maxInt(usize)),
|
|
736
|
-
};
|
|
737
|
-
const index_bit = blocks_offset * @bitSizeOf(usize) / FreeSet.shard_bits;
|
|
738
|
-
if (pattern.fill != .uniform_ones) decoded_expect.index.unset(index_bit);
|
|
739
|
-
blocks_offset += 1;
|
|
740
|
-
}
|
|
741
|
-
}
|
|
742
|
-
assert(blocks_offset == blocks.len);
|
|
743
|
-
}
|
|
744
|
-
|
|
745
|
-
var encoded = try std.testing.allocator.alignedAlloc(
|
|
746
|
-
u8,
|
|
747
|
-
@alignOf(usize),
|
|
748
|
-
FreeSet.encode_size_max(decoded_expect.blocks.bit_length),
|
|
749
|
-
);
|
|
750
|
-
defer std.testing.allocator.free(encoded);
|
|
751
|
-
|
|
752
|
-
try std.testing.expectEqual(encoded.len % 8, 0);
|
|
753
|
-
const encoded_length = decoded_expect.encode(encoded);
|
|
754
|
-
var decoded_actual = try FreeSet.init(std.testing.allocator, blocks_count);
|
|
755
|
-
defer decoded_actual.deinit(std.testing.allocator);
|
|
756
|
-
|
|
757
|
-
decoded_actual.decode(encoded[0..encoded_length]);
|
|
758
|
-
try expect_free_set_equal(decoded_expect, decoded_actual);
|
|
759
|
-
}
|
|
760
|
-
|
|
761
|
-
fn expect_free_set_equal(a: FreeSet, b: FreeSet) !void {
|
|
762
|
-
try expect_bit_set_equal(a.blocks, b.blocks);
|
|
763
|
-
try expect_bit_set_equal(a.index, b.index);
|
|
764
|
-
try expect_bit_set_equal(a.staging, b.staging);
|
|
765
|
-
}
|
|
766
|
-
|
|
767
|
-
fn expect_bit_set_equal(a: DynamicBitSetUnmanaged, b: DynamicBitSetUnmanaged) !void {
|
|
768
|
-
try std.testing.expectEqual(a.bit_length, b.bit_length);
|
|
769
|
-
const a_masks = bit_set_masks(a);
|
|
770
|
-
const b_masks = bit_set_masks(b);
|
|
771
|
-
for (a_masks) |aw, i| try std.testing.expectEqual(aw, b_masks[i]);
|
|
772
|
-
}
|
|
773
|
-
|
|
774
|
-
test "FreeSet decode small bitset into large bitset" {
|
|
775
|
-
const shard_bits = FreeSet.shard_bits;
|
|
776
|
-
var small_set = try FreeSet.init(std.testing.allocator, shard_bits);
|
|
777
|
-
defer small_set.deinit(std.testing.allocator);
|
|
778
|
-
|
|
779
|
-
{
|
|
780
|
-
// Set up a small bitset (with blocks_count==shard_bits) with no free blocks.
|
|
781
|
-
const reservation = small_set.reserve(small_set.blocks.bit_length).?;
|
|
782
|
-
defer small_set.forfeit(reservation);
|
|
783
|
-
|
|
784
|
-
var i: usize = 0;
|
|
785
|
-
while (i < small_set.blocks.bit_length) : (i += 1) _ = small_set.acquire(reservation);
|
|
786
|
-
}
|
|
787
|
-
|
|
788
|
-
var small_buffer = try std.testing.allocator.alignedAlloc(
|
|
789
|
-
u8,
|
|
790
|
-
@alignOf(usize),
|
|
791
|
-
FreeSet.encode_size_max(small_set.blocks.bit_length),
|
|
792
|
-
);
|
|
793
|
-
defer std.testing.allocator.free(small_buffer);
|
|
794
|
-
|
|
795
|
-
const small_buffer_written = small_set.encode(small_buffer);
|
|
796
|
-
// Decode the serialized small bitset into a larger bitset (with blocks_count==2*shard_bits).
|
|
797
|
-
var big_set = try FreeSet.init(std.testing.allocator, 2 * shard_bits);
|
|
798
|
-
defer big_set.deinit(std.testing.allocator);
|
|
799
|
-
|
|
800
|
-
big_set.decode(small_buffer[0..small_buffer_written]);
|
|
801
|
-
|
|
802
|
-
var block: usize = 0;
|
|
803
|
-
while (block < 2 * shard_bits) : (block += 1) {
|
|
804
|
-
const address = block + 1;
|
|
805
|
-
try std.testing.expectEqual(shard_bits <= block, big_set.is_free(address));
|
|
806
|
-
}
|
|
807
|
-
}
|
|
808
|
-
|
|
809
|
-
test "FreeSet encode/decode manual" {
|
|
810
|
-
const encoded_expect = mem.sliceAsBytes(&[_]usize{
|
|
811
|
-
// Mask 1: run of 2 words of 0s, then 3 literals
|
|
812
|
-
0 | (2 << 1) | (3 << 32),
|
|
813
|
-
0b10101010_10101010_10101010_10101010_10101010_10101010_10101010_10101010, // literal 1
|
|
814
|
-
0b01010101_01010101_01010101_01010101_01010101_01010101_01010101_01010101, // literal 2
|
|
815
|
-
0b10101010_10101010_10101010_10101010_10101010_10101010_10101010_10101010, // literal 3
|
|
816
|
-
// Mask 2: run of 59 words of 1s, then 0 literals
|
|
817
|
-
//
|
|
818
|
-
// 59 is chosen so that because the blocks_count must be a multiple of the shard size:
|
|
819
|
-
// shard_bits = 4096 bits = 64 words × 64 bits/word = (2+3+59)*64
|
|
820
|
-
1 | ((64 - 5) << 1),
|
|
821
|
-
});
|
|
822
|
-
const decoded_expect = [_]usize{
|
|
823
|
-
0b00000000_00000000_00000000_00000000_00000000_00000000_00000000_00000000, // run 1
|
|
824
|
-
0b00000000_00000000_00000000_00000000_00000000_00000000_00000000_00000000,
|
|
825
|
-
0b10101010_10101010_10101010_10101010_10101010_10101010_10101010_10101010, // literal 1
|
|
826
|
-
0b01010101_01010101_01010101_01010101_01010101_01010101_01010101_01010101, // literal 2
|
|
827
|
-
0b10101010_10101010_10101010_10101010_10101010_10101010_10101010_10101010, // literal 3
|
|
828
|
-
} ++ ([1]usize{~@as(usize, 0)} ** (64 - 5));
|
|
829
|
-
const blocks_count = decoded_expect.len * @bitSizeOf(usize);
|
|
830
|
-
|
|
831
|
-
// Test decode.
|
|
832
|
-
var decoded_actual = try FreeSet.init(std.testing.allocator, blocks_count);
|
|
833
|
-
defer decoded_actual.deinit(std.testing.allocator);
|
|
834
|
-
|
|
835
|
-
decoded_actual.decode(encoded_expect);
|
|
836
|
-
try std.testing.expectEqual(decoded_expect.len, bit_set_masks(decoded_actual.blocks).len);
|
|
837
|
-
try std.testing.expectEqualSlices(usize, &decoded_expect, bit_set_masks(decoded_actual.blocks));
|
|
838
|
-
|
|
839
|
-
// Test encode.
|
|
840
|
-
var encoded_actual = try std.testing.allocator.alignedAlloc(
|
|
841
|
-
u8,
|
|
842
|
-
@alignOf(usize),
|
|
843
|
-
FreeSet.encode_size_max(decoded_actual.blocks.bit_length),
|
|
844
|
-
);
|
|
845
|
-
defer std.testing.allocator.free(encoded_actual);
|
|
846
|
-
|
|
847
|
-
const encoded_actual_length = decoded_actual.encode(encoded_actual);
|
|
848
|
-
try std.testing.expectEqual(encoded_expect.len, encoded_actual_length);
|
|
849
|
-
}
|
|
850
|
-
|
|
851
|
-
/// Returns the index of the first set/unset bit (relative to the start of the bitset) within
|
|
852
|
-
/// the range bit_min…bit_max (inclusive…exclusive).
|
|
853
|
-
fn find_bit(
|
|
854
|
-
bit_set: DynamicBitSetUnmanaged,
|
|
855
|
-
bit_min: usize,
|
|
856
|
-
bit_max: usize,
|
|
857
|
-
comptime bit_kind: std.bit_set.IteratorOptions.Type,
|
|
858
|
-
) ?usize {
|
|
859
|
-
assert(bit_max >= bit_min);
|
|
860
|
-
assert(bit_max <= bit_set.bit_length);
|
|
861
|
-
|
|
862
|
-
const word_start = @divFloor(bit_min, @bitSizeOf(MaskInt)); // Inclusive.
|
|
863
|
-
const word_offset = @mod(bit_min, @bitSizeOf(MaskInt));
|
|
864
|
-
const word_end = div_ceil(bit_max, @bitSizeOf(MaskInt)); // Exclusive.
|
|
865
|
-
const words_total = div_ceil(bit_set.bit_length, @bitSizeOf(MaskInt));
|
|
866
|
-
if (word_end == word_start) return null;
|
|
867
|
-
assert(word_end > word_start);
|
|
868
|
-
|
|
869
|
-
// Only iterate over the subset of bits that were requested.
|
|
870
|
-
var iterator = bit_set.iterator(.{ .kind = bit_kind });
|
|
871
|
-
iterator.words_remain = bit_set.masks[word_start + 1 .. word_end];
|
|
872
|
-
|
|
873
|
-
const mask = ~@as(MaskInt, 0);
|
|
874
|
-
var word = bit_set.masks[word_start];
|
|
875
|
-
if (bit_kind == .unset) word = ~word;
|
|
876
|
-
iterator.bits_remain = word & std.math.shl(MaskInt, mask, word_offset);
|
|
877
|
-
|
|
878
|
-
if (word_end != words_total) iterator.last_word_mask = mask;
|
|
879
|
-
|
|
880
|
-
const b = bit_min - word_offset + (iterator.next() orelse return null);
|
|
881
|
-
return if (b < bit_max) b else null;
|
|
882
|
-
}
|
|
883
|
-
|
|
884
|
-
test "find_bit" {
|
|
885
|
-
var prng = std.rand.DefaultPrng.init(123);
|
|
886
|
-
const random = prng.random();
|
|
887
|
-
|
|
888
|
-
var bit_length: usize = 1;
|
|
889
|
-
while (bit_length <= @bitSizeOf(std.DynamicBitSetUnmanaged.MaskInt) * 4) : (bit_length += 1) {
|
|
890
|
-
var bit_set = try std.DynamicBitSetUnmanaged.initEmpty(std.testing.allocator, bit_length);
|
|
891
|
-
defer bit_set.deinit(std.testing.allocator);
|
|
892
|
-
|
|
893
|
-
const p = random.uintLessThan(usize, 100);
|
|
894
|
-
var b: usize = 0;
|
|
895
|
-
while (b < bit_length) : (b += 1) bit_set.setValue(b, p < random.uintLessThan(usize, 100));
|
|
896
|
-
|
|
897
|
-
var i: usize = 0;
|
|
898
|
-
while (i < 20) : (i += 1) try test_find_bit(random, bit_set, .set);
|
|
899
|
-
while (i < 40) : (i += 1) try test_find_bit(random, bit_set, .unset);
|
|
900
|
-
}
|
|
901
|
-
}
|
|
902
|
-
|
|
903
|
-
fn test_find_bit(
|
|
904
|
-
random: std.rand.Random,
|
|
905
|
-
bit_set: DynamicBitSetUnmanaged,
|
|
906
|
-
comptime bit_kind: std.bit_set.IteratorOptions.Type,
|
|
907
|
-
) !void {
|
|
908
|
-
const bit_min = random.uintLessThan(usize, bit_set.bit_length);
|
|
909
|
-
const bit_max = random.uintLessThan(usize, bit_set.bit_length - bit_min) + bit_min;
|
|
910
|
-
assert(bit_max >= bit_min);
|
|
911
|
-
assert(bit_max <= bit_set.bit_length);
|
|
912
|
-
|
|
913
|
-
const bit_actual = find_bit(bit_set, bit_min, bit_max, bit_kind);
|
|
914
|
-
if (bit_actual) |bit| {
|
|
915
|
-
assert(bit_set.isSet(bit) == (bit_kind == .set));
|
|
916
|
-
assert(bit >= bit_min);
|
|
917
|
-
assert(bit < bit_max);
|
|
918
|
-
}
|
|
919
|
-
|
|
920
|
-
var iterator = bit_set.iterator(.{ .kind = bit_kind });
|
|
921
|
-
while (iterator.next()) |bit| {
|
|
922
|
-
if (bit_min <= bit and bit < bit_max) {
|
|
923
|
-
try std.testing.expectEqual(bit_actual, bit);
|
|
924
|
-
break;
|
|
925
|
-
}
|
|
926
|
-
} else {
|
|
927
|
-
try std.testing.expectEqual(bit_actual, null);
|
|
928
|
-
}
|
|
929
|
-
}
|