tigerbeetle 0.0.34 → 0.0.37
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +10 -0
- data/ext/tb_client/extconf.rb +13 -13
- data/ext/tb_client/tigerbeetle/LICENSE +177 -0
- data/ext/tb_client/tigerbeetle/build.zig +2327 -0
- data/ext/tb_client/tigerbeetle/src/aof.zig +1000 -0
- data/ext/tb_client/tigerbeetle/src/build_multiversion.zig +808 -0
- data/ext/tb_client/tigerbeetle/src/cdc/amqp/protocol.zig +1283 -0
- data/ext/tb_client/tigerbeetle/src/cdc/amqp/spec.zig +1704 -0
- data/ext/tb_client/tigerbeetle/src/cdc/amqp/types.zig +341 -0
- data/ext/tb_client/tigerbeetle/src/cdc/amqp.zig +1450 -0
- data/ext/tb_client/tigerbeetle/src/cdc/runner.zig +1659 -0
- data/ext/tb_client/tigerbeetle/src/clients/c/samples/main.c +406 -0
- data/ext/tb_client/tigerbeetle/src/clients/c/tb_client/context.zig +1084 -0
- data/ext/tb_client/tigerbeetle/src/clients/c/tb_client/echo_client.zig +286 -0
- data/ext/tb_client/tigerbeetle/src/clients/c/tb_client/packet.zig +158 -0
- data/ext/tb_client/tigerbeetle/src/clients/c/tb_client/signal.zig +229 -0
- data/ext/tb_client/tigerbeetle/src/clients/c/tb_client/signal_fuzz.zig +110 -0
- data/ext/tb_client/tigerbeetle/src/clients/c/tb_client.h +386 -0
- data/ext/tb_client/tigerbeetle/src/clients/c/tb_client.zig +34 -0
- data/ext/tb_client/tigerbeetle/src/clients/c/tb_client_exports.zig +281 -0
- data/ext/tb_client/tigerbeetle/src/clients/c/tb_client_header.zig +312 -0
- data/ext/tb_client/tigerbeetle/src/clients/c/tb_client_header_test.zig +138 -0
- data/ext/tb_client/tigerbeetle/src/clients/c/test.zig +466 -0
- data/ext/tb_client/tigerbeetle/src/clients/docs_samples.zig +157 -0
- data/ext/tb_client/tigerbeetle/src/clients/docs_types.zig +90 -0
- data/ext/tb_client/tigerbeetle/src/clients/dotnet/ci.zig +203 -0
- data/ext/tb_client/tigerbeetle/src/clients/dotnet/docs.zig +79 -0
- data/ext/tb_client/tigerbeetle/src/clients/dotnet/dotnet_bindings.zig +542 -0
- data/ext/tb_client/tigerbeetle/src/clients/go/ci.zig +109 -0
- data/ext/tb_client/tigerbeetle/src/clients/go/docs.zig +86 -0
- data/ext/tb_client/tigerbeetle/src/clients/go/go_bindings.zig +370 -0
- data/ext/tb_client/tigerbeetle/src/clients/go/pkg/native/tb_client.h +386 -0
- data/ext/tb_client/tigerbeetle/src/clients/java/ci.zig +167 -0
- data/ext/tb_client/tigerbeetle/src/clients/java/docs.zig +126 -0
- data/ext/tb_client/tigerbeetle/src/clients/java/java_bindings.zig +996 -0
- data/ext/tb_client/tigerbeetle/src/clients/java/src/client.zig +748 -0
- data/ext/tb_client/tigerbeetle/src/clients/java/src/jni.zig +3238 -0
- data/ext/tb_client/tigerbeetle/src/clients/java/src/jni_tests.zig +1718 -0
- data/ext/tb_client/tigerbeetle/src/clients/java/src/jni_thread_cleaner.zig +190 -0
- data/ext/tb_client/tigerbeetle/src/clients/node/ci.zig +104 -0
- data/ext/tb_client/tigerbeetle/src/clients/node/docs.zig +75 -0
- data/ext/tb_client/tigerbeetle/src/clients/node/node.zig +522 -0
- data/ext/tb_client/tigerbeetle/src/clients/node/node_bindings.zig +267 -0
- data/ext/tb_client/tigerbeetle/src/clients/node/src/c.zig +3 -0
- data/ext/tb_client/tigerbeetle/src/clients/node/src/translate.zig +379 -0
- data/ext/tb_client/tigerbeetle/src/clients/python/ci.zig +131 -0
- data/ext/tb_client/tigerbeetle/src/clients/python/docs.zig +63 -0
- data/ext/tb_client/tigerbeetle/src/clients/python/python_bindings.zig +588 -0
- data/ext/tb_client/tigerbeetle/src/clients/rust/assets/tb_client.h +386 -0
- data/ext/tb_client/tigerbeetle/src/clients/rust/ci.zig +73 -0
- data/ext/tb_client/tigerbeetle/src/clients/rust/docs.zig +106 -0
- data/ext/tb_client/tigerbeetle/src/clients/rust/rust_bindings.zig +305 -0
- data/ext/tb_client/tigerbeetle/src/config.zig +296 -0
- data/ext/tb_client/tigerbeetle/src/constants.zig +790 -0
- data/ext/tb_client/tigerbeetle/src/copyhound.zig +202 -0
- data/ext/tb_client/tigerbeetle/src/counting_allocator.zig +72 -0
- data/ext/tb_client/tigerbeetle/src/direction.zig +11 -0
- data/ext/tb_client/tigerbeetle/src/docs_website/build.zig +158 -0
- data/ext/tb_client/tigerbeetle/src/docs_website/src/content.zig +156 -0
- data/ext/tb_client/tigerbeetle/src/docs_website/src/docs.zig +252 -0
- data/ext/tb_client/tigerbeetle/src/docs_website/src/file_checker.zig +313 -0
- data/ext/tb_client/tigerbeetle/src/docs_website/src/html.zig +87 -0
- data/ext/tb_client/tigerbeetle/src/docs_website/src/page_writer.zig +63 -0
- data/ext/tb_client/tigerbeetle/src/docs_website/src/redirects.zig +47 -0
- data/ext/tb_client/tigerbeetle/src/docs_website/src/search_index_writer.zig +28 -0
- data/ext/tb_client/tigerbeetle/src/docs_website/src/service_worker_writer.zig +61 -0
- data/ext/tb_client/tigerbeetle/src/docs_website/src/single_page_writer.zig +169 -0
- data/ext/tb_client/tigerbeetle/src/docs_website/src/website.zig +46 -0
- data/ext/tb_client/tigerbeetle/src/ewah.zig +445 -0
- data/ext/tb_client/tigerbeetle/src/ewah_benchmark.zig +128 -0
- data/ext/tb_client/tigerbeetle/src/ewah_fuzz.zig +171 -0
- data/ext/tb_client/tigerbeetle/src/fuzz_tests.zig +179 -0
- data/ext/tb_client/tigerbeetle/src/integration_tests.zig +662 -0
- data/ext/tb_client/tigerbeetle/src/io/common.zig +155 -0
- data/ext/tb_client/tigerbeetle/src/io/darwin.zig +1093 -0
- data/ext/tb_client/tigerbeetle/src/io/linux.zig +1880 -0
- data/ext/tb_client/tigerbeetle/src/io/test.zig +1005 -0
- data/ext/tb_client/tigerbeetle/src/io/windows.zig +1598 -0
- data/ext/tb_client/tigerbeetle/src/io.zig +34 -0
- data/ext/tb_client/tigerbeetle/src/iops.zig +134 -0
- data/ext/tb_client/tigerbeetle/src/list.zig +236 -0
- data/ext/tb_client/tigerbeetle/src/lsm/binary_search.zig +848 -0
- data/ext/tb_client/tigerbeetle/src/lsm/binary_search_benchmark.zig +179 -0
- data/ext/tb_client/tigerbeetle/src/lsm/cache_map.zig +424 -0
- data/ext/tb_client/tigerbeetle/src/lsm/cache_map_fuzz.zig +420 -0
- data/ext/tb_client/tigerbeetle/src/lsm/compaction.zig +2117 -0
- data/ext/tb_client/tigerbeetle/src/lsm/composite_key.zig +182 -0
- data/ext/tb_client/tigerbeetle/src/lsm/forest.zig +1119 -0
- data/ext/tb_client/tigerbeetle/src/lsm/forest_fuzz.zig +1102 -0
- data/ext/tb_client/tigerbeetle/src/lsm/forest_table_iterator.zig +200 -0
- data/ext/tb_client/tigerbeetle/src/lsm/groove.zig +1495 -0
- data/ext/tb_client/tigerbeetle/src/lsm/k_way_merge.zig +739 -0
- data/ext/tb_client/tigerbeetle/src/lsm/k_way_merge_benchmark.zig +166 -0
- data/ext/tb_client/tigerbeetle/src/lsm/manifest.zig +754 -0
- data/ext/tb_client/tigerbeetle/src/lsm/manifest_level.zig +1294 -0
- data/ext/tb_client/tigerbeetle/src/lsm/manifest_level_fuzz.zig +510 -0
- data/ext/tb_client/tigerbeetle/src/lsm/manifest_log.zig +1263 -0
- data/ext/tb_client/tigerbeetle/src/lsm/manifest_log_fuzz.zig +628 -0
- data/ext/tb_client/tigerbeetle/src/lsm/node_pool.zig +247 -0
- data/ext/tb_client/tigerbeetle/src/lsm/scan_buffer.zig +116 -0
- data/ext/tb_client/tigerbeetle/src/lsm/scan_builder.zig +543 -0
- data/ext/tb_client/tigerbeetle/src/lsm/scan_fuzz.zig +938 -0
- data/ext/tb_client/tigerbeetle/src/lsm/scan_lookup.zig +293 -0
- data/ext/tb_client/tigerbeetle/src/lsm/scan_merge.zig +362 -0
- data/ext/tb_client/tigerbeetle/src/lsm/scan_range.zig +99 -0
- data/ext/tb_client/tigerbeetle/src/lsm/scan_state.zig +17 -0
- data/ext/tb_client/tigerbeetle/src/lsm/scan_tree.zig +1036 -0
- data/ext/tb_client/tigerbeetle/src/lsm/schema.zig +617 -0
- data/ext/tb_client/tigerbeetle/src/lsm/scratch_memory.zig +84 -0
- data/ext/tb_client/tigerbeetle/src/lsm/segmented_array.zig +1500 -0
- data/ext/tb_client/tigerbeetle/src/lsm/segmented_array_benchmark.zig +149 -0
- data/ext/tb_client/tigerbeetle/src/lsm/segmented_array_fuzz.zig +7 -0
- data/ext/tb_client/tigerbeetle/src/lsm/set_associative_cache.zig +865 -0
- data/ext/tb_client/tigerbeetle/src/lsm/table.zig +607 -0
- data/ext/tb_client/tigerbeetle/src/lsm/table_memory.zig +843 -0
- data/ext/tb_client/tigerbeetle/src/lsm/table_value_iterator.zig +105 -0
- data/ext/tb_client/tigerbeetle/src/lsm/timestamp_range.zig +40 -0
- data/ext/tb_client/tigerbeetle/src/lsm/tree.zig +630 -0
- data/ext/tb_client/tigerbeetle/src/lsm/tree_fuzz.zig +933 -0
- data/ext/tb_client/tigerbeetle/src/lsm/zig_zag_merge.zig +557 -0
- data/ext/tb_client/tigerbeetle/src/message_buffer.zig +469 -0
- data/ext/tb_client/tigerbeetle/src/message_bus.zig +1214 -0
- data/ext/tb_client/tigerbeetle/src/message_bus_fuzz.zig +936 -0
- data/ext/tb_client/tigerbeetle/src/message_pool.zig +343 -0
- data/ext/tb_client/tigerbeetle/src/multiversion.zig +2195 -0
- data/ext/tb_client/tigerbeetle/src/queue.zig +390 -0
- data/ext/tb_client/tigerbeetle/src/repl/completion.zig +201 -0
- data/ext/tb_client/tigerbeetle/src/repl/parser.zig +1356 -0
- data/ext/tb_client/tigerbeetle/src/repl/terminal.zig +496 -0
- data/ext/tb_client/tigerbeetle/src/repl.zig +1034 -0
- data/ext/tb_client/tigerbeetle/src/scripts/amqp.zig +973 -0
- data/ext/tb_client/tigerbeetle/src/scripts/cfo.zig +1866 -0
- data/ext/tb_client/tigerbeetle/src/scripts/changelog.zig +304 -0
- data/ext/tb_client/tigerbeetle/src/scripts/ci.zig +227 -0
- data/ext/tb_client/tigerbeetle/src/scripts/client_readmes.zig +658 -0
- data/ext/tb_client/tigerbeetle/src/scripts/devhub.zig +466 -0
- data/ext/tb_client/tigerbeetle/src/scripts/release.zig +1058 -0
- data/ext/tb_client/tigerbeetle/src/scripts.zig +105 -0
- data/ext/tb_client/tigerbeetle/src/shell.zig +1195 -0
- data/ext/tb_client/tigerbeetle/src/stack.zig +260 -0
- data/ext/tb_client/tigerbeetle/src/state_machine/auditor.zig +911 -0
- data/ext/tb_client/tigerbeetle/src/state_machine/workload.zig +2079 -0
- data/ext/tb_client/tigerbeetle/src/state_machine.zig +4872 -0
- data/ext/tb_client/tigerbeetle/src/state_machine_fuzz.zig +288 -0
- data/ext/tb_client/tigerbeetle/src/state_machine_tests.zig +3128 -0
- data/ext/tb_client/tigerbeetle/src/static_allocator.zig +82 -0
- data/ext/tb_client/tigerbeetle/src/stdx/bit_set.zig +157 -0
- data/ext/tb_client/tigerbeetle/src/stdx/bounded_array.zig +292 -0
- data/ext/tb_client/tigerbeetle/src/stdx/debug.zig +65 -0
- data/ext/tb_client/tigerbeetle/src/stdx/flags.zig +1414 -0
- data/ext/tb_client/tigerbeetle/src/stdx/mlock.zig +92 -0
- data/ext/tb_client/tigerbeetle/src/stdx/prng.zig +677 -0
- data/ext/tb_client/tigerbeetle/src/stdx/radix.zig +336 -0
- data/ext/tb_client/tigerbeetle/src/stdx/ring_buffer.zig +511 -0
- data/ext/tb_client/tigerbeetle/src/stdx/sort_test.zig +112 -0
- data/ext/tb_client/tigerbeetle/src/stdx/stdx.zig +1160 -0
- data/ext/tb_client/tigerbeetle/src/stdx/testing/low_level_hash_vectors.zig +142 -0
- data/ext/tb_client/tigerbeetle/src/stdx/testing/snaptest.zig +361 -0
- data/ext/tb_client/tigerbeetle/src/stdx/time_units.zig +275 -0
- data/ext/tb_client/tigerbeetle/src/stdx/unshare.zig +295 -0
- data/ext/tb_client/tigerbeetle/src/stdx/vendored/aegis.zig +436 -0
- data/ext/tb_client/tigerbeetle/src/stdx/windows.zig +48 -0
- data/ext/tb_client/tigerbeetle/src/stdx/zipfian.zig +402 -0
- data/ext/tb_client/tigerbeetle/src/storage.zig +489 -0
- data/ext/tb_client/tigerbeetle/src/storage_fuzz.zig +180 -0
- data/ext/tb_client/tigerbeetle/src/testing/bench.zig +146 -0
- data/ext/tb_client/tigerbeetle/src/testing/cluster/grid_checker.zig +53 -0
- data/ext/tb_client/tigerbeetle/src/testing/cluster/journal_checker.zig +61 -0
- data/ext/tb_client/tigerbeetle/src/testing/cluster/manifest_checker.zig +76 -0
- data/ext/tb_client/tigerbeetle/src/testing/cluster/message_bus.zig +110 -0
- data/ext/tb_client/tigerbeetle/src/testing/cluster/network.zig +412 -0
- data/ext/tb_client/tigerbeetle/src/testing/cluster/state_checker.zig +331 -0
- data/ext/tb_client/tigerbeetle/src/testing/cluster/storage_checker.zig +458 -0
- data/ext/tb_client/tigerbeetle/src/testing/cluster.zig +1198 -0
- data/ext/tb_client/tigerbeetle/src/testing/exhaustigen.zig +128 -0
- data/ext/tb_client/tigerbeetle/src/testing/fixtures.zig +181 -0
- data/ext/tb_client/tigerbeetle/src/testing/fuzz.zig +144 -0
- data/ext/tb_client/tigerbeetle/src/testing/id.zig +97 -0
- data/ext/tb_client/tigerbeetle/src/testing/io.zig +317 -0
- data/ext/tb_client/tigerbeetle/src/testing/marks.zig +126 -0
- data/ext/tb_client/tigerbeetle/src/testing/packet_simulator.zig +533 -0
- data/ext/tb_client/tigerbeetle/src/testing/reply_sequence.zig +154 -0
- data/ext/tb_client/tigerbeetle/src/testing/state_machine.zig +389 -0
- data/ext/tb_client/tigerbeetle/src/testing/storage.zig +1247 -0
- data/ext/tb_client/tigerbeetle/src/testing/table.zig +249 -0
- data/ext/tb_client/tigerbeetle/src/testing/time.zig +98 -0
- data/ext/tb_client/tigerbeetle/src/testing/tmp_tigerbeetle.zig +212 -0
- data/ext/tb_client/tigerbeetle/src/testing/vortex/constants.zig +26 -0
- data/ext/tb_client/tigerbeetle/src/testing/vortex/faulty_network.zig +580 -0
- data/ext/tb_client/tigerbeetle/src/testing/vortex/java_driver/ci.zig +39 -0
- data/ext/tb_client/tigerbeetle/src/testing/vortex/logged_process.zig +214 -0
- data/ext/tb_client/tigerbeetle/src/testing/vortex/rust_driver/ci.zig +34 -0
- data/ext/tb_client/tigerbeetle/src/testing/vortex/supervisor.zig +766 -0
- data/ext/tb_client/tigerbeetle/src/testing/vortex/workload.zig +543 -0
- data/ext/tb_client/tigerbeetle/src/testing/vortex/zig_driver.zig +181 -0
- data/ext/tb_client/tigerbeetle/src/tidy.zig +1448 -0
- data/ext/tb_client/tigerbeetle/src/tigerbeetle/benchmark_driver.zig +227 -0
- data/ext/tb_client/tigerbeetle/src/tigerbeetle/benchmark_load.zig +1069 -0
- data/ext/tb_client/tigerbeetle/src/tigerbeetle/cli.zig +1422 -0
- data/ext/tb_client/tigerbeetle/src/tigerbeetle/inspect.zig +1658 -0
- data/ext/tb_client/tigerbeetle/src/tigerbeetle/inspect_integrity.zig +518 -0
- data/ext/tb_client/tigerbeetle/src/tigerbeetle/libtb_client.zig +36 -0
- data/ext/tb_client/tigerbeetle/src/tigerbeetle/main.zig +646 -0
- data/ext/tb_client/tigerbeetle/src/tigerbeetle.zig +958 -0
- data/ext/tb_client/tigerbeetle/src/time.zig +236 -0
- data/ext/tb_client/tigerbeetle/src/trace/event.zig +745 -0
- data/ext/tb_client/tigerbeetle/src/trace/statsd.zig +462 -0
- data/ext/tb_client/tigerbeetle/src/trace.zig +556 -0
- data/ext/tb_client/tigerbeetle/src/unit_tests.zig +321 -0
- data/ext/tb_client/tigerbeetle/src/vopr.zig +1785 -0
- data/ext/tb_client/tigerbeetle/src/vortex.zig +101 -0
- data/ext/tb_client/tigerbeetle/src/vsr/checkpoint_trailer.zig +473 -0
- data/ext/tb_client/tigerbeetle/src/vsr/checksum.zig +208 -0
- data/ext/tb_client/tigerbeetle/src/vsr/checksum_benchmark.zig +43 -0
- data/ext/tb_client/tigerbeetle/src/vsr/client.zig +768 -0
- data/ext/tb_client/tigerbeetle/src/vsr/client_replies.zig +532 -0
- data/ext/tb_client/tigerbeetle/src/vsr/client_sessions.zig +338 -0
- data/ext/tb_client/tigerbeetle/src/vsr/clock.zig +1019 -0
- data/ext/tb_client/tigerbeetle/src/vsr/fault_detector.zig +279 -0
- data/ext/tb_client/tigerbeetle/src/vsr/free_set.zig +1381 -0
- data/ext/tb_client/tigerbeetle/src/vsr/free_set_fuzz.zig +315 -0
- data/ext/tb_client/tigerbeetle/src/vsr/grid.zig +1460 -0
- data/ext/tb_client/tigerbeetle/src/vsr/grid_blocks_missing.zig +757 -0
- data/ext/tb_client/tigerbeetle/src/vsr/grid_scrubber.zig +797 -0
- data/ext/tb_client/tigerbeetle/src/vsr/journal.zig +2586 -0
- data/ext/tb_client/tigerbeetle/src/vsr/marzullo.zig +308 -0
- data/ext/tb_client/tigerbeetle/src/vsr/message_header.zig +1777 -0
- data/ext/tb_client/tigerbeetle/src/vsr/multi_batch.zig +715 -0
- data/ext/tb_client/tigerbeetle/src/vsr/multi_batch_fuzz.zig +185 -0
- data/ext/tb_client/tigerbeetle/src/vsr/repair_budget.zig +333 -0
- data/ext/tb_client/tigerbeetle/src/vsr/replica.zig +12355 -0
- data/ext/tb_client/tigerbeetle/src/vsr/replica_format.zig +416 -0
- data/ext/tb_client/tigerbeetle/src/vsr/replica_reformat.zig +165 -0
- data/ext/tb_client/tigerbeetle/src/vsr/replica_test.zig +2910 -0
- data/ext/tb_client/tigerbeetle/src/vsr/routing.zig +1075 -0
- data/ext/tb_client/tigerbeetle/src/vsr/superblock.zig +1603 -0
- data/ext/tb_client/tigerbeetle/src/vsr/superblock_fuzz.zig +484 -0
- data/ext/tb_client/tigerbeetle/src/vsr/superblock_quorums.zig +405 -0
- data/ext/tb_client/tigerbeetle/src/vsr/superblock_quorums_fuzz.zig +355 -0
- data/ext/tb_client/tigerbeetle/src/vsr/sync.zig +29 -0
- data/ext/tb_client/tigerbeetle/src/vsr.zig +1727 -0
- data/lib/tb_client/shared_lib.rb +12 -5
- data/lib/tigerbeetle/client.rb +1 -1
- data/lib/tigerbeetle/platforms.rb +9 -0
- data/lib/tigerbeetle/version.rb +2 -2
- data/tigerbeetle.gemspec +22 -5
- metadata +242 -3
- data/ext/tb_client/pkg.tar.gz +0 -0
|
@@ -0,0 +1,1500 @@
|
|
|
1
|
+
const std = @import("std");
|
|
2
|
+
|
|
3
|
+
const assert = std.debug.assert;
|
|
4
|
+
const math = std.math;
|
|
5
|
+
const mem = std.mem;
|
|
6
|
+
|
|
7
|
+
const stdx = @import("stdx");
|
|
8
|
+
const div_ceil = stdx.div_ceil;
|
|
9
|
+
const binary_search_values_upsert_index =
|
|
10
|
+
@import("binary_search.zig").binary_search_values_upsert_index;
|
|
11
|
+
const binary_search_keys = @import("binary_search.zig").binary_search_keys;
|
|
12
|
+
const Direction = @import("../direction.zig").Direction;
|
|
13
|
+
|
|
14
|
+
/// A "segmented array" is an array with efficient (amortized) random-insert/remove operations.
|
|
15
|
+
/// Also known as an "unrolled linked list": https://en.wikipedia.org/wiki/Unrolled_linked_list
|
|
16
|
+
///
|
|
17
|
+
/// The structure consists of an array list of "nodes". Each node is a non-empty array of T.
|
|
18
|
+
/// When a node fills, it is split into two adjacent, partially-full nodes.
|
|
19
|
+
/// When a node empties, it is joined with a nearby node.
|
|
20
|
+
///
|
|
21
|
+
/// An absolute index is offset from the start of the segmented array.
|
|
22
|
+
/// A relative index is offset from the start of a node.
|
|
23
|
+
pub fn SegmentedArrayType(
|
|
24
|
+
comptime T: type,
|
|
25
|
+
comptime NodePool: type,
|
|
26
|
+
comptime element_count_max: u32,
|
|
27
|
+
comptime options: Options,
|
|
28
|
+
) type {
|
|
29
|
+
return SegmentedArrayBaseType(T, NodePool, element_count_max, null, {}, options);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
pub fn SortedSegmentedArrayType(
|
|
33
|
+
comptime T: type,
|
|
34
|
+
comptime NodePool: type,
|
|
35
|
+
comptime element_count_max: u32,
|
|
36
|
+
comptime Key: type,
|
|
37
|
+
comptime key_from_value: fn (*const T) callconv(.@"inline") Key,
|
|
38
|
+
comptime options: Options,
|
|
39
|
+
) type {
|
|
40
|
+
return SegmentedArrayBaseType(T, NodePool, element_count_max, Key, key_from_value, options);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
pub const Options = struct {
|
|
44
|
+
/// Assert all invariants before/after every public function.
|
|
45
|
+
/// Very expensive - only enable for debugging/fuzzing.
|
|
46
|
+
verify: bool = false,
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
fn SegmentedArrayBaseType(
|
|
50
|
+
comptime T: type,
|
|
51
|
+
comptime NodePool: type,
|
|
52
|
+
comptime element_count_max: u32,
|
|
53
|
+
// Set when the SegmentedArray is ordered:
|
|
54
|
+
comptime Key: ?type,
|
|
55
|
+
comptime key_from_value: if (Key) |K| (fn (*const T) callconv(.@"inline") K) else void,
|
|
56
|
+
comptime options: Options,
|
|
57
|
+
) type {
|
|
58
|
+
comptime assert(Key == null or @typeInfo(Key.?) == .int or @typeInfo(Key.?) == .comptime_int);
|
|
59
|
+
|
|
60
|
+
return struct {
|
|
61
|
+
const SegmentedArray = @This();
|
|
62
|
+
|
|
63
|
+
pub const Cursor = struct {
|
|
64
|
+
node: u32,
|
|
65
|
+
relative_index: u32,
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
// We can't use @divExact() here as we store TableInfo structs of various sizes in this
|
|
69
|
+
// data structure. This means that there may be padding at the end of the node.
|
|
70
|
+
pub const node_capacity = blk: {
|
|
71
|
+
const max = @divFloor(NodePool.node_size, @sizeOf(T));
|
|
72
|
+
|
|
73
|
+
// We require that the node capacity is evenly divisible by 2 to simplify our code
|
|
74
|
+
// that splits/joins nodes at the midpoint.
|
|
75
|
+
const capacity = if (max % 2 == 0) max else max - 1;
|
|
76
|
+
|
|
77
|
+
assert(capacity >= 2);
|
|
78
|
+
assert(capacity % 2 == 0);
|
|
79
|
+
break :blk capacity;
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
comptime {
|
|
83
|
+
// If this assert fails, we should be using a non-segmented array instead!
|
|
84
|
+
assert(element_count_max > node_capacity);
|
|
85
|
+
|
|
86
|
+
// We use u32 for indexes and counts.
|
|
87
|
+
assert(element_count_max <= std.math.maxInt(u32));
|
|
88
|
+
|
|
89
|
+
// The buffers returned from the node_pool must be able to store T with correct
|
|
90
|
+
// alignment.
|
|
91
|
+
assert(NodePool.node_alignment >= @alignOf(T));
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
pub const node_count_max_naive = blk: {
|
|
95
|
+
// If a node fills up it is divided into two new nodes. Therefore,
|
|
96
|
+
// the worst possible space overhead is when all nodes are half full.
|
|
97
|
+
// This uses flooring division, we want to examine the worst case here.
|
|
98
|
+
const elements_per_node_min = @divExact(node_capacity, 2);
|
|
99
|
+
break :blk div_ceil(element_count_max, elements_per_node_min);
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
// We can't always actually reach node_count_max_naive in all configurations.
|
|
103
|
+
// If we're at node_count_max_naive-1 nodes, in order to split one more node we need:
|
|
104
|
+
pub const node_count_max = if (element_count_max >=
|
|
105
|
+
// * The node that we split must be full.
|
|
106
|
+
node_capacity +
|
|
107
|
+
// * The last node must have at least one element.
|
|
108
|
+
1 +
|
|
109
|
+
// * All other nodes must be at least half-full.
|
|
110
|
+
((node_count_max_naive -| 3) * @divExact(node_capacity, 2)) +
|
|
111
|
+
// * And then we insert one more element into the full node.
|
|
112
|
+
1)
|
|
113
|
+
node_count_max_naive
|
|
114
|
+
else
|
|
115
|
+
node_count_max_naive - 1;
|
|
116
|
+
|
|
117
|
+
node_count: u32 = 0,
|
|
118
|
+
/// This is the segmented array. The first node_count pointers are non-null.
|
|
119
|
+
/// The rest are null. We only use optional pointers here to get safety checks.
|
|
120
|
+
nodes: *[node_count_max]?*[node_capacity]T,
|
|
121
|
+
/// Since nodes in a segmented array are usually not full, computing the absolute index
|
|
122
|
+
/// of an element in the full array is O(N) over the number of nodes. To avoid this cost
|
|
123
|
+
/// we precompute the absolute index of the first element of each node.
|
|
124
|
+
/// To avoid a separate counts field, we derive the number of elements in a node from the
|
|
125
|
+
/// index of that node and the next node.
|
|
126
|
+
/// To avoid special casing the count() function for the last node, we increase the array
|
|
127
|
+
/// length by 1 and store the total element count in the last slot.
|
|
128
|
+
indexes: *[node_count_max + 1]u32,
|
|
129
|
+
|
|
130
|
+
pub fn init(allocator: mem.Allocator) !SegmentedArray {
|
|
131
|
+
const nodes = try allocator.create([node_count_max]?*[node_capacity]T);
|
|
132
|
+
errdefer allocator.destroy(nodes);
|
|
133
|
+
|
|
134
|
+
const indexes = try allocator.create([node_count_max + 1]u32);
|
|
135
|
+
errdefer allocator.destroy(indexes);
|
|
136
|
+
|
|
137
|
+
@memset(nodes, null);
|
|
138
|
+
indexes[0] = 0;
|
|
139
|
+
|
|
140
|
+
const array = SegmentedArray{
|
|
141
|
+
.nodes = nodes,
|
|
142
|
+
.indexes = indexes,
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
if (options.verify) array.verify();
|
|
146
|
+
|
|
147
|
+
return array;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
pub fn deinit(array: SegmentedArray, allocator: mem.Allocator, node_pool: *NodePool) void {
|
|
151
|
+
if (options.verify) array.verify();
|
|
152
|
+
|
|
153
|
+
for (array.nodes[0..array.node_count]) |node| {
|
|
154
|
+
node_pool.release(@ptrCast(@alignCast(node.?)));
|
|
155
|
+
}
|
|
156
|
+
allocator.free(array.nodes);
|
|
157
|
+
allocator.free(array.indexes);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
pub fn reset(array: *SegmentedArray, node_pool: *NodePool) void {
|
|
161
|
+
if (options.verify) array.verify();
|
|
162
|
+
|
|
163
|
+
for (array.nodes[0..array.node_count]) |node| {
|
|
164
|
+
node_pool.release(@ptrCast(@alignCast(node.?)));
|
|
165
|
+
}
|
|
166
|
+
@memset(array.nodes, null);
|
|
167
|
+
|
|
168
|
+
array.indexes[0] = 0;
|
|
169
|
+
array.* = .{
|
|
170
|
+
.nodes = array.nodes,
|
|
171
|
+
.indexes = array.indexes,
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
if (options.verify) array.verify();
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
pub fn verify(array: SegmentedArray) void {
|
|
178
|
+
assert(array.node_count <= node_count_max);
|
|
179
|
+
for (array.nodes, 0..) |node, node_index| {
|
|
180
|
+
if (node_index < array.node_count) {
|
|
181
|
+
// The first node_count pointers are non-null.
|
|
182
|
+
assert(node != null);
|
|
183
|
+
} else {
|
|
184
|
+
// The rest are non-null.
|
|
185
|
+
assert(node == null);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
for (array.nodes[0..array.node_count], 0..) |_, node_index| {
|
|
189
|
+
const c = array.count(@intCast(node_index));
|
|
190
|
+
// Every node is at most full.
|
|
191
|
+
assert(c <= node_capacity);
|
|
192
|
+
// Every node is at least half-full, except the last.
|
|
193
|
+
if (node_index < array.node_count - 1) {
|
|
194
|
+
assert(c >= @divTrunc(node_capacity, 2));
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
if (Key) |K| {
|
|
198
|
+
// If Key is not null then the elements must be sorted by key_from_value (but not
|
|
199
|
+
// necessarily unique).
|
|
200
|
+
var key_prior_or_null: ?K = null;
|
|
201
|
+
for (array.nodes[0..array.node_count], 0..) |_, node_index| {
|
|
202
|
+
for (array.node_elements(@intCast(node_index))) |*value| {
|
|
203
|
+
const key = key_from_value(value);
|
|
204
|
+
if (key_prior_or_null) |key_prior| {
|
|
205
|
+
assert(key_prior <= key);
|
|
206
|
+
}
|
|
207
|
+
key_prior_or_null = key;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/// Returns the absolute index of the element being inserted.
|
|
214
|
+
/// Available only when `Key != null`.
|
|
215
|
+
pub fn insert_element(
|
|
216
|
+
array: *SegmentedArray,
|
|
217
|
+
node_pool: *NodePool,
|
|
218
|
+
element: T,
|
|
219
|
+
) u32 {
|
|
220
|
+
comptime assert(Key != null);
|
|
221
|
+
if (options.verify) array.verify();
|
|
222
|
+
|
|
223
|
+
const count_before = array.len();
|
|
224
|
+
|
|
225
|
+
const cursor = array.search(key_from_value(&element));
|
|
226
|
+
const absolute_index = array.absolute_index_for_cursor(cursor);
|
|
227
|
+
array.insert_elements_at_absolute_index(node_pool, absolute_index, &[_]T{element});
|
|
228
|
+
|
|
229
|
+
if (options.verify) array.verify();
|
|
230
|
+
|
|
231
|
+
const count_after = array.len();
|
|
232
|
+
assert(count_after == count_before + 1);
|
|
233
|
+
|
|
234
|
+
return absolute_index;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/// Available only when `Key == null`.
|
|
238
|
+
pub fn insert_elements(
|
|
239
|
+
array: *SegmentedArray,
|
|
240
|
+
node_pool: *NodePool,
|
|
241
|
+
absolute_index: u32,
|
|
242
|
+
elements: []const T,
|
|
243
|
+
) void {
|
|
244
|
+
comptime assert(Key == null);
|
|
245
|
+
if (options.verify) array.verify();
|
|
246
|
+
|
|
247
|
+
const count_before = array.len();
|
|
248
|
+
array.insert_elements_at_absolute_index(
|
|
249
|
+
node_pool,
|
|
250
|
+
absolute_index,
|
|
251
|
+
elements,
|
|
252
|
+
);
|
|
253
|
+
|
|
254
|
+
const count_after = array.len();
|
|
255
|
+
assert(count_after == count_before + elements.len);
|
|
256
|
+
|
|
257
|
+
if (options.verify) array.verify();
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
fn insert_elements_at_absolute_index(
|
|
261
|
+
array: *SegmentedArray,
|
|
262
|
+
node_pool: *NodePool,
|
|
263
|
+
absolute_index: u32,
|
|
264
|
+
elements: []const T,
|
|
265
|
+
) void {
|
|
266
|
+
assert(elements.len > 0);
|
|
267
|
+
assert(absolute_index + elements.len <= element_count_max);
|
|
268
|
+
|
|
269
|
+
var i: u32 = 0;
|
|
270
|
+
while (i < elements.len) {
|
|
271
|
+
const batch = @min(node_capacity, elements.len - i);
|
|
272
|
+
array.insert_elements_batch(
|
|
273
|
+
node_pool,
|
|
274
|
+
absolute_index + i,
|
|
275
|
+
elements[i..][0..batch],
|
|
276
|
+
);
|
|
277
|
+
i += batch;
|
|
278
|
+
}
|
|
279
|
+
assert(i == elements.len);
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
fn insert_elements_batch(
|
|
283
|
+
array: *SegmentedArray,
|
|
284
|
+
node_pool: *NodePool,
|
|
285
|
+
absolute_index: u32,
|
|
286
|
+
elements: []const T,
|
|
287
|
+
) void {
|
|
288
|
+
assert(elements.len > 0);
|
|
289
|
+
assert(elements.len <= node_capacity);
|
|
290
|
+
assert(absolute_index + elements.len <= element_count_max);
|
|
291
|
+
|
|
292
|
+
if (array.node_count == 0) {
|
|
293
|
+
assert(absolute_index == 0);
|
|
294
|
+
|
|
295
|
+
array.insert_empty_node_at(node_pool, 0);
|
|
296
|
+
|
|
297
|
+
assert(array.node_count == 1);
|
|
298
|
+
assert(array.nodes[0] != null);
|
|
299
|
+
assert(array.indexes[0] == 0);
|
|
300
|
+
assert(array.indexes[1] == 0);
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
const cursor = array.cursor_for_absolute_index(absolute_index);
|
|
304
|
+
assert(cursor.node < array.node_count);
|
|
305
|
+
|
|
306
|
+
const a = cursor.node;
|
|
307
|
+
const a_pointer = array.nodes[a].?;
|
|
308
|
+
assert(cursor.relative_index <= array.count(a));
|
|
309
|
+
|
|
310
|
+
const total = array.count(a) + @as(u32, @intCast(elements.len));
|
|
311
|
+
if (total <= node_capacity) {
|
|
312
|
+
stdx.copy_right(
|
|
313
|
+
.inexact,
|
|
314
|
+
T,
|
|
315
|
+
a_pointer[cursor.relative_index + elements.len ..],
|
|
316
|
+
a_pointer[cursor.relative_index..array.count(a)],
|
|
317
|
+
);
|
|
318
|
+
stdx.copy_disjoint(.inexact, T, a_pointer[cursor.relative_index..], elements);
|
|
319
|
+
|
|
320
|
+
array.increment_indexes_after(a, @intCast(elements.len));
|
|
321
|
+
return;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
// Insert a new node after the node being split.
|
|
325
|
+
const b = a + 1;
|
|
326
|
+
array.insert_empty_node_at(node_pool, b);
|
|
327
|
+
const b_pointer = array.nodes[b].?;
|
|
328
|
+
|
|
329
|
+
const a_half = div_ceil(total, 2);
|
|
330
|
+
const b_half = total - a_half;
|
|
331
|
+
assert(a_half >= b_half);
|
|
332
|
+
assert(a_half + b_half == total);
|
|
333
|
+
|
|
334
|
+
// The 1st case can be seen as a special case of the 2nd.
|
|
335
|
+
// The 5th case can be seen as a special case of the 4th.
|
|
336
|
+
//
|
|
337
|
+
// elements: [yyyyyy], relative_index: 0
|
|
338
|
+
// [xxxxx_][______]
|
|
339
|
+
// [______][xxxxx_] // after first copy_backwards
|
|
340
|
+
// [______][xxxxx_] // skip mem.copyBackwards (a_half >= relative_index)
|
|
341
|
+
// [yyyyyy][xxxxx_] // after second copy_backwards
|
|
342
|
+
//
|
|
343
|
+
// elements: [yy], relative_index: 1
|
|
344
|
+
// [xxxxx_][______]
|
|
345
|
+
// [x__x__][xxx___] // after first copy_backwards
|
|
346
|
+
// [x__x__][xxx___] // skip mem.copyBackwards (a_half >= relative_index)
|
|
347
|
+
// [xyyx__][xxx___] // after second copy_backwards
|
|
348
|
+
//
|
|
349
|
+
// elements: [yy], relative_index: 2
|
|
350
|
+
// [xxx_][____]
|
|
351
|
+
// [xx__][_x__] // after first copy_backwards
|
|
352
|
+
// [xx__][_x__] // skip mem.copyBackwards (a_half >= relative_index)
|
|
353
|
+
// [xxy_][yx__] // after second copy_backwards
|
|
354
|
+
//
|
|
355
|
+
// elements: [yy], relative_index: 5
|
|
356
|
+
// [xxxxxx][______]
|
|
357
|
+
// [xxxxx_][___x__] // after first copy_backwards
|
|
358
|
+
// [xxxx__][x__x__] // after mem.copyBackwards (a_half < relative_index)
|
|
359
|
+
// [xxxx__][xyyx__] // after second copy_backwards
|
|
360
|
+
//
|
|
361
|
+
// elements: [yyyyy_], relative_index: 5
|
|
362
|
+
// [xxxxx_][______]
|
|
363
|
+
// [xxxxx_][______] // after first copy_backwards
|
|
364
|
+
// [xxxxx_][______] // skip mem.copyBackwards (a_half >= relative_index)
|
|
365
|
+
// [xxxxx_][yyyyy_] // after second copy_backwards
|
|
366
|
+
|
|
367
|
+
const a_half_pointer = a_pointer[0..a_half];
|
|
368
|
+
const b_half_pointer = b_pointer[0..b_half];
|
|
369
|
+
|
|
370
|
+
// Move part of `a` forwards to make space for elements.
|
|
371
|
+
copy_backwards(
|
|
372
|
+
a_half_pointer,
|
|
373
|
+
b_half_pointer,
|
|
374
|
+
cursor.relative_index + elements.len,
|
|
375
|
+
a_pointer[cursor.relative_index..array.count(a)],
|
|
376
|
+
);
|
|
377
|
+
|
|
378
|
+
if (a_half < cursor.relative_index) {
|
|
379
|
+
// Move the part of `a` that is past the half-way point into `b`.
|
|
380
|
+
stdx.copy_right(
|
|
381
|
+
.inexact,
|
|
382
|
+
T,
|
|
383
|
+
b_half_pointer,
|
|
384
|
+
a_pointer[a_half..cursor.relative_index],
|
|
385
|
+
);
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
// Move `elements` into `a` and/or `b`.
|
|
389
|
+
copy_backwards(
|
|
390
|
+
a_half_pointer,
|
|
391
|
+
b_half_pointer,
|
|
392
|
+
cursor.relative_index,
|
|
393
|
+
elements,
|
|
394
|
+
);
|
|
395
|
+
|
|
396
|
+
array.indexes[b] = array.indexes[a] + a_half;
|
|
397
|
+
array.increment_indexes_after(b, @intCast(elements.len));
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
/// Behaves like mem.copyBackwards, but as if `a` and `b` were a single contiguous slice.
|
|
401
|
+
/// `target` is the destination index within the concatenation of `a` and `b`.
|
|
402
|
+
fn copy_backwards(
|
|
403
|
+
a: []T,
|
|
404
|
+
b: []T,
|
|
405
|
+
target: usize,
|
|
406
|
+
source: []const T,
|
|
407
|
+
) void {
|
|
408
|
+
assert(target + source.len <= a.len + b.len);
|
|
409
|
+
const target_a = a[@min(target, a.len)..@min(target + source.len, a.len)];
|
|
410
|
+
const target_b = b[target -| a.len..(target + source.len) -| a.len];
|
|
411
|
+
assert(target_a.len + target_b.len == source.len);
|
|
412
|
+
const source_a = source[0..target_a.len];
|
|
413
|
+
const source_b = source[target_a.len..];
|
|
414
|
+
if (target_b.ptr != source_b.ptr) {
|
|
415
|
+
stdx.copy_right(.exact, T, target_b, source_b);
|
|
416
|
+
}
|
|
417
|
+
if (target_a.ptr != source_a.ptr) {
|
|
418
|
+
stdx.copy_right(.exact, T, target_a, source_a);
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
/// Insert an empty node at index `node`.
|
|
423
|
+
fn insert_empty_node_at(array: *SegmentedArray, node_pool: *NodePool, node: u32) void {
|
|
424
|
+
assert(node <= array.node_count);
|
|
425
|
+
assert(array.node_count + 1 <= node_count_max);
|
|
426
|
+
|
|
427
|
+
stdx.copy_right(
|
|
428
|
+
.exact,
|
|
429
|
+
?*[node_capacity]T,
|
|
430
|
+
array.nodes[node + 1 .. array.node_count + 1],
|
|
431
|
+
array.nodes[node..array.node_count],
|
|
432
|
+
);
|
|
433
|
+
stdx.copy_right(
|
|
434
|
+
.exact,
|
|
435
|
+
u32,
|
|
436
|
+
array.indexes[node + 1 .. array.node_count + 2],
|
|
437
|
+
array.indexes[node .. array.node_count + 1],
|
|
438
|
+
);
|
|
439
|
+
|
|
440
|
+
array.node_count += 1;
|
|
441
|
+
const node_pointer = node_pool.acquire();
|
|
442
|
+
comptime {
|
|
443
|
+
// @ptrCast does not check that the size or alignment agree
|
|
444
|
+
assert(std.meta.alignment(@TypeOf(node_pointer)) >= @alignOf(T));
|
|
445
|
+
assert(@sizeOf(@TypeOf(node_pointer.*)) >= @sizeOf([node_capacity]T));
|
|
446
|
+
}
|
|
447
|
+
array.nodes[node] = @ptrCast(@alignCast(node_pointer));
|
|
448
|
+
assert(array.indexes[node] == array.indexes[node + 1]);
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
pub fn remove_elements(
|
|
452
|
+
array: *SegmentedArray,
|
|
453
|
+
node_pool: *NodePool,
|
|
454
|
+
absolute_index: u32,
|
|
455
|
+
remove_count: u32,
|
|
456
|
+
) void {
|
|
457
|
+
if (options.verify) array.verify();
|
|
458
|
+
|
|
459
|
+
assert(array.node_count > 0);
|
|
460
|
+
assert(remove_count > 0);
|
|
461
|
+
assert(absolute_index + remove_count <= element_count_max);
|
|
462
|
+
assert(absolute_index + remove_count <= array.indexes[array.node_count]);
|
|
463
|
+
|
|
464
|
+
const half = @divExact(node_capacity, 2);
|
|
465
|
+
|
|
466
|
+
var i: u32 = remove_count;
|
|
467
|
+
while (i > 0) {
|
|
468
|
+
const batch = @min(half, i);
|
|
469
|
+
array.remove_elements_batch(node_pool, absolute_index, batch);
|
|
470
|
+
i -= batch;
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
if (options.verify) array.verify();
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
fn remove_elements_batch(
|
|
477
|
+
array: *SegmentedArray,
|
|
478
|
+
node_pool: *NodePool,
|
|
479
|
+
absolute_index: u32,
|
|
480
|
+
remove_count: u32,
|
|
481
|
+
) void {
|
|
482
|
+
assert(array.node_count > 0);
|
|
483
|
+
|
|
484
|
+
// Restricting the batch size to half node capacity ensures that elements
|
|
485
|
+
// are removed from at most two nodes.
|
|
486
|
+
const half = @divExact(node_capacity, 2);
|
|
487
|
+
assert(remove_count <= half);
|
|
488
|
+
assert(remove_count > 0);
|
|
489
|
+
|
|
490
|
+
assert(absolute_index + remove_count <= element_count_max);
|
|
491
|
+
assert(absolute_index + remove_count <= array.indexes[array.node_count]);
|
|
492
|
+
|
|
493
|
+
const cursor = array.cursor_for_absolute_index(absolute_index);
|
|
494
|
+
assert(cursor.node < array.node_count);
|
|
495
|
+
|
|
496
|
+
const a = cursor.node;
|
|
497
|
+
const a_pointer = array.nodes[a].?;
|
|
498
|
+
const a_remaining = cursor.relative_index;
|
|
499
|
+
|
|
500
|
+
// Remove elements from exactly one node:
|
|
501
|
+
if (a_remaining + remove_count <= array.count(a)) {
|
|
502
|
+
stdx.copy_left(
|
|
503
|
+
.inexact,
|
|
504
|
+
T,
|
|
505
|
+
a_pointer[a_remaining..],
|
|
506
|
+
a_pointer[a_remaining + remove_count .. array.count(a)],
|
|
507
|
+
);
|
|
508
|
+
|
|
509
|
+
array.decrement_indexes_after(a, remove_count);
|
|
510
|
+
|
|
511
|
+
array.maybe_remove_or_merge_node_with_next(node_pool, a);
|
|
512
|
+
return;
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
// Remove elements from exactly two nodes:
|
|
516
|
+
|
|
517
|
+
const b = a + 1;
|
|
518
|
+
const b_pointer = array.nodes[b].?;
|
|
519
|
+
const b_remaining = b_pointer[remove_count -
|
|
520
|
+
(array.count(a) - a_remaining) .. array.count(b)];
|
|
521
|
+
|
|
522
|
+
assert(@intFromPtr(b_remaining.ptr) > @intFromPtr(b_pointer));
|
|
523
|
+
|
|
524
|
+
// Only one of these nodes may become empty, as we limit batch size to
|
|
525
|
+
// half node capacity.
|
|
526
|
+
assert(a_remaining > 0 or b_remaining.len > 0);
|
|
527
|
+
|
|
528
|
+
if (a_remaining >= half) {
|
|
529
|
+
stdx.copy_left(.inexact, T, b_pointer, b_remaining);
|
|
530
|
+
|
|
531
|
+
array.indexes[b] = array.indexes[a] + a_remaining;
|
|
532
|
+
array.decrement_indexes_after(b, remove_count);
|
|
533
|
+
|
|
534
|
+
array.maybe_remove_or_merge_node_with_next(node_pool, b);
|
|
535
|
+
} else if (b_remaining.len >= half) {
|
|
536
|
+
assert(a_remaining < half);
|
|
537
|
+
|
|
538
|
+
array.indexes[b] = array.indexes[a] + a_remaining;
|
|
539
|
+
array.decrement_indexes_after(b, remove_count);
|
|
540
|
+
|
|
541
|
+
array.maybe_merge_nodes(node_pool, a, b_remaining);
|
|
542
|
+
} else {
|
|
543
|
+
assert(a_remaining < half and b_remaining.len < half);
|
|
544
|
+
assert(a_remaining + b_remaining.len <= node_capacity);
|
|
545
|
+
|
|
546
|
+
stdx.copy_disjoint(.inexact, T, a_pointer[a_remaining..], b_remaining);
|
|
547
|
+
|
|
548
|
+
array.indexes[b] =
|
|
549
|
+
array.indexes[a] + a_remaining + @as(u32, @intCast(b_remaining.len));
|
|
550
|
+
array.decrement_indexes_after(b, remove_count);
|
|
551
|
+
|
|
552
|
+
array.remove_empty_node_at(node_pool, b);
|
|
553
|
+
|
|
554
|
+
// Either:
|
|
555
|
+
// * `b` was the last node so now `a` is the last node
|
|
556
|
+
// * both `a` and `b` were at least half-full so now `a` is at least half-full
|
|
557
|
+
assert(b == array.node_count or array.count(a) >= half);
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
fn maybe_remove_or_merge_node_with_next(
|
|
562
|
+
array: *SegmentedArray,
|
|
563
|
+
node_pool: *NodePool,
|
|
564
|
+
node: u32,
|
|
565
|
+
) void {
|
|
566
|
+
assert(node < array.node_count);
|
|
567
|
+
|
|
568
|
+
if (array.count(node) == 0) {
|
|
569
|
+
array.remove_empty_node_at(node_pool, node);
|
|
570
|
+
return;
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
if (node == array.node_count - 1) return;
|
|
574
|
+
|
|
575
|
+
const next_elements = array.nodes[node + 1].?[0..array.count(node + 1)];
|
|
576
|
+
array.maybe_merge_nodes(node_pool, node, next_elements);
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
fn maybe_merge_nodes(
|
|
580
|
+
array: *SegmentedArray,
|
|
581
|
+
node_pool: *NodePool,
|
|
582
|
+
node: u32,
|
|
583
|
+
elements_next_node: []T,
|
|
584
|
+
) void {
|
|
585
|
+
const half = @divExact(node_capacity, 2);
|
|
586
|
+
|
|
587
|
+
const a = node;
|
|
588
|
+
const a_pointer = array.nodes[a].?;
|
|
589
|
+
assert(array.count(a) <= node_capacity);
|
|
590
|
+
|
|
591
|
+
// The elements_next_node slice may not be at the start of the node,
|
|
592
|
+
// but the length of the slice will match count(b).
|
|
593
|
+
const b = a + 1;
|
|
594
|
+
const b_pointer = array.nodes[b].?;
|
|
595
|
+
const b_elements = elements_next_node;
|
|
596
|
+
assert(b_elements.len == array.count(b));
|
|
597
|
+
assert(b_elements.len > 0);
|
|
598
|
+
assert(b_elements.len >= half or b == array.node_count - 1);
|
|
599
|
+
assert(b_elements.len <= node_capacity);
|
|
600
|
+
assert(@intFromPtr(b_elements.ptr) >= @intFromPtr(b_pointer));
|
|
601
|
+
|
|
602
|
+
// Our function would still be correct if this assert fails, but we would
|
|
603
|
+
// unnecessarily copy all elements of b to node a and then delete b
|
|
604
|
+
// instead of simply deleting a.
|
|
605
|
+
assert(!(array.count(a) == 0 and b_pointer == b_elements.ptr));
|
|
606
|
+
|
|
607
|
+
const total = array.count(a) + @as(u32, @intCast(b_elements.len));
|
|
608
|
+
if (total <= node_capacity) {
|
|
609
|
+
stdx.copy_disjoint(.inexact, T, a_pointer[array.count(a)..], b_elements);
|
|
610
|
+
|
|
611
|
+
array.indexes[b] = array.indexes[b + 1];
|
|
612
|
+
array.remove_empty_node_at(node_pool, b);
|
|
613
|
+
|
|
614
|
+
assert(array.count(a) >= half or a == array.node_count - 1);
|
|
615
|
+
} else if (array.count(a) < half) {
|
|
616
|
+
const a_half = div_ceil(total, 2);
|
|
617
|
+
const b_half = total - a_half;
|
|
618
|
+
assert(a_half >= b_half);
|
|
619
|
+
assert(a_half + b_half == total);
|
|
620
|
+
|
|
621
|
+
stdx.copy_disjoint(
|
|
622
|
+
.exact,
|
|
623
|
+
T,
|
|
624
|
+
a_pointer[array.count(a)..a_half],
|
|
625
|
+
b_elements[0 .. a_half - array.count(a)],
|
|
626
|
+
);
|
|
627
|
+
stdx.copy_left(.inexact, T, b_pointer, b_elements[a_half - array.count(a) ..]);
|
|
628
|
+
|
|
629
|
+
array.indexes[b] = array.indexes[a] + a_half;
|
|
630
|
+
|
|
631
|
+
assert(array.count(a) >= half);
|
|
632
|
+
assert(array.count(b) >= half);
|
|
633
|
+
} else {
|
|
634
|
+
assert(b_pointer == b_elements.ptr);
|
|
635
|
+
assert(array.indexes[b] + b_elements.len == array.indexes[b + 1]);
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
/// Remove an empty node at index `node`.
|
|
640
|
+
fn remove_empty_node_at(array: *SegmentedArray, node_pool: *NodePool, node: u32) void {
|
|
641
|
+
assert(array.node_count > 0);
|
|
642
|
+
assert(node < array.node_count);
|
|
643
|
+
assert(array.count(node) == 0);
|
|
644
|
+
|
|
645
|
+
node_pool.release(@ptrCast(@alignCast(array.nodes[node].?)));
|
|
646
|
+
|
|
647
|
+
stdx.copy_left(
|
|
648
|
+
.exact,
|
|
649
|
+
?*[node_capacity]T,
|
|
650
|
+
array.nodes[node .. array.node_count - 1],
|
|
651
|
+
array.nodes[node + 1 .. array.node_count],
|
|
652
|
+
);
|
|
653
|
+
stdx.copy_left(
|
|
654
|
+
.exact,
|
|
655
|
+
u32,
|
|
656
|
+
array.indexes[node..array.node_count],
|
|
657
|
+
array.indexes[node + 1 .. array.node_count + 1],
|
|
658
|
+
);
|
|
659
|
+
|
|
660
|
+
array.node_count -= 1;
|
|
661
|
+
array.nodes[array.node_count] = null;
|
|
662
|
+
array.indexes[array.node_count + 1] = undefined;
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
inline fn count(array: SegmentedArray, node: u32) u32 {
|
|
666
|
+
const result = array.indexes[node + 1] - array.indexes[node];
|
|
667
|
+
assert(result <= node_capacity);
|
|
668
|
+
return result;
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
inline fn increment_indexes_after(array: *SegmentedArray, node: u32, delta: u32) void {
|
|
672
|
+
for (array.indexes[node + 1 .. array.node_count + 1]) |*i| i.* += delta;
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
inline fn decrement_indexes_after(array: *SegmentedArray, node: u32, delta: u32) void {
|
|
676
|
+
for (array.indexes[node + 1 .. array.node_count + 1]) |*i| i.* -= delta;
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
pub inline fn node_elements(array: SegmentedArray, node: u32) []T {
|
|
680
|
+
assert(node < array.node_count);
|
|
681
|
+
return array.nodes[node].?[0..array.count(node)];
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
pub inline fn node_last_element(array: SegmentedArray, node: u32) T {
|
|
685
|
+
return array.node_elements(node)[array.count(node) - 1];
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
pub inline fn element_at_cursor(array: SegmentedArray, cursor: Cursor) T {
|
|
689
|
+
return array.node_elements(cursor.node)[cursor.relative_index];
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
pub inline fn first(_: SegmentedArray) Cursor {
|
|
693
|
+
return .{
|
|
694
|
+
.node = 0,
|
|
695
|
+
.relative_index = 0,
|
|
696
|
+
};
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
pub inline fn last(array: SegmentedArray) Cursor {
|
|
700
|
+
if (array.node_count == 0) return array.first();
|
|
701
|
+
|
|
702
|
+
return .{
|
|
703
|
+
.node = array.node_count - 1,
|
|
704
|
+
.relative_index = array.count(array.node_count - 1) - 1,
|
|
705
|
+
};
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
pub inline fn len(array: SegmentedArray) u32 {
|
|
709
|
+
const result = array.indexes[array.node_count];
|
|
710
|
+
assert(result <= element_count_max);
|
|
711
|
+
return result;
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
// TODO Consider enabling ReleaseFast for this once tested.
|
|
715
|
+
pub fn absolute_index_for_cursor(array: SegmentedArray, cursor: Cursor) u32 {
|
|
716
|
+
if (array.node_count == 0) {
|
|
717
|
+
assert(cursor.node == 0);
|
|
718
|
+
assert(cursor.relative_index == 0);
|
|
719
|
+
return 0;
|
|
720
|
+
}
|
|
721
|
+
assert(cursor.node < array.node_count);
|
|
722
|
+
if (cursor.node == array.node_count - 1) {
|
|
723
|
+
// Insertion may target the index one past the end of the array.
|
|
724
|
+
assert(cursor.relative_index <= array.count(cursor.node));
|
|
725
|
+
} else {
|
|
726
|
+
assert(cursor.relative_index < array.count(cursor.node));
|
|
727
|
+
}
|
|
728
|
+
return array.indexes[cursor.node] + cursor.relative_index;
|
|
729
|
+
}
|
|
730
|
+
|
|
731
|
+
fn cursor_for_absolute_index(array: SegmentedArray, absolute_index: u32) Cursor {
|
|
732
|
+
// This function could handle node_count == 0 by returning a zero Cursor.
|
|
733
|
+
// However, this is an internal function and we don't require this behavior.
|
|
734
|
+
assert(array.node_count > 0);
|
|
735
|
+
|
|
736
|
+
assert(absolute_index < element_count_max);
|
|
737
|
+
assert(absolute_index <= array.len());
|
|
738
|
+
|
|
739
|
+
const result = binary_search_keys(
|
|
740
|
+
u32,
|
|
741
|
+
array.indexes[0..array.node_count],
|
|
742
|
+
absolute_index,
|
|
743
|
+
.{},
|
|
744
|
+
);
|
|
745
|
+
|
|
746
|
+
if (result.exact) {
|
|
747
|
+
return .{
|
|
748
|
+
.node = result.index,
|
|
749
|
+
.relative_index = 0,
|
|
750
|
+
};
|
|
751
|
+
} else {
|
|
752
|
+
const node = result.index - 1;
|
|
753
|
+
const relative_index = absolute_index - array.indexes[node];
|
|
754
|
+
if (node == array.node_count - 1) {
|
|
755
|
+
// Insertion may target the index one past the end of the array.
|
|
756
|
+
assert(relative_index <= array.count(node));
|
|
757
|
+
} else {
|
|
758
|
+
assert(relative_index < array.count(node));
|
|
759
|
+
}
|
|
760
|
+
return .{
|
|
761
|
+
.node = node,
|
|
762
|
+
.relative_index = relative_index,
|
|
763
|
+
};
|
|
764
|
+
}
|
|
765
|
+
}
|
|
766
|
+
|
|
767
|
+
pub const Iterator = struct {
|
|
768
|
+
array: *const SegmentedArray,
|
|
769
|
+
direction: Direction,
|
|
770
|
+
|
|
771
|
+
cursor: Cursor,
|
|
772
|
+
|
|
773
|
+
/// The user may set this early to stop iteration. For example,
|
|
774
|
+
/// if the returned table info is outside the key range.
|
|
775
|
+
done: bool = false,
|
|
776
|
+
|
|
777
|
+
pub fn next(it: *Iterator) ?*T {
|
|
778
|
+
if (it.done) return null;
|
|
779
|
+
|
|
780
|
+
assert(it.cursor.node < it.array.node_count);
|
|
781
|
+
|
|
782
|
+
const elements = it.array.node_elements(it.cursor.node);
|
|
783
|
+
const element = &elements[it.cursor.relative_index];
|
|
784
|
+
|
|
785
|
+
switch (it.direction) {
|
|
786
|
+
.ascending => {
|
|
787
|
+
if (it.cursor.relative_index == elements.len - 1) {
|
|
788
|
+
if (it.cursor.node == it.array.node_count - 1) {
|
|
789
|
+
it.done = true;
|
|
790
|
+
} else {
|
|
791
|
+
it.cursor.node += 1;
|
|
792
|
+
it.cursor.relative_index = 0;
|
|
793
|
+
}
|
|
794
|
+
} else {
|
|
795
|
+
it.cursor.relative_index += 1;
|
|
796
|
+
}
|
|
797
|
+
},
|
|
798
|
+
.descending => {
|
|
799
|
+
if (it.cursor.relative_index == 0) {
|
|
800
|
+
if (it.cursor.node == 0) {
|
|
801
|
+
it.done = true;
|
|
802
|
+
} else {
|
|
803
|
+
it.cursor.node -= 1;
|
|
804
|
+
it.cursor.relative_index = it.array.count(it.cursor.node) - 1;
|
|
805
|
+
}
|
|
806
|
+
} else {
|
|
807
|
+
it.cursor.relative_index -= 1;
|
|
808
|
+
}
|
|
809
|
+
},
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
return element;
|
|
813
|
+
}
|
|
814
|
+
};
|
|
815
|
+
|
|
816
|
+
pub fn iterator_from_cursor(
|
|
817
|
+
array: *const SegmentedArray,
|
|
818
|
+
/// First element of iteration.
|
|
819
|
+
cursor: Cursor,
|
|
820
|
+
direction: Direction,
|
|
821
|
+
) Iterator {
|
|
822
|
+
if (array.node_count == 0) {
|
|
823
|
+
assert(cursor.node == 0);
|
|
824
|
+
assert(cursor.relative_index == 0);
|
|
825
|
+
|
|
826
|
+
return .{
|
|
827
|
+
.array = array,
|
|
828
|
+
.direction = direction,
|
|
829
|
+
.cursor = .{ .node = 0, .relative_index = 0 },
|
|
830
|
+
.done = true,
|
|
831
|
+
};
|
|
832
|
+
} else if (cursor.node == array.node_count - 1 and
|
|
833
|
+
cursor.relative_index == array.count(cursor.node))
|
|
834
|
+
{
|
|
835
|
+
return switch (direction) {
|
|
836
|
+
.ascending => .{
|
|
837
|
+
.array = array,
|
|
838
|
+
.direction = direction,
|
|
839
|
+
.cursor = cursor,
|
|
840
|
+
.done = true,
|
|
841
|
+
},
|
|
842
|
+
.descending => .{
|
|
843
|
+
.array = array,
|
|
844
|
+
.direction = direction,
|
|
845
|
+
.cursor = .{
|
|
846
|
+
.node = cursor.node,
|
|
847
|
+
.relative_index = cursor.relative_index - 1,
|
|
848
|
+
},
|
|
849
|
+
},
|
|
850
|
+
};
|
|
851
|
+
} else {
|
|
852
|
+
assert(cursor.node < array.node_count);
|
|
853
|
+
assert(cursor.relative_index < array.count(cursor.node));
|
|
854
|
+
|
|
855
|
+
return .{
|
|
856
|
+
.array = array,
|
|
857
|
+
.direction = direction,
|
|
858
|
+
.cursor = cursor,
|
|
859
|
+
};
|
|
860
|
+
}
|
|
861
|
+
}
|
|
862
|
+
|
|
863
|
+
pub fn iterator_from_index(
|
|
864
|
+
array: *const SegmentedArray,
|
|
865
|
+
/// First element of iteration.
|
|
866
|
+
absolute_index: u32,
|
|
867
|
+
direction: Direction,
|
|
868
|
+
) Iterator {
|
|
869
|
+
assert(absolute_index < element_count_max);
|
|
870
|
+
|
|
871
|
+
if (array.node_count == 0) {
|
|
872
|
+
assert(absolute_index == 0);
|
|
873
|
+
|
|
874
|
+
return Iterator{
|
|
875
|
+
.array = array,
|
|
876
|
+
.direction = direction,
|
|
877
|
+
.cursor = .{ .node = 0, .relative_index = 0 },
|
|
878
|
+
.done = true,
|
|
879
|
+
};
|
|
880
|
+
} else {
|
|
881
|
+
assert(absolute_index < array.len());
|
|
882
|
+
|
|
883
|
+
return Iterator{
|
|
884
|
+
.array = array,
|
|
885
|
+
.direction = direction,
|
|
886
|
+
.cursor = array.cursor_for_absolute_index(absolute_index),
|
|
887
|
+
};
|
|
888
|
+
}
|
|
889
|
+
}
|
|
890
|
+
|
|
891
|
+
/// Returns a cursor to the index of the key either exactly equal to the target key or,
|
|
892
|
+
/// if there is no exact match, the next greatest key.
|
|
893
|
+
/// Available only when `Key != null`.
|
|
894
|
+
pub fn search(
|
|
895
|
+
array: *const SegmentedArray,
|
|
896
|
+
key: K: {
|
|
897
|
+
assert(Key != null);
|
|
898
|
+
break :K Key.?;
|
|
899
|
+
},
|
|
900
|
+
) Cursor {
|
|
901
|
+
const K = Key.?;
|
|
902
|
+
if (array.node_count == 0) {
|
|
903
|
+
return .{
|
|
904
|
+
.node = 0,
|
|
905
|
+
.relative_index = 0,
|
|
906
|
+
};
|
|
907
|
+
}
|
|
908
|
+
|
|
909
|
+
var offset: usize = 0;
|
|
910
|
+
var length: usize = array.node_count;
|
|
911
|
+
while (length > 1) {
|
|
912
|
+
const half = length / 2;
|
|
913
|
+
const mid = offset + half;
|
|
914
|
+
|
|
915
|
+
const node = &array.nodes[mid].?[0];
|
|
916
|
+
|
|
917
|
+
if (key_from_value(node) < key) {
|
|
918
|
+
@branchHint(.unpredictable);
|
|
919
|
+
offset = mid;
|
|
920
|
+
}
|
|
921
|
+
|
|
922
|
+
length -= half;
|
|
923
|
+
}
|
|
924
|
+
|
|
925
|
+
// Unlike a normal binary search, don't increment the offset when "key" is higher
|
|
926
|
+
// than the element — "round down" to the previous node.
|
|
927
|
+
// This guarantees that the node result is never "== node_count".
|
|
928
|
+
//
|
|
929
|
+
// (If there are two adjacent nodes starting with keys A and C, and we search B,
|
|
930
|
+
// we want to pick the A node.)
|
|
931
|
+
const node: u32 = @intCast(offset);
|
|
932
|
+
assert(node < array.node_count);
|
|
933
|
+
|
|
934
|
+
const relative_index = binary_search_values_upsert_index(
|
|
935
|
+
K,
|
|
936
|
+
T,
|
|
937
|
+
key_from_value,
|
|
938
|
+
array.node_elements(node),
|
|
939
|
+
key,
|
|
940
|
+
.{},
|
|
941
|
+
);
|
|
942
|
+
|
|
943
|
+
// Follow the same rule as absolute_index_for_cursor:
|
|
944
|
+
// only return relative_index==array.count() at the last node.
|
|
945
|
+
if (node + 1 < array.node_count and
|
|
946
|
+
relative_index == array.count(node))
|
|
947
|
+
{
|
|
948
|
+
return .{
|
|
949
|
+
.node = node + 1,
|
|
950
|
+
.relative_index = 0,
|
|
951
|
+
};
|
|
952
|
+
} else {
|
|
953
|
+
return .{
|
|
954
|
+
.node = node,
|
|
955
|
+
.relative_index = relative_index,
|
|
956
|
+
};
|
|
957
|
+
}
|
|
958
|
+
}
|
|
959
|
+
};
|
|
960
|
+
}
|
|
961
|
+
|
|
962
|
+
test "SortedSegmentedArray duplicate elements" {
|
|
963
|
+
// Create [0, 0, 0, 100, 100, 100, ~0, ~0, ~0] array, verify that the search is left-biased.
|
|
964
|
+
const testing = std.testing;
|
|
965
|
+
|
|
966
|
+
const NodePoolType = @import("node_pool.zig").NodePoolType;
|
|
967
|
+
const TestPool = NodePoolType(128 * @sizeOf(u32), 2 * @alignOf(u32));
|
|
968
|
+
const TestArray = SortedSegmentedArrayType(
|
|
969
|
+
u32,
|
|
970
|
+
TestPool,
|
|
971
|
+
1024,
|
|
972
|
+
u32,
|
|
973
|
+
struct {
|
|
974
|
+
inline fn key_from_value(value: *const u32) u32 {
|
|
975
|
+
return value.*;
|
|
976
|
+
}
|
|
977
|
+
}.key_from_value,
|
|
978
|
+
.{ .verify = true },
|
|
979
|
+
);
|
|
980
|
+
|
|
981
|
+
var pool: TestPool = undefined;
|
|
982
|
+
try pool.init(testing.allocator, TestArray.node_count_max);
|
|
983
|
+
defer pool.deinit(testing.allocator);
|
|
984
|
+
|
|
985
|
+
var array = try TestArray.init(testing.allocator);
|
|
986
|
+
defer array.deinit(testing.allocator, &pool);
|
|
987
|
+
|
|
988
|
+
for (0..3) |index| {
|
|
989
|
+
// Elements are inserted to the left of a row of duplicates.
|
|
990
|
+
var inserted_at = array.insert_element(&pool, 0);
|
|
991
|
+
try testing.expectEqual(inserted_at, 0);
|
|
992
|
+
|
|
993
|
+
inserted_at = array.insert_element(&pool, 100);
|
|
994
|
+
try testing.expectEqual(inserted_at, @as(u32, @intCast(index + 1)));
|
|
995
|
+
|
|
996
|
+
inserted_at = array.insert_element(&pool, math.maxInt(u32));
|
|
997
|
+
try testing.expectEqual(inserted_at, @as(u32, @intCast((index + 1) * 2)));
|
|
998
|
+
}
|
|
999
|
+
try testing.expectEqual(array.len(), 9);
|
|
1000
|
+
|
|
1001
|
+
// Search finds the leftmost element.
|
|
1002
|
+
try testing.expectEqual(array.absolute_index_for_cursor(array.search(0)), 0);
|
|
1003
|
+
try testing.expectEqual(array.absolute_index_for_cursor(array.search(100)), 3);
|
|
1004
|
+
try testing.expectEqual(array.absolute_index_for_cursor(array.search(math.maxInt(u32))), 6);
|
|
1005
|
+
|
|
1006
|
+
// Ascending iterators pick the leftmost element.
|
|
1007
|
+
// Descending iterators are weird --- they _also_ pick the leftmost element, although the
|
|
1008
|
+
// rightmost makes more sense.
|
|
1009
|
+
{
|
|
1010
|
+
const target: u32 = 0;
|
|
1011
|
+
var it = array.iterator_from_cursor(array.search(target), .ascending);
|
|
1012
|
+
try testing.expectEqual(it.next().?.*, 0);
|
|
1013
|
+
try testing.expectEqual(it.next().?.*, 0);
|
|
1014
|
+
try testing.expectEqual(it.next().?.*, 0);
|
|
1015
|
+
try testing.expectEqual(it.next().?.*, 100);
|
|
1016
|
+
|
|
1017
|
+
it = array.iterator_from_cursor(array.search(target), .descending);
|
|
1018
|
+
try testing.expectEqual(it.next().?.*, 0);
|
|
1019
|
+
try testing.expectEqual(it.next(), null);
|
|
1020
|
+
}
|
|
1021
|
+
|
|
1022
|
+
{
|
|
1023
|
+
const target: u32 = 100;
|
|
1024
|
+
var it = array.iterator_from_cursor(array.search(target), .ascending);
|
|
1025
|
+
try testing.expectEqual(it.next().?.*, 100);
|
|
1026
|
+
try testing.expectEqual(it.next().?.*, 100);
|
|
1027
|
+
try testing.expectEqual(it.next().?.*, 100);
|
|
1028
|
+
try testing.expectEqual(it.next().?.*, math.maxInt(u32));
|
|
1029
|
+
|
|
1030
|
+
it = array.iterator_from_cursor(array.search(target), .descending);
|
|
1031
|
+
try testing.expectEqual(it.next().?.*, 100);
|
|
1032
|
+
try testing.expectEqual(it.next().?.*, 0);
|
|
1033
|
+
}
|
|
1034
|
+
|
|
1035
|
+
{
|
|
1036
|
+
const target: u32 = math.maxInt(u32);
|
|
1037
|
+
var it = array.iterator_from_cursor(array.search(target), .ascending);
|
|
1038
|
+
try testing.expectEqual(it.next().?.*, math.maxInt(u32));
|
|
1039
|
+
try testing.expectEqual(it.next().?.*, math.maxInt(u32));
|
|
1040
|
+
try testing.expectEqual(it.next().?.*, math.maxInt(u32));
|
|
1041
|
+
try testing.expectEqual(it.next(), null);
|
|
1042
|
+
|
|
1043
|
+
it = array.iterator_from_cursor(array.search(target), .descending);
|
|
1044
|
+
try testing.expectEqual(it.next().?.*, math.maxInt(u32));
|
|
1045
|
+
try testing.expectEqual(it.next().?.*, 100);
|
|
1046
|
+
}
|
|
1047
|
+
}
|
|
1048
|
+
|
|
1049
|
+
/// In order to avoid making internal details of segmented array public, the fuzzing code is defined
|
|
1050
|
+
/// in this file an is driven by =segmented_array_fuzz.zig`.
|
|
1051
|
+
fn FuzzContextType(
|
|
1052
|
+
comptime T: type,
|
|
1053
|
+
comptime node_size: u32,
|
|
1054
|
+
comptime element_count_max: u32,
|
|
1055
|
+
comptime Key: type,
|
|
1056
|
+
comptime key_from_value: fn (*const T) callconv(.@"inline") Key,
|
|
1057
|
+
comptime element_order: enum { sorted, unsorted },
|
|
1058
|
+
comptime options: Options,
|
|
1059
|
+
) type {
|
|
1060
|
+
return struct {
|
|
1061
|
+
const FuzzContext = @This();
|
|
1062
|
+
|
|
1063
|
+
const testing = std.testing;
|
|
1064
|
+
const log = false;
|
|
1065
|
+
|
|
1066
|
+
const NodePoolType = @import("node_pool.zig").NodePoolType;
|
|
1067
|
+
|
|
1068
|
+
// Test overaligned nodes to catch compile errors for missing @alignCast()
|
|
1069
|
+
const TestPool = NodePoolType(node_size, 2 * @alignOf(T));
|
|
1070
|
+
const TestArray = switch (element_order) {
|
|
1071
|
+
.sorted => SortedSegmentedArrayType(
|
|
1072
|
+
T,
|
|
1073
|
+
TestPool,
|
|
1074
|
+
element_count_max,
|
|
1075
|
+
Key,
|
|
1076
|
+
key_from_value,
|
|
1077
|
+
options,
|
|
1078
|
+
),
|
|
1079
|
+
.unsorted => SegmentedArrayType(T, TestPool, element_count_max, options),
|
|
1080
|
+
};
|
|
1081
|
+
|
|
1082
|
+
prng: *stdx.PRNG,
|
|
1083
|
+
|
|
1084
|
+
pool: TestPool,
|
|
1085
|
+
array: TestArray,
|
|
1086
|
+
|
|
1087
|
+
reference: std.ArrayList(T),
|
|
1088
|
+
|
|
1089
|
+
inserts: u64 = 0,
|
|
1090
|
+
removes: u64 = 0,
|
|
1091
|
+
|
|
1092
|
+
fn init(
|
|
1093
|
+
context: *FuzzContext,
|
|
1094
|
+
allocator: std.mem.Allocator,
|
|
1095
|
+
prng: *stdx.PRNG,
|
|
1096
|
+
) !void {
|
|
1097
|
+
context.* = .{
|
|
1098
|
+
.prng = prng,
|
|
1099
|
+
|
|
1100
|
+
.pool = undefined,
|
|
1101
|
+
.array = undefined,
|
|
1102
|
+
.reference = undefined,
|
|
1103
|
+
};
|
|
1104
|
+
|
|
1105
|
+
try context.pool.init(allocator, TestArray.node_count_max);
|
|
1106
|
+
errdefer context.pool.deinit(allocator);
|
|
1107
|
+
|
|
1108
|
+
context.array = try TestArray.init(allocator);
|
|
1109
|
+
errdefer context.array.deinit(allocator, &context.pool);
|
|
1110
|
+
|
|
1111
|
+
context.reference = std.ArrayList(T).init(allocator);
|
|
1112
|
+
errdefer context.reference.deinit();
|
|
1113
|
+
try context.reference.ensureTotalCapacity(element_count_max);
|
|
1114
|
+
}
|
|
1115
|
+
|
|
1116
|
+
fn deinit(context: *FuzzContext, allocator: std.mem.Allocator) void {
|
|
1117
|
+
context.array.deinit(allocator, &context.pool);
|
|
1118
|
+
context.pool.deinit(allocator);
|
|
1119
|
+
|
|
1120
|
+
context.reference.deinit();
|
|
1121
|
+
}
|
|
1122
|
+
|
|
1123
|
+
fn run(context: *FuzzContext) !void {
|
|
1124
|
+
const Action = enum { insert, remove };
|
|
1125
|
+
{
|
|
1126
|
+
var i: usize = 0;
|
|
1127
|
+
while (i < element_count_max * 2) : (i += 1) {
|
|
1128
|
+
switch (context.prng.enum_weighted(
|
|
1129
|
+
Action,
|
|
1130
|
+
.{
|
|
1131
|
+
.insert = 60,
|
|
1132
|
+
.remove = 40,
|
|
1133
|
+
},
|
|
1134
|
+
)) {
|
|
1135
|
+
.insert => try context.insert(),
|
|
1136
|
+
.remove => try context.remove(),
|
|
1137
|
+
}
|
|
1138
|
+
}
|
|
1139
|
+
}
|
|
1140
|
+
|
|
1141
|
+
{
|
|
1142
|
+
var i: usize = 0;
|
|
1143
|
+
while (i < element_count_max * 2) : (i += 1) {
|
|
1144
|
+
switch (context.prng.enum_weighted(
|
|
1145
|
+
Action,
|
|
1146
|
+
.{
|
|
1147
|
+
.insert = 40,
|
|
1148
|
+
.remove = 60,
|
|
1149
|
+
},
|
|
1150
|
+
)) {
|
|
1151
|
+
.insert => try context.insert(),
|
|
1152
|
+
.remove => try context.remove(),
|
|
1153
|
+
}
|
|
1154
|
+
}
|
|
1155
|
+
}
|
|
1156
|
+
|
|
1157
|
+
// Rarely, the code above won't generate an insert at all.
|
|
1158
|
+
if (context.inserts > 0) {
|
|
1159
|
+
try context.remove_all();
|
|
1160
|
+
}
|
|
1161
|
+
|
|
1162
|
+
if (element_order == .unsorted) {
|
|
1163
|
+
// Insert at the beginning of the array until the array is full.
|
|
1164
|
+
while (context.array.len() < element_count_max) {
|
|
1165
|
+
try context.insert_before_first();
|
|
1166
|
+
}
|
|
1167
|
+
assert(context.array.node_count >= TestArray.node_count_max - 1);
|
|
1168
|
+
|
|
1169
|
+
// Remove all-but-one elements from the last node and insert them into the first
|
|
1170
|
+
// node.
|
|
1171
|
+
const element_count_last = context.array.count(context.array.node_count - 1);
|
|
1172
|
+
var element_index: usize = 0;
|
|
1173
|
+
while (element_index < element_count_last - 1) : (element_index += 1) {
|
|
1174
|
+
try context.remove_last();
|
|
1175
|
+
try context.insert_before_first();
|
|
1176
|
+
}
|
|
1177
|
+
|
|
1178
|
+
// We should now have maxed out our node count.
|
|
1179
|
+
assert(context.array.node_count == TestArray.node_count_max);
|
|
1180
|
+
|
|
1181
|
+
try context.remove_all();
|
|
1182
|
+
}
|
|
1183
|
+
}
|
|
1184
|
+
|
|
1185
|
+
fn insert(context: *FuzzContext) !void {
|
|
1186
|
+
const reference_len: u32 = @intCast(context.reference.items.len);
|
|
1187
|
+
const count_free = element_count_max - reference_len;
|
|
1188
|
+
|
|
1189
|
+
if (count_free == 0) return;
|
|
1190
|
+
|
|
1191
|
+
var buffer: [TestArray.node_capacity * 3]T = undefined;
|
|
1192
|
+
const count_max = @min(count_free, TestArray.node_capacity * 3);
|
|
1193
|
+
const count = context.prng.range_inclusive(u32, 1, count_max);
|
|
1194
|
+
context.prng.fill(mem.sliceAsBytes(buffer[0..count]));
|
|
1195
|
+
|
|
1196
|
+
assert(context.reference.items.len <= element_count_max);
|
|
1197
|
+
|
|
1198
|
+
switch (element_order) {
|
|
1199
|
+
.unsorted => {
|
|
1200
|
+
const index = context.prng.int_inclusive(u32, reference_len);
|
|
1201
|
+
|
|
1202
|
+
context.array.insert_elements(&context.pool, index, buffer[0..count]);
|
|
1203
|
+
// TODO the standard library could use an AssumeCapacity variant of this.
|
|
1204
|
+
context.reference.insertSlice(index, buffer[0..count]) catch unreachable;
|
|
1205
|
+
},
|
|
1206
|
+
.sorted => {
|
|
1207
|
+
for (buffer[0..count]) |value| {
|
|
1208
|
+
const index_actual = context.array.insert_element(&context.pool, value);
|
|
1209
|
+
const index_expect = context.reference_index(key_from_value(&value));
|
|
1210
|
+
context.reference.insert(index_expect, value) catch unreachable;
|
|
1211
|
+
try std.testing.expectEqual(index_expect, index_actual);
|
|
1212
|
+
}
|
|
1213
|
+
},
|
|
1214
|
+
}
|
|
1215
|
+
context.inserts += count;
|
|
1216
|
+
|
|
1217
|
+
try context.verify();
|
|
1218
|
+
}
|
|
1219
|
+
|
|
1220
|
+
fn remove(context: *FuzzContext) !void {
|
|
1221
|
+
const reference_len: u32 = @intCast(context.reference.items.len);
|
|
1222
|
+
if (reference_len == 0) return;
|
|
1223
|
+
|
|
1224
|
+
const count_max = @min(reference_len, TestArray.node_capacity * 3);
|
|
1225
|
+
const count = context.prng.range_inclusive(u32, 1, count_max);
|
|
1226
|
+
|
|
1227
|
+
assert(context.reference.items.len <= element_count_max);
|
|
1228
|
+
const index = context.prng.int_inclusive(u32, reference_len - count);
|
|
1229
|
+
|
|
1230
|
+
context.array.remove_elements(&context.pool, index, count);
|
|
1231
|
+
|
|
1232
|
+
context.reference.replaceRange(index, count, &[0]T{}) catch unreachable;
|
|
1233
|
+
|
|
1234
|
+
context.removes += count;
|
|
1235
|
+
|
|
1236
|
+
try context.verify();
|
|
1237
|
+
}
|
|
1238
|
+
|
|
1239
|
+
fn insert_before_first(context: *FuzzContext) !void {
|
|
1240
|
+
assert(element_order == .unsorted);
|
|
1241
|
+
|
|
1242
|
+
const insert_index = context.array.absolute_index_for_cursor(context.array.first());
|
|
1243
|
+
|
|
1244
|
+
var element: T = undefined;
|
|
1245
|
+
context.prng.fill(mem.asBytes(&element));
|
|
1246
|
+
|
|
1247
|
+
context.array.insert_elements(&context.pool, insert_index, &.{element});
|
|
1248
|
+
context.reference.insert(insert_index, element) catch unreachable;
|
|
1249
|
+
|
|
1250
|
+
context.inserts += 1;
|
|
1251
|
+
|
|
1252
|
+
try context.verify();
|
|
1253
|
+
}
|
|
1254
|
+
|
|
1255
|
+
fn remove_last(context: *FuzzContext) !void {
|
|
1256
|
+
assert(element_order == .unsorted);
|
|
1257
|
+
|
|
1258
|
+
const remove_index = context.array.absolute_index_for_cursor(context.array.last());
|
|
1259
|
+
|
|
1260
|
+
context.array.remove_elements(&context.pool, remove_index, 1);
|
|
1261
|
+
context.reference.replaceRange(remove_index, 1, &[0]T{}) catch unreachable;
|
|
1262
|
+
|
|
1263
|
+
context.removes += 1;
|
|
1264
|
+
|
|
1265
|
+
try context.verify();
|
|
1266
|
+
}
|
|
1267
|
+
|
|
1268
|
+
fn remove_all(context: *FuzzContext) !void {
|
|
1269
|
+
while (context.reference.items.len > 0) try context.remove();
|
|
1270
|
+
|
|
1271
|
+
try testing.expectEqual(@as(u32, 0), context.array.len());
|
|
1272
|
+
try testing.expect(context.inserts > 0);
|
|
1273
|
+
try testing.expect(context.inserts == context.removes);
|
|
1274
|
+
|
|
1275
|
+
if (log) {
|
|
1276
|
+
std.debug.print("\ninserts: {}, removes: {}\n", .{
|
|
1277
|
+
context.inserts,
|
|
1278
|
+
context.removes,
|
|
1279
|
+
});
|
|
1280
|
+
}
|
|
1281
|
+
|
|
1282
|
+
try context.verify();
|
|
1283
|
+
}
|
|
1284
|
+
|
|
1285
|
+
fn verify(context: *FuzzContext) !void {
|
|
1286
|
+
if (log) {
|
|
1287
|
+
std.debug.print("expect: ", .{});
|
|
1288
|
+
for (context.reference.items) |i| std.debug.print("{}, ", .{i});
|
|
1289
|
+
|
|
1290
|
+
std.debug.print("\nactual: ", .{});
|
|
1291
|
+
var it = context.array.iterator_from_index(0, .ascending);
|
|
1292
|
+
while (it.next()) |i| std.debug.print("{}, ", .{i.*});
|
|
1293
|
+
std.debug.print("\n", .{});
|
|
1294
|
+
}
|
|
1295
|
+
|
|
1296
|
+
try testing.expectEqual(context.reference.items.len, context.array.len());
|
|
1297
|
+
|
|
1298
|
+
{
|
|
1299
|
+
var it = context.array.iterator_from_index(0, .ascending);
|
|
1300
|
+
|
|
1301
|
+
for (context.reference.items) |expect| {
|
|
1302
|
+
const actual = it.next() orelse return error.TestUnexpectedResult;
|
|
1303
|
+
try testing.expectEqual(expect, actual.*);
|
|
1304
|
+
}
|
|
1305
|
+
try testing.expectEqual(@as(?*const T, null), it.next());
|
|
1306
|
+
}
|
|
1307
|
+
|
|
1308
|
+
{
|
|
1309
|
+
var it = context.array.iterator_from_index(
|
|
1310
|
+
@as(u32, @intCast(context.reference.items.len)) -| 1,
|
|
1311
|
+
.descending,
|
|
1312
|
+
);
|
|
1313
|
+
|
|
1314
|
+
var i = context.reference.items.len;
|
|
1315
|
+
while (i > 0) {
|
|
1316
|
+
i -= 1;
|
|
1317
|
+
|
|
1318
|
+
const expect = context.reference.items[i];
|
|
1319
|
+
const actual = it.next() orelse return error.TestUnexpectedResult;
|
|
1320
|
+
try testing.expectEqual(expect, actual.*);
|
|
1321
|
+
}
|
|
1322
|
+
try testing.expectEqual(@as(?*const T, null), it.next());
|
|
1323
|
+
}
|
|
1324
|
+
|
|
1325
|
+
{
|
|
1326
|
+
for (context.reference.items, 0..) |_, i| {
|
|
1327
|
+
try testing.expect(std.meta.eql(
|
|
1328
|
+
i,
|
|
1329
|
+
context.array.absolute_index_for_cursor(
|
|
1330
|
+
context.array.cursor_for_absolute_index(@intCast(i)),
|
|
1331
|
+
),
|
|
1332
|
+
));
|
|
1333
|
+
}
|
|
1334
|
+
}
|
|
1335
|
+
|
|
1336
|
+
if (element_order == .sorted) {
|
|
1337
|
+
for (context.reference.items, 0..) |*expect, i| {
|
|
1338
|
+
if (i == 0) continue;
|
|
1339
|
+
try testing.expect(key_from_value(&context.reference.items[i - 1]) <=
|
|
1340
|
+
key_from_value(expect));
|
|
1341
|
+
}
|
|
1342
|
+
}
|
|
1343
|
+
|
|
1344
|
+
if (context.array.len() == 0) {
|
|
1345
|
+
try testing.expectEqual(@as(u32, 0), context.array.node_count);
|
|
1346
|
+
}
|
|
1347
|
+
|
|
1348
|
+
for (context.array.nodes[context.array.node_count..]) |node| {
|
|
1349
|
+
try testing.expectEqual(@as(?*[TestArray.node_capacity]T, null), node);
|
|
1350
|
+
}
|
|
1351
|
+
|
|
1352
|
+
{
|
|
1353
|
+
var i: u32 = 0;
|
|
1354
|
+
while (i < context.array.node_count -| 1) : (i += 1) {
|
|
1355
|
+
try testing.expect(context.array.count(i) >=
|
|
1356
|
+
@divExact(TestArray.node_capacity, 2));
|
|
1357
|
+
}
|
|
1358
|
+
}
|
|
1359
|
+
if (element_order == .sorted) try context.verify_search();
|
|
1360
|
+
}
|
|
1361
|
+
|
|
1362
|
+
fn verify_search(context: *FuzzContext) !void {
|
|
1363
|
+
var queries: [20]Key = undefined;
|
|
1364
|
+
context.prng.fill(mem.sliceAsBytes(&queries));
|
|
1365
|
+
|
|
1366
|
+
// Test min/max exceptional values on different SegmentedArray shapes.
|
|
1367
|
+
queries[0] = 0;
|
|
1368
|
+
queries[1] = math.maxInt(Key);
|
|
1369
|
+
|
|
1370
|
+
for (queries) |query| {
|
|
1371
|
+
try testing.expectEqual(
|
|
1372
|
+
context.reference_index(query),
|
|
1373
|
+
context.array.absolute_index_for_cursor(context.array.search(query)),
|
|
1374
|
+
);
|
|
1375
|
+
}
|
|
1376
|
+
|
|
1377
|
+
{
|
|
1378
|
+
var iterator_end = context.array.iterator_from_cursor(
|
|
1379
|
+
context.array.search(math.maxInt(Key)),
|
|
1380
|
+
.ascending,
|
|
1381
|
+
);
|
|
1382
|
+
while (iterator_end.next()) |item| {
|
|
1383
|
+
try testing.expectEqual(key_from_value(item), math.maxInt(Key));
|
|
1384
|
+
}
|
|
1385
|
+
}
|
|
1386
|
+
|
|
1387
|
+
{
|
|
1388
|
+
// 0 is not symmetric with maxInt, because `array.search` doesn't take direction
|
|
1389
|
+
// into account.
|
|
1390
|
+
var iterator_start = context.array.iterator_from_cursor(
|
|
1391
|
+
context.array.search(0),
|
|
1392
|
+
.descending,
|
|
1393
|
+
);
|
|
1394
|
+
if (context.reference.items.len == 0) {
|
|
1395
|
+
try testing.expectEqual(iterator_start.next(), null);
|
|
1396
|
+
} else {
|
|
1397
|
+
try testing.expect(iterator_start.next() != null);
|
|
1398
|
+
try testing.expectEqual(iterator_start.next(), null);
|
|
1399
|
+
}
|
|
1400
|
+
}
|
|
1401
|
+
}
|
|
1402
|
+
|
|
1403
|
+
fn reference_index(context: *const FuzzContext, key: Key) u32 {
|
|
1404
|
+
return binary_search_values_upsert_index(
|
|
1405
|
+
Key,
|
|
1406
|
+
T,
|
|
1407
|
+
key_from_value,
|
|
1408
|
+
context.reference.items,
|
|
1409
|
+
key,
|
|
1410
|
+
.{},
|
|
1411
|
+
);
|
|
1412
|
+
}
|
|
1413
|
+
};
|
|
1414
|
+
}
|
|
1415
|
+
|
|
1416
|
+
pub fn run_fuzz(allocator: std.mem.Allocator, seed: u64, comptime options: Options) !void {
|
|
1417
|
+
var prng = stdx.PRNG.from_seed(seed);
|
|
1418
|
+
|
|
1419
|
+
const CompositeKey = @import("composite_key.zig").CompositeKeyType(u64);
|
|
1420
|
+
const TableType = @import("table.zig").TableType;
|
|
1421
|
+
const TableInfoType = @import("manifest.zig").TreeTableInfoType;
|
|
1422
|
+
const TableInfo = TableInfoType(TableType(
|
|
1423
|
+
CompositeKey.Key,
|
|
1424
|
+
CompositeKey,
|
|
1425
|
+
CompositeKey.key_from_value,
|
|
1426
|
+
CompositeKey.sentinel_key,
|
|
1427
|
+
CompositeKey.tombstone,
|
|
1428
|
+
CompositeKey.tombstone_from_key,
|
|
1429
|
+
1, // Doesn't matter for this test.
|
|
1430
|
+
.general,
|
|
1431
|
+
));
|
|
1432
|
+
|
|
1433
|
+
const CompareInt = struct {
|
|
1434
|
+
inline fn key_from_value(value: *const u32) u32 {
|
|
1435
|
+
return value.*;
|
|
1436
|
+
}
|
|
1437
|
+
};
|
|
1438
|
+
|
|
1439
|
+
const CompareTable = struct {
|
|
1440
|
+
inline fn key_from_value(value: *const TableInfo) u64 {
|
|
1441
|
+
return value.address;
|
|
1442
|
+
}
|
|
1443
|
+
};
|
|
1444
|
+
|
|
1445
|
+
const TestOptions = struct {
|
|
1446
|
+
element_type: type,
|
|
1447
|
+
node_size: u32,
|
|
1448
|
+
element_count_max: u32,
|
|
1449
|
+
};
|
|
1450
|
+
|
|
1451
|
+
var tested_padding = false;
|
|
1452
|
+
var tested_node_capacity_min = false;
|
|
1453
|
+
|
|
1454
|
+
// We want to explore not just the bottom boundary but also the surrounding area
|
|
1455
|
+
// as it may also have interesting edge cases.
|
|
1456
|
+
inline for (.{
|
|
1457
|
+
TestOptions{ .element_type = u32, .node_size = 8, .element_count_max = 3 },
|
|
1458
|
+
TestOptions{ .element_type = u32, .node_size = 8, .element_count_max = 4 },
|
|
1459
|
+
TestOptions{ .element_type = u32, .node_size = 8, .element_count_max = 5 },
|
|
1460
|
+
TestOptions{ .element_type = u32, .node_size = 8, .element_count_max = 6 },
|
|
1461
|
+
TestOptions{ .element_type = u32, .node_size = 8, .element_count_max = 1024 },
|
|
1462
|
+
TestOptions{ .element_type = u32, .node_size = 16, .element_count_max = 1024 },
|
|
1463
|
+
TestOptions{ .element_type = u32, .node_size = 32, .element_count_max = 1024 },
|
|
1464
|
+
TestOptions{ .element_type = u32, .node_size = 64, .element_count_max = 1024 },
|
|
1465
|
+
TestOptions{ .element_type = TableInfo, .node_size = 256, .element_count_max = 3 },
|
|
1466
|
+
TestOptions{ .element_type = TableInfo, .node_size = 256, .element_count_max = 4 },
|
|
1467
|
+
TestOptions{ .element_type = TableInfo, .node_size = 256, .element_count_max = 1024 },
|
|
1468
|
+
TestOptions{ .element_type = TableInfo, .node_size = 512, .element_count_max = 1024 },
|
|
1469
|
+
TestOptions{ .element_type = TableInfo, .node_size = 1024, .element_count_max = 1024 },
|
|
1470
|
+
}) |test_options| {
|
|
1471
|
+
inline for (.{ .sorted, .unsorted }) |order| {
|
|
1472
|
+
const FuzzContext = FuzzContextType(
|
|
1473
|
+
test_options.element_type,
|
|
1474
|
+
test_options.node_size,
|
|
1475
|
+
test_options.element_count_max,
|
|
1476
|
+
if (test_options.element_type == u32) u32 else u64,
|
|
1477
|
+
if (test_options.element_type == u32)
|
|
1478
|
+
CompareInt.key_from_value
|
|
1479
|
+
else
|
|
1480
|
+
CompareTable.key_from_value,
|
|
1481
|
+
order,
|
|
1482
|
+
options,
|
|
1483
|
+
);
|
|
1484
|
+
|
|
1485
|
+
var context: FuzzContext = undefined;
|
|
1486
|
+
try context.init(allocator, &prng);
|
|
1487
|
+
defer context.deinit(allocator);
|
|
1488
|
+
|
|
1489
|
+
try context.run();
|
|
1490
|
+
|
|
1491
|
+
if (test_options.node_size % @sizeOf(test_options.element_type) != 0) {
|
|
1492
|
+
tested_padding = true;
|
|
1493
|
+
}
|
|
1494
|
+
if (FuzzContext.TestArray.node_capacity == 2) tested_node_capacity_min = true;
|
|
1495
|
+
}
|
|
1496
|
+
}
|
|
1497
|
+
|
|
1498
|
+
assert(tested_padding);
|
|
1499
|
+
assert(tested_node_capacity_min);
|
|
1500
|
+
}
|