tigerbeetle-node 0.11.5 → 0.11.7

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.
Files changed (77) hide show
  1. package/dist/.client.node.sha256 +1 -1
  2. package/dist/index.d.ts +41 -42
  3. package/dist/index.js +41 -42
  4. package/dist/index.js.map +1 -1
  5. package/package.json +2 -2
  6. package/src/index.ts +0 -1
  7. package/src/tigerbeetle/scripts/benchmark.bat +7 -3
  8. package/src/tigerbeetle/scripts/benchmark.sh +2 -3
  9. package/src/tigerbeetle/scripts/install.bat +7 -0
  10. package/src/tigerbeetle/scripts/install.sh +2 -3
  11. package/src/tigerbeetle/src/benchmark.zig +3 -3
  12. package/src/tigerbeetle/src/config.zig +24 -3
  13. package/src/tigerbeetle/src/constants.zig +8 -5
  14. package/src/tigerbeetle/src/ewah.zig +6 -5
  15. package/src/tigerbeetle/src/ewah_fuzz.zig +1 -1
  16. package/src/tigerbeetle/src/io/darwin.zig +19 -0
  17. package/src/tigerbeetle/src/io/linux.zig +8 -0
  18. package/src/tigerbeetle/src/io/windows.zig +20 -2
  19. package/src/tigerbeetle/src/iops.zig +7 -1
  20. package/src/tigerbeetle/src/lsm/compaction.zig +27 -72
  21. package/src/tigerbeetle/src/lsm/forest_fuzz.zig +10 -11
  22. package/src/tigerbeetle/src/lsm/grid.zig +267 -267
  23. package/src/tigerbeetle/src/lsm/groove.zig +3 -0
  24. package/src/tigerbeetle/src/lsm/level_iterator.zig +18 -1
  25. package/src/tigerbeetle/src/lsm/manifest.zig +29 -1
  26. package/src/tigerbeetle/src/lsm/manifest_level.zig +1 -0
  27. package/src/tigerbeetle/src/lsm/manifest_log.zig +5 -5
  28. package/src/tigerbeetle/src/lsm/manifest_log_fuzz.zig +19 -11
  29. package/src/tigerbeetle/src/lsm/merge_iterator.zig +106 -0
  30. package/src/tigerbeetle/src/lsm/posted_groove.zig +1 -0
  31. package/src/tigerbeetle/src/lsm/segmented_array.zig +1 -0
  32. package/src/tigerbeetle/src/lsm/set_associative_cache.zig +26 -70
  33. package/src/tigerbeetle/src/lsm/table.zig +56 -0
  34. package/src/tigerbeetle/src/lsm/table_iterator.zig +29 -2
  35. package/src/tigerbeetle/src/lsm/table_mutable.zig +49 -15
  36. package/src/tigerbeetle/src/lsm/test.zig +10 -7
  37. package/src/tigerbeetle/src/lsm/tree.zig +27 -6
  38. package/src/tigerbeetle/src/lsm/tree_fuzz.zig +302 -263
  39. package/src/tigerbeetle/src/message_pool.zig +2 -1
  40. package/src/tigerbeetle/src/simulator.zig +22 -84
  41. package/src/tigerbeetle/src/{test/accounting → state_machine}/auditor.zig +8 -8
  42. package/src/tigerbeetle/src/{test/accounting → state_machine}/workload.zig +108 -48
  43. package/src/tigerbeetle/src/state_machine.zig +20 -14
  44. package/src/tigerbeetle/src/storage.zig +58 -6
  45. package/src/tigerbeetle/src/test/cluster.zig +14 -11
  46. package/src/tigerbeetle/src/test/conductor.zig +2 -3
  47. package/src/tigerbeetle/src/test/id.zig +10 -0
  48. package/src/tigerbeetle/src/test/state_checker.zig +1 -1
  49. package/src/tigerbeetle/src/test/state_machine.zig +151 -46
  50. package/src/tigerbeetle/src/test/storage.zig +22 -1
  51. package/src/tigerbeetle/src/tigerbeetle.zig +0 -1
  52. package/src/tigerbeetle/src/tracer.zig +50 -28
  53. package/src/tigerbeetle/src/unit_tests.zig +11 -6
  54. package/src/tigerbeetle/src/vopr.zig +4 -4
  55. package/src/tigerbeetle/src/vsr/client.zig +5 -5
  56. package/src/tigerbeetle/src/vsr/clock.zig +2 -2
  57. package/src/tigerbeetle/src/vsr/journal.zig +647 -537
  58. package/src/tigerbeetle/src/vsr/replica.zig +333 -333
  59. package/src/tigerbeetle/src/vsr/replica_format.zig +7 -4
  60. package/src/tigerbeetle/src/vsr/superblock.zig +87 -39
  61. package/src/tigerbeetle/src/vsr/superblock_free_set.zig +114 -93
  62. package/src/tigerbeetle/src/vsr/superblock_free_set_fuzz.zig +1 -1
  63. package/src/tigerbeetle/src/vsr/superblock_fuzz.zig +11 -8
  64. package/src/tigerbeetle/src/vsr/superblock_quorums_fuzz.zig +3 -3
  65. package/src/tigerbeetle/src/vsr.zig +60 -13
  66. package/src/tigerbeetle/src/c/tb_client/context.zig +0 -304
  67. package/src/tigerbeetle/src/c/tb_client/echo_client.zig +0 -108
  68. package/src/tigerbeetle/src/c/tb_client/packet.zig +0 -80
  69. package/src/tigerbeetle/src/c/tb_client/signal.zig +0 -286
  70. package/src/tigerbeetle/src/c/tb_client/thread.zig +0 -88
  71. package/src/tigerbeetle/src/c/tb_client.h +0 -221
  72. package/src/tigerbeetle/src/c/tb_client.zig +0 -177
  73. package/src/tigerbeetle/src/c/tb_client_header.zig +0 -218
  74. package/src/tigerbeetle/src/c/tb_client_header_test.zig +0 -135
  75. package/src/tigerbeetle/src/c/test.zig +0 -371
  76. package/src/tigerbeetle/src/cli.zig +0 -375
  77. package/src/tigerbeetle/src/main.zig +0 -245
