perf: schedule batch decompilation reads on demand
This commit is contained in:
1 parent
23ab2c4069
commit
06fe7eecbc
3 files changed
+100
-105
No files matched your search
@@ -98,14 +98,6 @@ const isAnalysisCancellation = (
|
||||
error: AnalysisError,
|
||||
): error is AnalysisCancelledError => error._tag === "AnalysisCancelledError";
|
||||
|
||||
const firstAnalysisCancellation = <Value>(
|
||||
results: readonly Result<Value, AnalysisError>[],
|
||||
): AnalysisCancelledError | undefined => {
|
||||
for (const result of results)
|
||||
if (!result.ok && isAnalysisCancellation(result.error)) return result.error;
|
||||
return undefined;
|
||||
};
|
||||
|
||||
/**
|
||||
* Composes direct provider operations into bounded reverse-engineering tools.
|
||||
* Direct callers use `execute`; adapters that own validation use
|
||||
@@ -423,34 +415,24 @@ export class EnhancedTools {
|
||||
addresses: readonly string[],
|
||||
signal?: AbortSignal,
|
||||
): EnhancedResult {
|
||||
const resolutions = await Promise.all(
|
||||
addresses.map(async (address) => {
|
||||
const result = await resolveProcedureAddress(
|
||||
this.#call.bind(this),
|
||||
address,
|
||||
signal,
|
||||
);
|
||||
return { address, result };
|
||||
}),
|
||||
);
|
||||
const resolutionCancellation = firstAnalysisCancellation(
|
||||
resolutions.map(({ result }) => result),
|
||||
);
|
||||
if (resolutionCancellation !== undefined)
|
||||
return err(resolutionCancellation);
|
||||
|
||||
const resolutions: BatchResolution[] = [];
|
||||
for (const address of addresses) {
|
||||
const result = await resolveProcedureAddress(
|
||||
this.#call.bind(this),
|
||||
address,
|
||||
signal,
|
||||
);
|
||||
if (!result.ok && isAnalysisCancellation(result.error)) return result;
|
||||
resolutions.push({ address, result });
|
||||
}
|
||||
const identities = await this.#batchIdentities(resolutions, signal);
|
||||
const ready: BatchReadyItem[] = [];
|
||||
for (const identity of identities) {
|
||||
if (!identity.ok) return err(identity.error);
|
||||
ready.push(identity.value);
|
||||
}
|
||||
const decompilations = await this.#batchPseudocode(ready, signal);
|
||||
const items: BatchDecompileItem[] = [];
|
||||
for (const decompilation of decompilations) {
|
||||
if (!decompilation.ok) return err(decompilation.error);
|
||||
items.push(decompilation.value);
|
||||
}
|
||||
if (!identities.ok) return identities;
|
||||
const decompilations = await this.#batchPseudocode(
|
||||
identities.value,
|
||||
signal,
|
||||
);
|
||||
if (!decompilations.ok) return decompilations;
|
||||
const items = decompilations.value;
|
||||
const succeeded = items.filter(({ status }) => status === "ok").length;
|
||||
return ok({
|
||||
items,
|
||||
@@ -463,75 +445,73 @@ export class EnhancedTools {
|
||||
async #batchIdentities(
|
||||
resolutions: readonly BatchResolution[],
|
||||
signal?: AbortSignal,
|
||||
): Promise<Result<BatchReadyItem, AnalysisCancelledError>[]> {
|
||||
return Promise.all(
|
||||
resolutions.map(async ({ address, result }) => {
|
||||
if (!result.ok)
|
||||
return ok({
|
||||
address,
|
||||
providerProcedure: address,
|
||||
procedure: {
|
||||
status: "unknown" as const,
|
||||
error: projectAnalysisError(result.error),
|
||||
},
|
||||
});
|
||||
const identity = await this.#batchProcedureIdentity(
|
||||
result.value,
|
||||
signal,
|
||||
);
|
||||
return identity.ok
|
||||
? ok({
|
||||
address,
|
||||
providerProcedure: result.value,
|
||||
procedure: identity.value,
|
||||
})
|
||||
: err(identity.error);
|
||||
}),
|
||||
);
|
||||
): Promise<Result<BatchReadyItem[], AnalysisCancelledError>> {
|
||||
const ready: BatchReadyItem[] = [];
|
||||
for (const { address, result } of resolutions) {
|
||||
if (!result.ok) {
|
||||
ready.push({
|
||||
address,
|
||||
providerProcedure: address,
|
||||
procedure: {
|
||||
status: "unknown",
|
||||
error: projectAnalysisError(result.error),
|
||||
},
|
||||
});
|
||||
continue;
|
||||
}
|
||||
const identity = await this.#batchProcedureIdentity(result.value, signal);
|
||||
if (!identity.ok) return identity;
|
||||
ready.push({
|
||||
address,
|
||||
providerProcedure: result.value,
|
||||
procedure: identity.value,
|
||||
});
|
||||
}
|
||||
return ok(ready);
|
||||
}
|
||||
|
||||
async #batchPseudocode(
|
||||
ready: readonly BatchReadyItem[],
|
||||
signal?: AbortSignal,
|
||||
): Promise<Result<BatchDecompileItem, AnalysisCancelledError>[]> {
|
||||
return Promise.all(
|
||||
ready.map(async ({ address, procedure, providerProcedure }) => {
|
||||
const result = await this.#call(
|
||||
"procedure_pseudo_code",
|
||||
{ procedure: providerProcedure },
|
||||
signal,
|
||||
procedure.status === "resolved" ? "provider" : undefined,
|
||||
);
|
||||
if (!result.ok && isAnalysisCancellation(result.error))
|
||||
return err(result.error);
|
||||
const item: BatchDecompileItem = !result.ok
|
||||
): Promise<Result<BatchDecompileItem[], AnalysisCancelledError>> {
|
||||
const items: BatchDecompileItem[] = [];
|
||||
for (const { address, procedure, providerProcedure } of ready) {
|
||||
const result = await this.#call(
|
||||
"procedure_pseudo_code",
|
||||
{ procedure: providerProcedure },
|
||||
signal,
|
||||
procedure.status === "resolved" ? "provider" : undefined,
|
||||
);
|
||||
if (!result.ok && isAnalysisCancellation(result.error))
|
||||
return err(result.error);
|
||||
const item: BatchDecompileItem = !result.ok
|
||||
? {
|
||||
address,
|
||||
procedure,
|
||||
status: "error",
|
||||
error: projectAnalysisError(result.error),
|
||||
}
|
||||
: typeof result.value !== "string" || result.value.length === 0
|
||||
? {
|
||||
address,
|
||||
procedure,
|
||||
status: "error",
|
||||
error: projectAnalysisError(result.error),
|
||||
}
|
||||
: typeof result.value !== "string" || result.value.length === 0
|
||||
? {
|
||||
address,
|
||||
procedure,
|
||||
status: "error",
|
||||
error: projectAnalysisError(
|
||||
new AnalysisOutputError(
|
||||
"procedure_pseudo_code",
|
||||
"provider returned empty pseudocode",
|
||||
),
|
||||
error: projectAnalysisError(
|
||||
new AnalysisOutputError(
|
||||
"procedure_pseudo_code",
|
||||
"provider returned empty pseudocode",
|
||||
),
|
||||
}
|
||||
: {
|
||||
address,
|
||||
procedure,
|
||||
status: "ok",
|
||||
pseudocode: result.value,
|
||||
};
|
||||
return ok(item);
|
||||
}),
|
||||
);
|
||||
),
|
||||
}
|
||||
: {
|
||||
address,
|
||||
procedure,
|
||||
status: "ok",
|
||||
pseudocode: result.value,
|
||||
};
|
||||
items.push(item);
|
||||
}
|
||||
return ok(items);
|
||||
}
|
||||
|
||||
async #batchProcedureIdentity(
|
||||
|
||||
@@ -42,7 +42,7 @@ export const ENHANCED_TOOL_CONTRACTS = [
|
||||
),
|
||||
enhanced(
|
||||
"batch_decompile",
|
||||
"Decompile each explicit procedure symbol or address concurrently. Preserves each input selector in address and reports the observed canonical entry and entry label in procedure; identity or label failures remain explicit without discarding usable pseudocode. Returns ordered per-item success or error results and aggregate counts.",
|
||||
"Decompile each explicit procedure symbol or address, with one provider read active per batch. Preserves each input selector in address and reports the observed canonical entry and entry label in procedure; identity or label failures remain explicit without discarding usable pseudocode. Returns ordered per-item success or error results and aggregate counts. The complete reply retains every pseudocode body; use selected small batches to bound retained output.",
|
||||
enhancedInputSchemas.batch_decompile,
|
||||
),
|
||||
enhanced(
|
||||
|
||||
@@ -396,7 +396,11 @@ describe("enhanced MCP batch cancellation", () => {
|
||||
{
|
||||
stage: "entry-label lookup",
|
||||
cancelAt: "address_name",
|
||||
expectedCalls: ["procedure_address:0x1", "address_name:0x1"],
|
||||
expectedCalls: [
|
||||
"procedure_address:0x1",
|
||||
"procedure_address:0x2",
|
||||
"address_name:0x1",
|
||||
],
|
||||
operation: "address_name",
|
||||
},
|
||||
{
|
||||
@@ -404,7 +408,9 @@ describe("enhanced MCP batch cancellation", () => {
|
||||
cancelAt: "procedure_pseudo_code",
|
||||
expectedCalls: [
|
||||
"procedure_address:0x1",
|
||||
"procedure_address:0x2",
|
||||
"address_name:0x1",
|
||||
"address_name:0x2",
|
||||
"procedure_pseudo_code:0x1",
|
||||
],
|
||||
operation: "procedure_pseudo_code",
|
||||
@@ -430,7 +436,7 @@ describe("enhanced MCP batch cancellation", () => {
|
||||
|
||||
const response = await client.callTool({
|
||||
name: "batch_decompile",
|
||||
arguments: { addresses: ["0x1"] },
|
||||
arguments: { addresses: ["0x1", "0x2"] },
|
||||
});
|
||||
|
||||
expect(response.isError).toBe(true);
|
||||
@@ -472,7 +478,6 @@ describe("enhanced MCP batch cancellation", () => {
|
||||
"procedure_address:0x1",
|
||||
"procedure_address:0x2",
|
||||
"address_name:0x1",
|
||||
"address_name:0x2",
|
||||
]);
|
||||
});
|
||||
});
|
||||
@@ -483,16 +488,24 @@ describe("enhanced MCP batches", () => {
|
||||
{ length: 37 },
|
||||
(_, index) => `0x${index.toString(16)}`,
|
||||
);
|
||||
let active = 0;
|
||||
let peakActive = 0;
|
||||
const analysis: AnalysisOperationPort = {
|
||||
execute: async (name, arguments_) => {
|
||||
if (name === "procedure_address")
|
||||
return ok(String(arguments_.procedure));
|
||||
if (name === "address_name")
|
||||
return ok(`name for ${String(arguments_.address)}`);
|
||||
if (name !== "procedure_pseudo_code")
|
||||
throw new Error(`Unexpected operation: ${name}`);
|
||||
active++;
|
||||
peakActive = Math.max(peakActive, active);
|
||||
await new Promise((resolve) => setImmediate(resolve));
|
||||
return ok(`pseudo for ${String(arguments_.procedure)}`);
|
||||
try {
|
||||
if (name === "procedure_address")
|
||||
return ok(String(arguments_.procedure));
|
||||
if (name === "address_name")
|
||||
return ok(`name for ${String(arguments_.address)}`);
|
||||
if (name !== "procedure_pseudo_code")
|
||||
throw new Error(`Unexpected operation: ${name}`);
|
||||
return ok(`pseudo for ${String(arguments_.procedure)}`);
|
||||
} finally {
|
||||
active--;
|
||||
}
|
||||
},
|
||||
};
|
||||
const client = await connect(analysis);
|
||||
@@ -503,6 +516,8 @@ describe("enhanced MCP batches", () => {
|
||||
},
|
||||
});
|
||||
expect(result.isError).not.toBe(true);
|
||||
expect(peakActive).toBe(1);
|
||||
expect(active).toBe(0);
|
||||
expect(result.structuredContent).toMatchObject({
|
||||
normalized_result: {
|
||||
items: addresses.map((address) => ({
|
||||
|
||||
Reference in new issue
Block a user