Files
rea/experiments/atomic-codemode/native-ir.mjs
T

378 lines
12 KiB
JavaScript

// The native-format boundary owns validation and indexes; consumers never parse
// diagnostic statements to recover value identity, operand indexes or CFG edges.
import { FactFailure } from "./fact-error.mjs";
import {
indexSwitchFacet,
validateSwitchGraph,
validateSwitchValues,
createSwitchViews,
} from "./native-switch-views.mjs";
import { parseNativeFunction } from "./native-ir-contract.mjs";
import { createControlDependenceIndex } from "./control-dependence.mjs";
import { createGraphPathReader } from "./cfg-path.mjs";
import { createParameterViews } from "./native-parameters.mjs";
import { freezeFacts as freeze } from "./fact-reads.mjs";
import {
indexCallFacet,
validateCallGraph,
createCallViews,
} from "./native-call-views.mjs";
import { requireFact } from "./native-contract.mjs";
import {
indexOperationFacts,
createOperationViews,
coverageIssues,
isNativeOperationRelation,
} from "./native-operation-views.mjs";
import {
indexProjection,
createProjectionViews,
} from "./native-projection-views.mjs";
import {
uniqueNative as unique,
nativePage as page,
readNativeIdentities as readIdentities,
} from "./native-reads.mjs";
function validateBlocks(operations, values, blocks) {
const membership = new Set();
for (const block of blocks.values()) {
for (const id of block.operations) {
requireFact(
operations.get(id)?.block === block.id && !membership.has(id),
"Native operation/block ownership mismatch",
);
membership.add(id);
}
for (const [index, edge] of block.successors.entries()) {
requireFact(
edge.index === index &&
edge.id === `${block.id}:${index}` &&
blocks.get(edge.target)?.predecessors.includes(block.id),
"Native CFG successor/predecessor mismatch",
);
requireFact(
edge.condition_value === null || values.has(edge.condition_value),
"Dangling native branch condition",
);
}
for (const from of block.predecessors)
requireFact(
blocks.get(from)?.successors.some((edge) => edge.target === block.id),
"Dangling native predecessor",
);
}
requireFact(
membership.size === operations.size,
"Native operation omitted from block membership",
);
}
function validatePhiBindings(operations, blocks) {
for (const op of operations.values())
for (const [position, input] of op.inputs.entries()) {
if (input.phi_predecessor !== null)
requireFact(
blocks.get(op.block).predecessors[position] === input.phi_predecessor,
"Native phi/predecessor mismatch",
);
if (input.phi_predecessor_edge !== null)
requireFact(
blocks
.get(input.phi_predecessor)
?.successors.some(
(edge) =>
edge.id === input.phi_predecessor_edge &&
edge.target === op.block,
),
"Native phi incoming-edge identity mismatch",
);
}
}
function indexNative(facts) {
const core = indexOperationFacts(
facts.operations,
facts.values,
facts.coverage.omitted_definition_ids,
);
const blocks = unique(
facts.blocks.map(({ start, ...block }) =>
freeze({ ...block, origin_start: start }),
),
);
validateBlocks(core.operations, core.values, blocks);
validatePhiBindings(core.operations, blocks);
const incoming = new Map(),
outgoing = new Map();
for (const block of blocks.values())
for (const edge of block.successors) {
const row = freeze({
...edge,
from: block.id,
to: edge.target,
operation: block.operations.at(-1) ?? null,
kind: edge.polarity ? "conditional" : "recovered",
});
for (const [map, key] of [
[incoming, edge.target],
[outgoing, block.id],
]) {
if (!map.has(key)) map.set(key, []);
map.get(key).push(row);
}
}
return { ...core, blocks, incoming, outgoing };
}
function createViews(facts, code, indexes) {
const { blocks, incoming, outgoing, normalized } = indexes;
const {
issues,
indirect_branch_operations,
omitted_definition_ids,
...metadata
} = facts.coverage;
const coverage = freeze({
...metadata,
...coverageIssues(issues),
indirect_branch_operation_count: indirect_branch_operations.length,
omitted_definition_count: omitted_definition_ids.length,
operations: normalized.size,
values: indexes.values.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",
});
const core = createOperationViews(indexes, coverage);
let controls;
return Object.freeze({
...core,
code,
block: (id) => blocks.get(id) ?? null,
incoming: (id) => incoming.get(id) ?? [],
blocks: (input) => readIdentities(blocks, input),
path: createGraphPathReader(blocks, outgoing),
edges: (input) => {
if (isNativeOperationRelation(input.kind))
return core.operationRelations(input);
const seeds = [...new Set(input.seeds)],
unretained_seeds = seeds.filter((id) => !blocks.has(id));
if (input.kind === "controllers") {
controls ??= createControlDependenceIndex(facts.blocks);
return {
...controls.read({ ...input, seeds }),
unretained_seeds,
};
}
return {
...page(
seeds.flatMap(
(seed) =>
(input.kind === "incoming" ? incoming : outgoing).get(seed) ?? [],
),
input,
),
unretained_seeds,
};
},
});
}
const projectedFailure = (error) => ({
code: error.code ?? "invalid_provider_output",
message: error.message,
details: error.details ?? {},
});
const operationFailure = (projectionError, graphError) =>
projectionError?.details?.availability === "not-captured"
? graphError
: (projectionError ?? graphError);
const captureFacets = ({
graph,
projection,
calls,
graphError,
operationError,
callError,
switches,
switchError,
parameters,
parameterError,
}) =>
freeze({
code: { availability: "available" },
parameters: parameters
? { availability: "available", count: parameters.count }
: { availability: "unavailable", error: parameterError },
recovered_ir: graph
? { availability: "available" }
: { availability: "unavailable", error: graphError },
operations: graph
? {
availability: "available",
scope: "complete-for-recovered-graph",
count: graph.coverage.operations,
}
: projection
? {
availability: "available",
scope: "bounded-call-rooted-definitions",
count: projection.coverage.operations,
root_count: projection.coverage.root_count,
definition_frontier_count:
projection.coverage.definition_frontier_count,
}
: {
availability: "unavailable",
error: operationError,
},
calls: calls
? { availability: "available", ...calls.summary }
: { availability: "unavailable", error: callError },
switches: switches
? { availability: "available", ...switches.summary }
: { availability: "unavailable", error: switchError },
values: graph
? {
availability: "available",
scope: "complete-for-recovered-graph",
count: graph.coverage.values,
}
: projection
? {
availability: "available",
scope: "call-rooted-definitions",
count: projection.coverage.values,
}
: calls
? {
availability: "available",
scope: "call-boundary-only",
count: calls.value_count,
}
: { availability: "unavailable", error: callError },
});
const loadFacet = (result, build) => {
if (!result.ok) return { error: projectedFailure(result.error) };
try {
return build(result.value);
} catch (error) {
return { error: projectedFailure(error) };
}
};
const unavailable =
(facet, error = {}) =>
() => {
throw new FactFailure(error.code, error.message, {
...error.details,
facet,
});
};
export function parseNativeIR(text, entry, expectedInput) {
const capture = parseNativeFunction(text, entry, expectedInput);
const parameters = loadFacet(capture.parameters, (facts) => ({
view: createParameterViews(facts),
}));
const graph = loadFacet(capture.facts, (facts) => {
freeze(facts);
const index = indexNative(facts);
return { index, view: createViews(facts, capture.code, index) };
});
const calls = loadFacet(capture.calls, (facts) => {
const index = indexCallFacet(facts);
if (graph.view) validateCallGraph(index, graph.index);
return { index, view: createCallViews(facts, index) };
});
const unavailableGraph = unavailable("recovered_ir", graph.error);
const projection = graph.view
? {}
: loadFacet(capture.projection, (facts) => {
const index = indexProjection(facts);
// Without a full graph, the independent call owner validates the projection.
if (calls.view) validateCallGraph(calls.index, index);
return {
index,
view: createProjectionViews(
facts,
capture.code,
index,
unavailableGraph,
),
};
});
const unavailableCalls = unavailable("calls", calls.error);
const switches = loadFacet(capture.switches, (facts) => {
const index = indexSwitchFacet(facts);
if (graph.view) validateSwitchGraph(index, graph.index);
else {
validateSwitchValues(index, calls.index?.values);
validateSwitchValues(index, projection.index?.values);
}
return { view: createSwitchViews(facts, index) };
});
const unavailableSwitches = unavailable("switches", switches.error);
const operationError = operationFailure(projection.error, graph.error);
const unavailableOperations = unavailable("operations", operationError);
const fallback = {
code: capture.code,
coverage: freeze({
scope: "native reconstructed code; recovered IR facet unavailable",
operations: null,
blocks: null,
control_flow: "unknown",
machine_semantic_equivalence: "unknown",
issues: [graph.error?.message],
}),
...Object.fromEntries(
["operation", "findConstant", "uses", "select", "slice"].map((name) => [
name,
unavailableOperations,
]),
),
...Object.fromEntries(
["block", "incoming", "blocks"].map((name) => [name, unavailableGraph]),
),
edges: (input) =>
isNativeOperationRelation(input.kind)
? unavailableOperations()
: unavailableGraph(),
};
return Object.freeze({
...(graph.view ?? projection.view ?? fallback),
path: graph.view?.path ?? unavailableGraph,
parameters:
parameters.view?.parameters ??
unavailable("parameters", parameters.error),
edges: (input) =>
input.kind === "cases"
? (switches.view?.cases ?? unavailableSwitches)(input)
: (graph.view ?? projection.view ?? fallback).edges(input),
language: capture.language,
calls: calls.view?.calls ?? unavailableCalls,
switches: switches.view?.switches ?? unavailableSwitches,
values:
graph.view?.values ??
projection.view?.values ??
calls.view?.values ??
unavailableCalls,
facets: captureFacets({
graph: graph.view,
projection: projection.view,
calls: calls.view,
graphError: graph.error,
operationError,
callError: calls.error,
switches: switches.view,
switchError: switches.error,
parameters: parameters.view,
parameterError: parameters.error,
}),
});
}