Skip to content

[C-API] SVS Pure-C API binding - #284

Open
rfsaliev wants to merge 32 commits into
mainfrom
dev/c-api
Open

[C-API] SVS Pure-C API binding#284
rfsaliev wants to merge 32 commits into
mainfrom
dev/c-api

Conversation

@rfsaliev

Copy link
Copy Markdown
Member

NOTE: This PR is in-progress and marked as 'Draft' to prevent merging until completion

Scalable Vector Search C API bindings.

This pull request introduces a new C API binding for the project, providing C language access to the core Scalable Vector Search (SVS) functionality. The changes add a complete build system for the C API, define the public C API headers, and include a sample program to demonstrate usage. The most important changes are grouped below:

C API Design Document

  • Added the bindings/SVS_C_API_Design.md document which describes the design proposal for the Scalable Vector Search (SVS) C API including: architecture overview, core components design, naming conventions, usage rules, draft API reference, etc.

C API Implementation and Build System:

  • Added a new bindings/c directory with a CMakeLists.txt to build the shared library svs_c_api, set up installation rules, and link dependencies such as OpenMP and the core SVS library.
  • Updated the root CMakeLists.txt to include the new C API bindings in the build process.
  • Added a CMake config template (c_apiConfig.cmake.in) for downstream projects to find and use the C API library.

C API Public Headers:

  • Introduced svs_c.h and svs_c_config.h in bindings/c/include/svs/c_api/, defining the C API's types, enums, opaque handles, and functions for error handling, algorithm configuration, storage configuration, index building, searching, and result management. [1] [2]

Samples and Demonstration:

  • Added a samples directory with a CMakeLists.txt to build a simple example (c_api_simple) demonstrating how to use the new C API.

rfsaliev and others added 28 commits March 4, 2026 15:22
Add `svs_index_load()` and `svs_index_save()` API implementation for
static Vamana index
Done:

- [x] Create dynamic index with specified block size (default block size
should be supported)
- [x] Initialized with a dataset and labels list
- [x] Add labeled vectors to a dynamic index
- [x] Remove vectors by labels
- [x] Check if a label exists
- [x] Compute distance for label
- [x] Get vector by label
- [x] Consolidate/compact dynamic index
- [x] Implement Save/Load
Adds `svs_index_get_num_threads` / `svs_index_set_num_threads` to the C
API, enabling dynamic inspection and resizing of the search threadpool
after index construction.

### ThreadPoolBuilder
- Added `get_threads_num()` — delegates to the custom pool's `size()` op
when `kind == CUSTOM`, otherwise returns the stored count
- Added `resize(n)` — updates stored thread count; throws
`std::invalid_argument` for `n == 0`, `SINGLE_THREAD`, or `CUSTOM` kinds
(surfaced as `SVS_ERROR_INVALID_ARGUMENT` through `wrap_exceptions`)

### Index wrappers (`index.hpp`)
- `Index` stores a `ThreadPoolBuilder`; `get_num_threads()` is
pure-virtual — implemented in `IndexVamana` and `DynamicIndexVamana` by
delegating to the wrapped `svs::Vamana` / `svs::DynamicVamana` instance,
so the value reflects actual runtime state
- `set_num_threads(n)` calls `pool_builder.resize(n)` then rebuilds and
installs the threadpool via `set_threadpool()`

### C API (`svs_c.cpp` / `svs_c.h`)
- Both entry points validate `index->impl` non-null before dereferencing
(consistent with existing handle-check pattern)
- Public header documents supported kinds and expected error codes for
unsupported configurations
Resolve cmake version compatibility issue caused by using
DOWNLOAD_EXTRACT_TIMESTAMP which is introduced in v.3.24

This PR fixes #317
…306)

This pull request introduces a comprehensive C API test suite for the
SVS project, leveraging the Catch2 testing framework. It adds new test
files covering all major C API functionalities, integrates automated
test building and execution into the CMake build system, and improves
error handling and testability for dynamic index operations.