@@ -131,6 +131,7 @@ fn IndexTreeType(
131
131
  Key.sentinel_key,
132
132
  Key.tombstone,
133
133
  Key.tombstone_from_key,
134
+ .secondary_index,
134
135
  );
135
136
 
136
137
  return TreeType(Table, Storage, tree_name);
@@ -254,6 +255,7 @@ pub fn GrooveType(
254
255
  ObjectTreeHelpers(Object).sentinel_key,
255
256
  ObjectTreeHelpers(Object).tombstone,
256
257
  ObjectTreeHelpers(Object).tombstone_from_key,
258
+ .general,
257
259
  );
258
260
 
259
261
  const tree_name = @typeName(Object);
@@ -269,6 +271,7 @@ pub fn GrooveType(
269
271
  IdTreeValue.sentinel_key,
270
272
  IdTreeValue.tombstone,
271
273
  IdTreeValue.tombstone_from_key,
274
+ .general,
272
275
  );
273
276
 
274
277
  const tree_name = @typeName(Object) ++ ".id";
@@ -68,6 +68,9 @@ pub fn LevelIteratorType(comptime Table: type, comptime Storage: type) type {
68
68
  values: ValuesRingBuffer,
69
69
  tables: TablesRingBuffer,
70
70
 
71
+ // Used for verifying key order when constants.verify == true.
72
+ key_prev: ?Key,
73
+
71
74
  pub fn init(allocator: mem.Allocator) !LevelIterator {
72
75
  var values = try ValuesRingBuffer.init(allocator);
73
76
  errdefer values.deinit(allocator);
@@ -96,6 +99,7 @@ pub fn LevelIteratorType(comptime Table: type, comptime Storage: type) type {
96
99
  .{ .table_iterator = table_b },
97
100
  },
98
101
  },
102
+ .key_prev = null,
99
103
  };
100
104
  }
101
105
 
