// Licensing scope: see ../../LICENSE and this directory's LICENSE and NOTICE.
// Copyright 2023 WebAssembly Community Group participants.
// Retained historical upstream notice; current derivation remains unconfirmed.
// Conservative attribution from pinned Binaryen version_130 counterpart:
// Copyright 2016 WebAssembly Community Group participants
// Counterpart: src/passes/ReorderLocals.cpp (5d704ad5); origin remains unconfirmed.
// Potentially incorporated Binaryen expression retains Apache-2.0 terms.
// Original Starshine contributions use the scoped MIT grant in ../../LICENSE.
// Modified for Starshine through 2026: implementation and validation use
// Starshine's representations and APIs; this is not an unchanged upstream file.
// Conservative notice retention does not establish copying or independence.
///|
pub fn reorder_locals_summary() -> String {
"Sort body locals by access frequency and drop unaccessed body locals."
}
///|
priv struct RLScanState {
counts : Array[Int32]
first_uses : Array[Int32]
used_body_locals : Array[Int32]
param_count : Int32
mut next_rank : Int32
mut valid : Bool
}
///|
fn RLScanState::new(local_count : Int32, param_count : Int32) -> RLScanState {
{
counts: Array::make(local_count, 0),
first_uses: Array::make(local_count, 0),
used_body_locals: Array::new(capacity=local_count - param_count),
param_count,
next_rank: 1,
valid: true,
}
}
///|
priv struct RLFuncRewrite {
func : @lib.Func
old_to_new : Array[Int32]
changed : Bool
}
///|
fn rl_imported_func_count(mod_ : @lib.Module) -> Int32 {
let mut count = 0
match mod_.import_sec {
Some(@lib.ImportSec(imports)) =>
for import_ in imports {
match import_ {
@lib.Import(_, _, @lib.FuncExternType(_)) => count += 1
_ => ()
}
}
None => ()
}
count
}
///|
fn rl_local_idx_raw(idx : @lib.LocalIdx) -> Int32 {
let @lib.LocalIdx(raw) = idx
raw.reinterpret_as_int()
}
///|
fn rl_local_idx_from_int(idx : Int32) -> @lib.LocalIdx {
@lib.LocalIdx::new(idx.reinterpret_as_uint())
}
///|
///|
fn rl_flatten_locals(
locals : @lib.Locals,
local_count : Int32,
) -> Array[@lib.ValType] {
let out : Array[@lib.ValType] = Array::new(capacity=local_count)
for run in locals.runs() {
let ty = run.val_type()
let run_len = run.count().reinterpret_as_int()
for idx = 0; idx < run_len; idx = idx + 1 {
out.push(ty)
}
}
out
}
///|
fn rl_push_local_run(runs : Array[@lib.LocalRun], ty : @lib.ValType) -> Unit {
match runs.last() {
Some(last) if last.val_type() == ty =>
runs[runs.length() - 1] = @lib.LocalRun::new(last.count() + 1U, ty)
_ => runs.push(@lib.LocalRun::new(1U, ty))
}
}
///|
fn rl_note_access(state : RLScanState, idx : @lib.LocalIdx) -> Unit {
let raw = rl_local_idx_raw(idx)
if raw < 0 || raw >= state.counts.length() {
state.valid = false
return
}
state.counts[raw] += 1
if raw >= state.param_count && state.first_uses[raw] == 0 {
state.first_uses[raw] = state.next_rank
state.used_body_locals.push(raw)
state.next_rank += 1
}
}
///|
fn rl_scan_expr(expr : @lib.Expr, state : RLScanState) -> Unit {
let @lib.Expr(instrs) = expr
rl_scan_instrs(instrs, state)
}
///|
fn rl_scan_instrs(
instrs : Array[@lib.Instruction],
state : RLScanState,
) -> Unit {
for instr in instrs {
rl_scan_instruction(instr, state)
}
}
///|
fn rl_scan_instruction(instr : @lib.Instruction, state : RLScanState) -> Unit {
match instr {
@lib.Block(_, body) => rl_scan_expr(body, state)
@lib.Loop(_, body) => rl_scan_expr(body, state)
@lib.If(_, then_body, else_body) => {
rl_scan_instrs(then_body, state)
match else_body {
Some(body) => rl_scan_instrs(body, state)
None => ()
}
}
@lib.Try(_, body, catches, _) => {
rl_scan_expr(body, state)
for catch_ in catches {
match catch_ {
@lib.LegacyCatch(_, catch_body) | @lib.LegacyCatchAll(catch_body) =>
rl_scan_expr(catch_body, state)
}
}
}
@lib.TryTable(_, _, body) => rl_scan_expr(body, state)
@lib.LocalGet(idx) => rl_note_access(state, idx)
@lib.LocalSet(idx) => rl_note_access(state, idx)
@lib.LocalTee(idx) => rl_note_access(state, idx)
_ => ()
}
}
///|
fn rl_sort_used_body_locals(
used_body_locals : Array[Int32],
counts : Array[Int32],
first_uses : Array[Int32],
) -> Array[Int32] {
used_body_locals.sort_by(fn(lhs, rhs) {
let lhs_count = counts[lhs]
let rhs_count = counts[rhs]
if lhs_count == rhs_count {
if first_uses[lhs] == first_uses[rhs] {
lhs - rhs
} else {
first_uses[lhs] - first_uses[rhs]
}
} else {
rhs_count - lhs_count
}
})
used_body_locals
}
///|
fn rl_grouping_abs_key(abs : @lib.AbsHeapType) -> UInt64 {
match abs {
@lib.ExnAbsHeapType => 0UL
@lib.StringAbsHeapType => 1UL
@lib.ArrayAbsHeapType => 2UL
@lib.StructAbsHeapType => 3UL
@lib.I31AbsHeapType => 4UL
@lib.EqAbsHeapType => 5UL
@lib.AnyAbsHeapType => 6UL
@lib.ExternAbsHeapType => 7UL
@lib.FuncAbsHeapType => 8UL
@lib.ContAbsHeapType => 9UL
@lib.NoneAbsHeapType => 10UL
@lib.NoExternAbsHeapType => 11UL
@lib.NoFuncAbsHeapType => 12UL
@lib.NoExnAbsHeapType => 13UL
@lib.NoContAbsHeapType => 14UL
@lib.WaitqueueAbsHeapType => 15UL
@lib.NoWaitqueueAbsHeapType => 16UL
}
}
///|
// Scalar/heap-kind/reference flags occupy disjoint high bits; the payload keeps
// the full UInt32 index. No derived ValType graph hash or equality is needed.
fn rl_grouping_type_key(ty : @lib.ValType) -> UInt64? {
match ty {
@lib.NumTypeValType(@lib.I32NumType) => Some(0UL)
@lib.NumTypeValType(@lib.I64NumType) => Some(1UL)
@lib.NumTypeValType(@lib.F32NumType) => Some(2UL)
@lib.NumTypeValType(@lib.F64NumType) => Some(3UL)
@lib.VecTypeValType => Some(4UL)
@lib.BotValType => None
@lib.RefTypeValType(@lib.AbsHeapTypeRefType(abs)) =>
Some(0x1100000000000000UL | rl_grouping_abs_key(abs))
@lib.RefTypeValType(@lib.HeapTypeRefType(nullable, exact, heap)) => {
let payload = match heap {
@lib.AbsHeapTypeHeapType(abs) => rl_grouping_abs_key(abs)
@lib.SharedAbsHeapTypeHeapType(abs) =>
0x100000000UL | rl_grouping_abs_key(abs)
@lib.HeapType(@lib.TypeIdx(idx)) => 0x200000000UL | idx.to_uint64()
@lib.HeapType(@lib.RecIdx(idx)) => 0x300000000UL | idx.to_uint64()
@lib.DefTypeHeapType(_) => return None
}
let nullable_flag = if nullable { 0x0100000000000000UL } else { 0UL }
let exact_flag = if exact { 0x0200000000000000UL } else { 0UL }
Some(0x1000000000000000UL | nullable_flag | exact_flag | payload)
}
}
}
///|
// Equal-frequency permutations preserve the weighted local-index ULEB size,
// including width boundaries. Compress declaration runs only when their actual
// encoded bytes strictly improve; retain first-use ordering on ties/no gain.
fn rl_group_equal_frequency_locals(
order : Array[Int32],
counts : Array[Int32],
body_types : Array[@lib.ValType],
first_uses : Array[Int32],
param_count : Int32,
) -> Array[Int32] {
if order.length() <= 2 {
return order
}
let type_ranks : Map[UInt64, Int32] = Map([])
let ranks = Array::make(body_types.length(), 0)
for old_idx in order {
let key = match rl_grouping_type_key(body_types[old_idx - param_count]) {
Some(key) => key
None => return order
}
let rank = match type_ranks.get(key) {
Some(rank) => rank
None => {
let rank = type_ranks.length()
type_ranks[key] = rank
rank
}
}
ranks[old_idx - param_count] = rank
}
if type_ranks.length() <= 1 {
return order
}
let grouped = order.copy()
grouped.sort_by(fn(lhs, rhs) {
if counts[lhs] != counts[rhs] {
counts[rhs] - counts[lhs]
} else if ranks[lhs - param_count] != ranks[rhs - param_count] {
ranks[lhs - param_count] - ranks[rhs - param_count]
} else {
first_uses[lhs] - first_uses[rhs]
}
})
let current_runs : Array[@lib.LocalRun] = []
let grouped_runs : Array[@lib.LocalRun] = []
for old_idx in order {
rl_push_local_run(current_runs, body_types[old_idx - param_count])
}
for old_idx in grouped {
rl_push_local_run(grouped_runs, body_types[old_idx - param_count])
}
let current_bytes = @buffer.new()
let grouped_bytes = @buffer.new()
match
(
@binary.Encode::encode(@lib.Locals::new(current_runs), current_bytes),
@binary.Encode::encode(@lib.Locals::new(grouped_runs), grouped_bytes),
) {
(Ok(_), Ok(_)) if grouped_bytes.length() < current_bytes.length() => grouped
_ => order
}
}
///|
fn rl_rewrite_local_idx(
idx : @lib.LocalIdx,
old_to_new : Array[Int32],
) -> @lib.LocalIdx {
let raw = rl_local_idx_raw(idx)
if raw < 0 || raw >= old_to_new.length() {
abort("reorder-locals local remap out of range")
}
let mapped = old_to_new[raw]
if mapped < 0 {
abort("reorder-locals attempted to rewrite dropped local access")
}
rl_local_idx_from_int(mapped)
}
///|
fn rl_rewrite_expr(expr : @lib.Expr, old_to_new : Array[Int32]) -> @lib.Expr {
let @lib.Expr(instrs) = expr
let rewritten = instrs.copy()
rl_rewrite_instrs_in_place(rewritten, old_to_new)
@lib.Expr::new(rewritten)
}
///|
fn rl_rewrite_instrs_in_place(
instrs : Array[@lib.Instruction],
old_to_new : Array[Int32],
) -> Unit {
for idx = 0; idx < instrs.length(); idx = idx + 1 {
match instrs[idx] {
@lib.Block(block_type, @lib.Expr(body)) => {
let rewritten_body = body.copy()
rl_rewrite_instrs_in_place(rewritten_body, old_to_new)
instrs[idx] = @lib.Instruction::block(
block_type,
@lib.Expr::new(rewritten_body),
)
}
@lib.Loop(block_type, @lib.Expr(body)) => {
let rewritten_body = body.copy()
rl_rewrite_instrs_in_place(rewritten_body, old_to_new)
instrs[idx] = @lib.Instruction::loop_(
block_type,
@lib.Expr::new(rewritten_body),
)
}
@lib.If(block_type, then_body, else_body) => {
let rewritten_then = then_body.copy()
rl_rewrite_instrs_in_place(rewritten_then, old_to_new)
let rewritten_else = match else_body {
Some(else_instrs) => {
let next_else = else_instrs.copy()
rl_rewrite_instrs_in_place(next_else, old_to_new)
Some(next_else)
}
None => None
}
instrs[idx] = @lib.Instruction::if_(
block_type, rewritten_then, rewritten_else,
)
}
@lib.Try(block_type, @lib.Expr(body), catches, delegate) => {
let rewritten_body = body.copy()
rl_rewrite_instrs_in_place(rewritten_body, old_to_new)
let rewritten_catches : Array[@lib.LegacyCatch] = []
for catch_ in catches {
match catch_ {
@lib.LegacyCatch(tag_idx, @lib.Expr(catch_body)) => {
let rewritten_catch_body = catch_body.copy()
rl_rewrite_instrs_in_place(rewritten_catch_body, old_to_new)
rewritten_catches.push(
@lib.LegacyCatch::tagged(
tag_idx,
@lib.Expr::new(rewritten_catch_body),
),
)
}
@lib.LegacyCatchAll(@lib.Expr(catch_body)) => {
let rewritten_catch_body = catch_body.copy()
rl_rewrite_instrs_in_place(rewritten_catch_body, old_to_new)
rewritten_catches.push(
@lib.LegacyCatch::all(@lib.Expr::new(rewritten_catch_body)),
)
}
}
}
instrs[idx] = @lib.Instruction::try_(
block_type,
@lib.Expr::new(rewritten_body),
rewritten_catches,
delegate~,
)
}
@lib.TryTable(block_type, catches, @lib.Expr(body)) => {
let rewritten_body = body.copy()
rl_rewrite_instrs_in_place(rewritten_body, old_to_new)
instrs[idx] = @lib.Instruction::try_table(
block_type,
catches,
@lib.Expr::new(rewritten_body),
)
}
@lib.LocalGet(local_idx) => {
let next_idx = rl_rewrite_local_idx(local_idx, old_to_new)
if next_idx != local_idx {
instrs[idx] = @lib.Instruction::local_get(next_idx)
}
}
@lib.LocalSet(local_idx) => {
let next_idx = rl_rewrite_local_idx(local_idx, old_to_new)
if next_idx != local_idx {
instrs[idx] = @lib.Instruction::local_set(next_idx)
}
}
@lib.LocalTee(local_idx) => {
let next_idx = rl_rewrite_local_idx(local_idx, old_to_new)
if next_idx != local_idx {
instrs[idx] = @lib.Instruction::local_tee(next_idx)
}
}
_ => ()
}
}
}
///|
fn rl_module_subtypes(mod_ : @lib.Module) -> Array[@lib.SubType] {
let subtypes : Array[@lib.SubType] = match mod_.type_sec {
Some(@lib.TypeSec(rec_types)) => Array::new(capacity=rec_types.length())
None => []
}
match mod_.type_sec {
Some(@lib.TypeSec(rec_types)) =>
for rec_type in rec_types {
match rec_type {
@lib.SingleRecType(subtype) => subtypes.push(subtype)
@lib.GroupRecType(group) =>
for subtype in group {
subtypes.push(subtype)
}
}
}
None => abort("reorder-locals missing type section")
}
subtypes
}
///|
fn rl_params_for_type_idx(
subtypes : Array[@lib.SubType],
type_idx : @lib.TypeIdx,
) -> Array[@lib.ValType] {
let subtype = match type_idx {
@lib.TypeIdx(type_pos) =>
match subtypes.get(type_pos.reinterpret_as_int()) {
Some(subtype) => subtype
None => abort("reorder-locals missing function subtype")
}
@lib.RecIdx(_) =>
// Function-section type references are module-global TypeIdx values after
// decode/validation. A RecIdx here would mean a rec-group-local type
// escaped into the module function declaration table, so keep this as an
// invariant assertion instead of trying to reinterpret it.
abort("reorder-locals unsupported recursive function type index")
}
match subtype.get_comptype() {
@lib.FuncCompType(params, _) => params
_ => abort("reorder-locals expected function type")
}
}
///|
fn rl_defined_func_param_cache(
mod_ : @lib.Module,
) -> Array[Array[@lib.ValType]] {
let subtypes = rl_module_subtypes(mod_)
let out : Array[Array[@lib.ValType]] = match mod_.func_sec {
Some(@lib.FuncSec(type_idxs)) => Array::new(capacity=type_idxs.length())
None => []
}
match mod_.func_sec {
Some(@lib.FuncSec(type_idxs)) =>
for type_idx in type_idxs {
out.push(rl_params_for_type_idx(subtypes, type_idx))
}
None => abort("reorder-locals missing function section")
}
out
}
///|
fn rl_used_body_locals_are_original_prefix(
used_body_locals : Array[Int32],
param_count : Int32,
) -> Bool {
for idx = 0; idx < used_body_locals.length(); idx = idx + 1 {
if used_body_locals[idx] != param_count + idx {
return false
}
}
true
}
///|
fn rl_rewrite_func_type_stable(
func : @lib.Func,
params : Array[@lib.ValType],
) -> RLFuncRewrite {
let @lib.Func(body_locals, body) = func
let body_local_count = body_locals.length()
if body_local_count == 0 {
return { func, old_to_new: [], changed: false, }
}
let total_locals = params.length() + body_local_count
let scan = RLScanState::new(total_locals, params.length())
rl_scan_expr(body, scan)
if !scan.valid {
return { func, old_to_new: [], changed: false, }
}
let body_local_types = rl_flatten_locals(body_locals, body_local_count)
let old_to_new = Array::make(total_locals, -1)
for idx = 0; idx < params.length(); idx = idx + 1 {
old_to_new[idx] = idx
}
let reordered_body_runs : Array[@lib.LocalRun] = []
let mut old_body_start = 0
let mut next_body_idx = 0
let mut remapped = false
while old_body_start < body_local_count {
let ty = body_local_types[old_body_start]
let mut old_body_end = old_body_start + 1
while old_body_end < body_local_count &&
body_local_types[old_body_end] == ty {
old_body_end += 1
}
let used_in_run : Array[Int32] = []
for old_body_idx = old_body_start
old_body_idx < old_body_end
old_body_idx = old_body_idx + 1 {
let old_idx = params.length() + old_body_idx
if scan.counts[old_idx] > 0 {
used_in_run.push(old_idx)
}
}
ignore(rl_sort_used_body_locals(used_in_run, scan.counts, scan.first_uses))
for old_idx in used_in_run {
let new_idx = params.length() + next_body_idx
old_to_new[old_idx] = new_idx
if old_idx != new_idx {
remapped = true
}
rl_push_local_run(reordered_body_runs, ty)
next_body_idx += 1
}
old_body_start = old_body_end
}
if next_body_idx == body_local_count && !remapped {
return { func, old_to_new: [], changed: false, }
}
let rewritten_locals = @lib.Locals::new(reordered_body_runs)
let rewritten_body = if remapped {
rl_rewrite_expr(body, old_to_new)
} else {
body
}
{
func: @lib.Func::new(rewritten_locals, rewritten_body),
old_to_new,
changed: true,
}
}
///|
fn rl_rewrite_func(
func : @lib.Func,
params : Array[@lib.ValType],
) -> RLFuncRewrite {
let @lib.Func(body_locals, body) = func
let body_local_count = body_locals.length()
if body_local_count == 0 {
return { func, old_to_new: [], changed: false, }
}
let total_locals = params.length() + body_local_count
let scan = RLScanState::new(total_locals, params.length())
rl_scan_expr(body, scan)
if !scan.valid {
// Fail closed for invalid/stale intermediate modules. Optimizing nested
// cleanup callers can then validate-or-fallback instead of crashing while
// probing a cleanup candidate.
return { func, old_to_new: [], changed: false, }
}
let frequency_order = rl_sort_used_body_locals(
scan.used_body_locals,
scan.counts,
scan.first_uses,
)
if body_locals.runs().length() <= 1 &&
frequency_order.length() == body_local_count &&
rl_used_body_locals_are_original_prefix(frequency_order, params.length()) {
return { func, old_to_new: [], changed: false, }
}
let body_local_types = rl_flatten_locals(body_locals, body_local_count)
let used_body_locals = rl_group_equal_frequency_locals(
frequency_order,
scan.counts,
body_local_types,
scan.first_uses,
params.length(),
)
let used_body_locals_are_original_prefix = rl_used_body_locals_are_original_prefix(
used_body_locals,
params.length(),
)
if used_body_locals.length() == body_local_count &&
used_body_locals_are_original_prefix {
return { func, old_to_new: [], changed: false, }
}
let old_to_new = Array::make(total_locals, -1)
for idx = 0; idx < params.length(); idx = idx + 1 {
old_to_new[idx] = idx
}
let reordered_body_runs : Array[@lib.LocalRun] = Array::new(
capacity=used_body_locals.length(),
)
for body_idx = 0
body_idx < used_body_locals.length()
body_idx = body_idx + 1 {
let old_idx = used_body_locals[body_idx]
old_to_new[old_idx] = params.length() + body_idx
rl_push_local_run(
reordered_body_runs,
body_local_types[old_idx - params.length()],
)
}
let rewritten_locals = @lib.Locals::new(reordered_body_runs)
if used_body_locals_are_original_prefix {
let rewritten_func = @lib.Func::new(rewritten_locals, body)
return { func: rewritten_func, old_to_new, changed: true, }
}
let rewritten_body = rl_rewrite_expr(body, old_to_new)
let rewritten_func = @lib.Func::new(rewritten_locals, rewritten_body)
{ func: rewritten_func, old_to_new, changed: true, }
}
///|
fn rl_rewrite_name_map(
names : @lib.NameMap,
old_to_new : Array[Int32],
) -> @lib.NameMap? {
let @lib.NameMap(entries) = names
let rewritten : Array[@lib.NameAssoc] = Array::new(capacity=entries.length())
let mut sorted = true
let mut last_mapped = -1
for entry in entries {
let @lib.NameAssoc(idx, name) = entry
let raw = idx.reinterpret_as_int()
if raw < 0 || raw >= old_to_new.length() {
continue
}
let mapped = old_to_new[raw]
if mapped >= 0 {
if mapped < last_mapped {
sorted = false
} else {
last_mapped = mapped
}
rewritten.push(@lib.NameAssoc::new(mapped.reinterpret_as_uint(), name))
}
}
if rewritten.length() == 0 {
return None
}
if !sorted {
rewritten.sort_by(fn(lhs, rhs) {
let @lib.NameAssoc(lhs_idx, _) = lhs
let @lib.NameAssoc(rhs_idx, _) = rhs
lhs_idx.reinterpret_as_int() - rhs_idx.reinterpret_as_int()
})
}
Some(@lib.NameMap::new(rewritten))
}
///|
fn rl_rewrite_local_names(
names : @lib.IndirectNameMap?,
imported_func_count : Int32,
func_remaps : Array[Array[Int32]?],
) -> @lib.IndirectNameMap? {
match names {
Some(@lib.IndirectNameMap(entries)) => {
let rewritten : Array[@lib.IndirectNameAssoc] = Array::new(
capacity=entries.length(),
)
for entry in entries {
let @lib.IndirectNameAssoc(func_idx, local_names) = entry
let raw_func_idx = func_idx.reinterpret_as_int()
if raw_func_idx < imported_func_count {
rewritten.push(entry)
continue
}
let code_idx = raw_func_idx - imported_func_count
match func_remaps.get(code_idx) {
Some(Some(old_to_new)) =>
match rl_rewrite_name_map(local_names, old_to_new) {
Some(next_names) =>
rewritten.push(
@lib.IndirectNameAssoc::new(func_idx, next_names),
)
None => ()
}
_ => rewritten.push(entry)
}
}
if rewritten.length() == 0 {
None
} else {
Some(@lib.IndirectNameMap::new(rewritten))
}
}
None => None
}
}
///|
fn rl_name_sec_is_empty(name_sec : @lib.NameSec) -> Bool {
name_sec.module_name is None &&
name_sec.func_names is None &&
name_sec.local_names is None &&
name_sec.label_names is None &&
name_sec.type_names is None &&
name_sec.table_names is None &&
name_sec.memory_names is None &&
name_sec.global_names is None &&
name_sec.elem_names is None &&
name_sec.data_names is None &&
name_sec.field_names is None &&
name_sec.tag_names is None
}
///|
fn rl_rewrite_name_sec(
name_sec : @lib.NameSec?,
imported_func_count : Int32,
func_remaps : Array[Array[Int32]?],
) -> @lib.NameSec? {
match name_sec {
Some(sec) => {
let {
module_name,
func_names,
label_names,
type_names,
table_names,
memory_names,
global_names,
elem_names,
data_names,
field_names,
tag_names,
..
} = sec
let next = @lib.NameSec::new(
module_name~,
func_names~,
local_names=rl_rewrite_local_names(
sec.local_names,
imported_func_count,
func_remaps,
),
label_names~,
type_names~,
table_names~,
memory_names~,
global_names~,
elem_names~,
data_names~,
field_names~,
tag_names~,
)
if rl_name_sec_is_empty(next) {
None
} else {
Some(next)
}
}
None => None
}
}
///|
fn rl_rebuild_module(
mod_ : @lib.Module,
next_code_sec : @lib.CodeSec?,
next_name_sec : @lib.NameSec?,
) -> @lib.Module {
@lib.Module::new(
custom_secs=mod_.custom_secs,
type_sec=mod_.type_sec,
import_sec=mod_.import_sec,
func_annotation_sec=mod_.func_annotation_sec,
func_sec=mod_.func_sec,
table_sec=mod_.table_sec,
mem_sec=mod_.mem_sec,
tag_sec=mod_.tag_sec,
stringrefs_sec=mod_.stringrefs_sec,
global_sec=mod_.global_sec,
export_sec=mod_.export_sec,
start_sec=mod_.start_sec,
elem_sec=mod_.elem_sec,
data_cnt_sec=mod_.data_cnt_sec,
code_sec=next_code_sec,
data_sec=mod_.data_sec,
name_sec=next_name_sec,
)
}
///|
pub fn reorder_locals_run_module_pass(mod_ : @lib.Module) -> @lib.Module {
if pass_module_has_offset_sensitive_metadata(mod_) {
return mod_
}
let imported_func_count = rl_imported_func_count(mod_)
match mod_.code_sec {
Some(@lib.CodeSec(funcs)) => {
if funcs.is_empty() {
return mod_
}
let defined_func_params = rl_defined_func_param_cache(mod_)
let rewritten_funcs : Array[@lib.Func] = Array::new(
capacity=funcs.length(),
)
let func_remaps : Array[Array[Int32]?] = Array::new(
capacity=funcs.length(),
)
let mut changed = false
for code_idx = 0; code_idx < funcs.length(); code_idx = code_idx + 1 {
let func = funcs[code_idx]
let params = match defined_func_params.get(code_idx) {
Some(params) => params
None => abort("reorder-locals missing defined function params")
}
let rewrite = rl_rewrite_func(func, params)
rewritten_funcs.push(rewrite.func)
if rewrite.changed {
func_remaps.push(Some(rewrite.old_to_new))
changed = true
} else {
func_remaps.push(None)
}
}
if !changed {
return mod_
}
rl_rebuild_module(
mod_,
Some(@lib.CodeSec::new(rewritten_funcs)),
rl_rewrite_name_sec(mod_.name_sec, imported_func_count, func_remaps),
)
}
None => mod_
}
}
///|
fn run_hot_pipeline_apply_reorder_locals_to_touched(
mod_ : @lib.Module,
touched : Array[Bool],
params_by_func~ : Array[Array[@lib.ValType]]? = None,
) -> Result[@lib.Module, String] {
let funcs = match mod_.code_sec {
Some(@lib.CodeSec(funcs)) => funcs
None => return Ok(mod_)
}
let params = match params_by_func {
Some(params) => params
None => rl_defined_func_param_cache(mod_)
}
let func_remaps : Array[Array[Int32]?] = Array::make(funcs.length(), None)
let mut rewritten : Array[@lib.Func]? = None
for code_idx = 0; code_idx < funcs.length(); code_idx = code_idx + 1 {
if code_idx >= touched.length() || !touched[code_idx] {
continue
}
let func_params = match params.get(code_idx) {
Some(params) => params
None =>
return Err("missing defined function params for code index \{code_idx}")
}
let rewrite = rl_rewrite_func(funcs[code_idx], func_params)
if rewrite.changed {
func_remaps[code_idx] = Some(rewrite.old_to_new)
match rewritten {
Some(copy) => copy[code_idx] = rewrite.func
None => {
let copy = funcs.copy()
copy[code_idx] = rewrite.func
rewritten = Some(copy)
}
}
}
}
match rewritten {
Some(copy) => {
let next = mod_.with_code_sec(@lib.CodeSec::new(copy))
match
rl_rewrite_name_sec(
mod_.name_sec,
rl_imported_func_count(mod_),
func_remaps,
) {
Some(name_sec) => Ok(next.with_name_sec(name_sec))
None => Ok(next.without_name_sec())
}
}
None => Ok(mod_)
}
}