///|
fn function_path_status_text(status : FunctionPathStatus) -> String {
match status {
PathFound => "found"
PathNotFound => "not-found-within-model"
PathSearchLimited => "search-limited"
}
}
///|
fn function_path_scope() -> String {
"This is a path in the current reference model, not an execution trace or a prediction of recompiled or gzip savings. " +
graph_modeling_scope()
}
///|
fn path_root_kind(kind : FunctionRootKind) -> String {
match kind {
ExportRoot => "export"
StartRoot => "start"
ElementRoot => "conservative-element"
}
}
///|
fn path_edge_kind(kind : EdgeKind) -> String {
match kind {
Direct => "direct"
Reference => "reference"
Indirect => "indirect"
}
}
///|
fn path_optional_string(text : String?) -> Json {
match text {
Some(text) => Json::string(text)
None => Json::null()
}
}
///|
fn path_node_json(node : FunctionPathNode) -> Json {
let row : Map[String, Json] = Map([])
row.set("index", int_json(node.index))
row.set("name", Json::string(node.name))
row.set("raw_name", path_optional_string(node.raw_name))
row.set("imported", Json::boolean(node.imported))
row.set("encoded_size", optional_index_json(node.encoded_size))
row.set("import_module", path_optional_string(node.import_module))
row.set("import_name", path_optional_string(node.import_name))
Json::object(row)
}
///|
fn path_reason_json(reason : FunctionRootReason) -> Json {
let row : Map[String, Json] = Map([])
row.set("function_index", int_json(reason.function_index))
row.set("kind", Json::string(path_root_kind(reason.kind)))
row.set("name", path_optional_string(reason.name))
row.set("section_offset", int_json(reason.section_offset))
row.set("position_granularity", Json::string("section"))
row.set("ordinal", optional_index_json(reason.ordinal))
row.set("table_index", optional_index_json(reason.table_index))
row.set("element_mode", path_optional_string(reason.element_mode))
row.set("conservative", Json::boolean(reason.kind == ElementRoot))
Json::object(row)
}
///|
/// Independent format version; existing call-graph JSON is unchanged.
pub fn render_function_path_json(report : FunctionPathReport) -> String {
let root : Map[String, Json] = Map([])
root.set("schema_version", int_json(1))
root.set("format", Json::string("moonsize-function-path"))
let query : Map[String, Json] = Map([])
query.set("target_index", int_json(report.target.index))
query.set("root_index", optional_index_json(report.requested_root))
query.set(
"scope",
Json::string(
if report.requested_root is Some(_) {
"selected-root"
} else {
"all-model-roots"
},
),
)
root.set("query", Json::object(query))
root.set("status", Json::string(function_path_status_text(report.status)))
root.set("target", path_node_json(report.target))
root.set("path_root", optional_index_json(report.path_root))
root.set("selected_roots", Json::array(report.selected_roots.map(int_json)))
let model_roots : Array[Json] = []
let selected_reasons : Array[Json] = []
let selected = Set::from_array(report.selected_roots)
for item in report.roots {
let row : Map[String, Json] = Map([])
let reasons = item.reasons.map(path_reason_json)
row.set("index", int_json(item.index))
row.set("reasons", Json::array(reasons))
model_roots.push(Json::object(row))
if selected.contains(item.index) {
for reason in reasons {
selected_reasons.push(reason)
}
}
}
root.set("model_roots", Json::array(model_roots))
root.set(
"root_ledger_complete",
Json::boolean(report.search_stats.phase != "root-preparation"),
)
root.set("root_reasons", Json::array(selected_reasons))
root.set("nodes", Json::array(report.nodes.map(path_node_json)))
root.set(
"steps",
Json::array(
report.steps.map(fn(step) {
let row : Map[String, Json] = Map([])
row.set("caller", int_json(step.caller))
row.set("callee", int_json(step.callee))
row.set("kind", Json::string(path_edge_kind(step.kind)))
row.set("candidate", Json::boolean(path_edge_rank(step.kind) == 2))
row.set("opcode", Json::string(step.opcode.name()))
row.set("offset", int_json(step.offset))
row.set("type_index", optional_index_json(step.type_index))
row.set("table_index", optional_index_json(step.table_index))
Json::object(row)
}),
),
)
root.set("graph_complete", Json::boolean(report.graph_complete))
root.set("diagnostics", diagnostic_records_json(report.diagnostics))
root.set("modeling_scope", Json::string(function_path_scope()))
let limits : Map[String, Json] = Map([])
limits.set("max_nodes", int_json(report.limits.max_nodes))
limits.set("max_edges", int_json(report.limits.max_edges))
limits.set("max_depth", int_json(report.limits.max_depth))
root.set("limits", Json::object(limits))
let stats : Map[String, Json] = Map([])
stats.set("root_sources", int_json(report.search_stats.root_sources))
stats.set("prepared_roots", int_json(report.search_stats.prepared_roots))
stats.set("indexed_edges", int_json(report.search_stats.indexed_edges))
stats.set("visited_nodes", int_json(report.search_stats.visited_nodes))
stats.set("checked_edges", int_json(report.search_stats.checked_edges))
stats.set("max_depth", int_json(report.search_stats.max_depth))
stats.set("phase", Json::string(report.search_stats.phase))
stats.set(
"limit_reason",
path_optional_string(report.search_stats.limit_reason),
)
root.set("search_stats", Json::object(stats))
Json::object(root).stringify(indent=2) + "\n"
}
///|
pub fn render_function_path(report : FunctionPathReport) -> String {
let out = StringBuilder()
out.write_string(
"FUNCTION REFERENCE PATH\n\nTarget: #" +
report.target.index.to_string() +
" " +
Json::string(report.target.name).stringify() +
"\n",
)
out.write_string(
"Identity: " +
(if report.target.imported {
"imported; no local body (encoded size is not measured)\n"
} else {
"local; encoded bytes " +
report.target.encoded_size.unwrap_or(0).to_string() +
"\n"
}),
)
out.write_string(
"Scope: " +
(match report.requested_root {
Some(index) => "selected model root #" + index.to_string()
None =>
"all model roots (" +
report.selected_roots.length().to_string() +
" selected)"
}) +
"\n",
)
out.write_string("Status: " + function_path_status_text(report.status) + "\n")
if report.search_stats.phase == "root-preparation" {
out.write_string(
"Root preparation stopped early; the recorded root ledger is only a bounded prefix.\n",
)
}
if report.status == PathNotFound {
out.write_string(
if report.requested_root is Some(_) {
"No path from the selected root within this model. Other roots may still reach the target.\n"
} else {
"Search completed without a path from the model roots. This is not proof that the function can be removed.\n"
},
)
}
let selected = Set::from_array(report.selected_roots)
for item in report.roots {
if selected.contains(item.index) {
out.write_string("Search root #" + item.index.to_string() + "\n")
for reason in item.reasons {
out.write_string("Root reason: " + path_root_kind(reason.kind))
if reason.name is Some(name) {
out.write_string(" " + Json::string(name).stringify())
}
out.write_string(
"; section offset " + reason.section_offset.to_string(),
)
if reason.ordinal is Some(ordinal) {
out.write_string("; entry/segment ordinal " + ordinal.to_string())
}
if reason.element_mode is Some(mode) {
out.write_string(
"; " +
mode +
"; conservatively retained, not evidence of a business call",
)
}
out.write_string("\n")
}
}
}
if !report.nodes.is_empty() {
out.write_string(
"\n#" +
report.nodes[0].index.to_string() +
" " +
Json::string(report.nodes[0].name).stringify() +
"\n",
)
for i, step in report.steps {
out.write_string(
" -- " +
step.opcode.name() +
" [" +
(match step.kind {
Indirect => "indirect candidate"
Reference => "reference taken, not a call"
Direct => "direct"
}) +
"]",
)
if step.table_index is Some(table) {
out.write_string(" table " + table.to_string())
}
if step.type_index is Some(signature) {
out.write_string(" type " + signature.to_string())
}
out.write_string(
" at file offset " +
step.offset.to_string() +
" --> #" +
step.callee.to_string() +
" " +
Json::string(report.nodes[i + 1].name).stringify() +
"\n",
)
}
if report.steps.is_empty() {
out.write_string(
"Zero-step path: the target itself is a selected model root.\n",
)
}
}
if report.search_stats.limit_reason is Some(reason) {
out.write_string(
"Search stopped: " +
reason +
" during " +
report.search_stats.phase +
"; no completed not-found conclusion.\n",
)
}
if !report.graph_complete {
out.write_string(
"\nWARNING: graph incomplete; unmodelled references cannot be excluded, even if this query found a path.\n",
)
for diagnostic in report.diagnostics {
out.write_string(
" function " +
diagnostic.caller.to_string() +
", offset " +
diagnostic.offset.to_string() +
": " +
diagnostic.opcode.name() +
" [" +
diagnostic_code(diagnostic.kind) +
"]: " +
diagnostic_details(diagnostic) +
"\n",
)
}
}
out.write_string(
"\nSearch counts: roots " +
report.search_stats.prepared_roots.to_string() +
", root sources " +
report.search_stats.root_sources.to_string() +
", indexed edges " +
report.search_stats.indexed_edges.to_string() +
", visited nodes " +
report.search_stats.visited_nodes.to_string() +
", checked edges " +
report.search_stats.checked_edges.to_string() +
", depth " +
report.search_stats.max_depth.to_string() +
".\n",
)
out.write_string(
"Limits: nodes " +
report.limits.max_nodes.to_string() +
", edges/root sources " +
report.limits.max_edges.to_string() +
", depth " +
report.limits.max_depth.to_string() +
".\n\n" +
function_path_scope() +
"\n",
)
out.to_string()
}