// Licensing scope: see ../../LICENSE and this directory's LICENSE and NOTICE.
// 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.
///|
// Run after all writeback repair: restored functions still use original indices.
fn oi_cleanup_module_encoding(
mod_ : @lib.Module,
touched~ : Array[Bool]? = None,
) -> @lib.Module {
if pass_module_has_offset_sensitive_metadata(mod_) {
return mod_
}
let mut changed = false
let candidate = match mod_.code_sec {
Some(@lib.CodeSec(funcs)) => {
let next = funcs.mapi(fn(index, func) {
if touched is Some(selected) &&
(index >= selected.length() || !selected[index]) {
return func
}
let @lib.Func(locals, @lib.Expr(body)) = func
let (body, rewritten) = oi_remove_numeric_select_annotations(body)
changed = changed || rewritten
if rewritten {
@lib.Func::new(locals, @lib.Expr::new(body))
} else {
func
}
})
if changed {
mod_.with_code_sec(@lib.CodeSec::new(next))
} else {
mod_
}
}
None => mod_
}
let control_cleaned = oi_cleanup_declarative_elements(
oi_cleanup_control(candidate, touched~),
)
let type_cleaned = if pass_encoding_cleanup_may_remap_types(
control_cleaned, touched,
) {
dfe_cleanup_simple_type_encoding(
pass_cleanup_unused_type_groups(control_cleaned),
)
} else {
control_cleaned
}
let cleaned = oi_cleanup_descriptor_encoding(type_cleaned, touched~)
oi_group_numeric_locals_if_smaller(cleaned, touched~)
}
///|
// Grouping can only change a function with a removable zero run or a repeated
// numeric declaration type. Distinct nonzero numeric runs are already in the
// bucket order that the grouping helper would produce.
fn oi_numeric_group_has_declaration_candidate(
mod_ : @lib.Module,
touched : Array[Bool]?,
) -> Bool {
let funcs = match mod_.code_sec {
Some(@lib.CodeSec(funcs)) => funcs
None => return false
}
for index = 0; index < funcs.length(); index = index + 1 {
if touched is Some(selected) &&
(index >= selected.length() || !selected[index]) {
continue
}
let @lib.Func(locals, _) = funcs[index]
if locals.length() <= 1 {
continue
}
let seen : Array[@lib.ValType] = []
let mut candidate = false
let mut supported = true
for run in locals.runs() {
if run.count() == 0U {
candidate = true
continue
}
let ty = run.val_type()
if !run_hot_pipeline_raw_numeric_local_type(ty) {
supported = false
break
}
if seen.any(fn(existing) { existing == ty }) {
candidate = true
} else {
seen.push(ty)
}
}
if supported && candidate {
return true
}
}
false
}
///|
// Binaryen's OI writer groups scalar local declarations by type. Reuse the
// shared index and decoded-name remapper, but keep legacy EH and opaque names
// unchanged and commit only a validated whole-module byte saving.
fn oi_group_numeric_locals_if_smaller(
mod_ : @lib.Module,
touched~ : Array[Bool]? = None,
allow_frequency~ : Bool = true,
function_comparisons~ : Array[Int32]? = None,
perf~ : HotPerfSession? = None,
legacy_guard_scans~ : Array[Int32]? = None,
) -> @lib.Module {
let funcs = match mod_.code_sec {
Some(@lib.CodeSec(funcs)) => funcs
None => return mod_
}
if !oi_numeric_group_has_declaration_candidate(mod_, touched) {
// Wide frequency cleanup can remove unused locals even when declaration
// types are already grouped. Keep the cheap guard for other inputs.
let mut wide_frequency_candidate = false
if allow_frequency {
let params = rl_defined_func_param_cache(mod_)
for index in 0..= mask.length() || !mask[index]) {
continue
}
let @lib.Func(locals, _) = funcs[index]
if params.get(index) is Some(ps) &&
locals.length() > 0 &&
ps.length() + locals.length() > 128 &&
ps.all(run_hot_pipeline_raw_numeric_local_type) &&
locals
.runs()
.all(fn(run) { run_hot_pipeline_raw_numeric_local_type(run.vt) }) {
wide_frequency_candidate = true
break
}
}
}
if !wide_frequency_candidate {
return mod_
}
}
match legacy_guard_scans {
Some(scans) => scans[0] += 1
None => ()
}
if pass_module_has_legacy_try(mod_) ||
oi_local_group_has_offset_metadata(mod_) ||
(mod_.name_sec is None && mod_.raw_name_sec_payload is Some(_)) {
return mod_
}
let grouping_start = perf_start_timer_opt(
perf, "detail:simplify-locals:encoding-local-group-remap",
)
let grouped = run_hot_pipeline_raw_group_body_locals_by_type_module(
mod_,
touched~,
)
perf_stop_timer_opt(
perf, "detail:simplify-locals:encoding-local-group-remap", grouping_start,
)
ignore(function_comparisons)
// SL preserves body-local declarations and their names. Its boundary cleanup
// groups types only; OI can also choose the existing unused-local removal.
if !allow_frequency {
return oi_select_local_group_candidate(mod_, grouped, None)
}
// Frequency order can save more access bytes across a LEB boundary than
// first-type grouping saves in declarations. Compare one existing candidate;
// keep the numeric/touched admission boundary and the fixed parameter prefix.
let params = rl_defined_func_param_cache(mod_)
let mut selected : Array[Bool]? = None
for index in 0..= set.length() || !set[index]) {
continue
}
let @lib.Func(locals, _) = funcs[index]
if locals.length() == 0 {
continue
}
let ps = match params.get(index) {
Some(ps) => ps
None => continue
}
if ps.length() + locals.length() > 128 &&
ps.all(run_hot_pipeline_raw_numeric_local_type) &&
locals
.runs()
.all(fn(run) { run_hot_pipeline_raw_numeric_local_type(run.vt) }) {
if selected is None {
selected = Some(Array::make(funcs.length(), false))
}
selected.unwrap()[index] = true
}
}
let selected = match selected {
Some(selected) => selected
None => return oi_select_local_group_candidate(mod_, grouped, None)
}
let frequency = match
run_hot_pipeline_apply_reorder_locals_to_touched(
mod_,
selected,
params_by_func=Some(params),
) {
Ok(frequency) if !physical_equal(frequency, mod_) => Some(frequency)
_ => None
}
oi_select_local_group_candidate(mod_, grouped, frequency)
}
///|
// Candidates retain the caller's numeric/touched and metadata admission.
// Rank complete encoded costs first, then validate only the candidate needed.
// A cheaper invalid candidate must not exclude a profitable valid fallback.
fn oi_select_local_group_candidate(
before : @lib.Module,
grouped : @lib.Module,
frequency : @lib.Module?,
) -> @lib.Module {
let baseline = if before.compiler_fact_custom_section is Some(_) {
before.without_compiler_fact_custom_section()
} else {
before
}
let grouped_size = if physical_equal(grouped, before) {
None
} else {
match @binary.encoded_module_sizes(baseline, grouped) {
Ok((old_size, new_size)) if new_size < old_size => Some(new_size)
_ => None
}
}
let frequency_size = match frequency {
Some(candidate) if !physical_equal(candidate, before) =>
match @binary.encoded_module_sizes(baseline, candidate) {
Ok((old_size, new_size)) if new_size < old_size => Some(new_size)
_ => None
}
_ => None
}
let frequency_first = match (grouped_size, frequency_size) {
(None, Some(_)) => true
(Some(grouped_size), Some(frequency_size)) => frequency_size < grouped_size
_ => false
}
if frequency_first {
let candidate = frequency.unwrap()
if oi_validate_local_group_changed_bodies(before, candidate) {
return candidate
}
}
if grouped_size is Some(_) &&
oi_validate_local_group_changed_bodies(before, grouped) {
return grouped
}
if !frequency_first && frequency_size is Some(_) {
let candidate = frequency.unwrap()
if oi_validate_local_group_changed_bodies(before, candidate) {
return candidate
}
}
before
}
///|
// The grouping helper changes only scalar body locals, their accesses, decoded
// local names and stale compiler facts. Every declaration and initializer stays
// in the same index space; unchanged bodies retain their already checked code.
// Validate every replaced body against that unchanged module environment. The
// pipeline's complete final-module validation remains independent of this guard.
fn oi_local_group_changed_bodies(
before : @lib.Module,
grouped : @lib.Module,
) -> Array[(@lib.FuncIdx, @lib.Func)] {
let old = before.code_sec.unwrap().0
let next = grouped.code_sec.unwrap().0
let imported = run_hot_pipeline_imported_func_count(before)
let changed : Array[(@lib.FuncIdx, @lib.Func)] = []
for index in 0.. Bool {
let start = perf_start_timer_opt(
perf, "detail:simplify-locals:encoding-local-group-validate",
)
let result = match
@validate.validate_defined_funcs_against_module(
before,
oi_local_group_changed_bodies(before, grouped),
) {
Ok(errors) => errors.is_empty()
Err(_) => false
}
perf_stop_timer_opt(
perf, "detail:simplify-locals:encoding-local-group-validate", start,
)
result
}
///|
fn oi_local_group_has_offset_metadata(mod_ : @lib.Module) -> Bool {
for custom in mod_.custom_secs {
let @lib.CustomSec(@lib.Name(raw_name), _) = custom
let name = raw_name.to_owned()
if name.starts_with("metadata.code.") ||
name.starts_with("reloc.") ||
name == "linking" ||
name == "sourceMappingURL" ||
name == "external_debug_info" ||
name.starts_with(".debug_") {
return true
}
}
false
}
///|
fn oi_has_numeric_select_annotation(body : Array[@lib.Instruction]) -> Bool {
for instr in body {
match instr {
@lib.Select(Some(types)) if types.length() == 1 &&
types[0] is @lib.NumTypeValType(_) => return true
@lib.Block(_, @lib.Expr(child))
| @lib.Loop(_, @lib.Expr(child))
| @lib.TryTable(_, _, @lib.Expr(child)) =>
if oi_has_numeric_select_annotation(child) {
return true
}
@lib.If(_, yes, no) => {
if oi_has_numeric_select_annotation(yes) {
return true
}
match no {
Some(child) =>
if oi_has_numeric_select_annotation(child) {
return true
}
None => ()
}
}
@lib.Try(_, @lib.Expr(child), catches, _) => {
if oi_has_numeric_select_annotation(child) {
return true
}
for catch_ in catches {
let child = match catch_ {
@lib.LegacyCatch(_, @lib.Expr(child))
| @lib.LegacyCatchAll(@lib.Expr(child)) => child
}
if oi_has_numeric_select_annotation(child) {
return true
}
}
}
_ => ()
}
}
false
}
///|
fn oi_remove_numeric_select_annotations(
body : Array[@lib.Instruction],
) -> (Array[@lib.Instruction], Bool) {
if !oi_has_numeric_select_annotation(body) {
return (body, false)
}
let mut changed = false
let next = body.map(fn(instr) {
let instr = pass_lower_map_children(instr, fn(child) {
let (next, rewritten) = oi_remove_numeric_select_annotations(child)
changed = changed || rewritten
next
})
match instr {
@lib.Select(Some(types)) if types.length() == 1 &&
types[0] is @lib.NumTypeValType(_) => {
// Untyped select infers this scalar type from the same operands.
// Reference and multi-value annotations retain their original meaning.
changed = true
@lib.Instruction::select()
}
_ => instr
}
})
(next, changed)
}