@@ -138,6 +142,7 @@ pub fn LevelIteratorType(comptime Table: type, comptime Storage: type) type {
138
142
  .direction = context.direction,
139
143
  .values = .{ .buffer = it.values.buffer },
140
144
  .tables = .{ .buffer = it.tables.buffer },
145
+ .key_prev = null,
141
146
  };
142
147
 
143
148
  assert(it.key_exclusive == null);
@@ -281,7 +286,7 @@ pub fn LevelIteratorType(comptime Table: type, comptime Storage: type) type {
281
286
  /// Returns either:
282
287
  /// - the next Key, if available.
283
288
  /// - error.Empty when there are no values remaining to iterate.
284
- /// - error.Drained when the iterator isn't empty, but the values
289
+ /// - error.Drained when the iterator isn't empty, but the values
285
290
  /// still need to be buffered into memory via tick().
286
291
  pub fn peek(it: LevelIterator) error{ Empty, Drained }!Key {
287
292
  if (it.values.head_ptr_const()) |value| return key_from_value(value);
@@ -299,6 +304,18 @@ pub fn LevelIteratorType(comptime Table: type, comptime Storage: type) type {
299
304
 
300
305
  /// This may only be called after peek() returns a Key (and not Empty or Drained)
301
306
  pub fn pop(it: *LevelIterator) Value {
307
+ const value = it.pop_internal();
308
+
309
+ if (constants.verify) {
310
+ const key = Table.key_from_value(&value);
311
+ if (it.key_prev) |k| assert(Table.compare_keys(k, key) == .lt);
312
+ it.key_prev = key;
313
+ }
314
+
315
+ return value;
316
+ }
317
+
318
+ fn pop_internal(it: *LevelIterator) Value {
302
319
  if (it.values.pop()) |value| return value;
303
320
 
304
321
  const table_iterator = &it.tables.head_ptr().?.table_iterator;
@@ -531,7 +531,7 @@ pub fn ManifestType(comptime Table: type, comptime Storage: type) type {
531
531
  .ascending,
532
532
  KeyRange{ .key_min = range.key_min, .key_max = range.key_max },
533
533
  );
534
- if (it.next() == null) {
534
+ if (it.next() != null) {
535
535
  // If the range is being compacted into the last level then this is unreachable,
536
536
  // as the last level has no subsequent levels and must always drop tombstones.
537
537
  assert(level_b != constants.lsm_levels - 1);
@@ -585,5 +585,33 @@ pub fn ManifestType(comptime Table: type, comptime Storage: type) type {
585
585
  manifest.checkpoint_callback = null;
586
586
  callback(manifest);
587
587
  }
588
+
589
+ pub fn verify(manifest: *Manifest, snapshot: u64) void {
590
+ for (manifest.levels) |*level| {
591
+ var key_max_prev: ?Key = null;
592
+ var table_info_iter = level.iterator(
593
+ .visible,
594
+ &.{snapshot},
595
+ .ascending,
596
+ null,
597
+ );
598
+ while (table_info_iter.next()) |table_info| {
599
+ if (key_max_prev) |k| {
600
+ assert(compare_keys(k, table_info.key_min) == .lt);
601
+ }
602
+ // We could have key_min == key_max if there is only one value.
603
+ assert(compare_keys(table_info.key_min, table_info.key_max) != .gt);
604
+ key_max_prev = table_info.key_max;
605
+
606
+ Table.verify(
607
+ Storage,
608
+ manifest.manifest_log.grid.superblock.storage,
609
+ table_info.address,
610
+ table_info.key_min,
611
+ table_info.key_max,
612
+ );
613
+ }
614
+ }
615
+ }
588
616
  };
589
617
  }
@@ -417,6 +417,7 @@ pub fn TestContext(
417
417
  std.math.maxInt(Key),
418
418
  tombstone,
419
419
  tombstone_from_key,
420
+ .general,
420
421
  );
421
422
 
422
423
  const TableInfo = @import("manifest.zig").TableInfoType(Table);
@@ -404,14 +404,14 @@ pub fn ManifestLogType(comptime Storage: type, comptime TableInfo: type) type {
404
404
  return;
405
405
  }
406
406
 
407
- const block = manifest_log.blocks.head().?;
408
- verify_block(block, null, null);
407
+ const block = manifest_log.blocks.head_ptr().?;
408
+ verify_block(block.*, null, null);
409
409
 
410
- const header = mem.bytesAsValue(vsr.Header, block[0..@sizeOf(vsr.Header)]);
411
- const address = Block.address(block);
410
+ const header = mem.bytesAsValue(vsr.Header, block.*[0..@sizeOf(vsr.Header)]);
411
+ const address = Block.address(block.*);
412
412
  assert(address > 0);
413
413
 
414
- const entry_count = Block.entry_count(block);
414
+ const entry_count = Block.entry_count(block.*);
415
415
 
416
416
  if (manifest_log.blocks_closed == 1 and manifest_log.blocks.count == 1) {
417
417
  // This might be the last block of a checkpoint, which can be a partial block.
@@ -30,8 +30,6 @@ const fuzz = @import("../test/fuzz.zig");
30
30
 
31
31
  pub const tigerbeetle_config = @import("../config.zig").configs.test_min;
32
32
 
33
- const storage_size_max = data_file_size_min + constants.block_size * 1024;
34
-
35
33
  const entries_max_block = ManifestLog.Block.entry_count_max;
36
34
  const entries_max_buffered = entries_max_block *
37
35
  std.meta.fieldInfo(ManifestLog, .blocks).field_type.count_max;
@@ -66,20 +64,28 @@ fn run_fuzz(
66
64
  .write_latency_mean = 1 + random.uintLessThan(u64, 40),
67
65
  };
68
66
 
69
- var storage = try Storage.init(allocator, storage_size_max, storage_options);
67
+ var storage = try Storage.init(allocator, constants.storage_size_max, storage_options);
70
68
  defer storage.deinit(allocator);
71
69
 
72
- var storage_verify = try Storage.init(allocator, storage_size_max, storage_options);
70
+ var storage_verify = try Storage.init(allocator, constants.storage_size_max, storage_options);
73
71
  defer storage_verify.deinit(allocator);
74
72
 
75
73
  // The MessagePool is shared by both superblocks because they will not use it.
76
74
  var message_pool = try MessagePool.init(allocator, .replica);
77
75
  defer message_pool.deinit(allocator);
78
76
 
79
- var superblock = try SuperBlock.init(allocator, &storage, &message_pool);
77
+ var superblock = try SuperBlock.init(allocator, .{
78
+ .storage = &storage,
79
+ .storage_size_limit = constants.storage_size_max,
80
+ .message_pool = &message_pool,
81
+ });
80
82
  defer superblock.deinit(allocator);
81
83
 
82
- var superblock_verify = try SuperBlock.init(allocator, &storage_verify, &message_pool);
84
+ var superblock_verify = try SuperBlock.init(allocator, .{
85
+ .storage = &storage_verify,
86
+ .storage_size_limit = constants.storage_size_max,
87
+ .message_pool = &message_pool,
88
+ });
83
89
  defer superblock_verify.deinit(allocator);
84
90
 
85
91
  var grid = try Grid.init(allocator, &superblock);
@@ -313,7 +319,7 @@ const Environment = struct {
313
319
 
314
320
  fn wait(env: *Environment, manifest_log: *ManifestLog) void {
315
321
  while (env.pending > 0) {
316
- manifest_log.grid.tick();
322
+ // manifest_log.grid.tick();
317
323
  manifest_log.superblock.storage.tick();
318
324
  }
319
325
  }
@@ -324,7 +330,6 @@ const Environment = struct {
324
330
  env.manifest_log.superblock.format(format_superblock_callback, &env.superblock_context, .{
325
331
  .cluster = 0,
326
332
  .replica = 0,
327
- .size_max = storage_size_max,
328
333
  });
329
334
  }
330
335
 
@@ -453,8 +458,11 @@ const Environment = struct {
453
458
  test_superblock.deinit(env.allocator);
454
459
  test_superblock.* = try SuperBlock.init(
455
460
  env.allocator,
456
- test_storage,
457
- env.manifest_log.superblock.client_table.message_pool,
461
+ .{
462
+ .storage = test_storage,
463
+ .storage_size_limit = constants.storage_size_max,
464
+ .message_pool = env.manifest_log.superblock.client_table.message_pool,
465
+ },
458
466
  );
459
467
 
460
468
  test_grid.deinit(env.allocator);
@@ -627,7 +635,7 @@ fn verify_manifest_compaction_set(
627
635
  var compact_blocks_checked: u32 = 0;
628
636
 
629
637
  // This test doesn't include any actual table blocks, so all blocks are manifest blocks.
630
- var blocks = superblock.free_set.blocks.iterator(.{ .kind = .unset });
638
+ var blocks = superblock.free_set.blocks.iterator(.{ .kind = .set });
631
639
  while (blocks.next()) |block_index| {
632
640
  const block_address = block_index + 1;
633
641
  const block = superblock.storage.grid_block(block_address);
@@ -0,0 +1,106 @@
1
+ const std = @import("std");
2
+ const assert = std.debug.assert;
3
+
4
+ const util = @import("../util.zig");
5
+
6
+ const k_max = 2;
7
+
8
+ pub fn MergeIteratorType(
9
+ comptime Table: type,
10
+ comptime IteratorA: type,
11
+ comptime IteratorB: type,
12
+ ) type {
13
+ return struct {
14
+ const Self = @This();
15
+
16
+ iterator_a: *IteratorA,
17
+ iterator_b: *IteratorB,
18
+
19
+ empty_a: bool,
20
+ empty_b: bool,
21
+
22
+ previous_key_popped: ?Table.Key,
23
+
24
+ pub fn init(
25
+ iterator_a: *IteratorA,
26
+ iterator_b: *IteratorB,
27
+ ) Self {
28
+ return Self{
29
+ .iterator_a = iterator_a,
30
+ .iterator_b = iterator_b,
31
+ .empty_a = false,
32
+ .empty_b = false,
33
+ .previous_key_popped = null,
34
+ };
35
+ }
36
+
37
+ /// Returns `true` once both `iterator_a` and `iterator_b` have raised `error.Empty`.
38
+ pub fn empty(it: Self) bool {
39
+ return it.empty_a and it.empty_b;
40
+ }
41
+
42
+ /// Returns `null` if either `iterator_a` or `iterator_b` return `error.Empty` or `error.Drained`.
43
+ /// Check `it.empty()` to disambiguate.
44
+ pub fn pop(it: *Self) ?Table.Value {
45
+ while (true) {
46
+ if (it.empty_a and it.empty_b) {
47
+ return null;
48
+ }
49
+
50
+ if (it.empty_a) {
51
+ _ = it.iterator_b.peek() catch |error_b| switch (error_b) {
52
+ error.Drained => return null,
53
+ error.Empty => {
54
+ it.empty_b = true;
55
+ continue;
56
+ },
57
+ };
58
+ return it.iterator_b.pop();
59
+ }
60
+
61
+ if (it.empty_b) {
62
+ _ = it.iterator_a.peek() catch |error_a| switch (error_a) {
63
+ error.Drained => return null,
64
+ error.Empty => {
65
+ it.empty_a = true;
66
+ continue;
67
+ },
68
+ };
69
+ return it.iterator_a.pop();
70
+ }
71
+
72
+ const key_a = it.iterator_a.peek() catch |error_a| switch (error_a) {
73
+ error.Drained => return null,
74
+ error.Empty => {
75
+ it.empty_a = true;
76
+ continue;
77
+ },
78
+ };
79
+ const key_b = it.iterator_b.peek() catch |error_b| switch (error_b) {
80
+ error.Drained => return null,
81
+ error.Empty => {
82
+ it.empty_b = true;
83
+ continue;
84
+ },
85
+ };
86
+
87
+ switch (Table.compare_keys(key_a, key_b)) {
88
+ .lt => return it.iterator_a.pop(),
89
+ .gt => return it.iterator_b.pop(),
90
+ .eq => {
91
+ const value_a = it.iterator_a.pop();
92
+ const value_b = it.iterator_b.pop();
93
+ switch (Table.usage) {
94
+ .general => return value_a,
95
+ .secondary_index => {
96
+ // In secondary indexes, puts and removes alternate and can be safely cancelled out.
97
+ assert(Table.tombstone(&value_a) != Table.tombstone(&value_b));
98
+ continue;
99
+ },
100
+ }
101
+ },
102
+ }
103
+ }
104
+ }
105
+ };
106
+ }
@@ -67,6 +67,7 @@ pub fn PostedGrooveType(comptime Storage: type) type {
67
67
  Value.sentinel_key,
68
68
  Value.tombstone,
69
69
  Value.tombstone_from_key,
70
+ .general,
70
71
  );
71
72
 
72
73
  const Tree = TreeType(Table, Storage, "posted_groove");
@@ -1241,6 +1241,7 @@ pub fn run_tests(seed: u64, comptime options: Options) !void {
1241
1241
  Key.sentinel_key,
1242
1242
  Key.tombstone,
1243
1243
  Key.tombstone_from_key,
1244
+ .general,
1244
1245
  ));
1245
1246
 
1246
1247
  const CompareInt = struct {
@@ -188,21 +188,26 @@ pub fn SetAssociativeCache(
188
188
  mem.set(u64, self.clocks.words, 0);
189
189
  }
190
190
 
191
- /// Returns whether an entry with the given key is cached,
191
+ /// Returns whether an entry with the given key is cached,
192
192
  /// without modifying the entry's counter.
193
193
  pub fn exists(self: *Self, key: Key) bool {
194
194
  const set = self.associate(key);
195
195
  return self.search(set, key) != null;
196
196
  }
197
197
 
198
- pub fn get(self: *Self, key: Key) ?*align(value_alignment) Value {
198
+ pub fn get_index(self: *Self, key: Key) ?usize {
199
199
  const set = self.associate(key);
200
200
  const way = self.search(set, key) orelse return null;
201
201
 
202
202
  const count = self.counts.get(set.offset + way);
203
203
  self.counts.set(set.offset + way, count +| 1);
204
204
 
205
- return @alignCast(value_alignment, &set.values[way]);
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]);
206
211
  }
207
212
 
208
213
  /// Remove a key from the set associative cache if present.
@@ -211,6 +216,7 @@ pub fn SetAssociativeCache(
211
216
  const way = self.search(set, key) orelse return;
212
217
 
213
218
  self.counts.set(set.offset + way, 0);
219
+ set.values[way] = undefined;
214
220
  }
215
221
 
216
222
  /// Hint that the key is less likely to be accessed in the future, without actually removing
@@ -248,39 +254,21 @@ pub fn SetAssociativeCache(
248
254
  return @ptrCast(*const Ways, &result).*;
249
255
  }
250
256
 
251
- pub fn insert(self: *Self, key: Key) *align(value_alignment) Value {
252
- return self.insert_preserve_locked(
253
- void,
254
- struct {
255
- inline fn locked(_: void, _: *const Value) bool {
256
- return false;
257
- }
258
- }.locked,
259
- {},
260
- key,
261
- );
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);
262
260
  }
263
261
 
264
- /// Add a key, evicting an older entry if needed, and return a pointer to the value.
265
- /// The key must not already be in the cache.
266
- /// Never evicts keys for which locked() returns true.
267
- /// The caller must guarantee that locked() returns true for less than layout.ways keys.
268
- pub fn insert_preserve_locked(
269
- self: *Self,
270
- comptime Context: type,
271
- comptime locked: fn (
272
- Context,
273
- *align(value_alignment) const Value,
274
- ) callconv(.Inline) bool,
275
- context: Context,
276
- key: Key,
277
- ) *align(value_alignment) Value {
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);
278
266
  const set = self.associate(key);
279
267
  if (self.search(set, key)) |way| {
280
- // Remove the old entry for this key.
281
- // It should be a different value, but since we are returning a value pointer we
282
- // can't check against the new one.
283
- self.counts.set(set.offset + way, 0);
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;
284
272
  }
285
273
 
286
274
  const clock_index = @divExact(set.offset, layout.ways);
@@ -299,11 +287,6 @@ pub fn SetAssociativeCache(
299
287
  safety_count += 1;
300
288
  way +%= 1;
301
289
  }) {
302
- // We pass a value pointer to the callback here so that a cache miss
303
- // can be avoided if the caller is able to determine if the value is
304
- // locked by comparing pointers directly.
305
- if (locked(context, @alignCast(value_alignment, &set.values[way]))) continue;
306
-
307
290
  var count = self.counts.get(set.offset + way);
308
291
  if (count == 0) break; // Way is already free.
309
292
 
@@ -316,10 +299,11 @@ pub fn SetAssociativeCache(
316
299
  assert(self.counts.get(set.offset + way) == 0);
317
300
 
318
301
  set.tags[way] = set.tag;
302
+ set.values[way] = value.*;
319
303
  self.counts.set(set.offset + way, 1);
320
304
  self.clocks.set(clock_index, way +% 1);
321
305
 
322
- return @alignCast(value_alignment, &set.values[way]);
306
+ return set.offset + way;
323
307
  }
324
308
 
325
309
  const Set = struct {
@@ -430,7 +414,7 @@ fn set_associative_cache_test(
430
414
  try expectEqual(i, sac.clocks.get(0));
431
415
 
432
416
  const key = i * sac.sets;
433
- sac.insert(key).* = key;
417
+ sac.insert(&key);
434
418
  try expect(sac.counts.get(i) == 1);
435
419
  try expectEqual(key, sac.get(key).?.*);
436
420
  try expect(sac.counts.get(i) == 2);
@@ -443,7 +427,7 @@ fn set_associative_cache_test(
443
427
  // Insert another element into the first set, causing key 0 to be evicted.
444
428
  {
445
429
  const key = layout.ways * sac.sets;
446
- sac.insert(key).* = key;
430
+ sac.insert(&key);
447
431
  try expect(sac.counts.get(0) == 1);
448
432
  try expectEqual(key, sac.get(key).?.*);
449
433
  try expect(sac.counts.get(0) == 2);
@@ -460,34 +444,6 @@ fn set_associative_cache_test(
460
444
 
461
445
  if (log) sac.associate(0).inspect(sac);
462
446
 
463
- // Lock all other slots, causing key layout.ways * sac.sets to be evicted despite having the
464
- // highest count.
465
- {
466
- {
467
- assert(sac.counts.get(0) == 2);
468
- var i: usize = 1;
469
- while (i < layout.ways) : (i += 1) assert(sac.counts.get(i) == 1);
470
- }
471
-
472
- const key = (layout.ways + 1) * sac.sets;
473
- const expect_evicted = layout.ways * sac.sets;
474
-
475
- sac.insert_preserve_locked(
476
- u64,
477
- struct {
478
- inline fn locked(only_unlocked: u64, value: *const Value) bool {
479
- return value.* != only_unlocked;
480
- }
481
- }.locked,
482
- expect_evicted,
483
- key,
484
- ).* = key;
485
-
486
- try expectEqual(@as(?*Value, null), sac.get(expect_evicted));
487
- }
488
-
489
- if (log) sac.associate(0).inspect(sac);
490
-
491
447
  // Ensure removal works.
492
448
  {
493
449
  const key = 5 * sac.sets;
@@ -512,7 +468,7 @@ fn set_associative_cache_test(
512
468
  try expectEqual(i, sac.clocks.get(0));
513
469
 
514
470
  const key = i * sac.sets;
515
- sac.insert(key).* = key;
471
+ sac.insert(&key);
516
472
  try expect(sac.counts.get(i) == 1);
517
473
  var j: usize = 2;
518
474
  while (j <= math.maxInt(SAC.Count)) : (j += 1) {
@@ -530,7 +486,7 @@ fn set_associative_cache_test(
530
486
  // Insert another element into the first set, causing key 0 to be evicted.
531
487
  {
532
488
  const key = layout.ways * sac.sets;
533
- sac.insert(key).* = key;
489
+ sac.insert(&key);
534
490
  try expect(sac.counts.get(0) == 1);
535
491
  try expectEqual(key, sac.get(key).?.*);
536
492
  try expect(sac.counts.get(0) == 2);
@@ -16,6 +16,17 @@ const snapshot_latest = @import("tree.zig").snapshot_latest;
16
16
  const BlockType = @import("grid.zig").BlockType;
17
17
  const TableInfoType = @import("manifest.zig").TableInfoType;
18
18
 
19
+ pub const TableUsage = enum {
20
+ /// General purpose table.
21
+ general,
22
+ /// If your usage fits this pattern:
23
+ /// * Only put keys which are not present.
24
+ /// * Only remove keys which are present.
25
+ /// * TableKey == TableValue (modulo padding, eg CompositeKey)
26
+ /// Then we can unlock additional optimizations.
27
+ secondary_index,
28
+ };
29
+
19
30
  /// A table is a set of blocks:
20
31
  ///
21
32
  /// * Index block (exactly 1)
@@ -69,6 +80,7 @@ pub fn TableType(
69
80
  comptime table_tombstone: fn (*const TableValue) callconv(.Inline) bool,
70
81
  /// Returns a tombstone value representation for a key.
71
82
  comptime table_tombstone_from_key: fn (TableKey) callconv(.Inline) TableValue,
83
+ comptime usage: TableUsage,
72
84
  ) type {
73
85
  return struct {
74
86
  const Table = @This();
@@ -81,6 +93,7 @@ pub fn TableType(
81
93
  pub const sentinel_key = table_sentinel_key;
82
94
  pub const tombstone = table_tombstone;
83
95
  pub const tombstone_from_key = table_tombstone_from_key;
96
+ pub const usage = usage;
84
97
 
85
98
  // Export hashmap context for Key and Value
86
99
  pub const HashMapContextValue = struct {
@@ -616,6 +629,11 @@ pub fn TableType(
616
629
  assert(compare_keys(builder.key_min, builder.key_max) == .lt);
617
630
  }
618
631
 
632
+ if (current > 0) {
633
+ const key_max_prev = index_data_keys(builder.index_block)[current - 1];
634
+ assert(compare_keys(key_max_prev, key_from_value(&values[0])) == .lt);
635
+ }
636
+
619
637
  builder.data_block_count += 1;
620
638
  builder.value = 0;
621
639
 
@@ -966,6 +984,43 @@ pub fn TableType(
966
984
 
967
985
  return null;
968
986
  }
987
+
988
+ pub fn verify(
989
+ comptime Storage: type,
990
+ storage: *Storage,
991
+ index_address: u64,
992
+ key_min: ?Key,
993
+ key_max: ?Key,
994
+ ) void {
995
+ if (Storage != @import("../test/storage.zig").Storage)
996
+ // Too complicated to do async verification
997
+ return;
998
+
999
+ const index_block = storage.grid_block(index_address);
1000
+ const addresses = index_data_addresses(index_block);
1001
+ const data_blocks_used = index_data_blocks_used(index_block);
1002
+ var data_block_index: usize = 0;
1003
+ while (data_block_index < data_blocks_used) : (data_block_index += 1) {
1004
+ const address = addresses[data_block_index];
1005
+ const data_block = storage.grid_block(address);
1006
+ const values = data_block_values_used(data_block);
1007
+ if (values.len > 0) {
1008
+ if (data_block_index == 0) {
1009
+ assert(key_min == null or
1010
+ compare_keys(key_min.?, key_from_value(&values[0])) == .eq);
1011
+ }
1012
+ if (data_block_index == data_blocks_used - 1) {
1013
+ assert(key_max == null or
1014
+ compare_keys(key_from_value(&values[values.len - 1]), key_max.?) == .eq);
1015
+ }
1016
+ var a = &values[0];
1017
+ for (values[1..]) |*b| {
1018
+ assert(compare_keys(key_from_value(a), key_from_value(b)) == .lt);
1019
+ a = b;
1020
+ }
1021
+ }
1022
+ }
1023
+ }
969
1024
  };
970
1025
  }
971
1026
 
@@ -980,6 +1035,7 @@ test "Table" {
980
1035
  Key.sentinel_key,
981
1036
  Key.tombstone,
982
1037
  Key.tombstone_from_key,
1038
+ .general,
983
1039
  );
984
1040
 
985
1041
  _ = Table;