// 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/DuplicateFunctionElimination.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 dfe_summary() -> String {
"Merge duplicate defined functions, rewrite surviving function references, and compact duplicate function and bounded GC singleton type indices."
}
///|
fn dfe_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 dfe_func_annotation_map(
mod_ : @lib.Module,
) -> Map[UInt32, Array[@lib.FuncAnnotation]] {
let by_idx : Map[UInt32, Array[@lib.FuncAnnotation]] = Map([])
match mod_.func_annotation_sec {
Some(@lib.FuncAnnotationSec(entries)) =>
for entry in entries {
let @lib.FuncAnnotationAssoc(entry_idx, annotations) = entry
let merged = by_idx.get(entry_idx).unwrap_or([]).copy()
for annotation in annotations {
if !merged.contains(annotation) {
merged.push(annotation)
}
}
by_idx[entry_idx] = merged
}
None => ()
}
by_idx
}
///|
fn dfe_func_annotations(
by_idx : Map[UInt32, Array[@lib.FuncAnnotation]],
abs_idx : UInt32,
) -> Array[@lib.FuncAnnotation] {
by_idx.get(abs_idx).unwrap_or([])
}
///|
fn dfe_ref_type_is_nullable_funcref(ref_type : @lib.RefType) -> Bool {
ref_type == @lib.RefType::abs(@lib.AbsHeapType::func()) ||
ref_type ==
@lib.RefType::new(true, @lib.HeapType::abs(@lib.AbsHeapType::func()))
}
///|
fn dfe_compactable_elem_expr_func_idx(expr : @lib.Expr) -> @lib.FuncIdx? {
match expr {
@lib.Expr([@lib.RefFunc(func_idx)]) => Some(func_idx)
_ => None
}
}
///|
fn dfe_canonicalize_elem_kind(kind : @lib.ElemKind) -> @lib.ElemKind {
match kind {
@lib.FuncsElemKind(_) => kind
@lib.FuncExprsElemKind(exprs) => {
let funcs : Array[@lib.FuncIdx] = []
for expr in exprs {
match dfe_compactable_elem_expr_func_idx(expr) {
Some(func_idx) => funcs.push(func_idx)
None => return kind
}
}
@lib.ElemKind::funcs(funcs)
}
@lib.TypedExprsElemKind(ref_type, exprs) => {
if !dfe_ref_type_is_nullable_funcref(ref_type) {
return kind
}
let funcs : Array[@lib.FuncIdx] = []
for expr in exprs {
match dfe_compactable_elem_expr_func_idx(expr) {
Some(func_idx) => funcs.push(func_idx)
None => return kind
}
}
@lib.ElemKind::funcs(funcs)
}
}
}
///|
fn dfe_canonicalize_elem_segments(mod_ : @lib.Module) -> @lib.Module {
match mod_.elem_sec {
Some(@lib.ElemSec(elems)) => {
let next : Array[@lib.Elem] = []
let mut changed = false
for elem in elems {
let @lib.Elem(mode, kind) = elem
let rewritten_kind = dfe_canonicalize_elem_kind(kind)
if rewritten_kind != kind {
changed = true
}
next.push(@lib.Elem::new(mode, rewritten_kind))
}
if changed {
mod_.with_elem_sec(@lib.ElemSec::new(next))
} else {
mod_
}
}
None => mod_
}
}
///|
fn dfe_strip_name_sec(mod_ : @lib.Module) -> @lib.Module {
mod_.without_name_sec()
}
///|
fn dfe_supports_simple_type_dedup(rec_types : Array[@lib.RecType]) -> Bool {
for rec_type in rec_types {
match rec_type {
@lib.SingleRecType(subtype) => {
if subtype.super_types().length() > 0 ||
subtype.describes_type() is Some(_) ||
subtype.descriptor_type() is Some(_) {
return false
}
match subtype.get_comptype() {
@lib.FuncCompType(_, _) => ()
_ => return false
}
}
@lib.GroupRecType(_) => return false
}
}
true
}
///|
fn dfe_duplicate_simple_type_canonical_map(
rec_types : Array[@lib.RecType],
) -> Map[UInt32, UInt32] {
let canonical_map : Map[UInt32, UInt32] = Map([])
if !dfe_supports_simple_type_dedup(rec_types) {
return dfe_duplicate_backward_singleton_type_map(rec_types)
}
let buckets : @hashmap.HashMap[Int32, Array[Int32]] = @hashmap.new(
capacity=rec_types.length(),
)
let mut old_idx = 0
while old_idx < rec_types.length() {
let rec_type = rec_types[old_idx]
let mut canonical_old_idx : UInt32? = None
let hash_key = rec_type.hash()
match buckets.get(hash_key) {
Some(bucket) => {
let mut bucket_idx = 0
while bucket_idx < bucket.length() {
let prev_idx = bucket[bucket_idx]
if rec_types[prev_idx] == rec_type {
canonical_old_idx = Some(prev_idx.reinterpret_as_uint())
break
}
bucket_idx += 1
}
if canonical_old_idx is None {
bucket.push(old_idx)
}
}
None => buckets.set(hash_key, [old_idx])
}
match canonical_old_idx {
Some(canonical_old_idx) =>
canonical_map[old_idx.reinterpret_as_uint()] = canonical_old_idx
None => ()
}
old_idx += 1
}
canonical_map
}
///|
priv struct DfeTypeRewriteCache {
indices : Map[UInt32, @lib.TypeIdx]
heaps : Map[UInt32, @lib.HeapType]
}
///|
fn dfe_new_type_rewrite_cache() -> DfeTypeRewriteCache {
{ indices: Map([]), heaps: Map([]), }
}
// One CodeSec remap owns this cache. Its keys are direct final targets;
// only immutable indices and concrete heaps are shared, never IR containers
// or type graphs.
///|
fn dfe_shared_rewritten_type_idx(
target : UInt32,
cache : DfeTypeRewriteCache?,
) -> @lib.TypeIdx {
match cache {
None => @lib.TypeIdx::new(target)
Some(cache) =>
match cache.indices.get(target) {
Some(idx) => idx
None => {
let idx = @lib.TypeIdx::new(target)
cache.indices[target] = idx
idx
}
}
}
}
///|
fn dfe_rewrite_type_idx(
idx : @lib.TypeIdx,
remap : Map[UInt32, UInt32],
index_cache~ : DfeTypeRewriteCache? = None,
) -> @lib.TypeIdx {
match idx {
@lib.TypeIdx(raw) =>
dfe_shared_rewritten_type_idx(remap.get(raw).unwrap_or(raw), index_cache)
@lib.RecIdx(_) => idx
}
}
///|
fn dfe_try_rewrite_type_idx(
idx : @lib.TypeIdx,
remap : Map[UInt32, UInt32],
index_cache~ : DfeTypeRewriteCache? = None,
) -> @lib.TypeIdx? {
match idx {
@lib.TypeIdx(raw) =>
match remap.get(raw) {
Some(next) =>
if next != raw {
Some(dfe_shared_rewritten_type_idx(next, index_cache))
} else {
None
}
None => None
}
@lib.RecIdx(_) => None
}
}
///|
fn dfe_type_idx_is_remapped(
idx : @lib.TypeIdx,
remap : Map[UInt32, UInt32],
) -> Bool {
match idx {
@lib.TypeIdx(raw) => remap.get(raw) != None
@lib.RecIdx(_) => false
}
}
///|
fn dfe_type_metadata_uses_remap(
metadata : @lib.TypeMetadata,
remap : Map[UInt32, UInt32],
) -> Bool {
let { describes, descriptor, .. } = metadata
match describes {
Some(idx) => if dfe_type_idx_is_remapped(idx, remap) { return true }
None => ()
}
match descriptor {
Some(idx) => dfe_type_idx_is_remapped(idx, remap)
None => false
}
}
///|
fn dfe_heap_type_uses_remap(
heap_type : @lib.HeapType,
remap : Map[UInt32, UInt32],
) -> Bool {
match heap_type {
@lib.AbsHeapTypeHeapType(_) | @lib.SharedAbsHeapTypeHeapType(_) => false
@lib.HeapType(type_idx) => dfe_type_idx_is_remapped(type_idx, remap)
@lib.DefTypeHeapType(@lib.DefType(rec_type, _)) =>
dfe_rec_type_uses_remap(rec_type, remap)
}
}
///|
fn dfe_ref_type_uses_remap(
ref_type : @lib.RefType,
remap : Map[UInt32, UInt32],
) -> Bool {
match ref_type {
@lib.HeapTypeRefType(_, _, heap_type) =>
dfe_heap_type_uses_remap(heap_type, remap)
@lib.AbsHeapTypeRefType(_) => false
}
}
///|
fn dfe_val_type_uses_remap(
val_type : @lib.ValType,
remap : Map[UInt32, UInt32],
) -> Bool {
match val_type {
@lib.RefTypeValType(ref_type) => dfe_ref_type_uses_remap(ref_type, remap)
_ => false
}
}
///|
fn dfe_storage_type_uses_remap(
storage_type : @lib.StorageType,
remap : Map[UInt32, UInt32],
) -> Bool {
match storage_type {
@lib.ValTypeStorageType(val_type) =>
dfe_val_type_uses_remap(val_type, remap)
@lib.PackTypeStorageType(_) => false
}
}
///|
fn dfe_field_type_uses_remap(
field_type : @lib.FieldType,
remap : Map[UInt32, UInt32],
) -> Bool {
let @lib.FieldType(storage_type, _) = field_type
dfe_storage_type_uses_remap(storage_type, remap)
}
///|
fn dfe_field_types_use_remap(
field_types : Array[@lib.FieldType],
remap : Map[UInt32, UInt32],
) -> Bool {
let mut idx = 0
while idx < field_types.length() {
if dfe_field_type_uses_remap(field_types[idx], remap) {
return true
}
idx += 1
}
false
}
///|
fn dfe_val_types_use_remap(
val_types : Array[@lib.ValType],
remap : Map[UInt32, UInt32],
) -> Bool {
let mut idx = 0
while idx < val_types.length() {
if dfe_val_type_uses_remap(val_types[idx], remap) {
return true
}
idx += 1
}
false
}
///|
fn dfe_comp_type_uses_remap(
comp_type : @lib.CompType,
remap : Map[UInt32, UInt32],
) -> Bool {
match comp_type {
@lib.ArrayCompType(field_type) =>
dfe_field_type_uses_remap(field_type, remap)
@lib.StructCompType(field_types) =>
dfe_field_types_use_remap(field_types, remap)
@lib.FuncCompType(params, results) =>
dfe_val_types_use_remap(params, remap) ||
dfe_val_types_use_remap(results, remap)
@lib.ContCompType(func_type) => dfe_type_idx_is_remapped(func_type, remap)
}
}
///|
fn dfe_type_idxs_use_remap(
type_idxs : Array[@lib.TypeIdx],
remap : Map[UInt32, UInt32],
) -> Bool {
let mut idx = 0
while idx < type_idxs.length() {
if dfe_type_idx_is_remapped(type_idxs[idx], remap) {
return true
}
idx += 1
}
false
}
///|
fn dfe_sub_type_uses_remap(
subtype : @lib.SubType,
remap : Map[UInt32, UInt32],
) -> Bool {
match subtype {
@lib.SubType(_, supers, metadata, comp_type) =>
dfe_type_idxs_use_remap(supers, remap) ||
dfe_type_metadata_uses_remap(metadata, remap) ||
dfe_comp_type_uses_remap(comp_type, remap)
@lib.CompTypeSubType(metadata, comp_type) =>
dfe_type_metadata_uses_remap(metadata, remap) ||
dfe_comp_type_uses_remap(comp_type, remap)
}
}
///|
fn dfe_sub_types_use_remap(
subtypes : Array[@lib.SubType],
remap : Map[UInt32, UInt32],
) -> Bool {
let mut idx = 0
while idx < subtypes.length() {
if dfe_sub_type_uses_remap(subtypes[idx], remap) {
return true
}
idx += 1
}
false
}
///|
fn dfe_rec_type_uses_remap(
rec_type : @lib.RecType,
remap : Map[UInt32, UInt32],
) -> Bool {
match rec_type {
@lib.SingleRecType(subtype) => dfe_sub_type_uses_remap(subtype, remap)
@lib.GroupRecType(subtypes) => dfe_sub_types_use_remap(subtypes, remap)
}
}
///|
fn dfe_type_metadata_mentions_type_idxs(metadata : @lib.TypeMetadata) -> Bool {
let { describes, descriptor, .. } = metadata
describes is Some(_) || descriptor is Some(_)
}
///|
fn dfe_heap_type_mentions_type_idxs(heap_type : @lib.HeapType) -> Bool {
match heap_type {
@lib.AbsHeapTypeHeapType(_) | @lib.SharedAbsHeapTypeHeapType(_) => false
@lib.HeapType(_) => true
@lib.DefTypeHeapType(@lib.DefType(rec_type, _)) =>
dfe_rec_type_mentions_type_idxs(rec_type)
}
}
///|
fn dfe_ref_type_mentions_type_idxs(ref_type : @lib.RefType) -> Bool {
match ref_type {
@lib.HeapTypeRefType(_, _, heap_type) =>
dfe_heap_type_mentions_type_idxs(heap_type)
@lib.AbsHeapTypeRefType(_) => false
}
}
///|
fn dfe_val_type_mentions_type_idxs(val_type : @lib.ValType) -> Bool {
match val_type {
@lib.RefTypeValType(ref_type) => dfe_ref_type_mentions_type_idxs(ref_type)
_ => false
}
}
///|
fn dfe_storage_type_mentions_type_idxs(storage_type : @lib.StorageType) -> Bool {
match storage_type {
@lib.ValTypeStorageType(val_type) =>
dfe_val_type_mentions_type_idxs(val_type)
@lib.PackTypeStorageType(_) => false
}
}
///|
fn dfe_field_type_mentions_type_idxs(field_type : @lib.FieldType) -> Bool {
let @lib.FieldType(storage_type, _) = field_type
dfe_storage_type_mentions_type_idxs(storage_type)
}
///|
fn dfe_field_types_mention_type_idxs(
field_types : Array[@lib.FieldType],
) -> Bool {
let mut idx = 0
while idx < field_types.length() {
if dfe_field_type_mentions_type_idxs(field_types[idx]) {
return true
}
idx += 1
}
false
}
///|
fn dfe_val_types_mention_type_idxs(val_types : Array[@lib.ValType]) -> Bool {
let mut idx = 0
while idx < val_types.length() {
if dfe_val_type_mentions_type_idxs(val_types[idx]) {
return true
}
idx += 1
}
false
}
///|
fn dfe_type_idxs_mention_type_idxs(type_idxs : Array[@lib.TypeIdx]) -> Bool {
let mut idx = 0
while idx < type_idxs.length() {
match type_idxs[idx] {
@lib.TypeIdx(_) => return true
@lib.RecIdx(_) => ()
}
idx += 1
}
false
}
///|
fn dfe_comp_type_mentions_type_idxs(comp_type : @lib.CompType) -> Bool {
match comp_type {
@lib.ArrayCompType(field_type) =>
dfe_field_type_mentions_type_idxs(field_type)
@lib.StructCompType(field_types) =>
dfe_field_types_mention_type_idxs(field_types)
@lib.FuncCompType(params, results) =>
dfe_val_types_mention_type_idxs(params) ||
dfe_val_types_mention_type_idxs(results)
@lib.ContCompType(func_type) =>
match func_type {
@lib.TypeIdx(_) => true
@lib.RecIdx(_) => false
}
}
}
///|
fn dfe_sub_type_mentions_type_idxs(subtype : @lib.SubType) -> Bool {
match subtype {
@lib.SubType(_, supers, metadata, comp_type) =>
dfe_type_idxs_mention_type_idxs(supers) ||
dfe_type_metadata_mentions_type_idxs(metadata) ||
dfe_comp_type_mentions_type_idxs(comp_type)
@lib.CompTypeSubType(metadata, comp_type) =>
dfe_type_metadata_mentions_type_idxs(metadata) ||
dfe_comp_type_mentions_type_idxs(comp_type)
}
}
///|
fn dfe_sub_types_mention_type_idxs(subtypes : Array[@lib.SubType]) -> Bool {
let mut idx = 0
while idx < subtypes.length() {
if dfe_sub_type_mentions_type_idxs(subtypes[idx]) {
return true
}
idx += 1
}
false
}
///|
fn dfe_rec_type_mentions_type_idxs(rec_type : @lib.RecType) -> Bool {
match rec_type {
@lib.SingleRecType(subtype) => dfe_sub_type_mentions_type_idxs(subtype)
@lib.GroupRecType(subtypes) => dfe_sub_types_mention_type_idxs(subtypes)
}
}
///|
fn dfe_block_type_mentions_type_idxs(block_type : @lib.BlockType) -> Bool {
match block_type {
@lib.VoidBlockType => false
@lib.ValTypeBlockType(val_type) => dfe_val_type_mentions_type_idxs(val_type)
@lib.TypeIdxBlockType(_) => true
}
}
///|
fn dfe_instruction_array_mentions_type_idxs(
instrs : Array[@lib.Instruction],
) -> Bool {
let mut idx = 0
while idx < instrs.length() {
if dfe_instruction_mentions_type_idxs(instrs[idx]) {
return true
}
idx += 1
}
false
}
///|
fn dfe_expr_mentions_type_idxs(expr : @lib.Expr) -> Bool {
let @lib.Expr(instrs) = expr
dfe_instruction_array_mentions_type_idxs(instrs)
}
///|
fn _dfe_locals_mention_type_idxs(locals : @lib.Locals) -> Bool {
let runs = locals.runs()
let mut idx = 0
while idx < runs.length() {
if dfe_val_type_mentions_type_idxs(runs[idx].val_type()) {
return true
}
idx += 1
}
false
}
///|
fn dfe_instruction_mentions_type_idxs(instr : @lib.Instruction) -> Bool {
match instr {
@lib.StructWait(_, _)
| @lib.ArrayLoad(_, _, _)
| @lib.ArrayStore(_, _, _)
| @lib.StructAtomicSet(_, _, _)
| @lib.StructAtomicRmw(_, _, _, _)
| @lib.StructAtomicCmpxchg(_, _, _)
| @lib.ArrayAtomicGet(_, _)
| @lib.ArrayAtomicGetS(_, _)
| @lib.ArrayAtomicGetU(_, _)
| @lib.ArrayAtomicSet(_, _)
| @lib.ArrayAtomicRmw(_, _, _)
| @lib.ArrayAtomicCmpxchg(_, _) => true
@lib.ContNew(_)
| @lib.ContBind(_, _)
| @lib.Resume(_, _)
| @lib.ResumeThrow(_, _, _)
| @lib.ResumeThrowRef(_, _)
| @lib.StackSwitch(_, _)
| @lib.BrOnCastDescEq(_, _, _)
| @lib.BrOnCastDescEqFail(_, _, _) => true
@lib.Try(_, _, _, _) =>
dfe_scan_rewrite_instruction_type_idxs(instr, Map([])).mentions_type_idxs
@lib.Block(block_type, expr) =>
dfe_block_type_mentions_type_idxs(block_type) ||
dfe_expr_mentions_type_idxs(expr)
@lib.Loop(block_type, expr) =>
dfe_block_type_mentions_type_idxs(block_type) ||
dfe_expr_mentions_type_idxs(expr)
@lib.If(block_type, then_body, else_body) => {
let else_mentions = match else_body {
Some(body) => dfe_instruction_array_mentions_type_idxs(body)
None => false
}
dfe_block_type_mentions_type_idxs(block_type) ||
dfe_instruction_array_mentions_type_idxs(then_body) ||
else_mentions
}
@lib.CallIndirect(_, _) => true
@lib.ReturnCallIndirect(_, _) => true
@lib.CallRef(_) => true
@lib.ReturnCallRef(_) => true
@lib.Select(Some(types)) => dfe_val_types_mention_type_idxs(types)
@lib.Select(None) => false
@lib.TryTable(block_type, _, body) =>
dfe_block_type_mentions_type_idxs(block_type) ||
dfe_expr_mentions_type_idxs(body)
@lib.RefNull(ref_type) => dfe_ref_type_mentions_type_idxs(ref_type)
@lib.StructNew(_) => true
@lib.StructNewDefault(_) => true
@lib.StructNewDesc(_) => true
@lib.StructNewDefaultDesc(_) => true
@lib.StructGet(_, _) => true
@lib.StructGetS(_, _) => true
@lib.StructGetU(_, _) => true
@lib.StructAtomicGet(_, _, _) => true
@lib.StructAtomicGetS(_, _, _) => true
@lib.StructAtomicGetU(_, _, _) => true
@lib.StructSet(_, _) => true
@lib.ArrayNew(_) => true
@lib.ArrayNewDefault(_) => true
@lib.ArrayNewFixed(_, _) => true
@lib.ArrayNewData(_, _) => true
@lib.ArrayNewElem(_, _) => true
@lib.ArrayGet(_) => true
@lib.ArrayGetS(_) => true
@lib.ArrayGetU(_) => true
@lib.ArraySet(_) => true
@lib.ArrayFill(_) => true
@lib.ArrayCopy(_, _) => true
@lib.ArrayInitData(_, _) => true
@lib.ArrayInitElem(_, _) => true
@lib.RefGetDesc(_) => true
@lib.RefTest(_, _, heap_type) => dfe_heap_type_mentions_type_idxs(heap_type)
@lib.RefCast(_, _, heap_type) => dfe_heap_type_mentions_type_idxs(heap_type)
@lib.RefTestDesc(_, heap_type) =>
dfe_heap_type_mentions_type_idxs(heap_type)
@lib.RefCastDescEq(_, _, heap_type) =>
dfe_heap_type_mentions_type_idxs(heap_type)
@lib.BrOnCast(_, _, source, target) =>
dfe_heap_type_mentions_type_idxs(source) ||
dfe_heap_type_mentions_type_idxs(target)
@lib.BrOnCastFail(_, _, source, target) =>
dfe_heap_type_mentions_type_idxs(source) ||
dfe_heap_type_mentions_type_idxs(target)
_ => false
}
}
///|
fn dfe_rewrite_type_metadata(
metadata : @lib.TypeMetadata,
remap : Map[UInt32, UInt32],
) -> @lib.TypeMetadata {
let { describes, descriptor, shared, } = metadata
@lib.TypeMetadata::new(
describes=match describes {
Some(idx) => Some(dfe_rewrite_type_idx(idx, remap))
None => None
},
descriptor=match descriptor {
Some(idx) => Some(dfe_rewrite_type_idx(idx, remap))
None => None
},
shared~,
)
}
// The index was already mapped once. Cache only concrete absolute wrappers;
// recursive-group indices and inline/abstract/shared type forms stay separate.
///|
fn dfe_shared_rewritten_heap_type(
idx : @lib.TypeIdx,
cache : DfeTypeRewriteCache?,
) -> @lib.HeapType {
match (idx, cache) {
(@lib.TypeIdx(target), Some(cache)) =>
match cache.heaps.get(target) {
Some(heap_type) => heap_type
None => {
let heap_type = @lib.HeapType::new(idx)
cache.heaps[target] = heap_type
heap_type
}
}
_ => @lib.HeapType::new(idx)
}
}
///|
fn dfe_rewrite_heap_type(
heap_type : @lib.HeapType,
remap : Map[UInt32, UInt32],
index_cache~ : DfeTypeRewriteCache? = None,
) -> @lib.HeapType {
match heap_type {
@lib.AbsHeapTypeHeapType(_) | @lib.SharedAbsHeapTypeHeapType(_) => heap_type
@lib.HeapType(type_idx) =>
dfe_shared_rewritten_heap_type(
dfe_rewrite_type_idx(type_idx, remap, index_cache~),
index_cache,
)
@lib.DefTypeHeapType(@lib.DefType(rec_type, idx)) =>
@lib.HeapType::def_type(
@lib.DefType::new(dfe_rewrite_rec_type(rec_type, remap), idx),
)
}
}
///|
fn dfe_rewrite_ref_type(
ref_type : @lib.RefType,
remap : Map[UInt32, UInt32],
index_cache~ : DfeTypeRewriteCache? = None,
) -> @lib.RefType {
match ref_type {
@lib.HeapTypeRefType(nullable, exact, heap_type) =>
@lib.RefType::new(
nullable,
dfe_rewrite_heap_type(heap_type, remap, index_cache~),
exact~,
)
@lib.AbsHeapTypeRefType(_) => ref_type
}
}
///|
fn dfe_try_rewrite_heap_type(
heap_type : @lib.HeapType,
remap : Map[UInt32, UInt32],
index_cache~ : DfeTypeRewriteCache? = None,
) -> @lib.HeapType? {
if dfe_heap_type_uses_remap(heap_type, remap) {
Some(dfe_rewrite_heap_type(heap_type, remap, index_cache~))
} else {
None
}
}
///|
fn dfe_try_rewrite_ref_type(
ref_type : @lib.RefType,
remap : Map[UInt32, UInt32],
index_cache~ : DfeTypeRewriteCache? = None,
) -> @lib.RefType? {
if dfe_ref_type_uses_remap(ref_type, remap) {
Some(dfe_rewrite_ref_type(ref_type, remap, index_cache~))
} else {
None
}
}
///|
fn dfe_rewrite_val_type(
val_type : @lib.ValType,
remap : Map[UInt32, UInt32],
index_cache~ : DfeTypeRewriteCache? = None,
) -> @lib.ValType {
match val_type {
@lib.RefTypeValType(ref_type) =>
@lib.ValType::ref_type(
dfe_rewrite_ref_type(ref_type, remap, index_cache~),
)
_ => val_type
}
}
///|
fn dfe_try_rewrite_val_type(
val_type : @lib.ValType,
remap : Map[UInt32, UInt32],
index_cache~ : DfeTypeRewriteCache? = None,
) -> @lib.ValType? {
if dfe_val_type_uses_remap(val_type, remap) {
Some(dfe_rewrite_val_type(val_type, remap, index_cache~))
} else {
None
}
}
///|
fn dfe_try_rewrite_val_types(
val_types : Array[@lib.ValType],
remap : Map[UInt32, UInt32],
index_cache~ : DfeTypeRewriteCache? = None,
) -> Array[@lib.ValType]? {
let rewritten : Array[@lib.ValType] = Array::new(capacity=val_types.length())
let mut changed = false
let mut idx = 0
while idx < val_types.length() {
let val_type = val_types[idx]
match dfe_try_rewrite_val_type(val_type, remap, index_cache~) {
Some(next) => {
if !changed {
let mut prefix = 0
while prefix < idx {
rewritten.push(val_types[prefix])
prefix += 1
}
changed = true
}
rewritten.push(next)
}
None => if changed { rewritten.push(val_type) }
}
idx += 1
}
if changed {
Some(rewritten)
} else {
None
}
}
///|
fn dfe_rewrite_storage_type(
storage_type : @lib.StorageType,
remap : Map[UInt32, UInt32],
) -> @lib.StorageType {
match storage_type {
@lib.ValTypeStorageType(val_type) =>
@lib.StorageType::val_type(dfe_rewrite_val_type(val_type, remap))
@lib.PackTypeStorageType(_) => storage_type
}
}
///|
fn dfe_rewrite_field_type(
field_type : @lib.FieldType,
remap : Map[UInt32, UInt32],
) -> @lib.FieldType {
let @lib.FieldType(storage_type, mutability) = field_type
@lib.FieldType::new(dfe_rewrite_storage_type(storage_type, remap), mutability)
}
///|
fn dfe_rewrite_result_type(
values : Array[@lib.ValType],
remap : Map[UInt32, UInt32],
) -> Array[@lib.ValType] {
let rewritten : Array[@lib.ValType] = []
for value in values {
rewritten.push(dfe_rewrite_val_type(value, remap))
}
rewritten
}
///|
fn dfe_rewrite_comp_type(
comp_type : @lib.CompType,
remap : Map[UInt32, UInt32],
) -> @lib.CompType {
match comp_type {
@lib.ArrayCompType(field_type) =>
@lib.CompType::array(dfe_rewrite_field_type(field_type, remap))
@lib.StructCompType(field_types) => {
let rewritten : Array[@lib.FieldType] = []
for field_type in field_types {
rewritten.push(dfe_rewrite_field_type(field_type, remap))
}
@lib.CompType::struct_(rewritten)
}
@lib.FuncCompType(params, results) =>
@lib.CompType::func(
dfe_rewrite_result_type(params, remap),
dfe_rewrite_result_type(results, remap),
)
@lib.ContCompType(func_type) =>
@lib.CompType::cont(dfe_rewrite_type_idx(func_type, remap))
}
}
///|
fn dfe_rewrite_sub_type(
subtype : @lib.SubType,
remap : Map[UInt32, UInt32],
) -> @lib.SubType {
match subtype {
@lib.SubType(is_final, supers, metadata, comp_type) => {
let rewritten_supers : Array[@lib.TypeIdx] = []
for super_type in supers {
rewritten_supers.push(dfe_rewrite_type_idx(super_type, remap))
}
@lib.SubType::new(
is_final,
rewritten_supers,
dfe_rewrite_comp_type(comp_type, remap),
metadata=dfe_rewrite_type_metadata(metadata, remap),
)
}
@lib.CompTypeSubType(metadata, comp_type) =>
@lib.SubType::comp_type(
dfe_rewrite_comp_type(comp_type, remap),
metadata=dfe_rewrite_type_metadata(metadata, remap),
)
}
}
///|
fn dfe_rewrite_rec_type(
rec_type : @lib.RecType,
remap : Map[UInt32, UInt32],
) -> @lib.RecType {
match rec_type {
@lib.SingleRecType(subtype) =>
@lib.RecType::new(dfe_rewrite_sub_type(subtype, remap))
@lib.GroupRecType(subtypes) => {
let rewritten : Array[@lib.SubType] = []
for subtype in subtypes {
rewritten.push(dfe_rewrite_sub_type(subtype, remap))
}
@lib.RecType::group(rewritten)
}
}
}
///|
fn dfe_rewrite_block_type(
block_type : @lib.BlockType,
remap : Map[UInt32, UInt32],
) -> @lib.BlockType {
match block_type {
@lib.ValTypeBlockType(val_type) =>
@lib.BlockType::val_type(dfe_rewrite_val_type(val_type, remap))
@lib.TypeIdxBlockType(type_idx) =>
@lib.BlockType::type_idx(dfe_rewrite_type_idx(type_idx, remap))
_ => block_type
}
}
///|
fn dfe_try_rewrite_block_type(
block_type : @lib.BlockType,
remap : Map[UInt32, UInt32],
index_cache~ : DfeTypeRewriteCache? = None,
) -> @lib.BlockType? {
match block_type {
@lib.ValTypeBlockType(val_type) =>
match dfe_try_rewrite_val_type(val_type, remap, index_cache~) {
Some(next) => Some(@lib.BlockType::val_type(next))
None => None
}
@lib.TypeIdxBlockType(type_idx) =>
match dfe_try_rewrite_type_idx(type_idx, remap, index_cache~) {
Some(next) => Some(@lib.BlockType::type_idx(next))
None => None
}
_ => None
}
}
///|
priv struct DfeTypeRewriteScan[T] {
rewritten : T?
mentions_type_idxs : Bool
}
///|
fn dfe_scan_rewrite_val_type(
val_type : @lib.ValType,
remap : Map[UInt32, UInt32],
) -> DfeTypeRewriteScan[@lib.ValType] {
{
rewritten: dfe_try_rewrite_val_type(val_type, remap),
mentions_type_idxs: dfe_val_type_mentions_type_idxs(val_type),
}
}
///|
fn dfe_scan_rewrite_val_types(
val_types : Array[@lib.ValType],
remap : Map[UInt32, UInt32],
) -> DfeTypeRewriteScan[Array[@lib.ValType]] {
let rewritten : Array[@lib.ValType] = Array::new(capacity=val_types.length())
let mut changed = false
let mut mentions_type_idxs = false
let mut idx = 0
while idx < val_types.length() {
let val_type = val_types[idx]
let scan = dfe_scan_rewrite_val_type(val_type, remap)
mentions_type_idxs = mentions_type_idxs || scan.mentions_type_idxs
match scan.rewritten {
Some(next) => {
if !changed {
let mut prefix = 0
while prefix < idx {
rewritten.push(val_types[prefix])
prefix += 1
}
changed = true
}
rewritten.push(next)
}
None => if changed { rewritten.push(val_type) }
}
idx += 1
}
{
rewritten: if changed {
Some(rewritten)
} else {
None
},
mentions_type_idxs,
}
}
///|
fn dfe_scan_rewrite_block_type(
block_type : @lib.BlockType,
remap : Map[UInt32, UInt32],
) -> DfeTypeRewriteScan[@lib.BlockType] {
match block_type {
@lib.VoidBlockType => { rewritten: None, mentions_type_idxs: false, }
@lib.ValTypeBlockType(val_type) => {
let scan = dfe_scan_rewrite_val_type(val_type, remap)
{
rewritten: match scan.rewritten {
Some(next) => Some(@lib.BlockType::val_type(next))
None => None
},
mentions_type_idxs: scan.mentions_type_idxs,
}
}
@lib.TypeIdxBlockType(type_idx) =>
{
rewritten: match dfe_try_rewrite_type_idx(type_idx, remap) {
Some(next) => Some(@lib.BlockType::type_idx(next))
None => None
},
mentions_type_idxs: true,
}
}
}
///|
fn dfe_scan_rewrite_instruction_array_type_idxs(
instrs : Array[@lib.Instruction],
remap : Map[UInt32, UInt32],
) -> DfeTypeRewriteScan[Array[@lib.Instruction]] {
let rewritten : Array[@lib.Instruction] = Array::new(capacity=instrs.length())
let mut changed = false
let mut mentions_type_idxs = false
let mut idx = 0
while idx < instrs.length() {
let instr = instrs[idx]
let scan = dfe_scan_rewrite_instruction_type_idxs(instr, remap)
mentions_type_idxs = mentions_type_idxs || scan.mentions_type_idxs
match scan.rewritten {
Some(next) => {
if !changed {
let mut prefix = 0
while prefix < idx {
rewritten.push(instrs[prefix])
prefix += 1
}
changed = true
}
rewritten.push(next)
}
None => if changed { rewritten.push(instr) }
}
idx += 1
}
{
rewritten: if changed {
Some(rewritten)
} else {
None
},
mentions_type_idxs,
}
}
///|
fn dfe_scan_rewrite_expr_type_idxs(
expr : @lib.Expr,
remap : Map[UInt32, UInt32],
) -> DfeTypeRewriteScan[@lib.Expr] {
let @lib.Expr(instrs) = expr
let scan = dfe_scan_rewrite_instruction_array_type_idxs(instrs, remap)
{
rewritten: match scan.rewritten {
Some(next) => Some(@lib.Expr::new(next))
None => None
},
mentions_type_idxs: scan.mentions_type_idxs,
}
}
///|
fn dfe_scan_rewrite_locals_type_idxs(
locals : @lib.Locals,
remap : Map[UInt32, UInt32],
) -> DfeTypeRewriteScan[@lib.Locals] {
let runs = locals.runs()
let rewritten : Array[@lib.LocalRun] = Array::new(capacity=runs.length())
let mut changed = false
let mut mentions_type_idxs = false
let mut idx = 0
while idx < runs.length() {
let run = runs[idx]
let scan = dfe_scan_rewrite_val_type(run.val_type(), remap)
mentions_type_idxs = mentions_type_idxs || scan.mentions_type_idxs
match scan.rewritten {
Some(next) => {
if !changed {
let mut prefix = 0
while prefix < idx {
rewritten.push(runs[prefix])
prefix += 1
}
changed = true
}
rewritten.push(@lib.LocalRun::new(run.count(), next))
}
None => if changed { rewritten.push(run) }
}
idx += 1
}
{
rewritten: if changed {
Some(@lib.Locals::new(rewritten))
} else {
None
},
mentions_type_idxs,
}
}
///|
fn dfe_scan_rewrite_func_type_idxs(
func : @lib.Func,
remap : Map[UInt32, UInt32],
) -> DfeTypeRewriteScan[@lib.Func] {
let @lib.Func(locals, body) = func
let locals_scan = dfe_scan_rewrite_locals_type_idxs(locals, remap)
let body_scan = dfe_scan_rewrite_expr_type_idxs(body, remap)
{
rewritten: if locals_scan.rewritten is None && body_scan.rewritten is None {
None
} else {
Some(
@lib.Func::new(
match locals_scan.rewritten {
Some(next) => next
None => locals
},
match body_scan.rewritten {
Some(next) => next
None => body
},
),
)
},
mentions_type_idxs: locals_scan.mentions_type_idxs ||
body_scan.mentions_type_idxs,
}
}
///|
fn dfe_try_rewrite_instruction_array_type_idxs(
instrs : Array[@lib.Instruction],
remap : Map[UInt32, UInt32],
index_cache~ : DfeTypeRewriteCache? = None,
) -> Array[@lib.Instruction]? {
// Most regions mention no remapped type. Allocate output capacity only at
// the first actual rewrite, preserving the original prefix and input storage.
let mut rewritten : Array[@lib.Instruction]? = None
let mut idx = 0
while idx < instrs.length() {
let instr = instrs[idx]
match dfe_try_rewrite_instruction_type_idxs(instr, remap, index_cache~) {
Some(next) => {
let out = match rewritten {
Some(out) => out
None => {
let out : Array[@lib.Instruction] = Array::new(
capacity=instrs.length(),
)
let mut prefix = 0
while prefix < idx {
out.push(instrs[prefix])
prefix += 1
}
rewritten = Some(out)
out
}
}
out.push(next)
}
None =>
match rewritten {
Some(out) => out.push(instr)
None => ()
}
}
idx += 1
}
rewritten
}
///|
fn dfe_try_rewrite_expr_type_idxs(
expr : @lib.Expr,
remap : Map[UInt32, UInt32],
index_cache~ : DfeTypeRewriteCache? = None,
) -> @lib.Expr? {
let @lib.Expr(instrs) = expr
match
dfe_try_rewrite_instruction_array_type_idxs(instrs, remap, index_cache~) {
Some(rewritten) => Some(@lib.Expr::new(rewritten))
None => None
}
}
///|
fn dfe_try_rewrite_locals_type_idxs(
locals : @lib.Locals,
remap : Map[UInt32, UInt32],
index_cache~ : DfeTypeRewriteCache? = None,
) -> @lib.Locals? {
let runs = locals.runs()
let rewritten : Array[@lib.LocalRun] = Array::new(capacity=runs.length())
let mut changed = false
let mut idx = 0
while idx < runs.length() {
let run = runs[idx]
match dfe_try_rewrite_val_type(run.val_type(), remap, index_cache~) {
Some(next) => {
if !changed {
let mut prefix = 0
while prefix < idx {
rewritten.push(runs[prefix])
prefix += 1
}
changed = true
}
rewritten.push(@lib.LocalRun::new(run.count(), next))
}
None => if changed { rewritten.push(run) }
}
idx += 1
}
if changed {
Some(@lib.Locals::new(rewritten))
} else {
None
}
}
///|
fn dfe_try_rewrite_func_type_idxs(
func : @lib.Func,
remap : Map[UInt32, UInt32],
index_cache~ : DfeTypeRewriteCache? = None,
) -> @lib.Func? {
let @lib.Func(locals, body) = func
let rewritten_locals = dfe_try_rewrite_locals_type_idxs(
locals,
remap,
index_cache~,
)
let rewritten_body = dfe_try_rewrite_expr_type_idxs(body, remap, index_cache~)
if rewritten_locals is None && rewritten_body is None {
None
} else {
let next_locals = match rewritten_locals {
Some(next) => next
None => locals
}
let next_body = match rewritten_body {
Some(next) => next
None => body
}
Some(@lib.Func::new(next_locals, next_body))
}
}
///|
fn dfe_rewrite_instruction_type_idxs(
instr : @lib.Instruction,
remap : Map[UInt32, UInt32],
) -> @lib.Instruction {
match instr {
@lib.StructWait(type_idx, field_idx) =>
@lib.Instruction::struct_wait(
dfe_rewrite_type_idx(type_idx, remap),
field_idx,
)
@lib.ArrayLoad(op, type_idx, ma) =>
@lib.Instruction::array_load(
op,
dfe_rewrite_type_idx(type_idx, remap),
memarg=ma,
)
@lib.ArrayStore(op, type_idx, ma) =>
@lib.Instruction::array_store(
op,
dfe_rewrite_type_idx(type_idx, remap),
memarg=ma,
)
@lib.ContNew(type_idx) =>
@lib.Instruction::cont_new(dfe_rewrite_type_idx(type_idx, remap))
@lib.ContBind(source, target) =>
@lib.Instruction::cont_bind(
dfe_rewrite_type_idx(source, remap),
dfe_rewrite_type_idx(target, remap),
)
@lib.Resume(type_idx, handlers) =>
@lib.Instruction::resume_(dfe_rewrite_type_idx(type_idx, remap), handlers)
@lib.ResumeThrow(type_idx, tag, handlers) =>
@lib.Instruction::resume_throw(
dfe_rewrite_type_idx(type_idx, remap),
tag,
handlers,
)
@lib.ResumeThrowRef(type_idx, handlers) =>
@lib.Instruction::resume_throw_ref(
dfe_rewrite_type_idx(type_idx, remap),
handlers,
)
@lib.StackSwitch(type_idx, tag) =>
@lib.Instruction::stack_switch(dfe_rewrite_type_idx(type_idx, remap), tag)
@lib.BrOnCastDescEq(label, source, target) =>
@lib.Instruction::br_on_cast_desc_eq(
label,
dfe_rewrite_ref_type(source, remap),
dfe_rewrite_ref_type(target, remap),
)
@lib.BrOnCastDescEqFail(label, source, target) =>
@lib.Instruction::br_on_cast_desc_eq_fail(
label,
dfe_rewrite_ref_type(source, remap),
dfe_rewrite_ref_type(target, remap),
)
@lib.Try(_, _, _, _) =>
dfe_scan_rewrite_instruction_type_idxs(instr, remap).rewritten.unwrap_or(
instr,
)
@lib.Block(block_type, expr) =>
@lib.Instruction::block(
dfe_rewrite_block_type(block_type, remap),
dfe_rewrite_expr_type_idxs(expr, remap),
)
@lib.Loop(block_type, expr) =>
@lib.Instruction::loop_(
dfe_rewrite_block_type(block_type, remap),
dfe_rewrite_expr_type_idxs(expr, remap),
)
@lib.If(block_type, then_body, else_body) => {
let rewritten_then : Array[@lib.Instruction] = []
for nested in then_body {
rewritten_then.push(dfe_rewrite_instruction_type_idxs(nested, remap))
}
let rewritten_else = match else_body {
Some(body) => {
let rewritten : Array[@lib.Instruction] = []
for nested in body {
rewritten.push(dfe_rewrite_instruction_type_idxs(nested, remap))
}
Some(rewritten)
}
None => None
}
@lib.Instruction::if_(
dfe_rewrite_block_type(block_type, remap),
rewritten_then,
rewritten_else,
)
}
@lib.CallIndirect(type_idx, table_idx) =>
@lib.Instruction::call_indirect(
dfe_rewrite_type_idx(type_idx, remap),
table_idx,
)
@lib.ReturnCallIndirect(type_idx, table_idx) =>
@lib.Instruction::return_call_indirect(
dfe_rewrite_type_idx(type_idx, remap),
table_idx,
)
@lib.CallRef(type_idx) =>
@lib.Instruction::call_ref(dfe_rewrite_type_idx(type_idx, remap))
@lib.ReturnCallRef(type_idx) =>
@lib.Instruction::return_call_ref(dfe_rewrite_type_idx(type_idx, remap))
@lib.Select(Some(types)) =>
@lib.Instruction::select(
types=Some(
types.map(fn(val_type) { dfe_rewrite_val_type(val_type, remap) }),
),
)
@lib.TryTable(block_type, catches, body) =>
@lib.Instruction::try_table(
dfe_rewrite_block_type(block_type, remap),
catches,
dfe_rewrite_expr_type_idxs(body, remap),
)
@lib.RefNull(ref_type) =>
@lib.Instruction::ref_null_type(dfe_rewrite_ref_type(ref_type, remap))
@lib.StructNew(type_idx) =>
@lib.Instruction::struct_new(dfe_rewrite_type_idx(type_idx, remap))
@lib.StructNewDefault(type_idx) =>
@lib.Instruction::struct_new_default(
dfe_rewrite_type_idx(type_idx, remap),
)
@lib.StructNewDesc(type_idx) =>
@lib.Instruction::struct_new_desc(dfe_rewrite_type_idx(type_idx, remap))
@lib.StructNewDefaultDesc(type_idx) =>
@lib.Instruction::struct_new_default_desc(
dfe_rewrite_type_idx(type_idx, remap),
)
@lib.StructGet(type_idx, field_idx) =>
@lib.Instruction::struct_get(
dfe_rewrite_type_idx(type_idx, remap),
field_idx,
)
@lib.StructGetS(type_idx, field_idx) =>
@lib.Instruction::struct_get_s(
dfe_rewrite_type_idx(type_idx, remap),
field_idx,
)
@lib.StructGetU(type_idx, field_idx) =>
@lib.Instruction::struct_get_u(
dfe_rewrite_type_idx(type_idx, remap),
field_idx,
)
@lib.StructAtomicGet(order, type_idx, field_idx) =>
@lib.Instruction::struct_atomic_get(
order,
dfe_rewrite_type_idx(type_idx, remap),
field_idx,
)
@lib.StructAtomicGetS(order, type_idx, field_idx) =>
@lib.Instruction::struct_atomic_get_s(
order,
dfe_rewrite_type_idx(type_idx, remap),
field_idx,
)
@lib.StructAtomicGetU(order, type_idx, field_idx) =>
@lib.Instruction::struct_atomic_get_u(
order,
dfe_rewrite_type_idx(type_idx, remap),
field_idx,
)
@lib.StructAtomicSet(order, type_idx, field_idx) =>
@lib.Instruction::struct_atomic_set(
order,
dfe_rewrite_type_idx(type_idx, remap),
field_idx,
)
@lib.ArrayAtomicSet(order, type_idx) =>
@lib.Instruction::array_atomic_set(
order,
dfe_rewrite_type_idx(type_idx, remap),
)
@lib.ArrayAtomicGet(order, type_idx) =>
@lib.Instruction::array_atomic_get(
order,
dfe_rewrite_type_idx(type_idx, remap),
)
@lib.ArrayAtomicGetS(order, type_idx) =>
@lib.Instruction::array_atomic_get_s(
order,
dfe_rewrite_type_idx(type_idx, remap),
)
@lib.ArrayAtomicGetU(order, type_idx) =>
@lib.Instruction::array_atomic_get_u(
order,
dfe_rewrite_type_idx(type_idx, remap),
)
@lib.ArrayAtomicRmw(order, op, type_idx) =>
@lib.Instruction::array_atomic_rmw(
op,
dfe_rewrite_type_idx(type_idx, remap),
order~,
)
@lib.ArrayAtomicCmpxchg(order, type_idx) =>
@lib.Instruction::array_atomic_cmpxchg(
dfe_rewrite_type_idx(type_idx, remap),
order~,
)
@lib.StructAtomicRmw(order, op, type_idx, field_idx) =>
@lib.Instruction::struct_atomic_rmw(
op,
dfe_rewrite_type_idx(type_idx, remap),
field_idx,
order~,
)
@lib.StructAtomicCmpxchg(order, type_idx, field_idx) =>
@lib.Instruction::struct_atomic_cmpxchg(
dfe_rewrite_type_idx(type_idx, remap),
field_idx,
order~,
)
@lib.StructSet(type_idx, field_idx) =>
@lib.Instruction::struct_set(
dfe_rewrite_type_idx(type_idx, remap),
field_idx,
)
@lib.ArrayNew(type_idx) =>
@lib.Instruction::array_new(dfe_rewrite_type_idx(type_idx, remap))
@lib.ArrayNewDefault(type_idx) =>
@lib.Instruction::array_new_default(dfe_rewrite_type_idx(type_idx, remap))
@lib.ArrayNewFixed(type_idx, len) =>
@lib.Instruction::array_new_fixed(
dfe_rewrite_type_idx(type_idx, remap),
len,
)
@lib.ArrayNewData(type_idx, data_idx) =>
@lib.Instruction::array_new_data(
dfe_rewrite_type_idx(type_idx, remap),
data_idx,
)
@lib.ArrayNewElem(type_idx, elem_idx) =>
@lib.Instruction::array_new_elem(
dfe_rewrite_type_idx(type_idx, remap),
elem_idx,
)
@lib.ArrayGet(type_idx) =>
@lib.Instruction::array_get(dfe_rewrite_type_idx(type_idx, remap))
@lib.ArrayGetS(type_idx) =>
@lib.Instruction::array_get_s(dfe_rewrite_type_idx(type_idx, remap))
@lib.ArrayGetU(type_idx) =>
@lib.Instruction::array_get_u(dfe_rewrite_type_idx(type_idx, remap))
@lib.ArraySet(type_idx) =>
@lib.Instruction::array_set(dfe_rewrite_type_idx(type_idx, remap))
@lib.ArrayFill(type_idx) =>
@lib.Instruction::array_fill(dfe_rewrite_type_idx(type_idx, remap))
@lib.ArrayCopy(src_type, dst_type) =>
@lib.Instruction::array_copy(
dfe_rewrite_type_idx(src_type, remap),
dfe_rewrite_type_idx(dst_type, remap),
)
@lib.ArrayInitData(type_idx, data_idx) =>
@lib.Instruction::array_init_data(
dfe_rewrite_type_idx(type_idx, remap),
data_idx,
)
@lib.ArrayInitElem(type_idx, elem_idx) =>
@lib.Instruction::array_init_elem(
dfe_rewrite_type_idx(type_idx, remap),
elem_idx,
)
@lib.RefGetDesc(type_idx) =>
@lib.Instruction::ref_get_desc(dfe_rewrite_type_idx(type_idx, remap))
@lib.RefTest(nullable, exact, heap_type) =>
@lib.Instruction::ref_test_type(
@lib.RefType::new(
nullable,
dfe_rewrite_heap_type(heap_type, remap),
exact~,
),
)
@lib.RefCast(nullable, exact, heap_type) =>
@lib.Instruction::ref_cast_type(
@lib.RefType::new(
nullable,
dfe_rewrite_heap_type(heap_type, remap),
exact~,
),
)
@lib.RefTestDesc(nullable, heap_type) =>
@lib.Instruction::ref_test_desc(
nullable,
dfe_rewrite_heap_type(heap_type, remap),
)
@lib.RefCastDescEq(nullable, exact, heap_type) =>
@lib.Instruction::ref_cast_desc_eq_type(
@lib.RefType::new(
nullable,
dfe_rewrite_heap_type(heap_type, remap),
exact~,
),
)
@lib.BrOnCast(label_idx, cast_op, source, target) =>
@lib.Instruction::br_on_cast(
label_idx,
cast_op.source_nullable(),
dfe_rewrite_heap_type(source, remap),
cast_op.target_nullable(),
dfe_rewrite_heap_type(target, remap),
source_exact=cast_op.source_exact(),
target_exact=cast_op.target_exact(),
)
@lib.BrOnCastFail(label_idx, cast_op, source, target) =>
@lib.Instruction::br_on_cast_fail(
label_idx,
cast_op.source_nullable(),
dfe_rewrite_heap_type(source, remap),
cast_op.target_nullable(),
dfe_rewrite_heap_type(target, remap),
source_exact=cast_op.source_exact(),
target_exact=cast_op.target_exact(),
)
_ => instr
}
}
///|
fn dfe_scan_rewrite_instruction_type_idxs(
instr : @lib.Instruction,
remap : Map[UInt32, UInt32],
) -> DfeTypeRewriteScan[@lib.Instruction] {
match instr {
@lib.StructWait(_, _)
| @lib.ArrayLoad(_, _, _)
| @lib.ArrayStore(_, _, _) =>
{
rewritten: dfe_try_rewrite_instruction_type_idxs(instr, remap),
mentions_type_idxs: true,
}
@lib.ContNew(_)
| @lib.ContBind(_, _)
| @lib.Resume(_, _)
| @lib.ResumeThrow(_, _, _)
| @lib.ResumeThrowRef(_, _)
| @lib.StackSwitch(_, _)
| @lib.BrOnCastDescEq(_, _, _)
| @lib.BrOnCastDescEqFail(_, _, _) =>
{
rewritten: dfe_try_rewrite_instruction_type_idxs(instr, remap),
mentions_type_idxs: true,
}
@lib.Try(block_type, body, catches, delegate) => {
let block_scan = dfe_scan_rewrite_block_type(block_type, remap)
let body_scan = dfe_scan_rewrite_expr_type_idxs(body, remap)
let mut changed = block_scan.rewritten is Some(_) ||
body_scan.rewritten is Some(_)
let mut mentions = block_scan.mentions_type_idxs ||
body_scan.mentions_type_idxs
let next_catches = catches.map(fn(catch_) {
let expr = match catch_ {
@lib.LegacyCatch(_, expr) | @lib.LegacyCatchAll(expr) => expr
}
let scan = dfe_scan_rewrite_expr_type_idxs(expr, remap)
mentions = mentions || scan.mentions_type_idxs
match scan.rewritten {
Some(next) => {
changed = true
match catch_ {
@lib.LegacyCatch(tag, _) => @lib.LegacyCatch::tagged(tag, next)
@lib.LegacyCatchAll(_) => @lib.LegacyCatch::all(next)
}
}
None => catch_
}
})
{
mentions_type_idxs: mentions,
rewritten: if changed {
Some(
@lib.Instruction::try_(
block_scan.rewritten.unwrap_or(block_type),
body_scan.rewritten.unwrap_or(body),
next_catches,
delegate~,
),
)
} else {
None
},
}
}
@lib.Block(block_type, expr) => {
let block_scan = dfe_scan_rewrite_block_type(block_type, remap)
let expr_scan = dfe_scan_rewrite_expr_type_idxs(expr, remap)
{
rewritten: if block_scan.rewritten is None &&
expr_scan.rewritten is None {
None
} else {
Some(
@lib.Instruction::block(
match block_scan.rewritten {
Some(next) => next
None => block_type
},
match expr_scan.rewritten {
Some(next) => next
None => expr
},
),
)
},
mentions_type_idxs: block_scan.mentions_type_idxs ||
expr_scan.mentions_type_idxs,
}
}
@lib.Loop(block_type, expr) => {
let block_scan = dfe_scan_rewrite_block_type(block_type, remap)
let expr_scan = dfe_scan_rewrite_expr_type_idxs(expr, remap)
{
rewritten: if block_scan.rewritten is None &&
expr_scan.rewritten is None {
None
} else {
Some(
@lib.Instruction::loop_(
match block_scan.rewritten {
Some(next) => next
None => block_type
},
match expr_scan.rewritten {
Some(next) => next
None => expr
},
),
)
},
mentions_type_idxs: block_scan.mentions_type_idxs ||
expr_scan.mentions_type_idxs,
}
}
@lib.If(block_type, then_body, else_body) => {
let block_scan = dfe_scan_rewrite_block_type(block_type, remap)
let then_scan = dfe_scan_rewrite_instruction_array_type_idxs(
then_body, remap,
)
let else_scan = match else_body {
Some(body) => dfe_scan_rewrite_instruction_array_type_idxs(body, remap)
None => { rewritten: None, mentions_type_idxs: false, }
}
{
rewritten: if block_scan.rewritten is None &&
then_scan.rewritten is None &&
else_scan.rewritten is None {
None
} else {
Some(
@lib.Instruction::if_(
match block_scan.rewritten {
Some(next) => next
None => block_type
},
match then_scan.rewritten {
Some(next) => next
None => then_body
},
match else_body {
Some(body) =>
Some(
match else_scan.rewritten {
Some(next) => next
None => body
},
)
None => None
},
),
)
},
mentions_type_idxs: block_scan.mentions_type_idxs ||
then_scan.mentions_type_idxs ||
else_scan.mentions_type_idxs,
}
}
@lib.CallIndirect(type_idx, table_idx) =>
{
rewritten: match dfe_try_rewrite_type_idx(type_idx, remap) {
Some(next) => Some(@lib.Instruction::call_indirect(next, table_idx))
None => None
},
mentions_type_idxs: true,
}
@lib.ReturnCallIndirect(type_idx, table_idx) =>
{
rewritten: match dfe_try_rewrite_type_idx(type_idx, remap) {
Some(next) =>
Some(@lib.Instruction::return_call_indirect(next, table_idx))
None => None
},
mentions_type_idxs: true,
}
@lib.CallRef(type_idx) =>
{
rewritten: match dfe_try_rewrite_type_idx(type_idx, remap) {
Some(next) => Some(@lib.Instruction::call_ref(next))
None => None
},
mentions_type_idxs: true,
}
@lib.ReturnCallRef(type_idx) =>
{
rewritten: match dfe_try_rewrite_type_idx(type_idx, remap) {
Some(next) => Some(@lib.Instruction::return_call_ref(next))
None => None
},
mentions_type_idxs: true,
}
@lib.Select(Some(types)) => {
let scan = dfe_scan_rewrite_val_types(types, remap)
{
rewritten: match scan.rewritten {
Some(next) => Some(@lib.Instruction::select(types=Some(next)))
None => None
},
mentions_type_idxs: scan.mentions_type_idxs,
}
}
@lib.Select(None) => { rewritten: None, mentions_type_idxs: false, }
@lib.TryTable(block_type, catches, body) => {
let block_scan = dfe_scan_rewrite_block_type(block_type, remap)
let body_scan = dfe_scan_rewrite_expr_type_idxs(body, remap)
{
rewritten: if block_scan.rewritten is None &&
body_scan.rewritten is None {
None
} else {
Some(
@lib.Instruction::try_table(
match block_scan.rewritten {
Some(next) => next
None => block_type
},
catches,
match body_scan.rewritten {
Some(next) => next
None => body
},
),
)
},
mentions_type_idxs: block_scan.mentions_type_idxs ||
body_scan.mentions_type_idxs,
}
}
@lib.RefNull(ref_type) =>
{
rewritten: match dfe_try_rewrite_ref_type(ref_type, remap) {
Some(next) => Some(@lib.Instruction::ref_null_type(next))
None => None
},
mentions_type_idxs: dfe_ref_type_mentions_type_idxs(ref_type),
}
@lib.StructNew(type_idx) =>
{
rewritten: match dfe_try_rewrite_type_idx(type_idx, remap) {
Some(next) => Some(@lib.Instruction::struct_new(next))
None => None
},
mentions_type_idxs: true,
}
@lib.StructNewDefault(type_idx) =>
{
rewritten: match dfe_try_rewrite_type_idx(type_idx, remap) {
Some(next) => Some(@lib.Instruction::struct_new_default(next))
None => None
},
mentions_type_idxs: true,
}
@lib.StructNewDesc(type_idx) =>
{
rewritten: match dfe_try_rewrite_type_idx(type_idx, remap) {
Some(next) => Some(@lib.Instruction::struct_new_desc(next))
None => None
},
mentions_type_idxs: true,
}
@lib.StructNewDefaultDesc(type_idx) =>
{
rewritten: match dfe_try_rewrite_type_idx(type_idx, remap) {
Some(next) => Some(@lib.Instruction::struct_new_default_desc(next))
None => None
},
mentions_type_idxs: true,
}
@lib.StructGet(type_idx, field_idx) =>
{
rewritten: match dfe_try_rewrite_type_idx(type_idx, remap) {
Some(next) => Some(@lib.Instruction::struct_get(next, field_idx))
None => None
},
mentions_type_idxs: true,
}
@lib.StructGetS(type_idx, field_idx) =>
{
rewritten: match dfe_try_rewrite_type_idx(type_idx, remap) {
Some(next) => Some(@lib.Instruction::struct_get_s(next, field_idx))
None => None
},
mentions_type_idxs: true,
}
@lib.StructGetU(type_idx, field_idx) =>
{
rewritten: match dfe_try_rewrite_type_idx(type_idx, remap) {
Some(next) => Some(@lib.Instruction::struct_get_u(next, field_idx))
None => None
},
mentions_type_idxs: true,
}
@lib.StructAtomicGet(order, type_idx, field_idx) =>
{
rewritten: match dfe_try_rewrite_type_idx(type_idx, remap) {
Some(next) =>
Some(@lib.Instruction::struct_atomic_get(order, next, field_idx))
None => None
},
mentions_type_idxs: true,
}
@lib.StructAtomicGetS(order, type_idx, field_idx) =>
{
rewritten: match dfe_try_rewrite_type_idx(type_idx, remap) {
Some(next) =>
Some(@lib.Instruction::struct_atomic_get_s(order, next, field_idx))
None => None
},
mentions_type_idxs: true,
}
@lib.StructAtomicGetU(order, type_idx, field_idx) =>
{
rewritten: match dfe_try_rewrite_type_idx(type_idx, remap) {
Some(next) =>
Some(@lib.Instruction::struct_atomic_get_u(order, next, field_idx))
None => None
},
mentions_type_idxs: true,
}
@lib.StructAtomicSet(order, type_idx, field_idx) =>
{
rewritten: match dfe_try_rewrite_type_idx(type_idx, remap) {
Some(next) =>
Some(@lib.Instruction::struct_atomic_set(order, next, field_idx))
None => None
},
mentions_type_idxs: true,
}
@lib.ArrayAtomicSet(order, type_idx) =>
{
rewritten: match dfe_try_rewrite_type_idx(type_idx, remap) {
Some(next) => Some(@lib.Instruction::array_atomic_set(order, next))
None => None
},
mentions_type_idxs: true,
}
@lib.ArrayAtomicGet(order, type_idx) =>
{
rewritten: match dfe_try_rewrite_type_idx(type_idx, remap) {
Some(next) => Some(@lib.Instruction::array_atomic_get(order, next))
None => None
},
mentions_type_idxs: true,
}
@lib.ArrayAtomicGetS(order, type_idx) =>
{
rewritten: match dfe_try_rewrite_type_idx(type_idx, remap) {
Some(next) => Some(@lib.Instruction::array_atomic_get_s(order, next))
None => None
},
mentions_type_idxs: true,
}
@lib.ArrayAtomicGetU(order, type_idx) =>
{
rewritten: match dfe_try_rewrite_type_idx(type_idx, remap) {
Some(next) => Some(@lib.Instruction::array_atomic_get_u(order, next))
None => None
},
mentions_type_idxs: true,
}
@lib.ArrayAtomicRmw(order, op, type_idx) =>
{
rewritten: match dfe_try_rewrite_type_idx(type_idx, remap) {
Some(next) =>
Some(@lib.Instruction::array_atomic_rmw(op, next, order~))
None => None
},
mentions_type_idxs: true,
}
@lib.ArrayAtomicCmpxchg(order, type_idx) =>
{
rewritten: match dfe_try_rewrite_type_idx(type_idx, remap) {
Some(next) =>
Some(@lib.Instruction::array_atomic_cmpxchg(next, order~))
None => None
},
mentions_type_idxs: true,
}
@lib.StructAtomicRmw(order, op, type_idx, field_idx) =>
{
rewritten: match dfe_try_rewrite_type_idx(type_idx, remap) {
Some(next) =>
Some(
@lib.Instruction::struct_atomic_rmw(op, next, field_idx, order~),
)
None => None
},
mentions_type_idxs: true,
}
@lib.StructAtomicCmpxchg(order, type_idx, field_idx) =>
{
rewritten: match dfe_try_rewrite_type_idx(type_idx, remap) {
Some(next) =>
Some(
@lib.Instruction::struct_atomic_cmpxchg(next, field_idx, order~),
)
None => None
},
mentions_type_idxs: true,
}
@lib.StructSet(type_idx, field_idx) =>
{
rewritten: match dfe_try_rewrite_type_idx(type_idx, remap) {
Some(next) => Some(@lib.Instruction::struct_set(next, field_idx))
None => None
},
mentions_type_idxs: true,
}
@lib.ArrayNew(type_idx) =>
{
rewritten: match dfe_try_rewrite_type_idx(type_idx, remap) {
Some(next) => Some(@lib.Instruction::array_new(next))
None => None
},
mentions_type_idxs: true,
}
@lib.ArrayNewDefault(type_idx) =>
{
rewritten: match dfe_try_rewrite_type_idx(type_idx, remap) {
Some(next) => Some(@lib.Instruction::array_new_default(next))
None => None
},
mentions_type_idxs: true,
}
@lib.ArrayNewFixed(type_idx, len) =>
{
rewritten: match dfe_try_rewrite_type_idx(type_idx, remap) {
Some(next) => Some(@lib.Instruction::array_new_fixed(next, len))
None => None
},
mentions_type_idxs: true,
}
@lib.ArrayNewData(type_idx, data_idx) =>
{
rewritten: match dfe_try_rewrite_type_idx(type_idx, remap) {
Some(next) => Some(@lib.Instruction::array_new_data(next, data_idx))
None => None
},
mentions_type_idxs: true,
}
@lib.ArrayNewElem(type_idx, elem_idx) =>
{
rewritten: match dfe_try_rewrite_type_idx(type_idx, remap) {
Some(next) => Some(@lib.Instruction::array_new_elem(next, elem_idx))
None => None
},
mentions_type_idxs: true,
}
@lib.ArrayGet(type_idx) =>
{
rewritten: match dfe_try_rewrite_type_idx(type_idx, remap) {
Some(next) => Some(@lib.Instruction::array_get(next))
None => None
},
mentions_type_idxs: true,
}
@lib.ArrayGetS(type_idx) =>
{
rewritten: match dfe_try_rewrite_type_idx(type_idx, remap) {
Some(next) => Some(@lib.Instruction::array_get_s(next))
None => None
},
mentions_type_idxs: true,
}
@lib.ArrayGetU(type_idx) =>
{
rewritten: match dfe_try_rewrite_type_idx(type_idx, remap) {
Some(next) => Some(@lib.Instruction::array_get_u(next))
None => None
},
mentions_type_idxs: true,
}
@lib.ArraySet(type_idx) =>
{
rewritten: match dfe_try_rewrite_type_idx(type_idx, remap) {
Some(next) => Some(@lib.Instruction::array_set(next))
None => None
},
mentions_type_idxs: true,
}
@lib.ArrayFill(type_idx) =>
{
rewritten: match dfe_try_rewrite_type_idx(type_idx, remap) {
Some(next) => Some(@lib.Instruction::array_fill(next))
None => None
},
mentions_type_idxs: true,
}
@lib.ArrayCopy(src_type, dst_type) =>
{
rewritten: match
(
dfe_try_rewrite_type_idx(src_type, remap),
dfe_try_rewrite_type_idx(dst_type, remap),
) {
(Some(next_src), Some(next_dst)) =>
Some(@lib.Instruction::array_copy(next_src, next_dst))
(Some(next_src), None) =>
Some(@lib.Instruction::array_copy(next_src, dst_type))
(None, Some(next_dst)) =>
Some(@lib.Instruction::array_copy(src_type, next_dst))
(None, None) => None
},
mentions_type_idxs: true,
}
@lib.ArrayInitData(type_idx, data_idx) =>
{
rewritten: match dfe_try_rewrite_type_idx(type_idx, remap) {
Some(next) => Some(@lib.Instruction::array_init_data(next, data_idx))
None => None
},
mentions_type_idxs: true,
}
@lib.ArrayInitElem(type_idx, elem_idx) =>
{
rewritten: match dfe_try_rewrite_type_idx(type_idx, remap) {
Some(next) => Some(@lib.Instruction::array_init_elem(next, elem_idx))
None => None
},
mentions_type_idxs: true,
}
@lib.RefGetDesc(type_idx) =>
{
rewritten: match dfe_try_rewrite_type_idx(type_idx, remap) {
Some(next) => Some(@lib.Instruction::ref_get_desc(next))
None => None
},
mentions_type_idxs: true,
}
@lib.RefTest(nullable, exact, heap_type) =>
{
rewritten: match dfe_try_rewrite_heap_type(heap_type, remap) {
Some(next) =>
Some(
@lib.Instruction::ref_test_type(
@lib.RefType::new(nullable, next, exact~),
),
)
None => None
},
mentions_type_idxs: dfe_heap_type_mentions_type_idxs(heap_type),
}
@lib.RefCast(nullable, exact, heap_type) =>
{
rewritten: match dfe_try_rewrite_heap_type(heap_type, remap) {
Some(next) =>
Some(
@lib.Instruction::ref_cast_type(
@lib.RefType::new(nullable, next, exact~),
),
)
None => None
},
mentions_type_idxs: dfe_heap_type_mentions_type_idxs(heap_type),
}
@lib.RefTestDesc(nullable, heap_type) =>
{
rewritten: match dfe_try_rewrite_heap_type(heap_type, remap) {
Some(next) => Some(@lib.Instruction::ref_test_desc(nullable, next))
None => None
},
mentions_type_idxs: dfe_heap_type_mentions_type_idxs(heap_type),
}
@lib.RefCastDescEq(nullable, exact, heap_type) =>
{
rewritten: match dfe_try_rewrite_heap_type(heap_type, remap) {
Some(next) =>
Some(
@lib.Instruction::ref_cast_desc_eq_type(
@lib.RefType::new(nullable, next, exact~),
),
)
None => None
},
mentions_type_idxs: dfe_heap_type_mentions_type_idxs(heap_type),
}
@lib.BrOnCast(label_idx, cast_op, source, target) =>
{
rewritten: match
(
dfe_try_rewrite_heap_type(source, remap),
dfe_try_rewrite_heap_type(target, remap),
) {
(Some(next_source), Some(next_target)) =>
Some(
@lib.Instruction::br_on_cast(
label_idx,
cast_op.source_nullable(),
next_source,
cast_op.target_nullable(),
next_target,
source_exact=cast_op.source_exact(),
target_exact=cast_op.target_exact(),
),
)
(Some(next_source), None) =>
Some(
@lib.Instruction::br_on_cast(
label_idx,
cast_op.source_nullable(),
next_source,
cast_op.target_nullable(),
target,
source_exact=cast_op.source_exact(),
target_exact=cast_op.target_exact(),
),
)
(None, Some(next_target)) =>
Some(
@lib.Instruction::br_on_cast(
label_idx,
cast_op.source_nullable(),
source,
cast_op.target_nullable(),
next_target,
source_exact=cast_op.source_exact(),
target_exact=cast_op.target_exact(),
),
)
(None, None) => None
},
mentions_type_idxs: dfe_heap_type_mentions_type_idxs(source) ||
dfe_heap_type_mentions_type_idxs(target),
}
@lib.BrOnCastFail(label_idx, cast_op, source, target) =>
{
rewritten: match
(
dfe_try_rewrite_heap_type(source, remap),
dfe_try_rewrite_heap_type(target, remap),
) {
(Some(next_source), Some(next_target)) =>
Some(
@lib.Instruction::br_on_cast_fail(
label_idx,
cast_op.source_nullable(),
next_source,
cast_op.target_nullable(),
next_target,
source_exact=cast_op.source_exact(),
target_exact=cast_op.target_exact(),
),
)
(Some(next_source), None) =>
Some(
@lib.Instruction::br_on_cast_fail(
label_idx,
cast_op.source_nullable(),
next_source,
cast_op.target_nullable(),
target,
source_exact=cast_op.source_exact(),
target_exact=cast_op.target_exact(),
),
)
(None, Some(next_target)) =>
Some(
@lib.Instruction::br_on_cast_fail(
label_idx,
cast_op.source_nullable(),
source,
cast_op.target_nullable(),
next_target,
source_exact=cast_op.source_exact(),
target_exact=cast_op.target_exact(),
),
)
(None, None) => None
},
mentions_type_idxs: dfe_heap_type_mentions_type_idxs(source) ||
dfe_heap_type_mentions_type_idxs(target),
}
_ => { rewritten: None, mentions_type_idxs: false, }
}
}
///|
fn dfe_try_rewrite_instruction_type_idxs(
instr : @lib.Instruction,
remap : Map[UInt32, UInt32],
index_cache~ : DfeTypeRewriteCache? = None,
) -> @lib.Instruction? {
match instr {
@lib.StructWait(type_idx, field_idx) =>
match dfe_try_rewrite_type_idx(type_idx, remap, index_cache~) {
Some(next) => Some(@lib.Instruction::struct_wait(next, field_idx))
None => None
}
@lib.ArrayLoad(op, type_idx, ma) =>
match dfe_try_rewrite_type_idx(type_idx, remap, index_cache~) {
Some(next) => Some(@lib.Instruction::array_load(op, next, memarg=ma))
None => None
}
@lib.ArrayStore(op, type_idx, ma) =>
match dfe_try_rewrite_type_idx(type_idx, remap, index_cache~) {
Some(next) => Some(@lib.Instruction::array_store(op, next, memarg=ma))
None => None
}
@lib.ContNew(type_idx) =>
match dfe_try_rewrite_type_idx(type_idx, remap, index_cache~) {
Some(next) => Some(@lib.Instruction::cont_new(next))
None => None
}
@lib.Resume(type_idx, handlers) =>
match dfe_try_rewrite_type_idx(type_idx, remap, index_cache~) {
Some(next) => Some(@lib.Instruction::resume_(next, handlers))
None => None
}
@lib.ResumeThrow(type_idx, tag, handlers) =>
match dfe_try_rewrite_type_idx(type_idx, remap, index_cache~) {
Some(next) => Some(@lib.Instruction::resume_throw(next, tag, handlers))
None => None
}
@lib.ResumeThrowRef(type_idx, handlers) =>
match dfe_try_rewrite_type_idx(type_idx, remap, index_cache~) {
Some(next) => Some(@lib.Instruction::resume_throw_ref(next, handlers))
None => None
}
@lib.StackSwitch(type_idx, tag) =>
match dfe_try_rewrite_type_idx(type_idx, remap, index_cache~) {
Some(next) => Some(@lib.Instruction::stack_switch(next, tag))
None => None
}
@lib.ContBind(source, target) => {
let next_source = dfe_try_rewrite_type_idx(source, remap, index_cache~)
let next_target = dfe_try_rewrite_type_idx(target, remap, index_cache~)
if next_source is None && next_target is None {
None
} else {
Some(
@lib.Instruction::cont_bind(
next_source.unwrap_or(source),
next_target.unwrap_or(target),
),
)
}
}
@lib.BrOnCastDescEq(label, source, target) => {
let next_source = dfe_try_rewrite_ref_type(source, remap, index_cache~)
let next_target = dfe_try_rewrite_ref_type(target, remap, index_cache~)
if next_source is None && next_target is None {
None
} else {
Some(
@lib.Instruction::br_on_cast_desc_eq(
label,
next_source.unwrap_or(source),
next_target.unwrap_or(target),
),
)
}
}
@lib.BrOnCastDescEqFail(label, source, target) => {
let next_source = dfe_try_rewrite_ref_type(source, remap, index_cache~)
let next_target = dfe_try_rewrite_ref_type(target, remap, index_cache~)
if next_source is None && next_target is None {
None
} else {
Some(
@lib.Instruction::br_on_cast_desc_eq_fail(
label,
next_source.unwrap_or(source),
next_target.unwrap_or(target),
),
)
}
}
@lib.Try(_, _, _, _) =>
dfe_scan_rewrite_instruction_type_idxs(instr, remap).rewritten
@lib.Block(block_type, expr) => {
let rewritten_block_type = dfe_try_rewrite_block_type(
block_type,
remap,
index_cache~,
)
let rewritten_expr = dfe_try_rewrite_expr_type_idxs(
expr,
remap,
index_cache~,
)
if rewritten_block_type is None && rewritten_expr is None {
None
} else {
let next_block_type = match rewritten_block_type {
Some(next) => next
None => block_type
}
let next_expr = match rewritten_expr {
Some(next) => next
None => expr
}
Some(@lib.Instruction::block(next_block_type, next_expr))
}
}
@lib.Loop(block_type, expr) => {
let rewritten_block_type = dfe_try_rewrite_block_type(
block_type,
remap,
index_cache~,
)
let rewritten_expr = dfe_try_rewrite_expr_type_idxs(
expr,
remap,
index_cache~,
)
if rewritten_block_type is None && rewritten_expr is None {
None
} else {
let next_block_type = match rewritten_block_type {
Some(next) => next
None => block_type
}
let next_expr = match rewritten_expr {
Some(next) => next
None => expr
}
Some(@lib.Instruction::loop_(next_block_type, next_expr))
}
}
@lib.If(block_type, then_body, else_body) => {
let rewritten_block_type = dfe_try_rewrite_block_type(
block_type,
remap,
index_cache~,
)
let rewritten_then = dfe_try_rewrite_instruction_array_type_idxs(
then_body,
remap,
index_cache~,
)
let rewritten_else = match else_body {
Some(body) =>
match
dfe_try_rewrite_instruction_array_type_idxs(
body,
remap,
index_cache~,
) {
Some(next) => Some(Some(next))
None => None
}
None => None
}
if rewritten_block_type is None &&
rewritten_then is None &&
rewritten_else is None {
None
} else {
let next_block_type = match rewritten_block_type {
Some(next) => next
None => block_type
}
let next_then = match rewritten_then {
Some(next) => next
None => then_body
}
let next_else = match rewritten_else {
Some(next) => next
None => else_body
}
Some(@lib.Instruction::if_(next_block_type, next_then, next_else))
}
}
@lib.CallIndirect(type_idx, table_idx) =>
match dfe_try_rewrite_type_idx(type_idx, remap, index_cache~) {
Some(next) => Some(@lib.Instruction::call_indirect(next, table_idx))
None => None
}
@lib.ReturnCallIndirect(type_idx, table_idx) =>
match dfe_try_rewrite_type_idx(type_idx, remap, index_cache~) {
Some(next) =>
Some(@lib.Instruction::return_call_indirect(next, table_idx))
None => None
}
@lib.CallRef(type_idx) =>
match dfe_try_rewrite_type_idx(type_idx, remap, index_cache~) {
Some(next) => Some(@lib.Instruction::call_ref(next))
None => None
}
@lib.ReturnCallRef(type_idx) =>
match dfe_try_rewrite_type_idx(type_idx, remap, index_cache~) {
Some(next) => Some(@lib.Instruction::return_call_ref(next))
None => None
}
@lib.Select(Some(types)) =>
match dfe_try_rewrite_val_types(types, remap, index_cache~) {
Some(next) => Some(@lib.Instruction::select(types=Some(next)))
None => None
}
@lib.TryTable(block_type, catches, body) => {
let rewritten_block_type = dfe_try_rewrite_block_type(
block_type,
remap,
index_cache~,
)
let rewritten_body = dfe_try_rewrite_expr_type_idxs(
body,
remap,
index_cache~,
)
if rewritten_block_type is None && rewritten_body is None {
None
} else {
let next_block_type = match rewritten_block_type {
Some(next) => next
None => block_type
}
let next_body = match rewritten_body {
Some(next) => next
None => body
}
Some(@lib.Instruction::try_table(next_block_type, catches, next_body))
}
}
@lib.RefNull(ref_type) =>
match dfe_try_rewrite_ref_type(ref_type, remap, index_cache~) {
Some(next) => Some(@lib.Instruction::ref_null_type(next))
None => None
}
@lib.StructNew(type_idx) =>
match dfe_try_rewrite_type_idx(type_idx, remap, index_cache~) {
Some(next) => Some(@lib.Instruction::struct_new(next))
None => None
}
@lib.StructNewDefault(type_idx) =>
match dfe_try_rewrite_type_idx(type_idx, remap, index_cache~) {
Some(next) => Some(@lib.Instruction::struct_new_default(next))
None => None
}
@lib.StructNewDesc(type_idx) =>
match dfe_try_rewrite_type_idx(type_idx, remap, index_cache~) {
Some(next) => Some(@lib.Instruction::struct_new_desc(next))
None => None
}
@lib.StructNewDefaultDesc(type_idx) =>
match dfe_try_rewrite_type_idx(type_idx, remap, index_cache~) {
Some(next) => Some(@lib.Instruction::struct_new_default_desc(next))
None => None
}
@lib.StructGet(type_idx, field_idx) =>
match dfe_try_rewrite_type_idx(type_idx, remap, index_cache~) {
Some(next) => Some(@lib.Instruction::struct_get(next, field_idx))
None => None
}
@lib.StructGetS(type_idx, field_idx) =>
match dfe_try_rewrite_type_idx(type_idx, remap, index_cache~) {
Some(next) => Some(@lib.Instruction::struct_get_s(next, field_idx))
None => None
}
@lib.StructGetU(type_idx, field_idx) =>
match dfe_try_rewrite_type_idx(type_idx, remap, index_cache~) {
Some(next) => Some(@lib.Instruction::struct_get_u(next, field_idx))
None => None
}
@lib.StructAtomicGet(order, type_idx, field_idx) =>
match dfe_try_rewrite_type_idx(type_idx, remap, index_cache~) {
Some(next) =>
Some(@lib.Instruction::struct_atomic_get(order, next, field_idx))
None => None
}
@lib.StructAtomicGetS(order, type_idx, field_idx) =>
match dfe_try_rewrite_type_idx(type_idx, remap, index_cache~) {
Some(next) =>
Some(@lib.Instruction::struct_atomic_get_s(order, next, field_idx))
None => None
}
@lib.StructAtomicGetU(order, type_idx, field_idx) =>
match dfe_try_rewrite_type_idx(type_idx, remap, index_cache~) {
Some(next) =>
Some(@lib.Instruction::struct_atomic_get_u(order, next, field_idx))
None => None
}
@lib.StructAtomicSet(order, type_idx, field_idx) =>
match dfe_try_rewrite_type_idx(type_idx, remap, index_cache~) {
Some(next) =>
Some(@lib.Instruction::struct_atomic_set(order, next, field_idx))
None => None
}
@lib.ArrayAtomicSet(order, type_idx) =>
match dfe_try_rewrite_type_idx(type_idx, remap, index_cache~) {
Some(next) => Some(@lib.Instruction::array_atomic_set(order, next))
None => None
}
@lib.ArrayAtomicGet(order, type_idx) =>
match dfe_try_rewrite_type_idx(type_idx, remap, index_cache~) {
Some(next) => Some(@lib.Instruction::array_atomic_get(order, next))
None => None
}
@lib.ArrayAtomicGetS(order, type_idx) =>
match dfe_try_rewrite_type_idx(type_idx, remap, index_cache~) {
Some(next) => Some(@lib.Instruction::array_atomic_get_s(order, next))
None => None
}
@lib.ArrayAtomicGetU(order, type_idx) =>
match dfe_try_rewrite_type_idx(type_idx, remap, index_cache~) {
Some(next) => Some(@lib.Instruction::array_atomic_get_u(order, next))
None => None
}
@lib.ArrayAtomicRmw(order, op, type_idx) =>
match dfe_try_rewrite_type_idx(type_idx, remap, index_cache~) {
Some(next) => Some(@lib.Instruction::array_atomic_rmw(op, next, order~))
None => None
}
@lib.ArrayAtomicCmpxchg(order, type_idx) =>
match dfe_try_rewrite_type_idx(type_idx, remap, index_cache~) {
Some(next) => Some(@lib.Instruction::array_atomic_cmpxchg(next, order~))
None => None
}
@lib.StructAtomicRmw(order, op, type_idx, field_idx) =>
match dfe_try_rewrite_type_idx(type_idx, remap, index_cache~) {
Some(next) =>
Some(@lib.Instruction::struct_atomic_rmw(op, next, field_idx, order~))
None => None
}
@lib.StructAtomicCmpxchg(order, type_idx, field_idx) =>
match dfe_try_rewrite_type_idx(type_idx, remap, index_cache~) {
Some(next) =>
Some(@lib.Instruction::struct_atomic_cmpxchg(next, field_idx, order~))
None => None
}
@lib.StructSet(type_idx, field_idx) =>
match dfe_try_rewrite_type_idx(type_idx, remap, index_cache~) {
Some(next) => Some(@lib.Instruction::struct_set(next, field_idx))
None => None
}
@lib.ArrayNew(type_idx) =>
match dfe_try_rewrite_type_idx(type_idx, remap, index_cache~) {
Some(next) => Some(@lib.Instruction::array_new(next))
None => None
}
@lib.ArrayNewDefault(type_idx) =>
match dfe_try_rewrite_type_idx(type_idx, remap, index_cache~) {
Some(next) => Some(@lib.Instruction::array_new_default(next))
None => None
}
@lib.ArrayNewFixed(type_idx, len) =>
match dfe_try_rewrite_type_idx(type_idx, remap, index_cache~) {
Some(next) => Some(@lib.Instruction::array_new_fixed(next, len))
None => None
}
@lib.ArrayNewData(type_idx, data_idx) =>
match dfe_try_rewrite_type_idx(type_idx, remap, index_cache~) {
Some(next) => Some(@lib.Instruction::array_new_data(next, data_idx))
None => None
}
@lib.ArrayNewElem(type_idx, elem_idx) =>
match dfe_try_rewrite_type_idx(type_idx, remap, index_cache~) {
Some(next) => Some(@lib.Instruction::array_new_elem(next, elem_idx))
None => None
}
@lib.ArrayGet(type_idx) =>
match dfe_try_rewrite_type_idx(type_idx, remap, index_cache~) {
Some(next) => Some(@lib.Instruction::array_get(next))
None => None
}
@lib.ArrayGetS(type_idx) =>
match dfe_try_rewrite_type_idx(type_idx, remap, index_cache~) {
Some(next) => Some(@lib.Instruction::array_get_s(next))
None => None
}
@lib.ArrayGetU(type_idx) =>
match dfe_try_rewrite_type_idx(type_idx, remap, index_cache~) {
Some(next) => Some(@lib.Instruction::array_get_u(next))
None => None
}
@lib.ArraySet(type_idx) =>
match dfe_try_rewrite_type_idx(type_idx, remap, index_cache~) {
Some(next) => Some(@lib.Instruction::array_set(next))
None => None
}
@lib.ArrayFill(type_idx) =>
match dfe_try_rewrite_type_idx(type_idx, remap, index_cache~) {
Some(next) => Some(@lib.Instruction::array_fill(next))
None => None
}
@lib.ArrayCopy(src_type, dst_type) =>
match
(
dfe_try_rewrite_type_idx(src_type, remap, index_cache~),
dfe_try_rewrite_type_idx(dst_type, remap, index_cache~),
) {
(Some(next_src), Some(next_dst)) =>
Some(@lib.Instruction::array_copy(next_src, next_dst))
(Some(next_src), None) =>
Some(@lib.Instruction::array_copy(next_src, dst_type))
(None, Some(next_dst)) =>
Some(@lib.Instruction::array_copy(src_type, next_dst))
(None, None) => None
}
@lib.ArrayInitData(type_idx, data_idx) =>
match dfe_try_rewrite_type_idx(type_idx, remap, index_cache~) {
Some(next) => Some(@lib.Instruction::array_init_data(next, data_idx))
None => None
}
@lib.ArrayInitElem(type_idx, elem_idx) =>
match dfe_try_rewrite_type_idx(type_idx, remap, index_cache~) {
Some(next) => Some(@lib.Instruction::array_init_elem(next, elem_idx))
None => None
}
@lib.RefGetDesc(type_idx) =>
match dfe_try_rewrite_type_idx(type_idx, remap, index_cache~) {
Some(next) => Some(@lib.Instruction::ref_get_desc(next))
None => None
}
@lib.RefTest(nullable, exact, heap_type) =>
match dfe_try_rewrite_heap_type(heap_type, remap, index_cache~) {
Some(next) =>
Some(
@lib.Instruction::ref_test_type(
@lib.RefType::new(nullable, next, exact~),
),
)
None => None
}
@lib.RefCast(nullable, exact, heap_type) =>
match dfe_try_rewrite_heap_type(heap_type, remap, index_cache~) {
Some(next) =>
Some(
@lib.Instruction::ref_cast_type(
@lib.RefType::new(nullable, next, exact~),
),
)
None => None
}
@lib.RefTestDesc(nullable, heap_type) =>
match dfe_try_rewrite_heap_type(heap_type, remap, index_cache~) {
Some(next) => Some(@lib.Instruction::ref_test_desc(nullable, next))
None => None
}
@lib.RefCastDescEq(nullable, exact, heap_type) =>
match dfe_try_rewrite_heap_type(heap_type, remap, index_cache~) {
Some(next) =>
Some(
@lib.Instruction::ref_cast_desc_eq_type(
@lib.RefType::new(nullable, next, exact~),
),
)
None => None
}
@lib.BrOnCast(label_idx, cast_op, source, target) =>
match
(
dfe_try_rewrite_heap_type(source, remap, index_cache~),
dfe_try_rewrite_heap_type(target, remap, index_cache~),
) {
(Some(next_source), Some(next_target)) =>
Some(
@lib.Instruction::br_on_cast(
label_idx,
cast_op.source_nullable(),
next_source,
cast_op.target_nullable(),
next_target,
source_exact=cast_op.source_exact(),
target_exact=cast_op.target_exact(),
),
)
(Some(next_source), None) =>
Some(
@lib.Instruction::br_on_cast(
label_idx,
cast_op.source_nullable(),
next_source,
cast_op.target_nullable(),
target,
source_exact=cast_op.source_exact(),
target_exact=cast_op.target_exact(),
),
)
(None, Some(next_target)) =>
Some(
@lib.Instruction::br_on_cast(
label_idx,
cast_op.source_nullable(),
source,
cast_op.target_nullable(),
next_target,
source_exact=cast_op.source_exact(),
target_exact=cast_op.target_exact(),
),
)
(None, None) => None
}
@lib.BrOnCastFail(label_idx, cast_op, source, target) =>
match
(
dfe_try_rewrite_heap_type(source, remap, index_cache~),
dfe_try_rewrite_heap_type(target, remap, index_cache~),
) {
(Some(next_source), Some(next_target)) =>
Some(
@lib.Instruction::br_on_cast_fail(
label_idx,
cast_op.source_nullable(),
next_source,
cast_op.target_nullable(),
next_target,
source_exact=cast_op.source_exact(),
target_exact=cast_op.target_exact(),
),
)
(Some(next_source), None) =>
Some(
@lib.Instruction::br_on_cast_fail(
label_idx,
cast_op.source_nullable(),
next_source,
cast_op.target_nullable(),
target,
source_exact=cast_op.source_exact(),
target_exact=cast_op.target_exact(),
),
)
(None, Some(next_target)) =>
Some(
@lib.Instruction::br_on_cast_fail(
label_idx,
cast_op.source_nullable(),
source,
cast_op.target_nullable(),
next_target,
source_exact=cast_op.source_exact(),
target_exact=cast_op.target_exact(),
),
)
(None, None) => None
}
_ => None
}
}
///|
fn dfe_rewrite_expr_type_idxs(
expr : @lib.Expr,
remap : Map[UInt32, UInt32],
) -> @lib.Expr {
let @lib.Expr(instrs) = expr
let rewritten : Array[@lib.Instruction] = Array::new(capacity=instrs.length())
for instr in instrs {
rewritten.push(dfe_rewrite_instruction_type_idxs(instr, remap))
}
@lib.Expr::new(rewritten)
}
///|
fn _dfe_rewrite_locals_type_idxs(
locals : @lib.Locals,
remap : Map[UInt32, UInt32],
) -> @lib.Locals {
let runs = locals.runs()
let rewritten : Array[@lib.LocalRun] = Array::new(capacity=runs.length())
for run in runs {
rewritten.push(
@lib.LocalRun::new(
run.count(),
dfe_rewrite_val_type(run.val_type(), remap),
),
)
}
@lib.Locals::new(rewritten)
}
///|
fn dfe_rewrite_extern_type(
extern_type : @lib.ExternType,
remap : Map[UInt32, UInt32],
) -> @lib.ExternType {
match extern_type {
@lib.FuncExternType(type_idx) =>
@lib.ExternType::func(dfe_rewrite_type_idx(type_idx, remap))
@lib.TableExternType(table_type) => {
let @lib.TableType(ref_type, limits) = table_type
@lib.ExternType::table(
@lib.TableType::new(dfe_rewrite_ref_type(ref_type, remap), limits),
)
}
@lib.MemExternType(_) => extern_type
@lib.GlobalExternType(@lib.GlobalType(val_type, mutable_)) =>
@lib.ExternType::global(
@lib.GlobalType::new(dfe_rewrite_val_type(val_type, remap), mutable_),
)
@lib.TagExternType(@lib.TagType(type_idx)) =>
@lib.ExternType::tag(
@lib.TagType::new(dfe_rewrite_type_idx(type_idx, remap)),
)
}
}
///|
fn dfe_rewrite_import_type_idxs(
import_ : @lib.Import,
remap : Map[UInt32, UInt32],
) -> @lib.Import {
let @lib.Import(module_name, field_name, extern_type) = import_
@lib.Import::new(
module_name,
field_name,
dfe_rewrite_extern_type(extern_type, remap),
)
}
///|
fn dfe_rewrite_table_type_idxs(
table : @lib.Table,
remap : Map[UInt32, UInt32],
) -> @lib.Table {
let @lib.Table(@lib.TableType(ref_type, limits), init) = table
@lib.Table::new(
@lib.TableType::new(dfe_rewrite_ref_type(ref_type, remap), limits),
match init {
Some(expr) => Some(dfe_rewrite_expr_type_idxs(expr, remap))
None => None
},
)
}
///|
fn dfe_rewrite_global_type_idxs(
global : @lib.Global,
remap : Map[UInt32, UInt32],
) -> @lib.Global {
let @lib.Global(@lib.GlobalType(val_type, mutable_), expr) = global
@lib.Global::new(
@lib.GlobalType::new(dfe_rewrite_val_type(val_type, remap), mutable_),
dfe_rewrite_expr_type_idxs(expr, remap),
)
}
///|
fn dfe_rewrite_elem_mode_type_idxs(
mode : @lib.ElemMode,
remap : Map[UInt32, UInt32],
) -> @lib.ElemMode {
match mode {
@lib.Active(table_idx, expr) =>
@lib.ElemMode::active(table_idx, dfe_rewrite_expr_type_idxs(expr, remap))
_ => mode
}
}
///|
fn dfe_rewrite_elem_kind_type_idxs(
kind : @lib.ElemKind,
remap : Map[UInt32, UInt32],
) -> @lib.ElemKind {
match kind {
@lib.FuncsElemKind(_) => kind
@lib.FuncExprsElemKind(exprs) => {
let rewritten : Array[@lib.Expr] = []
for expr in exprs {
rewritten.push(dfe_rewrite_expr_type_idxs(expr, remap))
}
@lib.ElemKind::func_exprs(rewritten)
}
@lib.TypedExprsElemKind(ref_type, exprs) => {
let rewritten : Array[@lib.Expr] = []
for expr in exprs {
rewritten.push(dfe_rewrite_expr_type_idxs(expr, remap))
}
@lib.ElemKind::typed_exprs(
dfe_rewrite_ref_type(ref_type, remap),
rewritten,
)
}
}
}
///|
fn dfe_rewrite_elem_type_idxs(
elem : @lib.Elem,
remap : Map[UInt32, UInt32],
) -> @lib.Elem {
let @lib.Elem(mode, kind) = elem
@lib.Elem::new(
dfe_rewrite_elem_mode_type_idxs(mode, remap),
dfe_rewrite_elem_kind_type_idxs(kind, remap),
)
}
///|
fn dfe_rewrite_data_mode_type_idxs(
mode : @lib.DataMode,
remap : Map[UInt32, UInt32],
) -> @lib.DataMode {
match mode {
@lib.Active(mem_idx, expr) =>
@lib.DataMode::active(mem_idx, dfe_rewrite_expr_type_idxs(expr, remap))
@lib.Passive => mode
}
}
///|
fn dfe_rewrite_data_type_idxs(
data : @lib.Data,
remap : Map[UInt32, UInt32],
) -> @lib.Data {
let @lib.Data(mode, bytes) = data
@lib.Data::new(dfe_rewrite_data_mode_type_idxs(mode, remap), bytes)
}
///|
fn dfe_rewrite_module_type_idxs(
mod_ : @lib.Module,
remap : Map[UInt32, UInt32],
perf~ : HotPerfSession? = None,
code_rewrite_flags~ : @bitset.BitSet? = None,
rewrite_type_sec~ : Bool = true,
) -> @lib.Module {
let mut rewritten = mod_
match mod_.type_sec {
_ if !rewrite_type_sec => ()
Some(@lib.TypeSec(rec_types)) => {
let timer = dfe_perf_start_timer_opt(perf, "type-rewrite:type-sec")
let next : Array[@lib.RecType] = []
for rec_type in rec_types {
next.push(dfe_rewrite_rec_type(rec_type, remap))
}
rewritten = rewritten.with_type_sec(@lib.TypeSec::new(next))
dfe_perf_stop_timer_opt(perf, "type-rewrite:type-sec", timer)
}
None => ()
}
match mod_.import_sec {
Some(@lib.ImportSec(imports)) => {
let next : Array[@lib.Import] = []
for import_ in imports {
next.push(dfe_rewrite_import_type_idxs(import_, remap))
}
rewritten = rewritten.with_import_sec(@lib.ImportSec::new(next))
}
None => ()
}
match mod_.func_sec {
Some(@lib.FuncSec(type_idxs)) => {
let timer = dfe_perf_start_timer_opt(perf, "type-rewrite:func-sec")
let next : Array[@lib.TypeIdx] = []
for type_idx in type_idxs {
next.push(dfe_rewrite_type_idx(type_idx, remap))
}
rewritten = rewritten.with_func_sec(@lib.FuncSec::new(next))
dfe_perf_stop_timer_opt(perf, "type-rewrite:func-sec", timer)
}
None => ()
}
match mod_.table_sec {
Some(@lib.TableSec(tables)) => {
let next : Array[@lib.Table] = []
for table in tables {
next.push(dfe_rewrite_table_type_idxs(table, remap))
}
rewritten = rewritten.with_table_sec(@lib.TableSec::new(next))
}
None => ()
}
match mod_.tag_sec {
Some(@lib.TagSec(tags)) => {
let next : Array[@lib.TagType] = []
for tag in tags {
let @lib.TagType(type_idx) = tag
next.push(@lib.TagType::new(dfe_rewrite_type_idx(type_idx, remap)))
}
rewritten = rewritten.with_tag_sec(@lib.TagSec::new(next))
}
None => ()
}
match mod_.global_sec {
Some(@lib.GlobalSec(globals)) => {
let next : Array[@lib.Global] = []
for global in globals {
next.push(dfe_rewrite_global_type_idxs(global, remap))
}
rewritten = rewritten.with_global_sec(@lib.GlobalSec::new(next))
}
None => ()
}
match mod_.elem_sec {
Some(@lib.ElemSec(elems)) => {
let next : Array[@lib.Elem] = []
for elem in elems {
next.push(dfe_rewrite_elem_type_idxs(elem, remap))
}
rewritten = rewritten.with_elem_sec(@lib.ElemSec::new(next))
}
None => ()
}
match mod_.code_sec {
Some(@lib.CodeSec(funcs)) => {
// Share immutable TypeIdx and concrete HeapType boxes in this remap.
// Both caches are bounded by distinct reached final remap targets;
// no cache survives in the module or changes surrounding proposal flags.
let index_cache : DfeTypeRewriteCache? = Some(
dfe_new_type_rewrite_cache(),
)
let timer = dfe_perf_start_timer_opt(perf, "type-rewrite:code-sec")
let next : Array[@lib.Func] = []
match code_rewrite_flags {
Some(flags) if flags.length() == funcs.length() => {
let mut idx = 0
while idx < funcs.length() {
let func = funcs[idx]
if flags[idx] {
match dfe_try_rewrite_func_type_idxs(func, remap, index_cache~) {
Some(rewritten_func) => next.push(rewritten_func)
None => next.push(func)
}
} else {
next.push(func)
}
idx += 1
}
}
_ =>
for func in funcs {
match dfe_try_rewrite_func_type_idxs(func, remap, index_cache~) {
Some(rewritten_func) => next.push(rewritten_func)
None => next.push(func)
}
}
}
rewritten = rewritten.with_code_sec(@lib.CodeSec::new(next))
dfe_perf_stop_timer_opt(perf, "type-rewrite:code-sec", timer)
}
None => ()
}
match mod_.data_sec {
Some(@lib.DataSec(datas)) => {
let next : Array[@lib.Data] = []
for data in datas {
next.push(dfe_rewrite_data_type_idxs(data, remap))
}
rewritten = rewritten.with_data_sec(@lib.DataSec::new(next))
}
None => ()
}
rewritten
}
///|
fn dfe_rewrite_func_idx(
idx : @lib.FuncIdx,
remap : Array[UInt32],
) -> @lib.FuncIdx {
let @lib.FuncIdx(raw) = idx
let next = remap[raw.reinterpret_as_int()]
if next == raw {
idx
} else {
@lib.FuncIdx::new(next)
}
}
///|
fn dfe_mark_identity_visible_func(
func_idx : @lib.FuncIdx,
imported_count : Int32,
identity_visible : Array[Bool],
) -> Unit {
let @lib.FuncIdx(raw) = func_idx
let local_idx = raw.reinterpret_as_int() - imported_count
if local_idx >= 0 && local_idx < identity_visible.length() {
identity_visible[local_idx] = true
}
}
///|
fn dfe_mark_ref_func_identities_in_instruction(
instr : @lib.Instruction,
imported_count : Int32,
identity_visible : Array[Bool],
) -> Unit {
match instr {
@lib.Block(_, expr) | @lib.Loop(_, expr) | @lib.TryTable(_, _, expr) =>
dfe_mark_ref_func_identities_in_expr(
expr, imported_count, identity_visible,
)
@lib.Try(_, body, catches, _) => {
dfe_mark_ref_func_identities_in_expr(
body, imported_count, identity_visible,
)
for catch_ in catches {
let expr = match catch_ {
@lib.LegacyCatch(_, expr) | @lib.LegacyCatchAll(expr) => expr
}
dfe_mark_ref_func_identities_in_expr(
expr, imported_count, identity_visible,
)
}
}
@lib.If(_, then_body, else_body) => {
dfe_mark_ref_func_identities_in_instructions(
then_body, imported_count, identity_visible,
)
match else_body {
Some(body) =>
dfe_mark_ref_func_identities_in_instructions(
body, imported_count, identity_visible,
)
None => ()
}
}
@lib.RefFunc(func_idx) =>
dfe_mark_identity_visible_func(func_idx, imported_count, identity_visible)
_ => ()
}
}
///|
fn dfe_mark_ref_func_identities_in_instructions(
instrs : Array[@lib.Instruction],
imported_count : Int32,
identity_visible : Array[Bool],
) -> Unit {
for instr in instrs {
dfe_mark_ref_func_identities_in_instruction(
instr, imported_count, identity_visible,
)
}
}
///|
fn dfe_mark_ref_func_identities_in_expr(
expr : @lib.Expr,
imported_count : Int32,
identity_visible : Array[Bool],
) -> Unit {
let @lib.Expr(instrs) = expr
dfe_mark_ref_func_identities_in_instructions(
instrs, imported_count, identity_visible,
)
}
///|
fn dfe_mark_elem_kind_identities(
kind : @lib.ElemKind,
imported_count : Int32,
identity_visible : Array[Bool],
) -> Unit {
match kind {
@lib.FuncsElemKind(funcs) =>
for func_idx in funcs {
dfe_mark_identity_visible_func(
func_idx, imported_count, identity_visible,
)
}
@lib.FuncExprsElemKind(exprs) | @lib.TypedExprsElemKind(_, exprs) =>
for expr in exprs {
dfe_mark_ref_func_identities_in_expr(
expr, imported_count, identity_visible,
)
}
}
}
///|
fn dfe_identity_visible_defined_funcs(
mod_ : @lib.Module,
imported_count : Int32,
defined_count : Int32,
) -> Array[Bool] {
let identity_visible = Array::make(defined_count, false)
match mod_.export_sec {
Some(@lib.ExportSec(exports)) =>
for export_ in exports {
match export_ {
@lib.Export(_, @lib.FuncExternIdx(func_idx)) =>
dfe_mark_identity_visible_func(
func_idx, imported_count, identity_visible,
)
_ => ()
}
}
None => ()
}
match mod_.elem_sec {
Some(@lib.ElemSec(elems)) =>
for elem in elems {
let @lib.Elem(mode, kind) = elem
// Declarative segments only declare ref.func operands for validation;
// they never instantiate function references at runtime.
if !(mode is @lib.Declarative) {
dfe_mark_elem_kind_identities(kind, imported_count, identity_visible)
}
}
None => ()
}
match mod_.table_sec {
Some(@lib.TableSec(tables)) =>
for table in tables {
let @lib.Table(_, init) = table
match init {
Some(expr) =>
dfe_mark_ref_func_identities_in_expr(
expr, imported_count, identity_visible,
)
None => ()
}
}
None => ()
}
match mod_.global_sec {
Some(@lib.GlobalSec(globals)) =>
for global in globals {
let @lib.Global(_, init) = global
dfe_mark_ref_func_identities_in_expr(
init, imported_count, identity_visible,
)
}
None => ()
}
match mod_.code_sec {
Some(@lib.CodeSec(funcs)) =>
for func in funcs {
let @lib.Func(_, body) = func
dfe_mark_ref_func_identities_in_expr(
body, imported_count, identity_visible,
)
}
None => ()
}
identity_visible
}
///|
fn dfe_rewrite_instruction_func_idxs(
instr : @lib.Instruction,
remap : Array[UInt32],
) -> @lib.Instruction {
match instr {
@lib.Block(block_type, expr) => {
let rewritten = dfe_rewrite_expr_func_idxs(expr, remap)
if physical_equal(rewritten, expr) {
instr
} else {
@lib.Instruction::block(block_type, rewritten)
}
}
@lib.Loop(block_type, expr) => {
let rewritten = dfe_rewrite_expr_func_idxs(expr, remap)
if physical_equal(rewritten, expr) {
instr
} else {
@lib.Instruction::loop_(block_type, rewritten)
}
}
@lib.If(block_type, then_body, else_body) => {
let rewritten_then = dfe_rewrite_instrs_func_idxs(then_body, remap)
let mut else_changed = false
let rewritten_else = match else_body {
Some(body) => {
let rewritten = dfe_rewrite_instrs_func_idxs(body, remap)
else_changed = !physical_equal(rewritten, body)
Some(rewritten)
}
None => None
}
if physical_equal(rewritten_then, then_body) && !else_changed {
instr
} else {
@lib.Instruction::if_(block_type, rewritten_then, rewritten_else)
}
}
@lib.Call(func_idx) => {
let rewritten = dfe_rewrite_func_idx(func_idx, remap)
if rewritten == func_idx {
instr
} else {
@lib.Instruction::call(rewritten)
}
}
@lib.ReturnCall(func_idx) => {
let rewritten = dfe_rewrite_func_idx(func_idx, remap)
if rewritten == func_idx {
instr
} else {
@lib.Instruction::return_call(rewritten)
}
}
@lib.TryTable(block_type, catches, body) => {
let rewritten = dfe_rewrite_expr_func_idxs(body, remap)
if physical_equal(rewritten, body) {
instr
} else {
@lib.Instruction::try_table(block_type, catches, rewritten)
}
}
@lib.Try(block_type, body, catches, delegate) => {
let next_body = dfe_rewrite_expr_func_idxs(body, remap)
let mut next_catches : Array[@lib.LegacyCatch]? = None
for idx = 0; idx < catches.length(); idx = idx + 1 {
let catch_ = catches[idx]
let expr = match catch_ {
@lib.LegacyCatch(_, expr) | @lib.LegacyCatchAll(expr) => expr
}
let next_expr = dfe_rewrite_expr_func_idxs(expr, remap)
if physical_equal(next_expr, expr) {
continue
}
let next = match catch_ {
@lib.LegacyCatch(tag, _) => @lib.LegacyCatch::tagged(tag, next_expr)
@lib.LegacyCatchAll(_) => @lib.LegacyCatch::all(next_expr)
}
match next_catches {
Some(copy) => copy[idx] = next
None => {
let copy = catches.copy()
copy[idx] = next
next_catches = Some(copy)
}
}
}
if physical_equal(next_body, body) && next_catches is None {
instr
} else {
@lib.Instruction::try_(
block_type,
next_body,
next_catches.unwrap_or(catches),
delegate~,
)
}
}
@lib.RefFunc(func_idx) => {
let rewritten = dfe_rewrite_func_idx(func_idx, remap)
if rewritten == func_idx {
instr
} else {
@lib.Instruction::ref_func(rewritten)
}
}
_ => instr
}
}
///|
fn dfe_rewrite_instrs_func_idxs(
instrs : Array[@lib.Instruction],
remap : Array[UInt32],
) -> Array[@lib.Instruction] {
let mut out : Array[@lib.Instruction]? = None
for index, instr in instrs {
let rewritten = dfe_rewrite_instruction_func_idxs(instr, remap)
if out is None && !physical_equal(rewritten, instr) {
let prefix : Array[@lib.Instruction] = Array::new(
capacity=instrs.length(),
)
for offset = 0; offset < index; offset = offset + 1 {
prefix.push(instrs[offset])
}
out = Some(prefix)
}
if out is Some(next) {
next.push(rewritten)
}
}
out.unwrap_or(instrs)
}
///|
fn dfe_rewrite_expr_func_idxs(
expr : @lib.Expr,
remap : Array[UInt32],
) -> @lib.Expr {
let @lib.Expr(instrs) = expr
let rewritten = dfe_rewrite_instrs_func_idxs(instrs, remap)
if physical_equal(rewritten, instrs) {
expr
} else {
@lib.Expr::new(rewritten)
}
}
///|
fn dfe_rewrite_func_func_idxs(
func : @lib.Func,
remap : Array[UInt32],
) -> @lib.Func {
let @lib.Func(locals, body) = func
let rewritten = dfe_rewrite_expr_func_idxs(body, remap)
if physical_equal(rewritten, body) {
func
} else {
@lib.Func::new(locals, rewritten)
}
}
///|
fn dfe_rewrite_extern_idx_func_idxs(
idx : @lib.ExternIdx,
remap : Array[UInt32],
) -> @lib.ExternIdx {
match idx {
@lib.FuncExternIdx(func_idx) =>
@lib.ExternIdx::func(dfe_rewrite_func_idx(func_idx, remap))
_ => idx
}
}
///|
fn dfe_rewrite_export_func_idxs(
export_ : @lib.Export,
remap : Array[UInt32],
) -> @lib.Export {
let @lib.Export(name, idx) = export_
@lib.Export::new(name, dfe_rewrite_extern_idx_func_idxs(idx, remap))
}
///|
fn dfe_rewrite_elem_kind_func_idxs(
kind : @lib.ElemKind,
remap : Array[UInt32],
) -> @lib.ElemKind {
match kind {
@lib.FuncsElemKind(funcs) => {
let rewritten : Array[@lib.FuncIdx] = Array::new(capacity=funcs.length())
for func_idx in funcs {
rewritten.push(dfe_rewrite_func_idx(func_idx, remap))
}
@lib.ElemKind::funcs(rewritten)
}
@lib.FuncExprsElemKind(exprs) => {
let rewritten : Array[@lib.Expr] = Array::new(capacity=exprs.length())
for expr in exprs {
rewritten.push(dfe_rewrite_expr_func_idxs(expr, remap))
}
@lib.ElemKind::func_exprs(rewritten)
}
@lib.TypedExprsElemKind(ref_type, exprs) => {
let rewritten : Array[@lib.Expr] = Array::new(capacity=exprs.length())
for expr in exprs {
rewritten.push(dfe_rewrite_expr_func_idxs(expr, remap))
}
@lib.ElemKind::typed_exprs(ref_type, rewritten)
}
}
}
///|
fn dfe_rewrite_func_annotation_sec(
sec : @lib.FuncAnnotationSec,
remap : Array[UInt32],
drop_unmapped : Bool,
) -> @lib.FuncAnnotationSec {
let @lib.FuncAnnotationSec(entries) = sec
let rewritten_indices : Array[UInt32] = []
let rewritten_annotations : Array[Array[@lib.FuncAnnotation]] = []
for entry in entries {
let @lib.FuncAnnotationAssoc(idx, annotations) = entry
let next_idx = match remap.get(idx.reinterpret_as_int()) {
Some(next_idx) => next_idx
None => if drop_unmapped { continue } else { idx }
}
let mut slot = -1
for candidate = 0
candidate < rewritten_indices.length()
candidate = candidate + 1 {
if rewritten_indices[candidate] == next_idx {
slot = candidate
break
}
}
if slot < 0 {
rewritten_indices.push(next_idx)
rewritten_annotations.push([])
slot = rewritten_indices.length() - 1
}
for annotation in annotations {
if !rewritten_annotations[slot].contains(annotation) {
rewritten_annotations[slot].push(annotation)
}
}
}
let rewritten : Array[@lib.FuncAnnotationAssoc] = []
for slot = 0; slot < rewritten_indices.length(); slot = slot + 1 {
rewritten.push(
@lib.FuncAnnotationAssoc::new(
rewritten_indices[slot],
rewritten_annotations[slot],
),
)
}
@lib.FuncAnnotationSec::new(rewritten)
}
///|
fn dfe_rewrite_func_annotation_sec_in_module(
mod_ : @lib.Module,
remap : Array[UInt32],
drop_unmapped : Bool,
) -> @lib.Module {
match mod_.func_annotation_sec {
Some(sec) =>
mod_.with_func_annotation_sec(
dfe_rewrite_func_annotation_sec(sec, remap, drop_unmapped),
)
None => mod_
}
}
///|
fn dfe_rewrite_module_func_idxs(
mod_ : @lib.Module,
remap : Array[UInt32],
perf~ : HotPerfSession? = None,
code_rewrite_flags~ : @bitset.BitSet? = None,
) -> @lib.Module {
let mut rewritten = mod_
match mod_.export_sec {
Some(@lib.ExportSec(exports)) => {
let next : Array[@lib.Export] = Array::new(capacity=exports.length())
for export_ in exports {
next.push(dfe_rewrite_export_func_idxs(export_, remap))
}
rewritten = rewritten.with_export_sec(@lib.ExportSec::new(next))
}
None => ()
}
match mod_.start_sec {
Some(@lib.StartSec(func_idx)) =>
rewritten = rewritten.with_start_sec(
@lib.StartSec::new(dfe_rewrite_func_idx(func_idx, remap)),
)
None => ()
}
match mod_.elem_sec {
Some(@lib.ElemSec(elems)) => {
let next : Array[@lib.Elem] = Array::new(capacity=elems.length())
for elem in elems {
let @lib.Elem(mode, kind) = elem
next.push(
@lib.Elem::new(
match mode {
@lib.Active(table_idx, expr) =>
@lib.ElemMode::active(
table_idx,
dfe_rewrite_expr_func_idxs(expr, remap),
)
_ => mode
},
dfe_rewrite_elem_kind_func_idxs(kind, remap),
),
)
}
rewritten = rewritten.with_elem_sec(@lib.ElemSec::new(next))
}
None => ()
}
match mod_.table_sec {
Some(@lib.TableSec(tables)) => {
let next : Array[@lib.Table] = Array::new(capacity=tables.length())
for table in tables {
let @lib.Table(table_type, init) = table
next.push(
@lib.Table::new(
table_type,
match init {
Some(expr) => Some(dfe_rewrite_expr_func_idxs(expr, remap))
None => None
},
),
)
}
rewritten = rewritten.with_table_sec(@lib.TableSec::new(next))
}
None => ()
}
match mod_.global_sec {
Some(@lib.GlobalSec(globals)) => {
let next : Array[@lib.Global] = Array::new(capacity=globals.length())
for global in globals {
let @lib.Global(global_type, expr) = global
next.push(
@lib.Global::new(global_type, dfe_rewrite_expr_func_idxs(expr, remap)),
)
}
rewritten = rewritten.with_global_sec(@lib.GlobalSec::new(next))
}
None => ()
}
match mod_.code_sec {
Some(@lib.CodeSec(funcs)) => {
let timer = dfe_perf_start_timer_opt(perf, "func-rewrite:code-sec")
let next : Array[@lib.Func] = Array::new(capacity=funcs.length())
match code_rewrite_flags {
Some(flags) if flags.length() == funcs.length() =>
for idx = 0; idx < funcs.length(); idx = idx + 1 {
if flags[idx] {
next.push(dfe_rewrite_func_func_idxs(funcs[idx], remap))
} else {
next.push(funcs[idx])
}
}
_ =>
for func in funcs {
next.push(dfe_rewrite_func_func_idxs(func, remap))
}
}
rewritten = rewritten.with_code_sec(@lib.CodeSec::new(next))
dfe_perf_stop_timer_opt(perf, "func-rewrite:code-sec", timer)
}
None => ()
}
match mod_.data_sec {
Some(@lib.DataSec(datas)) => {
let next : Array[@lib.Data] = Array::new(capacity=datas.length())
for data in datas {
let @lib.Data(mode, bytes) = data
next.push(
@lib.Data::new(
match mode {
@lib.Active(mem_idx, expr) =>
@lib.DataMode::active(
mem_idx,
dfe_rewrite_expr_func_idxs(expr, remap),
)
@lib.Passive => mode
},
bytes,
),
)
}
rewritten = rewritten.with_data_sec(@lib.DataSec::new(next))
}
None => ()
}
dfe_rewrite_func_annotation_sec_in_module(rewritten, remap, false)
}
///|
fn dfe_canonical_remap_sources(
remap : Map[UInt32, UInt32],
) -> Map[UInt32, UInt32] {
let canonical : Map[UInt32, UInt32] = Map([])
for source_idx in remap.keys() {
let target_idx = remap[source_idx]
match canonical.get(target_idx) {
Some(existing) =>
if source_idx < existing {
canonical[target_idx] = source_idx
}
None => canonical[target_idx] = source_idx
}
}
canonical
}
///|
fn dfe_rewrite_name_map_filtered(
names : @lib.NameMap,
remap : Map[UInt32, UInt32],
drop_unmapped : Bool,
) -> @lib.NameMap {
let @lib.NameMap(entries) = names
let canonical_sources = dfe_canonical_remap_sources(remap)
let rewritten : Array[@lib.NameAssoc] = []
let mut last_idx : UInt32? = None
for entry in entries {
let @lib.NameAssoc(idx, name) = entry
match remap.get(idx) {
Some(next_idx) =>
if canonical_sources.get(next_idx) == Some(idx) &&
last_idx != Some(next_idx) {
rewritten.push(@lib.NameAssoc::new(next_idx, name))
last_idx = Some(next_idx)
}
None =>
if !drop_unmapped && last_idx != Some(idx) {
rewritten.push(entry)
last_idx = Some(idx)
}
}
}
@lib.NameMap::new(rewritten)
}
///|
fn _dfe_rewrite_local_name_map_filtered(
names : @lib.IndirectNameMap,
remap : Map[UInt32, UInt32],
drop_unmapped : Bool,
) -> @lib.IndirectNameMap {
let @lib.IndirectNameMap(entries) = names
let canonical_sources = dfe_canonical_remap_sources(remap)
let rewritten : Array[@lib.IndirectNameAssoc] = []
let mut last_idx : UInt32? = None
for entry in entries {
let @lib.IndirectNameAssoc(idx, local_names) = entry
match remap.get(idx) {
Some(next_idx) =>
if canonical_sources.get(next_idx) == Some(idx) &&
last_idx != Some(next_idx) {
rewritten.push(@lib.IndirectNameAssoc::new(next_idx, local_names))
last_idx = Some(next_idx)
}
None =>
if !drop_unmapped && last_idx != Some(idx) {
rewritten.push(entry)
last_idx = Some(idx)
}
}
}
@lib.IndirectNameMap::new(rewritten)
}
///|
fn dfe_rewrite_type_name_sec(
name_sec : @lib.NameSec,
type_remap : Map[UInt32, UInt32],
) -> @lib.NameSec {
let {
module_name,
func_names,
local_names,
label_names,
type_names,
table_names,
memory_names,
global_names,
elem_names,
data_names,
field_names,
tag_names,
} = name_sec
@lib.NameSec::new(
module_name~,
func_names~,
local_names~,
label_names~,
type_names=match type_names {
Some(names) =>
Some(dfe_rewrite_name_map_filtered(names, type_remap, true))
None => None
},
table_names~,
memory_names~,
global_names~,
elem_names~,
data_names~,
field_names~,
tag_names~,
)
}
///|
fn dfe_funcs_semantically_equal(left : @lib.Func, right : @lib.Func) -> Bool {
let @lib.Func(left_locals, @lib.Expr(left_body)) = left
let @lib.Func(right_locals, @lib.Expr(right_body)) = right
left_locals == right_locals &&
pass_const_instruction_vectors_bits_equal(left_body, right_body)
}
///|
fn dfe_perf_start_timer_opt(perf : HotPerfSession?, name : String) -> UInt64 {
match perf {
Some(session) =>
perf_start_timer(session, "detail:duplicate-function-elimination:\{name}")
None => 0UL
}
}
///|
fn dfe_perf_stop_timer_opt(
perf : HotPerfSession?,
name : String,
start_us : UInt64,
) -> Unit {
match perf {
Some(session) =>
perf_stop_timer(
session,
"detail:duplicate-function-elimination:\{name}",
start_us,
)
None => ()
}
}
///|
fn dfe_trace_opt(perf : HotPerfSession?, msg : String) -> Unit {
match perf {
Some(session) =>
if session.enabled {
(session.trace)("detail:duplicate-function-elimination:\{msg}")
}
None => ()
}
}
///|
fn dfe_functions_equivalent(
left_canon_type : UInt32,
left_func : @lib.Func,
left_annotations : Array[@lib.FuncAnnotation],
right_canon_type : UInt32,
right_func : @lib.Func,
right_annotations : Array[@lib.FuncAnnotation],
) -> Bool {
if !dfe_funcs_semantically_equal(left_func, right_func) ||
left_annotations != right_annotations {
return false
}
left_canon_type == right_canon_type
}
///|
fn dfe_functions_equivalent_no_annotations(
left_canon_type : UInt32,
left_func : @lib.Func,
right_canon_type : UInt32,
right_func : @lib.Func,
) -> Bool {
if !dfe_funcs_semantically_equal(left_func, right_func) {
return false
}
left_canon_type == right_canon_type
}
///|
fn dfe_shape_instruction_array_hash(
instrs : Array[@lib.Instruction],
mentions_type_idxs~ : Array[Bool]? = None,
needs_debris_cleanup~ : Array[Bool]? = None,
max_func_target~ : Array[Int32]? = None,
) -> Int32 {
let mut key = instrs.length().hash()
for idx = 0; idx < instrs.length(); idx = idx + 1 {
match needs_debris_cleanup {
Some(flag) =>
if !flag[0] {
match instrs[idx] {
@lib.Unreachable | @lib.Block(_, _) => {
let mut next = idx + 1
while next < instrs.length() {
match instrs[next] {
@lib.Drop => next += 1
_ => break
}
}
if next < instrs.length() && instrs[next] is @lib.Unreachable {
match instrs[idx] {
@lib.Unreachable => flag[0] = true
@lib.Block(_, _) =>
if pass_raw_dropped_block_has_unreachable_tail(instrs[idx]) ||
pass_raw_void_block_can_inline_before_unreachable(
instrs[idx],
) {
flag[0] = true
}
_ => ()
}
}
}
_ => ()
}
}
None => ()
}
key = key * 31 +
dfe_shape_instruction_hash(
instrs[idx],
mentions_type_idxs~,
needs_debris_cleanup~,
max_func_target~,
)
}
key
}
///|
fn dfe_shape_instruction_hash(
instr : @lib.Instruction,
mentions_type_idxs~ : Array[Bool]? = None,
needs_debris_cleanup~ : Array[Bool]? = None,
max_func_target~ : Array[Int32]? = None,
) -> Int32 {
match instr {
@lib.Block(block_type, @lib.Expr(body)) => {
if dfe_block_type_mentions_type_idxs(block_type) {
match mentions_type_idxs {
Some(flag) => flag[0] = true
None => ()
}
}
1009 * 31 +
dfe_shape_instruction_array_hash(
body,
mentions_type_idxs~,
needs_debris_cleanup~,
max_func_target~,
)
}
@lib.Loop(block_type, @lib.Expr(body)) => {
if dfe_block_type_mentions_type_idxs(block_type) {
match mentions_type_idxs {
Some(flag) => flag[0] = true
None => ()
}
}
1013 * 31 +
dfe_shape_instruction_array_hash(
body,
mentions_type_idxs~,
needs_debris_cleanup~,
max_func_target~,
)
}
@lib.If(block_type, then_body, else_body) => {
if dfe_block_type_mentions_type_idxs(block_type) {
match mentions_type_idxs {
Some(flag) => flag[0] = true
None => ()
}
}
let mut key = 1019 * 31 +
dfe_shape_instruction_array_hash(
then_body,
mentions_type_idxs~,
needs_debris_cleanup~,
max_func_target~,
)
match else_body {
Some(body) =>
key = key * 31 +
dfe_shape_instruction_array_hash(
body,
mentions_type_idxs~,
needs_debris_cleanup~,
max_func_target~,
)
None => key *= 31
}
key
}
@lib.TryTable(block_type, catches, @lib.Expr(body)) => {
if dfe_block_type_mentions_type_idxs(block_type) {
match mentions_type_idxs {
Some(flag) => flag[0] = true
None => ()
}
}
(1021 * 31 + catches.hash()) * 31 +
dfe_shape_instruction_array_hash(
body,
mentions_type_idxs~,
needs_debris_cleanup~,
max_func_target~,
)
}
@lib.Try(block_type, @lib.Expr(body), catches, delegate) => {
if dfe_block_type_mentions_type_idxs(block_type) {
match mentions_type_idxs {
Some(flag) => flag[0] = true
None => ()
}
}
let mut key = (1023 * 31 + delegate.hash()) * 31 +
dfe_shape_instruction_array_hash(
body,
mentions_type_idxs~,
needs_debris_cleanup~,
max_func_target~,
)
for catch_ in catches {
let expr = match catch_ {
@lib.LegacyCatch(tag, expr) => {
key = (key * 31 + 1025) * 31 + tag.hash()
expr
}
@lib.LegacyCatchAll(expr) => {
key = key * 31 + 1027
expr
}
}
let @lib.Expr(catch_body) = expr
key = key * 31 +
dfe_shape_instruction_array_hash(
catch_body,
mentions_type_idxs~,
needs_debris_cleanup~,
max_func_target~,
)
}
key
}
@lib.Call(func_idx) => {
match max_func_target {
Some(max_target) => {
let @lib.FuncIdx(raw) = func_idx
let target = raw.reinterpret_as_int()
if target > max_target[0] {
max_target[0] = target
}
}
None => ()
}
1031
}
@lib.ReturnCall(func_idx) => {
match max_func_target {
Some(max_target) => {
let @lib.FuncIdx(raw) = func_idx
let target = raw.reinterpret_as_int()
if target > max_target[0] {
max_target[0] = target
}
}
None => ()
}
1033
}
@lib.RefFunc(func_idx) => {
match max_func_target {
Some(max_target) => {
let @lib.FuncIdx(raw) = func_idx
let target = raw.reinterpret_as_int()
if target > max_target[0] {
max_target[0] = target
}
}
None => ()
}
1039
}
_ =>
if dfe_instruction_mentions_type_idxs(instr) {
match mentions_type_idxs {
Some(flag) => flag[0] = true
None => ()
}
1049
} else {
instr.hash()
}
}
}
///|
fn dfe_shape_locals_hash(locals : @lib.Locals) -> Int32 {
let mut key = 17
for run in locals.runs() {
key = key * 31 + run.count().hash()
let val_type_hash = if dfe_val_type_mentions_type_idxs(run.val_type()) {
1
} else {
run.val_type().hash()
}
key = key * 31 + val_type_hash
}
key
}
///|
fn dfe_function_shape_hash_no_annotations(
type_hash : Int32,
func : @lib.Func,
mentions_type_idxs~ : Array[Bool]? = None,
needs_debris_cleanup~ : Array[Bool]? = None,
max_func_target~ : Array[Int32]? = None,
) -> Int32 {
let @lib.Func(locals, @lib.Expr(instrs)) = func
for run in locals.runs() {
if dfe_val_type_mentions_type_idxs(run.val_type()) {
match mentions_type_idxs {
Some(flag) => flag[0] = true
None => ()
}
}
}
(
(dfe_shape_locals_hash(locals) * 31 + type_hash) * 31 +
instrs.length().hash()
) *
31 +
dfe_shape_instruction_array_hash(
instrs,
mentions_type_idxs~,
needs_debris_cleanup~,
max_func_target~,
)
}
///|
fn dfe_function_shape_hash(
type_hash : Int32,
func : @lib.Func,
annotations : Array[@lib.FuncAnnotation],
mentions_type_idxs~ : Array[Bool]? = None,
needs_debris_cleanup~ : Array[Bool]? = None,
max_func_target~ : Array[Int32]? = None,
) -> Int32 {
dfe_function_shape_hash_no_annotations(
type_hash,
func,
mentions_type_idxs~,
needs_debris_cleanup~,
max_func_target~,
) *
31 +
annotations.hash()
}
///|
fn dfe_canonical_local_idx(
replacements : Array[Int32],
local_idx : Int32,
) -> Int32 {
let mut current = local_idx
while true {
let next = replacements[current]
if next >= 0 {
current = next
} else {
return current
}
}
current
}
///|
fn dfe_type_idx_array_uses_remap(
type_idxs : Array[@lib.TypeIdx],
remap : Map[UInt32, UInt32],
) -> Bool {
for type_idx in type_idxs {
if dfe_type_idx_is_remapped(type_idx, remap) {
return true
}
}
false
}
///|
fn dfe_module_type_refs_use_remap(
mod_ : @lib.Module,
remap : Map[UInt32, UInt32],
) -> Bool {
match mod_.type_sec {
Some(@lib.TypeSec(rec_types)) =>
for rec_type in rec_types {
if dfe_rec_type_uses_remap(rec_type, remap) {
return true
}
}
None => ()
}
match mod_.import_sec {
Some(@lib.ImportSec(imports)) =>
for import_ in imports {
if dfe_rewrite_import_type_idxs(import_, remap) != import_ {
return true
}
}
None => ()
}
match mod_.func_sec {
Some(@lib.FuncSec(type_idxs)) =>
if dfe_type_idx_array_uses_remap(type_idxs, remap) {
return true
}
None => ()
}
match mod_.table_sec {
Some(@lib.TableSec(tables)) =>
for table in tables {
if dfe_rewrite_table_type_idxs(table, remap) != table {
return true
}
}
None => ()
}
match mod_.tag_sec {
Some(@lib.TagSec(tags)) =>
for tag in tags {
let @lib.TagType(type_idx) = tag
if dfe_type_idx_is_remapped(type_idx, remap) {
return true
}
}
None => ()
}
match mod_.global_sec {
Some(@lib.GlobalSec(globals)) =>
for global in globals {
if dfe_rewrite_global_type_idxs(global, remap) != global {
return true
}
}
None => ()
}
match mod_.elem_sec {
Some(@lib.ElemSec(elems)) =>
for elem in elems {
if dfe_rewrite_elem_type_idxs(elem, remap) != elem {
return true
}
}
None => ()
}
match mod_.code_sec {
Some(@lib.CodeSec(funcs)) =>
for func in funcs {
if dfe_scan_rewrite_func_type_idxs(func, remap).rewritten is Some(_) {
return true
}
}
None => ()
}
match mod_.data_sec {
Some(@lib.DataSec(datas)) =>
for data in datas {
if dfe_rewrite_data_type_idxs(data, remap) != data {
return true
}
}
None => ()
}
false
}
///|
fn dfe_rec_type_is_unused_suffix_prune_candidate(
rec_type : @lib.RecType,
) -> Bool {
match rec_type {
@lib.SingleRecType(subtype) => {
if subtype.super_types().length() > 0 ||
subtype.describes_type() is Some(_) ||
subtype.descriptor_type() is Some(_) {
return false
}
match subtype.get_comptype() {
@lib.FuncCompType(params, results) =>
!dfe_val_types_mention_type_idxs(params) &&
!dfe_val_types_mention_type_idxs(results)
_ => false
}
}
@lib.GroupRecType(_) => false
}
}
///|
fn dfe_prune_unused_simple_types(
mod_ : @lib.Module,
root_scans~ : Array[Int32]? = None,
) -> @lib.Module {
match mod_.type_sec {
Some(@lib.TypeSec(rec_types)) => {
if rec_types.length() == 0 {
return mod_
}
for rec_type in rec_types {
match rec_type {
@lib.SingleRecType(_) => ()
@lib.GroupRecType(_) => return mod_
}
}
let live : Array[Bool] = Array::make(rec_types.length(), false)
// Function-section references are direct and common. Avoid probing the
// entire module separately for each already-live signature.
match mod_.func_sec {
Some(@lib.FuncSec(type_idxs)) =>
for entry in type_idxs {
match entry {
@lib.TypeIdx(raw_idx) => {
let type_idx = raw_idx.reinterpret_as_int()
if type_idx >= 0 && type_idx < live.length() {
live[type_idx] = true
}
}
@lib.RecIdx(_) => ()
}
}
None => ()
}
match mod_.import_sec {
Some(@lib.ImportSec(imports)) =>
for import_ in imports {
match import_ {
@lib.Import(_, _, @lib.FuncExternType(@lib.TypeIdx(raw_idx))) => {
let type_idx = raw_idx.reinterpret_as_int()
if type_idx >= 0 && type_idx < live.length() {
live[type_idx] = true
}
}
@lib.Import(
_,
_,
@lib.TagExternType(@lib.TagType(@lib.TypeIdx(raw_idx)))
) => {
let type_idx = raw_idx.reinterpret_as_int()
if type_idx >= 0 && type_idx < live.length() {
live[type_idx] = true
}
}
_ => ()
}
}
None => ()
}
match mod_.tag_sec {
Some(@lib.TagSec(tags)) =>
for tag in tags {
match tag {
@lib.TagType(@lib.TypeIdx(raw_idx)) => {
let type_idx = raw_idx.reinterpret_as_int()
if type_idx >= 0 && type_idx < live.length() {
live[type_idx] = true
}
}
_ => ()
}
}
None => ()
}
if live.all(fn(is_live) { is_live }) {
return mod_
}
let refs : DfeTypeRefs = { live, pending: [], }
for idx in 0.. count[0] += 1
None => ()
}
dfe_type_refs_module_roots(mod_, refs)
let mut cursor = 0
while cursor < refs.pending.length() {
dfe_type_refs_rec_type(rec_types[refs.pending[cursor]], refs)
cursor += 1
}
let mut idx = 0
let kept : Array[@lib.RecType] = []
let remap : Map[UInt32, UInt32] = Map([])
idx = 0
while idx < rec_types.length() {
if live[idx] {
remap[idx.reinterpret_as_uint()] = kept.length().reinterpret_as_uint()
kept.push(rec_types[idx])
}
idx += 1
}
if kept.length() == rec_types.length() {
return mod_
}
let rewritten = dfe_rewrite_module_type_idxs(
mod_.with_type_sec(@lib.TypeSec::new(kept)),
remap,
)
match rewritten.name_sec {
Some(name_sec) =>
rewritten.with_name_sec(dfe_rewrite_type_name_sec(name_sec, remap))
None => rewritten
}
}
None => mod_
}
}
///|
fn dfe_prune_unused_simple_func_type_suffix(mod_ : @lib.Module) -> @lib.Module {
match mod_.type_sec {
Some(@lib.TypeSec(rec_types)) => {
let mut keep_count = rec_types.length()
while keep_count > 0 {
let candidate_idx = keep_count - 1
if !dfe_rec_type_is_unused_suffix_prune_candidate(
rec_types[candidate_idx],
) {
break
}
let remap : Map[UInt32, UInt32] = Map([])
remap[candidate_idx.reinterpret_as_uint()] = 0U
if dfe_module_type_refs_use_remap(mod_, remap) {
break
}
keep_count -= 1
}
if keep_count == rec_types.length() {
return mod_
}
let kept : Array[@lib.RecType] = []
let keep_remap : Map[UInt32, UInt32] = Map([])
for idx = 0; idx < keep_count; idx = idx + 1 {
kept.push(rec_types[idx])
keep_remap[idx.reinterpret_as_uint()] = idx.reinterpret_as_uint()
}
let with_types = mod_.with_type_sec(@lib.TypeSec::new(kept))
match with_types.name_sec {
Some(name_sec) =>
with_types.with_name_sec(
dfe_rewrite_type_name_sec(name_sec, keep_remap),
)
None => with_types
}
}
None => mod_
}
}
///|
fn dfe_canonicalize_duplicate_simple_type_indices(
mod_ : @lib.Module,
perf~ : HotPerfSession? = None,
code_rewrite_flags~ : @bitset.BitSet? = None,
) -> @lib.Module {
match mod_.type_sec {
Some(@lib.TypeSec(rec_types)) => {
let map_timer = dfe_perf_start_timer_opt(perf, "type-canon:map")
let canonical_old_idxs = dfe_duplicate_simple_type_canonical_map(
rec_types,
)
dfe_perf_stop_timer_opt(perf, "type-canon:map", map_timer)
if canonical_old_idxs.length() == 0 {
return mod_
}
let keep_timer = dfe_perf_start_timer_opt(perf, "type-canon:kept-types")
let kept_types : Array[@lib.RecType] = []
let old_to_new : Map[UInt32, UInt32] = Map([])
let mut old_idx = 0
let mut next_idx = 0
for rec_type in rec_types {
let member_count = dfe_rec_type_member_count(rec_type)
let raw_old_idx = old_idx.reinterpret_as_uint()
if canonical_old_idxs.get(raw_old_idx) is None {
for member = 0; member < member_count; member = member + 1 {
old_to_new[(old_idx + member).reinterpret_as_uint()] = (next_idx +
member).reinterpret_as_uint()
}
kept_types.push(rec_type)
next_idx += member_count
}
old_idx += member_count
}
let flat_type_count = old_idx
dfe_perf_stop_timer_opt(perf, "type-canon:kept-types", keep_timer)
if kept_types.length() == rec_types.length() {
return mod_
}
let remap_timer = dfe_perf_start_timer_opt(perf, "type-canon:remap")
// Operand rewriting needs only changed indices; explicit identity entries
// make the shared optional reference rewriter reconstruct unchanged trees.
// Names still need dense ownership/remapping to preserve unmoved entries.
let type_remap : Map[UInt32, UInt32] = Map([])
let name_remap : Map[UInt32, UInt32]? = match mod_.name_sec {
Some(_) => Some(Map([]))
None => None
}
old_idx = 0
while old_idx < flat_type_count {
let raw_old_idx = old_idx.reinterpret_as_uint()
let canonical_old_idx = canonical_old_idxs
.get(raw_old_idx)
.unwrap_or(raw_old_idx)
match old_to_new.get(canonical_old_idx) {
Some(new_idx) => {
if raw_old_idx != new_idx {
type_remap[raw_old_idx] = new_idx
}
match name_remap {
Some(names) => names[raw_old_idx] = new_idx
None => ()
}
}
None => return mod_
}
old_idx += 1
}
dfe_perf_stop_timer_opt(perf, "type-canon:remap", remap_timer)
let rewrite_timer = dfe_perf_start_timer_opt(perf, "type-canon:rewrite")
let rewritten = dfe_rewrite_module_type_idxs(
mod_,
type_remap,
perf~,
code_rewrite_flags~,
rewrite_type_sec=false,
)
let rewritten_kept_types : Array[@lib.RecType] = []
for kept_type in kept_types {
if dfe_rec_type_uses_remap(kept_type, type_remap) {
rewritten_kept_types.push(dfe_rewrite_rec_type(kept_type, type_remap))
} else {
rewritten_kept_types.push(kept_type)
}
}
dfe_perf_stop_timer_opt(perf, "type-canon:rewrite", rewrite_timer)
let with_types = rewritten.with_type_sec(
@lib.TypeSec::new(rewritten_kept_types),
)
match (with_types.name_sec, name_remap) {
(Some(name_sec), Some(names)) =>
with_types.with_name_sec(dfe_rewrite_type_name_sec(name_sec, names))
_ => with_types
}
}
None => mod_
}
}
///|
priv struct DfeEliminationResult {
mod_ : @lib.Module
changed : Bool
iteration_count : Int32
type_rewrite_flags : @bitset.BitSet?
cleanup_flags : @bitset.BitSet?
}
///|
fn dfe_canonical_abs_remap(
replacements : Array[Int32],
imported_count : Int32,
) -> Array[UInt32] {
let remap : Array[UInt32] = Array::new(
capacity=imported_count + replacements.length(),
)
for idx = 0; idx < imported_count; idx = idx + 1 {
remap.push(idx.reinterpret_as_uint())
}
for local_idx = 0
local_idx < replacements.length()
local_idx = local_idx + 1 {
remap.push(
(imported_count + dfe_canonical_local_idx(replacements, local_idx)).reinterpret_as_uint(),
)
}
remap
}
///|
fn dfe_type_normalized_candidate(
funcs : Array[@lib.Func],
local_idx : Int32,
type_canon : Map[UInt32, UInt32],
cache : Array[@lib.Func?],
ready : Array[Bool],
normalization_visits : Array[Int32]?,
normalized_rewrite_count : Array[Int32],
) -> @lib.Func {
if !ready[local_idx] {
match normalization_visits {
Some(visits) => visits[0] += 1
None => ()
}
let func = funcs[local_idx]
let normalized = if type_canon.length() == 0 {
func
} else {
let scan = dfe_scan_rewrite_func_type_idxs(func, type_canon)
match scan.rewritten {
Some(next) => {
normalized_rewrite_count[0] += 1
next
}
None => func
}
}
cache[local_idx] = Some(normalized)
ready[local_idx] = true
}
cache[local_idx].unwrap()
}
///|
fn dfe_eliminate_duplicate_functions(
mod_ : @lib.Module,
perf~ : HotPerfSession? = None,
hash_visits~ : Array[Int32]? = None,
normalization_visits~ : Array[Int32]? = None,
) -> DfeEliminationResult {
match (mod_.func_sec, mod_.code_sec) {
(Some(@lib.FuncSec(type_idxs)), Some(@lib.CodeSec(funcs))) => {
if type_idxs.length() != funcs.length() {
return {
mod_,
changed: false,
iteration_count: 0,
type_rewrite_flags: None,
cleanup_flags: None,
}
}
let type_canon_timer = dfe_perf_start_timer_opt(perf, "type-canon")
let type_canon = match mod_.type_sec {
Some(@lib.TypeSec(rec_types)) =>
dfe_duplicate_simple_type_canonical_map(rec_types)
None => Map([])
}
dfe_perf_stop_timer_opt(perf, "type-canon", type_canon_timer)
let imported_count = dfe_imported_func_count(mod_)
let identity_visible = dfe_identity_visible_defined_funcs(
mod_,
imported_count,
funcs.length(),
)
let annotations_by_abs_idx = dfe_func_annotation_map(mod_)
let has_annotations = annotations_by_abs_idx.length() > 0
let normalized_funcs : Array[@lib.Func?] = Array::make(
funcs.length(),
None,
)
let normalized_ready = Array::make(funcs.length(), false)
let normalized_rewrite_count = [0]
let canonical_type_raws : Array[UInt32] = Array::new(
capacity=type_idxs.length(),
)
let canonical_type_hashes : Array[Int32] = Array::new(
capacity=type_idxs.length(),
)
for type_idx in type_idxs {
let canonical_raw = match type_idx {
@lib.TypeIdx(raw) => type_canon.get(raw).unwrap_or(raw)
@lib.RecIdx(raw) => raw
}
canonical_type_raws.push(canonical_raw)
canonical_type_hashes.push(canonical_raw.hash())
}
let normalized_annotations : Array[Array[@lib.FuncAnnotation]] = Array::new(
capacity=funcs.length(),
)
if has_annotations {
for local_idx = 0; local_idx < funcs.length(); local_idx = local_idx + 1 {
normalized_annotations.push(
dfe_func_annotations(
annotations_by_abs_idx,
(imported_count + local_idx).reinterpret_as_uint(),
),
)
}
}
let hash_group_timer = dfe_perf_start_timer_opt(perf, "hash-groups")
let first_by_hash : @hashmap.HashMap[Int32, Int32] = @hashmap.new(
capacity=funcs.length(),
)
let collision_group_by_hash : @hashmap.HashMap[Int32, Int32] = @hashmap.new(
capacity=funcs.length() / 8 + 1,
)
let hash_groups : Array[Array[Int32]] = []
let original_type_rewrite_flags = @bitset.BitSet::new(funcs.length())
let original_cleanup_flags = @bitset.BitSet::new(funcs.length())
let max_func_target_by_local : Array[Int32] = []
for local_idx = 0; local_idx < funcs.length(); local_idx = local_idx + 1 {
match hash_visits {
Some(visits) => visits[0] += 1
None => ()
}
let mentions_type_idxs = [false]
let needs_debris_cleanup = [false]
let max_func_target = [-1]
let key = if has_annotations {
dfe_function_shape_hash(
canonical_type_hashes[local_idx],
funcs[local_idx],
normalized_annotations[local_idx],
mentions_type_idxs=Some(mentions_type_idxs),
needs_debris_cleanup=Some(needs_debris_cleanup),
max_func_target=Some(max_func_target),
)
} else {
dfe_function_shape_hash_no_annotations(
canonical_type_hashes[local_idx],
funcs[local_idx],
mentions_type_idxs=Some(mentions_type_idxs),
needs_debris_cleanup=Some(needs_debris_cleanup),
max_func_target=Some(max_func_target),
)
}
max_func_target_by_local.push(max_func_target[0])
if mentions_type_idxs[0] {
original_type_rewrite_flags[local_idx] = true
}
if needs_debris_cleanup[0] {
original_cleanup_flags[local_idx] = true
}
// Protected identities can be neither merged nor representatives.
// Keep their rewrite/cleanup facts above, but exclude them before
// normalization and collision grouping to avoid protected-only pair
// enumeration when presets admit modules with host boundaries.
if identity_visible[local_idx] {
continue
}
match first_by_hash.get(key) {
Some(first_idx) =>
match collision_group_by_hash.get(key) {
Some(group_idx) => hash_groups[group_idx].push(local_idx)
None => {
collision_group_by_hash.set(key, hash_groups.length())
hash_groups.push([first_idx, local_idx])
}
}
None => first_by_hash.set(key, local_idx)
}
}
dfe_perf_stop_timer_opt(perf, "hash-groups", hash_group_timer)
dfe_trace_opt(
perf,
"hash groups=\{hash_groups.length()} singleton_hashes=\{first_by_hash.length()}",
)
let replacements : Array[Int32] = Array::make(funcs.length(), -1)
let collision_timer = dfe_perf_start_timer_opt(perf, "collision-check")
let mut changed = false
let mut iteration_count = 0
let mut comparison_count = 0
let mut replacement_count = 0
while true {
let mut first_replaced_abs = -1
for local_idx = 0
local_idx < replacements.length()
local_idx = local_idx + 1 {
if replacements[local_idx] >= 0 {
first_replaced_abs = imported_count + local_idx
break
}
}
let canonical_abs = dfe_canonical_abs_remap(
replacements, imported_count,
)
let mut round_changed = false
for group in hash_groups {
if first_replaced_abs >= 0 {
let mut may_change = false
for local_idx in group {
if replacements[local_idx] < 0 &&
max_func_target_by_local[local_idx] >= first_replaced_abs {
may_change = true
break
}
}
// Unchanged function bodies were already compared in the prior
// round; they cannot form a new duplicate until a call target moves.
if !may_change {
continue
}
}
let live_indices : Array[Int32] = []
let live_funcs : Array[@lib.Func] = []
for local_idx in group {
if replacements[local_idx] < 0 {
live_indices.push(local_idx)
let normalized = dfe_type_normalized_candidate(
funcs, local_idx, type_canon, normalized_funcs, normalized_ready,
normalization_visits, normalized_rewrite_count,
)
live_funcs.push(
if first_replaced_abs < 0 ||
max_func_target_by_local[local_idx] < first_replaced_abs {
// Before the first replacement the map is the identity.
// Later epochs cannot change a target below its first delta.
normalized
} else {
dfe_rewrite_func_func_idxs(normalized, canonical_abs)
},
)
}
}
let comparison_groups : Array[Array[Int32]] = []
// A run of equal, mergeable members is cheaper to compare directly.
// The exact buckets help when one shape contains many distinct bodies.
let mut likely_duplicate_group = live_indices.length() >= 8
if likely_duplicate_group {
let first = live_indices[0]
if identity_visible[first] {
likely_duplicate_group = false
} else {
for sample_idx = 1
sample_idx < live_indices.length()
sample_idx = sample_idx + 1 {
let next = live_indices[sample_idx]
if identity_visible[next] ||
canonical_type_raws[first] != canonical_type_raws[next] ||
live_funcs[0] != live_funcs[sample_idx] ||
(
has_annotations &&
normalized_annotations[first] !=
normalized_annotations[next]
) {
likely_duplicate_group = false
break
}
}
}
}
if likely_duplicate_group {
let all_live : Array[Int32] = []
for live_idx = 0
live_idx < live_indices.length()
live_idx = live_idx + 1 {
all_live.push(live_idx)
}
comparison_groups.push(all_live)
} else {
let first_by_exact_hash : @hashmap.HashMap[Int32, Int32] = @hashmap.new(
capacity=live_indices.length(),
)
let collision_group_by_exact_hash : @hashmap.HashMap[Int32, Int32] = @hashmap.new()
for live_idx = 0
live_idx < live_indices.length()
live_idx = live_idx + 1 {
let local_idx = live_indices[live_idx]
let mut key = canonical_type_raws[local_idx].hash() * 31 +
live_funcs[live_idx].hash()
if has_annotations {
key = key * 31 + normalized_annotations[local_idx].hash()
}
match first_by_exact_hash.get(key) {
Some(first_idx) =>
match collision_group_by_exact_hash.get(key) {
Some(group_idx) =>
comparison_groups[group_idx].push(live_idx)
None => {
collision_group_by_exact_hash.set(
key,
comparison_groups.length(),
)
comparison_groups.push([first_idx, live_idx])
}
}
None => first_by_exact_hash.set(key, live_idx)
}
}
}
for comparison_group in comparison_groups {
let mut i = 0
while i + 1 < comparison_group.length() {
let first_live_idx = comparison_group[i]
let first = live_indices[first_live_idx]
if replacements[first] >= 0 {
i += 1
continue
}
let mut j = i + 1
while j < comparison_group.length() {
let second_live_idx = comparison_group[j]
let second = live_indices[second_live_idx]
if replacements[second] >= 0 {
j += 1
continue
}
// Exported and address-materialized functions have observable
// identities, including through host-visible tables and globals.
if identity_visible[first] || identity_visible[second] {
j += 1
continue
}
comparison_count += 1
let equivalent = if has_annotations {
dfe_functions_equivalent(
canonical_type_raws[first],
live_funcs[first_live_idx],
normalized_annotations[first],
canonical_type_raws[second],
live_funcs[second_live_idx],
normalized_annotations[second],
)
} else {
dfe_functions_equivalent_no_annotations(
canonical_type_raws[first],
live_funcs[first_live_idx],
canonical_type_raws[second],
live_funcs[second_live_idx],
)
}
if equivalent {
replacements[second] = first
changed = true
round_changed = true
replacement_count += 1
}
j += 1
}
i += 1
}
}
}
if !round_changed {
break
}
iteration_count += 1
}
dfe_perf_stop_timer_opt(perf, "collision-check", collision_timer)
dfe_trace_opt(
perf,
"normalize rewritten_funcs=\{normalized_rewrite_count[0]} total_funcs=\{funcs.length()}",
)
dfe_trace_opt(
perf,
"collision comparisons=\{comparison_count} replacements=\{replacement_count}",
)
if !changed {
return {
mod_,
changed: false,
iteration_count,
type_rewrite_flags: None,
cleanup_flags: None,
}
}
let rebuild_timer = dfe_perf_start_timer_opt(perf, "rebuild")
let kept_types : Array[@lib.TypeIdx] = Array::new(capacity=funcs.length())
let kept_funcs : Array[@lib.Func] = Array::new(capacity=funcs.length())
let kept_abs : Array[Int32] = Array::make(funcs.length(), -1)
let kept_original_indices : Array[Int32] = []
let kept_type_rewrite_flags = @bitset.BitSet::new(
funcs.length() - replacement_count,
)
let kept_cleanup_flags = @bitset.BitSet::new(
funcs.length() - replacement_count,
)
let kept_timer = dfe_perf_start_timer_opt(perf, "rebuild:kept")
let mut next_abs = imported_count
for idx = 0; idx < funcs.length(); idx = idx + 1 {
if replacements[idx] < 0 {
kept_abs[idx] = next_abs
kept_original_indices.push(idx)
kept_types.push(type_idxs[idx])
kept_funcs.push(funcs[idx])
if original_type_rewrite_flags[idx] {
kept_type_rewrite_flags[kept_funcs.length() - 1] = true
}
if original_cleanup_flags[idx] {
kept_cleanup_flags[kept_funcs.length() - 1] = true
}
next_abs += 1
}
}
dfe_perf_stop_timer_opt(perf, "rebuild:kept", kept_timer)
dfe_trace_opt(
perf,
"rebuild kept_funcs=\{kept_funcs.length()} removed_funcs=\{funcs.length() - kept_funcs.length()}",
)
let remap_timer = dfe_perf_start_timer_opt(perf, "rebuild:remap")
let abs_remap : Array[UInt32] = Array::new(
capacity=imported_count + funcs.length(),
)
for idx = 0; idx < imported_count; idx = idx + 1 {
abs_remap.push(idx.reinterpret_as_uint())
}
for idx = 0; idx < funcs.length(); idx = idx + 1 {
let canonical = dfe_canonical_local_idx(replacements, idx)
abs_remap.push(kept_abs[canonical].reinterpret_as_uint())
}
let mut first_removed_abs = -1
for local_idx = 0
local_idx < replacements.length()
local_idx = local_idx + 1 {
if replacements[local_idx] >= 0 {
first_removed_abs = imported_count + local_idx
break
}
}
let kept_func_rewrite_flags = @bitset.BitSet::new(kept_funcs.length())
let mut func_rewrite_count = 0
for kept_idx = 0
kept_idx < kept_original_indices.length()
kept_idx = kept_idx + 1 {
let original_idx = kept_original_indices[kept_idx]
if first_removed_abs >= 0 &&
max_func_target_by_local[original_idx] >= first_removed_abs {
kept_func_rewrite_flags[kept_idx] = true
func_rewrite_count += 1
}
}
dfe_perf_stop_timer_opt(perf, "rebuild:remap", remap_timer)
dfe_trace_opt(perf, "rebuild func_rewrite_count=\{func_rewrite_count}")
let compacted = mod_
.without_name_sec()
.with_func_sec(@lib.FuncSec::new(kept_types))
.with_code_sec(@lib.CodeSec::new(kept_funcs))
let rewrite_timer = dfe_perf_start_timer_opt(
perf, "rebuild:rewrite-module",
)
let rewritten = dfe_rewrite_module_func_idxs(
compacted,
abs_remap,
perf~,
code_rewrite_flags=Some(kept_func_rewrite_flags),
)
dfe_perf_stop_timer_opt(perf, "rebuild:rewrite-module", rewrite_timer)
dfe_perf_stop_timer_opt(perf, "rebuild", rebuild_timer)
{
mod_: rewritten,
changed: true,
iteration_count,
type_rewrite_flags: Some(kept_type_rewrite_flags),
cleanup_flags: Some(kept_cleanup_flags),
}
}
_ =>
{
mod_,
changed: false,
iteration_count: 0,
type_rewrite_flags: None,
cleanup_flags: None,
}
}
}
///|
fn dfe_instrs_need_unreachable_debris_cleanup(
instrs : Array[@lib.Instruction],
) -> Bool {
let mut idx = 0
while idx < instrs.length() {
match instrs[idx] {
@lib.Unreachable | @lib.Block(_, _) => {
let mut next = idx + 1
while next < instrs.length() {
match instrs[next] {
@lib.Drop => next += 1
_ => break
}
}
if next < instrs.length() && instrs[next] is @lib.Unreachable {
match instrs[idx] {
@lib.Unreachable => return true
@lib.Block(_, _) =>
if pass_raw_dropped_block_has_unreachable_tail(instrs[idx]) ||
pass_raw_void_block_can_inline_before_unreachable(instrs[idx]) {
return true
}
_ => ()
}
}
}
_ => ()
}
let nested = match instrs[idx] {
@lib.Block(_, @lib.Expr(body))
| @lib.Loop(_, @lib.Expr(body))
| @lib.TryTable(_, _, @lib.Expr(body)) =>
dfe_instrs_need_unreachable_debris_cleanup(body)
@lib.If(_, then_body, else_body) =>
if dfe_instrs_need_unreachable_debris_cleanup(then_body) {
true
} else {
match else_body {
Some(body) => dfe_instrs_need_unreachable_debris_cleanup(body)
None => false
}
}
_ => false
}
if nested {
return true
}
idx += 1
}
false
}
///|
fn dfe_cleanup_unreachable_debris(
mod_ : @lib.Module,
perf~ : HotPerfSession? = None,
cleanup_flags~ : @bitset.BitSet? = None,
) -> @lib.Module {
match mod_.code_sec {
Some(@lib.CodeSec(funcs)) => {
let next : Array[@lib.Func] = []
let mut changed = false
let mut changed_count = 0
for idx = 0; idx < funcs.length(); idx = idx + 1 {
let func = funcs[idx]
let @lib.Func(locals, @lib.Expr(instrs)) = func
let should_clean = match cleanup_flags {
Some(flags) if flags.length() == funcs.length() => flags[idx]
_ => dfe_instrs_need_unreachable_debris_cleanup(instrs)
}
if !should_clean {
next.push(func)
continue
}
let (next_instrs, func_changed) = pass_raw_remove_dropped_unreachable_debris(
instrs,
)
if func_changed {
changed = true
changed_count += 1
next.push(@lib.Func::new(locals, @lib.Expr::new(next_instrs)))
} else {
next.push(func)
}
}
if changed {
dfe_trace_opt(perf, "cleanup changed_funcs=\{changed_count}")
mod_.with_code_sec(@lib.CodeSec::new(next))
} else {
mod_
}
}
None => mod_
}
}
///|
fn dfe_run_module_pass_with_perf(
mod_ : @lib.Module,
perf : HotPerfSession?,
) -> @lib.Module {
if pass_module_has_offset_sensitive_metadata(mod_) {
return mod_
}
// Mixed GC modules require type canonicalization before private body
// hashing: otherwise equivalent aggregate/signature indices prevent merging.
// Keep the existing independent-function path and liveness-pruner guard.
let mod_ = match mod_.type_sec {
Some(@lib.TypeSec(rec_types)) if !dfe_supports_simple_type_dedup(rec_types) =>
dfe_canonicalize_duplicate_simple_type_indices(mod_, perf~)
_ => mod_
}
let iteration_timer = dfe_perf_start_timer_opt(perf, "iterations")
let elimination = dfe_eliminate_duplicate_functions(mod_, perf~)
let mut current = elimination.mod_
let changed = elimination.changed
dfe_perf_stop_timer_opt(perf, "iterations", iteration_timer)
dfe_trace_opt(perf, "iterations count=\{elimination.iteration_count}")
let cleanup_timer = dfe_perf_start_timer_opt(
perf, "cleanup-unreachable-debris",
)
current = dfe_cleanup_unreachable_debris(
current,
perf~,
cleanup_flags=elimination.cleanup_flags,
)
dfe_perf_stop_timer_opt(perf, "cleanup-unreachable-debris", cleanup_timer)
let elem_timer = dfe_perf_start_timer_opt(perf, "canonicalize-elem-segments")
let with_elem_segments = dfe_canonicalize_elem_segments(current)
let with_elem_segments = dfe_strip_name_sec(with_elem_segments)
dfe_perf_stop_timer_opt(perf, "canonicalize-elem-segments", elem_timer)
if !changed {
return dfe_cleanup_raw_encoding(with_elem_segments)
}
let type_timer = dfe_perf_start_timer_opt(
perf, "canonicalize-duplicate-types",
)
let rewritten = dfe_canonicalize_duplicate_simple_type_indices(
with_elem_segments,
perf~,
code_rewrite_flags=elimination.type_rewrite_flags,
)
dfe_perf_stop_timer_opt(perf, "canonicalize-duplicate-types", type_timer)
dfe_cleanup_raw_encoding(rewritten)
}
///|
pub fn dfe_run_module_pass(mod_ : @lib.Module) -> @lib.Module {
dfe_run_module_pass_with_perf(mod_, None)
}
///|
// Local grouping keeps its legacy control barrier and preserves parameter indices.
// Keep parameter indices stable and accept only an actual whole-module saving:
// fewer declaration runs can otherwise lose to wider local-index encodings.
fn dfe_group_numeric_locals_if_smaller(
mod_ : @lib.Module,
function_comparisons~ : Array[Int32]? = None,
) -> @lib.Module {
if mod_.code_sec is None || pass_module_has_legacy_try(mod_) {
return mod_
}
let grouped = run_hot_pipeline_raw_group_body_locals_by_type_module(mod_)
// The grouping helper returns its input unless a nonidentity local remap
// changed a body and its declarations; no body equality walk is needed.
ignore(function_comparisons)
if physical_equal(grouped, mod_) {
return mod_
}
match @binary.encoded_module_sizes(mod_, grouped) {
Ok((old_size, new_size)) if new_size < old_size =>
if @validate.validate_module(grouped) is Ok(_) {
return grouped
}
_ => ()
}
mod_
}
///|
fn dfe_cleanup_raw_encoding(mod_ : @lib.Module) -> @lib.Module {
if pass_module_has_offset_sensitive_metadata(mod_) {
return mod_
}
dfe_cleanup_simple_type_encoding(dfe_group_numeric_locals_if_smaller(mod_))
}
///|
fn dfe_cleanup_simple_type_encoding(
mod_ : @lib.Module,
full_size_checks~ : Array[Int32]? = None,
perf~ : HotPerfSession? = None,
) -> @lib.Module {
match
dfe_cleanup_simple_type_encoding_candidate(mod_, full_size_checks~, perf~) {
Some(candidate) =>
if @validate.validate_module(candidate) is Ok(_) {
candidate
} else {
mod_
}
None => mod_
}
}
///|
// Build only the guarded, strictly smaller candidate. Validation belongs to
// its caller so a transaction can validate its exact final module once.
fn dfe_cleanup_simple_type_encoding_candidate(
mod_ : @lib.Module,
full_size_comparisons~ : Array[Int32]? = None,
full_size_checks~ : Array[Int32]? = None,
body_size_proofs~ : Array[Int32]? = None,
perf~ : HotPerfSession? = None,
) -> @lib.Module? {
if mod_.name_sec is None && mod_.raw_name_sec_payload is Some(_) {
return None
}
let grouped = mod_
let types = match grouped.type_sec {
Some(@lib.TypeSec(types)) => types
None => return None
}
// The type walker includes legacy bodies and catches. Restrict this remapper
// to independent unshared function signatures; GC/proposal type graphs keep
// their definitions until their complete reference surfaces are supported.
for rec_type in types {
match rec_type {
@lib.SingleRecType(subtype) =>
if subtype.metadata().is_shared() ||
!dfe_rec_type_is_unused_suffix_prune_candidate(rec_type) {
return None
}
@lib.GroupRecType(_) => return None
}
}
// Distinct functions can still use equivalent signatures. The elimination
// fast path skips type canonicalization, so intern live plain signatures here
// as well as pruning dead definitions, under the same complete type guard.
let intern_start = perf_start_timer_opt(
perf, "detail:simplify-locals:encoding-type-intern",
)
let interned = dfe_canonicalize_duplicate_simple_type_indices(grouped, perf~)
perf_stop_timer_opt(
perf, "detail:simplify-locals:encoding-type-intern", intern_start,
)
let prune_start = perf_start_timer_opt(
perf, "detail:simplify-locals:encoding-type-prune",
)
let pruned = dfe_prune_unused_simple_types(interned)
perf_stop_timer_opt(
perf, "detail:simplify-locals:encoding-type-prune", prune_start,
)
if pruned.type_sec.unwrap().0.length() == types.length() {
return None
}
// Both remappers retain types in order and map duplicate types to earlier
// entries. Every rewritten type index is therefore no greater than its old
// index, so its LEB width cannot grow in any other section. Type names only
// lose entries or use smaller indices. Check the sole section that can make
// the decision without encoding and validating the entire code section.
let before_types = @lib.Module::new().with_type_sec(@lib.TypeSec::new(types))
let after_types = @lib.Module::new().with_type_sec(pruned.type_sec.unwrap())
let section_size_start = perf_start_timer_opt(
perf, "detail:simplify-locals:encoding-type-size",
)
let type_sizes = @binary.encoded_module_sizes(before_types, after_types)
perf_stop_timer_opt(
perf, "detail:simplify-locals:encoding-type-size", section_size_start,
)
match type_sizes {
Ok((before, after)) if after < before => return Some(pruned)
_ => ()
}
// The type-only acceptance above already selected this same candidate
// after an inconclusive body proof. Try it first to avoid walking every
// instruction when the strictly smaller type section decides the result.
// Keep the original body proof and full encoding for inconclusive cases.
match body_size_proofs {
Some(proofs) => proofs[0] += 1
None => ()
}
if @binary.encoded_type_compaction_is_strictly_smaller(grouped, pruned) {
return Some(pruned)
}
if full_size_comparisons is Some(count) {
count[0] += 1
}
// Keep the original guarded path if section-only encoding cannot decide.
match full_size_checks {
Some(checks) => checks[0] += 1
None => ()
}
match @binary.encoded_module_sizes(grouped, pruned) {
Ok((before, after)) if after < before => return Some(pruned)
_ => ()
}
None
}
///|
test "type encoding cleanup preserves live signatures and bytes without a whole body size proof" {
let input = pass_test_module_from_wat(
(
#|(module
#| (type $first (func (param i32) (result i32)))
#| (type $duplicate (func (param i32) (result i32)))
#| (func $first (type $first) (param i32) (result i32) local.get 0)
#| (func (export "run") (type $duplicate) (param i32) (result i32)
#| local.get 0 call $first))
),
)
@validate.validate_module(input).unwrap()
let saved = @binary.encode_module(input).unwrap()
let expected = dfe_prune_unused_simple_types(
dfe_canonicalize_duplicate_simple_type_indices(input),
)
let proofs = [0]
let output = dfe_cleanup_simple_type_encoding_candidate(
input,
body_size_proofs=Some(proofs),
).unwrap()
@validate.validate_module(output).unwrap()
assert_eq(output.type_sec.unwrap().0.length(), 1)
assert_eq(output.func_sec.unwrap().0, [
@lib.TypeIdx::new(0U),
@lib.TypeIdx::new(0U),
])
assert_eq(output.export_sec, input.export_sec)
assert_eq(
@binary.encode_module(output).unwrap(),
@binary.encode_module(expected).unwrap(),
)
assert_true(@binary.encode_module(output).unwrap().length() < saved.length())
let @lib.Func(_, @lib.Expr(body)) = output.code_sec.unwrap().0[1]
assert_eq(body, [
@lib.Instruction::local_get(@lib.LocalIdx::new(0U)),
@lib.Instruction::call(@lib.FuncIdx::new(0U)),
])
assert_eq(@binary.encode_module(input).unwrap(), saved)
assert_eq(proofs[0], 0)
}