Repository: ashvardanian/StringZilla (language: rust)

Issue:
Fix `findLast` offsets calculation in Swift and extend tests
It is not entirely clear, what the match offset should be in [this test](https://github.com/ashvardanian/StringZilla/blob/06423189e7a5b9e2882b471e98ec897d40734fc4/swift/Test.swift#L47-L52). Overall, we need more test coverage for such non-trivial Unicode-heavy strings in Swift.


Discussion / hints:

Fix patch:
diff --git a/.clang-format b/.clang-format
index cda5673b..c1418bae 100644
--- a/.clang-format
+++ b/.clang-format
@@ -67,4 +67,3 @@ SpacesInSquareBrackets: false
 BinPackArguments: true
 BinPackParameters: true
 PenaltyBreakBeforeFirstCallParameter: 1
-PenaltyBreakArgument: 1
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 33e768cf..ffbf94bd 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -300,7 +300,7 @@ jobs:
         run: |
           cmake -DCMAKE_BUILD_TYPE=Release -B build_release
           cmake --build build_release --config Release        
-          zip -r stringzillite_macos_${{ matrix.arch }}_${{ steps.version.outputs.version }}.zip build_release/libstringzillite.dylib include/stringzilla/stringzilla.h
+          zip -r stringzillite_macos_${{ matrix.arch }}_${{ steps.set_version.outputs.version }}.zip build_release/libstringzillite.dylib include/stringzilla/stringzilla.h
 
       - name: Upload archive
         uses: xresloader/upload-to-github-release@v1
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 35846700..aa7ccc28 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -136,7 +136,7 @@ function(set_compiler_flags target cpp_standard target_arch)
   target_compile_options(
     ${target}
     PRIVATE
-    "$<$<CXX_COMPILER_ID:MSVC>:/Bt;/wd4068;/wd4146;/utf-8>" # For MSVC, /WX would have been sufficient
+    "$<$<CXX_COMPILER_ID:MSVC>:/Bt;/wd4068;/wd4146;/utf-8;/WX>"
     "$<$<CXX_COMPILER_ID:GNU>:-Wall;-Wextra;-pedantic;-Werror;-Wfatal-errors;-Wno-unknown-pragmas;-Wno-cast-function-type;-Wno-unused-function>"
     "$<$<CXX_COMPILER_ID:Clang>:-Wall;-Wextra;-pedantic;-Werror;-Wfatal-errors;-Wno-unknown-pragmas>"
     "$<$<CXX_COMPILER_ID:AppleClang>:-Wall;-Wextra;-pedantic;-Werror;-Wfatal-errors;-Wno-unknown-pragmas>"
@@ -265,16 +265,19 @@ endif()
 if(${STRINGZILLA_BUILD_SHARED})
   add_library(stringzilla_shared SHARED c/lib.c)
   set_compiler_flags(stringzilla_shared "" "${STRINGZILLA_TARGET_ARCH}")
+  target_compile_definitions(stringzilla_shared PRIVATE "SZ_AVOID_LIBC=0")
+  target_compile_definitions(stringzilla_shared PRIVATE "SZ_OVERRIDE_LIBC=1")
   set_target_properties(stringzilla_shared PROPERTIES
-    VERSION ${PROJECT_VERSION}
-    SOVERSION 1
-    POSITION_INDEPENDENT_CODE ON
-    PUBLIC_HEADER include/stringzilla/stringzilla.h)
-
+  VERSION ${PROJECT_VERSION}
+  SOVERSION 1
+  POSITION_INDEPENDENT_CODE ON
+  PUBLIC_HEADER include/stringzilla/stringzilla.h)
+  
   # Try compiling a version without linking the LibC
   add_library(stringzillite SHARED c/lib.c)
   set_compiler_flags(stringzillite "" "${STRINGZILLA_TARGET_ARCH}")
   target_compile_definitions(stringzillite PRIVATE "SZ_AVOID_LIBC=1")
+  target_compile_definitions(stringzillite PRIVATE "SZ_OVERRIDE_LIBC=1")
   set_target_properties(stringzillite PROPERTIES
     VERSION ${PROJECT_VERSION}
     SOVERSION 1
@@ -283,9 +286,6 @@ if(${STRINGZILLA_BUILD_SHARED})
 
   # Avoid built-ins on MSVC and other compilers, as that will cause compileration errors
   target_compile_options(stringzillite PRIVATE
-    "$<$<CXX_COMPILER_ID:GNU,Clang>:-fno-builtin>"
-    "$<$<CXX_COMPILER_ID:MSVC>:/Oi->")
-  target_compile_options(stringzilla_shared PRIVATE
-    "$<$<CXX_COMPILER_ID:GNU,Clang>:-fno-builtin>"
+    "$<$<CXX_COMPILER_ID:GNU,Clang>:-fno-builtin;-nostdlib>"
     "$<$<CXX_COMPILER_ID:MSVC>:/Oi->")
 endif()
\ No newline at end of file
diff --git a/c/lib.c b/c/lib.c
index 281716d7..86831014 100644
--- a/c/lib.c
+++ b/c/lib.c
@@ -27,15 +27,15 @@
 // If we don't have the LibC, the `malloc` definition in `stringzilla.h` will be illformed.
 #ifdef _MSC_VER
 typedef sz_size_t size_t; // Reuse the type definition we've inferred from `stringzilla.h`
+extern __declspec(dllimport) int rand(void);
+extern __declspec(dllimport) void free(void *start);
+extern __declspec(dllimport) void *malloc(size_t length);
 #else
 typedef __SIZE_TYPE__ size_t; // For GCC/Clang
+extern int rand(void);
+extern void free(void *start);
+extern void *malloc(size_t length);
 #endif
-int rand(void) { return 0; }
-void free(void *start) { sz_unused(start); }
-void *malloc(size_t length) {
-    sz_unused(length);
-    return SZ_NULL;
-}
 #endif
 
 SZ_DYNAMIC sz_capability_t sz_capabilities(void) {
@@ -132,7 +132,12 @@ typedef struct sz_implementations_t {
     sz_hashes_t hashes;
 
 } sz_implementations_t;
-static sz_implementations_t sz_dispatch_table;
+
+#if defined(_MSC_VER)
+__declspec(align(64)) static sz_implementations_t sz_dispatch_table;
+#else
+__attribute__((aligned(64))) static sz_implementations_t sz_dispatch_table;
+#endif
 
 /**
  *  @brief  Initializes a global static "virtual table" of supported backends
@@ -211,8 +216,8 @@ static void sz_dispatch_table_init(void) {
 }
 
 #if defined(_MSC_VER)
-BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpReserved) {
-    switch (fdwReason) {
+BOOL WINAPI DllMain(HINSTANCE hints, DWORD forward_reason, LPVOID lp) {
+    switch (forward_reason) {
     case DLL_PROCESS_ATTACH: sz_dispatch_table_init(); return TRUE;
     case DLL_THREAD_ATTACH: return TRUE;
     case DLL_THREAD_DETACH: return TRUE;
@@ -336,14 +341,18 @@ SZ_DYNAMIC sz_cptr_t sz_rfind_char_not_from(sz_cptr_t h, sz_size_t h_length, sz_
     return sz_rfind_charset(h, h_length, &set);
 }
 
+#if !SZ_AVOID_LIBC
 sz_u64_t _sz_random_generator(void *empty_state) {
     sz_unused(empty_state);
     return (sz_u64_t)rand();
 }
+#endif
 
 SZ_DYNAMIC void sz_generate(sz_cptr_t alphabet, sz_size_t alphabet_size, sz_ptr_t result, sz_size_t result_length,
                             sz_random_generator_t generator, void *generator_user_data) {
+#if !SZ_AVOID_LIBC
     if (!generator) generator = _sz_random_generator;
+#endif
     sz_generate_serial(alphabet, alphabet_size, result, result_length, generator, generator_user_data);
 }
 
diff --git a/include/stringzilla/stringzilla.h b/include/stringzilla/stringzilla.h
index ffcef9bb..8e4765ce 100644
--- a/include/stringzilla/stringzilla.h
+++ b/include/stringzilla/stringzilla.h
@@ -4990,7 +4990,8 @@ SZ_INTERNAL sz_ssize_t _sz_alignment_score_wagner_fisher_upto17m_avx512( //
                     _mm512_maskz_loadu_epi32((__mmask16)mask, previous_distances + idx_longer + 32);
                 cost_substitution_vec.zmm = _mm512_add_epi32(
                     cost_substitution_vec.zmm, _mm512_cvtepi16_epi32(_mm512_extracti64x4_epi64(current_32_63_vec, 0)));
-                cost_deletion_vec.zmm = _mm512_maskz_loadu_epi32(mask, previous_distances + 1 + idx_longer + 32);
+                cost_deletion_vec.zmm =
+                    _mm512_maskz_loadu_epi32((__mmask16)mask, previous_distances + 1 + idx_longer + 32);
                 cost_deletion_vec.zmm = _mm512_add_epi32(cost_deletion_vec.zmm, gap_vec.zmm);
                 current_vec.zmm = _mm512_max_epi32(cost_substitution_vec.zmm, cost_deletion_vec.zmm);
 
diff --git a/scripts/bench.hpp b/scripts/bench.hpp
index b685474a..03bc4d1e 100644
--- a/scripts/bench.hpp
+++ b/scripts/bench.hpp
@@ -19,12 +19,6 @@
 
 #include "test.hpp" // `read_file`
 
-#if SZ_DEBUG // Make debugging faster
-#define default_seconds_m 10
-#else
-#define default_seconds_m 30
-#endif
-
 namespace sz = ashvardanian::stringzilla;
 
 namespace ashvardanian {
@@ -162,6 +156,8 @@ inline std::vector<result_string_type> filter_by_length(std::vector<from_string_
     return result;
 }
 
+inline static std::size_t seconds_per_benchmark = 5;
+
 struct dataset_t {
     std::string text;
     std::vector<std::string_view> tokens;
@@ -206,9 +202,20 @@ inline dataset_t make_dataset_from_path(std::string path) {
 /**
  *  @brief  Loads a dataset, depending on the passed CLI arguments.
  */
-inline dataset_t make_dataset(int argc, char const *argv[]) {
-    if (argc != 2) { throw std::runtime_error("Usage: " + std::string(argv[0]) + " <path>"); }
-    return make_dataset_from_path(argv[1]);
+inline dataset_t prepare_benchmark_environment(int argc, char const *argv[]) {
+    if (argc < 2 || argc > 3)
+        throw std::runtime_error("Usage: " + std::string(argv[0]) + " <path> [seconds_per_benchmark]");
+
+    dataset_t data = make_dataset_from_path(argv[1]);
+
+    // If the seconds_per_benchmark argument is provided, update the value in the dataset
+    if (argc == 3) {
+        seconds_per_benchmark = std::stoi(argv[2]);
+        if (seconds_per_benchmark == 0)
+            throw std::invalid_argument("The number of seconds per task must be greater than 0.");
+    }
+
+    return data;
 }
 
 inline sz_string_view_t to_c(std::string_view str) noexcept { return {str.data(), str.size()}; }
@@ -224,8 +231,7 @@ inline sz_string_view_t to_c(sz_string_view_t str) noexcept { return str; }
  *  @return Number of seconds per iteration.
  */
 template <typename strings_type, typename function_type>
-benchmark_result_t bench_on_tokens(strings_type &&strings, function_type &&function,
-                                   seconds_t max_time = default_seconds_m) {
+benchmark_result_t bench_on_tokens(strings_type &&strings, function_type &&function) {
 
     namespace stdc = std::chrono;
     using stdcc = stdc::high_resolution_clock;
@@ -245,7 +251,7 @@ benchmark_result_t bench_on_tokens(strings_type &&strings, function_type &&funct
 
         stdcc::time_point t2 = stdcc::now();
         result.seconds = stdc::duration_cast<stdc::nanoseconds>(t2 - t1).count() / 1.e9;
-        if (result.seconds > max_time) break;
+        if (result.seconds > seconds_per_benchmark) break;
     }
 
     return result;
@@ -259,8 +265,7 @@ benchmark_result_t bench_on_tokens(strings_type &&strings, function_type &&funct
  *  @return Number of seconds per iteration.
  */
 template <typename strings_type, typename function_type>
-benchmark_result_t bench_on_token_pairs(strings_type &&strings, function_type &&function,
-                                        seconds_t max_time = default_seconds_m) {
+benchmark_result_t bench_on_token_pairs(strings_type &&strings, function_type &&function) {
 
     namespace stdc = std::chrono;
     using stdcc = stdc::high_resolution_clock;
@@ -282,7 +287,7 @@ benchmark_result_t bench_on_token_pairs(strings_type &&strings, function_type &&
 
         stdcc::time_point t2 = stdcc::now();
         result.seconds = stdc::duration_cast<stdc::nanoseconds>(t2 - t1).count() / 1.e9;
-        if (result.seconds > max_time) break;
+        if (result.seconds > seconds_per_benchmark) break;
     }
 
     return result;
diff --git a/scripts/bench_container.cpp b/scripts/bench_container.cpp
index 6a77ba45..17cd1ec6 100644
--- a/scripts/bench_container.cpp
+++ b/scripts/bench_container.cpp
@@ -72,7 +72,7 @@ void bench_tokens(strings_type const &strings) {
 int main(int argc, char const **argv) {
     std::printf("StringZilla. Starting search benchmarks.\n");
 
-    dataset_t dataset = make_dataset(argc, argv);
+    dataset_t dataset = prepare_benchmark_environment(argc, argv);
 
     // Baseline benchmarks for real words, coming in all lengths
     std::printf("Benchmarking on real words:\n");
diff --git a/scripts/bench_search.cpp b/scripts/bench_search.cpp
index ca869334..ada4ded4 100644
--- a/scripts/bench_search.cpp
+++ b/scripts/bench_search.cpp
@@ -25,7 +25,7 @@ tracked_binary_functions_t find_functions() {
     tracked_binary_functions_t result = {
         {"std::string_view.find",
          [](std::string_view h, std::string_view n) {
-             auto match = h.find(n);
+             auto match = n.size() == 1 ? h.find(n.front()) : h.find(n);
              return (match == std::string_view::npos ? h.size() : match);
          }},
         {"sz_find_serial", wrap_sz(sz_find_serial), true},
@@ -38,15 +38,17 @@ tracked_binary_functions_t find_functions() {
 #if SZ_USE_ARM_NEON
         {"sz_find_neon", wrap_sz(sz_find_neon), true},
 #endif
-        {"strstr",
+        {"strstr/strchr",
          [](std::string_view h, std::string_view n) {
-             sz_cptr_t match = strstr(h.data(), n.data());
+             sz_cptr_t match = n.size() == 1 ? (sz_cptr_t)strchr(h.data(), n.front()) //
+                                             : (sz_cptr_t)strstr(h.data(), n.data());
              return (match ? match - h.data() : h.size());
          }},
 #ifdef _GNU_SOURCE
-        {"memmem", // Not supported on MSVC
+        {"memmem/memchr", // Not supported on MSVC
          [](std::string_view h, std::string_view n) {
-             sz_cptr_t match = (sz_cptr_t)memmem(h.data(), h.size(), n.data(), n.size());
+             sz_cptr_t match = n.size() == 1 ? (sz_cptr_t)memchr(h.data(), n.front(), h.size())
+                                             : (sz_cptr_t)memmem(h.data(), h.size(), n.data(), n.size());
              return (match ? match - h.data() : h.size());
          }},
 #endif
@@ -84,7 +86,7 @@ tracked_binary_functions_t rfind_functions() {
     tracked_binary_functions_t result = {
         {"std::string_view.rfind",
          [](std::string_view h, std::string_view n) {
-             auto match = h.rfind(n);
+             auto match = n.size() == 1 ? h.rfind(n.front()) : h.rfind(n);
              return (match == std::string_view::npos ? 0 : match);
          }},
         {"sz_rfind_serial", wrap_sz(sz_rfind_serial), true},
@@ -290,19 +292,23 @@ void bench_search(std::string const &haystack, std::vector<std::string> const &s
 int main(int argc, char const **argv) {
     std::printf("StringZilla. Starting search benchmarks.\n");
 
-    dataset_t dataset = make_dataset(argc, argv);
+    dataset_t dataset = prepare_benchmark_environment(argc, argv);
 
     // Splitting by new lines
     std::printf("Benchmarking for a newline symbol:\n");
     bench_finds(dataset.text, {"\n"}, find_functions());
     bench_rfinds(dataset.text, {"\n"}, rfind_functions());
 
+    std::printf("Benchmarking for one whitespace:\n");
+    bench_finds(dataset.text, {" "}, find_functions());
+    bench_rfinds(dataset.text, {" "}, rfind_functions());
+
     std::printf("Benchmarking for an [\\n\\r\\v\\f] RegEx:\n");
     bench_finds(dataset.text, {"\n\r\v\f"}, find_charset_functions());
     bench_rfinds(dataset.text, {"\n\r\v\f"}, rfind_charset_functions());
 
     // Typical ASCII tokenization and validation benchmarks
-    std::printf("Benchmarking for whitespaces:\n");
+    std::printf("Benchmarking for all whitespaces:\n");
     bench_finds(dataset.text, {{sz::whitespaces(), sizeof(sz::whitespaces())}}, find_charset_functions());
     bench_rfinds(dataset.text, {{sz::whitespaces(), sizeof(sz::whitespaces())}}, rfind_charset_functions());
 
diff --git a/scripts/bench_similarity.cpp b/scripts/bench_similarity.cpp
index e1112a69..b2c36a60 100644
--- a/scripts/bench_similarity.cpp
+++ b/scripts/bench_similarity.cpp
@@ -107,7 +107,7 @@ void bench_similarity_on_bio_data() {
 
 void bench_similarity_on_input_data(int argc, char const **argv) {
 
-    dataset_t dataset = make_dataset(argc, argv);
+    dataset_t dataset = prepare_benchmark_environment(argc, argv);
 
     // Baseline benchmarks for real words, coming in all lengths
     std::printf("Benchmarking on real words:\n");
diff --git a/scripts/bench_sort.cpp b/scripts/bench_sort.cpp
index bc4af07a..a5abd045 100644
--- a/scripts/bench_sort.cpp
+++ b/scripts/bench_sort.cpp
@@ -142,7 +142,7 @@ void bench_permute(char const *name, strings_t &strings, permute_t &permute, alg
 
 int main(int argc, char const **argv) {
     std::printf("StringZilla. Starting sorting benchmarks.\n");
-    dataset_t dataset = make_dataset(argc, argv);
+    dataset_t dataset = prepare_benchmark_environment(argc, argv);
     strings_t strings {dataset.tokens.begin(), dataset.tokens.end()};
 
     permute_t permute_base, permute_new;
@@ -203,6 +203,7 @@ int main(int argc, char const **argv) {
             qsort_r(array.order, array.count, sizeof(sz_u64_t), _get_qsort_order, &array);
         });
         expect_sorted(strings, permute_new);
+#elif defined(_MSC_VER)
 #else
         sz_unused(_get_qsort_order);
 #endif
diff --git a/scripts/bench_token.cpp b/scripts/bench_token.cpp
index 4eb1db04..0a57ff69 100644
--- a/scripts/bench_token.cpp
+++ b/scripts/bench_token.cpp
@@ -161,7 +161,7 @@ void bench(strings_type &&strings) {
 }
 
 void bench_on_input_data(int argc, char const **argv) {
-    dataset_t dataset = make_dataset(argc, argv);
+    dataset_t dataset = prepare_benchmark_environment(argc, argv);
 
     std::printf("Benchmarking on the entire dataset:\n");
     bench_unary_functions(dataset.tokens, random_generation_functions(100));
diff --git a/swift/Test.swift b/swift/Test.swift
index 908c0c3c..4b839022 100644
--- a/swift/Test.swift
+++ b/swift/Test.swift
@@ -44,17 +44,21 @@ class StringZillaTests: XCTestCase {
         XCTAssertEqual(testString[index...], "Hello, world! Welcome to StringZilla. 👋")
     }
 
-    // TODO: This fails!
-    // func testFindLastCharacterNotFromSet() {
-    //     let index = testString.findLast(characterNotFrom: "aeiou")!
-    //     XCTAssertEqual(testString.distance(from: testString.startIndex, to: index), 38)
-    //     XCTAssertEqual(testString[index...], "👋")
-    // }
+     func testFindLastCharacterNotFromSet() {
+        let index = testString.findLast(characterNotFrom: "aeiou")!
+        XCTAssertEqual(testString.distance(from: testString.startIndex, to: index), 38)
+        XCTAssertEqual(testString[index...], "👋")
+    }
     
     func testEditDistance() {
         let otherString = "Hello, world!"
-        let distance = try? testString.editDistance(from: otherString) // Using try?
+        let distance = try? testString.editDistance(from: otherString)
         XCTAssertNotNil(distance)
         XCTAssertEqual(distance, 29)
     }
+    
+    func testFindLastCharacterNotFromSetNoMatch() {
+        let index = "aeiou".findLast(characterNotFrom: "aeiou")
+        XCTAssertNil(index)
+    }
 }


Test patch: