// 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 2019 WebAssembly Community Group participants
// Counterpart: src/passes/Directize.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.
///|
priv enum DirectizeTarget {
DirectizeKnown(@lib.FuncIdx)
DirectizeTrap
DirectizeUnknown
}
///|
priv enum DirectizeTableEntry {
DirectizeEntryFunc(@lib.FuncIdx)
DirectizeEntryNull
}
///|
priv enum DirectizeTableDefault {
DirectizeDefaultFunc(@lib.FuncIdx)
DirectizeDefaultNull
DirectizeDefaultUnknown
}
///|
priv struct DirectizeTableInfo {
can_optimize : Bool
has_grow : Bool
initial_size : UInt64
entries : @hashmap.HashMap[UInt64, DirectizeTableEntry]
default_entry : DirectizeTableDefault
}
///|
priv struct DirectizeBlockSignature {
results : Array[@lib.ValType]
type_idx : @lib.TypeIdx
}
///|
priv struct DirectizeInfo {
env : @validate.Env
func_types : Array[@lib.TypeIdx]
imported_func_count : Int32
tables : Array[DirectizeTableInfo]
base_type_count : Int32
block_signatures : Array[DirectizeBlockSignature]
extra_block_types : Array[@lib.RecType]
}
///|
priv struct DirectizeFuncContext {
info : DirectizeInfo
base_local_count : Int32
locals : @lib.Locals
extra_locals : Array[@lib.ValType]
original_label_count : Array[Int32]
inserted_label_before : Array[Int32]
}
///|
priv struct DirectizeSelectRewrite {
arg_start : Int32
arg_instrs : Array[@lib.Instruction]
condition_instrs : Array[@lib.Instruction]
params : Array[@lib.ValType]
results : Array[@lib.ValType]
has_stack_prefix : Bool
is_return : Bool
then_target : DirectizeTarget
else_target : DirectizeTarget
}
///|
priv struct DirectizeProducerValue {
ty : @lib.ValType
start : Int32
}
///|
fn directize_summary() -> String {
"Rewrite provably known indirect calls through immutable table facts into direct calls, traps, or select-lowered branches."
}
///|
fn directize_table_slot(table_idx : @lib.TableIdx) -> Int32 {
match table_idx {
@lib.TableIdx(raw) => raw.reinterpret_as_int()
}
}
///|
fn directize_func_slot(func_idx : @lib.FuncIdx) -> Int32 {
match func_idx {
@lib.FuncIdx(raw) => raw.reinterpret_as_int()
}
}
///|
fn directize_const_table_offset(expr : @lib.Expr) -> UInt64? {
match expr {
@lib.Expr([@lib.I32Const(@lib.I32(value))]) =>
Some(value.reinterpret_as_uint().to_uint64())
@lib.Expr([@lib.I64Const(@lib.I64(value))]) =>
Some(value.reinterpret_as_uint64())
_ => None
}
}
///|
fn directize_func_from_elem_expr(expr : @lib.Expr) -> @lib.FuncIdx?? {
match expr {
@lib.Expr([@lib.RefFunc(func_idx)]) => Some(Some(func_idx))
@lib.Expr([@lib.RefNull(_)]) => Some(None)
_ => None
}
}
///|
fn directize_imported_table_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.TableExternType(_)) => count += 1
_ => ()
}
}
None => ()
}
count
}
///|
fn directize_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 directize_table_types(mod_ : @lib.Module) -> Array[@lib.TableType] {
let tables : Array[@lib.TableType] = []
match mod_.import_sec {
Some(@lib.ImportSec(imports)) =>
for import_ in imports {
match import_ {
@lib.Import(_, _, @lib.TableExternType(table_type)) =>
tables.push(table_type)
_ => ()
}
}
None => ()
}
match mod_.table_sec {
Some(@lib.TableSec(defs)) =>
for table in defs {
match table {
@lib.Table(table_type, _) => tables.push(table_type)
}
}
None => ()
}
tables
}
///|
fn directize_table_initial_size(table_type : @lib.TableType) -> UInt64 {
match table_type {
@lib.TableType(_, @lib.I32Limits(initial, _)) => initial.to_uint64()
@lib.TableType(_, @lib.I64Limits(initial, _)) => initial
}
}
///|
fn directize_func_types(mod_ : @lib.Module) -> Array[@lib.TypeIdx] {
let types : Array[@lib.TypeIdx] = []
match mod_.import_sec {
Some(@lib.ImportSec(imports)) =>
for import_ in imports {
match import_ {
@lib.Import(_, _, @lib.FuncExternType(type_idx)) =>
types.push(type_idx)
_ => ()
}
}
None => ()
}
match mod_.func_sec {
Some(@lib.FuncSec(type_idxs)) =>
for type_idx in type_idxs {
types.push(type_idx)
}
None => ()
}
types
}
///|
fn directize_mark_table(
tables : Array[Bool],
table_idx : @lib.TableIdx,
value : Bool,
) -> Unit {
let slot = directize_table_slot(table_idx)
if slot >= 0 && slot < tables.length() {
tables[slot] = value
}
}
///|
fn directize_mark_runtime_table_instr(
mutated : Array[Bool],
grown : Array[Bool],
instr : @lib.Instruction,
) -> Unit {
match instr {
@lib.Block(_, @lib.Expr(body)) | @lib.Loop(_, @lib.Expr(body)) =>
for nested in body {
directize_mark_runtime_table_instr(mutated, grown, nested)
}
@lib.If(_, then_body, else_body) => {
for nested in then_body {
directize_mark_runtime_table_instr(mutated, grown, nested)
}
match else_body {
Some(body) =>
for nested in body {
directize_mark_runtime_table_instr(mutated, grown, nested)
}
None => ()
}
}
@lib.Try(_, @lib.Expr(body), catches, _) => {
for nested in body {
directize_mark_runtime_table_instr(mutated, grown, nested)
}
for catch_ in catches {
match catch_ {
@lib.LegacyCatch(_, @lib.Expr(catch_body))
| @lib.LegacyCatchAll(@lib.Expr(catch_body)) =>
for nested in catch_body {
directize_mark_runtime_table_instr(mutated, grown, nested)
}
}
}
}
@lib.TryTable(_, _, @lib.Expr(body)) =>
for nested in body {
directize_mark_runtime_table_instr(mutated, grown, nested)
}
@lib.TableGrow(table_idx) => directize_mark_table(grown, table_idx, true)
@lib.TableSet(table_idx)
| @lib.TableFill(table_idx)
| @lib.TableInit(_, table_idx) =>
directize_mark_table(mutated, table_idx, true)
@lib.TableCopy(dst, _) => directize_mark_table(mutated, dst, true)
_ => ()
}
}
///|
fn directize_runtime_table_facts(
mod_ : @lib.Module,
table_count : Int32,
) -> (Array[Bool], Array[Bool]) {
let mutated = Array::make(table_count, false)
let grown = Array::make(table_count, false)
match mod_.code_sec {
Some(@lib.CodeSec(funcs)) =>
for func in funcs {
let @lib.Func(_, @lib.Expr(instrs)) = func
for instr in instrs {
directize_mark_runtime_table_instr(mutated, grown, instr)
}
}
None => ()
}
(mutated, grown)
}
///|
fn directize_base_table_infos(mod_ : @lib.Module) -> Array[DirectizeTableInfo] {
let table_types = directize_table_types(mod_)
let imported_tables = directize_imported_table_count(mod_)
let (mutated, grown) = directize_runtime_table_facts(
mod_,
table_types.length(),
)
let exported = Array::make(table_types.length(), false)
match mod_.export_sec {
Some(@lib.ExportSec(exports)) =>
for export_ in exports {
match export_ {
@lib.Export(_, @lib.TableExternIdx(table_idx)) =>
directize_mark_table(exported, table_idx, true)
_ => ()
}
}
None => ()
}
let infos : Array[DirectizeTableInfo] = []
for idx = 0; idx < table_types.length(); idx = idx + 1 {
infos.push({
can_optimize: idx >= imported_tables && !mutated[idx] && !exported[idx],
has_grow: grown[idx],
initial_size: directize_table_initial_size(table_types[idx]),
entries: @hashmap.new(),
default_entry: DirectizeDefaultNull,
})
}
infos
}
///|
fn directize_disable_table(
tables : Array[DirectizeTableInfo],
table_slot : Int32,
) -> Unit {
if table_slot >= 0 && table_slot < tables.length() {
tables[table_slot] = { ..tables[table_slot], can_optimize: false, }
}
}
///|
fn directize_write_table_entry(
tables : Array[DirectizeTableInfo],
table_slot : Int32,
offset : UInt64,
value : DirectizeTableEntry,
) -> Unit {
if table_slot < 0 || table_slot >= tables.length() {
return
}
let info = tables[table_slot]
if !info.can_optimize || offset >= info.initial_size {
directize_disable_table(tables, table_slot)
return
}
info.entries.set(offset, value)
}
///|
fn directize_record_elem_funcs(
tables : Array[DirectizeTableInfo],
table_slot : Int32,
base : UInt64,
funcs : Array[@lib.FuncIdx],
) -> Unit {
let mut rel = 0UL
for func_idx in funcs {
if base > 0xFFFF_FFFF_FFFF_FFFFUL - rel {
directize_disable_table(tables, table_slot)
return
}
directize_write_table_entry(
tables,
table_slot,
base + rel,
DirectizeEntryFunc(func_idx),
)
rel += 1UL
}
}
///|
fn directize_record_elem_exprs(
tables : Array[DirectizeTableInfo],
table_slot : Int32,
base : UInt64,
exprs : Array[@lib.Expr],
) -> Unit {
let mut rel = 0UL
for expr in exprs {
if base > 0xFFFF_FFFF_FFFF_FFFFUL - rel {
directize_disable_table(tables, table_slot)
return
}
match directize_func_from_elem_expr(expr) {
Some(Some(func_idx)) =>
directize_write_table_entry(
tables,
table_slot,
base + rel,
DirectizeEntryFunc(func_idx),
)
Some(None) =>
directize_write_table_entry(
tables,
table_slot,
base + rel,
DirectizeEntryNull,
)
None => directize_disable_table(tables, table_slot)
}
rel += 1UL
}
}
///|
fn directize_apply_table_initializers(
mod_ : @lib.Module,
tables : Array[DirectizeTableInfo],
) -> Unit {
let imported_tables = directize_imported_table_count(mod_)
match mod_.table_sec {
Some(@lib.TableSec(defs)) => {
let mut def_idx = 0
for table in defs {
let slot = imported_tables + def_idx
if slot >= 0 && slot < tables.length() && tables[slot].can_optimize {
let @lib.Table(_, init) = table
tables[slot] = {
..tables[slot],
default_entry: match init {
Some(@lib.Expr([@lib.RefFunc(func_idx)])) =>
DirectizeDefaultFunc(func_idx)
None => DirectizeDefaultNull
Some(_) => DirectizeDefaultUnknown
},
}
}
def_idx += 1
}
}
None => ()
}
}
///|
fn directize_populate_table_infos(
mod_ : @lib.Module,
tables : Array[DirectizeTableInfo],
) -> Unit {
match mod_.elem_sec {
Some(@lib.ElemSec(elems)) =>
for elem in elems {
match elem {
@lib.Elem(@lib.Active(table_idx, offset_expr), kind) => {
let table_slot = directize_table_slot(table_idx)
match directize_const_table_offset(offset_expr) {
Some(base) =>
match kind {
@lib.FuncsElemKind(funcs) =>
directize_record_elem_funcs(tables, table_slot, base, funcs)
@lib.FuncExprsElemKind(exprs)
| @lib.TypedExprsElemKind(_, exprs) =>
directize_record_elem_exprs(tables, table_slot, base, exprs)
}
None => directize_disable_table(tables, table_slot)
}
}
_ => ()
}
}
None => ()
}
}
///|
fn directize_type_count(mod_ : @lib.Module) -> Int32 {
match mod_.type_sec {
Some(@lib.TypeSec(rec_types)) => {
let mut count = 0
for rec_type in rec_types {
count += match rec_type {
@lib.SingleRecType(_) => 1
@lib.GroupRecType(subtypes) => subtypes.length()
}
}
count
}
None => 0
}
}
///|
fn directize_block_signatures(
mod_ : @lib.Module,
) -> Array[DirectizeBlockSignature] {
let signatures : Array[DirectizeBlockSignature] = []
match mod_.type_sec {
Some(@lib.TypeSec(rec_types)) => {
let mut flat_idx = 0
for rec_type in rec_types {
let subtypes = match rec_type {
@lib.SingleRecType(subtype) => [subtype]
@lib.GroupRecType(group) => group
}
for subtype in subtypes {
match subtype.get_comptype() {
@lib.FuncCompType(params, results) if params.length() == 0 =>
signatures.push({
results,
type_idx: @lib.TypeIdx::new(flat_idx.reinterpret_as_uint()),
})
_ => ()
}
flat_idx += 1
}
}
}
None => ()
}
signatures
}
///|
fn directize_build_info(mod_ : @lib.Module) -> DirectizeInfo {
let tables = directize_base_table_infos(mod_)
directize_apply_table_initializers(mod_, tables)
directize_populate_table_infos(mod_, tables)
{
env: @validate.Env::new().with_module(mod_),
func_types: directize_func_types(mod_),
imported_func_count: directize_imported_func_count(mod_),
tables,
base_type_count: directize_type_count(mod_),
block_signatures: directize_block_signatures(mod_),
extra_block_types: [],
}
}
///|
fn directize_func_type(
info : DirectizeInfo,
func_idx : @lib.FuncIdx,
) -> @lib.TypeIdx? {
let slot = directize_func_slot(func_idx)
if slot >= 0 && slot < info.func_types.length() {
Some(info.func_types[slot])
} else {
None
}
}
///|
fn directize_func_type_matches(
info : DirectizeInfo,
func_idx : @lib.FuncIdx,
call_type : @lib.TypeIdx,
) -> Bool {
match directize_func_type(info, func_idx) {
Some(type_idx) =>
@validate.Match::matches(
@lib.ExternType::func(type_idx),
@lib.ExternType::func(call_type),
info.env,
)
None => false
}
}
///|
fn directize_classify_func(
info : DirectizeInfo,
func_idx : @lib.FuncIdx,
call_type : @lib.TypeIdx,
) -> DirectizeTarget {
if directize_func_type_matches(info, func_idx, call_type) {
DirectizeKnown(func_idx)
} else if directize_func_slot(func_idx) < info.imported_func_count &&
directize_import_may_have_subtype(info, func_idx) {
// The imported function address may carry a runtime subtype of its
// declared import type. Retain the dynamic check when it matters.
DirectizeUnknown
} else {
DirectizeTrap
}
}
///|
fn directize_import_may_have_subtype(
info : DirectizeInfo,
func_idx : @lib.FuncIdx,
) -> Bool {
match directize_func_type(info, func_idx) {
Some(type_idx) =>
match info.env.resolve_subtype(type_idx) {
Some(@lib.SubType(final_, _, _, _)) => !final_
Some(@lib.CompTypeSubType(_, _)) => false
None => true
}
None => true
}
}
///|
fn directize_classify_target(
info : DirectizeInfo,
table_idx : @lib.TableIdx,
offset : UInt64,
call_type : @lib.TypeIdx,
) -> DirectizeTarget {
let table_slot = directize_table_slot(table_idx)
if table_slot < 0 || table_slot >= info.tables.length() {
return DirectizeUnknown
}
let table_info = info.tables[table_slot]
if !table_info.can_optimize {
return DirectizeUnknown
}
match table_info.entries.get(offset) {
Some(DirectizeEntryFunc(func_idx)) =>
return directize_classify_func(info, func_idx, call_type)
Some(DirectizeEntryNull) => return DirectizeTrap
None => ()
}
if offset >= table_info.initial_size {
return if table_info.has_grow { DirectizeUnknown } else { DirectizeTrap }
}
match table_info.default_entry {
DirectizeDefaultFunc(func_idx) =>
directize_classify_func(info, func_idx, call_type)
DirectizeDefaultNull => DirectizeTrap
DirectizeDefaultUnknown => DirectizeUnknown
}
}
///|
fn directize_target_const(instr : @lib.Instruction) -> UInt64? {
match instr {
@lib.I32Const(@lib.I32(value)) =>
Some(value.reinterpret_as_uint().to_uint64())
@lib.I64Const(@lib.I64(value)) => Some(value.reinterpret_as_uint64())
_ => None
}
}
///|
fn directize_call_param_types(
info : DirectizeInfo,
type_idx : @lib.TypeIdx,
) -> Array[@lib.ValType]? {
match info.env.resolve_functype(type_idx) {
Some(@lib.FuncType(params, _)) => Some(params)
None => None
}
}
///|
fn directize_call_signature(
info : DirectizeInfo,
type_idx : @lib.TypeIdx,
) -> (Array[@lib.ValType], Array[@lib.ValType])? {
match info.env.resolve_functype(type_idx) {
Some(@lib.FuncType(params, results)) => Some((params, results))
None => None
}
}
///|
fn directize_instr_is_removable_if_trapped(instr : @lib.Instruction) -> Bool {
match instr {
@lib.I32Const(_) | @lib.I64Const(_) | @lib.F32Const(_) | @lib.F64Const(_) =>
true
@lib.LocalGet(_) => true
@lib.GlobalGet(_) => true
@lib.RefNull(_) | @lib.RefFunc(_) => true
_ => false
}
}
///|
fn directize_removable_suffix_lower_bound(
out : Array[@lib.Instruction],
end : Int32,
) -> Int32 {
let mut lower = end
while lower > 0 && directize_instr_is_removable_if_trapped(out[lower - 1]) {
lower -= 1
}
lower
}
///|
fn directize_stack_matches(
actual : Array[@lib.ValType],
expected : Array[@lib.ValType],
env : @validate.Env,
) -> Bool {
if actual.length() != expected.length() {
return false
}
@validate.Match::matches(actual, expected, env)
}
///|
fn directize_suffix_produces_params(
suffix : Array[@lib.Instruction],
params : Array[@lib.ValType],
info : DirectizeInfo,
) -> Bool {
match
@validate.Typecheck::typecheck(
@lib.Expr::new(suffix),
@validate.make_state(info.env, []),
) {
Ok(state) =>
state.reachable &&
state.escape == @validate.tc_escape_none() &&
directize_stack_matches(state.stack, params, info.env)
Err(_) => false
}
}
///|
fn directize_find_removable_arg_suffix(
out : Array[@lib.Instruction],
params : Array[@lib.ValType],
info : DirectizeInfo,
) -> Int32? {
let end = out.length() - 1
if end < params.length() {
return None
}
// Every removable instruction consumes no operands and produces one value.
// Only this boundary can produce exactly the call's argument count. Retain
// the validator check for types, subtyping, and local/reference validity.
let start = end - params.length()
for idx = start; idx < end; idx = idx + 1 {
if !directize_instr_is_removable_if_trapped(out[idx]) {
return None
}
}
let suffix = directize_instr_slice(out, start, end)
if directize_suffix_produces_params(suffix, params, info) {
Some(start)
} else {
None
}
}
///|
fn directize_truncate_out(
out : Array[@lib.Instruction],
new_length : Int32,
) -> Unit {
while out.length() > new_length {
ignore(out.pop())
}
}
///|
fn directize_instr_slice(
instrs : Array[@lib.Instruction],
start : Int32,
end : Int32,
) -> Array[@lib.Instruction] {
let out : Array[@lib.Instruction] = []
for idx = start; idx < end; idx = idx + 1 {
out.push(instrs[idx])
}
out
}
///|
fn directize_allocate_local(
ctx : DirectizeFuncContext,
vt : @lib.ValType,
) -> @lib.LocalIdx {
let slot = ctx.base_local_count + ctx.extra_locals.length()
ctx.locals.push(vt)
ctx.extra_locals.push(vt)
@lib.LocalIdx::new(slot.reinterpret_as_uint())
}
///|
fn directize_blocktype_for_results(
results : Array[@lib.ValType],
info : DirectizeInfo,
) -> @lib.BlockType {
if results.length() == 0 {
@lib.BlockType::void_()
} else if results.length() == 1 {
@lib.BlockType::val_type(results[0])
} else {
for signature in info.block_signatures {
if signature.results == results {
return @lib.BlockType::type_idx(signature.type_idx)
}
}
let type_idx = @lib.TypeIdx::new(
(info.base_type_count + info.extra_block_types.length()).reinterpret_as_uint(),
)
info.extra_block_types.push(
@lib.RecType::new(
@lib.SubType::comp_type(@lib.CompType::func([], results)),
),
)
info.block_signatures.push({ results, type_idx, })
@lib.BlockType::type_idx(type_idx)
}
}
///|
fn directize_push_trap_replacement(
out : Array[@lib.Instruction],
params : Array[@lib.ValType],
info : DirectizeInfo,
) -> Unit {
match directize_find_removable_arg_suffix(out, params, info) {
Some(start) => directize_truncate_out(out, start)
None => {
ignore(out.pop())
for _i = 0; _i < params.length(); _i = _i + 1 {
out.push(@lib.Instruction::drop())
}
}
}
out.push(@lib.Instruction::unreachable_())
}
///|
fn directize_rewrite_nested_instr(
instr : @lib.Instruction,
ctx : DirectizeFuncContext,
) -> (@lib.Instruction, Bool) {
match instr {
@lib.Block(block_type, @lib.Expr(body)) => {
let (next_body, changed) = directize_rewrite_instrs(body, ctx)
if changed {
(@lib.Instruction::block(block_type, @lib.Expr::new(next_body)), true)
} else {
(instr, false)
}
}
@lib.Loop(block_type, @lib.Expr(body)) => {
let (next_body, changed) = directize_rewrite_instrs(body, ctx)
if changed {
(@lib.Instruction::loop_(block_type, @lib.Expr::new(next_body)), true)
} else {
(instr, false)
}
}
@lib.If(block_type, then_body, else_body) => {
let (next_then, then_changed) = directize_rewrite_instrs(then_body, ctx)
let (next_else, else_changed) = match else_body {
Some(body) => {
let (next, changed) = directize_rewrite_instrs(body, ctx)
(Some(next), changed)
}
None => (None, false)
}
if then_changed || else_changed {
(@lib.Instruction::if_(block_type, next_then, next_else), true)
} else {
(instr, false)
}
}
@lib.Try(block_type, @lib.Expr(body), catches, delegate) => {
let (next_body, body_changed) = directize_rewrite_instrs(body, ctx)
let next_catches : Array[@lib.LegacyCatch] = []
let mut catches_changed = false
for catch_ in catches {
match catch_ {
@lib.LegacyCatch(tag_idx, @lib.Expr(catch_body)) => {
let (next_catch, changed) = directize_rewrite_instrs(
catch_body, ctx,
)
if changed {
catches_changed = true
}
next_catches.push(
@lib.LegacyCatch::tagged(tag_idx, @lib.Expr::new(next_catch)),
)
}
@lib.LegacyCatchAll(@lib.Expr(catch_body)) => {
let (next_catch, changed) = directize_rewrite_instrs(
catch_body, ctx,
)
if changed {
catches_changed = true
}
next_catches.push(@lib.LegacyCatch::all(@lib.Expr::new(next_catch)))
}
}
}
if body_changed || catches_changed {
(
@lib.Instruction::try_(
block_type,
@lib.Expr::new(next_body),
next_catches,
delegate~,
),
true,
)
} else {
(instr, false)
}
}
@lib.TryTable(block_type, catches, @lib.Expr(body)) => {
let (next_body, changed) = directize_rewrite_instrs(body, ctx)
if changed {
(
@lib.Instruction::try_table(
block_type,
catches,
@lib.Expr::new(next_body),
),
true,
)
} else {
(instr, false)
}
}
_ => (instr, false)
}
}
///|
fn directize_stack_suffix(
stack : Array[@lib.ValType],
count : Int32,
) -> Array[@lib.ValType] {
let suffix : Array[@lib.ValType] = Array::new(capacity=count)
let start = stack.length() - count
for idx = start; idx < stack.length(); idx = idx + 1 {
suffix.push(stack[idx])
}
suffix
}
///|
fn directize_candidate_after_matches(
before_stack : Array[@lib.ValType],
after : @validate.TcState,
pop_count : Int32,
candidate_after : @validate.TcState,
) -> Bool {
if candidate_after.reachable != after.reachable ||
candidate_after.escape != after.escape {
return false
}
let prefix_len = before_stack.length() - pop_count
if prefix_len + candidate_after.stack.length() != after.stack.length() {
return false
}
for idx = 0; idx < prefix_len; idx = idx + 1 {
if before_stack[idx] != after.stack[idx] {
return false
}
}
for idx = 0; idx < candidate_after.stack.length(); idx = idx + 1 {
if candidate_after.stack[idx] != after.stack[prefix_len + idx] {
return false
}
}
true
}
///|
fn directize_fast_pop_count(instr : @lib.Instruction) -> Int32? {
match instr {
@lib.Nop
| @lib.I32Const(_)
| @lib.I64Const(_)
| @lib.F32Const(_)
| @lib.F64Const(_)
| @lib.StringConst(_)
| @lib.LocalGet(_)
| @lib.GlobalGet(_)
| @lib.MemorySize(_)
| @lib.TableSize(_)
| @lib.RefNull(_)
| @lib.RefFunc(_)
| @lib.DataDrop(_)
| @lib.ElemDrop(_) => Some(0)
@lib.LocalSet(_)
| @lib.LocalTee(_)
| @lib.GlobalSet(_)
| @lib.TableGet(_)
| @lib.MemoryGrow(_)
| @lib.Drop
| @lib.RefIsNull
| @lib.I32Eqz
| @lib.I64Eqz
| @lib.I32Clz
| @lib.I32Ctz
| @lib.I32Popcnt
| @lib.I64Clz
| @lib.I64Ctz
| @lib.I64Popcnt
| @lib.F32Abs
| @lib.F32Neg
| @lib.F32Ceil
| @lib.F32Floor
| @lib.F32Trunc
| @lib.F32Nearest
| @lib.F32Sqrt
| @lib.F64Abs
| @lib.F64Neg
| @lib.F64Ceil
| @lib.F64Floor
| @lib.F64Trunc
| @lib.F64Nearest
| @lib.F64Sqrt
| @lib.I32WrapI64
| @lib.I32TruncF32S
| @lib.I32TruncF32U
| @lib.I32TruncF64S
| @lib.I32TruncF64U
| @lib.I64ExtendI32S
| @lib.I64ExtendI32U
| @lib.I64TruncF32S
| @lib.I64TruncF32U
| @lib.I64TruncF64S
| @lib.I64TruncF64U
| @lib.F32ConvertI32S
| @lib.F32ConvertI32U
| @lib.F32ConvertI64S
| @lib.F32ConvertI64U
| @lib.F32DemoteF64
| @lib.F64ConvertI32S
| @lib.F64ConvertI32U
| @lib.F64ConvertI64S
| @lib.F64ConvertI64U
| @lib.F64PromoteF32
| @lib.I32ReinterpretF32
| @lib.I64ReinterpretF64
| @lib.F32ReinterpretI32
| @lib.F64ReinterpretI64
| @lib.I32Extend8S
| @lib.I32Extend16S
| @lib.I64Extend8S
| @lib.I64Extend16S
| @lib.I64Extend32S
| @lib.I32TruncSatF32S
| @lib.I32TruncSatF32U
| @lib.I32TruncSatF64S
| @lib.I32TruncSatF64U
| @lib.I64TruncSatF32S
| @lib.I64TruncSatF32U
| @lib.I64TruncSatF64S
| @lib.I64TruncSatF64U => Some(1)
@lib.TableSet(_)
| @lib.TableGrow(_)
| @lib.I32Eq
| @lib.I32Ne
| @lib.I32LtS
| @lib.I32LtU
| @lib.I32GtS
| @lib.I32GtU
| @lib.I32LeS
| @lib.I32LeU
| @lib.I32GeS
| @lib.I32GeU
| @lib.I64Eq
| @lib.I64Ne
| @lib.I64LtS
| @lib.I64LtU
| @lib.I64GtS
| @lib.I64GtU
| @lib.I64LeS
| @lib.I64LeU
| @lib.I64GeS
| @lib.I64GeU
| @lib.F32Eq
| @lib.F32Ne
| @lib.F32Lt
| @lib.F32Gt
| @lib.F32Le
| @lib.F32Ge
| @lib.F64Eq
| @lib.F64Ne
| @lib.F64Lt
| @lib.F64Gt
| @lib.F64Le
| @lib.F64Ge
| @lib.I32Add
| @lib.I32Sub
| @lib.I32Mul
| @lib.I32DivS
| @lib.I32DivU
| @lib.I32RemS
| @lib.I32RemU
| @lib.I32And
| @lib.I32Or
| @lib.I32Xor
| @lib.I32Shl
| @lib.I32ShrS
| @lib.I32ShrU
| @lib.I32Rotl
| @lib.I32Rotr
| @lib.I64Add
| @lib.I64Sub
| @lib.I64Mul
| @lib.I64DivS
| @lib.I64DivU
| @lib.I64RemS
| @lib.I64RemU
| @lib.I64And
| @lib.I64Or
| @lib.I64Xor
| @lib.I64Shl
| @lib.I64ShrS
| @lib.I64ShrU
| @lib.I64Rotl
| @lib.I64Rotr
| @lib.F32Add
| @lib.F32Sub
| @lib.F32Mul
| @lib.F32Div
| @lib.F32Min
| @lib.F32Max
| @lib.F32Copysign
| @lib.F64Add
| @lib.F64Sub
| @lib.F64Mul
| @lib.F64Div
| @lib.F64Min
| @lib.F64Max
| @lib.F64Copysign => Some(2)
@lib.TableFill(_)
| @lib.TableCopy(_, _)
| @lib.TableInit(_, _)
| @lib.MemoryCopy(_, _)
| @lib.MemoryFill(_)
| @lib.MemoryInit(_, _) => Some(3)
_ => None
}
}
///|
fn directize_instr_pop_count(
instr : @lib.Instruction,
before : @validate.TcState,
after : @validate.TcState,
) -> Int32? {
match directize_fast_pop_count(instr) {
Some(count) if count <= before.stack.length() => return Some(count)
_ => ()
}
for count = 0; count <= before.stack.length(); count = count + 1 {
let candidate = @validate.tc_state_with_owned_stack(
before,
directize_stack_suffix(before.stack, count),
)
match @validate.Typecheck::typecheck(instr, candidate) {
Ok(candidate_after) =>
if directize_candidate_after_matches(
before.stack,
after,
count,
candidate_after,
) {
return Some(count)
}
Err(_) => ()
}
}
None
}
///|
fn directize_scan_producer_instr(
instr : @lib.Instruction,
instr_idx : Int32,
state : @validate.TcState,
values : Array[DirectizeProducerValue],
) -> @validate.TcState? {
let typecheck_state = @validate.tc_state_with_stack(state, state.stack)
let after = match @validate.Typecheck::typecheck(instr, typecheck_state) {
Ok(next) => next
Err(_) => return None
}
if !after.reachable || after.escape != @validate.tc_escape_none() {
return None
}
let pop_count = match directize_instr_pop_count(instr, state, after) {
Some(count) => count
None => return None
}
if values.length() != state.stack.length() || pop_count > values.length() {
return None
}
let prefix_len = values.length() - pop_count
let mut producer_start = instr_idx
for idx = prefix_len; idx < values.length(); idx = idx + 1 {
if values[idx].start < producer_start {
producer_start = values[idx].start
}
}
while values.length() > prefix_len {
ignore(values.pop())
}
for idx = prefix_len; idx < after.stack.length(); idx = idx + 1 {
values.push({ ty: after.stack[idx], start: producer_start, })
}
Some(after)
}
///|
fn directize_producer_stack_before(
out : Array[@lib.Instruction],
start : Int32,
end : Int32,
info : DirectizeInfo,
) -> Array[DirectizeProducerValue] {
let values : Array[DirectizeProducerValue] = []
let mut state = @validate.make_state(info.env, [])
for idx = start; idx < end; idx = idx + 1 {
match directize_scan_producer_instr(out[idx], idx, state, values) {
Some(next) => state = next
None => {
values.clear()
state = @validate.make_state(info.env, [])
match directize_scan_producer_instr(out[idx], idx, state, values) {
Some(next) => state = next
None => {
values.clear()
state = @validate.make_state(info.env, [])
}
}
}
}
}
values
}
///|
fn directize_find_select_rewrite(
out : Array[@lib.Instruction],
scan_start : Int32,
type_idx : @lib.TypeIdx,
table_idx : @lib.TableIdx,
ctx : DirectizeFuncContext,
is_return : Bool,
) -> DirectizeSelectRewrite? {
if out.length() < 4 {
return None
}
match out[out.length() - 1] {
@lib.Select(_) => ()
_ => return None
}
let (params, results) = match directize_call_signature(ctx.info, type_idx) {
Some(sig) => sig
None => return None
}
let select_idx = out.length() - 1
let values = directize_producer_stack_before(
out,
scan_start,
select_idx,
ctx.info,
)
let required = params.length() + 3
if values.length() < required {
return None
}
let condition_slot = values.length() - 1
let false_slot = condition_slot - 1
let true_slot = condition_slot - 2
let arg_slot = true_slot - params.length()
if !directize_stack_matches(
[values[condition_slot].ty],
[@lib.ValType::i32()],
ctx.info.env,
) {
return None
}
let cond_start = values[condition_slot].start
if cond_start < scan_start + 2 ||
values[true_slot].start != cond_start - 2 ||
values[false_slot].start != cond_start - 1 {
return None
}
let if_true = match directize_target_const(out[cond_start - 2]) {
Some(value) => value
None => return None
}
let if_false = match directize_target_const(out[cond_start - 1]) {
Some(value) => value
None => return None
}
let actual_params : Array[@lib.ValType] = []
let mut arg_start = cond_start - 2
for idx = 0; idx < params.length(); idx = idx + 1 {
let value = values[arg_slot + idx]
actual_params.push(value.ty)
if value.start < arg_start {
arg_start = value.start
}
}
if !directize_stack_matches(actual_params, params, ctx.info.env) {
return None
}
let true_target = directize_classify_target(
ctx.info,
table_idx,
if_true,
type_idx,
)
let false_target = directize_classify_target(
ctx.info,
table_idx,
if_false,
type_idx,
)
if true_target is DirectizeUnknown || false_target is DirectizeUnknown {
return None
}
let arg_instrs = directize_instr_slice(out, arg_start, cond_start - 2)
let condition_instrs = directize_instr_slice(out, cond_start, select_idx)
// Validate only the two materialized matches. Discovery itself performs one
// stack-effect scan and does not allocate or typecheck every candidate suffix.
if !directize_suffix_produces_params(arg_instrs, params, ctx.info) ||
!directize_suffix_produces_params(
condition_instrs,
[@lib.ValType::i32()],
ctx.info,
) {
return None
}
Some({
arg_start,
arg_instrs,
condition_instrs,
params,
results,
has_stack_prefix: values.length() > required,
is_return,
then_target: true_target,
else_target: false_target,
})
}
///|
fn directize_make_call_body(
locals : Array[@lib.LocalIdx],
target : DirectizeTarget,
is_return : Bool,
) -> Array[@lib.Instruction] {
match target {
DirectizeKnown(func_idx) => {
let body : Array[@lib.Instruction] = []
for local_idx in locals {
body.push(@lib.Instruction::local_get(local_idx))
}
if is_return {
body.push(@lib.Instruction::return_call(func_idx))
} else {
body.push(@lib.Instruction::call(func_idx))
}
body
}
DirectizeTrap => [@lib.Instruction::unreachable_()]
DirectizeUnknown => []
}
}
///|
fn directize_apply_select_rewrite(
out : Array[@lib.Instruction],
rewrite : DirectizeSelectRewrite,
ctx : DirectizeFuncContext,
) -> Unit {
directize_truncate_out(out, rewrite.arg_start)
for instr in rewrite.arg_instrs {
out.push(instr)
}
let locals : Array[@lib.LocalIdx] = []
for param in rewrite.params {
locals.push(directize_allocate_local(ctx, param))
}
for idx = locals.length() - 1; idx >= 0; idx = idx - 1 {
out.push(@lib.Instruction::local_set(locals[idx]))
}
for instr in rewrite.condition_instrs {
out.push(instr)
}
let block_type = if rewrite.is_return {
@lib.BlockType::void_()
} else {
directize_blocktype_for_results(rewrite.results, ctx.info)
}
ctx.inserted_label_before.push(ctx.original_label_count[0])
out.push(
@lib.Instruction::if_(
block_type,
directize_make_call_body(locals, rewrite.then_target, rewrite.is_return),
Some(
directize_make_call_body(locals, rewrite.else_target, rewrite.is_return),
),
),
)
if rewrite.is_return {
out.push(@lib.Instruction::unreachable_())
}
}
///|
fn directize_call_input_producer(
out : Array[@lib.Instruction],
scan_start : Int32,
type_idx : @lib.TypeIdx,
info : DirectizeInfo,
) -> (Int32, Int32, Bool)? {
let (params, results) = match directize_call_signature(info, type_idx) {
Some(signature) => signature
None => return None
}
let values = directize_producer_stack_before(
out,
scan_start,
out.length(),
info,
)
let required = params.length() + 1
if values.length() < required {
return None
}
let mut producer_start = out.length()
for idx = values.length() - required; idx < values.length(); idx = idx + 1 {
if values[idx].start < producer_start {
producer_start = values[idx].start
}
}
Some((producer_start, results.length(), values.length() > required))
}
///|
fn directize_try_rewrite_call_tail(
out : Array[@lib.Instruction],
scan_start : Int32,
instr : @lib.Instruction,
ctx : DirectizeFuncContext,
) -> (Int32, Bool)? {
let info = ctx.info
if out.length() == 0 {
return None
}
match instr {
@lib.CallIndirect(type_idx, table_idx)
| @lib.ReturnCallIndirect(type_idx, table_idx) => {
let is_return = instr is @lib.ReturnCallIndirect(_, _)
match directize_target_const(out[out.length() - 1]) {
Some(offset) =>
match directize_classify_target(info, table_idx, offset, type_idx) {
DirectizeKnown(func_idx) => {
let input_producer = directize_call_input_producer(
out, scan_start, type_idx, info,
)
ignore(out.pop())
out.push(
if is_return {
@lib.Instruction::return_call(func_idx)
} else {
@lib.Instruction::call(func_idx)
},
)
match input_producer {
Some((producer_start, result_count, has_stack_prefix)) if !is_return &&
result_count > 0 =>
Some((producer_start, result_count == 1 && !has_stack_prefix))
_ => Some((out.length(), false))
}
}
DirectizeTrap =>
match directize_call_param_types(info, type_idx) {
Some(params) => {
directize_push_trap_replacement(out, params, info)
Some((out.length(), false))
}
None => None
}
DirectizeUnknown => None
}
None =>
match out[out.length() - 1] {
@lib.Select(_) =>
match
directize_find_select_rewrite(
out, scan_start, type_idx, table_idx, ctx, is_return,
) {
Some(rewrite) => {
let next_scan_start = if !is_return &&
rewrite.results.length() > 0 {
rewrite.arg_start
} else {
out.length()
}
directize_apply_select_rewrite(out, rewrite, ctx)
Some(
(
next_scan_start,
rewrite.results.length() == 1 && !rewrite.has_stack_prefix,
),
)
}
None => None
}
_ => None
}
}
}
_ => None
}
}
///|
fn directize_table_can_optimize(
info : DirectizeInfo,
table_idx : @lib.TableIdx,
) -> Bool {
let slot = directize_table_slot(table_idx)
slot >= 0 && slot < info.tables.length() && info.tables[slot].can_optimize
}
///|
fn directize_instr_has_nested_rewrite_candidate(
instr : @lib.Instruction,
info : DirectizeInfo,
) -> Bool {
match instr {
@lib.Block(_, @lib.Expr(body)) | @lib.Loop(_, @lib.Expr(body)) =>
directize_instrs_have_rewrite_candidate(body, info)
@lib.If(_, then_body, else_body) => {
if directize_instrs_have_rewrite_candidate(then_body, info) {
return true
}
match else_body {
Some(body) => directize_instrs_have_rewrite_candidate(body, info)
None => false
}
}
@lib.Try(_, @lib.Expr(body), catches, _) => {
if directize_instrs_have_rewrite_candidate(body, info) {
return true
}
for catch_ in catches {
let catch_body = match catch_ {
@lib.LegacyCatch(_, @lib.Expr(catch_body))
| @lib.LegacyCatchAll(@lib.Expr(catch_body)) => catch_body
}
if directize_instrs_have_rewrite_candidate(catch_body, info) {
return true
}
}
false
}
@lib.TryTable(_, _, @lib.Expr(body)) =>
directize_instrs_have_rewrite_candidate(body, info)
_ => false
}
}
///|
fn directize_instrs_have_rewrite_candidate(
instrs : Array[@lib.Instruction],
info : DirectizeInfo,
) -> Bool {
for idx = 0; idx < instrs.length(); idx = idx + 1 {
let instr = instrs[idx]
if directize_instr_has_nested_rewrite_candidate(instr, info) {
return true
}
let table_idx = match instr {
@lib.CallIndirect(_, table_idx) | @lib.ReturnCallIndirect(_, table_idx) =>
table_idx
_ => continue
}
if idx == 0 || !directize_table_can_optimize(info, table_idx) {
continue
}
match instrs[idx - 1] {
@lib.I32Const(_) | @lib.I64Const(_) | @lib.Select(_) => return true
_ => ()
}
}
false
}
///|
fn directize_rewrite_instrs(
instrs : Array[@lib.Instruction],
ctx : DirectizeFuncContext,
) -> (Array[@lib.Instruction], Bool) {
let out : Array[@lib.Instruction] = []
let mut changed = false
let mut scan_start = 0
let mut reset_scan_after_drop = false
for instr in instrs {
match instr {
@lib.Block(_, _)
| @lib.Loop(_, _)
| @lib.If(_, _, _)
| @lib.Try(_, _, _, _)
| @lib.TryTable(_, _, _) =>
ctx.original_label_count[0] = ctx.original_label_count[0] + 1
_ => ()
}
let (current, nested_changed) = directize_rewrite_nested_instr(instr, ctx)
if nested_changed {
changed = true
}
if reset_scan_after_drop {
reset_scan_after_drop = false
if current is @lib.Drop {
out.push(current)
scan_start = out.length()
continue
}
}
match directize_try_rewrite_call_tail(out, scan_start, current, ctx) {
Some((next_scan_start, reset_after_drop)) => {
changed = true
scan_start = next_scan_start
reset_scan_after_drop = reset_after_drop
}
None => {
let input_producer = match current {
@lib.CallIndirect(type_idx, _) =>
directize_call_input_producer(out, scan_start, type_idx, ctx.info)
_ => None
}
out.push(current)
match input_producer {
Some((producer_start, result_count, has_stack_prefix)) if result_count >
0 => {
scan_start = producer_start
reset_scan_after_drop = result_count == 1 && !has_stack_prefix
}
Some((_, _, _)) => scan_start = out.length()
None =>
if current is @lib.ReturnCallIndirect(_, _) {
scan_start = out.length()
}
}
}
}
}
(out, changed)
}
///|
fn directize_context_for_func(
base : DirectizeInfo,
func_idx : @lib.FuncIdx,
locals : @lib.Locals,
) -> DirectizeFuncContext {
match base.env.get_functype_by_funcidx(func_idx) {
Some(@lib.FuncType(params, results)) =>
match @lib.FunctionLocals::from_local_decls(params, locals) {
Ok(function_locals) => {
let all_locals = function_locals.all_locals()
let env = base.env
.with_locals(all_locals)
.with_return_type(Some(results))
{
info: { ..base, env, },
base_local_count: all_locals.length(),
locals: all_locals,
extra_locals: [],
original_label_count: [0],
inserted_label_before: [],
}
}
Err(_) =>
{
info: base,
base_local_count: locals.length(),
locals: locals.copy(),
extra_locals: [],
original_label_count: [0],
inserted_label_before: [],
}
}
None =>
{
info: base,
base_local_count: locals.length(),
locals: locals.copy(),
extra_locals: [],
original_label_count: [0],
inserted_label_before: [],
}
}
}
///|
fn directize_append_block_types(
mod_ : @lib.Module,
extra : Array[@lib.RecType],
) -> @lib.Module {
if extra.length() == 0 {
return mod_
}
let rec_types = match mod_.type_sec {
Some(@lib.TypeSec(existing)) => existing.copy()
None => []
}
for rec_type in extra {
rec_types.push(rec_type)
}
mod_.with_type_sec(@lib.TypeSec::new(rec_types))
}
///|
fn directize_remap_label_names(
mod_ : @lib.Module,
imported_funcs : Int32,
insertions : Array[Array[Int32]?],
) -> @lib.Module {
let names = match mod_.name_sec {
Some(names) => names
None => return mod_
}
let label_names = match names.label_names {
Some(@lib.IndirectNameMap(entries)) => entries
None => return mod_
}
let rewritten : Array[@lib.IndirectNameAssoc] = []
let mut changed = false
for entry in label_names {
let @lib.IndirectNameAssoc(func_idx, @lib.NameMap(labels)) = entry
let code_idx = func_idx.reinterpret_as_int() - imported_funcs
match insertions.get(code_idx) {
Some(Some(positions)) if positions.length() > 0 => {
let ordered_positions = positions.copy()
ordered_positions.sort_by(fn(left, right) { left - right })
let remapped : Array[@lib.NameAssoc] = []
for label in labels {
let @lib.NameAssoc(old_idx, name) = label
let shift = directize_insertion_count_at_or_before(
ordered_positions,
old_idx.reinterpret_as_int(),
None,
)
let new_idx = old_idx + shift.reinterpret_as_uint()
remapped.push(@lib.NameAssoc::new(new_idx, name))
}
rewritten.push(
@lib.IndirectNameAssoc::new(func_idx, @lib.NameMap::new(remapped)),
)
changed = true
}
_ => rewritten.push(entry)
}
}
if !changed {
return mod_
}
mod_.with_name_sec(
@lib.NameSec::new(
module_name=names.module_name,
func_names=names.func_names,
local_names=names.local_names,
label_names=Some(@lib.IndirectNameMap::new(rewritten)),
type_names=names.type_names,
table_names=names.table_names,
memory_names=names.memory_names,
global_names=names.global_names,
elem_names=names.elem_names,
data_names=names.data_names,
field_names=names.field_names,
tag_names=names.tag_names,
),
)
}
///|
fn directize_insertion_count_at_or_before(
positions : Array[Int32],
target : Int32,
visits : Array[Int32]?,
) -> Int32 {
let mut low = 0
let mut high = positions.length()
while low < high {
match visits {
Some(counter) => counter[0] += 1
None => ()
}
let middle = low + (high - low) / 2
if positions[middle] <= target {
low = middle + 1
} else {
high = middle
}
}
low
}
///|
/// Shared final lowering for an independently proven exact `call_ref` target.
/// The explicit reference operand is always evaluated and dropped before the
/// direct call, preserving its effects, traps, exceptions, and evaluation order.
fn directize_exact_ref_call_replacement(
env : @validate.Env,
instr : @lib.Instruction,
target : @lib.FuncIdx,
) -> Array[@lib.Instruction]? {
let call_type = match instr {
@lib.CallRef(type_) | @lib.ReturnCallRef(type_) => type_
_ => return None
}
let target_type = match env.get_functypeidx_by_funcidx(target) {
Some(type_) => type_
None => return None
}
if !@validate.Match::matches(
@lib.ExternType::func(target_type),
@lib.ExternType::func(call_type),
env,
) {
return None
}
Some([
@lib.Instruction::drop(),
match instr {
@lib.ReturnCallRef(_) => @lib.Instruction::return_call(target)
_ => @lib.Instruction::call(target)
},
])
}
///|
fn directize_run_module_pass(mod_ : @lib.Module) -> @lib.Module {
if pass_module_has_offset_sensitive_metadata(mod_) {
return mod_
}
let funcs = match mod_.code_sec {
Some(@lib.CodeSec(funcs)) => funcs
None => return mod_
}
let info = directize_build_info(mod_)
let mut has_optimizable_table = false
for table in info.tables {
if table.can_optimize {
has_optimizable_table = true
break
}
}
if !has_optimizable_table {
return mod_
}
let imported_funcs = directize_imported_func_count(mod_)
let rewritten : Array[@lib.Func] = []
let label_insertions : Array[Array[Int32]?] = Array::make(
funcs.length(),
None,
)
let mut changed = false
for code_idx = 0; code_idx < funcs.length(); code_idx = code_idx + 1 {
let func = funcs[code_idx]
let @lib.Func(locals, @lib.Expr(body)) = func
if !directize_instrs_have_rewrite_candidate(body, info) {
rewritten.push(func)
continue
}
let ctx = directize_context_for_func(
info,
@lib.FuncIdx::new((imported_funcs + code_idx).reinterpret_as_uint()),
locals,
)
let (next_body, func_changed) = directize_rewrite_instrs(body, ctx)
if func_changed {
changed = true
if ctx.inserted_label_before.length() > 0 {
label_insertions[code_idx] = Some(ctx.inserted_label_before)
}
let next_locals = locals.copy()
next_locals.append_types(ctx.extra_locals)
rewritten.push(@lib.Func::new(next_locals, @lib.Expr::new(next_body)))
} else {
rewritten.push(func)
}
}
if changed {
let next = directize_append_block_types(
mod_.with_code_sec(@lib.CodeSec::new(rewritten)),
info.extra_block_types,
)
directize_remap_label_names(next, imported_funcs, label_insertions)
} else {
mod_
}
}