tigerbeetle-node 0.11.13 → 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/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 +6 -16
- package/src/index.ts +56 -1
- package/src/node.zig +9 -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 -55
- package/src/tigerbeetle/scripts/benchmark.sh +0 -66
- package/src/tigerbeetle/scripts/confirm_image.sh +0 -44
- package/src/tigerbeetle/scripts/fail_on_diff.sh +0 -9
- package/src/tigerbeetle/scripts/fuzz_loop.sh +0 -15
- package/src/tigerbeetle/scripts/fuzz_loop_hash_log.sh +0 -12
- 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 -55
- package/src/tigerbeetle/scripts/scripts/benchmark.sh +0 -66
- package/src/tigerbeetle/scripts/scripts/confirm_image.sh +0 -44
- package/src/tigerbeetle/scripts/scripts/fail_on_diff.sh +0 -9
- package/src/tigerbeetle/scripts/scripts/fuzz_loop.sh +0 -15
- package/src/tigerbeetle/scripts/scripts/fuzz_loop_hash_log.sh +0 -12
- 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 -336
- package/src/tigerbeetle/src/config.zig +0 -233
- package/src/tigerbeetle/src/constants.zig +0 -428
- 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 -1071
- 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 -205
- package/src/tigerbeetle/src/lsm/forest_fuzz.zig +0 -450
- package/src/tigerbeetle/src/lsm/grid.zig +0 -573
- package/src/tigerbeetle/src/lsm/groove.zig +0 -1036
- 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 -878
- 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 -381
- package/src/tigerbeetle/src/lsm/segmented_array.zig +0 -1329
- 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 -1009
- package/src/tigerbeetle/src/lsm/table_immutable.zig +0 -192
- package/src/tigerbeetle/src/lsm/table_iterator.zig +0 -340
- package/src/tigerbeetle/src/lsm/table_mutable.zig +0 -203
- package/src/tigerbeetle/src/lsm/test.zig +0 -439
- package/src/tigerbeetle/src/lsm/tree.zig +0 -1169
- package/src/tigerbeetle/src/lsm/tree_fuzz.zig +0 -479
- package/src/tigerbeetle/src/message_bus.zig +0 -1013
- 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 -580
- package/src/tigerbeetle/src/state_machine/auditor.zig +0 -578
- package/src/tigerbeetle/src/state_machine/workload.zig +0 -883
- package/src/tigerbeetle/src/state_machine.zig +0 -2099
- package/src/tigerbeetle/src/static_allocator.zig +0 -65
- package/src/tigerbeetle/src/stdx.zig +0 -171
- 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 -444
- 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 -374
- 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 -250
- 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 -40
- 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 -855
- package/src/tigerbeetle/src/vsr/journal.zig +0 -2415
- 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 -6616
- 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 -1425
|
@@ -1,850 +0,0 @@
|
|
|
1
|
-
const std = @import("std");
|
|
2
|
-
const builtin = @import("builtin");
|
|
3
|
-
|
|
4
|
-
const assert = std.debug.assert;
|
|
5
|
-
const math = std.math;
|
|
6
|
-
const mem = std.mem;
|
|
7
|
-
const meta = std.meta;
|
|
8
|
-
const Vector = meta.Vector;
|
|
9
|
-
|
|
10
|
-
const constants = @import("../constants.zig");
|
|
11
|
-
const div_ceil = @import("../stdx.zig").div_ceil;
|
|
12
|
-
const verify = constants.verify;
|
|
13
|
-
|
|
14
|
-
pub const Layout = struct {
|
|
15
|
-
ways: u64 = 16,
|
|
16
|
-
tag_bits: u64 = 8,
|
|
17
|
-
clock_bits: u64 = 2,
|
|
18
|
-
cache_line_size: u64 = 64,
|
|
19
|
-
/// Set this to a non-null value to override the alignment of the stored values.
|
|
20
|
-
value_alignment: ?u29 = null,
|
|
21
|
-
};
|
|
22
|
-
|
|
23
|
-
/// Each Key is associated with a set of n consecutive ways (or slots) that may contain the Value.
|
|
24
|
-
pub fn SetAssociativeCache(
|
|
25
|
-
comptime Key: type,
|
|
26
|
-
comptime Value: type,
|
|
27
|
-
comptime key_from_value: fn (*const Value) callconv(.Inline) Key,
|
|
28
|
-
comptime hash: fn (Key) callconv(.Inline) u64,
|
|
29
|
-
comptime equal: fn (Key, Key) callconv(.Inline) bool,
|
|
30
|
-
comptime layout: Layout,
|
|
31
|
-
) type {
|
|
32
|
-
assert(math.isPowerOfTwo(@sizeOf(Key)));
|
|
33
|
-
assert(math.isPowerOfTwo(@sizeOf(Value)));
|
|
34
|
-
|
|
35
|
-
switch (layout.ways) {
|
|
36
|
-
// An 8-way set-associative cache has the clock hand as a u3, which would introduce padding.
|
|
37
|
-
2, 4, 16 => {},
|
|
38
|
-
else => @compileError("ways must be 2, 4 or 16 for optimal CLOCK hand size."),
|
|
39
|
-
}
|
|
40
|
-
switch (layout.tag_bits) {
|
|
41
|
-
8, 16 => {},
|
|
42
|
-
else => @compileError("tag_bits must be 8 or 16."),
|
|
43
|
-
}
|
|
44
|
-
switch (layout.clock_bits) {
|
|
45
|
-
1, 2, 4 => {},
|
|
46
|
-
else => @compileError("clock_bits must be 1, 2 or 4."),
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
if (layout.value_alignment) |alignment| {
|
|
50
|
-
assert(alignment >= @alignOf(Value));
|
|
51
|
-
assert(@sizeOf(Value) % alignment == 0);
|
|
52
|
-
}
|
|
53
|
-
const value_alignment = layout.value_alignment orelse @alignOf(Value);
|
|
54
|
-
|
|
55
|
-
assert(math.isPowerOfTwo(layout.ways));
|
|
56
|
-
assert(math.isPowerOfTwo(layout.tag_bits));
|
|
57
|
-
assert(math.isPowerOfTwo(layout.clock_bits));
|
|
58
|
-
assert(math.isPowerOfTwo(layout.cache_line_size));
|
|
59
|
-
|
|
60
|
-
assert(@sizeOf(Key) <= @sizeOf(Value));
|
|
61
|
-
assert(@sizeOf(Key) < layout.cache_line_size);
|
|
62
|
-
assert(layout.cache_line_size % @sizeOf(Key) == 0);
|
|
63
|
-
|
|
64
|
-
if (layout.cache_line_size > @sizeOf(Value)) {
|
|
65
|
-
assert(layout.cache_line_size % @sizeOf(Value) == 0);
|
|
66
|
-
} else {
|
|
67
|
-
assert(@sizeOf(Value) % layout.cache_line_size == 0);
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
const clock_hand_bits = math.log2_int(u64, layout.ways);
|
|
71
|
-
assert(math.isPowerOfTwo(clock_hand_bits));
|
|
72
|
-
assert((1 << clock_hand_bits) == layout.ways);
|
|
73
|
-
|
|
74
|
-
const tags_per_line = @divExact(layout.cache_line_size * 8, layout.ways * layout.tag_bits);
|
|
75
|
-
assert(tags_per_line > 0);
|
|
76
|
-
|
|
77
|
-
const clocks_per_line = @divExact(layout.cache_line_size * 8, layout.ways * layout.clock_bits);
|
|
78
|
-
assert(clocks_per_line > 0);
|
|
79
|
-
|
|
80
|
-
const clock_hands_per_line = @divExact(layout.cache_line_size * 8, clock_hand_bits);
|
|
81
|
-
assert(clock_hands_per_line > 0);
|
|
82
|
-
|
|
83
|
-
return struct {
|
|
84
|
-
const Self = @This();
|
|
85
|
-
|
|
86
|
-
const Tag = meta.Int(.unsigned, layout.tag_bits);
|
|
87
|
-
const Count = meta.Int(.unsigned, layout.clock_bits);
|
|
88
|
-
const Clock = meta.Int(.unsigned, clock_hand_bits);
|
|
89
|
-
|
|
90
|
-
sets: u64,
|
|
91
|
-
|
|
92
|
-
/// A short, partial hash of a Key, corresponding to a Value.
|
|
93
|
-
/// Because the tag is small, collisions are possible:
|
|
94
|
-
/// `tag(v₁) = tag(v₂)` does not imply `v₁ = v₂`.
|
|
95
|
-
/// However, most of the time, where the tag differs, a full key comparison can be avoided.
|
|
96
|
-
/// Since tags are 16-32x smaller than keys, they can also be kept hot in cache.
|
|
97
|
-
tags: []Tag,
|
|
98
|
-
|
|
99
|
-
/// When the corresponding Count is zero, the Value is absent.
|
|
100
|
-
values: []align(value_alignment) Value,
|
|
101
|
-
|
|
102
|
-
/// Each value has a Count, which tracks the number of recent reads.
|
|
103
|
-
///
|
|
104
|
-
/// * A Count is incremented when the value is accessed by `get`.
|
|
105
|
-
/// * A Count is decremented when a cache write to the value's Set misses.
|
|
106
|
-
/// * The value is evicted when its Count reaches zero.
|
|
107
|
-
///
|
|
108
|
-
counts: PackedUnsignedIntegerArray(Count),
|
|
109
|
-
|
|
110
|
-
/// Each set has a Clock: a counter that cycles between each of the set's ways (i.e. slots).
|
|
111
|
-
///
|
|
112
|
-
/// On cache write, entries are checked for occupancy (or eviction) beginning from the
|
|
113
|
-
/// clock's position, wrapping around.
|
|
114
|
-
///
|
|
115
|
-
/// The algorithm implemented is "CLOCK Nth-Chance" — each way has more than one bit,
|
|
116
|
-
/// to give ways more than one chance before eviction.
|
|
117
|
-
///
|
|
118
|
-
/// * A similar algorithm called "RRIParoo" is described in
|
|
119
|
-
/// "Kangaroo: Caching Billions of Tiny Objects on Flash".
|
|
120
|
-
/// * For more general information on CLOCK algorithms, see:
|
|
121
|
-
/// https://en.wikipedia.org/wiki/Page_replacement_algorithm.
|
|
122
|
-
clocks: PackedUnsignedIntegerArray(Clock),
|
|
123
|
-
|
|
124
|
-
pub fn init(allocator: mem.Allocator, value_count_max: u64) !Self {
|
|
125
|
-
assert(math.isPowerOfTwo(value_count_max));
|
|
126
|
-
assert(value_count_max > 0);
|
|
127
|
-
assert(value_count_max >= layout.ways);
|
|
128
|
-
assert(value_count_max % layout.ways == 0);
|
|
129
|
-
|
|
130
|
-
const sets = @divExact(value_count_max, layout.ways);
|
|
131
|
-
assert(math.isPowerOfTwo(sets));
|
|
132
|
-
|
|
133
|
-
const values_size_max = value_count_max * @sizeOf(Value);
|
|
134
|
-
assert(values_size_max >= layout.cache_line_size);
|
|
135
|
-
assert(values_size_max % layout.cache_line_size == 0);
|
|
136
|
-
|
|
137
|
-
const counts_size = @divExact(value_count_max * layout.clock_bits, 8);
|
|
138
|
-
assert(counts_size >= layout.cache_line_size);
|
|
139
|
-
assert(counts_size % layout.cache_line_size == 0);
|
|
140
|
-
|
|
141
|
-
const clocks_size = @divExact(sets * clock_hand_bits, 8);
|
|
142
|
-
assert(clocks_size >= layout.cache_line_size);
|
|
143
|
-
assert(clocks_size % layout.cache_line_size == 0);
|
|
144
|
-
|
|
145
|
-
const tags = try allocator.alloc(Tag, value_count_max);
|
|
146
|
-
errdefer allocator.free(tags);
|
|
147
|
-
|
|
148
|
-
const values = try allocator.allocAdvanced(
|
|
149
|
-
Value,
|
|
150
|
-
value_alignment,
|
|
151
|
-
value_count_max,
|
|
152
|
-
.exact,
|
|
153
|
-
);
|
|
154
|
-
errdefer allocator.free(values);
|
|
155
|
-
|
|
156
|
-
const counts = try allocator.alloc(u64, @divExact(counts_size, @sizeOf(u64)));
|
|
157
|
-
errdefer allocator.free(counts);
|
|
158
|
-
|
|
159
|
-
const clocks = try allocator.alloc(u64, @divExact(clocks_size, @sizeOf(u64)));
|
|
160
|
-
errdefer allocator.free(clocks);
|
|
161
|
-
|
|
162
|
-
var self = Self{
|
|
163
|
-
.sets = sets,
|
|
164
|
-
.tags = tags,
|
|
165
|
-
.values = values,
|
|
166
|
-
.counts = .{ .words = counts },
|
|
167
|
-
.clocks = .{ .words = clocks },
|
|
168
|
-
};
|
|
169
|
-
|
|
170
|
-
self.reset();
|
|
171
|
-
|
|
172
|
-
return self;
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
pub fn deinit(self: *Self, allocator: mem.Allocator) void {
|
|
176
|
-
assert(self.sets > 0);
|
|
177
|
-
self.sets = 0;
|
|
178
|
-
|
|
179
|
-
allocator.free(self.tags);
|
|
180
|
-
allocator.free(self.values);
|
|
181
|
-
allocator.free(self.counts.words);
|
|
182
|
-
allocator.free(self.clocks.words);
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
pub fn reset(self: *Self) void {
|
|
186
|
-
mem.set(Tag, self.tags, 0);
|
|
187
|
-
mem.set(u64, self.counts.words, 0);
|
|
188
|
-
mem.set(u64, self.clocks.words, 0);
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
/// Returns whether an entry with the given key is cached,
|
|
192
|
-
/// without modifying the entry's counter.
|
|
193
|
-
pub fn exists(self: *Self, key: Key) bool {
|
|
194
|
-
const set = self.associate(key);
|
|
195
|
-
return self.search(set, key) != null;
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
pub fn get_index(self: *Self, key: Key) ?usize {
|
|
199
|
-
const set = self.associate(key);
|
|
200
|
-
const way = self.search(set, key) orelse return null;
|
|
201
|
-
|
|
202
|
-
const count = self.counts.get(set.offset + way);
|
|
203
|
-
self.counts.set(set.offset + way, count +| 1);
|
|
204
|
-
|
|
205
|
-
return set.offset + way;
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
pub fn get(self: *Self, key: Key) ?*align(value_alignment) Value {
|
|
209
|
-
const index = self.get_index(key) orelse return null;
|
|
210
|
-
return @alignCast(value_alignment, &self.values[index]);
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
/// Remove a key from the set associative cache if present.
|
|
214
|
-
pub fn remove(self: *Self, key: Key) void {
|
|
215
|
-
const set = self.associate(key);
|
|
216
|
-
const way = self.search(set, key) orelse return;
|
|
217
|
-
|
|
218
|
-
self.counts.set(set.offset + way, 0);
|
|
219
|
-
set.values[way] = undefined;
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
/// Hint that the key is less likely to be accessed in the future, without actually removing
|
|
223
|
-
/// it from the cache.
|
|
224
|
-
pub fn demote(self: *Self, key: Key) void {
|
|
225
|
-
const set = self.associate(key);
|
|
226
|
-
const way = self.search(set, key) orelse return;
|
|
227
|
-
|
|
228
|
-
self.counts.set(set.offset + way, 1);
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
/// If the key is present in the set, returns the way. Otherwise returns null.
|
|
232
|
-
inline fn search(self: *const Self, set: Set, key: Key) ?usize {
|
|
233
|
-
const ways = search_tags(set.tags, set.tag);
|
|
234
|
-
|
|
235
|
-
var it = BitIterator(Ways){ .bits = ways };
|
|
236
|
-
while (it.next()) |way| {
|
|
237
|
-
const count = self.counts.get(set.offset + way);
|
|
238
|
-
if (count > 0 and equal(key_from_value(&set.values[way]), key)) {
|
|
239
|
-
return way;
|
|
240
|
-
}
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
return null;
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
/// Where each set bit represents the index of a way that has the same tag.
|
|
247
|
-
const Ways = meta.Int(.unsigned, layout.ways);
|
|
248
|
-
|
|
249
|
-
inline fn search_tags(tags: *const [layout.ways]Tag, tag: Tag) Ways {
|
|
250
|
-
const x: Vector(layout.ways, Tag) = tags.*;
|
|
251
|
-
const y: Vector(layout.ways, Tag) = @splat(layout.ways, tag);
|
|
252
|
-
|
|
253
|
-
const result: Vector(layout.ways, bool) = x == y;
|
|
254
|
-
return @ptrCast(*const Ways, &result).*;
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
/// Insert a value, evicting an older entry if needed.
|
|
258
|
-
pub fn insert(self: *Self, value: *const Value) void {
|
|
259
|
-
_ = self.insert_index(value);
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
/// Insert a value, evicting an older entry if needed.
|
|
263
|
-
/// Return the index at which the value was inserted.
|
|
264
|
-
pub fn insert_index(self: *Self, value: *const Value) usize {
|
|
265
|
-
const key = key_from_value(value);
|
|
266
|
-
const set = self.associate(key);
|
|
267
|
-
if (self.search(set, key)) |way| {
|
|
268
|
-
// Overwrite the old entry for this key.
|
|
269
|
-
self.counts.set(set.offset + way, 1);
|
|
270
|
-
set.values[way] = value.*;
|
|
271
|
-
return set.offset + way;
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
const clock_index = @divExact(set.offset, layout.ways);
|
|
275
|
-
|
|
276
|
-
var way = self.clocks.get(clock_index);
|
|
277
|
-
comptime assert(math.maxInt(@TypeOf(way)) == layout.ways - 1);
|
|
278
|
-
comptime assert(@as(@TypeOf(way), math.maxInt(@TypeOf(way))) +% 1 == 0);
|
|
279
|
-
|
|
280
|
-
// The maximum number of iterations happens when every slot in the set has the maximum
|
|
281
|
-
// count. In this case, the loop will iterate until all counts have been decremented
|
|
282
|
-
// to 1. Then in the next iteration it will decrement a count to 0 and break.
|
|
283
|
-
const clock_iterations_max = layout.ways * (math.maxInt(Count) - 1);
|
|
284
|
-
|
|
285
|
-
var safety_count: usize = 0;
|
|
286
|
-
while (safety_count <= clock_iterations_max) : ({
|
|
287
|
-
safety_count += 1;
|
|
288
|
-
way +%= 1;
|
|
289
|
-
}) {
|
|
290
|
-
var count = self.counts.get(set.offset + way);
|
|
291
|
-
if (count == 0) break; // Way is already free.
|
|
292
|
-
|
|
293
|
-
count -= 1;
|
|
294
|
-
self.counts.set(set.offset + way, count);
|
|
295
|
-
if (count == 0) break; // Way has become free.
|
|
296
|
-
} else {
|
|
297
|
-
unreachable;
|
|
298
|
-
}
|
|
299
|
-
assert(self.counts.get(set.offset + way) == 0);
|
|
300
|
-
|
|
301
|
-
set.tags[way] = set.tag;
|
|
302
|
-
set.values[way] = value.*;
|
|
303
|
-
self.counts.set(set.offset + way, 1);
|
|
304
|
-
self.clocks.set(clock_index, way +% 1);
|
|
305
|
-
|
|
306
|
-
return set.offset + way;
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
const Set = struct {
|
|
310
|
-
tag: Tag,
|
|
311
|
-
offset: u64,
|
|
312
|
-
tags: *[layout.ways]Tag,
|
|
313
|
-
values: *[layout.ways]Value,
|
|
314
|
-
|
|
315
|
-
fn inspect(set: Set, sac: Self) void {
|
|
316
|
-
const clock_index = @divExact(set.offset, layout.ways);
|
|
317
|
-
std.debug.print(
|
|
318
|
-
\\{{
|
|
319
|
-
\\ tag={}
|
|
320
|
-
\\ offset={}
|
|
321
|
-
\\ clock_hand={}
|
|
322
|
-
, .{
|
|
323
|
-
set.tag,
|
|
324
|
-
set.offset,
|
|
325
|
-
sac.clocks.get(clock_index),
|
|
326
|
-
});
|
|
327
|
-
|
|
328
|
-
std.debug.print("\n tags={}", .{set.tags[0]});
|
|
329
|
-
for (set.tags[1..]) |tag| std.debug.print(", {}", .{tag});
|
|
330
|
-
|
|
331
|
-
std.debug.print("\n values={}", .{set.values[0]});
|
|
332
|
-
for (set.values[1..]) |value| std.debug.print(", {}", .{value});
|
|
333
|
-
|
|
334
|
-
std.debug.print("\n counts={}", .{sac.counts.get(set.offset)});
|
|
335
|
-
var i: usize = 1;
|
|
336
|
-
while (i < layout.ways) : (i += 1) {
|
|
337
|
-
std.debug.print(", {}", .{sac.counts.get(set.offset + i)});
|
|
338
|
-
}
|
|
339
|
-
|
|
340
|
-
std.debug.print("\n}}\n", .{});
|
|
341
|
-
}
|
|
342
|
-
};
|
|
343
|
-
|
|
344
|
-
inline fn associate(self: *Self, key: Key) Set {
|
|
345
|
-
const entropy = hash(key);
|
|
346
|
-
|
|
347
|
-
const tag = @truncate(Tag, entropy >> math.log2_int(u64, self.sets));
|
|
348
|
-
const index = entropy % self.sets;
|
|
349
|
-
const offset = index * layout.ways;
|
|
350
|
-
|
|
351
|
-
return .{
|
|
352
|
-
.tag = tag,
|
|
353
|
-
.offset = offset,
|
|
354
|
-
.tags = self.tags[offset..][0..layout.ways],
|
|
355
|
-
.values = self.values[offset..][0..layout.ways],
|
|
356
|
-
};
|
|
357
|
-
}
|
|
358
|
-
|
|
359
|
-
pub fn inspect() void {
|
|
360
|
-
std.debug.print("\nKey={} Value={} ways={} tag_bits={} clock_bits={} " ++
|
|
361
|
-
"clock_hand_bits={} tags_per_line={} clocks_per_line={} " ++
|
|
362
|
-
"clock_hands_per_line={}\n", .{
|
|
363
|
-
@bitSizeOf(Key),
|
|
364
|
-
@sizeOf(Value),
|
|
365
|
-
layout.ways,
|
|
366
|
-
layout.tag_bits,
|
|
367
|
-
layout.clock_bits,
|
|
368
|
-
clock_hand_bits,
|
|
369
|
-
tags_per_line,
|
|
370
|
-
clocks_per_line,
|
|
371
|
-
clock_hands_per_line,
|
|
372
|
-
});
|
|
373
|
-
}
|
|
374
|
-
};
|
|
375
|
-
}
|
|
376
|
-
|
|
377
|
-
fn set_associative_cache_test(
|
|
378
|
-
comptime Key: type,
|
|
379
|
-
comptime Value: type,
|
|
380
|
-
comptime context: type,
|
|
381
|
-
comptime layout: Layout,
|
|
382
|
-
) type {
|
|
383
|
-
const testing = std.testing;
|
|
384
|
-
const expect = testing.expect;
|
|
385
|
-
const expectEqual = testing.expectEqual;
|
|
386
|
-
|
|
387
|
-
const log = false;
|
|
388
|
-
|
|
389
|
-
const SAC = SetAssociativeCache(
|
|
390
|
-
Key,
|
|
391
|
-
Value,
|
|
392
|
-
context.key_from_value,
|
|
393
|
-
context.hash,
|
|
394
|
-
context.equal,
|
|
395
|
-
layout,
|
|
396
|
-
);
|
|
397
|
-
|
|
398
|
-
return struct {
|
|
399
|
-
fn run() !void {
|
|
400
|
-
if (log) SAC.inspect();
|
|
401
|
-
|
|
402
|
-
// TODO Add a nice calculator method to help solve the minimum value_count_max required:
|
|
403
|
-
var sac = try SAC.init(testing.allocator, 16 * 16 * 8);
|
|
404
|
-
defer sac.deinit(testing.allocator);
|
|
405
|
-
|
|
406
|
-
for (sac.tags) |tag| try testing.expectEqual(@as(SAC.Tag, 0), tag);
|
|
407
|
-
for (sac.counts.words) |word| try testing.expectEqual(@as(u64, 0), word);
|
|
408
|
-
for (sac.clocks.words) |word| try testing.expectEqual(@as(u64, 0), word);
|
|
409
|
-
|
|
410
|
-
// Fill up the first set entirely.
|
|
411
|
-
{
|
|
412
|
-
var i: usize = 0;
|
|
413
|
-
while (i < layout.ways) : (i += 1) {
|
|
414
|
-
try expectEqual(i, sac.clocks.get(0));
|
|
415
|
-
|
|
416
|
-
const key = i * sac.sets;
|
|
417
|
-
sac.insert(&key);
|
|
418
|
-
try expect(sac.counts.get(i) == 1);
|
|
419
|
-
try expectEqual(key, sac.get(key).?.*);
|
|
420
|
-
try expect(sac.counts.get(i) == 2);
|
|
421
|
-
}
|
|
422
|
-
try expect(sac.clocks.get(0) == 0);
|
|
423
|
-
}
|
|
424
|
-
|
|
425
|
-
if (log) sac.associate(0).inspect(sac);
|
|
426
|
-
|
|
427
|
-
// Insert another element into the first set, causing key 0 to be evicted.
|
|
428
|
-
{
|
|
429
|
-
const key = layout.ways * sac.sets;
|
|
430
|
-
sac.insert(&key);
|
|
431
|
-
try expect(sac.counts.get(0) == 1);
|
|
432
|
-
try expectEqual(key, sac.get(key).?.*);
|
|
433
|
-
try expect(sac.counts.get(0) == 2);
|
|
434
|
-
|
|
435
|
-
try expectEqual(@as(?*Value, null), sac.get(0));
|
|
436
|
-
|
|
437
|
-
{
|
|
438
|
-
var i: usize = 1;
|
|
439
|
-
while (i < layout.ways) : (i += 1) {
|
|
440
|
-
try expect(sac.counts.get(i) == 1);
|
|
441
|
-
}
|
|
442
|
-
}
|
|
443
|
-
}
|
|
444
|
-
|
|
445
|
-
if (log) sac.associate(0).inspect(sac);
|
|
446
|
-
|
|
447
|
-
// Ensure removal works.
|
|
448
|
-
{
|
|
449
|
-
const key = 5 * sac.sets;
|
|
450
|
-
assert(sac.get(key).?.* == key);
|
|
451
|
-
try expect(sac.counts.get(5) == 2);
|
|
452
|
-
|
|
453
|
-
sac.remove(key);
|
|
454
|
-
try expectEqual(@as(?*Value, null), sac.get(key));
|
|
455
|
-
try expect(sac.counts.get(5) == 0);
|
|
456
|
-
}
|
|
457
|
-
|
|
458
|
-
sac.reset();
|
|
459
|
-
|
|
460
|
-
for (sac.tags) |tag| try testing.expectEqual(@as(SAC.Tag, 0), tag);
|
|
461
|
-
for (sac.counts.words) |word| try testing.expectEqual(@as(u64, 0), word);
|
|
462
|
-
for (sac.clocks.words) |word| try testing.expectEqual(@as(u64, 0), word);
|
|
463
|
-
|
|
464
|
-
// Fill up the first set entirely, maxing out the count for each slot.
|
|
465
|
-
{
|
|
466
|
-
var i: usize = 0;
|
|
467
|
-
while (i < layout.ways) : (i += 1) {
|
|
468
|
-
try expectEqual(i, sac.clocks.get(0));
|
|
469
|
-
|
|
470
|
-
const key = i * sac.sets;
|
|
471
|
-
sac.insert(&key);
|
|
472
|
-
try expect(sac.counts.get(i) == 1);
|
|
473
|
-
var j: usize = 2;
|
|
474
|
-
while (j <= math.maxInt(SAC.Count)) : (j += 1) {
|
|
475
|
-
try expectEqual(key, sac.get(key).?.*);
|
|
476
|
-
try expect(sac.counts.get(i) == j);
|
|
477
|
-
}
|
|
478
|
-
try expectEqual(key, sac.get(key).?.*);
|
|
479
|
-
try expect(sac.counts.get(i) == math.maxInt(SAC.Count));
|
|
480
|
-
}
|
|
481
|
-
try expect(sac.clocks.get(0) == 0);
|
|
482
|
-
}
|
|
483
|
-
|
|
484
|
-
if (log) sac.associate(0).inspect(sac);
|
|
485
|
-
|
|
486
|
-
// Insert another element into the first set, causing key 0 to be evicted.
|
|
487
|
-
{
|
|
488
|
-
const key = layout.ways * sac.sets;
|
|
489
|
-
sac.insert(&key);
|
|
490
|
-
try expect(sac.counts.get(0) == 1);
|
|
491
|
-
try expectEqual(key, sac.get(key).?.*);
|
|
492
|
-
try expect(sac.counts.get(0) == 2);
|
|
493
|
-
|
|
494
|
-
try expectEqual(@as(?*Value, null), sac.get(0));
|
|
495
|
-
|
|
496
|
-
{
|
|
497
|
-
var i: usize = 1;
|
|
498
|
-
while (i < layout.ways) : (i += 1) {
|
|
499
|
-
try expect(sac.counts.get(i) == 1);
|
|
500
|
-
}
|
|
501
|
-
}
|
|
502
|
-
}
|
|
503
|
-
|
|
504
|
-
if (log) sac.associate(0).inspect(sac);
|
|
505
|
-
}
|
|
506
|
-
};
|
|
507
|
-
}
|
|
508
|
-
|
|
509
|
-
test "SetAssociativeCache: eviction" {
|
|
510
|
-
const Key = u64;
|
|
511
|
-
const Value = u64;
|
|
512
|
-
|
|
513
|
-
const context = struct {
|
|
514
|
-
inline fn key_from_value(value: *const Value) Key {
|
|
515
|
-
return value.*;
|
|
516
|
-
}
|
|
517
|
-
inline fn hash(key: Key) u64 {
|
|
518
|
-
return key;
|
|
519
|
-
}
|
|
520
|
-
inline fn equal(a: Key, b: Key) bool {
|
|
521
|
-
return a == b;
|
|
522
|
-
}
|
|
523
|
-
};
|
|
524
|
-
|
|
525
|
-
try set_associative_cache_test(Key, Value, context, .{}).run();
|
|
526
|
-
}
|
|
527
|
-
|
|
528
|
-
test "SetAssociativeCache: hash collision" {
|
|
529
|
-
const Key = u64;
|
|
530
|
-
const Value = u64;
|
|
531
|
-
|
|
532
|
-
const context = struct {
|
|
533
|
-
inline fn key_from_value(value: *const Value) Key {
|
|
534
|
-
return value.*;
|
|
535
|
-
}
|
|
536
|
-
/// This hash function is intentionally broken to simulate hash collision.
|
|
537
|
-
inline fn hash(key: Key) u64 {
|
|
538
|
-
_ = key;
|
|
539
|
-
return 0;
|
|
540
|
-
}
|
|
541
|
-
inline fn equal(a: Key, b: Key) bool {
|
|
542
|
-
return a == b;
|
|
543
|
-
}
|
|
544
|
-
};
|
|
545
|
-
|
|
546
|
-
try set_associative_cache_test(Key, Value, context, .{}).run();
|
|
547
|
-
}
|
|
548
|
-
|
|
549
|
-
/// A little simpler than PackedIntArray in the std lib, restricted to little endian 64-bit words,
|
|
550
|
-
/// and using words exactly without padding.
|
|
551
|
-
fn PackedUnsignedIntegerArray(comptime UInt: type) type {
|
|
552
|
-
const Word = u64;
|
|
553
|
-
|
|
554
|
-
assert(builtin.target.cpu.arch.endian() == .Little);
|
|
555
|
-
assert(@typeInfo(UInt).Int.signedness == .unsigned);
|
|
556
|
-
assert(@typeInfo(UInt).Int.bits < meta.bitCount(u8));
|
|
557
|
-
assert(math.isPowerOfTwo(@typeInfo(UInt).Int.bits));
|
|
558
|
-
|
|
559
|
-
const word_bits = meta.bitCount(Word);
|
|
560
|
-
const uint_bits = meta.bitCount(UInt);
|
|
561
|
-
const uints_per_word = @divExact(word_bits, uint_bits);
|
|
562
|
-
|
|
563
|
-
// An index bounded by the number of unsigned integers that fit exactly into a word.
|
|
564
|
-
const WordIndex = meta.Int(.unsigned, math.log2_int(u64, uints_per_word));
|
|
565
|
-
assert(math.maxInt(WordIndex) == uints_per_word - 1);
|
|
566
|
-
|
|
567
|
-
// An index bounded by the number of bits (not unsigned integers) that fit exactly into a word.
|
|
568
|
-
const BitsIndex = math.Log2Int(Word);
|
|
569
|
-
assert(math.maxInt(BitsIndex) == meta.bitCount(Word) - 1);
|
|
570
|
-
assert(math.maxInt(BitsIndex) == word_bits - 1);
|
|
571
|
-
assert(math.maxInt(BitsIndex) == uint_bits * (math.maxInt(WordIndex) + 1) - 1);
|
|
572
|
-
|
|
573
|
-
return struct {
|
|
574
|
-
const Self = @This();
|
|
575
|
-
|
|
576
|
-
words: []Word,
|
|
577
|
-
|
|
578
|
-
/// Returns the unsigned integer at `index`.
|
|
579
|
-
pub inline fn get(self: Self, index: u64) UInt {
|
|
580
|
-
// This truncate is safe since we want to mask the right-shifted word by exactly a UInt:
|
|
581
|
-
return @truncate(UInt, self.word(index).* >> bits_index(index));
|
|
582
|
-
}
|
|
583
|
-
|
|
584
|
-
/// Sets the unsigned integer at `index` to `value`.
|
|
585
|
-
pub inline fn set(self: Self, index: u64, value: UInt) void {
|
|
586
|
-
const w = self.word(index);
|
|
587
|
-
w.* &= ~mask(index);
|
|
588
|
-
w.* |= @as(Word, value) << bits_index(index);
|
|
589
|
-
}
|
|
590
|
-
|
|
591
|
-
inline fn mask(index: u64) Word {
|
|
592
|
-
return @as(Word, math.maxInt(UInt)) << bits_index(index);
|
|
593
|
-
}
|
|
594
|
-
|
|
595
|
-
inline fn word(self: Self, index: u64) *Word {
|
|
596
|
-
return &self.words[@divFloor(index, uints_per_word)];
|
|
597
|
-
}
|
|
598
|
-
|
|
599
|
-
inline fn bits_index(index: u64) BitsIndex {
|
|
600
|
-
// If uint_bits=2, then it's normal for the maximum return value value to be 62, even
|
|
601
|
-
// where BitsIndex allows up to 63 (inclusive) for a 64-bit word. This is because 62 is
|
|
602
|
-
// the bit index of the highest 2-bit UInt (e.g. bit index + bit length == 64).
|
|
603
|
-
comptime assert(uint_bits * (math.maxInt(WordIndex) + 1) == math.maxInt(BitsIndex) + 1);
|
|
604
|
-
|
|
605
|
-
return @as(BitsIndex, uint_bits) * @truncate(WordIndex, index);
|
|
606
|
-
}
|
|
607
|
-
};
|
|
608
|
-
}
|
|
609
|
-
|
|
610
|
-
test "PackedUnsignedIntegerArray: unit" {
|
|
611
|
-
const expectEqual = std.testing.expectEqual;
|
|
612
|
-
|
|
613
|
-
var words = [8]u64{ 0, 0b10110010, 0, 0, 0, 0, 0, 0 };
|
|
614
|
-
|
|
615
|
-
var p: PackedUnsignedIntegerArray(u2) = .{
|
|
616
|
-
.words = &words,
|
|
617
|
-
};
|
|
618
|
-
|
|
619
|
-
try expectEqual(@as(u2, 0b10), p.get(32 + 0));
|
|
620
|
-
try expectEqual(@as(u2, 0b00), p.get(32 + 1));
|
|
621
|
-
try expectEqual(@as(u2, 0b11), p.get(32 + 2));
|
|
622
|
-
try expectEqual(@as(u2, 0b10), p.get(32 + 3));
|
|
623
|
-
|
|
624
|
-
p.set(0, 0b01);
|
|
625
|
-
try expectEqual(@as(u64, 0b00000001), words[0]);
|
|
626
|
-
try expectEqual(@as(u2, 0b01), p.get(0));
|
|
627
|
-
p.set(1, 0b10);
|
|
628
|
-
try expectEqual(@as(u64, 0b00001001), words[0]);
|
|
629
|
-
try expectEqual(@as(u2, 0b10), p.get(1));
|
|
630
|
-
p.set(2, 0b11);
|
|
631
|
-
try expectEqual(@as(u64, 0b00111001), words[0]);
|
|
632
|
-
try expectEqual(@as(u2, 0b11), p.get(2));
|
|
633
|
-
p.set(3, 0b11);
|
|
634
|
-
try expectEqual(@as(u64, 0b11111001), words[0]);
|
|
635
|
-
try expectEqual(@as(u2, 0b11), p.get(3));
|
|
636
|
-
p.set(3, 0b01);
|
|
637
|
-
try expectEqual(@as(u64, 0b01111001), words[0]);
|
|
638
|
-
try expectEqual(@as(u2, 0b01), p.get(3));
|
|
639
|
-
p.set(3, 0b00);
|
|
640
|
-
try expectEqual(@as(u64, 0b00111001), words[0]);
|
|
641
|
-
try expectEqual(@as(u2, 0b00), p.get(3));
|
|
642
|
-
|
|
643
|
-
p.set(4, 0b11);
|
|
644
|
-
try expectEqual(
|
|
645
|
-
@as(u64, 0b0000000000000000000000000000000000000000000000000000001100111001),
|
|
646
|
-
words[0],
|
|
647
|
-
);
|
|
648
|
-
p.set(31, 0b11);
|
|
649
|
-
try expectEqual(
|
|
650
|
-
@as(u64, 0b1100000000000000000000000000000000000000000000000000001100111001),
|
|
651
|
-
words[0],
|
|
652
|
-
);
|
|
653
|
-
}
|
|
654
|
-
|
|
655
|
-
fn PackedUnsignedIntegerArrayFuzzTest(comptime UInt: type) type {
|
|
656
|
-
const testing = std.testing;
|
|
657
|
-
|
|
658
|
-
return struct {
|
|
659
|
-
const Self = @This();
|
|
660
|
-
|
|
661
|
-
const Array = PackedUnsignedIntegerArray(UInt);
|
|
662
|
-
random: std.rand.Random,
|
|
663
|
-
|
|
664
|
-
array: Array,
|
|
665
|
-
reference: []UInt,
|
|
666
|
-
|
|
667
|
-
fn init(random: std.rand.Random, len: usize) !Self {
|
|
668
|
-
const words = try testing.allocator.alloc(u64, @divExact(len * @bitSizeOf(UInt), 64));
|
|
669
|
-
errdefer testing.allocator.free(words);
|
|
670
|
-
|
|
671
|
-
const reference = try testing.allocator.alloc(UInt, len);
|
|
672
|
-
errdefer testing.allocator.free(reference);
|
|
673
|
-
|
|
674
|
-
mem.set(u64, words, 0);
|
|
675
|
-
mem.set(UInt, reference, 0);
|
|
676
|
-
|
|
677
|
-
return Self{
|
|
678
|
-
.random = random,
|
|
679
|
-
.array = Array{ .words = words },
|
|
680
|
-
.reference = reference,
|
|
681
|
-
};
|
|
682
|
-
}
|
|
683
|
-
|
|
684
|
-
fn deinit(context: *Self) void {
|
|
685
|
-
testing.allocator.free(context.array.words);
|
|
686
|
-
testing.allocator.free(context.reference);
|
|
687
|
-
}
|
|
688
|
-
|
|
689
|
-
fn run(context: *Self) !void {
|
|
690
|
-
var iterations: usize = 0;
|
|
691
|
-
while (iterations < 10_000) : (iterations += 1) {
|
|
692
|
-
const index = context.random.uintLessThanBiased(usize, context.reference.len);
|
|
693
|
-
const value = context.random.int(UInt);
|
|
694
|
-
|
|
695
|
-
context.array.set(index, value);
|
|
696
|
-
context.reference[index] = value;
|
|
697
|
-
|
|
698
|
-
try context.verify();
|
|
699
|
-
}
|
|
700
|
-
}
|
|
701
|
-
|
|
702
|
-
fn verify(context: *Self) !void {
|
|
703
|
-
for (context.reference) |value, index| {
|
|
704
|
-
try testing.expectEqual(value, context.array.get(index));
|
|
705
|
-
}
|
|
706
|
-
}
|
|
707
|
-
};
|
|
708
|
-
}
|
|
709
|
-
|
|
710
|
-
test "PackedUnsignedIntegerArray: fuzz" {
|
|
711
|
-
const seed = 42;
|
|
712
|
-
|
|
713
|
-
var prng = std.rand.DefaultPrng.init(seed);
|
|
714
|
-
const random = prng.random();
|
|
715
|
-
|
|
716
|
-
inline for (.{ u1, u2, u4 }) |UInt| {
|
|
717
|
-
const Context = PackedUnsignedIntegerArrayFuzzTest(UInt);
|
|
718
|
-
|
|
719
|
-
var context = try Context.init(random, 1024);
|
|
720
|
-
defer context.deinit();
|
|
721
|
-
|
|
722
|
-
try context.run();
|
|
723
|
-
}
|
|
724
|
-
}
|
|
725
|
-
|
|
726
|
-
fn BitIterator(comptime Bits: type) type {
|
|
727
|
-
return struct {
|
|
728
|
-
const Self = @This();
|
|
729
|
-
const BitIndex = math.Log2Int(Bits);
|
|
730
|
-
|
|
731
|
-
bits: Bits,
|
|
732
|
-
|
|
733
|
-
/// Iterates over the bits, consuming them.
|
|
734
|
-
/// Returns the bit index of each set bit until there are no more set bits, then null.
|
|
735
|
-
inline fn next(it: *Self) ?BitIndex {
|
|
736
|
-
if (it.bits == 0) return null;
|
|
737
|
-
// This @intCast() is safe since we never pass 0 to @ctz().
|
|
738
|
-
const index = @intCast(BitIndex, @ctz(Bits, it.bits));
|
|
739
|
-
// Zero the lowest set bit.
|
|
740
|
-
it.bits &= it.bits - 1;
|
|
741
|
-
return index;
|
|
742
|
-
}
|
|
743
|
-
};
|
|
744
|
-
}
|
|
745
|
-
|
|
746
|
-
test "BitIterator" {
|
|
747
|
-
const expectEqual = @import("std").testing.expectEqual;
|
|
748
|
-
|
|
749
|
-
var it = BitIterator(u16){ .bits = 0b1000_0000_0100_0101 };
|
|
750
|
-
|
|
751
|
-
for ([_]u4{ 0, 2, 6, 15 }) |e| {
|
|
752
|
-
try expectEqual(@as(?u4, e), it.next());
|
|
753
|
-
}
|
|
754
|
-
try expectEqual(it.next(), null);
|
|
755
|
-
}
|
|
756
|
-
|
|
757
|
-
fn search_tags_test(comptime Key: type, comptime Value: type, comptime layout: Layout) type {
|
|
758
|
-
const testing = std.testing;
|
|
759
|
-
|
|
760
|
-
const log = false;
|
|
761
|
-
|
|
762
|
-
const context = struct {
|
|
763
|
-
inline fn key_from_value(value: *const Value) Key {
|
|
764
|
-
return value.*;
|
|
765
|
-
}
|
|
766
|
-
inline fn hash(key: Key) u64 {
|
|
767
|
-
return key;
|
|
768
|
-
}
|
|
769
|
-
inline fn equal(a: Key, b: Key) bool {
|
|
770
|
-
return a == b;
|
|
771
|
-
}
|
|
772
|
-
};
|
|
773
|
-
|
|
774
|
-
const SAC = SetAssociativeCache(
|
|
775
|
-
Key,
|
|
776
|
-
Value,
|
|
777
|
-
context.key_from_value,
|
|
778
|
-
context.hash,
|
|
779
|
-
context.equal,
|
|
780
|
-
layout,
|
|
781
|
-
);
|
|
782
|
-
|
|
783
|
-
const reference = struct {
|
|
784
|
-
inline fn search_tags(tags: *[layout.ways]SAC.Tag, tag: SAC.Tag) SAC.Ways {
|
|
785
|
-
var bits: SAC.Ways = 0;
|
|
786
|
-
var count: usize = 0;
|
|
787
|
-
for (tags) |t, i| {
|
|
788
|
-
if (t == tag) {
|
|
789
|
-
const bit = @intCast(math.Log2Int(SAC.Ways), i);
|
|
790
|
-
bits |= (@as(SAC.Ways, 1) << bit);
|
|
791
|
-
count += 1;
|
|
792
|
-
}
|
|
793
|
-
}
|
|
794
|
-
assert(@popCount(SAC.Ways, bits) == count);
|
|
795
|
-
return bits;
|
|
796
|
-
}
|
|
797
|
-
};
|
|
798
|
-
|
|
799
|
-
return struct {
|
|
800
|
-
fn run(random: std.rand.Random) !void {
|
|
801
|
-
if (log) SAC.inspect();
|
|
802
|
-
|
|
803
|
-
var iterations: usize = 0;
|
|
804
|
-
while (iterations < 10_000) : (iterations += 1) {
|
|
805
|
-
var tags: [layout.ways]SAC.Tag = undefined;
|
|
806
|
-
random.bytes(mem.asBytes(&tags));
|
|
807
|
-
|
|
808
|
-
const tag = random.int(SAC.Tag);
|
|
809
|
-
|
|
810
|
-
var indexes: [layout.ways]usize = undefined;
|
|
811
|
-
for (indexes) |*x, i| x.* = i;
|
|
812
|
-
random.shuffle(usize, &indexes);
|
|
813
|
-
|
|
814
|
-
const matches_count_min = random.uintAtMostBiased(u32, layout.ways);
|
|
815
|
-
for (indexes[0..matches_count_min]) |index| {
|
|
816
|
-
tags[index] = tag;
|
|
817
|
-
}
|
|
818
|
-
|
|
819
|
-
const expected = reference.search_tags(&tags, tag);
|
|
820
|
-
const actual = SAC.search_tags(&tags, tag);
|
|
821
|
-
if (log) std.debug.print("expected: {b:0>16}, actual: {b:0>16}\n", .{
|
|
822
|
-
expected,
|
|
823
|
-
actual,
|
|
824
|
-
});
|
|
825
|
-
try testing.expectEqual(expected, actual);
|
|
826
|
-
}
|
|
827
|
-
}
|
|
828
|
-
};
|
|
829
|
-
}
|
|
830
|
-
|
|
831
|
-
test "SetAssociativeCache: search_tags()" {
|
|
832
|
-
const seed = 42;
|
|
833
|
-
|
|
834
|
-
const Key = u64;
|
|
835
|
-
const Value = u64;
|
|
836
|
-
|
|
837
|
-
var prng = std.rand.DefaultPrng.init(seed);
|
|
838
|
-
const random = prng.random();
|
|
839
|
-
|
|
840
|
-
inline for ([_]u64{ 2, 4, 16 }) |ways| {
|
|
841
|
-
inline for ([_]u64{ 8, 16 }) |tag_bits| {
|
|
842
|
-
const case = search_tags_test(Key, Value, .{
|
|
843
|
-
.ways = ways,
|
|
844
|
-
.tag_bits = tag_bits,
|
|
845
|
-
});
|
|
846
|
-
|
|
847
|
-
try case.run(random);
|
|
848
|
-
}
|
|
849
|
-
}
|
|
850
|
-
}
|