///|
fn[T : Encode] compiler_fact_encoded_length(
value : T,
) -> Result[Int, BinaryEncodeError] {
let buf = @buffer.new()
match Encode::encode(value, buf) {
Ok(_) => Ok(buf.to_bytes().length())
Err(err) => Err(err)
}
}
///|
fn compiler_fact_collect_opcode_offsets(
instructions : Array[Instruction],
offsets : Array[UInt],
cursor : Ref[Int],
) -> Result[Unit, BinaryEncodeError] {
for instruction in instructions {
offsets.push(cursor.val.reinterpret_as_uint())
match instruction {
Block(block_type, Expr(body)) => {
let encoded = match
compiler_fact_encoded_length(
Instruction::block(block_type, Expr::new([])),
) {
Ok(length) => length
Err(err) => return Err(err)
}
cursor.val += encoded - 1
if compiler_fact_collect_opcode_offsets(body, offsets, cursor)
is Err(err) {
return Err(err)
}
cursor.val += 1
}
Loop(block_type, Expr(body)) => {
let encoded = match
compiler_fact_encoded_length(
Instruction::loop_(block_type, Expr::new([])),
) {
Ok(length) => length
Err(err) => return Err(err)
}
cursor.val += encoded - 1
if compiler_fact_collect_opcode_offsets(body, offsets, cursor)
is Err(err) {
return Err(err)
}
cursor.val += 1
}
If(block_type, then_body, else_body) => {
let encoded = match
compiler_fact_encoded_length(Instruction::if_(block_type, [], None)) {
Ok(length) => length
Err(err) => return Err(err)
}
cursor.val += encoded - 1
if compiler_fact_collect_opcode_offsets(then_body, offsets, cursor)
is Err(err) {
return Err(err)
}
match else_body {
Some(body) => {
cursor.val += 1
if compiler_fact_collect_opcode_offsets(body, offsets, cursor)
is Err(err) {
return Err(err)
}
}
None => ()
}
cursor.val += 1
}
Try(block_type, Expr(body), catches, delegate) => {
if (catches.is_empty() && delegate is None) ||
(!catches.is_empty() && delegate is Some(_)) {
return Err(BinaryEncodeError::InvalidLegacyTryShape)
}
let encoded = match
compiler_fact_encoded_length(
Instruction::block(block_type, Expr::new([])),
) {
Ok(length) => length
Err(err) => return Err(err)
}
cursor.val += encoded - 1
if compiler_fact_collect_opcode_offsets(body, offsets, cursor)
is Err(err) {
return Err(err)
}
match delegate {
Some(label) => {
cursor.val += 1
let label_length = match compiler_fact_encoded_length(label) {
Ok(length) => length
Err(err) => return Err(err)
}
cursor.val += label_length
}
None => {
for catch_ in catches {
match catch_ {
@lib.LegacyCatch(tag, Expr(catch_body)) => {
cursor.val += 1
let tag_length = match compiler_fact_encoded_length(tag) {
Ok(length) => length
Err(err) => return Err(err)
}
cursor.val += tag_length
if compiler_fact_collect_opcode_offsets(
catch_body, offsets, cursor,
)
is Err(err) {
return Err(err)
}
}
@lib.LegacyCatchAll(Expr(catch_body)) => {
cursor.val += 1
if compiler_fact_collect_opcode_offsets(
catch_body, offsets, cursor,
)
is Err(err) {
return Err(err)
}
}
}
}
cursor.val += 1
}
}
}
TryTable(block_type, catches, Expr(body)) => {
let encoded = match
compiler_fact_encoded_length(
Instruction::try_table(block_type, catches, Expr::new([])),
) {
Ok(length) => length
Err(err) => return Err(err)
}
cursor.val += encoded - 1
if compiler_fact_collect_opcode_offsets(body, offsets, cursor)
is Err(err) {
return Err(err)
}
cursor.val += 1
}
_ => {
let encoded = match compiler_fact_encoded_length(instruction) {
Ok(length) => length
Err(err) => return Err(err)
}
cursor.val += encoded
}
}
}
Ok(())
}
///|
/// Return the canonical encoded byte offsets of every instruction opcode in an
/// expression. Structural delimiter bytes such as `else`, `catch`, and `end`
/// are not instruction sites.
pub fn compiler_fact_opcode_offsets(
expression : Expr,
stringrefs? : Array[Bytes]? = None,
) -> Result[Array[UInt], BinaryEncodeError] {
with_binary_encode_stringrefs_context(stringrefs, fn() {
let Expr(instructions) = expression
let offsets : Array[UInt] = []
let cursor = Ref::new(0)
match compiler_fact_collect_opcode_offsets(instructions, offsets, cursor) {
Ok(_) => Ok(offsets)
Err(err) => Err(err)
}
})
}
///|
fn compiler_fact_collect_instructions(
instructions : Array[Instruction],
out : Array[Instruction],
) -> Unit {
for instruction in instructions {
out.push(instruction)
match instruction {
Block(_, Expr(body)) | Loop(_, Expr(body)) | TryTable(_, _, Expr(body)) =>
compiler_fact_collect_instructions(body, out)
If(_, then_body, else_body) => {
compiler_fact_collect_instructions(then_body, out)
match else_body {
Some(body) => compiler_fact_collect_instructions(body, out)
None => ()
}
}
Try(_, Expr(body), catches, _) => {
compiler_fact_collect_instructions(body, out)
for catch_ in catches {
match catch_ {
@lib.LegacyCatch(_, Expr(catch_body))
| @lib.LegacyCatchAll(Expr(catch_body)) =>
compiler_fact_collect_instructions(catch_body, out)
}
}
}
_ => ()
}
}
}
///|
/// Pair canonical opcode offsets with the exact source instructions in preorder.
/// Delimiters remain excluded, and offsets come only from the production encoder.
pub fn compiler_fact_opcode_sites(
expression : Expr,
stringrefs? : Array[Bytes]? = None,
) -> Result[Array[(UInt, Instruction)], BinaryEncodeError] {
let offsets = match compiler_fact_opcode_offsets(expression, stringrefs~) {
Ok(offsets) => offsets
Err(err) => return Err(err)
}
let Expr(instructions) = expression
let flattened : Array[Instruction] = []
compiler_fact_collect_instructions(instructions, flattened)
if offsets.length() != flattened.length() {
return Err(BinaryEncodeError::InvalidLegacyTryShape)
}
let sites : Array[(UInt, Instruction)] = []
for i = 0; i < offsets.length(); i = i + 1 {
sites.push((offsets[i], flattened[i]))
}
Ok(sites)
}
///|
/// Return canonical instruction-opcode offsets for every defined function body
/// using the same synthesized string-reference pool as module encoding.
pub fn compiler_fact_module_opcode_offsets(
module_ : Module,
) -> Result[Array[Array[UInt]], BinaryEncodeError] {
let string_pool = encode_module_stringrefs_pool(module_)
let stringrefs = string_pool.values
let context = if stringrefs.is_empty() { None } else { Some(stringrefs) }
with_binary_encode_stringrefs_context(
context,
fn() {
let functions = match module_.code_sec {
Some(CodeSec(functions)) => functions
None => []
}
let indexed : Array[Array[UInt]] = []
for function in functions {
let Func(_, Expr(instructions)) = function
let offsets : Array[UInt] = []
let cursor = Ref::new(0)
match
compiler_fact_collect_opcode_offsets(instructions, offsets, cursor) {
Ok(_) => indexed.push(offsets)
Err(err) => return Err(err)
}
}
Ok(indexed)
},
known_index=string_pool.seen,
)
}
///|
/// Return canonical `(offset, instruction)` maps for every defined function.
pub fn compiler_fact_module_opcode_sites(
module_ : Module,
) -> Result[Array[Array[(UInt, Instruction)]], BinaryEncodeError] {
let stringrefs = encode_module_stringrefs(module_)
let context = if stringrefs.is_empty() { None } else { Some(stringrefs) }
let functions = match module_.code_sec {
Some(CodeSec(functions)) => functions
None => []
}
let indexed : Array[Array[(UInt, Instruction)]] = []
for function in functions {
let Func(_, expression) = function
match compiler_fact_opcode_sites(expression, stringrefs=context) {
Ok(sites) => indexed.push(sites)
Err(err) => return Err(err)
}
}
Ok(indexed)
}