///|
/// Existing diagnostic fields and provenance, plus readable reasons in this new
/// format. Sorting and record encoding are shared with the graph reports.
fn summary_diagnostics(graph : CallGraph) -> Json {
let ordered = sorted_diagnostics(graph)
let value = diagnostic_records_json(ordered)
if value is Array(rows) {
for i, row in rows {
if row is Object(fields) {
fields.set("reason", Json::string(diagnostic_details(ordered[i])))
}
}
}
value
}
///|
/// Render only existing analysis results. No parsing, inspectors, graph,
/// instruction decoding, compression or retained calculation; hash once.
pub fn render_summary_json(analysis : Analysis) -> String {
let root : Map[String, Json] = Map([])
root.set("format", Json::string("moonsize-summary"))
root.set("schema_version", int_json(1))
let input : Map[String, Json] = Map([])
input.set("file_size", int_json(analysis.parsed.file_size))
input.set("sha256", Json::string(experiment_sha256(analysis.data)))
input.set("wasm_version", int_json(analysis.parsed.version))
root.set("input", Json::object(input))
let sections : Array[Json] = []
let ordered = analysis.parsed.sections.copy()
ordered.sort_by(fn(a, b) { a.offset.compare(b.offset) })
let mut section_bytes = 0
for ordinal, section in ordered {
let row : Map[String, Json] = Map([])
row.set("ordinal", int_json(ordinal))
row.set("id", int_json(section.id))
row.set("kind", Json::string(section.kind.name()))
row.set("offset", int_json(section.offset))
row.set("payload_size", int_json(section.payload_size))
row.set("total_size", int_json(section.total_size))
row.set(
"section_header_bytes",
int_json(section.total_size - section.payload_size),
)
row.set("custom_name_display", path_optional_string(section.custom_name))
sections.push(Json::object(row))
section_bytes += section.total_size
}
root.set("sections", Json::array(sections))
let retained : Map[Int, RetainedSize] = Map([])
for entry in analysis.retained {
retained.set(entry.index, entry)
}
let stats = analysis.stats.copy()
stats.sort_by(fn(a, b) { a.index.compare(b.index) })
let functions : Array[Json] = []
let by_module : Map[String, Array[FunctionStat]] = Map([])
let mut body_bytes = 0
let mut prefix_bytes = 0
let mut encoded_bytes = 0
let mut reachable_count = 0
let mut reachable_bytes = 0
for stat in stats {
let row : Map[String, Json] = Map([])
row.set("index", int_json(stat.index))
row.set("name", Json::string(stat.name))
row.set("raw_name", path_optional_string(stat.raw_name))
row.set("module_name", Json::string(stat.module_name))
row.set("offset", int_json(stat.offset))
row.set("body_size", int_json(stat.body_size))
row.set("prefix_size", int_json(stat.total_size - stat.body_size))
row.set("total_size", int_json(stat.total_size))
row.set(
"model_reachable",
Json::boolean(analysis.reachable.contains(stat.index)),
)
row.set("model_root", Json::boolean(analysis.roots.contains(stat.index)))
row.set(
"model_indirect_candidate",
Json::boolean(analysis.indirect.contains(stat.index)),
)
row.set(
"model_retained",
match retained.get(stat.index) {
None => Json::null()
Some(entry) => {
let fields : Map[String, Json] = Map([])
fields.set("own_encoded_bytes", int_json(entry.own_size))
fields.set("dominated_encoded_bytes", int_json(entry.retained))
fields.set(
"other_dominated_function_count",
int_json(entry.retained_functions),
)
Json::object(fields)
}
},
)
functions.push(Json::object(row))
let members = by_module.get(stat.module_name).unwrap_or([])
members.push(stat)
by_module.set(stat.module_name, members)
body_bytes += stat.body_size
prefix_bytes += stat.total_size - stat.body_size
encoded_bytes += stat.total_size
if analysis.reachable.contains(stat.index) {
reachable_count += 1
reachable_bytes += stat.total_size
}
}
root.set("functions", Json::array(functions))
let labels : Array[String] = []
for label, _ in by_module {
labels.push(label)
}
labels.sort_by(fn(a, b) { a.lexical_compare(b) })
let modules : Array[Json] = []
for label in labels {
let members = by_module.get(label).unwrap_or([])
let indices : Array[Json] = []
let mut body = 0
let mut prefix = 0
let mut encoded = 0
// Groups were built from the copied, index-sorted stats.
for stat in members {
indices.push(int_json(stat.index))
body += stat.body_size
prefix += stat.total_size - stat.body_size
encoded += stat.total_size
}
let row : Map[String, Json] = Map([])
row.set("module_name", Json::string(label))
row.set("function_count", int_json(members.length()))
row.set("body_bytes", int_json(body))
row.set("prefix_bytes", int_json(prefix))
row.set("encoded_bytes", int_json(encoded))
row.set("function_indices", Json::array(indices))
modules.push(Json::object(row))
}
root.set("modules", Json::array(modules))
let summary : Map[String, Json] = Map([])
summary.set("wasm_header_bytes", int_json(8))
summary.set("section_count", int_json(sections.length()))
summary.set("section_bytes", int_json(section_bytes))
summary.set("defined_function_count", int_json(stats.length()))
summary.set("defined_body_bytes", int_json(body_bytes))
summary.set("defined_prefix_bytes", int_json(prefix_bytes))
summary.set("defined_encoded_bytes", int_json(encoded_bytes))
summary.set("reachable_defined_count", int_json(reachable_count))
summary.set(
"unreachable_defined_count",
int_json(stats.length() - reachable_count),
)
summary.set("reachable_encoded_bytes", int_json(reachable_bytes))
summary.set(
"unreachable_encoded_bytes",
int_json(encoded_bytes - reachable_bytes),
)
summary.set("module_count", int_json(labels.length()))
root.set("summary", Json::object(summary))
let roots : Array[Int] = []
for index in analysis.roots {
roots.push(index)
}
roots.sort_by(fn(a, b) { a.compare(b) })
let model : Map[String, Json] = Map([])
model.set("graph_complete", Json::boolean(analysis.graph_complete))
model.set("unresolved_count", int_json(analysis.unresolved_count))
model.set("diagnostic_count", int_json(analysis.graph.diagnostics.length()))
model.set("diagnostics", summary_diagnostics(analysis.graph))
model.set(
"root_indices",
Json::array(roots.map(fn(index) { int_json(index) })),
)
model.set(
"modeling_scope",
Json::string(
graph_modeling_scope() +
" Model reachability does not prove actual execution or safe removal. Module names are existing symbol attribution labels, not verified source packages or dependencies. Function/module bytes are contained within sections; these views must not be added together.",
),
)
model.set(
"retained_scope",
Json::string(
retained_modeling_scope() +
" dominated_encoded_bytes includes the function itself; other_dominated_function_count excludes it. Missing results remain null, never inferred as zero. Retained sets may overlap; do not sum retained values.",
),
)
root.set("model", Json::object(model))
Json::object(root).stringify(indent=2) + "\n"
}