fix(kuna): run the inventory with the configured decode lanes

REA_KUNA_JOBS reached only `decompile-project`, so the export's first step,
the `kuna functions` inventory that every cold request waits for, always ran
serially. Kuna's `functions --jobs N` returns the serial inventory byte for
byte, so both inventory runs now pass the configured jobs. On a 254 MB
Mach-O the inventory takes 23 s with 8 lanes instead of 66 s; the default of
one job is unchanged.

Refs #1957

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Kaoru0822andClaude Opus 5.5 committed 2026-10-15 10:55:35 +00:00
1 parent f255899ee8
commit 225d664e49
6 files changed
+53 -11

No files matched your search

+8 -8
View File
@@ -13,14 +13,14 @@ rea doctor --provider kuna
Select it explicitly with `--provider kuna`, MCP's `open_binary.provider_id: "kuna"`, or `REA_ANALYSIS_PROVIDER=kuna`. With Kuna and another provider configured for the same target, `auto` reports `ambiguous` instead of choosing one.
| Setting | Default | Meaning |
| ----------------------------- | --------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `REA_KUNA_COMMAND` | none (required) | Absolute path of the `kuna` executable. |
| `REA_KUNA_CACHE_DIR` | `~/Library/Caches/rea/kuna` (macOS), `$XDG_CACHE_HOME/rea/kuna` (Linux; `~/.cache/rea/kuna` when `XDG_CACHE_HOME` is unset or relative) | Absolute directory REA owns for project exports. Kuna's own spec cache also lives here, not in your Kuna cache. |
| `REA_KUNA_CACHE_MAX_BYTES` | 4 GiB | Size cap; least recently used exports are evicted above it. |
| `REA_KUNA_PROJECT_TIMEOUT_MS` | 30 minutes | Deadline for one project export. |
| `REA_KUNA_TIMEOUT_MS` | 120 s | Deadline for one single-function decompile. |
| `REA_KUNA_JOBS` | 1 | `decompile-project --jobs`. 1 is Kuna's reference answer; more jobs finish sooner but lose callee-first prototypes, and change about 40–60% of function bodies on the measured binaries. |
| Setting | Default | Meaning |
| ----------------------------- | --------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `REA_KUNA_COMMAND` | none (required) | Absolute path of the `kuna` executable. |
| `REA_KUNA_CACHE_DIR` | `~/Library/Caches/rea/kuna` (macOS), `$XDG_CACHE_HOME/rea/kuna` (Linux; `~/.cache/rea/kuna` when `XDG_CACHE_HOME` is unset or relative) | Absolute directory REA owns for project exports. Kuna's own spec cache also lives here, not in your Kuna cache. |
| `REA_KUNA_CACHE_MAX_BYTES` | 4 GiB | Size cap; least recently used exports are evicted above it. |
| `REA_KUNA_PROJECT_TIMEOUT_MS` | 30 minutes | Deadline for one project export. |
| `REA_KUNA_TIMEOUT_MS` | 120 s | Deadline for one single-function decompile. |
| `REA_KUNA_JOBS` | 1 | `--jobs` for `kuna functions` and `decompile-project`. The inventory is identical for any value. For the export, 1 is Kuna's reference answer; more jobs finish sooner but lose callee-first prototypes, and change about 40–60% of function bodies on the measured binaries. |
Windows is not supported yet. Supported targets are ELF and thin Mach-O executables for x86-64, AArch64 and ARM; PE and universal Mach-O are rejected with a typed reason.
+10
View File
@@ -35,6 +35,16 @@ import { KUNA_PROVIDER_ID } from "./KunaRelease.js";
*/
export const KUNA_INVENTORY_OUTPUT_BYTES = 128 * 1024 * 1024;
/**
* The `kuna functions` inventory of one image. Its `--jobs` decode lanes
* return the serial inventory byte for byte, so the configured jobs change only
* how fast discovery runs (a 254 MB Mach-O: 66 s serially, 23 s with 8 lanes).
*/
export const kunaInventoryArguments = (
input: string,
jobs: number,
): readonly string[] => ["functions", input, "--json", "--jobs", String(jobs)];
/** Test seam for the owned process launcher; production uses the default. */
export type KunaProcessLauncher = (
options: OwnedProviderProcessSpawnOptions,
+2 -1
View File
@@ -16,6 +16,7 @@ import type { ProviderProcessSupervisor } from "../process/ProviderProcess.js";
import {
copyAndHash,
KUNA_INVENTORY_OUTPUT_BYTES,
kunaInventoryArguments,
runKunaCommand,
type KunaProcessLauncher,
} from "./KunaCommand.js";
@@ -209,7 +210,7 @@ export const exportKunaProject = async (
request.onStatus({ phase: "inventory", inventoryFunctions: null });
const listed = await run(
["functions", input, "--json"],
kunaInventoryArguments(input, configuration.jobs),
KUNA_INVENTORY_OUTPUT_BYTES,
);
if (!listed.ok) return listed.outcome;
+5 -1
View File
@@ -35,6 +35,7 @@ import type { ProviderProcessSupervisor } from "../process/ProviderProcess.js";
import {
copyAndHash,
KUNA_INVENTORY_OUTPUT_BYTES,
kunaInventoryArguments,
runKunaCommand,
type KunaProcessLauncher,
} from "./KunaCommand.js";
@@ -1167,7 +1168,10 @@ export class KunaSessionClient implements AnalysisClient {
if (!workspace.ok) return workspace;
const run = await runKunaCommand({
installation,
arguments: ["functions", workspace.value.input, "--json"],
arguments: kunaInventoryArguments(
workspace.value.input,
installation.configuration.jobs,
),
workspace: workspace.value.root.path,
operation: "list_procedures",
timeoutMs: installation.configuration.functionTimeoutMs,
@@ -168,7 +168,13 @@ it("publishes a verified export and serves later requests from the cache", async
// Kuna listed and decompiled a staged copy from REA's own HOME, in reference mode.
const [listing] = await callsOf(kuna, "functions");
const [exported] = await callsOf(kuna, "decompile-project");
expect(listing?.argv).toEqual(["functions", exported?.argv[1], "--json"]);
expect(listing?.argv).toEqual([
"functions",
exported?.argv[1],
"--json",
"--jobs",
"1",
]);
expect(exported?.argv.slice(2)).toEqual([
"-o",
expect.stringContaining(join("cache", "staging")),
@@ -223,6 +223,27 @@ it("verifies a warm export once for concurrent first requests on a fresh server"
}
});
it("runs every inventory with the configured decode lanes", async () => {
const { kuna, open } = await session(
"rea-kuna-inventory-jobs-",
{ project: "hang" },
{ REA_KUNA_JOBS: "4" },
);
const client = open();
try {
await run(client, "list_procedures");
await expect.poll(() => exportsOf(kuna)).toHaveLength(1);
const calls = await fakeKunaCalls(kuna);
const inventories = calls.filter(({ argv }) => argv[0] === "functions");
expect(inventories.length).toBeGreaterThan(0);
for (const { argv } of inventories)
expect(argv.slice(2)).toEqual(["--json", "--jobs", "4"]);
expect((await exportsOf(kuna))[0]?.argv.slice(-2)).toEqual(["--jobs", "4"]);
} finally {
expect(await client.close()).toEqual({ ok: true, value: null });
}
});
it("lists, searches and resolves procedures from Kuna's inventory", async () => {
const { open } = await session("rea-kuna-inventory-");
const client = open();