///|
/// Complete instruction encoding bytes include opcode, subopcode and immediates.
pub struct ModuleOpcodeGroup {
mnemonic : String
count : Int
encoded_bytes : Int
function_count : Int
}
///|
/// Compact local-function facts, without instructions or an opcode matrix.
pub struct ModuleOpcodeFunction {
stat : FunctionStat
locals_encoding : FunctionInstructionRange
instruction_count : Int
instruction_bytes : Int
}
///|
pub struct ModuleOpcodeReport {
file_size : Int
sha256 : String
code_section : Section?
count_encoding : FunctionInstructionRange?
imported_function_count : Int
local_function_count : Int
code_header_bytes : Int
count_encoding_bytes : Int
body_prefix_bytes : Int
locals_encoding_bytes : Int
instruction_bytes : Int
instruction_count : Int
groups : Array[ModuleOpcodeGroup]
functions : Array[ModuleOpcodeFunction]
scope : String
}
///|
pub enum ModuleOpcodeError {
ModuleOpcodeInputError(WasmError)
ModuleOpcodeBodyError(Int, WasmError)
InvalidModuleOpcodeEvidence(Int?, Int?, String)
}
///|
pub fn describe_module_opcode_error(error : ModuleOpcodeError) -> String {
match error {
ModuleOpcodeInputError(error) => describe_error(error)
ModuleOpcodeBodyError(index, error) =>
"function \{index}: " + describe_error(error)
InvalidModuleOpcodeEvidence(index, offset, reason) =>
"invalid module opcode evidence" +
(match index {
Some(index) => " for function \{index}"
None => ""
}) +
(match offset {
Some(offset) => " at file offset \{offset}"
None => ""
}) +
": " +
reason
}
}
///|
/// The decoded array and per-function set do not escape this helper. This makes
/// no promise about the runtime's exact garbage-collection/release timing.
fn module_opcode_function(
data : Bytes,
stat : FunctionStat,
totals : Map[String, ModuleOpcodeGroup],
) -> Result[ModuleOpcodeFunction, ModuleOpcodeError] {
if stat.body_size < 0 || stat.body_size >= stat.total_size {
return Err(
InvalidModuleOpcodeEvidence(
Some(stat.index),
Some(stat.offset),
"body/prefix range differs",
),
)
}
let reader = match Reader::new_range(data, stat.offset, stat.total_size) {
Ok(reader) => reader
Err(error) => return Err(ModuleOpcodeBodyError(stat.index, error))
}
match reader.read_bytes(stat.total_size - stat.body_size) {
Ok(_) => ()
Err(error) => return Err(ModuleOpcodeBodyError(stat.index, error))
}
let body_offset = reader.position()
match reader.read_bytes(stat.body_size) {
Ok(_) => ()
Err(error) => return Err(ModuleOpcodeBodyError(stat.index, error))
}
let body_end = reader.position()
let decoded = match decode_instructions(data, body_offset, stat.body_size) {
Ok(decoded) => decoded
Err(error) => return Err(ModuleOpcodeBodyError(stat.index, error))
}
let first = if decoded.is_empty() { body_end } else { decoded[0].offset }
if first < body_offset || first > body_end {
return Err(
InvalidModuleOpcodeEvidence(
Some(stat.index),
Some(body_offset),
"locals boundary outside body",
),
)
}
let seen : Set[String] = Set([])
let mut cursor = first
let mut instruction_bytes = 0
for instruction in decoded {
if instruction.offset != cursor ||
instruction.size <= 0 ||
instruction.size > body_end - cursor {
return Err(
InvalidModuleOpcodeEvidence(
Some(stat.index),
Some(instruction.offset),
"noncontiguous or out-of-bounds instruction",
),
)
}
let mnemonic = instruction.opcode.name()
let first_in_function = !seen.contains(mnemonic)
seen.add(mnemonic)
let previous = totals
.get(mnemonic)
.unwrap_or({ mnemonic, count: 0, encoded_bytes: 0, function_count: 0, })
totals.set(mnemonic, {
mnemonic,
count: previous.count + 1,
encoded_bytes: previous.encoded_bytes + instruction.size,
function_count: previous.function_count +
(if first_in_function { 1 } else { 0 }),
})
cursor += instruction.size
instruction_bytes += instruction.size
}
if cursor != body_end ||
instruction_bytes != stat.body_size - (first - body_offset) {
return Err(
InvalidModuleOpcodeEvidence(
Some(stat.index),
Some(cursor),
"instruction ledger does not close",
),
)
}
Ok({
stat,
locals_encoding: { file_offset: body_offset, size: first - body_offset, },
instruction_count: decoded.length(),
instruction_bytes,
})
}
///|
/// Collect ranges first, then decode each local body once. No repeated module
/// inspection per function, graph, retained or initializer-expression decoding.
pub fn inspect_module_opcodes(
data : Bytes,
) -> Result[ModuleOpcodeReport, ModuleOpcodeError] {
let parsed = match parse_module(data) {
Ok(parsed) => parsed
Err(error) => return Err(ModuleOpcodeInputError(error))
}
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(ModuleOpcodeInputError(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(ModuleOpcodeInputError(error))
}
None => []
}
let names = match sections.names {
Some(section) => parse_name_section(data, section)
None => None
}
let stats = function_stats(bodies, names)
let totals : Map[String, ModuleOpcodeGroup] = Map([])
let functions : Array[ModuleOpcodeFunction] = []
let mut count_encoding : FunctionInstructionRange? = None
let mut code_header_bytes = 0
let mut count_encoding_bytes = 0
let mut body_prefix_bytes = 0
let mut locals_encoding_bytes = 0
let mut instruction_bytes = 0
let mut instruction_count = 0
let mut entry_bytes = 0
if sections.code is Some(code) {
let reader = match reader_at_payload(data, code) {
Ok(reader) => reader
Err(error) => return Err(ModuleOpcodeInputError(error))
}
let count_offset = reader.position()
let count = match read_u32_as_int(reader) {
Ok(count) => count
Err(error) => return Err(ModuleOpcodeInputError(error))
}
code_header_bytes = code.total_size - code.payload_size
count_encoding_bytes = reader.position() - count_offset
count_encoding = Some({
file_offset: count_offset,
size: count_encoding_bytes,
})
if count != stats.length() {
return Err(
InvalidModuleOpcodeEvidence(
None,
Some(count_offset),
"function count differs from collected ranges",
),
)
}
for ordinal, stat in stats {
// parse_code_section already checked full-index addition and entry ranges.
if stat.index != imported + ordinal || stat.offset != reader.position() {
return Err(
InvalidModuleOpcodeEvidence(
Some(stat.index),
Some(stat.offset),
"function entries are not contiguous in index/file order",
),
)
}
match reader.read_bytes(stat.total_size) {
Ok(_) => ()
Err(error) => return Err(ModuleOpcodeBodyError(stat.index, error))
}
let summary = match module_opcode_function(data, stat, totals) {
Ok(summary) => summary
Err(error) => return Err(error)
}
functions.push(summary)
entry_bytes += stat.total_size
body_prefix_bytes += stat.total_size - stat.body_size
locals_encoding_bytes += summary.locals_encoding.size
instruction_bytes += summary.instruction_bytes
instruction_count += summary.instruction_count
}
if !reader.at_end() ||
entry_bytes !=
body_prefix_bytes + locals_encoding_bytes + instruction_bytes ||
code.total_size != code_header_bytes + count_encoding_bytes + entry_bytes {
return Err(
InvalidModuleOpcodeEvidence(
None,
Some(reader.position()),
"code section byte ledger does not close",
),
)
}
}
let groups : Array[ModuleOpcodeGroup] = []
let mut grouped_bytes = 0
let mut grouped_count = 0
for _, group in totals {
if group.function_count <= 0 ||
group.function_count > stats.length() ||
group.function_count > group.count {
return Err(
InvalidModuleOpcodeEvidence(
None,
None,
"opcode function count is inconsistent",
),
)
}
grouped_bytes += group.encoded_bytes
grouped_count += group.count
groups.push(group)
}
if grouped_bytes != instruction_bytes || grouped_count != instruction_count {
return Err(
InvalidModuleOpcodeEvidence(
None,
None,
"opcode/function aggregates do not close",
),
)
}
groups.sort_by(fn(a, b) {
if a.encoded_bytes != b.encoded_bytes {
b.encoded_bytes.compare(a.encoded_bytes)
} else if a.count != b.count {
b.count.compare(a.count)
} else {
a.mnemonic.lexical_compare(b.mnemonic)
}
})
Ok({
file_size: data.length(),
sha256: experiment_sha256(data),
code_section: sections.code,
count_encoding,
imported_function_count: imported,
local_function_count: stats.length(),
code_header_bytes,
count_encoding_bytes,
body_prefix_bytes,
locals_encoding_bytes,
instruction_bytes,
instruction_count,
groups,
functions,
scope: "Static encodings in all local function bodies, without reachability filtering. Imports have no local body or synthetic zero-byte row. Global, element and data initializer expressions are excluded. Each local body is decoded once after collecting checked ranges/names; no per-function reparse of the module, analyze, graph, retained, calls or local-accesses report. Any unsupported/unknown/truncated body fails the entire report with its full function index and original decoder error; no partial success. Complete encoding bytes include opcode, extension subopcode and immediates; counts are not execution frequency or runtime hotspots. Distinct function counts are deduplicated per mnemonic per body; summing them does not give the local-function total. Only opcode aggregates and compact function summaries are retained, not instructions, occurrence lists or an opcode-by-function matrix. Runtime memory release timing is not guaranteed. Missing code differs from an explicit zero-function section; zero-instruction structural results do not validate function termination or full type/stack/control semantics. File offsets are not memory addresses; encoded bytes do not predict runtime memory, deletion/recompilation or gzip/transfer shares. Top limits text opcode rows only, not totals, complete JSON, all-body decoding cost or resource usage.",
})
}