///|
pub suberror WiteError {
UnexpectedEof
InvalidMagic
UnsupportedVersion(String)
InvalidFormat(String)
}
///|
fn wite_error_to_string(err : WiteError) -> String {
match err {
WiteError::UnexpectedEof => "unexpected eof"
WiteError::InvalidMagic => "invalid wasm magic"
WiteError::UnsupportedVersion(msg) => "unsupported version: " + msg
WiteError::InvalidFormat(msg) => "invalid format: " + msg
}
}
///|
pub fn error_to_string(err : WiteError) -> String {
wite_error_to_string(err)
}
///|
pub struct SectionSize {
key : String
section_id : UInt
total_bytes : UInt
}
///|
pub struct OptimizeConfig {
strip_all_custom : Bool
strip_name_section : Bool
strip_producers_section : Bool
strip_debug_sections : Bool
strip_dwarf_sections : Bool
strip_target_features_section : Bool
keep_custom_sections : Array[String]
pass_rounds : UInt
enable_peephole : Bool
peephole_remove_nop : Bool
peephole_remove_const_drop : Bool
enable_vacuum : Bool
enable_merge_blocks : Bool
enable_remove_unused_brs : Bool
enable_dce : Bool
enable_dfe : Bool
enable_merge_similar_functions : Bool
enable_remove_unused_module_elements : Bool
closed_world : Bool
closed_world_root_exports : Array[String]
safe_mode : Bool
}
///|
pub fn OptimizeConfig::default() -> Self {
{
strip_all_custom: false,
strip_name_section: true,
strip_producers_section: true,
strip_debug_sections: false,
strip_dwarf_sections: false,
strip_target_features_section: false,
keep_custom_sections: [],
pass_rounds: 1U,
enable_peephole: true,
peephole_remove_nop: true,
peephole_remove_const_drop: true,
enable_vacuum: true,
enable_merge_blocks: true,
enable_remove_unused_brs: true,
enable_dce: false,
enable_dfe: false,
enable_merge_similar_functions: false,
enable_remove_unused_module_elements: false,
closed_world: false,
closed_world_root_exports: [],
safe_mode: false,
}
}
///|
pub fn OptimizeConfig::aggressive() -> Self {
{
strip_all_custom: true,
strip_name_section: true,
strip_producers_section: true,
strip_debug_sections: true,
strip_dwarf_sections: true,
strip_target_features_section: true,
keep_custom_sections: [],
pass_rounds: 1U,
enable_peephole: true,
peephole_remove_nop: true,
peephole_remove_const_drop: true,
enable_vacuum: true,
enable_merge_blocks: true,
enable_remove_unused_brs: true,
enable_dce: false,
enable_dfe: false,
enable_merge_similar_functions: false,
enable_remove_unused_module_elements: false,
closed_world: false,
closed_world_root_exports: [],
safe_mode: false,
}
}
///|
pub fn OptimizeConfig::o0() -> Self {
make_optimize_config(
strip_all_custom=false,
strip_name_section=false,
strip_producers_section=false,
strip_debug_sections=false,
strip_dwarf_sections=false,
strip_target_features_section=false,
keep_custom_sections=[],
pass_rounds=1U,
enable_peephole=false,
peephole_remove_nop=false,
peephole_remove_const_drop=false,
enable_vacuum=false,
enable_merge_blocks=false,
enable_remove_unused_brs=false,
enable_dce=false,
enable_dfe=false,
enable_merge_similar_functions=false,
)
}
///|
pub fn OptimizeConfig::o1() -> Self {
make_optimize_config(
strip_all_custom=false,
strip_name_section=false,
strip_producers_section=false,
strip_debug_sections=true,
strip_dwarf_sections=true,
strip_target_features_section=true,
keep_custom_sections=[],
pass_rounds=2U,
enable_peephole=true,
peephole_remove_nop=true,
peephole_remove_const_drop=true,
enable_vacuum=true,
enable_merge_blocks=true,
enable_remove_unused_brs=true,
enable_dce=true,
enable_dfe=true,
enable_merge_similar_functions=true,
enable_remove_unused_module_elements=true,
)
}
///|
pub fn OptimizeConfig::o2() -> Self {
make_optimize_config(
strip_all_custom=false,
strip_name_section=false,
strip_producers_section=false,
strip_debug_sections=false,
strip_dwarf_sections=false,
strip_target_features_section=false,
keep_custom_sections=[],
pass_rounds=2U,
enable_peephole=true,
peephole_remove_nop=true,
peephole_remove_const_drop=true,
enable_vacuum=true,
enable_merge_blocks=true,
enable_remove_unused_brs=true,
enable_dce=true,
enable_dfe=false,
enable_merge_similar_functions=false,
)
}
///|
pub fn OptimizeConfig::o3() -> Self {
make_optimize_config(
strip_all_custom=false,
strip_name_section=false,
strip_producers_section=false,
strip_debug_sections=false,
strip_dwarf_sections=false,
strip_target_features_section=false,
keep_custom_sections=[],
pass_rounds=3U,
enable_peephole=true,
peephole_remove_nop=true,
peephole_remove_const_drop=true,
enable_vacuum=true,
enable_merge_blocks=true,
enable_remove_unused_brs=true,
enable_dce=true,
enable_dfe=true,
enable_merge_similar_functions=true,
enable_remove_unused_module_elements=true,
)
}
///|
pub fn OptimizeConfig::os() -> Self {
make_optimize_config(
strip_all_custom=false,
strip_name_section=false,
strip_producers_section=false,
strip_debug_sections=false,
strip_dwarf_sections=false,
strip_target_features_section=false,
keep_custom_sections=[],
pass_rounds=3U,
enable_peephole=true,
peephole_remove_nop=true,
peephole_remove_const_drop=true,
enable_vacuum=true,
enable_merge_blocks=true,
enable_remove_unused_brs=true,
enable_dce=true,
enable_dfe=true,
enable_merge_similar_functions=false,
enable_remove_unused_module_elements=true,
)
}
///|
pub fn OptimizeConfig::oz() -> Self {
make_optimize_config(
strip_all_custom=false,
strip_name_section=false,
strip_producers_section=false,
strip_debug_sections=false,
strip_dwarf_sections=false,
strip_target_features_section=false,
keep_custom_sections=[],
pass_rounds=8U,
enable_peephole=true,
peephole_remove_nop=true,
peephole_remove_const_drop=true,
enable_vacuum=true,
enable_merge_blocks=true,
enable_remove_unused_brs=true,
enable_dce=true,
enable_dfe=true,
enable_merge_similar_functions=true,
enable_remove_unused_module_elements=true,
)
}
///|
pub fn optimize_config_from_opt_level(level : String) -> OptimizeConfig? {
match level {
"-O0" => Some(OptimizeConfig::o0())
"-O1" => Some(OptimizeConfig::o1())
"-O" | "-O2" => Some(OptimizeConfig::o2())
"-O3" | "-O4" => Some(OptimizeConfig::o3())
"-Os" => Some(OptimizeConfig::os())
"-Oz" => Some(OptimizeConfig::oz())
_ => None
}
}
///|
pub fn make_optimize_config(
strip_all_custom? : Bool = false,
strip_name_section? : Bool = true,
strip_producers_section? : Bool = true,
strip_debug_sections? : Bool = false,
strip_dwarf_sections? : Bool = false,
strip_target_features_section? : Bool = false,
keep_custom_sections? : Array[String] = [],
pass_rounds? : UInt = 1U,
enable_peephole? : Bool = true,
peephole_remove_nop? : Bool = true,
peephole_remove_const_drop? : Bool = true,
enable_vacuum? : Bool = true,
enable_merge_blocks? : Bool = true,
enable_remove_unused_brs? : Bool = true,
enable_dce? : Bool = false,
enable_dfe? : Bool = false,
enable_merge_similar_functions? : Bool = false,
enable_remove_unused_module_elements? : Bool = false,
closed_world? : Bool = false,
closed_world_root_exports? : Array[String] = [],
safe_mode? : Bool = false,
) -> OptimizeConfig {
{
strip_all_custom,
strip_name_section,
strip_producers_section,
strip_debug_sections,
strip_dwarf_sections,
strip_target_features_section,
keep_custom_sections,
pass_rounds,
enable_peephole,
peephole_remove_nop,
peephole_remove_const_drop,
enable_vacuum,
enable_merge_blocks,
enable_remove_unused_brs,
enable_dce,
enable_dfe,
enable_merge_similar_functions,
enable_remove_unused_module_elements,
closed_world,
closed_world_root_exports,
safe_mode,
}
}
///|
pub struct OptimizeResult {
bytes : Bytes
before_size : UInt
after_size : UInt
removed_sections : Array[String]
no_change_reasons : Array[String]
}
///|
pub struct ModuleProfile {
total_bytes : UInt
function_count : UInt
import_count : UInt
export_count : UInt
code_body_count : UInt
code_body_bytes : UInt
sections : Array[SectionSize]
}
///|
pub struct FunctionSize {
function_index : UInt
body_bytes : UInt
name : String?
export_names : Array[String]
}
///|
pub struct FunctionGapEntry {
match_kind : String
match_key : String
left_function_index : UInt?
right_function_index : UInt?
left_name : String?
right_name : String?
left_export_names : Array[String]
right_export_names : Array[String]
left_body_bytes : UInt
right_body_bytes : UInt
left_minus_right_bytes : Int
abs_gap_bytes : UInt
}
///|
pub struct FunctionGapReport {
left_total_body_bytes : UInt
right_total_body_bytes : UInt
left_minus_right_total_bytes : Int
unmatched_left_count : UInt
unmatched_right_count : UInt
entries : Array[FunctionGapEntry]
}
///|
pub struct CodeBlockSize {
function_index : UInt
function_name : String?
export_names : Array[String]
block_index : UInt
depth : UInt
kind : String
start_offset : UInt
end_offset : UInt
instruction_count : UInt
total_bytes : UInt
}
///|
pub struct CodeBlockSizeReport {
partial : Bool
function_count : UInt
block_count : UInt
total_body_bytes : UInt
total_instruction_bytes : UInt
blocks : Array[CodeBlockSize]
}
///|
pub struct CustomSectionBreakdown {
name : String
count : UInt
total_bytes : UInt
payload_bytes : UInt
content_bytes : UInt
}
///|
pub struct OpcodeStat {
key : String
mnemonic : String
count : UInt
total_bytes : UInt
}
///|
pub struct WasmBreakdownReport {
total_bytes : UInt
sections : Array[SectionSize]
custom_sections : Array[CustomSectionBreakdown]
imported_function_count : UInt
local_function_count : UInt
top_functions : Array[FunctionSize]
block_count : UInt
top_blocks : Array[CodeBlockSize]
instruction_count : UInt
instruction_bytes : UInt
opcode_partial : Bool
opcodes : Array[OpcodeStat]
callgraph_partial : Bool
has_indirect_calls : Bool
reachable_body_bytes : UInt
dead_body_bytes : UInt
}
///|
pub struct CallGraphNode {
function_index : UInt
body_bytes : UInt
name : String?
export_names : Array[String]
direct_callees : Array[UInt]
reachable_from_roots : Bool
}
///|
pub struct CallGraphReport {
imported_function_count : UInt
local_function_count : UInt
roots : Array[UInt]
has_indirect_calls : Bool
partial : Bool
reachable_body_bytes : UInt
dead_body_bytes : UInt
nodes : Array[CallGraphNode]
}
///|
pub struct CallGraphSummary {
imported_function_count : UInt
local_function_count : UInt
roots : Array[UInt]
has_indirect_calls : Bool
partial : Bool
reachable_function_count : UInt
dead_function_count : UInt
reachable_body_bytes : UInt
dead_body_bytes : UInt
}
///|
pub struct HostCodeHint {
kind : String
function_index : UInt
function_name : String?
target_function_index : UInt
target_function_name : String?
export_names : Array[String]
appended_i32_const_count : UInt
dropped_param_count : UInt
}
///|
pub struct HostGeneratedCodeReport {
imported_function_count : UInt
local_function_count : UInt
param_forwarding_thunk_count : UInt
const_forwarding_thunk_count : UInt
signature_refinable_thunk_count : UInt
directize_candidate_call_count : UInt
dce_removable_function_count : UInt
dce_removable_body_bytes : UInt
dce_partial : Bool
hints : Array[HostCodeHint]
}
///|
pub struct FunctionSizeDiff {
function_index : UInt
before_body_bytes : UInt
after_body_bytes : UInt
gain_bytes : UInt
regression_bytes : UInt
before_name : String?
after_name : String?
export_names : Array[String]
}
///|
pub struct OptimizeStageMetadata {
stage : String
before_size : UInt
after_size : UInt
gain_bytes : UInt
regression_bytes : UInt
function_gain_bytes : UInt
function_regression_bytes : UInt
function_diffs : Array[FunctionSizeDiff]
removed_sections : Array[String]
no_change_reasons : Array[String]
}
///|
pub struct OptimizeMetadataReport {
before_size : UInt
after_size : UInt
total_gain_bytes : UInt
total_regression_bytes : UInt
stages : Array[OptimizeStageMetadata]
}
///|
pub struct DceReport {
roots : Array[UInt]
partial : Bool
removable_function_count : UInt
removable_body_bytes : UInt
removable_functions : Array[FunctionSize]
}
///|
pub struct ComponentProfile {
total_bytes : UInt
import_count : UInt
export_count : UInt
core_module_count : UInt
nested_component_count : UInt
sections : Array[SectionSize]
core_modules : Array[ModuleProfile]
}
///|
pub struct ComponentFunctionSizeReport {
module_index : UInt
function_count : UInt
total_body_bytes : UInt
functions : Array[FunctionSize]
}
///|
pub struct ComponentCallGraphReport {
module_index : UInt
graph : CallGraphReport
}
///|
pub struct ComponentCoreOptimizeEntry {
module_index : UInt
before_bytes : UInt
after_bytes : UInt
removed_sections : Array[String]
no_change_reasons : Array[String]
}
///|
pub struct ComponentCoreOptimizeReport {
total_component_bytes : UInt
core_module_count : UInt
total_core_before_bytes : UInt
total_core_after_bytes : UInt
entries : Array[ComponentCoreOptimizeEntry]
}
///|
pub struct ContractReport {
component_imports : Array[String]
component_exports : Array[String]
wit_imports : Array[String]
wit_exports : Array[String]
missing_component_imports : Array[String]
missing_component_exports : Array[String]
}
///|
pub struct KeepReasonEntry {
function_index : UInt
name : String?
export_names : Array[String]
reasons : Array[String]
}
///|
pub struct KeepReasonReport {
partial : Bool
entries : Array[KeepReasonEntry]
}
///|
pub struct RetainPathEntry {
function_index : UInt
name : String?
export_names : Array[String]
body_bytes : UInt
root_reasons : Array[String]
path : Array[UInt]
}
///|
pub struct RetainPathReport {
partial : Bool
roots : Array[UInt]
entries : Array[RetainPathEntry]
}
///|
pub struct HotnessSizeEntry {
export_name : String
function_index : UInt?
function_name : String?
body_bytes : UInt
calls : UInt
total_ns : UInt64
average_ns : UInt64
bucket : String
unresolved_reason : String?
unresolved_detail : String?
}
///|
pub struct HotnessSizeBucket {
bucket : String
count : UInt
body_bytes : UInt
total_ns : UInt64
}
///|
pub struct HotnessUnresolvedReasonCount {
reason : String
count : UInt
}
///|
pub struct HotnessSizeMatrixReport {
iterations : UInt
instantiate_ns : UInt64
hot_threshold_ns : UInt64
large_threshold_bytes : UInt
unresolved_exports : Array[String]
unresolved_reason_counts : Array[HotnessUnresolvedReasonCount]
buckets : Array[HotnessSizeBucket]
entries : Array[HotnessSizeEntry]
}
///|
pub struct ComponentRootPolicyReport {
component_imports : Array[String]
component_exports : Array[String]
wit_imports : Array[String]
wit_exports : Array[String]
canonical_abi_roots : Array[String]
root_name_candidates : Array[String]
}
///|
pub struct RuntimeFunctionProfile {
name : String
calls : UInt
total_ns : UInt64
average_ns : UInt64
}
///|
pub struct RuntimeUnresolvedExport {
export_name : String
reason : String
detail : String
}
///|
pub struct RuntimeProfileScenario {
export_name : String
args : Array[Int]
}
///|
pub fn make_runtime_profile_scenario(
export_name : String,
args? : Array[Int] = [],
) -> RuntimeProfileScenario {
{ export_name, args }
}
///|
pub struct RuntimeProfile {
instantiate_ns : UInt64
iterations : UInt
functions : Array[RuntimeFunctionProfile]
skipped_exports : Array[String]
unresolved_exports : Array[RuntimeUnresolvedExport]
}
///|
priv struct Cursor {
bytes : Bytes
mut pos : Int
}
///|
fn Cursor::new(bytes : Bytes) -> Self {
{ bytes, pos: 0 }
}
///|
fn Cursor::eof(self : Cursor) -> Bool {
self.pos >= self.bytes.length()
}
///|
fn Cursor::get_pos(self : Cursor) -> Int {
self.pos
}
///|
fn Cursor::set_pos(self : Cursor, pos : Int) -> Unit {
self.pos = pos
}
///|
fn Cursor::read_byte(self : Cursor) -> Byte raise WiteError {
if self.pos < self.bytes.length() {
let b = self.bytes.unsafe_get(self.pos)
self.pos = self.pos + 1
b
} else {
raise WiteError::UnexpectedEof
}
}
///|
fn Cursor::read_u32_leb128(self : Cursor) -> UInt raise WiteError {
fn read_loop(shift : Int, result : UInt) -> UInt raise WiteError {
let byte = self.read_byte()
if shift >= 28 && (byte & 0x80) != 0 {
raise WiteError::InvalidFormat("LEB128 u32 overflow")
}
if shift >= 28 && byte > 0x0F {
raise WiteError::InvalidFormat("LEB128 u32 value overflow")
}
let value = byte.land(0x7f).to_uint() << shift
let next = result | value
if (byte & 0x80) == 0 {
next
} else {
read_loop(shift + 7, next)
}
}
read_loop(0, 0U)
}
///|
fn Cursor::skip(self : Cursor, n : Int) -> Unit raise WiteError {
if n < 0 {
raise WiteError::InvalidFormat("negative skip")
}
if self.pos + n <= self.bytes.length() {
self.pos = self.pos + n
} else {
raise WiteError::UnexpectedEof
}
}
///|
priv struct RawSection {
section_id : UInt
section_start : Int
payload_start : Int
section_end : Int
custom_name : String?
}
///|
fn ensure_core_header(bytes : Bytes) -> Unit raise WiteError {
if bytes.length() < 8 {
raise WiteError::UnexpectedEof
}
if bytes[0] != 0x00U.to_byte() ||
bytes[1] != 0x61U.to_byte() ||
bytes[2] != 0x73U.to_byte() ||
bytes[3] != 0x6dU.to_byte() {
raise WiteError::InvalidMagic
}
if bytes[4] != 0x01U.to_byte() ||
bytes[5] != 0x00U.to_byte() ||
bytes[6] != 0x00U.to_byte() ||
bytes[7] != 0x00U.to_byte() {
raise WiteError::UnsupportedVersion("expected core wasm version 1")
}
}
///|
fn ensure_component_header(bytes : Bytes) -> Unit raise WiteError {
if bytes.length() < 8 {
raise WiteError::UnexpectedEof
}
if bytes[0] != 0x00U.to_byte() ||
bytes[1] != 0x61U.to_byte() ||
bytes[2] != 0x73U.to_byte() ||
bytes[3] != 0x6dU.to_byte() {
raise WiteError::InvalidMagic
}
if bytes[4] != 0x0dU.to_byte() ||
bytes[5] != 0x00U.to_byte() ||
bytes[6] != 0x01U.to_byte() ||
bytes[7] != 0x00U.to_byte() {
raise WiteError::UnsupportedVersion(
"expected component wasm header 0d 00 01 00",
)
}
}
///|
fn parse_custom_section_name(payload : Bytes) -> String raise WiteError {
let parser = Cursor::new(payload)
let name_len_u = parser.read_u32_leb128()
let name_len = UInt::reinterpret_as_int(name_len_u)
if name_len < 0 {
raise WiteError::InvalidFormat("custom section name length overflow")
}
let start = parser.get_pos()
let end_ = start + name_len
if end_ > payload.length() {
raise WiteError::UnexpectedEof
}
let out : Array[Char] = []
for b in payload[start:end_] {
match b.to_int().to_char() {
Some(ch) => out.push(ch)
None => out.push('?')
}
}
String::from_array(out[:])
}
///|
fn parse_sections_from(
bytes : Bytes,
start : Int,
) -> Array[RawSection] raise WiteError {
let parser = Cursor::new(bytes)
parser.set_pos(start)
let sections : Array[RawSection] = []
while not(parser.eof()) {
let section_start = parser.get_pos()
let section_id = parser.read_byte().to_uint()
let payload_size_u = parser.read_u32_leb128()
let payload_size = UInt::reinterpret_as_int(payload_size_u)
if payload_size < 0 {
raise WiteError::InvalidFormat("section size overflow")
}
let payload_start = parser.get_pos()
let section_end = payload_start + payload_size
if section_end > bytes.length() {
raise WiteError::UnexpectedEof
}
let custom_name = if section_id == 0U {
let payload = bytes[payload_start:section_end].to_bytes()
Some(parse_custom_section_name(payload))
} else {
None
}
sections.push({
section_id,
section_start,
payload_start,
section_end,
custom_name,
})
parser.set_pos(section_end)
}
sections
}
///|
fn parse_core_sections_raise(
bytes : Bytes,
) -> Array[RawSection] raise WiteError {
ensure_core_header(bytes)
parse_sections_from(bytes, 8)
}
///|
fn parse_component_sections_raise(
bytes : Bytes,
) -> Array[RawSection] raise WiteError {
ensure_component_header(bytes)
parse_sections_from(bytes, 8)
}
///|
fn section_total_bytes(section : RawSection) -> UInt {
(section.section_end - section.section_start).reinterpret_as_uint()
}
///|
fn core_section_name(section_id : UInt) -> String {
match section_id {
1U => "type"
2U => "import"
3U => "function"
4U => "table"
5U => "memory"
6U => "global"
7U => "export"
8U => "start"
9U => "element"
10U => "code"
11U => "data"
12U => "data_count"
13U => "tag"
_ => "section-" + section_id.to_string()
}
}
///|
fn component_section_name(section_id : UInt) -> String {
match section_id {
1U => "core"
2U => "core-instance"
3U => "core-type"
4U => "component"
5U => "instance"
6U => "alias"
7U => "type"
8U => "canon"
9U => "start"
10U => "import"
11U => "export"
12U => "value"
_ => "section-" + section_id.to_string()
}
}
///|
fn section_key(section : RawSection, component : Bool) -> String {
if section.section_id == 0U {
match section.custom_name {
Some(name) => "custom:" + name
None => "custom:(anon)"
}
} else if component {
component_section_name(section.section_id)
} else {
core_section_name(section.section_id)
}
}
///|
fn summarize_sections(
sections : Array[RawSection],
component : Bool,
) -> Array[SectionSize] {
let grouped : Map[String, SectionSize] = {}
for section in sections {
let key = section_key(section, component)
let size = section_total_bytes(section)
match grouped.get(key) {
Some(prev) =>
grouped.set(key, {
key,
section_id: prev.section_id,
total_bytes: prev.total_bytes + size,
})
None =>
grouped.set(key, {
key,
section_id: section.section_id,
total_bytes: size,
})
}
}
let out : Array[SectionSize] = []
grouped.each((_, value) => out.push(value))
out.sort_by((a, b) => if a.total_bytes > b.total_bytes {
-1
} else if a.total_bytes < b.total_bytes {
1
} else {
a.key.compare(b.key)
})
out
}
///|
fn parse_vec_count(payload : Bytes) -> UInt raise WiteError {
let parser = Cursor::new(payload)
parser.read_u32_leb128()
}
///|
fn parse_code_section_metrics(payload : Bytes) -> (UInt, UInt) raise WiteError {
let parser = Cursor::new(payload)
let body_count = parser.read_u32_leb128()
let mut total_body_bytes = 0U
for _ in 0U.. String raise WiteError {
let name_len_u = parser.read_u32_leb128()
let name_len = UInt::reinterpret_as_int(name_len_u)
if name_len < 0 {
raise WiteError::InvalidFormat("name length overflow")
}
let start = parser.get_pos()
let end_ = start + name_len
if end_ > parser.bytes.length() {
raise WiteError::UnexpectedEof
}
let out : Array[Char] = []
for b in parser.bytes[start:end_] {
match b.to_int().to_char() {
Some(ch) => out.push(ch)
None => out.push('?')
}
}
parser.set_pos(end_)
String::from_array(out[:])
}
///|
fn skip_name_string(parser : Cursor) -> Unit raise WiteError {
let name_len_u = parser.read_u32_leb128()
let name_len = UInt::reinterpret_as_int(name_len_u)
if name_len < 0 {
raise WiteError::InvalidFormat("name length overflow")
}
parser.skip(name_len)
}
///|
fn bytes_to_name_string(bytes : Bytes) -> String {
let out : Array[Char] = []
for b in bytes {
match b.to_int().to_char() {
Some(ch) => out.push(ch)
None => out.push('?')
}
}
String::from_array(out[:])
}
///|
fn parse_code_body_sizes(payload : Bytes) -> Array[UInt] raise WiteError {
let parser = Cursor::new(payload)
let body_count = parser.read_u32_leb128()
let body_sizes : Array[UInt] = []
for _ in 0U.. Array[Bytes] raise WiteError {
let parser = Cursor::new(payload)
let body_count = parser.read_u32_leb128()
let out : Array[Bytes] = []
for _ in 0U.. payload.length() {
raise WiteError::UnexpectedEof
}
out.push(payload[start:end_].to_bytes())
parser.set_pos(end_)
}
out
}
///|
fn parse_start_function_index(payload : Bytes) -> UInt raise WiteError {
let parser = Cursor::new(payload)
parser.read_u32_leb128()
}
///|
fn parse_limits(parser : Cursor) -> Unit raise WiteError {
let flags = parser.read_u32_leb128()
ignore(parser.read_u32_leb128())
if (flags & 0x01U) != 0U {
ignore(parser.read_u32_leb128())
}
}
///|
fn parse_import_function_count(payload : Bytes) -> UInt raise WiteError {
let parser = Cursor::new(payload)
let count = parser.read_u32_leb128()
let mut function_import_count = 0U
for _ in 0U.. {
function_import_count += 1U
ignore(parser.read_u32_leb128())
}
0x01U => {
skip_heap_type(parser)
parse_limits(parser)
}
0x02U => parse_limits(parser)
0x03U => {
ignore(parser.read_byte())
ignore(parser.read_byte())
}
0x04U => {
ignore(parser.read_byte())
ignore(parser.read_u32_leb128())
}
_ =>
raise WiteError::InvalidFormat(
"unsupported import descriptor kind: " + kind.to_string(),
)
}
}
function_import_count
}
///|
priv struct ImportSectionEntry {
bytes : Bytes
kind : UInt
kind_index : UInt
}
///|
fn parse_import_section_entries_raise(
payload : Bytes,
) -> Array[ImportSectionEntry] raise WiteError {
let parser = Cursor::new(payload)
let count = parser.read_u32_leb128()
let out : Array[ImportSectionEntry] = []
let mut function_import_index = 0U
let mut table_import_index = 0U
let mut memory_import_index = 0U
let mut global_import_index = 0U
let mut tag_import_index = 0U
for _ in 0U.. {
let current = function_import_index
function_import_index += 1U
ignore(parser.read_u32_leb128())
current
}
0x01U => {
let current = table_import_index
table_import_index += 1U
skip_heap_type(parser)
parse_limits(parser)
current
}
0x02U => {
let current = memory_import_index
memory_import_index += 1U
parse_limits(parser)
current
}
0x03U => {
let current = global_import_index
global_import_index += 1U
ignore(parser.read_byte())
ignore(parser.read_byte())
current
}
0x04U => {
let current = tag_import_index
tag_import_index += 1U
ignore(parser.read_byte())
ignore(parser.read_u32_leb128())
current
}
_ =>
raise WiteError::InvalidFormat(
"unsupported import descriptor kind: " + kind.to_string(),
)
}
let end_ = parser.get_pos()
out.push({ bytes: payload[start:end_].to_bytes(), kind, kind_index })
}
out
}
///|
fn rewrite_import_section_payload_for_keep_raise(
payload : Bytes,
keep_table_imports : Array[Bool],
keep_memory_imports : Array[Bool],
keep_global_imports : Array[Bool],
) -> (Bytes, UInt) raise WiteError {
let entries = parse_import_section_entries_raise(payload)
let kept_entries : Array[Bytes] = []
let mut removed_count = 0U
for entry in entries {
let mut keep = true
if entry.kind == 1U {
let index = UInt::reinterpret_as_int(entry.kind_index)
if index < 0 || index >= keep_table_imports.length() {
raise WiteError::InvalidFormat("table import keep index out of range")
}
keep = keep_table_imports[index]
} else if entry.kind == 2U {
let index = UInt::reinterpret_as_int(entry.kind_index)
if index < 0 || index >= keep_memory_imports.length() {
raise WiteError::InvalidFormat("memory import keep index out of range")
}
keep = keep_memory_imports[index]
} else if entry.kind == 3U {
let index = UInt::reinterpret_as_int(entry.kind_index)
if index < 0 || index >= keep_global_imports.length() {
raise WiteError::InvalidFormat("global import keep index out of range")
}
keep = keep_global_imports[index]
}
if keep {
kept_entries.push(entry.bytes)
} else {
removed_count += 1U
}
}
let out : Array[Byte] = []
out.append(encode_u32_leb128(kept_entries.length().reinterpret_as_uint())[:])
for entry in kept_entries {
out.append(entry[:].to_array())
}
(Bytes::from_array(out[:]), removed_count)
}
///|
fn rewrite_import_section_payload_for_keep_kinds_raise(
payload : Bytes,
keep_function_imports : Array[Bool],
keep_table_imports : Array[Bool],
keep_memory_imports : Array[Bool],
keep_global_imports : Array[Bool],
) -> (Bytes, UInt) raise WiteError {
let entries = parse_import_section_entries_raise(payload)
let kept_entries : Array[Bytes] = []
let mut removed_count = 0U
for entry in entries {
let mut keep = true
if entry.kind == 0U {
let index = UInt::reinterpret_as_int(entry.kind_index)
if index < 0 || index >= keep_function_imports.length() {
raise WiteError::InvalidFormat(
"function import keep index out of range",
)
}
keep = keep_function_imports[index]
} else if entry.kind == 1U {
let index = UInt::reinterpret_as_int(entry.kind_index)
if index < 0 || index >= keep_table_imports.length() {
raise WiteError::InvalidFormat("table import keep index out of range")
}
keep = keep_table_imports[index]
} else if entry.kind == 2U {
let index = UInt::reinterpret_as_int(entry.kind_index)
if index < 0 || index >= keep_memory_imports.length() {
raise WiteError::InvalidFormat("memory import keep index out of range")
}
keep = keep_memory_imports[index]
} else if entry.kind == 3U {
let index = UInt::reinterpret_as_int(entry.kind_index)
if index < 0 || index >= keep_global_imports.length() {
raise WiteError::InvalidFormat("global import keep index out of range")
}
keep = keep_global_imports[index]
}
if keep {
kept_entries.push(entry.bytes)
} else {
removed_count += 1U
}
}
let out : Array[Byte] = []
out.append(encode_u32_leb128(kept_entries.length().reinterpret_as_uint())[:])
for entry in kept_entries {
out.append(entry[:].to_array())
}
(Bytes::from_array(out[:]), removed_count)
}
///|
fn parse_import_function_type_indices(
payload : Bytes,
) -> Array[UInt] raise WiteError {
let parser = Cursor::new(payload)
let count = parser.read_u32_leb128()
let out : Array[UInt] = []
for _ in 0U.. out.push(parser.read_u32_leb128())
0x01U => {
skip_heap_type(parser)
parse_limits(parser)
}
0x02U => parse_limits(parser)
0x03U => {
ignore(parser.read_byte())
ignore(parser.read_byte())
}
0x04U => {
ignore(parser.read_byte())
ignore(parser.read_u32_leb128())
}
_ =>
raise WiteError::InvalidFormat(
"unsupported import descriptor kind: " + kind.to_string(),
)
}
}
out
}
///|
fn map_push_string_array(
grouped : Map[UInt, Array[String]],
key : UInt,
value : String,
) -> Unit {
match grouped.get(key) {
Some(prev) => {
let next = prev.copy()
next.push(value)
grouped.set(key, next)
}
None => grouped.set(key, [value])
}
}
///|
fn parse_export_function_names(
payload : Bytes,
) -> Map[UInt, Array[String]] raise WiteError {
let parser = Cursor::new(payload)
let count = parser.read_u32_leb128()
let out : Map[UInt, Array[String]] = {}
for _ in 0U.. Array[UInt] raise WiteError {
parse_export_indices_by_kind(payload, 1U)
}
///|
fn parse_export_memory_indices(payload : Bytes) -> Array[UInt] raise WiteError {
parse_export_indices_by_kind(payload, 2U)
}
///|
fn parse_export_global_indices(payload : Bytes) -> Array[UInt] raise WiteError {
parse_export_indices_by_kind(payload, 3U)
}
///|
fn parse_export_indices_by_kind(
payload : Bytes,
kind_filter : UInt,
) -> Array[UInt] raise WiteError {
let parser = Cursor::new(payload)
let count = parser.read_u32_leb128()
let out : Array[UInt] = []
let seen : Map[UInt, Bool] = {}
for _ in 0U.. Unit {
source.each((index, names) => for name in names {
map_push_string_array(target, index, name)
})
}
///|
fn parse_custom_section_name_prefix_end(payload : Bytes) -> Int raise WiteError {
let parser = Cursor::new(payload)
let name_len_u = parser.read_u32_leb128()
let name_len = UInt::reinterpret_as_int(name_len_u)
if name_len < 0 {
raise WiteError::InvalidFormat("custom section name length overflow")
}
parser.skip(name_len)
parser.get_pos()
}
///|
fn parse_name_section_function_names(
payload : Bytes,
) -> Map[UInt, String] raise WiteError {
let parser = Cursor::new(payload)
parser.set_pos(parse_custom_section_name_prefix_end(payload))
let out : Map[UInt, String] = {}
while not(parser.eof()) {
let subsection_id = parser.read_byte().to_uint()
let subsection_size_u = parser.read_u32_leb128()
let subsection_size = UInt::reinterpret_as_int(subsection_size_u)
if subsection_size < 0 {
raise WiteError::InvalidFormat("name subsection size overflow")
}
let subsection_start = parser.get_pos()
let subsection_end = subsection_start + subsection_size
if subsection_end > payload.length() {
raise WiteError::UnexpectedEof
}
if subsection_id == 1U {
let count = parser.read_u32_leb128()
for _ in 0U.. Array[Byte] {
let out : Array[Byte] = []
let mut current = value
while true {
let low = current & 0x7fU
current = current >> 7
if current == 0U {
out.push(low.to_byte())
break
}
out.push((low | 0x80U).to_byte())
}
out
}
///|
fn encode_nonnegative_i32_leb128(value : UInt) -> Array[Byte] {
let out : Array[Byte] = []
let mut current = value
while true {
let low = current & 0x7fU
current = current >> 7
let sign_set = (low & 0x40U) != 0U
if current == 0U && not(sign_set) {
out.push(low.to_byte())
break
}
out.push((low | 0x80U).to_byte())
}
out
}
///|
fn append_encoded_section(
out : Array[Byte],
section_id : UInt,
payload : Bytes,
) -> Unit {
out.push(section_id.to_byte())
out.append(encode_u32_leb128(payload.length().reinterpret_as_uint())[:])
out.append(payload[:].to_array())
}
///|
fn skip_sleb_immediate(
parser : Cursor,
max_bytes : Int,
) -> Unit raise WiteError {
let mut consumed = 0
while true {
let b = parser.read_byte()
consumed += 1
if consumed > max_bytes {
raise WiteError::InvalidFormat("sleb immediate overflow")
}
if (b & 0x80) == 0 {
break
}
}
}
///|
fn skip_block_type(parser : Cursor) -> Unit raise WiteError {
let first = parser.read_byte()
match first.to_uint() {
0x40U | 0x7fU | 0x7eU | 0x7dU | 0x7cU | 0x7bU | 0x70U | 0x6fU => ()
_ =>
if (first & 0x80) != 0 {
let mut consumed = 1
while true {
let b = parser.read_byte()
consumed += 1
if consumed > 5 {
raise WiteError::InvalidFormat("block type s33 overflow")
}
if (b & 0x80) == 0 {
break
}
}
}
}
}
///|
fn skip_heap_type(parser : Cursor) -> Unit raise WiteError {
let first = parser.read_byte()
match first.to_uint() {
0x70U | 0x6fU => ()
_ =>
if (first & 0x80) != 0 {
let mut consumed = 1
while true {
let b = parser.read_byte()
consumed += 1
if consumed > 5 {
raise WiteError::InvalidFormat("heap type s33 overflow")
}
if (b & 0x80) == 0 {
break
}
}
}
}
}
///|
fn skip_memarg(parser : Cursor) -> Unit raise WiteError {
ignore(parser.read_u32_leb128())
ignore(parser.read_u32_leb128())
}
///|
fn skip_memarg_lane(parser : Cursor) -> Unit raise WiteError {
skip_memarg(parser)
ignore(parser.read_byte())
}
///|
fn skip_fc_prefixed_immediate(parser : Cursor) -> Unit raise WiteError {
let subopcode = parser.read_u32_leb128()
match subopcode {
0U..=7U => ()
8U => {
ignore(parser.read_u32_leb128())
ignore(parser.read_u32_leb128())
}
9U => ignore(parser.read_u32_leb128())
10U => {
ignore(parser.read_u32_leb128())
ignore(parser.read_u32_leb128())
}
11U => ignore(parser.read_u32_leb128())
12U => {
ignore(parser.read_u32_leb128())
ignore(parser.read_u32_leb128())
}
13U => ignore(parser.read_u32_leb128())
14U => {
ignore(parser.read_u32_leb128())
ignore(parser.read_u32_leb128())
}
15U | 16U | 17U => ignore(parser.read_u32_leb128())
_ =>
raise WiteError::InvalidFormat(
"unsupported 0xfc subopcode in parser: " + subopcode.to_string(),
)
}
}
///|
fn skip_fd_prefixed_immediate(parser : Cursor) -> Unit raise WiteError {
let subopcode = parser.read_u32_leb128()
match subopcode {
0x00U..=0x0bU | 0x5cU | 0x5dU => skip_memarg(parser)
0x0cU | 0x0dU => parser.skip(16)
0x15U..=0x22U => ignore(parser.read_byte())
0x54U..=0x5bU => skip_memarg_lane(parser)
0x0eU..=0x14U | 0x23U..=0x53U | 0x5eU..=0xffU => ()
_ =>
raise WiteError::InvalidFormat(
"unsupported 0xfd subopcode in parser: " + subopcode.to_string(),
)
}
}
///|
fn skip_instruction_immediate_raise(
parser : Cursor,
opcode : UInt,
) -> Unit raise WiteError {
match opcode {
0x02U | 0x03U | 0x04U => skip_block_type(parser)
0x0cU
| 0x0dU
| 0x10U
| 0x20U
| 0x21U
| 0x22U
| 0x23U
| 0x24U
| 0x25U
| 0x26U
| 0xd2U => ignore(parser.read_u32_leb128())
0x11U => {
ignore(parser.read_u32_leb128())
ignore(parser.read_u32_leb128())
}
0x0eU => {
let count = parser.read_u32_leb128()
for _ in 0U.. {
let count = parser.read_u32_leb128()
for _ in 0U.. {
ignore(parser.read_u32_leb128())
ignore(parser.read_u32_leb128())
}
0x3fU | 0x40U => ignore(parser.read_byte())
0x41U => skip_sleb_immediate(parser, 5)
0x42U => skip_sleb_immediate(parser, 10)
0x43U => parser.skip(4)
0x44U => parser.skip(8)
0xfcU => skip_fc_prefixed_immediate(parser)
0xfdU => skip_fd_prefixed_immediate(parser)
0xd0U => skip_heap_type(parser)
0x00U | 0x01U | 0x05U | 0x0bU | 0x0fU | 0x1aU | 0x1bU | 0xd1U => ()
0x45U..=0xc4U => ()
_ => raise WiteError::InvalidFormat("unsupported opcode in parser")
}
}
///|
priv struct InstrSpan {
start : Int
end_ : Int
opcode : UInt
}
///|
priv struct OpenCodeBlock {
entry_index : Int
start_span_index : Int
start_offset : Int
}
///|
fn parse_instruction_spans_raise(
instr_bytes : Bytes,
) -> Array[InstrSpan] raise WiteError {
let parser = Cursor::new(instr_bytes)
let spans : Array[InstrSpan] = []
while not(parser.eof()) {
let start = parser.get_pos()
let opcode = parser.read_byte().to_uint()
skip_instruction_immediate_raise(parser, opcode)
let end_ = parser.get_pos()
spans.push({ start, end_, opcode })
}
spans
}
///|
fn parse_instruction_spans(instr_bytes : Bytes) -> Array[InstrSpan]? {
let result = try? parse_instruction_spans_raise(instr_bytes)
match result {
Ok(v) => Some(v)
Err(_) => None
}
}
///|
fn decode_u32_leb128_bytes(bytes : Bytes) -> UInt? {
let parser = Cursor::new(bytes)
let result = try? parser.read_u32_leb128()
match result {
Ok(v) => if parser.eof() { Some(v) } else { None }
Err(_) => None
}
}
///|
fn decode_u32_leb128_range(bytes : Bytes, start : Int, end_ : Int) -> UInt? {
if start < 0 || start >= end_ || end_ > bytes.length() {
return None
}
let parser = Cursor::new(bytes)
parser.set_pos(start)
let result = try? parser.read_u32_leb128()
match result {
Ok(v) => if parser.get_pos() == end_ { Some(v) } else { None }
Err(_) => None
}
}
///|
fn collect_direct_callees(
instr_bytes : Bytes,
spans : Array[InstrSpan],
) -> (Array[UInt], Bool) {
let callees : Array[UInt] = []
let mut has_indirect = false
for span in spans {
if span.opcode == 0x10U {
match decode_span_u32_immediate(instr_bytes, span) {
Some(index) => push_unique_u32(callees, index)
None => ()
}
} else if span.opcode == 0x11U {
has_indirect = true
}
}
(callees, has_indirect)
}
///|
fn collect_direct_callees_from_body_raise(
body : Bytes,
) -> (Array[UInt], Bool) raise WiteError {
let prefix_end = parse_local_decl_prefix_end(body)
let parser = Cursor::new(body)
parser.set_pos(prefix_end)
let callees : Array[UInt] = []
let mut has_indirect = false
while not(parser.eof()) {
let opcode = parser.read_byte().to_uint()
match opcode {
0x10U => push_unique_u32(callees, parser.read_u32_leb128())
0x11U => {
has_indirect = true
ignore(parser.read_u32_leb128())
ignore(parser.read_u32_leb128())
}
_ => skip_instruction_immediate_raise(parser, opcode)
}
}
(callees, has_indirect)
}
///|
fn read_name_bytes(parser : Cursor) -> Bytes raise WiteError {
let name_len_u = parser.read_u32_leb128()
let name_len = UInt::reinterpret_as_int(name_len_u)
if name_len < 0 {
raise WiteError::InvalidFormat("name length overflow")
}
let start = parser.get_pos()
let end_ = start + name_len
if end_ > parser.bytes.length() {
raise WiteError::UnexpectedEof
}
parser.set_pos(end_)
parser.bytes[start:end_].to_bytes()
}
///|
fn append_name_bytes(out : Array[Byte], name : Bytes) -> Unit {
out.append(encode_u32_leb128(name.length().reinterpret_as_uint())[:])
out.append(name[:].to_array())
}
///|
fn remap_function_index_raise(
index : UInt,
index_map : Map[UInt, UInt],
) -> UInt raise WiteError {
match index_map.get(index) {
Some(v) => v
None =>
raise WiteError::InvalidFormat(
"dce remap missing function index: " + index.to_string(),
)
}
}
///|
fn remap_type_index_raise(
index : UInt,
type_index_map : Map[UInt, UInt],
) -> UInt raise WiteError {
match type_index_map.get(index) {
Some(v) => v
None =>
raise WiteError::InvalidFormat(
"type remap missing index: " + index.to_string(),
)
}
}
///|
fn read_val_type_bytes_raise(parser : Cursor) -> Bytes raise WiteError {
let start = parser.get_pos()
let code = parser.read_byte().to_uint()
match code {
0x7fU | 0x7eU | 0x7dU | 0x7cU | 0x7bU | 0x70U | 0x6fU => ()
0x63U | 0x64U => skip_heap_type(parser)
_ =>
raise WiteError::InvalidFormat(
"unsupported value type in global section: " + code.to_string(),
)
}
let end_ = parser.get_pos()
parser.bytes[start:end_].to_bytes()
}
///|
fn read_ref_type_bytes_raise(parser : Cursor) -> Bytes raise WiteError {
let start = parser.get_pos()
let code = parser.read_byte().to_uint()
match code {
0x70U | 0x6fU => ()
0x63U | 0x64U => skip_heap_type(parser)
_ =>
raise WiteError::InvalidFormat(
"unsupported reference type in element section: " + code.to_string(),
)
}
let end_ = parser.get_pos()
parser.bytes[start:end_].to_bytes()
}
///|
fn decode_heap_type_ref_index_bytes(heap_type_bytes : Bytes) -> UInt? {
if heap_type_bytes.length() == 0 {
return None
}
let last = heap_type_bytes[heap_type_bytes.length() - 1].to_uint()
// Signed heaptype immediates with sign bit set are negative builtins.
if (last & 0x40U) != 0U {
return None
}
decode_u32_leb128_bytes(heap_type_bytes)
}
///|
fn decode_ref_type_index_from_type_bytes(type_bytes : Bytes) -> UInt? {
if type_bytes.length() < 2 {
return None
}
let code = type_bytes[0].to_uint()
if code != 0x63U && code != 0x64U {
return None
}
decode_heap_type_ref_index_bytes(type_bytes[1:type_bytes.length()].to_bytes())
}
///|
fn remap_gc_type_index_for_refine(
index : UInt,
gc_type_canonical_map : Map[UInt, UInt],
) -> UInt {
match gc_type_canonical_map.get(index) {
Some(v) => v
None => index
}
}
///|
fn type_bytes_equal_with_gc_canonical_map(
left : Bytes,
right : Bytes,
gc_type_canonical_map : Map[UInt, UInt],
) -> Bool {
if bytes_equal(left, right) {
return true
}
if left.length() == 0 || right.length() == 0 {
return false
}
let left_code = left[0].to_uint()
let right_code = right[0].to_uint()
if left_code != right_code {
return false
}
let left_index = match decode_ref_type_index_from_type_bytes(left) {
Some(v) => v
None => return false
}
let right_index = match decode_ref_type_index_from_type_bytes(right) {
Some(v) => v
None => return false
}
remap_gc_type_index_for_refine(left_index, gc_type_canonical_map) ==
remap_gc_type_index_for_refine(right_index, gc_type_canonical_map)
}
///|
fn read_storage_type_bytes_raise(
parser : Cursor,
context : String,
) -> Bytes raise WiteError {
let start = parser.get_pos()
let code = parser.read_byte().to_uint()
match code {
0x78U | 0x77U => ()
0x7fU | 0x7eU | 0x7dU | 0x7cU | 0x7bU | 0x70U | 0x6fU => ()
0x63U | 0x64U => skip_heap_type(parser)
_ =>
raise WiteError::InvalidFormat(
"unsupported storage type in " + context + ": " + code.to_string(),
)
}
let end_ = parser.get_pos()
parser.bytes[start:end_].to_bytes()
}
///|
priv struct ParsedGcTypeField {
storage_type : Bytes
mutability : Byte
}
///|
priv struct ParsedGcTypeEntry {
form : UInt
fields : Array[ParsedGcTypeField]
}
///|
fn parse_gc_non_function_type_entry_from_raw_raise(
raw : Bytes,
context : String,
) -> ParsedGcTypeEntry raise WiteError {
let parser = Cursor::new(raw)
let form = parser.read_byte().to_uint()
let fields : Array[ParsedGcTypeField] = []
match form {
0x5fU => {
let field_count = parser.read_u32_leb128()
for _ in 0U.. 1U {
raise WiteError::InvalidFormat(
"invalid field mutability in " +
context +
": " +
mutability.to_string(),
)
}
fields.push({ storage_type, mutability: mutability.to_byte() })
}
}
0x5eU => {
let storage_type = read_storage_type_bytes_raise(parser, context)
let mutability = parser.read_byte().to_uint()
if mutability > 1U {
raise WiteError::InvalidFormat(
"invalid array field mutability in " +
context +
": " +
mutability.to_string(),
)
}
fields.push({ storage_type, mutability: mutability.to_byte() })
}
_ =>
raise WiteError::InvalidFormat(
"unsupported gc type form in " + context + ": " + form.to_string(),
)
}
if not(parser.eof()) {
raise WiteError::InvalidFormat(
"trailing bytes in gc type entry in " + context,
)
}
{ form, fields }
}
///|
fn gc_type_entries_equal_with_canonical_map_raise(
left_raw : Bytes,
right_raw : Bytes,
gc_type_canonical_map : Map[UInt, UInt],
) -> Bool raise WiteError {
if bytes_equal(left_raw, right_raw) {
return true
}
let left = parse_gc_non_function_type_entry_from_raw_raise(
left_raw, "type-refine-gc-compare",
)
let right = parse_gc_non_function_type_entry_from_raw_raise(
right_raw, "type-refine-gc-compare",
)
if left.form != right.form || left.fields.length() != right.fields.length() {
return false
}
for i in 0.. Bytes raise WiteError {
let start = parser.get_pos()
while true {
let opcode = parser.read_byte().to_uint()
skip_instruction_immediate_raise(parser, opcode)
if opcode == 0x0bU {
let end_ = parser.get_pos()
return payload[start:end_].to_bytes()
}
}
b""
}
///|
fn rewrite_const_expr_function_indices_raise(
expr_bytes : Bytes,
index_map : Map[UInt, UInt],
) -> Bytes raise WiteError {
let spans = parse_instruction_spans_raise(expr_bytes)
rewrite_instruction_function_indices_raise(expr_bytes, spans, index_map)
}
///|
fn push_unique_u32(out : Array[UInt], value : UInt) -> Unit {
if not(out.contains(value)) {
out.push(value)
}
}
///|
fn make_bool_array(length : Int, value : Bool) -> Array[Bool] {
let out : Array[Bool] = []
for _ in 0.. Bool {
let pos = UInt::reinterpret_as_int(index)
if pos < 0 || pos >= visited.length() {
false
} else {
visited[pos]
}
}
///|
fn mark_visited_function_index(visited : Array[Bool], index : UInt) -> Bool {
let pos = UInt::reinterpret_as_int(index)
if pos < 0 || pos >= visited.length() || visited[pos] {
false
} else {
visited[pos] = true
true
}
}
///|
fn collect_ref_func_indices_from_expr_raise(
expr_bytes : Bytes,
) -> Array[UInt] raise WiteError {
let spans = parse_instruction_spans_raise(expr_bytes)
let out : Array[UInt] = []
for span in spans {
if span.opcode == 0xd2U {
match decode_span_u32_immediate(expr_bytes, span) {
Some(index) => push_unique_u32(out, index)
None =>
raise WiteError::InvalidFormat(
"failed to decode ref.func immediate in const expr",
)
}
}
}
out
}
///|
fn collect_ref_func_indices_from_instruction_raise(
instr_bytes : Bytes,
spans : Array[InstrSpan],
) -> Array[UInt] raise WiteError {
let out : Array[UInt] = []
for span in spans {
if span.opcode == 0xd2U {
match decode_span_u32_immediate(instr_bytes, span) {
Some(index) => push_unique_u32(out, index)
None =>
raise WiteError::InvalidFormat(
"failed to decode ref.func immediate in code",
)
}
}
}
out
}
///|
fn collect_ref_func_indices_from_code_section_raise(
payload : Bytes,
) -> Array[UInt] raise WiteError {
let out : Array[UInt] = []
let bodies = parse_code_bodies(payload)
for body in bodies {
let prefix_end = parse_local_decl_prefix_end(body)
let instr_bytes = body[prefix_end:body.length()].to_bytes()
let spans = parse_instruction_spans_raise(instr_bytes)
for
index in collect_ref_func_indices_from_instruction_raise(
instr_bytes, spans,
) {
push_unique_u32(out, index)
}
}
out
}
///|
fn collect_global_get_indices_from_expr_raise(
expr_bytes : Bytes,
) -> Array[UInt] raise WiteError {
let spans = parse_instruction_spans_raise(expr_bytes)
let out : Array[UInt] = []
for span in spans {
if span.opcode == 0x23U {
match decode_span_u32_immediate(expr_bytes, span) {
Some(index) => push_unique_u32(out, index)
None =>
raise WiteError::InvalidFormat(
"failed to decode global.get immediate in const expr",
)
}
}
}
out
}
///|
fn remap_global_index_raise(
index : UInt,
global_index_map : Map[UInt, UInt],
) -> UInt raise WiteError {
match global_index_map.get(index) {
Some(v) => v
None =>
raise WiteError::InvalidFormat(
"global remap missing index: " + index.to_string(),
)
}
}
///|
fn remap_memory_index_raise(
index : UInt,
memory_index_map : Map[UInt, UInt],
) -> UInt raise WiteError {
match memory_index_map.get(index) {
Some(v) => v
None =>
raise WiteError::InvalidFormat(
"memory remap missing index: " + index.to_string(),
)
}
}
///|
fn rewrite_const_expr_global_indices_raise(
expr_bytes : Bytes,
global_index_map : Map[UInt, UInt],
) -> Bytes raise WiteError {
let spans = parse_instruction_spans_raise(expr_bytes)
let out : Array[Byte] = []
for span in spans {
if span.opcode == 0x23U {
match decode_span_u32_immediate(expr_bytes, span) {
Some(index) => {
out.push(0x23U.to_byte())
out.append(
encode_u32_leb128(remap_global_index_raise(index, global_index_map))[:],
)
}
None =>
raise WiteError::InvalidFormat(
"failed to decode global.get immediate",
)
}
} else {
out.append(expr_bytes[span.start:span.end_].to_array())
}
}
Bytes::from_array(out[:])
}
///|
fn parse_global_section_function_refs_raise(
payload : Bytes,
) -> Array[UInt] raise WiteError {
let parser = Cursor::new(payload)
let count = parser.read_u32_leb128()
let out : Array[UInt] = []
for _ in 0U.. Array[UInt] raise WiteError {
let parser = Cursor::new(payload)
let segment_count = parser.read_u32_leb128()
let out : Array[UInt] = []
for _ in 0U.. {
let offset_expr = read_const_expr_bytes_raise(parser, payload)
for index in collect_ref_func_indices_from_expr_raise(offset_expr) {
push_unique_u32(out, index)
}
let func_count = parser.read_u32_leb128()
for _ in 0U.. {
ignore(parser.read_byte())
let func_count = parser.read_u32_leb128()
for _ in 0U.. {
ignore(parser.read_u32_leb128())
let offset_expr = read_const_expr_bytes_raise(parser, payload)
for index in collect_ref_func_indices_from_expr_raise(offset_expr) {
push_unique_u32(out, index)
}
ignore(parser.read_byte())
let func_count = parser.read_u32_leb128()
for _ in 0U.. {
ignore(parser.read_byte())
let func_count = parser.read_u32_leb128()
for _ in 0U.. {
let offset_expr = read_const_expr_bytes_raise(parser, payload)
for index in collect_ref_func_indices_from_expr_raise(offset_expr) {
push_unique_u32(out, index)
}
let expr_count = parser.read_u32_leb128()
for _ in 0U.. {
ignore(read_ref_type_bytes_raise(parser))
let expr_count = parser.read_u32_leb128()
for _ in 0U.. {
ignore(parser.read_u32_leb128())
let offset_expr = read_const_expr_bytes_raise(parser, payload)
for index in collect_ref_func_indices_from_expr_raise(offset_expr) {
push_unique_u32(out, index)
}
ignore(read_ref_type_bytes_raise(parser))
let expr_count = parser.read_u32_leb128()
for _ in 0U.. {
ignore(read_ref_type_bytes_raise(parser))
let expr_count = parser.read_u32_leb128()
for _ in 0U..
raise WiteError::InvalidFormat(
"unsupported element segment flags in parser: " + flags.to_string(),
)
}
}
out
}
///|
fn collect_used_global_indices_from_element_section_raise(
payload : Bytes,
) -> Array[UInt] raise WiteError {
let parser = Cursor::new(payload)
let segment_count = parser.read_u32_leb128()
let out : Array[UInt] = []
for _ in 0U.. {
let offset_expr = read_const_expr_bytes_raise(parser, payload)
for index in collect_global_get_indices_from_expr_raise(offset_expr) {
push_unique_u32(out, index)
}
let func_count = parser.read_u32_leb128()
for _ in 0U.. {
ignore(parser.read_byte())
let func_count = parser.read_u32_leb128()
for _ in 0U.. {
ignore(parser.read_u32_leb128())
let offset_expr = read_const_expr_bytes_raise(parser, payload)
for index in collect_global_get_indices_from_expr_raise(offset_expr) {
push_unique_u32(out, index)
}
ignore(parser.read_byte())
let func_count = parser.read_u32_leb128()
for _ in 0U.. {
ignore(parser.read_byte())
let func_count = parser.read_u32_leb128()
for _ in 0U.. {
let offset_expr = read_const_expr_bytes_raise(parser, payload)
for index in collect_global_get_indices_from_expr_raise(offset_expr) {
push_unique_u32(out, index)
}
let expr_count = parser.read_u32_leb128()
for _ in 0U.. {
ignore(read_ref_type_bytes_raise(parser))
let expr_count = parser.read_u32_leb128()
for _ in 0U.. {
ignore(parser.read_u32_leb128())
let offset_expr = read_const_expr_bytes_raise(parser, payload)
for index in collect_global_get_indices_from_expr_raise(offset_expr) {
push_unique_u32(out, index)
}
ignore(read_ref_type_bytes_raise(parser))
let expr_count = parser.read_u32_leb128()
for _ in 0U.. {
ignore(read_ref_type_bytes_raise(parser))
let expr_count = parser.read_u32_leb128()
for _ in 0U..
raise WiteError::InvalidFormat(
"unsupported element segment flags in global parser: " +
flags.to_string(),
)
}
}
out
}
///|
fn rewrite_global_section_payload_for_index_map_raise(
payload : Bytes,
index_map : Map[UInt, UInt],
) -> Bytes raise WiteError {
let parser = Cursor::new(payload)
let count = parser.read_u32_leb128()
let out : Array[Byte] = []
out.append(encode_u32_leb128(count)[:])
for _ in 0U.. Bytes raise WiteError {
let parser = Cursor::new(payload)
let segment_count = parser.read_u32_leb128()
let out : Array[Byte] = []
out.append(encode_u32_leb128(segment_count)[:])
for _ in 0U.. {
let offset_expr = read_const_expr_bytes_raise(parser, payload)
out.append(
rewrite_const_expr_function_indices_raise(offset_expr, index_map)[:].to_array(),
)
let func_count = parser.read_u32_leb128()
out.append(encode_u32_leb128(func_count)[:])
for _ in 0U.. {
out.push(parser.read_byte())
let func_count = parser.read_u32_leb128()
out.append(encode_u32_leb128(func_count)[:])
for _ in 0U.. {
let table_index = parser.read_u32_leb128()
out.append(encode_u32_leb128(table_index)[:])
let offset_expr = read_const_expr_bytes_raise(parser, payload)
out.append(
rewrite_const_expr_function_indices_raise(offset_expr, index_map)[:].to_array(),
)
out.push(parser.read_byte())
let func_count = parser.read_u32_leb128()
out.append(encode_u32_leb128(func_count)[:])
for _ in 0U.. {
out.push(parser.read_byte())
let func_count = parser.read_u32_leb128()
out.append(encode_u32_leb128(func_count)[:])
for _ in 0U.. {
let offset_expr = read_const_expr_bytes_raise(parser, payload)
out.append(
rewrite_const_expr_function_indices_raise(offset_expr, index_map)[:].to_array(),
)
let expr_count = parser.read_u32_leb128()
out.append(encode_u32_leb128(expr_count)[:])
for _ in 0U.. {
out.append(read_ref_type_bytes_raise(parser)[:].to_array())
let expr_count = parser.read_u32_leb128()
out.append(encode_u32_leb128(expr_count)[:])
for _ in 0U.. {
let table_index = parser.read_u32_leb128()
out.append(encode_u32_leb128(table_index)[:])
let offset_expr = read_const_expr_bytes_raise(parser, payload)
out.append(
rewrite_const_expr_function_indices_raise(offset_expr, index_map)[:].to_array(),
)
out.append(read_ref_type_bytes_raise(parser)[:].to_array())
let expr_count = parser.read_u32_leb128()
out.append(encode_u32_leb128(expr_count)[:])
for _ in 0U.. {
out.append(read_ref_type_bytes_raise(parser)[:].to_array())
let expr_count = parser.read_u32_leb128()
out.append(encode_u32_leb128(expr_count)[:])
for _ in 0U..
raise WiteError::InvalidFormat(
"unsupported element segment flags in rewrite: " + flags.to_string(),
)
}
}
Bytes::from_array(out[:])
}
///|
priv struct GlobalSectionEntry {
val_type : Bytes
mutability : Byte
init_expr : Bytes
init_global_refs : Array[UInt]
}
///|
fn parse_global_section_entries_with_refs_raise(
payload : Bytes,
) -> Array[GlobalSectionEntry] raise WiteError {
let parser = Cursor::new(payload)
let count = parser.read_u32_leb128()
let out : Array[GlobalSectionEntry] = []
for _ in 0U.. Bytes raise WiteError {
let entries = parse_global_section_entries_with_refs_raise(payload)
if entries.length() != keep_local.length() {
raise WiteError::InvalidFormat(
"global section length mismatch: global=" +
entries.length().to_string() +
" keep=" +
keep_local.length().to_string(),
)
}
let kept_entries : Array[GlobalSectionEntry] = []
for i in 0.. Bytes raise WiteError {
let entries = parse_global_section_entries_with_refs_raise(payload)
if entries.length() != local_order.length() {
raise WiteError::InvalidFormat(
"global section length mismatch for reorder: global=" +
entries.length().to_string() +
" order=" +
local_order.length().to_string(),
)
}
let out : Array[Byte] = []
out.append(encode_u32_leb128(local_order.length().reinterpret_as_uint())[:])
for old_local_index_u in local_order {
let old_local_index = UInt::reinterpret_as_int(old_local_index_u)
if old_local_index < 0 || old_local_index >= entries.length() {
raise WiteError::InvalidFormat(
"global reorder index out of range: " + old_local_index_u.to_string(),
)
}
let entry = entries[old_local_index]
out.append(entry.val_type[:].to_array())
out.push(entry.mutability)
out.append(
rewrite_const_expr_global_indices_raise(entry.init_expr, global_index_map)[:].to_array(),
)
}
Bytes::from_array(out[:])
}
///|
fn parse_memory_section_entries_raise(
payload : Bytes,
) -> Array[Bytes] raise WiteError {
let parser = Cursor::new(payload)
let count = parser.read_u32_leb128()
let out : Array[Bytes] = []
for _ in 0U.. Bytes raise WiteError {
let entries = parse_memory_section_entries_raise(payload)
if entries.length() != keep_local.length() {
raise WiteError::InvalidFormat(
"memory section length mismatch: memory=" +
entries.length().to_string() +
" keep=" +
keep_local.length().to_string(),
)
}
let kept_entries : Array[Bytes] = []
for i in 0.. Bytes raise WiteError {
let entries = parse_memory_section_entries_raise(payload)
if entries.length() != local_order.length() {
raise WiteError::InvalidFormat(
"memory section length mismatch for reorder: memory=" +
entries.length().to_string() +
" order=" +
local_order.length().to_string(),
)
}
let out : Array[Byte] = []
out.append(encode_u32_leb128(local_order.length().reinterpret_as_uint())[:])
for old_local_index_u in local_order {
let old_local_index = UInt::reinterpret_as_int(old_local_index_u)
if old_local_index < 0 || old_local_index >= entries.length() {
raise WiteError::InvalidFormat(
"memory reorder index out of range: " + old_local_index_u.to_string(),
)
}
out.append(entries[old_local_index][:].to_array())
}
Bytes::from_array(out[:])
}
///|
fn parse_table_section_entries_raise(
payload : Bytes,
) -> Array[Bytes] raise WiteError {
let parser = Cursor::new(payload)
let count = parser.read_u32_leb128()
let out : Array[Bytes] = []
for _ in 0U.. Bytes raise WiteError {
let entries = parse_table_section_entries_raise(payload)
if entries.length() != keep_local.length() {
raise WiteError::InvalidFormat(
"table section length mismatch: table=" +
entries.length().to_string() +
" keep=" +
keep_local.length().to_string(),
)
}
let out : Array[Byte] = []
let mut kept_count = 0U
let kept_entries : Array[Bytes] = []
for i in 0.. UInt raise WiteError {
match table_index_map.get(index) {
Some(v) => v
None =>
raise WiteError::InvalidFormat(
"table remap missing index: " + index.to_string(),
)
}
}
///|
fn remap_element_segment_index_raise(
index : UInt,
element_index_map : Map[UInt, UInt],
) -> UInt raise WiteError {
match element_index_map.get(index) {
Some(v) => v
None =>
raise WiteError::InvalidFormat(
"element segment remap missing index: " + index.to_string(),
)
}
}
///|
fn remap_data_segment_index_raise(
index : UInt,
data_index_map : Map[UInt, UInt],
) -> UInt raise WiteError {
match data_index_map.get(index) {
Some(v) => v
None =>
raise WiteError::InvalidFormat(
"data segment remap missing index: " + index.to_string(),
)
}
}
///|
fn make_identity_index_map(count : UInt) -> Map[UInt, UInt] {
let out : Map[UInt, UInt] = {}
for i in 0U.. ImportKindCounts {
let mut table_count = 0U
let mut memory_count = 0U
let mut global_count = 0U
for entry in entries {
match entry.kind {
1U => table_count += 1U
2U => memory_count += 1U
3U => global_count += 1U
_ => ()
}
}
{ table_count, memory_count, global_count }
}
///|
fn count_reordered_local_indices(local_order : Array[UInt]) -> UInt {
let mut moved = 0U
for i in 0.. Unit {
let pos = UInt::reinterpret_as_int(index)
if pos >= 0 && pos < counts.length() {
counts[pos] += 1U
}
}
///|
fn index_count_in_array(counts : Array[UInt], index : UInt) -> UInt {
let pos = UInt::reinterpret_as_int(index)
if pos < 0 || pos >= counts.length() {
return 0U
}
counts[pos]
}
///|
fn collect_used_table_indices_from_instruction_raise(
instr_bytes : Bytes,
spans : Array[InstrSpan],
) -> Array[UInt] raise WiteError {
let out : Array[UInt] = []
for span in spans {
match span.opcode {
0x11U => {
if span.end_ <= span.start + 1 {
raise WiteError::InvalidFormat("invalid call_indirect immediate")
}
let imm = instr_bytes[span.start + 1:span.end_].to_bytes()
let parser = Cursor::new(imm)
ignore(parser.read_u32_leb128())
push_unique_u32(out, parser.read_u32_leb128())
}
0x25U | 0x26U =>
match decode_span_u32_immediate(instr_bytes, span) {
Some(index) => push_unique_u32(out, index)
None =>
raise WiteError::InvalidFormat(
"failed to decode table index immediate",
)
}
0xfcU => {
if span.end_ <= span.start + 1 {
raise WiteError::InvalidFormat("invalid 0xfc immediate")
}
let imm = instr_bytes[span.start + 1:span.end_].to_bytes()
let parser = Cursor::new(imm)
let subopcode = parser.read_u32_leb128()
match subopcode {
12U => {
ignore(parser.read_u32_leb128())
push_unique_u32(out, parser.read_u32_leb128())
}
14U => {
push_unique_u32(out, parser.read_u32_leb128())
push_unique_u32(out, parser.read_u32_leb128())
}
15U | 16U | 17U => push_unique_u32(out, parser.read_u32_leb128())
_ => ()
}
}
_ => ()
}
}
out
}
///|
fn collect_used_table_indices_from_code_section(
payload : Bytes,
) -> Array[UInt]? {
let result = try? {
let out : Array[UInt] = []
let bodies = parse_code_bodies(payload)
for body in bodies {
let prefix_end = parse_local_decl_prefix_end(body)
let instr_bytes = body[prefix_end:body.length()].to_bytes()
let spans = parse_instruction_spans_raise(instr_bytes)
for
index in collect_used_table_indices_from_instruction_raise(
instr_bytes, spans,
) {
push_unique_u32(out, index)
}
}
out
}
match result {
Ok(v) => Some(v)
Err(_) => None
}
}
///|
fn opcode_fd_uses_memory(subopcode : UInt) -> Bool {
(subopcode >= 0x00U && subopcode <= 0x0bU) ||
(subopcode >= 0x54U && subopcode <= 0x5bU) ||
subopcode == 0x5cU ||
subopcode == 0x5dU
}
///|
fn collect_used_global_indices_from_instruction_raise(
instr_bytes : Bytes,
spans : Array[InstrSpan],
) -> Array[UInt] raise WiteError {
let out : Array[UInt] = []
for span in spans {
if span.opcode == 0x23U || span.opcode == 0x24U {
match decode_span_u32_immediate(instr_bytes, span) {
Some(index) => push_unique_u32(out, index)
None =>
raise WiteError::InvalidFormat(
"failed to decode global index immediate",
)
}
}
}
out
}
///|
fn collect_used_memory_indices_from_instruction_raise(
instr_bytes : Bytes,
spans : Array[InstrSpan],
) -> Array[UInt] raise WiteError {
let out : Array[UInt] = []
for span in spans {
match span.opcode {
0x28U..=0x3eU => push_unique_u32(out, 0U)
0x3fU | 0x40U =>
match decode_span_u32_immediate(instr_bytes, span) {
Some(index) => push_unique_u32(out, index)
None =>
raise WiteError::InvalidFormat(
"failed to decode memory.size/memory.grow immediate",
)
}
0xfcU => {
if span.end_ <= span.start + 1 {
raise WiteError::InvalidFormat("invalid 0xfc immediate")
}
let imm = instr_bytes[span.start + 1:span.end_].to_bytes()
let parser = Cursor::new(imm)
let subopcode = parser.read_u32_leb128()
match subopcode {
8U => {
ignore(parser.read_u32_leb128())
push_unique_u32(out, parser.read_u32_leb128())
}
10U => {
push_unique_u32(out, parser.read_u32_leb128())
push_unique_u32(out, parser.read_u32_leb128())
}
11U => push_unique_u32(out, parser.read_u32_leb128())
_ => ()
}
}
0xfdU => {
if span.end_ <= span.start + 1 {
raise WiteError::InvalidFormat("invalid 0xfd immediate")
}
let imm = instr_bytes[span.start + 1:span.end_].to_bytes()
let parser = Cursor::new(imm)
if opcode_fd_uses_memory(parser.read_u32_leb128()) {
push_unique_u32(out, 0U)
}
}
_ => ()
}
}
out
}
///|
fn collect_used_global_indices_from_code_section(
payload : Bytes,
) -> Array[UInt]? {
let result = try? {
let out : Array[UInt] = []
let bodies = parse_code_bodies(payload)
for body in bodies {
let prefix_end = parse_local_decl_prefix_end(body)
let instr_bytes = body[prefix_end:body.length()].to_bytes()
let spans = parse_instruction_spans_raise(instr_bytes)
for
index in collect_used_global_indices_from_instruction_raise(
instr_bytes, spans,
) {
push_unique_u32(out, index)
}
}
out
}
match result {
Ok(v) => Some(v)
Err(_) => None
}
}
///|
fn collect_used_memory_indices_from_code_section(
payload : Bytes,
) -> Array[UInt]? {
let result = try? {
let out : Array[UInt] = []
let bodies = parse_code_bodies(payload)
for body in bodies {
let prefix_end = parse_local_decl_prefix_end(body)
let instr_bytes = body[prefix_end:body.length()].to_bytes()
let spans = parse_instruction_spans_raise(instr_bytes)
for
index in collect_used_memory_indices_from_instruction_raise(
instr_bytes, spans,
) {
push_unique_u32(out, index)
}
}
out
}
match result {
Ok(v) => Some(v)
Err(_) => None
}
}
///|
fn rewrite_instruction_module_indices_raise(
instr_bytes : Bytes,
spans : Array[InstrSpan],
table_index_map : Map[UInt, UInt],
memory_index_map : Map[UInt, UInt],
global_index_map : Map[UInt, UInt],
element_index_map : Map[UInt, UInt],
data_index_map : Map[UInt, UInt],
) -> Bytes raise WiteError {
let out : Array[Byte] = []
for span in spans {
match span.opcode {
0x11U => {
if span.end_ <= span.start + 1 {
raise WiteError::InvalidFormat("invalid call_indirect immediate")
}
let imm = instr_bytes[span.start + 1:span.end_].to_bytes()
let parser = Cursor::new(imm)
let type_index = parser.read_u32_leb128()
let table_index = parser.read_u32_leb128()
out.push(0x11U.to_byte())
out.append(encode_u32_leb128(type_index)[:])
out.append(
encode_u32_leb128(
remap_table_index_raise(table_index, table_index_map),
)[:],
)
}
0x23U | 0x24U =>
match decode_span_u32_immediate(instr_bytes, span) {
Some(index) => {
out.push(span.opcode.to_byte())
out.append(
encode_u32_leb128(
remap_global_index_raise(index, global_index_map),
)[:],
)
}
None =>
raise WiteError::InvalidFormat(
"failed to decode global index immediate",
)
}
0x25U | 0x26U =>
match decode_span_u32_immediate(instr_bytes, span) {
Some(index) => {
out.push(span.opcode.to_byte())
out.append(
encode_u32_leb128(remap_table_index_raise(index, table_index_map))[:],
)
}
None =>
raise WiteError::InvalidFormat(
"failed to decode table index immediate",
)
}
0x28U..=0x3eU => {
if remap_memory_index_raise(0U, memory_index_map) != 0U {
raise WiteError::InvalidFormat(
"implicit memory index remapped to non-zero",
)
}
out.append(instr_bytes[span.start:span.end_].to_array())
}
0x3fU | 0x40U =>
match decode_span_u32_immediate(instr_bytes, span) {
Some(index) => {
out.push(span.opcode.to_byte())
out.append(
encode_u32_leb128(
remap_memory_index_raise(index, memory_index_map),
)[:],
)
}
None =>
raise WiteError::InvalidFormat(
"failed to decode memory.size/memory.grow immediate",
)
}
0xfcU => {
if span.end_ <= span.start + 1 {
raise WiteError::InvalidFormat("invalid 0xfc immediate")
}
let imm = instr_bytes[span.start + 1:span.end_].to_bytes()
let parser = Cursor::new(imm)
let subopcode = parser.read_u32_leb128()
let rest_start = parser.get_pos()
out.push(0xfcU.to_byte())
out.append(encode_u32_leb128(subopcode)[:])
match subopcode {
8U => {
let data_index = parser.read_u32_leb128()
let memory_index = parser.read_u32_leb128()
out.append(
encode_u32_leb128(
remap_data_segment_index_raise(data_index, data_index_map),
)[:],
)
out.append(
encode_u32_leb128(
remap_memory_index_raise(memory_index, memory_index_map),
)[:],
)
}
9U => {
let data_index = parser.read_u32_leb128()
out.append(
encode_u32_leb128(
remap_data_segment_index_raise(data_index, data_index_map),
)[:],
)
}
10U => {
let dst_memory = parser.read_u32_leb128()
let src_memory = parser.read_u32_leb128()
out.append(
encode_u32_leb128(
remap_memory_index_raise(dst_memory, memory_index_map),
)[:],
)
out.append(
encode_u32_leb128(
remap_memory_index_raise(src_memory, memory_index_map),
)[:],
)
}
11U => {
let memory_index = parser.read_u32_leb128()
out.append(
encode_u32_leb128(
remap_memory_index_raise(memory_index, memory_index_map),
)[:],
)
}
12U => {
let elem_index = parser.read_u32_leb128()
let table_index = parser.read_u32_leb128()
out.append(
encode_u32_leb128(
remap_element_segment_index_raise(elem_index, element_index_map),
)[:],
)
out.append(
encode_u32_leb128(
remap_table_index_raise(table_index, table_index_map),
)[:],
)
}
13U => {
let elem_index = parser.read_u32_leb128()
out.append(
encode_u32_leb128(
remap_element_segment_index_raise(elem_index, element_index_map),
)[:],
)
}
14U => {
let dst_table = parser.read_u32_leb128()
let src_table = parser.read_u32_leb128()
out.append(
encode_u32_leb128(
remap_table_index_raise(dst_table, table_index_map),
)[:],
)
out.append(
encode_u32_leb128(
remap_table_index_raise(src_table, table_index_map),
)[:],
)
}
15U | 16U | 17U => {
let table_index = parser.read_u32_leb128()
out.append(
encode_u32_leb128(
remap_table_index_raise(table_index, table_index_map),
)[:],
)
}
_ => out.append(imm[rest_start:imm.length()].to_array())
}
}
_ => out.append(instr_bytes[span.start:span.end_].to_array())
}
}
Bytes::from_array(out[:])
}
///|
fn rewrite_code_body_for_module_index_map_raise(
body : Bytes,
table_index_map : Map[UInt, UInt],
memory_index_map : Map[UInt, UInt],
global_index_map : Map[UInt, UInt],
element_index_map : Map[UInt, UInt],
data_index_map : Map[UInt, UInt],
) -> Bytes raise WiteError {
let prefix_end = parse_local_decl_prefix_end(body)
let instr_bytes = body[prefix_end:body.length()].to_bytes()
let spans = parse_instruction_spans_raise(instr_bytes)
let rewritten_instr = rewrite_instruction_module_indices_raise(
instr_bytes, spans, table_index_map, memory_index_map, global_index_map, element_index_map,
data_index_map,
)
let out : Array[Byte] = body[0:prefix_end].to_array()
out.append(rewritten_instr[:].to_array())
Bytes::from_array(out[:])
}
///|
fn rewrite_code_section_payload_for_module_index_map_raise(
payload : Bytes,
table_index_map : Map[UInt, UInt],
memory_index_map : Map[UInt, UInt],
global_index_map : Map[UInt, UInt],
element_index_map : Map[UInt, UInt],
data_index_map : Map[UInt, UInt],
) -> Bytes raise WiteError {
let bodies = parse_code_bodies(payload)
let out : Array[Byte] = []
out.append(encode_u32_leb128(bodies.length().reinterpret_as_uint())[:])
for body in bodies {
let rewritten = rewrite_code_body_for_module_index_map_raise(
body, table_index_map, memory_index_map, global_index_map, element_index_map,
data_index_map,
)
out.append(encode_u32_leb128(rewritten.length().reinterpret_as_uint())[:])
out.append(rewritten[:].to_array())
}
Bytes::from_array(out[:])
}
///|
fn rewrite_export_section_payload_for_module_index_maps_raise(
payload : Bytes,
table_index_map : Map[UInt, UInt],
memory_index_map : Map[UInt, UInt],
global_index_map : Map[UInt, UInt],
) -> (Bytes, UInt) raise WiteError {
let parser = Cursor::new(payload)
let export_count = parser.read_u32_leb128()
let out : Array[Byte] = []
let mut kept_count = 0U
let mut removed_count = 0U
for _ in 0U..
match table_index_map.get(index) {
Some(v) => Some(v)
None => None
}
2U =>
match memory_index_map.get(index) {
Some(v) => Some(v)
None => None
}
3U =>
match global_index_map.get(index) {
Some(v) => Some(v)
None => None
}
_ => Some(index)
}
match mapped {
Some(remapped) => {
kept_count += 1U
append_name_bytes(out, name)
out.push(kind.to_byte())
out.append(encode_u32_leb128(remapped)[:])
}
None => removed_count += 1U
}
}
let encoded : Array[Byte] = []
encoded.append(encode_u32_leb128(kept_count)[:])
encoded.append(out[:])
(Bytes::from_array(encoded[:]), removed_count)
}
///|
fn read_function_index_vector_bytes_raise(
parser : Cursor,
) -> Bytes raise WiteError {
let start = parser.get_pos()
let count = parser.read_u32_leb128()
for _ in 0U.. Bytes raise WiteError {
let count = parser.read_u32_leb128()
let out : Array[Byte] = []
out.append(encode_u32_leb128(count)[:])
for _ in 0U.. (Bytes, UInt, Map[UInt, UInt]) raise WiteError {
let parser = Cursor::new(payload)
let segment_count = parser.read_u32_leb128()
let kept_segments : Array[Bytes] = []
let element_index_map : Map[UInt, UInt] = {}
let mut removed_segment_count = 0U
for old_segment_index in 0U.. {
let offset_expr = read_const_expr_bytes_raise(parser, payload)
let function_indices = read_function_index_vector_bytes_raise(parser)
if table_index_map.get(0U) is None {
keep_segment = false
} else {
segment_out.append(encode_u32_leb128(0U)[:])
segment_out.append(
rewrite_const_expr_global_indices_raise(
offset_expr, global_index_map,
)[:].to_array(),
)
segment_out.append(function_indices[:].to_array())
}
}
1U => {
let elem_kind = parser.read_byte()
let function_indices = read_function_index_vector_bytes_raise(parser)
segment_out.append(encode_u32_leb128(1U)[:])
segment_out.push(elem_kind)
segment_out.append(function_indices[:].to_array())
}
2U => {
let table_index = parser.read_u32_leb128()
let offset_expr = read_const_expr_bytes_raise(parser, payload)
let elem_kind = parser.read_byte()
let function_indices = read_function_index_vector_bytes_raise(parser)
match table_index_map.get(table_index) {
Some(remapped) => {
segment_out.append(encode_u32_leb128(2U)[:])
segment_out.append(encode_u32_leb128(remapped)[:])
segment_out.append(
rewrite_const_expr_global_indices_raise(
offset_expr, global_index_map,
)[:].to_array(),
)
segment_out.push(elem_kind)
segment_out.append(function_indices[:].to_array())
}
None => keep_segment = false
}
}
3U => {
let elem_kind = parser.read_byte()
let function_indices = read_function_index_vector_bytes_raise(parser)
segment_out.append(encode_u32_leb128(3U)[:])
segment_out.push(elem_kind)
segment_out.append(function_indices[:].to_array())
}
4U => {
let offset_expr = read_const_expr_bytes_raise(parser, payload)
let exprs = rewrite_const_expr_vector_global_indices_raise(
parser, payload, global_index_map,
)
if table_index_map.get(0U) is None {
keep_segment = false
} else {
segment_out.append(encode_u32_leb128(4U)[:])
segment_out.append(
rewrite_const_expr_global_indices_raise(
offset_expr, global_index_map,
)[:].to_array(),
)
segment_out.append(exprs[:].to_array())
}
}
5U => {
let ref_type = read_ref_type_bytes_raise(parser)
let exprs = rewrite_const_expr_vector_global_indices_raise(
parser, payload, global_index_map,
)
segment_out.append(encode_u32_leb128(5U)[:])
segment_out.append(ref_type[:].to_array())
segment_out.append(exprs[:].to_array())
}
6U => {
let table_index = parser.read_u32_leb128()
let offset_expr = read_const_expr_bytes_raise(parser, payload)
let ref_type = read_ref_type_bytes_raise(parser)
let exprs = rewrite_const_expr_vector_global_indices_raise(
parser, payload, global_index_map,
)
match table_index_map.get(table_index) {
Some(remapped) => {
segment_out.append(encode_u32_leb128(6U)[:])
segment_out.append(encode_u32_leb128(remapped)[:])
segment_out.append(
rewrite_const_expr_global_indices_raise(
offset_expr, global_index_map,
)[:].to_array(),
)
segment_out.append(ref_type[:].to_array())
segment_out.append(exprs[:].to_array())
}
None => keep_segment = false
}
}
7U => {
let ref_type = read_ref_type_bytes_raise(parser)
let exprs = rewrite_const_expr_vector_global_indices_raise(
parser, payload, global_index_map,
)
segment_out.append(encode_u32_leb128(7U)[:])
segment_out.append(ref_type[:].to_array())
segment_out.append(exprs[:].to_array())
}
_ =>
raise WiteError::InvalidFormat(
"unsupported element segment flags in table rewrite: " +
flags.to_string(),
)
}
if keep_segment {
element_index_map.set(
old_segment_index,
kept_segments.length().reinterpret_as_uint(),
)
kept_segments.push(Bytes::from_array(segment_out[:]))
} else {
removed_segment_count += 1U
}
}
let out : Array[Byte] = []
out.append(encode_u32_leb128(kept_segments.length().reinterpret_as_uint())[:])
for segment in kept_segments {
out.append(segment[:].to_array())
}
(Bytes::from_array(out[:]), removed_segment_count, element_index_map)
}
///|
fn read_data_init_bytes_raise(
parser : Cursor,
payload : Bytes,
) -> Bytes raise WiteError {
let start = parser.get_pos()
let size_u = parser.read_u32_leb128()
let size = UInt::reinterpret_as_int(size_u)
if size < 0 {
raise WiteError::InvalidFormat("data init size overflow")
}
parser.skip(size)
let end_ = parser.get_pos()
payload[start:end_].to_bytes()
}
///|
fn collect_used_memory_and_global_indices_from_data_section_raise(
payload : Bytes,
) -> (Array[UInt], Array[UInt]) raise WiteError {
let parser = Cursor::new(payload)
let segment_count = parser.read_u32_leb128()
let memory_indices : Array[UInt] = []
let global_indices : Array[UInt] = []
for _ in 0U.. {
push_unique_u32(memory_indices, 0U)
let offset_expr = read_const_expr_bytes_raise(parser, payload)
for index in collect_global_get_indices_from_expr_raise(offset_expr) {
push_unique_u32(global_indices, index)
}
ignore(read_data_init_bytes_raise(parser, payload))
}
1U => ignore(read_data_init_bytes_raise(parser, payload))
2U => {
let memory_index = parser.read_u32_leb128()
push_unique_u32(memory_indices, memory_index)
let offset_expr = read_const_expr_bytes_raise(parser, payload)
for index in collect_global_get_indices_from_expr_raise(offset_expr) {
push_unique_u32(global_indices, index)
}
ignore(read_data_init_bytes_raise(parser, payload))
}
_ =>
raise WiteError::InvalidFormat(
"unsupported data segment flags in parser: " + flags.to_string(),
)
}
}
(memory_indices, global_indices)
}
///|
fn rewrite_data_section_payload_for_memory_global_index_map_raise(
payload : Bytes,
memory_index_map : Map[UInt, UInt],
global_index_map : Map[UInt, UInt],
) -> (Bytes, UInt, Map[UInt, UInt]) raise WiteError {
let parser = Cursor::new(payload)
let segment_count = parser.read_u32_leb128()
let kept_segments : Array[Bytes] = []
let data_index_map : Map[UInt, UInt] = {}
let mut removed_segment_count = 0U
for old_segment_index in 0U.. {
let offset_expr = read_const_expr_bytes_raise(parser, payload)
let data_init = read_data_init_bytes_raise(parser, payload)
match memory_index_map.get(0U) {
Some(remapped_memory_index) =>
if remapped_memory_index == 0U {
segment_out.append(encode_u32_leb128(0U)[:])
segment_out.append(
rewrite_const_expr_global_indices_raise(
offset_expr, global_index_map,
)[:].to_array(),
)
segment_out.append(data_init[:].to_array())
} else {
segment_out.append(encode_u32_leb128(2U)[:])
segment_out.append(encode_u32_leb128(remapped_memory_index)[:])
segment_out.append(
rewrite_const_expr_global_indices_raise(
offset_expr, global_index_map,
)[:].to_array(),
)
segment_out.append(data_init[:].to_array())
}
None => keep_segment = false
}
}
1U => {
let data_init = read_data_init_bytes_raise(parser, payload)
segment_out.append(encode_u32_leb128(1U)[:])
segment_out.append(data_init[:].to_array())
}
2U => {
let memory_index = parser.read_u32_leb128()
let offset_expr = read_const_expr_bytes_raise(parser, payload)
let data_init = read_data_init_bytes_raise(parser, payload)
match memory_index_map.get(memory_index) {
Some(remapped_memory_index) => {
segment_out.append(encode_u32_leb128(2U)[:])
segment_out.append(encode_u32_leb128(remapped_memory_index)[:])
segment_out.append(
rewrite_const_expr_global_indices_raise(
offset_expr, global_index_map,
)[:].to_array(),
)
segment_out.append(data_init[:].to_array())
}
None => keep_segment = false
}
}
_ =>
raise WiteError::InvalidFormat(
"unsupported data segment flags in rewrite: " + flags.to_string(),
)
}
if keep_segment {
data_index_map.set(
old_segment_index,
kept_segments.length().reinterpret_as_uint(),
)
kept_segments.push(Bytes::from_array(segment_out[:]))
} else {
removed_segment_count += 1U
}
}
let out : Array[Byte] = []
out.append(encode_u32_leb128(kept_segments.length().reinterpret_as_uint())[:])
for segment in kept_segments {
out.append(segment[:].to_array())
}
(Bytes::from_array(out[:]), removed_segment_count, data_index_map)
}
///|
fn rewrite_export_section_payload_for_dce_raise(
payload : Bytes,
index_map : Map[UInt, UInt],
) -> Bytes raise WiteError {
let parser = Cursor::new(payload)
let export_count = parser.read_u32_leb128()
let kept_entries : Array[Bytes] = []
for _ in 0U.. {
entry.append(encode_u32_leb128(remapped)[:])
kept_entries.push(Bytes::from_array(entry[:]))
}
None => ()
}
} else {
entry.append(encode_u32_leb128(index)[:])
kept_entries.push(Bytes::from_array(entry[:]))
}
}
let out : Array[Byte] = []
out.append(encode_u32_leb128(kept_entries.length().reinterpret_as_uint())[:])
for entry in kept_entries {
out.append(entry[:].to_array())
}
Bytes::from_array(out[:])
}
///|
fn rewrite_export_section_payload_for_keep_names_raise(
payload : Bytes,
keep_names : Array[String],
) -> (Bytes, UInt) raise WiteError {
let parser = Cursor::new(payload)
let export_count = parser.read_u32_leb128()
let kept_entries : Array[Bytes] = []
let mut removed_count = 0U
for _ in 0U.. Bytes raise WiteError {
let parser = Cursor::new(payload)
let start_index = parser.read_u32_leb128()
let remapped = remap_function_index_raise(start_index, index_map)
Bytes::from_array(encode_u32_leb128(remapped))
}
///|
fn parse_function_type_indices(payload : Bytes) -> Array[UInt] raise WiteError {
let parser = Cursor::new(payload)
let count = parser.read_u32_leb128()
let out : Array[UInt] = []
for _ in 0U.. Unit {
if heap_type_bytes.length() == 0 {
return
}
let last = heap_type_bytes[heap_type_bytes.length() - 1].to_uint()
// Signed heaptype immediates with sign bit set are negative builtins.
if (last & 0x40U) != 0U {
return
}
match decode_u32_leb128_bytes(Bytes::from_array(heap_type_bytes[:])) {
Some(type_index) => push_unique_u32(referenced_type_indices, type_index)
None => ()
}
}
///|
fn read_heap_type_and_collect_refs_raise(
parser : Cursor,
referenced_type_indices : Array[UInt],
context : String,
) -> Unit raise WiteError {
let bytes : Array[Byte] = []
let first = parser.read_byte()
bytes.push(first)
if (first & 0x80) != 0 {
let mut consumed = 1
while true {
let b = parser.read_byte()
bytes.push(b)
consumed += 1
if consumed > 5 {
raise WiteError::InvalidFormat("heap type s33 overflow in " + context)
}
if (b & 0x80) == 0 {
break
}
}
}
maybe_collect_heap_type_ref_index(bytes, referenced_type_indices)
}
///|
fn read_type_entry_val_type_refs_raise(
parser : Cursor,
referenced_type_indices : Array[UInt],
context : String,
) -> Unit raise WiteError {
let code = parser.read_byte().to_uint()
match code {
0x7fU | 0x7eU | 0x7dU | 0x7cU | 0x7bU | 0x70U | 0x6fU => ()
0x63U | 0x64U =>
read_heap_type_and_collect_refs_raise(
parser, referenced_type_indices, context,
)
_ =>
raise WiteError::InvalidFormat(
"unsupported value type in " + context + ": " + code.to_string(),
)
}
}
///|
fn read_type_entry_storage_type_refs_raise(
parser : Cursor,
referenced_type_indices : Array[UInt],
context : String,
) -> Unit raise WiteError {
let code = parser.read_byte().to_uint()
match code {
0x78U | 0x77U => ()
0x7fU | 0x7eU | 0x7dU | 0x7cU | 0x7bU | 0x70U | 0x6fU => ()
0x63U | 0x64U =>
read_heap_type_and_collect_refs_raise(
parser, referenced_type_indices, context,
)
_ =>
raise WiteError::InvalidFormat(
"unsupported storage type in " + context + ": " + code.to_string(),
)
}
}
///|
fn read_type_entry_field_type_refs_raise(
parser : Cursor,
referenced_type_indices : Array[UInt],
context : String,
) -> Unit raise WiteError {
read_type_entry_storage_type_refs_raise(
parser, referenced_type_indices, context,
)
let mutability = parser.read_byte().to_uint()
if mutability > 1U {
raise WiteError::InvalidFormat(
"invalid field mutability in " + context + ": " + mutability.to_string(),
)
}
}
///|
fn parse_type_section_entries_with_refs_raise(
payload : Bytes,
context : String,
) -> Array[ParsedTypeSectionEntry] raise WiteError {
let parser = Cursor::new(payload)
let count = parser.read_u32_leb128()
let out : Array[ParsedTypeSectionEntry] = []
for _ in 0U.. {
let param_count = parser.read_u32_leb128()
for _ in 0U.. {
let field_count = parser.read_u32_leb128()
for _ in 0U..
read_type_entry_field_type_refs_raise(
parser, referenced_type_indices, context,
)
_ =>
raise WiteError::InvalidFormat(
"unsupported type form in " + context + ": " + form.to_string(),
)
}
let end_ = parser.get_pos()
out.push({
raw: payload[start:end_].to_bytes(),
form,
referenced_type_indices,
})
}
out
}
///|
fn parse_type_section_entries_raise(
payload : Bytes,
) -> Array[Bytes] raise WiteError {
let parsed = parse_type_section_entries_with_refs_raise(
payload, "type parser",
)
let out : Array[Bytes] = []
for entry in parsed {
out.push(entry.raw)
}
out
}
///|
priv struct FunctionTypeEntry {
params : Array[Bytes]
results : Array[Bytes]
}
///|
fn parse_function_type_entries_raise(
payload : Bytes,
context : String,
) -> Array[FunctionTypeEntry] raise WiteError {
let parser = Cursor::new(payload)
let count = parser.read_u32_leb128()
let out : Array[FunctionTypeEntry] = []
for _ in 0U.. FunctionTypeEntry raise WiteError {
let parser = Cursor::new(raw)
let form = parser.read_byte().to_uint()
if form != 0x60U {
raise WiteError::InvalidFormat(
"unsupported type form in " + context + ": " + form.to_string(),
)
}
let param_count = parser.read_u32_leb128()
let params : Array[Bytes] = []
for _ in 0U.. UInt raise WiteError {
let parser = Cursor::new(raw)
let form = parser.read_byte().to_uint()
if form != 0x60U {
raise WiteError::InvalidFormat(
"unsupported type form in " + context + ": " + form.to_string(),
)
}
let param_count = parser.read_u32_leb128()
for _ in 0U.. Bytes {
let out : Array[Byte] = [0x60U.to_byte()]
out.append(encode_u32_leb128(params.length().reinterpret_as_uint())[:])
for param in params {
out.append(param[:].to_array())
}
out.append(encode_u32_leb128(results.length().reinterpret_as_uint())[:])
for result in results {
out.append(result[:].to_array())
}
Bytes::from_array(out[:])
}
///|
fn encode_type_section_payload(entries : Array[Bytes]) -> Bytes {
let out : Array[Byte] = []
out.append(encode_u32_leb128(entries.length().reinterpret_as_uint())[:])
for entry in entries {
out.append(entry[:].to_array())
}
Bytes::from_array(out[:])
}
///|
fn find_type_entry_index(entries : Array[Bytes], target : Bytes) -> UInt? {
for i in 0.. Bytes {
let out : Array[Byte] = []
out.append(encode_u32_leb128(type_indices.length().reinterpret_as_uint())[:])
for type_index in type_indices {
out.append(encode_u32_leb128(type_index)[:])
}
Bytes::from_array(out[:])
}
///|
fn encode_code_section_payload_from_bodies(bodies : Array[Bytes]) -> Bytes {
let out : Array[Byte] = []
out.append(encode_u32_leb128(bodies.length().reinterpret_as_uint())[:])
for body in bodies {
out.append(encode_u32_leb128(body.length().reinterpret_as_uint())[:])
out.append(body[:].to_array())
}
Bytes::from_array(out[:])
}
///|
fn rewrite_type_section_payload_for_keep_raise(
payload : Bytes,
keep_types : Array[Bool],
) -> Bytes raise WiteError {
let entries = parse_type_section_entries_raise(payload)
if entries.length() != keep_types.length() {
raise WiteError::InvalidFormat(
"type section length mismatch: type=" +
entries.length().to_string() +
" keep=" +
keep_types.length().to_string(),
)
}
let kept_entries : Array[Bytes] = []
for i in 0.. Bytes raise WiteError {
let types = parse_function_type_indices(payload)
if types.length() != keep_local.length() {
raise WiteError::InvalidFormat(
"dce function section length mismatch: function=" +
types.length().to_string() +
" keep=" +
keep_local.length().to_string(),
)
}
let kept_types : Array[UInt] = []
for i in 0.. Bytes raise WiteError {
let parser = Cursor::new(payload)
let count = parser.read_u32_leb128()
let out : Array[Byte] = []
out.append(encode_u32_leb128(count)[:])
for _ in 0U.. Array[UInt] raise WiteError {
let parser = Cursor::new(payload)
let count = parser.read_u32_leb128()
let out : Array[UInt] = []
for _ in 0U.. push_unique_u32(out, parser.read_u32_leb128())
1U => {
skip_heap_type(parser)
parse_limits(parser)
}
2U => parse_limits(parser)
3U => {
ignore(read_val_type_bytes_raise(parser))
ignore(parser.read_byte())
}
4U =>
raise WiteError::InvalidFormat(
"tag import is not supported in remove-unused-types",
)
_ =>
raise WiteError::InvalidFormat(
"unsupported import descriptor kind in remove-unused-types: " +
kind.to_string(),
)
}
}
out
}
///|
fn rewrite_import_section_payload_for_type_index_map_raise(
payload : Bytes,
type_index_map : Map[UInt, UInt],
) -> Bytes raise WiteError {
let parser = Cursor::new(payload)
let count = parser.read_u32_leb128()
let out : Array[Byte] = []
out.append(encode_u32_leb128(count)[:])
for _ in 0U.. {
let old_type = parser.read_u32_leb128()
out.append(
encode_u32_leb128(remap_type_index_raise(old_type, type_index_map))[:],
)
}
1U => {
let start = parser.get_pos()
skip_heap_type(parser)
parse_limits(parser)
let end_ = parser.get_pos()
out.append(payload[start:end_].to_array())
}
2U => {
let start = parser.get_pos()
parse_limits(parser)
let end_ = parser.get_pos()
out.append(payload[start:end_].to_array())
}
3U => {
out.append(read_val_type_bytes_raise(parser)[:].to_array())
out.push(parser.read_byte())
}
4U =>
raise WiteError::InvalidFormat(
"tag import is not supported in remove-unused-types",
)
_ =>
raise WiteError::InvalidFormat(
"unsupported import descriptor kind in remove-unused-types: " +
kind.to_string(),
)
}
}
Bytes::from_array(out[:])
}
///|
fn rewrite_instruction_function_indices_raise(
instr_bytes : Bytes,
spans : Array[InstrSpan],
index_map : Map[UInt, UInt],
) -> Bytes raise WiteError {
let out : Array[Byte] = []
for span in spans {
if span.opcode == 0x10U || span.opcode == 0xd2U {
if span.end_ <= span.start + 1 {
raise WiteError::InvalidFormat("invalid call/ref.func immediate")
}
let imm = instr_bytes[span.start + 1:span.end_].to_bytes()
let old_index = match decode_u32_leb128_bytes(imm) {
Some(v) => v
None =>
raise WiteError::InvalidFormat(
"failed to decode function index immediate",
)
}
let new_index = remap_function_index_raise(old_index, index_map)
out.push(span.opcode.to_byte())
out.append(encode_u32_leb128(new_index)[:])
} else {
out.append(instr_bytes[span.start:span.end_].to_array())
}
}
Bytes::from_array(out[:])
}
///|
fn rewrite_code_body_for_dce_raise(
body_bytes : Bytes,
index_map : Map[UInt, UInt],
) -> Bytes raise WiteError {
let prefix_end = parse_local_decl_prefix_end(body_bytes)
let instr_bytes = body_bytes[prefix_end:body_bytes.length()].to_bytes()
let spans = match parse_instruction_spans(instr_bytes) {
Some(v) => v
None => raise WiteError::InvalidFormat("unsupported opcode in dce apply")
}
let rewritten_instr = rewrite_instruction_function_indices_raise(
instr_bytes, spans, index_map,
)
let out : Array[Byte] = body_bytes[0:prefix_end].to_array()
out.append(rewritten_instr[:].to_array())
Bytes::from_array(out[:])
}
///|
fn rewrite_code_section_payload_for_dce_raise(
payload : Bytes,
keep_local : Array[Bool],
index_map : Map[UInt, UInt],
) -> Bytes raise WiteError {
let bodies = parse_code_bodies(payload)
if bodies.length() != keep_local.length() {
raise WiteError::InvalidFormat(
"dce code section length mismatch: code=" +
bodies.length().to_string() +
" keep=" +
keep_local.length().to_string(),
)
}
let kept_bodies : Array[Bytes] = []
for i in 0.. (UInt, UInt)? {
let parser = Cursor::new(imm)
let result = try? {
let type_index = parser.read_u32_leb128()
let table_index = parser.read_u32_leb128()
if not(parser.eof()) {
raise WiteError::InvalidFormat(
"call_indirect immediate has trailing bytes",
)
}
(type_index, table_index)
}
match result {
Ok(v) => Some(v)
Err(_) => None
}
}
///|
fn collect_used_type_indices_from_instruction_raise(
instr_bytes : Bytes,
spans : Array[InstrSpan],
) -> Array[UInt] raise WiteError {
let out : Array[UInt] = []
for span in spans {
if span.opcode == 0x11U {
if span.end_ <= span.start + 1 {
raise WiteError::InvalidFormat("invalid call_indirect immediate")
}
let imm = instr_bytes[span.start + 1:span.end_].to_bytes()
match decode_call_indirect_immediate(imm) {
Some((type_index, _table_index)) => push_unique_u32(out, type_index)
None =>
raise WiteError::InvalidFormat(
"failed to decode call_indirect immediate",
)
}
}
}
out
}
///|
fn collect_used_type_indices_from_code_section_raise(
payload : Bytes,
) -> Array[UInt] raise WiteError {
let out : Array[UInt] = []
let bodies = parse_code_bodies(payload)
for body in bodies {
let prefix_end = parse_local_decl_prefix_end(body)
let instr_bytes = body[prefix_end:body.length()].to_bytes()
let spans = parse_instruction_spans_raise(instr_bytes)
for
type_index in collect_used_type_indices_from_instruction_raise(
instr_bytes, spans,
) {
push_unique_u32(out, type_index)
}
}
out
}
///|
fn rewrite_instruction_type_indices_raise(
instr_bytes : Bytes,
spans : Array[InstrSpan],
type_index_map : Map[UInt, UInt],
) -> Bytes raise WiteError {
let out : Array[Byte] = []
for span in spans {
if span.opcode == 0x11U {
if span.end_ <= span.start + 1 {
raise WiteError::InvalidFormat("invalid call_indirect immediate")
}
let imm = instr_bytes[span.start + 1:span.end_].to_bytes()
let (old_type_index, table_index) = match
decode_call_indirect_immediate(imm) {
Some(v) => v
None =>
raise WiteError::InvalidFormat(
"failed to decode call_indirect immediate",
)
}
out.push(0x11U.to_byte())
out.append(
encode_u32_leb128(
remap_type_index_raise(old_type_index, type_index_map),
)[:],
)
out.append(encode_u32_leb128(table_index)[:])
} else {
out.append(instr_bytes[span.start:span.end_].to_array())
}
}
Bytes::from_array(out[:])
}
///|
fn rewrite_code_body_for_type_index_map_raise(
body_bytes : Bytes,
type_index_map : Map[UInt, UInt],
) -> Bytes raise WiteError {
let prefix_end = parse_local_decl_prefix_end(body_bytes)
let instr_bytes = body_bytes[prefix_end:body_bytes.length()].to_bytes()
let spans = parse_instruction_spans_raise(instr_bytes)
let rewritten_instr = rewrite_instruction_type_indices_raise(
instr_bytes, spans, type_index_map,
)
let out : Array[Byte] = body_bytes[0:prefix_end].to_array()
out.append(rewritten_instr[:].to_array())
Bytes::from_array(out[:])
}
///|
fn rewrite_code_section_payload_for_type_index_map_raise(
payload : Bytes,
type_index_map : Map[UInt, UInt],
) -> Bytes raise WiteError {
let bodies = parse_code_bodies(payload)
let out : Array[Byte] = []
out.append(encode_u32_leb128(bodies.length().reinterpret_as_uint())[:])
for body in bodies {
let rewritten = rewrite_code_body_for_type_index_map_raise(
body, type_index_map,
)
out.append(encode_u32_leb128(rewritten.length().reinterpret_as_uint())[:])
out.append(rewritten[:].to_array())
}
Bytes::from_array(out[:])
}
///|
fn collect_used_param_indices_from_instruction_raise(
instr_bytes : Bytes,
spans : Array[InstrSpan],
param_count : UInt,
) -> Array[UInt] raise WiteError {
let out : Array[UInt] = []
for span in spans {
if span.opcode == 0x20U || span.opcode == 0x21U || span.opcode == 0x22U {
match decode_span_u32_immediate(instr_bytes, span) {
Some(local_index) =>
if local_index < param_count {
push_unique_u32(out, local_index)
}
None =>
raise WiteError::InvalidFormat(
"failed to decode local index immediate",
)
}
}
}
out
}
///|
fn compute_removed_trailing_param_count(
param_count : UInt,
used_param_indices : Array[UInt],
) -> UInt {
let mut keep_param_count = param_count
while keep_param_count > 0U {
let trailing = keep_param_count - 1U
if used_param_indices.contains(trailing) {
break
}
keep_param_count -= 1U
}
param_count - keep_param_count
}
///|
fn remap_local_index_for_signature_pruning_raise(
local_index : UInt,
old_param_count : UInt,
removed_param_count : UInt,
) -> UInt raise WiteError {
if removed_param_count == 0U {
return local_index
}
if old_param_count < removed_param_count {
raise WiteError::InvalidFormat(
"invalid signature-prune param count: old=" +
old_param_count.to_string() +
" removed=" +
removed_param_count.to_string(),
)
}
let kept_param_count = old_param_count - removed_param_count
if local_index < kept_param_count {
return local_index
}
if local_index < old_param_count {
raise WiteError::InvalidFormat(
"signature-prune touched removed param index: " + local_index.to_string(),
)
}
local_index - removed_param_count
}
///|
fn rewrite_instruction_local_indices_for_signature_pruning_raise(
instr_bytes : Bytes,
spans : Array[InstrSpan],
old_param_count : UInt,
removed_param_count : UInt,
) -> Bytes raise WiteError {
if removed_param_count == 0U {
return instr_bytes
}
let out : Array[Byte] = []
for span in spans {
if span.opcode == 0x20U || span.opcode == 0x21U || span.opcode == 0x22U {
if span.end_ <= span.start + 1 {
raise WiteError::InvalidFormat("invalid local immediate")
}
let imm = instr_bytes[span.start + 1:span.end_].to_bytes()
let local_index = match decode_u32_leb128_bytes(imm) {
Some(v) => v
None =>
raise WiteError::InvalidFormat(
"failed to decode local index immediate",
)
}
let remapped = remap_local_index_for_signature_pruning_raise(
local_index, old_param_count, removed_param_count,
)
out.push(span.opcode.to_byte())
out.append(encode_u32_leb128(remapped)[:])
} else {
out.append(instr_bytes[span.start:span.end_].to_array())
}
}
Bytes::from_array(out[:])
}
///|
fn rewrite_code_body_for_signature_pruning_raise(
body_bytes : Bytes,
old_param_count : UInt,
removed_param_count : UInt,
) -> Bytes raise WiteError {
if removed_param_count == 0U {
return body_bytes
}
let prefix_end = parse_local_decl_prefix_end(body_bytes)
let instr_bytes = body_bytes[prefix_end:body_bytes.length()].to_bytes()
let spans = parse_instruction_spans_raise(instr_bytes)
let rewritten_instr = rewrite_instruction_local_indices_for_signature_pruning_raise(
instr_bytes, spans, old_param_count, removed_param_count,
)
let out : Array[Byte] = body_bytes[0:prefix_end].to_array()
out.append(rewritten_instr[:].to_array())
Bytes::from_array(out[:])
}
///|
fn rewrite_instruction_calls_for_signature_pruning_raise(
instr_bytes : Bytes,
spans : Array[InstrSpan],
removed_param_count_by_function : Map[UInt, UInt],
) -> Bytes raise WiteError {
let out : Array[Byte] = []
for span in spans {
if span.opcode == 0x10U {
if span.end_ <= span.start + 1 {
raise WiteError::InvalidFormat("invalid call immediate")
}
let imm = instr_bytes[span.start + 1:span.end_].to_bytes()
let target = match decode_u32_leb128_bytes(imm) {
Some(v) => v
None =>
raise WiteError::InvalidFormat("failed to decode call immediate")
}
let removed_param_count = match
removed_param_count_by_function.get(target) {
Some(v) => v
None => 0U
}
for _ in 0U.. Bytes raise WiteError {
let prefix_end = parse_local_decl_prefix_end(body_bytes)
let instr_bytes = body_bytes[prefix_end:body_bytes.length()].to_bytes()
let spans = parse_instruction_spans_raise(instr_bytes)
let rewritten_instr = rewrite_instruction_calls_for_signature_pruning_raise(
instr_bytes, spans, removed_param_count_by_function,
)
let out : Array[Byte] = body_bytes[0:prefix_end].to_array()
out.append(rewritten_instr[:].to_array())
Bytes::from_array(out[:])
}
///|
fn is_single_val_type(val_type : Bytes, expected_code : UInt) -> Bool {
val_type.length() == 1 && val_type[0].to_uint() == expected_code
}
///|
fn is_const_opcode_compatible_with_result(
opcode : UInt,
result_type : Bytes,
) -> Bool {
match opcode {
0x41U => is_single_val_type(result_type, 0x7fU)
0x42U => is_single_val_type(result_type, 0x7eU)
0x43U => is_single_val_type(result_type, 0x7dU)
0x44U => is_single_val_type(result_type, 0x7cU)
_ => false
}
}
///|
fn extract_inlining_replacement_instr_raise(
body_bytes : Bytes,
type_entry : FunctionTypeEntry,
) -> Bytes? raise WiteError {
let prefix = parse_local_decl_prefix_raise(body_bytes)
if prefix.local_types.length() > 0 {
return None
}
let instr_bytes = body_bytes[prefix.prefix_end:body_bytes.length()].to_bytes()
let spans = parse_instruction_spans_raise(instr_bytes)
if type_entry.params.length() == 0 &&
type_entry.results.length() == 0 &&
spans.length() == 1 &&
spans[0].opcode == 0x0bU {
return Some(Bytes::from_array([]))
}
if spans.length() == 2 && spans[1].opcode == 0x0bU {
if type_entry.params.length() == 1 &&
type_entry.results.length() == 1 &&
bytes_equal(type_entry.params[0], type_entry.results[0]) &&
spans[0].opcode == 0x20U &&
decode_local_index_immediate_from_span(instr_bytes, spans[0]) == Some(0U) {
return Some(Bytes::from_array([]))
}
if type_entry.params.length() == 0 &&
type_entry.results.length() == 1 &&
is_const_opcode_compatible_with_result(
spans[0].opcode,
type_entry.results[0],
) {
return Some(instr_bytes[spans[0].start:spans[0].end_].to_bytes())
}
}
None
}
///|
fn rewrite_instruction_calls_for_inlining_raise(
instr_bytes : Bytes,
spans : Array[InstrSpan],
replacement_by_function_index : Map[UInt, Bytes],
) -> (Bytes, UInt) raise WiteError {
let out : Array[Byte] = []
let mut rewritten_call_count = 0U
for span in spans {
if span.opcode == 0x10U {
if span.end_ <= span.start + 1 {
raise WiteError::InvalidFormat("invalid call immediate in inlining")
}
let imm = instr_bytes[span.start + 1:span.end_].to_bytes()
let target = match decode_u32_leb128_bytes(imm) {
Some(v) => v
None =>
raise WiteError::InvalidFormat(
"failed to decode call immediate in inlining",
)
}
match replacement_by_function_index.get(target) {
Some(replacement) => {
let call_span_len = (span.end_ - span.start).reinterpret_as_uint()
let replacement_len = replacement.length().reinterpret_as_uint()
if replacement_len <= call_span_len {
out.append(replacement[:].to_array())
rewritten_call_count += 1U
} else {
out.append(instr_bytes[span.start:span.end_].to_array())
}
}
None => out.append(instr_bytes[span.start:span.end_].to_array())
}
} else {
out.append(instr_bytes[span.start:span.end_].to_array())
}
}
(Bytes::from_array(out[:]), rewritten_call_count)
}
///|
priv struct InliningApplyResult {
bytes : Bytes
inlined_call_count : UInt
}
///|
fn apply_inlining_optimizing(bytes : Bytes) -> InliningApplyResult {
let result = try? apply_inlining_optimizing_raise(bytes)
match result {
Ok(v) => v
Err(_) => { bytes, inlined_call_count: 0U }
}
}
///|
fn apply_inlining_optimizing_raise(
bytes : Bytes,
) -> InliningApplyResult raise WiteError {
let sections = parse_core_sections_raise(bytes)
let mut type_section_payload : Bytes? = None
let mut function_section_payload : Bytes? = None
let mut code_section_payload : Bytes? = None
let mut imported_function_count = 0U
for section in sections {
let payload = bytes[section.payload_start:section.section_end].to_bytes()
match section.section_id {
1U => type_section_payload = Some(payload)
2U => imported_function_count += parse_import_function_count(payload)
3U => function_section_payload = Some(payload)
10U => code_section_payload = Some(payload)
_ => ()
}
}
let type_payload = match type_section_payload {
Some(v) => v
None => return { bytes, inlined_call_count: 0U }
}
let function_payload = match function_section_payload {
Some(v) => v
None => return { bytes, inlined_call_count: 0U }
}
let code_payload = match code_section_payload {
Some(v) => v
None => return { bytes, inlined_call_count: 0U }
}
let type_entries = parse_function_type_entries_raise(type_payload, "inlining")
let function_type_indices = parse_function_type_indices(function_payload)
let code_bodies = parse_code_bodies(code_payload)
if function_type_indices.length() != code_bodies.length() {
raise WiteError::InvalidFormat(
"function/code section count mismatch for inlining: function=" +
function_type_indices.length().to_string() +
" code=" +
code_bodies.length().to_string(),
)
}
let type_count_u = type_entries.length().reinterpret_as_uint()
let replacement_by_function_index : Map[UInt, Bytes] = {}
for i in 0..= type_count_u {
raise WiteError::InvalidFormat(
"type index out of range in inlining: " + type_index.to_string(),
)
}
let type_pos = UInt::reinterpret_as_int(type_index)
if type_pos < 0 || type_pos >= type_entries.length() {
raise WiteError::InvalidFormat(
"type index position out of range in inlining: " +
type_index.to_string(),
)
}
match
extract_inlining_replacement_instr_raise(
code_bodies[i],
type_entries[type_pos],
) {
Some(replacement) =>
replacement_by_function_index.set(
imported_function_count + i.reinterpret_as_uint(),
replacement,
)
None => ()
}
}
let mut has_candidate = false
replacement_by_function_index.each((_, _) => has_candidate = true)
if not(has_candidate) {
return { bytes, inlined_call_count: 0U }
}
let rewritten_bodies : Array[Bytes] = []
let mut inlined_call_count = 0U
for body in code_bodies {
let prefix_end = parse_local_decl_prefix_end(body)
let instr_bytes = body[prefix_end:body.length()].to_bytes()
let spans = parse_instruction_spans_raise(instr_bytes)
let (rewritten_instr, rewritten_call_count) = rewrite_instruction_calls_for_inlining_raise(
instr_bytes, spans, replacement_by_function_index,
)
inlined_call_count += rewritten_call_count
if rewritten_call_count > 0U {
let rewritten_body : Array[Byte] = body[0:prefix_end].to_array()
rewritten_body.append(rewritten_instr[:].to_array())
rewritten_bodies.push(Bytes::from_array(rewritten_body[:]))
} else {
rewritten_bodies.push(body)
}
}
if inlined_call_count == 0U {
return { bytes, inlined_call_count: 0U }
}
let rewritten_code_payload = encode_code_section_payload_from_bodies(
rewritten_bodies,
)
let out : Array[Byte] = bytes[0:8].to_array()
for section in sections {
if section.section_id == 10U {
append_encoded_section(out, 10U, rewritten_code_payload)
} else {
out.append(bytes[section.section_start:section.section_end].to_array())
}
}
{ bytes: Bytes::from_array(out[:]), inlined_call_count }
}
///|
fn is_dae_pure_producer_opcode(opcode : UInt) -> Bool {
is_drop_elidable_producer_opcode(opcode)
}
///|
fn is_dae_pure_i32_unary_opcode(opcode : UInt) -> Bool {
opcode == 0x45U || opcode == 0x67U || opcode == 0x68U || opcode == 0x69U
}
///|
fn is_dae_pure_i32_binary_opcode(opcode : UInt) -> Bool {
opcode == 0x46U ||
opcode == 0x47U ||
opcode == 0x48U ||
opcode == 0x49U ||
opcode == 0x4aU ||
opcode == 0x4bU ||
opcode == 0x4cU ||
opcode == 0x4dU ||
opcode == 0x4eU ||
opcode == 0x4fU ||
opcode == 0x50U ||
opcode == 0x51U ||
opcode == 0x52U ||
opcode == 0x53U ||
opcode == 0x54U ||
opcode == 0x55U ||
opcode == 0x56U ||
opcode == 0x57U ||
opcode == 0x58U ||
opcode == 0x59U ||
opcode == 0x5aU ||
opcode == 0x5bU ||
opcode == 0x5cU ||
opcode == 0x5dU ||
opcode == 0x5eU ||
opcode == 0x5fU ||
opcode == 0x60U ||
opcode == 0x61U ||
opcode == 0x62U ||
opcode == 0x63U ||
opcode == 0x64U ||
opcode == 0x65U ||
opcode == 0x66U ||
opcode == 0x6aU ||
opcode == 0x6bU ||
opcode == 0x6cU ||
opcode == 0x71U ||
opcode == 0x72U ||
opcode == 0x73U ||
opcode == 0x74U ||
opcode == 0x75U ||
opcode == 0x76U ||
opcode == 0x77U ||
opcode == 0x78U
}
///|
fn rewrite_instruction_for_dae_raise(
instr_bytes : Bytes,
spans : Array[InstrSpan],
) -> (Bytes, UInt) {
let out : Array[Byte] = []
let mut removed_instr_count = 0U
let mut i = 0
while i < spans.length() {
if i + 3 < spans.length() &&
is_dae_pure_producer_opcode(spans[i].opcode) &&
is_dae_pure_producer_opcode(spans[i + 1].opcode) &&
is_dae_pure_i32_binary_opcode(spans[i + 2].opcode) &&
spans[i + 3].opcode == 0x1aU {
removed_instr_count += 4U
i += 4
continue
}
if i + 2 < spans.length() &&
is_dae_pure_producer_opcode(spans[i].opcode) &&
is_dae_pure_i32_unary_opcode(spans[i + 1].opcode) &&
spans[i + 2].opcode == 0x1aU {
removed_instr_count += 3U
i += 3
continue
}
if i + 1 < spans.length() &&
is_dae_pure_producer_opcode(spans[i].opcode) &&
spans[i + 1].opcode == 0x1aU {
removed_instr_count += 2U
i += 2
continue
}
out.append(instr_bytes[spans[i].start:spans[i].end_].to_array())
i += 1
}
(Bytes::from_array(out[:]), removed_instr_count)
}
///|
fn apply_dae_instruction_bytes_raise(
instr_bytes : Bytes,
) -> (Bytes, UInt) raise WiteError {
let mut current = instr_bytes
let mut removed_instr_count = 0U
let mut rounds = 0
while rounds < 16 {
let spans = parse_instruction_spans_raise(current)
let (rewritten, removed) = rewrite_instruction_for_dae_raise(current, spans)
if removed == 0U {
break
}
current = rewritten
removed_instr_count += removed
rounds += 1
}
(current, removed_instr_count)
}
///|
priv struct DaeApplyResult {
bytes : Bytes
removed_instr_count : UInt
}
///|
fn apply_dae_optimizing(bytes : Bytes) -> DaeApplyResult {
let result = try? apply_dae_optimizing_raise(bytes)
match result {
Ok(v) => v
Err(_) => { bytes, removed_instr_count: 0U }
}
}
///|
fn apply_dae_optimizing_raise(bytes : Bytes) -> DaeApplyResult raise WiteError {
let sections = parse_core_sections_raise(bytes)
let mut code_section_payload : Bytes? = None
for section in sections {
if section.section_id == 10U {
code_section_payload = Some(
bytes[section.payload_start:section.section_end].to_bytes(),
)
break
}
}
let code_payload = match code_section_payload {
Some(v) => v
None => return { bytes, removed_instr_count: 0U }
}
let code_bodies = parse_code_bodies(code_payload)
let rewritten_bodies : Array[Bytes] = []
let mut removed_instr_count = 0U
for body in code_bodies {
let prefix_end = parse_local_decl_prefix_end(body)
let instr_bytes = body[prefix_end:body.length()].to_bytes()
let (rewritten_instr, removed) = apply_dae_instruction_bytes_raise(
instr_bytes,
)
removed_instr_count += removed
if removed > 0U {
let rewritten_body : Array[Byte] = body[0:prefix_end].to_array()
rewritten_body.append(rewritten_instr[:].to_array())
rewritten_bodies.push(Bytes::from_array(rewritten_body[:]))
} else {
rewritten_bodies.push(body)
}
}
if removed_instr_count == 0U {
return { bytes, removed_instr_count: 0U }
}
let rewritten_code_payload = encode_code_section_payload_from_bodies(
rewritten_bodies,
)
let out : Array[Byte] = bytes[0:8].to_array()
for section in sections {
if section.section_id == 10U {
append_encoded_section(out, 10U, rewritten_code_payload)
} else {
out.append(bytes[section.section_start:section.section_end].to_array())
}
}
{ bytes: Bytes::from_array(out[:]), removed_instr_count }
}
///|
fn is_const_opcode(opcode : UInt) -> Bool {
opcode == 0x41U || opcode == 0x42U || opcode == 0x43U || opcode == 0x44U
}
///|
fn is_drop_elidable_producer_opcode(opcode : UInt) -> Bool {
is_const_opcode(opcode) ||
opcode == 0x20U ||
opcode == 0x23U ||
opcode == 0xd0U ||
opcode == 0xd2U
}
///|
fn is_drop_elidable_producer_span(
instr_bytes : Bytes,
span : InstrSpan,
) -> Bool {
if is_drop_elidable_producer_opcode(span.opcode) || span.opcode == 0x3fU {
return true
}
if span.opcode == 0xfcU {
match parse_prefixed_opcode_subopcode(instr_bytes, span) {
Some(16U) => return true
_ => ()
}
}
false
}
///|
fn is_block_start_opcode(opcode : UInt) -> Bool {
opcode == 0x02U || opcode == 0x03U || opcode == 0x04U
}
///|
fn is_branch_opcode(opcode : UInt) -> Bool {
opcode == 0x0cU || opcode == 0x0dU || opcode == 0x0eU
}
///|
fn decode_span_u32_immediate(instr_bytes : Bytes, span : InstrSpan) -> UInt? {
decode_u32_leb128_range(instr_bytes, span.start + 1, span.end_)
}
///|
fn decode_nonnegative_i32_leb128_bytes(bytes : Bytes) -> UInt? {
if bytes.length() == 0 || bytes.length() > 5 {
return None
}
let last = bytes[bytes.length() - 1].to_uint()
if (last & 0x80U) != 0U {
return None
}
if (last & 0x40U) != 0U {
return None
}
decode_u32_leb128_bytes(bytes)
}
///|
fn decode_i32_const_nonnegative_from_span(
instr_bytes : Bytes,
span : InstrSpan,
) -> UInt? {
if span.opcode != 0x41U || span.end_ <= span.start + 1 {
return None
}
let imm = instr_bytes[span.start + 1:span.end_].to_bytes()
decode_nonnegative_i32_leb128_bytes(imm)
}
///|
fn decode_i32_const_small_nonnegative_from_span(
instr_bytes : Bytes,
span : InstrSpan,
) -> UInt? {
if span.end_ != span.start + 2 {
return None
}
match decode_i32_const_nonnegative_from_span(instr_bytes, span) {
Some(value) => if value <= 63U { Some(value) } else { None }
None => None
}
}
///|
fn decode_i32_const_small_signed_from_span(
instr_bytes : Bytes,
span : InstrSpan,
) -> Int? {
if span.opcode != 0x41U || span.end_ != span.start + 2 {
return None
}
let raw = instr_bytes[span.start + 1].to_uint()
if (raw & 0x80U) != 0U {
return None
}
if (raw & 0x40U) == 0U {
Some(raw.reinterpret_as_int())
} else {
Some(raw.reinterpret_as_int() - 128)
}
}
///|
fn decode_local_index_immediate_from_span(
instr_bytes : Bytes,
span : InstrSpan,
) -> UInt? {
if span.opcode != 0x20U && span.opcode != 0x21U && span.opcode != 0x22U {
return None
}
decode_span_u32_immediate(instr_bytes, span)
}
///|
fn instr_spans_to_bytes(instr_bytes : Bytes, spans : Array[InstrSpan]) -> Bytes {
let out : Array[Byte] = []
for span in spans {
out.append(instr_bytes[span.start:span.end_].to_array())
}
Bytes::from_array(out[:])
}
///|
fn append_i32_const_small(out : Array[Byte], value : UInt) -> Unit {
out.push(0x41U.to_byte())
out.push(value.to_byte())
}
///|
fn is_i32_shift_rotate_opcode(opcode : UInt) -> Bool {
opcode == 0x74U || // i32.shl
opcode == 0x75U || // i32.shr_s
opcode == 0x76U || // i32.shr_u
opcode == 0x77U || // i32.rotl
opcode == 0x78U // i32.rotr
}
///|
fn is_optimize_instructions_i32_identity_opcode(
rhs_const : UInt,
opcode : UInt,
) -> Bool {
if is_i32_shift_rotate_opcode(opcode) && rhs_const % 32U == 0U {
return true
}
match rhs_const {
0U =>
opcode == 0x6aU || // i32.add
opcode == 0x6bU || // i32.sub
opcode == 0x72U || // i32.or
opcode == 0x73U || // i32.xor
is_i32_shift_rotate_opcode(opcode)
1U => opcode == 0x6cU // i32.mul
_ => false
}
}
///|
fn apply_optimize_instructions_i32_identity_ops(instr_bytes : Bytes) -> Bytes {
let spans = match parse_instruction_spans(instr_bytes) {
Some(v) => v
None => return instr_bytes
}
let out : Array[Byte] = []
let mut i = 0
while i < spans.length() {
if i + 1 < spans.length() && spans[i].opcode == 0x41U {
match decode_i32_const_small_signed_from_span(instr_bytes, spans[i]) {
Some(-1) =>
if spans[i + 1].opcode == 0x71U {
i += 2
continue
}
_ => ()
}
}
if i + 1 < spans.length() && spans[i].opcode == 0x41U {
match
decode_i32_const_small_nonnegative_from_span(instr_bytes, spans[i]) {
Some(rhs_const) =>
if is_optimize_instructions_i32_identity_opcode(
rhs_const,
spans[i + 1].opcode,
) {
i += 2
continue
}
None => ()
}
}
out.append(instr_bytes[spans[i].start:spans[i].end_].to_array())
i += 1
}
Bytes::from_array(out[:])
}
///|
fn is_optimize_instructions_i32_get_opcode(opcode : UInt) -> Bool {
opcode == 0x20U || opcode == 0x23U
}
///|
fn apply_optimize_instructions_i32_const_cmp_bitwise(
instr_bytes : Bytes,
) -> Bytes {
let spans = match parse_instruction_spans(instr_bytes) {
Some(v) => v
None => return instr_bytes
}
let out : Array[Byte] = []
let mut i = 0
while i < spans.length() {
if i + 2 < spans.length() &&
is_optimize_instructions_i32_get_opcode(spans[i].opcode) &&
spans[i + 1].opcode == 0x41U {
let lhs = instr_bytes[spans[i].start:spans[i].end_]
match
decode_i32_const_small_nonnegative_from_span(instr_bytes, spans[i + 1]) {
Some(0U) =>
match spans[i + 2].opcode {
0x71U
// i32.and
| 0x49U => { // i32.lt_u
append_i32_const_small(out, 0U)
i += 3
continue
}
0x4fU => { // i32.ge_u
append_i32_const_small(out, 1U)
i += 3
continue
}
0x46U | 0x4dU => { // i32.eq / i32.le_u => i32.eqz
out.append(lhs.to_array())
out.push(0x45U.to_byte())
i += 3
continue
}
0x47U | 0x4bU => { // i32.ne / i32.gt_u => i32.eqz; i32.eqz
out.append(lhs.to_array())
out.push(0x45U.to_byte())
out.push(0x45U.to_byte())
i += 3
continue
}
_ => ()
}
_ => ()
}
match decode_i32_const_small_signed_from_span(instr_bytes, spans[i + 1]) {
Some(-1) =>
if spans[i + 2].opcode == 0x71U {
// x & -1 => x
out.append(lhs.to_array())
i += 3
continue
}
_ => ()
}
}
if i + 2 < spans.length() &&
spans[i].opcode == 0x41U &&
is_optimize_instructions_i32_get_opcode(spans[i + 1].opcode) {
let rhs = instr_bytes[spans[i + 1].start:spans[i + 1].end_]
match
decode_i32_const_small_nonnegative_from_span(instr_bytes, spans[i]) {
Some(0U) =>
match spans[i + 2].opcode {
0x71U
// i32.and
| 0x6cU
// i32.mul
| 0x74U
// i32.shl
| 0x75U
// i32.shr_s
| 0x76U
// i32.shr_u
| 0x77U
// i32.rotl
| 0x78U => { // i32.rotr
append_i32_const_small(out, 0U)
i += 3
continue
}
0x6aU | 0x72U | 0x73U => {
// 0 + x / 0 | x / 0 ^ x => x
out.append(rhs.to_array())
i += 3
continue
}
0x46U => { // 0 == x => x.eqz
out.append(rhs.to_array())
out.push(0x45U.to_byte())
i += 3
continue
}
0x47U => { // 0 != x => x.eqz; i32.eqz
out.append(rhs.to_array())
out.push(0x45U.to_byte())
out.push(0x45U.to_byte())
i += 3
continue
}
_ => ()
}
Some(1U) =>
if spans[i + 2].opcode == 0x6cU {
// 1 * x => x
out.append(rhs.to_array())
i += 3
continue
}
_ => ()
}
match decode_i32_const_small_signed_from_span(instr_bytes, spans[i]) {
Some(-1) =>
match spans[i + 2].opcode {
0x71U => {
// -1 & x => x
out.append(rhs.to_array())
i += 3
continue
}
0x72U => {
// -1 | x => -1
out.append(instr_bytes[spans[i].start:spans[i].end_].to_array())
i += 3
continue
}
_ => ()
}
_ => ()
}
}
out.append(instr_bytes[spans[i].start:spans[i].end_].to_array())
i += 1
}
Bytes::from_array(out[:])
}
///|
fn apply_optimize_instructions_same_local_get_pair(
instr_bytes : Bytes,
) -> Bytes {
let spans = match parse_instruction_spans(instr_bytes) {
Some(v) => v
None => return instr_bytes
}
let out : Array[Byte] = []
let mut i = 0
while i < spans.length() {
if i + 2 < spans.length() &&
spans[i].opcode == 0x20U &&
spans[i + 1].opcode == 0x20U {
match decode_local_index_immediate_from_span(instr_bytes, spans[i]) {
Some(first_local) =>
match
decode_local_index_immediate_from_span(instr_bytes, spans[i + 1]) {
Some(second_local) =>
if first_local == second_local {
match spans[i + 2].opcode {
0x71U | 0x72U => {
// local.get x; local.get x; i32.and/or => local.get x
out.append(
instr_bytes[spans[i].start:spans[i].end_].to_array(),
)
i += 3
continue
}
0x46U | 0x4cU | 0x4dU | 0x4eU | 0x4fU => {
// x==x / x<=x / x>=x => 1
append_i32_const_small(out, 1U)
i += 3
continue
}
0x47U | 0x48U | 0x49U | 0x4aU | 0x4bU | 0x6bU | 0x73U => {
// x!=x / xx / x-x / x^x => 0
append_i32_const_small(out, 0U)
i += 3
continue
}
_ => ()
}
}
None => ()
}
None => ()
}
}
out.append(instr_bytes[spans[i].start:spans[i].end_].to_array())
i += 1
}
Bytes::from_array(out[:])
}
///|
fn apply_precompute_i32_add(instr_bytes : Bytes) -> Bytes {
let spans = match parse_instruction_spans(instr_bytes) {
Some(v) => v
None => return instr_bytes
}
let out : Array[Byte] = []
let mut i = 0
while i < spans.length() {
if i + 2 < spans.length() &&
spans[i].opcode == 0x41U &&
spans[i + 1].opcode == 0x41U &&
spans[i + 2].opcode == 0x6aU {
match
decode_i32_const_small_nonnegative_from_span(instr_bytes, spans[i]) {
Some(lhs) =>
match
decode_i32_const_small_nonnegative_from_span(
instr_bytes,
spans[i + 1],
) {
Some(rhs) => {
let sum = lhs + rhs
if sum <= 63U {
out.push(0x41U.to_byte())
out.push(sum.to_byte())
i += 3
continue
}
}
None => ()
}
None => ()
}
}
out.append(instr_bytes[spans[i].start:spans[i].end_].to_array())
i += 1
}
Bytes::from_array(out[:])
}
///|
fn apply_precompute_i32_identity_ops(instr_bytes : Bytes) -> Bytes {
apply_optimize_instructions_i32_identity_ops(instr_bytes)
}
///|
fn apply_precompute_i32_eqz_const(instr_bytes : Bytes) -> Bytes {
let spans = match parse_instruction_spans(instr_bytes) {
Some(v) => v
None => return instr_bytes
}
let out : Array[Byte] = []
let mut i = 0
while i < spans.length() {
if i + 1 < spans.length() &&
spans[i].opcode == 0x41U &&
spans[i + 1].opcode == 0x45U {
match
decode_i32_const_small_nonnegative_from_span(instr_bytes, spans[i]) {
Some(value) => {
append_i32_const_small(out, if value == 0U { 1U } else { 0U })
i += 2
continue
}
None => ()
}
}
out.append(instr_bytes[spans[i].start:spans[i].end_].to_array())
i += 1
}
Bytes::from_array(out[:])
}
///|
fn apply_precompute_i32_double_eqz_br_if(instr_bytes : Bytes) -> Bytes {
let spans = match parse_instruction_spans(instr_bytes) {
Some(v) => v
None => return instr_bytes
}
let out : Array[Byte] = []
let mut i = 0
while i < spans.length() {
if i + 2 < spans.length() &&
spans[i].opcode == 0x45U &&
spans[i + 1].opcode == 0x45U &&
spans[i + 2].opcode == 0x0dU {
out.append(instr_bytes[spans[i + 2].start:spans[i + 2].end_].to_array())
i += 3
continue
}
out.append(instr_bytes[spans[i].start:spans[i].end_].to_array())
i += 1
}
Bytes::from_array(out[:])
}
///|
fn is_local_const_propagation_barrier(opcode : UInt) -> Bool {
opcode == 0x00U || // unreachable
opcode == 0x02U || // block
opcode == 0x03U || // loop
opcode == 0x04U || // if
opcode == 0x05U || // else
opcode == 0x0bU || // end
opcode == 0x0cU || // br
opcode == 0x0dU || // br_if
opcode == 0x0eU || // br_table
opcode == 0x0fU
}
///|
fn known_local_const_get(known : Map[UInt, UInt], local_index : UInt) -> UInt? {
match known.get(local_index) {
Some(value) => if value == 0xffffffffU { None } else { Some(value) }
None => None
}
}
///|
fn apply_precompute_local_const_propagation(instr_bytes : Bytes) -> Bytes {
let spans = match parse_instruction_spans(instr_bytes) {
Some(v) => v
None => return instr_bytes
}
let out : Array[Byte] = []
let mut known_local_consts : Map[UInt, UInt] = {}
for i in 0.. {
let mut adjacent_set_same_local = false
if i > 0 && spans[i - 1].opcode == 0x21U {
match
decode_local_index_immediate_from_span(instr_bytes, spans[i - 1]) {
Some(prev_set_index) =>
if prev_set_index == local_index {
adjacent_set_same_local = true
}
None => ()
}
}
if not(adjacent_set_same_local) {
match known_local_const_get(known_local_consts, local_index) {
Some(value) => {
append_i32_const_small(out, value)
if is_local_const_propagation_barrier(span.opcode) {
known_local_consts = {}
}
continue
}
None => ()
}
}
}
None => ()
}
} else if span.opcode == 0x21U || span.opcode == 0x22U {
match decode_local_index_immediate_from_span(instr_bytes, span) {
Some(local_index) => {
let mut assigned_const : UInt? = None
if i > 0 {
assigned_const = decode_i32_const_small_nonnegative_from_span(
instr_bytes,
spans[i - 1],
)
}
match assigned_const {
Some(value) => known_local_consts.set(local_index, value)
None => known_local_consts.set(local_index, 0xffffffffU)
}
}
None => ()
}
}
out.append(instr_bytes[span.start:span.end_].to_array())
if is_local_const_propagation_barrier(span.opcode) {
known_local_consts = {}
}
}
Bytes::from_array(out[:])
}
///|
fn apply_precompute_br_if_const(instr_bytes : Bytes) -> Bytes {
let spans = match parse_instruction_spans(instr_bytes) {
Some(v) => v
None => return instr_bytes
}
let out : Array[Byte] = []
let mut i = 0
while i < spans.length() {
if i + 1 < spans.length() &&
spans[i].opcode == 0x41U &&
spans[i + 1].opcode == 0x0dU {
match
decode_i32_const_small_nonnegative_from_span(instr_bytes, spans[i]) {
Some(0U) => {
i += 2
continue
}
Some(_) =>
if spans[i + 1].end_ > spans[i + 1].start + 1 {
out.push(0x0cU.to_byte())
out.append(
instr_bytes[spans[i + 1].start + 1:spans[i + 1].end_].to_array(),
)
i += 2
continue
}
None => ()
}
}
out.append(instr_bytes[spans[i].start:spans[i].end_].to_array())
i += 1
}
Bytes::from_array(out[:])
}
///|
fn apply_simplify_local_set_get_to_tee(instr_bytes : Bytes) -> Bytes {
let spans = match parse_instruction_spans(instr_bytes) {
Some(v) => v
None => return instr_bytes
}
let out : Array[Byte] = []
let mut i = 0
while i < spans.length() {
if i + 1 < spans.length() &&
spans[i].opcode == 0x21U &&
spans[i + 1].opcode == 0x20U {
match decode_local_index_immediate_from_span(instr_bytes, spans[i]) {
Some(set_index) =>
match
decode_local_index_immediate_from_span(instr_bytes, spans[i + 1]) {
Some(get_index) =>
if set_index == get_index && spans[i].end_ > spans[i].start + 1 {
out.push(0x22U.to_byte())
out.append(
instr_bytes[spans[i].start + 1:spans[i].end_].to_array(),
)
i += 2
continue
}
None => ()
}
None => ()
}
}
out.append(instr_bytes[spans[i].start:spans[i].end_].to_array())
i += 1
}
Bytes::from_array(out[:])
}
///|
fn apply_simplify_local_get_set_noop(instr_bytes : Bytes) -> Bytes {
let spans = match parse_instruction_spans(instr_bytes) {
Some(v) => v
None => return instr_bytes
}
let out : Array[Byte] = []
let mut i = 0
while i < spans.length() {
if i + 1 < spans.length() &&
spans[i].opcode == 0x20U &&
spans[i + 1].opcode == 0x21U {
match decode_local_index_immediate_from_span(instr_bytes, spans[i]) {
Some(get_index) =>
match
decode_local_index_immediate_from_span(instr_bytes, spans[i + 1]) {
Some(set_index) =>
if get_index == set_index {
i += 2
continue
}
None => ()
}
None => ()
}
}
out.append(instr_bytes[spans[i].start:spans[i].end_].to_array())
i += 1
}
Bytes::from_array(out[:])
}
///|
fn apply_simplify_local_get_tee_to_get(instr_bytes : Bytes) -> Bytes {
let spans = match parse_instruction_spans(instr_bytes) {
Some(v) => v
None => return instr_bytes
}
let out : Array[Byte] = []
let mut i = 0
while i < spans.length() {
if i + 1 < spans.length() &&
spans[i].opcode == 0x20U &&
spans[i + 1].opcode == 0x22U {
match decode_local_index_immediate_from_span(instr_bytes, spans[i]) {
Some(get_index) =>
match
decode_local_index_immediate_from_span(instr_bytes, spans[i + 1]) {
Some(tee_index) =>
if get_index == tee_index {
out.append(instr_bytes[spans[i].start:spans[i].end_].to_array())
i += 2
continue
}
None => ()
}
None => ()
}
}
out.append(instr_bytes[spans[i].start:spans[i].end_].to_array())
i += 1
}
Bytes::from_array(out[:])
}
///|
fn apply_local_cse_get_set_get(instr_bytes : Bytes) -> Bytes {
let spans = match parse_instruction_spans(instr_bytes) {
Some(v) => v
None => return instr_bytes
}
let out : Array[Byte] = []
let mut i = 0
while i < spans.length() {
if i + 2 < spans.length() &&
spans[i].opcode == 0x20U &&
spans[i + 1].opcode == 0x21U &&
spans[i + 2].opcode == 0x20U {
match decode_local_index_immediate_from_span(instr_bytes, spans[i]) {
Some(first_get_index) =>
match
decode_local_index_immediate_from_span(instr_bytes, spans[i + 2]) {
Some(second_get_index) =>
if first_get_index == second_get_index &&
spans[i + 1].end_ > spans[i + 1].start + 1 {
out.append(instr_bytes[spans[i].start:spans[i].end_].to_array())
out.push(0x22U.to_byte())
out.append(
instr_bytes[spans[i + 1].start + 1:spans[i + 1].end_].to_array(),
)
i += 3
continue
}
None => ()
}
None => ()
}
}
out.append(instr_bytes[spans[i].start:spans[i].end_].to_array())
i += 1
}
Bytes::from_array(out[:])
}
///|
fn apply_simplify_local_tee_drop_to_set(instr_bytes : Bytes) -> Bytes {
let spans = match parse_instruction_spans(instr_bytes) {
Some(v) => v
None => return instr_bytes
}
let out : Array[Byte] = []
let mut i = 0
while i < spans.length() {
if i + 1 < spans.length() &&
spans[i].opcode == 0x22U &&
spans[i + 1].opcode == 0x1aU &&
spans[i].end_ > spans[i].start + 1 {
out.push(0x21U.to_byte())
out.append(instr_bytes[spans[i].start + 1:spans[i].end_].to_array())
i += 2
continue
}
out.append(instr_bytes[spans[i].start:spans[i].end_].to_array())
i += 1
}
Bytes::from_array(out[:])
}
///|
fn apply_rse_local_tee_set_same(instr_bytes : Bytes) -> Bytes {
let spans = match parse_instruction_spans(instr_bytes) {
Some(v) => v
None => return instr_bytes
}
let out : Array[Byte] = []
let mut i = 0
while i < spans.length() {
if i + 1 < spans.length() &&
spans[i].opcode == 0x22U &&
spans[i + 1].opcode == 0x21U {
match decode_local_index_immediate_from_span(instr_bytes, spans[i]) {
Some(tee_index) =>
match
decode_local_index_immediate_from_span(instr_bytes, spans[i + 1]) {
Some(set_index) =>
if tee_index == set_index && spans[i].end_ > spans[i].start + 1 {
out.push(0x21U.to_byte())
out.append(
instr_bytes[spans[i].start + 1:spans[i].end_].to_array(),
)
i += 2
continue
}
None => ()
}
None => ()
}
}
out.append(instr_bytes[spans[i].start:spans[i].end_].to_array())
i += 1
}
Bytes::from_array(out[:])
}
///|
fn apply_basic_peephole_to_spans(
instr_bytes : Bytes,
spans : Array[InstrSpan],
config : OptimizeConfig,
) -> Array[InstrSpan] {
let out : Array[InstrSpan] = []
let mut i = 0
while i < spans.length() {
let span = spans[i]
if config.peephole_remove_nop && span.opcode == 0x01U {
i += 1
continue
}
if config.peephole_remove_const_drop &&
i + 1 < spans.length() &&
is_drop_elidable_producer_span(instr_bytes, span) &&
spans[i + 1].opcode == 0x1aU {
i += 2
continue
}
out.push(span)
i += 1
}
out
}
///|
fn apply_vacuum_to_spans(spans : Array[InstrSpan]) -> Array[InstrSpan] {
let out : Array[InstrSpan] = []
let mut dead = false
let mut dead_depth = 0
for span in spans {
if dead {
match span.opcode {
0x02U | 0x03U | 0x04U => dead_depth += 1
0x05U =>
if dead_depth == 0 {
out.push(span)
dead = false
}
0x0bU =>
if dead_depth == 0 {
out.push(span)
dead = false
} else {
dead_depth -= 1
}
_ => ()
}
continue
}
out.push(span)
if span.opcode == 0x00U || span.opcode == 0x0fU || span.opcode == 0x0cU {
dead = true
dead_depth = 0
}
}
out
}
///|
fn compute_matching_end_indices(spans : Array[InstrSpan]) -> Map[Int, Int] {
let out : Map[Int, Int] = {}
let stack : Array[Int] = []
for i in 0.. 0 {
let start = stack[stack.length() - 1]
ignore(stack.pop())
out.set(start, i)
}
}
out
}
///|
fn has_branch_between(
spans : Array[InstrSpan],
start : Int,
end_ : Int,
) -> Bool {
for i in start.. Array[InstrSpan] {
let matching_end = compute_matching_end_indices(spans)
let remove : Array[Bool] = []
for _ in 0..
if not(has_branch_between(spans, i + 1, end_index)) {
remove[i] = true
remove[end_index] = true
}
None => ()
}
}
}
let out : Array[InstrSpan] = []
for i in 0.. Array[InstrSpan] {
let out : Array[InstrSpan] = []
let label_stack : Array[Int] = [1]
for i in 0.. 0 {
label_stack[label_stack.length() - 1]
} else {
1
}
let mut remove_current = false
if span.opcode == 0x0cU &&
i + 1 < spans.length() &&
spans[i + 1].opcode == 0x0bU &&
top_label != 2 {
if decode_span_u32_immediate(instr_bytes, span) is Some(0U) {
remove_current = true
}
}
if not(remove_current) {
out.push(span)
}
match span.opcode {
0x02U => label_stack.push(1)
0x03U => label_stack.push(2)
0x04U => label_stack.push(1)
0x0bU => if label_stack.length() > 0 { ignore(label_stack.pop()) }
_ => ()
}
}
out
}
///|
fn optimize_instruction_bytes(
instr_bytes : Bytes,
config : OptimizeConfig,
) -> Bytes {
let mut current_bytes = instr_bytes
if config.enable_peephole {
let mut local_simplify_round = 0
while local_simplify_round < 6 {
let before_local_simplify = current_bytes
current_bytes = apply_precompute_i32_add(current_bytes)
current_bytes = apply_precompute_i32_identity_ops(current_bytes)
current_bytes = apply_precompute_i32_eqz_const(current_bytes)
current_bytes = apply_precompute_i32_double_eqz_br_if(current_bytes)
current_bytes = apply_precompute_local_const_propagation(current_bytes)
current_bytes = apply_precompute_i32_add(current_bytes)
current_bytes = apply_precompute_i32_double_eqz_br_if(current_bytes)
current_bytes = apply_precompute_br_if_const(current_bytes)
current_bytes = apply_optimize_instructions_i32_identity_ops(
current_bytes,
)
current_bytes = apply_optimize_instructions_i32_const_cmp_bitwise(
current_bytes,
)
current_bytes = apply_optimize_instructions_same_local_get_pair(
current_bytes,
)
current_bytes = apply_simplify_local_set_get_to_tee(current_bytes)
current_bytes = apply_simplify_local_tee_drop_to_set(current_bytes)
current_bytes = apply_rse_local_tee_set_same(current_bytes)
current_bytes = apply_simplify_local_get_set_noop(current_bytes)
current_bytes = apply_simplify_local_get_tee_to_get(current_bytes)
current_bytes = apply_local_cse_get_set_get(current_bytes)
current_bytes = apply_simplify_local_tee_drop_to_set(current_bytes)
current_bytes = apply_rse_local_tee_set_same(current_bytes)
if bytes_equal(before_local_simplify, current_bytes) {
break
}
local_simplify_round += 1
}
}
let spans = match parse_instruction_spans(current_bytes) {
Some(v) => v
None => return current_bytes
}
let mut current = spans.copy()
if config.enable_vacuum {
current = apply_vacuum_to_spans(current)
}
if config.peephole_remove_nop || config.peephole_remove_const_drop {
current = apply_basic_peephole_to_spans(current_bytes, current, config)
}
if config.enable_remove_unused_brs {
current = apply_remove_unused_brs_to_spans(current_bytes, current)
}
if config.enable_merge_blocks {
current = apply_merge_blocks_to_spans(current)
}
if config.enable_vacuum {
current = apply_vacuum_to_spans(current)
}
instr_spans_to_bytes(current_bytes, current)
}
///|
priv struct LocalDeclPrefixParseResult {
prefix_end : Int
local_types : Array[Byte]
}
///|
fn parse_local_decl_prefix_raise(
body_bytes : Bytes,
) -> LocalDeclPrefixParseResult raise WiteError {
let parser = Cursor::new(body_bytes)
let local_group_count = parser.read_u32_leb128()
let local_types : Array[Byte] = []
for _ in 0U.. Int raise WiteError {
parse_local_decl_prefix_raise(body_bytes).prefix_end
}
///|
fn encode_local_decl_prefix_from_local_types(
local_types : Array[Byte],
) -> Bytes {
let out : Array[Byte] = []
if local_types.length() == 0 {
out.append(encode_u32_leb128(0U)[:])
return Bytes::from_array(out[:])
}
let counts : Array[UInt] = []
let values : Array[Byte] = []
let mut current = local_types[0]
let mut current_count = 1U
for i in 1.. Bytes {
let prefix_result = try? parse_local_decl_prefix_end(body_bytes)
let prefix_end = match prefix_result {
Ok(v) => v
Err(_) => return body_bytes
}
let instr_bytes = body_bytes[prefix_end:body_bytes.length()].to_bytes()
let optimized_instr = optimize_instruction_bytes(instr_bytes, config)
if optimized_instr.length() >= instr_bytes.length() {
return body_bytes
}
let out : Array[Byte] = body_bytes[0:prefix_end].to_array()
out.append(optimized_instr[:].to_array())
Bytes::from_array(out[:])
}
///|
fn optimize_code_section_payload_raise(
payload : Bytes,
config : OptimizeConfig,
) -> Bytes raise WiteError {
let parser = Cursor::new(payload)
let body_count = parser.read_u32_leb128()
let bodies : Array[Bytes] = []
for _ in 0U.. payload.length() {
raise WiteError::UnexpectedEof
}
let body_bytes = payload[start:end_].to_bytes()
bodies.push(optimize_code_body_bytes(body_bytes, config))
parser.set_pos(end_)
}
let out : Array[Byte] = []
out.append(encode_u32_leb128(body_count)[:])
for body in bodies {
out.append(encode_u32_leb128(body.length().reinterpret_as_uint())[:])
out.append(body[:].to_array())
}
Bytes::from_array(out[:])
}
///|
fn optimize_code_section_payload(
payload : Bytes,
config : OptimizeConfig,
) -> Bytes {
let result = try? optimize_code_section_payload_raise(payload, config)
match result {
Ok(v) => v
Err(_) => payload
}
}
///|
fn collect_used_local_indices_from_instruction_raise(
instr_bytes : Bytes,
spans : Array[InstrSpan],
) -> Array[UInt] raise WiteError {
let out : Array[UInt] = []
for span in spans {
if span.opcode == 0x20U || span.opcode == 0x21U || span.opcode == 0x22U {
match decode_span_u32_immediate(instr_bytes, span) {
Some(index) => push_unique_u32(out, index)
None =>
raise WiteError::InvalidFormat(
"failed to decode local immediate in coalesce-locals",
)
}
}
}
out
}
///|
fn rewrite_instruction_local_indices_for_coalesce_raise(
instr_bytes : Bytes,
spans : Array[InstrSpan],
param_count : UInt,
local_index_map : Map[UInt, UInt],
) -> Bytes raise WiteError {
let out : Array[Byte] = []
for span in spans {
if span.opcode == 0x20U || span.opcode == 0x21U || span.opcode == 0x22U {
if span.end_ <= span.start + 1 {
raise WiteError::InvalidFormat("invalid local immediate")
}
let imm = instr_bytes[span.start + 1:span.end_].to_bytes()
let old_index = match decode_u32_leb128_bytes(imm) {
Some(v) => v
None =>
raise WiteError::InvalidFormat(
"failed to decode local index immediate in coalesce-locals",
)
}
let new_index = if old_index < param_count {
old_index
} else {
match local_index_map.get(old_index) {
Some(v) => v
None =>
raise WiteError::InvalidFormat(
"coalesce-locals remap missing local index: " +
old_index.to_string(),
)
}
}
out.push(span.opcode.to_byte())
out.append(encode_u32_leb128(new_index)[:])
} else {
out.append(instr_bytes[span.start:span.end_].to_array())
}
}
Bytes::from_array(out[:])
}
///|
priv struct CoalesceLocalsApplyResult {
bytes : Bytes
removed_local_count : UInt
}
///|
fn apply_coalesce_locals_raise(
bytes : Bytes,
) -> CoalesceLocalsApplyResult raise WiteError {
let sections = parse_core_sections_raise(bytes)
let mut type_section_payload : Bytes? = None
let mut function_section_payload : Bytes? = None
let mut code_section_payload : Bytes? = None
for section in sections {
let payload = bytes[section.payload_start:section.section_end].to_bytes()
match section.section_id {
1U => type_section_payload = Some(payload)
3U => function_section_payload = Some(payload)
10U => code_section_payload = Some(payload)
_ => ()
}
}
let type_payload = match type_section_payload {
Some(v) => v
None => return { bytes, removed_local_count: 0U }
}
let function_payload = match function_section_payload {
Some(v) => v
None => return { bytes, removed_local_count: 0U }
}
let code_payload = match code_section_payload {
Some(v) => v
None => return { bytes, removed_local_count: 0U }
}
let function_type_indices = parse_function_type_indices(function_payload)
let code_bodies = parse_code_bodies(code_payload)
if function_type_indices.length() != code_bodies.length() {
raise WiteError::InvalidFormat(
"function/code section count mismatch for coalesce-locals: function=" +
function_type_indices.length().to_string() +
" code=" +
code_bodies.length().to_string(),
)
}
let type_entries = parse_function_type_entries_raise(
type_payload, "coalesce-locals",
)
let rewritten_code_bodies = code_bodies.copy()
let mut removed_local_count = 0U
for i in 0..= type_entries.length() {
raise WiteError::InvalidFormat(
"type index out of range in coalesce-locals: " + type_index.to_string(),
)
}
let param_count = type_entries[type_pos].params
.length()
.reinterpret_as_uint()
let body = code_bodies[i]
let prefix = parse_local_decl_prefix_raise(body)
if prefix.local_types.length() == 0 {
continue
}
let instr_bytes = body[prefix.prefix_end:body.length()].to_bytes()
let spans = parse_instruction_spans_raise(instr_bytes)
let used_local_indices = collect_used_local_indices_from_instruction_raise(
instr_bytes, spans,
)
let kept_local_types : Array[Byte] = []
let local_index_map : Map[UInt, UInt] = {}
let mut next_local = 0U
for j in 0.. DuplicateImportElimApplyResult {
let result = try? apply_duplicate_import_elimination_raise(bytes)
match result {
Ok(v) => v
Err(_) =>
{
bytes,
removed_import_count: 0U,
removed_function_import_count: 0U,
removed_table_import_count: 0U,
removed_memory_import_count: 0U,
removed_global_import_count: 0U,
dropped_name_section: false,
}
}
}
///|
fn apply_duplicate_import_elimination_raise(
bytes : Bytes,
) -> DuplicateImportElimApplyResult raise WiteError {
let sections = parse_core_sections_raise(bytes)
let mut import_section_payload : Bytes? = None
let mut function_section_payload : Bytes? = None
let mut table_section_payload : Bytes? = None
let mut memory_section_payload : Bytes? = None
let mut global_section_payload : Bytes? = None
let mut element_section_payload : Bytes? = None
let mut data_section_payload : Bytes? = None
for section in sections {
let payload = bytes[section.payload_start:section.section_end].to_bytes()
match section.section_id {
2U => import_section_payload = Some(payload)
3U => function_section_payload = Some(payload)
4U => table_section_payload = Some(payload)
5U => memory_section_payload = Some(payload)
6U => global_section_payload = Some(payload)
9U => element_section_payload = Some(payload)
11U => data_section_payload = Some(payload)
_ => ()
}
}
let import_payload = match import_section_payload {
Some(v) => v
None =>
return {
bytes,
removed_import_count: 0U,
removed_function_import_count: 0U,
removed_table_import_count: 0U,
removed_memory_import_count: 0U,
removed_global_import_count: 0U,
dropped_name_section: false,
}
}
let import_entries = parse_import_section_entries_raise(import_payload)
if import_entries.length() <= 1 {
return {
bytes,
removed_import_count: 0U,
removed_function_import_count: 0U,
removed_table_import_count: 0U,
removed_memory_import_count: 0U,
removed_global_import_count: 0U,
dropped_name_section: false,
}
}
let mut imported_function_count = 0U
let mut imported_table_count = 0U
let mut imported_memory_count = 0U
let mut imported_global_count = 0U
for entry in import_entries {
match entry.kind {
0U => imported_function_count += 1U
1U => imported_table_count += 1U
2U => imported_memory_count += 1U
3U => imported_global_count += 1U
_ => ()
}
}
let keep_function_imports : Array[Bool] = []
for _ in 0U.. {
let mut canonical = entry.kind_index
let mut found = false
for i in 0..= keep_function_imports.length() {
raise WiteError::InvalidFormat(
"function import dedupe index out of range",
)
}
keep_function_imports[old_pos] = false
removed_function_import_count += 1U
}
}
1U => {
let mut canonical = entry.kind_index
let mut found = false
for i in 0..= keep_table_imports.length() {
raise WiteError::InvalidFormat(
"table import dedupe index out of range",
)
}
keep_table_imports[old_pos] = false
removed_table_import_count += 1U
}
}
2U => {
let mut canonical = entry.kind_index
let mut found = false
for i in 0..= keep_memory_imports.length() {
raise WiteError::InvalidFormat(
"memory import dedupe index out of range",
)
}
keep_memory_imports[old_pos] = false
removed_memory_import_count += 1U
}
}
3U => {
let mut canonical = entry.kind_index
let mut found = false
for i in 0..= keep_global_imports.length() {
raise WiteError::InvalidFormat(
"global import dedupe index out of range",
)
}
keep_global_imports[old_pos] = false
removed_global_import_count += 1U
}
}
_ => ()
}
}
let removed_import_count = removed_function_import_count +
removed_table_import_count +
removed_memory_import_count +
removed_global_import_count
if removed_import_count == 0U {
return {
bytes,
removed_import_count: 0U,
removed_function_import_count: 0U,
removed_table_import_count: 0U,
removed_memory_import_count: 0U,
removed_global_import_count: 0U,
dropped_name_section: false,
}
}
let local_function_count = match function_section_payload {
Some(payload) => parse_vec_count(payload)
None => 0U
}
let local_table_count = match table_section_payload {
Some(payload) =>
parse_table_section_entries_raise(payload).length().reinterpret_as_uint()
None => 0U
}
let local_memory_count = match memory_section_payload {
Some(payload) =>
parse_memory_section_entries_raise(payload).length().reinterpret_as_uint()
None => 0U
}
let local_global_count = match global_section_payload {
Some(payload) =>
parse_global_section_entries_with_refs_raise(payload)
.length()
.reinterpret_as_uint()
None => 0U
}
let function_index_map : Map[UInt, UInt] = {}
let mut kept_function_import_count = 0U
for i in 0U..= keep_function_imports.length() {
raise WiteError::InvalidFormat(
"function import keep index out of range in dedupe map",
)
}
if keep_function_imports[i_pos] {
function_index_map.set(i, kept_function_import_count)
kept_function_import_count += 1U
}
}
for i in 0U..= keep_function_imports.length() {
raise WiteError::InvalidFormat(
"function import keep index out of range in dedupe map canonical",
)
}
if not(keep_function_imports[i_pos]) {
let canonical = match canonical_function_import_index.get(i) {
Some(v) => v
None => i
}
function_index_map.set(
i,
remap_function_index_raise(canonical, function_index_map),
)
}
}
for i in 0U..= keep_table_imports.length() {
raise WiteError::InvalidFormat(
"table import keep index out of range in dedupe map",
)
}
if keep_table_imports[i_pos] {
table_index_map.set(i, kept_table_import_count)
kept_table_import_count += 1U
}
}
for i in 0U..= keep_table_imports.length() {
raise WiteError::InvalidFormat(
"table import keep index out of range in dedupe map canonical",
)
}
if not(keep_table_imports[i_pos]) {
let canonical = match canonical_table_import_index.get(i) {
Some(v) => v
None => i
}
table_index_map.set(
i,
remap_table_index_raise(canonical, table_index_map),
)
}
}
for i in 0U..= keep_memory_imports.length() {
raise WiteError::InvalidFormat(
"memory import keep index out of range in dedupe map",
)
}
if keep_memory_imports[i_pos] {
memory_index_map.set(i, kept_memory_import_count)
kept_memory_import_count += 1U
}
}
for i in 0U..= keep_memory_imports.length() {
raise WiteError::InvalidFormat(
"memory import keep index out of range in dedupe map canonical",
)
}
if not(keep_memory_imports[i_pos]) {
let canonical = match canonical_memory_import_index.get(i) {
Some(v) => v
None => i
}
memory_index_map.set(
i,
remap_memory_index_raise(canonical, memory_index_map),
)
}
}
for i in 0U..= keep_global_imports.length() {
raise WiteError::InvalidFormat(
"global import keep index out of range in dedupe map",
)
}
if keep_global_imports[i_pos] {
global_index_map.set(i, kept_global_import_count)
kept_global_import_count += 1U
}
}
for i in 0U..= keep_global_imports.length() {
raise WiteError::InvalidFormat(
"global import keep index out of range in dedupe map canonical",
)
}
if not(keep_global_imports[i_pos]) {
let canonical = match canonical_global_import_index.get(i) {
Some(v) => v
None => i
}
global_index_map.set(
i,
remap_global_index_raise(canonical, global_index_map),
)
}
}
for i in 0U.. make_identity_index_map(parse_vec_count(payload))
None => make_identity_index_map(0U)
}
let data_index_map = match data_section_payload {
Some(payload) => make_identity_index_map(parse_vec_count(payload))
None => make_identity_index_map(0U)
}
let (rewritten_import_payload, removed_by_rewrite) = rewrite_import_section_payload_for_keep_kinds_raise(
import_payload, keep_function_imports, keep_table_imports, keep_memory_imports,
keep_global_imports,
)
let out : Array[Byte] = bytes[0:8].to_array()
let mut dropped_name_section = false
for section in sections {
let payload = bytes[section.payload_start:section.section_end].to_bytes()
match section.section_id {
2U =>
if parse_vec_count(rewritten_import_payload) > 0U {
append_encoded_section(out, 2U, rewritten_import_payload)
}
6U => {
let rewritten_function = rewrite_global_section_payload_for_index_map_raise(
payload, function_index_map,
)
let rewritten = rewrite_global_section_payload_for_keep_local_and_global_index_map_raise(
rewritten_function, keep_local_globals, global_index_map,
)
if parse_vec_count(rewritten) > 0U {
append_encoded_section(out, 6U, rewritten)
}
}
7U => {
let rewritten_function = rewrite_export_section_payload_for_dce_raise(
payload, function_index_map,
)
let (rewritten, _) = rewrite_export_section_payload_for_module_index_maps_raise(
rewritten_function, table_index_map, memory_index_map, global_index_map,
)
if parse_vec_count(rewritten) > 0U {
append_encoded_section(out, 7U, rewritten)
}
}
8U =>
append_encoded_section(
out,
8U,
rewrite_start_section_payload_for_dce_raise(
payload, function_index_map,
),
)
9U => {
let rewritten_function = rewrite_element_section_payload_for_index_map_raise(
payload, function_index_map,
)
let (rewritten, _, _) = rewrite_element_section_payload_for_table_index_map_raise(
rewritten_function, table_index_map, global_index_map,
)
if parse_vec_count(rewritten) > 0U {
append_encoded_section(out, 9U, rewritten)
}
}
10U => {
let rewritten_function = rewrite_code_section_payload_for_dce_raise(
payload, keep_local_functions, function_index_map,
)
let rewritten = rewrite_code_section_payload_for_module_index_map_raise(
rewritten_function, table_index_map, memory_index_map, global_index_map,
element_index_map, data_index_map,
)
if parse_vec_count(rewritten) > 0U {
append_encoded_section(out, 10U, rewritten)
}
}
11U => {
let (rewritten, _, _) = rewrite_data_section_payload_for_memory_global_index_map_raise(
payload, memory_index_map, global_index_map,
)
if parse_vec_count(rewritten) > 0U {
append_encoded_section(out, 11U, rewritten)
}
}
0U =>
match section.custom_name {
Some(name) =>
if name == "name" && removed_function_import_count > 0U {
dropped_name_section = true
} else {
out.append(
bytes[section.section_start:section.section_end].to_array(),
)
}
None =>
out.append(
bytes[section.section_start:section.section_end].to_array(),
)
}
_ =>
out.append(bytes[section.section_start:section.section_end].to_array())
}
}
{
bytes: Bytes::from_array(out[:]),
removed_import_count: removed_by_rewrite,
removed_function_import_count,
removed_table_import_count,
removed_memory_import_count,
removed_global_import_count,
dropped_name_section,
}
}
///|
fn decode_const_expr_i32_const_nonnegative_offset_raise(
expr_bytes : Bytes,
) -> UInt? raise WiteError {
let spans = parse_instruction_spans_raise(expr_bytes)
if spans.length() != 2 {
return None
}
if spans[1].opcode != 0x0bU {
return None
}
decode_i32_const_nonnegative_from_span(expr_bytes, spans[0])
}
///|
fn collect_static_table_function_map_from_element_section_raise(
payload : Bytes,
target_table_index : UInt,
) -> Map[UInt, UInt]? raise WiteError {
let parser = Cursor::new(payload)
let segment_count = parser.read_u32_leb128()
let table_map : Map[UInt, UInt] = {}
for _ in 0U.. {
let offset_expr = read_const_expr_bytes_raise(parser, payload)
let function_count = parser.read_u32_leb128()
(0U, offset_expr, function_count)
}
2U => {
let table_index = parser.read_u32_leb128()
let offset_expr = read_const_expr_bytes_raise(parser, payload)
if parser.read_byte().to_uint() != 0U {
return None
}
let function_count = parser.read_u32_leb128()
(table_index, offset_expr, function_count)
}
_ => return None
}
let offset = match
decode_const_expr_i32_const_nonnegative_offset_raise(offset_expr) {
Some(v) => v
None => return None
}
for i in 0U.. if prev != function_index { return None }
None => table_map.set(slot, function_index)
}
}
}
}
Some(table_map)
}
///|
fn has_disallowed_table_ops_in_instruction_raise(
instr_bytes : Bytes,
spans : Array[InstrSpan],
) -> Bool raise WiteError {
for span in spans {
match span.opcode {
0x25U | 0x26U => return true
0xfcU => {
if span.end_ <= span.start + 1 {
raise WiteError::InvalidFormat("invalid 0xfc immediate in directize")
}
let imm = instr_bytes[span.start + 1:span.end_].to_bytes()
let parser = Cursor::new(imm)
let subopcode = parser.read_u32_leb128()
if subopcode >= 12U && subopcode <= 17U {
return true
}
}
_ => ()
}
}
false
}
///|
fn rewrite_instruction_call_indirect_to_direct_raise(
instr_bytes : Bytes,
spans : Array[InstrSpan],
table_map : Map[UInt, UInt],
function_type_by_index : Array[UInt],
) -> (Bytes, UInt) raise WiteError {
let out : Array[Byte] = []
let mut rewritten_call_count = 0U
let mut i = 0
while i < spans.length() {
if i + 1 < spans.length() &&
spans[i].opcode == 0x41U &&
spans[i + 1].opcode == 0x11U {
match decode_i32_const_nonnegative_from_span(instr_bytes, spans[i]) {
Some(slot) =>
if spans[i + 1].end_ > spans[i + 1].start + 1 {
let imm = instr_bytes[spans[i + 1].start + 1:spans[i + 1].end_].to_bytes()
match decode_call_indirect_immediate(imm) {
Some((type_index, table_index)) =>
if table_index == 0U {
match table_map.get(slot) {
Some(target) => {
let target_pos = UInt::reinterpret_as_int(target)
if target_pos >= 0 &&
target_pos < function_type_by_index.length() &&
function_type_by_index[target_pos] == type_index {
out.push(0x10U.to_byte())
out.append(encode_u32_leb128(target)[:])
rewritten_call_count += 1U
i += 2
continue
}
}
None => ()
}
}
None =>
raise WiteError::InvalidFormat(
"failed to decode call_indirect immediate in directize",
)
}
}
None => ()
}
}
out.append(instr_bytes[spans[i].start:spans[i].end_].to_array())
i += 1
}
(Bytes::from_array(out[:]), rewritten_call_count)
}
///|
fn rewrite_code_section_payload_for_directize_raise(
payload : Bytes,
table_map : Map[UInt, UInt],
function_type_by_index : Array[UInt],
) -> (Bytes, UInt) raise WiteError {
let bodies = parse_code_bodies(payload)
let rewritten_bodies : Array[Bytes] = []
let mut rewritten_call_count = 0U
for body in bodies {
let prefix_end = parse_local_decl_prefix_end(body)
let instr_bytes = body[prefix_end:body.length()].to_bytes()
let spans = parse_instruction_spans_raise(instr_bytes)
let (rewritten_instr, rewritten_count) = rewrite_instruction_call_indirect_to_direct_raise(
instr_bytes, spans, table_map, function_type_by_index,
)
rewritten_call_count += rewritten_count
if rewritten_count > 0U {
let rewritten_body : Array[Byte] = body[0:prefix_end].to_array()
rewritten_body.append(rewritten_instr[:].to_array())
rewritten_bodies.push(Bytes::from_array(rewritten_body[:]))
} else {
rewritten_bodies.push(body)
}
}
(
encode_code_section_payload_from_bodies(rewritten_bodies),
rewritten_call_count,
)
}
///|
priv struct DirectizeApplyResult {
bytes : Bytes
rewritten_call_count : UInt
}
///|
priv struct SimplifyGlobalsApplyResult {
bytes : Bytes
rewritten_global_get_count : UInt
}
///|
priv struct ReorderGlobalsApplyResult {
bytes : Bytes
reordered_local_count : UInt
}
///|
priv struct MemoryPackingApplyResult {
bytes : Bytes
reordered_local_count : UInt
}
///|
fn apply_directize_raise(bytes : Bytes) -> DirectizeApplyResult raise WiteError {
let sections = parse_core_sections_raise(bytes)
let mut import_payload : Bytes? = None
let mut function_payload : Bytes? = None
let mut table_payload : Bytes? = None
let mut element_payload : Bytes? = None
let mut export_payload : Bytes? = None
let mut code_payload : Bytes? = None
let mut imported_table_count = 0U
for section in sections {
let payload = bytes[section.payload_start:section.section_end].to_bytes()
match section.section_id {
2U => {
import_payload = Some(payload)
let entries = parse_import_section_entries_raise(payload)
for entry in entries {
if entry.kind == 1U {
imported_table_count += 1U
}
}
}
3U => function_payload = Some(payload)
4U => table_payload = Some(payload)
7U => export_payload = Some(payload)
9U => element_payload = Some(payload)
10U => code_payload = Some(payload)
_ => ()
}
}
if imported_table_count > 0U {
return { bytes, rewritten_call_count: 0U }
}
let local_table_count = match table_payload {
Some(payload) =>
parse_table_section_entries_raise(payload).length().reinterpret_as_uint()
None => 0U
}
if local_table_count != 1U {
return { bytes, rewritten_call_count: 0U }
}
let exported_table_indices = match export_payload {
Some(payload) => parse_export_table_indices(payload)
None => []
}
if exported_table_indices.length() > 0 {
return { bytes, rewritten_call_count: 0U }
}
let element_payload = match element_payload {
Some(v) => v
None => return { bytes, rewritten_call_count: 0U }
}
let table_map = match
collect_static_table_function_map_from_element_section_raise(
element_payload, 0U,
) {
Some(v) => v
None => return { bytes, rewritten_call_count: 0U }
}
let mut has_table_entry = false
table_map.each((_, _value) => has_table_entry = true)
if not(has_table_entry) {
return { bytes, rewritten_call_count: 0U }
}
let function_payload = match function_payload {
Some(v) => v
None => return { bytes, rewritten_call_count: 0U }
}
let code_payload = match code_payload {
Some(v) => v
None => return { bytes, rewritten_call_count: 0U }
}
let code_bodies = parse_code_bodies(code_payload)
for body in code_bodies {
let prefix_end = parse_local_decl_prefix_end(body)
let instr_bytes = body[prefix_end:body.length()].to_bytes()
let spans = parse_instruction_spans_raise(instr_bytes)
if has_disallowed_table_ops_in_instruction_raise(instr_bytes, spans) {
return { bytes, rewritten_call_count: 0U }
}
}
let imported_function_type_indices = match import_payload {
Some(payload) => parse_import_function_type_indices(payload)
None => []
}
let local_function_type_indices = parse_function_type_indices(
function_payload,
)
if local_function_type_indices.length() != code_bodies.length() {
raise WiteError::InvalidFormat(
"function/code section count mismatch for directize: function=" +
local_function_type_indices.length().to_string() +
" code=" +
code_bodies.length().to_string(),
)
}
let function_type_by_index = imported_function_type_indices.copy()
function_type_by_index.append(local_function_type_indices[:])
let (rewritten_code_payload, rewritten_call_count) = rewrite_code_section_payload_for_directize_raise(
code_payload, table_map, function_type_by_index,
)
if rewritten_call_count == 0U {
return { bytes, rewritten_call_count }
}
let out : Array[Byte] = bytes[0:8].to_array()
for section in sections {
if section.section_id == 10U {
append_encoded_section(out, 10U, rewritten_code_payload)
} else {
out.append(bytes[section.section_start:section.section_end].to_array())
}
}
{ bytes: Bytes::from_array(out[:]), rewritten_call_count }
}
///|
fn collect_global_usage_counts_from_code_section_raise(
payload : Bytes,
counts : Array[UInt],
) -> Unit raise WiteError {
let bodies = parse_code_bodies(payload)
for body in bodies {
let prefix_end = parse_local_decl_prefix_end(body)
let instr_bytes = body[prefix_end:body.length()].to_bytes()
let spans = parse_instruction_spans_raise(instr_bytes)
for span in spans {
if span.opcode == 0x23U || span.opcode == 0x24U {
match decode_span_u32_immediate(instr_bytes, span) {
Some(index) => increment_index_count_in_array(counts, index)
None =>
raise WiteError::InvalidFormat(
"failed to decode global index immediate in reorder-globals",
)
}
}
}
}
}
///|
fn rewrite_instruction_global_get_to_i32_const_raise(
instr_bytes : Bytes,
spans : Array[InstrSpan],
global_i32_const_map : Map[UInt, UInt],
) -> (Bytes, UInt) raise WiteError {
let out : Array[Byte] = []
let mut rewritten_global_get_count = 0U
for span in spans {
if span.opcode == 0x23U {
match decode_span_u32_immediate(instr_bytes, span) {
Some(index) =>
match global_i32_const_map.get(index) {
Some(value) => {
let replacement : Array[Byte] = [0x41U.to_byte()]
replacement.append(encode_nonnegative_i32_leb128(value)[:])
if replacement.length() <= span.end_ - span.start {
out.append(replacement[:])
rewritten_global_get_count += 1U
continue
}
}
None => ()
}
None =>
raise WiteError::InvalidFormat(
"failed to decode global.get immediate in simplify-globals",
)
}
}
out.append(instr_bytes[span.start:span.end_].to_array())
}
(Bytes::from_array(out[:]), rewritten_global_get_count)
}
///|
fn rewrite_code_section_payload_for_simplify_globals_raise(
payload : Bytes,
global_i32_const_map : Map[UInt, UInt],
) -> (Bytes, UInt) raise WiteError {
let bodies = parse_code_bodies(payload)
let rewritten_bodies : Array[Bytes] = []
let mut rewritten_global_get_count = 0U
for body in bodies {
let prefix_end = parse_local_decl_prefix_end(body)
let instr_bytes = body[prefix_end:body.length()].to_bytes()
let spans = parse_instruction_spans_raise(instr_bytes)
let (rewritten_instr, rewritten_count) = rewrite_instruction_global_get_to_i32_const_raise(
instr_bytes, spans, global_i32_const_map,
)
rewritten_global_get_count += rewritten_count
if rewritten_count > 0U {
let rewritten_body : Array[Byte] = body[0:prefix_end].to_array()
rewritten_body.append(rewritten_instr[:].to_array())
rewritten_bodies.push(Bytes::from_array(rewritten_body[:]))
} else {
rewritten_bodies.push(body)
}
}
(
encode_code_section_payload_from_bodies(rewritten_bodies),
rewritten_global_get_count,
)
}
///|
fn apply_simplify_globals_raise(
bytes : Bytes,
) -> SimplifyGlobalsApplyResult raise WiteError {
let sections = parse_core_sections_raise(bytes)
let mut import_payload : Bytes? = None
let mut global_payload : Bytes? = None
let mut code_payload : Bytes? = None
for section in sections {
let payload = bytes[section.payload_start:section.section_end].to_bytes()
match section.section_id {
2U => import_payload = Some(payload)
6U => global_payload = Some(payload)
10U => code_payload = Some(payload)
_ => ()
}
}
let global_payload = match global_payload {
Some(v) => v
None => return { bytes, rewritten_global_get_count: 0U }
}
let code_payload = match code_payload {
Some(v) => v
None => return { bytes, rewritten_global_get_count: 0U }
}
let import_entries = match import_payload {
Some(payload) => parse_import_section_entries_raise(payload)
None => []
}
let imported_global_count = count_import_kinds(import_entries).global_count
let local_global_entries = parse_global_section_entries_with_refs_raise(
global_payload,
)
if local_global_entries.length() == 0 {
return { bytes, rewritten_global_get_count: 0U }
}
let global_i32_const_map : Map[UInt, UInt] = {}
let mut candidate_count = 0U
for i in 0.. {
global_i32_const_map.set(
imported_global_count + i.reinterpret_as_uint(),
value,
)
candidate_count += 1U
}
None => ()
}
}
}
if candidate_count == 0U {
return { bytes, rewritten_global_get_count: 0U }
}
let (rewritten_code_payload, rewritten_global_get_count) = rewrite_code_section_payload_for_simplify_globals_raise(
code_payload, global_i32_const_map,
)
if rewritten_global_get_count == 0U {
return { bytes, rewritten_global_get_count: 0U }
}
let out : Array[Byte] = bytes[0:8].to_array()
for section in sections {
if section.section_id == 10U {
append_encoded_section(out, 10U, rewritten_code_payload)
} else {
out.append(bytes[section.section_start:section.section_end].to_array())
}
}
{ bytes: Bytes::from_array(out[:]), rewritten_global_get_count }
}
///|
fn apply_simplify_globals(bytes : Bytes) -> SimplifyGlobalsApplyResult {
let result = try? apply_simplify_globals_raise(bytes)
match result {
Ok(v) => v
Err(_) => { bytes, rewritten_global_get_count: 0U }
}
}
///|
fn apply_reorder_globals_raise(
bytes : Bytes,
) -> ReorderGlobalsApplyResult raise WiteError {
let sections = parse_core_sections_raise(bytes)
let mut import_payload : Bytes? = None
let mut table_payload : Bytes? = None
let mut memory_payload : Bytes? = None
let mut global_payload : Bytes? = None
let mut export_payload : Bytes? = None
let mut element_payload : Bytes? = None
let mut code_payload : Bytes? = None
let mut data_payload : Bytes? = None
for section in sections {
let payload = bytes[section.payload_start:section.section_end].to_bytes()
match section.section_id {
2U => import_payload = Some(payload)
4U => table_payload = Some(payload)
5U => memory_payload = Some(payload)
6U => global_payload = Some(payload)
7U => export_payload = Some(payload)
9U => element_payload = Some(payload)
10U => code_payload = Some(payload)
11U => data_payload = Some(payload)
_ => ()
}
}
let global_payload = match global_payload {
Some(v) => v
None => return { bytes, reordered_local_count: 0U }
}
let import_entries = match import_payload {
Some(payload) => parse_import_section_entries_raise(payload)
None => []
}
let import_counts = count_import_kinds(import_entries)
let imported_table_count = import_counts.table_count
let imported_memory_count = import_counts.memory_count
let imported_global_count = import_counts.global_count
let local_global_entries = parse_global_section_entries_with_refs_raise(
global_payload,
)
if local_global_entries.length() <= 1 {
return { bytes, reordered_local_count: 0U }
}
// Avoid creating potentially invalid forward references across local globals.
for entry in local_global_entries {
for dep in entry.init_global_refs {
if dep >= imported_global_count {
return { bytes, reordered_local_count: 0U }
}
}
}
let local_global_count = local_global_entries.length().reinterpret_as_uint()
let total_global_count = imported_global_count + local_global_count
let usage_counts : Array[UInt] = []
for _ in 0U..
collect_global_usage_counts_from_code_section_raise(payload, usage_counts)
None => ()
}
match export_payload {
Some(payload) =>
for index in parse_export_global_indices(payload) {
increment_index_count_in_array(usage_counts, index)
}
None => ()
}
match element_payload {
Some(payload) =>
for
index in collect_used_global_indices_from_element_section_raise(payload) {
increment_index_count_in_array(usage_counts, index)
}
None => ()
}
match data_payload {
Some(payload) => {
let (_, global_indices) = collect_used_memory_and_global_indices_from_data_section_raise(
payload,
)
for index in global_indices {
increment_index_count_in_array(usage_counts, index)
}
}
None => ()
}
for entry in local_global_entries {
for dep in entry.init_global_refs {
increment_index_count_in_array(usage_counts, dep)
}
}
let local_order : Array[UInt] = []
for i in 0.. {
let a_count = index_count_in_array(usage_counts, imported_global_count + a)
let b_count = index_count_in_array(usage_counts, imported_global_count + b)
if a_count > b_count {
-1
} else if a_count < b_count {
1
} else if a < b {
-1
} else if a > b {
1
} else {
0
}
})
let reordered_local_count = count_reordered_local_indices(local_order)
if reordered_local_count == 0U {
return { bytes, reordered_local_count: 0U }
}
let global_index_map : Map[UInt, UInt] = {}
for i in 0U..
parse_table_section_entries_raise(payload).length().reinterpret_as_uint()
None => 0U
}
let local_memory_count = match memory_payload {
Some(payload) =>
parse_memory_section_entries_raise(payload).length().reinterpret_as_uint()
None => 0U
}
let table_index_map = make_identity_index_map(
imported_table_count + local_table_count,
)
let memory_index_map = make_identity_index_map(
imported_memory_count + local_memory_count,
)
let element_index_map = match element_payload {
Some(payload) => make_identity_index_map(parse_vec_count(payload))
None => make_identity_index_map(0U)
}
let data_index_map = match data_payload {
Some(payload) => make_identity_index_map(parse_vec_count(payload))
None => make_identity_index_map(0U)
}
let out : Array[Byte] = bytes[0:8].to_array()
for section in sections {
let payload = bytes[section.payload_start:section.section_end].to_bytes()
match section.section_id {
6U => {
let rewritten = rewrite_global_section_payload_for_local_order_and_global_index_map_raise(
payload, local_order, global_index_map,
)
if parse_vec_count(rewritten) > 0U {
append_encoded_section(out, 6U, rewritten)
}
}
7U => {
let (rewritten, _) = rewrite_export_section_payload_for_module_index_maps_raise(
payload, table_index_map, memory_index_map, global_index_map,
)
if parse_vec_count(rewritten) > 0U {
append_encoded_section(out, 7U, rewritten)
}
}
9U => {
let (rewritten, _, _) = rewrite_element_section_payload_for_table_index_map_raise(
payload, table_index_map, global_index_map,
)
if parse_vec_count(rewritten) > 0U {
append_encoded_section(out, 9U, rewritten)
}
}
10U =>
append_encoded_section(
out,
10U,
rewrite_code_section_payload_for_module_index_map_raise(
payload, table_index_map, memory_index_map, global_index_map, element_index_map,
data_index_map,
),
)
11U => {
let (rewritten, _, _) = rewrite_data_section_payload_for_memory_global_index_map_raise(
payload, memory_index_map, global_index_map,
)
if parse_vec_count(rewritten) > 0U {
append_encoded_section(out, 11U, rewritten)
}
}
_ =>
out.append(bytes[section.section_start:section.section_end].to_array())
}
}
{ bytes: Bytes::from_array(out[:]), reordered_local_count }
}
///|
fn apply_reorder_globals(bytes : Bytes) -> ReorderGlobalsApplyResult {
let result = try? apply_reorder_globals_raise(bytes)
match result {
Ok(v) => v
Err(_) => { bytes, reordered_local_count: 0U }
}
}
///|
fn is_memory_index_marked_used(used_flags : Array[Bool], index : UInt) -> Bool {
let pos = UInt::reinterpret_as_int(index)
pos >= 0 && pos < used_flags.length() && used_flags[pos]
}
///|
fn apply_memory_packing_raise(
bytes : Bytes,
) -> MemoryPackingApplyResult raise WiteError {
let sections = parse_core_sections_raise(bytes)
let mut import_payload : Bytes? = None
let mut table_payload : Bytes? = None
let mut memory_payload : Bytes? = None
let mut global_payload : Bytes? = None
let mut export_payload : Bytes? = None
let mut code_payload : Bytes? = None
let mut data_payload : Bytes? = None
for section in sections {
let payload = bytes[section.payload_start:section.section_end].to_bytes()
match section.section_id {
2U => import_payload = Some(payload)
4U => table_payload = Some(payload)
5U => memory_payload = Some(payload)
6U => global_payload = Some(payload)
7U => export_payload = Some(payload)
10U => code_payload = Some(payload)
11U => data_payload = Some(payload)
_ => ()
}
}
let memory_payload = match memory_payload {
Some(v) => v
None => return { bytes, reordered_local_count: 0U }
}
let import_entries = match import_payload {
Some(payload) => parse_import_section_entries_raise(payload)
None => []
}
let import_counts = count_import_kinds(import_entries)
let imported_table_count = import_counts.table_count
let imported_memory_count = import_counts.memory_count
let imported_global_count = import_counts.global_count
let local_memory_entries = parse_memory_section_entries_raise(memory_payload)
if local_memory_entries.length() <= 1 {
return { bytes, reordered_local_count: 0U }
}
let used_memory_indices : Array[UInt] = []
match export_payload {
Some(payload) =>
for index in parse_export_memory_indices(payload) {
push_unique_u32(used_memory_indices, index)
}
None => ()
}
match code_payload {
Some(payload) =>
match collect_used_memory_indices_from_code_section(payload) {
Some(indices) =>
for index in indices {
push_unique_u32(used_memory_indices, index)
}
None => return { bytes, reordered_local_count: 0U }
}
None => ()
}
match data_payload {
Some(payload) => {
let (memory_indices, _) = collect_used_memory_and_global_indices_from_data_section_raise(
payload,
)
for index in memory_indices {
push_unique_u32(used_memory_indices, index)
}
}
None => ()
}
let total_memory_count = imported_memory_count +
local_memory_entries.length().reinterpret_as_uint()
let used_flags : Array[Bool] = []
for _ in 0U..= 0 && pos < used_flags.length() {
used_flags[pos] = true
}
}
let local_order : Array[UInt] = []
let sortable_local_indices : Array[UInt] = []
let fixed_local_pos = if imported_memory_count == 0U { 0 } else { -1 }
for i in 0.. {
let a_used = is_memory_index_marked_used(
used_flags,
imported_memory_count + a,
)
let b_used = is_memory_index_marked_used(
used_flags,
imported_memory_count + b,
)
if a_used && not(b_used) {
-1
} else if not(a_used) && b_used {
1
} else if a < b {
-1
} else if a > b {
1
} else {
0
}
})
local_order.append(sortable_local_indices[:])
let reordered_local_count = count_reordered_local_indices(local_order)
if reordered_local_count == 0U {
return { bytes, reordered_local_count: 0U }
}
let memory_index_map : Map[UInt, UInt] = {}
for i in 0U..
parse_table_section_entries_raise(payload).length().reinterpret_as_uint()
None => 0U
}
let local_global_count = match global_payload {
Some(payload) =>
parse_global_section_entries_with_refs_raise(payload)
.length()
.reinterpret_as_uint()
None => 0U
}
let table_index_map = make_identity_index_map(
imported_table_count + local_table_count,
)
let global_index_map = make_identity_index_map(
imported_global_count + local_global_count,
)
let element_index_map = make_identity_index_map(0U)
let data_index_map = match data_payload {
Some(payload) => make_identity_index_map(parse_vec_count(payload))
None => make_identity_index_map(0U)
}
let out : Array[Byte] = bytes[0:8].to_array()
for section in sections {
let payload = bytes[section.payload_start:section.section_end].to_bytes()
match section.section_id {
5U => {
let rewritten = rewrite_memory_section_payload_for_local_order_raise(
payload, local_order,
)
if parse_vec_count(rewritten) > 0U {
append_encoded_section(out, 5U, rewritten)
}
}
7U => {
let (rewritten, _) = rewrite_export_section_payload_for_module_index_maps_raise(
payload, table_index_map, memory_index_map, global_index_map,
)
if parse_vec_count(rewritten) > 0U {
append_encoded_section(out, 7U, rewritten)
}
}
10U =>
append_encoded_section(
out,
10U,
rewrite_code_section_payload_for_module_index_map_raise(
payload, table_index_map, memory_index_map, global_index_map, element_index_map,
data_index_map,
),
)
11U => {
let (rewritten, _, _) = rewrite_data_section_payload_for_memory_global_index_map_raise(
payload, memory_index_map, global_index_map,
)
if parse_vec_count(rewritten) > 0U {
append_encoded_section(out, 11U, rewritten)
}
}
_ =>
out.append(bytes[section.section_start:section.section_end].to_array())
}
}
{ bytes: Bytes::from_array(out[:]), reordered_local_count }
}
///|
fn apply_memory_packing(bytes : Bytes) -> MemoryPackingApplyResult {
let result = try? apply_memory_packing_raise(bytes)
match result {
Ok(v) => v
Err(_) => { bytes, reordered_local_count: 0U }
}
}
///|
fn is_dwarf_custom_section_name(name : String) -> Bool {
name.has_prefix(".debug_") || name.has_prefix("reloc..debug_")
}
///|
fn is_debug_custom_section_name(name : String) -> Bool {
name == "sourceMappingURL" ||
name == "external_debug_info" ||
is_dwarf_custom_section_name(name)
}
///|
fn keep_custom_section(section : RawSection, config : OptimizeConfig) -> Bool {
if section.section_id != 0U {
return true
}
let name = match section.custom_name {
Some(v) => v
None => ""
}
if config.keep_custom_sections.contains(name) {
return true
}
if config.strip_all_custom {
return false
}
if config.strip_name_section && name == "name" {
return false
}
if config.strip_producers_section && name == "producers" {
return false
}
if config.strip_target_features_section && name == "target_features" {
return false
}
if config.strip_dwarf_sections && is_dwarf_custom_section_name(name) {
return false
}
if config.strip_debug_sections && is_debug_custom_section_name(name) {
return false
}
true
}
///|
fn should_optimize_code_section(config : OptimizeConfig) -> Bool {
config.enable_peephole ||
config.enable_vacuum ||
config.enable_merge_blocks ||
config.enable_remove_unused_brs
}
///|
fn should_remove_unused_types(config : OptimizeConfig) -> Bool {
config.enable_dce ||
config.enable_dfe ||
config.enable_merge_similar_functions ||
config.enable_remove_unused_module_elements
}
///|
fn should_refine_types(config : OptimizeConfig) -> Bool {
config.enable_dce ||
config.enable_dfe ||
config.enable_merge_similar_functions ||
config.enable_remove_unused_module_elements
}
///|
fn should_signature_prune(config : OptimizeConfig) -> Bool {
config.enable_dce ||
config.enable_dfe ||
config.enable_merge_similar_functions
}
///|
fn should_duplicate_import_eliminate(config : OptimizeConfig) -> Bool {
config.enable_dce ||
config.enable_dfe ||
config.enable_merge_similar_functions ||
config.enable_remove_unused_module_elements
}
///|
fn should_simplify_globals(config : OptimizeConfig) -> Bool {
should_optimize_code_section(config) ||
config.enable_dce ||
config.enable_remove_unused_module_elements
}
///|
fn should_reorder_globals(config : OptimizeConfig) -> Bool {
config.enable_dce || config.enable_remove_unused_module_elements
}
///|
fn should_memory_pack(config : OptimizeConfig) -> Bool {
config.enable_dce || config.enable_remove_unused_module_elements
}
///|
fn custom_strip_pass_requested(config : OptimizeConfig) -> Bool {
config.strip_all_custom ||
config.strip_name_section ||
config.strip_producers_section ||
config.strip_target_features_section ||
config.strip_dwarf_sections ||
config.strip_debug_sections
}
///|
fn any_optimization_pass_requested(config : OptimizeConfig) -> Bool {
custom_strip_pass_requested(config) ||
should_optimize_code_section(config) ||
config.enable_dce ||
config.enable_dfe ||
config.enable_merge_similar_functions ||
config.enable_remove_unused_module_elements ||
should_refine_types(config) ||
should_signature_prune(config) ||
should_remove_unused_types(config)
}
///|
fn push_reason(reasons : Array[String], reason : String) -> Unit {
if not(reasons.contains(reason)) {
reasons.push(reason)
}
}
///|
fn collect_optimize_no_change_reasons(
before : Bytes,
after : Bytes,
config : OptimizeConfig,
) -> Array[String] {
let reasons : Array[String] = []
if before.length() != after.length() {
return reasons
}
if not(bytes_equal(before, after)) {
reasons.push("size-neutral rewrite only (bytes changed but size unchanged)")
return reasons
}
if not(any_optimization_pass_requested(config)) {
reasons.push("no optimization pass is enabled in config")
return reasons
}
let sections_result = try? parse_core_sections_raise(before)
match sections_result {
Ok(sections) => {
if custom_strip_pass_requested(config) {
let mut has_custom_section = false
let mut has_removable_custom_section = false
for section in sections {
if section.section_id == 0U {
has_custom_section = true
if not(keep_custom_section(section, config)) {
has_removable_custom_section = true
}
}
}
if not(has_custom_section) {
push_reason(reasons, "no custom section exists for strip passes")
} else if not(has_removable_custom_section) {
push_reason(
reasons, "configured custom-section strip targets were not present",
)
}
}
if should_optimize_code_section(config) {
if not(sections.any(s => s.section_id == 10U)) {
push_reason(reasons, "no code section exists for code-level passes")
} else {
push_reason(
reasons, "code-level passes found no reducible instruction patterns",
)
}
}
}
Err(_) =>
push_reason(
reasons, "unable to inspect section layout for no-change diagnostics",
)
}
if config.enable_dce {
match analyze_dce_report(before) {
Ok(report) =>
if report.partial {
push_reason(reasons, "dce skipped: partial call graph")
} else if report.removable_function_count == 0U {
push_reason(reasons, "dce found no removable functions")
}
Err(_) => push_reason(reasons, "dce analysis failed")
}
}
if reasons.length() == 0 {
reasons.push("module is already optimized for active passes")
}
reasons
}
///|
priv struct OptimizeRoundResult {
bytes : Bytes
removed_sections : Array[String]
}
///|
fn optimize_for_size_round_raise(
bytes : Bytes,
config : OptimizeConfig,
) -> OptimizeRoundResult raise WiteError {
let sections = parse_core_sections_raise(bytes)
let stage1_out : Array[Byte] = bytes[0:8].to_array()
let removed_sections : Array[String] = []
for section in sections {
if section.section_id == 0U && not(keep_custom_section(section, config)) {
removed_sections.push(section_key(section, false))
} else if section.section_id == 10U && should_optimize_code_section(config) {
let payload = bytes[section.payload_start:section.section_end].to_bytes()
let optimized_payload = optimize_code_section_payload(payload, config)
append_encoded_section(stage1_out, section.section_id, optimized_payload)
} else {
stage1_out.append(
bytes[section.section_start:section.section_end].to_array(),
)
}
}
let mut optimized_bytes = Bytes::from_array(stage1_out[:])
if config.enable_peephole {
let coalesce = apply_coalesce_locals_raise(optimized_bytes)
optimized_bytes = coalesce.bytes
if coalesce.removed_local_count > 0U {
removed_sections.push(
"coalesce-locals:removed:" + coalesce.removed_local_count.to_string(),
)
}
}
if should_duplicate_import_eliminate(config) {
let duplicate_import_elim = apply_duplicate_import_elimination(
optimized_bytes,
)
optimized_bytes = duplicate_import_elim.bytes
if duplicate_import_elim.removed_import_count > 0U {
removed_sections.push(
"dup-imports:imports:" +
duplicate_import_elim.removed_import_count.to_string() +
" funcs:" +
duplicate_import_elim.removed_function_import_count.to_string() +
" tables:" +
duplicate_import_elim.removed_table_import_count.to_string() +
" memories:" +
duplicate_import_elim.removed_memory_import_count.to_string() +
" globals:" +
duplicate_import_elim.removed_global_import_count.to_string(),
)
}
if duplicate_import_elim.dropped_name_section {
removed_sections.push("custom:name(dup-imports)")
}
}
if config.enable_dce {
let dce_cluster_before = optimized_bytes
let dce_cluster_removed_sections : Array[String] = []
let directize = apply_directize_raise(optimized_bytes)
optimized_bytes = directize.bytes
if directize.rewritten_call_count > 0U {
dce_cluster_removed_sections.push(
"directize:calls:" + directize.rewritten_call_count.to_string(),
)
}
let cfp_const = apply_const_call_forwarding_propagation_raise(
optimized_bytes,
)
optimized_bytes = cfp_const.bytes
if cfp_const.rewritten_call_count > 0U {
dce_cluster_removed_sections.push(
"cfp-const:calls:" + cfp_const.rewritten_call_count.to_string(),
)
}
let cfp = apply_call_forwarding_propagation_raise(optimized_bytes)
optimized_bytes = cfp.bytes
if cfp.rewritten_call_count > 0U {
dce_cluster_removed_sections.push(
"cfp:calls:" + cfp.rewritten_call_count.to_string(),
)
}
let signature_refine = apply_signature_refining_raise(optimized_bytes)
optimized_bytes = signature_refine.bytes
if signature_refine.rewritten_call_count > 0U {
dce_cluster_removed_sections.push(
"signature-refine:calls:" +
signature_refine.rewritten_call_count.to_string(),
)
}
let dce = apply_dce_raise(optimized_bytes, config)
optimized_bytes = dce.bytes
if dce.removed_function_count > 0U {
dce_cluster_removed_sections.push(
"dce:functions:" +
dce.removed_function_count.to_string() +
" bodies:" +
dce.removed_body_bytes.to_string(),
)
}
if dce.dropped_name_section {
dce_cluster_removed_sections.push("custom:name(dce)")
}
let dce_cluster_before_size = dce_cluster_before
.length()
.reinterpret_as_uint()
let dce_cluster_after_size = optimized_bytes.length().reinterpret_as_uint()
if dce_cluster_after_size > dce_cluster_before_size {
optimized_bytes = dce_cluster_before
removed_sections.push(
"dce-cluster:rollback:size-regress:" +
dce_cluster_before_size.to_string() +
"->" +
dce_cluster_after_size.to_string(),
)
} else {
removed_sections.append(dce_cluster_removed_sections[:])
}
}
if should_refine_types(config) {
let type_refine = apply_type_refining(optimized_bytes)
optimized_bytes = type_refine.bytes
if type_refine.refined_type_count > 0U {
removed_sections.push(
"type-refine:types:" + type_refine.refined_type_count.to_string(),
)
}
}
if should_signature_prune(config) {
let signature_prune = apply_signature_pruning(optimized_bytes)
optimized_bytes = signature_prune.bytes
if signature_prune.pruned_function_count > 0U {
removed_sections.push(
"signature-prune:functions:" +
signature_prune.pruned_function_count.to_string() +
" params:" +
signature_prune.pruned_param_count.to_string(),
)
}
}
if config.enable_dfe {
let dfe = apply_dfe_raise(optimized_bytes)
optimized_bytes = dfe.bytes
if dfe.removed_function_count > 0U {
removed_sections.push(
"dfe:functions:" +
dfe.removed_function_count.to_string() +
" bodies:" +
dfe.removed_body_bytes.to_string(),
)
}
if dfe.dropped_name_section {
removed_sections.push("custom:name(dfe)")
}
}
if config.enable_merge_similar_functions {
let msf = apply_merge_similar_functions_raise(optimized_bytes)
optimized_bytes = msf.bytes
if msf.removed_function_count > 0U {
removed_sections.push(
"msf:functions:" +
msf.removed_function_count.to_string() +
" bodies:" +
msf.removed_body_bytes.to_string(),
)
}
if msf.dropped_name_section {
removed_sections.push("custom:name(msf)")
}
}
if config.enable_remove_unused_module_elements {
let rume = apply_remove_unused_module_elements(optimized_bytes, config)
optimized_bytes = rume.bytes
if rume.removed_import_count > 0U ||
rume.removed_table_count > 0U ||
rume.removed_memory_count > 0U ||
rume.removed_global_count > 0U ||
rume.removed_element_count > 0U ||
rume.removed_data_count > 0U ||
rume.removed_export_count > 0U {
removed_sections.push(
"rume:imports:" +
rume.removed_import_count.to_string() +
" tables:" +
rume.removed_table_count.to_string() +
" memories:" +
rume.removed_memory_count.to_string() +
" globals:" +
rume.removed_global_count.to_string() +
" elements:" +
rume.removed_element_count.to_string() +
" data:" +
rume.removed_data_count.to_string() +
" exports:" +
rume.removed_export_count.to_string(),
)
}
}
if should_simplify_globals(config) {
let simplify_globals = apply_simplify_globals(optimized_bytes)
optimized_bytes = simplify_globals.bytes
if simplify_globals.rewritten_global_get_count > 0U {
removed_sections.push(
"simplify-globals:gets:" +
simplify_globals.rewritten_global_get_count.to_string(),
)
}
}
if should_reorder_globals(config) {
let reorder_globals = apply_reorder_globals(optimized_bytes)
optimized_bytes = reorder_globals.bytes
if reorder_globals.reordered_local_count > 0U {
removed_sections.push(
"reorder-globals:locals:" +
reorder_globals.reordered_local_count.to_string(),
)
}
}
if should_memory_pack(config) {
let memory_pack = apply_memory_packing(optimized_bytes)
optimized_bytes = memory_pack.bytes
if memory_pack.reordered_local_count > 0U {
removed_sections.push(
"memory-packing:locals:" + memory_pack.reordered_local_count.to_string(),
)
}
}
if should_remove_unused_types(config) {
let remove_unused_types = apply_remove_unused_types(optimized_bytes)
optimized_bytes = remove_unused_types.bytes
if remove_unused_types.removed_type_count > 0U {
removed_sections.push(
"types:" + remove_unused_types.removed_type_count.to_string(),
)
}
}
if config.enable_dce {
let inlining = apply_inlining_optimizing(optimized_bytes)
optimized_bytes = inlining.bytes
if inlining.inlined_call_count > 0U {
removed_sections.push(
"inlining:calls:" + inlining.inlined_call_count.to_string(),
)
}
let dae = apply_dae_optimizing(optimized_bytes)
optimized_bytes = dae.bytes
if dae.removed_instr_count > 0U {
removed_sections.push("dae:instrs:" + dae.removed_instr_count.to_string())
}
}
{ bytes: optimized_bytes, removed_sections }
}
///|
pub fn analyze_section_sizes(
bytes : Bytes,
) -> Result[Array[SectionSize], WiteError] {
try? {
let sections = parse_core_sections_raise(bytes)
summarize_sections(sections, false)
}
}
///|
pub fn optimize_for_size(
bytes : Bytes,
config? : OptimizeConfig = OptimizeConfig::default(),
) -> Result[OptimizeResult, WiteError] {
try? {
let rounds = if config.pass_rounds == 0U { 1U } else { config.pass_rounds }
let mut current = bytes
let removed_sections : Array[String] = []
for _ in 0U.. before_round_size {
break
}
}
let no_change_reasons = collect_optimize_no_change_reasons(
bytes, current, config,
)
{
bytes: current,
before_size: bytes.length().reinterpret_as_uint(),
after_size: current.length().reinterpret_as_uint(),
removed_sections,
no_change_reasons,
}
}
}
///|
pub fn profile_module(bytes : Bytes) -> Result[ModuleProfile, WiteError] {
try? {
let sections = parse_core_sections_raise(bytes)
let mut function_count = 0U
let mut import_count = 0U
let mut export_count = 0U
let mut code_body_count = 0U
let mut code_body_bytes = 0U
for section in sections {
let payload = bytes[section.payload_start:section.section_end].to_bytes()
match section.section_id {
2U => import_count += parse_vec_count(payload)
3U => function_count += parse_vec_count(payload)
7U => export_count += parse_vec_count(payload)
10U => {
let (count, total_bytes) = parse_code_section_metrics(payload)
code_body_count += count
code_body_bytes += total_bytes
}
_ => ()
}
}
{
total_bytes: bytes.length().reinterpret_as_uint(),
function_count,
import_count,
export_count,
code_body_count,
code_body_bytes,
sections: summarize_sections(sections, false),
}
}
}
///|
pub fn analyze_function_sizes(
bytes : Bytes,
) -> Result[Array[FunctionSize], WiteError] {
try? {
let sections = parse_core_sections_raise(bytes)
let mut imported_function_count = 0U
let mut local_function_count = 0U
let mut code_body_sizes : Array[UInt] = []
let export_names : Map[UInt, Array[String]] = {}
let function_names : Map[UInt, String] = {}
for section in sections {
let payload = bytes[section.payload_start:section.section_end].to_bytes()
match section.section_id {
2U => imported_function_count += parse_import_function_count(payload)
3U => local_function_count += parse_vec_count(payload)
7U =>
merge_export_function_names(
export_names,
parse_export_function_names(payload),
)
10U => code_body_sizes = parse_code_body_sizes(payload)
0U =>
match section.custom_name {
Some(name) =>
if name == "name" {
parse_name_section_function_names(payload).each((
index,
function_name,
) => function_names.set(index, function_name))
}
None => ()
}
_ => ()
}
}
if code_body_sizes.length().reinterpret_as_uint() != local_function_count {
raise WiteError::InvalidFormat(
"function/code section count mismatch: function=" +
local_function_count.to_string() +
" code=" +
code_body_sizes.length().to_string(),
)
}
let functions : Array[FunctionSize] = []
for i in 0.. v
None => []
},
})
}
functions.sort_by((a, b) => if a.body_bytes > b.body_bytes {
-1
} else if a.body_bytes < b.body_bytes {
1
} else if a.function_index < b.function_index {
-1
} else if a.function_index > b.function_index {
1
} else {
0
})
functions
}
}
///|
priv struct FunctionMatchPair {
match_kind : String
match_key : String
left_index : UInt
right_index : UInt
}
///|
fn map_push_uint_array(
grouped : Map[String, Array[UInt]],
key : String,
value : UInt,
) -> Unit {
match grouped.get(key) {
Some(prev) => {
let next = prev.copy()
next.push(value)
grouped.set(key, next)
}
None => grouped.set(key, [value])
}
}
///|
fn build_export_match_map(
functions : Array[FunctionSize],
) -> Map[String, Array[UInt]] {
let grouped : Map[String, Array[UInt]] = {}
for function in functions {
for export_name in function.export_names {
map_push_uint_array(
grouped,
"export:" + export_name,
function.function_index,
)
}
}
grouped
}
///|
fn build_name_match_map(
functions : Array[FunctionSize],
) -> Map[String, Array[UInt]] {
let grouped : Map[String, Array[UInt]] = {}
for function in functions {
match function.name {
Some(name) =>
if not(name.is_empty()) {
map_push_uint_array(grouped, "name:" + name, function.function_index)
}
None => ()
}
}
grouped
}
///|
fn compare_function_match_pair(
a : FunctionMatchPair,
b : FunctionMatchPair,
) -> Int {
if a.match_kind != b.match_kind {
a.match_kind.compare(b.match_kind)
} else {
a.match_key.compare(b.match_key)
}
}
///|
fn collect_unique_function_pairs(
left_grouped : Map[String, Array[UInt]],
right_grouped : Map[String, Array[UInt]],
match_kind : String,
used_left : Map[UInt, Bool],
used_right : Map[UInt, Bool],
) -> Array[FunctionMatchPair] {
let keys : Array[String] = []
left_grouped.each((key, _) => keys.push(key))
keys.sort()
let out : Array[FunctionMatchPair] = []
for key in keys {
let left_indices = match left_grouped.get(key) {
Some(v) => v
None => []
}
let right_indices = match right_grouped.get(key) {
Some(v) => v
None => []
}
if left_indices.length() != 1 || right_indices.length() != 1 {
continue
}
let left_index = left_indices[0]
let right_index = right_indices[0]
if used_left.get(left_index) is Some(_) {
continue
}
if used_right.get(right_index) is Some(_) {
continue
}
used_left.set(left_index, true)
used_right.set(right_index, true)
out.push({ match_kind, match_key: key, left_index, right_index })
}
out.sort_by(compare_function_match_pair)
out
}
///|
fn collect_unmatched_functions(
functions : Array[FunctionSize],
used : Map[UInt, Bool],
) -> Array[FunctionSize] {
let out : Array[FunctionSize] = []
for function in functions {
if used.get(function.function_index) is None {
out.push(function)
}
}
out.sort_by(compare_function_size)
out
}
///|
fn signed_delta_from_uint(left : UInt, right : UInt) -> Int {
if left >= right {
(left - right).reinterpret_as_int()
} else {
-(right - left).reinterpret_as_int()
}
}
///|
fn abs_signed_delta_from_uint(left : UInt, right : UInt) -> UInt {
if left >= right {
left - right
} else {
right - left
}
}
///|
fn sum_function_body_bytes(functions : Array[FunctionSize]) -> UInt {
let mut total = 0U
for function in functions {
total += function.body_bytes
}
total
}
///|
fn make_function_gap_entry(
match_kind : String,
match_key : String,
left : FunctionSize?,
right : FunctionSize?,
) -> FunctionGapEntry {
let left_body_bytes = match left {
Some(v) => v.body_bytes
None => 0U
}
let right_body_bytes = match right {
Some(v) => v.body_bytes
None => 0U
}
{
match_kind,
match_key,
left_function_index: match left {
Some(v) => Some(v.function_index)
None => None
},
right_function_index: match right {
Some(v) => Some(v.function_index)
None => None
},
left_name: match left {
Some(v) => v.name
None => None
},
right_name: match right {
Some(v) => v.name
None => None
},
left_export_names: match left {
Some(v) => v.export_names
None => []
},
right_export_names: match right {
Some(v) => v.export_names
None => []
},
left_body_bytes,
right_body_bytes,
left_minus_right_bytes: signed_delta_from_uint(
left_body_bytes, right_body_bytes,
),
abs_gap_bytes: abs_signed_delta_from_uint(left_body_bytes, right_body_bytes),
}
}
///|
fn compare_function_gap_entry(
a : FunctionGapEntry,
b : FunctionGapEntry,
) -> Int {
if a.abs_gap_bytes > b.abs_gap_bytes {
-1
} else if a.abs_gap_bytes < b.abs_gap_bytes {
1
} else if a.left_body_bytes > b.left_body_bytes {
-1
} else if a.left_body_bytes < b.left_body_bytes {
1
} else if a.right_body_bytes > b.right_body_bytes {
-1
} else if a.right_body_bytes < b.right_body_bytes {
1
} else if a.match_kind != b.match_kind {
a.match_kind.compare(b.match_kind)
} else {
a.match_key.compare(b.match_key)
}
}
///|
pub fn analyze_function_size_gap(
left_bytes : Bytes,
right_bytes : Bytes,
top_limit? : UInt = 20U,
) -> Result[FunctionGapReport, WiteError] {
try? {
let left_functions = match analyze_function_sizes(left_bytes) {
Ok(v) => v
Err(e) => raise e
}
let right_functions = match analyze_function_sizes(right_bytes) {
Ok(v) => v
Err(e) => raise e
}
let left_by_index = map_function_sizes_by_index(left_functions)
let right_by_index = map_function_sizes_by_index(right_functions)
let used_left : Map[UInt, Bool] = {}
let used_right : Map[UInt, Bool] = {}
let entries : Array[FunctionGapEntry] = []
let left_export_grouped = build_export_match_map(left_functions)
let right_export_grouped = build_export_match_map(right_functions)
let export_pairs = collect_unique_function_pairs(
left_export_grouped, right_export_grouped, "export", used_left, used_right,
)
for pair in export_pairs {
match
(
left_by_index.get(pair.left_index),
right_by_index.get(pair.right_index),
) {
(Some(left), Some(right)) =>
entries.push(
make_function_gap_entry(
pair.match_kind,
pair.match_key,
Some(left),
Some(right),
),
)
_ => ()
}
}
let left_name_grouped = build_name_match_map(left_functions)
let right_name_grouped = build_name_match_map(right_functions)
let name_pairs = collect_unique_function_pairs(
left_name_grouped, right_name_grouped, "name", used_left, used_right,
)
for pair in name_pairs {
match
(
left_by_index.get(pair.left_index),
right_by_index.get(pair.right_index),
) {
(Some(left), Some(right)) =>
entries.push(
make_function_gap_entry(
pair.match_kind,
pair.match_key,
Some(left),
Some(right),
),
)
_ => ()
}
}
let left_unmatched = collect_unmatched_functions(left_functions, used_left)
let right_unmatched = collect_unmatched_functions(
right_functions, used_right,
)
let shared_unmatched_count = if left_unmatched.length() <
right_unmatched.length() {
left_unmatched.length()
} else {
right_unmatched.length()
}
for i in 0.. 0U && count >= top_limit {
break
}
limited_entries.push(entry)
count += 1U
}
let left_total_body_bytes = sum_function_body_bytes(left_functions)
let right_total_body_bytes = sum_function_body_bytes(right_functions)
{
left_total_body_bytes,
right_total_body_bytes,
left_minus_right_total_bytes: signed_delta_from_uint(
left_total_body_bytes, right_total_body_bytes,
),
unmatched_left_count,
unmatched_right_count,
entries: limited_entries,
}
}
}
///|
fn code_block_kind_from_opcode(opcode : UInt) -> String? {
match opcode {
0x02U => Some("block")
0x03U => Some("loop")
0x04U => Some("if")
_ => None
}
}
///|
fn collect_function_code_blocks(
function_index : UInt,
function_name : String?,
export_names : Array[String],
instr_bytes : Bytes,
spans : Array[InstrSpan],
) -> (Array[CodeBlockSize], Bool) {
let entries : Array[CodeBlockSize] = []
let instr_size = instr_bytes.length().reinterpret_as_uint()
entries.push({
function_index,
function_name,
export_names,
block_index: 0U,
depth: 0U,
kind: "function",
start_offset: 0U,
end_offset: instr_size,
instruction_count: spans.length().reinterpret_as_uint(),
total_bytes: instr_size,
})
let mut next_block_index = 1U
let open_stack : Array[OpenCodeBlock] = []
for i in 0.. {
let depth = open_stack.length().reinterpret_as_uint() + 1U
let start_offset = span.start.reinterpret_as_uint()
entries.push({
function_index,
function_name,
export_names,
block_index: next_block_index,
depth,
kind,
start_offset,
end_offset: start_offset,
instruction_count: 0U,
total_bytes: 0U,
})
open_stack.push({
entry_index: entries.length() - 1,
start_span_index: i,
start_offset: span.start,
})
next_block_index += 1U
}
None => ()
}
if span.opcode == 0x0bU && open_stack.length() > 0 {
let open = open_stack[open_stack.length() - 1]
ignore(open_stack.pop())
let prev = entries[open.entry_index]
entries[open.entry_index] = {
function_index: prev.function_index,
function_name: prev.function_name,
export_names: prev.export_names,
block_index: prev.block_index,
depth: prev.depth,
kind: prev.kind,
start_offset: prev.start_offset,
end_offset: span.end_.reinterpret_as_uint(),
instruction_count: (i - open.start_span_index + 1).reinterpret_as_uint(),
total_bytes: (span.end_ - open.start_offset).reinterpret_as_uint(),
}
}
}
if open_stack.length() == 0 {
return (entries, true)
}
let filtered : Array[CodeBlockSize] = []
for entry in entries {
if entry.kind == "function" || entry.total_bytes > 0U {
filtered.push(entry)
}
}
(filtered, false)
}
///|
pub fn analyze_code_block_sizes(
bytes : Bytes,
) -> Result[CodeBlockSizeReport, WiteError] {
try? {
let sections = parse_core_sections_raise(bytes)
let mut imported_function_count = 0U
let mut local_function_count = 0U
let mut code_bodies : Array[Bytes] = []
let export_names : Map[UInt, Array[String]] = {}
let function_names : Map[UInt, String] = {}
for section in sections {
let payload = bytes[section.payload_start:section.section_end].to_bytes()
match section.section_id {
2U => imported_function_count += parse_import_function_count(payload)
3U => local_function_count += parse_vec_count(payload)
7U =>
merge_export_function_names(
export_names,
parse_export_function_names(payload),
)
10U => code_bodies = parse_code_bodies(payload)
0U =>
match section.custom_name {
Some(name) =>
if name == "name" {
parse_name_section_function_names(payload).each((
index,
function_name,
) => function_names.set(index, function_name))
}
None => ()
}
_ => ()
}
}
if code_bodies.length().reinterpret_as_uint() != local_function_count {
raise WiteError::InvalidFormat(
"function/code section count mismatch: function=" +
local_function_count.to_string() +
" code=" +
code_bodies.length().to_string(),
)
}
let blocks : Array[CodeBlockSize] = []
let mut partial = false
let mut total_body_bytes = 0U
let mut total_instruction_bytes = 0U
for i in 0.. {
let instr_bytes = body[prefix_end:body.length()].to_bytes()
total_instruction_bytes += instr_bytes.length().reinterpret_as_uint()
match parse_instruction_spans(instr_bytes) {
Some(spans) => {
let exports = match export_names.get(function_index) {
Some(v) => v
None => []
}
let (function_blocks, complete) = collect_function_code_blocks(
function_index,
function_names.get(function_index),
exports,
instr_bytes,
spans,
)
blocks.append(function_blocks[:])
if not(complete) {
partial = true
}
}
None => partial = true
}
}
Err(_) => partial = true
}
}
blocks.sort_by((a, b) => if a.total_bytes > b.total_bytes {
-1
} else if a.total_bytes < b.total_bytes {
1
} else if a.function_index < b.function_index {
-1
} else if a.function_index > b.function_index {
1
} else if a.block_index < b.block_index {
-1
} else if a.block_index > b.block_index {
1
} else {
0
})
{
partial,
function_count: local_function_count,
block_count: blocks.length().reinterpret_as_uint(),
total_body_bytes,
total_instruction_bytes,
blocks,
}
}
}
///|
fn summarize_custom_sections_raise(
sections : Array[RawSection],
bytes : Bytes,
) -> Array[CustomSectionBreakdown] raise WiteError {
let grouped : Map[String, CustomSectionBreakdown] = {}
for section in sections {
if section.section_id != 0U {
continue
}
let name = match section.custom_name {
Some(v) => v
None => "(anon)"
}
let payload = bytes[section.payload_start:section.section_end].to_bytes()
let payload_bytes = payload.length().reinterpret_as_uint()
let prefix_end = parse_custom_section_name_prefix_end(payload)
let content_bytes = (payload.length() - prefix_end).reinterpret_as_uint()
let total_bytes = section_total_bytes(section)
match grouped.get(name) {
Some(prev) =>
grouped.set(name, {
name: prev.name,
count: prev.count + 1U,
total_bytes: prev.total_bytes + total_bytes,
payload_bytes: prev.payload_bytes + payload_bytes,
content_bytes: prev.content_bytes + content_bytes,
})
None =>
grouped.set(name, {
name,
count: 1U,
total_bytes,
payload_bytes,
content_bytes,
})
}
}
let out : Array[CustomSectionBreakdown] = []
grouped.each((_, value) => out.push(value))
out.sort_by((a, b) => if a.total_bytes > b.total_bytes {
-1
} else if a.total_bytes < b.total_bytes {
1
} else if a.count > b.count {
-1
} else if a.count < b.count {
1
} else {
a.name.compare(b.name)
})
out
}
///|
fn to_hex_digit(v : Int) -> Char {
match v {
0 => '0'
1 => '1'
2 => '2'
3 => '3'
4 => '4'
5 => '5'
6 => '6'
7 => '7'
8 => '8'
9 => '9'
10 => 'a'
11 => 'b'
12 => 'c'
13 => 'd'
14 => 'e'
15 => 'f'
_ => '0'
}
}
///|
fn format_opcode_hex(opcode : UInt) -> String {
let value = (opcode & 0xffU).reinterpret_as_int()
let hi = value / 16
let lo = value % 16
String::from_array(['0', 'x', to_hex_digit(hi), to_hex_digit(lo)])
}
///|
fn parse_prefixed_opcode_subopcode(
instr_bytes : Bytes,
span : InstrSpan,
) -> UInt? {
if span.opcode != 0xfcU && span.opcode != 0xfdU {
return None
}
if span.end_ <= span.start + 1 {
return None
}
let parser = Cursor::new(instr_bytes)
parser.set_pos(span.start + 1)
let result = try? parser.read_u32_leb128()
match result {
Ok(v) => if parser.get_pos() <= span.end_ { Some(v) } else { None }
Err(_) => None
}
}
///|
fn opcode_stat_key(opcode : UInt, subopcode : UInt?) -> String {
let base = format_opcode_hex(opcode)
if opcode == 0xfcU || opcode == 0xfdU {
match subopcode {
Some(v) => base + "." + v.to_string()
None => base
}
} else {
base
}
}
///|
fn opcode_mnemonic_or_key(
opcode : UInt,
subopcode : UInt?,
key : String,
) -> String {
match opcode {
0x00U => "unreachable"
0x01U => "nop"
0x02U => "block"
0x03U => "loop"
0x04U => "if"
0x05U => "else"
0x0bU => "end"
0x0cU => "br"
0x0dU => "br_if"
0x0eU => "br_table"
0x0fU => "return"
0x10U => "call"
0x11U => "call_indirect"
0x1aU => "drop"
0x1bU => "select"
0x20U => "local.get"
0x21U => "local.set"
0x22U => "local.tee"
0x23U => "global.get"
0x24U => "global.set"
0x28U => "i32.load"
0x36U => "i32.store"
0x3fU => "memory.size"
0x40U => "memory.grow"
0x41U => "i32.const"
0x42U => "i64.const"
0x43U => "f32.const"
0x44U => "f64.const"
0x45U => "i32.eqz"
0x46U => "i32.eq"
0x47U => "i32.ne"
0x48U => "i32.lt_s"
0x49U => "i32.lt_u"
0x4aU => "i32.gt_s"
0x4bU => "i32.gt_u"
0x4cU => "i32.le_s"
0x4dU => "i32.le_u"
0x4eU => "i32.ge_s"
0x4fU => "i32.ge_u"
0x67U => "i32.clz"
0x68U => "i32.ctz"
0x69U => "i32.popcnt"
0x6aU => "i32.add"
0x6bU => "i32.sub"
0x6cU => "i32.mul"
0x71U => "i32.and"
0x72U => "i32.or"
0x73U => "i32.xor"
0x74U => "i32.shl"
0x75U => "i32.shr_s"
0x76U => "i32.shr_u"
0xd0U => "ref.null"
0xd1U => "ref.is_null"
0xd2U => "ref.func"
0xfcU =>
match subopcode {
Some(0U) => "i32.trunc_sat_f32_s"
Some(1U) => "i32.trunc_sat_f32_u"
Some(2U) => "i32.trunc_sat_f64_s"
Some(3U) => "i32.trunc_sat_f64_u"
Some(4U) => "i64.trunc_sat_f32_s"
Some(5U) => "i64.trunc_sat_f32_u"
Some(6U) => "i64.trunc_sat_f64_s"
Some(7U) => "i64.trunc_sat_f64_u"
Some(8U) => "memory.init"
Some(9U) => "data.drop"
Some(10U) => "memory.copy"
Some(11U) => "memory.fill"
Some(12U) => "table.init"
Some(13U) => "elem.drop"
Some(14U) => "table.copy"
Some(15U) => "table.grow"
Some(16U) => "table.size"
Some(17U) => "table.fill"
_ => key
}
0xfdU =>
match subopcode {
Some(0U) => "v128.load"
Some(11U) => "v128.store"
_ => key
}
_ => key
}
}
///|
priv struct OpcodeCounter {
opcode : UInt
subopcode : UInt?
count : UInt
total_bytes : UInt
}
///|
fn compare_function_size(a : FunctionSize, b : FunctionSize) -> Int {
if a.body_bytes > b.body_bytes {
-1
} else if a.body_bytes < b.body_bytes {
1
} else if a.function_index < b.function_index {
-1
} else if a.function_index > b.function_index {
1
} else {
0
}
}
///|
fn compare_code_block_size(a : CodeBlockSize, b : CodeBlockSize) -> Int {
if a.total_bytes > b.total_bytes {
-1
} else if a.total_bytes < b.total_bytes {
1
} else if a.function_index < b.function_index {
-1
} else if a.function_index > b.function_index {
1
} else if a.block_index < b.block_index {
-1
} else if a.block_index > b.block_index {
1
} else {
0
}
}
///|
fn push_unique_u32_with_seen(
out : Array[UInt],
seen : Map[UInt, Bool],
value : UInt,
) -> Unit {
if seen.get(value) is Some(_) {
return
}
seen.set(value, true)
out.push(value)
}
///|
fn push_function_size_with_limit(
out : Array[FunctionSize],
entry : FunctionSize,
top_limit : UInt,
) -> Unit {
if top_limit == 0U {
out.push(entry)
return
}
if out.length().reinterpret_as_uint() < top_limit {
out.push(entry)
return
}
let mut worst = 0
for i in 1.. Unit {
if top_limit == 0U {
out.push(entry)
return
}
if out.length().reinterpret_as_uint() < top_limit {
out.push(entry)
return
}
let mut worst = 0
for i in 1.. Unit {
match grouped.get(opcode) {
Some(prev) =>
grouped.set(opcode, {
opcode,
subopcode: None,
count: prev.count + 1U,
total_bytes: prev.total_bytes + span_size,
})
None =>
grouped.set(opcode, {
opcode,
subopcode: None,
count: 1U,
total_bytes: span_size,
})
}
}
///|
fn add_opcode_counter_prefixed(
grouped : Map[String, OpcodeCounter],
opcode : UInt,
subopcode : UInt?,
span_size : UInt,
) -> Unit {
let key = opcode_stat_key(opcode, subopcode)
match grouped.get(key) {
Some(prev) =>
grouped.set(key, {
opcode,
subopcode,
count: prev.count + 1U,
total_bytes: prev.total_bytes + span_size,
})
None =>
grouped.set(key, { opcode, subopcode, count: 1U, total_bytes: span_size })
}
}
///|
fn opcode_stats_from_counters(
base_grouped : Map[UInt, OpcodeCounter],
prefixed_grouped : Map[String, OpcodeCounter],
) -> Array[OpcodeStat] {
let out : Array[OpcodeStat] = []
base_grouped.each((_opcode, counter) => {
let key = format_opcode_hex(counter.opcode)
out.push({
key,
mnemonic: opcode_mnemonic_or_key(counter.opcode, None, key),
count: counter.count,
total_bytes: counter.total_bytes,
})
})
prefixed_grouped.each((key, counter) => out.push({
key,
mnemonic: opcode_mnemonic_or_key(counter.opcode, counter.subopcode, key),
count: counter.count,
total_bytes: counter.total_bytes,
}))
out.sort_by((a, b) => if a.total_bytes > b.total_bytes {
-1
} else if a.total_bytes < b.total_bytes {
1
} else if a.count > b.count {
-1
} else if a.count < b.count {
1
} else {
a.key.compare(b.key)
})
out
}
///|
pub fn analyze_wasm_breakdown(
bytes : Bytes,
top_limit? : UInt = 0U,
) -> Result[WasmBreakdownReport, WiteError] {
try? {
let sections = parse_core_sections_raise(bytes)
let section_sizes = summarize_sections(sections, false)
let custom_sections = summarize_custom_sections_raise(sections, bytes)
let export_names : Map[UInt, Array[String]] = {}
let function_names : Map[UInt, String] = {}
let roots : Array[UInt] = []
let root_seen : Map[UInt, Bool] = {}
let mut imported_function_count = 0U
let mut local_function_count = 0U
let mut code_bodies : Array[Bytes] = []
let mut callgraph_partial = false
for section in sections {
let payload = bytes[section.payload_start:section.section_end].to_bytes()
match section.section_id {
2U => imported_function_count += parse_import_function_count(payload)
3U => local_function_count = parse_vec_count(payload)
7U => {
let parsed = parse_export_function_names(payload)
merge_export_function_names(export_names, parsed)
parsed.each((index, _names) => push_unique_u32_with_seen(
roots, root_seen, index,
))
}
8U =>
push_unique_u32_with_seen(
roots,
root_seen,
parse_start_function_index(payload),
)
6U => {
let refs_result = try? parse_global_section_function_refs_raise(
payload,
)
match refs_result {
Ok(indices) =>
for index in indices {
push_unique_u32_with_seen(roots, root_seen, index)
}
Err(_) => callgraph_partial = true
}
}
9U => {
let refs_result = try? parse_element_section_function_refs_raise(
payload,
)
match refs_result {
Ok(indices) =>
for index in indices {
push_unique_u32_with_seen(roots, root_seen, index)
}
Err(_) => callgraph_partial = true
}
}
10U => code_bodies = parse_code_bodies(payload)
0U =>
match section.custom_name {
Some(name) =>
if name == "name" {
parse_name_section_function_names(payload).each((
index,
function_name,
) => function_names.set(index, function_name))
}
None => ()
}
_ => ()
}
}
if code_bodies.length().reinterpret_as_uint() != local_function_count {
raise WiteError::InvalidFormat(
"function/code section count mismatch: function=" +
local_function_count.to_string() +
" code=" +
code_bodies.length().to_string(),
)
}
let top_functions : Array[FunctionSize] = []
let top_blocks : Array[CodeBlockSize] = []
let local_function_indices : Array[UInt] = []
let direct_callees_map : Map[UInt, Array[UInt]] = {}
let body_bytes_map : Map[UInt, UInt] = {}
let base_opcode_grouped : Map[UInt, OpcodeCounter] = {}
let prefixed_opcode_grouped : Map[String, OpcodeCounter] = {}
let mut block_count = 0U
let mut instruction_count = 0U
let mut instruction_bytes = 0U
let mut opcode_partial = false
let mut has_indirect_calls = false
for i in 0.. v
None => []
}
local_function_indices.push(function_index)
body_bytes_map.set(function_index, body_bytes)
push_function_size_with_limit(
top_functions,
{
function_index,
body_bytes,
name: function_names.get(function_index),
export_names: exports,
},
top_limit,
)
let mut direct_callees : Array[UInt] = []
let prefix_result = try? parse_local_decl_prefix_end(body)
match prefix_result {
Ok(prefix_end) => {
let instr_bytes = body[prefix_end:body.length()].to_bytes()
instruction_bytes += instr_bytes.length().reinterpret_as_uint()
match parse_instruction_spans(instr_bytes) {
Some(spans) => {
instruction_count += spans.length().reinterpret_as_uint()
let (function_blocks, complete) = collect_function_code_blocks(
function_index,
function_names.get(function_index),
exports,
instr_bytes,
spans,
)
block_count += function_blocks.length().reinterpret_as_uint()
for block in function_blocks {
push_code_block_size_with_limit(top_blocks, block, top_limit)
}
if not(complete) {
opcode_partial = true
}
let (callees, body_has_indirect) = collect_direct_callees(
instr_bytes, spans,
)
direct_callees = callees
if body_has_indirect {
has_indirect_calls = true
}
for span in spans {
let span_size = (span.end_ - span.start).reinterpret_as_uint()
if span.opcode == 0xfcU || span.opcode == 0xfdU {
add_opcode_counter_prefixed(
prefixed_opcode_grouped,
span.opcode,
parse_prefixed_opcode_subopcode(instr_bytes, span),
span_size,
)
} else {
add_opcode_counter_base(
base_opcode_grouped,
span.opcode,
span_size,
)
}
}
}
None => {
opcode_partial = true
callgraph_partial = true
}
}
}
Err(_) => {
opcode_partial = true
callgraph_partial = true
}
}
direct_callees_map.set(function_index, direct_callees)
}
top_functions.sort_by(compare_function_size)
top_blocks.sort_by(compare_code_block_size)
let opcodes = opcode_stats_from_counters(
base_opcode_grouped, prefixed_opcode_grouped,
)
let visited : Map[UInt, Bool] = {}
let queue = roots.copy()
let mut head = 0
while head < queue.length() {
let index = queue[head]
head += 1
if visited.get(index) is Some(_) {
continue
}
visited.set(index, true)
match direct_callees_map.get(index) {
Some(callees) =>
for callee in callees {
if visited.get(callee) is None {
queue.push(callee)
}
}
None => ()
}
}
let mut reachable_body_bytes = 0U
let mut dead_body_bytes = 0U
for function_index in local_function_indices {
let body_bytes = match body_bytes_map.get(function_index) {
Some(v) => v
None => 0U
}
if visited.get(function_index) is Some(_) {
reachable_body_bytes += body_bytes
} else {
dead_body_bytes += body_bytes
}
}
{
total_bytes: bytes.length().reinterpret_as_uint(),
sections: section_sizes,
custom_sections,
imported_function_count,
local_function_count,
top_functions,
block_count,
top_blocks,
instruction_count,
instruction_bytes,
opcode_partial,
opcodes,
callgraph_partial,
has_indirect_calls,
reachable_body_bytes,
dead_body_bytes,
}
}
}
///|
fn should_include_export_root(
names : Array[String],
export_root_names : Array[String]?,
) -> Bool {
match export_root_names {
Some(allowed) => names.any(name => allowed.contains(name))
None => true
}
}
///|
fn analyze_call_graph_raise(
bytes : Bytes,
export_root_names : Array[String]?,
) -> CallGraphReport raise WiteError {
let sections = parse_core_sections_raise(bytes)
let mut imported_function_count = 0U
let mut local_function_count = 0U
let mut code_bodies : Array[Bytes] = []
let export_names : Map[UInt, Array[String]] = {}
let function_names : Map[UInt, String] = {}
let roots : Array[UInt] = []
let root_seen : Map[UInt, Bool] = {}
let mut partial = false
for section in sections {
let payload = bytes[section.payload_start:section.section_end].to_bytes()
match section.section_id {
2U => imported_function_count += parse_import_function_count(payload)
3U => local_function_count += parse_vec_count(payload)
7U => {
let parsed = parse_export_function_names(payload)
merge_export_function_names(export_names, parsed)
parsed.each((index, names) => if should_include_export_root(
names, export_root_names,
) {
push_unique_u32_with_seen(roots, root_seen, index)
})
}
8U => {
let start_index = parse_start_function_index(payload)
push_unique_u32_with_seen(roots, root_seen, start_index)
}
6U => {
let refs_result = try? parse_global_section_function_refs_raise(payload)
match refs_result {
Ok(indices) =>
for index in indices {
push_unique_u32_with_seen(roots, root_seen, index)
}
Err(_) => partial = true
}
}
9U => {
let refs_result = try? parse_element_section_function_refs_raise(
payload,
)
match refs_result {
Ok(indices) =>
for index in indices {
push_unique_u32_with_seen(roots, root_seen, index)
}
Err(_) => partial = true
}
}
10U => code_bodies = parse_code_bodies(payload)
0U =>
match section.custom_name {
Some(name) =>
if name == "name" {
parse_name_section_function_names(payload).each((
index,
function_name,
) => function_names.set(index, function_name))
}
None => ()
}
_ => ()
}
}
if code_bodies.length().reinterpret_as_uint() != local_function_count {
raise WiteError::InvalidFormat(
"function/code section count mismatch: function=" +
local_function_count.to_string() +
" code=" +
code_bodies.length().to_string(),
)
}
let nodes : Array[CallGraphNode] = []
let index_to_pos : Map[UInt, Int] = {}
let mut has_indirect_calls = false
for i in 0.. {
direct_callees = callees
if body_has_indirect {
has_indirect_calls = true
}
}
Err(_) => partial = true
}
index_to_pos.set(function_index, nodes.length())
nodes.push({
function_index,
body_bytes: body.length().reinterpret_as_uint(),
name: function_names.get(function_index),
export_names: match export_names.get(function_index) {
Some(v) => v
None => []
},
direct_callees,
reachable_from_roots: false,
})
}
let total_function_count_u = imported_function_count + local_function_count
let total_function_count = UInt::reinterpret_as_int(total_function_count_u)
if total_function_count < 0 {
raise WiteError::InvalidFormat(
"function count overflow in analyze-call-graph",
)
}
let visited = make_bool_array(total_function_count, false)
let queue = roots.copy()
let mut head = 0
while head < queue.length() {
let index = queue[head]
head += 1
if not(mark_visited_function_index(visited, index)) {
continue
}
match index_to_pos.get(index) {
Some(pos) =>
for callee in nodes[pos].direct_callees {
if not(is_visited_function_index(visited, callee)) {
queue.push(callee)
}
}
None => ()
}
}
let mut reachable_body_bytes = 0U
let mut dead_body_bytes = 0U
for i in 0.. CallGraphSummary raise WiteError {
let sections = parse_core_sections_raise(bytes)
let mut imported_function_count = 0U
let mut local_function_count = 0U
let mut code_bodies : Array[Bytes] = []
let roots : Array[UInt] = []
let root_seen : Map[UInt, Bool] = {}
let mut partial = false
for section in sections {
let payload = bytes[section.payload_start:section.section_end].to_bytes()
match section.section_id {
2U => imported_function_count += parse_import_function_count(payload)
3U => local_function_count += parse_vec_count(payload)
7U =>
for index in parse_export_indices_by_kind(payload, 0U) {
push_unique_u32_with_seen(roots, root_seen, index)
}
8U =>
push_unique_u32_with_seen(
roots,
root_seen,
parse_start_function_index(payload),
)
6U => {
let refs_result = try? parse_global_section_function_refs_raise(payload)
match refs_result {
Ok(indices) =>
for index in indices {
push_unique_u32_with_seen(roots, root_seen, index)
}
Err(_) => partial = true
}
}
9U => {
let refs_result = try? parse_element_section_function_refs_raise(
payload,
)
match refs_result {
Ok(indices) =>
for index in indices {
push_unique_u32_with_seen(roots, root_seen, index)
}
Err(_) => partial = true
}
}
10U => code_bodies = parse_code_bodies(payload)
_ => ()
}
}
if code_bodies.length().reinterpret_as_uint() != local_function_count {
raise WiteError::InvalidFormat(
"function/code section count mismatch: function=" +
local_function_count.to_string() +
" code=" +
code_bodies.length().to_string(),
)
}
let local_body_bytes : Array[UInt] = []
let direct_callees_by_local : Array[Array[UInt]] = []
let mut has_indirect_calls = false
for body in code_bodies {
local_body_bytes.push(body.length().reinterpret_as_uint())
let mut direct_callees : Array[UInt] = []
let direct_result = try? collect_direct_callees_from_body_raise(body)
match direct_result {
Ok((callees, body_has_indirect)) => {
direct_callees = callees
if body_has_indirect {
has_indirect_calls = true
}
}
Err(_) => partial = true
}
direct_callees_by_local.push(direct_callees)
}
let total_function_count_u = imported_function_count + local_function_count
let total_function_count = UInt::reinterpret_as_int(total_function_count_u)
if total_function_count < 0 {
raise WiteError::InvalidFormat(
"function count overflow in analyze-call-graph-summary",
)
}
let visited = make_bool_array(total_function_count, false)
let queue = roots.copy()
let mut head = 0
while head < queue.length() {
let index = queue[head]
head += 1
if not(mark_visited_function_index(visited, index)) {
continue
}
if index < imported_function_count {
continue
}
let local_index = index - imported_function_count
let pos = UInt::reinterpret_as_int(local_index)
if pos < 0 || pos >= direct_callees_by_local.length() {
continue
}
for callee in direct_callees_by_local[pos] {
if not(is_visited_function_index(visited, callee)) {
queue.push(callee)
}
}
}
let mut reachable_function_count = 0U
let mut reachable_body_bytes = 0U
let mut dead_body_bytes = 0U
for i in 0.. Result[CallGraphReport, WiteError] {
try? analyze_call_graph_raise(bytes, None)
}
///|
pub fn analyze_call_graph_summary(
bytes : Bytes,
) -> Result[CallGraphSummary, WiteError] {
try? analyze_call_graph_summary_raise(bytes)
}
///|
fn compare_host_code_hint(a : HostCodeHint, b : HostCodeHint) -> Int {
if a.function_index < b.function_index {
-1
} else if a.function_index > b.function_index {
1
} else {
a.kind.compare(b.kind)
}
}
///|
fn size_gain_bytes(before_size : UInt, after_size : UInt) -> UInt {
if before_size > after_size {
before_size - after_size
} else {
0U
}
}
///|
fn size_regression_bytes(before_size : UInt, after_size : UInt) -> UInt {
if after_size > before_size {
after_size - before_size
} else {
0U
}
}
///|
pub fn analyze_host_generated_code(
bytes : Bytes,
) -> Result[HostGeneratedCodeReport, WiteError] {
try? {
let sections = parse_core_sections_raise(bytes)
let mut type_payload : Bytes? = None
let mut import_payload : Bytes? = None
let mut function_payload : Bytes? = None
let mut code_payload : Bytes? = None
let export_names : Map[UInt, Array[String]] = {}
let function_names : Map[UInt, String] = {}
for section in sections {
let payload = bytes[section.payload_start:section.section_end].to_bytes()
match section.section_id {
1U => type_payload = Some(payload)
2U => import_payload = Some(payload)
3U => function_payload = Some(payload)
7U => {
let parsed = parse_export_function_names(payload)
merge_export_function_names(export_names, parsed)
}
10U => code_payload = Some(payload)
0U =>
match section.custom_name {
Some(name) =>
if name == "name" {
parse_name_section_function_names(payload).each((
index,
function_name,
) => function_names.set(index, function_name))
}
None => ()
}
_ => ()
}
}
let imported_function_type_indices = match import_payload {
Some(payload) => parse_import_function_type_indices(payload)
None => []
}
let imported_function_count = imported_function_type_indices
.length()
.reinterpret_as_uint()
let mut local_function_count = 0U
let mut param_forwarding_thunk_count = 0U
let mut const_forwarding_thunk_count = 0U
let mut signature_refinable_thunk_count = 0U
let hints : Array[HostCodeHint] = []
match (type_payload, function_payload, code_payload) {
(Some(type_payload), Some(function_payload), Some(code_payload)) => {
let local_function_type_indices = parse_function_type_indices(
function_payload,
)
local_function_count = local_function_type_indices
.length()
.reinterpret_as_uint()
let code_bodies = parse_code_bodies(code_payload)
if local_function_type_indices.length() != code_bodies.length() {
raise WiteError::InvalidFormat(
"function/code section count mismatch for analyze-host: function=" +
local_function_type_indices.length().to_string() +
" code=" +
code_bodies.length().to_string(),
)
}
let function_type_by_index = imported_function_type_indices.copy()
function_type_by_index.append(local_function_type_indices[:])
let parsed_type_entries = parse_type_section_entries_with_refs_raise(
type_payload, "analyze-host",
)
let type_count_u = parsed_type_entries.length().reinterpret_as_uint()
let gc_type_canonical_map = collect_gc_type_canonical_map_from_entries_raise(
parsed_type_entries,
)
let function_type_entry_by_type_index : Map[UInt, FunctionTypeEntry] = {}
for i in 0..= type_count_u {
raise WiteError::InvalidFormat(
"type index out of range in analyze-host: " +
type_index.to_string(),
)
}
let type_pos = UInt::reinterpret_as_int(type_index)
if type_pos < 0 || type_pos >= parsed_type_entries.length() {
raise WiteError::InvalidFormat(
"type index position out of range in analyze-host: " +
type_index.to_string(),
)
}
if parsed_type_entries[type_pos].form != 0x60U {
raise WiteError::InvalidFormat(
"non-function type index used by function in analyze-host: " +
type_index.to_string(),
)
}
function_param_count_by_index.push(
parse_function_type_param_count_from_raw_raise(
parsed_type_entries[type_pos].raw,
"analyze-host",
),
)
}
let param_alias_map = collect_call_forwarding_alias_map(
code_bodies, imported_function_count, function_type_by_index, function_type_entry_by_type_index,
function_param_count_by_index, gc_type_canonical_map,
)
param_alias_map.each((function_index, target_function_index) => {
param_forwarding_thunk_count += 1U
hints.push({
kind: "param-forward",
function_index,
function_name: function_names.get(function_index),
target_function_index,
target_function_name: function_names.get(target_function_index),
export_names: match export_names.get(function_index) {
Some(v) => v
None => []
},
appended_i32_const_count: 0U,
dropped_param_count: 0U,
})
})
let const_alias_map = collect_const_call_forwarding_alias_map(
code_bodies, imported_function_count, function_type_by_index, function_type_entry_by_type_index,
gc_type_canonical_map,
)
const_alias_map.each((function_index, alias_info) => {
const_forwarding_thunk_count += 1U
hints.push({
kind: "const-forward",
function_index,
function_name: function_names.get(function_index),
target_function_index: alias_info.target,
target_function_name: function_names.get(alias_info.target),
export_names: match export_names.get(function_index) {
Some(v) => v
None => []
},
appended_i32_const_count: alias_info.appended_i32_const_instrs
.length()
.reinterpret_as_uint(),
dropped_param_count: 0U,
})
})
let signature_alias_map = collect_signature_refine_alias_map(
code_bodies, imported_function_count, function_type_by_index, function_type_entry_by_type_index,
gc_type_canonical_map,
)
signature_alias_map.each((function_index, alias_info) => {
signature_refinable_thunk_count += 1U
hints.push({
kind: "signature-refine",
function_index,
function_name: function_names.get(function_index),
target_function_index: alias_info.target,
target_function_name: function_names.get(alias_info.target),
export_names: match export_names.get(function_index) {
Some(v) => v
None => []
},
appended_i32_const_count: 0U,
dropped_param_count: alias_info.dropped_param_count,
})
})
}
_ => ()
}
hints.sort_by(compare_host_code_hint)
let directize_result = apply_directize_raise(bytes)
let dce_report = match analyze_dce_report(bytes) {
Ok(v) => v
Err(e) => raise e
}
{
imported_function_count,
local_function_count,
param_forwarding_thunk_count,
const_forwarding_thunk_count,
signature_refinable_thunk_count,
directize_candidate_call_count: directize_result.rewritten_call_count,
dce_removable_function_count: dce_report.removable_function_count,
dce_removable_body_bytes: dce_report.removable_body_bytes,
dce_partial: dce_report.partial,
hints,
}
}
}
///|
fn make_optimize_stage_config(
base : OptimizeConfig,
strip_only : Bool,
include_dce_group : Bool,
include_rume : Bool,
) -> OptimizeConfig {
let pass_rounds = if strip_only {
1U
} else if base.pass_rounds == 0U {
1U
} else {
base.pass_rounds
}
make_optimize_config(
strip_all_custom=base.strip_all_custom,
strip_name_section=base.strip_name_section,
strip_producers_section=base.strip_producers_section,
strip_debug_sections=base.strip_debug_sections,
strip_dwarf_sections=base.strip_dwarf_sections,
strip_target_features_section=base.strip_target_features_section,
keep_custom_sections=base.keep_custom_sections,
pass_rounds~,
enable_peephole=if strip_only { false } else { base.enable_peephole },
peephole_remove_nop=if strip_only {
false
} else {
base.peephole_remove_nop
},
peephole_remove_const_drop=if strip_only {
false
} else {
base.peephole_remove_const_drop
},
enable_vacuum=if strip_only { false } else { base.enable_vacuum },
enable_merge_blocks=if strip_only {
false
} else {
base.enable_merge_blocks
},
enable_remove_unused_brs=if strip_only {
false
} else {
base.enable_remove_unused_brs
},
enable_dce=if include_dce_group { base.enable_dce } else { false },
enable_dfe=if include_dce_group { base.enable_dfe } else { false },
enable_merge_similar_functions=if include_dce_group {
base.enable_merge_similar_functions
} else {
false
},
enable_remove_unused_module_elements=if include_rume {
base.enable_remove_unused_module_elements
} else {
false
},
closed_world=base.closed_world,
closed_world_root_exports=base.closed_world_root_exports,
safe_mode=base.safe_mode,
)
}
///|
fn compare_function_size_diff(
a : FunctionSizeDiff,
b : FunctionSizeDiff,
) -> Int {
if a.gain_bytes > b.gain_bytes {
-1
} else if a.gain_bytes < b.gain_bytes {
1
} else if a.regression_bytes > b.regression_bytes {
-1
} else if a.regression_bytes < b.regression_bytes {
1
} else if a.before_body_bytes > b.before_body_bytes {
-1
} else if a.before_body_bytes < b.before_body_bytes {
1
} else if a.function_index < b.function_index {
-1
} else if a.function_index > b.function_index {
1
} else {
0
}
}
///|
fn map_function_sizes_by_index(
functions : Array[FunctionSize],
) -> Map[UInt, FunctionSize] {
let out : Map[UInt, FunctionSize] = {}
for function in functions {
out.set(function.function_index, function)
}
out
}
///|
fn collect_function_size_diff_indices(
before_map : Map[UInt, FunctionSize],
after_map : Map[UInt, FunctionSize],
) -> Array[UInt] {
let out : Array[UInt] = []
let seen : Map[UInt, Bool] = {}
before_map.each((function_index, _) => {
seen.set(function_index, true)
out.push(function_index)
})
after_map.each((function_index, _) => if seen.get(function_index) is None {
seen.set(function_index, true)
out.push(function_index)
})
out
}
///|
fn merged_export_names_for_function_diff(
before : FunctionSize?,
after : FunctionSize?,
) -> Array[String] {
let names : Array[String] = []
match before {
Some(v) => names.append(v.export_names[:])
None => ()
}
match after {
Some(v) =>
for name in v.export_names {
if not(names.contains(name)) {
names.push(name)
}
}
None => ()
}
names
}
///|
fn collect_function_size_diffs_raise(
before_bytes : Bytes,
after_bytes : Bytes,
limit : UInt,
) -> (UInt, UInt, Array[FunctionSizeDiff]) raise WiteError {
let before_functions = match analyze_function_sizes(before_bytes) {
Ok(v) => v
Err(e) => raise e
}
let after_functions = match analyze_function_sizes(after_bytes) {
Ok(v) => v
Err(e) => raise e
}
let before_by_index = map_function_sizes_by_index(before_functions)
let after_by_index = map_function_sizes_by_index(after_functions)
let indices = collect_function_size_diff_indices(
before_by_index, after_by_index,
)
let mut total_gain = 0U
let mut total_regression = 0U
let diffs : Array[FunctionSizeDiff] = []
for function_index in indices {
let before_function = before_by_index.get(function_index)
let after_function = after_by_index.get(function_index)
let before_body_bytes = match before_function {
Some(v) => v.body_bytes
None => 0U
}
let after_body_bytes = match after_function {
Some(v) => v.body_bytes
None => 0U
}
let gain_bytes = size_gain_bytes(before_body_bytes, after_body_bytes)
let regression_bytes = size_regression_bytes(
before_body_bytes, after_body_bytes,
)
total_gain += gain_bytes
total_regression += regression_bytes
if gain_bytes > 0U || regression_bytes > 0U {
diffs.push({
function_index,
before_body_bytes,
after_body_bytes,
gain_bytes,
regression_bytes,
before_name: match before_function {
Some(v) => v.name
None => None
},
after_name: match after_function {
Some(v) => v.name
None => None
},
export_names: merged_export_names_for_function_diff(
before_function, after_function,
),
})
}
}
diffs.sort_by(compare_function_size_diff)
let top_diffs : Array[FunctionSizeDiff] = []
let mut count = 0U
for diff in diffs {
if limit > 0U && count >= limit {
break
}
top_diffs.push(diff)
count += 1U
}
(total_gain, total_regression, top_diffs)
}
///|
priv struct OptimizeStageRunResult {
bytes : Bytes
stage : OptimizeStageMetadata
}
///|
fn run_optimize_stage_raise(
stage_name : String,
bytes : Bytes,
config : OptimizeConfig,
function_diff_limit : UInt,
) -> OptimizeStageRunResult raise WiteError {
let optimized = match optimize_for_size(bytes, config~) {
Ok(v) => v
Err(err) => raise err
}
let (function_gain_bytes, function_regression_bytes, function_diffs) = collect_function_size_diffs_raise(
bytes,
optimized.bytes,
function_diff_limit,
)
let stage = {
stage: stage_name,
before_size: optimized.before_size,
after_size: optimized.after_size,
gain_bytes: size_gain_bytes(optimized.before_size, optimized.after_size),
regression_bytes: size_regression_bytes(
optimized.before_size,
optimized.after_size,
),
function_gain_bytes,
function_regression_bytes,
function_diffs,
removed_sections: optimized.removed_sections,
no_change_reasons: optimized.no_change_reasons,
}
{ bytes: optimized.bytes, stage }
}
///|
pub fn analyze_optimize_metadata(
bytes : Bytes,
config? : OptimizeConfig = OptimizeConfig::o1(),
function_diff_limit? : UInt = 20U,
) -> Result[OptimizeMetadataReport, WiteError] {
try? {
let strip_stage = run_optimize_stage_raise(
"strip",
bytes,
make_optimize_stage_config(config, true, false, false),
function_diff_limit,
)
let code_stage = run_optimize_stage_raise(
"code",
strip_stage.bytes,
make_optimize_stage_config(config, false, false, false),
function_diff_limit,
)
let dce_stage = run_optimize_stage_raise(
"dce",
code_stage.bytes,
make_optimize_stage_config(config, false, true, false),
function_diff_limit,
)
let rume_stage = run_optimize_stage_raise(
"rume",
dce_stage.bytes,
make_optimize_stage_config(config, false, true, true),
function_diff_limit,
)
let before_size = bytes.length().reinterpret_as_uint()
let after_size = rume_stage.bytes.length().reinterpret_as_uint()
{
before_size,
after_size,
total_gain_bytes: size_gain_bytes(before_size, after_size),
total_regression_bytes: size_regression_bytes(before_size, after_size),
stages: [
strip_stage.stage,
code_stage.stage,
dce_stage.stage,
rume_stage.stage,
],
}
}
}
///|
fn export_root_names_from_config(config : OptimizeConfig) -> Array[String]? {
if config.closed_world &&
not(config.safe_mode) &&
config.closed_world_root_exports.length() > 0 {
Some(config.closed_world_root_exports)
} else {
None
}
}
///|
fn collect_root_reasons_raise(
bytes : Bytes,
export_root_names : Array[String]?,
) -> (Map[UInt, Array[String]], Bool) raise WiteError {
let reasons_map : Map[UInt, Array[String]] = {}
let sections = parse_core_sections_raise(bytes)
let mut partial = false
for section in sections {
let payload = bytes[section.payload_start:section.section_end].to_bytes()
match section.section_id {
7U => {
let exports = parse_export_function_names(payload)
exports.each((index, names) => if should_include_export_root(
names, export_root_names,
) {
for export_name in names {
map_push_string_array(reasons_map, index, "export:" + export_name)
}
})
}
8U => {
let start_index = parse_start_function_index(payload)
map_push_string_array(reasons_map, start_index, "start")
}
6U => {
let result = try? parse_global_section_function_refs_raise(payload)
match result {
Ok(indices) =>
for index in indices {
map_push_string_array(reasons_map, index, "global-ref.func")
}
Err(_) => partial = true
}
}
9U => {
let result = try? parse_element_section_function_refs_raise(payload)
match result {
Ok(indices) =>
for index in indices {
map_push_string_array(reasons_map, index, "element-ref.func")
}
Err(_) => partial = true
}
}
_ => ()
}
}
(reasons_map, partial)
}
///|
fn build_retain_path_from_parent_map(
target : UInt,
parent_map : Map[UInt, UInt],
) -> Array[UInt] {
let reversed : Array[UInt] = [target]
let mut current = target
while true {
match parent_map.get(current) {
Some(parent) => {
reversed.push(parent)
current = parent
}
None => break
}
}
let path : Array[UInt] = []
for i in 0.. Int {
if a.function_index < b.function_index {
-1
} else if a.function_index > b.function_index {
1
} else {
0
}
}
///|
pub fn analyze_keep_reasons(
bytes : Bytes,
config? : OptimizeConfig = OptimizeConfig::default(),
) -> Result[KeepReasonReport, WiteError] {
try? {
let export_root_names = export_root_names_from_config(config)
let graph = analyze_call_graph_raise(bytes, export_root_names)
let (reasons_map, reasons_partial) = collect_root_reasons_raise(
bytes, export_root_names,
)
let index_to_pos : Map[UInt, Int] = {}
for i in 0..
for callee in graph.nodes[pos].direct_callees {
if visited.get(callee) is None {
if parent_map.get(callee) is None {
parent_map.set(callee, current)
}
queue.push(callee)
}
}
None => ()
}
}
let entries : Array[KeepReasonEntry] = []
for node in graph.nodes {
if node.reachable_from_roots {
let reasons = match reasons_map.get(node.function_index) {
Some(v) => v.copy()
None => []
}
if reasons.length() == 0 {
match parent_map.get(node.function_index) {
Some(parent) => reasons.push("call:" + parent.to_string())
None => reasons.push("reachable")
}
}
reasons.sort()
reasons.dedup()
entries.push({
function_index: node.function_index,
name: node.name,
export_names: node.export_names,
reasons,
})
}
}
entries.sort_by((a, b) => if a.function_index < b.function_index {
-1
} else if a.function_index > b.function_index {
1
} else {
0
})
{ partial: graph.partial || reasons_partial, entries }
}
}
///|
pub fn analyze_retain_paths(
bytes : Bytes,
config? : OptimizeConfig = OptimizeConfig::default(),
) -> Result[RetainPathReport, WiteError] {
try? {
let export_root_names = export_root_names_from_config(config)
let graph = analyze_call_graph_raise(bytes, export_root_names)
let (root_reasons_map, root_reason_partial) = collect_root_reasons_raise(
bytes, export_root_names,
)
let index_to_pos : Map[UInt, Int] = {}
for i in 0..
for callee in graph.nodes[pos].direct_callees {
if visited.get(callee) is None {
if parent_map.get(callee) is None {
parent_map.set(callee, current)
}
queue.push(callee)
}
}
None => ()
}
}
let entries : Array[RetainPathEntry] = []
for node in graph.nodes {
if not(node.reachable_from_roots) {
continue
}
let path = build_retain_path_from_parent_map(
node.function_index,
parent_map,
)
let root_index = if path.length() > 0 {
path[0]
} else {
node.function_index
}
let root_reasons = match root_reasons_map.get(root_index) {
Some(v) => v.copy()
None => []
}
if root_reasons.length() == 0 {
root_reasons.push("reachable")
}
root_reasons.sort()
root_reasons.dedup()
entries.push({
function_index: node.function_index,
name: node.name,
export_names: node.export_names,
body_bytes: node.body_bytes,
root_reasons,
path,
})
}
entries.sort_by(compare_retain_path_entry)
{
partial: graph.partial || root_reason_partial,
roots: graph.roots,
entries,
}
}
}
///|
pub fn analyze_dce_report(bytes : Bytes) -> Result[DceReport, WiteError] {
match analyze_call_graph(bytes) {
Ok(graph) => {
let removable_functions : Array[FunctionSize] = []
for node in graph.nodes {
if not(node.reachable_from_roots) {
removable_functions.push({
function_index: node.function_index,
body_bytes: node.body_bytes,
name: node.name,
export_names: node.export_names,
})
}
}
removable_functions.sort_by((a, b) => if a.body_bytes > b.body_bytes {
-1
} else if a.body_bytes < b.body_bytes {
1
} else if a.function_index < b.function_index {
-1
} else if a.function_index > b.function_index {
1
} else {
0
})
Ok({
roots: graph.roots,
partial: graph.partial,
removable_function_count: removable_functions
.length()
.reinterpret_as_uint(),
removable_body_bytes: graph.dead_body_bytes,
removable_functions,
})
}
Err(e) => Err(e)
}
}
///|
priv struct DceApplyResult {
bytes : Bytes
removed_function_count : UInt
removed_body_bytes : UInt
dropped_name_section : Bool
}
///|
priv struct DfeApplyResult {
bytes : Bytes
removed_function_count : UInt
removed_body_bytes : UInt
dropped_name_section : Bool
}
///|
priv struct CfpApplyResult {
bytes : Bytes
rewritten_call_count : UInt
}
///|
priv struct CfpConstApplyResult {
bytes : Bytes
rewritten_call_count : UInt
}
///|
priv struct SignatureRefineApplyResult {
bytes : Bytes
rewritten_call_count : UInt
}
///|
priv struct DuplicateImportElimApplyResult {
bytes : Bytes
removed_import_count : UInt
removed_function_import_count : UInt
removed_table_import_count : UInt
removed_memory_import_count : UInt
removed_global_import_count : UInt
dropped_name_section : Bool
}
///|
priv struct MsfApplyResult {
bytes : Bytes
removed_function_count : UInt
removed_body_bytes : UInt
dropped_name_section : Bool
}
///|
priv struct RumeApplyResult {
bytes : Bytes
removed_import_count : UInt
removed_table_count : UInt
removed_memory_count : UInt
removed_global_count : UInt
removed_element_count : UInt
removed_data_count : UInt
removed_export_count : UInt
}
///|
priv struct TypeRefineApplyResult {
bytes : Bytes
refined_type_count : UInt
}
///|
priv struct RemoveUnusedTypesApplyResult {
bytes : Bytes
removed_type_count : UInt
}
///|
priv struct SignaturePruneApplyResult {
bytes : Bytes
pruned_function_count : UInt
pruned_param_count : UInt
}
///|
fn bytes_equal(a : Bytes, b : Bytes) -> Bool {
if a.length() != b.length() {
return false
}
for i in 0.. Int? {
if spans.length() < min_prefix_span_count + 2 {
return None
}
let end_pos = spans.length() - 1
if spans[end_pos].opcode != 0x0bU {
return None
}
let direct_call_pos = end_pos - 1
if direct_call_pos >= min_prefix_span_count &&
spans[direct_call_pos].opcode == 0x10U {
return Some(direct_call_pos)
}
if direct_call_pos <= min_prefix_span_count ||
spans[direct_call_pos].opcode != 0x0fU {
return None
}
let call_pos = direct_call_pos - 1
if call_pos >= min_prefix_span_count && spans[call_pos].opcode == 0x10U {
return Some(call_pos)
}
None
}
///|
fn parse_forward_call_target_with_param_count(
body_bytes : Bytes,
param_count : UInt,
) -> UInt? {
let prefix_result = try? parse_local_decl_prefix_raise(body_bytes)
let prefix = match prefix_result {
Ok(v) => v
Err(_) => return None
}
let prefix_end = prefix.prefix_end
let param_count_i = UInt::reinterpret_as_int(param_count)
if param_count_i < 0 {
return None
}
let prefix_end_result = try? parse_local_decl_prefix_end(body_bytes)
match prefix_end_result {
Ok(v) => if v != prefix_end { return None }
Err(_) => return None
}
let instr_bytes = body_bytes[prefix_end:body_bytes.length()].to_bytes()
let spans = match parse_instruction_spans(instr_bytes) {
Some(v) => v
None => return None
}
let call_pos = match detect_forward_call_span_position(spans, param_count_i) {
Some(v) => v
None => return None
}
if call_pos != param_count_i {
return None
}
for i in 0..
if local_index != i.reinterpret_as_uint() {
return None
}
None => return None
}
}
decode_span_u32_immediate(instr_bytes, spans[call_pos])
}
///|
priv struct SignatureRefineAlias {
target : UInt
dropped_param_count : UInt
}
///|
fn parse_forward_call_target_with_prefix_param_count(
body_bytes : Bytes,
) -> (UInt, UInt)? {
let prefix_result = try? parse_local_decl_prefix_raise(body_bytes)
let prefix = match prefix_result {
Ok(v) => v
Err(_) => return None
}
let prefix_end = prefix.prefix_end
let prefix_end_result = try? parse_local_decl_prefix_end(body_bytes)
match prefix_end_result {
Ok(v) => if v != prefix_end { return None }
Err(_) => return None
}
let instr_bytes = body_bytes[prefix_end:body_bytes.length()].to_bytes()
let spans = match parse_instruction_spans(instr_bytes) {
Some(v) => v
None => return None
}
let call_pos = match detect_forward_call_span_position(spans, 0) {
Some(v) => v
None => return None
}
for i in 0..
if local_index != i.reinterpret_as_uint() {
return None
}
None => return None
}
}
match decode_span_u32_immediate(instr_bytes, spans[call_pos]) {
Some(target) => Some((target, call_pos.reinterpret_as_uint()))
None => None
}
}
///|
fn parse_forward_call_target_with_appended_i32_consts(
body_bytes : Bytes,
wrapper_param_count : UInt,
) -> (UInt, Array[Bytes])? {
let prefix_result = try? parse_local_decl_prefix_raise(body_bytes)
let prefix = match prefix_result {
Ok(v) => v
Err(_) => return None
}
let prefix_end = prefix.prefix_end
let prefix_end_result = try? parse_local_decl_prefix_end(body_bytes)
match prefix_end_result {
Ok(v) => if v != prefix_end { return None }
Err(_) => return None
}
let instr_bytes = body_bytes[prefix_end:body_bytes.length()].to_bytes()
let spans = match parse_instruction_spans(instr_bytes) {
Some(v) => v
None => return None
}
let wrapper_param_count_i = UInt::reinterpret_as_int(wrapper_param_count)
if wrapper_param_count_i < 0 {
return None
}
let call_pos = match
detect_forward_call_span_position(spans, wrapper_param_count_i) {
Some(v) => v
None => return None
}
if call_pos <= wrapper_param_count_i {
return None
}
for i in 0..
if local_index != i.reinterpret_as_uint() {
return None
}
None => return None
}
}
let appended_i32_const_instrs : Array[Bytes] = []
for i in wrapper_param_count_i.. Some((target, appended_i32_const_instrs))
None => None
}
}
///|
fn function_type_results_equal_with_gc_canonical_map(
a : FunctionTypeEntry,
b : FunctionTypeEntry,
gc_type_canonical_map : Map[UInt, UInt],
) -> Bool {
if a.results.length() != b.results.length() {
return false
}
for i in 0.. Bool {
value_type.length() == 1 && value_type[0] == 0x7fU.to_byte()
}
///|
fn collect_gc_type_canonical_map_from_entries_raise(
parsed_type_entries : Array[ParsedTypeSectionEntry],
) -> Map[UInt, UInt] raise WiteError {
let gc_type_canonical_map : Map[UInt, UInt] = {}
for i in 0.. Bool {
let wrapper_param_count = wrapper_type.params.length().reinterpret_as_uint()
let target_param_count = target_type.params.length().reinterpret_as_uint()
if target_param_count != wrapper_param_count + appended_i32_const_count {
return false
}
if not(
function_type_results_equal_with_gc_canonical_map(
wrapper_type, target_type, gc_type_canonical_map,
),
) {
return false
}
for i in 0.. Bool {
if a.params.length() != b.params.length() ||
a.results.length() != b.results.length() {
return false
}
for i in 0.. UInt? {
let wrapper_param_count = wrapper_type.params.length().reinterpret_as_uint()
let target_param_count = target_type.params.length().reinterpret_as_uint()
if forwarded_param_count != target_param_count {
return None
}
if wrapper_param_count <= target_param_count {
return None
}
if not(
function_type_results_equal_with_gc_canonical_map(
wrapper_type, target_type, gc_type_canonical_map,
),
) {
return None
}
let target_param_count_i = UInt::reinterpret_as_int(target_param_count)
for i in 0.. Map[UInt, CfpConstAlias] {
let alias_candidate : Map[UInt, CfpConstAlias] = {}
for i in 0..= function_type_by_index.length() {
continue
}
let wrapper_type_index = function_type_by_index[old_pos]
let wrapper_type = match
function_type_entry_by_type_index.get(wrapper_type_index) {
Some(v) => v
None => continue
}
let wrapper_param_count = wrapper_type.params.length().reinterpret_as_uint()
match
parse_forward_call_target_with_appended_i32_consts(
code_bodies[i],
wrapper_param_count,
) {
Some((target, appended_i32_const_instrs)) =>
if target != old_index {
let target_pos = UInt::reinterpret_as_int(target)
if target_pos >= 0 && target_pos < function_type_by_index.length() {
let target_type_index = function_type_by_index[target_pos]
match function_type_entry_by_type_index.get(target_type_index) {
Some(target_type) =>
if can_const_forward_with_appended_i32_to_target(
wrapper_type,
target_type,
appended_i32_const_instrs.length().reinterpret_as_uint(),
gc_type_canonical_map,
) {
alias_candidate.set(old_index, {
target,
appended_i32_const_instrs,
})
}
None => ()
}
}
}
None => ()
}
}
let resolved_alias : Map[UInt, CfpConstAlias] = {}
for i in 0.. {
let seen : Map[UInt, Bool] = {}
let mut current_target = initial_alias.target
let appended_i32_const_instrs = initial_alias.appended_i32_const_instrs.copy()
let mut valid = true
while true {
if current_target == old_index {
valid = false
break
}
if seen.get(current_target) is Some(_) {
valid = false
break
}
seen.set(current_target, true)
match alias_candidate.get(current_target) {
Some(next_alias) => {
appended_i32_const_instrs.append(
next_alias.appended_i32_const_instrs[:],
)
current_target = next_alias.target
}
None => break
}
}
if valid {
resolved_alias.set(old_index, {
target: current_target,
appended_i32_const_instrs,
})
}
}
None => ()
}
}
resolved_alias
}
///|
fn collect_call_forwarding_alias_map(
code_bodies : Array[Bytes],
imported_function_count : UInt,
function_type_by_index : Array[UInt],
function_type_entry_by_type_index : Map[UInt, FunctionTypeEntry],
function_param_count_by_index : Array[UInt],
gc_type_canonical_map : Map[UInt, UInt],
) -> Map[UInt, UInt] {
let alias_candidate : Map[UInt, UInt] = {}
for i in 0..= function_type_by_index.length() ||
old_pos >= function_param_count_by_index.length() {
continue
}
let param_count = function_param_count_by_index[old_pos]
match
parse_forward_call_target_with_param_count(code_bodies[i], param_count) {
Some(target) =>
if target != old_index {
let target_pos = UInt::reinterpret_as_int(target)
if target_pos >= 0 && target_pos < function_type_by_index.length() {
let wrapper_type_index = function_type_by_index[old_pos]
let target_type_index = function_type_by_index[target_pos]
match function_type_entry_by_type_index.get(wrapper_type_index) {
Some(wrapper_type) =>
match function_type_entry_by_type_index.get(target_type_index) {
Some(target_type) =>
if function_type_entries_equal_with_gc_canonical_map(
wrapper_type, target_type, gc_type_canonical_map,
) {
alias_candidate.set(old_index, target)
}
None => ()
}
None => ()
}
}
}
None => ()
}
}
let resolved_alias : Map[UInt, UInt] = {}
for i in 0.. {
let seen : Map[UInt, Bool] = {}
let mut current = initial_target
let mut valid = true
while true {
if current == old_index {
valid = false
break
}
if seen.get(current) is Some(_) {
valid = false
break
}
seen.set(current, true)
match alias_candidate.get(current) {
Some(next) => current = next
None => break
}
}
if valid {
resolved_alias.set(old_index, current)
}
}
None => ()
}
}
resolved_alias
}
///|
fn rewrite_instruction_calls_for_call_forwarding_raise(
instr_bytes : Bytes,
spans : Array[InstrSpan],
alias_map : Map[UInt, UInt],
) -> (Bytes, UInt) raise WiteError {
let out : Array[Byte] = []
let mut rewritten_call_count = 0U
for span in spans {
if span.opcode == 0x10U {
if span.end_ <= span.start + 1 {
raise WiteError::InvalidFormat("invalid call immediate in cfp")
}
let imm = instr_bytes[span.start + 1:span.end_].to_bytes()
let target = match decode_u32_leb128_bytes(imm) {
Some(v) => v
None =>
raise WiteError::InvalidFormat(
"failed to decode call immediate in cfp",
)
}
let rewritten_target = match alias_map.get(target) {
Some(v) => v
None => target
}
if rewritten_target != target &&
encode_u32_leb128(rewritten_target).length() <=
encode_u32_leb128(target).length() {
rewritten_call_count += 1U
out.push(0x10U.to_byte())
out.append(encode_u32_leb128(rewritten_target)[:])
} else {
out.append(instr_bytes[span.start:span.end_].to_array())
}
} else {
out.append(instr_bytes[span.start:span.end_].to_array())
}
}
(Bytes::from_array(out[:]), rewritten_call_count)
}
///|
fn rewrite_instruction_calls_for_const_call_forwarding_raise(
instr_bytes : Bytes,
spans : Array[InstrSpan],
alias_map : Map[UInt, CfpConstAlias],
) -> (Bytes, UInt) raise WiteError {
let out : Array[Byte] = []
let mut rewritten_call_count = 0U
for span in spans {
if span.opcode == 0x10U {
if span.end_ <= span.start + 1 {
raise WiteError::InvalidFormat("invalid call immediate in cfp-const")
}
let imm = instr_bytes[span.start + 1:span.end_].to_bytes()
let target = match decode_u32_leb128_bytes(imm) {
Some(v) => v
None =>
raise WiteError::InvalidFormat(
"failed to decode call immediate in cfp-const",
)
}
match alias_map.get(target) {
Some(alias_info) => {
for const_instr in alias_info.appended_i32_const_instrs {
out.append(const_instr[:].to_array())
}
out.push(0x10U.to_byte())
out.append(encode_u32_leb128(alias_info.target)[:])
rewritten_call_count += 1U
}
None => out.append(instr_bytes[span.start:span.end_].to_array())
}
} else {
out.append(instr_bytes[span.start:span.end_].to_array())
}
}
(Bytes::from_array(out[:]), rewritten_call_count)
}
///|
fn rewrite_code_section_payload_for_const_call_forwarding_raise(
payload : Bytes,
alias_map : Map[UInt, CfpConstAlias],
) -> (Bytes, UInt) raise WiteError {
let bodies = parse_code_bodies(payload)
let rewritten_bodies : Array[Bytes] = []
let mut rewritten_call_count = 0U
for body in bodies {
let prefix_end = parse_local_decl_prefix_end(body)
let instr_bytes = body[prefix_end:body.length()].to_bytes()
let spans = parse_instruction_spans_raise(instr_bytes)
let (rewritten_instr, rewritten_count) = rewrite_instruction_calls_for_const_call_forwarding_raise(
instr_bytes, spans, alias_map,
)
rewritten_call_count += rewritten_count
if rewritten_count > 0U {
let rewritten_body : Array[Byte] = body[0:prefix_end].to_array()
rewritten_body.append(rewritten_instr[:].to_array())
rewritten_bodies.push(Bytes::from_array(rewritten_body[:]))
} else {
rewritten_bodies.push(body)
}
}
(
encode_code_section_payload_from_bodies(rewritten_bodies),
rewritten_call_count,
)
}
///|
fn apply_const_call_forwarding_propagation_raise(
bytes : Bytes,
) -> CfpConstApplyResult raise WiteError {
let sections = parse_core_sections_raise(bytes)
let mut type_payload : Bytes? = None
let mut import_payload : Bytes? = None
let mut function_payload : Bytes? = None
let mut code_payload : Bytes? = None
for section in sections {
let payload = bytes[section.payload_start:section.section_end].to_bytes()
match section.section_id {
1U => type_payload = Some(payload)
2U => import_payload = Some(payload)
3U => function_payload = Some(payload)
10U => code_payload = Some(payload)
_ => ()
}
}
let type_payload = match type_payload {
Some(v) => v
None => return { bytes, rewritten_call_count: 0U }
}
let function_payload = match function_payload {
Some(v) => v
None => return { bytes, rewritten_call_count: 0U }
}
let code_payload = match code_payload {
Some(v) => v
None => return { bytes, rewritten_call_count: 0U }
}
let local_function_type_indices = parse_function_type_indices(
function_payload,
)
let code_bodies = parse_code_bodies(code_payload)
if local_function_type_indices.length() != code_bodies.length() {
raise WiteError::InvalidFormat(
"function/code section count mismatch for cfp-const: function=" +
local_function_type_indices.length().to_string() +
" code=" +
code_bodies.length().to_string(),
)
}
let imported_function_type_indices = match import_payload {
Some(payload) => parse_import_function_type_indices(payload)
None => []
}
let function_type_by_index = imported_function_type_indices.copy()
function_type_by_index.append(local_function_type_indices[:])
let parsed_type_entries = parse_type_section_entries_with_refs_raise(
type_payload, "cfp-const",
)
let gc_type_canonical_map = collect_gc_type_canonical_map_from_entries_raise(
parsed_type_entries,
)
let function_type_entry_by_type_index : Map[UInt, FunctionTypeEntry] = {}
for i in 0.. has_alias = true)
if not(has_alias) {
return { bytes, rewritten_call_count: 0U }
}
let (rewritten_code_payload, rewritten_call_count) = rewrite_code_section_payload_for_const_call_forwarding_raise(
code_payload, alias_map,
)
if rewritten_call_count == 0U {
return { bytes, rewritten_call_count: 0U }
}
let out : Array[Byte] = bytes[0:8].to_array()
for section in sections {
if section.section_id == 10U {
append_encoded_section(out, 10U, rewritten_code_payload)
} else {
out.append(bytes[section.section_start:section.section_end].to_array())
}
}
{ bytes: Bytes::from_array(out[:]), rewritten_call_count }
}
///|
fn rewrite_code_section_payload_for_call_forwarding_raise(
payload : Bytes,
alias_map : Map[UInt, UInt],
) -> (Bytes, UInt) raise WiteError {
let bodies = parse_code_bodies(payload)
let rewritten_bodies : Array[Bytes] = []
let mut rewritten_call_count = 0U
for body in bodies {
let prefix_end = parse_local_decl_prefix_end(body)
let instr_bytes = body[prefix_end:body.length()].to_bytes()
let spans = parse_instruction_spans_raise(instr_bytes)
let (rewritten_instr, rewritten_count) = rewrite_instruction_calls_for_call_forwarding_raise(
instr_bytes, spans, alias_map,
)
rewritten_call_count += rewritten_count
if rewritten_count > 0U {
let rewritten_body : Array[Byte] = body[0:prefix_end].to_array()
rewritten_body.append(rewritten_instr[:].to_array())
rewritten_bodies.push(Bytes::from_array(rewritten_body[:]))
} else {
rewritten_bodies.push(body)
}
}
(
encode_code_section_payload_from_bodies(rewritten_bodies),
rewritten_call_count,
)
}
///|
fn apply_call_forwarding_propagation_raise(
bytes : Bytes,
) -> CfpApplyResult raise WiteError {
let sections = parse_core_sections_raise(bytes)
let mut type_payload : Bytes? = None
let mut import_payload : Bytes? = None
let mut function_payload : Bytes? = None
let mut code_payload : Bytes? = None
for section in sections {
let payload = bytes[section.payload_start:section.section_end].to_bytes()
match section.section_id {
1U => type_payload = Some(payload)
2U => import_payload = Some(payload)
3U => function_payload = Some(payload)
10U => code_payload = Some(payload)
_ => ()
}
}
let type_payload = match type_payload {
Some(v) => v
None => return { bytes, rewritten_call_count: 0U }
}
let function_payload = match function_payload {
Some(v) => v
None => return { bytes, rewritten_call_count: 0U }
}
let code_payload = match code_payload {
Some(v) => v
None => return { bytes, rewritten_call_count: 0U }
}
let local_function_type_indices = parse_function_type_indices(
function_payload,
)
let code_bodies = parse_code_bodies(code_payload)
if local_function_type_indices.length() != code_bodies.length() {
raise WiteError::InvalidFormat(
"function/code section count mismatch for cfp: function=" +
local_function_type_indices.length().to_string() +
" code=" +
code_bodies.length().to_string(),
)
}
let imported_function_type_indices = match import_payload {
Some(payload) => parse_import_function_type_indices(payload)
None => []
}
let imported_function_count = imported_function_type_indices
.length()
.reinterpret_as_uint()
let parsed_type_entries = parse_type_section_entries_with_refs_raise(
type_payload, "cfp",
)
let type_count_u = parsed_type_entries.length().reinterpret_as_uint()
let function_type_entry_by_type_index : Map[UInt, FunctionTypeEntry] = {}
for i in 0..= type_count_u {
raise WiteError::InvalidFormat(
"type index out of range in cfp: " + type_index.to_string(),
)
}
let type_pos = UInt::reinterpret_as_int(type_index)
if type_pos < 0 || type_pos >= parsed_type_entries.length() {
raise WiteError::InvalidFormat(
"type index position out of range in cfp: " + type_index.to_string(),
)
}
if parsed_type_entries[type_pos].form != 0x60U {
raise WiteError::InvalidFormat(
"non-function type index used by function in cfp: " +
type_index.to_string(),
)
}
match function_type_entry_by_type_index.get(type_index) {
Some(function_type_entry) =>
function_param_count_by_index.push(
function_type_entry.params.length().reinterpret_as_uint(),
)
None =>
raise WiteError::InvalidFormat(
"missing function type entry in cfp: " + type_index.to_string(),
)
}
}
let alias_map = collect_call_forwarding_alias_map(
code_bodies, imported_function_count, function_type_by_index, function_type_entry_by_type_index,
function_param_count_by_index, gc_type_canonical_map,
)
let mut has_alias = false
alias_map.each((_, _target) => has_alias = true)
if not(has_alias) {
return { bytes, rewritten_call_count: 0U }
}
let (rewritten_code_payload, rewritten_call_count) = rewrite_code_section_payload_for_call_forwarding_raise(
code_payload, alias_map,
)
if rewritten_call_count == 0U {
return { bytes, rewritten_call_count: 0U }
}
let out : Array[Byte] = bytes[0:8].to_array()
for section in sections {
if section.section_id == 10U {
append_encoded_section(out, 10U, rewritten_code_payload)
} else {
out.append(bytes[section.section_start:section.section_end].to_array())
}
}
{ bytes: Bytes::from_array(out[:]), rewritten_call_count }
}
///|
fn collect_signature_refine_alias_map(
code_bodies : Array[Bytes],
imported_function_count : UInt,
function_type_by_index : Array[UInt],
function_type_entry_by_type_index : Map[UInt, FunctionTypeEntry],
gc_type_canonical_map : Map[UInt, UInt],
) -> Map[UInt, SignatureRefineAlias] {
let alias_candidate : Map[UInt, SignatureRefineAlias] = {}
for i in 0..= function_type_by_index.length() {
continue
}
let wrapper_type_index = function_type_by_index[old_pos]
let wrapper_type = match
function_type_entry_by_type_index.get(wrapper_type_index) {
Some(v) => v
None => continue
}
match parse_forward_call_target_with_prefix_param_count(code_bodies[i]) {
Some((target, forwarded_param_count)) =>
if target != old_index {
let target_pos = UInt::reinterpret_as_int(target)
if target_pos >= 0 && target_pos < function_type_by_index.length() {
let target_type_index = function_type_by_index[target_pos]
match function_type_entry_by_type_index.get(target_type_index) {
Some(target_type) =>
match
can_signature_refine_forward_to_target(
wrapper_type, target_type, forwarded_param_count, gc_type_canonical_map,
) {
Some(dropped_param_count) =>
alias_candidate.set(old_index, {
target,
dropped_param_count,
})
None => ()
}
None => ()
}
}
}
None => ()
}
}
let resolved_alias : Map[UInt, SignatureRefineAlias] = {}
for i in 0.. {
let seen : Map[UInt, Bool] = {}
let mut current_target = initial_alias.target
let mut dropped_param_count = initial_alias.dropped_param_count
let mut valid = true
while true {
if current_target == old_index {
valid = false
break
}
if seen.get(current_target) is Some(_) {
valid = false
break
}
seen.set(current_target, true)
match alias_candidate.get(current_target) {
Some(next_alias) => {
dropped_param_count += next_alias.dropped_param_count
current_target = next_alias.target
}
None => break
}
}
if valid {
resolved_alias.set(old_index, {
target: current_target,
dropped_param_count,
})
}
}
None => ()
}
}
resolved_alias
}
///|
fn rewrite_instruction_calls_for_signature_refining_raise(
instr_bytes : Bytes,
spans : Array[InstrSpan],
alias_map : Map[UInt, SignatureRefineAlias],
) -> (Bytes, UInt) raise WiteError {
let out : Array[Byte] = []
let mut rewritten_call_count = 0U
for span in spans {
if span.opcode == 0x10U {
if span.end_ <= span.start + 1 {
raise WiteError::InvalidFormat(
"invalid call immediate in signature-refine",
)
}
let imm = instr_bytes[span.start + 1:span.end_].to_bytes()
let target = match decode_u32_leb128_bytes(imm) {
Some(v) => v
None =>
raise WiteError::InvalidFormat(
"failed to decode call immediate in signature-refine",
)
}
match alias_map.get(target) {
Some(alias_info) => {
for _ in 0U.. out.append(instr_bytes[span.start:span.end_].to_array())
}
} else {
out.append(instr_bytes[span.start:span.end_].to_array())
}
}
(Bytes::from_array(out[:]), rewritten_call_count)
}
///|
fn rewrite_code_section_payload_for_signature_refining_raise(
payload : Bytes,
alias_map : Map[UInt, SignatureRefineAlias],
) -> (Bytes, UInt) raise WiteError {
let bodies = parse_code_bodies(payload)
let rewritten_bodies : Array[Bytes] = []
let mut rewritten_call_count = 0U
for body in bodies {
let prefix_end = parse_local_decl_prefix_end(body)
let instr_bytes = body[prefix_end:body.length()].to_bytes()
let spans = parse_instruction_spans_raise(instr_bytes)
let (rewritten_instr, rewritten_count) = rewrite_instruction_calls_for_signature_refining_raise(
instr_bytes, spans, alias_map,
)
rewritten_call_count += rewritten_count
if rewritten_count > 0U {
let rewritten_body : Array[Byte] = body[0:prefix_end].to_array()
rewritten_body.append(rewritten_instr[:].to_array())
rewritten_bodies.push(Bytes::from_array(rewritten_body[:]))
} else {
rewritten_bodies.push(body)
}
}
(
encode_code_section_payload_from_bodies(rewritten_bodies),
rewritten_call_count,
)
}
///|
fn apply_signature_refining_raise(
bytes : Bytes,
) -> SignatureRefineApplyResult raise WiteError {
let sections = parse_core_sections_raise(bytes)
let mut type_payload : Bytes? = None
let mut import_payload : Bytes? = None
let mut function_payload : Bytes? = None
let mut code_payload : Bytes? = None
for section in sections {
let payload = bytes[section.payload_start:section.section_end].to_bytes()
match section.section_id {
1U => type_payload = Some(payload)
2U => import_payload = Some(payload)
3U => function_payload = Some(payload)
10U => code_payload = Some(payload)
_ => ()
}
}
let type_payload = match type_payload {
Some(v) => v
None => return { bytes, rewritten_call_count: 0U }
}
let function_payload = match function_payload {
Some(v) => v
None => return { bytes, rewritten_call_count: 0U }
}
let code_payload = match code_payload {
Some(v) => v
None => return { bytes, rewritten_call_count: 0U }
}
let local_function_type_indices = parse_function_type_indices(
function_payload,
)
let code_bodies = parse_code_bodies(code_payload)
if local_function_type_indices.length() != code_bodies.length() {
raise WiteError::InvalidFormat(
"function/code section count mismatch for signature-refine: function=" +
local_function_type_indices.length().to_string() +
" code=" +
code_bodies.length().to_string(),
)
}
let imported_function_type_indices = match import_payload {
Some(payload) => parse_import_function_type_indices(payload)
None => []
}
let parsed_type_entries = parse_type_section_entries_with_refs_raise(
type_payload, "signature-refine",
)
let type_count_u = parsed_type_entries.length().reinterpret_as_uint()
let gc_type_canonical_map = collect_gc_type_canonical_map_from_entries_raise(
parsed_type_entries,
)
let function_type_entry_by_type_index : Map[UInt, FunctionTypeEntry] = {}
for i in 0..= type_count_u {
raise WiteError::InvalidFormat(
"type index out of range in signature-refine: " + type_index.to_string(),
)
}
let type_pos = UInt::reinterpret_as_int(type_index)
if type_pos < 0 || type_pos >= parsed_type_entries.length() {
raise WiteError::InvalidFormat(
"type index position out of range in signature-refine: " +
type_index.to_string(),
)
}
if parsed_type_entries[type_pos].form != 0x60U {
raise WiteError::InvalidFormat(
"non-function type index used by function in signature-refine: " +
type_index.to_string(),
)
}
}
let imported_function_count = imported_function_type_indices
.length()
.reinterpret_as_uint()
let alias_map = collect_signature_refine_alias_map(
code_bodies, imported_function_count, function_type_by_index, function_type_entry_by_type_index,
gc_type_canonical_map,
)
let mut has_alias = false
alias_map.each((_index, _alias) => has_alias = true)
if not(has_alias) {
return { bytes, rewritten_call_count: 0U }
}
let (rewritten_code_payload, rewritten_call_count) = rewrite_code_section_payload_for_signature_refining_raise(
code_payload, alias_map,
)
if rewritten_call_count == 0U {
return { bytes, rewritten_call_count: 0U }
}
let out : Array[Byte] = bytes[0:8].to_array()
for section in sections {
if section.section_id == 10U {
append_encoded_section(out, 10U, rewritten_code_payload)
} else {
out.append(bytes[section.section_start:section.section_end].to_array())
}
}
{ bytes: Bytes::from_array(out[:]), rewritten_call_count }
}
///|
fn apply_remove_unused_module_elements(
bytes : Bytes,
config : OptimizeConfig,
) -> RumeApplyResult {
let result = try? apply_remove_unused_module_elements_raise(bytes, config)
match result {
Ok(v) => v
Err(_) =>
{
bytes,
removed_import_count: 0U,
removed_table_count: 0U,
removed_memory_count: 0U,
removed_global_count: 0U,
removed_element_count: 0U,
removed_data_count: 0U,
removed_export_count: 0U,
}
}
}
///|
fn apply_remove_unused_module_elements_raise(
bytes : Bytes,
config : OptimizeConfig,
) -> RumeApplyResult raise WiteError {
let sections = parse_core_sections_raise(bytes)
let mut import_section_payload : Bytes? = None
let mut export_section_payload : Bytes? = None
let mut table_section_payload : Bytes? = None
let mut memory_section_payload : Bytes? = None
let mut global_section_payload : Bytes? = None
let mut element_section_payload : Bytes? = None
let mut data_section_payload : Bytes? = None
let mut import_entries : Array[ImportSectionEntry] = []
let used_table_indices : Array[UInt] = []
let used_memory_indices : Array[UInt] = []
let global_root_indices : Array[UInt] = []
let mut removed_export_by_name_count = 0U
let mut analysis_complete = true
for section in sections {
let payload = bytes[section.payload_start:section.section_end].to_bytes()
match section.section_id {
2U => {
import_section_payload = Some(payload)
import_entries = parse_import_section_entries_raise(payload)
}
4U => table_section_payload = Some(payload)
5U => memory_section_payload = Some(payload)
6U => global_section_payload = Some(payload)
7U => {
let mut effective_export_payload = payload
if config.closed_world &&
not(config.safe_mode) &&
config.closed_world_root_exports.length() > 0 {
let (filtered, removed) = rewrite_export_section_payload_for_keep_names_raise(
payload,
config.closed_world_root_exports,
)
effective_export_payload = filtered
removed_export_by_name_count += removed
}
export_section_payload = Some(effective_export_payload)
for index in parse_export_table_indices(effective_export_payload) {
push_unique_u32(used_table_indices, index)
}
for index in parse_export_memory_indices(effective_export_payload) {
push_unique_u32(used_memory_indices, index)
}
for index in parse_export_global_indices(effective_export_payload) {
push_unique_u32(global_root_indices, index)
}
}
9U => {
element_section_payload = Some(payload)
let global_result = try? collect_used_global_indices_from_element_section_raise(
payload,
)
match global_result {
Ok(indices) =>
for index in indices {
push_unique_u32(global_root_indices, index)
}
Err(_) => analysis_complete = false
}
}
10U => {
match collect_used_table_indices_from_code_section(payload) {
Some(indices) =>
for index in indices {
push_unique_u32(used_table_indices, index)
}
None => analysis_complete = false
}
match collect_used_memory_indices_from_code_section(payload) {
Some(indices) =>
for index in indices {
push_unique_u32(used_memory_indices, index)
}
None => analysis_complete = false
}
match collect_used_global_indices_from_code_section(payload) {
Some(indices) =>
for index in indices {
push_unique_u32(global_root_indices, index)
}
None => analysis_complete = false
}
}
11U => {
data_section_payload = Some(payload)
let data_result = try? collect_used_memory_and_global_indices_from_data_section_raise(
payload,
)
match data_result {
Ok((memory_indices, global_indices)) => {
for index in memory_indices {
push_unique_u32(used_memory_indices, index)
}
for index in global_indices {
push_unique_u32(global_root_indices, index)
}
}
Err(_) => analysis_complete = false
}
}
_ => ()
}
}
if not(analysis_complete) {
return {
bytes,
removed_import_count: 0U,
removed_table_count: 0U,
removed_memory_count: 0U,
removed_global_count: 0U,
removed_element_count: 0U,
removed_data_count: 0U,
removed_export_count: 0U,
}
}
let mut imported_table_count = 0U
let mut imported_memory_count = 0U
let mut imported_global_count = 0U
for entry in import_entries {
match entry.kind {
1U => imported_table_count += 1U
2U => imported_memory_count += 1U
3U => imported_global_count += 1U
_ => ()
}
}
let keep_import_tables : Array[Bool] = []
for i in 0U.. parse_table_section_entries_raise(payload)
None => []
}
let local_memory_entries = match memory_section_payload {
Some(payload) => parse_memory_section_entries_raise(payload)
None => []
}
let local_global_entries : Array[GlobalSectionEntry] = match
global_section_payload {
Some(payload) => parse_global_section_entries_with_refs_raise(payload)
None => []
}
let used_global_indices : Array[UInt] = []
for index in global_root_indices {
push_unique_u32(used_global_indices, index)
}
let queue : Array[UInt] = used_global_indices.copy()
let mut queue_index = 0
while queue_index < queue.length() {
let current = queue[queue_index]
queue_index += 1
if current < imported_global_count {
continue
}
let local_index_u = current - imported_global_count
let local_index = UInt::reinterpret_as_int(local_index_u)
if local_index >= 0 && local_index < local_global_entries.length() {
for dep in local_global_entries[local_index].init_global_refs {
if not(used_global_indices.contains(dep)) {
push_unique_u32(used_global_indices, dep)
queue.push(dep)
}
}
}
}
let keep_import_globals : Array[Bool] = []
for i in 0U..= keep_import_tables.length() {
raise WiteError::InvalidFormat("table import keep index out of range")
}
if keep_import_tables[index] {
table_index_map.set(i, kept_imported_table_count)
kept_imported_table_count += 1U
}
}
let keep_local_tables : Array[Bool] = []
let mut kept_local_table_count = 0U
let mut removed_table_count = 0U
for i in 0..= keep_import_memories.length() {
raise WiteError::InvalidFormat("memory import keep index out of range")
}
if keep_import_memories[index] {
memory_index_map.set(i, kept_imported_memory_count)
kept_imported_memory_count += 1U
}
}
let keep_local_memories : Array[Bool] = []
let mut kept_local_memory_count = 0U
let mut removed_memory_count = 0U
for i in 0..= keep_import_globals.length() {
raise WiteError::InvalidFormat("global import keep index out of range")
}
if keep_import_globals[index] {
global_index_map.set(i, kept_imported_global_count)
kept_imported_global_count += 1U
}
}
let keep_local_globals : Array[Bool] = []
let mut kept_local_global_count = 0U
let mut removed_global_count = 0U
for i in 0.. {
let (rewritten, removed) = rewrite_import_section_payload_for_keep_raise(
payload, keep_import_tables, keep_import_memories, keep_import_globals,
)
rewritten_import_payload = Some(rewritten)
removed_import_count = removed
}
None => ()
}
let mut removed_element_count = 0U
let mut rewritten_element_payload : Bytes? = None
let element_index_map : Map[UInt, UInt] = {}
match element_section_payload {
Some(payload) => {
let (rewritten, removed_segments, segment_index_map) = rewrite_element_section_payload_for_table_index_map_raise(
payload, table_index_map, global_index_map,
)
rewritten_element_payload = Some(rewritten)
removed_element_count = removed_segments
segment_index_map.each((key, value) => element_index_map.set(key, value))
}
None => ()
}
let mut removed_data_count = 0U
let mut rewritten_data_payload : Bytes? = None
let data_index_map : Map[UInt, UInt] = {}
match data_section_payload {
Some(payload) => {
let (rewritten, removed_segments, segment_index_map) = rewrite_data_section_payload_for_memory_global_index_map_raise(
payload, memory_index_map, global_index_map,
)
rewritten_data_payload = Some(rewritten)
removed_data_count = removed_segments
segment_index_map.each((key, value) => data_index_map.set(key, value))
}
None => ()
}
let mut removed_export_count = removed_export_by_name_count
let mut rewritten_export_payload : Bytes? = None
match export_section_payload {
Some(payload) => {
let (rewritten, removed) = rewrite_export_section_payload_for_module_index_maps_raise(
payload, table_index_map, memory_index_map, global_index_map,
)
rewritten_export_payload = Some(rewritten)
removed_export_count += removed
}
None => ()
}
if removed_import_count == 0U &&
removed_table_count == 0U &&
removed_memory_count == 0U &&
removed_global_count == 0U &&
removed_element_count == 0U &&
removed_data_count == 0U &&
removed_export_count == 0U {
return {
bytes,
removed_import_count,
removed_table_count,
removed_memory_count,
removed_global_count,
removed_element_count,
removed_data_count,
removed_export_count,
}
}
let out : Array[Byte] = bytes[0:8].to_array()
for section in sections {
let payload = bytes[section.payload_start:section.section_end].to_bytes()
match section.section_id {
2U =>
match rewritten_import_payload {
Some(rewritten) =>
if parse_vec_count(rewritten) > 0U {
append_encoded_section(out, 2U, rewritten)
}
None => ()
}
4U => {
let rewritten = rewrite_table_section_payload_for_keep_local_raise(
payload, keep_local_tables,
)
if parse_vec_count(rewritten) > 0U {
append_encoded_section(out, 4U, rewritten)
}
}
5U => {
let rewritten = rewrite_memory_section_payload_for_keep_local_raise(
payload, keep_local_memories,
)
if parse_vec_count(rewritten) > 0U {
append_encoded_section(out, 5U, rewritten)
}
}
6U => {
let rewritten = rewrite_global_section_payload_for_keep_local_and_global_index_map_raise(
payload, keep_local_globals, global_index_map,
)
if parse_vec_count(rewritten) > 0U {
append_encoded_section(out, 6U, rewritten)
}
}
7U =>
match rewritten_export_payload {
Some(rewritten) =>
if parse_vec_count(rewritten) > 0U {
append_encoded_section(out, 7U, rewritten)
}
None => ()
}
9U =>
match rewritten_element_payload {
Some(rewritten) =>
if parse_vec_count(rewritten) > 0U {
append_encoded_section(out, 9U, rewritten)
}
None =>
out.append(
bytes[section.section_start:section.section_end].to_array(),
)
}
10U =>
append_encoded_section(
out,
10U,
rewrite_code_section_payload_for_module_index_map_raise(
payload, table_index_map, memory_index_map, global_index_map, element_index_map,
data_index_map,
),
)
11U =>
match rewritten_data_payload {
Some(rewritten) =>
if parse_vec_count(rewritten) > 0U {
append_encoded_section(out, 11U, rewritten)
}
None =>
out.append(
bytes[section.section_start:section.section_end].to_array(),
)
}
_ =>
out.append(bytes[section.section_start:section.section_end].to_array())
}
}
{
bytes: Bytes::from_array(out[:]),
removed_import_count,
removed_table_count,
removed_memory_count,
removed_global_count,
removed_element_count,
removed_data_count,
removed_export_count,
}
}
///|
fn apply_type_refining(bytes : Bytes) -> TypeRefineApplyResult {
let result = try? apply_type_refining_raise(bytes)
match result {
Ok(v) => v
Err(_) => { bytes, refined_type_count: 0U }
}
}
///|
fn apply_type_refining_raise(
bytes : Bytes,
) -> TypeRefineApplyResult raise WiteError {
let sections = parse_core_sections_raise(bytes)
let mut type_section_payload : Bytes? = None
for section in sections {
match section.section_id {
1U =>
type_section_payload = Some(
bytes[section.payload_start:section.section_end].to_bytes(),
)
13U =>
raise WiteError::InvalidFormat(
"tag section is not supported in type-refine",
)
_ => ()
}
}
let type_payload = match type_section_payload {
Some(v) => v
None => return { bytes, refined_type_count: 0U }
}
let parsed_type_entries = parse_type_section_entries_with_refs_raise(
type_payload, "type-refine",
)
if parsed_type_entries.length() <= 1 {
return { bytes, refined_type_count: 0U }
}
let function_type_entry_by_type_index : Map[UInt, FunctionTypeEntry] = {}
let gc_type_canonical_map : Map[UInt, UInt] = {}
for i in 0.. v
None =>
raise WiteError::InvalidFormat(
"missing function type entry in type-refine: " +
old_type_index.to_string(),
)
}
let mut canonical = old_type_index
for j in 0.. v
None =>
raise WiteError::InvalidFormat(
"missing candidate function type entry in type-refine: " +
candidate_type_index.to_string(),
)
}
if function_type_entries_equal_with_gc_canonical_map(
current_function_type, candidate_function_type, gc_type_canonical_map,
) {
canonical = candidate_type_index
break
}
}
if canonical == old_type_index {
keep_types.push(true)
} else {
keep_types.push(false)
canonical_function_type_for_old.set(old_type_index, canonical)
refined_type_count += 1U
}
}
let type_index_map : Map[UInt, UInt] = {}
let mut new_type_count = 0U
for i in 0..
type_index_map.set(
old_type_index,
remap_type_index_raise(canonical, type_index_map),
)
None => ()
}
}
if refined_type_count == 0U {
return { bytes, refined_type_count: 0U }
}
let out : Array[Byte] = bytes[0:8].to_array()
for section in sections {
let payload = bytes[section.payload_start:section.section_end].to_bytes()
match section.section_id {
1U => {
let rewritten = rewrite_type_section_payload_for_keep_raise(
payload, keep_types,
)
if parse_vec_count(rewritten) > 0U {
append_encoded_section(out, 1U, rewritten)
}
}
2U =>
append_encoded_section(
out,
2U,
rewrite_import_section_payload_for_type_index_map_raise(
payload, type_index_map,
),
)
3U =>
append_encoded_section(
out,
3U,
rewrite_function_section_payload_for_type_index_map_raise(
payload, type_index_map,
),
)
10U =>
append_encoded_section(
out,
10U,
rewrite_code_section_payload_for_type_index_map_raise(
payload, type_index_map,
),
)
_ =>
out.append(bytes[section.section_start:section.section_end].to_array())
}
}
{ bytes: Bytes::from_array(out[:]), refined_type_count }
}
///|
fn apply_signature_pruning(bytes : Bytes) -> SignaturePruneApplyResult {
let result = try? apply_signature_pruning_raise(bytes)
match result {
Ok(v) => v
Err(_) => { bytes, pruned_function_count: 0U, pruned_param_count: 0U }
}
}
///|
fn apply_signature_pruning_raise(
bytes : Bytes,
) -> SignaturePruneApplyResult raise WiteError {
let sections = parse_core_sections_raise(bytes)
let mut imported_function_count = 0U
let mut type_section_payload : Bytes? = None
let mut function_section_payload : Bytes? = None
let mut code_section_payload : Bytes? = None
let protected_function_indices : Array[UInt] = []
for section in sections {
let payload = bytes[section.payload_start:section.section_end].to_bytes()
match section.section_id {
1U => type_section_payload = Some(payload)
2U => imported_function_count += parse_import_function_count(payload)
3U => function_section_payload = Some(payload)
6U =>
for index in parse_global_section_function_refs_raise(payload) {
push_unique_u32(protected_function_indices, index)
}
7U => {
let exports = parse_export_function_names(payload)
exports.each((index, _names) => push_unique_u32(
protected_function_indices, index,
))
}
8U =>
push_unique_u32(
protected_function_indices,
parse_start_function_index(payload),
)
9U =>
for index in parse_element_section_function_refs_raise(payload) {
push_unique_u32(protected_function_indices, index)
}
10U => code_section_payload = Some(payload)
13U =>
raise WiteError::InvalidFormat(
"tag section is not supported in signature-prune",
)
_ => ()
}
}
let type_payload = match type_section_payload {
Some(v) => v
None => return { bytes, pruned_function_count: 0U, pruned_param_count: 0U }
}
let function_payload = match function_section_payload {
Some(v) => v
None => return { bytes, pruned_function_count: 0U, pruned_param_count: 0U }
}
let code_payload = match code_section_payload {
Some(v) => v
None => return { bytes, pruned_function_count: 0U, pruned_param_count: 0U }
}
let function_type_indices = parse_function_type_indices(function_payload)
let code_bodies = parse_code_bodies(code_payload)
if function_type_indices.length() != code_bodies.length() {
raise WiteError::InvalidFormat(
"function/code section count mismatch for signature-prune: function=" +
function_type_indices.length().to_string() +
" code=" +
code_bodies.length().to_string(),
)
}
let parsed_type_entries = parse_type_section_entries_with_refs_raise(
type_payload, "signature-prune",
)
let type_count_u = parsed_type_entries.length().reinterpret_as_uint()
let function_type_entry_by_type_index : Map[UInt, FunctionTypeEntry] = {}
for i in 0..= type_count_u {
raise WiteError::InvalidFormat(
"type index out of range in signature-prune: " + type_index.to_string(),
)
}
let type_pos = UInt::reinterpret_as_int(type_index)
if type_pos < 0 || type_pos >= parsed_type_entries.length() {
raise WiteError::InvalidFormat(
"type index position out of range in signature-prune: " +
type_index.to_string(),
)
}
if parsed_type_entries[type_pos].form != 0x60U {
raise WiteError::InvalidFormat(
"non-function type index used by function in signature-prune: " +
type_index.to_string(),
)
}
}
for index in collect_ref_func_indices_from_code_section_raise(code_payload) {
push_unique_u32(protected_function_indices, index)
}
let rewritten_type_entries : Array[Bytes] = []
for entry in parsed_type_entries {
rewritten_type_entries.push(entry.raw)
}
let rewritten_function_type_indices = function_type_indices.copy()
let rewritten_code_bodies = code_bodies.copy()
let old_param_count_by_local_pos : Map[UInt, UInt] = {}
let removed_param_count_by_local_pos : Map[UInt, UInt] = {}
let removed_param_count_by_function : Map[UInt, UInt] = {}
let mut pruned_function_count = 0U
let mut pruned_param_count = 0U
for i in 0.. v
None =>
raise WiteError::InvalidFormat(
"non-function type index used by function in signature-prune: " +
type_index.to_string(),
)
}
let old_param_count = type_entry.params.length().reinterpret_as_uint()
if old_param_count == 0U {
continue
}
let body = code_bodies[i]
let prefix_end = parse_local_decl_prefix_end(body)
let instr_bytes = body[prefix_end:body.length()].to_bytes()
let spans = parse_instruction_spans_raise(instr_bytes)
let used_param_indices = collect_used_param_indices_from_instruction_raise(
instr_bytes, spans, old_param_count,
)
let removed_param_count = compute_removed_trailing_param_count(
old_param_count, used_param_indices,
)
if removed_param_count == 0U {
continue
}
let new_param_count = old_param_count - removed_param_count
let new_param_count_i = UInt::reinterpret_as_int(new_param_count)
if new_param_count_i < 0 || new_param_count_i > type_entry.params.length() {
raise WiteError::InvalidFormat(
"invalid param trim count in signature-prune: old=" +
old_param_count.to_string() +
" removed=" +
removed_param_count.to_string(),
)
}
let new_params : Array[Bytes] = []
for j in 0.. v
None => {
let idx = rewritten_type_entries.length().reinterpret_as_uint()
rewritten_type_entries.push(new_type_entry)
idx
}
}
rewritten_function_type_indices[i] = new_type_index
let local_pos = i.reinterpret_as_uint()
old_param_count_by_local_pos.set(local_pos, old_param_count)
removed_param_count_by_local_pos.set(local_pos, removed_param_count)
removed_param_count_by_function.set(function_index, removed_param_count)
pruned_function_count += 1U
pruned_param_count += removed_param_count
}
if pruned_function_count == 0U {
return { bytes, pruned_function_count: 0U, pruned_param_count: 0U }
}
for i in 0..
if removed_param_count > 0U {
let old_param_count = match
old_param_count_by_local_pos.get(local_pos) {
Some(v) => v
None =>
raise WiteError::InvalidFormat(
"signature-prune local metadata missing",
)
}
rewritten_code_bodies[i] = rewrite_code_body_for_signature_pruning_raise(
rewritten_code_bodies[i],
old_param_count,
removed_param_count,
)
}
None => ()
}
}
for i in 0.. append_encoded_section(out, 1U, rewritten_type_payload)
3U => append_encoded_section(out, 3U, rewritten_function_payload)
10U => append_encoded_section(out, 10U, rewritten_code_payload)
_ =>
out.append(bytes[section.section_start:section.section_end].to_array())
}
}
{
bytes: Bytes::from_array(out[:]),
pruned_function_count,
pruned_param_count,
}
}
///|
fn apply_remove_unused_types(bytes : Bytes) -> RemoveUnusedTypesApplyResult {
let result = try? apply_remove_unused_types_raise(bytes)
match result {
Ok(v) => v
Err(_) => { bytes, removed_type_count: 0U }
}
}
///|
fn apply_remove_unused_types_raise(
bytes : Bytes,
) -> RemoveUnusedTypesApplyResult raise WiteError {
let sections = parse_core_sections_raise(bytes)
let mut type_section_payload : Bytes? = None
let mut import_section_payload : Bytes? = None
let mut function_section_payload : Bytes? = None
let mut code_section_payload : Bytes? = None
for section in sections {
let payload = bytes[section.payload_start:section.section_end].to_bytes()
match section.section_id {
1U => type_section_payload = Some(payload)
2U => import_section_payload = Some(payload)
3U => function_section_payload = Some(payload)
10U => code_section_payload = Some(payload)
13U =>
raise WiteError::InvalidFormat(
"tag section is not supported in remove-unused-types",
)
_ => ()
}
}
let type_payload = match type_section_payload {
Some(v) => v
None => return { bytes, removed_type_count: 0U }
}
let type_entries = parse_type_section_entries_with_refs_raise(
type_payload, "remove-unused-types",
)
if type_entries.length() == 0 {
return { bytes, removed_type_count: 0U }
}
let used_type_indices : Array[UInt] = []
match import_section_payload {
Some(payload) =>
for
type_index in collect_used_type_indices_from_import_section_raise(
payload,
) {
push_unique_u32(used_type_indices, type_index)
}
None => ()
}
match function_section_payload {
Some(payload) =>
for type_index in parse_function_type_indices(payload) {
push_unique_u32(used_type_indices, type_index)
}
None => ()
}
match code_section_payload {
Some(payload) =>
for
type_index in collect_used_type_indices_from_code_section_raise(payload) {
push_unique_u32(used_type_indices, type_index)
}
None => ()
}
let type_count_u = type_entries.length().reinterpret_as_uint()
for type_index in used_type_indices {
if type_index >= type_count_u {
raise WiteError::InvalidFormat(
"type index out of range in remove-unused-types: " +
type_index.to_string(),
)
}
}
let queue = used_type_indices.copy()
let mut queue_head = 0
while queue_head < queue.length() {
let current = queue[queue_head]
queue_head += 1
let current_pos = UInt::reinterpret_as_int(current)
if current_pos < 0 || current_pos >= type_entries.length() {
raise WiteError::InvalidFormat(
"type index position out of range in remove-unused-types: " +
current.to_string(),
)
}
for dep in type_entries[current_pos].referenced_type_indices {
if dep >= type_count_u {
raise WiteError::InvalidFormat(
"referenced type index out of range in remove-unused-types: " +
dep.to_string(),
)
}
if not(used_type_indices.contains(dep)) {
push_unique_u32(used_type_indices, dep)
queue.push(dep)
}
}
}
let keep_types : Array[Bool] = []
let type_index_map : Map[UInt, UInt] = {}
let mut kept_type_count = 0U
for i in 0.. {
let rewritten = rewrite_type_section_payload_for_keep_raise(
payload, keep_types,
)
if parse_vec_count(rewritten) > 0U {
append_encoded_section(out, 1U, rewritten)
}
}
2U =>
append_encoded_section(
out,
2U,
rewrite_import_section_payload_for_type_index_map_raise(
payload, type_index_map,
),
)
3U =>
append_encoded_section(
out,
3U,
rewrite_function_section_payload_for_type_index_map_raise(
payload, type_index_map,
),
)
10U =>
append_encoded_section(
out,
10U,
rewrite_code_section_payload_for_type_index_map_raise(
payload, type_index_map,
),
)
_ =>
out.append(bytes[section.section_start:section.section_end].to_array())
}
}
{ bytes: Bytes::from_array(out[:]), removed_type_count }
}
///|
fn apply_dce_raise(
bytes : Bytes,
config : OptimizeConfig,
) -> DceApplyResult raise WiteError {
let export_root_names = export_root_names_from_config(config)
let call_graph = analyze_call_graph_raise(bytes, export_root_names)
if call_graph.partial {
return {
bytes,
removed_function_count: 0U,
removed_body_bytes: 0U,
dropped_name_section: false,
}
}
let local_count = UInt::reinterpret_as_int(call_graph.local_function_count)
if local_count < 0 {
raise WiteError::InvalidFormat("local function count overflow")
}
if local_count != call_graph.nodes.length() {
raise WiteError::InvalidFormat(
"call graph node length mismatch: expected=" +
local_count.to_string() +
" actual=" +
call_graph.nodes.length().to_string(),
)
}
let keep_local : Array[Bool] = []
let index_map : Map[UInt, UInt] = {}
for i in 0U.. {
let rewritten = rewrite_function_section_payload_for_dce_raise(
payload, keep_local,
)
if parse_vec_count(rewritten) > 0U {
append_encoded_section(out, 3U, rewritten)
}
}
7U =>
append_encoded_section(
out,
7U,
rewrite_export_section_payload_for_dce_raise(payload, index_map),
)
8U =>
append_encoded_section(
out,
8U,
rewrite_start_section_payload_for_dce_raise(payload, index_map),
)
10U => {
let rewritten = rewrite_code_section_payload_for_dce_raise(
payload, keep_local, index_map,
)
if parse_vec_count(rewritten) > 0U {
append_encoded_section(out, 10U, rewritten)
}
}
0U =>
match section.custom_name {
Some(name) =>
if name == "name" {
dropped_name_section = true
} else {
out.append(
bytes[section.section_start:section.section_end].to_array(),
)
}
None =>
out.append(
bytes[section.section_start:section.section_end].to_array(),
)
}
6U =>
append_encoded_section(
out,
6U,
rewrite_global_section_payload_for_index_map_raise(payload, index_map),
)
9U =>
append_encoded_section(
out,
9U,
rewrite_element_section_payload_for_index_map_raise(
payload, index_map,
),
)
_ =>
out.append(bytes[section.section_start:section.section_end].to_array())
}
}
{
bytes: Bytes::from_array(out[:]),
removed_function_count,
removed_body_bytes: call_graph.dead_body_bytes,
dropped_name_section,
}
}
///|
fn apply_dfe_raise(bytes : Bytes) -> DfeApplyResult raise WiteError {
let sections = parse_core_sections_raise(bytes)
let mut imported_function_count = 0U
let mut function_type_indices : Array[UInt] = []
let mut code_bodies : Array[Bytes] = []
for section in sections {
let payload = bytes[section.payload_start:section.section_end].to_bytes()
match section.section_id {
2U => imported_function_count += parse_import_function_count(payload)
3U => function_type_indices = parse_function_type_indices(payload)
10U => code_bodies = parse_code_bodies(payload)
_ => ()
}
}
if function_type_indices.length() != code_bodies.length() {
raise WiteError::InvalidFormat(
"function/code section count mismatch for dfe: function=" +
function_type_indices.length().to_string() +
" code=" +
code_bodies.length().to_string(),
)
}
let keep_local : Array[Bool] = []
let index_map : Map[UInt, UInt] = {}
for i in 0U.. {
keep_local.push(false)
let canonical_old_index = imported_function_count +
pos.reinterpret_as_uint()
let canonical_new_index = remap_function_index_raise(
canonical_old_index, index_map,
)
index_map.set(old_index, canonical_new_index)
removed_function_count += 1U
removed_body_bytes += code_bodies[i].length().reinterpret_as_uint()
}
None => {
keep_local.push(true)
index_map.set(old_index, imported_function_count + kept_local_count)
kept_local_count += 1U
}
}
}
if removed_function_count == 0U {
return {
bytes,
removed_function_count,
removed_body_bytes,
dropped_name_section: false,
}
}
let out : Array[Byte] = bytes[0:8].to_array()
let mut dropped_name_section = false
for section in sections {
let payload = bytes[section.payload_start:section.section_end].to_bytes()
match section.section_id {
3U =>
append_encoded_section(
out,
3U,
rewrite_function_section_payload_for_dce_raise(payload, keep_local),
)
7U =>
append_encoded_section(
out,
7U,
rewrite_export_section_payload_for_dce_raise(payload, index_map),
)
8U =>
append_encoded_section(
out,
8U,
rewrite_start_section_payload_for_dce_raise(payload, index_map),
)
10U =>
append_encoded_section(
out,
10U,
rewrite_code_section_payload_for_dce_raise(
payload, keep_local, index_map,
),
)
0U =>
match section.custom_name {
Some(name) =>
if name == "name" {
dropped_name_section = true
} else {
out.append(
bytes[section.section_start:section.section_end].to_array(),
)
}
None =>
out.append(
bytes[section.section_start:section.section_end].to_array(),
)
}
6U =>
append_encoded_section(
out,
6U,
rewrite_global_section_payload_for_index_map_raise(payload, index_map),
)
9U =>
append_encoded_section(
out,
9U,
rewrite_element_section_payload_for_index_map_raise(
payload, index_map,
),
)
_ =>
out.append(bytes[section.section_start:section.section_end].to_array())
}
}
{
bytes: Bytes::from_array(out[:]),
removed_function_count,
removed_body_bytes,
dropped_name_section,
}
}
///|
fn apply_merge_similar_functions_raise(
bytes : Bytes,
) -> MsfApplyResult raise WiteError {
let sections = parse_core_sections_raise(bytes)
let mut type_payload : Bytes? = None
let mut imported_function_type_indices : Array[UInt] = []
let mut local_function_type_indices : Array[UInt] = []
let mut code_bodies : Array[Bytes] = []
for section in sections {
let payload = bytes[section.payload_start:section.section_end].to_bytes()
match section.section_id {
1U => type_payload = Some(payload)
2U =>
imported_function_type_indices = parse_import_function_type_indices(
payload,
)
3U => local_function_type_indices = parse_function_type_indices(payload)
10U => code_bodies = parse_code_bodies(payload)
_ => ()
}
}
if local_function_type_indices.length() != code_bodies.length() {
raise WiteError::InvalidFormat(
"function/code section count mismatch for msf: function=" +
local_function_type_indices.length().to_string() +
" code=" +
code_bodies.length().to_string(),
)
}
let imported_function_count = imported_function_type_indices
.length()
.reinterpret_as_uint()
let function_type_by_index = imported_function_type_indices.copy()
function_type_by_index.append(local_function_type_indices[:])
let function_param_count_by_index : Array[UInt] = []
if function_type_by_index.length() > 0 {
let type_payload = match type_payload {
Some(v) => v
None => raise WiteError::InvalidFormat("type section is missing for msf")
}
let parsed_type_entries = parse_type_section_entries_with_refs_raise(
type_payload, "msf",
)
let type_count_u = parsed_type_entries.length().reinterpret_as_uint()
for type_index in function_type_by_index {
if type_index >= type_count_u {
raise WiteError::InvalidFormat(
"type index out of range in msf: " + type_index.to_string(),
)
}
let type_pos = UInt::reinterpret_as_int(type_index)
if type_pos < 0 || type_pos >= parsed_type_entries.length() {
raise WiteError::InvalidFormat(
"type index position out of range in msf: " + type_index.to_string(),
)
}
if parsed_type_entries[type_pos].form != 0x60U {
raise WiteError::InvalidFormat(
"non-function type index used by function in msf: " +
type_index.to_string(),
)
}
function_param_count_by_index.push(
parse_function_type_param_count_from_raw_raise(
parsed_type_entries[type_pos].raw,
"msf",
),
)
}
}
let alias_candidate : Map[UInt, UInt] = {}
for i in 0..= function_type_by_index.length() ||
old_pos >= function_param_count_by_index.length() {
continue
}
let param_count = function_param_count_by_index[old_pos]
match
parse_forward_call_target_with_param_count(code_bodies[i], param_count) {
Some(target) =>
if target != old_index {
let target_pos = UInt::reinterpret_as_int(target)
if target_pos >= 0 &&
target_pos < function_type_by_index.length() &&
function_type_by_index[old_pos] ==
function_type_by_index[target_pos] {
alias_candidate.set(old_index, target)
}
}
None => ()
}
}
let keep_local : Array[Bool] = []
let resolved_target_for_local : Array[UInt?] = []
for i in 0.. {
let seen : Map[UInt, Bool] = {}
let mut current = initial_target
let mut valid = true
while true {
if current == old_index {
valid = false
break
}
if seen.get(current) is Some(_) {
valid = false
break
}
seen.set(current, true)
match alias_candidate.get(current) {
Some(next) => current = next
None => break
}
}
if valid {
keep_local.push(false)
resolved_target_for_local.push(Some(current))
} else {
keep_local.push(true)
resolved_target_for_local.push(None)
}
}
None => {
keep_local.push(true)
resolved_target_for_local.push(None)
}
}
}
let index_map : Map[UInt, UInt] = {}
for i in 0U.. {
let old_index = imported_function_count + i.reinterpret_as_uint()
let target_new_index = remap_function_index_raise(
target_old_index, index_map,
)
index_map.set(old_index, target_new_index)
removed_function_count += 1U
removed_body_bytes += code_bodies[i].length().reinterpret_as_uint()
}
None => ()
}
}
if removed_function_count == 0U {
return {
bytes,
removed_function_count,
removed_body_bytes,
dropped_name_section: false,
}
}
let out : Array[Byte] = bytes[0:8].to_array()
let mut dropped_name_section = false
for section in sections {
let payload = bytes[section.payload_start:section.section_end].to_bytes()
match section.section_id {
3U =>
append_encoded_section(
out,
3U,
rewrite_function_section_payload_for_dce_raise(payload, keep_local),
)
7U =>
append_encoded_section(
out,
7U,
rewrite_export_section_payload_for_dce_raise(payload, index_map),
)
8U =>
append_encoded_section(
out,
8U,
rewrite_start_section_payload_for_dce_raise(payload, index_map),
)
10U =>
append_encoded_section(
out,
10U,
rewrite_code_section_payload_for_dce_raise(
payload, keep_local, index_map,
),
)
0U =>
match section.custom_name {
Some(name) =>
if name == "name" {
dropped_name_section = true
} else {
out.append(
bytes[section.section_start:section.section_end].to_array(),
)
}
None =>
out.append(
bytes[section.section_start:section.section_end].to_array(),
)
}
6U =>
append_encoded_section(
out,
6U,
rewrite_global_section_payload_for_index_map_raise(payload, index_map),
)
9U =>
append_encoded_section(
out,
9U,
rewrite_element_section_payload_for_index_map_raise(
payload, index_map,
),
)
_ =>
out.append(bytes[section.section_start:section.section_end].to_array())
}
}
{
bytes: Bytes::from_array(out[:]),
removed_function_count,
removed_body_bytes,
dropped_name_section,
}
}
///|
fn unique_sorted(xs : Array[String]) -> Array[String] {
let out = xs.copy()
out.sort()
out.dedup()
out
}
///|
fn merge_root_names(
base : Array[String],
extra : Array[String],
) -> Array[String] {
let merged = base.copy()
merged.append(extra[:])
unique_sorted(merged)
}
///|
fn clone_optimize_config(
config : OptimizeConfig,
pass_rounds? : UInt = config.pass_rounds,
closed_world_root_exports? : Array[String] = config.closed_world_root_exports,
) -> OptimizeConfig {
make_optimize_config(
strip_all_custom=config.strip_all_custom,
strip_name_section=config.strip_name_section,
strip_producers_section=config.strip_producers_section,
strip_debug_sections=config.strip_debug_sections,
strip_dwarf_sections=config.strip_dwarf_sections,
strip_target_features_section=config.strip_target_features_section,
keep_custom_sections=config.keep_custom_sections,
pass_rounds~,
enable_peephole=config.enable_peephole,
peephole_remove_nop=config.peephole_remove_nop,
peephole_remove_const_drop=config.peephole_remove_const_drop,
enable_vacuum=config.enable_vacuum,
enable_merge_blocks=config.enable_merge_blocks,
enable_remove_unused_brs=config.enable_remove_unused_brs,
enable_dce=config.enable_dce,
enable_dfe=config.enable_dfe,
enable_merge_similar_functions=config.enable_merge_similar_functions,
enable_remove_unused_module_elements=config.enable_remove_unused_module_elements,
closed_world=config.closed_world,
closed_world_root_exports~,
safe_mode=config.safe_mode,
)
}
///|
pub fn make_component_optimize_config(
base? : OptimizeConfig = OptimizeConfig::default(),
root_name_candidates? : Array[String] = [],
exclude? : Array[String] = [],
) -> OptimizeConfig {
let merged_roots = merge_root_names(
merge_root_names(base.closed_world_root_exports, root_name_candidates),
exclude,
)
clone_optimize_config(base, closed_world_root_exports=merged_roots)
}
///|
fn with_additional_closed_world_roots(
config : OptimizeConfig,
extra_roots : Array[String],
) -> OptimizeConfig {
if extra_roots.length() == 0 {
return config
}
make_component_optimize_config(base=config, exclude=extra_roots)
}
///|
fn is_core_module_payload(payload : Bytes) -> Bool {
payload.length() >= 8 &&
payload[0] == 0x00U.to_byte() &&
payload[1] == 0x61U.to_byte() &&
payload[2] == 0x73U.to_byte() &&
payload[3] == 0x6dU.to_byte() &&
payload[4] == 0x01U.to_byte() &&
payload[5] == 0x00U.to_byte() &&
payload[6] == 0x00U.to_byte() &&
payload[7] == 0x00U.to_byte()
}
///|
fn world_keys(xs : Map[String, @wit.RWorldItem]) -> Array[String] {
let out : Array[String] = []
xs.each((key, _) => out.push(key))
out
}
///|
pub fn profile_component(bytes : Bytes) -> Result[ComponentProfile, String] {
let sections_result = try? parse_component_sections_raise(bytes)
let sections = match sections_result {
Ok(v) => v
Err(e) =>
return Err("component section parse failed: " + wite_error_to_string(e))
}
let detail = match @mwac_component.parse_component_detail(bytes) {
Ok(v) => v
Err(e) => return Err("component detail parse failed: " + e.to_string())
}
let core_modules : Array[ModuleProfile] = []
for i in 0.. core_modules.push(profile)
Err(e) =>
return Err(
"core module profile failed at index " +
i.to_string() +
": " +
wite_error_to_string(e),
)
}
}
Ok({
total_bytes: bytes.length().reinterpret_as_uint(),
import_count: detail.imports.length().reinterpret_as_uint(),
export_count: detail.exports.length().reinterpret_as_uint(),
core_module_count: detail.core_modules.length().reinterpret_as_uint(),
nested_component_count: detail.components.length().reinterpret_as_uint(),
sections: summarize_sections(sections, true),
core_modules,
})
}
///|
pub fn analyze_component_function_sizes(
bytes : Bytes,
) -> Result[Array[ComponentFunctionSizeReport], String] {
let detail = match @mwac_component.parse_component_detail(bytes) {
Ok(v) => v
Err(e) => return Err("component detail parse failed: " + e.to_string())
}
let reports : Array[ComponentFunctionSizeReport] = []
for i in 0.. v
Err(e) =>
return Err(
"core module function-size analysis failed at index " +
i.to_string() +
": " +
wite_error_to_string(e),
)
}
let mut total_body_bytes = 0U
for fn_size in functions {
total_body_bytes += fn_size.body_bytes
}
reports.push({
module_index: i.reinterpret_as_uint(),
function_count: functions.length().reinterpret_as_uint(),
total_body_bytes,
functions,
})
}
Ok(reports)
}
///|
pub fn analyze_component_call_graphs(
bytes : Bytes,
) -> Result[Array[ComponentCallGraphReport], String] {
let detail = match @mwac_component.parse_component_detail(bytes) {
Ok(v) => v
Err(e) => return Err("component detail parse failed: " + e.to_string())
}
let reports : Array[ComponentCallGraphReport] = []
for i in 0.. v
Err(e) =>
return Err(
"core module call graph failed at index " +
i.to_string() +
": " +
wite_error_to_string(e),
)
}
reports.push({ module_index: i.reinterpret_as_uint(), graph })
}
Ok(reports)
}
///|
priv struct ComponentOptimizeRoundResult {
bytes : Bytes
core_module_count : UInt
removed_sections : Array[String]
no_change_reasons : Array[String]
}
///|
fn optimize_component_for_size_round(
bytes : Bytes,
config : OptimizeConfig,
) -> Result[ComponentOptimizeRoundResult, String] {
let sections_result = try? parse_component_sections_raise(bytes)
let sections = match sections_result {
Ok(v) => v
Err(e) =>
return Err("component section parse failed: " + wite_error_to_string(e))
}
let out : Array[Byte] = bytes[0:8].to_array()
let removed_sections : Array[String] = []
let no_change_reasons : Array[String] = []
let mut core_module_index = 0
for section in sections {
let payload = bytes[section.payload_start:section.section_end].to_bytes()
if section.section_id == 1U && is_core_module_payload(payload) {
let optimized = match optimize_for_size(payload, config~) {
Ok(v) => v
Err(e) =>
return Err(
"core module optimize failed at index " +
core_module_index.to_string() +
": " +
wite_error_to_string(e),
)
}
append_encoded_section(out, 1U, optimized.bytes)
for removed in optimized.removed_sections {
removed_sections.push(
"core#" + core_module_index.to_string() + ":" + removed,
)
}
for reason in optimized.no_change_reasons {
no_change_reasons.push(
"core#" + core_module_index.to_string() + ":" + reason,
)
}
core_module_index += 1
} else {
out.append(bytes[section.section_start:section.section_end].to_array())
}
}
if core_module_index == 0 {
push_reason(
no_change_reasons, "component has no embedded core module sections",
)
}
Ok({
bytes: Bytes::from_array(out[:]),
core_module_count: core_module_index.reinterpret_as_uint(),
removed_sections,
no_change_reasons,
})
}
///|
fn filter_prefixed_items(
items : Array[String],
prefix : String,
) -> Array[String] {
let out : Array[String] = []
for item in items {
match item.strip_prefix(prefix) {
Some(rest) => out.push(rest.to_string())
None => ()
}
}
out
}
///|
fn sum_core_module_bytes(core_modules : Array[Bytes]) -> UInt {
let mut total = 0U
for core_bytes in core_modules {
total += core_bytes.length().reinterpret_as_uint()
}
total
}
///|
pub fn analyze_component_core_optimize(
bytes : Bytes,
config? : OptimizeConfig = OptimizeConfig::default(),
exclude? : Array[String] = [],
) -> Result[ComponentCoreOptimizeReport, String] {
let before_detail = match @mwac_component.parse_component_detail(bytes) {
Ok(v) => v
Err(e) => return Err("component detail parse failed: " + e.to_string())
}
let optimized_component = match
optimize_component_for_size(bytes, config~, exclude~) {
Ok(v) => v
Err(e) => return Err("component optimize failed: " + e)
}
let after_detail = match
@mwac_component.parse_component_detail(optimized_component.bytes) {
Ok(v) => v
Err(e) =>
return Err("optimized component detail parse failed: " + e.to_string())
}
if before_detail.core_modules.length() != after_detail.core_modules.length() {
return Err(
"core module count mismatch after optimize: before=" +
before_detail.core_modules.length().to_string() +
" after=" +
after_detail.core_modules.length().to_string(),
)
}
let entries : Array[ComponentCoreOptimizeEntry] = []
for i in 0.. Result[OptimizeResult, String] {
let effective_config = with_additional_closed_world_roots(config, exclude)
let rounds = if effective_config.pass_rounds == 0U {
1U
} else {
effective_config.pass_rounds
}
let per_round_config = clone_optimize_config(effective_config, pass_rounds=1U)
let mut current = bytes
let removed_sections : Array[String] = []
let no_change_reasons : Array[String] = []
let mut converged = false
for i in 0U.. v
Err(e) => return Err(e)
}
let before_size = current.length().reinterpret_as_uint()
let after_size = round_result.bytes.length().reinterpret_as_uint()
let changed = not(bytes_equal(round_result.bytes, current))
if after_size < before_size || (after_size == before_size && changed) {
current = round_result.bytes
removed_sections.append(round_result.removed_sections[:])
continue
}
converged = true
removed_sections.push(
"component-converge:no-improve:" + (i + 1U).to_string(),
)
for reason in round_result.no_change_reasons {
push_reason(no_change_reasons, reason)
}
if round_result.core_module_count == 0U {
push_reason(
no_change_reasons, "component has no embedded core module sections",
)
}
break
}
if not(converged) {
removed_sections.push("component-converge:round-cap:" + rounds.to_string())
}
if current.length() == bytes.length() && no_change_reasons.length() == 0 {
no_change_reasons.push("component optimization produced no size change")
}
Ok({
bytes: current,
before_size: bytes.length().reinterpret_as_uint(),
after_size: current.length().reinterpret_as_uint(),
removed_sections,
no_change_reasons,
})
}
///|
pub fn optimize_binary_for_size(
bytes : Bytes,
config? : OptimizeConfig = OptimizeConfig::default(),
exclude? : Array[String] = [],
) -> Result[OptimizeResult, String] {
if bytes.length() >= 8 &&
bytes[0] == 0x00U.to_byte() &&
bytes[1] == 0x61U.to_byte() &&
bytes[2] == 0x73U.to_byte() &&
bytes[3] == 0x6dU.to_byte() &&
bytes[4] == 0x0dU.to_byte() &&
bytes[5] == 0x00U.to_byte() &&
bytes[6] == 0x01U.to_byte() &&
bytes[7] == 0x00U.to_byte() {
optimize_component_for_size(bytes, config~, exclude~)
} else {
match optimize_for_size(bytes, config~) {
Ok(v) => Ok(v)
Err(e) => Err(wite_error_to_string(e))
}
}
}
///|
pub fn analyze_component_contract(
bytes : Bytes,
resolved : @wit.ResolveInput,
) -> Result[ContractReport, String] {
let detail = match @mwac_component.parse_component_detail(bytes) {
Ok(v) => v
Err(e) => return Err("component detail parse failed: " + e.to_string())
}
if resolved.world_id < 0 ||
resolved.world_id >= resolved.resolve.worlds.length() {
return Err("invalid world id: " + resolved.world_id.to_string())
}
let world = resolved.resolve.worlds[resolved.world_id]
let component_imports = unique_sorted(
detail.imports.map(item => item.name.to_string()),
)
let component_exports = unique_sorted(
detail.exports.map(item => item.name.to_string()),
)
let wit_imports = unique_sorted(world_keys(world.imports))
let wit_exports = unique_sorted(world_keys(world.exports))
let missing_component_imports : Array[String] = []
for name in wit_imports {
if not(component_imports.contains(name)) {
missing_component_imports.push(name)
}
}
let missing_component_exports : Array[String] = []
for name in wit_exports {
if not(component_exports.contains(name)) {
missing_component_exports.push(name)
}
}
Ok({
component_imports,
component_exports,
wit_imports,
wit_exports,
missing_component_imports,
missing_component_exports,
})
}
///|
fn collect_canonical_abi_root_names(
component_exports : Array[String],
wit_exports : Array[String],
) -> Array[String] {
let roots : Array[String] = ["cabi_realloc"]
for name in component_exports {
match name.strip_prefix("cabi_") {
Some(_) => roots.push(name)
None => ()
}
}
for name in wit_exports {
roots.push("cabi_post_" + name)
}
unique_sorted(roots)
}
///|
pub fn analyze_component_root_policy(
bytes : Bytes,
resolved_wit? : @wit.ResolveInput? = None,
exclude? : Array[String] = [],
) -> Result[ComponentRootPolicyReport, String] {
let detail = match @mwac_component.parse_component_detail(bytes) {
Ok(v) => v
Err(e) => return Err("component detail parse failed: " + e.to_string())
}
let component_imports = unique_sorted(
detail.imports.map(item => item.name.to_string()),
)
let component_exports = unique_sorted(
detail.exports.map(item => item.name.to_string()),
)
let mut wit_imports : Array[String] = []
let mut wit_exports : Array[String] = []
match resolved_wit {
Some(resolved) => {
if resolved.world_id < 0 ||
resolved.world_id >= resolved.resolve.worlds.length() {
return Err("invalid world id: " + resolved.world_id.to_string())
}
let world = resolved.resolve.worlds[resolved.world_id]
wit_imports = unique_sorted(world_keys(world.imports))
wit_exports = unique_sorted(world_keys(world.exports))
}
None => ()
}
let canonical_abi_roots = collect_canonical_abi_root_names(
component_exports, wit_exports,
)
let root_name_candidates = component_exports.copy()
root_name_candidates.append(wit_exports[:])
root_name_candidates.append(canonical_abi_roots[:])
root_name_candidates.append(exclude[:])
Ok({
component_imports,
component_exports,
wit_imports,
wit_exports,
canonical_abi_roots,
root_name_candidates: unique_sorted(root_name_candidates),
})
}
///|
fn runtime_profile_scenario_display_name(
scenario : RuntimeProfileScenario,
) -> String {
if scenario.args.length() == 0 {
return scenario.export_name
}
let encoded_args : Array[String] = []
for arg in scenario.args {
encoded_args.push(arg.to_string())
}
scenario.export_name + "(" + encoded_args.join("_") + ")"
}
///|
fn average_u64(values : Array[UInt64]) -> UInt64 {
if values.length() == 0 {
return 0UL
}
let mut total = 0UL
for value in values {
total += value
}
total / values.length().to_uint64()
}
///|
fn average_u32(values : Array[UInt]) -> UInt {
if values.length() == 0 {
return 0U
}
let mut total = 0U
for value in values {
total += value
}
total / values.length().reinterpret_as_uint()
}
///|
fn map_export_name_to_function_index(
functions : Array[FunctionSize],
) -> Map[String, UInt] {
let out : Map[String, UInt] = {}
for function in functions {
for export_name in function.export_names {
if out.get(export_name) is None {
out.set(export_name, function.function_index)
}
}
}
out
}
///|
fn map_function_size_by_index(
functions : Array[FunctionSize],
) -> Map[UInt, FunctionSize] {
let out : Map[UInt, FunctionSize] = {}
for function in functions {
out.set(function.function_index, function)
}
out
}
///|
fn map_runtime_unresolved_by_export_name(
unresolved_exports : Array[RuntimeUnresolvedExport],
) -> Map[String, RuntimeUnresolvedExport] {
let out : Map[String, RuntimeUnresolvedExport] = {}
for entry in unresolved_exports {
if out.get(entry.export_name) is None {
out.set(entry.export_name, entry)
}
}
out
}
///|
fn map_runtime_scenario_display_to_export_name(
scenarios : Array[RuntimeProfileScenario],
) -> Map[String, String] {
let out : Map[String, String] = {}
for scenario in scenarios {
let display_name = runtime_profile_scenario_display_name(scenario)
if out.get(display_name) is None {
out.set(display_name, scenario.export_name)
}
}
out
}
///|
fn has_hotness_size_entry(
entries : Array[HotnessSizeEntry],
export_name : String,
) -> Bool {
for entry in entries {
if entry.export_name == export_name {
return true
}
}
false
}
///|
fn classify_hotness_size_bucket(
total_ns : UInt64,
body_bytes : UInt,
hot_threshold_ns : UInt64,
large_threshold_bytes : UInt,
resolved : Bool,
) -> String {
if not(resolved) {
return "unresolved"
}
let hot = total_ns >= hot_threshold_ns
let large = body_bytes >= large_threshold_bytes
if hot && large {
"hot-large"
} else if hot {
"hot-small"
} else if large {
"cold-large"
} else {
"cold-small"
}
}
///|
fn compare_hotness_size_entry(
a : HotnessSizeEntry,
b : HotnessSizeEntry,
) -> Int {
if a.total_ns > b.total_ns {
-1
} else if a.total_ns < b.total_ns {
1
} else if a.body_bytes > b.body_bytes {
-1
} else if a.body_bytes < b.body_bytes {
1
} else {
a.export_name.compare(b.export_name)
}
}
///|
fn compare_hotness_unresolved_reason_count(
a : HotnessUnresolvedReasonCount,
b : HotnessUnresolvedReasonCount,
) -> Int {
if a.count > b.count {
-1
} else if a.count < b.count {
1
} else {
a.reason.compare(b.reason)
}
}
///|
fn collect_hotness_size_buckets(
entries : Array[HotnessSizeEntry],
) -> Array[HotnessSizeBucket] {
let order = [
"hot-large", "hot-small", "cold-large", "cold-small", "unresolved",
]
let grouped : Map[String, HotnessSizeBucket] = {}
for bucket in order {
grouped.set(bucket, { bucket, count: 0U, body_bytes: 0U, total_ns: 0UL })
}
for entry in entries {
let prev = match grouped.get(entry.bucket) {
Some(v) => v
None => {
let empty = {
bucket: entry.bucket,
count: 0U,
body_bytes: 0U,
total_ns: 0UL,
}
grouped.set(entry.bucket, empty)
empty
}
}
grouped.set(entry.bucket, {
bucket: entry.bucket,
count: prev.count + 1U,
body_bytes: prev.body_bytes + entry.body_bytes,
total_ns: prev.total_ns + entry.total_ns,
})
}
let buckets : Array[HotnessSizeBucket] = []
for bucket in order {
match grouped.get(bucket) {
Some(v) => buckets.push(v)
None => buckets.push({ bucket, count: 0U, body_bytes: 0U, total_ns: 0UL })
}
}
buckets
}
///|
fn collect_hotness_unresolved_reason_counts(
entries : Array[HotnessSizeEntry],
) -> Array[HotnessUnresolvedReasonCount] {
let counts : Map[String, UInt] = {}
for entry in entries {
if entry.bucket != "unresolved" {
continue
}
let reason = match entry.unresolved_reason {
Some(v) => v
None => "unknown"
}
let prev = match counts.get(reason) {
Some(v) => v
None => 0U
}
counts.set(reason, prev + 1U)
}
let out : Array[HotnessUnresolvedReasonCount] = []
counts.each((reason, count) => out.push({ reason, count }))
out.sort_by(compare_hotness_unresolved_reason_count)
out
}
///|
pub fn analyze_hotness_size_matrix(
bytes : Bytes,
iterations? : UInt = 50U,
scenarios? : Array[RuntimeProfileScenario] = [],
) -> Result[HotnessSizeMatrixReport, String] {
let runtime = match profile_runtime_exports(bytes, iterations~, scenarios~) {
Ok(v) => v
Err(msg) => return Err(msg)
}
let function_sizes = match analyze_function_sizes(bytes) {
Ok(v) => v
Err(err) =>
return Err("function size analysis failed: " + wite_error_to_string(err))
}
let export_to_function_index = map_export_name_to_function_index(
function_sizes,
)
let function_size_by_index = map_function_size_by_index(function_sizes)
let runtime_unresolved_by_name = map_runtime_unresolved_by_export_name(
runtime.unresolved_exports,
)
let scenario_display_to_export_name = map_runtime_scenario_display_to_export_name(
scenarios,
)
let resolved_total_ns : Array[UInt64] = []
let resolved_body_bytes : Array[UInt] = []
let unresolved_exports : Array[String] = []
for fn_profile in runtime.functions {
let lookup_name = match
scenario_display_to_export_name.get(fn_profile.name) {
Some(v) => v
None => fn_profile.name
}
match export_to_function_index.get(lookup_name) {
Some(function_index) =>
match function_size_by_index.get(function_index) {
Some(function_size) => {
resolved_total_ns.push(fn_profile.total_ns)
resolved_body_bytes.push(function_size.body_bytes)
}
None => unresolved_exports.push(fn_profile.name)
}
None => unresolved_exports.push(fn_profile.name)
}
}
let hot_threshold_ns = average_u64(resolved_total_ns)
let large_threshold_bytes = average_u32(resolved_body_bytes)
let entries : Array[HotnessSizeEntry] = []
for fn_profile in runtime.functions {
let lookup_name = match
scenario_display_to_export_name.get(fn_profile.name) {
Some(v) => v
None => fn_profile.name
}
match export_to_function_index.get(lookup_name) {
Some(function_index) =>
match function_size_by_index.get(function_index) {
Some(function_size) =>
entries.push({
export_name: fn_profile.name,
function_index: Some(function_index),
function_name: function_size.name,
body_bytes: function_size.body_bytes,
calls: fn_profile.calls,
total_ns: fn_profile.total_ns,
average_ns: fn_profile.average_ns,
bucket: classify_hotness_size_bucket(
fn_profile.total_ns,
function_size.body_bytes,
hot_threshold_ns,
large_threshold_bytes,
true,
),
unresolved_reason: None,
unresolved_detail: None,
})
None => {
let unresolved = runtime_unresolved_by_name.get(fn_profile.name)
let unresolved_reason = match unresolved {
Some(v) => v.reason
None => "import-missing"
}
let unresolved_detail_text = match unresolved {
Some(v) =>
if v.detail.is_empty() {
"no local function body matched export"
} else {
v.detail
}
None => "no local function body matched export"
}
entries.push({
export_name: fn_profile.name,
function_index: None,
function_name: None,
body_bytes: 0U,
calls: fn_profile.calls,
total_ns: fn_profile.total_ns,
average_ns: fn_profile.average_ns,
bucket: "unresolved",
unresolved_reason: Some(unresolved_reason),
unresolved_detail: Some(unresolved_detail_text),
})
unresolved_exports.push(fn_profile.name)
}
}
None => {
let unresolved = runtime_unresolved_by_name.get(fn_profile.name)
let unresolved_reason = match unresolved {
Some(v) => v.reason
None => "import-missing"
}
let unresolved_detail_text = match unresolved {
Some(v) =>
if v.detail.is_empty() {
"no local function body matched export"
} else {
v.detail
}
None => "no local function body matched export"
}
entries.push({
export_name: fn_profile.name,
function_index: None,
function_name: None,
body_bytes: 0U,
calls: fn_profile.calls,
total_ns: fn_profile.total_ns,
average_ns: fn_profile.average_ns,
bucket: "unresolved",
unresolved_reason: Some(unresolved_reason),
unresolved_detail: Some(unresolved_detail_text),
})
unresolved_exports.push(fn_profile.name)
}
}
}
for unresolved in runtime.unresolved_exports {
if has_hotness_size_entry(entries, unresolved.export_name) {
continue
}
entries.push({
export_name: unresolved.export_name,
function_index: None,
function_name: None,
body_bytes: 0U,
calls: 0U,
total_ns: 0UL,
average_ns: 0UL,
bucket: "unresolved",
unresolved_reason: Some(unresolved.reason),
unresolved_detail: if unresolved.detail.is_empty() {
None
} else {
Some(unresolved.detail)
},
})
unresolved_exports.push(unresolved.export_name)
}
unresolved_exports.sort()
unresolved_exports.dedup()
entries.sort_by(compare_hotness_size_entry)
let buckets = collect_hotness_size_buckets(entries)
let unresolved_reason_counts = collect_hotness_unresolved_reason_counts(
entries,
)
Ok({
iterations: runtime.iterations,
instantiate_ns: runtime.instantiate_ns,
hot_threshold_ns,
large_threshold_bytes,
unresolved_exports,
unresolved_reason_counts,
buckets,
entries,
})
}