Skip to content

[Bulk index][C/C++] Share one Clang compilation-universe provider #73

Description

@samchon

Outcome

Replace the current C/C++ navigation artifact with one shared clangd-owned
semantic provider. The final provider must preserve Clang indexing roles and
relations in incremental background-index shards, treat headers as
translation-unit/configuration views, and export one atomic C/C++ generation
without reparsing every translation unit twice.

Current master baseline

PR #147 registers one
scip-clang provider for C and C++, prepares/validates a compilation database,
tracks generated inputs, and publishes both languages from one session.
Current strict grounding intentionally claims no edge families:
scip-clang 0.4.0 omits the enclosing source information required by the common
adapter to attribute even references safely.

The exact cold results were:

  • Redis C: 22.795 s strict versus 262.906 s clangd LSP fallback;
  • LevelDB C++: 8.353 s strict versus 26.452 s fallback.

Those fast strict cells are useful navigation baselines, not proof of calls,
reads/writes, construction, inheritance, overrides or complete headers.

The generic/static discovery bug is separately verified on master:
LANGUAGE_SPECS assigns .h only to C and source enumeration for a C++-only
request can omit .h entirely. A request for both languages partitions every
.h into C. Strict scip-clang documents can override that guess, but generic and
static lanes cannot.

Correct semantic model for headers

compile_commands.json contains translation units, not header rows. A header's
semantic language and preprocessor state come from each including translation
unit. Therefore:

Inclusion context Required graph view
only C TUs C
only C++ TUs C++
both, or different defines/targets separate semantic views in one atomic universe

Do not replace the extension guess with “look up the header in the compilation
database”; no direct entry exists. Build an include-graph/TU ownership map from
the actual index. A shared header may have several legitimate expansions.

Why SCIP plus a second AST pass is rejected as the final route

scip-clang is a maintained portable fallback, but:

  • it builds its own descriptor scheme rather than publishing Clang USRs;
  • internal-linkage twins and macros have identity risks;
  • it deduplicates well-behaved headers through an arbitrary including TU;
  • the common artifact does not prove call/read/write/construction roles; and
  • it is batch-only.

A separate libclang/AST enrichment pass would parse every TU a second time,
roughly paying another compiler analysis and losing clangd's resident preambles
and invalidation. That violates the speed objective.

Clang already exposes the required vocabulary. Its
SymbolRole includes
declaration, definition, reference, read, write, call, dynamic, implicit,
address-of and relation roles. IndexDataConsumer receives semantic
occurrences. Clangd already stores content-addressed per-file background-index
shards and incremental updates through
BackgroundIndex
and
BackgroundIndexStorage.
The current clangd shard/collector drops facts the graph needs.

Decision: thin clangd fork

Maintain a narrowly scoped, commit-pinned clangd fork that changes collection,
shard schema and bulk export:

  1. retain every relevant Clang role bit and relation endpoint;
  2. retain local/file-static declarations and their enclosing semantic owner;
  3. store declarations and definitions with full spans;
  4. store includes/modules, macros and expansion/spelling evidence;
  5. record the exact compile-command digest and TU/configuration identity;
  6. version the on-disk shard schema; and
  7. add a samchon/graphSnapshot request/CLI that freezes the required shard
    set, streams it through [Bulk index] Replace symbol-by-symbol LSP scans with compiler-owned snapshots #63's protocol, and refuses to commit if the
    compilation database or relevant shard generation moves.

This is more than adding transport, so it supersedes the earlier “do not fork
clangd merely to add transport” recommendation. An upstreamable design is still
preferred, but the semantic loss in current storage justifies the thin fork.

Fact mapping

  • contains: semantic owner relation, including locals and anonymous
    declarations;
  • imports: include/import/module evidence, keeping textual include distinct
    in provenance;
  • calls: call role and called relation;
  • accesses: read/write roles;
  • instantiates: constructor calls and construction expressions;
  • type_ref: type-role references;
  • extends/implements: direct base relation, with C++ class/protocol modeling
    documented rather than guessed;
  • overrides: direct override relation;
  • references: reference role;
  • exports: linkage/module export only where the language/build proves it;
  • decorates: attributes from the same AST/index event if retained;
  • tests and renders: framework enrichers, not core Clang facts.

