///|
/// The pipeline: one pass over a parsed module that produces everything the
/// reports need.
///
/// The section table, the function sizes and the call graph all read the same
/// bytes, and the graph has to decode the bodies anyway, so they are gathered
/// together rather than re-parsed per report.
///|
/// A parsed module with its sizes, call graph and reachability resolved.
pub struct Analysis {
/// The bytes the module was parsed from. Reports that measure a piece of the
/// file on its own — the compression view — need the actual bytes, not just
/// where they are.
data : Bytes
parsed : WasmModule
stats : Array[FunctionStat]
graph : CallGraph
/// Where reachability starts: exports, the start function, table contents.
roots : Set[Int]
/// Everything the roots can reach.
reachable : Set[Int]
/// What deleting each reachable function would free.
retained : Array[RetainedSize]
/// Functions a `call_indirect` could reach.
indirect : Set[Int]
}
///|
/// Analyse a module: names and sizes, then the call graph and what it implies.
pub fn analyze(
data : Bytes,
parsed : WasmModule,
) -> Result[Analysis, WasmError] {
let sections = find_sections(parsed)
let imported = match sections.imports {
Some(section) =>
match count_imported_functions(data, section) {
Ok(count) => count
Err(error) => return Err(error)
}
None => 0
}
let bodies = match sections.code {
Some(section) =>
match parse_code_section(data, section, base_index=imported) {
Ok(bodies) => bodies
Err(error) => return Err(error)
}
None => []
}
let names = match sections.names {
Some(section) => parse_name_section(data, section)
None => None
}
let stats = function_stats(bodies, names)
let elements = match sections.elements {
Some(section) =>
match parse_element_section(data, section) {
Ok(segments) => segments
Err(error) => return Err(error)
}
None => []
}
let exports = match sections.exports {
Some(section) =>
match parse_export_section(data, section) {
Ok(exports) => exports
Err(error) => return Err(error)
}
None => []
}
let start = match sections.start {
Some(section) =>
match parse_start_section(data, section) {
Ok(index) => Some(index)
Err(error) => return Err(error)
}
None => None
}
let graph = match build(data, bodies, elements) {
Ok(graph) => graph
Err(error) => return Err(error)
}
let roots = roots_of(exports, elements, start)
let reachable = reachable_from_roots(graph, roots)
let sizes : Map[Int, Int] = Map([])
for stat in stats {
sizes.set(stat.index, stat.total_size)
}
Ok({
data,
parsed,
stats,
graph,
roots,
reachable,
retained: retained_sizes(graph, sizes, roots),
indirect: indirect_targets(graph),
})
}