experiment(re)!: separate IR origins from machine instruction facts

A real model conflated an optimized CFG edge with the bytes at its instruction origin. Make provenance explicit and add bounded, batched instruction windows from the existing Kuna CLI, preserving exact bytes, per-window failures and Evidence. Record the task-cost comparison without treating the incomplete machine-path answer as an equal-quality win.

Verified 33 focused tests, targeted lint, real stock/patched MCP decoding with mixed success, and a real 4096-row capacity run under the resource observer.

BREAKING CHANGE: Experimental operation.address/addresses and block.start become origin_address/origin_addresses and origin_start. Printed CFG target_address becomes target_origin; historical source captures are unchanged.

Co-authored-by: gensokyo-dev <342580529+gensokyo-dev@users.noreply.github.com>
Co-authored-by: Kaoru0822 <208281322+kaoru0822-kitauji@users.noreply.github.com>
This commit is contained in:
committed 2026-10-16 04:51:55 +00:00
1 parent f2f029c5dd
commit 4f13bd6733
15 files changed
+427 -47

No files matched your search

+10 -2
View File
@@ -67,8 +67,16 @@ Runtime successors, memory aliases and machine-semantic equivalence remain
unknown. Other providers are not yet wired to this experimental service.
The initial [actual model comparison](trials.md) favors the focused traditional
workflow. Code Mode reduces atomic round trips but has not yet established a
whole-task advantage. The revised native relations need real-model evaluation.
workflow. The seeded value-origin task has lower cumulative input and wall time
through Code Mode, but also exposes an IR-to-machine-path error. An equal-quality
whole-task advantage remains unproven; the representation fix needs a new trial.
`instructions` reads selected machine byte/decode windows through the same
owner. Recovered operations use `origin_address` and blocks `origin_start`;
these locations cannot be substituted for machine branch destinations. The
value-origin trial demonstrates why this boundary matters. The experiment
currently has eleven fact methods, still behind the same four-tool Code Mode
catalog.
Completion requires actual model Code Mode use, independently checked task
answers, shared atomic/direct/Code Mode semantics, needed relation coverage,
+30 -6
View File
@@ -59,12 +59,32 @@ export const methods = {
"snapshot",
'await rea.snapshot({entry:"0x1000"})',
),
instructions: definition(
"Batch selected machine instruction windows. Read exact bytes and native disassembly to verify actual control transfers; optimized recovered CFG edges can bypass machine blocks. Address+size with the artifact context identifies the byte range. A caller-selected decode start does not prove execution/alignment.",
{
windows: z
.array(
z.strictObject({
address: hex,
count: z.number().int().min(1).max(4096).default(16),
}),
)
.min(1)
.max(16),
fields: z
.array(z.enum(["bytes", "text", "mnemonic", "operands"]))
.min(1)
.optional(),
},
"instructions",
'await rea.instructions({windows:[{address:"0x1000",count:8}],fields:["bytes","text"]})',
),
operations: definition(
"Select SSA operations by IDs, instruction origins, block IDs, constant literal or kind. Filters intersect; identities and source lines are always retained. A literal match is not object identity.",
"Select SSA operations by IDs, source instruction origins, block IDs, constant literal or kind. Filters intersect; identities and source lines are always retained. A literal match is not object identity. origin_addresses selects optimized recovered IR origins; use instructions for actual machine control transfers.",
{
snapshot,
ids: z.array(z.string()).max(128).optional(),
addresses: z.array(hex).max(128).optional(),
origin_addresses: z.array(hex).max(128).optional(),
block_ids: z.array(z.number().int().nonnegative()).max(128).optional(),
constant: hex.optional(),
kind: kinds.optional(),
@@ -120,12 +140,14 @@ export const methods = {
"await rea.values({snapshot:s,ids:op.inputs.map(i=>i.value_id).filter(Boolean)})",
),
blocks: definition(
"Read native recovered blocks by stable snapshot-local ID, with ordered predecessors, successor edges and member operation IDs. Requires native IR. A recovered CFG does not establish all runtime successors.",
"Read native recovered blocks by stable snapshot-local ID, with ordered predecessors, successor edges and member operation IDs. origin_start describes optimized IR provenance, not a machine block extent. Requires native IR. Runtime successors remain unknown.",
{
snapshot,
ids: z.array(z.number().int().nonnegative()).max(128).optional(),
fields: z
.array(z.enum(["start", "operations", "predecessors", "successors"]))
.array(
z.enum(["origin_start", "operations", "predecessors", "successors"]),
)
.min(1)
.optional(),
...page,
@@ -169,6 +191,8 @@ export const methods = {
// Compact return contracts let code compose before first observing a response.
const returns = {
instructions:
"data:{results:[{ok,address,source:{evidence_id,sha256},items:[{address,size,status,bytes?,text?,mnemonic?,operands?}],resume_address,coverage}|{ok:false,address,error}]}; decoded rows describe selected machine bytes, not optimized IR branches. Partial batches retain successful windows.",
strings:
"data:{source:{evidence_id,sha256},items:[{address,value,string:{encoding,byte_length}}],total,next_offset,coverage}",
references:
@@ -176,13 +200,13 @@ const returns = {
snapshot:
"data:{snapshot:string,entry,source:{evidence_id,sha256},coverage:{operations,blocks,control_flow,unrepresented_outgoing_blocks,issues}}",
operations:
"data:{items:[{id,address,block,source_line,kind,statement,definitions,constants,inputs?,output?,opcode?}],total,next_offset,evidence_id,coverage}. Native inputs:[{input_index,value_id,phi_predecessor}], output:value_id|null; definitions:[{definition,use,input_index,value_id,phi_predecessor}]. Diagnostic definitions use printed occurrence instead. fields projects columns; identity remains.",
"data:{items:[{id,origin_address,block,source_line,kind,statement,definitions,constants,inputs?,output?,opcode?}],total,next_offset,evidence_id,coverage}. Native inputs:[{input_index,value_id,phi_predecessor,phi_predecessor_edge}], output:value_id|null; definitions:[{definition,use,input_index,value_id,phi_predecessor,phi_predecessor_edge}]. Diagnostic definitions use printed occurrence instead. fields projects columns; identity remains.",
relations:
"data:{items:definition/use edges OR CFG edges{from,to,operation,polarity?,condition_value?,condition_test?} OR controllers{controller,dependent,edge},total,next_offset,evidence_id,coverage,relation_coverage?}. definitions/uses seeds are operation-ID strings; incoming/outgoing/controllers seeds are numeric block IDs. Native def/use edges include input_index,value_id,phi_predecessor; diagnostic edges include occurrence only.",
values:
"data:{items:[{id,space,offset,size_bytes,is_constant,is_input,is_annotation,definition,type_name}],total,next_offset,evidence_id,coverage}; definitions are operation IDs, offsets are exact hex strings.",
blocks:
"data:{items:[{id,start,operations,predecessors,successors:[{target,index,polarity,condition_value,condition_test}]}],total,next_offset,evidence_id,coverage}; IDs are local to this snapshot.",
"data:{items:[{id,origin_start,operations,predecessors,successors:[{target,index,polarity,condition_value,condition_test}]}],total,next_offset,evidence_id,coverage}; IDs are local to this snapshot.",
slice:
"data:{operations:[operation rows as above],coverage:{status,boundaries:[{id,reason}],missing,source_issues,runtime_reachability,memory_alias_dependencies},evidence_id}",
code: "data:{evidence_id,start_line,text,total_lines,next_offset,coverage}; text is reconstructed C. Use a large limit to inspect/filter the full text within code without returning it all.",
+4 -1
View File
@@ -129,7 +129,10 @@ const installBridge = (
lease,
);
for (const row of [response.data, ...(response.data?.results ?? [])]) {
const id = row?.source?.evidence_id ?? row?.evidence_id;
const id =
row?.source?.evidence_id ??
row?.evidence_id ??
row?.error?.details?.evidence_id;
if (id) state.evidence.add(id);
}
if (response.error?.details?.evidence_id)
@@ -1,3 +1,5 @@
import { projectAnalysisError } from "../../dist/domain/analysisErrorProjection.js";
// Shared expected-failure algebra; execution adapters do not depend on native protocols.
export class FactFailure extends Error {
constructor(code, message, details = {}) {
@@ -6,3 +8,17 @@ export class FactFailure extends Error {
this.details = details;
}
}
export const requireResult = (result, code = "provider_failure") => {
if (!result.ok) {
const failure = result.error?._tag
? projectAnalysisError(result.error)
: result.error;
throw new FactFailure(
failure?.code ?? code,
String(failure?.message ?? failure?.reason ?? failure),
{ failure },
);
}
return result.value;
};
@@ -0,0 +1,108 @@
// The machine-decoding boundary is separate from optimized recovered IR.
import { z } from "zod";
import { FactFailure } from "./fact-error.mjs";
const hex = z.string().regex(/^0x(?:0|[1-9a-f][0-9a-f]{0,15})$/u);
const rowSchema = z.object({
address_hex: hex,
size: z.number().int().positive().max(65536),
bytes: z.string().regex(/^(?:[0-9a-f]{2})+$/u),
mnemonic: z.string(),
operands: z.string(),
text: z.string(),
});
const envelopeSchema = z.object({
binary: z.string(),
kind: z.literal("code"),
start_hex: hex,
end_hex: hex,
count: z.number().int().nonnegative().max(4096),
bytes: z.number().int().nonnegative(),
truncated: z.boolean(),
notes: z.array(z.string()),
});
const requireFact = (condition, message) => {
if (!condition) throw new FactFailure("invalid_provider_output", message);
};
export function parseInstructions(
text,
inputPath,
request,
fields = ["bytes", "text"],
) {
try {
requireFact(
Buffer.byteLength(text) <= 8 * 1024 * 1024,
"Instruction source exceeds transport capacity",
);
const raw = JSON.parse(text);
requireFact(
Array.isArray(raw?.instructions) && raw.instructions.length <= 4096,
"Instruction window exceeds decoded-row capacity",
);
const doc = envelopeSchema.parse(raw);
requireFact(
doc.binary === inputPath && doc.start_hex === request.address,
"Instruction source does not match the selected input/window",
);
requireFact(
doc.count === raw.instructions.length && doc.count <= request.count,
"Instruction count does not match the requested window",
);
let next = BigInt(request.address);
const items = raw.instructions.map((value) => {
const row = rowSchema.parse(value);
requireFact(
BigInt(row.address_hex) === next && row.bytes.length === row.size * 2,
"Instruction bytes/size or sequential address mismatch",
);
next += BigInt(row.size);
const fact = {
address: row.address_hex,
size: row.size,
status: row.mnemonic === ".byte" ? "undecoded" : "decoded",
bytes: row.bytes,
mnemonic: row.mnemonic,
operands: row.operands,
text: row.text,
};
return Object.fromEntries(
[...new Set(["address", "size", "status", ...fields])].map((key) => [
key,
fact[key],
]),
);
});
requireFact(
next === BigInt(doc.end_hex) &&
next - BigInt(request.address) === BigInt(doc.bytes),
"Instruction window end/byte coverage mismatch",
);
return {
items,
resume_address: doc.end_hex,
coverage: {
authority: "machine bytes and native linear decoding; not recovered IR",
request,
listed_count: items.length,
truncated: doc.truncated,
notes: doc.notes,
alignment:
"caller-selected start; runtime execution and instruction alignment are not established",
},
};
} catch (error) {
if (error instanceof FactFailure) throw error;
throw new FactFailure(
"invalid_provider_output",
"Native instruction window does not match the declared format",
{
reason:
error instanceof z.ZodError
? error.issues[0]?.message.slice(0, 256)
: String(error.message).slice(0, 256),
},
);
}
}
@@ -0,0 +1,92 @@
import test from "node:test";
import assert from "node:assert/strict";
import { parseInstructions } from "./instruction-facts.mjs";
const document = (address = "0xd97e") => ({
binary: "/private/input",
kind: "code",
start_hex: address,
end_hex: `0x${(BigInt(address) + 6n).toString(16)}`,
count: 1,
bytes: 6,
truncated: true,
notes: ["selected window"],
instructions: [
{
address_hex: address,
size: 6,
bytes: "0f87e9080000",
mnemonic: "JA",
operands: "0xe26d",
text: "JA 0xe26d",
},
],
});
const parse = (doc, address = doc.start_hex) =>
parseInstructions(JSON.stringify(doc), "/private/input", {
address,
count: 1,
});
test("instruction facts preserve exact bytes/addresses and do not substitute an optimized CFG target", () => {
const result = parse(document());
assert.equal(result.items[0].address, "0xd97e");
assert.equal(result.items[0].text, "JA 0xe26d");
assert.equal(result.items[0].bytes, "0f87e9080000");
assert.equal(result.items[0].status, "decoded");
assert.deepEqual(result.coverage.notes, ["selected window"]);
const high = document("0x20000000000001");
high.instructions[0].address = Number(BigInt(high.start_hex));
assert.equal(parse(high).items[0].address, "0x20000000000001");
});
test("unknown bytes remain undecoded and projection retains byte-range identity", () => {
const doc = document();
doc.instructions[0].mnemonic = ".byte";
const result = parseInstructions(
JSON.stringify(doc),
"/private/input",
{ address: "0xd97e", count: 1 },
["mnemonic"],
);
assert.deepEqual(result.items[0], {
address: "0xd97e",
size: 6,
status: "undecoded",
mnemonic: ".byte",
});
});
test("wrong target/window, gaps, extent and byte-size contradictions fail at decoding boundary", () => {
for (const mutate of [
(doc) => {
doc.binary = "/other/input";
},
(doc) => {
doc.instructions[0].address_hex = "0xd97f";
},
(doc) => {
doc.instructions[0].size = 5;
},
(doc) => {
doc.end_hex = "0xd985";
},
(doc) => {
doc.count = 2;
},
]) {
const doc = document();
mutate(doc);
assert.throws(
() => parse(doc),
(error) => error.code === "invalid_provider_output",
);
}
assert.throws(() => parse(document(), "0xd970"), /window/u);
});
test("decoded-row capacity rejects oversized malformed arrays before expanding errors", () => {
const doc = document();
doc.instructions = Array.from({ length: 4097 }, () => ({}));
assert.throws(() => parse(doc), /capacity/u);
});
@@ -76,3 +76,26 @@ Upstream `check-spec` passes. The full upstream corpus, stages and workspace gat
have not run; this patch is not declared upstream-ready. SQLite, more complex
model tasks, paired warm runs and the revised actual model comparison remain
required work.
The value-origin model comparison exposed a representation boundary: optimized
recovered blocks can absorb a machine predecessor, so an IR edge is not an actual
branch instruction target. The experimental API now names operation locations
`origin_address`, their selector `origin_addresses`, and block locations
`origin_start`; printed CFG targets use `target_origin`. These replace the old
ambiguous fields without aliases. Historical captures retain their pinned old
contracts. Raw upstream JSON is preserved unchanged in Evidence.
The added `instructions` method batches selected machine-address windows, using
Kuna's existing [disassemble CLI](https://github.com/Noelo-Lab/kuna/blob/fe01d6294eb0d1053ce343c8943646df88e143fc/decompiler/crates/kuna-cli/src/disassemble.rs),
with exact bytes, decoded text, extent, coverage and per-window errors. Kuna
already has focused agent-oriented read surfaces; its port ancestry does not
establish which primitives are missing. No assembly decoding or branch-target
parser is duplicated in the MCP adapter.
Windows are capped at 4,096 decoded rows and 16 windows per call, sharing the
8 MiB capture and 64 MiB retained-source budgets. A real maximum-row CLI test
returns 4,096 rows in an 842,106-byte source, taking 1.227 family wall seconds,
1.042 CPU seconds and 112.0 MiB kernel memory peak. Both real stock and patched
MCP tests preserve good windows alongside an unmapped-address failure and report
the actual `JA 0xe26d` / `JMP 0xd9c9` bytes. Targeted tests now total 33; the
representation fix still requires a fresh real-model task trial.
+13 -5
View File
@@ -42,7 +42,7 @@ const page = (rows, { offset = 0, limit = 20, fields } = {}) => ({
.map((row) =>
fields
? Object.fromEntries(
[...new Set(["id", "address", "block", ...fields])]
[...new Set(["id", "origin_address", "block", ...fields])]
.filter((key) => Object.hasOwn(row, key))
.map((key) => [key, row[key]]),
)
@@ -146,7 +146,11 @@ function validateValues(facts, operations, values) {
function indexNative(facts) {
const operations = unique(facts.operations),
values = unique(facts.values),
blocks = unique(facts.blocks);
blocks = unique(
facts.blocks.map(({ start, ...block }) =>
freeze({ ...block, origin_start: start }),
),
);
validateBlocks(operations, values, blocks);
validateOperations(operations, values, blocks);
validateValues(facts, operations, values);
@@ -181,10 +185,12 @@ function indexNative(facts) {
index(constants, value.offset, op.id);
}
}
const { address: origin_address, ...payload } = op;
normalized.set(
op.id,
freeze({
...op,
...payload,
origin_address,
kind: kind(op.opcode),
source_line: null,
definitions,
@@ -224,6 +230,8 @@ function createViews(facts, code, indexes) {
operations: normalized.size,
blocks: blocks.size,
control_flow: "complete-for-recovered-graph",
representation:
"optimized recovered IR; origins do not establish machine branch targets or block extents",
runtime_successors: "unknown",
memory_alias_dependencies: "unknown",
issues: facts.coverage.issues,
@@ -274,7 +282,7 @@ function createViews(facts, code, indexes) {
function selectOperations(normalized, constants, input = {}) {
const ids = input.ids && new Set(input.ids),
addresses = input.addresses && new Set(input.addresses),
addresses = input.origin_addresses && new Set(input.origin_addresses),
blockIds = input.block_ids && new Set(input.block_ids);
const candidates = ids
? [...ids].flatMap((id) => normalized.get(id) ?? [])
@@ -286,7 +294,7 @@ function selectOperations(normalized, constants, input = {}) {
return page(
[...candidates].filter(
(op) =>
(!addresses || addresses.has(op.address)) &&
(!addresses || addresses.has(op.origin_address)) &&
(!blockIds || blockIds.has(op.block)) &&
(!input.constant ||
op.constants.some((literal) => literal.value === input.constant)) &&
@@ -119,6 +119,11 @@ function diamond() {
test("native identity, implicit CFG and operand/phi bindings do not depend on display text", () => {
const model = parseNativeIR(JSON.stringify(diamond()), "0x1000");
assert.equal(model.operation("0x3:0").origin_address, "0x3");
assert.equal(Object.hasOwn(model.operation("0x3:0"), "address"), false);
assert.equal(model.block(1).origin_start, "0x1001");
assert.equal(Object.hasOwn(model.block(1), "start"), false);
assert.equal(model.select({ origin_addresses: ["0x3"] }).total, 1);
assert.deepEqual(model.findConstant("0xffffffffffffffff"), [
"0x1:0",
"0x2:0",
+52 -16
View File
@@ -8,27 +8,14 @@ import { KunaTargetCommands } from "../../dist/kuna/KunaTargetCommands.js";
import { parseKunaStringInventory } from "../../dist/kuna/KunaStringInventory.js";
import { parseKunaIncomingReferenceFacts } from "../../dist/kuna/KunaReferenceFiles.js";
import { PrivateRuntimeRoot } from "../../dist/process/PrivateRuntimeRoot.js";
import { projectAnalysisError } from "../../dist/domain/analysisErrorProjection.js";
import { parseNativeIR } from "./native-ir.mjs";
import { FactFailure } from "./fact-error.mjs";
import { FactFailure, requireResult } from "./fact-error.mjs";
import { parseInstructions } from "./instruction-facts.mjs";
import {
parseKunaSsaDump,
backwardSlice,
} from "../atomic-re-tools/kuna-ssa.mjs";
const requireResult = (result, code = "provider_failure") => {
if (!result.ok) {
const failure = result.error?._tag
? projectAnalysisError(result.error)
: result.error;
throw new FactFailure(
failure?.code ?? code,
String(failure?.message ?? failure?.reason ?? failure),
{ failure },
);
}
return result.value;
};
const sha256 = (value) => createHash("sha256").update(value).digest("hex");
const sourceSummary = ({ evidence_id, sha256, bytes, elapsed_ms }) => ({
evidence_id,
@@ -297,7 +284,7 @@ export class KunaFactOwner {
evidence_id: capture.source.evidence_id,
});
const parsed = await this.#load(id);
if (parsed.block(0).start !== input.entry)
if (parsed.block(0).origin_start !== input.entry)
throw new FactFailure(
"subject_mismatch",
"Native SSA entry does not match the requested function entry",
@@ -309,6 +296,55 @@ export class KunaFactOwner {
coverage: parsed.coverage,
};
}
async instructions(input, signal) {
const results = [];
for (const request of input.windows) {
let capture;
try {
capture = await this.#native(
`instructions:${request.address}:${request.count}`,
(path) => [
"disassemble",
path,
request.address,
"--addr",
"--as",
"code",
"--count",
String(request.count),
"--json",
],
signal,
);
const parsed = parseInstructions(
await this.#sourceText(capture.source.evidence_id),
capture.input,
request,
input.fields,
);
results.push({
ok: true,
address: request.address,
source: sourceSummary(capture.source),
...parsed,
});
} catch (error) {
results.push({
ok: false,
address: request.address,
error: {
code: error.code ?? "provider_failure",
message: error.message,
details: {
...error.details,
...(capture ? { evidence_id: capture.source.evidence_id } : {}),
},
},
});
}
}
return { results };
}
async #load(id) {
const descriptor = this.#snapshots.get(id);
if (!descriptor)
+37
View File
@@ -150,3 +150,40 @@ already substantially supported. This is a reason to test task-specific value
tracing and small-function counterexamples, rather than to claim that more
granular tool catalogs are inherently efficient. Private artifacts are named
`zlib-native-codemode-v3*` beside the earlier trials.
The value-origin task uses the same patched engine in both lanes, with fresh
provider/session caches and the pinned `f2f029c5d` runtime. It asks about the
accumulator at the already identified instruction origin `0xd9c9`; this is a
seeded follow-up investigation, not a repeated string-discovery task. The
traditional client retains its nine selected tools. Both traces use the real
Kuna engine, complete without API errors, preserve the target/runtime digests
and verify profile/payload cleanup with no OOM kills.
| Measurement | Native IR + Code Mode | Traditional |
| --- | ---: | ---: |
| Outer MCP attempts | 8 | 5 |
| Cumulative model input tokens | 284,638 | 390,557 |
| Included cached input tokens | 220,672 | 326,016 |
| Uncached input tokens | 63,966 | 64,541 |
| Output / included reasoning tokens | 2,601 / 309 | 2,614 / 531 |
| Model runner wall seconds | 72.403 | 91.832 |
| Bounded family elapsed seconds | 72.841 | 92.254 |
| Bounded family CPU seconds | 11.896 | 20.866 |
| Kernel process-family memory peak MiB | 461.6 | 846.0 |
Both answers recover the three accumulator alternatives, byte loads, shifts,
additions, local bit-count conditions and unknown state. The native answer,
however, says that the branch at `0xd97e` directly reaches `0xd9c9`. This describes
the optimized recovered CFG, but the actual bytes `0f87e9080000` encode
`JA 0xe26d`; `0xe270` then encodes `JMP 0xd9c9`. The traditional answer identifies
that machine predecessor and intervening copy/jump. This was checked separately
with the shipped machine bytes, rather than accepting either model's answer as
the oracle.
The native trace's lower cumulative input and wall time therefore do **not** yet
establish an equal-correctness completed-task win. Uncached input is also almost
equal; cached cumulative input alone is not an economic-cost measurement. The
representation ambiguity needs a structural fix: distinguish IR origin fields
from physical instruction facts and expose selected instruction windows, then
rerun the unchanged question. Private artifacts are `zlib-value-codemode-v1*`
and `zlib-value-traditional-v1*`.
+12 -4
View File
@@ -80,10 +80,10 @@ const values=await read(rea.values({snapshot:s,ids:phi.items.flatMap(o=>o.inputs
return {blocks,controls,guards,phi,values};`,
});
assert.equal(native.value.controls.total, 2);
assert.deepEqual(native.value.guards.items.map((op) => op.address).sort(), [
"0xd9ec",
"0xda32",
]);
assert.deepEqual(
native.value.guards.items.map((op) => op.origin_address).sort(),
["0xd9ec", "0xda32"],
);
assert.ok(
native.value.phi.items.every((op) =>
op.inputs.every(
@@ -97,6 +97,14 @@ return {blocks,controls,guards,phi,values};`,
);
assert.ok(native.value.values.items.length > 0);
}
const machine = await call("execute_javascript", {
code: 'const r=await rea.instructions({windows:[{address:"0xd97e",count:1},{address:"0xe26d",count:2},{address:"0xdeadbeef",count:1}]});if(!r.ok)throw new Error(JSON.stringify(r));return r.data;',
});
assert.equal(machine.value.results[0].items[0].bytes, "0f87e9080000");
assert.match(machine.value.results[0].items[0].text, /e26d/u);
assert.equal(machine.value.results[1].items[1].address, "0xe270");
assert.match(machine.value.results[1].items[1].text, /d9c9/u);
assert.equal(machine.value.results[2].ok, false);
await call("close_binary");
} catch (error) {
failure = error.stack ?? String(error);
+23 -11
View File
@@ -86,7 +86,7 @@ const parseOperation = (match, blockId, sourceLine) => {
}));
const row = {
id,
address: id.split(":")[0],
origin_address: id.split(":")[0],
block: blockId,
kind,
statement,
@@ -110,7 +110,7 @@ const controlEdges = (row) => {
edges.push({
from: row.block,
to: ordinal(target),
target_address: address(targetAddress),
target_origin: address(targetAddress),
kind: "conditional",
polarity,
predicate: branch[3],
@@ -122,7 +122,7 @@ const controlEdges = (row) => {
edges.push({
from: row.block,
to: ordinal(jump[1]),
target_address: address(jump[2]),
target_origin: address(jump[2]),
kind: "direct",
operation: row.id,
source_line: row.source_line,
@@ -163,7 +163,7 @@ export const parseKunaSsaDump = (text) => {
// Stop is the last instruction's address, NOT an exclusive byte bound.
block = {
id,
start: address(header[2]),
origin_start: address(header[2]),
stop_instruction: address(header[3]),
operations: [],
source_line: i + 1,
@@ -177,7 +177,7 @@ export const parseKunaSsaDump = (text) => {
edges.push({
from: block.id,
to: ordinal(fallthrough[2]),
target_address: address(fallthrough[3]),
target_origin: address(fallthrough[3]),
kind: "printed-implied-goto",
operation: null,
source_line: i + 1,
@@ -204,8 +204,8 @@ export const parseKunaSsaDump = (text) => {
validateDefinitions(operations, issues);
for (const edge of edges) {
assert.equal(
blocks.get(edge.to)?.start,
edge.target_address,
blocks.get(edge.to)?.origin_start,
edge.target_origin,
"Branch target identity mismatch",
);
indexInto(incoming, edge.to, edge);
@@ -224,6 +224,8 @@ export const parseKunaSsaDump = (text) => {
identity: { kind: "native-ssa-snapshot", sha256: digest },
coverage: {
scope: "printed-SSA",
representation:
"optimized recovered IR; origins are not machine branch targets or block extents",
operations: operations.size,
blocks: blocks.size,
explicit_control_edges: edges.length,
@@ -252,7 +254,7 @@ const queryViews = ({
incoming: (id) => incoming.get(id) ?? [],
select: ({
ids,
addresses,
origin_addresses,
block_ids,
constant,
kind,
@@ -262,7 +264,9 @@ const queryViews = ({
} = {}) => {
const selectedIds = ids === undefined ? null : new Set(ids);
const locations =
addresses === undefined ? null : new Set(addresses.map(address));
origin_addresses === undefined
? null
: new Set(origin_addresses.map(address));
const selectedBlocks = block_ids === undefined ? null : new Set(block_ids);
const literalIds =
constant === undefined
@@ -271,7 +275,7 @@ const queryViews = ({
const rows = [...operations.values()].filter(
(row) =>
(!selectedIds || selectedIds.has(row.id)) &&
(!locations || locations.has(row.address)) &&
(!locations || locations.has(row.origin_address)) &&
(!selectedBlocks || selectedBlocks.has(row.block)) &&
(!literalIds || literalIds.has(row.id)) &&
(kind === undefined || row.kind === kind),
@@ -299,7 +303,15 @@ const page = (rows, offset, limit, fields) => ({
fields === undefined
? row
: Object.fromEntries(
[...new Set(["id", "address", "block", "source_line", ...fields])]
[
...new Set([
"id",
"origin_address",
"block",
"source_line",
...fields,
]),
]
.filter((key) => Object.hasOwn(row, key))
.map((key) => [key, row[key]]),
),
@@ -91,7 +91,7 @@ test("phi/load cutoffs preserve boundary rows and do not invent memory or predec
test("cloned blocks share starts; operation identity includes sequence, and facts stay immutable", () => {
const snapshot = parseKunaSsaDump(dump.replaceAll("0x120", "0x110"));
assert.equal(snapshot.block(1).start, snapshot.block(2).start);
assert.equal(snapshot.block(1).origin_start, snapshot.block(2).origin_start);
assert.notEqual(snapshot.block(1).id, snapshot.block(2).id);
assert.throws(() => {
snapshot.operation("0x100:1").kind = "store";
@@ -49,7 +49,7 @@ for (const [i, expectedLine] of [164, 551, 653].entries()) {
const failed = await mappedCode(native, original);
assert.equal(failed.status, "unknown");
const sites = answer.direct_uses.map((id) => ({
from: answer.operations.find((op) => op.id === id).address,
from: answer.operations.find((op) => op.id === id).origin_address,
}));
const mapped = await mappedCode(native, {
functions: original.functions,