**C API Test Infrastructure and Test Coverage:**

* Added a new directory of C API tests using Catch2, with individual
test files for error handling, algorithm configuration, storage, search
parameters, index building, and dynamic index operations.

**Dynamic Index Error Handling:**

* Refactored `svs_index_dynamic_delete_points` to improve error handling.
- Introduced `svs_id_filter_interface`  to define filtering operations.
- Implemented `svs_index_search_topK` to support an optional ID filter
for search operations.
- Updated existing search functions to use the new filtered search
capabilities.
- Added a new source file `filtered_search.hpp` containing the logic for
filtered top-K search.
- Modified existing samples and tests to demonstrate and validate the
new filtering functionality.
- Marked the previous `svs_index_search` function as deprecated,
directing users to use `svs_index_search_topK` instead.
…n) (#354)

## Summary

Exposes memory accounting in the **C API** for the Valkey-search
integration:

- `svs_index_get_memory_usage(index, size_t* out_bytes, err)` — total
allocated bytes.
- `svs_index_get_memory_breakdown(index, svs_memory_breakdown_t* out,
err)` — `{graph_bytes, data_bytes, metadata_bytes}` component split.
~~- `svs_index_element_size(index, size_t* out_bytes, err)` — bytes per
stored vector.~~ (keep at data level)

All follow the existing C API conventions (out-param + `svs_error_h`,
`wrap_exceptions`), matching the Phase-A design in the memory-accounting
contract (intel-innersource #333).

## Layers

- **C API** (`bindings/c`): the three functions +
`svs_memory_breakdown_t` in `svs_c.h`; interface virtuals + concrete
overrides in `src/index.hpp`; impls in `src/svs_c.cpp`.
- **Core / orchestrator**: brings in `get_memory_breakdown()`
(`MemoryBreakdown` struct + capacity-based
`svs::data::detail::dataset_allocated_bytes` helper) on `VamanaIndex` /
`MutableVamanaIndex` and through the orchestrator, plus an
`element_size()` accessor parallel to `dimensions()`. This mirrors the
approved public PR #345 so the C API can build and test standalone; once
#345 lands on `dev/c-api`, this reduces to just the C API layer.

## Tests

`bindings/c/tests/c_api_index.cpp` (static) and
`c_api_dynamic_index.cpp` (dynamic): usage > 0, breakdown total ==
usage, `graph_bytes`/`data_bytes` > 0 (metadata > 0 for dynamic),
`element_size == sizeof(float) * dimensions`, and null-arg handling.
Both test cases pass (84 / 166 assertions).

Related: builds on #345; memory-accounting
contract in intel-innersource #333 / #326.
#360 reopened directly
to C API branch

---------

Co-authored-by: Rafik Saliev <rafik.f.saliev@intel.com>
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Comment thread examples/c/CMakeLists.txt

@ethanglaser ethanglaser Aug 13, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a difference between scope of these "samples" vs. "examples" in repo root? I'd say it'd be more aligned with the rest of the repo to move bindings/c/samples to examples/c

@rfsaliev rfsaliev Aug 18, 2026

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for the note.
As far as bindings/c is not referenced in the root CMakeLists.txt, I would do following steps:

  1. Add bindings/c to the root CMake configuration (controlled by option SVS_BUILD_C_API)
  2. Move binding/c/samples to examples/c
  3. Modify examples/CMakeLists to add_subdirectory("c") if SVS_BUILD_C_API is ON (or if(TARGET svs_c_api) )
  4. Modify/update CI workflow and scripts to use root CMake directory for C API building

These changes can be made in this PR, or later upon merge - @ethanglaser, your opinion?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it can be done in this PR easily enough, unless there was a reason we avoided doing this. It would align with runtime bindings and the remainder of the repo which would be preferable

@rfsaliev rfsaliev Aug 19, 2026

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would align with runtime bindings and the remainder of the repo which would be preferable

Unfortunately, there is no examples for runtime bindings and no reference to runtime bindings from the root CMakeLists.txt. It seems C API samples/examples cannot be aligned with

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've moved bindings/c/samples to examples in the last commit.

**Important note:**
> **This API refactoring breaks compatibility with existing client code**

Refactor API for better consitency, stability, extensibility.
- Updated ThreadPoolBuilder to ensure custom threadpool pointers are validated and initialized correctly.
- Enhanced error handling in parallel_for method to catch exceptions and rethrow them appropriately.
- Modified IDFilterAdapter to check for null operations and validate filter rates during initialization.
- Adjusted test cases to reflect changes in function signatures and ensure proper error handling.
- Introduced new utility functions for initializing search results and memory breakdown structures.
- Updated sequential threadpool implementation to return a boolean indicating success
- All public headers moved to `include/svs/c/`, and installation paths
updated to match, replacing the old `c_api` directory.
- Added generated version header `svs_c_version.h` with version macros, configured and installed via CMake.
- Refactored `svs_search_result_t` structure now allows user to pre-allocate result buffers.
- Added a detailed `README.md` for the C API, including build instructions, usage, and sample code.
@rfsaliev
rfsaliev marked this pull request as ready for review August 18, 2026 10:18
@rfsaliev
rfsaliev requested a review from ethanglaser August 19, 2026 15:20
@ahuber21

Copy link
Copy Markdown
Contributor

Thanks for the ping, Rafik — this is a big, well-structured surface and the sub-PR history made it much easier to follow. I reviewed the whole branch against origin/main (merge-base cfb57c75) along two axes: conformance to the repo's documented standards, and fidelity to bindings/c/docs/C_API_Design.md.

Splitting my feedback into three comments: this one is the set I'd want resolved before merge, then one on naming (needs a decision now rather than after release), then non-blocking cleanups and open questions.


1. Drop the four include/svs/ changes

include/svs/index/vamana/{index,dynamic_index}.h and include/svs/orchestrators/{vamana,dynamic_vamana}.h are 61 insertions / 57 deletions that relocate get_memory_breakdown within each class and rewrite its doc comment. get_memory_breakdown already exists on main#345 landed. #354's own commit message anticipated this: "once #345 lands on dev/c-api, this reduces to just the C API layer."

Worth removing rather than leaving as harmless churn, because moving a pure virtual in VamanaInterface shifts every subsequent vtable slot. include/svs/AGENTS.md lists "changing header-only code without validating ABI compatibility" as a failure mode to avoid, and it bites concretely here: bindings/c/CMakeLists.txt:156 links a prebuilt v0.4.0/svs-shared-library.tar.gz compiled against the un-reordered layout.

2. Partial results are unexpressible — resolve the filtered_search.hpp TODO

These two are one decision, so raising them together.

svs_c.cpp:686 always writes offsets[i] = i * k. The inline accessor svs_search_results_row (svs_c.h:352) derives count = offsets[q+1] - offsets[q], so count is always k regardless of how many neighbours actually passed the filter. Shortfalls are padded with id == (size_t)-1, dist == inf (filtered_search.hpp:130-134), and that sentinel is not documented anywhere in svs_c.h — so a caller cannot distinguish a hit from padding. The result loop in README.md:150 would print the padding as results.

That means the CSR layout described at svs_c.h:244-249"This supports variadic per-query result counts (filtered search, range search)" — is built and then bypassed.

This is also why the TODO at filtered_search.hpp:211-212 is unresolved:

// TODO: clarify the contract here - should we return partial or no
// result if the hit rate is too low
if (found < num_neighbors && !hit_rate_sufficient(total_checked, found, filter_rate)) {
    found = 0;
    break;
}

found = 0 discards neighbours already validated for that query. (The other early-out at :180-185 is fine by comparison — it's a pre-search check off a filter sample, so no work is thrown away, though set_empty_result clearing every row does read oddly against the spec's "returns an empty result set for that query" at C_API_Design.md:400.)

Populating offsets with true cumulative counts answers the TODO on its own: partial results become expressible, found = 0 is no longer needed, and the sentinel disappears. Since this is the shape of a public result struct, I'd rather settle it before release than ship the padding contract and change it later.

3. Leak in ensure_search_results_capacity

svs_c.cpp:621-628, the zero-initialized branch, sets owns_buffers last:

r->offsets = new size_t[min_offsets_capacity];   // succeeds
r->offsets_capacity = min_offsets_capacity;
r->indices = new size_t[min_results_capacity];   // throws bad_alloc
...
r->owns_buffers = true;                          // never reached

owns_buffers stays false, so svs_search_results_free early-returns at :706 and offsets is unreachable for the rest of the process. (The grow branch above it is safe — owns_buffers is already true there.)

Hoisting owns_buffers = true to the top of the branch would fix the leak, but leaves the caller holding a half-built descriptor they must free, while svs_c.h:255 says "On zero-initialized objects the free call is a no-op". Commit-on-success keeps that promise:

} else if (r->offsets == nullptr && r->indices == nullptr && r->distances == nullptr) {
    // Descriptor was zero-initialized: allocate first, then commit, so a throw
    // leaves the descriptor untouched and there is nothing for the caller to free.
    auto offsets = std::make_unique_for_overwrite<size_t[]>(min_offsets_capacity);
    auto indices = std::make_unique_for_overwrite<size_t[]>(min_results_capacity);
    auto distances = std::make_unique_for_overwrite<float[]>(min_results_capacity);
    r->offsets = offsets.release();
    r->indices = indices.release();
    r->distances = distances.release();
    r->offsets_capacity = min_offsets_capacity;
    r->results_capacity = min_results_capacity;
    r->owns_buffers = true;
}

make_unique_for_overwrite is C++20 and avoids the zero-fill make_unique<T[]> would cost on large buffers; needs <memory>.

The same shape is worth applying to the grow branch for a different reason: it delete[]s the caller's existing buffers before allocating the replacements, so a bad_alloc there loses the previous contents and resets capacity to 0 — not a leak, but a reused descriptor silently empties.

4. svs_error_create throws where the header promises NULL

error.cpp:22:

extern "C" svs_error_h svs_error_create() { return new svs_error_desc{}; }

against svs_c.h:449-453:

@return A handle to the created error object or NULL if creation failed (e.g., due to memory allocation failure)
@remarks If this returns NULL, the value may still be passed as the optional out_err argument to other API functions…

new throws std::bad_alloc, so the documented NULL is unreachable and a C caller gets an exception across the extern "C" boundary instead — no handler in a C translation unit. One word:

extern "C" svs_error_h svs_error_create() { return new (std::nothrow) svs_error_desc{}; }

(needs <new>). svs_error_set at :23 already guards itself with try/catch, so this looks like an oversight rather than a design choice. The other non-wrap_exceptions entry points (svs_algorithm_free, svs_index_free, svs_search_results_free, and the remaining error.cpp getters) all look fine to me — this is the only one with both a throw path and a contract it breaks.

5. struct_size validation contradicts its own documented contract

svs_c.h:261-267 states:

On any write to this OUT struct, the library only touches fields covered by the caller-supplied struct_size. A caller compiled against an older header will never observe writes beyond its known fields.

But svs_c.cpp:664 rejects any such caller outright:

INVALID_ARGUMENT_IF(
    out_results->struct_size != sizeof(svs_search_results_t),
    "Incompatible svs_search_results_t struct_size"
);

svs_index_get_memory_breakdown uses > sizeof(...) at svs_c.cpp:970, which is the behaviour the contract describes. I believe :970 is right and :664 should match — but since the two disagree, worth confirming which you intended before I assume.

6. SVS_INIT_ID_FILTER_OPS cannot express the documented optional filter_rate

svs_c.h:206 and C_API_Design.md:384 both document filter_rate as optional ("or NULL / 0.0"), and types_support.hpp:85 handles the NULL case correctly. But the mandated macro takes the address of its argument (svs_c.h:223):

.is_member = &is_member_func, .filter_rate = &filter_rate_func

so SVS_INIT_ID_FILTER_OPS(my_is_member, NULL) expands to &NULL and won't compile. Dropping both & and having callers pass &my_is_member / NULL is the smaller fix and matches ordinary C initialization; a separate SVS_INIT_ID_FILTER_OPS_NO_RATE(is_member_func) would also work if you'd rather keep existing call sites untouched.

@ahuber21

Copy link
Copy Markdown
Contributor

Naming consistency — worth deciding now, not after release

Separating this from the defect list because it's a compatibility question rather than a bug, and because #363 already broke client compatibility once — that makes this the cheap moment to settle it.

C_API_Design.md:185 states the rule:

svs_<object>[_<specialization>]_<operation>

The good news is that the conforming form already dominates the header: eight functions are svs_algorithm_vamana_* and four are svs_index_dynamic_*. Only seven names put the specialization after the operation, and each has a sibling demonstrating the target form:

Current Suggested Precedent already in svs_c.h
svs_algorithm_create_vamana svs_algorithm_vamana_create svs_algorithm_vamana_get_alpha
svs_search_params_create_vamana svs_search_params_vamana_create
svs_storage_create_simple svs_storage_simple_create
svs_storage_create_lvq svs_storage_lvq_create
svs_storage_create_leanvec svs_storage_leanvec_create
svs_storage_create_sq svs_storage_sq_create
svs_index_build_dynamic svs_index_dynamic_build svs_index_dynamic_add_points
svs_index_load_dynamic svs_index_dynamic_load svs_index_dynamic_compact

The svs_index_*_dynamic pair is the one I'd push hardest on: svs_index_build_dynamic and svs_index_dynamic_add_points coexist in the same header with the specialization on opposite sides of the operation, which makes the API hard to predict from memory.

One thing to fix either way: the design document's own example table at C_API_Design.md:194 presents svs_storage_create_lvq() as conforming

| svs_storage_create_lvq() | svs + storage + create + lvq | Create LVQ storage configuration |

— nine lines after stating the rule that puts <specialization> before <operation>. So either the table or the rule needs correcting, independent of whether the renames happen.

Happy to defer the renames to a follow-up if you'd rather not churn the samples and tests in this PR — but in that case I'd want the follow-up issue opened before merge, since the cost only goes up once there are external callers.

@ahuber21 ahuber21 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please find my feedback in previous comments.

@ahuber21

Copy link
Copy Markdown
Contributor

Non-blocking: doc drift, cleanups, and open questions

None of these need to hold the merge, but a few are cheap and the first two are questions only you can answer.

Questions

The C++ runtime binding changes look deliberate but undeclared. bindings/cpp/CMakeLists.txt:61-64 adds SVS_REQUIRE_LTO_ARCHIVE and :151-160 adds the CMake-3.24 DOWNLOAD_EXTRACT_TIMESTAMP guard — both ports of fixes already made on the C side (#318), with considered comments, so clearly intentional. But nothing in the PR description mentions touching bindings/cpp, and reviewers of a C-API PR won't be looking there. Two things:

  • Would you rather split this into its own PR so it gets looked at on its own terms?
  • The non-LTO fallback is now pinned to v0.4.0 (:159) while the LTO path stays on nightly-2026-07-21-127 (:146). Is that release/nightly split intentional?

Your own 4-step CMake plan is 1/4 done. From the thread on 2026-08-18 you proposed:

  1. Add bindings/c to the root CMake configuration (controlled by option SVS_BUILD_C_API)
  2. Move binding/c/samples to examples/c
  3. Modify examples/CMakeLists to add_subdirectory("c") if SVS_BUILD_C_API is ON
  4. Modify/update CI workflow and scripts to use root CMake directory for C API building

#367 delivered step 2. Steps 1, 3 and 4 are outstanding, and I take your point that runtime bindings aren't referenced from root either — but Ethan's "I think it can be done in this PR easily enough" wasn't withdrawn, so it's worth closing the loop explicitly. Concretely, the visible consequence today is that examples/CMakeLists.txt:15 only adds cpp, so a root-tree build with SVS_BUILD_EXAMPLES=ON never builds the C examples — only a -S bindings/c build reaches them via bindings/c/CMakeLists.txt:274-277. Fine either way; I'd just like it in this PR or in a tracked issue rather than implicit.

Relatedly: examples/c/CMakeLists.txt registers no add_test (compare examples/cpp/CMakeLists.txt:33), so the samples aren't in ctest and .github/scripts/test-c-api-unit.sh:43 re-lists them by hand. Given the script's own comment — "They regressed to a non-zero exit once already, so they are part of the gate" — registering them would make the gate self-maintaining.

Documentation drift

  • C_API_Design.md:64,68 promise fine-grained control over "Memory allocation strategies (simple, hugepage, custom)" and "Logging system". Neither is on the public surface (allocator.hpp:23 is an internal Blocked-vs-plain alias). Suggest moving both to a "Future work" section rather than listing them under Flexibility.
  • C_API_Design.md:274 lists Simple storage as "FP32, FP16, INT8, UINT8, INT4, UINT4", but storage.hpp:50 accepts only FP32/FP16 and only float/Float16 are instantiated (data_builder/simple.hpp:89-90). Four of the six documented types aren't reachable.
  • Two worked examples in the design doc don't run: svs_storage_create_leanvec(128, FLOAT16, INT8) (:290) throws, since StorageLeanVec::to_bits_number accepts only INT4/UINT4/INT8/UINT8/VOID; and svs_storage_create_lvq(UINT8, UINT4) (:285) constructs but has no builder specialization — data_builder/lvq.hpp:104-107 covers (4,0), (8,0), (4,4), (4,8), not (8,4) — so it fails later at svs_index_build.
  • C_API_Design.md:458 and README.md:184-191 still link samples/, which moved to examples/c in [C API] Move C API samples to the examples directory #367.
  • README.md:44-45 says "Configure and build from the top of the ScalableVectorSearch tree; the C API is picked up as a subdirectory under bindings/c", but the command block immediately below is cmake -S bindings/c -B build — a standalone configure, not a root-tree one. The commands are right; the prose contradicts them.
  • README.md:50 uses a plain cmake --install build, where CI installs --component C_API specifically (build-c-api-bindings.sh:57, "the dependency headers that a full install would also emit are not part of the shipped interface"). Worth matching so README users don't install more than the shipped interface.

README.md quick-start has undefined behaviour

goto cleanup at README.md:121 jumps forward over the initialization of queries (:126) and results (:131), both of which the cleanup block then uses — svs_search_results_free(&results) at :168 reads an indeterminate struct and free(queries) at :176 frees a garbage pointer. Legal C, undefined behaviour. (The second goto at :140 is harmless; it only skips breakdown, unused in cleanup.)

Hoisting the declarations above the first goto fixes it:

    // 6. Prepare data
    size_t num_vectors = 10000;
    size_t num_queries = 10;
    size_t k = 5;
    float* data = (float*)malloc(num_vectors * dimensions * sizeof(float));
    float* queries = NULL;
    svs_search_results_t results = SVS_INIT_SEARCH_RESULTS();
    // ... fill data with vectors ...

    // 7. Build index
    svs_index_h index = svs_index_build(builder, data, num_vectors, err);
    if (!index) {
        fprintf(stderr, "Index build failed: %s\n", svs_error_get_message(err));
        goto cleanup;
    }

    // 8. Prepare queries
    queries = (float*)malloc(num_queries * dimensions * sizeof(float));

Worth checking examples/c/simple.c for the same pattern, since the README sample looks derived from it.

Small cleanups

  • svs_storage_create_lvq reports LeanVec in its own errors. svs_c.cpp:328 and :334 throw "Unsupported data type for LeanVec primary storage" / "…LeanVec secondary storage" from inside the LVQ constructor, so a bad LVQ type is reported as a LeanVec problem. (primary_bits/residual_bits on StorageLVQ are correct — this is just the message text.)

  • Typo lenavec_dims at storage.hpp:59, propagated to leanvec.hpp:98.

  • Index base returns a Vamana type. index.hpp:59 declares virtual svs::index::vamana::MemoryBreakdown get_memory_breakdown() const = 0; on the generic Index, so any future non-Vamana index inherits a Vamana-namespaced return type. Came in with [C API] Add memory accounting (get_memory_usage / get_memory_breakdown) #354 and went unremarked at the time. A neutral c_runtime::MemoryBreakdown (converted per implementation) would keep the base generic.

  • The algorithm-type predicate is tested twice on every path. svs_c.cpp:470-473 already rejects non-Vamana with NOT_IMPLEMENTED_IF, then IndexBuilder::build() re-tests the same thing at index_builder.hpp:69 and falls through to return nullptr at :85 — which makes the handler at svs_c.cpp:478-482 dead code. Repeated ×4 in index_builder.hpp (:69, 89, 113, 136) and ×4 in svs_c.cpp (:471, 507, 551, 577). One private accessor collapses it:

      private:
        // Vamana is the only dispatchable algorithm today. Anything else is rejected
        // here rather than returned as a nullptr every caller has to re-test.
        const AlgorithmVamana& vamana_algorithm() const {
            NOT_IMPLEMENTED_IF(
                algorithm->type != SVS_ALGORITHM_TYPE_VAMANA,
                "Only the Vamana algorithm is currently supported"
            );
            return static_cast<const AlgorithmVamana&>(*algorithm);
        }

    Eight predicate sites become one, the four unreachable return nullptr and four dead null-checks go away, and the currently-unchecked static_pointer_cast becomes guarded. It's also the natural home for SVS_ALGORITHM_TYPE_FLAT / _IVF when they arrive — those two are currently referenced nowhere outside their definitions at svs_c.h:73-74.

  • Unused declarations worth a decision. algorithm.hpp:42-43 (get_default_search_params / set_default_search_params) are pure virtuals with no C entry point and no caller; index.hpp:40 stores svs_algorithm_type algorithm that's never read; SVS_ERROR_OUT_OF_MEMORY (svs_c.h:53) is never set. Fine to keep as intentional stubs — just worth a comment saying so, since otherwise the next reader will try to delete them.

  • SVS_DEPRECATED on a brand-new API. svs_c.h:817 ships svs_index_search already deprecated in favour of svs_index_search_topk. Since nothing external depends on it yet, this is the one opportunity to simply delete it.

  • The ops-table version / struct_size fields are never read. C_API_Design.md:312-314 says they exist "for forward compatibility", but neither threadpool.hpp nor IDFilterAdapter (types_support.hpp:73-93, which validates ops, is_member and filter_rate but not the two version fields) inspects them. Either wire them up or note in the doc that they're reserved.

  • ~40 lines of LTO/FetchContent logic are now duplicated between bindings/c/CMakeLists.txt:104-170 and bindings/cpp/CMakeLists.txt:61-175, with both files independently defining SVS_REQUIRE_LTO_ARCHIVE. Unlike most duplication in this repo this is new-to-new, so a shared cmake/ module would be a genuine reduction rather than an inconsistency. Same thread as the first question above.

For completeness on things I checked and am not raising: the storage dispatch via lib::DispatchConverter<...>::match is idiomatic and I wouldn't change it; IndexBuilder unpacking its members into the dispatch_* free functions is the type-erasure boundary svs::lib::Dispatcher requires, not gratuitous delegation; every creator has a matching *_free; and #252's nine-item checklist, #305's thread-count accessors, #354's memory accounting and #363's header move all check out against what was promised.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants