///|
/// Half-open original-file range; not a memory address.
pub struct FunctionInstructionRange {
file_offset : Int
size : Int
}
///|
pub struct FunctionInstructionRecord {
ordinal : Int
mnemonic : String
file_offset : Int
body_relative_offset : Int
encoded_size : Int
}
///|
pub struct FunctionOpcodeGroup {
mnemonic : String
count : Int
encoded_bytes : Int
}
///|
pub struct FunctionInstructionsReport {
file_size : Int
sha256 : String
index : Int
name : String
raw_name : String?
entry : FunctionInstructionRange
prefix : FunctionInstructionRange
body : FunctionInstructionRange
locals_encoding : FunctionInstructionRange
instruction_bytes : Int
instruction_count : Int
groups : Array[FunctionOpcodeGroup]
instructions : Array[FunctionInstructionRecord]
scope : String
}
///|
pub enum FunctionInstructionsError {
InstructionsInputError(WasmError)
InvalidInstructionsTarget(Int, Int)
ImportedInstructionsTarget(Int)
InvalidInstructionsRange(Int, String)
}
///|
pub fn describe_function_instructions_error(
error : FunctionInstructionsError,
) -> String {
match error {
InstructionsInputError(error) => describe_error(error)
InvalidInstructionsTarget(index, count) =>
"target function index \{index} is outside the function index space of \{count}"
ImportedInstructionsTarget(index) =>
"target function \{index} is imported and has no local body"
InvalidInstructionsRange(offset, reason) =>
"invalid function instruction range at file offset \{offset}: \{reason}"
}
}
///|
/// Decode only the selected local body, once. Other bodies are range-scanned.
pub fn inspect_function_instructions(
data : Bytes,
index : Int,
) -> Result[FunctionInstructionsReport, FunctionInstructionsError] {
let parsed = match parse_module(data) {
Ok(value) => value
Err(error) => return Err(InstructionsInputError(error))
}
let sections = find_sections(parsed)
let imported = match sections.imports {
None => 0
Some(section) =>
match count_imported_functions(data, section) {
Ok(value) => value
Err(error) => return Err(InstructionsInputError(error))
}
}
let stats = match collect_function_stats(parsed, data) {
Ok(value) => value
Err(error) => return Err(InstructionsInputError(error))
}
let count = imported + stats.length()
if index < 0 || index >= count {
return Err(InvalidInstructionsTarget(index, count))
}
if index < imported {
return Err(ImportedInstructionsTarget(index))
}
let stat = stats[index - imported]
let reader = match Reader::new_range(data, stat.offset, stat.total_size) {
Ok(value) => value
Err(error) => return Err(InstructionsInputError(error))
}
if stat.body_size < 0 || stat.body_size > stat.total_size {
return Err(InvalidInstructionsRange(stat.offset, "body exceeds entry"))
}
let prefix_size = stat.total_size - stat.body_size
match reader.read_bytes(prefix_size) {
Ok(_) => ()
Err(error) => return Err(InstructionsInputError(error))
}
let body_offset = reader.position()
// The bounded entry validates the entire body before offset arithmetic.
match reader.read_bytes(stat.body_size) {
Ok(_) => ()
Err(error) => return Err(InstructionsInputError(error))
}
let decoded = match decode_instructions(data, body_offset, stat.body_size) {
Ok(value) => value
Err(error) => return Err(InstructionsInputError(error))
}
let body_end = reader.position()
let first = if decoded.is_empty() { body_end } else { decoded[0].offset }
if first < body_offset || first > body_end {
return Err(InvalidInstructionsRange(first, "locals boundary outside body"))
}
let records : Array[FunctionInstructionRecord] = []
let by_name : Map[String, FunctionOpcodeGroup] = Map([])
let mut cursor = first
let mut instruction_bytes = 0
for ordinal, instruction in decoded {
if instruction.offset != cursor ||
instruction.size <= 0 ||
instruction.size > body_end - cursor {
return Err(
InvalidInstructionsRange(
instruction.offset,
"noncontiguous or out-of-bounds instruction",
),
)
}
let mnemonic = instruction.opcode.name()
records.push({
ordinal,
mnemonic,
file_offset: cursor,
body_relative_offset: cursor - body_offset,
encoded_size: instruction.size,
})
let group = by_name
.get(mnemonic)
.unwrap_or({ mnemonic, count: 0, encoded_bytes: 0, })
by_name.set(mnemonic, {
mnemonic,
count: group.count + 1,
encoded_bytes: group.encoded_bytes + instruction.size,
})
cursor += instruction.size
instruction_bytes += instruction.size
}
let groups : Array[FunctionOpcodeGroup] = []
let mut group_bytes = 0
let mut group_count = 0
for _, group in by_name {
groups.push(group)
group_bytes += group.encoded_bytes
group_count += group.count
}
let locals_size = first - body_offset
if cursor != body_end ||
locals_size + instruction_bytes != stat.body_size ||
group_bytes != instruction_bytes ||
group_count != records.length() {
return Err(
InvalidInstructionsRange(cursor, "instruction byte ledger does 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),
index,
name: stat.name,
raw_name: stat.raw_name,
entry: { file_offset: stat.offset, size: stat.total_size, },
prefix: { file_offset: stat.offset, size: prefix_size, },
body: { file_offset: body_offset, size: stat.body_size, },
locals_encoding: { file_offset: body_offset, size: locals_size, },
instruction_bytes,
instruction_count: records.length(),
groups,
instructions: records,
scope: "Static encoding of one local function: locals declarations and instructions, excluding the body-length prefix from instruction bytes. File offsets are not memory addresses. Counts are not execution frequencies or timings; bytes do not predict deletion, recompilation or gzip savings. Existing decoder support only; no complete type, control, stack or instantiation validation. Other bodies are not instruction-decoded. Top limits text only, not analysis cost.",
})
}