A virtual call normally proves a declaration plus the override closure, not one
executed implementation. Store candidates in structured unresolved data.
Emit exact dispatches only for a compiler-proven closed/final target.
Inactive preprocessor branches are conditional-build.

Identity and universe

Use Clang USRs as the semantic core, then add the coordinates required where a
USR is not globally sufficient:

  • compile target/configuration;
  • TU/file discriminator for internal linkage;
  • module/target triple and language mode;
  • macro spelling/definition identity plus expansion evidence; and
  • source owner for local entities.

Exact fixtures must prove:

  • same-named static functions in two C files do not collide;
  • anonymous namespaces and anonymous types remain distinct;
  • overload insertion/reordering does not rename unrelated functions;
  • templates, specializations and instantiations are not conflated;
  • a macro survives unrelated line insertion where the compiler identity does;
    and
  • one .h included as C and C++ retains both views without cross-contamination.

The universe includes every compilation command, driver, working directory,
defines, standard, include/module paths, sysroot, target, generated header and
dependency digest. A compilation database is necessary but not sufficient when
wrappers or generated inputs are unresolved. Fail closed to clangd/static
fallback when preparation cannot prove them.

Incremental and platform design

Reuse clangd's preambles, AST caches and digest-based background index.

  • no-op: validate/load the same explicit shard set;
  • source edit: re-export clangd's changed source shard and affected header/TU
    views;
  • header change: invalidate every including TU reported by the include graph;
  • compilation database/config/generated-header change: invalidate the affected
    command universe;
  • delete/rename: explicit shard deletion; and
  • mid-export shard movement/cancel/crash: keep the prior graph.

Do not run clangd-indexer and call it incremental; its monolithic export is
batch. Do not force --jobs=1 or producer-side deterministic scheduling:
master already canonicalizes incoming facts, and serializing scip-clang caused
orders-of-magnitude slowdowns without fixing header selection.

scip-clang's release assets do not provide a stock Windows binary. The final
fork must have reproducible Windows, Linux and macOS builds or explicitly
decline. Compiler-wrapper and MSVC-clang-cl compatibility belong in the
platform matrix.

Implementation map

  1. Fix source discovery so .h reaches ownership resolution before language
    partitioning; add generic/static regression tests.
  2. Prototype full-role collection in a pinned clangd fork and publish a schema
    capability handshake.
  3. Add a provider beside the existing scip-clang entry; keep the latter as
    portable fallback.
  4. Adapt changed shards through the common transaction and preserve dual C/C++
    language slices.
  5. Add exact C and C++ fixtures before real Redis/LevelDB smoke.
  6. Package and verify the fork on all supported platforms.

Acceptance

  • No per-symbol LSP requests and no duplicate TU semantic analysis in the
    final path.
  • Headers are owned by include/TU configuration, never a global extension
    guess.
  • Exact fixtures cover internal linkage, macros, includes, modules,
    generated headers, overloads, templates/specializations, constructors,
    fields, inheritance, overrides and virtual calls.
  • All 15 fact families carry explicit coverage/unresolved status.
  • Same header in multiple TUs/configurations is neither arbitrarily
    deduplicated nor duplicated as the same identity.
  • No-op, edit, header, compdb, generated input, create/delete/rename,
    compiler error, cancel, crash and retry are atomic.
  • Cold time is compared with native clangd background indexing; no-op p95
    targets below 250 ms and one-TU body edit is proportional to the affected
    shard closure.
  • Windows, Linux and macOS selection/packaging/fallback are proven.
  • Redis and LevelDB smokes assert exact representative calls, access roles,
    type and inheritance facts, not counts.
  • Focused tests, C/C++ experiments, pnpm build, pnpm test, and
    pnpm coverage pass.

Primary references

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions