diff --git a/experiments/atomic-codemode/README.md b/experiments/atomic-codemode/README.md index 27f1fdca9..174cf242a 100644 --- a/experiments/atomic-codemode/README.md +++ b/experiments/atomic-codemode/README.md @@ -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, diff --git a/experiments/atomic-codemode/api.mjs b/experiments/atomic-codemode/api.mjs index 983c5b56d..9fab90e1f 100644 --- a/experiments/atomic-codemode/api.mjs +++ b/experiments/atomic-codemode/api.mjs @@ -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.", diff --git a/experiments/atomic-codemode/executor.mjs b/experiments/atomic-codemode/executor.mjs index 2b7a27cd6..6cb8183df 100644 --- a/experiments/atomic-codemode/executor.mjs +++ b/experiments/atomic-codemode/executor.mjs @@ -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) diff --git a/experiments/atomic-codemode/fact-error.mjs b/experiments/atomic-codemode/fact-error.mjs index d53d09e56..0de4803ee 100644 --- a/experiments/atomic-codemode/fact-error.mjs +++ b/experiments/atomic-codemode/fact-error.mjs @@ -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; +}; diff --git a/experiments/atomic-codemode/instruction-facts.mjs b/experiments/atomic-codemode/instruction-facts.mjs new file mode 100644 index 000000000..f4c5ffff3 --- /dev/null +++ b/experiments/atomic-codemode/instruction-facts.mjs @@ -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), + }, + ); + } +} diff --git a/experiments/atomic-codemode/instruction-facts.test.mjs b/experiments/atomic-codemode/instruction-facts.test.mjs new file mode 100644 index 000000000..b88588192 --- /dev/null +++ b/experiments/atomic-codemode/instruction-facts.test.mjs @@ -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); +}); diff --git a/experiments/atomic-codemode/native-engine.md b/experiments/atomic-codemode/native-engine.md index 05348023c..59a4d9660 100644 --- a/experiments/atomic-codemode/native-engine.md +++ b/experiments/atomic-codemode/native-engine.md @@ -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. diff --git a/experiments/atomic-codemode/native-ir.mjs b/experiments/atomic-codemode/native-ir.mjs index ca9eae60a..603fdc171 100644 --- a/experiments/atomic-codemode/native-ir.mjs +++ b/experiments/atomic-codemode/native-ir.mjs @@ -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)) && diff --git a/experiments/atomic-codemode/native-ir.test.mjs b/experiments/atomic-codemode/native-ir.test.mjs index d4222ec44..c2f4152c5 100644 --- a/experiments/atomic-codemode/native-ir.test.mjs +++ b/experiments/atomic-codemode/native-ir.test.mjs @@ -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", diff --git a/experiments/atomic-codemode/provider.mjs b/experiments/atomic-codemode/provider.mjs index 89c0a39cc..3655f5591 100644 --- a/experiments/atomic-codemode/provider.mjs +++ b/experiments/atomic-codemode/provider.mjs @@ -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) diff --git a/experiments/atomic-codemode/trials.md b/experiments/atomic-codemode/trials.md index 22c0ac55e..83efbb98b 100644 --- a/experiments/atomic-codemode/trials.md +++ b/experiments/atomic-codemode/trials.md @@ -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*`. diff --git a/experiments/atomic-codemode/verify-mcp.mjs b/experiments/atomic-codemode/verify-mcp.mjs index 61ad6c6aa..ba865e6ff 100644 --- a/experiments/atomic-codemode/verify-mcp.mjs +++ b/experiments/atomic-codemode/verify-mcp.mjs @@ -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); diff --git a/experiments/atomic-re-tools/kuna-ssa.mjs b/experiments/atomic-re-tools/kuna-ssa.mjs index 3deedf962..f60b668be 100644 --- a/experiments/atomic-re-tools/kuna-ssa.mjs +++ b/experiments/atomic-re-tools/kuna-ssa.mjs @@ -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]]), ), diff --git a/experiments/atomic-re-tools/kuna-ssa.test.mjs b/experiments/atomic-re-tools/kuna-ssa.test.mjs index 23289bf4d..2a6a2f4e2 100644 --- a/experiments/atomic-re-tools/kuna-ssa.test.mjs +++ b/experiments/atomic-re-tools/kuna-ssa.test.mjs @@ -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"; diff --git a/experiments/atomic-re-tools/verify-relations.mjs b/experiments/atomic-re-tools/verify-relations.mjs index 96c4357cc..a089b747e 100644 --- a/experiments/atomic-re-tools/verify-relations.mjs +++ b/experiments/atomic-re-tools/verify-relations.mjs @@ -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,