perf(kernel): direct-to-store decode — buffers flow to the store worker, main thread never materializes nodes

Kernel-routed files ship their flat tables from the parse worker to the
store worker as buffers (tryKernelExtractRaw → kernelBuffers on the
result → KernelStoreBundle); the store worker decodes and finalizes
(finalizeStoreBundle shared with the object path so filter semantics
can never drift). Files with applicable framework extract() hooks keep
the decoded path; the no-writer fallback materializes via
materializeKernelResult. Byte-identical dumps re-verified on dubbo,
excalidraw, express, gson; full suite green (2,467).

Measurement (plan §4d): dubbo's parse-loop wall is 94% store-writer
busy time — the many-core fresh-index wall is single-writer SQLite
ingest, not extraction or main-thread work. d2s improves the writer
lane ~11% (structured-clone deserialization avoided on the writer) and
frees the main thread; the remaining many-core gap is a
store-architecture arc (deferred index builds, multi-file
transactions, buffer→bind), out of the kernel project's scope.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Colby McHenry
2026-07-16 23:38:32 -05:00
co-authored by Claude Fable 5
parent 03d54e47a1
commit 28068fa0f1
7 changed files with 277 additions and 39 deletions
+83
View File
@@ -17,6 +17,11 @@
import type { ExtractionResult, Language } from '../../types';
import { getKernel, kernelSupports } from './loader';
import { decodeExtractBuffers } from './decode';
import {
KERNEL_ABI_VERSION as LAYOUT_ABI,
META as LAYOUT_META,
NONE as LAYOUT_NONE,
} from './layout';
export { getKernel, kernelSupports, resetKernelForTests } from './loader';
export { decodeExtractBuffers } from './decode';
@@ -66,6 +71,84 @@ export function kernelRoutes(language: Language): boolean {
/** Warned-once registry so a broken language logs a single line, not one per file. */
const warned = new Set<string>();
/** The raw table buffers + the cheap facts the orchestrator needs pre-decode. */
export interface KernelRawResult {
buffers: NonNullable<ExtractionResult['kernelBuffers']>;
counts: { nodes: number; edges: number; refs: number };
errors: ExtractionResult['errors'];
}
/**
* Extract via the kernel WITHOUT decoding — the bulk-index fast path. The
* tables ride to the store boundary as buffers (decoded on the store worker),
* so the main thread never materializes per-node objects. Returns null under
* exactly the conditions tryKernelExtract does, PLUS when the language has a
* registered post() pass (post passes operate on decoded results, so those
* languages keep the decoded path).
*/
export function tryKernelExtractRaw(
filePath: string,
source: string,
language: Language
): KernelRawResult | null {
if (!kernelRoutes(language) || POST_PASSES[language]) return null;
const kernel = getKernel();
if (!kernel) return null;
try {
const buffers = kernel.extractFile(filePath, source, language);
const meta = buffers.meta;
if (meta.readUInt8(LAYOUT_META.version) !== LAYOUT_ABI) {
throw new Error(`kernel buffer ABI ${meta.readUInt8(0)} != expected ${LAYOUT_ABI}`);
}
const counts = {
nodes: meta.readUInt32LE(LAYOUT_META.nodeCount),
edges: meta.readUInt32LE(LAYOUT_META.edgeCount),
refs: meta.readUInt32LE(LAYOUT_META.refCount),
};
let errors: ExtractionResult['errors'] = [];
const errorsOff = meta.readUInt32LE(LAYOUT_META.errorsOff);
if (errorsOff !== LAYOUT_NONE) {
const errorsLen = meta.readUInt32LE(LAYOUT_META.errorsLen);
errors = JSON.parse(
buffers.arena.toString('utf8', errorsOff, errorsOff + errorsLen)
) as ExtractionResult['errors'];
}
return { buffers, counts, errors };
} catch (err) {
const message = err instanceof Error ? err.message : String(err);
if (message.includes('defer:')) return null;
if (!warned.has(language)) {
warned.add(language);
process.stderr.write(
`[codegraph-kernel] ${language} extraction failed (${message}) — falling back to the wasm path\n`
);
}
return null;
}
}
/**
* Decode a buffer-carrying result (see ExtractionResult.kernelBuffers) into a
* plain, fully-materialized ExtractionResult — the fallback for store paths
* that need objects (main-thread store, tests).
*/
export function materializeKernelResult(
result: ExtractionResult,
filePath: string,
language: Language
): ExtractionResult {
if (!result.kernelBuffers) return result;
const b = result.kernelBuffers;
const asBuf = (u: Uint8Array) => Buffer.from(u.buffer, u.byteOffset, u.byteLength);
const decoded = decodeExtractBuffers(
{ meta: asBuf(b.meta), nodes: asBuf(b.nodes), edges: asBuf(b.edges), refs: asBuf(b.refs), arena: asBuf(b.arena) },
filePath,
language
);
decoded.durationMs = result.durationMs;
return decoded;
}
/**
* Extract via the native kernel. Returns null when the kernel doesn't apply
* (not routed / not available / kill switch) — the caller falls back to the