// 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.
///|
// An optional encoding cleanup. Whole recursion groups remain indivisible;
// only global indices are renamed, preserving relative recursive identities.
fn pass_cleanup_unused_type_groups(
mod_ : @lib.Module,
root_scans~ : Array[Int32]? = None,
) -> @lib.Module {
if !mod_.custom_secs.is_empty() {
return mod_
}
let groups = match mod_.type_sec {
Some(@lib.TypeSec(groups)) => groups
None => return mod_
}
let starts : Array[Int32] = []
let counts : Array[Int32] = []
let mut total = 0
for group in groups {
let members = match group {
@lib.SingleRecType(subtype) => [subtype]
@lib.GroupRecType(members) => members
}
starts.push(total)
counts.push(members.length())
total += members.length()
for subtype in members {
if subtype.metadata().is_shared() ||
subtype.describes_type() is Some(_) ||
subtype.descriptor_type() is Some(_) {
return mod_
}
match subtype.get_comptype() {
@lib.FuncCompType(_, _)
| @lib.StructCompType(_)
| @lib.ArrayCompType(_) => ()
_ => return mod_
}
}
}
if total == 0 || !pass_type_cleanup_reference_surfaces_supported(mod_) {
return mod_
}
let type_to_group = Array::make(total, -1)
for group_idx = 0; group_idx < groups.length(); group_idx = group_idx + 1 {
for offset = 0; offset < counts[group_idx]; offset = offset + 1 {
type_to_group[starts[group_idx] + offset] = group_idx
}
}
let refs : DfeTypeRefs = { live: Array::make(total, false), pending: [], }
match root_scans {
Some(count) => count[0] += 1
None => ()
}
dfe_type_refs_module_roots(mod_.with_type_sec(@lib.TypeSec::new([])), refs)
let live : Array[Bool] = Array::make(groups.length(), false)
let pending_groups : Array[Int32] = []
let mut type_cursor = 0
while type_cursor < refs.pending.length() {
let type_slot = refs.pending[type_cursor]
type_cursor += 1
let group_idx = type_to_group[type_slot]
if group_idx >= 0 && !live[group_idx] {
live[group_idx] = true
pending_groups.push(group_idx)
}
}
let mut group_cursor = 0
while group_cursor < pending_groups.length() {
let owner = pending_groups[group_cursor]
group_cursor += 1
dfe_type_refs_rec_type(groups[owner], refs)
while type_cursor < refs.pending.length() {
let type_slot = refs.pending[type_cursor]
type_cursor += 1
let dependency = type_to_group[type_slot]
if dependency >= 0 && !live[dependency] {
live[dependency] = true
pending_groups.push(dependency)
}
}
}
let kept : Array[@lib.RecType] = []
let remap : Map[UInt32, UInt32] = Map([])
let mut next = 0
for group_idx = 0; group_idx < groups.length(); group_idx = group_idx + 1 {
if live[group_idx] {
kept.push(groups[group_idx])
for offset = 0; offset < counts[group_idx]; offset = offset + 1 {
remap[(starts[group_idx] + offset).to_uint()] = (next + offset).to_uint()
}
next += counts[group_idx]
}
}
if next == total {
return mod_
}
let mut candidate = dfe_rewrite_module_type_idxs(
mod_.with_type_sec(@lib.TypeSec::new(kept)),
remap,
)
match mod_.name_sec {
Some(names) => {
let type_names = match names.type_names {
Some(names) => Some(dfe_rewrite_name_map_filtered(names, remap, true))
None => None
}
let field_names = match names.field_names {
Some(@lib.IndirectNameMap(entries)) => {
let rewritten : Array[@lib.IndirectNameAssoc] = []
for @lib.IndirectNameAssoc(old, fields) in entries {
if remap.get(old) is Some(next) {
rewritten.push(@lib.IndirectNameAssoc::new(next, fields))
}
}
Some(@lib.IndirectNameMap::new(rewritten))
}
None => None
}
candidate = candidate.with_name_sec(
@lib.NameSec::new(
module_name=names.module_name,
func_names=names.func_names,
local_names=names.local_names,
label_names=names.label_names,
type_names~,
field_names~,
table_names=names.table_names,
memory_names=names.memory_names,
global_names=names.global_names,
elem_names=names.elem_names,
data_names=names.data_names,
tag_names=names.tag_names,
),
)
}
None => if mod_.raw_name_sec_payload is Some(_) { return mod_ }
}
match (@binary.encode_module(mod_), @binary.encode_module(candidate)) {
(Ok(before), Ok(after)) if after.length() < before.length() =>
if @validate.validate_module(candidate) is Ok(_) {
return candidate
}
_ => ()
}
mod_
}
///|
fn pass_type_cleanup_reference_surfaces_supported(mod_ : @lib.Module) -> Bool {
let expr_supported = fn(expr : @lib.Expr) {
precompute_type_cleanup_instrs_supported(expr.0)
}
match mod_.code_sec {
Some(@lib.CodeSec(funcs)) =>
for func in funcs {
let @lib.Func(_, expr) = func
if !expr_supported(expr) {
return false
}
}
None => ()
}
match mod_.global_sec {
Some(@lib.GlobalSec(globals)) =>
for global in globals {
let @lib.Global(_, expr) = global
if !expr_supported(expr) {
return false
}
}
None => ()
}
match mod_.table_sec {
Some(@lib.TableSec(tables)) =>
for table in tables {
let @lib.Table(_, expr) = table
if expr is Some(expr) && !expr_supported(expr) {
return false
}
}
None => ()
}
match mod_.elem_sec {
Some(@lib.ElemSec(elems)) =>
for elem in elems {
let @lib.Elem(mode, kind) = elem
if mode is @lib.Active(_, offset) && !expr_supported(offset) {
return false
}
match kind {
@lib.FuncExprsElemKind(exprs) | @lib.TypedExprsElemKind(_, exprs) =>
for expr in exprs {
if !expr_supported(expr) {
return false
}
}
_ => ()
}
}
None => ()
}
match mod_.data_sec {
Some(@lib.DataSec(datas)) =>
for data in datas {
let @lib.Data(mode, _) = data
if mode is @lib.Active(_, offset) && !expr_supported(offset) {
return false
}
}
None => ()
}
true
}