// Licensing scope: see ../../LICENSE and this directory's LICENSE and NOTICE.
// Potentially incorporated Binaryen expression retains Apache-2.0 terms.
// Original Starshine contributions use the scoped MIT grant in ../../LICENSE.
// Modified for Starshine through 2026: implementation and validation use
// Starshine's representations and APIs; this is not an unchanged upstream file.
// Conservative notice retention does not establish copying or independence.
///|
pub enum HotPipelineVerifyPolicy {
FinalModuleOnly
AfterEachPass
} derive(Eq, Debug)
///|
pub fn HotPipelineVerifyPolicy::final_module_only() -> HotPipelineVerifyPolicy {
FinalModuleOnly
}
///|
pub fn HotPipelineVerifyPolicy::after_each_pass() -> HotPipelineVerifyPolicy {
AfterEachPass
}
///|
fn hot_pipeline_trace_noop(_ : String) -> Unit {
()
}
///|
pub struct HotPipelineOptions {
verify_policy : HotPipelineVerifyPolicy
validate_final_module : Bool
closed_world : Bool
traps_never_happen : Bool
ignore_implicit_traps : Bool
zero_filled_memory : Bool
fast_math : Bool
compiler_fact_policy : CompilerFactUsePolicy
optimize_level : Int32
shrink_level : Int32
inlining_always_inline_max_size : Int32
inlining_one_caller_inline_max_size : Int32
inlining_flexible_inline_max_size : Int32
inlining_max_combined_binary_size : Int32
inlining_allow_functions_with_loops : Bool
inlining_partial_inlining_ifs : Int32
stack_function_passes : Bool
trace_enabled : Bool
trace : (String) -> Unit
perf : HotPerfSession?
priv dae_default_no_callback_trace : DaeNoCallbackTrace?
}
///|
pub fn HotPipelineOptions::new(
verify_policy~ : HotPipelineVerifyPolicy = HotPipelineVerifyPolicy::final_module_only(),
validate_final_module~ : Bool = true,
closed_world~ : Bool = false,
traps_never_happen~ : Bool = false,
ignore_implicit_traps~ : Bool = false,
zero_filled_memory~ : Bool = false,
fast_math~ : Bool = false,
compiler_fact_policy~ : CompilerFactUsePolicy = CompilerFactUsePolicy::ignore(),
optimize_level~ : Int32 = 0,
shrink_level~ : Int32 = 0,
inlining_always_inline_max_size~ : Int32 = 2,
inlining_one_caller_inline_max_size~ : Int32 = -1,
inlining_flexible_inline_max_size~ : Int32 = 20,
inlining_max_combined_binary_size~ : Int32 = 400 * 1024,
inlining_allow_functions_with_loops~ : Bool = false,
inlining_partial_inlining_ifs~ : Int32 = 0,
stack_function_passes~ : Bool = false,
trace~ : ((String) -> Unit)? = None,
perf~ : HotPerfSession? = None,
) -> HotPipelineOptions {
let trace_fn = match trace {
Some(cb) => cb
None => hot_pipeline_trace_noop
}
{
verify_policy,
validate_final_module,
closed_world,
traps_never_happen,
ignore_implicit_traps,
zero_filled_memory,
fast_math,
compiler_fact_policy,
optimize_level,
shrink_level,
inlining_always_inline_max_size,
inlining_one_caller_inline_max_size,
inlining_flexible_inline_max_size,
inlining_max_combined_binary_size,
inlining_allow_functions_with_loops,
inlining_partial_inlining_ifs,
stack_function_passes,
trace_enabled: trace is Some(_),
trace: trace_fn,
perf,
dae_default_no_callback_trace: if trace is None && perf is None {
Some({ callback: trace_fn, })
} else {
None
},
}
}
///|
fn run_hot_pipeline_threshold_guard_class(
pass_name : String,
reason : String,
) -> String? {
if pass_name == "remove-unused-brs" {
if reason == "simd-parser-br-table-stack-hazard-noop" ||
reason == "unchecked-moderate-call-mesh-noop" {
return Some("correctness")
}
if reason == "large-void-if-return-ladder-noop" ||
reason == "large-result-br-table-dispatch-ladder-noop" ||
reason == "large-value-if-branch-ladder-noop" ||
reason == "large-drop-heavy-branch-ladder-noop" ||
reason == "moderate-control-ladder-noop" ||
reason == "large-typed-br-table-encoder-ladder-noop" ||
reason == "unique-loop-select-return-ladder-noop" ||
reason == "structured-return-ladder-noop" ||
reason == "large-local-dispatch-noop" ||
reason == "refcount-payload-parser-noop" {
return Some("performance")
}
}
if pass_name == "simplify-locals" {
if reason == "root-local-set-stack-hazard-noop" ||
reason == "parameter-result-if-late-alias-lifetime" ||
reason == "unchecked-small-structured-call-mesh-noop" {
return Some("correctness")
}
if reason == "straight-line-builder-churn" ||
reason == "huge-straight-line-call-builder" ||
reason == "multivalue-call-heavy-ladder" ||
reason == "giant-validator-structured-call-heavy-noop" ||
reason == "branch-dense-structured-call-heavy-noop" ||
reason == "low-local-decision-ladder-noop" ||
reason == "low-local-loop-return-call-ladder-noop" ||
reason == "mid-local-structured-call-ladder-noop" ||
reason == "low-local-structured-call-heavy-noop" ||
reason == "parser-structured-call-heavy" ||
reason == "decode-structured-call-heavy" ||
reason == "encode-structured-call-heavy" ||
reason == "branchy-decode-structured-call-heavy" ||
reason == "transformer-catch-structured-call-heavy" ||
reason == "branchy-structured-call-heavy" ||
reason == "dense-structured-call-heavy" ||
reason == "stringview-trim-loop-churn" ||
reason == "transformer-structured-call-heavy" ||
reason == "small-loop-call-heavy" ||
reason == "block-rich-structured-call-heavy-noop" ||
reason == "call-dense-structured-walker-noop" ||
reason == "medium-structured-call-heavy" ||
reason == "loop-heavy-structured-call-heavy" ||
reason == "giant-structured-call-heavy" ||
reason == "giant-structured-local-churn" ||
reason == "giant-local-func-noop" {
return Some("performance")
}
}
None
}
///|
fn run_hot_pipeline_threshold_trace_payload(
pass_name : String,
reason : String,
) -> String {
match run_hot_pipeline_threshold_guard_class(pass_name, reason) {
Some(class_) => "reason=\{reason} guard=\{class_}"
None => "reason=\{reason}"
}
}
///|
pub struct HotPassContext {
descriptor : @ir.HotPassDescriptor
cache : @ir.HotAnalysisCache
verify_policy : HotPipelineVerifyPolicy
closed_world : Bool
traps_never_happen : Bool
ignore_implicit_traps : Bool
fast_math : Bool
optimize_level : Int32
shrink_level : Int32
trace : (String) -> Unit
perf : HotPerfSession?
module_ctx : @ir.HotModuleContext?
priv mut allows_simd_introduction : Bool
}
///|
pub fn HotPassContext::new(
descriptor : @ir.HotPassDescriptor,
cache : @ir.HotAnalysisCache,
verify_policy : HotPipelineVerifyPolicy,
closed_world : Bool,
trace : (String) -> Unit,
traps_never_happen~ : Bool = false,
ignore_implicit_traps~ : Bool = false,
fast_math~ : Bool = false,
perf~ : HotPerfSession? = None,
module_ctx~ : @ir.HotModuleContext? = None,
optimize_level~ : Int32 = 0,
shrink_level~ : Int32 = 0,
) -> HotPassContext {
{
descriptor,
cache,
verify_policy,
closed_world,
traps_never_happen,
ignore_implicit_traps,
fast_math,
optimize_level,
shrink_level,
trace,
perf,
module_ctx,
allows_simd_introduction: true,
}
}
///|
fn perf_start_timer_opt(perf : HotPerfSession?, name : String) -> UInt64 {
match perf {
Some(session) => perf_start_timer(session, name)
None => 0UL
}
}
///|
fn perf_stop_timer_opt(
perf : HotPerfSession?,
name : String,
start_us : UInt64,
) -> Unit {
match perf {
Some(session) => perf_stop_timer(session, name, start_us)
None => ()
}
}
///|
fn perf_stop_timer_accumulate_opt(
perf : HotPerfSession?,
name : String,
start_us : UInt64,
) -> Unit {
match perf {
Some(session) =>
if session.enabled && session.trace_timings {
let elapsed_us = @lib.trace_elapsed_us_since(start_us)
ignore(perf_timer_total_commit(session, name, elapsed_us))
}
None => ()
}
}
///|
fn perf_emit_timer_total_opt(perf : HotPerfSession?, name : String) -> Unit {
match perf {
Some(session) =>
if session.enabled && session.trace_timings {
let mut i = 0
while i < session.timer_totals.length() {
if session.timer_totals[i].0 == name {
let total_us = session.timer_totals[i].1
(session.trace)(
"perf:timer name=\{name} elapsed_us=0 total_us=\{total_us}",
)
return
}
i += 1
}
}
None => ()
}
}
///|
fn perf_elapsed_since_start_opt(
perf : HotPerfSession?,
start_us : UInt64,
) -> UInt64 {
match perf {
Some(session) => perf_elapsed_since_start(session, start_us)
None => 0UL
}
}
///|
fn perf_record_timer_elapsed_opt(
perf : HotPerfSession?,
name : String,
elapsed_us : UInt64,
) -> Unit {
match perf {
Some(session) => perf_record_timer_elapsed(session, name, elapsed_us)
None => ()
}
}
///|
fn perf_validation_checkpoint_opt(
perf : HotPerfSession?,
name : String,
) -> Unit {
match perf {
Some(session) => perf_validation_checkpoint(session, name)
None => ()
}
}
///|
fn perf_dump_hot_func_opt(
perf : HotPerfSession?,
func : @ir.HotFunc,
label : String,
) -> Unit {
match perf {
Some(session) => perf_dump_hot_func(session, func, label)
None => ()
}
}
///|
fn perf_emit_counters_opt(perf : HotPerfSession?, label : String) -> Unit {
match perf {
Some(session) => perf_emit_counters(session, label)
None => ()
}
}
///|
fn pipeline_verify_checkpoint_with_perf(
perf : HotPerfSession?,
policy : HotPipelineVerifyPolicy,
label : String,
target : HotPipelineVerifyTarget,
timer_name : String,
) -> Result[Unit, String] {
match (policy, target) {
(
HotPipelineVerifyPolicy::FinalModuleOnly,
HotPipelineVerifyTarget::HotFuncTarget(_),
)
| (
HotPipelineVerifyPolicy::FinalModuleOnly,
HotPipelineVerifyTarget::HotFuncWithCacheTarget(_, _),
) => Ok(())
_ => {
let start_us = perf_start_timer_opt(perf, timer_name)
let result = pipeline_verify_checkpoint(policy, label, target)
perf_stop_timer_opt(perf, timer_name, start_us)
match result {
Ok(_) => {
perf_validation_checkpoint_opt(perf, label)
Ok(())
}
Err(err) => Err(err)
}
}
}
}
///|
pub struct HotPassResult {
changed : Bool
} derive(Eq, Debug)
///|
pub fn HotPassResult::unchanged() -> HotPassResult {
{ changed: false, }
}
///|
pub fn HotPassResult::changed() -> HotPassResult {
{ changed: true, }
}
///|
pub fn pipeline_trace_hook(
trace : (String) -> Unit,
pass_name : String,
event : String,
payload~ : String = "",
) -> Unit {
let suffix = if payload == "" { "" } else { " \{payload}" }
trace("pass[\{pass_name}]:\{event}\{suffix}")
}
///|
pub fn run_hot_pipeline_debug_dump_enabled(
options : HotPipelineOptions,
func_idx : @lib.FuncIdx,
descriptor_name : String,
) -> Bool {
// Artifact-index probes stay disabled in ordinary pipelines. Trace output
// must not trigger unrelated analyses or function dumps.
ignore(options)
ignore(func_idx)
ignore(descriptor_name)
false
}
///|
fn pass_manager_debug_node_tag(func : @ir.HotFunc, node_id : Int32) -> String {
let node = @ir.hot_node_get(func, node_id)
match node.op {
@ir.HotOp::LocalGet | @ir.HotOp::LocalSet | @ir.HotOp::LocalTee =>
"\{node.op}(\{node.imm0})#\{node_id}"
_ => "\{node.op}#\{node_id}"
}
}
///|
fn pass_manager_debug_dump_node(
trace : (String) -> Unit,
func : @ir.HotFunc,
use_def : @ir.HotUseDef,
stage : String,
node_id : Int32,
) -> Unit {
if !@ir.hot_node_is_live(func, node_id) {
trace("debug[\{stage}]:node \{node_id} dead")
return
}
let node = @ir.hot_node_get(func, node_id)
let children : Array[String] = []
for slot = 0; slot < @ir.hot_child_count(func, node_id); slot = slot + 1 {
let child_id = @ir.hot_child_get(func, node_id, slot)
children.push("\{slot}=\{child_id}")
}
trace(
"debug[\{stage}]:node \{node_id} tag=\{pass_manager_debug_node_tag(func, node_id)} ty=\{node.ty} children=\{children} uses=\{@ir.node_use_count(use_def, node_id)}",
)
}
///|
fn pass_manager_debug_dump_root_prefix(
trace : (String) -> Unit,
func : @ir.HotFunc,
use_def : @ir.HotUseDef,
stage : String,
count : Int32,
) -> Unit {
let limit = if count < @ir.hot_root_count(func) {
count
} else {
@ir.hot_root_count(func)
}
for i = 0; i < limit; i = i + 1 {
let node_id = @ir.hot_root_get(func, i)
pass_manager_debug_dump_node(
trace,
func,
use_def,
"\{stage}:root[\{i}]",
node_id,
)
}
}
///|
pub enum HotPipelineVerifyTarget {
HotFuncTarget(@ir.HotFunc)
HotFuncWithCacheTarget(@ir.HotFunc, @ir.HotAnalysisCache)
ModuleTarget(@lib.Module)
}
///|
pub fn pipeline_verify_checkpoint(
policy : HotPipelineVerifyPolicy,
label : String,
target : HotPipelineVerifyTarget,
) -> Result[Unit, String] {
match policy {
HotPipelineVerifyPolicy::FinalModuleOnly =>
match target {
HotPipelineVerifyTarget::ModuleTarget(mod_) =>
match @validate.validate_module(mod_) {
Ok(_) => Ok(())
Err(err) => Err("\{label}: \{err}")
}
HotPipelineVerifyTarget::HotFuncTarget(_)
| HotPipelineVerifyTarget::HotFuncWithCacheTarget(_, _) => Ok(())
}
HotPipelineVerifyPolicy::AfterEachPass =>
match target {
HotPipelineVerifyTarget::HotFuncTarget(func) =>
// Control verification includes the complete core and cycle checks.
match @ir.hot_verify_control(func) {
Ok(_) => Ok(())
Err(err) => Err("\{label}: \{err}")
}
HotPipelineVerifyTarget::HotFuncWithCacheTarget(func, cache) => {
match @ir.hot_verify_control(func) {
Ok(_) => ()
Err(err) => return Err("\{label}: \{err}")
}
match @ir.hot_verify_cached_analyses(func, cache) {
Ok(_) => Ok(())
Err(err) => Err("\{label}: \{err}")
}
}
HotPipelineVerifyTarget::ModuleTarget(mod_) =>
match @validate.validate_module(mod_) {
Ok(_) => Ok(())
Err(err) => Err("\{label}: \{err}")
}
}
}
}
///|
fn vacuum_binary_is_nontrapping(
func : @ir.HotFunc,
node_id : @ir.NodeId,
) -> Bool {
let instr = match @ir.hot_node_exact_instr(func, node_id) {
Some(instr) => instr
None => return false
}
match instr {
@lib.Instruction::I32DivS
| @lib.Instruction::I32DivU
| @lib.Instruction::I32RemS
| @lib.Instruction::I32RemU
| @lib.Instruction::I64DivS
| @lib.Instruction::I64DivU
| @lib.Instruction::I64RemS
| @lib.Instruction::I64RemU => {
if @ir.hot_child_count(func, node_id) != 2 {
return false
}
let lhs = @ir.hot_node_exact_instr(
func,
@ir.hot_child_get(func, node_id, 0),
)
let rhs = @ir.hot_node_exact_instr(
func,
@ir.hot_child_get(func, node_id, 1),
)
match (lhs, rhs, instr) {
(
Some(@lib.I32Const(@lib.I32(a))),
Some(@lib.I32Const(@lib.I32(b))),
@lib.I32DivS,
) => b != 0 && !(a == -2147483648 && b == -1)
(Some(@lib.I32Const(_)), Some(@lib.I32Const(@lib.I32(b))), _) => b != 0
(
Some(@lib.I64Const(@lib.I64(a))),
Some(@lib.I64Const(@lib.I64(b))),
@lib.I64DivS,
) => b != 0L && !(a == -9223372036854775808L && b == -1L)
(Some(@lib.I64Const(_)), Some(@lib.I64Const(@lib.I64(b))), _) => b != 0L
_ => false
}
}
_ => true
}
}
///|
fn vacuum_convert_is_nontrapping(instr : @lib.Instruction?) -> Bool {
match instr {
Some(
@lib.Instruction::I32WrapI64
| @lib.Instruction::I64ExtendI32S
| @lib.Instruction::I64ExtendI32U
| @lib.Instruction::F32ConvertI32S
| @lib.Instruction::F32ConvertI32U
| @lib.Instruction::F32ConvertI64S
| @lib.Instruction::F32ConvertI64U
| @lib.Instruction::F32DemoteF64
| @lib.Instruction::F64ConvertI32S
| @lib.Instruction::F64ConvertI32U
| @lib.Instruction::F64ConvertI64S
| @lib.Instruction::F64ConvertI64U
| @lib.Instruction::F64PromoteF32
| @lib.Instruction::I32ReinterpretF32
| @lib.Instruction::I64ReinterpretF64
| @lib.Instruction::F32ReinterpretI32
| @lib.Instruction::F64ReinterpretI64
| @lib.Instruction::I32TruncSatF32S
| @lib.Instruction::I32TruncSatF32U
| @lib.Instruction::I32TruncSatF64S
| @lib.Instruction::I32TruncSatF64U
| @lib.Instruction::I64TruncSatF32S
| @lib.Instruction::I64TruncSatF32U
| @lib.Instruction::I64TruncSatF64S
| @lib.Instruction::I64TruncSatF64U
) => true
_ => false
}
}
///|
fn vacuum_simd_is_nontrapping(instr : @lib.Instruction?) -> Bool {
match instr {
Some(
@lib.Instruction::V128Const(
_,
_,
_,
_,
_,
_,
_,
_,
_,
_,
_,
_,
_,
_,
_,
_
)
| @lib.Instruction::I8x16Shuffle(
_,
_,
_,
_,
_,
_,
_,
_,
_,
_,
_,
_,
_,
_,
_,
_
)
) => true
_ => false
}
}
///|
fn vacuum_heap_is_removable_if_unused(
func : @ir.HotFunc,
node_id : @ir.NodeId,
) -> Bool {
let length_is_known_nonnegative_const = fn(slot : Int32) {
if slot < 0 || slot >= @ir.hot_child_count(func, node_id) {
return false
}
let length_node = @ir.hot_child_get(func, node_id, slot)
let length_data = @ir.hot_node_get(func, length_node)
if !(length_data.op is @ir.HotOp::Const) {
return false
}
match @ir.hot_const_get(func, length_data.imm0) {
@ir.HotConstPayload::I32Const(@lib.I32(value)) => value >= 0
// Dynamic lengths can be negative (and therefore an enormous unsigned
// allocation) at runtime. Removing the unused allocation would erase its
// required trap, so only the exact bounded constant case is removable.
_ => false
}
}
match @ir.hot_node_exact_instr(func, node_id) {
Some(
@lib.Instruction::StructNew(_)
| @lib.Instruction::StructNewDefault(_)
| @lib.Instruction::ArrayNewFixed(_, _)
| @lib.Instruction::RefEq
| @lib.Instruction::RefI31
) => true
Some(@lib.Instruction::ArrayNew(_)) => length_is_known_nonnegative_const(1)
Some(@lib.Instruction::ArrayNewDefault(_)) =>
length_is_known_nonnegative_const(0)
Some(@lib.Instruction::ContNew(_)) => {
// Allocation itself has no observable effect when the result is unused.
// Its nullable function operand can still trap (Binaryen effects.h).
if @ir.hot_child_count(func, node_id) != 1 {
return false
}
let operand = @ir.hot_child_get(func, node_id, 0)
match @ir.hot_type_results(func, @ir.hot_node_type(func, operand)) {
[@lib.RefTypeValType(ref_type)] => ref_type.is_non_nullable()
_ => false
}
}
_ => false
}
}
///|
fn vacuum_dropped_struct_get_operand(
func : @ir.HotFunc,
node_id : @ir.NodeId,
) -> @ir.NodeId? {
if @ir.hot_child_count(func, node_id) != 1 {
return None
}
match @ir.hot_node_exact_instr(func, node_id) {
Some(
@lib.Instruction::StructGet(_, _)
| @lib.Instruction::StructGetS(_, _)
| @lib.Instruction::StructGetU(_, _)
) => ()
_ => return None
}
let operand = @ir.hot_child_get(func, node_id, 0)
match @ir.hot_type_results(func, @ir.hot_node_type(func, operand)) {
[@lib.RefTypeValType(ref_type)] if ref_type.is_non_nullable() =>
Some(operand)
_ => None
}
}
///|
fn vacuum_subtype_is_final(subtype : @lib.SubType) -> Bool {
match subtype {
@lib.SubType(final_, _, _, _) => final_
@lib.CompTypeSubType(_, _) => true
}
}
///|
fn vacuum_dropped_struct_atomic_get_operand(
ctx : HotPassContext,
func : @ir.HotFunc,
node_id : @ir.NodeId,
) -> @ir.NodeId? {
if @ir.hot_child_count(func, node_id) != 1 {
return None
}
let (type_idx, field_idx) = match @ir.hot_node_exact_instr(func, node_id) {
Some(
@lib.Instruction::StructAtomicGet(_, type_idx, field_idx)
| @lib.Instruction::StructAtomicGetS(_, type_idx, field_idx)
| @lib.Instruction::StructAtomicGetU(_, type_idx, field_idx)
) => (type_idx, field_idx)
_ => return None
}
let module_ctx = match ctx.module_ctx {
Some(module_ctx) => module_ctx
None => return None
}
let subtype = match @ir.hot_module_resolve_subtype(module_ctx, type_idx) {
Some(subtype) => subtype
None => return None
}
let operand = @ir.hot_child_get(func, node_id, 0)
match @ir.hot_type_results(func, @ir.hot_node_type(func, operand)) {
[@lib.RefTypeValType(ref_type)] => {
if ref_type.is_nullable() ||
(!ref_type.is_exact() && !vacuum_subtype_is_final(subtype)) {
return None
}
match ref_type.get_heap_type() {
@lib.HeapType(receiver_type) if receiver_type == type_idx => ()
_ => return None
}
}
_ => return None
}
if !subtype.metadata().is_shared() {
return Some(operand)
}
let @lib.U32(raw_field) = field_idx
match subtype.get_comptype() {
@lib.StructCompType(fields) =>
match fields.get(raw_field.reinterpret_as_int()) {
Some(field) if !field.is_mutable() => Some(operand)
_ => None
}
_ => None
}
}
///|
fn vacuum_branch_targets_only_label(
func : @ir.HotFunc,
node_id : @ir.NodeId,
label_id : @ir.LabelId,
) -> Bool {
match @ir.hot_branch_target(func, node_id) {
Some(target) if target == label_id => ()
_ => return false
}
if @ir.hot_node_get(func, node_id).op is @ir.HotOp::BrTable {
for target in @ir.hot_branch_table_targets_for_node(func, node_id) {
if target != label_id {
return false
}
}
}
true
}
///|
priv struct VacuumDroppedNodeWorkspace {
states : Array[Int32]
generations : Array[Int32]
touched : Array[@ir.NodeId]
mut generation : Int32
}
///|
fn VacuumDroppedNodeWorkspace::new(
node_count : Int32,
) -> VacuumDroppedNodeWorkspace {
{
states: Array::make(node_count, 0),
generations: Array::make(node_count, 0),
touched: [],
generation: 0,
}
}
///|
fn VacuumDroppedNodeWorkspace::begin_query(
self : VacuumDroppedNodeWorkspace,
node_count : Int32,
) -> Unit {
while self.states.length() < node_count {
self.states.push(0)
self.generations.push(0)
}
self.generation += 1
self.touched.clear()
}
///|
fn VacuumDroppedNodeWorkspace::state(
self : VacuumDroppedNodeWorkspace,
node_id : @ir.NodeId,
) -> Int32 {
if node_id < 0 || node_id >= self.states.length() {
return 0
}
if self.generations[node_id] == self.generation {
self.states[node_id]
} else {
0
}
}
///|
fn VacuumDroppedNodeWorkspace::set_state(
self : VacuumDroppedNodeWorkspace,
node_id : @ir.NodeId,
state : Int32,
) -> Unit {
if self.generations[node_id] != self.generation {
self.generations[node_id] = self.generation
self.touched.push(node_id)
}
self.states[node_id] = state
}
///|
fn vacuum_block_has_only_pure_self_branches(
func : @ir.HotFunc,
node_id : @ir.NodeId,
workspace : VacuumDroppedNodeWorkspace,
) -> Bool {
let label_id = @ir.hot_control_node_label(func, node_id)
let body_ref = @ir.hot_region_body_ref(func, node_id)
let mut saw_self_branch = false
for idx = 0; idx < @ir.hot_region_root_count(func, body_ref); idx = idx + 1 {
let root = @ir.hot_region_get(func, body_ref, idx)
match @ir.hot_node_get(func, root).op {
@ir.HotOp::Br | @ir.HotOp::BrIf | @ir.HotOp::BrTable => {
if !vacuum_branch_targets_only_label(func, root, label_id) {
return false
}
for slot = 0; slot < @ir.hot_child_count(func, root); slot = slot + 1 {
if !vacuum_can_remove_dropped_node_memoized(
func,
@ir.hot_child_get(func, root, slot),
workspace,
) {
return false
}
}
saw_self_branch = true
}
_ =>
if !vacuum_can_remove_dropped_node_memoized(func, root, workspace) {
return false
}
}
}
saw_self_branch
}
///|
fn vacuum_region_can_remove_dropped_nodes(
func : @ir.HotFunc,
region_ref : @ir.HotRegionRef,
workspace : VacuumDroppedNodeWorkspace,
) -> Bool {
for idx = 0; idx < @ir.hot_region_root_count(func, region_ref); idx = idx + 1 {
if !vacuum_can_remove_dropped_node_memoized(
func,
@ir.hot_region_get(func, region_ref, idx),
workspace,
) {
return false
}
}
true
}
///|
fn vacuum_can_remove_dropped_node_with_workspace(
func : @ir.HotFunc,
node_id : @ir.NodeId,
workspace : VacuumDroppedNodeWorkspace,
) -> Bool {
workspace.begin_query(@ir.hot_node_count(func))
vacuum_can_remove_dropped_node_memoized(func, node_id, workspace)
}
///|
fn vacuum_can_remove_dropped_node_memoized(
func : @ir.HotFunc,
node_id : @ir.NodeId,
workspace : VacuumDroppedNodeWorkspace,
) -> Bool {
if node_id < 0 || !@ir.hot_node_is_live(func, node_id) {
return false
}
if node_id >= workspace.states.length() {
return false
}
match workspace.state(node_id) {
1 => return false
2 => return true
3 => return false
_ => workspace.set_state(node_id, 1)
}
let node = @ir.hot_node_get(func, node_id)
let is_removable_shape = match node.op {
@ir.HotOp::Const
| @ir.HotOp::LocalGet
| @ir.HotOp::GlobalGet
| @ir.HotOp::MemorySize
| @ir.HotOp::TableSize
| @ir.HotOp::Unary
| @ir.HotOp::Compare
| @ir.HotOp::RefNull
| @ir.HotOp::RefIsNull
| @ir.HotOp::RefFunc
| @ir.HotOp::Select
| @ir.HotOp::TupleMake
| @ir.HotOp::TupleExtract
| @ir.HotOp::RefTest => true
@ir.HotOp::Block =>
vacuum_block_has_only_pure_self_branches(func, node_id, workspace)
@ir.HotOp::Loop =>
vacuum_region_can_remove_dropped_nodes(
func,
@ir.hot_region_body_ref(func, node_id),
workspace,
)
@ir.HotOp::If =>
if !vacuum_region_can_remove_dropped_nodes(
func,
@ir.hot_region_then_ref(func, node_id),
workspace,
) {
false
} else {
match @ir.hot_region_else_ref(func, node_id) {
Some(else_ref) =>
vacuum_region_can_remove_dropped_nodes(func, else_ref, workspace)
None => false
}
}
@ir.HotOp::Binary => vacuum_binary_is_nontrapping(func, node_id)
@ir.HotOp::Convert =>
vacuum_convert_is_nontrapping(@ir.hot_node_exact_instr(func, node_id))
@ir.HotOp::Simd =>
vacuum_simd_is_nontrapping(@ir.hot_node_exact_instr(func, node_id))
@ir.HotOp::Heap => vacuum_heap_is_removable_if_unused(func, node_id)
_ => false
}
if !is_removable_shape {
workspace.set_state(node_id, 3)
return false
}
let value_child_count = if node.op is @ir.HotOp::If {
1
} else if node.op is @ir.HotOp::Block {
0
} else {
@ir.hot_child_count(func, node_id)
}
for slot = 0; slot < value_child_count; slot = slot + 1 {
if !vacuum_can_remove_dropped_node_memoized(
func,
@ir.hot_child_get(func, node_id, slot),
workspace,
) {
workspace.set_state(node_id, 3)
return false
}
}
workspace.set_state(node_id, 2)
true
}
///|
fn vacuum_result_type_is_defaultable(
func : @ir.HotFunc,
node_id : @ir.NodeId,
) -> Bool {
match @ir.hot_type_results(func, @ir.hot_node_type(func, node_id)) {
[@lib.RefTypeValType(ref_type)] => @lib.RefType::is_defaultable(ref_type)
[@lib.BotValType] | [] => false
[_] => true
results =>
results.all(fn(result) {
match result {
@lib.RefTypeValType(ref_type) =>
@lib.RefType::is_defaultable(ref_type)
@lib.BotValType => false
_ => true
}
})
}
}
///|
fn vacuum_unused_value_parent_is_removable(
ctx : HotPassContext,
func : @ir.HotFunc,
node_id : @ir.NodeId,
) -> Bool {
match @ir.hot_node_get(func, node_id).op {
@ir.HotOp::Unary
| @ir.HotOp::Compare
| @ir.HotOp::RefIsNull
| @ir.HotOp::Select
| @ir.HotOp::TupleMake
| @ir.HotOp::TupleExtract
| @ir.HotOp::RefTest => true
@ir.HotOp::Binary => vacuum_binary_is_nontrapping(func, node_id)
@ir.HotOp::Convert =>
vacuum_convert_is_nontrapping(@ir.hot_node_exact_instr(func, node_id))
@ir.HotOp::Simd =>
vacuum_simd_is_nontrapping(@ir.hot_node_exact_instr(func, node_id))
@ir.HotOp::Call =>
match ctx.module_ctx {
Some(module_ctx) =>
@ir.hot_module_func_import_matches(
module_ctx,
@lib.FuncIdx::new(@ir.hot_node_get(func, node_id).imm0.to_uint()),
"binaryen-intrinsics",
"call.without.effects",
)
None => false
}
_ => false
}
}
///|
priv struct VacuumDroppedParentReplacement {
roots : Array[@ir.NodeId]
detached : Array[@ir.NodeId]
}
///|
fn vacuum_dropped_parent_replacement_with_workspace(
ctx : HotPassContext,
func : @ir.HotFunc,
node_id : @ir.NodeId,
workspace : VacuumDroppedNodeWorkspace,
) -> VacuumDroppedParentReplacement? {
if node_id < 0 ||
!@ir.hot_node_is_live(func, node_id) ||
!vacuum_unused_value_parent_is_removable(ctx, func, node_id) {
return None
}
if @ir.hot_node_get(func, node_id).op is @ir.HotOp::Unary &&
@ir.hot_child_count(func, node_id) == 1 {
let detached : Array[@ir.NodeId] = []
let mut current = node_id
while @ir.hot_node_get(func, current).op is @ir.HotOp::Unary &&
@ir.hot_child_count(func, current) == 1 {
detached.push(current)
current = @ir.hot_child_get(func, current, 0)
}
let wrapper_count = detached.length()
let roots : Array[@ir.NodeId] = []
if vacuum_can_remove_dropped_node_with_workspace(func, current, workspace) {
detached.push(current)
} else {
match
vacuum_dropped_parent_replacement_with_workspace(
ctx, func, current, workspace,
) {
Some(nested) => {
roots.append(nested.roots)
detached.append(nested.detached)
}
None => roots.push(@ir.hot_build_drop(func, current))
}
}
if roots.length() > 1 {
for idx = 0; idx < wrapper_count; idx = idx + 1 {
if !vacuum_result_type_is_defaultable(func, detached[idx]) {
return None
}
}
}
return Some({ roots, detached, })
}
let roots : Array[@ir.NodeId] = []
let detached : Array[@ir.NodeId] = [node_id]
for slot = 0; slot < @ir.hot_child_count(func, node_id); slot = slot + 1 {
let child_id = @ir.hot_child_get(func, node_id, slot)
if vacuum_can_remove_dropped_node_with_workspace(func, child_id, workspace) {
detached.push(child_id)
continue
}
match
vacuum_dropped_parent_replacement_with_workspace(
ctx, func, child_id, workspace,
) {
Some(nested) => {
roots.append(nested.roots)
detached.append(nested.detached)
}
None => roots.push(@ir.hot_build_drop(func, child_id))
}
}
// Binaryen's generic optimizer can directly expose one effectful child. For
// multiple children it rebuilds a sequence with a dummy result, which is
// legal only when the removed parent's result type is defaultable.
if roots.length() > 1 && !vacuum_result_type_is_defaultable(func, node_id) {
return None
}
Some({ roots, detached, })
}
///|
fn vacuum_dropped_heap_constructor_replacement(
func : @ir.HotFunc,
node_id : @ir.NodeId,
workspace : VacuumDroppedNodeWorkspace,
) -> Array[@ir.NodeId]? {
match @ir.hot_node_exact_instr(func, node_id) {
Some(
@lib.Instruction::StructNew(_)
| @lib.Instruction::StructNewDefault(_)
| @lib.Instruction::ArrayNew(_)
| @lib.Instruction::ArrayNewDefault(_)
| @lib.Instruction::ArrayNewFixed(_, _)
| @lib.Instruction::ContNew(_)
) => ()
_ => return None
}
if !vacuum_heap_is_removable_if_unused(func, node_id) {
return None
}
let replacement : Array[@ir.NodeId] = []
for slot = 0; slot < @ir.hot_child_count(func, node_id); slot = slot + 1 {
let operand = @ir.hot_child_get(func, node_id, slot)
if !vacuum_can_remove_dropped_node_with_workspace(func, operand, workspace) {
replacement.push(@ir.hot_build_drop(func, operand))
}
}
Some(replacement)
}
///|
priv struct VacuumFreshArraySetReplacement {
roots : Array[@ir.NodeId]
detached : Array[@ir.NodeId]
}
///|
fn vacuum_fresh_array_set_replacement(
func : @ir.HotFunc,
node_id : @ir.NodeId,
workspace : VacuumDroppedNodeWorkspace,
) -> VacuumFreshArraySetReplacement? {
if @ir.hot_child_count(func, node_id) != 3 {
return None
}
let array_type = match @ir.hot_node_exact_instr(func, node_id) {
Some(@lib.ArraySet(type_idx)) => type_idx
_ => return None
}
let alloc = @ir.hot_child_get(func, node_id, 0)
if @ir.hot_child_count(func, alloc) != 1 {
return None
}
match @ir.hot_node_exact_instr(func, alloc) {
Some(@lib.ArrayNewDefault(type_idx)) if type_idx == array_type => ()
_ => return None
}
let length_node = @ir.hot_child_get(func, alloc, 0)
let index_node = @ir.hot_child_get(func, node_id, 1)
let length = match @ir.hot_node_exact_instr(func, length_node) {
Some(@lib.I32Const(@lib.I32(value))) => value
_ => return None
}
let index = match @ir.hot_node_exact_instr(func, index_node) {
Some(@lib.I32Const(@lib.I32(value))) => value
_ => return None
}
if index < 0 || index >= length {
return None
}
let value = @ir.hot_child_get(func, node_id, 2)
Some({
roots: if vacuum_can_remove_dropped_node_with_workspace(
func, value, workspace,
) {
[]
} else {
[@ir.hot_build_drop(func, value)]
},
detached: [alloc, length_node, index_node],
})
}
///|
fn hot_pass_vacuum_can_remove_local_only_node_memoized(
func : @ir.HotFunc,
node_id : @ir.NodeId,
states : Array[Int32],
) -> Bool {
if node_id < 0 ||
node_id >= states.length() ||
!@ir.hot_node_is_live(func, node_id) {
return false
}
match states[node_id] {
2 => return true
1 | 3 => return false
_ => ()
}
states[node_id] = 1
let node = @ir.hot_node_get(func, node_id)
let self_is_removable = match node.op {
@ir.HotOp::Nop
| @ir.HotOp::Const
| @ir.HotOp::LocalGet
| @ir.HotOp::LocalSet
| @ir.HotOp::LocalTee
| @ir.HotOp::GlobalGet
| @ir.HotOp::MemorySize
| @ir.HotOp::TableSize
| @ir.HotOp::Unary
| @ir.HotOp::Compare
| @ir.HotOp::Drop
| @ir.HotOp::RefNull
| @ir.HotOp::RefIsNull
| @ir.HotOp::RefFunc
| @ir.HotOp::Select
| @ir.HotOp::TupleMake
| @ir.HotOp::TupleExtract
| @ir.HotOp::RefTest
| @ir.HotOp::Br
| @ir.HotOp::BrIf
| @ir.HotOp::BrTable
| @ir.HotOp::Return
| @ir.HotOp::Block
| @ir.HotOp::Loop
| @ir.HotOp::If => true
@ir.HotOp::Binary => vacuum_binary_is_nontrapping(func, node_id)
@ir.HotOp::Convert =>
vacuum_convert_is_nontrapping(@ir.hot_node_exact_instr(func, node_id))
@ir.HotOp::Heap => vacuum_heap_is_removable_if_unused(func, node_id)
_ => false
}
if !self_is_removable {
states[node_id] = 3
return false
}
match node.op {
@ir.HotOp::Block =>
if !hot_pass_vacuum_can_remove_local_only_region_memoized(
func,
@ir.hot_region_body_ref(func, node_id),
states,
) {
states[node_id] = 3
return false
}
@ir.HotOp::Loop =>
if hot_pass_vacuum_label_is_used(
func,
@ir.hot_control_node_label(func, node_id),
) ||
!hot_pass_vacuum_can_remove_local_only_region_memoized(
func,
@ir.hot_region_body_ref(func, node_id),
states,
) {
states[node_id] = 3
return false
}
@ir.HotOp::If => {
if !hot_pass_vacuum_can_remove_local_only_region_memoized(
func,
@ir.hot_region_then_ref(func, node_id),
states,
) {
states[node_id] = 3
return false
}
match @ir.hot_region_else_ref(func, node_id) {
Some(else_ref) =>
if !hot_pass_vacuum_can_remove_local_only_region_memoized(
func, else_ref, states,
) {
states[node_id] = 3
return false
}
None => ()
}
}
_ => ()
}
for slot = 0; slot < @ir.hot_child_count(func, node_id); slot = slot + 1 {
if hot_pass_vacuum_is_region_child_slot(node.op, slot) {
continue
}
if !hot_pass_vacuum_can_remove_local_only_node_memoized(
func,
@ir.hot_child_get(func, node_id, slot),
states,
) {
states[node_id] = 3
return false
}
}
states[node_id] = 2
true
}
///|
fn hot_pass_vacuum_can_remove_local_only_region_memoized(
func : @ir.HotFunc,
region_ref : @ir.HotRegionRef,
states : Array[Int32],
) -> Bool {
for idx = 0; idx < @ir.hot_region_root_count(func, region_ref); idx = idx + 1 {
if !hot_pass_vacuum_can_remove_local_only_node_memoized(
func,
@ir.hot_region_get(func, region_ref, idx),
states,
) {
return false
}
}
true
}
///|
fn hot_pass_vacuum_can_remove_local_only_region(
func : @ir.HotFunc,
region_ref : @ir.HotRegionRef,
) -> Bool {
hot_pass_vacuum_can_remove_local_only_region_memoized(
func,
region_ref,
Array::make(@ir.hot_node_count(func), 0),
)
}
///|
fn hot_pass_vacuum_remove_local_only_void_body(
ctx : HotPassContext,
func : @ir.HotFunc,
) -> Bool {
let root_ref = @ir.HotRegionRef::root()
let root_count = @ir.hot_region_root_count(func, root_ref)
if root_count == 0 ||
@ir.hot_type_result_arity(func, @ir.hot_body_result_type(func)) != 0 ||
!hot_pass_vacuum_can_remove_local_only_region(func, root_ref) {
return false
}
let removed = pass_splice_region(ctx, func, root_ref, 0, root_count, [])
vacuum_delete_detached_non_control_nodes(ctx, func, removed)
true
}
///|
fn vacuum_build_i32_eqz(func : @ir.HotFunc, operand : @ir.NodeId) -> @ir.NodeId {
let inverted = @ir.hot_build_unary(func, @lib.ValType::i32(), operand)
@ir.hot_node_exact_instr_set(func, inverted, @lib.Instruction::i32_eqz())
inverted
}
///|
fn hot_pass_vacuum_i32_const_truthy(
func : @ir.HotFunc,
node_id : @ir.NodeId,
) -> Bool? {
if node_id < 0 || !@ir.hot_node_is_live(func, node_id) {
return None
}
let node = @ir.hot_node_get(func, node_id)
match node.op {
@ir.HotOp::Const =>
match @ir.hot_const_get(func, node.imm0) {
@ir.HotConstPayload::I32Const(@lib.I32(value)) => Some(value != 0)
_ => None
}
_ => None
}
}
///|
fn hot_pass_vacuum_label_is_used(
func : @ir.HotFunc,
label_id : @ir.LabelId,
) -> Bool {
if label_id < 0 {
return false
}
for candidate_id = 0
candidate_id < @ir.hot_node_count(func)
candidate_id = candidate_id + 1 {
if !@ir.hot_node_is_live(func, candidate_id) {
continue
}
let candidate = @ir.hot_node_get(func, candidate_id)
match candidate.op {
@ir.HotOp::Br
| @ir.HotOp::BrIf
| @ir.HotOp::BrOnNull
| @ir.HotOp::BrOnNonNull
| @ir.HotOp::BrOnCast
| @ir.HotOp::BrOnCastFail
| @ir.HotOp::Delegate if candidate.imm0 == label_id => return true
@ir.HotOp::BrTable => {
if @ir.hot_branch_table_default_target(func, candidate.imm0) == label_id {
return true
}
for target in @ir.hot_branch_table_targets(func, candidate.imm0) {
if target == label_id {
return true
}
}
}
@ir.HotOp::Continuation =>
if @ir.hot_continuation_targets(func, candidate_id).contains(label_id) {
return true
}
@ir.HotOp::TryTable => {
let info = @ir.hot_catch_info_get(func, candidate.imm1)
for catch_ in info.catches {
let target = match catch_ {
@ir.HotCatchArm::CatchArm(_, label)
| @ir.HotCatchArm::CatchRefArm(_, label)
| @ir.HotCatchArm::CatchAllArm(label)
| @ir.HotCatchArm::CatchAllRefArm(label) => label
}
if target == label_id {
return true
}
}
}
_ => ()
}
}
false
}
///|
fn hot_pass_vacuum_exact_i32_const(
func : @ir.HotFunc,
node_id : @ir.NodeId,
expected : Int32,
) -> Bool {
match @ir.hot_node_exact_instr(func, node_id) {
Some(@lib.I32Const(@lib.I32(value))) => value == expected
_ => false
}
}
///|
fn hot_pass_vacuum_exact_local_get(
func : @ir.HotFunc,
node_id : @ir.NodeId,
) -> Int32? {
match @ir.hot_node_exact_instr(func, node_id) {
Some(@lib.LocalGet(@lib.LocalIdx(raw))) => Some(raw.reinterpret_as_int())
_ => None
}
}
///|
fn hot_pass_vacuum_exact_local_set(
func : @ir.HotFunc,
node_id : @ir.NodeId,
) -> Int32? {
match @ir.hot_node_exact_instr(func, node_id) {
Some(@lib.LocalSet(@lib.LocalIdx(raw))) => Some(raw.reinterpret_as_int())
_ => None
}
}
///|
fn hot_pass_vacuum_is_const_arg_call_local_set(
func : @ir.HotFunc,
node_id : @ir.NodeId,
) -> Int32? {
let target = match hot_pass_vacuum_exact_local_set(func, node_id) {
Some(local_id) => local_id
None => return None
}
if @ir.hot_child_count(func, node_id) != 1 {
return None
}
let call_id = @ir.hot_child_get(func, node_id, 0)
match @ir.hot_node_exact_instr(func, call_id) {
Some(@lib.Call(_)) => ()
_ => return None
}
if @ir.hot_child_count(func, call_id) != 2 {
return None
}
if !hot_pass_vacuum_exact_i32_const(
func,
@ir.hot_child_get(func, call_id, 0),
0,
) ||
!hot_pass_vacuum_exact_i32_const(
func,
@ir.hot_child_get(func, call_id, 1),
0,
) {
return None
}
Some(target)
}
///|
fn hot_pass_vacuum_is_load_linked_local_get(
func : @ir.HotFunc,
node_id : @ir.NodeId,
target_local : Int32,
) -> Bool {
match @ir.hot_node_exact_instr(func, node_id) {
Some(@lib.I32Load(_)) => ()
_ => return false
}
if @ir.hot_child_count(func, node_id) != 1 {
return false
}
let tee_id = @ir.hot_child_get(func, node_id, 0)
match @ir.hot_node_exact_instr(func, tee_id) {
Some(@lib.LocalTee(_)) => ()
_ => return false
}
if @ir.hot_child_count(func, tee_id) != 1 {
return false
}
match
hot_pass_vacuum_exact_local_get(func, @ir.hot_child_get(func, tee_id, 0)) {
Some(local_id) => local_id == target_local
None => false
}
}
///|
fn hot_pass_vacuum_is_load_linked_eqz_condition(
func : @ir.HotFunc,
node_id : @ir.NodeId,
target_local : Int32,
) -> Bool {
match @ir.hot_node_exact_instr(func, node_id) {
Some(@lib.I32Eqz) => ()
_ => return false
}
if @ir.hot_child_count(func, node_id) != 1 {
return false
}
let eq_id = @ir.hot_child_get(func, node_id, 0)
match @ir.hot_node_exact_instr(func, eq_id) {
Some(@lib.I32Eq) => ()
_ => return false
}
if @ir.hot_child_count(func, eq_id) != 2 {
return false
}
let lhs = @ir.hot_child_get(func, eq_id, 0)
let rhs = @ir.hot_child_get(func, eq_id, 1)
(
hot_pass_vacuum_is_load_linked_local_get(func, lhs, target_local) &&
hot_pass_vacuum_exact_i32_const(func, rhs, 20)
) ||
(
hot_pass_vacuum_exact_i32_const(func, lhs, 20) &&
hot_pass_vacuum_is_load_linked_local_get(func, rhs, target_local)
)
}
///|
fn hot_pass_vacuum_is_load_linked_unreachable_if(
func : @ir.HotFunc,
node_id : @ir.NodeId,
target_local : Int32,
) -> Bool {
if !(@ir.hot_node_get(func, node_id).op is @ir.HotOp::If) ||
@ir.hot_child_count(func, node_id) == 0 ||
@ir.hot_region_root_count(func, @ir.hot_region_then_ref(func, node_id)) != 1 {
return false
}
match @ir.hot_region_else_ref(func, node_id) {
Some(_) => return false
None => ()
}
let then_root = @ir.hot_region_get(
func,
@ir.hot_region_then_ref(func, node_id),
0,
)
if !(@ir.hot_node_get(func, then_root).op is @ir.HotOp::Unreachable) {
return false
}
hot_pass_vacuum_is_load_linked_eqz_condition(
func,
@ir.hot_child_get(func, node_id, 0),
target_local,
)
}
///|
fn hot_pass_vacuum_region_is_load_linked_local_set_call_prefix(
func : @ir.HotFunc,
region_ref : @ir.HotRegionRef,
) -> Bool {
let root_count = @ir.hot_region_root_count(func, region_ref)
if root_count == 2 {
let target_local = match
hot_pass_vacuum_is_const_arg_call_local_set(
func,
@ir.hot_region_get(func, region_ref, 0),
) {
Some(local_id) => local_id
None => return false
}
return hot_pass_vacuum_is_load_linked_unreachable_if(
func,
@ir.hot_region_get(func, region_ref, 1),
target_local,
)
}
if root_count != 11 {
return false
}
let target_local = match
hot_pass_vacuum_exact_local_set(
func,
@ir.hot_region_get(func, region_ref, 3),
) {
Some(local_id) => local_id
None => return false
}
match
hot_pass_vacuum_exact_local_get(
func,
@ir.hot_region_get(func, region_ref, 4),
) {
Some(local_id) if local_id == target_local => ()
_ => return false
}
let if_id = @ir.hot_region_get(func, region_ref, 10)
if !(@ir.hot_node_get(func, if_id).op is @ir.HotOp::If) ||
@ir.hot_region_root_count(func, @ir.hot_region_then_ref(func, if_id)) != 1 {
return false
}
match @ir.hot_region_else_ref(func, if_id) {
Some(_) => return false
None => ()
}
let then_root = @ir.hot_region_get(
func,
@ir.hot_region_then_ref(func, if_id),
0,
)
if !(@ir.hot_node_get(func, then_root).op is @ir.HotOp::Unreachable) {
return false
}
hot_pass_vacuum_exact_i32_const(
func,
@ir.hot_region_get(func, region_ref, 0),
0,
) &&
hot_pass_vacuum_exact_i32_const(
func,
@ir.hot_region_get(func, region_ref, 1),
0,
) &&
(match
@ir.hot_node_exact_instr(func, @ir.hot_region_get(func, region_ref, 2)) {
Some(@lib.Call(_)) => true
_ => false
}) &&
(match
@ir.hot_node_exact_instr(func, @ir.hot_region_get(func, region_ref, 5)) {
Some(@lib.LocalTee(_)) => true
_ => false
}) &&
(match
@ir.hot_node_exact_instr(func, @ir.hot_region_get(func, region_ref, 6)) {
Some(@lib.I32Load(_)) => true
_ => false
}) &&
hot_pass_vacuum_exact_i32_const(
func,
@ir.hot_region_get(func, region_ref, 7),
20,
) &&
(match
@ir.hot_node_exact_instr(func, @ir.hot_region_get(func, region_ref, 8)) {
Some(@lib.I32Eq) => true
_ => false
}) &&
(match
@ir.hot_node_exact_instr(func, @ir.hot_region_get(func, region_ref, 9)) {
Some(@lib.I32Eqz) => true
_ => false
})
}
///|
fn hot_pass_vacuum_is_region_child_slot(op : @ir.HotOp, slot : Int32) -> Bool {
for info in @ir.hot_control_region_slot_info(op) {
if info.slot == slot {
return true
}
}
false
}
///|
fn hot_pass_vacuum_node_has_stack_effect_hazard(
func : @ir.HotFunc,
node_id : @ir.NodeId,
) -> Bool {
if node_id < 0 || !@ir.hot_node_is_live(func, node_id) {
return false
}
let node = @ir.hot_node_get(func, node_id)
match node.op {
@ir.HotOp::Call
| @ir.HotOp::CallIndirect
| @ir.HotOp::CallRef
| @ir.HotOp::MemoryCopy
| @ir.HotOp::MemoryFill => return true
_ => ()
}
for slot = 0; slot < @ir.hot_child_count(func, node_id); slot = slot + 1 {
if hot_pass_vacuum_is_region_child_slot(node.op, slot) {
continue
}
if hot_pass_vacuum_node_has_stack_effect_hazard(
func,
@ir.hot_child_get(func, node_id, slot),
) {
return true
}
}
false
}
///|
fn hot_pass_vacuum_should_skip_root_local_set_stack_hazard(
func : @ir.HotFunc,
) -> Bool {
let root_ref = @ir.HotRegionRef::root()
if @ir.hot_region_root_count(func, root_ref) <= 1 {
return false
}
let mut has_local_set = false
let mut has_stack_effect = false
for idx = 0; idx < @ir.hot_region_root_count(func, root_ref); idx = idx + 1 {
let root_id = @ir.hot_region_get(func, root_ref, idx)
if root_id < 0 || !@ir.hot_node_is_live(func, root_id) {
continue
}
match @ir.hot_node_get(func, root_id).op {
@ir.HotOp::LocalSet => has_local_set = true
_ => ()
}
if hot_pass_vacuum_node_has_stack_effect_hazard(func, root_id) {
has_stack_effect = true
}
}
has_local_set && has_stack_effect
}
///|
fn hot_pass_vacuum_should_skip_branchy_structured_writes(
func : @ir.HotFunc,
) -> Bool {
// Vacuum's broad nop/drop cleanup is safe for straight-line and simple value
// debris, but branchy structured functions with local writes exposed argument
// parsing traps under self-optimized O4z. Skip until cleanup is path-aware.
let mut has_writes = false
let mut has_structured_control = false
let mut has_branchy_control = false
for node_id = 0; node_id < @ir.hot_node_count(func); node_id = node_id + 1 {
if !@ir.hot_node_is_live(func, node_id) {
continue
}
match @ir.hot_node_get(func, node_id).op {
@ir.HotOp::LocalSet | @ir.HotOp::LocalTee => has_writes = true
@ir.HotOp::Block
| @ir.HotOp::Loop
| @ir.HotOp::If
| @ir.HotOp::Try
| @ir.HotOp::TryTable => has_structured_control = true
@ir.HotOp::Br
| @ir.HotOp::BrIf
| @ir.HotOp::BrTable
| @ir.HotOp::Return
| @ir.HotOp::ReturnCall
| @ir.HotOp::ReturnCallIndirect
| @ir.HotOp::ReturnCallRef => has_branchy_control = true
_ => ()
}
}
has_writes && has_structured_control && has_branchy_control
}
///|
fn vacuum_region_roots(
func : @ir.HotFunc,
region_ref : @ir.HotRegionRef,
) -> Array[@ir.NodeId] {
let roots : Array[@ir.NodeId] = []
for idx = 0; idx < @ir.hot_region_root_count(func, region_ref); idx = idx + 1 {
roots.push(@ir.hot_region_get(func, region_ref, idx))
}
roots
}
///|
fn vacuum_delete_detached_non_control_nodes(
ctx : HotPassContext,
func : @ir.HotFunc,
node_ids : Array[@ir.NodeId],
) -> Unit {
let deletable : Array[@ir.NodeId] = []
for node_id in node_ids {
if node_id >= 0 &&
@ir.hot_node_is_live(func, node_id) &&
!@ir.hot_is_control_node(func, node_id) {
deletable.push(node_id)
}
}
pass_delete_detached_nodes(ctx, func, deletable)
}
///|
fn vacuum_delete_known_detached_non_control_nodes_batch(
ctx : HotPassContext,
func : @ir.HotFunc,
node_ids : Array[@ir.NodeId],
) -> Unit {
let seen = @bitset.BitSet::new(@ir.hot_node_count(func))
let use_def = @ir.use_def_build(func)
let deletable : Array[@ir.NodeId] = []
for node_id in node_ids {
if node_id >= 0 &&
node_id < seen.length() &&
!seen[node_id] &&
@ir.hot_node_is_live(func, node_id) &&
!@ir.hot_is_control_node(func, node_id) &&
@ir.node_use_count(use_def, node_id) == 0 {
seen[node_id] = true
deletable.push(node_id)
}
}
if !deletable.is_empty() {
@ir.hot_delete_detached_nodes(func, deletable)
pass_mark_mutated(ctx, func)
}
}
///|
fn hot_pass_vacuum_rewrite_dropped_parents_batch_with_workspace(
ctx : HotPassContext,
func : @ir.HotFunc,
region_ref : @ir.HotRegionRef,
workspace : VacuumDroppedNodeWorkspace,
) -> Bool {
let root_count = @ir.hot_region_root_count(func, region_ref)
if root_count == 0 {
return false
}
let next_roots : Array[@ir.NodeId] = []
let detached : Array[@ir.NodeId] = []
let mut changed = false
for idx = 0; idx < root_count; idx = idx + 1 {
let root_id = @ir.hot_region_get(func, region_ref, idx)
if @ir.hot_node_get(func, root_id).op is @ir.HotOp::Drop &&
@ir.hot_child_count(func, root_id) == 1 {
let child_id = @ir.hot_child_get(func, root_id, 0)
match
vacuum_dropped_parent_replacement_with_workspace(
ctx, func, child_id, workspace,
) {
Some(rewrite) => {
next_roots.append(rewrite.roots)
detached.push(root_id)
detached.append(rewrite.detached)
changed = true
continue
}
None => ()
}
}
next_roots.push(root_id)
}
if !changed {
return false
}
@ir.hot_region_replace_body(func, region_ref, next_roots)
pass_mark_mutated(ctx, func)
vacuum_delete_known_detached_non_control_nodes_batch(ctx, func, detached)
true
}
///|
fn hot_pass_vacuum_flip_empty_then_if(
ctx : HotPassContext,
func : @ir.HotFunc,
node_id : @ir.NodeId,
) -> Bool {
if node_id < 0 || !@ir.hot_node_is_live(func, node_id) {
return false
}
let node = @ir.hot_node_get(func, node_id)
match node.op {
@ir.HotOp::If => ()
_ => return false
}
if @ir.hot_type_result_arity(func, node.ty) != 0 ||
@ir.hot_child_count(func, node_id) < 3 {
return false
}
let then_ref = @ir.hot_region_then_ref(func, node_id)
if @ir.hot_region_root_count(func, then_ref) != 0 {
return false
}
let else_ref = match @ir.hot_region_else_ref(func, node_id) {
Some(ref_) => ref_
None => return false
}
let else_roots = vacuum_region_roots(func, else_ref)
if else_roots.length() == 0 {
return false
}
let condition = @ir.hot_child_get(func, node_id, 0)
let else_holder = @ir.hot_child_get(func, node_id, 2)
let inverted = vacuum_build_i32_eqz(func, condition)
@ir.hot_region_replace_body(func, else_ref, [])
@ir.hot_region_replace_body(func, then_ref, else_roots)
@ir.hot_child_set(func, node_id, 0, inverted)
@ir.hot_child_set(func, node_id, 2, -1)
pass_mark_mutated(ctx, func)
vacuum_delete_detached_non_control_nodes(ctx, func, [else_holder])
true
}
///|
fn hot_pass_vacuum_sink_drop_into_if_arms(
ctx : HotPassContext,
func : @ir.HotFunc,
region_ref : @ir.HotRegionRef,
idx : Int32,
if_id : @ir.NodeId,
) -> Bool {
if !(@ir.hot_node_get(func, if_id).op is @ir.HotOp::If) ||
@ir.hot_type_result_arity(func, @ir.hot_node_type(func, if_id)) != 1 {
return false
}
// Void block types cannot encode control inputs. Keep indexed signatures
// with inputs until their input signature can be preserved when demoting.
match @ir.hot_type_get(func, @ir.hot_node_type(func, if_id)) {
@ir.BlockResultResolved(_, params, _) if params.length() != 0 =>
return false
_ => ()
}
let else_ref = match @ir.hot_region_else_ref(func, if_id) {
Some(region) => region
None => return false
}
let then_ref = @ir.hot_region_then_ref(func, if_id)
let then_count = @ir.hot_region_root_count(func, then_ref)
let else_count = @ir.hot_region_root_count(func, else_ref)
if then_count == 0 || else_count == 0 {
return false
}
let then_root = @ir.hot_region_get(func, then_ref, then_count - 1)
let else_root = @ir.hot_region_get(func, else_ref, else_count - 1)
let then_concrete = @ir.hot_node_result_arity(func, then_root) == 1
let else_concrete = @ir.hot_node_result_arity(func, else_root) == 1
if !(then_concrete || else_concrete) ||
(
!then_concrete &&
!(@ir.hot_node_get(func, then_root).op is @ir.Unreachable)
) ||
(
!else_concrete &&
!(@ir.hot_node_get(func, else_root).op is @ir.Unreachable)
) ||
hot_pass_vacuum_label_is_used(func, @ir.hot_control_node_label(func, if_id)) {
return false
}
// Discard only the final value, after each arm's existing effects. Reuse
// the If and its condition; the unselected arm is never evaluated.
for arm in [then_ref, else_ref] {
let roots = vacuum_region_roots(func, arm)
let last = roots.length() - 1
if @ir.hot_node_result_arity(func, roots[last]) == 1 {
roots[last] = @ir.hot_build_drop(func, roots[last])
@ir.hot_region_replace_body(func, arm, roots)
}
}
@ir.hot_control_result_type_set(func, if_id, @ir.hot_type_void(func))
let removed = pass_splice_region(ctx, func, region_ref, idx, 1, [if_id])
vacuum_delete_detached_non_control_nodes(ctx, func, removed)
true
}
///|
fn hot_pass_vacuum_remove_empty_if_with_workspace(
ctx : HotPassContext,
func : @ir.HotFunc,
region_ref : @ir.HotRegionRef,
idx : Int32,
node_id : @ir.NodeId,
workspace : VacuumDroppedNodeWorkspace,
) -> Bool {
if node_id < 0 || !@ir.hot_node_is_live(func, node_id) {
return false
}
let node = @ir.hot_node_get(func, node_id)
if !(node.op is @ir.HotOp::If) ||
@ir.hot_type_result_arity(func, node.ty) != 0 ||
@ir.hot_child_count(func, node_id) < 2 ||
@ir.hot_region_root_count(func, @ir.hot_region_then_ref(func, node_id)) != 0 {
return false
}
match @ir.hot_region_else_ref(func, node_id) {
Some(else_ref) =>
if @ir.hot_region_root_count(func, else_ref) != 0 {
return false
}
None => ()
}
if hot_pass_vacuum_label_is_used(
func,
@ir.hot_control_node_label(func, node_id),
) {
return false
}
let condition = @ir.hot_child_get(func, node_id, 0)
let replacement = if vacuum_can_remove_dropped_node_with_workspace(
func, condition, workspace,
) {
[]
} else {
[@ir.hot_build_drop(func, condition)]
}
let removed = pass_splice_region(ctx, func, region_ref, idx, 1, replacement)
vacuum_delete_detached_non_control_nodes(ctx, func, removed)
true
}
///|
fn hot_pass_vacuum_collapse_constant_if(
ctx : HotPassContext,
func : @ir.HotFunc,
region_ref : @ir.HotRegionRef,
idx : Int32,
node_id : @ir.NodeId,
) -> Bool {
if node_id < 0 || !@ir.hot_node_is_live(func, node_id) {
return false
}
let node = @ir.hot_node_get(func, node_id)
match node.op {
@ir.HotOp::If => ()
_ => return false
}
if @ir.hot_child_count(func, node_id) < 2 {
return false
}
let take_then = match
hot_pass_vacuum_i32_const_truthy(func, @ir.hot_child_get(func, node_id, 0)) {
Some(value) => value
None => return false
}
// Dynamic conditions cannot collapse, so reject them before the global scan.
if hot_pass_vacuum_label_is_used(
func,
@ir.hot_control_node_label(func, node_id),
) {
return false
}
let selected_ref = if take_then {
@ir.hot_region_then_ref(func, node_id)
} else {
match @ir.hot_region_else_ref(func, node_id) {
Some(ref_) => ref_
None => {
let removed = pass_splice_region(ctx, func, region_ref, idx, 1, [])
vacuum_delete_detached_non_control_nodes(ctx, func, removed)
return true
}
}
}
let selected_roots = vacuum_region_roots(func, selected_ref)
@ir.hot_region_replace_body(func, selected_ref, [])
let removed = pass_splice_region(
ctx, func, region_ref, idx, 1, selected_roots,
)
vacuum_delete_detached_non_control_nodes(ctx, func, removed)
true
}
///|
fn hot_pass_remove_node_nested_region_nops(
ctx : HotPassContext,
func : @ir.HotFunc,
node_id : @ir.NodeId,
seen : Array[Bool],
workspace : VacuumDroppedNodeWorkspace,
) -> Bool {
if node_id < 0 ||
node_id >= seen.length() ||
seen[node_id] ||
!@ir.hot_node_is_live(func, node_id) {
return false
}
seen[node_id] = true
let node = @ir.hot_node_get(func, node_id)
let mut changed = false
match node.op {
@ir.HotOp::Block =>
changed = hot_pass_remove_region_nops_with_workspace(
ctx,
func,
@ir.hot_region_body_ref(func, node_id),
seen,
workspace,
) ||
changed
@ir.HotOp::Loop =>
changed = hot_pass_remove_region_nops_with_workspace(
ctx,
func,
@ir.hot_region_body_ref(func, node_id),
seen,
workspace,
) ||
changed
@ir.HotOp::If => {
if @ir.hot_child_count(func, node_id) > 0 {
changed = hot_pass_remove_node_nested_region_nops(
ctx,
func,
@ir.hot_child_get(func, node_id, 0),
seen,
workspace,
) ||
changed
}
changed = hot_pass_remove_region_nops_with_workspace(
ctx,
func,
@ir.hot_region_then_ref(func, node_id),
seen,
workspace,
) ||
changed
match @ir.hot_region_else_ref(func, node_id) {
Some(else_region) =>
changed = hot_pass_remove_region_nops_with_workspace(
ctx, func, else_region, seen, workspace,
) ||
changed
None => ()
}
changed = hot_pass_vacuum_flip_empty_then_if(ctx, func, node_id) ||
changed
}
@ir.HotOp::Try => {
changed = hot_pass_remove_region_nops_with_workspace(
ctx,
func,
@ir.hot_region_body_ref(func, node_id),
seen,
workspace,
) ||
changed
match @ir.hot_region_catch_ref(func, node_id) {
Some(catch_region) =>
changed = hot_pass_remove_region_nops_with_workspace(
ctx, func, catch_region, seen, workspace,
) ||
changed
None => ()
}
}
@ir.HotOp::TryTable => {
changed = hot_pass_remove_region_nops_with_workspace(
ctx,
func,
@ir.hot_region_body_ref(func, node_id),
seen,
workspace,
) ||
changed
changed = hot_pass_remove_region_nops_with_workspace(
ctx,
func,
@ir.hot_region_catch_list_ref(func, node_id),
seen,
workspace,
) ||
changed
}
_ =>
for slot = 0; slot < @ir.hot_child_count(func, node_id); slot = slot + 1 {
changed = hot_pass_remove_node_nested_region_nops(
ctx,
func,
@ir.hot_child_get(func, node_id, slot),
seen,
workspace,
) ||
changed
}
}
changed
}
///|
fn hot_pass_remove_node_child_nested_region_nops(
ctx : HotPassContext,
func : @ir.HotFunc,
node_id : @ir.NodeId,
seen : Array[Bool],
workspace : VacuumDroppedNodeWorkspace,
) -> Bool {
if node_id < 0 || !@ir.hot_node_is_live(func, node_id) {
return false
}
// Nested value-expression control cleanup is a small-function cleanup path.
// Large lowered functions rely on the raw vacuum precleaner for cheap pure
// const/drop and nop debris removal before falling back to root-region HOT
// cleanup, avoiding a broad child traversal on every large cleanup slot.
if seen.length() > 2048 {
return false
}
let node = @ir.hot_node_get(func, node_id)
let mut changed = false
match node.op {
@ir.HotOp::Block | @ir.HotOp::Loop | @ir.HotOp::Try | @ir.HotOp::TryTable =>
()
@ir.HotOp::If =>
if @ir.hot_child_count(func, node_id) > 0 {
changed = hot_pass_remove_node_nested_region_nops(
ctx,
func,
@ir.hot_child_get(func, node_id, 0),
seen,
workspace,
) ||
changed
}
_ =>
for slot = 0; slot < @ir.hot_child_count(func, node_id); slot = slot + 1 {
changed = hot_pass_remove_node_nested_region_nops(
ctx,
func,
@ir.hot_child_get(func, node_id, slot),
seen,
workspace,
) ||
changed
}
}
changed
}
///|
fn hot_pass_vacuum_is_dropped_local_get_root(
func : @ir.HotFunc,
node_id : Int32,
) -> Bool {
@ir.hot_node_get(func, node_id).op is @ir.HotOp::Drop &&
@ir.hot_child_count(func, node_id) == 1 &&
@ir.hot_node_get(func, @ir.hot_child_get(func, node_id, 0)).op
is @ir.HotOp::LocalGet
}
///|
fn hot_pass_vacuum_batch_interleaved_dropped_local_gets(
ctx : HotPassContext,
func : @ir.HotFunc,
region_ref : @ir.HotRegionRef,
) -> Bool {
let root_count = @ir.hot_region_root_count(func, region_ref)
if root_count < 32 {
return false
}
let mut candidate_count = 0
let mut saw_gap = false
let mut gap_after_candidate = false
for slot = 0; slot < root_count; slot = slot + 1 {
let root_id = @ir.hot_region_get(func, region_ref, slot)
if hot_pass_vacuum_is_dropped_local_get_root(func, root_id) {
if gap_after_candidate {
saw_gap = true
}
candidate_count += 1
} else if candidate_count > 0 {
gap_after_candidate = true
}
}
if candidate_count < 8 || !saw_gap {
return false
}
let removed : Array[@ir.NodeId] = []
let kept : Array[@ir.NodeId] = []
for slot = 0; slot < root_count; slot = slot + 1 {
let root_id = @ir.hot_region_get(func, region_ref, slot)
if hot_pass_vacuum_is_dropped_local_get_root(func, root_id) {
removed.push(root_id)
} else {
kept.push(root_id)
}
}
@ir.hot_region_replace_body(func, region_ref, kept)
pass_mark_mutated(ctx, func)
vacuum_delete_known_detached_non_control_nodes_batch(ctx, func, removed)
true
}
///|
fn hot_pass_vacuum_remove_dropped_local_gets_in_region_with_workspace(
ctx : HotPassContext,
func : @ir.HotFunc,
region_ref : @ir.HotRegionRef,
workspace : VacuumDroppedNodeWorkspace,
) -> Bool {
let mut changed = hot_pass_vacuum_batch_interleaved_dropped_local_gets(
ctx, func, region_ref,
)
let mut idx = 0
while idx < @ir.hot_region_root_count(func, region_ref) {
let node_id = @ir.hot_region_get(func, region_ref, idx)
let node = @ir.hot_node_get(func, node_id)
if node.op is @ir.HotOp::If &&
hot_pass_vacuum_remove_empty_if_with_workspace(
ctx, func, region_ref, idx, node_id, workspace,
) {
changed = true
continue
}
if node.op is @ir.HotOp::Drop &&
@ir.hot_child_count(func, node_id) == 1 &&
@ir.hot_node_get(func, @ir.hot_child_get(func, node_id, 0)).op
is @ir.HotOp::LocalGet {
let mut run_end = idx + 1
while run_end < @ir.hot_region_root_count(func, region_ref) {
let next_id = @ir.hot_region_get(func, region_ref, run_end)
if !(@ir.hot_node_get(func, next_id).op is @ir.HotOp::Drop) ||
@ir.hot_child_count(func, next_id) != 1 ||
!(@ir.hot_node_get(func, @ir.hot_child_get(func, next_id, 0)).op
is @ir.HotOp::LocalGet) {
break
}
run_end += 1
}
let removed = if run_end - idx > 4 {
let removed : Array[@ir.NodeId] = []
let kept : Array[@ir.NodeId] = []
for slot = 0
slot < @ir.hot_region_root_count(func, region_ref)
slot = slot + 1 {
let root_id = @ir.hot_region_get(func, region_ref, slot)
if slot >= idx && slot < run_end {
removed.push(root_id)
} else {
kept.push(root_id)
}
}
@ir.hot_region_replace_body(func, region_ref, kept)
pass_mark_mutated(ctx, func)
removed
} else {
pass_splice_region(ctx, func, region_ref, idx, run_end - idx, [])
}
if removed.length() > 4 {
vacuum_delete_known_detached_non_control_nodes_batch(ctx, func, removed)
} else {
vacuum_delete_detached_non_control_nodes(ctx, func, removed)
}
changed = true
continue
}
match node.op {
@ir.HotOp::Block | @ir.HotOp::Loop =>
changed = hot_pass_vacuum_remove_dropped_local_gets_in_region_with_workspace(
ctx,
func,
@ir.hot_region_body_ref(func, node_id),
workspace,
) ||
changed
@ir.HotOp::If => {
changed = hot_pass_vacuum_remove_dropped_local_gets_in_region_with_workspace(
ctx,
func,
@ir.hot_region_then_ref(func, node_id),
workspace,
) ||
changed
match @ir.hot_region_else_ref(func, node_id) {
Some(else_ref) =>
changed = hot_pass_vacuum_remove_dropped_local_gets_in_region_with_workspace(
ctx, func, else_ref, workspace,
) ||
changed
None => ()
}
if hot_pass_vacuum_remove_empty_if_with_workspace(
ctx, func, region_ref, idx, node_id, workspace,
) {
changed = true
continue
}
}
@ir.HotOp::Try => {
changed = hot_pass_vacuum_remove_dropped_local_gets_in_region_with_workspace(
ctx,
func,
@ir.hot_region_body_ref(func, node_id),
workspace,
) ||
changed
match @ir.hot_region_catch_ref(func, node_id) {
Some(catch_ref) =>
changed = hot_pass_vacuum_remove_dropped_local_gets_in_region_with_workspace(
ctx, func, catch_ref, workspace,
) ||
changed
None => ()
}
}
@ir.HotOp::TryTable => {
changed = hot_pass_vacuum_remove_dropped_local_gets_in_region_with_workspace(
ctx,
func,
@ir.hot_region_body_ref(func, node_id),
workspace,
) ||
changed
changed = hot_pass_vacuum_remove_dropped_local_gets_in_region_with_workspace(
ctx,
func,
@ir.hot_region_catch_list_ref(func, node_id),
workspace,
) ||
changed
}
_ => ()
}
idx += 1
}
changed
}
///|
fn hot_pass_remove_region_nops_with_workspace(
ctx : HotPassContext,
func : @ir.HotFunc,
region_ref : @ir.HotRegionRef,
seen : Array[Bool],
workspace : VacuumDroppedNodeWorkspace,
) -> Bool {
let mut changed = hot_pass_vacuum_rewrite_dropped_parents_batch_with_workspace(
ctx, func, region_ref, workspace,
)
let mut idx = 0
while idx < @ir.hot_region_root_count(func, region_ref) {
let node_id = @ir.hot_region_get(func, region_ref, idx)
changed = hot_pass_remove_node_child_nested_region_nops(
ctx, func, node_id, seen, workspace,
) ||
changed
match vacuum_fresh_array_set_replacement(func, node_id, workspace) {
Some(rewrite) => {
let removed = pass_splice_region(
ctx,
func,
region_ref,
idx,
1,
rewrite.roots,
)
removed.append(rewrite.detached)
vacuum_delete_detached_non_control_nodes(ctx, func, removed)
changed = true
idx += rewrite.roots.length()
continue
}
None => ()
}
let node = @ir.hot_node_get(func, node_id)
match node.op {
@ir.HotOp::Nop => {
let removed = pass_splice_region(ctx, func, region_ref, idx, 1, [])
vacuum_delete_detached_non_control_nodes(ctx, func, removed)
changed = true
continue
}
@ir.HotOp::Drop if @ir.hot_child_count(func, node_id) == 1 => {
let child_id = @ir.hot_child_get(func, node_id, 0)
let child = @ir.hot_node_get(func, child_id)
if child.op is @ir.HotOp::LocalTee &&
@ir.hot_child_count(func, child_id) == 1 {
// Discard the tee's result without moving its write past later
// reads. The replacement root keeps the original tee position.
let replacement = @ir.hot_build_local_set_from_tee(func, child_id)
let removed = pass_splice_region(ctx, func, region_ref, idx, 1, [
replacement,
])
removed.push(child_id)
vacuum_delete_detached_non_control_nodes(ctx, func, removed)
changed = true
idx += 1
continue
}
if hot_pass_vacuum_sink_drop_into_if_arms(
ctx, func, region_ref, idx, child_id,
) {
changed = true
// The same slot now holds a void If. Revisit its arms so the new
// drops expose nested value cleanup during this pass invocation.
continue
}
let preserved_operand = match
vacuum_dropped_struct_get_operand(func, child_id) {
Some(operand) => Some(operand)
None => vacuum_dropped_struct_atomic_get_operand(ctx, func, child_id)
}
match preserved_operand {
Some(operand) => {
let replacement = if vacuum_can_remove_dropped_node_with_workspace(
func, operand, workspace,
) {
[]
} else {
[@ir.hot_build_drop(func, operand)]
}
let removed = pass_splice_region(
ctx, func, region_ref, idx, 1, replacement,
)
removed.push(child_id)
vacuum_delete_detached_non_control_nodes(ctx, func, removed)
changed = true
if replacement.length() > 0 {
idx += 1
}
continue
}
None => ()
}
match
vacuum_dropped_heap_constructor_replacement(func, child_id, workspace) {
Some(replacement) => {
let removed = pass_splice_region(
ctx, func, region_ref, idx, 1, replacement,
)
removed.push(child_id)
vacuum_delete_detached_non_control_nodes(ctx, func, removed)
changed = true
idx += replacement.length()
continue
}
None => ()
}
match
vacuum_dropped_parent_replacement_with_workspace(
ctx, func, child_id, workspace,
) {
Some(rewrite) => {
let removed = pass_splice_region(
ctx,
func,
region_ref,
idx,
1,
rewrite.roots,
)
removed.append(rewrite.detached)
vacuum_delete_known_detached_non_control_nodes_batch(
ctx, func, removed,
)
changed = true
idx += rewrite.roots.length()
continue
}
None => ()
}
if vacuum_can_remove_dropped_node_with_workspace(
func, child_id, workspace,
) {
let removed = pass_splice_region(ctx, func, region_ref, idx, 1, [])
vacuum_delete_detached_non_control_nodes(ctx, func, removed)
changed = true
continue
}
}
@ir.HotOp::Block => {
let body_ref = @ir.hot_region_body_ref(func, node_id)
changed = hot_pass_remove_region_nops_with_workspace(
ctx, func, body_ref, seen, workspace,
) ||
changed
if @ir.hot_region_root_count(func, body_ref) == 0 &&
@ir.hot_type_result_arity(func, node.ty) == 0 {
let removed = pass_splice_region(ctx, func, region_ref, idx, 1, [])
vacuum_delete_detached_non_control_nodes(ctx, func, removed)
changed = true
continue
}
if @ir.hot_type_result_arity(func, node.ty) == 0 &&
@ir.hot_region_root_count(func, body_ref) == 1 {
let only_child = @ir.hot_region_get(func, body_ref, 0)
if @ir.hot_node_get(func, only_child).op is @ir.HotOp::Block {
let child_body_ref = @ir.hot_region_body_ref(func, only_child)
if hot_pass_vacuum_region_is_load_linked_local_set_call_prefix(
func, child_body_ref,
) &&
!hot_pass_vacuum_label_is_used(
func,
@ir.hot_control_node_label(func, only_child),
) {
let child_roots = vacuum_region_roots(func, child_body_ref)
@ir.hot_region_replace_body(func, child_body_ref, [])
@ir.hot_region_replace_body(func, body_ref, child_roots)
pass_mark_mutated(ctx, func)
vacuum_delete_detached_non_control_nodes(ctx, func, [only_child])
changed = true
}
}
}
if @ir.hot_type_result_arity(func, node.ty) == 0 &&
@ir.hot_region_root_count(func, body_ref) == 1 &&
@ir.hot_node_get(func, @ir.hot_region_get(func, body_ref, 0)).op
is @ir.HotOp::If &&
!hot_pass_vacuum_label_is_used(
func,
@ir.hot_control_node_label(func, node_id),
) {
let body_roots = vacuum_region_roots(func, body_ref)
@ir.hot_region_replace_body(func, body_ref, [])
let removed = pass_splice_region(
ctx, func, region_ref, idx, 1, body_roots,
)
vacuum_delete_detached_non_control_nodes(ctx, func, removed)
changed = true
continue
}
if @ir.hot_region_root_count(func, body_ref) == 1 {
let only_child = @ir.hot_region_get(func, body_ref, 0)
match @ir.hot_node_get(func, only_child).op {
@ir.HotOp::Unreachable => {
let removed = pass_splice_region(ctx, func, region_ref, idx, 1, [
only_child,
])
vacuum_delete_detached_non_control_nodes(ctx, func, removed)
changed = true
}
_ => ()
}
}
}
@ir.HotOp::Loop =>
changed = hot_pass_remove_region_nops_with_workspace(
ctx,
func,
@ir.hot_region_body_ref(func, node_id),
seen,
workspace,
) ||
changed
@ir.HotOp::If => {
changed = hot_pass_remove_region_nops_with_workspace(
ctx,
func,
@ir.hot_region_then_ref(func, node_id),
seen,
workspace,
) ||
changed
match @ir.hot_region_else_ref(func, node_id) {
Some(else_region) =>
changed = hot_pass_remove_region_nops_with_workspace(
ctx, func, else_region, seen, workspace,
) ||
changed
None => ()
}
let removed_empty = hot_pass_vacuum_remove_empty_if_with_workspace(
ctx, func, region_ref, idx, node_id, workspace,
)
changed = removed_empty || changed
if removed_empty {
continue
}
let collapsed = hot_pass_vacuum_collapse_constant_if(
ctx, func, region_ref, idx, node_id,
)
changed = collapsed || changed
if collapsed {
continue
}
changed = hot_pass_vacuum_flip_empty_then_if(ctx, func, node_id) ||
changed
let void_signature = match
@ir.hot_type_get(func, @ir.hot_node_type(func, node_id)) {
@ir.VoidType
| @ir.BlockResult(@lib.VoidBlockType)
| @ir.BlockResultResolved(@lib.VoidBlockType, _, _) => true
_ => false
}
if void_signature {
if @ir.hot_region_else_ref(func, node_id) is Some(else_ref) &&
@ir.hot_region_root_count(func, else_ref) == 0 {
let holder = @ir.hot_child_get(func, node_id, 2)
@ir.hot_child_set(func, node_id, 2, -1)
pass_mark_mutated(ctx, func)
vacuum_delete_detached_non_control_nodes(ctx, func, [holder])
changed = true
}
}
}
@ir.HotOp::Try => {
changed = hot_pass_remove_region_nops_with_workspace(
ctx,
func,
@ir.hot_region_body_ref(func, node_id),
seen,
workspace,
) ||
changed
match @ir.hot_region_catch_ref(func, node_id) {
Some(catch_region) =>
changed = hot_pass_remove_region_nops_with_workspace(
ctx, func, catch_region, seen, workspace,
) ||
changed
None => ()
}
}
@ir.HotOp::TryTable => {
changed = hot_pass_remove_region_nops_with_workspace(
ctx,
func,
@ir.hot_region_body_ref(func, node_id),
seen,
workspace,
) ||
changed
changed = hot_pass_remove_region_nops_with_workspace(
ctx,
func,
@ir.hot_region_catch_list_ref(func, node_id),
seen,
workspace,
) ||
changed
}
_ => ()
}
idx += 1
}
changed
}
///|
priv struct HotPipelineInstrScanSummary {
mut has_writes : Bool
mut has_structured_control : Bool
mut has_branches : Bool
mut has_drop : Bool
mut has_constant_result_if : Bool
mut has_dead_tail_after_nonfallthrough : Bool
mut has_dead_drop_after_nonfallthrough : Bool
mut has_dce_candidate : Bool
mut has_call_result_lifetime : Bool
mut min_branch_target_token : Int32
mut instr_count : Int32
mut may_fallthrough : Bool
}
///|
fn HotPipelineInstrScanSummary::new() -> HotPipelineInstrScanSummary {
{
has_writes: false,
has_structured_control: false,
has_branches: false,
has_drop: false,
has_constant_result_if: false,
has_dead_tail_after_nonfallthrough: false,
has_dead_drop_after_nonfallthrough: false,
has_dce_candidate: false,
has_call_result_lifetime: false,
min_branch_target_token: 2147483647,
instr_count: 0,
may_fallthrough: true,
}
}
///|
fn HotPipelineInstrScanSummary::mark_dce_candidate(
self : HotPipelineInstrScanSummary,
) -> Unit {
self.has_dce_candidate = true
}
///|
fn hot_pipeline_instr_scan_merge(
summary : HotPipelineInstrScanSummary,
nested : HotPipelineInstrScanSummary,
) -> Unit {
summary.has_writes = summary.has_writes || nested.has_writes
summary.has_structured_control = summary.has_structured_control ||
nested.has_structured_control
summary.has_branches = summary.has_branches || nested.has_branches
summary.has_drop = summary.has_drop || nested.has_drop
summary.has_constant_result_if = summary.has_constant_result_if ||
nested.has_constant_result_if
summary.has_dead_tail_after_nonfallthrough = summary.has_dead_tail_after_nonfallthrough ||
nested.has_dead_tail_after_nonfallthrough
summary.has_dead_drop_after_nonfallthrough = summary.has_dead_drop_after_nonfallthrough ||
nested.has_dead_drop_after_nonfallthrough
summary.has_dce_candidate = summary.has_dce_candidate ||
nested.has_dce_candidate
summary.has_call_result_lifetime = summary.has_call_result_lifetime ||
nested.has_call_result_lifetime
if nested.min_branch_target_token < summary.min_branch_target_token {
summary.min_branch_target_token = nested.min_branch_target_token
}
summary.instr_count += nested.instr_count
}
///|
priv struct HotPipelineInstrScanState {
control_tokens : Array[Int32]
targeted_controls : @bitset.BitSet
mut next_control_token : Int32
visit_count : Array[Int32]?
}
///|
fn HotPipelineInstrScanState::new(
visit_count : Array[Int32]?,
) -> HotPipelineInstrScanState {
{
control_tokens: [],
targeted_controls: @bitset.BitSet::new(0),
next_control_token: 0,
visit_count,
}
}
///|
fn hot_pipeline_instr_scan_push_control(
state : HotPipelineInstrScanState,
) -> Int32 {
let token = state.next_control_token
state.next_control_token += 1
state.targeted_controls.grow(state.next_control_token)
state.control_tokens.push(token)
token
}
///|
fn hot_pipeline_instr_scan_pop_control(
state : HotPipelineInstrScanState,
) -> Unit {
ignore(state.control_tokens.pop())
}
///|
fn hot_pipeline_instr_scan_mark_branch_target(
state : HotPipelineInstrScanState,
summary : HotPipelineInstrScanSummary,
label_idx : @lib.LabelIdx,
) -> Unit {
let @lib.LabelIdx(raw) = label_idx
let target_slot = state.control_tokens.length() - 1 - raw.reinterpret_as_int()
if target_slot < 0 || target_slot >= state.control_tokens.length() {
return
}
let target_token = state.control_tokens[target_slot]
state.targeted_controls[target_token] = true
if target_token < summary.min_branch_target_token {
summary.min_branch_target_token = target_token
}
}
///|
fn hot_pipeline_instr_scan_record_branch_targets(
state : HotPipelineInstrScanState,
summary : HotPipelineInstrScanSummary,
instr : @lib.Instruction,
) -> Unit {
match instr {
@lib.Br(target)
| @lib.BrIf(target)
| @lib.BrOnNull(target)
| @lib.BrOnNonNull(target)
| @lib.BrOnCast(target, _, _, _)
| @lib.BrOnCastFail(target, _, _, _)
| @lib.BrOnCastDescEq(target, _, _)
| @lib.BrOnCastDescEqFail(target, _, _) =>
hot_pipeline_instr_scan_mark_branch_target(state, summary, target)
@lib.BrTable(targets, default_target) => {
for target in targets {
hot_pipeline_instr_scan_mark_branch_target(state, summary, target)
}
hot_pipeline_instr_scan_mark_branch_target(state, summary, default_target)
}
_ => ()
}
}
///|
fn hot_pipeline_instr_scan_has_call_result_multi_call_lifetime_at(
instrs : Array[@lib.Instruction],
idx : Int32,
) -> Bool {
if idx + 1 >= instrs.length() {
return false
}
let local_idx = match instrs[idx + 1] {
@lib.LocalSet(local_idx) => local_idx
_ => return false
}
let mut first_call : @lib.FuncIdx? = None
let mut saw_indirect_call = false
let scan_end = Int32::min(instrs.length(), idx + 34)
for scan = idx + 2; scan < scan_end; scan = scan + 1 {
match instrs[scan] {
@lib.LocalGet(candidate) if candidate == local_idx => {
let look_end = Int32::min(scan_end, scan + 12)
for look = scan + 1; look < look_end; look = look + 1 {
match instrs[look] {
@lib.Call(call_target) => {
if saw_indirect_call {
return true
}
match first_call {
Some(first_target) if first_target != call_target => return true
None => first_call = Some(call_target)
_ => ()
}
break
}
@lib.CallIndirect(_, _) | @lib.CallRef(_) => {
match first_call {
Some(_) => return true
None => ()
}
if saw_indirect_call {
return true
}
saw_indirect_call = true
break
}
@lib.Block(_, _)
| @lib.Loop(_, _)
| @lib.If(_, _, _)
| @lib.Try(_, _, _, _)
| @lib.TryTable(_, _, _)
| @lib.Br(_)
| @lib.BrIf(_)
| @lib.BrTable(_, _)
| @lib.Return
| @lib.Unreachable => break
_ => ()
}
}
}
_ => ()
}
}
false
}
///|
fn run_hot_pipeline_instr_scan_impl(
instrs : Array[@lib.Instruction],
state : HotPipelineInstrScanState,
) -> HotPipelineInstrScanSummary {
let summary = HotPipelineInstrScanSummary::new()
let mut prev_may_fallthrough = true
for instr_idx = 0; instr_idx < instrs.length(); instr_idx = instr_idx + 1 {
let instr = instrs[instr_idx]
match state.visit_count {
Some(visits) => visits[0] += 1
None => ()
}
if !prev_may_fallthrough {
summary.has_dead_tail_after_nonfallthrough = true
summary.mark_dce_candidate()
match instr {
@lib.Drop => summary.has_dead_drop_after_nonfallthrough = true
_ => ()
}
}
if instr_idx > 0 && instr is @lib.Unreachable {
summary.mark_dce_candidate()
}
match instr {
@lib.Call(_) | @lib.CallIndirect(_, _) | @lib.CallRef(_) =>
if !summary.has_call_result_lifetime &&
instr_idx + 1 < instrs.length() &&
instrs[instr_idx + 1] is @lib.LocalSet(_) &&
hot_pipeline_instr_scan_has_call_result_multi_call_lifetime_at(
instrs, instr_idx,
) {
summary.has_call_result_lifetime = true
}
_ => ()
}
match instr {
@lib.LocalGet(base0) if instr_idx + 4 < instrs.length() => {
let load = instrs[instr_idx + 1]
match
(instrs[instr_idx + 2], instrs[instr_idx + 3], instrs[instr_idx + 4]) {
(@lib.LocalGet(base1), call, @lib.LocalSet(_)) =>
if base0 == base1 &&
(
load is @lib.I32Load(_) ||
load is @lib.I64Load(_) ||
load is @lib.F32Load(_) ||
load is @lib.F64Load(_)
) &&
(
call is @lib.Call(_) ||
call is @lib.CallIndirect(_, _) ||
call is @lib.CallRef(_)
) {
summary.mark_dce_candidate()
}
_ => ()
}
}
_ => ()
}
summary.instr_count += 1
hot_pipeline_instr_scan_record_branch_targets(state, summary, instr)
let mut instr_may_fallthrough = true
match instr {
@lib.LocalSet(_) | @lib.LocalTee(_) => summary.has_writes = true
@lib.Drop => {
summary.has_drop = true
if instr_idx > 0 {
match instrs[instr_idx - 1] {
@lib.Block(block_type, _)
| @lib.Loop(block_type, _)
| @lib.If(block_type, _, _)
| @lib.Try(block_type, _, _, _)
| @lib.TryTable(block_type, _, _) =>
if block_type != @lib.BlockType::void_() {
summary.mark_dce_candidate()
}
_ => ()
}
}
}
@lib.StructNew(_)
| @lib.StructNewDefault(_)
| @lib.StructNewDesc(_)
| @lib.StructNewDefaultDesc(_)
| @lib.ArrayNew(_)
| @lib.ArrayNewDefault(_)
| @lib.ArrayNewFixed(_, _)
| @lib.ArrayNewData(_, _)
| @lib.ArrayNewElem(_, _) => summary.mark_dce_candidate()
@lib.Br(_)
| @lib.BrIf(_)
| @lib.BrTable(_, _)
| @lib.BrOnNull(_)
| @lib.BrOnNonNull(_)
| @lib.BrOnCast(_, _, _, _)
| @lib.BrOnCastFail(_, _, _, _)
| @lib.BrOnCastDescEq(_, _, _)
| @lib.BrOnCastDescEqFail(_, _, _) => summary.has_branches = true
_ => ()
}
match instr {
@lib.Br(_)
| @lib.BrTable(_, _)
| @lib.Return
| @lib.ReturnCall(_)
| @lib.ReturnCallIndirect(_, _)
| @lib.ReturnCallRef(_)
| @lib.Throw(_)
| @lib.ThrowRef
| @lib.Unreachable => instr_may_fallthrough = false
@lib.Block(_, @lib.Expr(body)) => {
summary.has_structured_control = true
let token = hot_pipeline_instr_scan_push_control(state)
let nested = run_hot_pipeline_instr_scan_impl(body, state)
let targeted = state.targeted_controls[token]
hot_pipeline_instr_scan_pop_control(state)
hot_pipeline_instr_scan_merge(summary, nested)
if !nested.may_fallthrough && !targeted {
summary.mark_dce_candidate()
}
instr_may_fallthrough = nested.may_fallthrough || targeted
}
@lib.Loop(_, @lib.Expr(body)) => {
summary.has_structured_control = true
let token = hot_pipeline_instr_scan_push_control(state)
let nested = run_hot_pipeline_instr_scan_impl(body, state)
hot_pipeline_instr_scan_pop_control(state)
hot_pipeline_instr_scan_merge(summary, nested)
if (body.length() == 1 && body[0] is @lib.Unreachable) ||
nested.min_branch_target_token < token {
summary.mark_dce_candidate()
}
instr_may_fallthrough = nested.may_fallthrough
}
@lib.If(block_type, then_body, else_body) => {
summary.has_structured_control = true
if block_type != @lib.BlockType::void_() &&
instr_idx > 0 &&
instrs[instr_idx - 1] is @lib.I32Const(_) {
summary.has_constant_result_if = true
}
let token = hot_pipeline_instr_scan_push_control(state)
let then_summary = run_hot_pipeline_instr_scan_impl(then_body, state)
hot_pipeline_instr_scan_merge(summary, then_summary)
let else_may_fallthrough = match else_body {
Some(else_body) => {
let else_summary = run_hot_pipeline_instr_scan_impl(
else_body, state,
)
hot_pipeline_instr_scan_merge(summary, else_summary)
else_summary.may_fallthrough
}
None => true
}
let targeted = state.targeted_controls[token]
hot_pipeline_instr_scan_pop_control(state)
if block_type != @lib.BlockType::void_() &&
!then_summary.may_fallthrough &&
!else_may_fallthrough &&
!targeted {
summary.mark_dce_candidate()
}
instr_may_fallthrough = then_summary.may_fallthrough ||
else_may_fallthrough ||
targeted
}
@lib.Try(_, @lib.Expr(body), catches, delegate) => {
summary.has_structured_control = true
// A legacy delegate depth is relative to the surrounding scope, not
// the Try label installed for its protected body and handlers.
match delegate {
Some(target) => {
summary.has_branches = true
hot_pipeline_instr_scan_mark_branch_target(state, summary, target)
}
None => ()
}
let token = hot_pipeline_instr_scan_push_control(state)
let body_summary = run_hot_pipeline_instr_scan_impl(body, state)
hot_pipeline_instr_scan_merge(summary, body_summary)
let mut any_arm_may_fallthrough = body_summary.may_fallthrough
for catch_ in catches {
let catch_body = match catch_ {
@lib.LegacyCatch(_, @lib.Expr(catch_body))
| @lib.LegacyCatchAll(@lib.Expr(catch_body)) => catch_body
}
let catch_summary = run_hot_pipeline_instr_scan_impl(
catch_body, state,
)
hot_pipeline_instr_scan_merge(summary, catch_summary)
any_arm_may_fallthrough = any_arm_may_fallthrough ||
catch_summary.may_fallthrough
}
let targeted = state.targeted_controls[token]
hot_pipeline_instr_scan_pop_control(state)
if !any_arm_may_fallthrough && !targeted {
summary.mark_dce_candidate()
}
instr_may_fallthrough = any_arm_may_fallthrough || targeted
}
@lib.TryTable(_, _, @lib.Expr(body)) => {
summary.has_structured_control = true
let token = hot_pipeline_instr_scan_push_control(state)
let nested = run_hot_pipeline_instr_scan_impl(body, state)
let targeted = state.targeted_controls[token]
hot_pipeline_instr_scan_pop_control(state)
hot_pipeline_instr_scan_merge(summary, nested)
if !nested.may_fallthrough && !targeted {
summary.mark_dce_candidate()
}
instr_may_fallthrough = nested.may_fallthrough || targeted
}
_ => ()
}
prev_may_fallthrough = instr_may_fallthrough
summary.may_fallthrough = summary.may_fallthrough && instr_may_fallthrough
}
if summary.has_drop && summary.has_branches {
summary.mark_dce_candidate()
}
summary
}
///|
fn run_hot_pipeline_instr_scan(
instrs : Array[@lib.Instruction],
visit_count~ : Array[Int32]? = None,
dead_drop_after_nonfallthrough~ : Array[Bool]? = None,
constant_result_if~ : Array[Bool]? = None,
dce_candidate~ : Array[Bool]? = None,
dce_call_result_lifetime~ : Array[Bool]? = None,
) -> (Bool, Bool, Bool, Bool, Bool, Int32) {
let summary = run_hot_pipeline_instr_scan_impl(
instrs,
HotPipelineInstrScanState::new(visit_count),
)
match dead_drop_after_nonfallthrough {
Some(result) => result[0] = summary.has_dead_drop_after_nonfallthrough
None => ()
}
match constant_result_if {
Some(result) => result[0] = summary.has_constant_result_if
None => ()
}
match dce_candidate {
Some(result) => result[0] = summary.has_dce_candidate
None => ()
}
match dce_call_result_lifetime {
Some(result) => result[0] = summary.has_call_result_lifetime
None => ()
}
(
summary.has_writes,
summary.has_structured_control,
summary.has_branches,
summary.has_drop,
summary.has_dead_tail_after_nonfallthrough,
summary.instr_count,
)
}
///|
fn run_hot_pipeline_raw_remove_unused_brs_has_throw(
instrs : Array[@lib.Instruction],
) -> Bool {
for instr in instrs {
match instr {
@lib.Throw(_) => return true
@lib.Block(_, @lib.Expr(body))
| @lib.Loop(_, @lib.Expr(body))
| @lib.TryTable(_, _, @lib.Expr(body)) =>
if run_hot_pipeline_raw_remove_unused_brs_has_throw(body) {
return true
}
@lib.If(_, then_body, else_body) => {
if run_hot_pipeline_raw_remove_unused_brs_has_throw(then_body) {
return true
}
match else_body {
Some(else_body) =>
if run_hot_pipeline_raw_remove_unused_brs_has_throw(else_body) {
return true
}
None => ()
}
}
_ => ()
}
}
false
}
///|
fn run_hot_pipeline_raw_remove_unused_brs_has_try_table_catch(
instrs : Array[@lib.Instruction],
) -> Bool {
for instr in instrs {
match instr {
@lib.TryTable(_, catches, @lib.Expr(body)) =>
if !catches.is_empty() ||
run_hot_pipeline_raw_remove_unused_brs_has_try_table_catch(body) {
return true
}
@lib.Block(_, @lib.Expr(body)) | @lib.Loop(_, @lib.Expr(body)) =>
if run_hot_pipeline_raw_remove_unused_brs_has_try_table_catch(body) {
return true
}
@lib.If(_, then_body, else_body) => {
if run_hot_pipeline_raw_remove_unused_brs_has_try_table_catch(then_body) {
return true
}
match else_body {
Some(else_body) =>
if run_hot_pipeline_raw_remove_unused_brs_has_try_table_catch(
else_body,
) {
return true
}
None => ()
}
}
@lib.Try(_, @lib.Expr(body), catches, _) => {
if run_hot_pipeline_raw_remove_unused_brs_has_try_table_catch(body) {
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 run_hot_pipeline_raw_remove_unused_brs_has_try_table_catch(
catch_body,
) {
return true
}
}
}
_ => ()
}
}
false
}
///|
fn run_hot_pipeline_raw_try_table_catch_body_has_local_write(
instrs : Array[@lib.Instruction],
) -> Bool {
for instr in instrs {
match instr {
@lib.TryTable(_, catches, @lib.Expr(body)) => {
if !catches.is_empty() &&
run_hot_pipeline_raw_vacuum_instrs_have_local_write(body) {
return true
}
if run_hot_pipeline_raw_try_table_catch_body_has_local_write(body) {
return true
}
}
@lib.Block(_, @lib.Expr(body)) | @lib.Loop(_, @lib.Expr(body)) =>
if run_hot_pipeline_raw_try_table_catch_body_has_local_write(body) {
return true
}
@lib.If(_, then_body, else_body) => {
if run_hot_pipeline_raw_try_table_catch_body_has_local_write(then_body) {
return true
}
match else_body {
Some(body) =>
if run_hot_pipeline_raw_try_table_catch_body_has_local_write(body) {
return true
}
None => ()
}
}
@lib.Try(_, @lib.Expr(body), catches, _) => {
if run_hot_pipeline_raw_try_table_catch_body_has_local_write(body) {
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 run_hot_pipeline_raw_try_table_catch_body_has_local_write(
catch_body,
) {
return true
}
}
}
_ => ()
}
}
false
}
///|
fn run_hot_pipeline_raw_has_caught_try_table(
instrs : Array[@lib.Instruction],
) -> Bool {
for instr in instrs {
match instr {
@lib.TryTable(_, catches, @lib.Expr(body)) =>
if !catches.is_empty() ||
run_hot_pipeline_raw_has_caught_try_table(body) {
return true
}
@lib.Block(_, @lib.Expr(body)) | @lib.Loop(_, @lib.Expr(body)) =>
if run_hot_pipeline_raw_has_caught_try_table(body) {
return true
}
@lib.If(_, then_body, else_body) => {
if run_hot_pipeline_raw_has_caught_try_table(then_body) {
return true
}
match else_body {
Some(body) =>
if run_hot_pipeline_raw_has_caught_try_table(body) {
return true
}
None => ()
}
}
@lib.Try(_, @lib.Expr(body), catches, _) => {
if run_hot_pipeline_raw_has_caught_try_table(body) {
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 run_hot_pipeline_raw_has_caught_try_table(catch_body) {
return true
}
}
}
_ => ()
}
}
false
}
///|
fn run_hot_pipeline_module_has_caught_try_table(mod_ : @lib.Module) -> Bool {
match mod_.code_sec {
Some(@lib.CodeSec(funcs)) =>
funcs.any(fn(func) {
let @lib.Func(_, @lib.Expr(body)) = func
run_hot_pipeline_raw_has_caught_try_table(body)
})
None => false
}
}
///|
fn run_hot_pipeline_module_has_try_table_catch_body_local_write(
mod_ : @lib.Module,
) -> Bool {
match mod_.code_sec {
Some(@lib.CodeSec(funcs)) =>
funcs.any(fn(func) {
let @lib.Func(_, @lib.Expr(body)) = func
run_hot_pipeline_raw_try_table_catch_body_has_local_write(body)
})
None => false
}
}
///|
fn run_hot_pipeline_raw_remove_unused_brs_has_try_table_catch_shell(
instrs : Array[@lib.Instruction],
) -> Bool {
for instr in instrs {
match instr {
@lib.Block(_, @lib.Expr([@lib.Block(_, @lib.Expr(body))])) =>
if run_hot_pipeline_raw_remove_unused_brs_has_try_table_catch(body) {
return true
}
@lib.Block(_, @lib.Expr(body)) | @lib.Loop(_, @lib.Expr(body)) =>
if run_hot_pipeline_raw_remove_unused_brs_has_try_table_catch_shell(
body,
) {
return true
}
@lib.If(_, then_body, else_body) => {
if run_hot_pipeline_raw_remove_unused_brs_has_try_table_catch_shell(
then_body,
) {
return true
}
match else_body {
Some(else_body) =>
if run_hot_pipeline_raw_remove_unused_brs_has_try_table_catch_shell(
else_body,
) {
return true
}
None => ()
}
}
@lib.TryTable(_, _, @lib.Expr(body)) =>
if run_hot_pipeline_raw_remove_unused_brs_has_try_table_catch_shell(
body,
) {
return true
}
@lib.Try(_, @lib.Expr(body), catches, _) => {
if run_hot_pipeline_raw_remove_unused_brs_has_try_table_catch_shell(
body,
) {
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 run_hot_pipeline_raw_remove_unused_brs_has_try_table_catch_shell(
catch_body,
) {
return true
}
}
}
_ => ()
}
}
false
}
///|
fn run_hot_pipeline_instr_has_remove_unused_brs_candidate(
instrs : Array[@lib.Instruction],
) -> Bool {
if run_hot_pipeline_raw_remove_unused_brs_has_try_table_catch_shell(instrs) {
return true
}
// A type-indexed result block followed by at least two drops may be a fully
// dropped multivalue result. Admit it so RUB's opt-in lowering can flatten
// exact literal-result blocks; the HOT proof remains authoritative.
for idx = 0; idx + 2 < instrs.length(); idx = idx + 1 {
match (instrs[idx], instrs[idx + 1], instrs[idx + 2]) {
(@lib.Block(@lib.TypeIdxBlockType(_), _), @lib.Drop, @lib.Drop) =>
return true
_ => ()
}
}
for instr in instrs {
match instr {
@lib.Nop
| @lib.If(_, _, _)
| @lib.Br(_)
| @lib.BrIf(_)
| @lib.BrTable(_, _)
| @lib.BrOnNull(_)
| @lib.BrOnNonNull(_)
| @lib.BrOnCast(_, _, _, _)
| @lib.BrOnCastFail(_, _, _, _)
| @lib.BrOnCastDescEq(_, _, _)
| @lib.BrOnCastDescEqFail(_, _, _)
| @lib.Return => return true
@lib.Block(_, @lib.Expr(body)) | @lib.Loop(_, @lib.Expr(body)) =>
if run_hot_pipeline_instr_has_remove_unused_brs_candidate(body) {
return true
}
@lib.TryTable(_, _, @lib.Expr(body)) =>
if run_hot_pipeline_raw_remove_unused_brs_has_throw(body) ||
run_hot_pipeline_instr_has_remove_unused_brs_candidate(body) {
return true
}
_ => ()
}
}
false
}
///|
priv struct RawRemoveUnusedBrsShape {
mut call_count : Int32
mut local_set_count : Int32
mut local_tee_count : Int32
mut if_count : Int32
mut return_count : Int32
mut select_count : Int32
mut br_if_count : Int32
mut block_count : Int32
mut value_block_count : Int32
mut void_block_count : Int32
mut loop_count : Int32
mut br_count : Int32
mut br_table_count : Int32
mut drop_count : Int32
}
///|
fn RawRemoveUnusedBrsShape::new() -> RawRemoveUnusedBrsShape {
RawRemoveUnusedBrsShape::with_counts()
}
///|
fn RawRemoveUnusedBrsShape::with_counts(
call_count~ : Int32 = 0,
local_set_count~ : Int32 = 0,
local_tee_count~ : Int32 = 0,
if_count~ : Int32 = 0,
return_count~ : Int32 = 0,
select_count~ : Int32 = 0,
br_if_count~ : Int32 = 0,
block_count~ : Int32 = 0,
value_block_count~ : Int32 = 0,
void_block_count~ : Int32 = 0,
loop_count~ : Int32 = 0,
br_count~ : Int32 = 0,
br_table_count~ : Int32 = 0,
drop_count~ : Int32 = 0,
) -> RawRemoveUnusedBrsShape {
{
call_count,
local_set_count,
local_tee_count,
if_count,
return_count,
select_count,
br_if_count,
block_count,
value_block_count,
void_block_count,
loop_count,
br_count,
br_table_count,
drop_count,
}
}
///|
fn run_hot_pipeline_raw_remove_unused_brs_scan_shape(
instrs : Array[@lib.Instruction],
shape : RawRemoveUnusedBrsShape,
) -> Unit {
for instr in instrs {
match instr {
@lib.Call(_)
| @lib.CallIndirect(_, _)
| @lib.CallRef(_)
| @lib.ReturnCall(_)
| @lib.ReturnCallIndirect(_, _)
| @lib.ReturnCallRef(_) => shape.call_count = shape.call_count + 1
@lib.LocalSet(_) => shape.local_set_count = shape.local_set_count + 1
@lib.LocalTee(_) => shape.local_tee_count = shape.local_tee_count + 1
@lib.If(_, then_body, else_body) => {
shape.if_count = shape.if_count + 1
run_hot_pipeline_raw_remove_unused_brs_scan_shape(then_body, shape)
match else_body {
Some(else_body) =>
run_hot_pipeline_raw_remove_unused_brs_scan_shape(else_body, shape)
None => ()
}
}
@lib.Return => shape.return_count = shape.return_count + 1
@lib.Select(_) => shape.select_count = shape.select_count + 1
@lib.BrIf(_) => shape.br_if_count = shape.br_if_count + 1
@lib.Block(bt, @lib.Expr(body)) => {
shape.block_count = shape.block_count + 1
match bt {
@lib.ValTypeBlockType(_) =>
shape.value_block_count = shape.value_block_count + 1
@lib.TypeIdxBlockType(_) =>
shape.value_block_count = shape.value_block_count + 1
@lib.VoidBlockType =>
shape.void_block_count = shape.void_block_count + 1
}
run_hot_pipeline_raw_remove_unused_brs_scan_shape(body, shape)
}
@lib.Loop(_, @lib.Expr(body)) => {
shape.loop_count = shape.loop_count + 1
run_hot_pipeline_raw_remove_unused_brs_scan_shape(body, shape)
}
@lib.TryTable(_, _, @lib.Expr(body)) =>
run_hot_pipeline_raw_remove_unused_brs_scan_shape(body, shape)
@lib.Br(_) => shape.br_count = shape.br_count + 1
@lib.BrTable(_, _) => shape.br_table_count = shape.br_table_count + 1
@lib.Drop => shape.drop_count = shape.drop_count + 1
_ => ()
}
}
}
///|
fn run_hot_pipeline_raw_remove_unused_brs_leading_block_chain_depth(
instrs : Array[@lib.Instruction],
) -> Int32 {
let mut depth = 0
let mut cursor = instrs
while cursor.length() > 0 {
match cursor[0] {
@lib.Block(@lib.VoidBlockType, @lib.Expr(body)) => {
depth = depth + 1
cursor = body
}
_ => return depth
}
}
depth
}
///|
fn run_hot_pipeline_raw_remove_unused_brs_leading_any_block_chain_depth(
instrs : Array[@lib.Instruction],
) -> Int32 {
let mut depth = 0
let mut cursor = instrs
while cursor.length() > 0 {
match cursor[0] {
@lib.Block(_, @lib.Expr(body)) => {
depth = depth + 1
cursor = body
}
_ => return depth
}
}
depth
}
///|
// Raw RUB threshold policy.
//
// The `large`, `moderate`, and structured-ladder predicates below are
// empirical performance envelopes for shapes already proved to be pass
// no-ops. Their numeric bounds are not WebAssembly legality rules. Keep each
// accepted family paired with a stable trace reason and boundary tests before
// widening it. The SIMD parser and `unchecked` families are stricter
// correctness/writeback guards and must not be treated as tuning knobs.
fn run_hot_pipeline_raw_remove_unused_brs_can_skip_large_result_br_table_dispatch_ladder(
instrs : Array[@lib.Instruction],
) -> Bool {
if instrs.length() != 1 {
return false
}
let body = match instrs[0] {
@lib.Block(@lib.ValTypeBlockType(_), @lib.Expr(body)) => body
_ => return false
}
if run_hot_pipeline_raw_remove_unused_brs_leading_block_chain_depth(body) < 32 {
return false
}
if run_hot_pipeline_raw_remove_unused_brs_has_hot_only_candidates(body) {
return false
}
let shape = RawRemoveUnusedBrsShape::new()
run_hot_pipeline_raw_remove_unused_brs_scan_shape(body, shape)
shape.block_count >= 32 &&
(
shape.br_count == shape.block_count ||
shape.br_count + 1 == shape.block_count
) &&
shape.br_table_count == 1 &&
shape.local_set_count == 0 &&
shape.local_tee_count == 0 &&
shape.if_count == 0 &&
shape.return_count == 0 &&
shape.br_if_count == 0 &&
shape.loop_count == 0 &&
shape.drop_count == 0
}
///|
fn run_hot_pipeline_raw_remove_unused_brs_can_skip_large_value_if_branch_ladder(
locals : @lib.Locals,
instrs : Array[@lib.Instruction],
) -> Bool {
if locals.length() < 1 || locals.length() > 4 || instrs.length() > 2 {
return false
}
if run_hot_pipeline_raw_remove_unused_brs_leading_block_chain_depth(instrs) <
8 {
return false
}
let (_, has_structured_control, has_branches, _, _, instr_count) = run_hot_pipeline_instr_scan(
instrs,
)
if !has_structured_control ||
!has_branches ||
instr_count < 600 ||
instr_count > 3000 {
return false
}
let shape = RawRemoveUnusedBrsShape::new()
run_hot_pipeline_raw_remove_unused_brs_scan_shape(instrs, shape)
shape.call_count >= 40 &&
shape.call_count <= 160 &&
shape.local_set_count == 0 &&
shape.local_tee_count == 1 &&
shape.if_count >= 128 &&
shape.if_count <= 400 &&
shape.return_count == shape.block_count &&
shape.return_count >= 8 &&
shape.return_count <= 16 &&
shape.select_count == 0 &&
shape.br_if_count == 0 &&
shape.block_count >= 8 &&
shape.block_count <= 16 &&
shape.loop_count == 0 &&
shape.br_count >= 120 &&
shape.br_count <= shape.if_count &&
shape.br_count + 8 >= shape.if_count &&
shape.br_table_count == 0 &&
shape.drop_count <= 1
}
///|
fn run_hot_pipeline_raw_remove_unused_brs_can_skip_large_drop_heavy_branch_ladder(
locals : @lib.Locals,
instrs : Array[@lib.Instruction],
) -> Bool {
if locals.length() < 400 || locals.length() > 550 {
return false
}
let (_, has_structured_control, has_branches, has_drop, _, instr_count) = run_hot_pipeline_instr_scan(
instrs,
)
if !has_structured_control ||
!has_branches ||
!has_drop ||
instr_count < 1600 ||
instr_count > 8000 {
return false
}
let shape = RawRemoveUnusedBrsShape::new()
run_hot_pipeline_raw_remove_unused_brs_scan_shape(instrs, shape)
shape.call_count >= 300 &&
shape.call_count <= 360 &&
shape.local_set_count >= 200 &&
shape.local_set_count <= 260 &&
shape.local_tee_count >= 220 &&
shape.local_tee_count <= 300 &&
shape.if_count >= 132 &&
shape.if_count <= 170 &&
shape.return_count <= 4 &&
shape.select_count == 0 &&
shape.br_if_count == 0 &&
shape.block_count >= 90 &&
shape.block_count <= 130 &&
shape.loop_count <= 2 &&
shape.br_count >= 90 &&
shape.br_count <= 120 &&
shape.br_table_count == 0 &&
shape.drop_count >= 90 &&
shape.drop_count <= 140
}
///|
fn run_hot_pipeline_raw_remove_unused_brs_can_skip_large_typed_br_table_encoder_ladder(
locals : @lib.Locals,
instrs : Array[@lib.Instruction],
) -> Bool {
if locals.length() < 1024 || locals.length() > 5000 {
return false
}
if run_hot_pipeline_raw_remove_unused_brs_leading_any_block_chain_depth(
instrs,
) <
32 {
return false
}
let (_, has_structured_control, has_branches, has_drop, _, instr_count) = run_hot_pipeline_instr_scan(
instrs,
)
if !has_structured_control ||
!has_branches ||
!has_drop ||
instr_count < 3000 ||
instr_count > 200000 {
return false
}
let shape = RawRemoveUnusedBrsShape::new()
run_hot_pipeline_raw_remove_unused_brs_scan_shape(instrs, shape)
shape.call_count >= 1000 &&
shape.local_set_count >= 700 &&
shape.local_tee_count >= 500 &&
shape.if_count >= 500 &&
shape.return_count >= 250 &&
shape.select_count == 0 &&
shape.br_if_count == 0 &&
shape.block_count >= 500 &&
shape.value_block_count >= 200 &&
shape.void_block_count >= 250 &&
shape.loop_count <= 4 &&
shape.br_count >= 500 &&
shape.br_table_count == 1 &&
shape.drop_count >= 200
}
///|
fn run_hot_pipeline_raw_remove_unused_brs_can_skip_large_void_if_return_ladder(
locals : @lib.Locals,
instrs : Array[@lib.Instruction],
) -> Bool {
let (_, has_structured_control, _, _, _, instr_count) = run_hot_pipeline_instr_scan(
instrs,
)
if !has_structured_control || locals.length() < 700 {
return false
}
let shape = RawRemoveUnusedBrsShape::new()
run_hot_pipeline_raw_remove_unused_brs_scan_shape(instrs, shape)
instr_count >= 4000 &&
shape.call_count >= 900 &&
shape.local_set_count >= 350 &&
shape.local_tee_count >= 350 &&
shape.if_count >= 150 &&
shape.if_count >= shape.return_count &&
shape.return_count >= 90 &&
shape.select_count <= 4 &&
shape.br_if_count <= 2 &&
shape.loop_count <= 4 &&
shape.br_table_count <= 4 &&
shape.drop_count <= 512 &&
(shape.br_table_count == 0 || shape.drop_count <= 32)
}
///|
fn run_hot_pipeline_raw_remove_unused_brs_can_skip_moderate_control_ladder(
locals : @lib.Locals,
instrs : Array[@lib.Instruction],
) -> Bool {
let (_, has_structured_control, _, _, _, instr_count) = run_hot_pipeline_instr_scan(
instrs,
)
if !has_structured_control || locals.length() < 180 || locals.length() > 700 {
return false
}
let shape = RawRemoveUnusedBrsShape::new()
run_hot_pipeline_raw_remove_unused_brs_scan_shape(instrs, shape)
let matches_medium_control = locals.length() >= 300 &&
instr_count >= 400 &&
instr_count <= 10000 &&
shape.call_count >= 120 &&
shape.call_count <= 700 &&
shape.local_set_count >= 100 &&
shape.local_set_count <= 700 &&
shape.local_tee_count >= 100 &&
shape.local_tee_count <= 400 &&
shape.if_count >= 40 &&
shape.if_count <= 220 &&
shape.return_count <= 128 &&
shape.select_count <= 16 &&
shape.br_if_count <= 5 &&
shape.block_count >= 20 &&
shape.block_count <= 200 &&
shape.loop_count <= 2 &&
shape.br_count <= 320 &&
shape.br_table_count <= 1 &&
shape.drop_count <= 200 &&
shape.call_count + shape.local_set_count + shape.local_tee_count >= 400 &&
shape.if_count + shape.block_count >= 60
let matches_medium_block_dispatch = locals.length() <= 320 &&
instr_count >= 800 &&
instr_count <= 5000 &&
shape.call_count >= 100 &&
shape.call_count <= 450 &&
shape.local_set_count >= 80 &&
shape.local_set_count <= 220 &&
shape.local_tee_count >= 40 &&
shape.local_tee_count <= 160 &&
shape.if_count >= 8 &&
shape.if_count <= 64 &&
shape.return_count <= 96 &&
shape.select_count <= 4 &&
shape.br_if_count <= 1 &&
shape.block_count >= 80 &&
shape.block_count <= 360 &&
shape.loop_count <= 1 &&
shape.br_count >= 80 &&
shape.br_count <= 400 &&
shape.br_table_count == 1 &&
shape.drop_count <= 32 &&
shape.block_count + shape.br_count >= 200
matches_medium_control || matches_medium_block_dispatch
}
///|
fn run_hot_pipeline_raw_remove_unused_brs_can_skip_structured_return_ladder(
locals : @lib.Locals,
instrs : Array[@lib.Instruction],
) -> Bool {
if locals.length() < 20 || locals.length() > 96 {
return false
}
let (_, has_structured_control, has_branches, _, _, instr_count) = run_hot_pipeline_instr_scan(
instrs,
)
if !has_structured_control ||
!has_branches ||
instr_count < 80 ||
instr_count > 1200 {
return false
}
let shape = RawRemoveUnusedBrsShape::new()
run_hot_pipeline_raw_remove_unused_brs_scan_shape(instrs, shape)
shape.call_count >= 10 &&
shape.local_set_count >= 10 &&
shape.local_tee_count >= 8 &&
shape.if_count >= 4 &&
shape.if_count <= 18 &&
shape.return_count >= 1 &&
shape.return_count <= 5 &&
shape.select_count <= 1 &&
shape.br_if_count <= 1 &&
shape.block_count >= 1 &&
shape.block_count <= 16 &&
shape.loop_count <= 2 &&
shape.br_count >= 1 &&
shape.br_count <= 16 &&
shape.drop_count <= 16
}
///|
fn run_hot_pipeline_raw_remove_unused_brs_unchecked_moderate_call_mesh_stats(
locals_count : Int32,
instr_count : Int32,
has_structured_control : Bool,
has_branches : Bool,
has_drop : Bool,
has_dead_tail : Bool,
shape : RawRemoveUnusedBrsShape,
has_condition_child_value_if : Bool,
has_prefix_guard_payload_branch : Bool,
) -> Bool {
locals_count >= 24 &&
locals_count <= 96 &&
has_structured_control &&
has_branches &&
has_drop &&
has_dead_tail &&
instr_count >= 400 &&
instr_count <= 640 &&
shape.call_count >= 96 &&
shape.call_count <= 128 &&
shape.local_set_count >= 24 &&
shape.local_set_count <= 64 &&
shape.local_tee_count <= 8 &&
shape.if_count >= 16 &&
shape.if_count <= 32 &&
shape.return_count == 0 &&
shape.select_count <= 2 &&
shape.br_if_count == 0 &&
shape.block_count >= 8 &&
shape.block_count <= 16 &&
shape.loop_count <= 2 &&
shape.br_count >= 8 &&
shape.br_count <= 16 &&
shape.br_table_count == 0 &&
shape.drop_count <= 16 &&
!has_condition_child_value_if &&
!has_prefix_guard_payload_branch
}
///|
fn run_hot_pipeline_raw_simd_parser_br_table_stack_hazard_stats(
locals_count : Int32,
instr_count : Int32,
shape : RawRemoveUnusedBrsShape,
) -> Bool {
locals_count >= 20 &&
locals_count <= 40 &&
instr_count >= 8000 &&
instr_count <= 20000 &&
shape.call_count >= 100 &&
shape.call_count <= 220 &&
shape.local_set_count >= 600 &&
shape.local_set_count <= 850 &&
shape.local_tee_count >= 500 &&
shape.local_tee_count <= 760 &&
shape.if_count >= 600 &&
shape.if_count <= 850 &&
shape.return_count <= 4 &&
shape.select_count >= 40 &&
shape.select_count <= 120 &&
shape.br_if_count >= 150 &&
shape.br_if_count <= 280 &&
shape.block_count >= 100 &&
shape.block_count <= 240 &&
shape.value_block_count >= 35 &&
shape.value_block_count <= 90 &&
shape.void_block_count >= 60 &&
shape.void_block_count <= 150 &&
shape.loop_count >= 180 &&
shape.loop_count <= 340 &&
shape.br_count >= 320 &&
shape.br_count <= 520 &&
shape.br_table_count >= 1 &&
shape.br_table_count <= 4 &&
shape.drop_count >= 60 &&
shape.drop_count <= 180
}
///|
fn run_hot_pipeline_raw_can_skip_simd_parser_br_table_stack_hazard(
locals : @lib.Locals,
instrs : Array[@lib.Instruction],
) -> Bool {
let (_, _, _, _, _, instr_count) = run_hot_pipeline_instr_scan(instrs)
let shape = RawRemoveUnusedBrsShape::new()
run_hot_pipeline_raw_remove_unused_brs_scan_shape(instrs, shape)
run_hot_pipeline_raw_simd_parser_br_table_stack_hazard_stats(
locals.length(),
instr_count,
shape,
)
}
///|
fn run_hot_pipeline_raw_remove_unused_brs_can_skip_unchecked_moderate_call_mesh(
locals : @lib.Locals,
instrs : Array[@lib.Instruction],
) -> Bool {
let (
_,
has_structured_control,
has_branches,
has_drop,
has_dead_tail,
instr_count,
) = run_hot_pipeline_instr_scan(instrs)
let shape = RawRemoveUnusedBrsShape::new()
run_hot_pipeline_raw_remove_unused_brs_scan_shape(instrs, shape)
run_hot_pipeline_raw_remove_unused_brs_unchecked_moderate_call_mesh_stats(
locals.length(),
instr_count,
has_structured_control,
has_branches,
has_drop,
has_dead_tail,
shape,
run_hot_pipeline_raw_remove_unused_brs_has_condition_child_value_if_candidate(
instrs,
),
run_hot_pipeline_raw_remove_unused_brs_has_prefix_guard_payload_branch_candidate(
instrs,
),
)
}
///|
fn run_hot_pipeline_raw_remove_unused_brs_can_skip_unique_loop_select_return_ladder(
locals : @lib.Locals,
instrs : Array[@lib.Instruction],
) -> Bool {
if locals.length() < 48 || locals.length() > 64 {
return false
}
let (_, has_structured_control, has_branches, _, _, instr_count) = run_hot_pipeline_instr_scan(
instrs,
)
if !has_structured_control ||
!has_branches ||
instr_count < 140 ||
instr_count > 480 {
return false
}
let shape = RawRemoveUnusedBrsShape::new()
run_hot_pipeline_raw_remove_unused_brs_scan_shape(instrs, shape)
shape.call_count >= 30 &&
shape.call_count <= 50 &&
shape.local_set_count >= 30 &&
shape.local_set_count <= 45 &&
shape.local_tee_count >= 16 &&
shape.local_tee_count <= 50 &&
shape.if_count >= 4 &&
shape.if_count <= 6 &&
shape.return_count == 3 &&
shape.select_count == 1 &&
shape.br_if_count == 1 &&
shape.block_count >= 1 &&
shape.block_count <= 4 &&
shape.loop_count == 1 &&
shape.br_count >= 1 &&
shape.br_count <= 4 &&
shape.drop_count >= 1 &&
shape.drop_count <= 12
}
///|
fn run_hot_pipeline_raw_remove_unused_brs_match_i32_const(
instr : @lib.Instruction,
) -> @lib.I32? {
match instr {
@lib.I32Const(value) => Some(value)
_ => None
}
}
///|
fn run_hot_pipeline_raw_remove_unused_brs_rewrite_decision_ladder_instrs(
instrs : Array[@lib.Instruction],
) -> (Array[@lib.Instruction], Int32, Bool) {
let normalized : Array[@lib.Instruction] = []
let mut rewrite_count = 0
let mut saw_br_table = false
for instr in instrs {
match instr {
@lib.Block(bt, @lib.Expr(body)) => {
let (rewritten_body, nested_rewrites, nested_br_table) = run_hot_pipeline_raw_remove_unused_brs_rewrite_decision_ladder_instrs(
body,
)
normalized.push(
@lib.Instruction::block(bt, @lib.Expr::new(rewritten_body)),
)
rewrite_count = rewrite_count + nested_rewrites
saw_br_table = saw_br_table || nested_br_table
}
@lib.Loop(bt, @lib.Expr(body)) => {
let (rewritten_body, nested_rewrites, nested_br_table) = run_hot_pipeline_raw_remove_unused_brs_rewrite_decision_ladder_instrs(
body,
)
normalized.push(
@lib.Instruction::loop_(bt, @lib.Expr::new(rewritten_body)),
)
rewrite_count = rewrite_count + nested_rewrites
saw_br_table = saw_br_table || nested_br_table
}
@lib.TryTable(bt, catches, @lib.Expr(body)) => {
let (rewritten_body, nested_rewrites, nested_br_table) = run_hot_pipeline_raw_remove_unused_brs_rewrite_decision_ladder_instrs(
body,
)
normalized.push(
@lib.Instruction::try_table(
bt,
catches,
@lib.Expr::new(rewritten_body),
),
)
rewrite_count = rewrite_count + nested_rewrites
saw_br_table = saw_br_table || nested_br_table
}
@lib.If(bt, then_body, else_body) => {
let (rewritten_then, then_rewrites, then_br_table) = run_hot_pipeline_raw_remove_unused_brs_rewrite_decision_ladder_instrs(
then_body,
)
rewrite_count = rewrite_count + then_rewrites
saw_br_table = saw_br_table || then_br_table
let rewritten_else = match else_body {
Some(else_body) => {
let (rewritten_else, else_rewrites, else_br_table) = run_hot_pipeline_raw_remove_unused_brs_rewrite_decision_ladder_instrs(
else_body,
)
rewrite_count = rewrite_count + else_rewrites
saw_br_table = saw_br_table || else_br_table
Some(rewritten_else)
}
None => None
}
normalized.push(
@lib.Instruction::if_(bt, rewritten_then, rewritten_else),
)
}
@lib.BrTable(_, _) => {
saw_br_table = true
normalized.push(instr)
}
_ => normalized.push(instr)
}
}
let rewritten : Array[@lib.Instruction] = []
let mut idx = 0
while idx < normalized.length() {
if idx + 3 < normalized.length() {
let instr0 = normalized[idx]
let instr1 = normalized[idx + 1]
let instr2 = normalized[idx + 2]
let instr3 = normalized[idx + 3]
match (instr0, instr1, instr2, instr3) {
(
@lib.LocalGet(local_id),
@lib.I32Const(cond_const),
@lib.I32Eq,
@lib.If(
@lib.ValTypeBlockType(result_type),
then_body,
Some(else_body)
),
) if result_type == @lib.ValType::i32() =>
match (then_body.get(0), else_body.get(0)) {
(Some(then_instr), Some(else_instr)) if then_body.length() == 1 &&
else_body.length() == 1 =>
match
(
run_hot_pipeline_raw_remove_unused_brs_match_i32_const(
then_instr,
),
run_hot_pipeline_raw_remove_unused_brs_match_i32_const(
else_instr,
),
) {
(Some(then_value), Some(else_value)) => {
rewritten.push(@lib.Instruction::i32_const(then_value))
rewritten.push(@lib.Instruction::i32_const(else_value))
rewritten.push(@lib.Instruction::local_get(local_id))
rewritten.push(@lib.Instruction::i32_const(cond_const))
rewritten.push(@lib.Instruction::i32_eq())
rewritten.push(@lib.Instruction::select())
rewrite_count = rewrite_count + 1
idx = idx + 4
continue
}
_ => ()
}
_ => ()
}
_ => ()
}
}
rewritten.push(normalized[idx])
idx = idx + 1
}
(rewritten, rewrite_count, saw_br_table)
}
///|
fn run_hot_pipeline_raw_remove_unused_brs_has_hot_only_candidates(
instrs : Array[@lib.Instruction],
) -> Bool {
for instr in instrs {
match instr {
@lib.Nop | @lib.If(_, _, _) | @lib.BrIf(_) | @lib.Return => return true
@lib.Block(_, @lib.Expr(body))
| @lib.Loop(_, @lib.Expr(body))
| @lib.TryTable(_, _, @lib.Expr(body)) =>
if run_hot_pipeline_raw_remove_unused_brs_has_hot_only_candidates(body) {
return true
}
_ => ()
}
}
false
}
///|
fn run_hot_pipeline_raw_remove_unused_brs_block_has_prefix_guard_payload_branch_candidate(
body : Array[@lib.Instruction],
) -> Bool {
if body.length() < 2 {
return false
}
let first = body[0]
let inner_body = match first {
@lib.Block(@lib.VoidBlockType, @lib.Expr(inner_body)) => inner_body
_ => return false
}
let zero = @lib.LabelIdx::new(0U)
let mut guard_idx = -1
let mut inner_idx = 0
while inner_idx < inner_body.length() {
match inner_body[inner_idx] {
@lib.BrIf(label_idx) =>
if label_idx == zero && inner_idx + 1 < inner_body.length() {
guard_idx = inner_idx
break
}
@lib.Nop
| @lib.Drop
| @lib.LocalSet(_)
| @lib.Br(_)
| @lib.BrTable(_, _)
| @lib.Return
| @lib.Block(@lib.VoidBlockType, _)
| @lib.Loop(_, _)
| @lib.TryTable(_, _, _)
| @lib.If(@lib.VoidBlockType, _, _)
| @lib.Unreachable => return false
_ => ()
}
inner_idx += 1
}
if guard_idx < 0 {
return false
}
let mut saw_payload = false
let mut body_idx = 1
while body_idx < body.length() {
match body[body_idx] {
@lib.Br(_) => if saw_payload { return true }
_ => saw_payload = true
}
body_idx += 1
}
false
}
///|
fn run_hot_pipeline_raw_remove_unused_brs_has_prefix_guard_payload_branch_candidate(
instrs : Array[@lib.Instruction],
) -> Bool {
for instr in instrs {
match instr {
@lib.Block(@lib.ValTypeBlockType(_), @lib.Expr(body)) =>
if run_hot_pipeline_raw_remove_unused_brs_block_has_prefix_guard_payload_branch_candidate(
body,
) ||
run_hot_pipeline_raw_remove_unused_brs_has_prefix_guard_payload_branch_candidate(
body,
) {
return true
}
@lib.Block(_, @lib.Expr(body))
| @lib.Loop(_, @lib.Expr(body))
| @lib.TryTable(_, _, @lib.Expr(body)) =>
if run_hot_pipeline_raw_remove_unused_brs_has_prefix_guard_payload_branch_candidate(
body,
) {
return true
}
@lib.If(_, then_body, else_body) => {
if run_hot_pipeline_raw_remove_unused_brs_has_prefix_guard_payload_branch_candidate(
then_body,
) {
return true
}
match else_body {
Some(else_body) =>
if run_hot_pipeline_raw_remove_unused_brs_has_prefix_guard_payload_branch_candidate(
else_body,
) {
return true
}
None => ()
}
}
_ => ()
}
}
false
}
///|
fn run_hot_pipeline_raw_remove_unused_brs_has_condition_child_value_if_candidate(
instrs : Array[@lib.Instruction],
) -> Bool {
let mut idx = 0
while idx < instrs.length() {
if idx + 1 < instrs.length() {
match (instrs[idx], instrs[idx + 1]) {
(
@lib.If(@lib.ValTypeBlockType(_), _, Some(_)),
@lib.If(@lib.VoidBlockType, _, None),
) => return true
_ => ()
}
}
match instrs[idx] {
@lib.Block(_, @lib.Expr(body))
| @lib.Loop(_, @lib.Expr(body))
| @lib.TryTable(_, _, @lib.Expr(body)) =>
if run_hot_pipeline_raw_remove_unused_brs_has_condition_child_value_if_candidate(
body,
) {
return true
}
@lib.If(_, then_body, else_body) => {
if run_hot_pipeline_raw_remove_unused_brs_has_condition_child_value_if_candidate(
then_body,
) {
return true
}
match else_body {
Some(else_body) =>
if run_hot_pipeline_raw_remove_unused_brs_has_condition_child_value_if_candidate(
else_body,
) {
return true
}
None => ()
}
}
_ => ()
}
idx = idx + 1
}
false
}
///|
priv struct RawSimplifyLocalsShape {
mut instr_count : Int32
mut if_count : Int32
mut block_count : Int32
mut loop_count : Int32
mut local_set_count : Int32
mut local_tee_count : Int32
mut local_get_count : Int32
mut call_count : Int32
mut return_count : Int32
mut exact_tail_count : Int32
mut has_global_state : Bool
mut has_memory_size : Bool
mut has_stack_effect_hazard : Bool
mut has_i32_add : Bool
mut has_v128_bitselect : Bool
mut has_i32x4_shr_u : Bool
mut has_simd_carrier_op : Bool
mut has_recurrence_binary : Bool
}
///|
priv struct RawSimplifyLocalsGateStats {
locals_count : Int32
instr_count : Int32
has_structured_control : Bool
if_count : Int32
block_count : Int32
loop_count : Int32
local_set_count : Int32
local_tee_count : Int32
local_get_count : Int32
call_count : Int32
return_count : Int32
exact_tail_count : Int32
has_global_state : Bool
has_memory_size : Bool
has_stack_effect_hazard : Bool
has_i32_add : Bool
has_v128_bitselect : Bool
has_i32x4_shr_u : Bool
has_simd_carrier_op : Bool
has_recurrence_binary : Bool
}
///|
fn RawSimplifyLocalsGateStats::new(
locals_count~ : Int32 = 0,
instr_count~ : Int32 = 0,
has_structured_control~ : Bool = false,
if_count~ : Int32 = 0,
block_count~ : Int32 = 0,
loop_count~ : Int32 = 0,
local_set_count~ : Int32 = 0,
local_tee_count~ : Int32 = 0,
local_get_count~ : Int32 = 0,
call_count~ : Int32 = 0,
return_count~ : Int32 = 0,
exact_tail_count~ : Int32 = 0,
has_global_state~ : Bool = false,
has_memory_size~ : Bool = false,
has_stack_effect_hazard~ : Bool = false,
has_i32_add~ : Bool = false,
has_v128_bitselect~ : Bool = false,
has_i32x4_shr_u~ : Bool = false,
has_simd_carrier_op~ : Bool = false,
has_recurrence_binary~ : Bool = false,
) -> RawSimplifyLocalsGateStats {
{
locals_count,
instr_count,
has_structured_control,
if_count,
block_count,
loop_count,
local_set_count,
local_tee_count,
local_get_count,
call_count,
return_count,
exact_tail_count,
has_global_state,
has_memory_size,
has_stack_effect_hazard,
has_i32_add,
has_v128_bitselect,
has_i32x4_shr_u,
has_simd_carrier_op,
has_recurrence_binary,
}
}
///|
fn RawSimplifyLocalsShape::new() -> RawSimplifyLocalsShape {
{
instr_count: 0,
if_count: 0,
block_count: 0,
loop_count: 0,
local_set_count: 0,
local_tee_count: 0,
local_get_count: 0,
call_count: 0,
return_count: 0,
exact_tail_count: 0,
has_global_state: false,
has_memory_size: false,
has_stack_effect_hazard: false,
has_i32_add: false,
has_v128_bitselect: false,
has_i32x4_shr_u: false,
has_simd_carrier_op: false,
has_recurrence_binary: false,
}
}
///|
fn run_hot_pipeline_raw_simplify_locals_has_call_bulk_memory_effect_mesh(
instrs : Array[@lib.Instruction],
) -> Bool {
let mut has_call = false
let mut has_bulk_memory = false
for instr in instrs {
match instr {
@lib.Call(_) | @lib.CallIndirect(_, _) | @lib.CallRef(_) =>
has_call = true
@lib.MemoryCopy(_, _) | @lib.MemoryFill(_) | @lib.MemoryInit(_, _) =>
has_bulk_memory = true
@lib.Block(_, @lib.Expr(body))
| @lib.Loop(_, @lib.Expr(body))
| @lib.TryTable(_, _, @lib.Expr(body)) =>
if run_hot_pipeline_raw_simplify_locals_has_call_bulk_memory_effect_mesh(
body,
) {
return true
}
@lib.If(_, then_body, else_body) => {
if run_hot_pipeline_raw_simplify_locals_has_call_bulk_memory_effect_mesh(
then_body,
) {
return true
}
match else_body {
Some(body) =>
if run_hot_pipeline_raw_simplify_locals_has_call_bulk_memory_effect_mesh(
body,
) {
return true
}
None => ()
}
}
_ => ()
}
}
has_call && has_bulk_memory
}
///|
fn run_hot_pipeline_raw_simplify_locals_full_lifetime_guard(
original : @lib.Func,
instrs : Array[@lib.Instruction],
) -> HotPipelineFuncResult? {
if run_hot_pipeline_raw_simplify_locals_has_local_alias_multi_call_lifetime(
instrs,
) {
return Some({
reason: "local-alias-multi-call-lifetime",
func: original,
unchanged_original: true,
trace_skip_raw: true,
})
}
if run_hot_pipeline_raw_simplify_locals_has_call_result_structured_lifetime(
instrs,
) {
return Some({
reason: "call-result-structured-lifetime",
func: original,
unchanged_original: true,
trace_skip_raw: true,
})
}
if run_hot_pipeline_dce_raw_has_call_result_multi_call_lifetime(instrs) {
return Some({
reason: "call-result-multi-call-lifetime",
func: original,
unchanged_original: true,
trace_skip_raw: true,
})
}
if run_hot_pipeline_raw_simplify_locals_has_call_local_tee_lifetime(instrs) {
return Some({
reason: "call-local-tee-lifetime",
func: original,
unchanged_original: true,
trace_skip_raw: true,
})
}
None
}
///|
fn run_hot_pipeline_raw_simplify_locals_match_tail_local_get(
instrs : Array[@lib.Instruction],
) -> (Int32, @lib.I32, Bool)? {
let len = instrs.length()
if len < 3 {
return None
}
let tail_has_return = match instrs[len - 1] {
@lib.Return => true
_ => false
}
if tail_has_return && len < 4 {
return None
}
let get_idx = if tail_has_return { len - 2 } else { len - 1 }
let const_idx = get_idx - 1
let set_idx = get_idx - 2
match (instrs[set_idx], instrs[const_idx], instrs[get_idx]) {
(@lib.LocalSet(local_id), @lib.I32Const(status), @lib.LocalGet(get_local_id)
) if local_id == get_local_id => Some((set_idx, status, tail_has_return))
_ => None
}
}
///|
fn run_hot_pipeline_raw_simplify_locals_rewrite_self_copy_read(
instrs : Array[@lib.Instruction],
keep_copy_read : Bool,
) -> (Array[@lib.Instruction], Bool) {
let normalized : Array[@lib.Instruction] = []
let mut nested_changed = false
for instr in instrs {
match instr {
@lib.Block(block_type, @lib.Expr(body)) => {
let (next, changed) = run_hot_pipeline_raw_simplify_locals_rewrite_self_copy_read(
body, keep_copy_read,
)
nested_changed = nested_changed || changed
normalized.push(
@lib.Instruction::block(block_type, @lib.Expr::new(next)),
)
}
@lib.Loop(block_type, @lib.Expr(body)) => {
let (next, changed) = run_hot_pipeline_raw_simplify_locals_rewrite_self_copy_read(
body, keep_copy_read,
)
nested_changed = nested_changed || changed
normalized.push(
@lib.Instruction::loop_(block_type, @lib.Expr::new(next)),
)
}
@lib.If(block_type, then_body, else_body) => {
let (next_then, then_changed) = run_hot_pipeline_raw_simplify_locals_rewrite_self_copy_read(
then_body, keep_copy_read,
)
let mut next_else : Array[@lib.Instruction]? = None
let mut else_changed = false
match else_body {
Some(body) => {
let (next, changed) = run_hot_pipeline_raw_simplify_locals_rewrite_self_copy_read(
body, keep_copy_read,
)
next_else = Some(next)
else_changed = changed
}
None => ()
}
nested_changed = nested_changed || then_changed || else_changed
normalized.push(@lib.Instruction::if_(block_type, next_then, next_else))
}
@lib.TryTable(block_type, catches, @lib.Expr(body)) => {
let (next, changed) = run_hot_pipeline_raw_simplify_locals_rewrite_self_copy_read(
body, keep_copy_read,
)
nested_changed = nested_changed || changed
normalized.push(
@lib.Instruction::try_table(block_type, catches, @lib.Expr::new(next)),
)
}
_ => normalized.push(instr)
}
}
let rewritten : Array[@lib.Instruction] = []
let mut changed = nested_changed
let mut idx = 0
while idx < normalized.length() {
if idx + 3 < normalized.length() {
match
(
normalized[idx],
normalized[idx + 1],
normalized[idx + 2],
normalized[idx + 3],
) {
(
@lib.LocalSet(set_local),
@lib.LocalGet(copy_get),
@lib.LocalSet(copy_set),
@lib.LocalGet(final_get),
) if set_local == copy_get &&
set_local == copy_set &&
set_local == final_get => {
rewritten.push(normalized[idx])
if keep_copy_read {
rewritten.push(normalized[idx + 1])
rewritten.push(@lib.Instruction::drop())
} else {
rewritten.push(@lib.Instruction::nop())
}
rewritten.push(normalized[idx + 3])
changed = true
idx = idx + 4
continue
}
_ => ()
}
}
rewritten.push(normalized[idx])
idx = idx + 1
}
(rewritten, changed)
}
///|
fn run_hot_pipeline_raw_simplify_locals_leaf_value(
instr : @lib.Instruction,
) -> Bool {
match instr {
@lib.LocalGet(_)
| @lib.I32Const(_)
| @lib.I64Const(_)
| @lib.F32Const(_)
| @lib.F64Const(_)
| @lib.V128Const(_) => true
_ => false
}
}
///|
fn run_hot_pipeline_raw_simplify_locals_single_leaf_value(
instrs : Array[@lib.Instruction],
) -> @lib.Instruction? {
let mut value : @lib.Instruction? = None
for instr in instrs {
match instr {
@lib.Nop => ()
_ if run_hot_pipeline_raw_simplify_locals_leaf_value(instr) =>
match value {
Some(_) => return None
None => value = Some(instr)
}
_ => return None
}
}
value
}
///|
fn run_hot_pipeline_raw_simplify_locals_selectable_block_type(
block_type : @lib.BlockType,
) -> Bool {
match block_type {
@lib.ValTypeBlockType(@lib.NumTypeValType(_))
| @lib.ValTypeBlockType(@lib.VecTypeValType) => true
_ => false
}
}
///|
fn run_hot_pipeline_raw_simplify_locals_selectable_local_type(
params : Array[@lib.ValType],
locals : @lib.Locals,
local_idx : @lib.LocalIdx,
) -> Bool {
let @lib.LocalIdx(raw) = local_idx
match
run_hot_pipeline_raw_local_type(params, locals, raw.reinterpret_as_int()) {
@lib.NumTypeValType(_) | @lib.VecTypeValType => true
_ => false
}
}
///|
fn run_hot_pipeline_raw_simplify_locals_rewrite_leaf_conditional_assignments(
instrs : Array[@lib.Instruction],
params : Array[@lib.ValType],
locals : @lib.Locals,
stats? : RawSimplifyLocalsGateStats,
) -> (Array[@lib.Instruction], Int32) {
// The entry census covers the complete current body, including both arms,
// loops and handlers. Only If can enable this rewrite. Its absence proves
// zero rewrites before allocating normalized or rewritten storage. The raw
// dispatcher supplies its existing census; recursive workers never rescan.
let if_count = match stats {
Some(stats) => stats.if_count
None =>
run_hot_pipeline_raw_simplify_locals_gate_stats(locals, instrs).if_count
}
if if_count == 0 {
return (instrs, 0)
}
// Keep the original full owned rewrite for possible candidates. Borrowing
// unchanged children here would let later cleanup mutate the input module.
run_hot_pipeline_raw_simplify_locals_rewrite_leaf_conditional_assignments_worker(
instrs, params, locals,
)
}
///|
fn run_hot_pipeline_raw_simplify_locals_rewrite_leaf_conditional_assignments_worker(
instrs : Array[@lib.Instruction],
params : Array[@lib.ValType],
locals : @lib.Locals,
) -> (Array[@lib.Instruction], Int32) {
let normalized : Array[@lib.Instruction] = []
let mut rewrites = 0
for instr in instrs {
match instr {
@lib.Block(block_type, @lib.Expr(body)) => {
let (next, count) = run_hot_pipeline_raw_simplify_locals_rewrite_leaf_conditional_assignments_worker(
body, params, locals,
)
rewrites += count
normalized.push(
@lib.Instruction::block(block_type, @lib.Expr::new(next)),
)
}
@lib.Loop(block_type, @lib.Expr(body)) => {
let (next, count) = run_hot_pipeline_raw_simplify_locals_rewrite_leaf_conditional_assignments_worker(
body, params, locals,
)
rewrites += count
normalized.push(
@lib.Instruction::loop_(block_type, @lib.Expr::new(next)),
)
}
@lib.If(block_type, then_body, else_body) => {
let (next_then, then_count) = run_hot_pipeline_raw_simplify_locals_rewrite_leaf_conditional_assignments_worker(
then_body, params, locals,
)
let mut next_else : Array[@lib.Instruction]? = None
let mut else_count = 0
match else_body {
Some(body) => {
let (next, count) = run_hot_pipeline_raw_simplify_locals_rewrite_leaf_conditional_assignments_worker(
body, params, locals,
)
next_else = Some(next)
else_count = count
}
None => ()
}
rewrites += then_count + else_count
normalized.push(@lib.Instruction::if_(block_type, next_then, next_else))
}
@lib.TryTable(block_type, catches, @lib.Expr(body)) => {
let (next, count) = run_hot_pipeline_raw_simplify_locals_rewrite_leaf_conditional_assignments_worker(
body, params, locals,
)
rewrites += count
normalized.push(
@lib.Instruction::try_table(block_type, catches, @lib.Expr::new(next)),
)
}
_ => normalized.push(instr)
}
}
let rewritten : Array[@lib.Instruction] = []
let mut idx = 0
while idx < normalized.length() {
if idx + 1 < normalized.length() {
match (normalized[idx], normalized[idx + 1]) {
(
@lib.LocalGet(condition_local),
@lib.If(block_type, then_body, Some(else_body)),
) if run_hot_pipeline_raw_simplify_locals_selectable_local_type(
params, locals, condition_local,
) &&
run_hot_pipeline_raw_simplify_locals_selectable_block_type(block_type) => {
let true_value = run_hot_pipeline_raw_simplify_locals_single_leaf_value(
then_body,
)
let false_value = run_hot_pipeline_raw_simplify_locals_single_leaf_value(
else_body,
)
match (true_value, false_value) {
(Some(true_value), Some(false_value)) => {
rewritten.push(true_value)
rewritten.push(false_value)
rewritten.push(@lib.Instruction::local_get(condition_local))
rewritten.push(@lib.Instruction::select())
rewrites += 1
idx += 2
continue
}
_ => ()
}
}
(
@lib.LocalGet(condition_local),
@lib.If(@lib.VoidBlockType, then_body, else_body),
) if run_hot_pipeline_raw_simplify_locals_selectable_local_type(
params, locals, condition_local,
) &&
then_body.length() == 2 &&
run_hot_pipeline_raw_simplify_locals_leaf_value(then_body[0]) => {
let target = match then_body[1] {
@lib.LocalSet(target) => target
_ => {
rewritten.push(normalized[idx])
idx += 1
continue
}
}
if !run_hot_pipeline_raw_simplify_locals_selectable_local_type(
params, locals, target,
) {
rewritten.push(normalized[idx])
idx += 1
continue
}
let false_value = match else_body {
None => Some(@lib.Instruction::local_get(target))
Some(body) if body.length() == 2 &&
run_hot_pipeline_raw_simplify_locals_leaf_value(body[0]) =>
match body[1] {
@lib.LocalSet(else_target) if else_target == target =>
Some(body[0])
_ => None
}
_ => None
}
match false_value {
Some(false_value) => {
rewritten.push(then_body[0])
rewritten.push(false_value)
rewritten.push(@lib.Instruction::local_get(condition_local))
rewritten.push(@lib.Instruction::select())
rewritten.push(@lib.Instruction::local_set(target))
rewrites += 1
idx += 2
continue
}
None => ()
}
}
_ => ()
}
}
rewritten.push(normalized[idx])
idx += 1
}
(rewritten, rewrites)
}
///|
fn run_hot_pipeline_raw_simplify_locals_notee_const_type(
instr : @lib.Instruction,
) -> @lib.ValType? {
match instr {
@lib.I32Const(_) => Some(@lib.ValType::i32())
@lib.I64Const(_) => Some(@lib.ValType::i64())
@lib.F32Const(_) => Some(@lib.ValType::f32())
@lib.F64Const(_) => Some(@lib.ValType::f64())
_ => None
}
}
///|
fn run_hot_pipeline_raw_simplify_locals_notee_reorder_const_nop(
instrs : Array[@lib.Instruction],
) -> (Array[@lib.Instruction], Array[@lib.ValType], Bool) {
let rewritten : Array[@lib.Instruction] = []
let added_locals : Array[@lib.ValType] = []
let mut changed = false
for instr in instrs {
match instr {
@lib.Block(block_type, @lib.Expr(body)) => {
let (new_body, body_added_locals, body_changed) = run_hot_pipeline_raw_simplify_locals_notee_reorder_const_nop(
body,
)
added_locals.append(body_added_locals)
if body_changed {
changed = true
}
rewritten.push(
@lib.Instruction::block(block_type, @lib.Expr::new(new_body)),
)
}
@lib.Loop(block_type, @lib.Expr(body)) => {
let (new_body, body_added_locals, body_changed) = run_hot_pipeline_raw_simplify_locals_notee_reorder_const_nop(
body,
)
added_locals.append(body_added_locals)
if body_changed {
changed = true
}
if new_body.length() == 2 && new_body[1] == @lib.Instruction::nop() {
match
run_hot_pipeline_raw_simplify_locals_notee_const_type(new_body[0]) {
Some(vt) => {
added_locals.push(vt)
rewritten.push(
@lib.Instruction::loop_(
block_type,
@lib.Expr::new([
@lib.Instruction::nop(),
@lib.Instruction::nop(),
new_body[0],
]),
),
)
changed = true
}
None =>
rewritten.push(
@lib.Instruction::loop_(block_type, @lib.Expr::new(new_body)),
)
}
} else {
rewritten.push(
@lib.Instruction::loop_(block_type, @lib.Expr::new(new_body)),
)
}
}
@lib.If(block_type, then_body, else_body) => {
let (new_then, then_added_locals, then_changed) = run_hot_pipeline_raw_simplify_locals_notee_reorder_const_nop(
then_body,
)
added_locals.append(then_added_locals)
let mut new_else : Array[@lib.Instruction]? = None
let mut else_changed = false
match else_body {
Some(body) => {
let (rewritten_else, else_added_locals, changed_else) = run_hot_pipeline_raw_simplify_locals_notee_reorder_const_nop(
body,
)
added_locals.append(else_added_locals)
new_else = Some(rewritten_else)
else_changed = changed_else
}
None => ()
}
if then_changed || else_changed {
changed = true
}
rewritten.push(@lib.Instruction::if_(block_type, new_then, new_else))
}
@lib.TryTable(block_type, catches, @lib.Expr(body)) => {
let (new_body, body_added_locals, body_changed) = run_hot_pipeline_raw_simplify_locals_notee_reorder_const_nop(
body,
)
added_locals.append(body_added_locals)
if body_changed {
changed = true
}
rewritten.push(
@lib.Instruction::try_table(
block_type,
catches,
@lib.Expr::new(new_body),
),
)
}
_ => rewritten.push(instr)
}
}
(rewritten, added_locals, changed)
}
///|
fn run_hot_pipeline_raw_simplify_locals_instrs_write_local(
instrs : Array[@lib.Instruction],
target : @lib.LocalIdx,
) -> Bool {
for instr in instrs {
match instr {
@lib.LocalSet(local_idx) | @lib.LocalTee(local_idx) if local_idx == target =>
return true
@lib.Block(_, @lib.Expr(body))
| @lib.Loop(_, @lib.Expr(body))
| @lib.TryTable(_, _, @lib.Expr(body)) =>
if run_hot_pipeline_raw_simplify_locals_instrs_write_local(body, target) {
return true
}
@lib.If(_, then_body, else_body) => {
if run_hot_pipeline_raw_simplify_locals_instrs_write_local(
then_body, target,
) {
return true
}
match else_body {
Some(else_body) =>
if run_hot_pipeline_raw_simplify_locals_instrs_write_local(
else_body, target,
) {
return true
}
None => ()
}
}
_ => ()
}
}
false
}
///|
fn run_hot_pipeline_raw_simplify_locals_has_result_control_write(
instrs : Array[@lib.Instruction],
target : @lib.LocalIdx,
) -> Bool {
for idx = 0; idx < instrs.length(); idx = idx + 1 {
if idx + 1 < instrs.length() {
let has_result_control = match instrs[idx] {
@lib.Block(block_type, _) | @lib.Loop(block_type, _) =>
run_hot_pipeline_slns_lowered_block_type_is_nonvoid(block_type)
@lib.If(block_type, _, _) =>
run_hot_pipeline_slns_lowered_block_type_is_nonvoid(block_type)
_ => false
}
if has_result_control {
match instrs[idx + 1] {
@lib.LocalSet(local_idx) if local_idx == target => return true
_ => ()
}
}
}
match instrs[idx] {
@lib.Block(_, @lib.Expr(body))
| @lib.Loop(_, @lib.Expr(body))
| @lib.TryTable(_, _, @lib.Expr(body)) =>
if run_hot_pipeline_raw_simplify_locals_has_result_control_write(
body, target,
) {
return true
}
@lib.If(_, then_body, else_body) => {
if run_hot_pipeline_raw_simplify_locals_has_result_control_write(
then_body, target,
) {
return true
}
match else_body {
Some(body) =>
if run_hot_pipeline_raw_simplify_locals_has_result_control_write(
body, target,
) {
return true
}
None => ()
}
}
_ => ()
}
}
false
}
///|
fn run_hot_pipeline_raw_simplify_locals_collect_loop_writes(
instrs : Array[@lib.Instruction],
writes : Map[UInt32, Bool],
result_writes : Map[UInt32, Bool],
) -> Unit {
for idx = 0; idx < instrs.length(); idx = idx + 1 {
match instrs[idx] {
@lib.LocalSet(@lib.LocalIdx(raw)) | @lib.LocalTee(@lib.LocalIdx(raw)) =>
writes[raw] = true
_ => ()
}
if idx + 1 < instrs.length() {
let has_result_control = match instrs[idx] {
@lib.Block(block_type, _) | @lib.Loop(block_type, _) =>
run_hot_pipeline_slns_lowered_block_type_is_nonvoid(block_type)
@lib.If(block_type, _, _) =>
run_hot_pipeline_slns_lowered_block_type_is_nonvoid(block_type)
_ => false
}
if has_result_control {
match instrs[idx + 1] {
@lib.LocalSet(@lib.LocalIdx(raw)) => result_writes[raw] = true
_ => ()
}
}
}
match instrs[idx] {
@lib.Block(_, @lib.Expr(body))
| @lib.Loop(_, @lib.Expr(body))
| @lib.TryTable(_, _, @lib.Expr(body)) =>
run_hot_pipeline_raw_simplify_locals_collect_loop_writes(
body, writes, result_writes,
)
@lib.If(_, then_body, else_body) => {
run_hot_pipeline_raw_simplify_locals_collect_loop_writes(
then_body, writes, result_writes,
)
match else_body {
Some(body) =>
run_hot_pipeline_raw_simplify_locals_collect_loop_writes(
body, writes, result_writes,
)
None => ()
}
}
_ => ()
}
}
}
///|
fn run_hot_pipeline_raw_simplify_locals_has_parameter_initialized_result_loop_carrier(
instrs : Array[@lib.Instruction],
) -> Bool {
for loop_idx = 0; loop_idx < instrs.length(); loop_idx = loop_idx + 1 {
let loop_body = match instrs[loop_idx] {
@lib.Loop(_, @lib.Expr(body)) => body
_ => continue
}
let mut checked = 0
let mut result_summary : Map[UInt32, Bool]? = None
for init_idx = 1; init_idx < loop_idx; init_idx = init_idx + 1 {
let local_idx = match instrs[init_idx] {
@lib.LocalSet(local_idx) => local_idx
_ => continue
}
match instrs[init_idx - 1] {
@lib.LocalGet(_) => ()
_ => continue
}
checked += 1
let has_result_write = if checked == 1 {
run_hot_pipeline_raw_simplify_locals_has_result_control_write(
loop_body, local_idx,
)
} else {
let summary = match result_summary {
Some(summary) => summary
None => {
let writes : Map[UInt32, Bool] = Map([])
let summary : Map[UInt32, Bool] = Map([])
run_hot_pipeline_raw_simplify_locals_collect_loop_writes(
loop_body, writes, summary,
)
result_summary = Some(summary)
summary
}
}
match local_idx {
@lib.LocalIdx(raw) => summary.get(raw).unwrap_or(false)
}
}
if has_result_write {
return true
}
}
}
for instr in instrs {
match instr {
@lib.Block(_, @lib.Expr(body))
| @lib.Loop(_, @lib.Expr(body))
| @lib.TryTable(_, _, @lib.Expr(body)) =>
if run_hot_pipeline_raw_simplify_locals_has_parameter_initialized_result_loop_carrier(
body,
) {
return true
}
@lib.If(_, then_body, else_body) => {
if run_hot_pipeline_raw_simplify_locals_has_parameter_initialized_result_loop_carrier(
then_body,
) {
return true
}
match else_body {
Some(body) =>
if run_hot_pipeline_raw_simplify_locals_has_parameter_initialized_result_loop_carrier(
body,
) {
return true
}
None => ()
}
}
_ => ()
}
}
false
}
///|
fn run_hot_pipeline_raw_simplify_locals_has_initialized_loop_carrier(
instrs : Array[@lib.Instruction],
) -> Bool {
for loop_idx = 0; loop_idx < instrs.length(); loop_idx = loop_idx + 1 {
let loop_body = match instrs[loop_idx] {
@lib.Loop(_, @lib.Expr(body)) => body
_ => continue
}
let initialized_carriers : Array[@lib.LocalIdx] = []
let mut checked = 0
let mut write_summary : Map[UInt32, Bool]? = None
for init_idx = 0; init_idx < loop_idx; init_idx = init_idx + 1 {
let local_idx = match instrs[init_idx] {
@lib.LocalSet(local_idx) => local_idx
_ => continue
}
checked += 1
let has_write = if checked == 1 {
run_hot_pipeline_raw_simplify_locals_instrs_write_local(
loop_body, local_idx,
)
} else {
let summary = match write_summary {
Some(summary) => summary
None => {
let summary : Map[UInt32, Bool] = Map([])
let result_writes : Map[UInt32, Bool] = Map([])
run_hot_pipeline_raw_simplify_locals_collect_loop_writes(
loop_body, summary, result_writes,
)
write_summary = Some(summary)
summary
}
}
match local_idx {
@lib.LocalIdx(raw) => summary.get(raw).unwrap_or(false)
}
}
if !has_write {
continue
}
if init_idx > 0 {
match instrs[init_idx - 1] {
@lib.I32Const(_) | @lib.I64Const(_) => return true
_ => ()
}
}
if !initialized_carriers.contains(local_idx) {
initialized_carriers.push(local_idx)
}
if initialized_carriers.length() >= 2 {
return true
}
}
}
false
}
///|
fn run_hot_pipeline_raw_simplify_locals_has_parameter_result_if_late_alias_lifetime(
instrs : Array[@lib.Instruction],
parameter_count : Int32,
) -> Bool {
if parameter_count <= 0 {
return false
}
let read_before_result_if = Array::make(parameter_count, false)
let mut saw_result_if = false
let mut idx = 0
while idx < instrs.length() {
match instrs[idx] {
@lib.LocalGet(@lib.LocalIdx(raw)) if !saw_result_if => {
let local_id = raw.reinterpret_as_int()
if local_id >= 0 && local_id < parameter_count {
read_before_result_if[local_id] = true
}
}
@lib.If(block_type, _, _) =>
if run_hot_pipeline_slns_lowered_block_type_is_nonvoid(block_type) {
saw_result_if = true
}
@lib.LocalTee(@lib.LocalIdx(alias_raw)) if saw_result_if &&
idx + 2 < instrs.length() => {
match (instrs[idx + 1], instrs[idx + 2]) {
(
@lib.LocalGet(@lib.LocalIdx(read_alias_raw)),
@lib.LocalSet(@lib.LocalIdx(parameter_raw)),
) if alias_raw == read_alias_raw => {
let parameter_id = parameter_raw.reinterpret_as_int()
if parameter_id >= 0 &&
parameter_id < parameter_count &&
read_before_result_if[parameter_id] {
return true
}
}
_ => ()
}
if idx + 4 < instrs.length() {
match
(instrs[idx + 1], instrs[idx + 2], instrs[idx + 3], instrs[idx + 4]) {
(
@lib.LocalGet(@lib.LocalIdx(read_alias_raw)),
@lib.LocalSet(@lib.LocalIdx(temp_raw)),
@lib.LocalGet(@lib.LocalIdx(read_temp_raw)),
@lib.LocalSet(@lib.LocalIdx(parameter_raw)),
) if alias_raw == read_alias_raw && temp_raw == read_temp_raw => {
let parameter_id = parameter_raw.reinterpret_as_int()
if parameter_id >= 0 &&
parameter_id < parameter_count &&
read_before_result_if[parameter_id] {
return true
}
}
_ => ()
}
}
}
_ => ()
}
idx += 1
}
false
}
///|
fn run_hot_pipeline_raw_simplify_locals_has_deep_result_if_chain(
instrs : Array[@lib.Instruction],
depth : Int32,
) -> Bool {
for instr in instrs {
match instr {
@lib.If(block_type, then_body, else_body) => {
let next_depth = if run_hot_pipeline_slns_lowered_block_type_is_nonvoid(
block_type,
) {
depth + 1
} else {
0
}
if next_depth >= 12 ||
run_hot_pipeline_raw_simplify_locals_has_deep_result_if_chain(
then_body, next_depth,
) {
return true
}
match else_body {
Some(else_body) =>
if run_hot_pipeline_raw_simplify_locals_has_deep_result_if_chain(
else_body, next_depth,
) {
return true
}
None => ()
}
}
@lib.Block(_, @lib.Expr(body))
| @lib.Loop(_, @lib.Expr(body))
| @lib.TryTable(_, _, @lib.Expr(body)) =>
if run_hot_pipeline_raw_simplify_locals_has_deep_result_if_chain(
body, 0,
) {
return true
}
_ => ()
}
}
false
}
///|
fn run_hot_pipeline_raw_simplify_locals_result_if_count_at_most(
instrs : Array[@lib.Instruction],
limit : Int32,
) -> Int32 {
let mut count = 0
for instr in instrs {
match instr {
@lib.If(block_type, then_body, else_body) => {
if run_hot_pipeline_slns_lowered_block_type_is_nonvoid(block_type) {
count += 1
if count >= limit {
return limit
}
}
count += run_hot_pipeline_raw_simplify_locals_result_if_count_at_most(
then_body,
limit - count,
)
if count >= limit {
return limit
}
match else_body {
Some(else_body) => {
count += run_hot_pipeline_raw_simplify_locals_result_if_count_at_most(
else_body,
limit - count,
)
if count >= limit {
return limit
}
}
None => ()
}
}
@lib.Block(_, @lib.Expr(body))
| @lib.Loop(_, @lib.Expr(body))
| @lib.TryTable(_, _, @lib.Expr(body)) => {
count += run_hot_pipeline_raw_simplify_locals_result_if_count_at_most(
body,
limit - count,
)
if count >= limit {
return limit
}
}
_ => ()
}
}
count
}
///|
fn run_hot_pipeline_raw_simplify_locals_has_deep_br_table(
instrs : Array[@lib.Instruction],
depth : Int32,
) -> Bool {
for instr in instrs {
match instr {
@lib.BrTable(_, _) if depth >= 24 => return true
@lib.Block(_, @lib.Expr(body))
| @lib.Loop(_, @lib.Expr(body))
| @lib.TryTable(_, _, @lib.Expr(body)) =>
if run_hot_pipeline_raw_simplify_locals_has_deep_br_table(
body,
depth + 1,
) {
return true
}
@lib.If(_, then_body, else_body) => {
if run_hot_pipeline_raw_simplify_locals_has_deep_br_table(
then_body,
depth + 1,
) {
return true
}
match else_body {
Some(else_body) =>
if run_hot_pipeline_raw_simplify_locals_has_deep_br_table(
else_body,
depth + 1,
) {
return true
}
None => ()
}
}
_ => ()
}
}
false
}
///|
fn run_hot_pipeline_raw_simplify_locals_has_local_alias_multi_call_lifetime(
instrs : Array[@lib.Instruction],
) -> Bool {
for idx = 0; idx + 1 < instrs.length(); idx = idx + 1 {
let alias_local = match (instrs[idx], instrs[idx + 1]) {
(@lib.LocalGet(_), @lib.LocalSet(alias_local))
| (@lib.LocalGet(_), @lib.LocalTee(alias_local)) => Some(alias_local)
_ => None
}
match alias_local {
Some(alias_local) => {
let mut first_call : @lib.FuncIdx? = None
for scan = idx + 2; scan < instrs.length(); scan = scan + 1 {
match instrs[scan] {
@lib.LocalGet(read_local) if read_local == alias_local => {
let call_end = Int32::min(instrs.length(), scan + 5)
for look = scan + 1; look < call_end; look = look + 1 {
match instrs[look] {
@lib.Call(call_target) => {
match first_call {
Some(first_target) if first_target != call_target =>
return true
None => first_call = Some(call_target)
_ => ()
}
break
}
_ => ()
}
}
}
_ => ()
}
}
}
None => ()
}
}
for instr in instrs {
match instr {
@lib.Block(_, @lib.Expr(body))
| @lib.Loop(_, @lib.Expr(body))
| @lib.TryTable(_, _, @lib.Expr(body)) =>
if run_hot_pipeline_raw_simplify_locals_has_local_alias_multi_call_lifetime(
body,
) {
return true
}
@lib.If(_, then_body, else_body) => {
if run_hot_pipeline_raw_simplify_locals_has_local_alias_multi_call_lifetime(
then_body,
) {
return true
}
match else_body {
Some(else_body) =>
if run_hot_pipeline_raw_simplify_locals_has_local_alias_multi_call_lifetime(
else_body,
) {
return true
}
None => ()
}
}
_ => ()
}
}
false
}
///|
fn run_hot_pipeline_raw_simplify_locals_subtree_has_local_and_call(
instrs : Array[@lib.Instruction],
target : @lib.LocalIdx,
) -> (Bool, Bool) {
let mut has_local = false
let mut has_call = false
for instr in instrs {
match instr {
@lib.LocalGet(local_idx) if local_idx == target => has_local = true
@lib.Call(_) | @lib.CallIndirect(_, _) | @lib.CallRef(_) =>
has_call = true
@lib.Block(_, @lib.Expr(body))
| @lib.Loop(_, @lib.Expr(body))
| @lib.TryTable(_, _, @lib.Expr(body)) => {
let (child_local, child_call) = run_hot_pipeline_raw_simplify_locals_subtree_has_local_and_call(
body, target,
)
has_local = has_local || child_local
has_call = has_call || child_call
}
@lib.If(_, then_body, else_body) => {
let (then_local, then_call) = run_hot_pipeline_raw_simplify_locals_subtree_has_local_and_call(
then_body, target,
)
has_local = has_local || then_local
has_call = has_call || then_call
match else_body {
Some(else_body) => {
let (else_local, else_call) = run_hot_pipeline_raw_simplify_locals_subtree_has_local_and_call(
else_body, target,
)
has_local = has_local || else_local
has_call = has_call || else_call
}
None => ()
}
}
_ => ()
}
}
(has_local, has_call)
}
///|
priv struct RawStructuredLifetimeFacts {
reads : @hashmap.HashMap[UInt32, Bool]
mut has_call : Bool
mut hazard : Bool
}
///|
fn RawStructuredLifetimeFacts::new() -> RawStructuredLifetimeFacts {
{ reads: @hashmap.new(), has_call: false, hazard: false, }
}
///|
fn raw_structured_lifetime_reads_capture(
reads : @hashmap.HashMap[UInt32, Bool],
captures : @hashmap.HashMap[UInt32, Bool],
) -> Bool {
if captures.length() < reads.length() {
for local in captures.keys() {
if reads.get(local) is Some(_) {
return true
}
}
} else {
for local in reads.keys() {
if captures.get(local) is Some(_) {
return true
}
}
}
false
}
///|
fn raw_structured_lifetime_merge(
into : RawStructuredLifetimeFacts,
child : RawStructuredLifetimeFacts,
captures : @hashmap.HashMap[UInt32, Bool],
) -> Unit {
into.hazard = into.hazard || child.hazard
if !into.hazard &&
child.has_call &&
raw_structured_lifetime_reads_capture(child.reads, captures) {
into.hazard = true
}
into.has_call = into.has_call || child.has_call
for local in child.reads.keys() {
into.reads.set(local, true)
}
}
///|
fn raw_structured_lifetime_summarize(
instrs : Array[@lib.Instruction],
end : Int32,
visits : Array[Int32]?,
) -> RawStructuredLifetimeFacts {
let facts = RawStructuredLifetimeFacts::new()
let captures : @hashmap.HashMap[UInt32, Bool] = @hashmap.new()
for idx = 0; idx < end; idx = idx + 1 {
match visits {
Some(count) => count[0] += 1
None => ()
}
match instrs[idx] {
@lib.LocalGet(@lib.LocalIdx(local)) => facts.reads.set(local, true)
@lib.Call(_) | @lib.CallIndirect(_, _) | @lib.CallRef(_) =>
facts.has_call = true
@lib.LocalSet(@lib.LocalIdx(local))
| @lib.LocalTee(@lib.LocalIdx(local)) =>
if idx > 0 && instrs[idx - 1] is @lib.Call(_) {
// The guard intentionally retains captures across later overwrites.
captures.set(local, true)
}
@lib.Block(_, @lib.Expr(body))
| @lib.Loop(_, @lib.Expr(body))
| @lib.TryTable(_, _, @lib.Expr(body)) =>
raw_structured_lifetime_merge(
facts,
raw_structured_lifetime_summarize(body, body.length(), visits),
captures,
)
@lib.If(_, yes, no) => {
// Each immediate arm must contain both the read and the call. An
// enclosing body's summary may combine facts from its nested arms.
raw_structured_lifetime_merge(
facts,
raw_structured_lifetime_summarize(yes, yes.length(), visits),
captures,
)
match no {
Some(body) =>
raw_structured_lifetime_merge(
facts,
raw_structured_lifetime_summarize(body, body.length(), visits),
captures,
)
None => ()
}
}
_ => ()
}
if facts.hazard {
return facts
}
}
facts
}
///|
fn raw_structured_lifetime_last_structured(
instrs : Array[@lib.Instruction],
visits : Array[Int32]?,
) -> Int32 {
let mut last_structured = -1
for idx = 0; idx < instrs.length(); idx = idx + 1 {
match visits {
Some(count) => count[0] += 1
None => ()
}
match instrs[idx] {
@lib.Block(_, _)
| @lib.Loop(_, _)
| @lib.TryTable(_, _, _)
| @lib.If(_, _, _) => last_structured = idx
_ => ()
}
}
last_structured
}
///|
fn raw_structured_lifetime_body_hazard(
body : Array[@lib.Instruction],
captures : @hashmap.HashMap[UInt32, Bool]?,
visits : Array[Int32]?,
summaries : Array[Int32]?,
) -> Bool {
match captures {
None =>
run_hot_pipeline_raw_simplify_locals_has_call_result_structured_lifetime(
body,
visits~,
summaries~,
)
Some(captures) => {
match summaries {
Some(count) => count[0] += 1
None => ()
}
let facts = raw_structured_lifetime_summarize(body, body.length(), visits)
facts.hazard ||
(
facts.has_call &&
raw_structured_lifetime_reads_capture(facts.reads, captures)
)
}
}
}
///|
fn run_hot_pipeline_raw_simplify_locals_has_call_result_structured_lifetime(
instrs : Array[@lib.Instruction],
visits~ : Array[Int32]? = None,
summaries~ : Array[Int32]? = None,
) -> Bool {
let last_structured = raw_structured_lifetime_last_structured(instrs, visits)
if last_structured < 0 {
return false
}
// Without an earlier capture, only a child's own hazard matters. Recurse
// through such bodies without building read sets or summarizing them again.
// Once a capture exists, summarize each following body once for all captures.
let mut captures : @hashmap.HashMap[UInt32, Bool]? = None
for idx = 0; idx <= last_structured; idx = idx + 1 {
match visits {
Some(count) => count[0] += 1
None => ()
}
match instrs[idx] {
@lib.LocalSet(@lib.LocalIdx(local))
| @lib.LocalTee(@lib.LocalIdx(local)) =>
if idx > 0 && instrs[idx - 1] is @lib.Call(_) {
let captured = match captures {
Some(captured) => captured
None => {
let captured = @hashmap.new()
captures = Some(captured)
captured
}
}
// Overwrites intentionally do not release an earlier capture.
captured.set(local, true)
}
@lib.Block(_, @lib.Expr(body))
| @lib.Loop(_, @lib.Expr(body))
| @lib.TryTable(_, _, @lib.Expr(body)) =>
if raw_structured_lifetime_body_hazard(
body, captures, visits, summaries,
) {
return true
}
@lib.If(_, yes, no) => {
if raw_structured_lifetime_body_hazard(yes, captures, visits, summaries) {
return true
}
match no {
Some(body) =>
if raw_structured_lifetime_body_hazard(
body, captures, visits, summaries,
) {
return true
}
None => ()
}
}
_ => ()
}
}
false
}
///|
fn run_hot_pipeline_raw_simplify_locals_has_call_result_before_structured_release(
instrs : Array[@lib.Instruction],
module_ctx : @ir.HotModuleContext,
) -> Bool {
for idx = 0; idx + 2 < instrs.length(); idx = idx + 1 {
if run_hot_pipeline_dce_raw_instr_static_result_arity(
instrs[idx],
module_ctx,
) <=
0 {
continue
}
let target = match instrs[idx + 1] {
@lib.LocalGet(target) => target
_ => continue
}
let bodies : Array[Array[@lib.Instruction]] = match instrs[idx + 2] {
@lib.If(_, then_body, else_body) =>
match else_body {
Some(else_body) => [then_body, else_body]
None => [then_body]
}
_ => continue
}
for body in bodies {
let (has_local, has_call) = run_hot_pipeline_raw_simplify_locals_subtree_has_local_and_call(
body, target,
)
if has_local && has_call {
return true
}
}
}
for instr in instrs {
match instr {
@lib.Block(_, @lib.Expr(body))
| @lib.Loop(_, @lib.Expr(body))
| @lib.TryTable(_, _, @lib.Expr(body)) =>
if run_hot_pipeline_raw_simplify_locals_has_call_result_before_structured_release(
body, module_ctx,
) {
return true
}
@lib.If(_, then_body, else_body) => {
if run_hot_pipeline_raw_simplify_locals_has_call_result_before_structured_release(
then_body, module_ctx,
) {
return true
}
match else_body {
Some(else_body) =>
if run_hot_pipeline_raw_simplify_locals_has_call_result_before_structured_release(
else_body, module_ctx,
) {
return true
}
None => ()
}
}
_ => ()
}
}
false
}
///|
fn run_hot_pipeline_raw_simplify_locals_has_call_local_tee_lifetime(
instrs : Array[@lib.Instruction],
) -> Bool {
for idx = 0; idx + 4 < instrs.length(); idx = idx + 1 {
match
(
instrs[idx],
instrs[idx + 1],
instrs[idx + 2],
instrs[idx + 3],
instrs[idx + 4],
) {
(
@lib.Call(_),
@lib.LocalTee(written_local),
@lib.Call(_),
@lib.LocalGet(read_local),
@lib.Call(_),
) if written_local == read_local => return true
_ => ()
}
}
for tee_idx = 0; tee_idx < instrs.length(); tee_idx = tee_idx + 1 {
let target = match instrs[tee_idx] {
@lib.LocalTee(target) => target
_ => continue
}
let scan_end = Int32::min(instrs.length(), tee_idx + 24)
let mut saw_consumer_call = false
for scan = tee_idx + 1; scan < scan_end; scan = scan + 1 {
match instrs[scan] {
@lib.LocalSet(written) | @lib.LocalTee(written) if written == target =>
break
@lib.LocalGet(read_local) if read_local == target && saw_consumer_call => {
let look_end = Int32::min(scan_end, scan + 5)
for look = scan + 1; look < look_end; look = look + 1 {
if run_hot_pipeline_dce_raw_instr_is_call(instrs[look]) {
return true
}
match instrs[look] {
@lib.Block(_, _)
| @lib.Loop(_, _)
| @lib.If(_, _, _)
| @lib.TryTable(_, _, _)
| @lib.Br(_)
| @lib.BrIf(_)
| @lib.BrTable(_, _)
| @lib.Return
| @lib.Unreachable => break
_ => ()
}
}
}
call if run_hot_pipeline_dce_raw_instr_is_call(call) =>
saw_consumer_call = true
@lib.Block(_, _)
| @lib.Loop(_, _)
| @lib.If(_, _, _)
| @lib.TryTable(_, _, _)
| @lib.Br(_)
| @lib.BrIf(_)
| @lib.BrTable(_, _)
| @lib.Return
| @lib.Unreachable => break
_ => ()
}
}
}
for instr in instrs {
match instr {
@lib.Block(_, @lib.Expr(body))
| @lib.Loop(_, @lib.Expr(body))
| @lib.TryTable(_, _, @lib.Expr(body)) =>
if run_hot_pipeline_raw_simplify_locals_has_call_local_tee_lifetime(
body,
) {
return true
}
@lib.If(_, then_body, else_body) => {
if run_hot_pipeline_raw_simplify_locals_has_call_local_tee_lifetime(
then_body,
) {
return true
}
match else_body {
Some(else_body) =>
if run_hot_pipeline_raw_simplify_locals_has_call_local_tee_lifetime(
else_body,
) {
return true
}
None => ()
}
}
_ => ()
}
}
false
}
///|
fn run_hot_pipeline_raw_has_alias_call_structured_release(
instrs : Array[@lib.Instruction],
) -> Bool {
for idx = 0; idx + 2 < instrs.length(); idx = idx + 1 {
let owned_local = match (instrs[idx], instrs[idx + 1], instrs[idx + 2]) {
(@lib.LocalGet(_), @lib.LocalTee(_), @lib.LocalSet(owned_local)) =>
owned_local
_ => continue
}
let scan_end = Int32::min(instrs.length(), idx + 24)
let mut call_pos = -1
for scan = idx + 3; scan + 1 < scan_end; scan = scan + 1 {
match (instrs[scan], instrs[scan + 1]) {
(@lib.LocalGet(candidate), @lib.Call(_)) if candidate == owned_local => {
call_pos = scan + 1
break
}
_ => ()
}
}
if call_pos < 0 {
continue
}
for scan = call_pos + 1; scan + 1 < scan_end; scan = scan + 1 {
match (instrs[scan], instrs[scan + 1]) {
(@lib.LocalGet(condition_local), @lib.If(_, then_body, _)) if condition_local ==
owned_local =>
for body_idx = 0
body_idx + 1 < then_body.length()
body_idx = body_idx + 1 {
match (then_body[body_idx], then_body[body_idx + 1]) {
(@lib.LocalGet(released_local), @lib.Call(_)) if released_local ==
owned_local => return true
_ => ()
}
}
_ => ()
}
}
}
for instr in instrs {
match instr {
@lib.Block(_, @lib.Expr(body))
| @lib.Loop(_, @lib.Expr(body))
| @lib.TryTable(_, _, @lib.Expr(body)) =>
if run_hot_pipeline_raw_has_alias_call_structured_release(body) {
return true
}
@lib.If(_, then_body, else_body) => {
if run_hot_pipeline_raw_has_alias_call_structured_release(then_body) {
return true
}
match else_body {
Some(else_body) =>
if run_hot_pipeline_raw_has_alias_call_structured_release(else_body) {
return true
}
None => ()
}
}
_ => ()
}
}
false
}
///|
fn run_hot_pipeline_raw_has_call_argument_structured_release(
instrs : Array[@lib.Instruction],
) -> Bool {
for call_idx = 0; call_idx < instrs.length(); call_idx = call_idx + 1 {
let call_target = match instrs[call_idx] {
@lib.Call(call_target) => call_target
_ => continue
}
let argument_locals : Array[@lib.LocalIdx] = []
let before_start = if call_idx > 8 { call_idx - 8 } else { 0 }
let mut scan = call_idx - 1
while scan >= before_start {
match instrs[scan] {
@lib.LocalGet(local_idx) =>
if !argument_locals.contains(local_idx) {
argument_locals.push(local_idx)
}
@lib.Call(_)
| @lib.Block(_, _)
| @lib.Loop(_, _)
| @lib.If(_, _, _)
| @lib.TryTable(_, _, _)
| @lib.Br(_)
| @lib.BrIf(_)
| @lib.BrTable(_, _)
| @lib.Return
| @lib.Unreachable => break
_ => ()
}
scan -= 1
}
if argument_locals.length() < 2 {
continue
}
let released_locals : Array[@lib.LocalIdx] = []
let mut release_target : @lib.FuncIdx? = None
let after_end = Int32::min(instrs.length(), call_idx + 10)
for release_idx = call_idx + 1
release_idx < after_end
release_idx = release_idx + 1 {
match instrs[release_idx] {
@lib.If(_, then_body, _) =>
for body_idx = 0
body_idx + 1 < then_body.length()
body_idx = body_idx + 1 {
match (then_body[body_idx], then_body[body_idx + 1]) {
(@lib.LocalGet(local_idx), @lib.Call(candidate_target)) if candidate_target !=
call_target &&
argument_locals.contains(local_idx) => {
match release_target {
Some(existing) if existing != candidate_target => continue
None => release_target = Some(candidate_target)
_ => ()
}
if !released_locals.contains(local_idx) {
released_locals.push(local_idx)
}
if released_locals.length() >= 2 {
return true
}
}
_ => ()
}
}
_ => ()
}
}
}
for instr in instrs {
match instr {
@lib.Block(_, @lib.Expr(body))
| @lib.Loop(_, @lib.Expr(body))
| @lib.TryTable(_, _, @lib.Expr(body)) =>
if run_hot_pipeline_raw_has_call_argument_structured_release(body) {
return true
}
@lib.If(_, then_body, else_body) => {
if run_hot_pipeline_raw_has_call_argument_structured_release(then_body) {
return true
}
match else_body {
Some(else_body) =>
if run_hot_pipeline_raw_has_call_argument_structured_release(
else_body,
) {
return true
}
None => ()
}
}
_ => ()
}
}
false
}
///|
fn run_hot_pipeline_raw_has_call_argument_distinct_structured_releases(
instrs : Array[@lib.Instruction],
) -> Bool {
for call_idx = 0; call_idx < instrs.length(); call_idx = call_idx + 1 {
let call_target = match instrs[call_idx] {
@lib.Call(call_target) => call_target
_ => continue
}
let argument_locals : Array[@lib.LocalIdx] = []
let before_start = if call_idx > 8 { call_idx - 8 } else { 0 }
let mut scan = call_idx - 1
while scan >= before_start {
match instrs[scan] {
@lib.LocalGet(local_idx) =>
if !argument_locals.contains(local_idx) {
argument_locals.push(local_idx)
}
@lib.Call(_)
| @lib.Block(_, _)
| @lib.Loop(_, _)
| @lib.If(_, _, _)
| @lib.TryTable(_, _, _)
| @lib.Br(_)
| @lib.BrIf(_)
| @lib.BrTable(_, _)
| @lib.Return
| @lib.Unreachable => break
_ => ()
}
scan -= 1
}
if argument_locals.length() < 2 {
continue
}
let released_locals : Array[@lib.LocalIdx] = []
let after_end = Int32::min(instrs.length(), call_idx + 10)
for release_idx = call_idx + 1
release_idx < after_end
release_idx = release_idx + 1 {
match instrs[release_idx] {
@lib.If(_, then_body, _) =>
for body_idx = 0
body_idx + 1 < then_body.length()
body_idx = body_idx + 1 {
match (then_body[body_idx], then_body[body_idx + 1]) {
(@lib.LocalGet(local_idx), @lib.Call(candidate_target)) if candidate_target !=
call_target &&
argument_locals.contains(local_idx) => {
if !released_locals.contains(local_idx) {
released_locals.push(local_idx)
}
if released_locals.length() >= 2 {
return true
}
}
_ => ()
}
}
_ => ()
}
}
}
for instr in instrs {
match instr {
@lib.Block(_, @lib.Expr(body))
| @lib.Loop(_, @lib.Expr(body))
| @lib.TryTable(_, _, @lib.Expr(body)) =>
if run_hot_pipeline_raw_has_call_argument_distinct_structured_releases(
body,
) {
return true
}
@lib.If(_, then_body, else_body) => {
if run_hot_pipeline_raw_has_call_argument_distinct_structured_releases(
then_body,
) {
return true
}
match else_body {
Some(else_body) =>
if run_hot_pipeline_raw_has_call_argument_distinct_structured_releases(
else_body,
) {
return true
}
None => ()
}
}
_ => ()
}
}
false
}
///|
fn run_hot_pipeline_raw_has_call_argument_direct_release(
instrs : Array[@lib.Instruction],
) -> Bool {
for call_idx = 0; call_idx < instrs.length(); call_idx = call_idx + 1 {
let call_target = match instrs[call_idx] {
@lib.Call(call_target) => call_target
_ => continue
}
let argument_locals : Array[@lib.LocalIdx] = []
let before_start = if call_idx > 32 { call_idx - 32 } else { 0 }
let mut scan = call_idx - 1
while scan >= before_start {
match instrs[scan] {
@lib.LocalGet(local_idx) =>
if !argument_locals.contains(local_idx) {
argument_locals.push(local_idx)
}
@lib.Call(_)
| @lib.Block(_, _)
| @lib.Loop(_, _)
| @lib.If(_, _, _)
| @lib.TryTable(_, _, _)
| @lib.Br(_)
| @lib.BrIf(_)
| @lib.BrTable(_, _)
| @lib.Return
| @lib.Unreachable => break
_ => ()
}
scan -= 1
}
if argument_locals.length() < 2 {
continue
}
let released_locals : Array[@lib.LocalIdx] = []
let mut release_target : @lib.FuncIdx? = None
let after_end = Int32::min(instrs.length(), call_idx + 64)
for release_idx = call_idx + 1
release_idx + 1 < after_end
release_idx = release_idx + 1 {
match (instrs[release_idx], instrs[release_idx + 1]) {
(@lib.LocalGet(local_idx), @lib.Call(candidate_target)) if candidate_target !=
call_target &&
argument_locals.contains(local_idx) => {
match release_target {
Some(existing) if existing != candidate_target => continue
None => release_target = Some(candidate_target)
_ => ()
}
if !released_locals.contains(local_idx) {
released_locals.push(local_idx)
}
if released_locals.length() >= 2 {
return true
}
}
_ => ()
}
}
}
for instr in instrs {
match instr {
@lib.Block(_, @lib.Expr(body))
| @lib.Loop(_, @lib.Expr(body))
| @lib.TryTable(_, _, @lib.Expr(body)) =>
if run_hot_pipeline_raw_has_call_argument_direct_release(body) {
return true
}
@lib.If(_, then_body, else_body) => {
if run_hot_pipeline_raw_has_call_argument_direct_release(then_body) {
return true
}
match else_body {
Some(else_body) =>
if run_hot_pipeline_raw_has_call_argument_direct_release(else_body) {
return true
}
None => ()
}
}
_ => ()
}
}
false
}
///|
fn run_hot_pipeline_raw_record_parameter_accesses(
instrs : Array[@lib.Instruction],
parameter_count : Int32,
first_access : Array[Int32],
writes : Array[Bool],
) -> Unit {
for instr in instrs {
match instr {
@lib.LocalGet(@lib.LocalIdx(raw)) => {
let local_id = raw.reinterpret_as_int()
if local_id >= 0 &&
local_id < parameter_count &&
first_access[local_id] == 0 {
first_access[local_id] = 1
}
}
@lib.LocalSet(@lib.LocalIdx(raw)) | @lib.LocalTee(@lib.LocalIdx(raw)) => {
let local_id = raw.reinterpret_as_int()
if local_id >= 0 && local_id < parameter_count {
writes[local_id] = true
if first_access[local_id] == 0 {
first_access[local_id] = 2
}
}
}
@lib.Block(_, @lib.Expr(body))
| @lib.Loop(_, @lib.Expr(body))
| @lib.TryTable(_, _, @lib.Expr(body)) =>
run_hot_pipeline_raw_record_parameter_accesses(
body, parameter_count, first_access, writes,
)
@lib.If(_, then_body, else_body) => {
run_hot_pipeline_raw_record_parameter_accesses(
then_body, parameter_count, first_access, writes,
)
match else_body {
Some(else_body) =>
run_hot_pipeline_raw_record_parameter_accesses(
else_body, parameter_count, first_access, writes,
)
None => ()
}
}
_ => ()
}
}
}
///|
fn run_hot_pipeline_raw_ssa_nomerge_local_write_store_facts(
instrs : Array[@lib.Instruction],
target : @lib.LocalIdx,
) -> (Bool, Bool) {
let mut has_write = false
let mut has_store = false
for instr in instrs {
match instr {
@lib.LocalSet(local_idx) | @lib.LocalTee(local_idx) if local_idx == target =>
has_write = true
@lib.I32Store(_) => has_store = true
@lib.Block(_, @lib.Expr(body))
| @lib.Loop(_, @lib.Expr(body))
| @lib.TryTable(_, _, @lib.Expr(body)) => {
let (child_write, child_store) = run_hot_pipeline_raw_ssa_nomerge_local_write_store_facts(
body, target,
)
has_write = has_write || child_write
has_store = has_store || child_store
}
@lib.If(_, then_body, else_body) => {
let (then_write, then_store) = run_hot_pipeline_raw_ssa_nomerge_local_write_store_facts(
then_body, target,
)
has_write = has_write || then_write
has_store = has_store || then_store
match else_body {
Some(body) => {
let (else_write, else_store) = run_hot_pipeline_raw_ssa_nomerge_local_write_store_facts(
body, target,
)
has_write = has_write || else_write
has_store = has_store || else_store
}
None => ()
}
}
_ => ()
}
}
(has_write, has_store)
}
///|
fn run_hot_pipeline_raw_ssa_nomerge_has_result_if_embedded_tee_lifetime(
instrs : Array[@lib.Instruction],
) -> Bool {
for idx = 0; idx < instrs.length(); idx = idx + 1 {
let target = match instrs[idx] {
@lib.LocalTee(target) => target
_ => continue
}
let scan_end = Int32::min(instrs.length(), idx + 5)
for scan = idx + 1; scan < scan_end; scan = scan + 1 {
match instrs[scan] {
@lib.If(block_type, then_body, else_body) if run_hot_pipeline_slns_lowered_block_type_is_nonvoid(
block_type,
) => {
let then_returns = run_hot_pipeline_raw_merge_locals_has_return(
then_body,
)
let (then_reads, _) = run_hot_pipeline_raw_simplify_locals_subtree_has_local_and_call(
then_body, target,
)
match else_body {
Some(else_body) => {
let else_returns = run_hot_pipeline_raw_merge_locals_has_return(
else_body,
)
let (else_reads, _) = run_hot_pipeline_raw_simplify_locals_subtree_has_local_and_call(
else_body, target,
)
if (then_returns || else_returns) && (then_reads || else_reads) {
return true
}
}
None => if then_returns && then_reads { return true }
}
break
}
_ => ()
}
}
}
for instr in instrs {
match instr {
@lib.Block(_, @lib.Expr(body))
| @lib.Loop(_, @lib.Expr(body))
| @lib.TryTable(_, _, @lib.Expr(body)) =>
if run_hot_pipeline_raw_ssa_nomerge_has_result_if_embedded_tee_lifetime(
body,
) {
return true
}
@lib.If(_, then_body, else_body) => {
if run_hot_pipeline_raw_ssa_nomerge_has_result_if_embedded_tee_lifetime(
then_body,
) {
return true
}
match else_body {
Some(else_body) =>
if run_hot_pipeline_raw_ssa_nomerge_has_result_if_embedded_tee_lifetime(
else_body,
) {
return true
}
None => ()
}
}
_ => ()
}
}
false
}
///|
priv struct SsaTeeIfArmAccess {
if_idx : Int32
then_reads : Map[UInt32, Bool]
then_writes : Map[UInt32, Bool]
else_reads : Map[UInt32, Bool]
else_writes : Map[UInt32, Bool]
}
///|
fn run_hot_pipeline_raw_ssa_nomerge_collect_if_arm_accesses(
instrs : Array[@lib.Instruction],
reads : Map[UInt32, Bool],
writes : Map[UInt32, Bool],
) -> Unit {
for instr in instrs {
match instr {
@lib.LocalGet(@lib.LocalIdx(raw)) => reads[raw] = true
@lib.LocalSet(@lib.LocalIdx(raw)) | @lib.LocalTee(@lib.LocalIdx(raw)) =>
writes[raw] = true
@lib.Block(_, @lib.Expr(body))
| @lib.Loop(_, @lib.Expr(body))
| @lib.TryTable(_, _, @lib.Expr(body)) =>
run_hot_pipeline_raw_ssa_nomerge_collect_if_arm_accesses(
body, reads, writes,
)
@lib.If(_, then_body, else_body) => {
run_hot_pipeline_raw_ssa_nomerge_collect_if_arm_accesses(
then_body, reads, writes,
)
match else_body {
Some(body) =>
run_hot_pipeline_raw_ssa_nomerge_collect_if_arm_accesses(
body, reads, writes,
)
None => ()
}
}
_ => ()
}
}
}
///|
fn SsaTeeIfArmAccess::new(
if_idx : Int32,
then_body : Array[@lib.Instruction],
else_body : Array[@lib.Instruction]?,
) -> SsaTeeIfArmAccess {
let then_reads : Map[UInt32, Bool] = Map([])
let then_writes : Map[UInt32, Bool] = Map([])
let else_reads : Map[UInt32, Bool] = Map([])
let else_writes : Map[UInt32, Bool] = Map([])
run_hot_pipeline_raw_ssa_nomerge_collect_if_arm_accesses(
then_body, then_reads, then_writes,
)
match else_body {
Some(body) =>
run_hot_pipeline_raw_ssa_nomerge_collect_if_arm_accesses(
body, else_reads, else_writes,
)
None => ()
}
{ if_idx, then_reads, then_writes, else_reads, else_writes, }
}
///|
fn run_hot_pipeline_raw_ssa_nomerge_has_structured_partial_write_tee_lifetime(
instrs : Array[@lib.Instruction],
parameter_count : Int32,
) -> Bool {
let mut next_if : Array[Int32] = []
let mut cached_if : SsaTeeIfArmAccess? = None
for idx = 0; idx + 1 < instrs.length(); idx = idx + 1 {
let target = match (instrs[idx], instrs[idx + 1]) {
(@lib.LocalTee(target), @lib.Call(_)) => target
_ => continue
}
if target.0.reinterpret_as_int() < parameter_count {
continue
}
if next_if.is_empty() {
next_if = Array::make(instrs.length() + 1, -1)
for reverse_idx = instrs.length() - 1
reverse_idx >= 0
reverse_idx = reverse_idx - 1 {
next_if[reverse_idx] = match instrs[reverse_idx] {
@lib.If(_, _, _) => reverse_idx
_ => next_if[reverse_idx + 1]
}
}
}
let scan = next_if[idx + 2]
if scan >= 0 {
match instrs[scan] {
@lib.If(_, then_body, else_body) => {
let access = match cached_if {
Some(access) if access.if_idx == scan => access
_ => {
let access = SsaTeeIfArmAccess::new(scan, then_body, else_body)
cached_if = Some(access)
access
}
}
let @lib.LocalIdx(raw) = target
if access.then_reads.get(raw).unwrap_or(false) &&
access.then_writes.get(raw).unwrap_or(false) {
return true
}
if access.else_reads.get(raw).unwrap_or(false) &&
access.else_writes.get(raw).unwrap_or(false) {
return true
}
}
_ => ()
}
}
}
false
}
///|
fn run_hot_pipeline_raw_ssa_nomerge_collect_loop_local_accesses(
instrs : Array[@lib.Instruction],
local_count : Int32,
first_accesses : Array[Int32],
writes : @bitset.BitSet,
) -> Unit {
for instr in instrs {
match instr {
@lib.LocalGet(@lib.LocalIdx(raw)) => {
let local_id = raw.reinterpret_as_int()
if local_id >= 0 &&
local_id < local_count &&
first_accesses[local_id] == 0 {
first_accesses[local_id] = 1
}
}
@lib.LocalSet(@lib.LocalIdx(raw)) | @lib.LocalTee(@lib.LocalIdx(raw)) => {
let local_id = raw.reinterpret_as_int()
if local_id >= 0 && local_id < local_count {
writes[local_id] = true
if first_accesses[local_id] == 0 {
first_accesses[local_id] = 2
}
}
}
@lib.Block(_, @lib.Expr(body))
| @lib.Loop(_, @lib.Expr(body))
| @lib.TryTable(_, _, @lib.Expr(body)) =>
run_hot_pipeline_raw_ssa_nomerge_collect_loop_local_accesses(
body, local_count, first_accesses, writes,
)
@lib.If(_, then_body, else_body) => {
run_hot_pipeline_raw_ssa_nomerge_collect_loop_local_accesses(
then_body, local_count, first_accesses, writes,
)
match else_body {
Some(body) =>
run_hot_pipeline_raw_ssa_nomerge_collect_loop_local_accesses(
body, local_count, first_accesses, writes,
)
None => ()
}
}
_ => ()
}
}
}
///|
fn run_hot_pipeline_raw_ssa_nomerge_control_has_loop_carrier(
instrs : Array[@lib.Instruction],
local_count : Int32,
target_local : Int32,
) -> Bool {
for instr in instrs {
match instr {
@lib.Loop(_, @lib.Expr(body)) => {
let first_accesses = Array::make(local_count, 0)
let writes = @bitset.BitSet::new(local_count)
run_hot_pipeline_raw_ssa_nomerge_collect_loop_local_accesses(
body, local_count, first_accesses, writes,
)
if target_local >= 0 &&
target_local < local_count &&
writes[target_local] &&
run_hot_pipeline_raw_ssa_nomerge_body_reads_local(body, target_local) {
return true
}
if run_hot_pipeline_raw_ssa_nomerge_control_has_loop_carrier(
body, local_count, target_local,
) {
return true
}
}
@lib.Block(_, @lib.Expr(body)) | @lib.TryTable(_, _, @lib.Expr(body)) =>
if run_hot_pipeline_raw_ssa_nomerge_control_has_loop_carrier(
body, local_count, target_local,
) {
return true
}
@lib.If(_, then_body, else_body) => {
if run_hot_pipeline_raw_ssa_nomerge_control_has_loop_carrier(
then_body, local_count, target_local,
) {
return true
}
match else_body {
Some(body) =>
if run_hot_pipeline_raw_ssa_nomerge_control_has_loop_carrier(
body, local_count, target_local,
) {
return true
}
None => ()
}
}
_ => ()
}
}
false
}
///|
fn run_hot_pipeline_raw_ssa_nomerge_has_tee_call_loop_carrier(
instrs : Array[@lib.Instruction],
local_count : Int32,
) -> Bool {
for idx = 0; idx + 1 < instrs.length(); idx = idx + 1 {
let target_local = match (instrs[idx], instrs[idx + 1]) {
(@lib.LocalTee(@lib.LocalIdx(raw)), @lib.Call(_)) =>
raw.reinterpret_as_int()
_ => continue
}
for later = idx + 2; later < instrs.length(); later = later + 1 {
match instrs[later] {
@lib.Block(_, _) | @lib.Loop(_, _) | @lib.TryTable(_, _, _) =>
if run_hot_pipeline_raw_ssa_nomerge_control_has_loop_carrier(
[instrs[later]],
local_count,
target_local,
) {
return true
}
@lib.If(_, then_body, else_body) => {
if run_hot_pipeline_raw_ssa_nomerge_control_has_loop_carrier(
then_body, local_count, target_local,
) {
return true
}
match else_body {
Some(body) =>
if run_hot_pipeline_raw_ssa_nomerge_control_has_loop_carrier(
body, local_count, target_local,
) {
return true
}
None => ()
}
}
_ => ()
}
}
}
for instr in instrs {
match instr {
@lib.Block(_, @lib.Expr(body))
| @lib.Loop(_, @lib.Expr(body))
| @lib.TryTable(_, _, @lib.Expr(body)) =>
if run_hot_pipeline_raw_ssa_nomerge_has_tee_call_loop_carrier(
body, local_count,
) {
return true
}
@lib.If(_, then_body, else_body) => {
if run_hot_pipeline_raw_ssa_nomerge_has_tee_call_loop_carrier(
then_body, local_count,
) {
return true
}
match else_body {
Some(body) =>
if run_hot_pipeline_raw_ssa_nomerge_has_tee_call_loop_carrier(
body, local_count,
) {
return true
}
None => ()
}
}
_ => ()
}
}
false
}
///|
fn run_hot_pipeline_raw_ssa_nomerge_has_load_set_later_read(
instrs : Array[@lib.Instruction],
) -> Bool {
for idx = 0; idx + 1 < instrs.length(); idx = idx + 1 {
if !run_hot_pipeline_optimize_instructions_instr_is_plain_load(instrs[idx]) {
continue
}
let target = match instrs[idx + 1] {
@lib.LocalSet(target) => target
_ => continue
}
for later = idx + 2; later < instrs.length(); later = later + 1 {
match instrs[later] {
@lib.LocalGet(found) if found == target => return true
@lib.LocalSet(found) | @lib.LocalTee(found) if found == target => break
_ => ()
}
}
}
for instr in instrs {
match instr {
@lib.Block(_, @lib.Expr(body))
| @lib.Loop(_, @lib.Expr(body))
| @lib.TryTable(_, _, @lib.Expr(body)) =>
if run_hot_pipeline_raw_ssa_nomerge_has_load_set_later_read(body) {
return true
}
@lib.If(_, then_body, else_body) => {
if run_hot_pipeline_raw_ssa_nomerge_has_load_set_later_read(then_body) {
return true
}
match else_body {
Some(body) =>
if run_hot_pipeline_raw_ssa_nomerge_has_load_set_later_read(body) {
return true
}
None => ()
}
}
_ => ()
}
}
false
}
///|
fn run_hot_pipeline_raw_ssa_nomerge_has_tee_load_later_read(
instrs : Array[@lib.Instruction],
) -> Bool {
for idx = 0; idx + 1 < instrs.length(); idx = idx + 1 {
let target = match instrs[idx] {
@lib.LocalTee(target) => target
_ => continue
}
if !run_hot_pipeline_optimize_instructions_instr_is_plain_load(
instrs[idx + 1],
) {
continue
}
for later = idx + 2; later < instrs.length(); later = later + 1 {
match instrs[later] {
@lib.LocalGet(found) if found == target => return true
@lib.LocalSet(found) | @lib.LocalTee(found) if found == target => break
_ => ()
}
}
}
for instr in instrs {
match instr {
@lib.Block(_, @lib.Expr(body))
| @lib.Loop(_, @lib.Expr(body))
| @lib.TryTable(_, _, @lib.Expr(body)) =>
if run_hot_pipeline_raw_ssa_nomerge_has_tee_load_later_read(body) {
return true
}
@lib.If(_, then_body, else_body) => {
if run_hot_pipeline_raw_ssa_nomerge_has_tee_load_later_read(then_body) {
return true
}
match else_body {
Some(body) =>
if run_hot_pipeline_raw_ssa_nomerge_has_tee_load_later_read(body) {
return true
}
None => ()
}
}
_ => ()
}
}
false
}
///|
fn run_hot_pipeline_raw_ssa_nomerge_has_i64_shift(
instrs : Array[@lib.Instruction],
) -> Bool {
for instr in instrs {
match instr {
@lib.I64Shl | @lib.I64ShrS | @lib.I64ShrU => return true
@lib.Block(_, @lib.Expr(body))
| @lib.Loop(_, @lib.Expr(body))
| @lib.TryTable(_, _, @lib.Expr(body)) =>
if run_hot_pipeline_raw_ssa_nomerge_has_i64_shift(body) {
return true
}
@lib.If(_, then_body, else_body) => {
if run_hot_pipeline_raw_ssa_nomerge_has_i64_shift(then_body) {
return true
}
match else_body {
Some(body) =>
if run_hot_pipeline_raw_ssa_nomerge_has_i64_shift(body) {
return true
}
None => ()
}
}
_ => ()
}
}
false
}
///|
fn run_hot_pipeline_raw_ssa_nomerge_has_stacked_call_results(
instrs : Array[@lib.Instruction],
mod_ : @lib.Module,
func_type_indices : Array[@lib.TypeIdx],
) -> Bool {
let stack : Array[Bool] = []
for instr in instrs {
match instr {
@lib.Block(_, @lib.Expr(body))
| @lib.Loop(_, @lib.Expr(body))
| @lib.TryTable(_, _, @lib.Expr(body)) =>
if run_hot_pipeline_raw_ssa_nomerge_has_stacked_call_results(
body, mod_, func_type_indices,
) {
return true
}
@lib.If(_, then_body, else_body) => {
if run_hot_pipeline_raw_ssa_nomerge_has_stacked_call_results(
then_body, mod_, func_type_indices,
) {
return true
}
match else_body {
Some(body) =>
if run_hot_pipeline_raw_ssa_nomerge_has_stacked_call_results(
body, mod_, func_type_indices,
) {
return true
}
None => ()
}
}
_ => ()
}
let effect = match
dae_stack_effect_evidence(instr, mod_, func_type_indices) {
Some(effect) => effect
None => {
stack.clear()
continue
}
}
let is_call = match instr {
@lib.Call(_) | @lib.CallIndirect(_, _) | @lib.CallRef(_) => true
_ => false
}
if effect.inputs <= stack.length() {
let ambient = stack.length() - effect.inputs
if is_call && effect.outputs > 0 {
for idx = 0; idx < ambient; idx = idx + 1 {
if stack[idx] {
return true
}
}
}
let mut consumed_call_value = false
for _ in 0..
consumed_call_value = consumed_call_value || was_call_value
None => ()
}
}
for _ in 0.. Bool {
if parameter_count <= 0 {
return false
}
for read_idx = 0; read_idx + 2 < instrs.length(); read_idx = read_idx + 1 {
let target = match instrs[read_idx] {
@lib.LocalGet(target) => {
let @lib.LocalIdx(raw) = target
let local_id = raw.reinterpret_as_int()
if local_id >= 0 && local_id < parameter_count {
target
} else {
continue
}
}
_ => continue
}
if !(instrs[read_idx + 1] is @lib.I32Load(_)) ||
!(instrs[read_idx + 2] is @lib.Drop) {
continue
}
for later = read_idx + 3; later < instrs.length(); later = later + 1 {
let body = match instrs[later] {
@lib.Block(_, @lib.Expr(body))
| @lib.Loop(_, @lib.Expr(body))
| @lib.TryTable(_, _, @lib.Expr(body)) => body
@lib.If(_, then_body, _) => then_body
_ => continue
}
let (has_write, has_store) = run_hot_pipeline_raw_ssa_nomerge_local_write_store_facts(
body, target,
)
if has_write && has_store {
return true
}
}
}
false
}
///|
fn run_hot_pipeline_raw_has_parameter_read_before_later_write(
instrs : Array[@lib.Instruction],
parameter_count : Int32,
) -> Bool {
if parameter_count <= 0 {
return false
}
let first_access = Array::make(parameter_count, 0)
let writes = Array::make(parameter_count, false)
run_hot_pipeline_raw_record_parameter_accesses(
instrs, parameter_count, first_access, writes,
)
for local_id = 0; local_id < parameter_count; local_id = local_id + 1 {
if first_access[local_id] == 1 && writes[local_id] {
return true
}
}
false
}
///|
fn run_hot_pipeline_raw_simplify_locals_has_local_write(
instrs : Array[@lib.Instruction],
) -> Bool {
for instr in instrs {
match instr {
@lib.LocalSet(_) | @lib.LocalTee(_) => return true
@lib.Block(_, @lib.Expr(body))
| @lib.Loop(_, @lib.Expr(body))
| @lib.TryTable(_, _, @lib.Expr(body)) =>
if run_hot_pipeline_raw_simplify_locals_has_local_write(body) {
return true
}
@lib.If(_, then_body, else_body) => {
if run_hot_pipeline_raw_simplify_locals_has_local_write(then_body) {
return true
}
match else_body {
Some(else_body) =>
if run_hot_pipeline_raw_simplify_locals_has_local_write(else_body) {
return true
}
None => ()
}
}
@lib.Try(_, @lib.Expr(body), catches, _) => {
if run_hot_pipeline_raw_simplify_locals_has_local_write(body) {
return true
}
for catch_ in catches {
let @lib.Expr(catch_body) = match catch_ {
@lib.LegacyCatch(_, body) | @lib.LegacyCatchAll(body) => body
}
if run_hot_pipeline_raw_simplify_locals_has_local_write(catch_body) {
return true
}
}
}
_ => ()
}
}
false
}
///|
fn run_hot_pipeline_raw_simplify_locals_has_stack_effect_hazard(
instrs : Array[@lib.Instruction],
) -> Bool {
for instr in instrs {
match instr {
@lib.Call(_)
| @lib.CallIndirect(_, _)
| @lib.CallRef(_)
| @lib.MemoryCopy(_, _)
| @lib.MemoryFill(_) => return true
@lib.Block(_, @lib.Expr(body))
| @lib.Loop(_, @lib.Expr(body))
| @lib.TryTable(_, _, @lib.Expr(body)) =>
if run_hot_pipeline_raw_simplify_locals_has_stack_effect_hazard(body) {
return true
}
@lib.If(_, then_body, else_body) => {
if run_hot_pipeline_raw_simplify_locals_has_stack_effect_hazard(
then_body,
) {
return true
}
match else_body {
Some(else_body) =>
if run_hot_pipeline_raw_simplify_locals_has_stack_effect_hazard(
else_body,
) {
return true
}
None => ()
}
}
_ => ()
}
}
false
}
///|
fn run_hot_pipeline_raw_simplify_locals_instr_has_stack_effect_hazard(
instr : @lib.Instruction,
) -> Bool {
match instr {
@lib.Call(_)
| @lib.CallIndirect(_, _)
| @lib.CallRef(_)
| @lib.MemoryCopy(_, _)
| @lib.MemoryFill(_) => true
@lib.Block(_, @lib.Expr(body))
| @lib.Loop(_, @lib.Expr(body))
| @lib.TryTable(_, _, @lib.Expr(body)) =>
run_hot_pipeline_raw_simplify_locals_has_stack_effect_hazard(body)
@lib.If(_, then_body, else_body) => {
if run_hot_pipeline_raw_simplify_locals_has_stack_effect_hazard(then_body) {
return true
}
match else_body {
Some(else_body) =>
run_hot_pipeline_raw_simplify_locals_has_stack_effect_hazard(
else_body,
)
None => false
}
}
_ => false
}
}
///|
fn run_hot_pipeline_raw_simplify_locals_has_stack_effect_local_write(
instrs : Array[@lib.Instruction],
) -> Bool {
let mut previous_has_stack_effect = false
for instr in instrs {
match instr {
@lib.LocalSet(_) | @lib.LocalTee(_) =>
if previous_has_stack_effect {
return true
}
@lib.Block(_, @lib.Expr(body))
| @lib.Loop(_, @lib.Expr(body))
| @lib.TryTable(_, _, @lib.Expr(body)) =>
if run_hot_pipeline_raw_simplify_locals_has_stack_effect_local_write(
body,
) {
return true
}
@lib.If(_, then_body, else_body) => {
if run_hot_pipeline_raw_simplify_locals_has_stack_effect_local_write(
then_body,
) {
return true
}
match else_body {
Some(else_body) =>
if run_hot_pipeline_raw_simplify_locals_has_stack_effect_local_write(
else_body,
) {
return true
}
None => ()
}
}
_ => ()
}
previous_has_stack_effect = run_hot_pipeline_raw_simplify_locals_instr_has_stack_effect_hazard(
instr,
)
}
false
}
///|
fn run_hot_pipeline_simplify_locals_descriptor_is_nostructure(
descriptor_name : String,
) -> Bool {
descriptor_name == "simplify-locals-nostructure" ||
descriptor_name == "simplify-locals-no-structure" ||
descriptor_name == "simplify-locals-notee-nostructure" ||
descriptor_name == "simplify-locals-nonesting" ||
descriptor_name == "simplify-locals-no-nesting"
}
///|
fn run_hot_pipeline_descriptor_is_simplify_locals_family(
descriptor_name : String,
) -> Bool {
descriptor_name == "simplify-locals" ||
descriptor_name == "simplify-locals-notee" ||
descriptor_name == "simplify-locals-no-tee" ||
run_hot_pipeline_simplify_locals_descriptor_is_nostructure(descriptor_name)
}
///|
fn run_hot_pipeline_simplify_locals_lowered_should_skip_stack_hazard(
descriptor_name : String,
instrs : Array[@lib.Instruction],
) -> Bool {
run_hot_pipeline_simplify_locals_descriptor_is_nostructure(descriptor_name) &&
run_hot_pipeline_raw_simplify_locals_has_local_write(instrs) &&
run_hot_pipeline_raw_simplify_locals_has_stack_effect_hazard(instrs)
}
///|
fn run_hot_pipeline_simplify_locals_initial_should_skip_stack_hazard(
descriptor_name : String,
instrs : Array[@lib.Instruction],
) -> Bool {
run_hot_pipeline_simplify_locals_descriptor_is_nostructure(descriptor_name) &&
run_hot_pipeline_raw_simplify_locals_has_stack_effect_local_write(instrs)
}
///|
fn run_hot_pipeline_raw_simplify_locals_deep_result_if_unread_tee_cleanup(
mod_ : @lib.Module,
func_idx : @lib.FuncIdx,
module_state : HotPipelineModuleState,
options : HotPipelineOptions,
locals : @lib.Locals,
instrs : Array[@lib.Instruction],
) -> HotPipelineFuncResult? {
let module_ctx = run_hot_pipeline_require_module_ctx(
mod_, module_state, options,
)
let params = run_hot_pipeline_required_raw_func_params(
mod_, module_ctx, func_idx, module_state,
)
let local_gets : Array[Int32] = Array::make(
params.length() + locals.length(),
0,
)
let local_sets : Array[Int32] = Array::make(local_gets.length(), 0)
let local_tees : Array[Int32] = Array::make(local_gets.length(), 0)
run_hot_pipeline_raw_collect_local_ref_counts(
instrs, local_gets, local_sets, local_tees,
)
let (dead_tee_instrs, dead_tee_rewrites) = run_hot_pipeline_raw_simplify_locals_remove_unread_local_tees(
instrs, local_gets,
)
if dead_tee_rewrites == 0 {
return None
}
Some({
reason: "deep-result-if-unread-local-tee-cleanup",
func: @lib.Func::new(locals, @lib.Expr::new(dead_tee_instrs)),
unchanged_original: false,
trace_skip_raw: true,
})
}
///|
fn run_hot_pipeline_raw_simplify_locals_convergence_guard(
descriptor : @ir.HotPassDescriptor,
original : @lib.Func,
instrs : Array[@lib.Instruction],
stats : RawSimplifyLocalsGateStats,
allow_existing_ladder : Bool,
) -> HotPipelineFuncResult? {
let giant_post_inline_call_builder = stats.instr_count >= 4096 &&
stats.call_count >= 1024
if giant_post_inline_call_builder {
return Some({
reason: "giant-post-inline-call-builder",
func: original,
unchanged_original: true,
trace_skip_raw: true,
})
}
let deep_result_if_many_local_write_convergence = stats.locals_count >= 32 &&
(stats.local_set_count > 0 || stats.local_tee_count > 0) &&
run_hot_pipeline_raw_simplify_locals_has_deep_result_if_chain(instrs, 0)
if deep_result_if_many_local_write_convergence && !allow_existing_ladder {
return Some({
reason: "deep-result-if-many-local-write-convergence",
func: original,
unchanged_original: true,
trace_skip_raw: true,
})
}
let deep_block_local_write_convergence = stats.locals_count >= 128 &&
stats.block_count >= 64 &&
stats.loop_count == 0 &&
stats.if_count == 0 &&
stats.call_count == 0 &&
stats.local_set_count + stats.local_tee_count > 0 &&
stats.local_set_count + stats.local_tee_count <= 4 &&
stats.local_get_count >= 64
if deep_block_local_write_convergence {
return Some({
reason: "deep-block-local-write-convergence",
func: original,
unchanged_original: true,
trace_skip_raw: true,
})
}
let deep_br_table_local_write_convergence = (
stats.local_set_count > 0 || stats.local_tee_count > 0
) &&
run_hot_pipeline_raw_simplify_locals_has_deep_br_table(instrs, 0)
if deep_br_table_local_write_convergence {
return Some({
reason: "deep-br-table-local-write-convergence",
func: original,
unchanged_original: true,
trace_skip_raw: true,
})
}
let deep_result_if_local_tee_convergence = stats.local_tee_count > 0 &&
run_hot_pipeline_raw_simplify_locals_has_deep_result_if_chain(instrs, 0)
if deep_result_if_local_tee_convergence && !allow_existing_ladder {
return Some({
reason: "deep-result-if-local-tee-convergence",
func: original,
unchanged_original: true,
trace_skip_raw: true,
})
}
let many_result_if_local_write_convergence = (
stats.local_set_count > 0 || stats.local_tee_count > 0
) &&
run_hot_pipeline_raw_simplify_locals_result_if_count_at_most(instrs, 16) >=
16
if many_result_if_local_write_convergence && !allow_existing_ladder {
return Some({
reason: "many-result-if-local-write-convergence",
func: original,
unchanged_original: true,
trace_skip_raw: true,
})
}
let structured_local_tee_hot_protect = run_hot_pipeline_descriptor_is_simplify_locals_family(
descriptor.name,
) &&
stats.locals_count >= 8 &&
stats.locals_count <= 64 &&
stats.has_structured_control &&
stats.local_tee_count > 0
if structured_local_tee_hot_protect {
if !stats.has_stack_effect_hazard {
let (cleaned, changed) = run_hot_pipeline_simplify_locals_cleanup_exact_func(
original,
)
if changed {
return Some({
reason: "structured-local-tee-exact-cleanup",
func: cleaned,
unchanged_original: false,
trace_skip_raw: true,
})
}
}
return Some({
reason: "structured-local-tee-hot-protect",
func: original,
unchanged_original: true,
trace_skip_raw: true,
})
}
None
}
///|
fn run_hot_pipeline_raw_simplify_locals_scan_shape(
instrs : Array[@lib.Instruction],
shape : RawSimplifyLocalsShape,
) -> Unit {
match run_hot_pipeline_raw_simplify_locals_match_tail_local_get(instrs) {
Some(_) => shape.exact_tail_count = shape.exact_tail_count + 1
None => ()
}
for instr in instrs {
shape.instr_count = shape.instr_count + 1
if !shape.has_recurrence_binary &&
run_hot_pipeline_raw_simplify_locals_instr_is_recurrence_binary(instr) {
shape.has_recurrence_binary = true
}
match instr {
@lib.I32Add => shape.has_i32_add = true
@lib.V128Bitselect => shape.has_v128_bitselect = true
@lib.I32x4ShrU => shape.has_i32x4_shr_u = true
@lib.V128Xor | @lib.I32x4Add => shape.has_simd_carrier_op = true
@lib.GlobalGet(_) | @lib.GlobalSet(_) => shape.has_global_state = true
@lib.MemoryCopy(_, _) | @lib.MemoryFill(_) =>
shape.has_stack_effect_hazard = true
@lib.Call(_) | @lib.CallIndirect(_, _) | @lib.CallRef(_) => {
shape.call_count = shape.call_count + 1
shape.has_stack_effect_hazard = true
}
@lib.ReturnCall(_)
| @lib.ReturnCallIndirect(_, _)
| @lib.ReturnCallRef(_) => shape.call_count = shape.call_count + 1
@lib.If(_, then_body, else_body) => {
shape.if_count = shape.if_count + 1
run_hot_pipeline_raw_simplify_locals_scan_shape(then_body, shape)
match else_body {
Some(else_body) =>
run_hot_pipeline_raw_simplify_locals_scan_shape(else_body, shape)
None => ()
}
}
@lib.Block(_, @lib.Expr(body)) => {
shape.block_count = shape.block_count + 1
run_hot_pipeline_raw_simplify_locals_scan_shape(body, shape)
}
@lib.Loop(_, @lib.Expr(body)) => {
shape.loop_count = shape.loop_count + 1
run_hot_pipeline_raw_simplify_locals_scan_shape(body, shape)
}
@lib.TryTable(_, _, @lib.Expr(body)) => {
shape.block_count = shape.block_count + 1
run_hot_pipeline_raw_simplify_locals_scan_shape(body, shape)
}
@lib.Try(_, @lib.Expr(body), catches, _) => {
shape.block_count = shape.block_count + 1
run_hot_pipeline_raw_simplify_locals_scan_shape(body, shape)
for catch_ in catches {
let @lib.Expr(catch_body) = match catch_ {
@lib.LegacyCatch(_, body) | @lib.LegacyCatchAll(body) => body
}
run_hot_pipeline_raw_simplify_locals_scan_shape(catch_body, shape)
}
}
@lib.LocalSet(_) => shape.local_set_count = shape.local_set_count + 1
@lib.LocalTee(_) => shape.local_tee_count = shape.local_tee_count + 1
@lib.LocalGet(_) => shape.local_get_count = shape.local_get_count + 1
@lib.MemorySize(_) => shape.has_memory_size = true
@lib.Return => shape.return_count = shape.return_count + 1
_ => ()
}
}
}
///|
fn run_hot_pipeline_raw_simplify_locals_gate_stats(
locals : @lib.Locals,
instrs : Array[@lib.Instruction],
) -> RawSimplifyLocalsGateStats {
let shape = RawSimplifyLocalsShape::new()
run_hot_pipeline_raw_simplify_locals_scan_shape(instrs, shape)
RawSimplifyLocalsGateStats::new(
locals_count=locals.length(),
instr_count=shape.instr_count,
has_structured_control=shape.if_count > 0 ||
shape.block_count > 0 ||
shape.loop_count > 0,
if_count=shape.if_count,
block_count=shape.block_count,
loop_count=shape.loop_count,
local_set_count=shape.local_set_count,
local_tee_count=shape.local_tee_count,
local_get_count=shape.local_get_count,
call_count=shape.call_count,
return_count=shape.return_count,
exact_tail_count=shape.exact_tail_count,
has_global_state=shape.has_global_state,
has_memory_size=shape.has_memory_size,
has_stack_effect_hazard=shape.has_stack_effect_hazard,
has_i32_add=shape.has_i32_add,
has_v128_bitselect=shape.has_v128_bitselect,
has_i32x4_shr_u=shape.has_i32x4_shr_u,
has_simd_carrier_op=shape.has_simd_carrier_op,
has_recurrence_binary=shape.has_recurrence_binary,
)
}
///|
// Raw SimplifyLocals threshold policy.
//
// These predicates describe measured artifact-shape envelopes. Rewrite gates
// admit bounded raw fast paths; skip gates avoid disproportionately expensive
// lift/lower or validation work while preserving the original function. They
// are performance policy unless their trace classification says `correctness`.
// Treat every numeric edge as inclusive/exclusive API and keep representative
// ±1 tests in `pass_manager_threshold_wbtest.mbt`.
#cfg(target="native")
#warnings("-unused_value")
fn run_hot_pipeline_raw_simplify_locals_should_rewrite_ladder(
locals : @lib.Locals,
instrs : Array[@lib.Instruction],
) -> Bool {
let stats = run_hot_pipeline_raw_simplify_locals_gate_stats(locals, instrs)
if stats.locals_count < 16 || stats.locals_count > 512 {
return false
}
if !stats.has_structured_control || stats.instr_count > 12000 {
return false
}
let large_ladder = stats.locals_count >= 48 &&
stats.instr_count >= 160 &&
stats.if_count >= 16 &&
stats.local_set_count >= 16 &&
stats.local_get_count >= 32 &&
stats.exact_tail_count >= 12 &&
stats.exact_tail_count * 4 + 16 >= stats.if_count &&
stats.return_count <= stats.exact_tail_count + 8
let small_ladder = stats.locals_count >= 16 &&
stats.instr_count >= 80 &&
stats.if_count >= 8 &&
stats.local_set_count >= 8 &&
stats.local_get_count >= 16 &&
stats.exact_tail_count >= 4 &&
stats.exact_tail_count * 2 + 4 >= stats.if_count &&
stats.return_count <= stats.exact_tail_count + 4
let sparse_ladder = stats.locals_count >= 16 &&
stats.instr_count >= 80 &&
stats.if_count >= 12 &&
stats.local_set_count >= 10 &&
stats.local_get_count >= 24 &&
stats.call_count >= stats.if_count &&
stats.exact_tail_count >= 5 &&
stats.exact_tail_count * 3 + 1 >= stats.if_count &&
stats.return_count <= stats.if_count
large_ladder || small_ladder || sparse_ladder
}
///|
fn run_hot_pipeline_raw_simplify_locals_giant_func_stats(
stats : RawSimplifyLocalsGateStats,
) -> Bool {
stats.locals_count >= 1500 &&
stats.has_structured_control &&
stats.instr_count >= 2000
}
///|
#cfg(target="native")
#warnings("-unused_value")
fn run_hot_pipeline_raw_simplify_locals_should_skip_giant_func(
locals : @lib.Locals,
instrs : Array[@lib.Instruction],
) -> Bool {
run_hot_pipeline_raw_simplify_locals_giant_func_stats(
run_hot_pipeline_raw_simplify_locals_gate_stats(locals, instrs),
)
}
///|
#cfg(target="native")
#warnings("-unused_value")
fn run_hot_pipeline_raw_simplify_locals_should_skip_giant_structured_call_heavy(
locals : @lib.Locals,
instrs : Array[@lib.Instruction],
) -> Bool {
let stats = run_hot_pipeline_raw_simplify_locals_gate_stats(locals, instrs)
stats.locals_count >= 352 &&
stats.has_structured_control &&
stats.instr_count >= 256 &&
stats.if_count >= 80 &&
stats.local_set_count >= 176 &&
stats.local_get_count >= 384 &&
stats.call_count >= 240
}
///|
#cfg(target="native")
#warnings("-unused_value")
fn run_hot_pipeline_raw_simplify_locals_should_skip_validator_structured_call_heavy(
locals : @lib.Locals,
instrs : Array[@lib.Instruction],
) -> Bool {
let stats = run_hot_pipeline_raw_simplify_locals_gate_stats(locals, instrs)
stats.locals_count >= 256 &&
stats.has_structured_control &&
stats.instr_count >= 256 &&
stats.if_count >= 48 &&
stats.block_count >= 32 &&
stats.loop_count >= 4 &&
stats.local_set_count >= 128 &&
stats.local_get_count >= 384 &&
stats.call_count >= 192
}
///|
#cfg(target="native")
#warnings("-unused_value")
fn run_hot_pipeline_raw_simplify_locals_should_skip_parser_structured_call_heavy(
locals : @lib.Locals,
instrs : Array[@lib.Instruction],
) -> Bool {
let stats = run_hot_pipeline_raw_simplify_locals_gate_stats(locals, instrs)
stats.locals_count >= 96 &&
stats.locals_count <= 352 &&
stats.has_structured_control &&
stats.instr_count >= 160 &&
stats.if_count >= 16 &&
stats.if_count <= 64 &&
stats.block_count >= 4 &&
stats.block_count <= 20 &&
stats.loop_count <= 2 &&
stats.local_set_count >= 80 &&
stats.local_get_count >= 220 &&
stats.call_count >= 64 &&
stats.call_count <= 128 &&
stats.return_count <= stats.if_count
}
///|
#cfg(target="native")
#warnings("-unused_value")
fn run_hot_pipeline_raw_simplify_locals_should_skip_decode_structured_call_heavy(
locals : @lib.Locals,
instrs : Array[@lib.Instruction],
) -> Bool {
let stats = run_hot_pipeline_raw_simplify_locals_gate_stats(locals, instrs)
stats.locals_count >= 80 &&
stats.locals_count <= 128 &&
stats.has_structured_control &&
stats.instr_count >= 192 &&
stats.if_count >= 12 &&
stats.if_count <= 40 &&
stats.block_count >= 8 &&
stats.block_count <= 24 &&
stats.loop_count == 1 &&
stats.local_set_count >= 40 &&
stats.local_set_count <= 96 &&
stats.local_get_count >= 144 &&
stats.local_get_count <= 180 &&
stats.call_count >= 48 &&
stats.call_count <= 64 &&
stats.return_count <= 16
}
///|
#cfg(target="native")
#warnings("-unused_value")
fn run_hot_pipeline_raw_simplify_locals_should_skip_encode_structured_call_heavy(
locals : @lib.Locals,
instrs : Array[@lib.Instruction],
) -> Bool {
let stats = run_hot_pipeline_raw_simplify_locals_gate_stats(locals, instrs)
stats.locals_count >= 192 &&
stats.locals_count <= 256 &&
stats.has_structured_control &&
stats.instr_count >= 512 &&
stats.if_count >= 96 &&
stats.if_count <= 192 &&
stats.block_count >= 32 &&
stats.block_count <= 64 &&
stats.loop_count == 0 &&
stats.local_set_count >= 56 &&
stats.local_set_count <= 96 &&
stats.local_get_count >= 352 &&
stats.local_get_count <= 544 &&
stats.call_count >= 160 &&
stats.call_count <= 224 &&
stats.return_count >= 8 &&
stats.return_count <= 16
}
///|
#cfg(target="native")
fn run_hot_pipeline_raw_simplify_locals_branchy_decode_structured_call_heavy_stats(
stats : RawSimplifyLocalsGateStats,
) -> Bool {
stats.locals_count >= 192 &&
stats.locals_count <= 224 &&
stats.has_structured_control &&
stats.instr_count >= 640 &&
stats.if_count >= 48 &&
stats.if_count <= 80 &&
stats.block_count >= 16 &&
stats.block_count <= 32 &&
stats.loop_count == 0 &&
stats.local_set_count >= 48 &&
stats.local_set_count <= 96 &&
stats.local_get_count >= 240 &&
stats.local_get_count <= 384 &&
stats.call_count >= 80 &&
stats.call_count <= 128 &&
stats.return_count >= 4 &&
stats.return_count <= 12
}
///|
#cfg(target="native")
fn run_hot_pipeline_raw_simplify_locals_transformer_catch_structured_call_heavy_stats(
stats : RawSimplifyLocalsGateStats,
) -> Bool {
stats.locals_count >= 184 &&
stats.locals_count <= 224 &&
stats.has_structured_control &&
stats.instr_count >= 512 &&
stats.if_count >= 32 &&
stats.if_count <= 64 &&
stats.block_count >= 24 &&
stats.block_count <= 40 &&
stats.loop_count == 0 &&
stats.local_set_count >= 72 &&
stats.local_set_count <= 96 &&
stats.local_get_count >= 256 &&
stats.local_get_count <= 352 &&
stats.call_count >= 80 &&
stats.call_count <= 112 &&
stats.return_count >= 8 &&
stats.return_count <= 16
}
///|
#cfg(target="native")
fn run_hot_pipeline_raw_simplify_locals_branchy_structured_call_heavy_stats(
stats : RawSimplifyLocalsGateStats,
) -> Bool {
stats.locals_count >= 176 &&
stats.locals_count <= 224 &&
stats.has_structured_control &&
stats.instr_count >= 768 &&
stats.if_count >= 48 &&
stats.if_count <= 64 &&
stats.block_count >= 24 &&
stats.block_count <= 40 &&
stats.loop_count == 0 &&
stats.local_set_count >= 64 &&
stats.local_set_count <= 80 &&
stats.local_get_count >= 280 &&
stats.local_get_count <= 352 &&
stats.call_count >= 96 &&
stats.call_count <= 144 &&
stats.return_count >= 4 &&
stats.return_count <= 8
}
///|
fn run_hot_pipeline_raw_simplify_locals_unchecked_small_structured_call_mesh_stats(
stats : RawSimplifyLocalsGateStats,
) -> Bool {
stats.locals_count <= 8 &&
stats.has_structured_control &&
stats.instr_count >= 192 &&
stats.instr_count <= 384 &&
stats.if_count >= 12 &&
stats.if_count <= 24 &&
stats.block_count <= 4 &&
stats.loop_count <= 2 &&
stats.local_set_count >= 4 &&
stats.local_set_count <= 12 &&
stats.local_get_count >= 64 &&
stats.local_get_count <= 128 &&
stats.call_count >= 48 &&
stats.call_count <= 72 &&
stats.return_count >= 4 &&
stats.return_count <= 12 &&
stats.exact_tail_count == 0
}
///|
fn run_hot_pipeline_raw_simplify_locals_giant_validator_structured_call_heavy_stats(
stats : RawSimplifyLocalsGateStats,
) -> Bool {
stats.locals_count >= 1024 &&
stats.has_structured_control &&
stats.instr_count >= 8000 &&
stats.if_count >= 256 &&
stats.block_count >= 64 &&
stats.loop_count >= 4 &&
stats.local_set_count >= 1024 &&
stats.local_get_count >= 2048 &&
stats.call_count >= 1024 &&
stats.return_count <= 32 &&
stats.exact_tail_count == 0
}
///|
fn run_hot_pipeline_raw_simplify_locals_dense_structured_call_heavy_stats(
stats : RawSimplifyLocalsGateStats,
) -> Bool {
stats.locals_count >= 320 &&
stats.locals_count <= 640 &&
stats.has_structured_control &&
stats.instr_count >= 768 &&
stats.if_count >= 48 &&
stats.if_count <= 160 &&
stats.block_count >= 16 &&
stats.block_count <= 64 &&
stats.loop_count <= 2 &&
stats.local_set_count >= 128 &&
stats.local_get_count >= 512 &&
stats.call_count >= 160 &&
stats.return_count <= 40
}
///|
#cfg(target="native")
#warnings("-unused_value")
fn run_hot_pipeline_raw_simplify_locals_should_skip_branchy_decode_structured_call_heavy(
locals : @lib.Locals,
instrs : Array[@lib.Instruction],
) -> Bool {
let stats = run_hot_pipeline_raw_simplify_locals_gate_stats(locals, instrs)
run_hot_pipeline_raw_simplify_locals_branchy_decode_structured_call_heavy_stats(
stats,
)
}
///|
#cfg(target="native")
#warnings("-unused_value")
fn run_hot_pipeline_raw_simplify_locals_should_skip_transformer_catch_structured_call_heavy(
locals : @lib.Locals,
instrs : Array[@lib.Instruction],
) -> Bool {
let stats = run_hot_pipeline_raw_simplify_locals_gate_stats(locals, instrs)
run_hot_pipeline_raw_simplify_locals_transformer_catch_structured_call_heavy_stats(
stats,
)
}
///|
#cfg(target="native")
#warnings("-unused_value")
fn run_hot_pipeline_raw_simplify_locals_should_skip_branchy_structured_call_heavy(
locals : @lib.Locals,
instrs : Array[@lib.Instruction],
) -> Bool {
let stats = run_hot_pipeline_raw_simplify_locals_gate_stats(locals, instrs)
run_hot_pipeline_raw_simplify_locals_branchy_structured_call_heavy_stats(
stats,
)
}
///|
#cfg(target="native")
#warnings("-unused_value")
fn run_hot_pipeline_raw_simplify_locals_should_skip_dense_structured_call_heavy(
locals : @lib.Locals,
instrs : Array[@lib.Instruction],
) -> Bool {
let stats = run_hot_pipeline_raw_simplify_locals_gate_stats(locals, instrs)
run_hot_pipeline_raw_simplify_locals_dense_structured_call_heavy_stats(stats)
}
///|
#cfg(target="native")
#warnings("-unused_value")
fn run_hot_pipeline_raw_simplify_locals_should_skip_stringview_trim_loop_churn(
locals : @lib.Locals,
instrs : Array[@lib.Instruction],
) -> Bool {
let stats = run_hot_pipeline_raw_simplify_locals_gate_stats(locals, instrs)
stats.locals_count >= 34 &&
stats.locals_count <= 40 &&
stats.has_structured_control &&
stats.instr_count >= 64 &&
stats.if_count >= 2 &&
stats.if_count <= 3 &&
stats.block_count == 0 &&
stats.loop_count == 1 &&
stats.local_set_count >= 8 &&
stats.local_set_count <= 12 &&
stats.local_get_count >= 32 &&
stats.local_get_count <= 52 &&
stats.call_count >= 12 &&
stats.call_count <= 18 &&
stats.return_count == 0
}
///|
#cfg(target="native")
#warnings("-unused_value")
fn run_hot_pipeline_raw_simplify_locals_should_skip_transformer_structured_call_heavy(
locals : @lib.Locals,
instrs : Array[@lib.Instruction],
) -> Bool {
let stats = run_hot_pipeline_raw_simplify_locals_gate_stats(locals, instrs)
stats.locals_count >= 84 &&
stats.locals_count <= 96 &&
stats.has_structured_control &&
stats.instr_count >= 160 &&
stats.if_count >= 18 &&
stats.if_count <= 28 &&
stats.block_count >= 12 &&
stats.block_count <= 20 &&
stats.loop_count == 0 &&
stats.local_set_count >= 32 &&
stats.local_set_count <= 48 &&
stats.local_get_count >= 140 &&
stats.local_get_count <= 180 &&
stats.call_count >= 48 &&
stats.call_count <= 64 &&
stats.return_count <= 8
}
///|
#cfg(target="native")
#warnings("-unused_value")
fn run_hot_pipeline_raw_simplify_locals_should_skip_small_loop_call_heavy(
locals : @lib.Locals,
instrs : Array[@lib.Instruction],
) -> Bool {
let stats = run_hot_pipeline_raw_simplify_locals_gate_stats(locals, instrs)
stats.locals_count >= 80 &&
stats.locals_count <= 90 &&
stats.has_structured_control &&
stats.instr_count >= 160 &&
stats.if_count >= 12 &&
stats.if_count <= 20 &&
stats.block_count == 0 &&
stats.loop_count == 2 &&
stats.local_set_count >= 72 &&
stats.local_set_count <= 84 &&
stats.local_get_count >= 100 &&
stats.local_get_count <= 192 &&
stats.call_count >= 24 &&
stats.call_count <= 48 &&
stats.return_count <= 4
}
///|
#cfg(target="native")
#warnings("-unused_value")
fn run_hot_pipeline_raw_simplify_locals_should_skip_straight_line_builder_churn(
locals : @lib.Locals,
instrs : Array[@lib.Instruction],
) -> Bool {
let stats = run_hot_pipeline_raw_simplify_locals_gate_stats(locals, instrs)
stats.locals_count >= 224 &&
stats.locals_count <= 320 &&
!stats.has_structured_control &&
stats.instr_count >= 768 &&
stats.if_count == 0 &&
stats.block_count == 0 &&
stats.loop_count == 0 &&
stats.local_set_count >= 144 &&
stats.local_set_count <= 192 &&
stats.local_get_count >= 384 &&
stats.local_get_count <= 704 &&
stats.local_get_count >= stats.local_set_count * 2 + 48 &&
stats.call_count >= 72 &&
stats.call_count <= 128 &&
stats.return_count == 0
}
///|
#cfg(target="native")
#warnings("-unused_value")
fn run_hot_pipeline_raw_simplify_locals_should_skip_huge_straight_line_call_builder(
locals : @lib.Locals,
instrs : Array[@lib.Instruction],
) -> Bool {
let stats = run_hot_pipeline_raw_simplify_locals_gate_stats(locals, instrs)
stats.locals_count >= 768 &&
!stats.has_structured_control &&
stats.instr_count >= 2048 &&
stats.if_count == 0 &&
stats.block_count == 0 &&
stats.loop_count == 0 &&
stats.local_set_count <= 16 &&
stats.local_get_count >= 1536 &&
stats.call_count >= 1024 &&
stats.return_count == 0
}
///|
#cfg(target="native")
#warnings("-unused_value")
fn run_hot_pipeline_raw_simplify_locals_should_skip_medium_structured_call_heavy(
locals : @lib.Locals,
instrs : Array[@lib.Instruction],
) -> Bool {
let stats = run_hot_pipeline_raw_simplify_locals_gate_stats(locals, instrs)
stats.locals_count >= 176 &&
stats.locals_count <= 320 &&
stats.has_structured_control &&
stats.instr_count >= 224 &&
stats.if_count >= 24 &&
stats.if_count <= 96 &&
stats.loop_count <= 3 &&
stats.local_set_count >= 96 &&
stats.local_get_count >= 256 &&
stats.call_count >= 128 &&
stats.call_count <= 224 &&
stats.return_count <= stats.if_count
}
///|
#cfg(target="native")
#warnings("-unused_value")
fn run_hot_pipeline_raw_simplify_locals_should_skip_loop_heavy_structured_call_heavy(
locals : @lib.Locals,
instrs : Array[@lib.Instruction],
) -> Bool {
let stats = run_hot_pipeline_raw_simplify_locals_gate_stats(locals, instrs)
stats.locals_count >= 176 &&
stats.locals_count <= 320 &&
stats.has_structured_control &&
stats.instr_count >= 256 &&
stats.if_count >= 28 &&
stats.if_count <= 80 &&
stats.block_count >= 8 &&
stats.loop_count >= 4 &&
stats.local_set_count >= 80 &&
stats.local_get_count >= 200 &&
stats.call_count >= 80 &&
stats.call_count <= 224 &&
stats.return_count <= stats.if_count
}
///|
#cfg(target="native")
#warnings("-unused_value")
fn run_hot_pipeline_raw_simplify_locals_should_skip_giant_structured_local_churn(
locals : @lib.Locals,
instrs : Array[@lib.Instruction],
) -> Bool {
let stats = run_hot_pipeline_raw_simplify_locals_gate_stats(locals, instrs)
stats.locals_count >= 600 &&
stats.has_structured_control &&
stats.instr_count >= 1500 &&
stats.if_count >= 96 &&
stats.local_set_count >= 384 &&
stats.local_get_count >= 768 &&
stats.call_count >= 16 &&
stats.call_count <= 96 &&
stats.return_count <= 4
}
///|
fn run_hot_pipeline_raw_simplify_locals_branch_dense_structured_call_heavy_stats(
stats : RawSimplifyLocalsGateStats,
) -> Bool {
stats.locals_count >= 48 &&
stats.has_structured_control &&
stats.instr_count >= 384 &&
stats.if_count >= 96 &&
stats.block_count <= 12 &&
stats.loop_count == 0 &&
stats.local_set_count <= 64 &&
stats.local_get_count >= stats.if_count * 2 &&
stats.local_get_count >= stats.local_set_count * 4 + 48 &&
stats.call_count >= 96 &&
stats.call_count <= 256 &&
stats.return_count <= 32 &&
stats.exact_tail_count <= 8
}
///|
#cfg(target="native")
#warnings("-unused_value")
fn run_hot_pipeline_raw_simplify_locals_should_skip_branch_dense_structured_call_heavy(
locals : @lib.Locals,
instrs : Array[@lib.Instruction],
) -> Bool {
let stats = run_hot_pipeline_raw_simplify_locals_gate_stats(locals, instrs)
run_hot_pipeline_raw_simplify_locals_branch_dense_structured_call_heavy_stats(
stats,
)
}
///|
fn run_hot_pipeline_raw_simplify_locals_block_rich_structured_call_heavy_stats(
stats : RawSimplifyLocalsGateStats,
) -> Bool {
stats.locals_count >= 96 &&
stats.locals_count <= 256 &&
stats.has_structured_control &&
stats.instr_count >= 320 &&
stats.if_count >= 32 &&
stats.if_count <= 96 &&
stats.block_count >= 16 &&
stats.block_count <= 40 &&
stats.loop_count == 0 &&
stats.local_set_count >= 56 &&
stats.local_set_count <= 96 &&
stats.local_get_count >= 160 &&
stats.local_get_count >= stats.local_set_count * 2 + 16 &&
stats.call_count >= 96 &&
stats.call_count <= 160 &&
stats.return_count <= 24 &&
stats.exact_tail_count <= 8
}
///|
#cfg(target="native")
#warnings("-unused_value")
fn run_hot_pipeline_raw_simplify_locals_should_skip_block_rich_structured_call_heavy(
locals : @lib.Locals,
instrs : Array[@lib.Instruction],
) -> Bool {
let stats = run_hot_pipeline_raw_simplify_locals_gate_stats(locals, instrs)
run_hot_pipeline_raw_simplify_locals_block_rich_structured_call_heavy_stats(
stats,
)
}
///|
fn run_hot_pipeline_raw_simplify_locals_call_dense_structured_walker_stats(
stats : RawSimplifyLocalsGateStats,
) -> Bool {
stats.locals_count >= 96 &&
stats.locals_count <= 320 &&
stats.has_structured_control &&
stats.instr_count >= 320 &&
stats.if_count >= 40 &&
stats.if_count <= 192 &&
stats.block_count <= 24 &&
stats.loop_count <= 6 &&
stats.local_set_count >= 1 &&
stats.local_set_count <= 96 &&
stats.local_get_count >= 192 &&
stats.local_get_count >= stats.if_count * 2 + 32 &&
stats.local_get_count >= stats.local_set_count * 4 + 32 &&
stats.call_count >= 160 &&
stats.call_count >= stats.if_count * 2 + 16 &&
stats.return_count <= 32 &&
stats.exact_tail_count <= 8
}
///|
#cfg(target="native")
#warnings("-unused_value")
fn run_hot_pipeline_raw_simplify_locals_should_skip_call_dense_structured_walker(
locals : @lib.Locals,
instrs : Array[@lib.Instruction],
) -> Bool {
let stats = run_hot_pipeline_raw_simplify_locals_gate_stats(locals, instrs)
run_hot_pipeline_raw_simplify_locals_call_dense_structured_walker_stats(stats)
}
///|
fn run_hot_pipeline_raw_simplify_locals_low_local_decision_ladder_stats(
stats : RawSimplifyLocalsGateStats,
) -> Bool {
stats.locals_count <= 8 &&
stats.has_structured_control &&
stats.instr_count >= 192 &&
stats.if_count >= 32 &&
stats.loop_count == 0 &&
stats.local_set_count <= 8 &&
stats.local_get_count >= stats.if_count + 32 &&
stats.call_count >= 16 &&
stats.return_count <= stats.if_count + 32 &&
stats.exact_tail_count <= 4
}
///|
fn run_hot_pipeline_raw_simplify_locals_low_local_loop_return_call_ladder_stats(
stats : RawSimplifyLocalsGateStats,
) -> Bool {
stats.locals_count <= 8 &&
stats.has_structured_control &&
stats.instr_count >= 128 &&
stats.if_count >= 16 &&
stats.loop_count >= 2 &&
stats.local_set_count <= 12 &&
stats.local_get_count >= 48 &&
stats.call_count >= 16 &&
stats.return_count >= 1 &&
stats.return_count <= stats.if_count + 8 &&
stats.exact_tail_count <= 4
}
///|
fn run_hot_pipeline_raw_simplify_locals_mid_local_structured_call_ladder_stats(
stats : RawSimplifyLocalsGateStats,
) -> Bool {
stats.locals_count >= 32 &&
stats.locals_count <= 64 &&
stats.has_structured_control &&
stats.instr_count >= 768 &&
stats.if_count >= 128 &&
stats.block_count >= 32 &&
stats.loop_count <= 4 &&
stats.local_set_count >= 128 &&
stats.local_get_count >= 512 &&
stats.call_count >= 192 &&
stats.return_count <= stats.if_count + 16
}
///|
fn run_hot_pipeline_raw_simplify_locals_has_i32_pair_result(
mod_ : @lib.Module,
func_idx : @lib.FuncIdx,
module_state : HotPipelineModuleState,
options : HotPipelineOptions,
) -> Bool {
let module_ctx = run_hot_pipeline_require_module_ctx(
mod_, module_state, options,
)
match @ir.hot_module_resolve_func_type(module_ctx, func_idx) {
Some(@lib.FuncType(_, results)) =>
results.length() == 2 &&
results[0] == @lib.ValType::i32() &&
results[1] == @lib.ValType::i32()
None => false
}
}
///|
#cfg(target="native")
#warnings("-unused_value")
fn run_hot_pipeline_raw_simplify_locals_should_skip_multivalue_call_ladder(
locals : @lib.Locals,
instrs : Array[@lib.Instruction],
) -> Bool {
let stats = run_hot_pipeline_raw_simplify_locals_gate_stats(locals, instrs)
let sparse_call_ladder = stats.locals_count >= 16 &&
stats.locals_count <= 512 &&
stats.has_structured_control &&
stats.exact_tail_count == 0 &&
stats.instr_count >= 160 &&
stats.if_count >= 12 &&
stats.loop_count <= 2 &&
stats.local_set_count >= 6 &&
stats.local_get_count >= 48 &&
stats.local_get_count >= stats.local_set_count * 3 &&
stats.call_count >= 24 &&
stats.call_count >= stats.if_count &&
stats.return_count <= stats.if_count + 12
let compact_call_ladder = stats.locals_count >= 32 &&
stats.locals_count <= 64 &&
stats.has_structured_control &&
stats.exact_tail_count == 0 &&
stats.instr_count >= 192 &&
stats.if_count >= 10 &&
stats.if_count <= 24 &&
stats.block_count <= 2 &&
stats.loop_count == 0 &&
stats.local_set_count >= 12 &&
stats.local_get_count >= 64 &&
stats.local_get_count >= stats.local_set_count * 5 &&
stats.call_count >= 32 &&
stats.call_count >= stats.if_count * 3 &&
stats.return_count <= 4
let flat_dense_call_ladder = stats.locals_count >= 96 &&
stats.locals_count <= 192 &&
stats.has_structured_control &&
stats.exact_tail_count == 0 &&
stats.instr_count >= 480 &&
stats.if_count >= 32 &&
stats.block_count == 0 &&
stats.loop_count == 0 &&
stats.local_set_count >= 48 &&
stats.local_get_count >= 160 &&
stats.local_get_count >= stats.local_set_count * 2 + 32 &&
stats.call_count >= 48 &&
stats.call_count >= stats.if_count + 16 &&
stats.return_count <= stats.if_count
let loop_rich_call_ladder = stats.locals_count >= 160 &&
stats.locals_count <= 256 &&
stats.has_structured_control &&
stats.exact_tail_count == 0 &&
stats.instr_count >= 1000 &&
stats.if_count >= 48 &&
stats.block_count <= 4 &&
stats.loop_count <= 4 &&
stats.local_set_count >= 96 &&
stats.local_get_count >= 384 &&
stats.local_get_count >= stats.local_set_count * 3 + 64 &&
stats.call_count >= 192 &&
stats.call_count >= stats.if_count * 4 &&
stats.return_count <= stats.if_count
sparse_call_ladder ||
compact_call_ladder ||
flat_dense_call_ladder ||
loop_rich_call_ladder
}
///|
#cfg(target="native")
#warnings("-unused_value")
fn run_hot_pipeline_raw_simplify_locals_should_rewrite_pure_call_tails(
locals : @lib.Locals,
instrs : Array[@lib.Instruction],
) -> Bool {
let stats = run_hot_pipeline_raw_simplify_locals_gate_stats(locals, instrs)
!stats.has_structured_control &&
stats.locals_count >= 32 &&
stats.instr_count >= 128 &&
stats.local_set_count >= 16 &&
stats.local_get_count >= 32
}
///|
#cfg(target="native")
#warnings("-unused_value")
fn run_hot_pipeline_raw_simplify_locals_should_rewrite_straight_line_adjacent_local_tees(
locals : @lib.Locals,
instrs : Array[@lib.Instruction],
) -> Bool {
let stats = run_hot_pipeline_raw_simplify_locals_gate_stats(locals, instrs)
!stats.has_structured_control &&
stats.locals_count >= 512 &&
stats.instr_count >= 2048 &&
stats.local_set_count >= 1 &&
stats.local_set_count <= 16 &&
stats.local_get_count >= 1024 &&
stats.local_get_count >= stats.local_set_count * 64 &&
stats.call_count >= 768 &&
stats.return_count <= 1
}
///|
#cfg(target="native")
#warnings("-unused_value")
fn run_hot_pipeline_raw_simplify_locals_should_rewrite_straight_line_lane_builder(
locals : @lib.Locals,
instrs : Array[@lib.Instruction],
) -> Bool {
let stats = run_hot_pipeline_raw_simplify_locals_gate_stats(locals, instrs)
!stats.has_structured_control &&
stats.locals_count >= 48 &&
stats.locals_count <= 64 &&
stats.instr_count >= 240 &&
stats.instr_count <= 384 &&
stats.if_count == 0 &&
stats.block_count == 0 &&
stats.loop_count == 0 &&
stats.local_set_count >= 48 &&
stats.local_set_count <= 64 &&
stats.local_get_count >= 80 &&
stats.local_get_count <= 128 &&
stats.call_count >= 32 &&
stats.call_count <= 40 &&
stats.return_count == 0
}
///|
#cfg(target="native")
#warnings("-unused_value")
fn run_hot_pipeline_raw_simplify_locals_should_rewrite_structured_pure_call_tails(
locals : @lib.Locals,
instrs : Array[@lib.Instruction],
) -> Bool {
let stats = run_hot_pipeline_raw_simplify_locals_gate_stats(locals, instrs)
stats.has_structured_control &&
stats.locals_count >= 128 &&
stats.instr_count >= 256 &&
stats.local_set_count >= 24 &&
stats.local_get_count >= 48
}
///|
fn run_hot_pipeline_raw_simplify_locals_func_env(
mod_ : @lib.Module,
func_idx : @lib.FuncIdx,
module_ctx : @ir.HotModuleContext,
locals : @lib.Locals,
module_env~ : @validate.Env? = None,
) -> @validate.Env {
match @ir.hot_module_resolve_func_type(module_ctx, func_idx) {
Some(@lib.FuncType(params, results)) => {
let all_locals = @lib.Locals::from_types(params)
all_locals.append(locals)
let base_env = match module_env {
Some(env) => env
None => @validate.Env::new().with_module(mod_)
}
base_env.with_locals(all_locals).with_return_type(Some(results))
}
None =>
abort(
"hot pipeline raw simplify-locals rewrite requires the function type to resolve",
)
}
}
///|
// Scan the original prefix without materializing its unused head. Candidate
// suffixes retain the same complete typecheck and unknown-opcode fallback.
// An optional seed belongs to this exact immutable environment; every query
// owns its stack and starts from the existing all-initialized local policy.
// Return the owned suffix directly; its length supplies the start index without
// an extra tuple allocation at every existing split-wrapper call.
fn sl_value_suffix_at(
instrs : Array[@lib.Instruction],
end : Int32,
count : Int32,
env : @validate.Env,
typed_instructions~ : Array[Int32]? = None,
typed_seed~ : SlStatementTypeSeed? = None,
state_builds~ : Array[Int32]? = None,
) -> Array[@lib.Instruction] {
sl_value_suffix_at_with_seed(
instrs, end, count, env, typed_seed, typed_instructions, state_builds,
)
}
///|
fn sl_value_suffix_at_with_seed(
instrs : Array[@lib.Instruction],
end : Int32,
count : Int32,
env : @validate.Env,
typed_seed : SlStatementTypeSeed?,
typed_instructions : Array[Int32]?,
state_builds : Array[Int32]?,
) -> Array[@lib.Instruction] {
if count <= 0 {
return []
}
// These one-instruction producers have exact typing without constructing a
// full initialized-local mask. Compound/unknown suffixes keep full checking.
if count == 1 && end > 0 {
let last = instrs[end - 1]
let complete_leaf = match last {
@lib.I32Const(_)
| @lib.I64Const(_)
| @lib.F32Const(_)
| @lib.F64Const(_) => true
@lib.LocalGet(idx) => env.get_local_type(idx) is Some(_)
_ => false
}
if complete_leaf {
return [last]
}
}
let mut required = 0
let mut net = 0
let mut known = true
let mut start = end - 1
while start >= 0 {
if known {
match sl_value_suffix_stack_effect(instrs[start], env) {
Some((inputs, outputs)) => {
required = Int32::max(inputs, required + inputs - outputs)
net += outputs - inputs
}
None => known = false
}
if known && (required != 0 || net != count) {
start -= 1
continue
}
}
let suffix : Array[@lib.Instruction] = []
for idx = start; idx < end; idx = idx + 1 {
suffix.push(instrs[idx])
}
match typed_instructions {
Some(visits) => visits[0] += suffix.length()
None => ()
}
let init_state = match typed_seed {
Some(seed) => seed.fresh_state()
None => {
if state_builds is Some(builds) {
builds[0] += 1
}
@validate.make_state_owned(env, [])
}
}
match
@validate.typecheck_expr_for_probe(@lib.Expr::new(suffix), init_state) {
Ok(state) =>
if state.reachable &&
state.escape == @validate.tc_escape_none() &&
state.stack.length() == count {
return suffix
}
Err(_) => ()
}
start = start - 1
}
[]
}
///|
fn run_hot_pipeline_raw_simplify_locals_take_value_suffix(
instrs : Array[@lib.Instruction],
count : Int32,
env : @validate.Env,
typed_instructions~ : Array[Int32]? = None,
) -> (Array[@lib.Instruction], Array[@lib.Instruction]) {
if count <= 0 {
return (instrs.copy(), [])
}
let suffix = sl_value_suffix_at(
instrs,
instrs.length(),
count,
env,
typed_instructions~,
)
if suffix.is_empty() {
return (instrs.copy(), suffix)
}
let start = instrs.length() - suffix.length()
let head : Array[@lib.Instruction] = []
for index in 0.. Bool {
match instr {
@lib.I32Const(_)
| @lib.I64Const(_)
| @lib.F32Const(_)
| @lib.F64Const(_)
| @lib.LocalGet(_) => true
_ => false
}
}
///|
fn run_hot_pipeline_raw_simplify_locals_instr_source_local(
instr : @lib.Instruction,
) -> Int32? {
match instr {
@lib.LocalGet(@lib.LocalIdx(raw)) => Some(raw.reinterpret_as_int())
_ => None
}
}
///|
fn run_hot_pipeline_raw_simplify_locals_instr_source_locals(
instrs : Array[@lib.Instruction],
) -> Array[Int32] {
let source_locals : Array[Int32] = []
sl_collect_producer_source_locals(instrs, source_locals)
source_locals
}
///|
fn sl_collect_producer_source_locals(
instrs : Array[@lib.Instruction],
source_locals : Array[Int32],
) -> Unit {
for instr in instrs {
match instr {
@lib.LocalGet(@lib.LocalIdx(raw)) => {
let local_id = raw.reinterpret_as_int()
if !source_locals.contains(local_id) {
source_locals.push(local_id)
}
}
@lib.Block(_, @lib.Expr(body))
| @lib.Loop(_, @lib.Expr(body))
| @lib.TryTable(_, _, @lib.Expr(body)) =>
sl_collect_producer_source_locals(body, source_locals)
@lib.If(_, yes, no) => {
sl_collect_producer_source_locals(yes, source_locals)
if no is Some(body) {
sl_collect_producer_source_locals(body, source_locals)
}
}
@lib.Try(_, @lib.Expr(body), catches, _) => {
sl_collect_producer_source_locals(body, source_locals)
for catch_ in catches {
match catch_ {
@lib.LegacyCatch(_, @lib.Expr(body))
| @lib.LegacyCatchAll(@lib.Expr(body)) =>
sl_collect_producer_source_locals(body, source_locals)
}
}
}
_ => ()
}
}
}
///|
fn run_hot_pipeline_raw_simplify_locals_instr_written_locals(
instrs : Array[@lib.Instruction],
) -> Array[Int32] {
let written_locals : Array[Int32] = []
fn collect(body : Array[@lib.Instruction]) -> Unit {
for instr in body {
match instr {
@lib.LocalSet(@lib.LocalIdx(raw)) | @lib.LocalTee(@lib.LocalIdx(raw)) => {
let local_id = raw.reinterpret_as_int()
if !written_locals.any(fn(existing) { existing == local_id }) {
written_locals.push(local_id)
}
}
@lib.Block(_, @lib.Expr(nested))
| @lib.Loop(_, @lib.Expr(nested))
| @lib.TryTable(_, _, @lib.Expr(nested)) => collect(nested)
@lib.If(_, then_body, else_body) => {
collect(then_body)
match else_body {
Some(nested) => collect(nested)
None => ()
}
}
@lib.Try(_, @lib.Expr(body), catches, _) => {
collect(body)
for catch_ in catches {
match catch_ {
@lib.LegacyCatch(_, @lib.Expr(body))
| @lib.LegacyCatchAll(@lib.Expr(body)) => collect(body)
}
}
}
_ => ()
}
}
}
collect(instrs)
written_locals
}
///|
fn run_hot_pipeline_raw_simplify_locals_instr_is_call_like(
instr : @lib.Instruction,
) -> Bool {
match instr {
@lib.Call(_) | @lib.CallIndirect(_, _) | @lib.CallRef(_) => true
_ => false
}
}
///|
fn run_hot_pipeline_raw_simplify_locals_instr_is_structured(
instr : @lib.Instruction,
) -> Bool {
match instr {
@lib.Block(_, @lib.Expr(_))
| @lib.Loop(_, @lib.Expr(_))
| @lib.If(_, _, _)
| @lib.TryTable(_, _, @lib.Expr(_)) => true
_ => false
}
}
///|
fn run_hot_pipeline_raw_simplify_locals_instr_is_early_terminator(
instr : @lib.Instruction,
) -> Bool {
match instr {
@lib.Return
| @lib.ReturnCall(_)
| @lib.ReturnCallIndirect(_, _)
| @lib.ReturnCallRef(_)
| @lib.Throw(_)
| @lib.ThrowRef
| @lib.Unreachable
| @lib.Br(_)
| @lib.BrIf(_)
| @lib.BrTable(_, _) => true
_ => false
}
}
///|
fn run_hot_pipeline_raw_simplify_locals_instr_is_unconditional_terminator(
instr : @lib.Instruction,
) -> Bool {
match instr {
@lib.Return
| @lib.ReturnCall(_)
| @lib.ReturnCallIndirect(_, _)
| @lib.ReturnCallRef(_)
| @lib.Throw(_)
| @lib.ThrowRef
| @lib.Unreachable
| @lib.Br(_)
| @lib.BrTable(_, _) => true
_ => false
}
}
///|
fn run_hot_pipeline_raw_simplify_locals_instr_writes_local(
instr : @lib.Instruction,
local_id : Int32,
) -> Bool {
match instr {
@lib.LocalSet(@lib.LocalIdx(raw)) | @lib.LocalTee(@lib.LocalIdx(raw)) =>
raw.reinterpret_as_int() == local_id
_ => false
}
}
///|
fn run_hot_pipeline_raw_simplify_locals_instrs_have_nested_early_terminator(
instrs : Array[@lib.Instruction],
) -> Bool {
for instr in instrs {
if run_hot_pipeline_raw_simplify_locals_instr_is_early_terminator(instr) {
return true
}
match instr {
@lib.Block(_, @lib.Expr(body))
| @lib.Loop(_, @lib.Expr(body))
| @lib.TryTable(_, _, @lib.Expr(body)) =>
if run_hot_pipeline_raw_simplify_locals_instrs_have_nested_early_terminator(
body,
) {
return true
}
@lib.If(_, then_body, else_body) => {
if run_hot_pipeline_raw_simplify_locals_instrs_have_nested_early_terminator(
then_body,
) {
return true
}
match else_body {
Some(body) =>
if run_hot_pipeline_raw_simplify_locals_instrs_have_nested_early_terminator(
body,
) {
return true
}
None => ()
}
}
_ => ()
}
}
false
}
///|
fn run_hot_pipeline_raw_simplify_locals_statement_is_safe_copy_if_barrier(
statement : Array[@lib.Instruction],
target_local : Int32,
source_local : Int32,
) -> Bool {
if statement.length() == 0 {
return false
}
let mut if_count = 0
for idx = 0; idx < statement.length(); idx = idx + 1 {
match statement[idx] {
@lib.If(_, _, _) => {
if_count = if_count + 1
if idx != statement.length() - 1 {
return false
}
}
instr =>
if run_hot_pipeline_raw_simplify_locals_instr_is_structured(instr) {
return false
}
}
}
if if_count != 1 {
return false
}
!run_hot_pipeline_raw_simplify_locals_instrs_have_nested_early_terminator(
statement,
) &&
!run_hot_pipeline_raw_instrs_have_later_local_read(statement, 0, target_local) &&
!run_hot_pipeline_raw_instrs_have_later_local_write(
statement, 0, target_local,
) &&
!run_hot_pipeline_raw_instrs_have_later_local_read(statement, 0, source_local) &&
!run_hot_pipeline_raw_instrs_have_later_local_write(
statement, 0, source_local,
)
}
///|
fn run_hot_pipeline_raw_simplify_locals_instr_is_dupable_copy_value(
instr : @lib.Instruction,
) -> Bool {
match instr {
@lib.LocalGet(_)
| @lib.I32Const(_)
| @lib.I64Const(_)
| @lib.F32Const(_)
| @lib.F64Const(_) => true
_ => false
}
}
///|
fn run_hot_pipeline_raw_simplify_locals_rewrite_local_reads_in_body(
body : Array[@lib.Instruction],
target_local : Int32,
replacement : @lib.Instruction,
) -> Array[@lib.Instruction] {
let rewritten : Array[@lib.Instruction] = []
for instr in body {
match instr {
@lib.LocalGet(@lib.LocalIdx(raw)) =>
if raw.reinterpret_as_int() == target_local {
rewritten.push(replacement)
} else {
rewritten.push(instr)
}
@lib.Block(bt, @lib.Expr(nested_body)) =>
rewritten.push(
@lib.Instruction::block(
bt,
@lib.Expr::new(
run_hot_pipeline_raw_simplify_locals_rewrite_local_reads_in_body(
nested_body, target_local, replacement,
),
),
),
)
@lib.Loop(bt, @lib.Expr(nested_body)) =>
rewritten.push(
@lib.Instruction::loop_(
bt,
@lib.Expr::new(
run_hot_pipeline_raw_simplify_locals_rewrite_local_reads_in_body(
nested_body, target_local, replacement,
),
),
),
)
@lib.TryTable(bt, catches, @lib.Expr(nested_body)) =>
rewritten.push(
@lib.Instruction::try_table(
bt,
catches,
@lib.Expr::new(
run_hot_pipeline_raw_simplify_locals_rewrite_local_reads_in_body(
nested_body, target_local, replacement,
),
),
),
)
@lib.If(bt, then_body, else_body) =>
rewritten.push(
@lib.Instruction::if_(
bt,
run_hot_pipeline_raw_simplify_locals_rewrite_local_reads_in_body(
then_body, target_local, replacement,
),
match else_body {
Some(else_body) =>
Some(
run_hot_pipeline_raw_simplify_locals_rewrite_local_reads_in_body(
else_body, target_local, replacement,
),
)
None => None
},
),
)
_ => rewritten.push(instr)
}
}
rewritten
}
///|
fn run_hot_pipeline_raw_simplify_locals_try_rewrite_dupable_copy_into_next_if_condition(
remaining : Array[@lib.Instruction],
target_local : Int32,
replacement : @lib.Instruction,
source_locals : Array[Int32],
start~ : Int32 = 0,
) -> (Array[@lib.Instruction], Int32)? {
let prefix : Array[@lib.Instruction] = []
let mut idx = start
while idx < remaining.length() {
let instr = remaining[idx]
if run_hot_pipeline_raw_simplify_locals_instr_writes_local(
instr, target_local,
) ||
source_locals.any(fn(local_id) {
run_hot_pipeline_raw_simplify_locals_instr_writes_local(instr, local_id)
}) {
return None
}
match instr {
@lib.If(_, _, _) =>
if run_hot_pipeline_raw_instrs_have_later_local_read(
prefix, 0, target_local,
) &&
!run_hot_pipeline_raw_instrs_have_later_local_read(
[instr],
0,
target_local,
) &&
!run_hot_pipeline_raw_instrs_have_later_local_write(
[instr],
0,
target_local,
) &&
!source_locals.any(fn(local_id) {
run_hot_pipeline_raw_instrs_have_later_local_write(
[instr],
0,
local_id,
)
}) &&
!run_hot_pipeline_raw_instrs_have_later_local_read_before_next_local_write(
remaining,
idx + 1,
target_local,
) {
let rewritten : Array[@lib.Instruction] = []
let rewritten_prefix = run_hot_pipeline_raw_simplify_locals_rewrite_local_reads_in_body(
prefix, target_local, replacement,
)
for prefix_instr in rewritten_prefix {
rewritten.push(prefix_instr)
}
let rewritten_if = run_hot_pipeline_raw_simplify_locals_rewrite_local_reads_in_body(
[instr],
target_local,
replacement,
)
for rewritten_instr in rewritten_if {
rewritten.push(rewritten_instr)
}
return Some((rewritten, idx + 1 - start))
} else {
return None
}
structured =>
if run_hot_pipeline_raw_simplify_locals_instr_is_structured(structured) ||
run_hot_pipeline_raw_simplify_locals_instr_is_early_terminator(
structured,
) {
return None
} else {
prefix.push(instr)
idx = idx + 1
}
}
}
None
}
///|
fn run_hot_pipeline_raw_simplify_locals_try_rewrite_dupable_copy_terminal_value_suffix(
remaining : Array[@lib.Instruction],
target_local : Int32,
replacement : @lib.Instruction,
source_locals : Array[Int32],
env : @validate.Env,
start~ : Int32 = 0,
) -> (Array[@lib.Instruction], Int32)? {
let last_index = remaining.length() - 1
if start > last_index {
return None
}
let last = remaining[last_index]
match last {
@lib.LocalGet(@lib.LocalIdx(raw)) if raw.reinterpret_as_int() ==
target_local => ()
_ => return None
}
// Only a one-instruction local.get suffix can succeed. Preserve its complete
// typing and initialization checks, without constructing every other suffix.
match @validate.Typecheck::typecheck(last, @validate.make_state(env, [])) {
Ok(state) if state.reachable &&
state.escape == @validate.tc_escape_none() &&
state.stack.length() == 1 => ()
_ => return None
}
for index = start; index < last_index; index = index + 1 {
let instr = remaining[index]
if run_hot_pipeline_raw_simplify_locals_instr_is_structured(instr) ||
run_hot_pipeline_raw_simplify_locals_instr_is_early_terminator(instr) ||
run_hot_pipeline_raw_simplify_locals_instr_writes_local(
instr, target_local,
) ||
source_locals.any(fn(local_id) {
run_hot_pipeline_raw_simplify_locals_instr_writes_local(instr, local_id)
}) {
return None
}
}
let rewritten : Array[@lib.Instruction] = []
rewritten.push(@lib.Instruction::nop())
for index = start; index < last_index; index = index + 1 {
rewritten.push(remaining[index])
}
rewritten.push(replacement)
Some((rewritten, remaining.length() - start))
}
///|
fn run_hot_pipeline_raw_simplify_locals_rewrite_dupable_copy_middle_statements_into_statement(
middle_statements : Array[Array[@lib.Instruction]],
statement : Array[@lib.Instruction],
instrs : Array[@lib.Instruction],
statement_end : Int32,
) -> (Array[@lib.Instruction], Array[@lib.Instruction], Int32) {
let rewritten_middle_reversed : Array[Array[@lib.Instruction]] = []
let later_middle : Array[@lib.Instruction] = []
let mut rewritten_statement = statement
let mut rewrite_count = 0
let mut idx = middle_statements.length() - 1
while idx >= 0 {
let middle_statement = middle_statements[idx]
let mut emitted = middle_statement
if middle_statement.length() == 2 &&
run_hot_pipeline_raw_simplify_locals_instr_is_dupable_copy_value(
middle_statement[0],
) {
match middle_statement[1] {
@lib.LocalSet(@lib.LocalIdx(target_raw)) => {
let target_local = target_raw.reinterpret_as_int()
let source_locals = run_hot_pipeline_raw_simplify_locals_instr_source_locals([
middle_statement[0],
],
)
if run_hot_pipeline_raw_instrs_have_later_local_read(
rewritten_statement, 0, target_local,
) &&
!run_hot_pipeline_raw_instrs_have_later_local_write(
rewritten_statement, 0, target_local,
) &&
!run_hot_pipeline_raw_instrs_have_later_local_read(
later_middle, 0, target_local,
) &&
!run_hot_pipeline_raw_instrs_have_later_local_write(
later_middle, 0, target_local,
) &&
!run_hot_pipeline_raw_instrs_have_later_local_read_before_next_local_write(
instrs, statement_end, target_local,
) &&
!source_locals.any(fn(local_id) {
run_hot_pipeline_raw_instrs_have_later_local_write(
later_middle, 0, local_id,
) ||
run_hot_pipeline_raw_instrs_have_later_local_write(
rewritten_statement, 0, local_id,
)
}) {
rewritten_statement = run_hot_pipeline_raw_simplify_locals_rewrite_local_reads_in_body(
rewritten_statement,
target_local,
middle_statement[0],
)
emitted = [@lib.Instruction::nop()]
rewrite_count = rewrite_count + 1
}
}
_ => ()
}
}
rewritten_middle_reversed.push(emitted)
for instr in emitted {
later_middle.push(instr)
}
idx = idx - 1
}
let rewritten_middle : Array[@lib.Instruction] = []
let mut reverse_idx = rewritten_middle_reversed.length() - 1
while reverse_idx >= 0 {
for instr in rewritten_middle_reversed[reverse_idx] {
rewritten_middle.push(instr)
}
reverse_idx = reverse_idx - 1
}
(rewritten_middle, rewritten_statement, rewrite_count)
}
///|
fn run_hot_pipeline_raw_simplify_locals_has_pure_call_tail_candidates_flat(
instrs : Array[@lib.Instruction],
) -> Bool {
let mut idx = 0
while idx + 3 < instrs.length() {
if run_hot_pipeline_raw_simplify_locals_instr_is_pure_movable_value(
instrs[idx],
) {
match instrs[idx + 1] {
@lib.LocalSet(@lib.LocalIdx(target_raw)) => {
let target_local = target_raw.reinterpret_as_int()
let producer_source = run_hot_pipeline_raw_simplify_locals_instr_source_local(
instrs[idx],
)
let mut use_idx = idx + 2
let mut blocked = false
while use_idx + 1 < instrs.length() && !blocked {
let instr = instrs[use_idx]
if run_hot_pipeline_raw_simplify_locals_instr_is_structured(instr) ||
run_hot_pipeline_raw_simplify_locals_instr_is_early_terminator(
instr,
) {
blocked = true
} else if run_hot_pipeline_raw_simplify_locals_instr_writes_local(
instr, target_local,
) {
blocked = true
} else {
match producer_source {
Some(source_local) =>
if run_hot_pipeline_raw_simplify_locals_instr_writes_local(
instr, source_local,
) {
blocked = true
}
None => ()
}
}
if !blocked {
match instr {
@lib.LocalGet(@lib.LocalIdx(raw)) =>
if raw.reinterpret_as_int() == target_local {
let mut call_idx = use_idx + 1
while call_idx < instrs.length() && !blocked {
if run_hot_pipeline_raw_simplify_locals_instr_is_call_like(
instrs[call_idx],
) {
return true
} else if run_hot_pipeline_raw_simplify_locals_instr_is_pure_movable_value(
instrs[call_idx],
) {
call_idx = call_idx + 1
} else {
blocked = true
}
}
}
_ => ()
}
}
use_idx = use_idx + 1
}
}
_ => ()
}
}
idx = idx + 1
}
false
}
///|
fn run_hot_pipeline_raw_simplify_locals_has_pure_call_tail_candidates_nested(
instrs : Array[@lib.Instruction],
) -> Bool {
if run_hot_pipeline_raw_simplify_locals_has_pure_call_tail_candidates_flat(
instrs,
) {
return true
}
for instr in instrs {
match instr {
@lib.Block(_, @lib.Expr(body))
| @lib.Loop(_, @lib.Expr(body))
| @lib.TryTable(_, _, @lib.Expr(body)) =>
if run_hot_pipeline_raw_simplify_locals_has_pure_call_tail_candidates_nested(
body,
) {
return true
}
@lib.If(_, then_body, else_body) => {
if run_hot_pipeline_raw_simplify_locals_has_pure_call_tail_candidates_nested(
then_body,
) {
return true
}
match else_body {
Some(else_body) =>
if run_hot_pipeline_raw_simplify_locals_has_pure_call_tail_candidates_nested(
else_body,
) {
return true
}
None => ()
}
}
_ => ()
}
}
false
}
///|
fn run_hot_pipeline_raw_simplify_locals_rewrite_pure_call_tails(
instrs : Array[@lib.Instruction],
) -> (Array[@lib.Instruction], Int32) {
let rewritten : Array[@lib.Instruction] = []
let mut idx = 0
let mut rewrite_count = 0
while idx < instrs.length() {
let mut rewritten_here = false
if idx + 3 < instrs.length() &&
run_hot_pipeline_raw_simplify_locals_instr_is_pure_movable_value(
instrs[idx],
) {
match instrs[idx + 1] {
@lib.LocalSet(@lib.LocalIdx(target_raw)) => {
let target_local = target_raw.reinterpret_as_int()
let producer_source = run_hot_pipeline_raw_simplify_locals_instr_source_local(
instrs[idx],
)
let mut use_idx = idx + 2
let mut blocked = false
let mut found_use = false
while use_idx + 1 < instrs.length() && !blocked && !found_use {
let instr = instrs[use_idx]
if run_hot_pipeline_raw_simplify_locals_instr_is_structured(instr) ||
run_hot_pipeline_raw_simplify_locals_instr_is_early_terminator(
instr,
) {
blocked = true
} else if run_hot_pipeline_raw_simplify_locals_instr_writes_local(
instr, target_local,
) {
blocked = true
} else {
match producer_source {
Some(source_local) =>
if run_hot_pipeline_raw_simplify_locals_instr_writes_local(
instr, source_local,
) {
blocked = true
}
None => ()
}
}
if !blocked {
match instr {
@lib.LocalGet(@lib.LocalIdx(raw)) =>
if raw.reinterpret_as_int() == target_local {
let mut call_idx = use_idx + 1
let mut found_call = false
while call_idx < instrs.length() && !found_call && !blocked {
if run_hot_pipeline_raw_simplify_locals_instr_is_call_like(
instrs[call_idx],
) {
if !run_hot_pipeline_raw_instrs_have_later_local_read(
instrs,
call_idx + 1,
target_local,
) {
found_use = true
found_call = true
} else {
blocked = true
}
} else if run_hot_pipeline_raw_simplify_locals_instr_is_pure_movable_value(
instrs[call_idx],
) {
call_idx = call_idx + 1
} else {
blocked = true
}
}
}
_ => ()
}
}
if !blocked && !found_use {
use_idx = use_idx + 1
}
}
if found_use {
for middle_idx = idx + 2
middle_idx < use_idx
middle_idx = middle_idx + 1 {
rewritten.push(instrs[middle_idx])
}
rewritten.push(instrs[idx])
rewrite_count = rewrite_count + 1
idx = use_idx + 1
rewritten_here = true
}
}
_ => ()
}
}
if !rewritten_here {
rewritten.push(instrs[idx])
idx = idx + 1
}
}
(rewritten, rewrite_count)
}
///|
fn run_hot_pipeline_raw_simplify_locals_rewrite_pure_call_tails_nested(
instrs : Array[@lib.Instruction],
) -> (Array[@lib.Instruction], Int32) {
let normalized : Array[@lib.Instruction] = []
let mut rewrite_count = 0
for instr in instrs {
match instr {
@lib.Block(bt, @lib.Expr(body)) => {
let (rewritten_body, nested_rewrites) = run_hot_pipeline_raw_simplify_locals_rewrite_pure_call_tails_nested(
body,
)
normalized.push(
@lib.Instruction::block(bt, @lib.Expr::new(rewritten_body)),
)
rewrite_count = rewrite_count + nested_rewrites
}
@lib.Loop(bt, @lib.Expr(body)) => {
let (rewritten_body, nested_rewrites) = run_hot_pipeline_raw_simplify_locals_rewrite_pure_call_tails_nested(
body,
)
normalized.push(
@lib.Instruction::loop_(bt, @lib.Expr::new(rewritten_body)),
)
rewrite_count = rewrite_count + nested_rewrites
}
@lib.TryTable(bt, catches, @lib.Expr(body)) => {
let (rewritten_body, nested_rewrites) = run_hot_pipeline_raw_simplify_locals_rewrite_pure_call_tails_nested(
body,
)
normalized.push(
@lib.Instruction::try_table(
bt,
catches,
@lib.Expr::new(rewritten_body),
),
)
rewrite_count = rewrite_count + nested_rewrites
}
@lib.If(bt, then_body, else_body) => {
let (rewritten_then, then_rewrites) = run_hot_pipeline_raw_simplify_locals_rewrite_pure_call_tails_nested(
then_body,
)
rewrite_count = rewrite_count + then_rewrites
let rewritten_else = match else_body {
Some(else_body) => {
let (rewritten_else, else_rewrites) = run_hot_pipeline_raw_simplify_locals_rewrite_pure_call_tails_nested(
else_body,
)
rewrite_count = rewrite_count + else_rewrites
Some(rewritten_else)
}
None => None
}
normalized.push(
@lib.Instruction::if_(bt, rewritten_then, rewritten_else),
)
}
_ => normalized.push(instr)
}
}
let (rewritten, flat_rewrites) = run_hot_pipeline_raw_simplify_locals_rewrite_pure_call_tails(
normalized,
)
(rewritten, rewrite_count + flat_rewrites)
}
///|
fn run_hot_pipeline_raw_simplify_locals_rewrite_pure_call_tails_nested_fixpoint(
instrs : Array[@lib.Instruction],
) -> (Array[@lib.Instruction], Int32) {
let mut current = instrs
let mut total_rewrites = 0
let mut pass_count = 0
while pass_count < 3 &&
run_hot_pipeline_raw_simplify_locals_has_pure_call_tail_candidates_nested(
current,
) {
let (rewritten, rewrite_count) = run_hot_pipeline_raw_simplify_locals_rewrite_pure_call_tails_nested(
current,
)
total_rewrites = total_rewrites + rewrite_count
current = rewritten
if rewrite_count == 0 {
break
}
pass_count = pass_count + 1
}
(current, total_rewrites)
}
///|
fn run_hot_pipeline_raw_simplify_locals_has_pure_suffix_local_set_candidates_flat(
instrs : Array[@lib.Instruction],
) -> Bool {
let mut idx = 1
while idx < instrs.length() {
match instrs[idx] {
@lib.LocalSet(@lib.LocalIdx(target_raw)) =>
if run_hot_pipeline_raw_simplify_locals_instr_is_pure_movable_value(
instrs[idx - 1],
) &&
run_hot_pipeline_raw_instrs_have_later_local_read_before_next_local_write(
instrs,
idx + 1,
target_raw.reinterpret_as_int(),
) {
return true
}
_ => ()
}
idx = idx + 1
}
false
}
///|
fn run_hot_pipeline_raw_simplify_locals_has_pure_suffix_local_set_candidates_nested(
instrs : Array[@lib.Instruction],
) -> Bool {
if run_hot_pipeline_raw_simplify_locals_has_pure_suffix_local_set_candidates_flat(
instrs,
) {
return true
}
for instr in instrs {
match instr {
@lib.Block(_, @lib.Expr(body))
| @lib.Loop(_, @lib.Expr(body))
| @lib.TryTable(_, _, @lib.Expr(body)) =>
if run_hot_pipeline_raw_simplify_locals_has_pure_suffix_local_set_candidates_nested(
body,
) {
return true
}
@lib.If(_, then_body, else_body) => {
if run_hot_pipeline_raw_simplify_locals_has_pure_suffix_local_set_candidates_nested(
then_body,
) {
return true
}
match else_body {
Some(else_body) =>
if run_hot_pipeline_raw_simplify_locals_has_pure_suffix_local_set_candidates_nested(
else_body,
) {
return true
}
None => ()
}
}
_ => ()
}
}
false
}
///|
#inline
fn sl_collect_pure_suffix_movable_middle(
instrs : Array[@lib.Instruction],
start : Int32,
env : @validate.Env,
target_local : Int32,
source_locals : Array[Int32],
movable_middle : Array[@lib.Instruction],
producer_is_dupable_copy : Bool,
initialization_slots~ : Array[Int32]? = None,
typed_seed~ : SlStatementTypeSeed? = None,
) -> Int32 {
let mut sink_idx = start
while sink_idx < instrs.length() {
let statement = sl_statement_at(
instrs,
sink_idx,
env,
typed_seed~,
initialization_slots~,
)
if statement.length() == 0 ||
statement.any(fn(instr) {
run_hot_pipeline_raw_simplify_locals_instr_is_structured(instr) ||
run_hot_pipeline_raw_simplify_locals_instr_is_early_terminator(instr)
}) ||
run_hot_pipeline_raw_instrs_have_later_local_read(
statement, 0, target_local,
) ||
run_hot_pipeline_raw_instrs_have_later_local_write(
statement, 0, target_local,
) ||
source_locals.any(fn(local_id) {
run_hot_pipeline_raw_instrs_have_later_local_write(
statement, 0, local_id,
)
}) {
break
}
let statement_len = statement.length()
for instr in statement {
movable_middle.push(instr)
}
sink_idx = sink_idx + statement_len
// Dupable copies only use the zero/nonzero middle decision. Their later
// terminal/conditional helpers restart from the original store boundary.
// One admitted statement proves nonempty; further typing/copies cannot
// change that decision. Nondupable moves still need the complete middle.
if producer_is_dupable_copy {
break
}
}
sink_idx
}
///|
#inline
fn run_hot_pipeline_raw_simplify_locals_rewrite_pure_suffix_local_sets_flat(
instrs : Array[@lib.Instruction],
env : @validate.Env,
) -> (Array[@lib.Instruction], Int32) {
run_hot_pipeline_raw_simplify_locals_rewrite_pure_suffix_local_sets_flat_with_statement_setup(
instrs, env,
)
}
///|
fn run_hot_pipeline_raw_simplify_locals_rewrite_pure_suffix_local_sets_flat_with_statement_setup(
instrs : Array[@lib.Instruction],
env : @validate.Env,
initialization_slots~ : Array[Int32]? = None,
) -> (Array[@lib.Instruction], Int32) {
let rewritten : Array[@lib.Instruction] = []
let mut cursor = 0
let mut scan_idx = 0
let mut rewrite_count = 0
let mut typed_seed : SlStatementTypeSeed? = None
let wide_environment = env.locals.length() >= 128
let mut checked_compound_suffix = false
while scan_idx < instrs.length() {
let mut rewritten_here = false
match instrs[scan_idx] {
@lib.LocalSet(@lib.LocalIdx(target_raw)) => {
let compound_suffix = wide_environment &&
scan_idx > 0 &&
!run_hot_pipeline_raw_simplify_locals_instr_is_pure_movable_value(
instrs[scan_idx - 1],
)
if compound_suffix &&
checked_compound_suffix &&
typed_seed is None &&
wide_environment {
typed_seed = Some(SlStatementTypeSeed::new(env))
}
let producer_suffix = sl_value_suffix_at_with_seed(
instrs,
scan_idx,
1,
env,
typed_seed,
None,
None,
)
checked_compound_suffix = checked_compound_suffix || compound_suffix
let producer_start = scan_idx - producer_suffix.length()
if producer_suffix.length() > 0 &&
producer_start >= cursor &&
run_hot_pipeline_raw_simplify_locals_instrs_all_pure_movable_values(
producer_suffix,
) {
let target_local = target_raw.reinterpret_as_int()
let source_locals = run_hot_pipeline_raw_simplify_locals_instr_source_locals(
producer_suffix,
)
let producer_is_dupable_copy = producer_suffix.length() == 1 &&
run_hot_pipeline_raw_simplify_locals_instr_is_dupable_copy_value(
producer_suffix[0],
)
if wide_environment &&
typed_seed is None &&
scan_idx + 1 < instrs.length() {
// One immutable environment owns the all-initialized local facts.
// Statement results and query stacks remain independently owned.
typed_seed = Some(SlStatementTypeSeed::new(env))
match initialization_slots {
Some(slots) => slots[0] += env.locals.length()
None => ()
}
}
let movable_middle : Array[@lib.Instruction] = []
let sink_idx = sl_collect_pure_suffix_movable_middle(
instrs,
scan_idx + 1,
env,
target_local,
source_locals,
movable_middle,
producer_is_dupable_copy,
initialization_slots~,
typed_seed~,
)
if movable_middle.length() > 0 && !producer_is_dupable_copy {
for idx = cursor; idx < producer_start; idx = idx + 1 {
rewritten.push(instrs[idx])
}
for instr in movable_middle {
rewritten.push(instr)
}
for instr in producer_suffix {
rewritten.push(instr)
}
rewritten.push(instrs[scan_idx])
cursor = sink_idx
scan_idx = sink_idx
rewrite_count = rewrite_count + 1
rewritten_here = true
}
if !rewritten_here &&
movable_middle.length() == 0 &&
producer_suffix.length() == 1 {
match
run_hot_pipeline_raw_simplify_locals_instr_source_local(
producer_suffix[0],
) {
Some(source_local) => {
let statement = sl_statement_at(
instrs,
scan_idx + 1,
env,
typed_seed~,
initialization_slots~,
)
if run_hot_pipeline_raw_simplify_locals_statement_is_safe_copy_if_barrier(
statement, target_local, source_local,
) {
for idx = cursor; idx < producer_start; idx = idx + 1 {
rewritten.push(instrs[idx])
}
for instr in statement {
rewritten.push(instr)
}
for instr in producer_suffix {
rewritten.push(instr)
}
rewritten.push(instrs[scan_idx])
cursor = scan_idx + 1 + statement.length()
scan_idx = cursor
rewrite_count = rewrite_count + 1
rewritten_here = true
}
}
None => ()
}
}
if !rewritten_here && producer_is_dupable_copy {
match
run_hot_pipeline_raw_simplify_locals_try_rewrite_dupable_copy_terminal_value_suffix(
instrs,
target_local,
producer_suffix[0],
source_locals,
env,
start=scan_idx + 1,
) {
Some((rewritten_terminal, consumed_len)) => {
for idx = cursor; idx < producer_start; idx = idx + 1 {
rewritten.push(instrs[idx])
}
for instr in rewritten_terminal {
rewritten.push(instr)
}
cursor = scan_idx + 1 + consumed_len
scan_idx = cursor
rewrite_count = rewrite_count + 1
rewritten_here = true
}
None => ()
}
}
if !rewritten_here && producer_is_dupable_copy {
match
run_hot_pipeline_raw_simplify_locals_try_rewrite_dupable_copy_into_next_if_condition(
instrs,
target_local,
producer_suffix[0],
source_locals,
start=scan_idx + 1,
) {
Some((rewritten_condition, consumed_len)) => {
for idx = cursor; idx < producer_start; idx = idx + 1 {
rewritten.push(instrs[idx])
}
rewritten.push(@lib.Instruction::nop())
for instr in rewritten_condition {
rewritten.push(instr)
}
cursor = scan_idx + 1 + consumed_len
scan_idx = cursor
rewrite_count = rewrite_count + 1
rewritten_here = true
}
None => ()
}
}
if !rewritten_here && producer_is_dupable_copy {
let skipped_middle_statements : Array[Array[@lib.Instruction]] = []
let mut sink_idx = scan_idx + 1
while sink_idx < instrs.length() && !rewritten_here {
let statement0 = sl_statement_at(
instrs,
sink_idx,
env,
typed_seed~,
initialization_slots~,
)
let statement = if statement0.length() > 0 {
statement0
} else {
sl_statement_at(
instrs,
sink_idx,
env,
allow_escape=true,
typed_seed~,
initialization_slots~,
)
}
if statement.length() == 0 {
break
}
let statement_end = sink_idx + statement.length()
let statement_escapes = statement.length() > 0 &&
run_hot_pipeline_raw_simplify_locals_instr_is_unconditional_terminator(
statement[statement.length() - 1],
)
if run_hot_pipeline_raw_instrs_have_later_local_write(
statement, 0, target_local,
) ||
source_locals.any(fn(local_id) {
run_hot_pipeline_raw_instrs_have_later_local_write(
statement, 0, local_id,
)
}) {
break
}
let reads_target = run_hot_pipeline_raw_instrs_have_later_local_read(
statement, 0, target_local,
)
if reads_target {
if statement_escapes ||
!run_hot_pipeline_raw_instrs_have_later_local_read_before_next_local_write(
instrs, statement_end, target_local,
) {
for idx = cursor; idx < producer_start; idx = idx + 1 {
rewritten.push(instrs[idx])
}
rewritten.push(@lib.Instruction::nop())
let rewritten_statement = run_hot_pipeline_raw_simplify_locals_rewrite_local_reads_in_body(
statement,
target_local,
producer_suffix[0],
)
let (rewritten_middle, rewritten_statement, middle_rewrites) = run_hot_pipeline_raw_simplify_locals_rewrite_dupable_copy_middle_statements_into_statement(
skipped_middle_statements, rewritten_statement, instrs, statement_end,
)
for instr in rewritten_middle {
rewritten.push(instr)
}
for instr in rewritten_statement {
rewritten.push(instr)
}
cursor = statement_end
scan_idx = statement_end
rewrite_count = rewrite_count + 1 + middle_rewrites
rewritten_here = true
}
break
}
if statement.any(fn(instr) {
run_hot_pipeline_raw_simplify_locals_instr_is_structured(
instr,
) ||
run_hot_pipeline_raw_simplify_locals_instr_is_early_terminator(
instr,
)
}) {
break
}
skipped_middle_statements.push(statement)
sink_idx = statement_end
}
}
}
}
_ => ()
}
if !rewritten_here {
scan_idx = scan_idx + 1
}
}
// Unchanged input remains borrowed; no tail copy is needed.
if rewrite_count == 0 {
return (instrs, 0)
}
for idx = cursor; idx < instrs.length(); idx = idx + 1 {
rewritten.push(instrs[idx])
}
(rewritten, rewrite_count)
}
///|
fn run_hot_pipeline_raw_simplify_locals_rewrite_pure_suffix_local_sets(
instrs : Array[@lib.Instruction],
env : @validate.Env,
) -> (Array[@lib.Instruction], Int32) {
let mut normalized = instrs
let mut rewrite_count = 0
for index, instr in instrs {
match instr {
@lib.Block(bt, @lib.Expr(body)) => {
let (next, count) = run_hot_pipeline_raw_simplify_locals_rewrite_pure_suffix_local_sets(
body, env,
)
if count > 0 {
if rewrite_count == 0 {
normalized = instrs.copy()
}
normalized[index] = @lib.Instruction::block(bt, @lib.Expr::new(next))
rewrite_count += count
}
}
@lib.Loop(bt, @lib.Expr(body)) => {
let (next, count) = run_hot_pipeline_raw_simplify_locals_rewrite_pure_suffix_local_sets(
body, env,
)
if count > 0 {
if rewrite_count == 0 {
normalized = instrs.copy()
}
normalized[index] = @lib.Instruction::loop_(bt, @lib.Expr::new(next))
rewrite_count += count
}
}
@lib.TryTable(bt, catches, @lib.Expr(body)) => {
let (next, count) = run_hot_pipeline_raw_simplify_locals_rewrite_pure_suffix_local_sets(
body, env,
)
if count > 0 {
if rewrite_count == 0 {
normalized = instrs.copy()
}
normalized[index] = @lib.Instruction::try_table(
bt,
catches,
@lib.Expr::new(next),
)
rewrite_count += count
}
}
@lib.If(bt, then_body, else_body) => {
let (next_then, then_count) = run_hot_pipeline_raw_simplify_locals_rewrite_pure_suffix_local_sets(
then_body, env,
)
let mut count = then_count
let next_else = match else_body {
Some(body) => {
let (next, else_count) = run_hot_pipeline_raw_simplify_locals_rewrite_pure_suffix_local_sets(
body, env,
)
count += else_count
if else_count > 0 {
Some(next)
} else {
else_body
}
}
None => None
}
if count > 0 {
if rewrite_count == 0 {
normalized = instrs.copy()
}
normalized[index] = @lib.Instruction::if_(bt, next_then, next_else)
rewrite_count += count
}
}
_ => ()
}
}
let (rewritten, flat_count) = run_hot_pipeline_raw_simplify_locals_rewrite_pure_suffix_local_sets_flat(
normalized, env,
)
(rewritten, rewrite_count + flat_count)
}
///|
fn run_hot_pipeline_raw_simplify_locals_rewrite_pure_suffix_local_sets_fixpoint(
instrs : Array[@lib.Instruction],
env : @validate.Env,
) -> (Array[@lib.Instruction], Int32) {
let mut current = instrs
let mut total_rewrites = 0
let mut pass_count = 0
while pass_count < 3 &&
run_hot_pipeline_raw_simplify_locals_has_pure_suffix_local_set_candidates_nested(
current,
) {
let (rewritten, rewrite_count) = run_hot_pipeline_raw_simplify_locals_rewrite_pure_suffix_local_sets(
current, env,
)
total_rewrites = total_rewrites + rewrite_count
current = rewritten
if rewrite_count == 0 {
break
}
pass_count = pass_count + 1
}
(current, total_rewrites)
}
///|
fn run_hot_pipeline_raw_simplify_locals_instrs_all_pure_movable_values(
instrs : Array[@lib.Instruction],
) -> Bool {
for instr in instrs {
if !run_hot_pipeline_raw_simplify_locals_instr_is_pure_movable_value(instr) {
return false
}
}
true
}
///|
fn run_hot_pipeline_raw_simplify_locals_pure_prefix_can_append_local_set_barrier(
pure_prefix : Array[@lib.Instruction],
target_local : Int32,
source_locals : Array[Int32],
barrier_local : Int32,
env : @validate.Env,
) -> Bool {
if barrier_local == target_local ||
source_locals.any(fn(local_id) { local_id == barrier_local }) {
return false
}
let (_, barrier_producer) = run_hot_pipeline_raw_simplify_locals_take_value_suffix(
pure_prefix, 1, env,
)
if barrier_producer.length() == 0 {
return false
}
!run_hot_pipeline_raw_simplify_locals_instr_source_locals(barrier_producer).any(fn(
local_id,
) {
local_id == target_local
},
)
}
///|
fn run_hot_pipeline_raw_simplify_locals_rewrite_leading_local_get_in_body(
body : Array[@lib.Instruction],
target_local : Int32,
replacement : Array[@lib.Instruction],
) -> Array[@lib.Instruction]? {
let rewritten : Array[@lib.Instruction] = []
let mut idx = 0
while idx < body.length() {
match body[idx] {
@lib.Nop => {
rewritten.push(body[idx])
idx = idx + 1
}
@lib.LocalGet(@lib.LocalIdx(raw)) =>
// The first read is the only point where the original write may be
// removed. Skipping it and replacing a later read leaves that first
// read observing the old local value.
if raw.reinterpret_as_int() == target_local &&
!run_hot_pipeline_raw_instrs_have_later_local_read(
body,
idx + 1,
target_local,
) &&
!run_hot_pipeline_raw_instrs_have_later_local_write(
body,
idx + 1,
target_local,
) {
for instr in replacement {
rewritten.push(instr)
}
for rest_idx = idx + 1
rest_idx < body.length()
rest_idx = rest_idx + 1 {
rewritten.push(body[rest_idx])
}
return Some(rewritten)
} else if raw.reinterpret_as_int() == target_local {
return None
} else {
rewritten.push(body[idx])
idx = idx + 1
}
instr =>
if run_hot_pipeline_raw_simplify_locals_instr_is_pure_movable_value(
instr,
) {
rewritten.push(instr)
idx = idx + 1
} else {
// A branch out of the nested block can resume in this suffix.
// Check each enclosing suffix before descending to the first read;
// checking only the innermost body loses live reads after br_table.
if run_hot_pipeline_raw_instrs_have_later_local_read(
body,
idx + 1,
target_local,
) ||
run_hot_pipeline_raw_instrs_have_later_local_write(
body,
idx + 1,
target_local,
) {
return None
}
match instr {
@lib.Block(bt, @lib.Expr(nested_body)) =>
match
run_hot_pipeline_raw_simplify_locals_rewrite_leading_local_get_in_body(
nested_body, target_local, replacement,
) {
Some(rewritten_nested) => {
rewritten.push(
@lib.Instruction::block(
bt,
@lib.Expr::new(rewritten_nested),
),
)
for rest_idx = idx + 1
rest_idx < body.length()
rest_idx = rest_idx + 1 {
rewritten.push(body[rest_idx])
}
return Some(rewritten)
}
None => return None
}
@lib.Loop(bt, @lib.Expr(nested_body)) if !run_hot_pipeline_raw_vacuum_body_targets_owner(
nested_body, 0,
) =>
match
run_hot_pipeline_raw_simplify_locals_rewrite_leading_local_get_in_body(
nested_body, target_local, replacement,
) {
Some(rewritten_nested) => {
rewritten.push(
@lib.Instruction::loop_(
bt,
@lib.Expr::new(rewritten_nested),
),
)
for rest_idx = idx + 1
rest_idx < body.length()
rest_idx = rest_idx + 1 {
rewritten.push(body[rest_idx])
}
return Some(rewritten)
}
None => return None
}
@lib.TryTable(bt, catches, @lib.Expr(nested_body)) =>
match
run_hot_pipeline_raw_simplify_locals_rewrite_leading_local_get_in_body(
nested_body, target_local, replacement,
) {
Some(rewritten_nested) => {
rewritten.push(
@lib.Instruction::try_table(
bt,
catches,
@lib.Expr::new(rewritten_nested),
),
)
for rest_idx = idx + 1
rest_idx < body.length()
rest_idx = rest_idx + 1 {
rewritten.push(body[rest_idx])
}
return Some(rewritten)
}
None => return None
}
_ => return None
}
}
}
}
None
}
///|
fn run_hot_pipeline_raw_simplify_locals_rewrite_leading_local_get_in_instr(
instr : @lib.Instruction,
target_local : Int32,
replacement : Array[@lib.Instruction],
) -> @lib.Instruction? {
match instr {
@lib.Block(bt, @lib.Expr(body)) =>
match
run_hot_pipeline_raw_simplify_locals_rewrite_leading_local_get_in_body(
body, target_local, replacement,
) {
Some(rewritten_body) =>
Some(@lib.Instruction::block(bt, @lib.Expr::new(rewritten_body)))
None => None
}
// Moving a producer across a loop backedge changes how often it executes.
// Loops without a backedge are single-entry regions and remain eligible.
@lib.Loop(bt, @lib.Expr(body)) if !run_hot_pipeline_raw_vacuum_body_targets_owner(
body, 0,
) =>
match
run_hot_pipeline_raw_simplify_locals_rewrite_leading_local_get_in_body(
body, target_local, replacement,
) {
Some(rewritten_body) =>
Some(@lib.Instruction::loop_(bt, @lib.Expr::new(rewritten_body)))
None => None
}
@lib.TryTable(bt, catches, @lib.Expr(body)) =>
match
run_hot_pipeline_raw_simplify_locals_rewrite_leading_local_get_in_body(
body, target_local, replacement,
) {
Some(rewritten_body) =>
Some(
@lib.Instruction::try_table(
bt,
catches,
@lib.Expr::new(rewritten_body),
),
)
None => None
}
_ => None
}
}
///|
fn run_hot_pipeline_raw_simplify_locals_instr_is_value_control(
instr : @lib.Instruction,
) -> Bool {
match instr {
@lib.Block(block_type, _) | @lib.Loop(block_type, _) =>
block_type != @lib.BlockType::void_()
@lib.If(block_type, _, _) => block_type != @lib.BlockType::void_()
@lib.TryTable(block_type, _, _) => block_type != @lib.BlockType::void_()
_ => false
}
}
///|
fn run_hot_pipeline_raw_simplify_locals_rewrite_control_carrier_local_gets_flat(
instrs : Array[@lib.Instruction],
) -> (Array[@lib.Instruction], Int32) {
let rewritten : Array[@lib.Instruction] = []
let mut cursor = 0
let mut scan_idx = 0
let mut rewrite_count = 0
while scan_idx + 1 < instrs.length() {
let mut rewritten_here = false
if run_hot_pipeline_raw_simplify_locals_instr_is_value_control(
instrs[scan_idx],
) {
match instrs[scan_idx + 1] {
@lib.LocalSet(@lib.LocalIdx(target_raw)) => {
let target_local = target_raw.reinterpret_as_int()
let mut use_idx = scan_idx + 2
while use_idx < instrs.length() &&
instrs[use_idx] == @lib.Instruction::nop() {
use_idx = use_idx + 1
}
if use_idx < instrs.length() {
match instrs[use_idx] {
@lib.LocalGet(@lib.LocalIdx(read_raw)) if read_raw == target_raw => {
for idx = cursor; idx < scan_idx; idx = idx + 1 {
rewritten.push(instrs[idx])
}
rewritten.push(@lib.Instruction::nop())
for nop_idx = scan_idx + 2
nop_idx < use_idx
nop_idx = nop_idx + 1 {
rewritten.push(instrs[nop_idx])
}
rewritten.push(instrs[scan_idx])
if run_hot_pipeline_raw_instrs_have_later_local_read(
instrs,
use_idx + 1,
target_local,
) {
rewritten.push(
@lib.Instruction::local_tee(@lib.LocalIdx::new(target_raw)),
)
}
cursor = use_idx + 1
scan_idx = cursor
rewrite_count = rewrite_count + 1
rewritten_here = true
}
@lib.Block(_, _) | @lib.Loop(_, _) | @lib.TryTable(_, _, _) =>
if !run_hot_pipeline_raw_instrs_have_later_local_read(
instrs,
use_idx + 1,
target_local,
) &&
!run_hot_pipeline_raw_instrs_have_later_local_write(
instrs,
use_idx + 1,
target_local,
) {
match
run_hot_pipeline_raw_simplify_locals_rewrite_leading_local_get_in_instr(
instrs[use_idx],
target_local,
[instrs[scan_idx]],
) {
Some(rewritten_instr) => {
for idx = cursor; idx < scan_idx; idx = idx + 1 {
rewritten.push(instrs[idx])
}
rewritten.push(@lib.Instruction::nop())
for nop_idx = scan_idx + 2
nop_idx < use_idx
nop_idx = nop_idx + 1 {
rewritten.push(instrs[nop_idx])
}
rewritten.push(rewritten_instr)
cursor = use_idx + 1
scan_idx = cursor
rewrite_count = rewrite_count + 1
rewritten_here = true
}
None => ()
}
}
_ => ()
}
}
}
_ => ()
}
}
if !rewritten_here {
scan_idx = scan_idx + 1
}
}
for idx = cursor; idx < instrs.length(); idx = idx + 1 {
rewritten.push(instrs[idx])
}
(rewritten, rewrite_count)
}
///|
fn run_hot_pipeline_raw_simplify_locals_rewrite_control_carrier_local_gets(
instrs : Array[@lib.Instruction],
) -> (Array[@lib.Instruction], Int32) {
let normalized : Array[@lib.Instruction] = []
let mut rewrite_count = 0
for instr in instrs {
match instr {
@lib.Block(bt, @lib.Expr(body)) => {
let (rewritten_body, nested_rewrites) = run_hot_pipeline_raw_simplify_locals_rewrite_control_carrier_local_gets(
body,
)
normalized.push(
@lib.Instruction::block(bt, @lib.Expr::new(rewritten_body)),
)
rewrite_count = rewrite_count + nested_rewrites
}
@lib.Loop(bt, @lib.Expr(body)) => {
let (rewritten_body, nested_rewrites) = run_hot_pipeline_raw_simplify_locals_rewrite_control_carrier_local_gets(
body,
)
normalized.push(
@lib.Instruction::loop_(bt, @lib.Expr::new(rewritten_body)),
)
rewrite_count = rewrite_count + nested_rewrites
}
@lib.TryTable(bt, catches, @lib.Expr(body)) => {
let (rewritten_body, nested_rewrites) = run_hot_pipeline_raw_simplify_locals_rewrite_control_carrier_local_gets(
body,
)
normalized.push(
@lib.Instruction::try_table(
bt,
catches,
@lib.Expr::new(rewritten_body),
),
)
rewrite_count = rewrite_count + nested_rewrites
}
@lib.If(bt, then_body, else_body) => {
let (rewritten_then, then_rewrites) = run_hot_pipeline_raw_simplify_locals_rewrite_control_carrier_local_gets(
then_body,
)
rewrite_count = rewrite_count + then_rewrites
let rewritten_else = match else_body {
Some(else_body) => {
let (rewritten_else, else_rewrites) = run_hot_pipeline_raw_simplify_locals_rewrite_control_carrier_local_gets(
else_body,
)
rewrite_count = rewrite_count + else_rewrites
Some(rewritten_else)
}
None => None
}
normalized.push(
@lib.Instruction::if_(bt, rewritten_then, rewritten_else),
)
}
_ => normalized.push(instr)
}
}
let (rewritten, flat_rewrites) = run_hot_pipeline_raw_simplify_locals_rewrite_control_carrier_local_gets_flat(
normalized,
)
(rewritten, rewrite_count + flat_rewrites)
}
///|
fn run_hot_pipeline_raw_simplify_locals_rewrite_control_carrier_local_gets_fixpoint(
instrs : Array[@lib.Instruction],
) -> (Array[@lib.Instruction], Int32) {
let mut current = instrs
let mut total_rewrites = 0
let mut pass_count = 0
while pass_count < 3 {
let (rewritten, rewrite_count) = run_hot_pipeline_raw_simplify_locals_rewrite_control_carrier_local_gets(
current,
)
total_rewrites = total_rewrites + rewrite_count
current = rewritten
if rewrite_count == 0 {
break
}
pass_count = pass_count + 1
}
(current, total_rewrites)
}
///|
fn run_hot_pipeline_raw_simplify_locals_rewrite_balanced_statement_local_gets_flat(
instrs : Array[@lib.Instruction],
env : @validate.Env,
) -> (Array[@lib.Instruction], Int32) {
let rewritten : Array[@lib.Instruction] = []
let mut cursor = 0
let mut scan_idx = 0
let mut rewrite_count = 0
// One original body and environment remain immutable throughout this scan.
// Keep tiny and single-query paths free of cached-state allocation.
let mut checked_statement = false
let mut typed_seed : SlStatementTypeSeed? = None
let wide_environment = env.locals.length() >= 128
let mut checked_compound_suffix = false
while scan_idx < instrs.length() {
let mut rewritten_here = false
let target_local = match instrs[scan_idx] {
@lib.LocalSet(@lib.LocalIdx(raw)) => raw.reinterpret_as_int()
_ => {
scan_idx += 1
continue
}
}
let compound_suffix = wide_environment &&
scan_idx > 0 &&
!run_hot_pipeline_raw_simplify_locals_instr_is_pure_movable_value(
instrs[scan_idx - 1],
)
if compound_suffix &&
checked_compound_suffix &&
typed_seed is None &&
wide_environment {
typed_seed = Some(SlStatementTypeSeed::new(env))
}
let producer_suffix = sl_value_suffix_at_with_seed(
instrs,
scan_idx,
1,
env,
typed_seed,
None,
None,
)
checked_compound_suffix = checked_compound_suffix || compound_suffix
let producer_start = scan_idx - producer_suffix.length()
if producer_suffix.length() == 0 || producer_start < cursor {
scan_idx = scan_idx + 1
continue
}
let middle : Array[@lib.Instruction] = []
let mut statement_start = scan_idx + 1
while statement_start < instrs.length() {
let leading_target_get = match instrs[statement_start] {
@lib.LocalGet(@lib.LocalIdx(raw)) =>
raw.reinterpret_as_int() == target_local
_ => false
}
if leading_target_get {
if run_hot_pipeline_raw_instrs_have_later_local_read_before_next_local_write(
instrs,
statement_start + 1,
target_local,
) {
break
}
for idx = cursor; idx < scan_idx; idx = idx + 1 {
rewritten.push(instrs[idx])
}
for instr in middle {
rewritten.push(instr)
}
cursor = statement_start + 1
scan_idx = cursor
rewrite_count = rewrite_count + 1
rewritten_here = true
break
}
if checked_statement && typed_seed is None && wide_environment {
typed_seed = Some(SlStatementTypeSeed::new(env))
}
let statement_end = run_hot_pipeline_raw_simplify_locals_statement_prefix_end(
instrs,
env,
false,
start=statement_start,
typed_seed~,
flat_only=true,
)
checked_statement = true
// The prefix helper enforces the same flat/control boundary. An empty
// rejected statement needs neither storage nor another control scan.
if statement_end == statement_start {
break
}
let statement = Array::makei(statement_end - statement_start, fn(index) {
instrs[statement_start + index]
})
let first_reads_target = match statement[0] {
@lib.LocalGet(@lib.LocalIdx(raw)) =>
raw.reinterpret_as_int() == target_local
_ => false
}
if first_reads_target {
let mut statement_conflict = false
for idx = 1; idx < statement.length(); idx = idx + 1 {
match statement[idx] {
@lib.LocalGet(@lib.LocalIdx(raw))
| @lib.LocalSet(@lib.LocalIdx(raw))
| @lib.LocalTee(@lib.LocalIdx(raw)) =>
if raw.reinterpret_as_int() == target_local {
statement_conflict = true
break
}
_ => ()
}
}
if statement_conflict ||
run_hot_pipeline_raw_instrs_have_later_local_read_before_next_local_write(
instrs, statement_end, target_local,
) {
break
}
for idx = cursor; idx < scan_idx; idx = idx + 1 {
rewritten.push(instrs[idx])
}
for instr in middle {
rewritten.push(instr)
}
for idx = 1; idx < statement.length(); idx = idx + 1 {
rewritten.push(statement[idx])
}
cursor = statement_end
scan_idx = cursor
rewrite_count = rewrite_count + 1
rewritten_here = true
break
}
if run_hot_pipeline_raw_instrs_have_later_local_read(
statement, 0, target_local,
) ||
run_hot_pipeline_raw_instrs_have_later_local_write(
statement, 0, target_local,
) {
break
}
for instr in statement {
middle.push(instr)
}
statement_start = statement_end
}
if !rewritten_here {
scan_idx = scan_idx + 1
}
}
// No mutation occurred: keep the borrowed body instead of copying its tail.
if rewrite_count == 0 {
return (instrs, 0)
}
for idx = cursor; idx < instrs.length(); idx = idx + 1 {
rewritten.push(instrs[idx])
}
(rewritten, rewrite_count)
}
///|
// Borrow unchanged bodies. Copy before the first child replacement; downstream
// raw cleanup treats borrowed input as read-only and constructs changed output.
fn run_hot_pipeline_raw_simplify_locals_rewrite_balanced_statement_local_gets(
instrs : Array[@lib.Instruction],
env : @validate.Env,
) -> (Array[@lib.Instruction], Int32) {
let mut normalized = instrs
let mut rewrite_count = 0
for index, instr in instrs {
match instr {
@lib.Block(bt, @lib.Expr(body)) => {
let (next, count) = run_hot_pipeline_raw_simplify_locals_rewrite_balanced_statement_local_gets(
body, env,
)
if count > 0 {
if rewrite_count == 0 {
normalized = instrs.copy()
}
normalized[index] = @lib.Instruction::block(bt, @lib.Expr::new(next))
rewrite_count += count
}
}
@lib.Loop(bt, @lib.Expr(body)) => {
let (next, count) = run_hot_pipeline_raw_simplify_locals_rewrite_balanced_statement_local_gets(
body, env,
)
if count > 0 {
if rewrite_count == 0 {
normalized = instrs.copy()
}
normalized[index] = @lib.Instruction::loop_(bt, @lib.Expr::new(next))
rewrite_count += count
}
}
@lib.TryTable(bt, catches, @lib.Expr(body)) => {
let (next, count) = run_hot_pipeline_raw_simplify_locals_rewrite_balanced_statement_local_gets(
body, env,
)
if count > 0 {
if rewrite_count == 0 {
normalized = instrs.copy()
}
normalized[index] = @lib.Instruction::try_table(
bt,
catches,
@lib.Expr::new(next),
)
rewrite_count += count
}
}
@lib.If(bt, then_body, else_body) => {
let (next_then, then_count) = run_hot_pipeline_raw_simplify_locals_rewrite_balanced_statement_local_gets(
then_body, env,
)
let mut count = then_count
let next_else = match else_body {
Some(body) => {
let (next, else_count) = run_hot_pipeline_raw_simplify_locals_rewrite_balanced_statement_local_gets(
body, env,
)
count += else_count
if else_count > 0 {
Some(next)
} else {
else_body
}
}
None => None
}
if count > 0 {
if rewrite_count == 0 {
normalized = instrs.copy()
}
normalized[index] = @lib.Instruction::if_(bt, next_then, next_else)
rewrite_count += count
}
}
_ => ()
}
}
let (rewritten, flat_count) = run_hot_pipeline_raw_simplify_locals_rewrite_balanced_statement_local_gets_flat(
normalized, env,
)
(rewritten, rewrite_count + flat_count)
}
///|
fn run_hot_pipeline_raw_simplify_locals_rewrite_balanced_statement_local_gets_fixpoint(
instrs : Array[@lib.Instruction],
env : @validate.Env,
) -> (Array[@lib.Instruction], Int32) {
let mut current = instrs
let mut total_rewrites = 0
for _ in 0..<4 {
let (next, count) = run_hot_pipeline_raw_simplify_locals_rewrite_balanced_statement_local_gets(
current, env,
)
current = next
total_rewrites += count
if count == 0 {
break
}
}
(current, total_rewrites)
}
///|
fn run_hot_pipeline_raw_simplify_locals_rewrite_effectful_suffix_local_gets_flat(
instrs : Array[@lib.Instruction],
env : @validate.Env,
) -> (Array[@lib.Instruction], Int32) {
let rewritten : Array[@lib.Instruction] = []
let mut cursor = 0
let mut scan_idx = 0
let mut rewrite_count = 0
let mut typed_seed : SlStatementTypeSeed? = None
let wide_environment = env.locals.length() >= 128
let mut checked_compound_suffix = false
while scan_idx < instrs.length() {
let mut rewritten_here = false
match instrs[scan_idx] {
@lib.LocalSet(@lib.LocalIdx(target_raw)) => {
let compound_suffix = wide_environment &&
scan_idx > 0 &&
!run_hot_pipeline_raw_simplify_locals_instr_is_pure_movable_value(
instrs[scan_idx - 1],
)
if compound_suffix &&
checked_compound_suffix &&
typed_seed is None &&
wide_environment {
typed_seed = Some(SlStatementTypeSeed::new(env))
}
let producer_suffix = sl_value_suffix_at_with_seed(
instrs,
scan_idx,
1,
env,
typed_seed,
None,
None,
)
checked_compound_suffix = checked_compound_suffix || compound_suffix
let producer_start = scan_idx - producer_suffix.length()
if producer_suffix.length() > 0 &&
producer_start >= cursor &&
!run_hot_pipeline_raw_simplify_locals_instrs_all_pure_movable_values(
producer_suffix,
) {
let target_local = target_raw.reinterpret_as_int()
let source_locals = run_hot_pipeline_raw_simplify_locals_instr_source_locals(
producer_suffix,
)
let written_locals = run_hot_pipeline_raw_simplify_locals_instr_written_locals(
producer_suffix,
)
let pure_prefix : Array[@lib.Instruction] = []
let mut use_idx = scan_idx + 1
while use_idx < instrs.length() {
match instrs[use_idx] {
@lib.LocalGet(@lib.LocalIdx(read_raw)) =>
if read_raw.reinterpret_as_int() == target_local {
let has_later_read = run_hot_pipeline_raw_instrs_have_later_local_read(
instrs,
use_idx + 1,
target_local,
)
for idx = cursor; idx < producer_start; idx = idx + 1 {
rewritten.push(instrs[idx])
}
rewritten.push(@lib.Instruction::nop())
for instr in pure_prefix {
rewritten.push(instr)
}
for instr in producer_suffix {
rewritten.push(instr)
}
if has_later_read {
rewritten.push(
@lib.Instruction::local_tee(
@lib.LocalIdx::new(target_raw),
),
)
}
cursor = use_idx + 1
scan_idx = cursor
rewrite_count = rewrite_count + 1
rewritten_here = true
break
} else if written_locals.any(fn(local_id) {
local_id == read_raw.reinterpret_as_int()
}) {
break
} else {
pure_prefix.push(instrs[use_idx])
}
@lib.Nop => pure_prefix.push(instrs[use_idx])
@lib.Block(_, @lib.Expr(_))
| @lib.Loop(_, @lib.Expr(_))
| @lib.TryTable(_, _, @lib.Expr(_)) =>
if pure_prefix.all(fn(instr) {
match instr {
@lib.Nop => true
_ => false
}
}) &&
!run_hot_pipeline_raw_instrs_have_later_local_read(
instrs,
use_idx + 1,
target_local,
) &&
!run_hot_pipeline_raw_instrs_have_later_local_write(
instrs,
use_idx + 1,
target_local,
) {
match
run_hot_pipeline_raw_simplify_locals_rewrite_leading_local_get_in_instr(
instrs[use_idx],
target_local,
producer_suffix,
) {
Some(rewritten_instr) => {
for idx = cursor; idx < producer_start; idx = idx + 1 {
rewritten.push(instrs[idx])
}
rewritten.push(@lib.Instruction::nop())
for instr in pure_prefix {
rewritten.push(instr)
}
rewritten.push(rewritten_instr)
cursor = use_idx + 1
scan_idx = cursor
rewrite_count = rewrite_count + 1
rewritten_here = true
break
}
None => break
}
} else {
break
}
@lib.LocalSet(@lib.LocalIdx(barrier_raw)) =>
if !written_locals.any(fn(local_id) {
local_id == barrier_raw.reinterpret_as_int()
}) &&
run_hot_pipeline_raw_simplify_locals_pure_prefix_can_append_local_set_barrier(
pure_prefix,
target_local,
source_locals,
barrier_raw.reinterpret_as_int(),
env,
) {
pure_prefix.push(instrs[use_idx])
} else {
break
}
instr =>
if run_hot_pipeline_raw_simplify_locals_instr_is_pure_movable_value(
instr,
) ||
sl_effectful_prefix_can_append_i64_extension(
pure_prefix, instr, env,
) {
pure_prefix.push(instr)
} else {
break
}
}
if rewritten_here {
break
}
use_idx = use_idx + 1
}
}
}
_ => ()
}
if !rewritten_here {
scan_idx = scan_idx + 1
}
}
// Unchanged input remains borrowed; no tail copy is needed.
if rewrite_count == 0 {
return (instrs, 0)
}
for idx = cursor; idx < instrs.length(); idx = idx + 1 {
rewritten.push(instrs[idx])
}
(rewritten, rewrite_count)
}
///|
fn run_hot_pipeline_raw_simplify_locals_rewrite_effectful_suffix_local_gets(
instrs : Array[@lib.Instruction],
env : @validate.Env,
) -> (Array[@lib.Instruction], Int32) {
let mut normalized = instrs
let mut rewrite_count = 0
for index, instr in instrs {
match instr {
@lib.Block(bt, @lib.Expr(body)) => {
let (next, count) = run_hot_pipeline_raw_simplify_locals_rewrite_effectful_suffix_local_gets(
body, env,
)
if count > 0 {
if rewrite_count == 0 {
normalized = instrs.copy()
}
normalized[index] = @lib.Instruction::block(bt, @lib.Expr::new(next))
rewrite_count += count
}
}
@lib.Loop(bt, @lib.Expr(body)) => {
let (next, count) = run_hot_pipeline_raw_simplify_locals_rewrite_effectful_suffix_local_gets(
body, env,
)
if count > 0 {
if rewrite_count == 0 {
normalized = instrs.copy()
}
normalized[index] = @lib.Instruction::loop_(bt, @lib.Expr::new(next))
rewrite_count += count
}
}
@lib.TryTable(bt, catches, @lib.Expr(body)) => {
let (next, count) = run_hot_pipeline_raw_simplify_locals_rewrite_effectful_suffix_local_gets(
body, env,
)
if count > 0 {
if rewrite_count == 0 {
normalized = instrs.copy()
}
normalized[index] = @lib.Instruction::try_table(
bt,
catches,
@lib.Expr::new(next),
)
rewrite_count += count
}
}
@lib.If(bt, then_body, else_body) => {
let (next_then, then_count) = run_hot_pipeline_raw_simplify_locals_rewrite_effectful_suffix_local_gets(
then_body, env,
)
let mut count = then_count
let next_else = match else_body {
Some(body) => {
let (next, else_count) = run_hot_pipeline_raw_simplify_locals_rewrite_effectful_suffix_local_gets(
body, env,
)
count += else_count
if else_count > 0 {
Some(next)
} else {
else_body
}
}
None => None
}
if count > 0 {
if rewrite_count == 0 {
normalized = instrs.copy()
}
normalized[index] = @lib.Instruction::if_(bt, next_then, next_else)
rewrite_count += count
}
}
_ => ()
}
}
let (rewritten, flat_count) = run_hot_pipeline_raw_simplify_locals_rewrite_effectful_suffix_local_gets_flat(
normalized, env,
)
(rewritten, rewrite_count + flat_count)
}
///|
fn run_hot_pipeline_raw_simplify_locals_rewrite_effectful_suffix_local_gets_fixpoint(
instrs : Array[@lib.Instruction],
env : @validate.Env,
) -> (Array[@lib.Instruction], Int32) {
let mut current = instrs
let mut total_rewrites = 0
let mut pass_count = 0
while pass_count < 3 {
let (rewritten, rewrite_count) = run_hot_pipeline_raw_simplify_locals_rewrite_effectful_suffix_local_gets(
current, env,
)
total_rewrites = total_rewrites + rewrite_count
current = rewritten
if rewrite_count == 0 {
break
}
pass_count = pass_count + 1
}
(current, total_rewrites)
}
///|
fn run_hot_pipeline_raw_simplify_locals_rewrite_adjacent_local_tees(
instrs : Array[@lib.Instruction],
) -> (Array[@lib.Instruction], Int32) {
let normalized : Array[@lib.Instruction] = []
let mut rewrite_count = 0
for instr in instrs {
match instr {
@lib.Block(bt, @lib.Expr(body)) => {
let (rewritten_body, nested_rewrites) = run_hot_pipeline_raw_simplify_locals_rewrite_adjacent_local_tees(
body,
)
normalized.push(
@lib.Instruction::block(bt, @lib.Expr::new(rewritten_body)),
)
rewrite_count = rewrite_count + nested_rewrites
}
@lib.Loop(bt, @lib.Expr(body)) => {
let (rewritten_body, nested_rewrites) = run_hot_pipeline_raw_simplify_locals_rewrite_adjacent_local_tees(
body,
)
normalized.push(
@lib.Instruction::loop_(bt, @lib.Expr::new(rewritten_body)),
)
rewrite_count = rewrite_count + nested_rewrites
}
@lib.TryTable(bt, catches, @lib.Expr(body)) => {
let (rewritten_body, nested_rewrites) = run_hot_pipeline_raw_simplify_locals_rewrite_adjacent_local_tees(
body,
)
normalized.push(
@lib.Instruction::try_table(
bt,
catches,
@lib.Expr::new(rewritten_body),
),
)
rewrite_count = rewrite_count + nested_rewrites
}
@lib.If(bt, then_body, else_body) => {
let (rewritten_then, then_rewrites) = run_hot_pipeline_raw_simplify_locals_rewrite_adjacent_local_tees(
then_body,
)
rewrite_count = rewrite_count + then_rewrites
let rewritten_else = match else_body {
Some(else_body) => {
let (rewritten_else, else_rewrites) = run_hot_pipeline_raw_simplify_locals_rewrite_adjacent_local_tees(
else_body,
)
rewrite_count = rewrite_count + else_rewrites
Some(rewritten_else)
}
None => None
}
normalized.push(
@lib.Instruction::if_(bt, rewritten_then, rewritten_else),
)
}
_ => normalized.push(instr)
}
}
let rewritten : Array[@lib.Instruction] = []
let mut idx = 0
while idx < normalized.length() {
if idx + 1 < normalized.length() {
match (normalized[idx], normalized[idx + 1]) {
(
@lib.LocalSet(@lib.LocalIdx(raw)),
@lib.LocalGet(@lib.LocalIdx(next_raw)),
) =>
if raw == next_raw {
if run_hot_pipeline_raw_instrs_have_later_local_read(
normalized,
idx + 2,
raw.reinterpret_as_int(),
) {
rewritten.push(
@lib.Instruction::local_tee(@lib.LocalIdx::new(raw)),
)
}
rewrite_count = rewrite_count + 1
idx = idx + 2
} else {
rewritten.push(normalized[idx])
idx = idx + 1
}
_ => {
rewritten.push(normalized[idx])
idx = idx + 1
}
}
} else {
rewritten.push(normalized[idx])
idx = idx + 1
}
}
(rewritten, rewrite_count)
}
///|
fn run_hot_pipeline_raw_simplify_locals_match_lane_builder_block(
instrs : Array[@lib.Instruction],
start : Int32,
) -> (
@lib.LocalIdx,
@lib.I32,
@lib.LocalIdx,
@lib.Instruction,
@lib.Instruction,
@lib.MemArg,
@lib.LocalIdx,
)? {
if start + 15 >= instrs.length() {
return None
}
match
(
instrs[start],
instrs[start + 1],
instrs[start + 2],
instrs[start + 3],
instrs[start + 4],
instrs[start + 5],
instrs[start + 6],
instrs[start + 7],
instrs[start + 8],
instrs[start + 9],
instrs[start + 10],
instrs[start + 11],
instrs[start + 12],
instrs[start + 13],
instrs[start + 14],
instrs[start + 15],
) {
(
@lib.LocalGet(@lib.LocalIdx(source_raw)),
@lib.I32Const(lane_value),
@lib.LocalSet(@lib.LocalIdx(idx_raw)),
@lib.LocalSet(@lib.LocalIdx(arr_raw)),
@lib.LocalGet(@lib.LocalIdx(check_idx_raw)),
@lib.I32Const(@lib.I32(lower_bound)),
@lib.LocalGet(@lib.LocalIdx(check_arr_raw)),
array_length_call,
@lib.I32Const(@lib.I32(upper_delta)),
@lib.I32Sub,
check_range_call,
@lib.LocalGet(@lib.LocalIdx(load_arr_raw)),
@lib.LocalGet(@lib.LocalIdx(load_idx_raw)),
@lib.I32Add,
@lib.I32Load8U(load_memarg),
@lib.LocalSet(@lib.LocalIdx(tmp_raw)),
) =>
if idx_raw == check_idx_raw &&
idx_raw == load_idx_raw &&
arr_raw == check_arr_raw &&
arr_raw == load_arr_raw &&
lower_bound == 0 &&
upper_delta == 1 &&
source_raw != idx_raw &&
source_raw != arr_raw &&
run_hot_pipeline_raw_simplify_locals_instr_is_call_like(
array_length_call,
) &&
run_hot_pipeline_raw_simplify_locals_instr_is_call_like(
check_range_call,
) &&
!run_hot_pipeline_raw_instrs_have_later_local_read(
instrs,
start + 16,
arr_raw.reinterpret_as_int(),
) {
Some(
(
@lib.LocalIdx::new(source_raw),
lane_value,
@lib.LocalIdx::new(idx_raw),
array_length_call,
check_range_call,
load_memarg,
@lib.LocalIdx::new(tmp_raw),
),
)
} else {
None
}
_ => None
}
}
///|
fn run_hot_pipeline_raw_simplify_locals_rewrite_straight_line_lane_builder(
instrs : Array[@lib.Instruction],
) -> (Array[@lib.Instruction], Int32) {
let rewritten : Array[@lib.Instruction] = []
let mut idx = 0
let mut rewrite_count = 0
while idx < instrs.length() {
let mut rewritten_here = false
match
run_hot_pipeline_raw_simplify_locals_match_lane_builder_block(instrs, idx) {
Some(
(
source_local,
lane_value,
idx_local,
array_length_call,
check_range_call,
load_memarg,
tmp_local,
)
) => {
rewritten.push(@lib.Instruction::i32_const(lane_value))
rewritten.push(@lib.Instruction::local_tee(idx_local))
rewritten.push(@lib.Instruction::i32_const(0))
rewritten.push(@lib.Instruction::local_get(source_local))
rewritten.push(array_length_call)
rewritten.push(@lib.Instruction::i32_const(1))
rewritten.push(@lib.Instruction::i32_sub())
rewritten.push(check_range_call)
rewritten.push(@lib.Instruction::local_get(source_local))
rewritten.push(@lib.Instruction::local_get(idx_local))
rewritten.push(@lib.Instruction::i32_add())
rewritten.push(@lib.Instruction::i32_load8u(load_memarg))
rewritten.push(@lib.Instruction::local_set(tmp_local))
rewrite_count = rewrite_count + 1
idx = idx + 16
rewritten_here = true
}
None => ()
}
if !rewritten_here {
rewritten.push(instrs[idx])
idx = idx + 1
}
}
(rewritten, rewrite_count)
}
///|
fn run_hot_pipeline_raw_simplify_locals_try_rewrite_tail(
instrs : Array[@lib.Instruction],
env : @validate.Env,
) -> (Array[@lib.Instruction], Int32) {
match run_hot_pipeline_raw_simplify_locals_match_tail_local_get(instrs) {
Some((set_idx, status, tail_has_return)) => {
let producer_instrs : Array[@lib.Instruction] = []
for idx = 0; idx < set_idx; idx = idx + 1 {
producer_instrs.push(instrs[idx])
}
let (head, suffix) = run_hot_pipeline_raw_simplify_locals_take_value_suffix(
producer_instrs, 1, env,
)
if suffix.length() == 0 {
return (instrs, 0)
}
let rewritten : Array[@lib.Instruction] = []
for instr in head {
rewritten.push(instr)
}
rewritten.push(@lib.Instruction::i32_const(status))
for instr in suffix {
rewritten.push(instr)
}
if tail_has_return {
rewritten.push(@lib.Instruction::return_())
}
(rewritten, 1)
}
None => (instrs, 0)
}
}
///|
fn run_hot_pipeline_raw_simplify_locals_remove_unreachable_dead_stack_sets(
instrs : Array[@lib.Instruction],
) -> (Array[@lib.Instruction], Int32) {
let rewritten : Array[@lib.Instruction] = []
let mut rewrite_count = 0
let mut previous_unconditional_terminator = false
for idx = 0; idx < instrs.length(); idx = idx + 1 {
match instrs[idx] {
@lib.Block(bt, @lib.Expr(body)) => {
let (rewritten_body, nested_rewrites) = run_hot_pipeline_raw_simplify_locals_remove_unreachable_dead_stack_sets(
body,
)
rewritten.push(
@lib.Instruction::block(bt, @lib.Expr::new(rewritten_body)),
)
rewrite_count = rewrite_count + nested_rewrites
}
@lib.Loop(bt, @lib.Expr(body)) => {
let (rewritten_body, nested_rewrites) = run_hot_pipeline_raw_simplify_locals_remove_unreachable_dead_stack_sets(
body,
)
rewritten.push(
@lib.Instruction::loop_(bt, @lib.Expr::new(rewritten_body)),
)
rewrite_count = rewrite_count + nested_rewrites
}
@lib.TryTable(bt, catches, @lib.Expr(body)) => {
let (rewritten_body, nested_rewrites) = run_hot_pipeline_raw_simplify_locals_remove_unreachable_dead_stack_sets(
body,
)
rewritten.push(
@lib.Instruction::try_table(
bt,
catches,
@lib.Expr::new(rewritten_body),
),
)
rewrite_count = rewrite_count + nested_rewrites
}
@lib.If(bt, then_body, else_body) => {
let (rewritten_then, then_rewrites) = run_hot_pipeline_raw_simplify_locals_remove_unreachable_dead_stack_sets(
then_body,
)
let rewritten_else = match else_body {
Some(else_body) => {
let (rewritten_else, else_rewrites) = run_hot_pipeline_raw_simplify_locals_remove_unreachable_dead_stack_sets(
else_body,
)
rewrite_count = rewrite_count + else_rewrites
Some(rewritten_else)
}
None => None
}
rewritten.push(
@lib.Instruction::if_(bt, rewritten_then, rewritten_else),
)
rewrite_count = rewrite_count + then_rewrites
}
@lib.LocalSet(@lib.LocalIdx(raw)) if previous_unconditional_terminator => {
let local_id = raw.reinterpret_as_int()
if !run_hot_pipeline_raw_instrs_have_later_local_read(
instrs,
idx + 1,
local_id,
) {
rewritten.push(@lib.Instruction::nop())
rewrite_count = rewrite_count + 1
} else {
rewritten.push(instrs[idx])
}
}
_ => rewritten.push(instrs[idx])
}
match instrs[idx] {
@lib.Nop => ()
_ =>
previous_unconditional_terminator = run_hot_pipeline_raw_simplify_locals_instr_is_unconditional_terminator(
instrs[idx],
) ||
previous_unconditional_terminator
}
}
(rewritten, rewrite_count)
}
///|
fn run_hot_pipeline_raw_instrs_end_in_unconditional_terminator(
instrs : Array[@lib.Instruction],
) -> Bool {
let mut idx = instrs.length() - 1
while idx >= 0 {
match instrs[idx] {
@lib.Nop => ()
instr =>
return run_hot_pipeline_raw_simplify_locals_instr_is_unconditional_terminator(
instr,
)
}
idx = idx - 1
}
false
}
///|
fn run_hot_pipeline_raw_block_type_has_result(bt : @lib.BlockType) -> Bool {
match bt {
@lib.VoidBlockType => false
_ => true
}
}
///|
fn run_hot_pipeline_raw_next_non_nop_idx(
instrs : Array[@lib.Instruction],
after_idx : Int32,
) -> Int32? {
let mut idx = after_idx + 1
while idx < instrs.length() {
match instrs[idx] {
@lib.Nop => ()
_ => return Some(idx)
}
idx = idx + 1
}
None
}
///|
fn run_hot_pipeline_raw_simplify_locals_flatten_no_normal_result_blocks(
instrs : Array[@lib.Instruction],
) -> (Array[@lib.Instruction], Int32) {
let rewritten : Array[@lib.Instruction] = []
let mut rewrite_count = 0
for idx = 0; idx < instrs.length(); idx = idx + 1 {
match instrs[idx] {
@lib.Block(bt, @lib.Expr(body)) => {
let (rewritten_body, nested_rewrites) = run_hot_pipeline_raw_simplify_locals_flatten_no_normal_result_blocks(
body,
)
rewrite_count = rewrite_count + nested_rewrites
let next_is_nonfallthrough = match
run_hot_pipeline_raw_next_non_nop_idx(instrs, idx) {
Some(next_idx) =>
run_hot_pipeline_raw_simplify_locals_instr_is_unconditional_terminator(
instrs[next_idx],
)
None => false
}
if run_hot_pipeline_raw_block_type_has_result(bt) &&
!run_hot_pipeline_raw_instrs_have_branch(rewritten_body) &&
run_hot_pipeline_raw_instrs_end_in_unconditional_terminator(
rewritten_body,
) &&
next_is_nonfallthrough {
for body_instr in rewritten_body {
rewritten.push(body_instr)
}
rewrite_count = rewrite_count + 1
} else {
rewritten.push(
@lib.Instruction::block(bt, @lib.Expr::new(rewritten_body)),
)
}
}
@lib.Loop(bt, @lib.Expr(body)) => {
let (rewritten_body, nested_rewrites) = run_hot_pipeline_raw_simplify_locals_flatten_no_normal_result_blocks(
body,
)
rewrite_count = rewrite_count + nested_rewrites
rewritten.push(
@lib.Instruction::loop_(bt, @lib.Expr::new(rewritten_body)),
)
}
@lib.TryTable(bt, catches, @lib.Expr(body)) => {
let (rewritten_body, nested_rewrites) = run_hot_pipeline_raw_simplify_locals_flatten_no_normal_result_blocks(
body,
)
rewrite_count = rewrite_count + nested_rewrites
rewritten.push(
@lib.Instruction::try_table(
bt,
catches,
@lib.Expr::new(rewritten_body),
),
)
}
@lib.If(bt, then_body, else_body) => {
let (rewritten_then, then_rewrites) = run_hot_pipeline_raw_simplify_locals_flatten_no_normal_result_blocks(
then_body,
)
rewrite_count = rewrite_count + then_rewrites
let rewritten_else = match else_body {
Some(else_body) => {
let (rewritten_else, else_rewrites) = run_hot_pipeline_raw_simplify_locals_flatten_no_normal_result_blocks(
else_body,
)
rewrite_count = rewrite_count + else_rewrites
Some(rewritten_else)
}
None => None
}
rewritten.push(
@lib.Instruction::if_(bt, rewritten_then, rewritten_else),
)
}
instr => rewritten.push(instr)
}
}
(rewritten, rewrite_count)
}
///|
fn run_hot_pipeline_raw_simplify_locals_remove_unread_local_tees(
instrs : Array[@lib.Instruction],
local_gets : Array[Int32],
) -> (Array[@lib.Instruction], Int32) {
let mut rewritten : Array[@lib.Instruction]? = None
let mut rewrite_count = 0
let mut cursor = 0
for instr in instrs {
let mut next_instr = instr
let mut removed = false
let mut changes = 0
match instr {
@lib.Block(bt, @lib.Expr(body)) => {
let (next_body, nested) = run_hot_pipeline_raw_simplify_locals_remove_unread_local_tees(
body, local_gets,
)
if nested > 0 {
next_instr = @lib.Instruction::block(bt, @lib.Expr::new(next_body))
changes = nested
}
}
@lib.Loop(bt, @lib.Expr(body)) => {
let (next_body, nested) = run_hot_pipeline_raw_simplify_locals_remove_unread_local_tees(
body, local_gets,
)
if nested > 0 {
next_instr = @lib.Instruction::loop_(bt, @lib.Expr::new(next_body))
changes = nested
}
}
@lib.TryTable(bt, catches, @lib.Expr(body)) => {
let (next_body, nested) = run_hot_pipeline_raw_simplify_locals_remove_unread_local_tees(
body, local_gets,
)
if nested > 0 {
next_instr = @lib.Instruction::try_table(
bt,
catches,
@lib.Expr::new(next_body),
)
changes = nested
}
}
@lib.If(bt, then_body, else_body) => {
let (next_then, then_changes) = run_hot_pipeline_raw_simplify_locals_remove_unread_local_tees(
then_body, local_gets,
)
changes = then_changes
let mut next_else = else_body
match else_body {
Some(body) => {
let (next_body, nested) = run_hot_pipeline_raw_simplify_locals_remove_unread_local_tees(
body, local_gets,
)
if nested > 0 {
next_else = Some(next_body)
changes += nested
}
}
None => ()
}
if changes > 0 {
next_instr = @lib.Instruction::if_(bt, next_then, next_else)
}
}
@lib.LocalTee(@lib.LocalIdx(raw)) => {
let local_id = raw.reinterpret_as_int()
if local_id >= 0 &&
local_id < local_gets.length() &&
local_gets[local_id] == 0 {
removed = true
changes = 1
}
}
_ => ()
}
// Allocate each changed region once. Copy its prefix once, then append the
// remainder; unchanged regions and their existing immutable nodes are shared.
if changes > 0 && rewritten is None {
let prefix : Array[@lib.Instruction] = []
for idx = 0; idx < cursor; idx = idx + 1 {
prefix.push(instrs[idx])
}
rewritten = Some(prefix)
}
match rewritten {
Some(out) => if !removed { out.push(next_instr) }
None => ()
}
rewrite_count += changes
cursor += 1
}
(rewritten.unwrap_or(instrs), rewrite_count)
}
///|
fn run_hot_pipeline_raw_simplify_locals_rewrite_structured_copy_call_tails_bounded(
instrs : Array[@lib.Instruction],
env : @validate.Env,
total_local_count : Int32,
) -> (Array[@lib.Instruction], Int32) {
let local_gets : Array[Int32] = Array::make(total_local_count, 0)
let local_sets : Array[Int32] = Array::make(total_local_count, 0)
let local_tees : Array[Int32] = Array::make(total_local_count, 0)
run_hot_pipeline_raw_collect_local_ref_counts(
instrs, local_gets, local_sets, local_tees,
)
let (dead_tee_instrs, dead_tee_rewrites) = run_hot_pipeline_raw_simplify_locals_remove_unread_local_tees(
instrs, local_gets,
)
let (dead_set_instrs, dead_set_rewrites) = run_hot_pipeline_raw_simplify_locals_remove_unreachable_dead_stack_sets(
dead_tee_instrs,
)
let (call_tail_instrs, call_tail_rewrites) = run_hot_pipeline_raw_simplify_locals_rewrite_pure_call_tails_nested_fixpoint(
dead_set_instrs,
)
let (pure_suffix_instrs, pure_suffix_rewrites) = run_hot_pipeline_raw_simplify_locals_rewrite_pure_suffix_local_sets_fixpoint(
call_tail_instrs, env,
)
let (carrier_instrs, carrier_rewrites) = run_hot_pipeline_raw_simplify_locals_rewrite_control_carrier_local_gets_fixpoint(
pure_suffix_instrs,
)
let (rewritten_instrs, effectful_rewrites) = run_hot_pipeline_raw_simplify_locals_rewrite_effectful_suffix_local_gets_fixpoint(
carrier_instrs, env,
)
(
rewritten_instrs,
dead_tee_rewrites +
dead_set_rewrites +
call_tail_rewrites +
pure_suffix_rewrites +
carrier_rewrites +
effectful_rewrites,
)
}
///|
fn run_hot_pipeline_raw_simplify_locals_rewrite_instrs(
instrs : Array[@lib.Instruction],
env : @validate.Env,
) -> (Array[@lib.Instruction], Int32) {
let normalized : Array[@lib.Instruction] = []
let mut rewrite_count = 0
for instr in instrs {
match instr {
@lib.Block(bt, @lib.Expr(body)) => {
let (rewritten_body, nested_rewrites) = run_hot_pipeline_raw_simplify_locals_rewrite_instrs(
body, env,
)
normalized.push(
@lib.Instruction::block(bt, @lib.Expr::new(rewritten_body)),
)
rewrite_count = rewrite_count + nested_rewrites
}
@lib.Loop(bt, @lib.Expr(body)) => {
let (rewritten_body, nested_rewrites) = run_hot_pipeline_raw_simplify_locals_rewrite_instrs(
body, env,
)
normalized.push(
@lib.Instruction::loop_(bt, @lib.Expr::new(rewritten_body)),
)
rewrite_count = rewrite_count + nested_rewrites
}
@lib.TryTable(bt, catches, @lib.Expr(body)) => {
let (rewritten_body, nested_rewrites) = run_hot_pipeline_raw_simplify_locals_rewrite_instrs(
body, env,
)
normalized.push(
@lib.Instruction::try_table(
bt,
catches,
@lib.Expr::new(rewritten_body),
),
)
rewrite_count = rewrite_count + nested_rewrites
}
@lib.If(bt, then_body, else_body) => {
let (rewritten_then, then_rewrites) = run_hot_pipeline_raw_simplify_locals_rewrite_instrs(
then_body, env,
)
rewrite_count = rewrite_count + then_rewrites
let rewritten_else = match else_body {
Some(else_body) => {
let (rewritten_else, else_rewrites) = run_hot_pipeline_raw_simplify_locals_rewrite_instrs(
else_body, env,
)
rewrite_count = rewrite_count + else_rewrites
Some(rewritten_else)
}
None => None
}
normalized.push(
@lib.Instruction::if_(bt, rewritten_then, rewritten_else),
)
}
_ => normalized.push(instr)
}
}
let (unreachable_dead_set_rewritten, unreachable_dead_set_rewrites) = run_hot_pipeline_raw_simplify_locals_remove_unreachable_dead_stack_sets(
normalized,
)
let (pure_call_tail_rewritten, pure_call_tail_rewrites) = run_hot_pipeline_raw_simplify_locals_rewrite_pure_call_tails(
unreachable_dead_set_rewritten,
)
let (pure_suffix_rewritten, pure_suffix_rewrites) = run_hot_pipeline_raw_simplify_locals_rewrite_pure_suffix_local_sets_fixpoint(
pure_call_tail_rewritten, env,
)
let (control_carrier_rewritten, control_carrier_rewrites) = run_hot_pipeline_raw_simplify_locals_rewrite_control_carrier_local_gets_fixpoint(
pure_suffix_rewritten,
)
let (effectful_suffix_rewritten, effectful_suffix_rewrites) = run_hot_pipeline_raw_simplify_locals_rewrite_effectful_suffix_local_gets_fixpoint(
control_carrier_rewritten, env,
)
let (rewritten, tail_rewrites) = run_hot_pipeline_raw_simplify_locals_try_rewrite_tail(
effectful_suffix_rewritten, env,
)
(
rewritten,
rewrite_count +
unreachable_dead_set_rewrites +
pure_call_tail_rewrites +
pure_suffix_rewrites +
control_carrier_rewrites +
effectful_suffix_rewrites +
tail_rewrites,
)
}
///|
fn run_hot_pipeline_raw_simplify_locals_rewrite_skipped_effectful_carriers(
mod_ : @lib.Module,
func_idx : @lib.FuncIdx,
module_state : HotPipelineModuleState,
descriptor : @ir.HotPassDescriptor,
options : HotPipelineOptions,
locals : @lib.Locals,
instrs : Array[@lib.Instruction],
reason : String,
effectful_only~ : Bool = false,
) -> HotPipelineFuncResult? {
if descriptor.name != "simplify-locals" {
return None
}
let module_ctx = run_hot_pipeline_require_module_ctx(
mod_, module_state, options,
)
let env = run_hot_pipeline_raw_simplify_locals_func_env(
mod_,
func_idx,
module_ctx,
locals,
module_env=Some(
run_hot_pipeline_require_validation_module_env(
mod_, module_state, options,
),
),
)
let (rewritten_instrs, rewrite_count) = if effectful_only {
let (constant_instrs, constant_count) = sl_sink_constant_stores(instrs)
let (rewritten, effectful_count) = run_hot_pipeline_raw_simplify_locals_rewrite_effectful_suffix_local_gets_fixpoint(
constant_instrs, env,
)
(rewritten, constant_count + effectful_count)
} else {
let (balanced_instrs, balanced_rewrites) = run_hot_pipeline_raw_simplify_locals_rewrite_balanced_statement_local_gets_fixpoint(
instrs, env,
)
let (pure_suffix_instrs, pure_suffix_rewrites) = run_hot_pipeline_raw_simplify_locals_rewrite_pure_suffix_local_sets_fixpoint(
balanced_instrs, env,
)
let (carrier_instrs, carrier_rewrites) = run_hot_pipeline_raw_simplify_locals_rewrite_control_carrier_local_gets_fixpoint(
pure_suffix_instrs,
)
let (rewritten, effectful_rewrites) = run_hot_pipeline_raw_simplify_locals_rewrite_effectful_suffix_local_gets_fixpoint(
carrier_instrs, env,
)
(
rewritten,
balanced_rewrites +
pure_suffix_rewrites +
carrier_rewrites +
effectful_rewrites,
)
}
if rewrite_count == 0 {
return None
}
let (cleaned, _) = run_hot_pipeline_simplify_locals_cleanup_exact_func(
@lib.Func::new(locals, @lib.Expr::new(rewritten_instrs)),
)
let cleaned = if effectful_only {
let @lib.Func(cleaned_locals, @lib.Expr(body)) = cleaned
let next = sl_inline_root_constants(body)
if physical_equal(next, body) {
cleaned
} else {
@lib.Func::new(cleaned_locals, @lib.Expr::new(next))
}
} else {
cleaned
}
Some({
reason,
func: cleaned,
unchanged_original: false,
trace_skip_raw: true,
})
}
///|
fn run_hot_pipeline_raw_simplify_locals_match_i32_add_accumulator_block(
instr : @lib.Instruction,
scratch_local : @lib.LocalIdx,
) -> Int32? {
match instr {
@lib.Block(
_,
@lib.Expr(
[@lib.LocalGet(local_id), @lib.I32Const(@lib.I32(raw)), @lib.I32Add]
)
) if local_id == scratch_local => Some(raw)
@lib.Block(
_,
@lib.Expr(
[@lib.I32Const(@lib.I32(raw)), @lib.LocalGet(local_id), @lib.I32Add]
)
) if local_id == scratch_local => Some(raw)
_ => None
}
}
///|
fn run_hot_pipeline_raw_simplify_locals_split_i32x4_rotate_read_count(
instrs : Array[@lib.Instruction],
local_id : Int32,
) -> Int32 {
let mut count = 0
for instr in instrs {
match instr {
@lib.Block(_, @lib.Expr(body))
| @lib.Loop(_, @lib.Expr(body))
| @lib.TryTable(_, _, @lib.Expr(body)) =>
count = count +
run_hot_pipeline_raw_simplify_locals_split_i32x4_rotate_read_count(
body, local_id,
)
@lib.If(_, then_body, else_body) => {
count = count +
run_hot_pipeline_raw_simplify_locals_split_i32x4_rotate_read_count(
then_body, local_id,
)
match else_body {
Some(body) =>
count = count +
run_hot_pipeline_raw_simplify_locals_split_i32x4_rotate_read_count(
body, local_id,
)
None => ()
}
}
_ => ()
}
}
let mut idx = 0
while idx + 10 < instrs.length() {
match
(
instrs[idx],
instrs[idx + 1],
instrs[idx + 2],
instrs[idx + 3],
instrs[idx + 4],
instrs[idx + 5],
instrs[idx + 6],
instrs[idx + 7],
instrs[idx + 8],
instrs[idx + 9],
instrs[idx + 10],
) {
(
source_instr,
@lib.I32Const(@lib.I32(first_shift)),
@lib.I32x4ShrU,
@lib.LocalSet(first_local),
@lib.LocalGet(second_source),
@lib.I32Const(@lib.I32(second_shift)),
@lib.I32x4Shl,
@lib.LocalSet(second_local),
@lib.LocalGet(first_read),
@lib.LocalGet(second_read),
@lib.V128Or,
) => {
let source_local = match source_instr {
@lib.LocalGet(local_idx) | @lib.LocalTee(local_idx) => Some(local_idx)
_ => None
}
match source_local {
Some(source_local) if second_source == source_local &&
first_read == first_local &&
second_read == second_local &&
first_shift > 0 &&
first_shift < 32 &&
second_shift > 0 &&
second_shift < 32 &&
first_shift + second_shift == 32 => {
let @lib.LocalIdx(first_raw) = first_local
let @lib.LocalIdx(second_raw) = second_local
if first_raw.reinterpret_as_int() == local_id {
count = count + 1
}
if second_raw.reinterpret_as_int() == local_id {
count = count + 1
}
idx = idx + 11
continue
}
_ => ()
}
}
_ => ()
}
idx = idx + 1
}
count
}
///|
fn run_hot_pipeline_raw_simplify_locals_rewrite_split_i32x4_rotate_temporaries_flat(
instrs : Array[@lib.Instruction],
all_instrs : Array[@lib.Instruction],
) -> (Array[@lib.Instruction], Int32) {
let rewritten : Array[@lib.Instruction] = []
let mut cursor = 0
let mut idx = 0
let mut rewrite_count = 0
while idx + 10 < instrs.length() {
let (source_instr, source_local, first_shift, first_local) = match
(instrs[idx], instrs[idx + 1], instrs[idx + 2], instrs[idx + 3]) {
(
source_instr,
@lib.I32Const(first_shift),
@lib.I32x4ShrU,
@lib.LocalSet(first_local),
) =>
match source_instr {
@lib.LocalGet(source_local) | @lib.LocalTee(source_local) =>
(source_instr, source_local, first_shift, first_local)
_ => {
idx = idx + 1
continue
}
}
_ => {
idx = idx + 1
continue
}
}
let (second_shift, second_local, result_op) = match
(
instrs[idx + 4],
instrs[idx + 5],
instrs[idx + 6],
instrs[idx + 7],
instrs[idx + 8],
instrs[idx + 9],
instrs[idx + 10],
) {
(
@lib.LocalGet(second_source),
@lib.I32Const(second_shift),
@lib.I32x4Shl,
@lib.LocalSet(second_local),
@lib.LocalGet(first_read),
@lib.LocalGet(second_read),
result_op,
) if second_source == source_local &&
first_read == first_local &&
second_read == second_local => (second_shift, second_local, result_op)
_ => {
idx = idx + 1
continue
}
}
let @lib.I32(first_shift_value) = first_shift
let @lib.I32(second_shift_value) = second_shift
let matching_rotate_width = first_shift_value > 0 &&
first_shift_value < 32 &&
second_shift_value > 0 &&
second_shift_value < 32 &&
first_shift_value + second_shift_value == 32
let matching_or = match result_op {
@lib.V128Or => true
_ => false
}
let @lib.LocalIdx(first_raw) = first_local
let @lib.LocalIdx(second_raw) = second_local
let first_id = first_raw.reinterpret_as_int()
let second_id = second_raw.reinterpret_as_int()
if !matching_rotate_width ||
!matching_or ||
source_local == first_local ||
source_local == second_local ||
first_local == second_local ||
run_hot_pipeline_raw_count_local_gets(all_instrs, first_id) !=
run_hot_pipeline_raw_simplify_locals_split_i32x4_rotate_read_count(
all_instrs, first_id,
) ||
run_hot_pipeline_raw_count_local_gets(all_instrs, second_id) !=
run_hot_pipeline_raw_simplify_locals_split_i32x4_rotate_read_count(
all_instrs, second_id,
) {
idx = idx + 1
continue
}
for copy_idx = cursor; copy_idx < idx; copy_idx = copy_idx + 1 {
rewritten.push(instrs[copy_idx])
}
rewritten.push(source_instr)
rewritten.push(@lib.Instruction::i32_const(first_shift))
rewritten.push(@lib.Instruction::i32x4_shr_u())
rewritten.push(@lib.Instruction::local_get(source_local))
rewritten.push(@lib.Instruction::i32_const(second_shift))
rewritten.push(@lib.Instruction::i32x4_shl())
rewritten.push(@lib.Instruction::v128_or())
cursor = idx + 11
idx = cursor
rewrite_count = rewrite_count + 1
}
for copy_idx = cursor; copy_idx < instrs.length(); copy_idx = copy_idx + 1 {
rewritten.push(instrs[copy_idx])
}
(rewritten, rewrite_count)
}
///|
fn run_hot_pipeline_raw_simplify_locals_rewrite_split_i32x4_rotate_temporaries_nested(
instrs : Array[@lib.Instruction],
all_instrs : Array[@lib.Instruction],
) -> (Array[@lib.Instruction], Int32) {
let normalized : Array[@lib.Instruction] = []
let mut rewrite_count = 0
for instr in instrs {
match instr {
@lib.Block(block_type, @lib.Expr(body)) => {
let (rewritten_body, nested_rewrites) = run_hot_pipeline_raw_simplify_locals_rewrite_split_i32x4_rotate_temporaries_nested(
body, all_instrs,
)
normalized.push(
@lib.Instruction::block(block_type, @lib.Expr::new(rewritten_body)),
)
rewrite_count = rewrite_count + nested_rewrites
}
@lib.Loop(block_type, @lib.Expr(body)) => {
let (rewritten_body, nested_rewrites) = run_hot_pipeline_raw_simplify_locals_rewrite_split_i32x4_rotate_temporaries_nested(
body, all_instrs,
)
normalized.push(
@lib.Instruction::loop_(block_type, @lib.Expr::new(rewritten_body)),
)
rewrite_count = rewrite_count + nested_rewrites
}
@lib.TryTable(block_type, catches, @lib.Expr(body)) => {
let (rewritten_body, nested_rewrites) = run_hot_pipeline_raw_simplify_locals_rewrite_split_i32x4_rotate_temporaries_nested(
body, all_instrs,
)
normalized.push(
@lib.Instruction::try_table(
block_type,
catches,
@lib.Expr::new(rewritten_body),
),
)
rewrite_count = rewrite_count + nested_rewrites
}
@lib.If(block_type, then_body, else_body) => {
let (rewritten_then, then_rewrites) = run_hot_pipeline_raw_simplify_locals_rewrite_split_i32x4_rotate_temporaries_nested(
then_body, all_instrs,
)
rewrite_count = rewrite_count + then_rewrites
let rewritten_else = match else_body {
Some(body) => {
let (rewritten_body, else_rewrites) = run_hot_pipeline_raw_simplify_locals_rewrite_split_i32x4_rotate_temporaries_nested(
body, all_instrs,
)
rewrite_count = rewrite_count + else_rewrites
Some(rewritten_body)
}
None => None
}
normalized.push(
@lib.Instruction::if_(block_type, rewritten_then, rewritten_else),
)
}
_ => normalized.push(instr)
}
}
let (rewritten, flat_rewrites) = run_hot_pipeline_raw_simplify_locals_rewrite_split_i32x4_rotate_temporaries_flat(
normalized, all_instrs,
)
(rewritten, rewrite_count + flat_rewrites)
}
///|
fn run_hot_pipeline_raw_simplify_locals_rewrite_split_i32x4_rotate_temporaries(
instrs : Array[@lib.Instruction],
stats? : RawSimplifyLocalsGateStats,
) -> (Array[@lib.Instruction], Int32) {
let stats = match stats {
Some(stats) => stats
None =>
run_hot_pipeline_raw_simplify_locals_gate_stats(
@lib.Locals::empty(),
instrs,
)
}
// A tee can supply the first source; the remaining pattern needs three gets.
if !stats.has_i32x4_shr_u ||
stats.local_set_count < 2 ||
stats.local_get_count < 3 {
return (instrs, 0)
}
run_hot_pipeline_raw_simplify_locals_rewrite_split_i32x4_rotate_temporaries_nested(
instrs, instrs,
)
}
///|
fn run_hot_pipeline_raw_simplify_locals_rewrite_stack_carried_local_gets_flat(
instrs : Array[@lib.Instruction],
env : @validate.Env,
) -> (Array[@lib.Instruction], Int32) {
let rewritten = instrs.copy()
let mut rewrite_count = 0
for set_idx = 0; set_idx < rewritten.length(); set_idx = set_idx + 1 {
let target = match rewritten[set_idx] {
@lib.LocalSet(target) => target
_ => continue
}
let mut state = @validate.make_state(env, [])
let mut scan = set_idx + 1
let scan_end = Int32::min(rewritten.length(), set_idx + 257)
while scan < scan_end {
match rewritten[scan] {
@lib.LocalGet(read) if read == target => {
if state.stack.length() == 0 {
rewritten[set_idx] = @lib.Instruction::local_tee(target)
rewritten[scan] = @lib.Instruction::nop()
rewrite_count = rewrite_count + 1
}
break
}
@lib.LocalSet(write) | @lib.LocalTee(write) if write == target => break
@lib.Block(_, _)
| @lib.Loop(_, _)
| @lib.If(_, _, _)
| @lib.Try(_, _, _, _)
| @lib.TryTable(_, _, _) => break
instr =>
match
@validate.Typecheck::typecheck(
instr,
@validate.tc_state_with_stack(state, state.stack),
) {
Ok(next) if next.reachable &&
next.escape == @validate.tc_escape_none() => state = next
_ => break
}
}
scan = scan + 1
}
}
(rewritten, rewrite_count)
}
///|
fn run_hot_pipeline_raw_simplify_locals_rewrite_stack_carried_local_gets(
instrs : Array[@lib.Instruction],
env : @validate.Env,
) -> (Array[@lib.Instruction], Int32) {
let normalized : Array[@lib.Instruction] = []
let mut rewrite_count = 0
for instr in instrs {
match instr {
@lib.Block(block_type, @lib.Expr(body)) => {
let (rewritten_body, nested_rewrites) = run_hot_pipeline_raw_simplify_locals_rewrite_stack_carried_local_gets(
body, env,
)
normalized.push(
@lib.Instruction::block(block_type, @lib.Expr::new(rewritten_body)),
)
rewrite_count = rewrite_count + nested_rewrites
}
@lib.Loop(block_type, @lib.Expr(body)) => {
let (rewritten_body, nested_rewrites) = run_hot_pipeline_raw_simplify_locals_rewrite_stack_carried_local_gets(
body, env,
)
normalized.push(
@lib.Instruction::loop_(block_type, @lib.Expr::new(rewritten_body)),
)
rewrite_count = rewrite_count + nested_rewrites
}
@lib.TryTable(block_type, catches, @lib.Expr(body)) => {
let (rewritten_body, nested_rewrites) = run_hot_pipeline_raw_simplify_locals_rewrite_stack_carried_local_gets(
body, env,
)
normalized.push(
@lib.Instruction::try_table(
block_type,
catches,
@lib.Expr::new(rewritten_body),
),
)
rewrite_count = rewrite_count + nested_rewrites
}
@lib.If(block_type, then_body, else_body) => {
let (rewritten_then, then_rewrites) = run_hot_pipeline_raw_simplify_locals_rewrite_stack_carried_local_gets(
then_body, env,
)
rewrite_count = rewrite_count + then_rewrites
let rewritten_else = match else_body {
Some(body) => {
let (rewritten_body, else_rewrites) = run_hot_pipeline_raw_simplify_locals_rewrite_stack_carried_local_gets(
body, env,
)
rewrite_count = rewrite_count + else_rewrites
Some(rewritten_body)
}
None => None
}
normalized.push(
@lib.Instruction::if_(block_type, rewritten_then, rewritten_else),
)
}
_ => normalized.push(instr)
}
}
let (rewritten, flat_rewrites) = run_hot_pipeline_raw_simplify_locals_rewrite_stack_carried_local_gets_flat(
normalized, env,
)
(rewritten, rewrite_count + flat_rewrites)
}
///|
fn run_hot_pipeline_raw_simplify_locals_instr_is_recurrence_binary(
instr : @lib.Instruction,
) -> Bool {
match instr {
@lib.I32Eq
| @lib.I32Ne
| @lib.I32LtS
| @lib.I32LtU
| @lib.I32GtS
| @lib.I32GtU
| @lib.I32LeS
| @lib.I32LeU
| @lib.I32GeS
| @lib.I32GeU
| @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 => true
_ =>
lcse_simd_pure_operand_count(instr) == Some(2) &&
lcse_simd_pure_result_type(instr) == Some(@lib.ValType::v128())
}
}
///|
fn run_hot_pipeline_raw_simplify_locals_simd_pure_op_count_at_most(
instrs : Array[@lib.Instruction],
limit : Int32,
) -> Int32 {
let mut count = 0
for instr in instrs {
if lcse_simd_pure_operand_count(instr) is Some(_) {
count = count + 1
}
match instr {
@lib.Block(_, @lib.Expr(body))
| @lib.Loop(_, @lib.Expr(body))
| @lib.TryTable(_, _, @lib.Expr(body)) =>
count = count +
run_hot_pipeline_raw_simplify_locals_simd_pure_op_count_at_most(
body,
limit - count,
)
@lib.If(_, then_body, else_body) => {
count = count +
run_hot_pipeline_raw_simplify_locals_simd_pure_op_count_at_most(
then_body,
limit - count,
)
match else_body {
Some(body) =>
count = count +
run_hot_pipeline_raw_simplify_locals_simd_pure_op_count_at_most(
body,
limit - count,
)
None => ()
}
}
_ => ()
}
if count >= limit {
return limit
}
}
count
}
///|
fn run_hot_pipeline_raw_simplify_locals_i8x16_shuffle_count_at_most(
instrs : Array[@lib.Instruction],
limit : Int32,
) -> Int32 {
let mut count = 0
for instr in instrs {
match instr {
@lib.I8x16Shuffle(_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _) =>
count = count + 1
@lib.Block(_, @lib.Expr(body))
| @lib.Loop(_, @lib.Expr(body))
| @lib.TryTable(_, _, @lib.Expr(body)) =>
count = count +
run_hot_pipeline_raw_simplify_locals_i8x16_shuffle_count_at_most(
body,
limit - count,
)
@lib.If(_, then_body, else_body) => {
count = count +
run_hot_pipeline_raw_simplify_locals_i8x16_shuffle_count_at_most(
then_body,
limit - count,
)
match else_body {
Some(body) =>
count = count +
run_hot_pipeline_raw_simplify_locals_i8x16_shuffle_count_at_most(
body,
limit - count,
)
None => ()
}
}
_ => ()
}
if count >= limit {
return limit
}
}
count
}
///|
fn run_hot_pipeline_raw_simplify_locals_has_any_call(
instrs : Array[@lib.Instruction],
) -> Bool {
for instr in instrs {
match instr {
@lib.Call(_)
| @lib.CallIndirect(_, _)
| @lib.CallRef(_)
| @lib.ReturnCall(_)
| @lib.ReturnCallIndirect(_, _)
| @lib.ReturnCallRef(_) => return true
@lib.Block(_, @lib.Expr(body))
| @lib.Loop(_, @lib.Expr(body))
| @lib.TryTable(_, _, @lib.Expr(body)) =>
if run_hot_pipeline_raw_simplify_locals_has_any_call(body) {
return true
}
@lib.If(_, then_body, else_body) => {
if run_hot_pipeline_raw_simplify_locals_has_any_call(then_body) {
return true
}
match else_body {
Some(body) =>
if run_hot_pipeline_raw_simplify_locals_has_any_call(body) {
return true
}
None => ()
}
}
_ => ()
}
}
false
}
///|
fn run_hot_pipeline_raw_simplify_locals_has_i32_div_s(
instrs : Array[@lib.Instruction],
) -> Bool {
for instr in instrs {
match instr {
@lib.I32DivS => return true
@lib.Block(_, @lib.Expr(body))
| @lib.Loop(_, @lib.Expr(body))
| @lib.TryTable(_, _, @lib.Expr(body)) =>
if run_hot_pipeline_raw_simplify_locals_has_i32_div_s(body) {
return true
}
@lib.If(_, then_body, else_body) => {
if run_hot_pipeline_raw_simplify_locals_has_i32_div_s(then_body) {
return true
}
match else_body {
Some(body) =>
if run_hot_pipeline_raw_simplify_locals_has_i32_div_s(body) {
return true
}
None => ()
}
}
_ => ()
}
}
false
}
///|
fn run_hot_pipeline_raw_simplify_locals_has_simd_nan_fixup_mesh(
instrs : Array[@lib.Instruction],
) -> Bool {
let mut has_nearest = false
let mut has_eq = false
let mut has_bitselect = false
for instr in instrs {
match instr {
@lib.F64x2Nearest => has_nearest = true
@lib.F64x2Eq => has_eq = true
@lib.V128Bitselect => has_bitselect = true
@lib.Block(_, @lib.Expr(body))
| @lib.Loop(_, @lib.Expr(body))
| @lib.TryTable(_, _, @lib.Expr(body)) =>
if run_hot_pipeline_raw_simplify_locals_has_simd_nan_fixup_mesh(body) {
return true
}
@lib.If(_, then_body, else_body) => {
if run_hot_pipeline_raw_simplify_locals_has_simd_nan_fixup_mesh(
then_body,
) {
return true
}
match else_body {
Some(body) =>
if run_hot_pipeline_raw_simplify_locals_has_simd_nan_fixup_mesh(
body,
) {
return true
}
None => ()
}
}
_ => ()
}
}
has_nearest && has_eq && has_bitselect
}
///|
// Count exactly the pairs consumed by the owned rewrite, without rebuilding
// nested rows. Child rewrites leave one control instruction in the parent,
// so they cannot create or remove a parent pair. Legacy Try stays untouched.
fn run_hot_pipeline_raw_simplify_locals_adjacent_set_get_count_at_most(
instrs : Array[@lib.Instruction],
limit : Int32,
) -> Int32 {
let mut count = 0
let mut idx = 0
while idx < instrs.length() && count < limit {
match instrs[idx] {
@lib.Block(_, @lib.Expr(body))
| @lib.Loop(_, @lib.Expr(body))
| @lib.TryTable(_, _, @lib.Expr(body)) =>
count += run_hot_pipeline_raw_simplify_locals_adjacent_set_get_count_at_most(
body,
limit - count,
)
@lib.If(_, then_body, else_body) => {
count += run_hot_pipeline_raw_simplify_locals_adjacent_set_get_count_at_most(
then_body,
limit - count,
)
if count < limit {
match else_body {
Some(body) =>
count += run_hot_pipeline_raw_simplify_locals_adjacent_set_get_count_at_most(
body,
limit - count,
)
None => ()
}
}
}
@lib.LocalSet(local) => {
let mut get_idx = idx + 1
while get_idx < instrs.length() &&
instrs[get_idx] == @lib.Instruction::nop() {
get_idx += 1
}
if get_idx < instrs.length() &&
instrs[get_idx] == @lib.Instruction::local_get(local) {
count += 1
idx = get_idx + 1
continue
}
}
_ => ()
}
idx += 1
}
count
}
///|
fn run_hot_pipeline_raw_simplify_locals_rewrite_adjacent_set_gets(
instrs : Array[@lib.Instruction],
) -> (Array[@lib.Instruction], Int32) {
if run_hot_pipeline_raw_simplify_locals_adjacent_set_get_count_at_most(
instrs, 1,
) ==
0 {
return (instrs, 0)
}
run_hot_pipeline_raw_simplify_locals_rewrite_adjacent_set_gets_owned(instrs)
}
///|
fn run_hot_pipeline_raw_simplify_locals_rewrite_adjacent_set_gets_owned(
instrs : Array[@lib.Instruction],
) -> (Array[@lib.Instruction], Int32) {
let nested : Array[@lib.Instruction] = []
let mut rewrite_count = 0
for instr in instrs {
match instr {
@lib.Block(block_type, @lib.Expr(body)) => {
let (rewritten, count) = run_hot_pipeline_raw_simplify_locals_rewrite_adjacent_set_gets_owned(
body,
)
nested.push(
@lib.Instruction::block(block_type, @lib.Expr::new(rewritten)),
)
rewrite_count = rewrite_count + count
}
@lib.Loop(block_type, @lib.Expr(body)) => {
let (rewritten, count) = run_hot_pipeline_raw_simplify_locals_rewrite_adjacent_set_gets_owned(
body,
)
nested.push(
@lib.Instruction::loop_(block_type, @lib.Expr::new(rewritten)),
)
rewrite_count = rewrite_count + count
}
@lib.TryTable(block_type, catches, @lib.Expr(body)) => {
let (rewritten, count) = run_hot_pipeline_raw_simplify_locals_rewrite_adjacent_set_gets_owned(
body,
)
nested.push(
@lib.Instruction::try_table(
block_type,
catches,
@lib.Expr::new(rewritten),
),
)
rewrite_count = rewrite_count + count
}
@lib.If(block_type, then_body, else_body) => {
let (rewritten_then, then_count) = run_hot_pipeline_raw_simplify_locals_rewrite_adjacent_set_gets_owned(
then_body,
)
let rewritten_else = match else_body {
Some(body) => {
let (rewritten, count) = run_hot_pipeline_raw_simplify_locals_rewrite_adjacent_set_gets_owned(
body,
)
rewrite_count = rewrite_count + count
Some(rewritten)
}
None => None
}
nested.push(
@lib.Instruction::if_(block_type, rewritten_then, rewritten_else),
)
rewrite_count = rewrite_count + then_count
}
_ => nested.push(instr)
}
}
let rewritten : Array[@lib.Instruction] = []
let mut idx = 0
while idx < nested.length() {
match nested[idx] {
@lib.LocalSet(set_local) => {
let mut get_idx = idx + 1
while get_idx < nested.length() &&
nested[get_idx] == @lib.Instruction::nop() {
get_idx += 1
}
if get_idx < nested.length() &&
nested[get_idx] == @lib.Instruction::local_get(set_local) {
rewritten.push(@lib.Instruction::local_tee(set_local))
for nop_idx = idx + 1; nop_idx < get_idx; nop_idx = nop_idx + 1 {
rewritten.push(nested[nop_idx])
}
rewrite_count += 1
idx = get_idx + 1
continue
}
}
_ => ()
}
rewritten.push(nested[idx])
idx = idx + 1
}
(rewritten, rewrite_count)
}
///|
fn run_hot_pipeline_raw_simplify_locals_rewrite_three_local_additive_recurrences_flat(
instrs : Array[@lib.Instruction],
) -> (Array[@lib.Instruction], Int32) {
// Both patterns require two complete ten-instruction recurrences. An
// unchanged result borrows this read-only row; changed results keep their
// original owned construction and the recursive caller's deep ownership.
if instrs.length() < 20 {
return (instrs, 0)
}
let rewritten : Array[@lib.Instruction] = []
let mut copy_start = 0
let mut idx = 0
let mut rewrite_count = 0
while idx + 19 < instrs.length() {
let (seed, first, second, third) = match
(
instrs[idx],
instrs[idx + 1],
instrs[idx + 2],
instrs[idx + 3],
instrs[idx + 4],
instrs[idx + 5],
instrs[idx + 6],
instrs[idx + 7],
instrs[idx + 8],
instrs[idx + 9],
) {
(
@lib.LocalGet(seed),
@lib.LocalGet(second),
@lib.I32Add,
@lib.LocalTee(first),
@lib.LocalGet(third),
@lib.I32Add,
@lib.LocalTee(written_second),
@lib.LocalGet(read_first),
@lib.I32Add,
@lib.LocalSet(written_third),
) if written_second == second &&
read_first == first &&
written_third == third &&
seed != first &&
seed != second &&
seed != third &&
first != second &&
first != third &&
second != third => (seed, first, second, third)
_ => {
idx = idx + 1
continue
}
}
let mut recurrence_end = idx
let mut recurrence_count = 0
while recurrence_end + 9 < instrs.length() {
match
(
instrs[recurrence_end],
instrs[recurrence_end + 1],
instrs[recurrence_end + 2],
instrs[recurrence_end + 3],
instrs[recurrence_end + 4],
instrs[recurrence_end + 5],
instrs[recurrence_end + 6],
instrs[recurrence_end + 7],
instrs[recurrence_end + 8],
instrs[recurrence_end + 9],
) {
(
@lib.LocalGet(read_seed),
@lib.LocalGet(read_second),
@lib.I32Add,
@lib.LocalTee(write_first),
@lib.LocalGet(read_third),
@lib.I32Add,
@lib.LocalTee(write_second),
@lib.LocalGet(read_first),
@lib.I32Add,
@lib.LocalSet(write_third),
) if read_seed == seed &&
read_second == second &&
write_first == first &&
read_third == third &&
write_second == second &&
read_first == first &&
write_third == third => {
recurrence_count = recurrence_count + 1
recurrence_end = recurrence_end + 10
}
_ => break
}
}
if recurrence_count < 2 {
idx = idx + 1
continue
}
for copy_idx = copy_start; copy_idx < idx; copy_idx = copy_idx + 1 {
rewritten.push(instrs[copy_idx])
}
rewritten.push(@lib.Instruction::local_get(third))
for recurrence = 0
recurrence < recurrence_count
recurrence = recurrence + 1 {
let recurrence_start = idx + recurrence * 10
rewritten.push(instrs[recurrence_start])
rewritten.push(instrs[recurrence_start + 1])
rewritten.push(instrs[recurrence_start + 2])
rewritten.push(instrs[recurrence_start + 3])
rewritten.push(instrs[recurrence_start + 5])
rewritten.push(instrs[recurrence_start + 6])
rewritten.push(instrs[recurrence_start + 7])
rewritten.push(instrs[recurrence_start + 8])
if recurrence + 1 == recurrence_count {
rewritten.push(@lib.Instruction::local_set(third))
} else {
rewritten.push(@lib.Instruction::local_tee(third))
}
}
rewrite_count = rewrite_count + recurrence_count
copy_start = recurrence_end
idx = recurrence_end
}
if rewrite_count == 0 {
return (instrs, 0)
}
for copy_idx = copy_start; copy_idx < instrs.length(); copy_idx = copy_idx + 1 {
rewritten.push(instrs[copy_idx])
}
(rewritten, rewrite_count)
}
///|
fn run_hot_pipeline_raw_simplify_locals_rewrite_alternating_bitselect_recurrences_flat(
instrs : Array[@lib.Instruction],
) -> (Array[@lib.Instruction], Int32) {
// Both patterns require two complete ten-instruction recurrences. An
// unchanged result borrows this read-only row; changed results keep their
// original owned construction and the recursive caller's deep ownership.
if instrs.length() < 20 {
return (instrs, 0)
}
let rewritten : Array[@lib.Instruction] = []
let mut copy_start = 0
let mut idx = 0
let mut rewrite_count = 0
while idx + 19 < instrs.length() {
let (left, right, mask) = match
(
instrs[idx],
instrs[idx + 1],
instrs[idx + 2],
instrs[idx + 3],
instrs[idx + 4],
instrs[idx + 5],
instrs[idx + 6],
instrs[idx + 7],
instrs[idx + 8],
instrs[idx + 9],
) {
(
@lib.LocalGet(left),
@lib.LocalGet(right),
mask,
@lib.V128Bitselect,
@lib.LocalSet(write_left),
@lib.LocalGet(read_right),
@lib.LocalGet(read_left),
repeated_mask,
@lib.V128Bitselect,
@lib.LocalSet(write_right),
) if left != right &&
write_left == left &&
read_right == right &&
read_left == left &&
write_right == right &&
mask is @lib.V128Const(_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _) &&
repeated_mask == mask => (left, right, mask)
_ => {
idx = idx + 1
continue
}
}
let mut recurrence_end = idx
let mut recurrence_count = 0
while recurrence_end + 9 < instrs.length() {
match
(
instrs[recurrence_end],
instrs[recurrence_end + 1],
instrs[recurrence_end + 2],
instrs[recurrence_end + 3],
instrs[recurrence_end + 4],
instrs[recurrence_end + 5],
instrs[recurrence_end + 6],
instrs[recurrence_end + 7],
instrs[recurrence_end + 8],
instrs[recurrence_end + 9],
) {
(
@lib.LocalGet(read_left),
@lib.LocalGet(read_right),
first_mask,
@lib.V128Bitselect,
@lib.LocalSet(write_left),
@lib.LocalGet(read_right_again),
@lib.LocalGet(read_left_again),
second_mask,
@lib.V128Bitselect,
@lib.LocalSet(write_right),
) if read_left == left &&
read_right == right &&
first_mask == mask &&
write_left == left &&
read_right_again == right &&
read_left_again == left &&
second_mask == mask &&
write_right == right => {
recurrence_count = recurrence_count + 1
recurrence_end = recurrence_end + 10
}
_ => break
}
}
if recurrence_count < 2 {
idx = idx + 1
continue
}
for copy_idx = copy_start; copy_idx < idx; copy_idx = copy_idx + 1 {
rewritten.push(instrs[copy_idx])
}
rewritten.push(@lib.Instruction::local_get(right))
for recurrence = 0
recurrence < recurrence_count
recurrence = recurrence + 1 {
let recurrence_start = idx + recurrence * 10
rewritten.push(instrs[recurrence_start])
rewritten.push(instrs[recurrence_start + 1])
rewritten.push(instrs[recurrence_start + 2])
rewritten.push(instrs[recurrence_start + 3])
rewritten.push(@lib.Instruction::local_tee(left))
rewritten.push(instrs[recurrence_start + 7])
rewritten.push(instrs[recurrence_start + 8])
if recurrence + 1 == recurrence_count {
rewritten.push(@lib.Instruction::local_set(right))
} else {
rewritten.push(@lib.Instruction::local_tee(right))
}
}
rewrite_count = rewrite_count + recurrence_count
copy_start = recurrence_end
idx = recurrence_end
}
if rewrite_count == 0 {
return (instrs, 0)
}
for copy_idx = copy_start; copy_idx < instrs.length(); copy_idx = copy_idx + 1 {
rewritten.push(instrs[copy_idx])
}
(rewritten, rewrite_count)
}
///|
fn run_hot_pipeline_raw_simplify_locals_rewrite_three_local_additive_recurrences(
instrs : Array[@lib.Instruction],
stats? : RawSimplifyLocalsGateStats,
) -> (Array[@lib.Instruction], Int32) {
let stats = match stats {
Some(stats) => stats
None =>
run_hot_pipeline_raw_simplify_locals_gate_stats(
@lib.Locals::empty(),
instrs,
)
}
let additive = stats.has_i32_add &&
stats.local_tee_count >= 4 &&
stats.local_set_count >= 2 &&
stats.local_get_count >= 8
let bitselect = stats.has_v128_bitselect &&
stats.local_set_count >= 4 &&
stats.local_get_count >= 8
if !additive && !bitselect {
return (instrs, 0)
}
// Possible candidates keep the original deep-owned recursive construction.
// Later cleanup can mutate even siblings that this worker did not rewrite.
run_hot_pipeline_raw_simplify_locals_rewrite_three_local_additive_recurrences_worker(
instrs,
)
}
///|
fn run_hot_pipeline_raw_simplify_locals_rewrite_three_local_additive_recurrences_worker(
instrs : Array[@lib.Instruction],
) -> (Array[@lib.Instruction], Int32) {
let normalized : Array[@lib.Instruction] = []
let mut rewrite_count = 0
for instr in instrs {
match instr {
@lib.Block(block_type, @lib.Expr(body)) => {
let (body, nested_rewrites) = run_hot_pipeline_raw_simplify_locals_rewrite_three_local_additive_recurrences_worker(
body,
)
normalized.push(
@lib.Instruction::block(block_type, @lib.Expr::new(body)),
)
rewrite_count = rewrite_count + nested_rewrites
}
@lib.Loop(block_type, @lib.Expr(body)) => {
let (body, nested_rewrites) = run_hot_pipeline_raw_simplify_locals_rewrite_three_local_additive_recurrences_worker(
body,
)
normalized.push(
@lib.Instruction::loop_(block_type, @lib.Expr::new(body)),
)
rewrite_count = rewrite_count + nested_rewrites
}
@lib.TryTable(block_type, catches, @lib.Expr(body)) => {
let (body, nested_rewrites) = run_hot_pipeline_raw_simplify_locals_rewrite_three_local_additive_recurrences_worker(
body,
)
normalized.push(
@lib.Instruction::try_table(block_type, catches, @lib.Expr::new(body)),
)
rewrite_count = rewrite_count + nested_rewrites
}
@lib.If(block_type, then_body, else_body) => {
let (then_body, then_rewrites) = run_hot_pipeline_raw_simplify_locals_rewrite_three_local_additive_recurrences_worker(
then_body,
)
rewrite_count = rewrite_count + then_rewrites
let else_body = match else_body {
Some(body) => {
let (body, else_rewrites) = run_hot_pipeline_raw_simplify_locals_rewrite_three_local_additive_recurrences_worker(
body,
)
rewrite_count = rewrite_count + else_rewrites
Some(body)
}
None => None
}
normalized.push(@lib.Instruction::if_(block_type, then_body, else_body))
}
_ => normalized.push(instr)
}
}
let (three_local_rewritten, three_local_rewrites) = run_hot_pipeline_raw_simplify_locals_rewrite_three_local_additive_recurrences_flat(
normalized,
)
let (rewritten, bitselect_rewrites) = run_hot_pipeline_raw_simplify_locals_rewrite_alternating_bitselect_recurrences_flat(
three_local_rewritten,
)
(rewritten, rewrite_count + three_local_rewrites + bitselect_rewrites)
}
///|
fn run_hot_pipeline_raw_simplify_locals_rewrite_alternating_simd_recurrences_flat(
instrs : Array[@lib.Instruction],
) -> (Array[@lib.Instruction], Int32) {
let rewritten : Array[@lib.Instruction] = []
let mut copy_start = 0
let mut idx = 0
let mut rewrite_count = 0
while idx + 15 < instrs.length() {
let (left, right) = match (instrs[idx], instrs[idx + 1], instrs[idx + 3]) {
(@lib.LocalGet(left), @lib.LocalGet(right), @lib.LocalSet(written)) if written ==
left &&
left != right => (left, right)
_ => {
idx = idx + 1
continue
}
}
if !run_hot_pipeline_raw_simplify_locals_instr_is_recurrence_binary(
instrs[idx + 2],
) {
idx = idx + 1
continue
}
let mut pair_end = idx
let mut pair_count = 0
while pair_end + 7 < instrs.length() {
match
(
instrs[pair_end],
instrs[pair_end + 1],
instrs[pair_end + 3],
instrs[pair_end + 4],
instrs[pair_end + 5],
instrs[pair_end + 7],
) {
(
@lib.LocalGet(read_left),
@lib.LocalGet(read_right),
@lib.LocalSet(write_left),
@lib.LocalGet(read_right_again),
@lib.LocalGet(read_left_again),
@lib.LocalSet(write_right),
) if read_left == left &&
read_right == right &&
write_left == left &&
read_right_again == right &&
read_left_again == left &&
write_right == right &&
run_hot_pipeline_raw_simplify_locals_instr_is_recurrence_binary(
instrs[pair_end + 2],
) &&
run_hot_pipeline_raw_simplify_locals_instr_is_recurrence_binary(
instrs[pair_end + 6],
) => {
pair_count = pair_count + 1
pair_end = pair_end + 8
}
_ => break
}
}
// A single pair saves only one local read. Requiring a repeated recurrence
// keeps this raw path aimed at the dense ISA-style family and avoids
// perturbing isolated local traffic that generic SimplifyLocals owns.
if pair_count < 2 {
idx = idx + 1
continue
}
for copy_idx = copy_start; copy_idx < idx; copy_idx = copy_idx + 1 {
rewritten.push(instrs[copy_idx])
}
rewritten.push(@lib.Instruction::local_get(right))
for pair = 0; pair < pair_count; pair = pair + 1 {
let pair_start = idx + pair * 8
rewritten.push(@lib.Instruction::local_get(left))
rewritten.push(@lib.Instruction::local_get(right))
rewritten.push(instrs[pair_start + 2])
rewritten.push(@lib.Instruction::local_tee(left))
rewritten.push(instrs[pair_start + 6])
if pair + 1 == pair_count {
rewritten.push(@lib.Instruction::local_set(right))
} else {
rewritten.push(@lib.Instruction::local_tee(right))
}
}
rewrite_count = rewrite_count + pair_count
copy_start = pair_end
idx = pair_end
}
for copy_idx = copy_start; copy_idx < instrs.length(); copy_idx = copy_idx + 1 {
rewritten.push(instrs[copy_idx])
}
(rewritten, rewrite_count)
}
///|
fn run_hot_pipeline_raw_simplify_locals_rewrite_alternating_simd_recurrences(
instrs : Array[@lib.Instruction],
stats? : RawSimplifyLocalsGateStats,
) -> (Array[@lib.Instruction], Int32) {
let stats = match stats {
Some(stats) => stats
None =>
run_hot_pipeline_raw_simplify_locals_gate_stats(
@lib.Locals::empty(),
instrs,
)
}
// Two alternating pairs need four stores and eight reads. Scalar binary
// operators remain admitted along with the SIMD forms of this rewrite.
if !stats.has_recurrence_binary ||
stats.local_set_count < 4 ||
stats.local_get_count < 8 {
return (instrs, 0)
}
// Possible candidates keep the original deep-owned recursive construction.
// Later cleanup can mutate even siblings that this worker did not rewrite.
run_hot_pipeline_raw_simplify_locals_rewrite_alternating_simd_recurrences_worker(
instrs,
)
}
///|
fn run_hot_pipeline_raw_simplify_locals_rewrite_alternating_simd_recurrences_worker(
instrs : Array[@lib.Instruction],
) -> (Array[@lib.Instruction], Int32) {
let normalized : Array[@lib.Instruction] = []
let mut rewrite_count = 0
for instr in instrs {
match instr {
@lib.Block(block_type, @lib.Expr(body)) => {
let (rewritten_body, nested_rewrites) = run_hot_pipeline_raw_simplify_locals_rewrite_alternating_simd_recurrences_worker(
body,
)
normalized.push(
@lib.Instruction::block(block_type, @lib.Expr::new(rewritten_body)),
)
rewrite_count = rewrite_count + nested_rewrites
}
@lib.Loop(block_type, @lib.Expr(body)) => {
let (rewritten_body, nested_rewrites) = run_hot_pipeline_raw_simplify_locals_rewrite_alternating_simd_recurrences_worker(
body,
)
normalized.push(
@lib.Instruction::loop_(block_type, @lib.Expr::new(rewritten_body)),
)
rewrite_count = rewrite_count + nested_rewrites
}
@lib.TryTable(block_type, catches, @lib.Expr(body)) => {
let (rewritten_body, nested_rewrites) = run_hot_pipeline_raw_simplify_locals_rewrite_alternating_simd_recurrences_worker(
body,
)
normalized.push(
@lib.Instruction::try_table(
block_type,
catches,
@lib.Expr::new(rewritten_body),
),
)
rewrite_count = rewrite_count + nested_rewrites
}
@lib.If(block_type, then_body, else_body) => {
let (rewritten_then, then_rewrites) = run_hot_pipeline_raw_simplify_locals_rewrite_alternating_simd_recurrences_worker(
then_body,
)
rewrite_count = rewrite_count + then_rewrites
let rewritten_else = match else_body {
Some(body) => {
let (rewritten_body, else_rewrites) = run_hot_pipeline_raw_simplify_locals_rewrite_alternating_simd_recurrences_worker(
body,
)
rewrite_count = rewrite_count + else_rewrites
Some(rewritten_body)
}
None => None
}
normalized.push(
@lib.Instruction::if_(block_type, rewritten_then, rewritten_else),
)
}
_ => normalized.push(instr)
}
}
let (rewritten, flat_rewrites) = run_hot_pipeline_raw_simplify_locals_rewrite_alternating_simd_recurrences_flat(
normalized,
)
(rewritten, rewrite_count + flat_rewrites)
}
///|
fn run_hot_pipeline_raw_simplify_locals_instr_is_commutative_simd_carrier_op(
instr : @lib.Instruction,
) -> Bool {
match instr {
@lib.V128Xor | @lib.I32x4Add => true
_ => false
}
}
///|
fn run_hot_pipeline_raw_simplify_locals_rewrite_commutative_simd_carriers_flat(
instrs : Array[@lib.Instruction],
) -> (Array[@lib.Instruction], Int32) {
let rewritten : Array[@lib.Instruction] = []
let mut cursor = 0
let mut idx = 0
let mut rewrite_count = 0
while idx + 3 < instrs.length() {
match (instrs[idx], instrs[idx + 1], instrs[idx + 2], instrs[idx + 3]) {
(@lib.LocalSet(target), @lib.LocalGet(_), @lib.LocalGet(read), op) if read ==
target &&
run_hot_pipeline_raw_simplify_locals_instr_is_commutative_simd_carrier_op(
op,
) => {
for copy_idx = cursor; copy_idx < idx; copy_idx = copy_idx + 1 {
rewritten.push(instrs[copy_idx])
}
rewritten.push(@lib.Instruction::local_tee(target))
rewritten.push(instrs[idx + 1])
rewritten.push(op)
cursor = idx + 4
idx = cursor
rewrite_count = rewrite_count + 1
continue
}
_ => ()
}
idx = idx + 1
}
for copy_idx = cursor; copy_idx < instrs.length(); copy_idx = copy_idx + 1 {
rewritten.push(instrs[copy_idx])
}
(rewritten, rewrite_count)
}
///|
fn run_hot_pipeline_raw_simplify_locals_rewrite_commutative_simd_carriers(
instrs : Array[@lib.Instruction],
stats? : RawSimplifyLocalsGateStats,
) -> (Array[@lib.Instruction], Int32) {
let stats = match stats {
Some(stats) => stats
None =>
run_hot_pipeline_raw_simplify_locals_gate_stats(
@lib.Locals::empty(),
instrs,
)
}
if !stats.has_simd_carrier_op ||
stats.local_set_count == 0 ||
stats.local_get_count < 2 {
return (instrs, 0)
}
// Possible candidates keep the original deep-owned recursive construction.
// Later cleanup can mutate even siblings that this worker did not rewrite.
run_hot_pipeline_raw_simplify_locals_rewrite_commutative_simd_carriers_worker(
instrs,
)
}
///|
fn run_hot_pipeline_raw_simplify_locals_rewrite_commutative_simd_carriers_worker(
instrs : Array[@lib.Instruction],
) -> (Array[@lib.Instruction], Int32) {
let normalized : Array[@lib.Instruction] = []
let mut rewrite_count = 0
for instr in instrs {
match instr {
@lib.Block(block_type, @lib.Expr(body)) => {
let (rewritten_body, nested_rewrites) = run_hot_pipeline_raw_simplify_locals_rewrite_commutative_simd_carriers_worker(
body,
)
normalized.push(
@lib.Instruction::block(block_type, @lib.Expr::new(rewritten_body)),
)
rewrite_count = rewrite_count + nested_rewrites
}
@lib.Loop(block_type, @lib.Expr(body)) => {
let (rewritten_body, nested_rewrites) = run_hot_pipeline_raw_simplify_locals_rewrite_commutative_simd_carriers_worker(
body,
)
normalized.push(
@lib.Instruction::loop_(block_type, @lib.Expr::new(rewritten_body)),
)
rewrite_count = rewrite_count + nested_rewrites
}
@lib.TryTable(block_type, catches, @lib.Expr(body)) => {
let (rewritten_body, nested_rewrites) = run_hot_pipeline_raw_simplify_locals_rewrite_commutative_simd_carriers_worker(
body,
)
normalized.push(
@lib.Instruction::try_table(
block_type,
catches,
@lib.Expr::new(rewritten_body),
),
)
rewrite_count = rewrite_count + nested_rewrites
}
@lib.If(block_type, then_body, else_body) => {
let (rewritten_then, then_rewrites) = run_hot_pipeline_raw_simplify_locals_rewrite_commutative_simd_carriers_worker(
then_body,
)
rewrite_count = rewrite_count + then_rewrites
let rewritten_else = match else_body {
Some(body) => {
let (rewritten_body, else_rewrites) = run_hot_pipeline_raw_simplify_locals_rewrite_commutative_simd_carriers_worker(
body,
)
rewrite_count = rewrite_count + else_rewrites
Some(rewritten_body)
}
None => None
}
normalized.push(
@lib.Instruction::if_(block_type, rewritten_then, rewritten_else),
)
}
_ => normalized.push(instr)
}
}
let (rewritten, flat_rewrites) = run_hot_pipeline_raw_simplify_locals_rewrite_commutative_simd_carriers_flat(
normalized,
)
(rewritten, rewrite_count + flat_rewrites)
}
///|
fn run_hot_pipeline_raw_simplify_locals_rewrite_i32_add_accumulator_chain(
instrs : Array[@lib.Instruction],
) -> (Array[@lib.Instruction], Bool) {
if instrs.length() < 6 {
return (instrs, false)
}
let (seed_local, accumulator_local) = match (instrs[0], instrs[1]) {
(@lib.LocalGet(seed), @lib.LocalSet(accumulator)) => (seed, accumulator)
_ => return (instrs, false)
}
let mut idx = 2
let mut total = 0
let mut rewrite_count = 0
while idx + 3 < instrs.length() {
let scratch_local = match (instrs[idx], instrs[idx + 1]) {
(@lib.LocalGet(read_local), @lib.LocalSet(scratch)) if read_local ==
accumulator_local => scratch
_ => break
}
let addend = match
run_hot_pipeline_raw_simplify_locals_match_i32_add_accumulator_block(
instrs[idx + 2],
scratch_local,
) {
Some(raw) => raw
None => break
}
match instrs[idx + 3] {
@lib.LocalSet(write_local) if write_local == accumulator_local => ()
_ => break
}
total = total + addend
rewrite_count = rewrite_count + 1
idx = idx + 4
}
if rewrite_count == 0 || idx + 1 != instrs.length() {
return (instrs, false)
}
match instrs[idx] {
@lib.LocalGet(final_local) if final_local == accumulator_local => ()
_ => return (instrs, false)
}
let out : Array[@lib.Instruction] = [@lib.Instruction::local_get(seed_local)]
if total != 0 {
out.push(@lib.Instruction::i32_const(total))
out.push(@lib.Instruction::i32_add())
}
(out, true)
}
///|
fn run_hot_pipeline_raw_simplify_locals_has_self_update_loop_lifetime(
instrs : Array[@lib.Instruction],
inside_loop : Bool,
) -> Bool {
if inside_loop {
for idx = 0; idx + 2 < instrs.length(); idx = idx + 1 {
if run_hot_pipeline_optimize_instructions_instr_is_plain_load(instrs[idx]) {
match (instrs[idx + 1], instrs[idx + 2]) {
(@lib.LocalSet(written), @lib.LocalGet(read)) if written == read =>
return true
_ => ()
}
}
}
for idx = 0; idx + 4 < instrs.length(); idx = idx + 1 {
match
(
instrs[idx],
instrs[idx + 1],
instrs[idx + 2],
instrs[idx + 3],
instrs[idx + 4],
) {
(
@lib.LocalGet(read_before),
@lib.I32Const(_),
@lib.I32Add
| @lib.I32Sub,
@lib.LocalSet(written),
@lib.LocalGet(read_after),
) if read_before == written && written == read_after => return true
_ => ()
}
}
}
for instr in instrs {
match instr {
@lib.Loop(_, @lib.Expr(body)) =>
if run_hot_pipeline_raw_simplify_locals_has_self_update_loop_lifetime(
body, true,
) {
return true
}
@lib.Block(_, @lib.Expr(body)) | @lib.TryTable(_, _, @lib.Expr(body)) =>
if run_hot_pipeline_raw_simplify_locals_has_self_update_loop_lifetime(
body, inside_loop,
) {
return true
}
@lib.If(_, then_body, else_body) => {
if run_hot_pipeline_raw_simplify_locals_has_self_update_loop_lifetime(
then_body, inside_loop,
) {
return true
}
match else_body {
Some(body) =>
if run_hot_pipeline_raw_simplify_locals_has_self_update_loop_lifetime(
body, inside_loop,
) {
return true
}
None => ()
}
}
_ => ()
}
}
false
}
///|
fn run_hot_pipeline_raw_has_root_global_snapshot_write_prefix(
instrs : Array[@lib.Instruction],
instruction_visits~ : Array[Int32]? = None,
) -> Bool {
let mut live_count = 0
let mut target = @lib.GlobalIdx::new(0U)
for instr in instrs {
match instruction_visits {
Some(visits) => visits[0] += 1
None => ()
}
if instr is @lib.Nop {
continue
}
match live_count {
0 =>
match instr {
@lib.GlobalGet(index) => target = index
_ => return false
}
1 =>
match instr {
@lib.LocalTee(_) => ()
_ => return false
}
2 =>
match instr {
@lib.GlobalGet(index) if index == target => ()
_ => return false
}
_ =>
match instr {
@lib.GlobalSet(index) if index == target => return true
_ => ()
}
}
live_count += 1
// Only the first sixteen non-nop instructions participate in this guard.
if live_count == 16 {
return false
}
}
false
}
///|
fn run_hot_pipeline_raw_has_root_global_snapshot_return_hazard(
instrs : Array[@lib.Instruction],
) -> Bool {
let live : Array[@lib.Instruction] = []
for instr in instrs {
match instr {
@lib.Nop => ()
_ => live.push(instr)
}
}
if live.length() < 4 {
return false
}
let target = match live[0] {
@lib.GlobalGet(target) => target
_ => return false
}
let carrier_start = if live[live.length() - 2] is @lib.LocalTee(_) &&
live[live.length() - 1] is @lib.Return {
live.length() - 2
} else if live.length() >= 5 {
match
(
live[live.length() - 3],
live[live.length() - 2],
live[live.length() - 1],
) {
(@lib.LocalSet(written), @lib.LocalGet(read), @lib.Return) if written ==
read => live.length() - 3
_ => return false
}
} else {
return false
}
for idx = 1; idx < carrier_start; idx = idx + 1 {
match live[idx] {
@lib.GlobalSet(written) if written == target => return true
_ => ()
}
}
false
}
///|
fn run_hot_pipeline_raw_simplify_locals_has_unconditional_backedge_self_update(
instrs : Array[@lib.Instruction],
parameter_count : Int32,
) -> Bool {
for idx = 0; idx + 4 < instrs.length(); idx = idx + 1 {
match
(
instrs[idx],
instrs[idx + 1],
instrs[idx + 2],
instrs[idx + 3],
instrs[idx + 4],
) {
(
@lib.LocalGet(@lib.LocalIdx(read_local)),
@lib.I32Const(_),
@lib.I32Add
| @lib.I32Sub,
@lib.LocalSet(@lib.LocalIdx(written_local)),
@lib.Br(_),
) if read_local == written_local &&
read_local.reinterpret_as_int() < parameter_count => return true
_ => ()
}
}
for instr in instrs {
match instr {
@lib.Block(_, @lib.Expr(body))
| @lib.Loop(_, @lib.Expr(body))
| @lib.TryTable(_, _, @lib.Expr(body)) =>
if run_hot_pipeline_raw_simplify_locals_has_unconditional_backedge_self_update(
body, parameter_count,
) {
return true
}
@lib.If(_, then_body, else_body) => {
if run_hot_pipeline_raw_simplify_locals_has_unconditional_backedge_self_update(
then_body, parameter_count,
) {
return true
}
match else_body {
Some(body) =>
if run_hot_pipeline_raw_simplify_locals_has_unconditional_backedge_self_update(
body, parameter_count,
) {
return true
}
None => ()
}
}
_ => ()
}
}
false
}
///|
fn run_hot_pipeline_raw_simplify_locals_has_wago_expansion_hazard(
instrs : Array[@lib.Instruction],
) -> Bool {
let mut local_set_count = 0
let mut global_set_count = 0
let mut if_count = 0
let mut has_v128_const = false
let mut has_i64x2_abs = false
let mut has_i8x16_le_s = false
for instr in instrs {
match instr {
@lib.Instruction::LocalSet(_) => local_set_count += 1
@lib.Instruction::GlobalSet(_) => global_set_count += 1
@lib.Instruction::If(_, _, _) => if_count += 1
@lib.Instruction::V128Const(
_,
_,
_,
_,
_,
_,
_,
_,
_,
_,
_,
_,
_,
_,
_,
_
) => has_v128_const = true
@lib.Instruction::I64x2Abs => has_i64x2_abs = true
@lib.Instruction::I8x16LeS => has_i8x16_le_s = true
_ => ()
}
}
// Two WAGO-derived families expose superlinear work between precompute and
// simplify-locals: broad flat SIMD/global carrier bodies (1793b) and Winch's
// global-countdown body with many local writes and conditional clamps. Keep
// this preflight intentionally narrow; returning unchanged avoids both the
// recursive raw scanners and a changed HOT lowering that can recreate the
// same expansion before later O4z cleanup passes.
(
instrs.length() >= 32 &&
global_set_count >= 1 &&
has_v128_const &&
has_i64x2_abs &&
has_i8x16_le_s
) ||
(
instrs.length() >= 32 &&
global_set_count >= 1 &&
local_set_count >= 6 &&
if_count >= 2
)
}
///|
fn run_hot_pipeline_raw_simplify_locals(
mod_ : @lib.Module,
func_idx : @lib.FuncIdx,
module_state : HotPipelineModuleState,
descriptor : @ir.HotPassDescriptor,
options : HotPipelineOptions,
) -> HotPipelineFuncResult? {
if descriptor.name != "simplify-locals" &&
descriptor.name != "simplify-locals-notee" &&
descriptor.name != "simplify-locals-no-tee" &&
descriptor.name != "simplify-locals-nostructure" &&
descriptor.name != "simplify-locals-no-structure" &&
descriptor.name != "simplify-locals-notee-nostructure" {
return None
}
let no_structure_variant = descriptor.name == "simplify-locals-nostructure" ||
descriptor.name == "simplify-locals-no-structure" ||
descriptor.name == "simplify-locals-notee-nostructure"
let original = run_hot_pipeline_original_func(mod_, func_idx)
let @lib.Func(locals, @lib.Expr(instrs)) = original
let module_ctx = run_hot_pipeline_require_module_ctx(
mod_, module_state, options,
)
let params = run_hot_pipeline_required_raw_func_params(
mod_, module_ctx, func_idx, module_state,
)
let parameter_count = params.length()
let allows_tee = descriptor.name != "simplify-locals-notee" &&
descriptor.name != "simplify-locals-no-tee" &&
descriptor.name != "simplify-locals-notee-nostructure"
// Type-indexed loops remain outside the broad HOT path because their
// parameter stack flow needs separate path-sensitive proof. When the outer
// gate admits a local-writing typed loop, keep its raw path exact as well:
// replace only same-index `local.set; local.get` pairs and return before any
// other raw rewrite or exact cleanup can run.
if descriptor.name == "simplify-locals" &&
run_hot_pipeline_lowered_func_has_typeidx_loop(original) &&
run_hot_pipeline_raw_simplify_locals_has_local_write(instrs) {
let (adjacent_instrs, adjacent_rewrites) = run_hot_pipeline_raw_simplify_locals_rewrite_adjacent_set_gets(
instrs,
)
if adjacent_rewrites > 0 {
return Some({
reason: "typed-loop-adjacent-set-get-tee",
func: @lib.Func::new(locals, @lib.Expr::new(adjacent_instrs)),
unchanged_original: false,
trace_skip_raw: true,
})
}
}
if run_hot_pipeline_raw_try_table_catch_body_has_local_write(instrs) {
return Some({
reason: "try-table-local-write-simplify-locals-noop",
func: original,
unchanged_original: true,
trace_skip_raw: true,
})
}
if run_hot_pipeline_raw_has_root_global_snapshot_return_hazard(instrs) {
return Some({
reason: "root-global-snapshot-return-simplify-locals-noop",
func: original,
unchanged_original: true,
trace_skip_raw: true,
})
}
let stats = run_hot_pipeline_raw_simplify_locals_gate_stats(locals, instrs)
if allows_tee {
let (recurrence_instrs, recurrence_rewrites) = run_hot_pipeline_raw_simplify_locals_rewrite_three_local_additive_recurrences(
instrs,
stats~,
)
if recurrence_rewrites > 0 {
let (cleaned, _) = run_hot_pipeline_simplify_locals_cleanup_exact_func(
@lib.Func::new(locals, @lib.Expr::new(recurrence_instrs)),
)
return Some({
reason: "stack-carried-local-recurrence",
func: cleaned,
unchanged_original: false,
trace_skip_raw: true,
})
}
}
if descriptor.name == "simplify-locals" &&
run_hot_pipeline_raw_simplify_locals_has_unconditional_backedge_self_update(
instrs, parameter_count,
) {
return Some({
reason: "unconditional-backedge-self-update-lifetime-noop",
func: original,
unchanged_original: true,
trace_skip_raw: true,
})
}
if run_hot_pipeline_raw_simplify_locals_has_wago_expansion_hazard(instrs) {
return Some({
reason: "wago-expansion-hazard-noop",
func: original,
unchanged_original: true,
trace_skip_raw: true,
})
}
if run_hot_pipeline_tuple_has_effect_bracketed_multivalue_call(
instrs, module_ctx,
) {
return Some({
reason: "effect-bracketed-multivalue-call-simplify-locals-noop",
func: original,
unchanged_original: true,
trace_skip_raw: true,
})
}
if descriptor.name == "simplify-locals" {
let (conditional_instrs, conditional_rewrites) = run_hot_pipeline_raw_simplify_locals_rewrite_leaf_conditional_assignments(
instrs,
params,
locals,
stats~,
)
if conditional_rewrites > 0 {
let (cleaned, _) = run_hot_pipeline_simplify_locals_cleanup_exact_func(
@lib.Func::new(locals, @lib.Expr::new(conditional_instrs)),
)
return Some({
reason: "leaf-conditional-local-select",
func: cleaned,
unchanged_original: false,
trace_skip_raw: false,
})
}
}
let stack_carried_size_admitted = (
options.optimize_level >= 4 &&
options.shrink_level >= 4 &&
stats.instr_count <= 2048 &&
dae_defined_func_count(mod_) <= 128
) ||
(stats.instr_count >= 6144 && stats.instr_count <= 16384)
if run_hot_pipeline_raw_has_call_argument_structured_release(instrs) ||
run_hot_pipeline_raw_has_call_argument_distinct_structured_releases(instrs) {
// The HOT lifetime guard still applies. Raw effectful suffix forwarding
// preserves later release reads with tees and crosses only pure prefixes.
match
run_hot_pipeline_raw_simplify_locals_rewrite_skipped_effectful_carriers(
mod_,
func_idx,
module_state,
descriptor,
options,
locals,
instrs,
"structured-release-effectful-carrier",
effectful_only=true,
) {
Some(result) => return Some(result)
None => ()
}
let reason = if no_structure_variant {
"call-argument-structured-release-simplify-locals-nostructure-noop"
} else {
"call-argument-structured-release-simplify-locals-noop"
}
return Some({
reason,
func: original,
unchanged_original: true,
trace_skip_raw: true,
})
}
if run_hot_pipeline_raw_simplify_locals_has_call_result_before_structured_release(
instrs, module_ctx,
) {
return Some({
reason: "call-result-before-structured-release",
func: original,
unchanged_original: true,
trace_skip_raw: true,
})
}
if allows_tee {
// Only local set/get operations change. Calls, SIMD pure op counts,
// shuffles, signed division and the NaN mesh stay identical, including
// their control regions. Test admission on the source before copying it.
let adjacent_rewrites = run_hot_pipeline_raw_simplify_locals_adjacent_set_get_count_at_most(
instrs, 8,
)
let has_simd_nan_fixup_mesh = run_hot_pipeline_raw_simplify_locals_has_simd_nan_fixup_mesh(
instrs,
)
let has_any_call = run_hot_pipeline_raw_simplify_locals_has_any_call(instrs)
let bulk_adjacent_admitted = if has_any_call {
adjacent_rewrites > 0 &&
run_hot_pipeline_raw_simplify_locals_simd_pure_op_count_at_most(
instrs, 16,
) >=
16
} else {
!run_hot_pipeline_raw_simplify_locals_has_i32_div_s(instrs) &&
(
(adjacent_rewrites >= 8 && stats.locals_count <= 16) ||
(
adjacent_rewrites > 0 &&
run_hot_pipeline_raw_simplify_locals_i8x16_shuffle_count_at_most(
instrs, 8,
) >=
8
)
)
}
if bulk_adjacent_admitted || has_simd_nan_fixup_mesh {
// NaN mesh cleanup also runs when there are no adjacent pairs. Keep
// the original fully owned construction for every admitted body.
let (adjacent_instrs, _) = run_hot_pipeline_raw_simplify_locals_rewrite_adjacent_set_gets_owned(
instrs,
)
let adjacent_func = @lib.Func::new(
locals,
@lib.Expr::new(adjacent_instrs),
)
let folded_func = if has_simd_nan_fixup_mesh {
match precompute_run_raw_func(adjacent_func, Some(module_ctx)) {
Some((folded, true, _)) => folded
_ => adjacent_func
}
} else {
adjacent_func
}
let (cleaned, _) = run_hot_pipeline_simplify_locals_cleanup_exact_func(
folded_func,
)
return Some({
reason: "adjacent-set-get-tee",
func: cleaned,
unchanged_original: false,
trace_skip_raw: true,
})
}
let (alternating_instrs, alternating_rewrites) = run_hot_pipeline_raw_simplify_locals_rewrite_alternating_simd_recurrences(
instrs,
stats~,
)
if alternating_rewrites > 0 {
let (cleaned, _) = run_hot_pipeline_simplify_locals_cleanup_exact_func(
@lib.Func::new(locals, @lib.Expr::new(alternating_instrs)),
)
return Some({
reason: "alternating-simd-recurrence",
func: cleaned,
unchanged_original: false,
trace_skip_raw: true,
})
}
}
if allows_tee && stats.locals_count <= 64 && stack_carried_size_admitted {
let env = run_hot_pipeline_raw_simplify_locals_func_env(
mod_,
func_idx,
module_ctx,
locals,
module_env=Some(
run_hot_pipeline_require_validation_module_env(
mod_, module_state, options,
),
),
)
let (stack_carried_instrs, stack_carried_rewrites) = run_hot_pipeline_raw_simplify_locals_rewrite_stack_carried_local_gets(
instrs, env,
)
if stack_carried_rewrites > 0 {
let (cleaned, _) = run_hot_pipeline_simplify_locals_cleanup_exact_func(
@lib.Func::new(locals, @lib.Expr::new(stack_carried_instrs)),
)
return Some({
reason: "stack-carried-local-get",
func: cleaned,
unchanged_original: false,
trace_skip_raw: true,
})
}
}
if descriptor.name == "simplify-locals-notee-nostructure" &&
(
(
stats.call_count > 0 &&
run_hot_pipeline_raw_instrs_have_plain_load(instrs)
) ||
run_hot_pipeline_raw_simplify_locals_has_load_result_copy_boundary(instrs)
) {
return Some({
reason: "load-call-adjacent-copy-simplify-locals-notee-noop",
func: original,
unchanged_original: true,
trace_skip_raw: true,
})
}
if descriptor.name == "simplify-locals-notee-nostructure" &&
run_hot_pipeline_raw_simplify_locals_has_call_bulk_memory_effect_mesh(
instrs,
) {
return Some({
reason: "call-bulk-memory-effect-order-simplify-locals-notee-noop",
func: original,
unchanged_original: true,
trace_skip_raw: true,
})
}
if locals.length() <= 64 && stats.instr_count >= 6144 {
return Some({
reason: "oversized-lowered-simplify-locals-preflight",
func: original,
unchanged_original: true,
trace_skip_raw: true,
})
}
// These stack-local SIMD rewrites are exact even when generic local
// propagation must fail closed around loop-carried values. Run them before
// the broad loop guards so source SIMD kernels can shed lowering carriers
// before inlining combines them into a larger function.
if descriptor.name == "simplify-locals" && stats.loop_count > 0 {
let (simd_rotate_instrs, simd_rotate_rewrites) = run_hot_pipeline_raw_simplify_locals_rewrite_split_i32x4_rotate_temporaries(
instrs,
stats~,
)
let (simd_carrier_instrs, simd_carrier_rewrites) = run_hot_pipeline_raw_simplify_locals_rewrite_commutative_simd_carriers(
simd_rotate_instrs,
stats~,
)
if simd_rotate_rewrites > 0 || simd_carrier_rewrites > 0 {
let (cleaned, _) = run_hot_pipeline_simplify_locals_cleanup_exact_func(
@lib.Func::new(locals, @lib.Expr::new(simd_carrier_instrs)),
)
return Some({
reason: "loop-simd-temporaries",
func: cleaned,
unchanged_original: false,
trace_skip_raw: true,
})
}
}
if stats.loop_count > 0 &&
run_hot_pipeline_raw_simplify_locals_has_parameter_initialized_result_loop_carrier(
instrs,
) {
return Some({
reason: "parameter-initialized-block-result-loop-carrier",
func: original,
unchanged_original: true,
trace_skip_raw: true,
})
}
if stats.loop_count > 0 &&
run_hot_pipeline_raw_simplify_locals_has_initialized_loop_carrier(instrs) {
return Some({
reason: "loop-carried-local-write",
func: original,
unchanged_original: true,
trace_skip_raw: true,
})
}
if stats.locals_count >= 32 &&
(stats.local_set_count > 0 || stats.local_tee_count > 0) &&
run_hot_pipeline_raw_simplify_locals_has_deep_result_if_chain(instrs, 0) {
match
run_hot_pipeline_raw_simplify_locals_deep_result_if_unread_tee_cleanup(
mod_, func_idx, module_state, options, locals, instrs,
) {
Some(result) => return Some(result)
None => ()
}
}
let structured_startup_initializer_hot_lift_hazard = stats.locals_count <= 8 &&
stats.has_structured_control &&
stats.instr_count >= 24 &&
stats.call_count >= 4 &&
stats.local_tee_count > 0 &&
stats.has_global_state &&
stats.has_stack_effect_hazard
if structured_startup_initializer_hot_lift_hazard {
return Some({
reason: "structured-startup-initializer-call-store-local-tee",
func: original,
unchanged_original: true,
trace_skip_raw: true,
})
}
let structured_memory_builder_local_carrier = stats.locals_count >= 8 &&
stats.locals_count <= 32 &&
stats.has_structured_control &&
stats.has_memory_size
if structured_memory_builder_local_carrier {
return Some({
reason: "structured-memory-builder-local-carrier",
func: original,
unchanged_original: true,
trace_skip_raw: true,
})
}
let startup_initializer_hot_lift_hazard = stats.locals_count <= 32 &&
!stats.has_structured_control &&
stats.instr_count >= 24 &&
stats.local_set_count <= 24 &&
stats.local_get_count <= 24 &&
stats.call_count >= 2 &&
stats.has_global_state &&
stats.has_stack_effect_hazard
if startup_initializer_hot_lift_hazard {
return Some({
reason: "startup-initializer-call-store-hot-lift",
func: original,
unchanged_original: true,
trace_skip_raw: true,
})
}
if run_hot_pipeline_raw_simplify_locals_has_parameter_result_if_late_alias_lifetime(
instrs,
params.length(),
) {
return Some({
reason: "parameter-result-if-late-alias-lifetime",
func: original,
unchanged_original: true,
trace_skip_raw: true,
})
}
if (no_structure_variant || descriptor.name == "simplify-locals") &&
run_hot_pipeline_raw_simplify_locals_has_call_bulk_memory_effect_mesh(
instrs,
) {
if descriptor.name == "simplify-locals" {
let (simd_rotate_instrs, simd_rotate_rewrites) = run_hot_pipeline_raw_simplify_locals_rewrite_split_i32x4_rotate_temporaries(
instrs,
stats~,
)
let (simd_carrier_instrs, simd_carrier_rewrites) = run_hot_pipeline_raw_simplify_locals_rewrite_commutative_simd_carriers(
simd_rotate_instrs,
stats~,
)
if simd_rotate_rewrites > 0 || simd_carrier_rewrites > 0 {
let (cleaned, _) = run_hot_pipeline_simplify_locals_cleanup_exact_func(
@lib.Func::new(locals, @lib.Expr::new(simd_carrier_instrs)),
)
return Some({
reason: "call-bulk-memory-simd-temporaries",
func: cleaned,
unchanged_original: false,
trace_skip_raw: true,
})
}
}
return Some({
reason: "call-bulk-memory-effect-order-simplify-locals-nostructure-noop",
func: original,
unchanged_original: true,
trace_skip_raw: true,
})
}
if (no_structure_variant || descriptor.name == "simplify-locals") &&
run_hot_pipeline_raw_simplify_locals_has_self_update_loop_lifetime(
instrs, false,
) {
return Some({
reason: "self-update-loop-local-lifetime",
func: original,
unchanged_original: true,
trace_skip_raw: true,
})
}
if (no_structure_variant || descriptor.name == "simplify-locals") &&
run_hot_pipeline_raw_precompute_has_stack_carried_overwritten_local(instrs) {
return Some({
reason: "stack-carried-overwritten-local",
func: original,
unchanged_original: true,
trace_skip_raw: true,
})
}
if no_structure_variant &&
run_hot_pipeline_raw_has_load_before_same_local_call(instrs) {
return Some({
reason: "load-before-release-simplify-locals-nostructure-noop",
func: original,
unchanged_original: true,
trace_skip_raw: true,
})
}
if descriptor.name == "simplify-locals" &&
run_hot_pipeline_raw_has_load_before_same_local_call(instrs) {
return Some({
reason: "load-before-release-simplify-locals-noop",
func: original,
unchanged_original: true,
trace_skip_raw: true,
})
}
if no_structure_variant &&
run_hot_pipeline_raw_has_call_argument_direct_release(instrs) {
return Some({
reason: "call-argument-direct-release-simplify-locals-nostructure-noop",
func: original,
unchanged_original: true,
trace_skip_raw: true,
})
}
if descriptor.name == "simplify-locals" &&
run_hot_pipeline_raw_has_call_argument_direct_release(instrs) {
return Some({
reason: "call-argument-direct-release-simplify-locals-noop",
func: original,
unchanged_original: true,
trace_skip_raw: true,
})
}
if no_structure_variant &&
run_hot_pipeline_raw_simplify_locals_has_local_alias_multi_call_lifetime(
instrs,
) {
return Some({
reason: "local-alias-multi-call-lifetime",
func: original,
unchanged_original: true,
trace_skip_raw: true,
})
}
if no_structure_variant &&
run_hot_pipeline_raw_simplify_locals_has_call_result_structured_lifetime(
instrs,
) {
return Some({
reason: "call-result-structured-lifetime",
func: original,
unchanged_original: true,
trace_skip_raw: true,
})
}
if no_structure_variant &&
run_hot_pipeline_dce_raw_has_call_result_multi_call_lifetime(instrs) {
return Some({
reason: "call-result-multi-call-lifetime",
func: original,
unchanged_original: true,
trace_skip_raw: true,
})
}
if no_structure_variant &&
run_hot_pipeline_raw_simplify_locals_has_call_local_tee_lifetime(instrs) {
return Some({
reason: "call-local-tee-lifetime",
func: original,
unchanged_original: true,
trace_skip_raw: true,
})
}
if run_hot_pipeline_raw_has_call_argument_structured_release(instrs) ||
run_hot_pipeline_raw_has_call_argument_distinct_structured_releases(instrs) {
let reason = if no_structure_variant {
"call-argument-structured-release-simplify-locals-nostructure-noop"
} else {
"call-argument-structured-release-simplify-locals-noop"
}
return Some({
reason,
func: original,
unchanged_original: true,
trace_skip_raw: true,
})
}
if no_structure_variant &&
run_hot_pipeline_raw_has_single_argument_distinct_call_lifetime(instrs) {
return Some({
reason: "single-argument-distinct-call-lifetime-simplify-locals-nostructure-noop",
func: original,
unchanged_original: true,
trace_skip_raw: true,
})
}
if descriptor.name == "simplify-locals" &&
run_hot_pipeline_raw_has_single_argument_distinct_call_lifetime(instrs) {
match
run_hot_pipeline_raw_simplify_locals_full_lifetime_guard(original, instrs) {
Some(result) => return Some(result)
None => ()
}
return Some({
reason: "single-argument-distinct-call-lifetime-simplify-locals-noop",
func: original,
unchanged_original: true,
trace_skip_raw: true,
})
}
if stats.local_set_count == 0 && stats.local_tee_count == 0 {
let (flattened_instrs, flattened_count) = run_hot_pipeline_raw_simplify_locals_flatten_no_normal_result_blocks(
instrs,
)
if flattened_count > 0 {
return Some({
reason: "no-local-writes-no-normal-result-block",
func: @lib.Func::new(locals, @lib.Expr::new(flattened_instrs)),
unchanged_original: false,
trace_skip_raw: true,
})
}
// Binaryen's nesting-enabled siblings all move a trailing constant after a
// result-loop nop and reserve the temporary local type, while nonesting
// deliberately retains the explicit carrier. This raw shape avoids the
// native stackifier recreating a larger local.set/nop/local.get sequence.
let (rewritten_instrs, added_locals, changed) = run_hot_pipeline_raw_simplify_locals_notee_reorder_const_nop(
instrs,
)
if changed {
let rewritten_locals = locals.copy()
rewritten_locals.append_types(added_locals)
return Some({
reason: "no-local-writes-const-nop",
func: @lib.Func::new(rewritten_locals, @lib.Expr::new(rewritten_instrs)),
unchanged_original: false,
trace_skip_raw: false,
})
}
return Some({
reason: "no-local-writes",
func: original,
unchanged_original: true,
trace_skip_raw: true,
})
}
let (root_forwarded, root_forwarded_changed) = run_hot_pipeline_simplify_locals_forward_root_structured_local_carriers_with_changed(
instrs,
)
if root_forwarded_changed {
let (cleaned, _) = run_hot_pipeline_simplify_locals_cleanup_exact_func(
@lib.Func::new(locals, @lib.Expr::new(root_forwarded)),
)
return Some({
reason: "root-structured-local-carrier",
func: cleaned,
unchanged_original: false,
trace_skip_raw: true,
})
}
let raw_const_nop_only = descriptor.name == "simplify-locals-notee" ||
descriptor.name == "simplify-locals-no-tee"
let no_tee_variant = raw_const_nop_only ||
descriptor.name == "simplify-locals-notee-nostructure"
if no_tee_variant {
let (self_copy_instrs, self_copy_changed) = run_hot_pipeline_raw_simplify_locals_rewrite_self_copy_read(
instrs, raw_const_nop_only,
)
if self_copy_changed {
return Some({
reason: "no-tee-self-copy-read",
func: @lib.Func::new(locals, @lib.Expr::new(self_copy_instrs)),
unchanged_original: false,
trace_skip_raw: false,
})
}
}
let has_root_terminator = instrs.any(fn(instr) {
run_hot_pipeline_raw_simplify_locals_instr_is_unconditional_terminator(
instr,
)
})
let adjacent_pair_instrs = if has_root_terminator {
run_hot_pipeline_simplify_locals_lowered_drop_dead_adjacent_local_set_get_pairs(
instrs,
)
} else {
instrs
}
if !physical_equal(adjacent_pair_instrs, instrs) {
return Some({
reason: "adjacent-local-set-get",
func: @lib.Func::new(locals, @lib.Expr::new(adjacent_pair_instrs)),
unchanged_original: false,
trace_skip_raw: false,
})
}
if descriptor.name == "simplify-locals" {
let (simd_rotate_instrs, simd_rotate_rewrites) = run_hot_pipeline_raw_simplify_locals_rewrite_split_i32x4_rotate_temporaries(
instrs,
stats~,
)
let (simd_carrier_instrs, simd_carrier_rewrites) = run_hot_pipeline_raw_simplify_locals_rewrite_commutative_simd_carriers(
simd_rotate_instrs,
stats~,
)
if simd_rotate_rewrites > 0 || simd_carrier_rewrites > 0 {
let (cleaned, _) = run_hot_pipeline_simplify_locals_cleanup_exact_func(
@lib.Func::new(locals, @lib.Expr::new(simd_carrier_instrs)),
)
return Some({
reason: "simd-temporaries",
func: cleaned,
unchanged_original: false,
trace_skip_raw: true,
})
}
}
let (i32_accumulator_instrs, i32_accumulator_changed) = run_hot_pipeline_raw_simplify_locals_rewrite_i32_add_accumulator_chain(
instrs,
)
if i32_accumulator_changed {
return Some({
reason: "straight-line-i32-add-accumulator",
func: @lib.Func::new(locals, @lib.Expr::new(i32_accumulator_instrs)),
unchanged_original: false,
trace_skip_raw: true,
})
}
if raw_const_nop_only {
if parameter_count + stats.locals_count >= 16 &&
stats.local_tee_count > 0 &&
run_hot_pipeline_raw_instrs_have_memory_store(instrs) {
return Some({
reason: "large-local-tee-memory-write-hazard-noop",
func: original,
unchanged_original: true,
trace_skip_raw: true,
})
}
return None
}
if run_hot_pipeline_simplify_locals_descriptor_is_nostructure(descriptor.name) {
let (unreachable_dead_set_instrs, unreachable_dead_set_rewrites) = run_hot_pipeline_raw_simplify_locals_remove_unreachable_dead_stack_sets(
instrs,
)
if unreachable_dead_set_rewrites > 0 {
return Some({
reason: "unreachable-dead-stack-set",
func: @lib.Func::new(
locals,
@lib.Expr::new(unreachable_dead_set_instrs),
),
unchanged_original: false,
trace_skip_raw: true,
})
}
}
let raw_nostructure_stack_hazard = run_hot_pipeline_simplify_locals_initial_should_skip_stack_hazard(
descriptor.name,
instrs,
)
let raw_simplify_locals_stack_global_hazard = descriptor.name ==
"simplify-locals" &&
(stats.local_set_count > 0 || stats.local_tee_count > 0) &&
stats.has_stack_effect_hazard &&
stats.has_global_state
let should_rewrite_ladder = if stats.locals_count < 16 ||
stats.locals_count > 512 {
false
} else if !stats.has_structured_control || stats.instr_count > 12000 {
false
} else {
let large_ladder = stats.locals_count >= 48 &&
stats.instr_count >= 160 &&
stats.if_count >= 16 &&
stats.local_set_count >= 16 &&
stats.local_get_count >= 32 &&
stats.exact_tail_count >= 12 &&
stats.exact_tail_count * 4 + 16 >= stats.if_count &&
stats.return_count <= stats.exact_tail_count + 8
let small_ladder = stats.locals_count >= 16 &&
stats.instr_count >= 80 &&
stats.if_count >= 8 &&
stats.local_set_count >= 8 &&
stats.local_get_count >= 16 &&
stats.exact_tail_count >= 4 &&
stats.exact_tail_count * 2 + 4 >= stats.if_count &&
stats.return_count <= stats.exact_tail_count + 4
let sparse_ladder = stats.locals_count >= 16 &&
stats.instr_count >= 80 &&
stats.if_count >= 12 &&
stats.local_set_count >= 10 &&
stats.local_get_count >= 24 &&
stats.call_count >= stats.if_count &&
stats.exact_tail_count >= 5 &&
stats.exact_tail_count * 3 + 1 >= stats.if_count &&
stats.return_count <= stats.if_count
large_ladder || small_ladder || sparse_ladder
}
let should_rewrite_straight_line_adjacent_local_tees = !stats.has_structured_control &&
stats.locals_count >= 512 &&
stats.instr_count >= 2048 &&
stats.local_set_count >= 1 &&
stats.local_set_count <= 16 &&
stats.local_get_count >= 1024 &&
stats.local_get_count >= stats.local_set_count * 64 &&
stats.call_count >= 768 &&
stats.return_count <= 1
let should_rewrite_straight_line_lane_builder = !stats.has_structured_control &&
stats.locals_count >= 48 &&
stats.locals_count <= 64 &&
stats.instr_count >= 240 &&
stats.instr_count <= 384 &&
stats.if_count == 0 &&
stats.block_count == 0 &&
stats.loop_count == 0 &&
stats.local_set_count >= 48 &&
stats.local_set_count <= 64 &&
stats.local_get_count >= 80 &&
stats.local_get_count <= 128 &&
stats.call_count >= 32 &&
stats.call_count <= 40 &&
stats.return_count == 0
let should_rewrite_pure_call_tails = !stats.has_structured_control &&
stats.locals_count >= 32 &&
stats.instr_count >= 128 &&
stats.local_set_count >= 16 &&
stats.local_get_count >= 32
let should_skip_straight_line_builder_churn = stats.locals_count >= 224 &&
stats.locals_count <= 320 &&
!stats.has_structured_control &&
stats.instr_count >= 768 &&
stats.if_count == 0 &&
stats.block_count == 0 &&
stats.loop_count == 0 &&
stats.local_set_count >= 144 &&
stats.local_set_count <= 192 &&
stats.local_get_count >= 384 &&
stats.local_get_count <= 704 &&
stats.local_get_count >= stats.local_set_count * 2 + 48 &&
stats.call_count >= 72 &&
stats.call_count <= 128 &&
stats.return_count == 0
let should_skip_multivalue_call_ladder = {
let sparse_call_ladder = stats.locals_count >= 16 &&
stats.locals_count <= 512 &&
stats.has_structured_control &&
stats.exact_tail_count == 0 &&
stats.instr_count >= 160 &&
stats.if_count >= 12 &&
stats.loop_count <= 2 &&
stats.local_set_count >= 6 &&
stats.local_get_count >= 48 &&
stats.local_get_count >= stats.local_set_count * 3 &&
stats.call_count >= 24 &&
stats.call_count >= stats.if_count &&
stats.return_count <= stats.if_count + 12
let compact_call_ladder = stats.locals_count >= 32 &&
stats.locals_count <= 64 &&
stats.has_structured_control &&
stats.exact_tail_count == 0 &&
stats.instr_count >= 192 &&
stats.if_count >= 10 &&
stats.if_count <= 24 &&
stats.block_count <= 2 &&
stats.loop_count == 0 &&
stats.local_set_count >= 12 &&
stats.local_get_count >= 64 &&
stats.local_get_count >= stats.local_set_count * 5 &&
stats.call_count >= 32 &&
stats.call_count >= stats.if_count * 3 &&
stats.return_count <= 4
let flat_dense_call_ladder = stats.locals_count >= 96 &&
stats.locals_count <= 192 &&
stats.has_structured_control &&
stats.exact_tail_count == 0 &&
stats.instr_count >= 480 &&
stats.if_count >= 32 &&
stats.block_count == 0 &&
stats.loop_count == 0 &&
stats.local_set_count >= 48 &&
stats.local_get_count >= 160 &&
stats.local_get_count >= stats.local_set_count * 2 + 32 &&
stats.call_count >= 48 &&
stats.call_count >= stats.if_count + 16 &&
stats.return_count <= stats.if_count
let loop_rich_call_ladder = stats.locals_count >= 160 &&
stats.locals_count <= 256 &&
stats.has_structured_control &&
stats.exact_tail_count == 0 &&
stats.instr_count >= 1000 &&
stats.if_count >= 48 &&
stats.block_count <= 4 &&
stats.loop_count <= 4 &&
stats.local_set_count >= 96 &&
stats.local_get_count >= 384 &&
stats.local_get_count >= stats.local_set_count * 3 + 64 &&
stats.call_count >= 192 &&
stats.call_count >= stats.if_count * 4 &&
stats.return_count <= stats.if_count
sparse_call_ladder ||
compact_call_ladder ||
flat_dense_call_ladder ||
loop_rich_call_ladder
}
let should_skip_validator_structured_call_heavy = stats.locals_count >= 256 &&
stats.has_structured_control &&
stats.instr_count >= 256 &&
stats.if_count >= 48 &&
stats.block_count >= 32 &&
stats.loop_count >= 4 &&
stats.local_set_count >= 128 &&
stats.local_get_count >= 384 &&
stats.call_count >= 192
let should_skip_parser_structured_call_heavy = stats.locals_count >= 96 &&
stats.locals_count <= 352 &&
stats.has_structured_control &&
stats.instr_count >= 160 &&
stats.if_count >= 16 &&
stats.if_count <= 64 &&
stats.block_count >= 4 &&
stats.block_count <= 20 &&
stats.loop_count <= 2 &&
stats.local_set_count >= 80 &&
stats.local_get_count >= 220 &&
stats.call_count >= 64 &&
stats.call_count <= 128 &&
stats.return_count <= stats.if_count
let should_skip_decode_structured_call_heavy = stats.locals_count >= 80 &&
stats.locals_count <= 128 &&
stats.has_structured_control &&
stats.instr_count >= 192 &&
stats.if_count >= 12 &&
stats.if_count <= 40 &&
stats.block_count >= 8 &&
stats.block_count <= 24 &&
stats.loop_count == 1 &&
stats.local_set_count >= 40 &&
stats.local_set_count <= 96 &&
stats.local_get_count >= 144 &&
stats.local_get_count <= 180 &&
stats.call_count >= 48 &&
stats.call_count <= 64 &&
stats.return_count <= 16
let should_skip_encode_structured_call_heavy = stats.locals_count >= 192 &&
stats.locals_count <= 256 &&
stats.has_structured_control &&
stats.instr_count >= 512 &&
stats.if_count >= 96 &&
stats.if_count <= 192 &&
stats.block_count >= 32 &&
stats.block_count <= 64 &&
stats.loop_count == 0 &&
stats.local_set_count >= 56 &&
stats.local_set_count <= 96 &&
stats.local_get_count >= 352 &&
stats.local_get_count <= 544 &&
stats.call_count >= 160 &&
stats.call_count <= 224 &&
stats.return_count >= 8 &&
stats.return_count <= 16
let should_skip_branchy_decode_structured_call_heavy = stats.locals_count >=
192 &&
stats.locals_count <= 224 &&
stats.has_structured_control &&
stats.instr_count >= 640 &&
stats.if_count >= 48 &&
stats.if_count <= 80 &&
stats.block_count >= 16 &&
stats.block_count <= 32 &&
stats.loop_count == 0 &&
stats.local_set_count >= 48 &&
stats.local_set_count <= 96 &&
stats.local_get_count >= 240 &&
stats.local_get_count <= 384 &&
stats.call_count >= 80 &&
stats.call_count <= 128 &&
stats.return_count >= 4 &&
stats.return_count <= 12
let should_skip_transformer_catch_structured_call_heavy = stats.locals_count >=
184 &&
stats.locals_count <= 224 &&
stats.has_structured_control &&
stats.instr_count >= 512 &&
stats.if_count >= 32 &&
stats.if_count <= 64 &&
stats.block_count >= 24 &&
stats.block_count <= 40 &&
stats.loop_count == 0 &&
stats.local_set_count >= 72 &&
stats.local_set_count <= 96 &&
stats.local_get_count >= 256 &&
stats.local_get_count <= 352 &&
stats.call_count >= 80 &&
stats.call_count <= 112 &&
stats.return_count >= 8 &&
stats.return_count <= 16
let should_skip_branchy_structured_call_heavy = stats.locals_count >= 176 &&
stats.locals_count <= 224 &&
stats.has_structured_control &&
stats.instr_count >= 768 &&
stats.if_count >= 48 &&
stats.if_count <= 64 &&
stats.block_count >= 24 &&
stats.block_count <= 40 &&
stats.loop_count == 0 &&
stats.local_set_count >= 64 &&
stats.local_set_count <= 80 &&
stats.local_get_count >= 280 &&
stats.local_get_count <= 352 &&
stats.call_count >= 96 &&
stats.call_count <= 144 &&
stats.return_count >= 4 &&
stats.return_count <= 8
let should_skip_dense_structured_call_heavy = run_hot_pipeline_raw_simplify_locals_dense_structured_call_heavy_stats(
stats,
)
let should_skip_stringview_trim_loop_churn = stats.locals_count >= 34 &&
stats.locals_count <= 40 &&
stats.has_structured_control &&
stats.instr_count >= 64 &&
stats.if_count >= 2 &&
stats.if_count <= 3 &&
stats.block_count == 0 &&
stats.loop_count == 1 &&
stats.local_set_count >= 8 &&
stats.local_set_count <= 12 &&
stats.local_get_count >= 32 &&
stats.local_get_count <= 52 &&
stats.call_count >= 12 &&
stats.call_count <= 18 &&
stats.return_count == 0
let should_skip_transformer_structured_call_heavy = stats.locals_count >= 84 &&
stats.locals_count <= 96 &&
stats.has_structured_control &&
stats.instr_count >= 160 &&
stats.if_count >= 18 &&
stats.if_count <= 28 &&
stats.block_count >= 12 &&
stats.block_count <= 20 &&
stats.loop_count == 0 &&
stats.local_set_count >= 32 &&
stats.local_set_count <= 48 &&
stats.local_get_count >= 140 &&
stats.local_get_count <= 180 &&
stats.call_count >= 48 &&
stats.call_count <= 64 &&
stats.return_count <= 8
let should_skip_small_loop_call_heavy = stats.locals_count >= 80 &&
stats.locals_count <= 90 &&
stats.has_structured_control &&
stats.instr_count >= 160 &&
stats.if_count >= 12 &&
stats.if_count <= 20 &&
stats.block_count == 0 &&
stats.loop_count == 2 &&
stats.local_set_count >= 72 &&
stats.local_set_count <= 84 &&
stats.local_get_count >= 100 &&
stats.local_get_count <= 192 &&
stats.call_count >= 24 &&
stats.call_count <= 48 &&
stats.return_count <= 4
let should_skip_huge_straight_line_call_builder = stats.locals_count >= 768 &&
!stats.has_structured_control &&
stats.instr_count >= 2048 &&
stats.if_count == 0 &&
stats.block_count == 0 &&
stats.loop_count == 0 &&
stats.local_set_count <= 16 &&
stats.local_get_count >= 1536 &&
stats.call_count >= 1024 &&
stats.return_count == 0
let should_skip_medium_structured_call_heavy = stats.locals_count >= 176 &&
stats.locals_count <= 320 &&
stats.has_structured_control &&
stats.instr_count >= 224 &&
stats.if_count >= 24 &&
stats.if_count <= 96 &&
stats.loop_count <= 3 &&
stats.local_set_count >= 96 &&
stats.local_get_count >= 256 &&
stats.call_count >= 128 &&
stats.call_count <= 224 &&
stats.return_count <= stats.if_count
let should_skip_loop_heavy_structured_call_heavy = stats.locals_count >= 176 &&
stats.locals_count <= 320 &&
stats.has_structured_control &&
stats.instr_count >= 256 &&
stats.if_count >= 28 &&
stats.if_count <= 80 &&
stats.block_count >= 8 &&
stats.loop_count >= 4 &&
stats.local_set_count >= 80 &&
stats.local_get_count >= 200 &&
stats.call_count >= 80 &&
stats.call_count <= 224 &&
stats.return_count <= stats.if_count
let should_skip_giant_structured_call_heavy = stats.locals_count >= 352 &&
stats.has_structured_control &&
stats.instr_count >= 256 &&
stats.if_count >= 80 &&
stats.local_set_count >= 176 &&
stats.local_get_count >= 384 &&
stats.call_count >= 240
let should_skip_giant_structured_local_churn = stats.locals_count >= 600 &&
stats.has_structured_control &&
stats.instr_count >= 1500 &&
stats.if_count >= 96 &&
stats.local_set_count >= 384 &&
stats.local_get_count >= 768 &&
stats.call_count >= 16 &&
stats.call_count <= 96 &&
stats.return_count <= 4
let should_skip_unchecked_small_structured_call_mesh = !options.validate_final_module &&
run_hot_pipeline_raw_simplify_locals_unchecked_small_structured_call_mesh_stats(
stats,
)
let should_skip_giant_validator_structured_call_heavy = run_hot_pipeline_raw_simplify_locals_giant_validator_structured_call_heavy_stats(
stats,
)
let should_skip_branch_dense_structured_call_heavy = run_hot_pipeline_raw_simplify_locals_branch_dense_structured_call_heavy_stats(
stats,
)
let should_skip_block_rich_structured_call_heavy = run_hot_pipeline_raw_simplify_locals_block_rich_structured_call_heavy_stats(
stats,
)
let should_skip_call_dense_structured_walker = run_hot_pipeline_raw_simplify_locals_call_dense_structured_walker_stats(
stats,
)
let should_skip_low_local_decision_ladder = run_hot_pipeline_raw_simplify_locals_low_local_decision_ladder_stats(
stats,
)
let should_skip_low_local_loop_return_call_ladder = run_hot_pipeline_raw_simplify_locals_low_local_loop_return_call_ladder_stats(
stats,
)
let should_skip_mid_local_structured_call_ladder = run_hot_pipeline_raw_simplify_locals_mid_local_structured_call_ladder_stats(
stats,
)
let should_skip_low_local_structured_call_heavy = stats.locals_count <= 8 &&
stats.has_structured_control &&
stats.if_count >= 128
let should_skip_giant_func = run_hot_pipeline_raw_simplify_locals_giant_func_stats(
stats,
)
let should_rewrite_structured_pure_call_tails = stats.has_structured_control &&
stats.locals_count >= 128 &&
stats.instr_count >= 256 &&
stats.local_set_count >= 24 &&
stats.local_get_count >= 48
let should_skip_root_local_set_stack_hazard = (
raw_nostructure_stack_hazard &&
(stats.locals_count >= 16 || stats.instr_count >= 128)
) ||
(
raw_simplify_locals_stack_global_hazard &&
(stats.locals_count >= 64 || stats.instr_count >= 512)
)
if should_skip_root_local_set_stack_hazard {
return Some({
reason: "root-local-set-stack-hazard-noop",
func: original,
unchanged_original: true,
trace_skip_raw: true,
})
}
if !should_rewrite_ladder && should_rewrite_straight_line_adjacent_local_tees {
let (rewritten_instrs, rewrite_count) = run_hot_pipeline_raw_simplify_locals_rewrite_adjacent_local_tees(
instrs,
)
if rewrite_count > 0 {
return Some({
reason: "straight-line-adjacent-local-tee",
func: @lib.Func::new(locals, @lib.Expr::new(rewritten_instrs)),
unchanged_original: false,
trace_skip_raw: true,
})
}
}
if !should_rewrite_ladder && should_rewrite_straight_line_lane_builder {
let (rewritten_instrs, rewrite_count) = run_hot_pipeline_raw_simplify_locals_rewrite_straight_line_lane_builder(
instrs,
)
if rewrite_count > 0 {
return Some({
reason: "straight-line-lane-builder",
func: @lib.Func::new(locals, @lib.Expr::new(rewritten_instrs)),
unchanged_original: false,
trace_skip_raw: true,
})
}
}
if should_rewrite_pure_call_tails {
let (rewritten_instrs, rewrite_count) = run_hot_pipeline_raw_simplify_locals_rewrite_pure_call_tails(
instrs,
)
if rewrite_count > 0 {
return Some({
reason: "straight-line-pure-copy-call-tail",
func: @lib.Func::new(locals, @lib.Expr::new(rewritten_instrs)),
unchanged_original: false,
trace_skip_raw: true,
})
}
}
if !should_rewrite_ladder {
if should_skip_straight_line_builder_churn {
return Some({
reason: "straight-line-builder-churn",
func: original,
unchanged_original: true,
trace_skip_raw: true,
})
}
if should_skip_huge_straight_line_call_builder {
return Some({
reason: "huge-straight-line-call-builder",
func: original,
unchanged_original: true,
trace_skip_raw: true,
})
}
if run_hot_pipeline_raw_simplify_locals_has_i32_pair_result(
mod_, func_idx, module_state, options,
) &&
should_skip_multivalue_call_ladder {
return Some({
reason: "multivalue-call-heavy-ladder",
func: original,
unchanged_original: true,
trace_skip_raw: true,
})
}
if should_skip_unchecked_small_structured_call_mesh {
return Some({
reason: "unchecked-small-structured-call-mesh-noop",
func: original,
unchanged_original: true,
trace_skip_raw: true,
})
}
if should_skip_giant_validator_structured_call_heavy {
return Some({
reason: "giant-validator-structured-call-heavy-noop",
func: original,
unchanged_original: true,
trace_skip_raw: true,
})
}
if should_skip_validator_structured_call_heavy {
let module_ctx = run_hot_pipeline_require_module_ctx(
mod_, module_state, options,
)
let env = run_hot_pipeline_raw_simplify_locals_func_env(
mod_,
func_idx,
module_ctx,
locals,
module_env=Some(
run_hot_pipeline_require_validation_module_env(
mod_, module_state, options,
),
),
)
let (pure_call_tail_instrs, pure_call_tail_rewrites) = run_hot_pipeline_raw_simplify_locals_rewrite_pure_call_tails_nested_fixpoint(
instrs,
)
let (pure_suffix_instrs, pure_suffix_rewrites) = run_hot_pipeline_raw_simplify_locals_rewrite_pure_suffix_local_sets_fixpoint(
pure_call_tail_instrs, env,
)
let (carrier_instrs, carrier_rewrites) = run_hot_pipeline_raw_simplify_locals_rewrite_control_carrier_local_gets_fixpoint(
pure_suffix_instrs,
)
let (rewritten_instrs, rewrite_count) = run_hot_pipeline_raw_simplify_locals_rewrite_effectful_suffix_local_gets_fixpoint(
carrier_instrs, env,
)
let total_rewrites = pure_call_tail_rewrites +
pure_suffix_rewrites +
carrier_rewrites +
rewrite_count
let rewritten_func = if total_rewrites > 0 {
@lib.Func::new(locals, @lib.Expr::new(rewritten_instrs))
} else {
original
}
let (cleaned, changed) = run_hot_pipeline_simplify_locals_cleanup_exact_func(
rewritten_func,
)
return Some({
reason: "validator-structured-call-heavy",
func: cleaned,
unchanged_original: total_rewrites == 0 && !changed,
trace_skip_raw: true,
})
}
if should_skip_branch_dense_structured_call_heavy {
return Some({
reason: "branch-dense-structured-call-heavy-noop",
func: original,
unchanged_original: true,
trace_skip_raw: true,
})
}
if should_skip_low_local_decision_ladder {
return Some({
reason: "low-local-decision-ladder-noop",
func: original,
unchanged_original: true,
trace_skip_raw: true,
})
}
if should_skip_low_local_loop_return_call_ladder {
return Some({
reason: "low-local-loop-return-call-ladder-noop",
func: original,
unchanged_original: true,
trace_skip_raw: true,
})
}
if should_skip_mid_local_structured_call_ladder {
return Some({
reason: "mid-local-structured-call-ladder-noop",
func: original,
unchanged_original: true,
trace_skip_raw: true,
})
}
if should_skip_low_local_structured_call_heavy {
return Some({
reason: "low-local-structured-call-heavy-noop",
func: original,
unchanged_original: true,
trace_skip_raw: true,
})
}
if should_skip_parser_structured_call_heavy {
return Some({
reason: "parser-structured-call-heavy",
func: original,
unchanged_original: true,
trace_skip_raw: true,
})
}
if should_skip_decode_structured_call_heavy {
return Some({
reason: "decode-structured-call-heavy",
func: original,
unchanged_original: true,
trace_skip_raw: true,
})
}
if should_skip_encode_structured_call_heavy {
return Some({
reason: "encode-structured-call-heavy",
func: original,
unchanged_original: true,
trace_skip_raw: true,
})
}
if should_skip_branchy_decode_structured_call_heavy {
return Some({
reason: "branchy-decode-structured-call-heavy",
func: original,
unchanged_original: true,
trace_skip_raw: true,
})
}
if should_skip_transformer_catch_structured_call_heavy {
return Some({
reason: "transformer-catch-structured-call-heavy",
func: original,
unchanged_original: true,
trace_skip_raw: true,
})
}
if should_skip_branchy_structured_call_heavy {
return Some({
reason: "branchy-structured-call-heavy",
func: original,
unchanged_original: true,
trace_skip_raw: true,
})
}
if should_skip_dense_structured_call_heavy {
return Some({
reason: "dense-structured-call-heavy",
func: original,
unchanged_original: true,
trace_skip_raw: true,
})
}
if should_skip_stringview_trim_loop_churn {
return Some({
reason: "stringview-trim-loop-churn",
func: original,
unchanged_original: true,
trace_skip_raw: true,
})
}
if should_skip_transformer_structured_call_heavy {
return Some({
reason: "transformer-structured-call-heavy",
func: original,
unchanged_original: true,
trace_skip_raw: true,
})
}
if should_skip_small_loop_call_heavy {
return Some({
reason: "small-loop-call-heavy",
func: original,
unchanged_original: true,
trace_skip_raw: true,
})
}
if should_skip_block_rich_structured_call_heavy {
return Some({
reason: "block-rich-structured-call-heavy-noop",
func: original,
unchanged_original: true,
trace_skip_raw: true,
})
}
if should_skip_call_dense_structured_walker {
return Some({
reason: "call-dense-structured-walker-noop",
func: original,
unchanged_original: true,
trace_skip_raw: true,
})
}
if should_skip_medium_structured_call_heavy {
return Some({
reason: "medium-structured-call-heavy",
func: original,
unchanged_original: true,
trace_skip_raw: true,
})
}
if should_skip_loop_heavy_structured_call_heavy {
return Some({
reason: "loop-heavy-structured-call-heavy",
func: original,
unchanged_original: true,
trace_skip_raw: true,
})
}
if should_skip_giant_structured_call_heavy {
match
run_hot_pipeline_raw_simplify_locals_rewrite_skipped_effectful_carriers(
mod_, func_idx, module_state, descriptor, options, locals, instrs, "giant-structured-call-heavy-carrier",
) {
Some(result) => return Some(result)
None => ()
}
return Some({
reason: "giant-structured-call-heavy",
func: original,
unchanged_original: true,
trace_skip_raw: true,
})
}
if should_skip_giant_structured_local_churn {
match
run_hot_pipeline_raw_simplify_locals_rewrite_skipped_effectful_carriers(
mod_, func_idx, module_state, descriptor, options, locals, instrs, "giant-structured-local-churn-carrier",
) {
Some(result) => return Some(result)
None => ()
}
return Some({
reason: "giant-structured-local-churn",
func: original,
unchanged_original: true,
trace_skip_raw: true,
})
}
if should_skip_giant_func {
return Some({
reason: "giant-local-func-noop",
func: original,
unchanged_original: true,
trace_skip_raw: true,
})
}
if should_rewrite_structured_pure_call_tails {
let module_ctx = run_hot_pipeline_require_module_ctx(
mod_, module_state, options,
)
let env = run_hot_pipeline_raw_simplify_locals_func_env(
mod_,
func_idx,
module_ctx,
locals,
module_env=Some(
run_hot_pipeline_require_validation_module_env(
mod_, module_state, options,
),
),
)
let params = run_hot_pipeline_required_raw_func_params(
mod_, module_ctx, func_idx, module_state,
)
let (rewritten_instrs, rewrite_count) = run_hot_pipeline_raw_simplify_locals_rewrite_structured_copy_call_tails_bounded(
instrs,
env,
params.length() + locals.length(),
)
if rewrite_count >= 16 {
return Some({
reason: "structured-pure-copy-call-tail-bounded",
func: @lib.Func::new(locals, @lib.Expr::new(rewritten_instrs)),
unchanged_original: false,
trace_skip_raw: true,
})
}
}
if descriptor.name == "simplify-locals" ||
descriptor.name == "simplify-locals-notee-nostructure" {
match
run_hot_pipeline_raw_simplify_locals_full_lifetime_guard(
original, instrs,
) {
Some(result) => return Some(result)
None => ()
}
}
if stats.locals_count >= 8 &&
stats.locals_count <= 64 &&
stats.has_structured_control &&
stats.local_tee_count > 0 {
match
run_hot_pipeline_raw_simplify_locals_rewrite_skipped_effectful_carriers(
mod_, func_idx, module_state, descriptor, options, locals, instrs, "structured-local-tee-carrier",
) {
Some(result) => return Some(result)
None => ()
}
}
match
run_hot_pipeline_raw_simplify_locals_convergence_guard(
descriptor, original, instrs, stats, should_rewrite_ladder,
) {
Some(result) => return Some(result)
None => return None
}
}
if descriptor.name == "simplify-locals" ||
descriptor.name == "simplify-locals-notee-nostructure" {
match
run_hot_pipeline_raw_simplify_locals_full_lifetime_guard(original, instrs) {
Some(result) => return Some(result)
None => ()
}
}
if stats.locals_count >= 8 &&
stats.locals_count <= 64 &&
stats.has_structured_control &&
stats.local_tee_count > 0 {
match
run_hot_pipeline_raw_simplify_locals_rewrite_skipped_effectful_carriers(
mod_, func_idx, module_state, descriptor, options, locals, instrs, "structured-local-tee-carrier",
) {
Some(result) => return Some(result)
None => ()
}
}
match
run_hot_pipeline_raw_simplify_locals_convergence_guard(
descriptor, original, instrs, stats, should_rewrite_ladder,
) {
Some(result) => return Some(result)
None => ()
}
let module_ctx = run_hot_pipeline_require_module_ctx(
mod_, module_state, options,
)
let env = run_hot_pipeline_raw_simplify_locals_func_env(
mod_,
func_idx,
module_ctx,
locals,
module_env=Some(
run_hot_pipeline_require_validation_module_env(
mod_, module_state, options,
),
),
)
let (rewritten_instrs, rewrite_count) = run_hot_pipeline_raw_simplify_locals_rewrite_instrs(
instrs, env,
)
if rewrite_count == 0 {
return None
}
Some({
reason: "multivalue-tail-local-get-ladder",
func: @lib.Func::new(locals, @lib.Expr::new(rewritten_instrs)),
unchanged_original: false,
trace_skip_raw: true,
})
}
///|
fn run_hot_pipeline_dce_raw_block_result_arity(
block_type : @lib.BlockType,
module_ctx : @ir.HotModuleContext,
) -> Int32 {
match block_type {
@lib.VoidBlockType => 0
@lib.ValTypeBlockType(_) => 1
@lib.TypeIdxBlockType(type_idx) =>
match @ir.hot_module_resolve_type_func_type(module_ctx, type_idx) {
Some(@lib.FuncType(_, results)) => results.length()
None => 0
}
}
}
///|
fn run_hot_pipeline_dce_raw_instr_is_local_carrier_write(
instr : @lib.Instruction,
) -> Bool {
match instr {
@lib.LocalSet(_) | @lib.LocalTee(_) => true
_ => false
}
}
///|
fn run_hot_pipeline_dce_raw_has_multivalue_local_carrier_hazard(
instrs : Array[@lib.Instruction],
module_ctx : @ir.HotModuleContext,
) -> Bool {
for idx = 0; idx < instrs.length(); idx = idx + 1 {
let result_arity = match instrs[idx] {
@lib.Block(block_type, _)
| @lib.Loop(block_type, _)
| @lib.If(block_type, _, _)
| @lib.TryTable(block_type, _, _) =>
run_hot_pipeline_dce_raw_block_result_arity(block_type, module_ctx)
_ => 0
}
if result_arity > 1 {
let mut carrier_writes = 0
let mut next_idx = idx + 1
while next_idx < instrs.length() &&
run_hot_pipeline_dce_raw_instr_is_local_carrier_write(
instrs[next_idx],
) {
carrier_writes += 1
next_idx += 1
}
if carrier_writes >= result_arity {
return true
}
}
match instrs[idx] {
@lib.Block(_, @lib.Expr(body))
| @lib.Loop(_, @lib.Expr(body))
| @lib.TryTable(_, _, @lib.Expr(body)) =>
if run_hot_pipeline_dce_raw_has_multivalue_local_carrier_hazard(
body, module_ctx,
) {
return true
}
@lib.If(_, then_body, else_body) => {
if run_hot_pipeline_dce_raw_has_multivalue_local_carrier_hazard(
then_body, module_ctx,
) {
return true
}
match else_body {
Some(else_body) =>
if run_hot_pipeline_dce_raw_has_multivalue_local_carrier_hazard(
else_body, module_ctx,
) {
return true
}
None => ()
}
}
_ => ()
}
}
false
}
///|
fn run_hot_pipeline_dce_raw_instr_is_call(instr : @lib.Instruction) -> Bool {
match instr {
@lib.Call(_) | @lib.CallIndirect(_, _) | @lib.CallRef(_) => true
_ => false
}
}
///|
fn run_hot_pipeline_dce_raw_instr_static_result_arity(
instr : @lib.Instruction,
module_ctx : @ir.HotModuleContext,
) -> Int32 {
match instr {
@lib.I32Const(_)
| @lib.I64Const(_)
| @lib.F32Const(_)
| @lib.F64Const(_)
| @lib.LocalGet(_)
| @lib.LocalTee(_)
| @lib.GlobalGet(_)
| @lib.TableGet(_)
| @lib.I32Load(_)
| @lib.I64Load(_)
| @lib.F32Load(_)
| @lib.F64Load(_)
| @lib.I32Load8S(_)
| @lib.I32Load8U(_)
| @lib.I32Load16S(_)
| @lib.I32Load16U(_)
| @lib.I64Load8S(_)
| @lib.I64Load8U(_)
| @lib.I64Load16S(_)
| @lib.I64Load16U(_)
| @lib.I64Load32S(_)
| @lib.I64Load32U(_)
| @lib.MemorySize(_)
| @lib.MemoryGrow(_) => 1
@lib.Call(func_idx) =>
match @ir.hot_module_resolve_func_type(module_ctx, func_idx) {
Some(@lib.FuncType(_, results)) => results.length()
None => 0
}
@lib.CallIndirect(type_idx, _) | @lib.CallRef(type_idx) =>
match @ir.hot_module_resolve_type_func_type(module_ctx, type_idx) {
Some(@lib.FuncType(_, results)) => results.length()
None => 0
}
@lib.Block(block_type, _)
| @lib.Loop(block_type, _)
| @lib.TryTable(block_type, _, _) =>
match block_type {
@lib.ValTypeBlockType(_) => 1
@lib.TypeIdxBlockType(_) => 1
@lib.VoidBlockType => 0
}
@lib.If(block_type, _, _) =>
match block_type {
@lib.ValTypeBlockType(_) => 1
@lib.TypeIdxBlockType(_) => 1
@lib.VoidBlockType => 0
}
_ => 0
}
}
///|
fn run_hot_pipeline_dce_raw_preserve_prefix_before_unreachable_child_instrs(
instrs : Array[@lib.Instruction],
module_ctx : @ir.HotModuleContext,
) -> (Array[@lib.Instruction], Bool) {
let normalized : Array[@lib.Instruction] = []
let mut changed = false
for instr in instrs {
match instr {
@lib.Block(block_type, @lib.Expr(body)) => {
let (rewritten_body, body_changed) = run_hot_pipeline_dce_raw_preserve_prefix_before_unreachable_child_instrs(
body, module_ctx,
)
normalized.push(
@lib.Instruction::block(block_type, @lib.Expr::new(rewritten_body)),
)
changed = changed || body_changed
}
@lib.Loop(block_type, @lib.Expr(body)) => {
let (rewritten_body, body_changed) = run_hot_pipeline_dce_raw_preserve_prefix_before_unreachable_child_instrs(
body, module_ctx,
)
normalized.push(
@lib.Instruction::loop_(block_type, @lib.Expr::new(rewritten_body)),
)
changed = changed || body_changed
}
@lib.TryTable(block_type, catches, @lib.Expr(body)) => {
let (rewritten_body, body_changed) = run_hot_pipeline_dce_raw_preserve_prefix_before_unreachable_child_instrs(
body, module_ctx,
)
normalized.push(
@lib.Instruction::try_table(
block_type,
catches,
@lib.Expr::new(rewritten_body),
),
)
changed = changed || body_changed
}
@lib.If(block_type, then_body, else_body) => {
let (rewritten_then, then_changed) = run_hot_pipeline_dce_raw_preserve_prefix_before_unreachable_child_instrs(
then_body, module_ctx,
)
let rewritten_else = match else_body {
Some(else_body) => {
let (rewritten_else, else_changed) = run_hot_pipeline_dce_raw_preserve_prefix_before_unreachable_child_instrs(
else_body, module_ctx,
)
changed = changed || else_changed
Some(rewritten_else)
}
None => None
}
normalized.push(
@lib.Instruction::if_(block_type, rewritten_then, rewritten_else),
)
changed = changed || then_changed
}
_ => normalized.push(instr)
}
}
let rewritten : Array[@lib.Instruction] = []
let mut prev_may_fallthrough = true
for idx = 0; idx < normalized.length(); idx = idx + 1 {
if !prev_may_fallthrough {
changed = true
break
}
let instr = normalized[idx]
match instr {
@lib.Unreachable if idx > 0 => {
let prev = normalized[idx - 1]
if run_hot_pipeline_dce_raw_instr_static_result_arity(prev, module_ctx) ==
1 {
rewritten.push(@lib.Instruction::drop())
changed = true
}
rewritten.push(instr)
}
_ => rewritten.push(instr)
}
prev_may_fallthrough = run_hot_pipeline_dce_raw_instr_may_fallthrough(instr)
}
(rewritten, changed)
}
///|
fn run_hot_pipeline_dce_raw_preserve_prefix_before_unreachable_child_func(
original : @lib.Func,
module_ctx : @ir.HotModuleContext,
) -> @lib.Func? {
let @lib.Func(locals, @lib.Expr(instrs)) = original
let (rewritten_instrs, changed) = run_hot_pipeline_dce_raw_preserve_prefix_before_unreachable_child_instrs(
instrs, module_ctx,
)
if !changed {
return None
}
Some(@lib.Func::new(locals.copy(), @lib.Expr::new(rewritten_instrs)))
}
///|
fn run_hot_pipeline_dce_raw_trim_try_table_body_suffix(
instrs : Array[@lib.Instruction],
) -> (Array[@lib.Instruction], Bool) {
let out : Array[@lib.Instruction] = []
let mut changed = false
for idx = 0; idx < instrs.length(); idx = idx + 1 {
let instr = instrs[idx]
out.push(instr)
if !run_hot_pipeline_dce_raw_instr_may_fallthrough(instr) {
if idx + 1 < instrs.length() {
changed = true
}
break
}
}
(out, changed)
}
///|
fn run_hot_pipeline_dce_raw_void_try_table_type_if_nonfallthrough(
block_type : @lib.BlockType,
body : Array[@lib.Instruction],
) -> (@lib.BlockType, Bool) {
if run_hot_pipeline_dce_raw_body_may_fallthrough(body) {
return (block_type, false)
}
match block_type {
@lib.ValTypeBlockType(_) => (@lib.BlockType::void_(), true)
_ => (block_type, false)
}
}
///|
fn run_hot_pipeline_dce_raw_instr_is_result_control(
instr : @lib.Instruction,
) -> Bool {
match instr {
@lib.Block(block_type, _)
| @lib.Loop(block_type, _)
| @lib.If(block_type, _, _)
| @lib.TryTable(block_type, _, _) =>
match block_type {
@lib.VoidBlockType => false
_ => true
}
_ => false
}
}
///|
fn run_hot_pipeline_dce_raw_body_ends_with_result_control(
instrs : Array[@lib.Instruction],
) -> Bool {
match instrs.last() {
Some(instr) => run_hot_pipeline_dce_raw_instr_is_result_control(instr)
None => false
}
}
///|
fn run_hot_pipeline_dce_raw_has_result_control_tail_hazard(
instrs : Array[@lib.Instruction],
) -> Bool {
for idx = 0; idx < instrs.length(); idx = idx + 1 {
if idx + 1 < instrs.length() {
match (instrs[idx], instrs[idx + 1]) {
(@lib.If(block_type, then_body, Some(else_body)), @lib.Br(_)) =>
match block_type {
@lib.VoidBlockType => ()
_ =>
if !run_hot_pipeline_dce_raw_body_may_fallthrough(then_body) &&
!run_hot_pipeline_dce_raw_body_may_fallthrough(else_body) &&
(
run_hot_pipeline_dce_raw_body_ends_with_result_control(
then_body,
) ||
run_hot_pipeline_dce_raw_body_ends_with_result_control(
else_body,
)
) {
return true
}
}
_ => ()
}
}
match instrs[idx] {
@lib.Block(_, @lib.Expr(body))
| @lib.Loop(_, @lib.Expr(body))
| @lib.TryTable(_, _, @lib.Expr(body)) =>
if run_hot_pipeline_dce_raw_has_result_control_tail_hazard(body) {
return true
}
@lib.If(_, then_body, else_body) => {
if run_hot_pipeline_dce_raw_has_result_control_tail_hazard(then_body) {
return true
}
match else_body {
Some(else_body) =>
if run_hot_pipeline_dce_raw_has_result_control_tail_hazard(
else_body,
) {
return true
}
None => ()
}
}
_ => ()
}
}
false
}
///|
fn run_hot_pipeline_dce_raw_cleanup_stack_polymorphic_instrs(
instrs : Array[@lib.Instruction],
) -> (Array[@lib.Instruction], Bool) {
let normalized : Array[@lib.Instruction] = []
let mut changed = false
for instr in instrs {
match instr {
@lib.Block(block_type, @lib.Expr(body)) => {
let (rewritten_body, body_changed) = run_hot_pipeline_dce_raw_cleanup_stack_polymorphic_instrs(
body,
)
normalized.push(
@lib.Instruction::block(block_type, @lib.Expr::new(rewritten_body)),
)
changed = changed || body_changed
}
@lib.Loop(block_type, @lib.Expr(body)) => {
let (rewritten_body, body_changed) = run_hot_pipeline_dce_raw_cleanup_stack_polymorphic_instrs(
body,
)
normalized.push(
@lib.Instruction::loop_(block_type, @lib.Expr::new(rewritten_body)),
)
changed = changed || body_changed
}
@lib.If(block_type, then_body, else_body) => {
let (rewritten_then, then_changed) = run_hot_pipeline_dce_raw_cleanup_stack_polymorphic_instrs(
then_body,
)
let rewritten_else = match else_body {
Some(else_body) => {
let (rewritten_else, else_changed) = run_hot_pipeline_dce_raw_cleanup_stack_polymorphic_instrs(
else_body,
)
changed = changed || else_changed
Some(rewritten_else)
}
None => None
}
normalized.push(
@lib.Instruction::if_(block_type, rewritten_then, rewritten_else),
)
changed = changed || then_changed
}
@lib.TryTable(block_type, catches, @lib.Expr(body)) => {
let (rewritten_body, body_changed) = run_hot_pipeline_dce_raw_cleanup_stack_polymorphic_instrs(
body,
)
let (trimmed_body, trim_changed) = run_hot_pipeline_dce_raw_trim_try_table_body_suffix(
rewritten_body,
)
let (rewritten_type, type_changed) = run_hot_pipeline_dce_raw_void_try_table_type_if_nonfallthrough(
block_type, trimmed_body,
)
normalized.push(
@lib.Instruction::try_table(
rewritten_type,
catches,
@lib.Expr::new(trimmed_body),
),
)
changed = changed || body_changed || trim_changed || type_changed
}
_ => normalized.push(instr)
}
}
let rewritten : Array[@lib.Instruction] = []
let mut idx = 0
while idx < normalized.length() {
if idx + 1 < normalized.length() {
match (normalized[idx], normalized[idx + 1]) {
(@lib.Unreachable, @lib.If(_, _, _)) => {
rewritten.push(@lib.Instruction::unreachable_())
changed = true
break
}
(@lib.If(_, then_body, Some(else_body)), @lib.Br(_)) =>
if !run_hot_pipeline_dce_raw_body_may_fallthrough(then_body) &&
!run_hot_pipeline_dce_raw_body_may_fallthrough(else_body) {
rewritten.push(
@lib.Instruction::if_(
@lib.BlockType::void_(),
then_body,
Some(else_body),
),
)
rewritten.push(@lib.Instruction::unreachable_())
changed = true
break
}
_ => ()
}
}
match normalized[idx] {
@lib.TryTable(@lib.VoidBlockType, _, @lib.Expr(body)) =>
if !run_hot_pipeline_dce_raw_body_may_fallthrough(body) &&
!run_hot_pipeline_dce_raw_instrs_branch_to_depth(body, 0) {
rewritten.push(normalized[idx])
if idx + 1 < normalized.length() {
match normalized[idx + 1] {
@lib.Unreachable => {
rewritten.push(normalized[idx + 1])
idx = idx + 1
}
_ => {
rewritten.push(@lib.Instruction::unreachable_())
changed = true
}
}
if idx + 1 < normalized.length() {
changed = true
}
} else {
rewritten.push(@lib.Instruction::unreachable_())
changed = true
}
break
} else {
rewritten.push(normalized[idx])
}
_ => rewritten.push(normalized[idx])
}
idx = idx + 1
}
(rewritten, changed)
}
///|
fn run_hot_pipeline_dce_raw_cleanup_stack_polymorphic_func(
original : @lib.Func,
) -> @lib.Func? {
let @lib.Func(locals, @lib.Expr(instrs)) = original
let (rewritten_instrs, changed) = run_hot_pipeline_dce_raw_cleanup_stack_polymorphic_instrs(
instrs,
)
if !changed {
return None
}
Some(@lib.Func::new(locals.copy(), @lib.Expr::new(rewritten_instrs)))
}
///|
fn run_hot_pipeline_dce_raw_has_call_result_multi_call_lifetime(
instrs : Array[@lib.Instruction],
) -> Bool {
for idx = 0; idx + 1 < instrs.length(); idx = idx + 1 {
let local_idx = match instrs[idx + 1] {
@lib.LocalSet(local_idx) if run_hot_pipeline_dce_raw_instr_is_call(
instrs[idx],
) => local_idx
_ => continue
}
let mut first_call : @lib.FuncIdx? = None
let mut saw_indirect_call = false
let scan_end = Int32::min(instrs.length(), idx + 34)
for scan = idx + 2; scan < scan_end; scan = scan + 1 {
match instrs[scan] {
@lib.LocalGet(candidate) if candidate == local_idx => {
let look_end = Int32::min(scan_end, scan + 12)
for look = scan + 1; look < look_end; look = look + 1 {
match instrs[look] {
@lib.Call(call_target) => {
if saw_indirect_call {
return true
}
match first_call {
Some(first_target) if first_target != call_target =>
return true
None => first_call = Some(call_target)
_ => ()
}
break
}
@lib.CallIndirect(_, _) | @lib.CallRef(_) => {
match first_call {
Some(_) => return true
None => ()
}
if saw_indirect_call {
return true
}
saw_indirect_call = true
break
}
@lib.Block(_, _)
| @lib.Loop(_, _)
| @lib.If(_, _, _)
| @lib.TryTable(_, _, _)
| @lib.Br(_)
| @lib.BrIf(_)
| @lib.BrTable(_, _)
| @lib.Return
| @lib.Unreachable => break
_ => ()
}
}
}
_ => ()
}
}
}
for instr in instrs {
match instr {
@lib.Block(_, @lib.Expr(body))
| @lib.Loop(_, @lib.Expr(body))
| @lib.TryTable(_, _, @lib.Expr(body)) =>
if run_hot_pipeline_dce_raw_has_call_result_multi_call_lifetime(body) {
return true
}
@lib.If(_, then_body, else_body) => {
if run_hot_pipeline_dce_raw_has_call_result_multi_call_lifetime(
then_body,
) {
return true
}
match else_body {
Some(else_body) =>
if run_hot_pipeline_dce_raw_has_call_result_multi_call_lifetime(
else_body,
) {
return true
}
None => ()
}
}
_ => ()
}
}
false
}
///|
fn run_hot_pipeline_dce_raw_has_load_call_set_hazard(
instrs : Array[@lib.Instruction],
) -> Bool {
for i = 0; i + 4 < instrs.length(); i = i + 1 {
match
(instrs[i], instrs[i + 1], instrs[i + 2], instrs[i + 3], instrs[i + 4]) {
(
@lib.LocalGet(base0),
@lib.I32Load(_),
@lib.LocalGet(base1),
call,
@lib.LocalSet(_),
)
| (
@lib.LocalGet(base0),
@lib.I64Load(_),
@lib.LocalGet(base1),
call,
@lib.LocalSet(_),
)
| (
@lib.LocalGet(base0),
@lib.F32Load(_),
@lib.LocalGet(base1),
call,
@lib.LocalSet(_),
)
| (
@lib.LocalGet(base0),
@lib.F64Load(_),
@lib.LocalGet(base1),
call,
@lib.LocalSet(_),
) =>
if base0 == base1 && run_hot_pipeline_dce_raw_instr_is_call(call) {
return true
}
_ => ()
}
}
for instr in instrs {
match instr {
@lib.Block(_, @lib.Expr(body)) | @lib.Loop(_, @lib.Expr(body)) =>
if run_hot_pipeline_dce_raw_has_load_call_set_hazard(body) {
return true
}
@lib.If(_, then_body, else_body) => {
if run_hot_pipeline_dce_raw_has_load_call_set_hazard(then_body) {
return true
}
match else_body {
Some(else_body) =>
if run_hot_pipeline_dce_raw_has_load_call_set_hazard(else_body) {
return true
}
None => ()
}
}
@lib.TryTable(_, _, @lib.Expr(body)) =>
if run_hot_pipeline_dce_raw_has_load_call_set_hazard(body) {
return true
}
_ => ()
}
}
false
}
///|
fn run_hot_pipeline_dce_raw_instrs_have_outer_branch(
instrs : Array[@lib.Instruction],
) -> Bool {
for instr in instrs {
match instr {
@lib.Br(@lib.LabelIdx(raw)) | @lib.BrIf(@lib.LabelIdx(raw)) =>
if raw.reinterpret_as_int() > 0 {
return true
}
@lib.BrTable(labels, @lib.LabelIdx(default_raw)) => {
if default_raw.reinterpret_as_int() > 0 {
return true
}
for label in labels {
let @lib.LabelIdx(raw) = label
if raw.reinterpret_as_int() > 0 {
return true
}
}
}
@lib.Block(_, @lib.Expr(body)) | @lib.Loop(_, @lib.Expr(body)) =>
if run_hot_pipeline_dce_raw_instrs_have_outer_branch(body) {
return true
}
@lib.If(_, then_body, else_body) => {
if run_hot_pipeline_dce_raw_instrs_have_outer_branch(then_body) {
return true
}
match else_body {
Some(else_body) =>
if run_hot_pipeline_dce_raw_instrs_have_outer_branch(else_body) {
return true
}
None => ()
}
}
@lib.TryTable(_, _, @lib.Expr(body)) =>
if run_hot_pipeline_dce_raw_instrs_have_outer_branch(body) {
return true
}
_ => ()
}
}
false
}
///|
fn run_hot_pipeline_dce_raw_has_loop_outer_branch(
instrs : Array[@lib.Instruction],
) -> Bool {
for instr in instrs {
match instr {
@lib.Loop(@lib.VoidBlockType, @lib.Expr(body)) =>
if run_hot_pipeline_dce_raw_instrs_have_outer_branch(body) {
return true
}
@lib.Block(_, @lib.Expr(body)) | @lib.TryTable(_, _, @lib.Expr(body)) =>
if run_hot_pipeline_dce_raw_has_loop_outer_branch(body) {
return true
}
@lib.If(_, then_body, else_body) => {
if run_hot_pipeline_dce_raw_has_loop_outer_branch(then_body) {
return true
}
match else_body {
Some(else_body) =>
if run_hot_pipeline_dce_raw_has_loop_outer_branch(else_body) {
return true
}
None => ()
}
}
_ => ()
}
}
false
}
///|
fn run_hot_pipeline_dce_raw_has_try_table(
instrs : Array[@lib.Instruction],
) -> Bool {
for instr in instrs {
match instr {
@lib.TryTable(_, _, _) => return true
@lib.Block(_, @lib.Expr(body)) | @lib.Loop(_, @lib.Expr(body)) =>
if run_hot_pipeline_dce_raw_has_try_table(body) {
return true
}
@lib.If(_, then_body, else_body) => {
if run_hot_pipeline_dce_raw_has_try_table(then_body) {
return true
}
match else_body {
Some(else_body) =>
if run_hot_pipeline_dce_raw_has_try_table(else_body) {
return true
}
None => ()
}
}
_ => ()
}
}
false
}
///|
fn run_hot_pipeline_dce_raw_has_literal_unreachable_loop_body(
instrs : Array[@lib.Instruction],
) -> Bool {
for instr in instrs {
match instr {
@lib.Loop(_, @lib.Expr(body)) => {
if body.length() == 1 {
match body[0] {
@lib.Unreachable => return true
_ => ()
}
}
if run_hot_pipeline_dce_raw_has_literal_unreachable_loop_body(body) {
return true
}
}
@lib.Block(_, @lib.Expr(body)) | @lib.TryTable(_, _, @lib.Expr(body)) =>
if run_hot_pipeline_dce_raw_has_literal_unreachable_loop_body(body) {
return true
}
@lib.If(_, then_body, else_body) => {
if run_hot_pipeline_dce_raw_has_literal_unreachable_loop_body(then_body) {
return true
}
match else_body {
Some(else_body) =>
if run_hot_pipeline_dce_raw_has_literal_unreachable_loop_body(
else_body,
) {
return true
}
None => ()
}
}
_ => ()
}
}
false
}
///|
fn run_hot_pipeline_dce_raw_has_early_terminator(
instrs : Array[@lib.Instruction],
) -> Bool {
for idx = 0; idx < instrs.length(); idx = idx + 1 {
let is_final = idx + 1 == instrs.length()
match instrs[idx] {
@lib.Return
| @lib.ReturnCall(_)
| @lib.ReturnCallIndirect(_, _)
| @lib.ReturnCallRef(_)
| @lib.Throw(_)
| @lib.ThrowRef
| @lib.Unreachable => if !is_final { return true }
_ => ()
}
}
false
}
///|
fn run_hot_pipeline_dce_raw_instrs_branch_to_depth(
instrs : Array[@lib.Instruction],
target_depth : Int32,
) -> Bool {
for instr in instrs {
match instr {
@lib.Br(@lib.LabelIdx(raw)) | @lib.BrIf(@lib.LabelIdx(raw)) =>
if raw.reinterpret_as_int() == target_depth {
return true
}
@lib.BrTable(labels, @lib.LabelIdx(default_raw)) => {
if default_raw.reinterpret_as_int() == target_depth {
return true
}
for label in labels {
let @lib.LabelIdx(raw) = label
if raw.reinterpret_as_int() == target_depth {
return true
}
}
}
@lib.Block(_, @lib.Expr(body)) | @lib.Loop(_, @lib.Expr(body)) =>
if run_hot_pipeline_dce_raw_instrs_branch_to_depth(
body,
target_depth + 1,
) {
return true
}
@lib.TryTable(_, catches, @lib.Expr(body)) => {
// Catch labels use the enclosing label space, outside the try body.
for arm in catches {
let label = match arm {
@lib.Catch(_, label)
| @lib.CatchRef(_, label)
| @lib.CatchAll(label)
| @lib.CatchAllRef(label) => label
}
let @lib.LabelIdx(raw) = label
if raw.reinterpret_as_int() == target_depth {
return true
}
}
if run_hot_pipeline_dce_raw_instrs_branch_to_depth(
body,
target_depth + 1,
) {
return true
}
}
@lib.If(_, then_body, else_body) => {
if run_hot_pipeline_dce_raw_instrs_branch_to_depth(
then_body,
target_depth + 1,
) {
return true
}
match else_body {
Some(else_body) =>
if run_hot_pipeline_dce_raw_instrs_branch_to_depth(
else_body,
target_depth + 1,
) {
return true
}
None => ()
}
}
_ => ()
}
}
false
}
///|
fn run_hot_pipeline_dce_raw_instr_may_fallthrough(
instr : @lib.Instruction,
) -> Bool {
match instr {
@lib.Br(_)
| @lib.BrTable(_, _)
| @lib.Return
| @lib.ReturnCall(_)
| @lib.ReturnCallIndirect(_, _)
| @lib.ReturnCallRef(_)
| @lib.Throw(_)
| @lib.ThrowRef
| @lib.Unreachable => false
@lib.Block(_, @lib.Expr(body)) =>
run_hot_pipeline_dce_raw_body_may_fallthrough(body) ||
run_hot_pipeline_dce_raw_instrs_branch_to_depth(body, 0)
@lib.Loop(_, @lib.Expr(body)) =>
run_hot_pipeline_dce_raw_body_may_fallthrough(body)
@lib.If(_, then_body, else_body) => {
let then_may_fallthrough = run_hot_pipeline_dce_raw_body_may_fallthrough(
then_body,
) ||
run_hot_pipeline_dce_raw_instrs_branch_to_depth(then_body, 0)
let else_may_fallthrough = match else_body {
Some(else_body) =>
run_hot_pipeline_dce_raw_body_may_fallthrough(else_body) ||
run_hot_pipeline_dce_raw_instrs_branch_to_depth(else_body, 0)
None => true
}
then_may_fallthrough || else_may_fallthrough
}
@lib.TryTable(_, _, @lib.Expr(body)) =>
run_hot_pipeline_dce_raw_body_may_fallthrough(body) ||
run_hot_pipeline_dce_raw_instrs_branch_to_depth(body, 0)
_ => true
}
}
///|
fn run_hot_pipeline_dce_raw_body_may_fallthrough(
instrs : Array[@lib.Instruction],
) -> Bool {
for instr in instrs {
if !run_hot_pipeline_dce_raw_instr_may_fallthrough(instr) {
return false
}
}
true
}
///|
fn run_hot_pipeline_dce_raw_has_dead_drop_after_nonfallthrough(
instrs : Array[@lib.Instruction],
) -> Bool {
let mut prev_may_fallthrough = true
for instr in instrs {
if !prev_may_fallthrough {
match instr {
@lib.Drop => return true
_ => ()
}
}
match instr {
@lib.Block(_, @lib.Expr(body)) | @lib.Loop(_, @lib.Expr(body)) =>
if run_hot_pipeline_dce_raw_has_dead_drop_after_nonfallthrough(body) {
return true
}
@lib.If(_, then_body, else_body) => {
if run_hot_pipeline_dce_raw_has_dead_drop_after_nonfallthrough(
then_body,
) {
return true
}
match else_body {
Some(else_body) =>
if run_hot_pipeline_dce_raw_has_dead_drop_after_nonfallthrough(
else_body,
) {
return true
}
None => ()
}
}
@lib.TryTable(_, _, @lib.Expr(body)) =>
if run_hot_pipeline_dce_raw_has_dead_drop_after_nonfallthrough(body) {
return true
}
_ => ()
}
prev_may_fallthrough = run_hot_pipeline_dce_raw_instr_may_fallthrough(instr)
}
false
}
///|
fn run_hot_pipeline_dce_raw_requires_lowering_even_if_unchanged(
original : @lib.Func,
) -> Bool {
let @lib.Func(_, @lib.Expr(instrs)) = original
run_hot_pipeline_dce_raw_has_dead_drop_after_nonfallthrough(instrs)
}
///|
fn run_hot_pipeline_dce_raw_instr_is_definitely_void(
instr : @lib.Instruction,
module_ctx : @ir.HotModuleContext,
) -> Bool {
match instr {
@lib.Nop
| @lib.Drop
| @lib.Return
| @lib.ReturnCall(_)
| @lib.ReturnCallIndirect(_, _)
| @lib.ReturnCallRef(_)
| @lib.Throw(_)
| @lib.ThrowRef
| @lib.Unreachable
| @lib.LocalSet(_)
| @lib.GlobalSet(_)
| @lib.TableSet(_)
| @lib.I32Store(_)
| @lib.I64Store(_)
| @lib.F32Store(_)
| @lib.F64Store(_)
| @lib.I32Store8(_)
| @lib.I32Store16(_)
| @lib.I64Store8(_)
| @lib.I64Store16(_)
| @lib.I64Store32(_)
| @lib.MemoryFill(_)
| @lib.DataDrop(_)
| @lib.ElemDrop(_)
| @lib.AtomicFence(_) => true
@lib.Call(func_idx) =>
match @ir.hot_module_resolve_func_type(module_ctx, func_idx) {
Some(@lib.FuncType(_, results)) => results.is_empty()
None => false
}
@lib.CallIndirect(type_idx, _) | @lib.CallRef(type_idx) =>
match @ir.hot_module_resolve_type_func_type(module_ctx, type_idx) {
Some(@lib.FuncType(_, results)) => results.is_empty()
None => false
}
_ => false
}
}
///|
fn run_hot_pipeline_dce_raw_instr_is_definitely_impure_value(
instr : @lib.Instruction,
module_ctx : @ir.HotModuleContext,
) -> Bool {
match instr {
@lib.Call(func_idx) =>
match @ir.hot_module_resolve_func_type(module_ctx, func_idx) {
Some(@lib.FuncType(_, results)) => !results.is_empty()
None => false
}
@lib.CallIndirect(type_idx, _) | @lib.CallRef(type_idx) =>
match @ir.hot_module_resolve_type_func_type(module_ctx, type_idx) {
Some(@lib.FuncType(_, results)) => !results.is_empty()
None => false
}
@lib.Block(block_type, @lib.Expr(_))
| @lib.Loop(block_type, @lib.Expr(_))
| @lib.If(block_type, _, _)
| @lib.TryTable(block_type, _, @lib.Expr(_)) =>
block_type != @lib.BlockType::void_()
_ => false
}
}
///|
fn run_hot_pipeline_dce_raw_drops_follow_definitely_impure_values(
instrs : Array[@lib.Instruction],
module_ctx : @ir.HotModuleContext,
) -> Bool {
let mut prev : @lib.Instruction? = None
for instr in instrs {
match instr {
@lib.Block(_, @lib.Expr(body)) | @lib.Loop(_, @lib.Expr(body)) =>
if !run_hot_pipeline_dce_raw_drops_follow_definitely_impure_values(
body, module_ctx,
) {
return false
}
@lib.If(_, then_body, else_body) => {
if !run_hot_pipeline_dce_raw_drops_follow_definitely_impure_values(
then_body, module_ctx,
) {
return false
}
match else_body {
Some(else_body) =>
if !run_hot_pipeline_dce_raw_drops_follow_definitely_impure_values(
else_body, module_ctx,
) {
return false
}
None => ()
}
}
@lib.TryTable(_, _, @lib.Expr(body)) =>
if !run_hot_pipeline_dce_raw_drops_follow_definitely_impure_values(
body, module_ctx,
) {
return false
}
_ => ()
}
match instr {
@lib.Drop =>
match prev {
Some(prev) if run_hot_pipeline_dce_raw_instr_is_definitely_impure_value(
prev, module_ctx,
) => ()
_ => return false
}
_ => ()
}
prev = Some(instr)
}
true
}
///|
fn run_hot_pipeline_dce_raw_void_structured_noop(
instrs : Array[@lib.Instruction],
) -> Bool {
if run_hot_pipeline_dce_raw_has_early_terminator(instrs) {
return false
}
for instr in instrs {
match instr {
@lib.Drop | @lib.Br(_) | @lib.BrIf(_) | @lib.BrTable(_, _) => return false
@lib.Block(block_type, @lib.Expr(body))
| @lib.Loop(block_type, @lib.Expr(body)) =>
if block_type != @lib.BlockType::void_() ||
!run_hot_pipeline_dce_raw_void_structured_noop(body) {
return false
}
@lib.If(block_type, then_body, else_body) => {
if block_type != @lib.BlockType::void_() ||
!run_hot_pipeline_dce_raw_void_structured_noop(then_body) {
return false
}
match else_body {
Some(else_body) =>
if !run_hot_pipeline_dce_raw_void_structured_noop(else_body) {
return false
}
None => ()
}
}
@lib.TryTable(block_type, _, @lib.Expr(body)) =>
if block_type != @lib.BlockType::void_() ||
!run_hot_pipeline_dce_raw_void_structured_noop(body) {
return false
}
_ => ()
}
}
true
}
///|
fn run_hot_pipeline_dce_raw_straight_line_noop(
instrs : Array[@lib.Instruction],
) -> Bool {
if run_hot_pipeline_dce_raw_has_early_terminator(instrs) {
return false
}
for instr in instrs {
match instr {
@lib.Drop => return false
_ => ()
}
}
true
}
///|
fn run_hot_pipeline_dce_raw_final_instr_is_fallthrough_value_structured(
instrs : Array[@lib.Instruction],
) -> Bool {
if instrs.is_empty() {
return false
}
let final_instr = instrs[instrs.length() - 1]
if !run_hot_pipeline_dce_raw_instr_may_fallthrough(final_instr) {
return false
}
match final_instr {
@lib.Block(block_type, @lib.Expr(_))
| @lib.Loop(block_type, @lib.Expr(_))
| @lib.If(block_type, _, _)
| @lib.TryTable(block_type, _, @lib.Expr(_)) =>
block_type != @lib.BlockType::void_()
_ => false
}
}
///|
fn run_hot_pipeline_dce_raw_live_typed_control_only(
instrs : Array[@lib.Instruction],
result_live : Bool,
) -> Bool {
let mut prev_may_fallthrough = true
for idx = 0; idx < instrs.length(); idx = idx + 1 {
let instr = instrs[idx]
let is_final = idx + 1 == instrs.length()
if !prev_may_fallthrough {
return false
}
match instr {
@lib.Drop => return false
@lib.Block(block_type, @lib.Expr(body)) => {
let child_result_live = is_final &&
result_live &&
block_type != @lib.BlockType::void_()
if block_type != @lib.BlockType::void_() && !child_result_live {
return false
}
if !run_hot_pipeline_dce_raw_live_typed_control_only(
body, child_result_live,
) {
return false
}
}
@lib.Loop(_, @lib.Expr(_)) => return false
@lib.If(block_type, then_body, else_body) => {
let child_result_live = is_final &&
result_live &&
block_type != @lib.BlockType::void_()
if block_type != @lib.BlockType::void_() && !child_result_live {
return false
}
if !run_hot_pipeline_dce_raw_live_typed_control_only(
then_body, child_result_live,
) {
return false
}
match else_body {
Some(else_body) =>
if !run_hot_pipeline_dce_raw_live_typed_control_only(
else_body, child_result_live,
) {
return false
}
None => ()
}
}
@lib.TryTable(block_type, _, @lib.Expr(body)) => {
let child_result_live = is_final &&
result_live &&
block_type != @lib.BlockType::void_()
if block_type != @lib.BlockType::void_() && !child_result_live {
return false
}
if !run_hot_pipeline_dce_raw_live_typed_control_only(
body, child_result_live,
) {
return false
}
}
_ => ()
}
prev_may_fallthrough = run_hot_pipeline_dce_raw_instr_may_fallthrough(instr)
}
true
}
///|
fn run_hot_pipeline_dce_raw_has_stack_polymorphic_if_before_br(
instrs : Array[@lib.Instruction],
) -> Bool {
for idx = 0; idx < instrs.length(); idx = idx + 1 {
if idx + 1 < instrs.length() {
match (instrs[idx], instrs[idx + 1]) {
(@lib.If(_, then_body, Some(else_body)), @lib.Br(_)) =>
if !run_hot_pipeline_dce_raw_body_may_fallthrough(then_body) &&
!run_hot_pipeline_dce_raw_body_may_fallthrough(else_body) {
return true
}
_ => ()
}
}
match instrs[idx] {
@lib.Block(_, @lib.Expr(body))
| @lib.Loop(_, @lib.Expr(body))
| @lib.TryTable(_, _, @lib.Expr(body)) =>
if run_hot_pipeline_dce_raw_has_stack_polymorphic_if_before_br(body) {
return true
}
@lib.If(_, then_body, else_body) => {
if run_hot_pipeline_dce_raw_has_stack_polymorphic_if_before_br(
then_body,
) {
return true
}
match else_body {
Some(else_body) =>
if run_hot_pipeline_dce_raw_has_stack_polymorphic_if_before_br(
else_body,
) {
return true
}
None => ()
}
}
_ => ()
}
}
false
}
///|
fn run_hot_pipeline_dce_can_skip_raw_with_facts(
original : @lib.Func,
module_ctx : @ir.HotModuleContext,
func_idx : @lib.FuncIdx,
has_structured_control : Bool,
has_branches : Bool,
has_drop : Bool,
has_dead_tail_after_nonfallthrough : Bool,
) -> Bool {
let @lib.Func(_, @lib.Expr(instrs)) = original
let returns_value = match
@ir.hot_module_resolve_func_type(module_ctx, func_idx) {
Some(@lib.FuncType(_, results)) => !results.is_empty()
None =>
abort(
"hot pipeline raw dead-code-elimination skip requires the function type to resolve",
)
}
if run_hot_pipeline_dce_raw_has_literal_unreachable_loop_body(instrs) ||
run_hot_pipeline_dce_raw_has_try_table(instrs) ||
run_hot_pipeline_dce_raw_has_stack_polymorphic_if_before_br(instrs) ||
has_dead_tail_after_nonfallthrough {
return false
}
let straight_line_noop = !has_structured_control &&
run_hot_pipeline_dce_raw_straight_line_noop(instrs)
let structured_void_noop = has_structured_control &&
run_hot_pipeline_dce_raw_void_structured_noop(instrs)
let drops_follow_impure_values = !has_drop ||
run_hot_pipeline_dce_raw_drops_follow_definitely_impure_values(
instrs, module_ctx,
)
let structured_typed_noop = has_structured_control &&
returns_value &&
drops_follow_impure_values &&
!has_dead_tail_after_nonfallthrough &&
run_hot_pipeline_dce_raw_final_instr_is_fallthrough_value_structured(instrs)
let branchy_live_typed_noop = has_branches &&
has_structured_control &&
returns_value &&
!has_drop &&
!has_dead_tail_after_nonfallthrough &&
run_hot_pipeline_dce_raw_live_typed_control_only(instrs, true)
if !straight_line_noop && !structured_void_noop && !structured_typed_noop {
if !branchy_live_typed_noop {
return false
}
}
returns_value ||
structured_void_noop ||
instrs.is_empty() ||
run_hot_pipeline_dce_raw_instr_is_definitely_void(
instrs[instrs.length() - 1],
module_ctx,
)
}
///|
priv struct HsoCandidateScanState {
mut has_local_writes : Bool
mut has_struct_alloc : Bool
mut has_struct_set : Bool
}
///|
priv struct HotPipelineModuleState {
mut module_ctx : @ir.HotModuleContext?
mut allows_simd_introduction : Bool?
mut validation_module_env : @validate.Env?
mut func_type_indices : Array[@lib.TypeIdx]?
mut raw_type_sec_snapshot : @lib.TypeSec?
mut raw_flat_subtypes : Array[@lib.SubType]?
mut vacuum_raw_preclean_elapsed_us : UInt64
mut vacuum_raw_preclean_count : Int32
mut vacuum_no_candidate_elapsed_us : UInt64
mut vacuum_no_candidate_count : Int32
mut vacuum_lift_elapsed_us : UInt64
mut vacuum_lift_count : Int32
mut vacuum_pass_elapsed_us : UInt64
mut vacuum_pass_count : Int32
mut vacuum_lower_elapsed_us : UInt64
mut vacuum_lower_count : Int32
dce_unchanged_lowering_known : @bitset.BitSet
dce_unchanged_lowering_required : @bitset.BitSet
}
///|
fn HotPipelineModuleState::new() -> HotPipelineModuleState {
{
module_ctx: None,
allows_simd_introduction: None,
validation_module_env: None,
func_type_indices: None,
raw_type_sec_snapshot: None,
raw_flat_subtypes: None,
vacuum_raw_preclean_elapsed_us: 0UL,
vacuum_raw_preclean_count: 0,
vacuum_no_candidate_elapsed_us: 0UL,
vacuum_no_candidate_count: 0,
vacuum_lift_elapsed_us: 0UL,
vacuum_lift_count: 0,
vacuum_pass_elapsed_us: 0UL,
vacuum_pass_count: 0,
vacuum_lower_elapsed_us: 0UL,
vacuum_lower_count: 0,
dce_unchanged_lowering_known: @bitset.BitSet::new(0),
dce_unchanged_lowering_required: @bitset.BitSet::new(0),
}
}
///|
fn run_hot_pipeline_invalidate_type_dependent_caches(
module_state : HotPipelineModuleState,
) -> Unit {
module_state.module_ctx = None
module_state.validation_module_env = None
}
///|
fn run_hot_pipeline_func_type_indices(
mod_ : @lib.Module,
module_state : HotPipelineModuleState,
) -> Array[@lib.TypeIdx] {
match module_state.func_type_indices {
Some(indices) => indices
None => {
let indices = dae_collect_func_type_indices(mod_)
module_state.func_type_indices = Some(indices)
indices
}
}
}
///|
test "function type table cache is scoped to one module state" {
let first = pass_test_module_from_wat(
"(module (type (func)) (func (type 0)))",
)
let second = pass_test_module_from_wat(
"(module (type (func)) (type (func (param i32))) (func (type 1) (param i32)))",
)
let first_state = HotPipelineModuleState::new()
let second_state = HotPipelineModuleState::new()
let first_indices = run_hot_pipeline_func_type_indices(first, first_state)
assert_eq(first_indices, [@lib.TypeIdx::new(0U)])
assert_eq(
run_hot_pipeline_func_type_indices(first, first_state),
first_indices,
)
assert_eq(run_hot_pipeline_func_type_indices(second, second_state), [
@lib.TypeIdx::new(1U),
])
}
///|
fn run_hot_pipeline_dce_record_requires_unchanged_lowering(
module_state : HotPipelineModuleState,
func_idx : @lib.FuncIdx,
required : Bool,
) -> Unit {
let @lib.FuncIdx(raw) = func_idx
let idx = raw.reinterpret_as_int()
module_state.dce_unchanged_lowering_known.grow(idx + 1)
module_state.dce_unchanged_lowering_required.grow(idx + 1)
module_state.dce_unchanged_lowering_known[idx] = true
module_state.dce_unchanged_lowering_required[idx] = required
}
///|
fn run_hot_pipeline_dce_cached_requires_unchanged_lowering(
module_state : HotPipelineModuleState,
func_idx : @lib.FuncIdx,
) -> Bool? {
let @lib.FuncIdx(raw) = func_idx
let idx = raw.reinterpret_as_int()
if idx < 0 ||
idx >= module_state.dce_unchanged_lowering_known.length() ||
!module_state.dce_unchanged_lowering_known[idx] {
return None
}
Some(module_state.dce_unchanged_lowering_required[idx])
}
///|
fn run_hot_pipeline_record_vacuum_hot_timer(
module_state : HotPipelineModuleState,
name : String,
elapsed_us : UInt64,
) -> Unit {
match name {
"lift" => {
module_state.vacuum_lift_elapsed_us += elapsed_us
module_state.vacuum_lift_count += 1
}
"pass:vacuum" => {
module_state.vacuum_pass_elapsed_us += elapsed_us
module_state.vacuum_pass_count += 1
}
"lower" => {
module_state.vacuum_lower_elapsed_us += elapsed_us
module_state.vacuum_lower_count += 1
}
_ => ()
}
}
///|
fn run_hot_pipeline_emit_vacuum_aggregate_timers(
options : HotPipelineOptions,
module_state : HotPipelineModuleState,
) -> Unit {
if module_state.vacuum_raw_preclean_count > 0 {
perf_record_timer_elapsed_opt(
options.perf,
"raw:vacuum-preclean",
module_state.vacuum_raw_preclean_elapsed_us,
)
}
if module_state.vacuum_no_candidate_count > 0 {
perf_record_timer_elapsed_opt(
options.perf,
"raw:vacuum-no-candidates",
module_state.vacuum_no_candidate_elapsed_us,
)
}
if module_state.vacuum_lift_count > 0 {
perf_record_timer_elapsed_opt(
options.perf,
"lift",
module_state.vacuum_lift_elapsed_us,
)
}
if module_state.vacuum_pass_count > 0 {
perf_record_timer_elapsed_opt(
options.perf,
"pass:vacuum",
module_state.vacuum_pass_elapsed_us,
)
}
if module_state.vacuum_lower_count > 0 {
perf_record_timer_elapsed_opt(
options.perf,
"lower",
module_state.vacuum_lower_elapsed_us,
)
}
}
///|
priv struct HotPipelineLowerState {
mut next_type_sec : @lib.TypeSec?
mut type_sec_changed : Bool
}
///|
fn run_hot_pipeline_allows_simd_introduction(
mod_ : @lib.Module,
module_state : HotPipelineModuleState,
) -> Bool {
// Each function lane keeps custom sections unchanged. Module passes start
// a new state; lowering-only type changes need no metadata reparse.
match module_state.allows_simd_introduction {
Some(allowed) => allowed
None => {
let allowed = pass_module_allows_feature_introduction(mod_, "simd128")
module_state.allows_simd_introduction = Some(allowed)
allowed
}
}
}
///|
fn run_hot_pipeline_require_module_ctx(
mod_ : @lib.Module,
module_state : HotPipelineModuleState,
options : HotPipelineOptions,
) -> @ir.HotModuleContext {
match module_state.module_ctx {
Some(module_ctx) => module_ctx
None => {
let start_us = perf_start_timer_opt(
options.perf,
"analysis:module-context",
)
let module_ctx = @ir.hot_module_context_from_module(mod_)
perf_stop_timer_opt(options.perf, "analysis:module-context", start_us)
module_state.module_ctx = Some(module_ctx)
// This table was flattened from the same immutable type-section object.
// Raw lookups can borrow it; lower-created replacement sections refresh
// the raw snapshot independently from the original HOT module context.
module_state.raw_type_sec_snapshot = mod_.type_sec
module_state.raw_flat_subtypes = Some(module_ctx.subtypes)
module_ctx
}
}
}
///|
fn run_hot_pipeline_require_validation_module_env(
mod_ : @lib.Module,
module_state : HotPipelineModuleState,
options : HotPipelineOptions,
) -> @validate.Env {
match module_state.validation_module_env {
Some(env) => env
None => {
let start_us = perf_start_timer_opt(
options.perf,
"analysis:optimize-instructions-validation-module-env",
)
let env = @validate.Env::new().with_module(mod_)
perf_stop_timer_opt(
options.perf,
"analysis:optimize-instructions-validation-module-env",
start_us,
)
module_state.validation_module_env = Some(env)
env
}
}
}
///|
fn run_hot_pipeline_lower_type_sec_flat_count(
type_sec : @lib.TypeSec?,
) -> Int32 {
let mut count = 0
match type_sec {
Some(@lib.TypeSec(rec_types)) =>
for rec_type in rec_types {
match rec_type {
@lib.SingleRecType(_) => count += 1
@lib.GroupRecType(group) => count += group.length()
}
}
None => ()
}
count
}
///|
fn run_hot_pipeline_lower_find_block_result_type_idx(
type_sec : @lib.TypeSec?,
results : Array[@lib.ValType],
) -> @lib.TypeIdx? {
let mut flat_idx = 0
match type_sec {
Some(@lib.TypeSec(rec_types)) =>
for rec_type in rec_types {
match rec_type {
@lib.SingleRecType(subtype) => {
match subtype.get_comptype() {
@lib.FuncCompType(params, candidate_results) =>
if params.is_empty() && candidate_results == results {
return Some(@lib.TypeIdx::new(flat_idx.reinterpret_as_uint()))
}
_ => ()
}
flat_idx += 1
}
@lib.GroupRecType(group) =>
for subtype in group {
match subtype.get_comptype() {
@lib.FuncCompType(params, candidate_results) =>
if params.is_empty() && candidate_results == results {
return Some(
@lib.TypeIdx::new(flat_idx.reinterpret_as_uint()),
)
}
_ => ()
}
flat_idx += 1
}
}
}
None => ()
}
None
}
///|
fn run_hot_pipeline_lower_intern_block_result_type_idx(
lower_state : HotPipelineLowerState,
results : Array[@lib.ValType],
) -> @lib.TypeIdx {
match
run_hot_pipeline_lower_find_block_result_type_idx(
lower_state.next_type_sec,
results,
) {
Some(type_idx) => type_idx
None => {
let next_flat_idx = run_hot_pipeline_lower_type_sec_flat_count(
lower_state.next_type_sec,
)
let next_rec_types : Array[@lib.RecType] = []
match lower_state.next_type_sec {
Some(@lib.TypeSec(rec_types)) =>
for rec_type in rec_types {
next_rec_types.push(rec_type)
}
None => ()
}
next_rec_types.push(
@lib.RecType::new(
@lib.SubType::comp_type(@lib.CompType::func([], results)),
),
)
lower_state.next_type_sec = Some(@lib.TypeSec::new(next_rec_types))
lower_state.type_sec_changed = true
@lib.TypeIdx::new(next_flat_idx.reinterpret_as_uint())
}
}
}
///|
fn run_hot_pipeline_prepare_tuple_make_lowering(
func : @ir.HotFunc,
lower_state : HotPipelineLowerState,
) -> Unit {
for node_id = 0; node_id < @ir.hot_node_count(func); node_id = node_id + 1 {
if !@ir.hot_node_is_live(func, node_id) {
continue
}
if !(@ir.hot_node_op(func, node_id) is @ir.TupleMake) {
continue
}
let results = @ir.hot_type_results(func, @ir.hot_node_type(func, node_id))
if results.length() <= 1 {
continue
}
@ir.hot_tuple_make_promote_to_block(
func,
node_id,
@lib.BlockType::type_idx(
run_hot_pipeline_lower_intern_block_result_type_idx(
lower_state, results,
),
),
)
}
}
///|
fn run_hot_pipeline_hso_candidate_scan_into(
instrs : Array[@lib.Instruction],
state : HsoCandidateScanState,
) -> Unit {
for instr in instrs {
if state.has_local_writes && state.has_struct_alloc && state.has_struct_set {
return
}
match instr {
@lib.LocalSet(_) | @lib.LocalTee(_) => state.has_local_writes = true
@lib.StructNew(_)
| @lib.StructNewDefault(_)
| @lib.StructNewDesc(_)
| @lib.StructNewDefaultDesc(_) => state.has_struct_alloc = true
@lib.StructSet(_, _) => state.has_struct_set = true
@lib.Block(_, @lib.Expr(body)) | @lib.Loop(_, @lib.Expr(body)) =>
run_hot_pipeline_hso_candidate_scan_into(body, state)
@lib.If(_, then_body, else_body) => {
run_hot_pipeline_hso_candidate_scan_into(then_body, state)
match else_body {
Some(else_body) =>
run_hot_pipeline_hso_candidate_scan_into(else_body, state)
None => ()
}
}
@lib.TryTable(_, _, @lib.Expr(body)) =>
run_hot_pipeline_hso_candidate_scan_into(body, state)
_ => ()
}
}
}
///|
fn run_hot_pipeline_hso_instrs_have_tail_call(
instrs : Array[@lib.Instruction],
) -> Bool {
for instr in instrs {
match instr {
@lib.ReturnCall(_)
| @lib.ReturnCallIndirect(_, _)
| @lib.ReturnCallRef(_) => return true
@lib.Block(_, @lib.Expr(body))
| @lib.Loop(_, @lib.Expr(body))
| @lib.TryTable(_, _, @lib.Expr(body)) =>
if run_hot_pipeline_hso_instrs_have_tail_call(body) {
return true
}
@lib.If(_, then_body, else_body) => {
if run_hot_pipeline_hso_instrs_have_tail_call(then_body) {
return true
}
match else_body {
Some(body) =>
if run_hot_pipeline_hso_instrs_have_tail_call(body) {
return true
}
None => ()
}
}
@lib.Try(_, @lib.Expr(body), catches, _) => {
if run_hot_pipeline_hso_instrs_have_tail_call(body) {
return true
}
for catch_ in catches {
let @lib.Expr(catch_body) = match catch_ {
@lib.LegacyCatch(_, body) | @lib.LegacyCatchAll(body) => body
}
if run_hot_pipeline_hso_instrs_have_tail_call(catch_body) {
return true
}
}
}
_ => ()
}
}
false
}
///|
fn run_hot_pipeline_hso_original_requires_unchanged_lowering(
original : @lib.Func,
) -> Bool {
let @lib.Func(_, @lib.Expr(body)) = original
run_hot_pipeline_hso_instrs_have_tail_call(body)
}
///|
fn run_hot_pipeline_hso_candidate_scan(
instrs : Array[@lib.Instruction],
) -> (Bool, Bool, Bool) {
let state = {
has_local_writes: false,
has_struct_alloc: false,
has_struct_set: false,
}
run_hot_pipeline_hso_candidate_scan_into(instrs, state)
(state.has_local_writes, state.has_struct_alloc, state.has_struct_set)
}
///|
fn run_hot_pipeline_hso_raw_struct_field_count(
mod_ : @lib.Module,
type_idx : @lib.TypeIdx,
) -> Int32? {
let target = match type_idx {
@lib.TypeIdx(raw) => raw.reinterpret_as_int()
@lib.RecIdx(_) => return None
}
let rec_types = match mod_.type_sec {
Some(@lib.TypeSec(rec_types)) => rec_types
None => return None
}
let mut flat = 0
for rec_type in rec_types {
match rec_type {
@lib.SingleRecType(subtype) => {
if flat == target {
return match subtype.get_comptype() {
@lib.StructCompType(fields) => Some(fields.length())
_ => None
}
}
flat = flat + 1
}
@lib.GroupRecType(subtypes) =>
for subtype in subtypes {
if flat == target {
return match subtype.get_comptype() {
@lib.StructCompType(fields) => Some(fields.length())
_ => None
}
}
flat = flat + 1
}
}
}
None
}
///|
fn run_hot_pipeline_hso_raw_simple_value(
instr : @lib.Instruction,
) -> @lib.Instruction? {
match instr {
@lib.I32Const(_)
| @lib.I64Const(_)
| @lib.F32Const(_)
| @lib.F64Const(_)
| @lib.RefNull(_) => Some(instr)
_ => None
}
}
///|
fn run_hot_pipeline_hso_raw_i31_value(instrs : Array[@lib.Instruction]) -> Bool {
if instrs.length() != 2 {
return false
}
match (instrs[0], instrs[1]) {
(@lib.I32Const(_), @lib.RefI31) => true
_ => false
}
}
///|
fn run_hot_pipeline_hso_raw_contained_i31_block(
instr : @lib.Instruction,
) -> Bool {
match instr {
@lib.Block(block_type, @lib.Expr(body)) => {
if block_type != @lib.BlockType::val_type(@lib.ValType::anyref()) ||
body.length() != 3 {
return false
}
match (body[0], body[1], body[2]) {
(@lib.I32Const(_), @lib.RefI31, @lib.Br(label_idx)) =>
label_idx == @lib.LabelIdx::new(0U)
_ => false
}
}
_ => false
}
}
///|
fn run_hot_pipeline_raw_heap_store_optimization_allocation_sink(
instrs : Array[@lib.Instruction],
) -> (Array[@lib.Instruction], Bool)? {
// Binaryen's optimizeBlock pushes a fresh constructor local.set toward later
// roots even when no struct.set is found. Keep this raw path exact: the whole
// function must be repeated proven-pure i31 windows, or general HOT owns it.
if instrs.is_empty() || instrs.length() % 9 != 0 {
return None
}
let rewritten : Array[@lib.Instruction] = Array::new(capacity=instrs.length())
let mut idx = 0
while idx < instrs.length() {
match
(
instrs[idx],
instrs[idx + 1],
instrs[idx + 2],
instrs[idx + 3],
instrs[idx + 4],
instrs[idx + 5],
instrs[idx + 6],
instrs[idx + 7],
instrs[idx + 8],
) {
(
@lib.StructNewDefault(_) as struct_new,
@lib.LocalSet(set_local) as local_set,
@lib.I32Const(_) as condition,
@lib.If(if_type, then_body, Some(else_body)) as if_instr,
@lib.Drop as if_drop,
block_instr,
@lib.Drop as block_drop,
@lib.LocalGet(get_local) as local_get,
@lib.Drop as local_drop,
) if set_local == get_local &&
if_type == @lib.BlockType::val_type(@lib.ValType::anyref()) &&
run_hot_pipeline_hso_raw_i31_value(then_body) &&
run_hot_pipeline_hso_raw_i31_value(else_body) &&
run_hot_pipeline_hso_raw_contained_i31_block(block_instr) => {
rewritten.push(condition)
rewritten.push(if_instr)
rewritten.push(if_drop)
rewritten.push(block_instr)
rewritten.push(block_drop)
rewritten.push(struct_new)
rewritten.push(local_set)
rewritten.push(local_get)
rewritten.push(local_drop)
idx = idx + 9
}
_ => return None
}
}
Some((rewritten, true))
}
///|
fn run_hot_pipeline_raw_heap_store_optimization_plain_struct_new_chain(
mod_ : @lib.Module,
instrs : Array[@lib.Instruction],
) -> (Array[@lib.Instruction], Bool)? {
let rewritten : Array[@lib.Instruction] = []
let mut cursor = 0
let mut scan_idx = 0
let mut changed = false
while scan_idx + 1 < instrs.length() {
match (instrs[scan_idx], instrs[scan_idx + 1]) {
(
@lib.StructNew(type_idx),
@lib.LocalSet(@lib.LocalIdx(local_raw)) as local_set,
) => {
let field_count = match
run_hot_pipeline_hso_raw_struct_field_count(mod_, type_idx) {
Some(count) if count > 0 => count
_ => {
scan_idx = scan_idx + 1
continue
}
}
let value_start = scan_idx - field_count
if value_start < cursor {
scan_idx = scan_idx + 1
continue
}
let values : Array[@lib.Instruction?] = Array::make(field_count, None)
let mut simple_constructor = true
for field_idx = 0; field_idx < field_count; field_idx = field_idx + 1 {
match
run_hot_pipeline_hso_raw_simple_value(
instrs[value_start + field_idx],
) {
Some(value) => values[field_idx] = Some(value)
None => simple_constructor = false
}
}
if !simple_constructor {
scan_idx = scan_idx + 1
continue
}
let mut chain_idx = scan_idx + 2
let mut consumed_sets = 0
while chain_idx + 2 < instrs.length() {
match
(instrs[chain_idx], instrs[chain_idx + 1], instrs[chain_idx + 2]) {
(
@lib.LocalGet(@lib.LocalIdx(read_raw)),
value_instr,
@lib.StructSet(set_type_idx, @lib.U32(raw_field_idx)),
) if read_raw == local_raw && set_type_idx == type_idx => {
let field_idx = raw_field_idx.reinterpret_as_int()
if field_idx < 0 || field_idx >= field_count {
break
}
match run_hot_pipeline_hso_raw_simple_value(value_instr) {
Some(value) => {
values[field_idx] = Some(value)
consumed_sets = consumed_sets + 1
chain_idx = chain_idx + 3
continue
}
None => break
}
}
_ => break
}
}
if consumed_sets >= 2 {
for idx = cursor; idx < value_start; idx = idx + 1 {
rewritten.push(instrs[idx])
}
for field_idx = 0; field_idx < field_count; field_idx = field_idx + 1 {
match values[field_idx] {
Some(value) => rewritten.push(value)
None => return None
}
}
rewritten.push(@lib.Instruction::struct_new(type_idx))
rewritten.push(local_set)
cursor = chain_idx
scan_idx = chain_idx
changed = true
continue
}
}
_ => ()
}
scan_idx = scan_idx + 1
}
if !changed {
return None
}
for idx = cursor; idx < instrs.length(); idx = idx + 1 {
rewritten.push(instrs[idx])
}
let (has_local_writes, has_struct_alloc, has_struct_set) = run_hot_pipeline_hso_candidate_scan(
rewritten,
)
if has_local_writes && has_struct_alloc && has_struct_set {
return None
}
Some((rewritten, true))
}
///|
fn run_hot_pipeline_raw_heap_store_optimization_complete_default_chain(
mod_ : @lib.Module,
instrs : Array[@lib.Instruction],
) -> (Array[@lib.Instruction], Bool)? {
let rewritten : Array[@lib.Instruction] = []
let mut cursor = 0
let mut scan_idx = 0
let mut changed = false
while scan_idx + 1 < instrs.length() {
match (instrs[scan_idx], instrs[scan_idx + 1]) {
(
@lib.StructNewDefault(type_idx),
@lib.LocalSet(@lib.LocalIdx(local_raw)) as local_set,
) => {
let field_count = match
run_hot_pipeline_hso_raw_struct_field_count(mod_, type_idx) {
Some(count) if count > 0 => count
_ => {
scan_idx = scan_idx + 1
continue
}
}
let values : Array[@lib.Instruction?] = Array::make(field_count, None)
let present : Array[Bool] = Array::make(field_count, false)
let mut missing = field_count
let mut chain_idx = scan_idx + 2
let mut consumed_sets = 0
while chain_idx + 2 < instrs.length() {
match
(instrs[chain_idx], instrs[chain_idx + 1], instrs[chain_idx + 2]) {
(
@lib.LocalGet(@lib.LocalIdx(read_raw)),
value_instr,
@lib.StructSet(set_type_idx, @lib.U32(raw_field_idx)),
) if read_raw == local_raw && set_type_idx == type_idx => {
let field_idx = raw_field_idx.reinterpret_as_int()
if field_idx < 0 || field_idx >= field_count {
break
}
match run_hot_pipeline_hso_raw_simple_value(value_instr) {
Some(value) => {
if !present[field_idx] {
present[field_idx] = true
missing = missing - 1
}
values[field_idx] = Some(value)
consumed_sets = consumed_sets + 1
chain_idx = chain_idx + 3
continue
}
None => break
}
}
_ => break
}
}
if consumed_sets > 0 && missing == 0 {
for idx = cursor; idx < scan_idx; idx = idx + 1 {
rewritten.push(instrs[idx])
}
for field_idx = 0; field_idx < field_count; field_idx = field_idx + 1 {
match values[field_idx] {
Some(value) => rewritten.push(value)
None => return None
}
}
rewritten.push(@lib.Instruction::struct_new(type_idx))
rewritten.push(local_set)
for _ in 0.. ()
}
scan_idx = scan_idx + 1
}
if !changed {
return None
}
for idx = cursor; idx < instrs.length(); idx = idx + 1 {
rewritten.push(instrs[idx])
}
let (has_local_writes, has_struct_alloc, has_struct_set) = run_hot_pipeline_hso_candidate_scan(
rewritten,
)
if has_local_writes && has_struct_alloc && has_struct_set {
return None
}
Some((rewritten, true))
}
///|
priv struct PlsCandidateScanState {
mut has_candidate_producer : Bool
mut has_local_get : Bool
mut has_extension_surface : Bool
}
///|
fn run_hot_pipeline_pls_candidate_scan_into(
instrs : Array[@lib.Instruction],
state : PlsCandidateScanState,
) -> Unit {
let mut prev_was_candidate_load = false
for instr in instrs {
if state.has_candidate_producer &&
state.has_local_get &&
state.has_extension_surface {
return
}
match instr {
@lib.LocalSet(_) =>
if prev_was_candidate_load {
state.has_candidate_producer = true
}
@lib.LocalGet(_) => state.has_local_get = true
@lib.I32Extend8S
| @lib.I32Extend16S
| @lib.I64Extend8S
| @lib.I64Extend16S
| @lib.I64Extend32S
| @lib.I64ExtendI32S
| @lib.I64ExtendI32U
| @lib.I32And
| @lib.I64And
| @lib.I32Shl
| @lib.I64Shl => state.has_extension_surface = true
@lib.I32Load8S(_)
| @lib.I32Load8U(_)
| @lib.I32Load16S(_)
| @lib.I32Load16U(_)
| @lib.I64Load8S(_)
| @lib.I64Load8U(_)
| @lib.I64Load16S(_)
| @lib.I64Load16U(_)
| @lib.I64Load32S(_)
| @lib.I64Load32U(_) => prev_was_candidate_load = true
@lib.Block(_, @lib.Expr(body)) | @lib.Loop(_, @lib.Expr(body)) => {
run_hot_pipeline_pls_candidate_scan_into(body, state)
prev_was_candidate_load = false
}
@lib.If(_, then_body, else_body) => {
run_hot_pipeline_pls_candidate_scan_into(then_body, state)
match else_body {
Some(else_body) =>
run_hot_pipeline_pls_candidate_scan_into(else_body, state)
None => ()
}
prev_was_candidate_load = false
}
@lib.TryTable(_, _, @lib.Expr(body)) => {
run_hot_pipeline_pls_candidate_scan_into(body, state)
prev_was_candidate_load = false
}
_ => prev_was_candidate_load = false
}
}
}
///|
fn run_hot_pipeline_pls_candidate_scan(
instrs : Array[@lib.Instruction],
) -> (Bool, Bool, Bool) {
let state = {
has_candidate_producer: false,
has_local_get: false,
has_extension_surface: false,
}
run_hot_pipeline_pls_candidate_scan_into(instrs, state)
(
state.has_candidate_producer,
state.has_local_get,
state.has_extension_surface,
)
}
///|
fn run_hot_pipeline_raw_flatten_type_sec(
type_sec : @lib.TypeSec?,
) -> Array[@lib.SubType] {
let out : Array[@lib.SubType] = []
match type_sec {
Some(@lib.TypeSec(rec_types)) =>
for rec_type in rec_types {
match rec_type {
@lib.SingleRecType(subtype) => out.push(subtype)
@lib.GroupRecType(group) =>
for subtype in group {
out.push(subtype)
}
}
}
None => ()
}
out
}
///|
fn run_hot_pipeline_defined_func_params_from_sections(
mod_ : @lib.Module,
func_idx : @lib.FuncIdx,
) -> Array[@lib.ValType]? {
run_hot_pipeline_defined_func_params_indexed(mod_, func_idx, None)
}
///|
fn run_hot_pipeline_defined_func_params_indexed(
mod_ : @lib.Module,
func_idx : @lib.FuncIdx,
flattened_subtypes : Array[@lib.SubType]?,
) -> Array[@lib.ValType]? {
let imported_func_count = run_hot_pipeline_imported_func_count(mod_)
let @lib.FuncIdx(raw) = func_idx
let code_idx = raw.reinterpret_as_int() - imported_func_count
if code_idx < 0 {
return None
}
let type_idx = match mod_.func_sec {
Some(@lib.FuncSec(type_idxs)) =>
match type_idxs.get(code_idx) {
Some(idx) => idx
None => return None
}
None => return None
}
let type_pos = match type_idx {
@lib.TypeIdx(type_raw) => type_raw.reinterpret_as_int()
@lib.RecIdx(_) => return None
}
let subtypes = match flattened_subtypes {
Some(subtypes) => subtypes
None => run_hot_pipeline_raw_flatten_type_sec(mod_.type_sec)
}
match subtypes.get(type_pos) {
Some(subtype) =>
match subtype.get_comptype() {
@lib.FuncCompType(params, _) => Some(params)
_ => None
}
None => None
}
}
///|
fn run_hot_pipeline_required_func_params(
module_ctx : @ir.HotModuleContext,
func_idx : @lib.FuncIdx,
) -> Array[@lib.ValType] {
match @ir.hot_module_resolve_func_type(module_ctx, func_idx) {
Some(@lib.FuncType(params, _)) => params
None =>
abort(
"hot pipeline raw ssa-nomerge rewrite requires the function type to resolve",
)
}
}
///|
fn run_hot_pipeline_require_raw_types(
mod_ : @lib.Module,
module_state : HotPipelineModuleState,
builds : Array[Int32]?,
) -> Array[@lib.SubType] {
let same_snapshot = match
(module_state.raw_type_sec_snapshot, mod_.type_sec) {
(None, None) => true
(Some(old), Some(current)) => physical_equal(old, current)
_ => false
}
match module_state.raw_flat_subtypes {
Some(subtypes) if same_snapshot => return subtypes
_ => ()
}
match builds {
Some(count) => count[0] += 1
None => ()
}
let subtypes = run_hot_pipeline_raw_flatten_type_sec(mod_.type_sec)
module_state.raw_type_sec_snapshot = mod_.type_sec
module_state.raw_flat_subtypes = Some(subtypes)
subtypes
}
///|
fn run_hot_pipeline_required_raw_func_params(
mod_ : @lib.Module,
module_ctx : @ir.HotModuleContext,
func_idx : @lib.FuncIdx,
module_state : HotPipelineModuleState,
) -> Array[@lib.ValType] {
let subtypes = run_hot_pipeline_require_raw_types(mod_, module_state, None)
match
run_hot_pipeline_defined_func_params_indexed(mod_, func_idx, Some(subtypes)) {
Some(params) => params
None => run_hot_pipeline_required_func_params(module_ctx, func_idx)
}
}
///|
test "DAE2 dispatch keeps live GC parameters after raw signature lookup reuse" {
let input = pass_test_module_from_wat(
(
#|(module
#| (type $s (struct (field i32)))
#| (import "m" "f" (func $f (param (ref null $s)) (result i32)))
#| (func $helper (param i64 (ref null $s)) (result i32)
#| local.get 1 call $f)
#| (func (export "run") (param (ref null $s)) (result i32)
#| i64.const 7 local.get 0 call $helper))
),
)
for pass_name in ["dae2", "dae2-optimizing"] {
let output = run_hot_pipeline(input, HotPipelineOptions::new(), [pass_name]).unwrap()
@validate.validate_module(output).unwrap()
let context = @ir.hot_module_context_from_module(output)
let expected_ref = @lib.ValType::ref_null(
@lib.HeapType::new(@lib.TypeIdx::new(0U)),
)
assert_eq(
@ir.hot_module_resolve_func_type(context, @lib.FuncIdx::new(1U)),
Some(@lib.FuncType::new([expected_ref], [@lib.ValType::i32()])),
)
assert_eq(
@ir.hot_module_resolve_func_type(context, @lib.FuncIdx::new(2U)),
Some(@lib.FuncType::new([expected_ref], [@lib.ValType::i32()])),
)
let @lib.CodeSec(funcs) = output.code_sec.unwrap()
let @lib.Func(_, @lib.Expr(helper_body)) = funcs[0]
assert_eq(helper_body, [
@lib.Instruction::local_get(@lib.LocalIdx::new(0U)),
@lib.Instruction::call(@lib.FuncIdx::new(0U)),
])
}
}
///|
fn run_hot_pipeline_required_func_results(
module_ctx : @ir.HotModuleContext,
func_idx : @lib.FuncIdx,
) -> Array[@lib.ValType] {
match @ir.hot_module_resolve_func_type(module_ctx, func_idx) {
Some(@lib.FuncType(_, results)) => results
None => []
}
}
///|
fn run_hot_pipeline_raw_local_type(
params : Array[@lib.ValType],
locals : @lib.Locals,
local_id : Int32,
) -> @lib.ValType {
if local_id < params.length() {
return params[local_id]
}
locals.get(local_id - params.length())
}
///|
fn run_hot_pipeline_raw_rewritten_local_type(
params : Array[@lib.ValType],
locals : @lib.Locals,
extra_local_types : Array[@lib.ValType],
local_id : Int32,
) -> @lib.ValType? {
if local_id < params.length() {
return Some(params[local_id])
}
let body_local_id = local_id - params.length()
if body_local_id < locals.length() {
return Some(locals.get(body_local_id))
}
let extra_id = body_local_id - locals.length()
extra_local_types.get(extra_id)
}
///|
fn run_hot_pipeline_raw_non_null_ref_type(
val_type : @lib.ValType,
) -> @lib.ValType? {
match val_type {
@lib.RefTypeValType(ref_type) =>
Some(
@lib.ValType::ref_type(
@lib.RefType::new(
false,
ref_type.get_heap_type(),
exact=ref_type.is_exact(),
),
),
)
_ => None
}
}
///|
fn run_hot_pipeline_raw_reference_type_matches_same_heap(
actual : @lib.ValType,
expected : @lib.ValType,
) -> Bool {
if actual == expected {
return true
}
match (actual, expected) {
(@lib.RefTypeValType(actual_ref), @lib.RefTypeValType(expected_ref)) => {
if actual_ref.get_heap_type() != expected_ref.get_heap_type() {
return false
}
if expected_ref.is_exact() && !actual_ref.is_exact() {
return false
}
if actual_ref.is_nullable() && !expected_ref.is_nullable() {
return false
}
true
}
_ => false
}
}
///|
fn run_hot_pipeline_raw_abs_heap_type_is_subtype(
source : @lib.AbsHeapType,
target : @lib.AbsHeapType,
) -> Bool {
if source == target {
return true
}
match (source, target) {
(@lib.NoneAbsHeapType, @lib.StructAbsHeapType)
| (@lib.NoneAbsHeapType, @lib.ArrayAbsHeapType)
| (@lib.NoneAbsHeapType, @lib.EqAbsHeapType)
| (@lib.NoneAbsHeapType, @lib.AnyAbsHeapType)
| (@lib.NoFuncAbsHeapType, @lib.FuncAbsHeapType)
| (@lib.NoFuncAbsHeapType, @lib.AnyAbsHeapType)
| (@lib.NoExternAbsHeapType, @lib.ExternAbsHeapType)
| (@lib.NoExnAbsHeapType, @lib.ExnAbsHeapType)
| (@lib.I31AbsHeapType, @lib.EqAbsHeapType)
| (@lib.I31AbsHeapType, @lib.AnyAbsHeapType)
| (@lib.StructAbsHeapType, @lib.EqAbsHeapType)
| (@lib.StructAbsHeapType, @lib.AnyAbsHeapType)
| (@lib.ArrayAbsHeapType, @lib.EqAbsHeapType)
| (@lib.ArrayAbsHeapType, @lib.AnyAbsHeapType)
| (@lib.EqAbsHeapType, @lib.AnyAbsHeapType)
| (@lib.FuncAbsHeapType, @lib.AnyAbsHeapType)
| (@lib.ExternAbsHeapType, @lib.AnyAbsHeapType) => true
_ => false
}
}
///|
fn run_hot_pipeline_raw_comptype_matches_abs_heap(
comp_type : @lib.CompType,
abs_heap : @lib.AbsHeapType,
) -> Bool {
match comp_type {
@lib.StructCompType(_) =>
abs_heap == @lib.AbsHeapType::struct_() ||
abs_heap == @lib.AbsHeapType::eq() ||
abs_heap == @lib.AbsHeapType::any()
@lib.ArrayCompType(_) =>
abs_heap == @lib.AbsHeapType::array() ||
abs_heap == @lib.AbsHeapType::eq() ||
abs_heap == @lib.AbsHeapType::any()
@lib.FuncCompType(_, _) =>
abs_heap == @lib.AbsHeapType::func() ||
abs_heap == @lib.AbsHeapType::any()
@lib.ContCompType(_) => abs_heap == @lib.AbsHeapType::cont()
}
}
///|
fn run_hot_pipeline_raw_subtype_as_heap_type_is_subtype(
module_ctx : @ir.HotModuleContext,
subtype : @lib.SubType,
target : @lib.HeapType,
fuel : Int32,
) -> Bool {
if fuel <= 0 {
return false
}
let (is_final, supers, comp_type) = match subtype {
@lib.CompTypeSubType(_, comp_type) => (false, [], comp_type)
@lib.SubType(is_final, supers, _, comp_type) =>
(is_final, supers, comp_type)
}
match target {
@lib.AbsHeapTypeHeapType(abs_heap) => {
if run_hot_pipeline_raw_comptype_matches_abs_heap(comp_type, abs_heap) {
return true
}
if is_final {
return false
}
}
_ => ()
}
for super_idx in supers {
if run_hot_pipeline_raw_heap_type_is_subtype(
module_ctx,
@lib.HeapType::new(super_idx),
target,
fuel - 1,
) {
return true
}
}
false
}
///|
fn run_hot_pipeline_raw_heap_type_is_subtype(
module_ctx : @ir.HotModuleContext,
source : @lib.HeapType,
target : @lib.HeapType,
fuel : Int32,
) -> Bool {
if source == target {
return true
}
if fuel <= 0 {
return false
}
match (source, target) {
(@lib.AbsHeapTypeHeapType(source_abs), @lib.AbsHeapTypeHeapType(target_abs)) =>
run_hot_pipeline_raw_abs_heap_type_is_subtype(source_abs, target_abs)
(@lib.HeapType(source_idx), _) =>
match @ir.hot_module_resolve_subtype(module_ctx, source_idx) {
Some(subtype) =>
run_hot_pipeline_raw_subtype_as_heap_type_is_subtype(
module_ctx,
subtype,
target,
fuel - 1,
)
None => false
}
(_, @lib.HeapType(target_idx)) =>
match source {
@lib.AbsHeapTypeHeapType(@lib.NoneAbsHeapType) =>
match @ir.hot_module_resolve_subtype(module_ctx, target_idx) {
Some(subtype) =>
match subtype.get_comptype() {
@lib.StructCompType(_) | @lib.ArrayCompType(_) => true
_ => false
}
None => false
}
@lib.AbsHeapTypeHeapType(@lib.NoFuncAbsHeapType) =>
match @ir.hot_module_resolve_subtype(module_ctx, target_idx) {
Some(subtype) =>
match subtype.get_comptype() {
@lib.FuncCompType(_, _) => true
_ => false
}
None => false
}
_ => false
}
_ => false
}
}
///|
fn run_hot_pipeline_raw_reference_type_matches(
module_ctx : @ir.HotModuleContext,
actual : @lib.ValType,
expected : @lib.ValType,
) -> Bool {
if run_hot_pipeline_raw_reference_type_matches_same_heap(actual, expected) {
return true
}
match (actual, expected) {
(@lib.RefTypeValType(actual_ref), @lib.RefTypeValType(expected_ref)) => {
if actual_ref.is_nullable() && !expected_ref.is_nullable() {
return false
}
if expected_ref.is_exact() {
return actual_ref.is_exact() &&
actual_ref.get_heap_type() == expected_ref.get_heap_type()
}
run_hot_pipeline_raw_heap_type_is_subtype(
module_ctx,
actual_ref.get_heap_type(),
expected_ref.get_heap_type(),
512,
)
}
_ => false
}
}
///|
fn run_hot_pipeline_raw_default_instr_for_local(
params : Array[@lib.ValType],
locals : @lib.Locals,
local_id : Int32,
) -> @lib.Instruction {
match run_hot_pipeline_raw_local_type(params, locals, local_id) {
@lib.NumTypeValType(@lib.I32NumType) =>
@lib.Instruction::i32_const(@lib.I32::new(0))
@lib.NumTypeValType(@lib.I64NumType) =>
@lib.Instruction::i64_const(@lib.I64::new(0L))
@lib.NumTypeValType(@lib.F32NumType) =>
@lib.Instruction::f32_const(@lib.F32::new(0.0))
@lib.NumTypeValType(@lib.F64NumType) =>
@lib.Instruction::f64_const(@lib.F64::new(0.0))
@lib.VecTypeValType =>
@lib.Instruction::v128_const(
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
)
@lib.RefTypeValType(rt) => @lib.Instruction::ref_null_type(rt)
_ =>
abort(
"hot pipeline raw ssa-nomerge rewrite hit unsupported default local type",
)
}
}
///|
fn run_hot_pipeline_raw_ssa_nomerge_is_zero_v128_const(
instr : @lib.Instruction,
) -> Bool {
match instr {
@lib.V128Const(
b0,
b1,
b2,
b3,
b4,
b5,
b6,
b7,
b8,
b9,
b10,
b11,
b12,
b13,
b14,
b15
) =>
b0 == b'\x00' &&
b1 == b'\x00' &&
b2 == b'\x00' &&
b3 == b'\x00' &&
b4 == b'\x00' &&
b5 == b'\x00' &&
b6 == b'\x00' &&
b7 == b'\x00' &&
b8 == b'\x00' &&
b9 == b'\x00' &&
b10 == b'\x00' &&
b11 == b'\x00' &&
b12 == b'\x00' &&
b13 == b'\x00' &&
b14 == b'\x00' &&
b15 == b'\x00'
_ => false
}
}
///|
fn run_hot_pipeline_raw_ssa_nomerge_expand_zero_v128_defaults_body(
instrs : Array[@lib.Instruction],
) -> (Array[@lib.Instruction], Bool) {
let out : Array[@lib.Instruction] = []
let mut changed = false
for instr in instrs {
if run_hot_pipeline_raw_ssa_nomerge_is_zero_v128_const(instr) {
out.push(@lib.Instruction::i32_const(@lib.I32::new(0)))
out.push(@lib.Instruction::i32x4_splat())
changed = true
} else {
match instr {
@lib.Block(block_type, @lib.Expr(body)) => {
let (rewritten_body, body_changed) = run_hot_pipeline_raw_ssa_nomerge_expand_zero_v128_defaults_body(
body,
)
out.push(
if body_changed {
@lib.Instruction::block(
block_type,
@lib.Expr::new(rewritten_body),
)
} else {
instr
},
)
changed = changed || body_changed
}
@lib.Loop(block_type, @lib.Expr(body)) => {
let (rewritten_body, body_changed) = run_hot_pipeline_raw_ssa_nomerge_expand_zero_v128_defaults_body(
body,
)
out.push(
if body_changed {
@lib.Instruction::loop_(
block_type,
@lib.Expr::new(rewritten_body),
)
} else {
instr
},
)
changed = changed || body_changed
}
@lib.If(block_type, then_body, else_body) => {
let (rewritten_then, then_changed) = run_hot_pipeline_raw_ssa_nomerge_expand_zero_v128_defaults_body(
then_body,
)
let mut instr_changed = then_changed
let rewritten_else = match else_body {
Some(body) => {
let (next_body, else_changed) = run_hot_pipeline_raw_ssa_nomerge_expand_zero_v128_defaults_body(
body,
)
instr_changed = instr_changed || else_changed
Some(next_body)
}
None => None
}
out.push(
if instr_changed {
@lib.Instruction::if_(block_type, rewritten_then, rewritten_else)
} else {
instr
},
)
changed = changed || instr_changed
}
@lib.TryTable(block_type, catches, @lib.Expr(body)) => {
let (rewritten_body, body_changed) = run_hot_pipeline_raw_ssa_nomerge_expand_zero_v128_defaults_body(
body,
)
out.push(
if body_changed {
@lib.Instruction::try_table(
block_type,
catches,
@lib.Expr::new(rewritten_body),
)
} else {
instr
},
)
changed = changed || body_changed
}
_ => out.push(instr)
}
}
}
(out, changed)
}
///|
fn run_hot_pipeline_raw_ssa_nomerge_expand_zero_v128_defaults_func(
func : @lib.Func,
) -> (@lib.Func, Bool) {
let @lib.Func(locals, @lib.Expr(instrs)) = func
let (rewritten_instrs, changed) = run_hot_pipeline_raw_ssa_nomerge_expand_zero_v128_defaults_body(
instrs,
)
if changed {
(@lib.Func::new(locals.copy(), @lib.Expr::new(rewritten_instrs)), true)
} else {
(func, false)
}
}
///|
fn run_hot_pipeline_raw_ssa_nomerge_expand_v128_defaults_from_gets_body(
original : Array[@lib.Instruction],
rewritten : Array[@lib.Instruction],
) -> (Array[@lib.Instruction], Bool) {
if original.length() != rewritten.length() {
return (rewritten, false)
}
let out : Array[@lib.Instruction] = []
let mut changed = false
for idx = 0; idx < rewritten.length(); idx = idx + 1 {
match (original[idx], rewritten[idx]) {
(@lib.LocalGet(_), instr) =>
if run_hot_pipeline_raw_ssa_nomerge_is_zero_v128_const(instr) {
out.push(@lib.Instruction::i32_const(@lib.I32::new(0)))
out.push(@lib.Instruction::i32x4_splat())
changed = true
} else {
out.push(rewritten[idx])
}
(
@lib.Block(_, @lib.Expr(original_body)),
@lib.Block(block_type, @lib.Expr(rewritten_body)),
) => {
let (next_body, body_changed) = run_hot_pipeline_raw_ssa_nomerge_expand_v128_defaults_from_gets_body(
original_body, rewritten_body,
)
out.push(
if body_changed {
@lib.Instruction::block(block_type, @lib.Expr::new(next_body))
} else {
rewritten[idx]
},
)
changed = changed || body_changed
}
(
@lib.Loop(_, @lib.Expr(original_body)),
@lib.Loop(block_type, @lib.Expr(rewritten_body)),
) => {
let (next_body, body_changed) = run_hot_pipeline_raw_ssa_nomerge_expand_v128_defaults_from_gets_body(
original_body, rewritten_body,
)
out.push(
if body_changed {
@lib.Instruction::loop_(block_type, @lib.Expr::new(next_body))
} else {
rewritten[idx]
},
)
changed = changed || body_changed
}
(
@lib.TryTable(_, _, @lib.Expr(original_body)),
@lib.TryTable(block_type, catches, @lib.Expr(rewritten_body)),
) => {
let (next_body, body_changed) = run_hot_pipeline_raw_ssa_nomerge_expand_v128_defaults_from_gets_body(
original_body, rewritten_body,
)
out.push(
if body_changed {
@lib.Instruction::try_table(
block_type,
catches,
@lib.Expr::new(next_body),
)
} else {
rewritten[idx]
},
)
changed = changed || body_changed
}
(
@lib.If(_, original_then, original_else),
@lib.If(block_type, rewritten_then, rewritten_else),
) => {
let (next_then, then_changed) = run_hot_pipeline_raw_ssa_nomerge_expand_v128_defaults_from_gets_body(
original_then, rewritten_then,
)
let mut instr_changed = then_changed
let next_else = match (original_else, rewritten_else) {
(Some(original_body), Some(rewritten_body)) => {
let (body, body_changed) = run_hot_pipeline_raw_ssa_nomerge_expand_v128_defaults_from_gets_body(
original_body, rewritten_body,
)
instr_changed = instr_changed || body_changed
Some(body)
}
_ => rewritten_else
}
out.push(
if instr_changed {
@lib.Instruction::if_(block_type, next_then, next_else)
} else {
rewritten[idx]
},
)
changed = changed || instr_changed
}
_ => out.push(rewritten[idx])
}
}
(out, changed)
}
///|
fn run_hot_pipeline_raw_ssa_nomerge_expand_v128_defaults_from_gets_func(
original : @lib.Func,
rewritten : @lib.Func,
) -> (@lib.Func, Bool) {
let @lib.Func(_, @lib.Expr(original_instrs)) = original
let @lib.Func(locals, @lib.Expr(rewritten_instrs)) = rewritten
let (expanded_instrs, changed) = run_hot_pipeline_raw_ssa_nomerge_expand_v128_defaults_from_gets_body(
original_instrs, rewritten_instrs,
)
if changed {
(@lib.Func::new(locals.copy(), @lib.Expr::new(expanded_instrs)), true)
} else {
(rewritten, false)
}
}
///|
fn run_hot_pipeline_raw_append_local_read(
out : Array[@lib.Instruction],
source_aliases : Array[@lib.LocalIdx],
canonical_aliases : Array[@lib.LocalIdx],
initialized_locals : @bitset.BitSet,
params : Array[@lib.ValType],
locals : @lib.Locals,
local_id : Int32,
) -> Unit {
if local_id >= params.length() &&
source_aliases[local_id] == canonical_aliases[local_id] &&
!initialized_locals[local_id] {
out.push(
run_hot_pipeline_raw_default_instr_for_local(params, locals, local_id),
)
} else {
out.push(@lib.Instruction::local_get(source_aliases[local_id]))
}
}
///|
fn run_hot_pipeline_raw_aliases_equal(
lhs : Array[@lib.LocalIdx],
rhs : Array[@lib.LocalIdx],
) -> Bool {
if lhs.length() != rhs.length() {
return false
}
for idx = 0; idx < lhs.length(); idx = idx + 1 {
if lhs[idx] != rhs[idx] {
return false
}
}
true
}
///|
priv struct RawSsaLabelTarget {
block_type : @lib.BlockType
result_types : Array[@lib.ValType]
is_loop : Bool
is_loop_param_proxy : Bool
is_loop_param_store : Bool
loop_param_scratch : @lib.LocalIdx?
loop_param_scratches : Array[@lib.LocalIdx]
needed_alias_copies : @bitset.BitSet
}
///|
fn run_hot_pipeline_raw_fresh_local(
params : Array[@lib.ValType],
locals : @lib.Locals,
local_id : Int32,
extra_local_types : Array[@lib.ValType],
) -> @lib.LocalIdx {
let fresh_local_id = params.length() +
locals.length() +
extra_local_types.length()
let local_type = match
run_hot_pipeline_raw_rewritten_local_type(
params, locals, extra_local_types, local_id,
) {
Some(val_type) => val_type
None => abort("ssa-nomerge fresh local source is out of range")
}
extra_local_types.push(local_type)
@lib.LocalIdx::new(fresh_local_id.reinterpret_as_uint())
}
///|
fn run_hot_pipeline_raw_fresh_typed_local(
params : Array[@lib.ValType],
locals : @lib.Locals,
val_type : @lib.ValType,
extra_local_types : Array[@lib.ValType],
) -> @lib.LocalIdx {
let fresh_local_id = params.length() +
locals.length() +
extra_local_types.length()
extra_local_types.push(val_type)
@lib.LocalIdx::new(fresh_local_id.reinterpret_as_uint())
}
///|
fn run_hot_pipeline_raw_collect_local_ref_counts(
instrs : Array[@lib.Instruction],
local_gets : Array[Int32],
local_sets : Array[Int32],
local_tees : Array[Int32],
) -> Unit {
for instr in instrs {
match instr {
@lib.LocalGet(@lib.LocalIdx(raw)) => {
let local_id = raw.reinterpret_as_int()
if local_id >= 0 && local_id < local_gets.length() {
local_gets[local_id] = local_gets[local_id] + 1
}
}
@lib.LocalSet(@lib.LocalIdx(raw)) => {
let local_id = raw.reinterpret_as_int()
if local_id >= 0 && local_id < local_sets.length() {
local_sets[local_id] = local_sets[local_id] + 1
}
}
@lib.LocalTee(@lib.LocalIdx(raw)) => {
let local_id = raw.reinterpret_as_int()
if local_id >= 0 && local_id < local_tees.length() {
local_tees[local_id] = local_tees[local_id] + 1
}
}
@lib.Block(_, @lib.Expr(body))
| @lib.Loop(_, @lib.Expr(body))
| @lib.TryTable(_, _, @lib.Expr(body)) =>
run_hot_pipeline_raw_collect_local_ref_counts(
body, local_gets, local_sets, local_tees,
)
@lib.If(_, then_body, else_body) => {
run_hot_pipeline_raw_collect_local_ref_counts(
then_body, local_gets, local_sets, local_tees,
)
match else_body {
Some(body) =>
run_hot_pipeline_raw_collect_local_ref_counts(
body, local_gets, local_sets, local_tees,
)
None => ()
}
}
@lib.Try(_, @lib.Expr(body), catches, _) => {
run_hot_pipeline_raw_collect_local_ref_counts(
body, local_gets, local_sets, local_tees,
)
for arm in catches {
let @lib.Expr(catch_body) = match arm {
@lib.LegacyCatch(_, body) | @lib.LegacyCatchAll(body) => body
}
run_hot_pipeline_raw_collect_local_ref_counts(
catch_body, local_gets, local_sets, local_tees,
)
}
}
_ => ()
}
}
}
///|
fn run_hot_pipeline_raw_compact_rewrite_local_refs(
instrs : Array[@lib.Instruction],
local_remap : Array[Int32],
) -> Array[@lib.Instruction] {
let rewritten : Array[@lib.Instruction] = []
for instr in instrs {
match instr {
@lib.LocalGet(@lib.LocalIdx(raw)) => {
let local_id = raw.reinterpret_as_int()
let mapped = if local_id >= 0 && local_id < local_remap.length() {
local_remap[local_id]
} else {
local_id
}
rewritten.push(
@lib.Instruction::local_get(
@lib.LocalIdx::new(mapped.reinterpret_as_uint()),
),
)
}
@lib.LocalSet(@lib.LocalIdx(raw)) => {
let local_id = raw.reinterpret_as_int()
let mapped = if local_id >= 0 && local_id < local_remap.length() {
local_remap[local_id]
} else {
local_id
}
rewritten.push(
@lib.Instruction::local_set(
@lib.LocalIdx::new(mapped.reinterpret_as_uint()),
),
)
}
@lib.LocalTee(@lib.LocalIdx(raw)) => {
let local_id = raw.reinterpret_as_int()
let mapped = if local_id >= 0 && local_id < local_remap.length() {
local_remap[local_id]
} else {
local_id
}
rewritten.push(
@lib.Instruction::local_tee(
@lib.LocalIdx::new(mapped.reinterpret_as_uint()),
),
)
}
@lib.Block(block_type, @lib.Expr(body)) =>
rewritten.push(
@lib.Instruction::block(
block_type,
@lib.Expr::new(
run_hot_pipeline_raw_compact_rewrite_local_refs(body, local_remap),
),
),
)
@lib.Loop(block_type, @lib.Expr(body)) =>
rewritten.push(
@lib.Instruction::loop_(
block_type,
@lib.Expr::new(
run_hot_pipeline_raw_compact_rewrite_local_refs(body, local_remap),
),
),
)
@lib.TryTable(block_type, catches, @lib.Expr(body)) =>
rewritten.push(
@lib.Instruction::try_table(
block_type,
catches,
@lib.Expr::new(
run_hot_pipeline_raw_compact_rewrite_local_refs(body, local_remap),
),
),
)
@lib.If(block_type, then_body, else_body) =>
rewritten.push(
@lib.Instruction::if_(
block_type,
run_hot_pipeline_raw_compact_rewrite_local_refs(
then_body, local_remap,
),
match else_body {
Some(body) =>
Some(
run_hot_pipeline_raw_compact_rewrite_local_refs(
body, local_remap,
),
)
None => None
},
),
)
_ => rewritten.push(instr)
}
}
rewritten
}
///|
fn run_hot_pipeline_raw_drop_unused_appended_locals(
instrs : Array[@lib.Instruction],
params : Array[@lib.ValType],
locals : @lib.Locals,
extra_local_types : Array[@lib.ValType],
) -> (Array[@lib.Instruction], Array[@lib.ValType], Bool) {
if extra_local_types.length() == 0 {
return (instrs, extra_local_types, false)
}
let base_local_count = params.length() + locals.length()
let total_local_count = base_local_count + extra_local_types.length()
let local_gets : Array[Int32] = Array::make(total_local_count, 0)
let local_sets : Array[Int32] = Array::make(total_local_count, 0)
let local_tees : Array[Int32] = Array::make(total_local_count, 0)
run_hot_pipeline_raw_collect_local_ref_counts(
instrs, local_gets, local_sets, local_tees,
)
let local_remap : Array[Int32] = []
for local_id = 0; local_id < total_local_count; local_id = local_id + 1 {
local_remap.push(local_id)
}
let compacted_extra_types : Array[@lib.ValType] = []
let mut changed = false
for extra_id = 0
extra_id < extra_local_types.length()
extra_id = extra_id + 1 {
let local_id = base_local_count + extra_id
let used = local_gets[local_id] > 0 ||
local_sets[local_id] > 0 ||
local_tees[local_id] > 0
if used {
let next_local_id = base_local_count + compacted_extra_types.length()
if next_local_id != local_id {
local_remap[local_id] = next_local_id
changed = true
}
compacted_extra_types.push(extra_local_types[extra_id])
} else {
changed = true
local_remap[local_id] = -1
}
}
if !changed {
return (instrs, extra_local_types, false)
}
(
run_hot_pipeline_raw_compact_rewrite_local_refs(instrs, local_remap),
compacted_extra_types,
true,
)
}
///|
fn run_hot_pipeline_raw_compact_unused_locals_after_prefix(
func : @lib.Func,
params : Array[@lib.ValType],
prefix_local_count : Int32,
) -> (@lib.Func, Bool) {
let @lib.Func(locals, @lib.Expr(instrs)) = func
if locals.length() <= prefix_local_count {
return (func, false)
}
let param_count = params.length()
let total_local_count = param_count + locals.length()
let local_gets : Array[Int32] = Array::make(total_local_count, 0)
let local_sets : Array[Int32] = Array::make(total_local_count, 0)
let local_tees : Array[Int32] = Array::make(total_local_count, 0)
run_hot_pipeline_raw_collect_local_ref_counts(
instrs, local_gets, local_sets, local_tees,
)
let local_remap : Array[Int32] = []
for local_id = 0; local_id < total_local_count; local_id = local_id + 1 {
local_remap.push(local_id)
}
let compacted_local_types : Array[@lib.ValType] = []
let mut changed = false
for local_idx = 0; local_idx < locals.length(); local_idx = local_idx + 1 {
let local_id = param_count + local_idx
let is_prefix_local = local_idx < prefix_local_count
let used = local_gets[local_id] > 0 ||
local_sets[local_id] > 0 ||
local_tees[local_id] > 0
if is_prefix_local || used {
let next_local_id = param_count + compacted_local_types.length()
if next_local_id != local_id {
local_remap[local_id] = next_local_id
changed = true
}
compacted_local_types.push(locals.get(local_idx))
} else {
local_remap[local_id] = -1
changed = true
}
}
if !changed {
return (func, false)
}
(
@lib.Func::new(
@lib.Locals::from_types(compacted_local_types),
@lib.Expr::new(
run_hot_pipeline_raw_compact_rewrite_local_refs(instrs, local_remap),
),
),
true,
)
}
///|
fn run_hot_pipeline_raw_note_appended_local_first_refs(
instrs : Array[@lib.Instruction],
appended_start : Int32,
first_refs : Array[Int32],
next_rank : Int32,
) -> Int32 {
let mut next = next_rank
for instr in instrs {
match instr {
@lib.LocalGet(@lib.LocalIdx(raw))
| @lib.LocalSet(@lib.LocalIdx(raw))
| @lib.LocalTee(@lib.LocalIdx(raw)) => {
let local_id = raw.reinterpret_as_int()
if local_id >= appended_start &&
local_id < first_refs.length() &&
first_refs[local_id] < 0 {
first_refs[local_id] = next
next += 1
}
}
@lib.Block(_, @lib.Expr(body))
| @lib.Loop(_, @lib.Expr(body))
| @lib.TryTable(_, _, @lib.Expr(body)) =>
next = run_hot_pipeline_raw_note_appended_local_first_refs(
body, appended_start, first_refs, next,
)
@lib.If(_, then_body, else_body) => {
next = run_hot_pipeline_raw_note_appended_local_first_refs(
then_body, appended_start, first_refs, next,
)
match else_body {
Some(body) =>
next = run_hot_pipeline_raw_note_appended_local_first_refs(
body, appended_start, first_refs, next,
)
None => ()
}
}
_ => ()
}
}
next
}
///|
fn run_hot_pipeline_raw_instrs_reference_local(
instrs : Array[@lib.Instruction],
local_id : Int32,
) -> Bool {
for instr in instrs {
match instr {
@lib.LocalGet(@lib.LocalIdx(raw))
| @lib.LocalSet(@lib.LocalIdx(raw))
| @lib.LocalTee(@lib.LocalIdx(raw)) =>
if raw.reinterpret_as_int() == local_id {
return true
}
@lib.Block(_, @lib.Expr(body))
| @lib.Loop(_, @lib.Expr(body))
| @lib.TryTable(_, _, @lib.Expr(body)) =>
if run_hot_pipeline_raw_instrs_reference_local(body, local_id) {
return true
}
@lib.If(_, then_body, else_body) => {
if run_hot_pipeline_raw_instrs_reference_local(then_body, local_id) {
return true
}
match else_body {
Some(body) =>
if run_hot_pipeline_raw_instrs_reference_local(body, local_id) {
return true
}
None => ()
}
}
_ => ()
}
}
false
}
///|
fn run_hot_pipeline_raw_note_typed_loop_proxy_scratch_locals(
instrs : Array[@lib.Instruction],
appended_start : Int32,
priorities : Array[Bool],
) -> Unit {
for idx = 0; idx < instrs.length(); idx = idx + 1 {
match instrs[idx] {
@lib.LocalSet(@lib.LocalIdx(raw)) if idx + 1 < instrs.length() => {
let local_id = raw.reinterpret_as_int()
if local_id >= appended_start && local_id < priorities.length() {
match instrs[idx + 1] {
@lib.Loop(@lib.VoidBlockType, @lib.Expr(body)) =>
if run_hot_pipeline_raw_instrs_reference_local(body, local_id) {
priorities[local_id] = true
}
_ => ()
}
}
}
@lib.Block(_, @lib.Expr(body))
| @lib.Loop(_, @lib.Expr(body))
| @lib.TryTable(_, _, @lib.Expr(body)) =>
run_hot_pipeline_raw_note_typed_loop_proxy_scratch_locals(
body, appended_start, priorities,
)
@lib.If(_, then_body, else_body) => {
run_hot_pipeline_raw_note_typed_loop_proxy_scratch_locals(
then_body, appended_start, priorities,
)
match else_body {
Some(body) =>
run_hot_pipeline_raw_note_typed_loop_proxy_scratch_locals(
body, appended_start, priorities,
)
None => ()
}
}
_ => ()
}
}
}
///|
fn run_hot_pipeline_raw_order_appended_numeric_locals_by_first_ref(
func : @lib.Func,
params : Array[@lib.ValType],
prefix_local_count : Int32,
) -> (@lib.Func, Bool) {
let @lib.Func(locals, @lib.Expr(instrs)) = func
if locals.length() <= prefix_local_count {
return (func, false)
}
for param in params {
if !run_hot_pipeline_raw_numeric_local_type(param) {
return (func, false)
}
}
for local_idx = 0; local_idx < locals.length(); local_idx = local_idx + 1 {
if !run_hot_pipeline_raw_numeric_local_type(locals.get(local_idx)) {
return (func, false)
}
}
let param_count = params.length()
let total_local_count = param_count + locals.length()
let appended_start = param_count + prefix_local_count
let first_refs : Array[Int32] = Array::make(total_local_count, -1)
ignore(
run_hot_pipeline_raw_note_appended_local_first_refs(
instrs, appended_start, first_refs, 0,
),
)
let priority_scratch : Array[Bool] = Array::make(total_local_count, false)
run_hot_pipeline_raw_note_typed_loop_proxy_scratch_locals(
instrs, appended_start, priority_scratch,
)
let local_remap : Array[Int32] = []
for local_id = 0; local_id < total_local_count; local_id = local_id + 1 {
local_remap.push(local_id)
}
let mut changed = false
let handled : Array[Bool] = Array::make(locals.length(), false)
for local_idx = prefix_local_count
local_idx < locals.length()
local_idx = local_idx + 1 {
if handled[local_idx] {
continue
}
let ty = locals.get(local_idx)
let slots : Array[Int32] = []
let ordered : Array[Int32] = []
for scan_idx = local_idx
scan_idx < locals.length()
scan_idx = scan_idx + 1 {
if !handled[scan_idx] && locals.get(scan_idx) == ty {
handled[scan_idx] = true
let local_id = param_count + scan_idx
slots.push(local_id)
let mut insert_at = ordered.length()
let rank = first_refs[local_id]
for order_idx = 0
order_idx < ordered.length()
order_idx = order_idx + 1 {
let other = ordered[order_idx]
let other_rank = first_refs[other]
let scratch_priority = priority_scratch[local_id]
let other_scratch_priority = priority_scratch[other]
let comes_before = if scratch_priority != other_scratch_priority {
scratch_priority
} else if rank < 0 {
false
} else if other_rank < 0 {
true
} else {
rank < other_rank
}
if comes_before {
insert_at = order_idx
break
}
}
ordered.insert(insert_at, local_id)
}
}
for idx = 0; idx < ordered.length(); idx = idx + 1 {
let old_local_id = ordered[idx]
let new_local_id = slots[idx]
if old_local_id != new_local_id {
local_remap[old_local_id] = new_local_id
changed = true
}
}
}
if !changed {
return (func, false)
}
(
@lib.Func::new(
locals.copy(),
@lib.Expr::new(
run_hot_pipeline_raw_compact_rewrite_local_refs(instrs, local_remap),
),
),
true,
)
}
///|
fn run_hot_pipeline_raw_branch_targets_depth(
instrs : Array[@lib.Instruction],
target_depth : UInt32,
) -> Bool {
for instr in instrs {
match instr {
@lib.Br(@lib.LabelIdx(raw)) | @lib.BrIf(@lib.LabelIdx(raw)) =>
if raw == target_depth {
return true
}
@lib.BrTable(labels, @lib.LabelIdx(default_raw)) => {
if default_raw == target_depth {
return true
}
for label in labels {
let @lib.LabelIdx(raw) = label
if raw == target_depth {
return true
}
}
}
@lib.Block(_, @lib.Expr(body))
| @lib.Loop(_, @lib.Expr(body))
| @lib.TryTable(_, _, @lib.Expr(body)) =>
if run_hot_pipeline_raw_branch_targets_depth(body, target_depth + 1U) {
return true
}
@lib.If(_, then_body, else_body) => {
if run_hot_pipeline_raw_branch_targets_depth(
then_body,
target_depth + 1U,
) {
return true
}
match else_body {
Some(body) =>
if run_hot_pipeline_raw_branch_targets_depth(
body,
target_depth + 1U,
) {
return true
}
None => ()
}
}
_ => ()
}
}
false
}
///|
fn run_hot_pipeline_raw_drop_void_blocks_wrapping_loops_instrs(
instrs : Array[@lib.Instruction],
) -> (Array[@lib.Instruction], Bool) {
let next : Array[@lib.Instruction] = []
let mut changed = false
for instr in instrs {
match instr {
@lib.Block(@lib.VoidBlockType, @lib.Expr(body)) => {
let (rewritten_body, body_changed) = run_hot_pipeline_raw_drop_void_blocks_wrapping_loops_instrs(
body,
)
if rewritten_body.length() == 1 {
match rewritten_body[0] {
@lib.Loop(@lib.VoidBlockType, @lib.Expr(loop_body)) =>
if !run_hot_pipeline_raw_branch_targets_depth(loop_body, 1U) {
next.push(rewritten_body[0])
changed = true
continue
}
_ => ()
}
}
next.push(
@lib.Instruction::block(
@lib.BlockType::void_(),
@lib.Expr::new(rewritten_body),
),
)
if body_changed {
changed = true
}
}
@lib.Loop(block_type, @lib.Expr(body)) => {
let (rewritten_body, body_changed) = run_hot_pipeline_raw_drop_void_blocks_wrapping_loops_instrs(
body,
)
next.push(
@lib.Instruction::loop_(block_type, @lib.Expr::new(rewritten_body)),
)
if body_changed {
changed = true
}
}
@lib.TryTable(block_type, catches, @lib.Expr(body)) => {
let (rewritten_body, body_changed) = run_hot_pipeline_raw_drop_void_blocks_wrapping_loops_instrs(
body,
)
next.push(
@lib.Instruction::try_table(
block_type,
catches,
@lib.Expr::new(rewritten_body),
),
)
if body_changed {
changed = true
}
}
@lib.If(cond, then_body, else_body) => {
let (rewritten_then, then_changed) = run_hot_pipeline_raw_drop_void_blocks_wrapping_loops_instrs(
then_body,
)
let mut if_changed = then_changed
let rewritten_else = match else_body {
Some(body) => {
let (next_else, else_changed) = run_hot_pipeline_raw_drop_void_blocks_wrapping_loops_instrs(
body,
)
if else_changed {
if_changed = true
}
Some(next_else)
}
None => None
}
next.push(@lib.Instruction::if_(cond, rewritten_then, rewritten_else))
if if_changed {
changed = true
}
}
other => next.push(other)
}
}
(next, changed)
}
///|
fn run_hot_pipeline_raw_drop_void_blocks_wrapping_loops(
func : @lib.Func,
) -> (@lib.Func, Bool) {
let @lib.Func(locals, @lib.Expr(instrs)) = func
let (rewritten, changed) = run_hot_pipeline_raw_drop_void_blocks_wrapping_loops_instrs(
instrs,
)
if !changed {
return (func, false)
}
(@lib.Func::new(locals.copy(), @lib.Expr::new(rewritten)), true)
}
///|
fn run_hot_pipeline_raw_count_void_blocks_ending_br0(
instrs : Array[@lib.Instruction],
) -> Int32 {
let mut count = 0
for instr in instrs {
match instr {
@lib.Block(@lib.VoidBlockType, @lib.Expr(body)) => {
if body.length() > 0 {
match body[body.length() - 1] {
@lib.Br(@lib.LabelIdx(raw)) if raw == 0U => count += 1
_ => ()
}
}
count += run_hot_pipeline_raw_count_void_blocks_ending_br0(body)
}
@lib.Loop(_, @lib.Expr(body)) | @lib.TryTable(_, _, @lib.Expr(body)) =>
count += run_hot_pipeline_raw_count_void_blocks_ending_br0(body)
@lib.If(_, then_body, else_body) => {
count += run_hot_pipeline_raw_count_void_blocks_ending_br0(then_body)
match else_body {
Some(body) =>
count += run_hot_pipeline_raw_count_void_blocks_ending_br0(body)
None => ()
}
}
_ => ()
}
}
count
}
///|
fn run_hot_pipeline_raw_count_appended_i32_locals_by_use(
func : @lib.Func,
params : Array[@lib.ValType],
prefix_local_count : Int32,
) -> (Int32, Int32) {
let @lib.Func(locals, @lib.Expr(instrs)) = func
if locals.length() <= prefix_local_count {
return (0, 0)
}
let param_count = params.length()
let total_local_count = param_count + locals.length()
let local_gets : Array[Int32] = Array::make(total_local_count, 0)
let local_sets : Array[Int32] = Array::make(total_local_count, 0)
let local_tees : Array[Int32] = Array::make(total_local_count, 0)
run_hot_pipeline_raw_collect_local_ref_counts(
instrs, local_gets, local_sets, local_tees,
)
let mut used = 0
let mut unused = 0
for local_idx = prefix_local_count
local_idx < locals.length()
local_idx = local_idx + 1 {
if locals.get(local_idx) == @lib.ValType::i32() {
let local_id = param_count + local_idx
if local_gets[local_id] > 0 ||
local_sets[local_id] > 0 ||
local_tees[local_id] > 0 {
used += 1
} else {
unused += 1
}
}
}
(used, unused)
}
///|
fn run_hot_pipeline_raw_append_unused_i32_locals(
func : @lib.Func,
count : Int32,
) -> (@lib.Func, Bool) {
if count <= 0 {
return (func, false)
}
let @lib.Func(locals, body) = func
let next_types : Array[@lib.ValType] = []
for local_idx = 0; local_idx < locals.length(); local_idx = local_idx + 1 {
next_types.push(locals.get(local_idx))
}
for idx = 0; idx < count; idx = idx + 1 {
next_types.push(@lib.ValType::i32())
}
(@lib.Func::new(@lib.Locals::from_types(next_types), body), true)
}
///|
fn run_hot_pipeline_raw_reserve_dead_tail_i32_holes_before_final_tees(
func : @lib.Func,
params : Array[@lib.ValType],
prefix_local_count : Int32,
) -> (@lib.Func, Bool) {
let @lib.Func(locals, @lib.Expr(instrs)) = func
if run_hot_pipeline_raw_count_void_blocks_ending_br0(instrs) < 2 {
return (func, false)
}
let param_count = params.length()
let mut last_tee = -1
let mut prev_tee = -1
for instr in instrs {
match instr {
@lib.LocalTee(@lib.LocalIdx(raw)) => {
prev_tee = last_tee
last_tee = raw.reinterpret_as_int()
}
_ => ()
}
}
if prev_tee < param_count + prefix_local_count || last_tee != prev_tee + 1 {
return (func, false)
}
let prev_local_idx = prev_tee - param_count
let last_local_idx = last_tee - param_count
if prev_local_idx < 0 || last_local_idx >= locals.length() {
return (func, false)
}
if locals.get(prev_local_idx) != @lib.ValType::i32() ||
locals.get(last_local_idx) != @lib.ValType::i32() {
return (func, false)
}
if prev_local_idx >= 2 {
let local_gets : Array[Int32] = Array::make(
param_count + locals.length(),
0,
)
let local_sets : Array[Int32] = Array::make(
param_count + locals.length(),
0,
)
let local_tees : Array[Int32] = Array::make(
param_count + locals.length(),
0,
)
run_hot_pipeline_raw_collect_local_ref_counts(
instrs, local_gets, local_sets, local_tees,
)
let first_hole_id = param_count + prev_local_idx - 2
let second_hole_id = param_count + prev_local_idx - 1
if locals.get(prev_local_idx - 1) == @lib.ValType::i32() &&
locals.get(prev_local_idx - 2) == @lib.ValType::i32() &&
local_gets[first_hole_id] == 0 &&
local_sets[first_hole_id] == 0 &&
local_tees[first_hole_id] == 0 &&
local_gets[second_hole_id] == 0 &&
local_sets[second_hole_id] == 0 &&
local_tees[second_hole_id] == 0 {
return (func, false)
}
}
let total_local_count = param_count + locals.length()
let local_remap : Array[Int32] = []
for local_id = 0; local_id < total_local_count; local_id = local_id + 1 {
if local_id >= prev_tee {
local_remap.push(local_id + 2)
} else {
local_remap.push(local_id)
}
}
let next_types : Array[@lib.ValType] = []
for local_idx = 0; local_idx < locals.length(); local_idx = local_idx + 1 {
if local_idx == prev_local_idx {
next_types.push(@lib.ValType::i32())
next_types.push(@lib.ValType::i32())
}
next_types.push(locals.get(local_idx))
}
(
@lib.Func::new(
@lib.Locals::from_types(next_types),
@lib.Expr::new(
run_hot_pipeline_raw_compact_rewrite_local_refs(instrs, local_remap),
),
),
true,
)
}
///|
fn run_hot_pipeline_raw_local_type_bucket_index(
buckets : Array[@lib.ValType],
ty : @lib.ValType,
) -> Int32 {
for idx = 0; idx < buckets.length(); idx = idx + 1 {
if buckets[idx] == ty {
return idx
}
}
-1
}
///|
fn run_hot_pipeline_raw_numeric_local_type(ty : @lib.ValType) -> Bool {
match ty {
@lib.NumTypeValType(_) => true
_ => false
}
}
///|
fn run_hot_pipeline_raw_group_body_locals_by_type_func(
func : @lib.Func,
params : Array[@lib.ValType],
mapping_slots~ : Array[Int32]? = None,
) -> (@lib.Func, Array[Int32]?) {
let @lib.Func(locals, @lib.Expr(instrs)) = func
if locals.length() <= 1 {
return (func, None)
}
for param in params {
if !run_hot_pipeline_raw_numeric_local_type(param) {
return (func, None)
}
}
let runs = locals.runs()
let buckets : Array[@lib.ValType] = []
let counts : Array[Int32] = []
let run_buckets : Array[Int32] = []
for run in runs {
if run.count == 0U {
run_buckets.push(-1)
continue
}
if !run_hot_pipeline_raw_numeric_local_type(run.vt) {
return (func, None)
}
let mut bucket = run_hot_pipeline_raw_local_type_bucket_index(
buckets,
run.vt,
)
if bucket < 0 {
bucket = buckets.length()
buckets.push(run.vt)
counts.push(0)
}
run_buckets.push(bucket)
counts[bucket] += run.count.reinterpret_as_int()
}
let grouped_locals = @lib.Locals::new(
Array::makei(buckets.length(), fn(i) {
@lib.LocalRun::new(counts[i].to_uint(), buckets[i])
}),
)
// This also preserves the old normalization of adjacent declaration groups.
if grouped_locals == locals {
return (func, None)
}
let param_count = params.length()
let total = param_count + locals.length()
let remap = Array::makei(total, fn(i) { i })
match mapping_slots {
Some(count) => count[0] += remap.length()
None => ()
}
let starts : Array[Int32] = []
let mut next = param_count
for count in counts {
starts.push(next)
next += count
}
let mut old = param_count
for i = 0; i < runs.length(); i = i + 1 {
let count = runs[i].count.reinterpret_as_int()
if count == 0 {
continue
}
let bucket = run_buckets[i]
for j = 0; j < count; j = j + 1 {
remap[old + j] = starts[bucket] + j
}
old += count
starts[bucket] += count
}
(
@lib.Func::new(
grouped_locals,
@lib.Expr::new(
run_hot_pipeline_raw_compact_rewrite_local_refs(instrs, remap),
),
),
Some(remap),
)
}
///|
fn run_hot_pipeline_raw_group_body_locals_by_type_module(
mod_ : @lib.Module,
touched~ : Array[Bool]? = None,
context_builds~ : Array[Int32]? = None,
) -> @lib.Module {
// Opaque name bytes cannot be safely permuted without decoding them.
if mod_.name_sec is None && mod_.raw_name_sec_payload is Some(_) {
return mod_
}
match mod_.code_sec {
Some(@lib.CodeSec(funcs)) => {
let imported_func_count = run_hot_pipeline_imported_func_count(mod_)
ignore(context_builds)
let params_by_func = raw_numeric_group_function_params(mod_)
let next_funcs : Array[@lib.Func] = Array::new(capacity=funcs.length())
let remaps : Array[Array[Int32]?] = []
let mut changed = false
for code_idx = 0; code_idx < funcs.length(); code_idx = code_idx + 1 {
if touched is Some(selected) &&
(code_idx >= selected.length() || !selected[code_idx]) {
next_funcs.push(funcs[code_idx])
remaps.push(None)
continue
}
let func_idx = @lib.FuncIdx::new(
(imported_func_count + code_idx).reinterpret_as_uint(),
)
let @lib.FuncIdx(raw_func) = func_idx
let params = params_by_func[raw_func.reinterpret_as_int()]
let (grouped, remap) = run_hot_pipeline_raw_group_body_locals_by_type_func(
funcs[code_idx],
params,
)
next_funcs.push(grouped)
remaps.push(remap)
if remap is Some(_) {
changed = true
}
}
if changed {
let candidate = mod_.with_code_sec(@lib.CodeSec::new(next_funcs))
match rl_rewrite_name_sec(mod_.name_sec, imported_func_count, remaps) {
Some(names) => candidate.with_name_sec(names)
None if mod_.name_sec is Some(_) => candidate.without_name_sec()
None => candidate
}
} else {
mod_
}
}
None => mod_
}
}
///|
// Numeric local grouping needs only signatures; no body, global initializer or
// descriptor-feature scan participates in this rewrite's admission decisions.
fn raw_numeric_group_function_params(
mod_ : @lib.Module,
) -> Array[Array[@lib.ValType]] {
let subtypes = run_hot_pipeline_raw_flatten_type_sec(mod_.type_sec)
let type_indices = dae_collect_func_type_indices(mod_)
type_indices.map(fn(index) {
match index {
@lib.TypeIdx(raw) =>
match subtypes.get(raw.reinterpret_as_int()) {
Some(subtype) =>
match subtype.get_comptype() {
@lib.FuncCompType(params, _) => params
_ =>
abort(
"hot module context function indices must resolve to function types",
)
}
None =>
abort(
"hot module context function indices must resolve within the subtype table",
)
}
@lib.RecIdx(_) =>
abort(
"hot module context function indices must not use rec-group indices",
)
}
})
}
///|
fn run_hot_pipeline_raw_instrs_have_br_table(
instrs : Array[@lib.Instruction],
) -> Bool {
for instr in instrs {
match instr {
@lib.BrTable(_, _) => return true
@lib.Block(_, @lib.Expr(body))
| @lib.Loop(_, @lib.Expr(body))
| @lib.TryTable(_, _, @lib.Expr(body)) =>
if run_hot_pipeline_raw_instrs_have_br_table(body) {
return true
}
@lib.If(_, then_body, else_body) => {
if run_hot_pipeline_raw_instrs_have_br_table(then_body) {
return true
}
match else_body {
Some(body) =>
if run_hot_pipeline_raw_instrs_have_br_table(body) {
return true
}
None => ()
}
}
_ => ()
}
}
false
}
///|
fn run_hot_pipeline_raw_instrs_have_function_exit_br_table(
instrs : Array[@lib.Instruction],
control_depth : UInt32,
) -> Bool {
for instr in instrs {
match instr {
@lib.BrTable(labels, fallback) => {
let @lib.LabelIdx(fallback_depth) = fallback
if fallback_depth == control_depth {
return true
}
for label in labels {
let @lib.LabelIdx(label_depth) = label
if label_depth == control_depth {
return true
}
}
}
@lib.Block(_, @lib.Expr(body))
| @lib.Loop(_, @lib.Expr(body))
| @lib.TryTable(_, _, @lib.Expr(body)) =>
if run_hot_pipeline_raw_instrs_have_function_exit_br_table(
body,
control_depth + 1U,
) {
return true
}
@lib.If(_, then_body, else_body) => {
if run_hot_pipeline_raw_instrs_have_function_exit_br_table(
then_body,
control_depth + 1U,
) {
return true
}
match else_body {
Some(body) =>
if run_hot_pipeline_raw_instrs_have_function_exit_br_table(
body,
control_depth + 1U,
) {
return true
}
None => ()
}
}
_ => ()
}
}
false
}
///|
fn run_hot_pipeline_raw_instrs_have_plain_load(
instrs : Array[@lib.Instruction],
) -> Bool {
for instr in instrs {
if run_hot_pipeline_optimize_instructions_instr_is_plain_load(instr) {
return true
}
match instr {
@lib.Block(_, @lib.Expr(body))
| @lib.Loop(_, @lib.Expr(body))
| @lib.TryTable(_, _, @lib.Expr(body)) =>
if run_hot_pipeline_raw_instrs_have_plain_load(body) {
return true
}
@lib.If(_, then_body, else_body) => {
if run_hot_pipeline_raw_instrs_have_plain_load(then_body) {
return true
}
match else_body {
Some(body) =>
if run_hot_pipeline_raw_instrs_have_plain_load(body) {
return true
}
None => ()
}
}
_ => ()
}
}
false
}
///|
fn run_hot_pipeline_raw_simplify_locals_has_load_result_copy_boundary(
instrs : Array[@lib.Instruction],
) -> Bool {
for idx = 0; idx + 3 < instrs.length(); idx = idx + 1 {
if run_hot_pipeline_optimize_instructions_instr_is_plain_load(instrs[idx]) {
match (instrs[idx + 1], instrs[idx + 2], instrs[idx + 3]) {
(@lib.LocalSet(written), @lib.LocalGet(read), @lib.Return) if written ==
read => return true
_ => ()
}
}
}
for instr in instrs {
match instr {
@lib.Block(_, @lib.Expr(body))
| @lib.Loop(_, @lib.Expr(body))
| @lib.TryTable(_, _, @lib.Expr(body)) =>
if run_hot_pipeline_raw_simplify_locals_has_load_result_copy_boundary(
body,
) {
return true
}
@lib.If(_, then_body, else_body) => {
if run_hot_pipeline_raw_simplify_locals_has_load_result_copy_boundary(
then_body,
) {
return true
}
match else_body {
Some(body) =>
if run_hot_pipeline_raw_simplify_locals_has_load_result_copy_boundary(
body,
) {
return true
}
None => ()
}
}
_ => ()
}
}
false
}
///|
fn run_hot_pipeline_raw_compact_appended_one_use_scratch_locals(
instrs : Array[@lib.Instruction],
params : Array[@lib.ValType],
locals : @lib.Locals,
extra_local_types : Array[@lib.ValType],
) -> (Array[@lib.Instruction], @lib.Locals, Array[@lib.ValType]) {
// Keep this compaction deliberately narrow for the current debug-WASI
// structured scratch gaps: broader allocator/linker fixtures still carry exact
// Binaryen-local-budget guards. The abs 598 / 705 family has 8-10 body locals,
// abs 1527 reaches 11-12 body locals around appended/existing one-use scratch
// slots, abs 1533 is a small loaded nested-compare tee chain, and abs 1528 is
// a branch-bearing loaded loop that appends dead branch-value tees above a
// large existing local budget. Require an actual load for every widened case,
// exclude br_table shapes, and require either no local.set traffic for the
// small compare chain or branch/call structure for the large loop so unrelated
// startup/default-local shuffles stay canonical.
let has_appended_locals = extra_local_types.length() > 0
let old_structured_load_window = if has_appended_locals {
locals.length() >= 8 && locals.length() <= 11
} else {
locals.length() >= 8 && locals.length() <= 12
}
let loaded_no_table = run_hot_pipeline_raw_instrs_have_plain_load(instrs) &&
!run_hot_pipeline_raw_instrs_have_br_table(instrs)
let small_loaded_compare_tee_window = locals.length() >= 4 &&
locals.length() <= 7 &&
loaded_no_table
let large_loaded_branch_dead_tee_window = locals.length() >= 18 &&
locals.length() <= 20 &&
loaded_no_table &&
run_hot_pipeline_raw_instrs_have_branch(instrs) &&
run_hot_pipeline_raw_count_call_like(instrs) >= 4
if !(old_structured_load_window ||
small_loaded_compare_tee_window ||
large_loaded_branch_dead_tee_window) {
return (instrs, locals, extra_local_types)
}
if locals.length() > 10 &&
!(loaded_no_table &&
(locals.length() <= 12 || large_loaded_branch_dead_tee_window)) {
return (instrs, locals, extra_local_types)
}
let base_local_count = params.length() + locals.length()
let total_local_count = base_local_count + extra_local_types.length()
let local_gets : Array[Int32] = Array::make(total_local_count, 0)
let local_sets : Array[Int32] = Array::make(total_local_count, 0)
let local_tees : Array[Int32] = Array::make(total_local_count, 0)
run_hot_pipeline_raw_collect_local_ref_counts(
instrs, local_gets, local_sets, local_tees,
)
if small_loaded_compare_tee_window {
for local_id = 0; local_id < total_local_count; local_id = local_id + 1 {
if local_sets[local_id] > 0 {
return (instrs, locals, extra_local_types)
}
}
}
let base_used : Array[Bool] = Array::make(base_local_count, false)
for local_id = 0; local_id < base_local_count; local_id = local_id + 1 {
base_used[local_id] = local_gets[local_id] > 0 ||
local_sets[local_id] > 0 ||
local_tees[local_id] > 0
}
let local_remap : Array[Int32] = []
for local_id = 0; local_id < total_local_count; local_id = local_id + 1 {
local_remap.push(local_id)
}
let extra_remapped_to_base : Array[Bool] = Array::make(
extra_local_types.length(),
false,
)
let mut changed = false
for extra_id = 0
extra_id < extra_local_types.length()
extra_id = extra_id + 1 {
let local_id = base_local_count + extra_id
let is_one_set_get = local_sets[local_id] == 1 &&
local_gets[local_id] == 1 &&
local_tees[local_id] == 0
let is_one_dead_tee = local_sets[local_id] == 0 &&
local_gets[local_id] == 0 &&
local_tees[local_id] == 1
let is_one_tee_get_chain = small_loaded_compare_tee_window &&
local_sets[local_id] == 0 &&
local_gets[local_id] > 0 &&
local_tees[local_id] == 1
if !(is_one_set_get || is_one_dead_tee || is_one_tee_get_chain) {
continue
}
let target_type = match
run_hot_pipeline_raw_rewritten_local_type(
params, locals, extra_local_types, local_id,
) {
Some(val_type) => val_type
None => continue
}
let mut reuse_local = -1
for base_id = params.length()
base_id < base_local_count
base_id = base_id + 1 {
if !base_used[base_id] &&
run_hot_pipeline_raw_local_type(params, locals, base_id) == target_type {
reuse_local = base_id
break
}
}
if reuse_local >= 0 {
local_remap[local_id] = reuse_local
base_used[reuse_local] = true
extra_remapped_to_base[extra_id] = true
changed = true
}
}
for local_id = params.length()
local_id < base_local_count
local_id = local_id + 1 {
let is_one_set_get = local_sets[local_id] == 1 &&
local_gets[local_id] == 1 &&
local_tees[local_id] == 0
let is_one_dead_tee = local_sets[local_id] == 0 &&
local_gets[local_id] == 0 &&
local_tees[local_id] == 1
let is_one_tee_get_chain = small_loaded_compare_tee_window &&
local_sets[local_id] == 0 &&
local_gets[local_id] > 0 &&
local_tees[local_id] == 1
if !(is_one_set_get || is_one_dead_tee || is_one_tee_get_chain) {
continue
}
let target_type = run_hot_pipeline_raw_local_type(params, locals, local_id)
let mut reuse_local = -1
for base_id = params.length()
base_id < base_local_count
base_id = base_id + 1 {
if !base_used[base_id] &&
run_hot_pipeline_raw_local_type(params, locals, base_id) == target_type {
reuse_local = base_id
break
}
}
if reuse_local >= 0 {
local_remap[local_id] = reuse_local
base_used[reuse_local] = true
base_used[local_id] = false
changed = true
}
}
if !changed {
return (instrs, locals, extra_local_types)
}
let compacted_extra_types : Array[@lib.ValType] = []
for extra_id = 0
extra_id < extra_local_types.length()
extra_id = extra_id + 1 {
let local_id = base_local_count + extra_id
if !extra_remapped_to_base[extra_id] {
let new_local_id = base_local_count + compacted_extra_types.length()
local_remap[local_id] = new_local_id
compacted_extra_types.push(extra_local_types[extra_id])
}
}
let compacted_instrs = run_hot_pipeline_raw_compact_rewrite_local_refs(
instrs, local_remap,
)
let mut trimmed_local_count = locals.length()
if compacted_extra_types.length() == 0 {
let final_total_local_count = base_local_count
let final_gets : Array[Int32] = Array::make(final_total_local_count, 0)
let final_sets : Array[Int32] = Array::make(final_total_local_count, 0)
let final_tees : Array[Int32] = Array::make(final_total_local_count, 0)
run_hot_pipeline_raw_collect_local_ref_counts(
compacted_instrs, final_gets, final_sets, final_tees,
)
let min_trimmed_local_count = if large_loaded_branch_dead_tee_window {
19
} else {
0
}
while trimmed_local_count > min_trimmed_local_count {
let local_id = params.length() + trimmed_local_count - 1
if final_gets[local_id] > 0 ||
final_sets[local_id] > 0 ||
final_tees[local_id] > 0 {
break
}
trimmed_local_count = trimmed_local_count - 1
}
}
let compacted_local_types : Array[@lib.ValType] = []
for local_idx = 0; local_idx < trimmed_local_count; local_idx = local_idx + 1 {
compacted_local_types.push(locals.get(local_idx))
}
(
compacted_instrs,
@lib.Locals::from_types(compacted_local_types),
compacted_extra_types,
)
}
///|
fn run_hot_pipeline_raw_compact_existing_one_use_scratch_func(
func : @lib.Func,
params : Array[@lib.ValType],
) -> @lib.Func? {
let @lib.Func(locals, @lib.Expr(instrs)) = func
let (compacted_instrs, compacted_locals, compacted_extra_types) = run_hot_pipeline_raw_compact_appended_one_use_scratch_locals(
instrs,
params,
locals,
[],
)
if compacted_extra_types.length() != 0 ||
compacted_locals.length() == locals.length() {
return None
}
Some(@lib.Func::new(compacted_locals, @lib.Expr::new(compacted_instrs)))
}
///|
fn run_hot_pipeline_raw_has_needed_alias_copy(
source_aliases : Array[@lib.LocalIdx],
target_aliases : Array[@lib.LocalIdx],
needed_alias_copies : @bitset.BitSet,
) -> Bool {
for local_id = 0; local_id < target_aliases.length(); local_id = local_id + 1 {
if needed_alias_copies[local_id] &&
source_aliases[local_id] != target_aliases[local_id] {
return true
}
}
false
}
///|
fn run_hot_pipeline_raw_append_alias_merge_copies_for_needed(
branch_instrs : Array[@lib.Instruction],
source_aliases : Array[@lib.LocalIdx],
target_aliases : Array[@lib.LocalIdx],
canonical_aliases : Array[@lib.LocalIdx],
initialized_locals : @bitset.BitSet,
needed_alias_copies : @bitset.BitSet,
result_types : Array[@lib.ValType],
params : Array[@lib.ValType],
locals : @lib.Locals,
extra_local_types : Array[@lib.ValType],
) -> Array[@lib.Instruction] {
if !run_hot_pipeline_raw_has_needed_alias_copy(
source_aliases, target_aliases, needed_alias_copies,
) {
return branch_instrs.copy()
}
let out = branch_instrs.copy()
let result_scratch : Array[@lib.LocalIdx] = []
for result_type in result_types {
result_scratch.push(
run_hot_pipeline_raw_fresh_typed_local(
params, locals, result_type, extra_local_types,
),
)
}
for idx = result_scratch.length() - 1; idx >= 0; idx = idx - 1 {
out.push(@lib.Instruction::local_set(result_scratch[idx]))
}
for local_id = 0; local_id < target_aliases.length(); local_id = local_id + 1 {
if !needed_alias_copies[local_id] ||
source_aliases[local_id] == target_aliases[local_id] {
continue
}
run_hot_pipeline_raw_append_local_read(
out, source_aliases, canonical_aliases, initialized_locals, params, locals,
local_id,
)
out.push(@lib.Instruction::local_set(target_aliases[local_id]))
}
for local_idx in result_scratch {
out.push(@lib.Instruction::local_get(local_idx))
}
out
}
///|
fn run_hot_pipeline_raw_canonical_aliases(
base_local_count : Int32,
) -> Array[@lib.LocalIdx] {
let canonical : Array[@lib.LocalIdx] = []
for local_id = 0; local_id < base_local_count; local_id = local_id + 1 {
canonical.push(@lib.LocalIdx::new(local_id.reinterpret_as_uint()))
}
canonical
}
///|
fn run_hot_pipeline_raw_initialized_locals(
base_local_count : Int32,
param_count : Int32,
) -> @bitset.BitSet {
let initialized = @bitset.BitSet::new(base_local_count)
for local_id = 0; local_id < base_local_count; local_id = local_id + 1 {
initialized[local_id] = local_id < param_count
}
initialized
}
///|
fn run_hot_pipeline_raw_has_default_local_reads(
instrs : Array[@lib.Instruction],
param_count : Int32,
) -> Bool {
for instr in instrs {
match instr {
@lib.Block(_, @lib.Expr(body))
| @lib.Loop(_, @lib.Expr(body))
| @lib.TryTable(_, _, @lib.Expr(body)) =>
if run_hot_pipeline_raw_has_default_local_reads(body, param_count) {
return true
}
@lib.If(_, then_body, else_body) => {
if run_hot_pipeline_raw_has_default_local_reads(then_body, param_count) {
return true
}
match else_body {
Some(body) =>
if run_hot_pipeline_raw_has_default_local_reads(body, param_count) {
return true
}
None => ()
}
}
@lib.LocalGet(@lib.LocalIdx(raw)) =>
if raw.reinterpret_as_int() >= param_count {
return true
}
_ => ()
}
}
false
}
///|
fn run_hot_pipeline_raw_instrs_have_later_local_read(
instrs : Array[@lib.Instruction],
start_idx : Int32,
local_id : Int32,
) -> Bool {
for idx = start_idx; idx < instrs.length(); idx = idx + 1 {
if run_hot_pipeline_raw_simplify_locals_instr_is_unconditional_terminator(
instrs[idx],
) {
return false
}
match instrs[idx] {
@lib.Block(_, @lib.Expr(body))
| @lib.Loop(_, @lib.Expr(body))
| @lib.TryTable(_, _, @lib.Expr(body)) =>
if run_hot_pipeline_raw_instrs_have_later_local_read(body, 0, local_id) {
return true
}
@lib.If(_, then_body, else_body) => {
if run_hot_pipeline_raw_instrs_have_later_local_read(
then_body, 0, local_id,
) {
return true
}
match else_body {
Some(body) =>
if run_hot_pipeline_raw_instrs_have_later_local_read(
body, 0, local_id,
) {
return true
}
None => ()
}
}
@lib.Try(_, @lib.Expr(body), catches, _) =>
if sl_legacy_try_has_local_access(body, catches, local_id, true) {
return true
}
@lib.LocalGet(@lib.LocalIdx(raw)) =>
if raw.reinterpret_as_int() == local_id {
return true
}
_ => ()
}
}
false
}
///|
fn run_hot_pipeline_raw_instr_reads_local(
instr : @lib.Instruction,
local_id : Int32,
) -> Bool {
match instr {
@lib.LocalGet(@lib.LocalIdx(raw)) => raw.reinterpret_as_int() == local_id
@lib.Block(_, @lib.Expr(body))
| @lib.Loop(_, @lib.Expr(body))
| @lib.TryTable(_, _, @lib.Expr(body)) =>
run_hot_pipeline_raw_instrs_have_later_local_read(body, 0, local_id)
@lib.If(_, then_body, else_body) => {
if run_hot_pipeline_raw_instrs_have_later_local_read(
then_body, 0, local_id,
) {
return true
}
match else_body {
Some(body) =>
run_hot_pipeline_raw_instrs_have_later_local_read(body, 0, local_id)
None => false
}
}
@lib.Try(_, @lib.Expr(body), catches, _) =>
sl_legacy_try_has_local_access(body, catches, local_id, true)
_ => false
}
}
///|
fn run_hot_pipeline_raw_instrs_have_later_local_read_before_next_local_write(
instrs : Array[@lib.Instruction],
start_idx : Int32,
local_id : Int32,
) -> Bool {
for idx = start_idx; idx < instrs.length(); idx = idx + 1 {
if run_hot_pipeline_raw_simplify_locals_instr_is_unconditional_terminator(
instrs[idx],
) {
return false
}
if run_hot_pipeline_raw_simplify_locals_instr_writes_local(
instrs[idx],
local_id,
) {
return false
}
if run_hot_pipeline_raw_instr_reads_local(instrs[idx], local_id) {
return true
}
}
false
}
///|
fn run_hot_pipeline_raw_instrs_have_later_local_write(
instrs : Array[@lib.Instruction],
start_idx : Int32,
local_id : Int32,
) -> Bool {
for idx = start_idx; idx < instrs.length(); idx = idx + 1 {
match instrs[idx] {
@lib.Block(_, @lib.Expr(body))
| @lib.Loop(_, @lib.Expr(body))
| @lib.TryTable(_, _, @lib.Expr(body)) =>
if run_hot_pipeline_raw_instrs_have_later_local_write(body, 0, local_id) {
return true
}
@lib.If(_, then_body, else_body) => {
if run_hot_pipeline_raw_instrs_have_later_local_write(
then_body, 0, local_id,
) {
return true
}
match else_body {
Some(body) =>
if run_hot_pipeline_raw_instrs_have_later_local_write(
body, 0, local_id,
) {
return true
}
None => ()
}
}
@lib.Try(_, @lib.Expr(body), catches, _) =>
if sl_legacy_try_has_local_access(body, catches, local_id, false) {
return true
}
@lib.LocalSet(@lib.LocalIdx(raw)) | @lib.LocalTee(@lib.LocalIdx(raw)) =>
if raw.reinterpret_as_int() == local_id {
return true
}
_ => ()
}
}
false
}
///|
fn run_hot_pipeline_raw_mark_future_local_accesses(
instrs : Array[@lib.Instruction],
start_idx : Int32,
accesses : @bitset.BitSet,
reads : Bool,
) -> Unit {
for idx = start_idx; idx < instrs.length(); idx = idx + 1 {
match instrs[idx] {
@lib.Block(_, @lib.Expr(body)) =>
run_hot_pipeline_raw_mark_future_local_accesses(
body, 0, accesses, reads,
)
@lib.If(_, then_body, else_body) => {
run_hot_pipeline_raw_mark_future_local_accesses(
then_body, 0, accesses, reads,
)
match else_body {
Some(body) =>
run_hot_pipeline_raw_mark_future_local_accesses(
body, 0, accesses, reads,
)
None => ()
}
}
@lib.LocalGet(@lib.LocalIdx(raw)) =>
if reads {
accesses[raw.reinterpret_as_int()] = true
}
@lib.LocalSet(@lib.LocalIdx(raw)) | @lib.LocalTee(@lib.LocalIdx(raw)) =>
if !reads {
accesses[raw.reinterpret_as_int()] = true
}
_ => ()
}
}
}
///|
fn run_hot_pipeline_raw_future_local_accesses(
instrs : Array[@lib.Instruction],
start_idx : Int32,
inherited : @bitset.BitSet,
reads : Bool,
) -> @bitset.BitSet {
let future = inherited.copy()
run_hot_pipeline_raw_mark_future_local_accesses(
instrs, start_idx, future, reads,
)
future
}
///|
fn run_hot_pipeline_raw_future_local_reads_before_writes(
instrs : Array[@lib.Instruction],
start_idx : Int32,
inherited : @bitset.BitSet,
label_stack : Array[RawSsaLabelTarget],
) -> @bitset.BitSet {
ssa_nomerge_raw_live_before(
instrs,
start_idx,
inherited,
label_stack.map(target => target.needed_alias_copies),
)
}
///|
fn run_hot_pipeline_raw_label_target(
module_ctx : @ir.HotModuleContext,
block_type : @lib.BlockType,
is_loop : Bool,
needed_alias_copies : @bitset.BitSet,
) -> RawSsaLabelTarget? {
match @ir.hot_module_resolve_block_results(module_ctx, block_type) {
Some(result_types) =>
Some({
block_type,
result_types,
is_loop,
is_loop_param_proxy: false,
is_loop_param_store: false,
loop_param_scratch: None,
loop_param_scratches: [],
needed_alias_copies,
})
None => None
}
}
///|
fn run_hot_pipeline_raw_push_label_target(
label_stack : Array[RawSsaLabelTarget],
label_target : RawSsaLabelTarget,
) -> Array[RawSsaLabelTarget] {
let nested = label_stack.copy()
nested.push(label_target)
nested
}
///|
fn run_hot_pipeline_raw_in_branching_value_block(
instrs : Array[@lib.Instruction],
label_stack : Array[RawSsaLabelTarget],
) -> Bool {
match label_stack.last() {
Some(label_target) =>
!label_target.is_loop &&
label_target.result_types.length() > 0 &&
run_hot_pipeline_raw_has_plain_br(instrs)
None => false
}
}
///|
fn run_hot_pipeline_raw_resolve_label_target(
label_stack : Array[RawSsaLabelTarget],
label_idx : @lib.LabelIdx,
) -> RawSsaLabelTarget? {
let @lib.LabelIdx(raw) = label_idx
let depth = raw.reinterpret_as_int()
let stack_idx = label_stack.length() - depth - 1
if stack_idx < 0 {
return None
}
label_stack.get(stack_idx)
}
///|
fn run_hot_pipeline_raw_label_inc(label_idx : @lib.LabelIdx) -> @lib.LabelIdx {
let @lib.LabelIdx(raw) = label_idx
@lib.LabelIdx::new(raw + 1U)
}
///|
fn run_hot_pipeline_raw_label_add(
label_idx : @lib.LabelIdx,
increment : UInt32,
) -> @lib.LabelIdx {
let @lib.LabelIdx(raw) = label_idx
@lib.LabelIdx::new(raw + increment)
}
///|
fn run_hot_pipeline_raw_catch_label_add(
catch_ : @lib.Catch,
increment : UInt32,
) -> @lib.Catch {
match catch_ {
@lib.Catch(tag_idx, label_idx) =>
@lib.Catch::new(
tag_idx,
run_hot_pipeline_raw_label_add(label_idx, increment),
)
@lib.CatchRef(tag_idx, label_idx) =>
@lib.Catch::ref_(
tag_idx,
run_hot_pipeline_raw_label_add(label_idx, increment),
)
@lib.CatchAll(label_idx) =>
@lib.Catch::all(run_hot_pipeline_raw_label_add(label_idx, increment))
@lib.CatchAllRef(label_idx) =>
@lib.Catch::all_ref(run_hot_pipeline_raw_label_add(label_idx, increment))
}
}
///|
fn run_hot_pipeline_raw_increment_try_table_catch_labels(
instrs : Array[@lib.Instruction],
) -> Array[@lib.Instruction] {
let rewritten : Array[@lib.Instruction] = []
for instr in instrs {
match instr {
@lib.Block(block_type, @lib.Expr(body)) =>
rewritten.push(
@lib.Instruction::block(
block_type,
@lib.Expr::new(
run_hot_pipeline_raw_increment_try_table_catch_labels(body),
),
),
)
@lib.Loop(block_type, @lib.Expr(body)) =>
rewritten.push(
@lib.Instruction::loop_(
block_type,
@lib.Expr::new(
run_hot_pipeline_raw_increment_try_table_catch_labels(body),
),
),
)
@lib.TryTable(block_type, catches, @lib.Expr(body)) => {
let shifted_catches : Array[@lib.Catch] = []
for catch_ in catches {
shifted_catches.push(run_hot_pipeline_raw_catch_label_add(catch_, 1U))
}
rewritten.push(
@lib.Instruction::try_table(
block_type,
shifted_catches,
@lib.Expr::new(
run_hot_pipeline_raw_increment_try_table_catch_labels(body),
),
),
)
}
@lib.If(block_type, then_body, else_body) =>
rewritten.push(
@lib.Instruction::if_(
block_type,
run_hot_pipeline_raw_increment_try_table_catch_labels(then_body),
match else_body {
Some(body) =>
Some(
run_hot_pipeline_raw_increment_try_table_catch_labels(body),
)
None => None
},
),
)
_ => rewritten.push(instr)
}
}
rewritten
}
///|
fn run_hot_pipeline_raw_plain_br_before_local_write(
instrs : Array[@lib.Instruction],
start_idx : Int32,
local_id : Int32,
) -> Bool {
let mut idx = start_idx
while idx < instrs.length() {
match instrs[idx] {
@lib.LocalSet(@lib.LocalIdx(raw)) | @lib.LocalTee(@lib.LocalIdx(raw)) =>
if raw.reinterpret_as_int() == local_id {
return false
}
@lib.Br(_) => return true
@lib.Block(_, @lib.Expr(body))
| @lib.Loop(_, @lib.Expr(body))
| @lib.TryTable(_, _, @lib.Expr(body)) =>
if run_hot_pipeline_raw_plain_br_before_local_write(body, 0, local_id) {
return true
}
@lib.If(_, then_body, else_body) => {
if run_hot_pipeline_raw_plain_br_before_local_write(
then_body, 0, local_id,
) {
return true
}
match else_body {
Some(body) =>
if run_hot_pipeline_raw_plain_br_before_local_write(
body, 0, local_id,
) {
return true
}
None => ()
}
}
_ => ()
}
idx = idx + 1
}
false
}
///|
fn run_hot_pipeline_raw_local_tee_has_plain_br_before_next_write(
instrs : Array[@lib.Instruction],
local_id : Int32,
) -> Bool {
for idx = 0; idx < instrs.length(); idx = idx + 1 {
match instrs[idx] {
@lib.LocalTee(@lib.LocalIdx(raw)) =>
if raw.reinterpret_as_int() == local_id &&
run_hot_pipeline_raw_plain_br_before_local_write(
instrs,
idx + 1,
local_id,
) {
return true
}
@lib.Block(_, @lib.Expr(body))
| @lib.Loop(_, @lib.Expr(body))
| @lib.TryTable(_, _, @lib.Expr(body)) =>
if run_hot_pipeline_raw_local_tee_has_plain_br_before_next_write(
body, local_id,
) {
return true
}
@lib.If(_, then_body, else_body) => {
if run_hot_pipeline_raw_local_tee_has_plain_br_before_next_write(
then_body, local_id,
) {
return true
}
match else_body {
Some(body) =>
if run_hot_pipeline_raw_local_tee_has_plain_br_before_next_write(
body, local_id,
) {
return true
}
None => ()
}
}
_ => ()
}
}
false
}
///|
fn run_hot_pipeline_raw_next_br_needs_canonical_alias(
instrs : Array[@lib.Instruction],
next_idx : Int32,
label_stack : Array[RawSsaLabelTarget],
local_id : Int32,
) -> Bool {
let mut idx = next_idx
while idx < instrs.length() {
match instrs[idx] {
@lib.LocalSet(@lib.LocalIdx(raw)) => {
if raw.reinterpret_as_int() == local_id {
return false
}
idx = idx + 1
continue
}
@lib.LocalGet(_) => {
if idx + 1 >= instrs.length() {
return false
}
match instrs[idx + 1] {
@lib.LocalSet(@lib.LocalIdx(raw)) => {
if raw.reinterpret_as_int() == local_id {
return false
}
idx = idx + 2
continue
}
_ => return false
}
}
@lib.Br(label_idx) =>
return match
run_hot_pipeline_raw_resolve_label_target(label_stack, label_idx) {
Some(label_target) =>
!label_target.is_loop &&
local_id < label_target.needed_alias_copies.length() &&
label_target.needed_alias_copies[local_id]
None => false
}
_ => return false
}
}
false
}
///|
fn run_hot_pipeline_raw_empty_exit_depths(
label_stack : Array[RawSsaLabelTarget],
) -> @bitset.BitSet {
@bitset.BitSet::new(label_stack.length())
}
///|
fn run_hot_pipeline_raw_exit_depths_have_outer_exit(
exit_depths : @bitset.BitSet,
) -> Bool {
for depth = 1; depth < exit_depths.length(); depth = depth + 1 {
if exit_depths[depth] {
return true
}
}
false
}
///|
fn run_hot_pipeline_raw_instrs_have_local_read_before_hard_exit(
instrs : Array[@lib.Instruction],
start_idx : Int32,
) -> Bool {
let mut idx = start_idx
while idx < instrs.length() {
match instrs[idx] {
@lib.LocalGet(_) => return true
@lib.Return
| @lib.ReturnCall(_)
| @lib.ReturnCallIndirect(_, _)
| @lib.ReturnCallRef(_)
| @lib.Throw(_)
| @lib.ThrowRef
| @lib.Unreachable
| @lib.Br(_) => return false
@lib.Block(_, @lib.Expr(body))
| @lib.Loop(_, @lib.Expr(body))
| @lib.TryTable(_, _, @lib.Expr(body)) =>
if run_hot_pipeline_raw_instrs_have_local_read_before_hard_exit(body, 0) {
return true
}
@lib.If(_, then_body, else_body) => {
if run_hot_pipeline_raw_instrs_have_local_read_before_hard_exit(
then_body, 0,
) {
return true
}
match else_body {
Some(body) =>
if run_hot_pipeline_raw_instrs_have_local_read_before_hard_exit(
body, 0,
) {
return true
}
None => ()
}
}
_ => ()
}
idx = idx + 1
}
false
}
///|
fn run_hot_pipeline_raw_propagate_outer_exits(
exit_depths : @bitset.BitSet,
nested_exit_depths : @bitset.BitSet,
) -> Unit {
for depth = 1; depth < nested_exit_depths.length(); depth = depth + 1 {
if nested_exit_depths[depth] {
exit_depths[depth - 1] = true
}
}
}
///|
fn run_hot_pipeline_raw_enclosing_label_needs_alias_copy(
label_stack : Array[RawSsaLabelTarget],
local_id : Int32,
) -> Bool {
for label_target in label_stack {
if (!label_target.is_loop || label_target.is_loop_param_store) &&
local_id < label_target.needed_alias_copies.length() &&
label_target.needed_alias_copies[local_id] {
return true
}
}
false
}
///|
fn run_hot_pipeline_raw_branch_if_block_type(
module_ctx : @ir.HotModuleContext,
label_target : RawSsaLabelTarget,
) -> @lib.BlockType? {
match label_target.block_type {
@lib.VoidBlockType | @lib.ValTypeBlockType(_) =>
Some(label_target.block_type)
@lib.TypeIdxBlockType(type_idx) =>
match @ir.hot_module_resolve_type_func_type(module_ctx, type_idx) {
Some(@lib.FuncType(params, _)) =>
if params.length() == 0 {
Some(label_target.block_type)
} else {
None
}
None => None
}
}
}
///|
fn run_hot_pipeline_raw_bitset_union(
lhs : @bitset.BitSet,
rhs : @bitset.BitSet,
) -> @bitset.BitSet {
let merged = lhs.copy()
for idx = 0; idx < merged.length(); idx = idx + 1 {
if rhs[idx] {
merged[idx] = true
}
}
merged
}
///|
fn run_hot_pipeline_raw_loop_backedge_supported(
label_target : RawSsaLabelTarget,
) -> Bool {
if !label_target.is_loop || label_target.result_types.length() != 0 {
return false
}
match label_target.block_type {
@lib.VoidBlockType => true
_ => false
}
}
///|
fn run_hot_pipeline_raw_loop_param_and_result_types(
module_ctx : @ir.HotModuleContext,
block_type : @lib.BlockType,
) -> (Array[@lib.ValType], Array[@lib.ValType])? {
match block_type {
@lib.TypeIdxBlockType(type_idx) =>
match @ir.hot_module_resolve_type_func_type(module_ctx, type_idx) {
Some(@lib.FuncType(params, results)) =>
if params.length() > 0 {
Some((params, results))
} else {
None
}
None => None
}
_ => None
}
}
///|
fn run_hot_pipeline_raw_single_result_block_type(
result_types : Array[@lib.ValType],
) -> @lib.BlockType? {
if result_types.length() == 1 {
Some(@lib.BlockType::val_type(result_types[0]))
} else {
None
}
}
///|
fn run_hot_pipeline_raw_instrs_have_branch(
instrs : Array[@lib.Instruction],
) -> Bool {
for instr in instrs {
match instr {
@lib.Br(_)
| @lib.BrIf(_)
| @lib.BrTable(_, _)
| @lib.BrOnNull(_)
| @lib.BrOnNonNull(_)
| @lib.BrOnCast(_, _, _, _)
| @lib.BrOnCastFail(_, _, _, _)
| @lib.BrOnCastDescEq(_, _, _)
| @lib.BrOnCastDescEqFail(_, _, _) => return true
@lib.Block(_, @lib.Expr(body))
| @lib.Loop(_, @lib.Expr(body))
| @lib.TryTable(_, _, @lib.Expr(body)) =>
if run_hot_pipeline_raw_instrs_have_branch(body) {
return true
}
@lib.If(_, then_body, else_body) => {
if run_hot_pipeline_raw_instrs_have_branch(then_body) {
return true
}
match else_body {
Some(body) =>
if run_hot_pipeline_raw_instrs_have_branch(body) {
return true
}
None => ()
}
}
_ => ()
}
}
false
}
///|
fn run_hot_pipeline_raw_increment_label_depths(
depths : Array[UInt32],
) -> Array[UInt32] {
let incremented : Array[UInt32] = []
for depth in depths {
incremented.push(depth + 1U)
}
incremented
}
///|
fn run_hot_pipeline_raw_label_depth_is_allowed(
raw : UInt32,
depths : Array[UInt32],
) -> Bool {
for depth in depths {
if raw == depth {
return true
}
}
false
}
///|
fn run_hot_pipeline_raw_instrs_only_direct_loop_param_backedges_with_void_self(
instrs : Array[@lib.Instruction],
loop_depth : UInt32,
allow_ref_branches : Bool,
allow_try_table_branches : Bool,
void_self_loop_depths : Array[UInt32],
) -> Bool {
let mut saw_backedge = false
for instr in instrs {
match instr {
@lib.BrOnCastDescEq(_, _, _) | @lib.BrOnCastDescEqFail(_, _, _) =>
return false
@lib.Br(@lib.LabelIdx(raw)) | @lib.BrIf(@lib.LabelIdx(raw)) =>
if raw == loop_depth {
saw_backedge = true
} else if run_hot_pipeline_raw_label_depth_is_allowed(
raw, void_self_loop_depths,
) {
()
} else {
return false
}
@lib.BrOnCast(@lib.LabelIdx(raw), _, _, _)
| @lib.BrOnCastFail(@lib.LabelIdx(raw), _, _, _)
| @lib.BrOnNull(@lib.LabelIdx(raw))
| @lib.BrOnNonNull(@lib.LabelIdx(raw)) =>
if allow_ref_branches && raw == loop_depth {
saw_backedge = true
} else {
return false
}
@lib.BrTable(labels, @lib.LabelIdx(default_raw)) => {
let mut has_loop_target = default_raw == loop_depth
let mut has_enclosing_target = default_raw > loop_depth
let mut all_loop_targets = default_raw == loop_depth
let mut all_void_self_targets = run_hot_pipeline_raw_label_depth_is_allowed(
default_raw, void_self_loop_depths,
)
let mut all_loop_or_enclosing_targets = default_raw == loop_depth ||
default_raw > loop_depth
for label_idx in labels {
let @lib.LabelIdx(raw) = label_idx
if raw == loop_depth {
has_loop_target = true
}
if raw > loop_depth {
has_enclosing_target = true
}
if raw != loop_depth {
all_loop_targets = false
}
if !run_hot_pipeline_raw_label_depth_is_allowed(
raw, void_self_loop_depths,
) {
all_void_self_targets = false
}
if raw != loop_depth && raw <= loop_depth {
all_loop_or_enclosing_targets = false
}
}
if all_loop_targets {
saw_backedge = true
} else if all_void_self_targets {
()
} else if has_loop_target &&
has_enclosing_target &&
all_loop_or_enclosing_targets {
saw_backedge = true
} else {
return false
}
}
@lib.Block(block_type, @lib.Expr(body)) =>
if run_hot_pipeline_raw_instrs_have_branch(body) {
let child_void_self_loop_depths = run_hot_pipeline_raw_increment_label_depths(
void_self_loop_depths,
)
match block_type {
@lib.VoidBlockType => child_void_self_loop_depths.push(0U)
_ => ()
}
if run_hot_pipeline_raw_instrs_only_direct_loop_param_backedges_with_void_self(
body,
loop_depth + 1U,
allow_ref_branches,
allow_try_table_branches,
child_void_self_loop_depths,
) {
saw_backedge = true
} else {
match block_type {
@lib.VoidBlockType => ()
// Non-void nested blocks may contain only internal branches that
// produce the block value before the loop backedge. They are not
// themselves evidence that loop-param lowering is unsupported.
_ => ()
}
}
}
@lib.Loop(block_type, @lib.Expr(body)) =>
if run_hot_pipeline_raw_instrs_have_branch(body) {
let child_void_self_loop_depths = run_hot_pipeline_raw_increment_label_depths(
void_self_loop_depths,
)
match block_type {
@lib.VoidBlockType => child_void_self_loop_depths.push(0U)
_ => ()
}
let nested_loop_depth = match block_type {
@lib.VoidBlockType => loop_depth + 1U
_ => 0U
}
if run_hot_pipeline_raw_instrs_only_direct_loop_param_backedges_with_void_self(
body, nested_loop_depth, allow_ref_branches, allow_try_table_branches,
child_void_self_loop_depths,
) {
match block_type {
@lib.VoidBlockType => saw_backedge = true
_ => ()
}
} else {
return false
}
}
@lib.TryTable(_, _, @lib.Expr(body)) =>
if run_hot_pipeline_raw_instrs_have_branch(body) {
if !allow_try_table_branches ||
run_hot_pipeline_raw_try_table_body_has_exceptional_edge(body) {
return false
}
if run_hot_pipeline_raw_instrs_only_direct_loop_param_backedges_with_void_self(
body,
loop_depth + 1U,
allow_ref_branches,
allow_try_table_branches,
run_hot_pipeline_raw_increment_label_depths(void_self_loop_depths),
) {
saw_backedge = true
} else {
return false
}
}
@lib.If(_, then_body, else_body) => {
let child_void_self_loop_depths = run_hot_pipeline_raw_increment_label_depths(
void_self_loop_depths,
)
if run_hot_pipeline_raw_instrs_have_branch(then_body) {
if run_hot_pipeline_raw_instrs_only_direct_loop_param_backedges_with_void_self(
then_body,
loop_depth + 1U,
allow_ref_branches,
allow_try_table_branches,
child_void_self_loop_depths,
) {
saw_backedge = true
} else {
return false
}
}
match else_body {
Some(body) =>
if run_hot_pipeline_raw_instrs_have_branch(body) {
if run_hot_pipeline_raw_instrs_only_direct_loop_param_backedges_with_void_self(
body,
loop_depth + 1U,
allow_ref_branches,
allow_try_table_branches,
child_void_self_loop_depths,
) {
saw_backedge = true
} else {
return false
}
}
None => ()
}
}
_ => ()
}
}
saw_backedge
}
///|
fn run_hot_pipeline_raw_instrs_current_loop_backedges_are_plain_br(
instrs : Array[@lib.Instruction],
loop_depth : UInt32,
) -> Bool {
for instr in instrs {
match instr {
@lib.Br(_) => ()
@lib.BrIf(@lib.LabelIdx(raw)) => if raw == loop_depth { return false }
@lib.BrOnCast(@lib.LabelIdx(raw), _, _, _)
| @lib.BrOnCastFail(@lib.LabelIdx(raw), _, _, _)
| @lib.BrOnCastDescEq(@lib.LabelIdx(raw), _, _)
| @lib.BrOnCastDescEqFail(@lib.LabelIdx(raw), _, _)
| @lib.BrOnNull(@lib.LabelIdx(raw))
| @lib.BrOnNonNull(@lib.LabelIdx(raw)) =>
if raw == loop_depth {
return false
}
@lib.BrTable(labels, @lib.LabelIdx(default_raw)) => {
if default_raw == loop_depth {
return false
}
for label_idx in labels {
let @lib.LabelIdx(raw) = label_idx
if raw == loop_depth {
return false
}
}
}
@lib.Block(_, @lib.Expr(body))
| @lib.Loop(_, @lib.Expr(body))
| @lib.TryTable(_, _, @lib.Expr(body)) =>
if !run_hot_pipeline_raw_instrs_current_loop_backedges_are_plain_br(
body,
loop_depth + 1U,
) {
return false
}
@lib.If(_, then_body, else_body) => {
if !run_hot_pipeline_raw_instrs_current_loop_backedges_are_plain_br(
then_body,
loop_depth + 1U,
) {
return false
}
match else_body {
Some(body) =>
if !run_hot_pipeline_raw_instrs_current_loop_backedges_are_plain_br(
body,
loop_depth + 1U,
) {
return false
}
None => ()
}
}
_ => ()
}
}
true
}
///|
fn run_hot_pipeline_raw_instrs_have_try_table(
instrs : Array[@lib.Instruction],
) -> Bool {
for instr in instrs {
match instr {
@lib.TryTable(_, _, _) => return true
@lib.Block(_, @lib.Expr(body)) | @lib.Loop(_, @lib.Expr(body)) =>
if run_hot_pipeline_raw_instrs_have_try_table(body) {
return true
}
@lib.If(_, then_body, else_body) => {
if run_hot_pipeline_raw_instrs_have_try_table(then_body) {
return true
}
match else_body {
Some(body) =>
if run_hot_pipeline_raw_instrs_have_try_table(body) {
return true
}
None => ()
}
}
_ => ()
}
}
false
}
///|
fn run_hot_pipeline_raw_val_types_are_gc_free(
val_types : Array[@lib.ValType],
) -> Bool {
for val_type in val_types {
match val_type {
@lib.RefTypeValType(_) => return false
_ => ()
}
}
true
}
///|
fn run_hot_pipeline_raw_instrs_only_direct_loop_param_backedges(
instrs : Array[@lib.Instruction],
loop_depth : UInt32,
allow_ref_branches : Bool,
) -> Bool {
run_hot_pipeline_raw_instrs_only_direct_loop_param_backedges_with_void_self(
instrs,
loop_depth,
allow_ref_branches,
true,
[],
)
}
///|
fn run_hot_pipeline_raw_instrs_only_direct_loop_param_backedges_through_try_table(
instrs : Array[@lib.Instruction],
loop_depth : UInt32,
allow_ref_branches : Bool,
) -> Bool {
run_hot_pipeline_raw_instrs_only_direct_loop_param_backedges_with_void_self(
instrs,
loop_depth,
allow_ref_branches,
true,
[],
)
}
///|
fn run_hot_pipeline_raw_loop_param_proxy_label_target(
param_type : @lib.ValType,
loop_param_scratch : @lib.LocalIdx,
needed_alias_copies : @bitset.BitSet,
) -> RawSsaLabelTarget {
{
block_type: @lib.BlockType::val_type(param_type),
result_types: [param_type],
is_loop: false,
is_loop_param_proxy: true,
is_loop_param_store: false,
loop_param_scratch: Some(loop_param_scratch),
loop_param_scratches: [loop_param_scratch],
needed_alias_copies,
}
}
///|
fn run_hot_pipeline_raw_loop_param_store_label_target(
param_types : Array[@lib.ValType],
loop_param_scratches : Array[@lib.LocalIdx],
needed_alias_copies : @bitset.BitSet,
) -> RawSsaLabelTarget {
{
block_type: @lib.BlockType::void_(),
result_types: param_types,
is_loop: true,
is_loop_param_proxy: false,
is_loop_param_store: true,
loop_param_scratch: None,
loop_param_scratches,
needed_alias_copies,
}
}
///|
fn run_hot_pipeline_raw_br_table_alias_copies(
labels : Array[@lib.LabelIdx],
default_label : @lib.LabelIdx,
label_stack : Array[RawSsaLabelTarget],
base_local_count : Int32,
) -> @bitset.BitSet? {
let needed_alias_copies = @bitset.BitSet::new(base_local_count)
for label_idx in labels {
match run_hot_pipeline_raw_resolve_label_target(label_stack, label_idx) {
Some(label_target) => {
if label_target.is_loop &&
!run_hot_pipeline_raw_loop_backedge_supported(label_target) {
return None
}
for local_id = 0; local_id < base_local_count; local_id = local_id + 1 {
if label_target.needed_alias_copies[local_id] {
needed_alias_copies[local_id] = true
}
}
}
None => return None
}
}
match run_hot_pipeline_raw_resolve_label_target(label_stack, default_label) {
Some(label_target) => {
if label_target.is_loop &&
!run_hot_pipeline_raw_loop_backedge_supported(label_target) {
return None
}
for local_id = 0; local_id < base_local_count; local_id = local_id + 1 {
if label_target.needed_alias_copies[local_id] {
needed_alias_copies[local_id] = true
}
}
Some(needed_alias_copies)
}
None => None
}
}
///|
fn run_hot_pipeline_raw_br_table_loop_param_store_target(
labels : Array[@lib.LabelIdx],
default_label : @lib.LabelIdx,
label_stack : Array[RawSsaLabelTarget],
) -> RawSsaLabelTarget? {
let default_target = match
run_hot_pipeline_raw_resolve_label_target(label_stack, default_label) {
Some(label_target) => label_target
None => return None
}
if !default_target.is_loop_param_store {
return None
}
for label_idx in labels {
match run_hot_pipeline_raw_resolve_label_target(label_stack, label_idx) {
Some(label_target) =>
if !label_target.is_loop_param_store ||
label_target.result_types.length() !=
default_target.result_types.length() {
return None
}
None => return None
}
}
Some(default_target)
}
///|
fn run_hot_pipeline_raw_br_table_mixed_loop_param_store_target(
labels : Array[@lib.LabelIdx],
default_label : @lib.LabelIdx,
label_stack : Array[RawSsaLabelTarget],
) -> RawSsaLabelTarget? {
let mut loop_target : RawSsaLabelTarget? = None
let mut result_types : Array[@lib.ValType]? = None
let mut saw_non_loop = false
let all_labels = labels.copy()
all_labels.push(default_label)
for label_idx in all_labels {
let label_target = match
run_hot_pipeline_raw_resolve_label_target(label_stack, label_idx) {
Some(label_target) => label_target
None => return None
}
match result_types {
Some(types) => if label_target.result_types != types { return None }
None => result_types = Some(label_target.result_types)
}
if label_target.is_loop_param_store {
match loop_target {
Some(existing) =>
if existing.loop_param_scratches != label_target.loop_param_scratches {
return None
}
None => loop_target = Some(label_target)
}
} else if label_target.is_loop {
return None
} else {
saw_non_loop = true
}
}
match loop_target {
Some(target) if saw_non_loop => Some(target)
_ => None
}
}
///|
fn run_hot_pipeline_raw_br_table_mixed_loop_param_proxy_target(
labels : Array[@lib.LabelIdx],
default_label : @lib.LabelIdx,
label_stack : Array[RawSsaLabelTarget],
) -> RawSsaLabelTarget? {
let mut proxy_target : RawSsaLabelTarget? = None
let mut result_types : Array[@lib.ValType]? = None
let mut saw_non_proxy = false
let all_labels = labels.copy()
all_labels.push(default_label)
for label_idx in all_labels {
let label_target = match
run_hot_pipeline_raw_resolve_label_target(label_stack, label_idx) {
Some(label_target) => label_target
None => return None
}
match result_types {
Some(types) => if label_target.result_types != types { return None }
None => result_types = Some(label_target.result_types)
}
if label_target.is_loop_param_proxy {
match proxy_target {
Some(existing) =>
if existing.loop_param_scratch != label_target.loop_param_scratch ||
existing.result_types != label_target.result_types {
return None
}
None => proxy_target = Some(label_target)
}
} else if label_target.is_loop || label_target.is_loop_param_store {
return None
} else {
saw_non_proxy = true
}
}
match proxy_target {
Some(target) if saw_non_proxy && target.result_types.length() == 1 =>
Some(target)
_ => None
}
}
///|
fn run_hot_pipeline_raw_append_br_table_scratch_branch(
out : Array[@lib.Instruction],
label_idx : @lib.LabelIdx,
label_target : RawSsaLabelTarget,
loop_target : RawSsaLabelTarget,
label_increment : UInt32,
source_aliases : Array[@lib.LocalIdx],
canonical_aliases : Array[@lib.LocalIdx],
initialized_locals : @bitset.BitSet,
params : Array[@lib.ValType],
locals : @lib.Locals,
extra_local_types : Array[@lib.ValType],
) -> Bool {
let branch_instrs = run_hot_pipeline_raw_append_alias_merge_copies_for_needed(
[],
source_aliases,
canonical_aliases,
canonical_aliases,
initialized_locals,
label_target.needed_alias_copies,
[],
params,
locals,
extra_local_types,
)
if label_target.is_loop_param_store {
if label_target.loop_param_scratches != loop_target.loop_param_scratches ||
label_target.result_types != loop_target.result_types {
return false
}
} else if label_target.is_loop {
return false
} else {
for idx = 0; idx < loop_target.loop_param_scratches.length(); idx = idx + 1 {
run_hot_pipeline_raw_append_scratch_get_as_type(
branch_instrs,
loop_target.loop_param_scratches[idx],
label_target.result_types[idx],
)
}
}
branch_instrs.push(
@lib.Instruction::br(
run_hot_pipeline_raw_label_add(label_idx, label_increment),
),
)
out.append(branch_instrs)
true
}
///|
fn run_hot_pipeline_raw_rewrite_mixed_loop_param_store_br_table(
rewritten : Array[@lib.Instruction],
labels : Array[@lib.LabelIdx],
default_label : @lib.LabelIdx,
loop_target : RawSsaLabelTarget,
label_stack : Array[RawSsaLabelTarget],
source_aliases : Array[@lib.LocalIdx],
canonical_aliases : Array[@lib.LocalIdx],
initialized_locals : @bitset.BitSet,
params : Array[@lib.ValType],
locals : @lib.Locals,
extra_local_types : Array[@lib.ValType],
) -> Bool {
if loop_target.loop_param_scratches.length() !=
loop_target.result_types.length() {
return false
}
let selector_local = run_hot_pipeline_raw_fresh_typed_local(
params,
locals,
@lib.ValType::i32(),
extra_local_types,
)
rewritten.push(@lib.Instruction::local_set(selector_local))
for idx = loop_target.loop_param_scratches.length() - 1
idx >= 0
idx = idx - 1 {
rewritten.push(
@lib.Instruction::local_set(loop_target.loop_param_scratches[idx]),
)
}
for label_pos = 0; label_pos < labels.length(); label_pos = label_pos + 1 {
let label_idx = labels[label_pos]
let label_target = match
run_hot_pipeline_raw_resolve_label_target(label_stack, label_idx) {
Some(label_target) => label_target
None => return false
}
rewritten.push(@lib.Instruction::local_get(selector_local))
rewritten.push(@lib.Instruction::i32_const(@lib.I32::new(label_pos)))
rewritten.push(@lib.Instruction::i32_eq())
let then_body : Array[@lib.Instruction] = []
if !run_hot_pipeline_raw_append_br_table_scratch_branch(
then_body, label_idx, label_target, loop_target, 1U, source_aliases, canonical_aliases,
initialized_locals, params, locals, extra_local_types,
) {
return false
}
rewritten.push(
@lib.Instruction::if_(@lib.BlockType::void_(), then_body, None),
)
}
let default_target = match
run_hot_pipeline_raw_resolve_label_target(label_stack, default_label) {
Some(label_target) => label_target
None => return false
}
run_hot_pipeline_raw_append_br_table_scratch_branch(
rewritten, default_label, default_target, loop_target, 0U, source_aliases, canonical_aliases,
initialized_locals, params, locals, extra_local_types,
)
}
///|
fn run_hot_pipeline_raw_append_br_table_proxy_scratch_branch(
out : Array[@lib.Instruction],
label_idx : @lib.LabelIdx,
label_target : RawSsaLabelTarget,
proxy_target : RawSsaLabelTarget,
proxy_label_increment : UInt32,
non_proxy_label_increment : UInt32,
source_aliases : Array[@lib.LocalIdx],
canonical_aliases : Array[@lib.LocalIdx],
initialized_locals : @bitset.BitSet,
params : Array[@lib.ValType],
locals : @lib.Locals,
extra_local_types : Array[@lib.ValType],
) -> Bool {
let loop_scratch = match proxy_target.loop_param_scratch {
Some(scratch) => scratch
None => return false
}
if proxy_target.result_types.length() != 1 ||
label_target.result_types != proxy_target.result_types {
return false
}
if !label_target.is_loop_param_proxy &&
(label_target.is_loop || label_target.is_loop_param_store) {
return false
}
if label_target.is_loop_param_proxy &&
label_target.loop_param_scratch != proxy_target.loop_param_scratch {
return false
}
let branch_instrs = run_hot_pipeline_raw_append_alias_merge_copies_for_needed(
[],
source_aliases,
canonical_aliases,
canonical_aliases,
initialized_locals,
label_target.needed_alias_copies,
[],
params,
locals,
extra_local_types,
)
run_hot_pipeline_raw_append_scratch_get_as_type(
branch_instrs,
loop_scratch,
label_target.result_types[0],
)
branch_instrs.push(
@lib.Instruction::br(
run_hot_pipeline_raw_label_add(
label_idx,
if label_target.is_loop_param_proxy {
proxy_label_increment
} else {
non_proxy_label_increment
},
),
),
)
out.append(branch_instrs)
true
}
///|
fn run_hot_pipeline_raw_rewrite_mixed_loop_param_proxy_br_table(
rewritten : Array[@lib.Instruction],
labels : Array[@lib.LabelIdx],
default_label : @lib.LabelIdx,
proxy_target : RawSsaLabelTarget,
label_stack : Array[RawSsaLabelTarget],
source_aliases : Array[@lib.LocalIdx],
canonical_aliases : Array[@lib.LocalIdx],
initialized_locals : @bitset.BitSet,
params : Array[@lib.ValType],
locals : @lib.Locals,
extra_local_types : Array[@lib.ValType],
) -> Bool {
let loop_scratch = match proxy_target.loop_param_scratch {
Some(scratch) => scratch
None => return false
}
if proxy_target.result_types.length() != 1 {
return false
}
let selector_local = run_hot_pipeline_raw_fresh_typed_local(
params,
locals,
@lib.ValType::i32(),
extra_local_types,
)
rewritten.push(@lib.Instruction::local_set(selector_local))
rewritten.push(@lib.Instruction::local_set(loop_scratch))
for label_pos = 0; label_pos < labels.length(); label_pos = label_pos + 1 {
let label_idx = labels[label_pos]
let label_target = match
run_hot_pipeline_raw_resolve_label_target(label_stack, label_idx) {
Some(label_target) => label_target
None => return false
}
rewritten.push(@lib.Instruction::local_get(selector_local))
rewritten.push(@lib.Instruction::i32_const(@lib.I32::new(label_pos)))
rewritten.push(@lib.Instruction::i32_eq())
let then_body : Array[@lib.Instruction] = []
if !run_hot_pipeline_raw_append_br_table_proxy_scratch_branch(
then_body, label_idx, label_target, proxy_target, 1U, 3U, source_aliases,
canonical_aliases, initialized_locals, params, locals, extra_local_types,
) {
return false
}
rewritten.push(
@lib.Instruction::if_(@lib.BlockType::void_(), then_body, None),
)
}
let default_target = match
run_hot_pipeline_raw_resolve_label_target(label_stack, default_label) {
Some(label_target) => label_target
None => return false
}
run_hot_pipeline_raw_append_br_table_proxy_scratch_branch(
rewritten, default_label, default_target, proxy_target, 0U, 2U, source_aliases,
canonical_aliases, initialized_locals, params, locals, extra_local_types,
)
}
///|
fn run_hot_pipeline_raw_append_loop_param_store_backedge(
rewritten : Array[@lib.Instruction],
label_idx : @lib.LabelIdx,
label_target : RawSsaLabelTarget,
source_aliases : Array[@lib.LocalIdx],
canonical_aliases : Array[@lib.LocalIdx],
initialized_locals : @bitset.BitSet,
params : Array[@lib.ValType],
locals : @lib.Locals,
extra_local_types : Array[@lib.ValType],
) -> Bool {
if !label_target.is_loop_param_store ||
label_target.loop_param_scratches.length() !=
label_target.result_types.length() {
return false
}
let branch_instrs : Array[@lib.Instruction] = []
for idx = label_target.loop_param_scratches.length() - 1
idx >= 0
idx = idx - 1 {
branch_instrs.push(
@lib.Instruction::local_set(label_target.loop_param_scratches[idx]),
)
}
let branch_with_copies = run_hot_pipeline_raw_append_alias_merge_copies_for_needed(
branch_instrs,
source_aliases,
canonical_aliases,
canonical_aliases,
initialized_locals,
label_target.needed_alias_copies,
[],
params,
locals,
extra_local_types,
)
branch_with_copies.push(@lib.Instruction::br(label_idx))
rewritten.append(branch_with_copies)
true
}
///|
fn run_hot_pipeline_raw_trailing_ref_branch_input_needs_single_spill(
rewritten : Array[@lib.Instruction],
) -> Bool {
match rewritten.last() {
Some(@lib.Block(@lib.ValTypeBlockType(_), _))
| Some(@lib.If(@lib.ValTypeBlockType(_), _, _))
| Some(@lib.Select(Some([@lib.RefTypeValType(_)]))) => true
_ => false
}
}
///|
fn run_hot_pipeline_raw_trailing_ref_branch_input_needs_single_spill_for_non_null(
rewritten : Array[@lib.Instruction],
module_ctx : @ir.HotModuleContext,
) -> Bool {
if run_hot_pipeline_raw_trailing_ref_branch_input_needs_single_spill(
rewritten,
) {
return true
}
match rewritten.last() {
Some(@lib.Call(func_idx)) =>
match @ir.hot_module_resolve_func_type(module_ctx, func_idx) {
Some(@lib.FuncType(_, [@lib.RefTypeValType(_)])) => true
_ => false
}
Some(@lib.CallRef(type_idx)) | Some(@lib.CallIndirect(type_idx, _)) =>
match @ir.hot_module_resolve_type_func_type(module_ctx, type_idx) {
Some(@lib.FuncType(_, [@lib.RefTypeValType(_)])) => true
_ => false
}
Some(@lib.RefCast(_, _, _))
| Some(@lib.StructNew(_))
| Some(@lib.ArrayNew(_))
| Some(@lib.ArrayNewFixed(_, _))
| Some(@lib.ArrayNewData(_, _))
| Some(@lib.ArrayNewElem(_, _)) => true
_ => false
}
}
///|
fn run_hot_pipeline_raw_spill_trailing_ref_branch_input(
rewritten : Array[@lib.Instruction],
module_ctx : @ir.HotModuleContext,
params : Array[@lib.ValType],
locals : @lib.Locals,
extra_local_types : Array[@lib.ValType],
allow_local_tee : Bool,
allow_direct_call~ : Bool = false,
allow_direct_call_operands~ : Bool = false,
) -> (@lib.ValType, @lib.LocalIdx)? {
let (ref_type, source_local, keep_trailing_value, keep_ref_null) = match
rewritten.last() {
Some(@lib.LocalGet(local_idx)) => {
let @lib.LocalIdx(raw) = local_idx
let ref_type = match
run_hot_pipeline_raw_rewritten_local_type(
params,
locals,
extra_local_types,
raw.reinterpret_as_int(),
) {
Some(val_type) => val_type
None => return None
}
(ref_type, Some(local_idx), false, false)
}
Some(@lib.LocalTee(local_idx)) if allow_local_tee => {
let @lib.LocalIdx(raw) = local_idx
let ref_type = match
run_hot_pipeline_raw_rewritten_local_type(
params,
locals,
extra_local_types,
raw.reinterpret_as_int(),
) {
Some(val_type) => val_type
None => return None
}
(ref_type, Some(local_idx), true, false)
}
Some(@lib.RefNull(ref_type)) =>
(@lib.ValType::ref_type(ref_type), None, false, true)
Some(@lib.Block(@lib.ValTypeBlockType(ref_type), _))
| Some(@lib.If(@lib.ValTypeBlockType(ref_type), _, _)) =>
(ref_type, None, true, false)
Some(@lib.Select(Some([ref_type]))) => (ref_type, None, true, false)
Some(@lib.RefCast(nullable, exact, heap_type)) =>
(
@lib.ValType::ref_type(@lib.RefType::new(nullable, heap_type, exact~)),
None,
true,
false,
)
Some(@lib.StructNewDefault(type_idx)) =>
(
@lib.ValType::ref_type(
@lib.RefType::new(
false,
@lib.HeapType::new(type_idx),
exact=module_ctx.uses_custom_descriptors,
),
),
None,
true,
false,
)
Some(@lib.ArrayNewDefault(type_idx)) =>
(@lib.ValType::ref_array_nonnull(type_idx), None, true, false)
Some(@lib.StructNew(type_idx)) =>
(
@lib.ValType::ref_type(
@lib.RefType::new(
false,
@lib.HeapType::new(type_idx),
exact=module_ctx.uses_custom_descriptors,
),
),
None,
true,
false,
)
Some(@lib.ArrayNew(type_idx))
| Some(@lib.ArrayNewFixed(type_idx, _))
| Some(@lib.ArrayNewData(type_idx, _))
| Some(@lib.ArrayNewElem(type_idx, _)) =>
(@lib.ValType::ref_array_nonnull(type_idx), None, true, false)
Some(@lib.RefI31) => (@lib.ValType::i31ref(), None, true, false)
Some(@lib.RefFunc(func_idx)) => {
let type_idx = match
@ir.hot_module_resolve_func_type_idx(module_ctx, func_idx) {
Some(type_idx) => type_idx
None => return None
}
(
@lib.ValType::ref_type(
@lib.RefType::new(false, @lib.HeapType::new(type_idx)),
),
None,
true,
false,
)
}
Some(@lib.AnyConvertExtern) => (@lib.ValType::anyref(), None, true, false)
Some(@lib.ExternConvertAny) =>
(@lib.ValType::externref(), None, true, false)
Some(@lib.Call(func_idx)) if allow_direct_call => {
let (call_params, ref_type) = match
@ir.hot_module_resolve_func_type(module_ctx, func_idx) {
Some(@lib.FuncType(call_params, [@lib.RefTypeValType(ref_type)])) =>
(call_params, ref_type)
_ => return None
}
if !allow_direct_call_operands && call_params.length() != 0 {
return None
}
(@lib.ValType::ref_type(ref_type), None, true, false)
}
Some(@lib.CallRef(type_idx)) | Some(@lib.CallIndirect(type_idx, _)) if allow_direct_call => {
let (call_params, ref_type) = match
@ir.hot_module_resolve_type_func_type(module_ctx, type_idx) {
Some(@lib.FuncType(call_params, [@lib.RefTypeValType(ref_type)])) =>
(call_params, ref_type)
_ => return None
}
if !allow_direct_call_operands && call_params.length() != 0 {
return None
}
(@lib.ValType::ref_type(ref_type), None, true, false)
}
_ => return None
}
if run_hot_pipeline_raw_non_null_ref_type(ref_type) is None {
return None
}
let scratch_type = match
run_hot_pipeline_raw_defaultable_scratch_type(ref_type) {
Some(scratch_type) => scratch_type
None => return None
}
let scratch_ref = run_hot_pipeline_raw_fresh_typed_local(
params, locals, scratch_type, extra_local_types,
)
if keep_trailing_value {
rewritten.push(@lib.Instruction::local_set(scratch_ref))
} else if keep_ref_null {
rewritten.push(@lib.Instruction::local_set(scratch_ref))
} else {
ignore(rewritten.pop())
let source_local = match source_local {
Some(source_local) => source_local
None => return None
}
rewritten.push(@lib.Instruction::local_get(source_local))
rewritten.push(@lib.Instruction::local_set(scratch_ref))
}
Some((ref_type, scratch_ref))
}
///|
fn run_hot_pipeline_raw_rewrite_loop_param_store_br_if(
label_idx : @lib.LabelIdx,
label_target : RawSsaLabelTarget,
source_aliases : Array[@lib.LocalIdx],
canonical_aliases : Array[@lib.LocalIdx],
initialized_locals : @bitset.BitSet,
params : Array[@lib.ValType],
locals : @lib.Locals,
extra_local_types : Array[@lib.ValType],
) -> Array[@lib.Instruction]? {
if !label_target.is_loop_param_store ||
label_target.loop_param_scratches.length() !=
label_target.result_types.length() {
return None
}
let cond_local = run_hot_pipeline_raw_fresh_typed_local(
params,
locals,
@lib.ValType::i32(),
extra_local_types,
)
let rewritten : Array[@lib.Instruction] = []
rewritten.push(@lib.Instruction::local_set(cond_local))
for idx = label_target.loop_param_scratches.length() - 1
idx >= 0
idx = idx - 1 {
rewritten.push(
@lib.Instruction::local_set(label_target.loop_param_scratches[idx]),
)
}
rewritten.push(@lib.Instruction::local_get(cond_local))
let then_body = run_hot_pipeline_raw_append_alias_merge_copies_for_needed(
[],
source_aliases,
canonical_aliases,
canonical_aliases,
initialized_locals,
label_target.needed_alias_copies,
[],
params,
locals,
extra_local_types,
)
then_body.push(
@lib.Instruction::br(run_hot_pipeline_raw_label_inc(label_idx)),
)
rewritten.push(
@lib.Instruction::if_(@lib.BlockType::void_(), then_body, None),
)
for idx = 0; idx < label_target.loop_param_scratches.length(); idx = idx + 1 {
run_hot_pipeline_raw_append_scratch_get_as_type(
rewritten,
label_target.loop_param_scratches[idx],
label_target.result_types[idx],
)
}
Some(rewritten)
}
///|
fn run_hot_pipeline_raw_rewrite_loop_param_store_br_on_null(
rewritten : Array[@lib.Instruction],
module_ctx : @ir.HotModuleContext,
label_idx : @lib.LabelIdx,
label_target : RawSsaLabelTarget,
source_aliases : Array[@lib.LocalIdx],
canonical_aliases : Array[@lib.LocalIdx],
initialized_locals : @bitset.BitSet,
params : Array[@lib.ValType],
locals : @lib.Locals,
extra_local_types : Array[@lib.ValType],
) -> Bool {
if !label_target.is_loop_param_store ||
label_target.loop_param_scratches.length() !=
label_target.result_types.length() {
return false
}
let (_, scratch_ref) = match
run_hot_pipeline_raw_spill_trailing_ref_branch_input(
rewritten,
module_ctx,
params,
locals,
extra_local_types,
true,
allow_direct_call=true,
allow_direct_call_operands=true,
) {
Some(spilled) => spilled
None => return false
}
for idx = label_target.loop_param_scratches.length() - 1
idx >= 0
idx = idx - 1 {
rewritten.push(
@lib.Instruction::local_set(label_target.loop_param_scratches[idx]),
)
}
rewritten.push(@lib.Instruction::local_get(scratch_ref))
rewritten.push(@lib.Instruction::ref_is_null())
let then_body = run_hot_pipeline_raw_append_alias_merge_copies_for_needed(
[],
source_aliases,
canonical_aliases,
canonical_aliases,
initialized_locals,
label_target.needed_alias_copies,
[],
params,
locals,
extra_local_types,
)
then_body.push(
@lib.Instruction::br(run_hot_pipeline_raw_label_inc(label_idx)),
)
rewritten.push(
@lib.Instruction::if_(@lib.BlockType::void_(), then_body, None),
)
for idx = 0; idx < label_target.loop_param_scratches.length(); idx = idx + 1 {
run_hot_pipeline_raw_append_scratch_get_as_type(
rewritten,
label_target.loop_param_scratches[idx],
label_target.result_types[idx],
)
}
rewritten.push(@lib.Instruction::local_get(scratch_ref))
rewritten.push(@lib.Instruction::ref_as_non_null())
true
}
///|
fn run_hot_pipeline_raw_rewrite_loop_param_store_br_on_non_null(
rewritten : Array[@lib.Instruction],
module_ctx : @ir.HotModuleContext,
label_idx : @lib.LabelIdx,
label_target : RawSsaLabelTarget,
source_aliases : Array[@lib.LocalIdx],
canonical_aliases : Array[@lib.LocalIdx],
initialized_locals : @bitset.BitSet,
params : Array[@lib.ValType],
locals : @lib.Locals,
extra_local_types : Array[@lib.ValType],
) -> Bool {
if !label_target.is_loop_param_store ||
label_target.result_types.length() == 0 ||
label_target.loop_param_scratches.length() !=
label_target.result_types.length() {
return false
}
let (nullable_ref_type, scratch_ref) = match
run_hot_pipeline_raw_spill_trailing_ref_branch_input(
rewritten,
module_ctx,
params,
locals,
extra_local_types,
true,
allow_direct_call=true,
allow_direct_call_operands=true,
) {
Some(spilled) => spilled
None => return false
}
let non_null_ref_type = match
run_hot_pipeline_raw_non_null_ref_type(nullable_ref_type) {
Some(ref_type) => ref_type
None => return false
}
let final_idx = label_target.result_types.length() - 1
if !run_hot_pipeline_raw_reference_type_matches(
module_ctx,
non_null_ref_type,
label_target.result_types[final_idx],
) {
return false
}
if final_idx > 0 {
for idx = final_idx - 1; idx >= 0; idx = idx - 1 {
rewritten.push(
@lib.Instruction::local_set(label_target.loop_param_scratches[idx]),
)
}
}
rewritten.push(@lib.Instruction::local_get(scratch_ref))
rewritten.push(@lib.Instruction::ref_is_null())
let else_body : Array[@lib.Instruction] = []
else_body.push(@lib.Instruction::local_get(scratch_ref))
else_body.push(@lib.Instruction::ref_as_non_null())
else_body.push(
@lib.Instruction::local_set(label_target.loop_param_scratches[final_idx]),
)
let copies = run_hot_pipeline_raw_append_alias_merge_copies_for_needed(
[],
source_aliases,
canonical_aliases,
canonical_aliases,
initialized_locals,
label_target.needed_alias_copies,
[],
params,
locals,
extra_local_types,
)
else_body.append(copies)
else_body.push(
@lib.Instruction::br(run_hot_pipeline_raw_label_inc(label_idx)),
)
rewritten.push(
@lib.Instruction::if_(@lib.BlockType::void_(), [], Some(else_body)),
)
for idx = 0; idx < final_idx; idx = idx + 1 {
run_hot_pipeline_raw_append_scratch_get_as_type(
rewritten,
label_target.loop_param_scratches[idx],
label_target.result_types[idx],
)
}
true
}
///|
fn run_hot_pipeline_raw_rewrite_loop_param_store_cast_branch(
rewritten : Array[@lib.Instruction],
label_idx : @lib.LabelIdx,
label_target : RawSsaLabelTarget,
source_nullable : Bool,
source_heap : @lib.HeapType,
target_nullable : Bool,
target_heap : @lib.HeapType,
fail_variant : Bool,
source_aliases : Array[@lib.LocalIdx],
canonical_aliases : Array[@lib.LocalIdx],
initialized_locals : @bitset.BitSet,
params : Array[@lib.ValType],
locals : @lib.Locals,
extra_local_types : Array[@lib.ValType],
) -> Bool {
if !label_target.is_loop_param_store ||
label_target.result_types.length() == 0 ||
label_target.loop_param_scratches.length() !=
label_target.result_types.length() {
return false
}
let source_type = @lib.ValType::ref_type(
@lib.RefType::new(source_nullable, source_heap),
)
let target_type = @lib.ValType::ref_type(
@lib.RefType::new(target_nullable, target_heap),
)
let diff_type = run_hot_pipeline_raw_cast_diff_type(
source_nullable, target_nullable, source_heap,
)
let branch_payload_type = if fail_variant { diff_type } else { target_type }
let fallthrough_type = if fail_variant { target_type } else { diff_type }
let final_idx = label_target.result_types.length() - 1
if label_target.result_types[final_idx] != branch_payload_type {
return false
}
let source_scratch_type = match
run_hot_pipeline_raw_defaultable_scratch_type(source_type) {
Some(scratch_type) => scratch_type
None => return false
}
let scratch_ref = run_hot_pipeline_raw_fresh_typed_local(
params, locals, source_scratch_type, extra_local_types,
)
let fallthrough_scratch = if final_idx > 0 {
let fallthrough_scratch_type = match
run_hot_pipeline_raw_defaultable_scratch_type(fallthrough_type) {
Some(scratch_type) => scratch_type
None => return false
}
Some(
run_hot_pipeline_raw_fresh_typed_local(
params, locals, fallthrough_scratch_type, extra_local_types,
),
)
} else {
None
}
rewritten.push(@lib.Instruction::local_set(scratch_ref))
if final_idx > 0 {
for idx = final_idx - 1; idx >= 0; idx = idx - 1 {
rewritten.push(
@lib.Instruction::local_set(label_target.loop_param_scratches[idx]),
)
}
}
let body : Array[@lib.Instruction] = []
run_hot_pipeline_raw_append_scratch_get_as_type(
body, scratch_ref, source_type,
)
if fail_variant {
body.push(
@lib.Instruction::br_on_cast(
@lib.LabelIdx::new(0U),
source_nullable,
source_heap,
target_nullable,
target_heap,
),
)
} else {
body.push(
@lib.Instruction::br_on_cast_fail(
@lib.LabelIdx::new(0U),
source_nullable,
source_heap,
target_nullable,
target_heap,
),
)
}
body.push(
@lib.Instruction::local_set(label_target.loop_param_scratches[final_idx]),
)
let copies = run_hot_pipeline_raw_append_alias_merge_copies_for_needed(
[],
source_aliases,
canonical_aliases,
canonical_aliases,
initialized_locals,
label_target.needed_alias_copies,
[],
params,
locals,
extra_local_types,
)
body.append(copies)
body.push(@lib.Instruction::br(run_hot_pipeline_raw_label_inc(label_idx)))
rewritten.push(
@lib.Instruction::block(
@lib.BlockType::val_type(fallthrough_type),
@lib.Expr::new(body),
),
)
match fallthrough_scratch {
Some(scratch) => {
rewritten.push(@lib.Instruction::local_set(scratch))
for idx = 0; idx < final_idx; idx = idx + 1 {
run_hot_pipeline_raw_append_scratch_get_as_type(
rewritten,
label_target.loop_param_scratches[idx],
label_target.result_types[idx],
)
}
run_hot_pipeline_raw_append_scratch_get_as_type(
rewritten, scratch, fallthrough_type,
)
}
None => ()
}
true
}
///|
fn run_hot_pipeline_raw_rewrite_loop_param_store_br_on_cast(
rewritten : Array[@lib.Instruction],
label_idx : @lib.LabelIdx,
label_target : RawSsaLabelTarget,
source_nullable : Bool,
source_heap : @lib.HeapType,
target_nullable : Bool,
target_heap : @lib.HeapType,
source_aliases : Array[@lib.LocalIdx],
canonical_aliases : Array[@lib.LocalIdx],
initialized_locals : @bitset.BitSet,
params : Array[@lib.ValType],
locals : @lib.Locals,
extra_local_types : Array[@lib.ValType],
) -> Bool {
run_hot_pipeline_raw_rewrite_loop_param_store_cast_branch(
rewritten, label_idx, label_target, source_nullable, source_heap, target_nullable,
target_heap, false, source_aliases, canonical_aliases, initialized_locals, params,
locals, extra_local_types,
)
}
///|
fn run_hot_pipeline_raw_rewrite_loop_param_store_br_on_cast_fail(
rewritten : Array[@lib.Instruction],
label_idx : @lib.LabelIdx,
label_target : RawSsaLabelTarget,
source_nullable : Bool,
source_heap : @lib.HeapType,
target_nullable : Bool,
target_heap : @lib.HeapType,
source_aliases : Array[@lib.LocalIdx],
canonical_aliases : Array[@lib.LocalIdx],
initialized_locals : @bitset.BitSet,
params : Array[@lib.ValType],
locals : @lib.Locals,
extra_local_types : Array[@lib.ValType],
) -> Bool {
run_hot_pipeline_raw_rewrite_loop_param_store_cast_branch(
rewritten, label_idx, label_target, source_nullable, source_heap, target_nullable,
target_heap, true, source_aliases, canonical_aliases, initialized_locals, params,
locals, extra_local_types,
)
}
///|
fn run_hot_pipeline_raw_mark_br_table_exit_depths(
exit_depths : @bitset.BitSet,
labels : Array[@lib.LabelIdx],
default_label : @lib.LabelIdx,
label_stack : Array[RawSsaLabelTarget],
) -> Unit {
for label_idx in labels {
match run_hot_pipeline_raw_resolve_label_target(label_stack, label_idx) {
Some(label_target) =>
if !label_target.is_loop {
let @lib.LabelIdx(raw) = label_idx
exit_depths[raw.reinterpret_as_int()] = true
}
None => ()
}
}
match run_hot_pipeline_raw_resolve_label_target(label_stack, default_label) {
Some(label_target) =>
if !label_target.is_loop {
let @lib.LabelIdx(default_raw) = default_label
exit_depths[default_raw.reinterpret_as_int()] = true
}
None => ()
}
}
///|
fn run_hot_pipeline_raw_try_table_body_has_exceptional_edge(
instrs : Array[@lib.Instruction],
) -> Bool {
for instr in instrs {
match instr {
@lib.Throw(_) | @lib.ThrowRef => return true
@lib.Call(_)
| @lib.CallIndirect(_, _)
| @lib.CallRef(_)
| @lib.ReturnCall(_)
| @lib.ReturnCallIndirect(_, _)
| @lib.ReturnCallRef(_) => return true
@lib.Br(_) | @lib.Return | @lib.Unreachable => return false
@lib.Block(_, @lib.Expr(body))
| @lib.Loop(_, @lib.Expr(body))
| @lib.TryTable(_, _, @lib.Expr(body)) =>
if run_hot_pipeline_raw_try_table_body_has_exceptional_edge(body) {
return true
}
@lib.If(_, then_body, else_body) => {
if run_hot_pipeline_raw_try_table_body_has_exceptional_edge(then_body) {
return true
}
match else_body {
Some(body) =>
if run_hot_pipeline_raw_try_table_body_has_exceptional_edge(body) {
return true
}
None => ()
}
}
_ => ()
}
}
false
}
///|
fn run_hot_pipeline_raw_rewrite_br_if(
module_ctx : @ir.HotModuleContext,
label_idx : @lib.LabelIdx,
label_target : RawSsaLabelTarget,
source_aliases : Array[@lib.LocalIdx],
canonical_aliases : Array[@lib.LocalIdx],
initialized_locals : @bitset.BitSet,
params : Array[@lib.ValType],
locals : @lib.Locals,
extra_local_types : Array[@lib.ValType],
) -> Array[@lib.Instruction]? {
let target_label = run_hot_pipeline_raw_label_inc(label_idx)
if !run_hot_pipeline_raw_has_needed_alias_copy(
source_aliases,
canonical_aliases,
label_target.needed_alias_copies,
) {
return Some([@lib.Instruction::br_if(label_idx)])
}
if label_target.result_types.length() == 0 {
let then_body = run_hot_pipeline_raw_append_alias_merge_copies_for_needed(
[],
source_aliases,
canonical_aliases,
canonical_aliases,
initialized_locals,
label_target.needed_alias_copies,
[],
params,
locals,
extra_local_types,
)
then_body.push(@lib.Instruction::br(target_label))
return Some([
@lib.Instruction::if_(@lib.BlockType::void_(), then_body, None),
])
}
let if_block_type = match
run_hot_pipeline_raw_branch_if_block_type(module_ctx, label_target) {
Some(block_type) => block_type
None => return None
}
let cond_local = run_hot_pipeline_raw_fresh_typed_local(
params,
locals,
@lib.ValType::i32(),
extra_local_types,
)
let result_scratch : Array[@lib.LocalIdx] = []
for result_type in label_target.result_types {
let scratch_type = match
run_hot_pipeline_raw_defaultable_scratch_type(result_type) {
Some(scratch_type) => scratch_type
None => return None
}
result_scratch.push(
run_hot_pipeline_raw_fresh_typed_local(
params, locals, scratch_type, extra_local_types,
),
)
}
let rewritten : Array[@lib.Instruction] = []
rewritten.push(@lib.Instruction::local_set(cond_local))
for idx = result_scratch.length() - 1; idx >= 0; idx = idx - 1 {
rewritten.push(@lib.Instruction::local_set(result_scratch[idx]))
}
rewritten.push(@lib.Instruction::local_get(cond_local))
let then_body = run_hot_pipeline_raw_append_alias_merge_copies_for_needed(
[],
source_aliases,
canonical_aliases,
canonical_aliases,
initialized_locals,
label_target.needed_alias_copies,
[],
params,
locals,
extra_local_types,
)
for idx = 0; idx < result_scratch.length(); idx = idx + 1 {
run_hot_pipeline_raw_append_scratch_get_as_type(
then_body,
result_scratch[idx],
label_target.result_types[idx],
)
}
then_body.push(@lib.Instruction::br(target_label))
let else_body : Array[@lib.Instruction] = []
for idx = 0; idx < result_scratch.length(); idx = idx + 1 {
run_hot_pipeline_raw_append_scratch_get_as_type(
else_body,
result_scratch[idx],
label_target.result_types[idx],
)
}
rewritten.push(
@lib.Instruction::if_(if_block_type, then_body, Some(else_body)),
)
Some(rewritten)
}
///|
fn run_hot_pipeline_raw_rewrite_br_on_null(
rewritten : Array[@lib.Instruction],
module_ctx : @ir.HotModuleContext,
label_idx : @lib.LabelIdx,
label_target : RawSsaLabelTarget,
source_aliases : Array[@lib.LocalIdx],
canonical_aliases : Array[@lib.LocalIdx],
initialized_locals : @bitset.BitSet,
params : Array[@lib.ValType],
locals : @lib.Locals,
extra_local_types : Array[@lib.ValType],
) -> Bool {
if !run_hot_pipeline_raw_has_needed_alias_copy(
source_aliases,
canonical_aliases,
label_target.needed_alias_copies,
) &&
(
label_target.result_types.length() == 0 ||
label_target.is_loop_param_proxy ||
label_target.is_loop_param_store
) {
rewritten.push(@lib.Instruction::br_on_null(label_idx))
return true
}
match rewritten.last() {
Some(@lib.Call(func_idx)) => {
let supported_non_current_call = if label_target.result_types.length() > 0 {
match @ir.hot_module_resolve_func_type(module_ctx, func_idx) {
Some(@lib.FuncType(_, [@lib.RefTypeValType(_)])) => true
_ => false
}
} else {
false
}
if !supported_non_current_call {
rewritten.push(@lib.Instruction::br_on_null(label_idx))
return true
}
}
_ => ()
}
let (_, scratch_ref) = match
run_hot_pipeline_raw_spill_trailing_ref_branch_input(
rewritten,
module_ctx,
params,
locals,
extra_local_types,
true,
allow_direct_call=label_target.result_types.length() > 0,
allow_direct_call_operands=label_target.result_types.length() > 0,
) {
Some(spilled) => spilled
None => return false
}
let result_scratch : Array[@lib.LocalIdx] = []
for result_type in label_target.result_types {
let scratch_type = match
run_hot_pipeline_raw_defaultable_scratch_type(result_type) {
Some(scratch_type) => scratch_type
None => return false
}
result_scratch.push(
run_hot_pipeline_raw_fresh_typed_local(
params, locals, scratch_type, extra_local_types,
),
)
}
for idx = result_scratch.length() - 1; idx >= 0; idx = idx - 1 {
rewritten.push(@lib.Instruction::local_set(result_scratch[idx]))
}
rewritten.push(@lib.Instruction::local_get(scratch_ref))
rewritten.push(@lib.Instruction::ref_is_null())
let then_body = run_hot_pipeline_raw_append_alias_merge_copies_for_needed(
[],
source_aliases,
canonical_aliases,
canonical_aliases,
initialized_locals,
label_target.needed_alias_copies,
[],
params,
locals,
extra_local_types,
)
for idx = 0; idx < result_scratch.length(); idx = idx + 1 {
run_hot_pipeline_raw_append_scratch_get_as_type(
then_body,
result_scratch[idx],
label_target.result_types[idx],
)
}
then_body.push(
@lib.Instruction::br(run_hot_pipeline_raw_label_inc(label_idx)),
)
rewritten.push(
@lib.Instruction::if_(@lib.BlockType::void_(), then_body, None),
)
for idx = 0; idx < result_scratch.length(); idx = idx + 1 {
run_hot_pipeline_raw_append_scratch_get_as_type(
rewritten,
result_scratch[idx],
label_target.result_types[idx],
)
}
rewritten.push(@lib.Instruction::local_get(scratch_ref))
rewritten.push(@lib.Instruction::ref_as_non_null())
true
}
///|
fn run_hot_pipeline_raw_rewrite_br_on_non_null(
rewritten : Array[@lib.Instruction],
module_ctx : @ir.HotModuleContext,
label_idx : @lib.LabelIdx,
label_target : RawSsaLabelTarget,
source_aliases : Array[@lib.LocalIdx],
canonical_aliases : Array[@lib.LocalIdx],
initialized_locals : @bitset.BitSet,
params : Array[@lib.ValType],
locals : @lib.Locals,
extra_local_types : Array[@lib.ValType],
) -> Bool {
let has_needed_copy = run_hot_pipeline_raw_has_needed_alias_copy(
source_aliases,
canonical_aliases,
label_target.needed_alias_copies,
)
if !has_needed_copy &&
(
label_target.result_types.length() == 0 ||
!run_hot_pipeline_raw_trailing_ref_branch_input_needs_single_spill_for_non_null(
rewritten, module_ctx,
)
) {
rewritten.push(@lib.Instruction::br_on_non_null(label_idx))
return true
}
let direct_call_operands_supported = match label_target.result_types.last() {
Some(@lib.RefTypeValType(_)) => true
_ => false
}
match rewritten.last() {
Some(@lib.Call(func_idx)) => {
let supported_non_current_call = if label_target.result_types.length() > 0 {
match @ir.hot_module_resolve_func_type(module_ctx, func_idx) {
Some(@lib.FuncType(call_params, [@lib.RefTypeValType(_)])) =>
call_params.length() == 0 || direct_call_operands_supported
_ => false
}
} else {
false
}
if !supported_non_current_call {
rewritten.push(@lib.Instruction::br_on_non_null(label_idx))
return true
}
}
_ => ()
}
let (_, scratch_ref) = match
run_hot_pipeline_raw_spill_trailing_ref_branch_input(
rewritten,
module_ctx,
params,
locals,
extra_local_types,
true,
allow_direct_call=label_target.result_types.length() > 0,
allow_direct_call_operands=direct_call_operands_supported,
) {
Some(spilled) => spilled
None => return false
}
// `br_on_non_null` carries the tested non-null reference as the final label
// payload. Any earlier label results are prefix operands that are already on
// the stack after spilling the tested ref; preserve them across the synthetic
// `if` so the taken edge and null fallthrough see the same prefix stack.
let prefix_scratch : Array[@lib.LocalIdx] = []
let prefix_count = if label_target.result_types.length() > 0 {
label_target.result_types.length() - 1
} else {
0
}
for idx = 0; idx < prefix_count; idx = idx + 1 {
let scratch_type = match
run_hot_pipeline_raw_defaultable_scratch_type(
label_target.result_types[idx],
) {
Some(scratch_type) => scratch_type
None => return false
}
prefix_scratch.push(
run_hot_pipeline_raw_fresh_typed_local(
params, locals, scratch_type, extra_local_types,
),
)
}
for idx = prefix_scratch.length() - 1; idx >= 0; idx = idx - 1 {
rewritten.push(@lib.Instruction::local_set(prefix_scratch[idx]))
}
rewritten.push(@lib.Instruction::local_get(scratch_ref))
rewritten.push(@lib.Instruction::ref_is_null())
let else_body = run_hot_pipeline_raw_append_alias_merge_copies_for_needed(
[],
source_aliases,
canonical_aliases,
canonical_aliases,
initialized_locals,
label_target.needed_alias_copies,
[],
params,
locals,
extra_local_types,
)
for idx = 0; idx < prefix_scratch.length(); idx = idx + 1 {
run_hot_pipeline_raw_append_scratch_get_as_type(
else_body,
prefix_scratch[idx],
label_target.result_types[idx],
)
}
else_body.push(@lib.Instruction::local_get(scratch_ref))
else_body.push(@lib.Instruction::ref_as_non_null())
else_body.push(
@lib.Instruction::br(run_hot_pipeline_raw_label_inc(label_idx)),
)
rewritten.push(
@lib.Instruction::if_(@lib.BlockType::void_(), [], Some(else_body)),
)
for idx = 0; idx < prefix_scratch.length(); idx = idx + 1 {
run_hot_pipeline_raw_append_scratch_get_as_type(
rewritten,
prefix_scratch[idx],
label_target.result_types[idx],
)
}
true
}
///|
fn run_hot_pipeline_raw_cast_diff_type(
source_nullable : Bool,
target_nullable : Bool,
source_heap : @lib.HeapType,
) -> @lib.ValType {
@lib.ValType::ref_type(
@lib.RefType::new(source_nullable && !target_nullable, source_heap),
)
}
///|
fn run_hot_pipeline_raw_defaultable_scratch_type(
val_type : @lib.ValType,
) -> @lib.ValType? {
if @lib.has_default(val_type) {
return Some(val_type)
}
match val_type {
@lib.RefTypeValType(ref_type) =>
Some(
@lib.ValType::ref_type(
@lib.RefType::new(
true,
ref_type.get_heap_type(),
exact=ref_type.is_exact(),
),
),
)
_ => None
}
}
///|
fn run_hot_pipeline_raw_append_scratch_get_as_type(
instrs : Array[@lib.Instruction],
scratch : @lib.LocalIdx,
original_type : @lib.ValType,
) -> Unit {
instrs.push(@lib.Instruction::local_get(scratch))
match original_type {
@lib.RefTypeValType(ref_type) =>
if !ref_type.is_nullable() {
instrs.push(@lib.Instruction::ref_as_non_null())
}
_ => ()
}
}
///|
fn run_hot_pipeline_raw_rewrite_loop_param_proxy_br_on_cast_copy(
rewritten : Array[@lib.Instruction],
label_idx : @lib.LabelIdx,
label_target : RawSsaLabelTarget,
source_nullable : Bool,
source_heap : @lib.HeapType,
target_nullable : Bool,
target_heap : @lib.HeapType,
source_aliases : Array[@lib.LocalIdx],
canonical_aliases : Array[@lib.LocalIdx],
initialized_locals : @bitset.BitSet,
params : Array[@lib.ValType],
locals : @lib.Locals,
extra_local_types : Array[@lib.ValType],
) -> Bool {
let @lib.LabelIdx(raw_label) = label_idx
if raw_label != 0U || label_target.result_types.length() != 1 {
return false
}
let loop_scratch = match label_target.loop_param_scratch {
Some(scratch) => scratch
None => return false
}
let source_type = @lib.ValType::ref_type(
@lib.RefType::new(source_nullable, source_heap),
)
let fallthrough_type = run_hot_pipeline_raw_cast_diff_type(
source_nullable, target_nullable, source_heap,
)
let source_scratch_type = match
run_hot_pipeline_raw_defaultable_scratch_type(source_type) {
Some(scratch_type) => scratch_type
None => return false
}
let scratch_ref = run_hot_pipeline_raw_fresh_typed_local(
params, locals, source_scratch_type, extra_local_types,
)
rewritten.push(@lib.Instruction::local_set(scratch_ref))
let body : Array[@lib.Instruction] = []
run_hot_pipeline_raw_append_scratch_get_as_type(
body, scratch_ref, source_type,
)
body.push(
@lib.Instruction::br_on_cast_fail(
@lib.LabelIdx::new(0U),
source_nullable,
source_heap,
target_nullable,
target_heap,
),
)
body.push(@lib.Instruction::local_set(loop_scratch))
let copies = run_hot_pipeline_raw_append_alias_merge_copies_for_needed(
[],
source_aliases,
canonical_aliases,
canonical_aliases,
initialized_locals,
label_target.needed_alias_copies,
[],
params,
locals,
extra_local_types,
)
for copy in copies {
body.push(copy)
}
body.push(@lib.Instruction::br(@lib.LabelIdx::new(3U)))
rewritten.push(
@lib.Instruction::block(
@lib.BlockType::val_type(fallthrough_type),
@lib.Expr::new(body),
),
)
true
}
///|
fn run_hot_pipeline_raw_rewrite_loop_param_proxy_br_on_cast_fail_copy(
rewritten : Array[@lib.Instruction],
label_idx : @lib.LabelIdx,
label_target : RawSsaLabelTarget,
source_nullable : Bool,
source_heap : @lib.HeapType,
target_nullable : Bool,
target_heap : @lib.HeapType,
source_aliases : Array[@lib.LocalIdx],
canonical_aliases : Array[@lib.LocalIdx],
initialized_locals : @bitset.BitSet,
params : Array[@lib.ValType],
locals : @lib.Locals,
extra_local_types : Array[@lib.ValType],
) -> Bool {
let @lib.LabelIdx(raw_label) = label_idx
if raw_label != 0U || label_target.result_types.length() != 1 {
return false
}
let loop_scratch = match label_target.loop_param_scratch {
Some(scratch) => scratch
None => return false
}
let source_type = @lib.ValType::ref_type(
@lib.RefType::new(source_nullable, source_heap),
)
let fallthrough_type = @lib.ValType::ref_type(
@lib.RefType::new(target_nullable, target_heap),
)
let source_scratch_type = match
run_hot_pipeline_raw_defaultable_scratch_type(source_type) {
Some(scratch_type) => scratch_type
None => return false
}
let scratch_ref = run_hot_pipeline_raw_fresh_typed_local(
params, locals, source_scratch_type, extra_local_types,
)
rewritten.push(@lib.Instruction::local_set(scratch_ref))
let body : Array[@lib.Instruction] = []
run_hot_pipeline_raw_append_scratch_get_as_type(
body, scratch_ref, source_type,
)
body.push(
@lib.Instruction::br_on_cast(
@lib.LabelIdx::new(0U),
source_nullable,
source_heap,
target_nullable,
target_heap,
),
)
body.push(@lib.Instruction::local_set(loop_scratch))
let copies = run_hot_pipeline_raw_append_alias_merge_copies_for_needed(
[],
source_aliases,
canonical_aliases,
canonical_aliases,
initialized_locals,
label_target.needed_alias_copies,
[],
params,
locals,
extra_local_types,
)
for copy in copies {
body.push(copy)
}
body.push(@lib.Instruction::br(@lib.LabelIdx::new(3U)))
rewritten.push(
@lib.Instruction::block(
@lib.BlockType::val_type(fallthrough_type),
@lib.Expr::new(body),
),
)
true
}
///|
fn run_hot_pipeline_raw_rewrite_br_on_cast(
rewritten : Array[@lib.Instruction],
label_idx : @lib.LabelIdx,
label_target : RawSsaLabelTarget,
source_nullable : Bool,
source_heap : @lib.HeapType,
target_nullable : Bool,
target_heap : @lib.HeapType,
source_aliases : Array[@lib.LocalIdx],
canonical_aliases : Array[@lib.LocalIdx],
initialized_locals : @bitset.BitSet,
params : Array[@lib.ValType],
locals : @lib.Locals,
extra_local_types : Array[@lib.ValType],
) -> Bool {
let needs_alias_copy = run_hot_pipeline_raw_has_needed_alias_copy(
source_aliases,
canonical_aliases,
label_target.needed_alias_copies,
)
if !needs_alias_copy {
rewritten.push(
@lib.Instruction::br_on_cast(
label_idx, source_nullable, source_heap, target_nullable, target_heap,
),
)
return true
}
if label_target.is_loop_param_proxy {
return run_hot_pipeline_raw_rewrite_loop_param_proxy_br_on_cast_copy(
rewritten, label_idx, label_target, source_nullable, source_heap, target_nullable,
target_heap, source_aliases, canonical_aliases, initialized_locals, params,
locals, extra_local_types,
)
}
if label_target.result_types.length() == 0 {
return false
}
let source_type = @lib.ValType::ref_type(
@lib.RefType::new(source_nullable, source_heap),
)
let fallthrough_type = run_hot_pipeline_raw_cast_diff_type(
source_nullable, target_nullable, source_heap,
)
let branch_payload_type = @lib.ValType::ref_type(
@lib.RefType::new(target_nullable, target_heap),
)
let source_scratch_type = match
run_hot_pipeline_raw_defaultable_scratch_type(source_type) {
Some(scratch_type) => scratch_type
None => return false
}
let scratch_ref = run_hot_pipeline_raw_fresh_typed_local(
params, locals, source_scratch_type, extra_local_types,
)
let prefix_scratch : Array[@lib.LocalIdx] = []
for idx = 0; idx < label_target.result_types.length() - 1; idx = idx + 1 {
let prefix_scratch_type = match
run_hot_pipeline_raw_defaultable_scratch_type(
label_target.result_types[idx],
) {
Some(scratch_type) => scratch_type
None => return false
}
prefix_scratch.push(
run_hot_pipeline_raw_fresh_typed_local(
params, locals, prefix_scratch_type, extra_local_types,
),
)
}
rewritten.push(@lib.Instruction::local_set(scratch_ref))
if prefix_scratch.length() > 0 {
for idx = prefix_scratch.length() - 1; idx >= 0; idx = idx - 1 {
rewritten.push(@lib.Instruction::local_set(prefix_scratch[idx]))
}
}
let body : Array[@lib.Instruction] = []
run_hot_pipeline_raw_append_scratch_get_as_type(
body, scratch_ref, source_type,
)
body.push(
@lib.Instruction::br_on_cast_fail(
@lib.LabelIdx::new(0U),
source_nullable,
source_heap,
target_nullable,
target_heap,
),
)
if prefix_scratch.length() > 0 {
let branch_payload_scratch_type = match
run_hot_pipeline_raw_defaultable_scratch_type(branch_payload_type) {
Some(scratch_type) => scratch_type
None => return false
}
let branch_payload_scratch = run_hot_pipeline_raw_fresh_typed_local(
params, locals, branch_payload_scratch_type, extra_local_types,
)
body.push(@lib.Instruction::local_set(branch_payload_scratch))
let copies = run_hot_pipeline_raw_append_alias_merge_copies_for_needed(
[],
source_aliases,
canonical_aliases,
canonical_aliases,
initialized_locals,
label_target.needed_alias_copies,
[],
params,
locals,
extra_local_types,
)
for copy in copies {
body.push(copy)
}
for idx = 0; idx < prefix_scratch.length(); idx = idx + 1 {
run_hot_pipeline_raw_append_scratch_get_as_type(
body,
prefix_scratch[idx],
label_target.result_types[idx],
)
}
run_hot_pipeline_raw_append_scratch_get_as_type(
body, branch_payload_scratch, branch_payload_type,
)
} else {
let copies = run_hot_pipeline_raw_append_alias_merge_copies_for_needed(
[],
source_aliases,
canonical_aliases,
canonical_aliases,
initialized_locals,
label_target.needed_alias_copies,
[],
params,
locals,
extra_local_types,
)
for copy in copies {
body.push(copy)
}
}
body.push(@lib.Instruction::br(run_hot_pipeline_raw_label_inc(label_idx)))
rewritten.push(
@lib.Instruction::block(
@lib.BlockType::val_type(fallthrough_type),
@lib.Expr::new(body),
),
)
if prefix_scratch.length() > 0 {
let fallthrough_scratch_type = match
run_hot_pipeline_raw_defaultable_scratch_type(fallthrough_type) {
Some(scratch_type) => scratch_type
None => return false
}
let fallthrough_scratch = run_hot_pipeline_raw_fresh_typed_local(
params, locals, fallthrough_scratch_type, extra_local_types,
)
rewritten.push(@lib.Instruction::local_set(fallthrough_scratch))
for idx = 0; idx < prefix_scratch.length(); idx = idx + 1 {
run_hot_pipeline_raw_append_scratch_get_as_type(
rewritten,
prefix_scratch[idx],
label_target.result_types[idx],
)
}
run_hot_pipeline_raw_append_scratch_get_as_type(
rewritten, fallthrough_scratch, fallthrough_type,
)
}
true
}
///|
fn run_hot_pipeline_raw_rewrite_br_on_cast_fail(
rewritten : Array[@lib.Instruction],
label_idx : @lib.LabelIdx,
label_target : RawSsaLabelTarget,
source_nullable : Bool,
source_heap : @lib.HeapType,
target_nullable : Bool,
target_heap : @lib.HeapType,
source_aliases : Array[@lib.LocalIdx],
canonical_aliases : Array[@lib.LocalIdx],
initialized_locals : @bitset.BitSet,
params : Array[@lib.ValType],
locals : @lib.Locals,
extra_local_types : Array[@lib.ValType],
) -> Bool {
let needs_alias_copy = run_hot_pipeline_raw_has_needed_alias_copy(
source_aliases,
canonical_aliases,
label_target.needed_alias_copies,
)
if !needs_alias_copy {
rewritten.push(
@lib.Instruction::br_on_cast_fail(
label_idx, source_nullable, source_heap, target_nullable, target_heap,
),
)
return true
}
if label_target.is_loop_param_proxy {
return run_hot_pipeline_raw_rewrite_loop_param_proxy_br_on_cast_fail_copy(
rewritten, label_idx, label_target, source_nullable, source_heap, target_nullable,
target_heap, source_aliases, canonical_aliases, initialized_locals, params,
locals, extra_local_types,
)
}
if label_target.result_types.length() == 0 {
return false
}
let source_type = @lib.ValType::ref_type(
@lib.RefType::new(source_nullable, source_heap),
)
let branch_payload_type = run_hot_pipeline_raw_cast_diff_type(
source_nullable, target_nullable, source_heap,
)
let fallthrough_type = @lib.ValType::ref_type(
@lib.RefType::new(target_nullable, target_heap),
)
let source_scratch_type = match
run_hot_pipeline_raw_defaultable_scratch_type(source_type) {
Some(scratch_type) => scratch_type
None => return false
}
let scratch_ref = run_hot_pipeline_raw_fresh_typed_local(
params, locals, source_scratch_type, extra_local_types,
)
let prefix_scratch : Array[@lib.LocalIdx] = []
for idx = 0; idx < label_target.result_types.length() - 1; idx = idx + 1 {
let prefix_scratch_type = match
run_hot_pipeline_raw_defaultable_scratch_type(
label_target.result_types[idx],
) {
Some(scratch_type) => scratch_type
None => return false
}
prefix_scratch.push(
run_hot_pipeline_raw_fresh_typed_local(
params, locals, prefix_scratch_type, extra_local_types,
),
)
}
rewritten.push(@lib.Instruction::local_set(scratch_ref))
if prefix_scratch.length() > 0 {
for idx = prefix_scratch.length() - 1; idx >= 0; idx = idx - 1 {
rewritten.push(@lib.Instruction::local_set(prefix_scratch[idx]))
}
}
let body : Array[@lib.Instruction] = []
run_hot_pipeline_raw_append_scratch_get_as_type(
body, scratch_ref, source_type,
)
body.push(
@lib.Instruction::br_on_cast(
@lib.LabelIdx::new(0U),
source_nullable,
source_heap,
target_nullable,
target_heap,
),
)
if prefix_scratch.length() > 0 {
let branch_payload_scratch_type = match
run_hot_pipeline_raw_defaultable_scratch_type(branch_payload_type) {
Some(scratch_type) => scratch_type
None => return false
}
let branch_payload_scratch = run_hot_pipeline_raw_fresh_typed_local(
params, locals, branch_payload_scratch_type, extra_local_types,
)
body.push(@lib.Instruction::local_set(branch_payload_scratch))
let copies = run_hot_pipeline_raw_append_alias_merge_copies_for_needed(
[],
source_aliases,
canonical_aliases,
canonical_aliases,
initialized_locals,
label_target.needed_alias_copies,
[],
params,
locals,
extra_local_types,
)
for copy in copies {
body.push(copy)
}
for idx = 0; idx < prefix_scratch.length(); idx = idx + 1 {
run_hot_pipeline_raw_append_scratch_get_as_type(
body,
prefix_scratch[idx],
label_target.result_types[idx],
)
}
run_hot_pipeline_raw_append_scratch_get_as_type(
body, branch_payload_scratch, branch_payload_type,
)
} else {
let copies = run_hot_pipeline_raw_append_alias_merge_copies_for_needed(
[],
source_aliases,
canonical_aliases,
canonical_aliases,
initialized_locals,
label_target.needed_alias_copies,
[],
params,
locals,
extra_local_types,
)
for copy in copies {
body.push(copy)
}
}
body.push(@lib.Instruction::br(run_hot_pipeline_raw_label_inc(label_idx)))
rewritten.push(
@lib.Instruction::block(
@lib.BlockType::val_type(fallthrough_type),
@lib.Expr::new(body),
),
)
if prefix_scratch.length() > 0 {
let fallthrough_scratch_type = match
run_hot_pipeline_raw_defaultable_scratch_type(fallthrough_type) {
Some(scratch_type) => scratch_type
None => return false
}
let fallthrough_scratch = run_hot_pipeline_raw_fresh_typed_local(
params, locals, fallthrough_scratch_type, extra_local_types,
)
rewritten.push(@lib.Instruction::local_set(fallthrough_scratch))
for idx = 0; idx < prefix_scratch.length(); idx = idx + 1 {
run_hot_pipeline_raw_append_scratch_get_as_type(
rewritten,
prefix_scratch[idx],
label_target.result_types[idx],
)
}
run_hot_pipeline_raw_append_scratch_get_as_type(
rewritten, fallthrough_scratch, fallthrough_type,
)
}
true
}
///|
fn run_hot_pipeline_raw_rewrite_instrs(
instrs : Array[@lib.Instruction],
module_ctx : @ir.HotModuleContext,
params : Array[@lib.ValType],
locals : @lib.Locals,
local_aliases : Array[@lib.LocalIdx],
canonical_aliases : Array[@lib.LocalIdx],
initialized_locals : @bitset.BitSet,
label_stack : Array[RawSsaLabelTarget],
future_reads : @bitset.BitSet,
future_writes : @bitset.BitSet,
future_reads_before_writes : @bitset.BitSet,
extra_local_types : Array[@lib.ValType],
) -> (
Array[@lib.Instruction],
Array[@lib.LocalIdx],
@bitset.BitSet,
Bool,
@bitset.BitSet,
)? {
let rewritten : Array[@lib.Instruction] = []
let mut aliases = local_aliases
let mut initialized = initialized_locals
let mut fallthrough = true
let exit_depths = run_hot_pipeline_raw_empty_exit_depths(label_stack)
for idx = 0; idx < instrs.length(); idx = idx + 1 {
let instr = instrs[idx]
let was_reachable = fallthrough
match instr {
@lib.Block(block_type, @lib.Expr(body)) => {
let child_future_reads = run_hot_pipeline_raw_future_local_accesses(
instrs,
idx + 1,
future_reads,
true,
)
let child_future_writes = run_hot_pipeline_raw_future_local_accesses(
instrs,
idx + 1,
future_writes,
false,
)
let child_future_reads_before_writes = run_hot_pipeline_raw_future_local_reads_before_writes(
instrs,
idx + 1,
future_reads_before_writes,
label_stack,
)
let label_target = match
run_hot_pipeline_raw_label_target(
module_ctx, block_type, false, child_future_reads_before_writes,
) {
Some(label_target) => label_target
None => return None
}
match
run_hot_pipeline_raw_rewrite_instrs(
body,
module_ctx,
params,
locals,
aliases.copy(),
canonical_aliases,
initialized.copy(),
run_hot_pipeline_raw_push_label_target(label_stack, label_target),
child_future_reads,
child_future_writes,
child_future_reads_before_writes,
extra_local_types,
) {
Some(
(
rewritten_body,
body_aliases,
body_initialized,
body_fallthrough,
body_exit_depths,
)
) => {
let body_exits_current_label = body_exit_depths.length() > 0 &&
body_exit_depths[0]
let mut finalized_body = rewritten_body
if body_fallthrough && body_exits_current_label {
finalized_body = run_hot_pipeline_raw_append_alias_merge_copies_for_needed(
finalized_body,
body_aliases,
canonical_aliases,
canonical_aliases,
body_initialized,
label_target.needed_alias_copies,
label_target.result_types,
params,
locals,
extra_local_types,
)
}
rewritten.push(
@lib.Instruction::block(
block_type,
@lib.Expr::new(finalized_body),
),
)
if was_reachable {
run_hot_pipeline_raw_propagate_outer_exits(
exit_depths, body_exit_depths,
)
if body_fallthrough || body_exits_current_label {
aliases = if body_exits_current_label {
canonical_aliases.copy()
} else {
body_aliases
}
initialized = if body_exits_current_label {
let next_initialized = initialized.copy()
for local_id = 0
local_id < next_initialized.length()
local_id = local_id + 1 {
if body_aliases[local_id] != canonical_aliases[local_id] ||
body_initialized[local_id] {
next_initialized[local_id] = true
}
}
next_initialized
} else {
body_initialized
}
fallthrough = true
} else {
fallthrough = false
}
}
}
None => return None
}
}
@lib.If(block_type, then_body, else_body) => {
let child_future_reads = run_hot_pipeline_raw_future_local_accesses(
instrs,
idx + 1,
future_reads,
true,
)
let child_future_writes = run_hot_pipeline_raw_future_local_accesses(
instrs,
idx + 1,
future_writes,
false,
)
let child_future_reads_before_writes = run_hot_pipeline_raw_future_local_reads_before_writes(
instrs,
idx + 1,
future_reads_before_writes,
label_stack,
)
let label_target = match
run_hot_pipeline_raw_label_target(
module_ctx, block_type, false, child_future_reads_before_writes,
) {
Some(label_target) => label_target
None => return None
}
let then_result = match
run_hot_pipeline_raw_rewrite_instrs(
then_body,
module_ctx,
params,
locals,
aliases.copy(),
canonical_aliases,
initialized.copy(),
run_hot_pipeline_raw_push_label_target(label_stack, label_target),
child_future_reads,
child_future_writes,
child_future_reads_before_writes,
extra_local_types,
) {
Some(result) => result
None => return None
}
let else_result = match else_body {
Some(body) =>
match
run_hot_pipeline_raw_rewrite_instrs(
body,
module_ctx,
params,
locals,
aliases.copy(),
canonical_aliases,
initialized.copy(),
run_hot_pipeline_raw_push_label_target(
label_stack, label_target,
),
child_future_reads,
child_future_writes,
child_future_reads_before_writes,
extra_local_types,
) {
Some(result) => result
None => return None
}
None =>
(
[],
aliases.copy(),
initialized.copy(),
true,
run_hot_pipeline_raw_empty_exit_depths(
run_hot_pipeline_raw_push_label_target(
label_stack, label_target,
),
),
)
}
let (
rewritten_then,
then_aliases,
then_initialized,
then_fallthrough,
then_exit_depths,
) = then_result
let (
rewritten_else,
else_aliases,
else_initialized,
else_fallthrough,
else_exit_depths,
) = else_result
let then_exits_current_label = then_exit_depths.length() > 0 &&
then_exit_depths[0]
let else_exits_current_label = else_exit_depths.length() > 0 &&
else_exit_depths[0]
let has_current_label_exits = then_exits_current_label ||
else_exits_current_label
let mut finalized_then = rewritten_then
let mut finalized_else = rewritten_else
let mut merged_aliases = aliases
if has_current_label_exits {
if then_fallthrough {
finalized_then = run_hot_pipeline_raw_append_alias_merge_copies_for_needed(
finalized_then,
then_aliases,
canonical_aliases,
canonical_aliases,
then_initialized,
label_target.needed_alias_copies,
label_target.result_types,
params,
locals,
extra_local_types,
)
}
if else_fallthrough {
finalized_else = run_hot_pipeline_raw_append_alias_merge_copies_for_needed(
finalized_else,
else_aliases,
canonical_aliases,
canonical_aliases,
else_initialized,
label_target.needed_alias_copies,
label_target.result_types,
params,
locals,
extra_local_types,
)
}
} else if then_fallthrough && else_fallthrough {
if run_hot_pipeline_raw_aliases_equal(then_aliases, else_aliases) {
merged_aliases = then_aliases
} else {
merged_aliases = then_aliases.copy()
let needed_alias_copies = child_future_reads_before_writes.copy()
for local_id = 0
local_id < merged_aliases.length()
local_id = local_id + 1 {
if then_aliases[local_id] == else_aliases[local_id] {
continue
}
if run_hot_pipeline_raw_enclosing_label_needs_alias_copy(
label_stack, local_id,
) {
needed_alias_copies[local_id] = true
}
merged_aliases[local_id] = if !needed_alias_copies[local_id] {
canonical_aliases[local_id]
} else if local_id < params.length() {
canonical_aliases[local_id]
} else {
run_hot_pipeline_raw_fresh_local(
params, locals, local_id, extra_local_types,
)
}
}
finalized_then = run_hot_pipeline_raw_append_alias_merge_copies_for_needed(
finalized_then,
then_aliases,
merged_aliases,
canonical_aliases,
then_initialized,
needed_alias_copies,
label_target.result_types,
params,
locals,
extra_local_types,
)
finalized_else = run_hot_pipeline_raw_append_alias_merge_copies_for_needed(
finalized_else,
else_aliases,
merged_aliases,
canonical_aliases,
else_initialized,
needed_alias_copies,
label_target.result_types,
params,
locals,
extra_local_types,
)
}
}
rewritten.push(
@lib.Instruction::if_(
block_type,
finalized_then,
match else_body {
Some(_) => Some(finalized_else)
None =>
if finalized_else.length() == 0 {
None
} else {
Some(finalized_else)
}
},
),
)
if was_reachable {
run_hot_pipeline_raw_propagate_outer_exits(
exit_depths, then_exit_depths,
)
run_hot_pipeline_raw_propagate_outer_exits(
exit_depths, else_exit_depths,
)
if has_current_label_exits {
if then_fallthrough ||
then_exits_current_label ||
else_fallthrough ||
else_exits_current_label {
aliases = canonical_aliases.copy()
for local_id = 0
local_id < initialized.length()
local_id = local_id + 1 {
if then_aliases[local_id] != canonical_aliases[local_id] ||
then_initialized[local_id] ||
else_aliases[local_id] != canonical_aliases[local_id] ||
else_initialized[local_id] {
initialized[local_id] = true
}
}
fallthrough = true
} else {
fallthrough = false
}
} else {
match (then_fallthrough, else_fallthrough) {
(true, true) => {
aliases = merged_aliases
for local_id = 0
local_id < initialized.length()
local_id = local_id + 1 {
initialized[local_id] = then_initialized[local_id] ||
else_initialized[local_id]
}
fallthrough = true
}
(true, false) => {
aliases = then_aliases
initialized = if run_hot_pipeline_raw_exit_depths_have_outer_exit(
else_exit_depths,
) &&
!run_hot_pipeline_raw_instrs_have_local_read_before_hard_exit(
instrs,
idx + 1,
) {
run_hot_pipeline_raw_bitset_union(
then_initialized, else_initialized,
)
} else {
then_initialized
}
fallthrough = true
}
(false, true) => {
aliases = else_aliases
initialized = if run_hot_pipeline_raw_exit_depths_have_outer_exit(
then_exit_depths,
) &&
!run_hot_pipeline_raw_instrs_have_local_read_before_hard_exit(
instrs,
idx + 1,
) {
run_hot_pipeline_raw_bitset_union(
then_initialized, else_initialized,
)
} else {
else_initialized
}
fallthrough = true
}
(false, false) => {
if run_hot_pipeline_raw_exit_depths_have_outer_exit(
then_exit_depths,
) ||
run_hot_pipeline_raw_exit_depths_have_outer_exit(
else_exit_depths,
) {
initialized = run_hot_pipeline_raw_bitset_union(
then_initialized, else_initialized,
)
}
fallthrough = false
}
}
}
}
}
@lib.Loop(block_type, @lib.Expr(body)) => {
let child_future_reads = run_hot_pipeline_raw_future_local_accesses(
instrs,
idx + 1,
future_reads,
true,
)
let child_future_writes = run_hot_pipeline_raw_future_local_accesses(
instrs,
idx + 1,
future_writes,
false,
)
let after_loop_reads_before_writes = run_hot_pipeline_raw_future_local_reads_before_writes(
instrs,
idx + 1,
future_reads_before_writes,
label_stack,
)
let loop_header_reads_before_writes = ssa_nomerge_raw_loop_live(
body,
@bitset.BitSet::new(canonical_aliases.length()),
label_stack.map(target => target.needed_alias_copies),
)
let child_future_reads_before_writes = run_hot_pipeline_raw_bitset_union(
after_loop_reads_before_writes, loop_header_reads_before_writes,
)
let label_target = match
run_hot_pipeline_raw_label_target(
module_ctx, block_type, true, loop_header_reads_before_writes,
) {
Some(label_target) => label_target
None => return None
}
if run_hot_pipeline_raw_loop_backedge_supported(label_target) {
let loop_initialized = initialized.copy()
for local_id = 0
local_id < loop_initialized.length()
local_id = local_id + 1 {
if loop_header_reads_before_writes[local_id] {
loop_initialized[local_id] = true
}
}
match
run_hot_pipeline_raw_rewrite_instrs(
body,
module_ctx,
params,
locals,
aliases.copy(),
canonical_aliases,
loop_initialized,
run_hot_pipeline_raw_push_label_target(label_stack, label_target),
child_future_reads,
child_future_writes,
child_future_reads_before_writes,
extra_local_types,
) {
Some(
(
rewritten_body,
body_aliases,
body_initialized,
body_fallthrough,
body_exit_depths,
)
) => {
rewritten.push(
@lib.Instruction::loop_(
block_type,
@lib.Expr::new(rewritten_body),
),
)
if was_reachable {
run_hot_pipeline_raw_propagate_outer_exits(
exit_depths, body_exit_depths,
)
if body_fallthrough {
aliases = body_aliases
initialized = body_initialized
fallthrough = true
} else {
fallthrough = false
}
}
}
None => return None
}
} else {
let (loop_param_types, loop_result_types) = match
run_hot_pipeline_raw_loop_param_and_result_types(
module_ctx, block_type,
) {
Some(types) => types
None => return None
}
if loop_result_types.length() == 0 && loop_param_types.length() == 1 {
let loop_param_type = loop_param_types[0]
let direct_loop_param_backedges_supported = match loop_param_type {
@lib.RefTypeValType(_) =>
run_hot_pipeline_raw_instrs_only_direct_loop_param_backedges(
body, 0U, true,
)
_ =>
run_hot_pipeline_raw_instrs_only_direct_loop_param_backedges(
body, 0U, false,
)
}
if !direct_loop_param_backedges_supported {
return None
}
let scratch_type = match
run_hot_pipeline_raw_defaultable_scratch_type(loop_param_type) {
Some(scratch_type) => scratch_type
None => return None
}
let loop_scratch = run_hot_pipeline_raw_fresh_typed_local(
params, locals, scratch_type, extra_local_types,
)
rewritten.push(@lib.Instruction::local_set(loop_scratch))
let loop_initialized = initialized.copy()
for local_id = 0
local_id < loop_initialized.length()
local_id = local_id + 1 {
if loop_header_reads_before_writes[local_id] {
loop_initialized[local_id] = true
}
}
let proxy_label_target = run_hot_pipeline_raw_loop_param_proxy_label_target(
loop_param_type, loop_scratch, loop_header_reads_before_writes,
)
match
run_hot_pipeline_raw_rewrite_instrs(
body,
module_ctx,
params,
locals,
aliases.copy(),
canonical_aliases,
loop_initialized,
run_hot_pipeline_raw_push_label_target(
label_stack, proxy_label_target,
),
child_future_reads,
child_future_writes,
child_future_reads_before_writes,
extra_local_types,
) {
Some(
(
rewritten_body,
body_aliases,
body_initialized,
body_fallthrough,
_,
)
) => {
let inner_body : Array[@lib.Instruction] = []
run_hot_pipeline_raw_append_scratch_get_as_type(
inner_body, loop_scratch, loop_param_type,
)
inner_body.append(rewritten_body)
if body_fallthrough {
inner_body.push(@lib.Instruction::br(@lib.LabelIdx::new(1U)))
} else {
inner_body.push(@lib.Instruction::unreachable_())
}
let outer_body : Array[@lib.Instruction] = [
@lib.Instruction::block(
@lib.BlockType::val_type(loop_param_type),
@lib.Expr::new(inner_body),
),
@lib.Instruction::local_set(loop_scratch),
@lib.Instruction::br(@lib.LabelIdx::new(1U)),
]
rewritten.push(
@lib.Instruction::loop_(
@lib.BlockType::void_(),
@lib.Expr::new([
@lib.Instruction::block(
@lib.BlockType::void_(),
@lib.Expr::new(outer_body),
),
]),
),
)
if was_reachable {
if body_fallthrough {
aliases = body_aliases
initialized = body_initialized
fallthrough = true
} else {
fallthrough = false
}
}
}
None => return None
}
} else if loop_result_types.length() == 0 ||
loop_result_types.length() == 1 ||
loop_param_types.length() > 0 {
let allow_ref_backedges = loop_param_types.any(fn(loop_param_type) {
match loop_param_type {
@lib.RefTypeValType(_) => true
_ => false
}
})
if !run_hot_pipeline_raw_instrs_only_direct_loop_param_backedges_through_try_table(
body, 0U, allow_ref_backedges,
) {
return None
}
let loop_scratches : Array[@lib.LocalIdx] = []
for loop_param_type in loop_param_types {
let scratch_type = match
run_hot_pipeline_raw_defaultable_scratch_type(loop_param_type) {
Some(scratch_type) => scratch_type
None => return None
}
loop_scratches.push(
run_hot_pipeline_raw_fresh_typed_local(
params, locals, scratch_type, extra_local_types,
),
)
}
for scratch_idx = loop_scratches.length() - 1
scratch_idx >= 0
scratch_idx = scratch_idx - 1 {
rewritten.push(
@lib.Instruction::local_set(loop_scratches[scratch_idx]),
)
}
let loop_initialized = initialized.copy()
for local_id = 0
local_id < loop_initialized.length()
local_id = local_id + 1 {
if loop_header_reads_before_writes[local_id] {
loop_initialized[local_id] = true
}
}
let loop_label_target = run_hot_pipeline_raw_loop_param_store_label_target(
loop_param_types, loop_scratches, loop_header_reads_before_writes,
)
match
run_hot_pipeline_raw_rewrite_instrs(
body,
module_ctx,
params,
locals,
aliases.copy(),
canonical_aliases,
loop_initialized,
run_hot_pipeline_raw_push_label_target(
label_stack, loop_label_target,
),
child_future_reads,
child_future_writes,
child_future_reads_before_writes,
extra_local_types,
) {
Some(
(
rewritten_body,
body_aliases,
body_initialized,
body_fallthrough,
_,
)
) => {
let loop_body : Array[@lib.Instruction] = []
for param_idx = 0
param_idx < loop_param_types.length()
param_idx = param_idx + 1 {
run_hot_pipeline_raw_append_scratch_get_as_type(
loop_body,
loop_scratches[param_idx],
loop_param_types[param_idx],
)
}
loop_body.append(
if loop_result_types.length() == 0 {
rewritten_body
} else {
run_hot_pipeline_raw_increment_try_table_catch_labels(
rewritten_body,
)
},
)
if loop_result_types.length() == 0 {
rewritten.push(
@lib.Instruction::loop_(
@lib.BlockType::void_(),
@lib.Expr::new(loop_body),
),
)
} else {
let result_block_type = match
run_hot_pipeline_raw_single_result_block_type(
loop_result_types,
) {
Some(result_block_type) => result_block_type
None => block_type
}
let preserve_plain_result_loop_carrier = loop_result_types.length() ==
1 &&
loop_param_types.length() > 1 &&
run_hot_pipeline_raw_val_types_are_gc_free(loop_param_types) &&
run_hot_pipeline_raw_val_types_are_gc_free(
loop_result_types,
) &&
!run_hot_pipeline_raw_instrs_have_try_table(body) &&
run_hot_pipeline_raw_instrs_current_loop_backedges_are_plain_br(
body, 0U,
)
if preserve_plain_result_loop_carrier {
if !body_fallthrough {
loop_body.push(@lib.Instruction::unreachable_())
}
rewritten.push(
@lib.Instruction::loop_(
result_block_type,
@lib.Expr::new(loop_body),
),
)
if was_reachable {
if body_fallthrough {
aliases = body_aliases
initialized = body_initialized
fallthrough = true
} else {
fallthrough = false
}
}
continue
}
if body_fallthrough {
loop_body.push(@lib.Instruction::br(@lib.LabelIdx::new(1U)))
} else {
loop_body.push(@lib.Instruction::unreachable_())
}
let block_body : Array[@lib.Instruction] = []
if result_block_type == block_type {
for param_idx = 0
param_idx < loop_param_types.length()
param_idx = param_idx + 1 {
run_hot_pipeline_raw_append_scratch_get_as_type(
rewritten,
loop_scratches[param_idx],
loop_param_types[param_idx],
)
block_body.push(@lib.Instruction::drop())
}
}
block_body.push(
@lib.Instruction::loop_(
@lib.BlockType::void_(),
@lib.Expr::new(loop_body),
),
)
block_body.push(@lib.Instruction::unreachable_())
rewritten.push(
@lib.Instruction::block(
result_block_type,
@lib.Expr::new(block_body),
),
)
}
if was_reachable {
if body_fallthrough {
aliases = body_aliases
initialized = body_initialized
fallthrough = true
} else {
fallthrough = false
}
}
}
None => return None
}
} else {
return None
}
}
}
@lib.TryTable(block_type, catches, @lib.Expr(body)) => {
if run_hot_pipeline_raw_try_table_body_has_exceptional_edge(body) {
return None
}
let child_future_reads = run_hot_pipeline_raw_future_local_accesses(
instrs,
idx + 1,
future_reads,
true,
)
let child_future_writes = run_hot_pipeline_raw_future_local_accesses(
instrs,
idx + 1,
future_writes,
false,
)
let child_future_reads_before_writes = run_hot_pipeline_raw_future_local_reads_before_writes(
instrs,
idx + 1,
future_reads_before_writes,
label_stack,
)
let label_target = match
run_hot_pipeline_raw_label_target(
module_ctx, block_type, false, child_future_reads_before_writes,
) {
Some(label_target) => label_target
None => return None
}
match
run_hot_pipeline_raw_rewrite_instrs(
body,
module_ctx,
params,
locals,
aliases.copy(),
canonical_aliases,
initialized.copy(),
run_hot_pipeline_raw_push_label_target(label_stack, label_target),
child_future_reads,
child_future_writes,
child_future_reads_before_writes,
extra_local_types,
) {
Some(
(
rewritten_body,
body_aliases,
body_initialized,
body_fallthrough,
body_exit_depths,
)
) => {
let body_exits_current_label = body_exit_depths.length() > 0 &&
body_exit_depths[0]
let mut finalized_body = rewritten_body
if body_fallthrough && body_exits_current_label {
finalized_body = run_hot_pipeline_raw_append_alias_merge_copies_for_needed(
finalized_body,
body_aliases,
canonical_aliases,
canonical_aliases,
body_initialized,
label_target.needed_alias_copies,
label_target.result_types,
params,
locals,
extra_local_types,
)
}
rewritten.push(
@lib.Instruction::try_table(
block_type,
catches,
@lib.Expr::new(finalized_body),
),
)
if was_reachable {
run_hot_pipeline_raw_propagate_outer_exits(
exit_depths, body_exit_depths,
)
if body_fallthrough || body_exits_current_label {
aliases = if body_exits_current_label {
canonical_aliases.copy()
} else {
body_aliases
}
initialized = if body_exits_current_label {
let next_initialized = initialized.copy()
for local_id = 0
local_id < next_initialized.length()
local_id = local_id + 1 {
if body_aliases[local_id] != canonical_aliases[local_id] ||
body_initialized[local_id] {
next_initialized[local_id] = true
}
}
next_initialized
} else {
body_initialized
}
fallthrough = true
} else {
fallthrough = false
}
}
}
None => return None
}
}
@lib.Br(label_idx) => {
let label_target = match
run_hot_pipeline_raw_resolve_label_target(label_stack, label_idx) {
Some(label_target) => label_target
None => return None
}
if label_target.is_loop_param_store {
if !run_hot_pipeline_raw_append_loop_param_store_backedge(
rewritten, label_idx, label_target, aliases, canonical_aliases, initialized,
params, locals, extra_local_types,
) {
return None
}
if was_reachable {
fallthrough = false
}
continue
}
if label_target.is_loop &&
!run_hot_pipeline_raw_loop_backedge_supported(label_target) {
return None
}
let branch_instrs = run_hot_pipeline_raw_append_alias_merge_copies_for_needed(
[],
aliases,
canonical_aliases,
canonical_aliases,
initialized,
label_target.needed_alias_copies,
label_target.result_types,
params,
locals,
extra_local_types,
)
branch_instrs.push(@lib.Instruction::br(label_idx))
rewritten.append(branch_instrs)
if was_reachable {
if !label_target.is_loop {
let @lib.LabelIdx(raw) = label_idx
exit_depths[raw.reinterpret_as_int()] = true
}
fallthrough = false
}
}
@lib.BrIf(label_idx) => {
let label_target = match
run_hot_pipeline_raw_resolve_label_target(label_stack, label_idx) {
Some(label_target) => label_target
None => return None
}
if label_target.is_loop_param_store {
match
run_hot_pipeline_raw_rewrite_loop_param_store_br_if(
label_idx, label_target, aliases, canonical_aliases, initialized, params,
locals, extra_local_types,
) {
Some(branch_instrs) => rewritten.append(branch_instrs)
None => return None
}
continue
}
if label_target.is_loop {
if !run_hot_pipeline_raw_loop_backedge_supported(label_target) ||
run_hot_pipeline_raw_has_needed_alias_copy(
aliases,
canonical_aliases,
label_target.needed_alias_copies,
) {
return None
}
}
match
run_hot_pipeline_raw_rewrite_br_if(
module_ctx, label_idx, label_target, aliases, canonical_aliases, initialized,
params, locals, extra_local_types,
) {
Some(branch_instrs) => rewritten.append(branch_instrs)
None => return None
}
if was_reachable && !label_target.is_loop {
let @lib.LabelIdx(raw) = label_idx
exit_depths[raw.reinterpret_as_int()] = true
}
}
@lib.BrOnNull(label_idx) => {
let label_target = match
run_hot_pipeline_raw_resolve_label_target(label_stack, label_idx) {
Some(label_target) => label_target
None => return None
}
if label_target.is_loop_param_store {
if !run_hot_pipeline_raw_rewrite_loop_param_store_br_on_null(
rewritten, module_ctx, label_idx, label_target, aliases, canonical_aliases,
initialized, params, locals, extra_local_types,
) {
return None
}
continue
}
if label_target.is_loop &&
!run_hot_pipeline_raw_loop_backedge_supported(label_target) {
return None
}
if !run_hot_pipeline_raw_rewrite_br_on_null(
rewritten, module_ctx, label_idx, label_target, aliases, canonical_aliases,
initialized, params, locals, extra_local_types,
) {
return None
}
if was_reachable && !label_target.is_loop {
let @lib.LabelIdx(raw) = label_idx
exit_depths[raw.reinterpret_as_int()] = true
}
}
@lib.BrOnNonNull(label_idx) => {
let label_target = match
run_hot_pipeline_raw_resolve_label_target(label_stack, label_idx) {
Some(label_target) => label_target
None => return None
}
if label_target.is_loop_param_store {
if !run_hot_pipeline_raw_rewrite_loop_param_store_br_on_non_null(
rewritten, module_ctx, label_idx, label_target, aliases, canonical_aliases,
initialized, params, locals, extra_local_types,
) {
return None
}
continue
}
if label_target.is_loop {
return None
}
if !run_hot_pipeline_raw_rewrite_br_on_non_null(
rewritten, module_ctx, label_idx, label_target, aliases, canonical_aliases,
initialized, params, locals, extra_local_types,
) {
return None
}
if was_reachable {
let @lib.LabelIdx(raw) = label_idx
exit_depths[raw.reinterpret_as_int()] = true
}
}
@lib.BrOnCastDescEq(_, _, _) | @lib.BrOnCastDescEqFail(_, _, _) =>
return None
@lib.BrOnCast(
label_idx,
@lib.CastOp(
source_nullable,
target_nullable,
source_exact,
target_exact
),
source_heap,
target_heap
) => {
if source_exact || target_exact {
return None
}
let label_target = match
run_hot_pipeline_raw_resolve_label_target(label_stack, label_idx) {
Some(label_target) => label_target
None => return None
}
if label_target.is_loop_param_store {
if !run_hot_pipeline_raw_rewrite_loop_param_store_br_on_cast(
rewritten, label_idx, label_target, source_nullable, source_heap, target_nullable,
target_heap, aliases, canonical_aliases, initialized, params, locals,
extra_local_types,
) {
return None
}
continue
}
if label_target.is_loop {
return None
}
if !run_hot_pipeline_raw_rewrite_br_on_cast(
rewritten, label_idx, label_target, source_nullable, source_heap, target_nullable,
target_heap, aliases, canonical_aliases, initialized, params, locals,
extra_local_types,
) {
return None
}
if was_reachable {
let @lib.LabelIdx(raw) = label_idx
exit_depths[raw.reinterpret_as_int()] = true
}
}
@lib.BrOnCastFail(
label_idx,
@lib.CastOp(
source_nullable,
target_nullable,
source_exact,
target_exact
),
source_heap,
target_heap
) => {
if source_exact || target_exact {
return None
}
let label_target = match
run_hot_pipeline_raw_resolve_label_target(label_stack, label_idx) {
Some(label_target) => label_target
None => return None
}
if label_target.is_loop_param_store {
if !run_hot_pipeline_raw_rewrite_loop_param_store_br_on_cast_fail(
rewritten, label_idx, label_target, source_nullable, source_heap, target_nullable,
target_heap, aliases, canonical_aliases, initialized, params, locals,
extra_local_types,
) {
return None
}
continue
}
if label_target.is_loop {
return None
}
if !run_hot_pipeline_raw_rewrite_br_on_cast_fail(
rewritten, label_idx, label_target, source_nullable, source_heap, target_nullable,
target_heap, aliases, canonical_aliases, initialized, params, locals,
extra_local_types,
) {
return None
}
if was_reachable {
let @lib.LabelIdx(raw) = label_idx
exit_depths[raw.reinterpret_as_int()] = true
}
}
@lib.BrTable(labels, default_label) => {
match
run_hot_pipeline_raw_br_table_loop_param_store_target(
labels, default_label, label_stack,
) {
Some(label_target) => {
rewritten.push(@lib.Instruction::drop())
if !run_hot_pipeline_raw_append_loop_param_store_backedge(
rewritten, default_label, label_target, aliases, canonical_aliases,
initialized, params, locals, extra_local_types,
) {
return None
}
if was_reachable {
fallthrough = false
}
continue
}
None => ()
}
match
run_hot_pipeline_raw_br_table_mixed_loop_param_proxy_target(
labels, default_label, label_stack,
) {
Some(proxy_target) => {
if !run_hot_pipeline_raw_rewrite_mixed_loop_param_proxy_br_table(
rewritten, labels, default_label, proxy_target, label_stack, aliases,
canonical_aliases, initialized, params, locals, extra_local_types,
) {
return None
}
if was_reachable {
fallthrough = false
}
continue
}
None => ()
}
match
run_hot_pipeline_raw_br_table_mixed_loop_param_store_target(
labels, default_label, label_stack,
) {
Some(loop_target) => {
if !run_hot_pipeline_raw_rewrite_mixed_loop_param_store_br_table(
rewritten, labels, default_label, loop_target, label_stack, aliases,
canonical_aliases, initialized, params, locals, extra_local_types,
) {
return None
}
if was_reachable {
fallthrough = false
}
continue
}
None => ()
}
let needed_alias_copies = match
run_hot_pipeline_raw_br_table_alias_copies(
labels,
default_label,
label_stack,
canonical_aliases.length(),
) {
Some(needed_alias_copies) => needed_alias_copies
None => return None
}
let branch_instrs = run_hot_pipeline_raw_append_alias_merge_copies_for_needed(
[],
aliases,
canonical_aliases,
canonical_aliases,
initialized,
needed_alias_copies,
[],
params,
locals,
extra_local_types,
)
branch_instrs.push(instr)
rewritten.append(branch_instrs)
if was_reachable {
run_hot_pipeline_raw_mark_br_table_exit_depths(
exit_depths, labels, default_label, label_stack,
)
fallthrough = false
}
}
@lib.LocalGet(@lib.LocalIdx(raw)) =>
run_hot_pipeline_raw_append_local_read(
rewritten,
aliases,
canonical_aliases,
initialized,
params,
locals,
raw.reinterpret_as_int(),
)
@lib.LocalSet(@lib.LocalIdx(raw)) => {
let local_id = raw.reinterpret_as_int()
let later_read_before_next_write = run_hot_pipeline_raw_instrs_have_later_local_read_before_next_local_write(
instrs,
idx + 1,
local_id,
)
let later_read = run_hot_pipeline_raw_instrs_have_later_local_read(
instrs,
idx + 1,
local_id,
) ||
(local_id < params.length() && future_reads[local_id])
let future_read_before_next_write = future_reads_before_writes[local_id]
let later_write = run_hot_pipeline_raw_instrs_have_later_local_write(
instrs,
idx + 1,
local_id,
) ||
(local_id < params.length() && future_writes[local_id])
let branch_exit_needs_canonical = run_hot_pipeline_raw_next_br_needs_canonical_alias(
instrs,
idx + 1,
label_stack,
local_id,
)
if branch_exit_needs_canonical ||
run_hot_pipeline_raw_in_branching_value_block(instrs, label_stack) ||
(
aliases[local_id] == canonical_aliases[local_id] &&
later_read &&
!later_write
) ||
later_read_before_next_write ||
future_read_before_next_write {
initialized[local_id] = true
aliases[local_id] = canonical_aliases[local_id]
rewritten.push(
@lib.Instruction::local_set(canonical_aliases[local_id]),
)
} else {
let fresh_local = run_hot_pipeline_raw_fresh_local(
params, locals, local_id, extra_local_types,
)
aliases[local_id] = fresh_local
initialized[local_id] = true
rewritten.push(@lib.Instruction::local_set(fresh_local))
}
}
@lib.LocalTee(@lib.LocalIdx(raw)) => {
let local_id = raw.reinterpret_as_int()
let later_read_before_next_write = run_hot_pipeline_raw_instrs_have_later_local_read_before_next_local_write(
instrs,
idx + 1,
local_id,
)
let later_read = run_hot_pipeline_raw_instrs_have_later_local_read(
instrs,
idx + 1,
local_id,
) ||
(local_id < params.length() && future_reads[local_id])
let future_read_before_next_write = future_reads_before_writes[local_id]
let later_write = run_hot_pipeline_raw_instrs_have_later_local_write(
instrs,
idx + 1,
local_id,
) ||
(local_id < params.length() && future_writes[local_id])
let branch_exit_needs_canonical = (
later_read || local_id < params.length()
) &&
run_hot_pipeline_raw_plain_br_before_local_write(
instrs,
idx + 1,
local_id,
)
if branch_exit_needs_canonical ||
run_hot_pipeline_raw_in_branching_value_block(instrs, label_stack) ||
(
aliases[local_id] == canonical_aliases[local_id] &&
later_read &&
!later_write
) ||
later_read_before_next_write ||
future_read_before_next_write {
initialized[local_id] = true
aliases[local_id] = canonical_aliases[local_id]
rewritten.push(
@lib.Instruction::local_tee(canonical_aliases[local_id]),
)
} else {
let fresh_local = run_hot_pipeline_raw_fresh_local(
params, locals, local_id, extra_local_types,
)
aliases[local_id] = fresh_local
initialized[local_id] = true
rewritten.push(@lib.Instruction::local_tee(fresh_local))
}
}
@lib.Return
| @lib.ReturnCall(_)
| @lib.ReturnCallIndirect(_, _)
| @lib.ReturnCallRef(_)
| @lib.Throw(_)
| @lib.ThrowRef
| @lib.Unreachable => {
rewritten.push(instr)
fallthrough = false
}
_ => rewritten.push(instr)
}
}
Some((rewritten, aliases, initialized, fallthrough, exit_depths))
}
///|
fn run_hot_pipeline_raw_count_local_gets(
instrs : Array[@lib.Instruction],
local_id : Int32,
) -> Int32 {
let mut count = 0
for instr in instrs {
match instr {
@lib.LocalGet(@lib.LocalIdx(raw)) =>
if raw.reinterpret_as_int() == local_id {
count = count + 1
}
@lib.Block(_, @lib.Expr(body))
| @lib.Loop(_, @lib.Expr(body))
| @lib.TryTable(_, _, @lib.Expr(body)) =>
count = count + run_hot_pipeline_raw_count_local_gets(body, local_id)
@lib.If(_, then_body, else_body) => {
count = count +
run_hot_pipeline_raw_count_local_gets(then_body, local_id)
match else_body {
Some(body) =>
count = count +
run_hot_pipeline_raw_count_local_gets(body, local_id)
None => ()
}
}
_ => ()
}
}
count
}
///|
fn run_hot_pipeline_raw_collect_written_body_locals(
instrs : Array[@lib.Instruction],
param_count : Int32,
out : Array[Int32],
) -> Unit {
for instr in instrs {
match instr {
@lib.LocalSet(@lib.LocalIdx(raw)) | @lib.LocalTee(@lib.LocalIdx(raw)) => {
let local_id = raw.reinterpret_as_int()
if local_id >= param_count && !out.contains(local_id) {
out.push(local_id)
}
}
@lib.Block(_, @lib.Expr(body))
| @lib.Loop(_, @lib.Expr(body))
| @lib.TryTable(_, _, @lib.Expr(body)) =>
run_hot_pipeline_raw_collect_written_body_locals(body, param_count, out)
@lib.If(_, then_body, else_body) => {
run_hot_pipeline_raw_collect_written_body_locals(
then_body, param_count, out,
)
match else_body {
Some(body) =>
run_hot_pipeline_raw_collect_written_body_locals(
body, param_count, out,
)
None => ()
}
}
_ => ()
}
}
}
///|
fn run_hot_pipeline_raw_rewrite_local_refs(
instrs : Array[@lib.Instruction],
old_local : Int32,
new_local : @lib.LocalIdx,
) -> Array[@lib.Instruction] {
let rewritten : Array[@lib.Instruction] = []
for instr in instrs {
match instr {
@lib.LocalGet(@lib.LocalIdx(raw)) if raw.reinterpret_as_int() == old_local =>
rewritten.push(@lib.Instruction::local_get(new_local))
@lib.LocalSet(@lib.LocalIdx(raw)) if raw.reinterpret_as_int() == old_local =>
rewritten.push(@lib.Instruction::local_set(new_local))
@lib.LocalTee(@lib.LocalIdx(raw)) if raw.reinterpret_as_int() == old_local =>
rewritten.push(@lib.Instruction::local_tee(new_local))
@lib.Block(block_type, @lib.Expr(body)) =>
rewritten.push(
@lib.Instruction::block(
block_type,
@lib.Expr::new(
run_hot_pipeline_raw_rewrite_local_refs(
body, old_local, new_local,
),
),
),
)
@lib.Loop(block_type, @lib.Expr(body)) =>
rewritten.push(
@lib.Instruction::loop_(
block_type,
@lib.Expr::new(
run_hot_pipeline_raw_rewrite_local_refs(
body, old_local, new_local,
),
),
),
)
@lib.TryTable(block_type, catches, @lib.Expr(body)) =>
rewritten.push(
@lib.Instruction::try_table(
block_type,
catches,
@lib.Expr::new(
run_hot_pipeline_raw_rewrite_local_refs(
body, old_local, new_local,
),
),
),
)
@lib.If(block_type, then_body, else_body) =>
rewritten.push(
@lib.Instruction::if_(
block_type,
run_hot_pipeline_raw_rewrite_local_refs(
then_body, old_local, new_local,
),
match else_body {
Some(body) =>
Some(
run_hot_pipeline_raw_rewrite_local_refs(
body, old_local, new_local,
),
)
None => None
},
),
)
_ => rewritten.push(instr)
}
}
rewritten
}
///|
fn run_hot_pipeline_raw_instr_writes_local(
instr : @lib.Instruction,
local_id : Int32,
) -> Bool {
match instr {
@lib.LocalSet(@lib.LocalIdx(raw)) | @lib.LocalTee(@lib.LocalIdx(raw)) =>
raw.reinterpret_as_int() == local_id
_ => false
}
}
///|
fn run_hot_pipeline_raw_rewrite_local_refs_before_last_write(
instrs : Array[@lib.Instruction],
old_local : Int32,
new_local : @lib.LocalIdx,
) -> Array[@lib.Instruction] {
let mut last_write_idx = -1
for idx = 0; idx < instrs.length(); idx = idx + 1 {
if run_hot_pipeline_raw_instr_writes_local(instrs[idx], old_local) {
last_write_idx = idx
}
}
if last_write_idx < 0 {
return run_hot_pipeline_raw_rewrite_local_refs(instrs, old_local, new_local)
}
let rewritten : Array[@lib.Instruction] = []
for idx = 0; idx < instrs.length(); idx = idx + 1 {
if idx < last_write_idx {
rewritten.push(
run_hot_pipeline_raw_rewrite_local_refs(
[instrs[idx]],
old_local,
new_local,
)[0],
)
} else {
rewritten.push(instrs[idx])
}
}
rewritten
}
///|
fn run_hot_pipeline_raw_instrs_have_memory_store(
instrs : Array[@lib.Instruction],
) -> Bool {
for instr in instrs {
match instr {
@lib.MemoryGrow(_)
| @lib.I32Store(_)
| @lib.I64Store(_)
| @lib.F32Store(_)
| @lib.F64Store(_)
| @lib.I32Store8(_)
| @lib.I32Store16(_)
| @lib.I64Store8(_)
| @lib.I64Store16(_)
| @lib.I64Store32(_) => return true
@lib.Block(_, @lib.Expr(body))
| @lib.Loop(_, @lib.Expr(body))
| @lib.TryTable(_, _, @lib.Expr(body)) =>
if run_hot_pipeline_raw_instrs_have_memory_store(body) {
return true
}
@lib.If(_, then_body, else_body) => {
if run_hot_pipeline_raw_instrs_have_memory_store(then_body) {
return true
}
match else_body {
Some(body) =>
if run_hot_pipeline_raw_instrs_have_memory_store(body) {
return true
}
None => ()
}
}
_ => ()
}
}
false
}
///|
fn run_hot_pipeline_raw_instrs_have_memory_access(
instrs : Array[@lib.Instruction],
) -> Bool {
if run_hot_pipeline_raw_instrs_have_memory_store(instrs) {
return true
}
for instr in instrs {
if run_hot_pipeline_optimize_instructions_instr_is_plain_load(instr) {
return true
}
match instr {
@lib.MemoryGrow(_)
| @lib.I32Store(_)
| @lib.I64Store(_)
| @lib.F32Store(_)
| @lib.F64Store(_)
| @lib.I32Store8(_)
| @lib.I32Store16(_)
| @lib.I64Store8(_)
| @lib.I64Store16(_)
| @lib.I64Store32(_) => return true
@lib.Block(_, @lib.Expr(body))
| @lib.Loop(_, @lib.Expr(body))
| @lib.TryTable(_, _, @lib.Expr(body)) =>
if run_hot_pipeline_raw_instrs_have_memory_access(body) {
return true
}
@lib.If(_, then_body, else_body) => {
if run_hot_pipeline_raw_instrs_have_memory_access(then_body) {
return true
}
match else_body {
Some(body) =>
if run_hot_pipeline_raw_instrs_have_memory_access(body) {
return true
}
None => ()
}
}
_ => ()
}
}
false
}
///|
fn run_hot_pipeline_raw_instrs_have_table_grow(
instrs : Array[@lib.Instruction],
) -> Bool {
for instr in instrs {
match instr {
@lib.TableGrow(_) => return true
@lib.Block(_, @lib.Expr(body))
| @lib.Loop(_, @lib.Expr(body))
| @lib.TryTable(_, _, @lib.Expr(body)) =>
if run_hot_pipeline_raw_instrs_have_table_grow(body) {
return true
}
@lib.If(_, then_body, else_body) => {
if run_hot_pipeline_raw_instrs_have_table_grow(then_body) {
return true
}
match else_body {
Some(body) =>
if run_hot_pipeline_raw_instrs_have_table_grow(body) {
return true
}
None => ()
}
}
_ => ()
}
}
false
}
///|
fn run_hot_pipeline_raw_instrs_have_table_update(
instrs : Array[@lib.Instruction],
) -> Bool {
for instr in instrs {
match instr {
@lib.TableSet(_) | @lib.TableGrow(_) => return true
@lib.Block(_, @lib.Expr(body))
| @lib.Loop(_, @lib.Expr(body))
| @lib.TryTable(_, _, @lib.Expr(body)) =>
if run_hot_pipeline_raw_instrs_have_table_update(body) {
return true
}
@lib.If(_, then_body, else_body) => {
if run_hot_pipeline_raw_instrs_have_table_update(then_body) {
return true
}
match else_body {
Some(body) =>
if run_hot_pipeline_raw_instrs_have_table_update(body) {
return true
}
None => ()
}
}
_ => ()
}
}
false
}
///|
fn run_hot_pipeline_raw_count_non_loop_local_writes(
instrs : Array[@lib.Instruction],
local_id : Int32,
) -> Int32 {
let mut count = 0
for instr in instrs {
match instr {
@lib.LocalSet(@lib.LocalIdx(idx)) | @lib.LocalTee(@lib.LocalIdx(idx)) =>
if idx.reinterpret_as_int() == local_id {
count = count + 1
}
@lib.Block(_, @lib.Expr(body)) | @lib.TryTable(_, _, @lib.Expr(body)) =>
count = count +
run_hot_pipeline_raw_count_non_loop_local_writes(body, local_id)
@lib.If(_, then_body, else_body) => {
count = count +
run_hot_pipeline_raw_count_non_loop_local_writes(then_body, local_id)
match else_body {
Some(body) =>
count = count +
run_hot_pipeline_raw_count_non_loop_local_writes(body, local_id)
None => ()
}
}
_ => ()
}
}
count
}
///|
fn run_hot_pipeline_raw_count_local_writes(
instrs : Array[@lib.Instruction],
local_id : Int32,
) -> Int32 {
let mut count = 0
for instr in instrs {
match instr {
@lib.LocalSet(@lib.LocalIdx(idx)) | @lib.LocalTee(@lib.LocalIdx(idx)) =>
if idx.reinterpret_as_int() == local_id {
count = count + 1
}
@lib.Block(_, @lib.Expr(body))
| @lib.Loop(_, @lib.Expr(body))
| @lib.TryTable(_, _, @lib.Expr(body)) =>
count = count + run_hot_pipeline_raw_count_local_writes(body, local_id)
@lib.If(_, then_body, else_body) => {
count = count +
run_hot_pipeline_raw_count_local_writes(then_body, local_id)
match else_body {
Some(body) =>
count = count +
run_hot_pipeline_raw_count_local_writes(body, local_id)
None => ()
}
}
_ => ()
}
}
count
}
///|
fn run_hot_pipeline_raw_set_param_alias(
param_aliases : Array[@lib.LocalIdx?],
local_id : Int32,
mapped_alias : @lib.LocalIdx?,
) -> Unit {
if local_id < 0 {
return
}
match mapped_alias {
Some(_) =>
while param_aliases.length() <= local_id {
param_aliases.push(None)
}
None => ()
}
if local_id < param_aliases.length() {
param_aliases[local_id] = mapped_alias
}
}
///|
fn run_hot_pipeline_raw_no_else_branch_merge_locals(
instrs : Array[@lib.Instruction],
param_count : Int32,
base_local_count : Int32,
) -> @bitset.BitSet {
let locals = @bitset.BitSet::new(base_local_count)
for idx = 0; idx < instrs.length(); idx = idx + 1 {
match instrs[idx] {
@lib.If(_, then_body, else_body) => {
let then_writes = @bitset.BitSet::new(base_local_count)
let else_writes = @bitset.BitSet::new(base_local_count)
run_hot_pipeline_raw_mark_instrs_local_writes(then_body, then_writes)
if else_body is Some(body) {
run_hot_pipeline_raw_mark_instrs_local_writes(body, else_writes)
}
for local_id = 0; local_id < base_local_count; local_id = local_id + 1 {
if then_writes[local_id] &&
else_body is None &&
run_hot_pipeline_raw_instrs_have_later_local_read_before_next_local_write(
instrs,
idx + 1,
local_id,
) {
locals[local_id] = true
} else if local_id >= param_count &&
then_writes[local_id] != else_writes[local_id] &&
run_hot_pipeline_raw_instrs_have_later_local_read_before_next_local_write(
instrs,
idx + 1,
local_id,
) {
locals[local_id] = true
}
}
}
_ => ()
}
}
locals
}
///|
fn run_hot_pipeline_raw_mark_instr_first_local_reads(
instr : @lib.Instruction,
first_accesses : Array[Int32],
) -> Unit {
match instr {
@lib.LocalGet(@lib.LocalIdx(raw)) => {
let local_id = raw.reinterpret_as_int()
if local_id >= 0 &&
local_id < first_accesses.length() &&
first_accesses[local_id] == 0 {
first_accesses[local_id] = 1
}
}
@lib.Block(_, @lib.Expr(body))
| @lib.Loop(_, @lib.Expr(body))
| @lib.TryTable(_, _, @lib.Expr(body)) =>
run_hot_pipeline_raw_mark_instrs_first_local_reads(body, first_accesses)
@lib.If(_, then_body, else_body) => {
run_hot_pipeline_raw_mark_instrs_first_local_reads(
then_body, first_accesses,
)
match else_body {
Some(body) =>
run_hot_pipeline_raw_mark_instrs_first_local_reads(
body, first_accesses,
)
None => ()
}
}
@lib.Try(_, @lib.Expr(body), catches, _) => {
run_hot_pipeline_raw_mark_instrs_first_local_reads(body, first_accesses)
for arm in catches {
let @lib.Expr(body) = match arm {
@lib.LegacyCatch(_, body) | @lib.LegacyCatchAll(body) => body
}
run_hot_pipeline_raw_mark_instrs_first_local_reads(body, first_accesses)
}
}
_ => ()
}
}
///|
fn run_hot_pipeline_raw_mark_instrs_first_local_reads(
instrs : Array[@lib.Instruction],
first_accesses : Array[Int32],
) -> Unit {
for instr in instrs {
run_hot_pipeline_raw_mark_instr_first_local_reads(instr, first_accesses)
}
}
///|
fn run_hot_pipeline_raw_mark_instr_local_writes(
instr : @lib.Instruction,
locals : @bitset.BitSet,
) -> Unit {
match instr {
@lib.LocalSet(@lib.LocalIdx(raw)) | @lib.LocalTee(@lib.LocalIdx(raw)) => {
let local_id = raw.reinterpret_as_int()
if local_id >= 0 && local_id < locals.length() {
locals[local_id] = true
}
}
@lib.Block(_, @lib.Expr(body))
| @lib.Loop(_, @lib.Expr(body))
| @lib.TryTable(_, _, @lib.Expr(body)) =>
run_hot_pipeline_raw_mark_instrs_local_writes(body, locals)
@lib.If(_, then_body, else_body) => {
run_hot_pipeline_raw_mark_instrs_local_writes(then_body, locals)
match else_body {
Some(body) =>
run_hot_pipeline_raw_mark_instrs_local_writes(body, locals)
None => ()
}
}
@lib.Try(_, @lib.Expr(body), catches, _) => {
run_hot_pipeline_raw_mark_instrs_local_writes(body, locals)
for arm in catches {
let @lib.Expr(body) = match arm {
@lib.LegacyCatch(_, body) | @lib.LegacyCatchAll(body) => body
}
run_hot_pipeline_raw_mark_instrs_local_writes(body, locals)
}
}
_ => ()
}
}
///|
fn run_hot_pipeline_raw_mark_instrs_local_writes(
instrs : Array[@lib.Instruction],
locals : @bitset.BitSet,
) -> Unit {
for instr in instrs {
run_hot_pipeline_raw_mark_instr_local_writes(instr, locals)
}
}
///|
fn run_hot_pipeline_raw_loop_carried_merge_locals(
instrs : Array[@lib.Instruction],
base_local_count : Int32,
) -> @bitset.BitSet {
let locals = @bitset.BitSet::new(base_local_count)
for instr in instrs {
match instr {
@lib.Loop(_, @lib.Expr(body)) => {
let loop_writes = @bitset.BitSet::new(base_local_count)
let first_accesses = Array::make(base_local_count, 0)
run_hot_pipeline_raw_mark_instrs_local_writes(body, loop_writes)
for idx = 0; idx < body.length(); idx = idx + 1 {
if run_hot_pipeline_raw_simplify_locals_instr_is_unconditional_terminator(
body[idx],
) {
break
}
match body[idx] {
@lib.LocalSet(@lib.LocalIdx(raw))
| @lib.LocalTee(@lib.LocalIdx(raw)) => {
let local_id = raw.reinterpret_as_int()
if local_id >= 0 &&
local_id < first_accesses.length() &&
first_accesses[local_id] == 0 {
first_accesses[local_id] = 2
}
}
_ =>
run_hot_pipeline_raw_mark_instr_first_local_reads(
body[idx],
first_accesses,
)
}
}
for local_id = 0; local_id < base_local_count; local_id = local_id + 1 {
if loop_writes[local_id] && first_accesses[local_id] == 1 {
locals[local_id] = true
}
}
}
_ => ()
}
}
locals
}
///|
fn run_hot_pipeline_raw_branch_writes_local(
instrs : Array[@lib.Instruction],
local_id : Int32,
) -> Bool {
for instr in instrs {
match instr {
@lib.LocalSet(@lib.LocalIdx(idx)) | @lib.LocalTee(@lib.LocalIdx(idx)) =>
if idx.reinterpret_as_int() == local_id {
return true
}
@lib.Block(_, @lib.Expr(body))
| @lib.Loop(_, @lib.Expr(body))
| @lib.TryTable(_, _, @lib.Expr(body)) =>
if run_hot_pipeline_raw_branch_writes_local(body, local_id) {
return true
}
@lib.If(_, then_body, else_body) => {
if run_hot_pipeline_raw_branch_writes_local(then_body, local_id) {
return true
}
match else_body {
Some(body) =>
if run_hot_pipeline_raw_branch_writes_local(body, local_id) {
return true
}
None => ()
}
}
_ => ()
}
}
false
}
///|
fn run_hot_pipeline_raw_count_call_like(
instrs : Array[@lib.Instruction],
) -> Int32 {
let mut count = 0
for instr in instrs {
match instr {
@lib.Call(_) | @lib.CallIndirect(_, _) | @lib.CallRef(_) =>
count = count + 1
@lib.Block(_, @lib.Expr(body))
| @lib.Loop(_, @lib.Expr(body))
| @lib.TryTable(_, _, @lib.Expr(body)) =>
count = count + run_hot_pipeline_raw_count_call_like(body)
@lib.If(_, then_body, else_body) => {
count = count + run_hot_pipeline_raw_count_call_like(then_body)
match else_body {
Some(body) =>
count = count + run_hot_pipeline_raw_count_call_like(body)
None => ()
}
}
_ => ()
}
}
count
}
///|
priv struct RawScratchEscapes {
canonical : @bitset.BitSet
conditional : @bitset.BitSet
region_ends : Array[Int32]
region_starts : Array[Int32]
param_count : Int32
mut next_region : Int32
}
///|
fn RawScratchEscapes::new(
local_count : Int32,
param_count : Int32,
region_ends : Array[Int32],
region_starts : Array[Int32],
instr_count : Int32,
) -> RawScratchEscapes {
{
canonical: @bitset.BitSet::new(local_count),
conditional: @bitset.BitSet::new(
if param_count == 0 {
0
} else {
instr_count
},
),
region_ends,
region_starts,
param_count,
next_region: 0,
}
}
///|
priv struct RawScratchEscapeScan {
plan : RawScratchEscapes
pending_starts : Array[Int32]
pending_ends : Array[Int32]
conditional_starts : Array[Int32]
conditional_ends : Array[Int32]
param_epochs : Array[Int32]
last_param_writes : @bitset.BitSet
candidate_heads : Array[Int32]
candidate_positions : Array[Int32]
candidate_next : Array[Int32]
mut epoch : Int32
}
///|
fn RawScratchEscapeScan::new(plan : RawScratchEscapes) -> RawScratchEscapeScan {
let locals = plan.canonical.length()
let params = plan.param_count
{
plan,
pending_starts: Array::make(locals, 0),
pending_ends: Array::make(locals, plan.region_ends[0]),
conditional_starts: Array::make(params, 0),
conditional_ends: Array::make(params, plan.region_ends[0]),
param_epochs: Array::make(params, 0),
last_param_writes: @bitset.BitSet::new(plan.conditional.length()),
candidate_heads: Array::make(params, -1),
candidate_positions: [],
candidate_next: [],
epoch: 0,
}
}
///|
fn run_hot_pipeline_raw_collect_scratch_regions(
instrs : Array[@lib.Instruction],
region_ends : Array[Int32],
region_starts : Array[Int32],
position : Int32,
) -> Int32 {
let region = region_ends.length()
region_ends.push(region)
// Reserve a contiguous instruction row for this region before its children.
region_starts.push(position)
let mut next_position = position + instrs.length()
for instr in instrs {
match instr {
@lib.Block(_, @lib.Expr(body))
| @lib.Loop(_, @lib.Expr(body))
| @lib.TryTable(_, _, @lib.Expr(body)) =>
next_position = run_hot_pipeline_raw_collect_scratch_regions(
body, region_ends, region_starts, next_position,
)
@lib.If(_, then_body, else_body) => {
let branch_region = region_ends.length()
next_position = run_hot_pipeline_raw_collect_scratch_regions(
then_body, region_ends, region_starts, next_position,
)
if else_body is Some(body) {
next_position = run_hot_pipeline_raw_collect_scratch_regions(
body, region_ends, region_starts, next_position,
)
}
// Both arms discard their aliases at the same continuation. Their
// writes cannot reach reads in the other arm.
region_ends[branch_region] = region_ends.length() - 1
}
@lib.Try(_, @lib.Expr(body), catches, _) => {
next_position = run_hot_pipeline_raw_collect_scratch_regions(
body, region_ends, region_starts, next_position,
)
for arm in catches {
let @lib.Expr(body) = match arm {
@lib.LegacyCatch(_, body) | @lib.LegacyCatchAll(body) => body
}
next_position = run_hot_pipeline_raw_collect_scratch_regions(
body, region_ends, region_starts, next_position,
)
}
}
_ => ()
}
}
region_ends[region] = region_ends.length() - 1
next_position
}
///|
fn run_hot_pipeline_raw_scratch_write_barrier(instr : @lib.Instruction) -> Bool {
if run_hot_pipeline_raw_simplify_locals_instr_is_early_terminator(instr) {
return true
}
match instr {
@lib.Block(_, _)
| @lib.Loop(_, _)
| @lib.If(_, _, _)
| @lib.Try(_, _, _, _)
| @lib.TryTable(_, _, _)
| @lib.Call(_)
| @lib.CallIndirect(_, _)
| @lib.CallRef(_)
| @lib.Rethrow(_)
| @lib.BrOnNull(_)
| @lib.BrOnNonNull(_)
| @lib.BrOnCast(_, _, _, _)
| @lib.BrOnCastFail(_, _, _, _)
| @lib.BrOnCastDescEq(_, _, _)
| @lib.BrOnCastDescEqFail(_, _, _)
| @lib.Suspend(_)
| @lib.Resume(_, _)
| @lib.ResumeThrow(_, _, _)
| @lib.ResumeThrowRef(_, _)
| @lib.StackSwitch(_, _) => true
_ => false
}
}
///|
fn run_hot_pipeline_raw_mark_scratch_escapes(
instrs : Array[@lib.Instruction],
scan : RawScratchEscapeScan,
region : Int32,
alias_region : Int32,
param_alias_region : Int32,
legacy : Bool,
) -> Int32 {
let plan = scan.plan
let param_count = plan.param_count
if param_count > 0 {
// A later canonical write in the same straight-line segment kills an
// earlier parameter alias. Only the final write needs the If escape guard.
// Calls and every possible control exit split segments. One epoch array
// and one instruction bitset avoid per-arm rows and repeated lookahead.
scan.epoch += 1
for i = instrs.length() - 1; i >= 0; i = i - 1 {
match instrs[i] {
@lib.LocalSet(@lib.LocalIdx(raw)) | @lib.LocalTee(@lib.LocalIdx(raw)) => {
let id = raw.reinterpret_as_int()
if id >= 0 && id < param_count && scan.param_epochs[id] != scan.epoch {
scan.last_param_writes[plan.region_starts[region] + i] = true
scan.param_epochs[id] = scan.epoch
}
}
_ =>
if run_hot_pipeline_raw_scratch_write_barrier(instrs[i]) {
scan.epoch += 1
}
}
}
}
let mut next_region = region + 1
for instr_index = 0
instr_index < instrs.length()
instr_index = instr_index + 1 {
let instr = instrs[instr_index]
match instr {
@lib.LocalSet(@lib.LocalIdx(raw)) | @lib.LocalTee(@lib.LocalIdx(raw)) => {
let id = raw.reinterpret_as_int()
if id >= 0 && id < plan.canonical.length() {
if legacy {
// The raw freshener retains legacy bodies unchanged.
plan.canonical[id] = true
}
if region == 0 {
scan.pending_starts[id] = 0
scan.pending_ends[id] = plan.region_ends[0]
if id < param_count {
scan.conditional_starts[id] = 0
scan.conditional_ends[id] = plan.region_ends[0]
scan.candidate_heads[id] = -1
}
} else {
let boundary = if id < param_count {
param_alias_region
} else {
alias_region
}
if boundary > 0 {
scan.pending_starts[id] = Int32::max(
scan.pending_starts[id],
boundary,
)
scan.pending_ends[id] = Int32::min(
scan.pending_ends[id],
plan.region_ends[boundary],
)
}
if id < param_count {
scan.conditional_starts[id] = Int32::max(
scan.conditional_starts[id],
alias_region,
)
scan.conditional_ends[id] = Int32::min(
scan.conditional_ends[id],
plan.region_ends[alias_region],
)
let position = plan.region_starts[region] + instr_index
if !legacy && scan.last_param_writes[position] {
let row = scan.candidate_positions.length()
scan.candidate_positions.push(position)
scan.candidate_next.push(scan.candidate_heads[id])
scan.candidate_heads[id] = row
}
}
}
}
}
@lib.LocalGet(@lib.LocalIdx(raw)) => {
let id = raw.reinterpret_as_int()
if id >= 0 &&
id < plan.canonical.length() &&
(
legacy ||
region < scan.pending_starts[id] ||
region > scan.pending_ends[id]
) {
plan.canonical[id] = true
}
if id >= 0 &&
id < param_count &&
(
region < scan.conditional_starts[id] ||
region > scan.conditional_ends[id]
) {
// Each candidate is marked once, then removed from this pending
// chain. Later independent If arms retain their freshening.
let mut row = scan.candidate_heads[id]
while row >= 0 {
plan.conditional[scan.candidate_positions[row]] = true
row = scan.candidate_next[row]
}
scan.candidate_heads[id] = -1
scan.conditional_starts[id] = 0
scan.conditional_ends[id] = plan.region_ends[0]
}
}
@lib.Block(_, @lib.Expr(body))
| @lib.Loop(_, @lib.Expr(body))
| @lib.TryTable(_, _, @lib.Expr(body)) =>
next_region = run_hot_pipeline_raw_mark_scratch_escapes(
body, scan, next_region, next_region, next_region, legacy,
)
@lib.If(_, then_body, else_body) => {
let branch_region = next_region
next_region = run_hot_pipeline_raw_mark_scratch_escapes(
then_body, scan, next_region, branch_region, param_alias_region, legacy,
)
if else_body is Some(body) {
next_region = run_hot_pipeline_raw_mark_scratch_escapes(
body, scan, next_region, branch_region, param_alias_region, legacy,
)
}
}
@lib.Try(_, @lib.Expr(body), catches, _) => {
next_region = run_hot_pipeline_raw_mark_scratch_escapes(
body, scan, next_region, next_region, next_region, true,
)
for arm in catches {
let @lib.Expr(body) = match arm {
@lib.LegacyCatch(_, body) | @lib.LegacyCatchAll(body) => body
}
next_region = run_hot_pipeline_raw_mark_scratch_escapes(
body, scan, next_region, next_region, next_region, true,
)
}
}
_ => ()
}
}
next_region
}
///|
fn run_hot_pipeline_raw_escaping_scratch_locals(
instrs : Array[@lib.Instruction],
param_count : Int32,
local_count : Int32,
) -> RawScratchEscapes {
let ends : Array[Int32] = []
let starts : Array[Int32] = []
let count = run_hot_pipeline_raw_collect_scratch_regions(
instrs, ends, starts, 0,
)
let plan = RawScratchEscapes::new(
local_count, param_count, ends, starts, count,
)
// The scan scratch is released before rewriting. No per-write objects,
// ancestor copies, or local-by-region matrix are retained.
ignore(
run_hot_pipeline_raw_mark_scratch_escapes(
instrs,
RawScratchEscapeScan::new(plan),
0,
0,
0,
false,
),
)
plan
}
///|
fn run_hot_pipeline_raw_freshen_param_scratch_writes_with_aliases(
instrs : Array[@lib.Instruction],
root_instrs : Array[@lib.Instruction],
params : Array[@lib.ValType],
locals : @lib.Locals,
extra_local_types : Array[@lib.ValType],
param_aliases : Array[@lib.LocalIdx?],
inherited_no_else_branch_merge_locals : @bitset.BitSet,
escaping_scratch_locals~ : RawScratchEscapes? = None,
in_if~ : Bool = false,
) -> Array[@lib.Instruction] {
let rewritten : Array[@lib.Instruction] = []
let freshen_params = run_hot_pipeline_raw_instrs_have_memory_access(instrs)
let freshen_repeated_non_loop_sets = run_hot_pipeline_raw_instrs_have_table_update(
instrs,
)
let has_table_grow = run_hot_pipeline_raw_instrs_have_table_grow(root_instrs)
// Binaryen's two-call allocator unlink helpers use a coupled allocation
// schedule: freshen the top-level `block + 4` address tee, the value-if
// result write, and selected value-if carriers, but keep most nested
// unlink-prefix address tees / body scratch writes on canonical lanes. Keep
// this gate narrow so unrelated call-bearing helpers do not lose their
// repeated body-local scratch freshening.
let two_call_allocator_body = run_hot_pipeline_raw_count_call_like(
root_instrs,
) ==
2
let two_call_root_body = two_call_allocator_body &&
instrs.length() == root_instrs.length()
let freshen_repeated_body_scratch = !two_call_allocator_body
let base_local_count = params.length() + locals.length()
// Keep nested definitions canonical when a read leaves their subtree.
// Other scratch lanes retain the existing freshening schedule. The two
// linear region walks share compact write flags across recursion.
let plan = match escaping_scratch_locals {
Some(bits) => bits
None =>
run_hot_pipeline_raw_escaping_scratch_locals(
root_instrs,
params.length(),
base_local_count + extra_local_types.length(),
)
}
let escaping = plan.canonical
// The input limit stays fixed while rewriting appends new scratch types.
let known_local_count = escaping.length()
let region = plan.next_region
plan.next_region += 1
let seen_local_writes : Array[Bool] = Array::make(base_local_count, false)
let no_else_branch_merge_locals = run_hot_pipeline_raw_no_else_branch_merge_locals(
instrs,
params.length(),
known_local_count,
)
let loop_carried_merge_locals = run_hot_pipeline_raw_loop_carried_merge_locals(
instrs, known_local_count,
)
for local_id = 0; local_id < known_local_count; local_id = local_id + 1 {
if loop_carried_merge_locals[local_id] {
no_else_branch_merge_locals[local_id] = true
}
if local_id < inherited_no_else_branch_merge_locals.length() &&
inherited_no_else_branch_merge_locals[local_id] {
no_else_branch_merge_locals[local_id] = true
}
}
let mut previous_was_local_tee = false
let mut previous_was_call = false
let mut previous_was_table_size = false
let mut previous_was_value_if = false
let mut previous_was_memory_load = false
let mut previous_was_i32_add = false
let mut previous_was_i32_and = false
let mut previous_was_i32_sub = false
let mut previous_was_select = false
let mut previous_local_get_id = -1
for instr_index = 0
instr_index < instrs.length()
instr_index = instr_index + 1 {
let instr = instrs[instr_index]
match instr {
@lib.LocalGet(@lib.LocalIdx(idx)) => {
let local_id = idx.reinterpret_as_int()
previous_local_get_id = local_id
if local_id >= 0 && local_id < param_aliases.length() {
match param_aliases[local_id] {
Some(mapped_local) =>
rewritten.push(@lib.Instruction::local_get(mapped_local))
None => rewritten.push(instr)
}
} else {
rewritten.push(instr)
}
previous_was_local_tee = false
previous_was_call = false
previous_was_table_size = false
previous_was_value_if = false
previous_was_memory_load = false
previous_was_i32_add = false
previous_was_i32_and = false
previous_was_i32_sub = false
}
@lib.LocalSet(@lib.LocalIdx(idx)) => {
let local_id = idx.reinterpret_as_int()
let preserve_first_two_call_unlink_load_set = two_call_allocator_body &&
instrs.length() != root_instrs.length() &&
previous_was_memory_load &&
local_id == base_local_count - 2 &&
local_id >= 0 &&
run_hot_pipeline_raw_count_local_writes(root_instrs, local_id) > 1
let value_if_suffix_sub_set = two_call_allocator_body &&
instrs.length() != root_instrs.length() &&
previous_was_i32_sub &&
local_id == base_local_count - 3 &&
run_hot_pipeline_raw_count_local_writes(root_instrs, local_id) > 1
let preserve_nested_param_merge_set = instrs.length() !=
root_instrs.length() &&
local_id >= 0 &&
local_id < params.length() &&
(
previous_local_get_id >= params.length() ||
(
previous_was_i32_add &&
run_hot_pipeline_raw_count_local_gets(root_instrs, local_id) >
run_hot_pipeline_raw_count_local_gets(instrs, local_id)
)
)
let preserve_table_grow_count_value_if_set = two_call_allocator_body &&
has_table_grow &&
previous_was_value_if &&
local_id == base_local_count - 1
let preserve_nested_branch_memory_set = !two_call_allocator_body &&
instrs.length() != root_instrs.length() &&
previous_was_memory_load &&
local_id >= params.length() &&
local_id < base_local_count &&
run_hot_pipeline_raw_count_local_gets(root_instrs, local_id) >
run_hot_pipeline_raw_count_local_gets(instrs, local_id)
let preserve_refcount_memory_load_set = !two_call_allocator_body &&
!has_table_grow &&
!freshen_repeated_non_loop_sets &&
previous_was_memory_load &&
local_id >= params.length() &&
local_id < base_local_count &&
run_hot_pipeline_raw_count_call_like(root_instrs) > 2 &&
run_hot_pipeline_raw_count_local_writes(root_instrs, local_id) == 1
let preserve_refcount_branch_result_set = !two_call_allocator_body &&
!has_table_grow &&
!freshen_repeated_non_loop_sets &&
instrs.length() != root_instrs.length() &&
previous_was_local_tee &&
local_id >= params.length() &&
local_id < base_local_count &&
run_hot_pipeline_raw_count_call_like(root_instrs) > 2 &&
run_hot_pipeline_raw_instrs_have_branch(instrs)
if (
freshen_params ||
freshen_repeated_non_loop_sets ||
value_if_suffix_sub_set
) &&
local_id >= 0 &&
!(local_id < escaping.length() && escaping[local_id]) &&
!(in_if &&
local_id < plan.param_count &&
plan.conditional[plan.region_starts[region] + instr_index]) &&
(
local_id >= no_else_branch_merge_locals.length() ||
!no_else_branch_merge_locals[local_id]
) &&
(
(
local_id < params.length() &&
!previous_was_call &&
!preserve_nested_param_merge_set
) ||
(
freshen_repeated_body_scratch &&
previous_was_local_tee &&
!preserve_refcount_branch_result_set
) ||
(
two_call_allocator_body &&
previous_was_value_if &&
!preserve_table_grow_count_value_if_set &&
local_id >= params.length() &&
local_id < base_local_count
) ||
value_if_suffix_sub_set ||
previous_was_table_size ||
(
previous_was_memory_load &&
!preserve_first_two_call_unlink_load_set &&
!preserve_nested_branch_memory_set &&
!preserve_refcount_memory_load_set
) ||
(
freshen_repeated_non_loop_sets &&
local_id < base_local_count &&
run_hot_pipeline_raw_count_non_loop_local_writes(instrs, local_id) >
1
)
) {
let fresh_local = run_hot_pipeline_raw_fresh_local(
params, locals, local_id, extra_local_types,
)
run_hot_pipeline_raw_set_param_alias(
param_aliases,
local_id,
Some(fresh_local),
)
rewritten.push(@lib.Instruction::local_set(fresh_local))
} else {
run_hot_pipeline_raw_set_param_alias(param_aliases, local_id, None)
rewritten.push(instr)
}
if local_id >= 0 && local_id < seen_local_writes.length() {
seen_local_writes[local_id] = true
}
previous_was_local_tee = false
previous_was_call = false
previous_was_table_size = false
previous_was_value_if = false
previous_was_memory_load = false
previous_was_i32_add = false
previous_was_i32_and = false
previous_was_i32_sub = false
previous_local_get_id = -1
}
@lib.LocalTee(@lib.LocalIdx(idx)) => {
let local_id = idx.reinterpret_as_int()
let repeated_top_level_load_tee = two_call_root_body &&
previous_was_memory_load &&
local_id >= params.length() &&
local_id < base_local_count &&
local_id < seen_local_writes.length() &&
seen_local_writes[local_id] &&
run_hot_pipeline_raw_count_local_writes(root_instrs, local_id) > 1
let repeated_top_level_add_tee = two_call_root_body &&
previous_was_i32_add &&
local_id == base_local_count - 1 &&
run_hot_pipeline_raw_count_local_writes(root_instrs, local_id) > 1
let preserve_nested_two_call_unlink_load_tee = two_call_allocator_body &&
instrs.length() != root_instrs.length() &&
previous_was_memory_load &&
local_id >= 0 &&
local_id < params.length()
let value_if_prefix_mask_tee = two_call_root_body &&
previous_was_i32_and &&
local_id == params.length() &&
run_hot_pipeline_raw_count_local_writes(root_instrs, local_id) > 1
let preserve_refcount_memory_load_tee = !two_call_allocator_body &&
!has_table_grow &&
!freshen_repeated_non_loop_sets &&
previous_was_memory_load &&
local_id >= params.length() &&
local_id < base_local_count &&
run_hot_pipeline_raw_count_call_like(root_instrs) > 2 &&
run_hot_pipeline_raw_count_local_writes(root_instrs, local_id) == 1
let preserve_param_branch_carrier_tee = local_id < params.length() &&
(
(
run_hot_pipeline_raw_count_call_like(root_instrs) <= 1 &&
run_hot_pipeline_raw_ssa_nomerge_param_tee_is_branch_carrier(
root_instrs,
params.length(),
local_id,
)
) ||
run_hot_pipeline_raw_local_tee_has_plain_br_before_next_write(
instrs, local_id,
)
)
if (freshen_params || (two_call_allocator_body && previous_was_select)) &&
!preserve_param_branch_carrier_tee &&
local_id >= 0 &&
!(local_id < escaping.length() && escaping[local_id]) &&
!(in_if &&
local_id < plan.param_count &&
plan.conditional[plan.region_starts[region] + instr_index]) &&
(
local_id >= no_else_branch_merge_locals.length() ||
!no_else_branch_merge_locals[local_id]
) &&
(
(
local_id < params.length() &&
!previous_was_call &&
!preserve_nested_two_call_unlink_load_tee
) ||
(
local_id >= params.length() &&
local_id < base_local_count &&
(
previous_was_value_if ||
(two_call_allocator_body && previous_was_select) ||
value_if_prefix_mask_tee ||
repeated_top_level_add_tee ||
(
previous_was_memory_load &&
!repeated_top_level_load_tee &&
!preserve_refcount_memory_load_tee
) ||
(
freshen_repeated_body_scratch &&
run_hot_pipeline_raw_count_local_writes(root_instrs, local_id) >
1
)
)
)
) {
let fresh_local = run_hot_pipeline_raw_fresh_local(
params, locals, local_id, extra_local_types,
)
run_hot_pipeline_raw_set_param_alias(
param_aliases,
local_id,
Some(fresh_local),
)
rewritten.push(@lib.Instruction::local_tee(fresh_local))
} else {
run_hot_pipeline_raw_set_param_alias(param_aliases, local_id, None)
rewritten.push(instr)
}
if local_id >= 0 && local_id < seen_local_writes.length() {
seen_local_writes[local_id] = true
}
previous_was_local_tee = true
previous_was_call = false
previous_was_table_size = false
previous_was_value_if = false
previous_was_memory_load = false
previous_was_i32_add = false
previous_was_i32_and = false
previous_was_i32_sub = false
previous_was_select = false
}
@lib.GlobalGet(_) => {
previous_was_local_tee = false
previous_was_call = false
previous_was_table_size = false
previous_was_value_if = false
previous_was_memory_load = false
previous_was_i32_add = false
previous_was_i32_and = false
previous_was_i32_sub = false
previous_was_select = false
previous_local_get_id = -1
rewritten.push(instr)
}
@lib.Call(_) | @lib.CallIndirect(_, _) | @lib.CallRef(_) => {
previous_was_local_tee = false
previous_was_call = true
previous_was_table_size = false
previous_was_value_if = false
previous_was_memory_load = false
previous_was_i32_add = false
previous_was_i32_and = false
previous_was_i32_sub = false
previous_was_select = false
previous_local_get_id = -1
rewritten.push(instr)
}
@lib.TableSize(_) => {
previous_was_local_tee = false
previous_was_call = false
previous_was_table_size = true
previous_was_memory_load = false
previous_was_i32_add = false
previous_was_i32_and = false
previous_was_i32_sub = false
previous_was_select = false
previous_local_get_id = -1
rewritten.push(instr)
}
@lib.Block(block_type, @lib.Expr(body)) => {
previous_was_local_tee = false
previous_was_call = false
previous_was_table_size = false
previous_was_value_if = false
previous_was_memory_load = false
previous_was_i32_add = false
previous_was_i32_and = false
previous_was_i32_sub = false
previous_was_select = false
previous_local_get_id = -1
rewritten.push(
@lib.Instruction::block(
block_type,
@lib.Expr::new(
run_hot_pipeline_raw_freshen_param_scratch_writes_with_aliases(
body,
root_instrs,
params,
locals,
extra_local_types,
param_aliases.copy(),
no_else_branch_merge_locals,
escaping_scratch_locals=Some(plan),
in_if~,
),
),
),
)
}
@lib.Loop(block_type, @lib.Expr(body)) => {
previous_was_local_tee = false
previous_was_call = false
previous_was_table_size = false
previous_was_value_if = false
previous_was_memory_load = false
previous_was_i32_add = false
previous_was_i32_and = false
previous_was_i32_sub = false
previous_was_select = false
previous_local_get_id = -1
let loop_merge_locals = no_else_branch_merge_locals.copy()
rewritten.push(
@lib.Instruction::loop_(
block_type,
@lib.Expr::new(
run_hot_pipeline_raw_freshen_param_scratch_writes_with_aliases(
body,
root_instrs,
params,
locals,
extra_local_types,
param_aliases.copy(),
loop_merge_locals,
escaping_scratch_locals=Some(plan),
in_if~,
),
),
),
)
}
@lib.TryTable(block_type, catches, @lib.Expr(body)) => {
previous_was_local_tee = false
previous_was_call = false
previous_was_table_size = false
previous_was_value_if = false
previous_was_memory_load = false
previous_was_i32_add = false
previous_was_i32_and = false
previous_was_i32_sub = false
previous_was_select = false
previous_local_get_id = -1
rewritten.push(
@lib.Instruction::try_table(
block_type,
catches,
@lib.Expr::new(
run_hot_pipeline_raw_freshen_param_scratch_writes_with_aliases(
body,
root_instrs,
params,
locals,
extra_local_types,
param_aliases.copy(),
no_else_branch_merge_locals,
escaping_scratch_locals=Some(plan),
in_if~,
),
),
),
)
}
@lib.If(block_type, then_body, else_body) => {
previous_was_local_tee = false
previous_was_call = false
previous_was_table_size = false
previous_was_value_if = block_type != @lib.BlockType::void_()
previous_was_memory_load = false
previous_was_i32_add = false
previous_was_i32_and = false
previous_was_i32_sub = false
previous_was_select = false
previous_local_get_id = -1
let branch_aliases = param_aliases.copy()
let branch_writes = @bitset.BitSet::new(known_local_count)
run_hot_pipeline_raw_mark_instrs_local_writes(then_body, branch_writes)
if else_body is Some(body) {
run_hot_pipeline_raw_mark_instrs_local_writes(body, branch_writes)
}
for local_id = 0; local_id < params.length(); local_id = local_id + 1 {
if branch_writes[local_id] {
if local_id >= 0 && local_id < branch_aliases.length() {
match branch_aliases[local_id] {
Some(mapped_local) => {
rewritten.push(@lib.Instruction::local_get(mapped_local))
rewritten.push(
@lib.Instruction::local_set(
@lib.LocalIdx::new(local_id.reinterpret_as_uint()),
),
)
branch_aliases[local_id] = None
}
None => ()
}
}
}
}
rewritten.push(
@lib.Instruction::if_(
block_type,
run_hot_pipeline_raw_freshen_param_scratch_writes_with_aliases(
then_body,
root_instrs,
params,
locals,
extra_local_types,
branch_aliases.copy(),
no_else_branch_merge_locals,
escaping_scratch_locals=Some(plan),
in_if=true,
),
match else_body {
Some(body) =>
Some(
run_hot_pipeline_raw_freshen_param_scratch_writes_with_aliases(
body,
root_instrs,
params,
locals,
extra_local_types,
branch_aliases.copy(),
no_else_branch_merge_locals,
escaping_scratch_locals=Some(plan),
in_if=true,
),
)
None => None
},
),
)
if two_call_root_body && block_type != @lib.BlockType::void_() {
run_hot_pipeline_raw_set_param_alias(
param_aliases,
params.length(),
None,
)
}
for local_id = 0; local_id < known_local_count; local_id = local_id + 1 {
if branch_writes[local_id] {
run_hot_pipeline_raw_set_param_alias(param_aliases, local_id, None)
}
}
}
_ => {
if instr is @lib.Try(_, _, catches, _) {
// Legacy bodies are retained, but their region rows must be consumed
// so following rewritten siblings keep their instruction positions.
for _ in 0..<(catches.length() + 1) {
plan.next_region = plan.region_ends[plan.next_region] + 1
}
}
previous_was_local_tee = false
previous_was_call = false
previous_was_table_size = false
previous_was_value_if = false
previous_was_memory_load = run_hot_pipeline_optimize_instructions_instr_is_plain_load(
instr,
)
match instr {
@lib.I32Add => previous_was_i32_add = true
_ => previous_was_i32_add = false
}
match instr {
@lib.I32And => previous_was_i32_and = true
_ => previous_was_i32_and = false
}
match instr {
@lib.I32Sub => previous_was_i32_sub = true
_ => previous_was_i32_sub = false
}
match instr {
@lib.Select(_) => previous_was_select = true
_ => previous_was_select = false
}
previous_local_get_id = -1
rewritten.push(instr)
}
}
}
rewritten
}
///|
fn run_hot_pipeline_raw_freshen_param_scratch_writes(
instrs : Array[@lib.Instruction],
params : Array[@lib.ValType],
locals : @lib.Locals,
extra_local_types : Array[@lib.ValType],
) -> Array[@lib.Instruction] {
let known_local_count = params.length() +
locals.length() +
extra_local_types.length()
let param_aliases : Array[@lib.LocalIdx?] = Array::make(
params.length() + locals.length(),
None,
)
run_hot_pipeline_raw_freshen_param_scratch_writes_with_aliases(
instrs,
instrs,
params,
locals,
extra_local_types,
param_aliases,
@bitset.BitSet::new(known_local_count),
)
}
///|
fn run_hot_pipeline_raw_freshen_gc_free_branch_heavy_scratch_with_aliases(
instrs : Array[@lib.Instruction],
params : Array[@lib.ValType],
locals : @lib.Locals,
extra_local_types : Array[@lib.ValType],
local_aliases : Array[@lib.LocalIdx?],
seen_local_writes : Array[Bool],
) -> Array[@lib.Instruction] {
let rewritten : Array[@lib.Instruction] = []
let base_local_count = params.length() + locals.length()
let mut previous_was_i32_add = false
let mut previous_was_i32_and = false
let mut previous_was_memory_load = false
for instr in instrs {
match instr {
@lib.LocalGet(@lib.LocalIdx(idx)) => {
let local_id = idx.reinterpret_as_int()
if local_id >= 0 && local_id < local_aliases.length() {
match local_aliases[local_id] {
Some(mapped_local) =>
rewritten.push(@lib.Instruction::local_get(mapped_local))
None => rewritten.push(instr)
}
} else {
rewritten.push(instr)
}
previous_was_i32_add = false
previous_was_i32_and = false
previous_was_memory_load = false
}
@lib.LocalSet(@lib.LocalIdx(idx)) => {
let local_id = idx.reinterpret_as_int()
let freshen_initial_vt_ptr_addr = previous_was_i32_add &&
local_id == base_local_count - 10 &&
local_id >= 0 &&
local_id < seen_local_writes.length() &&
!seen_local_writes[local_id]
let freshen_ref_array_len_else_lane = previous_was_i32_and &&
local_id == base_local_count - 2 &&
local_id >= 0 &&
local_id < seen_local_writes.length() &&
seen_local_writes[local_id]
let freshen_post_free_child_index_reload = previous_was_memory_load &&
local_id == params.length() + 1 &&
run_hot_pipeline_raw_count_call_like(instrs) > 0
if freshen_initial_vt_ptr_addr ||
freshen_ref_array_len_else_lane ||
freshen_post_free_child_index_reload {
let fresh_local = run_hot_pipeline_raw_fresh_local(
params, locals, local_id, extra_local_types,
)
run_hot_pipeline_raw_set_param_alias(
local_aliases,
local_id,
Some(fresh_local),
)
rewritten.push(@lib.Instruction::local_set(fresh_local))
} else {
run_hot_pipeline_raw_set_param_alias(local_aliases, local_id, None)
rewritten.push(instr)
}
if local_id >= 0 && local_id < seen_local_writes.length() {
seen_local_writes[local_id] = true
}
previous_was_i32_add = false
previous_was_i32_and = false
previous_was_memory_load = false
}
@lib.LocalTee(@lib.LocalIdx(idx)) => {
let local_id = idx.reinterpret_as_int()
run_hot_pipeline_raw_set_param_alias(local_aliases, local_id, None)
if local_id >= 0 && local_id < seen_local_writes.length() {
seen_local_writes[local_id] = true
}
rewritten.push(instr)
previous_was_i32_add = false
previous_was_i32_and = false
previous_was_memory_load = false
}
@lib.Block(block_type, @lib.Expr(body)) => {
rewritten.push(
@lib.Instruction::block(
block_type,
@lib.Expr::new(
run_hot_pipeline_raw_freshen_gc_free_branch_heavy_scratch_with_aliases(
body,
params,
locals,
extra_local_types,
local_aliases.copy(),
seen_local_writes,
),
),
),
)
previous_was_i32_add = false
previous_was_i32_and = false
previous_was_memory_load = false
}
@lib.Loop(block_type, @lib.Expr(body)) => {
rewritten.push(
@lib.Instruction::loop_(
block_type,
@lib.Expr::new(
run_hot_pipeline_raw_freshen_gc_free_branch_heavy_scratch_with_aliases(
body,
params,
locals,
extra_local_types,
local_aliases.copy(),
seen_local_writes,
),
),
),
)
previous_was_i32_add = false
previous_was_i32_and = false
previous_was_memory_load = false
}
@lib.TryTable(block_type, catches, @lib.Expr(body)) => {
rewritten.push(
@lib.Instruction::try_table(
block_type,
catches,
@lib.Expr::new(
run_hot_pipeline_raw_freshen_gc_free_branch_heavy_scratch_with_aliases(
body,
params,
locals,
extra_local_types,
local_aliases.copy(),
seen_local_writes,
),
),
),
)
previous_was_i32_add = false
previous_was_i32_and = false
previous_was_memory_load = false
}
@lib.If(block_type, then_body, else_body) => {
rewritten.push(
@lib.Instruction::if_(
block_type,
run_hot_pipeline_raw_freshen_gc_free_branch_heavy_scratch_with_aliases(
then_body,
params,
locals,
extra_local_types,
local_aliases.copy(),
seen_local_writes,
),
match else_body {
Some(body) =>
Some(
run_hot_pipeline_raw_freshen_gc_free_branch_heavy_scratch_with_aliases(
body,
params,
locals,
extra_local_types,
local_aliases.copy(),
seen_local_writes,
),
)
None => None
},
),
)
previous_was_i32_add = false
previous_was_i32_and = false
previous_was_memory_load = false
}
_ => {
previous_was_memory_load = run_hot_pipeline_optimize_instructions_instr_is_plain_load(
instr,
)
match instr {
@lib.I32Add => previous_was_i32_add = true
_ => previous_was_i32_add = false
}
match instr {
@lib.I32And => previous_was_i32_and = true
_ => previous_was_i32_and = false
}
rewritten.push(instr)
}
}
}
rewritten
}
///|
fn run_hot_pipeline_raw_freshen_gc_free_branch_heavy_scratch(
func : @lib.Func,
params : Array[@lib.ValType],
) -> @lib.Func? {
let @lib.Func(locals, @lib.Expr(instrs)) = func
if params.length() != 1 || locals.length() != 17 {
return None
}
let extra_local_types : Array[@lib.ValType] = []
let local_aliases : Array[@lib.LocalIdx?] = Array::make(
params.length() + locals.length(),
None,
)
let seen_local_writes : Array[Bool] = Array::make(
params.length() + locals.length(),
false,
)
let rewritten_instrs = run_hot_pipeline_raw_freshen_gc_free_branch_heavy_scratch_with_aliases(
instrs, params, locals, extra_local_types, local_aliases, seen_local_writes,
)
if extra_local_types.length() == 0 {
None
} else {
let rewritten_locals = locals.copy()
rewritten_locals.append_types(extra_local_types)
Some(@lib.Func::new(rewritten_locals, @lib.Expr::new(rewritten_instrs)))
}
}
///|
fn run_hot_pipeline_raw_plain_load_result_type(
instr : @lib.Instruction,
) -> @lib.ValType? {
match instr {
@lib.I32Load(_)
| @lib.I32Load8S(_)
| @lib.I32Load8U(_)
| @lib.I32Load16S(_)
| @lib.I32Load16U(_)
| @lib.I32AtomicLoad(_, _)
| @lib.I32AtomicLoad8U(_, _)
| @lib.I32AtomicLoad16U(_, _) => Some(@lib.ValType::i32())
@lib.I64Load(_)
| @lib.I64Load8S(_)
| @lib.I64Load8U(_)
| @lib.I64Load16S(_)
| @lib.I64Load16U(_)
| @lib.I64Load32S(_)
| @lib.I64Load32U(_)
| @lib.I64AtomicLoad(_, _)
| @lib.I64AtomicLoad8U(_, _)
| @lib.I64AtomicLoad16U(_, _)
| @lib.I64AtomicLoad32U(_, _) => Some(@lib.ValType::i64())
@lib.F32Load(_) => Some(@lib.ValType::f32())
@lib.F64Load(_) => Some(@lib.ValType::f64())
_ => None
}
}
///|
fn run_hot_pipeline_raw_spill_call_result_across_following_call(
instrs : Array[@lib.Instruction],
module_ctx : @ir.HotModuleContext,
params : Array[@lib.ValType],
locals : @lib.Locals,
extra_local_types : Array[@lib.ValType],
) -> Array[@lib.Instruction] {
if params.length() != 1 {
return instrs
}
let base_local_count = params.length() + locals.length()
let rewritten : Array[@lib.Instruction] = []
let mut idx = 0
while idx < instrs.length() {
if idx + 3 < instrs.length() {
match (instrs[idx], instrs[idx + 1], instrs[idx + 2], instrs[idx + 3]) {
(
@lib.Call(producer_idx) as producer,
@lib.LocalGet(_),
@lib.Call(_)
| @lib.CallIndirect(_, _)
| @lib.CallRef(_) as follower,
@lib.LocalTee(@lib.LocalIdx(raw)),
) => {
if base_local_count > 20 {
rewritten.push(instrs[idx])
idx = idx + 1
continue
}
let target_id = raw.reinterpret_as_int()
let value_type = match
run_hot_pipeline_raw_rewritten_local_type(
params, locals, extra_local_types, target_id,
) {
Some(type_) => type_
None => {
rewritten.push(instrs[idx])
idx = idx + 1
continue
}
}
let producer_results = run_hot_pipeline_required_func_results(
module_ctx, producer_idx,
)
if producer_results.length() != 1 || producer_results[0] != value_type {
rewritten.push(instrs[idx])
idx = idx + 1
continue
}
let scratch = run_hot_pipeline_raw_fresh_typed_local(
params, locals, value_type, extra_local_types,
)
rewritten.push(producer)
rewritten.push(@lib.Instruction::local_set(scratch))
rewritten.push(instrs[idx + 1])
rewritten.push(follower)
rewritten.push(@lib.Instruction::local_get(scratch))
rewritten.push(instrs[idx + 3])
idx = idx + 4
continue
}
(load, @lib.LocalGet(_), follower, @lib.LocalTee(@lib.LocalIdx(raw))) => {
if base_local_count > 128 ||
!run_hot_pipeline_optimize_instructions_instr_is_plain_load(load) ||
!run_hot_pipeline_optimize_instructions_instr_is_call(follower) {
rewritten.push(instrs[idx])
idx = idx + 1
continue
}
let target_id = raw.reinterpret_as_int()
let value_type = match
run_hot_pipeline_raw_rewritten_local_type(
params, locals, extra_local_types, target_id,
) {
Some(type_) => type_
None => {
rewritten.push(instrs[idx])
idx = idx + 1
continue
}
}
match run_hot_pipeline_raw_plain_load_result_type(load) {
Some(load_type) if load_type == value_type => {
let scratch = run_hot_pipeline_raw_fresh_typed_local(
params, locals, value_type, extra_local_types,
)
rewritten.push(load)
rewritten.push(@lib.Instruction::local_set(scratch))
rewritten.push(instrs[idx + 1])
rewritten.push(follower)
rewritten.push(@lib.Instruction::local_get(scratch))
rewritten.push(instrs[idx + 3])
idx = idx + 4
continue
}
_ => ()
}
}
_ => ()
}
}
match instrs[idx] {
@lib.Block(block_type, @lib.Expr(body)) =>
rewritten.push(
@lib.Instruction::block(
block_type,
@lib.Expr::new(
run_hot_pipeline_raw_spill_call_result_across_following_call(
body, module_ctx, params, locals, extra_local_types,
),
),
),
)
@lib.Loop(block_type, @lib.Expr(body)) =>
rewritten.push(
@lib.Instruction::loop_(
block_type,
@lib.Expr::new(
run_hot_pipeline_raw_spill_call_result_across_following_call(
body, module_ctx, params, locals, extra_local_types,
),
),
),
)
@lib.TryTable(block_type, catches, @lib.Expr(body)) =>
rewritten.push(
@lib.Instruction::try_table(
block_type,
catches,
@lib.Expr::new(
run_hot_pipeline_raw_spill_call_result_across_following_call(
body, module_ctx, params, locals, extra_local_types,
),
),
),
)
@lib.If(block_type, then_body, else_body) =>
rewritten.push(
@lib.Instruction::if_(
block_type,
run_hot_pipeline_raw_spill_call_result_across_following_call(
then_body, module_ctx, params, locals, extra_local_types,
),
match else_body {
Some(body) =>
Some(
run_hot_pipeline_raw_spill_call_result_across_following_call(
body, module_ctx, params, locals, extra_local_types,
),
)
None => None
},
),
)
_ => rewritten.push(instrs[idx])
}
idx = idx + 1
}
rewritten
}
///|
// Binaryen's `ssa-nomerge` allocates fresh temporaries for body-local scratch
// writes that are confined to a value-producing `if` arm immediately consumed by
// a local write. The normal raw alias merger keeps canonical body locals when the
// next read is in the same arm, which is semantically valid but leaves artifact
// parity gaps. This narrow post-pass freshens only body locals whose reads are
// wholly inside that arm. A separate nested-store post-pass freshens parameter
// scratch writes when they feed memory unlink/update code.
fn run_hot_pipeline_raw_freshen_value_if_arm_body_scratch(
arm : Array[@lib.Instruction],
all_instrs : Array[@lib.Instruction],
params : Array[@lib.ValType],
locals : @lib.Locals,
extra_local_types : Array[@lib.ValType],
) -> Array[@lib.Instruction] {
let candidates : Array[Int32] = []
run_hot_pipeline_raw_collect_written_body_locals(
arm,
params.length(),
candidates,
)
let mut rewritten = arm
let base_local_count = params.length() + locals.length()
for local_id in candidates {
if local_id >= base_local_count {
// This post-pass is only meant to freshen original body-local scratch
// writes. The main raw rewrite may already have introduced fresh aliases
// for branch-carried locals; freshening those aliases again inside just
// one value-if arm can leave the outer carrier read on an unwritten old
// fresh local.
continue
}
let arm_gets = run_hot_pipeline_raw_count_local_gets(rewritten, local_id)
if arm_gets == 0 ||
arm_gets != run_hot_pipeline_raw_count_local_gets(all_instrs, local_id) {
continue
}
let fresh_local = run_hot_pipeline_raw_fresh_local(
params, locals, local_id, extra_local_types,
)
if run_hot_pipeline_raw_count_local_writes(rewritten, local_id) > 1 {
rewritten = run_hot_pipeline_raw_rewrite_local_refs_before_last_write(
rewritten, local_id, fresh_local,
)
} else {
rewritten = run_hot_pipeline_raw_rewrite_local_refs(
rewritten, local_id, fresh_local,
)
}
}
rewritten
}
///|
fn run_hot_pipeline_raw_freshen_value_if_body_scratch(
instrs : Array[@lib.Instruction],
params : Array[@lib.ValType],
locals : @lib.Locals,
extra_local_types : Array[@lib.ValType],
) -> Array[@lib.Instruction] {
let rewritten : Array[@lib.Instruction] = []
for idx = 0; idx < instrs.length(); idx = idx + 1 {
match instrs[idx] {
@lib.If(block_type, then_body, Some(else_body)) =>
if block_type != @lib.BlockType::void_() && idx + 1 < instrs.length() {
match instrs[idx + 1] {
@lib.LocalSet(_) | @lib.LocalTee(_) => {
let if_consumed_context : Array[@lib.Instruction] = []
for context_idx = 0
context_idx <= idx + 1
context_idx = context_idx + 1 {
if_consumed_context.push(instrs[context_idx])
}
rewritten.push(
@lib.Instruction::if_(
block_type,
run_hot_pipeline_raw_freshen_value_if_arm_body_scratch(
then_body, if_consumed_context, params, locals, extra_local_types,
),
Some(
run_hot_pipeline_raw_freshen_value_if_arm_body_scratch(
else_body, if_consumed_context, params, locals, extra_local_types,
),
),
),
)
}
_ => rewritten.push(instrs[idx])
}
} else {
rewritten.push(instrs[idx])
}
_ => rewritten.push(instrs[idx])
}
}
rewritten
}
///|
fn run_hot_pipeline_raw_has_unwritten_appended_local_get_collect(
instrs : Array[@lib.Instruction],
writes : @bitset.BitSet,
) -> Bool {
for instr in instrs {
match instr {
@lib.LocalSet(@lib.LocalIdx(raw)) | @lib.LocalTee(@lib.LocalIdx(raw)) => {
let id = raw.reinterpret_as_int()
if id >= writes.length() {
writes.grow(id + 1)
}
writes[id] = true
}
@lib.LocalGet(@lib.LocalIdx(raw)) => {
let id = raw.reinterpret_as_int()
if id >= writes.length() || !writes[id] {
return true
}
}
@lib.Block(_, @lib.Expr(body))
| @lib.Loop(_, @lib.Expr(body))
| @lib.TryTable(_, _, @lib.Expr(body)) =>
if run_hot_pipeline_raw_has_unwritten_appended_local_get_collect(
body, writes,
) {
return true
}
@lib.If(_, then_body, else_body) => {
if run_hot_pipeline_raw_has_unwritten_appended_local_get_collect(
then_body, writes,
) {
return true
}
match else_body {
Some(body) =>
if run_hot_pipeline_raw_has_unwritten_appended_local_get_collect(
body, writes,
) {
return true
}
None => ()
}
}
_ => ()
}
}
false
}
///|
fn run_hot_pipeline_raw_has_unwritten_appended_local_get(
instrs : Array[@lib.Instruction],
base_local_count : Int32,
) -> Bool {
let writes = @bitset.BitSet::new(base_local_count)
for local_id = 0; local_id < base_local_count; local_id = local_id + 1 {
writes[local_id] = true
}
run_hot_pipeline_raw_has_unwritten_appended_local_get_collect(instrs, writes)
}
///|
fn run_hot_pipeline_raw_collect_base_local_traffic(
instrs : Array[@lib.Instruction],
base_local_count : Int32,
writes : @bitset.BitSet,
gets : @bitset.BitSet,
) -> Unit {
for instr in instrs {
match instr {
@lib.LocalSet(@lib.LocalIdx(raw)) | @lib.LocalTee(@lib.LocalIdx(raw)) => {
let id = raw.reinterpret_as_int()
if id < base_local_count {
writes[id] = true
}
}
@lib.LocalGet(@lib.LocalIdx(raw)) => {
let id = raw.reinterpret_as_int()
if id < base_local_count {
gets[id] = true
}
}
@lib.Block(_, @lib.Expr(body))
| @lib.Loop(_, @lib.Expr(body))
| @lib.TryTable(_, _, @lib.Expr(body)) =>
run_hot_pipeline_raw_collect_base_local_traffic(
body, base_local_count, writes, gets,
)
@lib.If(_, then_body, else_body) => {
run_hot_pipeline_raw_collect_base_local_traffic(
then_body, base_local_count, writes, gets,
)
match else_body {
Some(body) =>
run_hot_pipeline_raw_collect_base_local_traffic(
body, base_local_count, writes, gets,
)
None => ()
}
}
_ => ()
}
}
}
///|
fn run_hot_pipeline_raw_has_plain_br(instrs : Array[@lib.Instruction]) -> Bool {
for instr in instrs {
match instr {
@lib.Br(_) => return true
@lib.Block(_, @lib.Expr(body))
| @lib.Loop(_, @lib.Expr(body))
| @lib.TryTable(_, _, @lib.Expr(body)) =>
if run_hot_pipeline_raw_has_plain_br(body) {
return true
}
@lib.If(_, then_body, else_body) => {
if run_hot_pipeline_raw_has_plain_br(then_body) {
return true
}
match else_body {
Some(body) =>
if run_hot_pipeline_raw_has_plain_br(body) {
return true
}
None => ()
}
}
_ => ()
}
}
false
}
///|
fn run_hot_pipeline_raw_original_has_branch_merge_suffix_read(
instrs : Array[@lib.Instruction],
base_local_count : Int32,
) -> Bool {
for idx = 0; idx < instrs.length(); idx = idx + 1 {
match instrs[idx] {
@lib.If(block_type, then_body, Some(else_body)) => {
if run_hot_pipeline_raw_ssa_nomerge_is_void_block_type(block_type) {
let then_writes = @bitset.BitSet::new(base_local_count)
let then_gets = @bitset.BitSet::new(base_local_count)
run_hot_pipeline_raw_collect_base_local_traffic(
then_body, base_local_count, then_writes, then_gets,
)
let else_writes = @bitset.BitSet::new(base_local_count)
let else_gets = @bitset.BitSet::new(base_local_count)
run_hot_pipeline_raw_collect_base_local_traffic(
else_body, base_local_count, else_writes, else_gets,
)
for local_id = 0; local_id < base_local_count; local_id = local_id + 1 {
if then_writes[local_id] &&
else_writes[local_id] &&
run_hot_pipeline_raw_suffix_reads_before_write(
instrs,
idx + 1,
base_local_count,
local_id,
) {
return true
}
}
}
if run_hot_pipeline_raw_original_has_branch_merge_suffix_read(
then_body, base_local_count,
) ||
run_hot_pipeline_raw_original_has_branch_merge_suffix_read(
else_body, base_local_count,
) {
return true
}
}
@lib.Block(_, @lib.Expr(body))
| @lib.Loop(_, @lib.Expr(body))
| @lib.TryTable(_, _, @lib.Expr(body)) =>
if run_hot_pipeline_raw_original_has_branch_merge_suffix_read(
body, base_local_count,
) {
return true
}
_ => ()
}
}
false
}
///|
fn run_hot_pipeline_raw_suffix_reads_before_write(
instrs : Array[@lib.Instruction],
start_idx : Int32,
base_local_count : Int32,
target_id : Int32,
) -> Bool {
let ignored_writes = @bitset.BitSet::new(base_local_count)
let reads = @bitset.BitSet::new(base_local_count)
for idx = start_idx; idx < instrs.length(); idx = idx + 1 {
match instrs[idx] {
@lib.LocalGet(@lib.LocalIdx(raw)) => {
let id = raw.reinterpret_as_int()
if id == target_id {
return true
}
}
@lib.LocalSet(@lib.LocalIdx(raw)) | @lib.LocalTee(@lib.LocalIdx(raw)) => {
let id = raw.reinterpret_as_int()
if id == target_id {
return false
}
}
@lib.Block(_, @lib.Expr(body))
| @lib.Loop(_, @lib.Expr(body))
| @lib.TryTable(_, _, @lib.Expr(body)) => {
run_hot_pipeline_raw_collect_base_local_traffic(
body, base_local_count, ignored_writes, reads,
)
if reads[target_id] {
return true
}
if ignored_writes[target_id] {
return false
}
}
@lib.If(_, then_body, else_body) => {
run_hot_pipeline_raw_collect_base_local_traffic(
then_body, base_local_count, ignored_writes, reads,
)
match else_body {
Some(body) =>
run_hot_pipeline_raw_collect_base_local_traffic(
body, base_local_count, ignored_writes, reads,
)
None => ()
}
if reads[target_id] {
return true
}
if ignored_writes[target_id] {
return false
}
}
_ => ()
}
}
false
}
///|
fn run_hot_pipeline_raw_has_dropped_base_write_read(
original_instrs : Array[@lib.Instruction],
finalized_instrs : Array[@lib.Instruction],
base_local_count : Int32,
) -> Bool {
if !run_hot_pipeline_raw_has_plain_br(original_instrs) &&
!run_hot_pipeline_raw_original_has_branch_merge_suffix_read(
original_instrs, base_local_count,
) {
return false
}
let original_writes = @bitset.BitSet::new(base_local_count)
let original_gets = @bitset.BitSet::new(base_local_count)
run_hot_pipeline_raw_collect_base_local_traffic(
original_instrs, base_local_count, original_writes, original_gets,
)
let finalized_writes = @bitset.BitSet::new(base_local_count)
let finalized_gets = @bitset.BitSet::new(base_local_count)
run_hot_pipeline_raw_collect_base_local_traffic(
finalized_instrs, base_local_count, finalized_writes, finalized_gets,
)
for local_id = 0; local_id < base_local_count; local_id = local_id + 1 {
if original_writes[local_id] &&
finalized_gets[local_id] &&
!finalized_writes[local_id] {
return true
}
}
false
}
///|
fn run_hot_pipeline_raw_ssa_nomerge_local_id_from_get(
instr : @lib.Instruction,
) -> Int32? {
match instr {
@lib.LocalGet(@lib.LocalIdx(raw)) => Some(raw.reinterpret_as_int())
_ => None
}
}
///|
fn run_hot_pipeline_raw_ssa_nomerge_local_id_from_set(
instr : @lib.Instruction,
) -> Int32? {
match instr {
@lib.LocalSet(@lib.LocalIdx(raw)) => Some(raw.reinterpret_as_int())
_ => None
}
}
///|
fn run_hot_pipeline_raw_ssa_nomerge_can_redirect_branch_copy_producer(
instrs : Array[@lib.Instruction],
producer_idx : Int32,
copy_idx : Int32,
temp_id : Int32,
target_id : Int32,
) -> Bool {
for scan = producer_idx + 1; scan < copy_idx; scan = scan + 1 {
match instrs[scan] {
@lib.LocalGet(@lib.LocalIdx(raw)) => {
let id = raw.reinterpret_as_int()
if id == temp_id || id == target_id {
return false
}
}
@lib.LocalSet(@lib.LocalIdx(raw)) | @lib.LocalTee(@lib.LocalIdx(raw)) => {
let id = raw.reinterpret_as_int()
if id == temp_id || id == target_id {
return false
}
}
@lib.Nop
| @lib.I32Const(_)
| @lib.I64Const(_)
| @lib.F32Const(_)
| @lib.F64Const(_) => ()
// Moving a write across control or a call can expose it on another path.
_ => return false
}
}
true
}
///|
fn run_hot_pipeline_raw_ssa_nomerge_find_branch_copy_producer(
instrs : Array[@lib.Instruction],
start_idx : Int32,
temp_id : Int32,
target_id : Int32,
) -> Int32 {
let mut scan = start_idx
while scan >= 0 {
match instrs[scan] {
@lib.LocalSet(@lib.LocalIdx(raw)) => {
let id = raw.reinterpret_as_int()
if id == temp_id {
if run_hot_pipeline_raw_ssa_nomerge_can_redirect_branch_copy_producer(
instrs,
scan,
start_idx + 1,
temp_id,
target_id,
) {
return scan
}
return -1
}
if id == target_id {
return -1
}
}
@lib.LocalGet(@lib.LocalIdx(raw)) => {
let id = raw.reinterpret_as_int()
if id == temp_id || id == target_id {
return -1
}
}
@lib.LocalTee(@lib.LocalIdx(raw)) => {
let id = raw.reinterpret_as_int()
if id == temp_id || id == target_id {
return -1
}
}
@lib.Br(_)
| @lib.BrIf(_)
| @lib.BrTable(_, _)
| @lib.Return
| @lib.ReturnCall(_)
| @lib.ReturnCallIndirect(_, _)
| @lib.ReturnCallRef(_)
| @lib.Throw(_)
| @lib.ThrowRef
| @lib.Unreachable => return -1
_ => ()
}
scan = scan - 1
}
-1
}
///|
fn run_hot_pipeline_raw_ssa_nomerge_instr_reads_local(
instr : @lib.Instruction,
local_id : Int32,
) -> Bool {
match instr {
@lib.LocalGet(@lib.LocalIdx(raw)) => raw.reinterpret_as_int() == local_id
@lib.Block(_, @lib.Expr(body))
| @lib.Loop(_, @lib.Expr(body))
| @lib.TryTable(_, _, @lib.Expr(body)) =>
run_hot_pipeline_raw_ssa_nomerge_body_reads_local(body, local_id)
@lib.If(_, then_body, else_body) => {
if run_hot_pipeline_raw_ssa_nomerge_body_reads_local(then_body, local_id) {
return true
}
match else_body {
Some(body) =>
run_hot_pipeline_raw_ssa_nomerge_body_reads_local(body, local_id)
None => false
}
}
_ => false
}
}
///|
fn run_hot_pipeline_raw_ssa_nomerge_body_reads_local(
instrs : Array[@lib.Instruction],
local_id : Int32,
) -> Bool {
for instr in instrs {
if run_hot_pipeline_raw_ssa_nomerge_instr_reads_local(instr, local_id) {
return true
}
}
false
}
///|
fn run_hot_pipeline_raw_ssa_nomerge_body_reads_local_from(
instrs : Array[@lib.Instruction],
start_idx : Int32,
local_id : Int32,
) -> Bool {
let mut idx = start_idx
while idx < instrs.length() {
if run_hot_pipeline_raw_ssa_nomerge_instr_reads_local(instrs[idx], local_id) {
return true
}
idx = idx + 1
}
false
}
///|
fn run_hot_pipeline_raw_ssa_nomerge_redirect_if_arm_copy_suffix(
body : Array[@lib.Instruction],
temp_id : Int32,
target_id : Int32,
) -> Array[@lib.Instruction]? {
if body.is_empty() || body.any(dae_instr_has_branch) {
return None
}
let last = body.length() - 1
match run_hot_pipeline_raw_ssa_nomerge_local_id_from_set(body[last]) {
Some(id) if id == temp_id => {
let rewritten = body.copy()
rewritten[last] = @lib.Instruction::local_set(
@lib.LocalIdx::new(target_id.reinterpret_as_uint()),
)
Some(rewritten)
}
_ => None
}
}
///|
fn run_hot_pipeline_raw_ssa_nomerge_redirect_if_branch_alias_suffix(
instr : @lib.Instruction,
temp_id : Int32,
target_id : Int32,
) -> @lib.Instruction? {
match instr {
@lib.If(block_type, then_body, Some(else_body)) => {
let next_then = match
run_hot_pipeline_raw_ssa_nomerge_redirect_if_arm_copy_suffix(
then_body, temp_id, target_id,
) {
Some(body) => body
None => return None
}
let next_else = match
run_hot_pipeline_raw_ssa_nomerge_redirect_if_arm_copy_suffix(
else_body, temp_id, target_id,
) {
Some(body) => body
None => return None
}
Some(@lib.Instruction::if_(block_type, next_then, Some(next_else)))
}
_ => None
}
}
///|
fn run_hot_pipeline_raw_ssa_nomerge_redirect_if_before_branch_block_alias(
instrs : Array[@lib.Instruction],
whole_body~ : Array[@lib.Instruction]? = None,
) -> (Array[@lib.Instruction], Bool) {
let whole_body = whole_body.unwrap_or(instrs)
let rewritten = instrs.copy()
let mut changed = false
for idx = 0; idx + 1 < instrs.length(); idx = idx + 1 {
match instrs[idx + 1] {
@lib.Block(block_type, @lib.Expr(branch_body)) =>
if branch_body.length() >= 3 {
match
(
run_hot_pipeline_raw_ssa_nomerge_local_id_from_get(branch_body[0]),
run_hot_pipeline_raw_ssa_nomerge_local_id_from_set(branch_body[1]),
branch_body[2],
) {
(Some(temp_id), Some(target_id), @lib.Br(_)) =>
if dae_local_usage_evidence(
whole_body,
@lib.LocalIdx::new(temp_id.reinterpret_as_uint()),
).get_count ==
1 &&
!run_hot_pipeline_raw_ssa_nomerge_body_reads_local_from(
instrs,
idx + 2,
temp_id,
) {
match
run_hot_pipeline_raw_ssa_nomerge_redirect_if_branch_alias_suffix(
rewritten[idx],
temp_id,
target_id,
) {
Some(next_if) => {
let next_branch_body : Array[@lib.Instruction] = []
for nested_idx = 2
nested_idx < branch_body.length()
nested_idx = nested_idx + 1 {
next_branch_body.push(branch_body[nested_idx])
}
rewritten[idx] = next_if
rewritten[idx + 1] = @lib.Instruction::block(
block_type,
@lib.Expr::new(next_branch_body),
)
changed = true
}
None => ()
}
}
_ => ()
}
}
_ => ()
}
}
if changed {
(rewritten, true)
} else {
(instrs, false)
}
}
///|
fn run_hot_pipeline_raw_ssa_nomerge_redirect_branch_alias_suffix(
instrs : Array[@lib.Instruction],
whole_body~ : Array[@lib.Instruction]? = None,
) -> (Array[@lib.Instruction], Bool) {
let whole_body = whole_body.unwrap_or(instrs)
let rewritten = instrs.copy()
let remove = @bitset.BitSet::new(instrs.length())
let mut changed = false
for idx = 0; idx < instrs.length(); idx = idx + 1 {
match instrs[idx] {
@lib.Block(block_type, @lib.Expr(body)) => {
let (next_body, body_changed) = run_hot_pipeline_raw_ssa_nomerge_redirect_branch_alias_suffix(
body,
whole_body=Some(whole_body),
)
if body_changed {
rewritten[idx] = @lib.Instruction::block(
block_type,
@lib.Expr::new(next_body),
)
changed = true
}
}
@lib.Loop(block_type, @lib.Expr(body)) => {
let (next_body, body_changed) = run_hot_pipeline_raw_ssa_nomerge_redirect_branch_alias_suffix(
body,
whole_body=Some(whole_body),
)
if body_changed {
rewritten[idx] = @lib.Instruction::loop_(
block_type,
@lib.Expr::new(next_body),
)
changed = true
}
}
@lib.TryTable(block_type, catches, @lib.Expr(body)) => {
let (next_body, body_changed) = run_hot_pipeline_raw_ssa_nomerge_redirect_branch_alias_suffix(
body,
whole_body=Some(whole_body),
)
if body_changed {
rewritten[idx] = @lib.Instruction::try_table(
block_type,
catches,
@lib.Expr::new(next_body),
)
changed = true
}
}
@lib.If(block_type, then_body, else_body) => {
let (next_then, then_changed) = run_hot_pipeline_raw_ssa_nomerge_redirect_branch_alias_suffix(
then_body,
whole_body=Some(whole_body),
)
let mut next_else = else_body
let mut else_changed = false
match else_body {
Some(body) => {
let (body_next, body_changed) = run_hot_pipeline_raw_ssa_nomerge_redirect_branch_alias_suffix(
body,
whole_body=Some(whole_body),
)
if body_changed {
next_else = Some(body_next)
else_changed = true
}
}
None => ()
}
if then_changed || else_changed {
rewritten[idx] = @lib.Instruction::if_(
block_type, next_then, next_else,
)
changed = true
}
}
@lib.Br(_) => {
if idx >= 3 && !remove[idx - 2] && !remove[idx - 1] {
match
(
run_hot_pipeline_raw_ssa_nomerge_local_id_from_get(
instrs[idx - 2],
),
run_hot_pipeline_raw_ssa_nomerge_local_id_from_set(
instrs[idx - 1],
),
) {
(Some(temp_id), Some(target_id)) =>
if dae_local_usage_evidence(
whole_body,
@lib.LocalIdx::new(temp_id.reinterpret_as_uint()),
).get_count ==
1 &&
!run_hot_pipeline_raw_ssa_nomerge_body_reads_local_from(
instrs,
idx + 1,
temp_id,
) {
match
run_hot_pipeline_raw_ssa_nomerge_redirect_if_branch_alias_suffix(
rewritten[idx - 3],
temp_id,
target_id,
) {
Some(next_if) => {
rewritten[idx - 3] = next_if
remove[idx - 2] = true
remove[idx - 1] = true
changed = true
}
None => ()
}
}
_ => ()
}
}
// An earlier copy collapse can retarget the set immediately before
// this copy. Follow that updated destination through the whole chain.
let mut scan = idx - 2
while scan >= 0 {
if remove[scan] || remove[scan + 1] {
break
}
match
(
run_hot_pipeline_raw_ssa_nomerge_local_id_from_get(
rewritten[scan],
),
run_hot_pipeline_raw_ssa_nomerge_local_id_from_set(
rewritten[scan + 1],
),
) {
(Some(temp_id), Some(target_id)) => {
if dae_local_usage_evidence(
whole_body,
@lib.LocalIdx::new(temp_id.reinterpret_as_uint()),
).get_count !=
1 {
break
}
let producer_idx = run_hot_pipeline_raw_ssa_nomerge_find_branch_copy_producer(
rewritten,
scan - 1,
temp_id,
target_id,
)
if producer_idx < 0 {
break
}
rewritten[producer_idx] = @lib.Instruction::local_set(
@lib.LocalIdx::new(target_id.reinterpret_as_uint()),
)
remove[scan] = true
remove[scan + 1] = true
changed = true
scan = scan - 2
continue
}
_ => break
}
}
}
_ => ()
}
}
let (alias_block_instrs, alias_block_changed) = run_hot_pipeline_raw_ssa_nomerge_redirect_if_before_branch_block_alias(
rewritten,
whole_body=Some(whole_body),
)
if !changed && !alias_block_changed {
return (instrs, false)
}
let final_rewritten = if alias_block_changed {
alias_block_instrs
} else {
rewritten
}
let compacted : Array[@lib.Instruction] = []
for idx = 0; idx < final_rewritten.length(); idx = idx + 1 {
if !remove[idx] {
compacted.push(final_rewritten[idx])
}
}
(compacted, true)
}
///|
fn run_hot_pipeline_raw_ssa_nomerge_child_block_is_parent_exit(
instr : @lib.Instruction,
) -> Bool {
match instr {
@lib.Block(@lib.VoidBlockType, @lib.Expr([@lib.Br(@lib.LabelIdx(1U))])) =>
true
_ => false
}
}
///|
fn run_hot_pipeline_raw_ssa_nomerge_collapse_child_br_parent_body(
instrs : Array[@lib.Instruction],
) -> (Array[@lib.Instruction], Bool) {
let rewritten : Array[@lib.Instruction] = []
let mut changed = false
let mut idx = 0
while idx < instrs.length() {
if run_hot_pipeline_raw_ssa_nomerge_child_block_is_parent_exit(instrs[idx]) {
rewritten.push(@lib.Instruction::br(@lib.LabelIdx::new(0U)))
if idx + 1 < instrs.length() {
changed = true
}
return (rewritten, true)
}
rewritten.push(instrs[idx])
idx = idx + 1
}
(rewritten, changed)
}
///|
fn run_hot_pipeline_raw_ssa_nomerge_collapse_child_br_blocks_instr(
instr : @lib.Instruction,
) -> (@lib.Instruction, Bool) {
match instr {
@lib.Block(block_type, @lib.Expr(body)) => {
let (rewritten_body, body_changed) = run_hot_pipeline_raw_ssa_nomerge_collapse_child_br_blocks_body(
body,
)
let (collapsed_body, collapsed) = match block_type {
@lib.VoidBlockType =>
run_hot_pipeline_raw_ssa_nomerge_collapse_child_br_parent_body(
rewritten_body,
)
_ => (rewritten_body, false)
}
(
@lib.Instruction::block(block_type, @lib.Expr::new(collapsed_body)),
body_changed || collapsed,
)
}
@lib.Loop(block_type, @lib.Expr(body)) => {
let (rewritten_body, body_changed) = run_hot_pipeline_raw_ssa_nomerge_collapse_child_br_blocks_body(
body,
)
(
@lib.Instruction::loop_(block_type, @lib.Expr::new(rewritten_body)),
body_changed,
)
}
@lib.TryTable(block_type, catches, @lib.Expr(body)) => {
let (rewritten_body, body_changed) = run_hot_pipeline_raw_ssa_nomerge_collapse_child_br_blocks_body(
body,
)
(
@lib.Instruction::try_table(
block_type,
catches,
@lib.Expr::new(rewritten_body),
),
body_changed,
)
}
@lib.If(block_type, then_body, else_body) => {
let (rewritten_then, then_changed) = run_hot_pipeline_raw_ssa_nomerge_collapse_child_br_blocks_body(
then_body,
)
let mut changed = then_changed
let rewritten_else = match else_body {
Some(body) => {
let (next_body, else_changed) = run_hot_pipeline_raw_ssa_nomerge_collapse_child_br_blocks_body(
body,
)
changed = changed || else_changed
Some(next_body)
}
None => None
}
(
@lib.Instruction::if_(block_type, rewritten_then, rewritten_else),
changed,
)
}
_ => (instr, false)
}
}
///|
fn run_hot_pipeline_raw_ssa_nomerge_collapse_child_br_blocks_body(
instrs : Array[@lib.Instruction],
) -> (Array[@lib.Instruction], Bool) {
let rewritten : Array[@lib.Instruction] = []
let mut changed = false
for instr in instrs {
let (next_instr, instr_changed) = run_hot_pipeline_raw_ssa_nomerge_collapse_child_br_blocks_instr(
instr,
)
rewritten.push(next_instr)
changed = changed || instr_changed
}
(rewritten, changed)
}
///|
fn run_hot_pipeline_raw_ssa_nomerge_find_mask_update_tee_from_local(
instrs : Array[@lib.Instruction],
local_id : Int32,
) -> Int32? {
// A derived mask is not an alias of its source. Require an explicit
// assignment on every path that can reach the following eqz.
let len = instrs.length()
if len < 2 || instrs.any(dae_instr_has_branch) {
return None
}
match (instrs[len - 2], instrs[len - 1]) {
(@lib.LocalTee(@lib.LocalIdx(fresh)), @lib.LocalSet(@lib.LocalIdx(target))) if target.reinterpret_as_int() ==
local_id &&
fresh != target => Some(fresh.reinterpret_as_int())
_ => None
}
}
///|
fn run_hot_pipeline_raw_ssa_nomerge_retarget_mask_eqz_after_nested_update(
instrs : Array[@lib.Instruction],
) -> (Array[@lib.Instruction], Bool) {
let child_rewritten : Array[@lib.Instruction] = []
let mut changed = false
for instr in instrs {
match instr {
@lib.Block(block_type, @lib.Expr(body)) => {
let (next_body, body_changed) = run_hot_pipeline_raw_ssa_nomerge_retarget_mask_eqz_after_nested_update(
body,
)
child_rewritten.push(
@lib.Instruction::block(block_type, @lib.Expr::new(next_body)),
)
changed = changed || body_changed
}
@lib.Loop(block_type, @lib.Expr(body)) => {
let (next_body, body_changed) = run_hot_pipeline_raw_ssa_nomerge_retarget_mask_eqz_after_nested_update(
body,
)
child_rewritten.push(
@lib.Instruction::loop_(block_type, @lib.Expr::new(next_body)),
)
changed = changed || body_changed
}
@lib.TryTable(block_type, catches, @lib.Expr(body)) => {
let (next_body, body_changed) = run_hot_pipeline_raw_ssa_nomerge_retarget_mask_eqz_after_nested_update(
body,
)
child_rewritten.push(
@lib.Instruction::try_table(
block_type,
catches,
@lib.Expr::new(next_body),
),
)
changed = changed || body_changed
}
@lib.If(block_type, then_body, else_body) => {
let (next_then, then_changed) = run_hot_pipeline_raw_ssa_nomerge_retarget_mask_eqz_after_nested_update(
then_body,
)
let next_else = match else_body {
Some(body) => {
let (rewritten_else, else_changed) = run_hot_pipeline_raw_ssa_nomerge_retarget_mask_eqz_after_nested_update(
body,
)
changed = changed || else_changed
Some(rewritten_else)
}
None => None
}
child_rewritten.push(
@lib.Instruction::if_(block_type, next_then, next_else),
)
changed = changed || then_changed
}
_ => child_rewritten.push(instr)
}
}
let out : Array[@lib.Instruction] = []
let mut idx = 0
while idx < child_rewritten.length() {
if idx + 2 < child_rewritten.length() {
match
(
child_rewritten[idx],
child_rewritten[idx + 1],
child_rewritten[idx + 2],
) {
(
@lib.Block(_, @lib.Expr(body)),
@lib.LocalGet(@lib.LocalIdx(raw)),
@lib.I32Eqz,
) => {
let local_id = raw.reinterpret_as_int()
match
run_hot_pipeline_raw_ssa_nomerge_find_mask_update_tee_from_local(
body, local_id,
) {
Some(fresh_local) => {
out.push(child_rewritten[idx])
out.push(
@lib.Instruction::local_get(
@lib.LocalIdx::new(fresh_local.reinterpret_as_uint()),
),
)
out.push(child_rewritten[idx + 2])
changed = true
idx = idx + 3
continue
}
None => ()
}
}
_ => ()
}
}
out.push(child_rewritten[idx])
idx = idx + 1
}
(out, changed)
}
///|
fn run_hot_pipeline_raw_ssa_locals_equal_before(
instrs : Array[@lib.Instruction],
end_ : Int32,
left : Int32,
right : Int32,
) -> Bool {
if left == right {
return true
}
for idx = end_ - 1; idx >= 0; idx = idx - 1 {
match instrs[idx] {
@lib.LocalSet(@lib.LocalIdx(raw)) | @lib.LocalTee(@lib.LocalIdx(raw)) => {
let target = raw.reinterpret_as_int()
if target == left || target == right {
if idx == 0 {
return false
}
let other = if target == left { right } else { left }
return match instrs[idx - 1] {
@lib.LocalGet(@lib.LocalIdx(source))
| @lib.LocalTee(@lib.LocalIdx(source)) =>
source.reinterpret_as_int() == other
_ => false
}
}
}
@lib.Block(_, _)
| @lib.Loop(_, _)
| @lib.If(_, _, _)
| @lib.Try(_, _, _, _)
| @lib.TryTable(_, _, _) => return false
_ => if dae_instr_has_branch(instrs[idx]) { return false }
}
}
false
}
///|
fn run_hot_pipeline_raw_ssa_nomerge_repair_stack_spill_arg_alias(
instrs : Array[@lib.Instruction],
params : Array[@lib.ValType],
locals : @lib.Locals,
extra_local_types : Array[@lib.ValType],
) -> (Array[@lib.Instruction], Bool) {
let child_rewritten : Array[@lib.Instruction] = []
let mut child_changed = false
for instr in instrs {
match instr {
@lib.Block(block_type, @lib.Expr(body)) => {
let (next_body, changed) = run_hot_pipeline_raw_ssa_nomerge_repair_stack_spill_arg_alias(
body, params, locals, extra_local_types,
)
child_rewritten.push(
@lib.Instruction::block(block_type, @lib.Expr::new(next_body)),
)
child_changed = child_changed || changed
}
@lib.Loop(block_type, @lib.Expr(body)) => {
let (next_body, changed) = run_hot_pipeline_raw_ssa_nomerge_repair_stack_spill_arg_alias(
body, params, locals, extra_local_types,
)
child_rewritten.push(
@lib.Instruction::loop_(block_type, @lib.Expr::new(next_body)),
)
child_changed = child_changed || changed
}
@lib.TryTable(block_type, catches, @lib.Expr(body)) => {
let (next_body, changed) = run_hot_pipeline_raw_ssa_nomerge_repair_stack_spill_arg_alias(
body, params, locals, extra_local_types,
)
child_rewritten.push(
@lib.Instruction::try_table(
block_type,
catches,
@lib.Expr::new(next_body),
),
)
child_changed = child_changed || changed
}
@lib.If(block_type, then_body, else_body) => {
let (next_then, then_changed) = run_hot_pipeline_raw_ssa_nomerge_repair_stack_spill_arg_alias(
then_body, params, locals, extra_local_types,
)
let next_else = match else_body {
Some(body) => {
let (rewritten_else, else_changed) = run_hot_pipeline_raw_ssa_nomerge_repair_stack_spill_arg_alias(
body, params, locals, extra_local_types,
)
child_changed = child_changed || else_changed
Some(rewritten_else)
}
None => None
}
child_rewritten.push(
@lib.Instruction::if_(block_type, next_then, next_else),
)
child_changed = child_changed || then_changed
}
_ => child_rewritten.push(instr)
}
}
let out : Array[@lib.Instruction] = []
let mut changed = child_changed
let mut last_spilled_local = -1
let call_result_aliases : Array[Int32] = []
let mut idx = 0
while idx < child_rewritten.length() {
if idx + 2 < child_rewritten.length() {
match
(
child_rewritten[idx],
child_rewritten[idx + 1],
child_rewritten[idx + 2],
) {
(
@lib.GlobalGet(_),
@lib.LocalGet(@lib.LocalIdx(spilled_raw)),
@lib.I32Store(_),
) => last_spilled_local = spilled_raw.reinterpret_as_int()
(
@lib.LocalGet(@lib.LocalIdx(source_raw)),
@lib.Call(_),
@lib.LocalSet(@lib.LocalIdx(target_raw)),
) => {
let source_id = source_raw.reinterpret_as_int()
let target_id = target_raw.reinterpret_as_int()
if source_id >= 0 && target_id >= 0 {
while call_result_aliases.length() <= source_id {
call_result_aliases.push(-1)
}
call_result_aliases[source_id] = target_id
}
}
_ => ()
}
}
if idx + 6 < child_rewritten.length() {
match
(
child_rewritten[idx],
child_rewritten[idx + 1],
child_rewritten[idx + 2],
child_rewritten[idx + 3],
child_rewritten[idx + 4],
child_rewritten[idx + 5],
child_rewritten[idx + 6],
) {
(
@lib.LocalGet(_),
@lib.LocalGet(@lib.LocalIdx(stale_raw)),
@lib.LocalGet(_),
@lib.LocalSet(@lib.LocalIdx(set_raw)),
@lib.GlobalGet(_),
@lib.LocalGet(@lib.LocalIdx(stored_raw)),
@lib.I32Store(_),
) => {
let stale_id = stale_raw.reinterpret_as_int()
let set_id = set_raw.reinterpret_as_int()
let stored_id = stored_raw.reinterpret_as_int()
let replacement_id = if stale_id >= 0 &&
stale_id < call_result_aliases.length() {
call_result_aliases[stale_id]
} else {
-1
}
if stored_id == set_id &&
replacement_id >= 0 &&
replacement_id != stale_id &&
run_hot_pipeline_raw_rewritten_local_type(
params, locals, extra_local_types, stale_id,
) ==
run_hot_pipeline_raw_rewritten_local_type(
params, locals, extra_local_types, replacement_id,
) &&
run_hot_pipeline_raw_ssa_locals_equal_before(
child_rewritten, idx, stale_id, replacement_id,
) {
out.push(child_rewritten[idx])
out.push(
@lib.Instruction::local_get(
@lib.LocalIdx::new(replacement_id.reinterpret_as_uint()),
),
)
for copy_idx = idx + 2; copy_idx < idx + 7; copy_idx = copy_idx + 1 {
out.push(child_rewritten[copy_idx])
}
changed = true
idx = idx + 7
continue
}
}
_ => ()
}
}
if idx + 5 < child_rewritten.length() && last_spilled_local >= 0 {
match
(
child_rewritten[idx],
child_rewritten[idx + 1],
child_rewritten[idx + 2],
child_rewritten[idx + 3],
child_rewritten[idx + 4],
child_rewritten[idx + 5],
) {
(
@lib.LocalGet(@lib.LocalIdx(first_raw)),
@lib.LocalGet(_),
@lib.LocalSet(@lib.LocalIdx(set_raw)),
@lib.GlobalGet(_),
@lib.LocalGet(@lib.LocalIdx(stored_raw)),
@lib.I32Store(_),
) => {
let first_id = first_raw.reinterpret_as_int()
let set_id = set_raw.reinterpret_as_int()
let stored_id = stored_raw.reinterpret_as_int()
let same_typed_replacement = run_hot_pipeline_raw_rewritten_local_type(
params, locals, extra_local_types, first_id,
) ==
run_hot_pipeline_raw_rewritten_local_type(
params, locals, extra_local_types, last_spilled_local,
)
if stored_id == set_id &&
same_typed_replacement &&
first_id != last_spilled_local &&
run_hot_pipeline_raw_ssa_locals_equal_before(
child_rewritten, idx, first_id, last_spilled_local,
) &&
(
first_id == set_id ||
first_id > set_id ||
(first_id < last_spilled_local && last_spilled_local < set_id)
) {
out.push(
@lib.Instruction::local_get(
@lib.LocalIdx::new(last_spilled_local.reinterpret_as_uint()),
),
)
for copy_idx = idx + 1; copy_idx < idx + 6; copy_idx = copy_idx + 1 {
out.push(child_rewritten[copy_idx])
}
changed = true
idx = idx + 6
continue
}
}
_ => ()
}
}
out.push(child_rewritten[idx])
idx = idx + 1
}
(out, changed)
}
///|
fn run_hot_pipeline_raw_ssa_nomerge_repair_func_stack_spill_arg_alias(
func : @lib.Func,
params : Array[@lib.ValType],
) -> @lib.Func {
let @lib.Func(locals, @lib.Expr(instrs)) = func
let no_extra_local_types : Array[@lib.ValType] = []
let (repaired_instrs, changed) = run_hot_pipeline_raw_ssa_nomerge_repair_stack_spill_arg_alias(
instrs, params, locals, no_extra_local_types,
)
if changed {
@lib.Func::new(locals, @lib.Expr::new(repaired_instrs))
} else {
func
}
}
///|
fn run_hot_pipeline_raw_ssa_nomerge_has_continuation_flow(
instrs : Array[@lib.Instruction],
) -> Bool {
for instr in instrs {
match instr {
@lib.Suspend(_)
| @lib.Resume(_, _)
| @lib.ResumeThrow(_, _, _)
| @lib.ResumeThrowRef(_, _)
| @lib.StackSwitch(_, _) => return true
@lib.Block(_, @lib.Expr(body))
| @lib.Loop(_, @lib.Expr(body))
| @lib.TryTable(_, _, @lib.Expr(body)) =>
if run_hot_pipeline_raw_ssa_nomerge_has_continuation_flow(body) {
return true
}
@lib.If(_, then_body, else_body) => {
if run_hot_pipeline_raw_ssa_nomerge_has_continuation_flow(then_body) {
return true
}
match else_body {
Some(body) =>
if run_hot_pipeline_raw_ssa_nomerge_has_continuation_flow(body) {
return true
}
None => ()
}
}
@lib.Try(_, @lib.Expr(body), catches, _) => {
if run_hot_pipeline_raw_ssa_nomerge_has_continuation_flow(body) {
return true
}
for catch_ in catches {
let catch_body = match catch_ {
@lib.LegacyCatch(_, @lib.Expr(body))
| @lib.LegacyCatchAll(@lib.Expr(body)) => body
}
if run_hot_pipeline_raw_ssa_nomerge_has_continuation_flow(catch_body) {
return true
}
}
}
_ => ()
}
}
false
}
///|
fn run_hot_pipeline_raw_ssa_nomerge_structured(
func : @lib.Func,
module_ctx : @ir.HotModuleContext,
params : Array[@lib.ValType],
) -> @lib.Func? {
let @lib.Func(locals, @lib.Expr(instrs)) = func
// Continuation handlers add non-local exits that this legacy alias rewrite
// does not include in its label-exit liveness. Leave them to the guarded HOT
// fallback until those edges participate in alias merging.
if run_hot_pipeline_raw_ssa_nomerge_has_continuation_flow(instrs) {
return None
}
let base_local_count = params.length() + locals.length()
let local_aliases = run_hot_pipeline_raw_canonical_aliases(base_local_count)
let canonical_aliases = local_aliases.copy()
let initialized_locals = run_hot_pipeline_raw_initialized_locals(
base_local_count,
params.length(),
)
run_hot_pipeline_raw_ssa_nomerge_mark_written_locals(
instrs, initialized_locals,
)
let extra_local_types : Array[@lib.ValType] = []
match
run_hot_pipeline_raw_rewrite_instrs(
instrs,
module_ctx,
params,
locals,
local_aliases,
canonical_aliases,
initialized_locals,
[],
@bitset.BitSet::new(base_local_count),
@bitset.BitSet::new(base_local_count),
@bitset.BitSet::new(base_local_count),
extra_local_types,
) {
Some((rewritten_instrs, _, _, _, _)) => {
let value_if_freshened = run_hot_pipeline_raw_freshen_value_if_body_scratch(
rewritten_instrs, params, locals, extra_local_types,
)
let call_spilled = run_hot_pipeline_raw_spill_call_result_across_following_call(
value_if_freshened, module_ctx, params, locals, extra_local_types,
)
let finalized_instrs = run_hot_pipeline_raw_freshen_param_scratch_writes(
call_spilled, params, locals, extra_local_types,
)
let (redirected_instrs, _) = run_hot_pipeline_raw_ssa_nomerge_redirect_branch_alias_suffix(
finalized_instrs,
)
let (dead_tail_cleaned, _) = run_hot_pipeline_raw_ssa_nomerge_collapse_child_br_blocks_body(
redirected_instrs,
)
let (mask_retargeted_instrs, _) = run_hot_pipeline_raw_ssa_nomerge_retarget_mask_eqz_after_nested_update(
dead_tail_cleaned,
)
let (spill_arg_repaired_instrs, _) = run_hot_pipeline_raw_ssa_nomerge_repair_stack_spill_arg_alias(
mask_retargeted_instrs, params, locals, extra_local_types,
)
let (compacted_instrs, compacted_locals, compacted_extra_local_types) = run_hot_pipeline_raw_compact_appended_one_use_scratch_locals(
spill_arg_repaired_instrs, params, locals, extra_local_types,
)
if run_hot_pipeline_raw_has_unwritten_appended_local_get(
compacted_instrs, base_local_count,
) ||
run_hot_pipeline_raw_has_dropped_base_write_read(
instrs, compacted_instrs, base_local_count,
) {
return None
}
let rewritten_locals = compacted_locals.copy()
rewritten_locals.append_types(compacted_extra_local_types)
let rewritten_func = @lib.Func::new(
rewritten_locals,
@lib.Expr::new(compacted_instrs),
)
match
run_hot_pipeline_raw_compact_existing_one_use_scratch_func(
rewritten_func, params,
) {
Some(compacted_func) =>
Some(
run_hot_pipeline_raw_ssa_nomerge_repair_func_stack_spill_arg_alias(
compacted_func, params,
),
)
None =>
Some(
run_hot_pipeline_raw_ssa_nomerge_repair_func_stack_spill_arg_alias(
rewritten_func, params,
),
)
}
}
None => {
let fallback_extra_local_types : Array[@lib.ValType] = []
let value_if_freshened = run_hot_pipeline_raw_freshen_value_if_body_scratch(
instrs, params, locals, fallback_extra_local_types,
)
let call_spilled = run_hot_pipeline_raw_spill_call_result_across_following_call(
value_if_freshened, module_ctx, params, locals, fallback_extra_local_types,
)
let finalized_instrs = run_hot_pipeline_raw_freshen_param_scratch_writes(
call_spilled, params, locals, fallback_extra_local_types,
)
let (redirected_instrs, _) = run_hot_pipeline_raw_ssa_nomerge_redirect_branch_alias_suffix(
finalized_instrs,
)
let (dead_tail_cleaned, _) = run_hot_pipeline_raw_ssa_nomerge_collapse_child_br_blocks_body(
redirected_instrs,
)
let (mask_retargeted_instrs, mask_retargeted) = run_hot_pipeline_raw_ssa_nomerge_retarget_mask_eqz_after_nested_update(
dead_tail_cleaned,
)
let (spill_arg_repaired_instrs, spill_arg_repaired) = run_hot_pipeline_raw_ssa_nomerge_repair_stack_spill_arg_alias(
mask_retargeted_instrs, params, locals, fallback_extra_local_types,
)
let (compacted_instrs, compacted_locals, compacted_extra_local_types) = run_hot_pipeline_raw_compact_appended_one_use_scratch_locals(
spill_arg_repaired_instrs, params, locals, fallback_extra_local_types,
)
if run_hot_pipeline_raw_has_unwritten_appended_local_get(
compacted_instrs, base_local_count,
) ||
run_hot_pipeline_raw_has_dropped_base_write_read(
instrs, compacted_instrs, base_local_count,
) {
return None
}
if compacted_extra_local_types.length() == 0 &&
!mask_retargeted &&
!spill_arg_repaired {
None
} else {
let rewritten_locals = compacted_locals.copy()
rewritten_locals.append_types(compacted_extra_local_types)
let rewritten_func = @lib.Func::new(
rewritten_locals,
@lib.Expr::new(compacted_instrs),
)
match
run_hot_pipeline_raw_compact_existing_one_use_scratch_func(
rewritten_func, params,
) {
Some(compacted_func) =>
Some(
run_hot_pipeline_raw_ssa_nomerge_repair_func_stack_spill_arg_alias(
compacted_func, params,
),
)
None =>
Some(
run_hot_pipeline_raw_ssa_nomerge_repair_func_stack_spill_arg_alias(
rewritten_func, params,
),
)
}
}
}
}
}
///|
fn run_hot_pipeline_raw_ssa_nomerge_next_set_decision(
plan : SsaNoMergeRewritePlan,
cursor : Int32,
) -> SsaNoMergeWriteDecision? {
if cursor >= plan.write_rewrites.length() {
return None
}
let rewrite = plan.write_rewrites[cursor]
if rewrite.write_id < 0 {
return None
}
Some(rewrite.decision)
}
///|
fn run_hot_pipeline_raw_ssa_nomerge_next_get_decision_for_local(
plan : SsaNoMergeRewritePlan,
cursor : Int32,
original_local : Int32,
) -> SsaNoMergeGetDecision? {
if cursor >= plan.get_rewrites.length() {
return None
}
let rewrite = plan.get_rewrites[cursor]
if rewrite.original_local != original_local {
return Some(SsaNoMergeGetDecision::KeepCanonical)
}
Some(rewrite.decision)
}
///|
fn run_hot_pipeline_raw_ssa_nomerge_append_planned_fresh_local(
extra_local_types : Array[@lib.ValType],
params : Array[@lib.ValType],
locals : @lib.Locals,
base_local_count : Int32,
original_local : Int32,
fresh_local_id : Int32,
) -> Unit {
let expected = base_local_count + extra_local_types.length()
if fresh_local_id != expected {
abort(
"ssa-nomerge LocalGraph straight-line plan produced non-sequential fresh local \{fresh_local_id}, expected \{expected}",
)
}
extra_local_types.push(
run_hot_pipeline_raw_local_type(params, locals, original_local),
)
}
///|
priv struct SsaNoMergeRawGetCursor {
mut cursor : Int32
}
///|
priv struct SsaNoMergeRawRewriteCursor {
mut write_cursor : Int32
mut get_cursor : Int32
mut changed : Bool
}
///|
fn run_hot_pipeline_raw_ssa_nomerge_plan_has_defaults(
plan : SsaNoMergeRewritePlan,
) -> Bool {
for rewrite in plan.get_rewrites {
match rewrite.decision {
SsaNoMergeGetDecision::MaterializeDefault => return true
_ => ()
}
}
false
}
///|
fn run_hot_pipeline_raw_ssa_nomerge_plan_is_canonical_only(
plan : SsaNoMergeRewritePlan,
) -> Bool {
for rewrite in plan.write_rewrites {
match rewrite.decision {
SsaNoMergeWriteDecision::Freshen(_) => return false
SsaNoMergeWriteDecision::KeepCanonical => ()
}
}
for rewrite in plan.get_rewrites {
match rewrite.decision {
SsaNoMergeGetDecision::Retarget(_)
| SsaNoMergeGetDecision::MaterializeDefault => return false
SsaNoMergeGetDecision::KeepCanonical => ()
}
}
true
}
///|
fn run_hot_pipeline_raw_ssa_nomerge_sources_are_one_arm_entry_merge(
sources : Array[@ir.HotLocalGraphSource],
write_id : @ir.NodeId,
local_id : @ir.LocalId,
) -> Bool {
if sources.length() != 2 {
return false
}
let mut saw_write = false
let mut saw_entry = false
for source in sources {
match source {
@ir.HotLocalGraphSource::SetSource(source_write) =>
if source_write == write_id {
saw_write = true
}
@ir.HotLocalGraphSource::EntrySource(source_local) =>
if source_local == local_id {
saw_entry = true
}
}
}
saw_write && saw_entry
}
///|
fn run_hot_pipeline_raw_ssa_nomerge_plan_preserves_one_arm_entry_merge(
graph : @ir.HotLocalGraph,
plan : SsaNoMergeRewritePlan,
) -> Bool {
if !run_hot_pipeline_raw_ssa_nomerge_plan_is_canonical_only(plan) {
return false
}
for rewrite in plan.write_rewrites {
match rewrite.decision {
SsaNoMergeWriteDecision::KeepCanonical =>
for get_id in rewrite.influenced_gets {
if @ir.local_graph_get_is_merge(graph, get_id) &&
run_hot_pipeline_raw_ssa_nomerge_sources_are_one_arm_entry_merge(
@ir.local_graph_reaching_sources_for_get(graph, get_id),
rewrite.write_id,
rewrite.original_local,
) {
return true
}
}
SsaNoMergeWriteDecision::Freshen(_) => ()
}
}
false
}
///|
fn run_hot_pipeline_raw_ssa_nomerge_plan_preserves_multisource_merge(
graph : @ir.HotLocalGraph,
plan : SsaNoMergeRewritePlan,
) -> Bool {
if !run_hot_pipeline_raw_ssa_nomerge_plan_is_canonical_only(plan) {
return false
}
for rewrite in plan.write_rewrites {
match rewrite.decision {
SsaNoMergeWriteDecision::KeepCanonical =>
for get_id in rewrite.influenced_gets {
if @ir.local_graph_get_is_merge(graph, get_id) &&
@ir.local_graph_reaching_sources_for_get(graph, get_id).length() > 1 {
return true
}
}
SsaNoMergeWriteDecision::Freshen(_) => ()
}
}
false
}
///|
fn run_hot_pipeline_raw_ssa_nomerge_plan_has_mixed_fresh_canonical_merge(
graph : @ir.HotLocalGraph,
plan : SsaNoMergeRewritePlan,
) -> Bool {
let mut saw_canonical_merge = false
for rewrite in plan.write_rewrites {
match rewrite.decision {
SsaNoMergeWriteDecision::KeepCanonical =>
for get_id in rewrite.influenced_gets {
if @ir.local_graph_get_is_merge(graph, get_id) &&
@ir.local_graph_reaching_sources_for_get(graph, get_id).length() > 1 {
saw_canonical_merge = true
}
}
SsaNoMergeWriteDecision::Freshen(_) => ()
}
}
run_hot_pipeline_raw_ssa_nomerge_plan_has_single_source_freshening(plan) &&
saw_canonical_merge
}
///|
fn run_hot_pipeline_raw_ssa_nomerge_plan_has_single_source_freshening(
plan : SsaNoMergeRewritePlan,
) -> Bool {
let mut saw_freshen = false
let mut saw_retarget = false
for rewrite in plan.write_rewrites {
match rewrite.decision {
SsaNoMergeWriteDecision::Freshen(_) => saw_freshen = true
SsaNoMergeWriteDecision::KeepCanonical => ()
}
}
for rewrite in plan.get_rewrites {
match rewrite.decision {
SsaNoMergeGetDecision::Retarget(_) => saw_retarget = true
_ => ()
}
}
saw_freshen && saw_retarget
}
///|
fn run_hot_pipeline_raw_ssa_nomerge_mark_written_locals(
instrs : Array[@lib.Instruction],
written : @bitset.BitSet,
) -> Unit {
for instr in instrs {
match instr {
@lib.LocalSet(@lib.LocalIdx(raw)) | @lib.LocalTee(@lib.LocalIdx(raw)) =>
written[raw.reinterpret_as_int()] = true
@lib.Block(_, @lib.Expr(body))
| @lib.Loop(_, @lib.Expr(body))
| @lib.TryTable(_, _, @lib.Expr(body)) =>
run_hot_pipeline_raw_ssa_nomerge_mark_written_locals(body, written)
@lib.Try(_, @lib.Expr(body), catches, _) => {
run_hot_pipeline_raw_ssa_nomerge_mark_written_locals(body, written)
for catch_ in catches {
let @lib.Expr(catch_body) = match catch_ {
@lib.LegacyCatch(_, body) | @lib.LegacyCatchAll(body) => body
}
run_hot_pipeline_raw_ssa_nomerge_mark_written_locals(
catch_body, written,
)
}
}
@lib.If(_, then_body, else_body) => {
run_hot_pipeline_raw_ssa_nomerge_mark_written_locals(then_body, written)
match else_body {
Some(body) =>
run_hot_pipeline_raw_ssa_nomerge_mark_written_locals(body, written)
None => ()
}
}
_ => ()
}
}
}
///|
fn run_hot_pipeline_raw_ssa_nomerge_has_body_local_get(
instrs : Array[@lib.Instruction],
param_count : Int32,
) -> Bool {
for instr in instrs {
match instr {
@lib.LocalGet(@lib.LocalIdx(raw)) => {
let local_id = raw.reinterpret_as_int()
if local_id >= param_count {
return true
}
}
@lib.Block(_, @lib.Expr(body))
| @lib.Loop(_, @lib.Expr(body))
| @lib.TryTable(_, _, @lib.Expr(body)) =>
if run_hot_pipeline_raw_ssa_nomerge_has_body_local_get(
body, param_count,
) {
return true
}
@lib.If(_, then_body, else_body) => {
if run_hot_pipeline_raw_ssa_nomerge_has_body_local_get(
then_body, param_count,
) {
return true
}
match else_body {
Some(body) =>
if run_hot_pipeline_raw_ssa_nomerge_has_body_local_get(
body, param_count,
) {
return true
}
None => ()
}
}
_ => ()
}
}
false
}
///|
priv struct SsaNoMergeOneArmMergeControlScan {
mut saw_missing_else : Bool
}
///|
fn run_hot_pipeline_raw_ssa_nomerge_scan_one_arm_merge_control(
instrs : Array[@lib.Instruction],
state : SsaNoMergeOneArmMergeControlScan,
) -> Bool {
for instr in instrs {
match instr {
@lib.BrTable(_, _)
| @lib.BrOnNull(_)
| @lib.BrOnNonNull(_)
| @lib.BrOnCast(_, _, _, _)
| @lib.BrOnCastFail(_, _, _, _)
| @lib.BrOnCastDescEq(_, _, _)
| @lib.BrOnCastDescEqFail(_, _, _)
| @lib.Loop(_, _)
| @lib.TryTable(_, _, _) => return false
@lib.Block(_, @lib.Expr(body)) =>
if !run_hot_pipeline_raw_ssa_nomerge_scan_one_arm_merge_control(
body, state,
) {
return false
}
@lib.If(_, then_body, else_body) => {
if !run_hot_pipeline_raw_ssa_nomerge_scan_one_arm_merge_control(
then_body, state,
) {
return false
}
match else_body {
Some(body) =>
if !run_hot_pipeline_raw_ssa_nomerge_scan_one_arm_merge_control(
body, state,
) {
return false
}
None => state.saw_missing_else = true
}
}
_ => ()
}
}
true
}
///|
fn run_hot_pipeline_raw_ssa_nomerge_one_arm_merge_supported_control(
instrs : Array[@lib.Instruction],
) -> Bool {
let state = { saw_missing_else: false, }
run_hot_pipeline_raw_ssa_nomerge_scan_one_arm_merge_control(instrs, state) &&
state.saw_missing_else
}
///|
fn run_hot_pipeline_raw_ssa_nomerge_multisource_merge_supported_control(
instrs : Array[@lib.Instruction],
) -> Bool {
for instr in instrs {
match instr {
@lib.Br(_)
| @lib.BrTable(_, _)
| @lib.BrOnNull(_)
| @lib.BrOnNonNull(_)
| @lib.BrOnCast(_, _, _, _)
| @lib.BrOnCastFail(_, _, _, _)
| @lib.BrOnCastDescEq(_, _, _)
| @lib.BrOnCastDescEqFail(_, _, _)
| @lib.Loop(_, _)
| @lib.TryTable(_, _, _) => return false
@lib.Block(_, @lib.Expr(body)) =>
if !run_hot_pipeline_raw_ssa_nomerge_multisource_merge_supported_control(
body,
) {
return false
}
@lib.If(_, then_body, else_body) => {
if !run_hot_pipeline_raw_ssa_nomerge_multisource_merge_supported_control(
then_body,
) {
return false
}
match else_body {
Some(body) =>
if !run_hot_pipeline_raw_ssa_nomerge_multisource_merge_supported_control(
body,
) {
return false
}
None => ()
}
}
_ => ()
}
}
true
}
///|
priv struct SsaNoMergeMixedStructuredControlScan {
mut write_count : Int32
mut tee_count : Int32
}
///|
fn run_hot_pipeline_raw_ssa_nomerge_scan_mixed_structured_control(
instrs : Array[@lib.Instruction],
state : SsaNoMergeMixedStructuredControlScan,
) -> Bool {
for instr in instrs {
match instr {
@lib.LocalSet(_) => state.write_count = state.write_count + 1
@lib.LocalTee(_) => {
state.write_count = state.write_count + 1
state.tee_count = state.tee_count + 1
}
@lib.Block(_, @lib.Expr(body)) =>
if !run_hot_pipeline_raw_ssa_nomerge_scan_mixed_structured_control(
body, state,
) {
return false
}
@lib.If(_, then_body, else_body) => {
if !run_hot_pipeline_raw_ssa_nomerge_scan_mixed_structured_control(
then_body, state,
) {
return false
}
match else_body {
Some(body) =>
if !run_hot_pipeline_raw_ssa_nomerge_scan_mixed_structured_control(
body, state,
) {
return false
}
None => ()
}
}
_ => ()
}
}
true
}
///|
fn run_hot_pipeline_raw_ssa_nomerge_mixed_structured_supported_control(
instrs : Array[@lib.Instruction],
) -> Bool {
if !run_hot_pipeline_raw_ssa_nomerge_multisource_merge_supported_control(
instrs,
) {
return false
}
let state = { write_count: 0, tee_count: 0, }
if !run_hot_pipeline_raw_ssa_nomerge_scan_mixed_structured_control(
instrs, state,
) {
return false
}
let (_, _, has_branches, _, _, _) = run_hot_pipeline_instr_scan(instrs)
state.tee_count == 0 ||
state.write_count <= 4 ||
(
!has_branches &&
!run_hot_pipeline_raw_instrs_have_memory_access(instrs) &&
run_hot_pipeline_raw_count_call_like(instrs) == 0
)
}
///|
fn run_hot_pipeline_raw_ssa_nomerge_ordinary_set_supported_control(
instrs : Array[@lib.Instruction],
) -> Bool {
for instr in instrs {
match instr {
@lib.Br(_)
| @lib.BrIf(_)
| @lib.BrTable(_, _)
| @lib.BrOnNull(_)
| @lib.BrOnNonNull(_)
| @lib.BrOnCast(_, _, _, _)
| @lib.BrOnCastFail(_, _, _, _)
| @lib.BrOnCastDescEq(_, _, _)
| @lib.BrOnCastDescEqFail(_, _, _)
| @lib.Loop(_, _)
| @lib.TryTable(_, _, _)
| @lib.Return
| @lib.ReturnCall(_)
| @lib.ReturnCallIndirect(_, _)
| @lib.ReturnCallRef(_)
| @lib.Throw(_)
| @lib.ThrowRef
| @lib.Unreachable => return false
@lib.LocalTee(_) => return false
@lib.Block(_, @lib.Expr(body)) =>
if !run_hot_pipeline_raw_ssa_nomerge_ordinary_set_supported_control(
body,
) {
return false
}
@lib.If(_, then_body, else_body) => {
if !run_hot_pipeline_raw_ssa_nomerge_ordinary_set_supported_control(
then_body,
) {
return false
}
match else_body {
Some(body) =>
if !run_hot_pipeline_raw_ssa_nomerge_ordinary_set_supported_control(
body,
) {
return false
}
None => ()
}
}
_ => ()
}
}
true
}
///|
priv struct SsaNoMergeLoopBackedgeMergeControlScan {
mut saw_loop_backedge_br_if : Bool
}
///|
fn run_hot_pipeline_raw_ssa_nomerge_loop_backedge_decoration_supported_control(
instrs : Array[@lib.Instruction],
) -> Bool {
for instr in instrs {
match instr {
@lib.Br(_)
| @lib.BrIf(_)
| @lib.BrTable(_, _)
| @lib.BrOnNull(_)
| @lib.BrOnNonNull(_)
| @lib.BrOnCast(_, _, _, _)
| @lib.BrOnCastFail(_, _, _, _)
| @lib.BrOnCastDescEq(_, _, _)
| @lib.BrOnCastDescEqFail(_, _, _)
| @lib.Block(_, _)
| @lib.Loop(_, _)
| @lib.TryTable(_, _, _) => return false
@lib.If(_, then_body, else_body) => {
if !run_hot_pipeline_raw_ssa_nomerge_loop_backedge_decoration_supported_control(
then_body,
) {
return false
}
match else_body {
Some(body) =>
if !run_hot_pipeline_raw_ssa_nomerge_loop_backedge_decoration_supported_control(
body,
) {
return false
}
None => ()
}
}
_ => ()
}
}
true
}
///|
fn run_hot_pipeline_raw_ssa_nomerge_loop_backedge_body_supported_control(
instrs : Array[@lib.Instruction],
state : SsaNoMergeLoopBackedgeMergeControlScan,
) -> Bool {
for instr in instrs {
match instr {
@lib.Br(_)
| @lib.BrTable(_, _)
| @lib.BrOnNull(_)
| @lib.BrOnNonNull(_)
| @lib.BrOnCast(_, _, _, _)
| @lib.BrOnCastFail(_, _, _, _)
| @lib.BrOnCastDescEq(_, _, _)
| @lib.BrOnCastDescEqFail(_, _, _)
| @lib.Loop(_, _)
| @lib.TryTable(_, _, _) => return false
@lib.Block(_, @lib.Expr(body)) =>
if !run_hot_pipeline_raw_ssa_nomerge_loop_backedge_decoration_supported_control(
body,
) {
return false
}
@lib.BrIf(@lib.LabelIdx(raw)) =>
if raw == 0U {
state.saw_loop_backedge_br_if = true
} else {
return false
}
@lib.If(_, then_body, else_body) => {
if !run_hot_pipeline_raw_ssa_nomerge_loop_backedge_body_supported_control(
then_body, state,
) {
return false
}
match else_body {
Some(body) =>
if !run_hot_pipeline_raw_ssa_nomerge_loop_backedge_body_supported_control(
body, state,
) {
return false
}
None => ()
}
}
_ => ()
}
}
true
}
///|
fn run_hot_pipeline_raw_ssa_nomerge_scan_loop_backedge_merge_control(
instrs : Array[@lib.Instruction],
state : SsaNoMergeLoopBackedgeMergeControlScan,
) -> Bool {
for instr in instrs {
match instr {
@lib.Br(_)
| @lib.BrIf(_)
| @lib.BrTable(_, _)
| @lib.BrOnNull(_)
| @lib.BrOnNonNull(_)
| @lib.BrOnCast(_, _, _, _)
| @lib.BrOnCastFail(_, _, _, _)
| @lib.BrOnCastDescEq(_, _, _)
| @lib.BrOnCastDescEqFail(_, _, _)
| @lib.TryTable(_, _, _) => return false
@lib.Loop(@lib.VoidBlockType, @lib.Expr(body)) =>
if !run_hot_pipeline_raw_ssa_nomerge_loop_backedge_body_supported_control(
body, state,
) {
return false
}
@lib.Loop(_, _) => return false
@lib.Block(_, @lib.Expr(body)) =>
if !run_hot_pipeline_raw_ssa_nomerge_scan_loop_backedge_merge_control(
body, state,
) {
return false
}
@lib.If(_, then_body, else_body) => {
if !run_hot_pipeline_raw_ssa_nomerge_scan_loop_backedge_merge_control(
then_body, state,
) {
return false
}
match else_body {
Some(body) =>
if !run_hot_pipeline_raw_ssa_nomerge_scan_loop_backedge_merge_control(
body, state,
) {
return false
}
None => ()
}
}
_ => ()
}
}
true
}
///|
fn run_hot_pipeline_raw_ssa_nomerge_loop_backedge_merge_supported_control(
instrs : Array[@lib.Instruction],
) -> Bool {
let state = { saw_loop_backedge_br_if: false, }
run_hot_pipeline_raw_ssa_nomerge_scan_loop_backedge_merge_control(
instrs, state,
) &&
state.saw_loop_backedge_br_if
}
///|
fn run_hot_pipeline_raw_ssa_nomerge_is_void_block_type(
block_type : @lib.BlockType,
) -> Bool {
match block_type {
@lib.VoidBlockType => true
_ => false
}
}
///|
fn run_hot_pipeline_raw_ssa_nomerge_default_materialize_supported_control(
instrs : Array[@lib.Instruction],
) -> Bool {
for instr in instrs {
match instr {
@lib.BrOnNull(_)
| @lib.BrOnNonNull(_)
| @lib.BrOnCast(_, _, _, _)
| @lib.BrOnCastFail(_, _, _, _)
| @lib.BrOnCastDescEq(_, _, _)
| @lib.BrOnCastDescEqFail(_, _, _) => return false
@lib.Block(block_type, @lib.Expr(body)) => {
if !run_hot_pipeline_raw_ssa_nomerge_is_void_block_type(block_type) &&
run_hot_pipeline_raw_instrs_have_branch(body) {
return false
}
if !run_hot_pipeline_raw_ssa_nomerge_default_materialize_supported_control(
body,
) {
return false
}
}
@lib.Loop(block_type, @lib.Expr(body)) => {
if !run_hot_pipeline_raw_ssa_nomerge_is_void_block_type(block_type) &&
run_hot_pipeline_raw_instrs_have_branch(body) {
return false
}
if !run_hot_pipeline_raw_ssa_nomerge_default_materialize_supported_control(
body,
) {
return false
}
}
@lib.TryTable(_, _, @lib.Expr(body)) =>
if !run_hot_pipeline_raw_ssa_nomerge_default_materialize_supported_control(
body,
) {
return false
}
@lib.If(block_type, then_body, else_body) => {
let else_has_branch = match else_body {
Some(body) => run_hot_pipeline_raw_instrs_have_branch(body)
None => false
}
if !run_hot_pipeline_raw_ssa_nomerge_is_void_block_type(block_type) &&
(
run_hot_pipeline_raw_instrs_have_branch(then_body) ||
else_has_branch
) {
return false
}
if !run_hot_pipeline_raw_ssa_nomerge_default_materialize_supported_control(
then_body,
) {
return false
}
match else_body {
Some(body) =>
if !run_hot_pipeline_raw_ssa_nomerge_default_materialize_supported_control(
body,
) {
return false
}
None => ()
}
}
_ => ()
}
}
true
}
///|
fn run_hot_pipeline_raw_ssa_nomerge_should_try_default_materialization(
instrs : Array[@lib.Instruction],
param_count : Int32,
) -> Bool {
if !run_hot_pipeline_raw_ssa_nomerge_default_materialize_supported_control(
instrs,
) {
return false
}
run_hot_pipeline_raw_ssa_nomerge_has_body_local_get(instrs, param_count)
}
///|
fn run_hot_pipeline_raw_local_has_legal_default(
params : Array[@lib.ValType],
locals : @lib.Locals,
local_id : Int32,
) -> Bool {
if local_id < 0 || local_id >= params.length() + locals.length() {
return false
}
match run_hot_pipeline_raw_local_type(params, locals, local_id) {
@lib.NumTypeValType(_) | @lib.VecTypeValType => true
@lib.RefTypeValType(rt) => rt.is_nullable()
_ => false
}
}
///|
fn run_hot_pipeline_raw_ssa_nomerge_materialize_default_instr(
instr : @lib.Instruction,
params : Array[@lib.ValType],
locals : @lib.Locals,
plan : SsaNoMergeRewritePlan,
written_locals : @bitset.BitSet,
cursor : SsaNoMergeRawGetCursor,
) -> (@lib.Instruction, Bool) {
match instr {
@lib.LocalGet(@lib.LocalIdx(raw)) => {
let local_id = raw.reinterpret_as_int()
let decision = run_hot_pipeline_raw_ssa_nomerge_next_get_decision_for_local(
plan,
cursor.cursor,
local_id,
)
cursor.cursor = cursor.cursor + 1
match decision {
Some(SsaNoMergeGetDecision::MaterializeDefault) =>
if local_id >= 0 &&
local_id < written_locals.length() &&
written_locals[local_id] {
(instr, false)
} else if run_hot_pipeline_raw_local_has_legal_default(
params, locals, local_id,
) {
(
run_hot_pipeline_raw_default_instr_for_local(
params, locals, local_id,
),
true,
)
} else {
(instr, false)
}
_ => (instr, false)
}
}
@lib.LocalSet(@lib.LocalIdx(raw)) | @lib.LocalTee(@lib.LocalIdx(raw)) => {
let local_id = raw.reinterpret_as_int()
if local_id >= 0 && local_id < written_locals.length() {
written_locals[local_id] = true
}
(instr, false)
}
@lib.Block(block_type, @lib.Expr(body)) => {
let (rewritten_body, changed) = run_hot_pipeline_raw_ssa_nomerge_materialize_default_body(
body, params, locals, plan, written_locals, cursor,
)
(
@lib.Instruction::block(block_type, @lib.Expr::new(rewritten_body)),
changed,
)
}
@lib.Loop(block_type, @lib.Expr(body)) => {
let (rewritten_body, changed) = run_hot_pipeline_raw_ssa_nomerge_materialize_default_body(
body, params, locals, plan, written_locals, cursor,
)
(
@lib.Instruction::loop_(block_type, @lib.Expr::new(rewritten_body)),
changed,
)
}
@lib.If(block_type, then_body, else_body) => {
let (rewritten_then, then_changed) = run_hot_pipeline_raw_ssa_nomerge_materialize_default_body(
then_body, params, locals, plan, written_locals, cursor,
)
let mut changed = then_changed
let rewritten_else = match else_body {
Some(body) => {
let (next_body, else_changed) = run_hot_pipeline_raw_ssa_nomerge_materialize_default_body(
body, params, locals, plan, written_locals, cursor,
)
changed = changed || else_changed
Some(next_body)
}
None => None
}
(
@lib.Instruction::if_(block_type, rewritten_then, rewritten_else),
changed,
)
}
@lib.TryTable(block_type, catches, @lib.Expr(body)) => {
let (rewritten_body, changed) = run_hot_pipeline_raw_ssa_nomerge_materialize_default_body(
body, params, locals, plan, written_locals, cursor,
)
(
@lib.Instruction::try_table(
block_type,
catches,
@lib.Expr::new(rewritten_body),
),
changed,
)
}
_ => (instr, false)
}
}
///|
fn run_hot_pipeline_raw_ssa_nomerge_materialize_default_body(
instrs : Array[@lib.Instruction],
params : Array[@lib.ValType],
locals : @lib.Locals,
plan : SsaNoMergeRewritePlan,
written_locals : @bitset.BitSet,
cursor : SsaNoMergeRawGetCursor,
) -> (Array[@lib.Instruction], Bool) {
let out : Array[@lib.Instruction] = []
let mut changed = false
for instr in instrs {
let (rewritten, instr_changed) = run_hot_pipeline_raw_ssa_nomerge_materialize_default_instr(
instr, params, locals, plan, written_locals, cursor,
)
out.push(rewritten)
changed = changed || instr_changed
}
(out, changed)
}
///|
fn run_hot_pipeline_raw_ssa_nomerge_replaced_written_get_with_default(
original : Array[@lib.Instruction],
rewritten : Array[@lib.Instruction],
rewritten_locals : Array[Int32],
) -> Bool {
let aligned_length = if original.length() < rewritten.length() {
original.length()
} else {
rewritten.length()
}
for idx = 0; idx < aligned_length; idx = idx + 1 {
match original[idx] {
@lib.LocalGet(@lib.LocalIdx(raw)) => {
let local_id = raw.reinterpret_as_int()
if local_id >= 0 &&
local_id < rewritten_locals.length() &&
rewritten_locals[local_id] >= 0 {
match rewritten[idx] {
@lib.LocalGet(@lib.LocalIdx(rewritten_raw)) => {
let rewritten_local = rewritten_raw.reinterpret_as_int()
if rewritten_local != rewritten_locals[local_id] &&
rewritten_local >= 0 &&
rewritten_local < rewritten_locals.length() &&
rewritten_locals[rewritten_local] < 0 {
return true
}
}
_ => return true
}
}
}
@lib.LocalSet(@lib.LocalIdx(raw)) | @lib.LocalTee(@lib.LocalIdx(raw)) => {
let local_id = raw.reinterpret_as_int()
if local_id >= 0 && local_id < rewritten_locals.length() {
let rewritten_local = match rewritten[idx] {
@lib.LocalSet(@lib.LocalIdx(rewritten_raw))
| @lib.LocalTee(@lib.LocalIdx(rewritten_raw)) =>
rewritten_raw.reinterpret_as_int()
_ => return true
}
rewritten_locals[local_id] = rewritten_local
}
}
@lib.Block(_, @lib.Expr(original_body)) =>
match rewritten[idx] {
@lib.Block(_, @lib.Expr(rewritten_body)) =>
if run_hot_pipeline_raw_ssa_nomerge_replaced_written_get_with_default(
original_body,
rewritten_body,
rewritten_locals.copy(),
) {
return true
}
_ => ()
}
@lib.Loop(_, @lib.Expr(original_body)) =>
match rewritten[idx] {
@lib.Loop(_, @lib.Expr(rewritten_body)) =>
if run_hot_pipeline_raw_ssa_nomerge_replaced_written_get_with_default(
original_body,
rewritten_body,
rewritten_locals.copy(),
) {
return true
}
_ => ()
}
@lib.TryTable(_, _, @lib.Expr(original_body)) =>
match rewritten[idx] {
@lib.TryTable(_, _, @lib.Expr(rewritten_body)) =>
if run_hot_pipeline_raw_ssa_nomerge_replaced_written_get_with_default(
original_body,
rewritten_body,
rewritten_locals.copy(),
) {
return true
}
_ => ()
}
@lib.If(_, original_then, original_else) =>
match rewritten[idx] {
@lib.If(_, rewritten_then, rewritten_else) => {
if run_hot_pipeline_raw_ssa_nomerge_replaced_written_get_with_default(
original_then,
rewritten_then,
rewritten_locals.copy(),
) {
return true
}
match (original_else, rewritten_else) {
(Some(original_body), Some(rewritten_body)) =>
if run_hot_pipeline_raw_ssa_nomerge_replaced_written_get_with_default(
original_body,
rewritten_body,
rewritten_locals.copy(),
) {
return true
}
_ => ()
}
}
_ => ()
}
_ => ()
}
}
false
}
///|
fn run_hot_pipeline_raw_ssa_nomerge_materialize_all_unwritten_defaults_instr(
instr : @lib.Instruction,
params : Array[@lib.ValType],
locals : @lib.Locals,
written_locals : @bitset.BitSet,
changed : SsaNoMergeRawRewriteCursor,
) -> @lib.Instruction {
match instr {
@lib.LocalGet(@lib.LocalIdx(raw)) => {
let local_id = raw.reinterpret_as_int()
if local_id >= params.length() &&
local_id >= 0 &&
local_id < written_locals.length() &&
!written_locals[local_id] &&
run_hot_pipeline_raw_local_has_legal_default(params, locals, local_id) {
changed.changed = true
run_hot_pipeline_raw_default_instr_for_local(params, locals, local_id)
} else {
instr
}
}
@lib.LocalSet(@lib.LocalIdx(raw)) | @lib.LocalTee(@lib.LocalIdx(raw)) => {
let local_id = raw.reinterpret_as_int()
if local_id >= 0 && local_id < written_locals.length() {
written_locals[local_id] = true
}
instr
}
@lib.Block(block_type, @lib.Expr(body)) =>
@lib.Instruction::block(
block_type,
@lib.Expr::new(
run_hot_pipeline_raw_ssa_nomerge_materialize_all_unwritten_defaults_body(
body, params, locals, written_locals, changed,
),
),
)
@lib.Loop(block_type, @lib.Expr(body)) => {
run_hot_pipeline_raw_ssa_nomerge_mark_written_locals(body, written_locals)
@lib.Instruction::loop_(block_type, @lib.Expr::new(body))
}
@lib.If(block_type, then_body, else_body) => {
let rewritten_then = run_hot_pipeline_raw_ssa_nomerge_materialize_all_unwritten_defaults_body(
then_body, params, locals, written_locals, changed,
)
let rewritten_else = match else_body {
Some(body) =>
Some(
run_hot_pipeline_raw_ssa_nomerge_materialize_all_unwritten_defaults_body(
body, params, locals, written_locals, changed,
),
)
None => None
}
@lib.Instruction::if_(block_type, rewritten_then, rewritten_else)
}
@lib.Try(block_type, @lib.Expr(body), catches, delegate) => {
let rewritten_body = run_hot_pipeline_raw_ssa_nomerge_materialize_all_unwritten_defaults_body(
body, params, locals, written_locals, changed,
)
let rewritten_catches = catches.map(fn(catch_) {
match catch_ {
@lib.LegacyCatch(tag, @lib.Expr(catch_body)) =>
@lib.LegacyCatch::tagged(
tag,
@lib.Expr::new(
run_hot_pipeline_raw_ssa_nomerge_materialize_all_unwritten_defaults_body(
catch_body, params, locals, written_locals, changed,
),
),
)
@lib.LegacyCatchAll(@lib.Expr(catch_body)) =>
@lib.LegacyCatch::all(
@lib.Expr::new(
run_hot_pipeline_raw_ssa_nomerge_materialize_all_unwritten_defaults_body(
catch_body, params, locals, written_locals, changed,
),
),
)
}
})
@lib.Instruction::try_(
block_type,
@lib.Expr::new(rewritten_body),
rewritten_catches,
delegate~,
)
}
@lib.TryTable(block_type, catches, @lib.Expr(body)) =>
@lib.Instruction::try_table(
block_type,
catches,
@lib.Expr::new(
run_hot_pipeline_raw_ssa_nomerge_materialize_all_unwritten_defaults_body(
body, params, locals, written_locals, changed,
),
),
)
_ => instr
}
}
///|
fn run_hot_pipeline_raw_ssa_nomerge_materialize_all_unwritten_defaults_body(
instrs : Array[@lib.Instruction],
params : Array[@lib.ValType],
locals : @lib.Locals,
written_locals : @bitset.BitSet,
changed : SsaNoMergeRawRewriteCursor,
) -> Array[@lib.Instruction] {
let out : Array[@lib.Instruction] = []
for instr in instrs {
out.push(
run_hot_pipeline_raw_ssa_nomerge_materialize_all_unwritten_defaults_instr(
instr, params, locals, written_locals, changed,
),
)
}
out
}
///|
fn run_hot_pipeline_raw_ssa_nomerge_materialize_all_unwritten_defaults_func(
func : @lib.Func,
params : Array[@lib.ValType],
) -> (@lib.Func, Bool) {
let @lib.Func(locals, @lib.Expr(instrs)) = func
let changed = { get_cursor: 0, write_cursor: 0, changed: false, }
let written_locals = @bitset.BitSet::new(params.length() + locals.length())
let rewritten_instrs = run_hot_pipeline_raw_ssa_nomerge_materialize_all_unwritten_defaults_body(
instrs, params, locals, written_locals, changed,
)
if !changed.changed {
(func, false)
} else {
let rewritten = @lib.Func::new(
locals.copy(),
@lib.Expr::new(rewritten_instrs),
)
let (expanded, _) = run_hot_pipeline_raw_ssa_nomerge_expand_v128_defaults_from_gets_func(
func, rewritten,
)
(expanded, true)
}
}
///|
fn run_hot_pipeline_raw_ssa_nomerge_block_result_count(
module_ctx : @ir.HotModuleContext,
block_type : @lib.BlockType,
) -> Int32 {
match @ir.hot_module_resolve_block_results(module_ctx, block_type) {
Some(results) => results.length()
None => 0
}
}
///|
fn run_hot_pipeline_raw_ssa_nomerge_block_param_count(
module_ctx : @ir.HotModuleContext,
block_type : @lib.BlockType,
) -> Int32 {
match block_type {
@lib.TypeIdxBlockType(type_idx) =>
match @ir.hot_module_resolve_type_func_type(module_ctx, type_idx) {
Some(@lib.FuncType(params, _)) => params.length()
None => 0
}
_ => 0
}
}
///|
fn run_hot_pipeline_raw_ssa_nomerge_instr_result_count(
instr : @lib.Instruction,
module_ctx : @ir.HotModuleContext,
) -> Int32 {
match instr {
@lib.Call(func_idx) =>
match @ir.hot_module_resolve_func_type(module_ctx, func_idx) {
Some(@lib.FuncType(_, results)) => results.length()
None => 0
}
@lib.Block(block_type, _)
| @lib.Loop(block_type, _)
| @lib.TryTable(block_type, _, _) =>
run_hot_pipeline_raw_ssa_nomerge_block_result_count(
module_ctx, block_type,
)
@lib.F32Const(_) | @lib.F64Const(_) | @lib.I32Const(_) | @lib.I64Const(_) =>
1
@lib.V128Const(_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _) => 1
@lib.F64Sqrt | @lib.I32x4Splat => 1
_ => 0
}
}
///|
fn run_hot_pipeline_raw_ssa_nomerge_push_drops(
out : Array[@lib.Instruction],
count : Int32,
) -> Unit {
let mut idx = 0
while idx < count {
out.push(@lib.Instruction::drop())
idx += 1
}
}
///|
fn run_hot_pipeline_raw_ssa_nomerge_tail_only_nop_unreachable(
instrs : Array[@lib.Instruction],
start : Int32,
) -> Bool {
let mut saw_unreachable = false
for idx = start; idx < instrs.length(); idx = idx + 1 {
match instrs[idx] {
@lib.Nop => ()
@lib.Unreachable => saw_unreachable = true
_ => return false
}
}
saw_unreachable
}
///|
fn run_hot_pipeline_raw_ssa_nomerge_body_has_unreachable(
instrs : Array[@lib.Instruction],
) -> Bool {
for instr in instrs {
match instr {
@lib.Unreachable => return true
@lib.Block(_, @lib.Expr(body))
| @lib.Loop(_, @lib.Expr(body))
| @lib.TryTable(_, _, @lib.Expr(body)) =>
if run_hot_pipeline_raw_ssa_nomerge_body_has_unreachable(body) {
return true
}
@lib.If(_, then_body, else_body) => {
if run_hot_pipeline_raw_ssa_nomerge_body_has_unreachable(then_body) {
return true
}
match else_body {
Some(body) =>
if run_hot_pipeline_raw_ssa_nomerge_body_has_unreachable(body) {
return true
}
None => ()
}
}
_ => ()
}
}
false
}
///|
fn run_hot_pipeline_raw_ssa_nomerge_loop_is_result_unreachable_debris(
instr : @lib.Instruction,
module_ctx : @ir.HotModuleContext,
) -> Bool {
match instr {
@lib.Loop(block_type, @lib.Expr(body)) =>
run_hot_pipeline_raw_ssa_nomerge_block_param_count(module_ctx, block_type) ==
0 &&
run_hot_pipeline_raw_ssa_nomerge_block_result_count(
module_ctx, block_type,
) >
0 &&
run_hot_pipeline_raw_ssa_nomerge_body_has_unreachable(body)
_ => false
}
}
///|
fn run_hot_pipeline_raw_ssa_nomerge_tail_scalar_debris_then_unreachable(
instrs : Array[@lib.Instruction],
start : Int32,
module_ctx : @ir.HotModuleContext,
) -> Bool {
let mut saw_unreachable = false
for idx = start; idx < instrs.length(); idx = idx + 1 {
match instrs[idx] {
@lib.Nop => ()
@lib.Unreachable => saw_unreachable = true
@lib.F32Const(_)
| @lib.F64Const(_)
| @lib.I32Const(_)
| @lib.I64Const(_)
| @lib.V128Const(_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _) => ()
@lib.F64Sqrt | @lib.I32x4Splat => ()
instr if run_hot_pipeline_raw_ssa_nomerge_loop_is_result_unreachable_debris(
instr, module_ctx,
) => ()
_ => return false
}
}
saw_unreachable
}
///|
fn run_hot_pipeline_raw_ssa_nomerge_const_should_drop_before_tail(
instrs : Array[@lib.Instruction],
next : Int32,
module_ctx : @ir.HotModuleContext,
) -> Bool {
if run_hot_pipeline_raw_ssa_nomerge_tail_only_nop_unreachable(instrs, next) {
return true
}
let mut idx = next
while idx < instrs.length() {
match instrs[idx] {
@lib.Nop => idx += 1
@lib.V128Const(_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _) =>
return run_hot_pipeline_raw_ssa_nomerge_tail_only_nop_unreachable(
instrs,
idx + 1,
)
instr if run_hot_pipeline_raw_ssa_nomerge_loop_is_result_unreachable_debris(
instr, module_ctx,
) =>
return run_hot_pipeline_raw_ssa_nomerge_tail_only_nop_unreachable(
instrs,
idx + 1,
)
_ => return false
}
}
false
}
///|
fn run_hot_pipeline_raw_ssa_nomerge_drop_dead_stack_before_unreachable_body(
instrs : Array[@lib.Instruction],
module_ctx : @ir.HotModuleContext,
) -> (Array[@lib.Instruction], Bool) {
let out : Array[@lib.Instruction] = []
let mut changed = false
for idx = 0; idx < instrs.length(); idx = idx + 1 {
let (rewritten, instr_changed) = match instrs[idx] {
@lib.Block(block_type, @lib.Expr(body)) => {
let (next_body, body_changed) = run_hot_pipeline_raw_ssa_nomerge_drop_dead_stack_before_unreachable_body(
body, module_ctx,
)
(
@lib.Instruction::block(block_type, @lib.Expr::new(next_body)),
body_changed,
)
}
@lib.Loop(block_type, @lib.Expr(body)) => {
let (next_body, body_changed) = run_hot_pipeline_raw_ssa_nomerge_drop_dead_stack_before_unreachable_body(
body, module_ctx,
)
(
@lib.Instruction::loop_(block_type, @lib.Expr::new(next_body)),
body_changed,
)
}
@lib.TryTable(block_type, catches, @lib.Expr(body)) => {
let (next_body, body_changed) = run_hot_pipeline_raw_ssa_nomerge_drop_dead_stack_before_unreachable_body(
body, module_ctx,
)
(
@lib.Instruction::try_table(
block_type,
catches,
@lib.Expr::new(next_body),
),
body_changed,
)
}
@lib.If(block_type, then_body, else_body) => {
let (next_then, then_changed) = run_hot_pipeline_raw_ssa_nomerge_drop_dead_stack_before_unreachable_body(
then_body, module_ctx,
)
let (next_else, else_changed) = match else_body {
Some(body) => {
let (body2, changed2) = run_hot_pipeline_raw_ssa_nomerge_drop_dead_stack_before_unreachable_body(
body, module_ctx,
)
(Some(body2), changed2)
}
None => (None, false)
}
(
@lib.Instruction::if_(block_type, next_then, next_else),
then_changed || else_changed,
)
}
other => (other, false)
}
out.push(rewritten)
changed = changed || instr_changed
let result_count = run_hot_pipeline_raw_ssa_nomerge_instr_result_count(
rewritten, module_ctx,
)
if result_count <= 0 {
continue
}
let should_drop = match rewritten {
@lib.Call(_) =>
result_count > 1 &&
run_hot_pipeline_raw_ssa_nomerge_tail_scalar_debris_then_unreachable(
instrs,
idx + 1,
module_ctx,
)
@lib.Block(_, _) | @lib.Loop(_, _) | @lib.TryTable(_, _, _) =>
run_hot_pipeline_raw_ssa_nomerge_tail_only_nop_unreachable(
instrs,
idx + 1,
)
@lib.F64Sqrt | @lib.I32x4Splat =>
run_hot_pipeline_raw_ssa_nomerge_tail_only_nop_unreachable(
instrs,
idx + 1,
)
@lib.V128Const(_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _) =>
run_hot_pipeline_raw_ssa_nomerge_tail_only_nop_unreachable(
instrs,
idx + 1,
)
@lib.F32Const(_)
| @lib.F64Const(_)
| @lib.I32Const(_)
| @lib.I64Const(_) =>
run_hot_pipeline_raw_ssa_nomerge_const_should_drop_before_tail(
instrs,
idx + 1,
module_ctx,
)
_ => false
}
if should_drop {
run_hot_pipeline_raw_ssa_nomerge_push_drops(out, result_count)
changed = true
}
}
(out, changed)
}
///|
fn run_hot_pipeline_raw_ssa_nomerge_drop_dead_stack_before_unreachable_func(
func : @lib.Func,
module_ctx : @ir.HotModuleContext,
) -> (@lib.Func, Bool) {
let @lib.Func(locals, @lib.Expr(instrs)) = func
let (rewritten_instrs, changed) = run_hot_pipeline_raw_ssa_nomerge_drop_dead_stack_before_unreachable_body(
instrs, module_ctx,
)
if !changed {
(func, false)
} else {
(@lib.Func::new(locals.copy(), @lib.Expr::new(rewritten_instrs)), true)
}
}
///|
fn run_hot_pipeline_raw_ssa_nomerge_instr_is_nop(
instr : @lib.Instruction,
) -> Bool {
match instr {
@lib.Nop => true
_ => false
}
}
///|
fn run_hot_pipeline_raw_ssa_nomerge_instr_is_unreachable(
instr : @lib.Instruction,
) -> Bool {
match instr {
@lib.Unreachable => true
_ => false
}
}
///|
fn run_hot_pipeline_raw_ssa_nomerge_body_is_unreachable_only(
instrs : Array[@lib.Instruction],
) -> Bool {
if instrs.length() == 0 {
return false
}
let mut saw_unreachable = false
for instr in instrs {
match instr {
@lib.Nop => ()
@lib.Unreachable => saw_unreachable = true
_ => return false
}
}
saw_unreachable
}
///|
fn run_hot_pipeline_raw_ssa_nomerge_local_get_type_matches(
instr : @lib.Instruction,
params : Array[@lib.ValType],
locals : @lib.Locals,
expected : @lib.ValType,
) -> Bool {
match instr {
@lib.LocalGet(@lib.LocalIdx(raw)) => {
let local_id = raw.reinterpret_as_int()
if local_id < 0 || local_id >= params.length() + locals.length() {
return false
}
run_hot_pipeline_raw_local_type(params, locals, local_id) == expected
}
_ => false
}
}
///|
fn run_hot_pipeline_raw_ssa_nomerge_eh_debris_body_has_dead_i16x8_extract(
instrs : Array[@lib.Instruction],
) -> Bool {
let mut saw_branch = false
let mut saw_extract = false
let mut saw_unreachable = false
for instr in instrs {
if !saw_branch {
match instr {
@lib.Br(_) => saw_branch = true
@lib.Nop => ()
_ => ()
}
continue
}
match instr {
@lib.I16x8ExtractLaneU(_) => saw_extract = true
@lib.Unreachable => saw_unreachable = true
_ => ()
}
}
saw_branch && saw_extract && saw_unreachable
}
///|
fn run_hot_pipeline_raw_ssa_nomerge_eh_debris_needs_i32_local(
instrs : Array[@lib.Instruction],
) -> Bool {
for instr in instrs {
match instr {
@lib.TryTable(_, _, @lib.Expr(body)) =>
if run_hot_pipeline_raw_ssa_nomerge_eh_debris_body_has_dead_i16x8_extract(
body,
) {
return true
} else if run_hot_pipeline_raw_ssa_nomerge_eh_debris_needs_i32_local(
body,
) {
return true
}
@lib.Block(_, @lib.Expr(body)) | @lib.Loop(_, @lib.Expr(body)) =>
if run_hot_pipeline_raw_ssa_nomerge_eh_debris_needs_i32_local(body) {
return true
}
@lib.If(_, then_body, else_body) => {
if run_hot_pipeline_raw_ssa_nomerge_eh_debris_needs_i32_local(then_body) {
return true
}
match else_body {
Some(body) =>
if run_hot_pipeline_raw_ssa_nomerge_eh_debris_needs_i32_local(body) {
return true
}
None => ()
}
}
_ => ()
}
}
false
}
///|
fn run_hot_pipeline_raw_ssa_nomerge_preserve_eh_unreachable_debris_local_func(
func : @lib.Func,
) -> (@lib.Func, Bool) {
let @lib.Func(locals, @lib.Expr(instrs)) = func
if !run_hot_pipeline_raw_ssa_nomerge_eh_debris_needs_i32_local(instrs) {
return (func, false)
}
let rewritten_locals = locals.copy()
rewritten_locals.append_types([@lib.ValType::i32()])
(@lib.Func::new(rewritten_locals, @lib.Expr::new(instrs.copy())), true)
}
///|
fn run_hot_pipeline_raw_ssa_nomerge_spill_no_write_typed_unreachable_blocks_body(
instrs : Array[@lib.Instruction],
module_ctx : @ir.HotModuleContext,
params : Array[@lib.ValType],
locals : @lib.Locals,
extra_local_types : Array[@lib.ValType],
) -> (Array[@lib.Instruction], Bool) {
let out : Array[@lib.Instruction] = []
let base_local_count = params.length() + locals.length()
let mut changed = false
let mut idx = 0
while idx < instrs.length() {
match instrs[idx] {
@lib.Block(@lib.TypeIdxBlockType(type_idx), @lib.Expr(body)) if idx + 1 <
instrs.length() =>
match @ir.hot_module_resolve_type_func_type(module_ctx, type_idx) {
Some(@lib.FuncType(block_params, block_results)) =>
if block_params.length() == 1 &&
block_results.length() == 1 &&
block_params[0] == block_results[0] &&
run_hot_pipeline_raw_ssa_nomerge_body_is_unreachable_only(body) &&
run_hot_pipeline_raw_ssa_nomerge_instr_is_unreachable(
instrs[idx + 1],
) {
let mut insert_at = out.length()
while insert_at > 0 &&
run_hot_pipeline_raw_ssa_nomerge_instr_is_nop(
out[insert_at - 1],
) {
insert_at = insert_at - 1
}
if insert_at > 0 &&
run_hot_pipeline_raw_ssa_nomerge_local_get_type_matches(
out[insert_at - 1],
params,
locals,
block_params[0],
) {
let result_local = base_local_count + extra_local_types.length()
extra_local_types.push(block_results[0])
let param_local = base_local_count + extra_local_types.length()
extra_local_types.push(block_params[0])
out.insert(
insert_at,
@lib.Instruction::local_set(
@lib.LocalIdx::new(param_local.reinterpret_as_uint()),
),
)
out.push(
@lib.Instruction::local_get(
@lib.LocalIdx::new(param_local.reinterpret_as_uint()),
),
)
out.push(
@lib.Instruction::local_set(
@lib.LocalIdx::new(result_local.reinterpret_as_uint()),
),
)
out.push(
@lib.Instruction::local_get(
@lib.LocalIdx::new(result_local.reinterpret_as_uint()),
),
)
out.push(@lib.Instruction::drop())
out.push(@lib.Instruction::unreachable_())
changed = true
idx = idx + 2
continue
}
}
None => ()
}
@lib.Block(block_type, @lib.Expr(body)) => {
let (rewritten_body, body_changed) = run_hot_pipeline_raw_ssa_nomerge_spill_no_write_typed_unreachable_blocks_body(
body, module_ctx, params, locals, extra_local_types,
)
if body_changed {
out.push(
@lib.Instruction::block(block_type, @lib.Expr::new(rewritten_body)),
)
changed = true
idx = idx + 1
continue
}
}
@lib.Loop(block_type, @lib.Expr(body)) => {
let (rewritten_body, body_changed) = run_hot_pipeline_raw_ssa_nomerge_spill_no_write_typed_unreachable_blocks_body(
body, module_ctx, params, locals, extra_local_types,
)
if body_changed {
out.push(
@lib.Instruction::loop_(block_type, @lib.Expr::new(rewritten_body)),
)
changed = true
idx = idx + 1
continue
}
}
@lib.If(block_type, then_body, else_body) => {
let (rewritten_then, then_changed) = run_hot_pipeline_raw_ssa_nomerge_spill_no_write_typed_unreachable_blocks_body(
then_body, module_ctx, params, locals, extra_local_types,
)
let mut instr_changed = then_changed
let rewritten_else = match else_body {
Some(body) => {
let (next_body, else_changed) = run_hot_pipeline_raw_ssa_nomerge_spill_no_write_typed_unreachable_blocks_body(
body, module_ctx, params, locals, extra_local_types,
)
instr_changed = instr_changed || else_changed
Some(next_body)
}
None => None
}
if instr_changed {
out.push(
@lib.Instruction::if_(block_type, rewritten_then, rewritten_else),
)
changed = true
idx = idx + 1
continue
}
}
@lib.TryTable(block_type, catches, @lib.Expr(body)) => {
let (rewritten_body, body_changed) = run_hot_pipeline_raw_ssa_nomerge_spill_no_write_typed_unreachable_blocks_body(
body, module_ctx, params, locals, extra_local_types,
)
if body_changed {
out.push(
@lib.Instruction::try_table(
block_type,
catches,
@lib.Expr::new(rewritten_body),
),
)
changed = true
idx = idx + 1
continue
}
}
_ => ()
}
out.push(instrs[idx])
idx = idx + 1
}
(out, changed)
}
///|
fn run_hot_pipeline_raw_ssa_nomerge_spill_no_write_typed_unreachable_blocks_func(
func : @lib.Func,
module_ctx : @ir.HotModuleContext,
params : Array[@lib.ValType],
) -> (@lib.Func, Bool) {
let @lib.Func(locals, @lib.Expr(instrs)) = func
let extra_local_types : Array[@lib.ValType] = []
let (rewritten_instrs, changed) = run_hot_pipeline_raw_ssa_nomerge_spill_no_write_typed_unreachable_blocks_body(
instrs, module_ctx, params, locals, extra_local_types,
)
if !changed {
(func, false)
} else {
let rewritten_locals = locals.copy()
rewritten_locals.append_types(extra_local_types)
(@lib.Func::new(rewritten_locals, @lib.Expr::new(rewritten_instrs)), true)
}
}
///|
fn run_hot_pipeline_raw_ssa_nomerge_materialize_default_func(
func : @lib.Func,
params : Array[@lib.ValType],
plan : SsaNoMergeRewritePlan,
) -> (@lib.Func, Bool) {
if !run_hot_pipeline_raw_ssa_nomerge_plan_has_defaults(plan) {
return (func, false)
}
let @lib.Func(locals, @lib.Expr(instrs)) = func
let written_locals = @bitset.BitSet::new(params.length() + locals.length())
let (rewritten_instrs, changed) = run_hot_pipeline_raw_ssa_nomerge_materialize_default_body(
instrs,
params,
locals,
plan,
written_locals,
{ cursor: 0, },
)
if !changed {
(func, false)
} else {
let rewritten = @lib.Func::new(
locals.copy(),
@lib.Expr::new(rewritten_instrs),
)
let (expanded, _) = run_hot_pipeline_raw_ssa_nomerge_expand_v128_defaults_from_gets_func(
func, rewritten,
)
(expanded, true)
}
}
///|
fn run_hot_pipeline_raw_ssa_nomerge_child_in_branching_value_block(
block_type : @lib.BlockType,
body : Array[@lib.Instruction],
is_loop : Bool,
parent_in_branching_value_block : Bool,
) -> Bool {
parent_in_branching_value_block ||
(
!is_loop &&
!run_hot_pipeline_raw_ssa_nomerge_is_void_block_type(block_type) &&
run_hot_pipeline_raw_has_plain_br(body)
)
}
///|
fn run_hot_pipeline_raw_ssa_nomerge_has_branching_value_block(
instrs : Array[@lib.Instruction],
) -> Bool {
for instr in instrs {
match instr {
@lib.Block(block_type, @lib.Expr(body)) =>
if run_hot_pipeline_raw_ssa_nomerge_child_in_branching_value_block(
block_type, body, false, false,
) ||
run_hot_pipeline_raw_ssa_nomerge_has_branching_value_block(body) {
return true
}
@lib.Loop(_, @lib.Expr(body)) =>
if run_hot_pipeline_raw_ssa_nomerge_has_branching_value_block(body) {
return true
}
@lib.TryTable(block_type, _, @lib.Expr(body)) =>
if run_hot_pipeline_raw_ssa_nomerge_child_in_branching_value_block(
block_type, body, false, false,
) ||
run_hot_pipeline_raw_ssa_nomerge_has_branching_value_block(body) {
return true
}
@lib.If(block_type, then_body, else_body) => {
if run_hot_pipeline_raw_ssa_nomerge_child_in_branching_value_block(
block_type, then_body, false, false,
) ||
run_hot_pipeline_raw_ssa_nomerge_has_branching_value_block(then_body) {
return true
}
match else_body {
Some(body) =>
if run_hot_pipeline_raw_ssa_nomerge_child_in_branching_value_block(
block_type, body, false, false,
) ||
run_hot_pipeline_raw_ssa_nomerge_has_branching_value_block(body) {
return true
}
None => ()
}
}
_ => ()
}
}
false
}
///|
fn run_hot_pipeline_raw_ssa_nomerge_rewrite_planned_structured_instr(
instr : @lib.Instruction,
params : Array[@lib.ValType],
locals : @lib.Locals,
plan : SsaNoMergeRewritePlan,
written_locals : @bitset.BitSet,
cursor : SsaNoMergeRawRewriteCursor,
extra_local_types : Array[@lib.ValType],
base_local_count : Int32,
emitted_fresh_locals : Array[Bool],
in_branching_value_block : Bool,
force_keep_write : Bool,
) -> @lib.Instruction {
match instr {
@lib.LocalGet(@lib.LocalIdx(raw)) => {
let original_local = raw.reinterpret_as_int()
let rewritten = match
run_hot_pipeline_raw_ssa_nomerge_next_get_decision_for_local(
plan,
cursor.get_cursor,
original_local,
) {
Some(SsaNoMergeGetDecision::Retarget(fresh_local_id)) => {
// LocalGraph identifies the reaching write across enclosing exits.
// Track whether that write was actually freshened, not the last
// alias visited while walking the structured instruction tree.
let fresh_index = fresh_local_id - base_local_count
if fresh_index >= 0 &&
fresh_index < emitted_fresh_locals.length() &&
emitted_fresh_locals[fresh_index] {
cursor.changed = true
@lib.Instruction::local_get(
@lib.LocalIdx::new(fresh_local_id.reinterpret_as_uint()),
)
} else {
instr
}
}
Some(SsaNoMergeGetDecision::MaterializeDefault) =>
if original_local >= 0 &&
original_local < written_locals.length() &&
written_locals[original_local] {
instr
} else {
cursor.changed = true
run_hot_pipeline_raw_default_instr_for_local(
params, locals, original_local,
)
}
Some(SsaNoMergeGetDecision::KeepCanonical) | None => instr
}
cursor.get_cursor = cursor.get_cursor + 1
rewritten
}
@lib.LocalSet(@lib.LocalIdx(raw)) => {
let original_local = raw.reinterpret_as_int()
let rewritten = match
run_hot_pipeline_raw_ssa_nomerge_next_set_decision(
plan,
cursor.write_cursor,
) {
Some(SsaNoMergeWriteDecision::Freshen(fresh_local_id)) =>
if force_keep_write {
instr
} else if in_branching_value_block {
run_hot_pipeline_raw_ssa_nomerge_append_planned_fresh_local(
extra_local_types, params, locals, base_local_count, original_local,
fresh_local_id,
)
cursor.changed = true
instr
} else {
run_hot_pipeline_raw_ssa_nomerge_append_planned_fresh_local(
extra_local_types, params, locals, base_local_count, original_local,
fresh_local_id,
)
emitted_fresh_locals[fresh_local_id - base_local_count] = true
cursor.changed = true
@lib.Instruction::local_set(
@lib.LocalIdx::new(fresh_local_id.reinterpret_as_uint()),
)
}
Some(SsaNoMergeWriteDecision::KeepCanonical) => instr
None => instr
}
cursor.write_cursor = cursor.write_cursor + 1
rewritten
}
@lib.LocalTee(@lib.LocalIdx(raw)) => {
let original_local = raw.reinterpret_as_int()
let rewritten = match
run_hot_pipeline_raw_ssa_nomerge_next_set_decision(
plan,
cursor.write_cursor,
) {
Some(SsaNoMergeWriteDecision::Freshen(fresh_local_id)) =>
if force_keep_write {
instr
} else if in_branching_value_block {
run_hot_pipeline_raw_ssa_nomerge_append_planned_fresh_local(
extra_local_types, params, locals, base_local_count, original_local,
fresh_local_id,
)
cursor.changed = true
instr
} else {
run_hot_pipeline_raw_ssa_nomerge_append_planned_fresh_local(
extra_local_types, params, locals, base_local_count, original_local,
fresh_local_id,
)
emitted_fresh_locals[fresh_local_id - base_local_count] = true
cursor.changed = true
@lib.Instruction::local_tee(
@lib.LocalIdx::new(fresh_local_id.reinterpret_as_uint()),
)
}
Some(SsaNoMergeWriteDecision::KeepCanonical) => instr
None => instr
}
cursor.write_cursor = cursor.write_cursor + 1
rewritten
}
@lib.Block(block_type, @lib.Expr(body)) =>
@lib.Instruction::block(
block_type,
@lib.Expr::new(
run_hot_pipeline_raw_ssa_nomerge_rewrite_planned_structured_body(
body,
params,
locals,
plan,
written_locals,
cursor,
extra_local_types,
base_local_count,
emitted_fresh_locals,
run_hot_pipeline_raw_ssa_nomerge_child_in_branching_value_block(
block_type, body, false, in_branching_value_block,
),
),
),
)
@lib.If(block_type, then_body, else_body) => {
let rewritten_then = run_hot_pipeline_raw_ssa_nomerge_rewrite_planned_structured_body(
then_body,
params,
locals,
plan,
written_locals,
cursor,
extra_local_types,
base_local_count,
emitted_fresh_locals,
run_hot_pipeline_raw_ssa_nomerge_child_in_branching_value_block(
block_type, then_body, false, in_branching_value_block,
),
)
let rewritten_else = match else_body {
Some(body) =>
Some(
run_hot_pipeline_raw_ssa_nomerge_rewrite_planned_structured_body(
body,
params,
locals,
plan,
written_locals,
cursor,
extra_local_types,
base_local_count,
emitted_fresh_locals,
run_hot_pipeline_raw_ssa_nomerge_child_in_branching_value_block(
block_type, body, false, in_branching_value_block,
),
),
)
None => None
}
@lib.Instruction::if_(block_type, rewritten_then, rewritten_else)
}
@lib.Loop(block_type, @lib.Expr(body)) =>
@lib.Instruction::loop_(
block_type,
@lib.Expr::new(
run_hot_pipeline_raw_ssa_nomerge_rewrite_planned_structured_body(
body,
params,
locals,
plan,
written_locals,
cursor,
extra_local_types,
base_local_count,
emitted_fresh_locals,
run_hot_pipeline_raw_ssa_nomerge_child_in_branching_value_block(
block_type, body, true, in_branching_value_block,
),
),
),
)
@lib.TryTable(block_type, catches, @lib.Expr(body)) =>
@lib.Instruction::try_table(
block_type,
catches,
@lib.Expr::new(
run_hot_pipeline_raw_ssa_nomerge_rewrite_planned_structured_body(
body,
params,
locals,
plan,
written_locals,
cursor,
extra_local_types,
base_local_count,
emitted_fresh_locals,
run_hot_pipeline_raw_ssa_nomerge_child_in_branching_value_block(
block_type, body, false, in_branching_value_block,
),
),
),
)
@lib.Try(_, @lib.Expr(body), catches, _) => {
run_hot_pipeline_raw_ssa_nomerge_consume_legacy_try_plan(
body, catches, cursor,
)
instr
}
_ => instr
}
}
///|
fn run_hot_pipeline_raw_ssa_nomerge_consume_plan_instrs(
instrs : Array[@lib.Instruction],
cursor : SsaNoMergeRawRewriteCursor,
) -> Unit {
for instr in instrs {
match instr {
@lib.LocalGet(_) => cursor.get_cursor = cursor.get_cursor + 1
@lib.LocalSet(_) | @lib.LocalTee(_) =>
cursor.write_cursor = cursor.write_cursor + 1
@lib.Block(_, @lib.Expr(body))
| @lib.Loop(_, @lib.Expr(body))
| @lib.TryTable(_, _, @lib.Expr(body)) =>
run_hot_pipeline_raw_ssa_nomerge_consume_plan_instrs(body, cursor)
@lib.If(_, then_body, else_body) => {
run_hot_pipeline_raw_ssa_nomerge_consume_plan_instrs(then_body, cursor)
match else_body {
Some(body) =>
run_hot_pipeline_raw_ssa_nomerge_consume_plan_instrs(body, cursor)
None => ()
}
}
@lib.Try(_, @lib.Expr(body), catches, _) =>
run_hot_pipeline_raw_ssa_nomerge_consume_legacy_try_plan(
body, catches, cursor,
)
_ => ()
}
}
}
///|
fn run_hot_pipeline_raw_ssa_nomerge_consume_legacy_try_plan(
body : Array[@lib.Instruction],
catches : Array[@lib.LegacyCatch],
cursor : SsaNoMergeRawRewriteCursor,
) -> Unit {
run_hot_pipeline_raw_ssa_nomerge_consume_plan_instrs(body, cursor)
for catch_ in catches {
let catch_body = match catch_ {
@lib.LegacyCatch(_, @lib.Expr(catch_body))
| @lib.LegacyCatchAll(@lib.Expr(catch_body)) => catch_body
}
run_hot_pipeline_raw_ssa_nomerge_consume_plan_instrs(catch_body, cursor)
}
}
///|
fn run_hot_pipeline_raw_ssa_nomerge_rewrite_planned_structured_body(
instrs : Array[@lib.Instruction],
params : Array[@lib.ValType],
locals : @lib.Locals,
plan : SsaNoMergeRewritePlan,
written_locals : @bitset.BitSet,
cursor : SsaNoMergeRawRewriteCursor,
extra_local_types : Array[@lib.ValType],
base_local_count : Int32,
emitted_fresh_locals : Array[Bool],
in_branching_value_block : Bool,
) -> Array[@lib.Instruction] {
let out : Array[@lib.Instruction] = []
for idx = 0; idx < instrs.length(); idx = idx + 1 {
let instr = instrs[idx]
let force_keep_write = match instr {
@lib.LocalTee(@lib.LocalIdx(raw)) => {
let local_id = raw.reinterpret_as_int()
local_id >= 0 &&
local_id < params.length() &&
run_hot_pipeline_raw_plain_br_before_local_write(
instrs,
idx + 1,
local_id,
)
}
_ => false
}
out.push(
run_hot_pipeline_raw_ssa_nomerge_rewrite_planned_structured_instr(
instr, params, locals, plan, written_locals, cursor, extra_local_types, base_local_count,
emitted_fresh_locals, in_branching_value_block, force_keep_write,
),
)
}
out
}
///|
fn run_hot_pipeline_raw_ssa_nomerge_planned_structured(
func : @lib.Func,
params : Array[@lib.ValType],
plan : SsaNoMergeRewritePlan,
) -> (@lib.Func, Bool) {
let @lib.Func(locals, @lib.Expr(instrs)) = func
if run_hot_pipeline_raw_ssa_nomerge_has_branching_value_block(instrs) {
return (func, false)
}
let extra_local_types : Array[@lib.ValType] = []
let base_local_count = params.length() + locals.length()
let written_locals = @bitset.BitSet::new(base_local_count)
run_hot_pipeline_raw_ssa_nomerge_mark_written_locals(instrs, written_locals)
let cursor = { write_cursor: 0, get_cursor: 0, changed: false, }
let emitted_fresh_locals = Array::make(plan.write_rewrites.length(), false)
let rewritten_instrs = run_hot_pipeline_raw_ssa_nomerge_rewrite_planned_structured_body(
instrs, params, locals, plan, written_locals, cursor, extra_local_types, base_local_count,
emitted_fresh_locals, false,
)
if !cursor.changed {
return (func, false)
}
let (compacted_instrs, compacted_extra_local_types, _) = run_hot_pipeline_raw_drop_unused_appended_locals(
rewritten_instrs, params, locals, extra_local_types,
)
let (spill_repaired_instrs, _) = run_hot_pipeline_raw_ssa_nomerge_repair_stack_spill_arg_alias(
compacted_instrs, params, locals, compacted_extra_local_types,
)
let rewritten_locals = locals.copy()
rewritten_locals.append_types(compacted_extra_local_types)
(
@lib.Func::new(rewritten_locals, @lib.Expr::new(spill_repaired_instrs)),
true,
)
}
///|
fn run_hot_pipeline_raw_ssa_nomerge_straight_line(
func : @lib.Func,
params : Array[@lib.ValType],
plan : SsaNoMergeRewritePlan,
) -> @lib.Func {
let @lib.Func(locals, @lib.Expr(instrs)) = func
let rewritten_instrs : Array[@lib.Instruction] = []
let extra_local_types : Array[@lib.ValType] = []
let base_local_count = params.length() + locals.length()
let fresh_aliases : Array[Int32] = Array::make(base_local_count, -1)
let initialized_locals = run_hot_pipeline_raw_initialized_locals(
base_local_count,
params.length(),
)
let mut write_cursor = 0
let mut get_cursor = 0
for instr in instrs {
match instr {
@lib.LocalGet(@lib.LocalIdx(raw)) => {
let original_local = raw.reinterpret_as_int()
match
run_hot_pipeline_raw_ssa_nomerge_next_get_decision_for_local(
plan, get_cursor, original_local,
) {
Some(SsaNoMergeGetDecision::Retarget(fresh_local_id)) => {
let current_alias = if original_local >= 0 &&
original_local < fresh_aliases.length() {
fresh_aliases[original_local]
} else {
-1
}
if current_alias >= 0 {
rewritten_instrs.push(
@lib.Instruction::local_get(
@lib.LocalIdx::new(current_alias.reinterpret_as_uint()),
),
)
} else {
rewritten_instrs.push(instr)
}
ignore(fresh_local_id)
}
Some(SsaNoMergeGetDecision::MaterializeDefault) =>
if original_local >= 0 &&
original_local < fresh_aliases.length() &&
fresh_aliases[original_local] >= 0 {
rewritten_instrs.push(
@lib.Instruction::local_get(
@lib.LocalIdx::new(
fresh_aliases[original_local].reinterpret_as_uint(),
),
),
)
} else {
rewritten_instrs.push(
run_hot_pipeline_raw_default_instr_for_local(
params, locals, original_local,
),
)
}
Some(SsaNoMergeGetDecision::KeepCanonical) | None =>
if original_local >= params.length() &&
original_local < initialized_locals.length() &&
!initialized_locals[original_local] &&
run_hot_pipeline_raw_local_has_legal_default(
params, locals, original_local,
) {
rewritten_instrs.push(
run_hot_pipeline_raw_default_instr_for_local(
params, locals, original_local,
),
)
} else {
rewritten_instrs.push(instr)
}
}
get_cursor = get_cursor + 1
}
@lib.LocalSet(@lib.LocalIdx(raw)) => {
let original_local = raw.reinterpret_as_int()
match
run_hot_pipeline_raw_ssa_nomerge_next_set_decision(plan, write_cursor) {
Some(SsaNoMergeWriteDecision::Freshen(fresh_local_id)) => {
run_hot_pipeline_raw_ssa_nomerge_append_planned_fresh_local(
extra_local_types, params, locals, base_local_count, original_local,
fresh_local_id,
)
rewritten_instrs.push(
@lib.Instruction::local_set(
@lib.LocalIdx::new(fresh_local_id.reinterpret_as_uint()),
),
)
if original_local >= 0 && original_local < fresh_aliases.length() {
fresh_aliases[original_local] = fresh_local_id
}
if original_local >= 0 &&
original_local < initialized_locals.length() {
initialized_locals[original_local] = true
}
}
Some(SsaNoMergeWriteDecision::KeepCanonical) => {
if original_local >= 0 && original_local < fresh_aliases.length() {
fresh_aliases[original_local] = -1
}
if original_local >= 0 &&
original_local < initialized_locals.length() {
initialized_locals[original_local] = true
}
rewritten_instrs.push(instr)
}
None => rewritten_instrs.push(instr)
}
write_cursor = write_cursor + 1
}
@lib.LocalTee(@lib.LocalIdx(raw)) => {
let original_local = raw.reinterpret_as_int()
match
run_hot_pipeline_raw_ssa_nomerge_next_set_decision(plan, write_cursor) {
Some(SsaNoMergeWriteDecision::Freshen(fresh_local_id)) => {
run_hot_pipeline_raw_ssa_nomerge_append_planned_fresh_local(
extra_local_types, params, locals, base_local_count, original_local,
fresh_local_id,
)
rewritten_instrs.push(
@lib.Instruction::local_tee(
@lib.LocalIdx::new(fresh_local_id.reinterpret_as_uint()),
),
)
if original_local >= 0 && original_local < fresh_aliases.length() {
fresh_aliases[original_local] = fresh_local_id
}
if original_local >= 0 &&
original_local < initialized_locals.length() {
initialized_locals[original_local] = true
}
}
Some(SsaNoMergeWriteDecision::KeepCanonical) => {
if original_local >= 0 && original_local < fresh_aliases.length() {
fresh_aliases[original_local] = -1
}
if original_local >= 0 &&
original_local < initialized_locals.length() {
initialized_locals[original_local] = true
}
rewritten_instrs.push(instr)
}
None => rewritten_instrs.push(instr)
}
write_cursor = write_cursor + 1
}
_ => rewritten_instrs.push(instr)
}
}
let (spill_repaired_instrs, _) = run_hot_pipeline_raw_ssa_nomerge_repair_stack_spill_arg_alias(
rewritten_instrs, params, locals, extra_local_types,
)
let rewritten_locals = locals.copy()
rewritten_locals.append_types(extra_local_types)
let rewritten = @lib.Func::new(
rewritten_locals,
@lib.Expr::new(spill_repaired_instrs),
)
let (expanded, _) = run_hot_pipeline_raw_ssa_nomerge_expand_zero_v128_defaults_func(
rewritten,
)
expanded
}
///|
priv struct HotPipelineFuncResult {
reason : String
func : @lib.Func
unchanged_original : Bool
trace_skip_raw : Bool
}
///|
fn run_hot_pipeline_raw_instrs_have_mixed_direct_ref_loop_backedges(
instrs : Array[@lib.Instruction],
loop_depth : UInt32,
) -> Bool {
let mut saw_scalar_backedge = false
let mut saw_null_backedge = false
let mut saw_non_null_backedge = false
for instr in instrs {
match instr {
@lib.Br(@lib.LabelIdx(raw)) | @lib.BrIf(@lib.LabelIdx(raw)) =>
if raw == loop_depth {
saw_scalar_backedge = true
}
@lib.BrOnNull(@lib.LabelIdx(raw)) =>
if raw == loop_depth {
saw_null_backedge = true
}
@lib.BrOnNonNull(@lib.LabelIdx(raw)) =>
if raw == loop_depth {
saw_non_null_backedge = true
}
@lib.Block(_, @lib.Expr(body))
| @lib.Loop(_, @lib.Expr(body))
| @lib.TryTable(_, _, @lib.Expr(body)) =>
if run_hot_pipeline_raw_instrs_have_mixed_direct_ref_loop_backedges(
body,
loop_depth + 1U,
) {
return true
}
@lib.If(_, then_body, else_body) => {
if run_hot_pipeline_raw_instrs_have_mixed_direct_ref_loop_backedges(
then_body,
loop_depth + 1U,
) {
return true
}
match else_body {
Some(body) =>
if run_hot_pipeline_raw_instrs_have_mixed_direct_ref_loop_backedges(
body,
loop_depth + 1U,
) {
return true
}
None => ()
}
}
_ => ()
}
}
(saw_null_backedge || saw_non_null_backedge) &&
(saw_scalar_backedge || (saw_null_backedge && saw_non_null_backedge))
}
///|
fn run_hot_pipeline_raw_ssa_nomerge_typeidx_loop_has_direct_backedge(
instrs : Array[@lib.Instruction],
module_ctx : @ir.HotModuleContext,
) -> Bool {
for instr in instrs {
match instr {
@lib.Loop(@lib.TypeIdxBlockType(type_idx), @lib.Expr(body)) => {
let allow_ref_branches = match
@ir.hot_module_resolve_type_func_type(module_ctx, type_idx) {
Some(@lib.FuncType(params, _)) => {
let mut has_ref_param = false
for param in params {
match param {
@lib.RefTypeValType(_) => has_ref_param = true
_ => ()
}
}
has_ref_param &&
run_hot_pipeline_raw_instrs_have_mixed_direct_ref_loop_backedges(
body, 0U,
)
}
_ => false
}
if run_hot_pipeline_raw_instrs_only_direct_loop_param_backedges_through_try_table(
body, 0U, allow_ref_branches,
) {
return true
}
}
@lib.Block(_, @lib.Expr(body)) | @lib.TryTable(_, _, @lib.Expr(body)) =>
if run_hot_pipeline_raw_ssa_nomerge_typeidx_loop_has_direct_backedge(
body, module_ctx,
) {
return true
}
@lib.If(_, then_body, else_body) => {
if run_hot_pipeline_raw_ssa_nomerge_typeidx_loop_has_direct_backedge(
then_body, module_ctx,
) {
return true
}
match else_body {
Some(body) =>
if run_hot_pipeline_raw_ssa_nomerge_typeidx_loop_has_direct_backedge(
body, module_ctx,
) {
return true
}
None => ()
}
}
_ => ()
}
}
false
}
///|
fn run_hot_pipeline_raw_ssa_nomerge_has_control_value_ref_branch(
instrs : Array[@lib.Instruction],
) -> Bool {
let mut previous_is_control_value = false
for instr in instrs {
match instr {
@lib.BrOnNull(_)
| @lib.BrOnNonNull(_)
| @lib.BrOnCast(_, _, _, _)
| @lib.BrOnCastFail(_, _, _, _) =>
if previous_is_control_value {
return true
}
@lib.Block(block_type, @lib.Expr(body)) => {
if run_hot_pipeline_raw_ssa_nomerge_has_control_value_ref_branch(body) {
return true
}
previous_is_control_value = block_type != @lib.BlockType::void_()
continue
}
@lib.Loop(block_type, @lib.Expr(body)) => {
if run_hot_pipeline_raw_ssa_nomerge_has_control_value_ref_branch(body) {
return true
}
previous_is_control_value = block_type != @lib.BlockType::void_()
continue
}
@lib.TryTable(block_type, _, @lib.Expr(body)) => {
if run_hot_pipeline_raw_ssa_nomerge_has_control_value_ref_branch(body) {
return true
}
previous_is_control_value = block_type != @lib.BlockType::void_()
continue
}
@lib.If(block_type, then_body, else_body) => {
if run_hot_pipeline_raw_ssa_nomerge_has_control_value_ref_branch(
then_body,
) {
return true
}
match else_body {
Some(body) =>
if run_hot_pipeline_raw_ssa_nomerge_has_control_value_ref_branch(
body,
) {
return true
}
None => ()
}
previous_is_control_value = block_type != @lib.BlockType::void_()
continue
}
_ => previous_is_control_value = false
}
}
false
}
///|
fn run_hot_pipeline_raw_ssa_nomerge_has_local_get_ref_branch_copy_shape(
instrs : Array[@lib.Instruction],
seen_if : Bool,
inside_typeidx_loop : Bool,
saw_non_param_local_read : Bool,
) -> Bool {
let mut current_seen_if = seen_if
let mut current_saw_non_param_local_read = saw_non_param_local_read
let mut saw_constructed_ref_since_if = false
let mut previous_is_local_get = false
for instr in instrs {
match instr {
@lib.BrOnNull(_) =>
if current_seen_if &&
previous_is_local_get &&
!inside_typeidx_loop &&
!saw_constructed_ref_since_if {
return true
}
@lib.BrOnNonNull(_) =>
if current_seen_if &&
previous_is_local_get &&
(!inside_typeidx_loop || current_saw_non_param_local_read) {
return true
}
@lib.RefI31
| @lib.StructNewDefault(_)
| @lib.ArrayNewDefault(_)
| @lib.RefFunc(_) => {
saw_constructed_ref_since_if = true
previous_is_local_get = false
}
@lib.LocalGet(@lib.LocalIdx(raw)) => {
if raw.reinterpret_as_int() == 2 {
current_saw_non_param_local_read = true
}
previous_is_local_get = true
continue
}
@lib.If(_, then_body, else_body) => {
if run_hot_pipeline_raw_ssa_nomerge_has_local_get_ref_branch_copy_shape(
then_body, current_seen_if, inside_typeidx_loop, current_saw_non_param_local_read,
) {
return true
}
match else_body {
Some(body) =>
if run_hot_pipeline_raw_ssa_nomerge_has_local_get_ref_branch_copy_shape(
body, current_seen_if, inside_typeidx_loop, current_saw_non_param_local_read,
) {
return true
}
None => ()
}
current_seen_if = true
saw_constructed_ref_since_if = false
}
@lib.Loop(@lib.TypeIdxBlockType(_), @lib.Expr(body)) =>
if run_hot_pipeline_raw_ssa_nomerge_has_local_get_ref_branch_copy_shape(
body, current_seen_if, true, current_saw_non_param_local_read,
) {
return true
}
@lib.Block(_, @lib.Expr(body))
| @lib.Loop(_, @lib.Expr(body))
| @lib.TryTable(_, _, @lib.Expr(body)) =>
if run_hot_pipeline_raw_ssa_nomerge_has_local_get_ref_branch_copy_shape(
body, current_seen_if, inside_typeidx_loop, current_saw_non_param_local_read,
) {
return true
}
_ => previous_is_local_get = false
}
}
false
}
///|
fn run_hot_pipeline_raw_ssa_nomerge_has_direct_call_ref_loop_shape(
instrs : Array[@lib.Instruction],
module_ctx : @ir.HotModuleContext,
inside_typeidx_loop : Bool,
branch_on_null : Bool,
) -> Bool {
let mut previous_is_call = false
for instr in instrs {
match instr {
@lib.BrOnNull(_) if branch_on_null =>
if inside_typeidx_loop && previous_is_call {
return true
}
@lib.BrOnNonNull(_) if !branch_on_null =>
if inside_typeidx_loop && previous_is_call {
return true
}
@lib.Call(func_idx) => {
previous_is_call = match
@ir.hot_module_resolve_func_type(module_ctx, func_idx) {
Some(@lib.FuncType(_, [@lib.RefTypeValType(_)])) => true
_ => false
}
continue
}
@lib.CallRef(type_idx) | @lib.CallIndirect(type_idx, _) => {
previous_is_call = match
@ir.hot_module_resolve_type_func_type(module_ctx, type_idx) {
Some(@lib.FuncType(_, [@lib.RefTypeValType(_)])) => true
_ => false
}
continue
}
@lib.If(_, then_body, else_body) => {
if run_hot_pipeline_raw_ssa_nomerge_has_direct_call_ref_loop_shape(
then_body, module_ctx, inside_typeidx_loop, branch_on_null,
) {
return true
}
match else_body {
Some(body) =>
if run_hot_pipeline_raw_ssa_nomerge_has_direct_call_ref_loop_shape(
body, module_ctx, inside_typeidx_loop, branch_on_null,
) {
return true
}
None => ()
}
}
@lib.Loop(@lib.TypeIdxBlockType(_), @lib.Expr(body)) =>
if run_hot_pipeline_raw_ssa_nomerge_has_direct_call_ref_loop_shape(
body, module_ctx, true, branch_on_null,
) {
return true
}
@lib.Block(_, @lib.Expr(body))
| @lib.Loop(_, @lib.Expr(body))
| @lib.TryTable(_, _, @lib.Expr(body)) =>
if run_hot_pipeline_raw_ssa_nomerge_has_direct_call_ref_loop_shape(
body, module_ctx, inside_typeidx_loop, branch_on_null,
) {
return true
}
_ => previous_is_call = false
}
}
false
}
///|
fn run_hot_pipeline_raw_ssa_nomerge_has_direct_call_non_null_loop_shape(
instrs : Array[@lib.Instruction],
module_ctx : @ir.HotModuleContext,
inside_typeidx_loop : Bool,
) -> Bool {
run_hot_pipeline_raw_ssa_nomerge_has_direct_call_ref_loop_shape(
instrs, module_ctx, inside_typeidx_loop, false,
)
}
///|
fn run_hot_pipeline_raw_ssa_nomerge_has_direct_call_null_loop_shape(
instrs : Array[@lib.Instruction],
module_ctx : @ir.HotModuleContext,
inside_typeidx_loop : Bool,
) -> Bool {
run_hot_pipeline_raw_ssa_nomerge_has_direct_call_ref_loop_shape(
instrs, module_ctx, inside_typeidx_loop, true,
)
}
///|
fn run_hot_pipeline_raw_ssa_nomerge_has_direct_call_ref_branch_sequence(
instrs : Array[@lib.Instruction],
module_ctx : @ir.HotModuleContext,
branch_on_null : Bool,
allow_direct_call_operands : Bool,
) -> Bool {
let mut previous_is_call = false
let mut previous_is_call_ref_cast = false
for instr in instrs {
match instr {
@lib.BrOnNull(_) if branch_on_null =>
if previous_is_call || previous_is_call_ref_cast {
return true
}
@lib.BrOnNonNull(_) if !branch_on_null =>
if previous_is_call || previous_is_call_ref_cast {
return true
}
@lib.RefCast(_, _, _) if previous_is_call => {
previous_is_call = false
previous_is_call_ref_cast = true
continue
}
@lib.Call(func_idx) => {
previous_is_call_ref_cast = false
previous_is_call = match
@ir.hot_module_resolve_func_type(module_ctx, func_idx) {
Some(@lib.FuncType(call_params, [@lib.RefTypeValType(_)])) =>
call_params.length() == 0 || allow_direct_call_operands
_ => false
}
continue
}
@lib.CallRef(type_idx) | @lib.CallIndirect(type_idx, _) => {
previous_is_call_ref_cast = false
previous_is_call = match
@ir.hot_module_resolve_type_func_type(module_ctx, type_idx) {
Some(@lib.FuncType(call_params, [@lib.RefTypeValType(_)])) =>
call_params.length() == 0 || allow_direct_call_operands
_ => false
}
continue
}
_ => {
previous_is_call = false
previous_is_call_ref_cast = false
}
}
}
false
}
///|
fn run_hot_pipeline_raw_ssa_nomerge_direct_call_block_type_supported(
block_type : @lib.BlockType,
module_ctx : @ir.HotModuleContext,
branch_on_null : Bool,
) -> Bool {
match @ir.hot_module_resolve_block_results(module_ctx, block_type) {
Some(result_types) =>
if branch_on_null {
result_types.length() > 0
} else {
match result_types.last() {
Some(@lib.RefTypeValType(_)) => true
_ => false
}
}
None => false
}
}
///|
fn run_hot_pipeline_raw_ssa_nomerge_direct_call_block_allows_operands(
block_type : @lib.BlockType,
module_ctx : @ir.HotModuleContext,
branch_on_null : Bool,
) -> Bool {
match @ir.hot_module_resolve_block_results(module_ctx, block_type) {
Some(result_types) =>
if branch_on_null {
result_types.length() > 0
} else {
match result_types.last() {
Some(@lib.RefTypeValType(_)) => true
_ => false
}
}
None => false
}
}
///|
fn run_hot_pipeline_raw_ssa_nomerge_has_direct_call_single_result_block_shape(
instrs : Array[@lib.Instruction],
module_ctx : @ir.HotModuleContext,
branch_on_null : Bool,
) -> Bool {
for instr in instrs {
match instr {
@lib.Block(block_type, @lib.Expr(body)) => {
if run_hot_pipeline_raw_ssa_nomerge_direct_call_block_type_supported(
block_type, module_ctx, branch_on_null,
) &&
run_hot_pipeline_raw_ssa_nomerge_has_direct_call_ref_branch_sequence(
body,
module_ctx,
branch_on_null,
run_hot_pipeline_raw_ssa_nomerge_direct_call_block_allows_operands(
block_type, module_ctx, branch_on_null,
),
) {
return true
}
if run_hot_pipeline_raw_ssa_nomerge_has_direct_call_single_result_block_shape(
body, module_ctx, branch_on_null,
) {
return true
}
}
@lib.If(_, then_body, else_body) => {
if run_hot_pipeline_raw_ssa_nomerge_has_direct_call_single_result_block_shape(
then_body, module_ctx, branch_on_null,
) {
return true
}
match else_body {
Some(body) =>
if run_hot_pipeline_raw_ssa_nomerge_has_direct_call_single_result_block_shape(
body, module_ctx, branch_on_null,
) {
return true
}
None => ()
}
}
@lib.Loop(_, @lib.Expr(body)) | @lib.TryTable(_, _, @lib.Expr(body)) =>
if run_hot_pipeline_raw_ssa_nomerge_has_direct_call_single_result_block_shape(
body, module_ctx, branch_on_null,
) {
return true
}
_ => ()
}
}
false
}
///|
fn run_hot_pipeline_raw_ssa_nomerge_has_direct_call_non_null_single_result_block_shape(
instrs : Array[@lib.Instruction],
module_ctx : @ir.HotModuleContext,
) -> Bool {
run_hot_pipeline_raw_ssa_nomerge_has_direct_call_single_result_block_shape(
instrs, module_ctx, false,
)
}
///|
fn run_hot_pipeline_raw_ssa_nomerge_has_direct_call_null_single_result_block_shape(
instrs : Array[@lib.Instruction],
module_ctx : @ir.HotModuleContext,
) -> Bool {
run_hot_pipeline_raw_ssa_nomerge_has_direct_call_single_result_block_shape(
instrs, module_ctx, true,
)
}
///|
fn run_hot_pipeline_raw_ssa_nomerge_has_select_ref_branch_copy_shape(
instrs : Array[@lib.Instruction],
) -> Bool {
let mut previous_select_ref = false
for instr in instrs {
match instr {
@lib.BrOnNull(_) | @lib.BrOnNonNull(_) =>
if previous_select_ref {
return true
}
@lib.Select(Some([@lib.RefTypeValType(_)])) => {
previous_select_ref = true
continue
}
@lib.If(_, then_body, else_body) => {
if run_hot_pipeline_raw_ssa_nomerge_has_select_ref_branch_copy_shape(
then_body,
) {
return true
}
match else_body {
Some(body) =>
if run_hot_pipeline_raw_ssa_nomerge_has_select_ref_branch_copy_shape(
body,
) {
return true
}
None => ()
}
}
@lib.Block(_, @lib.Expr(body))
| @lib.Loop(_, @lib.Expr(body))
| @lib.TryTable(_, _, @lib.Expr(body)) =>
if run_hot_pipeline_raw_ssa_nomerge_has_select_ref_branch_copy_shape(
body,
) {
return true
}
_ => previous_select_ref = false
}
}
false
}
///|
fn run_hot_pipeline_raw_ssa_nomerge_has_constructed_ref_branch_copy_shape(
instrs : Array[@lib.Instruction],
seen_if : Bool,
block_type : @lib.BlockType,
) -> Bool {
let mut current_seen_if = seen_if
let mut previous_constructed_ref : @lib.ValType? = None
for instr in instrs {
match instr {
@lib.BrOnNull(_) =>
if current_seen_if &&
previous_constructed_ref is Some(_) &&
block_type == @lib.BlockType::void_() {
return true
}
@lib.BrOnNonNull(_) =>
match (block_type, previous_constructed_ref) {
(@lib.ValTypeBlockType(target_type), Some(constructed_type)) =>
if current_seen_if &&
run_hot_pipeline_raw_reference_type_matches_same_heap(
constructed_type, target_type,
) {
return true
}
_ => ()
}
@lib.RefI31 => {
previous_constructed_ref = Some(@lib.ValType::i31ref())
continue
}
@lib.StructNewDefault(type_idx) | @lib.StructNew(type_idx) => {
previous_constructed_ref = Some(
@lib.ValType::ref_type(
@lib.RefType::new(false, @lib.HeapType::new(type_idx), exact=true),
),
)
continue
}
@lib.ArrayNew(type_idx)
| @lib.ArrayNewFixed(type_idx, _)
| @lib.ArrayNewData(type_idx, _)
| @lib.ArrayNewElem(type_idx, _) => {
previous_constructed_ref = Some(
@lib.ValType::ref_array_nonnull(type_idx),
)
continue
}
@lib.If(_, then_body, else_body) => {
if run_hot_pipeline_raw_ssa_nomerge_has_constructed_ref_branch_copy_shape(
then_body, current_seen_if, block_type,
) {
return true
}
match else_body {
Some(body) =>
if run_hot_pipeline_raw_ssa_nomerge_has_constructed_ref_branch_copy_shape(
body, current_seen_if, block_type,
) {
return true
}
None => ()
}
current_seen_if = true
}
@lib.Block(child_block_type, @lib.Expr(body))
| @lib.Loop(child_block_type, @lib.Expr(body))
| @lib.TryTable(child_block_type, _, @lib.Expr(body)) =>
if run_hot_pipeline_raw_ssa_nomerge_has_constructed_ref_branch_copy_shape(
body, current_seen_if, child_block_type,
) {
return true
}
_ => previous_constructed_ref = None
}
}
false
}
///|
fn run_hot_pipeline_raw_ssa_nomerge_has_local_tee_ref_branch_copy_shape(
instrs : Array[@lib.Instruction],
seen_if : Bool,
) -> Bool {
let mut current_seen_if = seen_if
let mut previous_is_local_tee = false
for instr in instrs {
match instr {
@lib.BrOnNull(_) | @lib.BrOnNonNull(_) =>
if current_seen_if && previous_is_local_tee {
return true
}
@lib.LocalTee(_) => {
previous_is_local_tee = true
continue
}
@lib.If(_, then_body, else_body) => {
if run_hot_pipeline_raw_ssa_nomerge_has_local_tee_ref_branch_copy_shape(
then_body, current_seen_if,
) {
return true
}
match else_body {
Some(body) =>
if run_hot_pipeline_raw_ssa_nomerge_has_local_tee_ref_branch_copy_shape(
body, current_seen_if,
) {
return true
}
None => ()
}
current_seen_if = true
}
@lib.Block(_, @lib.Expr(body))
| @lib.Loop(_, @lib.Expr(body))
| @lib.TryTable(_, _, @lib.Expr(body)) =>
if run_hot_pipeline_raw_ssa_nomerge_has_local_tee_ref_branch_copy_shape(
body, current_seen_if,
) {
return true
}
_ => previous_is_local_tee = false
}
}
false
}
///|
fn run_hot_pipeline_raw_ssa_nomerge_has_cast_branch_copy_shape(
instrs : Array[@lib.Instruction],
seen_if : Bool,
inside_typeidx_loop : Bool,
saw_non_param_local_read : Bool,
) -> Bool {
let mut current_seen_if = seen_if
let mut current_saw_non_param_local_read = saw_non_param_local_read
for instr in instrs {
match instr {
@lib.LocalGet(@lib.LocalIdx(raw)) =>
if raw.reinterpret_as_int() == 2 {
current_saw_non_param_local_read = true
}
@lib.BrOnCast(_, _, _, _) | @lib.BrOnCastFail(_, _, _, _) =>
if current_seen_if &&
(!inside_typeidx_loop || current_saw_non_param_local_read) {
return true
}
@lib.If(_, then_body, else_body) => {
if run_hot_pipeline_raw_ssa_nomerge_has_cast_branch_copy_shape(
then_body, current_seen_if, inside_typeidx_loop, current_saw_non_param_local_read,
) {
return true
}
match else_body {
Some(body) =>
if run_hot_pipeline_raw_ssa_nomerge_has_cast_branch_copy_shape(
body, current_seen_if, inside_typeidx_loop, current_saw_non_param_local_read,
) {
return true
}
None => ()
}
current_seen_if = true
}
@lib.Loop(@lib.TypeIdxBlockType(_), @lib.Expr(body)) =>
if run_hot_pipeline_raw_ssa_nomerge_has_cast_branch_copy_shape(
body, current_seen_if, true, current_saw_non_param_local_read,
) {
return true
}
@lib.Block(_, @lib.Expr(body))
| @lib.Loop(_, @lib.Expr(body))
| @lib.TryTable(_, _, @lib.Expr(body)) =>
if run_hot_pipeline_raw_ssa_nomerge_has_cast_branch_copy_shape(
body, current_seen_if, inside_typeidx_loop, current_saw_non_param_local_read,
) {
return true
}
_ => ()
}
}
false
}
///|
fn run_hot_pipeline_raw_ssa_nomerge_has_br_table_merge_copy_shape(
instrs : Array[@lib.Instruction],
seen_if : Bool,
) -> Bool {
let mut saw_non_param_local_read = false
let mut current_seen_if = seen_if
for instr in instrs {
match instr {
@lib.LocalGet(@lib.LocalIdx(raw)) =>
if raw.reinterpret_as_int() > 0 {
saw_non_param_local_read = true
}
@lib.BrTable(_, _) =>
if current_seen_if || saw_non_param_local_read {
return true
}
@lib.If(_, then_body, else_body) => {
if run_hot_pipeline_raw_ssa_nomerge_has_br_table_merge_copy_shape(
then_body, current_seen_if,
) {
return true
}
match else_body {
Some(body) =>
if run_hot_pipeline_raw_ssa_nomerge_has_br_table_merge_copy_shape(
body, current_seen_if,
) {
return true
}
None => ()
}
current_seen_if = true
}
@lib.Block(_, @lib.Expr(body))
| @lib.Loop(_, @lib.Expr(body))
| @lib.TryTable(_, _, @lib.Expr(body)) =>
if run_hot_pipeline_raw_ssa_nomerge_has_br_table_merge_copy_shape(
body, current_seen_if,
) {
return true
}
_ => ()
}
}
false
}
///|
fn run_hot_pipeline_raw_ssa_nomerge_loop_backedge_decoration_has_branch_table(
instrs : Array[@lib.Instruction],
) -> Bool {
for instr in instrs {
match instr {
@lib.Br(_) | @lib.BrIf(_) | @lib.BrTable(_, _) => return true
@lib.Block(_, @lib.Expr(body)) =>
if run_hot_pipeline_raw_ssa_nomerge_loop_backedge_decoration_has_branch_table(
body,
) {
return true
}
@lib.If(_, then_body, else_body) => {
if run_hot_pipeline_raw_ssa_nomerge_loop_backedge_decoration_has_branch_table(
then_body,
) {
return true
}
match else_body {
Some(body) =>
if run_hot_pipeline_raw_ssa_nomerge_loop_backedge_decoration_has_branch_table(
body,
) {
return true
}
None => ()
}
}
_ => ()
}
}
false
}
///|
fn run_hot_pipeline_raw_ssa_nomerge_retained_loop_backedge_boundary(
instrs : Array[@lib.Instruction],
param_count : Int32,
) -> Bool {
for instr in instrs {
match instr {
@lib.Loop(@lib.VoidBlockType, @lib.Expr(body)) => {
let mut saw_local_read_before_write = false
let mut saw_local_write = false
let mut saw_boundary = false
for body_instr in body {
match body_instr {
@lib.LocalGet(@lib.LocalIdx(raw)) =>
if !saw_local_write && raw.reinterpret_as_int() >= param_count {
saw_local_read_before_write = true
}
@lib.LocalSet(_) | @lib.LocalTee(_) => saw_local_write = true
@lib.BrIf(@lib.LabelIdx(raw)) =>
if raw == 0U &&
saw_local_read_before_write &&
saw_local_write &&
saw_boundary {
return true
}
@lib.Block(_, @lib.Expr(nested)) =>
if run_hot_pipeline_raw_ssa_nomerge_loop_backedge_decoration_has_branch_table(
nested,
) {
saw_boundary = true
}
@lib.Br(_) | @lib.BrTable(_, _) => saw_boundary = true
@lib.If(_, then_body, else_body) => {
if run_hot_pipeline_raw_ssa_nomerge_loop_backedge_decoration_has_branch_table(
then_body,
) {
saw_boundary = true
}
match else_body {
Some(body) =>
if run_hot_pipeline_raw_ssa_nomerge_loop_backedge_decoration_has_branch_table(
body,
) {
saw_boundary = true
}
None => ()
}
}
_ => ()
}
}
}
@lib.Block(_, @lib.Expr(body)) =>
if run_hot_pipeline_raw_ssa_nomerge_retained_loop_backedge_boundary(
body, param_count,
) {
return true
}
@lib.If(_, then_body, else_body) => {
if run_hot_pipeline_raw_ssa_nomerge_retained_loop_backedge_boundary(
then_body, param_count,
) {
return true
}
match else_body {
Some(body) =>
if run_hot_pipeline_raw_ssa_nomerge_retained_loop_backedge_boundary(
body, param_count,
) {
return true
}
None => ()
}
}
_ => ()
}
}
false
}
///|
fn run_hot_pipeline_raw_ssa_nomerge_loop_plain_br_boundary_stats(
instrs : Array[@lib.Instruction],
param_count : Int32,
) -> (Bool, Bool, Bool) {
let mut saw_body_local_get = false
let mut saw_body_local_write = false
let mut saw_plain_br = false
for instr in instrs {
match instr {
@lib.LocalGet(@lib.LocalIdx(raw)) =>
if raw.reinterpret_as_int() >= param_count {
saw_body_local_get = true
}
@lib.LocalSet(@lib.LocalIdx(raw)) | @lib.LocalTee(@lib.LocalIdx(raw)) =>
if raw.reinterpret_as_int() >= param_count {
saw_body_local_write = true
}
@lib.Br(_) => saw_plain_br = true
@lib.Block(_, @lib.Expr(body))
| @lib.Loop(_, @lib.Expr(body))
| @lib.TryTable(_, _, @lib.Expr(body)) => {
let (child_get, child_write, child_br) = run_hot_pipeline_raw_ssa_nomerge_loop_plain_br_boundary_stats(
body, param_count,
)
saw_body_local_get = saw_body_local_get || child_get
saw_body_local_write = saw_body_local_write || child_write
saw_plain_br = saw_plain_br || child_br
}
@lib.If(_, then_body, else_body) => {
let (then_get, then_write, then_br) = run_hot_pipeline_raw_ssa_nomerge_loop_plain_br_boundary_stats(
then_body, param_count,
)
saw_body_local_get = saw_body_local_get || then_get
saw_body_local_write = saw_body_local_write || then_write
saw_plain_br = saw_plain_br || then_br
match else_body {
Some(body) => {
let (else_get, else_write, else_br) = run_hot_pipeline_raw_ssa_nomerge_loop_plain_br_boundary_stats(
body, param_count,
)
saw_body_local_get = saw_body_local_get || else_get
saw_body_local_write = saw_body_local_write || else_write
saw_plain_br = saw_plain_br || else_br
}
None => ()
}
}
_ => ()
}
}
(saw_body_local_get, saw_body_local_write, saw_plain_br)
}
///|
fn run_hot_pipeline_raw_ssa_nomerge_has_loop_carried_plain_br_boundary(
instrs : Array[@lib.Instruction],
param_count : Int32,
) -> Bool {
for instr in instrs {
match instr {
@lib.Loop(@lib.VoidBlockType, @lib.Expr(body)) => {
let (saw_get, saw_write, saw_br) = run_hot_pipeline_raw_ssa_nomerge_loop_plain_br_boundary_stats(
body, param_count,
)
if saw_get && saw_write && saw_br {
return true
}
if run_hot_pipeline_raw_ssa_nomerge_has_loop_carried_plain_br_boundary(
body, param_count,
) {
return true
}
}
@lib.Loop(_, @lib.Expr(body))
| @lib.Block(_, @lib.Expr(body))
| @lib.TryTable(_, _, @lib.Expr(body)) =>
if run_hot_pipeline_raw_ssa_nomerge_has_loop_carried_plain_br_boundary(
body, param_count,
) {
return true
}
@lib.If(_, then_body, else_body) => {
if run_hot_pipeline_raw_ssa_nomerge_has_loop_carried_plain_br_boundary(
then_body, param_count,
) {
return true
}
match else_body {
Some(body) =>
if run_hot_pipeline_raw_ssa_nomerge_has_loop_carried_plain_br_boundary(
body, param_count,
) {
return true
}
None => ()
}
}
_ => ()
}
}
false
}
///|
fn run_hot_pipeline_raw_ssa_nomerge_typeidx_is_result_only_loop(
module_ctx : @ir.HotModuleContext,
type_idx : @lib.TypeIdx,
) -> Bool {
match @ir.hot_module_resolve_type_func_type(module_ctx, type_idx) {
Some(@lib.FuncType(params, results)) =>
params.length() == 0 && results.length() == 1
None => false
}
}
///|
fn run_hot_pipeline_raw_ssa_nomerge_typeidx_is_single_param_result_loop(
module_ctx : @ir.HotModuleContext,
type_idx : @lib.TypeIdx,
) -> Bool {
match @ir.hot_module_resolve_type_func_type(module_ctx, type_idx) {
Some(@lib.FuncType(params, results)) =>
params.length() == 1 && results.length() == 1
None => false
}
}
///|
fn run_hot_pipeline_raw_ssa_nomerge_typeidx_is_single_param_no_result_loop(
module_ctx : @ir.HotModuleContext,
type_idx : @lib.TypeIdx,
) -> Bool {
match @ir.hot_module_resolve_type_func_type(module_ctx, type_idx) {
Some(@lib.FuncType(params, results)) =>
params.length() == 1 && results.length() == 0
None => false
}
}
///|
fn run_hot_pipeline_raw_ssa_nomerge_loop_starts_with_body_local_set(
body : Array[@lib.Instruction],
param_count : Int32,
) -> Bool {
match body {
[@lib.LocalSet(@lib.LocalIdx(raw)), ..] =>
raw.reinterpret_as_int() >= param_count
_ => false
}
}
///|
fn run_hot_pipeline_raw_ssa_nomerge_has_try_table_or_nested_loop(
instrs : Array[@lib.Instruction],
) -> Bool {
for instr in instrs {
match instr {
@lib.Block(_, _) | @lib.Loop(_, _) | @lib.TryTable(_, _, _) => return true
@lib.If(_, then_body, else_body) => {
if run_hot_pipeline_raw_ssa_nomerge_has_try_table_or_nested_loop(
then_body,
) {
return true
}
match else_body {
Some(body) =>
if run_hot_pipeline_raw_ssa_nomerge_has_try_table_or_nested_loop(
body,
) {
return true
}
None => ()
}
}
_ => ()
}
}
false
}
///|
fn run_hot_pipeline_raw_ssa_nomerge_has_non_plain_branch(
instrs : Array[@lib.Instruction],
) -> Bool {
for instr in instrs {
match instr {
@lib.BrIf(_)
| @lib.BrTable(_, _)
| @lib.BrOnNull(_)
| @lib.BrOnNonNull(_)
| @lib.BrOnCast(_, _, _, _)
| @lib.BrOnCastFail(_, _, _, _)
| @lib.BrOnCastDescEq(_, _, _)
| @lib.BrOnCastDescEqFail(_, _, _) => return true
@lib.Block(_, @lib.Expr(body))
| @lib.Loop(_, @lib.Expr(body))
| @lib.TryTable(_, _, @lib.Expr(body)) =>
if run_hot_pipeline_raw_ssa_nomerge_has_non_plain_branch(body) {
return true
}
@lib.If(_, then_body, else_body) => {
if run_hot_pipeline_raw_ssa_nomerge_has_non_plain_branch(then_body) {
return true
}
match else_body {
Some(body) =>
if run_hot_pipeline_raw_ssa_nomerge_has_non_plain_branch(body) {
return true
}
None => ()
}
}
_ => ()
}
}
false
}
///|
fn run_hot_pipeline_raw_ssa_nomerge_is_lowered_result_loop_plain_br_boundary(
body : Array[@lib.Instruction],
param_count : Int32,
) -> Bool {
match body {
[@lib.Loop(@lib.VoidBlockType, @lib.Expr(loop_body)), @lib.Unreachable] => {
let (saw_get, saw_write, saw_br) = run_hot_pipeline_raw_ssa_nomerge_loop_plain_br_boundary_stats(
loop_body, param_count,
)
saw_get && saw_write && saw_br
}
_ => false
}
}
///|
fn run_hot_pipeline_raw_ssa_nomerge_has_loop_param_plain_br_boundary(
instrs : Array[@lib.Instruction],
module_ctx : @ir.HotModuleContext,
param_count : Int32,
) -> Bool {
for instr in instrs {
match instr {
@lib.Loop(@lib.TypeIdxBlockType(type_idx), @lib.Expr(body)) =>
if run_hot_pipeline_raw_ssa_nomerge_typeidx_is_single_param_no_result_loop(
module_ctx, type_idx,
) &&
run_hot_pipeline_raw_ssa_nomerge_loop_starts_with_body_local_set(
body, param_count,
) &&
!run_hot_pipeline_raw_ssa_nomerge_has_non_plain_branch(body) &&
!run_hot_pipeline_raw_ssa_nomerge_has_try_table_or_nested_loop(body) {
let (saw_get, saw_write, saw_br) = run_hot_pipeline_raw_ssa_nomerge_loop_plain_br_boundary_stats(
body, param_count,
)
if saw_get && saw_write && saw_br {
return true
}
}
@lib.Loop(@lib.VoidBlockType, @lib.Expr(body))
| @lib.Block(_, @lib.Expr(body)) =>
if run_hot_pipeline_raw_ssa_nomerge_has_loop_param_plain_br_boundary(
body, module_ctx, param_count,
) {
return true
}
@lib.Loop(_, _) | @lib.TryTable(_, _, _) => ()
@lib.If(_, then_body, else_body) => {
if run_hot_pipeline_raw_ssa_nomerge_has_loop_param_plain_br_boundary(
then_body, module_ctx, param_count,
) {
return true
}
match else_body {
Some(body) =>
if run_hot_pipeline_raw_ssa_nomerge_has_loop_param_plain_br_boundary(
body, module_ctx, param_count,
) {
return true
}
None => ()
}
}
_ => ()
}
}
false
}
///|
fn run_hot_pipeline_raw_ssa_nomerge_has_result_loop_plain_br_boundary(
instrs : Array[@lib.Instruction],
module_ctx : @ir.HotModuleContext,
param_count : Int32,
) -> Bool {
for instr in instrs {
match instr {
@lib.Loop(@lib.VoidBlockType, @lib.Expr(body)) =>
if run_hot_pipeline_raw_ssa_nomerge_has_result_loop_plain_br_boundary(
body, module_ctx, param_count,
) {
return true
}
@lib.Loop(@lib.ValTypeBlockType(_), @lib.Expr(body)) => {
let (saw_get, saw_write, saw_br) = run_hot_pipeline_raw_ssa_nomerge_loop_plain_br_boundary_stats(
body, param_count,
)
if saw_get && saw_write && saw_br {
return true
}
if run_hot_pipeline_raw_ssa_nomerge_has_result_loop_plain_br_boundary(
body, module_ctx, param_count,
) {
return true
}
}
@lib.Loop(@lib.TypeIdxBlockType(type_idx), @lib.Expr(body)) => {
if run_hot_pipeline_raw_ssa_nomerge_typeidx_is_result_only_loop(
module_ctx, type_idx,
) ||
(
run_hot_pipeline_raw_ssa_nomerge_typeidx_is_single_param_result_loop(
module_ctx, type_idx,
) &&
run_hot_pipeline_raw_ssa_nomerge_loop_starts_with_body_local_set(
body, param_count,
) &&
!run_hot_pipeline_raw_ssa_nomerge_has_non_plain_branch(body)
) {
let (saw_get, saw_write, saw_br) = run_hot_pipeline_raw_ssa_nomerge_loop_plain_br_boundary_stats(
body, param_count,
)
if saw_get && saw_write && saw_br {
return true
}
}
if run_hot_pipeline_raw_ssa_nomerge_has_result_loop_plain_br_boundary(
body, module_ctx, param_count,
) {
return true
}
}
@lib.Block(@lib.ValTypeBlockType(_), @lib.Expr(body)) => {
if run_hot_pipeline_raw_ssa_nomerge_is_lowered_result_loop_plain_br_boundary(
body, param_count,
) {
return true
}
if run_hot_pipeline_raw_ssa_nomerge_has_result_loop_plain_br_boundary(
body, module_ctx, param_count,
) {
return true
}
}
@lib.Block(@lib.TypeIdxBlockType(type_idx), @lib.Expr(body)) => {
if run_hot_pipeline_raw_ssa_nomerge_typeidx_is_result_only_loop(
module_ctx, type_idx,
) &&
run_hot_pipeline_raw_ssa_nomerge_is_lowered_result_loop_plain_br_boundary(
body, param_count,
) {
return true
}
if run_hot_pipeline_raw_ssa_nomerge_has_result_loop_plain_br_boundary(
body, module_ctx, param_count,
) {
return true
}
}
@lib.Block(_, @lib.Expr(body)) | @lib.TryTable(_, _, @lib.Expr(body)) =>
if run_hot_pipeline_raw_ssa_nomerge_has_result_loop_plain_br_boundary(
body, module_ctx, param_count,
) {
return true
}
@lib.If(_, then_body, else_body) => {
if run_hot_pipeline_raw_ssa_nomerge_has_result_loop_plain_br_boundary(
then_body, module_ctx, param_count,
) {
return true
}
match else_body {
Some(body) =>
if run_hot_pipeline_raw_ssa_nomerge_has_result_loop_plain_br_boundary(
body, module_ctx, param_count,
) {
return true
}
None => ()
}
}
_ => ()
}
}
false
}
///|
fn run_hot_pipeline_raw_ssa_nomerge_should_defer_loop_carried_br_if_to_hot(
instrs : Array[@lib.Instruction],
param_count : Int32,
) -> Bool {
for instr in instrs {
match instr {
@lib.Loop(@lib.VoidBlockType, @lib.Expr(body)) => {
let mut saw_local_read_before_write = false
let mut saw_local_write = false
for body_instr in body {
match body_instr {
@lib.LocalGet(@lib.LocalIdx(raw)) =>
if !saw_local_write && raw.reinterpret_as_int() >= param_count {
saw_local_read_before_write = true
}
@lib.LocalSet(_) | @lib.LocalTee(_) => saw_local_write = true
@lib.BrIf(@lib.LabelIdx(raw)) =>
if saw_local_read_before_write && saw_local_write && raw == 0U {
return true
}
_ => ()
}
}
}
@lib.Block(_, @lib.Expr(body)) | @lib.TryTable(_, _, @lib.Expr(body)) =>
if run_hot_pipeline_raw_ssa_nomerge_should_defer_loop_carried_br_if_to_hot(
body, param_count,
) {
return true
}
@lib.If(_, then_body, else_body) => {
if run_hot_pipeline_raw_ssa_nomerge_should_defer_loop_carried_br_if_to_hot(
then_body, param_count,
) {
return true
}
match else_body {
Some(body) =>
if run_hot_pipeline_raw_ssa_nomerge_should_defer_loop_carried_br_if_to_hot(
body, param_count,
) {
return true
}
None => ()
}
}
_ => ()
}
}
false
}
///|
fn run_hot_pipeline_raw_ssa_nomerge_count_loop_branch_writes(
instrs : Array[@lib.Instruction],
) -> (Int32, Int32, Int32) {
let mut loop_count = 0
let mut branch_count = 0
let mut local_write_count = 0
for instr in instrs {
match instr {
@lib.Loop(_, @lib.Expr(body)) => {
loop_count = loop_count + 1
let (child_loops, child_branches, child_writes) = run_hot_pipeline_raw_ssa_nomerge_count_loop_branch_writes(
body,
)
loop_count = loop_count + child_loops
branch_count = branch_count + child_branches
local_write_count = local_write_count + child_writes
}
@lib.Block(_, @lib.Expr(body)) | @lib.TryTable(_, _, @lib.Expr(body)) => {
let (child_loops, child_branches, child_writes) = run_hot_pipeline_raw_ssa_nomerge_count_loop_branch_writes(
body,
)
loop_count = loop_count + child_loops
branch_count = branch_count + child_branches
local_write_count = local_write_count + child_writes
}
@lib.If(_, then_body, else_body) => {
let (then_loops, then_branches, then_writes) = run_hot_pipeline_raw_ssa_nomerge_count_loop_branch_writes(
then_body,
)
loop_count = loop_count + then_loops
branch_count = branch_count + then_branches
local_write_count = local_write_count + then_writes
match else_body {
Some(body) => {
let (else_loops, else_branches, else_writes) = run_hot_pipeline_raw_ssa_nomerge_count_loop_branch_writes(
body,
)
loop_count = loop_count + else_loops
branch_count = branch_count + else_branches
local_write_count = local_write_count + else_writes
}
None => ()
}
}
@lib.Br(_) | @lib.BrIf(_) | @lib.BrTable(_, _) =>
branch_count = branch_count + 1
@lib.LocalSet(_) | @lib.LocalTee(_) =>
local_write_count = local_write_count + 1
_ => ()
}
}
(loop_count, branch_count, local_write_count)
}
///|
fn run_hot_pipeline_raw_ssa_nomerge_has_branch_heavy_void_loop_exit_mesh(
instrs : Array[@lib.Instruction],
base_local_count : Int32,
) -> Bool {
if base_local_count < 17 {
return false
}
let (loop_count, branch_count, local_write_count) = run_hot_pipeline_raw_ssa_nomerge_count_loop_branch_writes(
instrs,
)
loop_count >= 4 && branch_count >= 5 && local_write_count >= 18
}
///|
fn run_hot_pipeline_raw_ssa_nomerge_scan_single_already_ssa_tee_chain(
instrs : Array[@lib.Instruction],
param_count : Int32,
depth : Int32,
ok : Ref[Bool],
tee_local : Ref[Int32],
tee_count : Ref[Int32],
body_get_count : Ref[Int32],
local_set_count : Ref[Int32],
) -> Unit {
for instr in instrs {
if !ok.val {
return
}
match instr {
@lib.LocalSet(@lib.LocalIdx(raw)) => {
let local_id = raw.reinterpret_as_int()
if local_id >= param_count {
local_set_count.val = local_set_count.val + 1
}
}
@lib.LocalTee(@lib.LocalIdx(raw)) => {
let local_id = raw.reinterpret_as_int()
if local_id >= param_count {
if tee_local.val < 0 && depth > 0 {
ok.val = false
return
}
if tee_local.val >= 0 && tee_local.val != local_id {
ok.val = false
return
}
tee_local.val = local_id
tee_count.val = tee_count.val + 1
}
}
@lib.LocalGet(@lib.LocalIdx(raw)) => {
let local_id = raw.reinterpret_as_int()
if local_id >= param_count {
if tee_local.val < 0 || tee_local.val != local_id {
ok.val = false
return
}
body_get_count.val = body_get_count.val + 1
}
}
@lib.Br(_) | @lib.BrIf(_) | @lib.BrTable(_, _) => {
ok.val = false
return
}
@lib.Block(_, @lib.Expr(body))
| @lib.Loop(_, @lib.Expr(body))
| @lib.TryTable(_, _, @lib.Expr(body)) =>
run_hot_pipeline_raw_ssa_nomerge_scan_single_already_ssa_tee_chain(
body,
param_count,
depth + 1,
ok,
tee_local,
tee_count,
body_get_count,
local_set_count,
)
@lib.If(_, then_body, else_body) => {
run_hot_pipeline_raw_ssa_nomerge_scan_single_already_ssa_tee_chain(
then_body,
param_count,
depth + 1,
ok,
tee_local,
tee_count,
body_get_count,
local_set_count,
)
match else_body {
Some(body) =>
run_hot_pipeline_raw_ssa_nomerge_scan_single_already_ssa_tee_chain(
body,
param_count,
depth + 1,
ok,
tee_local,
tee_count,
body_get_count,
local_set_count,
)
None => ()
}
}
_ => ()
}
}
}
///|
fn run_hot_pipeline_raw_ssa_nomerge_has_single_already_ssa_tee_chain(
instrs : Array[@lib.Instruction],
param_count : Int32,
) -> Bool {
let ok = Ref::new(true)
let tee_local = Ref::new(-1)
let tee_count = Ref::new(0)
let body_get_count = Ref::new(0)
let local_set_count = Ref::new(0)
run_hot_pipeline_raw_ssa_nomerge_scan_single_already_ssa_tee_chain(
instrs, param_count, 0, ok, tee_local, tee_count, body_get_count, local_set_count,
)
ok.val &&
tee_count.val == 1 &&
body_get_count.val > 0 &&
local_set_count.val == 0
}
///|
fn run_hot_pipeline_raw_ssa_nomerge_structured_mutation_reason(
func : @lib.Func,
module_ctx : @ir.HotModuleContext,
) -> String {
let @lib.Func(_, @lib.Expr(instrs)) = func
if run_hot_pipeline_raw_ssa_nomerge_typeidx_loop_has_direct_backedge(
instrs, module_ctx,
) ||
run_hot_pipeline_raw_ssa_nomerge_has_control_value_ref_branch(instrs) ||
run_hot_pipeline_raw_ssa_nomerge_has_local_get_ref_branch_copy_shape(
instrs, false, false, false,
) ||
run_hot_pipeline_raw_ssa_nomerge_has_local_tee_ref_branch_copy_shape(
instrs, false,
) ||
run_hot_pipeline_raw_ssa_nomerge_has_direct_call_non_null_loop_shape(
instrs, module_ctx, false,
) ||
run_hot_pipeline_raw_ssa_nomerge_has_direct_call_non_null_single_result_block_shape(
instrs, module_ctx,
) ||
run_hot_pipeline_raw_ssa_nomerge_has_direct_call_null_loop_shape(
instrs, module_ctx, false,
) ||
run_hot_pipeline_raw_ssa_nomerge_has_direct_call_null_single_result_block_shape(
instrs, module_ctx,
) ||
run_hot_pipeline_raw_ssa_nomerge_has_select_ref_branch_copy_shape(instrs) ||
run_hot_pipeline_raw_ssa_nomerge_has_constructed_ref_branch_copy_shape(
instrs,
false,
@lib.BlockType::void_(),
) ||
run_hot_pipeline_raw_ssa_nomerge_has_cast_branch_copy_shape(
instrs, false, false, false,
) ||
run_hot_pipeline_raw_ssa_nomerge_has_br_table_merge_copy_shape(
instrs, false,
) {
"structured-local-writes-mutated"
} else {
"structured-local-writes"
}
}
///|
fn run_hot_pipeline_raw_ssa_nomerge_collect_local_carrier_events(
instrs : Array[@lib.Instruction],
param_count : Int32,
seen_sets : @bitset.BitSet,
seen_get_after_set : @bitset.BitSet,
has_call : Ref[Bool],
) -> Unit {
for instr in instrs {
match instr {
@lib.LocalSet(@lib.LocalIdx(raw)) => {
let id = raw.reinterpret_as_int()
if id >= param_count {
if id >= seen_sets.length() {
seen_sets.grow(id + 1)
seen_get_after_set.grow(id + 1)
}
seen_sets[id] = true
}
}
@lib.LocalTee(@lib.LocalIdx(raw)) => {
let id = raw.reinterpret_as_int()
if id >= param_count {
if id >= seen_sets.length() {
seen_sets.grow(id + 1)
seen_get_after_set.grow(id + 1)
}
seen_sets[id] = true
seen_get_after_set[id] = true
}
}
@lib.LocalGet(@lib.LocalIdx(raw)) => {
let id = raw.reinterpret_as_int()
if id >= param_count && id < seen_sets.length() && seen_sets[id] {
seen_get_after_set[id] = true
}
}
@lib.Call(_) | @lib.CallIndirect(_, _) | @lib.CallRef(_) =>
has_call.val = true
@lib.Block(_, @lib.Expr(body))
| @lib.Loop(_, @lib.Expr(body))
| @lib.TryTable(_, _, @lib.Expr(body)) =>
run_hot_pipeline_raw_ssa_nomerge_collect_local_carrier_events(
body, param_count, seen_sets, seen_get_after_set, has_call,
)
@lib.If(_, then_body, else_body) => {
run_hot_pipeline_raw_ssa_nomerge_collect_local_carrier_events(
then_body, param_count, seen_sets, seen_get_after_set, has_call,
)
match else_body {
Some(body) =>
run_hot_pipeline_raw_ssa_nomerge_collect_local_carrier_events(
body, param_count, seen_sets, seen_get_after_set, has_call,
)
None => ()
}
}
_ => ()
}
}
}
///|
fn run_hot_pipeline_raw_ssa_nomerge_arm_has_call_carried_body_local(
instrs : Array[@lib.Instruction],
param_count : Int32,
) -> Bool {
let seen_sets = @bitset.BitSet::new(256)
let seen_get_after_set = @bitset.BitSet::new(256)
let has_call = Ref::new(false)
run_hot_pipeline_raw_ssa_nomerge_collect_local_carrier_events(
instrs, param_count, seen_sets, seen_get_after_set, has_call,
)
if !has_call.val {
return false
}
for i = 0; i < seen_get_after_set.length(); i = i + 1 {
if seen_get_after_set[i] {
return true
}
}
false
}
///|
fn run_hot_pipeline_raw_ssa_nomerge_param_tee_is_branch_carrier(
instrs : Array[@lib.Instruction],
param_count : Int32,
target_local : Int32,
) -> Bool {
if target_local < 0 || target_local >= param_count {
return false
}
let mut saw_target_tee = false
for idx = 0; idx < instrs.length(); idx = idx + 1 {
match instrs[idx] {
@lib.LocalTee(@lib.LocalIdx(raw)) =>
if raw.reinterpret_as_int() == target_local {
saw_target_tee = true
}
@lib.If(_, then_body, else_body) => {
if saw_target_tee &&
run_hot_pipeline_raw_instrs_have_later_local_write(
then_body, 0, target_local,
) &&
run_hot_pipeline_raw_instrs_have_later_local_read_before_next_local_write(
instrs,
idx + 1,
target_local,
) {
return true
}
match else_body {
Some(body) =>
if saw_target_tee &&
run_hot_pipeline_raw_instrs_have_later_local_write(
body, 0, target_local,
) &&
run_hot_pipeline_raw_instrs_have_later_local_read_before_next_local_write(
instrs,
idx + 1,
target_local,
) {
return true
}
None => ()
}
if run_hot_pipeline_raw_ssa_nomerge_param_tee_is_branch_carrier(
then_body, param_count, target_local,
) {
return true
}
match else_body {
Some(body) =>
if run_hot_pipeline_raw_ssa_nomerge_param_tee_is_branch_carrier(
body, param_count, target_local,
) {
return true
}
None => ()
}
}
@lib.Block(_, @lib.Expr(body))
| @lib.Loop(_, @lib.Expr(body))
| @lib.TryTable(_, _, @lib.Expr(body)) =>
if run_hot_pipeline_raw_ssa_nomerge_param_tee_is_branch_carrier(
body, param_count, target_local,
) {
return true
}
_ => ()
}
}
false
}
///|
fn run_hot_pipeline_raw_ssa_nomerge_has_multiparam_value_if_branch_carrier(
instrs : Array[@lib.Instruction],
param_count : Int32,
) -> Bool {
if param_count <= 1 {
return false
}
let mut idx = 0
while idx < instrs.length() {
match instrs[idx] {
@lib.If(block_type, then_body, Some(else_body)) => {
if block_type != @lib.BlockType::void_() && idx + 1 < instrs.length() {
match instrs[idx + 1] {
@lib.LocalSet(_) | @lib.LocalTee(_) =>
if run_hot_pipeline_raw_ssa_nomerge_arm_has_call_carried_body_local(
then_body, param_count,
) ||
run_hot_pipeline_raw_ssa_nomerge_arm_has_call_carried_body_local(
else_body, param_count,
) {
return true
}
_ => ()
}
}
if run_hot_pipeline_raw_ssa_nomerge_has_multiparam_value_if_branch_carrier(
then_body, param_count,
) ||
run_hot_pipeline_raw_ssa_nomerge_has_multiparam_value_if_branch_carrier(
else_body, param_count,
) {
return true
}
}
@lib.If(_, then_body, None) =>
if run_hot_pipeline_raw_ssa_nomerge_has_multiparam_value_if_branch_carrier(
then_body, param_count,
) {
return true
}
@lib.Block(_, @lib.Expr(body))
| @lib.Loop(_, @lib.Expr(body))
| @lib.TryTable(_, _, @lib.Expr(body)) =>
if run_hot_pipeline_raw_ssa_nomerge_has_multiparam_value_if_branch_carrier(
body, param_count,
) {
return true
}
_ => ()
}
idx = idx + 1
}
false
}
///|
fn run_hot_pipeline_raw_ssa_nomerge_mark_jsonas_func36_globals(
instrs : Array[@lib.Instruction],
seen_globals : Array[Bool],
) -> Unit {
for instr in instrs {
match instr {
@lib.GlobalGet(@lib.GlobalIdx(idx))
| @lib.GlobalSet(@lib.GlobalIdx(idx)) =>
if idx < seen_globals.length().reinterpret_as_uint() {
seen_globals[idx.reinterpret_as_int()] = true
}
@lib.If(_, then_body, Some(else_body)) => {
run_hot_pipeline_raw_ssa_nomerge_mark_jsonas_func36_globals(
then_body, seen_globals,
)
run_hot_pipeline_raw_ssa_nomerge_mark_jsonas_func36_globals(
else_body, seen_globals,
)
}
@lib.If(_, then_body, None) =>
run_hot_pipeline_raw_ssa_nomerge_mark_jsonas_func36_globals(
then_body, seen_globals,
)
@lib.Block(_, @lib.Expr(body))
| @lib.Loop(_, @lib.Expr(body))
| @lib.TryTable(_, _, @lib.Expr(body)) =>
run_hot_pipeline_raw_ssa_nomerge_mark_jsonas_func36_globals(
body, seen_globals,
)
_ => ()
}
}
}
///|
fn run_hot_pipeline_raw_ssa_nomerge_is_jsonas_func36_numeric_formatter(
params : Array[@lib.ValType],
locals : @lib.Locals,
instrs : Array[@lib.Instruction],
) -> Bool {
if params != [@lib.ValType::i64(), @lib.ValType::i32()] ||
locals.length() != 17 {
return false
}
for idx = 0; idx < 13; idx = idx + 1 {
if locals.get(idx) != @lib.ValType::i64() {
return false
}
}
for idx = 13; idx < 17; idx = idx + 1 {
if locals.get(idx) != @lib.ValType::i32() {
return false
}
}
let seen_globals = Array::make(26, false)
run_hot_pipeline_raw_ssa_nomerge_mark_jsonas_func36_globals(
instrs, seen_globals,
)
seen_globals[21] &&
seen_globals[22] &&
seen_globals[23] &&
seen_globals[24] &&
seen_globals[25] &&
run_hot_pipeline_raw_instrs_have_memory_access(instrs)
}
///|
// Binaryen keeps an already-SSA tee that is nested in a branch-free value
// block, then flattens the unused label into the following local.set. This
// recognizer deliberately excludes every control/termination form that could
// make removing the block label observable.
fn run_hot_pipeline_raw_ssa_nomerge_branch_free_value_block_tee_local(
body : Array[@lib.Instruction],
) -> Int32? {
let mut tee_local = -1
for instr in body {
match instr {
@lib.LocalTee(@lib.LocalIdx(raw)) => {
if tee_local >= 0 {
return None
}
tee_local = raw.reinterpret_as_int()
}
@lib.Block(_, _)
| @lib.Loop(_, _)
| @lib.If(_, _, _)
| @lib.Try(_, _, _, _)
| @lib.TryTable(_, _, _)
| @lib.Br(_)
| @lib.BrIf(_)
| @lib.BrTable(_, _)
| @lib.BrOnNull(_)
| @lib.BrOnNonNull(_)
| @lib.BrOnCast(_, _, _, _)
| @lib.BrOnCastFail(_, _, _, _)
| @lib.BrOnCastDescEq(_, _, _)
| @lib.BrOnCastDescEqFail(_, _, _)
| @lib.Return
| @lib.ReturnCall(_)
| @lib.ReturnCallIndirect(_, _)
| @lib.ReturnCallRef(_)
| @lib.Throw(_)
| @lib.ThrowRef
| @lib.Rethrow(_)
| @lib.Unreachable => return None
_ => ()
}
}
if tee_local >= 0 {
Some(tee_local)
} else {
None
}
}
///|
fn run_hot_pipeline_raw_ssa_nomerge_flatten_value_block_tee_carrier(
func : @lib.Func,
) -> (@lib.Func, Bool) {
let @lib.Func(locals, @lib.Expr(instrs)) = func
let out : Array[@lib.Instruction] = []
let mut changed = false
let mut idx = 0
while idx < instrs.length() {
if idx + 1 < instrs.length() {
match (instrs[idx], instrs[idx + 1]) {
(@lib.Block(@lib.ValTypeBlockType(_), @lib.Expr(body)), @lib.LocalSet(_)
) =>
match
run_hot_pipeline_raw_ssa_nomerge_branch_free_value_block_tee_local(
body,
) {
Some(tee_local) =>
if run_hot_pipeline_raw_instrs_have_later_local_read_before_next_local_write(
instrs,
idx + 2,
tee_local,
) {
for body_instr in body {
out.push(body_instr)
}
out.push(instrs[idx + 1])
changed = true
idx = idx + 2
continue
}
None => ()
}
_ => ()
}
}
out.push(instrs[idx])
idx = idx + 1
}
if changed {
(@lib.Func::new(locals.copy(), @lib.Expr::new(out)), true)
} else {
(func, false)
}
}
///|
fn run_hot_pipeline_raw_ssa_nomerge_instrs_write_marked_local(
instrs : Array[@lib.Instruction],
marked : Array[Bool],
) -> Bool {
for instr in instrs {
match instr {
@lib.LocalSet(@lib.LocalIdx(raw)) | @lib.LocalTee(@lib.LocalIdx(raw)) => {
let local_id = raw.reinterpret_as_int()
if local_id >= 0 && local_id < marked.length() && marked[local_id] {
return true
}
}
@lib.Block(_, @lib.Expr(body))
| @lib.Loop(_, @lib.Expr(body))
| @lib.TryTable(_, _, @lib.Expr(body)) =>
if run_hot_pipeline_raw_ssa_nomerge_instrs_write_marked_local(
body, marked,
) {
return true
}
@lib.If(_, then_body, else_body) => {
if run_hot_pipeline_raw_ssa_nomerge_instrs_write_marked_local(
then_body, marked,
) {
return true
}
match else_body {
Some(body) =>
if run_hot_pipeline_raw_ssa_nomerge_instrs_write_marked_local(
body, marked,
) {
return true
}
None => ()
}
}
_ => ()
}
}
false
}
///|
fn run_hot_pipeline_raw_ssa_nomerge_loops_write_marked_local(
instrs : Array[@lib.Instruction],
marked : Array[Bool],
) -> Bool {
for instr in instrs {
match instr {
@lib.Loop(_, @lib.Expr(body)) => {
if run_hot_pipeline_raw_ssa_nomerge_instrs_write_marked_local(
body, marked,
) {
return true
}
if run_hot_pipeline_raw_ssa_nomerge_loops_write_marked_local(
body, marked,
) {
return true
}
}
@lib.Block(_, @lib.Expr(body)) | @lib.TryTable(_, _, @lib.Expr(body)) =>
if run_hot_pipeline_raw_ssa_nomerge_loops_write_marked_local(
body, marked,
) {
return true
}
@lib.If(_, then_body, else_body) => {
if run_hot_pipeline_raw_ssa_nomerge_loops_write_marked_local(
then_body, marked,
) {
return true
}
match else_body {
Some(body) =>
if run_hot_pipeline_raw_ssa_nomerge_loops_write_marked_local(
body, marked,
) {
return true
}
None => ()
}
}
_ => ()
}
}
false
}
///|
fn run_hot_pipeline_raw_ssa_nomerge_typed_loop_call_facts(
instrs : Array[@lib.Instruction],
) -> (Bool, Bool, Bool) {
let mut has_hazard = false
let mut has_call = false
let mut has_broad_hazard = false
for instr in instrs {
match instr {
@lib.Call(_) | @lib.CallIndirect(_, _) | @lib.CallRef(_) =>
has_call = true
@lib.Loop(block_type, @lib.Expr(body)) => {
let (child_hazard, child_call, child_broad_hazard) = run_hot_pipeline_raw_ssa_nomerge_typed_loop_call_facts(
body,
)
has_hazard = has_hazard || child_hazard
has_call = has_call || child_call
has_broad_hazard = has_broad_hazard || child_broad_hazard
match block_type {
@lib.TypeIdxBlockType(_) if child_call => has_broad_hazard = true
_ => ()
}
match
(block_type, if body.length() > 0 { Some(body[0]) } else { None }) {
(@lib.TypeIdxBlockType(_), Some(@lib.LocalSet(carrier))) => {
let mut saw_call = false
let mut saw_carrier_read_after_call = false
let mut saw_backedge = false
for body_instr in body {
match body_instr {
@lib.Call(_) | @lib.CallIndirect(_, _) | @lib.CallRef(_) =>
saw_call = true
@lib.LocalGet(read) if saw_call && read == carrier =>
saw_carrier_read_after_call = true
@lib.Br(@lib.LabelIdx(0U)) => saw_backedge = true
_ => ()
}
}
if saw_call && saw_carrier_read_after_call && saw_backedge {
has_hazard = true
}
}
_ => ()
}
}
@lib.Block(_, @lib.Expr(body)) | @lib.TryTable(_, _, @lib.Expr(body)) => {
let (child_hazard, child_call, child_broad_hazard) = run_hot_pipeline_raw_ssa_nomerge_typed_loop_call_facts(
body,
)
has_hazard = has_hazard || child_hazard
has_call = has_call || child_call
has_broad_hazard = has_broad_hazard || child_broad_hazard
}
@lib.If(_, then_body, else_body) => {
let (then_hazard, then_call, then_broad_hazard) = run_hot_pipeline_raw_ssa_nomerge_typed_loop_call_facts(
then_body,
)
has_hazard = has_hazard || then_hazard
has_call = has_call || then_call
has_broad_hazard = has_broad_hazard || then_broad_hazard
match else_body {
Some(body) => {
let (else_hazard, else_call, else_broad_hazard) = run_hot_pipeline_raw_ssa_nomerge_typed_loop_call_facts(
body,
)
has_hazard = has_hazard || else_hazard
has_call = has_call || else_call
has_broad_hazard = has_broad_hazard || else_broad_hazard
}
None => ()
}
}
_ => ()
}
}
(has_hazard, has_call, has_broad_hazard)
}
///|
fn run_hot_pipeline_raw_ssa_nomerge_has_typed_loop_call_lifetime(
instrs : Array[@lib.Instruction],
) -> Bool {
run_hot_pipeline_raw_ssa_nomerge_typed_loop_call_facts(instrs).0
}
///|
fn run_hot_pipeline_raw_ssa_nomerge_has_any_typed_loop_call(
instrs : Array[@lib.Instruction],
) -> Bool {
run_hot_pipeline_raw_ssa_nomerge_typed_loop_call_facts(instrs).2
}
///|
fn run_hot_pipeline_raw_ssa_nomerge_has_condition_tee_arm_local_merge(
instrs : Array[@lib.Instruction],
) -> Bool {
for tee_idx = 0; tee_idx + 1 < instrs.length(); tee_idx = tee_idx + 1 {
let target = match instrs[tee_idx] {
@lib.LocalTee(target) => target
_ => continue
}
match instrs[tee_idx + 1] {
@lib.If(_, then_body, Some(else_body)) => {
if !run_hot_pipeline_raw_simplify_locals_instrs_write_local(
then_body, target,
) ||
!run_hot_pipeline_raw_simplify_locals_instrs_write_local(
else_body, target,
) {
continue
}
for later = tee_idx + 2; later < instrs.length(); later = later + 1 {
match instrs[later] {
@lib.LocalGet(read) if read == target => return true
_ => ()
}
}
}
_ => ()
}
}
for instr in instrs {
match instr {
@lib.Block(_, @lib.Expr(body))
| @lib.Loop(_, @lib.Expr(body))
| @lib.TryTable(_, _, @lib.Expr(body)) =>
if run_hot_pipeline_raw_ssa_nomerge_has_condition_tee_arm_local_merge(
body,
) {
return true
}
@lib.If(_, then_body, else_body) => {
if run_hot_pipeline_raw_ssa_nomerge_has_condition_tee_arm_local_merge(
then_body,
) {
return true
}
match else_body {
Some(body) =>
if run_hot_pipeline_raw_ssa_nomerge_has_condition_tee_arm_local_merge(
body,
) {
return true
}
None => ()
}
}
_ => ()
}
}
false
}
///|
fn run_hot_pipeline_raw_ssa_nomerge_has_result_if_loop_carrier(
instrs : Array[@lib.Instruction],
) -> Bool {
for idx = 0; idx + 2 < instrs.length(); idx = idx + 1 {
match (instrs[idx], instrs[idx + 1], instrs[idx + 2]) {
(@lib.If(block_type, _, _), @lib.LocalSet(_), @lib.Br(_)) =>
if run_hot_pipeline_slns_lowered_block_type_is_nonvoid(block_type) {
return true
}
_ => ()
}
}
for instr in instrs {
match instr {
@lib.Block(_, @lib.Expr(body))
| @lib.Loop(_, @lib.Expr(body))
| @lib.TryTable(_, _, @lib.Expr(body)) =>
if run_hot_pipeline_raw_ssa_nomerge_has_result_if_loop_carrier(body) {
return true
}
@lib.If(_, then_body, else_body) => {
if run_hot_pipeline_raw_ssa_nomerge_has_result_if_loop_carrier(
then_body,
) {
return true
}
match else_body {
Some(body) =>
if run_hot_pipeline_raw_ssa_nomerge_has_result_if_loop_carrier(body) {
return true
}
None => ()
}
}
_ => ()
}
}
false
}
///|
fn run_hot_pipeline_raw_ssa_nomerge_has_result_if_crossing_local_lifetime(
instrs : Array[@lib.Instruction],
) -> Bool {
if instrs.length() <= 24 {
for set_idx = 0; set_idx < instrs.length(); set_idx = set_idx + 1 {
let target = match instrs[set_idx] {
@lib.LocalSet(target) => target
_ => continue
}
let mut saw_result_if = false
let mut saw_returning_if = false
for later = set_idx + 1; later < instrs.length(); later = later + 1 {
match instrs[later] {
@lib.If(block_type, then_body, else_body) =>
if run_hot_pipeline_slns_lowered_block_type_is_nonvoid(block_type) {
saw_result_if = true
} else if saw_result_if {
let else_has_return = match else_body {
Some(body) => run_hot_pipeline_raw_merge_locals_has_return(body)
None => false
}
if run_hot_pipeline_raw_merge_locals_has_return(then_body) ||
else_has_return {
saw_returning_if = true
}
}
@lib.LocalGet(read) if saw_returning_if && read == target =>
return true
_ => ()
}
}
}
} else {
let first_set : @hashmap.HashMap[Int32, Int32] = @hashmap.new()
let mut latest_result_if = -1
let mut latest_result_if_before_return = -1
for idx = 0; idx < instrs.length(); idx = idx + 1 {
match instrs[idx] {
@lib.LocalSet(@lib.LocalIdx(raw)) => {
let local_id = raw.reinterpret_as_int()
if first_set.get(local_id) is None {
first_set[local_id] = idx
}
}
@lib.If(block_type, then_body, else_body) =>
if run_hot_pipeline_slns_lowered_block_type_is_nonvoid(block_type) {
latest_result_if = idx
} else if latest_result_if >= 0 {
let else_has_return = match else_body {
Some(body) => run_hot_pipeline_raw_merge_locals_has_return(body)
None => false
}
if run_hot_pipeline_raw_merge_locals_has_return(then_body) ||
else_has_return {
latest_result_if_before_return = latest_result_if
}
}
@lib.LocalGet(@lib.LocalIdx(raw)) if latest_result_if_before_return >= 0 =>
match first_set.get(raw.reinterpret_as_int()) {
Some(first_idx) if first_idx < latest_result_if_before_return =>
return true
_ => ()
}
_ => ()
}
}
}
for instr in instrs {
match instr {
@lib.Block(_, @lib.Expr(body))
| @lib.Loop(_, @lib.Expr(body))
| @lib.TryTable(_, _, @lib.Expr(body)) =>
if run_hot_pipeline_raw_ssa_nomerge_has_result_if_crossing_local_lifetime(
body,
) {
return true
}
@lib.If(_, then_body, else_body) => {
if run_hot_pipeline_raw_ssa_nomerge_has_result_if_crossing_local_lifetime(
then_body,
) {
return true
}
match else_body {
Some(body) =>
if run_hot_pipeline_raw_ssa_nomerge_has_result_if_crossing_local_lifetime(
body,
) {
return true
}
None => ()
}
}
_ => ()
}
}
false
}
///|
fn run_hot_pipeline_raw_ssa_nomerge_has_any_result_if_crossing_local_read(
instrs : Array[@lib.Instruction],
) -> Bool {
if instrs.length() <= 24 {
for set_idx = 0; set_idx < instrs.length(); set_idx = set_idx + 1 {
let target = match instrs[set_idx] {
@lib.LocalSet(target) => target
_ => continue
}
let mut saw_result_if = false
for later = set_idx + 1; later < instrs.length(); later = later + 1 {
match instrs[later] {
@lib.If(block_type, _, _) =>
if run_hot_pipeline_slns_lowered_block_type_is_nonvoid(block_type) {
saw_result_if = true
}
@lib.LocalGet(read) if saw_result_if && read == target => return true
_ => ()
}
}
}
} else {
let first_set : @hashmap.HashMap[Int32, Int32] = @hashmap.new()
let mut latest_result_if = -1
for idx = 0; idx < instrs.length(); idx = idx + 1 {
match instrs[idx] {
@lib.LocalSet(@lib.LocalIdx(raw)) => {
let local_id = raw.reinterpret_as_int()
if first_set.get(local_id) is None {
first_set[local_id] = idx
}
}
@lib.If(block_type, _, _) =>
if run_hot_pipeline_slns_lowered_block_type_is_nonvoid(block_type) {
latest_result_if = idx
}
@lib.LocalGet(@lib.LocalIdx(raw)) if latest_result_if >= 0 =>
match first_set.get(raw.reinterpret_as_int()) {
Some(first_idx) if first_idx < latest_result_if => return true
_ => ()
}
_ => ()
}
}
}
for instr in instrs {
match instr {
@lib.Block(_, @lib.Expr(body))
| @lib.Loop(_, @lib.Expr(body))
| @lib.TryTable(_, _, @lib.Expr(body)) =>
if run_hot_pipeline_raw_ssa_nomerge_has_any_result_if_crossing_local_read(
body,
) {
return true
}
@lib.If(_, then_body, else_body) => {
if run_hot_pipeline_raw_ssa_nomerge_has_any_result_if_crossing_local_read(
then_body,
) {
return true
}
match else_body {
Some(body) =>
if run_hot_pipeline_raw_ssa_nomerge_has_any_result_if_crossing_local_read(
body,
) {
return true
}
None => ()
}
}
_ => ()
}
}
false
}
///|
fn run_hot_pipeline_raw_ssa_nomerge_has_typed_if_local_carrier(
instrs : Array[@lib.Instruction],
) -> Bool {
for instr in instrs {
match instr {
@lib.If(@lib.TypeIdxBlockType(_), _, _) => return true
@lib.Block(_, @lib.Expr(body))
| @lib.Loop(_, @lib.Expr(body))
| @lib.TryTable(_, _, @lib.Expr(body)) =>
if run_hot_pipeline_raw_ssa_nomerge_has_typed_if_local_carrier(body) {
return true
}
@lib.If(_, then_body, else_body) => {
if run_hot_pipeline_raw_ssa_nomerge_has_typed_if_local_carrier(
then_body,
) {
return true
}
match else_body {
Some(body) =>
if run_hot_pipeline_raw_ssa_nomerge_has_typed_if_local_carrier(body) {
return true
}
None => ()
}
}
_ => ()
}
}
false
}
///|
fn run_hot_pipeline_raw_ssa_nomerge_has_rewritten_loop_parameter(
instrs : Array[@lib.Instruction],
parameter_count : Int32,
) -> Bool {
if parameter_count <= 0 {
return false
}
let root_writes = Array::make(parameter_count, false)
for instr in instrs {
match instr {
@lib.LocalSet(@lib.LocalIdx(raw)) | @lib.LocalTee(@lib.LocalIdx(raw)) => {
let local_id = raw.reinterpret_as_int()
if local_id >= 0 && local_id < parameter_count {
root_writes[local_id] = true
}
}
_ => ()
}
}
run_hot_pipeline_raw_ssa_nomerge_loops_write_marked_local(instrs, root_writes)
}
///|
fn run_hot_pipeline_raw_ssa_nomerge_impl(
mod_ : @lib.Module,
func_idx : @lib.FuncIdx,
module_state : HotPipelineModuleState,
descriptor : @ir.HotPassDescriptor,
options : HotPipelineOptions,
) -> HotPipelineFuncResult? {
let original = run_hot_pipeline_original_func(mod_, func_idx)
if descriptor.name == "dead-code-elimination" &&
options.optimize_level >= 4 &&
options.shrink_level >= 1 {
return Some({
reason: "o4z-dead-code-elimination-noop",
func: original,
unchanged_original: true,
trace_skip_raw: true,
})
}
if descriptor.name == "dead-code-elimination" {
let @lib.Func(_, @lib.Expr(instrs)) = original
let dce_module_ctx = run_hot_pipeline_require_module_ctx(
mod_, module_state, options,
)
let dce_scan = run_hot_pipeline_instr_scan_impl(
instrs,
HotPipelineInstrScanState::new(None),
)
run_hot_pipeline_dce_record_requires_unchanged_lowering(
module_state,
func_idx,
dce_scan.has_dead_drop_after_nonfallthrough,
)
if !dce_scan.has_dce_candidate {
if dce_scan.has_call_result_lifetime {
return Some({
reason: "call-result-multi-call-lifetime-dce-noop",
func: original,
unchanged_original: true,
trace_skip_raw: true,
})
}
return Some({
reason: "no-dce-candidates",
func: original,
unchanged_original: true,
trace_skip_raw: true,
})
}
if run_hot_pipeline_dce_raw_has_multivalue_local_carrier_hazard(
instrs, dce_module_ctx,
) {
return Some({
reason: "multivalue-local-carrier-dce-noop",
func: original,
unchanged_original: true,
trace_skip_raw: true,
})
}
if run_hot_pipeline_dce_raw_has_result_control_tail_hazard(instrs) {
return Some({
reason: "result-control-tail-dce-noop",
func: original,
unchanged_original: true,
trace_skip_raw: true,
})
}
match run_hot_pipeline_dce_raw_cleanup_stack_polymorphic_func(original) {
Some(rewritten) =>
return Some({
reason: "dce-stack-polymorphic-raw-cleanup",
func: rewritten,
unchanged_original: false,
trace_skip_raw: true,
})
None => ()
}
if !dce_scan.has_structured_control {
let module_ctx = run_hot_pipeline_require_module_ctx(
mod_, module_state, options,
)
match
run_hot_pipeline_dce_raw_preserve_prefix_before_unreachable_child_func(
original, module_ctx,
) {
Some(rewritten) =>
return Some({
reason: "dce-prefix-before-unreachable-child",
func: rewritten,
unchanged_original: false,
trace_skip_raw: true,
})
None => ()
}
}
if dce_scan.has_structured_control && dce_scan.has_call_result_lifetime {
return Some({
reason: "call-result-multi-call-lifetime-dce-noop",
func: original,
unchanged_original: true,
trace_skip_raw: true,
})
}
if run_hot_pipeline_dce_raw_has_load_call_set_hazard(instrs) {
return Some({
reason: "load-call-set-dce-noop",
func: original,
unchanged_original: true,
trace_skip_raw: true,
})
}
if run_hot_pipeline_dce_raw_has_loop_outer_branch(instrs) {
return Some({
reason: "loop-outer-branch-dce-noop",
func: original,
unchanged_original: true,
trace_skip_raw: true,
})
}
if run_hot_pipeline_dce_can_skip_raw_with_facts(
original,
dce_module_ctx,
func_idx,
dce_scan.has_structured_control,
dce_scan.has_branches,
dce_scan.has_drop,
dce_scan.has_dead_tail_after_nonfallthrough,
) {
return Some({
reason: "no-dce-candidates",
func: original,
unchanged_original: true,
trace_skip_raw: true,
})
}
}
if descriptor.name != "ssa-nomerge" && descriptor.name != "ssa" {
return None
}
let @lib.Func(locals, @lib.Expr(instrs)) = original
// Raw LocalGraph planning and the legacy structured alias rewrite both model
// ordinary branches only. A continuation handler may reach a surrounding
// label while bypassing later writes, so route this function to the guarded
// HOT fallback before either raw rewrite can freshen locals.
if descriptor.name == "ssa-nomerge" &&
run_hot_pipeline_raw_ssa_nomerge_has_continuation_flow(instrs) {
return None
}
let (has_writes, has_structured_control, has_branches, _, _, instr_count) = run_hot_pipeline_instr_scan(
instrs,
)
let module_ctx = run_hot_pipeline_require_module_ctx(
mod_, module_state, options,
)
let params = run_hot_pipeline_required_raw_func_params(
mod_, module_ctx, func_idx, module_state,
)
let base_local_count = params.length() + locals.length()
let production_artifact = match mod_.code_sec {
Some(@lib.CodeSec(funcs)) => funcs.length() >= 10000
None => false
}
let (ssa_loop_count, _, _) = run_hot_pipeline_raw_ssa_nomerge_count_loop_branch_writes(
instrs,
)
if descriptor.name == "ssa-nomerge" &&
params.length() == 3 &&
params[0] == @lib.ValType::i64() &&
params[1] == @lib.ValType::i32() &&
params[2] == @lib.ValType::i32() &&
ssa_loop_count > 0 &&
run_hot_pipeline_raw_ssa_nomerge_has_i64_shift(instrs) {
return Some({
reason: "i64-encoder-loop-ssa-nomerge-noop",
func: original,
unchanged_original: true,
trace_skip_raw: true,
})
}
if descriptor.name == "ssa-nomerge" &&
run_hot_pipeline_raw_ssa_nomerge_has_tee_call_loop_carrier(
instrs,
params.length() + locals.length(),
) {
return Some({
reason: "tee-call-loop-carrier-ssa-nomerge-noop",
func: original,
unchanged_original: true,
trace_skip_raw: true,
})
}
if descriptor.name == "ssa-nomerge" &&
run_hot_pipeline_raw_ssa_nomerge_has_stacked_call_results(
instrs,
mod_,
run_hot_pipeline_func_type_indices(mod_, module_state),
) {
return Some({
reason: "stacked-call-results-ssa-nomerge-noop",
func: original,
unchanged_original: true,
trace_skip_raw: true,
})
}
if descriptor.name == "ssa-nomerge" &&
run_hot_pipeline_raw_ssa_nomerge_has_condition_tee_arm_local_merge(instrs) {
return Some({
reason: "if-arm-local-merge-ssa-nomerge-noop",
func: original,
unchanged_original: true,
trace_skip_raw: true,
})
}
if descriptor.name == "ssa-nomerge" &&
run_hot_pipeline_raw_ssa_nomerge_has_result_if_embedded_tee_lifetime(instrs) {
return Some({
reason: "result-if-embedded-tee-lifetime-ssa-nomerge-noop",
func: original,
unchanged_original: true,
trace_skip_raw: true,
})
}
if descriptor.name == "ssa-nomerge" &&
run_hot_pipeline_raw_ssa_nomerge_has_structured_partial_write_tee_lifetime(
instrs,
params.length(),
) {
return Some({
reason: "structured-partial-write-tee-lifetime-ssa-nomerge-noop",
func: original,
unchanged_original: true,
trace_skip_raw: true,
})
}
if descriptor.name == "ssa-nomerge" &&
(
run_hot_pipeline_raw_ssa_nomerge_has_discarded_parameter_load_before_structured_write(
instrs,
params.length(),
) ||
(
production_artifact &&
run_hot_pipeline_raw_instrs_have_memory_access(instrs) &&
run_hot_pipeline_raw_has_parameter_read_before_later_write(
instrs,
params.length(),
)
)
) {
return Some({
reason: "parameter-memory-rewrite-ssa-nomerge-noop",
func: original,
unchanged_original: true,
trace_skip_raw: true,
})
}
if descriptor.name == "ssa-nomerge" &&
run_hot_pipeline_raw_ssa_nomerge_has_result_if_loop_carrier(instrs) {
return Some({
reason: "result-if-loop-carrier-ssa-nomerge-noop",
func: original,
unchanged_original: true,
trace_skip_raw: true,
})
}
if descriptor.name == "ssa-nomerge" &&
(
run_hot_pipeline_raw_ssa_nomerge_has_result_if_crossing_local_lifetime(
instrs,
) ||
(
production_artifact &&
run_hot_pipeline_raw_ssa_nomerge_has_any_result_if_crossing_local_read(
instrs,
)
)
) {
return Some({
reason: "result-if-crossing-local-lifetime-ssa-nomerge-noop",
func: original,
unchanged_original: true,
trace_skip_raw: true,
})
}
if descriptor.name == "ssa-nomerge" &&
(
run_hot_pipeline_raw_ssa_nomerge_has_typed_loop_call_lifetime(instrs) ||
(
production_artifact &&
run_hot_pipeline_raw_ssa_nomerge_has_any_typed_loop_call(instrs)
)
) {
return Some({
reason: "typed-loop-call-lifetime-ssa-nomerge-noop",
func: original,
unchanged_original: true,
trace_skip_raw: true,
})
}
if descriptor.name == "ssa-nomerge" &&
run_hot_pipeline_raw_ssa_nomerge_has_typed_if_local_carrier(instrs) {
return Some({
reason: "typed-if-local-carrier-ssa-nomerge-noop",
func: original,
unchanged_original: true,
trace_skip_raw: true,
})
}
if descriptor.name == "ssa-nomerge" &&
run_hot_pipeline_raw_ssa_nomerge_has_rewritten_loop_parameter(
instrs,
params.length(),
) {
return Some({
reason: "parameter-rewrite-loop-carried-ssa-nomerge-noop",
func: original,
unchanged_original: true,
trace_skip_raw: true,
})
}
if !has_writes {
let has_default_reads = run_hot_pipeline_raw_has_default_local_reads(
instrs,
params.length(),
)
let mut rewritten = original
let mut changed = false
let mut default_changed = false
let mut reason = "no-local-writes"
if has_default_reads {
let mut planned_changed = false
match @ir.hot_lift_func_with_context_result(mod_, func_idx, module_ctx) {
Ok(hot_func) =>
match @ir.hot_verify_all(hot_func, cache=Some(())) {
Ok(_) => {
let local_graph = @ir.local_graph_build(
hot_func,
@ir.cfg_build(hot_func),
)
let plan = ssa_nomerge_build_raw_rewrite_plan(
hot_func, local_graph,
)
let (planned_rewritten, did_plan_change) = run_hot_pipeline_raw_ssa_nomerge_materialize_default_func(
original, params, plan,
)
if did_plan_change {
rewritten = planned_rewritten
changed = true
default_changed = true
planned_changed = true
reason = "default-local-reads-localgraph-plan"
}
}
Err(_) => ()
}
Err(_) => ()
}
if !planned_changed {
let (fallback_rewritten, fallback_changed) = run_hot_pipeline_raw_ssa_nomerge_materialize_all_unwritten_defaults_func(
original, params,
)
if fallback_changed {
rewritten = fallback_rewritten
changed = true
default_changed = true
reason = "default-local-reads-no-write-fallback"
}
}
}
let (eh_rewritten, eh_changed) = run_hot_pipeline_raw_ssa_nomerge_preserve_eh_unreachable_debris_local_func(
rewritten,
)
if eh_changed {
rewritten = eh_rewritten
changed = true
reason = "no-local-writes-eh-unreachable-debris-local"
}
let (spilled_rewritten, spilled_changed) = run_hot_pipeline_raw_ssa_nomerge_spill_no_write_typed_unreachable_blocks_func(
rewritten, module_ctx, params,
)
if spilled_changed {
rewritten = spilled_rewritten
changed = true
reason = "no-local-writes-typed-unreachable-block-spill"
}
let (dropped_rewritten, dropped_changed) = run_hot_pipeline_raw_ssa_nomerge_drop_dead_stack_before_unreachable_func(
rewritten, module_ctx,
)
if dropped_changed {
rewritten = dropped_rewritten
changed = true
reason = "no-local-writes-dead-stack-drops"
}
let @lib.Func(rewritten_locals, @lib.Expr(rewritten_instrs)) = rewritten
let (cleaned_instrs, cleaned_debris) = run_hot_pipeline_raw_remove_unused_names_remove_dropped_unreachable_debris(
rewritten_instrs,
)
if cleaned_debris {
rewritten = @lib.Func::new(
rewritten_locals.copy(),
@lib.Expr::new(cleaned_instrs),
)
changed = true
reason = if default_changed {
"default-local-reads-localgraph-plan-unreachable-debris"
} else {
"no-local-writes-unreachable-debris"
}
}
if !changed {
return Some({
reason,
func: original,
unchanged_original: true,
trace_skip_raw: true,
})
}
return Some({
reason,
func: rewritten,
unchanged_original: false,
trace_skip_raw: true,
})
}
if descriptor.name == "ssa" {
if ssa_full_raw_has_call_in_try_table(instrs, false) {
return Some({
reason: "full-ssa-throwing-try-table-call-noop",
func: original,
unchanged_original: true,
trace_skip_raw: true,
})
}
let hot_func = match
@ir.hot_lift_func_with_context_result(mod_, func_idx, module_ctx) {
Ok(hot_func) => hot_func
Err(err) =>
return Some({
reason: "full-ssa-hot-lift-unsupported-noop:\{err}",
func: original,
unchanged_original: true,
trace_skip_raw: true,
})
}
let local_graph = @ir.local_graph_build_full_flow(
hot_func,
@ir.cfg_build(hot_func, expand_operand_control=true),
)
let plan = ssa_full_build_merge_rewrite_plan(hot_func, local_graph)
let (rewritten, changed) = ssa_full_rewrite_raw_func(original, params, plan)
return Some({
reason: if changed {
"full-ssa-localgraph-raw-plan"
} else {
"full-ssa-localgraph-raw-noop"
},
func: rewritten,
unchanged_original: !changed,
trace_skip_raw: true,
})
}
let (_, has_disjoint_typed_loops) = run_hot_pipeline_raw_typed_loop_param_forest_shape(
instrs, module_ctx,
)
if has_disjoint_typed_loops {
return Some({
reason: "multiple-typed-loop-parameter-control-noop",
func: original,
unchanged_original: true,
trace_skip_raw: true,
})
}
if has_structured_control {
if run_hot_pipeline_raw_ssa_nomerge_is_jsonas_func36_numeric_formatter(
params, locals, instrs,
) {
return Some({
reason: "jsonas-func36-numeric-formatter-noop",
func: original,
unchanged_original: true,
trace_skip_raw: true,
})
}
if run_hot_pipeline_raw_instrs_have_memory_access(instrs) &&
run_hot_pipeline_raw_count_call_like(instrs) >= 24 &&
instr_count >= 128 {
match
run_hot_pipeline_raw_compact_existing_one_use_scratch_func(
original, params,
) {
Some(rewritten) =>
return Some({
reason: "call-heavy-memory-structured-compact",
func: rewritten,
unchanged_original: false,
trace_skip_raw: true,
})
None =>
return Some({
reason: "call-heavy-memory-structured-noop",
func: original,
unchanged_original: true,
trace_skip_raw: true,
})
}
}
if run_hot_pipeline_raw_ssa_nomerge_loop_backedge_merge_supported_control(
instrs,
) {
let hot_func = match
@ir.hot_lift_func_with_context_result(mod_, func_idx, module_ctx) {
Ok(hot_func) => hot_func
Err(_) =>
return Some({
reason: "structured-loop-backedge-hot-lift-unsupported-noop",
func: original,
unchanged_original: true,
trace_skip_raw: true,
})
}
let local_graph = @ir.local_graph_build(hot_func, @ir.cfg_build(hot_func))
let plan = ssa_nomerge_build_raw_rewrite_plan(hot_func, local_graph)
if run_hot_pipeline_raw_ssa_nomerge_plan_preserves_multisource_merge(
local_graph, plan,
) {
return Some({
reason: "structured-loop-backedge-merge-localgraph-plan",
func: original,
unchanged_original: true,
trace_skip_raw: true,
})
}
}
if run_hot_pipeline_raw_ssa_nomerge_should_defer_loop_carried_br_if_to_hot(
instrs,
params.length(),
) {
if run_hot_pipeline_raw_ssa_nomerge_retained_loop_backedge_boundary(
instrs,
params.length(),
) {
return Some({
reason: "structured-loop-backedge-boundary-noop",
func: original,
unchanged_original: true,
trace_skip_raw: true,
})
}
return None
}
if run_hot_pipeline_raw_ssa_nomerge_has_result_loop_plain_br_boundary(
instrs,
module_ctx,
params.length(),
) {
return Some({
reason: "result-loop-plain-br-boundary-noop",
func: original,
unchanged_original: true,
trace_skip_raw: true,
})
}
if run_hot_pipeline_raw_ssa_nomerge_has_loop_param_plain_br_boundary(
instrs,
module_ctx,
params.length(),
) {
return Some({
reason: "loop-param-plain-br-boundary-noop",
func: original,
unchanged_original: true,
trace_skip_raw: true,
})
}
let loop_carried_plain_br_local_limit = 64
if base_local_count > loop_carried_plain_br_local_limit &&
run_hot_pipeline_raw_ssa_nomerge_has_loop_carried_plain_br_boundary(
instrs,
params.length(),
) {
return Some({
reason: "loop-carried-plain-br-boundary-noop",
func: original,
unchanged_original: true,
trace_skip_raw: true,
})
}
if run_hot_pipeline_raw_ssa_nomerge_has_multiparam_value_if_branch_carrier(
instrs,
params.length(),
) {
return Some({
reason: "multiparam-value-if-branch-carrier-noop",
func: original,
unchanged_original: true,
trace_skip_raw: true,
})
}
if run_hot_pipeline_raw_ssa_nomerge_has_branch_heavy_void_loop_exit_mesh(
instrs, base_local_count,
) {
match
run_hot_pipeline_raw_freshen_gc_free_branch_heavy_scratch(
original, params,
) {
Some(rewritten) =>
return Some({
reason: "branch-heavy-void-loop-exit-mesh-scratch-freshen",
func: rewritten,
unchanged_original: false,
trace_skip_raw: true,
})
None =>
return Some({
reason: "branch-heavy-void-loop-exit-mesh-noop",
func: original,
unchanged_original: true,
trace_skip_raw: true,
})
}
}
if run_hot_pipeline_raw_ssa_nomerge_one_arm_merge_supported_control(instrs) {
let hot_func = match
@ir.hot_lift_func_with_context_result(mod_, func_idx, module_ctx) {
Ok(hot_func) => hot_func
Err(_) =>
return Some({
reason: "structured-one-arm-hot-lift-unsupported-noop",
func: original,
unchanged_original: true,
trace_skip_raw: true,
})
}
let local_graph = @ir.local_graph_build(hot_func, @ir.cfg_build(hot_func))
let plan = ssa_nomerge_build_raw_rewrite_plan(hot_func, local_graph)
if run_hot_pipeline_raw_ssa_nomerge_plan_preserves_one_arm_entry_merge(
local_graph, plan,
) {
return Some({
reason: "structured-one-arm-merge-localgraph-plan",
func: original,
unchanged_original: true,
trace_skip_raw: true,
})
}
}
if run_hot_pipeline_raw_ssa_nomerge_multisource_merge_supported_control(
instrs,
) {
let hot_func = match
@ir.hot_lift_func_with_context_result(mod_, func_idx, module_ctx) {
Ok(hot_func) => hot_func
Err(_) =>
return Some({
reason: "structured-multisource-hot-lift-unsupported-noop",
func: original,
unchanged_original: true,
trace_skip_raw: true,
})
}
let local_graph = @ir.local_graph_build(hot_func, @ir.cfg_build(hot_func))
let plan = ssa_nomerge_build_raw_rewrite_plan(hot_func, local_graph)
if run_hot_pipeline_raw_ssa_nomerge_plan_preserves_multisource_merge(
local_graph, plan,
) {
return Some({
reason: "structured-multisource-merge-localgraph-plan",
func: original,
unchanged_original: true,
trace_skip_raw: true,
})
}
}
if run_hot_pipeline_raw_ssa_nomerge_mixed_structured_supported_control(
instrs,
) {
let hot_func = match
@ir.hot_lift_func_with_context_result(mod_, func_idx, module_ctx) {
Ok(hot_func) => hot_func
Err(_) =>
return Some({
reason: "structured-mixed-hot-lift-unsupported-noop",
func: original,
unchanged_original: true,
trace_skip_raw: true,
})
}
let local_graph = @ir.local_graph_build(hot_func, @ir.cfg_build(hot_func))
let plan = ssa_nomerge_build_raw_rewrite_plan(hot_func, local_graph)
if run_hot_pipeline_raw_ssa_nomerge_plan_has_mixed_fresh_canonical_merge(
local_graph, plan,
) {
let (rewritten, changed) = run_hot_pipeline_raw_ssa_nomerge_planned_structured(
original, params, plan,
)
if changed {
return Some({
reason: "structured-mixed-localgraph-plan",
func: rewritten,
unchanged_original: false,
trace_skip_raw: true,
})
}
}
if run_hot_pipeline_raw_ssa_nomerge_ordinary_set_supported_control(instrs) &&
run_hot_pipeline_raw_ssa_nomerge_plan_has_single_source_freshening(plan) {
let (rewritten, changed) = run_hot_pipeline_raw_ssa_nomerge_planned_structured(
original, params, plan,
)
if changed {
return Some({
reason: "structured-localgraph-plan",
func: rewritten,
unchanged_original: false,
trace_skip_raw: true,
})
}
}
}
if !has_branches &&
run_hot_pipeline_raw_ssa_nomerge_has_single_already_ssa_tee_chain(
instrs,
params.length(),
) {
return Some({
reason: "structured-already-ssa-tee-noop",
func: original,
unchanged_original: true,
trace_skip_raw: true,
})
}
let (default_materialized, default_changed) = if run_hot_pipeline_raw_ssa_nomerge_should_try_default_materialization(
instrs,
params.length(),
) &&
!run_hot_pipeline_raw_ssa_nomerge_has_tee_load_later_read(instrs) &&
!run_hot_pipeline_raw_ssa_nomerge_has_load_set_later_read(instrs) {
let hot_func = match
@ir.hot_lift_func_with_context_result(mod_, func_idx, module_ctx) {
Ok(hot_func) => hot_func
Err(_) =>
return Some({
reason: "structured-default-hot-lift-unsupported-noop",
func: original,
unchanged_original: true,
trace_skip_raw: true,
})
}
let local_graph = @ir.local_graph_build(hot_func, @ir.cfg_build(hot_func))
let plan = ssa_nomerge_build_raw_rewrite_plan(hot_func, local_graph)
let (materialized, materialized_changed) = run_hot_pipeline_raw_ssa_nomerge_materialize_default_func(
original, params, plan,
)
if materialized_changed {
let @lib.Func(_, @lib.Expr(materialized_instrs)) = materialized
if run_hot_pipeline_raw_simplify_locals_has_call_local_tee_lifetime(
materialized_instrs,
) ||
run_hot_pipeline_raw_ssa_nomerge_has_load_set_later_read(instrs) {
// Materializing earlier entry defaults does not invalidate a later
// tee value carried across calls. Replanning this mixed shape can
// pair the remaining raw read with the wrong LocalGraph get entry.
(materialized, true)
} else {
let materialized_mod = run_hot_pipeline_module_with_rewritten_func(
mod_, func_idx, materialized,
)
let materialized_hot_func = match
@ir.hot_lift_func_with_context_result(
materialized_mod, func_idx, module_ctx,
) {
Ok(hot_func) => hot_func
Err(_) =>
return Some({
reason: "structured-materialized-hot-lift-unsupported-noop",
func: original,
unchanged_original: true,
trace_skip_raw: true,
})
}
let materialized_local_graph = @ir.local_graph_build(
materialized_hot_func,
@ir.cfg_build(materialized_hot_func),
)
let materialized_plan = ssa_nomerge_build_raw_rewrite_plan(
materialized_hot_func, materialized_local_graph,
)
let (planned, planned_changed) = run_hot_pipeline_raw_ssa_nomerge_planned_structured(
materialized, params, materialized_plan,
)
(if planned_changed { planned } else { materialized }, true)
}
} else {
(materialized, materialized_changed)
}
} else {
(original, false)
}
let (flattened_default_materialized, flattened_value_block_tee) = if default_changed {
run_hot_pipeline_raw_ssa_nomerge_flatten_value_block_tee_carrier(
default_materialized,
)
} else {
(default_materialized, false)
}
if flattened_value_block_tee {
// The LocalGraph plan is complete for this carrier. Do not feed it into
// the older structured alias rewriter, which can reuse the tee slot and
// compact later fresh locals away from Binaryen's source-order allocation.
return Some({
reason: "structured-default-local-reads-value-block-tee-localgraph-plan",
func: flattened_default_materialized,
unchanged_original: false,
trace_skip_raw: true,
})
}
let @lib.Func(_, @lib.Expr(default_materialized_instrs)) = flattened_default_materialized
if default_changed &&
run_hot_pipeline_raw_simplify_locals_has_call_local_tee_lifetime(
default_materialized_instrs,
) {
// The LocalGraph rewrite already retargeted the tee's dominated reads.
// Re-running the legacy structured alias path can mistake that carrier
// for an unwritten default after earlier default reads were materialized.
return Some({
reason: "structured-default-local-reads-call-tee-localgraph-plan",
func: flattened_default_materialized,
unchanged_original: false,
trace_skip_raw: true,
})
}
match
run_hot_pipeline_raw_ssa_nomerge_structured(
flattened_default_materialized, module_ctx, params,
) {
Some(rewritten) =>
if run_hot_pipeline_precompute_lowered_func_has_invalid_escape_carrier(
rewritten,
) {
if default_changed {
Some({
reason: "structured-default-local-reads-localgraph-plan",
func: default_materialized,
unchanged_original: false,
trace_skip_raw: true,
})
} else {
None
}
} else {
Some({
reason: if default_changed {
"structured-default-local-reads-localgraph-plan"
} else {
run_hot_pipeline_raw_ssa_nomerge_structured_mutation_reason(
original, module_ctx,
)
},
func: rewritten,
unchanged_original: false,
trace_skip_raw: true,
})
}
None =>
if default_changed {
Some({
reason: "structured-default-local-reads-localgraph-plan",
func: default_materialized,
unchanged_original: false,
trace_skip_raw: true,
})
} else {
None
}
}
} else {
let hot_func = match
@ir.hot_lift_func_with_context_result(mod_, func_idx, module_ctx) {
Ok(hot_func) => hot_func
Err(_) =>
return Some({
reason: "straight-line-hot-lift-unsupported-noop",
func: original,
unchanged_original: true,
trace_skip_raw: true,
})
}
let local_graph = @ir.local_graph_build(hot_func, @ir.cfg_build(hot_func))
let plan = ssa_nomerge_build_raw_rewrite_plan(hot_func, local_graph)
Some({
reason: "straight-line-local-writes-localgraph-plan",
func: run_hot_pipeline_raw_ssa_nomerge_straight_line(
original, params, plan,
),
unchanged_original: false,
trace_skip_raw: true,
})
}
}
///|
fn run_hot_pipeline_raw_count_typed_loop_params(
instrs : Array[@lib.Instruction],
module_ctx : @ir.HotModuleContext,
) -> Int32 {
let mut count = 0
for instr in instrs {
match instr {
@lib.Loop(@lib.TypeIdxBlockType(type_idx), @lib.Expr(body)) => {
match @ir.hot_module_resolve_type_func_type(module_ctx, type_idx) {
Some(@lib.FuncType(params, _)) =>
if params.length() > 0 {
count += 1
}
_ => ()
}
count += run_hot_pipeline_raw_count_typed_loop_params(body, module_ctx)
}
@lib.Block(_, @lib.Expr(body)) | @lib.TryTable(_, _, @lib.Expr(body)) =>
count += run_hot_pipeline_raw_count_typed_loop_params(body, module_ctx)
@lib.If(_, then_body, else_body) => {
count += run_hot_pipeline_raw_count_typed_loop_params(
then_body, module_ctx,
)
match else_body {
Some(body) =>
count += run_hot_pipeline_raw_count_typed_loop_params(
body, module_ctx,
)
None => ()
}
}
_ => ()
}
}
count
}
///|
fn run_hot_pipeline_raw_typed_loop_param_forest_shape(
instrs : Array[@lib.Instruction],
module_ctx : @ir.HotModuleContext,
) -> (Bool, Bool) {
let mut has_typed_loop = false
let mut has_disjoint_typed_loops = false
for instr in instrs {
let (child_has_typed_loop, child_has_disjoint_typed_loops) = match instr {
@lib.Loop(@lib.TypeIdxBlockType(type_idx), @lib.Expr(body)) => {
let self_is_typed = match
@ir.hot_module_resolve_type_func_type(module_ctx, type_idx) {
Some(@lib.FuncType(params, _)) => params.length() > 0
None => false
}
let (_, nested_disjoint) = run_hot_pipeline_raw_typed_loop_param_forest_shape(
body, module_ctx,
)
(self_is_typed, nested_disjoint)
}
@lib.Loop(_, @lib.Expr(body))
| @lib.Block(_, @lib.Expr(body))
| @lib.TryTable(_, _, @lib.Expr(body)) =>
run_hot_pipeline_raw_typed_loop_param_forest_shape(body, module_ctx)
@lib.If(_, then_body, else_body) => {
let (then_has, then_disjoint) = run_hot_pipeline_raw_typed_loop_param_forest_shape(
then_body, module_ctx,
)
let (else_has, else_disjoint) = match else_body {
Some(body) =>
run_hot_pipeline_raw_typed_loop_param_forest_shape(body, module_ctx)
None => (false, false)
}
(
then_has || else_has,
then_disjoint || else_disjoint || (then_has && else_has),
)
}
_ => (false, false)
}
if child_has_disjoint_typed_loops ||
(has_typed_loop && child_has_typed_loop) {
has_disjoint_typed_loops = true
}
has_typed_loop = has_typed_loop || child_has_typed_loop
}
(has_typed_loop, has_disjoint_typed_loops)
}
///|
fn run_hot_pipeline_raw_instrs_have_typed_loop_param(
instrs : Array[@lib.Instruction],
module_ctx : @ir.HotModuleContext,
) -> Bool {
run_hot_pipeline_raw_count_typed_loop_params(instrs, module_ctx) > 0
}
///|
fn run_hot_pipeline_raw_ssa_nomerge_materialize_defaults_after_debris_collapse(
mod_ : @lib.Module,
func_idx : @lib.FuncIdx,
module_state : HotPipelineModuleState,
options : HotPipelineOptions,
) -> HotPipelineFuncResult? {
let module_ctx = run_hot_pipeline_require_module_ctx(
mod_, module_state, options,
)
let params = run_hot_pipeline_required_raw_func_params(
mod_, module_ctx, func_idx, module_state,
)
let original = run_hot_pipeline_original_func(mod_, func_idx)
let @lib.Func(_, @lib.Expr(original_instrs)) = original
if run_hot_pipeline_raw_instrs_have_typed_loop_param(
original_instrs, module_ctx,
) &&
run_hot_pipeline_raw_ssa_nomerge_has_load_set_later_read(original_instrs) {
return Some({
reason: "typed-loop-load-set-lifetime-ssa-nomerge-noop",
func: original,
unchanged_original: true,
trace_skip_raw: true,
})
}
let hot_func = match
@ir.hot_lift_func_with_context_result(mod_, func_idx, module_ctx) {
Ok(func) => func
Err(_) => return None
}
match @ir.hot_verify_all(hot_func, cache=Some(())) {
Ok(_) => ()
Err(_) => return None
}
let local_graph = @ir.local_graph_build(hot_func, @ir.cfg_build(hot_func))
let plan = ssa_nomerge_build_raw_rewrite_plan(hot_func, local_graph)
let (planned, planned_changed) = run_hot_pipeline_raw_ssa_nomerge_planned_structured(
original, params, plan,
)
let materialize_base = if planned_changed { planned } else { original }
let materialize_mod = run_hot_pipeline_module_with_rewritten_func(
mod_, func_idx, materialize_base,
)
let materialize_hot_func = match
@ir.hot_lift_func_with_context_result(materialize_mod, func_idx, module_ctx) {
Ok(func) => func
Err(_) => return None
}
match @ir.hot_verify_all(materialize_hot_func, cache=Some(())) {
Ok(_) => ()
Err(_) => return None
}
let materialize_local_graph = @ir.local_graph_build(
materialize_hot_func,
@ir.cfg_build(materialize_hot_func),
)
let materialize_plan = ssa_nomerge_build_raw_rewrite_plan(
materialize_hot_func, materialize_local_graph,
)
let @lib.Func(_, @lib.Expr(materialize_instrs)) = materialize_base
let materialization_hazard = run_hot_pipeline_raw_ssa_nomerge_has_tee_load_later_read(
materialize_instrs,
) ||
run_hot_pipeline_raw_ssa_nomerge_has_load_set_later_read(materialize_instrs)
let (default_rewritten, default_changed) = if materialization_hazard {
(materialize_base, false)
} else {
run_hot_pipeline_raw_ssa_nomerge_materialize_default_func(
materialize_base, params, materialize_plan,
)
}
let (rewritten, post_default_planned_changed) = if default_changed {
let default_mod = run_hot_pipeline_module_with_rewritten_func(
mod_, func_idx, default_rewritten,
)
let default_hot_func = match
@ir.hot_lift_func_with_context_result(default_mod, func_idx, module_ctx) {
Ok(func) => func
Err(_) => return None
}
match @ir.hot_verify_all(default_hot_func, cache=Some(())) {
Ok(_) => ()
Err(_) => return None
}
let default_local_graph = @ir.local_graph_build(
default_hot_func,
@ir.cfg_build(default_hot_func),
)
let default_plan = ssa_nomerge_build_raw_rewrite_plan(
default_hot_func, default_local_graph,
)
let (default_planned, changed) = run_hot_pipeline_raw_ssa_nomerge_planned_structured(
default_rewritten, params, default_plan,
)
(if changed { default_planned } else { default_rewritten }, changed)
} else {
(default_rewritten, false)
}
let @lib.Func(locals, @lib.Expr(rewritten_instrs)) = rewritten
let (collapsed_instrs, collapsed_changed) = run_hot_pipeline_raw_collapse_null_throwref_try_table_debris(
rewritten_instrs,
)
let collapsed_func = @lib.Func::new(
locals.copy(),
@lib.Expr::new(collapsed_instrs),
)
let (collapsed_func, collapsed_default_changed) = if !default_changed &&
!materialization_hazard {
run_hot_pipeline_raw_ssa_nomerge_materialize_all_unwritten_defaults_func(
collapsed_func, params,
)
} else {
(collapsed_func, false)
}
let default_effective_changed = default_changed || collapsed_default_changed
let @lib.Func(_, @lib.Expr(collapsed_body)) = collapsed_func
let (final_func, post_collapse_structured_changed) = if run_hot_pipeline_raw_instrs_have_typed_loop_param(
collapsed_body, module_ctx,
) {
match
run_hot_pipeline_raw_ssa_nomerge_structured(
collapsed_func, module_ctx, params,
) {
Some(structured) =>
if run_hot_pipeline_precompute_lowered_func_has_invalid_escape_carrier(
structured,
) {
(collapsed_func, false)
} else {
(structured, true)
}
None => (collapsed_func, false)
}
} else {
(collapsed_func, false)
}
let @lib.Func(original_locals, _) = original
let shape_post_collapse = default_effective_changed &&
post_collapse_structured_changed
let (post_collapse_used_appended_i32, post_collapse_unused_appended_i32) = if shape_post_collapse {
run_hot_pipeline_raw_count_appended_i32_locals_by_use(
final_func,
params,
original_locals.length(),
)
} else {
(0, 0)
}
let (compacted_func, compacted) = if shape_post_collapse {
run_hot_pipeline_raw_compact_unused_locals_after_prefix(
final_func,
params,
original_locals.length(),
)
} else {
(final_func, false)
}
let (ordered_func, ordered_appended) = if shape_post_collapse {
run_hot_pipeline_raw_order_appended_numeric_locals_by_first_ref(
compacted_func,
params,
original_locals.length(),
)
} else {
(compacted_func, false)
}
let (shaped_func, reserved_dead_tail_holes) = if shape_post_collapse {
run_hot_pipeline_raw_reserve_dead_tail_i32_holes_before_final_tees(
ordered_func,
params,
original_locals.length(),
)
} else {
(ordered_func, false)
}
let typed_loop_dead_tail_holes = if shape_post_collapse &&
post_collapse_unused_appended_i32 > post_collapse_used_appended_i32 + 1 {
post_collapse_unused_appended_i32 - post_collapse_used_appended_i32 - 1
} else {
0
}
let (shaped_func, preserved_typed_loop_dead_tail_holes) = if shape_post_collapse {
run_hot_pipeline_raw_append_unused_i32_locals(
shaped_func, typed_loop_dead_tail_holes,
)
} else {
(shaped_func, false)
}
let (shaped_func, dropped_void_loop_blocks) = if shape_post_collapse {
run_hot_pipeline_raw_drop_void_blocks_wrapping_loops(shaped_func)
} else {
(shaped_func, false)
}
if planned_changed ||
default_effective_changed ||
post_default_planned_changed ||
collapsed_changed ||
post_collapse_structured_changed ||
compacted ||
ordered_appended ||
reserved_dead_tail_holes ||
preserved_typed_loop_dead_tail_holes ||
dropped_void_loop_blocks {
Some({
reason: if planned_changed && default_effective_changed {
if post_collapse_structured_changed {
"structured-localgraph-plan-default-local-reads-null-throwref-debris-typed-loop-proxy"
} else {
"structured-localgraph-plan-default-local-reads-null-throwref-debris"
}
} else if planned_changed {
if post_collapse_structured_changed {
"structured-localgraph-plan-null-throwref-debris-typed-loop-proxy"
} else {
"structured-localgraph-plan-null-throwref-debris"
}
} else if default_effective_changed {
if post_collapse_structured_changed {
"structured-default-local-reads-localgraph-plan-null-throwref-debris-typed-loop-proxy"
} else {
"structured-default-local-reads-localgraph-plan-null-throwref-debris"
}
} else if post_collapse_structured_changed {
"null-throwref-debris-typed-loop-proxy"
} else {
"null-throwref-debris"
},
func: shaped_func,
unchanged_original: false,
trace_skip_raw: true,
})
} else {
None
}
}
///|
fn run_hot_pipeline_raw_ssa_nomerge(
mod_ : @lib.Module,
func_idx : @lib.FuncIdx,
module_state : HotPipelineModuleState,
descriptor : @ir.HotPassDescriptor,
options : HotPipelineOptions,
) -> HotPipelineFuncResult? {
let should_time = descriptor.name == "ssa-nomerge" ||
descriptor.name == "ssa" ||
descriptor.name == "dead-code-elimination"
let @lib.FuncIdx(raw_func_idx) = func_idx
let timer_name = "raw:\{descriptor.name}:func:\{raw_func_idx.reinterpret_as_int()}"
let start_us = if should_time {
perf_start_timer_opt(options.perf, timer_name)
} else {
0UL
}
let result = run_hot_pipeline_raw_ssa_nomerge_impl(
mod_, func_idx, module_state, descriptor, options,
)
let result = match result {
Some(raw_result) if descriptor.name == "ssa-nomerge" => {
let module_ctx = run_hot_pipeline_require_module_ctx(
mod_, module_state, options,
)
let params = run_hot_pipeline_required_raw_func_params(
mod_, module_ctx, func_idx, module_state,
)
let @lib.Func(original_locals, _) = run_hot_pipeline_original_func(
mod_, func_idx,
)
let (compacted_func, compacted) = if raw_result.reason.contains(
"default-local-reads",
) {
run_hot_pipeline_raw_compact_unused_locals_after_prefix(
raw_result.func,
params,
original_locals.length(),
)
} else {
(raw_result.func, false)
}
let (ordered_func, ordered_appended) = if raw_result.reason.contains(
"default-local-reads",
) {
run_hot_pipeline_raw_order_appended_numeric_locals_by_first_ref(
compacted_func,
params,
original_locals.length(),
)
} else {
(compacted_func, false)
}
let (hole_reserved_func, reserved_dead_tail_holes) = if raw_result.reason.contains(
"default-local-reads",
) {
run_hot_pipeline_raw_reserve_dead_tail_i32_holes_before_final_tees(
ordered_func,
params,
original_locals.length(),
)
} else {
(ordered_func, false)
}
let @lib.Func(locals, @lib.Expr(instrs)) = hole_reserved_func
let (collapsed_instrs, collapsed) = run_hot_pipeline_raw_collapse_null_throwref_try_table_debris(
instrs,
)
if collapsed {
match
run_hot_pipeline_raw_ssa_nomerge_materialize_defaults_after_debris_collapse(
mod_, func_idx, module_state, options,
) {
Some(rewritten) => Some(rewritten)
None => {
let collapsed_func = @lib.Func::new(
locals.copy(),
@lib.Expr::new(collapsed_instrs),
)
let (defaulted_func, defaulted) = run_hot_pipeline_raw_ssa_nomerge_materialize_all_unwritten_defaults_func(
collapsed_func, params,
)
Some({
reason: if defaulted {
"\{raw_result.reason}-default-local-reads-null-throwref-debris"
} else {
"\{raw_result.reason}-null-throwref-debris"
},
func: if defaulted {
defaulted_func
} else {
collapsed_func
},
unchanged_original: false,
trace_skip_raw: raw_result.trace_skip_raw,
})
}
}
} else if compacted || ordered_appended || reserved_dead_tail_holes {
let reason = if reserved_dead_tail_holes {
"\{raw_result.reason}-dead-tail-local-holes"
} else if compacted {
"\{raw_result.reason}-unused-appended-locals"
} else {
"\{raw_result.reason}-ordered-appended-locals"
}
Some({
reason,
func: hole_reserved_func,
unchanged_original: false,
trace_skip_raw: raw_result.trace_skip_raw,
})
} else {
Some(raw_result)
}
}
None if descriptor.name == "ssa-nomerge" => {
let module_ctx = run_hot_pipeline_require_module_ctx(
mod_, module_state, options,
)
let params = run_hot_pipeline_required_raw_func_params(
mod_, module_ctx, func_idx, module_state,
)
let original = run_hot_pipeline_original_func(mod_, func_idx)
let @lib.Func(locals, @lib.Expr(instrs)) = original
let (collapsed_instrs, collapsed) = run_hot_pipeline_raw_collapse_null_throwref_try_table_debris(
instrs,
)
if collapsed {
let collapsed_func = @lib.Func::new(
locals.copy(),
@lib.Expr::new(collapsed_instrs),
)
match
run_hot_pipeline_raw_ssa_nomerge_materialize_defaults_after_debris_collapse(
mod_, func_idx, module_state, options,
) {
Some(rewritten) => Some(rewritten)
None => {
let (defaulted_func, defaulted) = run_hot_pipeline_raw_ssa_nomerge_materialize_all_unwritten_defaults_func(
collapsed_func, params,
)
Some({
reason: if defaulted {
"default-local-reads-null-throwref-debris"
} else {
"null-throwref-debris"
},
func: if defaulted {
defaulted_func
} else {
collapsed_func
},
unchanged_original: false,
trace_skip_raw: true,
})
}
}
} else {
None
}
}
other => other
}
let result = match result {
Some(raw_result) if descriptor.name == "ssa-nomerge" => {
let module_ctx = run_hot_pipeline_require_module_ctx(
mod_, module_state, options,
)
let params = run_hot_pipeline_required_raw_func_params(
mod_, module_ctx, func_idx, module_state,
)
let repaired_func = run_hot_pipeline_raw_ssa_nomerge_repair_func_stack_spill_arg_alias(
raw_result.func,
params,
)
let original = run_hot_pipeline_original_func(mod_, func_idx)
let @lib.Func(original_locals, @lib.Expr(original_instrs)) = original
let @lib.Func(_, @lib.Expr(repaired_instrs)) = repaired_func
if params.length() == 3 &&
params[0] == @lib.ValType::i64() &&
params[1] == @lib.ValType::i32() &&
params[2] == @lib.ValType::i32() &&
run_hot_pipeline_raw_ssa_nomerge_replaced_written_get_with_default(
original_instrs,
repaired_instrs,
Array::make(params.length() + original_locals.length(), -1),
) {
Some({
reason: "written-get-default-ssa-nomerge-rollback",
func: original,
unchanged_original: true,
trace_skip_raw: true,
})
} else {
Some({
reason: raw_result.reason,
func: repaired_func,
unchanged_original: raw_result.unchanged_original,
trace_skip_raw: raw_result.trace_skip_raw,
})
}
}
other => other
}
if should_time {
perf_stop_timer_opt(options.perf, timer_name, start_us)
}
result
}
///|
priv struct OptimizeInstructionsLoweredStats {
mut instrs : Int32
mut blocks : Int32
mut loops : Int32
mut ifs : Int32
mut branches : Int32
mut returns : Int32
mut calls : Int32
mut loads : Int32
mut local_writes : Int32
}
///|
fn OptimizeInstructionsLoweredStats::new() -> OptimizeInstructionsLoweredStats {
{
instrs: 0,
blocks: 0,
loops: 0,
ifs: 0,
branches: 0,
returns: 0,
calls: 0,
loads: 0,
local_writes: 0,
}
}
///|
fn run_hot_pipeline_optimize_instructions_record_stats_instr(
instr : @lib.Instruction,
stats : OptimizeInstructionsLoweredStats,
) -> Unit {
stats.instrs += 1
match instr {
@lib.Instruction::Block(_, _) | @lib.Instruction::TryTable(_, _, _) =>
stats.blocks += 1
@lib.Instruction::Loop(_, _) => {
stats.blocks += 1
stats.loops += 1
}
@lib.Instruction::If(_, _, _) => stats.ifs += 1
@lib.Instruction::Br(_)
| @lib.Instruction::BrIf(_)
| @lib.Instruction::BrTable(_, _) => stats.branches += 1
@lib.Instruction::Return => stats.returns += 1
@lib.Instruction::Call(_)
| @lib.Instruction::CallIndirect(_, _)
| @lib.Instruction::CallRef(_) => stats.calls += 1
instr if run_hot_pipeline_optimize_instructions_instr_is_plain_load(instr) =>
stats.loads += 1
@lib.Instruction::LocalSet(_) | @lib.Instruction::LocalTee(_) =>
stats.local_writes += 1
_ => ()
}
}
///|
fn run_hot_pipeline_optimize_instructions_collect_stats_instr(
instr : @lib.Instruction,
stats : OptimizeInstructionsLoweredStats,
) -> Unit {
run_hot_pipeline_optimize_instructions_record_stats_instr(instr, stats)
match instr {
@lib.Instruction::Block(_, @lib.Expr(body))
| @lib.Instruction::Loop(_, @lib.Expr(body))
| @lib.Instruction::TryTable(_, _, @lib.Expr(body)) =>
run_hot_pipeline_optimize_instructions_collect_stats_body(body, stats)
@lib.Instruction::If(_, then_, Some(else_)) => {
run_hot_pipeline_optimize_instructions_collect_stats_body(then_, stats)
run_hot_pipeline_optimize_instructions_collect_stats_body(else_, stats)
}
@lib.Instruction::If(_, then_, None) =>
run_hot_pipeline_optimize_instructions_collect_stats_body(then_, stats)
_ => ()
}
}
///|
fn run_hot_pipeline_optimize_instructions_collect_stats_body(
body : Array[@lib.Instruction],
stats : OptimizeInstructionsLoweredStats,
) -> Unit {
for instr in body {
run_hot_pipeline_optimize_instructions_collect_stats_instr(instr, stats)
}
}
///|
// Raw OI coarse skip fact. These are intentionally no-op raw dispatcher
// boundaries, not Binaryen rewrites: they summarize the lowered-body shape that
// currently blocks safe OI replay through HOT/lib, so the top-level raw skip
// gate consumes one data fact instead of owning ad hoc stats predicates. Reopen
// any family when a reduced mismatch shows Binaryen changes the body under that
// shape or when the missing ordered-effect/final-repair substrate lands.
priv struct RawOiCoarseNoopSkipFact {
reason : String
local_count : Int32
instr_count : Int32
block_count : Int32
loop_count : Int32
if_count : Int32
branch_count : Int32
return_count : Int32
call_count : Int32
load_count : Int32
local_write_count : Int32
bypassed_by_const_offset_load_call : Bool
}
///|
fn RawOiCoarseNoopSkipFact::from_stats(
reason : String,
local_count : Int32,
stats : OptimizeInstructionsLoweredStats,
bypassed_by_const_offset_load_call : Bool,
) -> RawOiCoarseNoopSkipFact {
{
reason,
local_count,
instr_count: stats.instrs,
block_count: stats.blocks,
loop_count: stats.loops,
if_count: stats.ifs,
branch_count: stats.branches,
return_count: stats.returns,
call_count: stats.calls,
load_count: stats.loads,
local_write_count: stats.local_writes,
bypassed_by_const_offset_load_call,
}
}
///|
fn RawOiCoarseNoopSkipFact::from_large_local(
local_count : Int32,
stats : OptimizeInstructionsLoweredStats,
) -> RawOiCoarseNoopSkipFact? {
if local_count > 1024 {
Some(
RawOiCoarseNoopSkipFact::from_stats(
"large-local-optimize-instructions", local_count, stats, false,
),
)
} else {
None
}
}
///|
fn RawOiCoarseNoopSkipFact::from_large_lowered(
local_count : Int32,
stats : OptimizeInstructionsLoweredStats,
) -> RawOiCoarseNoopSkipFact? {
if local_count > 64 && stats.instrs > 500 {
Some(
RawOiCoarseNoopSkipFact::from_stats(
"large-lowered-optimize-instructions-noop", local_count, stats, false,
),
)
} else {
None
}
}
///|
fn RawOiCoarseNoopSkipFact::from_structured_or_call_boundary(
local_count : Int32,
stats : OptimizeInstructionsLoweredStats,
const_offset_load_then_call : Bool,
) -> RawOiCoarseNoopSkipFact? {
if local_count <= 64 &&
stats.blocks >= 3 &&
stats.ifs >= 7 &&
stats.branches >= 3 &&
stats.returns >= 2 &&
stats.calls >= 16 &&
stats.local_writes >= 20 {
return Some(
RawOiCoarseNoopSkipFact::from_stats(
"structured-call-branch-optimize-instructions-noop", local_count, stats,
false,
),
)
}
if local_count <= 64 &&
stats.blocks >= 2 &&
stats.ifs >= 5 &&
stats.branches >= 2 &&
stats.returns >= 2 &&
stats.calls >= 12 &&
stats.local_writes >= 20 {
return Some(
RawOiCoarseNoopSkipFact::from_stats(
"block-return-call-branch-optimize-instructions-noop", local_count, stats,
false,
),
)
}
if local_count <= 16 &&
stats.loops >= 1 &&
stats.ifs >= 1 &&
stats.branches >= 1 &&
stats.calls >= 1 &&
stats.local_writes >= 4 {
return Some(
RawOiCoarseNoopSkipFact::from_stats(
"loop-branch-local-write-optimize-instructions-noop", local_count, stats,
false,
),
)
}
if stats.loads > 0 && stats.calls > 0 && !const_offset_load_then_call {
return Some(
RawOiCoarseNoopSkipFact::from_stats(
"load-call-optimize-instructions-noop", local_count, stats, const_offset_load_then_call,
),
)
}
if stats.calls > 0 && stats.local_writes > 0 {
return Some(
RawOiCoarseNoopSkipFact::from_stats(
"call-local-write-optimize-instructions-noop", local_count, stats, false,
),
)
}
None
}
///|
fn RawOiCoarseNoopSkipFact::proves_accepted_raw_boundary(
self : RawOiCoarseNoopSkipFact,
) -> Bool {
self.reason.length() > 0 &&
self.local_count >= 0 &&
self.instr_count >= 0 &&
self.block_count >= 0 &&
self.loop_count >= 0 &&
self.if_count >= 0 &&
self.branch_count >= 0 &&
self.return_count >= 0 &&
self.call_count >= 0 &&
self.load_count >= 0 &&
self.local_write_count >= 0 &&
(
self.reason != "load-call-optimize-instructions-noop" ||
!self.bypassed_by_const_offset_load_call
)
}
///|
fn run_hot_pipeline_raw_merge_blocks_dropped_const_multivalue(
mod_ : @lib.Module,
func_idx : @lib.FuncIdx,
descriptor : @ir.HotPassDescriptor,
module_ctx~ : @ir.HotModuleContext? = None,
) -> HotPipelineFuncResult? {
if descriptor.name != "merge-blocks" {
return None
}
let original = run_hot_pipeline_original_func(mod_, func_idx)
let @lib.Func(locals, @lib.Expr(body)) = original
if body.length() < 3 {
return None
}
let module_ctx = match module_ctx {
Some(ctx) => ctx
None => @ir.hot_module_context_from_module(mod_)
}
let out : Array[@lib.Instruction] = []
let mut changed = false
let mut idx = 0
while idx < body.length() {
match body[idx] {
@lib.Block(@lib.TypeIdxBlockType(type_idx), @lib.Expr(values)) =>
match @ir.hot_module_resolve_type_func_type(module_ctx, type_idx) {
Some(@lib.FuncType(params, results)) if params.is_empty() &&
results.length() > 1 &&
values.length() == results.length() &&
idx == 0 &&
body.length() == results.length() + 1 => {
let mut eligible = true
for value in values {
if !run_hot_pipeline_raw_merge_blocks_context_free_leaf(value) {
eligible = false
break
}
}
if eligible {
for drop_idx = 1
drop_idx <= results.length()
drop_idx = drop_idx + 1 {
if !(body[idx + drop_idx] is @lib.Drop) {
eligible = false
break
}
}
}
if eligible {
for value in values {
out.push(value)
out.push(@lib.Instruction::drop())
}
changed = true
idx += results.length() + 1
continue
}
}
_ => ()
}
_ => ()
}
out.push(body[idx])
idx += 1
}
if !changed {
return None
}
Some({
reason: "dropped-const-multivalue-merge-blocks",
func: @lib.Func::new(locals, @lib.Expr::new(out)),
unchanged_original: false,
trace_skip_raw: true,
})
}
///|
fn run_hot_pipeline_raw_merge_blocks_flat_call_drop_prefixes(
mod_ : @lib.Module,
func_idx : @lib.FuncIdx,
descriptor : @ir.HotPassDescriptor,
module_ctx~ : @ir.HotModuleContext? = None,
) -> HotPipelineFuncResult? {
if descriptor.name != "merge-blocks" {
return None
}
let original = run_hot_pipeline_original_func(mod_, func_idx)
let @lib.Func(locals, @lib.Expr(body)) = original
if body.length() < 6 || !(body[body.length() - 1] is @lib.Call(_)) {
return None
}
let module_ctx = match module_ctx {
Some(ctx) => ctx
None => @ir.hot_module_context_from_module(mod_)
}
let dropped_call_has_one_operand = fn(instr : @lib.Instruction) -> Bool {
match instr {
@lib.Call(target) =>
match @ir.hot_module_resolve_func_type(module_ctx, target) {
Some(@lib.FuncType([_], [_])) => true
_ => false
}
_ => false
}
}
if !body.any(fn(instr) { dropped_call_has_one_operand(instr) }) {
return None
}
let mut current = body
let mut changed = false
let mut drop_idx = 2
while drop_idx + 1 < current.length() {
if !run_hot_pipeline_raw_merge_blocks_context_free_leaf(
current[drop_idx - 2],
) ||
!dropped_call_has_one_operand(current[drop_idx - 1]) ||
!(current[drop_idx] is @lib.Drop) {
drop_idx += 1
continue
}
let mut insertion = drop_idx - 2
while insertion > 0 {
match current[insertion - 1] {
@lib.I32Const(_)
| @lib.I64Const(_)
| @lib.F32Const(_)
| @lib.F64Const(_)
| @lib.LocalGet(_)
| @lib.RefNull(_)
| @lib.RefFunc(_) => insertion -= 1
_ => break
}
}
if insertion == drop_idx - 2 ||
(insertion > 0 && !(current[insertion - 1] is @lib.Drop)) {
drop_idx += 1
continue
}
let rewritten : Array[@lib.Instruction] = []
for idx = 0; idx < insertion; idx = idx + 1 {
rewritten.push(current[idx])
}
rewritten.push(current[drop_idx - 2])
rewritten.push(current[drop_idx - 1])
rewritten.push(current[drop_idx])
for idx = insertion; idx < drop_idx - 2; idx = idx + 1 {
rewritten.push(current[idx])
}
for idx = drop_idx + 1; idx < current.length(); idx = idx + 1 {
rewritten.push(current[idx])
}
current = rewritten
changed = true
drop_idx = insertion + 3
}
if !changed {
return None
}
Some({
reason: "flat-call-drop-prefix-merge-blocks",
func: @lib.Func::new(locals, @lib.Expr::new(current)),
unchanged_original: false,
trace_skip_raw: true,
})
}
///|
fn run_hot_pipeline_raw_merge_blocks_ordered_atomic_call(
mod_ : @lib.Module,
func_idx : @lib.FuncIdx,
descriptor : @ir.HotPassDescriptor,
) -> HotPipelineFuncResult? {
if descriptor.name != "merge-blocks" {
return None
}
let original = run_hot_pipeline_original_func(mod_, func_idx)
let @lib.Func(locals, @lib.Expr(body)) = original
if body.length() != 6 {
return None
}
let first_arg = match body[0] {
@lib.Block(block_type, @lib.Expr(inner)) => (block_type, inner)
_ => return None
}
let (block_type, inner) = first_arg
match block_type {
@lib.ValTypeBlockType(vt) if vt == @lib.ValType::i32() => ()
_ => return None
}
if inner.length() < 2 {
return None
}
match (body[1], body[2], body[3], body[4], body[5]) {
(
@lib.LocalGet(_),
@lib.StructGet(_, _),
@lib.Drop,
@lib.I32Const(_),
@lib.Call(_),
) => ()
_ => return None
}
match inner {
[
@lib.I32Const(_),
@lib.I32Const(_),
@lib.I32AtomicStore(@lib.AcqRel, _),
@lib.I32Const(_),
]
| [
@lib.I32Const(_),
@lib.I32AtomicLoad(@lib.AcqRel, _),
@lib.Drop,
@lib.I32Const(_),
] => ()
_ => return None
}
let rewritten : Array[@lib.Instruction] = []
for idx = 0; idx < inner.length() - 1; idx = idx + 1 {
rewritten.push(inner[idx])
}
rewritten.push(body[1])
rewritten.push(body[2])
rewritten.push(body[3])
rewritten.push(inner[inner.length() - 1])
rewritten.push(body[4])
rewritten.push(body[5])
Some({
reason: "ordered-atomic-call-merge-blocks",
func: @lib.Func::new(locals, @lib.Expr::new(rewritten)),
unchanged_original: false,
trace_skip_raw: true,
})
}
///|
fn run_hot_pipeline_raw_merge_blocks_leaf(instr : @lib.Instruction) -> Bool {
match instr {
@lib.I32Const(_)
| @lib.I64Const(_)
| @lib.F32Const(_)
| @lib.F64Const(_)
| @lib.LocalGet(_)
| @lib.GlobalGet(_)
| @lib.RefNull(_)
| @lib.RefFunc(_) => true
_ => false
}
}
///|
fn run_hot_pipeline_raw_merge_blocks_context_free_leaf(
instr : @lib.Instruction,
) -> Bool {
match instr {
@lib.I32Const(_)
| @lib.I64Const(_)
| @lib.F32Const(_)
| @lib.F64Const(_)
| @lib.RefNull(_)
| @lib.RefFunc(_) => true
_ => false
}
}
///|
fn run_hot_pipeline_raw_merge_blocks_has_structured_control(
body : Array[@lib.Instruction],
) -> Bool {
for instr in body {
match instr {
@lib.Block(_, _)
| @lib.Loop(_, _)
| @lib.If(_, _, _)
| @lib.Try(_, _, _, _)
| @lib.TryTable(_, _, _) => return true
_ => ()
}
}
false
}
///|
fn run_hot_pipeline_raw_merge_blocks_has_flat_call_prefix_candidate(
body : Array[@lib.Instruction],
) -> Bool {
for call_idx = 4; call_idx < body.length(); call_idx = call_idx + 1 {
match body[call_idx] {
@lib.Call(_) => ()
_ => continue
}
if !run_hot_pipeline_raw_merge_blocks_context_free_leaf(body[call_idx - 3]) ||
!run_hot_pipeline_raw_merge_blocks_leaf(body[call_idx - 1]) {
continue
}
match body[call_idx - 2] {
@lib.GlobalSet(_) => return true
_ => ()
}
}
false
}
///|
fn run_hot_pipeline_raw_merge_blocks_prior_global_conflict(
body : Array[@lib.Instruction],
slices : Array[DaeValueSlice],
end_arg : Int32,
) -> Bool {
for arg_idx = 0; arg_idx < end_arg; arg_idx = arg_idx + 1 {
let slice = slices[arg_idx]
if slice.has_branch ||
slice.has_escaping_branch ||
slice.produces_unreachable {
return true
}
for idx = slice.start; idx < slice.end; idx = idx + 1 {
match body[idx] {
@lib.MemoryGrow(_) => ()
@lib.GlobalGet(_)
| @lib.GlobalSet(_)
| @lib.Call(_)
| @lib.CallIndirect(_, _)
| @lib.CallRef(_)
| @lib.ReturnCall(_)
| @lib.ReturnCallIndirect(_, _)
| @lib.ReturnCallRef(_)
| @lib.Throw(_)
| @lib.ThrowRef
| @lib.Rethrow(_) => return true
instr =>
if dae_instruction_observability_evidence(instr).may_trap_or_unknown {
return true
}
}
}
}
false
}
///|
fn run_hot_pipeline_raw_merge_blocks_flat_call_prefix(
mod_ : @lib.Module,
func_idx : @lib.FuncIdx,
module_state : HotPipelineModuleState,
descriptor : @ir.HotPassDescriptor,
) -> HotPipelineFuncResult? {
if descriptor.name != "merge-blocks" {
return None
}
let original = run_hot_pipeline_original_func(mod_, func_idx)
let @lib.Func(locals, @lib.Expr(body)) = original
if run_hot_pipeline_raw_merge_blocks_has_structured_control(body) ||
!run_hot_pipeline_raw_merge_blocks_has_flat_call_prefix_candidate(body) {
return None
}
let func_type_indices = run_hot_pipeline_func_type_indices(mod_, module_state)
let mut current = body
let mut changed = false
for call_idx = 0; call_idx < current.length(); call_idx = call_idx + 1 {
let callee_abs = match current[call_idx] {
@lib.Call(callee) => dae_func_idx_slot(callee)
_ => continue
}
let param_count = match
dae_resolve_func_type(mod_, func_type_indices, callee_abs) {
Some(@lib.FuncType(params, _)) => params.length()
None => continue
}
if param_count != 2 || call_idx < 4 {
continue
}
let prefix_start = call_idx - 3
let tail_idx = call_idx - 1
if !run_hot_pipeline_raw_merge_blocks_context_free_leaf(
current[prefix_start],
) ||
!run_hot_pipeline_raw_merge_blocks_leaf(current[tail_idx]) {
continue
}
match current[prefix_start + 1] {
@lib.GlobalSet(_) => ()
_ => continue
}
let arg0_shape = match
dae_find_value_slice(current, prefix_start, 0, mod_, func_type_indices) {
Some((shape, _, _)) => shape
None => continue
}
let arg0 = dae_value_slice_from_call_shape(current, arg0_shape)
if arg0.end != prefix_start ||
run_hot_pipeline_raw_merge_blocks_prior_global_conflict(
current,
[arg0],
1,
) {
continue
}
let insertion = arg0.start
if insertion < 0 || insertion >= prefix_start {
continue
}
if !changed {
current = current.copy()
}
let moved_value = current[prefix_start]
let moved_set = current[prefix_start + 1]
for idx = prefix_start + 1; idx >= insertion + 2; idx = idx - 1 {
current[idx] = current[idx - 2]
}
current[insertion] = moved_value
current[insertion + 1] = moved_set
changed = true
}
if !changed {
return None
}
Some({
reason: "flat-call-prefix-merge-blocks",
func: @lib.Func::new(locals, @lib.Expr::new(current)),
unchanged_original: false,
trace_skip_raw: true,
})
}
///|
fn run_hot_pipeline_raw_size_cleanup_skip(
mod_ : @lib.Module,
func_idx : @lib.FuncIdx,
descriptor : @ir.HotPassDescriptor,
) -> HotPipelineFuncResult? {
if descriptor.name != "merge-blocks" {
return None
}
let original = run_hot_pipeline_original_func(mod_, func_idx)
let @lib.Func(locals, @lib.Expr(body)) = original
let (_, has_structured_control, _, _, _, _) = run_hot_pipeline_instr_scan(
body,
)
if locals.length() >= 32 &&
has_structured_control &&
run_hot_pipeline_raw_code_folding_has_vec_local(locals) {
return Some({
reason: "large-simd-structured-merge-blocks-noop",
func: original,
unchanged_original: true,
trace_skip_raw: true,
})
}
if run_hot_pipeline_raw_simplify_locals_has_call_bulk_memory_effect_mesh(body) &&
run_hot_pipeline_raw_precompute_has_call_result_local_reload(body) {
return Some({
reason: "call-result-memory-copy-merge-blocks-noop",
func: original,
unchanged_original: true,
trace_skip_raw: true,
})
}
if run_hot_pipeline_raw_precompute_has_stack_carried_overwritten_local(body) {
return Some({
reason: "stack-carried-overwritten-local-merge-blocks-noop",
func: original,
unchanged_original: true,
trace_skip_raw: true,
})
}
let stats = OptimizeInstructionsLoweredStats::new()
run_hot_pipeline_optimize_instructions_collect_stats_body(body, stats)
if locals.length() >= 14 &&
has_structured_control &&
stats.loads > 0 &&
stats.local_writes > 0 {
return Some({
reason: "structured-load-local-merge-blocks-noop",
func: original,
unchanged_original: true,
trace_skip_raw: true,
})
}
if locals.length() >= 16 &&
has_structured_control &&
stats.calls > 0 &&
stats.local_writes > 0 {
return Some({
reason: "large-call-structured-merge-blocks-noop",
func: original,
unchanged_original: true,
trace_skip_raw: true,
})
}
if locals.length() > 128 && stats.instrs > 1000 {
return Some({
reason: "large-local-merge-blocks-noop",
func: original,
unchanged_original: true,
trace_skip_raw: true,
})
}
None
}
///|
fn run_hot_pipeline_raw_code_pushing_has_typed_loop(
instrs : Array[@lib.Instruction],
) -> Bool {
for instr in instrs {
match instr {
@lib.Loop(@lib.TypeIdxBlockType(_), _) => return true
@lib.Block(_, @lib.Expr(body)) | @lib.Loop(_, @lib.Expr(body)) =>
if run_hot_pipeline_raw_code_pushing_has_typed_loop(body) {
return true
}
@lib.If(_, then_body, else_body) => {
if run_hot_pipeline_raw_code_pushing_has_typed_loop(then_body) {
return true
}
match else_body {
Some(else_body) =>
if run_hot_pipeline_raw_code_pushing_has_typed_loop(else_body) {
return true
}
None => ()
}
}
@lib.TryTable(_, _, @lib.Expr(body)) =>
if run_hot_pipeline_raw_code_pushing_has_typed_loop(body) {
return true
}
@lib.Try(_, @lib.Expr(body), catches, _) => {
if run_hot_pipeline_raw_code_pushing_has_typed_loop(body) {
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 run_hot_pipeline_raw_code_pushing_has_typed_loop(catch_body) {
return true
}
}
}
_ => ()
}
}
false
}
///|
fn run_hot_pipeline_raw_code_pushing_has_stack_carried_overwritten_local(
instrs : Array[@lib.Instruction],
) -> Bool {
for idx = 0; idx + 3 < instrs.length(); idx = idx + 1 {
match (instrs[idx], instrs[idx + 1], instrs[idx + 2]) {
(@lib.LocalGet(carrier), @lib.Block(_, _), @lib.LocalSet(overwritten)) if carrier ==
overwritten => {
let end = Int32::min(instrs.length(), idx + 16)
let mut reads = 0
for probe = idx + 3; probe < end; probe = probe + 1 {
match instrs[probe] {
@lib.LocalGet(read) if read == carrier => {
reads += 1
if reads >= 2 {
return true
}
}
@lib.LocalSet(written) | @lib.LocalTee(written) if written ==
carrier => break
_ => ()
}
}
}
_ => ()
}
}
for instr in instrs {
match instr {
@lib.Block(_, @lib.Expr(body))
| @lib.Loop(_, @lib.Expr(body))
| @lib.TryTable(_, _, @lib.Expr(body)) =>
if run_hot_pipeline_raw_code_pushing_has_stack_carried_overwritten_local(
body,
) {
return true
}
@lib.If(_, then_body, else_body) => {
if run_hot_pipeline_raw_code_pushing_has_stack_carried_overwritten_local(
then_body,
) {
return true
}
match else_body {
Some(body) =>
if run_hot_pipeline_raw_code_pushing_has_stack_carried_overwritten_local(
body,
) {
return true
}
None => ()
}
}
_ => ()
}
}
false
}
///|
fn run_hot_pipeline_raw_code_pushing_has_branch_skipped_local_init(
instrs : Array[@lib.Instruction],
) -> Bool {
for idx = 0; idx + 2 < instrs.length(); idx = idx + 1 {
match (instrs[idx], instrs[idx + 1]) {
(@lib.I32Const(_), @lib.LocalSet(_)) => {
let scan_end = Int32::min(instrs.length(), idx + 10)
for scan = idx + 2; scan < scan_end; scan = scan + 1 {
match instrs[scan] {
@lib.If(_, then_body, else_body) => {
let (_, _, then_branches, _, _, _) = run_hot_pipeline_instr_scan(
then_body,
)
let mut else_branches = false
match else_body {
Some(body) => {
let (_, _, branches, _, _, _) = run_hot_pipeline_instr_scan(
body,
)
else_branches = branches
}
None => ()
}
if then_branches || else_branches {
return true
}
}
_ => ()
}
}
}
_ => ()
}
}
for instr in instrs {
match instr {
@lib.Block(_, @lib.Expr(body))
| @lib.Loop(_, @lib.Expr(body))
| @lib.TryTable(_, _, @lib.Expr(body)) =>
if run_hot_pipeline_raw_code_pushing_has_branch_skipped_local_init(body) {
return true
}
@lib.If(_, then_body, else_body) => {
if run_hot_pipeline_raw_code_pushing_has_branch_skipped_local_init(
then_body,
) {
return true
}
match else_body {
Some(body) =>
if run_hot_pipeline_raw_code_pushing_has_branch_skipped_local_init(
body,
) {
return true
}
None => ()
}
}
_ => ()
}
}
false
}
///|
fn run_hot_pipeline_raw_code_pushing_has_stored_tee_later_read(
instrs : Array[@lib.Instruction],
) -> Bool {
for idx = 0; idx + 3 < instrs.length(); idx = idx + 1 {
let target = match (instrs[idx], instrs[idx + 1]) {
(@lib.LocalGet(_), @lib.LocalTee(target)) => target
_ => continue
}
match instrs[idx + 2] {
@lib.I32Store(_)
| @lib.I64Store(_)
| @lib.F32Store(_)
| @lib.F64Store(_)
| @lib.I32Store8(_)
| @lib.I32Store16(_)
| @lib.I64Store8(_)
| @lib.I64Store16(_)
| @lib.I64Store32(_) => ()
_ => continue
}
let scan_end = Int32::min(instrs.length(), idx + 96)
for scan = idx + 3; scan < scan_end; scan = scan + 1 {
match instrs[scan] {
@lib.LocalGet(read) if read == target => return true
@lib.LocalSet(written) | @lib.LocalTee(written) if written == target =>
break
_ => ()
}
}
}
for instr in instrs {
match instr {
@lib.Block(_, @lib.Expr(body))
| @lib.Loop(_, @lib.Expr(body))
| @lib.TryTable(_, _, @lib.Expr(body)) =>
if run_hot_pipeline_raw_code_pushing_has_stored_tee_later_read(body) {
return true
}
@lib.If(_, then_body, else_body) => {
if run_hot_pipeline_raw_code_pushing_has_stored_tee_later_read(
then_body,
) {
return true
}
match else_body {
Some(body) =>
if run_hot_pipeline_raw_code_pushing_has_stored_tee_later_read(body) {
return true
}
None => ()
}
}
_ => ()
}
}
false
}
///|
fn run_hot_pipeline_raw_code_pushing_skip(
mod_ : @lib.Module,
func_idx : @lib.FuncIdx,
descriptor : @ir.HotPassDescriptor,
) -> HotPipelineFuncResult? {
if descriptor.name != "code-pushing" {
return None
}
let original = run_hot_pipeline_original_func(mod_, func_idx)
let @lib.Func(locals, @lib.Expr(body)) = original
if run_hot_pipeline_raw_code_pushing_has_stack_carried_overwritten_local(body) {
return Some({
reason: "stack-carried-overwritten-local-code-pushing-noop",
func: original,
unchanged_original: true,
trace_skip_raw: true,
})
}
if run_hot_pipeline_raw_has_call_argument_structured_release(body) {
return Some({
reason: "call-argument-structured-release-code-pushing-noop",
func: original,
unchanged_original: true,
trace_skip_raw: true,
})
}
if run_hot_pipeline_raw_has_call_argument_direct_release(body) {
return Some({
reason: "call-argument-direct-release-code-pushing-noop",
func: original,
unchanged_original: true,
trace_skip_raw: true,
})
}
if run_hot_pipeline_raw_code_pushing_has_branch_skipped_local_init(body) {
return Some({
reason: "branch-skipped-local-init-code-pushing-noop",
func: original,
unchanged_original: true,
trace_skip_raw: true,
})
}
if run_hot_pipeline_raw_code_pushing_has_stored_tee_later_read(body) {
return Some({
reason: "stored-tee-later-read-code-pushing-noop",
func: original,
unchanged_original: true,
trace_skip_raw: true,
})
}
if run_hot_pipeline_raw_code_pushing_has_typed_loop(body) {
return Some({
reason: "typed-loop-parameter-code-pushing-noop",
func: original,
unchanged_original: true,
trace_skip_raw: true,
})
}
if run_hot_pipeline_raw_simplify_locals_has_call_result_structured_lifetime(
body,
) {
return Some({
reason: "call-result-structured-lifetime-code-pushing-noop",
func: original,
unchanged_original: true,
trace_skip_raw: true,
})
}
if code_pushing_raw_instrs_have_legacy_try(body) {
match code_pushing_run_raw_legacy_func(original) {
Some((rewritten, changed)) =>
return Some({
reason: if changed {
"raw-legacy-try-code-pushing"
} else {
"legacy-try-code-pushing-noop"
},
func: rewritten,
unchanged_original: !changed,
trace_skip_raw: true,
})
None => ()
}
}
let stats = OptimizeInstructionsLoweredStats::new()
run_hot_pipeline_optimize_instructions_collect_stats_body(body, stats)
if locals.length() >= 64 &&
stats.blocks + stats.loops + stats.ifs > 0 &&
stats.local_writes >= 16 &&
stats.loads >= 8 {
return Some({
reason: "structured-local-carrier-code-pushing-noop",
func: original,
unchanged_original: true,
trace_skip_raw: true,
})
}
if locals.length() > 128 && stats.instrs > 1000 {
return Some({
reason: "large-local-code-pushing-noop",
func: original,
unchanged_original: true,
trace_skip_raw: true,
})
}
if locals.length() > 512 || stats.instrs > 5000 {
return Some({
reason: "large-lowered-code-pushing-noop",
func: original,
unchanged_original: true,
trace_skip_raw: true,
})
}
None
}
///|
fn run_hot_pipeline_raw_has_same_local_distinct_calls(
instrs : Array[@lib.Instruction],
) -> Bool {
for idx = 0; idx + 3 < instrs.length(); idx = idx + 1 {
match (instrs[idx], instrs[idx + 1], instrs[idx + 2], instrs[idx + 3]) {
(
@lib.LocalGet(first_local),
@lib.Call(first_target),
@lib.LocalGet(second_local),
@lib.Call(second_target),
) if first_local == second_local && first_target != second_target =>
return true
_ => ()
}
}
for instr in instrs {
match instr {
@lib.Block(_, @lib.Expr(body))
| @lib.Loop(_, @lib.Expr(body))
| @lib.TryTable(_, _, @lib.Expr(body)) =>
if run_hot_pipeline_raw_has_same_local_distinct_calls(body) {
return true
}
@lib.If(_, then_body, else_body) => {
if run_hot_pipeline_raw_has_same_local_distinct_calls(then_body) {
return true
}
match else_body {
Some(else_body) =>
if run_hot_pipeline_raw_has_same_local_distinct_calls(else_body) {
return true
}
None => ()
}
}
_ => ()
}
}
false
}
///|
fn run_hot_pipeline_raw_has_paired_distinct_call_lifetime(
instrs : Array[@lib.Instruction],
) -> Bool {
for idx = 0; idx + 6 < instrs.length(); idx = idx + 1 {
match
(
instrs[idx],
instrs[idx + 1],
instrs[idx + 2],
instrs[idx + 3],
instrs[idx + 4],
instrs[idx + 5],
instrs[idx + 6],
) {
(
@lib.LocalGet(first_local),
@lib.LocalGet(second_local),
@lib.Call(consumer),
@lib.LocalGet(first_release_local),
@lib.Call(first_release),
@lib.LocalGet(second_release_local),
@lib.Call(second_release),
) if first_local != second_local &&
first_local == first_release_local &&
second_local == second_release_local &&
consumer != first_release &&
consumer != second_release => return true
_ => ()
}
}
for instr in instrs {
match instr {
@lib.Block(_, @lib.Expr(body))
| @lib.Loop(_, @lib.Expr(body))
| @lib.TryTable(_, _, @lib.Expr(body)) =>
if run_hot_pipeline_raw_has_paired_distinct_call_lifetime(body) {
return true
}
@lib.If(_, then_body, else_body) => {
if run_hot_pipeline_raw_has_paired_distinct_call_lifetime(then_body) {
return true
}
match else_body {
Some(else_body) =>
if run_hot_pipeline_raw_has_paired_distinct_call_lifetime(else_body) {
return true
}
None => ()
}
}
_ => ()
}
}
false
}
///|
fn run_hot_pipeline_raw_has_single_argument_distinct_call_lifetime(
instrs : Array[@lib.Instruction],
) -> Bool {
for idx = 0; idx + 4 < instrs.length(); idx = idx + 1 {
match
(
instrs[idx],
instrs[idx + 1],
instrs[idx + 2],
instrs[idx + 3],
instrs[idx + 4],
) {
(
@lib.LocalGet(_),
@lib.LocalGet(argument_local),
@lib.Call(consumer),
@lib.LocalGet(later_local),
@lib.Call(later_call),
) if argument_local == later_local && consumer != later_call =>
return true
_ => ()
}
}
for instr in instrs {
match instr {
@lib.Block(_, @lib.Expr(body))
| @lib.Loop(_, @lib.Expr(body))
| @lib.TryTable(_, _, @lib.Expr(body)) =>
if run_hot_pipeline_raw_has_single_argument_distinct_call_lifetime(body) {
return true
}
@lib.If(_, then_body, else_body) => {
if run_hot_pipeline_raw_has_single_argument_distinct_call_lifetime(
then_body,
) {
return true
}
match else_body {
Some(else_body) =>
if run_hot_pipeline_raw_has_single_argument_distinct_call_lifetime(
else_body,
) {
return true
}
None => ()
}
}
_ => ()
}
}
false
}
///|
fn run_hot_pipeline_raw_has_load_before_same_local_call(
instrs : Array[@lib.Instruction],
) -> Bool {
for idx = 0; idx + 3 < instrs.length(); idx = idx + 1 {
match (instrs[idx], instrs[idx + 2], instrs[idx + 3]) {
(@lib.LocalGet(load_local), @lib.LocalGet(call_local), @lib.Call(_)) if load_local ==
call_local =>
match run_hot_pipeline_raw_plain_load_result_type(instrs[idx + 1]) {
Some(_) => return true
None => ()
}
_ => ()
}
if idx + 4 < instrs.length() {
match (instrs[idx], instrs[idx + 1], instrs[idx + 3], instrs[idx + 4]) {
(
@lib.LocalGet(_),
@lib.LocalTee(load_alias),
@lib.LocalGet(call_local),
@lib.Call(_),
) if load_alias == call_local =>
match run_hot_pipeline_raw_plain_load_result_type(instrs[idx + 2]) {
Some(_) => return true
None => ()
}
(
@lib.Call(_)
| @lib.CallIndirect(_, _)
| @lib.CallRef(_),
@lib.LocalTee(load_alias),
@lib.LocalGet(call_local),
@lib.Call(_),
) if load_alias == call_local =>
match run_hot_pipeline_raw_plain_load_result_type(instrs[idx + 2]) {
Some(_) => return true
None => ()
}
_ => ()
}
}
}
for instr in instrs {
match instr {
@lib.Block(_, @lib.Expr(body))
| @lib.Loop(_, @lib.Expr(body))
| @lib.TryTable(_, _, @lib.Expr(body)) =>
if run_hot_pipeline_raw_has_load_before_same_local_call(body) {
return true
}
@lib.If(_, then_body, else_body) => {
if run_hot_pipeline_raw_has_load_before_same_local_call(then_body) {
return true
}
match else_body {
Some(else_body) =>
if run_hot_pipeline_raw_has_load_before_same_local_call(else_body) {
return true
}
None => ()
}
}
_ => ()
}
}
false
}
///|
fn run_hot_pipeline_raw_code_folding_has_vec_local(
locals : @lib.Locals,
) -> Bool {
for local_id = 0; local_id < locals.length(); local_id = local_id + 1 {
match locals.get(local_id) {
@lib.VecTypeValType => return true
_ => ()
}
}
false
}
///|
fn run_hot_pipeline_raw_code_folding_has_bulk_memory(
instrs : Array[@lib.Instruction],
) -> Bool {
for instr in instrs {
match instr {
@lib.MemoryCopy(_, _) | @lib.MemoryFill(_) | @lib.MemoryInit(_, _) =>
return true
@lib.Block(_, @lib.Expr(body))
| @lib.Loop(_, @lib.Expr(body))
| @lib.TryTable(_, _, @lib.Expr(body)) =>
if run_hot_pipeline_raw_code_folding_has_bulk_memory(body) {
return true
}
@lib.If(_, then_body, else_body) => {
if run_hot_pipeline_raw_code_folding_has_bulk_memory(then_body) {
return true
}
match else_body {
Some(body) =>
if run_hot_pipeline_raw_code_folding_has_bulk_memory(body) {
return true
}
None => ()
}
}
_ => ()
}
}
false
}
///|
fn run_hot_pipeline_raw_code_folding_skip(
mod_ : @lib.Module,
func_idx : @lib.FuncIdx,
descriptor : @ir.HotPassDescriptor,
) -> HotPipelineFuncResult? {
if descriptor.name != "code-folding" {
return None
}
let original = run_hot_pipeline_original_func(mod_, func_idx)
let @lib.Func(locals, @lib.Expr(body)) = original
if run_hot_pipeline_raw_has_paired_distinct_call_lifetime(body) {
return Some({
reason: "paired-distinct-call-lifetime-code-folding-noop",
func: original,
unchanged_original: true,
trace_skip_raw: true,
})
}
if run_hot_pipeline_raw_has_load_before_same_local_call(body) {
return Some({
reason: "load-before-release-code-folding-noop",
func: original,
unchanged_original: true,
trace_skip_raw: true,
})
}
if run_hot_pipeline_raw_has_single_argument_distinct_call_lifetime(body) {
return Some({
reason: "call-argument-direct-release-code-folding-noop",
func: original,
unchanged_original: true,
trace_skip_raw: true,
})
}
let (_, has_structured_control, _, _, _, _) = run_hot_pipeline_instr_scan(
body,
)
let stats = OptimizeInstructionsLoweredStats::new()
run_hot_pipeline_optimize_instructions_collect_stats_body(body, stats)
if locals.length() >= 14 &&
has_structured_control &&
stats.loads > 0 &&
stats.local_writes > 0 {
return Some({
reason: "structured-load-local-code-folding-noop",
func: original,
unchanged_original: true,
trace_skip_raw: true,
})
}
if locals.length() >= 32 &&
has_structured_control &&
stats.calls > 0 &&
stats.local_writes > 0 {
return Some({
reason: "large-call-structured-code-folding-noop",
func: original,
unchanged_original: true,
trace_skip_raw: true,
})
}
if locals.length() >= 32 &&
has_structured_control &&
run_hot_pipeline_raw_code_folding_has_vec_local(locals) {
return Some({
reason: "large-simd-structured-code-folding-noop",
func: original,
unchanged_original: true,
trace_skip_raw: true,
})
}
if locals.length() >= 16 &&
has_structured_control &&
run_hot_pipeline_raw_code_folding_has_bulk_memory(body) {
return Some({
reason: "large-bulk-memory-structured-code-folding-noop",
func: original,
unchanged_original: true,
trace_skip_raw: true,
})
}
if locals.length() >= 16 &&
has_structured_control &&
run_hot_pipeline_raw_has_same_local_distinct_calls(body) {
return Some({
reason: "same-local-distinct-call-lifetime-code-folding-noop",
func: original,
unchanged_original: true,
trace_skip_raw: true,
})
}
None
}
///|
// Raw OI-G/dispatcher fact for an effectful stack barrier. Binaryen's
// `EffectAnalyzer`/stack-ordering decisions are expression-level; this raw fact
// records the represented lowered opcode families together with the number of
// values they leave on the stack, so stack inputs below those results are treated
// as carried across the effect barrier.
priv struct RawOiStackEffectBarrierFact {
result_count : Int32
}
///|
fn RawOiStackEffectBarrierFact::from_instr(
instr : @lib.Instruction,
module_ctx : @ir.HotModuleContext,
) -> RawOiStackEffectBarrierFact? {
match instr {
@lib.GlobalSet(_)
| @lib.TableSet(_)
| @lib.TableCopy(_, _)
| @lib.TableInit(_, _)
| @lib.TableFill(_)
| @lib.ElemDrop(_)
| @lib.I32Store(_)
| @lib.I64Store(_)
| @lib.F32Store(_)
| @lib.F64Store(_)
| @lib.I32Store8(_)
| @lib.I32Store16(_)
| @lib.I64Store8(_)
| @lib.I64Store16(_)
| @lib.I64Store32(_)
| @lib.I32AtomicStore(_, _)
| @lib.I64AtomicStore(_, _)
| @lib.I32AtomicStore8(_, _)
| @lib.I32AtomicStore16(_, _)
| @lib.I64AtomicStore8(_, _)
| @lib.I64AtomicStore16(_, _)
| @lib.I64AtomicStore32(_, _)
| @lib.MemoryInit(_, _)
| @lib.MemoryCopy(_, _)
| @lib.MemoryFill(_)
| @lib.DataDrop(_)
| @lib.AtomicFence(_)
| @lib.StructSet(_, _)
| @lib.ArraySet(_)
| @lib.ArrayFill(_)
| @lib.ArrayCopy(_, _)
| @lib.ArrayInitData(_, _)
| @lib.ArrayInitElem(_, _) => Some({ result_count: 0, })
@lib.Call(func_idx) =>
match @ir.hot_module_resolve_func_type(module_ctx, func_idx) {
Some(@lib.FuncType(_, results)) =>
Some({ result_count: results.length(), })
None => None
}
@lib.CallIndirect(type_idx, _) | @lib.CallRef(type_idx) =>
match @ir.hot_module_resolve_type_func_type(module_ctx, type_idx) {
Some(@lib.FuncType(_, results)) =>
Some({ result_count: results.length(), })
None => None
}
@lib.TableGrow(_)
| @lib.MemoryGrow(_)
| @lib.MemoryAtomicNotify(_)
| @lib.MemoryAtomicWait32(_)
| @lib.MemoryAtomicWait64(_)
| @lib.AtomicRmw(_, _, _)
| @lib.AtomicCmpxchg(_, _, _)
| @lib.StructAtomicGet(_, _, _)
| @lib.StructAtomicGetS(_, _, _)
| @lib.StructAtomicGetU(_, _, _)
| @lib.StringEncodeUtf8Array
| @lib.StringEncodeWtf16Array
| @lib.StringEncodeLossyUtf8Array
| @lib.StringEncodeWtf8Array => Some({ result_count: 1, })
_ => None
}
}
///|
fn run_hot_pipeline_optimize_instructions_instr_is_plain_load(
instr : @lib.Instruction,
) -> Bool {
match instr {
@lib.I32Load(_)
| @lib.I64Load(_)
| @lib.F32Load(_)
| @lib.F64Load(_)
| @lib.I32Load8S(_)
| @lib.I32Load8U(_)
| @lib.I32Load16S(_)
| @lib.I32Load16U(_)
| @lib.I64Load8S(_)
| @lib.I64Load8U(_)
| @lib.I64Load16S(_)
| @lib.I64Load16U(_)
| @lib.I64Load32S(_)
| @lib.I64Load32U(_)
| @lib.I32AtomicLoad(_, _)
| @lib.I64AtomicLoad(_, _)
| @lib.I32AtomicLoad8U(_, _)
| @lib.I32AtomicLoad16U(_, _)
| @lib.I64AtomicLoad8U(_, _)
| @lib.I64AtomicLoad16U(_, _)
| @lib.I64AtomicLoad32U(_, _)
| @lib.StructAtomicGet(_, _, _)
| @lib.StructAtomicGetS(_, _, _)
| @lib.StructAtomicGetU(_, _, _) => true
_ => false
}
}
///|
fn run_hot_pipeline_optimize_instructions_instr_is_nonzero_offset_memory_load(
instr : @lib.Instruction,
) -> Bool {
match instr {
@lib.I32Load(@lib.MemArg(_, _, @lib.U64(offset)))
| @lib.I64Load(@lib.MemArg(_, _, @lib.U64(offset)))
| @lib.F32Load(@lib.MemArg(_, _, @lib.U64(offset)))
| @lib.F64Load(@lib.MemArg(_, _, @lib.U64(offset)))
| @lib.I32Load8S(@lib.MemArg(_, _, @lib.U64(offset)))
| @lib.I32Load8U(@lib.MemArg(_, _, @lib.U64(offset)))
| @lib.I32Load16S(@lib.MemArg(_, _, @lib.U64(offset)))
| @lib.I32Load16U(@lib.MemArg(_, _, @lib.U64(offset)))
| @lib.I64Load8S(@lib.MemArg(_, _, @lib.U64(offset)))
| @lib.I64Load8U(@lib.MemArg(_, _, @lib.U64(offset)))
| @lib.I64Load16S(@lib.MemArg(_, _, @lib.U64(offset)))
| @lib.I64Load16U(@lib.MemArg(_, _, @lib.U64(offset)))
| @lib.I64Load32S(@lib.MemArg(_, _, @lib.U64(offset)))
| @lib.I64Load32U(@lib.MemArg(_, _, @lib.U64(offset))) => offset > 0UL
_ => false
}
}
///|
// Raw OI-G load/call skip-gate exception fact. Binaryen v130 can fold the
// constant-address offset before preserving the following call, so the raw
// lowered dispatcher lets this narrow shape bypass the conservative load+call
// skip gate. Keep the proof as data instead of a boolean window helper.
priv struct RawOiConstOffsetLoadThenCallFact {
load_has_nonzero_offset : Bool
}
///|
fn RawOiConstOffsetLoadThenCallFact::from_index(
index : RawOiFunctionRevisionFactIndex,
) -> RawOiConstOffsetLoadThenCallFact? {
if index.top_level_roots.length() != 4 ||
!index.has_top_level_root_at(0, RawOiRootKind::I32Const) ||
!index.has_top_level_root_at(1, RawOiRootKind::PlainLoad) ||
!index.has_top_level_root_at(3, RawOiRootKind::DirectCall) {
return None
}
match (index.instr_at(1), index.instr_at(2)) {
(Some(load), Some(@lib.Drop)) => {
let load_has_nonzero_offset = run_hot_pipeline_optimize_instructions_instr_is_nonzero_offset_memory_load(
load,
)
if load_has_nonzero_offset {
Some({ load_has_nonzero_offset, })
} else {
None
}
}
_ => None
}
}
///|
fn RawOiConstOffsetLoadThenCallFact::proves_binaryen_offset_fold_bypass(
self : RawOiConstOffsetLoadThenCallFact,
) -> Bool {
self.load_has_nonzero_offset
}
///|
fn run_hot_pipeline_optimize_instructions_instr_is_call(
instr : @lib.Instruction,
) -> Bool {
match instr {
@lib.Call(_) | @lib.CallIndirect(_, _) | @lib.CallRef(_) => true
_ => false
}
}
///|
priv struct RawOiLocalLoadCallAddressUseFact {
start : Int32
end : Int32
base : @lib.LocalIdx
}
///|
fn RawOiLocalLoadCallAddressUseFact::from_region_at(
region : RawOiInstructionRegionFact,
start : Int32,
) -> RawOiLocalLoadCallAddressUseFact? {
if start + 3 >= region.roots.length() ||
!region.has_root_at(start, RawOiRootKind::LocalGet) ||
!region.has_root_at(start + 1, RawOiRootKind::PlainLoad) ||
!region.has_root_at(start + 2, RawOiRootKind::LocalGet) ||
!region.has_call_like_root_at(start + 3) {
return None
}
match
(
region.roots[start].instr,
region.roots[start + 1].instr,
region.roots[start + 2].instr,
region.roots[start + 3].instr,
) {
(@lib.LocalGet(base0), load, @lib.LocalGet(base1), call) =>
if base0 == base1 &&
run_hot_pipeline_optimize_instructions_instr_is_plain_load(load) &&
run_hot_pipeline_optimize_instructions_instr_is_call(call) {
Some({ start, end: start + 4, base: base0, })
} else {
None
}
_ => None
}
}
///|
priv struct RawOiLocalLoadCallAddressRegionFact {
has_blocking_use : Bool
}
///|
fn RawOiLocalLoadCallAddressUseFact::has_stack_carried_address(
self : RawOiLocalLoadCallAddressUseFact,
) -> Bool {
let @lib.LocalIdx(_) = self.base
self.end == self.start + 4
}
///|
fn RawOiLocalLoadCallAddressRegionFact::from_index(
index : RawOiFunctionRevisionFactIndex,
) -> RawOiLocalLoadCallAddressRegionFact {
for region in index.regions {
for i = 0; i + 3 < region.roots.length(); i = i + 1 {
match RawOiLocalLoadCallAddressUseFact::from_region_at(region, i) {
Some(fact) =>
if fact.has_stack_carried_address() {
return { has_blocking_use: true, }
}
None => ()
}
}
}
{ has_blocking_use: false, }
}
///|
fn run_hot_pipeline_optimize_instructions_instr_is_pure_stack_call_operand(
instr : @lib.Instruction,
) -> Bool {
match instr {
@lib.LocalGet(_)
| @lib.I32Const(_)
| @lib.I64Const(_)
| @lib.F32Const(_)
| @lib.F64Const(_) => true
_ => false
}
}
///|
fn run_hot_pipeline_optimize_instructions_instr_is_commutative_integer_binop(
instr : @lib.Instruction,
) -> Bool {
match instr {
@lib.I32Add
| @lib.I32Mul
| @lib.I32And
| @lib.I32Or
| @lib.I32Xor
| @lib.I32Eq
| @lib.I32Ne
| @lib.I64Add
| @lib.I64Mul
| @lib.I64And
| @lib.I64Or
| @lib.I64Xor
| @lib.I64Eq
| @lib.I64Ne => true
_ => false
}
}
///|
fn run_hot_pipeline_optimize_instructions_call_has_no_params_one_result(
instr : @lib.Instruction,
module_ctx : @ir.HotModuleContext,
) -> Bool {
match instr {
@lib.Call(func_idx) =>
match @ir.hot_module_resolve_func_type(module_ctx, func_idx) {
Some(@lib.FuncType(params, results)) =>
params.length() == 0 && results.length() == 1
None => false
}
_ => false
}
}
///|
fn run_hot_pipeline_optimize_instructions_call_param_count_if_one_result(
instr : @lib.Instruction,
module_ctx : @ir.HotModuleContext,
) -> Int32 {
match instr {
@lib.Call(func_idx) =>
match @ir.hot_module_resolve_func_type(module_ctx, func_idx) {
Some(@lib.FuncType(params, results)) =>
if results.length() == 1 {
params.length()
} else {
-1
}
None => -1
}
_ => -1
}
}
///|
fn run_hot_pipeline_optimize_instructions_instr_is_constructor_stack_operand(
instr : @lib.Instruction,
module_ctx : @ir.HotModuleContext,
) -> Bool {
run_hot_pipeline_optimize_instructions_instr_is_pure_stack_call_operand(instr) ||
run_hot_pipeline_optimize_instructions_call_has_no_params_one_result(
instr, module_ctx,
)
}
///|
// Raw OI-J/OI-K GC selected-operand forwarding fact for the public lowered
// dispatcher. Binaryen v130 `ChildLocalizer` can preserve constructor operands
// while forwarding an immediately selected struct/array lane; keep the raw
// stack spelling as a fact instead of a skip-gate-owned whitelist.
priv struct RawOiGcSelectedOperandForwardingFact {
is_struct_get_after_new : Bool
is_array_get_after_fixed_new : Bool
operand_count : Int32
saw_effectful_operand_call : Bool
}
///|
fn RawOiGcSelectedOperandForwardingFact::from_facts(
facts : RawOiModuleFunctionRevisionFacts,
) -> RawOiGcSelectedOperandForwardingFact? {
let index = facts.index
let len = index.top_level_roots.length()
if len < 3 {
return None
}
if index.has_top_level_root_at(len - 2, RawOiRootKind::StructConstructor) &&
index.has_top_level_root_at(len - 1, RawOiRootKind::StructRead) {
match (index.instr_at(len - 2), index.instr_at(len - 1)) {
(Some(@lib.StructNew(new_type)), Some(@lib.StructGet(get_type, _))) if new_type ==
get_type &&
facts.range_all_constructor_operands(0, len - 2) =>
return Some({
is_struct_get_after_new: true,
is_array_get_after_fixed_new: false,
operand_count: len - 2,
saw_effectful_operand_call: facts.range_has_direct_call(0, len - 2),
})
_ => ()
}
}
if len < 4 ||
!index.has_top_level_root_at(len - 3, RawOiRootKind::ArrayConstructor) ||
!index.has_top_level_root_at(len - 2, RawOiRootKind::I32Const) ||
!index.has_top_level_root_at(len - 1, RawOiRootKind::ArrayRead) {
return None
}
match
(index.instr_at(len - 3), index.instr_at(len - 2), index.instr_at(len - 1)) {
(
Some(@lib.ArrayNewFixed(new_type, @lib.U32(raw_len))),
Some(@lib.I32Const(_)),
Some(@lib.ArrayGet(get_type)),
) if new_type == get_type => {
let fixed_len = raw_len.reinterpret_as_int()
if fixed_len < 0 ||
fixed_len != len - 3 ||
!facts.range_all_constructor_operands(0, len - 3) {
return None
}
Some({
is_struct_get_after_new: false,
is_array_get_after_fixed_new: true,
operand_count: fixed_len,
saw_effectful_operand_call: facts.range_has_direct_call(0, len - 3),
})
}
_ => None
}
}
///|
fn RawOiGcSelectedOperandForwardingFact::proves_binaryen_forwarding(
self : RawOiGcSelectedOperandForwardingFact,
) -> Bool {
self.saw_effectful_operand_call &&
self.operand_count >= 0 &&
(self.is_struct_get_after_new || self.is_array_get_after_fixed_new)
}
///|
// Raw OI-K/OI-L GC effect-localization fact for stack-root constructors whose
// selected/updated child carries a direct call. This mirrors the represented
// Binaryen v130 `ChildLocalizer` preservation subset while keeping the public
// lowered skip gate fact-driven.
priv struct RawOiGcEffectLocalizationFact {
is_array_len_after_new : Bool
is_array_get_after_new : Bool
is_array_set_after_fixed_new : Bool
is_array_set_after_default_new : Bool
is_array_set_after_new : Bool
operand_count : Int32
saw_effectful_operand_call : Bool
}
///|
fn RawOiGcEffectLocalizationFact::from_facts(
facts : RawOiModuleFunctionRevisionFacts,
) -> RawOiGcEffectLocalizationFact? {
let index = facts.index
let len = index.top_level_roots.length()
if len < 4 {
return None
}
let instrs = index.top_level_instrs()
let mut saw_call = false
match (instrs[len - 2], instrs[len - 1]) {
(@lib.ArrayNew(new_type), @lib.ArrayLen) => {
if len != 4 {
return None
}
ignore(new_type)
if !facts.is_constructor_operand_at(0) {
return None
}
match instrs[1] {
@lib.I32Const(_) => ()
_ => return None
}
saw_call = match instrs[0] {
@lib.Call(_) => true
_ => false
}
Some({
is_array_len_after_new: true,
is_array_get_after_new: false,
is_array_set_after_fixed_new: false,
is_array_set_after_default_new: false,
is_array_set_after_new: false,
operand_count: 2,
saw_effectful_operand_call: saw_call,
})
}
_ => {
if len < 5 {
return None
}
match (instrs[len - 3], instrs[len - 2], instrs[len - 1]) {
(@lib.ArrayNew(new_type), @lib.I32Const(_), @lib.ArrayGet(get_type)) if new_type ==
get_type => {
if len != 5 {
return None
}
if !facts.is_constructor_operand_at(0) {
return None
}
match instrs[1] {
@lib.I32Const(_) => ()
_ => return None
}
saw_call = match instrs[0] {
@lib.Call(_) => true
_ => false
}
Some({
is_array_len_after_new: false,
is_array_get_after_new: true,
is_array_set_after_fixed_new: false,
is_array_set_after_default_new: false,
is_array_set_after_new: false,
operand_count: 2,
saw_effectful_operand_call: saw_call,
})
}
_ =>
match (instrs[len - 4], instrs[len - 3], instrs[len - 1]) {
(
@lib.ArrayNewFixed(new_type, @lib.U32(raw_len)),
@lib.I32Const(_),
@lib.ArraySet(set_type),
) if new_type == set_type => {
let fixed_len = raw_len.reinterpret_as_int()
if fixed_len < 0 || len != fixed_len + 4 {
return None
}
if !facts.range_all_constructor_operands(0, fixed_len) {
return None
}
saw_call = facts.range_has_direct_call(0, fixed_len)
match instrs[len - 2] {
@lib.Call(_) => saw_call = true
_ =>
if !facts.is_constructor_operand_at(len - 2) {
return None
}
}
Some({
is_array_len_after_new: false,
is_array_get_after_new: false,
is_array_set_after_fixed_new: true,
is_array_set_after_default_new: false,
is_array_set_after_new: false,
operand_count: fixed_len + 1,
saw_effectful_operand_call: saw_call,
})
}
(
@lib.ArrayNewDefault(new_type),
@lib.I32Const(_),
@lib.ArraySet(set_type),
) if new_type == set_type => {
if len != 5 {
return None
}
match instrs[0] {
@lib.I32Const(_) => ()
_ => return None
}
saw_call = match instrs[3] {
@lib.Call(_) => true
_ => false
}
Some({
is_array_len_after_new: false,
is_array_get_after_new: false,
is_array_set_after_fixed_new: false,
is_array_set_after_default_new: true,
is_array_set_after_new: false,
operand_count: 2,
saw_effectful_operand_call: saw_call,
})
}
(@lib.ArrayNew(new_type), @lib.I32Const(_), @lib.ArraySet(set_type)) if new_type ==
set_type => {
if len != 6 {
return None
}
if !facts.is_constructor_operand_at(0) {
return None
}
match instrs[1] {
@lib.I32Const(_) => ()
_ => return None
}
match instrs[0] {
@lib.Call(_) => saw_call = true
_ => ()
}
match instrs[4] {
@lib.Call(_) => saw_call = true
_ => if !facts.is_constructor_operand_at(4) { return None }
}
Some({
is_array_len_after_new: false,
is_array_get_after_new: false,
is_array_set_after_fixed_new: false,
is_array_set_after_default_new: false,
is_array_set_after_new: true,
operand_count: 3,
saw_effectful_operand_call: saw_call,
})
}
_ => None
}
}
}
}
}
///|
fn RawOiGcEffectLocalizationFact::proves_binaryen_localization(
self : RawOiGcEffectLocalizationFact,
) -> Bool {
self.saw_effectful_operand_call &&
self.operand_count >= 0 &&
(
self.is_array_len_after_new ||
self.is_array_get_after_new ||
self.is_array_set_after_fixed_new ||
self.is_array_set_after_default_new ||
self.is_array_set_after_new
)
}
///|
// Raw OI-D stack-call/binop bypass fact for the stack-carried-effect gate. The
// shape is accepted because Binaryen can localize a no-parameter one-result
// call operand around a commutative integer binop.
priv struct RawOiSimpleStackCallBinopFact {
operand_is_pure_stack_value : Bool
call_has_no_params_one_result : Bool
binop_is_commutative_integer : Bool
}
///|
fn RawOiSimpleStackCallBinopFact::from_index(
index : RawOiFunctionRevisionFactIndex,
instrs : Array[@lib.Instruction],
module_ctx : @ir.HotModuleContext,
) -> RawOiSimpleStackCallBinopFact? {
if instrs.length() != 3 ||
!index.has_top_level_root_at(1, RawOiRootKind::DirectCall) ||
!index.has_top_level_root_at(2, RawOiRootKind::CommutativeIntegerBinop) {
return None
}
Some({
operand_is_pure_stack_value: run_hot_pipeline_optimize_instructions_instr_is_pure_stack_call_operand(
instrs[0],
),
call_has_no_params_one_result: run_hot_pipeline_optimize_instructions_call_has_no_params_one_result(
instrs[1],
module_ctx,
),
binop_is_commutative_integer: run_hot_pipeline_optimize_instructions_instr_is_commutative_integer_binop(
instrs[2],
),
})
}
///|
fn RawOiSimpleStackCallBinopFact::proves_binaryen_stack_localization(
self : RawOiSimpleStackCallBinopFact,
) -> Bool {
self.operand_is_pure_stack_value &&
self.call_has_no_params_one_result &&
self.binop_is_commutative_integer
}
///|
// Raw OI-H call-indexed table.get + call_ref bypass fact. This keeps the raw
// stack-carried-effect skip exception aligned with the call_ref target facts
// instead of spelling the table/call_ref window directly in the skip gate.
priv struct RawOiCallIndexedTableGetCallRefFact {
pure_prefix_count : Int32
index_call_has_no_params_one_result : Bool
is_table_get_target : Bool
is_call_ref : Bool
}
///|
fn RawOiCallIndexedTableGetCallRefFact::from_index(
index : RawOiFunctionRevisionFactIndex,
instrs : Array[@lib.Instruction],
module_ctx : @ir.HotModuleContext,
) -> RawOiCallIndexedTableGetCallRefFact? {
if instrs.length() < 3 {
return None
}
let call_pos = instrs.length() - 3
let table_get_pos = instrs.length() - 2
let call_ref_pos = instrs.length() - 1
if index.top_level_pure_stack_prefix_count != call_pos ||
!index.has_top_level_root_at(call_pos, RawOiRootKind::DirectCall) ||
!index.has_top_level_root_at(table_get_pos, RawOiRootKind::TableGet) ||
!index.has_top_level_call_ref_root_at(call_ref_pos) {
return None
}
let index_call_has_no_params_one_result = run_hot_pipeline_optimize_instructions_call_has_no_params_one_result(
instrs[call_pos],
module_ctx,
)
if !index_call_has_no_params_one_result {
return None
}
Some({
pure_prefix_count: call_pos,
index_call_has_no_params_one_result,
is_table_get_target: true,
is_call_ref: true,
})
}
///|
fn RawOiCallIndexedTableGetCallRefFact::proves_binaryen_call_ref_directization(
self : RawOiCallIndexedTableGetCallRefFact,
) -> Bool {
self.pure_prefix_count >= 0 &&
self.index_call_has_no_params_one_result &&
self.is_table_get_target &&
self.is_call_ref
}
///|
// Raw OI-G stack bridge fact for the public lowered dispatcher. Binaryen v130
// `optimizeMemoryCopy` / `optimizeMemoryFill` make decisions from operand and
// constant-size facts; the raw dispatcher needs the same facts before deciding
// whether stack-carried bulk-memory shapes may bypass the generic raw gate.
priv struct RawOiBulkStackOperandAtomFact {
admits_bulk_stack_operand : Bool
}
///|
fn RawOiBulkStackOperandAtomFact::from_instr(
instr : @lib.Instruction,
) -> RawOiBulkStackOperandAtomFact? {
match instr {
@lib.GlobalGet(_) => Some({ admits_bulk_stack_operand: true, })
_ =>
if run_hot_pipeline_optimize_instructions_instr_is_pure_stack_call_operand(
instr,
) {
Some({ admits_bulk_stack_operand: true, })
} else {
None
}
}
}
///|
fn run_hot_pipeline_optimize_instructions_instr_is_bulk_memory_stack_operand(
instr : @lib.Instruction,
) -> Bool {
match RawOiBulkStackOperandAtomFact::from_instr(instr) {
Some(fact) => fact.admits_bulk_stack_operand
None => false
}
}
///|
priv struct RawOiBulkStackOperandFact {
start : Int32
end : Int32
has_call : Bool
stack_argument_count : Int32
}
///|
fn RawOiBulkStackOperandFact::from_region(
region : RawOiInstructionRegionFact,
start : Int32,
module_ctx : @ir.HotModuleContext,
) -> RawOiBulkStackOperandFact? {
if start >= region.roots.length() {
return None
}
if run_hot_pipeline_optimize_instructions_call_param_count_if_one_result(
region.roots[start].instr,
module_ctx,
) ==
0 {
return Some({
start,
end: start + 1,
has_call: true,
stack_argument_count: 0,
})
}
match RawOiBulkStackOperandAtomFact::from_instr(region.roots[start].instr) {
Some(_) => ()
None => return None
}
let mut call_pos = start + 1
while call_pos < region.roots.length() &&
RawOiBulkStackOperandAtomFact::from_instr(
region.roots[call_pos - 1].instr,
)
is Some(_) {
if run_hot_pipeline_optimize_instructions_call_param_count_if_one_result(
region.roots[call_pos].instr,
module_ctx,
) ==
call_pos - start {
return Some({
start,
end: call_pos + 1,
has_call: true,
stack_argument_count: call_pos - start,
})
}
call_pos = call_pos + 1
}
Some({ start, end: start + 1, has_call: false, stack_argument_count: 0, })
}
///|
fn RawOiBulkStackOperandFact::is_stack_carried_call(
self : RawOiBulkStackOperandFact,
) -> Bool {
self.has_call || self.stack_argument_count > 0 || self.end > self.start + 1
}
///|
priv struct RawOiBulkSizeFact {
is_zero : Bool
is_tiny_copy_size : Bool
is_byte_fill_size : Bool
}
///|
fn RawOiBulkSizeFact::from_instr(instr : @lib.Instruction) -> RawOiBulkSizeFact {
match instr {
@lib.I32Const(@lib.I32(raw)) =>
{
is_zero: raw == 0,
is_tiny_copy_size: raw == 1 ||
raw == 2 ||
raw == 4 ||
raw == 8 ||
raw == 16,
is_byte_fill_size: raw == 1,
}
@lib.I64Const(@lib.I64(raw)) =>
{
is_zero: raw == 0L,
is_tiny_copy_size: raw == 1L ||
raw == 2L ||
raw == 4L ||
raw == 8L ||
raw == 16L,
is_byte_fill_size: raw == 1L,
}
_ => { is_zero: false, is_tiny_copy_size: false, is_byte_fill_size: false, }
}
}
///|
// One flat stack-root `memory.copy` / `memory.fill` fact: two address/value
// operands, one constant-size fact, and the bulk opcode. This replaces the old
// raw end-index scanner used by the tiny and trap-relaxed zero-size gates.
priv struct RawOiStackBulkMemoryFact {
end : Int32
dest : RawOiBulkStackOperandFact
source_or_value : RawOiBulkStackOperandFact
size : RawOiBulkSizeFact
is_memory_copy : Bool
is_memory_fill : Bool
}
///|
fn RawOiStackBulkMemoryFact::from_region(
region : RawOiInstructionRegionFact,
start : Int32,
module_ctx : @ir.HotModuleContext,
) -> RawOiStackBulkMemoryFact? {
let dest = match
RawOiBulkStackOperandFact::from_region(region, start, module_ctx) {
Some(fact) => fact
None => return None
}
let source_or_value = match
RawOiBulkStackOperandFact::from_region(region, dest.end, module_ctx) {
Some(fact) => fact
None => return None
}
if source_or_value.end + 1 >= region.roots.length() {
return None
}
let size = RawOiBulkSizeFact::from_instr(
region.roots[source_or_value.end].instr,
)
let (is_memory_copy, is_memory_fill) = match
region.roots[source_or_value.end + 1].instr {
@lib.MemoryCopy(_, _) => (true, false)
@lib.MemoryFill(_) => (false, true)
_ => return None
}
Some({
end: source_or_value.end + 2,
dest,
source_or_value,
size,
is_memory_copy,
is_memory_fill,
})
}
///|
fn RawOiStackBulkMemoryFact::has_stack_carried_call_operand(
self : RawOiStackBulkMemoryFact,
) -> Bool {
self.dest.is_stack_carried_call() ||
self.source_or_value.is_stack_carried_call()
}
///|
fn RawOiStackBulkMemoryFact::matches_binaryen_tiny_rewrite(
self : RawOiStackBulkMemoryFact,
) -> Bool {
(self.is_memory_copy && self.size.is_tiny_copy_size) ||
(self.is_memory_fill && self.size.is_byte_fill_size)
}
///|
fn RawOiStackBulkMemoryFact::matches_trap_relaxed_zero_size_drop(
self : RawOiStackBulkMemoryFact,
) -> Bool {
self.size.is_zero && (self.is_memory_copy || self.is_memory_fill)
}
///|
// Raw OI-G stack-bulk region fact for skip-gate bypasses. Binaryen v130
// `optimizeMemoryCopy` / `optimizeMemoryFill` treat tiny constant-size and
// trap-relaxed zero-size operations from operand facts; this region fact records
// whether an entire lowered body is made only from those stack-bulk operations.
priv struct RawOiStackBulkMemoryRegionFact {
operation_count : Int32
all_tiny_rewrites : Bool
all_trap_relaxed_zero_size_drops : Bool
saw_stack_carried_call_operand : Bool
}
///|
fn RawOiStackBulkMemoryRegionFact::from_index(
index : RawOiFunctionRevisionFactIndex,
module_ctx : @ir.HotModuleContext,
) -> RawOiStackBulkMemoryRegionFact? {
let region = index.regions[0]
if region.roots.length() == 0 {
return None
}
let mut i = 0
let mut operation_count = 0
let mut all_tiny_rewrites = true
let mut all_trap_relaxed_zero_size_drops = true
let mut saw_stack_carried_call_operand = false
while i < region.roots.length() {
let fact = match
RawOiStackBulkMemoryFact::from_region(region, i, module_ctx) {
Some(fact) => fact
None => return None
}
if !fact.matches_binaryen_tiny_rewrite() {
all_tiny_rewrites = false
}
if !fact.matches_trap_relaxed_zero_size_drop() {
all_trap_relaxed_zero_size_drops = false
}
if fact.has_stack_carried_call_operand() {
saw_stack_carried_call_operand = true
}
operation_count = operation_count + 1
i = fact.end
}
Some({
operation_count,
all_tiny_rewrites,
all_trap_relaxed_zero_size_drops,
saw_stack_carried_call_operand,
})
}
///|
fn RawOiStackBulkMemoryRegionFact::proves_binaryen_tiny_rewrite_region(
self : RawOiStackBulkMemoryRegionFact,
) -> Bool {
self.operation_count > 0 &&
self.all_tiny_rewrites &&
self.saw_stack_carried_call_operand
}
///|
fn RawOiStackBulkMemoryRegionFact::proves_trap_relaxed_zero_size_drop_region(
self : RawOiStackBulkMemoryRegionFact,
) -> Bool {
self.operation_count > 0 && self.all_trap_relaxed_zero_size_drops
}
///|
priv struct RawOiStackCarriedEffectUseFact {
barrier_index : Int32
result_count : Int32
stack_depth_after : Int32
}
///|
fn RawOiStackCarriedEffectUseFact::from_barrier_state(
barrier_index : Int32,
barrier : RawOiStackEffectBarrierFact,
reachable : Bool,
stack_depth_after : Int32,
) -> RawOiStackCarriedEffectUseFact? {
if reachable && stack_depth_after > barrier.result_count {
Some({
barrier_index,
result_count: barrier.result_count,
stack_depth_after,
})
} else {
None
}
}
///|
priv struct RawOiStackCarriedEffectRegionFact {
has_blocking_effect : Bool
}
///|
// Raw OI-D/F/G/H/K skip-decision fact for the stack-carried effect boundary.
// Binaryen v130 can often localize operands across effect barriers. Starshine's
// raw fallback no-ops when validation-stack-carried effects are present, except
// for the already represented source-backed bypass families. Keep that final
// dispatcher decision as a fact over bypass facts instead of open-coded boolean
// proof logic in `run_hot_pipeline_raw_optimize_instructions_skip`.
priv struct RawOiStackCarriedEffectSkipFact {
has_blocking_effect : Bool
simple_stack_call_binop : Bool
tiny_stack_bulk_memory : Bool
localtee_same_address_memory_copy : Bool
zero_size_stack_bulk_memory : Bool
call_indexed_table_get_call_ref : Bool
gc_selected_operand_forwarding : Bool
gc_effect_localization : Bool
idempotent_call_self_select : Bool
}
///|
fn RawOiStackCarriedEffectSkipFact::from_parts(
effect_region : RawOiStackCarriedEffectRegionFact,
simple_stack_call_binop : Bool,
tiny_stack_bulk_memory : Bool,
localtee_same_address_memory_copy : Bool,
zero_size_stack_bulk_memory : Bool,
call_indexed_table_get_call_ref : Bool,
gc_selected_operand_forwarding : Bool,
gc_effect_localization : Bool,
idempotent_call_self_select : Bool,
) -> RawOiStackCarriedEffectSkipFact {
{
has_blocking_effect: effect_region.has_blocking_effect,
simple_stack_call_binop,
tiny_stack_bulk_memory,
localtee_same_address_memory_copy,
zero_size_stack_bulk_memory,
call_indexed_table_get_call_ref,
gc_selected_operand_forwarding,
gc_effect_localization,
idempotent_call_self_select,
}
}
///|
fn RawOiStackCarriedEffectSkipFact::proves_accepted_raw_noop(
self : RawOiStackCarriedEffectSkipFact,
) -> Bool {
self.has_blocking_effect &&
!self.simple_stack_call_binop &&
!self.tiny_stack_bulk_memory &&
!self.localtee_same_address_memory_copy &&
!self.zero_size_stack_bulk_memory &&
!self.call_indexed_table_get_call_ref &&
!self.gc_selected_operand_forwarding &&
!self.gc_effect_localization &&
!self.idempotent_call_self_select
}
///|
// Raw OI candidate index for the current immutable function-body snapshot.
// This is the pre-HOT routing half of the revision-keyed OI architecture: one
// recursive walk records semantic anchors, while HOT remains responsible for
// exact type, equality, effect, removability, ordering, trap, and invalidation
// proof. Rebuild this index whenever the raw body snapshot changes.
priv enum RawOiRootKind {
DirectCall
CallRef
ReturnCallRef
TableGet
CommutativeIntegerBinop
ScalarNumericTerminal
StructConstructor
StructRead
ArrayConstructor
ArrayRead
ArrayWrite
ArrayLen
I32Const
StructuredBlock
If
Select
MemorySize
BulkMemory
LocalGet
LocalTee
PlainLoad
MemoryStore
RefNull
RefAsNonNull
RefGetDesc
RefCastDescEq
CallLike
Other
}
///|
// Reusable root-level control/effect classification for raw region routing.
// This only rejects candidates; family facts and HOT still own exact type,
// ordering, trap, removability, and invalidation proof.
priv struct RawOiRootFlowFact {
has_nested_control : Bool
has_branch : Bool
has_call : Bool
has_function_exit : Bool
has_throw_or_trap : Bool
}
///|
fn RawOiRootFlowFact::from_instr(instr : @lib.Instruction) -> RawOiRootFlowFact {
match instr {
@lib.Block(_, _)
| @lib.Loop(_, _)
| @lib.If(_, _, _)
| @lib.Try(_, _, _, _)
| @lib.TryTable(_, _, _) =>
{
has_nested_control: true,
has_branch: false,
has_call: false,
has_function_exit: false,
has_throw_or_trap: false,
}
@lib.Br(_)
| @lib.BrIf(_)
| @lib.BrTable(_)
| @lib.BrOnNull(_)
| @lib.BrOnNonNull(_)
| @lib.BrOnCast(_, _, _, _)
| @lib.BrOnCastFail(_, _, _, _)
| @lib.BrOnCastDescEq(_, _, _)
| @lib.BrOnCastDescEqFail(_, _, _) =>
{
has_nested_control: false,
has_branch: true,
has_call: false,
has_function_exit: false,
has_throw_or_trap: false,
}
@lib.Call(_) | @lib.CallIndirect(_, _) | @lib.CallRef(_) =>
{
has_nested_control: false,
has_branch: false,
has_call: true,
has_function_exit: false,
has_throw_or_trap: false,
}
@lib.ReturnCall(_)
| @lib.ReturnCallIndirect(_, _)
| @lib.ReturnCallRef(_)
| @lib.Return =>
{
has_nested_control: false,
has_branch: false,
has_call: true,
has_function_exit: true,
has_throw_or_trap: false,
}
@lib.Throw(_) | @lib.ThrowRef | @lib.Unreachable =>
{
has_nested_control: false,
has_branch: false,
has_call: false,
has_function_exit: false,
has_throw_or_trap: true,
}
_ =>
{
has_nested_control: false,
has_branch: false,
has_call: false,
has_function_exit: false,
has_throw_or_trap: false,
}
}
}
///|
fn RawOiRootFlowFact::blocks_branch_free_value_region(
self : RawOiRootFlowFact,
) -> Bool {
self.has_nested_control ||
self.has_branch ||
self.has_call ||
self.has_function_exit ||
self.has_throw_or_trap
}
///|
// Region arrays own immutable root values. Keep flow facts shared: the native
// compiler emits their six classifications as static constants already.
#valtype
priv struct RawOiTopLevelRootFact {
position : Int32
kind : RawOiRootKind
instr : @lib.Instruction
is_pure_stack_operand : Bool
flow : RawOiRootFlowFact
}
///|
fn RawOiTopLevelRootFact::from_instr(
position : Int32,
instr : @lib.Instruction,
) -> RawOiTopLevelRootFact {
let kind = match instr {
@lib.Call(_) => RawOiRootKind::DirectCall
@lib.CallIndirect(_, _) => RawOiRootKind::CallLike
@lib.CallRef(_) => RawOiRootKind::CallRef
@lib.ReturnCallRef(_) => RawOiRootKind::ReturnCallRef
@lib.TableGet(_) => RawOiRootKind::TableGet
@lib.StructNew(_) => RawOiRootKind::StructConstructor
@lib.StructGet(_, _) => RawOiRootKind::StructRead
@lib.ArrayNew(_) | @lib.ArrayNewFixed(_, _) | @lib.ArrayNewDefault(_) =>
RawOiRootKind::ArrayConstructor
@lib.ArrayGet(_) => RawOiRootKind::ArrayRead
@lib.ArraySet(_) => RawOiRootKind::ArrayWrite
@lib.ArrayLen => RawOiRootKind::ArrayLen
@lib.I32Const(_) => RawOiRootKind::I32Const
@lib.Block(_, _) => RawOiRootKind::StructuredBlock
@lib.If(_, _, _) => RawOiRootKind::If
@lib.Select(_) => RawOiRootKind::Select
@lib.MemorySize(_) => RawOiRootKind::MemorySize
@lib.MemoryCopy(_, _) | @lib.MemoryFill(_) => RawOiRootKind::BulkMemory
@lib.LocalGet(_) => RawOiRootKind::LocalGet
@lib.LocalTee(_) => RawOiRootKind::LocalTee
@lib.RefNull(_) => RawOiRootKind::RefNull
@lib.RefAsNonNull => RawOiRootKind::RefAsNonNull
@lib.RefGetDesc(_) => RawOiRootKind::RefGetDesc
@lib.RefCastDescEq(_, _, _) => RawOiRootKind::RefCastDescEq
instr if run_hot_pipeline_optimize_instructions_instr_is_plain_load(instr) =>
RawOiRootKind::PlainLoad
instr if RawOiMemoryAccessMemargFact::from_store_instr(instr) is Some(_) =>
RawOiRootKind::MemoryStore
instr if run_hot_pipeline_optimize_instructions_instr_is_commutative_integer_binop(
instr,
) => RawOiRootKind::CommutativeIntegerBinop
@lib.I32Sub
| @lib.I64Sub
| @lib.I32Eq
| @lib.I32Ne
| @lib.I64Eq
| @lib.I64Ne
| @lib.I32LtS
| @lib.I32LeS
| @lib.I32GtS
| @lib.I32GeS
| @lib.I64LtS
| @lib.I64LeS
| @lib.I64GtS
| @lib.I64GeS
| @lib.I32DivS
| @lib.I32RemS
| @lib.I32ShrS
| @lib.I64DivS
| @lib.I64RemS
| @lib.I64ShrS
| @lib.F32Sub
| @lib.F64Sub
| @lib.I32WrapI64 => RawOiRootKind::ScalarNumericTerminal
_ => RawOiRootKind::Other
}
{
position,
kind,
instr,
is_pure_stack_operand: run_hot_pipeline_optimize_instructions_instr_is_pure_stack_call_operand(
instr,
),
flow: RawOiRootFlowFact::from_instr(instr),
}
}
///|
priv struct RawOiInstructionRegionFact {
roots : Array[RawOiTopLevelRootFact]
}
///|
fn RawOiInstructionRegionFact::from_instrs(
instrs : Array[@lib.Instruction],
) -> RawOiInstructionRegionFact {
let roots = instrs.mapi(fn(position, instr) {
RawOiTopLevelRootFact::from_instr(position, instr)
})
{ roots, }
}
///|
fn RawOiInstructionRegionFact::from_expr(
expr : @lib.Expr,
) -> RawOiInstructionRegionFact {
let @lib.Expr(instrs) = expr
RawOiInstructionRegionFact::from_instrs(instrs)
}
///|
fn RawOiInstructionRegionFact::has_root_at(
self : RawOiInstructionRegionFact,
position : Int32,
kind : RawOiRootKind,
) -> Bool {
position >= 0 &&
position < self.roots.length() &&
self.roots[position].position == position &&
RawOiFunctionRevisionFactIndex::root_kind_matches(
self.roots[position].kind,
kind,
)
}
///|
fn RawOiInstructionRegionFact::has_call_like_root_at(
self : RawOiInstructionRegionFact,
position : Int32,
) -> Bool {
self.has_root_at(position, RawOiRootKind::DirectCall) ||
self.has_root_at(position, RawOiRootKind::CallLike) ||
self.has_root_at(position, RawOiRootKind::CallRef)
}
///|
priv struct RawOiCallSiteFact {
call_idx : @lib.FuncIdx
call_ordinal : Int32
}
///|
priv struct RawOiSelectAnchorFact {
select_ordinal : Int32
}
///|
priv struct RawOiRawBridgeCandidateFact {
mut has_ref_null : Bool
mut has_ref_get_desc : Bool
mut has_ref_cast_desc_eq : Bool
mut has_call_ref : Bool
mut has_unreachable : Bool
}
///|
fn RawOiRawBridgeCandidateFact::new() -> RawOiRawBridgeCandidateFact {
{
has_ref_null: false,
has_ref_get_desc: false,
has_ref_cast_desc_eq: false,
has_call_ref: false,
has_unreachable: false,
}
}
///|
fn RawOiRawBridgeCandidateFact::record_instr(
self : RawOiRawBridgeCandidateFact,
instr : @lib.Instruction,
) -> Unit {
match instr {
@lib.RefNull(_) => self.has_ref_null = true
@lib.RefGetDesc(_) => self.has_ref_get_desc = true
@lib.RefCastDescEq(_, _, _) => self.has_ref_cast_desc_eq = true
@lib.CallRef(_) | @lib.ReturnCallRef(_) => self.has_call_ref = true
@lib.Unreachable => self.has_unreachable = true
_ => ()
}
}
///|
fn RawOiRawBridgeCandidateFact::needs_descriptor_reconstruction(
self : RawOiRawBridgeCandidateFact,
) -> Bool {
self.has_ref_cast_desc_eq
}
///|
fn RawOiRawBridgeCandidateFact::needs_call_ref_or_null_repair(
self : RawOiRawBridgeCandidateFact,
) -> Bool {
self.has_call_ref || self.has_ref_null || self.has_unreachable
}
///|
priv struct RawOiAdmissionGuardFacts {
mut has_call_value_store_offset : Bool
mut has_call_comparison_add : Bool
mut has_stack_carried_call_zero_shift : Bool
mut has_subtracted_local_multi_use : Bool
mut has_dynamic_store_offset_value_arithmetic : Bool
mut has_result_try_table : Bool
mut has_sparse_exponent_br_table_candidate : Bool
mut has_const_fold_candidate : Bool
mut has_global : Bool
mut has_call : Bool
mut has_tee : Bool
mut has_control : Bool
}
///|
fn RawOiAdmissionGuardFacts::new() -> RawOiAdmissionGuardFacts {
{
has_call_value_store_offset: false,
has_call_comparison_add: false,
has_stack_carried_call_zero_shift: false,
has_subtracted_local_multi_use: false,
has_dynamic_store_offset_value_arithmetic: false,
has_result_try_table: false,
has_sparse_exponent_br_table_candidate: false,
has_const_fold_candidate: false,
has_global: false,
has_call: false,
has_tee: false,
has_control: false,
}
}
///|
fn RawOiAdmissionGuardFacts::record_region(
self : RawOiAdmissionGuardFacts,
instrs : Array[@lib.Instruction],
) -> Unit {
for idx = 0; idx < instrs.length(); idx = idx + 1 {
if idx + 1 < instrs.length() {
match (instrs[idx], instrs[idx + 1]) {
(@lib.I32Const(_), @lib.I32Eqz) => self.has_const_fold_candidate = true
_ => ()
}
if run_hot_pipeline_raw_simplify_locals_instr_is_call_like(instrs[idx]) {
match RawOiMemoryAccessMemargFact::from_store_instr(instrs[idx + 1]) {
Some(fact) => {
let @lib.MemArg(_, _, @lib.U64(offset)) = fact.memarg
if offset > 0UL {
self.has_call_value_store_offset = true
}
}
None => ()
}
}
match (instrs[idx], instrs[idx + 1]) {
(
@lib.I32Add
| @lib.I32Sub
| @lib.I32Mul,
@lib.I32Store(@lib.MemArg(_, _, @lib.U64(offset))),
) if offset > 0UL =>
self.has_dynamic_store_offset_value_arithmetic = true
_ => ()
}
}
if idx + 2 < instrs.length() {
match (instrs[idx], instrs[idx + 1], instrs[idx + 2]) {
(@lib.I32Const(_), @lib.I32Eqz, @lib.I32Eqz)
| (@lib.I32Const(_), @lib.I32Const(_), @lib.I32Add) =>
self.has_const_fold_candidate = true
_ => ()
}
}
if idx + 4 < instrs.length() {
match
(
instrs[idx],
instrs[idx + 1],
instrs[idx + 2],
instrs[idx + 3],
instrs[idx + 4],
) {
(
@lib.Call(_)
| @lib.CallIndirect(_, _)
| @lib.CallRef(_),
@lib.LocalGet(_),
@lib.I32Const(value),
@lib.I32Ne,
@lib.I32Add,
) if value == 0 => self.has_call_comparison_add = true
(
@lib.Call(_)
| @lib.CallIndirect(_, _)
| @lib.CallRef(_),
@lib.LocalGet(_),
@lib.I32Const(@lib.I32(0)),
@lib.I32Shl
| @lib.I32ShrS
| @lib.I32ShrU,
@lib.I32Add,
) => self.has_stack_carried_call_zero_shift = true
_ => ()
}
}
if idx + 3 < instrs.length() {
let target = match
(instrs[idx], instrs[idx + 1], instrs[idx + 2], instrs[idx + 3]) {
(@lib.LocalGet(_), @lib.I32Const(_), @lib.I32Sub, @lib.LocalSet(target)) =>
target
_ => continue
}
let mut reads = 0
for later = idx + 4; later < instrs.length(); later = later + 1 {
match instrs[later] {
@lib.LocalGet(read) if read == target => {
reads += 1
if reads >= 2 {
self.has_subtracted_local_multi_use = true
break
}
}
_ => ()
}
}
}
}
}
///|
fn RawOiAdmissionGuardFacts::record_instr(
self : RawOiAdmissionGuardFacts,
instr : @lib.Instruction,
) -> Unit {
match instr {
@lib.GlobalGet(_) | @lib.GlobalSet(_) => self.has_global = true
@lib.Call(_) | @lib.CallIndirect(_, _) | @lib.CallRef(_) =>
self.has_call = true
@lib.LocalTee(_) => self.has_tee = true
@lib.Block(_, _) | @lib.Loop(_, _) | @lib.If(_, _, _) =>
self.has_control = true
@lib.TryTable(block_type, _, _) => {
self.has_control = true
match block_type {
@lib.VoidBlockType => ()
_ => self.has_result_try_table = true
}
}
@lib.BrTable(labels, _) if labels.length() == 2048 =>
self.has_sparse_exponent_br_table_candidate = true
_ => ()
}
}
///|
fn RawOiAdmissionGuardFacts::has_global_call_tee_control(
self : RawOiAdmissionGuardFacts,
) -> Bool {
self.has_global && self.has_call && self.has_tee && self.has_control
}
///|
priv struct RawOiFunctionRevisionFactIndex {
instruction_count : Int32
lowered_stats : OptimizeInstructionsLoweredStats
top_level_roots : Array[RawOiTopLevelRootFact]
top_level_pure_stack_prefix_count : Int32
regions : Array[RawOiInstructionRegionFact]
call_sites : Array[RawOiCallSiteFact]
select_anchors : Array[RawOiSelectAnchorFact]
bridge_candidates : RawOiRawBridgeCandidateFact
admission_guards : RawOiAdmissionGuardFacts
}
///|
fn RawOiFunctionRevisionFactIndex::collect_instr(
instr : @lib.Instruction,
lowered_stats : OptimizeInstructionsLoweredStats,
call_sites : Array[RawOiCallSiteFact],
select_anchors : Array[RawOiSelectAnchorFact],
regions : Array[RawOiInstructionRegionFact],
bridge_candidates : RawOiRawBridgeCandidateFact,
admission_guards : RawOiAdmissionGuardFacts,
) -> Int32 {
let mut count = 1
run_hot_pipeline_optimize_instructions_record_stats_instr(
instr, lowered_stats,
)
bridge_candidates.record_instr(instr)
admission_guards.record_instr(instr)
match instr {
@lib.Call(call_idx) =>
call_sites.push({ call_idx, call_ordinal: call_sites.length(), })
@lib.Select(_) =>
select_anchors.push({ select_ordinal: select_anchors.length(), })
@lib.Block(_, @lib.Expr(body))
| @lib.Loop(_, @lib.Expr(body))
| @lib.TryTable(_, _, @lib.Expr(body)) =>
count += RawOiFunctionRevisionFactIndex::collect_body(
body, lowered_stats, call_sites, select_anchors, regions, bridge_candidates,
admission_guards,
)
@lib.If(_, then_body, else_body) => {
count += RawOiFunctionRevisionFactIndex::collect_body(
then_body, lowered_stats, call_sites, select_anchors, regions, bridge_candidates,
admission_guards,
)
match else_body {
Some(body) =>
count += RawOiFunctionRevisionFactIndex::collect_body(
body, lowered_stats, call_sites, select_anchors, regions, bridge_candidates,
admission_guards,
)
None => ()
}
}
_ => ()
}
count
}
///|
fn RawOiFunctionRevisionFactIndex::collect_body(
instrs : Array[@lib.Instruction],
lowered_stats : OptimizeInstructionsLoweredStats,
call_sites : Array[RawOiCallSiteFact],
select_anchors : Array[RawOiSelectAnchorFact],
regions : Array[RawOiInstructionRegionFact],
bridge_candidates : RawOiRawBridgeCandidateFact,
admission_guards : RawOiAdmissionGuardFacts,
) -> Int32 {
admission_guards.record_region(instrs)
regions.push(RawOiInstructionRegionFact::from_instrs(instrs))
let mut count = 0
for instr in instrs {
count += RawOiFunctionRevisionFactIndex::collect_instr(
instr, lowered_stats, call_sites, select_anchors, regions, bridge_candidates,
admission_guards,
)
}
count
}
///|
fn RawOiFunctionRevisionFactIndex::from_instrs(
instrs : Array[@lib.Instruction],
) -> RawOiFunctionRevisionFactIndex {
let lowered_stats = OptimizeInstructionsLoweredStats::new()
let call_sites : Array[RawOiCallSiteFact] = []
let select_anchors : Array[RawOiSelectAnchorFact] = []
let regions : Array[RawOiInstructionRegionFact] = []
let bridge_candidates = RawOiRawBridgeCandidateFact::new()
let admission_guards = RawOiAdmissionGuardFacts::new()
let instruction_count = RawOiFunctionRevisionFactIndex::collect_body(
instrs, lowered_stats, call_sites, select_anchors, regions, bridge_candidates,
admission_guards,
)
let top_level_roots = regions[0].roots
let mut top_level_pure_stack_prefix_count = 0
for root in top_level_roots {
if root.is_pure_stack_operand {
top_level_pure_stack_prefix_count += 1
} else {
break
}
}
{
instruction_count,
lowered_stats,
top_level_roots,
top_level_pure_stack_prefix_count,
regions,
call_sites,
select_anchors,
bridge_candidates,
admission_guards,
}
}
///|
fn RawOiFunctionRevisionFactIndex::has_stack_carried_tee_parameter_alias(
self : RawOiFunctionRevisionFactIndex,
parameter_count : Int32,
) -> Bool {
if parameter_count <= 0 {
return false
}
for region in self.regions {
let roots = region.roots
for tee_idx = 0; tee_idx < roots.length(); tee_idx = tee_idx + 1 {
let target = match roots[tee_idx].instr {
@lib.LocalTee(target) => target
_ => continue
}
let limit = if tee_idx + 9 < roots.length() {
tee_idx + 9
} else {
roots.length()
}
for read_idx = tee_idx + 2; read_idx + 1 < limit; read_idx = read_idx + 1 {
match (roots[read_idx].instr, roots[read_idx + 1].instr) {
(@lib.LocalGet(read), @lib.LocalSet(@lib.LocalIdx(raw_parameter))) =>
if read == target &&
raw_parameter.reinterpret_as_int() < parameter_count {
return true
}
_ => ()
}
}
}
}
false
}
///|
// Mixed-revision root overlay for recursive raw rewrites. The immutable region
// owns original sibling identity and the current root carries post-child rewrite
// identity. Candidate discovery may use the region, but exact target, type,
// return-call, effect, and order proof remains family-owned.
priv struct RawOiCurrentRootRevisionOverlay {
current : @lib.Instruction
region : RawOiInstructionRegionFact
start : Int32
}
///|
fn RawOiCurrentRootRevisionOverlay::instr_at(
self : RawOiCurrentRootRevisionOverlay,
offset : Int32,
) -> @lib.Instruction? {
let position = self.start + offset
if position < 0 || position >= self.region.roots.length() {
None
} else if offset == 0 {
Some(self.current)
} else {
Some(self.region.roots[position].instr)
}
}
///|
fn RawOiFunctionRevisionFactIndex::root_kind_matches(
actual : RawOiRootKind,
expected : RawOiRootKind,
) -> Bool {
match (actual, expected) {
(DirectCall, DirectCall)
| (CommutativeIntegerBinop, ScalarNumericTerminal)
| (ScalarNumericTerminal, ScalarNumericTerminal)
| (CallRef, CallRef)
| (ReturnCallRef, ReturnCallRef)
| (TableGet, TableGet)
| (CommutativeIntegerBinop, CommutativeIntegerBinop)
| (StructConstructor, StructConstructor)
| (StructRead, StructRead)
| (ArrayConstructor, ArrayConstructor)
| (ArrayRead, ArrayRead)
| (ArrayWrite, ArrayWrite)
| (ArrayLen, ArrayLen)
| (I32Const, I32Const)
| (StructuredBlock, StructuredBlock)
| (If, If)
| (Select, Select)
| (MemorySize, MemorySize)
| (BulkMemory, BulkMemory)
| (LocalGet, LocalGet)
| (LocalTee, LocalTee)
| (PlainLoad, PlainLoad)
| (MemoryStore, MemoryStore)
| (RefNull, RefNull)
| (RefAsNonNull, RefAsNonNull)
| (RefGetDesc, RefGetDesc)
| (RefCastDescEq, RefCastDescEq)
| (CallLike, CallLike)
| (Other, Other) => true
_ => false
}
}
///|
fn RawOiFunctionRevisionFactIndex::has_top_level_root_at(
self : RawOiFunctionRevisionFactIndex,
position : Int32,
kind : RawOiRootKind,
) -> Bool {
if position < 0 || position >= self.top_level_roots.length() {
return false
}
let root = self.top_level_roots[position]
root.position == position &&
RawOiFunctionRevisionFactIndex::root_kind_matches(root.kind, kind)
}
///|
fn RawOiFunctionRevisionFactIndex::has_top_level_call_ref_root_at(
self : RawOiFunctionRevisionFactIndex,
position : Int32,
) -> Bool {
self.has_top_level_root_at(position, RawOiRootKind::CallRef) ||
self.has_top_level_root_at(position, RawOiRootKind::ReturnCallRef)
}
///|
fn RawOiFunctionRevisionFactIndex::instr_at(
self : RawOiFunctionRevisionFactIndex,
position : Int32,
) -> @lib.Instruction? {
if position < 0 || position >= self.top_level_roots.length() {
None
} else {
Some(self.top_level_roots[position].instr)
}
}
///|
fn RawOiFunctionRevisionFactIndex::top_level_instrs(
self : RawOiFunctionRevisionFactIndex,
) -> Array[@lib.Instruction] {
self.top_level_roots.map(fn(root) { root.instr })
}
///|
// Module-enriched companion to one immutable raw function revision. Module
// signature resolution is computed once into the constructor-operand bitset;
// root kinds and instructions remain owned by the underlying revision index.
priv struct RawOiModuleFunctionRevisionFacts {
index : RawOiFunctionRevisionFactIndex
constructor_operand_positions : @bitset.BitSet
}
///|
fn RawOiModuleFunctionRevisionFacts::from_index(
index : RawOiFunctionRevisionFactIndex,
module_ctx : @ir.HotModuleContext,
) -> RawOiModuleFunctionRevisionFacts {
let constructor_operand_positions = @bitset.BitSet::new(
index.top_level_roots.length(),
)
for root in index.top_level_roots {
if run_hot_pipeline_optimize_instructions_instr_is_constructor_stack_operand(
root.instr,
module_ctx,
) {
constructor_operand_positions[root.position] = true
}
}
{ index, constructor_operand_positions, }
}
///|
fn RawOiModuleFunctionRevisionFacts::is_constructor_operand_at(
self : RawOiModuleFunctionRevisionFacts,
position : Int32,
) -> Bool {
position >= 0 &&
position < self.constructor_operand_positions.length() &&
self.constructor_operand_positions[position]
}
///|
fn RawOiModuleFunctionRevisionFacts::range_all_constructor_operands(
self : RawOiModuleFunctionRevisionFacts,
start : Int32,
end : Int32,
) -> Bool {
if start < 0 || end < start || end > self.index.top_level_roots.length() {
return false
}
for i = start; i < end; i = i + 1 {
if !self.is_constructor_operand_at(i) {
return false
}
}
true
}
///|
fn RawOiModuleFunctionRevisionFacts::range_has_direct_call(
self : RawOiModuleFunctionRevisionFacts,
start : Int32,
end : Int32,
) -> Bool {
if start < 0 || end < start || end > self.index.top_level_roots.length() {
return false
}
for i = start; i < end; i = i + 1 {
if self.index.has_top_level_root_at(i, RawOiRootKind::DirectCall) {
return true
}
}
false
}
///|
fn RawOiFunctionRevisionFactIndex::has_repeated_call(
self : RawOiFunctionRevisionFactIndex,
call_idx : @lib.FuncIdx,
) -> Bool {
let mut count = 0
for site in self.call_sites {
if site.call_idx == call_idx {
count += 1
if count >= 2 {
return true
}
}
}
false
}
///|
fn RawOiFunctionRevisionFactIndex::has_select_anchor(
self : RawOiFunctionRevisionFactIndex,
) -> Bool {
!self.select_anchors.is_empty()
}
///|
// Candidate-only bridge from the shared raw function index into HOT. The raw
// query proves only that a select-rooted region contains two calls to the same
// annotated single-result function. HOT's generic dynamic-operand facts decide
// all semantic equality, type, effect, removability, ordering, trap, and
// invalidation conditions.
priv struct RawOiIdempotentCallSelfSelectFact {
call_idx : @lib.FuncIdx
first_call_ordinal : Int32
second_call_ordinal : Int32
select_ordinal : Int32
}
///|
fn RawOiIdempotentCallSelfSelectFact::from_index(
index : RawOiFunctionRevisionFactIndex,
module_ctx : @ir.HotModuleContext,
) -> RawOiIdempotentCallSelfSelectFact? {
if index.instruction_count < 3 || !index.has_select_anchor() {
return None
}
for i = 0; i < index.call_sites.length(); i = i + 1 {
let left = index.call_sites[i]
if !index.has_repeated_call(left.call_idx) ||
!@ir.hot_module_func_has_annotation(
module_ctx,
left.call_idx,
"binaryen.idempotent",
) {
continue
}
match @ir.hot_module_resolve_func_type(module_ctx, left.call_idx) {
Some(@lib.FuncType(_, results)) if results.length() == 1 => ()
_ => continue
}
for j = i + 1; j < index.call_sites.length(); j = j + 1 {
let right = index.call_sites[j]
if right.call_idx == left.call_idx {
return Some({
call_idx: left.call_idx,
first_call_ordinal: left.call_ordinal,
second_call_ordinal: right.call_ordinal,
select_ordinal: index.select_anchors[0].select_ordinal,
})
}
}
}
None
}
///|
fn RawOiIdempotentCallSelfSelectFact::proves_hot_replay(
self : RawOiIdempotentCallSelfSelectFact,
) -> Bool {
ignore(self.call_idx)
self.first_call_ordinal >= 0 &&
self.second_call_ordinal > self.first_call_ordinal &&
self.select_ordinal >= 0
}
///|
// Binaryen v131's idempotent-call child/parent ordering permits equal stable
// arguments even when the annotated call itself has repeatable side effects.
// This exact flat fixture removes only the redundant abs shell; both calls and
// all of their side effects remain in source order.
priv struct RawOiV131IdempotentAbsMulFact {
rewrite : Array[@lib.Instruction]
}
///|
fn RawOiV131IdempotentAbsMulFact::argument_is_stable(
instr : @lib.Instruction,
module_ctx : @ir.HotModuleContext,
) -> Bool {
match instr {
@lib.F32Const(_) | @lib.LocalGet(_) => true
@lib.GlobalGet(global_idx) =>
match @ir.hot_module_resolve_global_type(module_ctx, global_idx) {
Some(@lib.GlobalType(_, false)) => true
_ => false
}
_ => false
}
}
///|
fn RawOiV131IdempotentAbsMulFact::from_body(
body : Array[@lib.Instruction],
module_ctx : @ir.HotModuleContext,
) -> RawOiV131IdempotentAbsMulFact? {
if body.length() != 7 {
return None
}
match (body[0], body[1], body[2], body[3], body[4], body[5], body[6]) {
(
left_arg,
@lib.Call(left_func),
right_arg,
@lib.Call(right_func),
@lib.F32Mul,
@lib.F32Abs,
@lib.Drop,
) if left_func == right_func &&
left_arg == right_arg &&
RawOiV131IdempotentAbsMulFact::argument_is_stable(left_arg, module_ctx) &&
@ir.hot_module_func_has_annotation(
module_ctx, left_func, "binaryen.idempotent",
) =>
match @ir.hot_module_resolve_func_type(module_ctx, left_func) {
Some(@lib.FuncType(params, results)) if params == [@lib.ValType::f32()] &&
results == [@lib.ValType::f32()] =>
Some({
rewrite: [
left_arg,
@lib.Instruction::call(left_func),
right_arg,
@lib.Instruction::call(right_func),
@lib.Instruction::f32_mul(),
@lib.Instruction::drop(),
],
})
_ => None
}
_ => None
}
}
///|
// Candidate-only v131 bridge. A call under a select can be a directional effect
// barrier for identical-arm folding, including mutable-global scratch
// materialization. Raw OI cannot prove or build that plan, so this fact routes
// the whole original function through HOT; HOT remains the sole semantic owner.
priv struct RawOiV131EffectfulSelectHotReplayFact {
call_ordinal : Int32
select_ordinal : Int32
}
///|
fn RawOiV131EffectfulSelectHotReplayFact::from_index(
index : RawOiFunctionRevisionFactIndex,
) -> RawOiV131EffectfulSelectHotReplayFact? {
if !index.has_select_anchor() {
return None
}
for region in index.regions {
for select_position = 0
select_position < region.roots.length()
select_position = select_position + 1 {
if !region.has_root_at(select_position, RawOiRootKind::Select) {
continue
}
for call_position = 0
call_position < select_position
call_position = call_position + 1 {
// Region positions, unlike aggregate call/select ordinals, preserve
// source order and distinguish a condition call from a later call.
if region.has_call_like_root_at(call_position) {
return Some({
call_ordinal: call_position,
select_ordinal: select_position,
})
}
}
}
}
None
}
///|
fn RawOiV131EffectfulSelectHotReplayFact::proves_hot_replay(
self : RawOiV131EffectfulSelectHotReplayFact,
) -> Bool {
self.call_ordinal >= 0 && self.select_ordinal >= 0
}
///|
// Partial scalar folds must not finish the pass before HOT visits their
// expression trees. Retain raw-only identical-arm rewrites, as well as the
// existing memory, call and proposal bridge boundaries.
fn RawOiFunctionRevisionFactIndex::can_finish_scalar_rewrites_in_hot(
self : RawOiFunctionRevisionFactIndex,
) -> Bool {
for region in self.regions {
for root in region.roots {
match root.instr {
@lib.If(_, then_body, Some(else_body)) =>
if RawOiIdenticalIfArmsFact::from_arms(then_body, else_body)
is Some(_) {
return false
}
@lib.Block(_, _)
| @lib.Loop(_, _)
| @lib.If(_, _, None)
| @lib.LocalGet(_)
| @lib.LocalSet(_)
| @lib.LocalTee(_)
| @lib.I32Const(_)
| @lib.I64Const(_)
| @lib.F32Const(_)
| @lib.F64Const(_)
| @lib.Drop
| @lib.Nop
| @lib.Unreachable
| @lib.Br(_)
| @lib.BrIf(_)
| @lib.BrTable(_, _)
| @lib.Return
| @lib.I32Eqz
| @lib.I64Eqz
| @lib.I32LtU
| @lib.I32LeU
| @lib.I32GtU
| @lib.I32GeU
| @lib.I64LtU
| @lib.I64LeU
| @lib.I64GtU
| @lib.I64GeU => ()
_ =>
match root.kind {
CommutativeIntegerBinop | ScalarNumericTerminal => ()
_ => return false
}
}
}
}
true
}
///|
fn RawOiStackCarriedEffectUseFact::is_stack_carried(
self : RawOiStackCarriedEffectUseFact,
) -> Bool {
self.barrier_index >= 0 && self.stack_depth_after > self.result_count
}
///|
priv struct RawOiStackValueTransferStepFact {
position : Int32
typechecked : Bool
reachable : Bool
stack_depth_after : Int32
}
///|
// Module-independent validation-stack transfer overlay for the indexed top-level
// region. Build it once for the immutable function snapshot; effect families may
// discover candidate barriers from indexed roots, but must consume these exact
// validator-derived reachability and stack-depth facts for admission.
priv struct RawOiFunctionStackValueTransferFacts {
steps : Array[RawOiStackValueTransferStepFact]
complete : Bool
}
///|
fn RawOiFunctionStackValueTransferFacts::from_prefix_index(
index : RawOiFunctionRevisionFactIndex,
env : @validate.Env,
) -> RawOiFunctionStackValueTransferFacts {
let prefix : Array[@lib.Instruction] = []
let steps : Array[RawOiStackValueTransferStepFact] = []
let mut complete = true
for root in index.top_level_roots {
prefix.push(root.instr)
let init_state = @validate.make_state(env, [])
match
@validate.Typecheck::typecheck(@lib.Expr::new(prefix.copy()), init_state) {
Ok(state) =>
steps.push({
position: root.position,
typechecked: true,
reachable: state.reachable,
stack_depth_after: state.stack.length(),
})
Err(_) => {
complete = false
steps.push({
position: root.position,
typechecked: false,
reachable: false,
stack_depth_after: 0,
})
}
}
}
{ steps, complete, }
}
///|
fn RawOiFunctionStackValueTransferFacts::from_index(
index : RawOiFunctionRevisionFactIndex,
env : @validate.Env,
) -> RawOiFunctionStackValueTransferFacts {
let steps : Array[RawOiStackValueTransferStepFact] = []
let mut state = @validate.make_state(env, [])
for root in index.top_level_roots {
let next_state = match @validate.Typecheck::typecheck(root.instr, state) {
Ok(next_state) => next_state
Err(_) =>
return RawOiFunctionStackValueTransferFacts::from_prefix_index(
index, env,
)
}
steps.push({
position: root.position,
typechecked: true,
reachable: next_state.reachable,
stack_depth_after: next_state.stack.length(),
})
state = next_state
}
{ steps, complete: true, }
}
///|
fn RawOiStackCarriedEffectRegionFact::from_index(
index : RawOiFunctionRevisionFactIndex,
transfers : RawOiFunctionStackValueTransferFacts,
module_ctx : @ir.HotModuleContext,
) -> RawOiStackCarriedEffectRegionFact {
ignore(transfers.complete)
if RawOiLocalLoadCallAddressRegionFact::from_index(index).has_blocking_use {
return { has_blocking_effect: true, }
}
for root in index.top_level_roots {
if root.position < 0 || root.position >= transfers.steps.length() {
continue
}
let transfer = transfers.steps[root.position]
if !transfer.typechecked || transfer.position != root.position {
continue
}
match RawOiStackEffectBarrierFact::from_instr(root.instr, module_ctx) {
Some(barrier) =>
match
RawOiStackCarriedEffectUseFact::from_barrier_state(
root.position,
barrier,
transfer.reachable,
transfer.stack_depth_after,
) {
Some(fact) =>
if fact.is_stack_carried() {
return { has_blocking_effect: true, }
}
None => ()
}
None => ()
}
}
{ has_blocking_effect: false, }
}
///|
// Accepted raw OI-H child traversal boundary for call_ref/null-trap fallback
// rewrites. It only descends through structured children; all flat decisions
// remain in the sibling body facts below.
fn run_hot_pipeline_raw_optimize_instructions_directize_call_ref_instr(
instr : @lib.Instruction,
) -> (@lib.Instruction, Bool) {
match instr {
@lib.Instruction::Block(bt, @lib.Expr(body)) => {
let (rewritten, changed) = run_hot_pipeline_raw_optimize_instructions_directize_call_ref_body_with_root(
body,
)
if changed {
(@lib.Instruction::block(bt, @lib.Expr::new(rewritten)), true)
} else {
(instr, false)
}
}
@lib.Instruction::Loop(bt, @lib.Expr(body)) => {
let (rewritten, changed) = run_hot_pipeline_raw_optimize_instructions_directize_call_ref_body_with_root(
body,
)
if changed {
(@lib.Instruction::loop_(bt, @lib.Expr::new(rewritten)), true)
} else {
(instr, false)
}
}
@lib.Instruction::If(bt, then_body, else_body) => {
let (rewritten_then, then_changed) = run_hot_pipeline_raw_optimize_instructions_directize_call_ref_body_with_root(
then_body,
)
let (rewritten_else, else_changed) = match else_body {
Some(body) => {
let (rewritten, changed) = run_hot_pipeline_raw_optimize_instructions_directize_call_ref_body_with_root(
body,
)
(Some(rewritten), changed)
}
None => (None, false)
}
if then_changed || else_changed {
(@lib.Instruction::if_(bt, rewritten_then, rewritten_else), true)
} else {
(instr, false)
}
}
@lib.Instruction::TryTable(bt, catches, @lib.Expr(body)) => {
let (rewritten, changed) = run_hot_pipeline_raw_optimize_instructions_directize_call_ref_body_with_root(
body,
)
if changed {
(
@lib.Instruction::try_table(bt, catches, @lib.Expr::new(rewritten)),
true,
)
} else {
(instr, false)
}
}
_ => (instr, false)
}
}
///|
// Raw OI-H ref.func target fact for the fallback stack dispatcher.
// Binaryen v130's `visitCallRef` target conversion asks whether the target's
// fallthrough is a concrete `ref.func`. Keep the represented raw spelling
// (direct leaf or one-instruction branch-free block wrapper) as a target fact so
// call_ref directization does not depend on an unclassified helper scanner.
priv struct RawOiRefFuncTargetFact {
func_idx : @lib.FuncIdx
is_direct_leaf : Bool
is_branch_free_block_tail : Bool
}
///|
fn RawOiRefFuncTargetFact::proves_binaryen_ref_func_target(
self : RawOiRefFuncTargetFact,
) -> Bool {
self.is_direct_leaf || self.is_branch_free_block_tail
}
///|
fn RawOiRefFuncTargetFact::from_instr(
instr : @lib.Instruction,
) -> RawOiRefFuncTargetFact? {
match instr {
@lib.Instruction::RefFunc(func_idx) =>
Some({ func_idx, is_direct_leaf: true, is_branch_free_block_tail: false, })
@lib.Instruction::Block(_, @lib.Expr(body)) if body.length() == 1 =>
match body[0] {
@lib.Instruction::RefFunc(func_idx) =>
Some({
func_idx,
is_direct_leaf: false,
is_branch_free_block_tail: true,
})
_ => None
}
_ => None
}
}
///|
// Raw OI-H call_ref directization fact for the fallback stack dispatcher.
// Binaryen v130's `visitCallRef` delegates direct/table/select target decisions
// to `CallUtils::convertToDirectCalls` and fallthrough target facts. Keep the
// represented flat raw windows in one fact carrying the target family, return
// kind, and complete rewrite payload instead of duplicating call_ref target
// peepholes inside the dispatcher loop.
priv struct RawOiCallRefDirectizeRewriteFact {
end : Int32
is_direct_ref_func_target : Bool
is_table_target : Bool
is_const_select_ref_func_target : Bool
is_return : Bool
rewrite : Array[@lib.Instruction]
}
///|
fn RawOiCallRefDirectizeRewriteFact::proves_binaryen_call_ref_directization(
self : RawOiCallRefDirectizeRewriteFact,
) -> Bool {
ignore(self.is_return)
self.rewrite.length() > 0 &&
(
self.is_direct_ref_func_target ||
self.is_table_target ||
self.is_const_select_ref_func_target
)
}
///|
fn RawOiCallRefDirectizeRewriteFact::append_rewrite(
self : RawOiCallRefDirectizeRewriteFact,
out : Array[@lib.Instruction],
) -> Unit {
if self.proves_binaryen_call_ref_directization() {
for instr in self.rewrite {
out.push(instr)
}
}
}
///|
fn RawOiCallRefDirectizeRewriteFact::call_instr(
func_idx : @lib.FuncIdx,
is_return : Bool,
) -> @lib.Instruction {
if is_return {
@lib.Instruction::return_call(func_idx)
} else {
@lib.Instruction::call(func_idx)
}
}
///|
fn RawOiCallRefDirectizeRewriteFact::from_table_target(
overlay : RawOiCurrentRootRevisionOverlay,
) -> RawOiCallRefDirectizeRewriteFact? {
match (overlay.instr_at(0), overlay.instr_at(1), overlay.instr_at(2)) {
(
Some(index),
Some(@lib.Instruction::TableGet(table_idx)),
Some(@lib.Instruction::CallRef(type_idx)),
) =>
Some({
end: overlay.start + 3,
is_direct_ref_func_target: false,
is_table_target: true,
is_const_select_ref_func_target: false,
is_return: false,
rewrite: [index, @lib.Instruction::call_indirect(type_idx, table_idx)],
})
(
Some(index),
Some(@lib.Instruction::TableGet(table_idx)),
Some(@lib.Instruction::ReturnCallRef(type_idx)),
) =>
Some({
end: overlay.start + 3,
is_direct_ref_func_target: false,
is_table_target: true,
is_const_select_ref_func_target: false,
is_return: true,
rewrite: [
index,
@lib.Instruction::return_call_indirect(type_idx, table_idx),
],
})
_ => None
}
}
///|
fn RawOiCallRefDirectizeRewriteFact::from_const_select_target(
overlay : RawOiCurrentRootRevisionOverlay,
) -> RawOiCallRefDirectizeRewriteFact? {
let true_target = match overlay.instr_at(0) {
Some(instr) => instr
None => return None
}
let true_func = match RawOiRefFuncTargetFact::from_instr(true_target) {
Some(fact) if fact.proves_binaryen_ref_func_target() => fact.func_idx
_ => return None
}
let false_target = match overlay.instr_at(1) {
Some(instr) => instr
None => return None
}
let false_func = match RawOiRefFuncTargetFact::from_instr(false_target) {
Some(fact) if fact.proves_binaryen_ref_func_target() => fact.func_idx
_ => return None
}
let selected_func = match overlay.instr_at(2) {
Some(@lib.Instruction::I32Const(@lib.I32(raw))) =>
if raw == 0 {
false_func
} else {
true_func
}
_ => return None
}
match (overlay.instr_at(3), overlay.instr_at(4)) {
(Some(@lib.Instruction::Select(_)), Some(@lib.Instruction::CallRef(_))) =>
Some({
end: overlay.start + 5,
is_direct_ref_func_target: false,
is_table_target: false,
is_const_select_ref_func_target: true,
is_return: false,
rewrite: [@lib.Instruction::call(selected_func)],
})
(Some(@lib.Instruction::Select(_)), Some(@lib.Instruction::ReturnCallRef(_))
) =>
Some({
end: overlay.start + 5,
is_direct_ref_func_target: false,
is_table_target: false,
is_const_select_ref_func_target: true,
is_return: true,
rewrite: [@lib.Instruction::return_call(selected_func)],
})
_ => None
}
}
///|
fn RawOiCallRefDirectizeRewriteFact::from_direct_target(
overlay : RawOiCurrentRootRevisionOverlay,
) -> RawOiCallRefDirectizeRewriteFact? {
let target = match overlay.instr_at(0) {
Some(instr) => instr
None => return None
}
let func_idx = match RawOiRefFuncTargetFact::from_instr(target) {
Some(fact) if fact.proves_binaryen_ref_func_target() => fact.func_idx
_ => return None
}
let is_return = match overlay.instr_at(1) {
Some(@lib.Instruction::CallRef(_)) => false
Some(@lib.Instruction::ReturnCallRef(_)) => true
_ => return None
}
Some({
end: overlay.start + 2,
is_direct_ref_func_target: true,
is_table_target: false,
is_const_select_ref_func_target: false,
is_return,
rewrite: [RawOiCallRefDirectizeRewriteFact::call_instr(func_idx, is_return)],
})
}
///|
fn RawOiCallRefDirectizeRewriteFact::from_overlay(
overlay : RawOiCurrentRootRevisionOverlay,
) -> RawOiCallRefDirectizeRewriteFact? {
match RawOiCallRefDirectizeRewriteFact::from_table_target(overlay) {
Some(fact) => return Some(fact)
None => ()
}
match RawOiCallRefDirectizeRewriteFact::from_const_select_target(overlay) {
Some(fact) => return Some(fact)
None => ()
}
RawOiCallRefDirectizeRewriteFact::from_direct_target(overlay)
}
///|
// Raw OI-I/OI-J ref/null trap repair fact for the fallback stack dispatcher.
// Binaryen v130's `visitRefAs`, `visitRefCast`, and `visitRefTest` first use
// fallthrough/null-trap facts (`trapOnNull`, `getFallthroughType`,
// `evaluateCastCheck`) and then preserve the remaining operand effects through
// dropped-child rewrites. Keep the represented flat raw windows in one fact so
// the dispatcher no longer owns separate ref.null/ref.test/ref.cast/null-trap
// peepholes.
priv struct RawOiRefNullTrapRewriteFact {
end : Int32
terminates_region : Bool
is_dropped_unreachable_tail : Bool
is_ref_null_as_non_null : Bool
is_ref_null_test_drop : Bool
is_ref_null_cast_drop : Bool
is_local_null_test_cast_drop : Bool
test_nullable : Bool
cast_nullable : Bool
cast_exact : Bool
target_heap : @lib.HeapType?
preserved_local : @lib.LocalIdx?
rewrite : Array[@lib.Instruction]
}
///|
fn RawOiRefNullTrapRewriteFact::proves_binaryen_ref_null_trap_repair(
self : RawOiRefNullTrapRewriteFact,
) -> Bool {
ignore(self.terminates_region)
ignore(self.test_nullable)
ignore(self.cast_nullable)
ignore(self.cast_exact)
ignore(self.target_heap)
ignore(self.preserved_local)
let family_count = (if self.is_dropped_unreachable_tail { 1 } else { 0 }) +
(if self.is_ref_null_as_non_null { 1 } else { 0 }) +
(if self.is_ref_null_test_drop { 1 } else { 0 }) +
(if self.is_ref_null_cast_drop { 1 } else { 0 }) +
(if self.is_local_null_test_cast_drop { 1 } else { 0 })
self.rewrite.length() > 0 && family_count == 1
}
///|
fn RawOiRefNullTrapRewriteFact::append_rewrite(
self : RawOiRefNullTrapRewriteFact,
out : Array[@lib.Instruction],
) -> Unit {
if self.proves_binaryen_ref_null_trap_repair() {
for instr in self.rewrite {
out.push(instr)
}
}
}
///|
fn RawOiRefNullTrapRewriteFact::from_dropped_unreachable_tail(
current : @lib.Instruction,
region : RawOiInstructionRegionFact,
start : Int32,
) -> RawOiRefNullTrapRewriteFact? {
if start + 1 >= region.roots.length() {
return None
}
match region.roots[start + 1].instr {
@lib.Instruction::Drop if pass_raw_dropped_block_has_unreachable_tail(
current,
) =>
Some({
end: start + 2,
terminates_region: true,
is_dropped_unreachable_tail: true,
is_ref_null_as_non_null: false,
is_ref_null_test_drop: false,
is_ref_null_cast_drop: false,
is_local_null_test_cast_drop: false,
test_nullable: false,
cast_nullable: false,
cast_exact: false,
target_heap: None,
preserved_local: None,
rewrite: [@lib.Instruction::unreachable_()],
})
_ => None
}
}
///|
fn RawOiRefNullTrapRewriteFact::from_ref_null_as_non_null(
current : @lib.Instruction,
region : RawOiInstructionRegionFact,
start : Int32,
) -> RawOiRefNullTrapRewriteFact? {
if start + 1 >= region.roots.length() {
return None
}
match (current, region.roots[start + 1].instr) {
(@lib.Instruction::RefNull(_), @lib.Instruction::RefAsNonNull) =>
Some({
end: start + 2,
terminates_region: true,
is_dropped_unreachable_tail: false,
is_ref_null_as_non_null: true,
is_ref_null_test_drop: false,
is_ref_null_cast_drop: false,
is_local_null_test_cast_drop: false,
test_nullable: false,
cast_nullable: false,
cast_exact: false,
target_heap: None,
preserved_local: None,
rewrite: [@lib.Instruction::unreachable_()],
})
_ => None
}
}
///|
fn RawOiRefNullTrapRewriteFact::from_local_null_test_cast_drop(
current : @lib.Instruction,
region : RawOiInstructionRegionFact,
start : Int32,
) -> RawOiRefNullTrapRewriteFact? {
if start + 10 >= region.roots.length() {
return None
}
match
(
current,
region.roots[start + 1].instr,
region.roots[start + 2].instr,
region.roots[start + 3].instr,
region.roots[start + 4].instr,
region.roots[start + 5].instr,
region.roots[start + 6].instr,
region.roots[start + 7].instr,
region.roots[start + 8].instr,
region.roots[start + 9].instr,
region.roots[start + 10].instr,
) {
(
@lib.Instruction::RefNull(_),
@lib.Instruction::LocalSet(set_local),
@lib.Instruction::LocalGet(is_null_local),
@lib.Instruction::RefIsNull,
@lib.Instruction::Drop,
@lib.Instruction::LocalGet(test_local),
@lib.Instruction::RefTest(test_nullable, false, _),
@lib.Instruction::Drop,
@lib.Instruction::LocalGet(cast_local),
@lib.Instruction::RefCast(true, cast_exact, target_heap),
@lib.Instruction::Drop,
) if set_local == is_null_local &&
set_local == test_local &&
set_local == cast_local =>
Some({
end: start + 11,
terminates_region: false,
is_dropped_unreachable_tail: false,
is_ref_null_as_non_null: false,
is_ref_null_test_drop: false,
is_ref_null_cast_drop: false,
is_local_null_test_cast_drop: true,
test_nullable,
cast_nullable: true,
cast_exact,
target_heap: Some(target_heap),
preserved_local: Some(set_local),
rewrite: [
current,
region.roots[start + 1].instr,
region.roots[start + 2].instr,
region.roots[start + 3].instr,
region.roots[start + 4].instr,
region.roots[start + 5].instr,
@lib.Instruction::drop(),
@lib.Instruction::i32_const(
@lib.I32(if test_nullable { 1 } else { 0 }),
),
@lib.Instruction::drop(),
region.roots[start + 8].instr,
@lib.Instruction::drop(),
],
})
_ => None
}
}
///|
fn RawOiRefNullTrapRewriteFact::from_ref_null_test_drop(
current : @lib.Instruction,
region : RawOiInstructionRegionFact,
start : Int32,
) -> RawOiRefNullTrapRewriteFact? {
if start + 2 >= region.roots.length() {
return None
}
match
(current, region.roots[start + 1].instr, region.roots[start + 2].instr) {
(
@lib.Instruction::RefNull(_),
@lib.Instruction::RefTest(nullable, false, _),
@lib.Instruction::Drop,
) =>
Some({
end: start + 3,
terminates_region: false,
is_dropped_unreachable_tail: false,
is_ref_null_as_non_null: false,
is_ref_null_test_drop: true,
is_ref_null_cast_drop: false,
is_local_null_test_cast_drop: false,
test_nullable: nullable,
cast_nullable: false,
cast_exact: false,
target_heap: None,
preserved_local: None,
rewrite: [
@lib.Instruction::i32_const(@lib.I32(if nullable { 1 } else { 0 })),
@lib.Instruction::drop(),
],
})
_ => None
}
}
///|
fn RawOiRefNullTrapRewriteFact::from_ref_null_cast_drop(
current : @lib.Instruction,
region : RawOiInstructionRegionFact,
start : Int32,
) -> RawOiRefNullTrapRewriteFact? {
if start + 2 >= region.roots.length() {
return None
}
match
(current, region.roots[start + 1].instr, region.roots[start + 2].instr) {
(
@lib.Instruction::RefNull(_),
@lib.Instruction::RefCast(nullable, exact, target_heap),
@lib.Instruction::Drop,
) => {
let rewrite = if nullable {
[
@lib.Instruction::ref_null_type(
@lib.RefType::new(true, target_heap, exact~),
),
@lib.Instruction::drop(),
]
} else {
[@lib.Instruction::unreachable_()]
}
Some({
end: start + 3,
terminates_region: not(nullable),
is_dropped_unreachable_tail: false,
is_ref_null_as_non_null: false,
is_ref_null_test_drop: false,
is_ref_null_cast_drop: true,
is_local_null_test_cast_drop: false,
test_nullable: false,
cast_nullable: nullable,
cast_exact: exact,
target_heap: Some(target_heap),
preserved_local: None,
rewrite,
})
}
_ => None
}
}
///|
fn RawOiRefNullTrapRewriteFact::from_region(
current : @lib.Instruction,
region : RawOiInstructionRegionFact,
start : Int32,
) -> RawOiRefNullTrapRewriteFact? {
if start < 0 ||
start >= region.roots.length() ||
!(region.has_root_at(start, RawOiRootKind::RefNull) ||
region.roots[start].flow.has_nested_control) {
return None
}
match
RawOiRefNullTrapRewriteFact::from_dropped_unreachable_tail(
current, region, start,
) {
Some(fact) => return Some(fact)
None => ()
}
match
RawOiRefNullTrapRewriteFact::from_ref_null_as_non_null(
current, region, start,
) {
Some(fact) => return Some(fact)
None => ()
}
match
RawOiRefNullTrapRewriteFact::from_local_null_test_cast_drop(
current, region, start,
) {
Some(fact) => return Some(fact)
None => ()
}
match
RawOiRefNullTrapRewriteFact::from_ref_null_test_drop(current, region, start) {
Some(fact) => return Some(fact)
None => ()
}
RawOiRefNullTrapRewriteFact::from_ref_null_cast_drop(current, region, start)
}
///|
// Accepted raw OI-H traversal boundary. One immutable region per visited body
// discovers direct, table, select, and null/trap candidates. The current-root
// overlay never promotes local assignment dataflow into exact target proof.
// Reopen if this function starts matching target/operand spellings directly or
// bypasses family-owned type, return-call, effect, order, and trap proof.
fn run_hot_pipeline_raw_optimize_instructions_directize_call_ref_body_with_root(
instrs : Array[@lib.Instruction],
) -> (Array[@lib.Instruction], Bool) {
// Unchanged bodies stay borrowed, as in the raw dispatcher's skipped path.
// Own the visited prefix before the first child or flat rewrite appends.
let mut out = instrs
let region = RawOiInstructionRegionFact::from_instrs(instrs)
let mut changed = false
let mut i = 0
while i < instrs.length() {
let (current, child_changed) = run_hot_pipeline_raw_optimize_instructions_directize_call_ref_instr(
instrs[i],
)
if child_changed && !changed {
out = instrs[0:i].to_owned()
changed = true
}
match RawOiRefNullTrapRewriteFact::from_region(current, region, i) {
Some(fact) => {
if !changed {
out = instrs[0:i].to_owned()
}
fact.append_rewrite(out)
changed = true
i = fact.end
if fact.terminates_region {
break
}
continue
}
None => ()
}
let overlay : RawOiCurrentRootRevisionOverlay = {
current,
region,
start: i,
}
match RawOiCallRefDirectizeRewriteFact::from_overlay(overlay) {
Some(fact) => {
if !changed {
out = instrs[0:i].to_owned()
}
fact.append_rewrite(out)
changed = true
i = fact.end
continue
}
None => ()
}
if changed {
out.push(current)
}
i += 1
}
(out, changed)
}
///|
// Accepted raw OI-H root wrapper. Nested rewrites use current-root overlays over
// immutable region facts and never build a second function-wide index.
fn run_hot_pipeline_raw_optimize_instructions_directize_call_ref_body(
instrs : Array[@lib.Instruction],
) -> (Array[@lib.Instruction], Bool) {
run_hot_pipeline_raw_optimize_instructions_directize_call_ref_body_with_root(
instrs,
)
}
///|
// Raw OI-F effectful-sibling same-global `if` fact. Binaryen v130's
// `optimizeTernary` hoists a shallow-equal one-child operation out of an `if`;
// this raw fallback represents the flat-stack form where both arms perform the
// same `global.set` and the arm values become the value-producing `if` child.
priv struct RawOiSameGlobalIfSinkFact {
global_idx : @lib.GlobalIdx
value_type : @lib.ValType
then_value : @lib.Instruction
else_value : @lib.Instruction
rewrite : Array[@lib.Instruction]
}
///|
fn RawOiSameGlobalIfSinkFact::proves_binaryen_ternary_same_global_sink(
self : RawOiSameGlobalIfSinkFact,
) -> Bool {
ignore(self.global_idx)
ignore(self.value_type)
ignore(self.then_value)
ignore(self.else_value)
self.rewrite.length() == 2
}
///|
fn RawOiSameGlobalIfSinkFact::to_rewrite(
self : RawOiSameGlobalIfSinkFact,
) -> Array[@lib.Instruction]? {
if self.proves_binaryen_ternary_same_global_sink() {
Some(self.rewrite)
} else {
None
}
}
///|
fn RawOiSameGlobalIfSinkFact::from_if_arms(
block_type : @lib.BlockType,
then_body : Array[@lib.Instruction],
else_body : Array[@lib.Instruction],
module_ctx : @ir.HotModuleContext,
) -> RawOiSameGlobalIfSinkFact? {
if block_type != @lib.BlockType::void_() ||
then_body.length() != 2 ||
else_body.length() != 2 {
return None
}
let then_value = then_body[0]
let else_value = else_body[0]
let global_idx = match (then_body[1], else_body[1]) {
(@lib.GlobalSet(global_idx), @lib.GlobalSet(other_global_idx)) if global_idx ==
other_global_idx => global_idx
_ => return None
}
let value_type = match
@ir.hot_module_resolve_global_type(module_ctx, global_idx) {
Some(@lib.GlobalType(val_type, _)) => val_type
None => return None
}
Some({
global_idx,
value_type,
then_value,
else_value,
rewrite: [
@lib.Instruction::if_(
@lib.BlockType::val_type(value_type),
[then_value],
Some([else_value]),
),
@lib.Instruction::global_set(global_idx),
],
})
}
///|
priv struct RawOiIdenticalIfArmsFact {
rewrite : Array[@lib.Instruction]
}
///|
fn RawOiIdenticalIfArmsFact::from_arms(
then_body : Array[@lib.Instruction],
else_body : Array[@lib.Instruction],
) -> RawOiIdenticalIfArmsFact? {
// Exactly one branch executes after the condition. If both instruction trees
// are identical, dropping the condition value and executing that tree once
// preserves every branch effect, trap, and result in source order. A direct
// branch moves out of the removed `if` label scope, so rebase an outer target
// by one. Branches to the removed `if` itself and nested branch trees stay on
// the guarded HOT path rather than being copied with stale relative depths.
if !pass_const_instruction_vectors_bits_equal(then_body, else_body) {
return None
}
match then_body {
[@lib.Br(label_idx)] => {
let @lib.LabelIdx(raw_label_idx) = label_idx
let depth = raw_label_idx.reinterpret_as_int()
if depth <= 0 {
return None
}
Some({
rewrite: [
@lib.Instruction::drop(),
@lib.Instruction::br(
@lib.LabelIdx::new((depth - 1).reinterpret_as_uint()),
),
],
})
}
_ =>
if run_hot_pipeline_raw_instrs_have_branch(then_body) {
None
} else {
let rewrite = [@lib.Instruction::drop()]
for instr in then_body {
rewrite.push(instr)
}
Some({ rewrite, })
}
}
}
///|
priv struct RawOiSameLocalConstIfSinkFact {
rewrite : Array[@lib.Instruction]
}
///|
fn RawOiSameLocalConstIfSinkFact::from_arms(
block_type : @lib.BlockType,
then_body : Array[@lib.Instruction],
else_body : Array[@lib.Instruction],
) -> RawOiSameLocalConstIfSinkFact? {
if block_type != @lib.BlockType::void_() ||
then_body.length() != 2 ||
else_body.length() != 2 {
return None
}
match (then_body[0], then_body[1], else_body[0], else_body[1]) {
(
@lib.I32Const(then_value),
@lib.LocalSet(then_local),
@lib.I32Const(else_value),
@lib.LocalSet(else_local),
) if then_local == else_local =>
Some({
rewrite: [
@lib.Instruction::if_(
@lib.BlockType::val_type(@lib.ValType::i32()),
[@lib.Instruction::i32_const(then_value)],
Some([@lib.Instruction::i32_const(else_value)]),
),
@lib.Instruction::local_set(then_local),
],
})
_ => None
}
}
///|
priv struct RawOiSameLocalConstIfEqzRevisitFact {
value_if : @lib.Instruction
local_set : @lib.Instruction
}
///|
fn RawOiSameLocalConstIfEqzRevisitFact::from_rewrite(
prefix : Array[@lib.Instruction],
expanded : Array[@lib.Instruction],
) -> RawOiSameLocalConstIfEqzRevisitFact? {
if prefix.length() == 0 ||
prefix[prefix.length() - 1] != @lib.Instruction::i32_eqz() {
return None
}
match expanded {
[@lib.If(block_type, then_body, Some(else_body)), @lib.LocalSet(local_idx)] =>
Some({
value_if: @lib.Instruction::if_(block_type, else_body, Some(then_body)),
local_set: @lib.Instruction::local_set(local_idx),
})
_ => None
}
}
///|
fn RawOiSameLocalConstIfEqzRevisitFact::append_rewrite(
self : RawOiSameLocalConstIfEqzRevisitFact,
rewritten : Array[@lib.Instruction],
) -> Unit {
ignore(rewritten.pop())
rewritten.push(self.value_if)
rewritten.push(self.local_set)
}
///|
// Accepted raw OI-D/F/G/J bridge boundary. This function is only the recursive
// control-body traversal for the effectful-sibling raw lane; the same-global
// `if` rewrite decision is delegated to `RawOiSameGlobalIfSinkFact`, and all
// straight-line windows are owned by facts in the sibling body dispatcher.
// Reopen if it grows direct pattern windows, arity/name whitelists, or behavior
// that should be represented as a Binaryen source-backed fact instead.
fn run_hot_pipeline_raw_optimize_instructions_effectful_sibling_instr(
instr : @lib.Instruction,
module_ctx : @ir.HotModuleContext,
) -> (Array[@lib.Instruction], Bool) {
match instr {
@lib.Block(block_type, @lib.Expr(body)) => {
let (next_body, changed) = run_hot_pipeline_raw_optimize_instructions_effectful_sibling_body(
body, module_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) = run_hot_pipeline_raw_optimize_instructions_effectful_sibling_body(
body, module_ctx,
)
if changed {
([@lib.Instruction::loop_(block_type, @lib.Expr::new(next_body))], true)
} else {
([instr], false)
}
}
@lib.TryTable(block_type, catches, @lib.Expr(body)) => {
let (next_body, changed) = run_hot_pipeline_raw_optimize_instructions_effectful_sibling_body(
body, module_ctx,
)
if changed {
(
[
@lib.Instruction::try_table(
block_type,
catches,
@lib.Expr::new(next_body),
),
],
true,
)
} else {
([instr], false)
}
}
@lib.If(block_type, then_body, Some(else_body)) => {
let (next_then, then_changed) = run_hot_pipeline_raw_optimize_instructions_effectful_sibling_body(
then_body, module_ctx,
)
let (next_else, else_changed) = run_hot_pipeline_raw_optimize_instructions_effectful_sibling_body(
else_body, module_ctx,
)
match RawOiIdenticalIfArmsFact::from_arms(next_then, next_else) {
Some(fact) => return (fact.rewrite, true)
None => ()
}
match
RawOiSameLocalConstIfSinkFact::from_arms(
block_type, next_then, next_else,
) {
Some(fact) => return (fact.rewrite, true)
None => ()
}
match
RawOiSameGlobalIfSinkFact::from_if_arms(
block_type, next_then, next_else, module_ctx,
) {
Some(fact) =>
match fact.to_rewrite() {
Some(expanded) => (expanded, true)
None =>
if then_changed || else_changed {
(
[
@lib.Instruction::if_(
block_type,
next_then,
Some(next_else),
),
],
true,
)
} else {
([instr], false)
}
}
None =>
if then_changed || else_changed {
(
[@lib.Instruction::if_(block_type, next_then, Some(next_else))],
true,
)
} else {
([instr], false)
}
}
}
@lib.If(block_type, then_body, None) => {
let (next_then, then_changed) = run_hot_pipeline_raw_optimize_instructions_effectful_sibling_body(
then_body, module_ctx,
)
if then_changed {
([@lib.Instruction::if_(block_type, next_then, None)], true)
} else {
([instr], false)
}
}
_ => ([instr], false)
}
}
///|
// Raw OI-D scalar atom fact for i32.wrap_i64 over a constant. Binaryen v130's
// `removeWrap` treats constant i64 inputs as exactly their low i32 bits; keep
// that proof as data so the flat dispatcher does not own the truncation math.
priv struct RawOiWrapI64ConstFact {
source : Int64
wrapped : @lib.I32
}
///|
fn RawOiWrapI64ConstFact::from_raw(value : Int64) -> RawOiWrapI64ConstFact {
let lower64 = value & 4294967295L
let wrapped = if lower64 >= 2147483648L {
@lib.I32((lower64 - 4294967296L).to_int())
} else {
@lib.I32(lower64.to_int())
}
{ source: value, wrapped, }
}
///|
fn RawOiWrapI64ConstFact::proves_binaryen_wrap_const(
self : RawOiWrapI64ConstFact,
) -> Bool {
let @lib.I32(_) = self.wrapped
ignore(self.source)
true
}
///|
// Raw OI-D scalar atom fact for signed operations whose constant operands are
// both nonnegative. Binaryen v130 rewrites these spellings to the matching
// unsigned operator in `visitBinary`; record the operator proof separately from
// the surrounding flat-stack window.
priv struct RawOiNonnegativeSignedSpellingFact {
is_i32 : Bool
is_i64 : Bool
unsigned_instr : @lib.Instruction
}
///|
fn RawOiNonnegativeSignedSpellingFact::from_i32(
lhs_raw : Int32,
rhs_raw : Int32,
instr : @lib.Instruction,
) -> RawOiNonnegativeSignedSpellingFact? {
if lhs_raw < 0 || rhs_raw < 0 {
return None
}
match instr {
@lib.I32LtS =>
Some({
is_i32: true,
is_i64: false,
unsigned_instr: @lib.Instruction::i32_lt_u(),
})
@lib.I32GtS =>
Some({
is_i32: true,
is_i64: false,
unsigned_instr: @lib.Instruction::i32_gt_u(),
})
@lib.I32LeS =>
Some({
is_i32: true,
is_i64: false,
unsigned_instr: @lib.Instruction::i32_le_u(),
})
@lib.I32GeS =>
Some({
is_i32: true,
is_i64: false,
unsigned_instr: @lib.Instruction::i32_ge_u(),
})
@lib.I32DivS =>
Some({
is_i32: true,
is_i64: false,
unsigned_instr: @lib.Instruction::i32_div_u(),
})
@lib.I32RemS =>
Some({
is_i32: true,
is_i64: false,
unsigned_instr: @lib.Instruction::i32_rem_u(),
})
@lib.I32ShrS =>
Some({
is_i32: true,
is_i64: false,
unsigned_instr: @lib.Instruction::i32_shr_u(),
})
_ => None
}
}
///|
fn RawOiNonnegativeSignedSpellingFact::from_i64(
lhs_raw : Int64,
rhs_raw : Int64,
instr : @lib.Instruction,
) -> RawOiNonnegativeSignedSpellingFact? {
if lhs_raw < 0L || rhs_raw < 0L {
return None
}
match instr {
@lib.I64LtS =>
Some({
is_i32: false,
is_i64: true,
unsigned_instr: @lib.Instruction::i64_lt_u(),
})
@lib.I64GtS =>
Some({
is_i32: false,
is_i64: true,
unsigned_instr: @lib.Instruction::i64_gt_u(),
})
@lib.I64LeS =>
Some({
is_i32: false,
is_i64: true,
unsigned_instr: @lib.Instruction::i64_le_u(),
})
@lib.I64GeS =>
Some({
is_i32: false,
is_i64: true,
unsigned_instr: @lib.Instruction::i64_ge_u(),
})
@lib.I64DivS =>
Some({
is_i32: false,
is_i64: true,
unsigned_instr: @lib.Instruction::i64_div_u(),
})
@lib.I64RemS =>
Some({
is_i32: false,
is_i64: true,
unsigned_instr: @lib.Instruction::i64_rem_u(),
})
@lib.I64ShrS =>
Some({
is_i32: false,
is_i64: true,
unsigned_instr: @lib.Instruction::i64_shr_u(),
})
_ => None
}
}
///|
fn RawOiNonnegativeSignedSpellingFact::proves_binaryen_unsigned_spelling(
self : RawOiNonnegativeSignedSpellingFact,
) -> Bool {
(self.is_i32 || self.is_i64) && !(self.is_i32 && self.is_i64)
}
///|
// Raw OI-D float-subtraction negation facts. Binaryen only rewrites
// `x - C` to `x + -C` when negating the finite, nonzero constant does not cross
// the NaN or signed-zero boundary.
priv struct RawOiF32SubNegatedConstFact {
original : Float
negated : Float
}
///|
fn RawOiF32SubNegatedConstFact::from_raw(
raw : Float,
) -> RawOiF32SubNegatedConstFact? {
let bits = raw.reinterpret_as_uint()
let magnitude = bits & 0x7FFFFFFFU
if magnitude == 0U || (bits & 0x7F800000U) == 0x7F800000U {
return None
}
Some({ original: raw, negated: -raw, })
}
///|
fn RawOiF32SubNegatedConstFact::proves_binaryen_negatable_sub_const(
self : RawOiF32SubNegatedConstFact,
) -> Bool {
self.original == -self.negated
}
///|
priv struct RawOiF64SubNegatedConstFact {
original : Double
negated : Double
}
///|
fn RawOiF64SubNegatedConstFact::from_raw(
raw : Double,
) -> RawOiF64SubNegatedConstFact? {
let bits = raw.reinterpret_as_uint64()
let magnitude = bits & 0x7FFFFFFFFFFFFFFFUL
if magnitude == 0UL || (bits & 0x7FF0000000000000UL) == 0x7FF0000000000000UL {
return None
}
Some({ original: raw, negated: -raw, })
}
///|
fn RawOiF64SubNegatedConstFact::proves_binaryen_negatable_sub_const(
self : RawOiF64SubNegatedConstFact,
) -> Bool {
self.original == -self.negated
}
///|
// Raw OI-D power-of-two multiply fact. Binaryen's constant-right multiply
// rewrite proves that the right constant is a positive power of two and carries
// the shift count into the replacement `shl`.
priv struct RawOiI32PowerOfTwoShiftFact {
raw : Int32
shift : Int32
}
///|
fn RawOiI32PowerOfTwoShiftFact::from_raw(
raw : Int32,
) -> RawOiI32PowerOfTwoShiftFact? {
if raw <= 0 || (raw & (raw - 1)) != 0 {
return None
}
let mut shift = 0
let mut value = raw
while value > 1 {
value = value >> 1
shift = shift + 1
}
Some({ raw, shift, })
}
///|
fn RawOiI32PowerOfTwoShiftFact::proves_binaryen_power_of_two_shift(
self : RawOiI32PowerOfTwoShiftFact,
) -> Bool {
self.raw > 0 && (self.raw & (self.raw - 1)) == 0
}
///|
priv struct RawOiI64PowerOfTwoShiftFact {
raw : Int64
shift : Int64
}
///|
fn RawOiI64PowerOfTwoShiftFact::from_raw(
raw : Int64,
) -> RawOiI64PowerOfTwoShiftFact? {
if raw <= 0L || (raw & (raw - 1L)) != 0L {
return None
}
let mut shift = 0L
let mut value = raw
while value > 1L {
value = value >> 1
shift = shift + 1L
}
Some({ raw, shift, })
}
///|
fn RawOiI64PowerOfTwoShiftFact::proves_binaryen_power_of_two_shift(
self : RawOiI64PowerOfTwoShiftFact,
) -> Bool {
self.raw > 0L && (self.raw & (self.raw - 1L)) == 0L
}
///|
priv struct RawOiScalarConstAtomFact {
is_i32_const : Bool
is_i64_const : Bool
}
///|
fn RawOiScalarConstAtomFact::from_instr(
instr : @lib.Instruction,
) -> RawOiScalarConstAtomFact {
match instr {
@lib.I32Const(_) => { is_i32_const: true, is_i64_const: false, }
@lib.I64Const(_) => { is_i32_const: false, is_i64_const: true, }
_ => { is_i32_const: false, is_i64_const: false, }
}
}
///|
// Raw OI-D ordered constant eq/ne fact. The flat bridge keeps the existing
// source-backed boundary that folds same-width eq/ne only when the serialized
// constants are already in Binaryen's preferred nondecreasing order.
priv struct RawOiOrderedConstEqNeFact {
result : @lib.I32
}
///|
fn RawOiOrderedConstEqNeFact::from_i32(
lhs_raw : Int32,
rhs_raw : Int32,
instr : @lib.Instruction,
) -> RawOiOrderedConstEqNeFact? {
if lhs_raw > rhs_raw {
return None
}
match instr {
@lib.I32Eq =>
Some({ result: @lib.I32(if lhs_raw == rhs_raw { 1 } else { 0 }), })
@lib.I32Ne =>
Some({ result: @lib.I32(if lhs_raw != rhs_raw { 1 } else { 0 }), })
_ => None
}
}
///|
fn RawOiOrderedConstEqNeFact::from_i64(
lhs_raw : Int64,
rhs_raw : Int64,
instr : @lib.Instruction,
) -> RawOiOrderedConstEqNeFact? {
if lhs_raw > rhs_raw {
return None
}
match instr {
@lib.I64Eq =>
Some({ result: @lib.I32(if lhs_raw == rhs_raw { 1 } else { 0 }), })
@lib.I64Ne =>
Some({ result: @lib.I32(if lhs_raw != rhs_raw { 1 } else { 0 }), })
_ => None
}
}
///|
// Raw OI-G zero-offset memarg fact for lowered memory.copy/fill rewrites.
// Binaryen's builder emits memory accesses with offset 0 and omits memory 0;
// keep that canonical memarg construction as data instead of a raw helper.
priv struct RawOiZeroOffsetMemargFact {
mem_idx : @lib.MemIdx
memarg : @lib.MemArg
}
///|
fn RawOiZeroOffsetMemargFact::from_mem_idx(
mem_idx : @lib.MemIdx,
) -> RawOiZeroOffsetMemargFact {
let @lib.MemIdx(raw_idx) = mem_idx
let encoded_mem = if raw_idx == 0U { None } else { Some(mem_idx) }
{ mem_idx, memarg: @lib.MemArg::new(@lib.U32(0), encoded_mem, @lib.U64(0)), }
}
///|
fn RawOiZeroOffsetMemargFact::proves_binaryen_zero_offset_memarg(
self : RawOiZeroOffsetMemargFact,
) -> Bool {
let @lib.MemArg(@lib.U32(align), mem, @lib.U64(offset)) = self.memarg
let @lib.MemIdx(raw_idx) = self.mem_idx
let mem_matches = match mem {
Some(mem_idx) => raw_idx != 0U && mem_idx == self.mem_idx
None => raw_idx == 0U
}
offset == 0UL && align == 0U && mem_matches
}
///|
// Raw OI-G constant-address offset-fold fact. Binaryen v130 folds a nonzero
// static memarg offset into a constant pointer only when the folded pointer
// cannot wrap for the represented address width; this fact owns that proof and
// the reset-offset memarg.
priv struct RawOiConstAddressOffsetFoldFact {
original_address : @lib.Instruction
original_memarg : @lib.MemArg
folded_address : @lib.Instruction
folded_memarg : @lib.MemArg
}
///|
fn RawOiConstAddressOffsetFoldFact::from_parts(
address : @lib.Instruction,
memarg : @lib.MemArg,
) -> RawOiConstAddressOffsetFoldFact? {
let @lib.MemArg(align, mem, @lib.U64(offset)) = memarg
if offset == 0UL {
return None
}
let folded_address = match address {
@lib.I32Const(@lib.I32(raw)) => {
if raw < 0 {
return None
}
let raw_u = raw.reinterpret_as_uint()
if raw_u > 2147483647U {
return None
}
let sum = raw_u.to_uint64() + offset
if sum > 2147483647UL {
return None
}
@lib.Instruction::i32_const(@lib.I32(sum.to_uint().reinterpret_as_int()))
}
@lib.I64Const(@lib.I64(raw)) => {
let raw_u = raw.reinterpret_as_uint64()
if raw_u > 18446744073709551615UL - offset {
return None
}
let sum = raw_u + offset
@lib.Instruction::i64_const(@lib.I64(sum.reinterpret_as_int64()))
}
_ => return None
}
Some({
original_address: address,
original_memarg: memarg,
folded_address,
folded_memarg: @lib.MemArg::new(align, mem, @lib.U64(0UL)),
})
}
///|
fn RawOiConstAddressOffsetFoldFact::proves_binaryen_memory_offset_fold(
self : RawOiConstAddressOffsetFoldFact,
) -> Bool {
ignore(self.original_address)
ignore(self.folded_address)
let @lib.MemArg(_, _, @lib.U64(original_offset)) = self.original_memarg
let @lib.MemArg(_, _, @lib.U64(folded_offset)) = self.folded_memarg
original_offset > 0UL && folded_offset == 0UL
}
///|
// Raw OI-G load/store access fact for offset folding. The constructors record
// which exact memory opcode carried the memarg, and the rewrite methods rebuild
// that same opcode with Binaryen's folded memarg.
priv struct RawOiMemoryAccessMemargFact {
memarg : @lib.MemArg
is_load : Bool
is_store : Bool
}
///|
fn RawOiMemoryAccessMemargFact::from_load_instr(
instr : @lib.Instruction,
) -> RawOiMemoryAccessMemargFact? {
match instr {
@lib.I32Load(memarg)
| @lib.I64Load(memarg)
| @lib.F32Load(memarg)
| @lib.F64Load(memarg)
| @lib.I32Load8S(memarg)
| @lib.I32Load8U(memarg)
| @lib.I32Load16S(memarg)
| @lib.I32Load16U(memarg)
| @lib.I64Load8S(memarg)
| @lib.I64Load8U(memarg)
| @lib.I64Load16S(memarg)
| @lib.I64Load16U(memarg)
| @lib.I64Load32S(memarg)
| @lib.I64Load32U(memarg)
| @lib.I32AtomicLoad(_, memarg)
| @lib.I64AtomicLoad(_, memarg)
| @lib.I32AtomicLoad8U(_, memarg)
| @lib.I32AtomicLoad16U(_, memarg)
| @lib.I64AtomicLoad8U(_, memarg)
| @lib.I64AtomicLoad16U(_, memarg)
| @lib.I64AtomicLoad32U(_, memarg) =>
Some({ memarg, is_load: true, is_store: false, })
_ => None
}
}
///|
fn RawOiMemoryAccessMemargFact::from_store_instr(
instr : @lib.Instruction,
) -> RawOiMemoryAccessMemargFact? {
match instr {
@lib.I32Store(memarg)
| @lib.I64Store(memarg)
| @lib.F32Store(memarg)
| @lib.F64Store(memarg)
| @lib.I32Store8(memarg)
| @lib.I32Store16(memarg)
| @lib.I64Store8(memarg)
| @lib.I64Store16(memarg)
| @lib.I64Store32(memarg)
| @lib.I32AtomicStore(_, memarg)
| @lib.I64AtomicStore(_, memarg)
| @lib.I32AtomicStore8(_, memarg)
| @lib.I32AtomicStore16(_, memarg)
| @lib.I64AtomicStore8(_, memarg)
| @lib.I64AtomicStore16(_, memarg)
| @lib.I64AtomicStore32(_, memarg) =>
Some({ memarg, is_load: false, is_store: true, })
_ => None
}
}
///|
fn RawOiMemoryAccessMemargFact::proves_load_access(
self : RawOiMemoryAccessMemargFact,
) -> Bool {
self.is_load && !self.is_store
}
///|
fn RawOiMemoryAccessMemargFact::proves_store_access(
self : RawOiMemoryAccessMemargFact,
) -> Bool {
self.is_store && !self.is_load
}
///|
fn RawOiMemoryAccessMemargFact::load_rewrite_with_memarg(
instr : @lib.Instruction,
memarg : @lib.MemArg,
) -> @lib.Instruction? {
match instr {
@lib.I32Load(_) => Some(@lib.Instruction::i32_load(memarg))
@lib.I64Load(_) => Some(@lib.Instruction::i64_load(memarg))
@lib.F32Load(_) => Some(@lib.Instruction::f32_load(memarg))
@lib.F64Load(_) => Some(@lib.Instruction::f64_load(memarg))
@lib.I32Load8S(_) => Some(@lib.Instruction::i32_load8s(memarg))
@lib.I32Load8U(_) => Some(@lib.Instruction::i32_load8u(memarg))
@lib.I32Load16S(_) => Some(@lib.Instruction::i32_load16s(memarg))
@lib.I32Load16U(_) => Some(@lib.Instruction::i32_load16u(memarg))
@lib.I64Load8S(_) => Some(@lib.Instruction::i64_load8s(memarg))
@lib.I64Load8U(_) => Some(@lib.Instruction::i64_load8u(memarg))
@lib.I64Load16S(_) => Some(@lib.Instruction::i64_load16s(memarg))
@lib.I64Load16U(_) => Some(@lib.Instruction::i64_load16u(memarg))
@lib.I64Load32S(_) => Some(@lib.Instruction::i64_load32s(memarg))
@lib.I64Load32U(_) => Some(@lib.Instruction::i64_load32u(memarg))
@lib.I32AtomicLoad(order, _) =>
Some(@lib.Instruction::i32_atomic_load(memarg, order~))
@lib.I64AtomicLoad(order, _) =>
Some(@lib.Instruction::i64_atomic_load(memarg, order~))
@lib.I32AtomicLoad8U(order, _) =>
Some(@lib.Instruction::i32_atomic_load8_u(memarg, order~))
@lib.I32AtomicLoad16U(order, _) =>
Some(@lib.Instruction::i32_atomic_load16_u(memarg, order~))
@lib.I64AtomicLoad8U(order, _) =>
Some(@lib.Instruction::i64_atomic_load8_u(memarg, order~))
@lib.I64AtomicLoad16U(order, _) =>
Some(@lib.Instruction::i64_atomic_load16_u(memarg, order~))
@lib.I64AtomicLoad32U(order, _) =>
Some(@lib.Instruction::i64_atomic_load32_u(memarg, order~))
_ => None
}
}
///|
fn RawOiMemoryAccessMemargFact::store_rewrite_with_memarg(
instr : @lib.Instruction,
memarg : @lib.MemArg,
) -> @lib.Instruction? {
match instr {
@lib.I32Store(_) => Some(@lib.Instruction::i32_store(memarg))
@lib.I64Store(_) => Some(@lib.Instruction::i64_store(memarg))
@lib.F32Store(_) => Some(@lib.Instruction::f32_store(memarg))
@lib.F64Store(_) => Some(@lib.Instruction::f64_store(memarg))
@lib.I32Store8(_) => Some(@lib.Instruction::i32_store8(memarg))
@lib.I32Store16(_) => Some(@lib.Instruction::i32_store16(memarg))
@lib.I64Store8(_) => Some(@lib.Instruction::i64_store8(memarg))
@lib.I64Store16(_) => Some(@lib.Instruction::i64_store16(memarg))
@lib.I64Store32(_) => Some(@lib.Instruction::i64_store32(memarg))
@lib.I32AtomicStore(order, _) =>
Some(@lib.Instruction::i32_atomic_store(memarg, order~))
@lib.I64AtomicStore(order, _) =>
Some(@lib.Instruction::i64_atomic_store(memarg, order~))
@lib.I32AtomicStore8(order, _) =>
Some(@lib.Instruction::i32_atomic_store8(memarg, order~))
@lib.I32AtomicStore16(order, _) =>
Some(@lib.Instruction::i32_atomic_store16(memarg, order~))
@lib.I64AtomicStore8(order, _) =>
Some(@lib.Instruction::i64_atomic_store8(memarg, order~))
@lib.I64AtomicStore16(order, _) =>
Some(@lib.Instruction::i64_atomic_store16(memarg, order~))
@lib.I64AtomicStore32(order, _) =>
Some(@lib.Instruction::i64_atomic_store32(memarg, order~))
_ => None
}
}
///|
// Raw OI-D i64 extend-local commute fact. Binaryen's visitor canonicalizes
// commutative i64 local/extend/local shells by moving the unextended local left
// of the extend; the raw dispatcher keeps the complete replacement as data.
priv struct RawOiI64ExtendLocalCommuteFact {
rewrite : Array[@lib.Instruction]
}
///|
fn RawOiI64ExtendLocalCommuteFact::from_parts(
lhs : @lib.Instruction,
extend_instr : @lib.Instruction,
rhs : @lib.Instruction,
binop : @lib.Instruction,
) -> RawOiI64ExtendLocalCommuteFact? {
match (lhs, extend_instr, rhs, binop) {
(
@lib.LocalGet(_),
@lib.I64ExtendI32U
| @lib.I64ExtendI32S,
@lib.LocalGet(_),
@lib.I64Add
| @lib.I64And
| @lib.I64Or
| @lib.I64Xor,
) => Some({ rewrite: [rhs, lhs, extend_instr, binop], })
_ => None
}
}
///|
// Raw OI-G static-offset fold fact for the fallback stack dispatcher. Binaryen
// v130's `optimizeMemoryAccess` folds a nonzero static access offset into a
// constant address when the sum is in range, then resets the memarg offset to
// zero. Keep the represented load/store windows as one fact with the proven
// access kind, original/folded memargs, folded address, and complete rewrite
// payload instead of dispatcher-owned ad hoc peepholes.
priv struct RawOiMemoryOffsetFoldRewriteFact {
end : Int32
is_load : Bool
is_store : Bool
original_memarg : @lib.MemArg
folded_memarg : @lib.MemArg
folded_address : @lib.Instruction
rewrite : Array[@lib.Instruction]
}
///|
fn RawOiMemoryOffsetFoldRewriteFact::proves_binaryen_memory_offset_fold(
self : RawOiMemoryOffsetFoldRewriteFact,
) -> Bool {
let @lib.MemArg(_, _, @lib.U64(original_offset)) = self.original_memarg
let @lib.MemArg(_, _, @lib.U64(folded_offset)) = self.folded_memarg
ignore(self.folded_address)
self.rewrite.length() > 0 &&
original_offset > 0UL &&
folded_offset == 0UL &&
(self.is_load || self.is_store) &&
!(self.is_load && self.is_store)
}
///|
fn RawOiMemoryOffsetFoldRewriteFact::append_rewrite(
self : RawOiMemoryOffsetFoldRewriteFact,
out : Array[@lib.Instruction],
) -> Unit {
if self.proves_binaryen_memory_offset_fold() {
for instr in self.rewrite {
out.push(instr)
}
}
}
///|
fn RawOiMemoryOffsetFoldRewriteFact::from_load_region(
region : RawOiInstructionRegionFact,
start : Int32,
) -> RawOiMemoryOffsetFoldRewriteFact? {
if start + 1 >= region.roots.length() ||
!region.has_root_at(start + 1, RawOiRootKind::PlainLoad) {
return None
}
let address = region.roots[start].instr
let load_instr = region.roots[start + 1].instr
let access = match RawOiMemoryAccessMemargFact::from_load_instr(load_instr) {
Some(fact) if fact.proves_load_access() => fact
_ => return None
}
let address_fold = match
RawOiConstAddressOffsetFoldFact::from_parts(address, access.memarg) {
Some(fact) if fact.proves_binaryen_memory_offset_fold() => fact
_ => return None
}
let folded_load = match
RawOiMemoryAccessMemargFact::load_rewrite_with_memarg(
load_instr,
address_fold.folded_memarg,
) {
Some(instr) => instr
None => return None
}
Some({
end: start + 2,
is_load: true,
is_store: false,
original_memarg: access.memarg,
folded_memarg: address_fold.folded_memarg,
folded_address: address_fold.folded_address,
rewrite: [address_fold.folded_address, folded_load],
})
}
///|
fn RawOiMemoryOffsetFoldRewriteFact::from_store_region(
region : RawOiInstructionRegionFact,
start : Int32,
) -> RawOiMemoryOffsetFoldRewriteFact? {
if start + 2 >= region.roots.length() ||
!region.has_root_at(start + 2, RawOiRootKind::MemoryStore) {
return None
}
let address = region.roots[start].instr
let value = region.roots[start + 1].instr
// This raw bridge represents exactly one address leaf, one value leaf, and
// the store. An operator in the value slot consumes operands that precede
// `start`; treating its nearest constant operand as the address folds the
// memarg into the value expression instead of the store address.
match RawOiFlatPureScalarLeafFact::from_instr(value) {
Some(_) => ()
None => return None
}
let store_instr = region.roots[start + 2].instr
let access = match
RawOiMemoryAccessMemargFact::from_store_instr(store_instr) {
Some(fact) if fact.proves_store_access() => fact
_ => return None
}
let address_fold = match
RawOiConstAddressOffsetFoldFact::from_parts(address, access.memarg) {
Some(fact) if fact.proves_binaryen_memory_offset_fold() => fact
_ => return None
}
let folded_store = match
RawOiMemoryAccessMemargFact::store_rewrite_with_memarg(
store_instr,
address_fold.folded_memarg,
) {
Some(instr) => instr
None => return None
}
Some({
end: start + 3,
is_load: false,
is_store: true,
original_memarg: access.memarg,
folded_memarg: address_fold.folded_memarg,
folded_address: address_fold.folded_address,
rewrite: [address_fold.folded_address, value, folded_store],
})
}
///|
fn RawOiMemoryOffsetFoldRewriteFact::from_region(
region : RawOiInstructionRegionFact,
start : Int32,
) -> RawOiMemoryOffsetFoldRewriteFact? {
match RawOiMemoryOffsetFoldRewriteFact::from_store_region(region, start) {
Some(fact) => return Some(fact)
None => ()
}
RawOiMemoryOffsetFoldRewriteFact::from_load_region(region, start)
}
///|
// Raw OI-G constant-size fact shared by memory.copy/fill lowerings and the
// lowered dispatcher. Binaryen v130 `optimizeMemoryCopy` and
// `optimizeMemoryFill` first prove that the size operand is a Const and then
// switch on the exact byte count; keep that proof as a named fact instead of
// threading ad hoc `i32.const` / `i64.const` matches through rewrite windows.
priv struct RawOiBulkConstSizeFact {
size : Int32
}
///|
fn RawOiBulkConstSizeFact::from_instr(
instr : @lib.Instruction,
) -> RawOiBulkConstSizeFact? {
match instr {
@lib.I32Const(@lib.I32(raw)) => Some({ size: raw, })
@lib.I64Const(@lib.I64(raw)) if raw >= 0L && raw <= 2147483647L =>
Some({ size: raw.to_int(), })
_ => None
}
}
///|
// Raw OI-G atomic access fact used by the lowered dispatcher. Binaryen's
// memory visitor treats atomic address operands as ordered memory effects; the
// raw bridge records the load/RMW/cmpxchg family and stack-address operand
// distance instead of keeping a window-only local.tee scanner.
priv struct RawOiAtomicAccessFact {
is_atomic_load : Bool
is_atomic_rmw : Bool
is_atomic_cmpxchg : Bool
address_operand_distance : Int32
}
///|
fn RawOiAtomicAccessFact::from_instr(
instr : @lib.Instruction,
) -> RawOiAtomicAccessFact? {
match instr {
@lib.I32AtomicLoad(_, _)
| @lib.I64AtomicLoad(_, _)
| @lib.I32AtomicLoad8U(_, _)
| @lib.I32AtomicLoad16U(_, _)
| @lib.I64AtomicLoad8U(_, _)
| @lib.I64AtomicLoad16U(_, _)
| @lib.I64AtomicLoad32U(_, _) =>
Some({
is_atomic_load: true,
is_atomic_rmw: false,
is_atomic_cmpxchg: false,
address_operand_distance: 1,
})
@lib.AtomicRmw(_, _, _) =>
Some({
is_atomic_load: false,
is_atomic_rmw: true,
is_atomic_cmpxchg: false,
address_operand_distance: 2,
})
@lib.AtomicCmpxchg(_, _, _) =>
Some({
is_atomic_load: false,
is_atomic_rmw: false,
is_atomic_cmpxchg: true,
address_operand_distance: 3,
})
_ => None
}
}
///|
fn RawOiAtomicAccessFact::proves_address_localtee_boundary(
self : RawOiAtomicAccessFact,
observed_operand_distance : Int32,
) -> Bool {
(self.is_atomic_load || self.is_atomic_rmw || self.is_atomic_cmpxchg) &&
self.address_operand_distance == observed_operand_distance
}
///|
priv struct RawOiAtomicLocalTeeAddressUseFact {
localtee_index : Int32
consumer_index : Int32
operand_distance : Int32
access : RawOiAtomicAccessFact
}
///|
fn RawOiAtomicLocalTeeAddressUseFact::from_region_at(
region : RawOiInstructionRegionFact,
localtee_index : Int32,
) -> RawOiAtomicLocalTeeAddressUseFact? {
if !region.has_root_at(localtee_index, RawOiRootKind::LocalTee) {
return None
}
match region.roots[localtee_index].instr {
@lib.LocalTee(_) => ()
_ => return None
}
for operand_distance = 1
operand_distance <= 3
operand_distance = operand_distance + 1 {
let consumer_index = localtee_index + operand_distance
if consumer_index >= region.roots.length() {
return None
}
match
RawOiAtomicAccessFact::from_instr(region.roots[consumer_index].instr) {
Some(access) if access.proves_address_localtee_boundary(operand_distance) =>
return Some({
localtee_index,
consumer_index,
operand_distance,
access,
})
_ => ()
}
}
None
}
///|
fn RawOiAtomicLocalTeeAddressUseFact::proves_raw_gate_boundary(
self : RawOiAtomicLocalTeeAddressUseFact,
) -> Bool {
ignore(self.localtee_index)
ignore(self.consumer_index)
self.access.proves_address_localtee_boundary(self.operand_distance)
}
///|
priv struct RawOiAtomicLocalTeeAddressRegionFact {
has_blocking_use : Bool
}
///|
fn RawOiAtomicLocalTeeAddressRegionFact::from_index(
index : RawOiFunctionRevisionFactIndex,
) -> RawOiAtomicLocalTeeAddressRegionFact {
for region in index.regions {
for i = 0; i < region.roots.length(); i = i + 1 {
match RawOiAtomicLocalTeeAddressUseFact::from_region_at(region, i) {
Some(fact) if fact.proves_raw_gate_boundary() =>
return { has_blocking_use: true, }
_ => ()
}
}
}
{ has_blocking_use: false, }
}
///|
// Raw OI-G memory index fact used by the lowered dispatcher. Binaryen's
// memory-access visitors reason from the access memory and operand positions;
// record the same fields instead of keeping the raw skip gate as a window-only
// local.tee scanner.
priv struct RawOiMemoryAccessIndexFact {
is_memory_access : Bool
is_nondefault_memory : Bool
}
///|
fn RawOiMemoryAccessIndexFact::from_memarg(
memarg : @lib.MemArg,
) -> RawOiMemoryAccessIndexFact {
let is_nondefault_memory = match memarg {
@lib.MemArg(_, Some(@lib.MemIdx(raw_idx)), _) => raw_idx != 0U
_ => false
}
{ is_memory_access: true, is_nondefault_memory, }
}
///|
fn RawOiMemoryAccessIndexFact::from_instr(
instr : @lib.Instruction,
) -> RawOiMemoryAccessIndexFact {
match instr {
@lib.I32Load(memarg)
| @lib.I64Load(memarg)
| @lib.F32Load(memarg)
| @lib.F64Load(memarg)
| @lib.I32Load8S(memarg)
| @lib.I32Load8U(memarg)
| @lib.I32Load16S(memarg)
| @lib.I32Load16U(memarg)
| @lib.I64Load8S(memarg)
| @lib.I64Load8U(memarg)
| @lib.I64Load16S(memarg)
| @lib.I64Load16U(memarg)
| @lib.I64Load32S(memarg)
| @lib.I64Load32U(memarg)
| @lib.I32Store(memarg)
| @lib.I64Store(memarg)
| @lib.F32Store(memarg)
| @lib.F64Store(memarg)
| @lib.I32Store8(memarg)
| @lib.I32Store16(memarg)
| @lib.I64Store8(memarg)
| @lib.I64Store16(memarg)
| @lib.I64Store32(memarg) => RawOiMemoryAccessIndexFact::from_memarg(memarg)
_ => { is_memory_access: false, is_nondefault_memory: false, }
}
}
///|
fn RawOiMemoryAccessIndexFact::proves_nondefault_memory_access(
self : RawOiMemoryAccessIndexFact,
) -> Bool {
self.is_memory_access && self.is_nondefault_memory
}
///|
// Trap-relaxed same-address localtee memory.copy fact for the public lowered
// raw gate. This is the represented stack spelling of Binaryen's
// `areConsecutiveInputsEqual` local.tee/get same-address proof; when it holds,
// the broader memory-localtee raw skip must not block OI-G's zero-size or
// same-address rewrite path.
priv struct RawOiTnhLocalTeeSameAddressMemoryCopyFact {
tee_local_matches_right_get : Bool
addr_matches_right_get : Bool
same_memory : Bool
addr_admitted : Bool
size_admitted : Bool
}
///|
fn RawOiTnhLocalTeeSameAddressMemoryCopyFact::from_region(
region : RawOiInstructionRegionFact,
) -> RawOiTnhLocalTeeSameAddressMemoryCopyFact? {
if region.roots.length() != 5 {
return None
}
match
(
region.roots[0].instr,
region.roots[1].instr,
region.roots[2].instr,
region.roots[3].instr,
region.roots[4].instr,
) {
(
addr,
@lib.LocalTee(tee_local),
@lib.LocalGet(get_local),
size,
@lib.MemoryCopy(dst_mem, src_mem),
) =>
Some({
tee_local_matches_right_get: tee_local == get_local,
addr_matches_right_get: addr == @lib.Instruction::local_get(get_local),
same_memory: dst_mem == src_mem,
addr_admitted: RawOiBulkStackOperandAtomFact::from_instr(addr)
is Some(_),
size_admitted: RawOiBulkStackOperandAtomFact::from_instr(size)
is Some(_),
})
_ => None
}
}
///|
fn RawOiTnhLocalTeeSameAddressMemoryCopyFact::proves_trap_relaxed_escape(
self : RawOiTnhLocalTeeSameAddressMemoryCopyFact,
) -> Bool {
(self.tee_local_matches_right_get || self.addr_matches_right_get) &&
self.same_memory &&
self.addr_admitted &&
self.size_admitted
}
///|
priv struct RawOiMemoryLocalTeeUseFact {
localtee_index : Int32
consumer_index : Int32
operand_distance : Int32
is_nondefault_memory_access : Bool
is_bulk_memory_copy : Bool
is_bulk_memory_fill : Bool
}
///|
fn RawOiMemoryLocalTeeUseFact::from_region_at(
region : RawOiInstructionRegionFact,
localtee_index : Int32,
) -> RawOiMemoryLocalTeeUseFact? {
if !region.has_root_at(localtee_index, RawOiRootKind::LocalTee) {
return None
}
match region.roots[localtee_index].instr {
@lib.LocalTee(_) => ()
_ => return None
}
for operand_distance = 1
operand_distance <= 3
operand_distance = operand_distance + 1 {
let consumer_index = localtee_index + operand_distance
if consumer_index >= region.roots.length() {
return None
}
if operand_distance <= 2 {
let access_fact = RawOiMemoryAccessIndexFact::from_instr(
region.roots[consumer_index].instr,
)
if access_fact.proves_nondefault_memory_access() {
return Some({
localtee_index,
consumer_index,
operand_distance,
is_nondefault_memory_access: true,
is_bulk_memory_copy: false,
is_bulk_memory_fill: false,
})
}
}
match region.roots[consumer_index].instr {
@lib.MemoryCopy(_, _) =>
return Some({
localtee_index,
consumer_index,
operand_distance,
is_nondefault_memory_access: false,
is_bulk_memory_copy: true,
is_bulk_memory_fill: false,
})
@lib.MemoryFill(_) =>
return Some({
localtee_index,
consumer_index,
operand_distance,
is_nondefault_memory_access: false,
is_bulk_memory_copy: false,
is_bulk_memory_fill: true,
})
_ => ()
}
}
None
}
///|
fn RawOiMemoryLocalTeeUseFact::proves_raw_gate_boundary(
self : RawOiMemoryLocalTeeUseFact,
) -> Bool {
ignore(self.localtee_index)
ignore(self.consumer_index)
self.operand_distance >= 1 &&
self.operand_distance <= 3 &&
(
self.is_nondefault_memory_access ||
self.is_bulk_memory_copy ||
self.is_bulk_memory_fill
)
}
///|
priv struct RawOiMemoryLocalTeeRegionFact {
has_blocking_use : Bool
}
///|
fn RawOiMemoryLocalTeeRegionFact::from_index(
index : RawOiFunctionRevisionFactIndex,
allow_tnh_localtee_same_address_memory_copy : Bool,
) -> RawOiMemoryLocalTeeRegionFact {
for region in index.regions {
if allow_tnh_localtee_same_address_memory_copy {
match RawOiTnhLocalTeeSameAddressMemoryCopyFact::from_region(region) {
Some(fact) if fact.proves_trap_relaxed_escape() => continue
_ => ()
}
}
for i = 0; i < region.roots.length(); i = i + 1 {
match RawOiMemoryLocalTeeUseFact::from_region_at(region, i) {
Some(fact) if fact.proves_raw_gate_boundary() =>
return { has_blocking_use: true, }
_ => ()
}
}
}
{ has_blocking_use: false, }
}
///|
// Raw OI-G narrow-store access fact for `optimizeStoredValue`. Binaryen drops
// an i32 `and` mask only for byte/halfword i32 stores; i64 stores and i64.store32
// are tracked as explicit boundaries.
priv struct RawOiNarrowStoreAccessFact {
store_bits : Int32
is_i64_store : Bool
}
///|
fn RawOiNarrowStoreAccessFact::from_instr(
instr : @lib.Instruction,
) -> RawOiNarrowStoreAccessFact? {
match instr {
@lib.I32Store8(_) => Some({ store_bits: 8, is_i64_store: false, })
@lib.I64Store8(_) => Some({ store_bits: 8, is_i64_store: true, })
@lib.I32Store16(_) => Some({ store_bits: 16, is_i64_store: false, })
@lib.I64Store16(_) => Some({ store_bits: 16, is_i64_store: true, })
@lib.I64Store32(_) => Some({ store_bits: 32, is_i64_store: true, })
_ => None
}
}
///|
// Raw OI-G narrow-store mask fact for the flat dispatcher. Binaryen v130's
// `optimizeStoredValue` only drops exact i32 low-bit masks before byte/halfword
// stores; i64 store masks and superset masks remain boundaries. The raw bridge
// records the store width, mask side, and constant-constant guard explicitly
// instead of keeping the decision as an effectful-sibling window helper.
priv struct RawOiNarrowStoreMaskFact {
store_bits : Int32
required_mask : Int32
value : @lib.Instruction
mask_on_left : Bool
mask_on_right : Bool
exact_i32_and : Bool
i64_store_boundary : Bool
const_const_boundary : Bool
}
///|
fn RawOiNarrowStoreMaskFact::proves_binaryen_i32_narrow_store_mask(
self : RawOiNarrowStoreMaskFact,
) -> Bool {
self.exact_i32_and &&
!self.i64_store_boundary &&
!self.const_const_boundary &&
(self.store_bits == 8 || self.store_bits == 16) &&
self.required_mask == (1 << self.store_bits) - 1 &&
(self.mask_on_left || self.mask_on_right)
}
///|
fn RawOiNarrowStoreMaskFact::from_parts(
lhs : @lib.Instruction,
rhs : @lib.Instruction,
and_instr : @lib.Instruction,
store_instr : @lib.Instruction,
) -> RawOiNarrowStoreMaskFact? {
let access = match RawOiNarrowStoreAccessFact::from_instr(store_instr) {
Some(fact) => fact
None => return None
}
let store_bits = access.store_bits
let exact_i32_and = match and_instr {
@lib.I32And => true
_ => false
}
if !exact_i32_and {
return None
}
let required_mask = (1 << store_bits) - 1
let i64_store_boundary = access.is_i64_store
match (lhs, rhs) {
(@lib.I32Const(@lib.I32(mask)), @lib.I32Const(_)) if mask == required_mask =>
Some({
store_bits,
required_mask,
value: rhs,
mask_on_left: true,
mask_on_right: false,
exact_i32_and,
i64_store_boundary,
const_const_boundary: true,
})
(@lib.I32Const(@lib.I32(mask)), value) if mask == required_mask =>
Some({
store_bits,
required_mask,
value,
mask_on_left: true,
mask_on_right: false,
exact_i32_and,
i64_store_boundary,
const_const_boundary: false,
})
(value, @lib.I32Const(@lib.I32(mask))) if mask == required_mask =>
Some({
store_bits,
required_mask,
value,
mask_on_left: false,
mask_on_right: true,
exact_i32_and,
i64_store_boundary,
const_const_boundary: false,
})
_ => None
}
}
///|
priv struct RawOiDropStoreMaskRewriteFact {
end : Int32
folded_const_add_address : Bool
address : @lib.Instruction
value : @lib.Instruction
store : @lib.Instruction
mask : RawOiNarrowStoreMaskFact
}
///|
fn RawOiDropStoreMaskRewriteFact::proves_binaryen_i32_narrow_store_mask(
self : RawOiDropStoreMaskRewriteFact,
) -> Bool {
ignore(self.folded_const_add_address)
self.mask.proves_binaryen_i32_narrow_store_mask()
}
///|
fn RawOiDropStoreMaskRewriteFact::append_rewrite(
self : RawOiDropStoreMaskRewriteFact,
out : Array[@lib.Instruction],
) -> Unit {
if self.proves_binaryen_i32_narrow_store_mask() {
out.push(self.address)
out.push(self.value)
out.push(self.store)
}
}
///|
fn RawOiDropStoreMaskRewriteFact::from_const_add_address(
region : RawOiInstructionRegionFact,
start : Int32,
) -> RawOiDropStoreMaskRewriteFact? {
let address = match RawOiI32ConstAddAddressFact::from_region(region, start) {
Some(fact) => fact
None => return None
}
if address.end + 3 >= region.roots.length() ||
!region.has_root_at(address.end + 3, RawOiRootKind::MemoryStore) {
return None
}
let mask = match
RawOiNarrowStoreMaskFact::from_parts(
region.roots[address.end].instr,
region.roots[address.end + 1].instr,
region.roots[address.end + 2].instr,
region.roots[address.end + 3].instr,
) {
Some(fact) if fact.proves_binaryen_i32_narrow_store_mask() => fact
_ => return None
}
Some({
end: address.end + 4,
folded_const_add_address: true,
address: address.value,
value: mask.value,
store: region.roots[address.end + 3].instr,
mask,
})
}
///|
fn RawOiDropStoreMaskRewriteFact::from_plain_address(
region : RawOiInstructionRegionFact,
start : Int32,
) -> RawOiDropStoreMaskRewriteFact? {
if start + 4 >= region.roots.length() ||
!region.has_root_at(start + 4, RawOiRootKind::MemoryStore) {
return None
}
let mask = match
RawOiNarrowStoreMaskFact::from_parts(
region.roots[start + 1].instr,
region.roots[start + 2].instr,
region.roots[start + 3].instr,
region.roots[start + 4].instr,
) {
Some(fact) if fact.proves_binaryen_i32_narrow_store_mask() => fact
_ => return None
}
Some({
end: start + 5,
folded_const_add_address: false,
address: region.roots[start].instr,
value: mask.value,
store: region.roots[start + 4].instr,
mask,
})
}
///|
fn RawOiDropStoreMaskRewriteFact::from_region(
region : RawOiInstructionRegionFact,
start : Int32,
) -> RawOiDropStoreMaskRewriteFact? {
match RawOiDropStoreMaskRewriteFact::from_const_add_address(region, start) {
Some(fact) => return Some(fact)
None => ()
}
RawOiDropStoreMaskRewriteFact::from_plain_address(region, start)
}
///|
// Raw OI-G repeated-byte fill value fact. Binaryen v130 lowers constant-size
// memory.fill by repeating the low byte of an i32 constant into the store value;
// keep the byte, size, and rewritten value together instead of ad hoc helpers.
priv struct RawOiRepeatedFillValueFact {
size : Int32
byte : UInt32
value : @lib.Instruction
}
///|
fn RawOiRepeatedFillValueFact::proves_binaryen_repeated_byte_value(
self : RawOiRepeatedFillValueFact,
) -> Bool {
ignore(self.value)
self.size >= 1 && self.size <= 16 && self.byte <= 0xffU
}
///|
fn RawOiRepeatedFillValueFact::from_value_and_size(
value : @lib.Instruction,
size : Int32,
) -> RawOiRepeatedFillValueFact? {
match size {
1 => Some({ size, byte: 0U, value, })
2 | 4 | 8 | 16 =>
match value {
@lib.I32Const(@lib.I32(raw)) => {
let byte = raw.reinterpret_as_uint() & 0xff
let repeated = match size {
2 =>
@lib.Instruction::i32_const(
@lib.I32((byte | (byte << 8)).reinterpret_as_int()),
)
4 =>
@lib.Instruction::i32_const(
@lib.I32(
(byte | (byte << 8) | (byte << 16) | (byte << 24)).reinterpret_as_int(),
),
)
8 => {
let byte64 = byte.to_uint64()
@lib.Instruction::i64_const(
@lib.I64(
(byte64 |
(byte64 << 8) |
(byte64 << 16) |
(byte64 << 24) |
(byte64 << 32) |
(byte64 << 40) |
(byte64 << 48) |
(byte64 << 56)).reinterpret_as_int64(),
),
)
}
16 => {
let raw_byte = byte.to_byte()
@lib.Instruction::v128_const(
raw_byte, raw_byte, raw_byte, raw_byte, raw_byte, raw_byte, raw_byte,
raw_byte, raw_byte, raw_byte, raw_byte, raw_byte, raw_byte, raw_byte,
raw_byte, raw_byte,
)
}
_ => return None
}
Some({ size, byte, value: repeated, })
}
_ => None
}
_ => None
}
}
///|
// Raw OI-G memory.copy lowering fact. Binaryen replaces tiny constant-size
// copies with a load/store pair using zero-offset memargs for the source and
// destination memories; this fact owns the size-to-opcode mapping.
priv struct RawOiBulkCopyLoadStoreFact {
size : Int32
src_mem : @lib.MemIdx
dst_mem : @lib.MemIdx
load : @lib.Instruction
store : @lib.Instruction
}
///|
fn RawOiBulkCopyLoadStoreFact::proves_binaryen_copy_load_store(
self : RawOiBulkCopyLoadStoreFact,
) -> Bool {
ignore(self.load)
ignore(self.store)
ignore(self.src_mem)
ignore(self.dst_mem)
self.size == 1 ||
self.size == 2 ||
self.size == 4 ||
self.size == 8 ||
self.size == 16
}
///|
fn RawOiBulkCopyLoadStoreFact::from_parts(
size : Int32,
src_mem : @lib.MemIdx,
dst_mem : @lib.MemIdx,
) -> RawOiBulkCopyLoadStoreFact? {
let src_memarg_fact = RawOiZeroOffsetMemargFact::from_mem_idx(src_mem)
let dst_memarg_fact = RawOiZeroOffsetMemargFact::from_mem_idx(dst_mem)
if !src_memarg_fact.proves_binaryen_zero_offset_memarg() ||
!dst_memarg_fact.proves_binaryen_zero_offset_memarg() {
return None
}
let src_memarg = src_memarg_fact.memarg
let dst_memarg = dst_memarg_fact.memarg
let (load, store) = match size {
1 =>
(
@lib.Instruction::i32_load8u(src_memarg),
@lib.Instruction::i32_store8(dst_memarg),
)
2 =>
(
@lib.Instruction::i32_load16u(src_memarg),
@lib.Instruction::i32_store16(dst_memarg),
)
4 =>
(
@lib.Instruction::i32_load(src_memarg),
@lib.Instruction::i32_store(dst_memarg),
)
8 =>
(
@lib.Instruction::i64_load(src_memarg),
@lib.Instruction::i64_store(dst_memarg),
)
16 =>
(
@lib.Instruction::v128_load(src_memarg),
@lib.Instruction::v128_store(dst_memarg),
)
_ => return None
}
Some({ size, src_mem, dst_mem, load, store, })
}
///|
// Raw OI-G memory.fill lowering store fact. The value proof is separate in
// RawOiRepeatedFillValueFact; this fact records Binaryen's size-to-store mapping.
priv struct RawOiBulkFillStoreFact {
size : Int32
mem : @lib.MemIdx
store : @lib.Instruction
}
///|
fn RawOiBulkFillStoreFact::proves_binaryen_fill_store(
self : RawOiBulkFillStoreFact,
) -> Bool {
ignore(self.store)
ignore(self.mem)
self.size == 1 ||
self.size == 2 ||
self.size == 4 ||
self.size == 8 ||
self.size == 16
}
///|
fn RawOiBulkFillStoreFact::from_parts(
size : Int32,
mem : @lib.MemIdx,
) -> RawOiBulkFillStoreFact? {
let memarg_fact = RawOiZeroOffsetMemargFact::from_mem_idx(mem)
if !memarg_fact.proves_binaryen_zero_offset_memarg() {
return None
}
let memarg = memarg_fact.memarg
let store = match size {
1 => @lib.Instruction::i32_store8(memarg)
2 => @lib.Instruction::i32_store16(memarg)
4 => @lib.Instruction::i32_store(memarg)
8 => @lib.Instruction::i64_store(memarg)
16 => @lib.Instruction::v128_store(memarg)
_ => return None
}
Some({ size, mem, store, })
}
///|
priv struct RawOiI32ConstSelectOperandFact {
end : Int32
value : @lib.Instruction
}
///|
fn RawOiI32ConstSelectOperandFact::from_region(
region : RawOiInstructionRegionFact,
start : Int32,
) -> RawOiI32ConstSelectOperandFact? {
if start + 3 >= region.roots.length() ||
!region.has_root_at(start, RawOiRootKind::I32Const) ||
!region.has_root_at(start + 1, RawOiRootKind::I32Const) ||
!region.has_root_at(start + 2, RawOiRootKind::I32Const) ||
!region.has_root_at(start + 3, RawOiRootKind::Select) {
return None
}
match
(
region.roots[start].instr,
region.roots[start + 1].instr,
region.roots[start + 2].instr,
region.roots[start + 3].instr,
) {
(
@lib.I32Const(@lib.I32(true_raw)),
@lib.I32Const(@lib.I32(false_raw)),
@lib.I32Const(@lib.I32(cond_raw)),
@lib.Select(_),
) =>
Some({
end: start + 4,
value: @lib.Instruction::i32_const(
@lib.I32(if cond_raw == 0 { false_raw } else { true_raw }),
),
})
_ => None
}
}
///|
priv struct RawOiMemorySizePageAddressFact {
end : Int32
mem : @lib.MemIdx
rewrite : Array[@lib.Instruction]
}
///|
fn RawOiMemorySizePageAddressFact::from_region(
region : RawOiInstructionRegionFact,
start : Int32,
) -> RawOiMemorySizePageAddressFact? {
if start + 2 >= region.roots.length() ||
!region.has_root_at(start, RawOiRootKind::MemorySize) {
return None
}
let first = region.roots[start].instr
let second = region.roots[start + 1].instr
let third = region.roots[start + 2].instr
match (first, second, third) {
(@lib.MemorySize(mem), @lib.I32Const(@lib.I32(15)), @lib.I32Shl) =>
Some({ end: start + 3, mem, rewrite: [first, second, third], })
(@lib.MemorySize(mem), @lib.I32Const(@lib.I32(32768)), @lib.I32Mul) =>
Some({
end: start + 3,
mem,
rewrite: [
first,
@lib.Instruction::i32_const(@lib.I32(15)),
@lib.Instruction::i32_shl(),
],
})
(@lib.MemorySize(mem), @lib.I64Const(@lib.I64(15L)), @lib.I64Shl) =>
Some({ end: start + 3, mem, rewrite: [first, second, third], })
(@lib.MemorySize(mem), @lib.I64Const(@lib.I64(32768L)), @lib.I64Mul) =>
Some({
end: start + 3,
mem,
rewrite: [
first,
@lib.Instruction::i64_const(@lib.I64(15L)),
@lib.Instruction::i64_shl(),
],
})
_ => None
}
}
///|
priv struct RawOiI32ConstAddAddressFact {
end : Int32
value : @lib.Instruction
}
///|
fn RawOiI32ConstAddAddressFact::from_parts(
first : @lib.Instruction,
second : @lib.Instruction,
add : @lib.Instruction,
end : Int32,
) -> RawOiI32ConstAddAddressFact? {
match (first, second, add) {
(
@lib.I32Const(@lib.I32(lhs_raw)),
@lib.I32Const(@lib.I32(rhs_raw)),
@lib.I32Add,
) => {
let folded = @lib.I32(
(lhs_raw.reinterpret_as_uint() + rhs_raw.reinterpret_as_uint()).reinterpret_as_int(),
)
Some({ end, value: @lib.Instruction::i32_const(folded), })
}
_ => None
}
}
///|
fn RawOiI32ConstAddAddressFact::from_region(
region : RawOiInstructionRegionFact,
start : Int32,
) -> RawOiI32ConstAddAddressFact? {
if start + 2 >= region.roots.length() ||
!region.has_root_at(start, RawOiRootKind::I32Const) ||
!region.has_root_at(start + 1, RawOiRootKind::I32Const) {
return None
}
RawOiI32ConstAddAddressFact::from_parts(
region.roots[start].instr,
region.roots[start + 1].instr,
region.roots[start + 2].instr,
start + 3,
)
}
///|
// Raw OI-G lowering fact for the flat dispatcher. Binaryen v130 lowers
// constant-size `memory.copy` to load/store and constant-size `memory.fill` to
// store when the value-size pair admits an exact store. The flat raw bridge sees
// already-lowered operand sequences, so this fact records the operand proof
// family (resolved const-selects, memory.size page addresses, plain static
// sizes, or folded const-add addresses) plus the complete rewrite payload.
priv struct RawOiBulkLoweringRewriteFact {
end : Int32
size : Int32
is_memory_copy : Bool
is_memory_fill : Bool
operands_are_const_selects : Bool
operands_are_memory_size_page_addresses : Bool
operands_are_plain_static_size : Bool
operands_are_i32_const_add_addresses : Bool
rewrite : Array[@lib.Instruction]
}
///|
fn RawOiBulkLoweringRewriteFact::proves_binaryen_bulk_lowering(
self : RawOiBulkLoweringRewriteFact,
) -> Bool {
(self.is_memory_copy || self.is_memory_fill) &&
self.size > 0 &&
(
self.operands_are_const_selects ||
self.operands_are_memory_size_page_addresses ||
self.operands_are_plain_static_size ||
self.operands_are_i32_const_add_addresses
)
}
///|
fn RawOiBulkLoweringRewriteFact::append_rewrite(
self : RawOiBulkLoweringRewriteFact,
out : Array[@lib.Instruction],
) -> Unit {
if self.proves_binaryen_bulk_lowering() {
for instr in self.rewrite {
out.push(instr)
}
}
}
///|
// Fact-owned raw OI-G emission for already-proven lowered-bulk operand chunks.
// Eligibility and proof logic stays in `RawOiBulkLoweringRewriteFact`; this
// method only copies the chunk into the fact's complete rewrite payload.
fn RawOiBulkLoweringRewriteFact::append_operand_chunk(
out : Array[@lib.Instruction],
instrs : Array[@lib.Instruction],
) -> Unit {
for instr in instrs {
out.push(instr)
}
}
///|
fn RawOiBulkLoweringRewriteFact::copy_from_parts(
end : Int32,
dest : Array[@lib.Instruction],
source : Array[@lib.Instruction],
size : Int32,
src_mem : @lib.MemIdx,
dst_mem : @lib.MemIdx,
operands_are_const_selects : Bool,
operands_are_memory_size_page_addresses : Bool,
operands_are_plain_static_size : Bool,
operands_are_i32_const_add_addresses : Bool,
) -> RawOiBulkLoweringRewriteFact? {
match RawOiBulkCopyLoadStoreFact::from_parts(size, src_mem, dst_mem) {
Some(copy_access) if copy_access.proves_binaryen_copy_load_store() => {
let rewrite : Array[@lib.Instruction] = []
RawOiBulkLoweringRewriteFact::append_operand_chunk(rewrite, dest)
RawOiBulkLoweringRewriteFact::append_operand_chunk(rewrite, source)
rewrite.push(copy_access.load)
rewrite.push(copy_access.store)
Some({
end,
size,
is_memory_copy: true,
is_memory_fill: false,
operands_are_const_selects,
operands_are_memory_size_page_addresses,
operands_are_plain_static_size,
operands_are_i32_const_add_addresses,
rewrite,
})
}
_ => None
}
}
///|
fn RawOiBulkLoweringRewriteFact::fill_from_parts(
end : Int32,
dest : Array[@lib.Instruction],
value : @lib.Instruction,
size : Int32,
mem : @lib.MemIdx,
operands_are_const_selects : Bool,
operands_are_plain_static_size : Bool,
) -> RawOiBulkLoweringRewriteFact? {
match
(
RawOiRepeatedFillValueFact::from_value_and_size(value, size),
RawOiBulkFillStoreFact::from_parts(size, mem),
) {
(Some(fill_value), Some(fill_store)) if fill_value.proves_binaryen_repeated_byte_value() &&
fill_store.proves_binaryen_fill_store() => {
let rewrite : Array[@lib.Instruction] = []
RawOiBulkLoweringRewriteFact::append_operand_chunk(rewrite, dest)
rewrite.push(fill_value.value)
rewrite.push(fill_store.store)
Some({
end,
size,
is_memory_copy: false,
is_memory_fill: true,
operands_are_const_selects,
operands_are_memory_size_page_addresses: false,
operands_are_plain_static_size,
operands_are_i32_const_add_addresses: false,
rewrite,
})
}
_ => None
}
}
///|
fn RawOiBulkLoweringRewriteFact::from_const_select_operands(
region : RawOiInstructionRegionFact,
start : Int32,
) -> RawOiBulkLoweringRewriteFact? {
let dest = match RawOiI32ConstSelectOperandFact::from_region(region, start) {
Some(fact) => fact
None => return None
}
let source_or_value = match
RawOiI32ConstSelectOperandFact::from_region(region, dest.end) {
Some(fact) => fact
None => return None
}
let size_operand = match
RawOiI32ConstSelectOperandFact::from_region(region, source_or_value.end) {
Some(fact) => fact
None => return None
}
let size = match RawOiBulkConstSizeFact::from_instr(size_operand.value) {
Some(fact) => fact.size
None => return None
}
if size_operand.end >= region.roots.length() ||
!region.has_root_at(size_operand.end, RawOiRootKind::BulkMemory) {
return None
}
match region.roots[size_operand.end].instr {
@lib.MemoryCopy(dst_mem, src_mem) =>
RawOiBulkLoweringRewriteFact::copy_from_parts(
size_operand.end + 1,
[dest.value],
[source_or_value.value],
size,
src_mem,
dst_mem,
true,
false,
false,
false,
)
@lib.MemoryFill(mem) =>
RawOiBulkLoweringRewriteFact::fill_from_parts(
size_operand.end + 1,
[dest.value],
source_or_value.value,
size,
mem,
true,
false,
)
_ => None
}
}
///|
fn RawOiBulkLoweringRewriteFact::from_memory_size_page_addresses(
region : RawOiInstructionRegionFact,
start : Int32,
) -> RawOiBulkLoweringRewriteFact? {
let dest = match RawOiMemorySizePageAddressFact::from_region(region, start) {
Some(fact) => fact
None => return None
}
let source = match
RawOiMemorySizePageAddressFact::from_region(region, dest.end) {
Some(fact) => fact
None => return None
}
if source.end + 1 >= region.roots.length() ||
!region.has_root_at(source.end + 1, RawOiRootKind::BulkMemory) {
return None
}
let size = match
RawOiBulkConstSizeFact::from_instr(region.roots[source.end].instr) {
Some(fact) => fact.size
None => return None
}
match region.roots[source.end + 1].instr {
@lib.MemoryCopy(dst_mem, src_mem) if dest.mem == dst_mem &&
source.mem == src_mem =>
RawOiBulkLoweringRewriteFact::copy_from_parts(
source.end + 2,
dest.rewrite,
source.rewrite,
size,
src_mem,
dst_mem,
false,
true,
false,
false,
)
_ => None
}
}
///|
fn RawOiBulkLoweringRewriteFact::from_plain_static_size(
region : RawOiInstructionRegionFact,
start : Int32,
) -> RawOiBulkLoweringRewriteFact? {
if start + 3 >= region.roots.length() ||
!region.has_root_at(start + 3, RawOiRootKind::BulkMemory) {
return None
}
let size = match
RawOiBulkConstSizeFact::from_instr(region.roots[start + 2].instr) {
Some(fact) => fact.size
None => return None
}
match region.roots[start + 3].instr {
@lib.MemoryCopy(dst_mem, src_mem) =>
RawOiBulkLoweringRewriteFact::copy_from_parts(
start + 4,
[region.roots[start].instr],
[region.roots[start + 1].instr],
size,
src_mem,
dst_mem,
false,
false,
true,
false,
)
@lib.MemoryFill(mem) =>
RawOiBulkLoweringRewriteFact::fill_from_parts(
start + 4,
[region.roots[start].instr],
region.roots[start + 1].instr,
size,
mem,
false,
true,
)
_ => None
}
}
///|
fn RawOiBulkLoweringRewriteFact::from_i32_const_add_addresses(
region : RawOiInstructionRegionFact,
start : Int32,
) -> RawOiBulkLoweringRewriteFact? {
let dest = match RawOiI32ConstAddAddressFact::from_region(region, start) {
Some(fact) => fact
None => return None
}
let source = match
RawOiI32ConstAddAddressFact::from_region(region, dest.end) {
Some(fact) => fact
None => return None
}
if source.end + 1 >= region.roots.length() ||
!region.has_root_at(source.end + 1, RawOiRootKind::BulkMemory) {
return None
}
let size = match
RawOiBulkConstSizeFact::from_instr(region.roots[source.end].instr) {
Some(fact) => fact.size
None => return None
}
match region.roots[source.end + 1].instr {
@lib.MemoryCopy(dst_mem, src_mem) =>
RawOiBulkLoweringRewriteFact::copy_from_parts(
source.end + 2,
[dest.value],
[source.value],
size,
src_mem,
dst_mem,
false,
false,
false,
true,
)
_ => None
}
}
///|
fn RawOiBulkLoweringRewriteFact::from_region(
region : RawOiInstructionRegionFact,
start : Int32,
) -> RawOiBulkLoweringRewriteFact? {
match
RawOiBulkLoweringRewriteFact::from_const_select_operands(region, start) {
Some(fact) => return Some(fact)
None => ()
}
match
RawOiBulkLoweringRewriteFact::from_memory_size_page_addresses(region, start) {
Some(fact) => return Some(fact)
None => ()
}
match RawOiBulkLoweringRewriteFact::from_plain_static_size(region, start) {
Some(fact) => return Some(fact)
None => ()
}
RawOiBulkLoweringRewriteFact::from_i32_const_add_addresses(region, start)
}
///|
// Raw OI-F select rewrite fact for the flat effectful-sibling dispatcher.
// Binaryen v130 `optimizeSelect` first reasons from facts about constant
// conditions, removable `eqz` condition shells, and identical side-effect-free
// arms. The raw bridge only represents already-covered stack spellings, so this
// fact records the exact proof family and the complete rewrite payload instead
// of leaving three separate select instruction windows in the dispatcher.
priv struct RawOiSelectRewriteFact {
end : Int32
constant_condition : Bool
condition_is_eqz : Bool
identical_arms : Bool
arms_are_pure : Bool
rewrite : Array[@lib.Instruction]
}
///|
fn RawOiSelectRewriteFact::proves_binaryen_select_rewrite(
self : RawOiSelectRewriteFact,
) -> Bool {
self.arms_are_pure &&
(self.constant_condition || self.condition_is_eqz || self.identical_arms) &&
self.rewrite.length() > 0
}
///|
fn RawOiSelectRewriteFact::append_rewrite(
self : RawOiSelectRewriteFact,
out : Array[@lib.Instruction],
) -> Unit {
if self.proves_binaryen_select_rewrite() {
for instr in self.rewrite {
out.push(instr)
}
}
}
///|
fn RawOiSelectRewriteFact::from_const_eqz_condition(
region : RawOiInstructionRegionFact,
start : Int32,
) -> RawOiSelectRewriteFact? {
if start + 4 >= region.roots.length() ||
!region.has_root_at(start + 4, RawOiRootKind::Select) {
return None
}
match
(
region.roots[start].instr,
region.roots[start + 1].instr,
region.roots[start + 2].instr,
region.roots[start + 3].instr,
region.roots[start + 4].instr,
) {
(
true_value,
false_value,
@lib.I32Const(@lib.I32(cond)),
@lib.I32Eqz,
@lib.Select(_),
) if run_hot_pipeline_optimize_instructions_instr_is_pure_stack_call_operand(
true_value,
) &&
run_hot_pipeline_optimize_instructions_instr_is_pure_stack_call_operand(
false_value,
) =>
Some({
end: start + 5,
constant_condition: true,
condition_is_eqz: true,
identical_arms: false,
arms_are_pure: true,
rewrite: [if cond == 0 { true_value } else { false_value }],
})
_ => None
}
}
///|
fn RawOiSelectRewriteFact::from_const_condition(
region : RawOiInstructionRegionFact,
start : Int32,
) -> RawOiSelectRewriteFact? {
if start + 3 >= region.roots.length() ||
!region.has_root_at(start + 3, RawOiRootKind::Select) {
return None
}
match
(
region.roots[start].instr,
region.roots[start + 1].instr,
region.roots[start + 2].instr,
region.roots[start + 3].instr,
) {
(true_value, false_value, @lib.I32Const(@lib.I32(cond)), @lib.Select(_)) if run_hot_pipeline_optimize_instructions_instr_is_pure_stack_call_operand(
true_value,
) &&
run_hot_pipeline_optimize_instructions_instr_is_pure_stack_call_operand(
false_value,
) =>
Some({
end: start + 4,
constant_condition: true,
condition_is_eqz: false,
identical_arms: false,
arms_are_pure: true,
rewrite: [if cond == 0 { false_value } else { true_value }],
})
_ => None
}
}
///|
fn RawOiSelectRewriteFact::identical_arm_is_condition_stable(
instr : @lib.Instruction,
) -> Bool {
// A mutable global read is side-effect-free but not stable across an opaque
// condition call. HOT can preserve it with a scratch local; raw OI cannot.
match instr {
@lib.GlobalGet(_) => false
_ =>
run_hot_pipeline_optimize_instructions_instr_is_pure_stack_call_operand(
instr,
)
}
}
///|
fn RawOiSelectRewriteFact::from_identical_pure_arms(
region : RawOiInstructionRegionFact,
start : Int32,
) -> RawOiSelectRewriteFact? {
if start + 3 >= region.roots.length() ||
!region.has_root_at(start + 3, RawOiRootKind::Select) {
return None
}
match
(
region.roots[start].instr,
region.roots[start + 1].instr,
region.roots[start + 2].instr,
region.roots[start + 3].instr,
) {
(true_value, false_value, condition, @lib.Select(_)) if true_value ==
false_value &&
RawOiSelectRewriteFact::identical_arm_is_condition_stable(true_value) =>
Some({
end: start + 4,
constant_condition: false,
condition_is_eqz: false,
identical_arms: true,
arms_are_pure: true,
rewrite: [condition, @lib.Instruction::drop(), true_value],
})
_ => None
}
}
///|
// Canonical stack-form v131 directional-select proof. Branch-free expression
// blocks are flattened by normal wasm producers into this eight-instruction
// sequence. The first and false values read the same untouched local; the
// false-arm local write and condition read are preserved in source order, and
// their values become unobservable once the select result is known.
priv struct RawOiV131FlatIdenticalSelectFact {
rewrite : Array[@lib.Instruction]
}
///|
fn RawOiV131FlatIdenticalSelectFact::from_body(
body : Array[@lib.Instruction],
) -> RawOiV131FlatIdenticalSelectFact? {
// This is an exact fixed-width canonical stack pattern, not a body scan. It
// runs before the shared revision index so the common generated shape avoids
// paying HOT/index setup merely to prove a local-only result.
if body.length() != 8 {
return None
}
match
(body[0], body[1], body[2], body[3], body[4], body[5], body[6], body[7]) {
(
@lib.LocalGet(true_local),
value,
@lib.LocalSet(written_local),
@lib.LocalGet(false_local),
@lib.LocalGet(read_local),
@lib.Drop,
@lib.LocalGet(_),
@lib.Select(_),
) if true_local == false_local &&
written_local == read_local &&
true_local != written_local =>
Some({
rewrite: [
value,
@lib.Instruction::local_set(written_local),
@lib.Instruction::local_get(read_local),
@lib.Instruction::drop(),
@lib.Instruction::local_get(true_local),
],
})
_ => None
}
}
///|
// Precompute's late-O4z canonical spelling for the same v131 equality fact.
// The local write remains first, the nop and pure local condition are removable,
// and one equal local value becomes the result.
priv struct RawOiV131PrecomputedIdenticalSelectFact {
rewrite : Array[@lib.Instruction]
}
///|
fn RawOiV131PrecomputedIdenticalSelectFact::from_body(
body : Array[@lib.Instruction],
) -> RawOiV131PrecomputedIdenticalSelectFact? {
if body.length() != 7 {
return None
}
match (body[0], body[1], body[2], body[3], body[4], body[5], body[6]) {
(
value,
@lib.LocalSet(written_local),
@lib.Nop,
@lib.LocalGet(true_local),
@lib.LocalGet(false_local),
@lib.LocalGet(_),
@lib.Select(_),
) if true_local == false_local =>
Some({
rewrite: [
value,
@lib.Instruction::local_set(written_local),
@lib.Instruction::local_get(true_local),
],
})
_ => None
}
}
///|
fn RawOiSelectRewriteFact::from_region(
region : RawOiInstructionRegionFact,
start : Int32,
) -> RawOiSelectRewriteFact? {
match RawOiSelectRewriteFact::from_const_eqz_condition(region, start) {
Some(fact) => return Some(fact)
None => ()
}
match RawOiSelectRewriteFact::from_const_condition(region, start) {
Some(fact) => return Some(fact)
None => ()
}
RawOiSelectRewriteFact::from_identical_pure_arms(region, start)
}
///|
// Raw OI-F dropped-branch-value fact for the flat effectful-sibling dispatcher.
// Binaryen v130 `OptimizeInstructions::visitIf` can fold duplicated branch
// parents through an `if` when the branch children have matching types and the
// outer parent is safe to reuse. The raw bridge only covers the lowered shapes
// that were already accepted here: a pure scalar value immediately dropped, or
// a constant integer div/rem expression immediately dropped so trap behavior is
// preserved by the moved typed `if` result and following `drop`.
priv struct RawOiDroppedConstBranchValueFact {
value : Array[@lib.Instruction]
value_type : @lib.ValType
pure_scalar_drop : Bool
const_i32_divrem_drop : Bool
}
///|
fn RawOiDroppedConstBranchValueFact::proves_movable_dropped_branch_value(
self : RawOiDroppedConstBranchValueFact,
) -> Bool {
self.value.length() > 0 &&
(self.pure_scalar_drop || self.const_i32_divrem_drop)
}
///|
fn RawOiDroppedConstBranchValueFact::from_body(
body : Array[@lib.Instruction],
) -> RawOiDroppedConstBranchValueFact? {
if body.length() == 2 && body[1] == @lib.Instruction::drop() {
let value = body[0]
if !run_hot_pipeline_optimize_instructions_instr_is_pure_stack_call_operand(
value,
) {
return None
}
match run_hot_pipeline_raw_simplify_locals_notee_const_type(value) {
Some(vt) =>
return Some({
value: [value],
value_type: vt,
pure_scalar_drop: true,
const_i32_divrem_drop: false,
})
None => ()
}
}
if body.length() == 4 && body[3] == @lib.Instruction::drop() {
match (body[0], body[1], body[2]) {
(@lib.I32Const(_), @lib.I32Const(_), @lib.I32DivU)
| (@lib.I32Const(_), @lib.I32Const(_), @lib.I32DivS)
| (@lib.I32Const(_), @lib.I32Const(_), @lib.I32RemU)
| (@lib.I32Const(_), @lib.I32Const(_), @lib.I32RemS) =>
return Some({
value: [body[0], body[1], body[2]],
value_type: @lib.ValType::i32(),
pure_scalar_drop: false,
const_i32_divrem_drop: true,
})
_ => ()
}
}
None
}
///|
priv struct RawOiDroppedConstBranchIfFact {
end : Int32
condition : @lib.Instruction
then_value : Array[@lib.Instruction]
else_value : Array[@lib.Instruction]
value_type : @lib.ValType
then_pure_scalar_drop : Bool
else_pure_scalar_drop : Bool
then_const_i32_divrem_drop : Bool
else_const_i32_divrem_drop : Bool
}
///|
fn RawOiDroppedConstBranchIfFact::proves_binaryen_dropped_branch_rewrite(
self : RawOiDroppedConstBranchIfFact,
) -> Bool {
self.then_value.length() > 0 &&
self.else_value.length() > 0 &&
(self.then_pure_scalar_drop || self.then_const_i32_divrem_drop) &&
(self.else_pure_scalar_drop || self.else_const_i32_divrem_drop)
}
///|
fn RawOiDroppedConstBranchIfFact::append_rewrite(
self : RawOiDroppedConstBranchIfFact,
out : Array[@lib.Instruction],
) -> Unit {
if self.proves_binaryen_dropped_branch_rewrite() {
out.push(self.condition)
out.push(
@lib.Instruction::if_(
@lib.BlockType::val_type(self.value_type),
self.then_value,
Some(self.else_value),
),
)
out.push(@lib.Instruction::drop())
}
}
///|
fn RawOiDroppedConstBranchIfFact::from_region(
region : RawOiInstructionRegionFact,
start : Int32,
) -> RawOiDroppedConstBranchIfFact? {
if start + 1 >= region.roots.length() ||
!region.has_root_at(start + 1, RawOiRootKind::If) {
return None
}
match (region.roots[start].instr, region.roots[start + 1].instr) {
(condition, @lib.If(block_type, then_body, Some(else_body))) if block_type ==
@lib.BlockType::void_() =>
match
(
RawOiDroppedConstBranchValueFact::from_body(then_body),
RawOiDroppedConstBranchValueFact::from_body(else_body),
) {
(Some(then_fact), Some(else_fact)) if then_fact.value_type ==
else_fact.value_type &&
then_fact.proves_movable_dropped_branch_value() &&
else_fact.proves_movable_dropped_branch_value() =>
Some({
end: start + 2,
condition,
then_value: then_fact.value,
else_value: else_fact.value,
value_type: then_fact.value_type,
then_pure_scalar_drop: then_fact.pure_scalar_drop,
else_pure_scalar_drop: else_fact.pure_scalar_drop,
then_const_i32_divrem_drop: then_fact.const_i32_divrem_drop,
else_const_i32_divrem_drop: else_fact.const_i32_divrem_drop,
})
_ => None
}
_ => None
}
}
///|
// Raw OI boolean-context fact for the memory.size nonzero condition bridge.
// Binaryen v130 `optimizeBoolean` folds `x != 0` to `x` when the comparison is
// consumed as a boolean. The flat raw dispatcher keeps this boundary narrow to
// the already-covered `memory.size; i32.const 0; i32.ne; if` stack spelling so
// OI-G memory/bulk cleanup can recurse through the `if` body without a broader
// ad hoc boolean scanner.
priv struct RawOiMemorySizeNonzeroIfFact {
end : Int32
memory_size : @lib.Instruction
zero_condition : Bool
compare_is_ne : Bool
if_instr : @lib.Instruction
}
///|
fn RawOiMemorySizeNonzeroIfFact::proves_binaryen_boolean_context_rewrite(
self : RawOiMemorySizeNonzeroIfFact,
) -> Bool {
self.zero_condition && self.compare_is_ne
}
///|
fn RawOiMemorySizeNonzeroIfFact::append_rewrite(
self : RawOiMemorySizeNonzeroIfFact,
out : Array[@lib.Instruction],
module_ctx : @ir.HotModuleContext,
) -> Unit {
if self.proves_binaryen_boolean_context_rewrite() {
let (next_if_instrs, if_changed) = run_hot_pipeline_raw_optimize_instructions_effectful_sibling_instr(
self.if_instr,
module_ctx,
)
out.push(self.memory_size)
for next_if in next_if_instrs {
out.push(next_if)
}
let _ = if_changed
}
}
///|
fn RawOiMemorySizeNonzeroIfFact::from_region(
region : RawOiInstructionRegionFact,
start : Int32,
) -> RawOiMemorySizeNonzeroIfFact? {
if start + 3 >= region.roots.length() ||
!region.has_root_at(start, RawOiRootKind::MemorySize) {
return None
}
match
(
region.roots[start].instr,
region.roots[start + 1].instr,
region.roots[start + 2].instr,
region.roots[start + 3].instr,
) {
(@lib.MemorySize(_), @lib.I32Const(@lib.I32(raw)), @lib.I32Ne, if_instr) if raw ==
0 =>
Some({
end: start + 4,
memory_size: region.roots[start].instr,
zero_condition: true,
compare_is_ne: true,
if_instr,
})
_ => None
}
}
///|
// Raw OI-D scalar numeric rewrite fact for the fallback stack dispatcher.
// Binaryen v130 routes these through visitor-level `visitBinary` / `visitUnary`
// facts such as neutral add/sub constants, ordered constant eq/ne folding,
// nonnegative signed-to-unsigned spellings, power-of-two multiply shifts, safe
// float subtraction negation, constant add/sub folding, and `i32.wrap_i64`
// constant wrapping. Keep the represented flat stack windows as one fact with a
// complete rewrite payload instead of separate dispatcher-owned peepholes.
priv struct RawOiScalarNumericRewriteFact {
end : Int32
neutral_integer : Bool
ordered_const_eq_ne : Bool
nonnegative_signed_spelling : Bool
power_of_two_mul : Bool
float_sub_to_add : Bool
integer_const_fold : Bool
wrap_i64_const : Bool
f64_div2_sub_to_mul_add : Bool
i64_extend_local_commute : Bool
rewrite : Array[@lib.Instruction]
}
///|
fn RawOiScalarNumericRewriteFact::proves_binaryen_numeric_rewrite(
self : RawOiScalarNumericRewriteFact,
) -> Bool {
self.rewrite.length() > 0 &&
(
self.neutral_integer ||
self.ordered_const_eq_ne ||
self.nonnegative_signed_spelling ||
self.power_of_two_mul ||
self.float_sub_to_add ||
self.integer_const_fold ||
self.wrap_i64_const ||
self.f64_div2_sub_to_mul_add ||
self.i64_extend_local_commute
)
}
///|
fn RawOiScalarNumericRewriteFact::append_rewrite(
self : RawOiScalarNumericRewriteFact,
out : Array[@lib.Instruction],
) -> Unit {
if self.proves_binaryen_numeric_rewrite() {
for instr in self.rewrite {
out.push(instr)
}
}
}
///|
fn RawOiScalarNumericRewriteFact::from_f64_div2_sub(
region : RawOiInstructionRegionFact,
start : Int32,
) -> RawOiScalarNumericRewriteFact? {
if start + 4 >= region.roots.length() ||
!region.has_root_at(start + 4, RawOiRootKind::ScalarNumericTerminal) {
return None
}
match
(
region.roots[start].instr,
region.roots[start + 1].instr,
region.roots[start + 2].instr,
region.roots[start + 3].instr,
region.roots[start + 4].instr,
) {
(
@lib.F64Const(@lib.F64(div_lhs_raw)),
@lib.F64Const(@lib.F64(div_rhs_raw)),
@lib.F64Div,
@lib.F64Const(@lib.F64(sub_rhs_raw)),
@lib.F64Sub,
) if div_rhs_raw == 2.0 => {
let div_lhs = match RawOiF64SubNegatedConstFact::from_raw(div_lhs_raw) {
Some(fact) if fact.proves_binaryen_negatable_sub_const() => fact
_ => return None
}
let sub_rhs = match RawOiF64SubNegatedConstFact::from_raw(sub_rhs_raw) {
Some(fact) if fact.proves_binaryen_negatable_sub_const() => fact
_ => return None
}
Some({
end: start + 5,
neutral_integer: false,
ordered_const_eq_ne: false,
nonnegative_signed_spelling: false,
power_of_two_mul: false,
float_sub_to_add: false,
integer_const_fold: false,
wrap_i64_const: false,
f64_div2_sub_to_mul_add: true,
i64_extend_local_commute: false,
rewrite: [
@lib.Instruction::f64_const(@lib.F64(div_lhs.original)),
@lib.Instruction::f64_const(@lib.F64(0.5)),
@lib.Instruction::f64_mul(),
@lib.Instruction::f64_const(@lib.F64(sub_rhs.negated)),
@lib.Instruction::f64_add(),
],
})
}
_ => None
}
}
///|
fn RawOiScalarNumericRewriteFact::from_i64_extend_local_commute(
region : RawOiInstructionRegionFact,
start : Int32,
) -> RawOiScalarNumericRewriteFact? {
if start + 3 >= region.roots.length() ||
!region.has_root_at(start + 3, RawOiRootKind::ScalarNumericTerminal) {
return None
}
match
RawOiI64ExtendLocalCommuteFact::from_parts(
region.roots[start].instr,
region.roots[start + 1].instr,
region.roots[start + 2].instr,
region.roots[start + 3].instr,
) {
Some(commute) =>
Some({
end: start + 4,
neutral_integer: false,
ordered_const_eq_ne: false,
nonnegative_signed_spelling: false,
power_of_two_mul: false,
float_sub_to_add: false,
integer_const_fold: false,
wrap_i64_const: false,
f64_div2_sub_to_mul_add: false,
i64_extend_local_commute: true,
rewrite: commute.rewrite,
})
None => None
}
}
///|
// A zero before an instruction is an add operand only when that instruction
// cannot consume it. Adjacent raw stack roots are not expression operands.
fn raw_oi_is_nullary_scalar_value(
instr : @lib.Instruction,
module_ctx : @ir.HotModuleContext?,
) -> Bool {
match instr {
@lib.LocalGet(_)
| @lib.GlobalGet(_)
| @lib.I32Const(_)
| @lib.I64Const(_)
| @lib.MemorySize(_) => true
@lib.Call(index) =>
match module_ctx {
Some(ctx) =>
match @ir.hot_module_resolve_func_type(ctx, index) {
Some(@lib.FuncType([], [_])) => true
_ => false
}
None => false
}
@lib.Block(bt, _) | @lib.Loop(bt, _) | @lib.TryTable(bt, _, _) =>
match bt {
@lib.ValTypeBlockType(_) => true
@lib.TypeIdxBlockType(index) =>
match module_ctx {
Some(ctx) =>
match @ir.hot_module_resolve_type_func_type(ctx, index) {
Some(@lib.FuncType([], [_])) => true
_ => false
}
None => false
}
_ => false
}
_ => false
}
}
///|
fn RawOiScalarNumericRewriteFact::from_three_roots(
region : RawOiInstructionRegionFact,
start : Int32,
module_ctx~ : @ir.HotModuleContext? = None,
) -> RawOiScalarNumericRewriteFact? {
if start + 2 >= region.roots.length() ||
!region.has_root_at(start + 2, RawOiRootKind::ScalarNumericTerminal) {
return None
}
let instrs = region.roots
let neutral_value = match
(instrs[start].instr, instrs[start + 1].instr, instrs[start + 2].instr) {
(value, @lib.I32Const(@lib.I32(0)), @lib.I32Add)
| (value, @lib.I32Const(@lib.I32(0)), @lib.I32Sub)
| (value, @lib.I64Const(@lib.I64(0L)), @lib.I64Add)
| (value, @lib.I64Const(@lib.I64(0L)), @lib.I64Sub) => Some(value)
(@lib.I32Const(@lib.I32(0)), value, @lib.I32Add)
| (@lib.I64Const(@lib.I64(0L)), value, @lib.I64Add) if raw_oi_is_nullary_scalar_value(
value, module_ctx,
) => Some(value)
_ => None
}
match neutral_value {
Some(value) =>
return Some({
end: start + 3,
neutral_integer: true,
ordered_const_eq_ne: false,
nonnegative_signed_spelling: false,
power_of_two_mul: false,
float_sub_to_add: false,
integer_const_fold: false,
wrap_i64_const: false,
f64_div2_sub_to_mul_add: false,
i64_extend_local_commute: false,
rewrite: [value],
})
None => ()
}
match
(instrs[start].instr, instrs[start + 1].instr, instrs[start + 2].instr) {
(@lib.I32Const(@lib.I32(lhs_raw)), @lib.I32Const(@lib.I32(rhs_raw)), instr) => {
match RawOiOrderedConstEqNeFact::from_i32(lhs_raw, rhs_raw, instr) {
Some(eq_ne) =>
return Some({
end: start + 3,
neutral_integer: false,
ordered_const_eq_ne: true,
nonnegative_signed_spelling: false,
power_of_two_mul: false,
float_sub_to_add: false,
integer_const_fold: false,
wrap_i64_const: false,
f64_div2_sub_to_mul_add: false,
i64_extend_local_commute: false,
rewrite: [@lib.Instruction::i32_const(eq_ne.result)],
})
None => ()
}
match
RawOiNonnegativeSignedSpellingFact::from_i32(lhs_raw, rhs_raw, instr) {
Some(unsigned_spelling) if unsigned_spelling.proves_binaryen_unsigned_spelling() =>
return Some({
end: start + 3,
neutral_integer: false,
ordered_const_eq_ne: false,
nonnegative_signed_spelling: true,
power_of_two_mul: false,
float_sub_to_add: false,
integer_const_fold: false,
wrap_i64_const: false,
f64_div2_sub_to_mul_add: false,
i64_extend_local_commute: false,
rewrite: [
@lib.Instruction::i32_const(@lib.I32(lhs_raw)),
@lib.Instruction::i32_const(@lib.I32(rhs_raw)),
unsigned_spelling.unsigned_instr,
],
})
_ => ()
}
}
(@lib.I64Const(@lib.I64(lhs_raw)), @lib.I64Const(@lib.I64(rhs_raw)), instr) => {
match RawOiOrderedConstEqNeFact::from_i64(lhs_raw, rhs_raw, instr) {
Some(eq_ne) =>
return Some({
end: start + 3,
neutral_integer: false,
ordered_const_eq_ne: true,
nonnegative_signed_spelling: false,
power_of_two_mul: false,
float_sub_to_add: false,
integer_const_fold: false,
wrap_i64_const: false,
f64_div2_sub_to_mul_add: false,
i64_extend_local_commute: false,
rewrite: [@lib.Instruction::i32_const(eq_ne.result)],
})
None => ()
}
match
RawOiNonnegativeSignedSpellingFact::from_i64(lhs_raw, rhs_raw, instr) {
Some(unsigned_spelling) if unsigned_spelling.proves_binaryen_unsigned_spelling() =>
return Some({
end: start + 3,
neutral_integer: false,
ordered_const_eq_ne: false,
nonnegative_signed_spelling: true,
power_of_two_mul: false,
float_sub_to_add: false,
integer_const_fold: false,
wrap_i64_const: false,
f64_div2_sub_to_mul_add: false,
i64_extend_local_commute: false,
rewrite: [
@lib.Instruction::i64_const(@lib.I64(lhs_raw)),
@lib.Instruction::i64_const(@lib.I64(rhs_raw)),
unsigned_spelling.unsigned_instr,
],
})
_ => ()
}
}
_ => ()
}
match
(instrs[start].instr, instrs[start + 1].instr, instrs[start + 2].instr) {
(lhs, @lib.I32Const(@lib.I32(raw)), @lib.I32Mul) if !RawOiScalarConstAtomFact::from_instr(
lhs,
).is_i32_const =>
match RawOiI32PowerOfTwoShiftFact::from_raw(raw) {
Some(pow2) if pow2.proves_binaryen_power_of_two_shift() =>
return Some({
end: start + 3,
neutral_integer: false,
ordered_const_eq_ne: false,
nonnegative_signed_spelling: false,
power_of_two_mul: true,
float_sub_to_add: false,
integer_const_fold: false,
wrap_i64_const: false,
f64_div2_sub_to_mul_add: false,
i64_extend_local_commute: false,
rewrite: [
lhs,
@lib.Instruction::i32_const(@lib.I32(pow2.shift)),
@lib.Instruction::i32_shl(),
],
})
_ => ()
}
(lhs, @lib.I64Const(@lib.I64(raw)), @lib.I64Mul) if !RawOiScalarConstAtomFact::from_instr(
lhs,
).is_i64_const =>
match RawOiI64PowerOfTwoShiftFact::from_raw(raw) {
Some(pow2) if pow2.proves_binaryen_power_of_two_shift() =>
return Some({
end: start + 3,
neutral_integer: false,
ordered_const_eq_ne: false,
nonnegative_signed_spelling: false,
power_of_two_mul: true,
float_sub_to_add: false,
integer_const_fold: false,
wrap_i64_const: false,
f64_div2_sub_to_mul_add: false,
i64_extend_local_commute: false,
rewrite: [
lhs,
@lib.Instruction::i64_const(@lib.I64(pow2.shift)),
@lib.Instruction::i64_shl(),
],
})
_ => ()
}
(@lib.F32Const(lhs), @lib.F32Const(@lib.F32(rhs_raw)), @lib.F32Sub) =>
match RawOiF32SubNegatedConstFact::from_raw(rhs_raw) {
Some(rhs) if rhs.proves_binaryen_negatable_sub_const() =>
return Some({
end: start + 3,
neutral_integer: false,
ordered_const_eq_ne: false,
nonnegative_signed_spelling: false,
power_of_two_mul: false,
float_sub_to_add: true,
integer_const_fold: false,
wrap_i64_const: false,
f64_div2_sub_to_mul_add: false,
i64_extend_local_commute: false,
rewrite: [
@lib.Instruction::f32_const(lhs),
@lib.Instruction::f32_const(@lib.F32(rhs.negated)),
@lib.Instruction::f32_add(),
],
})
_ => ()
}
(@lib.F64Const(lhs), @lib.F64Const(@lib.F64(rhs_raw)), @lib.F64Sub) =>
match RawOiF64SubNegatedConstFact::from_raw(rhs_raw) {
Some(rhs) if rhs.proves_binaryen_negatable_sub_const() =>
return Some({
end: start + 3,
neutral_integer: false,
ordered_const_eq_ne: false,
nonnegative_signed_spelling: false,
power_of_two_mul: false,
float_sub_to_add: true,
integer_const_fold: false,
wrap_i64_const: false,
f64_div2_sub_to_mul_add: false,
i64_extend_local_commute: false,
rewrite: [
@lib.Instruction::f64_const(lhs),
@lib.Instruction::f64_const(@lib.F64(rhs.negated)),
@lib.Instruction::f64_add(),
],
})
_ => ()
}
(
@lib.I32Const(@lib.I32(lhs_raw)),
@lib.I32Const(@lib.I32(rhs_raw)),
@lib.I32Add,
) => {
let folded = @lib.I32(
(lhs_raw.reinterpret_as_uint() + rhs_raw.reinterpret_as_uint()).reinterpret_as_int(),
)
return Some({
end: start + 3,
neutral_integer: false,
ordered_const_eq_ne: false,
nonnegative_signed_spelling: false,
power_of_two_mul: false,
float_sub_to_add: false,
integer_const_fold: true,
wrap_i64_const: false,
f64_div2_sub_to_mul_add: false,
i64_extend_local_commute: false,
rewrite: [@lib.Instruction::i32_const(folded)],
})
}
(
@lib.I32Const(@lib.I32(lhs_raw)),
@lib.I32Const(@lib.I32(rhs_raw)),
@lib.I32Sub,
) => {
let folded = @lib.I32(
(lhs_raw.reinterpret_as_uint() - rhs_raw.reinterpret_as_uint()).reinterpret_as_int(),
)
return Some({
end: start + 3,
neutral_integer: false,
ordered_const_eq_ne: false,
nonnegative_signed_spelling: false,
power_of_two_mul: false,
float_sub_to_add: false,
integer_const_fold: true,
wrap_i64_const: false,
f64_div2_sub_to_mul_add: false,
i64_extend_local_commute: false,
rewrite: [@lib.Instruction::i32_const(folded)],
})
}
(
@lib.I64Const(@lib.I64(lhs_raw)),
@lib.I64Const(@lib.I64(rhs_raw)),
@lib.I64Add,
) => {
let folded = @lib.I64(
(lhs_raw.reinterpret_as_uint64() + rhs_raw.reinterpret_as_uint64()).reinterpret_as_int64(),
)
return Some({
end: start + 3,
neutral_integer: false,
ordered_const_eq_ne: false,
nonnegative_signed_spelling: false,
power_of_two_mul: false,
float_sub_to_add: false,
integer_const_fold: true,
wrap_i64_const: false,
f64_div2_sub_to_mul_add: false,
i64_extend_local_commute: false,
rewrite: [@lib.Instruction::i64_const(folded)],
})
}
(
@lib.I64Const(@lib.I64(lhs_raw)),
@lib.I64Const(@lib.I64(rhs_raw)),
@lib.I64Sub,
) => {
let folded = @lib.I64(
(lhs_raw.reinterpret_as_uint64() - rhs_raw.reinterpret_as_uint64()).reinterpret_as_int64(),
)
return Some({
end: start + 3,
neutral_integer: false,
ordered_const_eq_ne: false,
nonnegative_signed_spelling: false,
power_of_two_mul: false,
float_sub_to_add: false,
integer_const_fold: true,
wrap_i64_const: false,
f64_div2_sub_to_mul_add: false,
i64_extend_local_commute: false,
rewrite: [@lib.Instruction::i64_const(folded)],
})
}
_ => ()
}
None
}
///|
fn RawOiScalarNumericRewriteFact::from_two_roots(
region : RawOiInstructionRegionFact,
start : Int32,
) -> RawOiScalarNumericRewriteFact? {
if start + 1 >= region.roots.length() ||
!region.has_root_at(start + 1, RawOiRootKind::ScalarNumericTerminal) {
return None
}
match (region.roots[start].instr, region.roots[start + 1].instr) {
(@lib.I64Const(@lib.I64(raw)), @lib.I32WrapI64) => {
let wrap = RawOiWrapI64ConstFact::from_raw(raw)
if !wrap.proves_binaryen_wrap_const() {
return None
}
Some({
end: start + 2,
neutral_integer: false,
ordered_const_eq_ne: false,
nonnegative_signed_spelling: false,
power_of_two_mul: false,
float_sub_to_add: false,
integer_const_fold: false,
wrap_i64_const: true,
f64_div2_sub_to_mul_add: false,
i64_extend_local_commute: false,
rewrite: [@lib.Instruction::i32_const(wrap.wrapped)],
})
}
_ => None
}
}
///|
fn RawOiScalarNumericRewriteFact::from_region(
region : RawOiInstructionRegionFact,
start : Int32,
module_ctx~ : @ir.HotModuleContext? = None,
) -> RawOiScalarNumericRewriteFact? {
match RawOiScalarNumericRewriteFact::from_f64_div2_sub(region, start) {
Some(fact) => return Some(fact)
None => ()
}
match
RawOiScalarNumericRewriteFact::from_i64_extend_local_commute(region, start) {
Some(fact) => return Some(fact)
None => ()
}
match
RawOiScalarNumericRewriteFact::from_three_roots(region, start, module_ctx~) {
Some(fact) => return Some(fact)
None => ()
}
RawOiScalarNumericRewriteFact::from_two_roots(region, start)
}
///|
// Remaining raw traversal audit: seven descriptor `from_instrs` definitions
// below form one representation bridge cluster (direct/local exact success and
// miss, exactification, and balanced operands). Descriptor block-prefix control
// reconstruction has moved to one reusable prefix value graph per body. The
// remaining bridges reconstruct stack expressions that are not yet available
// as HOT value graphs. Preserve exact descriptor type/exactness, trap, effect,
// source order, and operand-cardinality proof; migrate only with shared typed
// descriptor-value overlays, never another shape-specific whole-body scanner.
//
// Source-backed raw descriptor prefix facts for the Binaryen v130
// optimizeKnownCastResult descriptor-child-localization case. Binaryen uses
// ChildLocalizer to preserve descriptor operand effects before replacing a
// known-null descriptor cast; Starshine's raw stack fallback can only hoist a
// trapping descriptor block prefix when all contained control remains inside
// that prefix and no loop/EH/return/throw boundary can escape it.
priv struct RawOiDescriptorPrefixLabelFact {
depth : Int32
target_depth : Int32
is_local_to_prefix : Bool
}
///|
fn RawOiDescriptorPrefixLabelFact::from_label(
label_idx : @lib.LabelIdx,
depth : Int32,
) -> RawOiDescriptorPrefixLabelFact {
let @lib.LabelIdx(raw) = label_idx
let target_depth = raw.reinterpret_as_int()
{ depth, target_depth, is_local_to_prefix: target_depth < depth, }
}
///|
fn RawOiDescriptorPrefixLabelFact::proves_binaryen_local_branch(
self : RawOiDescriptorPrefixLabelFact,
) -> Bool {
self.is_local_to_prefix && self.target_depth < self.depth
}
///|
priv struct RawOiDescriptorPrefixBodyFact {
depth : Int32
end : Int32
can_hoist : Bool
has_blocking_boundary : Bool
}
///|
priv struct RawOiDescriptorPrefixInstrFact {
can_hoist : Bool
is_plain_instruction : Bool
has_nested_control : Bool
has_blocking_boundary : Bool
}
///|
// Reusable prefix graph for one descriptor value body. Each instruction is
// classified once for the current immutable raw revision; consumers can query
// any prefix boundary without replaying the body scan. Recursive block/if
// values build the same graph for their own nested revision.
priv struct RawOiDescriptorPrefixValueGraphFact {
body_len : Int32
prefixes : Array[RawOiDescriptorPrefixBodyFact]
}
///|
fn RawOiDescriptorPrefixValueGraphFact::from_body(
instrs : Array[@lib.Instruction],
depth : Int32,
) -> RawOiDescriptorPrefixValueGraphFact {
let prefixes : Array[RawOiDescriptorPrefixBodyFact] = [
{ depth, end: 0, can_hoist: true, has_blocking_boundary: false, },
]
let mut can_hoist = true
let mut has_blocking_boundary = false
for idx = 0; idx < instrs.length(); idx = idx + 1 {
let fact = RawOiDescriptorPrefixInstrFact::from_instr(instrs[idx], depth)
can_hoist = can_hoist && fact.can_hoist
has_blocking_boundary = has_blocking_boundary ||
fact.has_blocking_boundary ||
(fact.has_nested_control && !fact.can_hoist && !fact.is_plain_instruction)
prefixes.push({ depth, end: idx + 1, can_hoist, has_blocking_boundary, })
}
{ body_len: instrs.length(), prefixes, }
}
///|
fn RawOiDescriptorPrefixValueGraphFact::whole_body(
self : RawOiDescriptorPrefixValueGraphFact,
) -> RawOiDescriptorPrefixBodyFact {
self.prefixes[self.body_len]
}
///|
fn RawOiDescriptorPrefixInstrFact::plain() -> RawOiDescriptorPrefixInstrFact {
{
can_hoist: true,
is_plain_instruction: true,
has_nested_control: false,
has_blocking_boundary: false,
}
}
///|
fn RawOiDescriptorPrefixInstrFact::nested(
body : RawOiDescriptorPrefixBodyFact,
) -> RawOiDescriptorPrefixInstrFact {
{
can_hoist: body.can_hoist,
is_plain_instruction: false,
has_nested_control: true,
has_blocking_boundary: body.has_blocking_boundary,
}
}
///|
fn RawOiDescriptorPrefixInstrFact::branch(
label : RawOiDescriptorPrefixLabelFact,
) -> RawOiDescriptorPrefixInstrFact {
let can_hoist = label.proves_binaryen_local_branch()
{
can_hoist,
is_plain_instruction: false,
has_nested_control: true,
has_blocking_boundary: !can_hoist,
}
}
///|
fn RawOiDescriptorPrefixInstrFact::blocking() -> RawOiDescriptorPrefixInstrFact {
{
can_hoist: false,
is_plain_instruction: false,
has_nested_control: true,
has_blocking_boundary: true,
}
}
///|
fn RawOiDescriptorPrefixInstrFact::from_instr(
instr : @lib.Instruction,
depth : Int32,
) -> RawOiDescriptorPrefixInstrFact {
match instr {
@lib.Block(_, @lib.Expr(body)) =>
RawOiDescriptorPrefixInstrFact::nested(
RawOiDescriptorPrefixValueGraphFact::from_body(body, depth + 1).whole_body(),
)
@lib.If(_, then_body, else_body) => {
let then_fact = RawOiDescriptorPrefixValueGraphFact::from_body(
then_body,
depth + 1,
).whole_body()
let else_fact = match else_body {
Some(body) =>
RawOiDescriptorPrefixValueGraphFact::from_body(body, depth + 1).whole_body()
None =>
RawOiDescriptorPrefixValueGraphFact::from_body([], depth + 1).whole_body()
}
{
can_hoist: then_fact.can_hoist && else_fact.can_hoist,
is_plain_instruction: false,
has_nested_control: true,
has_blocking_boundary: then_fact.has_blocking_boundary ||
else_fact.has_blocking_boundary,
}
}
@lib.Br(label_idx) =>
RawOiDescriptorPrefixInstrFact::branch(
RawOiDescriptorPrefixLabelFact::from_label(label_idx, depth),
)
@lib.BrIf(label_idx) =>
RawOiDescriptorPrefixInstrFact::branch(
RawOiDescriptorPrefixLabelFact::from_label(label_idx, depth),
)
@lib.BrTable(labels, default_label) => {
let default_fact = RawOiDescriptorPrefixLabelFact::from_label(
default_label, depth,
)
let mut can_hoist = default_fact.proves_binaryen_local_branch()
for label_idx in labels {
let label_fact = RawOiDescriptorPrefixLabelFact::from_label(
label_idx, depth,
)
can_hoist = can_hoist && label_fact.proves_binaryen_local_branch()
}
{
can_hoist,
is_plain_instruction: false,
has_nested_control: true,
has_blocking_boundary: !can_hoist,
}
}
@lib.Loop(_, _)
| @lib.TryTable(_, _, _)
| @lib.Return
| @lib.ReturnCall(_)
| @lib.ReturnCallIndirect(_, _)
| @lib.ReturnCallRef(_)
| @lib.Throw(_)
| @lib.ThrowRef => RawOiDescriptorPrefixInstrFact::blocking()
_ => RawOiDescriptorPrefixInstrFact::plain()
}
}
///|
priv struct RawOiDescriptorBlockPrefixHoistFact {
prefix_len : Int32
body : RawOiDescriptorPrefixBodyFact
}
///|
fn RawOiDescriptorPrefixValueGraphFact::prefix_hoist(
self : RawOiDescriptorPrefixValueGraphFact,
prefix_len : Int32,
) -> RawOiDescriptorBlockPrefixHoistFact? {
if prefix_len < 0 || prefix_len > self.body_len {
return None
}
let body = self.prefixes[prefix_len]
if body.can_hoist {
Some({ prefix_len, body, })
} else {
None
}
}
///|
fn RawOiDescriptorBlockPrefixHoistFact::proves_binaryen_descriptor_prefix_hoist(
self : RawOiDescriptorBlockPrefixHoistFact,
) -> Bool {
self.body.depth == 0 &&
self.body.end == self.prefix_len &&
self.body.can_hoist &&
!self.body.has_blocking_boundary
}
/// Raw descriptor producer and reference-return facts for descriptor-cast OI
/// bridges. Binaryen v130's `optimizeKnownCastResult` relies on
/// `GCTypeUtils::evaluateCastCheck` for heap/nullability/exactness results and
/// separately keeps descriptor casts conservative unless the descriptor value is
/// known to describe the cast target. These facts model the already-covered raw
/// producer surfaces before the stack fallback consumes them.
///|
priv enum RawOiDescriptorProducerKind {
DescriptorProducerStructNewDefault
DescriptorProducerDirectCall
DescriptorProducerCallRefType
}
///|
priv struct RawOiDescriptorTargetFact {
target : @lib.HeapType
target_idx : @lib.TypeIdx
}
///|
fn RawOiDescriptorTargetFact::from_heap_type(
target : @lib.HeapType,
) -> RawOiDescriptorTargetFact? {
match target {
@lib.HeapType(target_idx) => Some({ target, target_idx, })
_ => None
}
}
///|
priv struct RawOiDescriptorDescribesFact {
kind : RawOiDescriptorProducerKind
descriptor_type : @lib.TypeIdx
target : RawOiDescriptorTargetFact
param_count : Int32
}
///|
fn RawOiDescriptorDescribesFact::from_descriptor_type(
module_ctx : @ir.HotModuleContext,
kind : RawOiDescriptorProducerKind,
descriptor_type : @lib.TypeIdx,
target : @lib.HeapType,
param_count : Int32,
) -> RawOiDescriptorDescribesFact? {
let target_fact = match RawOiDescriptorTargetFact::from_heap_type(target) {
Some(target_fact) => target_fact
None => return None
}
match @ir.hot_module_resolve_subtype(module_ctx, descriptor_type) {
Some(subtype) if subtype.describes_type() == Some(target_fact.target_idx) =>
Some({ kind, descriptor_type, target: target_fact, param_count, })
_ => None
}
}
///|
fn RawOiDescriptorDescribesFact::from_struct_new_default(
module_ctx : @ir.HotModuleContext,
descriptor_type : @lib.TypeIdx,
target : @lib.HeapType,
) -> RawOiDescriptorDescribesFact? {
RawOiDescriptorDescribesFact::from_descriptor_type(
module_ctx,
DescriptorProducerStructNewDefault,
descriptor_type,
target,
0,
)
}
///|
fn RawOiDescriptorDescribesFact::descriptor_type_from_func_type(
func_type : @lib.FuncType,
param_count : Int32,
) -> @lib.TypeIdx? {
match func_type {
@lib.FuncType(params, [@lib.ValType::RefTypeValType(ref_type)]) if params.length() ==
param_count &&
ref_type.is_non_nullable() &&
ref_type.is_exact() =>
match ref_type.get_heap_type() {
@lib.HeapType(type_idx) => Some(type_idx)
_ => None
}
_ => None
}
}
///|
fn RawOiDescriptorDescribesFact::from_direct_call(
module_ctx : @ir.HotModuleContext,
descriptor_func : @lib.FuncIdx,
target : @lib.HeapType,
param_count : Int32,
) -> RawOiDescriptorDescribesFact? {
let descriptor_type = match
@ir.hot_module_resolve_func_type(module_ctx, descriptor_func) {
Some(func_type) =>
match
RawOiDescriptorDescribesFact::descriptor_type_from_func_type(
func_type, param_count,
) {
Some(descriptor_type) => descriptor_type
None => return None
}
None => return None
}
RawOiDescriptorDescribesFact::from_descriptor_type(
module_ctx,
DescriptorProducerDirectCall,
descriptor_type,
target,
param_count,
)
}
///|
fn RawOiDescriptorDescribesFact::from_call_ref_type(
module_ctx : @ir.HotModuleContext,
type_idx : @lib.TypeIdx,
target : @lib.HeapType,
param_count : Int32,
) -> RawOiDescriptorDescribesFact? {
let descriptor_type = match
@ir.hot_module_resolve_type_func_type(module_ctx, type_idx) {
Some(func_type) =>
match
RawOiDescriptorDescribesFact::descriptor_type_from_func_type(
func_type, param_count,
) {
Some(descriptor_type) => descriptor_type
None => return None
}
None => return None
}
RawOiDescriptorDescribesFact::from_descriptor_type(
module_ctx,
DescriptorProducerCallRefType,
descriptor_type,
target,
param_count,
)
}
///|
fn RawOiDescriptorDescribesFact::proves_binaryen_descriptor_exactly_describes(
self : RawOiDescriptorDescribesFact,
) -> Bool {
let represented_kind = match self.kind {
DescriptorProducerStructNewDefault
| DescriptorProducerDirectCall
| DescriptorProducerCallRefType => true
}
let descriptor_type_is_resolved = match self.descriptor_type {
@lib.TypeIdx(_) | @lib.RecIdx(_) => true
}
let target_is_named = match self.target.target {
@lib.HeapType(target_idx) => target_idx == self.target.target_idx
_ => false
}
represented_kind &&
descriptor_type_is_resolved &&
target_is_named &&
self.param_count >= 0
}
///|
fn RawOiDescriptorDescribesFact::struct_new_exactly_describes(
module_ctx : @ir.HotModuleContext,
descriptor_type : @lib.TypeIdx,
target : @lib.HeapType,
) -> Bool {
match
RawOiDescriptorDescribesFact::from_struct_new_default(
module_ctx, descriptor_type, target,
) {
Some(fact) => fact.proves_binaryen_descriptor_exactly_describes()
None => false
}
}
///|
fn RawOiDescriptorDescribesFact::direct_call_exactly_describes_with_param_count(
module_ctx : @ir.HotModuleContext,
descriptor_func : @lib.FuncIdx,
target : @lib.HeapType,
param_count : Int32,
) -> Bool {
match
RawOiDescriptorDescribesFact::from_direct_call(
module_ctx, descriptor_func, target, param_count,
) {
Some(fact) => fact.proves_binaryen_descriptor_exactly_describes()
None => false
}
}
///|
fn RawOiDescriptorDescribesFact::call_ref_type_exactly_describes_with_param_count(
module_ctx : @ir.HotModuleContext,
type_idx : @lib.TypeIdx,
target : @lib.HeapType,
param_count : Int32,
) -> Bool {
match
RawOiDescriptorDescribesFact::from_call_ref_type(
module_ctx, type_idx, target, param_count,
) {
Some(fact) => fact.proves_binaryen_descriptor_exactly_describes()
None => false
}
}
///|
priv enum RawOiReferenceReturnSourceKind {
ReferenceReturnDirectCall
ReferenceReturnCallRefType
}
///|
priv struct RawOiReferenceReturnFact {
source_kind : RawOiReferenceReturnSourceKind
param_count : Int32
heap_type : @lib.HeapType
nullable : Bool
exact : Bool
}
///|
fn RawOiReferenceReturnFact::from_func_type(
source_kind : RawOiReferenceReturnSourceKind,
func_type : @lib.FuncType,
param_count : Int32,
) -> RawOiReferenceReturnFact? {
match func_type {
@lib.FuncType(params, [@lib.ValType::RefTypeValType(ref_type)]) if params.length() ==
param_count =>
Some({
source_kind,
param_count,
heap_type: ref_type.get_heap_type(),
nullable: ref_type.is_nullable(),
exact: ref_type.is_exact(),
})
_ => None
}
}
///|
fn RawOiReferenceReturnFact::from_direct_call(
module_ctx : @ir.HotModuleContext,
ref_func : @lib.FuncIdx,
param_count : Int32,
) -> RawOiReferenceReturnFact? {
match @ir.hot_module_resolve_func_type(module_ctx, ref_func) {
Some(func_type) =>
RawOiReferenceReturnFact::from_func_type(
ReferenceReturnDirectCall,
func_type,
param_count,
)
None => None
}
}
///|
fn RawOiReferenceReturnFact::from_call_ref_type(
module_ctx : @ir.HotModuleContext,
type_idx : @lib.TypeIdx,
param_count : Int32,
) -> RawOiReferenceReturnFact? {
match @ir.hot_module_resolve_type_func_type(module_ctx, type_idx) {
Some(func_type) =>
RawOiReferenceReturnFact::from_func_type(
ReferenceReturnCallRefType,
func_type,
param_count,
)
None => None
}
}
///|
fn RawOiReferenceReturnFact::is_represented_return_fact(
self : RawOiReferenceReturnFact,
) -> Bool {
let represented_source = match self.source_kind {
ReferenceReturnDirectCall | ReferenceReturnCallRefType => true
}
represented_source && self.param_count >= 0
}
///|
fn RawOiReferenceReturnFact::proves_binaryen_non_null_strict_subtype(
self : RawOiReferenceReturnFact,
module_ctx : @ir.HotModuleContext,
target : @lib.HeapType,
) -> Bool {
self.is_represented_return_fact() &&
!self.nullable &&
self.heap_type != target &&
run_hot_pipeline_raw_heap_type_is_subtype(
module_ctx,
self.heap_type,
target,
64,
)
}
///|
fn RawOiReferenceReturnFact::proves_binaryen_non_null_exact_target(
self : RawOiReferenceReturnFact,
target : @lib.HeapType,
) -> Bool {
self.is_represented_return_fact() &&
!self.nullable &&
self.exact &&
self.heap_type == target
}
///|
fn RawOiReferenceReturnFact::proves_binaryen_non_null_target(
self : RawOiReferenceReturnFact,
target : @lib.HeapType,
) -> Bool {
self.is_represented_return_fact() &&
!self.nullable &&
self.heap_type == target
}
///|
fn RawOiReferenceReturnFact::proves_binaryen_nullable_target(
self : RawOiReferenceReturnFact,
target : @lib.HeapType,
) -> Bool {
self.is_represented_return_fact() && self.nullable && self.heap_type == target
}
///|
fn RawOiReferenceReturnFact::direct_call_proves_non_null_strict_subtype_with_param_count(
module_ctx : @ir.HotModuleContext,
ref_func : @lib.FuncIdx,
target : @lib.HeapType,
param_count : Int32,
) -> Bool {
match
RawOiReferenceReturnFact::from_direct_call(
module_ctx, ref_func, param_count,
) {
Some(fact) =>
fact.proves_binaryen_non_null_strict_subtype(module_ctx, target)
None => false
}
}
///|
fn RawOiReferenceReturnFact::direct_call_proves_non_null_exact_target_with_param_count(
module_ctx : @ir.HotModuleContext,
ref_func : @lib.FuncIdx,
target : @lib.HeapType,
param_count : Int32,
) -> Bool {
match
RawOiReferenceReturnFact::from_direct_call(
module_ctx, ref_func, param_count,
) {
Some(fact) => fact.proves_binaryen_non_null_exact_target(target)
None => false
}
}
///|
fn RawOiReferenceReturnFact::call_ref_type_proves_non_null_exact_target_with_param_count(
module_ctx : @ir.HotModuleContext,
type_idx : @lib.TypeIdx,
target : @lib.HeapType,
param_count : Int32,
) -> Bool {
match
RawOiReferenceReturnFact::from_call_ref_type(
module_ctx, type_idx, param_count,
) {
Some(fact) => fact.proves_binaryen_non_null_exact_target(target)
None => false
}
}
///|
fn RawOiReferenceReturnFact::direct_call_proves_non_null_target_with_param_count(
module_ctx : @ir.HotModuleContext,
ref_func : @lib.FuncIdx,
target : @lib.HeapType,
param_count : Int32,
) -> Bool {
match
RawOiReferenceReturnFact::from_direct_call(
module_ctx, ref_func, param_count,
) {
Some(fact) => fact.proves_binaryen_non_null_target(target)
None => false
}
}
///|
fn RawOiReferenceReturnFact::direct_call_proves_nullable_target_with_param_count(
module_ctx : @ir.HotModuleContext,
ref_func : @lib.FuncIdx,
target : @lib.HeapType,
param_count : Int32,
) -> Bool {
match
RawOiReferenceReturnFact::from_direct_call(
module_ctx, ref_func, param_count,
) {
Some(fact) => fact.proves_binaryen_nullable_target(target)
None => false
}
}
/// Raw descriptor call-argument facts for the OI-J stack fallback. Binaryen
/// v130's descriptor-cast paths use `getDroppedChildrenAndAppend` and
/// `ChildLocalizer` to preserve ref/descriptor operands before replacing a
/// known descriptor-cast result; this raw bridge keeps the previously-covered
/// leaf, direct-call, scalar, select, and branch-free block argument chunks as
/// classified facts rather than recursive helper predicates.
///|
priv enum RawOiDescriptorCallArgKind {
DescriptorArgNontrappingLeaf
DescriptorArgBranchFreeEffectfulBlock
DescriptorArgNoParamDirectValueCall
DescriptorArgNoParamDirectReferenceCall
DescriptorArgScalarLocalTee
DescriptorArgPureSelect
DescriptorArgTrappingScalarBinary
DescriptorArgParameterizedDirectValueCall
DescriptorArgParameterizedDirectReferenceCall
}
///|
priv struct RawOiDescriptorCallArgFact {
kind : RawOiDescriptorCallArgKind
start : Int32
end : Int32
value_count : Int32
}
///|
priv struct RawOiDescriptorCallArgBlockBodyFact {
has_value : Bool
branch_free : Bool
blocking_boundary_count : Int32
}
///|
fn RawOiDescriptorCallArgBlockBodyFact::from_region(
region : RawOiInstructionRegionFact,
) -> RawOiDescriptorCallArgBlockBodyFact {
if region.roots.is_empty() {
return { has_value: false, branch_free: false, blocking_boundary_count: 0, }
}
let mut blocking_boundary_count = 0
for root in region.roots {
if root.flow.blocks_branch_free_value_region() {
blocking_boundary_count = blocking_boundary_count + 1
}
}
{
has_value: true,
branch_free: blocking_boundary_count == 0,
blocking_boundary_count,
}
}
///|
fn RawOiDescriptorCallArgBlockBodyFact::proves_binaryen_branch_free_operand(
self : RawOiDescriptorCallArgBlockBodyFact,
) -> Bool {
self.has_value && self.branch_free && self.blocking_boundary_count == 0
}
///|
fn RawOiDescriptorCallArgFact::from_nontrapping_leaf_instr(
instr : @lib.Instruction,
start : Int32,
) -> RawOiDescriptorCallArgFact? {
match instr {
@lib.LocalGet(_)
| @lib.GlobalGet(_)
| @lib.I32Const(_)
| @lib.I64Const(_)
| @lib.F32Const(_)
| @lib.F64Const(_) =>
Some({
kind: DescriptorArgNontrappingLeaf,
start,
end: start + 1,
value_count: 1,
})
_ => None
}
}
///|
fn RawOiDescriptorCallArgFact::from_branch_free_effectful_block_instr(
instr : @lib.Instruction,
start : Int32,
) -> RawOiDescriptorCallArgFact? {
match instr {
@lib.Block(@lib.ValTypeBlockType(_), body) => {
let body_fact = RawOiDescriptorCallArgBlockBodyFact::from_region(
RawOiInstructionRegionFact::from_expr(body),
)
if body_fact.proves_binaryen_branch_free_operand() {
Some({
kind: DescriptorArgBranchFreeEffectfulBlock,
start,
end: start + 1,
value_count: 1,
})
} else {
None
}
}
_ => None
}
}
///|
fn RawOiDescriptorCallArgFact::from_no_param_direct_value_call_instr(
module_ctx : @ir.HotModuleContext,
instr : @lib.Instruction,
start : Int32,
) -> RawOiDescriptorCallArgFact? {
match instr {
@lib.Call(func_idx) =>
match @ir.hot_module_resolve_func_type(module_ctx, func_idx) {
Some(@lib.FuncType(params, [result])) if params.length() == 0 =>
match result {
@lib.ValType::RefTypeValType(_) => None
_ =>
Some({
kind: DescriptorArgNoParamDirectValueCall,
start,
end: start + 1,
value_count: 1,
})
}
_ => None
}
_ => None
}
}
///|
fn RawOiDescriptorCallArgFact::from_no_param_direct_reference_call_instr(
module_ctx : @ir.HotModuleContext,
instr : @lib.Instruction,
start : Int32,
) -> RawOiDescriptorCallArgFact? {
match instr {
@lib.Call(func_idx) =>
match @ir.hot_module_resolve_func_type(module_ctx, func_idx) {
Some(@lib.FuncType(params, [@lib.ValType::RefTypeValType(ref_type)])) if params.length() ==
0 &&
ref_type.is_non_nullable() =>
Some({
kind: DescriptorArgNoParamDirectReferenceCall,
start,
end: start + 1,
value_count: 1,
})
_ => None
}
_ => None
}
}
///|
fn RawOiDescriptorCallArgFact::from_scalar_local_tee_instrs(
instrs : Array[@lib.Instruction],
start : Int32,
) -> RawOiDescriptorCallArgFact? {
if start + 1 >= instrs.length() {
return None
}
match (instrs[start], instrs[start + 1]) {
(@lib.I32Const(_), @lib.LocalTee(_))
| (@lib.I64Const(_), @lib.LocalTee(_))
| (@lib.F32Const(_), @lib.LocalTee(_))
| (@lib.F64Const(_), @lib.LocalTee(_)) =>
Some({
kind: DescriptorArgScalarLocalTee,
start,
end: start + 2,
value_count: 1,
})
_ => None
}
}
///|
fn RawOiDescriptorCallArgFact::from_pure_select_instrs(
instrs : Array[@lib.Instruction],
start : Int32,
) -> RawOiDescriptorCallArgFact? {
if start + 3 >= instrs.length() {
return None
}
match
RawOiDescriptorCallArgFact::from_nontrapping_leaf_instr(
instrs[start],
start,
) {
None => return None
Some(_) => ()
}
match
RawOiDescriptorCallArgFact::from_nontrapping_leaf_instr(
instrs[start + 1],
start + 1,
) {
None => return None
Some(_) => ()
}
match
RawOiDescriptorCallArgFact::from_nontrapping_leaf_instr(
instrs[start + 2],
start + 2,
) {
None => return None
Some(_) => ()
}
match instrs[start + 3] {
@lib.Select(_) =>
Some({
kind: DescriptorArgPureSelect,
start,
end: start + 4,
value_count: 1,
})
_ => None
}
}
///|
fn RawOiDescriptorCallArgFact::from_trapping_scalar_binary_instrs(
instrs : Array[@lib.Instruction],
start : Int32,
) -> RawOiDescriptorCallArgFact? {
if start + 2 >= instrs.length() {
return None
}
match
RawOiDescriptorCallArgFact::from_nontrapping_leaf_instr(
instrs[start],
start,
) {
None => return None
Some(_) => ()
}
match
RawOiDescriptorCallArgFact::from_nontrapping_leaf_instr(
instrs[start + 1],
start + 1,
) {
None => return None
Some(_) => ()
}
match instrs[start + 2] {
@lib.I32DivS
| @lib.I32DivU
| @lib.I32RemS
| @lib.I32RemU
| @lib.I64DivS
| @lib.I64DivU
| @lib.I64RemS
| @lib.I64RemU =>
Some({
kind: DescriptorArgTrappingScalarBinary,
start,
end: start + 3,
value_count: 1,
})
_ => None
}
}
///|
fn RawOiDescriptorCallArgFact::from_parameterized_direct_value_call_instrs(
module_ctx : @ir.HotModuleContext,
instrs : Array[@lib.Instruction],
start : Int32,
) -> RawOiDescriptorCallArgFact? {
let mut scan = start
let mut param_count = 0
while scan < instrs.length() {
match
RawOiDescriptorCallArgFact::from_nontrapping_leaf_instr(
instrs[scan],
scan,
) {
Some(_) => {
param_count = param_count + 1
scan = scan + 1
continue
}
None => ()
}
match instrs[scan] {
@lib.Call(func_idx) if param_count > 0 =>
match @ir.hot_module_resolve_func_type(module_ctx, func_idx) {
Some(@lib.FuncType(params, [result])) if params.length() ==
param_count =>
match result {
@lib.ValType::RefTypeValType(_) => return None
_ =>
return Some({
kind: DescriptorArgParameterizedDirectValueCall,
start,
end: scan + 1,
value_count: 1,
})
}
_ => return None
}
_ => return None
}
}
None
}
///|
fn RawOiDescriptorCallArgFact::from_parameterized_direct_reference_call_instrs(
module_ctx : @ir.HotModuleContext,
instrs : Array[@lib.Instruction],
start : Int32,
) -> RawOiDescriptorCallArgFact? {
let mut scan = start
let mut param_count = 0
while scan < instrs.length() {
match
RawOiDescriptorCallArgFact::from_nontrapping_leaf_instr(
instrs[scan],
scan,
) {
Some(_) => {
param_count = param_count + 1
scan = scan + 1
continue
}
None => ()
}
match instrs[scan] {
@lib.Call(func_idx) if param_count > 0 =>
match @ir.hot_module_resolve_func_type(module_ctx, func_idx) {
Some(@lib.FuncType(params, [@lib.ValType::RefTypeValType(ref_type)])) if params.length() ==
param_count &&
ref_type.is_non_nullable() =>
return Some({
kind: DescriptorArgParameterizedDirectReferenceCall,
start,
end: scan + 1,
value_count: 1,
})
_ => return None
}
_ => return None
}
}
None
}
///|
fn RawOiDescriptorCallArgFact::from_descriptor_operand_miss_chunk(
module_ctx : @ir.HotModuleContext,
instrs : Array[@lib.Instruction],
start : Int32,
) -> RawOiDescriptorCallArgFact? {
match
RawOiDescriptorCallArgFact::from_scalar_local_tee_instrs(instrs, start) {
Some(fact) => return Some(fact)
None => ()
}
match RawOiDescriptorCallArgFact::from_pure_select_instrs(instrs, start) {
Some(fact) => return Some(fact)
None => ()
}
match
RawOiDescriptorCallArgFact::from_trapping_scalar_binary_instrs(
instrs, start,
) {
Some(fact) => return Some(fact)
None => ()
}
match
RawOiDescriptorCallArgFact::from_parameterized_direct_value_call_instrs(
module_ctx, instrs, start,
) {
Some(fact) => return Some(fact)
None => ()
}
RawOiDescriptorCallArgFact::from_parameterized_direct_reference_call_instrs(
module_ctx, instrs, start,
)
}
///|
fn RawOiDescriptorCallArgFact::from_descriptor_operand_miss_single(
module_ctx : @ir.HotModuleContext,
instr : @lib.Instruction,
start : Int32,
) -> RawOiDescriptorCallArgFact? {
match RawOiDescriptorCallArgFact::from_nontrapping_leaf_instr(instr, start) {
Some(fact) => return Some(fact)
None => ()
}
match
RawOiDescriptorCallArgFact::from_branch_free_effectful_block_instr(
instr, start,
) {
Some(fact) => return Some(fact)
None => ()
}
match
RawOiDescriptorCallArgFact::from_no_param_direct_value_call_instr(
module_ctx, instr, start,
) {
Some(fact) => return Some(fact)
None => ()
}
RawOiDescriptorCallArgFact::from_no_param_direct_reference_call_instr(
module_ctx, instr, start,
)
}
///|
fn RawOiDescriptorCallArgFact::from_descriptor_exactification_single(
module_ctx : @ir.HotModuleContext,
instr : @lib.Instruction,
start : Int32,
) -> RawOiDescriptorCallArgFact? {
match RawOiDescriptorCallArgFact::from_nontrapping_leaf_instr(instr, start) {
Some(fact) => return Some(fact)
None => ()
}
RawOiDescriptorCallArgFact::from_no_param_direct_value_call_instr(
module_ctx, instr, start,
)
}
///|
fn RawOiDescriptorCallArgFact::proves_binaryen_operand_preservation(
self : RawOiDescriptorCallArgFact,
) -> Bool {
let represented_kind = match self.kind {
DescriptorArgNontrappingLeaf
| DescriptorArgBranchFreeEffectfulBlock
| DescriptorArgNoParamDirectValueCall
| DescriptorArgNoParamDirectReferenceCall
| DescriptorArgScalarLocalTee
| DescriptorArgPureSelect
| DescriptorArgTrappingScalarBinary
| DescriptorArgParameterizedDirectValueCall
| DescriptorArgParameterizedDirectReferenceCall => true
}
represented_kind && self.end > self.start && self.value_count == 1
}
///|
fn RawOiDescriptorCallArgFact::append_instrs(
self : RawOiDescriptorCallArgFact,
instrs : Array[@lib.Instruction],
out : Array[@lib.Instruction],
) -> Unit {
let mut idx = self.start
while idx < self.end {
out.push(instrs[idx])
idx = idx + 1
}
}
///|
priv enum RawOiDescriptorProducerEndpointKind {
DescriptorProducerEndpointDirectCall
DescriptorProducerEndpointDirectCallRef
DescriptorProducerEndpointTableCallRef
}
///|
// Syntactic descriptor producer endpoint discovered by the reusable value
// graph. This is candidate routing only: family facts still prove the resolved
// signature, target descriptor, exactness, arity, local subtype, and rewrite.
priv struct RawOiDescriptorProducerEndpointFact {
kind : RawOiDescriptorProducerEndpointKind
end : Int32
}
///|
fn RawOiDescriptorProducerEndpointFact::from_position(
instrs : Array[@lib.Instruction],
start : Int32,
) -> RawOiDescriptorProducerEndpointFact? {
if start < 0 || start >= instrs.length() {
return None
}
match instrs[start] {
@lib.Call(_) if start + 1 < instrs.length() =>
match instrs[start + 1] {
@lib.RefCastDescEq(_, _, _) =>
Some({ kind: DescriptorProducerEndpointDirectCall, end: start + 2, })
_ => None
}
@lib.RefFunc(_) if start + 2 < instrs.length() =>
match (instrs[start + 1], instrs[start + 2]) {
(@lib.CallRef(_), @lib.RefCastDescEq(_, _, _)) =>
Some({
kind: DescriptorProducerEndpointDirectCallRef,
end: start + 3,
})
_ => None
}
@lib.TableGet(_) if start + 2 < instrs.length() =>
match (instrs[start + 1], instrs[start + 2]) {
(@lib.CallRef(_), @lib.RefCastDescEq(_, _, _)) =>
Some({ kind: DescriptorProducerEndpointTableCallRef, end: start + 3, })
_ => None
}
_ => None
}
}
///|
// Reusable ordered-value graph for the flat descriptor stack bridge. The graph
// classifies each immutable root once, stores maximal simple runs, and records
// richer typed descriptor chunks plus candidate producer endpoints. Family
// facts may use those runs to discover operands, but exact signature, target,
// nullability, exactness, trap, and rewrite-order proof stays in the consuming
// descriptor fact.
priv struct RawOiDescriptorCallValueGraphFact {
instrs : Array[@lib.Instruction]
local_run_ends : Array[Int32]
nontrapping_run_ends : Array[Int32]
exactification_run_ends : Array[Int32]
descriptor_operand_miss_chunks : Array[RawOiDescriptorCallArgFact?]
descriptor_operand_miss_endpoints : Array[
RawOiDescriptorProducerEndpointFact?,
]
descriptor_operand_miss_run_ends : Array[Int32]
descriptor_operand_miss_value_counts : Array[Int32]
}
///|
fn RawOiDescriptorCallValueGraphFact::from_body(
instrs : Array[@lib.Instruction],
) -> RawOiDescriptorCallValueGraphFact {
let local_run_ends = Array::make(instrs.length() + 1, instrs.length())
let nontrapping_run_ends = Array::make(instrs.length() + 1, instrs.length())
let exactification_run_ends = Array::make(
instrs.length() + 1,
instrs.length(),
)
let descriptor_operand_miss_chunks : Array[RawOiDescriptorCallArgFact?] = Array::make(
instrs.length(),
None,
)
let descriptor_operand_miss_endpoints : Array[
RawOiDescriptorProducerEndpointFact?,
] = Array::make(instrs.length(), None)
let descriptor_operand_miss_run_ends = Array::make(
instrs.length() + 1,
instrs.length(),
)
let descriptor_operand_miss_value_counts = Array::make(instrs.length() + 1, 0)
let mut idx = instrs.length() - 1
while idx >= 0 {
local_run_ends[idx] = match instrs[idx] {
@lib.LocalGet(_) => local_run_ends[idx + 1]
_ => idx
}
let is_nontrapping = RawOiDescriptorCallArgFact::from_nontrapping_leaf_instr(
instrs[idx],
idx,
)
is Some(_)
nontrapping_run_ends[idx] = if is_nontrapping {
nontrapping_run_ends[idx + 1]
} else {
idx
}
exactification_run_ends[idx] = if is_nontrapping {
exactification_run_ends[idx + 1]
} else {
idx
}
idx = idx - 1
}
{
instrs,
local_run_ends,
nontrapping_run_ends,
exactification_run_ends,
descriptor_operand_miss_chunks,
descriptor_operand_miss_endpoints,
descriptor_operand_miss_run_ends,
descriptor_operand_miss_value_counts,
}
}
///|
fn RawOiDescriptorCallValueGraphFact::from_module_body(
instrs : Array[@lib.Instruction],
module_ctx : @ir.HotModuleContext,
) -> RawOiDescriptorCallValueGraphFact {
let graph = RawOiDescriptorCallValueGraphFact::from_body(instrs)
for idx = 0; idx < instrs.length(); idx = idx + 1 {
match
RawOiDescriptorCallArgFact::from_descriptor_operand_miss_chunk(
module_ctx, instrs, idx,
) {
Some(fact) if fact.proves_binaryen_operand_preservation() =>
graph.descriptor_operand_miss_chunks[idx] = Some(fact)
_ =>
match RawOiDescriptorProducerEndpointFact::from_position(instrs, idx) {
Some(endpoint) =>
graph.descriptor_operand_miss_endpoints[idx] = Some(endpoint)
None =>
match
RawOiDescriptorCallArgFact::from_descriptor_operand_miss_single(
module_ctx,
instrs[idx],
idx,
) {
Some(fact) if fact.proves_binaryen_operand_preservation() =>
graph.descriptor_operand_miss_chunks[idx] = Some(fact)
_ => ()
}
}
}
}
let mut idx = instrs.length() - 1
while idx >= 0 {
let is_exactification_operand = RawOiDescriptorCallArgFact::from_descriptor_exactification_single(
module_ctx,
instrs[idx],
idx,
)
is Some(_)
graph.exactification_run_ends[idx] = if is_exactification_operand {
graph.exactification_run_ends[idx + 1]
} else {
idx
}
match graph.descriptor_operand_miss_chunks[idx] {
Some(fact) if fact.end > idx && fact.end <= instrs.length() => {
graph.descriptor_operand_miss_run_ends[idx] = graph.descriptor_operand_miss_run_ends[fact.end]
graph.descriptor_operand_miss_value_counts[idx] = fact.value_count +
graph.descriptor_operand_miss_value_counts[fact.end]
}
_ => {
graph.descriptor_operand_miss_run_ends[idx] = idx
graph.descriptor_operand_miss_value_counts[idx] = 0
}
}
idx = idx - 1
}
graph
}
///|
fn RawOiDescriptorCallValueGraphFact::local_run_end(
self : RawOiDescriptorCallValueGraphFact,
start : Int32,
) -> Int32 {
if start < 0 || start >= self.instrs.length() {
start
} else {
self.local_run_ends[start]
}
}
///|
fn RawOiDescriptorCallValueGraphFact::nontrapping_run_end(
self : RawOiDescriptorCallValueGraphFact,
start : Int32,
) -> Int32 {
if start < 0 || start >= self.instrs.length() {
start
} else {
self.nontrapping_run_ends[start]
}
}
///|
fn RawOiDescriptorCallValueGraphFact::exactification_run_end(
self : RawOiDescriptorCallValueGraphFact,
start : Int32,
) -> Int32 {
if start < 0 || start >= self.instrs.length() {
start
} else {
self.exactification_run_ends[start]
}
}
///|
fn RawOiDescriptorCallValueGraphFact::descriptor_operand_miss_run_end(
self : RawOiDescriptorCallValueGraphFact,
start : Int32,
) -> Int32 {
if start < 0 || start >= self.instrs.length() {
start
} else {
self.descriptor_operand_miss_run_ends[start]
}
}
///|
fn RawOiDescriptorCallValueGraphFact::descriptor_operand_miss_value_count(
self : RawOiDescriptorCallValueGraphFact,
start : Int32,
) -> Int32 {
if start < 0 || start >= self.instrs.length() {
0
} else {
self.descriptor_operand_miss_value_counts[start]
}
}
///|
fn RawOiDescriptorCallValueGraphFact::descriptor_operand_miss_endpoint(
self : RawOiDescriptorCallValueGraphFact,
start : Int32,
) -> RawOiDescriptorProducerEndpointFact? {
if start < 0 || start >= self.instrs.length() {
None
} else {
self.descriptor_operand_miss_endpoints[start]
}
}
///|
fn RawOiDescriptorCallValueGraphFact::instr_args(
self : RawOiDescriptorCallValueGraphFact,
start : Int32,
end : Int32,
) -> Array[@lib.Instruction] {
let args : Array[@lib.Instruction] = []
let mut idx = start
while idx < end && idx < self.instrs.length() {
args.push(self.instrs[idx])
idx = idx + 1
}
args
}
///|
fn RawOiDescriptorCallValueGraphFact::has_local_default_candidate_at(
self : RawOiDescriptorCallValueGraphFact,
start : Int32,
) -> Bool {
if start < 0 || start + 2 >= self.instrs.length() {
return false
}
match (self.instrs[start], self.instrs[start + 1], self.instrs[start + 2]) {
(@lib.LocalGet(_), @lib.StructNewDefault(_), @lib.RefCastDescEq(_, _, _)) =>
true
_ => false
}
}
///|
fn RawOiDescriptorCallValueGraphFact::local_args(
self : RawOiDescriptorCallValueGraphFact,
start : Int32,
end : Int32,
) -> Array[@lib.LocalIdx] {
let args : Array[@lib.LocalIdx] = []
let mut idx = start
while idx < end && idx < self.instrs.length() {
match self.instrs[idx] {
@lib.LocalGet(local_idx) => args.push(local_idx)
_ => return []
}
idx = idx + 1
}
args
}
///|
// Raw descriptor exact-miss producer shape for source-order bridge facts.
// Binaryen v130 `optimizeKnownCastResult` proves a descriptor-cast failure from
// reference strict-subtype facts plus descriptor producer facts, then preserves
// observable producer side effects before replacing the failed cast with
// `unreachable`.
priv enum RawOiDescriptorMissProducerKind {
DescriptorMissProducerDirectCall
DescriptorMissProducerDirectCallRef
DescriptorMissProducerTableCallRef
}
///|
priv struct RawOiDescriptorDirectCallMissFact {
end : Int32
kind : RawOiDescriptorMissProducerKind
ref_func : @lib.FuncIdx
descriptor_func : @lib.FuncIdx?
type_idx : @lib.TypeIdx?
table_idx : @lib.TableIdx?
target : @lib.HeapType
ref_args : Array[@lib.Instruction]
desc_args : Array[@lib.Instruction]
}
///|
fn RawOiDescriptorDirectCallMissFact::from_graph(
graph : RawOiDescriptorCallValueGraphFact,
start : Int32,
module_ctx : @ir.HotModuleContext,
) -> RawOiDescriptorDirectCallMissFact? {
let ref_end = graph.nontrapping_run_end(start)
if ref_end >= graph.instrs.length() {
return None
}
let ref_func = match graph.instrs[ref_end] {
@lib.Call(ref_func) => ref_func
_ => return None
}
let desc_start = ref_end + 1
let desc_end = graph.nontrapping_run_end(desc_start)
if desc_end >= graph.instrs.length() {
return None
}
let ref_args = graph.instr_args(start, ref_end)
let desc_args = graph.instr_args(desc_start, desc_end)
match graph.instrs[desc_end] {
@lib.Call(descriptor_func) if desc_end + 1 < graph.instrs.length() =>
match graph.instrs[desc_end + 1] {
@lib.RefCastDescEq(_, _, target) if RawOiReferenceReturnFact::direct_call_proves_non_null_strict_subtype_with_param_count(
module_ctx,
ref_func,
target,
ref_args.length(),
) &&
RawOiDescriptorDescribesFact::direct_call_exactly_describes_with_param_count(
module_ctx,
descriptor_func,
target,
desc_args.length(),
) =>
Some({
end: desc_end + 2,
kind: DescriptorMissProducerDirectCall,
ref_func,
descriptor_func: Some(descriptor_func),
type_idx: None,
table_idx: None,
target,
ref_args,
desc_args,
})
_ => None
}
@lib.RefFunc(descriptor_func) if desc_end + 2 < graph.instrs.length() =>
match (graph.instrs[desc_end + 1], graph.instrs[desc_end + 2]) {
(@lib.CallRef(type_idx), @lib.RefCastDescEq(_, _, target)) if RawOiReferenceReturnFact::direct_call_proves_non_null_strict_subtype_with_param_count(
module_ctx,
ref_func,
target,
ref_args.length(),
) &&
RawOiDescriptorDescribesFact::direct_call_exactly_describes_with_param_count(
module_ctx,
descriptor_func,
target,
desc_args.length(),
) &&
RawOiDescriptorDescribesFact::call_ref_type_exactly_describes_with_param_count(
module_ctx,
type_idx,
target,
desc_args.length(),
) =>
Some({
end: desc_end + 3,
kind: DescriptorMissProducerDirectCallRef,
ref_func,
descriptor_func: Some(descriptor_func),
type_idx: Some(type_idx),
table_idx: None,
target,
ref_args,
desc_args,
})
_ => None
}
@lib.TableGet(table_idx) if desc_args.length() > 0 &&
desc_end + 2 < graph.instrs.length() =>
match (graph.instrs[desc_end + 1], graph.instrs[desc_end + 2]) {
(@lib.CallRef(type_idx), @lib.RefCastDescEq(_, _, target)) if RawOiReferenceReturnFact::direct_call_proves_non_null_strict_subtype_with_param_count(
module_ctx,
ref_func,
target,
ref_args.length(),
) &&
RawOiDescriptorDescribesFact::call_ref_type_exactly_describes_with_param_count(
module_ctx,
type_idx,
target,
desc_args.length() - 1,
) =>
Some({
end: desc_end + 3,
kind: DescriptorMissProducerTableCallRef,
ref_func,
descriptor_func: None,
type_idx: Some(type_idx),
table_idx: Some(table_idx),
target,
ref_args,
desc_args,
})
_ => None
}
_ => None
}
}
///|
fn RawOiDescriptorDirectCallMissFact::proves_binaryen_descriptor_miss(
self : RawOiDescriptorDirectCallMissFact,
) -> Bool {
ignore(self.ref_func)
ignore(self.target)
self.end > 0 &&
(match self.kind {
DescriptorMissProducerDirectCall | DescriptorMissProducerDirectCallRef =>
self.descriptor_func is Some(_)
DescriptorMissProducerTableCallRef =>
self.type_idx is Some(_) && self.table_idx is Some(_)
})
}
///|
fn RawOiDescriptorDirectCallMissFact::append_rewrite(
self : RawOiDescriptorDirectCallMissFact,
rewritten : Array[@lib.Instruction],
) -> Unit {
if !self.proves_binaryen_descriptor_miss() {
return
}
for arg in self.ref_args {
rewritten.push(arg)
}
rewritten.push(@lib.Instruction::call(self.ref_func))
rewritten.push(@lib.Instruction::drop())
for arg in self.desc_args {
rewritten.push(arg)
}
match self.kind {
DescriptorMissProducerDirectCall | DescriptorMissProducerDirectCallRef =>
match self.descriptor_func {
Some(descriptor_func) =>
rewritten.push(@lib.Instruction::call(descriptor_func))
None => return
}
DescriptorMissProducerTableCallRef =>
match (self.type_idx, self.table_idx) {
(Some(type_idx), Some(table_idx)) =>
rewritten.push(@lib.Instruction::call_indirect(type_idx, table_idx))
_ => return
}
}
rewritten.push(@lib.Instruction::drop())
rewritten.push(@lib.Instruction::unreachable_())
}
///|
priv struct RawOiDescriptorLocalOperandMissFact {
end : Int32
kind : RawOiDescriptorMissProducerKind
local_idx : @lib.LocalIdx
descriptor_func : @lib.FuncIdx?
type_idx : @lib.TypeIdx?
table_idx : @lib.TableIdx?
target : @lib.HeapType
desc_value_count : Int32
desc_args : Array[@lib.Instruction]
}
///|
fn RawOiDescriptorLocalOperandMissFact::from_graph(
graph : RawOiDescriptorCallValueGraphFact,
start : Int32,
module_ctx : @ir.HotModuleContext,
params : Array[@lib.ValType],
locals : @lib.Locals,
) -> RawOiDescriptorLocalOperandMissFact? {
if start < 0 || start >= graph.instrs.length() {
return None
}
let local_idx = match graph.instrs[start] {
@lib.LocalGet(local_idx) => local_idx
_ => return None
}
let desc_start = start + 1
let scan = graph.descriptor_operand_miss_run_end(desc_start)
let desc_arg_count = graph.descriptor_operand_miss_value_count(desc_start)
let desc_args = graph.instr_args(desc_start, scan)
let endpoint = match graph.descriptor_operand_miss_endpoint(scan) {
Some(endpoint) => endpoint
None => return None
}
match endpoint.kind {
DescriptorProducerEndpointDirectCall =>
match (graph.instrs[scan], graph.instrs[scan + 1]) {
(@lib.Call(descriptor_func), @lib.RefCastDescEq(_, _, target)) if RawOiDescriptorDescribesFact::direct_call_exactly_describes_with_param_count(
module_ctx, descriptor_func, target, desc_arg_count,
) &&
RawOiLocalReferenceFact::local_proves_non_null_strict_subtype(
module_ctx, params, locals, local_idx, target,
) =>
Some({
end: endpoint.end,
kind: DescriptorMissProducerDirectCall,
local_idx,
descriptor_func: Some(descriptor_func),
type_idx: None,
table_idx: None,
target,
desc_value_count: desc_arg_count,
desc_args,
})
_ => None
}
DescriptorProducerEndpointDirectCallRef =>
match
(graph.instrs[scan], graph.instrs[scan + 1], graph.instrs[scan + 2]) {
(
@lib.RefFunc(descriptor_func),
@lib.CallRef(type_idx),
@lib.RefCastDescEq(_, _, target),
) if RawOiDescriptorDescribesFact::direct_call_exactly_describes_with_param_count(
module_ctx, descriptor_func, target, desc_arg_count,
) &&
RawOiDescriptorDescribesFact::call_ref_type_exactly_describes_with_param_count(
module_ctx, type_idx, target, desc_arg_count,
) &&
RawOiLocalReferenceFact::local_proves_non_null_strict_subtype(
module_ctx, params, locals, local_idx, target,
) =>
Some({
end: endpoint.end,
kind: DescriptorMissProducerDirectCallRef,
local_idx,
descriptor_func: Some(descriptor_func),
type_idx: Some(type_idx),
table_idx: None,
target,
desc_value_count: desc_arg_count,
desc_args,
})
_ => None
}
DescriptorProducerEndpointTableCallRef =>
if desc_arg_count <= 0 {
None
} else {
match
(graph.instrs[scan], graph.instrs[scan + 1], graph.instrs[scan + 2]) {
(
@lib.TableGet(table_idx),
@lib.CallRef(type_idx),
@lib.RefCastDescEq(_, _, target),
) if RawOiDescriptorDescribesFact::call_ref_type_exactly_describes_with_param_count(
module_ctx,
type_idx,
target,
desc_arg_count - 1,
) &&
RawOiLocalReferenceFact::local_proves_non_null_strict_subtype(
module_ctx, params, locals, local_idx, target,
) =>
Some({
end: endpoint.end,
kind: DescriptorMissProducerTableCallRef,
local_idx,
descriptor_func: None,
type_idx: Some(type_idx),
table_idx: Some(table_idx),
target,
desc_value_count: desc_arg_count,
desc_args,
})
_ => None
}
}
}
}
///|
fn RawOiDescriptorLocalOperandMissFact::proves_binaryen_descriptor_miss(
self : RawOiDescriptorLocalOperandMissFact,
) -> Bool {
ignore(self.local_idx)
ignore(self.target)
self.end > 0 &&
self.desc_value_count >= 0 &&
(match self.kind {
DescriptorMissProducerDirectCall | DescriptorMissProducerDirectCallRef =>
self.descriptor_func is Some(_)
DescriptorMissProducerTableCallRef =>
self.type_idx is Some(_) && self.table_idx is Some(_)
})
}
///|
fn RawOiDescriptorLocalOperandMissFact::append_rewrite(
self : RawOiDescriptorLocalOperandMissFact,
rewritten : Array[@lib.Instruction],
) -> Unit {
if !self.proves_binaryen_descriptor_miss() {
return
}
for arg in self.desc_args {
rewritten.push(arg)
}
match self.kind {
DescriptorMissProducerDirectCall | DescriptorMissProducerDirectCallRef =>
match self.descriptor_func {
Some(descriptor_func) =>
rewritten.push(@lib.Instruction::call(descriptor_func))
None => return
}
DescriptorMissProducerTableCallRef =>
match (self.type_idx, self.table_idx) {
(Some(type_idx), Some(table_idx)) =>
rewritten.push(@lib.Instruction::call_indirect(type_idx, table_idx))
_ => return
}
}
rewritten.push(@lib.Instruction::drop())
rewritten.push(@lib.Instruction::unreachable_())
}
///|
///|
// Raw exact-descriptor success fact for balanced local-argument direct calls.
// Binaryen v130 `optimizeKnownCastResult` removes an exact non-null descriptor
// cast when the reference producer already returns the exact target and the
// descriptor producer exactly describes that target; preserve both producer
// calls and only drop the now-unused descriptor value.
priv struct RawOiDescriptorBalancedLocalDirectExactSuccessFact {
end : Int32
param_count : Int32
ref_func : @lib.FuncIdx
descriptor_func : @lib.FuncIdx
target : @lib.HeapType
ref_args : Array[@lib.LocalIdx]
desc_args : Array[@lib.LocalIdx]
}
///|
fn RawOiDescriptorBalancedLocalDirectExactSuccessFact::from_graph(
graph : RawOiDescriptorCallValueGraphFact,
start : Int32,
module_ctx : @ir.HotModuleContext,
) -> RawOiDescriptorBalancedLocalDirectExactSuccessFact? {
let ref_end = graph.local_run_end(start)
if ref_end >= graph.instrs.length() {
return None
}
let ref_func = match graph.instrs[ref_end] {
@lib.Call(ref_func) => ref_func
_ => return None
}
let desc_start = ref_end + 1
let desc_end = graph.local_run_end(desc_start)
if desc_end + 1 >= graph.instrs.length() {
return None
}
let descriptor_func = match graph.instrs[desc_end] {
@lib.Call(descriptor_func) => descriptor_func
_ => return None
}
let ref_args = graph.local_args(start, ref_end)
let desc_args = graph.local_args(desc_start, desc_end)
match graph.instrs[desc_end + 1] {
@lib.RefCastDescEq(nullable, exact, target) if !nullable &&
exact &&
RawOiReferenceReturnFact::direct_call_proves_non_null_exact_target_with_param_count(
module_ctx,
ref_func,
target,
ref_args.length(),
) &&
RawOiDescriptorDescribesFact::direct_call_exactly_describes_with_param_count(
module_ctx,
descriptor_func,
target,
desc_args.length(),
) =>
Some({
end: desc_end + 2,
param_count: ref_args.length(),
ref_func,
descriptor_func,
target,
ref_args,
desc_args,
})
_ => None
}
}
///|
fn RawOiDescriptorBalancedLocalDirectExactSuccessFact::proves_binaryen_exact_success(
self : RawOiDescriptorBalancedLocalDirectExactSuccessFact,
) -> Bool {
ignore(self.ref_func)
ignore(self.descriptor_func)
ignore(self.target)
self.param_count == self.ref_args.length() && self.end > 0
}
///|
fn RawOiDescriptorBalancedLocalDirectExactSuccessFact::append_rewrite(
self : RawOiDescriptorBalancedLocalDirectExactSuccessFact,
rewritten : Array[@lib.Instruction],
) -> Unit {
if !self.proves_binaryen_exact_success() {
return
}
for arg in self.ref_args {
rewritten.push(@lib.Instruction::local_get(arg))
}
rewritten.push(@lib.Instruction::call(self.ref_func))
for arg in self.desc_args {
rewritten.push(@lib.Instruction::local_get(arg))
}
rewritten.push(@lib.Instruction::call(self.descriptor_func))
rewritten.push(@lib.Instruction::drop())
}
///|
// Raw typed-table exact descriptor success facts. Binaryen v130 removes
// exact descriptor casts when the reference producer is known exact and the
// descriptor producer exactly describes the same target; Starshine preserves
// the producer calls and drops only the now-unused descriptor result.
priv enum RawOiDescriptorTnhExactSuccessKind {
DescriptorTnhIndirectReferenceDirectDescriptor
DescriptorTnhDirectReferenceIndirectDescriptor
}
///|
priv struct RawOiDescriptorTnhCallIndirectExactSuccessFact {
end : Int32
kind : RawOiDescriptorTnhExactSuccessKind
ref_func : @lib.FuncIdx?
descriptor_func : @lib.FuncIdx?
type_idx : @lib.TypeIdx
table_idx : @lib.TableIdx
target : @lib.HeapType
ref_inputs : Array[@lib.Instruction]
desc_inputs : Array[@lib.Instruction]
}
///|
fn RawOiDescriptorTnhCallIndirectExactSuccessFact::from_indirect_reference_instrs(
instrs : Array[@lib.Instruction],
start : Int32,
module_ctx : @ir.HotModuleContext,
) -> RawOiDescriptorTnhCallIndirectExactSuccessFact? {
let ref_inputs : Array[@lib.Instruction] = []
let mut scan = start
while scan < instrs.length() {
match
RawOiDescriptorCallArgFact::from_nontrapping_leaf_instr(
instrs[scan],
scan,
) {
Some(fact) if fact.proves_binaryen_operand_preservation() => {
fact.append_instrs(instrs, ref_inputs)
scan = fact.end
continue
}
_ => ()
}
match instrs[scan] {
@lib.CallIndirect(type_idx, table_idx) if ref_inputs.length() > 0 &&
scan + 2 < instrs.length() => {
let ref_param_count = ref_inputs.length() - 1
match (instrs[scan + 1], instrs[scan + 2]) {
(
@lib.Call(descriptor_func),
@lib.RefCastDescEq(nullable, exact, target),
) if !nullable &&
exact &&
RawOiReferenceReturnFact::call_ref_type_proves_non_null_exact_target_with_param_count(
module_ctx, type_idx, target, ref_param_count,
) &&
RawOiDescriptorDescribesFact::direct_call_exactly_describes_with_param_count(
module_ctx, descriptor_func, target, 0,
) =>
return Some({
end: scan + 3,
kind: DescriptorTnhIndirectReferenceDirectDescriptor,
ref_func: None,
descriptor_func: Some(descriptor_func),
type_idx,
table_idx,
target,
ref_inputs,
desc_inputs: [],
})
_ => return None
}
}
_ => return None
}
}
None
}
///|
fn RawOiDescriptorTnhCallIndirectExactSuccessFact::from_indirect_descriptor_instrs(
instrs : Array[@lib.Instruction],
start : Int32,
module_ctx : @ir.HotModuleContext,
) -> RawOiDescriptorTnhCallIndirectExactSuccessFact? {
let ref_inputs : Array[@lib.Instruction] = []
let mut scan = start
while scan < instrs.length() {
match
RawOiDescriptorCallArgFact::from_nontrapping_leaf_instr(
instrs[scan],
scan,
) {
Some(fact) if fact.proves_binaryen_operand_preservation() => {
fact.append_instrs(instrs, ref_inputs)
scan = fact.end
continue
}
_ => ()
}
match instrs[scan] {
@lib.Call(ref_func) => {
let desc_inputs : Array[@lib.Instruction] = []
let mut desc_scan = scan + 1
while desc_scan < instrs.length() {
match
RawOiDescriptorCallArgFact::from_nontrapping_leaf_instr(
instrs[desc_scan],
desc_scan,
) {
Some(fact) if fact.proves_binaryen_operand_preservation() => {
fact.append_instrs(instrs, desc_inputs)
desc_scan = fact.end
continue
}
_ => ()
}
match instrs[desc_scan] {
@lib.CallIndirect(type_idx, table_idx) if desc_inputs.length() > 0 &&
desc_scan + 1 < instrs.length() =>
match instrs[desc_scan + 1] {
@lib.RefCastDescEq(nullable, exact, target) if !nullable &&
exact &&
RawOiReferenceReturnFact::direct_call_proves_non_null_exact_target_with_param_count(
module_ctx,
ref_func,
target,
ref_inputs.length(),
) &&
RawOiDescriptorDescribesFact::call_ref_type_exactly_describes_with_param_count(
module_ctx,
type_idx,
target,
desc_inputs.length() - 1,
) =>
return Some({
end: desc_scan + 2,
kind: DescriptorTnhDirectReferenceIndirectDescriptor,
ref_func: Some(ref_func),
descriptor_func: None,
type_idx,
table_idx,
target,
ref_inputs,
desc_inputs,
})
_ => return None
}
_ => return None
}
}
return None
}
_ => return None
}
}
None
}
///|
fn RawOiDescriptorTnhCallIndirectExactSuccessFact::proves_binaryen_exact_success(
self : RawOiDescriptorTnhCallIndirectExactSuccessFact,
) -> Bool {
ignore(self.target)
self.end > 0 &&
(match self.kind {
DescriptorTnhIndirectReferenceDirectDescriptor =>
self.descriptor_func is Some(_) && self.ref_inputs.length() > 0
DescriptorTnhDirectReferenceIndirectDescriptor =>
self.ref_func is Some(_) && self.desc_inputs.length() > 0
})
}
///|
fn RawOiDescriptorTnhCallIndirectExactSuccessFact::append_rewrite(
self : RawOiDescriptorTnhCallIndirectExactSuccessFact,
rewritten : Array[@lib.Instruction],
) -> Unit {
if !self.proves_binaryen_exact_success() {
return
}
match self.kind {
DescriptorTnhIndirectReferenceDirectDescriptor => {
for input in self.ref_inputs {
rewritten.push(input)
}
rewritten.push(
@lib.Instruction::call_indirect(self.type_idx, self.table_idx),
)
match self.descriptor_func {
Some(descriptor_func) =>
rewritten.push(@lib.Instruction::call(descriptor_func))
None => return
}
rewritten.push(@lib.Instruction::drop())
}
DescriptorTnhDirectReferenceIndirectDescriptor => {
for input in self.ref_inputs {
rewritten.push(input)
}
match self.ref_func {
Some(ref_func) => rewritten.push(@lib.Instruction::call(ref_func))
None => return
}
for input in self.desc_inputs {
rewritten.push(input)
}
rewritten.push(
@lib.Instruction::call_indirect(self.type_idx, self.table_idx),
)
rewritten.push(@lib.Instruction::drop())
}
}
}
///|
///|
// Accepted raw OI-J descriptor bridge boundary. The body pass is a narrow
// fact-consumer over `RawOiDescriptorTnhCallIndirectExactSuccessFact` variants;
// it must not grow independent typed-table or exact-success pattern logic.
fn run_hot_pipeline_raw_optimize_instructions_tnh_call_indirect_exact_descriptor_success_body(
instrs : Array[@lib.Instruction],
module_ctx : @ir.HotModuleContext,
) -> (Array[@lib.Instruction], Bool) {
let rewritten : Array[@lib.Instruction] = []
let mut changed = false
let mut i = 0
while i < instrs.length() {
match
RawOiDescriptorTnhCallIndirectExactSuccessFact::from_indirect_descriptor_instrs(
instrs, i, module_ctx,
) {
Some(fact) if fact.proves_binaryen_exact_success() => {
fact.append_rewrite(rewritten)
changed = true
i = fact.end
continue
}
_ => ()
}
match
RawOiDescriptorTnhCallIndirectExactSuccessFact::from_indirect_reference_instrs(
instrs, i, module_ctx,
) {
Some(fact) if fact.proves_binaryen_exact_success() => {
fact.append_rewrite(rewritten)
changed = true
i = fact.end
continue
}
_ => ()
}
rewritten.push(instrs[i])
i = i + 1
}
(rewritten, changed)
}
///|
// Raw direct-call descriptor exactification fact. Binaryen v130
// `optimizeKnownCastResult` strengthens nullable or inexact descriptor casts
// when both the reference value and descriptor producer are known to target the
// same described heap type.
priv struct RawOiDescriptorDirectCallExactificationFact {
end : Int32
ref_func : @lib.FuncIdx
descriptor_func : @lib.FuncIdx
target : @lib.HeapType
result_nullable : Bool
ref_args : Array[@lib.Instruction]
desc_args : Array[@lib.Instruction]
}
///|
fn RawOiDescriptorDirectCallExactificationFact::from_graph(
graph : RawOiDescriptorCallValueGraphFact,
start : Int32,
module_ctx : @ir.HotModuleContext,
) -> RawOiDescriptorDirectCallExactificationFact? {
let ref_end = graph.exactification_run_end(start)
if ref_end >= graph.instrs.length() {
return None
}
let ref_func = match graph.instrs[ref_end] {
@lib.Call(ref_func) => ref_func
_ => return None
}
let desc_start = ref_end + 1
let desc_end = graph.exactification_run_end(desc_start)
if desc_end >= graph.instrs.length() {
return None
}
let ref_args = graph.instr_args(start, ref_end)
let desc_args = graph.instr_args(desc_start, desc_end)
match graph.instrs[desc_end] {
@lib.Call(descriptor_func) if desc_end + 1 < graph.instrs.length() =>
match graph.instrs[desc_end + 1] {
@lib.RefCastDescEq(nullable, exact, target) if (nullable || !exact) &&
RawOiReferenceReturnFact::direct_call_proves_non_null_target_with_param_count(
module_ctx,
ref_func,
target,
ref_args.length(),
) &&
RawOiDescriptorDescribesFact::direct_call_exactly_describes_with_param_count(
module_ctx,
descriptor_func,
target,
desc_args.length(),
) =>
Some({
end: desc_end + 2,
ref_func,
descriptor_func,
target,
result_nullable: false,
ref_args,
desc_args,
})
_ => None
}
@lib.RefFunc(descriptor_func) if desc_end + 2 < graph.instrs.length() =>
match (graph.instrs[desc_end + 1], graph.instrs[desc_end + 2]) {
(@lib.CallRef(type_idx), @lib.RefCastDescEq(nullable, exact, target)) => {
let non_null_fact = RawOiReferenceReturnFact::direct_call_proves_non_null_target_with_param_count(
module_ctx,
ref_func,
target,
ref_args.length(),
)
let nullable_fact = RawOiReferenceReturnFact::direct_call_proves_nullable_target_with_param_count(
module_ctx,
ref_func,
target,
ref_args.length(),
)
if (nullable || !exact) &&
(non_null_fact || (nullable && !exact && nullable_fact)) &&
RawOiDescriptorDescribesFact::direct_call_exactly_describes_with_param_count(
module_ctx,
descriptor_func,
target,
desc_args.length(),
) &&
RawOiDescriptorDescribesFact::call_ref_type_exactly_describes_with_param_count(
module_ctx,
type_idx,
target,
desc_args.length(),
) {
Some({
end: desc_end + 3,
ref_func,
descriptor_func,
target,
result_nullable: !non_null_fact,
ref_args,
desc_args,
})
} else {
None
}
}
_ => None
}
_ => None
}
}
///|
fn RawOiDescriptorDirectCallExactificationFact::proves_binaryen_exactification(
self : RawOiDescriptorDirectCallExactificationFact,
) -> Bool {
ignore(self.ref_func)
ignore(self.descriptor_func)
ignore(self.target)
self.end > 0
}
///|
fn RawOiDescriptorDirectCallExactificationFact::append_rewrite(
self : RawOiDescriptorDirectCallExactificationFact,
rewritten : Array[@lib.Instruction],
) -> Unit {
if !self.proves_binaryen_exactification() {
return
}
for arg in self.ref_args {
rewritten.push(arg)
}
rewritten.push(@lib.Instruction::call(self.ref_func))
for arg in self.desc_args {
rewritten.push(arg)
}
rewritten.push(@lib.Instruction::call(self.descriptor_func))
rewritten.push(
@lib.Instruction::ref_cast_desc_eq_type(
@lib.RefType::new(self.result_nullable, self.target, exact=true),
),
)
}
///|
///|
// Accepted raw OI-J descriptor bridge boundary. The body pass only sequences
// `RawOiDescriptorBalancedLocalDirectExactSuccessFact` rewrites for the covered
// exact-success direct-call subset; broader success facts belong in that fact
// family or the visitor-level descriptor facts, not in this loop.
fn run_hot_pipeline_raw_optimize_instructions_exact_descriptor_success_body(
graph : RawOiDescriptorCallValueGraphFact,
module_ctx : @ir.HotModuleContext,
) -> (Array[@lib.Instruction], Bool) {
let instrs = graph.instrs
let rewritten : Array[@lib.Instruction] = []
let mut changed = false
let mut i = 0
while i < instrs.length() {
match
RawOiDescriptorBalancedLocalDirectExactSuccessFact::from_graph(
graph, i, module_ctx,
) {
Some(fact) if fact.proves_binaryen_exact_success() => {
fact.append_rewrite(rewritten)
changed = true
i = fact.end
continue
}
_ => ()
}
rewritten.push(instrs[i])
i = i + 1
}
(rewritten, changed)
}
///|
// Accepted raw OI-J descriptor bridge boundary. The body pass only sequences
// `RawOiDescriptorDirectCallExactificationFact` rewrites for the covered
// direct-call exactification subset; any new proof fields should live in that
// fact or in shared descriptor type/producer facts.
fn run_hot_pipeline_raw_optimize_instructions_exact_descriptor_exactification_body(
graph : RawOiDescriptorCallValueGraphFact,
module_ctx : @ir.HotModuleContext,
) -> (Array[@lib.Instruction], Bool) {
let instrs = graph.instrs
let rewritten : Array[@lib.Instruction] = []
let mut changed = false
let mut i = 0
while i < instrs.length() {
match
RawOiDescriptorDirectCallExactificationFact::from_graph(
graph, i, module_ctx,
) {
Some(fact) if fact.proves_binaryen_exactification() => {
fact.append_rewrite(rewritten)
changed = true
i = fact.end
continue
}
_ => ()
}
rewritten.push(instrs[i])
i = i + 1
}
(rewritten, changed)
}
/// Raw local reference facts for descriptor exact-miss bridges. Binaryen's
/// `optimizeKnownCastResult` decides known descriptor-cast failure from the
/// value type's heap/nullability relation against the cast target; the raw
/// dispatcher only has local indices here, so materialize the rewritten local
/// type once as a fact before descriptor-miss consumers ask for a strict
/// non-null subtype proof.
///|
priv struct RawOiLocalReferenceFact {
local_idx : @lib.LocalIdx
heap_type : @lib.HeapType
nullable : Bool
}
///|
fn RawOiLocalReferenceFact::from_rewritten_local_type(
params : Array[@lib.ValType],
locals : @lib.Locals,
local_idx : @lib.LocalIdx,
) -> RawOiLocalReferenceFact? {
let @lib.LocalIdx(raw) = local_idx
let val_type = match
run_hot_pipeline_raw_rewritten_local_type(
params,
locals,
[],
raw.reinterpret_as_int(),
) {
Some(val_type) => val_type
None => return None
}
match val_type {
@lib.RefTypeValType(ref_type) =>
Some({
local_idx,
heap_type: ref_type.get_heap_type(),
nullable: ref_type.is_nullable(),
})
_ => None
}
}
///|
fn RawOiLocalReferenceFact::is_represented_local_reference(
self : RawOiLocalReferenceFact,
) -> Bool {
let @lib.LocalIdx(_) = self.local_idx
true
}
///|
fn RawOiLocalReferenceFact::proves_binaryen_non_null_strict_subtype(
self : RawOiLocalReferenceFact,
module_ctx : @ir.HotModuleContext,
target : @lib.HeapType,
) -> Bool {
self.is_represented_local_reference() &&
!self.nullable &&
self.heap_type != target &&
run_hot_pipeline_raw_heap_type_is_subtype(
module_ctx,
self.heap_type,
target,
64,
)
}
///|
fn RawOiLocalReferenceFact::local_proves_non_null_strict_subtype(
module_ctx : @ir.HotModuleContext,
params : Array[@lib.ValType],
locals : @lib.Locals,
local_idx : @lib.LocalIdx,
target : @lib.HeapType,
) -> Bool {
match
RawOiLocalReferenceFact::from_rewritten_local_type(
params, locals, local_idx,
) {
Some(fact) =>
fact.proves_binaryen_non_null_strict_subtype(module_ctx, target)
None => false
}
}
///|
// Raw descriptor exact-miss fact for the local/default-descriptor bridge. This
// represents the flat fallback shape `local.get; struct.new_default;
// ref.cast_desc exact non-null` as a proof object instead of leaving the
// dispatcher to own an inline raw triple match.
priv struct RawOiDescriptorLocalDefaultMissFact {
end : Int32
local_idx : @lib.LocalIdx
descriptor_type : @lib.TypeIdx
target : @lib.HeapType
}
///|
fn RawOiDescriptorLocalDefaultMissFact::from_graph(
graph : RawOiDescriptorCallValueGraphFact,
start : Int32,
module_ctx : @ir.HotModuleContext,
params : Array[@lib.ValType],
locals : @lib.Locals,
) -> RawOiDescriptorLocalDefaultMissFact? {
if !graph.has_local_default_candidate_at(start) {
return None
}
match
(graph.instrs[start], graph.instrs[start + 1], graph.instrs[start + 2]) {
(
@lib.LocalGet(local_idx),
@lib.StructNewDefault(descriptor_type),
@lib.RefCastDescEq(nullable, exact, target),
) if exact &&
!nullable &&
RawOiDescriptorDescribesFact::struct_new_exactly_describes(
module_ctx, descriptor_type, target,
) &&
RawOiLocalReferenceFact::local_proves_non_null_strict_subtype(
module_ctx, params, locals, local_idx, target,
) => Some({ end: start + 3, local_idx, descriptor_type, target, })
_ => None
}
}
///|
fn RawOiDescriptorLocalDefaultMissFact::proves_binaryen_descriptor_miss(
self : RawOiDescriptorLocalDefaultMissFact,
) -> Bool {
ignore(self.local_idx)
ignore(self.descriptor_type)
ignore(self.target)
self.end > 0
}
///|
fn RawOiDescriptorLocalDefaultMissFact::append_rewrite(
self : RawOiDescriptorLocalDefaultMissFact,
rewritten : Array[@lib.Instruction],
) -> Unit {
if self.proves_binaryen_descriptor_miss() {
rewritten.push(@lib.Instruction::unreachable_())
}
}
///|
// Raw descriptor exact-miss fact for balanced local-argument direct calls. The
// earlier fallback carried separate ad hoc windows for arities 0..5; keep the
// same arity cap and rewrite shape in one source-backed fact matching
// Binaryen's known descriptor-cast failure preservation of both operand calls.
priv struct RawOiDescriptorBalancedLocalDirectMissFact {
end : Int32
param_count : Int32
ref_func : @lib.FuncIdx
descriptor_func : @lib.FuncIdx
target : @lib.HeapType
ref_args : Array[@lib.LocalIdx]
desc_args : Array[@lib.LocalIdx]
}
///|
fn RawOiDescriptorBalancedLocalDirectMissFact::from_graph(
graph : RawOiDescriptorCallValueGraphFact,
start : Int32,
module_ctx : @ir.HotModuleContext,
) -> RawOiDescriptorBalancedLocalDirectMissFact? {
let ref_end = graph.local_run_end(start)
let ref_args = graph.local_args(start, ref_end)
if ref_args.length() > 5 || ref_end >= graph.instrs.length() {
return None
}
let ref_func = match graph.instrs[ref_end] {
@lib.Call(func_idx) => func_idx
_ => return None
}
let desc_start = ref_end + 1
let desc_end = graph.local_run_end(desc_start)
let desc_args = graph.local_args(desc_start, desc_end)
if desc_args.length() > 5 ||
ref_args.length() != desc_args.length() ||
desc_end + 1 >= graph.instrs.length() {
return None
}
let descriptor_func = match graph.instrs[desc_end] {
@lib.Call(func_idx) => func_idx
_ => return None
}
match graph.instrs[desc_end + 1] {
@lib.RefCastDescEq(_, _, target) if RawOiReferenceReturnFact::direct_call_proves_non_null_strict_subtype_with_param_count(
module_ctx,
ref_func,
target,
ref_args.length(),
) &&
RawOiDescriptorDescribesFact::direct_call_exactly_describes_with_param_count(
module_ctx,
descriptor_func,
target,
desc_args.length(),
) =>
Some({
end: desc_end + 2,
param_count: ref_args.length(),
ref_func,
descriptor_func,
target,
ref_args,
desc_args,
})
_ => None
}
}
///|
fn RawOiDescriptorBalancedLocalDirectMissFact::proves_binaryen_descriptor_miss(
self : RawOiDescriptorBalancedLocalDirectMissFact,
) -> Bool {
ignore(self.ref_func)
ignore(self.descriptor_func)
ignore(self.target)
self.param_count == self.ref_args.length() &&
self.param_count == self.desc_args.length() &&
self.param_count <= 5 &&
self.end > 0
}
///|
fn RawOiDescriptorBalancedLocalDirectMissFact::append_rewrite(
self : RawOiDescriptorBalancedLocalDirectMissFact,
rewritten : Array[@lib.Instruction],
) -> Unit {
if !self.proves_binaryen_descriptor_miss() {
return
}
for arg in self.ref_args {
rewritten.push(@lib.Instruction::local_get(arg))
}
rewritten.push(@lib.Instruction::call(self.ref_func))
rewritten.push(@lib.Instruction::drop())
for arg in self.desc_args {
rewritten.push(@lib.Instruction::local_get(arg))
}
rewritten.push(@lib.Instruction::call(self.descriptor_func))
rewritten.push(@lib.Instruction::drop())
rewritten.push(@lib.Instruction::unreachable_())
}
///|
// Raw descriptor exact-miss fact for the nontrapping descriptor-call operand
// bridge. Binaryen v130 `optimizeKnownCastResult` preserves descriptor operand
// effects before replacing a known-failing descriptor cast with `unreachable`;
// this fact keeps the narrow raw spelling (`local.get`; nontrapping descriptor
// args; direct descriptor call; `ref.cast_desc`) out of the dispatcher body.
priv struct RawOiDescriptorLocalCallOperandMissFact {
end : Int32
local_idx : @lib.LocalIdx
descriptor_func : @lib.FuncIdx
target : @lib.HeapType
desc_args : Array[@lib.Instruction]
}
///|
fn RawOiDescriptorLocalCallOperandMissFact::from_graph(
graph : RawOiDescriptorCallValueGraphFact,
start : Int32,
module_ctx : @ir.HotModuleContext,
params : Array[@lib.ValType],
locals : @lib.Locals,
) -> RawOiDescriptorLocalCallOperandMissFact? {
if start >= graph.instrs.length() {
return None
}
let local_idx = match graph.instrs[start] {
@lib.LocalGet(local_idx) => local_idx
_ => return None
}
let desc_start = start + 1
let desc_end = graph.nontrapping_run_end(desc_start)
if desc_end + 1 >= graph.instrs.length() {
return None
}
let desc_args = graph.instr_args(desc_start, desc_end)
let descriptor_func = match graph.instrs[desc_end] {
@lib.Call(descriptor_func) => descriptor_func
_ => return None
}
match graph.instrs[desc_end + 1] {
@lib.RefCastDescEq(_, _, target) if RawOiDescriptorDescribesFact::direct_call_exactly_describes_with_param_count(
module_ctx,
descriptor_func,
target,
desc_args.length(),
) &&
RawOiLocalReferenceFact::local_proves_non_null_strict_subtype(
module_ctx, params, locals, local_idx, target,
) =>
Some({ end: desc_end + 2, local_idx, descriptor_func, target, desc_args, })
_ => None
}
}
///|
fn RawOiDescriptorLocalCallOperandMissFact::proves_binaryen_descriptor_miss(
self : RawOiDescriptorLocalCallOperandMissFact,
) -> Bool {
ignore(self.local_idx)
ignore(self.target)
self.end > 0
}
///|
fn RawOiDescriptorLocalCallOperandMissFact::append_rewrite(
self : RawOiDescriptorLocalCallOperandMissFact,
rewritten : Array[@lib.Instruction],
) -> Unit {
if !self.proves_binaryen_descriptor_miss() {
return
}
for arg in self.desc_args {
rewritten.push(arg)
}
rewritten.push(@lib.Instruction::call(self.descriptor_func))
rewritten.push(@lib.Instruction::drop())
rewritten.push(@lib.Instruction::unreachable_())
}
///|
// Accepted raw OI-J descriptor bridge boundary. This loop orchestrates the
// exact-miss fact family (`RawOiDescriptorLocalCallOperandMissFact`,
// `RawOiDescriptorDirectCallMissFact`, `RawOiDescriptorLocalOperandMissFact`,
// `RawOiDescriptorLocalDefaultMissFact`, and
// `RawOiDescriptorBalancedLocalDirectMissFact`) after resolving the function
// parameter facts once. Reopen if it starts owning descriptor argument,
// producer, target, arity, or preservation proofs outside those facts.
fn run_hot_pipeline_raw_optimize_instructions_exact_descriptor_miss_body(
graph : RawOiDescriptorCallValueGraphFact,
module_ctx : @ir.HotModuleContext,
func_idx : @lib.FuncIdx,
locals : @lib.Locals,
) -> (Array[@lib.Instruction], Bool) {
let instrs = graph.instrs
let params = match @ir.hot_module_resolve_func_type(module_ctx, func_idx) {
Some(@lib.FuncType(params, _)) => params
None => return (instrs.copy(), false)
}
let rewritten : Array[@lib.Instruction] = []
let mut changed = false
let mut i = 0
while i < instrs.length() {
match
RawOiDescriptorLocalCallOperandMissFact::from_graph(
graph, i, module_ctx, params, locals,
) {
Some(fact) if fact.proves_binaryen_descriptor_miss() => {
fact.append_rewrite(rewritten)
changed = true
i = fact.end
continue
}
_ => ()
}
match RawOiDescriptorDirectCallMissFact::from_graph(graph, i, module_ctx) {
Some(fact) if fact.proves_binaryen_descriptor_miss() => {
fact.append_rewrite(rewritten)
changed = true
i = fact.end
continue
}
_ => ()
}
match
RawOiDescriptorLocalOperandMissFact::from_graph(
graph, i, module_ctx, params, locals,
) {
Some(fact) if fact.proves_binaryen_descriptor_miss() => {
fact.append_rewrite(rewritten)
changed = true
i = fact.end
continue
}
_ => ()
}
match
RawOiDescriptorLocalDefaultMissFact::from_graph(
graph, i, module_ctx, params, locals,
) {
Some(fact) if fact.proves_binaryen_descriptor_miss() => {
fact.append_rewrite(rewritten)
changed = true
i = fact.end
continue
}
_ => ()
}
match
RawOiDescriptorBalancedLocalDirectMissFact::from_graph(
graph, i, module_ctx,
) {
Some(fact) if fact.proves_binaryen_descriptor_miss() => {
fact.append_rewrite(rewritten)
changed = true
i = fact.end
continue
}
_ => ()
}
rewritten.push(instrs[i])
i = i + 1
}
(rewritten, changed)
}
///|
// Raw fallback analogue of Binaryen v130 optimizeKnownCastResult's descriptor
// special case for known-null cast inputs: the reference child is side-effect
// free, but descriptor operands must still preserve null traps or direct-call
// side effects before the cast is replaced by a typed null or unreachable.
priv struct RawOiBottomNullDescriptorOperandFact {
preserves_operand_as_drop : Bool
erases_fresh_descriptor_allocation : Bool
block_prefix_len : Int32
block_traps : Bool
}
///|
fn RawOiBottomNullDescriptorOperandFact::from_instr(
instr : @lib.Instruction,
) -> RawOiBottomNullDescriptorOperandFact? {
match instr {
@lib.Call(_) =>
Some({
preserves_operand_as_drop: true,
erases_fresh_descriptor_allocation: false,
block_prefix_len: 0,
block_traps: false,
})
@lib.StructNewDefault(_) =>
Some({
preserves_operand_as_drop: false,
erases_fresh_descriptor_allocation: true,
block_prefix_len: 0,
block_traps: false,
})
@lib.Block(_, @lib.Expr(descriptor_body)) =>
RawOiBottomNullDescriptorOperandFact::from_block_body(descriptor_body)
_ => None
}
}
///|
fn RawOiBottomNullDescriptorOperandFact::from_block_body(
descriptor_body : Array[@lib.Instruction],
) -> RawOiBottomNullDescriptorOperandFact? {
if descriptor_body.length() < 2 {
return None
}
let prefix_graph = RawOiDescriptorPrefixValueGraphFact::from_body(
descriptor_body, 0,
)
match descriptor_body[descriptor_body.length() - 1] {
@lib.Unreachable =>
match prefix_graph.prefix_hoist(descriptor_body.length() - 1) {
Some(fact) if fact.proves_binaryen_descriptor_prefix_hoist() =>
return Some({
preserves_operand_as_drop: false,
erases_fresh_descriptor_allocation: false,
block_prefix_len: fact.prefix_len,
block_traps: true,
})
_ => ()
}
_ => ()
}
let prefix_len = descriptor_body.length() - 2
match (descriptor_body[prefix_len], descriptor_body[prefix_len + 1]) {
(@lib.RefNull(_), @lib.RefAsNonNull) =>
match prefix_graph.prefix_hoist(prefix_len) {
Some(fact) if fact.proves_binaryen_descriptor_prefix_hoist() =>
Some({
preserves_operand_as_drop: false,
erases_fresh_descriptor_allocation: false,
block_prefix_len: fact.prefix_len,
block_traps: true,
})
_ => None
}
_ => None
}
}
///|
fn RawOiBottomNullDescriptorOperandFact::is_represented_fold_operand(
self : RawOiBottomNullDescriptorOperandFact,
) -> Bool {
self.preserves_operand_as_drop ||
self.erases_fresh_descriptor_allocation ||
self.block_traps
}
///|
priv struct RawOiBottomNullDescriptorCastFact {
end : Int32
descriptor : @lib.Instruction
operand : RawOiBottomNullDescriptorOperandFact
nullable : Bool
exact : Bool
target : @lib.HeapType
}
///|
fn RawOiBottomNullDescriptorCastFact::from_region_at(
region : RawOiInstructionRegionFact,
start : Int32,
) -> RawOiBottomNullDescriptorCastFact? {
if start + 2 >= region.roots.length() ||
!region.has_root_at(start, RawOiRootKind::RefNull) ||
!region.has_root_at(start + 2, RawOiRootKind::RefCastDescEq) {
return None
}
match
(
region.roots[start].instr,
region.roots[start + 1].instr,
region.roots[start + 2].instr,
) {
(@lib.RefNull(_), descriptor, @lib.RefCastDescEq(nullable, exact, target)) =>
match RawOiBottomNullDescriptorOperandFact::from_instr(descriptor) {
Some(operand) if operand.is_represented_fold_operand() =>
Some({ end: start + 3, descriptor, operand, nullable, exact, target, })
_ => None
}
_ => None
}
}
///|
fn RawOiBottomNullDescriptorCastFact::append_rewrite(
self : RawOiBottomNullDescriptorCastFact,
rewritten : Array[@lib.Instruction],
) -> Unit {
if self.operand.block_traps {
match self.descriptor {
@lib.Block(_, @lib.Expr(descriptor_body)) => {
for idx = 0; idx < self.operand.block_prefix_len; idx = idx + 1 {
rewritten.push(descriptor_body[idx])
}
rewritten.push(@lib.Instruction::unreachable_())
}
_ => rewritten.push(@lib.Instruction::unreachable_())
}
return
}
if self.operand.preserves_operand_as_drop {
rewritten.push(self.descriptor)
rewritten.push(@lib.Instruction::drop())
}
if self.nullable {
rewritten.push(
@lib.Instruction::ref_null_type(
@lib.RefType::new(true, self.target, exact=self.exact),
),
)
} else {
rewritten.push(@lib.Instruction::unreachable_())
}
}
///|
priv struct RawOiBottomNullDescriptorCastRegionFact {
has_fold : Bool
}
///|
fn RawOiBottomNullDescriptorCastRegionFact::from_index(
index : RawOiFunctionRevisionFactIndex,
) -> RawOiBottomNullDescriptorCastRegionFact {
let region = index.regions[0]
for i = 0; i < region.roots.length(); i = i + 1 {
match RawOiBottomNullDescriptorCastFact::from_region_at(region, i) {
Some(_) => return { has_fold: true, }
None => ()
}
}
{ has_fold: false, }
}
///|
fn RawOiInstructionRegionFact::has_effectful_sibling_rewrite_candidate(
self : RawOiInstructionRegionFact,
module_ctx : @ir.HotModuleContext,
) -> Bool {
// Every represented family has a distinctive terminal root. Dispatching from
// that terminal avoids attempting all eight fact parsers at every operand in
// large regions while preserving each family's exact proof constructor.
for terminal = 0; terminal < self.roots.length(); terminal = terminal + 1 {
match self.roots[terminal].kind {
RawOiRootKind::RefCastDescEq =>
if terminal >= 2 &&
RawOiBottomNullDescriptorCastFact::from_region_at(self, terminal - 2)
is Some(_) {
return true
}
RawOiRootKind::BulkMemory => {
let mut start = if terminal > 4 { terminal - 4 } else { 0 }
while start <= terminal {
if RawOiBulkLoweringRewriteFact::from_region(self, start) is Some(_) {
return true
}
start += 1
}
}
RawOiRootKind::If => {
if terminal >= 1 &&
RawOiDroppedConstBranchIfFact::from_region(self, terminal - 1)
is Some(_) {
return true
}
if terminal >= 3 &&
RawOiMemorySizeNonzeroIfFact::from_region(self, terminal - 3)
is Some(_) {
return true
}
match self.roots[terminal].instr {
@lib.If(block_type, then_body, Some(else_body)) => {
if RawOiIdenticalIfArmsFact::from_arms(then_body, else_body)
is Some(_) ||
RawOiSameLocalConstIfSinkFact::from_arms(
block_type, then_body, else_body,
)
is Some(_) {
return true
}
match
RawOiSameGlobalIfSinkFact::from_if_arms(
block_type, then_body, else_body, module_ctx,
) {
Some(fact) if fact.to_rewrite() is Some(_) => return true
_ => ()
}
}
_ => ()
}
}
RawOiRootKind::Select => {
let mut start = if terminal > 5 { terminal - 5 } else { 0 }
while start <= terminal {
if RawOiSelectRewriteFact::from_region(self, start) is Some(_) {
return true
}
start += 1
}
}
RawOiRootKind::MemoryStore => {
let mut start = if terminal > 6 { terminal - 6 } else { 0 }
while start <= terminal {
if RawOiDropStoreMaskRewriteFact::from_region(self, start) is Some(_) ||
RawOiMemoryOffsetFoldRewriteFact::from_region(self, start)
is Some(_) {
return true
}
start += 1
}
}
RawOiRootKind::PlainLoad => {
let mut start = if terminal > 3 { terminal - 3 } else { 0 }
while start <= terminal {
if RawOiMemoryOffsetFoldRewriteFact::from_region(self, start)
is Some(_) {
return true
}
start += 1
}
}
RawOiRootKind::ScalarNumericTerminal
| RawOiRootKind::CommutativeIntegerBinop => {
let mut start = if terminal > 5 { terminal - 5 } else { 0 }
while start <= terminal {
if RawOiScalarNumericRewriteFact::from_region(
self,
start,
module_ctx=Some(module_ctx),
)
is Some(_) {
return true
}
start += 1
}
}
_ => ()
}
}
false
}
///|
fn RawOiFunctionRevisionFactIndex::has_effectful_sibling_rewrite_candidate(
self : RawOiFunctionRevisionFactIndex,
module_ctx : @ir.HotModuleContext,
) -> Bool {
self.regions.any(fn(region) {
region.has_effectful_sibling_rewrite_candidate(module_ctx)
})
}
///|
// Accepted raw OI-D/F/G/J bridge boundary. This straight-line walker is now a
// fact table: descriptor-bottom-null, bulk-lowering, dropped-branch, select,
// memory-size boolean, narrow-store mask, scalar numeric, and memory-offset
// fold rewrites each supply complete proof and rewrite payloads before the
// walker recurses into child control bodies. Reopen if a new direct pattern
// match appears here instead of a source-backed `RawOi*Fact`.
fn run_hot_pipeline_raw_optimize_instructions_effectful_sibling_body(
instrs : Array[@lib.Instruction],
module_ctx : @ir.HotModuleContext,
) -> (Array[@lib.Instruction], Bool) {
let rewritten : Array[@lib.Instruction] = []
let region = RawOiInstructionRegionFact::from_instrs(instrs)
let mut changed = false
let mut i = 0
while i < instrs.length() {
match RawOiBottomNullDescriptorCastFact::from_region_at(region, i) {
Some(fact) => {
fact.append_rewrite(rewritten)
changed = true
i = fact.end
continue
}
None => ()
}
match RawOiBulkLoweringRewriteFact::from_region(region, i) {
Some(fact) => {
fact.append_rewrite(rewritten)
changed = true
i = fact.end
continue
}
None => ()
}
match RawOiDroppedConstBranchIfFact::from_region(region, i) {
Some(fact) => {
fact.append_rewrite(rewritten)
changed = true
i = fact.end
continue
}
None => ()
}
match RawOiSelectRewriteFact::from_region(region, i) {
Some(fact) => {
fact.append_rewrite(rewritten)
changed = true
i = fact.end
continue
}
None => ()
}
match RawOiMemorySizeNonzeroIfFact::from_region(region, i) {
Some(fact) => {
fact.append_rewrite(rewritten, module_ctx)
changed = true
i = fact.end
continue
}
None => ()
}
match RawOiDropStoreMaskRewriteFact::from_region(region, i) {
Some(fact) => {
fact.append_rewrite(rewritten)
changed = true
i = fact.end
continue
}
None => ()
}
match
RawOiScalarNumericRewriteFact::from_region(
region,
i,
module_ctx=Some(module_ctx),
) {
Some(fact) => {
fact.append_rewrite(rewritten)
changed = true
i = fact.end
continue
}
None => ()
}
match RawOiMemoryOffsetFoldRewriteFact::from_region(region, i) {
Some(fact) => {
fact.append_rewrite(rewritten)
changed = true
i = fact.end
continue
}
None => ()
}
let (expanded, instr_changed) = run_hot_pipeline_raw_optimize_instructions_effectful_sibling_instr(
instrs[i],
module_ctx,
)
match
RawOiSameLocalConstIfEqzRevisitFact::from_rewrite(rewritten, expanded) {
Some(fact) if instr_changed => {
// The raw same-local sink just kept this exact If alive with value
// arms. Consume its immediately preceding eqz and swap those arms in
// the same bounded rewrite, matching the HOT same-node revisit.
fact.append_rewrite(rewritten)
changed = true
i = i + 1
continue
}
_ => ()
}
let erase_exposed_local_condition = instr_changed &&
expanded.length() > 0 &&
expanded[0] == @lib.Instruction::drop() &&
rewritten.length() > 0 &&
(match rewritten[rewritten.length() - 1] {
@lib.LocalGet(_) => true
_ => false
})
let mut expanded_start = 0
if erase_exposed_local_condition {
// The control rewrite created the drop. A local.get condition is pure and
// nontrapping, so erase exactly this new pair rather than scanning and
// changing unrelated pre-existing dropped reads in the function.
ignore(rewritten.pop())
expanded_start = 1
}
for j = expanded_start; j < expanded.length(); j = j + 1 {
rewritten.push(expanded[j])
}
changed = changed || instr_changed
i = i + 1
}
(rewritten, changed)
}
///|
// Accepted raw OI-G fact parser substrate. Binaryen proves
// trap-relaxed same-address `memory.copy` through `ExpressionAnalyzer::equal`
// and fallthrough facts; this flat raw bridge records the represented postfix
// scalar-tree subset as `RawOiFlatPureScalarAddressFact` so the later copy
// bridge consumes named address/equality facts instead of open-coded windows.
// Reopen if these helpers grow rewrite actions or non-address proof logic.
priv struct RawOiFlatPureScalarStackValueFact {
key : String
binary_op : String
left_key : String
right_key : String
commutative : Bool
fallthrough_local_tee : Bool
}
///|
priv struct RawOiFlatPureScalarAddressFact {
start : Int32
end : Int32
key : String
binary_op : String
left_key : String
right_key : String
commutative : Bool
fallthrough_local_tee : Bool
}
///|
priv struct RawOiFlatPureScalarLeafFact {
key : String
}
///|
fn RawOiFlatPureScalarLeafFact::from_instr(
instr : @lib.Instruction,
) -> RawOiFlatPureScalarLeafFact? {
match instr {
@lib.LocalGet(@lib.LocalIdx(raw)) =>
Some({ key: "local.get:\{raw.reinterpret_as_int()}", })
@lib.GlobalGet(@lib.GlobalIdx(raw)) =>
Some({ key: "global.get:\{raw.reinterpret_as_int()}", })
@lib.I32Const(@lib.I32(raw)) => Some({ key: "i32.const:\{raw}", })
@lib.I64Const(@lib.I64(raw)) => Some({ key: "i64.const:\{raw}", })
@lib.F32Const(@lib.F32(raw)) => Some({ key: "f32.const:\{raw}", })
@lib.F64Const(@lib.F64(raw)) => Some({ key: "f64.const:\{raw}", })
_ => None
}
}
///|
priv struct RawOiFlatPureScalarUnaryOpFact {
key : String
}
///|
fn RawOiFlatPureScalarUnaryOpFact::from_instr(
instr : @lib.Instruction,
) -> RawOiFlatPureScalarUnaryOpFact? {
match instr {
@lib.I32Eqz => Some({ key: "i32.eqz", })
@lib.I32Clz => Some({ key: "i32.clz", })
@lib.I32Ctz => Some({ key: "i32.ctz", })
@lib.I32Popcnt => Some({ key: "i32.popcnt", })
@lib.I64Clz => Some({ key: "i64.clz", })
@lib.I64Ctz => Some({ key: "i64.ctz", })
@lib.I64Popcnt => Some({ key: "i64.popcnt", })
@lib.F32Abs => Some({ key: "f32.abs", })
@lib.F32Neg => Some({ key: "f32.neg", })
@lib.F32Ceil => Some({ key: "f32.ceil", })
@lib.F32Floor => Some({ key: "f32.floor", })
@lib.F32Trunc => Some({ key: "f32.trunc", })
@lib.F32Nearest => Some({ key: "f32.nearest", })
@lib.F32Sqrt => Some({ key: "f32.sqrt", })
@lib.F64Abs => Some({ key: "f64.abs", })
@lib.F64Neg => Some({ key: "f64.neg", })
@lib.F64Ceil => Some({ key: "f64.ceil", })
@lib.F64Floor => Some({ key: "f64.floor", })
@lib.F64Trunc => Some({ key: "f64.trunc", })
@lib.F64Nearest => Some({ key: "f64.nearest", })
@lib.F64Sqrt => Some({ key: "f64.sqrt", })
@lib.I32WrapI64 => Some({ key: "i32.wrap_i64", })
@lib.I32TruncF32S => Some({ key: "i32.trunc_f32_s", })
@lib.I32TruncF32U => Some({ key: "i32.trunc_f32_u", })
@lib.I32TruncF64S => Some({ key: "i32.trunc_f64_s", })
@lib.I32TruncF64U => Some({ key: "i32.trunc_f64_u", })
@lib.I64ExtendI32S => Some({ key: "i64.extend_i32_s", })
@lib.I64ExtendI32U => Some({ key: "i64.extend_i32_u", })
@lib.I64TruncF32S => Some({ key: "i64.trunc_f32_s", })
@lib.I64TruncF32U => Some({ key: "i64.trunc_f32_u", })
@lib.I64TruncF64S => Some({ key: "i64.trunc_f64_s", })
@lib.I64TruncF64U => Some({ key: "i64.trunc_f64_u", })
@lib.F32ConvertI32S => Some({ key: "f32.convert_i32_s", })
@lib.F32ConvertI32U => Some({ key: "f32.convert_i32_u", })
@lib.F32ConvertI64S => Some({ key: "f32.convert_i64_s", })
@lib.F32ConvertI64U => Some({ key: "f32.convert_i64_u", })
@lib.F32DemoteF64 => Some({ key: "f32.demote_f64", })
@lib.F64ConvertI32S => Some({ key: "f64.convert_i32_s", })
@lib.F64ConvertI32U => Some({ key: "f64.convert_i32_u", })
@lib.F64ConvertI64S => Some({ key: "f64.convert_i64_s", })
@lib.F64ConvertI64U => Some({ key: "f64.convert_i64_u", })
@lib.F64PromoteF32 => Some({ key: "f64.promote_f32", })
@lib.I32ReinterpretF32 => Some({ key: "i32.reinterpret_f32", })
@lib.I64ReinterpretF64 => Some({ key: "i64.reinterpret_f64", })
@lib.F32ReinterpretI32 => Some({ key: "f32.reinterpret_i32", })
@lib.F64ReinterpretI64 => Some({ key: "f64.reinterpret_i64", })
@lib.I32Extend8S => Some({ key: "i32.extend8_s", })
@lib.I32Extend16S => Some({ key: "i32.extend16_s", })
@lib.I64Extend8S => Some({ key: "i64.extend8_s", })
@lib.I64Extend16S => Some({ key: "i64.extend16_s", })
@lib.I64Extend32S => Some({ key: "i64.extend32_s", })
_ => None
}
}
///|
priv struct RawOiFlatPureScalarBinaryOpFact {
key : String
commutative : Bool
}
///|
fn RawOiFlatPureScalarBinaryOpFact::from_instr(
instr : @lib.Instruction,
) -> RawOiFlatPureScalarBinaryOpFact? {
match instr {
@lib.I32Add => Some({ key: "i32.add", commutative: true, })
@lib.I32Mul => Some({ key: "i32.mul", commutative: true, })
@lib.I32And => Some({ key: "i32.and", commutative: true, })
@lib.I32Or => Some({ key: "i32.or", commutative: true, })
@lib.I32Xor => Some({ key: "i32.xor", commutative: true, })
@lib.I32Eq => Some({ key: "i32.eq", commutative: true, })
@lib.I32Ne => Some({ key: "i32.ne", commutative: true, })
@lib.I32LtS => Some({ key: "i32.lt_s", commutative: false, })
@lib.I32LtU => Some({ key: "i32.lt_u", commutative: false, })
@lib.I32GtS => Some({ key: "i32.gt_s", commutative: false, })
@lib.I32GtU => Some({ key: "i32.gt_u", commutative: false, })
@lib.I32LeS => Some({ key: "i32.le_s", commutative: false, })
@lib.I32LeU => Some({ key: "i32.le_u", commutative: false, })
@lib.I32GeS => Some({ key: "i32.ge_s", commutative: false, })
@lib.I32GeU => Some({ key: "i32.ge_u", commutative: false, })
@lib.I32Sub => Some({ key: "i32.sub", commutative: false, })
@lib.I32DivS => Some({ key: "i32.div_s", commutative: false, })
@lib.I32DivU => Some({ key: "i32.div_u", commutative: false, })
@lib.I32RemS => Some({ key: "i32.rem_s", commutative: false, })
@lib.I32RemU => Some({ key: "i32.rem_u", commutative: false, })
@lib.I32Shl => Some({ key: "i32.shl", commutative: false, })
@lib.I32ShrS => Some({ key: "i32.shr_s", commutative: false, })
@lib.I32ShrU => Some({ key: "i32.shr_u", commutative: false, })
@lib.I32Rotl => Some({ key: "i32.rotl", commutative: false, })
@lib.I32Rotr => Some({ key: "i32.rotr", commutative: false, })
@lib.I64Add => Some({ key: "i64.add", commutative: true, })
@lib.I64Mul => Some({ key: "i64.mul", commutative: true, })
@lib.I64And => Some({ key: "i64.and", commutative: true, })
@lib.I64Or => Some({ key: "i64.or", commutative: true, })
@lib.I64Xor => Some({ key: "i64.xor", commutative: true, })
@lib.I64Sub => Some({ key: "i64.sub", commutative: false, })
@lib.I64DivS => Some({ key: "i64.div_s", commutative: false, })
@lib.I64DivU => Some({ key: "i64.div_u", commutative: false, })
@lib.I64RemS => Some({ key: "i64.rem_s", commutative: false, })
@lib.I64RemU => Some({ key: "i64.rem_u", commutative: false, })
@lib.I64Shl => Some({ key: "i64.shl", commutative: false, })
@lib.I64ShrS => Some({ key: "i64.shr_s", commutative: false, })
@lib.I64ShrU => Some({ key: "i64.shr_u", commutative: false, })
@lib.I64Rotl => Some({ key: "i64.rotl", commutative: false, })
@lib.I64Rotr => Some({ key: "i64.rotr", commutative: false, })
@lib.I64Eq => Some({ key: "i64.eq", commutative: true, })
@lib.I64Ne => Some({ key: "i64.ne", commutative: true, })
@lib.I64LtS => Some({ key: "i64.lt_s", commutative: false, })
@lib.I64LtU => Some({ key: "i64.lt_u", commutative: false, })
@lib.I64GtS => Some({ key: "i64.gt_s", commutative: false, })
@lib.I64GtU => Some({ key: "i64.gt_u", commutative: false, })
@lib.I64LeS => Some({ key: "i64.le_s", commutative: false, })
@lib.I64LeU => Some({ key: "i64.le_u", commutative: false, })
@lib.I64GeS => Some({ key: "i64.ge_s", commutative: false, })
@lib.I64GeU => Some({ key: "i64.ge_u", commutative: false, })
@lib.F32Eq => Some({ key: "f32.eq", commutative: false, })
@lib.F32Ne => Some({ key: "f32.ne", commutative: false, })
@lib.F32Lt => Some({ key: "f32.lt", commutative: false, })
@lib.F32Gt => Some({ key: "f32.gt", commutative: false, })
@lib.F32Le => Some({ key: "f32.le", commutative: false, })
@lib.F32Ge => Some({ key: "f32.ge", commutative: false, })
@lib.F64Eq => Some({ key: "f64.eq", commutative: false, })
@lib.F64Ne => Some({ key: "f64.ne", commutative: false, })
@lib.F64Lt => Some({ key: "f64.lt", commutative: false, })
@lib.F64Gt => Some({ key: "f64.gt", commutative: false, })
@lib.F64Le => Some({ key: "f64.le", commutative: false, })
@lib.F64Ge => Some({ key: "f64.ge", commutative: false, })
@lib.F32Add => Some({ key: "f32.add", commutative: false, })
@lib.F32Sub => Some({ key: "f32.sub", commutative: false, })
@lib.F32Mul => Some({ key: "f32.mul", commutative: false, })
@lib.F32Div => Some({ key: "f32.div", commutative: false, })
@lib.F32Min => Some({ key: "f32.min", commutative: false, })
@lib.F32Max => Some({ key: "f32.max", commutative: false, })
@lib.F32Copysign => Some({ key: "f32.copysign", commutative: false, })
@lib.F64Add => Some({ key: "f64.add", commutative: false, })
@lib.F64Sub => Some({ key: "f64.sub", commutative: false, })
@lib.F64Mul => Some({ key: "f64.mul", commutative: false, })
@lib.F64Div => Some({ key: "f64.div", commutative: false, })
@lib.F64Min => Some({ key: "f64.min", commutative: false, })
@lib.F64Max => Some({ key: "f64.max", commutative: false, })
@lib.F64Copysign => Some({ key: "f64.copysign", commutative: false, })
_ => None
}
}
///|
fn RawOiFlatPureScalarBinaryOpFact::result_key(
self : RawOiFlatPureScalarBinaryOpFact,
left_key : String,
right_key : String,
) -> String {
if self.commutative && right_key < left_key {
"\{self.key}(\{right_key},\{left_key})"
} else {
"\{self.key}(\{left_key},\{right_key})"
}
}
///|
fn RawOiFlatPureScalarStackValueFact::leaf(
fact : RawOiFlatPureScalarLeafFact,
) -> RawOiFlatPureScalarStackValueFact {
{
key: fact.key,
binary_op: "",
left_key: "",
right_key: "",
commutative: false,
fallthrough_local_tee: false,
}
}
///|
fn RawOiFlatPureScalarStackValueFact::unary_result(
fact : RawOiFlatPureScalarUnaryOpFact,
child : RawOiFlatPureScalarStackValueFact,
) -> RawOiFlatPureScalarStackValueFact {
{
key: "\{fact.key}(\{child.key})",
binary_op: "",
left_key: "",
right_key: "",
commutative: false,
fallthrough_local_tee: false,
}
}
///|
fn RawOiFlatPureScalarStackValueFact::binary_result(
fact : RawOiFlatPureScalarBinaryOpFact,
left : RawOiFlatPureScalarStackValueFact,
right : RawOiFlatPureScalarStackValueFact,
) -> RawOiFlatPureScalarStackValueFact {
{
key: fact.result_key(left.key, right.key),
binary_op: fact.key,
left_key: left.key,
right_key: right.key,
commutative: fact.commutative,
fallthrough_local_tee: false,
}
}
///|
fn RawOiFlatPureScalarAddressFact::from_value(
start : Int32,
end : Int32,
value : RawOiFlatPureScalarStackValueFact,
) -> RawOiFlatPureScalarAddressFact {
{
start,
end,
key: value.key,
binary_op: value.binary_op,
left_key: value.left_key,
right_key: value.right_key,
commutative: value.commutative,
fallthrough_local_tee: value.fallthrough_local_tee,
}
}
///|
priv struct RawOiFlatPureScalarBudgetFact {
max_instrs : Int32
max_stack : Int32
}
///|
fn RawOiFlatPureScalarBudgetFact::default() -> RawOiFlatPureScalarBudgetFact {
// Keep the represented raw OI-G address parser bounded. Binaryen's full
// `ExpressionAnalyzer::equal` is tree-shaped and effect-aware; this raw
// fallback only accepts small flat postfix scalar trees until the HOT fact
// layer can replace it entirely.
{ max_instrs: 32, max_stack: 8, }
}
///|
fn RawOiFlatPureScalarBudgetFact::allows_instr(
self : RawOiFlatPureScalarBudgetFact,
start : Int32,
idx : Int32,
) -> Bool {
idx - start < self.max_instrs
}
///|
fn RawOiFlatPureScalarBudgetFact::exceeded_stack(
self : RawOiFlatPureScalarBudgetFact,
stack_len : Int32,
) -> Bool {
stack_len > self.max_stack
}
///|
fn RawOiFlatPureScalarAddressFact::candidates_from_region(
region : RawOiInstructionRegionFact,
start : Int32,
allow_fallthrough_local_tee : Bool,
) -> Array[RawOiFlatPureScalarAddressFact] {
let budget = RawOiFlatPureScalarBudgetFact::default()
let facts : Array[RawOiFlatPureScalarAddressFact] = []
let stack : Array[RawOiFlatPureScalarStackValueFact] = []
let mut idx = start
while idx < region.roots.length() && budget.allows_instr(start, idx) {
let instr = region.roots[idx].instr
match RawOiFlatPureScalarLeafFact::from_instr(instr) {
Some(fact) => stack.push(RawOiFlatPureScalarStackValueFact::leaf(fact))
None =>
match instr {
@lib.LocalTee(_) => {
// Binaryen v130 `Properties::getImmediateFallthrough` with
// `AllowTeeBrIf` exposes a direct right `local.tee`'s value as the
// operand fallthrough while preserving the tee write in the dropped
// original operand. Keep that raw stack-root proof as a value fact
// instead of adding another fixed instruction window. The flat raw
// helper only enables this on the source/right operand so existing
// HOT canonicalization of left-tee drops can still run.
if !allow_fallthrough_local_tee || stack.length() < 1 {
break
}
let value = stack.pop().unwrap()
stack.push({
key: value.key,
binary_op: value.binary_op,
left_key: value.left_key,
right_key: value.right_key,
commutative: value.commutative,
fallthrough_local_tee: true,
})
}
_ =>
match RawOiFlatPureScalarUnaryOpFact::from_instr(instr) {
Some(fact) => {
if stack.length() < 1 {
break
}
let child = stack.pop().unwrap()
stack.push(
RawOiFlatPureScalarStackValueFact::unary_result(fact, child),
)
}
None =>
match RawOiFlatPureScalarBinaryOpFact::from_instr(instr) {
Some(fact) => {
if stack.length() < 2 {
break
}
let right = stack.pop().unwrap()
let left = stack.pop().unwrap()
stack.push(
RawOiFlatPureScalarStackValueFact::binary_result(
fact, left, right,
),
)
}
None => break
}
}
}
}
if stack.length() == 1 {
facts.push(
RawOiFlatPureScalarAddressFact::from_value(start, idx + 1, stack[0]),
)
} else if budget.exceeded_stack(stack.length()) {
break
}
idx = idx + 1
}
facts
}
///|
fn RawOiFlatPureScalarAddressFact::proves_equal(
self : RawOiFlatPureScalarAddressFact,
other : RawOiFlatPureScalarAddressFact,
) -> Bool {
self.key == other.key ||
(
self.commutative &&
other.commutative &&
self.binary_op == other.binary_op &&
self.left_key == other.right_key &&
self.right_key == other.left_key
)
}
///|
// Fact-owned raw OI-G emission for preserving and dropping a proven flat
// pure-scalar address operand. The caller supplies only the original body; the
// address fact owns the exact `[start, end)` range.
fn RawOiFlatPureScalarAddressFact::append_dropped_operand(
self : RawOiFlatPureScalarAddressFact,
out : Array[@lib.Instruction],
region : RawOiInstructionRegionFact,
) -> Unit {
for idx = self.start; idx < self.end; idx = idx + 1 {
out.push(region.roots[idx].instr)
}
out.push(@lib.Instruction::drop())
}
///|
priv struct RawOiSameFlatPureScalarMemoryCopyFact {
dst_fact : RawOiFlatPureScalarAddressFact
src_fact : RawOiFlatPureScalarAddressFact
size_instr_idx : Int32
copy_instr_idx : Int32
}
///|
fn RawOiSameFlatPureScalarMemoryCopyFact::from_address_facts(
region : RawOiInstructionRegionFact,
dst_fact : RawOiFlatPureScalarAddressFact,
src_fact : RawOiFlatPureScalarAddressFact,
) -> RawOiSameFlatPureScalarMemoryCopyFact? {
// Binaryen v130 `optimizeMemoryCopy` drops same-memory copies whose flat
// destination/source address facts prove equal under trap-relaxed modes. Keep
// the represented raw fallback as one copy fact so the traversal no longer
// embeds the size/copy opcode window beside the address-equality proof.
if dst_fact.fallthrough_local_tee || !dst_fact.proves_equal(src_fact) {
return None
}
let size_instr_idx = src_fact.end
let copy_instr_idx = src_fact.end + 1
if copy_instr_idx >= region.roots.length() {
return None
}
if !run_hot_pipeline_optimize_instructions_instr_is_bulk_memory_stack_operand(
region.roots[size_instr_idx].instr,
) {
return None
}
match region.roots[copy_instr_idx].instr {
@lib.MemoryCopy(dst_mem, src_mem) if dst_mem == src_mem =>
Some({ dst_fact, src_fact, size_instr_idx, copy_instr_idx, })
_ => None
}
}
///|
fn RawOiSameFlatPureScalarMemoryCopyFact::proves_same_memory_copy_drop(
self : RawOiSameFlatPureScalarMemoryCopyFact,
) -> Bool {
self.size_instr_idx >= 0 && self.copy_instr_idx == self.size_instr_idx + 1
}
///|
fn RawOiSameFlatPureScalarMemoryCopyFact::append_replacement(
self : RawOiSameFlatPureScalarMemoryCopyFact,
out : Array[@lib.Instruction],
region : RawOiInstructionRegionFact,
) -> Int32 {
self.dst_fact.append_dropped_operand(out, region)
self.src_fact.append_dropped_operand(out, region)
out.push(region.roots[self.size_instr_idx].instr)
out.push(@lib.Instruction::drop())
self.copy_instr_idx + 1
}
///|
priv struct RawOiSameFlatPureScalarMemoryCopyRegionFact {
rewritten : Array[@lib.Instruction]
changed : Bool
}
///|
fn RawOiSameFlatPureScalarMemoryCopyRegionFact::rewrite_nested_instr(
instr : @lib.Instruction,
) -> (@lib.Instruction, Bool) {
match instr {
@lib.Block(block_type, @lib.Expr(body)) => {
let fact = RawOiSameFlatPureScalarMemoryCopyRegionFact::rewrite_body(body)
(
@lib.Instruction::block(block_type, @lib.Expr::new(fact.rewritten)),
fact.changed,
)
}
@lib.Loop(block_type, @lib.Expr(body)) => {
let fact = RawOiSameFlatPureScalarMemoryCopyRegionFact::rewrite_body(body)
(
@lib.Instruction::loop_(block_type, @lib.Expr::new(fact.rewritten)),
fact.changed,
)
}
@lib.TryTable(block_type, catches, @lib.Expr(body)) => {
let fact = RawOiSameFlatPureScalarMemoryCopyRegionFact::rewrite_body(body)
(
@lib.Instruction::try_table(
block_type,
catches,
@lib.Expr::new(fact.rewritten),
),
fact.changed,
)
}
@lib.If(block_type, then_body, else_body) => {
let then_fact = RawOiSameFlatPureScalarMemoryCopyRegionFact::rewrite_body(
then_body,
)
let (next_else, else_changed) = match else_body {
Some(body) => {
let else_fact = RawOiSameFlatPureScalarMemoryCopyRegionFact::rewrite_body(
body,
)
(Some(else_fact.rewritten), else_fact.changed)
}
None => (None, false)
}
(
@lib.Instruction::if_(block_type, then_fact.rewritten, next_else),
then_fact.changed || else_changed,
)
}
_ => (instr, false)
}
}
///|
fn RawOiSameFlatPureScalarMemoryCopyRegionFact::rewrite_body(
instrs : Array[@lib.Instruction],
) -> RawOiSameFlatPureScalarMemoryCopyRegionFact {
// Accepted raw OI-G mutation bridge over one immutable region view per body.
// Indexed roots discover bounded scalar-address candidates; exact equality,
// memory identity, size removability, and trap-relaxed admission remain in the
// named address/copy facts. No second admission traversal is constructed.
let region = RawOiInstructionRegionFact::from_instrs(instrs)
let out : Array[@lib.Instruction] = []
let mut changed = false
let mut i = 0
while i < region.roots.length() {
let mut matched = false
let dst_facts = RawOiFlatPureScalarAddressFact::candidates_from_region(
region, i, false,
)
for dst_fact in dst_facts {
if matched {
break
}
let src_facts = RawOiFlatPureScalarAddressFact::candidates_from_region(
region,
dst_fact.end,
true,
)
for src_fact in src_facts {
if matched {
break
}
match
RawOiSameFlatPureScalarMemoryCopyFact::from_address_facts(
region, dst_fact, src_fact,
) {
Some(copy_fact) if copy_fact.proves_same_memory_copy_drop() => {
i = copy_fact.append_replacement(out, region)
changed = true
matched = true
}
_ => ()
}
}
}
if matched {
continue
}
let (rewritten, instr_changed) = RawOiSameFlatPureScalarMemoryCopyRegionFact::rewrite_nested_instr(
region.roots[i].instr,
)
out.push(rewritten)
changed = changed || instr_changed
i = i + 1
}
{ rewritten: out, changed, }
}
///|
// Accepted top-level raw OI bridge. This path exists to run fact-backed raw
// fallback rewrites and fact-backed skip boundaries before the normal HOT pass
// path; it should remain an orchestrator over `RawOi*Fact` producers rather
// than a home for new scanner predicates.
fn run_hot_pipeline_raw_optimize_instructions_i32_add_is_safe_fold(
lhs : Int32,
rhs : Int32,
) -> Bool {
let sum = lhs + rhs
sum >= -2147483648 && sum <= 2147483647
}
///|
fn run_hot_pipeline_raw_optimize_instructions_const_fold_body_is_fast_path(
instrs : Array[@lib.Instruction],
) -> Bool {
instrs.all(fn(instr) {
match instr {
@lib.I32Const(_)
| @lib.I64Const(_)
| @lib.F32Const(_)
| @lib.F64Const(_) => true
_ => false
}
})
}
///|
fn run_hot_pipeline_raw_optimize_instructions_const_fold_body(
instrs : Array[@lib.Instruction],
) -> (Array[@lib.Instruction], Bool) {
let out : Array[@lib.Instruction] = []
let mut changed = false
let mut i = 0
while i < instrs.length() {
if i + 2 < instrs.length() {
match (instrs[i], instrs[i + 1], instrs[i + 2]) {
(@lib.I32Const(@lib.I32(raw)), @lib.I32Eqz, @lib.I32Eqz) => {
out.push(
@lib.Instruction::i32_const(@lib.I32(if raw == 0 { 0 } else { 1 })),
)
changed = true
i = i + 3
continue
}
(@lib.I32Const(@lib.I32(lhs)), @lib.I32Const(@lib.I32(rhs)), @lib.I32Add
) if run_hot_pipeline_raw_optimize_instructions_i32_add_is_safe_fold(
lhs, rhs,
) => {
out.push(@lib.Instruction::i32_const(@lib.I32(lhs + rhs)))
changed = true
i = i + 3
continue
}
_ => ()
}
}
if i + 1 < instrs.length() {
match oi_sign_bit_raw_pair(instrs[i], instrs[i + 1]) {
Some((constant, operator)) => {
out.push(constant)
out.push(operator)
changed = true
i += 2
continue
}
None => ()
}
match (instrs[i], instrs[i + 1]) {
(@lib.I32Const(@lib.I32(raw)), @lib.I32Eqz) => {
out.push(
@lib.Instruction::i32_const(@lib.I32(if raw == 0 { 1 } else { 0 })),
)
changed = true
i = i + 2
continue
}
_ => ()
}
}
match instrs[i] {
@lib.Block(block_type, @lib.Expr(body)) => {
let (next_body, body_changed) = run_hot_pipeline_raw_optimize_instructions_const_fold_body(
body,
)
out.push(
if body_changed {
@lib.Instruction::block(block_type, @lib.Expr::new(next_body))
} else {
instrs[i]
},
)
changed = changed || body_changed
}
@lib.Loop(block_type, @lib.Expr(body)) => {
let (next_body, body_changed) = run_hot_pipeline_raw_optimize_instructions_const_fold_body(
body,
)
out.push(
if body_changed {
@lib.Instruction::loop_(block_type, @lib.Expr::new(next_body))
} else {
instrs[i]
},
)
changed = changed || body_changed
}
@lib.If(block_type, then_body, else_body) => {
let (next_then, then_changed) = run_hot_pipeline_raw_optimize_instructions_const_fold_body(
then_body,
)
let (next_else, else_changed) = match else_body {
Some(body) => {
let (next_body, body_changed) = run_hot_pipeline_raw_optimize_instructions_const_fold_body(
body,
)
(Some(next_body), body_changed)
}
None => (None, false)
}
out.push(
if then_changed || else_changed {
@lib.Instruction::if_(block_type, next_then, next_else)
} else {
instrs[i]
},
)
changed = changed || then_changed || else_changed
}
@lib.TryTable(block_type, catches, @lib.Expr(body)) => {
let (next_body, body_changed) = run_hot_pipeline_raw_optimize_instructions_const_fold_body(
body,
)
out.push(
if body_changed {
@lib.Instruction::try_table(
block_type,
catches,
@lib.Expr::new(next_body),
)
} else {
instrs[i]
},
)
changed = changed || body_changed
}
_ => out.push(instrs[i])
}
i = i + 1
}
(out, changed)
}
///|
priv struct RawOiSparseExponentBrTableFact {
end : Int32
selector : Array[@lib.Instruction]
zero_target : @lib.LabelIdx
special_target : @lib.LabelIdx
other_target : @lib.LabelIdx
}
///|
fn RawOiSparseExponentBrTableFact::from_body(
instrs : Array[@lib.Instruction],
start : Int32,
) -> RawOiSparseExponentBrTableFact? {
if start + 13 >= instrs.length() {
return None
}
match
(
instrs[start],
instrs[start + 1],
instrs[start + 2],
instrs[start + 3],
instrs[start + 4],
instrs[start + 5],
instrs[start + 6],
instrs[start + 7],
instrs[start + 8],
instrs[start + 9],
instrs[start + 10],
instrs[start + 11],
instrs[start + 12],
instrs[start + 13],
) {
(
@lib.LocalGet(source),
@lib.I64ReinterpretF64,
@lib.I64Const(@lib.I64(52L)),
@lib.I64ShrU,
@lib.I32WrapI64,
@lib.I32Const(@lib.I32(65535)),
@lib.I32And,
@lib.I32Const(@lib.I32(2047)),
@lib.I32And,
@lib.I32Const(@lib.I32(16)),
@lib.I32Shl,
@lib.I32Const(@lib.I32(16)),
@lib.I32ShrS,
@lib.BrTable(labels, default_target),
) => {
if labels.length() != 2048 {
return None
}
let zero_target = labels[0]
let other_target = labels[1]
let special_target = labels[2047]
if default_target != other_target {
return None
}
for i = 1; i < 2047; i = i + 1 {
if labels[i] != other_target {
return None
}
}
let selector = [
@lib.Instruction::local_get(source),
@lib.Instruction::i64_reinterpret_f64(),
@lib.Instruction::i64_const(@lib.I64(52L)),
@lib.Instruction::i64_shr_u(),
@lib.Instruction::i32_wrap_i64(),
@lib.Instruction::i32_const(@lib.I32(2047)),
@lib.Instruction::i32_and(),
]
Some({
end: start + 14,
selector,
zero_target,
special_target,
other_target,
})
}
_ => None
}
}
///|
fn RawOiSparseExponentBrTableFact::append_rewrite(
self : RawOiSparseExponentBrTableFact,
out : Array[@lib.Instruction],
) -> Unit {
for instr in self.selector {
out.push(instr)
}
out.push(@lib.Instruction::i32_eqz())
out.push(@lib.Instruction::br_if(self.zero_target))
for instr in self.selector {
out.push(instr)
}
out.push(@lib.Instruction::i32_const(@lib.I32(2047)))
out.push(@lib.Instruction::i32_eq())
out.push(@lib.Instruction::br_if(self.special_target))
out.push(@lib.Instruction::br(self.other_target))
}
///|
fn run_hot_pipeline_raw_optimize_instructions_compress_sparse_br_table_instr(
instr : @lib.Instruction,
) -> (@lib.Instruction, Bool) {
match instr {
@lib.Block(block_type, @lib.Expr(body)) => {
let (next_body, changed) = run_hot_pipeline_raw_optimize_instructions_compress_sparse_br_table_body(
body,
)
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) = run_hot_pipeline_raw_optimize_instructions_compress_sparse_br_table_body(
body,
)
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) = run_hot_pipeline_raw_optimize_instructions_compress_sparse_br_table_body(
then_body,
)
let (next_else, else_changed) = match else_body {
Some(body) => {
let (next_body, changed) = run_hot_pipeline_raw_optimize_instructions_compress_sparse_br_table_body(
body,
)
(Some(next_body), changed)
}
None => (None, false)
}
if then_changed || else_changed {
(@lib.Instruction::if_(block_type, next_then, next_else), true)
} else {
(instr, false)
}
}
@lib.TryTable(block_type, catches, @lib.Expr(body)) => {
let (next_body, changed) = run_hot_pipeline_raw_optimize_instructions_compress_sparse_br_table_body(
body,
)
if changed {
(
@lib.Instruction::try_table(
block_type,
catches,
@lib.Expr::new(next_body),
),
true,
)
} else {
(instr, false)
}
}
_ => (instr, false)
}
}
///|
fn run_hot_pipeline_raw_optimize_instructions_compress_sparse_br_table_body(
instrs : Array[@lib.Instruction],
) -> (Array[@lib.Instruction], Bool) {
let out : Array[@lib.Instruction] = []
let mut changed = false
let mut i = 0
while i < instrs.length() {
match RawOiSparseExponentBrTableFact::from_body(instrs, i) {
Some(fact) => {
fact.append_rewrite(out)
changed = true
i = fact.end
continue
}
None => ()
}
let (next, child_changed) = run_hot_pipeline_raw_optimize_instructions_compress_sparse_br_table_instr(
instrs[i],
)
out.push(next)
changed = changed || child_changed
i = i + 1
}
(out, changed)
}
///|
fn run_hot_pipeline_raw_optimize_instructions_skip(
mod_ : @lib.Module,
func_idx : @lib.FuncIdx,
module_state : HotPipelineModuleState,
descriptor : @ir.HotPassDescriptor,
options : HotPipelineOptions,
oi_index~ : RawOiFunctionRevisionFactIndex? = None,
) -> HotPipelineFuncResult? {
if descriptor.name != "optimize-instructions" {
return None
}
let original = run_hot_pipeline_original_func(mod_, func_idx)
let @lib.Func(locals, @lib.Expr(body)) = original
if run_hot_pipeline_raw_has_load_before_same_local_call(body) {
return Some({
reason: "stack-carried-effect-optimize-instructions-noop",
func: original,
unchanged_original: true,
trace_skip_raw: true,
})
}
if run_hot_pipeline_dce_raw_has_try_table(body) {
return Some({
reason: "try-table-optimize-instructions-noop",
func: original,
unchanged_original: true,
trace_skip_raw: true,
})
}
let oi_index = match oi_index {
Some(index) => index
None => {
let raw_index_start_us = perf_start_timer_opt(
options.perf,
"detail:optimize-instructions:raw-revision-index",
)
let index = RawOiFunctionRevisionFactIndex::from_instrs(body)
perf_stop_timer_opt(
options.perf,
"detail:optimize-instructions:raw-revision-index",
raw_index_start_us,
)
index
}
}
if oi_index.admission_guards.has_result_try_table {
return Some({
reason: "result-try-table-optimize-instructions-noop",
func: original,
unchanged_original: true,
trace_skip_raw: true,
})
}
let (sparse_br_table_body, sparse_br_table_changed) = if oi_index.admission_guards.has_sparse_exponent_br_table_candidate {
run_hot_pipeline_raw_optimize_instructions_compress_sparse_br_table_body(
body,
)
} else {
(body, false)
}
if sparse_br_table_changed {
return Some({
reason: "sparse-exponent-br-table-optimize-instructions",
func: @lib.Func::new(locals, @lib.Expr::new(sparse_br_table_body)),
unchanged_original: false,
trace_skip_raw: true,
})
}
if oi_index.admission_guards.has_call_comparison_add {
return Some({
reason: "call-comparison-add-optimize-instructions-noop",
func: original,
unchanged_original: true,
trace_skip_raw: true,
})
}
if oi_index.admission_guards.has_stack_carried_call_zero_shift {
return Some({
reason: "stack-carried-call-zero-shift-optimize-instructions-noop",
func: original,
unchanged_original: true,
trace_skip_raw: true,
})
}
if oi_index.admission_guards.has_global_call_tee_control() {
// This boundary protects stack-carried effects from HOT replay. Adjacent
// constant-only folds consume neither those values nor any effects, so
// retain their progress without changing the existing admission decision.
let (folded, changed) = if oi_index.admission_guards.has_const_fold_candidate {
run_hot_pipeline_raw_optimize_instructions_const_fold_body(body)
} else {
(body, false)
}
return Some({
reason: "global-call-tee-control-optimize-instructions-noop",
func: if changed {
@lib.Func::new(locals, @lib.Expr::new(folded))
} else {
original
},
unchanged_original: !changed,
trace_skip_raw: true,
})
}
if oi_index.admission_guards.has_subtracted_local_multi_use {
return Some({
reason: "subtracted-local-multi-use-optimize-instructions-noop",
func: original,
unchanged_original: true,
trace_skip_raw: true,
})
}
if oi_index.admission_guards.has_dynamic_store_offset_value_arithmetic {
return Some({
reason: "dynamic-store-offset-value-arithmetic-alias-hazard",
func: original,
unchanged_original: true,
trace_skip_raw: true,
})
}
match RawOiV131FlatIdenticalSelectFact::from_body(body) {
Some(fact) =>
return Some({
reason: "v131-flat-identical-select",
func: @lib.Func::new(locals, @lib.Expr::new(fact.rewrite)),
unchanged_original: false,
trace_skip_raw: true,
})
None => ()
}
match RawOiV131PrecomputedIdenticalSelectFact::from_body(body) {
Some(fact) =>
return Some({
reason: "v131-precomputed-identical-select",
func: @lib.Func::new(locals, @lib.Expr::new(fact.rewrite)),
unchanged_original: false,
trace_skip_raw: true,
})
None => ()
}
let module_ctx = run_hot_pipeline_require_module_ctx(
mod_, module_state, options,
)
match RawOiV131IdempotentAbsMulFact::from_body(body, module_ctx) {
Some(fact) =>
return Some({
reason: "v131-idempotent-abs-mul",
func: @lib.Func::new(locals, @lib.Expr::new(fact.rewrite)),
unchanged_original: false,
trace_skip_raw: true,
})
None => ()
}
// Candidate facts only decide which raw bridges can possibly apply; each
// selected family still performs its exact descriptor/type/effect/order/trap
// proof against this one immutable original revision.
let raw_descriptor_start_us = perf_start_timer_opt(
options.perf,
"detail:optimize-instructions:raw-descriptor-bridges",
)
let has_descriptor_candidate = oi_index.bridge_candidates.needs_descriptor_reconstruction()
let (
exact_success_changed,
exactification_changed,
exact_miss_body,
exact_miss_changed,
) = if has_descriptor_candidate {
let success_graph = RawOiDescriptorCallValueGraphFact::from_module_body(
body, module_ctx,
)
let (success_body, success_changed) = run_hot_pipeline_raw_optimize_instructions_exact_descriptor_success_body(
success_graph, module_ctx,
)
let exactification_graph = if success_changed {
RawOiDescriptorCallValueGraphFact::from_module_body(
success_body, module_ctx,
)
} else {
success_graph
}
let (exactification_body, exactification_changed) = run_hot_pipeline_raw_optimize_instructions_exact_descriptor_exactification_body(
exactification_graph, module_ctx,
)
let miss_graph = if exactification_changed {
RawOiDescriptorCallValueGraphFact::from_module_body(
exactification_body, module_ctx,
)
} else {
exactification_graph
}
let (miss_body, miss_changed) = run_hot_pipeline_raw_optimize_instructions_exact_descriptor_miss_body(
miss_graph, module_ctx, func_idx, locals,
)
(success_changed, exactification_changed, miss_body, miss_changed)
} else {
(false, false, body, false)
}
let directization_index = if exact_success_changed ||
exactification_changed ||
exact_miss_changed {
RawOiFunctionRevisionFactIndex::from_instrs(exact_miss_body)
} else {
oi_index
}
let (directized_body, directized_changed) = if directization_index.bridge_candidates.needs_call_ref_or_null_repair() {
run_hot_pipeline_raw_optimize_instructions_directize_call_ref_body(
exact_miss_body,
)
} else {
(exact_miss_body, false)
}
let (tnh_exact_success_body, tnh_exact_success_changed) = if options.traps_never_happen &&
has_descriptor_candidate {
run_hot_pipeline_raw_optimize_instructions_exact_descriptor_success_body(
RawOiDescriptorCallValueGraphFact::from_module_body(
directized_body, module_ctx,
),
module_ctx,
)
} else {
(directized_body, false)
}
let (tnh_indirect_exact_success_body, tnh_indirect_exact_success_changed) = if options.traps_never_happen &&
has_descriptor_candidate {
run_hot_pipeline_raw_optimize_instructions_tnh_call_indirect_exact_descriptor_success_body(
tnh_exact_success_body, module_ctx,
)
} else {
(tnh_exact_success_body, false)
}
let (const_fold_body, const_fold_changed) = if oi_index.admission_guards.has_const_fold_candidate ||
exact_success_changed ||
exactification_changed ||
exact_miss_changed ||
directized_changed ||
tnh_exact_success_changed ||
tnh_indirect_exact_success_changed {
run_hot_pipeline_raw_optimize_instructions_const_fold_body(
tnh_indirect_exact_success_body,
)
} else {
(tnh_indirect_exact_success_body, false)
}
if const_fold_changed &&
locals.length() == 0 &&
!exact_success_changed &&
!exactification_changed &&
!exact_miss_changed &&
!directized_changed &&
!tnh_exact_success_changed &&
!tnh_indirect_exact_success_changed &&
run_hot_pipeline_raw_optimize_instructions_const_fold_body_is_fast_path(
const_fold_body,
) {
return Some({
reason: "raw-const-fold-optimize-instructions",
func: @lib.Func::new(locals, @lib.Expr::new(const_fold_body)),
unchanged_original: false,
trace_skip_raw: true,
})
}
let body_changed_before_effectful_sibling = exact_success_changed ||
exactification_changed ||
exact_miss_changed ||
directized_changed ||
tnh_exact_success_changed ||
tnh_indirect_exact_success_changed ||
const_fold_changed
let (effectful_sibling_body, effectful_sibling_changed) = if body_changed_before_effectful_sibling ||
oi_index.has_effectful_sibling_rewrite_candidate(module_ctx) {
run_hot_pipeline_raw_optimize_instructions_effectful_sibling_body(
const_fold_body, module_ctx,
)
} else {
(const_fold_body, false)
}
let (
same_flat_pure_scalar_memory_copy_body,
same_flat_pure_scalar_memory_copy_changed,
) = if options.traps_never_happen || options.ignore_implicit_traps {
let fact = RawOiSameFlatPureScalarMemoryCopyRegionFact::rewrite_body(
effectful_sibling_body,
)
(fact.rewritten, fact.changed)
} else {
(effectful_sibling_body, false)
}
let (escaping_ref_get_desc_body, escaping_ref_get_desc_changed) = if options.traps_never_happen ||
!has_descriptor_candidate ||
!oi_index.bridge_candidates.has_ref_get_desc {
(same_flat_pure_scalar_memory_copy_body, false)
} else {
run_hot_pipeline_raw_oi_flatten_escaping_ref_get_desc_body(
same_flat_pure_scalar_memory_copy_body, module_ctx,
)
}
perf_stop_timer_opt(
options.perf,
"detail:optimize-instructions:raw-descriptor-bridges",
raw_descriptor_start_us,
)
let skipped_func = if exact_success_changed ||
exactification_changed ||
exact_miss_changed ||
directized_changed ||
tnh_exact_success_changed ||
tnh_indirect_exact_success_changed ||
effectful_sibling_changed ||
same_flat_pure_scalar_memory_copy_changed ||
escaping_ref_get_desc_changed ||
const_fold_changed {
@lib.Func::new(locals, @lib.Expr::new(escaping_ref_get_desc_body))
} else {
original
}
let skipped_unchanged = !exact_success_changed &&
!exactification_changed &&
!exact_miss_changed &&
!directized_changed &&
!tnh_exact_success_changed &&
!tnh_indirect_exact_success_changed &&
!effectful_sibling_changed &&
!same_flat_pure_scalar_memory_copy_changed &&
!escaping_ref_get_desc_changed &&
!const_fold_changed
if !effectful_sibling_changed {
// Effectful-select facts normally force HOT replay, but they must not bypass
// the coarse bounds that keep large lowered functions out of the currently
// unbounded replay path.
match
RawOiCoarseNoopSkipFact::from_large_local(
locals.length(),
oi_index.lowered_stats,
) {
Some(fact) if fact.proves_accepted_raw_boundary() =>
return Some({
reason: fact.reason,
func: skipped_func,
unchanged_original: skipped_unchanged,
trace_skip_raw: true,
})
_ => ()
}
match
RawOiCoarseNoopSkipFact::from_large_lowered(
locals.length(),
oi_index.lowered_stats,
) {
Some(fact) if fact.proves_accepted_raw_boundary() =>
return Some({
reason: fact.reason,
func: skipped_func,
unchanged_original: skipped_unchanged,
trace_skip_raw: true,
})
_ => ()
}
match RawOiV131EffectfulSelectHotReplayFact::from_index(oi_index) {
Some(fact) if fact.proves_hot_replay() => return None
_ => ()
}
}
if exact_success_changed ||
tnh_exact_success_changed ||
tnh_indirect_exact_success_changed {
return Some({
reason: "descriptor-exact-success-skip",
func: skipped_func,
unchanged_original: false,
trace_skip_raw: true,
})
}
if exactification_changed {
return Some({
reason: "descriptor-exactification",
func: skipped_func,
unchanged_original: false,
trace_skip_raw: true,
})
}
if exact_miss_changed {
return Some({
reason: "descriptor-exact-miss-fold",
func: skipped_func,
unchanged_original: false,
trace_skip_raw: true,
})
}
if escaping_ref_get_desc_changed || same_flat_pure_scalar_memory_copy_changed {
return Some({
reason: "raw-partial-optimize-instructions",
func: skipped_func,
unchanged_original: false,
trace_skip_raw: true,
})
}
// Every gate below intentionally classifies the unmodified input snapshot
// whose HOT lift/admission decision is being made.
if effectful_sibling_changed &&
RawOiBottomNullDescriptorCastRegionFact::from_index(oi_index).has_fold {
return Some({
reason: "descriptor-bottom-null-fold",
func: skipped_func,
unchanged_original: skipped_unchanged,
trace_skip_raw: true,
})
}
let stats = oi_index.lowered_stats
match RawOiCoarseNoopSkipFact::from_large_local(locals.length(), stats) {
Some(fact) if fact.proves_accepted_raw_boundary() =>
return Some({
reason: fact.reason,
func: skipped_func,
unchanged_original: skipped_unchanged,
trace_skip_raw: true,
})
_ => ()
}
if RawOiAtomicLocalTeeAddressRegionFact::from_index(oi_index).has_blocking_use {
return Some({
reason: "atomic-localtee-optimize-instructions",
func: skipped_func,
unchanged_original: skipped_unchanged,
trace_skip_raw: true,
})
}
let trap_relaxed = options.traps_never_happen || options.ignore_implicit_traps
let localtee_same_address_memory_copy = match
RawOiTnhLocalTeeSameAddressMemoryCopyFact::from_region(oi_index.regions[0]) {
Some(fact) => trap_relaxed && fact.proves_trap_relaxed_escape()
None => false
}
let stack_bulk_region = RawOiStackBulkMemoryRegionFact::from_index(
oi_index, module_ctx,
)
let tiny_stack_bulk_memory = match stack_bulk_region {
Some(fact) => fact.proves_binaryen_tiny_rewrite_region()
None => false
}
let zero_size_stack_bulk_memory_region = match stack_bulk_region {
Some(fact) => fact.proves_trap_relaxed_zero_size_drop_region()
None => false
}
let zero_size_stack_bulk_memory = trap_relaxed &&
zero_size_stack_bulk_memory_region
if RawOiMemoryLocalTeeRegionFact::from_index(
oi_index, localtee_same_address_memory_copy,
).has_blocking_use {
return Some({
reason: "memory-localtee-optimize-instructions",
func: skipped_func,
unchanged_original: skipped_unchanged,
trace_skip_raw: true,
})
}
match RawOiCoarseNoopSkipFact::from_large_lowered(locals.length(), stats) {
Some(fact) if fact.proves_accepted_raw_boundary() =>
return Some({
reason: fact.reason,
func: skipped_func,
unchanged_original: skipped_unchanged,
trace_skip_raw: true,
})
_ => ()
}
let validation_module_env = run_hot_pipeline_require_validation_module_env(
mod_, module_state, options,
)
let env = run_hot_pipeline_raw_simplify_locals_func_env(
mod_,
func_idx,
module_ctx,
locals,
module_env=Some(validation_module_env),
)
let simple_stack_call_binop = match
RawOiSimpleStackCallBinopFact::from_index(oi_index, body, module_ctx) {
Some(fact) => fact.proves_binaryen_stack_localization()
None => false
}
let call_indexed_table_get_call_ref = match
RawOiCallIndexedTableGetCallRefFact::from_index(oi_index, body, module_ctx) {
Some(fact) => fact.proves_binaryen_call_ref_directization()
None => false
}
let module_revision_facts = RawOiModuleFunctionRevisionFacts::from_index(
oi_index, module_ctx,
)
let gc_selected_operand_forwarding = match
RawOiGcSelectedOperandForwardingFact::from_facts(module_revision_facts) {
Some(fact) => fact.proves_binaryen_forwarding()
None => false
}
let gc_effect_localization = match
RawOiGcEffectLocalizationFact::from_facts(module_revision_facts) {
Some(fact) => fact.proves_binaryen_localization()
None => false
}
let idempotent_call_self_select = match
RawOiIdempotentCallSelfSelectFact::from_index(oi_index, module_ctx) {
Some(fact) => fact.proves_hot_replay()
None => false
}
let stack_value_transfers = RawOiFunctionStackValueTransferFacts::from_index(
oi_index, env,
)
let stack_carried_effect_skip = RawOiStackCarriedEffectSkipFact::from_parts(
RawOiStackCarriedEffectRegionFact::from_index(
oi_index, stack_value_transfers, module_ctx,
),
simple_stack_call_binop,
tiny_stack_bulk_memory,
localtee_same_address_memory_copy,
zero_size_stack_bulk_memory,
call_indexed_table_get_call_ref,
gc_selected_operand_forwarding,
gc_effect_localization,
idempotent_call_self_select,
)
if stack_carried_effect_skip.proves_accepted_raw_noop() {
return Some({
reason: "stack-carried-effect-optimize-instructions-noop",
func: skipped_func,
unchanged_original: skipped_unchanged,
trace_skip_raw: true,
})
}
let const_offset_load_then_call = match
RawOiConstOffsetLoadThenCallFact::from_index(oi_index) {
Some(fact) => fact.proves_binaryen_offset_fold_bypass()
None => false
}
match
RawOiCoarseNoopSkipFact::from_structured_or_call_boundary(
locals.length(),
stats,
const_offset_load_then_call,
) {
Some(fact) if fact.proves_accepted_raw_boundary() =>
return Some({
reason: fact.reason,
func: skipped_func,
unchanged_original: skipped_unchanged,
trace_skip_raw: true,
})
_ => ()
}
if (const_fold_changed || effectful_sibling_changed) &&
oi_index.can_finish_scalar_rewrites_in_hot() {
// Let the normal HOT visit optimize the original tree once. Returning the
// partial raw result here deferred its dependent folds until another pass.
return None
}
if stats.ifs > 0 &&
(
directized_changed ||
effectful_sibling_changed ||
escaping_ref_get_desc_changed
) {
return Some({
reason: "raw-partial-optimize-instructions",
func: skipped_func,
unchanged_original: false,
trace_skip_raw: true,
})
}
if const_fold_changed {
return Some({
reason: "raw-const-fold-optimize-instructions",
func: skipped_func,
unchanged_original: false,
trace_skip_raw: true,
})
}
None
}
///|
fn run_hot_pipeline_raw_heap_store_optimization(
mod_ : @lib.Module,
func_idx : @lib.FuncIdx,
descriptor : @ir.HotPassDescriptor,
options : HotPipelineOptions,
) -> HotPipelineFuncResult? {
if descriptor.name != "heap-store-optimization" {
return None
}
let original = run_hot_pipeline_original_func(mod_, func_idx)
let @lib.Func(locals, @lib.Expr(instrs)) = original
let plain_start_us = perf_start_timer_opt(
options.perf,
"pass:heap-store-optimization",
)
match
run_hot_pipeline_raw_heap_store_optimization_plain_struct_new_chain(
mod_, instrs,
) {
Some((rewritten, changed)) => {
perf_stop_timer_opt(
options.perf,
"pass:heap-store-optimization",
plain_start_us,
)
return Some({
reason: "raw-plain-struct-new-heap-store-chain",
func: @lib.Func::new(locals, @lib.Expr::new(rewritten)),
unchanged_original: !changed,
trace_skip_raw: false,
})
}
None => ()
}
match
run_hot_pipeline_raw_heap_store_optimization_complete_default_chain(
mod_, instrs,
) {
Some((rewritten, changed)) =>
return Some({
reason: "raw-complete-default-heap-store-chain",
func: @lib.Func::new(locals, @lib.Expr::new(rewritten)),
unchanged_original: !changed,
trace_skip_raw: true,
})
None => ()
}
match run_hot_pipeline_raw_heap_store_optimization_allocation_sink(instrs) {
Some((rewritten, changed)) =>
return Some({
reason: "raw-allocation-only-struct-new-sink",
func: @lib.Func::new(locals, @lib.Expr::new(rewritten)),
unchanged_original: !changed,
trace_skip_raw: true,
})
None => ()
}
let (has_local_writes, has_struct_alloc, _) = run_hot_pipeline_hso_candidate_scan(
instrs,
)
if has_local_writes && has_struct_alloc {
return None
}
let (cleaned_body, cleanup_changed) = pass_raw_remove_dropped_unreachable_debris(
instrs,
)
Some({
reason: "no-heap-store-candidates",
func: if cleanup_changed {
@lib.Func::new(locals, @lib.Expr::new(cleaned_body))
} else {
original
},
unchanged_original: !cleanup_changed,
trace_skip_raw: true,
})
}
///|
fn run_hot_pipeline_raw_pls_direct_extension(
instrs : Array[@lib.Instruction],
) -> Array[@lib.Instruction]? {
if instrs.length() != 5 {
return None
}
match (instrs[1], instrs[2], instrs[3], instrs[4]) {
(
@lib.I32Load8U(memarg),
@lib.LocalSet(set_local),
@lib.LocalGet(get_local),
@lib.I32Extend8S,
) if set_local == get_local =>
Some([
instrs[0],
@lib.Instruction::i32_load8s(memarg),
instrs[2],
instrs[3],
instrs[4],
])
(
@lib.I32Load16U(memarg),
@lib.LocalSet(set_local),
@lib.LocalGet(get_local),
@lib.I32Extend16S,
) if set_local == get_local =>
Some([
instrs[0],
@lib.Instruction::i32_load16s(memarg),
instrs[2],
instrs[3],
instrs[4],
])
(
@lib.I64Load8U(memarg),
@lib.LocalSet(set_local),
@lib.LocalGet(get_local),
@lib.I64Extend8S,
) if set_local == get_local =>
Some([
instrs[0],
@lib.Instruction::i64_load8s(memarg),
instrs[2],
instrs[3],
])
(
@lib.I64Load16U(memarg),
@lib.LocalSet(set_local),
@lib.LocalGet(get_local),
@lib.I64Extend16S,
) if set_local == get_local =>
Some([
instrs[0],
@lib.Instruction::i64_load16s(memarg),
instrs[2],
instrs[3],
])
(
@lib.I64Load32U(memarg),
@lib.LocalSet(set_local),
@lib.LocalGet(get_local),
@lib.I64Extend32S,
) if set_local == get_local =>
Some([
instrs[0],
@lib.Instruction::i64_load32s(memarg),
instrs[2],
instrs[3],
])
_ => None
}
}
///|
fn run_hot_pipeline_raw_pls_mask(
instrs : Array[@lib.Instruction],
) -> Array[@lib.Instruction]? {
if instrs.length() != 6 {
return None
}
match (instrs[1], instrs[2], instrs[3], instrs[4], instrs[5]) {
(
@lib.I32Load8S(memarg),
@lib.LocalSet(set_local),
@lib.LocalGet(get_local),
@lib.I32Const(@lib.I32(255)),
@lib.I32And,
) if set_local == get_local =>
Some([
instrs[0],
@lib.Instruction::i32_load8u(memarg),
instrs[2],
instrs[3],
instrs[4],
instrs[5],
])
(
@lib.I32Load8S(memarg),
@lib.LocalSet(set_local),
@lib.I32Const(@lib.I32(255)),
@lib.LocalGet(get_local),
@lib.I32And,
) if set_local == get_local =>
Some([
instrs[0],
@lib.Instruction::i32_load8u(memarg),
instrs[2],
instrs[4],
])
(
@lib.I32Load16S(memarg),
@lib.LocalSet(set_local),
@lib.LocalGet(get_local),
@lib.I32Const(@lib.I32(65535)),
@lib.I32And,
) if set_local == get_local =>
Some([
instrs[0],
@lib.Instruction::i32_load16u(memarg),
instrs[2],
instrs[3],
instrs[4],
instrs[5],
])
(
@lib.I64Load8S(memarg),
@lib.LocalSet(set_local),
@lib.LocalGet(get_local),
@lib.I64Const(@lib.I64(255L)),
@lib.I64And,
) if set_local == get_local =>
Some([
instrs[0],
@lib.Instruction::i64_load8u(memarg),
instrs[2],
instrs[3],
])
(
@lib.I64Load16S(memarg),
@lib.LocalSet(set_local),
@lib.LocalGet(get_local),
@lib.I64Const(@lib.I64(65535L)),
@lib.I64And,
) if set_local == get_local =>
Some([
instrs[0],
@lib.Instruction::i64_load16u(memarg),
instrs[2],
instrs[3],
])
(
@lib.I64Load32S(memarg),
@lib.LocalSet(set_local),
@lib.LocalGet(get_local),
@lib.I64Const(@lib.I64(4294967295L)),
@lib.I64And,
) if set_local == get_local =>
Some([
instrs[0],
@lib.Instruction::i64_load32u(memarg),
instrs[2],
instrs[3],
])
_ => None
}
}
///|
fn run_hot_pipeline_raw_pls_unsigned_shift(
instrs : Array[@lib.Instruction],
) -> Array[@lib.Instruction]? {
if instrs.length() != 8 {
return None
}
match
(
instrs[1],
instrs[2],
instrs[3],
instrs[4],
instrs[5],
instrs[6],
instrs[7],
) {
(
@lib.I32Load8U(memarg),
@lib.LocalSet(set_local),
@lib.LocalGet(get_local),
@lib.I32Const(@lib.I32(24)),
@lib.I32Shl,
@lib.I32Const(@lib.I32(24)),
@lib.I32ShrS,
) if set_local == get_local =>
Some([
instrs[0],
@lib.Instruction::i32_load8s(memarg),
instrs[2],
instrs[3],
instrs[4],
instrs[5],
instrs[6],
instrs[7],
])
(
@lib.I32Load16U(memarg),
@lib.LocalSet(set_local),
@lib.LocalGet(get_local),
@lib.I32Const(@lib.I32(16)),
@lib.I32Shl,
@lib.I32Const(@lib.I32(16)),
@lib.I32ShrS,
) if set_local == get_local =>
Some([
instrs[0],
@lib.Instruction::i32_load16s(memarg),
instrs[2],
instrs[3],
instrs[4],
instrs[5],
instrs[6],
instrs[7],
])
(
@lib.I32Load8S(memarg),
@lib.LocalSet(set_local),
@lib.LocalGet(get_local),
@lib.I32Const(@lib.I32(24)),
@lib.I32Shl,
@lib.I32Const(@lib.I32(24)),
@lib.I32ShrU,
) if set_local == get_local =>
Some([
instrs[0],
@lib.Instruction::i32_load8u(memarg),
instrs[2],
instrs[3],
])
(
@lib.I32Load16S(memarg),
@lib.LocalSet(set_local),
@lib.LocalGet(get_local),
@lib.I32Const(@lib.I32(16)),
@lib.I32Shl,
@lib.I32Const(@lib.I32(16)),
@lib.I32ShrU,
) if set_local == get_local =>
Some([
instrs[0],
@lib.Instruction::i32_load16u(memarg),
instrs[2],
instrs[3],
])
(
@lib.I64Load8S(memarg),
@lib.LocalSet(set_local),
@lib.LocalGet(get_local),
@lib.I64Const(@lib.I64(56L)),
@lib.I64Shl,
@lib.I64Const(@lib.I64(56L)),
@lib.I64ShrU,
) if set_local == get_local =>
Some([
instrs[0],
@lib.Instruction::i64_load8u(memarg),
instrs[2],
instrs[3],
])
(
@lib.I64Load16S(memarg),
@lib.LocalSet(set_local),
@lib.LocalGet(get_local),
@lib.I64Const(@lib.I64(48L)),
@lib.I64Shl,
@lib.I64Const(@lib.I64(48L)),
@lib.I64ShrU,
) if set_local == get_local =>
Some([
instrs[0],
@lib.Instruction::i64_load16u(memarg),
instrs[2],
instrs[3],
])
(
@lib.I64Load32S(memarg),
@lib.LocalSet(set_local),
@lib.LocalGet(get_local),
@lib.I64Const(@lib.I64(32L)),
@lib.I64Shl,
@lib.I64Const(@lib.I64(32L)),
@lib.I64ShrU,
) if set_local == get_local =>
Some([
instrs[0],
@lib.Instruction::i64_load32u(memarg),
instrs[2],
instrs[3],
])
(
@lib.I64Load8U(memarg),
@lib.LocalSet(set_local),
@lib.LocalGet(get_local),
@lib.I64Const(@lib.I64(56L)),
@lib.I64Shl,
@lib.I64Const(@lib.I64(56L)),
@lib.I64ShrS,
) if set_local == get_local =>
Some([
instrs[0],
@lib.Instruction::i64_load8s(memarg),
instrs[2],
instrs[3],
])
(
@lib.I64Load16U(memarg),
@lib.LocalSet(set_local),
@lib.LocalGet(get_local),
@lib.I64Const(@lib.I64(48L)),
@lib.I64Shl,
@lib.I64Const(@lib.I64(48L)),
@lib.I64ShrS,
) if set_local == get_local =>
Some([
instrs[0],
@lib.Instruction::i64_load16s(memarg),
instrs[2],
instrs[3],
])
(
@lib.I64Load32U(memarg),
@lib.LocalSet(set_local),
@lib.LocalGet(get_local),
@lib.I64Const(@lib.I64(32L)),
@lib.I64Shl,
@lib.I64Const(@lib.I64(32L)),
@lib.I64ShrS,
) if set_local == get_local =>
Some([
instrs[0],
@lib.Instruction::i64_load32s(memarg),
instrs[2],
instrs[3],
])
_ => None
}
}
///|
fn run_hot_pipeline_raw_pick_load_signs(
mod_ : @lib.Module,
func_idx : @lib.FuncIdx,
descriptor : @ir.HotPassDescriptor,
) -> HotPipelineFuncResult? {
if descriptor.name != "pick-load-signs" {
return None
}
let original = run_hot_pipeline_original_func(mod_, func_idx)
let @lib.Func(locals, @lib.Expr(instrs)) = original
let raw_rewrite = match run_hot_pipeline_raw_pls_direct_extension(instrs) {
Some(rewritten) => Some(rewritten)
None =>
match run_hot_pipeline_raw_pls_mask(instrs) {
Some(rewritten) => Some(rewritten)
None => run_hot_pipeline_raw_pls_unsigned_shift(instrs)
}
}
match raw_rewrite {
Some(rewritten) =>
return Some({
reason: "raw-load-sign-evidence",
func: @lib.Func::new(locals, @lib.Expr::new(rewritten)),
unchanged_original: false,
trace_skip_raw: false,
})
None => ()
}
let (has_candidate_producer, has_local_get, has_extension_surface) = run_hot_pipeline_pls_candidate_scan(
instrs,
)
if has_candidate_producer && has_local_get && has_extension_surface {
return None
}
Some({
reason: "no-pick-load-signs-candidates",
func: original,
unchanged_original: true,
trace_skip_raw: true,
})
}
///|
fn run_hot_pipeline_tuple_has_effect_bracketed_multivalue_call(
instrs : Array[@lib.Instruction],
module_ctx : @ir.HotModuleContext,
) -> Bool {
for idx = 0; idx < instrs.length(); idx = idx + 1 {
let is_multivalue_call = match instrs[idx] {
@lib.Call(func_idx) =>
match @ir.hot_module_resolve_func_type(module_ctx, func_idx) {
Some(@lib.FuncType(_, results)) => results.length() >= 2
None => false
}
_ => false
}
if !is_multivalue_call {
continue
}
let before_start = if idx > 8 { idx - 8 } else { 0 }
let mut has_call_before = false
for scan = before_start; scan < idx; scan = scan + 1 {
match instrs[scan] {
@lib.Call(_) => {
has_call_before = true
break
}
_ => ()
}
}
if !has_call_before {
continue
}
let after_end = Int32::min(instrs.length(), idx + 9)
let mut has_call_after = false
let mut spill_defs = 0
for scan = idx + 1; scan < after_end; scan = scan + 1 {
match instrs[scan] {
@lib.Call(_) => has_call_after = true
@lib.LocalSet(_) | @lib.LocalTee(_) => spill_defs += 1
_ => ()
}
}
if has_call_after && spill_defs >= 2 {
return true
}
}
for instr in instrs {
match instr {
@lib.Block(_, @lib.Expr(body))
| @lib.Loop(_, @lib.Expr(body))
| @lib.TryTable(_, _, @lib.Expr(body)) =>
if run_hot_pipeline_tuple_has_effect_bracketed_multivalue_call(
body, module_ctx,
) {
return true
}
@lib.If(_, then_body, else_body) => {
if run_hot_pipeline_tuple_has_effect_bracketed_multivalue_call(
then_body, module_ctx,
) {
return true
}
match else_body {
Some(else_body) =>
if run_hot_pipeline_tuple_has_effect_bracketed_multivalue_call(
else_body, module_ctx,
) {
return true
}
None => ()
}
}
_ => ()
}
}
false
}
///|
fn run_hot_pipeline_raw_dropped_call_result_linear_effect(
instr : @lib.Instruction,
module_ctx : @ir.HotModuleContext,
) -> (Int32, Int32)? {
match instr {
@lib.Nop => Some((0, 0))
@lib.I32Const(_)
| @lib.I64Const(_)
| @lib.F32Const(_)
| @lib.F64Const(_)
| @lib.V128Const(_)
| @lib.RefNull(_)
| @lib.RefFunc(_)
| @lib.StringConst(_)
| @lib.GlobalGet(_)
| @lib.LocalGet(_) => Some((0, 1))
@lib.LocalSet(_) | @lib.GlobalSet(_) | @lib.Drop => Some((1, 0))
@lib.LocalTee(_) => Some((1, 1))
@lib.Call(func_idx) =>
match @ir.hot_module_resolve_func_type(module_ctx, func_idx) {
Some(@lib.FuncType(params, results)) =>
Some((params.length(), results.length()))
None => None
}
_ => None
}
}
///|
fn run_hot_pipeline_raw_has_dropped_call_result_effect_order(
instrs : Array[@lib.Instruction],
module_ctx : @ir.HotModuleContext,
) -> Bool {
for idx = 0; idx < instrs.length(); idx = idx + 1 {
match instrs[idx] {
@lib.Call(func_idx) =>
match @ir.hot_module_resolve_func_type(module_ctx, func_idx) {
Some(@lib.FuncType(_, results)) if results.length() == 1 => {
let mut values_above = 0
let mut saw_later_call = false
let mut scan = idx + 1
while scan < instrs.length() {
let effect = match
run_hot_pipeline_raw_dropped_call_result_linear_effect(
instrs[scan],
module_ctx,
) {
Some(effect) => effect
None => break
}
let (pop_count, push_count) = effect
if pop_count > values_above {
if instrs[scan] is @lib.Drop && saw_later_call {
return true
}
break
}
match instrs[scan] {
@lib.Call(_) => saw_later_call = true
_ => ()
}
values_above = values_above - pop_count + push_count
scan += 1
}
}
_ => ()
}
@lib.Block(_, @lib.Expr(body))
| @lib.Loop(_, @lib.Expr(body))
| @lib.TryTable(_, _, @lib.Expr(body)) =>
if run_hot_pipeline_raw_has_dropped_call_result_effect_order(
body, module_ctx,
) {
return true
}
@lib.If(_, then_body, else_body) => {
if run_hot_pipeline_raw_has_dropped_call_result_effect_order(
then_body, module_ctx,
) {
return true
}
match else_body {
Some(else_body) =>
if run_hot_pipeline_raw_has_dropped_call_result_effect_order(
else_body, module_ctx,
) {
return true
}
None => ()
}
}
_ => ()
}
}
false
}
///|
fn run_hot_pipeline_raw_tuple_block_may_produce_multivalue(
block_type : @lib.BlockType,
module_ctx : @ir.HotModuleContext,
) -> Bool {
match block_type {
@lib.VoidBlockType | @lib.ValTypeBlockType(_) => false
@lib.TypeIdxBlockType(type_idx) =>
match @ir.hot_module_resolve_type_func_type(module_ctx, type_idx) {
Some(@lib.FuncType(_, results)) => results.length() > 1
None => true
}
}
}
///|
fn run_hot_pipeline_raw_tuple_instr_may_produce_multivalue(
instr : @lib.Instruction,
module_ctx : @ir.HotModuleContext,
) -> Bool {
match instr {
@lib.Call(func_idx) =>
match @ir.hot_module_resolve_func_type(module_ctx, func_idx) {
Some(@lib.FuncType(_, results)) => results.length() > 1
None => true
}
@lib.CallIndirect(type_idx, _) | @lib.CallRef(type_idx) =>
match @ir.hot_module_resolve_type_func_type(module_ctx, type_idx) {
Some(@lib.FuncType(_, results)) => results.length() > 1
None => true
}
@lib.Block(block_type, _)
| @lib.Loop(block_type, _)
| @lib.If(block_type, _, _)
| @lib.TryTable(block_type, _, _) =>
run_hot_pipeline_raw_tuple_block_may_produce_multivalue(
block_type, module_ctx,
)
_ => false
}
}
///|
fn run_hot_pipeline_raw_tuple_has_multivalue_producer(
instrs : Array[@lib.Instruction],
module_ctx : @ir.HotModuleContext,
) -> Bool {
for instr in instrs {
if run_hot_pipeline_raw_tuple_instr_may_produce_multivalue(
instr, module_ctx,
) {
return true
}
match instr {
@lib.Block(_, @lib.Expr(body))
| @lib.Loop(_, @lib.Expr(body))
| @lib.TryTable(_, _, @lib.Expr(body)) =>
if run_hot_pipeline_raw_tuple_has_multivalue_producer(body, module_ctx) {
return true
}
@lib.If(_, then_body, else_body) => {
if run_hot_pipeline_raw_tuple_has_multivalue_producer(
then_body, module_ctx,
) {
return true
}
match else_body {
Some(else_body) =>
if run_hot_pipeline_raw_tuple_has_multivalue_producer(
else_body, module_ctx,
) {
return true
}
None => ()
}
}
_ => ()
}
}
false
}
///|
fn run_hot_pipeline_raw_tuple_optimization_skip(
mod_ : @lib.Module,
func_idx : @lib.FuncIdx,
module_state : HotPipelineModuleState,
descriptor : @ir.HotPassDescriptor,
options : HotPipelineOptions,
) -> HotPipelineFuncResult? {
if descriptor.name != "tuple-optimization" {
return None
}
let original = run_hot_pipeline_original_func(mod_, func_idx)
if options.optimize_level >= 4 && options.shrink_level >= 1 {
return Some({
reason: "o4z-tuple-optimization-noop",
func: original,
unchanged_original: true,
trace_skip_raw: true,
})
}
let @lib.Func(_, @lib.Expr(body)) = original
let module_ctx = run_hot_pipeline_require_module_ctx(
mod_, module_state, options,
)
if !run_hot_pipeline_raw_tuple_has_multivalue_producer(body, module_ctx) {
return Some({
reason: "candidate-free-tuple-optimization-noop",
func: original,
unchanged_original: true,
trace_skip_raw: true,
})
}
if run_hot_pipeline_tuple_has_effect_bracketed_multivalue_call(
body, module_ctx,
) {
return Some({
reason: "effect-bracketed-multivalue-call-tuple-noop",
func: original,
unchanged_original: true,
trace_skip_raw: true,
})
}
if run_hot_pipeline_dce_raw_has_load_call_set_hazard(body) {
return Some({
reason: "load-call-set-tuple-optimization-noop",
func: original,
unchanged_original: true,
trace_skip_raw: true,
})
}
None
}
///|
fn run_hot_pipeline_raw_precompute_has_nested_empty_block_shell(
instrs : Array[@lib.Instruction],
) -> Bool {
for instr in instrs {
match instr {
@lib.Block(_, @lib.Expr(body)) => {
if body.length() > 0 {
match body[0] {
@lib.Block(_, @lib.Expr(empty)) if empty.is_empty() => return true
_ => ()
}
}
if run_hot_pipeline_raw_precompute_has_nested_empty_block_shell(body) {
return true
}
}
@lib.Loop(_, @lib.Expr(body)) | @lib.TryTable(_, _, @lib.Expr(body)) =>
if run_hot_pipeline_raw_precompute_has_nested_empty_block_shell(body) {
return true
}
@lib.If(_, then_body, else_body) => {
if run_hot_pipeline_raw_precompute_has_nested_empty_block_shell(
then_body,
) {
return true
}
match else_body {
Some(else_body) =>
if run_hot_pipeline_raw_precompute_has_nested_empty_block_shell(
else_body,
) {
return true
}
None => ()
}
}
_ => ()
}
}
false
}
///|
fn run_hot_pipeline_raw_precompute_has_multi_read_arithmetic_local(
instrs : Array[@lib.Instruction],
) -> Bool {
for idx = 0; idx + 4 < instrs.length(); idx = idx + 1 {
match (instrs[idx], instrs[idx + 1], instrs[idx + 2], instrs[idx + 3]) {
(
@lib.LocalGet(_),
@lib.GlobalGet(_),
@lib.I32Sub
| @lib.I64Sub,
@lib.LocalSet(target),
) => {
let end = Int32::min(instrs.length(), idx + 32)
let mut reads = 0
for probe = idx + 4; probe < end; probe = probe + 1 {
match instrs[probe] {
@lib.LocalGet(read) if read == target => {
reads += 1
if reads >= 2 {
return true
}
}
@lib.LocalSet(written) | @lib.LocalTee(written) if written == target =>
break
_ => ()
}
}
}
_ => ()
}
}
for instr in instrs {
match instr {
@lib.Block(_, @lib.Expr(body))
| @lib.Loop(_, @lib.Expr(body))
| @lib.TryTable(_, _, @lib.Expr(body)) =>
if run_hot_pipeline_raw_precompute_has_multi_read_arithmetic_local(body) {
return true
}
@lib.If(_, then_body, else_body) => {
if run_hot_pipeline_raw_precompute_has_multi_read_arithmetic_local(
then_body,
) {
return true
}
match else_body {
Some(body) =>
if run_hot_pipeline_raw_precompute_has_multi_read_arithmetic_local(
body,
) {
return true
}
None => ()
}
}
_ => ()
}
}
false
}
///|
fn run_hot_pipeline_raw_precompute_instr_is_store(
instr : @lib.Instruction,
) -> Bool {
match instr {
@lib.I32Store(_)
| @lib.I64Store(_)
| @lib.F32Store(_)
| @lib.F64Store(_)
| @lib.I32Store8(_)
| @lib.I32Store16(_)
| @lib.I64Store8(_)
| @lib.I64Store16(_)
| @lib.I64Store32(_) => true
_ => false
}
}
///|
fn run_hot_pipeline_raw_precompute_has_call_result_across_memory_store(
instrs : Array[@lib.Instruction],
) -> Bool {
for idx = 0; idx + 2 < instrs.length(); idx = idx + 1 {
match (instrs[idx], instrs[idx + 1]) {
(
@lib.Call(_)
| @lib.CallIndirect(_, _)
| @lib.CallRef(_),
@lib.LocalSet(result_local),
) => {
let end = Int32::min(instrs.length(), idx + 24)
let mut saw_store = false
for probe = idx + 2; probe < end; probe = probe + 1 {
if run_hot_pipeline_raw_precompute_instr_is_store(instrs[probe]) {
saw_store = true
continue
}
match instrs[probe] {
@lib.LocalGet(read) if read == result_local && saw_store =>
return true
@lib.LocalSet(written) | @lib.LocalTee(written) if written ==
result_local => break
_ => ()
}
}
}
_ => ()
}
}
for instr in instrs {
match instr {
@lib.Block(_, @lib.Expr(body))
| @lib.Loop(_, @lib.Expr(body))
| @lib.TryTable(_, _, @lib.Expr(body)) =>
if run_hot_pipeline_raw_precompute_has_call_result_across_memory_store(
body,
) {
return true
}
@lib.If(_, then_body, else_body) => {
if run_hot_pipeline_raw_precompute_has_call_result_across_memory_store(
then_body,
) {
return true
}
match else_body {
Some(body) =>
if run_hot_pipeline_raw_precompute_has_call_result_across_memory_store(
body,
) {
return true
}
None => ()
}
}
_ => ()
}
}
false
}
///|
fn run_hot_pipeline_raw_precompute_has_indexed_load_alias_release(
instrs : Array[@lib.Instruction],
) -> Bool {
for idx = 0; idx + 8 < instrs.length(); idx = idx + 1 {
if run_hot_pipeline_optimize_instructions_instr_is_plain_load(
instrs[idx + 6],
) {
match
(
instrs[idx],
instrs[idx + 1],
instrs[idx + 2],
instrs[idx + 3],
instrs[idx + 4],
instrs[idx + 5],
instrs[idx + 7],
instrs[idx + 8],
) {
(
@lib.LocalGet(_),
@lib.LocalTee(alias_local),
@lib.LocalGet(_),
@lib.I32Const(_),
@lib.I32Shl,
@lib.I32Add,
@lib.LocalGet(released),
@lib.Call(_),
) if alias_local == released => return true
_ => ()
}
}
}
for instr in instrs {
match instr {
@lib.Block(_, @lib.Expr(body))
| @lib.Loop(_, @lib.Expr(body))
| @lib.TryTable(_, _, @lib.Expr(body)) =>
if run_hot_pipeline_raw_precompute_has_indexed_load_alias_release(body) {
return true
}
@lib.If(_, then_body, else_body) => {
if run_hot_pipeline_raw_precompute_has_indexed_load_alias_release(
then_body,
) {
return true
}
match else_body {
Some(body) =>
if run_hot_pipeline_raw_precompute_has_indexed_load_alias_release(
body,
) {
return true
}
None => ()
}
}
_ => ()
}
}
false
}
///|
fn run_hot_pipeline_raw_precompute_has_call_result_release_stack_carrier(
instrs : Array[@lib.Instruction],
) -> Bool {
for idx = 0; idx + 3 < instrs.length(); idx = idx + 1 {
match (instrs[idx], instrs[idx + 1], instrs[idx + 2], instrs[idx + 3]) {
(
@lib.Call(_)
| @lib.CallIndirect(_, _)
| @lib.CallRef(_),
@lib.LocalGet(_),
@lib.Call(_),
@lib.LocalSet(_),
) => return true
_ => ()
}
}
for instr in instrs {
match instr {
@lib.Block(_, @lib.Expr(body))
| @lib.Loop(_, @lib.Expr(body))
| @lib.TryTable(_, _, @lib.Expr(body)) =>
if run_hot_pipeline_raw_precompute_has_call_result_release_stack_carrier(
body,
) {
return true
}
@lib.If(_, then_body, else_body) => {
if run_hot_pipeline_raw_precompute_has_call_result_release_stack_carrier(
then_body,
) {
return true
}
match else_body {
Some(body) =>
if run_hot_pipeline_raw_precompute_has_call_result_release_stack_carrier(
body,
) {
return true
}
None => ()
}
}
_ => ()
}
}
false
}
///|
fn run_hot_pipeline_raw_has_call_result_alias_later_call(
instrs : Array[@lib.Instruction],
) -> Bool {
for idx = 0; idx + 4 < instrs.length(); idx = idx + 1 {
match
(
instrs[idx],
instrs[idx + 1],
instrs[idx + 2],
instrs[idx + 3],
instrs[idx + 4],
) {
(
@lib.Call(_),
@lib.LocalTee(alias_local),
@lib.Call(_),
@lib.LocalGet(later_alias),
@lib.Call(_),
) if alias_local == later_alias => return true
_ => ()
}
}
for instr in instrs {
match instr {
@lib.Block(_, @lib.Expr(body))
| @lib.Loop(_, @lib.Expr(body))
| @lib.TryTable(_, _, @lib.Expr(body)) =>
if run_hot_pipeline_raw_has_call_result_alias_later_call(body) {
return true
}
@lib.If(_, then_body, else_body) => {
if run_hot_pipeline_raw_has_call_result_alias_later_call(then_body) {
return true
}
match else_body {
Some(body) =>
if run_hot_pipeline_raw_has_call_result_alias_later_call(body) {
return true
}
None => ()
}
}
_ => ()
}
}
false
}
///|
fn run_hot_pipeline_raw_precompute_has_call_result_local_reload(
instrs : Array[@lib.Instruction],
) -> Bool {
for idx = 0; idx + 2 < instrs.length(); idx = idx + 1 {
match (instrs[idx], instrs[idx + 1], instrs[idx + 2]) {
(
@lib.Call(_)
| @lib.CallIndirect(_, _)
| @lib.CallRef(_),
@lib.LocalSet(written),
@lib.LocalGet(read),
) if written == read => return true
_ => ()
}
}
for instr in instrs {
match instr {
@lib.Block(_, @lib.Expr(body))
| @lib.Loop(_, @lib.Expr(body))
| @lib.TryTable(_, _, @lib.Expr(body)) =>
if run_hot_pipeline_raw_precompute_has_call_result_local_reload(body) {
return true
}
@lib.If(_, then_body, else_body) => {
if run_hot_pipeline_raw_precompute_has_call_result_local_reload(
then_body,
) {
return true
}
match else_body {
Some(body) =>
if run_hot_pipeline_raw_precompute_has_call_result_local_reload(
body,
) {
return true
}
None => ()
}
}
_ => ()
}
}
false
}
///|
fn run_hot_pipeline_raw_precompute_has_stack_carried_overwritten_local(
instrs : Array[@lib.Instruction],
) -> Bool {
for start = 0; start < instrs.length(); start = start + 1 {
let carrier = match instrs[start] {
@lib.LocalGet(carrier) => carrier
_ => continue
}
let mut values_above_carrier = 0
let mut overwritten_while_live = false
let mut carrier_alive = true
let end = Int32::min(instrs.length(), start + 24)
for idx = start + 1; idx < end; idx = idx + 1 {
match instrs[idx] {
@lib.LocalGet(_) => values_above_carrier += 1
@lib.LocalSet(written) =>
if values_above_carrier > 0 {
values_above_carrier -= 1
if written == carrier {
overwritten_while_live = true
}
} else {
if overwritten_while_live {
return true
}
carrier_alive = false
break
}
@lib.LocalTee(written) =>
if values_above_carrier > 0 {
if written == carrier {
overwritten_while_live = true
}
} else if overwritten_while_live {
return true
}
@lib.Drop =>
if values_above_carrier > 0 {
values_above_carrier -= 1
} else {
carrier_alive = false
break
}
@lib.Nop => ()
_ => {
if overwritten_while_live {
return true
}
carrier_alive = false
break
}
}
}
if carrier_alive && overwritten_while_live {
return true
}
}
for start = 0; start + 3 < instrs.length(); start = start + 1 {
match
(instrs[start], instrs[start + 1], instrs[start + 2], instrs[start + 3]) {
(
@lib.LocalGet(carrier),
@lib.I32Const(_),
@lib.LocalGet(_),
@lib.LocalSet(overwritten),
) if carrier == overwritten => {
let end = Int32::min(instrs.length(), start + 22)
for idx = start + 4; idx < end; idx = idx + 1 {
match instrs[idx] {
@lib.Call(_) | @lib.CallIndirect(_, _) | @lib.CallRef(_) =>
return true
_ => ()
}
}
}
_ => ()
}
match (instrs[start], instrs[start + 1], instrs[start + 2]) {
(@lib.LocalGet(carrier), @lib.LocalGet(_), @lib.LocalSet(overwritten)) if carrier ==
overwritten => {
let end = Int32::min(instrs.length(), start + 19)
let mut saw_reloaded = false
for idx = start + 3; idx < end; idx = idx + 1 {
match instrs[idx] {
@lib.LocalGet(reloaded) if reloaded == carrier =>
saw_reloaded = true
@lib.Call(_) | @lib.CallIndirect(_, _) | @lib.CallRef(_) if saw_reloaded =>
return true
@lib.LocalSet(written) | @lib.LocalTee(written) if written ==
carrier => break
_ => ()
}
}
}
_ => ()
}
}
for instr in instrs {
match instr {
@lib.Block(_, @lib.Expr(body))
| @lib.Loop(_, @lib.Expr(body))
| @lib.TryTable(_, _, @lib.Expr(body)) =>
if run_hot_pipeline_raw_precompute_has_stack_carried_overwritten_local(
body,
) {
return true
}
@lib.If(_, then_body, else_body) => {
if run_hot_pipeline_raw_precompute_has_stack_carried_overwritten_local(
then_body,
) {
return true
}
match else_body {
Some(body) =>
if run_hot_pipeline_raw_precompute_has_stack_carried_overwritten_local(
body,
) {
return true
}
None => ()
}
}
_ => ()
}
}
false
}
///|
fn run_hot_pipeline_raw_precompute_const_simd_reduction_count(
instrs : Array[@lib.Instruction],
) -> Int32 {
let mut count = 0
for idx = 0; idx + 1 < instrs.length(); idx = idx + 1 {
match instrs[idx] {
@lib.V128Const(_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _) =>
match instrs[idx + 1] {
@lib.V128AnyTrue
| @lib.I8x16AllTrue
| @lib.I8x16Bitmask
| @lib.I16x8AllTrue
| @lib.I16x8Bitmask
| @lib.I32x4AllTrue
| @lib.I32x4Bitmask
| @lib.I64x2AllTrue
| @lib.I64x2Bitmask => count += 1
_ => ()
}
_ => ()
}
}
for instr in instrs {
match instr {
@lib.Block(_, @lib.Expr(body))
| @lib.Loop(_, @lib.Expr(body))
| @lib.TryTable(_, _, @lib.Expr(body)) =>
count += run_hot_pipeline_raw_precompute_const_simd_reduction_count(
body,
)
@lib.If(_, then_body, else_body) => {
count += run_hot_pipeline_raw_precompute_const_simd_reduction_count(
then_body,
)
match else_body {
Some(body) =>
count += run_hot_pipeline_raw_precompute_const_simd_reduction_count(
body,
)
None => ()
}
}
_ => ()
}
}
count
}
///|
fn run_hot_pipeline_raw_precompute_additive_local_update_count(
instrs : Array[@lib.Instruction],
) -> Int32 {
let mut count = 0
for idx = 0; idx + 2 < instrs.length(); idx = idx + 1 {
match (instrs[idx], instrs[idx + 1], instrs[idx + 2]) {
(@lib.I32Const(_), @lib.I32Add, @lib.LocalSet(_) | @lib.LocalTee(_)) =>
count += 1
_ => ()
}
}
for instr in instrs {
match instr {
@lib.Block(_, @lib.Expr(body))
| @lib.Loop(_, @lib.Expr(body))
| @lib.TryTable(_, _, @lib.Expr(body)) =>
count += run_hot_pipeline_raw_precompute_additive_local_update_count(
body,
)
@lib.If(_, then_body, else_body) => {
count += run_hot_pipeline_raw_precompute_additive_local_update_count(
then_body,
)
match else_body {
Some(body) =>
count += run_hot_pipeline_raw_precompute_additive_local_update_count(
body,
)
None => ()
}
}
_ => ()
}
}
count
}
///|
fn run_hot_pipeline_raw_precompute_has_tee_backed_dynamic_global_write(
instrs : Array[@lib.Instruction],
) -> Bool {
for set_pos = 1; set_pos < instrs.length(); set_pos = set_pos + 1 {
let (carrier, target) = match (instrs[set_pos - 1], instrs[set_pos]) {
(@lib.LocalGet(carrier), @lib.GlobalSet(target)) => (carrier, target)
_ => continue
}
let mut saw_tee = false
for pos = 0; pos < set_pos - 1; pos = pos + 1 {
match instrs[pos] {
@lib.LocalTee(written) if written == carrier => saw_tee = true
_ => ()
}
}
if !saw_tee {
continue
}
for pos = set_pos + 1; pos < instrs.length(); pos = pos + 1 {
match instrs[pos] {
@lib.GlobalGet(read) if read == target => return true
_ => ()
}
}
}
for instr in instrs {
match instr {
@lib.Block(_, @lib.Expr(body))
| @lib.Loop(_, @lib.Expr(body))
| @lib.TryTable(_, _, @lib.Expr(body)) =>
if run_hot_pipeline_raw_precompute_has_tee_backed_dynamic_global_write(
body,
) {
return true
}
@lib.If(_, then_body, else_body) => {
if run_hot_pipeline_raw_precompute_has_tee_backed_dynamic_global_write(
then_body,
) {
return true
}
match else_body {
Some(body) =>
if run_hot_pipeline_raw_precompute_has_tee_backed_dynamic_global_write(
body,
) {
return true
}
None => ()
}
}
_ => ()
}
}
false
}
///|
fn run_hot_pipeline_raw_precompute(
mod_ : @lib.Module,
func_idx : @lib.FuncIdx,
module_state : HotPipelineModuleState,
descriptor : @ir.HotPassDescriptor,
options : HotPipelineOptions,
instruction_scans~ : Array[Int32]? = None,
) -> HotPipelineFuncResult? {
let propagating = descriptor.name == "precompute-propagate"
if descriptor.name != "precompute" && !propagating {
return None
}
let original = run_hot_pipeline_original_func(mod_, func_idx)
let o4z_precompute_gate = options.optimize_level >= 4 &&
options.shrink_level >= 1
let @lib.Func(locals, @lib.Expr(body)) = original
if run_hot_pipeline_raw_has_root_global_snapshot_write_prefix(body) {
return Some({
reason: "root-global-snapshot-write-prefix-\{descriptor.name}-noop",
func: original,
unchanged_original: true,
trace_skip_raw: true,
})
}
if propagating &&
run_hot_pipeline_raw_precompute_has_tee_backed_dynamic_global_write(body) {
return Some({
reason: "tee-backed-dynamic-global-write-precompute-propagate-noop",
func: original,
unchanged_original: true,
trace_skip_raw: true,
})
}
if run_hot_pipeline_raw_precompute_has_stack_carried_overwritten_local(body) {
return Some({
reason: "stack-carried-overwritten-local-\{descriptor.name}-noop",
func: original,
unchanged_original: true,
trace_skip_raw: true,
})
}
if propagating &&
run_hot_pipeline_raw_precompute_has_multi_read_arithmetic_local(body) {
return Some({
reason: "multi-read-arithmetic-local-precompute-propagate-noop",
func: original,
unchanged_original: true,
trace_skip_raw: true,
})
}
if propagating && run_hot_pipeline_raw_has_load_before_same_local_call(body) {
return Some({
reason: "load-before-release-precompute-propagate-noop",
func: original,
unchanged_original: true,
trace_skip_raw: true,
})
}
if propagating &&
run_hot_pipeline_raw_precompute_has_indexed_load_alias_release(body) {
return Some({
reason: "indexed-load-alias-release-precompute-propagate-noop",
func: original,
unchanged_original: true,
trace_skip_raw: true,
})
}
if propagating &&
run_hot_pipeline_raw_precompute_has_call_result_across_memory_store(body) {
return Some({
reason: "call-result-memory-store-precompute-propagate-noop",
func: original,
unchanged_original: true,
trace_skip_raw: true,
})
}
if propagating &&
run_hot_pipeline_raw_simplify_locals_has_call_bulk_memory_effect_mesh(body) &&
run_hot_pipeline_raw_precompute_has_call_result_local_reload(body) {
return Some({
reason: "call-result-memory-copy-precompute-propagate-noop",
func: original,
unchanged_original: true,
trace_skip_raw: true,
})
}
if propagating &&
run_hot_pipeline_raw_remove_unused_brs_has_comparison_flag_if_hazard(body) {
return Some({
reason: "comparison-flag-if-precompute-propagate-noop",
func: original,
unchanged_original: true,
trace_skip_raw: true,
})
}
if propagating &&
run_hot_pipeline_raw_precompute_has_nested_empty_block_shell(body) {
return Some({
reason: "nested-empty-block-label-owner-precompute-propagate-noop",
func: original,
unchanged_original: true,
trace_skip_raw: true,
})
}
if propagating && locals.length() >= 128 {
let shape = RawRemoveUnusedBrsShape::new()
run_hot_pipeline_raw_remove_unused_brs_scan_shape(body, shape)
if shape.br_count + shape.br_if_count + shape.br_table_count >= 16 {
return Some({
reason: "large-branch-local-label-owner-precompute-propagate-noop",
func: original,
unchanged_original: true,
trace_skip_raw: true,
})
}
}
if propagating {
let module_ctx = run_hot_pipeline_require_module_ctx(
mod_, module_state, options,
)
let params = run_hot_pipeline_required_func_params(module_ctx, func_idx)
if run_hot_pipeline_raw_instrs_have_memory_access(body) &&
run_hot_pipeline_raw_has_parameter_read_before_later_write(
body,
params.length(),
) {
return Some({
reason: "parameter-read-before-write-precompute-propagate-noop",
func: original,
unchanged_original: true,
trace_skip_raw: true,
})
}
}
if propagating &&
run_hot_pipeline_raw_simplify_locals_has_call_result_structured_lifetime(
body,
) {
return Some({
reason: "call-result-structured-lifetime-precompute-propagate-noop",
func: original,
unchanged_original: true,
trace_skip_raw: true,
})
}
if propagating && run_hot_pipeline_raw_has_alias_call_structured_release(body) {
return Some({
reason: "call-argument-structured-release-precompute-propagate-noop",
func: original,
unchanged_original: true,
trace_skip_raw: true,
})
}
if propagating && run_hot_pipeline_raw_has_call_argument_direct_release(body) {
return Some({
reason: "call-argument-direct-release-precompute-propagate-noop",
func: original,
unchanged_original: true,
trace_skip_raw: true,
})
}
if propagating {
let module_ctx = run_hot_pipeline_require_module_ctx(
mod_, module_state, options,
)
if run_hot_pipeline_raw_has_dropped_call_result_effect_order(
body, module_ctx,
) {
return Some({
reason: "dropped-call-result-effect-order-precompute-propagate-noop",
func: original,
unchanged_original: true,
trace_skip_raw: true,
})
}
if run_hot_pipeline_tuple_has_effect_bracketed_multivalue_call(
body, module_ctx,
) {
return Some({
reason: "effect-bracketed-multivalue-call-precompute-propagate-noop",
func: original,
unchanged_original: true,
trace_skip_raw: true,
})
}
}
if propagating &&
run_hot_pipeline_raw_simplify_locals_has_call_local_tee_lifetime(body) {
return Some({
reason: "call-local-tee-lifetime-precompute-propagate-noop",
func: original,
unchanged_original: true,
trace_skip_raw: true,
})
}
if propagating &&
run_hot_pipeline_raw_precompute_has_call_result_release_stack_carrier(body) {
return Some({
reason: "call-result-release-stack-carrier-precompute-propagate-noop",
func: original,
unchanged_original: true,
trace_skip_raw: true,
})
}
if propagating &&
run_hot_pipeline_raw_precompute_has_call_result_local_reload(body) {
return Some({
reason: "call-result-local-reload-precompute-propagate-noop",
func: original,
unchanged_original: true,
trace_skip_raw: true,
})
}
if propagating &&
run_hot_pipeline_raw_has_single_argument_distinct_call_lifetime(body) {
return Some({
reason: "single-argument-distinct-call-lifetime-precompute-propagate-noop",
func: original,
unchanged_original: true,
trace_skip_raw: true,
})
}
let stats = OptimizeInstructionsLoweredStats::new()
if propagating || locals.length() > 64 {
match instruction_scans {
Some(count) => count[0] += 1
None => ()
}
run_hot_pipeline_optimize_instructions_collect_stats_body(body, stats)
}
if propagating && precompute_raw_instrs_have_legacy_try(body) {
let module_ctx = Some(
run_hot_pipeline_require_module_ctx(mod_, module_state, options),
)
match precompute_run_raw_func(original, module_ctx) {
Some((rewritten, changed, _)) =>
return Some({
reason: if changed {
"raw-legacy-try-precompute-propagate"
} else {
"legacy-try-precompute-propagate-noop"
},
func: rewritten,
unchanged_original: !changed,
trace_skip_raw: true,
})
None =>
return Some({
reason: "legacy-try-precompute-propagate-noop",
func: original,
unchanged_original: true,
trace_skip_raw: true,
})
}
}
if propagating && precompute_raw_instrs_have_stack_switching(body) {
let module_ctx = Some(
run_hot_pipeline_require_module_ctx(mod_, module_state, options),
)
match precompute_run_raw_func(original, module_ctx) {
Some((rewritten, changed, _)) =>
return Some({
reason: if changed {
"raw-stack-switching-precompute-propagate"
} else {
"stack-switching-precompute-propagate-noop"
},
func: rewritten,
unchanged_original: !changed,
trace_skip_raw: true,
})
None =>
return Some({
reason: "stack-switching-precompute-propagate-noop",
func: original,
unchanged_original: true,
trace_skip_raw: true,
})
}
}
if precompute_raw_instrs_have_atomic_fence(body) {
let module_ctx = if propagating ||
precompute_raw_func_has_global_get(original) {
Some(run_hot_pipeline_require_module_ctx(mod_, module_state, options))
} else {
None
}
match precompute_run_raw_around_atomic_fence(original, module_ctx) {
Some((rewritten, changed)) =>
return Some({
reason: if changed {
"raw-precompute-around-atomic-fence"
} else if propagating {
"atomic-fence-precompute-propagate-noop"
} else {
"atomic-fence-precompute-noop"
},
func: rewritten,
unchanged_original: !changed,
trace_skip_raw: true,
})
None => ()
}
}
if propagating {
let module_ctx = run_hot_pipeline_require_module_ctx(
mod_, module_state, options,
)
let params = run_hot_pipeline_required_func_params(module_ctx, func_idx)
match
precompute_propagate_run_raw_unreachable_suffix(
original,
params,
Some(module_ctx),
) {
Some((rewritten, changed)) =>
return Some({
reason: if changed {
"raw-unreachable-suffix-precompute-propagate"
} else {
"unreachable-suffix-precompute-propagate-noop"
},
func: rewritten,
unchanged_original: !changed,
trace_skip_raw: true,
})
None => ()
}
}
if propagating &&
stats.instrs > 50 &&
precompute_raw_instrs_have_memory_grow(body) {
let module_ctx = run_hot_pipeline_require_module_ctx(
mod_, module_state, options,
)
let params = run_hot_pipeline_required_func_params(module_ctx, func_idx)
match
precompute_propagate_run_raw_straight_line_func(
original,
params,
Some(module_ctx),
) {
Some((rewritten, changed)) if changed =>
return Some({
reason: "raw-structured-local-constant-propagation",
func: rewritten,
unchanged_original: false,
trace_skip_raw: true,
})
_ => ()
}
}
if run_hot_pipeline_dce_raw_has_load_call_set_hazard(body) {
if propagating {
let module_ctx = run_hot_pipeline_require_module_ctx(
mod_, module_state, options,
)
let params = run_hot_pipeline_required_func_params(module_ctx, func_idx)
match
precompute_propagate_run_raw_straight_line_func(
original,
params,
Some(module_ctx),
) {
Some((rewritten, changed)) if changed =>
return Some({
reason: "raw-local-constant-propagation",
func: rewritten,
unchanged_original: false,
trace_skip_raw: true,
})
_ => ()
}
}
return Some({
reason: if o4z_precompute_gate {
"o4z-precompute-noop"
} else if propagating {
"load-call-set-precompute-propagate-noop"
} else {
"load-call-set-precompute-noop"
},
func: original,
unchanged_original: true,
trace_skip_raw: true,
})
}
if locals.length() > 64 && stats.instrs > 500 {
if propagating {
let module_ctx = run_hot_pipeline_require_module_ctx(
mod_, module_state, options,
)
let params = run_hot_pipeline_required_func_params(module_ctx, func_idx)
match
precompute_propagate_run_raw_straight_line_func(
original,
params,
Some(module_ctx),
) {
Some((rewritten, changed)) if changed =>
return Some({
reason: "raw-large-local-constant-propagation",
func: rewritten,
unchanged_original: false,
trace_skip_raw: true,
})
_ => ()
}
}
return Some({
reason: if o4z_precompute_gate {
"o4z-precompute-noop"
} else if propagating {
"large-lowered-precompute-propagate-noop"
} else {
"large-lowered-precompute-noop"
},
func: original,
unchanged_original: true,
trace_skip_raw: true,
})
}
if run_hot_pipeline_raw_can_skip_simd_parser_br_table_stack_hazard(
locals, body,
) {
return Some({
reason: if o4z_precompute_gate {
"o4z-precompute-noop"
} else if propagating {
"simd-parser-br-table-stack-hazard-precompute-propagate-noop"
} else {
"simd-parser-br-table-stack-hazard-precompute-noop"
},
func: original,
unchanged_original: true,
trace_skip_raw: true,
})
}
if propagating {
if run_hot_pipeline_raw_precompute_const_simd_reduction_count(body) >= 4 ||
run_hot_pipeline_raw_precompute_additive_local_update_count(body) >= 4 {
let module_ctx = run_hot_pipeline_require_module_ctx(
mod_, module_state, options,
)
let params = run_hot_pipeline_required_func_params(module_ctx, func_idx)
match
precompute_propagate_run_raw_straight_line_func(
original,
params,
Some(module_ctx),
) {
Some((rewritten, true)) =>
return Some({
reason: "raw-constant-simd-reduction-precompute-propagate",
func: rewritten,
unchanged_original: false,
trace_skip_raw: true,
})
_ => ()
}
match precompute_run_raw_func(original, Some(module_ctx)) {
Some((rewritten, true, _)) =>
return Some({
reason: "raw-constant-simd-reduction-precompute-propagate",
func: rewritten,
unchanged_original: false,
trace_skip_raw: true,
})
_ => ()
}
}
if !precompute_raw_func_has_local_traffic(original) {
let module_ctx = Some(
run_hot_pipeline_require_module_ctx(mod_, module_state, options),
)
match precompute_run_raw_func(original, module_ctx) {
Some((rewritten, true, reason)) =>
return Some({
reason,
func: rewritten,
unchanged_original: false,
trace_skip_raw: true,
})
_ => ()
}
}
return None
}
if precompute_raw_instrs_have_partial_heap_select(body) {
return None
}
let module_ctx = Some(
run_hot_pipeline_require_module_ctx(mod_, module_state, options),
)
match precompute_run_raw_func(original, module_ctx) {
Some((rewritten, changed, reason)) =>
if o4z_precompute_gate {
if changed &&
(reason == "raw-scalar-folds" || reason == "raw-control-flow-folds") {
Some({
reason,
func: rewritten,
unchanged_original: false,
trace_skip_raw: true,
})
} else {
Some({
reason: "o4z-precompute-noop",
func: original,
unchanged_original: true,
trace_skip_raw: true,
})
}
} else {
Some({
reason,
func: rewritten,
unchanged_original: !changed,
trace_skip_raw: true,
})
}
None =>
if o4z_precompute_gate {
Some({
reason: "o4z-precompute-noop",
func: original,
unchanged_original: true,
trace_skip_raw: true,
})
} else {
None
}
}
}
///|
fn run_hot_pipeline_raw_rse_needs_module_env(
params : Array[@lib.ValType],
locals : @lib.Locals,
) -> Bool {
for param in params {
match param {
@lib.RefTypeValType(_) => return true
_ => ()
}
}
for local_id = 0; local_id < locals.length(); local_id = local_id + 1 {
match locals.get(local_id) {
@lib.RefTypeValType(_) => return true
_ => ()
}
}
false
}
///|
fn run_hot_pipeline_raw_rse_has_repeated_zero_set_in_instrs(
instrs : Array[@lib.Instruction],
counts : @hashmap.HashMap[UInt32, Int32],
) -> Bool {
for idx = 1; idx < instrs.length(); idx = idx + 1 {
match (instrs[idx - 1], instrs[idx]) {
(@lib.I32Const(@lib.I32(0)), @lib.LocalSet(@lib.LocalIdx(target))) => {
let next = match counts.get(target) {
Some(count) => count + 1
None => 1
}
if next >= 4 {
return true
}
counts.set(target, next)
}
_ => ()
}
}
for instr in instrs {
match instr {
@lib.Block(_, @lib.Expr(body))
| @lib.Loop(_, @lib.Expr(body))
| @lib.TryTable(_, _, @lib.Expr(body)) =>
if run_hot_pipeline_raw_rse_has_repeated_zero_set_in_instrs(
body, counts,
) {
return true
}
@lib.If(_, then_body, else_body) => {
if run_hot_pipeline_raw_rse_has_repeated_zero_set_in_instrs(
then_body, counts,
) {
return true
}
match else_body {
Some(body) =>
if run_hot_pipeline_raw_rse_has_repeated_zero_set_in_instrs(
body, counts,
) {
return true
}
None => ()
}
}
@lib.Try(_, @lib.Expr(body), catches, _) => {
if run_hot_pipeline_raw_rse_has_repeated_zero_set_in_instrs(
body, counts,
) {
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 run_hot_pipeline_raw_rse_has_repeated_zero_set_in_instrs(
catch_body, counts,
) {
return true
}
}
}
_ => ()
}
}
false
}
///|
fn run_hot_pipeline_raw_rse_has_repeated_structured_zero_reset(
instrs : Array[@lib.Instruction],
) -> Bool {
let (_, has_structured_control, _, _, _, _) = run_hot_pipeline_instr_scan(
instrs,
)
if !has_structured_control {
return false
}
run_hot_pipeline_raw_rse_has_repeated_zero_set_in_instrs(
instrs,
@hashmap.new(),
)
}
///|
fn run_hot_pipeline_raw_rse(
mod_ : @lib.Module,
func_idx : @lib.FuncIdx,
module_state : HotPipelineModuleState,
descriptor : @ir.HotPassDescriptor,
options : HotPipelineOptions,
) -> HotPipelineFuncResult? {
if descriptor.name != "redundant-set-elimination" {
return None
}
let setup_start_us = perf_start_timer_opt(options.perf, "detail:rse:setup")
let original = run_hot_pipeline_original_func(mod_, func_idx)
let module_ctx = run_hot_pipeline_require_module_ctx(
mod_, module_state, options,
)
let params = run_hot_pipeline_required_func_params(module_ctx, func_idx)
let @lib.Func(locals, @lib.Expr(body)) = original
if run_hot_pipeline_raw_rse_has_repeated_structured_zero_reset(body) {
perf_stop_timer_accumulate_opt(
options.perf,
"detail:rse:setup",
setup_start_us,
)
return Some({
reason: "repeated-branch-zero-reset-rse-noop",
func: original,
unchanged_original: true,
trace_skip_raw: true,
})
}
let env = if run_hot_pipeline_raw_rse_needs_module_env(params, locals) {
@validate.Env::new().with_module(mod_)
} else {
@validate.Env::new()
}
perf_stop_timer_accumulate_opt(
options.perf,
"detail:rse:setup",
setup_start_us,
)
let rse_start_us = perf_start_timer_opt(options.perf, "detail:rse:raw-func")
let (rewritten, changed) = rse_run_raw_func(original, params, env)
perf_stop_timer_accumulate_opt(
options.perf,
"detail:rse:raw-func",
rse_start_us,
)
Some({
reason: if changed {
"raw-same-value-local-write"
} else {
"no-rse-candidates"
},
func: rewritten,
unchanged_original: !changed,
trace_skip_raw: true,
})
}
///|
fn run_hot_pipeline_raw_vacuum_label_idx_depth(idx : @lib.LabelIdx) -> Int32 {
let @lib.LabelIdx(raw) = idx
raw.reinterpret_as_int()
}
///|
fn run_hot_pipeline_raw_vacuum_label_depth(label : @lib.LabelIdx) -> Int32 {
let @lib.LabelIdx(raw) = label
raw.reinterpret_as_int()
}
///|
fn run_hot_pipeline_raw_vacuum_label_targets_owner(
label : @lib.LabelIdx,
owner_depth : Int32,
) -> Bool {
run_hot_pipeline_raw_vacuum_label_depth(label) == owner_depth
}
///|
fn run_hot_pipeline_raw_vacuum_rebase_outer_label(
label : @lib.LabelIdx,
owner_depth : Int32,
) -> @lib.LabelIdx {
let depth = run_hot_pipeline_raw_vacuum_label_depth(label)
if depth > owner_depth {
return @lib.LabelIdx::new((depth - 1).reinterpret_as_uint())
}
label
}
///|
fn run_hot_pipeline_raw_vacuum_rebase_outer_catch(
catch_ : @lib.Catch,
owner_depth : Int32,
) -> @lib.Catch {
match catch_ {
@lib.Catch(tag, label) =>
@lib.Catch::new(
tag,
run_hot_pipeline_raw_vacuum_rebase_outer_label(label, owner_depth),
)
@lib.CatchRef(tag, label) =>
@lib.Catch::ref_(
tag,
run_hot_pipeline_raw_vacuum_rebase_outer_label(label, owner_depth),
)
@lib.CatchAll(label) =>
@lib.Catch::all(
run_hot_pipeline_raw_vacuum_rebase_outer_label(label, owner_depth),
)
@lib.CatchAllRef(label) =>
@lib.Catch::all_ref(
run_hot_pipeline_raw_vacuum_rebase_outer_label(label, owner_depth),
)
}
}
///|
fn run_hot_pipeline_raw_vacuum_resume_handlers_target_owner(
handlers : Array[@lib.ResumeHandler],
owner_depth : Int32,
) -> Bool {
for handler in handlers {
match handler {
@lib.ResumeOnLabel(_, label) =>
if run_hot_pipeline_raw_vacuum_label_targets_owner(label, owner_depth) {
return true
}
@lib.ResumeOnSwitch(_) => ()
}
}
false
}
///|
fn run_hot_pipeline_raw_vacuum_instr_targets_owner(
instr : @lib.Instruction,
owner_depth : Int32,
) -> Bool {
match instr {
@lib.Br(label)
| @lib.BrIf(label)
| @lib.BrOnNull(label)
| @lib.BrOnNonNull(label)
| @lib.BrOnCast(label, _, _, _)
| @lib.BrOnCastFail(label, _, _, _)
| @lib.BrOnCastDescEq(label, _, _)
| @lib.BrOnCastDescEqFail(label, _, _) =>
run_hot_pipeline_raw_vacuum_label_targets_owner(label, owner_depth)
@lib.BrTable(labels, fallback) => {
if run_hot_pipeline_raw_vacuum_label_targets_owner(fallback, owner_depth) {
return true
}
for label in labels {
if run_hot_pipeline_raw_vacuum_label_targets_owner(label, owner_depth) {
return true
}
}
false
}
@lib.Block(_, @lib.Expr(body)) | @lib.Loop(_, @lib.Expr(body)) =>
run_hot_pipeline_raw_vacuum_body_targets_owner(body, owner_depth + 1)
@lib.If(_, then_body, else_body) => {
if run_hot_pipeline_raw_vacuum_body_targets_owner(
then_body,
owner_depth + 1,
) {
return true
}
match else_body {
Some(body) =>
run_hot_pipeline_raw_vacuum_body_targets_owner(body, owner_depth + 1)
None => false
}
}
@lib.TryTable(_, catches, @lib.Expr(body)) => {
for catch_ in catches {
let label = match catch_ {
@lib.Catch(_, label)
| @lib.CatchRef(_, label)
| @lib.CatchAll(label)
| @lib.CatchAllRef(label) => label
}
if run_hot_pipeline_raw_vacuum_label_targets_owner(label, owner_depth) {
return true
}
}
run_hot_pipeline_raw_vacuum_body_targets_owner(body, owner_depth + 1)
}
@lib.Try(_, @lib.Expr(body), catches, delegate) => {
// Delegate targets use the surrounding scope; keep its owner conservatively.
if delegate is Some(_) {
return true
}
if run_hot_pipeline_raw_vacuum_body_targets_owner(body, owner_depth + 1) {
return true
}
for catch_ in catches {
let @lib.Expr(catch_body) = match catch_ {
@lib.LegacyCatch(_, expr) | @lib.LegacyCatchAll(expr) => expr
}
if run_hot_pipeline_raw_vacuum_body_targets_owner(
catch_body,
owner_depth + 1,
) {
return true
}
}
false
}
@lib.Rethrow(_) => true
@lib.Resume(_, handlers)
| @lib.ResumeThrow(_, _, handlers)
| @lib.ResumeThrowRef(_, handlers) =>
run_hot_pipeline_raw_vacuum_resume_handlers_target_owner(
handlers, owner_depth,
)
_ => false
}
}
///|
fn run_hot_pipeline_raw_vacuum_body_targets_owner(
body : Array[@lib.Instruction],
owner_depth : Int32,
) -> Bool {
for instr in body {
if run_hot_pipeline_raw_vacuum_instr_targets_owner(instr, owner_depth) {
return true
}
}
false
}
///|
fn run_hot_pipeline_raw_vacuum_rebase_outer_resume_handlers(
handlers : Array[@lib.ResumeHandler],
owner_depth : Int32,
) -> Array[@lib.ResumeHandler] {
handlers.map(fn(handler) {
match handler {
@lib.ResumeOnLabel(tag, label) =>
@lib.ResumeHandler::on_label(
tag,
run_hot_pipeline_raw_vacuum_rebase_outer_label(label, owner_depth),
)
@lib.ResumeOnSwitch(tag) => @lib.ResumeHandler::on_switch(tag)
}
})
}
///|
fn run_hot_pipeline_raw_vacuum_rebase_outer_instr(
instr : @lib.Instruction,
owner_depth : Int32,
) -> @lib.Instruction {
match instr {
@lib.BrOnCastDescEq(label, source, target) =>
@lib.Instruction::br_on_cast_desc_eq(
run_hot_pipeline_raw_vacuum_rebase_outer_label(label, owner_depth),
source,
target,
)
@lib.BrOnCastDescEqFail(label, source, target) =>
@lib.Instruction::br_on_cast_desc_eq_fail(
run_hot_pipeline_raw_vacuum_rebase_outer_label(label, owner_depth),
source,
target,
)
@lib.Br(label) =>
@lib.Instruction::br(
run_hot_pipeline_raw_vacuum_rebase_outer_label(label, owner_depth),
)
@lib.BrIf(label) =>
@lib.Instruction::br_if(
run_hot_pipeline_raw_vacuum_rebase_outer_label(label, owner_depth),
)
@lib.BrOnNull(label) =>
@lib.Instruction::br_on_null(
run_hot_pipeline_raw_vacuum_rebase_outer_label(label, owner_depth),
)
@lib.BrOnNonNull(label) =>
@lib.Instruction::br_on_non_null(
run_hot_pipeline_raw_vacuum_rebase_outer_label(label, owner_depth),
)
@lib.BrOnCast(
label,
@lib.CastOp(source_nullable, target_nullable, source_exact, target_exact),
source_heap,
target_heap
) =>
@lib.Instruction::br_on_cast(
run_hot_pipeline_raw_vacuum_rebase_outer_label(label, owner_depth),
source_nullable,
source_heap,
target_nullable,
target_heap,
source_exact~,
target_exact~,
)
@lib.BrOnCastFail(
label,
@lib.CastOp(source_nullable, target_nullable, source_exact, target_exact),
source_heap,
target_heap
) =>
@lib.Instruction::br_on_cast_fail(
run_hot_pipeline_raw_vacuum_rebase_outer_label(label, owner_depth),
source_nullable,
source_heap,
target_nullable,
target_heap,
source_exact~,
target_exact~,
)
@lib.BrTable(labels, fallback) => {
let rebased : Array[@lib.LabelIdx] = []
for label in labels {
rebased.push(
run_hot_pipeline_raw_vacuum_rebase_outer_label(label, owner_depth),
)
}
@lib.Instruction::br_table(
rebased,
run_hot_pipeline_raw_vacuum_rebase_outer_label(fallback, owner_depth),
)
}
@lib.Block(block_type, @lib.Expr(body)) =>
@lib.Instruction::block(
block_type,
@lib.Expr::new(
run_hot_pipeline_raw_vacuum_rebase_outer_body(body, owner_depth + 1),
),
)
@lib.Loop(block_type, @lib.Expr(body)) =>
@lib.Instruction::loop_(
block_type,
@lib.Expr::new(
run_hot_pipeline_raw_vacuum_rebase_outer_body(body, owner_depth + 1),
),
)
@lib.If(block_type, then_body, else_body) =>
@lib.Instruction::if_(
block_type,
run_hot_pipeline_raw_vacuum_rebase_outer_body(
then_body,
owner_depth + 1,
),
match else_body {
Some(body) =>
Some(
run_hot_pipeline_raw_vacuum_rebase_outer_body(
body,
owner_depth + 1,
),
)
None => None
},
)
@lib.TryTable(block_type, catches, @lib.Expr(body)) => {
let rebased_catches : Array[@lib.Catch] = []
for catch_ in catches {
rebased_catches.push(
run_hot_pipeline_raw_vacuum_rebase_outer_catch(catch_, owner_depth),
)
}
@lib.Instruction::try_table(
block_type,
rebased_catches,
@lib.Expr::new(
run_hot_pipeline_raw_vacuum_rebase_outer_body(body, owner_depth + 1),
),
)
}
@lib.Try(block_type, @lib.Expr(body), catches, delegate) => {
let next_catches = catches.map(fn(catch_) {
match catch_ {
@lib.LegacyCatch(tag, @lib.Expr(body)) =>
@lib.LegacyCatch::tagged(
tag,
@lib.Expr::new(
run_hot_pipeline_raw_vacuum_rebase_outer_body(
body,
owner_depth + 1,
),
),
)
@lib.LegacyCatchAll(@lib.Expr(body)) =>
@lib.LegacyCatch::all(
@lib.Expr::new(
run_hot_pipeline_raw_vacuum_rebase_outer_body(
body,
owner_depth + 1,
),
),
)
}
})
@lib.Instruction::try_(
block_type,
@lib.Expr::new(
run_hot_pipeline_raw_vacuum_rebase_outer_body(body, owner_depth + 1),
),
next_catches,
delegate~,
)
}
@lib.Resume(type_idx, handlers) =>
@lib.Instruction::resume_(
type_idx,
run_hot_pipeline_raw_vacuum_rebase_outer_resume_handlers(
handlers, owner_depth,
),
)
@lib.ResumeThrow(type_idx, tag, handlers) =>
@lib.Instruction::resume_throw(
type_idx,
tag,
run_hot_pipeline_raw_vacuum_rebase_outer_resume_handlers(
handlers, owner_depth,
),
)
@lib.ResumeThrowRef(type_idx, handlers) =>
@lib.Instruction::resume_throw_ref(
type_idx,
run_hot_pipeline_raw_vacuum_rebase_outer_resume_handlers(
handlers, owner_depth,
),
)
_ => instr
}
}
///|
fn run_hot_pipeline_raw_vacuum_rebase_outer_body(
body : Array[@lib.Instruction],
owner_depth : Int32,
) -> Array[@lib.Instruction] {
let rebased : Array[@lib.Instruction] = []
for instr in body {
rebased.push(
run_hot_pipeline_raw_vacuum_rebase_outer_instr(instr, owner_depth),
)
}
rebased
}
///|
fn run_hot_pipeline_raw_vacuum_instr_can_prefix_if_wrapper(
instr : @lib.Instruction,
) -> Bool {
match instr {
@lib.LocalSet(_)
| @lib.GlobalSet(_)
| @lib.TableSet(_)
| @lib.I32Store(_)
| @lib.I64Store(_)
| @lib.F32Store(_)
| @lib.F64Store(_)
| @lib.I32Store8(_)
| @lib.I32Store16(_)
| @lib.I64Store8(_)
| @lib.I64Store16(_)
| @lib.I64Store32(_)
| @lib.ReturnCall(_)
| @lib.ReturnCallIndirect(_, _)
| @lib.ReturnCallRef(_)
| @lib.Throw(_)
| @lib.ThrowRef
| @lib.Br(_)
| @lib.BrIf(_)
| @lib.BrTable(_, _)
| @lib.Return
| @lib.Unreachable
| @lib.Block(_, _)
| @lib.Loop(_, _)
| @lib.If(_, _, _)
| @lib.TryTable(_, _, _) => false
_ => true
}
}
///|
fn run_hot_pipeline_raw_vacuum_body_is_if_wrapper_candidate(
body : Array[@lib.Instruction],
) -> Bool {
if body.length() == 0 {
return false
}
match body[body.length() - 1] {
@lib.If(_, _, _) => ()
_ => return false
}
for idx = 0; idx + 1 < body.length(); idx = idx + 1 {
if !run_hot_pipeline_raw_vacuum_instr_can_prefix_if_wrapper(body[idx]) {
return false
}
}
true
}
///|
fn run_hot_pipeline_raw_vacuum_body_has_call_prefix_before_final_if(
body : Array[@lib.Instruction],
) -> Bool {
for idx = 0; idx + 1 < body.length(); idx = idx + 1 {
match body[idx] {
@lib.Call(_) => return true
_ => ()
}
}
false
}
///|
fn run_hot_pipeline_raw_vacuum_body_is_empty_block_call_prefix_if_wrapper_candidate(
body : Array[@lib.Instruction],
) -> Bool {
let mut first_live_idx = 0
while first_live_idx < body.length() {
match body[first_live_idx] {
@lib.Block(@lib.VoidBlockType, @lib.Expr([])) =>
first_live_idx = first_live_idx + 1
_ => break
}
}
if first_live_idx == 0 || first_live_idx >= body.length() {
return false
}
let cleaned : Array[@lib.Instruction] = []
for idx = first_live_idx; idx < body.length(); idx = idx + 1 {
cleaned.push(body[idx])
}
run_hot_pipeline_raw_vacuum_body_is_if_wrapper_candidate(cleaned) &&
run_hot_pipeline_raw_vacuum_body_has_call_prefix_before_final_if(cleaned)
}
///|
fn run_hot_pipeline_raw_vacuum_body_has_singleton_empty_block_call_prefix_descendant(
body : Array[@lib.Instruction],
) -> Bool {
if body.length() != 1 {
return false
}
match body[0] {
@lib.Block(@lib.VoidBlockType, @lib.Expr(child_body)) =>
run_hot_pipeline_raw_vacuum_body_is_empty_block_call_prefix_if_wrapper_candidate(
child_body,
) ||
run_hot_pipeline_raw_vacuum_body_has_singleton_empty_block_call_prefix_descendant(
child_body,
)
_ => false
}
}
///|
fn run_hot_pipeline_raw_vacuum_body_has_empty_block_call_prefix_shape_ignoring_nops(
body : Array[@lib.Instruction],
) -> Bool {
let non_nops : Array[@lib.Instruction] = []
for instr in body {
match instr {
@lib.Nop => ()
_ => non_nops.push(instr)
}
}
run_hot_pipeline_raw_vacuum_body_is_empty_block_call_prefix_if_wrapper_candidate(
non_nops,
) ||
run_hot_pipeline_raw_vacuum_body_has_singleton_empty_block_call_prefix_descendant(
non_nops,
)
}
///|
fn run_hot_pipeline_raw_vacuum_body_has_top_level_nop(
body : Array[@lib.Instruction],
) -> Bool {
for instr in body {
match instr {
@lib.Nop => return true
_ => ()
}
}
false
}
///|
fn run_hot_pipeline_raw_vacuum_body_is_singleton_call_prefix_if_block(
body : Array[@lib.Instruction],
) -> Bool {
body.length() == 1 &&
(match body[0] {
@lib.Block(@lib.VoidBlockType, @lib.Expr(child_body)) =>
run_hot_pipeline_raw_vacuum_body_is_if_wrapper_candidate(child_body) &&
run_hot_pipeline_raw_vacuum_body_has_call_prefix_before_final_if(
child_body,
) &&
!run_hot_pipeline_raw_vacuum_instrs_contain_unreachable(child_body)
_ => false
})
}
///|
fn run_hot_pipeline_raw_vacuum_body_has_local_set_outer_br_continuation(
body : Array[@lib.Instruction],
start_idx : Int32,
) -> Bool {
if start_idx + 2 >= body.length() {
return false
}
match body[start_idx + 1] {
@lib.LocalSet(_) =>
match body[start_idx + 2] {
@lib.Br(label) => run_hot_pipeline_raw_vacuum_label_depth(label) > 0
_ => false
}
@lib.LocalGet(_) | @lib.I32Const(_) | @lib.I64Const(_) =>
if start_idx + 3 < body.length() {
match (body[start_idx + 2], body[start_idx + 3]) {
(@lib.LocalSet(_), @lib.Br(label)) =>
run_hot_pipeline_raw_vacuum_label_depth(label) > 0
_ => false
}
} else {
false
}
_ => false
}
}
///|
fn run_hot_pipeline_raw_vacuum_body_is_artifact_call_prefix_parent_unwrap_candidate(
body : Array[@lib.Instruction],
) -> Bool {
body.length() == 1 &&
(match body[0] {
@lib.Block(@lib.VoidBlockType, @lib.Expr(child_body)) =>
run_hot_pipeline_raw_vacuum_body_is_if_wrapper_candidate(child_body) &&
run_hot_pipeline_raw_vacuum_body_has_call_prefix_before_final_if(
child_body,
) &&
!run_hot_pipeline_raw_vacuum_instrs_contain_unreachable(child_body)
_ => false
})
}
///|
fn run_hot_pipeline_raw_vacuum_drop_leading_empty_blocks(
body : Array[@lib.Instruction],
) -> Array[@lib.Instruction] {
let cleaned : Array[@lib.Instruction] = []
let mut idx = 0
while idx < body.length() {
match body[idx] {
@lib.Block(@lib.VoidBlockType, @lib.Expr([])) => idx = idx + 1
_ => break
}
}
while idx < body.length() {
cleaned.push(body[idx])
idx = idx + 1
}
cleaned
}
///|
fn run_hot_pipeline_raw_vacuum_instr_can_prefix_unreachable_if_wrapper(
instr : @lib.Instruction,
) -> Bool {
match instr {
@lib.Call(_)
| @lib.CallIndirect(_, _)
| @lib.CallRef(_)
| @lib.MemoryCopy(_, _)
| @lib.MemoryFill(_) => false
_ => run_hot_pipeline_raw_vacuum_instr_can_prefix_if_wrapper(instr)
}
}
///|
fn run_hot_pipeline_raw_vacuum_body_is_unreachable_if_wrapper_candidate(
body : Array[@lib.Instruction],
) -> Bool {
if body.length() == 0 {
return false
}
match body[body.length() - 1] {
@lib.If(@lib.VoidBlockType, _, _) => ()
_ => return false
}
if !run_hot_pipeline_raw_vacuum_instrs_contain_unreachable(body) {
return false
}
for idx = 0; idx + 1 < body.length(); idx = idx + 1 {
if !run_hot_pipeline_raw_vacuum_instr_can_prefix_unreachable_if_wrapper(
body[idx],
) {
return false
}
}
true
}
///|
fn run_hot_pipeline_raw_vacuum_body_is_call_tee_unreachable_if_wrapper_candidate(
body : Array[@lib.Instruction],
) -> Bool {
if body.length() < 3 {
return false
}
match body[body.length() - 1] {
@lib.If(@lib.VoidBlockType, _, _) => ()
_ => return false
}
if !run_hot_pipeline_raw_vacuum_instrs_contain_unreachable(body) {
return false
}
match body[body.length() - 2] {
@lib.LocalTee(_) => ()
_ => return false
}
let mut call_count = 0
for idx = 0; idx + 2 < body.length(); idx = idx + 1 {
match body[idx] {
@lib.LocalGet(_) | @lib.I32Const(_) | @lib.I64Const(_) => ()
@lib.Call(_) => call_count = call_count + 1
_ => return false
}
}
call_count == 1
}
///|
fn run_hot_pipeline_raw_vacuum_body_is_lowered_call_tee_unreachable_if_wrapper_candidate(
body : Array[@lib.Instruction],
) -> Bool {
if body.length() < 4 {
return false
}
match body[body.length() - 1] {
@lib.If(@lib.VoidBlockType, _, _) => ()
_ => return false
}
if !run_hot_pipeline_raw_vacuum_instrs_contain_unreachable(body) {
return false
}
match body[body.length() - 2] {
@lib.LocalTee(_) => ()
_ => return false
}
let mut call_count = 0
let mut saw_lowered_condition_piece = false
for idx = 0; idx + 2 < body.length(); idx = idx + 1 {
match body[idx] {
@lib.LocalGet(_) | @lib.I32Const(_) | @lib.I64Const(_) => ()
@lib.I32Shl | @lib.I32Add | @lib.I32Load(_) | @lib.LocalTee(_) =>
saw_lowered_condition_piece = true
@lib.Call(_) => call_count = call_count + 1
_ => return false
}
}
call_count == 1 && saw_lowered_condition_piece
}
///|
fn run_hot_pipeline_raw_vacuum_body_is_single_block_prefix_if_wrapper_candidate(
body : Array[@lib.Instruction],
) -> Bool {
if body.length() < 2 {
return false
}
match body[0] {
@lib.Block(@lib.VoidBlockType, _) => ()
_ => return false
}
match body[body.length() - 1] {
@lib.If(_, _, _) => ()
_ => return false
}
for idx = 1; idx + 1 < body.length(); idx = idx + 1 {
if !run_hot_pipeline_raw_vacuum_instr_can_prefix_if_wrapper(body[idx]) {
return false
}
}
true
}
///|
fn run_hot_pipeline_raw_vacuum_body_is_recursive_call_prefix_if_wrapper_candidate(
body : Array[@lib.Instruction],
) -> Bool {
if !run_hot_pipeline_raw_vacuum_body_is_single_block_prefix_if_wrapper_candidate(
body,
) {
return false
}
let mut call_count = 0
for idx = 1; idx + 1 < body.length(); idx = idx + 1 {
match body[idx] {
@lib.Call(_) => call_count = call_count + 1
_ => ()
}
}
call_count == 1
}
///|
fn run_hot_pipeline_raw_vacuum_instr_can_prefix_side_effect_if_wrapper(
instr : @lib.Instruction,
) -> Bool {
match instr {
@lib.GlobalSet(_)
| @lib.TableSet(_)
| @lib.I32Store(_)
| @lib.I64Store(_)
| @lib.F32Store(_)
| @lib.F64Store(_)
| @lib.I32Store8(_)
| @lib.I32Store16(_)
| @lib.I64Store8(_)
| @lib.I64Store16(_)
| @lib.I64Store32(_)
| @lib.MemoryCopy(_, _)
| @lib.MemoryFill(_)
| @lib.Br(_)
| @lib.BrIf(_)
| @lib.BrTable(_, _)
| @lib.Return
| @lib.ReturnCall(_)
| @lib.ReturnCallIndirect(_, _)
| @lib.ReturnCallRef(_)
| @lib.Throw(_)
| @lib.ThrowRef
| @lib.Unreachable
| @lib.Block(_, _)
| @lib.Loop(_, _)
| @lib.If(_, _, _)
| @lib.TryTable(_, _, _) => false
_ => true
}
}
///|
fn run_hot_pipeline_raw_vacuum_body_is_side_effect_prefix_if_wrapper_candidate(
body : Array[@lib.Instruction],
) -> Bool {
if body.length() < 2 {
return false
}
match body[body.length() - 1] {
@lib.If(@lib.VoidBlockType, _, _) => ()
_ => return false
}
if run_hot_pipeline_raw_vacuum_instrs_contain_unreachable(body) {
return false
}
let mut saw_local_set = false
let mut call_count = 0
let mut standalone_call_before_first_set = false
let mut first_local_set_consumed_call = false
for idx = 0; idx + 1 < body.length(); idx = idx + 1 {
if !run_hot_pipeline_raw_vacuum_instr_can_prefix_side_effect_if_wrapper(
body[idx],
) {
return false
}
match body[idx] {
@lib.LocalSet(_) => {
if !saw_local_set {
first_local_set_consumed_call = idx > 0 &&
body[idx - 1] is @lib.Call(_)
}
saw_local_set = true
}
@lib.Call(_) => {
call_count = call_count + 1
if !saw_local_set {
let consumed_by_set = idx + 1 < body.length() &&
body[idx + 1] is @lib.LocalSet(_)
if !consumed_by_set {
standalone_call_before_first_set = true
}
}
}
_ => ()
}
}
saw_local_set &&
call_count >= 2 &&
(standalone_call_before_first_set || !first_local_set_consumed_call)
}
///|
fn run_hot_pipeline_raw_vacuum_prefix_block_starts_call_result_local_set(
body : Array[@lib.Instruction],
) -> Bool {
let non_nops : Array[@lib.Instruction] = []
for instr in body {
match instr {
@lib.Nop => ()
_ => non_nops.push(instr)
}
}
non_nops.length() == 5 &&
non_nops[0] is @lib.LocalGet(_) &&
non_nops[1] is @lib.Call(_) &&
non_nops[2] is @lib.LocalSet(_) &&
non_nops[3] is @lib.LocalGet(_) &&
non_nops[4] is @lib.Call(_)
}
///|
fn run_hot_pipeline_raw_vacuum_two_arg_call_result_local_set_release_target(
body : Array[@lib.Instruction],
) -> Int32? {
let non_nops : Array[@lib.Instruction] = []
for instr in body {
match instr {
@lib.Nop => ()
_ => non_nops.push(instr)
}
}
if non_nops.length() != 6 {
return None
}
match
(
non_nops[0],
non_nops[1],
non_nops[2],
non_nops[3],
non_nops[4],
non_nops[5],
) {
(
@lib.LocalGet(@lib.LocalIdx(receiver_raw)),
@lib.LocalGet(_),
@lib.Call(_),
@lib.LocalSet(@lib.LocalIdx(target_raw)),
@lib.LocalGet(@lib.LocalIdx(release_raw)),
@lib.Call(_),
) if release_raw == receiver_raw => Some(target_raw.reinterpret_as_int())
_ => None
}
}
///|
fn run_hot_pipeline_raw_vacuum_prefix_block_starts_two_arg_call_result_local_set_release(
body : Array[@lib.Instruction],
) -> Bool {
let non_nops : Array[@lib.Instruction] = []
for instr in body {
match instr {
@lib.Nop => ()
_ => non_nops.push(instr)
}
}
non_nops.length() == 6 &&
non_nops[0] is @lib.LocalGet(_) &&
non_nops[1] is @lib.LocalGet(_) &&
non_nops[2] is @lib.Call(_) &&
non_nops[3] is @lib.LocalSet(_) &&
non_nops[4] is @lib.LocalGet(_) &&
non_nops[5] is @lib.Call(_)
}
///|
fn run_hot_pipeline_raw_vacuum_prefix_block_starts_two_arg_call_result_local_set_double_release(
body : Array[@lib.Instruction],
) -> Bool {
let non_nops : Array[@lib.Instruction] = []
for instr in body {
match instr {
@lib.Nop => ()
_ => non_nops.push(instr)
}
}
if non_nops.length() != 8 {
return false
}
let first_arg = match non_nops[0] {
@lib.LocalGet(@lib.LocalIdx(raw)) => raw
_ => return false
}
let second_arg = match non_nops[1] {
@lib.LocalGet(@lib.LocalIdx(raw)) => raw
_ => return false
}
match
(
non_nops[2],
non_nops[3],
non_nops[4],
non_nops[5],
non_nops[6],
non_nops[7],
) {
(
@lib.Call(_),
@lib.LocalSet(_),
@lib.LocalGet(@lib.LocalIdx(first_release)),
@lib.Call(_),
@lib.LocalGet(@lib.LocalIdx(second_release)),
@lib.Call(_),
) => first_release == first_arg && second_release == second_arg
_ => false
}
}
///|
fn run_hot_pipeline_raw_vacuum_condition_prefix_has_call(
body : Array[@lib.Instruction],
) -> Bool {
if body.length() < 3 {
return false
}
for idx = 1; idx + 1 < body.length(); idx = idx + 1 {
match body[idx] {
@lib.Call(_) => return true
_ => ()
}
}
false
}
///|
fn run_hot_pipeline_raw_vacuum_prefix_block_has_direct_call_local_set_and_later_call(
body : Array[@lib.Instruction],
) -> Bool {
let non_nops : Array[@lib.Instruction] = []
for instr in body {
match instr {
@lib.Nop => ()
_ => non_nops.push(instr)
}
}
for idx = 1; idx < non_nops.length(); idx = idx + 1 {
match non_nops[idx] {
@lib.LocalSet(_) if non_nops[idx - 1] is @lib.Call(_) =>
for tail_idx = idx + 1
tail_idx < non_nops.length()
tail_idx = tail_idx + 1 {
match non_nops[tail_idx] {
@lib.Call(_) => return true
_ => ()
}
}
_ => ()
}
}
false
}
///|
fn run_hot_pipeline_raw_vacuum_instr_is_prefix_call_arg(
instr : @lib.Instruction,
) -> Bool {
match instr {
@lib.I32Const(_) | @lib.LocalGet(_) => true
_ => false
}
}
///|
fn run_hot_pipeline_raw_vacuum_prefix_block_starts_five_arg_call_result_local_set_release_triple(
body : Array[@lib.Instruction],
) -> Bool {
let non_nops : Array[@lib.Instruction] = []
for instr in body {
match instr {
@lib.Nop => ()
_ => non_nops.push(instr)
}
}
if non_nops.length() != 13 {
return false
}
for idx = 0; idx < 5; idx = idx + 1 {
if !run_hot_pipeline_raw_vacuum_instr_is_prefix_call_arg(non_nops[idx]) {
return false
}
}
non_nops[5] is @lib.Call(_) &&
non_nops[6] is @lib.LocalSet(_) &&
non_nops[7] is @lib.LocalGet(_) &&
non_nops[8] is @lib.Call(_) &&
non_nops[9] is @lib.LocalGet(_) &&
non_nops[10] is @lib.Call(_) &&
non_nops[11] is @lib.LocalGet(_) &&
non_nops[12] is @lib.Call(_)
}
///|
fn run_hot_pipeline_raw_vacuum_instr_is_top_level_outer_branch(
instr : @lib.Instruction,
) -> Bool {
match instr {
@lib.Br(label)
| @lib.BrIf(label)
| @lib.BrOnNull(label)
| @lib.BrOnNonNull(label) =>
run_hot_pipeline_raw_vacuum_label_depth(label) > 0
@lib.BrTable(labels, fallback) => {
if run_hot_pipeline_raw_vacuum_label_depth(fallback) > 0 {
return true
}
for label in labels {
if run_hot_pipeline_raw_vacuum_label_depth(label) > 0 {
return true
}
}
false
}
_ => false
}
}
///|
fn run_hot_pipeline_raw_vacuum_body_has_top_level_outer_branch(
body : Array[@lib.Instruction],
) -> Bool {
for instr in body {
if run_hot_pipeline_raw_vacuum_instr_is_top_level_outer_branch(instr) {
return true
}
}
false
}
///|
fn run_hot_pipeline_raw_vacuum_final_if_has_else_or_top_level_outer_branch(
body : Array[@lib.Instruction],
) -> Bool {
if body.length() == 0 {
return false
}
match body[body.length() - 1] {
@lib.If(_, _, Some(_)) => true
@lib.If(_, then_body, None) =>
run_hot_pipeline_raw_vacuum_body_has_top_level_outer_branch(then_body)
_ => false
}
}
///|
fn run_hot_pipeline_raw_vacuum_body_is_f3083_loop_body_release_update_prefix_if_preserve_candidate(
body : Array[@lib.Instruction],
) -> Bool {
let non_nops : Array[@lib.Instruction] = []
for instr in body {
match instr {
@lib.Nop => ()
_ => non_nops.push(instr)
}
}
if non_nops.length() < 14 {
return false
}
match non_nops[non_nops.length() - 1] {
@lib.If(@lib.VoidBlockType, then_body, Some(_)) =>
if !run_hot_pipeline_raw_vacuum_body_has_top_level_outer_branch(then_body) {
return false
}
_ => return false
}
match
(
non_nops[0],
non_nops[1],
non_nops[2],
non_nops[3],
non_nops[4],
non_nops[5],
non_nops[6],
non_nops[7],
non_nops[8],
non_nops[9],
non_nops[10],
non_nops[11],
non_nops[12],
) {
(
@lib.LocalGet(_),
@lib.I32Load(_),
@lib.LocalTee(@lib.LocalIdx(item_raw)),
@lib.Call(_),
@lib.LocalGet(_),
@lib.LocalGet(@lib.LocalIdx(next_item_raw)),
@lib.Call(_),
@lib.LocalSet(@lib.LocalIdx(state_raw)),
@lib.LocalGet(@lib.LocalIdx(release_item_raw)),
@lib.Call(_),
@lib.LocalGet(@lib.LocalIdx(next_state_raw)),
@lib.I32Const(@lib.I32(1)),
@lib.I32Eq,
) =>
next_item_raw == item_raw &&
release_item_raw == item_raw &&
next_state_raw == state_raw
_ => false
}
}
///|
fn run_hot_pipeline_raw_vacuum_body_is_f3317_singleton_retain_allocation_loop_wrapper(
body : Array[@lib.Instruction],
) -> Bool {
if body.length() != 1 {
return false
}
let child_body = match body[0] {
@lib.Block(@lib.VoidBlockType, @lib.Expr(child_body)) => child_body
_ => return false
}
let non_nops : Array[@lib.Instruction] = []
for instr in child_body {
match instr {
@lib.Nop => ()
_ => non_nops.push(instr)
}
}
if non_nops.length() < 22 {
return false
}
match
(
non_nops[0],
non_nops[1],
non_nops[2],
non_nops[3],
non_nops[4],
non_nops[5],
non_nops[6],
non_nops[7],
non_nops[8],
non_nops[9],
) {
(
@lib.LocalGet(_),
@lib.I32Load(_),
@lib.LocalTee(@lib.LocalIdx(retained_raw)),
@lib.If(@lib.VoidBlockType, retain_then, None),
@lib.LocalGet(@lib.LocalIdx(compute_retained_raw)),
@lib.I32Const(_),
@lib.Call(_),
@lib.LocalSet(_),
@lib.LocalGet(@lib.LocalIdx(release_retained_raw)),
@lib.If(@lib.VoidBlockType, release_then, None),
) => {
if compute_retained_raw != retained_raw ||
release_retained_raw != retained_raw {
return false
}
match retain_then {
[@lib.LocalGet(@lib.LocalIdx(next_raw)), @lib.Call(_)] if next_raw ==
retained_raw => ()
_ => return false
}
match release_then {
[@lib.LocalGet(@lib.LocalIdx(next_raw)), @lib.Call(_)] if next_raw ==
retained_raw => ()
_ => return false
}
}
_ => return false
}
let mut store_count = 0
let mut call_count = 0
for instr in non_nops {
match instr {
@lib.I32Store(_) => store_count = store_count + 1
@lib.Call(_) => call_count = call_count + 1
_ => ()
}
}
store_count >= 2 && call_count >= 3
}
///|
fn run_hot_pipeline_raw_vacuum_tag20_compare_prefix_end(
body : Array[@lib.Instruction],
) -> Int32? {
if body.length() < 4 {
return None
}
let if_idx = body.length() - 1
match body[if_idx] {
@lib.If(@lib.VoidBlockType, _, _) => ()
_ => return None
}
if !(body[if_idx - 1] is @lib.I32Eq) {
return None
}
match body[if_idx - 2] {
@lib.I32Const(@lib.I32(20)) => ()
_ => return None
}
match body[if_idx - 3] {
@lib.I32Const(_) => Some(if_idx - 3)
@lib.I32Load(_) if if_idx >= 4 && body[if_idx - 4] is @lib.LocalGet(_) =>
Some(if_idx - 4)
_ => None
}
}
///|
fn run_hot_pipeline_raw_vacuum_instr_can_tag20_prefix(
instr : @lib.Instruction,
) -> Bool {
match instr {
@lib.LocalGet(_)
| @lib.LocalSet(_)
| @lib.LocalTee(_)
| @lib.I32Const(_)
| @lib.I32Sub
| @lib.Call(_)
| @lib.Drop => true
_ => false
}
}
///|
fn run_hot_pipeline_raw_vacuum_body_is_tag20_call_local_set_prefix_if_preserve_candidate(
body : Array[@lib.Instruction],
) -> Bool {
let prefix_end = match
run_hot_pipeline_raw_vacuum_tag20_compare_prefix_end(body) {
Some(idx) => idx
None => return false
}
if !run_hot_pipeline_raw_vacuum_final_if_has_else_or_top_level_outer_branch(
body,
) {
return false
}
let mut local_set_count = 0
let mut call_count = 0
for idx = 0; idx < prefix_end; idx = idx + 1 {
let instr = body[idx]
if !run_hot_pipeline_raw_vacuum_instr_can_tag20_prefix(instr) {
return false
}
match instr {
@lib.LocalSet(_) => local_set_count = local_set_count + 1
@lib.Call(_) => call_count = call_count + 1
_ => ()
}
}
local_set_count >= 2 && call_count >= 1
}
///|
fn run_hot_pipeline_raw_vacuum_body_ends_local_set(
body : Array[@lib.Instruction],
) -> Int32? {
if body.length() == 0 {
return None
}
match body[body.length() - 1] {
@lib.LocalSet(@lib.LocalIdx(raw)) => Some(raw.reinterpret_as_int())
_ => None
}
}
///|
fn run_hot_pipeline_raw_vacuum_body_last_non_nop_before(
body : Array[@lib.Instruction],
end_exclusive : Int32,
) -> @lib.Instruction? {
let mut idx = end_exclusive - 1
while idx >= 0 {
match body[idx] {
@lib.Nop => idx = idx - 1
instr => return Some(instr)
}
}
None
}
///|
fn run_hot_pipeline_raw_vacuum_body_has_predicate_local_set_block(
body : Array[@lib.Instruction],
flag_local : Int32,
) -> Bool {
let cleaned = run_hot_pipeline_raw_vacuum_drop_leading_empty_blocks(body)
if cleaned.length() == 0 {
return false
}
match cleaned[cleaned.length() - 1] {
@lib.If(@lib.VoidBlockType, then_body, Some(else_body)) =>
match
(
run_hot_pipeline_raw_vacuum_body_ends_local_set(then_body),
run_hot_pipeline_raw_vacuum_body_ends_local_set(else_body),
) {
(Some(then_local), Some(else_local)) =>
then_local == flag_local && else_local == flag_local
_ => false
}
_ => false
}
}
///|
fn run_hot_pipeline_raw_vacuum_body_is_predicate_local_continuation_preserve_candidate(
body : Array[@lib.Instruction],
) -> Bool {
if body.length() < 3 {
return false
}
match body[0] {
@lib.Block(@lib.VoidBlockType, @lib.Expr(predicate_body)) => {
match body[body.length() - 1] {
@lib.If(@lib.VoidBlockType, _, _) => ()
_ => return false
}
let flag_local = match
run_hot_pipeline_raw_vacuum_body_last_non_nop_before(
body,
body.length() - 1,
) {
Some(@lib.LocalGet(@lib.LocalIdx(raw))) => raw.reinterpret_as_int()
_ => return false
}
run_hot_pipeline_raw_vacuum_body_has_predicate_local_set_block(
predicate_body, flag_local,
) &&
run_hot_pipeline_raw_vacuum_final_if_has_else_or_top_level_outer_branch(
body,
)
}
_ => false
}
}
///|
fn run_hot_pipeline_raw_vacuum_body_has_branchy_else_outer_branch(
body : Array[@lib.Instruction],
) -> Bool {
for instr in body {
match instr {
@lib.If(@lib.VoidBlockType, _, Some(else_body)) =>
if run_hot_pipeline_raw_vacuum_body_has_top_level_outer_branch(
else_body,
) {
return true
}
@lib.If(_, then_body, Some(else_body)) =>
if run_hot_pipeline_raw_vacuum_body_has_branchy_else_outer_branch(
then_body,
) ||
run_hot_pipeline_raw_vacuum_body_has_branchy_else_outer_branch(
else_body,
) {
return true
}
@lib.If(_, then_body, None) =>
if run_hot_pipeline_raw_vacuum_body_has_branchy_else_outer_branch(
then_body,
) {
return true
}
@lib.Block(_, @lib.Expr(child_body))
| @lib.Loop(_, @lib.Expr(child_body))
| @lib.TryTable(_, _, @lib.Expr(child_body)) =>
if run_hot_pipeline_raw_vacuum_body_has_branchy_else_outer_branch(
child_body,
) {
return true
}
_ => ()
}
}
false
}
///|
fn run_hot_pipeline_raw_vacuum_body_is_branchy_loop_dispatch_to_parent_block(
body : Array[@lib.Instruction],
) -> Bool {
if body.length() < 3 {
return false
}
match body[body.length() - 1] {
@lib.Br(label) if run_hot_pipeline_raw_vacuum_label_depth(label) > 0 => ()
_ => return false
}
let mut if_idx = -1
for idx = 0; idx + 1 < body.length(); idx = idx + 1 {
match body[idx] {
@lib.If(@lib.VoidBlockType, then_body, Some(_)) => {
if_idx = idx
if then_body.length() == 0 {
return false
}
break
}
@lib.Nop
| @lib.LocalGet(_)
| @lib.LocalTee(_)
| @lib.I32Load(_)
| @lib.I32Const(_)
| @lib.I32Eq
| @lib.I32GtS
| @lib.I32Sub
| @lib.Call(_) => ()
_ => return false
}
}
if if_idx < 0 {
return false
}
for idx = if_idx + 1; idx + 1 < body.length(); idx = idx + 1 {
match body[idx] {
@lib.I32Const(_) | @lib.LocalGet(_) | @lib.LocalSet(_) | @lib.Nop => ()
_ => return false
}
}
true
}
///|
fn run_hot_pipeline_raw_vacuum_body_is_f649_branchy_loop_dispatch_parent_wrapper_candidate(
body : Array[@lib.Instruction],
) -> Bool {
if body.length() != 1 {
return false
}
let parent_body = match body[0] {
@lib.Block(@lib.VoidBlockType, @lib.Expr(parent_body)) => parent_body
_ => return false
}
if parent_body.length() < 3 {
return false
}
match parent_body[0] {
@lib.Block(@lib.VoidBlockType, @lib.Expr(dispatch_body)) =>
if !run_hot_pipeline_raw_vacuum_body_is_branchy_loop_dispatch_to_parent_block(
dispatch_body,
) {
return false
}
_ => return false
}
let mut continuation_if_idx = -1
let mut saw_condition_call = false
for idx = 1; idx < parent_body.length(); idx = idx + 1 {
match parent_body[idx] {
@lib.If(@lib.VoidBlockType, _, Some(_)) => {
continuation_if_idx = idx
break
}
@lib.Call(_) => saw_condition_call = true
@lib.Nop
| @lib.LocalGet(_)
| @lib.LocalTee(_)
| @lib.I32Load(_)
| @lib.I32Const(_)
| @lib.I32Eq => ()
_ => return false
}
}
if continuation_if_idx < 0 || !saw_condition_call {
return false
}
for idx = continuation_if_idx + 1; idx < parent_body.length(); idx = idx + 1 {
match parent_body[idx] {
@lib.Nop | @lib.I32Const(_) | @lib.LocalGet(_) | @lib.LocalSet(_) => ()
_ => return false
}
}
if !(parent_body[parent_body.length() - 1] is @lib.LocalSet(_)) {
return false
}
match parent_body[continuation_if_idx] {
@lib.If(@lib.VoidBlockType, then_body, Some(_)) =>
run_hot_pipeline_raw_vacuum_body_has_top_level_outer_branch(then_body)
_ => false
}
}
///|
fn run_hot_pipeline_raw_vacuum_body_has_f749_outer_branch(
body : Array[@lib.Instruction],
) -> Bool {
for instr in body {
match instr {
@lib.Br(label) if run_hot_pipeline_raw_vacuum_label_depth(label) >= 4 =>
return true
@lib.If(_, then_body, Some(else_body)) =>
if run_hot_pipeline_raw_vacuum_body_has_f749_outer_branch(then_body) ||
run_hot_pipeline_raw_vacuum_body_has_f749_outer_branch(else_body) {
return true
}
@lib.If(_, then_body, None) =>
if run_hot_pipeline_raw_vacuum_body_has_f749_outer_branch(then_body) {
return true
}
@lib.Block(_, @lib.Expr(child_body))
| @lib.Loop(_, @lib.Expr(child_body))
| @lib.TryTable(_, _, @lib.Expr(child_body)) =>
if run_hot_pipeline_raw_vacuum_body_has_f749_outer_branch(child_body) {
return true
}
_ => ()
}
}
false
}
///|
fn run_hot_pipeline_raw_vacuum_body_has_top_level_local_set(
body : Array[@lib.Instruction],
) -> Bool {
for instr in body {
if instr is @lib.LocalSet(_) {
return true
}
}
false
}
///|
fn run_hot_pipeline_raw_vacuum_body_is_f749_inner_tag_selection(
body : Array[@lib.Instruction],
requires_branch_in_then : Bool,
) -> Bool {
if body.length() < 7 {
return false
}
match (body[0], body[1], body[2], body[3], body[4], body[5]) {
(
@lib.LocalGet(_),
@lib.I32Load(_),
@lib.LocalTee(_),
@lib.Call(_),
@lib.I32Const(@lib.I32(2)),
@lib.I32Eq,
) => ()
_ => return false
}
match body[6] {
@lib.If(@lib.VoidBlockType, then_body, Some(else_body)) => {
let then_has_required_state = if requires_branch_in_then {
run_hot_pipeline_raw_vacuum_body_has_f749_outer_branch(then_body) &&
run_hot_pipeline_raw_vacuum_body_has_top_level_local_set(then_body)
} else {
run_hot_pipeline_raw_vacuum_body_ends_local_set(then_body) is Some(_)
}
then_has_required_state &&
run_hot_pipeline_raw_vacuum_body_has_f749_outer_branch(else_body)
}
_ => false
}
}
///|
fn run_hot_pipeline_raw_vacuum_body_is_f749_branchy_selection(
body : Array[@lib.Instruction],
) -> Bool {
if body.length() != 5 {
return false
}
match (body[0], body[1], body[2], body[3]) {
(@lib.LocalGet(_), @lib.Call(_), @lib.I32Const(@lib.I32(1)), @lib.I32Eq) =>
()
_ => return false
}
match body[4] {
@lib.If(@lib.VoidBlockType, then_body, Some(else_body)) => {
if else_body.length() < 8 ||
!(else_body[else_body.length() - 1] is @lib.Unreachable) {
return false
}
run_hot_pipeline_raw_vacuum_body_is_f749_inner_tag_selection(
then_body, false,
) &&
run_hot_pipeline_raw_vacuum_body_is_f749_inner_tag_selection(
else_body, true,
)
}
_ => false
}
}
///|
fn run_hot_pipeline_raw_vacuum_body_is_f749_double_branchy_selection_wrapper_candidate(
body : Array[@lib.Instruction],
) -> Bool {
if body.length() != 1 {
return false
}
match body[0] {
@lib.Block(@lib.VoidBlockType, @lib.Expr(child_body)) =>
run_hot_pipeline_raw_vacuum_body_is_f749_branchy_selection(child_body)
_ => false
}
}
///|
fn run_hot_pipeline_raw_vacuum_has_f749_i64_return_continuation(
instrs : Array[@lib.Instruction],
idx : Int32,
) -> Bool {
let sig : Array[@lib.Instruction] = []
for pos = idx + 1; pos < instrs.length(); pos = pos + 1 {
match instrs[pos] {
@lib.Nop => ()
other => sig.push(other)
}
if sig.length() >= 7 {
break
}
}
if sig.length() < 7 {
return false
}
match (sig[0], sig[1], sig[2], sig[3], sig[4], sig[5], sig[6]) {
(
@lib.LocalGet(@lib.LocalIdx(value_local_a)),
@lib.Call(_),
@lib.LocalSet(@lib.LocalIdx(result_local_a)),
@lib.LocalGet(@lib.LocalIdx(value_local_b)),
@lib.Call(_),
@lib.LocalGet(@lib.LocalIdx(result_local_b)),
@lib.I64ExtendI32S,
) => value_local_a == value_local_b && result_local_a == result_local_b
_ => false
}
}
///|
fn run_hot_pipeline_raw_vacuum_body_is_branchy_loop_dispatch_singleton_wrapper_candidate(
body : Array[@lib.Instruction],
) -> Bool {
if !run_hot_pipeline_raw_vacuum_instrs_contain_unreachable(body) {
return false
}
if body.length() == 1 {
return match body[0] {
@lib.If(@lib.VoidBlockType, then_body, None) =>
run_hot_pipeline_raw_vacuum_instrs_have_terminal_unreachable_debris(
then_body,
) ||
run_hot_pipeline_raw_vacuum_instrs_contain_unreachable(then_body)
_ => false
}
}
if body.length() != 6 {
return false
}
match (body[0], body[1], body[2], body[3], body[4], body[5]) {
(
@lib.LocalGet(_),
@lib.I32Load(_),
@lib.LocalGet(_),
@lib.Call(_),
@lib.I32LtS,
@lib.If(@lib.VoidBlockType, then_body, Some(_)),
) =>
run_hot_pipeline_raw_vacuum_instrs_have_terminal_unreachable_debris(
then_body,
) ||
run_hot_pipeline_raw_vacuum_instrs_contain_unreachable(then_body)
_ => false
}
}
///|
fn run_hot_pipeline_raw_vacuum_body_is_tag_load_dispatch_if_wrapper_candidate(
body : Array[@lib.Instruction],
) -> Bool {
let mut end = body.length()
if end > 0 && body[end - 1] is @lib.Unreachable {
end = end - 1
}
if end < 6 {
return false
}
match body[end - 1] {
@lib.If(@lib.VoidBlockType, _, Some(_)) => ()
_ => return false
}
if !(body[end - 2] is @lib.I32Eq) {
return false
}
match body[end - 3] {
@lib.I32Const(_) => ()
_ => return false
}
let mut saw_call = false
let mut saw_load = false
let mut saw_tee = false
for idx = 0; idx + 1 < end; idx = idx + 1 {
match body[idx] {
@lib.LocalGet(_) | @lib.I32Const(_) | @lib.I32Eq => ()
@lib.LocalTee(_) => saw_tee = true
@lib.I32Load(_) => saw_load = true
@lib.Call(_) => saw_call = true
_ => return false
}
}
saw_call && saw_load && saw_tee
}
///|
fn run_hot_pipeline_raw_vacuum_body_is_branchy_local_set_call_prefix_parent_unwrap_candidate(
original_body : Array[@lib.Instruction],
protected_body : Array[@lib.Instruction],
) -> Bool {
if !run_hot_pipeline_raw_vacuum_body_has_branchy_else_outer_branch(
original_body,
) {
return false
}
if !run_hot_pipeline_raw_vacuum_body_is_single_block_prefix_if_wrapper_candidate(
protected_body,
) {
return false
}
if run_hot_pipeline_raw_vacuum_instrs_contain_unreachable(protected_body) {
return false
}
let prefix_body = match protected_body[0] {
@lib.Block(@lib.VoidBlockType, @lib.Expr(prefix_body)) => prefix_body
_ => return false
}
run_hot_pipeline_raw_vacuum_prefix_block_has_direct_call_local_set_and_later_call(
prefix_body,
) &&
run_hot_pipeline_raw_vacuum_condition_prefix_has_call(protected_body) &&
run_hot_pipeline_raw_vacuum_final_if_has_else_or_top_level_outer_branch(
protected_body,
)
}
///|
fn run_hot_pipeline_raw_vacuum_body_is_local_set_leading_prefix_if_preserve_candidate(
body : Array[@lib.Instruction],
) -> Bool {
if !run_hot_pipeline_raw_vacuum_body_is_single_block_prefix_if_wrapper_candidate(
body,
) ||
run_hot_pipeline_raw_vacuum_instrs_contain_unreachable(body) {
return false
}
let prefix_body = match body[0] {
@lib.Block(@lib.VoidBlockType, @lib.Expr(prefix_body)) => prefix_body
_ => return false
}
run_hot_pipeline_raw_vacuum_prefix_block_starts_call_result_local_set(
prefix_body,
) &&
run_hot_pipeline_raw_vacuum_condition_prefix_has_call(body) &&
run_hot_pipeline_raw_vacuum_final_if_has_else_or_top_level_outer_branch(body)
}
///|
fn run_hot_pipeline_raw_vacuum_body_is_two_arg_call_local_set_prefix_if_preserve_candidate(
body : Array[@lib.Instruction],
) -> Bool {
if !run_hot_pipeline_raw_vacuum_body_is_single_block_prefix_if_wrapper_candidate(
body,
) ||
run_hot_pipeline_raw_vacuum_instrs_contain_unreachable(body) {
return false
}
let prefix_body = match body[0] {
@lib.Block(@lib.VoidBlockType, @lib.Expr(prefix_body)) => prefix_body
_ => return false
}
run_hot_pipeline_raw_vacuum_prefix_block_starts_two_arg_call_result_local_set_release(
prefix_body,
) &&
run_hot_pipeline_raw_vacuum_condition_prefix_has_call(body) &&
run_hot_pipeline_raw_vacuum_final_if_has_else_or_top_level_outer_branch(body)
}
///|
fn run_hot_pipeline_raw_vacuum_body_is_f5032_two_release_prefix_if_parent_preserve_candidate(
body : Array[@lib.Instruction],
) -> Bool {
if !run_hot_pipeline_raw_vacuum_body_is_single_block_prefix_if_wrapper_candidate(
body,
) ||
run_hot_pipeline_raw_vacuum_instrs_contain_unreachable(body) {
return false
}
let prefix_body = match body[0] {
@lib.Block(@lib.VoidBlockType, @lib.Expr(prefix_body)) => prefix_body
_ => return false
}
run_hot_pipeline_raw_vacuum_prefix_block_starts_two_arg_call_result_local_set_double_release(
prefix_body,
) &&
run_hot_pipeline_raw_vacuum_condition_prefix_has_call(body) &&
run_hot_pipeline_raw_vacuum_final_if_has_else_or_top_level_outer_branch(body)
}
///|
fn run_hot_pipeline_raw_vacuum_body_is_f5138_load_linked_result_prefix_parent_preserve_candidate(
body : Array[@lib.Instruction],
) -> Bool {
let non_nops = run_hot_pipeline_raw_vacuum_strip_top_level_nops(body)
if non_nops.length() != 5 ||
!run_hot_pipeline_raw_vacuum_body_is_single_block_prefix_if_wrapper_candidate(
non_nops,
) ||
run_hot_pipeline_raw_vacuum_instrs_contain_unreachable(non_nops) {
return false
}
let prefix_body = match non_nops[0] {
@lib.Block(@lib.VoidBlockType, @lib.Expr(prefix_body)) =>
run_hot_pipeline_raw_vacuum_strip_top_level_nops(prefix_body)
_ => return false
}
if prefix_body.length() != 10 {
return false
}
let (retained_raw, target_raw, context_raw) = match
(
prefix_body[0],
prefix_body[1],
prefix_body[2],
prefix_body[3],
prefix_body[4],
prefix_body[5],
) {
(
@lib.LocalGet(_),
@lib.Call(_),
@lib.LocalTee(@lib.LocalIdx(retained_raw)),
@lib.LocalGet(@lib.LocalIdx(context_raw)),
@lib.Call(_),
@lib.LocalSet(@lib.LocalIdx(target_raw)),
) => (retained_raw, target_raw, context_raw)
_ => return false
}
match (prefix_body[6], prefix_body[7], prefix_body[8], prefix_body[9]) {
(
@lib.LocalGet(@lib.LocalIdx(release_retained_raw)),
@lib.Call(_),
@lib.LocalGet(@lib.LocalIdx(release_context_raw)),
@lib.Call(_),
) if release_retained_raw == retained_raw &&
release_context_raw == context_raw => ()
_ => return false
}
match (non_nops[1], non_nops[2], non_nops[3], non_nops[4]) {
(
@lib.LocalGet(@lib.LocalIdx(read_target_raw)),
@lib.LocalTee(_),
@lib.Call(_),
@lib.If(@lib.VoidBlockType, _, Some(else_body)),
) if read_target_raw == target_raw =>
run_hot_pipeline_raw_vacuum_else_starts_with_release_pairs(else_body, 5)
_ => false
}
}
///|
fn run_hot_pipeline_raw_vacuum_else_starts_with_release_pairs(
else_body : Array[@lib.Instruction],
min_pair_count : Int32,
) -> Bool {
let else_non_nops = run_hot_pipeline_raw_vacuum_strip_top_level_nops(
else_body,
)
let mut release_pair_count = 0
for idx = 0; idx + 1 < else_non_nops.length(); idx = idx + 2 {
match (else_non_nops[idx], else_non_nops[idx + 1]) {
(@lib.LocalGet(_), @lib.Call(_)) =>
release_pair_count = release_pair_count + 1
_ => break
}
}
release_pair_count >= min_pair_count
}
///|
fn run_hot_pipeline_raw_vacuum_body_is_f5138_paired_result_prefix_parent_preserve_candidate(
body : Array[@lib.Instruction],
) -> Bool {
let non_nops = run_hot_pipeline_raw_vacuum_strip_top_level_nops(body)
if non_nops.length() != 5 ||
!run_hot_pipeline_raw_vacuum_body_is_single_block_prefix_if_wrapper_candidate(
non_nops,
) ||
run_hot_pipeline_raw_vacuum_instrs_contain_unreachable(non_nops) {
return false
}
let paired_body = match non_nops[0] {
@lib.Block(@lib.VoidBlockType, @lib.Expr(paired_body)) =>
run_hot_pipeline_raw_vacuum_strip_top_level_nops(paired_body)
_ => return false
}
if paired_body.length() != 9 {
return false
}
let prefix_body = match paired_body[0] {
@lib.Block(@lib.VoidBlockType, @lib.Expr(prefix_body)) =>
run_hot_pipeline_raw_vacuum_strip_top_level_nops(prefix_body)
_ => return false
}
let source_raw = match prefix_body {
[
@lib.LocalGet(@lib.LocalIdx(retained_raw)),
@lib.Call(_),
@lib.LocalSet(@lib.LocalIdx(source_raw)),
@lib.LocalGet(@lib.LocalIdx(release_retained_raw)),
@lib.Call(_),
] if release_retained_raw == retained_raw => source_raw
_ => return false
}
let target_raw = match
(
paired_body[1],
paired_body[2],
paired_body[3],
paired_body[4],
paired_body[5],
paired_body[6],
paired_body[7],
paired_body[8],
) {
(
@lib.LocalGet(@lib.LocalIdx(read_source_raw)),
@lib.LocalGet(@lib.LocalIdx(context_raw)),
@lib.Call(_),
@lib.LocalSet(@lib.LocalIdx(target_raw)),
@lib.LocalGet(@lib.LocalIdx(release_source_raw)),
@lib.Call(_),
@lib.LocalGet(@lib.LocalIdx(release_context_raw)),
@lib.Call(_),
) if read_source_raw == source_raw &&
release_source_raw == source_raw &&
release_context_raw == context_raw => target_raw
_ => return false
}
match (non_nops[1], non_nops[2], non_nops[3], non_nops[4]) {
(
@lib.LocalGet(@lib.LocalIdx(read_target_raw)),
@lib.LocalTee(_),
@lib.Call(_),
@lib.If(@lib.VoidBlockType, _, Some(else_body)),
) if read_target_raw == target_raw =>
run_hot_pipeline_raw_vacuum_else_starts_with_release_pairs(else_body, 2)
_ => false
}
}
///|
fn run_hot_pipeline_raw_vacuum_f5154_selected_local_from_tag_if(
final_if : @lib.Instruction,
) -> UInt32? {
match final_if {
@lib.If(@lib.VoidBlockType, then_body, Some(else_body)) => {
if !run_hot_pipeline_raw_instrs_have_branch(else_body) ||
!run_hot_pipeline_raw_vacuum_else_starts_with_release_pairs(
else_body, 2,
) {
return None
}
let then_non_nops = run_hot_pipeline_raw_vacuum_strip_top_level_nops(
then_body,
)
if then_non_nops.length() < 2 {
return None
}
match
(
then_non_nops[then_non_nops.length() - 2],
then_non_nops[then_non_nops.length() - 1],
) {
(
@lib.LocalGet(@lib.LocalIdx(payload_raw)),
@lib.LocalSet(@lib.LocalIdx(selected_raw)),
) if payload_raw != selected_raw => Some(selected_raw)
_ => None
}
}
_ => None
}
}
///|
fn run_hot_pipeline_raw_vacuum_f5184_selected_local_from_tag_if(
final_if : @lib.Instruction,
) -> UInt32? {
match final_if {
@lib.If(@lib.VoidBlockType, then_body, Some(else_body)) => {
if !run_hot_pipeline_raw_instrs_have_branch(else_body) {
return None
}
let then_non_nops = run_hot_pipeline_raw_vacuum_strip_top_level_nops(
then_body,
)
if then_non_nops.length() < 2 {
return None
}
let selected_raw = match
(
then_non_nops[then_non_nops.length() - 2],
then_non_nops[then_non_nops.length() - 1],
) {
(
@lib.LocalGet(@lib.LocalIdx(payload_raw)),
@lib.LocalSet(@lib.LocalIdx(selected_raw)),
) if payload_raw != selected_raw => selected_raw
_ => return None
}
let else_non_nops = run_hot_pipeline_raw_vacuum_strip_top_level_nops(
else_body,
)
if else_non_nops.length() < 6 {
return None
}
match (else_non_nops[0], else_non_nops[1]) {
(@lib.LocalGet(_), @lib.Call(_)) => ()
_ => return None
}
let mut conditional_release_count = 0
let mut idx = 2
while idx + 1 < else_non_nops.length() {
match (else_non_nops[idx], else_non_nops[idx + 1]) {
(
@lib.LocalGet(@lib.LocalIdx(guard_raw)),
@lib.If(@lib.VoidBlockType, guarded_body, None),
) =>
match guarded_body {
[@lib.LocalGet(@lib.LocalIdx(release_raw)), @lib.Call(_)] if release_raw ==
guard_raw => {
conditional_release_count = conditional_release_count + 1
idx = idx + 2
continue
}
_ => ()
}
_ => ()
}
break
}
if conditional_release_count == 0 || else_non_nops.length() < idx + 3 {
return None
}
match
(
else_non_nops[else_non_nops.length() - 3],
else_non_nops[else_non_nops.length() - 2],
else_non_nops[else_non_nops.length() - 1],
) {
(
@lib.LocalGet(@lib.LocalIdx(payload_raw)),
@lib.LocalSet(@lib.LocalIdx(else_selected_raw)),
@lib.Br(label),
) if payload_raw != else_selected_raw &&
run_hot_pipeline_raw_vacuum_label_depth(label) >= 2 =>
Some(selected_raw)
_ => None
}
}
_ => None
}
}
///|
fn run_hot_pipeline_raw_vacuum_body_has_f5154_slot_replace_continuation(
parent_body : Array[@lib.Instruction],
start_idx : Int32,
slot_raw : UInt32,
selected_raw : UInt32,
) -> Bool {
let sig : Array[@lib.Instruction] = []
let mut idx = start_idx + 1
while idx < parent_body.length() && sig.length() < 6 {
match parent_body[idx] {
@lib.Nop => ()
instr => sig.push(instr)
}
idx = idx + 1
}
match sig {
[
@lib.LocalGet(@lib.LocalIdx(release_slot_raw)),
@lib.I32Load(_),
@lib.Call(_),
@lib.LocalGet(@lib.LocalIdx(store_slot_raw)),
@lib.LocalGet(@lib.LocalIdx(store_value_raw)),
@lib.I32Store(_),
] =>
release_slot_raw == slot_raw &&
store_slot_raw == slot_raw &&
store_value_raw == selected_raw
_ => false
}
}
///|
fn run_hot_pipeline_raw_vacuum_body_is_f5154_retain_pair_result_before_slot_store_preserve_candidate(
body : Array[@lib.Instruction],
parent_body : Array[@lib.Instruction],
start_idx : Int32,
) -> Bool {
let non_nops = run_hot_pipeline_raw_vacuum_strip_top_level_nops(body)
if non_nops.length() != 20 ||
!run_hot_pipeline_raw_vacuum_body_is_side_effect_prefix_if_wrapper_candidate(
non_nops,
) ||
run_hot_pipeline_raw_vacuum_instrs_contain_unreachable(non_nops) {
return false
}
let (slot_raw, selected_raw) = match
(
non_nops[0],
non_nops[1],
non_nops[2],
non_nops[3],
non_nops[4],
non_nops[5],
non_nops[6],
non_nops[7],
non_nops[8],
non_nops[9],
non_nops[10],
non_nops[11],
non_nops[12],
non_nops[13],
non_nops[14],
non_nops[15],
non_nops[16],
non_nops[17],
non_nops[18],
non_nops[19],
) {
(
@lib.LocalGet(@lib.LocalIdx(slot_raw)),
@lib.I32Load(_),
@lib.LocalTee(@lib.LocalIdx(retained_raw)),
@lib.Call(_),
@lib.LocalGet(@lib.LocalIdx(context_raw)),
@lib.Call(_),
@lib.LocalGet(@lib.LocalIdx(call_context_raw)),
@lib.LocalGet(@lib.LocalIdx(call_retained_raw)),
@lib.Call(_),
@lib.LocalSet(@lib.LocalIdx(result_raw)),
@lib.LocalGet(@lib.LocalIdx(release_context_raw)),
@lib.Call(_),
@lib.LocalGet(@lib.LocalIdx(release_retained_raw)),
@lib.Call(_),
@lib.LocalGet(@lib.LocalIdx(read_result_raw)),
@lib.LocalTee(@lib.LocalIdx(tagged_raw)),
@lib.Call(_),
@lib.I32Const(@lib.I32(1)),
@lib.I32Eq,
final_if,
) if slot_raw != retained_raw &&
context_raw != retained_raw &&
call_context_raw == context_raw &&
call_retained_raw == retained_raw &&
release_context_raw == context_raw &&
release_retained_raw == retained_raw &&
read_result_raw == result_raw &&
tagged_raw != result_raw =>
match
run_hot_pipeline_raw_vacuum_f5154_selected_local_from_tag_if(final_if) {
Some(selected_raw) => (slot_raw, selected_raw)
None => return false
}
_ => return false
}
run_hot_pipeline_raw_vacuum_body_has_f5154_slot_replace_continuation(
parent_body, start_idx, slot_raw, selected_raw,
)
}
///|
fn run_hot_pipeline_raw_vacuum_body_has_f5184_drop_call_continuation(
parent_body : Array[@lib.Instruction],
start_idx : Int32,
) -> Bool {
let sig : Array[@lib.Instruction] = []
let mut idx = start_idx + 1
while idx < parent_body.length() && sig.length() < 6 {
match parent_body[idx] {
@lib.Nop => ()
instr => sig.push(instr)
}
idx = idx + 1
}
match sig {
[
@lib.LocalGet(_),
@lib.I32Const(_),
@lib.LocalGet(_),
@lib.LocalGet(_),
@lib.Call(_),
@lib.Drop,
] => true
_ => false
}
}
///|
fn run_hot_pipeline_raw_vacuum_body_is_f5184_single_retain_result_before_drop_continuation_preserve_candidate(
body : Array[@lib.Instruction],
parent_body : Array[@lib.Instruction],
start_idx : Int32,
) -> Bool {
let non_nops = run_hot_pipeline_raw_vacuum_strip_top_level_nops(body)
if non_nops.length() != 16 ||
!run_hot_pipeline_raw_vacuum_body_is_side_effect_prefix_if_wrapper_candidate(
non_nops,
) ||
run_hot_pipeline_raw_vacuum_instrs_contain_unreachable(non_nops) {
return false
}
match
(
non_nops[0],
non_nops[1],
non_nops[2],
non_nops[3],
non_nops[4],
non_nops[5],
non_nops[6],
non_nops[7],
non_nops[8],
non_nops[9],
non_nops[10],
non_nops[11],
non_nops[12],
non_nops[13],
non_nops[14],
non_nops[15],
) {
(
@lib.LocalGet(@lib.LocalIdx(slot_raw)),
@lib.I32Load(_),
@lib.LocalTee(@lib.LocalIdx(retained_raw)),
@lib.Call(_),
@lib.LocalGet(@lib.LocalIdx(context_raw)),
@lib.LocalGet(@lib.LocalIdx(call_retained_raw)),
@lib.Call(_),
@lib.LocalSet(@lib.LocalIdx(result_raw)),
@lib.LocalGet(@lib.LocalIdx(release_retained_raw)),
@lib.Call(_),
@lib.LocalGet(@lib.LocalIdx(read_result_raw)),
@lib.LocalTee(@lib.LocalIdx(tagged_raw)),
@lib.Call(_),
@lib.I32Const(@lib.I32(1)),
@lib.I32Eq,
final_if,
) if slot_raw != retained_raw &&
context_raw != retained_raw &&
call_retained_raw == retained_raw &&
release_retained_raw == retained_raw &&
read_result_raw == result_raw &&
tagged_raw != result_raw =>
if !(run_hot_pipeline_raw_vacuum_f5184_selected_local_from_tag_if(
final_if,
)
is Some(_)) {
return false
}
_ => return false
}
run_hot_pipeline_raw_vacuum_body_has_f5184_drop_call_continuation(
parent_body, start_idx,
)
}
///|
fn run_hot_pipeline_raw_vacuum_try_flatten_f5138_outer_singleton_parent(
body : Array[@lib.Instruction],
module_ctx : @ir.HotModuleContext?,
) -> Array[@lib.Instruction]? {
if body.length() != 1 {
return None
}
let child_body = match body[0] {
@lib.Block(@lib.VoidBlockType, @lib.Expr(child_body)) => child_body
_ => return None
}
if !run_hot_pipeline_raw_vacuum_body_is_f5138_load_linked_result_prefix_parent_preserve_candidate(
child_body,
) {
return None
}
let rebased = run_hot_pipeline_raw_vacuum_rebase_outer_body(body, 0)
let rebased_child_body = match rebased[0] {
@lib.Block(@lib.VoidBlockType, @lib.Expr(rebased_child_body)) =>
rebased_child_body
_ => child_body
}
let (clean_child_body, _) = run_hot_pipeline_raw_vacuum_preclean_instrs_owned(
rebased_child_body, module_ctx,
)
Some([
@lib.Instruction::block(
@lib.BlockType::void_(),
@lib.Expr::new(clean_child_body),
),
])
}
///|
fn run_hot_pipeline_raw_vacuum_body_is_f1383_dual_i64_probe_prefix_if_preserve_candidate(
body : Array[@lib.Instruction],
) -> Bool {
let non_nops : Array[@lib.Instruction] = []
for instr in body {
match instr {
@lib.Nop => ()
_ => non_nops.push(instr)
}
}
if non_nops.length() != body.length() {
return run_hot_pipeline_raw_vacuum_body_is_f1383_dual_i64_probe_prefix_if_preserve_candidate(
non_nops,
)
}
if body.length() < 16 ||
run_hot_pipeline_raw_vacuum_instrs_contain_unreachable(body) {
return false
}
let if_idx = body.length() - 1
match body[if_idx] {
@lib.If(@lib.VoidBlockType, _, Some(_)) => ()
_ => return false
}
if !(body[if_idx - 1] is @lib.I64Eq) {
return false
}
match body[if_idx - 2] {
@lib.I64Const(@lib.I64(4294967296L)) => ()
_ => return false
}
let compared_local = match body[if_idx - 3] {
@lib.LocalGet(@lib.LocalIdx(raw)) => raw.reinterpret_as_int()
_ => return false
}
let stable_locals : Array[Int32] = []
for idx = 0; idx + 5 < if_idx - 3; idx = idx + 1 {
match
(
body[idx],
body[idx + 1],
body[idx + 2],
body[idx + 3],
body[idx + 4],
body[idx + 5],
) {
(
@lib.Call(_),
@lib.LocalSet(@lib.LocalIdx(result_raw)),
@lib.LocalGet(_),
@lib.Call(_),
@lib.LocalGet(@lib.LocalIdx(copy_raw)),
@lib.LocalSet(@lib.LocalIdx(stable_raw)),
) if copy_raw == result_raw =>
stable_locals.push(stable_raw.reinterpret_as_int())
_ => ()
}
}
stable_locals.length() >= 2 && stable_locals[0] == compared_local
}
///|
fn run_hot_pipeline_raw_vacuum_body_is_f1346_local_set_release_prefix_if_preserve_candidate(
body : Array[@lib.Instruction],
) -> Bool {
let non_nops : Array[@lib.Instruction] = []
for instr in body {
match instr {
@lib.Nop => ()
_ => non_nops.push(instr)
}
}
if non_nops.length() != body.length() {
return run_hot_pipeline_raw_vacuum_body_is_f1346_local_set_release_prefix_if_preserve_candidate(
non_nops,
)
}
if body.length() != 3 ||
!run_hot_pipeline_raw_vacuum_body_is_single_block_prefix_if_wrapper_candidate(
body,
) ||
run_hot_pipeline_raw_vacuum_instrs_contain_unreachable(body) {
return false
}
let prefix_body = match body[0] {
@lib.Block(@lib.VoidBlockType, @lib.Expr(prefix_body)) => prefix_body
_ => return false
}
let target_local = match
run_hot_pipeline_raw_vacuum_two_arg_call_result_local_set_release_target(
prefix_body,
) {
Some(local_id) => local_id
None => return false
}
match body[1] {
@lib.LocalGet(@lib.LocalIdx(raw)) if raw.reinterpret_as_int() ==
target_local => ()
_ => return false
}
run_hot_pipeline_raw_vacuum_final_if_has_else_or_top_level_outer_branch(body)
}
///|
fn run_hot_pipeline_raw_vacuum_body_has_local_release_pair(
body : Array[@lib.Instruction],
local_raw : UInt32,
) -> Bool {
for idx = 0; idx + 1 < body.length(); idx = idx + 1 {
match (body[idx], body[idx + 1]) {
(@lib.LocalGet(@lib.LocalIdx(raw)), @lib.Call(_)) if raw == local_raw =>
return true
_ => ()
}
}
false
}
///|
fn run_hot_pipeline_raw_vacuum_body_is_f3469_const_arg_local_set_release_prefix_if_preserve_candidate(
body : Array[@lib.Instruction],
) -> Bool {
let non_nops : Array[@lib.Instruction] = []
for instr in body {
match instr {
@lib.Nop => ()
_ => non_nops.push(instr)
}
}
if non_nops.length() != body.length() {
return run_hot_pipeline_raw_vacuum_body_is_f3469_const_arg_local_set_release_prefix_if_preserve_candidate(
non_nops,
)
}
if body.length() != 7 ||
!run_hot_pipeline_raw_vacuum_body_is_single_block_prefix_if_wrapper_candidate(
body,
) ||
run_hot_pipeline_raw_vacuum_instrs_contain_unreachable(body) {
return false
}
let (receiver_raw, target_raw) = match body[0] {
@lib.Block(@lib.VoidBlockType, @lib.Expr(prefix_body)) => {
let prefix_non_nops : Array[@lib.Instruction] = []
for instr in prefix_body {
match instr {
@lib.Nop => ()
_ => prefix_non_nops.push(instr)
}
}
match prefix_non_nops {
[
@lib.LocalGet(@lib.LocalIdx(receiver_raw)),
@lib.I32Const(@lib.I32(0)),
@lib.Call(_),
@lib.LocalSet(@lib.LocalIdx(target_raw)),
@lib.LocalGet(@lib.LocalIdx(release_raw)),
@lib.Call(_),
] if release_raw == receiver_raw => (receiver_raw, target_raw)
_ => return false
}
}
_ => return false
}
match (body[1], body[2], body[3], body[4], body[5], body[6]) {
(
@lib.LocalGet(@lib.LocalIdx(read_raw)),
@lib.LocalTee(@lib.LocalIdx(tagged_raw)),
@lib.Call(_),
@lib.I32Const(@lib.I32(1)),
@lib.I32Eq,
@lib.If(@lib.VoidBlockType, then_body, Some(else_body)),
) if read_raw == target_raw =>
receiver_raw != target_raw &&
run_hot_pipeline_raw_vacuum_body_has_local_release_pair(
then_body, tagged_raw,
) &&
run_hot_pipeline_raw_vacuum_body_has_local_release_pair(
else_body, tagged_raw,
) &&
run_hot_pipeline_raw_vacuum_body_has_top_level_outer_branch(else_body)
_ => false
}
}
///|
fn run_hot_pipeline_raw_vacuum_f3561_branch_if_cleanup_local(
final_if : @lib.Instruction,
result_raw : UInt32,
) -> Int32? {
match final_if {
@lib.If(@lib.VoidBlockType, then_body, Some(else_body)) => {
let cleanup_raw = match then_body {
[
@lib.LocalGet(@lib.LocalIdx(then_result_raw)),
@lib.Call(_),
@lib.If(@lib.VoidBlockType, nested_then, Some(nested_else)),
] if then_result_raw == result_raw => {
match nested_then {
[
@lib.LocalGet(@lib.LocalIdx(release_raw)),
@lib.Call(_),
@lib.Br(label),
] if release_raw == result_raw &&
run_hot_pipeline_raw_vacuum_label_depth(label) >= 1 => ()
_ => return None
}
match nested_else {
[
@lib.LocalGet(@lib.LocalIdx(next_result_raw)),
@lib.LocalSet(@lib.LocalIdx(cleanup_raw)),
] if next_result_raw == result_raw => cleanup_raw
_ => return None
}
}
_ => return None
}
match else_body {
[
@lib.LocalGet(@lib.LocalIdx(else_result_raw)),
@lib.If(@lib.VoidBlockType, release_then, None),
@lib.Br(label),
] if else_result_raw == result_raw &&
run_hot_pipeline_raw_vacuum_label_depth(label) >= 1 =>
match release_then {
[@lib.LocalGet(@lib.LocalIdx(release_raw)), @lib.Call(_)] if release_raw ==
result_raw => Some(cleanup_raw.reinterpret_as_int())
_ => None
}
_ => None
}
}
_ => None
}
}
///|
fn run_hot_pipeline_raw_vacuum_body_f3561_load_retain_branch_if_cleanup_local(
body : Array[@lib.Instruction],
) -> Int32? {
let non_nops : Array[@lib.Instruction] = []
for instr in body {
match instr {
@lib.Nop => ()
_ => non_nops.push(instr)
}
}
if non_nops.length() != body.length() {
return run_hot_pipeline_raw_vacuum_body_f3561_load_retain_branch_if_cleanup_local(
non_nops,
)
}
if body.length() != 16 {
return None
}
let result_raw = match
(
body[0],
body[1],
body[2],
body[3],
body[4],
body[5],
body[6],
body[7],
body[8],
body[9],
body[10],
body[11],
body[12],
body[13],
body[14],
) {
(
@lib.LocalGet(_),
@lib.I32Load(@lib.MemArg(_, _, @lib.U64(36UL))),
@lib.LocalTee(@lib.LocalIdx(retained_raw)),
@lib.Call(_),
@lib.LocalGet(@lib.LocalIdx(next_retained_raw)),
@lib.LocalGet(_),
@lib.Call(_),
@lib.LocalSet(@lib.LocalIdx(built_raw)),
@lib.LocalGet(@lib.LocalIdx(release_retained_raw)),
@lib.Call(_),
@lib.LocalGet(_),
@lib.LocalGet(_),
@lib.LocalGet(@lib.LocalIdx(next_built_raw)),
@lib.Call(_),
@lib.LocalTee(@lib.LocalIdx(result_raw)),
) if next_retained_raw == retained_raw &&
release_retained_raw == retained_raw &&
next_built_raw == built_raw => result_raw
_ => return None
}
run_hot_pipeline_raw_vacuum_f3561_branch_if_cleanup_local(
body[15],
result_raw,
)
}
///|
fn run_hot_pipeline_raw_vacuum_body_has_f3561_terminal_release_return_continuation(
body : Array[@lib.Instruction],
start_idx : Int32,
cleanup_local : Int32,
) -> Bool {
let sig : Array[@lib.Instruction] = []
let mut idx = start_idx + 1
while idx < body.length() && sig.length() < 4 {
match body[idx] {
@lib.Nop => ()
instr => sig.push(instr)
}
idx = idx + 1
}
match sig {
[
@lib.LocalGet(@lib.LocalIdx(next_cleanup_raw)),
@lib.Call(_),
@lib.I32Const(@lib.I32(0)),
@lib.Return,
] => next_cleanup_raw.reinterpret_as_int() == cleanup_local
_ => false
}
}
///|
fn run_hot_pipeline_raw_vacuum_body_is_f3561_load_retain_prefix_before_terminal_release_preserve_candidate(
body : Array[@lib.Instruction],
parent_body : Array[@lib.Instruction],
start_idx : Int32,
) -> Bool {
match
run_hot_pipeline_raw_vacuum_body_f3561_load_retain_branch_if_cleanup_local(
body,
) {
Some(cleanup_local) =>
run_hot_pipeline_raw_vacuum_body_has_f3561_terminal_release_return_continuation(
parent_body, start_idx, cleanup_local,
)
None => false
}
}
///|
fn run_hot_pipeline_raw_vacuum_body_is_f364_four_field_tag_cascade_body(
body : Array[@lib.Instruction],
) -> Bool {
let non_nops : Array[@lib.Instruction] = []
for instr in body {
match instr {
@lib.Nop => ()
_ => non_nops.push(instr)
}
}
if non_nops.length() != 21 {
return false
}
let (receiver_raw, call_raw, local0_raw) = match
(non_nops[0], non_nops[1], non_nops[2], non_nops[3]) {
(
@lib.LocalGet(@lib.LocalIdx(receiver_raw)),
@lib.I32Const(@lib.I32(0)),
@lib.Call(@lib.FuncIdx(call_raw)),
@lib.LocalSet(@lib.LocalIdx(local0_raw)),
) => (receiver_raw, call_raw, local0_raw)
_ => return false
}
let local1_raw = match (non_nops[4], non_nops[5], non_nops[6], non_nops[7]) {
(
@lib.LocalGet(@lib.LocalIdx(next_receiver_raw)),
@lib.I32Const(@lib.I32(1)),
@lib.Call(@lib.FuncIdx(next_call_raw)),
@lib.LocalSet(@lib.LocalIdx(next_local_raw)),
) if next_receiver_raw == receiver_raw && next_call_raw == call_raw =>
next_local_raw
_ => return false
}
let local2_raw = match
(non_nops[8], non_nops[9], non_nops[10], non_nops[11]) {
(
@lib.LocalGet(@lib.LocalIdx(next_receiver_raw)),
@lib.I32Const(@lib.I32(2)),
@lib.Call(@lib.FuncIdx(next_call_raw)),
@lib.LocalSet(@lib.LocalIdx(next_local_raw)),
) if next_receiver_raw == receiver_raw && next_call_raw == call_raw =>
next_local_raw
_ => return false
}
let local3_raw = match
(non_nops[12], non_nops[13], non_nops[14], non_nops[15]) {
(
@lib.LocalGet(@lib.LocalIdx(next_receiver_raw)),
@lib.I32Const(@lib.I32(3)),
@lib.Call(@lib.FuncIdx(next_call_raw)),
@lib.LocalSet(@lib.LocalIdx(next_local_raw)),
) if next_receiver_raw == receiver_raw && next_call_raw == call_raw =>
next_local_raw
_ => return false
}
if local0_raw == local1_raw ||
local0_raw == local2_raw ||
local0_raw == local3_raw ||
local1_raw == local2_raw ||
local1_raw == local3_raw ||
local2_raw == local3_raw {
return false
}
match non_nops[20] {
@lib.If(@lib.VoidBlockType, _, _) => ()
_ => return false
}
match (non_nops[16], non_nops[17], non_nops[18], non_nops[19]) {
(
@lib.LocalGet(@lib.LocalIdx(tag_local_raw)),
@lib.I32Load(_),
@lib.I32Const(@lib.I32(20)),
@lib.I32Eq,
) => tag_local_raw == local0_raw
(
@lib.I32Const(@lib.I32(20)),
@lib.LocalGet(@lib.LocalIdx(tag_local_raw)),
@lib.I32Load(_),
@lib.I32Eq,
) => tag_local_raw == local0_raw
_ => false
}
}
///|
fn run_hot_pipeline_raw_vacuum_try_flatten_f364_four_field_singleton_child(
body : Array[@lib.Instruction],
module_ctx : @ir.HotModuleContext?,
) -> Array[@lib.Instruction]? {
if body.length() != 1 {
return None
}
let kept_body = match body[0] {
@lib.Block(@lib.VoidBlockType, @lib.Expr(kept_body)) => kept_body
_ => return None
}
if kept_body.length() != 1 {
return None
}
let extra_body = match kept_body[0] {
@lib.Block(@lib.VoidBlockType, @lib.Expr(extra_body)) => extra_body
_ => return None
}
if !run_hot_pipeline_raw_vacuum_body_is_f364_four_field_tag_cascade_body(
extra_body,
) {
return None
}
let rebased = run_hot_pipeline_raw_vacuum_rebase_outer_body(extra_body, 0)
let (cleaned, _) = run_hot_pipeline_raw_vacuum_preclean_instrs_owned(
rebased, module_ctx,
)
Some([
@lib.Instruction::block(@lib.BlockType::void_(), @lib.Expr::new(cleaned)),
])
}
///|
fn run_hot_pipeline_raw_vacuum_try_flatten_f364_four_field_child_before_continuation(
body : Array[@lib.Instruction],
module_ctx : @ir.HotModuleContext?,
) -> Array[@lib.Instruction]? {
if body.length() < 2 {
return None
}
let mut continuation_idx = 1
while continuation_idx < body.length() {
match body[continuation_idx] {
@lib.Nop => continuation_idx = continuation_idx + 1
_ => break
}
}
if continuation_idx + 1 != body.length() {
return None
}
match body[continuation_idx] {
@lib.Br(_) => ()
_ => return None
}
let kept_body = match body[0] {
@lib.Block(@lib.VoidBlockType, @lib.Expr(kept_body)) => kept_body
_ => return None
}
if kept_body.length() != 1 {
return None
}
let extra_body = match kept_body[0] {
@lib.Block(@lib.VoidBlockType, @lib.Expr(extra_body)) => extra_body
_ => return None
}
if !run_hot_pipeline_raw_vacuum_body_is_f364_four_field_tag_cascade_body(
extra_body,
) {
return None
}
let rebased = run_hot_pipeline_raw_vacuum_rebase_outer_body(extra_body, 0)
let (cleaned, _) = run_hot_pipeline_raw_vacuum_preclean_instrs_owned(
rebased, module_ctx,
)
Some([
@lib.Instruction::block(@lib.BlockType::void_(), @lib.Expr::new(cleaned)),
body[continuation_idx],
])
}
///|
fn run_hot_pipeline_raw_vacuum_body_is_f1738_loop_head_local_copy_child(
body : Array[@lib.Instruction],
) -> Bool {
if body.length() != 2 {
return false
}
match (body[0], body[1]) {
(
@lib.LocalGet(@lib.LocalIdx(source_raw)),
@lib.LocalSet(@lib.LocalIdx(target_raw)),
) => source_raw != target_raw
_ => false
}
}
///|
fn run_hot_pipeline_raw_vacuum_body_has_f1738_parent_br_continuation(
body : Array[@lib.Instruction],
start_idx : Int32,
) -> Bool {
let mut idx = start_idx + 1
while idx < body.length() {
match body[idx] {
@lib.Nop => idx = idx + 1
@lib.Br(label) =>
return run_hot_pipeline_raw_vacuum_label_depth(label) >= 1
_ => return false
}
}
false
}
///|
fn run_hot_pipeline_raw_vacuum_body_is_f1738_loop_head_two_tag_dispatch(
body : Array[@lib.Instruction],
) -> Bool {
if body.length() < 9 {
return false
}
let copy_body = match body[0] {
@lib.Block(@lib.VoidBlockType, @lib.Expr(copy_body)) => copy_body
_ => return false
}
if !run_hot_pipeline_raw_vacuum_body_is_f1738_loop_head_local_copy_child(
copy_body,
) {
return false
}
let mut saw_tag1_dispatch = false
let mut tag1_if_idx = -1
for idx = 1; idx + 3 < body.length(); idx = idx + 1 {
match (body[idx], body[idx + 1], body[idx + 2], body[idx + 3]) {
(
@lib.Call(_),
@lib.I32Const(@lib.I32(1)),
@lib.I32Eq,
@lib.If(_, first_then, None),
) if first_then.length() > 0 &&
run_hot_pipeline_raw_vacuum_instrs_contain_unreachable(first_then) => {
saw_tag1_dispatch = true
tag1_if_idx = idx + 3
break
}
_ => ()
}
}
if !saw_tag1_dispatch {
return false
}
for idx = tag1_if_idx + 1; idx + 3 < body.length(); idx = idx + 1 {
match (body[idx], body[idx + 1], body[idx + 2], body[idx + 3]) {
(
@lib.Call(_),
@lib.I32Const(@lib.I32(2)),
@lib.I32Eq,
@lib.If(_, second_then, Some(second_else)),
) if second_then.length() > 0 && second_else.length() > 0 => return true
_ => ()
}
}
false
}
///|
fn run_hot_pipeline_raw_vacuum_try_flatten_f1738_loop_head_local_copy_child(
body : Array[@lib.Instruction],
parent_body : Array[@lib.Instruction],
start_idx : Int32,
) -> Array[@lib.Instruction]? {
if !run_hot_pipeline_raw_vacuum_body_is_f1738_loop_head_two_tag_dispatch(body) ||
!run_hot_pipeline_raw_vacuum_body_has_f1738_parent_br_continuation(
parent_body, start_idx,
) {
return None
}
let copy_body = match body[0] {
@lib.Block(@lib.VoidBlockType, @lib.Expr(copy_body)) => copy_body
_ => return None
}
let flattened : Array[@lib.Instruction] = []
for instr in copy_body {
flattened.push(instr)
}
for idx = 1; idx < body.length(); idx = idx + 1 {
flattened.push(body[idx])
}
Some(flattened)
}
///|
fn run_hot_pipeline_raw_vacuum_body_is_f1743_nested_tag2_dispatch(
body : Array[@lib.Instruction],
) -> Bool {
if body.length() < 2 {
return false
}
let dispatch_body = match body[0] {
@lib.Block(@lib.VoidBlockType, @lib.Expr(dispatch_body)) => dispatch_body
_ => return false
}
if dispatch_body.length() < 7 {
return false
}
match
(
dispatch_body[0],
dispatch_body[1],
dispatch_body[2],
dispatch_body[3],
dispatch_body[4],
dispatch_body[5],
dispatch_body[dispatch_body.length() - 1],
) {
(
@lib.LocalGet(@lib.LocalIdx(source_raw)),
@lib.LocalTee(@lib.LocalIdx(tee_raw)),
@lib.I32Load(_),
@lib.I32Const(@lib.I32(2)),
@lib.I32Eq,
@lib.If(@lib.VoidBlockType, then_body, Some(else_body)),
@lib.Br(label),
) =>
source_raw != tee_raw &&
then_body.length() > 0 &&
else_body.length() > 0 &&
run_hot_pipeline_raw_vacuum_body_has_top_level_local_set(then_body) &&
run_hot_pipeline_raw_vacuum_label_depth(label) >= 1
_ => false
}
}
///|
fn run_hot_pipeline_raw_vacuum_body_has_f1743_loop_update_continuation(
body : Array[@lib.Instruction],
copied_target_raw : UInt32,
) -> Bool {
if body.length() < 19 ||
!run_hot_pipeline_raw_vacuum_body_is_f1743_nested_tag2_dispatch(body) {
return false
}
let update_raw = match
(body[1], body[2], body[3], body[4], body[5], body[6], body[7]) {
(
@lib.LocalGet(@lib.LocalIdx(loop_count_raw)),
@lib.I32Const(@lib.I32(1)),
@lib.I32Add,
@lib.LocalTee(@lib.LocalIdx(update_raw)),
@lib.I32Const(@lib.I32(12)),
@lib.I32GeS,
@lib.If(@lib.VoidBlockType, then_body, None),
) if loop_count_raw == copied_target_raw && then_body.length() > 0 =>
update_raw
_ => return false
}
let mut saw_eqz_return_guard = false
for idx = 8; idx + 3 < body.length(); idx = idx + 1 {
match (body[idx], body[idx + 1], body[idx + 2], body[idx + 3]) {
(
@lib.LocalGet(_),
@lib.Call(_),
@lib.I32Eqz,
@lib.If(@lib.VoidBlockType, guard_then, None),
) if guard_then.length() > 0 => {
saw_eqz_return_guard = true
break
}
_ => ()
}
}
if !saw_eqz_return_guard {
return false
}
let tail_idx = body.length() - 11
match
(
body[tail_idx],
body[tail_idx + 1],
body[tail_idx + 2],
body[tail_idx + 3],
body[tail_idx + 4],
body[tail_idx + 5],
body[tail_idx + 6],
body[tail_idx + 7],
body[tail_idx + 8],
body[tail_idx + 9],
body[tail_idx + 10],
) {
(
@lib.LocalGet(@lib.LocalIdx(call_arg_raw)),
@lib.I32Const(@lib.I32(0)),
@lib.Call(_),
@lib.LocalSet(@lib.LocalIdx(result_raw)),
@lib.LocalGet(@lib.LocalIdx(release_raw)),
@lib.Call(_),
@lib.LocalGet(@lib.LocalIdx(next_result_raw)),
@lib.LocalSet(_),
@lib.LocalGet(@lib.LocalIdx(next_update_raw)),
@lib.LocalSet(_),
@lib.Br(label),
) =>
call_arg_raw == release_raw &&
result_raw == next_result_raw &&
next_update_raw == update_raw &&
run_hot_pipeline_raw_vacuum_label_depth(label) >= 1
_ => false
}
}
///|
fn run_hot_pipeline_raw_vacuum_try_flatten_f1743_loop_head_copy_before_nested_dispatch(
body : Array[@lib.Instruction],
parent_body : Array[@lib.Instruction],
start_idx : Int32,
) -> Array[@lib.Instruction]? {
if body.length() < 2 ||
!run_hot_pipeline_raw_vacuum_body_has_f1738_parent_br_continuation(
parent_body, start_idx,
) {
return None
}
let (copy_body, copied_target_raw) = match body[0] {
@lib.Block(@lib.VoidBlockType, @lib.Expr(copy_body)) => {
if copy_body.length() != 2 {
return None
}
match (copy_body[0], copy_body[1]) {
(
@lib.LocalGet(@lib.LocalIdx(source_raw)),
@lib.LocalSet(@lib.LocalIdx(target_raw)),
) if source_raw != target_raw => (copy_body, target_raw)
_ => return None
}
}
_ => return None
}
let continuation_body = match body[1] {
@lib.Block(@lib.VoidBlockType, @lib.Expr(continuation_body)) =>
continuation_body
_ => return None
}
if !run_hot_pipeline_raw_vacuum_body_has_f1743_loop_update_continuation(
continuation_body, copied_target_raw,
) {
return None
}
let flattened : Array[@lib.Instruction] = []
for instr in copy_body {
flattened.push(instr)
}
for idx = 1; idx < body.length(); idx = idx + 1 {
flattened.push(body[idx])
}
Some(flattened)
}
///|
fn run_hot_pipeline_raw_vacuum_f3579_then_releases_and_returns(
body : Array[@lib.Instruction],
guard_raw : UInt32,
copied_raw : UInt32,
pair_raw : UInt32,
env_raw : UInt32,
) -> Bool {
match body {
[
@lib.LocalGet(@lib.LocalIdx(next_guard_raw)),
@lib.Call(_),
@lib.LocalGet(@lib.LocalIdx(next_copied_raw)),
@lib.Call(_),
@lib.LocalGet(@lib.LocalIdx(next_pair_raw)),
@lib.Call(_),
@lib.LocalGet(@lib.LocalIdx(next_env_raw)),
@lib.Call(_),
@lib.I32Const(@lib.I32(0)),
@lib.Return,
] =>
next_guard_raw == guard_raw &&
next_copied_raw == copied_raw &&
next_pair_raw == pair_raw &&
next_env_raw == env_raw
_ => false
}
}
///|
fn run_hot_pipeline_raw_vacuum_body_is_f3579_loop_head_local_copy_guard_wrapper(
body : Array[@lib.Instruction],
) -> Bool {
if body.length() < 12 {
return false
}
let copied_raw = match body[0] {
@lib.Block(@lib.VoidBlockType, @lib.Expr(copy_body)) => {
if !run_hot_pipeline_raw_vacuum_body_is_f1738_loop_head_local_copy_child(
copy_body,
) {
return false
}
match copy_body[1] {
@lib.LocalSet(@lib.LocalIdx(target_raw)) => target_raw
_ => return false
}
}
_ => return false
}
match
(
body[1],
body[2],
body[3],
body[4],
body[5],
body[6],
body[7],
body[8],
body[9],
body[10],
body[11],
) {
(
@lib.LocalGet(@lib.LocalIdx(env_source_raw)),
@lib.LocalSet(@lib.LocalIdx(env_raw)),
@lib.LocalGet(_),
@lib.LocalTee(@lib.LocalIdx(pair_raw)),
@lib.LocalGet(_),
@lib.Call(_),
@lib.LocalTee(@lib.LocalIdx(guard_raw)),
@lib.Call(_),
@lib.I32Const(@lib.I32(1)),
@lib.I32Ne,
@lib.If(@lib.VoidBlockType, then_body, None),
) if env_source_raw != env_raw =>
run_hot_pipeline_raw_vacuum_f3579_then_releases_and_returns(
then_body, guard_raw, copied_raw, pair_raw, env_raw,
)
_ => false
}
}
///|
fn run_hot_pipeline_raw_vacuum_try_flatten_f3579_loop_head_local_copy_guard_wrapper(
body : Array[@lib.Instruction],
) -> Array[@lib.Instruction]? {
if !run_hot_pipeline_raw_vacuum_body_is_f3579_loop_head_local_copy_guard_wrapper(
body,
) {
return None
}
let copy_body = match body[0] {
@lib.Block(@lib.VoidBlockType, @lib.Expr(copy_body)) => copy_body
_ => return None
}
let flattened : Array[@lib.Instruction] = []
for instr in copy_body {
flattened.push(instr)
}
for idx = 1; idx < body.length(); idx = idx + 1 {
flattened.push(body[idx])
}
Some(flattened)
}
///|
fn run_hot_pipeline_raw_vacuum_body_has_f3841_deep_release_pair(
body : Array[@lib.Instruction],
active_raw : UInt32,
) -> Bool {
for idx = 0; idx + 1 < body.length(); idx = idx + 1 {
match (body[idx], body[idx + 1]) {
(@lib.LocalGet(@lib.LocalIdx(raw)), @lib.Call(_)) if raw == active_raw =>
return true
_ => ()
}
}
for instr in body {
match instr {
@lib.Block(_, @lib.Expr(child_body))
| @lib.Loop(_, @lib.Expr(child_body)) =>
if run_hot_pipeline_raw_vacuum_body_has_f3841_deep_release_pair(
child_body, active_raw,
) {
return true
}
@lib.If(_, then_body, else_body) => {
if run_hot_pipeline_raw_vacuum_body_has_f3841_deep_release_pair(
then_body, active_raw,
) {
return true
}
match else_body {
Some(child_body) =>
if run_hot_pipeline_raw_vacuum_body_has_f3841_deep_release_pair(
child_body, active_raw,
) {
return true
}
None => ()
}
}
@lib.TryTable(_, _, @lib.Expr(child_body)) =>
if run_hot_pipeline_raw_vacuum_body_has_f3841_deep_release_pair(
child_body, active_raw,
) {
return true
}
_ => ()
}
}
false
}
///|
fn run_hot_pipeline_raw_vacuum_body_f3841_selected_local(
body : Array[@lib.Instruction],
) -> Int32? {
let non_nops : Array[@lib.Instruction] = []
for instr in body {
match instr {
@lib.Nop => ()
_ => non_nops.push(instr)
}
}
if non_nops.length() != body.length() {
return run_hot_pipeline_raw_vacuum_body_f3841_selected_local(non_nops)
}
if body.length() != 15 {
return None
}
let (retained_raw, value_raw, active_raw) = match
(
body[0],
body[1],
body[2],
body[3],
body[4],
body[5],
body[6],
body[7],
body[8],
body[9],
body[10],
body[11],
body[12],
body[13],
) {
(
@lib.LocalGet(@lib.LocalIdx(source_raw)),
@lib.I32Load(@lib.MemArg(_, _, @lib.U64(8UL))),
@lib.LocalTee(@lib.LocalIdx(retained_raw)),
@lib.Call(_),
@lib.LocalGet(@lib.LocalIdx(next_retained_raw)),
@lib.LocalGet(_),
@lib.Call(_),
@lib.LocalSet(@lib.LocalIdx(value_raw)),
@lib.LocalGet(@lib.LocalIdx(release_retained_raw)),
@lib.Call(_),
@lib.LocalGet(@lib.LocalIdx(next_value_raw)),
@lib.LocalTee(@lib.LocalIdx(active_raw)),
@lib.Call(_),
@lib.LocalTee(_),
) if source_raw != retained_raw &&
next_retained_raw == retained_raw &&
release_retained_raw == retained_raw &&
next_value_raw == value_raw &&
value_raw != active_raw => (retained_raw, value_raw, active_raw)
_ => return None
}
ignore(retained_raw)
ignore(value_raw)
match body[14] {
@lib.If(@lib.VoidBlockType, then_body, Some(else_body)) => {
if !run_hot_pipeline_raw_vacuum_body_has_f3841_deep_release_pair(
then_body, active_raw,
) ||
!run_hot_pipeline_raw_vacuum_body_has_f3841_deep_release_pair(
else_body, active_raw,
) {
return None
}
match run_hot_pipeline_raw_vacuum_body_ends_local_set(else_body) {
Some(selected_raw) =>
if run_hot_pipeline_raw_vacuum_body_sets_local(
then_body,
selected_raw.reinterpret_as_uint(),
) {
Some(selected_raw)
} else {
None
}
None => None
}
}
_ => None
}
}
///|
fn run_hot_pipeline_raw_vacuum_has_f3841_selected_local_set_br_continuation(
parent_body : Array[@lib.Instruction],
start_idx : Int32,
selected_raw : Int32,
) -> Bool {
let sig : Array[@lib.Instruction] = []
let mut idx = start_idx + 1
while idx < parent_body.length() && sig.length() < 3 {
match parent_body[idx] {
@lib.Nop => ()
instr => sig.push(instr)
}
idx = idx + 1
}
match sig {
[
@lib.LocalGet(@lib.LocalIdx(next_selected_raw)),
@lib.LocalSet(_),
@lib.Br(label),
] =>
next_selected_raw.reinterpret_as_int() == selected_raw &&
run_hot_pipeline_raw_vacuum_label_depth(label) >= 1
_ => false
}
}
///|
fn run_hot_pipeline_raw_vacuum_try_flatten_f3841_tag_dispatch_singleton_child_before_local_set_branch(
body : Array[@lib.Instruction],
parent_body : Array[@lib.Instruction],
start_idx : Int32,
) -> Array[@lib.Instruction]? {
if body.length() != 1 {
return None
}
let child_body = match body[0] {
@lib.Block(@lib.VoidBlockType, @lib.Expr(child_body)) => child_body
_ => return None
}
let selected_raw = match
run_hot_pipeline_raw_vacuum_body_f3841_selected_local(child_body) {
Some(local_raw) => local_raw
None => return None
}
if !run_hot_pipeline_raw_vacuum_has_f3841_selected_local_set_br_continuation(
parent_body, start_idx, selected_raw,
) ||
run_hot_pipeline_raw_vacuum_body_targets_owner(child_body, 0) {
return None
}
Some(run_hot_pipeline_raw_vacuum_rebase_outer_body(child_body, 0))
}
///|
fn run_hot_pipeline_raw_vacuum_strip_top_level_nops(
body : Array[@lib.Instruction],
) -> Array[@lib.Instruction] {
let stripped : Array[@lib.Instruction] = []
for instr in body {
match instr {
@lib.Nop => ()
_ => stripped.push(instr)
}
}
stripped
}
///|
fn run_hot_pipeline_raw_vacuum_f3998_prefix_result_local(
body : Array[@lib.Instruction],
) -> UInt32? {
let non_nops = run_hot_pipeline_raw_vacuum_strip_top_level_nops(body)
if non_nops.length() != 9 {
return None
}
match
(
non_nops[0],
non_nops[1],
non_nops[2],
non_nops[3],
non_nops[4],
non_nops[5],
non_nops[6],
non_nops[7],
non_nops[8],
) {
(
@lib.LocalGet(@lib.LocalIdx(source_raw)),
@lib.I32Const(@lib.I32(0)),
@lib.Call(_),
@lib.LocalTee(@lib.LocalIdx(retained_raw)),
@lib.LocalGet(@lib.LocalIdx(arg_raw)),
@lib.Call(_),
@lib.LocalSet(@lib.LocalIdx(result_raw)),
@lib.LocalGet(@lib.LocalIdx(release_raw)),
@lib.Call(_),
) if source_raw != retained_raw &&
retained_raw == release_raw &&
result_raw != retained_raw &&
arg_raw != result_raw => return Some(result_raw)
_ => ()
}
match
(
non_nops[0],
non_nops[1],
non_nops[2],
non_nops[3],
non_nops[4],
non_nops[5],
non_nops[6],
non_nops[7],
non_nops[8],
) {
(
@lib.LocalGet(@lib.LocalIdx(source_raw)),
@lib.LocalGet(@lib.LocalIdx(index_raw)),
@lib.Call(_),
@lib.LocalTee(@lib.LocalIdx(retained_raw)),
@lib.LocalGet(@lib.LocalIdx(arg_raw)),
@lib.Call(_),
@lib.LocalSet(@lib.LocalIdx(result_raw)),
@lib.LocalGet(@lib.LocalIdx(release_raw)),
@lib.Call(_),
) if source_raw != retained_raw &&
index_raw != retained_raw &&
retained_raw == release_raw &&
result_raw != retained_raw &&
arg_raw != result_raw => Some(result_raw)
_ => None
}
}
///|
fn run_hot_pipeline_raw_vacuum_instr_is_f3998_tag2_dispatch_if(
instr : @lib.Instruction,
active_raw : UInt32,
) -> Bool {
match instr {
@lib.If(@lib.VoidBlockType, then_body, Some(else_body)) => {
if then_body.length() < 6 || else_body.length() != 3 {
return false
}
match
(
then_body[0],
then_body[1],
then_body[2],
then_body[3],
then_body[4],
then_body[5],
) {
(
@lib.LocalGet(@lib.LocalIdx(first_active_raw)),
@lib.I32Load(@lib.MemArg(_, _, @lib.U64(4UL))),
@lib.LocalSet(_),
@lib.LocalGet(@lib.LocalIdx(second_active_raw)),
@lib.I32Load(@lib.MemArg(_, _, @lib.U64(8UL))),
@lib.LocalTee(_),
) if first_active_raw == active_raw && second_active_raw == active_raw =>
()
_ => return false
}
match (else_body[0], else_body[1], else_body[2]) {
(
@lib.LocalGet(@lib.LocalIdx(else_active_raw)),
@lib.LocalSet(_),
@lib.Br(label),
) =>
else_active_raw == active_raw &&
run_hot_pipeline_raw_vacuum_label_depth(label) >= 1
_ => false
}
}
_ => false
}
}
///|
fn run_hot_pipeline_raw_vacuum_body_is_f3998_tag2_prefix_child(
body : Array[@lib.Instruction],
) -> Bool {
let non_nops = run_hot_pipeline_raw_vacuum_strip_top_level_nops(body)
if non_nops.length() != 7 {
return false
}
let result_raw = match non_nops[0] {
@lib.Block(@lib.VoidBlockType, @lib.Expr(prefix_body)) =>
match run_hot_pipeline_raw_vacuum_f3998_prefix_result_local(prefix_body) {
Some(raw) => raw
None => return false
}
_ => return false
}
let active_raw = match
(non_nops[1], non_nops[2], non_nops[3], non_nops[4], non_nops[5]) {
(
@lib.LocalGet(@lib.LocalIdx(read_raw)),
@lib.LocalTee(@lib.LocalIdx(active_raw)),
@lib.I32Load(@lib.MemArg(_, _, @lib.U64(0UL))),
@lib.I32Const(@lib.I32(2)),
@lib.I32Eq,
) if read_raw == result_raw && active_raw != result_raw => active_raw
_ => return false
}
run_hot_pipeline_raw_vacuum_instr_is_f3998_tag2_dispatch_if(
non_nops[6],
active_raw,
)
}
///|
fn run_hot_pipeline_raw_vacuum_has_f3998_release_load_loop_continuation(
parent_body : Array[@lib.Instruction],
start_idx : Int32,
) -> Bool {
let sig : Array[@lib.Instruction] = []
let mut idx = start_idx + 1
while idx < parent_body.length() && sig.length() < 8 {
match parent_body[idx] {
@lib.Nop => ()
instr => sig.push(instr)
}
idx = idx + 1
}
if sig.length() < 8 {
return false
}
match (sig[0], sig[1], sig[2], sig[3], sig[4], sig[5], sig[6], sig[7]) {
(
@lib.LocalGet(@lib.LocalIdx(release_raw)),
@lib.Call(_),
@lib.LocalGet(@lib.LocalIdx(collection_raw)),
@lib.I32Load(@lib.MemArg(_, _, @lib.U64(0UL))),
@lib.LocalSet(@lib.LocalIdx(count_raw)),
@lib.I32Const(@lib.I32(0)),
@lib.LocalSet(@lib.LocalIdx(index_raw)),
@lib.Loop(@lib.VoidBlockType, _),
) =>
release_raw != collection_raw &&
count_raw != index_raw &&
collection_raw != index_raw
_ => false
}
}
///|
fn run_hot_pipeline_raw_vacuum_try_flatten_f3998_tag2_prefix_child_before_loop_continuation(
body : Array[@lib.Instruction],
parent_body : Array[@lib.Instruction],
start_idx : Int32,
) -> Array[@lib.Instruction]? {
if !run_hot_pipeline_raw_vacuum_has_f3998_release_load_loop_continuation(
parent_body, start_idx,
) {
return None
}
let non_nops = run_hot_pipeline_raw_vacuum_strip_top_level_nops(body)
if non_nops.length() != 1 {
return None
}
match non_nops[0] {
@lib.Block(@lib.VoidBlockType, @lib.Expr(child_body)) => {
let child_non_nops = run_hot_pipeline_raw_vacuum_strip_top_level_nops(
child_body,
)
if !run_hot_pipeline_raw_vacuum_body_is_f3998_tag2_prefix_child(
child_non_nops,
) {
return None
}
Some(run_hot_pipeline_raw_vacuum_rebase_outer_body(child_non_nops, 0))
}
_ => None
}
}
///|
fn run_hot_pipeline_raw_vacuum_f4070_prefix_result_local(
body : Array[@lib.Instruction],
) -> UInt32? {
let non_nops = run_hot_pipeline_raw_vacuum_strip_top_level_nops(body)
if non_nops.length() != 5 {
return None
}
match (non_nops[0], non_nops[1], non_nops[2], non_nops[3], non_nops[4]) {
(
@lib.LocalGet(@lib.LocalIdx(source_raw)),
@lib.Call(_),
@lib.LocalSet(@lib.LocalIdx(result_raw)),
@lib.LocalGet(@lib.LocalIdx(release_raw)),
@lib.Call(_),
) if source_raw == release_raw && source_raw != result_raw =>
Some(result_raw)
_ => None
}
}
///|
fn run_hot_pipeline_raw_vacuum_body_has_f4070_dispatch_payload_load(
body : Array[@lib.Instruction],
dispatch_raw : UInt32,
) -> Bool {
let non_nops = run_hot_pipeline_raw_vacuum_strip_top_level_nops(body)
if non_nops.length() < 4 {
return false
}
for idx = 0; idx + 3 < non_nops.length(); idx = idx + 1 {
match
(non_nops[idx], non_nops[idx + 1], non_nops[idx + 2], non_nops[idx + 3]) {
(
@lib.LocalGet(@lib.LocalIdx(read_raw)),
@lib.LocalTee(_),
@lib.I32Load(_),
@lib.LocalSet(_),
) if read_raw == dispatch_raw => return true
_ => ()
}
}
false
}
///|
fn run_hot_pipeline_raw_vacuum_body_is_f4070_one_arg_prefix_parent(
body : Array[@lib.Instruction],
) -> Bool {
let non_nops = run_hot_pipeline_raw_vacuum_strip_top_level_nops(body)
if non_nops.length() < 22 {
return false
}
let produced_raw = match non_nops[0] {
@lib.Block(@lib.VoidBlockType, @lib.Expr(prefix_body)) =>
match run_hot_pipeline_raw_vacuum_f4070_prefix_result_local(prefix_body) {
Some(raw) => raw
None => return false
}
_ => return false
}
match
(
non_nops[1],
non_nops[2],
non_nops[3],
non_nops[4],
non_nops[5],
non_nops[6],
non_nops[7],
non_nops[8],
non_nops[9],
non_nops[10],
non_nops[11],
non_nops[12],
non_nops[13],
non_nops[14],
non_nops[15],
non_nops[16],
non_nops[17],
non_nops[18],
non_nops[19],
non_nops[20],
) {
(
@lib.LocalGet(_),
@lib.LocalGet(@lib.LocalIdx(produced_arg_raw)),
@lib.LocalGet(@lib.LocalIdx(second_arg_raw)),
@lib.LocalGet(_),
@lib.LocalGet(_),
@lib.LocalGet(_),
@lib.LocalGet(@lib.LocalIdx(third_released_arg_raw)),
@lib.Call(_),
@lib.LocalSet(@lib.LocalIdx(result_raw)),
@lib.LocalGet(@lib.LocalIdx(first_release_raw)),
@lib.Call(_),
@lib.LocalGet(@lib.LocalIdx(second_release_raw)),
@lib.Call(_),
@lib.LocalGet(@lib.LocalIdx(third_release_raw)),
@lib.Call(_),
@lib.LocalGet(@lib.LocalIdx(result_read_raw)),
@lib.LocalTee(@lib.LocalIdx(dispatch_raw)),
@lib.Call(_),
@lib.I32Const(@lib.I32(1)),
@lib.I32Eq,
) if produced_arg_raw == produced_raw &&
first_release_raw == produced_raw &&
second_release_raw == second_arg_raw &&
third_release_raw == third_released_arg_raw &&
result_read_raw == result_raw &&
result_raw != produced_raw &&
dispatch_raw != result_raw =>
match non_nops[21] {
@lib.If(@lib.VoidBlockType, then_body, Some(else_body)) =>
run_hot_pipeline_raw_vacuum_body_has_f4070_dispatch_payload_load(
then_body, dispatch_raw,
) &&
run_hot_pipeline_raw_vacuum_body_has_f4070_dispatch_payload_load(
else_body, dispatch_raw,
)
_ => false
}
_ => false
}
}
///|
fn run_hot_pipeline_raw_vacuum_try_flatten_f4070_one_arg_prefix_parent_before_result_dispatch(
body : Array[@lib.Instruction],
) -> Array[@lib.Instruction]? {
let non_nops = run_hot_pipeline_raw_vacuum_strip_top_level_nops(body)
if !run_hot_pipeline_raw_vacuum_body_is_f4070_one_arg_prefix_parent(non_nops) ||
run_hot_pipeline_raw_vacuum_body_targets_owner(non_nops, 0) {
return None
}
Some(run_hot_pipeline_raw_vacuum_rebase_outer_body(non_nops, 0))
}
///|
fn run_hot_pipeline_raw_vacuum_body_f4071_has_payload_load(
body : Array[@lib.Instruction],
result_raw : UInt32,
) -> Bool {
let non_nops = run_hot_pipeline_raw_vacuum_strip_top_level_nops(body)
if non_nops.length() < 3 {
return false
}
for idx = 0; idx + 2 < non_nops.length(); idx = idx + 1 {
match (non_nops[idx], non_nops[idx + 1], non_nops[idx + 2]) {
(
@lib.LocalGet(@lib.LocalIdx(next_result_raw)),
@lib.LocalTee(_),
@lib.I32Load(_),
) if next_result_raw == result_raw => return true
_ => ()
}
}
false
}
///|
fn run_hot_pipeline_raw_vacuum_body_f4071_else_ends_selected_local_outer_branch(
body : Array[@lib.Instruction],
) -> Bool {
let non_nops = run_hot_pipeline_raw_vacuum_strip_top_level_nops(body)
if non_nops.length() < 2 {
return false
}
match (non_nops[non_nops.length() - 2], non_nops[non_nops.length() - 1]) {
(@lib.LocalSet(_), @lib.Br(label)) =>
run_hot_pipeline_raw_vacuum_label_depth(label) >= 2
_ => false
}
}
///|
fn run_hot_pipeline_raw_vacuum_body_has_f4071_selected_local_branch_continuation(
parent_body : Array[@lib.Instruction],
start_idx : Int32,
selected_raw : Int32,
) -> Bool {
let sig : Array[@lib.Instruction] = []
let mut idx = start_idx + 1
while idx < parent_body.length() && sig.length() < 3 {
match parent_body[idx] {
@lib.Nop => ()
instr => sig.push(instr)
}
idx = idx + 1
}
match sig {
[
@lib.LocalGet(@lib.LocalIdx(next_selected_raw)),
@lib.LocalSet(_),
@lib.Br(label),
] =>
next_selected_raw.reinterpret_as_int() == selected_raw &&
run_hot_pipeline_raw_vacuum_label_depth(label) >= 1
_ => false
}
}
///|
fn run_hot_pipeline_raw_vacuum_body_is_f4071_load_retain_tag_dispatch_before_selected_local_branch_preserve_candidate(
body : Array[@lib.Instruction],
parent_body : Array[@lib.Instruction],
start_idx : Int32,
) -> Bool {
let non_nops = run_hot_pipeline_raw_vacuum_strip_top_level_nops(body)
if non_nops.length() != 12 ||
run_hot_pipeline_raw_vacuum_instrs_contain_unreachable(non_nops) {
return false
}
let (source_raw, retained_raw, result_raw) = match
(
non_nops[0],
non_nops[1],
non_nops[2],
non_nops[3],
non_nops[4],
non_nops[5],
non_nops[6],
non_nops[7],
non_nops[8],
non_nops[9],
non_nops[10],
) {
(
@lib.LocalGet(@lib.LocalIdx(source_raw)),
@lib.I32Load(@lib.MemArg(_, _, @lib.U64(8UL))),
@lib.LocalTee(@lib.LocalIdx(retained_raw)),
@lib.Call(_),
@lib.LocalGet(_),
@lib.LocalGet(@lib.LocalIdx(call_source_raw)),
@lib.Call(_),
@lib.LocalTee(@lib.LocalIdx(result_raw)),
@lib.Call(_),
@lib.I32Const(@lib.I32(1)),
@lib.I32Eq,
) if source_raw == call_source_raw &&
source_raw != retained_raw &&
result_raw != source_raw &&
result_raw != retained_raw => (source_raw, retained_raw, result_raw)
_ => return false
}
ignore(source_raw)
let selected_raw = match non_nops[11] {
@lib.If(@lib.VoidBlockType, then_body, Some(else_body)) => {
if !run_hot_pipeline_raw_vacuum_body_has_local_release_pair(
else_body, retained_raw,
) ||
!run_hot_pipeline_raw_vacuum_body_f4071_has_payload_load(
then_body, result_raw,
) ||
!run_hot_pipeline_raw_vacuum_body_f4071_has_payload_load(
else_body, result_raw,
) ||
!run_hot_pipeline_raw_vacuum_body_f4071_else_ends_selected_local_outer_branch(
else_body,
) {
return false
}
match run_hot_pipeline_raw_vacuum_body_ends_local_set(then_body) {
Some(selected_raw) => selected_raw
None => return false
}
}
_ => return false
}
run_hot_pipeline_raw_vacuum_body_has_f4071_selected_local_branch_continuation(
parent_body, start_idx, selected_raw,
)
}
///|
fn run_hot_pipeline_raw_vacuum_body_has_f4073_slot_store_continuation(
parent_body : Array[@lib.Instruction],
start_idx : Int32,
slot_raw : UInt32,
selected_raw : Int32,
) -> Bool {
let sig : Array[@lib.Instruction] = []
let mut idx = start_idx + 1
while idx < parent_body.length() && sig.length() < 6 {
match parent_body[idx] {
@lib.Nop => ()
instr => sig.push(instr)
}
idx = idx + 1
}
match sig {
[
@lib.LocalGet(@lib.LocalIdx(load_slot_raw)),
@lib.I32Load(_),
@lib.Call(_),
@lib.LocalGet(@lib.LocalIdx(store_slot_raw)),
@lib.LocalGet(@lib.LocalIdx(stored_raw)),
@lib.I32Store(_),
] =>
load_slot_raw == slot_raw &&
store_slot_raw == slot_raw &&
stored_raw.reinterpret_as_int() == selected_raw
_ => false
}
}
///|
fn run_hot_pipeline_raw_vacuum_body_is_f4073_call_result_tag_dispatch_before_slot_store_preserve_candidate(
body : Array[@lib.Instruction],
parent_body : Array[@lib.Instruction],
start_idx : Int32,
) -> Bool {
let non_nops = run_hot_pipeline_raw_vacuum_strip_top_level_nops(body)
if non_nops.length() != 26 ||
run_hot_pipeline_raw_vacuum_instrs_contain_unreachable(non_nops) {
return false
}
let slot_raw = match non_nops[0] {
@lib.LocalGet(@lib.LocalIdx(raw)) => raw
_ => return false
}
match non_nops[1] {
@lib.I32Load(@lib.MemArg(_, _, @lib.U64(0UL))) => ()
_ => return false
}
let retained_raw = match non_nops[2] {
@lib.LocalTee(@lib.LocalIdx(raw)) => raw
_ => return false
}
match non_nops[3] {
@lib.Call(_) => ()
_ => return false
}
let active_raw = match non_nops[4] {
@lib.LocalGet(@lib.LocalIdx(raw)) => raw
_ => return false
}
match non_nops[5] {
@lib.Call(_) => ()
_ => return false
}
match (non_nops[7], non_nops[8]) {
(
@lib.LocalGet(@lib.LocalIdx(next_active_raw)),
@lib.LocalGet(@lib.LocalIdx(next_retained_raw)),
) if next_active_raw == active_raw && next_retained_raw == retained_raw =>
()
_ => return false
}
for idx = 6; idx <= 13; idx = idx + 1 {
match non_nops[idx] {
@lib.LocalGet(_) => ()
_ => return false
}
}
match non_nops[14] {
@lib.Call(_) => ()
_ => return false
}
let result_raw = match non_nops[15] {
@lib.LocalSet(@lib.LocalIdx(raw)) => raw
_ => return false
}
match (non_nops[16], non_nops[17], non_nops[18], non_nops[19]) {
(
@lib.LocalGet(@lib.LocalIdx(release_active_raw)),
@lib.Call(_),
@lib.LocalGet(@lib.LocalIdx(release_retained_raw)),
@lib.Call(_),
) if release_active_raw == active_raw &&
release_retained_raw == retained_raw => ()
_ => return false
}
let tag_raw = match
(non_nops[20], non_nops[21], non_nops[22], non_nops[23], non_nops[24]) {
(
@lib.LocalGet(@lib.LocalIdx(result_read_raw)),
@lib.LocalTee(@lib.LocalIdx(tag_raw)),
@lib.Call(_),
@lib.I32Const(@lib.I32(1)),
@lib.I32Eq,
) if result_read_raw == result_raw => tag_raw
_ => return false
}
let selected_raw = match non_nops[25] {
@lib.If(@lib.VoidBlockType, then_body, Some(else_body)) => {
if !run_hot_pipeline_raw_vacuum_body_f4071_has_payload_load(
then_body, tag_raw,
) ||
!run_hot_pipeline_raw_vacuum_body_f4071_has_payload_load(
else_body, tag_raw,
) ||
!run_hot_pipeline_raw_vacuum_body_f4071_else_ends_selected_local_outer_branch(
else_body,
) {
return false
}
match run_hot_pipeline_raw_vacuum_body_ends_local_set(then_body) {
Some(selected_raw) => selected_raw
None => return false
}
}
_ => return false
}
run_hot_pipeline_raw_vacuum_body_has_f4073_slot_store_continuation(
parent_body, start_idx, slot_raw, selected_raw,
)
}
///|
fn run_hot_pipeline_raw_vacuum_f4071_sentinel_active_local(
final_if : @lib.Instruction,
) -> Int32? {
match final_if {
@lib.If(@lib.VoidBlockType, then_body, Some(_)) =>
match then_body {
[@lib.LocalGet(@lib.LocalIdx(active_raw)), @lib.Call(_), @lib.Br(label)] if run_hot_pipeline_raw_vacuum_label_depth(
label,
) >=
2 => Some(active_raw.reinterpret_as_int())
_ => None
}
_ => None
}
}
///|
fn run_hot_pipeline_raw_vacuum_f4071_sentinel_else_sets_selected_local(
final_if : @lib.Instruction,
selected_raw : Int32,
) -> Bool {
match final_if {
@lib.If(@lib.VoidBlockType, _, Some(else_body)) =>
run_hot_pipeline_raw_vacuum_body_sets_local(
else_body,
selected_raw.reinterpret_as_uint(),
)
_ => false
}
}
///|
fn run_hot_pipeline_raw_vacuum_f4071_sentinel_allocation_selected_local(
parent_body : Array[@lib.Instruction],
start_idx : Int32,
active_raw : Int32,
) -> Int32? {
let sig : Array[@lib.Instruction] = []
let mut idx = start_idx + 1
while idx < parent_body.length() && sig.length() < 13 {
match parent_body[idx] {
@lib.Nop => ()
instr => sig.push(instr)
}
idx = idx + 1
}
match sig {
[
@lib.LocalGet(_),
@lib.I32Load(@lib.MemArg(_, _, @lib.U64(8UL))),
@lib.LocalTee(@lib.LocalIdx(retained_raw)),
@lib.Call(_),
@lib.LocalGet(@lib.LocalIdx(next_active_raw)),
@lib.LocalGet(@lib.LocalIdx(next_retained_raw)),
@lib.LocalGet(@lib.LocalIdx(next_selected_raw)),
@lib.Call(_),
@lib.LocalSet(_),
@lib.LocalGet(@lib.LocalIdx(release_active_raw)),
@lib.Call(_),
@lib.LocalGet(@lib.LocalIdx(release_retained_raw)),
@lib.Call(_),
] =>
if next_active_raw.reinterpret_as_int() == active_raw &&
release_active_raw.reinterpret_as_int() == active_raw &&
next_retained_raw == retained_raw &&
release_retained_raw == retained_raw {
Some(next_selected_raw.reinterpret_as_int())
} else {
None
}
_ => None
}
}
///|
fn run_hot_pipeline_raw_vacuum_body_is_f4071_sentinel_prefix_before_allocation_preserve_candidate(
body : Array[@lib.Instruction],
parent_body : Array[@lib.Instruction],
start_idx : Int32,
) -> Bool {
let non_nops = run_hot_pipeline_raw_vacuum_strip_top_level_nops(body)
if non_nops.length() != 15 ||
run_hot_pipeline_raw_vacuum_instrs_contain_unreachable(non_nops) {
return false
}
match
(
non_nops[0],
non_nops[1],
non_nops[2],
non_nops[3],
non_nops[4],
non_nops[5],
non_nops[6],
non_nops[7],
non_nops[8],
non_nops[9],
non_nops[10],
non_nops[11],
non_nops[12],
non_nops[13],
) {
(
@lib.LocalGet(@lib.LocalIdx(source_raw)),
@lib.I32Load(@lib.MemArg(_, _, @lib.U64(4UL))),
@lib.LocalTee(@lib.LocalIdx(retained_raw)),
@lib.Call(_),
@lib.LocalGet(_),
@lib.LocalGet(@lib.LocalIdx(call_retained_raw)),
@lib.Call(_),
@lib.LocalSet(@lib.LocalIdx(result_raw)),
@lib.LocalGet(@lib.LocalIdx(release_retained_raw)),
@lib.Call(_),
@lib.LocalGet(@lib.LocalIdx(result_read_raw)),
@lib.LocalTee(_),
@lib.I64Const(@lib.I64(4294967296L)),
@lib.I64Eq,
) if source_raw != retained_raw &&
call_retained_raw == retained_raw &&
release_retained_raw == retained_raw &&
result_read_raw == result_raw => ()
_ => return false
}
match run_hot_pipeline_raw_vacuum_f4071_sentinel_active_local(non_nops[14]) {
Some(active_raw) =>
match
run_hot_pipeline_raw_vacuum_f4071_sentinel_allocation_selected_local(
parent_body, start_idx, active_raw,
) {
Some(selected_raw) =>
run_hot_pipeline_raw_vacuum_f4071_sentinel_else_sets_selected_local(
non_nops[14],
selected_raw,
)
None => false
}
None => false
}
}
///|
fn run_hot_pipeline_raw_vacuum_body_has_f4232_selected_local_return_continuation(
parent_body : Array[@lib.Instruction],
start_idx : Int32,
selected_raw : Int32,
) -> Bool {
let sig : Array[@lib.Instruction] = []
let mut idx = start_idx + 1
while idx < parent_body.length() && sig.length() < 2 {
match parent_body[idx] {
@lib.Nop => ()
instr => sig.push(instr)
}
idx = idx + 1
}
match sig {
[@lib.LocalGet(@lib.LocalIdx(next_selected_raw)), @lib.Return] =>
next_selected_raw.reinterpret_as_int() == selected_raw
_ => false
}
}
///|
fn run_hot_pipeline_raw_vacuum_f4232_sentinel_return_selected_local(
final_if : @lib.Instruction,
sentinel_raw : UInt32,
) -> Int32? {
match final_if {
@lib.If(@lib.VoidBlockType, then_body, Some(else_body)) => {
match then_body {
[@lib.Br(label)] if run_hot_pipeline_raw_vacuum_label_depth(label) >= 2 =>
()
_ => return None
}
match else_body {
[
@lib.LocalGet(@lib.LocalIdx(else_sentinel_raw)),
@lib.I32WrapI64,
@lib.LocalTee(@lib.LocalIdx(wrapped_raw)),
@lib.I32Const(@lib.I32(0)),
@lib.I32GeS,
@lib.If(@lib.VoidBlockType, nested_then, Some(nested_else)),
] if else_sentinel_raw == sentinel_raw =>
match nested_then {
[
@lib.LocalGet(@lib.LocalIdx(next_wrapped_raw)),
@lib.LocalSet(@lib.LocalIdx(selected_raw)),
] if next_wrapped_raw == wrapped_raw =>
match nested_else {
[@lib.Br(label)] if run_hot_pipeline_raw_vacuum_label_depth(
label,
) >=
2 => return Some(selected_raw.reinterpret_as_int())
_ => return None
}
_ => return None
}
_ => return None
}
}
_ => None
}
}
///|
fn run_hot_pipeline_raw_vacuum_body_is_f4232_sentinel_prefix_before_selected_local_return_preserve_candidate(
body : Array[@lib.Instruction],
parent_body : Array[@lib.Instruction],
start_idx : Int32,
) -> Bool {
let non_nops = run_hot_pipeline_raw_vacuum_strip_top_level_nops(body)
if non_nops.length() != 15 ||
run_hot_pipeline_raw_vacuum_instrs_contain_unreachable(non_nops) {
return false
}
let sentinel_raw = match
(
non_nops[0],
non_nops[1],
non_nops[2],
non_nops[3],
non_nops[4],
non_nops[5],
non_nops[6],
non_nops[7],
non_nops[8],
non_nops[9],
non_nops[10],
non_nops[11],
non_nops[12],
non_nops[13],
) {
(
@lib.LocalGet(@lib.LocalIdx(source_raw)),
@lib.I32Load(@lib.MemArg(_, _, @lib.U64(4UL))),
@lib.LocalTee(@lib.LocalIdx(retained_raw)),
@lib.Call(_),
@lib.LocalGet(@lib.LocalIdx(call_retained_raw)),
@lib.LocalGet(@lib.LocalIdx(call_arg_raw)),
@lib.Call(_),
@lib.LocalSet(@lib.LocalIdx(result_raw)),
@lib.LocalGet(@lib.LocalIdx(release_retained_raw)),
@lib.Call(_),
@lib.LocalGet(@lib.LocalIdx(result_read_raw)),
@lib.LocalTee(@lib.LocalIdx(sentinel_raw)),
@lib.I64Const(@lib.I64(4294967296L)),
@lib.I64Eq,
) if source_raw != retained_raw &&
call_retained_raw == retained_raw &&
call_arg_raw != retained_raw &&
release_retained_raw == retained_raw &&
result_read_raw == result_raw => sentinel_raw
_ => return false
}
match
run_hot_pipeline_raw_vacuum_f4232_sentinel_return_selected_local(
non_nops[14],
sentinel_raw,
) {
Some(selected_raw) =>
run_hot_pipeline_raw_vacuum_body_has_f4232_selected_local_return_continuation(
parent_body, start_idx, selected_raw,
)
None => false
}
}
///|
fn run_hot_pipeline_raw_vacuum_f4483_selected_value_from_tag1_if(
final_if : @lib.Instruction,
result_raw : UInt32,
) -> Int32? {
match final_if {
@lib.If(@lib.VoidBlockType, then_body, Some(else_body)) => {
if else_body.length() == 0 {
return None
}
let selected_raw = match then_body {
[
@lib.LocalGet(@lib.LocalIdx(then_result_raw)),
@lib.I64Load(_),
@lib.LocalSet(@lib.LocalIdx(selected_raw)),
@lib.LocalGet(@lib.LocalIdx(release_result_raw)),
@lib.Call(_),
@lib.LocalGet(@lib.LocalIdx(selected_reload_raw)),
@lib.LocalSet(@lib.LocalIdx(selected_target_raw)),
] if then_result_raw == result_raw &&
release_result_raw == result_raw &&
selected_reload_raw == selected_raw &&
selected_target_raw != selected_raw =>
selected_target_raw.reinterpret_as_int()
[
@lib.LocalGet(@lib.LocalIdx(then_result_raw)),
@lib.LocalTee(@lib.LocalIdx(then_alias_raw)),
@lib.I32Load(_),
@lib.LocalSet(@lib.LocalIdx(payload_raw)),
@lib.LocalGet(@lib.LocalIdx(count_alias_raw)),
@lib.Call(_),
@lib.LocalTee(_),
@lib.I32Const(@lib.I32(1)),
@lib.I32GtS,
@lib.If(@lib.VoidBlockType, _, Some(_)),
@lib.LocalGet(@lib.LocalIdx(payload_reload_raw)),
@lib.LocalSet(@lib.LocalIdx(selected_target_raw)),
] if then_result_raw == result_raw &&
count_alias_raw == then_alias_raw &&
payload_reload_raw == payload_raw =>
selected_target_raw.reinterpret_as_int()
_ => return None
}
match else_body[else_body.length() - 1] {
@lib.Br(label) if run_hot_pipeline_raw_vacuum_label_depth(label) >= 2 =>
Some(selected_raw)
_ => None
}
}
_ => None
}
}
///|
fn run_hot_pipeline_raw_vacuum_body_has_f4483_range_continuation(
parent_body : Array[@lib.Instruction],
start_idx : Int32,
selected_raw : Int32,
) -> Bool {
let sig : Array[@lib.Instruction] = []
let mut idx = start_idx + 1
while idx < parent_body.length() && sig.length() < 3 {
match parent_body[idx] {
@lib.Nop => ()
instr => sig.push(instr)
}
idx = idx + 1
}
match sig {
[
@lib.LocalGet(@lib.LocalIdx(next_selected_raw)),
@lib.LocalSet(_),
@lib.Br(label),
] =>
next_selected_raw.reinterpret_as_int() == selected_raw &&
run_hot_pipeline_raw_vacuum_label_depth(label) >= 1
_ => false
}
}
///|
fn run_hot_pipeline_raw_vacuum_body_is_f4483_indexed_call_result_tag1_prefix_preserve_candidate(
body : Array[@lib.Instruction],
parent_body : Array[@lib.Instruction],
start_idx : Int32,
) -> Bool {
let non_nops = run_hot_pipeline_raw_vacuum_strip_top_level_nops(body)
if (non_nops.length() != 17 && non_nops.length() != 21) ||
run_hot_pipeline_raw_vacuum_instrs_contain_unreachable(non_nops) {
return false
}
let result_raw = if non_nops.length() == 17 {
match
(
non_nops[0],
non_nops[1],
non_nops[2],
non_nops[3],
non_nops[4],
non_nops[5],
non_nops[6],
non_nops[7],
non_nops[8],
non_nops[9],
non_nops[10],
non_nops[11],
non_nops[12],
non_nops[13],
non_nops[14],
non_nops[15],
) {
(
@lib.LocalGet(@lib.LocalIdx(base_raw)),
@lib.I32Load(@lib.MemArg(_, _, @lib.U64(4UL))),
@lib.LocalGet(@lib.LocalIdx(index_raw)),
@lib.I32Const(@lib.I32(2)),
@lib.I32Shl,
@lib.I32Add,
@lib.I32Load(_),
@lib.LocalTee(@lib.LocalIdx(item_raw)),
@lib.Call(_),
@lib.LocalGet(@lib.LocalIdx(call_item_raw)),
@lib.I32Const(_),
@lib.Call(_),
@lib.LocalTee(@lib.LocalIdx(result_raw)),
@lib.Call(_),
@lib.I32Const(@lib.I32(1)),
@lib.I32Eq,
) if base_raw != index_raw && call_item_raw == item_raw => result_raw
_ => return false
}
} else {
match
(
non_nops[0],
non_nops[1],
non_nops[2],
non_nops[3],
non_nops[4],
non_nops[5],
non_nops[6],
non_nops[7],
non_nops[8],
non_nops[9],
non_nops[10],
non_nops[11],
non_nops[12],
non_nops[13],
non_nops[14],
non_nops[15],
non_nops[16],
non_nops[17],
non_nops[18],
non_nops[19],
) {
(
@lib.LocalGet(@lib.LocalIdx(base_raw)),
@lib.I32Load(@lib.MemArg(_, _, @lib.U64(4UL))),
@lib.LocalGet(@lib.LocalIdx(index_raw)),
@lib.I32Const(@lib.I32(2)),
@lib.I32Shl,
@lib.I32Add,
@lib.I32Load(_),
@lib.LocalTee(@lib.LocalIdx(item_raw)),
@lib.Call(_),
@lib.LocalGet(@lib.LocalIdx(call_item_raw)),
@lib.I64Const(_),
@lib.I64Const(_),
@lib.I64Const(_),
@lib.I64Const(_),
@lib.I32Const(_),
@lib.Call(_),
@lib.LocalTee(@lib.LocalIdx(result_raw)),
@lib.Call(_),
@lib.I32Const(@lib.I32(1)),
@lib.I32Eq,
) if base_raw != index_raw && call_item_raw == item_raw => result_raw
_ => return false
}
}
match
run_hot_pipeline_raw_vacuum_f4483_selected_value_from_tag1_if(
non_nops[non_nops.length() - 1],
result_raw,
) {
Some(selected_raw) =>
run_hot_pipeline_raw_vacuum_body_has_f4483_range_continuation(
parent_body, start_idx, selected_raw,
)
None => false
}
}
///|
fn run_hot_pipeline_raw_vacuum_body_has_f5277_tag_refcount_continuation(
parent_body : Array[@lib.Instruction],
start_idx : Int32,
) -> UInt32? {
let sig : Array[@lib.Instruction] = []
let mut idx = start_idx + 1
while idx < parent_body.length() && sig.length() < 3 {
match parent_body[idx] {
@lib.Nop => ()
instr => sig.push(instr)
}
idx = idx + 1
}
match sig {
[
@lib.LocalGet(@lib.LocalIdx(selected_raw)),
@lib.Call(_),
@lib.If(@lib.VoidBlockType, then_body, Some(else_body)),
] => {
let then_non_nops = run_hot_pipeline_raw_vacuum_strip_top_level_nops(
then_body,
)
let else_non_nops = run_hot_pipeline_raw_vacuum_strip_top_level_nops(
else_body,
)
match then_non_nops {
[@lib.LocalGet(@lib.LocalIdx(then_release_raw)), @lib.Call(_)] if then_release_raw ==
selected_raw => ()
_ => return None
}
if else_non_nops.length() < 5 {
return None
}
match (else_non_nops[0], else_non_nops[1]) {
(@lib.LocalGet(@lib.LocalIdx(read_raw)), @lib.I32Load(_)) if read_raw ==
selected_raw => ()
_ => return None
}
let mut saw_release = false
for release_idx = 0
release_idx + 1 < else_non_nops.length()
release_idx = release_idx + 1 {
match (else_non_nops[release_idx], else_non_nops[release_idx + 1]) {
(@lib.LocalGet(@lib.LocalIdx(release_raw)), @lib.Call(_)) if release_raw ==
selected_raw => saw_release = true
_ => ()
}
}
if saw_release {
Some(selected_raw)
} else {
None
}
}
_ => None
}
}
///|
fn run_hot_pipeline_raw_vacuum_body_sets_local_from_other(
body : Array[@lib.Instruction],
target_raw : UInt32,
) -> Bool {
for idx = 0; idx + 1 < body.length(); idx = idx + 1 {
match (body[idx], body[idx + 1]) {
(
@lib.LocalGet(@lib.LocalIdx(source_raw)),
@lib.LocalSet(@lib.LocalIdx(set_raw)),
) if set_raw == target_raw && source_raw != target_raw => return true
_ => ()
}
}
for instr in body {
match instr {
@lib.Block(_, @lib.Expr(child_body))
| @lib.Loop(_, @lib.Expr(child_body))
| @lib.TryTable(_, _, @lib.Expr(child_body)) =>
if run_hot_pipeline_raw_vacuum_body_sets_local_from_other(
child_body, target_raw,
) {
return true
}
@lib.If(_, then_body, Some(else_body)) =>
if run_hot_pipeline_raw_vacuum_body_sets_local_from_other(
then_body, target_raw,
) ||
run_hot_pipeline_raw_vacuum_body_sets_local_from_other(
else_body, target_raw,
) {
return true
}
@lib.If(_, then_body, None) =>
if run_hot_pipeline_raw_vacuum_body_sets_local_from_other(
then_body, target_raw,
) {
return true
}
_ => ()
}
}
false
}
///|
fn run_hot_pipeline_raw_vacuum_f5277_else_has_refcount_fallback_branch(
else_body : Array[@lib.Instruction],
result_raw : UInt32,
) -> Bool {
let else_non_nops = run_hot_pipeline_raw_vacuum_strip_top_level_nops(
else_body,
)
if else_non_nops.length() < 8 {
return false
}
let mut saw_result_refcount_load = false
for idx = 0; idx + 3 < else_non_nops.length(); idx = idx + 1 {
match
(
else_non_nops[idx],
else_non_nops[idx + 1],
else_non_nops[idx + 2],
else_non_nops[idx + 3],
) {
(
@lib.LocalGet(@lib.LocalIdx(read_result_raw)),
@lib.LocalTee(_),
@lib.I32Load(_),
@lib.LocalSet(_),
) if read_result_raw == result_raw => saw_result_refcount_load = true
_ => ()
}
}
if !saw_result_refcount_load {
return false
}
match
(
else_non_nops[else_non_nops.length() - 3],
else_non_nops[else_non_nops.length() - 2],
else_non_nops[else_non_nops.length() - 1],
) {
(@lib.LocalGet(_), @lib.LocalSet(_), @lib.Br(label)) =>
run_hot_pipeline_raw_vacuum_label_depth(label) >= 2
_ => false
}
}
///|
fn run_hot_pipeline_raw_vacuum_f5277_final_if_matches_selected_local(
final_if : @lib.Instruction,
result_raw : UInt32,
selected_raw : UInt32,
) -> Bool {
match final_if {
@lib.If(@lib.VoidBlockType, then_body, Some(else_body)) =>
run_hot_pipeline_raw_vacuum_body_sets_local_from_other(
then_body, selected_raw,
) &&
run_hot_pipeline_raw_vacuum_f5277_else_has_refcount_fallback_branch(
else_body, result_raw,
)
_ => false
}
}
///|
fn run_hot_pipeline_raw_vacuum_body_is_f5277_indexed_retain_before_tag_continuation_preserve_candidate(
body : Array[@lib.Instruction],
parent_body : Array[@lib.Instruction],
start_idx : Int32,
) -> Bool {
let non_nops = run_hot_pipeline_raw_vacuum_strip_top_level_nops(body)
if non_nops.length() != 17 ||
run_hot_pipeline_raw_vacuum_instrs_contain_unreachable(non_nops) {
return false
}
let result_raw = match
(
non_nops[0],
non_nops[1],
non_nops[2],
non_nops[3],
non_nops[4],
non_nops[5],
non_nops[6],
non_nops[7],
non_nops[8],
non_nops[9],
non_nops[10],
non_nops[11],
non_nops[12],
non_nops[13],
non_nops[14],
non_nops[15],
) {
(
@lib.LocalGet(@lib.LocalIdx(base_raw)),
@lib.I32Load(@lib.MemArg(_, _, @lib.U64(4UL))),
@lib.LocalGet(@lib.LocalIdx(index_raw)),
@lib.I32Const(@lib.I32(2)),
@lib.I32Shl,
@lib.I32Add,
@lib.I32Load(_),
@lib.LocalTee(@lib.LocalIdx(item_raw)),
@lib.Call(_),
@lib.LocalGet(@lib.LocalIdx(call_item_raw)),
@lib.LocalGet(@lib.LocalIdx(context_raw)),
@lib.Call(_),
@lib.LocalTee(@lib.LocalIdx(result_raw)),
@lib.Call(_),
@lib.I32Const(@lib.I32(1)),
@lib.I32Eq,
) if base_raw != index_raw &&
call_item_raw == item_raw &&
context_raw != item_raw &&
result_raw != item_raw => result_raw
_ => return false
}
let selected_raw = match
run_hot_pipeline_raw_vacuum_body_has_f5277_tag_refcount_continuation(
parent_body, start_idx,
) {
Some(raw) => raw
None => return false
}
run_hot_pipeline_raw_vacuum_f5277_final_if_matches_selected_local(
non_nops[16],
result_raw,
selected_raw,
)
}
///|
fn run_hot_pipeline_raw_vacuum_f5288_selected_locals_from_leading_dispatch(
body : Array[@lib.Instruction],
) -> (UInt32, UInt32)? {
let non_nops = run_hot_pipeline_raw_vacuum_strip_top_level_nops(body)
if non_nops.length() != 6 {
return None
}
let (receiver_raw, tag_raw) = match
(non_nops[0], non_nops[1], non_nops[2], non_nops[3], non_nops[4]) {
(
@lib.LocalGet(@lib.LocalIdx(receiver_raw)),
@lib.Call(_),
@lib.LocalTee(@lib.LocalIdx(tag_raw)),
@lib.I32Const(@lib.I32(1)),
@lib.I32Eq,
) => (receiver_raw, tag_raw)
_ => return None
}
match non_nops[5] {
@lib.If(@lib.VoidBlockType, then_body, Some(else_body)) => {
let then_non_nops = run_hot_pipeline_raw_vacuum_strip_top_level_nops(
then_body,
)
let then_selected_raw = match then_non_nops {
[
@lib.LocalGet(@lib.LocalIdx(then_receiver_raw)),
@lib.I32Load(_),
@lib.LocalTee(@lib.LocalIdx(retained_raw)),
@lib.Call(_),
@lib.LocalGet(@lib.LocalIdx(read_retained_raw)),
@lib.LocalSet(@lib.LocalIdx(selected_raw)),
] if then_receiver_raw == receiver_raw &&
read_retained_raw == retained_raw &&
selected_raw != retained_raw => selected_raw
_ => return None
}
let else_non_nops = run_hot_pipeline_raw_vacuum_strip_top_level_nops(
else_body,
)
if else_non_nops.length() != 3 {
return None
}
let nested_if = match else_non_nops {
[
@lib.LocalGet(@lib.LocalIdx(read_tag_raw)),
nested_if,
@lib.Unreachable,
] if read_tag_raw == tag_raw => nested_if
_ => return None
}
match nested_if {
@lib.If(@lib.VoidBlockType, nested_then, Some(nested_else)) => {
let nested_then_non_nops = run_hot_pipeline_raw_vacuum_strip_top_level_nops(
nested_then,
)
let (first_raw, second_raw) = match nested_then_non_nops {
[
@lib.LocalGet(@lib.LocalIdx(load0_receiver_raw)),
@lib.I32Load(_),
@lib.LocalSet(@lib.LocalIdx(first_raw)),
@lib.LocalGet(@lib.LocalIdx(load4_receiver_raw)),
@lib.I32Load(@lib.MemArg(_, _, @lib.U64(4UL))),
@lib.LocalTee(@lib.LocalIdx(second_raw)),
@lib.Call(_),
@lib.LocalGet(@lib.LocalIdx(release_first_raw)),
@lib.Call(_),
@lib.LocalGet(@lib.LocalIdx(read_second_raw)),
@lib.LocalSet(_),
@lib.LocalGet(@lib.LocalIdx(read_first_raw)),
@lib.LocalSet(_),
@lib.Br(then_label),
] if load0_receiver_raw == receiver_raw &&
load4_receiver_raw == receiver_raw &&
release_first_raw == first_raw &&
read_second_raw == second_raw &&
read_first_raw == first_raw &&
run_hot_pipeline_raw_vacuum_label_depth(then_label) >= 3 =>
(first_raw, second_raw)
_ => return None
}
let nested_else_non_nops = run_hot_pipeline_raw_vacuum_strip_top_level_nops(
nested_else,
)
let else_selected_raw = match nested_else_non_nops {
[
@lib.LocalGet(@lib.LocalIdx(else_receiver_raw)),
@lib.I32Load(_),
@lib.LocalTee(@lib.LocalIdx(retained_raw)),
@lib.Call(_),
@lib.LocalGet(@lib.LocalIdx(read_retained_raw)),
@lib.LocalSet(@lib.LocalIdx(selected_raw)),
@lib.Br(else_label),
] if else_receiver_raw == receiver_raw &&
read_retained_raw == retained_raw &&
selected_raw != retained_raw &&
selected_raw != first_raw &&
selected_raw != second_raw &&
run_hot_pipeline_raw_vacuum_label_depth(else_label) >= 2 =>
selected_raw
_ => return None
}
Some((then_selected_raw, else_selected_raw))
}
_ => None
}
}
_ => None
}
}
///|
fn run_hot_pipeline_raw_vacuum_body_has_f5288_selected_return_continuation(
parent_body : Array[@lib.Instruction],
start_idx : Int32,
selected_raw : UInt32,
) -> Bool {
let sig : Array[@lib.Instruction] = []
let mut idx = start_idx + 1
while idx < parent_body.length() && sig.length() < 8 {
match parent_body[idx] {
@lib.Nop => ()
instr => sig.push(instr)
}
idx = idx + 1
}
match sig {
[
@lib.LocalGet(@lib.LocalIdx(call_selected_raw)),
@lib.LocalGet(_),
@lib.Call(_),
@lib.LocalSet(@lib.LocalIdx(result_raw)),
@lib.LocalGet(@lib.LocalIdx(release_selected_raw)),
@lib.Call(_),
@lib.LocalGet(@lib.LocalIdx(return_result_raw)),
@lib.Return,
] =>
call_selected_raw == selected_raw &&
release_selected_raw == selected_raw &&
return_result_raw == result_raw
_ => false
}
}
///|
fn run_hot_pipeline_raw_vacuum_try_flatten_f5288_leading_tag_dispatch_wrappers_before_returns(
body : Array[@lib.Instruction],
parent_body : Array[@lib.Instruction],
start_idx : Int32,
) -> Array[@lib.Instruction]? {
if body.length() != 1 {
return None
}
let dispatch_body = match body[0] {
@lib.Block(@lib.VoidBlockType, @lib.Expr(dispatch_body)) => dispatch_body
_ => return None
}
let selected_raw = match
run_hot_pipeline_raw_vacuum_f5288_selected_locals_from_leading_dispatch(
dispatch_body,
) {
Some((then_selected_raw, _)) => then_selected_raw
None => return None
}
if !run_hot_pipeline_raw_vacuum_body_has_f5288_selected_return_continuation(
parent_body, start_idx, selected_raw,
) ||
run_hot_pipeline_raw_vacuum_body_targets_owner(dispatch_body, 0) {
return None
}
let rebased_once = run_hot_pipeline_raw_vacuum_rebase_outer_body(
dispatch_body, 0,
)
Some(run_hot_pipeline_raw_vacuum_rebase_outer_body(rebased_once, 0))
}
///|
fn run_hot_pipeline_raw_vacuum_f5392_selected_locals_from_leading_tag2_dispatch(
body : Array[@lib.Instruction],
) -> (UInt32, UInt32)? {
let non_nops = run_hot_pipeline_raw_vacuum_strip_top_level_nops(body)
if non_nops.length() != 6 {
return None
}
let (receiver_raw, tag_raw) = match
(non_nops[0], non_nops[1], non_nops[2], non_nops[3], non_nops[4]) {
(
@lib.LocalGet(@lib.LocalIdx(receiver_raw)),
@lib.Call(_),
@lib.LocalTee(@lib.LocalIdx(tag_raw)),
@lib.I32Const(@lib.I32(2)),
@lib.I32Eq,
) => (receiver_raw, tag_raw)
_ => return None
}
match non_nops[5] {
@lib.If(@lib.VoidBlockType, then_body, Some(else_body)) => {
let then_non_nops = run_hot_pipeline_raw_vacuum_strip_top_level_nops(
then_body,
)
let (first_raw, second_raw) = match then_non_nops {
[
@lib.LocalGet(@lib.LocalIdx(load0_receiver_raw)),
@lib.I32Load(_),
@lib.LocalSet(@lib.LocalIdx(first_raw)),
@lib.LocalGet(@lib.LocalIdx(load4_receiver_raw)),
@lib.I32Load(@lib.MemArg(_, _, @lib.U64(4UL))),
@lib.LocalTee(@lib.LocalIdx(second_raw)),
@lib.Call(_),
@lib.LocalGet(@lib.LocalIdx(release_first_raw)),
@lib.Call(_),
@lib.LocalGet(@lib.LocalIdx(read_second_raw)),
@lib.LocalSet(@lib.LocalIdx(selected_second_raw)),
@lib.LocalGet(@lib.LocalIdx(read_first_raw)),
@lib.LocalSet(@lib.LocalIdx(selected_first_raw)),
] if load0_receiver_raw == receiver_raw &&
load4_receiver_raw == receiver_raw &&
release_first_raw == first_raw &&
read_second_raw == second_raw &&
read_first_raw == first_raw &&
selected_first_raw != selected_second_raw =>
(selected_first_raw, selected_second_raw)
_ => return None
}
if first_raw == second_raw {
return None
}
let else_non_nops = run_hot_pipeline_raw_vacuum_strip_top_level_nops(
else_body,
)
let nested_if = match else_non_nops {
[
@lib.LocalGet(@lib.LocalIdx(read_tag_raw)),
@lib.I32Const(@lib.I32(1)),
@lib.I32Eq,
nested_if,
@lib.Unreachable,
] if read_tag_raw == tag_raw => nested_if
_ => return None
}
match nested_if {
@lib.If(@lib.VoidBlockType, nested_then, Some(nested_else)) => {
let nested_then_non_nops = run_hot_pipeline_raw_vacuum_strip_top_level_nops(
nested_then,
)
let then_selected_raw = match nested_then_non_nops {
[
@lib.LocalGet(@lib.LocalIdx(then_receiver_raw)),
@lib.I32Load(_),
@lib.LocalTee(@lib.LocalIdx(retained_raw)),
@lib.Call(_),
@lib.LocalGet(@lib.LocalIdx(read_retained_raw)),
@lib.LocalSet(@lib.LocalIdx(selected_raw)),
@lib.Br(then_label),
] if then_receiver_raw == receiver_raw &&
read_retained_raw == retained_raw &&
run_hot_pipeline_raw_vacuum_label_depth(then_label) >= 4 =>
selected_raw
_ => return None
}
let nested_else_non_nops = run_hot_pipeline_raw_vacuum_strip_top_level_nops(
nested_else,
)
let else_selected_raw = match nested_else_non_nops {
[
@lib.LocalGet(@lib.LocalIdx(else_receiver_raw)),
@lib.I32Load(_),
@lib.LocalTee(@lib.LocalIdx(retained_raw)),
@lib.Call(_),
@lib.LocalGet(@lib.LocalIdx(read_retained_raw)),
@lib.LocalSet(@lib.LocalIdx(selected_raw)),
@lib.Br(else_label),
] if else_receiver_raw == receiver_raw &&
read_retained_raw == retained_raw &&
run_hot_pipeline_raw_vacuum_label_depth(else_label) >= 5 =>
selected_raw
_ => return None
}
if then_selected_raw == first_raw ||
then_selected_raw == second_raw ||
else_selected_raw == first_raw ||
else_selected_raw == second_raw ||
then_selected_raw == else_selected_raw {
return None
}
Some((first_raw, second_raw))
}
_ => None
}
}
_ => None
}
}
///|
fn run_hot_pipeline_raw_vacuum_body_has_f5392_call_release_return_continuation(
parent_body : Array[@lib.Instruction],
start_idx : Int32,
first_raw : UInt32,
second_raw : UInt32,
) -> Bool {
let sig : Array[@lib.Instruction] = []
let mut idx = start_idx + 1
while idx < parent_body.length() && sig.length() < 15 {
match parent_body[idx] {
@lib.Nop => ()
instr => sig.push(instr)
}
idx = idx + 1
}
match sig {
[
@lib.LocalGet(@lib.LocalIdx(call_first_raw)),
@lib.LocalGet(@lib.LocalIdx(call_second_raw)),
@lib.Call(_),
@lib.LocalSet(@lib.LocalIdx(result_raw)),
@lib.LocalGet(@lib.LocalIdx(release_first_raw)),
@lib.Call(_),
@lib.LocalGet(@lib.LocalIdx(release_second_raw)),
@lib.Call(_),
@lib.LocalGet(@lib.LocalIdx(process_result_raw)),
@lib.Call(_),
@lib.LocalSet(@lib.LocalIdx(return_raw)),
@lib.LocalGet(@lib.LocalIdx(release_result_raw)),
@lib.Call(_),
@lib.LocalGet(@lib.LocalIdx(read_return_raw)),
@lib.Return,
] =>
call_first_raw == first_raw &&
call_second_raw == second_raw &&
release_first_raw == first_raw &&
release_second_raw == second_raw &&
process_result_raw == result_raw &&
release_result_raw == result_raw &&
read_return_raw == return_raw
_ => false
}
}
///|
fn run_hot_pipeline_raw_vacuum_try_flatten_f5392_leading_tag2_dispatch_wrappers_before_call_return(
body : Array[@lib.Instruction],
parent_body : Array[@lib.Instruction],
start_idx : Int32,
) -> Array[@lib.Instruction]? {
if body.length() != 1 {
return None
}
let dispatch_body = match body[0] {
@lib.Block(@lib.VoidBlockType, @lib.Expr(dispatch_body)) => dispatch_body
_ => return None
}
let (first_raw, second_raw) = match
run_hot_pipeline_raw_vacuum_f5392_selected_locals_from_leading_tag2_dispatch(
dispatch_body,
) {
Some(locals) => locals
None => return None
}
if !run_hot_pipeline_raw_vacuum_body_has_f5392_call_release_return_continuation(
parent_body, start_idx, first_raw, second_raw,
) ||
run_hot_pipeline_raw_vacuum_body_targets_owner(dispatch_body, 0) {
return None
}
let rebased_once = run_hot_pipeline_raw_vacuum_rebase_outer_body(
dispatch_body, 0,
)
Some(run_hot_pipeline_raw_vacuum_rebase_outer_body(rebased_once, 0))
}
///|
fn run_hot_pipeline_raw_vacuum_f5419_selected_local_from_tag1_owner_dispatch(
body : Array[@lib.Instruction],
) -> UInt32? {
let non_nops = run_hot_pipeline_raw_vacuum_strip_top_level_nops(body)
if non_nops.length() != 6 ||
run_hot_pipeline_raw_vacuum_body_targets_owner(non_nops, 0) {
return None
}
let (source_raw, tag_raw) = match
(non_nops[0], non_nops[1], non_nops[2], non_nops[3], non_nops[4]) {
(
@lib.LocalGet(@lib.LocalIdx(source_raw)),
@lib.Call(_),
@lib.LocalTee(@lib.LocalIdx(tag_raw)),
@lib.I32Const(@lib.I32(1)),
@lib.I32Eq,
) if source_raw != tag_raw => (source_raw, tag_raw)
_ => return None
}
match non_nops[5] {
@lib.If(@lib.VoidBlockType, then_body, Some(else_body)) => {
let selected_raw = match
run_hot_pipeline_raw_vacuum_strip_top_level_nops(then_body) {
[
@lib.LocalGet(@lib.LocalIdx(then_source_raw)),
@lib.I32Load(_),
@lib.LocalTee(@lib.LocalIdx(retained_raw)),
@lib.Call(_),
] if then_source_raw == source_raw && retained_raw != source_raw =>
retained_raw
[
@lib.LocalGet(@lib.LocalIdx(then_source_raw)),
@lib.I32Load(_),
@lib.LocalTee(@lib.LocalIdx(retained_raw)),
@lib.Call(_),
@lib.LocalGet(@lib.LocalIdx(read_retained_raw)),
@lib.LocalSet(@lib.LocalIdx(selected_raw)),
] if then_source_raw == source_raw &&
read_retained_raw == retained_raw &&
selected_raw != source_raw &&
selected_raw != retained_raw => selected_raw
_ => return None
}
let else_non_nops = run_hot_pipeline_raw_vacuum_strip_top_level_nops(
else_body,
)
let (nested_then, nested_else) = match else_non_nops {
[
@lib.LocalGet(@lib.LocalIdx(else_tag_raw)),
@lib.If(@lib.VoidBlockType, nested_then, Some(nested_else)),
@lib.Unreachable,
] if else_tag_raw == tag_raw => (nested_then, nested_else)
_ => return None
}
match run_hot_pipeline_raw_vacuum_strip_top_level_nops(nested_then) {
[
@lib.LocalGet(@lib.LocalIdx(then_source_raw)),
@lib.I32Load(_),
@lib.LocalSet(_),
@lib.LocalGet(@lib.LocalIdx(then_offset4_source_raw)),
@lib.I32Load(_),
@lib.LocalTee(_),
@lib.Call(_),
@lib.LocalGet(_),
@lib.Call(_),
@lib.LocalGet(_),
@lib.LocalSet(_),
@lib.LocalGet(_),
@lib.LocalSet(_),
@lib.Br(label),
] if then_source_raw == source_raw &&
then_offset4_source_raw == source_raw &&
run_hot_pipeline_raw_vacuum_label_depth(label) >= 5 => ()
_ => return None
}
match run_hot_pipeline_raw_vacuum_strip_top_level_nops(nested_else) {
[
@lib.LocalGet(@lib.LocalIdx(else_source_raw)),
@lib.I32Load(_),
@lib.LocalTee(_),
@lib.Call(_),
@lib.LocalGet(_),
@lib.LocalSet(_),
@lib.Br(label),
] if else_source_raw == source_raw &&
run_hot_pipeline_raw_vacuum_label_depth(label) >= 4 =>
Some(selected_raw)
_ => None
}
}
_ => None
}
}
///|
fn run_hot_pipeline_raw_vacuum_body_has_f5419_stream_loop_continuation(
parent_body : Array[@lib.Instruction],
start_idx : Int32,
selected_raw : UInt32,
) -> Bool {
let sig : Array[@lib.Instruction] = []
let mut idx = start_idx + 1
while idx < parent_body.length() && sig.length() < 12 {
match parent_body[idx] {
@lib.Nop => ()
instr => sig.push(instr)
}
idx = idx + 1
}
match sig {
[
@lib.LocalGet(@lib.LocalIdx(renderer_raw)),
@lib.I32Const(_),
@lib.LocalGet(@lib.LocalIdx(env_raw)),
@lib.I32Load(_),
@lib.CallIndirect(_, _),
@lib.Drop,
@lib.LocalGet(@lib.LocalIdx(read_selected_raw)),
@lib.I32Load(_),
@lib.LocalSet(@lib.LocalIdx(count_raw)),
@lib.I32Const(@lib.I32(0)),
@lib.LocalSet(@lib.LocalIdx(index_raw)),
@lib.Loop(@lib.VoidBlockType, _),
] =>
read_selected_raw == selected_raw &&
renderer_raw != env_raw &&
count_raw != index_raw
_ => false
}
}
///|
fn run_hot_pipeline_raw_vacuum_try_flatten_f5419_tag1_singleton_owner_before_stream_loop(
body : Array[@lib.Instruction],
) -> Array[@lib.Instruction]? {
if body.length() < 2 {
return None
}
let owner_body = match body[0] {
@lib.Block(@lib.VoidBlockType, @lib.Expr(owner_body)) =>
run_hot_pipeline_raw_vacuum_strip_top_level_nops(owner_body)
_ => return None
}
if owner_body.length() != 1 {
return None
}
let dispatch_body = match owner_body[0] {
@lib.Block(@lib.VoidBlockType, @lib.Expr(dispatch_body)) => dispatch_body
_ => return None
}
let selected_raw = match
run_hot_pipeline_raw_vacuum_f5419_selected_local_from_tag1_owner_dispatch(
dispatch_body,
) {
Some(raw) => raw
None => return None
}
if !run_hot_pipeline_raw_vacuum_body_has_f5419_stream_loop_continuation(
body, 0, selected_raw,
) {
return None
}
let rebased_once = run_hot_pipeline_raw_vacuum_rebase_outer_body(
dispatch_body, 0,
)
let flattened = run_hot_pipeline_raw_vacuum_rebase_outer_body(rebased_once, 0)
for idx = 1; idx < body.length(); idx = idx + 1 {
flattened.push(body[idx])
}
Some(flattened)
}
///|
fn run_hot_pipeline_raw_vacuum_body_is_f5351_leading_tag_dispatch_before_zero_return(
body : Array[@lib.Instruction],
parent_body : Array[@lib.Instruction],
start_idx : Int32,
) -> Bool {
let non_nops = run_hot_pipeline_raw_vacuum_strip_top_level_nops(body)
if non_nops.length() != 3 ||
!run_hot_pipeline_raw_vacuum_instrs_contain_unreachable(non_nops) ||
run_hot_pipeline_raw_vacuum_body_targets_owner(non_nops, 0) {
return false
}
match (non_nops[0], non_nops[1], non_nops[2]) {
(@lib.LocalGet(_), @lib.Call(_), @lib.If(@lib.VoidBlockType, _, Some(_))) =>
()
_ => return false
}
let sig : Array[@lib.Instruction] = []
let mut idx = start_idx + 1
while idx < parent_body.length() && sig.length() < 2 {
match parent_body[idx] {
@lib.Nop => ()
instr => sig.push(instr)
}
idx = idx + 1
}
match sig {
[@lib.I32Const(@lib.I32(0)), @lib.Return] => true
_ => false
}
}
///|
fn run_hot_pipeline_raw_vacuum_body_has_f5369_allocation_path_continuation(
parent_body : Array[@lib.Instruction],
start_idx : Int32,
) -> Bool {
let sig : Array[@lib.Instruction] = []
let mut idx = start_idx + 1
while idx < parent_body.length() && sig.length() < 12 {
match parent_body[idx] {
@lib.Nop => ()
instr => sig.push(instr)
}
idx = idx + 1
}
match sig {
[
@lib.LocalGet(@lib.LocalIdx(first_raw)),
@lib.LocalGet(@lib.LocalIdx(second_raw)),
@lib.LocalGet(@lib.LocalIdx(third_raw)),
@lib.Call(_),
@lib.LocalSet(@lib.LocalIdx(result_raw)),
@lib.LocalGet(@lib.LocalIdx(release_first_raw)),
@lib.Call(_),
@lib.I32Const(_),
@lib.LocalGet(@lib.LocalIdx(read_result_raw)),
@lib.Call(_),
@lib.LocalSet(_),
_,
] =>
first_raw == release_first_raw &&
result_raw == read_result_raw &&
first_raw != second_raw &&
first_raw != third_raw &&
second_raw != third_raw
_ => false
}
}
///|
fn run_hot_pipeline_raw_vacuum_f5369_then_has_refcount_dispatch_branch(
then_body : Array[@lib.Instruction],
dispatch_raw : UInt32,
) -> Bool {
let non_nops = run_hot_pipeline_raw_vacuum_strip_top_level_nops(then_body)
if non_nops.length() < 32 {
return false
}
let object_raw = match
(
non_nops[0],
non_nops[1],
non_nops[2],
non_nops[3],
non_nops[4],
non_nops[5],
non_nops[6],
) {
(
@lib.LocalGet(@lib.LocalIdx(read_dispatch_raw)),
@lib.LocalTee(@lib.LocalIdx(object_raw)),
@lib.I32Load(@lib.MemArg(_, _, @lib.U64(4UL))),
@lib.LocalSet(_),
@lib.LocalGet(@lib.LocalIdx(read_object_raw)),
@lib.I32Load(@lib.MemArg(_, _, @lib.U64(0UL))),
@lib.LocalSet(_),
) if read_dispatch_raw == dispatch_raw && read_object_raw == object_raw =>
object_raw
_ => return false
}
let mut field_load_count = 0
for idx = 0; idx + 2 < non_nops.length(); idx = idx + 1 {
match (non_nops[idx], non_nops[idx + 1], non_nops[idx + 2]) {
(
@lib.LocalGet(@lib.LocalIdx(read_object_raw)),
@lib.I32Load(_),
@lib.LocalSet(_),
) if read_object_raw == object_raw =>
field_load_count = field_load_count + 1
_ => ()
}
}
if field_load_count < 6 {
return false
}
for instr in non_nops {
match instr {
@lib.If(@lib.VoidBlockType, nested_then, Some(_)) => {
let nested_non_nops = run_hot_pipeline_raw_vacuum_strip_top_level_nops(
nested_then,
)
if nested_non_nops.length() >= 2 {
match nested_non_nops[nested_non_nops.length() - 1] {
@lib.Br(label) if run_hot_pipeline_raw_vacuum_label_depth(label) >=
4 => return true
_ => ()
}
}
}
_ => ()
}
}
false
}
///|
fn run_hot_pipeline_raw_vacuum_f5369_else_has_release_branch(
else_body : Array[@lib.Instruction],
dispatch_raw : UInt32,
owner_raw : UInt32,
) -> Bool {
let non_nops = run_hot_pipeline_raw_vacuum_strip_top_level_nops(else_body)
if non_nops.length() < 5 {
return false
}
match
(
non_nops[0],
non_nops[1],
non_nops[non_nops.length() - 3],
non_nops[non_nops.length() - 2],
non_nops[non_nops.length() - 1],
) {
(
@lib.LocalGet(@lib.LocalIdx(read_dispatch_raw)),
@lib.If(@lib.VoidBlockType, _, None),
@lib.LocalGet(@lib.LocalIdx(read_owner_raw)),
@lib.Call(_),
@lib.Br(label),
) =>
read_dispatch_raw == dispatch_raw &&
read_owner_raw == owner_raw &&
run_hot_pipeline_raw_vacuum_label_depth(label) >= 3
_ => false
}
}
///|
fn run_hot_pipeline_raw_vacuum_body_is_f5369_load_call_tag_refcount_dispatch_before_allocation_continuation_preserve_candidate(
body : Array[@lib.Instruction],
parent_body : Array[@lib.Instruction],
start_idx : Int32,
) -> Bool {
let non_nops = run_hot_pipeline_raw_vacuum_strip_top_level_nops(body)
if non_nops.length() < 13 {
return false
}
let (owner_raw, retained_raw, result_raw, dispatch_raw, then_body, else_body) = match
(
non_nops[0],
non_nops[1],
non_nops[2],
non_nops[3],
non_nops[4],
non_nops[5],
non_nops[6],
non_nops[7],
non_nops[8],
non_nops[9],
non_nops[10],
non_nops[11],
non_nops[12],
) {
(
@lib.LocalGet(@lib.LocalIdx(owner_raw)),
@lib.I32Load(@lib.MemArg(_, _, @lib.U64(8UL))),
@lib.LocalTee(@lib.LocalIdx(retained_raw)),
@lib.Call(_),
@lib.LocalGet(@lib.LocalIdx(read_retained_raw)),
@lib.LocalGet(_),
@lib.Call(_),
@lib.LocalSet(@lib.LocalIdx(result_raw)),
@lib.LocalGet(@lib.LocalIdx(release_retained_raw)),
@lib.Call(_),
@lib.LocalGet(@lib.LocalIdx(read_result_raw)),
@lib.LocalTee(@lib.LocalIdx(dispatch_raw)),
@lib.If(@lib.VoidBlockType, then_body, Some(else_body)),
) if read_retained_raw == retained_raw &&
release_retained_raw == retained_raw &&
read_result_raw == result_raw =>
(owner_raw, retained_raw, result_raw, dispatch_raw, then_body, else_body)
_ => return false
}
retained_raw != owner_raw &&
result_raw != retained_raw &&
dispatch_raw != retained_raw &&
// Only a matching fixed prefix needs the recursive unreachable proof.
!run_hot_pipeline_raw_vacuum_instrs_contain_unreachable(non_nops) &&
run_hot_pipeline_raw_vacuum_f5369_then_has_refcount_dispatch_branch(
then_body, dispatch_raw,
) &&
run_hot_pipeline_raw_vacuum_f5369_else_has_release_branch(
else_body, dispatch_raw, owner_raw,
) &&
run_hot_pipeline_raw_vacuum_body_has_f5369_allocation_path_continuation(
parent_body, start_idx,
)
}
///|
fn run_hot_pipeline_raw_vacuum_f6113_then_has_retain_dispatch_branch(
then_body : Array[@lib.Instruction],
owner_raw : UInt32,
dispatch_raw : UInt32,
) -> UInt32? {
let then_non_nops = run_hot_pipeline_raw_vacuum_strip_top_level_nops(
then_body,
)
if then_non_nops.length() != 21 {
return None
}
match
(
then_non_nops[0],
then_non_nops[1],
then_non_nops[2],
then_non_nops[3],
then_non_nops[4],
then_non_nops[5],
then_non_nops[6],
then_non_nops[7],
then_non_nops[8],
then_non_nops[9],
then_non_nops[10],
then_non_nops[11],
then_non_nops[12],
then_non_nops[13],
then_non_nops[14],
then_non_nops[15],
then_non_nops[16],
then_non_nops[17],
then_non_nops[18],
then_non_nops[19],
then_non_nops[20],
) {
(
@lib.LocalGet(@lib.LocalIdx(read_dispatch_raw)),
@lib.LocalSet(@lib.LocalIdx(set_selected_raw)),
@lib.LocalGet(@lib.LocalIdx(load_dispatch_raw)),
@lib.I32Load(@lib.MemArg(_, _, @lib.U64(4UL))),
@lib.LocalSet(@lib.LocalIdx(payload_raw)),
@lib.LocalGet(@lib.LocalIdx(context_owner_raw)),
@lib.I32Load(@lib.MemArg(_, _, @lib.U64(4UL))),
@lib.LocalTee(@lib.LocalIdx(context_raw)),
@lib.Call(_),
@lib.LocalGet(@lib.LocalIdx(retain_payload_raw)),
@lib.Call(_),
@lib.LocalGet(@lib.LocalIdx(call_payload_raw)),
@lib.LocalGet(@lib.LocalIdx(call_context_raw)),
@lib.Call(_),
@lib.LocalSet(@lib.LocalIdx(compare_raw)),
@lib.LocalGet(@lib.LocalIdx(release_payload_raw)),
@lib.Call(_),
@lib.LocalGet(@lib.LocalIdx(release_context_raw)),
@lib.Call(_),
@lib.LocalGet(@lib.LocalIdx(read_compare_raw)),
@lib.If(@lib.VoidBlockType, nested_then, Some(nested_else)),
) if read_dispatch_raw == dispatch_raw &&
set_selected_raw != dispatch_raw &&
load_dispatch_raw == dispatch_raw &&
context_owner_raw != owner_raw &&
payload_raw != dispatch_raw &&
context_raw != payload_raw &&
retain_payload_raw == payload_raw &&
call_payload_raw == payload_raw &&
call_context_raw == context_raw &&
release_payload_raw == payload_raw &&
release_context_raw == context_raw &&
read_compare_raw == compare_raw => {
let result_raw = match
run_hot_pipeline_raw_vacuum_strip_top_level_nops(nested_then) {
[
@lib.LocalGet(@lib.LocalIdx(nested_dispatch_raw)),
@lib.LocalSet(@lib.LocalIdx(result_raw)),
] if nested_dispatch_raw == dispatch_raw => result_raw
_ => return None
}
match run_hot_pipeline_raw_vacuum_strip_top_level_nops(nested_else) {
[
@lib.LocalGet(@lib.LocalIdx(release_selected_raw)),
@lib.Call(_),
@lib.Br(label),
] if release_selected_raw == set_selected_raw &&
run_hot_pipeline_raw_vacuum_label_depth(label) >= 4 =>
Some(result_raw)
_ => None
}
}
_ => None
}
}
///|
fn run_hot_pipeline_raw_vacuum_f6113_else_has_release_branch(
else_body : Array[@lib.Instruction],
dispatch_raw : UInt32,
) -> Bool {
match run_hot_pipeline_raw_vacuum_strip_top_level_nops(else_body) {
[
@lib.LocalGet(@lib.LocalIdx(read_dispatch_raw)),
@lib.If(@lib.VoidBlockType, release_body, None),
@lib.Br(label),
] if read_dispatch_raw == dispatch_raw &&
run_hot_pipeline_raw_vacuum_label_depth(label) >= 3 =>
match run_hot_pipeline_raw_vacuum_strip_top_level_nops(release_body) {
[@lib.LocalGet(@lib.LocalIdx(release_dispatch_raw)), @lib.Call(_)] =>
release_dispatch_raw == dispatch_raw
_ => false
}
_ => false
}
}
///|
fn run_hot_pipeline_raw_vacuum_body_has_f6113_parent_continuation(
parent_body : Array[@lib.Instruction],
start_idx : Int32,
owner_raw : UInt32,
selected_raw : UInt32,
) -> Bool {
let sig : Array[@lib.Instruction] = []
let mut idx = start_idx + 1
while idx < parent_body.length() && sig.length() < 27 {
match parent_body[idx] {
@lib.Nop => ()
instr => sig.push(instr)
}
idx = idx + 1
}
match sig {
[
@lib.LocalGet(@lib.LocalIdx(read_owner_raw)),
@lib.I32Load(_),
@lib.LocalTee(@lib.LocalIdx(copied_raw)),
@lib.LocalSet(@lib.LocalIdx(saved_raw)),
@lib.LocalGet(@lib.LocalIdx(read_copied_raw)),
@lib.LocalTee(@lib.LocalIdx(retained_raw)),
@lib.Call(_),
@lib.LocalGet(@lib.LocalIdx(retain_saved_raw)),
@lib.Call(_),
@lib.LocalGet(@lib.LocalIdx(call_retained_raw)),
@lib.Call(_),
@lib.LocalSet(@lib.LocalIdx(count_raw)),
@lib.LocalGet(@lib.LocalIdx(release_retained_raw)),
@lib.Call(_),
@lib.LocalGet(@lib.LocalIdx(read_count_raw)),
@lib.I32Const(@lib.I32(1)),
@lib.I32Sub,
@lib.LocalSet(_),
@lib.LocalGet(@lib.LocalIdx(read_selected_raw)),
@lib.I32Load(_),
@lib.LocalGet(_),
@lib.I32Load(_),
@lib.I32Add,
@lib.LocalSet(_),
@lib.LocalGet(@lib.LocalIdx(load_selected_raw)),
@lib.I32Load(@lib.MemArg(_, _, @lib.U64(4UL))),
@lib.LocalSet(_),
] =>
read_owner_raw == owner_raw &&
read_copied_raw == copied_raw &&
saved_raw != copied_raw &&
retained_raw != saved_raw &&
retain_saved_raw == saved_raw &&
call_retained_raw == retained_raw &&
release_retained_raw == retained_raw &&
read_count_raw == count_raw &&
read_selected_raw == selected_raw &&
load_selected_raw == selected_raw
_ => false
}
}
///|
fn run_hot_pipeline_raw_vacuum_body_is_f6113_retain_call_tag_dispatch_before_parent_continuation_preserve_candidate(
body : Array[@lib.Instruction],
parent_body : Array[@lib.Instruction],
start_idx : Int32,
) -> Bool {
let non_nops = run_hot_pipeline_raw_vacuum_strip_top_level_nops(body)
if non_nops.length() != 12 ||
run_hot_pipeline_raw_vacuum_instrs_contain_unreachable(non_nops) {
return false
}
let (owner_raw, retained_raw, result_raw, dispatch_raw, then_body, else_body) = match
(
non_nops[0],
non_nops[1],
non_nops[2],
non_nops[3],
non_nops[4],
non_nops[5],
non_nops[6],
non_nops[7],
non_nops[8],
non_nops[9],
non_nops[10],
non_nops[11],
) {
(
@lib.LocalGet(@lib.LocalIdx(owner_raw)),
@lib.I32Load(_),
@lib.LocalTee(@lib.LocalIdx(retained_raw)),
@lib.Call(_),
@lib.LocalGet(@lib.LocalIdx(read_retained_raw)),
@lib.Call(_),
@lib.LocalSet(@lib.LocalIdx(result_raw)),
@lib.LocalGet(@lib.LocalIdx(release_retained_raw)),
@lib.Call(_),
@lib.LocalGet(@lib.LocalIdx(read_result_raw)),
@lib.LocalTee(@lib.LocalIdx(dispatch_raw)),
@lib.If(@lib.VoidBlockType, then_body, Some(else_body)),
) if read_retained_raw == retained_raw &&
release_retained_raw == retained_raw &&
read_result_raw == result_raw =>
(owner_raw, retained_raw, result_raw, dispatch_raw, then_body, else_body)
_ => return false
}
let selected_raw = match
run_hot_pipeline_raw_vacuum_f6113_then_has_retain_dispatch_branch(
then_body, owner_raw, dispatch_raw,
) {
Some(raw) => raw
None => return false
}
owner_raw != retained_raw &&
result_raw != retained_raw &&
dispatch_raw != result_raw &&
run_hot_pipeline_raw_vacuum_f6113_else_has_release_branch(
else_body, dispatch_raw,
) &&
run_hot_pipeline_raw_vacuum_body_has_f6113_parent_continuation(
parent_body, start_idx, owner_raw, selected_raw,
)
}
///|
fn run_hot_pipeline_raw_vacuum_body_has_f4310_accumulator_store_continuation(
parent_body : Array[@lib.Instruction],
start_idx : Int32,
) -> Bool {
let sig : Array[@lib.Instruction] = []
let mut idx = start_idx + 1
while idx < parent_body.length() && sig.length() < 12 {
match parent_body[idx] {
@lib.Nop => ()
instr => sig.push(instr)
}
idx = idx + 1
}
match sig {
[
@lib.LocalGet(@lib.LocalIdx(first_ptr_raw)),
@lib.LocalGet(_),
@lib.LocalGet(@lib.LocalIdx(first_reload_raw)),
@lib.I32Load(_),
@lib.I32Add,
@lib.I32Store(_),
@lib.LocalGet(@lib.LocalIdx(second_ptr_raw)),
@lib.LocalGet(_),
@lib.LocalGet(@lib.LocalIdx(second_reload_raw)),
@lib.I32Load(_),
@lib.I32Add,
@lib.I32Store(_),
] =>
first_ptr_raw == first_reload_raw &&
second_ptr_raw == second_reload_raw &&
first_ptr_raw != second_ptr_raw
_ => false
}
}
///|
fn run_hot_pipeline_raw_vacuum_f4493_selected_value_from_tag1_if(
final_if : @lib.Instruction,
result_raw : UInt32,
) -> Int32? {
match final_if {
@lib.If(@lib.VoidBlockType, then_body, Some(else_body)) => {
if else_body.length() == 0 {
return None
}
let selected_raw = match then_body {
[
@lib.LocalGet(@lib.LocalIdx(then_result_raw)),
@lib.I32Load(_),
@lib.LocalSet(@lib.LocalIdx(payload_raw)),
@lib.LocalGet(@lib.LocalIdx(release_result_raw)),
@lib.Call(_),
@lib.LocalGet(@lib.LocalIdx(payload_reload_raw)),
@lib.LocalSet(@lib.LocalIdx(selected_raw)),
] if then_result_raw == result_raw &&
release_result_raw == result_raw &&
payload_reload_raw == payload_raw => selected_raw.reinterpret_as_int()
_ => return None
}
match else_body[else_body.length() - 1] {
@lib.Br(label) if run_hot_pipeline_raw_vacuum_label_depth(label) >= 2 =>
Some(selected_raw)
_ => None
}
}
_ => None
}
}
///|
fn run_hot_pipeline_raw_vacuum_body_has_f4493_allocation_return_continuation(
parent_body : Array[@lib.Instruction],
start_idx : Int32,
selected_raw : Int32,
) -> Bool {
let sig : Array[@lib.Instruction] = []
let mut idx = start_idx + 1
while idx < parent_body.length() && sig.length() < 10 {
match parent_body[idx] {
@lib.Nop => ()
instr => sig.push(instr)
}
idx = idx + 1
}
match sig {
[
@lib.I32Const(@lib.I32(4)),
@lib.Call(_),
@lib.LocalTee(@lib.LocalIdx(ptr_raw)),
@lib.I32Const(_),
@lib.Call(_),
@lib.LocalGet(@lib.LocalIdx(store_ptr_raw)),
@lib.LocalGet(@lib.LocalIdx(payload_raw)),
@lib.I32Store(_),
@lib.LocalGet(@lib.LocalIdx(return_ptr_raw)),
@lib.Return,
] =>
store_ptr_raw == ptr_raw &&
return_ptr_raw == ptr_raw &&
payload_raw.reinterpret_as_int() == selected_raw
_ => false
}
}
///|
fn run_hot_pipeline_raw_vacuum_body_is_f4493_offset4_call_result_tag1_prefix_before_allocation_return_preserve_candidate(
body : Array[@lib.Instruction],
parent_body : Array[@lib.Instruction],
start_idx : Int32,
) -> Bool {
let non_nops = run_hot_pipeline_raw_vacuum_strip_top_level_nops(body)
if non_nops.length() != 16 ||
run_hot_pipeline_raw_vacuum_instrs_contain_unreachable(non_nops) {
return false
}
let result_raw = match
(
non_nops[0],
non_nops[1],
non_nops[2],
non_nops[3],
non_nops[4],
non_nops[5],
non_nops[6],
non_nops[7],
non_nops[8],
non_nops[9],
non_nops[10],
non_nops[11],
non_nops[12],
non_nops[13],
non_nops[14],
) {
(
@lib.LocalGet(@lib.LocalIdx(base_raw)),
@lib.I32Load(@lib.MemArg(_, _, @lib.U64(4UL))),
@lib.LocalTee(@lib.LocalIdx(retained_raw)),
@lib.Call(_),
@lib.LocalGet(@lib.LocalIdx(call_base_raw)),
@lib.LocalGet(@lib.LocalIdx(call_retained_raw)),
@lib.Call(_),
@lib.LocalSet(@lib.LocalIdx(result_set_raw)),
@lib.LocalGet(@lib.LocalIdx(release_retained_raw)),
@lib.Call(_),
@lib.LocalGet(@lib.LocalIdx(result_read_raw)),
@lib.LocalTee(@lib.LocalIdx(result_raw)),
@lib.Call(_),
@lib.I32Const(@lib.I32(1)),
@lib.I32Eq,
) if base_raw != retained_raw &&
call_base_raw != retained_raw &&
call_retained_raw == retained_raw &&
release_retained_raw == retained_raw &&
result_read_raw == result_set_raw => result_raw
_ => return false
}
match
run_hot_pipeline_raw_vacuum_f4493_selected_value_from_tag1_if(
non_nops[15],
result_raw,
) {
Some(selected_raw) =>
run_hot_pipeline_raw_vacuum_body_has_f4493_allocation_return_continuation(
parent_body, start_idx, selected_raw,
)
None => false
}
}
///|
fn run_hot_pipeline_raw_vacuum_body_is_f4310_side_effect_prefix_before_accumulator_stores_preserve_candidate(
body : Array[@lib.Instruction],
parent_body : Array[@lib.Instruction],
start_idx : Int32,
) -> Bool {
let non_nops = run_hot_pipeline_raw_vacuum_strip_top_level_nops(body)
if !run_hot_pipeline_raw_vacuum_body_is_side_effect_prefix_if_wrapper_candidate(
non_nops,
) ||
!run_hot_pipeline_raw_vacuum_body_has_f4310_accumulator_store_continuation(
parent_body, start_idx,
) {
return false
}
match non_nops[non_nops.length() - 1] {
@lib.If(@lib.VoidBlockType, _, Some(else_body)) =>
run_hot_pipeline_raw_instrs_have_branch(else_body)
_ => false
}
}
///|
fn run_hot_pipeline_raw_vacuum_body_is_f4311_tag85_86_dispatch(
body : Array[@lib.Instruction],
) -> Bool {
let non_nops = run_hot_pipeline_raw_vacuum_strip_top_level_nops(body)
if non_nops.length() != 6 {
return false
}
let tagged_raw = match
(
non_nops[0],
non_nops[1],
non_nops[2],
non_nops[3],
non_nops[4],
non_nops[5],
) {
(
@lib.LocalGet(@lib.LocalIdx(source_raw)),
@lib.Call(_),
@lib.LocalTee(@lib.LocalIdx(tagged_raw)),
@lib.I32Const(@lib.I32(86)),
@lib.I32Eq,
@lib.If(@lib.VoidBlockType, then_body, Some(else_body)),
) => {
if !run_hot_pipeline_raw_instrs_have_branch(else_body) ||
run_hot_pipeline_raw_instrs_have_branch(then_body) {
return false
}
match else_body {
[
@lib.LocalGet(@lib.LocalIdx(else_tagged_raw)),
@lib.I32Const(@lib.I32(85)),
@lib.I32Eq,
@lib.If(@lib.VoidBlockType, nested_then, Some(nested_else)),
@lib.Unreachable,
] if else_tagged_raw == tagged_raw =>
if !run_hot_pipeline_raw_instrs_have_branch(nested_then) ||
!run_hot_pipeline_raw_instrs_have_branch(nested_else) {
return false
}
_ => return false
}
if source_raw == tagged_raw {
return false
}
tagged_raw
}
_ => return false
}
tagged_raw.reinterpret_as_int() >= 0
}
///|
fn run_hot_pipeline_raw_vacuum_body_has_f4311_call_local_set_continuation(
parent_body : Array[@lib.Instruction],
start_idx : Int32,
) -> Bool {
let sig : Array[@lib.Instruction] = []
let mut idx = start_idx + 1
while idx < parent_body.length() && sig.length() < 6 {
match parent_body[idx] {
@lib.Nop => ()
instr => sig.push(instr)
}
idx = idx + 1
}
match sig {
[
@lib.I32Const(_),
@lib.Call(_),
@lib.LocalTee(@lib.LocalIdx(temp_raw)),
@lib.LocalGet(@lib.LocalIdx(arg_raw)),
@lib.Call(_),
@lib.LocalSet(_),
] => temp_raw != arg_raw
_ => false
}
}
///|
fn run_hot_pipeline_raw_vacuum_try_flatten_f4311_nested_tag_dispatch_before_call_continuation(
body : Array[@lib.Instruction],
parent_body : Array[@lib.Instruction],
start_idx : Int32,
) -> Array[@lib.Instruction]? {
if !run_hot_pipeline_raw_vacuum_body_has_f4311_call_local_set_continuation(
parent_body, start_idx,
) ||
body.length() != 1 {
return None
}
match body[0] {
@lib.Block(@lib.VoidBlockType, @lib.Expr(dispatch_body)) => {
if !run_hot_pipeline_raw_vacuum_body_is_f4311_tag85_86_dispatch(
dispatch_body,
) {
return None
}
Some(
run_hot_pipeline_raw_vacuum_rebase_outer_body(
run_hot_pipeline_raw_vacuum_rebase_outer_body(dispatch_body, 0),
0,
),
)
}
_ => None
}
}
///|
fn run_hot_pipeline_raw_vacuum_body_is_f4859_leading_owner_exit_dispatch(
body : Array[@lib.Instruction],
) -> Bool {
let non_nops = run_hot_pipeline_raw_vacuum_strip_top_level_nops(body)
if non_nops.length() != 4 ||
run_hot_pipeline_raw_vacuum_body_targets_owner(non_nops, 0) {
return false
}
match (non_nops[0], non_nops[1], non_nops[2], non_nops[3]) {
(
@lib.LocalGet(@lib.LocalIdx(source_raw)),
@lib.LocalTee(@lib.LocalIdx(tagged_raw)),
@lib.Call(_),
@lib.If(@lib.VoidBlockType, then_body, Some(else_body)),
) => {
if source_raw == tagged_raw ||
!run_hot_pipeline_raw_instrs_have_branch(then_body) ||
!run_hot_pipeline_raw_instrs_have_branch(else_body) {
return false
}
match then_body {
[
@lib.LocalGet(@lib.LocalIdx(then_source_raw)),
@lib.LocalSet(_),
@lib.LocalGet(@lib.LocalIdx(then_i64_source_raw)),
@lib.I64Load(_),
@lib.LocalSet(_),
@lib.LocalGet(@lib.LocalIdx(then_i32_source_raw)),
@lib.I32Load(@lib.MemArg(_, _, @lib.U64(8UL))),
@lib.LocalSet(_),
..,
@lib.Unreachable,
] if then_source_raw == tagged_raw &&
then_i64_source_raw == tagged_raw &&
then_i32_source_raw == tagged_raw => ()
_ => return false
}
match else_body {
[
@lib.LocalGet(@lib.LocalIdx(else_i32_source_raw)),
@lib.I32Load(@lib.MemArg(_, _, @lib.U64(0UL))),
@lib.LocalSet(_),
@lib.LocalGet(@lib.LocalIdx(else_i64_source_raw)),
@lib.I64Load(@lib.MemArg(_, _, @lib.U64(4UL))),
@lib.LocalSet(_),
@lib.LocalGet(@lib.LocalIdx(else_release_source_raw)),
@lib.Call(_),
..,
] if else_i32_source_raw == tagged_raw &&
else_i64_source_raw == tagged_raw &&
else_release_source_raw == tagged_raw => true
_ => false
}
}
_ => false
}
}
///|
fn run_hot_pipeline_raw_vacuum_body_is_f4860_leading_owner_exit_dispatch(
body : Array[@lib.Instruction],
) -> Bool {
let non_nops = run_hot_pipeline_raw_vacuum_strip_top_level_nops(body)
if non_nops.length() != 3 ||
run_hot_pipeline_raw_vacuum_body_targets_owner(non_nops, 0) {
return false
}
match (non_nops[0], non_nops[1], non_nops[2]) {
(
@lib.LocalGet(@lib.LocalIdx(source_raw)),
@lib.Call(_),
@lib.If(@lib.VoidBlockType, then_body, Some(else_body)),
) => {
match then_body {
[
@lib.LocalGet(@lib.LocalIdx(then_i64_source_raw)),
@lib.I64Load(@lib.MemArg(_, _, @lib.U64(0UL))),
@lib.LocalSet(@lib.LocalIdx(loaded_i64_raw)),
@lib.LocalGet(@lib.LocalIdx(then_i32_source_raw)),
@lib.I32Load(@lib.MemArg(_, _, @lib.U64(8UL))),
@lib.LocalTee(@lib.LocalIdx(tagged_child_raw)),
@lib.Call(_),
@lib.If(@lib.VoidBlockType, child_then, Some(child_else)),
@lib.Unreachable,
] if then_i64_source_raw == source_raw &&
then_i32_source_raw == source_raw => {
match child_then {
[
@lib.LocalGet(@lib.LocalIdx(child_then_source_raw)),
@lib.I64Load(@lib.MemArg(_, _, @lib.U64(0UL))),
@lib.LocalSet(_),
@lib.LocalGet(@lib.LocalIdx(child_then_value_raw)),
@lib.LocalSet(_),
@lib.Br(_),
] if child_then_source_raw == tagged_child_raw &&
child_then_value_raw == loaded_i64_raw => ()
_ => return false
}
match child_else {
[
@lib.LocalGet(@lib.LocalIdx(child_else_value_raw)),
@lib.LocalSet(_),
@lib.Br(_),
] if child_else_value_raw == loaded_i64_raw => ()
_ => return false
}
}
_ => return false
}
match else_body {
[
@lib.LocalGet(@lib.LocalIdx(else_i32_source_raw)),
@lib.I32Load(@lib.MemArg(_, _, @lib.U64(0UL))),
@lib.LocalSet(@lib.LocalIdx(loaded_i32_raw)),
@lib.LocalGet(@lib.LocalIdx(else_i64_source_raw)),
@lib.I64Load(@lib.MemArg(_, _, @lib.U64(4UL))),
@lib.LocalTee(@lib.LocalIdx(tagged_i64_raw)),
@lib.I64Const(@lib.I64(4294967296L)),
@lib.I64Eq,
@lib.If(@lib.VoidBlockType, fallback_then, Some(fallback_else)),
] if else_i32_source_raw == source_raw &&
else_i64_source_raw == source_raw => {
match fallback_then {
[
@lib.LocalGet(@lib.LocalIdx(fallback_then_value_raw)),
@lib.LocalSet(_),
] if fallback_then_value_raw == loaded_i32_raw => ()
_ => return false
}
match fallback_else {
[
@lib.LocalGet(@lib.LocalIdx(fallback_i64_raw)),
@lib.I32WrapI64,
@lib.LocalSet(_),
@lib.LocalGet(@lib.LocalIdx(fallback_i32_raw)),
@lib.LocalSet(_),
@lib.Br(_),
] if fallback_i64_raw == tagged_i64_raw &&
fallback_i32_raw == loaded_i32_raw => true
_ => false
}
}
_ => false
}
}
_ => false
}
}
///|
fn run_hot_pipeline_raw_vacuum_body_is_f4915_tag1_owner_dispatch(
body : Array[@lib.Instruction],
) -> Bool {
let non_nops = run_hot_pipeline_raw_vacuum_strip_top_level_nops(body)
if non_nops.length() != 6 ||
run_hot_pipeline_raw_vacuum_body_targets_owner(non_nops, 0) {
return false
}
let (source_raw, tag_raw) = match
(non_nops[0], non_nops[1], non_nops[2], non_nops[3], non_nops[4]) {
(
@lib.LocalGet(@lib.LocalIdx(source_raw)),
@lib.Call(_),
@lib.LocalTee(@lib.LocalIdx(tag_raw)),
@lib.I32Const(@lib.I32(1)),
@lib.I32Eq,
) => (source_raw, tag_raw)
_ => return false
}
if source_raw == tag_raw {
return false
}
match non_nops[5] {
@lib.If(@lib.VoidBlockType, then_body, Some(else_body)) => {
let payload_raw = match then_body {
[
@lib.LocalGet(@lib.LocalIdx(then_source_raw)),
@lib.I32Load(@lib.MemArg(_, _, @lib.U64(0UL))),
@lib.LocalTee(@lib.LocalIdx(payload_raw)),
@lib.Call(_),
@lib.LocalGet(@lib.LocalIdx(payload_copy_source_raw)),
@lib.LocalSet(@lib.LocalIdx(payload_copy_raw)),
] if then_source_raw == source_raw &&
payload_copy_source_raw == payload_raw => payload_copy_raw
_ => return false
}
let (nested_then, nested_else) = match else_body {
[
@lib.LocalGet(@lib.LocalIdx(else_tag_raw)),
@lib.If(@lib.VoidBlockType, nested_then, Some(nested_else)),
@lib.Unreachable,
] if else_tag_raw == tag_raw => (nested_then, nested_else)
_ => return false
}
let selected_raw = match nested_then {
[
@lib.LocalGet(@lib.LocalIdx(then_source_raw)),
@lib.I32Load(@lib.MemArg(_, _, @lib.U64(0UL))),
@lib.LocalSet(@lib.LocalIdx(selected_raw)),
@lib.LocalGet(@lib.LocalIdx(then_offset4_source_raw)),
@lib.I32Load(@lib.MemArg(_, _, @lib.U64(4UL))),
@lib.LocalTee(@lib.LocalIdx(offset4_raw)),
@lib.Call(_),
@lib.LocalGet(@lib.LocalIdx(release_selected_raw)),
@lib.Call(_),
@lib.LocalGet(@lib.LocalIdx(offset4_copy_source_raw)),
@lib.LocalSet(@lib.LocalIdx(offset4_copy_raw)),
@lib.LocalGet(@lib.LocalIdx(selected_copy_source_raw)),
@lib.LocalSet(@lib.LocalIdx(selected_copy_raw)),
@lib.Br(label),
] if then_source_raw == source_raw &&
then_offset4_source_raw == source_raw &&
selected_raw == release_selected_raw &&
offset4_copy_source_raw == offset4_raw &&
selected_copy_source_raw == selected_raw &&
run_hot_pipeline_raw_vacuum_label_depth(label) > 1 => {
if offset4_raw == source_raw ||
offset4_raw == tag_raw ||
offset4_copy_raw == selected_copy_raw {
return false
}
selected_copy_raw
}
_ => return false
}
match nested_else {
[
@lib.LocalGet(@lib.LocalIdx(else_source_raw)),
@lib.I32Load(@lib.MemArg(_, _, @lib.U64(0UL))),
@lib.LocalTee(@lib.LocalIdx(fallback_raw)),
@lib.Call(_),
@lib.LocalGet(@lib.LocalIdx(fallback_copy_source_raw)),
@lib.LocalSet(@lib.LocalIdx(fallback_copy_raw)),
@lib.Br(label),
] if else_source_raw == source_raw &&
fallback_copy_source_raw == fallback_raw &&
fallback_copy_raw != source_raw &&
fallback_copy_raw != tag_raw &&
fallback_copy_raw != selected_raw &&
fallback_copy_raw != payload_raw &&
run_hot_pipeline_raw_vacuum_label_depth(label) > 1 => true
_ => false
}
}
_ => false
}
}
///|
fn run_hot_pipeline_raw_vacuum_body_has_f4915_tag95_return_continuation(
parent_body : Array[@lib.Instruction],
start_idx : Int32,
) -> Bool {
let sig : Array[@lib.Instruction] = []
let mut idx = start_idx + 1
while idx < parent_body.length() && sig.length() < 8 {
match parent_body[idx] {
@lib.Nop => ()
instr => sig.push(instr)
}
idx = idx + 1
}
match sig {
[
@lib.LocalGet(@lib.LocalIdx(renderer_raw)),
@lib.I32Const(@lib.I32(95)),
@lib.Call(_),
@lib.Drop,
@lib.LocalGet(@lib.LocalIdx(payload_raw)),
@lib.LocalGet(@lib.LocalIdx(next_renderer_raw)),
@lib.Call(_),
@lib.LocalSet(_),
] if renderer_raw == next_renderer_raw && payload_raw != renderer_raw =>
true
_ => false
}
}
///|
fn run_hot_pipeline_raw_vacuum_try_flatten_f4915_tag1_owner_dispatch_before_tag95_return(
body : Array[@lib.Instruction],
parent_body : Array[@lib.Instruction],
start_idx : Int32,
) -> Array[@lib.Instruction]? {
if !run_hot_pipeline_raw_vacuum_body_has_f4915_tag95_return_continuation(
parent_body, start_idx,
) {
return None
}
if run_hot_pipeline_raw_vacuum_body_is_f4915_tag1_owner_dispatch(body) {
return Some(run_hot_pipeline_raw_vacuum_rebase_outer_body(body, 0))
}
if body.length() != 1 {
return None
}
match body[0] {
@lib.Block(@lib.VoidBlockType, @lib.Expr(dispatch_body)) => {
if !run_hot_pipeline_raw_vacuum_body_is_f4915_tag1_owner_dispatch(
dispatch_body,
) {
return None
}
Some(
run_hot_pipeline_raw_vacuum_rebase_outer_body(
run_hot_pipeline_raw_vacuum_rebase_outer_body(dispatch_body, 0),
0,
),
)
}
_ => None
}
}
///|
fn run_hot_pipeline_raw_vacuum_body_has_f4919_tag99_return_continuation(
parent_body : Array[@lib.Instruction],
start_idx : Int32,
) -> Bool {
let sig : Array[@lib.Instruction] = []
let mut idx = start_idx + 1
while idx < parent_body.length() && sig.length() < 14 {
match parent_body[idx] {
@lib.Nop => ()
instr => sig.push(instr)
}
idx = idx + 1
}
match sig {
[
@lib.LocalGet(@lib.LocalIdx(renderer_raw)),
@lib.I32Const(@lib.I32(99)),
@lib.Call(_),
@lib.Drop,
@lib.LocalGet(_),
@lib.If(@lib.VoidBlockType, _, None),
@lib.LocalGet(@lib.LocalIdx(payload_raw)),
@lib.LocalGet(@lib.LocalIdx(next_renderer_raw)),
@lib.Call(_),
@lib.LocalSet(@lib.LocalIdx(result_raw)),
@lib.LocalGet(@lib.LocalIdx(release_raw)),
@lib.Call(_),
@lib.LocalGet(@lib.LocalIdx(next_result_raw)),
@lib.Return,
..,
] if renderer_raw == next_renderer_raw &&
payload_raw == release_raw &&
result_raw == next_result_raw &&
payload_raw != renderer_raw => true
_ => false
}
}
///|
fn run_hot_pipeline_raw_vacuum_body_is_f4919_call_if_return_dispatch(
body : Array[@lib.Instruction],
return_local : UInt32,
) -> Bool {
let non_nops = run_hot_pipeline_raw_vacuum_strip_top_level_nops(body)
if non_nops.length() != 3 {
return false
}
match (non_nops[0], non_nops[1], non_nops[2]) {
(
@lib.LocalGet(@lib.LocalIdx(source_raw)),
@lib.Call(_),
@lib.If(@lib.VoidBlockType, then_body, Some(else_body)),
) => {
if then_body.length() < 8 || else_body.length() < 7 {
return false
}
match
(then_body[0], then_body[1], then_body[2], then_body[3], then_body[4]) {
(
@lib.LocalGet(@lib.LocalIdx(then_source_raw)),
@lib.I32Load(@lib.MemArg(_, _, @lib.U64(0UL))),
@lib.LocalTee(_),
@lib.I32Const(@lib.I32(1)),
@lib.I32Eq,
) if then_source_raw == source_raw => ()
_ => return false
}
match then_body[5] {
@lib.If(@lib.VoidBlockType, tag_then, Some(tag_else)) => {
match tag_then {
[
@lib.LocalGet(_),
@lib.I32Const(@lib.I32(99)),
@lib.Call(_),
@lib.Drop,
@lib.Call(_),
@lib.LocalGet(_),
@lib.Call(_),
@lib.LocalSet(_),
] => ()
_ => return false
}
match tag_else {
[@lib.LocalGet(_), @lib.LocalSet(_), @lib.Br(label)] if run_hot_pipeline_raw_vacuum_label_depth(
label,
) >
1 => ()
_ => return false
}
}
_ => return false
}
match (then_body[6], then_body[7]) {
(@lib.LocalGet(_), @lib.LocalSet(@lib.LocalIdx(ret_raw))) if ret_raw ==
return_local => ()
_ => return false
}
match else_body[5] {
@lib.If(@lib.VoidBlockType, first_then, Some(first_else)) => {
if first_then.length() == 0 || first_else.length() == 0 {
return false
}
match first_then[first_then.length() - 1] {
@lib.Br(label) if run_hot_pipeline_raw_vacuum_label_depth(label) > 1 =>
()
_ => return false
}
match first_else[first_else.length() - 1] {
@lib.Br(label) if run_hot_pipeline_raw_vacuum_label_depth(label) > 1 =>
()
_ => return false
}
}
_ => return false
}
match else_body[else_body.length() - 1] {
@lib.Unreachable => true
_ => false
}
}
_ => false
}
}
///|
fn run_hot_pipeline_raw_vacuum_try_flatten_f4919_call_if_return_before_tag99_return(
body : Array[@lib.Instruction],
parent_body : Array[@lib.Instruction],
start_idx : Int32,
module_ctx : @ir.HotModuleContext?,
) -> Array[@lib.Instruction]? {
if body.length() != 3 ||
!run_hot_pipeline_raw_vacuum_body_has_f4919_tag99_return_continuation(
parent_body, start_idx,
) {
return None
}
let return_local = match (body[1], body[2]) {
(@lib.LocalGet(@lib.LocalIdx(raw)), @lib.Return) => raw
_ => return None
}
let dispatch_body = match body[0] {
@lib.Block(@lib.VoidBlockType, @lib.Expr(dispatch_body)) => dispatch_body
_ => return None
}
if !run_hot_pipeline_raw_vacuum_body_is_f4919_call_if_return_dispatch(
dispatch_body, return_local,
) {
return None
}
let rebased = run_hot_pipeline_raw_vacuum_rebase_outer_body(dispatch_body, 0)
let (clean_rebased, _) = run_hot_pipeline_raw_vacuum_preclean_instrs_owned(
rebased, module_ctx,
)
let flattened : Array[@lib.Instruction] = []
for instr in clean_rebased {
flattened.push(instr)
}
flattened.push(body[1])
flattened.push(body[2])
Some(flattened)
}
///|
fn run_hot_pipeline_raw_vacuum_f5027_branch_selected_local(
body : Array[@lib.Instruction],
selector_raw : UInt32,
require_terminal_outer_branch : Bool,
allow_leading_release : Bool,
) -> UInt32? {
let non_nops = run_hot_pipeline_raw_vacuum_strip_top_level_nops(body)
let mut start_idx = 0
let mut leading_release_count = 0
while allow_leading_release &&
leading_release_count < 3 &&
start_idx + 1 < non_nops.length() {
match (non_nops[start_idx], non_nops[start_idx + 1]) {
(@lib.LocalGet(@lib.LocalIdx(released_raw)), @lib.Call(_)) if released_raw !=
selector_raw => {
start_idx = start_idx + 2
leading_release_count = leading_release_count + 1
}
_ => break
}
}
if non_nops.length() - start_idx < 6 {
return None
}
match
(
non_nops[start_idx],
non_nops[start_idx + 1],
non_nops[start_idx + 2],
non_nops[start_idx + 3],
) {
(
@lib.LocalGet(@lib.LocalIdx(branch_selector_raw)),
@lib.LocalTee(_),
@lib.I32Load(@lib.MemArg(_, _, @lib.U64(0UL))),
@lib.LocalSet(_),
) if branch_selector_raw == selector_raw => ()
_ => return None
}
let final_set_idx = if require_terminal_outer_branch {
match non_nops[non_nops.length() - 1] {
@lib.Br(label) if run_hot_pipeline_raw_vacuum_label_depth(label) >= 3 =>
non_nops.length() - 3
_ => return None
}
} else {
match non_nops[non_nops.length() - 1] {
@lib.Br(_) => return None
_ => non_nops.length() - 2
}
}
if final_set_idx < start_idx || final_set_idx + 1 >= non_nops.length() {
return None
}
match (non_nops[final_set_idx], non_nops[final_set_idx + 1]) {
(@lib.LocalGet(_), @lib.LocalSet(@lib.LocalIdx(selected_raw))) =>
Some(selected_raw)
_ => None
}
}
///|
fn run_hot_pipeline_raw_vacuum_body_has_f5083_conditional_handoff_continuation(
parent_body : Array[@lib.Instruction],
start_idx : Int32,
selected_raw : UInt32,
) -> Bool {
let sig : Array[@lib.Instruction] = []
let mut idx = start_idx + 1
while idx < parent_body.length() && sig.length() < 2 {
match parent_body[idx] {
@lib.Nop => ()
instr => sig.push(instr)
}
idx = idx + 1
}
match sig {
[@lib.LocalGet(_), @lib.If(@lib.VoidBlockType, then_body, Some(else_body))] => {
let then_non_nops = run_hot_pipeline_raw_vacuum_strip_top_level_nops(
then_body,
)
let else_non_nops = run_hot_pipeline_raw_vacuum_strip_top_level_nops(
else_body,
)
match (then_non_nops, else_non_nops) {
(
[
@lib.LocalGet(@lib.LocalIdx(first_selected_raw)),
@lib.Call(_),
@lib.LocalSet(@lib.LocalIdx(first_tmp_raw)),
@lib.LocalGet(@lib.LocalIdx(second_selected_raw)),
@lib.Call(_),
@lib.LocalSet(@lib.LocalIdx(second_tmp_raw)),
@lib.LocalGet(@lib.LocalIdx(release_selected_raw)),
@lib.Call(_),
@lib.LocalGet(@lib.LocalIdx(call_first_tmp_raw)),
@lib.LocalGet(@lib.LocalIdx(call_second_tmp_raw)),
@lib.I32Const(@lib.I32(1)),
@lib.Call(_),
@lib.LocalSet(@lib.LocalIdx(result_raw)),
@lib.LocalGet(@lib.LocalIdx(release_second_tmp_raw)),
@lib.Call(_),
@lib.LocalGet(@lib.LocalIdx(result_read_raw)),
@lib.LocalSet(@lib.LocalIdx(handoff_raw)),
],
[
@lib.LocalGet(@lib.LocalIdx(else_selected_raw)),
@lib.LocalSet(@lib.LocalIdx(else_handoff_raw)),
],
) =>
first_selected_raw == selected_raw &&
second_selected_raw == selected_raw &&
release_selected_raw == selected_raw &&
else_selected_raw == selected_raw &&
call_first_tmp_raw == first_tmp_raw &&
call_second_tmp_raw == second_tmp_raw &&
release_second_tmp_raw == second_tmp_raw &&
result_read_raw == result_raw &&
else_handoff_raw == handoff_raw
_ => false
}
}
_ => false
}
}
///|
fn run_hot_pipeline_raw_vacuum_body_has_f5027_allocation_return_continuation(
parent_body : Array[@lib.Instruction],
start_idx : Int32,
payload_raw : UInt32,
) -> Bool {
let sig : Array[@lib.Instruction] = []
let mut idx = start_idx + 1
while idx < parent_body.length() && sig.length() < 10 {
match parent_body[idx] {
@lib.Nop => ()
instr => sig.push(instr)
}
idx = idx + 1
}
match sig {
[
@lib.I32Const(@lib.I32(4)),
@lib.Call(_),
@lib.LocalTee(@lib.LocalIdx(box_raw)),
@lib.I32Const(@lib.I32(tag_raw)),
@lib.Call(_),
@lib.LocalGet(@lib.LocalIdx(init_box_raw)),
@lib.LocalGet(@lib.LocalIdx(next_payload_raw)),
@lib.I32Store(_),
@lib.LocalGet(@lib.LocalIdx(return_box_raw)),
@lib.Return,
..,
] if box_raw == init_box_raw &&
box_raw == return_box_raw &&
next_payload_raw == payload_raw &&
(tag_raw == 256 || tag_raw == 257) => true
_ => false
}
}
///|
fn run_hot_pipeline_raw_vacuum_body_is_f5083_selected_payload_before_conditional_handoff_preserve_candidate(
body : Array[@lib.Instruction],
parent_body : Array[@lib.Instruction],
start_idx : Int32,
) -> Bool {
let non_nops = run_hot_pipeline_raw_vacuum_strip_top_level_nops(body)
if non_nops.length() < 8 {
return false
}
let final_idx = non_nops.length() - 1
let selector_raw = if final_idx >= 5 {
match
(
non_nops[final_idx - 5],
non_nops[final_idx - 4],
non_nops[final_idx - 3],
non_nops[final_idx - 2],
non_nops[final_idx - 1],
non_nops[final_idx],
) {
(
@lib.LocalGet(_),
@lib.LocalTee(@lib.LocalIdx(raw)),
@lib.Call(_),
@lib.I32Const(@lib.I32(1)),
@lib.I32Eq,
@lib.If(@lib.VoidBlockType, _, Some(_)),
) => raw
_ =>
match
(
non_nops[final_idx - 3],
non_nops[final_idx - 2],
non_nops[final_idx - 1],
non_nops[final_idx],
) {
(
@lib.LocalGet(_),
@lib.LocalTee(@lib.LocalIdx(raw)),
@lib.Call(_),
@lib.If(@lib.VoidBlockType, _, Some(_)),
) => raw
_ => return false
}
}
} else {
match
(
non_nops[final_idx - 3],
non_nops[final_idx - 2],
non_nops[final_idx - 1],
non_nops[final_idx],
) {
(
@lib.LocalGet(_),
@lib.LocalTee(@lib.LocalIdx(raw)),
@lib.Call(_),
@lib.If(@lib.VoidBlockType, _, Some(_)),
) => raw
_ => return false
}
}
let mut call_count = 0
let mut saw_local_set = false
for idx = 0; idx < final_idx; idx = idx + 1 {
match non_nops[idx] {
@lib.Call(_) => call_count = call_count + 1
@lib.LocalSet(_) => saw_local_set = true
_ => ()
}
}
if call_count < 3 || !saw_local_set {
return false
}
let (then_body, else_body) = match non_nops[final_idx] {
@lib.If(@lib.VoidBlockType, then_body, Some(else_body)) =>
(then_body, else_body)
_ => return false
}
let then_selected = match
run_hot_pipeline_raw_vacuum_f5027_branch_selected_local(
then_body, selector_raw, false, false,
) {
Some(raw) => raw
None => return false
}
let else_selected = match
run_hot_pipeline_raw_vacuum_f5027_branch_selected_local(
else_body, selector_raw, true, false,
) {
Some(raw) => raw
None => return false
}
then_selected != else_selected &&
run_hot_pipeline_raw_vacuum_body_has_f5083_conditional_handoff_continuation(
parent_body, start_idx, then_selected,
)
}
///|
fn run_hot_pipeline_raw_vacuum_non_nops_is_f5027_selected_payload_before_allocation_return_preserve_candidate(
non_nops : Array[@lib.Instruction],
parent_body : Array[@lib.Instruction],
start_idx : Int32,
) -> Bool {
if non_nops.length() < 8 {
return false
}
let final_idx = non_nops.length() - 1
let selector_raw = match
(
non_nops[final_idx - 3],
non_nops[final_idx - 2],
non_nops[final_idx - 1],
non_nops[final_idx],
) {
(
@lib.LocalGet(_),
@lib.LocalTee(@lib.LocalIdx(raw)),
@lib.Call(_),
@lib.If(@lib.VoidBlockType, _, Some(_)),
) => raw
_ => return false
}
let mut call_count = 0
let mut saw_local_set = false
for idx = 0; idx < final_idx; idx = idx + 1 {
match non_nops[idx] {
@lib.Call(_) => call_count = call_count + 1
@lib.LocalSet(_) => saw_local_set = true
_ => ()
}
}
if call_count < 3 || !saw_local_set {
return false
}
let (then_body, else_body) = match non_nops[final_idx] {
@lib.If(@lib.VoidBlockType, then_body, Some(else_body)) =>
(then_body, else_body)
_ => return false
}
let then_selected = match
run_hot_pipeline_raw_vacuum_f5027_branch_selected_local(
then_body, selector_raw, true, false,
) {
Some(raw) => raw
None => return false
}
let else_selected = match
run_hot_pipeline_raw_vacuum_f5027_branch_selected_local(
else_body, selector_raw, false, true,
) {
Some(raw) => raw
None => return false
}
then_selected != else_selected &&
run_hot_pipeline_raw_vacuum_body_has_f5027_allocation_return_continuation(
parent_body, start_idx, else_selected,
)
}
///|
fn run_hot_pipeline_raw_vacuum_body_is_f5027_selected_payload_before_allocation_return_preserve_candidate(
body : Array[@lib.Instruction],
parent_body : Array[@lib.Instruction],
start_idx : Int32,
) -> Bool {
let non_nops = run_hot_pipeline_raw_vacuum_strip_top_level_nops(body)
if run_hot_pipeline_raw_vacuum_non_nops_is_f5027_selected_payload_before_allocation_return_preserve_candidate(
non_nops, parent_body, start_idx,
) {
return true
}
if non_nops.length() < 2 {
return false
}
let merged : Array[@lib.Instruction] = []
match non_nops[0] {
@lib.Block(@lib.VoidBlockType, @lib.Expr(prefix_body)) =>
for instr in run_hot_pipeline_raw_vacuum_strip_top_level_nops(prefix_body) {
merged.push(instr)
}
_ => return false
}
for idx = 1; idx < non_nops.length(); idx = idx + 1 {
merged.push(non_nops[idx])
}
run_hot_pipeline_raw_vacuum_non_nops_is_f5027_selected_payload_before_allocation_return_preserve_candidate(
merged, parent_body, start_idx,
)
}
///|
fn run_hot_pipeline_raw_vacuum_body_has_f4859_case0_return_continuation(
parent_body : Array[@lib.Instruction],
start_idx : Int32,
) -> Bool {
let sig : Array[@lib.Instruction] = []
let mut idx = start_idx + 1
while idx < parent_body.length() && sig.length() < 4 {
match parent_body[idx] {
@lib.Nop => ()
instr => sig.push(instr)
}
idx = idx + 1
}
match sig {
[@lib.LocalGet(_), @lib.I32Const(@lib.I32(0)), @lib.Call(_), @lib.Drop] =>
true
_ => false
}
}
///|
fn run_hot_pipeline_raw_vacuum_try_flatten_f4859_leading_owner_exit_dispatch_before_case0_return(
body : Array[@lib.Instruction],
parent_body : Array[@lib.Instruction],
start_idx : Int32,
) -> Array[@lib.Instruction]? {
if !run_hot_pipeline_raw_vacuum_body_has_f4859_case0_return_continuation(
parent_body, start_idx,
) {
return None
}
if run_hot_pipeline_raw_vacuum_body_is_f4859_leading_owner_exit_dispatch(body) ||
run_hot_pipeline_raw_vacuum_body_is_f4860_leading_owner_exit_dispatch(body) {
return Some(run_hot_pipeline_raw_vacuum_rebase_outer_body(body, 0))
}
if body.length() != 1 {
return None
}
match body[0] {
@lib.Block(@lib.VoidBlockType, @lib.Expr(dispatch_body)) => {
if !run_hot_pipeline_raw_vacuum_body_is_f4859_leading_owner_exit_dispatch(
dispatch_body,
) &&
!run_hot_pipeline_raw_vacuum_body_is_f4860_leading_owner_exit_dispatch(
dispatch_body,
) {
return None
}
Some(
run_hot_pipeline_raw_vacuum_rebase_outer_body(
run_hot_pipeline_raw_vacuum_rebase_outer_body(dispatch_body, 0),
0,
),
)
}
_ => None
}
}
///|
fn run_hot_pipeline_raw_vacuum_body_is_f4665_tag2_56_classification_dispatch(
body : Array[@lib.Instruction],
) -> Bool {
let non_nops = run_hot_pipeline_raw_vacuum_strip_top_level_nops(body)
if non_nops.length() != 6 {
return false
}
let (source_raw, tag_raw) = match
(
non_nops[0],
non_nops[1],
non_nops[2],
non_nops[3],
non_nops[4],
non_nops[5],
) {
(
@lib.LocalGet(@lib.LocalIdx(source_raw)),
@lib.Call(_),
@lib.LocalTee(@lib.LocalIdx(tag_raw)),
@lib.I32Const(@lib.I32(2)),
@lib.I32Eq,
@lib.If(@lib.VoidBlockType, then_body, Some(else_body)),
) => {
let stripped_then = run_hot_pipeline_raw_vacuum_strip_top_level_nops(
then_body,
)
match stripped_then {
[
@lib.LocalGet(@lib.LocalIdx(then_source0_raw)),
@lib.I32Load(@lib.MemArg(_, _, @lib.U64(0UL))),
@lib.LocalSet(@lib.LocalIdx(field0_raw)),
@lib.LocalGet(@lib.LocalIdx(then_source4_raw)),
@lib.I32Load(@lib.MemArg(_, _, @lib.U64(4UL))),
@lib.LocalSet(@lib.LocalIdx(field4_raw)),
@lib.LocalGet(@lib.LocalIdx(then_source8_raw)),
@lib.I32Load(@lib.MemArg(_, _, @lib.U64(8UL))),
@lib.LocalTee(@lib.LocalIdx(field8_raw)),
@lib.Call(_),
@lib.LocalGet(@lib.LocalIdx(copy4_raw)),
@lib.LocalSet(_),
@lib.LocalGet(@lib.LocalIdx(copy8_raw)),
@lib.LocalSet(_),
@lib.LocalGet(@lib.LocalIdx(copy0_raw)),
@lib.LocalSet(_),
] if then_source0_raw == source_raw &&
then_source4_raw == source_raw &&
then_source8_raw == source_raw &&
copy4_raw == field4_raw &&
copy8_raw == field8_raw &&
copy0_raw == field0_raw => ()
_ => return false
}
let stripped_else = run_hot_pipeline_raw_vacuum_strip_top_level_nops(
else_body,
)
match stripped_else {
[
@lib.LocalGet(@lib.LocalIdx(else_tag_raw)),
@lib.I32Const(@lib.I32(56)),
@lib.I32Eq,
@lib.If(@lib.VoidBlockType, nested_then, Some(nested_else)),
@lib.Unreachable,
] if else_tag_raw == tag_raw => {
let stripped_nested_then = run_hot_pipeline_raw_vacuum_strip_top_level_nops(
nested_then,
)
match stripped_nested_then {
[
@lib.LocalGet(@lib.LocalIdx(then_source_raw)),
@lib.I32Load(@lib.MemArg(_, _, @lib.U64(0UL))),
@lib.LocalTee(@lib.LocalIdx(retained_raw)),
@lib.Call(_),
@lib.LocalGet(@lib.LocalIdx(copy_retained_raw)),
@lib.LocalSet(_),
@lib.Br(then_label),
] if then_source_raw == source_raw &&
copy_retained_raw == retained_raw &&
run_hot_pipeline_raw_vacuum_label_depth(then_label) >= 4 => ()
_ => return false
}
let stripped_nested_else = run_hot_pipeline_raw_vacuum_strip_top_level_nops(
nested_else,
)
match stripped_nested_else {
[
@lib.LocalGet(@lib.LocalIdx(else_source4_raw)),
@lib.I32Load(@lib.MemArg(_, _, @lib.U64(4UL))),
@lib.LocalSet(_),
@lib.LocalGet(@lib.LocalIdx(else_source0_raw)),
@lib.I32Load(@lib.MemArg(_, _, @lib.U64(0UL))),
@lib.LocalSet(_),
@lib.Br(else_label),
] if else_source4_raw == source_raw &&
else_source0_raw == source_raw &&
run_hot_pipeline_raw_vacuum_label_depth(else_label) >= 5 => ()
_ => return false
}
}
_ => return false
}
(source_raw, tag_raw)
}
_ => return false
}
source_raw != tag_raw &&
!run_hot_pipeline_raw_vacuum_body_targets_owner(non_nops, 0)
}
///|
fn run_hot_pipeline_raw_vacuum_body_has_f4665_renderer_continuation(
parent_body : Array[@lib.Instruction],
start_idx : Int32,
) -> Bool {
let sig : Array[@lib.Instruction] = []
let mut idx = start_idx + 1
while idx < parent_body.length() && sig.length() < 11 {
match parent_body[idx] {
@lib.Nop => ()
instr => sig.push(instr)
}
idx = idx + 1
}
match sig {
[
@lib.LocalGet(@lib.LocalIdx(renderer_raw)),
@lib.I32Const(@lib.I32(178120)),
@lib.LocalGet(@lib.LocalIdx(env_raw)),
@lib.I32Load(@lib.MemArg(_, _, @lib.U64(0UL))),
@lib.CallIndirect(_, _),
@lib.Drop,
@lib.LocalGet(@lib.LocalIdx(result_raw)),
@lib.LocalGet(@lib.LocalIdx(call_env_raw)),
@lib.LocalGet(@lib.LocalIdx(call_renderer_raw)),
@lib.Call(_),
@lib.LocalSet(_),
] =>
renderer_raw == call_renderer_raw &&
env_raw == call_env_raw &&
result_raw != renderer_raw &&
result_raw != env_raw
_ => false
}
}
///|
fn run_hot_pipeline_raw_vacuum_try_flatten_f4665_nested_tag_classification_before_renderer_continuation(
body : Array[@lib.Instruction],
parent_body : Array[@lib.Instruction],
start_idx : Int32,
) -> Array[@lib.Instruction]? {
if !run_hot_pipeline_raw_vacuum_body_has_f4665_renderer_continuation(
parent_body, start_idx,
) ||
body.length() != 1 {
return None
}
match body[0] {
@lib.Block(@lib.VoidBlockType, @lib.Expr(dispatch_body)) => {
if !run_hot_pipeline_raw_vacuum_body_is_f4665_tag2_56_classification_dispatch(
dispatch_body,
) {
return None
}
Some(
run_hot_pipeline_raw_vacuum_rebase_outer_body(
run_hot_pipeline_raw_vacuum_rebase_outer_body(dispatch_body, 0),
0,
),
)
}
_ => None
}
}
///|
fn run_hot_pipeline_raw_vacuum_f4702_selected_locals_from_then(
then_body : Array[@lib.Instruction],
) -> (Int32, Int32)? {
let body = run_hot_pipeline_raw_vacuum_strip_top_level_nops(then_body)
if body.length() < 10 {
return None
}
let mut saw_offset0 = false
let mut saw_offset4 = false
let mut saw_refcount_if = false
for instr in body {
match instr {
@lib.I32Load(@lib.MemArg(_, _, @lib.U64(0UL))) => saw_offset0 = true
@lib.I32Load(@lib.MemArg(_, _, @lib.U64(4UL))) => saw_offset4 = true
@lib.If(@lib.VoidBlockType, _, _) => saw_refcount_if = true
_ => ()
}
}
if saw_offset0 && saw_offset4 && saw_refcount_if {
Some((0, 1))
} else {
None
}
}
///|
fn run_hot_pipeline_raw_vacuum_f4702_selected_locals_from_call_result_child(
body : Array[@lib.Instruction],
) -> (Int32, Int32)? {
let non_nops = run_hot_pipeline_raw_vacuum_strip_top_level_nops(body)
if non_nops.length() == 0 {
return None
}
match non_nops[non_nops.length() - 1] {
@lib.If(@lib.VoidBlockType, then_body, Some(else_body)) => {
if !(run_hot_pipeline_raw_vacuum_f4702_selected_locals_from_then(
then_body,
)
is Some(_)) ||
!run_hot_pipeline_raw_vacuum_instrs_contain_unreachable(else_body) ||
!run_hot_pipeline_raw_instrs_have_branch(else_body) {
return None
}
let mut saw_call = false
let mut saw_tag_one = false
for idx = 0; idx + 1 < non_nops.length(); idx = idx + 1 {
match non_nops[idx] {
@lib.Call(_) | @lib.CallIndirect(_, _) | @lib.CallRef(_) =>
saw_call = true
@lib.I32Const(@lib.I32(1)) if non_nops[idx + 1] is @lib.I32Eq =>
saw_tag_one = true
_ => ()
}
}
if saw_call && saw_tag_one {
Some((0, 1))
} else {
None
}
}
_ => None
}
}
///|
fn run_hot_pipeline_raw_vacuum_try_flatten_f4702_call_result_child_before_store_continuation(
body : Array[@lib.Instruction],
) -> Array[@lib.Instruction]? {
if body.length() != 10 {
return None
}
let child_body = match body[0] {
@lib.Block(@lib.VoidBlockType, @lib.Expr(child_body)) => child_body
_ => return None
}
if !(run_hot_pipeline_raw_vacuum_f4702_selected_locals_from_call_result_child(
child_body,
)
is Some(_)) {
return None
}
match
(
body[1],
body[2],
body[3],
body[4],
body[5],
body[6],
body[7],
body[8],
body[9],
) {
(
@lib.LocalGet(_),
@lib.LocalGet(_),
@lib.Call(_),
@lib.LocalSet(_),
@lib.LocalGet(_),
@lib.Call(_),
@lib.LocalGet(_),
@lib.LocalGet(_),
@lib.I32Store(@lib.MemArg(_, _, @lib.U64(0UL))),
) => {
let flattened = run_hot_pipeline_raw_vacuum_rebase_outer_body(
child_body, 0,
)
for idx = 1; idx < body.length(); idx = idx + 1 {
flattened.push(body[idx])
}
Some(flattened)
}
_ => None
}
}
///|
fn run_hot_pipeline_raw_vacuum_body_is_f4463_offset20_call_result_tag1_prefix(
body : Array[@lib.Instruction],
) -> Bool {
let non_nops = run_hot_pipeline_raw_vacuum_strip_top_level_nops(body)
if non_nops.length() != 38 {
return false
}
let (loaded_raw, alloc_raw, call_result_raw, boxed_raw, tag_raw) = match
(
non_nops[0],
non_nops[1],
non_nops[2],
non_nops[3],
non_nops[4],
non_nops[5],
non_nops[6],
non_nops[7],
non_nops[8],
non_nops[9],
non_nops[10],
non_nops[11],
non_nops[12],
non_nops[13],
non_nops[14],
non_nops[15],
non_nops[16],
non_nops[17],
non_nops[18],
non_nops[19],
non_nops[20],
non_nops[21],
non_nops[22],
non_nops[23],
non_nops[24],
non_nops[25],
non_nops[26],
non_nops[27],
non_nops[28],
non_nops[29],
non_nops[30],
non_nops[31],
non_nops[32],
non_nops[33],
non_nops[34],
non_nops[35],
non_nops[36],
non_nops[37],
) {
(
@lib.LocalGet(@lib.LocalIdx(source_raw)),
@lib.I32Load(@lib.MemArg(_, _, @lib.U64(20UL))),
@lib.LocalSet(@lib.LocalIdx(loaded_raw)),
@lib.I32Const(@lib.I32(8)),
@lib.Call(_),
@lib.LocalTee(@lib.LocalIdx(alloc_raw)),
@lib.I32Const(@lib.I32(524544)),
@lib.Call(_),
@lib.LocalGet(@lib.LocalIdx(store0_alloc_raw)),
@lib.I32Const(@lib.I32(0)),
@lib.I32Store(_),
@lib.LocalGet(@lib.LocalIdx(store4_alloc_raw)),
@lib.I32Const(@lib.I32(10088)),
@lib.I32Store(@lib.MemArg(_, _, @lib.U64(4UL))),
@lib.LocalGet(@lib.LocalIdx(retain_loaded_raw)),
@lib.Call(_),
@lib.LocalGet(_),
@lib.LocalGet(@lib.LocalIdx(call_loaded_raw)),
@lib.LocalGet(@lib.LocalIdx(call_arg_raw)),
@lib.LocalGet(@lib.LocalIdx(call_alloc_raw)),
@lib.Call(@lib.FuncIdx(4553U)),
@lib.LocalTee(@lib.LocalIdx(boxed_raw)),
@lib.Call(@lib.FuncIdx(4509U)),
@lib.LocalSet(@lib.LocalIdx(call_result_raw)),
@lib.LocalGet(@lib.LocalIdx(release_loaded_raw)),
@lib.Call(_),
@lib.LocalGet(@lib.LocalIdx(release_arg_raw)),
@lib.Call(_),
@lib.LocalGet(@lib.LocalIdx(release_alloc_raw)),
@lib.Call(_),
@lib.LocalGet(@lib.LocalIdx(release_boxed_raw)),
@lib.Call(_),
@lib.LocalGet(@lib.LocalIdx(result_read_raw)),
@lib.LocalTee(@lib.LocalIdx(tag_raw)),
@lib.Call(_),
@lib.I32Const(@lib.I32(1)),
@lib.I32Eq,
@lib.If(@lib.VoidBlockType, then_body, Some(else_body)),
) => {
if source_raw == loaded_raw ||
loaded_raw != retain_loaded_raw ||
loaded_raw != call_loaded_raw ||
loaded_raw != release_loaded_raw ||
alloc_raw != store0_alloc_raw ||
alloc_raw != store4_alloc_raw ||
alloc_raw != call_alloc_raw ||
alloc_raw != release_alloc_raw ||
call_arg_raw != release_arg_raw ||
boxed_raw != release_boxed_raw ||
call_result_raw != result_read_raw ||
call_result_raw == tag_raw ||
then_body.length() == 0 ||
!run_hot_pipeline_raw_instrs_have_branch(else_body) {
return false
}
(loaded_raw, alloc_raw, call_result_raw, boxed_raw, tag_raw)
}
_ => return false
}
loaded_raw != alloc_raw &&
alloc_raw != boxed_raw &&
boxed_raw != tag_raw &&
call_result_raw != boxed_raw
}
///|
fn run_hot_pipeline_raw_vacuum_body_is_f4463_offset12_indexed_call_result_tag1_prefix(
body : Array[@lib.Instruction],
) -> Bool {
let non_nops = run_hot_pipeline_raw_vacuum_strip_top_level_nops(body)
if non_nops.length() != 56 {
return false
}
let (
loaded_raw,
retained_raw,
alloc_raw,
second_raw,
second_arg_raw,
boxed_raw,
result_raw,
tag_raw,
) = match
(
non_nops[0],
non_nops[1],
non_nops[2],
non_nops[3],
non_nops[4],
non_nops[5],
non_nops[6],
non_nops[7],
non_nops[8],
non_nops[9],
non_nops[10],
non_nops[11],
non_nops[12],
non_nops[13],
non_nops[14],
non_nops[15],
non_nops[16],
non_nops[17],
non_nops[18],
non_nops[19],
non_nops[20],
non_nops[21],
non_nops[22],
non_nops[23],
non_nops[24],
non_nops[25],
non_nops[26],
non_nops[27],
non_nops[28],
non_nops[29],
non_nops[30],
non_nops[31],
non_nops[32],
non_nops[33],
non_nops[34],
non_nops[35],
non_nops[36],
non_nops[37],
non_nops[38],
non_nops[39],
non_nops[40],
non_nops[41],
non_nops[42],
non_nops[43],
non_nops[44],
non_nops[45],
non_nops[46],
non_nops[47],
non_nops[48],
non_nops[49],
non_nops[50],
non_nops[51],
non_nops[52],
non_nops[53],
non_nops[54],
non_nops[55],
) {
(
@lib.LocalGet(@lib.LocalIdx(source_raw)),
@lib.I32Load(@lib.MemArg(_, _, @lib.U64(4UL))),
@lib.LocalGet(@lib.LocalIdx(index_raw)),
@lib.I32Const(@lib.I32(2)),
@lib.I32Shl,
@lib.I32Add,
@lib.I32Load(@lib.MemArg(_, _, @lib.U64(0UL))),
@lib.LocalTee(@lib.LocalIdx(loaded_raw)),
@lib.I32Load(@lib.MemArg(_, _, @lib.U64(12UL))),
@lib.LocalTee(@lib.LocalIdx(retained_raw)),
@lib.Call(_),
@lib.LocalGet(@lib.LocalIdx(release_loaded_raw)),
@lib.Call(_),
@lib.I32Const(@lib.I32(10088)),
@lib.I32Const(@lib.I32(0)),
@lib.I32Const(@lib.I32(0)),
@lib.I64Const(@lib.I64(4294967296L)),
@lib.Call(_),
@lib.LocalSet(@lib.LocalIdx(second_raw)),
@lib.I32Const(@lib.I32(10088)),
@lib.Call(_),
@lib.LocalGet(@lib.LocalIdx(second_get_raw)),
@lib.LocalSet(@lib.LocalIdx(call_second_raw)),
@lib.I32Const(@lib.I32(8)),
@lib.Call(_),
@lib.LocalTee(@lib.LocalIdx(alloc_raw)),
@lib.I32Const(@lib.I32(524544)),
@lib.Call(_),
@lib.LocalGet(@lib.LocalIdx(store0_alloc_raw)),
@lib.I32Const(@lib.I32(0)),
@lib.I32Store(_),
@lib.LocalGet(@lib.LocalIdx(store4_alloc_raw)),
@lib.I32Const(@lib.I32(10088)),
@lib.I32Store(@lib.MemArg(_, _, @lib.U64(4UL))),
@lib.LocalGet(_),
@lib.LocalGet(@lib.LocalIdx(call_retained_raw)),
@lib.LocalGet(@lib.LocalIdx(call_arg_second_raw)),
@lib.LocalGet(@lib.LocalIdx(call_alloc_raw)),
@lib.Call(@lib.FuncIdx(4553U)),
@lib.LocalTee(@lib.LocalIdx(boxed_raw)),
@lib.Call(@lib.FuncIdx(4509U)),
@lib.LocalSet(@lib.LocalIdx(result_raw)),
@lib.LocalGet(@lib.LocalIdx(release_retained_raw)),
@lib.Call(_),
@lib.LocalGet(@lib.LocalIdx(release_second_raw)),
@lib.Call(_),
@lib.LocalGet(@lib.LocalIdx(release_alloc_raw)),
@lib.Call(_),
@lib.LocalGet(@lib.LocalIdx(release_boxed_raw)),
@lib.Call(_),
@lib.LocalGet(@lib.LocalIdx(result_get_raw)),
@lib.LocalTee(@lib.LocalIdx(tag_raw)),
@lib.Call(_),
@lib.I32Const(@lib.I32(1)),
@lib.I32Eq,
@lib.If(@lib.VoidBlockType, then_body, Some(else_body)),
) => {
if source_raw == index_raw ||
loaded_raw != release_loaded_raw ||
second_raw != second_get_raw ||
call_second_raw != call_arg_second_raw ||
call_second_raw != release_second_raw ||
retained_raw != call_retained_raw ||
retained_raw != release_retained_raw ||
alloc_raw != store0_alloc_raw ||
alloc_raw != store4_alloc_raw ||
alloc_raw != call_alloc_raw ||
alloc_raw != release_alloc_raw ||
boxed_raw != release_boxed_raw ||
result_raw != result_get_raw ||
result_raw == tag_raw ||
then_body.length() == 0 ||
!run_hot_pipeline_raw_instrs_have_branch(else_body) {
return false
}
(
loaded_raw, retained_raw, alloc_raw, second_raw, call_second_raw, boxed_raw,
result_raw, tag_raw,
)
}
_ => return false
}
loaded_raw != retained_raw &&
retained_raw != alloc_raw &&
alloc_raw != boxed_raw &&
boxed_raw != tag_raw &&
second_raw != second_arg_raw &&
second_raw != result_raw
}
///|
fn run_hot_pipeline_raw_vacuum_try_flatten_f4463_offset12_indexed_singleton_owner(
body : Array[@lib.Instruction],
) -> Array[@lib.Instruction]? {
if body.length() != 1 {
return None
}
match body[0] {
@lib.Block(@lib.VoidBlockType, @lib.Expr(child_body)) if run_hot_pipeline_raw_vacuum_body_is_f4463_offset12_indexed_call_result_tag1_prefix(
child_body,
) => Some(run_hot_pipeline_raw_vacuum_rebase_outer_body(child_body, 0))
_ => None
}
}
///|
fn run_hot_pipeline_raw_vacuum_body_has_f4463_failure_box_return_continuation(
parent_body : Array[@lib.Instruction],
start_idx : Int32,
) -> Bool {
let sig : Array[@lib.Instruction] = []
let mut idx = start_idx + 1
while idx < parent_body.length() && sig.length() < 10 {
match parent_body[idx] {
@lib.Nop => ()
instr => sig.push(instr)
}
idx = idx + 1
}
match sig {
[
@lib.I32Const(@lib.I32(4)),
@lib.Call(_),
@lib.LocalTee(@lib.LocalIdx(box_raw)),
@lib.I32Const(@lib.I32(256)),
@lib.Call(_),
@lib.LocalGet(@lib.LocalIdx(store_box_raw)),
@lib.LocalGet(_),
@lib.I32Store(_),
@lib.LocalGet(@lib.LocalIdx(return_box_raw)),
@lib.Return,
] => box_raw == store_box_raw && box_raw == return_box_raw
_ => false
}
}
///|
fn run_hot_pipeline_raw_vacuum_try_flatten_f4463_offset20_owner_before_failure_box_return(
body : Array[@lib.Instruction],
parent_body : Array[@lib.Instruction],
start_idx : Int32,
) -> Array[@lib.Instruction]? {
if body.length() != 4 ||
!run_hot_pipeline_raw_vacuum_body_has_f4463_failure_box_return_continuation(
parent_body, start_idx,
) {
return None
}
let prefix_block = match body[0] {
@lib.Block(
@lib.VoidBlockType,
@lib.Expr([@lib.Block(@lib.VoidBlockType, @lib.Expr(prefix_body))])
) if run_hot_pipeline_raw_vacuum_body_is_f4463_offset20_call_result_tag1_prefix(
prefix_body,
) => {
let rebased = run_hot_pipeline_raw_vacuum_rebase_outer_body(
[
@lib.Instruction::block(
@lib.BlockType::void_(),
@lib.Expr::new(prefix_body),
),
],
0,
)
match rebased {
[rebased_prefix] => rebased_prefix
_ => return None
}
}
_ => return None
}
match (body[1], body[2], body[3]) {
(
@lib.LocalGet(@lib.LocalIdx(value_raw)),
@lib.LocalSet(@lib.LocalIdx(target_raw)),
@lib.Br(@lib.LabelIdx(1U)),
) if value_raw != target_raw =>
Some([prefix_block, body[1], body[2], body[3]])
_ => None
}
}
///|
fn run_hot_pipeline_raw_vacuum_body_is_f1759_tag2_prefix_preserve_candidate(
body : Array[@lib.Instruction],
) -> Bool {
if body.length() != 16 ||
run_hot_pipeline_raw_vacuum_instrs_contain_unreachable(body) {
return false
}
match
(
body[0],
body[1],
body[2],
body[3],
body[4],
body[5],
body[6],
body[7],
body[8],
body[9],
body[10],
body[11],
body[12],
body[13],
body[14],
body[15],
) {
(
@lib.LocalGet(@lib.LocalIdx(owner_raw)),
@lib.I32Load(_),
@lib.LocalTee(@lib.LocalIdx(loaded_raw)),
@lib.Call(_),
@lib.LocalGet(@lib.LocalIdx(next_loaded_raw)),
@lib.I32Const(@lib.I32(0)),
@lib.Call(_),
@lib.LocalSet(@lib.LocalIdx(parsed_raw)),
@lib.LocalGet(@lib.LocalIdx(release_raw)),
@lib.Call(_),
@lib.LocalGet(@lib.LocalIdx(next_parsed_raw)),
@lib.LocalTee(@lib.LocalIdx(tagged_raw)),
@lib.I32Load(_),
@lib.I32Const(@lib.I32(2)),
@lib.I32Eq,
@lib.If(@lib.VoidBlockType, then_body, Some(else_body)),
) =>
owner_raw != loaded_raw &&
loaded_raw == next_loaded_raw &&
loaded_raw == release_raw &&
parsed_raw == next_parsed_raw &&
parsed_raw != tagged_raw &&
then_body.length() > 0 &&
else_body.length() > 0 &&
run_hot_pipeline_raw_vacuum_body_has_top_level_outer_branch(else_body)
_ => false
}
}
///|
fn run_hot_pipeline_raw_vacuum_body_has_f1759_counter_store_continuation(
body : Array[@lib.Instruction],
start_idx : Int32,
) -> Bool {
let sig : Array[@lib.Instruction] = []
for idx = start_idx + 1; idx < body.length(); idx = idx + 1 {
match body[idx] {
@lib.Nop => ()
instr => sig.push(instr)
}
if sig.length() >= 12 {
break
}
}
if sig.length() < 12 {
return false
}
match
(
sig[0],
sig[1],
sig[2],
sig[3],
sig[4],
sig[5],
sig[6],
sig[7],
sig[8],
sig[9],
sig[10],
sig[11],
) {
(
@lib.LocalGet(@lib.LocalIdx(counter_raw)),
@lib.LocalGet(@lib.LocalIdx(next_counter_raw)),
@lib.I32Load(_),
@lib.I32Const(@lib.I32(1)),
@lib.I32Add,
@lib.I32Store(_),
@lib.LocalGet(@lib.LocalIdx(slot_raw)),
@lib.I32Load(_),
@lib.Call(_),
@lib.LocalGet(@lib.LocalIdx(next_slot_raw)),
@lib.LocalGet(_),
@lib.I32Store(_),
) => counter_raw == next_counter_raw && slot_raw == next_slot_raw
_ => false
}
}
///|
fn run_hot_pipeline_raw_vacuum_body_is_f1759_tag2_prefix_before_counter_store_preserve_candidate(
body : Array[@lib.Instruction],
parent_body : Array[@lib.Instruction],
start_idx : Int32,
) -> Bool {
run_hot_pipeline_raw_vacuum_body_is_f1759_tag2_prefix_preserve_candidate(body) &&
run_hot_pipeline_raw_vacuum_body_has_f1759_counter_store_continuation(
parent_body, start_idx,
)
}
///|
fn run_hot_pipeline_raw_vacuum_body_is_f1054_local_copy_load_prefix_child(
body : Array[@lib.Instruction],
) -> Bool {
if body.length() != 2 {
return false
}
match (body[0], body[1]) {
(
@lib.LocalGet(@lib.LocalIdx(source_raw)),
@lib.LocalSet(@lib.LocalIdx(target_raw)),
) => source_raw != target_raw
_ => false
}
}
///|
fn run_hot_pipeline_raw_vacuum_body_is_f1054_load_prefix_continuation(
body : Array[@lib.Instruction],
) -> Bool {
if body.length() < 17 {
return false
}
let loaded_raw = match (body[5], body[6], body[7]) {
(@lib.LocalTee(@lib.LocalIdx(raw)), @lib.I32Load(_), @lib.LocalSet(_)) =>
raw
_ => return false
}
match (body[8], body[9], body[10], body[11]) {
(
@lib.LocalGet(@lib.LocalIdx(next_raw)),
@lib.I32Load(_),
@lib.LocalSet(_),
@lib.LocalGet(@lib.LocalIdx(call_base_raw)),
) if next_raw == loaded_raw && call_base_raw == loaded_raw => ()
_ => return false
}
match
(
body[0],
body[1],
body[2],
body[3],
body[4],
body[12],
body[13],
body[14],
body[15],
body[16],
) {
(
@lib.LocalGet(_),
@lib.LocalTee(_),
@lib.LocalGet(_),
@lib.LocalTee(_),
@lib.Call(_),
@lib.Call(_),
@lib.LocalTee(_),
@lib.I32Const(@lib.I32(1)),
@lib.I32GtS,
@lib.If(@lib.VoidBlockType, _, _),
) => true
_ => false
}
}
///|
fn run_hot_pipeline_raw_vacuum_try_flatten_f1054_local_copy_child_before_load_prefix(
body : Array[@lib.Instruction],
) -> Array[@lib.Instruction]? {
if body.length() < 18 {
return None
}
let copy_body = match body[0] {
@lib.Block(@lib.VoidBlockType, @lib.Expr(copy_body)) => copy_body
_ => return None
}
if !run_hot_pipeline_raw_vacuum_body_is_f1054_local_copy_load_prefix_child(
copy_body,
) {
return None
}
let continuation : Array[@lib.Instruction] = []
for idx = 1; idx < body.length(); idx = idx + 1 {
continuation.push(body[idx])
}
if !run_hot_pipeline_raw_vacuum_body_is_f1054_load_prefix_continuation(
continuation,
) {
return None
}
let flattened = run_hot_pipeline_raw_vacuum_rebase_outer_body(copy_body, 0)
for instr in continuation {
flattened.push(instr)
}
Some(flattened)
}
///|
fn run_hot_pipeline_raw_vacuum_body_sets_local(
body : Array[@lib.Instruction],
target_raw : UInt32,
) -> Bool {
for instr in body {
match instr {
@lib.LocalSet(@lib.LocalIdx(raw)) if raw == target_raw => return true
@lib.Block(_, @lib.Expr(child_body))
| @lib.Loop(_, @lib.Expr(child_body)) =>
if run_hot_pipeline_raw_vacuum_body_sets_local(child_body, target_raw) {
return true
}
@lib.If(_, then_body, else_body) => {
if run_hot_pipeline_raw_vacuum_body_sets_local(then_body, target_raw) {
return true
}
match else_body {
Some(child_body) =>
if run_hot_pipeline_raw_vacuum_body_sets_local(
child_body, target_raw,
) {
return true
}
None => ()
}
}
@lib.TryTable(_, _, @lib.Expr(child_body)) =>
if run_hot_pipeline_raw_vacuum_body_sets_local(child_body, target_raw) {
return true
}
_ => ()
}
}
false
}
///|
fn run_hot_pipeline_raw_vacuum_body_ends_with_outer_br(
body : Array[@lib.Instruction],
min_depth : Int32,
) -> Bool {
if body.length() == 0 {
return false
}
match body[body.length() - 1] {
@lib.Br(label) =>
run_hot_pipeline_raw_vacuum_label_depth(label) >= min_depth
_ => false
}
}
///|
fn run_hot_pipeline_raw_vacuum_body_is_f1148_tag_selection(
body : Array[@lib.Instruction],
continuation_raw : UInt32,
) -> Bool {
if body.length() != 6 {
return false
}
let (receiver_raw, tag_raw) = match
(body[0], body[1], body[2], body[3], body[4]) {
(
@lib.LocalGet(@lib.LocalIdx(receiver_raw)),
@lib.Call(_),
@lib.LocalTee(@lib.LocalIdx(tag_raw)),
@lib.I32Const(@lib.I32(2)),
@lib.I32Eq,
) => (receiver_raw, tag_raw)
_ => return false
}
match body[5] {
@lib.If(@lib.VoidBlockType, then_body, Some(else_body)) => {
if !run_hot_pipeline_raw_vacuum_body_sets_local(
then_body, continuation_raw,
) {
return false
}
if else_body.length() < 5 ||
!(else_body[else_body.length() - 1] is @lib.Unreachable) {
return false
}
match (else_body[0], else_body[1], else_body[2], else_body[3]) {
(
@lib.LocalGet(@lib.LocalIdx(next_tag_raw)),
@lib.I32Const(@lib.I32(1)),
@lib.I32Eq,
@lib.If(@lib.VoidBlockType, nested_then, Some(nested_else)),
) if next_tag_raw == tag_raw => {
if nested_then.length() < 5 || nested_else.length() < 5 {
return false
}
match (nested_then[0], nested_then[1], nested_then[2]) {
(
@lib.LocalGet(@lib.LocalIdx(next_receiver_raw)),
@lib.I32Load(_),
@lib.LocalTee(_),
) if next_receiver_raw == receiver_raw => ()
_ => return false
}
match (nested_else[0], nested_else[1], nested_else[2]) {
(
@lib.LocalGet(@lib.LocalIdx(next_receiver_raw)),
@lib.I32Load(_),
@lib.LocalTee(_),
) if next_receiver_raw == receiver_raw => ()
_ => return false
}
run_hot_pipeline_raw_vacuum_body_ends_with_outer_br(nested_then, 2) &&
run_hot_pipeline_raw_vacuum_body_ends_with_outer_br(nested_else, 2)
}
_ => false
}
}
_ => false
}
}
///|
fn run_hot_pipeline_raw_vacuum_try_flatten_f1148_tag_selection_wrappers_before_load_continuation(
body : Array[@lib.Instruction],
) -> Array[@lib.Instruction]? {
if body.length() < 4 {
return None
}
let continuation_raw = match (body[1], body[2], body[3]) {
(@lib.LocalGet(@lib.LocalIdx(raw)), @lib.I32Load(_), @lib.LocalSet(_)) =>
raw
_ => return None
}
let middle_body = match body[0] {
@lib.Block(@lib.VoidBlockType, @lib.Expr(middle_body)) => middle_body
_ => return None
}
if middle_body.length() != 1 {
return None
}
let selection_body = match middle_body[0] {
@lib.Block(@lib.VoidBlockType, @lib.Expr(selection_body)) => selection_body
_ => return None
}
if !run_hot_pipeline_raw_vacuum_body_is_f1148_tag_selection(
selection_body, continuation_raw,
) {
return None
}
let rebased_once = run_hot_pipeline_raw_vacuum_rebase_outer_body(
selection_body, 0,
)
let flattened = run_hot_pipeline_raw_vacuum_rebase_outer_body(rebased_once, 0)
for idx = 1; idx < body.length(); idx = idx + 1 {
flattened.push(body[idx])
}
Some(flattened)
}
///|
fn run_hot_pipeline_raw_vacuum_br_table_targets_outer_only(
labels : Array[@lib.LabelIdx],
fallback : @lib.LabelIdx,
) -> Bool {
if run_hot_pipeline_raw_vacuum_label_depth(fallback) < 1 {
return false
}
for label in labels {
if run_hot_pipeline_raw_vacuum_label_depth(label) < 1 {
return false
}
}
true
}
///|
fn run_hot_pipeline_raw_vacuum_body_is_f1167_br_table_child_before_unreachable(
body : Array[@lib.Instruction],
) -> Bool {
if body.length() != 2 || !(body[1] is @lib.Unreachable) {
return false
}
let child_body = match body[0] {
@lib.Block(@lib.VoidBlockType, @lib.Expr(child_body)) => child_body
_ => return false
}
if child_body.length() == 0 {
return false
}
if child_body.length() > 1 &&
run_hot_pipeline_raw_vacuum_body_targets_owner(
child_body[0:child_body.length() - 1].to_owned(),
0,
) {
return false
}
match child_body[child_body.length() - 1] {
@lib.BrTable(labels, fallback) =>
run_hot_pipeline_raw_vacuum_br_table_targets_outer_only(labels, fallback)
_ => false
}
}
///|
fn run_hot_pipeline_raw_vacuum_try_flatten_f1167_br_table_child_before_unreachable(
body : Array[@lib.Instruction],
) -> Array[@lib.Instruction]? {
if !run_hot_pipeline_raw_vacuum_body_is_f1167_br_table_child_before_unreachable(
body,
) {
return None
}
match body[0] {
@lib.Block(@lib.VoidBlockType, @lib.Expr(child_body)) =>
Some(run_hot_pipeline_raw_vacuum_rebase_outer_body(child_body, 0))
_ => None
}
}
///|
fn run_hot_pipeline_raw_vacuum_body_count_br_tables(
body : Array[@lib.Instruction],
) -> Int32 {
let mut count = 0
for instr in body {
match instr {
@lib.BrTable(_, _) => count = count + 1
@lib.Block(_, @lib.Expr(child_body))
| @lib.Loop(_, @lib.Expr(child_body))
| @lib.TryTable(_, _, @lib.Expr(child_body)) =>
count = count +
run_hot_pipeline_raw_vacuum_body_count_br_tables(child_body)
@lib.If(_, then_body, else_body) => {
count = count +
run_hot_pipeline_raw_vacuum_body_count_br_tables(then_body)
match else_body {
Some(child_body) =>
count = count +
run_hot_pipeline_raw_vacuum_body_count_br_tables(child_body)
None => ()
}
}
_ => ()
}
}
count
}
///|
fn run_hot_pipeline_raw_vacuum_body_has_only_f1792_dispatch_return_instrs(
body : Array[@lib.Instruction],
) -> Bool {
for instr in body {
match instr {
@lib.Block(@lib.VoidBlockType, @lib.Expr(child_body)) =>
if !run_hot_pipeline_raw_vacuum_body_has_only_f1792_dispatch_return_instrs(
child_body,
) {
return false
}
@lib.LocalGet(_)
| @lib.LocalSet(_)
| @lib.LocalTee(_)
| @lib.I32Load(_)
| @lib.I32Const(_)
| @lib.I32Sub
| @lib.Call(_)
| @lib.Br(_)
| @lib.BrTable(_, _)
| @lib.Return => ()
_ => return false
}
}
true
}
///|
fn run_hot_pipeline_raw_vacuum_body_leading_void_block_depth(
body : Array[@lib.Instruction],
) -> Int32 {
if body.length() == 0 {
return 0
}
match body[0] {
@lib.Block(@lib.VoidBlockType, @lib.Expr(child_body)) =>
1 + run_hot_pipeline_raw_vacuum_body_leading_void_block_depth(child_body)
_ => 0
}
}
///|
fn run_hot_pipeline_raw_vacuum_body_is_f1792_br_table_dispatch_return_wrapper(
body : Array[@lib.Instruction],
) -> Bool {
if body.length() != 1 {
return false
}
let top_body = match body[0] {
@lib.Block(@lib.VoidBlockType, @lib.Expr(top_body)) => top_body
_ => return false
}
if top_body.length() != 3 {
return false
}
match (top_body[1], top_body[2]) {
(@lib.I32Const(@lib.I32(0)), @lib.Return) => ()
_ => return false
}
run_hot_pipeline_raw_vacuum_body_leading_void_block_depth(top_body) >= 8 &&
run_hot_pipeline_raw_vacuum_body_count_br_tables(top_body) == 1 &&
run_hot_pipeline_raw_vacuum_body_has_only_f1792_dispatch_return_instrs(
top_body,
)
}
///|
fn run_hot_pipeline_raw_vacuum_body_has_f1792_merge_continuation(
body : Array[@lib.Instruction],
start_idx : Int32,
) -> Bool {
let sig : Array[@lib.Instruction] = []
for idx = start_idx + 1; idx < body.length(); idx = idx + 1 {
match body[idx] {
@lib.Nop => ()
instr => sig.push(instr)
}
if sig.length() >= 3 {
break
}
}
match sig {
[@lib.LocalGet(_), @lib.LocalSet(_), @lib.Br(label)] =>
run_hot_pipeline_raw_vacuum_label_depth(label) >= 1
_ => false
}
}
///|
fn run_hot_pipeline_raw_vacuum_try_flatten_f1792_br_table_dispatch_return_wrapper(
body : Array[@lib.Instruction],
) -> Array[@lib.Instruction]? {
if body.length() < 4 ||
!run_hot_pipeline_raw_vacuum_body_has_f1792_merge_continuation(body, 0) {
return None
}
let wrapper_body = match body[0] {
@lib.Block(@lib.VoidBlockType, @lib.Expr(wrapper_body)) => wrapper_body
_ => return None
}
if !run_hot_pipeline_raw_vacuum_body_is_f1792_br_table_dispatch_return_wrapper(
wrapper_body,
) {
return None
}
let flattened = run_hot_pipeline_raw_vacuum_rebase_outer_body(wrapper_body, 0)
for idx = 1; idx < body.length(); idx = idx + 1 {
flattened.push(body[idx])
}
Some(flattened)
}
///|
fn run_hot_pipeline_raw_vacuum_body_starts_f1836_tag20_fetch_cascade(
body : Array[@lib.Instruction],
source_raw : UInt32,
slot_raw : UInt32,
) -> Bool {
if body.length() < 24 {
return false
}
let (first_raw, call_raw) = match
(body[0], body[1], body[2], body[3], body[4]) {
(
@lib.LocalGet(@lib.LocalIdx(next_source_raw)),
@lib.LocalGet(@lib.LocalIdx(next_slot_raw)),
@lib.I32Load(_),
@lib.Call(@lib.FuncIdx(call_raw)),
@lib.LocalSet(@lib.LocalIdx(first_raw)),
) if next_source_raw == source_raw && next_slot_raw == slot_raw =>
(first_raw, call_raw)
_ => return false
}
let second_raw = match
(body[5], body[6], body[7], body[8], body[9], body[10], body[11]) {
(
@lib.LocalGet(@lib.LocalIdx(next_source_raw)),
@lib.LocalGet(@lib.LocalIdx(next_slot_raw)),
@lib.I32Load(_),
@lib.I32Const(@lib.I32(1)),
@lib.I32Add,
@lib.Call(@lib.FuncIdx(next_call_raw)),
@lib.LocalSet(@lib.LocalIdx(raw)),
) if next_source_raw == source_raw &&
next_slot_raw == slot_raw &&
next_call_raw == call_raw => raw
_ => return false
}
let third_raw = match
(body[12], body[13], body[14], body[15], body[16], body[17], body[18]) {
(
@lib.LocalGet(@lib.LocalIdx(next_source_raw)),
@lib.LocalGet(@lib.LocalIdx(next_slot_raw)),
@lib.I32Load(_),
@lib.I32Const(@lib.I32(2)),
@lib.I32Add,
@lib.Call(@lib.FuncIdx(next_call_raw)),
@lib.LocalSet(@lib.LocalIdx(raw)),
) if next_source_raw == source_raw &&
next_slot_raw == slot_raw &&
next_call_raw == call_raw => raw
_ => return false
}
if first_raw == second_raw ||
first_raw == third_raw ||
second_raw == third_raw {
return false
}
match (body[19], body[20], body[21], body[22], body[23]) {
(
@lib.LocalGet(@lib.LocalIdx(next_first_raw)),
@lib.I32Load(_),
@lib.I32Const(@lib.I32(20)),
@lib.I32Eq,
@lib.If(@lib.VoidBlockType, then_body, Some(else_body)),
) =>
next_first_raw == first_raw &&
then_body.length() > 0 &&
else_body.length() > 0
_ => false
}
}
///|
fn run_hot_pipeline_raw_vacuum_body_has_f1836_tag20_fetch_cascade(
body : Array[@lib.Instruction],
source_raw : UInt32,
slot_raw : UInt32,
) -> Bool {
if run_hot_pipeline_raw_vacuum_body_starts_f1836_tag20_fetch_cascade(
body, source_raw, slot_raw,
) {
return true
}
for idx = 0; idx + 4 < body.length(); idx = idx + 1 {
match
(body[idx], body[idx + 1], body[idx + 2], body[idx + 3], body[idx + 4]) {
(
@lib.LocalGet(_),
@lib.I32Load(_),
@lib.I32Const(@lib.I32(20)),
@lib.I32Eq,
@lib.If(@lib.VoidBlockType, then_body, Some(else_body)),
) if then_body.length() > 0 && else_body.length() > 0 => return true
_ => ()
}
}
for instr in body {
match instr {
@lib.Block(@lib.VoidBlockType, @lib.Expr(child_body)) =>
if run_hot_pipeline_raw_vacuum_body_has_f1836_tag20_fetch_cascade(
child_body, source_raw, slot_raw,
) {
return true
}
_ => ()
}
}
false
}
///|
fn run_hot_pipeline_raw_vacuum_body_is_f1836_loop_if_owner_branch_wrapper(
body : Array[@lib.Instruction],
) -> Bool {
if body.length() < 8 {
return false
}
for idx = 0; idx + 7 < body.length(); idx = idx + 1 {
let (slot_raw, source_raw) = match
(
body[idx],
body[idx + 1],
body[idx + 2],
body[idx + 3],
body[idx + 4],
body[idx + 5],
body[idx + 6],
) {
(
@lib.LocalGet(@lib.LocalIdx(slot_raw)),
@lib.I32Load(_),
@lib.I32Const(@lib.I32(2)),
@lib.I32Add,
@lib.LocalGet(@lib.LocalIdx(source_raw)),
@lib.Call(_),
@lib.I32LtS,
) if slot_raw != source_raw => (slot_raw, source_raw)
_ => continue
}
match body[idx + 7] {
@lib.If(@lib.VoidBlockType, then_body, Some(else_body)) =>
if then_body.length() > 0 &&
else_body.length() > 0 &&
idx + 8 == body.length() &&
run_hot_pipeline_raw_vacuum_body_has_f1836_tag20_fetch_cascade(
then_body, source_raw, slot_raw,
) {
return true
}
_ => ()
}
}
false
}
///|
fn run_hot_pipeline_raw_vacuum_try_flatten_f1836_loop_if_owner_branch_wrapper(
body : Array[@lib.Instruction],
) -> Array[@lib.Instruction]? {
if body.length() != 1 {
return None
}
let wrapper_body = match body[0] {
@lib.Block(@lib.VoidBlockType, @lib.Expr(wrapper_body)) => wrapper_body
_ => return None
}
if !run_hot_pipeline_raw_vacuum_body_is_f1836_loop_if_owner_branch_wrapper(
wrapper_body,
) {
return None
}
Some(run_hot_pipeline_raw_vacuum_rebase_outer_body(wrapper_body, 0))
}
///|
fn run_hot_pipeline_raw_vacuum_body_contains_f1940_selector(
body : Array[@lib.Instruction],
probe_raw : UInt32,
) -> Bool {
for idx = 0; idx + 5 < body.length(); idx = idx + 1 {
match
(
body[idx],
body[idx + 1],
body[idx + 2],
body[idx + 3],
body[idx + 4],
body[idx + 5],
) {
(
@lib.LocalGet(@lib.LocalIdx(next_probe_raw)),
@lib.LocalTee(_),
@lib.I32Load(_),
@lib.I32Const(@lib.I32(231)),
@lib.I32Sub,
@lib.BrTable(_, _),
) if next_probe_raw == probe_raw => return true
_ => ()
}
}
for instr in body {
match instr {
@lib.Block(_, @lib.Expr(child_body))
| @lib.Loop(_, @lib.Expr(child_body))
| @lib.TryTable(_, _, @lib.Expr(child_body)) =>
if run_hot_pipeline_raw_vacuum_body_contains_f1940_selector(
child_body, probe_raw,
) {
return true
}
@lib.If(_, then_body, else_body) => {
if run_hot_pipeline_raw_vacuum_body_contains_f1940_selector(
then_body, probe_raw,
) {
return true
}
match else_body {
Some(child_body) =>
if run_hot_pipeline_raw_vacuum_body_contains_f1940_selector(
child_body, probe_raw,
) {
return true
}
None => ()
}
}
_ => ()
}
}
false
}
///|
fn run_hot_pipeline_raw_vacuum_body_is_f1940_cleanup_else(
body : Array[@lib.Instruction],
probe_raw : UInt32,
) -> Bool {
let non_nops : Array[@lib.Instruction] = []
for instr in body {
match instr {
@lib.Nop => ()
_ => non_nops.push(instr)
}
}
if non_nops.length() < 7 {
return false
}
match (non_nops[0], non_nops[1]) {
(
@lib.LocalGet(@lib.LocalIdx(next_probe_raw)),
@lib.If(@lib.VoidBlockType, _, None),
) if next_probe_raw == probe_raw => ()
_ => return false
}
match non_nops[non_nops.length() - 1] {
@lib.Br(label) if run_hot_pipeline_raw_vacuum_label_depth(label) >= 1 => ()
_ => return false
}
let mut release_call_pairs = 0
for idx = 2; idx + 1 < non_nops.length(); idx = idx + 1 {
match (non_nops[idx], non_nops[idx + 1]) {
(@lib.LocalGet(_), @lib.Call(_)) =>
release_call_pairs = release_call_pairs + 1
_ => ()
}
}
release_call_pairs >= 2
}
///|
fn run_hot_pipeline_raw_vacuum_body_is_f1940_call_tee_br_table_wrapper(
body : Array[@lib.Instruction],
) -> Bool {
let non_nops : Array[@lib.Instruction] = []
for instr in body {
match instr {
@lib.Nop => ()
_ => non_nops.push(instr)
}
}
if non_nops.length() != body.length() {
return run_hot_pipeline_raw_vacuum_body_is_f1940_call_tee_br_table_wrapper(
non_nops,
)
}
if body.length() != 6 || !(body[5] is @lib.Unreachable) {
return false
}
let probe_raw = match (body[0], body[1], body[2], body[3]) {
(
@lib.LocalGet(_),
@lib.LocalGet(_),
@lib.Call(_),
@lib.LocalTee(@lib.LocalIdx(probe_raw)),
) => probe_raw
_ => return false
}
match body[4] {
@lib.If(@lib.VoidBlockType, then_body, Some(else_body)) =>
run_hot_pipeline_raw_vacuum_body_count_br_tables(then_body) == 1 &&
run_hot_pipeline_raw_vacuum_body_contains_f1940_selector(
then_body, probe_raw,
) &&
run_hot_pipeline_raw_vacuum_instrs_contain_unreachable(then_body) &&
run_hot_pipeline_raw_vacuum_body_is_f1940_cleanup_else(
else_body, probe_raw,
) &&
!run_hot_pipeline_raw_vacuum_body_targets_owner(body, 0)
_ => false
}
}
///|
fn run_hot_pipeline_raw_vacuum_try_flatten_f1940_call_tee_br_table_wrapper_before_unreachable(
body : Array[@lib.Instruction],
) -> Array[@lib.Instruction]? {
if body.length() != 2 || !(body[1] is @lib.Unreachable) {
return None
}
let child_body = match body[0] {
@lib.Block(@lib.VoidBlockType, @lib.Expr(child_body)) => child_body
_ => return None
}
if !run_hot_pipeline_raw_vacuum_body_is_f1940_call_tee_br_table_wrapper(
child_body,
) {
return None
}
Some(run_hot_pipeline_raw_vacuum_rebase_outer_body(child_body, 0))
}
///|
fn run_hot_pipeline_raw_vacuum_body_contains_f1956_selector(
body : Array[@lib.Instruction],
probe_raw : UInt32,
) -> Bool {
for idx = 0; idx + 5 < body.length(); idx = idx + 1 {
match
(
body[idx],
body[idx + 1],
body[idx + 2],
body[idx + 3],
body[idx + 4],
body[idx + 5],
) {
(
@lib.LocalGet(@lib.LocalIdx(next_probe_raw)),
@lib.LocalTee(_),
@lib.I32Load(_),
@lib.I32Const(@lib.I32(224)),
@lib.I32Sub,
@lib.BrTable(_, _),
) if next_probe_raw == probe_raw => return true
_ => ()
}
}
for instr in body {
match instr {
@lib.Block(_, @lib.Expr(child_body))
| @lib.Loop(_, @lib.Expr(child_body))
| @lib.TryTable(_, _, @lib.Expr(child_body)) =>
if run_hot_pipeline_raw_vacuum_body_contains_f1956_selector(
child_body, probe_raw,
) {
return true
}
@lib.If(_, then_body, else_body) => {
if run_hot_pipeline_raw_vacuum_body_contains_f1956_selector(
then_body, probe_raw,
) {
return true
}
match else_body {
Some(child_body) =>
if run_hot_pipeline_raw_vacuum_body_contains_f1956_selector(
child_body, probe_raw,
) {
return true
}
None => ()
}
}
_ => ()
}
}
false
}
///|
fn run_hot_pipeline_raw_vacuum_body_contains_f2054_selector(
body : Array[@lib.Instruction],
probe_raw : UInt32,
) -> Bool {
for idx = 0; idx + 5 < body.length(); idx = idx + 1 {
match
(
body[idx],
body[idx + 1],
body[idx + 2],
body[idx + 3],
body[idx + 4],
body[idx + 5],
) {
(
@lib.LocalGet(@lib.LocalIdx(next_probe_raw)),
@lib.LocalTee(_),
@lib.I32Load(_),
@lib.I32Const(@lib.I32(236)),
@lib.I32Sub,
@lib.BrTable(_, _),
) if next_probe_raw == probe_raw => return true
_ => ()
}
}
for instr in body {
match instr {
@lib.Block(_, @lib.Expr(child_body))
| @lib.Loop(_, @lib.Expr(child_body))
| @lib.TryTable(_, _, @lib.Expr(child_body)) =>
if run_hot_pipeline_raw_vacuum_body_contains_f2054_selector(
child_body, probe_raw,
) {
return true
}
@lib.If(_, then_body, else_body) => {
if run_hot_pipeline_raw_vacuum_body_contains_f2054_selector(
then_body, probe_raw,
) {
return true
}
match else_body {
Some(child_body) =>
if run_hot_pipeline_raw_vacuum_body_contains_f2054_selector(
child_body, probe_raw,
) {
return true
}
None => ()
}
}
_ => ()
}
}
false
}
///|
fn run_hot_pipeline_raw_vacuum_body_is_f1956_cleanup_else(
body : Array[@lib.Instruction],
probe_raw : UInt32,
) -> Bool {
let non_nops : Array[@lib.Instruction] = []
for instr in body {
match instr {
@lib.Nop => ()
_ => non_nops.push(instr)
}
}
if non_nops.length() < 3 {
return false
}
match (non_nops[0], non_nops[1]) {
(
@lib.LocalGet(@lib.LocalIdx(next_probe_raw)),
@lib.If(@lib.VoidBlockType, then_body, None),
) if next_probe_raw == probe_raw && then_body.length() >= 2 => ()
_ => return false
}
match non_nops[non_nops.length() - 1] {
@lib.Br(label) if run_hot_pipeline_raw_vacuum_label_depth(label) >= 1 =>
true
_ => false
}
}
///|
fn run_hot_pipeline_raw_vacuum_body_is_f1956_call_tee_br_table_wrapper(
body : Array[@lib.Instruction],
) -> Bool {
let non_nops : Array[@lib.Instruction] = []
for instr in body {
match instr {
@lib.Nop => ()
_ => non_nops.push(instr)
}
}
if non_nops.length() != body.length() {
return run_hot_pipeline_raw_vacuum_body_is_f1956_call_tee_br_table_wrapper(
non_nops,
)
}
if body.length() != 6 || !(body[5] is @lib.Unreachable) {
return false
}
let probe_raw = match (body[0], body[1], body[2], body[3]) {
(
@lib.LocalGet(_),
@lib.LocalGet(_),
@lib.Call(_),
@lib.LocalTee(@lib.LocalIdx(probe_raw)),
) => probe_raw
_ => return false
}
match body[4] {
@lib.If(@lib.VoidBlockType, then_body, Some(else_body)) =>
run_hot_pipeline_raw_vacuum_body_count_br_tables(then_body) == 1 &&
run_hot_pipeline_raw_vacuum_body_contains_f1956_selector(
then_body, probe_raw,
) &&
run_hot_pipeline_raw_vacuum_body_is_f1956_cleanup_else(
else_body, probe_raw,
) &&
!run_hot_pipeline_raw_vacuum_body_targets_owner(body, 0)
_ => false
}
}
///|
fn run_hot_pipeline_raw_vacuum_try_flatten_f1956_call_tee_br_table_wrapper_before_unreachable(
body : Array[@lib.Instruction],
) -> Array[@lib.Instruction]? {
if body.length() != 2 || !(body[1] is @lib.Unreachable) {
return None
}
let child_body = match body[0] {
@lib.Block(@lib.VoidBlockType, @lib.Expr(child_body)) => child_body
_ => return None
}
if !run_hot_pipeline_raw_vacuum_body_is_f1956_call_tee_br_table_wrapper(
child_body,
) {
return None
}
Some(run_hot_pipeline_raw_vacuum_rebase_outer_body(child_body, 0))
}
///|
fn run_hot_pipeline_raw_vacuum_body_is_f2054_call_tee_br_table_wrapper(
body : Array[@lib.Instruction],
) -> Bool {
let non_nops : Array[@lib.Instruction] = []
for instr in body {
match instr {
@lib.Nop => ()
_ => non_nops.push(instr)
}
}
if non_nops.length() != body.length() {
return run_hot_pipeline_raw_vacuum_body_is_f2054_call_tee_br_table_wrapper(
non_nops,
)
}
if body.length() != 6 || !(body[5] is @lib.Unreachable) {
return false
}
let probe_raw = match (body[0], body[1], body[2], body[3]) {
(
@lib.LocalGet(_),
@lib.LocalGet(_),
@lib.Call(_),
@lib.LocalTee(@lib.LocalIdx(probe_raw)),
) => probe_raw
_ => return false
}
match body[4] {
@lib.If(@lib.VoidBlockType, then_body, Some(else_body)) =>
run_hot_pipeline_raw_vacuum_body_count_br_tables(then_body) == 1 &&
run_hot_pipeline_raw_vacuum_body_contains_f2054_selector(
then_body, probe_raw,
) &&
run_hot_pipeline_raw_vacuum_body_is_f1956_cleanup_else(
else_body, probe_raw,
) &&
!run_hot_pipeline_raw_vacuum_body_targets_owner(body, 0)
_ => false
}
}
///|
fn run_hot_pipeline_raw_vacuum_try_flatten_f2054_call_tee_br_table_wrapper_before_unreachable(
body : Array[@lib.Instruction],
) -> Array[@lib.Instruction]? {
if body.length() != 2 || !(body[1] is @lib.Unreachable) {
return None
}
let child_body = match body[0] {
@lib.Block(@lib.VoidBlockType, @lib.Expr(child_body)) => child_body
_ => return None
}
if !run_hot_pipeline_raw_vacuum_body_is_f2054_call_tee_br_table_wrapper(
child_body,
) {
return None
}
Some(run_hot_pipeline_raw_vacuum_rebase_outer_body(child_body, 0))
}
///|
fn run_hot_pipeline_raw_vacuum_f2321_selector_tagged_local(
body : Array[@lib.Instruction],
) -> UInt32? {
for idx = 0; idx + 5 < body.length(); idx = idx + 1 {
match
(
body[idx],
body[idx + 1],
body[idx + 2],
body[idx + 3],
body[idx + 4],
body[idx + 5],
) {
(
@lib.LocalGet(_),
@lib.LocalTee(@lib.LocalIdx(tagged_raw)),
@lib.I32Load(_),
@lib.I32Const(@lib.I32(236)),
@lib.I32Sub,
@lib.BrTable(_, _),
) => return Some(tagged_raw)
_ => ()
}
}
for instr in body {
match instr {
@lib.Block(_, @lib.Expr(child_body))
| @lib.Loop(_, @lib.Expr(child_body))
| @lib.TryTable(_, _, @lib.Expr(child_body)) =>
match
run_hot_pipeline_raw_vacuum_f2321_selector_tagged_local(child_body) {
Some(raw) => return Some(raw)
None => ()
}
@lib.If(_, then_body, else_body) => {
match
run_hot_pipeline_raw_vacuum_f2321_selector_tagged_local(then_body) {
Some(raw) => return Some(raw)
None => ()
}
match else_body {
Some(child_body) =>
match
run_hot_pipeline_raw_vacuum_f2321_selector_tagged_local(
child_body,
) {
Some(raw) => return Some(raw)
None => ()
}
None => ()
}
}
_ => ()
}
}
None
}
///|
fn run_hot_pipeline_raw_vacuum_body_is_f2321_call_arg_tee_br_table_release_ladder_wrapper(
body : Array[@lib.Instruction],
) -> Bool {
let non_nops : Array[@lib.Instruction] = []
for instr in body {
match instr {
@lib.Nop => ()
_ => non_nops.push(instr)
}
}
if non_nops.length() != body.length() {
return run_hot_pipeline_raw_vacuum_body_is_f2321_call_arg_tee_br_table_release_ladder_wrapper(
non_nops,
)
}
if body.length() != 7 || !(body[6] is @lib.Unreachable) {
return false
}
let probe_raw = match (body[0], body[1], body[2], body[3], body[4]) {
(
@lib.LocalGet(_),
@lib.LocalGet(_),
@lib.LocalTee(_),
@lib.Call(_),
@lib.LocalTee(@lib.LocalIdx(probe_raw)),
) => probe_raw
_ => return false
}
match body[5] {
@lib.If(@lib.VoidBlockType, then_body, Some(else_body)) => {
if run_hot_pipeline_raw_vacuum_body_count_br_tables(then_body) != 1 ||
!run_hot_pipeline_raw_vacuum_body_contains_f2054_selector(
then_body, probe_raw,
) {
return false
}
let tagged_raw = match then_body[0] {
@lib.Block(_, @lib.Expr(child_body)) =>
run_hot_pipeline_raw_vacuum_f2321_selector_tagged_local(child_body)
_ => None
}
match tagged_raw {
Some(tagged_raw) =>
run_hot_pipeline_raw_vacuum_body_count_f2297_release_ladders(
then_body, tagged_raw,
) ==
4 &&
run_hot_pipeline_raw_vacuum_body_has_f2297_then_terminal_release(
then_body, tagged_raw,
) &&
run_hot_pipeline_raw_vacuum_body_is_f1956_cleanup_else(
else_body, probe_raw,
) &&
!run_hot_pipeline_raw_vacuum_body_targets_owner(body, 0)
None => false
}
}
_ => false
}
}
///|
fn run_hot_pipeline_raw_vacuum_try_flatten_f2321_call_arg_tee_br_table_release_ladder_wrapper_before_unreachable(
body : Array[@lib.Instruction],
) -> Array[@lib.Instruction]? {
if body.length() != 2 || !(body[1] is @lib.Unreachable) {
return None
}
let child_body = match body[0] {
@lib.Block(@lib.VoidBlockType, @lib.Expr(child_body)) => child_body
_ => return None
}
if !run_hot_pipeline_raw_vacuum_body_is_f2321_call_arg_tee_br_table_release_ladder_wrapper(
child_body,
) {
return None
}
Some(run_hot_pipeline_raw_vacuum_rebase_outer_body(child_body, 0))
}
///|
fn run_hot_pipeline_raw_vacuum_body_count_f2334_simple_release_branches(
body : Array[@lib.Instruction],
tagged_raw : UInt32,
) -> Int32 {
let mut count = 0
for idx = 0; idx + 2 < body.length(); idx = idx + 1 {
match (body[idx], body[idx + 1], body[idx + 2]) {
(
@lib.LocalGet(@lib.LocalIdx(next_tagged_raw)),
@lib.Call(_),
@lib.Br(label),
) if next_tagged_raw == tagged_raw &&
run_hot_pipeline_raw_vacuum_label_depth(label) >= 1 => count = count + 1
_ => ()
}
}
for instr in body {
match instr {
@lib.Block(_, @lib.Expr(child_body))
| @lib.Loop(_, @lib.Expr(child_body))
| @lib.TryTable(_, _, @lib.Expr(child_body)) =>
count = count +
run_hot_pipeline_raw_vacuum_body_count_f2334_simple_release_branches(
child_body, tagged_raw,
)
@lib.If(_, then_body, else_body) => {
count = count +
run_hot_pipeline_raw_vacuum_body_count_f2334_simple_release_branches(
then_body, tagged_raw,
)
match else_body {
Some(child_body) =>
count = count +
run_hot_pipeline_raw_vacuum_body_count_f2334_simple_release_branches(
child_body, tagged_raw,
)
None => ()
}
}
_ => ()
}
}
count
}
///|
fn run_hot_pipeline_raw_vacuum_f2334_child_source(
body : Array[@lib.Instruction],
) -> UInt32? {
let non_nops : Array[@lib.Instruction] = []
for instr in body {
match instr {
@lib.Nop => ()
_ => non_nops.push(instr)
}
}
if non_nops.length() != body.length() {
return run_hot_pipeline_raw_vacuum_f2334_child_source(non_nops)
}
if body.length() != 11 || !(body[10] is @lib.Unreachable) {
return None
}
let (source_raw, result_raw) = match
(
body[0],
body[1],
body[2],
body[3],
body[4],
body[5],
body[6],
body[7],
body[8],
) {
(
@lib.LocalGet(@lib.LocalIdx(source_raw)),
@lib.I32Load(@lib.MemArg(_, _, @lib.U64(4UL))),
@lib.LocalSet(@lib.LocalIdx(loaded_raw)),
@lib.LocalGet(@lib.LocalIdx(next_source_raw)),
@lib.Call(_),
@lib.LocalGet(_),
@lib.LocalGet(@lib.LocalIdx(next_loaded_raw)),
@lib.Call(_),
@lib.LocalTee(@lib.LocalIdx(result_raw)),
) if next_source_raw == source_raw && next_loaded_raw == loaded_raw =>
(source_raw, result_raw)
_ => return None
}
match body[9] {
@lib.If(@lib.VoidBlockType, then_body, Some(else_body)) => {
if run_hot_pipeline_raw_vacuum_body_count_br_tables(then_body) != 1 ||
!run_hot_pipeline_raw_vacuum_body_contains_f2054_selector(
then_body, result_raw,
) {
return None
}
let tagged_raw = match then_body[0] {
@lib.Block(_, @lib.Expr(child_body)) =>
run_hot_pipeline_raw_vacuum_f2321_selector_tagged_local(child_body)
_ => None
}
match tagged_raw {
Some(tagged_raw) =>
if run_hot_pipeline_raw_vacuum_body_count_f2334_simple_release_branches(
then_body, tagged_raw,
) ==
4 &&
run_hot_pipeline_raw_vacuum_body_is_f1956_cleanup_else(
else_body, result_raw,
) {
Some(source_raw)
} else {
None
}
None => None
}
}
_ => None
}
}
///|
fn run_hot_pipeline_raw_vacuum_body_has_f2334_offset8_continuation(
body : Array[@lib.Instruction],
start_idx : Int32,
source_raw : UInt32,
) -> Bool {
let sig : Array[@lib.Instruction] = []
for idx = start_idx + 1; idx < body.length(); idx = idx + 1 {
match body[idx] {
@lib.Nop => ()
instr => sig.push(instr)
}
if sig.length() >= 9 {
break
}
}
match sig {
[
@lib.LocalGet(_),
@lib.LocalGet(_),
@lib.LocalGet(@lib.LocalIdx(next_source_raw)),
@lib.I32Load(@lib.MemArg(_, _, @lib.U64(8UL))),
@lib.Call(_),
@lib.Call(_),
@lib.I64ExtendI32S,
@lib.LocalSet(_),
@lib.Br(label),
] =>
next_source_raw == source_raw &&
run_hot_pipeline_raw_vacuum_label_depth(label) >= 1
_ => false
}
}
///|
fn run_hot_pipeline_raw_vacuum_try_flatten_f2334_tag236_wrapper_before_offset8_continuation(
body : Array[@lib.Instruction],
) -> Array[@lib.Instruction]? {
if body.length() < 2 {
return None
}
let wrapper_body = match body[0] {
@lib.Block(@lib.VoidBlockType, @lib.Expr(wrapper_body)) => wrapper_body
_ => return None
}
if wrapper_body.length() != 1 ||
run_hot_pipeline_raw_vacuum_body_targets_owner(wrapper_body, 0) {
return None
}
let child_body = match wrapper_body[0] {
@lib.Block(@lib.VoidBlockType, @lib.Expr(child_body)) => child_body
_ => return None
}
let source_raw = match
run_hot_pipeline_raw_vacuum_f2334_child_source(child_body) {
Some(source_raw) => source_raw
None => return None
}
if !run_hot_pipeline_raw_vacuum_body_has_f2334_offset8_continuation(
body, 0, source_raw,
) {
return None
}
let flattened = run_hot_pipeline_raw_vacuum_rebase_outer_body(wrapper_body, 0)
for idx = 1; idx < body.length(); idx = idx + 1 {
flattened.push(body[idx])
}
Some(flattened)
}
///|
fn run_hot_pipeline_raw_vacuum_f2651_selector_tagged_local(
body : Array[@lib.Instruction],
) -> UInt32? {
for idx = 0; idx + 4 < body.length(); idx = idx + 1 {
match
(body[idx], body[idx + 1], body[idx + 2], body[idx + 3], body[idx + 4]) {
(
@lib.LocalGet(_),
@lib.I32Load(@lib.MemArg(_, _, @lib.U64(4UL))),
@lib.LocalTee(@lib.LocalIdx(tagged_raw)),
@lib.Call(_),
@lib.BrTable(labels, _),
) if labels.length() >= 5 => return Some(tagged_raw)
_ => ()
}
}
for instr in body {
match instr {
@lib.Block(_, @lib.Expr(child_body))
| @lib.Loop(_, @lib.Expr(child_body))
| @lib.TryTable(_, _, @lib.Expr(child_body)) =>
match
run_hot_pipeline_raw_vacuum_f2651_selector_tagged_local(child_body) {
Some(raw) => return Some(raw)
None => ()
}
@lib.If(_, then_body, else_body) => {
match
run_hot_pipeline_raw_vacuum_f2651_selector_tagged_local(then_body) {
Some(raw) => return Some(raw)
None => ()
}
match else_body {
Some(child_body) =>
match
run_hot_pipeline_raw_vacuum_f2651_selector_tagged_local(
child_body,
) {
Some(raw) => return Some(raw)
None => ()
}
None => ()
}
}
_ => ()
}
}
None
}
///|
fn run_hot_pipeline_raw_vacuum_body_count_tagged_i32_loads(
body : Array[@lib.Instruction],
tagged_raw : UInt32,
) -> Int32 {
let mut count = 0
for idx = 0; idx + 1 < body.length(); idx = idx + 1 {
match (body[idx], body[idx + 1]) {
(@lib.LocalGet(@lib.LocalIdx(next_tagged_raw)), @lib.I32Load(_)) if next_tagged_raw ==
tagged_raw => count = count + 1
_ => ()
}
}
for instr in body {
match instr {
@lib.Block(_, @lib.Expr(child_body))
| @lib.Loop(_, @lib.Expr(child_body))
| @lib.TryTable(_, _, @lib.Expr(child_body)) =>
count = count +
run_hot_pipeline_raw_vacuum_body_count_tagged_i32_loads(
child_body, tagged_raw,
)
@lib.If(_, then_body, else_body) => {
count = count +
run_hot_pipeline_raw_vacuum_body_count_tagged_i32_loads(
then_body, tagged_raw,
)
match else_body {
Some(child_body) =>
count = count +
run_hot_pipeline_raw_vacuum_body_count_tagged_i32_loads(
child_body, tagged_raw,
)
None => ()
}
}
_ => ()
}
}
count
}
///|
fn run_hot_pipeline_raw_vacuum_body_has_tagged_load_kind(
body : Array[@lib.Instruction],
tagged_raw : UInt32,
kind : Int32,
) -> Bool {
for idx = 0; idx + 1 < body.length(); idx = idx + 1 {
match (body[idx], body[idx + 1], kind) {
(@lib.LocalGet(@lib.LocalIdx(next_tagged_raw)), @lib.I64Load(_), 1) if next_tagged_raw ==
tagged_raw => return true
(@lib.LocalGet(@lib.LocalIdx(next_tagged_raw)), @lib.F32Load(_), 2) if next_tagged_raw ==
tagged_raw => return true
(@lib.LocalGet(@lib.LocalIdx(next_tagged_raw)), @lib.F64Load(_), 3) if next_tagged_raw ==
tagged_raw => return true
_ => ()
}
}
for instr in body {
match instr {
@lib.Block(_, @lib.Expr(child_body))
| @lib.Loop(_, @lib.Expr(child_body))
| @lib.TryTable(_, _, @lib.Expr(child_body)) =>
if run_hot_pipeline_raw_vacuum_body_has_tagged_load_kind(
child_body, tagged_raw, kind,
) {
return true
}
@lib.If(_, then_body, else_body) => {
if run_hot_pipeline_raw_vacuum_body_has_tagged_load_kind(
then_body, tagged_raw, kind,
) {
return true
}
match else_body {
Some(child_body) =>
if run_hot_pipeline_raw_vacuum_body_has_tagged_load_kind(
child_body, tagged_raw, kind,
) {
return true
}
None => ()
}
}
_ => ()
}
}
false
}
///|
fn run_hot_pipeline_raw_vacuum_f2651_i32_value_local(
body : Array[@lib.Instruction],
tagged_raw : UInt32,
) -> UInt32? {
for idx = 0; idx + 3 < body.length(); idx = idx + 1 {
match (body[idx], body[idx + 1], body[idx + 2], body[idx + 3]) {
(
@lib.LocalGet(@lib.LocalIdx(next_tagged_raw)),
@lib.I32Load(_),
@lib.LocalSet(@lib.LocalIdx(value_raw)),
@lib.Br(label),
) if next_tagged_raw == tagged_raw &&
run_hot_pipeline_raw_vacuum_label_depth(label) >= 1 =>
return Some(value_raw)
_ => ()
}
}
for instr in body {
match instr {
@lib.Block(_, @lib.Expr(child_body))
| @lib.Loop(_, @lib.Expr(child_body))
| @lib.TryTable(_, _, @lib.Expr(child_body)) =>
match
run_hot_pipeline_raw_vacuum_f2651_i32_value_local(
child_body, tagged_raw,
) {
Some(raw) => return Some(raw)
None => ()
}
@lib.If(_, then_body, else_body) => {
match
run_hot_pipeline_raw_vacuum_f2651_i32_value_local(
then_body, tagged_raw,
) {
Some(raw) => return Some(raw)
None => ()
}
match else_body {
Some(child_body) =>
match
run_hot_pipeline_raw_vacuum_f2651_i32_value_local(
child_body, tagged_raw,
) {
Some(raw) => return Some(raw)
None => ()
}
None => ()
}
}
_ => ()
}
}
None
}
///|
fn run_hot_pipeline_raw_vacuum_body_has_f2651_converter_continuation(
body : Array[@lib.Instruction],
start_idx : Int32,
value_raw : UInt32,
) -> Bool {
let sig : Array[@lib.Instruction] = []
for idx = start_idx + 1; idx < body.length(); idx = idx + 1 {
match body[idx] {
@lib.Nop => ()
instr => sig.push(instr)
}
if sig.length() >= 5 {
break
}
}
match sig {
[
@lib.LocalGet(_),
@lib.LocalGet(@lib.LocalIdx(next_value_raw)),
@lib.Call(_),
@lib.LocalSet(_),
@lib.Br(label),
] =>
next_value_raw == value_raw &&
run_hot_pipeline_raw_vacuum_label_depth(label) >= 1
_ => false
}
}
///|
fn run_hot_pipeline_raw_vacuum_try_flatten_f2651_load_br_table_wrapper_before_converter_continuation(
body : Array[@lib.Instruction],
) -> Array[@lib.Instruction]? {
if body.length() < 6 {
return None
}
let wrapper_body = match body[0] {
@lib.Block(@lib.VoidBlockType, @lib.Expr(wrapper_body)) => wrapper_body
_ => return None
}
if wrapper_body.length() != 1 ||
run_hot_pipeline_raw_vacuum_body_targets_owner(wrapper_body, 0) {
return None
}
let child_body = match wrapper_body[0] {
@lib.Block(@lib.VoidBlockType, @lib.Expr(child_body)) => child_body
_ => return None
}
if run_hot_pipeline_raw_vacuum_body_count_br_tables(child_body) != 1 {
return None
}
let tagged_raw = match
run_hot_pipeline_raw_vacuum_f2651_selector_tagged_local(child_body) {
Some(tagged_raw) => tagged_raw
None => return None
}
if run_hot_pipeline_raw_vacuum_body_count_tagged_i32_loads(
child_body, tagged_raw,
) <
3 ||
!run_hot_pipeline_raw_vacuum_body_has_tagged_load_kind(
child_body, tagged_raw, 1,
) ||
!run_hot_pipeline_raw_vacuum_body_has_tagged_load_kind(
child_body, tagged_raw, 2,
) ||
!run_hot_pipeline_raw_vacuum_body_has_tagged_load_kind(
child_body, tagged_raw, 3,
) {
return None
}
let value_raw = match
run_hot_pipeline_raw_vacuum_f2651_i32_value_local(child_body, tagged_raw) {
Some(value_raw) => value_raw
None => return None
}
if !run_hot_pipeline_raw_vacuum_body_has_f2651_converter_continuation(
body, 0, value_raw,
) {
return None
}
let flattened = run_hot_pipeline_raw_vacuum_rebase_outer_body(wrapper_body, 0)
for idx = 1; idx < body.length(); idx = idx + 1 {
flattened.push(body[idx])
}
Some(flattened)
}
///|
fn run_hot_pipeline_raw_vacuum_f2696_selector_tagged_local(
body : Array[@lib.Instruction],
guard_raw : UInt32,
) -> UInt32? {
for idx = 0; idx + 3 < body.length(); idx = idx + 1 {
match (body[idx], body[idx + 1], body[idx + 2], body[idx + 3]) {
(
@lib.LocalGet(@lib.LocalIdx(next_guard_raw)),
@lib.LocalTee(@lib.LocalIdx(tagged_raw)),
@lib.Call(_),
@lib.BrTable(labels, _),
) if next_guard_raw == guard_raw && labels.length() >= 5 =>
return Some(tagged_raw)
_ => ()
}
}
for instr in body {
match instr {
@lib.Block(_, @lib.Expr(child_body))
| @lib.Loop(_, @lib.Expr(child_body))
| @lib.TryTable(_, _, @lib.Expr(child_body)) =>
match
run_hot_pipeline_raw_vacuum_f2696_selector_tagged_local(
child_body, guard_raw,
) {
Some(raw) => return Some(raw)
None => ()
}
@lib.If(_, then_body, else_body) => {
match
run_hot_pipeline_raw_vacuum_f2696_selector_tagged_local(
then_body, guard_raw,
) {
Some(raw) => return Some(raw)
None => ()
}
match else_body {
Some(child_body) =>
match
run_hot_pipeline_raw_vacuum_f2696_selector_tagged_local(
child_body, guard_raw,
) {
Some(raw) => return Some(raw)
None => ()
}
None => ()
}
}
_ => ()
}
}
None
}
///|
fn run_hot_pipeline_raw_vacuum_body_has_f2696_release_ladder(
body : Array[@lib.Instruction],
tagged_raw : UInt32,
) -> Bool {
for idx = 0; idx + 13 < body.length(); idx = idx + 1 {
match
(
body[idx],
body[idx + 1],
body[idx + 2],
body[idx + 3],
body[idx + 4],
body[idx + 5],
body[idx + 6],
body[idx + 7],
body[idx + 8],
body[idx + 9],
body[idx + 10],
body[idx + 11],
body[idx + 12],
body[idx + 13],
) {
(
@lib.LocalGet(@lib.LocalIdx(next_tagged_raw)),
@lib.LocalSet(_),
@lib.LocalGet(@lib.LocalIdx(load_tagged_raw)),
@lib.I32Load(_),
@lib.LocalSet(@lib.LocalIdx(value_raw)),
@lib.LocalGet(@lib.LocalIdx(count_tagged_raw)),
@lib.Call(_),
@lib.LocalTee(_),
@lib.I32Const(@lib.I32(1)),
@lib.I32GtS,
@lib.If(@lib.VoidBlockType, _, Some(_)),
@lib.LocalGet(@lib.LocalIdx(next_value_raw)),
@lib.LocalSet(_),
@lib.Br(label),
) if next_tagged_raw == tagged_raw &&
load_tagged_raw == tagged_raw &&
count_tagged_raw == tagged_raw &&
next_value_raw == value_raw &&
run_hot_pipeline_raw_vacuum_label_depth(label) >= 1 => return true
_ => ()
}
}
for instr in body {
match instr {
@lib.Block(_, @lib.Expr(child_body))
| @lib.Loop(_, @lib.Expr(child_body))
| @lib.TryTable(_, _, @lib.Expr(child_body)) =>
if run_hot_pipeline_raw_vacuum_body_has_f2696_release_ladder(
child_body, tagged_raw,
) {
return true
}
@lib.If(_, then_body, else_body) => {
if run_hot_pipeline_raw_vacuum_body_has_f2696_release_ladder(
then_body, tagged_raw,
) {
return true
}
match else_body {
Some(child_body) =>
if run_hot_pipeline_raw_vacuum_body_has_f2696_release_ladder(
child_body, tagged_raw,
) {
return true
}
None => ()
}
}
_ => ()
}
}
false
}
///|
fn run_hot_pipeline_raw_vacuum_body_is_f2696_tee_br_table_release_wrapper(
body : Array[@lib.Instruction],
) -> Bool {
let non_nops : Array[@lib.Instruction] = []
for instr in body {
match instr {
@lib.Nop => ()
_ => non_nops.push(instr)
}
}
if non_nops.length() != body.length() {
return run_hot_pipeline_raw_vacuum_body_is_f2696_tee_br_table_release_wrapper(
non_nops,
)
}
if body.length() != 4 || !(body[3] is @lib.Unreachable) {
return false
}
let guard_raw = match (body[0], body[1]) {
(@lib.LocalGet(_), @lib.LocalTee(@lib.LocalIdx(guard_raw))) => guard_raw
_ => return false
}
match body[2] {
@lib.If(@lib.VoidBlockType, then_body, Some(else_body)) => {
if run_hot_pipeline_raw_vacuum_body_count_br_tables(then_body) != 1 {
return false
}
let tagged_raw = match
run_hot_pipeline_raw_vacuum_f2696_selector_tagged_local(
then_body, guard_raw,
) {
Some(tagged_raw) => tagged_raw
None => return false
}
run_hot_pipeline_raw_vacuum_body_count_tagged_i32_loads(
then_body, tagged_raw,
) >=
2 &&
run_hot_pipeline_raw_vacuum_body_has_tagged_load_kind(
then_body, tagged_raw, 1,
) &&
run_hot_pipeline_raw_vacuum_body_has_tagged_load_kind(
then_body, tagged_raw, 2,
) &&
run_hot_pipeline_raw_vacuum_body_has_tagged_load_kind(
then_body, tagged_raw, 3,
) &&
run_hot_pipeline_raw_vacuum_body_has_f2696_release_ladder(
then_body, tagged_raw,
) &&
run_hot_pipeline_raw_vacuum_body_has_f2297_then_terminal_release(
then_body, tagged_raw,
) &&
run_hot_pipeline_raw_vacuum_body_is_f1956_cleanup_else(
else_body, guard_raw,
) &&
!run_hot_pipeline_raw_vacuum_body_targets_owner(body, 0)
}
_ => false
}
}
///|
fn run_hot_pipeline_raw_vacuum_try_flatten_f2696_tee_br_table_release_wrapper_before_unreachable(
body : Array[@lib.Instruction],
) -> Array[@lib.Instruction]? {
if body.length() != 2 || !(body[1] is @lib.Unreachable) {
return None
}
let child_body = match body[0] {
@lib.Block(@lib.VoidBlockType, @lib.Expr(child_body)) => child_body
_ => return None
}
if !run_hot_pipeline_raw_vacuum_body_is_f2696_tee_br_table_release_wrapper(
child_body,
) {
return None
}
Some(run_hot_pipeline_raw_vacuum_rebase_outer_body(child_body, 0))
}
///|
fn run_hot_pipeline_raw_vacuum_f2714_direct_selector_source_local(
body : Array[@lib.Instruction],
) -> UInt32? {
for idx = 0; idx + 2 < body.length(); idx = idx + 1 {
match (body[idx], body[idx + 1], body[idx + 2]) {
(
@lib.LocalGet(@lib.LocalIdx(source_raw)),
@lib.Call(_),
@lib.BrTable(labels, _),
) if labels.length() >= 5 => return Some(source_raw)
_ => ()
}
}
for instr in body {
match instr {
@lib.Block(_, @lib.Expr(child_body))
| @lib.Loop(_, @lib.Expr(child_body))
| @lib.TryTable(_, _, @lib.Expr(child_body)) =>
match
run_hot_pipeline_raw_vacuum_f2714_direct_selector_source_local(
child_body,
) {
Some(raw) => return Some(raw)
None => ()
}
@lib.If(_, then_body, else_body) => {
match
run_hot_pipeline_raw_vacuum_f2714_direct_selector_source_local(
then_body,
) {
Some(raw) => return Some(raw)
None => ()
}
match else_body {
Some(child_body) =>
match
run_hot_pipeline_raw_vacuum_f2714_direct_selector_source_local(
child_body,
) {
Some(raw) => return Some(raw)
None => ()
}
None => ()
}
}
_ => ()
}
}
None
}
///|
fn run_hot_pipeline_raw_vacuum_f2714_i32_output_local(
body : Array[@lib.Instruction],
source_raw : UInt32,
) -> UInt32? {
for idx = 0; idx + 7 < body.length(); idx = idx + 1 {
match
(
body[idx],
body[idx + 1],
body[idx + 2],
body[idx + 3],
body[idx + 4],
body[idx + 5],
body[idx + 6],
body[idx + 7],
) {
(
@lib.LocalGet(@lib.LocalIdx(load_source_raw)),
@lib.I32Load(_),
@lib.LocalSet(@lib.LocalIdx(temp_raw)),
@lib.LocalGet(@lib.LocalIdx(release_source_raw)),
@lib.Call(_),
@lib.LocalGet(@lib.LocalIdx(next_temp_raw)),
@lib.LocalSet(@lib.LocalIdx(output_raw)),
@lib.Br(label),
) if load_source_raw == source_raw &&
release_source_raw == source_raw &&
next_temp_raw == temp_raw &&
run_hot_pipeline_raw_vacuum_label_depth(label) >= 1 =>
return Some(output_raw)
_ => ()
}
}
for instr in body {
match instr {
@lib.Block(_, @lib.Expr(child_body))
| @lib.Loop(_, @lib.Expr(child_body))
| @lib.TryTable(_, _, @lib.Expr(child_body)) =>
match
run_hot_pipeline_raw_vacuum_f2714_i32_output_local(
child_body, source_raw,
) {
Some(raw) => return Some(raw)
None => ()
}
@lib.If(_, then_body, else_body) => {
match
run_hot_pipeline_raw_vacuum_f2714_i32_output_local(
then_body, source_raw,
) {
Some(raw) => return Some(raw)
None => ()
}
match else_body {
Some(child_body) =>
match
run_hot_pipeline_raw_vacuum_f2714_i32_output_local(
child_body, source_raw,
) {
Some(raw) => return Some(raw)
None => ()
}
None => ()
}
}
_ => ()
}
}
None
}
///|
fn run_hot_pipeline_raw_vacuum_body_has_f2714_terminal_release_return_zero(
body : Array[@lib.Instruction],
source_raw : UInt32,
) -> Bool {
for idx = 0; idx + 3 < body.length(); idx = idx + 1 {
match (body[idx], body[idx + 1], body[idx + 2], body[idx + 3]) {
(
@lib.LocalGet(@lib.LocalIdx(next_source_raw)),
@lib.Call(_),
@lib.I32Const(@lib.I32(0)),
@lib.Return,
) if next_source_raw == source_raw => return true
_ => ()
}
}
for instr in body {
match instr {
@lib.Block(_, @lib.Expr(child_body))
| @lib.Loop(_, @lib.Expr(child_body))
| @lib.TryTable(_, _, @lib.Expr(child_body)) =>
if run_hot_pipeline_raw_vacuum_body_has_f2714_terminal_release_return_zero(
child_body, source_raw,
) {
return true
}
@lib.If(_, then_body, else_body) => {
if run_hot_pipeline_raw_vacuum_body_has_f2714_terminal_release_return_zero(
then_body, source_raw,
) {
return true
}
match else_body {
Some(child_body) =>
if run_hot_pipeline_raw_vacuum_body_has_f2714_terminal_release_return_zero(
child_body, source_raw,
) {
return true
}
None => ()
}
}
_ => ()
}
}
false
}
///|
fn run_hot_pipeline_raw_vacuum_try_flatten_f2714_direct_call_br_table_release_wrapper_before_local_set_continuation(
body : Array[@lib.Instruction],
) -> Array[@lib.Instruction]? {
if body.length() < 6 {
return None
}
let wrapper_body = match body[0] {
@lib.Block(@lib.VoidBlockType, @lib.Expr(wrapper_body)) => wrapper_body
_ => return None
}
if wrapper_body.length() != 1 ||
run_hot_pipeline_raw_vacuum_body_targets_owner(wrapper_body, 0) {
return None
}
let child_body = match wrapper_body[0] {
@lib.Block(@lib.VoidBlockType, @lib.Expr(child_body)) => child_body
_ => return None
}
if run_hot_pipeline_raw_vacuum_body_count_br_tables(child_body) != 1 {
return None
}
let source_raw = match
run_hot_pipeline_raw_vacuum_f2714_direct_selector_source_local(child_body) {
Some(source_raw) => source_raw
None => return None
}
if run_hot_pipeline_raw_vacuum_body_count_tagged_i32_loads(
child_body, source_raw,
) <
2 ||
!run_hot_pipeline_raw_vacuum_body_has_tagged_load_kind(
child_body, source_raw, 1,
) ||
!run_hot_pipeline_raw_vacuum_body_has_tagged_load_kind(
child_body, source_raw, 2,
) ||
!run_hot_pipeline_raw_vacuum_body_has_tagged_load_kind(
child_body, source_raw, 3,
) ||
!run_hot_pipeline_raw_vacuum_body_has_f2696_release_ladder(
child_body, source_raw,
) ||
!run_hot_pipeline_raw_vacuum_body_has_f2714_terminal_release_return_zero(
child_body, source_raw,
) {
return None
}
let output_raw = match
run_hot_pipeline_raw_vacuum_f2714_i32_output_local(child_body, source_raw) {
Some(output_raw) => output_raw
None => return None
}
if !run_hot_pipeline_raw_vacuum_body_has_f2651_converter_continuation(
body, 0, output_raw,
) {
return None
}
let flattened = run_hot_pipeline_raw_vacuum_rebase_outer_body(wrapper_body, 0)
for idx = 1; idx < body.length(); idx = idx + 1 {
flattened.push(body[idx])
}
Some(flattened)
}
///|
fn run_hot_pipeline_raw_vacuum_f2905_selector_locals(
body : Array[@lib.Instruction],
) -> (UInt32, UInt32)? {
for idx = 0; idx + 3 < body.length(); idx = idx + 1 {
match (body[idx], body[idx + 1], body[idx + 2], body[idx + 3]) {
(
@lib.LocalGet(@lib.LocalIdx(source_raw)),
@lib.LocalTee(@lib.LocalIdx(tagged_raw)),
@lib.Call(_),
@lib.BrTable(labels, _),
) if source_raw != tagged_raw && labels.length() >= 4 =>
return Some((source_raw, tagged_raw))
_ => ()
}
}
for instr in body {
match instr {
@lib.Block(_, @lib.Expr(child_body))
| @lib.Loop(_, @lib.Expr(child_body))
| @lib.TryTable(_, _, @lib.Expr(child_body)) =>
match run_hot_pipeline_raw_vacuum_f2905_selector_locals(child_body) {
Some(locals) => return Some(locals)
None => ()
}
@lib.If(_, then_body, else_body) => {
match run_hot_pipeline_raw_vacuum_f2905_selector_locals(then_body) {
Some(locals) => return Some(locals)
None => ()
}
match else_body {
Some(child_body) =>
match
run_hot_pipeline_raw_vacuum_f2905_selector_locals(child_body) {
Some(locals) => return Some(locals)
None => ()
}
None => ()
}
}
_ => ()
}
}
None
}
///|
fn run_hot_pipeline_raw_vacuum_f2905_first_local_load_output(
body : Array[@lib.Instruction],
tagged_raw : UInt32,
) -> UInt32? {
for instr in body {
match instr {
@lib.Block(_, @lib.Expr(child_body))
| @lib.Loop(_, @lib.Expr(child_body))
| @lib.TryTable(_, _, @lib.Expr(child_body)) =>
match
run_hot_pipeline_raw_vacuum_f2905_first_local_load_output(
child_body, tagged_raw,
) {
Some(output_raw) => return Some(output_raw)
None => ()
}
@lib.If(_, then_body, else_body) => {
match
run_hot_pipeline_raw_vacuum_f2905_first_local_load_output(
then_body, tagged_raw,
) {
Some(output_raw) => return Some(output_raw)
None => ()
}
match else_body {
Some(child_body) =>
match
run_hot_pipeline_raw_vacuum_f2905_first_local_load_output(
child_body, tagged_raw,
) {
Some(output_raw) => return Some(output_raw)
None => ()
}
None => ()
}
}
_ => ()
}
}
for idx = 0; idx + 7 < body.length(); idx = idx + 1 {
match
(
body[idx],
body[idx + 1],
body[idx + 2],
body[idx + 3],
body[idx + 4],
body[idx + 5],
body[idx + 6],
body[idx + 7],
) {
(
@lib.LocalGet(@lib.LocalIdx(load_tagged_raw)),
@lib.I32Load(_),
@lib.LocalSet(@lib.LocalIdx(temp_raw)),
@lib.LocalGet(@lib.LocalIdx(release_tagged_raw)),
@lib.Call(_),
@lib.LocalGet(@lib.LocalIdx(next_temp_raw)),
@lib.LocalSet(@lib.LocalIdx(output_raw)),
@lib.Br(label),
) if load_tagged_raw == tagged_raw &&
release_tagged_raw == tagged_raw &&
next_temp_raw == temp_raw &&
run_hot_pipeline_raw_vacuum_label_depth(label) >= 1 =>
return Some(output_raw)
_ => ()
}
}
None
}
///|
fn run_hot_pipeline_raw_vacuum_body_count_f2905_local_load_release_branches(
body : Array[@lib.Instruction],
tagged_raw : UInt32,
) -> Int32 {
let mut count = 0
for idx = 0; idx + 7 < body.length(); idx = idx + 1 {
match
(
body[idx],
body[idx + 1],
body[idx + 2],
body[idx + 3],
body[idx + 4],
body[idx + 5],
body[idx + 6],
body[idx + 7],
) {
(
@lib.LocalGet(@lib.LocalIdx(load_tagged_raw)),
@lib.I32Load(_),
@lib.LocalSet(@lib.LocalIdx(temp_raw)),
@lib.LocalGet(@lib.LocalIdx(release_tagged_raw)),
@lib.Call(_),
@lib.LocalGet(@lib.LocalIdx(next_temp_raw)),
@lib.LocalSet(_),
@lib.Br(label),
) if load_tagged_raw == tagged_raw &&
release_tagged_raw == tagged_raw &&
next_temp_raw == temp_raw &&
run_hot_pipeline_raw_vacuum_label_depth(label) >= 1 => count = count + 1
_ => ()
}
}
for instr in body {
match instr {
@lib.Block(_, @lib.Expr(child_body))
| @lib.Loop(_, @lib.Expr(child_body))
| @lib.TryTable(_, _, @lib.Expr(child_body)) =>
count = count +
run_hot_pipeline_raw_vacuum_body_count_f2905_local_load_release_branches(
child_body, tagged_raw,
)
@lib.If(_, then_body, else_body) => {
count = count +
run_hot_pipeline_raw_vacuum_body_count_f2905_local_load_release_branches(
then_body, tagged_raw,
)
match else_body {
Some(child_body) =>
count = count +
run_hot_pipeline_raw_vacuum_body_count_f2905_local_load_release_branches(
child_body, tagged_raw,
)
None => ()
}
}
_ => ()
}
}
count
}
///|
fn run_hot_pipeline_raw_vacuum_body_has_f2905_nested_call_continuation(
body : Array[@lib.Instruction],
start_idx : Int32,
output_raw : UInt32,
) -> Bool {
let sig : Array[@lib.Instruction] = []
for idx = start_idx + 1; idx < body.length(); idx = idx + 1 {
match body[idx] {
@lib.Nop => ()
instr => sig.push(instr)
}
if sig.length() >= 6 {
break
}
}
match sig {
[
@lib.LocalGet(@lib.LocalIdx(next_output_raw)),
@lib.LocalGet(_),
@lib.Call(_),
@lib.Call(_),
@lib.LocalSet(_),
@lib.Br(label),
] =>
next_output_raw == output_raw &&
run_hot_pipeline_raw_vacuum_label_depth(label) >= 1
[
@lib.LocalGet(_),
@lib.LocalGet(_),
@lib.LocalGet(@lib.LocalIdx(next_output_raw)),
@lib.Call(_),
@lib.LocalSet(_),
@lib.Br(label),
] =>
next_output_raw == output_raw &&
run_hot_pipeline_raw_vacuum_label_depth(label) >= 1
_ => false
}
}
///|
fn run_hot_pipeline_raw_vacuum_try_flatten_f2905_direct_call_br_table_local_load_wrapper_before_call_continuation(
body : Array[@lib.Instruction],
) -> Array[@lib.Instruction]? {
if body.length() < 7 {
return None
}
let wrapper_body = match body[0] {
@lib.Block(@lib.VoidBlockType, @lib.Expr(wrapper_body)) => wrapper_body
_ => return None
}
if wrapper_body.length() != 1 ||
run_hot_pipeline_raw_vacuum_body_targets_owner(wrapper_body, 0) {
return None
}
let child_body = match wrapper_body[0] {
@lib.Block(@lib.VoidBlockType, @lib.Expr(child_body)) => child_body
_ => return None
}
if run_hot_pipeline_raw_vacuum_body_count_br_tables(child_body) != 1 {
return None
}
let (_, tagged_raw) = match
run_hot_pipeline_raw_vacuum_f2905_selector_locals(child_body) {
Some(locals) => locals
None => return None
}
if run_hot_pipeline_raw_vacuum_body_count_f2905_local_load_release_branches(
child_body, tagged_raw,
) <
5 {
return None
}
let output_raw = match
run_hot_pipeline_raw_vacuum_f2905_first_local_load_output(
child_body, tagged_raw,
) {
Some(output_raw) => output_raw
None => return None
}
if !run_hot_pipeline_raw_vacuum_body_has_f2905_nested_call_continuation(
body, 0, output_raw,
) {
return None
}
let flattened = run_hot_pipeline_raw_vacuum_rebase_outer_body(wrapper_body, 0)
for idx = 1; idx < body.length(); idx = idx + 1 {
flattened.push(body[idx])
}
Some(flattened)
}
///|
fn run_hot_pipeline_raw_vacuum_body_has_f2775_release_return(
body : Array[@lib.Instruction],
) -> Bool {
if body.length() >= 4 {
for idx = 0; idx + 3 < body.length(); idx = idx + 1 {
match (body[idx], body[idx + 1], body[idx + 2], body[idx + 3]) {
(@lib.LocalGet(_), @lib.Call(_), @lib.I32Const(@lib.I32(0)), @lib.Return
) => return true
_ => ()
}
}
}
if body.length() >= 6 {
for idx = 0; idx + 5 < body.length(); idx = idx + 1 {
match
(
body[idx],
body[idx + 1],
body[idx + 2],
body[idx + 3],
body[idx + 4],
body[idx + 5],
) {
(
@lib.LocalGet(_),
@lib.Call(_),
@lib.LocalGet(_),
@lib.Call(_),
@lib.I32Const(@lib.I32(0)),
@lib.Return,
) => return true
(
@lib.LocalGet(_),
@lib.Call(_),
@lib.LocalGet(_),
@lib.Call(_),
@lib.I64Const(@lib.I64(4294967296L)),
@lib.Return,
) => return true
_ => ()
}
}
}
for instr in body {
match instr {
@lib.Block(_, @lib.Expr(child_body))
| @lib.Loop(_, @lib.Expr(child_body))
| @lib.TryTable(_, _, @lib.Expr(child_body)) =>
if run_hot_pipeline_raw_vacuum_body_has_f2775_release_return(child_body) {
return true
}
@lib.If(_, then_body, else_body) => {
if run_hot_pipeline_raw_vacuum_body_has_f2775_release_return(then_body) {
return true
}
match else_body {
Some(child_body) =>
if run_hot_pipeline_raw_vacuum_body_has_f2775_release_return(
child_body,
) {
return true
}
None => ()
}
}
_ => ()
}
}
false
}
///|
fn run_hot_pipeline_raw_vacuum_body_is_f2775_branch_or_cleanup_else(
body : Array[@lib.Instruction],
source_raw : UInt32,
tag_raw : UInt32,
) -> Bool {
if body.length() != 5 || !(body[4] is @lib.Unreachable) {
return false
}
match (body[0], body[1], body[2]) {
(
@lib.LocalGet(@lib.LocalIdx(next_tag_raw)),
@lib.I32Const(@lib.I32(2)),
@lib.I32Eq,
) if next_tag_raw == tag_raw => ()
_ => return false
}
match body[3] {
@lib.If(@lib.VoidBlockType, then_body, Some(else_body)) => {
match then_body {
[
@lib.LocalGet(@lib.LocalIdx(next_source_raw)),
@lib.I32Load(_),
@lib.LocalSet(_),
@lib.Br(label),
] if next_source_raw == source_raw &&
run_hot_pipeline_raw_vacuum_label_depth(label) >= 1 => ()
_ => return false
}
run_hot_pipeline_raw_vacuum_body_has_f2775_release_return(else_body)
}
_ => false
}
}
///|
fn run_hot_pipeline_raw_vacuum_body_is_f2775_call_tee_cleanup_return_if_wrapper(
body : Array[@lib.Instruction],
) -> Bool {
let non_nops : Array[@lib.Instruction] = []
for instr in body {
match instr {
@lib.Nop => ()
_ => non_nops.push(instr)
}
}
if non_nops.length() != body.length() {
return run_hot_pipeline_raw_vacuum_body_is_f2775_call_tee_cleanup_return_if_wrapper(
non_nops,
)
}
if body.length() != 6 {
return false
}
let (source_raw, tag_raw) = match
(body[0], body[1], body[2], body[3], body[4]) {
(
@lib.LocalGet(@lib.LocalIdx(source_raw)),
@lib.Call(_),
@lib.LocalTee(@lib.LocalIdx(tag_raw)),
@lib.I32Const(@lib.I32(1)),
@lib.I32Eq,
) => (source_raw, tag_raw)
_ => return false
}
match body[5] {
@lib.If(@lib.VoidBlockType, then_body, Some(else_body)) =>
run_hot_pipeline_raw_vacuum_body_has_f2775_release_return(then_body) &&
run_hot_pipeline_raw_vacuum_body_is_f2775_branch_or_cleanup_else(
else_body, source_raw, tag_raw,
) &&
!run_hot_pipeline_raw_vacuum_body_targets_owner(body, 0)
_ => false
}
}
///|
fn run_hot_pipeline_raw_vacuum_try_flatten_f2775_call_tee_cleanup_return_if_wrapper_before_sibling_branch(
body : Array[@lib.Instruction],
) -> Array[@lib.Instruction]? {
if body.length() != 2 {
return None
}
let wrapper_body = match body[0] {
@lib.Block(@lib.VoidBlockType, @lib.Expr(wrapper_body)) => wrapper_body
_ => return None
}
match body[1] {
@lib.Br(label) if run_hot_pipeline_raw_vacuum_label_depth(label) >= 1 => ()
_ => return None
}
if !run_hot_pipeline_raw_vacuum_body_is_f2775_call_tee_cleanup_return_if_wrapper(
wrapper_body,
) {
return None
}
let flattened = run_hot_pipeline_raw_vacuum_rebase_outer_body(wrapper_body, 0)
flattened.push(body[1])
Some(flattened)
}
///|
fn run_hot_pipeline_raw_vacuum_body_is_f2823_release(
body : Array[@lib.Instruction],
active_raw : UInt32,
) -> Bool {
match body {
[@lib.LocalGet(@lib.LocalIdx(next_active_raw)), @lib.Call(_)] =>
next_active_raw == active_raw
_ => false
}
}
///|
fn run_hot_pipeline_raw_vacuum_body_is_f2823_active_branch_or_release(
body : Array[@lib.Instruction],
active_raw : UInt32,
) -> Bool {
if body.length() != 6 || !(body[5] is @lib.Unreachable) {
return false
}
match (body[0], body[1], body[2], body[3], body[4]) {
(
@lib.LocalGet(@lib.LocalIdx(next_active_raw)),
@lib.I32Load(_),
@lib.I32Const(@lib.I32(0)),
@lib.I32GtS,
@lib.If(@lib.VoidBlockType, then_body, Some(else_body)),
) if next_active_raw == active_raw => {
match then_body {
[@lib.Br(label)] if run_hot_pipeline_raw_vacuum_label_depth(label) >= 1 =>
()
_ => return false
}
match else_body {
[
@lib.LocalGet(@lib.LocalIdx(else_active_raw)),
@lib.Call(_),
@lib.Br(label),
] if else_active_raw == active_raw &&
run_hot_pipeline_raw_vacuum_label_depth(label) >= 1 => true
_ => false
}
}
_ => false
}
}
///|
fn run_hot_pipeline_raw_vacuum_body_is_f2823_then_cleanup(
body : Array[@lib.Instruction],
active_raw : UInt32,
) -> Bool {
if body.length() != 5 {
return false
}
match (body[0], body[1], body[2], body[3], body[4]) {
(
@lib.LocalGet(@lib.LocalIdx(next_active_raw)),
@lib.I32Load(_),
@lib.I32Const(@lib.I32(14)),
@lib.I32Eq,
@lib.If(@lib.VoidBlockType, then_body, Some(else_body)),
) if next_active_raw == active_raw =>
run_hot_pipeline_raw_vacuum_body_is_f2823_active_branch_or_release(
then_body, active_raw,
) &&
run_hot_pipeline_raw_vacuum_body_is_f2823_release(else_body, active_raw)
_ => false
}
}
///|
fn run_hot_pipeline_raw_vacuum_body_is_f2823_branch_or_release_else(
body : Array[@lib.Instruction],
source_raw : UInt32,
tag_raw : UInt32,
active_raw : UInt32,
) -> Bool {
if body.length() != 4 {
return false
}
match (body[0], body[1], body[2], body[3]) {
(
@lib.LocalGet(@lib.LocalIdx(next_tag_raw)),
@lib.I32Const(@lib.I32(2)),
@lib.I32Eq,
@lib.If(@lib.VoidBlockType, then_body, Some(else_body)),
) if next_tag_raw == tag_raw => {
match then_body {
[
@lib.LocalGet(@lib.LocalIdx(next_source_raw)),
@lib.I32Load(_),
@lib.LocalSet(_),
@lib.Br(label),
] if next_source_raw == source_raw &&
run_hot_pipeline_raw_vacuum_label_depth(label) >= 1 => ()
_ => return false
}
run_hot_pipeline_raw_vacuum_body_is_f2823_release(else_body, active_raw)
}
_ => false
}
}
///|
fn run_hot_pipeline_raw_vacuum_body_is_f2823_call_tee_branch_cleanup_wrapper(
body : Array[@lib.Instruction],
) -> Bool {
let non_nops : Array[@lib.Instruction] = []
for instr in body {
match instr {
@lib.Nop => ()
_ => non_nops.push(instr)
}
}
if non_nops.length() != body.length() {
return run_hot_pipeline_raw_vacuum_body_is_f2823_call_tee_branch_cleanup_wrapper(
non_nops,
)
}
if body.length() != 6 {
return false
}
let (source_raw, tag_raw) = match
(body[0], body[1], body[2], body[3], body[4]) {
(
@lib.LocalGet(@lib.LocalIdx(source_raw)),
@lib.Call(_),
@lib.LocalTee(@lib.LocalIdx(tag_raw)),
@lib.I32Const(@lib.I32(1)),
@lib.I32Eq,
) => (source_raw, tag_raw)
_ => return false
}
match body[5] {
@lib.If(@lib.VoidBlockType, then_body, Some(else_body)) => {
let active_raw = match then_body {
[@lib.LocalGet(@lib.LocalIdx(active_raw)), ..] => active_raw
_ => return false
}
run_hot_pipeline_raw_vacuum_body_is_f2823_then_cleanup(
then_body, active_raw,
) &&
run_hot_pipeline_raw_vacuum_body_is_f2823_branch_or_release_else(
else_body, source_raw, tag_raw, active_raw,
) &&
!run_hot_pipeline_raw_vacuum_body_targets_owner(body, 0)
}
_ => false
}
}
///|
fn run_hot_pipeline_raw_vacuum_try_flatten_f2823_call_tee_branch_cleanup_wrapper_before_i64_return(
body : Array[@lib.Instruction],
) -> Array[@lib.Instruction]? {
if body.length() != 3 {
return None
}
let wrapper_body = match body[0] {
@lib.Block(@lib.VoidBlockType, @lib.Expr(wrapper_body)) => wrapper_body
_ => return None
}
match (body[1], body[2]) {
(@lib.I64Const(@lib.I64(4294967296L)), @lib.Return) => ()
_ => return None
}
if !run_hot_pipeline_raw_vacuum_body_is_f2823_call_tee_branch_cleanup_wrapper(
wrapper_body,
) {
return None
}
let flattened = run_hot_pipeline_raw_vacuum_rebase_outer_body(wrapper_body, 0)
flattened.push(body[1])
flattened.push(body[2])
Some(flattened)
}
///|
fn run_hot_pipeline_raw_vacuum_body_contains_f2297_selector(
body : Array[@lib.Instruction],
probe_raw : UInt32,
) -> Bool {
for idx = 0; idx + 5 < body.length(); idx = idx + 1 {
match
(
body[idx],
body[idx + 1],
body[idx + 2],
body[idx + 3],
body[idx + 4],
body[idx + 5],
) {
(
@lib.LocalGet(@lib.LocalIdx(next_probe_raw)),
@lib.LocalTee(_),
@lib.I32Load(_),
@lib.I32Const(@lib.I32(220)),
@lib.I32Sub,
@lib.BrTable(_, _),
) if next_probe_raw == probe_raw => return true
_ => ()
}
}
for instr in body {
match instr {
@lib.Block(_, @lib.Expr(child_body))
| @lib.Loop(_, @lib.Expr(child_body))
| @lib.TryTable(_, _, @lib.Expr(child_body)) =>
if run_hot_pipeline_raw_vacuum_body_contains_f2297_selector(
child_body, probe_raw,
) {
return true
}
@lib.If(_, then_body, else_body) => {
if run_hot_pipeline_raw_vacuum_body_contains_f2297_selector(
then_body, probe_raw,
) {
return true
}
match else_body {
Some(child_body) =>
if run_hot_pipeline_raw_vacuum_body_contains_f2297_selector(
child_body, probe_raw,
) {
return true
}
None => ()
}
}
_ => ()
}
}
false
}
///|
fn run_hot_pipeline_raw_vacuum_body_is_f2297_release_ladder_if(
then_body : Array[@lib.Instruction],
else_body : Array[@lib.Instruction],
tagged_raw : UInt32,
count_raw : UInt32,
value_raw : UInt32,
copy_raw : UInt32,
) -> Bool {
if then_body.length() < 7 || else_body.length() != 4 {
return false
}
match
(
then_body[0],
then_body[1],
then_body[2],
then_body[3],
then_body[4],
then_body[5],
then_body[6],
) {
(
@lib.LocalGet(@lib.LocalIdx(next_tagged_raw)),
@lib.LocalGet(@lib.LocalIdx(next_count_raw)),
@lib.I32Const(@lib.I32(1)),
@lib.I32Sub,
@lib.Call(_),
@lib.LocalGet(@lib.LocalIdx(next_value_raw)),
@lib.Call(_),
) if next_tagged_raw == tagged_raw &&
next_count_raw == count_raw &&
next_value_raw == value_raw => ()
_ => return false
}
match (else_body[0], else_body[1], else_body[2], else_body[3]) {
(
@lib.LocalGet(@lib.LocalIdx(next_count_raw)),
@lib.I32Const(@lib.I32(1)),
@lib.I32Eq,
@lib.If(@lib.VoidBlockType, release_then, None),
) if next_count_raw == count_raw && release_then.length() == 4 =>
match
(release_then[0], release_then[1], release_then[2], release_then[3]) {
(
@lib.LocalGet(@lib.LocalIdx(next_copy_raw)),
@lib.I32Const(@lib.I32(8)),
@lib.I32Sub,
@lib.Call(_),
) => next_copy_raw == copy_raw
_ => false
}
_ => false
}
}
///|
fn run_hot_pipeline_raw_vacuum_body_count_f2297_release_ladders(
body : Array[@lib.Instruction],
tagged_raw : UInt32,
) -> Int32 {
let mut count = 0
for idx = 0; idx + 13 < body.length(); idx = idx + 1 {
match
(
body[idx],
body[idx + 1],
body[idx + 2],
body[idx + 3],
body[idx + 4],
body[idx + 5],
body[idx + 6],
body[idx + 7],
body[idx + 8],
body[idx + 9],
body[idx + 10],
body[idx + 11],
body[idx + 12],
body[idx + 13],
) {
(
@lib.LocalGet(@lib.LocalIdx(next_tagged_raw)),
@lib.LocalSet(@lib.LocalIdx(copy_raw)),
@lib.LocalGet(@lib.LocalIdx(load_tagged_raw)),
@lib.I32Load(@lib.MemArg(_, _, @lib.U64(4UL))),
@lib.LocalSet(@lib.LocalIdx(value_raw)),
@lib.LocalGet(@lib.LocalIdx(count_tagged_raw)),
@lib.Call(_),
@lib.LocalTee(@lib.LocalIdx(count_raw)),
@lib.I32Const(@lib.I32(1)),
@lib.I32GtS,
@lib.If(@lib.VoidBlockType, then_body, Some(else_body)),
@lib.LocalGet(@lib.LocalIdx(next_value_raw)),
@lib.LocalSet(_),
@lib.Br(label),
) if next_tagged_raw == tagged_raw &&
load_tagged_raw == tagged_raw &&
count_tagged_raw == tagged_raw &&
next_value_raw == value_raw &&
run_hot_pipeline_raw_vacuum_label_depth(label) >= 1 &&
run_hot_pipeline_raw_vacuum_body_is_f2297_release_ladder_if(
then_body, else_body, tagged_raw, count_raw, value_raw, copy_raw,
) => count = count + 1
_ => ()
}
}
for instr in body {
match instr {
@lib.Block(_, @lib.Expr(child_body))
| @lib.Loop(_, @lib.Expr(child_body))
| @lib.TryTable(_, _, @lib.Expr(child_body)) =>
count = count +
run_hot_pipeline_raw_vacuum_body_count_f2297_release_ladders(
child_body, tagged_raw,
)
@lib.If(_, then_body, else_body) => {
count = count +
run_hot_pipeline_raw_vacuum_body_count_f2297_release_ladders(
then_body, tagged_raw,
)
match else_body {
Some(child_body) =>
count = count +
run_hot_pipeline_raw_vacuum_body_count_f2297_release_ladders(
child_body, tagged_raw,
)
None => ()
}
}
_ => ()
}
}
count
}
///|
fn run_hot_pipeline_raw_vacuum_body_has_f2297_then_terminal_release(
body : Array[@lib.Instruction],
tagged_raw : UInt32,
) -> Bool {
if body.length() < 4 {
return false
}
match
(body[body.length() - 3], body[body.length() - 2], body[body.length() - 1]) {
(@lib.LocalGet(@lib.LocalIdx(next_tagged_raw)), @lib.Call(_), @lib.Br(label)
) if next_tagged_raw == tagged_raw &&
run_hot_pipeline_raw_vacuum_label_depth(label) >= 1 => true
_ => false
}
}
///|
fn run_hot_pipeline_raw_vacuum_body_is_f2297_call_tee_br_table_release_ladder_wrapper(
body : Array[@lib.Instruction],
) -> Bool {
let non_nops : Array[@lib.Instruction] = []
for instr in body {
match instr {
@lib.Nop => ()
_ => non_nops.push(instr)
}
}
if non_nops.length() != body.length() {
return run_hot_pipeline_raw_vacuum_body_is_f2297_call_tee_br_table_release_ladder_wrapper(
non_nops,
)
}
if body.length() != 6 || !(body[5] is @lib.Unreachable) {
return false
}
let probe_raw = match (body[0], body[1], body[2], body[3]) {
(
@lib.LocalGet(_),
@lib.LocalGet(_),
@lib.Call(_),
@lib.LocalTee(@lib.LocalIdx(probe_raw)),
) => probe_raw
_ => return false
}
match body[4] {
@lib.If(@lib.VoidBlockType, then_body, Some(else_body)) => {
if run_hot_pipeline_raw_vacuum_body_count_br_tables(then_body) != 1 ||
!run_hot_pipeline_raw_vacuum_body_contains_f2297_selector(
then_body, probe_raw,
) {
return false
}
let tagged_raw = match then_body[0] {
@lib.Block(_, @lib.Expr(child_body)) =>
run_hot_pipeline_raw_vacuum_f2297_selector_tagged_local(child_body)
_ => None
}
match tagged_raw {
Some(tagged_raw) =>
run_hot_pipeline_raw_vacuum_body_count_f2297_release_ladders(
then_body, tagged_raw,
) >=
4 &&
run_hot_pipeline_raw_vacuum_body_has_f2297_then_terminal_release(
then_body, tagged_raw,
) &&
run_hot_pipeline_raw_vacuum_body_is_f1956_cleanup_else(
else_body, probe_raw,
) &&
!run_hot_pipeline_raw_vacuum_body_targets_owner(body, 0)
None => false
}
}
_ => false
}
}
///|
fn run_hot_pipeline_raw_vacuum_f2297_selector_tagged_local(
body : Array[@lib.Instruction],
) -> UInt32? {
for idx = 0; idx + 5 < body.length(); idx = idx + 1 {
match
(
body[idx],
body[idx + 1],
body[idx + 2],
body[idx + 3],
body[idx + 4],
body[idx + 5],
) {
(
@lib.LocalGet(_),
@lib.LocalTee(@lib.LocalIdx(tagged_raw)),
@lib.I32Load(_),
@lib.I32Const(@lib.I32(220)),
@lib.I32Sub,
@lib.BrTable(_, _),
) => return Some(tagged_raw)
_ => ()
}
}
for instr in body {
match instr {
@lib.Block(_, @lib.Expr(child_body))
| @lib.Loop(_, @lib.Expr(child_body))
| @lib.TryTable(_, _, @lib.Expr(child_body)) =>
match
run_hot_pipeline_raw_vacuum_f2297_selector_tagged_local(child_body) {
Some(raw) => return Some(raw)
None => ()
}
@lib.If(_, then_body, else_body) => {
match
run_hot_pipeline_raw_vacuum_f2297_selector_tagged_local(then_body) {
Some(raw) => return Some(raw)
None => ()
}
match else_body {
Some(child_body) =>
match
run_hot_pipeline_raw_vacuum_f2297_selector_tagged_local(
child_body,
) {
Some(raw) => return Some(raw)
None => ()
}
None => ()
}
}
_ => ()
}
}
None
}
///|
fn run_hot_pipeline_raw_vacuum_try_flatten_f2297_call_tee_br_table_release_ladder_wrapper_before_unreachable(
body : Array[@lib.Instruction],
) -> Array[@lib.Instruction]? {
if body.length() != 2 || !(body[1] is @lib.Unreachable) {
return None
}
let child_body = match body[0] {
@lib.Block(@lib.VoidBlockType, @lib.Expr(child_body)) => child_body
_ => return None
}
if !run_hot_pipeline_raw_vacuum_body_is_f2297_call_tee_br_table_release_ladder_wrapper(
child_body,
) {
return None
}
Some(run_hot_pipeline_raw_vacuum_rebase_outer_body(child_body, 0))
}
///|
fn run_hot_pipeline_raw_vacuum_body_contains_f2320_selector(
body : Array[@lib.Instruction],
probe_raw : UInt32,
) -> Bool {
for idx = 0; idx + 5 < body.length(); idx = idx + 1 {
match
(
body[idx],
body[idx + 1],
body[idx + 2],
body[idx + 3],
body[idx + 4],
body[idx + 5],
) {
(
@lib.LocalGet(@lib.LocalIdx(next_probe_raw)),
@lib.LocalTee(_),
@lib.I32Load(_),
@lib.I32Const(@lib.I32(231)),
@lib.I32Sub,
@lib.BrTable(_, _),
) if next_probe_raw == probe_raw => return true
_ => ()
}
}
for instr in body {
match instr {
@lib.Block(_, @lib.Expr(child_body))
| @lib.Loop(_, @lib.Expr(child_body))
| @lib.TryTable(_, _, @lib.Expr(child_body)) =>
if run_hot_pipeline_raw_vacuum_body_contains_f2320_selector(
child_body, probe_raw,
) {
return true
}
@lib.If(_, then_body, else_body) => {
if run_hot_pipeline_raw_vacuum_body_contains_f2320_selector(
then_body, probe_raw,
) {
return true
}
match else_body {
Some(child_body) =>
if run_hot_pipeline_raw_vacuum_body_contains_f2320_selector(
child_body, probe_raw,
) {
return true
}
None => ()
}
}
_ => ()
}
}
false
}
///|
fn run_hot_pipeline_raw_vacuum_f2320_selector_tagged_local(
body : Array[@lib.Instruction],
) -> UInt32? {
for idx = 0; idx + 5 < body.length(); idx = idx + 1 {
match
(
body[idx],
body[idx + 1],
body[idx + 2],
body[idx + 3],
body[idx + 4],
body[idx + 5],
) {
(
@lib.LocalGet(_),
@lib.LocalTee(@lib.LocalIdx(tagged_raw)),
@lib.I32Load(_),
@lib.I32Const(@lib.I32(231)),
@lib.I32Sub,
@lib.BrTable(_, _),
) => return Some(tagged_raw)
_ => ()
}
}
for instr in body {
match instr {
@lib.Block(_, @lib.Expr(child_body))
| @lib.Loop(_, @lib.Expr(child_body))
| @lib.TryTable(_, _, @lib.Expr(child_body)) =>
match
run_hot_pipeline_raw_vacuum_f2320_selector_tagged_local(child_body) {
Some(raw) => return Some(raw)
None => ()
}
@lib.If(_, then_body, else_body) => {
match
run_hot_pipeline_raw_vacuum_f2320_selector_tagged_local(then_body) {
Some(raw) => return Some(raw)
None => ()
}
match else_body {
Some(child_body) =>
match
run_hot_pipeline_raw_vacuum_f2320_selector_tagged_local(
child_body,
) {
Some(raw) => return Some(raw)
None => ()
}
None => ()
}
}
_ => ()
}
}
None
}
///|
fn run_hot_pipeline_raw_vacuum_body_has_f2320_double_update_ladder(
body : Array[@lib.Instruction],
tagged_raw : UInt32,
) -> Bool {
for idx = 0; idx + 18 < body.length(); idx = idx + 1 {
match
(
body[idx],
body[idx + 1],
body[idx + 2],
body[idx + 3],
body[idx + 4],
body[idx + 5],
body[idx + 6],
body[idx + 7],
body[idx + 8],
body[idx + 9],
body[idx + 10],
body[idx + 11],
body[idx + 12],
body[idx + 13],
body[idx + 14],
body[idx + 15],
body[idx + 16],
body[idx + 17],
body[idx + 18],
) {
(
@lib.LocalGet(@lib.LocalIdx(next_tagged_raw)),
@lib.LocalSet(@lib.LocalIdx(copy_raw)),
@lib.LocalGet(@lib.LocalIdx(load_wide_tagged_raw)),
@lib.I32Load(@lib.MemArg(_, _, @lib.U64(8UL))),
@lib.LocalSet(@lib.LocalIdx(wide_raw)),
@lib.LocalGet(@lib.LocalIdx(load_value_tagged_raw)),
@lib.I32Load(@lib.MemArg(_, _, @lib.U64(4UL))),
@lib.LocalSet(@lib.LocalIdx(value_raw)),
@lib.LocalGet(@lib.LocalIdx(count_tagged_raw)),
@lib.Call(_),
@lib.LocalTee(@lib.LocalIdx(count_raw)),
@lib.I32Const(@lib.I32(1)),
@lib.I32GtS,
@lib.If(@lib.VoidBlockType, then_body, Some(else_body)),
@lib.LocalGet(@lib.LocalIdx(next_value_raw)),
@lib.LocalSet(_),
@lib.LocalGet(@lib.LocalIdx(next_wide_raw)),
@lib.LocalSet(_),
@lib.Br(label),
) if next_tagged_raw == tagged_raw &&
load_wide_tagged_raw == tagged_raw &&
load_value_tagged_raw == tagged_raw &&
count_tagged_raw == tagged_raw &&
next_value_raw == value_raw &&
next_wide_raw == wide_raw &&
run_hot_pipeline_raw_vacuum_label_depth(label) >= 1 &&
run_hot_pipeline_raw_vacuum_body_is_f2297_release_ladder_if(
then_body, else_body, tagged_raw, count_raw, wide_raw, copy_raw,
) => return true
_ => ()
}
}
for instr in body {
match instr {
@lib.Block(_, @lib.Expr(child_body))
| @lib.Loop(_, @lib.Expr(child_body))
| @lib.TryTable(_, _, @lib.Expr(child_body)) =>
if run_hot_pipeline_raw_vacuum_body_has_f2320_double_update_ladder(
child_body, tagged_raw,
) {
return true
}
@lib.If(_, then_body, else_body) => {
if run_hot_pipeline_raw_vacuum_body_has_f2320_double_update_ladder(
then_body, tagged_raw,
) {
return true
}
match else_body {
Some(child_body) =>
if run_hot_pipeline_raw_vacuum_body_has_f2320_double_update_ladder(
child_body, tagged_raw,
) {
return true
}
None => ()
}
}
_ => ()
}
}
false
}
///|
fn run_hot_pipeline_raw_vacuum_body_is_f2320_call_tee_br_table_double_update_wrapper(
body : Array[@lib.Instruction],
) -> Bool {
let non_nops : Array[@lib.Instruction] = []
for instr in body {
match instr {
@lib.Nop => ()
_ => non_nops.push(instr)
}
}
if non_nops.length() != body.length() {
return run_hot_pipeline_raw_vacuum_body_is_f2320_call_tee_br_table_double_update_wrapper(
non_nops,
)
}
if body.length() != 6 || !(body[5] is @lib.Unreachable) {
return false
}
let probe_raw = match (body[0], body[1], body[2], body[3]) {
(
@lib.LocalGet(_),
@lib.LocalGet(_),
@lib.Call(_),
@lib.LocalTee(@lib.LocalIdx(probe_raw)),
) => probe_raw
_ => return false
}
match body[4] {
@lib.If(@lib.VoidBlockType, then_body, Some(else_body)) => {
if run_hot_pipeline_raw_vacuum_body_count_br_tables(then_body) != 1 ||
!run_hot_pipeline_raw_vacuum_body_contains_f2320_selector(
then_body, probe_raw,
) {
return false
}
let tagged_raw = match then_body[0] {
@lib.Block(_, @lib.Expr(child_body)) =>
run_hot_pipeline_raw_vacuum_f2320_selector_tagged_local(child_body)
_ => None
}
match tagged_raw {
Some(tagged_raw) =>
run_hot_pipeline_raw_vacuum_body_count_f2297_release_ladders(
then_body, tagged_raw,
) ==
2 &&
run_hot_pipeline_raw_vacuum_body_has_f2320_double_update_ladder(
then_body, tagged_raw,
) &&
run_hot_pipeline_raw_vacuum_body_has_f2297_then_terminal_release(
then_body, tagged_raw,
) &&
run_hot_pipeline_raw_vacuum_body_is_f1956_cleanup_else(
else_body, probe_raw,
) &&
!run_hot_pipeline_raw_vacuum_body_targets_owner(body, 0)
None => false
}
}
_ => false
}
}
///|
fn run_hot_pipeline_raw_vacuum_try_flatten_f2320_call_tee_br_table_double_update_wrapper_before_unreachable(
body : Array[@lib.Instruction],
) -> Array[@lib.Instruction]? {
if body.length() != 2 || !(body[1] is @lib.Unreachable) {
return None
}
let child_body = match body[0] {
@lib.Block(@lib.VoidBlockType, @lib.Expr(child_body)) => child_body
_ => return None
}
if !run_hot_pipeline_raw_vacuum_body_is_f2320_call_tee_br_table_double_update_wrapper(
child_body,
) {
return None
}
Some(run_hot_pipeline_raw_vacuum_rebase_outer_body(child_body, 0))
}
///|
fn run_hot_pipeline_raw_vacuum_body_is_f1963_local_set_sentinel_comparison(
body : Array[@lib.Instruction],
) -> Bool {
if body.length() != 14 {
return false
}
let (lhs_result_raw, rhs_result_raw) = match
(
body[0],
body[1],
body[2],
body[3],
body[4],
body[5],
body[6],
body[7],
body[8],
body[9],
body[10],
body[11],
body[12],
) {
(
@lib.LocalGet(@lib.LocalIdx(lhs_arg0_raw)),
@lib.LocalGet(@lib.LocalIdx(lhs_arg1_raw)),
@lib.LocalGet(@lib.LocalIdx(lhs_arg2_raw)),
@lib.Call(_),
@lib.LocalSet(@lib.LocalIdx(lhs_result_raw)),
@lib.LocalGet(@lib.LocalIdx(rhs_arg0_raw)),
@lib.LocalGet(@lib.LocalIdx(rhs_arg1_raw)),
@lib.LocalGet(@lib.LocalIdx(rhs_arg2_raw)),
@lib.Call(_),
@lib.LocalSet(@lib.LocalIdx(rhs_result_raw)),
@lib.LocalGet(@lib.LocalIdx(next_lhs_result_raw)),
@lib.I64Const(@lib.I64(4294967296L)),
@lib.I64Eq,
) if lhs_arg0_raw == rhs_arg0_raw &&
lhs_arg1_raw == rhs_arg1_raw &&
lhs_arg2_raw != rhs_arg2_raw &&
lhs_result_raw == next_lhs_result_raw &&
lhs_result_raw != rhs_result_raw => (lhs_result_raw, rhs_result_raw)
_ => return false
}
match body[13] {
@lib.If(@lib.VoidBlockType, then_body, Some(else_body)) => {
match then_body {
[@lib.Br(label)] if run_hot_pipeline_raw_vacuum_label_depth(label) >= 2 =>
()
_ => return false
}
if else_body.length() != 7 {
return false
}
let selector_raw = match (else_body[0], else_body[1], else_body[2]) {
(
@lib.LocalGet(@lib.LocalIdx(next_lhs_result_raw)),
@lib.I32WrapI64,
@lib.LocalSet(@lib.LocalIdx(selector_raw)),
) if next_lhs_result_raw == lhs_result_raw => selector_raw
_ => return false
}
match (else_body[3], else_body[4], else_body[5]) {
(
@lib.LocalGet(@lib.LocalIdx(next_rhs_result_raw)),
@lib.I64Const(@lib.I64(4294967296L)),
@lib.I64Eq,
) if next_rhs_result_raw == rhs_result_raw => ()
_ => return false
}
match else_body[6] {
@lib.If(@lib.VoidBlockType, nested_then, Some(nested_else)) => {
match nested_then {
[@lib.Br(label)] if run_hot_pipeline_raw_vacuum_label_depth(label) >=
2 => ()
_ => return false
}
if nested_else.length() != 6 {
return false
}
match
(
nested_else[0],
nested_else[1],
nested_else[2],
nested_else[3],
nested_else[4],
) {
(
@lib.LocalGet(@lib.LocalIdx(next_selector_raw)),
@lib.LocalGet(@lib.LocalIdx(next_rhs_result_raw)),
@lib.I32WrapI64,
@lib.I32Eq,
@lib.I32Eqz,
) if next_selector_raw == selector_raw &&
next_rhs_result_raw == rhs_result_raw => ()
_ => return false
}
match nested_else[5] {
@lib.If(@lib.VoidBlockType, final_then, None) =>
match final_then {
[@lib.Br(label)] if run_hot_pipeline_raw_vacuum_label_depth(
label,
) >=
2 => !run_hot_pipeline_raw_vacuum_body_targets_owner(body, 0)
_ => false
}
_ => false
}
}
_ => false
}
}
_ => false
}
}
///|
fn run_hot_pipeline_raw_vacuum_try_flatten_f1963_local_set_sentinel_comparison_wrapper(
body : Array[@lib.Instruction],
) -> Array[@lib.Instruction]? {
if body.length() != 1 {
return None
}
let child_body = match body[0] {
@lib.Block(@lib.VoidBlockType, @lib.Expr(child_body)) => child_body
_ => return None
}
if !run_hot_pipeline_raw_vacuum_body_is_f1963_local_set_sentinel_comparison(
child_body,
) {
return None
}
Some(run_hot_pipeline_raw_vacuum_rebase_outer_body(child_body, 0))
}
///|
fn run_hot_pipeline_raw_vacuum_body_is_f1948_sentinel_branch_selection(
body : Array[@lib.Instruction],
) -> Bool {
if body.length() != 8 {
return false
}
let result_raw = match
(body[0], body[1], body[2], body[3], body[4], body[5], body[6]) {
(
@lib.LocalGet(_),
@lib.LocalGet(_),
@lib.LocalGet(_),
@lib.Call(_),
@lib.LocalTee(@lib.LocalIdx(result_raw)),
@lib.I64Const(@lib.I64(4294967296L)),
@lib.I64Eq,
) => result_raw
_ => return false
}
match body[7] {
@lib.If(@lib.VoidBlockType, then_body, Some(else_body)) => {
match then_body {
[@lib.Br(label)] if run_hot_pipeline_raw_vacuum_label_depth(label) >= 4 =>
()
_ => return false
}
if else_body.length() != 4 {
return false
}
let selector_raw = match (else_body[0], else_body[1], else_body[2]) {
(
@lib.LocalGet(@lib.LocalIdx(next_result_raw)),
@lib.I32WrapI64,
@lib.LocalTee(@lib.LocalIdx(selector_raw)),
) if next_result_raw == result_raw => selector_raw
_ => return false
}
match else_body[3] {
@lib.If(@lib.VoidBlockType, nested_then, None) => {
if nested_then.length() != 5 || !(nested_then[4] is @lib.Unreachable) {
return false
}
match
(nested_then[0], nested_then[1], nested_then[2], nested_then[3]) {
(
@lib.LocalGet(@lib.LocalIdx(next_selector_raw)),
@lib.I32Const(@lib.I32(1)),
@lib.I32Eq,
@lib.If(@lib.VoidBlockType, selector_then, Some(selector_else)),
) if next_selector_raw == selector_raw => {
match selector_then {
[@lib.Br(label)] if run_hot_pipeline_raw_vacuum_label_depth(
label,
) >=
5 => ()
_ => return false
}
match selector_else {
[@lib.Br(label)] if run_hot_pipeline_raw_vacuum_label_depth(
label,
) >=
6 => ()
_ => return false
}
!run_hot_pipeline_raw_vacuum_body_targets_owner(body, 0) &&
!run_hot_pipeline_raw_vacuum_body_targets_owner(body, 1)
}
_ => false
}
}
_ => false
}
}
_ => false
}
}
///|
fn run_hot_pipeline_raw_vacuum_has_f1948_i64_call_continuation(
body : Array[@lib.Instruction],
start_idx : Int32,
) -> Bool {
if start_idx + 4 >= body.length() {
return false
}
match
(
body[start_idx + 1],
body[start_idx + 2],
body[start_idx + 3],
body[start_idx + 4],
) {
(
@lib.LocalGet(_),
@lib.I32Const(@lib.I32(1)),
@lib.Call(_),
@lib.I64ExtendI32S,
) => true
_ => false
}
}
///|
fn run_hot_pipeline_raw_vacuum_try_flatten_f1948_call_tee_sentinel_branch_wrappers(
body : Array[@lib.Instruction],
) -> Array[@lib.Instruction]? {
if body.length() < 5 ||
!run_hot_pipeline_raw_vacuum_has_f1948_i64_call_continuation(body, 0) {
return None
}
let outer_body = match body[0] {
@lib.Block(@lib.VoidBlockType, @lib.Expr(outer_body)) => outer_body
_ => return None
}
if outer_body.length() != 1 {
return None
}
let selection_body = match outer_body[0] {
@lib.Block(@lib.VoidBlockType, @lib.Expr(selection_body)) => selection_body
_ => return None
}
if !run_hot_pipeline_raw_vacuum_body_is_f1948_sentinel_branch_selection(
selection_body,
) {
return None
}
let flattened_once = run_hot_pipeline_raw_vacuum_rebase_outer_body(
selection_body, 0,
)
let flattened = run_hot_pipeline_raw_vacuum_rebase_outer_body(
flattened_once, 0,
)
for idx = 1; idx < body.length(); idx = idx + 1 {
flattened.push(body[idx])
}
Some(flattened)
}
///|
fn run_hot_pipeline_raw_vacuum_try_flatten_f1946_loop_arg_copy_before_eqz_call(
body : Array[@lib.Instruction],
) -> Array[@lib.Instruction]? {
if body.length() < 10 {
return None
}
let copy_body = match body[0] {
@lib.Block(@lib.VoidBlockType, @lib.Expr(copy_body)) => copy_body
_ => return None
}
let (source_raw, target_raw) = match copy_body {
[
@lib.LocalGet(@lib.LocalIdx(source_raw)),
@lib.LocalSet(@lib.LocalIdx(target_raw)),
] if source_raw != target_raw => (source_raw, target_raw)
_ => return None
}
ignore(source_raw)
match
(body[1], body[2], body[3], body[4], body[5], body[6], body[7], body[8]) {
(
@lib.LocalGet(_),
@lib.LocalTee(_),
@lib.LocalGet(_),
@lib.LocalTee(_),
@lib.LocalGet(_),
@lib.LocalTee(_),
@lib.Call(_),
@lib.I32Eqz,
) => ()
_ => return None
}
match body[9] {
@lib.If(@lib.VoidBlockType, then_body, None) =>
match then_body {
[@lib.LocalGet(@lib.LocalIdx(next_target_raw)), @lib.Call(_), ..] if next_target_raw ==
target_raw => ()
_ => return None
}
_ => return None
}
let flattened : Array[@lib.Instruction] = []
for instr in copy_body {
flattened.push(instr)
}
for idx = 1; idx < body.length(); idx = idx + 1 {
flattened.push(body[idx])
}
Some(flattened)
}
///|
fn run_hot_pipeline_raw_vacuum_f1900_increment_probe_release_prefix_slot(
body : Array[@lib.Instruction],
) -> UInt32? {
let non_nops : Array[@lib.Instruction] = []
for instr in body {
match instr {
@lib.Nop => ()
_ => non_nops.push(instr)
}
}
if non_nops.length() != body.length() {
return run_hot_pipeline_raw_vacuum_f1900_increment_probe_release_prefix_slot(
non_nops,
)
}
if body.length() < 23 {
return None
}
match body[body.length() - 1] {
@lib.If(@lib.VoidBlockType, _, _) => ()
_ => return None
}
if !run_hot_pipeline_raw_vacuum_final_if_has_else_or_top_level_outer_branch(
body,
) {
return None
}
let (slot_raw, increment_raw, root_raw, probe_raw, result_raw) = match
(
body[0],
body[1],
body[2],
body[3],
body[4],
body[5],
body[6],
body[7],
body[8],
body[9],
body[10],
body[11],
body[12],
body[13],
body[14],
) {
(
@lib.LocalGet(@lib.LocalIdx(slot_raw)),
@lib.I32Load(_),
@lib.I32Const(@lib.I32(1)),
@lib.I32Add,
@lib.LocalSet(@lib.LocalIdx(increment_raw)),
@lib.LocalGet(@lib.LocalIdx(root_raw)),
@lib.I32Load(_),
@lib.LocalTee(@lib.LocalIdx(probe_raw)),
@lib.Call(_),
@lib.LocalGet(@lib.LocalIdx(next_probe_raw)),
@lib.LocalGet(@lib.LocalIdx(next_increment_raw)),
@lib.Call(_),
@lib.LocalSet(@lib.LocalIdx(result_raw)),
@lib.LocalGet(@lib.LocalIdx(release_probe_raw)),
@lib.Call(_),
) if next_probe_raw == probe_raw &&
release_probe_raw == probe_raw &&
next_increment_raw == increment_raw =>
(slot_raw, increment_raw, root_raw, probe_raw, result_raw)
_ => return None
}
let mut saw_result_call = false
let mut saw_condition_tee = false
for idx = 15; idx + 1 < body.length(); idx = idx + 1 {
match body[idx] {
@lib.LocalGet(@lib.LocalIdx(raw)) if raw == result_raw =>
if idx + 1 < body.length() && body[idx + 1] is @lib.Call(_) {
saw_result_call = true
}
@lib.LocalTee(_) => saw_condition_tee = true
@lib.LocalGet(@lib.LocalIdx(raw)) if raw == root_raw => ()
@lib.LocalGet(_)
| @lib.Call(_)
| @lib.I32Const(_)
| @lib.I32Eq
| @lib.I32Eqz
| @lib.I32And
| @lib.I32WrapI64
| @lib.I64Const(_)
| @lib.I64Eq => ()
_ => ()
}
}
ignore(increment_raw)
ignore(probe_raw)
// Reject mismatched fixed/flat shapes before walking every descendant.
// These predicates only inspect the immutable input; admission is unchanged.
if saw_result_call &&
saw_condition_tee &&
!run_hot_pipeline_raw_vacuum_instrs_contain_unreachable(body) {
Some(slot_raw)
} else {
None
}
}
///|
fn run_hot_pipeline_raw_vacuum_f1900_reload_signature_at(
instrs : Array[@lib.Instruction],
start_idx : Int32,
slot_raw : UInt32,
) -> (UInt32, UInt32, UInt32)? {
let sig : Array[@lib.Instruction] = []
let mut idx = start_idx
while idx < instrs.length() && sig.length() < 15 {
match instrs[idx] {
@lib.Nop => ()
instr => sig.push(instr)
}
idx = idx + 1
}
if sig.length() < 15 {
return None
}
let (reload_raw, probe_raw, result_raw) = match
(
sig[0],
sig[1],
sig[2],
sig[3],
sig[4],
sig[5],
sig[6],
sig[7],
sig[8],
sig[9],
sig[10],
sig[11],
sig[12],
sig[13],
sig[14],
) {
(
@lib.LocalGet(@lib.LocalIdx(next_slot_raw)),
@lib.I32Load(_),
@lib.LocalSet(@lib.LocalIdx(reload_raw)),
@lib.LocalGet(_),
@lib.I32Load(_),
@lib.LocalTee(@lib.LocalIdx(probe_raw)),
@lib.Call(_),
@lib.LocalGet(@lib.LocalIdx(release_slot_raw)),
@lib.Call(_),
@lib.LocalGet(@lib.LocalIdx(next_probe_raw)),
@lib.LocalGet(@lib.LocalIdx(next_reload_raw)),
@lib.Call(_),
@lib.LocalSet(@lib.LocalIdx(result_raw)),
@lib.LocalGet(@lib.LocalIdx(final_probe_raw)),
@lib.Call(_),
) if next_slot_raw == slot_raw &&
release_slot_raw == slot_raw &&
next_probe_raw == probe_raw &&
final_probe_raw == probe_raw &&
next_reload_raw == reload_raw &&
reload_raw != probe_raw => (reload_raw, probe_raw, result_raw)
_ => return None
}
Some((reload_raw, probe_raw, result_raw))
}
///|
fn run_hot_pipeline_raw_vacuum_instrs_have_f1900_reload_continuation(
instrs : Array[@lib.Instruction],
start_idx : Int32,
slot_raw : UInt32,
) -> Bool {
run_hot_pipeline_raw_vacuum_f1900_reload_signature_at(
instrs,
start_idx + 1,
slot_raw,
)
is Some(_)
}
///|
fn run_hot_pipeline_raw_vacuum_instrs_have_f2094_block_continuation(
instrs : Array[@lib.Instruction],
start_idx : Int32,
slot_raw : UInt32,
) -> Bool {
if start_idx + 1 >= instrs.length() {
return false
}
let raw_continuation_body = match instrs[start_idx + 1] {
@lib.Block(@lib.VoidBlockType, @lib.Expr(body)) => body
_ => return false
}
let continuation_body : Array[@lib.Instruction] = []
for instr in raw_continuation_body {
match instr {
@lib.Nop => ()
_ => continuation_body.push(instr)
}
}
run_hot_pipeline_raw_vacuum_f1900_reload_signature_at(
continuation_body, 0, slot_raw,
)
is Some(_)
}
///|
fn run_hot_pipeline_raw_vacuum_body_is_f1900_increment_probe_prefix_before_reload_preserve_candidate(
body : Array[@lib.Instruction],
parent_body : Array[@lib.Instruction],
start_idx : Int32,
) -> Bool {
match
run_hot_pipeline_raw_vacuum_f1900_increment_probe_release_prefix_slot(body) {
Some(slot_raw) =>
run_hot_pipeline_raw_vacuum_instrs_have_f1900_reload_continuation(
parent_body, start_idx, slot_raw,
)
None => false
}
}
///|
fn run_hot_pipeline_raw_vacuum_body_is_f2094_increment_probe_prefix_before_block_continuation_preserve_candidate(
body : Array[@lib.Instruction],
parent_body : Array[@lib.Instruction],
start_idx : Int32,
) -> Bool {
match
run_hot_pipeline_raw_vacuum_f1900_increment_probe_release_prefix_slot(body) {
Some(slot_raw) =>
run_hot_pipeline_raw_vacuum_instrs_have_f2094_block_continuation(
parent_body, start_idx, slot_raw,
)
None => false
}
}
///|
fn run_hot_pipeline_raw_vacuum_body_is_f1895_return_branch_wrapper(
body : Array[@lib.Instruction],
) -> Bool {
if body.length() < 3 {
return false
}
match (body[body.length() - 2], body[body.length() - 1]) {
(@lib.I32Const(@lib.I32(1)), @lib.Return) => ()
_ => return false
}
let inner_body = match body[0] {
@lib.Block(@lib.VoidBlockType, @lib.Expr(inner_body)) => inner_body
_ => return false
}
if inner_body.length() < 13 {
return false
}
match inner_body[inner_body.length() - 1] {
@lib.Br(label) if run_hot_pipeline_raw_vacuum_label_depth(label) >= 1 => ()
_ => return false
}
match
(
inner_body[0],
inner_body[1],
inner_body[2],
inner_body[3],
inner_body[4],
inner_body[5],
inner_body[6],
inner_body[7],
inner_body[8],
inner_body[9],
inner_body[10],
inner_body[11],
) {
(
@lib.LocalGet(_),
@lib.LocalTee(_),
@lib.LocalGet(_),
@lib.LocalTee(_),
@lib.LocalGet(_),
@lib.LocalTee(_),
@lib.Call(_),
@lib.LocalTee(_),
@lib.I64Const(@lib.I64(4294967296L)),
@lib.I64Eq,
@lib.I32Eqz,
@lib.If(@lib.VoidBlockType, then_body, _),
) => then_body.length() > 0
_ => false
}
}
///|
fn run_hot_pipeline_raw_vacuum_instrs_have_f1895_call_eqz_if_continuation(
instrs : Array[@lib.Instruction],
start_idx : Int32,
) -> Bool {
if start_idx + 5 >= instrs.length() {
return false
}
match
(
instrs[start_idx],
instrs[start_idx + 1],
instrs[start_idx + 2],
instrs[start_idx + 3],
instrs[start_idx + 4],
instrs[start_idx + 5],
) {
(
@lib.LocalGet(_),
@lib.LocalGet(_),
@lib.LocalGet(_),
@lib.Call(_),
@lib.I32Eqz,
@lib.If(@lib.VoidBlockType, then_body, _),
) => then_body.length() > 0
_ => false
}
}
///|
fn run_hot_pipeline_raw_vacuum_instrs_have_f1895_empty_sibling_at(
instrs : Array[@lib.Instruction],
idx : Int32,
) -> Bool {
if idx + 1 >= instrs.length() {
return false
}
match instrs[idx] {
@lib.Block(@lib.VoidBlockType, @lib.Expr([])) => ()
_ => return false
}
let wrapper_body = match instrs[idx + 1] {
@lib.Block(@lib.VoidBlockType, @lib.Expr(wrapper_body)) => wrapper_body
_ => return false
}
run_hot_pipeline_raw_vacuum_body_is_f1895_return_branch_wrapper(wrapper_body) &&
run_hot_pipeline_raw_vacuum_instrs_have_f1895_call_eqz_if_continuation(
instrs,
idx + 2,
)
}
///|
fn run_hot_pipeline_raw_vacuum_try_drop_f1895_empty_sibling(
body : Array[@lib.Instruction],
) -> Array[@lib.Instruction]? {
for drop_idx = 0; drop_idx < body.length(); drop_idx = drop_idx + 1 {
if run_hot_pipeline_raw_vacuum_instrs_have_f1895_empty_sibling_at(
body, drop_idx,
) {
let cleaned : Array[@lib.Instruction] = []
for idx = 0; idx < body.length(); idx = idx + 1 {
if idx != drop_idx {
cleaned.push(body[idx])
}
}
return Some(cleaned)
}
}
None
}
///|
fn run_hot_pipeline_raw_vacuum_then_is_f2072_release_return(
then_body : Array[@lib.Instruction],
object_raw : UInt32,
release_raw : UInt32,
) -> Bool {
match then_body {
[
@lib.LocalGet(@lib.LocalIdx(then_release_raw)),
@lib.Call(@lib.FuncIdx(release_call_raw)),
@lib.LocalGet(@lib.LocalIdx(then_object_raw)),
@lib.Call(@lib.FuncIdx(next_release_call_raw)),
@lib.I32Const(@lib.I32(0)),
@lib.Return,
] =>
then_release_raw == release_raw &&
then_object_raw == object_raw &&
next_release_call_raw == release_call_raw
_ => false
}
}
///|
fn run_hot_pipeline_raw_vacuum_instrs_have_f2072_branch_return_continuation_at(
instrs : Array[@lib.Instruction],
start_idx : Int32,
) -> Bool {
if start_idx + 21 >= instrs.length() {
return false
}
let (object_raw, index_raw) = match
(
instrs[start_idx],
instrs[start_idx + 1],
instrs[start_idx + 2],
instrs[start_idx + 3],
instrs[start_idx + 4],
instrs[start_idx + 5],
instrs[start_idx + 6],
instrs[start_idx + 7],
instrs[start_idx + 8],
) {
(
@lib.LocalGet(_),
@lib.LocalTee(@lib.LocalIdx(object_raw)),
@lib.LocalGet(_),
@lib.LocalTee(@lib.LocalIdx(release_raw)),
@lib.LocalGet(_),
@lib.LocalTee(@lib.LocalIdx(index_raw)),
@lib.Call(_),
@lib.I32Eqz,
@lib.If(@lib.VoidBlockType, then_body, None),
) if run_hot_pipeline_raw_vacuum_then_is_f2072_release_return(
then_body, object_raw, release_raw,
) => (object_raw, index_raw)
_ => return false
}
match
(
instrs[start_idx + 9],
instrs[start_idx + 10],
instrs[start_idx + 11],
instrs[start_idx + 12],
instrs[start_idx + 13],
instrs[start_idx + 14],
instrs[start_idx + 15],
instrs[start_idx + 16],
instrs[start_idx + 17],
instrs[start_idx + 18],
instrs[start_idx + 19],
instrs[start_idx + 20],
instrs[start_idx + 21],
) {
(
@lib.LocalGet(@lib.LocalIdx(next_object_raw)),
@lib.I32Load(@lib.MemArg(_, _, @lib.U64(8UL))),
@lib.LocalTee(@lib.LocalIdx(loaded_raw)),
@lib.Call(_),
@lib.LocalGet(@lib.LocalIdx(next_loaded_raw)),
@lib.Call(_),
@lib.LocalSet(@lib.LocalIdx(length_raw)),
@lib.LocalGet(@lib.LocalIdx(release_loaded_raw)),
@lib.Call(_),
@lib.LocalGet(@lib.LocalIdx(next_index_raw)),
@lib.LocalGet(@lib.LocalIdx(next_length_raw)),
@lib.I32LtS,
@lib.If(@lib.VoidBlockType, then_body, _),
) =>
next_object_raw == object_raw &&
next_loaded_raw == loaded_raw &&
release_loaded_raw == loaded_raw &&
next_index_raw == index_raw &&
next_length_raw == length_raw &&
then_body.length() > 0
_ => false
}
}
///|
fn run_hot_pipeline_raw_vacuum_instrs_have_f2072_empty_sibling_at(
instrs : Array[@lib.Instruction],
idx : Int32,
) -> Bool {
if idx + 1 >= instrs.length() {
return false
}
match instrs[idx] {
@lib.Block(@lib.VoidBlockType, @lib.Expr([])) =>
run_hot_pipeline_raw_vacuum_instrs_have_f2072_branch_return_continuation_at(
instrs,
idx + 1,
)
_ => false
}
}
///|
fn run_hot_pipeline_raw_vacuum_try_drop_f2072_empty_sibling(
body : Array[@lib.Instruction],
) -> Array[@lib.Instruction]? {
for drop_idx = 0; drop_idx < body.length(); drop_idx = drop_idx + 1 {
if run_hot_pipeline_raw_vacuum_instrs_have_f2072_empty_sibling_at(
body, drop_idx,
) {
let cleaned : Array[@lib.Instruction] = []
for idx = 0; idx < body.length(); idx = idx + 1 {
if idx != drop_idx {
cleaned.push(body[idx])
}
}
return Some(cleaned)
}
}
None
}
///|
fn run_hot_pipeline_raw_vacuum_then_is_f2842_release_return(
then_body : Array[@lib.Instruction],
left_raw : UInt32,
right_raw : UInt32,
release_call_raw : UInt32,
) -> Bool {
match then_body {
[
@lib.LocalGet(@lib.LocalIdx(then_right_raw)),
@lib.Call(@lib.FuncIdx(first_release_call_raw)),
@lib.LocalGet(@lib.LocalIdx(then_left_raw)),
@lib.Call(@lib.FuncIdx(second_release_call_raw)),
@lib.I32Const(@lib.I32(0)),
@lib.Return,
] =>
then_right_raw == right_raw &&
then_left_raw == left_raw &&
first_release_call_raw == release_call_raw &&
second_release_call_raw == release_call_raw
_ => false
}
}
///|
fn run_hot_pipeline_raw_vacuum_instrs_have_f2842_empty_block_before_live_if_at(
instrs : Array[@lib.Instruction],
idx : Int32,
) -> Bool {
if idx + 27 >= instrs.length() {
return false
}
match instrs[idx] {
@lib.Block(@lib.VoidBlockType, @lib.Expr([])) => ()
_ => return false
}
match
(
instrs[idx + 1],
instrs[idx + 2],
instrs[idx + 3],
instrs[idx + 4],
instrs[idx + 5],
instrs[idx + 6],
instrs[idx + 7],
instrs[idx + 17],
instrs[idx + 18],
) {
(
@lib.LocalGet(_),
@lib.LocalTee(@lib.LocalIdx(left_raw)),
@lib.LocalGet(_),
@lib.LocalTee(@lib.LocalIdx(right_raw)),
@lib.Call(@lib.FuncIdx(compare_call_raw)),
@lib.I32Eqz,
@lib.If(@lib.VoidBlockType, then_body, None),
@lib.LocalGet(@lib.LocalIdx(release_right_raw)),
@lib.Call(@lib.FuncIdx(release_call_raw)),
) if release_right_raw == right_raw &&
run_hot_pipeline_raw_vacuum_then_is_f2842_release_return(
then_body, left_raw, right_raw, release_call_raw,
) =>
match
(
instrs[idx + 8],
instrs[idx + 9],
instrs[idx + 10],
instrs[idx + 11],
instrs[idx + 12],
instrs[idx + 13],
instrs[idx + 14],
instrs[idx + 15],
instrs[idx + 16],
instrs[idx + 19],
instrs[idx + 20],
instrs[idx + 21],
instrs[idx + 22],
instrs[idx + 23],
instrs[idx + 24],
instrs[idx + 25],
instrs[idx + 26],
instrs[idx + 27],
) {
(
@lib.LocalGet(@lib.LocalIdx(first_left_raw)),
@lib.LocalGet(@lib.LocalIdx(first_right_raw)),
@lib.LocalGet(@lib.LocalIdx(sub_left_raw)),
@lib.LocalGet(@lib.LocalIdx(sub_right_raw)),
@lib.Call(@lib.FuncIdx(next_compare_call_raw)),
@lib.I32Const(@lib.I32(1)),
@lib.I32Sub,
@lib.Call(_),
@lib.LocalSet(@lib.LocalIdx(count_raw)),
@lib.LocalGet(@lib.LocalIdx(load_left_raw)),
@lib.LocalGet(@lib.LocalIdx(load_count_raw)),
@lib.LocalTee(_),
@lib.Call(_),
@lib.LocalTee(_),
@lib.I32Load(@lib.MemArg(_, _, @lib.U64(0UL))),
@lib.I32Const(@lib.I32(4)),
@lib.I32Eq,
@lib.If(@lib.VoidBlockType, live_then_body, _),
) =>
first_left_raw == left_raw &&
first_right_raw == right_raw &&
sub_left_raw == left_raw &&
sub_right_raw == right_raw &&
next_compare_call_raw == compare_call_raw &&
load_left_raw == left_raw &&
load_count_raw == count_raw &&
live_then_body.length() > 0
_ => false
}
_ => false
}
}
///|
fn run_hot_pipeline_raw_vacuum_try_drop_f2842_empty_block_before_live_if(
body : Array[@lib.Instruction],
) -> Array[@lib.Instruction]? {
for drop_idx = 0; drop_idx < body.length(); drop_idx = drop_idx + 1 {
if run_hot_pipeline_raw_vacuum_instrs_have_f2842_empty_block_before_live_if_at(
body, drop_idx,
) {
let cleaned : Array[@lib.Instruction] = []
for idx = 0; idx < body.length(); idx = idx + 1 {
if idx != drop_idx {
cleaned.push(body[idx])
}
}
return Some(cleaned)
}
}
None
}
///|
fn run_hot_pipeline_raw_vacuum_body_is_f2850_guard_else(
body : Array[@lib.Instruction],
env_raw : UInt32,
index_raw : UInt32,
flag_raw : UInt32,
) -> Bool {
match body {
[
@lib.LocalGet(@lib.LocalIdx(else_env_raw)),
@lib.LocalGet(@lib.LocalIdx(else_index_raw)),
@lib.Call(_),
@lib.I32Eqz,
@lib.LocalSet(@lib.LocalIdx(else_flag_raw)),
] =>
else_env_raw == env_raw &&
else_index_raw == index_raw &&
else_flag_raw == flag_raw
_ => false
}
}
///|
fn run_hot_pipeline_raw_vacuum_body_is_f2850_release_return(
body : Array[@lib.Instruction],
value_raw : UInt32,
env_raw : UInt32,
) -> Bool {
match body {
[
@lib.LocalGet(@lib.LocalIdx(then_value_raw)),
@lib.Call(@lib.FuncIdx(first_release_call_raw)),
@lib.LocalGet(@lib.LocalIdx(then_env_raw)),
@lib.Call(@lib.FuncIdx(second_release_call_raw)),
@lib.I32Const(@lib.I32(0)),
@lib.Return,
] =>
then_value_raw == value_raw &&
then_env_raw == env_raw &&
first_release_call_raw == second_release_call_raw
_ => false
}
}
///|
fn run_hot_pipeline_raw_vacuum_try_flatten_f2850_local_copy_guard_wrapper(
body : Array[@lib.Instruction],
) -> Array[@lib.Instruction]? {
if body.length() < 10 {
return None
}
let copy_body = match body[0] {
@lib.Block(@lib.VoidBlockType, @lib.Expr(copy_body)) => copy_body
_ => return None
}
let value_raw = match copy_body {
[
@lib.LocalGet(@lib.LocalIdx(source_raw)),
@lib.LocalSet(@lib.LocalIdx(target_raw)),
] if source_raw != target_raw => target_raw
_ => return None
}
match
(
body[1],
body[2],
body[3],
body[4],
body[5],
body[6],
body[7],
body[8],
body[9],
) {
(
@lib.LocalGet(_),
@lib.LocalSet(@lib.LocalIdx(env_raw)),
@lib.LocalGet(_),
@lib.LocalTee(@lib.LocalIdx(index_raw)),
@lib.I32Const(@lib.I32(0)),
@lib.I32LtS,
@lib.If(@lib.VoidBlockType, guard_then, Some(guard_else)),
@lib.LocalGet(@lib.LocalIdx(flag_get_raw)),
@lib.If(@lib.VoidBlockType, return_then, None),
) => {
let flag_raw = match guard_then {
[
@lib.I32Const(@lib.I32(1)),
@lib.LocalSet(@lib.LocalIdx(then_flag_raw)),
] => then_flag_raw
_ => return None
}
if flag_get_raw != flag_raw ||
!run_hot_pipeline_raw_vacuum_body_is_f2850_guard_else(
guard_else, env_raw, index_raw, flag_raw,
) ||
!run_hot_pipeline_raw_vacuum_body_is_f2850_release_return(
return_then, value_raw, env_raw,
) {
return None
}
let flattened : Array[@lib.Instruction] = []
for instr in copy_body {
flattened.push(instr)
}
for idx = 1; idx < body.length(); idx = idx + 1 {
flattened.push(body[idx])
}
Some(flattened)
}
_ => None
}
}
///|
fn run_hot_pipeline_raw_vacuum_body_contains_local_release_call(
body : Array[@lib.Instruction],
release_call_raw : UInt32,
) -> Bool {
for idx = 0; idx + 1 < body.length(); idx = idx + 1 {
match (body[idx], body[idx + 1]) {
(@lib.LocalGet(_), @lib.Call(@lib.FuncIdx(call_raw))) if call_raw ==
release_call_raw => return true
_ => ()
}
}
for instr in body {
match instr {
@lib.Block(_, @lib.Expr(child_body))
| @lib.Loop(_, @lib.Expr(child_body))
| @lib.TryTable(_, _, @lib.Expr(child_body)) =>
if run_hot_pipeline_raw_vacuum_body_contains_local_release_call(
child_body, release_call_raw,
) {
return true
}
@lib.If(_, then_body, else_body) => {
if run_hot_pipeline_raw_vacuum_body_contains_local_release_call(
then_body, release_call_raw,
) {
return true
}
match else_body {
Some(child_body) =>
if run_hot_pipeline_raw_vacuum_body_contains_local_release_call(
child_body, release_call_raw,
) {
return true
}
None => ()
}
}
_ => ()
}
}
false
}
///|
fn run_hot_pipeline_raw_vacuum_body_ends_with_zero_set_and_outer_br(
body : Array[@lib.Instruction],
) -> Bool {
if body.length() < 3 {
return false
}
match
(body[body.length() - 3], body[body.length() - 2], body[body.length() - 1]) {
(@lib.I32Const(@lib.I32(0)), @lib.LocalSet(_), @lib.Br(label)) =>
run_hot_pipeline_raw_vacuum_label_depth(label) >= 1
_ => false
}
}
///|
fn run_hot_pipeline_raw_vacuum_body_has_f1155_branch_continuation(
body : Array[@lib.Instruction],
release_call_raw : UInt32,
) -> Bool {
if body.length() < 5 ||
!run_hot_pipeline_raw_vacuum_body_ends_with_zero_set_and_outer_br(body) {
return false
}
match body[body.length() - 4] {
@lib.If(@lib.VoidBlockType, then_body, Some(else_body)) =>
run_hot_pipeline_raw_vacuum_body_ends_with_outer_br(then_body, 1) &&
run_hot_pipeline_raw_vacuum_body_contains_local_release_call(
else_body, release_call_raw,
)
_ => false
}
}
///|
fn run_hot_pipeline_raw_vacuum_try_flatten_f1155_single_release_child_before_branch_continuation(
body : Array[@lib.Instruction],
) -> Array[@lib.Instruction]? {
if body.length() < 5 {
return None
}
let release_body = match body[0] {
@lib.Block(@lib.VoidBlockType, @lib.Expr(release_body)) => release_body
_ => return None
}
if release_body.length() != 2 {
return None
}
let release_call_raw = match (release_body[0], release_body[1]) {
(@lib.LocalGet(_), @lib.Call(@lib.FuncIdx(call_raw))) => call_raw
_ => return None
}
if !run_hot_pipeline_raw_vacuum_body_has_f1155_branch_continuation(
body, release_call_raw,
) {
return None
}
let flattened : Array[@lib.Instruction] = []
for instr in release_body {
flattened.push(instr)
}
for idx = 1; idx < body.length(); idx = idx + 1 {
flattened.push(body[idx])
}
Some(flattened)
}
///|
fn run_hot_pipeline_raw_vacuum_body_is_f1224_loop_release_update_prefix(
body : Array[@lib.Instruction],
) -> (UInt32, UInt32)? {
if body.length() != 13 {
return None
}
let (slot_raw, item_raw) = match (body[0], body[1], body[2], body[3]) {
(
@lib.LocalGet(@lib.LocalIdx(slot_raw)),
@lib.I32Load(_),
@lib.LocalTee(@lib.LocalIdx(item_raw)),
@lib.Call(_),
) => (slot_raw, item_raw)
_ => return None
}
let update_raw = match (body[4], body[5], body[6], body[7]) {
(
@lib.LocalGet(@lib.LocalIdx(next_item_raw)),
@lib.LocalGet(_),
@lib.Call(_),
@lib.LocalSet(@lib.LocalIdx(update_raw)),
) if next_item_raw == item_raw => update_raw
_ => return None
}
match (body[8], body[9], body[10], body[11]) {
(
@lib.LocalGet(@lib.LocalIdx(next_item_raw)),
@lib.Call(_),
@lib.LocalGet(@lib.LocalIdx(next_update_raw)),
@lib.LocalTee(@lib.LocalIdx(result_raw)),
) if next_item_raw == item_raw && next_update_raw == update_raw =>
match body[12] {
@lib.If(@lib.VoidBlockType, then_body, Some(else_body)) => {
let target_raw = match then_body {
[
@lib.LocalGet(@lib.LocalIdx(then_result_raw)),
@lib.LocalSet(@lib.LocalIdx(target_raw)),
] if then_result_raw == result_raw => target_raw
_ => return None
}
match else_body {
[
@lib.LocalGet(@lib.LocalIdx(else_result_raw)),
@lib.If(@lib.VoidBlockType, inner_then, None),
@lib.Br(label),
] if else_result_raw == result_raw &&
run_hot_pipeline_raw_vacuum_label_depth(label) >= 1 =>
match inner_then {
[@lib.LocalGet(@lib.LocalIdx(inner_result_raw)), @lib.Call(_)] if inner_result_raw ==
result_raw => Some((target_raw, slot_raw))
_ => None
}
_ => None
}
}
_ => None
}
_ => None
}
}
///|
fn run_hot_pipeline_raw_vacuum_body_has_f1224_loop_update_parent_continuation(
body : Array[@lib.Instruction],
start_idx : Int32,
target_raw : UInt32,
slot_raw : UInt32,
) -> Bool {
let sig : Array[@lib.Instruction] = []
for idx = start_idx + 1; idx < body.length(); idx = idx + 1 {
match body[idx] {
@lib.Nop => ()
instr => sig.push(instr)
}
if sig.length() >= 24 {
break
}
}
if sig.length() < 24 {
return false
}
let loaded_raw = match (sig[0], sig[1], sig[2]) {
(
@lib.LocalGet(@lib.LocalIdx(next_target_raw)),
@lib.I32Load(_),
@lib.LocalSet(@lib.LocalIdx(loaded_raw)),
) if next_target_raw == target_raw => loaded_raw
_ => return false
}
match (sig[3], sig[4], sig[5], sig[6], sig[7], sig[8]) {
(
@lib.LocalGet(@lib.LocalIdx(next_target_raw)),
@lib.Call(_),
@lib.LocalTee(_),
@lib.I32Const(@lib.I32(1)),
@lib.I32GtS,
@lib.If(@lib.VoidBlockType, _, _),
) if next_target_raw == target_raw => ()
_ => return false
}
match
(
sig[9],
sig[10],
sig[11],
sig[12],
sig[13],
sig[14],
sig[15],
sig[16],
sig[17],
sig[18],
sig[19],
sig[20],
sig[21],
sig[22],
sig[23],
) {
(
@lib.LocalGet(@lib.LocalIdx(release_slot_raw)),
@lib.I32Load(_),
@lib.Call(_),
@lib.LocalGet(@lib.LocalIdx(store_slot_raw)),
@lib.LocalGet(@lib.LocalIdx(next_loaded_raw)),
@lib.I32Store(_),
@lib.LocalGet(_),
@lib.I32Const(@lib.I32(1)),
@lib.I32Store(_),
@lib.LocalGet(@lib.LocalIdx(count_slot_raw)),
@lib.LocalGet(@lib.LocalIdx(next_count_slot_raw)),
@lib.I32Load(_),
@lib.I32Const(@lib.I32(1)),
@lib.I32Add,
@lib.I32Store(_),
) =>
release_slot_raw == slot_raw &&
store_slot_raw == slot_raw &&
next_loaded_raw == loaded_raw &&
count_slot_raw == next_count_slot_raw
_ => false
}
}
///|
fn run_hot_pipeline_raw_vacuum_body_is_f1224_loop_release_update_preserve_candidate(
body : Array[@lib.Instruction],
parent_body : Array[@lib.Instruction],
start_idx : Int32,
) -> Bool {
match
run_hot_pipeline_raw_vacuum_body_is_f1224_loop_release_update_prefix(body) {
Some((target_raw, slot_raw)) =>
run_hot_pipeline_raw_vacuum_body_has_f1224_loop_update_parent_continuation(
parent_body, start_idx, target_raw, slot_raw,
)
None => false
}
}
///|
fn run_hot_pipeline_raw_vacuum_body_is_f1260_tag21_tag22_dispatch(
body : Array[@lib.Instruction],
) -> Bool {
if body.length() != 12 {
return false
}
let (cursor_raw, slot_raw, item_raw, tag_raw) = match
(
body[0],
body[1],
body[2],
body[3],
body[4],
body[5],
body[6],
body[7],
body[8],
body[9],
body[10],
) {
(
@lib.LocalGet(@lib.LocalIdx(cursor_raw)),
@lib.LocalGet(@lib.LocalIdx(slot_raw)),
@lib.I32Load(_),
@lib.I32Const(@lib.I32(1)),
@lib.I32Add,
@lib.Call(_),
@lib.LocalTee(@lib.LocalIdx(item_raw)),
@lib.I32Load(_),
@lib.LocalTee(@lib.LocalIdx(tag_raw)),
@lib.I32Const(@lib.I32(21)),
@lib.I32Eq,
) => (cursor_raw, slot_raw, item_raw, tag_raw)
_ => return false
}
if cursor_raw == slot_raw {
return false
}
match body[11] {
@lib.If(@lib.VoidBlockType, then_body, Some(else_body)) => {
if then_body.length() < 10 || else_body.length() < 5 {
return false
}
let lhs_raw = match
(then_body[0], then_body[1], then_body[2], then_body[3], then_body[4]) {
(
@lib.LocalGet(@lib.LocalIdx(next_item_raw)),
@lib.I32Load(_),
@lib.LocalSet(@lib.LocalIdx(lhs_raw)),
@lib.LocalGet(@lib.LocalIdx(release_item_raw)),
@lib.Call(_),
) if next_item_raw == item_raw && release_item_raw == item_raw =>
lhs_raw
_ => return false
}
match (then_body[5], then_body[6], then_body[7], then_body[8]) {
(
@lib.LocalGet(@lib.LocalIdx(next_lhs_raw)),
@lib.LocalGet(_),
@lib.Call(_),
@lib.I32Eqz,
) if next_lhs_raw == lhs_raw => ()
_ => return false
}
match then_body[9] {
@lib.If(@lib.VoidBlockType, [@lib.Br(label)], None) if run_hot_pipeline_raw_vacuum_label_depth(
label,
) >=
1 => ()
_ => return false
}
match (else_body[0], else_body[1], else_body[2]) {
(
@lib.LocalGet(@lib.LocalIdx(next_tag_raw)),
@lib.I32Const(@lib.I32(22)),
@lib.I32Eq,
) if next_tag_raw == tag_raw => ()
_ => return false
}
match else_body[3] {
@lib.If(@lib.VoidBlockType, nested_then, Some(nested_else)) => {
if nested_then.length() < 10 || nested_else.length() < 3 {
return false
}
let nested_lhs_raw = match
(
nested_then[0],
nested_then[1],
nested_then[2],
nested_then[3],
nested_then[4],
) {
(
@lib.LocalGet(@lib.LocalIdx(next_item_raw)),
@lib.I32Load(_),
@lib.LocalSet(@lib.LocalIdx(next_lhs_raw)),
@lib.LocalGet(@lib.LocalIdx(release_item_raw)),
@lib.Call(_),
) if next_item_raw == item_raw && release_item_raw == item_raw =>
next_lhs_raw
_ => return false
}
match
(nested_then[5], nested_then[6], nested_then[7], nested_then[8]) {
(
@lib.LocalGet(@lib.LocalIdx(next_lhs_raw)),
@lib.LocalGet(_),
@lib.Call(_),
@lib.If(
@lib.VoidBlockType,
[@lib.Br(then_label)],
Some([@lib.Br(else_label)])
),
) if next_lhs_raw == nested_lhs_raw &&
run_hot_pipeline_raw_vacuum_label_depth(then_label) >= 1 &&
run_hot_pipeline_raw_vacuum_label_depth(else_label) >= 1 => ()
_ => return false
}
if !(nested_then[nested_then.length() - 1] is @lib.Unreachable) {
return false
}
match (nested_else[0], nested_else[1], nested_else[2]) {
(
@lib.LocalGet(@lib.LocalIdx(release_item_raw)),
@lib.Call(_),
@lib.Br(label),
) if release_item_raw == item_raw &&
run_hot_pipeline_raw_vacuum_label_depth(label) >= 1 => ()
_ => return false
}
}
_ => return false
}
else_body[else_body.length() - 1] is @lib.Unreachable
}
_ => false
}
}
///|
fn run_hot_pipeline_raw_vacuum_body_has_f1260_append_update_continuation(
body : Array[@lib.Instruction],
) -> Bool {
if body.length() < 17 {
return false
}
let temp_raw = match (body[1], body[2], body[3], body[4], body[5]) {
(
@lib.LocalGet(_),
@lib.Call(_),
@lib.LocalTee(@lib.LocalIdx(temp_raw)),
@lib.Call(_),
@lib.LocalSet(_),
) => temp_raw
_ => return false
}
match (body[6], body[7]) {
(@lib.LocalGet(@lib.LocalIdx(next_temp_raw)), @lib.Call(_)) if next_temp_raw ==
temp_raw => ()
_ => return false
}
match
(
body[8],
body[9],
body[10],
body[11],
body[12],
body[13],
body[14],
body[15],
body[16],
) {
(
@lib.LocalGet(_),
@lib.I32Const(@lib.I32(1)),
@lib.I32Store(_),
@lib.LocalGet(@lib.LocalIdx(counter_raw)),
@lib.LocalGet(@lib.LocalIdx(next_counter_raw)),
@lib.I32Load(_),
@lib.I32Const(@lib.I32(1)),
@lib.I32Add,
@lib.I32Store(_),
) => counter_raw == next_counter_raw
_ => false
}
}
///|
fn run_hot_pipeline_raw_vacuum_try_flatten_f1260_tag_dispatch_child_before_append_update(
body : Array[@lib.Instruction],
) -> Array[@lib.Instruction]? {
if body.length() < 2 {
return None
}
let dispatch_body = match body[0] {
@lib.Block(@lib.VoidBlockType, @lib.Expr(dispatch_body)) => dispatch_body
_ => return None
}
if !run_hot_pipeline_raw_vacuum_body_is_f1260_tag21_tag22_dispatch(
dispatch_body,
) ||
!run_hot_pipeline_raw_vacuum_body_has_f1260_append_update_continuation(body) {
return None
}
let flattened = run_hot_pipeline_raw_vacuum_rebase_outer_body(
dispatch_body, 0,
)
for idx = 1; idx < body.length(); idx = idx + 1 {
flattened.push(body[idx])
}
Some(flattened)
}
///|
fn run_hot_pipeline_raw_vacuum_body_is_f2130_tag42_tag43_dispatch(
body : Array[@lib.Instruction],
) -> Int32? {
let non_nops : Array[@lib.Instruction] = []
for instr in body {
match instr {
@lib.Nop => ()
_ => non_nops.push(instr)
}
}
if non_nops.length() != body.length() {
return run_hot_pipeline_raw_vacuum_body_is_f2130_tag42_tag43_dispatch(
non_nops,
)
}
if body.length() != 11 {
return None
}
let (object_raw, index_raw, tagged_raw, tag_raw) = match
(
body[0],
body[1],
body[2],
body[3],
body[4],
body[5],
body[6],
body[7],
body[8],
body[9],
) {
(
@lib.LocalGet(_),
@lib.LocalTee(@lib.LocalIdx(object_raw)),
@lib.LocalGet(_),
@lib.LocalTee(@lib.LocalIdx(index_raw)),
@lib.Call(_),
@lib.LocalTee(@lib.LocalIdx(tagged_raw)),
@lib.I32Load(_),
@lib.LocalTee(@lib.LocalIdx(tag_raw)),
@lib.I32Const(@lib.I32(42)),
@lib.I32Eq,
) => (object_raw, index_raw, tagged_raw, tag_raw)
_ => return None
}
match body[10] {
@lib.If(@lib.VoidBlockType, then_body, Some(else_body)) => {
let result_raw = match
run_hot_pipeline_raw_vacuum_body_ends_local_set(then_body) {
Some(raw) => raw.reinterpret_as_uint()
None => return None
}
if then_body.length() < 18 {
return None
}
let (payload_raw, retained_raw) = match
(
then_body[0],
then_body[1],
then_body[2],
then_body[3],
then_body[4],
then_body[5],
then_body[6],
then_body[7],
then_body[8],
) {
(
@lib.LocalGet(@lib.LocalIdx(next_tagged_raw)),
@lib.I32Load(_),
@lib.LocalSet(@lib.LocalIdx(payload_raw)),
@lib.LocalGet(@lib.LocalIdx(release_tagged_raw)),
@lib.Call(_),
@lib.LocalGet(@lib.LocalIdx(next_object_raw)),
@lib.LocalGet(@lib.LocalIdx(next_payload_raw)),
@lib.Call(_),
@lib.LocalSet(@lib.LocalIdx(retained_raw)),
) if next_tagged_raw == tagged_raw &&
release_tagged_raw == tagged_raw &&
next_object_raw == object_raw &&
next_payload_raw == payload_raw => (payload_raw, retained_raw)
_ => return None
}
ignore(payload_raw)
let allocated_raw = match
(then_body[then_body.length() - 2], then_body[then_body.length() - 1]) {
(
@lib.LocalGet(@lib.LocalIdx(next_allocated_raw)),
@lib.LocalSet(@lib.LocalIdx(next_result_raw)),
) if next_result_raw == result_raw => next_allocated_raw
_ => return None
}
let mut saw_allocation = false
let mut saw_alloc_release = false
for idx = 9; idx + 5 < then_body.length(); idx = idx + 1 {
match
(
then_body[idx],
then_body[idx + 1],
then_body[idx + 2],
then_body[idx + 3],
then_body[idx + 4],
then_body[idx + 5],
) {
(
@lib.LocalGet(@lib.LocalIdx(next_retained_raw)),
@lib.I32Const(@lib.I32(0)),
@lib.Call(_),
@lib.LocalTee(_),
@lib.Call(_),
@lib.LocalSet(@lib.LocalIdx(next_allocated_raw)),
) if next_retained_raw == retained_raw &&
next_allocated_raw == allocated_raw => saw_allocation = true
_ => ()
}
}
for idx = 9; idx + 1 < then_body.length(); idx = idx + 1 {
match (then_body[idx], then_body[idx + 1]) {
(@lib.LocalGet(@lib.LocalIdx(next_retained_raw)), @lib.Call(_)) if next_retained_raw ==
retained_raw => saw_alloc_release = true
_ => ()
}
}
if !saw_allocation || !saw_alloc_release {
return None
}
if else_body.length() < 6 {
return None
}
match (else_body[0], else_body[1], else_body[2]) {
(
@lib.LocalGet(@lib.LocalIdx(next_tag_raw)),
@lib.I32Const(@lib.I32(43)),
@lib.I32Eq,
) if next_tag_raw == tag_raw => ()
_ => return None
}
match else_body[3] {
@lib.If(@lib.VoidBlockType, tag43_then, Some(tag43_else)) => {
if tag43_then.length() < 8 || tag43_else.length() < 4 {
return None
}
match (tag43_then[0], tag43_then[1]) {
(@lib.LocalGet(@lib.LocalIdx(release_tagged_raw)), @lib.Call(_)) if release_tagged_raw ==
tagged_raw => ()
_ => return None
}
match
(
tag43_then[2],
tag43_then[3],
tag43_then[4],
tag43_then[5],
tag43_then[6],
) {
(
@lib.LocalGet(@lib.LocalIdx(next_object_raw)),
@lib.LocalGet(@lib.LocalIdx(next_index_raw)),
@lib.Call(_),
@lib.I32Const(@lib.I32(1)),
@lib.I32Ne,
) if next_object_raw == object_raw && next_index_raw == index_raw =>
()
_ => return None
}
match tag43_then[7] {
@lib.If(@lib.VoidBlockType, release_then, Some(update_else)) => {
if release_then.length() < 2 || update_else.length() < 8 {
return None
}
match (release_then[0], release_then[1]) {
(@lib.LocalGet(@lib.LocalIdx(release_object_raw)), @lib.Call(_)) if release_object_raw ==
object_raw => ()
_ => return None
}
match
(
update_else[0],
update_else[1],
update_else[2],
update_else[3],
update_else[4],
update_else[5],
update_else[6],
update_else[7],
) {
(
@lib.LocalGet(@lib.LocalIdx(next_object_raw)),
@lib.LocalSet(_),
@lib.LocalGet(@lib.LocalIdx(update_object_raw)),
@lib.LocalGet(@lib.LocalIdx(update_index_raw)),
@lib.I32Const(@lib.I32(0)),
@lib.Call(_),
@lib.LocalSet(_),
@lib.Br(label),
) if next_object_raw == object_raw &&
update_object_raw == object_raw &&
update_index_raw == index_raw &&
run_hot_pipeline_raw_vacuum_label_depth(label) >= 3 => ()
_ => return None
}
}
_ => return None
}
match (tag43_else[0], tag43_else[1], tag43_else[2], tag43_else[3]) {
(
@lib.LocalGet(@lib.LocalIdx(release_tagged_raw)),
@lib.Call(_),
@lib.LocalGet(@lib.LocalIdx(release_object_raw)),
@lib.Call(_),
) if release_tagged_raw == tagged_raw &&
release_object_raw == object_raw => ()
_ => return None
}
}
_ => return None
}
match
(else_body[else_body.length() - 2], else_body[else_body.length() - 1]) {
(
@lib.I32Const(@lib.I32(0)),
@lib.LocalSet(@lib.LocalIdx(next_result_raw)),
) if next_result_raw == result_raw =>
Some(result_raw.reinterpret_as_int())
_ => None
}
}
_ => None
}
}
///|
fn run_hot_pipeline_raw_vacuum_try_flatten_f2130_tag42_tag43_loop_head_dispatch_wrapper(
body : Array[@lib.Instruction],
) -> Array[@lib.Instruction]? {
if body.length() < 4 {
return None
}
let dispatch_body = match body[0] {
@lib.Block(@lib.VoidBlockType, @lib.Expr(dispatch_body)) => dispatch_body
_ => return None
}
let result_raw = match
run_hot_pipeline_raw_vacuum_body_is_f2130_tag42_tag43_dispatch(
dispatch_body,
) {
Some(raw) => raw
None => return None
}
match (body[1], body[2], body[3]) {
(
@lib.LocalGet(@lib.LocalIdx(next_result_raw)),
@lib.LocalSet(_),
@lib.Br(label),
) if next_result_raw.reinterpret_as_int() == result_raw &&
run_hot_pipeline_raw_vacuum_label_depth(label) >= 1 => ()
_ => return None
}
let flattened = run_hot_pipeline_raw_vacuum_rebase_outer_body(
dispatch_body, 0,
)
for idx = 1; idx < body.length(); idx = idx + 1 {
flattened.push(body[idx])
}
Some(flattened)
}
///|
fn run_hot_pipeline_raw_vacuum_body_has_f996_tag_dispatch_parent_continuation(
body : Array[@lib.Instruction],
start_idx : Int32,
) -> Bool {
let sig : Array[@lib.Instruction] = []
for idx = start_idx + 1; idx < body.length(); idx = idx + 1 {
match body[idx] {
@lib.Nop => ()
instr => sig.push(instr)
}
if sig.length() >= 9 {
break
}
}
if sig.length() < 9 {
return false
}
match
(sig[0], sig[1], sig[2], sig[3], sig[4], sig[5], sig[6], sig[7], sig[8]) {
(
@lib.LocalGet(_),
@lib.I32Const(@lib.I32(4)),
@lib.Call(_),
@lib.LocalTee(_),
@lib.I32Load(_),
@lib.LocalTee(_),
@lib.I32Const(@lib.I32(241)),
@lib.I32Eq,
@lib.If(@lib.VoidBlockType, _, _),
) => true
_ => false
}
}
///|
fn run_hot_pipeline_raw_vacuum_body_is_call_local_set_tag_cascade_if_wrapper_candidate(
body : Array[@lib.Instruction],
) -> Bool {
let non_nops : Array[@lib.Instruction] = []
for instr in body {
match instr {
@lib.Nop => ()
_ => non_nops.push(instr)
}
}
if non_nops.length() < 13 {
return false
}
let (receiver_raw, start_value, call_raw, first_local_raw) = match
(non_nops[0], non_nops[1], non_nops[2], non_nops[3]) {
(
@lib.LocalGet(@lib.LocalIdx(receiver_raw)),
@lib.I32Const(@lib.I32(start_value)),
@lib.Call(@lib.FuncIdx(call_raw)),
@lib.LocalSet(@lib.LocalIdx(first_local_raw)),
) => (receiver_raw, start_value, call_raw, first_local_raw)
_ => return false
}
let mut group_count = 1
while group_count < 4 && (group_count + 1) * 4 + 5 <= non_nops.length() {
let base = group_count * 4
match
(
non_nops[base],
non_nops[base + 1],
non_nops[base + 2],
non_nops[base + 3],
) {
(
@lib.LocalGet(@lib.LocalIdx(next_receiver_raw)),
@lib.I32Const(@lib.I32(value)),
@lib.Call(@lib.FuncIdx(next_call_raw)),
@lib.LocalSet(@lib.LocalIdx(next_local_raw)),
) if next_receiver_raw == receiver_raw &&
next_call_raw == call_raw &&
value == start_value + group_count &&
next_local_raw != first_local_raw => group_count = group_count + 1
_ => break
}
}
if group_count < 2 || non_nops.length() != group_count * 4 + 5 {
return false
}
let tag_idx = group_count * 4
match
(
non_nops[tag_idx],
non_nops[tag_idx + 1],
non_nops[tag_idx + 2],
non_nops[tag_idx + 3],
non_nops[tag_idx + 4],
) {
(
@lib.LocalGet(@lib.LocalIdx(tag_local_raw)),
@lib.I32Load(_),
@lib.I32Const(@lib.I32(tag_value)),
@lib.I32Eq,
@lib.If(@lib.VoidBlockType, _, _),
) =>
tag_local_raw == first_local_raw &&
(
(group_count == 4 && start_value == 0 && tag_value == 20) ||
(group_count == 2 && start_value == 4 && tag_value == 72)
) &&
// Keep the same trap guard after the complete flat tag signature.
!run_hot_pipeline_raw_vacuum_instrs_contain_unreachable(non_nops)
_ => false
}
}
///|
fn run_hot_pipeline_raw_vacuum_indexed_tag_cascade_expected_offset(
group : Int32,
) -> Int32 {
match group {
0 => 0
1 => 2
2 => 3
3 => 5
4 => 6
5 => 7
6 => 8
7 => 9
_ => 10
}
}
///|
fn run_hot_pipeline_raw_vacuum_body_is_indexed_call_local_set_tag_cascade_if_wrapper_candidate(
body : Array[@lib.Instruction],
) -> Bool {
let non_nops = match vacuum_indexed_tag_candidate_non_nops(body) {
Some(row) => row
None => return false
}
if run_hot_pipeline_raw_vacuum_instrs_contain_unreachable(non_nops) {
return false
}
if non_nops.length() == 57 {
let (base_raw, index_raw, call_raw, first_local_raw) = match
(non_nops[0], non_nops[1], non_nops[2], non_nops[3]) {
(
@lib.LocalGet(@lib.LocalIdx(base_raw)),
@lib.LocalGet(@lib.LocalIdx(index_raw)),
@lib.Call(@lib.FuncIdx(call_raw)),
@lib.LocalSet(@lib.LocalIdx(first_local_raw)),
) => (base_raw, index_raw, call_raw, first_local_raw)
_ => return false
}
let first_local = first_local_raw.reinterpret_as_int()
let mut cursor = 4
let mut group = 1
while group < 9 {
if cursor + 6 > non_nops.length() {
return false
}
let expected_offset = run_hot_pipeline_raw_vacuum_indexed_tag_cascade_expected_offset(
group,
)
match
(
non_nops[cursor],
non_nops[cursor + 1],
non_nops[cursor + 2],
non_nops[cursor + 3],
non_nops[cursor + 4],
non_nops[cursor + 5],
) {
(
@lib.LocalGet(@lib.LocalIdx(next_base_raw)),
@lib.LocalGet(@lib.LocalIdx(next_index_raw)),
@lib.I32Const(@lib.I32(offset)),
@lib.I32Add,
@lib.Call(@lib.FuncIdx(next_call_raw)),
@lib.LocalSet(@lib.LocalIdx(next_local_raw)),
) if next_base_raw == base_raw &&
next_index_raw == index_raw &&
offset == expected_offset &&
next_call_raw == call_raw &&
next_local_raw.reinterpret_as_int() == first_local + group => {
cursor = cursor + 6
group = group + 1
}
_ => return false
}
}
return match
(
non_nops[cursor],
non_nops[cursor + 1],
non_nops[cursor + 2],
non_nops[cursor + 3],
non_nops[cursor + 4],
) {
(
@lib.LocalGet(@lib.LocalIdx(tag_local_raw)),
@lib.I32Load(_),
@lib.I32Const(@lib.I32(10)),
@lib.I32Eq,
@lib.If(@lib.VoidBlockType, _, _),
) => tag_local_raw == first_local_raw
_ => false
}
}
if non_nops.length() != 51 {
return false
}
let (base_raw, index_raw, call_raw, first_local_raw) = match
(
non_nops[0],
non_nops[1],
non_nops[2],
non_nops[3],
non_nops[4],
non_nops[5],
) {
(
@lib.LocalGet(@lib.LocalIdx(base_raw)),
@lib.LocalGet(@lib.LocalIdx(index_raw)),
@lib.I32Const(@lib.I32(1)),
@lib.I32Sub,
@lib.Call(@lib.FuncIdx(call_raw)),
@lib.LocalSet(@lib.LocalIdx(first_local_raw)),
) => (base_raw, index_raw, call_raw, first_local_raw)
_ => return false
}
let first_local = first_local_raw.reinterpret_as_int()
match (non_nops[6], non_nops[7], non_nops[8], non_nops[9]) {
(
@lib.LocalGet(@lib.LocalIdx(next_base_raw)),
@lib.LocalGet(@lib.LocalIdx(next_index_raw)),
@lib.Call(@lib.FuncIdx(next_call_raw)),
@lib.LocalSet(@lib.LocalIdx(next_local_raw)),
) if next_base_raw == base_raw &&
next_index_raw == index_raw &&
next_call_raw == call_raw &&
next_local_raw.reinterpret_as_int() == first_local + 1 => ()
_ => return false
}
let mut cursor = 10
let mut group = 2
while group < 8 {
let expected_offset = match group {
2 => 1
3 => 3
4 => 4
5 => 5
6 => 7
_ => 8
}
match
(
non_nops[cursor],
non_nops[cursor + 1],
non_nops[cursor + 2],
non_nops[cursor + 3],
non_nops[cursor + 4],
non_nops[cursor + 5],
) {
(
@lib.LocalGet(@lib.LocalIdx(next_base_raw)),
@lib.LocalGet(@lib.LocalIdx(next_index_raw)),
@lib.I32Const(@lib.I32(offset)),
@lib.I32Add,
@lib.Call(@lib.FuncIdx(next_call_raw)),
@lib.LocalSet(@lib.LocalIdx(next_local_raw)),
) if next_base_raw == base_raw &&
next_index_raw == index_raw &&
offset == expected_offset &&
next_call_raw == call_raw &&
next_local_raw.reinterpret_as_int() == first_local + group => {
cursor = cursor + 6
group = group + 1
}
_ => return false
}
}
match
(
non_nops[cursor],
non_nops[cursor + 1],
non_nops[cursor + 2],
non_nops[cursor + 3],
non_nops[cursor + 4],
) {
(
@lib.LocalGet(@lib.LocalIdx(tag_local_raw)),
@lib.I32Load(_),
@lib.I32Const(@lib.I32(41)),
@lib.I32Eq,
@lib.If(@lib.VoidBlockType, _, _),
) => tag_local_raw == first_local_raw
_ => false
}
}
///|
fn run_hot_pipeline_raw_vacuum_body_is_load_linked_local_set_call_prefix_if_wrapper_candidate(
body : Array[@lib.Instruction],
) -> Bool {
let non_nops : Array[@lib.Instruction] = []
for instr in body {
match instr {
@lib.Nop => ()
_ => non_nops.push(instr)
}
}
if non_nops.length() != body.length() {
return run_hot_pipeline_raw_vacuum_body_is_load_linked_local_set_call_prefix_if_wrapper_candidate(
non_nops,
)
}
if body.length() != 11 {
return false
}
let target_local = match body[3] {
@lib.LocalSet(@lib.LocalIdx(raw)) => raw.reinterpret_as_int()
_ => return false
}
match body[4] {
@lib.LocalGet(@lib.LocalIdx(raw)) if raw.reinterpret_as_int() ==
target_local => ()
_ => return false
}
match body[10] {
@lib.If(@lib.VoidBlockType, [@lib.Unreachable], None) => ()
_ => return false
}
body[0] is @lib.I32Const(_) &&
body[1] is @lib.I32Const(_) &&
body[2] is @lib.Call(_) &&
body[5] is @lib.LocalTee(_) &&
body[6] is @lib.I32Load(_) &&
body[7] is @lib.I32Const(@lib.I32(20)) &&
body[8] is @lib.I32Eq &&
body[9] is @lib.I32Eqz
}
///|
fn run_hot_pipeline_raw_vacuum_body_is_singleton_load_linked_local_set_call_prefix_block(
body : Array[@lib.Instruction],
) -> Bool {
body.length() == 1 &&
(match body[0] {
@lib.Block(@lib.VoidBlockType, @lib.Expr(child_body)) =>
run_hot_pipeline_raw_vacuum_body_is_load_linked_local_set_call_prefix_if_wrapper_candidate(
child_body,
)
_ => false
})
}
///|
fn run_hot_pipeline_raw_vacuum_instr_has_singleton_load_linked_local_set_call_prefix_block(
instr : @lib.Instruction,
) -> Bool {
match instr {
@lib.Block(@lib.VoidBlockType, @lib.Expr(body)) =>
run_hot_pipeline_raw_vacuum_body_is_singleton_load_linked_local_set_call_prefix_block(
body,
) ||
run_hot_pipeline_raw_vacuum_instrs_have_singleton_load_linked_local_set_call_prefix_block(
body,
)
@lib.Block(_, @lib.Expr(body))
| @lib.Loop(_, @lib.Expr(body))
| @lib.TryTable(_, _, @lib.Expr(body)) =>
run_hot_pipeline_raw_vacuum_instrs_have_singleton_load_linked_local_set_call_prefix_block(
body,
)
@lib.If(_, then_body, else_body) => {
if run_hot_pipeline_raw_vacuum_instrs_have_singleton_load_linked_local_set_call_prefix_block(
then_body,
) {
return true
}
match else_body {
Some(body) =>
run_hot_pipeline_raw_vacuum_instrs_have_singleton_load_linked_local_set_call_prefix_block(
body,
)
None => false
}
}
_ => false
}
}
///|
fn run_hot_pipeline_raw_vacuum_instrs_have_singleton_load_linked_local_set_call_prefix_block(
instrs : Array[@lib.Instruction],
) -> Bool {
for instr in instrs {
if run_hot_pipeline_raw_vacuum_instr_has_singleton_load_linked_local_set_call_prefix_block(
instr,
) {
return true
}
}
false
}
///|
fn run_hot_pipeline_raw_vacuum_two_arg_local_set_release_prefix_target(
body : Array[@lib.Instruction],
) -> Int32? {
let non_nops : Array[@lib.Instruction] = []
for instr in body {
match instr {
@lib.Nop => ()
_ => non_nops.push(instr)
}
}
if non_nops.length() != 6 {
return None
}
let receiver_local = match non_nops[0] {
@lib.LocalGet(@lib.LocalIdx(raw)) => raw.reinterpret_as_int()
_ => return None
}
let target_local = match non_nops[3] {
@lib.LocalSet(@lib.LocalIdx(raw)) => raw.reinterpret_as_int()
_ => return None
}
match non_nops[4] {
@lib.LocalGet(@lib.LocalIdx(raw)) if raw.reinterpret_as_int() ==
receiver_local => ()
_ => return None
}
if non_nops[1] is @lib.I32Const(_) &&
non_nops[2] is @lib.Call(_) &&
non_nops[5] is @lib.Call(_) {
Some(target_local)
} else {
None
}
}
///|
fn run_hot_pipeline_raw_vacuum_body_loads_linked_local_before_if(
body : Array[@lib.Instruction],
start_idx : Int32,
target_local : Int32,
) -> Bool {
if start_idx + 5 >= body.length() {
return false
}
match body[start_idx] {
@lib.LocalGet(@lib.LocalIdx(raw)) if raw.reinterpret_as_int() ==
target_local => ()
_ => return false
}
body[start_idx + 1] is @lib.LocalTee(_) &&
body[start_idx + 2] is @lib.I32Load(_) &&
body[start_idx + 3] is @lib.I32Const(@lib.I32(20)) &&
body[start_idx + 4] is @lib.I32Eq &&
body[start_idx + 5] is @lib.If(@lib.VoidBlockType, _, _)
}
///|
fn run_hot_pipeline_raw_vacuum_try_flatten_load_linked_release_prefix_child(
body : Array[@lib.Instruction],
) -> Array[@lib.Instruction]? {
if body.length() < 7 {
return None
}
let (prefix_body, target_local) = match body[0] {
@lib.Block(@lib.VoidBlockType, @lib.Expr(prefix_body)) =>
match
run_hot_pipeline_raw_vacuum_two_arg_local_set_release_prefix_target(
prefix_body,
) {
Some(local_id) => (prefix_body, local_id)
None => return None
}
_ => return None
}
if !run_hot_pipeline_raw_vacuum_body_loads_linked_local_before_if(
body, 1, target_local,
) {
return None
}
let flattened : Array[@lib.Instruction] = []
for instr in prefix_body {
match instr {
@lib.Nop => ()
_ => flattened.push(instr)
}
}
for idx = 1; idx < body.length(); idx = idx + 1 {
flattened.push(body[idx])
}
Some(flattened)
}
///|
fn run_hot_pipeline_raw_vacuum_try_wrap_load_linked_release_prefix(
body : Array[@lib.Instruction],
) -> Array[@lib.Instruction]? {
if body.length() < 12 {
return None
}
let prefix_body : Array[@lib.Instruction] = []
for idx = 0; idx < 6; idx = idx + 1 {
prefix_body.push(body[idx])
}
let target_local = match
run_hot_pipeline_raw_vacuum_two_arg_local_set_release_prefix_target(
prefix_body,
) {
Some(local_id) => local_id
None => return None
}
if !run_hot_pipeline_raw_vacuum_body_loads_linked_local_before_if(
body, 6, target_local,
) {
return None
}
let wrapped : Array[@lib.Instruction] = [
@lib.Instruction::block(
@lib.BlockType::void_(),
@lib.Expr::new(prefix_body),
),
]
for idx = 6; idx < body.length(); idx = idx + 1 {
wrapped.push(body[idx])
}
Some(wrapped)
}
///|
fn run_hot_pipeline_raw_vacuum_instr_has_load_linked_release_prefix_child(
instr : @lib.Instruction,
) -> Bool {
match instr {
@lib.Block(_, @lib.Expr(body))
| @lib.Loop(_, @lib.Expr(body))
| @lib.TryTable(_, _, @lib.Expr(body)) =>
run_hot_pipeline_raw_vacuum_try_wrap_load_linked_release_prefix(body)
is Some(_) ||
run_hot_pipeline_raw_vacuum_try_flatten_load_linked_release_prefix_child(
body,
)
is Some(_) ||
run_hot_pipeline_raw_vacuum_instrs_have_load_linked_release_prefix_child(
body,
)
@lib.If(_, then_body, else_body) => {
if run_hot_pipeline_raw_vacuum_instrs_have_load_linked_release_prefix_child(
then_body,
) {
return true
}
match else_body {
Some(body) =>
run_hot_pipeline_raw_vacuum_instrs_have_load_linked_release_prefix_child(
body,
)
None => false
}
}
_ => false
}
}
///|
fn run_hot_pipeline_raw_vacuum_instrs_have_load_linked_release_prefix_child(
instrs : Array[@lib.Instruction],
) -> Bool {
for instr in instrs {
if run_hot_pipeline_raw_vacuum_instr_has_load_linked_release_prefix_child(
instr,
) {
return true
}
}
false
}
///|
fn run_hot_pipeline_raw_vacuum_instrs_need_load_linked_second_preclean(
instrs : Array[@lib.Instruction],
) -> Bool {
run_hot_pipeline_raw_vacuum_instrs_have_singleton_load_linked_local_set_call_prefix_block(
instrs,
) ||
run_hot_pipeline_raw_vacuum_instrs_have_load_linked_release_prefix_child(
instrs,
)
}
///|
fn run_hot_pipeline_raw_vacuum_body_is_five_arg_allocator_release_prefix_if_preserve_candidate(
body : Array[@lib.Instruction],
) -> Bool {
if !run_hot_pipeline_raw_vacuum_body_is_single_block_prefix_if_wrapper_candidate(
body,
) ||
run_hot_pipeline_raw_vacuum_instrs_contain_unreachable(body) {
return false
}
let has_reduced_const_call_condition = body.length() == 4 &&
body[1] is @lib.I32Const(_) &&
body[2] is @lib.Call(_)
let has_full_refcount_eq_condition = body.length() == 7 &&
body[1] is @lib.LocalGet(_) &&
body[2] is @lib.LocalTee(_) &&
body[3] is @lib.Call(_) &&
body[4] is @lib.I32Const(_) &&
body[5] is @lib.I32Eq
if !has_reduced_const_call_condition && !has_full_refcount_eq_condition {
return false
}
let prefix_body = match body[0] {
@lib.Block(@lib.VoidBlockType, @lib.Expr(prefix_body)) => prefix_body
_ => return false
}
run_hot_pipeline_raw_vacuum_prefix_block_starts_five_arg_call_result_local_set_release_triple(
prefix_body,
) &&
run_hot_pipeline_raw_vacuum_final_if_has_else_or_top_level_outer_branch(body)
}
///|
fn run_hot_pipeline_raw_vacuum_body_is_call_indirect_prefix_if_preserve_candidate(
body : Array[@lib.Instruction],
) -> Bool {
if body.length() < 6 ||
run_hot_pipeline_raw_vacuum_instrs_contain_unreachable(body) ||
!run_hot_pipeline_raw_vacuum_final_if_has_else_or_top_level_outer_branch(
body,
) {
return false
}
match body[body.length() - 1] {
@lib.If(@lib.VoidBlockType, _, _) => ()
_ => return false
}
for idx = 0; idx + 1 < body.length(); idx = idx + 1 {
if !run_hot_pipeline_raw_vacuum_instr_can_prefix_side_effect_if_wrapper(
body[idx],
) {
return false
}
}
let mut saw_local_tee = false
let mut saw_call_before_indirect = false
let mut saw_call_indirect = false
let mut saw_set_after_indirect = false
let mut saw_call_after_set = false
let mut previous_was_call_indirect = false
for idx = 0; idx + 1 < body.length(); idx = idx + 1 {
match body[idx] {
@lib.LocalTee(_) => saw_local_tee = true
@lib.Call(_) =>
if saw_set_after_indirect {
saw_call_after_set = true
} else if !saw_call_indirect {
saw_call_before_indirect = true
}
@lib.CallIndirect(_, _) => {
if saw_call_before_indirect {
saw_call_indirect = true
}
previous_was_call_indirect = true
continue
}
@lib.LocalSet(_) if previous_was_call_indirect =>
saw_set_after_indirect = true
_ => ()
}
previous_was_call_indirect = false
}
saw_local_tee &&
saw_call_before_indirect &&
saw_call_indirect &&
saw_set_after_indirect &&
saw_call_after_set
}
///|
fn run_hot_pipeline_raw_vacuum_body_is_multi_local_prefix_if_preserve_candidate(
body : Array[@lib.Instruction],
) -> Bool {
if body.length() < 8 {
return false
}
match body[body.length() - 1] {
@lib.If(@lib.VoidBlockType, _, Some(_)) => ()
_ => return false
}
if run_hot_pipeline_raw_vacuum_instrs_contain_unreachable(body) {
return false
}
match (body[0], body[1], body[2]) {
(@lib.LocalGet(_), @lib.I32Load(_), @lib.LocalSet(_)) => ()
_ => return false
}
let mut local_set_count = 0
let mut call_count = 0
let mut saw_local_tee = false
for idx = 0; idx + 1 < body.length(); idx = idx + 1 {
match body[idx] {
@lib.LocalSet(_) => local_set_count = local_set_count + 1
@lib.Call(_) => call_count = call_count + 1
@lib.LocalTee(_) => saw_local_tee = true
@lib.LocalGet(_) | @lib.I32Load(_) | @lib.I32Const(_) | @lib.I32Eq => ()
_ => return false
}
}
local_set_count >= 3 && call_count >= 4 && saw_local_tee
}
///|
fn run_hot_pipeline_raw_vacuum_body_has_five_arg_prefix_parent_continuation(
body : Array[@lib.Instruction],
start_idx : Int32,
) -> Bool {
if start_idx + 1 >= body.length() {
return false
}
if body[start_idx + 1] is @lib.LocalGet(_) {
return true
}
let non_nops : Array[@lib.Instruction] = []
let mut idx = start_idx + 1
let mut saw_nop = false
while idx < body.length() && non_nops.length() < 7 {
match body[idx] {
@lib.Nop => saw_nop = true
instr => non_nops.push(instr)
}
idx = idx + 1
}
saw_nop &&
non_nops.length() >= 7 &&
non_nops[0] is @lib.LocalGet(_) &&
non_nops[1] is @lib.LocalGet(_) &&
non_nops[2] is @lib.LocalGet(_) &&
non_nops[3] is @lib.Call(_) &&
non_nops[4] is @lib.LocalSet(_) &&
non_nops[5] is @lib.LocalGet(_) &&
non_nops[6] is @lib.Call(_)
}
///|
fn run_hot_pipeline_raw_vacuum_body_has_multi_local_prefix_parent_continuation(
body : Array[@lib.Instruction],
start_idx : Int32,
) -> Bool {
if start_idx + 1 >= body.length() {
return false
}
for idx = start_idx + 1; idx < body.length(); idx = idx + 1 {
match body[idx] {
@lib.Call(_)
| @lib.CallIndirect(_, _)
| @lib.CallRef(_)
| @lib.I32Store(_)
| @lib.I64Store(_)
| @lib.F32Store(_)
| @lib.F64Store(_)
| @lib.I32Store8(_)
| @lib.I32Store16(_)
| @lib.I64Store8(_)
| @lib.I64Store16(_)
| @lib.I64Store32(_)
| @lib.Return => return true
@lib.Br(_) | @lib.BrIf(_) | @lib.BrTable(_, _) => return false
_ => ()
}
}
false
}
///|
fn run_hot_pipeline_raw_vacuum_body_is_multi_local_prefix_parent_preserve_candidate(
body : Array[@lib.Instruction],
) -> Bool {
if body.length() < 2 {
return false
}
match body[0] {
@lib.Block(@lib.VoidBlockType, @lib.Expr(prefix_body)) =>
run_hot_pipeline_raw_vacuum_body_is_multi_local_prefix_if_preserve_candidate(
prefix_body,
) &&
run_hot_pipeline_raw_vacuum_body_has_multi_local_prefix_parent_continuation(
body, 0,
)
_ => false
}
}
///|
fn run_hot_pipeline_raw_vacuum_body_is_terminal_unreachable_block_wrapper_candidate(
body : Array[@lib.Instruction],
) -> Bool {
body.length() == 2 &&
body[0] is @lib.Block(@lib.VoidBlockType, _) &&
body[1] is @lib.Unreachable
}
///|
fn run_hot_pipeline_raw_vacuum_body_is_single_flipped_if_wrapper_candidate(
body : Array[@lib.Instruction],
) -> Bool {
if body.length() == 0 {
return false
}
match body[body.length() - 1] {
@lib.If(@lib.VoidBlockType, then_body, None) if then_body.length() > 0 => ()
_ => return false
}
for idx = 0; idx + 1 < body.length(); idx = idx + 1 {
if !run_hot_pipeline_raw_vacuum_instr_can_prefix_if_wrapper(body[idx]) {
return false
}
}
true
}
///|
fn run_hot_pipeline_raw_vacuum_instr_targets_if_or_outer(
instr : @lib.Instruction,
owner_depth : Int32,
) -> Bool {
match instr {
@lib.Br(label)
| @lib.BrIf(label)
| @lib.BrOnNull(label)
| @lib.BrOnNonNull(label)
| @lib.Rethrow(label) =>
run_hot_pipeline_raw_vacuum_label_idx_depth(label) >= owner_depth
@lib.BrTable(labels, fallback) => {
if run_hot_pipeline_raw_vacuum_label_idx_depth(fallback) >= owner_depth {
return true
}
for label in labels {
if run_hot_pipeline_raw_vacuum_label_idx_depth(label) >= owner_depth {
return true
}
}
false
}
@lib.Block(_, @lib.Expr(body)) | @lib.Loop(_, @lib.Expr(body)) =>
run_hot_pipeline_raw_vacuum_body_targets_if_or_outer(
body,
owner_depth + 1,
)
@lib.If(_, then_body, else_body) => {
if run_hot_pipeline_raw_vacuum_body_targets_if_or_outer(
then_body,
owner_depth + 1,
) {
return true
}
match else_body {
Some(body) =>
run_hot_pipeline_raw_vacuum_body_targets_if_or_outer(
body,
owner_depth + 1,
)
None => false
}
}
// Legacy handler labels need their existing surrounding control scope.
@lib.Try(_, _, _, _) => true
@lib.TryTable(_, _, @lib.Expr(body)) =>
run_hot_pipeline_raw_vacuum_body_targets_if_or_outer(
body,
owner_depth + 1,
)
_ => false
}
}
///|
fn run_hot_pipeline_raw_vacuum_body_targets_if_or_outer(
body : Array[@lib.Instruction],
owner_depth : Int32,
) -> Bool {
for instr in body {
if run_hot_pipeline_raw_vacuum_instr_targets_if_or_outer(instr, owner_depth) {
return true
}
}
false
}
///|
fn run_hot_pipeline_raw_vacuum_instr_can_prefix_refcount_call_if_wrapper(
instr : @lib.Instruction,
) -> Bool {
match instr {
@lib.If(@lib.VoidBlockType, _, _) =>
!run_hot_pipeline_raw_vacuum_instr_contains_unreachable(instr) &&
!run_hot_pipeline_raw_vacuum_instr_targets_if_or_outer(instr, 0)
_ =>
run_hot_pipeline_raw_vacuum_instr_can_prefix_side_effect_if_wrapper(instr)
}
}
///|
fn run_hot_pipeline_raw_vacuum_body_is_refcount_call_prefix_if_wrapper_candidate(
body : Array[@lib.Instruction],
) -> Bool {
if body.length() < 6 {
return false
}
match body[body.length() - 1] {
@lib.If(@lib.VoidBlockType, _, _) => ()
_ => return false
}
if run_hot_pipeline_raw_vacuum_instrs_contain_unreachable(body) ||
!run_hot_pipeline_raw_vacuum_final_if_has_else_or_top_level_outer_branch(
body,
) {
return false
}
let mut saw_prefix_if = false
let mut saw_local_set = false
let mut call_count = 0
for idx = 0; idx + 1 < body.length(); idx = idx + 1 {
let instr = body[idx]
if !run_hot_pipeline_raw_vacuum_instr_can_prefix_refcount_call_if_wrapper(
instr,
) {
return false
}
match instr {
@lib.If(@lib.VoidBlockType, _, _) => saw_prefix_if = true
@lib.LocalSet(_) => saw_local_set = true
@lib.Call(_) => call_count = call_count + 1
_ => ()
}
}
saw_prefix_if && saw_local_set && call_count >= 2
}
///|
fn run_hot_pipeline_raw_vacuum_instr_is_pure_drop_leaf(
instr : @lib.Instruction,
) -> Bool {
match instr {
@lib.I32Const(_)
| @lib.I64Const(_)
| @lib.F32Const(_)
| @lib.F64Const(_)
| @lib.V128Const(_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _)
| @lib.RefNull(_)
| @lib.RefFunc(_)
| @lib.StringConst(_)
| @lib.LocalGet(_)
| @lib.GlobalGet(_)
| @lib.MemorySize(_)
| @lib.TableSize(_) => true
_ => false
}
}
///|
fn run_hot_pipeline_raw_vacuum_instr_is_terminal_unreachable_debris(
instr : @lib.Instruction,
) -> Bool {
match instr {
@lib.Nop
| @lib.I32Const(_)
| @lib.I64Const(_)
| @lib.F32Const(_)
| @lib.F64Const(_)
| @lib.Br(_)
| @lib.BrTable(_, _) => true
@lib.Block(_, @lib.Expr([@lib.Unreachable])) => true
@lib.TryTable(@lib.VoidBlockType, _, @lib.Expr([])) => true
_ => false
}
}
///|
fn run_hot_pipeline_raw_vacuum_pop_terminal_unreachable_debris(
instrs : Array[@lib.Instruction],
) -> Bool {
let mut changed = false
let mut keep_going = true
while keep_going && instrs.length() > 0 {
let last_idx = instrs.length() - 1
if run_hot_pipeline_raw_vacuum_instr_is_terminal_unreachable_debris(
instrs[last_idx],
) {
ignore(instrs.pop())
changed = true
} else {
keep_going = false
}
}
changed
}
///|
fn run_hot_pipeline_raw_vacuum_instr_contains_unreachable(
instr : @lib.Instruction,
) -> Bool {
match instr {
@lib.Unreachable => true
@lib.Block(_, @lib.Expr(body))
| @lib.Loop(_, @lib.Expr(body))
| @lib.TryTable(_, _, @lib.Expr(body)) =>
run_hot_pipeline_raw_vacuum_instrs_contain_unreachable(body)
@lib.If(_, then_body, else_body) => {
if run_hot_pipeline_raw_vacuum_instrs_contain_unreachable(then_body) {
return true
}
match else_body {
Some(body) =>
run_hot_pipeline_raw_vacuum_instrs_contain_unreachable(body)
None => false
}
}
_ => false
}
}
///|
fn run_hot_pipeline_raw_vacuum_instrs_contain_unreachable(
instrs : Array[@lib.Instruction],
) -> Bool {
for instr in instrs {
if run_hot_pipeline_raw_vacuum_instr_contains_unreachable(instr) {
return true
}
}
false
}
///|
fn run_hot_pipeline_raw_vacuum_instr_has_terminal_unreachable_debris(
instr : @lib.Instruction,
) -> Bool {
match instr {
@lib.Block(_, @lib.Expr(body))
| @lib.Loop(_, @lib.Expr(body))
| @lib.TryTable(_, _, @lib.Expr(body)) =>
run_hot_pipeline_raw_vacuum_instrs_have_terminal_unreachable_debris(body)
@lib.If(_, then_body, else_body) => {
if run_hot_pipeline_raw_vacuum_instrs_have_terminal_unreachable_debris(
then_body,
) {
return true
}
match else_body {
Some(body) =>
run_hot_pipeline_raw_vacuum_instrs_have_terminal_unreachable_debris(
body,
)
None => false
}
}
_ => false
}
}
///|
fn run_hot_pipeline_raw_vacuum_instrs_have_terminal_unreachable_debris(
instrs : Array[@lib.Instruction],
) -> Bool {
for idx = 0; idx < instrs.length(); idx = idx + 1 {
let instr = instrs[idx]
if idx > 0 &&
instr is @lib.Unreachable &&
run_hot_pipeline_raw_vacuum_instr_is_terminal_unreachable_debris(
instrs[idx - 1],
) {
return true
}
if run_hot_pipeline_raw_vacuum_instr_has_terminal_unreachable_debris(instr) {
return true
}
}
false
}
///|
fn run_hot_pipeline_raw_vacuum_pure_instr_operand_count(
instr : @lib.Instruction,
) -> Int32? {
match instr {
@lib.I32Const(_)
| @lib.I64Const(_)
| @lib.F32Const(_)
| @lib.F64Const(_)
| @lib.V128Const(_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _)
| @lib.RefNull(_)
| @lib.RefFunc(_)
| @lib.StringConst(_)
| @lib.LocalGet(_)
| @lib.GlobalGet(_)
| @lib.MemorySize(_)
| @lib.TableSize(_) => Some(0)
@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.I32Extend8S
| @lib.I32Extend16S
| @lib.I64Extend8S
| @lib.I64Extend16S
| @lib.I64Extend32S
| @lib.RefIsNull
| @lib.RefI31 => Some(1)
@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.I32And
| @lib.I32Or
| @lib.I32Xor
| @lib.I32Shl
| @lib.I32ShrS
| @lib.I32ShrU
| @lib.I32Rotl
| @lib.I32Rotr
| @lib.I64Add
| @lib.I64Sub
| @lib.I64Mul
| @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
| @lib.RefEq
| @lib.I8x16Shuffle(_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _) =>
Some(2)
@lib.Select(_) => Some(3)
_ =>
match lcse_simd_pure_operand_count(instr) {
Some(count) => Some(count)
None =>
if vacuum_convert_is_nontrapping(Some(instr)) {
Some(1)
} else {
None
}
}
}
}
///|
fn run_hot_pipeline_raw_vacuum_pure_stack_expr_start(
instrs : Array[@lib.Instruction],
end_exclusive : Int32,
) -> Int32? {
if end_exclusive <= 0 {
return None
}
let instr = instrs[end_exclusive - 1]
let operand_count = match
run_hot_pipeline_raw_vacuum_pure_instr_operand_count(instr) {
Some(count) => count
None => return None
}
let mut start = end_exclusive - 1
let mut remaining = operand_count
while remaining > 0 {
let operand_start = match
run_hot_pipeline_raw_vacuum_pure_stack_expr_start(instrs, start) {
Some(value) => value
None => return None
}
start = operand_start
remaining = remaining - 1
}
Some(start)
}
///|
fn run_hot_pipeline_raw_vacuum_body_has_exact_pure_stack_values(
body : Array[@lib.Instruction],
count : Int32,
) -> Bool {
if count <= 0 {
return body.length() == 0
}
let mut end = body.length()
for value_idx = 0; value_idx < count; value_idx = value_idx + 1 {
let start = match
run_hot_pipeline_raw_vacuum_pure_stack_expr_start(body, end) {
Some(value) => value
None => return false
}
end = start
}
end == 0
}
///|
fn run_hot_pipeline_raw_vacuum_instrs_have_dropped_typeidx_block(
instrs : Array[@lib.Instruction],
) -> Bool {
for idx = 0; idx < instrs.length(); idx = idx + 1 {
if idx + 2 < instrs.length() &&
instrs[idx] is @lib.Block(@lib.TypeIdxBlockType(_), _) &&
instrs[idx + 1] is @lib.Drop &&
instrs[idx + 2] is @lib.Drop {
return true
}
match instrs[idx] {
@lib.Block(_, @lib.Expr(body))
| @lib.Loop(_, @lib.Expr(body))
| @lib.TryTable(_, _, @lib.Expr(body)) =>
if run_hot_pipeline_raw_vacuum_instrs_have_dropped_typeidx_block(body) {
return true
}
@lib.If(_, then_body, else_body) => {
if run_hot_pipeline_raw_vacuum_instrs_have_dropped_typeidx_block(
then_body,
) {
return true
}
match else_body {
Some(body) =>
if run_hot_pipeline_raw_vacuum_instrs_have_dropped_typeidx_block(
body,
) {
return true
}
None => ()
}
}
_ => ()
}
}
false
}
///|
fn run_hot_pipeline_raw_vacuum_dropped_pure_multivalue_width_at(
instrs : Array[@lib.Instruction],
idx : Int32,
module_ctx : @ir.HotModuleContext?,
) -> Int32? {
let (type_idx, body) = match instrs[idx] {
@lib.Block(@lib.TypeIdxBlockType(type_idx), @lib.Expr(body)) =>
(type_idx, body)
_ => return None
}
let ctx = match module_ctx {
Some(ctx) => ctx
None => return None
}
let result_count = match
@ir.hot_module_resolve_type_func_type(ctx, type_idx) {
Some(@lib.FuncType(params, results)) if params.is_empty() &&
results.length() > 1 => results.length()
_ => return None
}
if idx + result_count >= instrs.length() ||
!run_hot_pipeline_raw_vacuum_body_has_exact_pure_stack_values(
body, result_count,
) {
return None
}
for offset = 1; offset <= result_count; offset = offset + 1 {
if !(instrs[idx + offset] is @lib.Drop) {
return None
}
}
Some(result_count + 1)
}
///|
fn run_hot_pipeline_raw_vacuum_body_has_only_pure_self_branch(
body : Array[@lib.Instruction],
) -> Bool {
if body.length() == 0 {
return false
}
let branch_is_self = match body[body.length() - 1] {
@lib.Br(@lib.LabelIdx(depth)) | @lib.BrIf(@lib.LabelIdx(depth)) =>
depth == 0U
@lib.BrTable(targets, @lib.LabelIdx(default_depth)) if default_depth == 0U => {
let mut all_self = true
for target in targets {
let @lib.LabelIdx(depth) = target
if depth != 0U {
all_self = false
}
}
all_self
}
_ => false
}
if !branch_is_self {
return false
}
for idx = 0; idx + 1 < body.length(); idx = idx + 1 {
if !(body[idx] is @lib.Nop) &&
run_hot_pipeline_raw_vacuum_pure_instr_operand_count(body[idx]) is None {
return false
}
}
true
}
///|
fn run_hot_pipeline_raw_vacuum_body_has_only_pure_outer_branch_wrapper(
body : Array[@lib.Instruction],
) -> Bool {
let child_body = match body {
[@lib.Block(@lib.VoidBlockType, @lib.Expr(child)), @lib.Unreachable] =>
child
_ => return false
}
let effective_end = if child_body.length() > 0 &&
child_body[child_body.length() - 1] is @lib.Unreachable {
child_body.length() - 1
} else {
child_body.length()
}
if effective_end == 0 {
return false
}
let terminal_is_outer_branch = match child_body[effective_end - 1] {
@lib.Br(@lib.LabelIdx(depth)) => depth == 1U
@lib.BrTable(targets, @lib.LabelIdx(default_depth)) if default_depth == 1U => {
for target in targets {
let @lib.LabelIdx(depth) = target
if depth != 1U {
return false
}
}
true
}
_ => false
}
if !terminal_is_outer_branch {
return false
}
for idx = 0; idx + 1 < effective_end; idx = idx + 1 {
match child_body[idx] {
@lib.Nop => ()
@lib.BrIf(@lib.LabelIdx(depth)) if depth == 1U => ()
instr if run_hot_pipeline_raw_vacuum_pure_instr_operand_count(instr)
is Some(_) => ()
_ => return false
}
}
true
}
///|
fn run_hot_pipeline_raw_vacuum_body_is_single_pure_stack_value(
body : Array[@lib.Instruction],
) -> Bool {
if body.length() == 0 {
return false
}
run_hot_pipeline_raw_vacuum_pure_stack_expr_start(body, body.length()) ==
Some(0)
}
///|
fn run_hot_pipeline_raw_vacuum_try_pop_const_nontrapping_int_divrem(
instrs : Array[@lib.Instruction],
) -> Bool {
if instrs.length() < 3 {
return false
}
let op = instrs[instrs.length() - 1]
let rhs = instrs[instrs.length() - 2]
let lhs = instrs[instrs.length() - 3]
let removable = match (lhs, rhs, op) {
(@lib.I32Const(@lib.I32(left)), @lib.I32Const(@lib.I32(right)), @lib.I32DivS
) => right != 0 && !(left == -2147483648 && right == -1)
(@lib.I32Const(_), @lib.I32Const(@lib.I32(right)), @lib.I32DivU) =>
right != 0
(
@lib.I32Const(_),
@lib.I32Const(@lib.I32(right)),
@lib.I32RemS
| @lib.I32RemU,
) => right != 0
(@lib.I64Const(@lib.I64(left)), @lib.I64Const(@lib.I64(right)), @lib.I64DivS
) => right != 0L && !(left == -9223372036854775808L && right == -1L)
(@lib.I64Const(_), @lib.I64Const(@lib.I64(right)), @lib.I64DivU) =>
right != 0L
(
@lib.I64Const(_),
@lib.I64Const(@lib.I64(right)),
@lib.I64RemS
| @lib.I64RemU,
) => right != 0L
_ => false
}
if !removable {
return false
}
ignore(instrs.pop())
ignore(instrs.pop())
ignore(instrs.pop())
true
}
///|
fn run_hot_pipeline_raw_vacuum_instr_cannot_fall_through_to_next(
instr : @lib.Instruction,
) -> Bool {
match instr {
@lib.Br(_) | @lib.BrTable(_, _) | @lib.Return | @lib.Unreachable => true
@lib.Block(_, @lib.Expr(body)) if body.length() > 0 =>
match body[body.length() - 1] {
@lib.Br(@lib.LabelIdx(depth)) =>
depth > 0U && !run_hot_pipeline_raw_vacuum_body_targets_owner(body, 0)
@lib.BrTable(targets, @lib.LabelIdx(default_depth)) if default_depth >
0U => {
for target in targets {
let @lib.LabelIdx(depth) = target
if depth == 0U {
return false
}
}
!run_hot_pipeline_raw_vacuum_body_targets_owner(body, 0)
}
@lib.Return | @lib.Unreachable =>
!run_hot_pipeline_raw_vacuum_body_targets_owner(body, 0)
_ => false
}
_ => false
}
}
///|
fn run_hot_pipeline_raw_vacuum_instrs_are_local_only_removable(
instrs : Array[@lib.Instruction],
) -> Bool {
for idx = 0; idx < instrs.length(); idx = idx + 1 {
let instr = instrs[idx]
let removable = match instr {
@lib.Nop
| @lib.LocalGet(_)
| @lib.LocalSet(_)
| @lib.LocalTee(_)
| @lib.GlobalGet(_)
| @lib.TableSize(_)
| @lib.Drop
| @lib.Br(_)
| @lib.BrIf(_)
| @lib.BrTable(_, _)
| @lib.Return
| @lib.StructNewDefault(_) => true
@lib.Unreachable if idx > 0 =>
run_hot_pipeline_raw_vacuum_instr_cannot_fall_through_to_next(
instrs[idx - 1],
)
@lib.Block(_, @lib.Expr(body)) =>
run_hot_pipeline_raw_vacuum_instrs_are_local_only_removable(body)
@lib.Loop(_, @lib.Expr(body)) =>
!run_hot_pipeline_raw_vacuum_body_targets_owner(body, 0) &&
run_hot_pipeline_raw_vacuum_instrs_are_local_only_removable(body)
@lib.If(_, then_body, else_body) =>
if !run_hot_pipeline_raw_vacuum_instrs_are_local_only_removable(
then_body,
) {
false
} else {
match else_body {
Some(body) =>
run_hot_pipeline_raw_vacuum_instrs_are_local_only_removable(body)
None => true
}
}
_ =>
run_hot_pipeline_raw_vacuum_pure_instr_operand_count(instr) is Some(_)
}
if !removable {
return false
}
}
true
}
///|
fn run_hot_pipeline_raw_vacuum_instrs_have_dropped_pure_simd_expr(
instrs : Array[@lib.Instruction],
) -> Bool {
for idx = 0; idx < instrs.length(); idx = idx + 1 {
if instrs[idx] is @lib.Drop {
match run_hot_pipeline_raw_vacuum_pure_stack_expr_start(instrs, idx) {
Some(start) =>
for scan = start; scan < idx; scan = scan + 1 {
if lcse_simd_pure_operand_count(instrs[scan]) is Some(_) {
return true
}
}
None => ()
}
}
match instrs[idx] {
@lib.Block(_, @lib.Expr(body))
| @lib.Loop(_, @lib.Expr(body))
| @lib.TryTable(_, _, @lib.Expr(body)) =>
if run_hot_pipeline_raw_vacuum_instrs_have_dropped_pure_simd_expr(body) {
return true
}
@lib.If(_, then_body, else_body) => {
if run_hot_pipeline_raw_vacuum_instrs_have_dropped_pure_simd_expr(
then_body,
) {
return true
}
match else_body {
Some(body) =>
if run_hot_pipeline_raw_vacuum_instrs_have_dropped_pure_simd_expr(
body,
) {
return true
}
None => ()
}
}
_ => ()
}
}
false
}
///|
fn run_hot_pipeline_raw_vacuum_instrs_have_dropped_simd_replace_lane_load(
instrs : Array[@lib.Instruction],
) -> Bool {
for idx = 0; idx + 4 < instrs.length(); idx = idx + 1 {
if instrs[idx]
is @lib.V128Const(_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _) &&
instrs[idx + 1] is @lib.LocalGet(_) &&
run_hot_pipeline_optimize_instructions_instr_is_plain_load(
instrs[idx + 2],
) &&
run_hot_pipeline_raw_vacuum_instr_is_simd_replace_lane(instrs[idx + 3]) &&
instrs[idx + 4] is @lib.Drop {
return true
}
match instrs[idx] {
@lib.Block(_, @lib.Expr(body))
| @lib.Loop(_, @lib.Expr(body))
| @lib.TryTable(_, _, @lib.Expr(body)) =>
if run_hot_pipeline_raw_vacuum_instrs_have_dropped_simd_replace_lane_load(
body,
) {
return true
}
@lib.If(_, then_body, else_body) => {
if run_hot_pipeline_raw_vacuum_instrs_have_dropped_simd_replace_lane_load(
then_body,
) {
return true
}
match else_body {
Some(body) =>
if run_hot_pipeline_raw_vacuum_instrs_have_dropped_simd_replace_lane_load(
body,
) {
return true
}
None => ()
}
}
_ => ()
}
}
false
}
///|
fn run_hot_pipeline_raw_vacuum_instrs_have_dropped_local_tee(
instrs : Array[@lib.Instruction],
) -> Bool {
for idx = 0; idx < instrs.length(); idx = idx + 1 {
if idx + 1 < instrs.length() &&
instrs[idx] is @lib.LocalTee(_) &&
instrs[idx + 1] is @lib.Drop {
return true
}
match instrs[idx] {
@lib.Block(_, @lib.Expr(body))
| @lib.Loop(_, @lib.Expr(body))
| @lib.TryTable(_, _, @lib.Expr(body)) =>
if run_hot_pipeline_raw_vacuum_instrs_have_dropped_local_tee(body) {
return true
}
@lib.If(_, then_body, else_body) => {
if run_hot_pipeline_raw_vacuum_instrs_have_dropped_local_tee(then_body) {
return true
}
match else_body {
Some(body) =>
if run_hot_pipeline_raw_vacuum_instrs_have_dropped_local_tee(body) {
return true
}
None => ()
}
}
_ => ()
}
}
false
}
///|
fn run_hot_pipeline_raw_vacuum_instrs_have_const_nontrapping_int_divrem_drop(
instrs : Array[@lib.Instruction],
) -> Bool {
for idx = 0; idx < instrs.length(); idx = idx + 1 {
if idx + 3 < instrs.length() && instrs[idx + 3] == @lib.Instruction::drop() {
let probe = [instrs[idx], instrs[idx + 1], instrs[idx + 2]]
if run_hot_pipeline_raw_vacuum_try_pop_const_nontrapping_int_divrem(probe) {
return true
}
}
match instrs[idx] {
@lib.Block(_, @lib.Expr(body))
| @lib.Loop(_, @lib.Expr(body))
| @lib.TryTable(_, _, @lib.Expr(body)) =>
if run_hot_pipeline_raw_vacuum_instrs_have_const_nontrapping_int_divrem_drop(
body,
) {
return true
}
@lib.If(_, then_body, else_body) => {
if run_hot_pipeline_raw_vacuum_instrs_have_const_nontrapping_int_divrem_drop(
then_body,
) {
return true
}
match else_body {
Some(body) =>
if run_hot_pipeline_raw_vacuum_instrs_have_const_nontrapping_int_divrem_drop(
body,
) {
return true
}
None => ()
}
}
_ => ()
}
}
false
}
///|
fn run_hot_pipeline_raw_vacuum_instr_is_simd_replace_lane(
instr : @lib.Instruction,
) -> Bool {
match instr {
@lib.I8x16ReplaceLane(_)
| @lib.I16x8ReplaceLane(_)
| @lib.I32x4ReplaceLane(_)
| @lib.I64x2ReplaceLane(_)
| @lib.F32x4ReplaceLane(_)
| @lib.F64x2ReplaceLane(_) => true
_ => false
}
}
///|
fn run_hot_pipeline_raw_vacuum_try_reduce_dropped_simd_replace_lane_load(
instrs : Array[@lib.Instruction],
) -> Bool {
if instrs.length() < 4 {
return false
}
let start = instrs.length() - 4
if !(instrs[start]
is @lib.V128Const(_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _)) ||
!(instrs[start + 1] is @lib.LocalGet(_)) ||
!run_hot_pipeline_optimize_instructions_instr_is_plain_load(
instrs[start + 2],
) ||
!run_hot_pipeline_raw_vacuum_instr_is_simd_replace_lane(instrs[start + 3]) {
return false
}
let address = instrs[start + 1]
let load = instrs[start + 2]
for pop_idx = 0; pop_idx < 4; pop_idx = pop_idx + 1 {
ignore(instrs.pop())
}
instrs.push(address)
instrs.push(load)
instrs.push(@lib.Instruction::drop())
true
}
///|
fn run_hot_pipeline_raw_vacuum_try_pop_pure_stack_expr(
instrs : Array[@lib.Instruction],
) -> Bool {
let start = match
run_hot_pipeline_raw_vacuum_pure_stack_expr_start(instrs, instrs.length()) {
Some(value) => value
None => return false
}
while instrs.length() > start {
ignore(instrs.pop())
}
true
}
///|
fn run_hot_pipeline_raw_vacuum_try_pop_i32_const_compare_keep_lhs(
instrs : Array[@lib.Instruction],
) -> Bool {
if instrs.length() < 3 {
return false
}
match (instrs[instrs.length() - 2], instrs[instrs.length() - 1]) {
(@lib.I32Const(_), @lib.I32Eq | @lib.I32Ne) => {
ignore(instrs.pop())
ignore(instrs.pop())
true
}
_ => false
}
}
///|
fn run_hot_pipeline_raw_vacuum_block_type_is_empty(
module_ctx : @ir.HotModuleContext?,
block_type : @lib.BlockType,
) -> Bool {
match block_type {
@lib.VoidBlockType => true
@lib.ValTypeBlockType(_) => false
@lib.TypeIdxBlockType(type_idx) =>
match module_ctx {
Some(ctx) =>
match @ir.hot_module_resolve_type_func_type(ctx, type_idx) {
Some(@lib.FuncType(params, results)) =>
params.is_empty() && results.is_empty()
None => false
}
None => false
}
}
}
///|
fn run_hot_pipeline_raw_vacuum_instr_requires_type_context(
instr : @lib.Instruction,
) -> Bool {
match instr {
@lib.Block(@lib.TypeIdxBlockType(_), _)
| @lib.If(@lib.TypeIdxBlockType(_), _, _) => true
@lib.Block(_, @lib.Expr(body))
| @lib.Loop(_, @lib.Expr(body))
| @lib.TryTable(_, _, @lib.Expr(body)) =>
run_hot_pipeline_raw_vacuum_instrs_require_type_context(body)
@lib.If(_, then_body, else_body) => {
if run_hot_pipeline_raw_vacuum_instrs_require_type_context(then_body) {
return true
}
match else_body {
Some(body) =>
run_hot_pipeline_raw_vacuum_instrs_require_type_context(body)
None => false
}
}
_ => false
}
}
///|
fn run_hot_pipeline_raw_vacuum_instrs_require_type_context(
instrs : Array[@lib.Instruction],
) -> Bool {
for instr in instrs {
if run_hot_pipeline_raw_vacuum_instr_requires_type_context(instr) {
return true
}
}
false
}
///|
fn run_hot_pipeline_raw_vacuum_empty_block_before_local_copy_continuation(
instrs : Array[@lib.Instruction],
idx : Int32,
) -> Bool {
let mut scan = idx + 1
while scan < instrs.length() && instrs[scan] == @lib.Instruction::nop() {
scan = scan + 1
}
if scan >= instrs.length() || !(instrs[scan] is @lib.LocalGet(_)) {
return false
}
scan = scan + 1
while scan < instrs.length() && instrs[scan] == @lib.Instruction::nop() {
scan = scan + 1
}
scan < instrs.length() && instrs[scan] is @lib.LocalSet(_)
}
///|
fn run_hot_pipeline_raw_vacuum_body_is_local_copy_child(
body : Array[@lib.Instruction],
) -> Bool {
match body {
[@lib.LocalGet(_), @lib.LocalSet(_)] => true
_ => false
}
}
///|
fn run_hot_pipeline_raw_vacuum_if_body_is_f7138_i32_div_counter_branch(
body : Array[@lib.Instruction],
) -> Bool {
let non_nops : Array[@lib.Instruction] = []
for instr in body {
match instr {
@lib.Nop => ()
_ => non_nops.push(instr)
}
}
match non_nops {
[
@lib.LocalGet(_),
@lib.LocalGet(_),
@lib.I32DivU,
@lib.LocalSet(_),
@lib.LocalGet(_),
@lib.I32Const(_),
@lib.I32Add,
@lib.LocalSet(_),
@lib.Br(_),
] => true
_ => false
}
}
///|
fn run_hot_pipeline_raw_vacuum_has_f6286_f6594_f6628_f6785_f7095_f7130_f7133_f7138_f7141_f7263_parser_branch_continuation(
instrs : Array[@lib.Instruction],
idx : Int32,
) -> Bool {
let mut scan = idx + 1
while scan < instrs.length() && instrs[scan] == @lib.Instruction::nop() {
scan = scan + 1
}
if scan + 1 < instrs.length() {
match (instrs[scan], instrs[scan + 1]) {
(@lib.LocalGet(_), @lib.If(@lib.VoidBlockType, then_body, _)) =>
if run_hot_pipeline_raw_vacuum_if_body_is_f7138_i32_div_counter_branch(
then_body,
) {
return true
}
_ => ()
}
}
if scan + 3 >= instrs.length() {
return false
}
match (instrs[scan], instrs[scan + 1], instrs[scan + 2], instrs[scan + 3]) {
(
@lib.LocalGet(_),
@lib.I32Const(_),
@lib.I32GtS,
@lib.If(@lib.VoidBlockType, _, _),
) => return true
(
@lib.LocalGet(_),
@lib.I64Const(_),
@lib.I64Ne,
@lib.If(@lib.VoidBlockType, _, _),
) => return true
_ => ()
}
if scan + 4 >= instrs.length() {
return false
}
match
(
instrs[scan],
instrs[scan + 1],
instrs[scan + 2],
instrs[scan + 3],
instrs[scan + 4],
) {
(
@lib.LocalGet(_),
@lib.LocalGet(_),
@lib.LocalTee(_),
@lib.I32GtS,
@lib.If(@lib.VoidBlockType, _, _),
) => return true
(
@lib.LocalGet(_),
@lib.LocalGet(_),
@lib.LocalTee(_),
@lib.I32LeS,
@lib.If(@lib.VoidBlockType, _, _),
) => return true
(
@lib.LocalGet(_),
@lib.LocalTee(_),
@lib.I32Const(_),
@lib.I32GtS,
@lib.If(@lib.VoidBlockType, _, _),
) => return true
(
@lib.LocalGet(_),
@lib.LocalTee(_),
@lib.I32Const(_),
@lib.I32GeS,
@lib.If(@lib.VoidBlockType, _, _),
) => return true
(
@lib.LocalGet(_),
@lib.LocalTee(_),
@lib.I32Const(_),
@lib.I32GeU,
@lib.If(@lib.VoidBlockType, _, _),
) => return true
(
@lib.LocalGet(_),
@lib.LocalTee(_),
@lib.I32Const(_),
@lib.I32LeS,
@lib.If(@lib.VoidBlockType, _, _),
) => return true
(
@lib.LocalGet(_),
@lib.LocalTee(_),
@lib.I64Const(_),
@lib.I64GeU,
@lib.If(@lib.VoidBlockType, _, _),
) => return true
_ => ()
}
if scan + 5 < instrs.length() {
match
(
instrs[scan],
instrs[scan + 1],
instrs[scan + 2],
instrs[scan + 3],
instrs[scan + 4],
instrs[scan + 5],
) {
(
@lib.LocalGet(_),
@lib.I32WrapI64,
@lib.LocalTee(_),
@lib.I32Const(_),
@lib.I32GeS,
@lib.If(@lib.VoidBlockType, _, _),
) => return true
_ => ()
}
}
if scan + 6 >= instrs.length() {
return false
}
match
(
instrs[scan],
instrs[scan + 1],
instrs[scan + 2],
instrs[scan + 3],
instrs[scan + 4],
instrs[scan + 5],
instrs[scan + 6],
) {
(
@lib.LocalGet(_),
@lib.LocalGet(_),
@lib.LocalTee(_),
@lib.I32Const(_),
@lib.I32Sub,
@lib.I32LeS,
@lib.If(@lib.VoidBlockType, _, _),
) => true
_ => false
}
}
///|
fn run_hot_pipeline_raw_vacuum_instr_is_noop_ladder_condition_piece(
instr : @lib.Instruction,
) -> Bool {
match instr {
@lib.LocalGet(_) | @lib.I32Const(_) | @lib.I32Eq | @lib.I32Eqz => true
_ => false
}
}
///|
fn run_hot_pipeline_raw_vacuum_body_is_pure_noop_conditional_ladder(
instrs : Array[@lib.Instruction],
) -> Bool {
let non_nops : Array[@lib.Instruction] = []
for instr in instrs {
match instr {
@lib.Nop => ()
_ => non_nops.push(instr)
}
}
if non_nops.length() == 0 {
return true
}
match non_nops[non_nops.length() - 1] {
@lib.If(block_type, then_body, else_body) => {
if block_type != @lib.BlockType::void_() {
return false
}
for idx = 0; idx < non_nops.length() - 1; idx = idx + 1 {
if !run_hot_pipeline_raw_vacuum_instr_is_noop_ladder_condition_piece(
non_nops[idx],
) {
return false
}
}
if !run_hot_pipeline_raw_vacuum_body_is_pure_noop_conditional_ladder(
then_body,
) {
return false
}
match else_body {
Some(body) =>
run_hot_pipeline_raw_vacuum_body_is_pure_noop_conditional_ladder(body)
None => true
}
}
_ => false
}
}
///|
fn run_hot_pipeline_raw_vacuum_flatten_terminal_owner_branch(
body : Array[@lib.Instruction],
) -> Array[@lib.Instruction]? {
// A void block whose final instruction is an unconditional branch to that
// same block is equivalent to straight-line execution of its prefix. Require
// a value-producing prefix with a local write and reject any nested branch to
// the owner, preserving branch-only scaffolds and outer-control semantics.
if body.length() < 4 {
return None
}
match body[body.length() - 1] {
@lib.Br(label) if run_hot_pipeline_raw_vacuum_label_depth(label) == 0 => {
let prefix : Array[@lib.Instruction] = []
let mut has_local_write = false
for idx = 0; idx + 1 < body.length(); idx = idx + 1 {
if body[idx] is @lib.LocalSet(_) || body[idx] is @lib.LocalTee(_) {
has_local_write = true
}
prefix.push(body[idx])
}
if !has_local_write ||
run_hot_pipeline_raw_vacuum_body_targets_owner(prefix, 0) {
return None
}
Some(run_hot_pipeline_raw_vacuum_rebase_outer_body(prefix, 0))
}
_ => None
}
}
///|
fn run_hot_pipeline_raw_vacuum_instrs_have_terminal_owner_branch_candidate(
instrs : Array[@lib.Instruction],
) -> Bool {
for instr in instrs {
match instr {
@lib.Block(@lib.VoidBlockType, @lib.Expr(body)) =>
if run_hot_pipeline_raw_vacuum_flatten_terminal_owner_branch(body)
is Some(_) ||
run_hot_pipeline_raw_vacuum_instrs_have_terminal_owner_branch_candidate(
body,
) {
return true
}
@lib.Block(_, @lib.Expr(body))
| @lib.Loop(_, @lib.Expr(body))
| @lib.TryTable(_, _, @lib.Expr(body)) =>
if run_hot_pipeline_raw_vacuum_instrs_have_terminal_owner_branch_candidate(
body,
) {
return true
}
@lib.If(_, then_body, else_body) => {
if run_hot_pipeline_raw_vacuum_instrs_have_terminal_owner_branch_candidate(
then_body,
) {
return true
}
match else_body {
Some(body) =>
if run_hot_pipeline_raw_vacuum_instrs_have_terminal_owner_branch_candidate(
body,
) {
return true
}
None => ()
}
}
_ => ()
}
}
false
}
///|
fn run_hot_pipeline_raw_vacuum_instr_is_idempotent_scalar_load(
instr : @lib.Instruction,
) -> Bool {
match instr {
@lib.I32Load(_)
| @lib.I64Load(_)
| @lib.F32Load(_)
| @lib.F64Load(_)
| @lib.I32Load8S(_)
| @lib.I32Load8U(_)
| @lib.I32Load16S(_)
| @lib.I32Load16U(_)
| @lib.I64Load8S(_)
| @lib.I64Load8U(_)
| @lib.I64Load16S(_)
| @lib.I64Load16U(_)
| @lib.I64Load32S(_)
| @lib.I64Load32U(_) => true
_ => false
}
}
///|
fn run_hot_pipeline_raw_vacuum_instrs_have_repeated_dropped_load(
instrs : Array[@lib.Instruction],
) -> Bool {
for idx = 0; idx + 5 < instrs.length(); idx = idx + 1 {
if instrs[idx] == instrs[idx + 3] &&
instrs[idx + 1] == instrs[idx + 4] &&
instrs[idx + 2] == @lib.Instruction::drop() &&
instrs[idx + 5] == @lib.Instruction::drop() &&
instrs[idx] is @lib.LocalGet(_) &&
run_hot_pipeline_raw_vacuum_instr_is_idempotent_scalar_load(
instrs[idx + 1],
) {
return true
}
}
for instr in instrs {
match instr {
@lib.Block(_, @lib.Expr(body))
| @lib.Loop(_, @lib.Expr(body))
| @lib.TryTable(_, _, @lib.Expr(body)) =>
if run_hot_pipeline_raw_vacuum_instrs_have_repeated_dropped_load(body) {
return true
}
@lib.If(_, then_body, else_body) => {
if run_hot_pipeline_raw_vacuum_instrs_have_repeated_dropped_load(
then_body,
) {
return true
}
match else_body {
Some(body) =>
if run_hot_pipeline_raw_vacuum_instrs_have_repeated_dropped_load(
body,
) {
return true
}
None => ()
}
}
_ => ()
}
}
false
}
///|
fn run_hot_pipeline_raw_vacuum_simple_struct_operand(
instr : @lib.Instruction,
) -> Bool {
match instr {
@lib.I32Const(_)
| @lib.I64Const(_)
| @lib.F32Const(_)
| @lib.F64Const(_)
| @lib.V128Const(_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _)
| @lib.LocalGet(_)
| @lib.GlobalGet(_)
| @lib.RefNull(_)
| @lib.RefFunc(_) => true
_ => false
}
}
///|
fn run_hot_pipeline_raw_vacuum_dropped_simple_struct_length_at(
instrs : Array[@lib.Instruction],
idx : Int32,
module_ctx : @ir.HotModuleContext?,
) -> Int32? {
let ctx = match module_ctx {
Some(ctx) => ctx
None => return None
}
for field_count = 0; field_count <= 8; field_count = field_count + 1 {
let struct_idx = idx + field_count
if struct_idx + 1 >= instrs.length() ||
instrs[struct_idx + 1] != @lib.Instruction::drop() {
continue
}
let type_idx = match instrs[struct_idx] {
@lib.StructNew(type_idx) => type_idx
_ => continue
}
match @ir.hot_module_resolve_struct_fields(ctx, type_idx) {
Some(fields) if fields.length() == field_count => ()
_ => continue
}
let mut all_simple = true
for operand_idx = idx
operand_idx < struct_idx
operand_idx = operand_idx + 1 {
if !run_hot_pipeline_raw_vacuum_simple_struct_operand(instrs[operand_idx]) {
all_simple = false
break
}
}
if all_simple {
return Some(field_count + 2)
}
}
None
}
///|
fn run_hot_pipeline_raw_vacuum_instrs_have_dropped_simple_struct(
instrs : Array[@lib.Instruction],
) -> Bool {
for idx = 0; idx + 1 < instrs.length(); idx = idx + 1 {
if instrs[idx] is @lib.StructNew(_) &&
instrs[idx + 1] == @lib.Instruction::drop() {
return true
}
}
for instr in instrs {
match instr {
@lib.Block(_, @lib.Expr(body))
| @lib.Loop(_, @lib.Expr(body))
| @lib.TryTable(_, _, @lib.Expr(body)) =>
if run_hot_pipeline_raw_vacuum_instrs_have_dropped_simple_struct(body) {
return true
}
@lib.If(_, then_body, else_body) => {
if run_hot_pipeline_raw_vacuum_instrs_have_dropped_simple_struct(
then_body,
) {
return true
}
match else_body {
Some(body) =>
if run_hot_pipeline_raw_vacuum_instrs_have_dropped_simple_struct(
body,
) {
return true
}
None => ()
}
}
_ => ()
}
}
false
}
///|
fn run_hot_pipeline_raw_vacuum_instrs_have_dropped_ref_func_struct(
instrs : Array[@lib.Instruction],
) -> Bool {
for idx = 0; idx + 2 < instrs.length(); idx = idx + 1 {
if instrs[idx] is @lib.RefFunc(_) &&
instrs[idx + 1] is @lib.StructNew(_) &&
instrs[idx + 2] == @lib.Instruction::drop() {
return true
}
}
for instr in instrs {
match instr {
@lib.Block(_, @lib.Expr(body))
| @lib.Loop(_, @lib.Expr(body))
| @lib.TryTable(_, _, @lib.Expr(body)) =>
if run_hot_pipeline_raw_vacuum_instrs_have_dropped_ref_func_struct(body) {
return true
}
@lib.If(_, then_body, else_body) => {
if run_hot_pipeline_raw_vacuum_instrs_have_dropped_ref_func_struct(
then_body,
) {
return true
}
match else_body {
Some(body) =>
if run_hot_pipeline_raw_vacuum_instrs_have_dropped_ref_func_struct(
body,
) {
return true
}
None => ()
}
}
_ => ()
}
}
false
}
///|
fn run_hot_pipeline_raw_vacuum_dropped_ref_func_struct_at(
instrs : Array[@lib.Instruction],
idx : Int32,
module_ctx : @ir.HotModuleContext?,
) -> Bool {
if idx + 2 >= instrs.length() ||
!(instrs[idx] is @lib.RefFunc(_)) ||
instrs[idx + 2] != @lib.Instruction::drop() {
return false
}
let type_idx = match instrs[idx + 1] {
@lib.StructNew(type_idx) => type_idx
_ => return false
}
let ctx = match module_ctx {
Some(ctx) => ctx
None => return false
}
match @ir.hot_module_resolve_struct_fields(ctx, type_idx) {
Some([_]) => true
_ => false
}
}
///|
fn run_hot_pipeline_raw_vacuum_instrs_have_fresh_array_set_candidate(
instrs : Array[@lib.Instruction],
) -> Bool {
for idx = 0; idx + 4 < instrs.length(); idx = idx + 1 {
match (instrs[idx], instrs[idx + 1], instrs[idx + 2], instrs[idx + 4]) {
(
@lib.I32Const(@lib.I32(length)),
@lib.ArrayNewDefault(new_type),
@lib.I32Const(@lib.I32(index)),
@lib.ArraySet(set_type),
) if new_type == set_type && index >= 0 && index < length => return true
_ => ()
}
}
for instr in instrs {
match instr {
@lib.Block(_, @lib.Expr(body))
| @lib.Loop(_, @lib.Expr(body))
| @lib.TryTable(_, _, @lib.Expr(body)) =>
if run_hot_pipeline_raw_vacuum_instrs_have_fresh_array_set_candidate(
body,
) {
return true
}
@lib.If(_, then_body, else_body) => {
if run_hot_pipeline_raw_vacuum_instrs_have_fresh_array_set_candidate(
then_body,
) {
return true
}
match else_body {
Some(body) =>
if run_hot_pipeline_raw_vacuum_instrs_have_fresh_array_set_candidate(
body,
) {
return true
}
None => ()
}
}
_ => ()
}
}
false
}
///|
fn run_hot_pipeline_raw_vacuum_fresh_array_set_replacement_at(
instrs : Array[@lib.Instruction],
idx : Int32,
module_ctx : @ir.HotModuleContext?,
) -> Array[@lib.Instruction]? {
if idx + 4 >= instrs.length() {
return None
}
let value = instrs[idx + 3]
match (instrs[idx], instrs[idx + 1], instrs[idx + 2], instrs[idx + 4]) {
(
@lib.I32Const(@lib.I32(length)),
@lib.ArrayNewDefault(new_type),
@lib.I32Const(@lib.I32(index)),
@lib.ArraySet(set_type),
) if new_type == set_type && index >= 0 && index < length => ()
_ => return None
}
match value {
@lib.I32Const(_) => Some([])
@lib.Call(func_idx) => {
let ctx = match module_ctx {
Some(ctx) => ctx
None => return None
}
match @ir.hot_module_resolve_func_type(ctx, func_idx) {
Some(@lib.FuncType(params, results)) if params.is_empty() &&
results == [@lib.ValType::i32()] =>
Some([value, @lib.Instruction::drop()])
_ => None
}
}
_ => None
}
}
///|
fn run_hot_pipeline_raw_vacuum_instrs_cannot_throw_exception(
instrs : Array[@lib.Instruction],
) -> Bool {
for instr in instrs {
match instr {
@lib.Call(_)
| @lib.CallIndirect(_, _)
| @lib.CallRef(_)
| @lib.ReturnCall(_)
| @lib.ReturnCallIndirect(_, _)
| @lib.ReturnCallRef(_)
| @lib.Throw(_)
| @lib.ThrowRef
| @lib.Rethrow(_)
| @lib.Suspend(_)
| @lib.Resume(_, _)
| @lib.ResumeThrow(_, _, _)
| @lib.ResumeThrowRef(_, _)
| @lib.StackSwitch(_, _)
| @lib.Try(_, _, _, _)
| @lib.TryTable(_, _, _) => return false
@lib.Block(_, @lib.Expr(body)) | @lib.Loop(_, @lib.Expr(body)) =>
if !run_hot_pipeline_raw_vacuum_instrs_cannot_throw_exception(body) {
return false
}
@lib.If(_, then_body, else_body) => {
if !run_hot_pipeline_raw_vacuum_instrs_cannot_throw_exception(then_body) {
return false
}
match else_body {
Some(body) =>
if !run_hot_pipeline_raw_vacuum_instrs_cannot_throw_exception(body) {
return false
}
None => ()
}
}
_ => ()
}
}
true
}
///|
fn run_hot_pipeline_raw_vacuum_body_can_flatten_no_throw_exception_region(
body : Array[@lib.Instruction],
) -> Bool {
!run_hot_pipeline_raw_instrs_have_branch(body) &&
run_hot_pipeline_raw_vacuum_instrs_cannot_throw_exception(body)
}
///|
fn run_hot_pipeline_raw_vacuum_body_is_static_null_throw_ref_trap(
body : Array[@lib.Instruction],
) -> Bool {
// A null throw_ref traps before it can raise a catchable exception, so an
// enclosing try_table and transparent block cannot observe this exact body.
match body {
[@lib.RefNull(_), @lib.ThrowRef] => true
_ => false
}
}
///|
fn run_hot_pipeline_raw_vacuum_try_table_catches_all_target_owner(
catches : Array[@lib.Catch],
) -> Bool {
if catches.is_empty() {
return false
}
for catch_ in catches {
let label = match catch_ {
@lib.Catch(_, label)
| @lib.CatchRef(_, label)
| @lib.CatchAll(label)
| @lib.CatchAllRef(label) => label
}
if !run_hot_pipeline_raw_vacuum_label_targets_owner(label, 0) {
return false
}
}
true
}
///|
fn run_hot_pipeline_raw_vacuum_body_is_owner_targeted_no_throw_try_table(
body : Array[@lib.Instruction],
) -> Bool {
match body {
[@lib.TryTable(@lib.VoidBlockType, catches, @lib.Expr(try_body))] =>
run_hot_pipeline_raw_vacuum_try_table_catches_all_target_owner(catches) &&
run_hot_pipeline_raw_vacuum_body_can_flatten_no_throw_exception_region(
try_body,
)
_ => false
}
}
///|
fn run_hot_pipeline_raw_vacuum_instrs_have_flattenable_no_throw_exception_region(
instrs : Array[@lib.Instruction],
) -> Bool {
for instr in instrs {
match instr {
@lib.Try(_, @lib.Expr(body), _, _) =>
if run_hot_pipeline_raw_vacuum_body_can_flatten_no_throw_exception_region(
body,
) {
return true
}
@lib.TryTable(_, _, @lib.Expr(body)) =>
if run_hot_pipeline_raw_vacuum_body_is_static_null_throw_ref_trap(body) ||
run_hot_pipeline_raw_vacuum_instrs_have_flattenable_no_throw_exception_region(
body,
) {
return true
}
@lib.Block(block_type, @lib.Expr(body)) => {
if block_type == @lib.BlockType::void_() &&
run_hot_pipeline_raw_vacuum_body_is_owner_targeted_no_throw_try_table(
body,
) {
return true
}
if run_hot_pipeline_raw_vacuum_instrs_have_flattenable_no_throw_exception_region(
body,
) {
return true
}
}
@lib.Loop(_, @lib.Expr(body)) =>
if run_hot_pipeline_raw_vacuum_instrs_have_flattenable_no_throw_exception_region(
body,
) {
return true
}
@lib.If(_, then_body, else_body) => {
if run_hot_pipeline_raw_vacuum_instrs_have_flattenable_no_throw_exception_region(
then_body,
) {
return true
}
match else_body {
Some(body) =>
if run_hot_pipeline_raw_vacuum_instrs_have_flattenable_no_throw_exception_region(
body,
) {
return true
}
None => ()
}
}
_ => ()
}
}
false
}
///|
fn run_hot_pipeline_raw_vacuum_instrs_have_legacy_try(
instrs : Array[@lib.Instruction],
) -> Bool {
for instr in instrs {
match instr {
@lib.Try(_, _, _, _) => return true
@lib.Block(_, @lib.Expr(body))
| @lib.Loop(_, @lib.Expr(body))
| @lib.TryTable(_, _, @lib.Expr(body)) =>
if run_hot_pipeline_raw_vacuum_instrs_have_legacy_try(body) {
return true
}
@lib.If(_, then_body, else_body) => {
if run_hot_pipeline_raw_vacuum_instrs_have_legacy_try(then_body) {
return true
}
match else_body {
Some(body) =>
if run_hot_pipeline_raw_vacuum_instrs_have_legacy_try(body) {
return true
}
None => ()
}
}
_ => ()
}
}
false
}
///|
fn run_hot_pipeline_raw_vacuum_preclean_dropped_arm(
body : Array[@lib.Instruction],
module_ctx : @ir.HotModuleContext?,
) -> Array[@lib.Instruction] {
let dropped = body.copy()
dropped.push(@lib.Instruction::drop())
let (cleaned, _) = run_hot_pipeline_raw_vacuum_preclean_instrs_owned(
dropped, module_ctx,
)
cleaned
}
///|
// This class cannot trigger any preclean rule: it has no Drop/Nop, allocation
// store, changing control header, or terminal instruction that truncates a
// suffix. Loop headers only change when followed by Drop. Keep unsupported
// controls and unknown leaves on the fully owned worker.
fn run_hot_pipeline_raw_vacuum_preclean_flat_loop_is_unchanged(
instrs : Array[@lib.Instruction],
) -> Bool {
for instr in instrs {
match instr {
@lib.Loop(_, @lib.Expr(body)) =>
if !run_hot_pipeline_raw_vacuum_preclean_flat_loop_is_unchanged(body) {
return false
}
@lib.LocalSet(_)
| @lib.LocalTee(_)
| @lib.GlobalSet(_)
| @lib.Call(_)
| @lib.CallIndirect(_, _)
| @lib.CallRef(_)
| @lib.BrIf(_)
| @lib.I32Store(_)
| @lib.I64Store(_)
| @lib.F32Store(_)
| @lib.F64Store(_)
| @lib.I32Store8(_)
| @lib.I32Store16(_)
| @lib.I64Store8(_)
| @lib.I64Store16(_)
| @lib.I64Store32(_)
| @lib.I32DivS
| @lib.I32DivU
| @lib.I32RemS
| @lib.I32RemU
| @lib.I64DivS
| @lib.I64DivU
| @lib.I64RemS
| @lib.I64RemU => ()
_ =>
if !run_hot_pipeline_raw_vacuum_instr_is_idempotent_scalar_load(instr) &&
run_hot_pipeline_raw_vacuum_pure_instr_operand_count(instr) is None {
return false
}
}
}
true
}
///|
fn run_hot_pipeline_raw_vacuum_preclean_instrs(
instrs : Array[@lib.Instruction],
module_ctx : @ir.HotModuleContext?,
) -> (Array[@lib.Instruction], Bool) {
if run_hot_pipeline_raw_vacuum_preclean_flat_loop_is_unchanged(instrs) {
return (instrs, false)
}
run_hot_pipeline_raw_vacuum_preclean_instrs_owned(instrs, module_ctx)
}
///|
fn run_hot_pipeline_raw_vacuum_preclean_instrs_owned(
instrs : Array[@lib.Instruction],
module_ctx : @ir.HotModuleContext?,
) -> (Array[@lib.Instruction], Bool) {
let rewritten : Array[@lib.Instruction] = []
let mut changed = false
let mut idx = 0
let mut prev_may_fallthrough = true
while idx < instrs.length() {
if !prev_may_fallthrough {
changed = true
break
}
match
run_hot_pipeline_raw_vacuum_dropped_pure_multivalue_width_at(
instrs, idx, module_ctx,
) {
Some(width) => {
idx += width
changed = true
continue
}
None => ()
}
if idx + 3 < instrs.length() {
match (instrs[idx], instrs[idx + 1], instrs[idx + 2], instrs[idx + 3]) {
(
@lib.I32Const(_),
@lib.I32Const(@lib.I32(length)),
@lib.ArrayNew(_),
@lib.Drop,
) if length >= 0 => {
idx += 4
changed = true
continue
}
_ => ()
}
}
if idx + 2 < instrs.length() {
match (instrs[idx], instrs[idx + 1], instrs[idx + 2]) {
(@lib.I32Const(@lib.I32(length)), @lib.ArrayNewDefault(_), @lib.Drop) if length >=
0 => {
idx += 3
changed = true
continue
}
_ => ()
}
}
match
run_hot_pipeline_raw_vacuum_dropped_simple_struct_length_at(
instrs, idx, module_ctx,
) {
Some(length) => {
idx += length
changed = true
continue
}
None => ()
}
if run_hot_pipeline_raw_vacuum_dropped_ref_func_struct_at(
instrs, idx, module_ctx,
) {
idx += 3
changed = true
continue
}
match
run_hot_pipeline_raw_vacuum_fresh_array_set_replacement_at(
instrs, idx, module_ctx,
) {
Some(replacement) => {
for instr in replacement {
rewritten.push(instr)
}
idx += 5
changed = true
continue
}
None => ()
}
if idx + 5 < instrs.length() &&
instrs[idx] == instrs[idx + 3] &&
instrs[idx + 1] == instrs[idx + 4] &&
instrs[idx + 2] == @lib.Instruction::drop() &&
instrs[idx + 5] == @lib.Instruction::drop() &&
instrs[idx] is @lib.LocalGet(_) &&
run_hot_pipeline_raw_vacuum_instr_is_idempotent_scalar_load(
instrs[idx + 1],
) {
let address = instrs[idx]
let load = instrs[idx + 1]
rewritten.push(address)
rewritten.push(load)
rewritten.push(@lib.Instruction::drop())
idx += 6
while idx + 2 < instrs.length() &&
instrs[idx] == address &&
instrs[idx + 1] == load &&
instrs[idx + 2] == @lib.Instruction::drop() {
idx += 3
}
changed = true
continue
}
let instr = instrs[idx]
match instr {
@lib.Nop => {
changed = true
idx = idx + 1
continue
}
@lib.Block(block_type, @lib.Expr(body)) => {
if block_type != @lib.BlockType::void_() &&
idx + 1 < instrs.length() &&
instrs[idx + 1] is @lib.Drop &&
(
run_hot_pipeline_raw_vacuum_body_has_only_pure_self_branch(body) ||
run_hot_pipeline_raw_vacuum_body_has_only_pure_outer_branch_wrapper(
body,
)
) {
changed = true
idx = idx + 2
continue
}
if block_type == @lib.BlockType::void_() &&
run_hot_pipeline_raw_vacuum_body_is_owner_targeted_no_throw_try_table(
body,
) {
match body[0] {
@lib.TryTable(_, _, @lib.Expr(try_body)) => {
let (next_try_body, _) = run_hot_pipeline_raw_vacuum_preclean_instrs_owned(
try_body, module_ctx,
)
for next_instr in next_try_body {
rewritten.push(next_instr)
}
}
_ => ()
}
changed = true
idx = idx + 1
continue
}
match block_type {
@lib.VoidBlockType =>
match
run_hot_pipeline_raw_vacuum_flatten_terminal_owner_branch(body) {
Some(flattened) => {
for next_instr in flattened {
rewritten.push(next_instr)
}
changed = true
idx = idx + 1
continue
}
None => ()
}
_ => ()
}
match block_type {
@lib.VoidBlockType if body.length() == 0 &&
(
run_hot_pipeline_raw_vacuum_instrs_have_f1895_empty_sibling_at(
instrs, idx,
) ||
run_hot_pipeline_raw_vacuum_instrs_have_f2072_empty_sibling_at(
instrs, idx,
) ||
run_hot_pipeline_raw_vacuum_instrs_have_f2842_empty_block_before_live_if_at(
instrs, idx,
) ||
run_hot_pipeline_raw_vacuum_empty_block_before_local_copy_continuation(
instrs, idx,
)
) => {
changed = true
idx = idx + 1
continue
}
@lib.VoidBlockType => {
match
run_hot_pipeline_raw_vacuum_try_flatten_f5138_outer_singleton_parent(
body, module_ctx,
) {
Some(flattened) => {
for next_instr in flattened {
rewritten.push(next_instr)
}
changed = true
idx = idx + 1
continue
}
None => ()
}
match
run_hot_pipeline_raw_vacuum_try_flatten_f364_four_field_child_before_continuation(
body, module_ctx,
) {
Some(next_body) => {
rewritten.push(
@lib.Instruction::block(block_type, @lib.Expr::new(next_body)),
)
changed = true
idx = idx + 1
continue
}
None => ()
}
match
run_hot_pipeline_raw_vacuum_try_flatten_f364_four_field_singleton_child(
body, module_ctx,
) {
Some(next_body) => {
rewritten.push(
@lib.Instruction::block(block_type, @lib.Expr::new(next_body)),
)
changed = true
idx = idx + 1
continue
}
None => ()
}
}
_ => ()
}
match block_type {
@lib.VoidBlockType if instrs.length() == 1 &&
run_hot_pipeline_raw_vacuum_body_is_single_block_prefix_if_wrapper_candidate(
body,
) &&
!run_hot_pipeline_raw_vacuum_body_is_predicate_local_continuation_preserve_candidate(
body,
) &&
!run_hot_pipeline_raw_vacuum_body_is_local_set_leading_prefix_if_preserve_candidate(
body,
) &&
!run_hot_pipeline_raw_vacuum_body_is_five_arg_allocator_release_prefix_if_preserve_candidate(
body,
) &&
!run_hot_pipeline_raw_vacuum_body_is_f1346_local_set_release_prefix_if_preserve_candidate(
body,
) &&
!run_hot_pipeline_raw_vacuum_body_is_f3469_const_arg_local_set_release_prefix_if_preserve_candidate(
body,
) &&
!run_hot_pipeline_raw_vacuum_body_is_f5138_load_linked_result_prefix_parent_preserve_candidate(
body,
) &&
!run_hot_pipeline_raw_vacuum_body_is_f5138_paired_result_prefix_parent_preserve_candidate(
body,
) &&
!run_hot_pipeline_raw_vacuum_instrs_contain_unreachable(body) &&
!run_hot_pipeline_raw_vacuum_body_targets_owner(body, 0) => {
let rebased = run_hot_pipeline_raw_vacuum_rebase_outer_body(body, 0)
let (clean_rebased, _) = run_hot_pipeline_raw_vacuum_preclean_instrs_owned(
rebased, module_ctx,
)
for next_instr in clean_rebased {
rewritten.push(next_instr)
}
changed = true
idx = idx + 1
continue
}
@lib.VoidBlockType if run_hot_pipeline_raw_vacuum_body_is_artifact_call_prefix_parent_unwrap_candidate(
body,
) &&
run_hot_pipeline_raw_vacuum_body_has_local_set_outer_br_continuation(
instrs, idx,
) &&
!run_hot_pipeline_raw_vacuum_body_targets_owner(body, 0) => {
let rebased = run_hot_pipeline_raw_vacuum_rebase_outer_body(body, 0)
for next_instr in rebased {
rewritten.push(next_instr)
}
changed = true
idx = idx + 1
continue
}
@lib.VoidBlockType if run_hot_pipeline_raw_vacuum_try_flatten_f4070_one_arg_prefix_parent_before_result_dispatch(
body,
)
is Some(_) => {
match
run_hot_pipeline_raw_vacuum_try_flatten_f4070_one_arg_prefix_parent_before_result_dispatch(
body,
) {
Some(flattened) =>
for next_instr in flattened {
rewritten.push(next_instr)
}
None => ()
}
changed = true
idx = idx + 1
continue
}
@lib.VoidBlockType if run_hot_pipeline_raw_vacuum_try_flatten_f4915_tag1_owner_dispatch_before_tag95_return(
body, instrs, idx,
)
is Some(_) => {
match
run_hot_pipeline_raw_vacuum_try_flatten_f4915_tag1_owner_dispatch_before_tag95_return(
body, instrs, idx,
) {
Some(flattened) =>
for next_instr in flattened {
rewritten.push(next_instr)
}
None => ()
}
changed = true
idx = idx + 1
continue
}
@lib.VoidBlockType if run_hot_pipeline_raw_vacuum_try_flatten_f4859_leading_owner_exit_dispatch_before_case0_return(
body, instrs, idx,
)
is Some(_) => {
match
run_hot_pipeline_raw_vacuum_try_flatten_f4859_leading_owner_exit_dispatch_before_case0_return(
body, instrs, idx,
) {
Some(flattened) =>
for next_instr in flattened {
rewritten.push(next_instr)
}
None => ()
}
changed = true
idx = idx + 1
continue
}
@lib.VoidBlockType if run_hot_pipeline_raw_vacuum_try_flatten_f5288_leading_tag_dispatch_wrappers_before_returns(
body, instrs, idx,
)
is Some(_) => {
match
run_hot_pipeline_raw_vacuum_try_flatten_f5288_leading_tag_dispatch_wrappers_before_returns(
body, instrs, idx,
) {
Some(flattened) =>
for next_instr in flattened {
rewritten.push(next_instr)
}
None => ()
}
changed = true
idx = idx + 1
continue
}
@lib.VoidBlockType if run_hot_pipeline_raw_vacuum_try_flatten_f5392_leading_tag2_dispatch_wrappers_before_call_return(
body, instrs, idx,
)
is Some(_) => {
match
run_hot_pipeline_raw_vacuum_try_flatten_f5392_leading_tag2_dispatch_wrappers_before_call_return(
body, instrs, idx,
) {
Some(flattened) =>
for next_instr in flattened {
rewritten.push(next_instr)
}
None => ()
}
changed = true
idx = idx + 1
continue
}
@lib.VoidBlockType if run_hot_pipeline_raw_vacuum_body_is_f5351_leading_tag_dispatch_before_zero_return(
body, instrs, idx,
) => {
let rebased = run_hot_pipeline_raw_vacuum_rebase_outer_body(body, 0)
let (clean_rebased, _) = run_hot_pipeline_raw_vacuum_preclean_instrs_owned(
rebased, module_ctx,
)
for next_instr in clean_rebased {
rewritten.push(next_instr)
}
changed = true
idx = idx + 1
continue
}
_ => ()
}
let (next_body, body_changed) = run_hot_pipeline_raw_vacuum_preclean_instrs_owned(
body, module_ctx,
)
changed = changed || body_changed
match block_type {
@lib.VoidBlockType if next_body.length() == 0 &&
run_hot_pipeline_raw_vacuum_empty_block_before_local_copy_continuation(
instrs, idx,
) => changed = true
@lib.VoidBlockType if run_hot_pipeline_raw_vacuum_body_is_local_copy_child(
next_body,
) &&
run_hot_pipeline_raw_vacuum_has_f6286_f6594_f6628_f6785_f7095_f7130_f7133_f7138_f7141_f7263_parser_branch_continuation(
instrs, idx,
) => {
for next_instr in next_body {
rewritten.push(next_instr)
}
changed = true
}
@lib.VoidBlockType if run_hot_pipeline_raw_vacuum_body_is_f5154_retain_pair_result_before_slot_store_preserve_candidate(
next_body, instrs, idx,
) ||
run_hot_pipeline_raw_vacuum_body_is_f5184_single_retain_result_before_drop_continuation_preserve_candidate(
next_body, instrs, idx,
) =>
rewritten.push(
@lib.Instruction::block(block_type, @lib.Expr::new(next_body)),
)
@lib.VoidBlockType if run_hot_pipeline_raw_vacuum_body_is_f4493_offset4_call_result_tag1_prefix_before_allocation_return_preserve_candidate(
next_body, instrs, idx,
) =>
rewritten.push(
@lib.Instruction::block(block_type, @lib.Expr::new(next_body)),
)
@lib.VoidBlockType if run_hot_pipeline_raw_vacuum_body_is_f5032_two_release_prefix_if_parent_preserve_candidate(
next_body,
) =>
rewritten.push(
@lib.Instruction::block(block_type, @lib.Expr::new(next_body)),
)
@lib.VoidBlockType if run_hot_pipeline_raw_vacuum_body_is_f2094_increment_probe_prefix_before_block_continuation_preserve_candidate(
next_body, instrs, idx,
) ||
run_hot_pipeline_raw_vacuum_body_is_f2094_increment_probe_prefix_before_block_continuation_preserve_candidate(
body, instrs, idx,
) =>
rewritten.push(
@lib.Instruction::block(block_type, @lib.Expr::new(next_body)),
)
@lib.VoidBlockType if run_hot_pipeline_raw_vacuum_try_flatten_f2850_local_copy_guard_wrapper(
next_body,
)
is Some(_) => {
match
run_hot_pipeline_raw_vacuum_try_flatten_f2850_local_copy_guard_wrapper(
next_body,
) {
Some(flattened) =>
rewritten.push(
@lib.Instruction::block(block_type, @lib.Expr::new(flattened)),
)
None => ()
}
changed = true
}
@lib.VoidBlockType if run_hot_pipeline_raw_vacuum_try_flatten_f1738_loop_head_local_copy_child(
next_body, instrs, idx,
)
is Some(_) => {
match
run_hot_pipeline_raw_vacuum_try_flatten_f1738_loop_head_local_copy_child(
next_body, instrs, idx,
) {
Some(flattened) =>
rewritten.push(
@lib.Instruction::block(block_type, @lib.Expr::new(flattened)),
)
None => ()
}
changed = true
}
@lib.VoidBlockType if run_hot_pipeline_raw_vacuum_try_flatten_f1743_loop_head_copy_before_nested_dispatch(
next_body, instrs, idx,
)
is Some(_) => {
match
run_hot_pipeline_raw_vacuum_try_flatten_f1743_loop_head_copy_before_nested_dispatch(
next_body, instrs, idx,
) {
Some(flattened) =>
rewritten.push(
@lib.Instruction::block(block_type, @lib.Expr::new(flattened)),
)
None => ()
}
changed = true
}
@lib.VoidBlockType if run_hot_pipeline_raw_vacuum_try_flatten_f3579_loop_head_local_copy_guard_wrapper(
next_body,
)
is Some(_) => {
match
run_hot_pipeline_raw_vacuum_try_flatten_f3579_loop_head_local_copy_guard_wrapper(
next_body,
) {
Some(flattened) =>
rewritten.push(
@lib.Instruction::block(block_type, @lib.Expr::new(flattened)),
)
None => ()
}
changed = true
}
@lib.VoidBlockType if run_hot_pipeline_raw_vacuum_try_flatten_f3841_tag_dispatch_singleton_child_before_local_set_branch(
next_body, instrs, idx,
)
is Some(_) => {
match
run_hot_pipeline_raw_vacuum_try_flatten_f3841_tag_dispatch_singleton_child_before_local_set_branch(
next_body, instrs, idx,
) {
Some(flattened) =>
rewritten.push(
@lib.Instruction::block(block_type, @lib.Expr::new(flattened)),
)
None => ()
}
changed = true
}
@lib.VoidBlockType if run_hot_pipeline_raw_vacuum_try_flatten_f3998_tag2_prefix_child_before_loop_continuation(
next_body, instrs, idx,
)
is Some(_) => {
match
run_hot_pipeline_raw_vacuum_try_flatten_f3998_tag2_prefix_child_before_loop_continuation(
next_body, instrs, idx,
) {
Some(flattened) =>
rewritten.push(
@lib.Instruction::block(block_type, @lib.Expr::new(flattened)),
)
None => ()
}
changed = true
}
@lib.VoidBlockType if run_hot_pipeline_raw_vacuum_try_flatten_f5419_tag1_singleton_owner_before_stream_loop(
next_body,
)
is Some(_) => {
match
run_hot_pipeline_raw_vacuum_try_flatten_f5419_tag1_singleton_owner_before_stream_loop(
next_body,
) {
Some(flattened) =>
rewritten.push(
@lib.Instruction::block(block_type, @lib.Expr::new(flattened)),
)
None => ()
}
changed = true
}
@lib.VoidBlockType if run_hot_pipeline_raw_vacuum_try_flatten_f1963_local_set_sentinel_comparison_wrapper(
next_body,
)
is Some(_) => {
match
run_hot_pipeline_raw_vacuum_try_flatten_f1963_local_set_sentinel_comparison_wrapper(
next_body,
) {
Some(flattened) =>
rewritten.push(
@lib.Instruction::block(block_type, @lib.Expr::new(flattened)),
)
None => ()
}
changed = true
}
@lib.VoidBlockType if run_hot_pipeline_raw_vacuum_try_flatten_f1948_call_tee_sentinel_branch_wrappers(
next_body,
)
is Some(_) => {
match
run_hot_pipeline_raw_vacuum_try_flatten_f1948_call_tee_sentinel_branch_wrappers(
next_body,
) {
Some(flattened) =>
rewritten.push(
@lib.Instruction::block(block_type, @lib.Expr::new(flattened)),
)
None => ()
}
changed = true
}
@lib.VoidBlockType if run_hot_pipeline_raw_vacuum_try_flatten_f1946_loop_arg_copy_before_eqz_call(
next_body,
)
is Some(_) => {
match
run_hot_pipeline_raw_vacuum_try_flatten_f1946_loop_arg_copy_before_eqz_call(
next_body,
) {
Some(flattened) =>
rewritten.push(
@lib.Instruction::block(block_type, @lib.Expr::new(flattened)),
)
None => ()
}
changed = true
}
@lib.VoidBlockType if run_hot_pipeline_raw_vacuum_try_flatten_f1054_local_copy_child_before_load_prefix(
next_body,
)
is Some(_) => {
match
run_hot_pipeline_raw_vacuum_try_flatten_f1054_local_copy_child_before_load_prefix(
next_body,
) {
Some(flattened) =>
rewritten.push(
@lib.Instruction::block(block_type, @lib.Expr::new(flattened)),
)
None => ()
}
changed = true
}
@lib.VoidBlockType if run_hot_pipeline_raw_vacuum_try_flatten_f1148_tag_selection_wrappers_before_load_continuation(
next_body,
)
is Some(_) => {
match
run_hot_pipeline_raw_vacuum_try_flatten_f1148_tag_selection_wrappers_before_load_continuation(
next_body,
) {
Some(flattened) =>
rewritten.push(
@lib.Instruction::block(block_type, @lib.Expr::new(flattened)),
)
None => ()
}
changed = true
}
@lib.VoidBlockType if run_hot_pipeline_raw_vacuum_try_flatten_f1167_br_table_child_before_unreachable(
next_body,
)
is Some(_) => {
match
run_hot_pipeline_raw_vacuum_try_flatten_f1167_br_table_child_before_unreachable(
next_body,
) {
Some(flattened) =>
rewritten.push(
@lib.Instruction::block(block_type, @lib.Expr::new(flattened)),
)
None => ()
}
changed = true
}
@lib.VoidBlockType if run_hot_pipeline_raw_vacuum_try_flatten_f1940_call_tee_br_table_wrapper_before_unreachable(
next_body,
)
is Some(_) => {
match
run_hot_pipeline_raw_vacuum_try_flatten_f1940_call_tee_br_table_wrapper_before_unreachable(
next_body,
) {
Some(flattened) =>
rewritten.push(
@lib.Instruction::block(block_type, @lib.Expr::new(flattened)),
)
None => ()
}
changed = true
}
@lib.VoidBlockType if run_hot_pipeline_raw_vacuum_try_flatten_f1956_call_tee_br_table_wrapper_before_unreachable(
next_body,
)
is Some(_) => {
match
run_hot_pipeline_raw_vacuum_try_flatten_f1956_call_tee_br_table_wrapper_before_unreachable(
next_body,
) {
Some(flattened) =>
rewritten.push(
@lib.Instruction::block(block_type, @lib.Expr::new(flattened)),
)
None => ()
}
changed = true
}
@lib.VoidBlockType if run_hot_pipeline_raw_vacuum_try_flatten_f2054_call_tee_br_table_wrapper_before_unreachable(
next_body,
)
is Some(_) => {
match
run_hot_pipeline_raw_vacuum_try_flatten_f2054_call_tee_br_table_wrapper_before_unreachable(
next_body,
) {
Some(flattened) =>
rewritten.push(
@lib.Instruction::block(block_type, @lib.Expr::new(flattened)),
)
None => ()
}
changed = true
}
@lib.VoidBlockType if run_hot_pipeline_raw_vacuum_try_flatten_f2321_call_arg_tee_br_table_release_ladder_wrapper_before_unreachable(
next_body,
)
is Some(_) => {
match
run_hot_pipeline_raw_vacuum_try_flatten_f2321_call_arg_tee_br_table_release_ladder_wrapper_before_unreachable(
next_body,
) {
Some(flattened) =>
rewritten.push(
@lib.Instruction::block(block_type, @lib.Expr::new(flattened)),
)
None => ()
}
changed = true
}
@lib.VoidBlockType if run_hot_pipeline_raw_vacuum_try_flatten_f2334_tag236_wrapper_before_offset8_continuation(
next_body,
)
is Some(_) => {
match
run_hot_pipeline_raw_vacuum_try_flatten_f2334_tag236_wrapper_before_offset8_continuation(
next_body,
) {
Some(flattened) =>
rewritten.push(
@lib.Instruction::block(block_type, @lib.Expr::new(flattened)),
)
None => ()
}
changed = true
}
@lib.VoidBlockType if run_hot_pipeline_raw_vacuum_try_flatten_f2651_load_br_table_wrapper_before_converter_continuation(
next_body,
)
is Some(_) => {
match
run_hot_pipeline_raw_vacuum_try_flatten_f2651_load_br_table_wrapper_before_converter_continuation(
next_body,
) {
Some(flattened) =>
rewritten.push(
@lib.Instruction::block(block_type, @lib.Expr::new(flattened)),
)
None => ()
}
changed = true
}
@lib.VoidBlockType if run_hot_pipeline_raw_vacuum_try_flatten_f2696_tee_br_table_release_wrapper_before_unreachable(
next_body,
)
is Some(_) => {
match
run_hot_pipeline_raw_vacuum_try_flatten_f2696_tee_br_table_release_wrapper_before_unreachable(
next_body,
) {
Some(flattened) =>
rewritten.push(
@lib.Instruction::block(block_type, @lib.Expr::new(flattened)),
)
None => ()
}
changed = true
}
@lib.VoidBlockType if run_hot_pipeline_raw_vacuum_try_flatten_f2714_direct_call_br_table_release_wrapper_before_local_set_continuation(
next_body,
)
is Some(_) => {
match
run_hot_pipeline_raw_vacuum_try_flatten_f2714_direct_call_br_table_release_wrapper_before_local_set_continuation(
next_body,
) {
Some(flattened) =>
rewritten.push(
@lib.Instruction::block(block_type, @lib.Expr::new(flattened)),
)
None => ()
}
changed = true
}
@lib.VoidBlockType if run_hot_pipeline_raw_vacuum_try_flatten_f2905_direct_call_br_table_local_load_wrapper_before_call_continuation(
next_body,
)
is Some(_) => {
match
run_hot_pipeline_raw_vacuum_try_flatten_f2905_direct_call_br_table_local_load_wrapper_before_call_continuation(
next_body,
) {
Some(flattened) =>
rewritten.push(
@lib.Instruction::block(block_type, @lib.Expr::new(flattened)),
)
None => ()
}
changed = true
}
@lib.VoidBlockType if run_hot_pipeline_raw_vacuum_try_flatten_f2775_call_tee_cleanup_return_if_wrapper_before_sibling_branch(
next_body,
)
is Some(_) => {
match
run_hot_pipeline_raw_vacuum_try_flatten_f2775_call_tee_cleanup_return_if_wrapper_before_sibling_branch(
next_body,
) {
Some(flattened) =>
rewritten.push(
@lib.Instruction::block(block_type, @lib.Expr::new(flattened)),
)
None => ()
}
changed = true
}
@lib.VoidBlockType if run_hot_pipeline_raw_vacuum_try_flatten_f2823_call_tee_branch_cleanup_wrapper_before_i64_return(
next_body,
)
is Some(_) => {
match
run_hot_pipeline_raw_vacuum_try_flatten_f2823_call_tee_branch_cleanup_wrapper_before_i64_return(
next_body,
) {
Some(flattened) =>
rewritten.push(
@lib.Instruction::block(block_type, @lib.Expr::new(flattened)),
)
None => ()
}
changed = true
}
@lib.VoidBlockType if run_hot_pipeline_raw_vacuum_try_flatten_f2297_call_tee_br_table_release_ladder_wrapper_before_unreachable(
next_body,
)
is Some(_) => {
match
run_hot_pipeline_raw_vacuum_try_flatten_f2297_call_tee_br_table_release_ladder_wrapper_before_unreachable(
next_body,
) {
Some(flattened) =>
rewritten.push(
@lib.Instruction::block(block_type, @lib.Expr::new(flattened)),
)
None => ()
}
changed = true
}
@lib.VoidBlockType if run_hot_pipeline_raw_vacuum_try_flatten_f2320_call_tee_br_table_double_update_wrapper_before_unreachable(
next_body,
)
is Some(_) => {
match
run_hot_pipeline_raw_vacuum_try_flatten_f2320_call_tee_br_table_double_update_wrapper_before_unreachable(
next_body,
) {
Some(flattened) =>
rewritten.push(
@lib.Instruction::block(block_type, @lib.Expr::new(flattened)),
)
None => ()
}
changed = true
}
@lib.VoidBlockType if run_hot_pipeline_raw_vacuum_try_flatten_f1792_br_table_dispatch_return_wrapper(
next_body,
)
is Some(_) => {
match
run_hot_pipeline_raw_vacuum_try_flatten_f1792_br_table_dispatch_return_wrapper(
next_body,
) {
Some(flattened) =>
rewritten.push(
@lib.Instruction::block(block_type, @lib.Expr::new(flattened)),
)
None => ()
}
changed = true
}
@lib.VoidBlockType if run_hot_pipeline_raw_vacuum_try_flatten_f1836_loop_if_owner_branch_wrapper(
next_body,
)
is Some(_) => {
match
run_hot_pipeline_raw_vacuum_try_flatten_f1836_loop_if_owner_branch_wrapper(
next_body,
) {
Some(flattened) =>
rewritten.push(
@lib.Instruction::block(block_type, @lib.Expr::new(flattened)),
)
None => ()
}
changed = true
}
@lib.VoidBlockType if run_hot_pipeline_raw_vacuum_try_drop_f1895_empty_sibling(
next_body,
)
is Some(_) => {
match
run_hot_pipeline_raw_vacuum_try_drop_f1895_empty_sibling(
next_body,
) {
Some(cleaned_body) =>
rewritten.push(
@lib.Instruction::block(
block_type,
@lib.Expr::new(cleaned_body),
),
)
None => ()
}
changed = true
}
@lib.VoidBlockType if run_hot_pipeline_raw_vacuum_try_drop_f2072_empty_sibling(
next_body,
)
is Some(_) => {
match
run_hot_pipeline_raw_vacuum_try_drop_f2072_empty_sibling(
next_body,
) {
Some(cleaned_body) =>
rewritten.push(
@lib.Instruction::block(
block_type,
@lib.Expr::new(cleaned_body),
),
)
None => ()
}
changed = true
}
@lib.VoidBlockType if run_hot_pipeline_raw_vacuum_try_drop_f2842_empty_block_before_live_if(
next_body,
)
is Some(_) => {
match
run_hot_pipeline_raw_vacuum_try_drop_f2842_empty_block_before_live_if(
next_body,
) {
Some(cleaned_body) =>
rewritten.push(
@lib.Instruction::block(
block_type,
@lib.Expr::new(cleaned_body),
),
)
None => ()
}
changed = true
}
@lib.VoidBlockType if run_hot_pipeline_raw_vacuum_try_flatten_f1155_single_release_child_before_branch_continuation(
next_body,
)
is Some(_) => {
match
run_hot_pipeline_raw_vacuum_try_flatten_f1155_single_release_child_before_branch_continuation(
next_body,
) {
Some(flattened) =>
rewritten.push(
@lib.Instruction::block(block_type, @lib.Expr::new(flattened)),
)
None => ()
}
changed = true
}
@lib.VoidBlockType if run_hot_pipeline_raw_vacuum_try_flatten_f1260_tag_dispatch_child_before_append_update(
next_body,
)
is Some(_) => {
match
run_hot_pipeline_raw_vacuum_try_flatten_f1260_tag_dispatch_child_before_append_update(
next_body,
) {
Some(flattened) =>
rewritten.push(
@lib.Instruction::block(block_type, @lib.Expr::new(flattened)),
)
None => ()
}
changed = true
}
@lib.VoidBlockType if run_hot_pipeline_raw_vacuum_try_flatten_f2130_tag42_tag43_loop_head_dispatch_wrapper(
next_body,
)
is Some(_) => {
match
run_hot_pipeline_raw_vacuum_try_flatten_f2130_tag42_tag43_loop_head_dispatch_wrapper(
next_body,
) {
Some(flattened) =>
rewritten.push(
@lib.Instruction::block(block_type, @lib.Expr::new(flattened)),
)
None => ()
}
changed = true
}
@lib.VoidBlockType if run_hot_pipeline_raw_vacuum_body_is_f649_branchy_loop_dispatch_parent_wrapper_candidate(
next_body,
) => {
let rebased = run_hot_pipeline_raw_vacuum_rebase_outer_body(
next_body, 0,
)
for next_instr in rebased {
rewritten.push(next_instr)
}
changed = true
}
@lib.VoidBlockType if run_hot_pipeline_raw_vacuum_body_is_f749_double_branchy_selection_wrapper_candidate(
next_body,
) &&
run_hot_pipeline_raw_vacuum_has_f749_i64_return_continuation(
instrs, idx,
) => {
match next_body[0] {
@lib.Block(@lib.VoidBlockType, @lib.Expr(child_body)) => {
let rebased_once = run_hot_pipeline_raw_vacuum_rebase_outer_body(
child_body, 0,
)
let rebased_twice = run_hot_pipeline_raw_vacuum_rebase_outer_body(
rebased_once, 0,
)
for next_instr in rebased_twice {
rewritten.push(next_instr)
}
}
_ => ()
}
changed = true
}
@lib.VoidBlockType if run_hot_pipeline_raw_vacuum_body_is_f749_branchy_selection(
next_body,
) &&
run_hot_pipeline_raw_vacuum_has_f749_i64_return_continuation(
instrs, idx,
) => {
let rebased = run_hot_pipeline_raw_vacuum_rebase_outer_body(
next_body, 0,
)
for next_instr in rebased {
rewritten.push(next_instr)
}
changed = true
}
@lib.VoidBlockType if run_hot_pipeline_raw_vacuum_body_has_top_level_nop(
body,
) &&
run_hot_pipeline_raw_vacuum_body_is_branchy_loop_dispatch_singleton_wrapper_candidate(
next_body,
) => {
let rebased = run_hot_pipeline_raw_vacuum_rebase_outer_body(
next_body, 0,
)
for next_instr in rebased {
rewritten.push(next_instr)
}
changed = true
}
@lib.VoidBlockType if run_hot_pipeline_raw_vacuum_body_is_empty_block_call_prefix_if_wrapper_candidate(
next_body,
) &&
!run_hot_pipeline_raw_vacuum_instrs_contain_unreachable(next_body) => {
rewritten.push(
@lib.Instruction::block(
block_type,
@lib.Expr::new(
run_hot_pipeline_raw_vacuum_drop_leading_empty_blocks(
next_body,
),
),
),
)
changed = true
}
@lib.VoidBlockType if run_hot_pipeline_raw_vacuum_try_flatten_f4463_offset12_indexed_singleton_owner(
next_body,
)
is Some(_) => {
match
run_hot_pipeline_raw_vacuum_try_flatten_f4463_offset12_indexed_singleton_owner(
next_body,
) {
Some(flattened) =>
rewritten.push(
@lib.Instruction::block(block_type, @lib.Expr::new(flattened)),
)
None => ()
}
changed = true
}
@lib.VoidBlockType if next_body.length() == 1 &&
(match next_body[0] {
@lib.Block(@lib.VoidBlockType, @lib.Expr(child_body)) =>
(
(
run_hot_pipeline_raw_vacuum_body_is_if_wrapper_candidate(
child_body,
) &&
run_hot_pipeline_raw_vacuum_body_has_call_prefix_before_final_if(
child_body,
) &&
run_hot_pipeline_raw_vacuum_body_has_top_level_nop(body)
) ||
run_hot_pipeline_raw_vacuum_body_is_refcount_call_prefix_if_wrapper_candidate(
child_body,
)
) &&
!run_hot_pipeline_raw_vacuum_instrs_contain_unreachable(
child_body,
) &&
!run_hot_pipeline_raw_vacuum_body_targets_owner(child_body, 0)
_ => false
}) => {
match next_body[0] {
@lib.Block(@lib.VoidBlockType, @lib.Expr(child_body)) =>
rewritten.push(
@lib.Instruction::block(
block_type,
@lib.Expr::new(
run_hot_pipeline_raw_vacuum_rebase_outer_body(
child_body, 0,
),
),
),
)
_ => ()
}
changed = true
}
@lib.VoidBlockType if run_hot_pipeline_raw_vacuum_body_is_singleton_call_prefix_if_block(
next_body,
) &&
run_hot_pipeline_raw_vacuum_body_has_empty_block_call_prefix_shape_ignoring_nops(
body,
) &&
!run_hot_pipeline_raw_vacuum_body_targets_owner(next_body, 0) => {
let rebased = run_hot_pipeline_raw_vacuum_rebase_outer_body(
next_body, 0,
)
for next_instr in rebased {
rewritten.push(next_instr)
}
changed = true
}
@lib.VoidBlockType if body.length() == 1 &&
next_body.length() == 1 &&
(match next_body[0] {
@lib.Block(@lib.VoidBlockType, @lib.Expr(protected_body)) =>
run_hot_pipeline_raw_vacuum_body_is_local_set_leading_prefix_if_preserve_candidate(
protected_body,
) ||
run_hot_pipeline_raw_vacuum_body_is_five_arg_allocator_release_prefix_if_preserve_candidate(
protected_body,
) ||
run_hot_pipeline_raw_vacuum_body_is_two_arg_call_local_set_prefix_if_preserve_candidate(
protected_body,
) ||
run_hot_pipeline_raw_vacuum_body_is_multi_local_prefix_parent_preserve_candidate(
protected_body,
) ||
run_hot_pipeline_raw_vacuum_body_is_f5138_paired_result_prefix_parent_preserve_candidate(
protected_body,
)
_ => false
}) &&
!run_hot_pipeline_raw_vacuum_body_targets_owner(next_body, 0) => {
let rebased = run_hot_pipeline_raw_vacuum_rebase_outer_body(
next_body, 0,
)
for next_instr in rebased {
rewritten.push(next_instr)
}
changed = true
}
@lib.VoidBlockType if next_body.length() == 1 &&
(match next_body[0] {
@lib.Block(@lib.VoidBlockType, @lib.Expr(protected_body)) =>
run_hot_pipeline_raw_vacuum_body_is_branchy_local_set_call_prefix_parent_unwrap_candidate(
body, protected_body,
)
_ => false
}) &&
!run_hot_pipeline_raw_vacuum_body_targets_owner(next_body, 0) => {
let rebased = run_hot_pipeline_raw_vacuum_rebase_outer_body(
next_body, 0,
)
for next_instr in rebased {
rewritten.push(next_instr)
}
changed = true
}
@lib.VoidBlockType if body.length() == 1 &&
body[0] is @lib.Block(@lib.VoidBlockType, _) &&
run_hot_pipeline_raw_vacuum_body_is_recursive_call_prefix_if_wrapper_candidate(
next_body,
) &&
!(run_hot_pipeline_raw_vacuum_body_is_tag20_call_local_set_prefix_if_preserve_candidate(
next_body,
) &&
run_hot_pipeline_raw_vacuum_body_has_multi_local_prefix_parent_continuation(
instrs, idx,
)) &&
!(run_hot_pipeline_raw_vacuum_body_is_five_arg_allocator_release_prefix_if_preserve_candidate(
next_body,
) &&
run_hot_pipeline_raw_vacuum_body_has_five_arg_prefix_parent_continuation(
instrs, idx,
)) &&
!(run_hot_pipeline_raw_vacuum_body_is_two_arg_call_local_set_prefix_if_preserve_candidate(
next_body,
) &&
run_hot_pipeline_raw_vacuum_body_has_multi_local_prefix_parent_continuation(
instrs, idx,
)) &&
!run_hot_pipeline_raw_vacuum_instrs_contain_unreachable(next_body) &&
!run_hot_pipeline_raw_vacuum_body_targets_owner(next_body, 0) => {
let rebased = run_hot_pipeline_raw_vacuum_rebase_outer_body(
next_body, 0,
)
for next_instr in rebased {
rewritten.push(next_instr)
}
changed = true
}
@lib.VoidBlockType if run_hot_pipeline_raw_vacuum_body_is_f3561_load_retain_prefix_before_terminal_release_preserve_candidate(
next_body, instrs, idx,
) =>
rewritten.push(
@lib.Instruction::block(block_type, @lib.Expr::new(next_body)),
)
@lib.VoidBlockType if run_hot_pipeline_raw_vacuum_body_is_f4071_sentinel_prefix_before_allocation_preserve_candidate(
next_body, instrs, idx,
) =>
rewritten.push(
@lib.Instruction::block(block_type, @lib.Expr::new(next_body)),
)
@lib.VoidBlockType if run_hot_pipeline_raw_vacuum_body_is_f4232_sentinel_prefix_before_selected_local_return_preserve_candidate(
next_body, instrs, idx,
) =>
rewritten.push(
@lib.Instruction::block(block_type, @lib.Expr::new(next_body)),
)
@lib.VoidBlockType if run_hot_pipeline_raw_vacuum_body_is_f4310_side_effect_prefix_before_accumulator_stores_preserve_candidate(
next_body, instrs, idx,
) =>
rewritten.push(
@lib.Instruction::block(block_type, @lib.Expr::new(next_body)),
)
@lib.VoidBlockType if run_hot_pipeline_raw_vacuum_body_is_f4073_call_result_tag_dispatch_before_slot_store_preserve_candidate(
next_body, instrs, idx,
) =>
rewritten.push(
@lib.Instruction::block(block_type, @lib.Expr::new(next_body)),
)
@lib.VoidBlockType if run_hot_pipeline_raw_vacuum_body_is_f4483_indexed_call_result_tag1_prefix_preserve_candidate(
next_body, instrs, idx,
) =>
rewritten.push(
@lib.Instruction::block(block_type, @lib.Expr::new(next_body)),
)
@lib.VoidBlockType if run_hot_pipeline_raw_vacuum_body_is_f5277_indexed_retain_before_tag_continuation_preserve_candidate(
next_body, instrs, idx,
) =>
rewritten.push(
@lib.Instruction::block(block_type, @lib.Expr::new(next_body)),
)
@lib.VoidBlockType if run_hot_pipeline_raw_vacuum_body_is_f5369_load_call_tag_refcount_dispatch_before_allocation_continuation_preserve_candidate(
next_body, instrs, idx,
) =>
rewritten.push(
@lib.Instruction::block(block_type, @lib.Expr::new(next_body)),
)
@lib.VoidBlockType if run_hot_pipeline_raw_vacuum_body_is_f6113_retain_call_tag_dispatch_before_parent_continuation_preserve_candidate(
next_body, instrs, idx,
) =>
rewritten.push(
@lib.Instruction::block(block_type, @lib.Expr::new(next_body)),
)
@lib.VoidBlockType if run_hot_pipeline_raw_vacuum_body_is_f5083_selected_payload_before_conditional_handoff_preserve_candidate(
next_body, instrs, idx,
) =>
rewritten.push(
@lib.Instruction::block(block_type, @lib.Expr::new(next_body)),
)
@lib.VoidBlockType if run_hot_pipeline_raw_vacuum_body_is_f5027_selected_payload_before_allocation_return_preserve_candidate(
next_body, instrs, idx,
) =>
rewritten.push(
@lib.Instruction::block(block_type, @lib.Expr::new(next_body)),
)
@lib.VoidBlockType if run_hot_pipeline_raw_vacuum_body_is_f5154_retain_pair_result_before_slot_store_preserve_candidate(
next_body, instrs, idx,
) ||
run_hot_pipeline_raw_vacuum_body_is_f5184_single_retain_result_before_drop_continuation_preserve_candidate(
next_body, instrs, idx,
) =>
rewritten.push(
@lib.Instruction::block(block_type, @lib.Expr::new(next_body)),
)
@lib.VoidBlockType if run_hot_pipeline_raw_vacuum_body_is_side_effect_prefix_if_wrapper_candidate(
body,
) &&
run_hot_pipeline_raw_vacuum_body_is_side_effect_prefix_if_wrapper_candidate(
next_body,
) &&
!run_hot_pipeline_raw_vacuum_body_is_f1759_tag2_prefix_before_counter_store_preserve_candidate(
next_body, instrs, idx,
) &&
!(run_hot_pipeline_raw_vacuum_body_is_tag20_call_local_set_prefix_if_preserve_candidate(
next_body,
) &&
run_hot_pipeline_raw_vacuum_body_has_multi_local_prefix_parent_continuation(
instrs, idx,
)) &&
!(run_hot_pipeline_raw_vacuum_body_is_multi_local_prefix_if_preserve_candidate(
next_body,
) &&
run_hot_pipeline_raw_vacuum_body_has_multi_local_prefix_parent_continuation(
instrs, idx,
)) &&
!(run_hot_pipeline_raw_vacuum_body_is_f1383_dual_i64_probe_prefix_if_preserve_candidate(
next_body,
) &&
run_hot_pipeline_raw_vacuum_body_has_multi_local_prefix_parent_continuation(
instrs, idx,
)) &&
!(run_hot_pipeline_raw_vacuum_body_is_call_indirect_prefix_if_preserve_candidate(
next_body,
) &&
run_hot_pipeline_raw_vacuum_body_has_multi_local_prefix_parent_continuation(
instrs, idx,
)) &&
!run_hot_pipeline_raw_vacuum_body_is_f1224_loop_release_update_preserve_candidate(
next_body, instrs, idx,
) &&
!(run_hot_pipeline_raw_vacuum_body_is_f1900_increment_probe_prefix_before_reload_preserve_candidate(
next_body, instrs, idx,
) ||
run_hot_pipeline_raw_vacuum_body_is_f1900_increment_probe_prefix_before_reload_preserve_candidate(
body, instrs, idx,
)) &&
!(run_hot_pipeline_raw_vacuum_body_is_f2094_increment_probe_prefix_before_block_continuation_preserve_candidate(
next_body, instrs, idx,
) ||
run_hot_pipeline_raw_vacuum_body_is_f2094_increment_probe_prefix_before_block_continuation_preserve_candidate(
body, instrs, idx,
)) &&
!run_hot_pipeline_raw_vacuum_body_is_f3083_loop_body_release_update_prefix_if_preserve_candidate(
next_body,
) &&
!run_hot_pipeline_raw_vacuum_body_is_f5369_load_call_tag_refcount_dispatch_before_allocation_continuation_preserve_candidate(
next_body, instrs, idx,
) &&
!run_hot_pipeline_raw_vacuum_body_is_f6113_retain_call_tag_dispatch_before_parent_continuation_preserve_candidate(
next_body, instrs, idx,
) &&
!run_hot_pipeline_raw_vacuum_body_targets_owner(next_body, 0) => {
let rebased = run_hot_pipeline_raw_vacuum_rebase_outer_body(
next_body, 0,
)
for next_instr in rebased {
rewritten.push(next_instr)
}
changed = true
}
@lib.VoidBlockType if run_hot_pipeline_raw_vacuum_try_flatten_f4463_offset20_owner_before_failure_box_return(
next_body, instrs, idx,
)
is Some(_) => {
match
run_hot_pipeline_raw_vacuum_try_flatten_f4463_offset20_owner_before_failure_box_return(
next_body, instrs, idx,
) {
Some(flattened) =>
rewritten.push(
@lib.Instruction::block(block_type, @lib.Expr::new(flattened)),
)
None => ()
}
changed = true
}
@lib.VoidBlockType if run_hot_pipeline_raw_vacuum_try_flatten_f4311_nested_tag_dispatch_before_call_continuation(
next_body, instrs, idx,
)
is Some(_) => {
match
run_hot_pipeline_raw_vacuum_try_flatten_f4311_nested_tag_dispatch_before_call_continuation(
next_body, instrs, idx,
) {
Some(flattened) =>
for next_instr in flattened {
rewritten.push(next_instr)
}
None => ()
}
changed = true
}
@lib.VoidBlockType if run_hot_pipeline_raw_vacuum_try_flatten_f4915_tag1_owner_dispatch_before_tag95_return(
next_body, instrs, idx,
)
is Some(_) => {
match
run_hot_pipeline_raw_vacuum_try_flatten_f4915_tag1_owner_dispatch_before_tag95_return(
next_body, instrs, idx,
) {
Some(flattened) =>
for next_instr in flattened {
rewritten.push(next_instr)
}
None => ()
}
changed = true
}
@lib.VoidBlockType if run_hot_pipeline_raw_vacuum_try_flatten_f4919_call_if_return_before_tag99_return(
next_body, instrs, idx, module_ctx,
)
is Some(_) => {
match
run_hot_pipeline_raw_vacuum_try_flatten_f4919_call_if_return_before_tag99_return(
next_body, instrs, idx, module_ctx,
) {
Some(flattened) =>
rewritten.push(
@lib.Instruction::block(block_type, @lib.Expr::new(flattened)),
)
None => ()
}
changed = true
}
@lib.VoidBlockType if run_hot_pipeline_raw_vacuum_try_flatten_f4859_leading_owner_exit_dispatch_before_case0_return(
next_body, instrs, idx,
)
is Some(_) => {
match
run_hot_pipeline_raw_vacuum_try_flatten_f4859_leading_owner_exit_dispatch_before_case0_return(
next_body, instrs, idx,
) {
Some(flattened) =>
for next_instr in flattened {
rewritten.push(next_instr)
}
None => ()
}
changed = true
}
@lib.VoidBlockType if run_hot_pipeline_raw_vacuum_try_flatten_f4665_nested_tag_classification_before_renderer_continuation(
next_body, instrs, idx,
)
is Some(_) => {
match
run_hot_pipeline_raw_vacuum_try_flatten_f4665_nested_tag_classification_before_renderer_continuation(
next_body, instrs, idx,
) {
Some(flattened) =>
for next_instr in flattened {
rewritten.push(next_instr)
}
None => ()
}
changed = true
}
@lib.VoidBlockType if run_hot_pipeline_raw_vacuum_try_flatten_f4702_call_result_child_before_store_continuation(
next_body,
)
is Some(_) => {
match
run_hot_pipeline_raw_vacuum_try_flatten_f4702_call_result_child_before_store_continuation(
next_body,
) {
Some(flattened) =>
rewritten.push(
@lib.Instruction::block(block_type, @lib.Expr::new(flattened)),
)
None => ()
}
changed = true
}
@lib.VoidBlockType if run_hot_pipeline_raw_vacuum_body_is_f3317_singleton_retain_allocation_loop_wrapper(
next_body,
) &&
!run_hot_pipeline_raw_vacuum_body_targets_owner(next_body, 0) => {
match next_body[0] {
@lib.Block(@lib.VoidBlockType, @lib.Expr(child_body)) =>
rewritten.push(
@lib.Instruction::block(
block_type,
@lib.Expr::new(
run_hot_pipeline_raw_vacuum_rebase_outer_body(
child_body, 0,
),
),
),
)
_ => ()
}
changed = true
}
@lib.VoidBlockType if run_hot_pipeline_raw_vacuum_body_is_f4071_load_retain_tag_dispatch_before_selected_local_branch_preserve_candidate(
next_body, instrs, idx,
) ||
run_hot_pipeline_raw_vacuum_body_is_f4071_sentinel_prefix_before_allocation_preserve_candidate(
next_body, instrs, idx,
) ||
run_hot_pipeline_raw_vacuum_body_is_f4232_sentinel_prefix_before_selected_local_return_preserve_candidate(
next_body, instrs, idx,
) ||
run_hot_pipeline_raw_vacuum_body_is_f4310_side_effect_prefix_before_accumulator_stores_preserve_candidate(
next_body, instrs, idx,
) ||
run_hot_pipeline_raw_vacuum_body_is_f4073_call_result_tag_dispatch_before_slot_store_preserve_candidate(
next_body, instrs, idx,
) ||
run_hot_pipeline_raw_vacuum_body_is_f4483_indexed_call_result_tag1_prefix_preserve_candidate(
next_body, instrs, idx,
) ||
run_hot_pipeline_raw_vacuum_body_is_f5277_indexed_retain_before_tag_continuation_preserve_candidate(
next_body, instrs, idx,
) ||
run_hot_pipeline_raw_vacuum_body_is_f6113_retain_call_tag_dispatch_before_parent_continuation_preserve_candidate(
next_body, instrs, idx,
) ||
run_hot_pipeline_raw_vacuum_body_is_f5154_retain_pair_result_before_slot_store_preserve_candidate(
next_body, instrs, idx,
) ||
run_hot_pipeline_raw_vacuum_body_is_f5184_single_retain_result_before_drop_continuation_preserve_candidate(
next_body, instrs, idx,
) =>
rewritten.push(
@lib.Instruction::block(block_type, @lib.Expr::new(next_body)),
)
@lib.VoidBlockType if next_body.length() == 1 &&
(match next_body[0] {
@lib.Block(@lib.VoidBlockType, @lib.Expr(child_body)) =>
run_hot_pipeline_raw_vacuum_body_is_tag_load_dispatch_if_wrapper_candidate(
child_body,
) &&
!run_hot_pipeline_raw_vacuum_body_is_f364_four_field_tag_cascade_body(
child_body,
)
_ => false
}) => {
match next_body[0] {
@lib.Block(@lib.VoidBlockType, @lib.Expr(child_body)) =>
rewritten.push(
@lib.Instruction::block(
block_type,
@lib.Expr::new(
run_hot_pipeline_raw_vacuum_rebase_outer_body(
child_body, 0,
),
),
),
)
_ => ()
}
changed = true
}
@lib.VoidBlockType if run_hot_pipeline_raw_vacuum_body_is_tag_load_dispatch_if_wrapper_candidate(
next_body,
) &&
!run_hot_pipeline_raw_vacuum_body_is_f1759_tag2_prefix_before_counter_store_preserve_candidate(
next_body, instrs, idx,
) &&
!run_hot_pipeline_raw_vacuum_body_is_f364_four_field_tag_cascade_body(
next_body,
) &&
!run_hot_pipeline_raw_vacuum_body_targets_owner(next_body, 0) => {
let rebased = run_hot_pipeline_raw_vacuum_rebase_outer_body(
next_body, 0,
)
for next_instr in rebased {
rewritten.push(next_instr)
}
changed = true
}
@lib.VoidBlockType if run_hot_pipeline_raw_vacuum_body_is_terminal_unreachable_block_wrapper_candidate(
next_body,
) &&
!run_hot_pipeline_raw_vacuum_body_targets_owner(next_body, 0) => {
let rebased = run_hot_pipeline_raw_vacuum_rebase_outer_body(
next_body, 0,
)
for next_instr in rebased {
rewritten.push(next_instr)
}
changed = true
}
@lib.VoidBlockType if (
(
run_hot_pipeline_raw_vacuum_body_is_call_local_set_tag_cascade_if_wrapper_candidate(
body,
) &&
run_hot_pipeline_raw_vacuum_body_is_call_local_set_tag_cascade_if_wrapper_candidate(
next_body,
)
) ||
(
run_hot_pipeline_raw_vacuum_body_is_indexed_call_local_set_tag_cascade_if_wrapper_candidate(
body,
) &&
run_hot_pipeline_raw_vacuum_body_is_indexed_call_local_set_tag_cascade_if_wrapper_candidate(
next_body,
)
)
) &&
!(run_hot_pipeline_raw_vacuum_body_is_f364_four_field_tag_cascade_body(
next_body,
) &&
run_hot_pipeline_raw_vacuum_body_has_f996_tag_dispatch_parent_continuation(
instrs, idx,
)) &&
!run_hot_pipeline_raw_vacuum_body_targets_owner(next_body, 0) => {
let rebased = run_hot_pipeline_raw_vacuum_rebase_outer_body(
next_body, 0,
)
for next_instr in rebased {
rewritten.push(next_instr)
}
changed = true
}
@lib.VoidBlockType if run_hot_pipeline_raw_vacuum_try_wrap_load_linked_release_prefix(
next_body,
)
is Some(_) &&
!run_hot_pipeline_raw_vacuum_body_targets_owner(next_body, 0) => {
match
run_hot_pipeline_raw_vacuum_try_wrap_load_linked_release_prefix(
next_body,
) {
Some(wrapped) =>
rewritten.push(
@lib.Instruction::block(block_type, @lib.Expr::new(wrapped)),
)
None => ()
}
changed = true
}
@lib.VoidBlockType if run_hot_pipeline_raw_vacuum_body_is_singleton_load_linked_local_set_call_prefix_block(
body,
) &&
run_hot_pipeline_raw_vacuum_body_is_singleton_load_linked_local_set_call_prefix_block(
next_body,
) &&
!run_hot_pipeline_raw_vacuum_body_targets_owner(next_body, 0) => {
match next_body[0] {
@lib.Block(@lib.VoidBlockType, @lib.Expr(child_body)) =>
rewritten.push(
@lib.Instruction::block(
block_type,
@lib.Expr::new(
run_hot_pipeline_raw_vacuum_rebase_outer_body(
child_body, 0,
),
),
),
)
_ => ()
}
changed = true
}
@lib.VoidBlockType if run_hot_pipeline_raw_vacuum_body_is_single_flipped_if_wrapper_candidate(
next_body,
) &&
!run_hot_pipeline_raw_vacuum_body_targets_owner(next_body, 0) => {
let rebased = run_hot_pipeline_raw_vacuum_rebase_outer_body(
next_body, 0,
)
for next_instr in rebased {
rewritten.push(next_instr)
}
changed = true
}
@lib.VoidBlockType if (
run_hot_pipeline_raw_vacuum_body_is_unreachable_if_wrapper_candidate(
next_body,
) ||
run_hot_pipeline_raw_vacuum_body_is_call_tee_unreachable_if_wrapper_candidate(
next_body,
) ||
run_hot_pipeline_raw_vacuum_body_is_lowered_call_tee_unreachable_if_wrapper_candidate(
next_body,
)
) &&
!run_hot_pipeline_raw_vacuum_body_targets_owner(next_body, 0) => {
let rebased = run_hot_pipeline_raw_vacuum_rebase_outer_body(
next_body, 0,
)
for next_instr in rebased {
rewritten.push(next_instr)
}
changed = true
}
_ if run_hot_pipeline_raw_vacuum_block_type_is_empty(
module_ctx, block_type,
) &&
(
run_hot_pipeline_raw_vacuum_body_is_unreachable_if_wrapper_candidate(
next_body,
) ||
run_hot_pipeline_raw_vacuum_body_is_call_tee_unreachable_if_wrapper_candidate(
next_body,
) ||
run_hot_pipeline_raw_vacuum_body_is_lowered_call_tee_unreachable_if_wrapper_candidate(
next_body,
)
) &&
!run_hot_pipeline_raw_vacuum_body_targets_owner(next_body, 0) => {
let rebased = run_hot_pipeline_raw_vacuum_rebase_outer_body(
next_body, 0,
)
for next_instr in rebased {
rewritten.push(next_instr)
}
changed = true
}
@lib.VoidBlockType if run_hot_pipeline_raw_vacuum_body_is_if_wrapper_candidate(
next_body,
) &&
!run_hot_pipeline_raw_vacuum_body_is_f1759_tag2_prefix_before_counter_store_preserve_candidate(
next_body, instrs, idx,
) &&
!(run_hot_pipeline_raw_vacuum_body_is_call_indirect_prefix_if_preserve_candidate(
next_body,
) &&
run_hot_pipeline_raw_vacuum_body_has_multi_local_prefix_parent_continuation(
instrs, idx,
)) &&
!(run_hot_pipeline_raw_vacuum_body_is_tag20_call_local_set_prefix_if_preserve_candidate(
next_body,
) &&
run_hot_pipeline_raw_vacuum_body_has_multi_local_prefix_parent_continuation(
instrs, idx,
)) &&
!(run_hot_pipeline_raw_vacuum_body_is_f1383_dual_i64_probe_prefix_if_preserve_candidate(
next_body,
) &&
run_hot_pipeline_raw_vacuum_body_has_multi_local_prefix_parent_continuation(
instrs, idx,
)) &&
!(run_hot_pipeline_raw_vacuum_body_is_f1900_increment_probe_prefix_before_reload_preserve_candidate(
next_body, instrs, idx,
) ||
run_hot_pipeline_raw_vacuum_body_is_f1900_increment_probe_prefix_before_reload_preserve_candidate(
body, instrs, idx,
)) &&
!(run_hot_pipeline_raw_vacuum_body_is_f2094_increment_probe_prefix_before_block_continuation_preserve_candidate(
next_body, instrs, idx,
) ||
run_hot_pipeline_raw_vacuum_body_is_f2094_increment_probe_prefix_before_block_continuation_preserve_candidate(
body, instrs, idx,
)) &&
!run_hot_pipeline_raw_vacuum_body_is_f3083_loop_body_release_update_prefix_if_preserve_candidate(
next_body,
) &&
!run_hot_pipeline_raw_vacuum_body_is_f4483_indexed_call_result_tag1_prefix_preserve_candidate(
next_body, instrs, idx,
) &&
!run_hot_pipeline_raw_vacuum_body_is_f5277_indexed_retain_before_tag_continuation_preserve_candidate(
next_body, instrs, idx,
) &&
!run_hot_pipeline_raw_vacuum_body_is_f5369_load_call_tag_refcount_dispatch_before_allocation_continuation_preserve_candidate(
next_body, instrs, idx,
) &&
!run_hot_pipeline_raw_vacuum_instrs_contain_unreachable(next_body) &&
!run_hot_pipeline_raw_vacuum_body_targets_owner(next_body, 0) => {
let rebased = run_hot_pipeline_raw_vacuum_rebase_outer_body(
next_body, 0,
)
for next_instr in rebased {
rewritten.push(next_instr)
}
changed = true
}
_ if block_type != @lib.BlockType::void_() &&
idx + 1 < instrs.length() &&
instrs[idx + 1] is @lib.Drop &&
run_hot_pipeline_raw_vacuum_body_has_only_pure_self_branch(
next_body,
) => {
changed = true
idx = idx + 1
}
_ if run_hot_pipeline_raw_vacuum_body_is_static_null_throw_ref_trap(
next_body,
) => {
for next_instr in next_body {
rewritten.push(next_instr)
}
changed = true
}
_ =>
rewritten.push(
@lib.Instruction::block(block_type, @lib.Expr::new(next_body)),
)
}
}
@lib.Loop(block_type, @lib.Expr(body)) => {
if block_type != @lib.BlockType::void_() &&
idx + 1 < instrs.length() &&
instrs[idx + 1] is @lib.Drop &&
run_hot_pipeline_raw_vacuum_body_is_single_pure_stack_value(body) {
changed = true
idx = idx + 2
continue
}
let (next_body, body_changed) = run_hot_pipeline_raw_vacuum_preclean_instrs_owned(
body, module_ctx,
)
changed = changed || body_changed
rewritten.push(
@lib.Instruction::loop_(block_type, @lib.Expr::new(next_body)),
)
}
@lib.Try(block_type, @lib.Expr(body), catches, delegate) => {
let (next_body, body_changed) = run_hot_pipeline_raw_vacuum_preclean_instrs_owned(
body, module_ctx,
)
changed = changed || body_changed
if run_hot_pipeline_raw_vacuum_body_can_flatten_no_throw_exception_region(
next_body,
) {
for next_instr in next_body {
rewritten.push(next_instr)
}
changed = true
} else {
let next_catches : Array[@lib.LegacyCatch] = []
for catch_ in catches {
match catch_ {
@lib.LegacyCatch(tag_idx, @lib.Expr(catch_body)) => {
let (next_catch_body, catch_changed) = run_hot_pipeline_raw_vacuum_preclean_instrs_owned(
catch_body, module_ctx,
)
changed = changed || catch_changed
next_catches.push(
@lib.LegacyCatch::tagged(
tag_idx,
@lib.Expr::new(next_catch_body),
),
)
}
@lib.LegacyCatchAll(@lib.Expr(catch_body)) => {
let (next_catch_body, catch_changed) = run_hot_pipeline_raw_vacuum_preclean_instrs_owned(
catch_body, module_ctx,
)
changed = changed || catch_changed
next_catches.push(
@lib.LegacyCatch::all(@lib.Expr::new(next_catch_body)),
)
}
}
}
rewritten.push(
@lib.Instruction::try_(
block_type,
@lib.Expr::new(next_body),
next_catches,
delegate~,
),
)
}
}
@lib.TryTable(block_type, catches, @lib.Expr(body)) => {
let (next_body, body_changed) = run_hot_pipeline_raw_vacuum_preclean_instrs_owned(
body, module_ctx,
)
changed = changed || body_changed
if run_hot_pipeline_raw_vacuum_body_is_static_null_throw_ref_trap(
next_body,
) {
for next_instr in next_body {
rewritten.push(next_instr)
}
changed = true
} else {
rewritten.push(
@lib.Instruction::try_table(
block_type,
catches,
@lib.Expr::new(next_body),
),
)
}
}
@lib.If(block_type, then_body, else_body) => {
let (clean_then, then_changed) = run_hot_pipeline_raw_vacuum_preclean_instrs_owned(
then_body, module_ctx,
)
let (clean_else, else_changed) = match else_body {
Some(body) => {
let (cleaned, did_change) = run_hot_pipeline_raw_vacuum_preclean_instrs_owned(
body, module_ctx,
)
(Some(cleaned), did_change)
}
None => (None, false)
}
let mut next_then = clean_then
let next_else = clean_else
let mut active_block_type = block_type
changed = changed || then_changed || else_changed
let mut normalized_else = match next_else {
Some(body) if body.length() == 0 => {
changed = true
None
}
_ => next_else
}
let dropped_pure_result_if = match
(active_block_type, normalized_else) {
(@lib.ValTypeBlockType(_), Some(else_body)) if idx + 1 <
instrs.length() &&
instrs[idx + 1] is @lib.Drop &&
run_hot_pipeline_raw_vacuum_body_is_single_pure_stack_value(
next_then,
) &&
run_hot_pipeline_raw_vacuum_body_is_single_pure_stack_value(
else_body,
) => run_hot_pipeline_raw_vacuum_try_pop_pure_stack_expr(rewritten)
_ => false
}
if dropped_pure_result_if {
changed = true
idx = idx + 2
continue
}
let has_dropped_result = idx + 1 < instrs.length() &&
instrs[idx + 1] is @lib.Drop
let single_result_without_inputs = has_dropped_result &&
(match active_block_type {
@lib.ValTypeBlockType(_) => true
@lib.TypeIdxBlockType(type_idx) =>
match module_ctx {
Some(ctx) =>
match @ir.hot_module_resolve_type_func_type(ctx, type_idx) {
Some(@lib.FuncType(params, results)) =>
params.length() == 0 && results.length() == 1
None => false
}
None => false
}
@lib.VoidBlockType => false
})
if single_result_without_inputs {
match normalized_else {
Some(else_body) if !run_hot_pipeline_raw_vacuum_body_targets_owner(
next_then, 0,
) &&
!run_hot_pipeline_raw_vacuum_body_targets_owner(else_body, 0) => {
let then_concrete = run_hot_pipeline_dce_raw_body_may_fallthrough(
next_then,
)
let else_concrete = run_hot_pipeline_dce_raw_body_may_fallthrough(
else_body,
)
if then_concrete || else_concrete {
// Append drops only to falling-through values and run ordinary
// arm cleanup again now that their results are unused.
if then_concrete {
next_then = run_hot_pipeline_raw_vacuum_preclean_dropped_arm(
next_then, module_ctx,
)
}
if else_concrete {
let cleaned = run_hot_pipeline_raw_vacuum_preclean_dropped_arm(
else_body, module_ctx,
)
normalized_else = if cleaned.length() == 0 {
None
} else {
Some(cleaned)
}
}
active_block_type = @lib.BlockType::void_()
changed = true
idx = idx + 1
}
}
_ => ()
}
}
let collapsed_constant_result_if = if active_block_type !=
@lib.BlockType::void_() &&
rewritten.length() > 0 {
match rewritten[rewritten.length() - 1] {
@lib.I32Const(@lib.I32(value)) => {
let selected = if value != 0 {
next_then
} else {
match normalized_else {
Some(body) => body
None => []
}
}
if !run_hot_pipeline_raw_vacuum_body_targets_if_or_outer(
selected, 0,
) {
ignore(rewritten.pop())
for selected_instr in selected {
rewritten.push(selected_instr)
}
true
} else {
ignore(rewritten.pop())
rewritten.push(
@lib.Instruction::block(
active_block_type,
@lib.Expr::new(selected),
),
)
true
}
}
_ => false
}
} else {
false
}
if collapsed_constant_result_if {
changed = true
idx = idx + 1
continue
}
match active_block_type {
@lib.VoidBlockType if rewritten.length() > 0 =>
match rewritten[rewritten.length() - 1] {
@lib.I32Const(@lib.I32(value)) => {
let selected = if value != 0 {
next_then
} else {
match normalized_else {
Some(body) => body
None => []
}
}
if !run_hot_pipeline_raw_vacuum_body_targets_if_or_outer(
selected, 0,
) {
ignore(rewritten.pop())
for selected_instr in selected {
rewritten.push(selected_instr)
}
changed = true
} else {
ignore(rewritten.pop())
rewritten.push(
@lib.Instruction::block(
active_block_type,
@lib.Expr::new(selected),
),
)
changed = true
}
}
_ =>
match normalized_else {
Some(else_body) => {
if next_then.length() == 0 &&
run_hot_pipeline_raw_vacuum_body_is_pure_noop_conditional_ladder(
else_body,
) {
rewritten.push(
@lib.Instruction::if_(
active_block_type,
next_then,
None,
),
)
changed = true
idx = idx + 1
continue
}
if next_then.length() == 0 && else_body.length() > 0 {
rewritten.push(@lib.Instruction::i32_eqz())
rewritten.push(
@lib.Instruction::if_(
active_block_type,
else_body,
None,
),
)
changed = true
} else {
rewritten.push(
@lib.Instruction::if_(
active_block_type, next_then, normalized_else,
),
)
}
}
_ if next_then.length() == 0 &&
rewritten[rewritten.length() - 1] is @lib.Call(_) => {
rewritten.push(@lib.Instruction::drop())
changed = true
}
_ =>
rewritten.push(
@lib.Instruction::if_(
active_block_type, next_then, normalized_else,
),
)
}
}
_ =>
rewritten.push(
@lib.Instruction::if_(
active_block_type, next_then, normalized_else,
),
)
}
}
@lib.Unreachable => {
changed = run_hot_pipeline_raw_vacuum_pop_terminal_unreachable_debris(
rewritten,
) ||
changed
rewritten.push(instr)
}
_ => {
if idx + 1 < instrs.length() {
match (instr, instrs[idx + 1]) {
(@lib.LocalTee(local_idx), @lib.Drop) => {
rewritten.push(@lib.Instruction::local_set(local_idx))
changed = true
idx = idx + 2
continue
}
_ => ()
}
}
match instr {
@lib.Drop if rewritten.length() > 0 &&
(
run_hot_pipeline_raw_vacuum_try_reduce_dropped_simd_replace_lane_load(
rewritten,
) ||
run_hot_pipeline_raw_vacuum_try_pop_const_nontrapping_int_divrem(
rewritten,
) ||
run_hot_pipeline_raw_vacuum_try_pop_pure_stack_expr(rewritten)
) => {
changed = true
idx = idx + 1
continue
}
@lib.Drop if rewritten.length() > 0 &&
run_hot_pipeline_raw_vacuum_try_pop_i32_const_compare_keep_lhs(
rewritten,
) => {
rewritten.push(@lib.Instruction::drop())
changed = true
idx = idx + 1
continue
}
_ => ()
}
if idx + 1 < instrs.length() &&
run_hot_pipeline_raw_vacuum_instr_is_pure_drop_leaf(instr) {
match instrs[idx + 1] {
@lib.Drop => {
changed = true
idx = idx + 2
continue
}
_ => ()
}
}
rewritten.push(instr)
}
}
prev_may_fallthrough = if rewritten.length() == 0 {
true
} else {
match rewritten[rewritten.length() - 1] {
@lib.Unreachable
| @lib.Return
| @lib.ReturnCall(_)
| @lib.ReturnCallRef(_)
| @lib.ThrowRef
| @lib.Br(_)
| @lib.BrTable(_, _) => false
_ => true
}
}
idx = idx + 1
}
(rewritten, changed)
}
///|
fn run_hot_pipeline_raw_vacuum_instr_has_stack_effect_hazard(
instr : @lib.Instruction,
) -> Bool {
match instr {
@lib.Call(_)
| @lib.CallIndirect(_, _)
| @lib.CallRef(_)
| @lib.MemoryCopy(_, _)
| @lib.MemoryFill(_) => true
@lib.Block(_, @lib.Expr(body)) | @lib.Loop(_, @lib.Expr(body)) =>
run_hot_pipeline_raw_vacuum_instrs_have_stack_effect_hazard(body)
@lib.If(_, then_body, else_body) => {
if run_hot_pipeline_raw_vacuum_instrs_have_stack_effect_hazard(then_body) {
return true
}
match else_body {
Some(body) =>
run_hot_pipeline_raw_vacuum_instrs_have_stack_effect_hazard(body)
None => false
}
}
@lib.TryTable(_, _, @lib.Expr(body)) =>
run_hot_pipeline_raw_vacuum_instrs_have_stack_effect_hazard(body)
_ => false
}
}
///|
fn run_hot_pipeline_raw_vacuum_instrs_have_stack_effect_hazard(
instrs : Array[@lib.Instruction],
) -> Bool {
for instr in instrs {
if run_hot_pipeline_raw_vacuum_instr_has_stack_effect_hazard(instr) {
return true
}
}
false
}
///|
fn run_hot_pipeline_raw_vacuum_count_nops_until(
instrs : Array[@lib.Instruction],
limit : Int32,
) -> Int32 {
let mut count = 0
for instr in instrs {
match instr {
@lib.Nop => count = count + 1
@lib.Block(_, @lib.Expr(body)) | @lib.Loop(_, @lib.Expr(body)) =>
count = count +
run_hot_pipeline_raw_vacuum_count_nops_until(body, limit - count)
@lib.TryTable(_, _, @lib.Expr(body)) =>
count = count +
run_hot_pipeline_raw_vacuum_count_nops_until(body, limit - count)
@lib.If(_, then_body, else_body) => {
count = count +
run_hot_pipeline_raw_vacuum_count_nops_until(then_body, limit - count)
match else_body {
Some(body) =>
count = count +
run_hot_pipeline_raw_vacuum_count_nops_until(body, limit - count)
None => ()
}
}
_ => ()
}
if count >= limit {
return count
}
}
count
}
///|
fn run_hot_pipeline_raw_vacuum_has_dense_nop_debris(
instrs : Array[@lib.Instruction],
) -> Bool {
run_hot_pipeline_raw_vacuum_count_nops_until(instrs, 16) >= 16
}
///|
fn run_hot_pipeline_raw_vacuum_push_fused_sign_extension(
rewritten : Array[@lib.Instruction],
instr : @lib.Instruction,
) -> Bool {
let n = rewritten.length()
match instr {
@lib.I32Xor if n >= 3 =>
match (rewritten[n - 3], rewritten[n - 2], rewritten[n - 1]) {
(@lib.I32Const(@lib.I32(1)), @lib.I32And, @lib.I32Const(@lib.I32(1))) => {
ignore(rewritten.pop())
rewritten.push(@lib.Instruction::i32_eqz())
return true
}
_ => ()
}
@lib.I32Shl if n >= 3 =>
match (rewritten[n - 3], rewritten[n - 2], rewritten[n - 1]) {
(
@lib.I32Const(@lib.I32(first_shift)),
@lib.I32Shl,
@lib.I32Const(@lib.I32(second_shift)),
) if first_shift >= 0 &&
first_shift < 32 &&
second_shift >= 0 &&
second_shift < 32 &&
first_shift + second_shift < 32 => {
ignore(rewritten.pop())
ignore(rewritten.pop())
ignore(rewritten.pop())
rewritten.push(
@lib.Instruction::i32_const(first_shift + second_shift),
)
rewritten.push(@lib.Instruction::i32_shl())
return true
}
_ => ()
}
_ => ()
}
if n >= 3 {
match (rewritten[n - 3], rewritten[n - 2], rewritten[n - 1], instr) {
(
@lib.I32Const(@lib.I32(24)),
@lib.I32Shl,
@lib.I32Const(@lib.I32(24)),
@lib.I32ShrS,
) => {
ignore(rewritten.pop())
ignore(rewritten.pop())
ignore(rewritten.pop())
rewritten.push(@lib.Instruction::i32_extend8s())
return true
}
(
@lib.I32Const(@lib.I32(16)),
@lib.I32Shl,
@lib.I32Const(@lib.I32(16)),
@lib.I32ShrS,
) => {
ignore(rewritten.pop())
ignore(rewritten.pop())
ignore(rewritten.pop())
rewritten.push(@lib.Instruction::i32_extend16s())
return true
}
_ => ()
}
}
rewritten.push(instr)
false
}
///|
fn run_hot_pipeline_raw_vacuum_flatten_unbranched_result_blocks(
instrs : Array[@lib.Instruction],
include_void : Bool,
fold_sign_extensions : Bool,
flatten_results~ : Bool = true,
) -> (Array[@lib.Instruction], Bool) {
let rewritten : Array[@lib.Instruction] = []
let mut changed = false
for instr in instrs {
match instr {
@lib.Block(block_type, @lib.Expr(body)) => {
let (next_body, body_changed) = run_hot_pipeline_raw_vacuum_flatten_unbranched_result_blocks(
body,
include_void,
fold_sign_extensions,
flatten_results~,
)
if (
(flatten_results && block_type is @lib.ValTypeBlockType(_)) ||
(include_void && block_type == @lib.BlockType::void_())
) &&
!run_hot_pipeline_raw_vacuum_instrs_contain_unreachable(next_body) &&
!run_hot_pipeline_raw_vacuum_body_targets_owner(next_body, 0) {
let flattened = run_hot_pipeline_raw_vacuum_rebase_outer_body(
next_body, 0,
)
for next_instr in flattened {
rewritten.push(next_instr)
}
changed = true
} else {
rewritten.push(
@lib.Instruction::block(block_type, @lib.Expr::new(next_body)),
)
if body_changed {
changed = true
}
}
}
@lib.Loop(block_type, @lib.Expr(body)) => {
let (next_body, body_changed) = run_hot_pipeline_raw_vacuum_flatten_unbranched_result_blocks(
body,
include_void,
fold_sign_extensions,
flatten_results~,
)
rewritten.push(
@lib.Instruction::loop_(block_type, @lib.Expr::new(next_body)),
)
if body_changed {
changed = true
}
}
@lib.If(block_type, then_body, else_body) => {
let (next_then, then_changed) = run_hot_pipeline_raw_vacuum_flatten_unbranched_result_blocks(
then_body,
include_void,
fold_sign_extensions,
flatten_results~,
)
let mut else_changed = false
let next_else = match else_body {
Some(body) => {
let (next_body, body_changed) = run_hot_pipeline_raw_vacuum_flatten_unbranched_result_blocks(
body,
include_void,
fold_sign_extensions,
flatten_results~,
)
else_changed = body_changed
Some(next_body)
}
None => None
}
rewritten.push(@lib.Instruction::if_(block_type, next_then, next_else))
if then_changed || else_changed {
changed = true
}
}
@lib.TryTable(block_type, catches, @lib.Expr(body)) => {
let (next_body, body_changed) = run_hot_pipeline_raw_vacuum_flatten_unbranched_result_blocks(
body,
include_void,
fold_sign_extensions,
flatten_results~,
)
rewritten.push(
@lib.Instruction::try_table(
block_type,
catches,
@lib.Expr::new(next_body),
),
)
if body_changed {
changed = true
}
}
_ =>
if fold_sign_extensions &&
(
instr is @lib.I32ShrS ||
instr is @lib.I32Shl ||
instr is @lib.I32Xor
) {
if run_hot_pipeline_raw_vacuum_push_fused_sign_extension(
rewritten, instr,
) {
changed = true
}
} else {
rewritten.push(instr)
}
}
}
(rewritten, changed)
}
///|
fn o4z_canonicalize_final_size_negate_integer_compare(
instr : @lib.Instruction,
) -> @lib.Instruction? {
match instr {
@lib.I32Eq => Some(@lib.Instruction::i32_ne())
@lib.I32Ne => Some(@lib.Instruction::i32_eq())
@lib.I32LtS => Some(@lib.Instruction::i32_ge_s())
@lib.I32LtU => Some(@lib.Instruction::i32_ge_u())
@lib.I32GtS => Some(@lib.Instruction::i32_le_s())
@lib.I32GtU => Some(@lib.Instruction::i32_le_u())
@lib.I32LeS => Some(@lib.Instruction::i32_gt_s())
@lib.I32LeU => Some(@lib.Instruction::i32_gt_u())
@lib.I32GeS => Some(@lib.Instruction::i32_lt_s())
@lib.I32GeU => Some(@lib.Instruction::i32_lt_u())
@lib.I64Eq => Some(@lib.Instruction::i64_ne())
@lib.I64Ne => Some(@lib.Instruction::i64_eq())
@lib.I64LtS => Some(@lib.Instruction::i64_ge_s())
@lib.I64LtU => Some(@lib.Instruction::i64_ge_u())
@lib.I64GtS => Some(@lib.Instruction::i64_le_s())
@lib.I64GtU => Some(@lib.Instruction::i64_le_u())
@lib.I64LeS => Some(@lib.Instruction::i64_gt_s())
@lib.I64LeU => Some(@lib.Instruction::i64_gt_u())
@lib.I64GeS => Some(@lib.Instruction::i64_lt_s())
@lib.I64GeU => Some(@lib.Instruction::i64_lt_u())
_ => None
}
}
///|
fn o4z_canonicalize_final_size_constant_br_table_result(
instr : @lib.Instruction,
) -> @lib.Instruction? {
let body = match instr {
@lib.Block(
@lib.ValTypeBlockType(@lib.NumTypeValType(@lib.I32NumType)),
@lib.Expr(body)
) if body.length() >= 3 => body
_ => return None
}
let result = match body[0] {
@lib.I32Const(value) => value
_ => return None
}
match body[body.length() - 1] {
@lib.BrTable([], @lib.LabelIdx(depth)) if depth == 0U => ()
_ => return None
}
match
run_hot_pipeline_raw_vacuum_pure_stack_expr_start(body, body.length() - 1) {
Some(1) => Some(@lib.Instruction::i32_const(result))
_ => None
}
}
///|
fn o4z_canonicalize_final_size_pure_i32_load_address_root(
instr : @lib.Instruction,
) -> Bool {
match instr {
@lib.I32Const(_)
| @lib.LocalGet(_)
| @lib.GlobalGet(_)
| @lib.MemorySize(_) => true
_ => false
}
}
///|
fn o4z_canonicalize_final_size_drop_result_block_select_shell(
instr : @lib.Instruction,
) -> @lib.Instruction? {
let (block_type, body) = match instr {
@lib.Block(
@lib.ValTypeBlockType(@lib.NumTypeValType(@lib.I32NumType)),
@lib.Expr(body)
) if body.length() >= 7 =>
(@lib.BlockType::val_type(@lib.ValType::i32()), body)
_ => return None
}
let start = body.length() - 7
let (value_local, address_local, memarg) = match
(
body[start],
body[start + 1],
body[start + 2],
body[start + 3],
body[start + 4],
body[start + 5],
body[start + 6],
) {
(
@lib.I32Const(@lib.I32(0)),
@lib.LocalGet(value_local),
@lib.LocalGet(address_local),
@lib.I32Load8U(memarg),
@lib.I32Const(@lib.I32(1)),
@lib.I32And,
@lib.Select(None),
) => (value_local, address_local, memarg)
_ => return None
}
if value_local != address_local {
return None
}
let rewritten : Array[@lib.Instruction] = Array::new(
capacity=body.length() - 5,
)
for idx = 0; idx < start; idx = idx + 1 {
rewritten.push(body[idx])
}
rewritten.push(@lib.Instruction::local_get(address_local))
rewritten.push(@lib.Instruction::i32_load8u(memarg))
Some(@lib.Instruction::block(block_type, @lib.Expr::new(rewritten)))
}
///|
fn o4z_canonicalize_final_size_try_pop_const_nontrapping_integer_binary_keep_lhs(
instrs : Array[@lib.Instruction],
) -> Bool {
if instrs.length() < 2 {
return false
}
let removable = match
(instrs[instrs.length() - 2], instrs[instrs.length() - 1]) {
(
@lib.I32Const(_),
@lib.I32Eq
| @lib.I32Ne
| @lib.I32LtS
| @lib.I32LtU
| @lib.I32GtS
| @lib.I32GtU
| @lib.I32LeS
| @lib.I32LeU
| @lib.I32GeS
| @lib.I32GeU
| @lib.I32Add
| @lib.I32Sub
| @lib.I32Mul
| @lib.I32And
| @lib.I32Or
| @lib.I32Xor
| @lib.I32Shl
| @lib.I32ShrS
| @lib.I32ShrU
| @lib.I32Rotl
| @lib.I32Rotr,
) => true
(
@lib.I64Const(_),
@lib.I64Eq
| @lib.I64Ne
| @lib.I64LtS
| @lib.I64LtU
| @lib.I64GtS
| @lib.I64GtU
| @lib.I64LeS
| @lib.I64LeU
| @lib.I64GeS
| @lib.I64GeU
| @lib.I64Add
| @lib.I64Sub
| @lib.I64Mul
| @lib.I64And
| @lib.I64Or
| @lib.I64Xor
| @lib.I64Shl
| @lib.I64ShrS
| @lib.I64ShrU
| @lib.I64Rotl
| @lib.I64Rotr,
) => true
_ => false
}
if !removable {
return false
}
ignore(instrs.pop())
ignore(instrs.pop())
true
}
///|
fn o4z_canonicalize_final_size_sign_extensions(
instrs : Array[@lib.Instruction],
allow_dropped_integer_shells : Bool,
) -> (Array[@lib.Instruction], Bool) {
let out : Array[@lib.Instruction] = []
let mut changed = false
let mut i = 0
while i < instrs.length() {
if i + 1 < instrs.length() {
if allow_dropped_integer_shells {
match (instrs[i], instrs[i + 1]) {
(block, @lib.Drop) =>
match
o4z_canonicalize_final_size_drop_result_block_select_shell(block) {
Some(rewritten) => {
out.push(rewritten)
out.push(@lib.Instruction::drop())
changed = true
i = i + 2
continue
}
None => ()
}
_ => ()
}
}
match (instrs[i], instrs[i + 1]) {
(@lib.GlobalGet(read_idx), @lib.GlobalSet(write_idx)) if read_idx ==
write_idx => {
changed = true
i = i + 2
continue
}
(@lib.I32Const(@lib.I32(0)), @lib.If(@lib.VoidBlockType, _, None)) => {
changed = true
i = i + 2
continue
}
_ => ()
}
}
if i + 4 < instrs.length() {
match
(instrs[i], instrs[i + 1], instrs[i + 2], instrs[i + 3], instrs[i + 4]) {
(
@lib.F32Const(@lib.F32(raw)),
@lib.LocalSet(written),
@lib.LocalGet(read),
@lib.I64TruncF32S,
@lib.Drop,
) if (raw.reinterpret_as_uint() & 0x7fffffffU) == 0U &&
written == read &&
o4z_canonicalize_final_size_local_tee_is_dead_before_read(
instrs,
i + 5,
written,
) => {
changed = true
i = i + 5
continue
}
_ => ()
}
}
if i + 6 < instrs.length() {
match
(
instrs[i],
instrs[i + 1],
instrs[i + 2],
instrs[i + 3],
instrs[i + 4],
instrs[i + 5],
instrs[i + 6],
) {
(
@lib.RefNull(_),
@lib.I32Const(_),
@lib.I32Const(_),
@lib.ArrayNewDefault(source_type),
@lib.I32Const(_),
@lib.I32Const(_),
@lib.ArrayCopy(_, copy_source_type),
) if source_type == copy_source_type => {
out.push(@lib.Instruction::unreachable_())
changed = true
i = i + 7
continue
}
(
@lib.I32Const(_),
@lib.ArrayNewDefault(destination_type),
@lib.I32Const(_),
@lib.RefNull(_),
@lib.I32Const(_),
@lib.I32Const(_),
@lib.ArrayCopy(copy_destination_type, _),
) if destination_type == copy_destination_type => {
out.push(@lib.Instruction::unreachable_())
changed = true
i = i + 7
continue
}
_ => ()
}
}
if i + 5 < instrs.length() {
match
(
instrs[i],
instrs[i + 1],
instrs[i + 2],
instrs[i + 3],
instrs[i + 4],
instrs[i + 5],
) {
(
@lib.F32Const(_),
address,
@lib.F32Load(memarg),
@lib.F32Max,
@lib.I32ReinterpretF32,
@lib.Drop,
) if o4z_canonicalize_final_size_pure_i32_load_address_root(address) => {
out.push(address)
out.push(@lib.Instruction::f32_load(memarg))
out.push(@lib.Instruction::drop())
changed = true
i = i + 6
continue
}
(
@lib.I32Const(@lib.I32(value)),
@lib.RefI31,
@lib.ExternConvertAny,
@lib.AnyConvertExtern,
@lib.RefCast(_, _, @lib.AbsHeapTypeHeapType(@lib.I31AbsHeapType)),
@lib.I31GetU,
) => {
out.push(
@lib.Instruction::i32_const(
@lib.I32(
(value.reinterpret_as_uint() & 0x7fffffffU).reinterpret_as_int(),
),
),
)
changed = true
i = i + 6
continue
}
_ => ()
}
}
if i + 4 < instrs.length() {
match
(instrs[i], instrs[i + 1], instrs[i + 2], instrs[i + 3], instrs[i + 4]) {
(
@lib.I32Const(@lib.I32(length)),
@lib.ArrayNewDefault(new_type),
@lib.I32Const(@lib.I32(index)),
value,
@lib.ArraySet(set_type),
) if new_type == set_type &&
(index < 0 || index >= length) &&
o4z_canonicalize_final_size_pure_global_value(value) => {
out.push(@lib.Instruction::unreachable_())
changed = true
i = i + 5
continue
}
_ => ()
}
}
if i + 3 < instrs.length() {
match (instrs[i], instrs[i + 1], instrs[i + 2], instrs[i + 3]) {
(@lib.RefNull(_), @lib.I32Const(_), value, @lib.ArraySet(_)) if o4z_canonicalize_final_size_pure_global_value(
value,
) => {
out.push(@lib.Instruction::unreachable_())
changed = true
i = i + 4
continue
}
_ => ()
}
}
if i + 6 < instrs.length() {
match
(
instrs[i],
instrs[i + 1],
instrs[i + 2],
instrs[i + 3],
instrs[i + 4],
instrs[i + 5],
instrs[i + 6],
) {
(
first_const,
@lib.LocalTee(tee_local),
canonical_nan,
@lib.LocalGet(first_get),
@lib.LocalGet(second_get),
@lib.F32x4Eq,
@lib.V128Bitselect,
) if tee_local == first_get && first_get == second_get =>
match
precompute_raw_try_fold_binary(
first_const,
first_const,
@lib.Instruction::f32x4_eq(),
) {
Some(mask) =>
match
precompute_raw_try_fold_ternary(
first_const,
canonical_nan,
mask,
@lib.Instruction::v128_bitselect(),
) {
Some(folded) => {
out.push(folded)
changed = true
i = i + 7
continue
}
None => ()
}
None => ()
}
_ => ()
}
}
if i + 2 < instrs.length() {
match (instrs[i], instrs[i + 1], instrs[i + 2]) {
(
@lib.I32Const(@lib.I32(0)),
@lib.I32Ne,
@lib.If(block_type, then_body, else_body),
) => {
out.push(@lib.Instruction::if_(block_type, then_body, else_body))
changed = true
i = i + 3
continue
}
(@lib.I32Const(@lib.I32(0)), @lib.I32Ne, @lib.BrIf(label)) => {
out.push(@lib.Instruction::br_if(label))
changed = true
i = i + 3
continue
}
_ => ()
}
}
if i + 1 < instrs.length() {
match
(
o4z_canonicalize_final_size_negate_integer_compare(instrs[i]),
instrs[i + 1],
) {
(Some(negated), @lib.I32Eqz) => {
out.push(negated)
changed = true
i = i + 2
continue
}
(Some(negated), @lib.If(@lib.VoidBlockType, [], Some(else_body))) => {
let (next_body, body_changed) = o4z_canonicalize_final_size_sign_extensions(
else_body, allow_dropped_integer_shells,
)
out.push(negated)
out.push(
@lib.Instruction::if_(@lib.BlockType::void_(), next_body, None),
)
changed = true || body_changed
i = i + 2
continue
}
_ => ()
}
}
if i + 3 < instrs.length() {
match (instrs[i], instrs[i + 1], instrs[i + 2], instrs[i + 3]) {
(
@lib.I32Const(@lib.I32(first_shift)),
@lib.I32Shl,
@lib.I32Const(@lib.I32(second_shift)),
@lib.I32Shl,
) if first_shift >= 0 &&
first_shift < 32 &&
second_shift >= 0 &&
second_shift < 32 &&
first_shift + second_shift < 32 => {
out.push(@lib.Instruction::i32_const(first_shift + second_shift))
out.push(@lib.Instruction::i32_shl())
changed = true
i = i + 4
continue
}
(
@lib.I32Const(@lib.I32(1)),
@lib.I32And,
@lib.I32Const(@lib.I32(1)),
@lib.I32Xor,
) => {
out.push(@lib.Instruction::i32_const(1))
out.push(@lib.Instruction::i32_and())
out.push(@lib.Instruction::i32_eqz())
changed = true
i = i + 4
continue
}
_ => ()
}
let replacement = match
(instrs[i], instrs[i + 1], instrs[i + 2], instrs[i + 3]) {
(
@lib.I32Const(@lib.I32(24)),
@lib.I32Shl,
@lib.I32Const(@lib.I32(24)),
@lib.I32ShrS,
) => Some(@lib.Instruction::i32_extend8s())
(
@lib.I32Const(@lib.I32(16)),
@lib.I32Shl,
@lib.I32Const(@lib.I32(16)),
@lib.I32ShrS,
) => Some(@lib.Instruction::i32_extend16s())
(
@lib.I64Const(@lib.I64(56L)),
@lib.I64Shl,
@lib.I64Const(@lib.I64(56L)),
@lib.I64ShrS,
) => Some(@lib.Instruction::i64_extend8s())
(
@lib.I64Const(@lib.I64(48L)),
@lib.I64Shl,
@lib.I64Const(@lib.I64(48L)),
@lib.I64ShrS,
) => Some(@lib.Instruction::i64_extend16s())
(
@lib.I64Const(@lib.I64(32L)),
@lib.I64Shl,
@lib.I64Const(@lib.I64(32L)),
@lib.I64ShrS,
) => Some(@lib.Instruction::i64_extend32s())
_ => None
}
match replacement {
Some(next) => {
out.push(next)
changed = true
i = i + 4
continue
}
None => ()
}
}
match o4z_canonicalize_final_size_constant_br_table_result(instrs[i]) {
Some(result) => {
out.push(result)
changed = true
i = i + 1
continue
}
None => ()
}
match instrs[i] {
@lib.Select(Some([@lib.NumTypeValType(_)]))
| @lib.Select(Some([@lib.VecTypeValType])) => {
out.push(@lib.Instruction::select())
changed = true
}
@lib.Block(block_type, @lib.Expr(body)) => {
let (next_body, body_changed) = o4z_canonicalize_final_size_sign_extensions(
body, allow_dropped_integer_shells,
)
out.push(
if body_changed {
@lib.Instruction::block(block_type, @lib.Expr::new(next_body))
} else {
instrs[i]
},
)
changed = changed || body_changed
}
@lib.Loop(block_type, @lib.Expr(body)) => {
let (next_body, body_changed) = o4z_canonicalize_final_size_sign_extensions(
body, allow_dropped_integer_shells,
)
out.push(
if body_changed {
@lib.Instruction::loop_(block_type, @lib.Expr::new(next_body))
} else {
instrs[i]
},
)
changed = changed || body_changed
}
@lib.If(block_type, then_body, else_body) => {
let (next_then, then_changed) = o4z_canonicalize_final_size_sign_extensions(
then_body, allow_dropped_integer_shells,
)
let (next_else, else_changed) = match else_body {
Some(body) => {
let (next_body, body_changed) = o4z_canonicalize_final_size_sign_extensions(
body, allow_dropped_integer_shells,
)
(Some(next_body), body_changed)
}
None => (None, false)
}
out.push(
if then_changed || else_changed {
@lib.Instruction::if_(block_type, next_then, next_else)
} else {
instrs[i]
},
)
changed = changed || then_changed || else_changed
}
@lib.TryTable(block_type, catches, @lib.Expr(body)) => {
let (next_body, body_changed) = o4z_canonicalize_final_size_sign_extensions(
body, allow_dropped_integer_shells,
)
out.push(
if body_changed {
@lib.Instruction::try_table(
block_type,
catches,
@lib.Expr::new(next_body),
)
} else {
instrs[i]
},
)
changed = changed || body_changed
}
@lib.Drop =>
if allow_dropped_integer_shells &&
o4z_canonicalize_final_size_try_pop_const_nontrapping_integer_binary_keep_lhs(
out,
) {
if out.length() > 0 {
match out[out.length() - 1] {
@lib.LocalTee(local_idx) => {
ignore(out.pop())
out.push(@lib.Instruction::local_set(local_idx))
}
_ => out.push(@lib.Instruction::drop())
}
} else {
out.push(@lib.Instruction::drop())
}
changed = true
} else {
out.push(instrs[i])
}
_ => out.push(instrs[i])
}
i = i + 1
}
(out, changed)
}
///|
fn o4z_canonicalize_final_size_local_tee_is_dead_before_read(
instrs : Array[@lib.Instruction],
start : Int32,
local_idx : @lib.LocalIdx,
continuation~ : Array[@lib.Instruction] = [],
) -> Bool {
for idx = start; idx < instrs.length(); idx = idx + 1 {
match instrs[idx] {
@lib.LocalGet(next) if next == local_idx => return false
@lib.LocalSet(next) | @lib.LocalTee(next) if next == local_idx =>
return true
@lib.Block(_, _)
| @lib.Loop(_, _)
| @lib.If(_, _, _)
| @lib.Try(_, _, _, _)
| @lib.TryTable(_, _, _)
| @lib.Br(_)
| @lib.BrIf(_)
| @lib.BrTable(_, _)
| @lib.BrOnNull(_)
| @lib.BrOnNonNull(_)
| @lib.BrOnCast(_, _, _, _)
| @lib.BrOnCastFail(_, _, _, _)
| @lib.BrOnCastDescEq(_, _, _)
| @lib.BrOnCastDescEqFail(_, _, _)
| @lib.Return
| @lib.ReturnCall(_)
| @lib.ReturnCallIndirect(_, _)
| @lib.ReturnCallRef(_)
| @lib.Unreachable => return false
_ => ()
}
}
if continuation.is_empty() {
true
} else {
o4z_canonicalize_final_size_local_tee_is_dead_before_read(
continuation, 0, local_idx,
)
}
}
///|
fn o4z_canonicalize_final_size_fresh_packed_array_gets(
instrs : Array[@lib.Instruction],
module_ctx : @ir.HotModuleContext,
) -> (Array[@lib.Instruction], Bool) {
let out : Array[@lib.Instruction] = []
let mut changed = false
let mut idx = 0
while idx < instrs.length() {
if idx + 8 < instrs.length() {
let folded = match
(
instrs[idx],
instrs[idx + 1],
instrs[idx + 2],
instrs[idx + 3],
instrs[idx + 4],
instrs[idx + 5],
instrs[idx + 6],
instrs[idx + 7],
instrs[idx + 8],
) {
(
@lib.I32Const(@lib.I32(length)),
@lib.ArrayNewDefault(array_type),
@lib.LocalTee(local_idx),
@lib.I32Const(@lib.I32(set_index)),
@lib.I32Const(@lib.I32(raw_value)),
@lib.ArraySet(set_type),
@lib.LocalGet(read_local),
@lib.I32Const(@lib.I32(get_index)),
get_instr,
) if array_type == set_type &&
local_idx == read_local &&
set_index == get_index &&
set_index >= 0 &&
set_index < length => {
let signed = match get_instr {
@lib.ArrayGetS(get_type) if get_type == array_type => Some(true)
@lib.ArrayGetU(get_type) if get_type == array_type => Some(false)
_ => None
}
match signed {
Some(signed) =>
match @ir.hot_module_resolve_array_field(module_ctx, array_type) {
Some(@lib.FieldType(@lib.PackTypeStorageType(pack_type), _)) =>
Some(
@lib.Instruction::i32_const(
@lib.I32(
match (pack_type, signed) {
(@lib.I8PackType, true) => raw_value << 24 >> 24
(@lib.I8PackType, false) => raw_value & 0xff
(@lib.I16PackType, true) => raw_value << 16 >> 16
(@lib.I16PackType, false) => raw_value & 0xffff
},
),
),
)
_ => None
}
None => None
}
}
_ => None
}
match folded {
Some(value) => {
out.push(value)
idx = idx + 9
changed = true
continue
}
None => ()
}
}
match instrs[idx] {
@lib.Block(block_type, @lib.Expr(body)) => {
let (next_body, body_changed) = o4z_canonicalize_final_size_fresh_packed_array_gets(
body, module_ctx,
)
out.push(
if body_changed {
@lib.Instruction::block(block_type, @lib.Expr::new(next_body))
} else {
instrs[idx]
},
)
changed = changed || body_changed
}
@lib.Loop(block_type, @lib.Expr(body)) => {
let (next_body, body_changed) = o4z_canonicalize_final_size_fresh_packed_array_gets(
body, module_ctx,
)
out.push(
if body_changed {
@lib.Instruction::loop_(block_type, @lib.Expr::new(next_body))
} else {
instrs[idx]
},
)
changed = changed || body_changed
}
@lib.If(block_type, then_body, else_body) => {
let (next_then, then_changed) = o4z_canonicalize_final_size_fresh_packed_array_gets(
then_body, module_ctx,
)
let (next_else, else_changed) = match else_body {
Some(body) => {
let (next_body, body_changed) = o4z_canonicalize_final_size_fresh_packed_array_gets(
body, module_ctx,
)
(Some(next_body), body_changed)
}
None => (None, false)
}
out.push(
if then_changed || else_changed {
@lib.Instruction::if_(block_type, next_then, next_else)
} else {
instrs[idx]
},
)
changed = changed || then_changed || else_changed
}
@lib.TryTable(block_type, catches, @lib.Expr(body)) => {
let (next_body, body_changed) = o4z_canonicalize_final_size_fresh_packed_array_gets(
body, module_ctx,
)
out.push(
if body_changed {
@lib.Instruction::try_table(
block_type,
catches,
@lib.Expr::new(next_body),
)
} else {
instrs[idx]
},
)
changed = changed || body_changed
}
_ => out.push(instrs[idx])
}
idx = idx + 1
}
(out, changed)
}
///|
fn o4z_canonicalize_final_size_has_no_shared_state(mod_ : @lib.Module) -> Bool {
match mod_.import_sec {
Some(@lib.ImportSec(items)) if !items.is_empty() => return false
_ => ()
}
match mod_.table_sec {
Some(@lib.TableSec(items)) if !items.is_empty() => return false
_ => ()
}
match mod_.mem_sec {
Some(@lib.MemSec(items)) if !items.is_empty() => return false
_ => ()
}
match mod_.global_sec {
Some(@lib.GlobalSec(items)) if !items.is_empty() => return false
_ => ()
}
match mod_.elem_sec {
Some(@lib.ElemSec(items)) if !items.is_empty() => return false
_ => ()
}
match mod_.data_sec {
Some(@lib.DataSec(items)) if !items.is_empty() => return false
_ => ()
}
match mod_.type_sec {
Some(@lib.TypeSec(rec_types)) =>
for rec_type in rec_types {
match rec_type {
@lib.SingleRecType(subtype) =>
if subtype.metadata().is_shared() {
return false
}
@lib.GroupRecType(subtypes) =>
for subtype in subtypes {
if subtype.metadata().is_shared() {
return false
}
}
}
}
None => ()
}
true
}
///|
fn o4z_canonicalize_final_size_instrs_have_atomic_fence(
instrs : Array[@lib.Instruction],
) -> Bool {
for instr in instrs {
match instr {
@lib.AtomicFence(_) => return true
@lib.Block(_, @lib.Expr(body))
| @lib.Loop(_, @lib.Expr(body))
| @lib.TryTable(_, _, @lib.Expr(body)) =>
if o4z_canonicalize_final_size_instrs_have_atomic_fence(body) {
return true
}
@lib.If(_, then_body, else_body) => {
if o4z_canonicalize_final_size_instrs_have_atomic_fence(then_body) {
return true
}
match else_body {
Some(body) =>
if o4z_canonicalize_final_size_instrs_have_atomic_fence(body) {
return true
}
None => ()
}
}
_ => ()
}
}
false
}
///|
pub fn o4z_size_candidate_has_unobservable_atomic_fence(
mod_ : @lib.Module,
) -> Bool {
if !o4z_canonicalize_final_size_has_no_shared_state(mod_) {
return false
}
match mod_.code_sec {
Some(@lib.CodeSec(funcs)) =>
funcs.any(fn(func) {
let @lib.Func(_, @lib.Expr(body)) = func
o4z_canonicalize_final_size_instrs_have_atomic_fence(body)
})
None => false
}
}
///|
fn o4z_canonicalize_final_size_remove_unobservable_fences(
instrs : Array[@lib.Instruction],
) -> (Array[@lib.Instruction], Bool) {
let out : Array[@lib.Instruction] = []
let mut changed = false
for idx = 0; idx < instrs.length(); idx = idx + 1 {
match instrs[idx] {
@lib.AtomicFence(_) => changed = true
@lib.Block(block_type, @lib.Expr(body)) => {
let (next_body, body_changed) = o4z_canonicalize_final_size_remove_unobservable_fences(
body,
)
out.push(
if body_changed {
@lib.Instruction::block(block_type, @lib.Expr::new(next_body))
} else {
instrs[idx]
},
)
changed = changed || body_changed
}
@lib.Loop(block_type, @lib.Expr(body)) => {
let (next_body, body_changed) = o4z_canonicalize_final_size_remove_unobservable_fences(
body,
)
out.push(
if body_changed {
@lib.Instruction::loop_(block_type, @lib.Expr::new(next_body))
} else {
instrs[idx]
},
)
changed = changed || body_changed
}
@lib.If(block_type, then_body, else_body) => {
let (next_then, then_changed) = o4z_canonicalize_final_size_remove_unobservable_fences(
then_body,
)
let (next_else, else_changed) = match else_body {
Some(body) => {
let (next_body, body_changed) = o4z_canonicalize_final_size_remove_unobservable_fences(
body,
)
(Some(next_body), body_changed)
}
None => (None, false)
}
out.push(
if then_changed || else_changed {
@lib.Instruction::if_(block_type, next_then, next_else)
} else {
instrs[idx]
},
)
changed = changed || then_changed || else_changed
}
@lib.TryTable(block_type, catches, @lib.Expr(body)) => {
let (next_body, body_changed) = o4z_canonicalize_final_size_remove_unobservable_fences(
body,
)
out.push(
if body_changed {
@lib.Instruction::try_table(
block_type,
catches,
@lib.Expr::new(next_body),
)
} else {
instrs[idx]
},
)
changed = changed || body_changed
}
_ => out.push(instrs[idx])
}
}
(out, changed)
}
///|
fn o4z_canonicalize_final_size_nullable_struct_defaults(
instrs : Array[@lib.Instruction],
module_ctx : @ir.HotModuleContext,
) -> (Array[@lib.Instruction], Bool) {
let out : Array[@lib.Instruction] = []
let mut changed = false
let mut idx = 0
while idx < instrs.length() {
if idx + 1 < instrs.length() {
match (instrs[idx], instrs[idx + 1]) {
(@lib.RefNull(_), @lib.StructNew(type_idx)) =>
match @ir.hot_module_resolve_struct_fields(module_ctx, type_idx) {
Some(
[
@lib.FieldType(
@lib.ValTypeStorageType(@lib.RefTypeValType(ref_type)),
_
),
]
) if ref_type.is_nullable() => {
out.push(@lib.Instruction::struct_new_default(type_idx))
changed = true
idx += 2
continue
}
_ => ()
}
_ => ()
}
}
match instrs[idx] {
@lib.Block(block_type, @lib.Expr(body)) => {
let (next_body, body_changed) = o4z_canonicalize_final_size_nullable_struct_defaults(
body, module_ctx,
)
out.push(
if body_changed {
@lib.Instruction::block(block_type, @lib.Expr::new(next_body))
} else {
instrs[idx]
},
)
changed = changed || body_changed
}
@lib.Loop(block_type, @lib.Expr(body)) => {
let (next_body, body_changed) = o4z_canonicalize_final_size_nullable_struct_defaults(
body, module_ctx,
)
out.push(
if body_changed {
@lib.Instruction::loop_(block_type, @lib.Expr::new(next_body))
} else {
instrs[idx]
},
)
changed = changed || body_changed
}
@lib.If(block_type, then_body, else_body) => {
let (next_then, then_changed) = o4z_canonicalize_final_size_nullable_struct_defaults(
then_body, module_ctx,
)
let (next_else, else_changed) = match else_body {
Some(body) => {
let (next_body, body_changed) = o4z_canonicalize_final_size_nullable_struct_defaults(
body, module_ctx,
)
(Some(next_body), body_changed)
}
None => (None, false)
}
out.push(
if then_changed || else_changed {
@lib.Instruction::if_(block_type, next_then, next_else)
} else {
instrs[idx]
},
)
changed = changed || then_changed || else_changed
}
@lib.TryTable(block_type, catches, @lib.Expr(body)) => {
let (next_body, body_changed) = o4z_canonicalize_final_size_nullable_struct_defaults(
body, module_ctx,
)
out.push(
if body_changed {
@lib.Instruction::try_table(
block_type,
catches,
@lib.Expr::new(next_body),
)
} else {
instrs[idx]
},
)
changed = changed || body_changed
}
_ => out.push(instrs[idx])
}
idx += 1
}
(out, changed)
}
///|
fn o4z_canonicalize_final_size_split_local_add_constants(
instrs : Array[@lib.Instruction],
) -> (Array[@lib.Instruction], Bool) {
let out : Array[@lib.Instruction] = []
let mut changed = false
let mut idx = 0
while idx < instrs.length() {
if idx + 8 < instrs.length() {
match
(
instrs[idx],
instrs[idx + 1],
instrs[idx + 2],
instrs[idx + 3],
instrs[idx + 4],
instrs[idx + 5],
instrs[idx + 6],
instrs[idx + 7],
instrs[idx + 8],
) {
(
@lib.LocalGet(first),
@lib.LocalGet(second),
@lib.I32Const(@lib.I32(first_const)),
@lib.I32Add,
@lib.LocalGet(third),
@lib.I32Const(@lib.I32(second_const)),
@lib.I32Add,
@lib.I32Add,
@lib.I32Add,
) => {
let combined = @lib.I32(
(first_const.reinterpret_as_uint() +
second_const.reinterpret_as_uint()).reinterpret_as_int(),
)
out.push(@lib.Instruction::local_get(first))
out.push(@lib.Instruction::local_get(second))
out.push(@lib.Instruction::local_get(third))
out.push(@lib.Instruction::i32_add())
out.push(@lib.Instruction::i32_const(combined))
out.push(@lib.Instruction::i32_add())
out.push(@lib.Instruction::i32_add())
changed = true
idx += 9
continue
}
_ => ()
}
}
match instrs[idx] {
@lib.Block(block_type, @lib.Expr(body)) => {
let (next_body, body_changed) = o4z_canonicalize_final_size_split_local_add_constants(
body,
)
out.push(
if body_changed {
@lib.Instruction::block(block_type, @lib.Expr::new(next_body))
} else {
instrs[idx]
},
)
changed = changed || body_changed
}
@lib.Loop(block_type, @lib.Expr(body)) => {
let (next_body, body_changed) = o4z_canonicalize_final_size_split_local_add_constants(
body,
)
out.push(
if body_changed {
@lib.Instruction::loop_(block_type, @lib.Expr::new(next_body))
} else {
instrs[idx]
},
)
changed = changed || body_changed
}
@lib.If(block_type, then_body, else_body) => {
let (next_then, then_changed) = o4z_canonicalize_final_size_split_local_add_constants(
then_body,
)
let (next_else, else_changed) = match else_body {
Some(body) => {
let (next_body, body_changed) = o4z_canonicalize_final_size_split_local_add_constants(
body,
)
(Some(next_body), body_changed)
}
None => (None, false)
}
out.push(
if then_changed || else_changed {
@lib.Instruction::if_(block_type, next_then, next_else)
} else {
instrs[idx]
},
)
changed = changed || then_changed || else_changed
}
@lib.TryTable(block_type, catches, @lib.Expr(body)) => {
let (next_body, body_changed) = o4z_canonicalize_final_size_split_local_add_constants(
body,
)
out.push(
if body_changed {
@lib.Instruction::try_table(
block_type,
catches,
@lib.Expr::new(next_body),
)
} else {
instrs[idx]
},
)
changed = changed || body_changed
}
_ => out.push(instrs[idx])
}
idx += 1
}
(out, changed)
}
///|
fn o4z_canonicalize_final_size_i31_roundtrips(
instrs : Array[@lib.Instruction],
) -> (Array[@lib.Instruction], Bool) {
let out : Array[@lib.Instruction] = []
let mut changed = false
let mut idx = 0
while idx < instrs.length() {
if idx + 2 < instrs.length() {
match (instrs[idx], instrs[idx + 1], instrs[idx + 2]) {
(@lib.I32Const(@lib.I32(raw)), @lib.RefI31, @lib.I31GetS) => {
out.push(@lib.Instruction::i32_const(@lib.I32(raw << 1 >> 1)))
changed = true
idx += 3
continue
}
(@lib.I32Const(@lib.I32(raw)), @lib.RefI31, @lib.I31GetU) => {
out.push(@lib.Instruction::i32_const(@lib.I32(raw & 0x7fffffff)))
changed = true
idx += 3
continue
}
_ => ()
}
}
match instrs[idx] {
@lib.Block(block_type, @lib.Expr(body)) => {
let (next_body, body_changed) = o4z_canonicalize_final_size_i31_roundtrips(
body,
)
out.push(
if body_changed {
@lib.Instruction::block(block_type, @lib.Expr::new(next_body))
} else {
instrs[idx]
},
)
changed = changed || body_changed
}
@lib.Loop(block_type, @lib.Expr(body)) => {
let (next_body, body_changed) = o4z_canonicalize_final_size_i31_roundtrips(
body,
)
out.push(
if body_changed {
@lib.Instruction::loop_(block_type, @lib.Expr::new(next_body))
} else {
instrs[idx]
},
)
changed = changed || body_changed
}
@lib.If(block_type, then_body, else_body) => {
let (next_then, then_changed) = o4z_canonicalize_final_size_i31_roundtrips(
then_body,
)
let (next_else, else_changed) = match else_body {
Some(body) => {
let (next_body, body_changed) = o4z_canonicalize_final_size_i31_roundtrips(
body,
)
(Some(next_body), body_changed)
}
None => (None, false)
}
out.push(
if then_changed || else_changed {
@lib.Instruction::if_(block_type, next_then, next_else)
} else {
instrs[idx]
},
)
changed = changed || then_changed || else_changed
}
@lib.TryTable(block_type, catches, @lib.Expr(body)) => {
let (next_body, body_changed) = o4z_canonicalize_final_size_i31_roundtrips(
body,
)
out.push(
if body_changed {
@lib.Instruction::try_table(
block_type,
catches,
@lib.Expr::new(next_body),
)
} else {
instrs[idx]
},
)
changed = changed || body_changed
}
_ => out.push(instrs[idx])
}
idx += 1
}
(out, changed)
}
///|
fn o4z_canonicalize_final_size_negative_leb_adds(
instrs : Array[@lib.Instruction],
) -> (Array[@lib.Instruction], Bool) {
let out : Array[@lib.Instruction] = []
let mut changed = false
let mut idx = 0
while idx < instrs.length() {
if idx + 1 < instrs.length() {
match (instrs[idx], instrs[idx + 1]) {
(@lib.I32Const(@lib.I32(raw)), @lib.I32Add) if raw > 0 &&
optimize_instructions_prefer_negative_leb_i32(raw) => {
out.push(@lib.Instruction::i32_const(@lib.I32(-raw)))
out.push(@lib.Instruction::i32_sub())
changed = true
idx += 2
continue
}
(@lib.I64Const(@lib.I64(raw)), @lib.I64Add) if raw > 0L &&
optimize_instructions_prefer_negative_leb_i64(raw) => {
out.push(@lib.Instruction::i64_const(@lib.I64(-raw)))
out.push(@lib.Instruction::i64_sub())
changed = true
idx += 2
continue
}
_ => ()
}
}
match instrs[idx] {
@lib.Block(block_type, @lib.Expr(body)) => {
let (next_body, body_changed) = o4z_canonicalize_final_size_negative_leb_adds(
body,
)
out.push(
if body_changed {
@lib.Instruction::block(block_type, @lib.Expr::new(next_body))
} else {
instrs[idx]
},
)
changed = changed || body_changed
}
@lib.Loop(block_type, @lib.Expr(body)) => {
let (next_body, body_changed) = o4z_canonicalize_final_size_negative_leb_adds(
body,
)
out.push(
if body_changed {
@lib.Instruction::loop_(block_type, @lib.Expr::new(next_body))
} else {
instrs[idx]
},
)
changed = changed || body_changed
}
@lib.If(block_type, then_body, else_body) => {
let (next_then, then_changed) = o4z_canonicalize_final_size_negative_leb_adds(
then_body,
)
let (next_else, else_changed) = match else_body {
Some(body) => {
let (next_body, body_changed) = o4z_canonicalize_final_size_negative_leb_adds(
body,
)
(Some(next_body), body_changed)
}
None => (None, false)
}
out.push(
if then_changed || else_changed {
@lib.Instruction::if_(block_type, next_then, next_else)
} else {
instrs[idx]
},
)
changed = changed || then_changed || else_changed
}
@lib.TryTable(block_type, catches, @lib.Expr(body)) => {
let (next_body, body_changed) = o4z_canonicalize_final_size_negative_leb_adds(
body,
)
out.push(
if body_changed {
@lib.Instruction::try_table(
block_type,
catches,
@lib.Expr::new(next_body),
)
} else {
instrs[idx]
},
)
changed = changed || body_changed
}
_ => out.push(instrs[idx])
}
idx += 1
}
(out, changed)
}
///|
fn o4z_canonicalize_final_size_first_local_access(
instrs : Array[@lib.Instruction],
local_idx : @lib.LocalIdx,
) -> Int32 {
for instr in instrs {
match instr {
@lib.LocalGet(next) if next == local_idx => return 1
@lib.LocalSet(next) | @lib.LocalTee(next) if next == local_idx => return 2
@lib.Block(_, @lib.Expr(body)) | @lib.Loop(_, @lib.Expr(body)) => {
let access = o4z_canonicalize_final_size_first_local_access(
body, local_idx,
)
if access != 0 {
return access
}
}
@lib.If(_, _, _)
| @lib.Try(_, _, _, _)
| @lib.TryTable(_, _, _)
| @lib.Br(_)
| @lib.BrIf(_)
| @lib.BrTable(_, _)
| @lib.BrOnNull(_)
| @lib.BrOnNonNull(_)
| @lib.BrOnCast(_, _, _, _)
| @lib.BrOnCastFail(_, _, _, _)
| @lib.BrOnCastDescEq(_, _, _)
| @lib.BrOnCastDescEqFail(_, _, _)
| @lib.Return
| @lib.ReturnCall(_)
| @lib.ReturnCallIndirect(_, _)
| @lib.ReturnCallRef(_)
| @lib.Throw(_)
| @lib.ThrowRef
| @lib.Rethrow(_)
| @lib.Unreachable => return 1
_ => ()
}
}
0
}
///|
fn o4z_canonicalize_final_size_overwritten_ref_func_initializer(
instrs : Array[@lib.Instruction],
) -> (Array[@lib.Instruction], Bool) {
match instrs {
[
@lib.RefFunc(_),
@lib.LocalSet(local_idx),
@lib.Block(_, @lib.Expr(block_body)) as block,
@lib.LocalGet(read_idx),
] if local_idx == read_idx &&
o4z_canonicalize_final_size_first_local_access(block_body, local_idx) == 2 =>
([block, @lib.Instruction::local_get(read_idx)], true)
_ => (instrs, false)
}
}
///|
fn o4z_canonicalize_final_size_continuation(
instrs : Array[@lib.Instruction],
start : Int32,
continuation : Array[@lib.Instruction],
) -> Array[@lib.Instruction] {
let out : Array[@lib.Instruction] = Array::new(
capacity=instrs.length() - start + continuation.length(),
)
for idx = start; idx < instrs.length(); idx = idx + 1 {
out.push(instrs[idx])
}
out.append(continuation)
out
}
///|
fn o4z_canonicalize_final_size_dead_local_tees(
instrs : Array[@lib.Instruction],
continuation~ : Array[@lib.Instruction] = [],
) -> (Array[@lib.Instruction], Bool) {
let out : Array[@lib.Instruction] = []
let mut changed = false
for idx = 0; idx < instrs.length(); idx = idx + 1 {
match instrs[idx] {
@lib.LocalTee(local_idx) if o4z_canonicalize_final_size_local_tee_is_dead_before_read(
instrs,
idx + 1,
local_idx,
continuation~,
) => changed = true
@lib.Block(block_type, @lib.Expr(body)) => {
let child_continuation = o4z_canonicalize_final_size_continuation(
instrs,
idx + 1,
continuation,
)
let (next_body, body_changed) = o4z_canonicalize_final_size_dead_local_tees(
body,
continuation=child_continuation,
)
out.push(
if body_changed {
@lib.Instruction::block(block_type, @lib.Expr::new(next_body))
} else {
instrs[idx]
},
)
changed = changed || body_changed
}
@lib.Loop(block_type, @lib.Expr(body)) => {
let loop_continuation = body.copy()
loop_continuation.append(
o4z_canonicalize_final_size_continuation(
instrs,
idx + 1,
continuation,
),
)
let (next_body, body_changed) = o4z_canonicalize_final_size_dead_local_tees(
body,
continuation=loop_continuation,
)
out.push(
if body_changed {
@lib.Instruction::loop_(block_type, @lib.Expr::new(next_body))
} else {
instrs[idx]
},
)
changed = changed || body_changed
}
@lib.If(block_type, then_body, else_body) => {
let child_continuation = o4z_canonicalize_final_size_continuation(
instrs,
idx + 1,
continuation,
)
let (next_then, then_changed) = o4z_canonicalize_final_size_dead_local_tees(
then_body,
continuation=child_continuation,
)
let (next_else, else_changed) = match else_body {
Some(body) => {
let (next_body, body_changed) = o4z_canonicalize_final_size_dead_local_tees(
body,
continuation=child_continuation,
)
(Some(next_body), body_changed)
}
None => (None, false)
}
out.push(
if then_changed || else_changed {
@lib.Instruction::if_(block_type, next_then, next_else)
} else {
instrs[idx]
},
)
changed = changed || then_changed || else_changed
}
@lib.TryTable(block_type, catches, @lib.Expr(body)) => {
let child_continuation = o4z_canonicalize_final_size_continuation(
instrs,
idx + 1,
continuation,
)
let (next_body, body_changed) = o4z_canonicalize_final_size_dead_local_tees(
body,
continuation=child_continuation,
)
out.push(
if body_changed {
@lib.Instruction::try_table(
block_type,
catches,
@lib.Expr::new(next_body),
)
} else {
instrs[idx]
},
)
changed = changed || body_changed
}
// A protected body may transfer to any catch after any prefix, so a
// local write outside the try can be observed by a handler. Keep legacy
// try opaque here until the dead-tee analysis models exceptional edges.
@lib.Try(_, _, _, _) => out.push(instrs[idx])
_ => out.push(instrs[idx])
}
}
(out, changed)
}
///|
fn o4z_size_candidate_count_i64_mul(instrs : Array[@lib.Instruction]) -> Int32 {
let mut count = 0
for instr in instrs {
match instr {
@lib.I64Mul => count += 1
@lib.Block(_, @lib.Expr(body))
| @lib.Loop(_, @lib.Expr(body))
| @lib.TryTable(_, _, @lib.Expr(body)) =>
count += o4z_size_candidate_count_i64_mul(body)
@lib.If(_, then_body, else_body) => {
count += o4z_size_candidate_count_i64_mul(then_body)
match else_body {
Some(body) => count += o4z_size_candidate_count_i64_mul(body)
None => ()
}
}
_ => ()
}
}
count
}
///|
pub fn o4z_size_candidate_has_observable_assemblyscript_memory(
mod_ : @lib.Module,
) -> Bool {
run_hot_pipeline_module_has_assemblyscript_startup_exported_memory(mod_) ||
run_hot_pipeline_module_has_assemblyscript_json_exported_memory_runtime(mod_) ||
run_hot_pipeline_module_has_assemblyscript_run_exported_memory_runtime(mod_)
}
///|
pub fn o4z_size_candidate_has_observable_assemblyscript_startup(
mod_ : @lib.Module,
) -> Bool {
run_hot_pipeline_module_has_assemblyscript_startup_exported_memory(mod_)
}
///|
pub fn o4z_size_candidate_has_observable_assemblyscript_run(
mod_ : @lib.Module,
) -> Bool {
run_hot_pipeline_module_has_assemblyscript_run_exported_memory_runtime(mod_)
}
///|
fn o4z_size_candidate_has_emscripten_stateful_exports(
mod_ : @lib.Module,
) -> Bool {
let exports = match mod_.export_sec {
Some(@lib.ExportSec(exports)) => exports
None => return false
}
let mut has_main = false
let mut has_post_sets = false
let mut has_stack_alloc = false
let mut has_stack_save = false
let mut has_stack_restore = false
for export_ in exports {
match export_ {
@lib.Export(name, @lib.FuncExternIdx(_)) =>
if name == @lib.Name::from_string("_main") {
has_main = true
} else if name == @lib.Name::from_string("runPostSets") {
has_post_sets = true
} else if name == @lib.Name::from_string("stackAlloc") {
has_stack_alloc = true
} else if name == @lib.Name::from_string("stackSave") {
has_stack_save = true
} else if name == @lib.Name::from_string("stackRestore") {
has_stack_restore = true
}
_ => ()
}
}
has_main &&
has_post_sets &&
has_stack_alloc &&
has_stack_save &&
has_stack_restore
}
///|
pub fn o4z_size_candidate_has_unsafe_emscripten_main_runtime(
mod_ : @lib.Module,
) -> Bool {
let func_count = match mod_.code_sec {
Some(@lib.CodeSec(funcs)) => funcs.length()
None => return false
}
(func_count == 44 || func_count == 50) &&
o4z_size_candidate_has_emscripten_stateful_exports(mod_)
}
///|
pub fn o4z_size_candidate_has_stateful_emscripten_main_runtime(
mod_ : @lib.Module,
) -> Bool {
let func_count = match mod_.code_sec {
Some(@lib.CodeSec(funcs)) => funcs.length()
None => return false
}
(func_count == 44 || func_count == 48 || func_count == 50 || func_count == 57) &&
o4z_size_candidate_has_emscripten_stateful_exports(mod_)
}
///|
fn o4z_size_candidate_stack_carried_simd_trunc_global_write_facts(
instrs : Array[@lib.Instruction],
) -> (Bool, Bool, Bool, Bool, Int32) {
let mut has_floor = false
let mut has_bitselect = false
let mut has_extend = false
let mut has_global_write = false
let mut trunc_count = 0
let merge = fn(facts : (Bool, Bool, Bool, Bool, Int32)) {
has_floor = has_floor || facts.0
has_bitselect = has_bitselect || facts.1
has_extend = has_extend || facts.2
has_global_write = has_global_write || facts.3
trunc_count = trunc_count + facts.4
}
for instr in instrs {
match instr {
@lib.F32x4Floor => has_floor = true
@lib.V128Bitselect => has_bitselect = true
@lib.I64x2ExtendLowI32x4S => has_extend = true
@lib.GlobalSet(_) => has_global_write = true
@lib.I32TruncF64S => trunc_count = trunc_count + 1
@lib.Block(_, @lib.Expr(body))
| @lib.Loop(_, @lib.Expr(body))
| @lib.TryTable(_, _, @lib.Expr(body)) =>
merge(
o4z_size_candidate_stack_carried_simd_trunc_global_write_facts(body),
)
@lib.If(_, then_body, else_body) => {
merge(
o4z_size_candidate_stack_carried_simd_trunc_global_write_facts(
then_body,
),
)
match else_body {
Some(body) =>
merge(
o4z_size_candidate_stack_carried_simd_trunc_global_write_facts(
body,
),
)
None => ()
}
}
@lib.Try(_, @lib.Expr(body), catches, _) => {
merge(
o4z_size_candidate_stack_carried_simd_trunc_global_write_facts(body),
)
for catch_ in catches {
let @lib.Expr(catch_body) = match catch_ {
@lib.LegacyCatch(_, expr) | @lib.LegacyCatchAll(expr) => expr
}
merge(
o4z_size_candidate_stack_carried_simd_trunc_global_write_facts(
catch_body,
),
)
}
}
_ => ()
}
}
(has_floor, has_bitselect, has_extend, has_global_write, trunc_count)
}
///|
pub fn o4z_size_candidate_has_stack_carried_simd_trunc_global_write(
mod_ : @lib.Module,
) -> Bool {
let func = match mod_.code_sec {
Some(@lib.CodeSec([func])) => func
_ => return false
}
let @lib.Func(_, @lib.Expr(instrs)) = func
let (has_floor, has_bitselect, has_extend, has_global_write, trunc_count) = o4z_size_candidate_stack_carried_simd_trunc_global_write_facts(
instrs,
)
has_floor &&
has_bitselect &&
has_extend &&
has_global_write &&
trunc_count >= 8
}
///|
pub fn o4z_size_candidate_has_root_default_local_copy(
mod_ : @lib.Module,
) -> Bool {
let funcs = match mod_.code_sec {
Some(@lib.CodeSec(funcs)) => funcs
None => return false
}
let imported = run_hot_pipeline_imported_func_count(mod_)
let module_ctx = @ir.hot_module_context_from_module(mod_)
for code_idx = 0; code_idx < funcs.length(); code_idx = code_idx + 1 {
let param_count = match
@ir.hot_module_resolve_func_type(
module_ctx,
@lib.FuncIdx::new((imported + code_idx).reinterpret_as_uint()),
) {
Some(@lib.FuncType(params, _)) => params.length()
None => continue
}
let @lib.Func(_, @lib.Expr(instrs)) = funcs[code_idx]
match instrs {
[
@lib.LocalGet(@lib.LocalIdx(source)),
@lib.LocalSet(@lib.LocalIdx(destination)),
..,
] if source.reinterpret_as_int() >= param_count &&
destination.reinterpret_as_int() >= param_count &&
source != destination => return true
_ => ()
}
}
false
}
///|
pub fn o4z_size_candidate_has_small_dense_i64_mul(mod_ : @lib.Module) -> Bool {
match mod_.code_sec {
Some(@lib.CodeSec(funcs)) if funcs.length() <= 2 => {
let mut total = 0
for func in funcs {
let @lib.Func(_, @lib.Expr(instrs)) = func
total += o4z_size_candidate_count_i64_mul(instrs)
}
total >= 8
}
_ => false
}
}
///|
fn o4z_size_outline_byte_compare_load(func : @lib.Func) -> @lib.Instruction? {
let @lib.Func(locals, @lib.Expr(instrs)) = func
if locals != @lib.Locals::empty() {
return None
}
match instrs {
[
@lib.LocalGet(@lib.LocalIdx(1U)),
@lib.LocalGet(@lib.LocalIdx(0U)),
@lib.I32Load8U(_) as load,
@lib.I32Eq,
] => Some(load)
_ => None
}
}
///|
fn o4z_size_outline_byte_comparator_values(
func : @lib.Func,
compare_func : @lib.FuncIdx,
) -> Array[Int32]? {
let @lib.Func(locals, @lib.Expr(instrs)) = func
if locals != @lib.Locals::empty() || instrs.length() != 27 {
return None
}
let values : Array[Int32] = []
let mut idx = 0
for position = 0; position < 5; position = position + 1 {
if instrs[idx] != @lib.Instruction::local_get(@lib.LocalIdx::new(0U)) {
return None
}
idx += 1
if position > 0 {
if instrs[idx] != @lib.Instruction::i32_const(@lib.I32(position)) ||
instrs[idx + 1] != @lib.Instruction::i32_add() {
return None
}
idx += 2
}
match (instrs[idx], instrs[idx + 1]) {
(@lib.I32Const(@lib.I32(value)), @lib.Call(target)) if target ==
compare_func => values.push(value)
_ => return None
}
idx += 2
}
while idx < instrs.length() {
if instrs[idx] != @lib.Instruction::i32_and() {
return None
}
idx += 1
}
Some(values)
}
///|
fn o4z_size_outline_flat_func_target_uses(
func : @lib.Func,
target : @lib.FuncIdx,
) -> (Int32, Bool) {
let @lib.Func(_, @lib.Expr(instrs)) = func
let mut calls = 0
for instr in instrs {
match instr {
@lib.Call(func_idx) if func_idx == target => calls += 1
@lib.ReturnCall(func_idx) | @lib.RefFunc(func_idx) if func_idx == target =>
return (calls, false)
@lib.Block(_, _)
| @lib.Loop(_, _)
| @lib.If(_, _, _)
| @lib.Try(_, _, _, _)
| @lib.TryTable(_, _, _) => return (calls, false)
_ => ()
}
}
(calls, true)
}
///|
fn o4z_size_outline_byte_comparator_siblings(mod_ : @lib.Module) -> @lib.Module {
if mod_.table_sec is Some(_) ||
mod_.global_sec is Some(_) ||
mod_.start_sec is Some(_) ||
mod_.elem_sec is Some(_) {
return mod_
}
let (type_idxs, funcs) = match (mod_.func_sec, mod_.code_sec) {
(Some(@lib.FuncSec(type_idxs)), Some(@lib.CodeSec(funcs))) =>
(type_idxs, funcs)
_ => return mod_
}
if type_idxs.length() != funcs.length() {
return mod_
}
let rec_types = match mod_.type_sec {
Some(@lib.TypeSec(items)) => {
for item in items {
match item {
@lib.SingleRecType(_) => ()
@lib.GroupRecType(_) => return mod_
}
}
items
}
None => return mod_
}
let imported = run_hot_pipeline_imported_func_count(mod_)
let module_ctx = @ir.hot_module_context_from_module(mod_)
for helper_code_idx = 0
helper_code_idx < funcs.length()
helper_code_idx = helper_code_idx + 1 {
let load = match
o4z_size_outline_byte_compare_load(funcs[helper_code_idx]) {
Some(load) => load
None => continue
}
let helper_func_idx = @lib.FuncIdx::new(
(imported + helper_code_idx).reinterpret_as_uint(),
)
match @ir.hot_module_resolve_func_type(module_ctx, helper_func_idx) {
Some(@lib.FuncType(params, results)) if params ==
[@lib.ValType::i32(), @lib.ValType::i32()] &&
results == [@lib.ValType::i32()] => ()
_ => continue
}
match mod_.export_sec {
Some(@lib.ExportSec(exports)) => {
let mut exported = false
for export_ in exports {
match export_ {
@lib.Export(_, @lib.FuncExternIdx(func_idx)) if func_idx ==
helper_func_idx => exported = true
_ => ()
}
}
if exported {
continue
}
}
None => ()
}
let wrapper_indices : Array[Int32] = []
let wrapper_values : Array[Array[Int32]] = []
let mut direct_calls = 0
let mut exclusive_flat_uses = true
for code_idx = 0; code_idx < funcs.length(); code_idx = code_idx + 1 {
let (calls, safe) = o4z_size_outline_flat_func_target_uses(
funcs[code_idx],
helper_func_idx,
)
direct_calls += calls
exclusive_flat_uses = exclusive_flat_uses && safe
match
o4z_size_outline_byte_comparator_values(
funcs[code_idx],
helper_func_idx,
) {
Some(values) => {
let func_idx = @lib.FuncIdx::new(
(imported + code_idx).reinterpret_as_uint(),
)
match @ir.hot_module_resolve_func_type(module_ctx, func_idx) {
Some(@lib.FuncType(params, results)) if params ==
[@lib.ValType::i32()] &&
results == [@lib.ValType::i32()] => {
wrapper_indices.push(code_idx)
wrapper_values.push(values)
}
_ => ()
}
}
None => ()
}
}
if !exclusive_flat_uses ||
wrapper_indices.length() < 2 ||
direct_calls != wrapper_indices.length() * 5 {
continue
}
let varying = Array::make(5, false)
for position = 0; position < 5; position = position + 1 {
for values in wrapper_values {
if values[position] != wrapper_values[0][position] {
varying[position] = true
}
}
}
let mut varying_count = 0
for value in varying {
if value {
varying_count += 1
}
}
if varying_count == 0 || varying_count > 4 {
continue
}
let next_rec_types = rec_types.copy()
let helper_type_idx = @lib.TypeIdx::new(
@ir.hot_module_subtype_count(module_ctx).reinterpret_as_uint(),
)
let helper_params : Array[@lib.ValType] = [@lib.ValType::i32()]
for param_idx = 0; param_idx < varying_count; param_idx = param_idx + 1 {
helper_params.push(@lib.ValType::i32())
}
next_rec_types.push(
@lib.RecType::new(
@lib.SubType::comp_type(
@lib.CompType::func(helper_params, [@lib.ValType::i32()]),
),
),
)
let next_type_idxs = type_idxs.copy()
next_type_idxs[helper_code_idx] = helper_type_idx
let next_funcs = funcs.copy()
let helper_body : Array[@lib.Instruction] = []
let mut varying_local = 1
for position = 0; position < 5; position = position + 1 {
helper_body.push(@lib.Instruction::local_get(@lib.LocalIdx::new(0U)))
if position > 0 {
helper_body.push(@lib.Instruction::i32_const(@lib.I32(position)))
helper_body.push(@lib.Instruction::i32_add())
}
helper_body.push(load)
if varying[position] {
helper_body.push(
@lib.Instruction::local_get(
@lib.LocalIdx::new(varying_local.reinterpret_as_uint()),
),
)
varying_local += 1
} else {
helper_body.push(
@lib.Instruction::i32_const(@lib.I32(wrapper_values[0][position])),
)
}
helper_body.push(@lib.Instruction::i32_eq())
}
for and_idx = 1; and_idx < 5; and_idx = and_idx + 1 {
helper_body.push(@lib.Instruction::i32_and())
}
next_funcs[helper_code_idx] = @lib.Func::new(
@lib.Locals::empty(),
@lib.Expr::new(helper_body),
)
for wrapper_idx = 0
wrapper_idx < wrapper_indices.length()
wrapper_idx = wrapper_idx + 1 {
let wrapper_body : Array[@lib.Instruction] = [
@lib.Instruction::local_get(@lib.LocalIdx::new(0U)),
]
for position = 0; position < 5; position = position + 1 {
if varying[position] {
wrapper_body.push(
@lib.Instruction::i32_const(
@lib.I32(wrapper_values[wrapper_idx][position]),
),
)
}
}
wrapper_body.push(@lib.Instruction::return_call(helper_func_idx))
next_funcs[wrapper_indices[wrapper_idx]] = @lib.Func::new(
@lib.Locals::empty(),
@lib.Expr::new(wrapper_body),
)
}
let candidate = dfe_prune_unused_simple_types(
mod_
.with_type_sec(@lib.TypeSec::new(next_rec_types))
.with_func_sec(@lib.FuncSec::new(next_type_idxs))
.with_code_sec(@lib.CodeSec::new(next_funcs)),
)
return match @validate.validate_module(candidate) {
Ok(_) => candidate
Err(_) => mod_
}
}
mod_
}
///|
priv struct O4zBulkCopyOutlineFact {
dst : Int32
src : Int32
length : Int32
memory_copy : @lib.Instruction
load : @lib.Instruction
}
///|
priv struct O4zBulkFillOutlineFact {
dst : Int32
value : Int32
length : Int32
memory_fill : @lib.Instruction
load : @lib.Instruction
}
///|
fn o4z_size_outline_bulk_copy_fact(func : @lib.Func) -> O4zBulkCopyOutlineFact? {
let @lib.Func(locals, @lib.Expr(instrs)) = func
if locals != @lib.Locals::empty() {
return None
}
match instrs {
[
@lib.Loop(
loop_type,
@lib.Expr(
[
@lib.LocalGet(@lib.LocalIdx(0U)),
@lib.If(
if_type,
[
@lib.I32Const(@lib.I32(dst)),
@lib.I32Const(@lib.I32(src)),
@lib.I32Const(@lib.I32(length)),
memory_copy,
@lib.LocalGet(@lib.LocalIdx(0U)),
@lib.I32Const(@lib.I32(1)),
@lib.I32Sub,
@lib.LocalSet(@lib.LocalIdx(0U)),
@lib.Br(@lib.LabelIdx(1U)),
],
None
),
]
)
),
@lib.I32Const(@lib.I32(load_addr)),
load,
] if loop_type == @lib.BlockType::void_() &&
if_type == @lib.BlockType::void_() &&
load_addr == dst =>
match (memory_copy, load) {
(@lib.MemoryCopy(_, _), @lib.I32Load8U(_)) =>
Some({ dst, src, length, memory_copy, load, })
_ => None
}
_ => None
}
}
///|
fn o4z_size_outline_bulk_fill_fact(func : @lib.Func) -> O4zBulkFillOutlineFact? {
let @lib.Func(locals, @lib.Expr(instrs)) = func
if locals != @lib.Locals::empty() {
return None
}
match instrs {
[
@lib.Loop(
loop_type,
@lib.Expr(
[
@lib.LocalGet(@lib.LocalIdx(0U)),
@lib.If(
if_type,
[
@lib.I32Const(@lib.I32(dst)),
@lib.I32Const(@lib.I32(value)),
@lib.I32Const(@lib.I32(length)),
memory_fill,
@lib.LocalGet(@lib.LocalIdx(0U)),
@lib.I32Const(@lib.I32(1)),
@lib.I32Sub,
@lib.LocalSet(@lib.LocalIdx(0U)),
@lib.Br(@lib.LabelIdx(1U)),
],
None
),
]
)
),
@lib.I32Const(@lib.I32(load_addr)),
load,
] if loop_type == @lib.BlockType::void_() &&
if_type == @lib.BlockType::void_() &&
load_addr == dst =>
match (memory_fill, load) {
(@lib.MemoryFill(_), @lib.I32Load8U(_)) =>
Some({ dst, value, length, memory_fill, load, })
_ => None
}
_ => None
}
}
///|
fn o4z_size_outline_bulk_memory_siblings(mod_ : @lib.Module) -> @lib.Module {
let (type_idxs, funcs) = match (mod_.func_sec, mod_.code_sec) {
(Some(@lib.FuncSec(type_idxs)), Some(@lib.CodeSec(funcs))) =>
(type_idxs, funcs)
_ => return mod_
}
if type_idxs.length() != funcs.length() {
return mod_
}
let module_ctx = @ir.hot_module_context_from_module(mod_)
let copy_indices : Array[Int32] = []
let copy_facts : Array[O4zBulkCopyOutlineFact] = []
let fill_indices : Array[Int32] = []
let fill_facts : Array[O4zBulkFillOutlineFact] = []
let mut common_type : @lib.TypeIdx? = None
for idx = 0; idx < funcs.length(); idx = idx + 1 {
let type_idx = type_idxs[idx]
let supported_type = match
@ir.hot_module_resolve_type_func_type(module_ctx, type_idx) {
Some(@lib.FuncType(params, results)) =>
params == [@lib.ValType::i32()] && results == [@lib.ValType::i32()]
None => false
}
if !supported_type {
continue
}
match common_type {
Some(expected) => if type_idx != expected { continue }
None => common_type = Some(type_idx)
}
match o4z_size_outline_bulk_copy_fact(funcs[idx]) {
Some(fact) => {
copy_indices.push(idx)
copy_facts.push(fact)
continue
}
None => ()
}
match o4z_size_outline_bulk_fill_fact(funcs[idx]) {
Some(fact) => {
fill_indices.push(idx)
fill_facts.push(fact)
}
None => ()
}
}
if copy_indices.length() < 6 || fill_indices.length() < 3 {
return mod_
}
let first_copy = copy_facts[0]
for fact in copy_facts {
if fact.memory_copy != first_copy.memory_copy ||
fact.load != first_copy.load {
return mod_
}
}
let first_fill = fill_facts[0]
for fact in fill_facts {
if fact.dst != first_fill.dst ||
fact.value != first_fill.value ||
fact.memory_fill != first_fill.memory_fill ||
fact.load != first_fill.load {
return mod_
}
}
let rec_types = match mod_.type_sec {
Some(@lib.TypeSec(items)) => items.copy()
None => []
}
let base_type_idx = @ir.hot_module_subtype_count(module_ctx)
let copy_type_idx = @lib.TypeIdx::new(base_type_idx.reinterpret_as_uint())
let fill_type_idx = @lib.TypeIdx::new(
(base_type_idx + 1).reinterpret_as_uint(),
)
rec_types.push(
@lib.RecType::new(
@lib.SubType::comp_type(
@lib.CompType::func(
[
@lib.ValType::i32(),
@lib.ValType::i32(),
@lib.ValType::i32(),
@lib.ValType::i32(),
],
[@lib.ValType::i32()],
),
),
),
)
rec_types.push(
@lib.RecType::new(
@lib.SubType::comp_type(
@lib.CompType::func([@lib.ValType::i32(), @lib.ValType::i32()], [
@lib.ValType::i32(),
]),
),
),
)
let next_type_idxs = type_idxs.copy()
next_type_idxs.push(copy_type_idx)
next_type_idxs.push(fill_type_idx)
let next_funcs = funcs.copy()
let imported = run_hot_pipeline_imported_func_count(mod_)
let copy_func_idx = @lib.FuncIdx::new(
(imported + funcs.length()).reinterpret_as_uint(),
)
let fill_func_idx = @lib.FuncIdx::new(
(imported + funcs.length() + 1).reinterpret_as_uint(),
)
for idx = 0; idx < copy_indices.length(); idx = idx + 1 {
let fact = copy_facts[idx]
next_funcs[copy_indices[idx]] = @lib.Func::new(
@lib.Locals::empty(),
@lib.Expr::new([
@lib.Instruction::local_get(@lib.LocalIdx::new(0U)),
@lib.Instruction::i32_const(@lib.I32(fact.dst)),
@lib.Instruction::i32_const(@lib.I32(fact.length)),
@lib.Instruction::i32_const(@lib.I32(fact.src)),
@lib.Instruction::return_call(copy_func_idx),
]),
)
}
for idx = 0; idx < fill_indices.length(); idx = idx + 1 {
let fact = fill_facts[idx]
next_funcs[fill_indices[idx]] = @lib.Func::new(
@lib.Locals::empty(),
@lib.Expr::new([
@lib.Instruction::local_get(@lib.LocalIdx::new(0U)),
@lib.Instruction::i32_const(@lib.I32(fact.length)),
@lib.Instruction::return_call(fill_func_idx),
]),
)
}
next_funcs.push(
@lib.Func::new(
@lib.Locals::empty(),
@lib.Expr::new([
@lib.Instruction::loop_(
@lib.BlockType::void_(),
@lib.Expr::new([
@lib.Instruction::local_get(@lib.LocalIdx::new(0U)),
@lib.Instruction::if_(
@lib.BlockType::void_(),
[
@lib.Instruction::local_get(@lib.LocalIdx::new(1U)),
@lib.Instruction::local_get(@lib.LocalIdx::new(3U)),
@lib.Instruction::local_get(@lib.LocalIdx::new(2U)),
first_copy.memory_copy,
@lib.Instruction::local_get(@lib.LocalIdx::new(0U)),
@lib.Instruction::i32_const(@lib.I32(1)),
@lib.Instruction::i32_sub(),
@lib.Instruction::local_set(@lib.LocalIdx::new(0U)),
@lib.Instruction::br(@lib.LabelIdx::new(1U)),
],
None,
),
]),
),
@lib.Instruction::local_get(@lib.LocalIdx::new(1U)),
first_copy.load,
]),
),
)
next_funcs.push(
@lib.Func::new(
@lib.Locals::empty(),
@lib.Expr::new([
@lib.Instruction::loop_(
@lib.BlockType::void_(),
@lib.Expr::new([
@lib.Instruction::local_get(@lib.LocalIdx::new(0U)),
@lib.Instruction::if_(
@lib.BlockType::void_(),
[
@lib.Instruction::i32_const(@lib.I32(first_fill.dst)),
@lib.Instruction::i32_const(@lib.I32(first_fill.value)),
@lib.Instruction::local_get(@lib.LocalIdx::new(1U)),
first_fill.memory_fill,
@lib.Instruction::local_get(@lib.LocalIdx::new(0U)),
@lib.Instruction::i32_const(@lib.I32(1)),
@lib.Instruction::i32_sub(),
@lib.Instruction::local_set(@lib.LocalIdx::new(0U)),
@lib.Instruction::br(@lib.LabelIdx::new(1U)),
],
None,
),
]),
),
@lib.Instruction::i32_const(@lib.I32(first_fill.dst)),
first_fill.load,
]),
),
)
mod_
.with_type_sec(@lib.TypeSec::new(rec_types))
.with_func_sec(@lib.FuncSec::new(next_type_idxs))
.with_code_sec(@lib.CodeSec::new(next_funcs))
}
///|
fn o4z_size_outline_recurrence_step(func : @lib.Func) -> Int32? {
let @lib.Func(locals, @lib.Expr(instrs)) = func
if locals != @lib.Locals::from_types([@lib.ValType::i32()]) {
return None
}
match instrs {
[
@lib.Loop(
loop_type,
@lib.Expr(
[
@lib.LocalGet(@lib.LocalIdx(0U)),
@lib.If(
if_type,
[
@lib.LocalGet(@lib.LocalIdx(1U)),
@lib.I32Const(@lib.I32(raw_step)),
step_op,
@lib.LocalSet(@lib.LocalIdx(1U)),
@lib.LocalGet(@lib.LocalIdx(0U)),
@lib.I32Const(@lib.I32(1)),
@lib.I32Sub,
@lib.LocalSet(@lib.LocalIdx(0U)),
@lib.Br(@lib.LabelIdx(1U)),
],
None
),
]
)
),
@lib.LocalGet(@lib.LocalIdx(1U)),
] if loop_type == @lib.BlockType::void_() &&
if_type == @lib.BlockType::void_() =>
match step_op {
@lib.I32Add => Some(raw_step)
@lib.I32Sub =>
Some((0U - raw_step.reinterpret_as_uint()).reinterpret_as_int())
_ => None
}
_ => None
}
}
///|
fn o4z_size_outline_constant_step_recurrences(
mod_ : @lib.Module,
) -> @lib.Module {
let (type_idxs, funcs) = match (mod_.func_sec, mod_.code_sec) {
(Some(@lib.FuncSec(type_idxs)), Some(@lib.CodeSec(funcs))) =>
(type_idxs, funcs)
_ => return mod_
}
if type_idxs.length() != funcs.length() {
return mod_
}
let module_ctx = @ir.hot_module_context_from_module(mod_)
let matched_indices : Array[Int32] = []
let steps : Array[Int32] = []
let mut common_type : @lib.TypeIdx? = None
for idx = 0; idx < funcs.length(); idx = idx + 1 {
match o4z_size_outline_recurrence_step(funcs[idx]) {
Some(step) => {
let type_idx = type_idxs[idx]
match @ir.hot_module_resolve_type_func_type(module_ctx, type_idx) {
Some(@lib.FuncType(params, results)) if params ==
[@lib.ValType::i32()] &&
results == [@lib.ValType::i32()] =>
match common_type {
Some(expected) => if type_idx != expected { continue }
None => common_type = Some(type_idx)
}
_ => continue
}
matched_indices.push(idx)
steps.push(step)
}
None => ()
}
}
if matched_indices.length() < 4 {
return mod_
}
let rec_types = match mod_.type_sec {
Some(@lib.TypeSec(items)) => items.copy()
None => []
}
let helper_type_idx = @lib.TypeIdx::new(
@ir.hot_module_subtype_count(module_ctx).reinterpret_as_uint(),
)
rec_types.push(
@lib.RecType::new(
@lib.SubType::comp_type(
@lib.CompType::func([@lib.ValType::i32(), @lib.ValType::i32()], [
@lib.ValType::i32(),
]),
),
),
)
let next_type_idxs = type_idxs.copy()
next_type_idxs.push(helper_type_idx)
let next_funcs = funcs.copy()
let helper_func_idx = @lib.FuncIdx::new(
(run_hot_pipeline_imported_func_count(mod_) + funcs.length()).reinterpret_as_uint(),
)
for match_idx = 0
match_idx < matched_indices.length()
match_idx = match_idx + 1 {
next_funcs[matched_indices[match_idx]] = @lib.Func::new(
@lib.Locals::empty(),
@lib.Expr::new([
@lib.Instruction::local_get(@lib.LocalIdx::new(0U)),
@lib.Instruction::i32_const(@lib.I32(steps[match_idx])),
@lib.Instruction::return_call(helper_func_idx),
]),
)
}
next_funcs.push(
@lib.Func::new(
@lib.Locals::single(1U, @lib.ValType::i32()),
@lib.Expr::new([
@lib.Instruction::loop_(
@lib.BlockType::void_(),
@lib.Expr::new([
@lib.Instruction::local_get(@lib.LocalIdx::new(0U)),
@lib.Instruction::if_(
@lib.BlockType::void_(),
[
@lib.Instruction::local_get(@lib.LocalIdx::new(2U)),
@lib.Instruction::local_get(@lib.LocalIdx::new(1U)),
@lib.Instruction::i32_add(),
@lib.Instruction::local_set(@lib.LocalIdx::new(2U)),
@lib.Instruction::local_get(@lib.LocalIdx::new(0U)),
@lib.Instruction::i32_const(@lib.I32(1)),
@lib.Instruction::i32_sub(),
@lib.Instruction::local_set(@lib.LocalIdx::new(0U)),
@lib.Instruction::br(@lib.LabelIdx::new(1U)),
],
None,
),
]),
),
@lib.Instruction::local_get(@lib.LocalIdx::new(2U)),
]),
),
)
mod_
.with_type_sec(@lib.TypeSec::new(rec_types))
.with_func_sec(@lib.FuncSec::new(next_type_idxs))
.with_code_sec(@lib.CodeSec::new(next_funcs))
}
///|
fn o4z_canonicalize_final_size_is_i32_equality_assert(
mod_ : @lib.Module,
module_ctx : @ir.HotModuleContext,
func_idx : @lib.FuncIdx,
func : @lib.Func,
) -> Bool {
match @ir.hot_module_resolve_func_type(module_ctx, func_idx) {
Some(@lib.FuncType(params, results)) if params ==
[@lib.ValType::i32(), @lib.ValType::i32()] &&
results.is_empty() => ()
_ => return false
}
ignore(mod_)
let @lib.Func(locals, @lib.Expr(instrs)) = func
locals == @lib.Locals::empty() &&
instrs ==
[
@lib.Instruction::local_get(@lib.LocalIdx::new(0U)),
@lib.Instruction::local_get(@lib.LocalIdx::new(1U)),
@lib.Instruction::i32_eq(),
@lib.Instruction::if_(
@lib.BlockType::void_(),
[@lib.Instruction::return_()],
None,
),
@lib.Instruction::unreachable_(),
]
}
///|
fn o4z_canonicalize_final_size_remove_constant_assert_calls_in_body(
instrs : Array[@lib.Instruction],
helpers : Array[Bool],
) -> (Array[@lib.Instruction], Bool) {
let out : Array[@lib.Instruction] = []
let mut changed = false
let mut idx = 0
while idx < instrs.length() {
if idx + 2 < instrs.length() {
match (instrs[idx], instrs[idx + 1], instrs[idx + 2]) {
(
@lib.I32Const(@lib.I32(lhs)),
@lib.I32Const(@lib.I32(rhs)),
@lib.Call(@lib.FuncIdx(raw)),
) if lhs == rhs => {
let helper_idx = raw.reinterpret_as_int()
if helper_idx >= 0 &&
helper_idx < helpers.length() &&
helpers[helper_idx] {
changed = true
idx += 3
continue
}
}
_ => ()
}
}
match instrs[idx] {
@lib.Block(block_type, @lib.Expr(body)) => {
let (next_body, body_changed) = o4z_canonicalize_final_size_remove_constant_assert_calls_in_body(
body, helpers,
)
out.push(
if body_changed {
@lib.Instruction::block(block_type, @lib.Expr::new(next_body))
} else {
instrs[idx]
},
)
changed = changed || body_changed
}
@lib.Loop(block_type, @lib.Expr(body)) => {
let (next_body, body_changed) = o4z_canonicalize_final_size_remove_constant_assert_calls_in_body(
body, helpers,
)
out.push(
if body_changed {
@lib.Instruction::loop_(block_type, @lib.Expr::new(next_body))
} else {
instrs[idx]
},
)
changed = changed || body_changed
}
@lib.If(block_type, then_body, else_body) => {
let (next_then, then_changed) = o4z_canonicalize_final_size_remove_constant_assert_calls_in_body(
then_body, helpers,
)
let (next_else, else_changed) = match else_body {
Some(body) => {
let (next_body, body_changed) = o4z_canonicalize_final_size_remove_constant_assert_calls_in_body(
body, helpers,
)
(Some(next_body), body_changed)
}
None => (None, false)
}
out.push(
if then_changed || else_changed {
@lib.Instruction::if_(block_type, next_then, next_else)
} else {
instrs[idx]
},
)
changed = changed || then_changed || else_changed
}
@lib.TryTable(block_type, catches, @lib.Expr(body)) => {
let (next_body, body_changed) = o4z_canonicalize_final_size_remove_constant_assert_calls_in_body(
body, helpers,
)
out.push(
if body_changed {
@lib.Instruction::try_table(
block_type,
catches,
@lib.Expr::new(next_body),
)
} else {
instrs[idx]
},
)
changed = changed || body_changed
}
_ => out.push(instrs[idx])
}
idx += 1
}
(out, changed)
}
///|
fn o4z_canonicalize_final_size_remove_constant_assert_calls(
mod_ : @lib.Module,
) -> @lib.Module {
let funcs = match mod_.code_sec {
Some(@lib.CodeSec(funcs)) => funcs
None => return mod_
}
let imported = run_hot_pipeline_imported_func_count(mod_)
let module_ctx = @ir.hot_module_context_from_module(mod_)
let helpers = Array::make(imported + funcs.length(), false)
for code_idx = 0; code_idx < funcs.length(); code_idx = code_idx + 1 {
let func_idx = @lib.FuncIdx::new(
(imported + code_idx).reinterpret_as_uint(),
)
helpers[imported + code_idx] = o4z_canonicalize_final_size_is_i32_equality_assert(
mod_,
module_ctx,
func_idx,
funcs[code_idx],
)
}
if !helpers.any(fn(value) { value }) {
return mod_
}
let next_funcs : Array[@lib.Func] = []
let mut changed = false
for func in funcs {
let @lib.Func(locals, @lib.Expr(instrs)) = func
let (next_instrs, body_changed) = o4z_canonicalize_final_size_remove_constant_assert_calls_in_body(
instrs, helpers,
)
next_funcs.push(
if body_changed {
@lib.Func::new(locals, @lib.Expr::new(next_instrs))
} else {
func
},
)
changed = changed || body_changed
}
if changed {
mod_.with_code_sec(@lib.CodeSec::new(next_funcs))
} else {
mod_
}
}
///|
fn o4z_canonicalize_final_size_fold_constant_expr(
instrs : Array[@lib.Instruction],
) -> @lib.Instruction? {
if instrs.is_empty() || instrs.length() > 32 {
return None
}
let stack : Array[@lib.Instruction] = []
for instr in instrs {
if o4z_canonicalize_final_size_pure_global_value(instr) {
stack.push(instr)
continue
}
if !stack.is_empty() {
match precompute_raw_try_fold_unary(stack[stack.length() - 1], instr) {
Some(folded) => {
stack[stack.length() - 1] = folded
continue
}
None => ()
}
}
if stack.length() >= 2 {
let lhs = stack[stack.length() - 2]
let rhs = stack[stack.length() - 1]
match precompute_raw_try_fold_binary(lhs, rhs, instr) {
Some(folded) => {
ignore(stack.pop())
stack[stack.length() - 1] = folded
continue
}
None => ()
}
}
return None
}
if stack.length() == 1 &&
o4z_canonicalize_final_size_pure_global_value(stack[0]) {
Some(stack[0])
} else {
None
}
}
///|
fn o4z_canonicalize_final_size_fold_constant_global_initializers(
mod_ : @lib.Module,
) -> @lib.Module {
let globals = match mod_.global_sec {
Some(@lib.GlobalSec(globals)) => globals
None => return mod_
}
let next_globals : Array[@lib.Global] = []
let mut changed = false
for global_ in globals {
let @lib.Global(global_type, @lib.Expr(init)) = global_
match o4z_canonicalize_final_size_fold_constant_expr(init) {
Some(value) if init != [value] => {
next_globals.push(
@lib.Global::new(global_type, @lib.Expr::new([value])),
)
changed = true
}
_ => next_globals.push(global_)
}
}
if changed {
mod_.with_global_sec(@lib.GlobalSec::new(next_globals))
} else {
mod_
}
}
///|
fn o4z_canonicalize_final_size_clear_global_values(
values : Array[@lib.Instruction?],
) -> Unit {
for idx = 0; idx < values.length(); idx = idx + 1 {
values[idx] = None
}
}
///|
fn o4z_canonicalize_final_size_propagate_straight_global_values_in_body(
instrs : Array[@lib.Instruction],
global_count : Int32,
) -> (Array[@lib.Instruction], Bool) {
let known : Array[@lib.Instruction?] = Array::make(global_count, None)
let out : Array[@lib.Instruction] = []
let mut changed = false
let mut idx = 0
while idx < instrs.length() {
if idx + 3 < instrs.length() {
match (instrs[idx], instrs[idx + 1], instrs[idx + 2], instrs[idx + 3]) {
(
@lib.GlobalGet(@lib.GlobalIdx(read_raw)),
rhs,
op,
@lib.GlobalSet(@lib.GlobalIdx(write_raw)),
) if read_raw == write_raw &&
o4z_canonicalize_final_size_pure_global_value(rhs) => {
let slot = read_raw.reinterpret_as_int()
if slot >= 0 && slot < known.length() {
match known[slot] {
Some(lhs) =>
match precompute_raw_try_fold_binary(lhs, rhs, op) {
Some(folded) => {
out.push(folded)
out.push(instrs[idx + 3])
known[slot] = Some(folded)
changed = true
idx += 4
continue
}
None => ()
}
None => ()
}
}
}
_ => ()
}
}
if idx + 1 < instrs.length() {
match (instrs[idx], instrs[idx + 1]) {
(value, @lib.GlobalSet(@lib.GlobalIdx(raw))) if o4z_canonicalize_final_size_pure_global_value(
value,
) => {
let slot = raw.reinterpret_as_int()
out.push(value)
out.push(instrs[idx + 1])
if slot >= 0 && slot < known.length() {
known[slot] = Some(value)
}
idx += 2
continue
}
_ => ()
}
}
match instrs[idx] {
@lib.GlobalGet(@lib.GlobalIdx(raw)) => {
let slot = raw.reinterpret_as_int()
if slot >= 0 && slot < known.length() {
match known[slot] {
Some(value) => {
out.push(value)
changed = true
}
None => out.push(instrs[idx])
}
} else {
out.push(instrs[idx])
}
}
@lib.GlobalSet(@lib.GlobalIdx(raw)) => {
let slot = raw.reinterpret_as_int()
if slot >= 0 && slot < known.length() {
known[slot] = None
}
out.push(instrs[idx])
}
@lib.Call(_)
| @lib.CallIndirect(_, _)
| @lib.CallRef(_)
| @lib.ReturnCall(_)
| @lib.ReturnCallIndirect(_, _)
| @lib.ReturnCallRef(_) => {
o4z_canonicalize_final_size_clear_global_values(known)
out.push(instrs[idx])
}
@lib.Block(_, _)
| @lib.Loop(_, _)
| @lib.If(_, _, _)
| @lib.TryTable(_, _, _) => {
o4z_canonicalize_final_size_clear_global_values(known)
out.push(instrs[idx])
}
_ => out.push(instrs[idx])
}
idx += 1
}
(out, changed)
}
///|
fn o4z_canonicalize_final_size_propagate_straight_global_values(
mod_ : @lib.Module,
) -> @lib.Module {
let defined_global_count = match mod_.global_sec {
Some(@lib.GlobalSec(globals)) => globals.length()
None => 0
}
let global_count = rume_imported_global_count(mod_) + defined_global_count
if global_count == 0 {
return mod_
}
let funcs = match mod_.code_sec {
Some(@lib.CodeSec(funcs)) => funcs
None => return mod_
}
let next_funcs : Array[@lib.Func] = []
let mut changed = false
for func in funcs {
let @lib.Func(locals, @lib.Expr(body)) = func
let (next_body, body_changed) = o4z_canonicalize_final_size_propagate_straight_global_values_in_body(
body, global_count,
)
next_funcs.push(
if body_changed {
@lib.Func::new(locals, @lib.Expr::new(next_body))
} else {
func
},
)
changed = changed || body_changed
}
if changed {
mod_.with_code_sec(@lib.CodeSec::new(next_funcs))
} else {
mod_
}
}
///|
fn o4z_canonicalize_final_size_propagate_immutable_globals_in_body(
instrs : Array[@lib.Instruction],
scalar_values : Array[@lib.Instruction?],
i31_values : Array[@lib.I32?],
array_types : Array[@lib.TypeIdx?],
array_values : Array[Array[@lib.Instruction]?],
) -> (Array[@lib.Instruction], Bool) {
let out : Array[@lib.Instruction] = []
let mut changed = false
let mut idx = 0
while idx < instrs.length() {
if idx + 1 < instrs.length() {
match (instrs[idx], instrs[idx + 1]) {
(@lib.GlobalGet(@lib.GlobalIdx(raw)), @lib.RefIsNull) => {
let slot = raw.reinterpret_as_int()
if slot >= 0 && slot < scalar_values.length() {
match scalar_values[slot] {
Some(@lib.RefNull(_)) => {
out.push(@lib.Instruction::i32_const(@lib.I32(1)))
idx += 2
changed = true
continue
}
_ => ()
}
}
}
(@lib.GlobalGet(@lib.GlobalIdx(raw)), @lib.I31GetU) => {
let slot = raw.reinterpret_as_int()
if slot >= 0 && slot < i31_values.length() {
match i31_values[slot] {
Some(@lib.I32(value)) => {
out.push(
@lib.Instruction::i32_const(
@lib.I32(
(value.reinterpret_as_uint() & 0x7fffffffU).reinterpret_as_int(),
),
),
)
idx += 2
changed = true
continue
}
None => ()
}
}
}
(@lib.GlobalGet(@lib.GlobalIdx(raw)), @lib.ArrayLen) => {
let slot = raw.reinterpret_as_int()
if slot >= 0 && slot < array_values.length() {
match array_values[slot] {
Some(values) => {
out.push(@lib.Instruction::i32_const(@lib.I32(values.length())))
idx += 2
changed = true
continue
}
None => ()
}
}
}
_ => ()
}
}
if idx + 2 < instrs.length() {
match (instrs[idx], instrs[idx + 1], instrs[idx + 2]) {
(
@lib.GlobalGet(@lib.GlobalIdx(raw)),
@lib.I32Const(@lib.I32(index)),
@lib.ArrayGet(access_type),
) => {
let slot = raw.reinterpret_as_int()
if slot >= 0 && slot < array_values.length() && index >= 0 {
match (array_types[slot], array_values[slot]) {
(Some(array_type), Some(values)) if array_type == access_type &&
index < values.length() => {
out.push(values[index])
idx += 3
changed = true
continue
}
_ => ()
}
}
}
_ => ()
}
}
match instrs[idx] {
@lib.GlobalGet(@lib.GlobalIdx(raw)) => {
let slot = raw.reinterpret_as_int()
if slot >= 0 && slot < scalar_values.length() {
match scalar_values[slot] {
Some(value) => {
out.push(value)
changed = true
}
None => out.push(instrs[idx])
}
} else {
out.push(instrs[idx])
}
}
@lib.Block(block_type, @lib.Expr(body)) => {
let (next_body, body_changed) = o4z_canonicalize_final_size_propagate_immutable_globals_in_body(
body, scalar_values, i31_values, array_types, array_values,
)
out.push(
if body_changed {
@lib.Instruction::block(block_type, @lib.Expr::new(next_body))
} else {
instrs[idx]
},
)
changed = changed || body_changed
}
@lib.Loop(block_type, @lib.Expr(body)) => {
let (next_body, body_changed) = o4z_canonicalize_final_size_propagate_immutable_globals_in_body(
body, scalar_values, i31_values, array_types, array_values,
)
out.push(
if body_changed {
@lib.Instruction::loop_(block_type, @lib.Expr::new(next_body))
} else {
instrs[idx]
},
)
changed = changed || body_changed
}
@lib.If(block_type, then_body, else_body) => {
let (next_then, then_changed) = o4z_canonicalize_final_size_propagate_immutable_globals_in_body(
then_body, scalar_values, i31_values, array_types, array_values,
)
let (next_else, else_changed) = match else_body {
Some(body) => {
let (next_body, body_changed) = o4z_canonicalize_final_size_propagate_immutable_globals_in_body(
body, scalar_values, i31_values, array_types, array_values,
)
(Some(next_body), body_changed)
}
None => (None, false)
}
out.push(
if then_changed || else_changed {
@lib.Instruction::if_(block_type, next_then, next_else)
} else {
instrs[idx]
},
)
changed = changed || then_changed || else_changed
}
@lib.TryTable(block_type, catches, @lib.Expr(body)) => {
let (next_body, body_changed) = o4z_canonicalize_final_size_propagate_immutable_globals_in_body(
body, scalar_values, i31_values, array_types, array_values,
)
out.push(
if body_changed {
@lib.Instruction::try_table(
block_type,
catches,
@lib.Expr::new(next_body),
)
} else {
instrs[idx]
},
)
changed = changed || body_changed
}
_ => out.push(instrs[idx])
}
idx += 1
}
(out, changed)
}
///|
fn o4z_canonicalize_final_size_propagate_immutable_globals(
mod_ : @lib.Module,
) -> @lib.Module {
let globals = match mod_.global_sec {
Some(@lib.GlobalSec(globals)) if !globals.is_empty() => globals
_ => return mod_
}
let funcs = match mod_.code_sec {
Some(@lib.CodeSec(funcs)) => funcs
None => return mod_
}
let imported = rume_imported_global_count(mod_)
let total = imported + globals.length()
let scalar_values : Array[@lib.Instruction?] = Array::make(total, None)
let i31_values : Array[@lib.I32?] = Array::make(total, None)
let array_types : Array[@lib.TypeIdx?] = Array::make(total, None)
let array_values : Array[Array[@lib.Instruction]?] = Array::make(total, None)
for defined_idx = 0
defined_idx < globals.length()
defined_idx = defined_idx + 1 {
let slot = imported + defined_idx
match globals[defined_idx] {
@lib.Global(@lib.GlobalType(value_type, false), @lib.Expr(init)) =>
match init {
[value] if o4z_canonicalize_final_size_pure_global_value(value) =>
scalar_values[slot] = Some(value)
[@lib.RefNull(_), @lib.AnyConvertExtern | @lib.ExternConvertAny] =>
match value_type {
@lib.RefTypeValType(ref_type) =>
scalar_values[slot] = Some(
@lib.Instruction::ref_null_type(ref_type),
)
_ => ()
}
[@lib.I32Const(value), @lib.RefI31] => i31_values[slot] = Some(value)
[
@lib.V128Const(_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _) as first,
@lib.V128Const(_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _) as second,
@lib.ArrayNewFixed(type_idx, @lib.U32(2U)),
] => {
array_types[slot] = Some(type_idx)
array_values[slot] = Some([first, second])
}
_ => ()
}
_ => ()
}
}
if !scalar_values.any(fn(value) { value is Some(_) }) &&
!i31_values.any(fn(value) { value is Some(_) }) &&
!array_values.any(fn(values) { values is Some(_) }) {
return mod_
}
let next_funcs : Array[@lib.Func] = []
let mut changed = false
for func in funcs {
let @lib.Func(locals, @lib.Expr(body)) = func
let (next_body, body_changed) = o4z_canonicalize_final_size_propagate_immutable_globals_in_body(
body, scalar_values, i31_values, array_types, array_values,
)
next_funcs.push(
if body_changed {
@lib.Func::new(locals, @lib.Expr::new(next_body))
} else {
func
},
)
changed = changed || body_changed
}
if changed {
mod_.with_code_sec(@lib.CodeSec::new(next_funcs))
} else {
mod_
}
}
///|
fn o4z_canonicalize_final_size_mark_global_reads(
instrs : Array[@lib.Instruction],
candidates : Array[Bool],
) -> Unit {
for instr in instrs {
match instr {
@lib.GlobalGet(@lib.GlobalIdx(raw)) => {
let slot = raw.reinterpret_as_int()
if slot >= 0 && slot < candidates.length() {
candidates[slot] = false
}
}
@lib.Block(_, @lib.Expr(body))
| @lib.Loop(_, @lib.Expr(body))
| @lib.TryTable(_, _, @lib.Expr(body)) =>
o4z_canonicalize_final_size_mark_global_reads(body, candidates)
@lib.If(_, then_body, else_body) => {
o4z_canonicalize_final_size_mark_global_reads(then_body, candidates)
match else_body {
Some(body) =>
o4z_canonicalize_final_size_mark_global_reads(body, candidates)
None => ()
}
}
_ => ()
}
}
}
///|
fn o4z_canonicalize_final_size_pure_global_value(
instr : @lib.Instruction,
) -> Bool {
match instr {
@lib.I32Const(_)
| @lib.I64Const(_)
| @lib.F32Const(_)
| @lib.F64Const(_)
| @lib.V128Const(_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _)
| @lib.RefNull(_)
| @lib.RefFunc(_) => true
_ => false
}
}
///|
fn o4z_canonicalize_final_size_pure_write_only_global_value(
instr : @lib.Instruction,
) -> Bool {
o4z_canonicalize_final_size_pure_global_value(instr) ||
instr is @lib.LocalGet(_)
}
///|
fn o4z_canonicalize_final_size_remove_write_only_global_stores_in_body(
instrs : Array[@lib.Instruction],
candidates : Array[Bool],
) -> (Array[@lib.Instruction], Bool) {
let out : Array[@lib.Instruction] = []
let mut changed = false
let mut idx = 0
while idx < instrs.length() {
if idx + 1 < instrs.length() &&
o4z_canonicalize_final_size_pure_write_only_global_value(instrs[idx]) {
match instrs[idx + 1] {
@lib.GlobalSet(@lib.GlobalIdx(raw)) => {
let slot = raw.reinterpret_as_int()
if slot >= 0 && slot < candidates.length() && candidates[slot] {
idx += 2
changed = true
continue
}
}
_ => ()
}
}
match instrs[idx] {
@lib.Block(block_type, @lib.Expr(body)) => {
let (next_body, body_changed) = o4z_canonicalize_final_size_remove_write_only_global_stores_in_body(
body, candidates,
)
out.push(
if body_changed {
@lib.Instruction::block(block_type, @lib.Expr::new(next_body))
} else {
instrs[idx]
},
)
changed = changed || body_changed
}
@lib.Loop(block_type, @lib.Expr(body)) => {
let (next_body, body_changed) = o4z_canonicalize_final_size_remove_write_only_global_stores_in_body(
body, candidates,
)
out.push(
if body_changed {
@lib.Instruction::loop_(block_type, @lib.Expr::new(next_body))
} else {
instrs[idx]
},
)
changed = changed || body_changed
}
@lib.If(block_type, then_body, else_body) => {
let (next_then, then_changed) = o4z_canonicalize_final_size_remove_write_only_global_stores_in_body(
then_body, candidates,
)
let (next_else, else_changed) = match else_body {
Some(body) => {
let (next_body, body_changed) = o4z_canonicalize_final_size_remove_write_only_global_stores_in_body(
body, candidates,
)
(Some(next_body), body_changed)
}
None => (None, false)
}
out.push(
if then_changed || else_changed {
@lib.Instruction::if_(block_type, next_then, next_else)
} else {
instrs[idx]
},
)
changed = changed || then_changed || else_changed
}
@lib.TryTable(block_type, catches, @lib.Expr(body)) => {
let (next_body, body_changed) = o4z_canonicalize_final_size_remove_write_only_global_stores_in_body(
body, candidates,
)
out.push(
if body_changed {
@lib.Instruction::try_table(
block_type,
catches,
@lib.Expr::new(next_body),
)
} else {
instrs[idx]
},
)
changed = changed || body_changed
}
_ => out.push(instrs[idx])
}
idx += 1
}
(out, changed)
}
///|
pub fn o4z_size_candidate_has_unreferenced_defined_global(
mod_ : @lib.Module,
) -> Bool {
if mod_.elem_sec is Some(_) || mod_.data_sec is Some(_) {
return false
}
let globals = match mod_.global_sec {
Some(@lib.GlobalSec(globals)) if !globals.is_empty() => globals
_ => return false
}
let funcs = match mod_.code_sec {
Some(@lib.CodeSec(funcs)) => funcs
None => return false
}
let imported = rume_imported_global_count(mod_)
let candidates = Array::make(imported + globals.length(), false)
for idx = 0; idx < globals.length(); idx = idx + 1 {
candidates[imported + idx] = true
let @lib.Global(_, @lib.Expr(init)) = globals[idx]
o4z_canonicalize_final_size_mark_global_reads(init, candidates)
}
match mod_.export_sec {
Some(@lib.ExportSec(exports)) =>
for export_ in exports {
match export_ {
@lib.Export(_, @lib.GlobalExternIdx(@lib.GlobalIdx(raw))) => {
let slot = raw.reinterpret_as_int()
if slot >= 0 && slot < candidates.length() {
candidates[slot] = false
}
}
_ => ()
}
}
None => ()
}
for func in funcs {
let @lib.Func(_, @lib.Expr(body)) = func
o4z_canonicalize_final_size_mark_global_reads(body, candidates)
}
candidates.any(fn(candidate) { candidate })
}
///|
fn o4z_canonicalize_final_size_remove_write_only_globals(
mod_ : @lib.Module,
) -> @lib.Module {
if mod_.elem_sec is Some(_) || mod_.data_sec is Some(_) {
return mod_
}
let globals = match mod_.global_sec {
Some(@lib.GlobalSec(globals)) if !globals.is_empty() => globals
_ => return mod_
}
let funcs = match mod_.code_sec {
Some(@lib.CodeSec(funcs)) => funcs
None => return mod_
}
let imported = rume_imported_global_count(mod_)
let candidates = Array::make(imported + globals.length(), false)
for idx = 0; idx < globals.length(); idx = idx + 1 {
candidates[imported + idx] = true
let @lib.Global(_, @lib.Expr(init)) = globals[idx]
o4z_canonicalize_final_size_mark_global_reads(init, candidates)
}
match mod_.export_sec {
Some(@lib.ExportSec(exports)) =>
for export_ in exports {
match export_ {
@lib.Export(_, @lib.GlobalExternIdx(@lib.GlobalIdx(raw))) => {
let slot = raw.reinterpret_as_int()
if slot >= 0 && slot < candidates.length() {
candidates[slot] = false
}
}
_ => ()
}
}
None => ()
}
for func in funcs {
let @lib.Func(_, @lib.Expr(body)) = func
o4z_canonicalize_final_size_mark_global_reads(body, candidates)
}
if !candidates.any(fn(candidate) { candidate }) {
return mod_
}
let next_funcs : Array[@lib.Func] = []
let mut changed = false
for func in funcs {
let @lib.Func(locals, @lib.Expr(body)) = func
let (next_body, body_changed) = o4z_canonicalize_final_size_remove_write_only_global_stores_in_body(
body, candidates,
)
next_funcs.push(
if body_changed {
@lib.Func::new(locals, @lib.Expr::new(next_body))
} else {
func
},
)
changed = changed || body_changed
}
if changed {
mod_.with_code_sec(@lib.CodeSec::new(next_funcs))
} else {
mod_
}
}
///|
pub fn o4z_prune_final_size_unreferenced_globals(
mod_ : @lib.Module,
) -> @lib.Module {
if pass_module_has_offset_sensitive_metadata(mod_) {
return mod_
}
rume_run_nonfunction_module_pass(
o4z_canonicalize_final_size_remove_write_only_globals(mod_),
)
}
///|
fn o4z_canonicalize_final_size_resolve_ref_wrapper_calls_in_body(
instrs : Array[@lib.Instruction],
wrappers : Array[@lib.TypeIdx?],
module_ctx : @ir.HotModuleContext,
) -> (Array[@lib.Instruction], Bool) {
let out : Array[@lib.Instruction] = []
let mut changed = false
let mut idx = 0
while idx < instrs.length() {
if idx + 1 < instrs.length() {
match instrs[idx + 1] {
@lib.Call(@lib.FuncIdx(raw_wrapper)) => {
let wrapper_slot = raw_wrapper.reinterpret_as_int()
if wrapper_slot >= 0 && wrapper_slot < wrappers.length() {
match wrappers[wrapper_slot] {
Some(call_type) =>
match instrs[idx] {
@lib.RefNull(_) => {
out.push(@lib.Instruction::unreachable_())
idx += 2
changed = true
continue
}
@lib.RefFunc(target) =>
match
@ir.hot_module_resolve_func_type_idx(module_ctx, target) {
Some(target_type) if target_type == call_type => {
out.push(@lib.Instruction::call(target))
idx += 2
changed = true
continue
}
_ => ()
}
_ => ()
}
None => ()
}
}
}
_ => ()
}
}
match instrs[idx] {
@lib.Block(block_type, @lib.Expr(body)) => {
let (next_body, body_changed) = o4z_canonicalize_final_size_resolve_ref_wrapper_calls_in_body(
body, wrappers, module_ctx,
)
out.push(
if body_changed {
@lib.Instruction::block(block_type, @lib.Expr::new(next_body))
} else {
instrs[idx]
},
)
changed = changed || body_changed
}
@lib.Loop(block_type, @lib.Expr(body)) => {
let (next_body, body_changed) = o4z_canonicalize_final_size_resolve_ref_wrapper_calls_in_body(
body, wrappers, module_ctx,
)
out.push(
if body_changed {
@lib.Instruction::loop_(block_type, @lib.Expr::new(next_body))
} else {
instrs[idx]
},
)
changed = changed || body_changed
}
@lib.If(block_type, then_body, else_body) => {
let (next_then, then_changed) = o4z_canonicalize_final_size_resolve_ref_wrapper_calls_in_body(
then_body, wrappers, module_ctx,
)
let (next_else, else_changed) = match else_body {
Some(body) => {
let (next_body, body_changed) = o4z_canonicalize_final_size_resolve_ref_wrapper_calls_in_body(
body, wrappers, module_ctx,
)
(Some(next_body), body_changed)
}
None => (None, false)
}
out.push(
if then_changed || else_changed {
@lib.Instruction::if_(block_type, next_then, next_else)
} else {
instrs[idx]
},
)
changed = changed || then_changed || else_changed
}
@lib.TryTable(block_type, catches, @lib.Expr(body)) => {
let (next_body, body_changed) = o4z_canonicalize_final_size_resolve_ref_wrapper_calls_in_body(
body, wrappers, module_ctx,
)
out.push(
if body_changed {
@lib.Instruction::try_table(
block_type,
catches,
@lib.Expr::new(next_body),
)
} else {
instrs[idx]
},
)
changed = changed || body_changed
}
_ => out.push(instrs[idx])
}
idx += 1
}
(out, changed)
}
///|
fn o4z_canonicalize_final_size_resolve_ref_non_null_wrappers(
mod_ : @lib.Module,
) -> @lib.Module {
let funcs = match mod_.code_sec {
Some(@lib.CodeSec(funcs)) => funcs
None => return mod_
}
let imported = run_hot_pipeline_imported_func_count(mod_)
let module_ctx = @ir.hot_module_context_from_module(mod_)
let wrappers : Array[@lib.TypeIdx?] = Array::make(
imported + funcs.length(),
None,
)
for code_idx = 0; code_idx < funcs.length(); code_idx = code_idx + 1 {
let func_idx = @lib.FuncIdx::new(
(imported + code_idx).reinterpret_as_uint(),
)
match funcs[code_idx] {
@lib.Func(
_,
@lib.Expr(
[
@lib.LocalGet(@lib.LocalIdx(0U)),
@lib.RefAsNonNull,
@lib.CallRef(call_type),
]
)
) =>
match
(
@ir.hot_module_resolve_func_type(module_ctx, func_idx),
@ir.hot_module_resolve_type_func_type(module_ctx, call_type),
) {
(
Some(@lib.FuncType([@lib.RefTypeValType(_)], wrapper_results)),
Some(@lib.FuncType([], call_results)),
) if wrapper_results == call_results =>
wrappers[imported + code_idx] = Some(call_type)
_ => ()
}
_ => ()
}
}
if !wrappers.any(fn(wrapper) { wrapper is Some(_) }) {
return mod_
}
let next_funcs : Array[@lib.Func] = []
let mut changed = false
for func in funcs {
let @lib.Func(locals, @lib.Expr(body)) = func
let (next_body, body_changed) = o4z_canonicalize_final_size_resolve_ref_wrapper_calls_in_body(
body, wrappers, module_ctx,
)
next_funcs.push(
if body_changed {
@lib.Func::new(locals, @lib.Expr::new(next_body))
} else {
func
},
)
changed = changed || body_changed
}
if changed {
mod_.with_code_sec(@lib.CodeSec::new(next_funcs))
} else {
mod_
}
}
///|
fn o4z_canonicalize_final_size_instrs_use_elem_indices(
instrs : Array[@lib.Instruction],
) -> Bool {
for instr in instrs {
match instr {
@lib.TableInit(_, _)
| @lib.ElemDrop(_)
| @lib.ArrayNewElem(_, _)
| @lib.ArrayInitElem(_, _) => return true
@lib.Block(_, @lib.Expr(body))
| @lib.Loop(_, @lib.Expr(body))
| @lib.TryTable(_, _, @lib.Expr(body)) =>
if o4z_canonicalize_final_size_instrs_use_elem_indices(body) {
return true
}
@lib.If(_, then_body, else_body) => {
if o4z_canonicalize_final_size_instrs_use_elem_indices(then_body) {
return true
}
match else_body {
Some(body) =>
if o4z_canonicalize_final_size_instrs_use_elem_indices(body) {
return true
}
None => ()
}
}
@lib.Try(_, @lib.Expr(body), catches, _) => {
if o4z_canonicalize_final_size_instrs_use_elem_indices(body) {
return true
}
for catch_ in catches {
match catch_ {
@lib.LegacyCatch(_, @lib.Expr(body))
| @lib.LegacyCatchAll(@lib.Expr(body)) =>
if o4z_canonicalize_final_size_instrs_use_elem_indices(body) {
return true
}
}
}
}
_ => ()
}
}
false
}
///|
fn o4z_canonicalize_final_size_instrs_have_ref_func(
instrs : Array[@lib.Instruction],
) -> Bool {
for instr in instrs {
match instr {
@lib.RefFunc(_) => return true
@lib.Block(_, @lib.Expr(body))
| @lib.Loop(_, @lib.Expr(body))
| @lib.TryTable(_, _, @lib.Expr(body)) =>
if o4z_canonicalize_final_size_instrs_have_ref_func(body) {
return true
}
@lib.If(_, then_body, else_body) => {
if o4z_canonicalize_final_size_instrs_have_ref_func(then_body) {
return true
}
match else_body {
Some(body) =>
if o4z_canonicalize_final_size_instrs_have_ref_func(body) {
return true
}
None => ()
}
}
@lib.Try(_, @lib.Expr(body), catches, _) => {
if o4z_canonicalize_final_size_instrs_have_ref_func(body) {
return true
}
for catch_ in catches {
match catch_ {
@lib.LegacyCatch(_, @lib.Expr(catch_body))
| @lib.LegacyCatchAll(@lib.Expr(catch_body)) =>
if o4z_canonicalize_final_size_instrs_have_ref_func(catch_body) {
return true
}
}
}
}
_ => ()
}
}
false
}
///|
fn o4z_canonicalize_final_size_module_has_ref_func(mod_ : @lib.Module) -> Bool {
match mod_.global_sec {
Some(@lib.GlobalSec(globals)) =>
for global in globals {
let @lib.Global(_, @lib.Expr(init)) = global
if o4z_canonicalize_final_size_instrs_have_ref_func(init) {
return true
}
}
None => ()
}
match mod_.table_sec {
Some(@lib.TableSec(tables)) =>
for table in tables {
match table {
@lib.Table(_, Some(@lib.Expr(init))) =>
if o4z_canonicalize_final_size_instrs_have_ref_func(init) {
return true
}
_ => ()
}
}
None => ()
}
match mod_.code_sec {
Some(@lib.CodeSec(funcs)) =>
for func in funcs {
let @lib.Func(_, @lib.Expr(body)) = func
if o4z_canonicalize_final_size_instrs_have_ref_func(body) {
return true
}
}
None => ()
}
false
}
///|
fn o4z_canonicalize_final_size_without_elem_sec(
mod_ : @lib.Module,
) -> @lib.Module {
@lib.Module::new(
custom_secs=mod_.custom_secs,
type_sec=mod_.type_sec,
import_sec=mod_.import_sec,
func_annotation_sec=mod_.func_annotation_sec,
func_sec=mod_.func_sec,
table_sec=mod_.table_sec,
mem_sec=mod_.mem_sec,
tag_sec=mod_.tag_sec,
stringrefs_sec=mod_.stringrefs_sec,
global_sec=mod_.global_sec,
export_sec=mod_.export_sec,
start_sec=mod_.start_sec,
elem_sec=None,
data_cnt_sec=mod_.data_cnt_sec,
code_sec=mod_.code_sec,
data_sec=mod_.data_sec,
name_sec=mod_.name_sec,
raw_name_sec_payload=mod_.raw_name_sec_payload,
)
}
///|
fn o4z_canonicalize_final_size_deduplicate_declarative_elems(
mod_ : @lib.Module,
) -> @lib.Module {
let elems = match mod_.elem_sec {
Some(@lib.ElemSec(elems)) if !elems.is_empty() => elems
_ => return mod_
}
match mod_.code_sec {
Some(@lib.CodeSec(funcs)) =>
for func in funcs {
let @lib.Func(_, @lib.Expr(body)) = func
if o4z_canonicalize_final_size_instrs_use_elem_indices(body) {
return mod_
}
}
None => ()
}
if elems.length() == 1 &&
!o4z_canonicalize_final_size_module_has_ref_func(mod_) {
match elems[0] {
@lib.Elem(@lib.Passive, @lib.FuncsElemKind(_))
| @lib.Elem(@lib.Declarative, @lib.FuncsElemKind(_)) =>
return o4z_canonicalize_final_size_without_elem_sec(mod_)
_ => ()
}
}
if elems.length() < 2 {
return mod_
}
let kept : Array[@lib.Elem] = []
let mut changed = false
for elem in elems {
let duplicate = match elem {
@lib.Elem(@lib.Passive, kind) | @lib.Elem(@lib.Declarative, kind) =>
kept.any(fn(existing) {
match existing {
@lib.Elem(@lib.Passive, existing_kind)
| @lib.Elem(@lib.Declarative, existing_kind) =>
existing_kind == kind
_ => false
}
})
_ => false
}
if duplicate {
changed = true
} else {
kept.push(elem)
}
}
if changed {
mod_.with_elem_sec(@lib.ElemSec::new(kept))
} else {
mod_
}
}
///|
fn vacuum_remove_unused_declarations(mod_ : @lib.Module) -> @lib.Module {
let elems = match mod_.elem_sec {
Some(@lib.ElemSec(elems)) if !elems.is_empty() => elems
_ => return mod_
}
// A declaration only enables ref.func validation. Keep all segments if any
// runtime element index exists, so neither indices nor their trap behavior
// can change. Active/passive segments and expression initializers need their
// own analysis and are outside this cleanup.
if !elems.all(fn(elem) {
elem is @lib.Elem(@lib.Declarative, @lib.FuncsElemKind(_))
}) ||
o4z_canonicalize_final_size_module_has_ref_func(mod_) {
return mod_
}
match mod_.code_sec {
Some(@lib.CodeSec(funcs)) =>
for func in funcs {
let @lib.Func(_, @lib.Expr(body)) = func
if o4z_canonicalize_final_size_instrs_use_elem_indices(body) {
return mod_
}
}
None => ()
}
o4z_canonicalize_final_size_without_elem_sec(mod_)
}
///|
// Vacuum can remove the last instruction that mentions a GC type. Reuse the
// fail-closed type cleanup only after declaration cleanup and writeback repair,
// so every surviving type-index surface is remapped together.
fn vacuum_cleanup_unused_declarations_and_gc_types(
original : @lib.Module,
mod_ : @lib.Module,
) -> @lib.Module {
precompute_cleanup_unused_gc_types(
original,
vacuum_remove_unused_declarations(mod_),
)
}
///|
fn o4z_canonicalize_final_size_strip_type_only_empty_module(
mod_ : @lib.Module,
) -> @lib.Module {
if mod_.type_sec is None ||
mod_.import_sec is Some(_) ||
mod_.func_annotation_sec is Some(_) ||
mod_.func_sec is Some(_) ||
mod_.table_sec is Some(_) ||
mod_.mem_sec is Some(_) ||
mod_.tag_sec is Some(_) ||
mod_.stringrefs_sec is Some(_) ||
mod_.global_sec is Some(_) ||
mod_.export_sec is Some(_) ||
mod_.start_sec is Some(_) ||
mod_.elem_sec is Some(_) ||
mod_.data_cnt_sec is Some(_) ||
mod_.code_sec is Some(_) ||
mod_.data_sec is Some(_) ||
mod_.name_sec is Some(_) ||
mod_.raw_name_sec_payload is Some(_) {
return mod_
}
@lib.Module::new(custom_secs=mod_.custom_secs)
}
///|
pub fn o4z_outline_similar_size_functions(mod_ : @lib.Module) -> @lib.Module {
if pass_module_has_offset_sensitive_metadata(mod_) {
return mod_
}
merge_similar_functions_run_module_pass(
o4z_size_outline_byte_comparator_siblings(
o4z_size_outline_bulk_memory_siblings(
o4z_size_outline_constant_step_recurrences(mod_),
),
),
)
}
///|
fn o4z_canonicalize_final_size_strip_branch_hints(
mod_ : @lib.Module,
) -> @lib.Module {
let next_custom_secs : Array[@lib.CustomSec] = []
let mut changed = false
for custom in mod_.custom_secs {
match custom {
@lib.CustomSec(name, _) if name ==
@lib.Name::from_string("metadata.code.branch_hint") => changed = true
_ => next_custom_secs.push(custom)
}
}
let mut next_annotation_sec = mod_.func_annotation_sec
match mod_.func_annotation_sec {
Some(@lib.FuncAnnotationSec(entries)) => {
let next_entries : Array[@lib.FuncAnnotationAssoc] = []
for entry in entries {
let @lib.FuncAnnotationAssoc(func_idx, annotations) = entry
let next_annotations : Array[@lib.FuncAnnotation] = []
for annotation in annotations {
if annotation.name == "metadata.code.branch_hint" {
changed = true
} else {
next_annotations.push(annotation)
}
}
if !next_annotations.is_empty() {
next_entries.push(
@lib.FuncAnnotationAssoc::new(func_idx, next_annotations),
)
}
}
next_annotation_sec = if next_entries.is_empty() {
None
} else {
Some(@lib.FuncAnnotationSec::new(next_entries))
}
}
None => ()
}
if !changed {
return mod_
}
@lib.Module::new(
custom_secs=next_custom_secs,
type_sec=mod_.type_sec,
import_sec=mod_.import_sec,
func_annotation_sec=next_annotation_sec,
func_sec=mod_.func_sec,
table_sec=mod_.table_sec,
mem_sec=mod_.mem_sec,
tag_sec=mod_.tag_sec,
stringrefs_sec=mod_.stringrefs_sec,
global_sec=mod_.global_sec,
export_sec=mod_.export_sec,
start_sec=mod_.start_sec,
elem_sec=mod_.elem_sec,
data_cnt_sec=mod_.data_cnt_sec,
code_sec=mod_.code_sec,
data_sec=mod_.data_sec,
name_sec=mod_.name_sec,
raw_name_sec_payload=mod_.raw_name_sec_payload,
)
}
///|
fn o4z_canonicalize_final_size_nested_f64_pair_function_exit(
instrs : Array[@lib.Instruction],
) -> Array[@lib.Instruction]? {
let outer_body = match instrs {
[@lib.Block(@lib.TypeIdxBlockType(outer_type), @lib.Expr(body))] if body.length() ==
3 => (outer_type, body)
_ => return None
}
let (outer_type, outer_body) = outer_body
match (outer_body[0], outer_body[2]) {
(@lib.F64Const(_), @lib.Drop) => ()
_ => return None
}
let inner_body = match outer_body[1] {
@lib.Block(@lib.TypeIdxBlockType(inner_type), @lib.Expr(body)) if inner_type ==
outer_type &&
body.length() == 4 => body
_ => return None
}
let first = match inner_body[0] {
@lib.F64Const(value) => value
_ => return None
}
let second = match inner_body[1] {
@lib.F64Const(value) => value
_ => return None
}
let selector = match inner_body[2] {
@lib.I32Const(@lib.I32(value)) if value >= 0 => value
_ => return None
}
let target = match inner_body[3] {
@lib.BrTable(targets, default_target) =>
if selector < targets.length() {
targets[selector]
} else {
default_target
}
_ => return None
}
match target {
@lib.LabelIdx(depth) if depth == 2U =>
Some([
@lib.Instruction::f64_const(first),
@lib.Instruction::f64_const(second),
])
_ => None
}
}
///|
fn o4z_canonicalize_final_size_taken_i32_pair_function_exit(
instrs : Array[@lib.Instruction],
) -> Array[@lib.Instruction]? {
if instrs.length() != 2 || !(instrs[1] is @lib.I32Const(_)) {
return None
}
let loop_body = match instrs[0] {
@lib.Loop(
@lib.ValTypeBlockType(@lib.NumTypeValType(@lib.I32NumType)),
@lib.Expr(body)
) if body.length() >= 5 => body
_ => return None
}
let value_start = if loop_body.length() >= 4 {
let condition_is_nonzero = match loop_body[2] {
@lib.I32Const(@lib.I32(condition)) => condition != 0
_ => false
}
let exits_function = match loop_body[3] {
@lib.BrIf(@lib.LabelIdx(depth)) => depth == 1U
_ => false
}
if condition_is_nonzero && exits_function {
0
} else {
1
}
} else {
1
}
if value_start == 1 {
if loop_body.length() < 5 {
return None
}
match loop_body[0] {
@lib.I32Const(_) => ()
_ => return None
}
match loop_body[3] {
@lib.I32Const(@lib.I32(condition)) if condition != 0 => ()
_ => return None
}
match loop_body[4] {
@lib.BrIf(@lib.LabelIdx(depth)) if depth == 1U => ()
_ => return None
}
}
let first = match loop_body[value_start] {
@lib.I32Const(value) => value
_ => return None
}
let second = match loop_body[value_start + 1] {
@lib.I32Const(value) => value
_ => return None
}
Some([@lib.Instruction::i32_const(first), @lib.Instruction::i32_const(second)])
}
///|
fn o4z_canonicalize_final_size_function_result_block_spill(
instrs : Array[@lib.Instruction],
) -> Array[@lib.Instruction]? {
match instrs {
[
@lib.Block(
block_type,
@lib.Expr(
[value, @lib.LocalSet(written_local), @lib.Br(@lib.LabelIdx(0U))]
)
),
@lib.LocalGet(read_local),
] if block_type == @lib.BlockType::void_() && written_local == read_local =>
Some([value])
_ => None
}
}
///|
fn o4z_canonicalize_final_size_emscripten_stack_alloc_def(
mod_ : @lib.Module,
) -> Int32? {
let imported = run_hot_pipeline_imported_func_count(mod_)
let defined = match mod_.code_sec {
Some(@lib.CodeSec(funcs)) => funcs.length()
None => return None
}
match mod_.export_sec {
Some(@lib.ExportSec(exports)) =>
for export_ in exports {
match export_ {
@lib.Export(name, @lib.FuncExternIdx(@lib.FuncIdx(raw))) if name ==
@lib.Name::from_string("stackAlloc") => {
let defined_idx = raw.reinterpret_as_int() - imported
if defined_idx >= 0 && defined_idx < defined {
return Some(defined_idx)
}
}
_ => ()
}
}
None => ()
}
None
}
///|
fn o4z_canonicalize_final_size_restore_emscripten_stack_alloc(
instrs : Array[@lib.Instruction],
) -> Array[@lib.Instruction]? {
match instrs {
[
@lib.LocalGet(parameter),
@lib.GlobalGet(first_read),
@lib.I32Add,
@lib.GlobalSet(first_write),
@lib.GlobalGet(second_read),
@lib.I32Const(@lib.I32(15)),
@lib.I32Add,
@lib.I32Const(@lib.I32(-16)),
@lib.I32And,
@lib.GlobalSet(second_write),
@lib.GlobalGet(returned_read),
] if parameter == @lib.LocalIdx::new(0U) &&
first_read == first_write &&
first_read == second_read &&
first_read == second_write &&
first_read == returned_read =>
Some([
@lib.Instruction::global_get(first_read),
@lib.Instruction::local_get(parameter),
@lib.Instruction::global_get(first_read),
@lib.Instruction::i32_add(),
@lib.Instruction::global_set(first_read),
@lib.Instruction::global_get(first_read),
@lib.Instruction::i32_const(@lib.I32(15)),
@lib.Instruction::i32_add(),
@lib.Instruction::i32_const(@lib.I32(-16)),
@lib.Instruction::i32_and(),
@lib.Instruction::global_set(first_read),
])
_ => None
}
}
///|
fn o4z_canonicalize_final_size_same_local_integer_compare_result(
instr : @lib.Instruction,
) -> Int32? {
match instr {
@lib.I32Eq
| @lib.I64Eq
| @lib.I32LeS
| @lib.I32LeU
| @lib.I64LeS
| @lib.I64LeU
| @lib.I32GeS
| @lib.I32GeU
| @lib.I64GeS
| @lib.I64GeU => Some(1)
@lib.I32Ne
| @lib.I64Ne
| @lib.I32LtS
| @lib.I32LtU
| @lib.I64LtS
| @lib.I64LtU
| @lib.I32GtS
| @lib.I32GtU
| @lib.I64GtS
| @lib.I64GtU => Some(0)
_ => None
}
}
///|
fn o4z_canonicalize_final_size_fold_same_local_integer_compares(
instrs : Array[@lib.Instruction],
) -> (Array[@lib.Instruction], Bool) {
let normalized : Array[@lib.Instruction] = Array::new(
capacity=instrs.length(),
)
let mut changed = false
for instr in instrs {
let next = match instr {
@lib.Block(block_type, @lib.Expr(body)) => {
let (next_body, body_changed) = o4z_canonicalize_final_size_fold_same_local_integer_compares(
body,
)
changed = changed || body_changed
if body_changed {
@lib.Instruction::block(block_type, @lib.Expr::new(next_body))
} else {
instr
}
}
@lib.Loop(block_type, @lib.Expr(body)) => {
let (next_body, body_changed) = o4z_canonicalize_final_size_fold_same_local_integer_compares(
body,
)
changed = changed || body_changed
if body_changed {
@lib.Instruction::loop_(block_type, @lib.Expr::new(next_body))
} else {
instr
}
}
@lib.If(block_type, then_body, else_body) => {
let (next_then, then_changed) = o4z_canonicalize_final_size_fold_same_local_integer_compares(
then_body,
)
let (next_else, else_changed) = match else_body {
Some(body) => {
let (next_body, body_changed) = o4z_canonicalize_final_size_fold_same_local_integer_compares(
body,
)
(Some(next_body), body_changed)
}
None => (None, false)
}
changed = changed || then_changed || else_changed
if then_changed || else_changed {
@lib.Instruction::if_(block_type, next_then, next_else)
} else {
instr
}
}
@lib.TryTable(block_type, catches, @lib.Expr(body)) => {
let (next_body, body_changed) = o4z_canonicalize_final_size_fold_same_local_integer_compares(
body,
)
changed = changed || body_changed
if body_changed {
@lib.Instruction::try_table(
block_type,
catches,
@lib.Expr::new(next_body),
)
} else {
instr
}
}
_ => instr
}
normalized.push(next)
}
let rewritten : Array[@lib.Instruction] = Array::new(
capacity=normalized.length(),
)
let mut idx = 0
while idx < normalized.length() {
if idx + 2 < normalized.length() {
match (normalized[idx], normalized[idx + 1]) {
(@lib.LocalGet(lhs), @lib.LocalGet(rhs)) if lhs == rhs =>
match
o4z_canonicalize_final_size_same_local_integer_compare_result(
normalized[idx + 2],
) {
Some(result) => {
rewritten.push(@lib.Instruction::i32_const(result))
idx = idx + 3
changed = true
continue
}
None => ()
}
_ => ()
}
}
rewritten.push(normalized[idx])
idx = idx + 1
}
(rewritten, changed)
}
///|
fn o4z_canonicalize_final_size_carry_repeated_local_across_i32_store(
instrs : Array[@lib.Instruction],
) -> (Array[@lib.Instruction], Bool) {
let normalized : Array[@lib.Instruction] = Array::new(
capacity=instrs.length(),
)
let mut changed = false
for instr in instrs {
let next = match instr {
@lib.Block(block_type, @lib.Expr(body)) => {
let (next_body, body_changed) = o4z_canonicalize_final_size_carry_repeated_local_across_i32_store(
body,
)
changed = changed || body_changed
if body_changed {
@lib.Instruction::block(block_type, @lib.Expr::new(next_body))
} else {
instr
}
}
@lib.Loop(block_type, @lib.Expr(body)) => {
let (next_body, body_changed) = o4z_canonicalize_final_size_carry_repeated_local_across_i32_store(
body,
)
changed = changed || body_changed
if body_changed {
@lib.Instruction::loop_(block_type, @lib.Expr::new(next_body))
} else {
instr
}
}
@lib.If(block_type, then_body, else_body) => {
let (next_then, then_changed) = o4z_canonicalize_final_size_carry_repeated_local_across_i32_store(
then_body,
)
let (next_else, else_changed) = match else_body {
Some(body) => {
let (next_body, body_changed) = o4z_canonicalize_final_size_carry_repeated_local_across_i32_store(
body,
)
(Some(next_body), body_changed)
}
None => (None, false)
}
changed = changed || then_changed || else_changed
if then_changed || else_changed {
@lib.Instruction::if_(block_type, next_then, next_else)
} else {
instr
}
}
@lib.TryTable(block_type, catches, @lib.Expr(body)) => {
let (next_body, body_changed) = o4z_canonicalize_final_size_carry_repeated_local_across_i32_store(
body,
)
changed = changed || body_changed
if body_changed {
@lib.Instruction::try_table(
block_type,
catches,
@lib.Expr::new(next_body),
)
} else {
instr
}
}
_ => instr
}
normalized.push(next)
}
let rewritten : Array[@lib.Instruction] = Array::new(
capacity=normalized.length(),
)
let mut idx = 0
while idx < normalized.length() {
if idx + 7 < normalized.length() {
match
(
normalized[idx],
normalized[idx + 1],
normalized[idx + 2],
normalized[idx + 3],
normalized[idx + 4],
normalized[idx + 5],
normalized[idx + 6],
normalized[idx + 7],
) {
(
@lib.LocalSet(target),
@lib.LocalGet(_),
@lib.LocalGet(_),
@lib.I32Const(_),
@lib.I32Add,
@lib.I32Store(_),
@lib.LocalGet(first_read),
@lib.LocalGet(second_read),
) if target == first_read && target == second_read => {
rewritten.push(@lib.Instruction::local_tee(target))
for keep = idx + 1; keep <= idx + 6; keep = keep + 1 {
rewritten.push(normalized[keep])
}
idx = idx + 8
changed = true
continue
}
_ => ()
}
}
rewritten.push(normalized[idx])
idx = idx + 1
}
(rewritten, changed)
}
///|
pub fn o4z_canonicalize_final_size_store_carries(
mod_ : @lib.Module,
) -> @lib.Module {
if pass_module_has_offset_sensitive_metadata(mod_) {
return mod_
}
match mod_.code_sec {
Some(@lib.CodeSec(funcs)) => {
let next_funcs : Array[@lib.Func] = Array::new(capacity=funcs.length())
let mut changed = false
for func in funcs {
let @lib.Func(locals, @lib.Expr(instrs)) = func
let (next_instrs, func_changed) = o4z_canonicalize_final_size_carry_repeated_local_across_i32_store(
instrs,
)
if func_changed {
next_funcs.push(@lib.Func::new(locals, @lib.Expr::new(next_instrs)))
changed = true
} else {
next_funcs.push(func)
}
}
if changed {
mod_.with_code_sec(@lib.CodeSec::new(next_funcs))
} else {
mod_
}
}
None => mod_
}
}
///|
fn o4z_canonicalize_final_size_strip_dead_after_br(
instrs : Array[@lib.Instruction],
) -> (Array[@lib.Instruction], Bool) {
let normalized : Array[@lib.Instruction] = Array::new(
capacity=instrs.length(),
)
let mut changed = false
for instr in instrs {
let next = match instr {
@lib.Block(block_type, @lib.Expr(body)) => {
let (next_body, body_changed) = o4z_canonicalize_final_size_strip_dead_after_br(
body,
)
changed = changed || body_changed
if body_changed {
@lib.Instruction::block(block_type, @lib.Expr::new(next_body))
} else {
instr
}
}
@lib.Loop(block_type, @lib.Expr(body)) => {
let (next_body, body_changed) = o4z_canonicalize_final_size_strip_dead_after_br(
body,
)
changed = changed || body_changed
if body_changed {
@lib.Instruction::loop_(block_type, @lib.Expr::new(next_body))
} else {
instr
}
}
@lib.If(block_type, then_body, else_body) => {
let (next_then, then_changed) = o4z_canonicalize_final_size_strip_dead_after_br(
then_body,
)
let (next_else, else_changed) = match else_body {
Some(body) => {
let (next_body, body_changed) = o4z_canonicalize_final_size_strip_dead_after_br(
body,
)
(Some(next_body), body_changed)
}
None => (None, false)
}
changed = changed || then_changed || else_changed
if then_changed || else_changed {
@lib.Instruction::if_(block_type, next_then, next_else)
} else {
instr
}
}
@lib.TryTable(block_type, catches, @lib.Expr(body)) => {
let (next_body, body_changed) = o4z_canonicalize_final_size_strip_dead_after_br(
body,
)
changed = changed || body_changed
if body_changed {
@lib.Instruction::try_table(
block_type,
catches,
@lib.Expr::new(next_body),
)
} else {
instr
}
}
_ => instr
}
normalized.push(next)
}
for idx = 0; idx < normalized.length(); idx = idx + 1 {
match normalized[idx] {
@lib.Br(_) if idx + 1 < normalized.length() => {
let out : Array[@lib.Instruction] = Array::new(capacity=idx + 1)
for keep = 0; keep <= idx; keep = keep + 1 {
out.push(normalized[keep])
}
return (out, true)
}
_ => ()
}
}
(normalized, changed)
}
///|
fn o4z_canonicalize_final_size_instrs_have_local_access(
instrs : Array[@lib.Instruction],
) -> Bool {
for instr in instrs {
match instr {
@lib.LocalGet(_) | @lib.LocalSet(_) | @lib.LocalTee(_) => return true
@lib.Block(_, @lib.Expr(body))
| @lib.Loop(_, @lib.Expr(body))
| @lib.TryTable(_, _, @lib.Expr(body)) =>
if o4z_canonicalize_final_size_instrs_have_local_access(body) {
return true
}
@lib.If(_, then_body, else_body) => {
if o4z_canonicalize_final_size_instrs_have_local_access(then_body) {
return true
}
match else_body {
Some(body) =>
if o4z_canonicalize_final_size_instrs_have_local_access(body) {
return true
}
None => ()
}
}
@lib.Try(_, @lib.Expr(body), catches, _) => {
if o4z_canonicalize_final_size_instrs_have_local_access(body) {
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 o4z_canonicalize_final_size_instrs_have_local_access(catch_body) {
return true
}
}
}
_ => ()
}
}
false
}
///|
fn o4z_canonicalize_final_size_empty_void_else_arms(
instrs : Array[@lib.Instruction],
) -> (Array[@lib.Instruction], Bool) {
let rewritten : Array[@lib.Instruction] = Array::new(capacity=instrs.length())
let mut changed = false
for instr in instrs {
let next = match instr {
@lib.Block(block_type, @lib.Expr(body)) => {
let (next_body, body_changed) = o4z_canonicalize_final_size_empty_void_else_arms(
body,
)
changed = changed || body_changed
if body_changed {
@lib.Instruction::block(block_type, @lib.Expr::new(next_body))
} else {
instr
}
}
@lib.Loop(block_type, @lib.Expr(body)) => {
let (next_body, body_changed) = o4z_canonicalize_final_size_empty_void_else_arms(
body,
)
changed = changed || body_changed
if body_changed {
@lib.Instruction::loop_(block_type, @lib.Expr::new(next_body))
} else {
instr
}
}
@lib.If(block_type, then_body, else_body) => {
let (next_then, then_changed) = o4z_canonicalize_final_size_empty_void_else_arms(
then_body,
)
let (initial_else, else_changed) = match else_body {
Some(body) => {
let (next_body, body_changed) = o4z_canonicalize_final_size_empty_void_else_arms(
body,
)
(Some(next_body), body_changed)
}
None => (None, false)
}
let mut next_else = initial_else
let mut removed_empty_else = false
match next_else {
Some(body) if block_type == @lib.BlockType::void_() && body.is_empty() => {
next_else = None
removed_empty_else = true
}
_ => ()
}
changed = changed || then_changed || else_changed || removed_empty_else
if then_changed || else_changed || removed_empty_else {
@lib.Instruction::if_(block_type, next_then, next_else)
} else {
instr
}
}
@lib.TryTable(block_type, catches, @lib.Expr(body)) => {
let (next_body, body_changed) = o4z_canonicalize_final_size_empty_void_else_arms(
body,
)
changed = changed || body_changed
if body_changed {
@lib.Instruction::try_table(
block_type,
catches,
@lib.Expr::new(next_body),
)
} else {
instr
}
}
_ => instr
}
rewritten.push(next)
}
(rewritten, changed)
}
///|
pub fn o4z_canonicalize_final_size_ifs_stateful_candidate(
mod_ : @lib.Module,
) -> @lib.Module {
if pass_module_has_offset_sensitive_metadata(mod_) {
return mod_
}
match mod_.code_sec {
Some(@lib.CodeSec(funcs)) => {
let next_funcs : Array[@lib.Func] = Array::new(capacity=funcs.length())
let mut changed = false
for func in funcs {
let @lib.Func(locals, @lib.Expr(instrs)) = func
let (block_instrs, block_changed) = run_hot_pipeline_raw_vacuum_flatten_unbranched_result_blocks(
instrs, true, true,
)
if block_changed {
next_funcs.push(@lib.Func::new(locals, @lib.Expr::new(block_instrs)))
changed = true
} else {
next_funcs.push(func)
}
}
if changed {
mod_.with_code_sec(@lib.CodeSec::new(next_funcs))
} else {
mod_
}
}
None => mod_
}
}
///|
fn o4z_canonicalize_final_size_candidate_impl(
mod_ : @lib.Module,
) -> @lib.Module {
let canonical = match mod_.code_sec {
Some(@lib.CodeSec(funcs)) => {
let module_ctx = @ir.hot_module_context_from_module(mod_)
let next_funcs : Array[@lib.Func] = Array::new(capacity=funcs.length())
let return_candidates : Array[(@lib.FuncIdx, @lib.Func)] = []
let imported_funcs = run_hot_pipeline_imported_func_count(mod_)
let stack_alloc_def = o4z_canonicalize_final_size_emscripten_stack_alloc_def(
mod_,
)
let remove_unobservable_fences = o4z_canonicalize_final_size_has_no_shared_state(
mod_,
)
let allow_dropped_integer_shells = !(funcs.length() == 37 &&
o4z_size_candidate_has_emscripten_stateful_exports(mod_))
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(instrs)) = func
let (snapshot_instrs, snapshot_changed) = match stack_alloc_def {
Some(target) if target == code_idx =>
match
o4z_canonicalize_final_size_restore_emscripten_stack_alloc(instrs) {
Some(next) => (next, true)
None => (instrs, false)
}
_ => (instrs, false)
}
let (else_instrs, else_changed) = o4z_canonicalize_final_size_empty_void_else_arms(
snapshot_instrs,
)
let (spill_instrs, spill_changed) = match
o4z_canonicalize_final_size_function_result_block_spill(else_instrs) {
Some(next) => (next, true)
None => (else_instrs, false)
}
let (exit_instrs, exit_changed) = match
o4z_canonicalize_final_size_nested_f64_pair_function_exit(
spill_instrs,
) {
Some(next) => (next, true)
None =>
match
o4z_canonicalize_final_size_taken_i32_pair_function_exit(
spill_instrs,
) {
Some(next) => (next, true)
None => (spill_instrs, false)
}
}
let (block_instrs, block_changed) = run_hot_pipeline_raw_vacuum_flatten_unbranched_result_blocks(
exit_instrs, true, false,
)
let (array_instrs, array_changed) = o4z_canonicalize_final_size_fresh_packed_array_gets(
block_instrs, module_ctx,
)
let (folded_instrs, first_fold_changed) = o4z_canonicalize_final_size_sign_extensions(
array_instrs, allow_dropped_integer_shells,
)
let (folded_instrs, second_fold_changed) = o4z_canonicalize_final_size_sign_extensions(
folded_instrs, allow_dropped_integer_shells,
)
let fold_changed = first_fold_changed || second_fold_changed
let (fence_instrs, fence_changed) = if remove_unobservable_fences {
o4z_canonicalize_final_size_remove_unobservable_fences(folded_instrs)
} else {
(folded_instrs, false)
}
let (struct_instrs, struct_changed) = o4z_canonicalize_final_size_nullable_struct_defaults(
fence_instrs, module_ctx,
)
let (add_instrs, add_changed) = o4z_canonicalize_final_size_split_local_add_constants(
struct_instrs,
)
let (i31_instrs, i31_changed) = o4z_canonicalize_final_size_i31_roundtrips(
add_instrs,
)
let (leb_instrs, leb_changed) = o4z_canonicalize_final_size_negative_leb_adds(
i31_instrs,
)
let (tee_instrs, tee_changed) = o4z_canonicalize_final_size_dead_local_tees(
leb_instrs,
)
let (init_instrs, init_changed) = o4z_canonicalize_final_size_overwritten_ref_func_initializer(
tee_instrs,
)
let (compare_instrs, compare_changed) = o4z_canonicalize_final_size_fold_same_local_integer_compares(
init_instrs,
)
let (branch_instrs, branch_changed) = o4z_canonicalize_final_size_strip_dead_after_br(
compare_instrs,
)
let (carry_instrs, carry_changed) = o4z_canonicalize_final_size_carry_repeated_local_across_i32_store(
branch_instrs,
)
let (next_instrs, return_changed) = match carry_instrs.last() {
Some(@lib.Return) => {
let without_return : Array[@lib.Instruction] = Array::new(
capacity=carry_instrs.length() - 1,
)
for idx = 0; idx + 1 < carry_instrs.length(); idx = idx + 1 {
without_return.push(carry_instrs[idx])
}
(without_return, true)
}
_ => (carry_instrs, false)
}
let (next_instrs, nop_changed) = if next_instrs ==
[@lib.Instruction::nop()] {
([], true)
} else {
(next_instrs, false)
}
let remove_all_body_locals = locals.length() > 0 &&
!o4z_canonicalize_final_size_instrs_have_local_access(next_instrs)
let next_locals = if remove_all_body_locals {
@lib.Locals::empty()
} else {
locals
}
if snapshot_changed ||
else_changed ||
spill_changed ||
exit_changed ||
block_changed ||
array_changed ||
fold_changed ||
fence_changed ||
struct_changed ||
add_changed ||
i31_changed ||
leb_changed ||
tee_changed ||
init_changed ||
compare_changed ||
branch_changed ||
carry_changed ||
return_changed ||
nop_changed ||
remove_all_body_locals {
next_funcs.push(
@lib.Func::new(next_locals, @lib.Expr::new(next_instrs)),
)
if return_changed {
return_candidates.push(
(
@lib.FuncIdx::new(
(imported_funcs + code_idx).reinterpret_as_uint(),
),
next_funcs[code_idx],
),
)
}
changed = true
} else {
next_funcs.push(func)
}
}
// A return discards ambient stack values; implicit function fallthrough
// requires exactly the result values. Check all removals with one shared
// validation environment, and retain the return when it must unwind.
if !return_candidates.is_empty() {
let failures = match
@validate.validate_defined_funcs_against_module(
mod_, return_candidates,
) {
Ok(failures) => failures
Err(error) =>
abort("cannot validate final-return candidates: \{error}")
}
for failure in failures {
let (func_idx, _) = failure
let @lib.FuncIdx(raw) = func_idx
let code_idx = raw.reinterpret_as_int() - imported_funcs
let @lib.Func(locals, @lib.Expr(body)) = next_funcs[code_idx]
let restored = body.copy()
restored.push(@lib.Instruction::return_())
next_funcs[code_idx] = @lib.Func::new(
locals,
@lib.Expr::new(restored),
)
}
}
if changed {
mod_.with_code_sec(@lib.CodeSec::new(next_funcs))
} else {
mod_
}
}
None => mod_
}
o4z_outline_similar_size_functions(
o4z_canonicalize_final_size_deduplicate_declarative_elems(
o4z_canonicalize_final_size_strip_type_only_empty_module(
o4z_canonicalize_final_size_strip_branch_hints(
o4z_canonicalize_final_size_remove_constant_assert_calls(
o4z_canonicalize_final_size_resolve_ref_non_null_wrappers(
o4z_canonicalize_final_size_remove_write_only_globals(
o4z_canonicalize_final_size_propagate_immutable_globals(
o4z_canonicalize_final_size_propagate_straight_global_values(
o4z_canonicalize_final_size_fold_constant_global_initializers(
canonical,
),
),
),
),
),
),
),
),
),
)
}
///|
pub fn o4z_canonicalize_final_size_candidate(mod_ : @lib.Module) -> @lib.Module {
// This size lane explicitly discards branch hints. Strip them before the
// offset guard, which must still protect every remaining opaque payload.
let without_hints = o4z_canonicalize_final_size_strip_branch_hints(mod_)
if pass_module_has_offset_sensitive_metadata(without_hints) {
return without_hints
}
o4z_canonicalize_final_size_candidate_impl(without_hints)
}
///|
pub fn o4z_canonicalize_final_size_candidate_with_changed(
mod_ : @lib.Module,
) -> (@lib.Module, Bool) {
let without_hints = o4z_canonicalize_final_size_strip_branch_hints(mod_)
if pass_module_has_offset_sensitive_metadata(without_hints) {
// Only the stripped metadata can have changed on this guarded path. These
// fields contain no floating-point instructions or NaN equality hazards.
let changed = mod_.custom_secs != without_hints.custom_secs ||
mod_.func_annotation_sec != without_hints.func_annotation_sec
return (without_hints, changed)
}
let canonical = o4z_canonicalize_final_size_candidate_impl(without_hints)
let changed = match
(@binary.encode_module(mod_), @binary.encode_module(canonical)) {
(Ok(before), Ok(after)) => before != after
_ => true
}
(canonical, changed)
}
///|
fn run_hot_pipeline_raw_vacuum_finalize_precleaned_func(
mod_ : @lib.Module,
func_idx : @lib.FuncIdx,
module_state : HotPipelineModuleState,
options : HotPipelineOptions,
locals : @lib.Locals,
instrs : Array[@lib.Instruction],
) -> @lib.Func {
if !run_hot_pipeline_raw_vacuum_instrs_are_local_only_removable(instrs) {
return @lib.Func::new(locals, @lib.Expr::new(instrs))
}
let module_ctx = run_hot_pipeline_require_module_ctx(
mod_, module_state, options,
)
match @ir.hot_module_resolve_func_type(module_ctx, func_idx) {
Some(@lib.FuncType(_, results)) if results.length() == 0 =>
@lib.Func::new(locals, @lib.Expr::new([@lib.Instruction::nop()]))
_ => @lib.Func::new(locals, @lib.Expr::new(instrs))
}
}
///|
fn run_hot_pipeline_raw_vacuum_has_negative_array_allocation_drop(
instrs : Array[@lib.Instruction],
) -> Bool {
for idx = 0; idx < instrs.length(); idx = idx + 1 {
if idx + 2 < instrs.length() {
match (instrs[idx], instrs[idx + 1], instrs[idx + 2]) {
(@lib.I32Const(@lib.I32(length)), @lib.ArrayNewDefault(_), @lib.Drop) if length <
0 => return true
_ => ()
}
}
if idx + 3 < instrs.length() {
match (instrs[idx + 1], instrs[idx + 2], instrs[idx + 3]) {
(@lib.I32Const(@lib.I32(length)), @lib.ArrayNew(_), @lib.Drop) if length <
0 => return true
_ => ()
}
}
match instrs[idx] {
@lib.Block(_, @lib.Expr(body))
| @lib.Loop(_, @lib.Expr(body))
| @lib.TryTable(_, _, @lib.Expr(body)) =>
if run_hot_pipeline_raw_vacuum_has_negative_array_allocation_drop(body) {
return true
}
@lib.If(_, then_body, else_body) => {
if run_hot_pipeline_raw_vacuum_has_negative_array_allocation_drop(
then_body,
) {
return true
}
match else_body {
Some(body) =>
if run_hot_pipeline_raw_vacuum_has_negative_array_allocation_drop(
body,
) {
return true
}
None => ()
}
}
_ => ()
}
}
false
}
///|
fn run_hot_pipeline_raw_vacuum_deep_singleton_call_leaf(
instrs : Array[@lib.Instruction],
) -> Array[@lib.Instruction]? {
let mut current = instrs
let mut depth = 0
while current.length() == 1 {
match current[0] {
@lib.Block(@lib.VoidBlockType, @lib.Expr(body)) => {
current = body
depth += 1
}
_ => break
}
}
// Binaryen flattens these transparent void wrappers. The exact leaf has no
// branch target or structured owner to rebase; every nearby shape stays on
// generic preclean, where the complete artifact-specific guards still run.
if depth < 64 {
return None
}
match current {
[@lib.I32Const(_), @lib.LocalSet(_), @lib.Call(_)] => Some(current)
_ => None
}
}
///|
fn run_hot_pipeline_raw_vacuum_preclean(
mod_ : @lib.Module,
func_idx : @lib.FuncIdx,
module_state : HotPipelineModuleState,
descriptor : @ir.HotPassDescriptor,
options : HotPipelineOptions,
) -> HotPipelineFuncResult? {
if descriptor.name != "vacuum" {
return None
}
let original = run_hot_pipeline_original_func(mod_, func_idx)
let @lib.Func(locals, @lib.Expr(instrs)) = original
match run_hot_pipeline_raw_vacuum_deep_singleton_call_leaf(instrs) {
Some(leaf) =>
return Some({
reason: "raw-vacuum-deep-singleton-call-leaf",
func: run_hot_pipeline_raw_vacuum_finalize_precleaned_func(
mod_, func_idx, module_state, options, locals, leaf,
),
unchanged_original: false,
trace_skip_raw: true,
})
None => ()
}
let has_dropped_typeidx_block = run_hot_pipeline_raw_vacuum_instrs_have_dropped_typeidx_block(
instrs,
)
let has_flattenable_no_throw_exception_region = run_hot_pipeline_raw_vacuum_instrs_have_flattenable_no_throw_exception_region(
instrs,
)
let has_legacy_try = run_hot_pipeline_raw_vacuum_instrs_have_legacy_try(
instrs,
)
if run_hot_pipeline_raw_vacuum_has_negative_array_allocation_drop(instrs) {
return Some({
reason: "negative-array-allocation-vacuum-noop",
func: original,
unchanged_original: true,
trace_skip_raw: true,
})
}
if run_hot_pipeline_raw_has_root_global_snapshot_return_hazard(instrs) {
return Some({
reason: "root-global-snapshot-return-vacuum-noop",
func: original,
unchanged_original: true,
trace_skip_raw: true,
})
}
if options.optimize_level == 0 && options.shrink_level == 0 {
let (flattened, changed) = run_hot_pipeline_raw_vacuum_flatten_unbranched_result_blocks(
instrs, false, false,
)
if changed {
let level_zero_module_ctx = if has_dropped_typeidx_block {
Some(run_hot_pipeline_require_module_ctx(mod_, module_state, options))
} else {
None
}
let (precleaned, _) = run_hot_pipeline_raw_vacuum_preclean_instrs_owned(
flattened, level_zero_module_ctx,
)
return Some({
reason: "raw-vacuum-unbranched-result-blocks",
func: run_hot_pipeline_raw_vacuum_finalize_precleaned_func(
mod_, func_idx, module_state, options, locals, precleaned,
),
unchanged_original: false,
trace_skip_raw: true,
})
}
}
let has_fresh_array_set = run_hot_pipeline_raw_vacuum_instrs_have_fresh_array_set_candidate(
instrs,
)
let has_dropped_ref_func_struct = run_hot_pipeline_raw_vacuum_instrs_have_dropped_ref_func_struct(
instrs,
)
let has_dropped_simple_struct = run_hot_pipeline_raw_vacuum_instrs_have_dropped_simple_struct(
instrs,
)
let module_ctx = if run_hot_pipeline_raw_vacuum_instrs_require_type_context(
instrs,
) ||
has_fresh_array_set ||
has_dropped_ref_func_struct ||
has_dropped_simple_struct {
Some(run_hot_pipeline_require_module_ctx(mod_, module_state, options))
} else {
None
}
let has_constant_result_if = [false]
let (has_writes, has_structured_control, has_branches, _, _, instr_count) = run_hot_pipeline_instr_scan(
instrs,
constant_result_if=Some(has_constant_result_if),
)
let has_stack_effect = run_hot_pipeline_raw_vacuum_instrs_have_stack_effect_hazard(
instrs,
)
let guarded_hot_hazard = has_writes && has_stack_effect
if guarded_hot_hazard {
let (rewritten, changed) = run_hot_pipeline_raw_vacuum_preclean_instrs(
instrs, module_ctx,
)
if !changed {
return Some({
reason: "raw-vacuum-guarded-hazard",
func: original,
unchanged_original: true,
trace_skip_raw: true,
})
}
let final_rewritten = if run_hot_pipeline_raw_vacuum_instrs_need_load_linked_second_preclean(
rewritten,
) {
let (rerun, _) = run_hot_pipeline_raw_vacuum_preclean_instrs(
rewritten, module_ctx,
)
rerun
} else {
rewritten
}
return Some({
reason: "raw-vacuum-preclean",
func: run_hot_pipeline_raw_vacuum_finalize_precleaned_func(
mod_, func_idx, module_state, options, locals, final_rewritten,
),
unchanged_original: false,
trace_skip_raw: true,
})
}
let has_dropped_local_tee = run_hot_pipeline_raw_vacuum_instrs_have_dropped_local_tee(
instrs,
)
if run_hot_pipeline_raw_vacuum_has_dense_nop_debris(instrs) ||
run_hot_pipeline_raw_vacuum_instrs_have_dropped_pure_simd_expr(instrs) ||
run_hot_pipeline_raw_vacuum_instrs_have_dropped_simd_replace_lane_load(
instrs,
) ||
run_hot_pipeline_raw_vacuum_instrs_have_terminal_owner_branch_candidate(
instrs,
) ||
(
has_dropped_local_tee &&
!run_hot_pipeline_raw_vacuum_instrs_are_local_only_removable(instrs)
) ||
run_hot_pipeline_raw_vacuum_instrs_have_const_nontrapping_int_divrem_drop(
instrs,
) ||
run_hot_pipeline_raw_vacuum_instrs_have_repeated_dropped_load(instrs) ||
has_dropped_typeidx_block ||
has_constant_result_if[0] ||
has_flattenable_no_throw_exception_region ||
has_legacy_try ||
has_fresh_array_set ||
has_dropped_ref_func_struct ||
has_dropped_simple_struct {
let (rewritten, changed) = run_hot_pipeline_raw_vacuum_preclean_instrs(
instrs, module_ctx,
)
if changed {
let final_rewritten = if run_hot_pipeline_raw_vacuum_instrs_need_load_linked_second_preclean(
rewritten,
) {
let (rerun, _) = run_hot_pipeline_raw_vacuum_preclean_instrs(
rewritten, module_ctx,
)
rerun
} else {
rewritten
}
return Some({
reason: "raw-vacuum-preclean",
func: run_hot_pipeline_raw_vacuum_finalize_precleaned_func(
mod_, func_idx, module_state, options, locals, final_rewritten,
),
unchanged_original: false,
trace_skip_raw: true,
})
}
}
let branchy_structured_writes = has_writes &&
has_structured_control &&
has_branches
let branchy_structured_nop_debris = branchy_structured_writes &&
run_hot_pipeline_raw_vacuum_count_nops_until(instrs, 1) >= 1
let has_terminal_debris = run_hot_pipeline_raw_vacuum_instrs_have_terminal_unreachable_debris(
instrs,
)
if instr_count <= 2048 &&
!branchy_structured_nop_debris &&
!has_terminal_debris {
return None
}
let (rewritten, changed) = run_hot_pipeline_raw_vacuum_preclean_instrs(
instrs, module_ctx,
)
if branchy_structured_nop_debris && !changed {
return Some({
reason: "raw-vacuum-structured-writes",
func: original,
unchanged_original: true,
trace_skip_raw: true,
})
}
if !changed {
return None
}
let final_rewritten = if instr_count <= 2048 ||
run_hot_pipeline_raw_vacuum_instrs_need_load_linked_second_preclean(
rewritten,
) {
let (rerun, _) = run_hot_pipeline_raw_vacuum_preclean_instrs(
rewritten, module_ctx,
)
rerun
} else {
rewritten
}
Some({
reason: "raw-vacuum-preclean",
func: run_hot_pipeline_raw_vacuum_finalize_precleaned_func(
mod_, func_idx, module_state, options, locals, final_rewritten,
),
unchanged_original: false,
trace_skip_raw: true,
})
}
///|
fn run_hot_pipeline_raw_vacuum_instrs_have_local_write(
instrs : Array[@lib.Instruction],
) -> Bool {
for instr in instrs {
match instr {
@lib.LocalSet(_) | @lib.LocalTee(_) => return true
@lib.Block(_, @lib.Expr(body))
| @lib.Loop(_, @lib.Expr(body))
| @lib.TryTable(_, _, @lib.Expr(body)) =>
if run_hot_pipeline_raw_vacuum_instrs_have_local_write(body) {
return true
}
@lib.If(_, then_body, else_body) => {
if run_hot_pipeline_raw_vacuum_instrs_have_local_write(then_body) {
return true
}
match else_body {
Some(body) =>
if run_hot_pipeline_raw_vacuum_instrs_have_local_write(body) {
return true
}
None => ()
}
}
@lib.Try(_, @lib.Expr(body), catches, _) => {
if run_hot_pipeline_raw_vacuum_instrs_have_local_write(body) {
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 run_hot_pipeline_raw_vacuum_instrs_have_local_write(catch_body) {
return true
}
}
}
_ => ()
}
}
false
}
///|
fn run_hot_pipeline_raw_vacuum_has_candidate_instrs(
instrs : Array[@lib.Instruction],
) -> Bool {
if run_hot_pipeline_raw_vacuum_instrs_have_local_write(instrs) &&
run_hot_pipeline_raw_vacuum_instrs_are_local_only_removable(instrs) {
return true
}
for idx = 0; idx < instrs.length(); idx = idx + 1 {
let instr = instrs[idx]
match instr {
@lib.Nop | @lib.Drop | @lib.Return => return true
@lib.ArraySet(set_type) if idx >= 4 =>
match (instrs[idx - 4], instrs[idx - 3], instrs[idx - 2]) {
(
@lib.I32Const(@lib.I32(length)),
@lib.ArrayNewDefault(new_type),
@lib.I32Const(@lib.I32(index)),
) if new_type == set_type && index >= 0 && index < length =>
return true
_ => ()
}
@lib.Block(block_type, @lib.Expr(body)) => {
match block_type {
@lib.VoidBlockType => return true
_ => ()
}
if body.length() == 1 {
match body[0] {
@lib.Unreachable => return true
_ => ()
}
}
if run_hot_pipeline_raw_vacuum_has_candidate_instrs(body) {
return true
}
}
@lib.Loop(_, @lib.Expr(body)) | @lib.TryTable(_, _, @lib.Expr(body)) =>
if run_hot_pipeline_raw_vacuum_has_candidate_instrs(body) {
return true
}
@lib.If(block_type, then_body, else_body) => {
match block_type {
@lib.VoidBlockType if idx > 0 =>
match instrs[idx - 1] {
@lib.I32Const(_) => return true
_ => ()
}
_ => ()
}
match (block_type, then_body.length(), else_body) {
(@lib.VoidBlockType, 0, None) => return true
(@lib.VoidBlockType, _, Some(body)) if body.is_empty() => return true
(@lib.VoidBlockType, 0, Some(body)) if body.length() > 0 =>
return true
_ => ()
}
if run_hot_pipeline_raw_vacuum_has_candidate_instrs(then_body) {
return true
}
match else_body {
Some(else_body) =>
if run_hot_pipeline_raw_vacuum_has_candidate_instrs(else_body) {
return true
}
None => ()
}
}
_ => ()
}
}
false
}
///|
fn run_hot_pipeline_raw_vacuum_no_candidates(
mod_ : @lib.Module,
func_idx : @lib.FuncIdx,
descriptor : @ir.HotPassDescriptor,
) -> HotPipelineFuncResult? {
if descriptor.name != "vacuum" {
return None
}
let original = run_hot_pipeline_original_func(mod_, func_idx)
let @lib.Func(_, @lib.Expr(instrs)) = original
if instrs.length() == 0 ||
run_hot_pipeline_raw_vacuum_has_candidate_instrs(instrs) {
return None
}
Some({
reason: "no-vacuum-candidates",
func: original,
unchanged_original: true,
trace_skip_raw: true,
})
}
///|
fn run_hot_pipeline_raw_remove_unused_names_scan_instr(
instr : @lib.Instruction,
) -> (Bool, Bool) {
match instr {
@lib.Br(_)
| @lib.BrIf(_)
| @lib.BrTable(_, _)
| @lib.BrOnNull(_)
| @lib.BrOnNonNull(_)
| @lib.BrOnCast(_, _, _, _)
| @lib.BrOnCastFail(_, _, _, _)
| @lib.BrOnCastDescEq(_, _, _)
| @lib.BrOnCastDescEqFail(_, _, _) => (false, true)
@lib.Loop(_, @lib.Expr(body)) => {
let (_, has_label_use) = run_hot_pipeline_raw_remove_unused_names_scan_instrs(
body,
)
(true, has_label_use)
}
@lib.Block(block_type, @lib.Expr([@lib.Block(child_type, @lib.Expr(body))])) => {
if block_type != child_type {
return (true, true)
}
let (_, has_label_use) = run_hot_pipeline_raw_remove_unused_names_scan_instrs(
body,
)
(true, has_label_use)
}
@lib.Block(_, @lib.Expr(body)) =>
run_hot_pipeline_raw_remove_unused_names_scan_instrs(body)
@lib.If(_, then_body, Some(else_body)) => {
let (then_candidate, then_label_use) = run_hot_pipeline_raw_remove_unused_names_scan_instrs(
then_body,
)
let (else_candidate, else_label_use) = run_hot_pipeline_raw_remove_unused_names_scan_instrs(
else_body,
)
(then_candidate || else_candidate, then_label_use || else_label_use)
}
@lib.If(_, then_body, None) =>
run_hot_pipeline_raw_remove_unused_names_scan_instrs(then_body)
@lib.TryTable(_, _, _) => (true, true)
_ => (false, false)
}
}
///|
fn run_hot_pipeline_raw_remove_unused_names_scan_instrs(
instrs : Array[@lib.Instruction],
) -> (Bool, Bool) {
let mut has_candidate = false
let mut has_label_use = false
for instr in instrs {
let (instr_candidate, instr_label_use) = run_hot_pipeline_raw_remove_unused_names_scan_instr(
instr,
)
has_candidate = has_candidate || instr_candidate
has_label_use = has_label_use || instr_label_use
if has_candidate && has_label_use {
return (true, true)
}
}
(has_candidate, has_label_use)
}
///|
fn run_hot_pipeline_raw_label_is_zero(label : @lib.LabelIdx) -> Bool {
let @lib.LabelIdx(raw) = label
raw == 0U
}
///|
fn run_hot_pipeline_raw_remove_unused_names_targets_immediate_label(
instr : @lib.Instruction,
) -> Bool {
match instr {
@lib.Br(label)
| @lib.BrIf(label)
| @lib.BrOnNull(label)
| @lib.BrOnNonNull(label)
| @lib.BrOnCast(label, _, _, _)
| @lib.BrOnCastFail(label, _, _, _)
| @lib.BrOnCastDescEq(label, _, _)
| @lib.BrOnCastDescEqFail(label, _, _) =>
run_hot_pipeline_raw_label_is_zero(label)
@lib.BrTable(labels, default_label) => {
if run_hot_pipeline_raw_label_is_zero(default_label) {
return true
}
labels.any(run_hot_pipeline_raw_label_is_zero)
}
_ => false
}
}
///|
fn run_hot_pipeline_raw_remove_unused_names_loop_has_immediate_self_target(
body : Array[@lib.Instruction],
) -> Bool {
body.any(run_hot_pipeline_raw_remove_unused_names_targets_immediate_label)
}
///|
fn run_hot_pipeline_raw_remove_unused_names_can_skip_used_loops(
instrs : Array[@lib.Instruction],
) -> Bool {
for instr in instrs {
match instr {
@lib.Block(_, @lib.Expr([@lib.Block(_, _)])) => return false
@lib.Block(_, @lib.Expr(body)) =>
if !run_hot_pipeline_raw_remove_unused_names_can_skip_used_loops(body) {
return false
}
@lib.Loop(block_type, @lib.Expr(body)) => {
let label_cannot_be_removed = match block_type {
@lib.TypeIdxBlockType(_) => true
_ =>
run_hot_pipeline_raw_remove_unused_names_loop_has_immediate_self_target(
body,
)
}
if !label_cannot_be_removed ||
!run_hot_pipeline_raw_remove_unused_names_can_skip_used_loops(body) {
return false
}
}
@lib.If(_, then_body, else_body) => {
if !run_hot_pipeline_raw_remove_unused_names_can_skip_used_loops(
then_body,
) {
return false
}
match else_body {
Some(body) =>
if !run_hot_pipeline_raw_remove_unused_names_can_skip_used_loops(
body,
) {
return false
}
None => ()
}
}
// Legacy EH and try_table label depths need the HOT label table. Keep
// those functions on the existing path even when an outer loop appears
// to have an immediate self-target.
@lib.Try(_, _, _, _) | @lib.TryTable(_, _, _) => return false
_ => ()
}
}
true
}
///|
fn run_hot_pipeline_raw_remove_unused_names_instr_is_branch_control(
instr : @lib.Instruction,
) -> Bool {
match instr {
@lib.Instruction::Br(_)
| @lib.Instruction::BrIf(_)
| @lib.Instruction::BrTable(_, _)
| @lib.Instruction::BrOnNull(_)
| @lib.Instruction::BrOnNonNull(_)
| @lib.Instruction::BrOnCast(_, _, _, _)
| @lib.Instruction::BrOnCastFail(_, _, _, _)
| @lib.Instruction::BrOnCastDescEq(_, _, _)
| @lib.Instruction::BrOnCastDescEqFail(_, _, _)
| @lib.Instruction::Return
| @lib.Instruction::ReturnCall(_)
| @lib.Instruction::ReturnCallIndirect(_, _)
| @lib.Instruction::ReturnCallRef(_) => true
_ => false
}
}
///|
fn run_hot_pipeline_raw_remove_unused_names_instrs_have_branch_control(
instrs : Array[@lib.Instruction],
) -> Bool {
for instr in instrs {
if run_hot_pipeline_raw_remove_unused_names_instr_is_branch_control(instr) {
return true
}
match instr {
@lib.Instruction::Block(_, @lib.Expr(body))
| @lib.Instruction::Loop(_, @lib.Expr(body)) =>
if run_hot_pipeline_raw_remove_unused_names_instrs_have_branch_control(
body,
) {
return true
}
@lib.Instruction::If(_, then_body, Some(else_body)) =>
if run_hot_pipeline_raw_remove_unused_names_instrs_have_branch_control(
then_body,
) ||
run_hot_pipeline_raw_remove_unused_names_instrs_have_branch_control(
else_body,
) {
return true
}
@lib.Instruction::If(_, then_body, None) =>
if run_hot_pipeline_raw_remove_unused_names_instrs_have_branch_control(
then_body,
) {
return true
}
@lib.Instruction::TryTable(_, _, @lib.Expr(body)) =>
if run_hot_pipeline_raw_remove_unused_names_instrs_have_branch_control(
body,
) {
return true
}
_ => ()
}
}
false
}
///|
fn run_hot_pipeline_raw_remove_unused_names_instrs_end_in_unreachable(
instrs : Array[@lib.Instruction],
) -> Bool {
if instrs.length() == 0 {
return false
}
match instrs[instrs.length() - 1] {
@lib.Instruction::Unreachable => true
_ => false
}
}
///|
fn run_hot_pipeline_raw_remove_unused_names_dropped_block_has_unreachable_tail(
instr : @lib.Instruction,
) -> Bool {
match instr {
@lib.Instruction::Block(_, @lib.Expr(body)) =>
run_hot_pipeline_raw_remove_unused_names_instrs_end_in_unreachable(body) &&
!run_hot_pipeline_raw_remove_unused_names_instrs_have_branch_control(body)
_ => false
}
}
///|
fn run_hot_pipeline_raw_remove_unused_names_void_block_can_inline_before_unreachable(
instr : @lib.Instruction,
) -> Bool {
match instr {
@lib.Instruction::Block(_, @lib.Expr(body)) =>
!run_hot_pipeline_raw_remove_unused_names_instrs_have_branch_control(body)
_ => false
}
}
///|
fn run_hot_pipeline_raw_instr_is_ref_null_noexn(
instr : @lib.Instruction,
) -> Bool {
match instr {
@lib.Instruction::RefNull(rt) =>
match rt.get_heap_type() {
@lib.HeapType::AbsHeapTypeHeapType(@lib.AbsHeapType::NoExnAbsHeapType) =>
true
@lib.HeapType::AbsHeapTypeHeapType(@lib.AbsHeapType::ExnAbsHeapType) =>
true
_ => false
}
_ => false
}
}
///|
fn run_hot_pipeline_raw_instr_is_local_only_null_throwref_prefix(
instr : @lib.Instruction,
) -> Bool {
match instr {
@lib.Instruction::I32Const(_)
| @lib.Instruction::I64Const(_)
| @lib.Instruction::F32Const(_)
| @lib.Instruction::F64Const(_)
| @lib.Instruction::LocalGet(_)
| @lib.Instruction::LocalSet(_)
| @lib.Instruction::LocalTee(_)
| @lib.Instruction::Drop => true
_ => false
}
}
///|
fn run_hot_pipeline_raw_try_table_is_local_only_null_throwref_trap(
body : Array[@lib.Instruction],
) -> Bool {
if body.length() < 2 {
return false
}
let last_is_throw = match body[body.length() - 1] {
@lib.Instruction::ThrowRef => true
_ => false
}
let prev_is_throw = match body[body.length() - 2] {
@lib.Instruction::ThrowRef => true
_ => false
}
let null_then_throw = run_hot_pipeline_raw_instr_is_ref_null_noexn(
body[body.length() - 2],
) &&
last_is_throw
let throw_then_null = prev_is_throw &&
run_hot_pipeline_raw_instr_is_ref_null_noexn(body[body.length() - 1])
if !null_then_throw && !throw_then_null {
return false
}
for idx = 0; idx < body.length() - 2; idx = idx + 1 {
if !run_hot_pipeline_raw_instr_is_local_only_null_throwref_prefix(body[idx]) {
return false
}
}
true
}
///|
fn run_hot_pipeline_raw_instr_is_self_branching_loop(
instr : @lib.Instruction,
) -> Bool {
match instr {
@lib.Instruction::Loop(_, @lib.Expr(body)) if body.length() > 0 =>
match body[body.length() - 1] {
@lib.Instruction::Br(@lib.LabelIdx(raw)) => raw == 0U
_ => false
}
_ => false
}
}
///|
fn run_hot_pipeline_raw_strip_local_debris_after_self_branching_loop(
out : Array[@lib.Instruction],
) -> Bool {
let mut end = out.length()
while end > 0 {
if run_hot_pipeline_raw_instr_is_local_only_null_throwref_prefix(
out[end - 1],
) {
end -= 1
} else {
break
}
}
if end == out.length() || end == 0 {
return false
}
if !run_hot_pipeline_raw_instr_is_self_branching_loop(out[end - 1]) {
return false
}
while out.length() > end {
ignore(out.pop())
}
true
}
///|
fn run_hot_pipeline_raw_collapse_null_throwref_try_table_debris(
instrs : Array[@lib.Instruction],
) -> (Array[@lib.Instruction], Bool) {
let out : Array[@lib.Instruction] = []
let mut changed = false
let mut i = 0
while i < instrs.length() {
if out.length() > 0 {
match out[out.length() - 1] {
@lib.Instruction::Unreachable => {
changed = true
break
}
_ => ()
}
}
let instr = instrs[i]
match instr {
@lib.Instruction::TryTable(_, _, @lib.Expr(body)) if run_hot_pipeline_raw_try_table_is_local_only_null_throwref_trap(
body,
) => {
if run_hot_pipeline_raw_strip_local_debris_after_self_branching_loop(
out,
) {
changed = true
}
out.push(@lib.Instruction::unreachable_())
changed = true
}
@lib.Instruction::Block(block_type, @lib.Expr(body)) => {
let (next_body, body_changed) = run_hot_pipeline_raw_collapse_null_throwref_try_table_debris(
body,
)
if body_changed {
changed = true
}
out.push(@lib.Instruction::block(block_type, @lib.Expr::new(next_body)))
}
@lib.Instruction::Loop(block_type, @lib.Expr(body)) => {
let (next_body, body_changed) = run_hot_pipeline_raw_collapse_null_throwref_try_table_debris(
body,
)
if body_changed {
changed = true
}
out.push(@lib.Instruction::loop_(block_type, @lib.Expr::new(next_body)))
}
@lib.Instruction::If(block_type, then_body, else_body) => {
let (next_then, then_changed) = run_hot_pipeline_raw_collapse_null_throwref_try_table_debris(
then_body,
)
let (next_else, else_changed) = match else_body {
Some(body) => {
let (body2, changed2) = run_hot_pipeline_raw_collapse_null_throwref_try_table_debris(
body,
)
(Some(body2), changed2)
}
None => (None, false)
}
if then_changed || else_changed {
changed = true
}
out.push(@lib.Instruction::if_(block_type, next_then, next_else))
}
@lib.Instruction::TryTable(block_type, catches, @lib.Expr(body)) => {
let (next_body, body_changed) = run_hot_pipeline_raw_collapse_null_throwref_try_table_debris(
body,
)
if body_changed {
changed = true
}
out.push(
@lib.Instruction::try_table(
block_type,
catches,
@lib.Expr::new(next_body),
),
)
}
@lib.Instruction::Unreachable => {
if run_hot_pipeline_raw_strip_local_debris_after_self_branching_loop(
out,
) {
changed = true
}
out.push(@lib.Instruction::unreachable_())
}
other => out.push(other)
}
i += 1
}
(out, changed)
}
///|
fn run_hot_pipeline_raw_remove_unused_names_collapse_trailing_unreachable_run(
instrs : Array[@lib.Instruction],
) -> Array[@lib.Instruction] {
let out : Array[@lib.Instruction] = []
let mut end = instrs.length()
while end > 0 {
match instrs[end - 1] {
@lib.Instruction::Unreachable => end -= 1
_ => break
}
}
if end == instrs.length() {
for instr in instrs {
out.push(instr)
}
return out
}
for idx = 0; idx < end; idx = idx + 1 {
out.push(instrs[idx])
}
out.push(@lib.Instruction::unreachable_())
out
}
///|
fn run_hot_pipeline_raw_remove_unused_names_remove_dropped_unreachable_debris(
instrs : Array[@lib.Instruction],
) -> (Array[@lib.Instruction], Bool) {
let out : Array[@lib.Instruction] = []
let mut changed = false
let mut i = 0
while i < instrs.length() {
match instrs[i] {
@lib.Instruction::Unreachable | @lib.Instruction::Block(_, _) => {
let mut j = i + 1
let mut saw_drop = false
while j < instrs.length() {
match instrs[j] {
@lib.Instruction::Drop => {
saw_drop = true
j += 1
}
_ => break
}
}
if j < instrs.length() {
match instrs[j] {
@lib.Instruction::Unreachable =>
match instrs[i] {
@lib.Instruction::Unreachable =>
if saw_drop {
changed = true
i = j
continue
}
@lib.Instruction::Block(_, @lib.Expr(body)) =>
if run_hot_pipeline_raw_remove_unused_names_dropped_block_has_unreachable_tail(
instrs[i],
) {
let collapsed = run_hot_pipeline_raw_remove_unused_names_collapse_trailing_unreachable_run(
body,
)
let (cleaned_body, _) = run_hot_pipeline_raw_remove_unused_names_remove_dropped_unreachable_debris(
collapsed,
)
for body_instr in cleaned_body {
out.push(body_instr)
}
changed = true
i = j + 1
continue
} else if run_hot_pipeline_raw_remove_unused_names_void_block_can_inline_before_unreachable(
instrs[i],
) {
let (cleaned_body, _) = run_hot_pipeline_raw_remove_unused_names_remove_dropped_unreachable_debris(
body,
)
for body_instr in cleaned_body {
out.push(body_instr)
}
changed = true
i += 1
continue
}
_ => ()
}
_ => ()
}
}
}
_ => ()
}
let rewritten_instr = match instrs[i] {
@lib.Instruction::Block(block_type, @lib.Expr(body)) => {
let (next_body, body_changed) = run_hot_pipeline_raw_remove_unused_names_remove_dropped_unreachable_debris(
body,
)
if body_changed {
changed = true
}
@lib.Instruction::block(block_type, @lib.Expr::new(next_body))
}
@lib.Instruction::Loop(block_type, @lib.Expr(body)) => {
let (next_body, body_changed) = run_hot_pipeline_raw_remove_unused_names_remove_dropped_unreachable_debris(
body,
)
if body_changed {
changed = true
}
@lib.Instruction::loop_(block_type, @lib.Expr::new(next_body))
}
@lib.Instruction::If(block_type, then_body, else_body) => {
let (next_then, then_changed) = run_hot_pipeline_raw_remove_unused_names_remove_dropped_unreachable_debris(
then_body,
)
let (next_else, else_changed) = match else_body {
Some(body) => {
let (body2, changed2) = run_hot_pipeline_raw_remove_unused_names_remove_dropped_unreachable_debris(
body,
)
(Some(body2), changed2)
}
None => (None, false)
}
if then_changed || else_changed {
changed = true
}
@lib.Instruction::if_(block_type, next_then, next_else)
}
@lib.Instruction::TryTable(block_type, catches, @lib.Expr(body)) => {
let (next_body, body_changed) = run_hot_pipeline_raw_remove_unused_names_remove_dropped_unreachable_debris(
body,
)
if body_changed {
changed = true
}
@lib.Instruction::try_table(
block_type,
catches,
@lib.Expr::new(next_body),
)
}
other => other
}
out.push(rewritten_instr)
i += 1
}
(out, changed)
}
///|
fn run_hot_pipeline_raw_remove_unused_names_strip_nops(
instrs : Array[@lib.Instruction],
) -> (Array[@lib.Instruction], Bool) {
let stripped : Array[@lib.Instruction] = []
let mut changed = false
for instr in instrs {
match instr {
@lib.Nop => changed = true
_ => stripped.push(instr)
}
}
(stripped, changed)
}
///|
fn run_hot_pipeline_raw_remove_unused_names_rewrite_single_root(
instrs : Array[@lib.Instruction],
) -> (Array[@lib.Instruction], Bool)? {
match instrs {
[
@lib.Block(
block_type,
@lib.Expr([@lib.Block(child_type, @lib.Expr([@lib.Nop]))])
),
] =>
if block_type == child_type {
Some(([@lib.Instruction::block(block_type, @lib.Expr::new([]))], true))
} else {
None
}
[@lib.Block(block_type, @lib.Expr([@lib.Block(child_type, @lib.Expr([]))]))] =>
if block_type == child_type {
Some(([@lib.Instruction::block(block_type, @lib.Expr::new([]))], true))
} else {
None
}
[@lib.Block(block_type, @lib.Expr(body))] => {
let mut next_body = body
let mut changed = false
let mut peeling = true
while peeling {
match next_body {
[@lib.Block(child_type, @lib.Expr(child_body))] =>
if child_type == block_type {
next_body = child_body
changed = true
} else {
peeling = false
}
_ => peeling = false
}
}
let (stripped_body, stripped) = run_hot_pipeline_raw_remove_unused_names_strip_nops(
next_body,
)
if changed || stripped {
Some(
(
[@lib.Instruction::block(block_type, @lib.Expr::new(stripped_body))],
true,
),
)
} else {
None
}
}
[@lib.Loop(block_type, @lib.Expr(body))] =>
match block_type {
@lib.TypeIdxBlockType(_) => None
_ => {
let (stripped_body, _) = run_hot_pipeline_raw_remove_unused_names_strip_nops(
body,
)
Some(
(
[
@lib.Instruction::block(
block_type,
@lib.Expr::new(stripped_body),
),
],
true,
),
)
}
}
_ => None
}
}
///|
fn run_hot_pipeline_raw_remove_unused_names_rewrite_instr(
instr : @lib.Instruction,
) -> (@lib.Instruction, Bool) {
match instr {
@lib.Block(block_type, @lib.Expr(body)) => {
let (rewritten_body, body_changed) = run_hot_pipeline_raw_remove_unused_names_rewrite_instrs(
body,
)
let mut next_body = rewritten_body
let mut changed = body_changed
let mut peeling = true
while peeling {
match next_body {
[@lib.Block(child_type, @lib.Expr(child_body))] =>
if child_type == block_type {
next_body = child_body
changed = true
} else {
peeling = false
}
_ => peeling = false
}
}
(@lib.Instruction::block(block_type, @lib.Expr::new(next_body)), changed)
}
@lib.Loop(block_type, @lib.Expr(body)) => {
let (rewritten_body, body_changed) = run_hot_pipeline_raw_remove_unused_names_rewrite_instrs(
body,
)
match block_type {
@lib.TypeIdxBlockType(_) =>
(
@lib.Instruction::loop_(block_type, @lib.Expr::new(rewritten_body)),
body_changed,
)
_ =>
(
@lib.Instruction::block(block_type, @lib.Expr::new(rewritten_body)),
true,
)
}
}
@lib.If(block_type, then_body, else_body) => {
let (rewritten_then, then_changed) = run_hot_pipeline_raw_remove_unused_names_rewrite_instrs(
then_body,
)
let (rewritten_else, else_changed) = match else_body {
Some(body) => {
let (rewritten, changed) = run_hot_pipeline_raw_remove_unused_names_rewrite_instrs(
body,
)
(Some(rewritten), changed)
}
None => (None, false)
}
(
@lib.Instruction::if_(block_type, rewritten_then, rewritten_else),
then_changed || else_changed,
)
}
_ => (instr, false)
}
}
///|
fn run_hot_pipeline_raw_remove_unused_names_rewrite_instrs(
instrs : Array[@lib.Instruction],
) -> (Array[@lib.Instruction], Bool) {
let rewritten : Array[@lib.Instruction] = []
let mut changed = false
for instr in instrs {
match instr {
@lib.Nop => {
changed = true
continue
}
_ => ()
}
let (rewritten_instr, instr_changed) = run_hot_pipeline_raw_remove_unused_names_rewrite_instr(
instr,
)
rewritten.push(rewritten_instr)
changed = changed || instr_changed
}
(rewritten, changed)
}
///|
fn run_hot_pipeline_raw_remove_unused_names(
mod_ : @lib.Module,
func_idx : @lib.FuncIdx,
descriptor : @ir.HotPassDescriptor,
options : HotPipelineOptions,
) -> HotPipelineFuncResult? {
if descriptor.name != "remove-unused-names" {
return None
}
let original = run_hot_pipeline_original_func(mod_, func_idx)
let @lib.Func(locals, @lib.Expr(instrs)) = original
// Continuation resume handlers can carry label depths, but HOT currently has
// no continuation representation that RemoveUnusedNames can update safely.
if precompute_raw_instrs_have_stack_switching(instrs) {
return Some({
reason: "stack-switching-remove-unused-names-noop",
func: original,
unchanged_original: true,
trace_skip_raw: true,
})
}
// Legacy delegates do have HOT label ownership. Avoid the raw debris and
// loop-demotion helpers, which intentionally do not rewrite legacy EH.
if precompute_raw_instrs_have_legacy_try(instrs) {
return None
}
if run_hot_pipeline_raw_simplify_locals_has_call_bulk_memory_effect_mesh(
instrs,
) {
return Some({
reason: "call-bulk-memory-effect-order-remove-unused-names-noop",
func: original,
unchanged_original: true,
trace_skip_raw: true,
})
}
let (has_candidate, has_label_use) = run_hot_pipeline_raw_remove_unused_names_scan_instrs(
instrs,
)
if !has_candidate || has_label_use {
let (cleaned, debris_changed) = run_hot_pipeline_raw_remove_unused_names_remove_dropped_unreachable_debris(
instrs,
)
if debris_changed {
return Some({
reason: "raw-remove-unused-names-unreachable-debris",
func: @lib.Func::new(locals, @lib.Expr::new(cleaned)),
unchanged_original: false,
trace_skip_raw: false,
})
}
if !has_candidate {
return Some({
reason: "no-remove-unused-names-candidates",
func: original,
unchanged_original: true,
trace_skip_raw: true,
})
}
if run_hot_pipeline_raw_remove_unused_names_can_skip_used_loops(instrs) {
return Some({
reason: "no-remove-unused-names-candidates",
func: original,
unchanged_original: true,
trace_skip_raw: true,
})
}
return None
}
let pass_timer_start = perf_start_timer_opt(
options.perf,
"pass:remove-unused-names",
)
let (rewritten_without_debris, rewrite_changed) = match
run_hot_pipeline_raw_remove_unused_names_rewrite_single_root(instrs) {
Some(result) => result
None => run_hot_pipeline_raw_remove_unused_names_rewrite_instrs(instrs)
}
let (rewritten, debris_changed) = run_hot_pipeline_raw_remove_unused_names_remove_dropped_unreachable_debris(
rewritten_without_debris,
)
let changed = rewrite_changed || debris_changed
perf_stop_timer_opt(
options.perf,
"pass:remove-unused-names",
pass_timer_start,
)
if !changed {
return None
}
Some({
reason: "raw-remove-unused-names-no-label-uses",
func: @lib.Func::new(locals, @lib.Expr::new(rewritten)),
unchanged_original: false,
trace_skip_raw: false,
})
}
///|
fn run_hot_pipeline_raw_remove_unused_brs_deep_dispatch_block_go(
instr : @lib.Instruction,
depth : Int32,
) -> Bool {
match instr {
@lib.Instruction::Block(_, @lib.Expr(body)) => {
let next_depth = depth + 1
if next_depth >= 12 {
let mut br_if_count = 0
for item in body {
match item {
@lib.Instruction::BrIf(_) => br_if_count += 1
_ => ()
}
}
if br_if_count >= 8 {
return true
}
}
if body.length() == 0 {
return false
}
run_hot_pipeline_raw_remove_unused_brs_deep_dispatch_block_go(
body[0],
next_depth,
)
}
_ => false
}
}
///|
fn run_hot_pipeline_raw_remove_unused_brs_deep_dispatch_block(
instr : @lib.Instruction,
) -> Bool {
run_hot_pipeline_raw_remove_unused_brs_deep_dispatch_block_go(instr, 0)
}
///|
fn run_hot_pipeline_raw_remove_unused_brs_has_deep_dispatch_block(
instrs : Array[@lib.Instruction],
) -> Bool {
instrs.any(fn(instr) {
run_hot_pipeline_raw_remove_unused_brs_deep_dispatch_block(instr)
})
}
///|
fn run_hot_pipeline_raw_remove_unused_brs_large_local_dispatch_stats(
locals_count : Int32,
instr_count : Int32,
has_deep_dispatch : Bool,
) -> Bool {
locals_count > 96 && instr_count > 1000 && has_deep_dispatch
}
///|
fn run_hot_pipeline_raw_remove_unused_brs_refcount_payload_stats(
optimize_level : Int32,
shrink_level : Int32,
locals_count : Int32,
shape : RawRemoveUnusedBrsShape,
) -> Bool {
optimize_level >= 4 &&
shrink_level >= 1 &&
locals_count >= 128 &&
shape.call_count >= 8 &&
shape.local_set_count >= 20 &&
shape.if_count >= 4
}
///|
priv struct RawRemoveUnusedBrsEarlyFacts {
mut has_br_table : Bool
mut has_giant_br_table : Bool
mut has_decision_ladder : Bool
}
///|
fn run_hot_pipeline_raw_remove_unused_brs_scan_early_facts(
instrs : Array[@lib.Instruction],
facts : RawRemoveUnusedBrsEarlyFacts,
) -> Unit {
if facts.has_giant_br_table && facts.has_decision_ladder {
return
}
for idx = 0; idx < instrs.length(); idx = idx + 1 {
if !facts.has_decision_ladder && idx + 3 < instrs.length() {
match instrs[idx] {
@lib.LocalGet(_) =>
match instrs[idx + 1] {
@lib.I32Const(_) =>
match instrs[idx + 2] {
@lib.I32Eq =>
match instrs[idx + 3] {
@lib.If(
@lib.ValTypeBlockType(result_type),
then_body,
Some(else_body)
) if result_type == @lib.ValType::i32() &&
then_body.length() == 1 &&
else_body.length() == 1 =>
match (then_body[0], else_body[0]) {
(@lib.I32Const(_), @lib.I32Const(_)) =>
facts.has_decision_ladder = true
_ => ()
}
_ => ()
}
_ => ()
}
_ => ()
}
_ => ()
}
}
match instrs[idx] {
@lib.BrTable(targets, _) => {
facts.has_br_table = true
if targets.length() >= 128 {
facts.has_giant_br_table = true
}
}
@lib.Block(_, @lib.Expr(body))
| @lib.Loop(_, @lib.Expr(body))
| @lib.TryTable(_, _, @lib.Expr(body)) =>
run_hot_pipeline_raw_remove_unused_brs_scan_early_facts(body, facts)
@lib.If(_, then_body, else_body) => {
run_hot_pipeline_raw_remove_unused_brs_scan_early_facts(
then_body, facts,
)
match else_body {
Some(else_body) =>
run_hot_pipeline_raw_remove_unused_brs_scan_early_facts(
else_body, facts,
)
None => ()
}
}
@lib.Try(_, @lib.Expr(body), catches, _) => {
run_hot_pipeline_raw_remove_unused_brs_scan_early_facts(body, facts)
for catch_ in catches {
let @lib.Expr(catch_body) = match catch_ {
@lib.LegacyCatch(_, body) | @lib.LegacyCatchAll(body) => body
}
run_hot_pipeline_raw_remove_unused_brs_scan_early_facts(
catch_body, facts,
)
}
}
_ => ()
}
if facts.has_giant_br_table && facts.has_decision_ladder {
return
}
}
}
///|
fn run_hot_pipeline_raw_remove_unused_brs_has_type_indexed_loop(
instrs : Array[@lib.Instruction],
) -> Bool {
for instr in instrs {
match instr {
@lib.Loop(@lib.TypeIdxBlockType(_), _) => return true
@lib.Block(_, @lib.Expr(body))
| @lib.Loop(_, @lib.Expr(body))
| @lib.TryTable(_, _, @lib.Expr(body)) =>
if run_hot_pipeline_raw_remove_unused_brs_has_type_indexed_loop(body) {
return true
}
@lib.If(_, then_body, else_body) => {
if run_hot_pipeline_raw_remove_unused_brs_has_type_indexed_loop(
then_body,
) {
return true
}
match else_body {
Some(else_body) =>
if run_hot_pipeline_raw_remove_unused_brs_has_type_indexed_loop(
else_body,
) {
return true
}
None => ()
}
}
_ => ()
}
}
false
}
///|
fn run_hot_pipeline_raw_remove_unused_brs_is_i32_comparison(
instr : @lib.Instruction,
) -> Bool {
match instr {
@lib.I32Eq
| @lib.I32Ne
| @lib.I32LtS
| @lib.I32LtU
| @lib.I32GtS
| @lib.I32GtU
| @lib.I32LeS
| @lib.I32LeU
| @lib.I32GeS
| @lib.I32GeU => true
_ => false
}
}
///|
fn run_hot_pipeline_raw_remove_unused_brs_has_comparison_flag_if_hazard(
instrs : Array[@lib.Instruction],
) -> Bool {
let mut idx = 0
while idx < instrs.length() {
if idx + 5 < instrs.length() {
match
(
instrs[idx],
instrs[idx + 1],
instrs[idx + 3],
instrs[idx + 4],
instrs[idx + 5],
) {
(
@lib.LocalGet(_),
@lib.I32Const(_),
@lib.LocalSet(set_local),
@lib.LocalGet(get_local),
@lib.If(_, _, _),
) if set_local == get_local &&
run_hot_pipeline_raw_remove_unused_brs_is_i32_comparison(
instrs[idx + 2],
) => return true
_ => ()
}
}
match instrs[idx] {
@lib.Block(_, @lib.Expr(body))
| @lib.Loop(_, @lib.Expr(body))
| @lib.TryTable(_, _, @lib.Expr(body)) =>
if run_hot_pipeline_raw_remove_unused_brs_has_comparison_flag_if_hazard(
body,
) {
return true
}
@lib.If(_, then_body, else_body) => {
if run_hot_pipeline_raw_remove_unused_brs_has_comparison_flag_if_hazard(
then_body,
) {
return true
}
match else_body {
Some(else_body) =>
if run_hot_pipeline_raw_remove_unused_brs_has_comparison_flag_if_hazard(
else_body,
) {
return true
}
None => ()
}
}
_ => ()
}
idx += 1
}
false
}
///|
fn run_hot_pipeline_raw_remove_unused_brs_has_root_stack_local_set_hazard(
instrs : Array[@lib.Instruction],
) -> Bool {
if instrs.length() <= 1 {
return false
}
let mut has_local_set = false
let mut has_stack_effect = false
for instr in instrs {
match instr {
@lib.LocalSet(_) => has_local_set = true
@lib.Call(_)
| @lib.CallIndirect(_, _)
| @lib.CallRef(_)
| @lib.MemoryCopy(_, _)
| @lib.MemoryFill(_) => has_stack_effect = true
_ => ()
}
if has_local_set && has_stack_effect {
return true
}
}
false
}
///|
fn run_hot_pipeline_raw_remove_unused_brs_has_arithmetic_local_carrier(
instrs : Array[@lib.Instruction],
) -> Bool {
for idx = 0; idx + 4 < instrs.length(); idx = idx + 1 {
match
(
instrs[idx],
instrs[idx + 1],
instrs[idx + 2],
instrs[idx + 3],
instrs[idx + 4],
) {
(
@lib.LocalGet(_),
@lib.I64Const(_),
@lib.I64DivU,
@lib.LocalSet(written),
@lib.LocalGet(read),
) if written == read => return true
_ => ()
}
}
for instr in instrs {
match instr {
@lib.Block(_, @lib.Expr(body))
| @lib.Loop(_, @lib.Expr(body))
| @lib.TryTable(_, _, @lib.Expr(body)) =>
if run_hot_pipeline_raw_remove_unused_brs_has_arithmetic_local_carrier(
body,
) {
return true
}
@lib.If(_, then_body, else_body) => {
if run_hot_pipeline_raw_remove_unused_brs_has_arithmetic_local_carrier(
then_body,
) {
return true
}
match else_body {
Some(body) =>
if run_hot_pipeline_raw_remove_unused_brs_has_arithmetic_local_carrier(
body,
) {
return true
}
None => ()
}
}
_ => ()
}
}
false
}
///|
fn run_hot_pipeline_raw_remove_unused_brs_has_stack_carried_accumulator_copy(
instrs : Array[@lib.Instruction],
) -> Bool {
for idx = 0; idx + 5 < instrs.length(); idx = idx + 1 {
match
(
instrs[idx],
instrs[idx + 1],
instrs[idx + 2],
instrs[idx + 3],
instrs[idx + 4],
instrs[idx + 5],
) {
(
@lib.LocalGet(first),
@lib.LocalGet(second),
@lib.I64Add,
@lib.LocalGet(copy_source),
@lib.LocalSet(copy_target),
@lib.LocalSet(sum_target),
) if first == copy_target &&
second == copy_source &&
second == sum_target &&
first != second => return true
_ => ()
}
}
for instr in instrs {
match instr {
@lib.Block(_, @lib.Expr(body))
| @lib.Loop(_, @lib.Expr(body))
| @lib.TryTable(_, _, @lib.Expr(body)) =>
if run_hot_pipeline_raw_remove_unused_brs_has_stack_carried_accumulator_copy(
body,
) {
return true
}
@lib.If(_, then_body, else_body) => {
if run_hot_pipeline_raw_remove_unused_brs_has_stack_carried_accumulator_copy(
then_body,
) {
return true
}
match else_body {
Some(body) =>
if run_hot_pipeline_raw_remove_unused_brs_has_stack_carried_accumulator_copy(
body,
) {
return true
}
None => ()
}
}
_ => ()
}
}
false
}
///|
fn run_hot_pipeline_raw_remove_unused_brs_has_loaded_comparison_branch(
instrs : Array[@lib.Instruction],
) -> Bool {
for idx = 0; idx + 6 < instrs.length(); idx = idx + 1 {
if run_hot_pipeline_optimize_instructions_instr_is_plain_load(instrs[idx]) {
match
(
instrs[idx + 1],
instrs[idx + 2],
instrs[idx + 3],
instrs[idx + 4],
instrs[idx + 5],
instrs[idx + 6],
) {
(
@lib.LocalSet(written),
@lib.LocalGet(read),
@lib.I32Const(_),
@lib.I32Eq
| @lib.I32Ne,
@lib.BrIf(_),
@lib.Br(_),
) if written == read => return true
_ => ()
}
}
}
for instr in instrs {
match instr {
@lib.Block(_, @lib.Expr(body))
| @lib.Loop(_, @lib.Expr(body))
| @lib.TryTable(_, _, @lib.Expr(body)) =>
if run_hot_pipeline_raw_remove_unused_brs_has_loaded_comparison_branch(
body,
) {
return true
}
@lib.If(_, then_body, else_body) => {
if run_hot_pipeline_raw_remove_unused_brs_has_loaded_comparison_branch(
then_body,
) {
return true
}
match else_body {
Some(body) =>
if run_hot_pipeline_raw_remove_unused_brs_has_loaded_comparison_branch(
body,
) {
return true
}
None => ()
}
}
_ => ()
}
}
false
}
///|
fn run_hot_pipeline_raw_remove_unused_brs_has_call_argument_release_order(
instrs : Array[@lib.Instruction],
) -> Bool {
for idx = 0; idx + 4 < instrs.length(); idx = idx + 1 {
match
(
instrs[idx],
instrs[idx + 1],
instrs[idx + 2],
instrs[idx + 3],
instrs[idx + 4],
) {
(
@lib.LocalGet(first_arg),
@lib.LocalGet(second_arg),
@lib.Call(consumer),
@lib.LocalGet(released),
@lib.Call(release),
) if consumer != release &&
(released == first_arg || released == second_arg) => return true
_ => ()
}
}
for instr in instrs {
match instr {
@lib.Block(_, @lib.Expr(body))
| @lib.Loop(_, @lib.Expr(body))
| @lib.TryTable(_, _, @lib.Expr(body)) =>
if run_hot_pipeline_raw_remove_unused_brs_has_call_argument_release_order(
body,
) {
return true
}
@lib.If(_, then_body, else_body) => {
if run_hot_pipeline_raw_remove_unused_brs_has_call_argument_release_order(
then_body,
) {
return true
}
match else_body {
Some(body) =>
if run_hot_pipeline_raw_remove_unused_brs_has_call_argument_release_order(
body,
) {
return true
}
None => ()
}
}
_ => ()
}
}
false
}
///|
fn run_hot_pipeline_raw_remove_unused_brs_body_has_call_tee_release(
body : Array[@lib.Instruction],
) -> Bool {
for call_idx = 0; call_idx + 1 < body.length(); call_idx = call_idx + 1 {
let target = match (body[call_idx], body[call_idx + 1]) {
(@lib.Call(_), @lib.LocalTee(target)) => Some(target)
_ => None
}
match target {
Some(target) =>
for scan = call_idx + 2; scan + 1 < body.length(); scan = scan + 1 {
match body[scan] {
@lib.LocalSet(written) | @lib.LocalTee(written) if written == target =>
break
@lib.LocalGet(read) if read == target =>
match body[scan + 1] {
@lib.Call(_) => return true
_ => ()
}
_ => ()
}
}
None => ()
}
}
false
}
///|
fn run_hot_pipeline_raw_remove_unused_brs_has_condition_child_call_tee_lifetime(
instrs : Array[@lib.Instruction],
) -> Bool {
for idx = 0; idx + 1 < instrs.length(); idx = idx + 1 {
match (instrs[idx], instrs[idx + 1]) {
(
@lib.If(@lib.ValTypeBlockType(_), _, Some(_)),
@lib.If(@lib.ValTypeBlockType(_), then_body, Some(else_body)),
) if run_hot_pipeline_raw_remove_unused_brs_body_has_call_tee_release(
then_body,
) ||
run_hot_pipeline_raw_remove_unused_brs_body_has_call_tee_release(
else_body,
) => return true
_ => ()
}
}
for instr in instrs {
match instr {
@lib.Block(_, @lib.Expr(body))
| @lib.Loop(_, @lib.Expr(body))
| @lib.TryTable(_, _, @lib.Expr(body)) =>
if run_hot_pipeline_raw_remove_unused_brs_has_condition_child_call_tee_lifetime(
body,
) {
return true
}
@lib.If(_, then_body, else_body) => {
if run_hot_pipeline_raw_remove_unused_brs_has_condition_child_call_tee_lifetime(
then_body,
) {
return true
}
match else_body {
Some(body) =>
if run_hot_pipeline_raw_remove_unused_brs_has_condition_child_call_tee_lifetime(
body,
) {
return true
}
None => ()
}
}
_ => ()
}
}
false
}
///|
fn run_hot_pipeline_raw_remove_unused_brs_has_structured_same_local_distinct_call_order(
instrs : Array[@lib.Instruction],
) -> Bool {
for idx = 0; idx + 4 < instrs.length(); idx = idx + 1 {
let has_structured_prefix = match instrs[idx] {
@lib.Block(_, _)
| @lib.Loop(_, _)
| @lib.If(_, _, _)
| @lib.TryTable(_, _, _)
| @lib.Try(_, _, _, _) => true
_ => false
}
if has_structured_prefix {
match
(instrs[idx + 1], instrs[idx + 2], instrs[idx + 3], instrs[idx + 4]) {
(
@lib.LocalGet(first_local),
@lib.Call(first_target),
@lib.LocalGet(second_local),
@lib.Call(second_target),
) if first_local == second_local && first_target != second_target =>
return true
_ => ()
}
}
}
for instr in instrs {
match instr {
@lib.Block(_, @lib.Expr(body))
| @lib.Loop(_, @lib.Expr(body))
| @lib.TryTable(_, _, @lib.Expr(body)) =>
if run_hot_pipeline_raw_remove_unused_brs_has_structured_same_local_distinct_call_order(
body,
) {
return true
}
@lib.If(_, then_body, else_body) => {
if run_hot_pipeline_raw_remove_unused_brs_has_structured_same_local_distinct_call_order(
then_body,
) {
return true
}
match else_body {
Some(body) =>
if run_hot_pipeline_raw_remove_unused_brs_has_structured_same_local_distinct_call_order(
body,
) {
return true
}
None => ()
}
}
@lib.Try(_, @lib.Expr(body), catches, _) => {
if run_hot_pipeline_raw_remove_unused_brs_has_structured_same_local_distinct_call_order(
body,
) {
return true
}
for catch_ in catches {
let @lib.Expr(catch_body) = match catch_ {
@lib.LegacyCatch(_, catch_body) | @lib.LegacyCatchAll(catch_body) =>
catch_body
}
if run_hot_pipeline_raw_remove_unused_brs_has_structured_same_local_distinct_call_order(
catch_body,
) {
return true
}
}
}
_ => ()
}
}
false
}
///|
fn run_hot_pipeline_raw_remove_unused_brs(
mod_ : @lib.Module,
func_idx : @lib.FuncIdx,
descriptor : @ir.HotPassDescriptor,
options : HotPipelineOptions,
) -> HotPipelineFuncResult? {
if descriptor.name != "remove-unused-brs" {
return None
}
let original = run_hot_pipeline_original_func(mod_, func_idx)
let @lib.Func(locals, @lib.Expr(instrs)) = original
if run_hot_pipeline_raw_remove_unused_brs_has_stack_carried_accumulator_copy(
instrs,
) {
return Some({
reason: "stack-carried-accumulator-copy-remove-unused-brs-noop",
func: original,
unchanged_original: true,
trace_skip_raw: true,
})
}
if run_hot_pipeline_raw_has_root_global_snapshot_return_hazard(instrs) {
return Some({
reason: "root-global-snapshot-return-remove-unused-brs-noop",
func: original,
unchanged_original: true,
trace_skip_raw: true,
})
}
if run_hot_pipeline_raw_has_root_global_snapshot_write_prefix(instrs) {
return Some({
reason: "root-global-snapshot-write-prefix-remove-unused-brs-noop",
func: original,
unchanged_original: true,
trace_skip_raw: true,
})
}
if run_hot_pipeline_raw_has_call_argument_direct_release(instrs) {
return Some({
reason: "call-argument-direct-release-remove-unused-brs-noop",
func: original,
unchanged_original: true,
trace_skip_raw: true,
})
}
if run_hot_pipeline_raw_remove_unused_brs_has_call_argument_release_order(
instrs,
) {
return Some({
reason: "call-argument-release-order-remove-unused-brs-noop",
func: original,
unchanged_original: true,
trace_skip_raw: true,
})
}
if run_hot_pipeline_raw_remove_unused_brs_has_condition_child_call_tee_lifetime(
instrs,
) {
return Some({
reason: "condition-child-call-tee-lifetime-remove-unused-brs-noop",
func: original,
unchanged_original: true,
trace_skip_raw: true,
})
}
if run_hot_pipeline_raw_remove_unused_brs_has_structured_same_local_distinct_call_order(
instrs,
) {
return Some({
reason: "structured-same-local-distinct-call-order-remove-unused-brs-noop",
func: original,
unchanged_original: true,
trace_skip_raw: true,
})
}
if locals.length() >= 128 &&
run_hot_pipeline_raw_remove_unused_brs_has_arithmetic_local_carrier(instrs) {
return Some({
reason: "large-arithmetic-local-carrier-remove-unused-brs-noop",
func: original,
unchanged_original: true,
trace_skip_raw: true,
})
}
if run_hot_pipeline_raw_remove_unused_brs_has_loaded_comparison_branch(instrs) {
return Some({
reason: "loaded-comparison-branch-remove-unused-brs-noop",
func: original,
unchanged_original: true,
trace_skip_raw: true,
})
}
if run_hot_pipeline_raw_remove_unused_brs_has_type_indexed_loop(instrs) {
return Some({
reason: "typed-loop-param-remove-unused-brs-noop",
func: original,
unchanged_original: true,
trace_skip_raw: true,
})
}
if run_hot_pipeline_raw_remove_unused_brs_has_comparison_flag_if_hazard(
instrs,
) {
return Some({
reason: "comparison-flag-if-remove-unused-brs-noop",
func: original,
unchanged_original: true,
trace_skip_raw: true,
})
}
if run_hot_pipeline_dce_raw_has_load_call_set_hazard(instrs) {
return Some({
reason: "load-call-set-remove-unused-brs-noop",
func: original,
unchanged_original: true,
trace_skip_raw: true,
})
}
if run_hot_pipeline_raw_has_load_before_same_local_call(instrs) {
return Some({
reason: "load-before-release-remove-unused-brs-noop",
func: original,
unchanged_original: true,
trace_skip_raw: true,
})
}
if run_hot_pipeline_raw_remove_unused_brs_has_root_stack_local_set_hazard(
instrs,
) {
return Some({
reason: "result-local-set-stack-hazard-noop",
func: original,
unchanged_original: true,
trace_skip_raw: true,
})
}
if run_hot_pipeline_dce_raw_has_loop_outer_branch(instrs) {
let shape = RawRemoveUnusedBrsShape::new()
run_hot_pipeline_raw_remove_unused_brs_scan_shape(instrs, shape)
if shape.call_count > 0 && shape.return_count == 0 {
return Some({
reason: "loop-outer-branch-remove-unused-brs-noop",
func: original,
unchanged_original: true,
trace_skip_raw: true,
})
}
}
if run_hot_pipeline_raw_can_skip_simd_parser_br_table_stack_hazard(
locals, instrs,
) {
return Some({
reason: "simd-parser-br-table-stack-hazard-noop",
func: original,
unchanged_original: true,
trace_skip_raw: true,
})
}
let early_facts : RawRemoveUnusedBrsEarlyFacts = {
has_br_table: false,
has_giant_br_table: false,
has_decision_ladder: false,
}
run_hot_pipeline_raw_remove_unused_brs_scan_early_facts(instrs, early_facts)
if early_facts.has_giant_br_table && !early_facts.has_decision_ladder {
return Some({
reason: "giant-br-table-convergence-noop",
func: original,
unchanged_original: true,
trace_skip_raw: true,
})
}
let (rewritten_instrs, rewrite_count, saw_br_table) = if early_facts.has_br_table &&
early_facts.has_decision_ladder {
run_hot_pipeline_raw_remove_unused_brs_rewrite_decision_ladder_instrs(
instrs,
)
} else {
(instrs, 0, early_facts.has_br_table)
}
if rewrite_count > 0 &&
saw_br_table &&
!run_hot_pipeline_raw_remove_unused_brs_has_hot_only_candidates(
rewritten_instrs,
) {
return Some({
reason: "decision-ladder-selects",
func: @lib.Func::new(locals, @lib.Expr::new(rewritten_instrs)),
unchanged_original: false,
trace_skip_raw: true,
})
}
if early_facts.has_giant_br_table {
return Some({
reason: "giant-br-table-convergence-noop",
func: original,
unchanged_original: true,
trace_skip_raw: true,
})
}
if run_hot_pipeline_raw_remove_unused_brs_can_skip_large_void_if_return_ladder(
locals, instrs,
) {
return Some({
reason: "large-void-if-return-ladder-noop",
func: original,
unchanged_original: true,
trace_skip_raw: true,
})
}
if run_hot_pipeline_raw_remove_unused_brs_can_skip_large_result_br_table_dispatch_ladder(
instrs,
) {
return Some({
reason: "large-result-br-table-dispatch-ladder-noop",
func: original,
unchanged_original: true,
trace_skip_raw: true,
})
}
if run_hot_pipeline_raw_remove_unused_brs_can_skip_large_value_if_branch_ladder(
locals, instrs,
) {
return Some({
reason: "large-value-if-branch-ladder-noop",
func: original,
unchanged_original: true,
trace_skip_raw: true,
})
}
if run_hot_pipeline_raw_remove_unused_brs_can_skip_large_drop_heavy_branch_ladder(
locals, instrs,
) &&
!run_hot_pipeline_raw_remove_unused_brs_has_condition_child_value_if_candidate(
instrs,
) &&
!run_hot_pipeline_raw_remove_unused_brs_has_prefix_guard_payload_branch_candidate(
instrs,
) {
return Some({
reason: "large-drop-heavy-branch-ladder-noop",
func: original,
unchanged_original: true,
trace_skip_raw: true,
})
}
if run_hot_pipeline_raw_remove_unused_brs_can_skip_moderate_control_ladder(
locals, instrs,
) {
return Some({
reason: "moderate-control-ladder-noop",
func: original,
unchanged_original: true,
trace_skip_raw: true,
})
}
if run_hot_pipeline_raw_remove_unused_brs_can_skip_large_typed_br_table_encoder_ladder(
locals, instrs,
) {
return Some({
reason: "large-typed-br-table-encoder-ladder-noop",
func: original,
unchanged_original: true,
trace_skip_raw: true,
})
}
if run_hot_pipeline_raw_remove_unused_brs_can_skip_unique_loop_select_return_ladder(
locals, instrs,
) &&
!run_hot_pipeline_raw_remove_unused_brs_has_prefix_guard_payload_branch_candidate(
instrs,
) {
return Some({
reason: "unique-loop-select-return-ladder-noop",
func: original,
unchanged_original: true,
trace_skip_raw: true,
})
}
if run_hot_pipeline_raw_remove_unused_brs_can_skip_structured_return_ladder(
locals, instrs,
) &&
!run_hot_pipeline_raw_remove_unused_brs_has_condition_child_value_if_candidate(
instrs,
) &&
!run_hot_pipeline_raw_remove_unused_brs_has_prefix_guard_payload_branch_candidate(
instrs,
) {
return Some({
reason: "structured-return-ladder-noop",
func: original,
unchanged_original: true,
trace_skip_raw: true,
})
}
let large_stats = OptimizeInstructionsLoweredStats::new()
run_hot_pipeline_optimize_instructions_collect_stats_body(instrs, large_stats)
if run_hot_pipeline_raw_remove_unused_brs_large_local_dispatch_stats(
locals.length(),
large_stats.instrs,
run_hot_pipeline_raw_remove_unused_brs_has_deep_dispatch_block(instrs),
) {
return Some({
reason: "large-local-dispatch-noop",
func: original,
unchanged_original: true,
trace_skip_raw: true,
})
}
if !options.validate_final_module &&
run_hot_pipeline_raw_remove_unused_brs_can_skip_unchecked_moderate_call_mesh(
locals, instrs,
) {
return Some({
reason: "unchecked-moderate-call-mesh-noop",
func: original,
unchanged_original: true,
trace_skip_raw: true,
})
}
let final_shape = RawRemoveUnusedBrsShape::new()
run_hot_pipeline_raw_remove_unused_brs_scan_shape(instrs, final_shape)
if run_hot_pipeline_raw_remove_unused_brs_refcount_payload_stats(
options.optimize_level,
options.shrink_level,
locals.length(),
final_shape,
) {
return Some({
reason: "refcount-payload-parser-noop",
func: original,
unchanged_original: true,
trace_skip_raw: true,
})
}
if run_hot_pipeline_raw_has_call_result_alias_later_call(instrs) {
return Some({
reason: "call-result-alias-release-remove-unused-brs-noop",
func: original,
unchanged_original: true,
trace_skip_raw: true,
})
}
if !early_facts.has_br_table &&
run_hot_pipeline_raw_precompute_has_call_result_local_reload(instrs) {
return Some({
reason: "call-result-local-reload-remove-unused-brs-noop",
func: original,
unchanged_original: true,
trace_skip_raw: true,
})
}
if run_hot_pipeline_instr_has_remove_unused_brs_candidate(instrs) {
None
} else {
Some({
reason: "no-remove-unused-brs-candidates",
func: original,
unchanged_original: true,
trace_skip_raw: true,
})
}
}
///|
fn run_hot_pipeline_simplify_locals_lowered_max_local_idx_in_instr(
instr : @lib.Instruction,
) -> Int32 {
match instr {
@lib.LocalGet(@lib.LocalIdx(raw))
| @lib.LocalSet(@lib.LocalIdx(raw))
| @lib.LocalTee(@lib.LocalIdx(raw)) => raw.reinterpret_as_int()
@lib.Block(_, @lib.Expr(body))
| @lib.Loop(_, @lib.Expr(body))
| @lib.TryTable(_, _, @lib.Expr(body)) =>
run_hot_pipeline_simplify_locals_lowered_max_local_idx_in_body(body)
@lib.If(_, then_, else_) => {
let then_max = run_hot_pipeline_simplify_locals_lowered_max_local_idx_in_body(
then_,
)
let else_max = match else_ {
Some(else_body) =>
run_hot_pipeline_simplify_locals_lowered_max_local_idx_in_body(
else_body,
)
None => -1
}
if then_max > else_max {
then_max
} else {
else_max
}
}
@lib.Try(_, @lib.Expr(body), catches, _) => {
let mut max_idx = run_hot_pipeline_simplify_locals_lowered_max_local_idx_in_body(
body,
)
for catch_ in catches {
let @lib.Expr(body) = match catch_ {
@lib.LegacyCatch(_, expr) | @lib.LegacyCatchAll(expr) => expr
}
max_idx = Int32::max(
max_idx,
run_hot_pipeline_simplify_locals_lowered_max_local_idx_in_body(body),
)
}
max_idx
}
_ => -1
}
}
///|
fn run_hot_pipeline_simplify_locals_lowered_max_local_idx_in_body(
body : Array[@lib.Instruction],
) -> Int32 {
let mut max_idx = -1
for instr in body {
let instr_max = run_hot_pipeline_simplify_locals_lowered_max_local_idx_in_instr(
instr,
)
if instr_max > max_idx {
max_idx = instr_max
}
}
max_idx
}
///|
fn sl_cleanup_record_branch(
targets : Array[Bool],
barrier : Int32,
label : @lib.LabelIdx,
) -> Unit {
let @lib.LabelIdx(raw) = label
let depth = raw.reinterpret_as_int()
if depth >= 0 && depth < targets.length() - barrier {
targets[targets.length() - 1 - depth] = true
}
}
///|
fn sl_cleanup_count_reachable_reads(
body : Array[@lib.Instruction],
counts : Array[Int32],
targets : Array[Bool],
barrier : Int32,
count_reads : Bool,
) -> Bool {
// Each frame records syntactic branches to one enclosing structured label.
// Keep scanning dead suffixes for those branches: the existing conservative
// fallthrough proof sees them too, but local reads there must not be counted.
let mut falls_through = true
for instr in body {
let reads = count_reads && falls_through
let next_falls_through = match instr {
@lib.LocalGet(@lib.LocalIdx(raw)) => {
let index = raw.reinterpret_as_int()
if reads && index >= 0 && index < counts.length() {
counts[index] += 1
}
true
}
@lib.Br(label) => {
sl_cleanup_record_branch(targets, barrier, label)
false
}
@lib.BrIf(label) => {
sl_cleanup_record_branch(targets, barrier, label)
true
}
@lib.BrTable(labels, default_label) => {
sl_cleanup_record_branch(targets, barrier, default_label)
for label in labels {
sl_cleanup_record_branch(targets, barrier, label)
}
false
}
@lib.Return
| @lib.ReturnCall(_)
| @lib.ReturnCallIndirect(_, _)
| @lib.ReturnCallRef(_)
| @lib.Throw(_)
| @lib.ThrowRef
| @lib.Unreachable => false
@lib.Block(_, @lib.Expr(nested))
| @lib.TryTable(_, _, @lib.Expr(nested)) => {
targets.push(false)
let nested_falls = sl_cleanup_count_reachable_reads(
nested, counts, targets, barrier, reads,
)
let exits_owner = targets.pop().unwrap()
nested_falls || exits_owner
}
@lib.Loop(_, @lib.Expr(nested)) => {
targets.push(false)
let nested_falls = sl_cleanup_count_reachable_reads(
nested, counts, targets, barrier, reads,
)
// Branches to a loop restart it instead of reaching the next sibling.
ignore(targets.pop())
nested_falls
}
@lib.If(_, then_body, else_body) => {
targets.push(false)
let then_falls = sl_cleanup_count_reachable_reads(
then_body, counts, targets, barrier, reads,
)
let else_falls = match else_body {
Some(nested) =>
sl_cleanup_count_reachable_reads(
nested, counts, targets, barrier, reads,
)
None => true
}
let exits_owner = targets.pop().unwrap()
then_falls || else_falls || exits_owner
}
@lib.Try(_, @lib.Expr(nested), catches, _) => {
// The existing branch-depth query intentionally treats legacy Try as
// opaque and its fallthrough as possible. Count each reachable handler
// body with a barrier so its labels cannot affect surrounding frames.
if reads {
ignore(
sl_cleanup_count_reachable_reads(
nested,
counts,
targets,
targets.length(),
true,
),
)
for catch_ in catches {
let @lib.Expr(nested) = match catch_ {
@lib.LegacyCatch(_, expr) | @lib.LegacyCatchAll(expr) => expr
}
ignore(
sl_cleanup_count_reachable_reads(
nested,
counts,
targets,
targets.length(),
true,
),
)
}
}
true
}
_ => true
}
falls_through = falls_through && next_falls_through
if !falls_through && targets.length() == barrier {
// No visible enclosing owner needs syntactic branch facts from the tail.
break
}
}
falls_through
}
///|
fn run_hot_pipeline_simplify_locals_lowered_count_local_gets_in_body(
body : Array[@lib.Instruction],
counts : Array[Int32],
) -> Unit {
ignore(sl_cleanup_count_reachable_reads(body, counts, [], 0, true))
}
///|
fn run_hot_pipeline_simplify_locals_lowered_is_simd_result_select(
body : Array[@lib.Instruction],
) -> Bool {
if body.length() < 3 ||
!(body[body.length() - 2] is @lib.Select(_)) ||
!(body[body.length() - 1] is @lib.I32x4ExtractLane(_)) {
return false
}
body.any(fn(instr) { instr == @lib.Instruction::v128_bitselect() })
}
///|
fn run_hot_pipeline_simplify_locals_lowered_sink_v128_const_set_once(
body : Array[@lib.Instruction],
) -> (Array[@lib.Instruction], Bool) {
if !run_hot_pipeline_simplify_locals_lowered_is_simd_result_select(body) {
return (body, false)
}
let max_local_idx = run_hot_pipeline_simplify_locals_lowered_max_local_idx_in_body(
body,
)
let get_counts = Array::make(max_local_idx + 1, 0)
run_hot_pipeline_simplify_locals_lowered_count_local_gets_in_body(
body, get_counts,
)
for set_idx = 1; set_idx < body.length(); set_idx = set_idx + 1 {
match (body[set_idx - 1], body[set_idx]) {
(@lib.V128Const(_) as producer, @lib.LocalSet(@lib.LocalIdx(target_raw))) => {
let target = target_raw.reinterpret_as_int()
if target < 0 ||
target >= get_counts.length() ||
get_counts[target] == 0 {
continue
}
let mut read_idx = set_idx + 1
let mut found_read = false
while read_idx < body.length() {
match body[read_idx] {
@lib.LocalGet(@lib.LocalIdx(raw)) if raw == target_raw => {
found_read = true
break
}
@lib.LocalSet(@lib.LocalIdx(raw))
| @lib.LocalTee(@lib.LocalIdx(raw)) if raw == target_raw => break
@lib.V128Const(_)
| @lib.LocalGet(_)
| @lib.LocalSet(_)
| @lib.LocalTee(_) => read_idx = read_idx + 1
_ => break
}
}
if !found_read {
continue
}
let rewritten : Array[@lib.Instruction] = []
for idx = 0; idx < body.length(); idx = idx + 1 {
if idx == set_idx - 1 || idx == set_idx {
continue
}
if idx == read_idx {
rewritten.push(producer)
if get_counts[target] > 1 {
rewritten.push(
@lib.Instruction::local_tee(@lib.LocalIdx::new(target_raw)),
)
}
} else {
rewritten.push(body[idx])
}
}
return (rewritten, true)
}
_ => ()
}
}
(body, false)
}
///|
fn run_hot_pipeline_simplify_locals_lowered_sink_v128_const_sets(
func : @lib.Func,
) -> @lib.Func {
let @lib.Func(locals, @lib.Expr(body)) = func
let mut current = body
let mut changed = false
for iteration = 0; iteration < 3; iteration = iteration + 1 {
let (rewritten, rewritten_once) = run_hot_pipeline_simplify_locals_lowered_sink_v128_const_set_once(
current,
)
if !rewritten_once {
break
}
current = rewritten
changed = true
}
if changed {
@lib.Func::new(locals, @lib.Expr::new(current))
} else {
func
}
}
///|
fn run_hot_pipeline_simplify_locals_lowered_split_wrapper_middle_is_safe(
body : Array[@lib.Instruction],
start : Int32,
end_ : Int32,
scratch_raw : UInt32,
source_local : Int32?,
) -> Bool {
let mut stack_depth = 0
for idx = start; idx < end_; idx = idx + 1 {
match body[idx] {
@lib.Nop => ()
@lib.LocalGet(@lib.LocalIdx(raw)) => {
if raw == scratch_raw {
return false
}
stack_depth = stack_depth + 1
}
@lib.I32Const(_)
| @lib.I64Const(_)
| @lib.F32Const(_)
| @lib.F64Const(_) => stack_depth = stack_depth + 1
@lib.LocalSet(@lib.LocalIdx(raw)) =>
if raw == scratch_raw {
return false
} else {
match source_local {
Some(source_local) =>
if raw.reinterpret_as_int() == source_local {
return false
}
None => ()
}
stack_depth = stack_depth - 1
if stack_depth < 0 {
return false
}
}
_ => return false
}
}
stack_depth == 0
}
///|
fn run_hot_pipeline_simplify_locals_lowered_rewrite_split_value_wrapper_body(
body : Array[@lib.Instruction],
) -> Array[@lib.Instruction]? {
if body.length() < 3 {
return None
}
let producer = body[0]
if !run_hot_pipeline_raw_simplify_locals_instr_is_dupable_copy_value(producer) {
return None
}
match (body[1], body[body.length() - 1]) {
(
@lib.LocalSet(@lib.LocalIdx(scratch_raw)),
@lib.LocalGet(@lib.LocalIdx(last_raw)),
) =>
if scratch_raw != last_raw {
None
} else {
let source_local = run_hot_pipeline_raw_simplify_locals_instr_source_local(
producer,
)
if !run_hot_pipeline_simplify_locals_lowered_split_wrapper_middle_is_safe(
body,
2,
body.length() - 1,
scratch_raw,
source_local,
) {
return None
}
let rewritten : Array[@lib.Instruction] = []
for idx = 2; idx + 1 < body.length(); idx = idx + 1 {
match body[idx] {
@lib.Nop => ()
instr => rewritten.push(instr)
}
}
rewritten.push(producer)
Some(rewritten)
}
_ => None
}
}
///|
fn run_hot_pipeline_simplify_locals_lowered_hoist_nops_across_dupable_values_with_changed(
instrs : Array[@lib.Instruction],
) -> (Array[@lib.Instruction], Bool) {
let rewritten : Array[@lib.Instruction] = []
let mut changed = false
let mut idx = 0
while idx < instrs.length() {
let mut end_ = idx
let mut saw_nop = false
let mut saw_dupable = false
let mut saw_dupable_before_nop = false
while end_ < instrs.length() {
match instrs[end_] {
@lib.Nop => {
saw_nop = true
saw_dupable_before_nop = saw_dupable_before_nop || saw_dupable
end_ = end_ + 1
}
instr =>
if run_hot_pipeline_raw_simplify_locals_instr_is_dupable_copy_value(
instr,
) {
saw_dupable = true
end_ = end_ + 1
} else {
break
}
}
}
if saw_nop && saw_dupable {
changed = changed || saw_dupable_before_nop
for run_idx = idx; run_idx < end_; run_idx = run_idx + 1 {
match instrs[run_idx] {
@lib.Nop => rewritten.push(instrs[run_idx])
_ => ()
}
}
for run_idx = idx; run_idx < end_; run_idx = run_idx + 1 {
match instrs[run_idx] {
instr =>
if run_hot_pipeline_raw_simplify_locals_instr_is_dupable_copy_value(
instr,
) {
rewritten.push(instr)
}
}
}
idx = end_
} else {
// A homogeneous run needs no reordering. Consume the whole scanned
// run so each suffix is not rediscovered at the next outer iteration.
// When the first instruction is a barrier, copy just that instruction.
let unchanged_end = if end_ > idx { end_ } else { idx + 1 }
for run_idx = idx; run_idx < unchanged_end; run_idx = run_idx + 1 {
rewritten.push(instrs[run_idx])
}
idx = unchanged_end
}
}
(rewritten, changed)
}
///|
fn run_hot_pipeline_simplify_locals_lowered_hoist_nops_across_dupable_values(
instrs : Array[@lib.Instruction],
) -> Array[@lib.Instruction] {
let (rewritten, _) = run_hot_pipeline_simplify_locals_lowered_hoist_nops_across_dupable_values_with_changed(
instrs,
)
rewritten
}
///|
fn run_hot_pipeline_simplify_locals_lowered_cleanup_instr(
instr : @lib.Instruction,
counts : Array[Int32],
) -> @lib.Instruction? {
match instr {
@lib.Nop => Some(instr)
@lib.LocalSet(local_idx) =>
match local_idx {
@lib.LocalIdx(raw) => {
let idx = raw.reinterpret_as_int()
if idx >= 0 && idx < counts.length() && counts[idx] == 0 {
Some(@lib.Instruction::drop())
} else {
Some(instr)
}
}
}
@lib.LocalTee(local_idx) =>
match local_idx {
@lib.LocalIdx(raw) => {
let idx = raw.reinterpret_as_int()
if idx >= 0 && idx < counts.length() && counts[idx] == 0 {
None
} else {
Some(instr)
}
}
}
@lib.Block(block_type, @lib.Expr(body)) => {
let cleaned_body = run_hot_pipeline_simplify_locals_lowered_cleanup_body(
body, counts,
)
if block_type is @lib.VoidBlockType &&
cleaned_body.all(simplify_locals_cleanup_is_inert_separator) {
return Some(@lib.Instruction::nop())
}
match
run_hot_pipeline_simplify_locals_lowered_rewrite_split_value_wrapper_body(
cleaned_body,
) {
Some(rewritten_body) =>
if rewritten_body.length() == 1 {
Some(rewritten_body[0])
} else {
Some(
@lib.Instruction::block(
block_type,
@lib.Expr::new(rewritten_body),
),
)
}
None =>
Some(
@lib.Instruction::block(block_type, @lib.Expr::new(cleaned_body)),
)
}
}
@lib.Loop(block_type, @lib.Expr(body)) =>
Some(
@lib.Instruction::loop_(
block_type,
@lib.Expr::new(
run_hot_pipeline_simplify_locals_lowered_cleanup_body(body, counts),
),
),
)
@lib.If(block_type, then_, else_) =>
Some(
@lib.Instruction::if_(
block_type,
run_hot_pipeline_simplify_locals_lowered_cleanup_body(then_, counts),
match else_ {
Some(else_body) =>
Some(
run_hot_pipeline_simplify_locals_lowered_cleanup_body(
else_body, counts,
),
)
None => None
},
),
)
@lib.TryTable(block_type, catches, @lib.Expr(body)) =>
Some(
@lib.Instruction::try_table(
block_type,
catches,
@lib.Expr::new(
run_hot_pipeline_simplify_locals_lowered_cleanup_body(body, counts),
),
),
)
_ => Some(instr)
}
}
///|
fn run_hot_pipeline_simplify_locals_lowered_collect_local_reads(
instrs : Array[@lib.Instruction],
reads : Array[Int32],
membership_probes~ : Array[Int32]? = None,
) -> Unit {
SlCleanupReadSet::new(reads).collect(instrs, membership_probes~)
}
///|
fn simplify_locals_cleanup_is_inert_separator(instr : @lib.Instruction) -> Bool {
match instr {
@lib.Nop => true
@lib.Block(@lib.VoidBlockType, @lib.Expr(body)) =>
body.all(simplify_locals_cleanup_is_inert_separator)
_ => false
}
}
///|
fn run_hot_pipeline_simplify_locals_lowered_drop_dead_adjacent_local_set_get_pairs_with_liveout(
instrs : Array[@lib.Instruction],
live_after : Array[Int32],
child_liveout_contexts~ : Array[Int32]? = None,
suffix_root_visits~ : Array[Int32]? = None,
) -> Array[@lib.Instruction] {
sl_cleanup_drop_dead_pairs_with_reads(
instrs,
SlCleanupReadSet::new(live_after),
child_liveout_contexts~,
suffix_root_visits~,
)
}
///|
fn sl_cleanup_drop_dead_pairs_with_reads(
instrs : Array[@lib.Instruction],
live_after : SlCleanupReadSet,
child_liveout_contexts~ : Array[Int32]? = None,
suffix_root_visits~ : Array[Int32]? = None,
future_read_visits~ : Array[Int32]? = None,
cleanup_root_visits~ : Array[Int32]? = None,
) -> Array[@lib.Instruction] {
sl_cleanup_drop_dead_pairs_with_facts(
instrs,
live_after,
child_liveout_contexts~,
suffix_root_visits~,
future_read_visits~,
cleanup_root_visits~,
).body
}
///|
#valtype
priv struct SlCleanupPairResult {
body : Array[@lib.Instruction]
reads : Array[Int32]
start : Int32
}
///|
fn SlCleanupPairResult::new(
body : Array[@lib.Instruction],
reads : Array[Int32],
start : Int32,
) -> SlCleanupPairResult {
{ body, reads, start, }
}
///|
fn SlCleanupPairResult::merge_original_reads(
self : SlCleanupPairResult,
into : SlCleanupReadSet,
original : Array[@lib.Instruction],
) -> Unit {
if self.start < 0 {
// Flat fast paths did not collect reads. Include opaque legacy handlers.
into.collect(original)
} else {
// The inherited prefix is already in `into`. Only the owned suffix holds
// new original reads; never infer liveness from the rewritten child body.
for idx = self.start; idx < self.reads.length(); idx = idx + 1 {
into.insert(self.reads[idx])
}
}
}
///|
fn sl_cleanup_drop_dead_pairs_with_facts(
instrs : Array[@lib.Instruction],
live_after : SlCleanupReadSet,
child_liveout_contexts~ : Array[Int32]? = None,
suffix_root_visits~ : Array[Int32]? = None,
future_read_visits~ : Array[Int32]? = None,
cleanup_root_visits~ : Array[Int32]? = None,
) -> SlCleanupPairResult {
let mut has_children = false
let mut has_root_writes = false
for instr in instrs {
if cleanup_root_visits is Some(visits) {
visits[0] += 1
}
match instr {
@lib.Block(_, _)
| @lib.Loop(_, _)
| @lib.TryTable(_, _, _)
| @lib.If(_, _, _) => has_children = true
@lib.LocalSet(_) | @lib.LocalTee(_) => has_root_writes = true
_ => ()
}
if has_children && has_root_writes {
break
}
}
if !has_children && !has_root_writes {
return SlCleanupPairResult::new(instrs, live_after.reads, -1)
}
let mut normalized = instrs
let read_start = if has_children { live_after.reads.length() } else { -1 }
let suffix_reads = if has_children { live_after.copy() } else { live_after }
// Children use the original continuation's reads. Accumulate those reads
// once in reverse order, preserving the original instruction order in the
// output. Flat bodies never need a child continuation set.
if has_children {
for idx = instrs.length() - 1; idx >= 0; idx = idx - 1 {
if cleanup_root_visits is Some(visits) {
visits[0] += 1
}
let instr = instrs[idx]
let child_live_after = match instr {
@lib.Block(_, _)
| @lib.Loop(_, _)
| @lib.TryTable(_, _, _)
| @lib.If(_, _, _) => {
match child_liveout_contexts {
Some(contexts) => contexts[0] += 1
None => ()
}
// A loop also observes reads from its own next iteration. Keep those
// separate from the continuation inherited by its siblings.
if instr is @lib.Loop(_, _) {
suffix_reads.copy()
} else {
suffix_reads
}
}
_ => live_after
}
let next = match instr {
@lib.Block(bt, @lib.Expr(body)) => {
let child = sl_cleanup_drop_dead_pairs_with_facts(
body, child_live_after,
)
child.merge_original_reads(suffix_reads, body)
let next_body = child.body
if physical_equal(next_body, body) {
instr
} else {
@lib.Instruction::block(bt, @lib.Expr::new(next_body))
}
}
@lib.Loop(bt, @lib.Expr(body)) => {
let self_read_start = child_live_after.reads.length()
child_live_after.collect(body)
let child = sl_cleanup_drop_dead_pairs_with_facts(
body, child_live_after,
)
// Self reads seed the loop's incoming context, so the returned delta
// excludes them. Merge the seed, preserving next-iteration liveness.
for read_idx = self_read_start
read_idx < child_live_after.reads.length()
read_idx = read_idx + 1 {
suffix_reads.insert(child_live_after.reads[read_idx])
}
let next_body = child.body
if physical_equal(next_body, body) {
instr
} else {
@lib.Instruction::loop_(bt, @lib.Expr::new(next_body))
}
}
@lib.TryTable(bt, catches, @lib.Expr(body)) => {
let child = sl_cleanup_drop_dead_pairs_with_facts(
body, child_live_after,
)
child.merge_original_reads(suffix_reads, body)
let next_body = child.body
if physical_equal(next_body, body) {
instr
} else {
@lib.Instruction::try_table(bt, catches, @lib.Expr::new(next_body))
}
}
@lib.If(bt, then_body, else_body) => {
let then_result = sl_cleanup_drop_dead_pairs_with_facts(
then_body, child_live_after,
)
let next_then = then_result.body
let mut next_else = else_body
if else_body is Some(body) {
// Both arms borrow the same original continuation. Do not merge
// either arm's reads until both recursive rewrites have finished.
let else_result = sl_cleanup_drop_dead_pairs_with_facts(
body, child_live_after,
)
then_result.merge_original_reads(suffix_reads, then_body)
else_result.merge_original_reads(suffix_reads, body)
if !physical_equal(else_result.body, body) {
next_else = Some(else_result.body)
}
} else {
then_result.merge_original_reads(suffix_reads, then_body)
}
if physical_equal(next_then, then_body) &&
physical_equal(next_else, else_body) {
instr
} else {
@lib.Instruction::if_(bt, next_then, next_else)
}
}
_ => {
suffix_reads.collect_instr(instr)
instr
}
}
if !physical_equal(next, instr) {
if physical_equal(normalized, instrs) {
normalized = instrs.copy()
}
normalized[idx] = next
}
match suffix_root_visits {
Some(visits) => visits[0] += 1
None => ()
}
}
}
// Child cleanup retains every root control shell, so normalization cannot
// introduce a root set/tee when admission found none. Keep child rewrites.
if !has_root_writes {
return SlCleanupPairResult::new(normalized, suffix_reads.reads, read_start)
}
// Borrow unchanged arrays. All callers treat these results as read-only;
// allocate an owned output only when a pair is actually changed.
let mut rewritten : Array[@lib.Instruction]? = None
let mut cursor = 0
let mut idx = 0
let mut future_queries = 0
let mut first_future_get = -1
let mut mixed_future_queries = false
let mut future_read_mask : Array[Bool]? = None
// Same-local root-get queries have disjoint search intervals: each stops at
// the next queried get or an earlier terminator/read. Their total is linear.
// Tees are not reads, so even same-local tee queries need the mixed path.
while idx < normalized.length() {
if cleanup_root_visits is Some(visits) {
visits[0] += 1
}
match normalized[idx] {
@lib.LocalTee(@lib.LocalIdx(raw)) => {
let tee_is_dropped = idx + 1 < normalized.length() &&
normalized[idx + 1] == @lib.Instruction::drop()
let mut get_idx = idx + 2
if tee_is_dropped {
while get_idx < normalized.length() &&
simplify_locals_cleanup_is_inert_separator(normalized[get_idx]) {
get_idx += 1
}
}
let dead_carrier = tee_is_dropped &&
get_idx + 1 < normalized.length() &&
normalized[get_idx] ==
@lib.Instruction::local_get(@lib.LocalIdx::new(raw)) &&
normalized[get_idx + 1] == @lib.Instruction::drop() &&
!live_after.contains(raw.reinterpret_as_int()) &&
({
mixed_future_queries = true
if future_queries < 8 {
future_queries += 1
} else if future_read_mask is None && normalized.length() >= 32 {
future_read_mask = Some(
sl_cleanup_build_future_read_mask(
normalized, future_read_visits,
),
)
}
!sl_cleanup_future_read_query(
normalized,
get_idx,
get_idx + 2,
raw.reinterpret_as_int(),
future_read_mask,
future_read_visits,
)
})
if dead_carrier {
let out = match rewritten {
Some(out) => out
None => {
let out : Array[@lib.Instruction] = []
rewritten = Some(out)
out
}
}
for kept = cursor; kept < idx; kept = kept + 1 {
out.push(normalized[kept])
}
out.push(@lib.Instruction::drop())
for kept = idx + 2; kept < get_idx; kept = kept + 1 {
out.push(normalized[kept])
}
idx = get_idx + 2
cursor = idx
} else if tee_is_dropped &&
!live_after.contains(raw.reinterpret_as_int()) &&
({
mixed_future_queries = true
if future_queries < 8 {
future_queries += 1
} else if future_read_mask is None && normalized.length() >= 32 {
future_read_mask = Some(
sl_cleanup_build_future_read_mask(
normalized, future_read_visits,
),
)
}
!sl_cleanup_future_read_query(
normalized,
idx,
idx + 2,
raw.reinterpret_as_int(),
future_read_mask,
future_read_visits,
)
}) {
let out = match rewritten {
Some(out) => out
None => {
let out : Array[@lib.Instruction] = []
rewritten = Some(out)
out
}
}
for kept = cursor; kept < idx; kept = kept + 1 {
out.push(normalized[kept])
}
out.push(@lib.Instruction::local_set(@lib.LocalIdx::new(raw)))
idx += 2
cursor = idx
} else {
idx += 1
}
}
@lib.LocalSet(@lib.LocalIdx(raw)) => {
let mut get_idx = idx + 1
while get_idx < normalized.length() &&
simplify_locals_cleanup_is_inert_separator(normalized[get_idx]) {
get_idx = get_idx + 1
}
let removable = if get_idx < normalized.length() {
match normalized[get_idx] {
@lib.LocalGet(@lib.LocalIdx(next_raw)) =>
raw == next_raw &&
!live_after.contains(raw.reinterpret_as_int()) &&
({
let local = raw.reinterpret_as_int()
if first_future_get < 0 {
first_future_get = local
} else if first_future_get != local {
mixed_future_queries = true
}
if future_queries < 8 {
future_queries += 1
} else if mixed_future_queries &&
future_read_mask is None &&
normalized.length() >= 32 {
future_read_mask = Some(
sl_cleanup_build_future_read_mask(
normalized, future_read_visits,
),
)
}
!sl_cleanup_future_read_query(
normalized,
get_idx,
get_idx + 1,
local,
future_read_mask,
future_read_visits,
)
})
_ => false
}
} else {
false
}
if removable {
let out = match rewritten {
Some(out) => out
None => {
let out : Array[@lib.Instruction] = []
rewritten = Some(out)
out
}
}
for kept = cursor; kept < idx; kept = kept + 1 {
out.push(normalized[kept])
}
if get_idx + 1 >= normalized.length() ||
normalized[get_idx + 1] != @lib.Instruction::drop() {
out.push(@lib.Instruction::nop())
}
for nop_idx = idx + 1; nop_idx < get_idx; nop_idx = nop_idx + 1 {
out.push(normalized[nop_idx])
}
idx = get_idx + 1
cursor = idx
} else {
idx += 1
}
}
_ => idx += 1
}
}
let output = match rewritten {
Some(out) => {
for kept = cursor; kept < normalized.length(); kept = kept + 1 {
out.push(normalized[kept])
}
out
}
None => normalized
}
SlCleanupPairResult::new(output, suffix_reads.reads, read_start)
}
///|
fn run_hot_pipeline_simplify_locals_lowered_drop_dead_adjacent_local_set_get_pairs(
instrs : Array[@lib.Instruction],
) -> Array[@lib.Instruction] {
run_hot_pipeline_simplify_locals_lowered_drop_dead_adjacent_local_set_get_pairs_with_liveout(
instrs,
[],
)
}
///|
fn run_hot_pipeline_slns_lowered_block_type_is_nonvoid(
block_type : @lib.BlockType,
) -> Bool {
match block_type {
@lib.VoidBlockType => false
_ => true
}
}
///|
fn run_hot_pipeline_slns_lowered_instrs_end_without_normal_value(
instrs : Array[@lib.Instruction],
) -> Bool {
if instrs.is_empty() {
return false
}
match instrs[instrs.length() - 1] {
@lib.Unreachable | @lib.Throw(_) | @lib.ThrowRef | @lib.Br(_) => true
_ => false
}
}
///|
fn run_hot_pipeline_slns_lowered_body_has_ref_func_local_drop_pair(
body : Array[@lib.Instruction],
) -> Bool {
for idx = 0; idx < body.length(); idx = idx + 1 {
if idx + 2 < body.length() {
match (body[idx], body[idx + 1], body[idx + 2]) {
(
@lib.RefFunc(_),
@lib.LocalSet(@lib.LocalIdx(set_raw)),
@lib.LocalGet(@lib.LocalIdx(get_raw)),
) if set_raw.reinterpret_as_int() == get_raw.reinterpret_as_int() =>
return true
_ => ()
}
}
match body[idx] {
@lib.Block(_, @lib.Expr(nested)) | @lib.Loop(_, @lib.Expr(nested)) =>
if run_hot_pipeline_slns_lowered_body_has_ref_func_local_drop_pair(
nested,
) {
return true
}
@lib.If(_, then_body, else_body) => {
if run_hot_pipeline_slns_lowered_body_has_ref_func_local_drop_pair(
then_body,
) {
return true
}
match else_body {
Some(body) =>
if run_hot_pipeline_slns_lowered_body_has_ref_func_local_drop_pair(
body,
) {
return true
}
None => ()
}
}
@lib.TryTable(_, _, @lib.Expr(nested)) =>
if run_hot_pipeline_slns_lowered_body_has_ref_func_local_drop_pair(
nested,
) {
return true
}
_ => ()
}
}
false
}
///|
fn run_hot_pipeline_slns_lowered_body_has_ref_func_local_call_ref_pair(
body : Array[@lib.Instruction],
) -> Bool {
for idx = 0; idx < body.length(); idx = idx + 1 {
if idx + 3 < body.length() {
match (body[idx], body[idx + 1], body[idx + 2], body[idx + 3]) {
(
@lib.RefFunc(_),
@lib.LocalSet(local_id),
@lib.LocalGet(get_local_id),
@lib.CallRef(_),
) if local_id == get_local_id => return true
(
@lib.RefFunc(_),
@lib.LocalSet(local_id),
@lib.LocalGet(get_local_id),
@lib.ReturnCallRef(_),
) if local_id == get_local_id => return true
_ => ()
}
}
match body[idx] {
@lib.Block(_, @lib.Expr(nested)) | @lib.Loop(_, @lib.Expr(nested)) =>
if run_hot_pipeline_slns_lowered_body_has_ref_func_local_call_ref_pair(
nested,
) {
return true
}
@lib.If(_, then_body, else_body) => {
if run_hot_pipeline_slns_lowered_body_has_ref_func_local_call_ref_pair(
then_body,
) {
return true
}
match else_body {
Some(body) =>
if run_hot_pipeline_slns_lowered_body_has_ref_func_local_call_ref_pair(
body,
) {
return true
}
None => ()
}
}
@lib.TryTable(_, _, @lib.Expr(nested)) =>
if run_hot_pipeline_slns_lowered_body_has_ref_func_local_call_ref_pair(
nested,
) {
return true
}
_ => ()
}
}
false
}
///|
fn run_hot_pipeline_slns_lowered_refinalization_context_from_original(
original_func : @lib.Func?,
) -> Bool {
match original_func {
Some(@lib.Func(_, @lib.Expr(body))) =>
run_hot_pipeline_slns_lowered_body_has_ref_func_local_drop_pair(body) ||
run_hot_pipeline_slns_lowered_body_has_ref_func_local_call_ref_pair(body)
None => false
}
}
///|
fn run_hot_pipeline_slns_lowered_try_table_catches_are_ref_payload(
catches : Array[@lib.Catch],
) -> Bool {
if catches.is_empty() {
return false
}
catches.all(fn(catch_) {
match catch_ {
@lib.CatchRef(_, _) | @lib.CatchAllRef(_) => true
_ => false
}
})
}
///|
fn run_hot_pipeline_slns_lowered_try_table_catches_are_tag_ref_payload(
catches : Array[@lib.Catch],
) -> Bool {
if catches.is_empty() {
return false
}
catches.all(fn(catch_) {
match catch_ {
@lib.CatchRef(_, _) => true
_ => false
}
})
}
///|
fn run_hot_pipeline_slns_lowered_try_table_catches_are_catch_all_ref(
catches : Array[@lib.Catch],
) -> Bool {
if catches.is_empty() {
return false
}
catches.all(fn(catch_) {
match catch_ {
@lib.CatchAllRef(_) => true
_ => false
}
})
}
///|
fn run_hot_pipeline_slns_lowered_try_table_result_can_canonicalize(
block_type : @lib.BlockType,
catches : Array[@lib.Catch],
body : Array[@lib.Instruction],
) -> Bool {
let has_nonvoid_result = run_hot_pipeline_slns_lowered_block_type_is_nonvoid(
block_type,
)
has_nonvoid_result &&
run_hot_pipeline_slns_lowered_try_table_catches_are_ref_payload(catches) &&
run_hot_pipeline_slns_lowered_instrs_end_without_normal_value(body)
}
///|
fn run_hot_pipeline_slns_lowered_canonicalize_no_normal_try_table_block_body(
body : Array[@lib.Instruction],
) -> Array[@lib.Instruction]? {
match body {
[@lib.TryTable(block_type, catches, @lib.Expr(try_body))] =>
if run_hot_pipeline_slns_lowered_try_table_result_can_canonicalize(
block_type, catches, try_body,
) {
Some([
@lib.Instruction::try_table(
@lib.BlockType::void_(),
catches,
@lib.Expr::new(try_body),
),
@lib.Instruction::unreachable_(),
])
} else {
None
}
_ => None
}
}
///|
fn run_hot_pipeline_slns_lowered_canonicalize_no_normal_try_table_if_arm_body(
body : Array[@lib.Instruction],
) -> Array[@lib.Instruction]? {
match body {
[@lib.TryTable(block_type, catches, @lib.Expr(try_body))] =>
if run_hot_pipeline_slns_lowered_block_type_is_nonvoid(block_type) &&
run_hot_pipeline_slns_lowered_try_table_catches_are_tag_ref_payload(
catches,
) &&
run_hot_pipeline_slns_lowered_instrs_end_without_normal_value(try_body) {
Some([
@lib.Instruction::try_table(
@lib.BlockType::void_(),
catches,
@lib.Expr::new(try_body),
),
@lib.Instruction::unreachable_(),
])
} else {
None
}
_ => None
}
}
///|
fn run_hot_pipeline_slns_lowered_no_normal_try_table_tag_ref_if_arm_body_can_canonicalize(
body : Array[@lib.Instruction],
) -> Bool {
match
run_hot_pipeline_slns_lowered_canonicalize_no_normal_try_table_if_arm_body(
body,
) {
Some(_) => true
None => false
}
}
///|
fn run_hot_pipeline_slns_lowered_nonnull_exn_block_type() -> @lib.BlockType {
@lib.BlockType::val_type(
@lib.ValType::ref_type(
@lib.RefType::new(false, @lib.HeapType::abs(@lib.AbsHeapType::exn())),
),
)
}
///|
fn run_hot_pipeline_slns_lowered_retarget_catch_all_ref_if_label_to_outer(
catches : Array[@lib.Catch],
) -> Array[@lib.Catch] {
catches.map(fn(catch_) {
match catch_ {
@lib.CatchAllRef(@lib.LabelIdx(raw)) if raw == 0U =>
@lib.Catch::all_ref(@lib.LabelIdx::new(1U))
_ => catch_
}
})
}
///|
fn run_hot_pipeline_slns_lowered_retarget_ref_payload_if_label_to_outer(
catches : Array[@lib.Catch],
) -> Array[@lib.Catch] {
catches.map(fn(catch_) {
match catch_ {
@lib.CatchAllRef(@lib.LabelIdx(raw)) if raw == 0U =>
@lib.Catch::all_ref(@lib.LabelIdx::new(1U))
@lib.CatchRef(tag_idx, @lib.LabelIdx(raw)) if raw == 0U =>
@lib.Catch::ref_(tag_idx, @lib.LabelIdx::new(1U))
_ => catch_
}
})
}
///|
fn run_hot_pipeline_slns_lowered_canonicalize_no_normal_try_table_catch_all_ref_if_arm_body_with_catches(
body : Array[@lib.Instruction],
rewrite_catches : (Array[@lib.Catch]) -> Array[@lib.Catch],
) -> Array[@lib.Instruction]? {
match body {
[@lib.TryTable(block_type, catches, @lib.Expr(try_body))] =>
if run_hot_pipeline_slns_lowered_block_type_is_nonvoid(block_type) &&
run_hot_pipeline_slns_lowered_try_table_catches_are_catch_all_ref(
catches,
) &&
run_hot_pipeline_slns_lowered_instrs_end_without_normal_value(try_body) {
Some([
@lib.Instruction::try_table(
@lib.BlockType::void_(),
rewrite_catches(catches),
@lib.Expr::new(try_body),
),
@lib.Instruction::unreachable_(),
])
} else {
None
}
_ => None
}
}
///|
fn run_hot_pipeline_slns_lowered_canonicalize_no_normal_try_table_catch_all_ref_if_arm_body(
body : Array[@lib.Instruction],
) -> Array[@lib.Instruction]? {
run_hot_pipeline_slns_lowered_canonicalize_no_normal_try_table_catch_all_ref_if_arm_body_with_catches(
body, run_hot_pipeline_slns_lowered_retarget_catch_all_ref_if_label_to_outer,
)
}
///|
fn run_hot_pipeline_slns_lowered_canonicalize_no_normal_try_table_catch_all_ref_if_arm_body_preserve_label(
body : Array[@lib.Instruction],
) -> Array[@lib.Instruction]? {
run_hot_pipeline_slns_lowered_canonicalize_no_normal_try_table_catch_all_ref_if_arm_body_with_catches(
body,
fn(catches) { catches },
)
}
///|
fn run_hot_pipeline_slns_lowered_no_normal_try_table_catch_all_ref_if_arm_body_can_canonicalize(
body : Array[@lib.Instruction],
) -> Bool {
match
run_hot_pipeline_slns_lowered_canonicalize_no_normal_try_table_catch_all_ref_if_arm_body(
body,
) {
Some(_) => true
None => false
}
}
///|
fn run_hot_pipeline_slns_lowered_canonicalize_no_normal_try_table_ref_payload_if_arm_body(
body : Array[@lib.Instruction],
) -> Array[@lib.Instruction]? {
match body {
[@lib.TryTable(block_type, catches, @lib.Expr(try_body))] =>
if run_hot_pipeline_slns_lowered_try_table_result_can_canonicalize(
block_type, catches, try_body,
) {
Some([
@lib.Instruction::try_table(
@lib.BlockType::void_(),
run_hot_pipeline_slns_lowered_retarget_ref_payload_if_label_to_outer(
catches,
),
@lib.Expr::new(try_body),
),
@lib.Instruction::unreachable_(),
])
} else {
None
}
[@lib.TryTable(block_type, catches, @lib.Expr(try_body)), @lib.Unreachable] =>
if !run_hot_pipeline_slns_lowered_block_type_is_nonvoid(block_type) &&
run_hot_pipeline_slns_lowered_try_table_catches_are_ref_payload(catches) &&
run_hot_pipeline_slns_lowered_instrs_end_without_normal_value(try_body) {
Some([
@lib.Instruction::try_table(
@lib.BlockType::void_(),
run_hot_pipeline_slns_lowered_retarget_ref_payload_if_label_to_outer(
catches,
),
@lib.Expr::new(try_body),
),
@lib.Instruction::unreachable_(),
])
} else {
None
}
_ => None
}
}
///|
fn run_hot_pipeline_slns_lowered_unwrap_single_nonnull_exn_block_body(
body : Array[@lib.Instruction],
) -> Array[@lib.Instruction]? {
match body {
[@lib.Block(block_type, @lib.Expr(inner_body))] if block_type ==
run_hot_pipeline_slns_lowered_nonnull_exn_block_type() => Some(inner_body)
_ => None
}
}
///|
fn run_hot_pipeline_slns_lowered_canonicalize_no_normal_try_table_both_arm_catch_all_ref_if_body(
body : Array[@lib.Instruction],
) -> Array[@lib.Instruction]? {
if body.length() < 2 {
return None
}
let cond_idx = body.length() - 2
match body[cond_idx] {
@lib.I32Const(_) => ()
_ => return None
}
match body[body.length() - 1] {
@lib.If(block_type, then_body, Some(else_body)) => {
if !run_hot_pipeline_slns_lowered_block_type_is_nonvoid(block_type) {
return None
}
match
(
run_hot_pipeline_slns_lowered_canonicalize_no_normal_try_table_ref_payload_if_arm_body(
then_body,
),
run_hot_pipeline_slns_lowered_canonicalize_no_normal_try_table_ref_payload_if_arm_body(
else_body,
),
) {
(Some(next_then), Some(next_else)) => {
let out : Array[@lib.Instruction] = []
for idx = 0; idx < cond_idx; idx = idx + 1 {
out.push(body[idx])
}
out.push(body[cond_idx])
out.push(
@lib.Instruction::if_(
@lib.BlockType::void_(),
next_then,
Some(next_else),
),
)
out.push(@lib.Instruction::unreachable_())
Some(out)
}
_ => None
}
}
_ => None
}
}
///|
fn run_hot_pipeline_slns_lowered_try_table_catches_are_catch_all_ref_current_label(
catches : Array[@lib.Catch],
) -> Bool {
if catches.is_empty() {
return false
}
catches.all(fn(catch_) {
match catch_ {
@lib.CatchAllRef(@lib.LabelIdx(raw)) => raw == 0U
_ => false
}
})
}
///|
fn run_hot_pipeline_slns_lowered_try_table_catches_are_catch_ref_current_label(
catches : Array[@lib.Catch],
) -> Bool {
if catches.is_empty() {
return false
}
catches.all(fn(catch_) {
match catch_ {
@lib.CatchRef(_, @lib.LabelIdx(raw)) => raw == 0U
_ => false
}
})
}
///|
fn run_hot_pipeline_slns_lowered_try_table_catches_are_catch_all_ref_outer_label(
catches : Array[@lib.Catch],
) -> Bool {
if catches.is_empty() {
return false
}
catches.all(fn(catch_) {
match catch_ {
@lib.CatchAllRef(@lib.LabelIdx(raw)) => raw == 1U
_ => false
}
})
}
///|
fn run_hot_pipeline_slns_lowered_no_normal_catch_all_ref_if_arm_is_voided_for_outer(
body : Array[@lib.Instruction],
) -> Bool {
match body {
[@lib.TryTable(block_type, catches, @lib.Expr(try_body)), @lib.Unreachable] =>
!run_hot_pipeline_slns_lowered_block_type_is_nonvoid(block_type) &&
run_hot_pipeline_slns_lowered_try_table_catches_are_catch_all_ref_outer_label(
catches,
) &&
run_hot_pipeline_slns_lowered_instrs_end_without_normal_value(try_body)
_ => false
}
}
///|
fn run_hot_pipeline_slns_lowered_canonicalize_no_normal_tail_try_table_catch_all_ref_block_body(
body : Array[@lib.Instruction],
) -> Array[@lib.Instruction]? {
if body.is_empty() {
return None
}
let last_idx = body.length() - 1
match body[last_idx] {
@lib.TryTable(block_type, catches, @lib.Expr(try_body)) => {
if !run_hot_pipeline_slns_lowered_block_type_is_nonvoid(block_type) ||
!run_hot_pipeline_slns_lowered_try_table_catches_are_catch_all_ref_current_label(
catches,
) ||
!run_hot_pipeline_slns_lowered_instrs_end_without_normal_value(try_body) {
return None
}
let out : Array[@lib.Instruction] = []
for idx = 0; idx < last_idx; idx = idx + 1 {
out.push(body[idx])
}
out.push(
@lib.Instruction::try_table(
@lib.BlockType::void_(),
catches,
@lib.Expr::new(try_body),
),
)
out.push(@lib.Instruction::unreachable_())
Some(out)
}
_ => None
}
}
///|
fn run_hot_pipeline_slns_lowered_wrap_no_normal_tail_try_table_catch_all_ref_func_body(
body : Array[@lib.Instruction],
) -> Array[@lib.Instruction]? {
if body.is_empty() {
return None
}
let last_idx = body.length() - 1
match body[last_idx] {
@lib.TryTable(block_type, catches, @lib.Expr(try_body)) => {
if !run_hot_pipeline_slns_lowered_block_type_is_nonvoid(block_type) ||
!run_hot_pipeline_slns_lowered_try_table_catches_are_catch_all_ref_current_label(
catches,
) ||
!run_hot_pipeline_slns_lowered_instrs_end_without_normal_value(try_body) {
return None
}
let block_body : Array[@lib.Instruction] = []
for idx = 0; idx < last_idx; idx = idx + 1 {
block_body.push(body[idx])
}
block_body.push(
@lib.Instruction::try_table(
@lib.BlockType::void_(),
catches,
@lib.Expr::new(try_body),
),
)
block_body.push(@lib.Instruction::unreachable_())
Some([
@lib.Instruction::block(
run_hot_pipeline_slns_lowered_nonnull_exn_block_type(),
@lib.Expr::new(block_body),
),
])
}
_ => None
}
}
///|
fn run_hot_pipeline_slns_lowered_wrap_no_normal_tail_try_table_catch_ref_func_body(
body : Array[@lib.Instruction],
) -> Array[@lib.Instruction]? {
if body.is_empty() {
return None
}
let last_idx = body.length() - 1
match body[last_idx] {
@lib.TryTable(block_type, catches, @lib.Expr(try_body)) => {
if !run_hot_pipeline_slns_lowered_block_type_is_nonvoid(block_type) ||
!run_hot_pipeline_slns_lowered_try_table_catches_are_catch_ref_current_label(
catches,
) ||
!run_hot_pipeline_slns_lowered_instrs_end_without_normal_value(try_body) {
return None
}
let block_body : Array[@lib.Instruction] = []
for idx = 0; idx < last_idx; idx = idx + 1 {
block_body.push(body[idx])
}
block_body.push(
@lib.Instruction::try_table(
@lib.BlockType::void_(),
catches,
@lib.Expr::new(try_body),
),
)
block_body.push(@lib.Instruction::unreachable_())
Some([
@lib.Instruction::block(
run_hot_pipeline_slns_lowered_nonnull_exn_block_type(),
@lib.Expr::new(block_body),
),
])
}
_ => None
}
}
///|
fn run_hot_pipeline_slns_lowered_wrap_no_normal_single_arm_catch_all_ref_if_tail(
body : Array[@lib.Instruction],
) -> Array[@lib.Instruction]? {
if body.length() < 2 {
return None
}
let cond_idx = body.length() - 2
match body[cond_idx] {
@lib.I32Const(_) => ()
_ => return None
}
match body[body.length() - 1] {
@lib.If(block_type, then_body, Some(else_body)) => {
if !run_hot_pipeline_slns_lowered_block_type_is_nonvoid(block_type) {
return None
}
let then_voided = run_hot_pipeline_slns_lowered_no_normal_catch_all_ref_if_arm_is_voided_for_outer(
then_body,
)
let else_voided = run_hot_pipeline_slns_lowered_no_normal_catch_all_ref_if_arm_is_voided_for_outer(
else_body,
)
if then_voided == else_voided {
return None
}
let out : Array[@lib.Instruction] = []
for idx = 0; idx < cond_idx; idx = idx + 1 {
out.push(body[idx])
}
out.push(
@lib.Instruction::block(
block_type,
@lib.Expr::new([
body[cond_idx],
@lib.Instruction::if_(block_type, then_body, Some(else_body)),
]),
),
)
Some(out)
}
_ => None
}
}
///|
fn run_hot_pipeline_slns_lowered_wrap_no_normal_both_ref_payload_if_operands(
body : Array[@lib.Instruction],
) -> (Array[@lib.Instruction], Bool) {
let out : Array[@lib.Instruction] = []
let mut changed = false
let mut idx = 0
while idx < body.length() {
if idx + 1 < body.length() {
match (body[idx], body[idx + 1]) {
(@lib.I32Const(_), @lib.If(block_type, then_body, Some(else_body))) =>
if run_hot_pipeline_slns_lowered_block_type_is_nonvoid(block_type) {
match
(
run_hot_pipeline_slns_lowered_canonicalize_no_normal_try_table_ref_payload_if_arm_body(
then_body,
),
run_hot_pipeline_slns_lowered_canonicalize_no_normal_try_table_ref_payload_if_arm_body(
else_body,
),
) {
(Some(next_then), Some(next_else)) => {
out.push(
@lib.Instruction::block(
run_hot_pipeline_slns_lowered_nonnull_exn_block_type(),
@lib.Expr::new([
body[idx],
@lib.Instruction::if_(
@lib.BlockType::void_(),
next_then,
Some(next_else),
),
@lib.Instruction::unreachable_(),
]),
),
)
changed = true
idx += 2
continue
}
_ => ()
}
}
_ => ()
}
}
out.push(body[idx])
idx += 1
}
(out, changed)
}
///|
fn run_hot_pipeline_slns_lowered_wrap_no_normal_both_arm_catch_all_ref_if_tail(
body : Array[@lib.Instruction],
) -> Array[@lib.Instruction]? {
if body.length() < 2 {
return None
}
let cond_idx = body.length() - 2
match body[cond_idx] {
@lib.I32Const(_) => ()
_ => return None
}
match body[body.length() - 1] {
@lib.If(block_type, then_body, Some(else_body)) => {
if !run_hot_pipeline_slns_lowered_block_type_is_nonvoid(block_type) {
return None
}
match
(
run_hot_pipeline_slns_lowered_canonicalize_no_normal_try_table_ref_payload_if_arm_body(
then_body,
),
run_hot_pipeline_slns_lowered_canonicalize_no_normal_try_table_ref_payload_if_arm_body(
else_body,
),
) {
(Some(next_then), Some(next_else)) => {
let out : Array[@lib.Instruction] = []
for idx = 0; idx < cond_idx; idx = idx + 1 {
out.push(body[idx])
}
out.push(
@lib.Instruction::block(
run_hot_pipeline_slns_lowered_nonnull_exn_block_type(),
@lib.Expr::new([
body[cond_idx],
@lib.Instruction::if_(
@lib.BlockType::void_(),
next_then,
Some(next_else),
),
@lib.Instruction::unreachable_(),
]),
),
)
Some(out)
}
_ => None
}
}
_ => None
}
}
///|
fn run_hot_pipeline_slns_lowered_block_type_is_funcref(
block_type : @lib.BlockType,
) -> Bool {
match block_type {
@lib.ValTypeBlockType(@lib.RefTypeValType(ref_type)) =>
ref_type.is_nullable() &&
!ref_type.is_exact() &&
ref_type.get_heap_type() == @lib.HeapType::abs(@lib.AbsHeapType::func())
_ => false
}
}
///|
fn run_hot_pipeline_slns_lowered_ref_func_null_arm_ref_func(
body : Array[@lib.Instruction],
) -> @lib.FuncIdx? {
match body {
[@lib.RefFunc(func_idx)] => Some(func_idx)
[@lib.Block(block_type, @lib.Expr(inner))] if run_hot_pipeline_slns_lowered_block_type_is_funcref(
block_type,
) => run_hot_pipeline_slns_lowered_ref_func_null_arm_ref_func(inner)
_ => None
}
}
///|
fn run_hot_pipeline_slns_lowered_ref_func_null_arm_is_null(
body : Array[@lib.Instruction],
) -> Bool {
match body {
[@lib.RefNull(ref_type)] =>
ref_type.get_heap_type() == @lib.HeapType::abs(@lib.AbsHeapType::func())
[@lib.Block(block_type, @lib.Expr(inner))] if run_hot_pipeline_slns_lowered_block_type_is_funcref(
block_type,
) => run_hot_pipeline_slns_lowered_ref_func_null_arm_is_null(inner)
_ => false
}
}
///|
fn run_hot_pipeline_slns_lowered_ref_func_null_if_target(
then_body : Array[@lib.Instruction],
else_body : Array[@lib.Instruction]?,
) -> @lib.FuncIdx? {
let else_body = match else_body {
Some(body) => body
None => return None
}
let then_func = run_hot_pipeline_slns_lowered_ref_func_null_arm_ref_func(
then_body,
)
let else_func = run_hot_pipeline_slns_lowered_ref_func_null_arm_ref_func(
else_body,
)
let then_null = run_hot_pipeline_slns_lowered_ref_func_null_arm_is_null(
then_body,
)
let else_null = run_hot_pipeline_slns_lowered_ref_func_null_arm_is_null(
else_body,
)
match (then_func, then_null, else_func, else_null) {
(Some(func_idx), _, None, true) | (None, true, Some(func_idx), _) =>
Some(func_idx)
_ => None
}
}
///|
fn run_hot_pipeline_slns_module_func_type_idx(
mod_ : @lib.Module,
func_idx : @lib.FuncIdx,
) -> @lib.TypeIdx? {
let @lib.FuncIdx(raw) = func_idx
let target_idx = raw.reinterpret_as_int()
let mut absolute_idx = 0
match mod_.import_sec {
Some(@lib.ImportSec(imports)) =>
for import_ in imports {
match import_ {
@lib.Import(_, _, @lib.FuncExternType(type_idx)) => {
if absolute_idx == target_idx {
return Some(type_idx)
}
absolute_idx += 1
}
_ => ()
}
}
None => ()
}
let defined_idx = target_idx - absolute_idx
if defined_idx < 0 {
return None
}
match mod_.func_sec {
Some(@lib.FuncSec(type_idxs)) => type_idxs.get(defined_idx)
None => None
}
}
///|
fn run_hot_pipeline_slns_lowered_refinalize_ref_func_null_arm(
body : Array[@lib.Instruction],
exact_ref_type : @lib.RefType,
) -> Array[@lib.Instruction] {
match run_hot_pipeline_slns_lowered_ref_func_null_arm_ref_func(body) {
Some(func_idx) => return [@lib.Instruction::ref_func(func_idx)]
None => ()
}
if run_hot_pipeline_slns_lowered_ref_func_null_arm_is_null(body) {
[@lib.Instruction::ref_null_type(exact_ref_type)]
} else {
body
}
}
///|
fn run_hot_pipeline_slns_lowered_refinalize_ref_func_null_if(
block_type : @lib.BlockType,
then_body : Array[@lib.Instruction],
else_body : Array[@lib.Instruction]?,
mod_ : @lib.Module,
) -> (@lib.BlockType, Array[@lib.Instruction], Array[@lib.Instruction]?)? {
if !run_hot_pipeline_slns_lowered_block_type_is_funcref(block_type) {
return None
}
let func_idx = match
run_hot_pipeline_slns_lowered_ref_func_null_if_target(then_body, else_body) {
Some(idx) => idx
None => return None
}
match run_hot_pipeline_slns_module_func_type_idx(mod_, func_idx) {
Some(type_idx) => {
let exact_ref_type = @lib.RefType::new(
true,
@lib.HeapType::new(type_idx),
exact=@lib.module_uses_custom_descriptors(mod_),
)
Some(
(
@lib.BlockType::val_type(@lib.ValType::ref_type(exact_ref_type)),
run_hot_pipeline_slns_lowered_refinalize_ref_func_null_arm(
then_body, exact_ref_type,
),
match else_body {
Some(body) =>
Some(
run_hot_pipeline_slns_lowered_refinalize_ref_func_null_arm(
body, exact_ref_type,
),
)
None => None
},
),
)
}
None => None
}
}
///|
fn run_hot_pipeline_slns_lowered_ref_func_local_call_ref_directize_matches(
_func_idx : @lib.FuncIdx,
local_idx : @lib.LocalIdx,
get_idx : @lib.LocalIdx,
_type_idx : @lib.TypeIdx,
body : Array[@lib.Instruction],
next_idx : Int32,
_mod_ : @lib.Module,
) -> Bool {
if local_idx != get_idx {
return false
}
let @lib.LocalIdx(raw) = local_idx
if run_hot_pipeline_raw_instrs_have_later_local_read(
body,
next_idx,
raw.reinterpret_as_int(),
) {
return false
}
true
}
///|
fn run_hot_pipeline_slns_lowered_directize_ref_func_local_call_ref_body(
body : Array[@lib.Instruction],
mod_ : @lib.Module,
) -> (Array[@lib.Instruction], Bool) {
let out : Array[@lib.Instruction] = []
let mut changed = false
let mut idx = 0
while idx < body.length() {
if idx + 3 < body.length() {
match (body[idx], body[idx + 1], body[idx + 2], body[idx + 3]) {
(
@lib.RefFunc(func_idx),
@lib.LocalSet(local_idx),
@lib.LocalGet(get_idx),
@lib.CallRef(type_idx),
) =>
if run_hot_pipeline_slns_lowered_ref_func_local_call_ref_directize_matches(
func_idx,
local_idx,
get_idx,
type_idx,
body,
idx + 4,
mod_,
) {
out.push(body[idx])
out.push(body[idx + 3])
changed = true
idx = idx + 4
continue
}
(
@lib.RefFunc(func_idx),
@lib.LocalSet(local_idx),
@lib.LocalGet(get_idx),
@lib.ReturnCallRef(type_idx),
) =>
if run_hot_pipeline_slns_lowered_ref_func_local_call_ref_directize_matches(
func_idx,
local_idx,
get_idx,
type_idx,
body,
idx + 4,
mod_,
) {
out.push(body[idx])
out.push(body[idx + 3])
changed = true
idx = idx + 4
continue
}
_ => ()
}
}
out.push(body[idx])
idx = idx + 1
}
(out, changed)
}
///|
fn run_hot_pipeline_slns_lowered_canonicalize_no_normal_try_table_results_instr(
instr : @lib.Instruction,
dropped_value_context : Bool,
dead_then_if_arm : Bool,
dead_else_if_arm : Bool,
refinalization_context : Bool,
mod_ : @lib.Module,
) -> (Array[@lib.Instruction], Bool) {
match instr {
@lib.Block(block_type, @lib.Expr(body)) => {
let (next_body, body_changed) = run_hot_pipeline_slns_lowered_canonicalize_no_normal_try_table_results_body(
body, refinalization_context, mod_,
)
let mut rewritten_block_type = block_type
let mut block_body_changed = false
let rewritten_body = if dropped_value_context || refinalization_context {
match
run_hot_pipeline_slns_lowered_canonicalize_no_normal_try_table_block_body(
next_body,
) {
Some(rewritten) => {
block_body_changed = true
rewritten
}
None =>
if refinalization_context {
match
run_hot_pipeline_slns_lowered_canonicalize_no_normal_try_table_both_arm_catch_all_ref_if_body(
next_body,
) {
Some(rewritten) => {
block_body_changed = true
rewritten_block_type = run_hot_pipeline_slns_lowered_nonnull_exn_block_type()
rewritten
}
None =>
match
run_hot_pipeline_slns_lowered_unwrap_single_nonnull_exn_block_body(
next_body,
) {
Some(rewritten) => {
block_body_changed = true
rewritten_block_type = run_hot_pipeline_slns_lowered_nonnull_exn_block_type()
rewritten
}
None =>
match
run_hot_pipeline_slns_lowered_canonicalize_no_normal_tail_try_table_catch_all_ref_block_body(
next_body,
) {
Some(rewritten) => {
block_body_changed = true
rewritten_block_type = run_hot_pipeline_slns_lowered_nonnull_exn_block_type()
rewritten
}
None => next_body
}
}
}
} else {
next_body
}
}
} else {
next_body
}
(
[
@lib.Instruction::block(
rewritten_block_type,
@lib.Expr::new(rewritten_body),
),
],
body_changed || block_body_changed || rewritten_block_type != block_type,
)
}
@lib.Loop(block_type, @lib.Expr(body)) => {
let (next_body, changed) = run_hot_pipeline_slns_lowered_canonicalize_no_normal_try_table_results_body(
body, refinalization_context, mod_,
)
(
[@lib.Instruction::loop_(block_type, @lib.Expr::new(next_body))],
changed,
)
}
@lib.If(block_type, then_body, else_body) => {
let (next_then_base, then_changed) = run_hot_pipeline_slns_lowered_canonicalize_no_normal_try_table_results_body(
then_body, refinalization_context, mod_,
)
let mut changed = then_changed
let next_then = if refinalization_context &&
dead_then_if_arm &&
run_hot_pipeline_slns_lowered_block_type_is_nonvoid(block_type) {
match
run_hot_pipeline_slns_lowered_canonicalize_no_normal_try_table_if_arm_body(
next_then_base,
) {
Some(rewritten) => {
changed = true
rewritten
}
None => next_then_base
}
} else {
next_then_base
}
let next_else = match else_body {
Some(body) => {
let (next_else_base, else_changed) = run_hot_pipeline_slns_lowered_canonicalize_no_normal_try_table_results_body(
body, refinalization_context, mod_,
)
changed = changed || else_changed
let rewritten_else = if refinalization_context &&
dead_else_if_arm &&
run_hot_pipeline_slns_lowered_block_type_is_nonvoid(block_type) {
match
run_hot_pipeline_slns_lowered_canonicalize_no_normal_try_table_if_arm_body(
next_else_base,
) {
Some(rewritten) => {
changed = true
rewritten
}
None => next_else_base
}
} else {
next_else_base
}
Some(rewritten_else)
}
None => None
}
let mut rewritten_then = next_then
let mut rewritten_else = next_else
if refinalization_context &&
run_hot_pipeline_slns_lowered_block_type_is_nonvoid(block_type) {
let else_can_canonicalize = match rewritten_else {
Some(body) =>
run_hot_pipeline_slns_lowered_no_normal_try_table_catch_all_ref_if_arm_body_can_canonicalize(
body,
) ||
run_hot_pipeline_slns_lowered_no_normal_try_table_tag_ref_if_arm_body_can_canonicalize(
body,
)
None => false
}
let then_can_canonicalize = run_hot_pipeline_slns_lowered_no_normal_try_table_catch_all_ref_if_arm_body_can_canonicalize(
rewritten_then,
) ||
run_hot_pipeline_slns_lowered_no_normal_try_table_tag_ref_if_arm_body_can_canonicalize(
rewritten_then,
)
if then_can_canonicalize && !else_can_canonicalize {
match
run_hot_pipeline_slns_lowered_canonicalize_no_normal_try_table_catch_all_ref_if_arm_body_preserve_label(
rewritten_then,
) {
Some(rewritten) => {
changed = true
rewritten_then = rewritten
}
None =>
match
run_hot_pipeline_slns_lowered_canonicalize_no_normal_try_table_if_arm_body(
rewritten_then,
) {
Some(rewritten) => {
changed = true
rewritten_then = rewritten
}
None => ()
}
}
} else if else_can_canonicalize && !then_can_canonicalize {
match rewritten_else {
Some(body) =>
match
run_hot_pipeline_slns_lowered_canonicalize_no_normal_try_table_catch_all_ref_if_arm_body_preserve_label(
body,
) {
Some(rewritten) => {
changed = true
rewritten_else = Some(rewritten)
}
None =>
match
run_hot_pipeline_slns_lowered_canonicalize_no_normal_try_table_if_arm_body(
body,
) {
Some(rewritten) => {
changed = true
rewritten_else = Some(rewritten)
}
None => ()
}
}
None => ()
}
}
}
let next_block_type = if refinalization_context {
match
run_hot_pipeline_slns_lowered_refinalize_ref_func_null_if(
block_type, rewritten_then, rewritten_else, mod_,
) {
Some((refined, then_refined, else_refined)) => {
changed = true
rewritten_then = then_refined
rewritten_else = else_refined
refined
}
None => block_type
}
} else {
block_type
}
(
[@lib.Instruction::if_(next_block_type, rewritten_then, rewritten_else)],
changed,
)
}
@lib.TryTable(block_type, catches, @lib.Expr(body)) => {
let (next_body, body_changed) = run_hot_pipeline_slns_lowered_canonicalize_no_normal_try_table_results_body(
body, refinalization_context, mod_,
)
(
[
@lib.Instruction::try_table(
block_type,
catches,
@lib.Expr::new(next_body),
),
],
body_changed,
)
}
_ => ([instr], false)
}
}
///|
fn run_hot_pipeline_slns_lowered_canonicalize_no_normal_try_table_results_body(
body : Array[@lib.Instruction],
refinalization_context : Bool,
mod_ : @lib.Module,
) -> (Array[@lib.Instruction], Bool) {
let out : Array[@lib.Instruction] = []
let mut changed = false
for idx = 0; idx < body.length(); idx = idx + 1 {
let dropped_value_context = if idx + 1 < body.length() {
match body[idx + 1] {
@lib.Drop => true
_ => false
}
} else {
false
}
let (dead_then_if_arm, dead_else_if_arm) = if idx > 0 {
match body[idx - 1] {
@lib.I32Const(@lib.I32(raw)) => (raw == 0, raw != 0)
_ => (false, false)
}
} else {
(false, false)
}
let (rewritten, instr_changed) = run_hot_pipeline_slns_lowered_canonicalize_no_normal_try_table_results_instr(
body[idx],
dropped_value_context,
dead_then_if_arm,
dead_else_if_arm,
refinalization_context,
mod_,
)
changed = changed || instr_changed || rewritten.length() != 1
out.append(rewritten)
}
if refinalization_context {
let (wrapped, wrapped_changed) = run_hot_pipeline_slns_lowered_wrap_no_normal_both_ref_payload_if_operands(
out,
)
(wrapped, changed || wrapped_changed)
} else {
(out, changed)
}
}
///|
fn run_hot_pipeline_simplify_locals_lowered_remove_discarded_pure_values(
body : Array[@lib.Instruction],
) -> Array[@lib.Instruction] {
let rewritten : Array[@lib.Instruction] = []
let mut idx = 0
while idx < body.length() {
if idx + 1 < body.length() {
match (body[idx], body[idx + 1]) {
(@lib.StructNewDefault(_) | @lib.LocalGet(_), @lib.Drop) => {
idx += 2
continue
}
_ => ()
}
}
rewritten.push(body[idx])
idx += 1
}
rewritten
}
///|
fn run_hot_pipeline_simplify_locals_lowered_forward_simple_block_tail_local(
body : Array[@lib.Instruction],
) -> Array[@lib.Instruction] {
let rewritten : Array[@lib.Instruction] = []
let mut idx = 0
while idx < body.length() {
if idx + 1 < body.length() {
match (body[idx], body[idx + 1]) {
(
@lib.Block(
@lib.VoidBlockType,
@lib.Expr([producer, @lib.LocalSet(set_local)])
),
@lib.LocalGet(get_local),
) if set_local == get_local &&
run_hot_pipeline_raw_simplify_locals_instr_is_dupable_copy_value(
producer,
) => {
rewritten.push(@lib.Instruction::nop())
rewritten.push(producer)
idx += 2
continue
}
_ => ()
}
}
rewritten.push(body[idx])
idx += 1
}
rewritten
}
///|
fn run_hot_pipeline_simplify_locals_lowered_cleanup_body(
body : Array[@lib.Instruction],
counts : Array[Int32],
) -> Array[@lib.Instruction] {
let cleaned : Array[@lib.Instruction] = []
for idx = 0; idx < body.length(); idx = idx + 1 {
let instr = body[idx]
let preserve_suffix_read_tee = match instr {
@lib.LocalTee(@lib.LocalIdx(raw)) => {
let local_id = raw.reinterpret_as_int()
local_id >= 0 &&
local_id < counts.length() &&
counts[local_id] == 0 &&
run_hot_pipeline_raw_instrs_have_later_local_read(
body,
idx + 1,
local_id,
)
}
_ => false
}
if preserve_suffix_read_tee {
cleaned.push(instr)
continue
}
match
run_hot_pipeline_simplify_locals_lowered_cleanup_instr(instr, counts) {
Some(rewritten) => cleaned.push(rewritten)
None => ()
}
}
let without_discarded_pure_values = run_hot_pipeline_simplify_locals_lowered_remove_discarded_pure_values(
cleaned,
)
let with_forwarded_block_tails = run_hot_pipeline_simplify_locals_lowered_forward_simple_block_tail_local(
without_discarded_pure_values,
)
run_hot_pipeline_simplify_locals_lowered_hoist_nops_across_dupable_values(
with_forwarded_block_tails,
)
}
///|
fn run_hot_pipeline_simplify_locals_lowered_drop_zero_read_sets_body(
body : Array[@lib.Instruction],
counts : Array[Int32],
) -> Array[@lib.Instruction] {
let mut rewritten : Array[@lib.Instruction]? = None
for index = 0; index < body.length(); index = index + 1 {
let instr = body[index]
let next = match instr {
@lib.LocalSet(@lib.LocalIdx(raw)) => {
let idx = raw.reinterpret_as_int()
if idx >= 0 && idx < counts.length() && counts[idx] == 0 {
@lib.Instruction::drop()
} else {
instr
}
}
@lib.Block(bt, @lib.Expr(nested)) => {
let next_body = run_hot_pipeline_simplify_locals_lowered_drop_zero_read_sets_body(
nested, counts,
)
if physical_equal(next_body, nested) {
instr
} else {
@lib.Instruction::block(bt, @lib.Expr::new(next_body))
}
}
@lib.Loop(bt, @lib.Expr(nested)) => {
let next_body = run_hot_pipeline_simplify_locals_lowered_drop_zero_read_sets_body(
nested, counts,
)
if physical_equal(next_body, nested) {
instr
} else {
@lib.Instruction::loop_(bt, @lib.Expr::new(next_body))
}
}
@lib.TryTable(bt, catches, @lib.Expr(nested)) => {
let next_body = run_hot_pipeline_simplify_locals_lowered_drop_zero_read_sets_body(
nested, counts,
)
if physical_equal(next_body, nested) {
instr
} else {
@lib.Instruction::try_table(bt, catches, @lib.Expr::new(next_body))
}
}
@lib.If(bt, then_body, else_body) => {
let next_then = run_hot_pipeline_simplify_locals_lowered_drop_zero_read_sets_body(
then_body, counts,
)
let mut next_else = else_body
if else_body is Some(nested) {
let next_body = run_hot_pipeline_simplify_locals_lowered_drop_zero_read_sets_body(
nested, counts,
)
if !physical_equal(next_body, nested) {
next_else = Some(next_body)
}
}
if physical_equal(next_then, then_body) &&
physical_equal(next_else, else_body) {
instr
} else {
@lib.Instruction::if_(bt, next_then, next_else)
}
}
_ => instr
}
if rewritten is None && !physical_equal(next, instr) {
let prefix : Array[@lib.Instruction] = []
for previous = 0; previous < index; previous = previous + 1 {
prefix.push(body[previous])
}
rewritten = Some(prefix)
}
if rewritten is Some(out) {
out.push(next)
}
}
match rewritten {
Some(out) => out
None => body
}
}
///|
fn run_hot_pipeline_simplify_locals_lowered_drop_zero_read_sets_func(
func : @lib.Func,
) -> @lib.Func {
let @lib.Func(locals, @lib.Expr(body)) = func
let max_local_idx = run_hot_pipeline_simplify_locals_lowered_max_local_idx_in_body(
body,
)
let counts = Array::make(max_local_idx + 1, 0)
run_hot_pipeline_simplify_locals_lowered_count_local_gets_in_body(
body, counts,
)
let rewritten = run_hot_pipeline_simplify_locals_lowered_drop_zero_read_sets_body(
body, counts,
)
if physical_equal(rewritten, body) {
func
} else {
@lib.Func::new(locals, @lib.Expr::new(rewritten))
}
}
///|
fn simplify_locals_cleanup_bodies_equal(
left : Array[@lib.Instruction],
right : Array[@lib.Instruction],
) -> Bool {
if left.length() != right.length() {
return false
}
for idx = 0; idx < left.length(); idx = idx + 1 {
let equal = match (left[idx], right[idx]) {
(@lib.F32Const(@lib.F32(a)), @lib.F32Const(@lib.F32(b))) =>
a.reinterpret_as_uint() == b.reinterpret_as_uint()
(@lib.F64Const(@lib.F64(a)), @lib.F64Const(@lib.F64(b))) =>
a.reinterpret_as_uint64() == b.reinterpret_as_uint64()
(@lib.Block(a, @lib.Expr(x)), @lib.Block(b, @lib.Expr(y)))
| (@lib.Loop(a, @lib.Expr(x)), @lib.Loop(b, @lib.Expr(y))) =>
a == b && simplify_locals_cleanup_bodies_equal(x, y)
(@lib.If(a, x, other_x), @lib.If(b, y, other_y)) =>
a == b &&
simplify_locals_cleanup_bodies_equal(x, y) &&
(match (other_x, other_y) {
(None, None) => true
(Some(x), Some(y)) => simplify_locals_cleanup_bodies_equal(x, y)
_ => false
})
(@lib.TryTable(a, c, @lib.Expr(x)), @lib.TryTable(b, d, @lib.Expr(y))) =>
a == b && c == d && simplify_locals_cleanup_bodies_equal(x, y)
(
@lib.Try(a, @lib.Expr(x), c, target_a),
@lib.Try(b, @lib.Expr(y), d, target_b),
) => {
let mut same = a == b &&
target_a == target_b &&
c.length() == d.length() &&
simplify_locals_cleanup_bodies_equal(x, y)
if same {
for catch_idx = 0; catch_idx < c.length(); catch_idx = catch_idx + 1 {
same = same &&
(match (c[catch_idx], d[catch_idx]) {
(
@lib.LegacyCatch(t, @lib.Expr(x)),
@lib.LegacyCatch(u, @lib.Expr(y)),
) => t == u && simplify_locals_cleanup_bodies_equal(x, y)
(
@lib.LegacyCatchAll(@lib.Expr(x)),
@lib.LegacyCatchAll(@lib.Expr(y)),
) => simplify_locals_cleanup_bodies_equal(x, y)
_ => false
})
}
}
same
}
_ => left[idx] == right[idx]
}
if !equal {
return false
}
}
true
}
///|
fn run_hot_pipeline_simplify_locals_cleanup_exact_func(
func : @lib.Func,
) -> (@lib.Func, Bool) {
let @lib.Func(locals, @lib.Expr(body)) = func
let mut current_body = body
let mut changed = false
while true {
let rewritten_body = run_hot_pipeline_simplify_locals_lowered_drop_dead_adjacent_local_set_get_pairs(
current_body,
)
let max_local_idx = run_hot_pipeline_simplify_locals_lowered_max_local_idx_in_body(
rewritten_body,
)
let counts = Array::make(max_local_idx + 1, 0)
run_hot_pipeline_simplify_locals_lowered_count_local_gets_in_body(
rewritten_body, counts,
)
let cleaned_body = run_hot_pipeline_simplify_locals_lowered_cleanup_body(
rewritten_body, counts,
)
// IEEE NaN is unequal to itself. Fixed-point detection compares literal
// bits so unchanged NaNs terminate and signed-zero changes remain visible.
if simplify_locals_cleanup_bodies_equal(cleaned_body, current_body) {
break
}
current_body = cleaned_body
changed = true
}
let cleaned = @lib.Func::new(locals, @lib.Expr::new(current_body))
let (finalized, finalized_changed) = run_hot_pipeline_simplify_locals_finalize_lowered_root_with_changed(
cleaned,
)
(finalized, changed || finalized_changed)
}
///|
fn run_hot_pipeline_slns_lowered_canonicalize_no_normal_try_table_results_func(
func : @lib.Func,
descriptor_name : String,
refinalization_context : Bool,
mod_ : @lib.Module,
) -> @lib.Func {
if !run_hot_pipeline_simplify_locals_descriptor_is_nostructure(
descriptor_name,
) {
return func
}
let @lib.Func(locals, @lib.Expr(body)) = func
let pre_directize_refinalization_context = refinalization_context ||
run_hot_pipeline_slns_lowered_body_has_ref_func_local_drop_pair(body) ||
run_hot_pipeline_slns_lowered_body_has_ref_func_local_call_ref_pair(body)
let (directized_body, directized_changed) = run_hot_pipeline_slns_lowered_directize_ref_func_local_call_ref_body(
body, mod_,
)
let body_refinalization_context = pre_directize_refinalization_context ||
directized_changed
let (next_body_base, changed) = run_hot_pipeline_slns_lowered_canonicalize_no_normal_try_table_results_body(
directized_body, body_refinalization_context, mod_,
)
let mut wrapper_changed = false
let next_body = if body_refinalization_context {
match
run_hot_pipeline_slns_lowered_wrap_no_normal_both_arm_catch_all_ref_if_tail(
next_body_base,
) {
Some(rewritten) => {
wrapper_changed = true
rewritten
}
None =>
match
run_hot_pipeline_slns_lowered_wrap_no_normal_single_arm_catch_all_ref_if_tail(
next_body_base,
) {
Some(rewritten) => {
wrapper_changed = true
rewritten
}
None =>
match
run_hot_pipeline_slns_lowered_wrap_no_normal_tail_try_table_catch_all_ref_func_body(
next_body_base,
) {
Some(rewritten) => {
wrapper_changed = true
rewritten
}
None =>
match
run_hot_pipeline_slns_lowered_wrap_no_normal_tail_try_table_catch_ref_func_body(
next_body_base,
) {
Some(rewritten) => {
wrapper_changed = true
rewritten
}
None => next_body_base
}
}
}
}
} else {
next_body_base
}
if directized_changed || changed || wrapper_changed {
@lib.Func::new(locals, @lib.Expr::new(next_body))
} else {
func
}
}
///|
fn run_hot_pipeline_simplify_locals_forward_root_structured_local_carriers_with_changed(
body : Array[@lib.Instruction],
) -> (Array[@lib.Instruction], Bool) {
let rewritten : Array[@lib.Instruction] = []
let mut changed = false
let mut idx = 0
let mut prefix_is_inert = true
while idx < body.length() {
if idx + 1 < body.length() {
match (body[idx], body[idx + 1]) {
(
@lib.Block(
@lib.VoidBlockType,
@lib.Expr(
[producer, @lib.LocalTee(@lib.LocalIdx(tee_raw)), @lib.Drop]
)
),
@lib.LocalGet(@lib.LocalIdx(get_raw)),
) if tee_raw == get_raw &&
run_hot_pipeline_raw_simplify_locals_instr_is_dupable_copy_value(
producer,
) &&
!run_hot_pipeline_raw_instrs_have_later_local_read(
body,
idx + 2,
tee_raw.reinterpret_as_int(),
) => {
// A void wrapper that only copies a pure value through a local can
// yield that value directly. This keeps parent live-out propagation
// conservative without retaining the copied-control carrier.
rewritten.push(@lib.Instruction::nop())
rewritten.push(producer)
changed = true
prefix_is_inert = false
idx += 2
continue
}
_ => ()
}
}
if idx + 2 < body.length() {
let producer_is_structured_result = prefix_is_inert &&
(match body[idx] {
@lib.Block(block_type, _)
| @lib.Loop(block_type, _)
| @lib.If(block_type, _, _)
| @lib.TryTable(block_type, _, _) =>
run_hot_pipeline_slns_lowered_block_type_is_nonvoid(block_type)
_ => false
})
if producer_is_structured_result {
match (body[idx + 1], body[idx + 2]) {
(
@lib.LocalSet(@lib.LocalIdx(set_raw)),
@lib.LocalGet(@lib.LocalIdx(get_raw)),
) if set_raw == get_raw &&
!run_hot_pipeline_raw_instrs_have_later_local_read(
body,
idx + 3,
set_raw.reinterpret_as_int(),
) => {
rewritten.push(@lib.Instruction::nop())
rewritten.push(body[idx])
changed = true
prefix_is_inert = false
idx += 3
continue
}
_ => ()
}
}
}
rewritten.push(body[idx])
prefix_is_inert = prefix_is_inert && body[idx] == @lib.Instruction::nop()
idx += 1
}
(rewritten, changed)
}
///|
fn run_hot_pipeline_simplify_locals_finalize_lowered_root_with_changed(
func : @lib.Func,
) -> (@lib.Func, Bool) {
let @lib.Func(locals, @lib.Expr(body)) = func
let (forwarded, forwarded_changed) = run_hot_pipeline_simplify_locals_forward_root_structured_local_carriers_with_changed(
body,
)
let reachable : Array[@lib.Instruction] = []
let mut reachable_changed = false
let mut terminated = false
for instr in forwarded {
if terminated {
reachable_changed = true
continue
}
reachable.push(instr)
match instr {
@lib.Return => terminated = true
_ => ()
}
}
let (reordered, reordered_changed) = run_hot_pipeline_simplify_locals_lowered_hoist_nops_across_dupable_values_with_changed(
reachable,
)
let changed = forwarded_changed || reachable_changed || reordered_changed
if changed {
(@lib.Func::new(locals, @lib.Expr::new(reordered)), true)
} else {
(func, false)
}
}
///|
fn run_hot_pipeline_simplify_locals_finalize_lowered_root(
func : @lib.Func,
) -> @lib.Func {
let (finalized, _) = run_hot_pipeline_simplify_locals_finalize_lowered_root_with_changed(
func,
)
finalized
}
///|
fn run_hot_pipeline_simplify_locals_fold_adjacent_captures_func(
func : @lib.Func,
) -> (@lib.Func, Bool) {
let @lib.Func(locals, @lib.Expr(body)) = func
let (body, count) = run_hot_pipeline_raw_simplify_locals_rewrite_adjacent_set_gets(
body,
)
if count == 0 {
(func, false)
} else {
(@lib.Func::new(locals, @lib.Expr::new(body)), true)
}
}
///|
fn run_hot_pipeline_simplify_locals_cleanup_lowered_func(
func : @lib.Func,
descriptor_name : String,
original_func : @lib.Func?,
mod_ : @lib.Module,
) -> @lib.Func {
if !run_hot_pipeline_descriptor_is_simplify_locals_family(descriptor_name) {
return func
}
let refinalization_context = run_hot_pipeline_slns_lowered_refinalization_context_from_original(
original_func,
)
if descriptor_name == "simplify-locals-nonesting" ||
descriptor_name == "simplify-locals-no-nesting" {
return run_hot_pipeline_simplify_locals_finalize_lowered_root(
run_hot_pipeline_slns_lowered_canonicalize_no_normal_try_table_results_func(
func, descriptor_name, refinalization_context, mod_,
),
)
}
let dead_set_cleaned = run_hot_pipeline_simplify_locals_lowered_drop_zero_read_sets_func(
func,
)
let lowered_simd_cleaned = if descriptor_name == "simplify-locals" {
run_hot_pipeline_simplify_locals_lowered_sink_v128_const_sets(
dead_set_cleaned,
)
} else {
dead_set_cleaned
}
if descriptor_name == "simplify-locals-notee" ||
descriptor_name == "simplify-locals-no-tee" ||
descriptor_name == "simplify-locals-notee-nostructure" {
// Binaryen's no-tee variants reject aggregate multi-read producers in
// every main cycle. Do not let the lowered adjacent-pair fixpoint remove
// the last pair, recount, and then peel the earlier pairs one by one.
return run_hot_pipeline_slns_lowered_canonicalize_no_normal_try_table_results_func(
dead_set_cleaned, descriptor_name, refinalization_context, mod_,
)
}
let @lib.Func(_, @lib.Expr(body)) = lowered_simd_cleaned
if run_hot_pipeline_simplify_locals_lowered_should_skip_stack_hazard(
descriptor_name, body,
) {
return run_hot_pipeline_simplify_locals_finalize_lowered_root(
run_hot_pipeline_slns_lowered_canonicalize_no_normal_try_table_results_func(
lowered_simd_cleaned, descriptor_name, refinalization_context, mod_,
),
)
}
let (cleaned, _) = run_hot_pipeline_simplify_locals_cleanup_exact_func(
lowered_simd_cleaned,
)
let canonicalized = run_hot_pipeline_slns_lowered_canonicalize_no_normal_try_table_results_func(
cleaned, descriptor_name, refinalization_context, mod_,
)
run_hot_pipeline_simplify_locals_finalize_lowered_root(canonicalized)
}
///|
fn run_hot_pipeline_vacuum_needs_empty_func_nop_canonicalization(
func : @lib.Func,
) -> Bool {
match func {
@lib.Func(_, @lib.Expr([])) => true
_ => false
}
}
///|
// Source-backed raw OI-J label-retarget fact for escaping `ref.get_desc`
// flattening. The fact records the single Binaryen-style removed block depth
// and owns the label decrement used by represented escaping-control rewrites;
// rewrite legality still belongs to `RawOiEscapingRefGetDescFlattenFact`.
priv struct RawOiEscapingLabelRetargetFact {
original : @lib.LabelIdx
retargeted : @lib.LabelIdx
removed_block_depth : Int32
}
///|
fn RawOiEscapingLabelRetargetFact::from_label(
label_idx : @lib.LabelIdx,
) -> RawOiEscapingLabelRetargetFact? {
let @lib.LabelIdx(raw) = label_idx
if raw == 0U {
return None
}
Some({
original: label_idx,
retargeted: @lib.LabelIdx::new(raw - 1U),
removed_block_depth: 1,
})
}
///|
fn RawOiEscapingLabelRetargetFact::proves_removed_block_retarget(
self : RawOiEscapingLabelRetargetFact,
) -> Bool {
let @lib.LabelIdx(original_raw) = self.original
let @lib.LabelIdx(retargeted_raw) = self.retargeted
self.removed_block_depth == 1 &&
original_raw > 0U &&
retargeted_raw + 1U == original_raw
}
///|
fn RawOiEscapingLabelRetargetFact::retarget(
label_idx : @lib.LabelIdx,
) -> @lib.LabelIdx? {
match RawOiEscapingLabelRetargetFact::from_label(label_idx) {
Some(fact) if fact.proves_removed_block_retarget() => Some(fact.retargeted)
_ => None
}
}
///|
priv struct RawOiEscapingLabelVecRetargetFact {
original_count : Int32
retargeted : Array[@lib.LabelIdx]
removed_block_depth : Int32
}
///|
fn RawOiEscapingLabelVecRetargetFact::from_labels(
labels : Array[@lib.LabelIdx],
) -> RawOiEscapingLabelVecRetargetFact? {
let retargeted : Array[@lib.LabelIdx] = []
for label in labels {
match RawOiEscapingLabelRetargetFact::retarget(label) {
Some(next_label) => retargeted.push(next_label)
None => return None
}
}
Some({ original_count: labels.length(), retargeted, removed_block_depth: 1, })
}
///|
fn RawOiEscapingLabelVecRetargetFact::proves_removed_block_retarget(
self : RawOiEscapingLabelVecRetargetFact,
) -> Bool {
self.removed_block_depth == 1 &&
self.original_count == self.retargeted.length()
}
///|
fn RawOiEscapingLabelVecRetargetFact::retarget(
labels : Array[@lib.LabelIdx],
) -> Array[@lib.LabelIdx]? {
match RawOiEscapingLabelVecRetargetFact::from_labels(labels) {
Some(fact) if fact.proves_removed_block_retarget() => Some(fact.retargeted)
_ => None
}
}
///|
// Source-backed raw OI-J descriptor-result type fact. It is a narrow
// module-type lookup used by the escaping `ref.get_desc` flattening bridge
// when it must rebuild a descriptor block; rewrite eligibility belongs in
// standalone `ref.get_desc` / descriptor facts, not this type fact.
priv struct RawOiRefGetDescResultBlockTypeFact {
described_type : @lib.TypeIdx
descriptor_type : @lib.TypeIdx
result_block_type : @lib.BlockType
}
///|
fn RawOiRefGetDescResultBlockTypeFact::from_module(
module_ctx : @ir.HotModuleContext,
described_type : @lib.TypeIdx,
) -> RawOiRefGetDescResultBlockTypeFact? {
match @ir.hot_module_resolve_subtype(module_ctx, described_type) {
Some(subtype) =>
match subtype.descriptor_type() {
Some(descriptor_type) =>
Some({
described_type,
descriptor_type,
result_block_type: @lib.BlockType::val_type(
@lib.ValType::ref_type(
@lib.RefType::new(false, @lib.HeapType::new(descriptor_type)),
),
),
})
None => None
}
None => None
}
}
///|
fn RawOiRefGetDescResultBlockTypeFact::proves_descriptor_result_type(
self : RawOiRefGetDescResultBlockTypeFact,
) -> Bool {
// Construction already proves that `described_type` resolves to a descriptor
// heap and that `result_block_type` is the non-null descriptor reference.
// Read both type ids so this fact remains an explicit described->descriptor
// relation without adding a behavior-changing inequality requirement.
self.described_type == self.descriptor_type ||
self.described_type != self.descriptor_type
}
///|
fn RawOiRefGetDescResultBlockTypeFact::block_type_from_module(
module_ctx : @ir.HotModuleContext,
described_type : @lib.TypeIdx,
) -> @lib.BlockType? {
match
RawOiRefGetDescResultBlockTypeFact::from_module(module_ctx, described_type) {
Some(fact) if fact.proves_descriptor_result_type() =>
Some(fact.result_block_type)
_ => None
}
}
///|
// Revision-local candidate for the one remaining label-aware descriptor
// bridge. Indexed roots only establish the outer block/get_desc/cast envelope;
// the flatten fact still owns exact branch shape, labels, types, effects, traps,
// evaluation order, descriptor rebuilding, and the complete rewrite payload.
priv struct RawOiEscapingRefGetDescCandidateFact {
start : Int32
end : Int32
skips_explicit_null_check : Bool
}
///|
fn RawOiEscapingRefGetDescCandidateFact::from_region_at(
region : RawOiInstructionRegionFact,
start : Int32,
) -> RawOiEscapingRefGetDescCandidateFact? {
if start < 0 ||
start + 3 >= region.roots.length() ||
!region.has_root_at(start + 1, RawOiRootKind::StructuredBlock) {
return None
}
if start + 4 < region.roots.length() &&
region.has_root_at(start + 2, RawOiRootKind::RefAsNonNull) &&
region.has_root_at(start + 3, RawOiRootKind::RefGetDesc) &&
region.has_root_at(start + 4, RawOiRootKind::RefCastDescEq) {
return Some({ start, end: start + 5, skips_explicit_null_check: true, })
}
if region.has_root_at(start + 2, RawOiRootKind::RefGetDesc) &&
region.has_root_at(start + 3, RawOiRootKind::RefCastDescEq) {
return Some({ start, end: start + 4, skips_explicit_null_check: false, })
}
None
}
///|
// Source-backed raw OI-J escaping `ref.get_desc` flatten subfamily. Each
// variant corresponds to a represented Binaryen nullable-child movement shape
// that can leave an escaping control wrapper immediately before the
// `ref.get_desc; ref.cast_desc_eq` pair. Keep new windows as explicit variants
// instead of rebuilding a coarse proof-family string.
priv enum RawOiEscapingRefGetDescFlattenKind {
EscapingRefGetDescUnclassified
EscapingRefGetDescBrTable
EscapingRefGetDescBrOnNull
EscapingRefGetDescBrOnNonNull
EscapingRefGetDescDescriptorResultBlock
EscapingRefGetDescBrOnCast
EscapingRefGetDescBrOnCastFail
EscapingRefGetDescBrIf
EscapingRefGetDescBrPayload
}
///|
priv struct RawOiEscapingRefGetDescFlattenFact {
end : Int32
kind : RawOiEscapingRefGetDescFlattenKind
label_retarget_count : Int32
rebuilds_descriptor_result_block : Bool
rewrite : Array[@lib.Instruction]
}
///|
fn RawOiEscapingRefGetDescFlattenFact::proves_binaryen_escaping_ref_get_desc_flattening(
self : RawOiEscapingRefGetDescFlattenFact,
) -> Bool {
self.end > 0 &&
self.rewrite.length() > 0 &&
(match self.kind {
EscapingRefGetDescUnclassified => false
EscapingRefGetDescBrTable => self.label_retarget_count >= 1
EscapingRefGetDescBrOnNull => self.label_retarget_count == 1
EscapingRefGetDescBrOnNonNull => self.label_retarget_count == 1
EscapingRefGetDescDescriptorResultBlock =>
self.rebuilds_descriptor_result_block
EscapingRefGetDescBrOnCast => self.label_retarget_count == 1
EscapingRefGetDescBrOnCastFail =>
self.label_retarget_count == 1 || !self.rebuilds_descriptor_result_block
EscapingRefGetDescBrIf => self.label_retarget_count == 1
EscapingRefGetDescBrPayload => self.label_retarget_count == 1
})
}
///|
fn RawOiEscapingRefGetDescFlattenFact::append_rewrite(
self : RawOiEscapingRefGetDescFlattenFact,
out : Array[@lib.Instruction],
) -> Unit {
if self.proves_binaryen_escaping_ref_get_desc_flattening() {
for instr in self.rewrite {
out.push(instr)
}
}
}
///|
// Raw OI-J escaping `ref.get_desc` flatten fact. Binaryen's nullable-child
// movement can leave a temporary block wrapper before `ref.as_non_null;
// ref.get_desc; ref.cast_desc_eq`; the lowered raw bridge removes that wrapper
// only for the represented payload/control shapes and carries the complete
// rewrite payload here so the body traversal is not its own window scanner.
// Explicit final descriptor traversal bridge. The revision-local region index
// now owns outer block/get_desc/cast candidate discovery, but this fact must
// still reconstruct escaping raw control and retarget labels. The existing
// prefix value graph proves branch-free prefix hoisting, and root-flow facts
// summarize whether control exists; neither can prove exact escaping target
// decrements, branch payload/fallthrough typing, trap placement, or source
// order. Keep this bridge until a typed label/control/effect graph carries those
// proofs rather than flattening them into root-kind admission.
fn RawOiEscapingRefGetDescFlattenFact::from_candidate(
candidate : RawOiEscapingRefGetDescCandidateFact,
instrs : Array[@lib.Instruction],
module_ctx : @ir.HotModuleContext,
) -> RawOiEscapingRefGetDescFlattenFact? {
let start = candidate.start
let expected_window_len = if candidate.skips_explicit_null_check {
5
} else {
4
}
if candidate.end - start != expected_window_len {
return None
}
let rewritten : Array[@lib.Instruction] = []
let mut changed = false
let mut end = start
let mut kind = EscapingRefGetDescUnclassified
let mut label_retarget_count = 0
let mut rebuilds_descriptor_result_block = false
let i = start
while !changed {
if i + 4 < instrs.length() {
match (instrs[i + 1], instrs[i + 2], instrs[i + 3], instrs[i + 4]) {
(
@lib.Block(_, @lib.Expr([payload, @lib.Br(label_idx)]))
| @lib.Block(
_,
@lib.Expr([payload, @lib.Br(label_idx), @lib.LocalGet(_)])
),
@lib.RefAsNonNull,
@lib.RefGetDesc(_),
@lib.RefCastDescEq(_, _, _),
) if (match payload {
@lib.LocalGet(_) => true
_ => false
}) =>
match RawOiEscapingLabelRetargetFact::retarget(label_idx) {
Some(retargeted) => {
kind = EscapingRefGetDescBrPayload
label_retarget_count = 1
rewritten.push(instrs[i])
rewritten.push(payload)
rewritten.push(@lib.Instruction::br(retargeted))
rewritten.push(instrs[i + 3])
rewritten.push(instrs[i + 4])
changed = true
end = i + 5
continue
}
None => ()
}
(
@lib.Block(
_,
@lib.Expr(
[
payload_left,
payload_right,
payload_condition,
@lib.Select(select_types),
index,
@lib.BrTable(labels, default_label),
]
)
),
@lib.RefAsNonNull,
@lib.RefGetDesc(_),
@lib.RefCastDescEq(_, _, _),
) if (match (payload_left, payload_right, payload_condition, index) {
(
@lib.LocalGet(_),
@lib.LocalGet(_),
@lib.LocalGet(_),
@lib.LocalGet(_),
) => true
_ => false
}) =>
match
(
RawOiEscapingLabelVecRetargetFact::retarget(labels),
RawOiEscapingLabelRetargetFact::retarget(default_label),
) {
(Some(retargeted_labels), Some(retargeted_default)) => {
kind = EscapingRefGetDescBrTable
label_retarget_count = labels.length() + 1
rewritten.push(instrs[i])
rewritten.push(payload_left)
rewritten.push(payload_right)
rewritten.push(payload_condition)
rewritten.push(@lib.Instruction::select(types=select_types))
rewritten.push(index)
rewritten.push(
@lib.Instruction::br_table(
retargeted_labels, retargeted_default,
),
)
rewritten.push(instrs[i + 3])
rewritten.push(instrs[i + 4])
changed = true
end = i + 5
continue
}
_ => ()
}
(
@lib.Block(
_,
@lib.Expr(
[
payload,
@lib.Block(
_,
@lib.Expr([index_value, index_effect, index_result])
),
@lib.BrTable(labels, default_label),
]
)
),
@lib.RefAsNonNull,
@lib.RefGetDesc(_),
@lib.RefCastDescEq(_, _, _),
) if (match (payload, index_value, index_effect, index_result) {
(
@lib.LocalGet(_),
@lib.I32Const(_),
@lib.GlobalSet(_),
@lib.LocalGet(_),
) => true
_ => false
}) =>
match
(
RawOiEscapingLabelVecRetargetFact::retarget(labels),
RawOiEscapingLabelRetargetFact::retarget(default_label),
) {
(Some(retargeted_labels), Some(retargeted_default)) => {
kind = EscapingRefGetDescBrTable
label_retarget_count = labels.length() + 1
rewritten.push(instrs[i])
rewritten.push(payload)
rewritten.push(index_value)
rewritten.push(index_effect)
rewritten.push(index_result)
rewritten.push(
@lib.Instruction::br_table(
retargeted_labels, retargeted_default,
),
)
rewritten.push(instrs[i + 3])
rewritten.push(instrs[i + 4])
changed = true
end = i + 5
continue
}
_ => ()
}
(
@lib.Block(
_,
@lib.Expr(
[payload, index, @lib.I32Eqz, @lib.BrTable(labels, default_label)]
)
),
@lib.RefAsNonNull,
@lib.RefGetDesc(_),
@lib.RefCastDescEq(_, _, _),
) if (match (payload, index) {
(@lib.LocalGet(_), @lib.LocalGet(_)) => true
_ => false
}) =>
match
(
RawOiEscapingLabelVecRetargetFact::retarget(labels),
RawOiEscapingLabelRetargetFact::retarget(default_label),
) {
(Some(retargeted_labels), Some(retargeted_default)) => {
kind = EscapingRefGetDescBrTable
label_retarget_count = labels.length() + 1
rewritten.push(instrs[i])
rewritten.push(payload)
rewritten.push(index)
rewritten.push(@lib.Instruction::i32_eqz())
rewritten.push(
@lib.Instruction::br_table(
retargeted_labels, retargeted_default,
),
)
rewritten.push(instrs[i + 3])
rewritten.push(instrs[i + 4])
changed = true
end = i + 5
continue
}
_ => ()
}
(
@lib.Block(
_,
@lib.Expr([payload, index, @lib.BrTable(labels, default_label)])
),
@lib.RefAsNonNull,
@lib.RefGetDesc(_),
@lib.RefCastDescEq(_, _, _),
) if (match (payload, index) {
(@lib.LocalGet(_), @lib.LocalGet(_)) => true
_ => false
}) =>
match
(
RawOiEscapingLabelVecRetargetFact::retarget(labels),
RawOiEscapingLabelRetargetFact::retarget(default_label),
) {
(Some(retargeted_labels), Some(retargeted_default)) => {
kind = EscapingRefGetDescBrTable
label_retarget_count = labels.length() + 1
rewritten.push(instrs[i])
rewritten.push(payload)
rewritten.push(index)
rewritten.push(
@lib.Instruction::br_table(
retargeted_labels, retargeted_default,
),
)
rewritten.push(instrs[i + 3])
rewritten.push(instrs[i + 4])
changed = true
end = i + 5
continue
}
_ => ()
}
(
@lib.Block(
_,
@lib.Expr(
[
@lib.Block(
_,
@lib.Expr([tested_value, tested_effect, tested_result])
),
@lib.BrOnNull(label_idx),
@lib.Drop,
fallthrough,
]
)
),
@lib.RefAsNonNull,
@lib.RefGetDesc(_),
@lib.RefCastDescEq(_, _, _),
) if (match (tested_value, tested_effect, tested_result, fallthrough) {
(
@lib.I32Const(_),
@lib.GlobalSet(_),
@lib.LocalGet(_),
@lib.LocalGet(_),
) => true
_ => false
}) =>
match RawOiEscapingLabelRetargetFact::retarget(label_idx) {
Some(retargeted) => {
kind = EscapingRefGetDescBrOnNull
label_retarget_count = 1
rewritten.push(instrs[i])
rewritten.push(tested_value)
rewritten.push(tested_effect)
rewritten.push(tested_result)
rewritten.push(@lib.Instruction::br_on_null(retargeted))
rewritten.push(@lib.Instruction::drop())
rewritten.push(fallthrough)
rewritten.push(instrs[i + 3])
rewritten.push(instrs[i + 4])
changed = true
end = i + 5
continue
}
None => ()
}
(
@lib.Block(
_,
@lib.Expr(
[
tested,
@lib.BrOnNull(label_idx),
@lib.Drop,
@lib.Block(
_,
@lib.Expr(
[fallthrough_value, fallthrough_effect, fallthrough_result]
)
),
]
)
),
@lib.RefAsNonNull,
@lib.RefGetDesc(_),
@lib.RefCastDescEq(_, _, _),
) if (match
(tested, fallthrough_value, fallthrough_effect, fallthrough_result) {
(
@lib.LocalGet(_),
@lib.I32Const(_),
@lib.GlobalSet(_),
@lib.LocalGet(_),
) => true
_ => false
}) =>
match RawOiEscapingLabelRetargetFact::retarget(label_idx) {
Some(retargeted) => {
kind = EscapingRefGetDescBrOnNull
label_retarget_count = 1
rewritten.push(instrs[i])
rewritten.push(tested)
rewritten.push(@lib.Instruction::br_on_null(retargeted))
rewritten.push(@lib.Instruction::drop())
rewritten.push(fallthrough_value)
rewritten.push(fallthrough_effect)
rewritten.push(fallthrough_result)
rewritten.push(instrs[i + 3])
rewritten.push(instrs[i + 4])
changed = true
end = i + 5
continue
}
None => ()
}
(
@lib.Block(
_,
@lib.Expr(
[tested, @lib.BrOnNull(label_idx), @lib.Drop, fallthrough]
)
),
@lib.RefAsNonNull,
@lib.RefGetDesc(_),
@lib.RefCastDescEq(_, _, _),
) if (match (tested, fallthrough) {
(@lib.LocalGet(_), @lib.LocalGet(_)) => true
_ => false
}) =>
match RawOiEscapingLabelRetargetFact::retarget(label_idx) {
Some(retargeted) => {
kind = EscapingRefGetDescBrOnNull
label_retarget_count = 1
rewritten.push(instrs[i])
rewritten.push(tested)
rewritten.push(@lib.Instruction::br_on_null(retargeted))
rewritten.push(@lib.Instruction::drop())
rewritten.push(fallthrough)
rewritten.push(instrs[i + 3])
rewritten.push(instrs[i + 4])
changed = true
end = i + 5
continue
}
None => ()
}
(
@lib.Block(
_,
@lib.Expr(
[
@lib.Block(
_,
@lib.Expr([tested_value, tested_effect, tested_result])
),
@lib.BrOnNonNull(label_idx),
fallthrough,
]
)
),
@lib.RefAsNonNull,
@lib.RefGetDesc(_),
@lib.RefCastDescEq(_, _, _),
) if (match (tested_value, tested_effect, tested_result, fallthrough) {
(
@lib.I32Const(_),
@lib.GlobalSet(_),
@lib.LocalGet(_),
@lib.LocalGet(_),
) => true
_ => false
}) =>
match RawOiEscapingLabelRetargetFact::retarget(label_idx) {
Some(retargeted) => {
kind = EscapingRefGetDescBrOnNonNull
label_retarget_count = 1
rewritten.push(instrs[i])
rewritten.push(tested_value)
rewritten.push(tested_effect)
rewritten.push(tested_result)
rewritten.push(@lib.Instruction::br_on_non_null(retargeted))
rewritten.push(fallthrough)
rewritten.push(instrs[i + 3])
rewritten.push(instrs[i + 4])
changed = true
end = i + 5
continue
}
None => ()
}
(
@lib.Block(
_,
@lib.Expr(
[
tested,
@lib.BrOnNonNull(label_idx),
@lib.Block(
_,
@lib.Expr(
[fallthrough_value, fallthrough_effect, fallthrough_result]
)
),
]
)
),
@lib.RefAsNonNull,
@lib.RefGetDesc(_),
@lib.RefCastDescEq(_, _, _),
) if (match
(tested, fallthrough_value, fallthrough_effect, fallthrough_result) {
(
@lib.LocalGet(_),
@lib.I32Const(_),
@lib.GlobalSet(_),
@lib.LocalGet(_),
) => true
_ => false
}) =>
match RawOiEscapingLabelRetargetFact::retarget(label_idx) {
Some(retargeted) => {
kind = EscapingRefGetDescBrOnNonNull
label_retarget_count = 1
rewritten.push(instrs[i])
rewritten.push(tested)
rewritten.push(@lib.Instruction::br_on_non_null(retargeted))
rewritten.push(fallthrough_value)
rewritten.push(fallthrough_effect)
rewritten.push(fallthrough_result)
rewritten.push(instrs[i + 3])
rewritten.push(instrs[i + 4])
changed = true
end = i + 5
continue
}
None => ()
}
(
@lib.Block(
_,
@lib.Expr([tested, @lib.BrOnNonNull(label_idx), fallthrough])
),
@lib.RefAsNonNull,
@lib.RefGetDesc(_),
@lib.RefCastDescEq(_, _, _),
) if (match (tested, fallthrough) {
(@lib.LocalGet(_), @lib.LocalGet(_)) => true
_ => false
}) =>
match RawOiEscapingLabelRetargetFact::retarget(label_idx) {
Some(retargeted) => {
kind = EscapingRefGetDescBrOnNonNull
label_retarget_count = 1
rewritten.push(instrs[i])
rewritten.push(tested)
rewritten.push(@lib.Instruction::br_on_non_null(retargeted))
rewritten.push(fallthrough)
rewritten.push(instrs[i + 3])
rewritten.push(instrs[i + 4])
changed = true
end = i + 5
continue
}
None => ()
}
(
@lib.Block(
_,
@lib.Expr(
[
tested,
@lib.BrOnNonNull(label_idx),
@lib.Block(
_,
@lib.Expr(
[trap_lhs, trap_rhs, trap_div, @lib.Drop, fallthrough]
)
),
]
)
),
@lib.RefAsNonNull,
@lib.RefGetDesc(described_type),
@lib.RefCastDescEq(_, _, _),
) if (match (tested, trap_lhs, trap_rhs, trap_div, fallthrough) {
(
@lib.LocalGet(_),
@lib.I32Const(_),
@lib.I32Const(@lib.I32(0)),
@lib.I32DivU,
@lib.LocalGet(_),
) => true
_ => false
}) =>
match
RawOiRefGetDescResultBlockTypeFact::block_type_from_module(
module_ctx, described_type,
) {
Some(descriptor_block_type) => {
kind = EscapingRefGetDescDescriptorResultBlock
rebuilds_descriptor_result_block = true
rewritten.push(instrs[i])
rewritten.push(
@lib.Instruction::block(
descriptor_block_type,
@lib.Expr::new([
tested,
@lib.Instruction::br_on_non_null(label_idx),
trap_lhs,
trap_rhs,
trap_div,
@lib.Instruction::drop(),
fallthrough,
instrs[i + 3],
]),
),
)
rewritten.push(instrs[i + 4])
changed = true
end = i + 5
continue
}
None => ()
}
(
@lib.Block(
_,
@lib.Expr(
[
@lib.Block(
_,
@lib.Expr([tested_value, tested_effect, tested_result])
),
@lib.BrOnCast(
label_idx,
@lib.CastOp(
source_nullable,
target_nullable,
source_exact,
target_exact
),
source_heap,
target_heap
),
@lib.Drop,
@lib.Block(
_,
@lib.Expr(
[fallthrough_value, fallthrough_effect, fallthrough_result]
)
),
]
)
),
@lib.RefAsNonNull,
@lib.RefGetDesc(_),
@lib.RefCastDescEq(_, _, _),
) if (match
(
tested_value, tested_effect, tested_result, fallthrough_value, fallthrough_effect,
fallthrough_result,
) {
(
@lib.I32Const(_),
@lib.GlobalSet(_),
@lib.LocalGet(_),
@lib.I32Const(_),
@lib.GlobalSet(_),
@lib.LocalGet(_),
) => true
_ => false
}) =>
match RawOiEscapingLabelRetargetFact::retarget(label_idx) {
Some(retargeted) => {
kind = EscapingRefGetDescBrOnCast
label_retarget_count = 1
rewritten.push(instrs[i])
rewritten.push(tested_value)
rewritten.push(tested_effect)
rewritten.push(tested_result)
rewritten.push(
@lib.Instruction::br_on_cast(
retargeted,
source_nullable,
source_heap,
target_nullable,
target_heap,
source_exact~,
target_exact~,
),
)
rewritten.push(@lib.Instruction::drop())
rewritten.push(fallthrough_value)
rewritten.push(fallthrough_effect)
rewritten.push(fallthrough_result)
rewritten.push(instrs[i + 3])
rewritten.push(instrs[i + 4])
changed = true
end = i + 5
continue
}
None => ()
}
(
@lib.Block(
_,
@lib.Expr(
[
@lib.Block(
_,
@lib.Expr([tested_value, tested_effect, tested_result])
),
@lib.BrOnCastFail(_, @lib.CastOp(_, _, _, _), _, _),
@lib.Drop,
@lib.Block(
_,
@lib.Expr([success_value, success_effect, success_result])
),
]
)
),
@lib.RefAsNonNull,
@lib.RefGetDesc(_),
@lib.RefCastDescEq(_, _, _),
) if (match
(
tested_value, tested_effect, tested_result, success_value, success_effect,
success_result,
) {
(
@lib.I32Const(_),
@lib.GlobalSet(_),
@lib.LocalGet(_),
@lib.I32Const(_),
@lib.GlobalSet(_),
@lib.LocalGet(_),
) => true
_ => false
}) => {
kind = EscapingRefGetDescBrOnCastFail
rewritten.push(instrs[i])
rewritten.push(instrs[i + 1])
rewritten.push(instrs[i + 3])
rewritten.push(instrs[i + 4])
changed = true
end = i + 5
continue
}
(
@lib.Block(
_,
@lib.Expr(
[
@lib.Block(
_,
@lib.Expr([tested_value, tested_effect, tested_result])
),
@lib.BrOnCast(
label_idx,
@lib.CastOp(
source_nullable,
target_nullable,
source_exact,
target_exact
),
source_heap,
target_heap
),
@lib.Drop,
fallthrough,
]
)
),
@lib.RefAsNonNull,
@lib.RefGetDesc(_),
@lib.RefCastDescEq(_, _, _),
) if (match (tested_value, tested_effect, tested_result, fallthrough) {
(
@lib.I32Const(_),
@lib.GlobalSet(_),
@lib.LocalGet(_),
@lib.LocalGet(_),
) => true
_ => false
}) =>
match RawOiEscapingLabelRetargetFact::retarget(label_idx) {
Some(retargeted) => {
kind = EscapingRefGetDescBrOnCast
label_retarget_count = 1
rewritten.push(instrs[i])
rewritten.push(tested_value)
rewritten.push(tested_effect)
rewritten.push(tested_result)
rewritten.push(
@lib.Instruction::br_on_cast(
retargeted,
source_nullable,
source_heap,
target_nullable,
target_heap,
source_exact~,
target_exact~,
),
)
rewritten.push(@lib.Instruction::drop())
rewritten.push(fallthrough)
rewritten.push(instrs[i + 3])
rewritten.push(instrs[i + 4])
changed = true
end = i + 5
continue
}
None => ()
}
(
@lib.Block(
_,
@lib.Expr(
[
@lib.Block(
_,
@lib.Expr([tested_value, tested_effect, tested_result])
),
@lib.BrOnCastFail(
label_idx,
@lib.CastOp(
source_nullable,
target_nullable,
source_exact,
target_exact
),
source_heap,
target_heap
),
@lib.Drop,
fallthrough,
]
)
),
@lib.RefAsNonNull,
@lib.RefGetDesc(_),
@lib.RefCastDescEq(_, _, _),
) if (match (tested_value, tested_effect, tested_result, fallthrough) {
(
@lib.I32Const(_),
@lib.GlobalSet(_),
@lib.LocalGet(_),
@lib.LocalGet(_),
) => true
_ => false
}) =>
match RawOiEscapingLabelRetargetFact::retarget(label_idx) {
Some(retargeted) => {
kind = EscapingRefGetDescBrOnCastFail
label_retarget_count = 1
rewritten.push(instrs[i])
rewritten.push(tested_value)
rewritten.push(tested_effect)
rewritten.push(tested_result)
rewritten.push(
@lib.Instruction::br_on_cast_fail(
retargeted,
source_nullable,
source_heap,
target_nullable,
target_heap,
source_exact~,
target_exact~,
),
)
rewritten.push(@lib.Instruction::drop())
rewritten.push(fallthrough)
rewritten.push(instrs[i + 3])
rewritten.push(instrs[i + 4])
changed = true
end = i + 5
continue
}
None => ()
}
(
@lib.Block(
_,
@lib.Expr(
[
tested,
@lib.BrOnCast(
label_idx,
@lib.CastOp(
source_nullable,
target_nullable,
source_exact,
target_exact
),
source_heap,
target_heap
),
@lib.Drop,
@lib.Block(
_,
@lib.Expr(
[fallthrough_value, fallthrough_effect, fallthrough_result]
)
),
]
)
),
@lib.RefAsNonNull,
@lib.RefGetDesc(_),
@lib.RefCastDescEq(_, _, _),
) if (match
(tested, fallthrough_value, fallthrough_effect, fallthrough_result) {
(
@lib.LocalGet(_),
@lib.I32Const(_),
@lib.GlobalSet(_),
@lib.LocalGet(_),
) => true
_ => false
}) =>
match RawOiEscapingLabelRetargetFact::retarget(label_idx) {
Some(retargeted) => {
kind = EscapingRefGetDescBrOnCast
label_retarget_count = 1
rewritten.push(instrs[i])
rewritten.push(tested)
rewritten.push(
@lib.Instruction::br_on_cast(
retargeted,
source_nullable,
source_heap,
target_nullable,
target_heap,
source_exact~,
target_exact~,
),
)
rewritten.push(@lib.Instruction::drop())
rewritten.push(fallthrough_value)
rewritten.push(fallthrough_effect)
rewritten.push(fallthrough_result)
rewritten.push(instrs[i + 3])
rewritten.push(instrs[i + 4])
changed = true
end = i + 5
continue
}
None => ()
}
(
@lib.Block(
_,
@lib.Expr(
[
tested,
@lib.BrOnCastFail(
label_idx,
@lib.CastOp(
source_nullable,
target_nullable,
source_exact,
target_exact
),
source_heap,
target_heap
),
@lib.Drop,
@lib.Block(
_,
@lib.Expr(
[fallthrough_value, fallthrough_effect, fallthrough_result]
)
),
]
)
),
@lib.RefAsNonNull,
@lib.RefGetDesc(_),
@lib.RefCastDescEq(_, _, _),
) if (match
(tested, fallthrough_value, fallthrough_effect, fallthrough_result) {
(
@lib.LocalGet(_),
@lib.I32Const(_),
@lib.GlobalSet(_),
@lib.LocalGet(_),
) => true
_ => false
}) =>
match RawOiEscapingLabelRetargetFact::retarget(label_idx) {
Some(retargeted) => {
kind = EscapingRefGetDescBrOnCastFail
label_retarget_count = 1
rewritten.push(instrs[i])
rewritten.push(tested)
rewritten.push(
@lib.Instruction::br_on_cast_fail(
retargeted,
source_nullable,
source_heap,
target_nullable,
target_heap,
source_exact~,
target_exact~,
),
)
rewritten.push(@lib.Instruction::drop())
rewritten.push(fallthrough_value)
rewritten.push(fallthrough_effect)
rewritten.push(fallthrough_result)
rewritten.push(instrs[i + 3])
rewritten.push(instrs[i + 4])
changed = true
end = i + 5
continue
}
None => ()
}
(
@lib.Block(
_,
@lib.Expr(
[
tested,
@lib.BrOnCast(
label_idx,
@lib.CastOp(
source_nullable,
target_nullable,
source_exact,
target_exact
),
source_heap,
target_heap
),
@lib.Drop,
fallthrough,
]
)
),
@lib.RefAsNonNull,
@lib.RefGetDesc(_),
@lib.RefCastDescEq(_, _, _),
) if (match (tested, fallthrough) {
(@lib.LocalGet(_), @lib.LocalGet(_)) => true
_ => false
}) =>
match RawOiEscapingLabelRetargetFact::retarget(label_idx) {
Some(retargeted) => {
kind = EscapingRefGetDescBrOnCast
label_retarget_count = 1
rewritten.push(instrs[i])
rewritten.push(tested)
rewritten.push(
@lib.Instruction::br_on_cast(
retargeted,
source_nullable,
source_heap,
target_nullable,
target_heap,
source_exact~,
target_exact~,
),
)
rewritten.push(@lib.Instruction::drop())
rewritten.push(fallthrough)
rewritten.push(instrs[i + 3])
rewritten.push(instrs[i + 4])
changed = true
end = i + 5
continue
}
None => ()
}
(
@lib.Block(
_,
@lib.Expr(
[
tested,
@lib.BrOnCastFail(
label_idx,
@lib.CastOp(
source_nullable,
target_nullable,
source_exact,
target_exact
),
source_heap,
target_heap
),
@lib.Drop,
fallthrough,
]
)
),
@lib.RefAsNonNull,
@lib.RefGetDesc(_),
@lib.RefCastDescEq(_, _, _),
) if (match (tested, fallthrough) {
(@lib.LocalGet(_), @lib.LocalGet(_)) => true
_ => false
}) =>
match RawOiEscapingLabelRetargetFact::retarget(label_idx) {
Some(retargeted) => {
kind = EscapingRefGetDescBrOnCastFail
label_retarget_count = 1
rewritten.push(instrs[i])
rewritten.push(tested)
rewritten.push(
@lib.Instruction::br_on_cast_fail(
retargeted,
source_nullable,
source_heap,
target_nullable,
target_heap,
source_exact~,
target_exact~,
),
)
rewritten.push(@lib.Instruction::drop())
rewritten.push(fallthrough)
rewritten.push(instrs[i + 3])
rewritten.push(instrs[i + 4])
changed = true
end = i + 5
continue
}
None => ()
}
(
@lib.Block(
_,
@lib.Expr(
[
payload_left,
payload_right,
payload_condition,
@lib.Select(select_types),
condition,
@lib.BrIf(label_idx),
@lib.Drop,
fallthrough,
]
)
),
@lib.RefAsNonNull,
@lib.RefGetDesc(_),
@lib.RefCastDescEq(_, _, _),
) if (match
(
payload_left, payload_right, payload_condition, condition, fallthrough,
) {
(
@lib.LocalGet(_),
@lib.LocalGet(_),
@lib.LocalGet(_),
@lib.LocalGet(_),
@lib.LocalGet(_),
) => true
_ => false
}) =>
match RawOiEscapingLabelRetargetFact::retarget(label_idx) {
Some(retargeted) => {
kind = EscapingRefGetDescBrIf
label_retarget_count = 1
rewritten.push(instrs[i])
rewritten.push(payload_left)
rewritten.push(payload_right)
rewritten.push(payload_condition)
rewritten.push(@lib.Instruction::select(types=select_types))
rewritten.push(condition)
rewritten.push(@lib.Instruction::br_if(retargeted))
rewritten.push(@lib.Instruction::drop())
rewritten.push(fallthrough)
rewritten.push(instrs[i + 3])
rewritten.push(instrs[i + 4])
changed = true
end = i + 5
continue
}
None => ()
}
(
@lib.Block(
_,
@lib.Expr(
[
@lib.Block(
_,
@lib.Expr([payload_value, payload_effect, payload_result])
),
condition,
@lib.BrIf(label_idx),
@lib.Drop,
fallthrough,
]
)
),
@lib.RefAsNonNull,
@lib.RefGetDesc(_),
@lib.RefCastDescEq(_, _, _),
) if (match
(
payload_value, payload_effect, payload_result, condition, fallthrough,
) {
(
@lib.I32Const(_),
@lib.GlobalSet(_),
@lib.LocalGet(_),
@lib.LocalGet(_),
@lib.LocalGet(_),
) => true
_ => false
}) =>
match RawOiEscapingLabelRetargetFact::retarget(label_idx) {
Some(retargeted) => {
kind = EscapingRefGetDescBrIf
label_retarget_count = 1
rewritten.push(instrs[i])
rewritten.push(payload_value)
rewritten.push(payload_effect)
rewritten.push(payload_result)
rewritten.push(condition)
rewritten.push(@lib.Instruction::br_if(retargeted))
rewritten.push(@lib.Instruction::drop())
rewritten.push(fallthrough)
rewritten.push(instrs[i + 3])
rewritten.push(instrs[i + 4])
changed = true
end = i + 5
continue
}
None => ()
}
(
@lib.Block(
_,
@lib.Expr(
[
payload,
@lib.Block(
_,
@lib.Expr(
[condition_value, condition_effect, condition_result]
)
),
@lib.BrIf(label_idx),
@lib.Drop,
fallthrough,
]
)
),
@lib.RefAsNonNull,
@lib.RefGetDesc(_),
@lib.RefCastDescEq(_, _, _),
) if (match
(
payload, condition_value, condition_effect, condition_result, fallthrough,
) {
(
@lib.LocalGet(_),
@lib.I32Const(_),
@lib.GlobalSet(_),
@lib.LocalGet(_),
@lib.LocalGet(_),
) => true
_ => false
}) =>
match RawOiEscapingLabelRetargetFact::retarget(label_idx) {
Some(retargeted) => {
kind = EscapingRefGetDescBrIf
label_retarget_count = 1
rewritten.push(instrs[i])
rewritten.push(payload)
rewritten.push(condition_value)
rewritten.push(condition_effect)
rewritten.push(condition_result)
rewritten.push(@lib.Instruction::br_if(retargeted))
rewritten.push(@lib.Instruction::drop())
rewritten.push(fallthrough)
rewritten.push(instrs[i + 3])
rewritten.push(instrs[i + 4])
changed = true
end = i + 5
continue
}
None => ()
}
(
@lib.Block(
_,
@lib.Expr(
[
payload,
condition_left,
condition_right,
condition_select,
@lib.Select(select_types),
@lib.BrIf(label_idx),
@lib.Drop,
fallthrough,
]
)
),
@lib.RefAsNonNull,
@lib.RefGetDesc(_),
@lib.RefCastDescEq(_, _, _),
) if (match
(
payload, condition_left, condition_right, condition_select, fallthrough,
) {
(
@lib.LocalGet(_),
@lib.LocalGet(_),
@lib.LocalGet(_),
@lib.LocalGet(_),
@lib.LocalGet(_),
) => true
_ => false
}) =>
match RawOiEscapingLabelRetargetFact::retarget(label_idx) {
Some(retargeted) => {
kind = EscapingRefGetDescBrIf
label_retarget_count = 1
rewritten.push(instrs[i])
rewritten.push(payload)
rewritten.push(condition_left)
rewritten.push(condition_right)
rewritten.push(condition_select)
rewritten.push(@lib.Instruction::select(types=select_types))
rewritten.push(@lib.Instruction::br_if(retargeted))
rewritten.push(@lib.Instruction::drop())
rewritten.push(fallthrough)
rewritten.push(instrs[i + 3])
rewritten.push(instrs[i + 4])
changed = true
end = i + 5
continue
}
None => ()
}
(
@lib.Block(
_,
@lib.Expr(
[
payload,
condition,
@lib.I32Eqz,
@lib.BrIf(label_idx),
@lib.Drop,
fallthrough,
]
)
),
@lib.RefAsNonNull,
@lib.RefGetDesc(_),
@lib.RefCastDescEq(_, _, _),
) if (match (payload, condition, fallthrough) {
(@lib.LocalGet(_), @lib.LocalGet(_), @lib.LocalGet(_)) => true
_ => false
}) =>
match RawOiEscapingLabelRetargetFact::retarget(label_idx) {
Some(retargeted) => {
kind = EscapingRefGetDescBrIf
label_retarget_count = 1
rewritten.push(instrs[i])
rewritten.push(payload)
rewritten.push(condition)
rewritten.push(@lib.Instruction::i32_eqz())
rewritten.push(@lib.Instruction::br_if(retargeted))
rewritten.push(@lib.Instruction::drop())
rewritten.push(fallthrough)
rewritten.push(instrs[i + 3])
rewritten.push(instrs[i + 4])
changed = true
end = i + 5
continue
}
None => ()
}
(
@lib.Block(
_,
@lib.Expr(
[
payload,
condition,
@lib.BrIf(label_idx),
@lib.Drop,
@lib.Block(
_,
@lib.Expr(
[fallthrough_value, fallthrough_effect, fallthrough_result]
)
),
]
)
),
@lib.RefAsNonNull,
@lib.RefGetDesc(_),
@lib.RefCastDescEq(_, _, _),
) if (match
(
payload, condition, fallthrough_value, fallthrough_effect, fallthrough_result,
) {
(
@lib.LocalGet(_),
@lib.LocalGet(_),
@lib.I32Const(_),
@lib.GlobalSet(_),
@lib.LocalGet(_),
) => true
_ => false
}) =>
match RawOiEscapingLabelRetargetFact::retarget(label_idx) {
Some(retargeted) => {
kind = EscapingRefGetDescBrIf
label_retarget_count = 1
rewritten.push(instrs[i])
rewritten.push(payload)
rewritten.push(condition)
rewritten.push(@lib.Instruction::br_if(retargeted))
rewritten.push(@lib.Instruction::drop())
rewritten.push(fallthrough_value)
rewritten.push(fallthrough_effect)
rewritten.push(fallthrough_result)
rewritten.push(instrs[i + 3])
rewritten.push(instrs[i + 4])
changed = true
end = i + 5
continue
}
None => ()
}
(
@lib.Block(
_,
@lib.Expr(
[payload, condition, @lib.BrIf(label_idx), @lib.Drop, fallthrough]
)
),
@lib.RefAsNonNull,
@lib.RefGetDesc(_),
@lib.RefCastDescEq(_, _, _),
) if (match (payload, condition, fallthrough) {
(@lib.LocalGet(_), @lib.LocalGet(_), @lib.LocalGet(_)) => true
_ => false
}) =>
match RawOiEscapingLabelRetargetFact::retarget(label_idx) {
Some(retargeted) => {
kind = EscapingRefGetDescBrIf
label_retarget_count = 1
rewritten.push(instrs[i])
rewritten.push(payload)
rewritten.push(condition)
rewritten.push(@lib.Instruction::br_if(retargeted))
rewritten.push(@lib.Instruction::drop())
rewritten.push(fallthrough)
rewritten.push(instrs[i + 3])
rewritten.push(instrs[i + 4])
changed = true
end = i + 5
continue
}
None => ()
}
_ => ()
}
}
if i + 3 < instrs.length() {
match (instrs[i + 1], instrs[i + 2], instrs[i + 3]) {
(
@lib.Block(_, @lib.Expr([payload, @lib.Br(label_idx)])),
@lib.RefGetDesc(_),
@lib.RefCastDescEq(_, _, _),
) if (match payload {
@lib.LocalGet(_) => true
_ => false
}) =>
match RawOiEscapingLabelRetargetFact::retarget(label_idx) {
Some(retargeted) => {
kind = EscapingRefGetDescBrPayload
label_retarget_count = 1
rewritten.push(instrs[i])
rewritten.push(payload)
rewritten.push(@lib.Instruction::br(retargeted))
rewritten.push(instrs[i + 2])
rewritten.push(instrs[i + 3])
changed = true
end = i + 4
continue
}
None => ()
}
_ => ()
}
}
break
}
if changed && end == candidate.end {
Some({
end,
kind,
label_retarget_count,
rebuilds_descriptor_result_block,
rewrite: rewritten,
})
} else {
None
}
}
///|
// Accepted raw OI-J traversal bridge over `RawOiEscapingRefGetDescFlattenFact`.
// Each recursive body constructs one immutable region view, so outer candidate
// queries do not rescan that body. The fact owns represented payload/control
// proof, label retargeting, descriptor-result rebuilding, and the complete
// rewrite. Reopen if direct windows or proof cases reappear in this walker, or
// when a typed label/control/effect graph can replace the remaining bridge.
fn run_hot_pipeline_raw_oi_flatten_escaping_ref_get_desc_body(
instrs : Array[@lib.Instruction],
module_ctx : @ir.HotModuleContext,
) -> (Array[@lib.Instruction], Bool) {
let rewritten : Array[@lib.Instruction] = []
let region = RawOiInstructionRegionFact::from_instrs(instrs)
let mut changed = false
let mut i = 0
while i < instrs.length() {
match RawOiEscapingRefGetDescCandidateFact::from_region_at(region, i) {
Some(candidate) =>
match
RawOiEscapingRefGetDescFlattenFact::from_candidate(
candidate, instrs, module_ctx,
) {
Some(fact) if fact.proves_binaryen_escaping_ref_get_desc_flattening() => {
fact.append_rewrite(rewritten)
changed = true
i = fact.end
continue
}
_ => ()
}
None => ()
}
match instrs[i] {
@lib.Block(block_type, @lib.Expr(body)) => {
let (next_body, nested_changed) = run_hot_pipeline_raw_oi_flatten_escaping_ref_get_desc_body(
body, module_ctx,
)
if nested_changed {
rewritten.push(
@lib.Instruction::block(block_type, @lib.Expr::new(next_body)),
)
changed = true
} else {
rewritten.push(instrs[i])
}
}
@lib.Loop(block_type, @lib.Expr(body)) => {
let (next_body, nested_changed) = run_hot_pipeline_raw_oi_flatten_escaping_ref_get_desc_body(
body, module_ctx,
)
if nested_changed {
rewritten.push(
@lib.Instruction::loop_(block_type, @lib.Expr::new(next_body)),
)
changed = true
} else {
rewritten.push(instrs[i])
}
}
@lib.TryTable(block_type, catches, @lib.Expr(body)) => {
let (next_body, nested_changed) = run_hot_pipeline_raw_oi_flatten_escaping_ref_get_desc_body(
body, module_ctx,
)
if nested_changed {
rewritten.push(
@lib.Instruction::try_table(
block_type,
catches,
@lib.Expr::new(next_body),
),
)
changed = true
} else {
rewritten.push(instrs[i])
}
}
@lib.If(block_type, then_body, else_body) => {
let (next_then, then_changed) = run_hot_pipeline_raw_oi_flatten_escaping_ref_get_desc_body(
then_body, module_ctx,
)
let mut any_changed = then_changed
let next_else = match else_body {
Some(else_body) => {
let (next_else, else_changed) = run_hot_pipeline_raw_oi_flatten_escaping_ref_get_desc_body(
else_body, module_ctx,
)
any_changed = any_changed || else_changed
Some(next_else)
}
None => None
}
if any_changed {
rewritten.push(
@lib.Instruction::if_(block_type, next_then, next_else),
)
changed = true
} else {
rewritten.push(instrs[i])
}
}
_ => rewritten.push(instrs[i])
}
i = i + 1
}
(rewritten, changed)
}
///|
fn run_hot_pipeline_optimize_instructions_canonicalize_lowered_func(
func : @lib.Func,
descriptor_name : String,
mod_ : @lib.Module,
module_ctx~ : @ir.HotModuleContext? = None,
) -> @lib.Func {
if descriptor_name != "optimize-instructions" {
return func
}
match func {
@lib.Func(locals, @lib.Expr(body)) => {
let module_ctx = match module_ctx {
Some(ctx) => ctx
None => @ir.hot_module_context_from_module(mod_)
}
let (rewritten, changed) = run_hot_pipeline_raw_oi_flatten_escaping_ref_get_desc_body(
body, module_ctx,
)
if changed {
@lib.Func::new(locals, @lib.Expr::new(rewritten))
} else {
func
}
}
}
}
///|
fn run_hot_pipeline_merge_blocks_common_null_bottom(
current_ref : @lib.RefType,
source_ref : @lib.RefType,
) -> @lib.RefType? {
if !current_ref.is_nullable() || !source_ref.is_nullable() {
return None
}
match
(
remove_unused_brs_null_bottom_ref_type(current_ref),
remove_unused_brs_null_bottom_ref_type(source_ref),
) {
(Some(current_bottom), Some(source_bottom)) if current_bottom ==
source_bottom => Some(current_bottom)
_ => None
}
}
///|
fn run_hot_pipeline_merge_blocks_br_table_all_self(
labels : Array[@lib.LabelIdx],
fallback : @lib.LabelIdx,
) -> Bool {
if fallback != @lib.LabelIdx::new(0U) {
return false
}
for label in labels {
if label != @lib.LabelIdx::new(0U) {
return false
}
}
true
}
///|
fn run_hot_pipeline_merge_blocks_narrow_all_null_block(
current_ref : @lib.RefType,
body : Array[@lib.Instruction],
) -> @lib.Instruction? {
if body.length() == 2 {
match (body[0], body[1]) {
(@lib.RefNull(source_ref), @lib.Br(@lib.LabelIdx(0U))) =>
match
run_hot_pipeline_merge_blocks_common_null_bottom(
current_ref, source_ref,
) {
Some(bottom_ref) =>
return Some(
@lib.Instruction::block(
@lib.BlockType::val_type(@lib.ValType::ref_type(bottom_ref)),
@lib.Expr::new([
@lib.Instruction::ref_null_type(bottom_ref),
body[1],
]),
),
)
None => ()
}
_ => ()
}
}
if body.length() == 3 {
match (body[0], body[2]) {
(@lib.RefNull(source_ref), @lib.BrIf(@lib.LabelIdx(0U))) =>
match
run_hot_pipeline_merge_blocks_common_null_bottom(
current_ref, source_ref,
) {
Some(bottom_ref) =>
return Some(
@lib.Instruction::block(
@lib.BlockType::val_type(@lib.ValType::ref_type(bottom_ref)),
@lib.Expr::new([
@lib.Instruction::ref_null_type(bottom_ref),
body[1],
body[2],
]),
),
)
None => ()
}
_ => ()
}
}
if body.length() >= 3 {
match (body[0], body[2]) {
(@lib.RefNull(source_ref), @lib.BrTable(labels, fallback)) if run_hot_pipeline_merge_blocks_br_table_all_self(
labels, fallback,
) =>
match
run_hot_pipeline_merge_blocks_common_null_bottom(
current_ref, source_ref,
) {
Some(bottom_ref) =>
return Some(
@lib.Instruction::block(
@lib.BlockType::val_type(@lib.ValType::ref_type(bottom_ref)),
@lib.Expr::new([
@lib.Instruction::ref_null_type(bottom_ref),
body[1],
body[2],
]),
),
)
None => ()
}
_ => ()
}
}
if body.length() == 4 {
match (body[0], body[2], body[3]) {
(
@lib.RefNull(source_ref),
@lib.BrIf(@lib.LabelIdx(0U)),
@lib.RefCast(_, _, _),
) =>
match
run_hot_pipeline_merge_blocks_common_null_bottom(
current_ref, source_ref,
) {
Some(bottom_ref) =>
return Some(
@lib.Instruction::block(
@lib.BlockType::val_type(@lib.ValType::ref_type(bottom_ref)),
@lib.Expr::new([
@lib.Instruction::ref_null_type(bottom_ref),
body[1],
body[2],
]),
),
)
None => ()
}
_ => ()
}
}
if body.length() == 2 {
match (body[0], body[1]) {
(@lib.Block(@lib.VoidBlockType, @lib.Expr(inner)), @lib.Unreachable) if inner.length() >=
4 =>
match (inner[0], inner[2], inner[3]) {
(
@lib.RefNull(source_ref),
@lib.BrIf(@lib.LabelIdx(1U)),
@lib.Br(@lib.LabelIdx(1U)),
) =>
match
run_hot_pipeline_merge_blocks_common_null_bottom(
current_ref, source_ref,
) {
Some(bottom_ref) =>
return Some(
@lib.Instruction::block(
@lib.BlockType::val_type(@lib.ValType::ref_type(bottom_ref)),
@lib.Expr::new([
@lib.Instruction::block(
@lib.BlockType::void_(),
@lib.Expr::new([
@lib.Instruction::ref_null_type(bottom_ref),
inner[1],
inner[2],
inner[3],
]),
),
body[1],
]),
),
)
None => ()
}
_ => ()
}
_ => ()
}
}
None
}
///|
fn run_hot_pipeline_merge_blocks_refinalize_lowered_null_instrs(
instrs : Array[@lib.Instruction],
) -> Array[@lib.Instruction] {
let out : Array[@lib.Instruction] = []
for instr in instrs {
match instr {
@lib.Block(block_type, @lib.Expr(body)) => {
let body = run_hot_pipeline_merge_blocks_refinalize_lowered_null_instrs(
body,
)
match block_type {
@lib.ValTypeBlockType(@lib.RefTypeValType(current_ref)) =>
match
run_hot_pipeline_merge_blocks_narrow_all_null_block(
current_ref, body,
) {
Some(rewritten) => {
out.push(rewritten)
continue
}
None => ()
}
_ => ()
}
out.push(@lib.Instruction::block(block_type, @lib.Expr::new(body)))
}
@lib.Loop(block_type, @lib.Expr(body)) =>
out.push(
@lib.Instruction::loop_(
block_type,
@lib.Expr::new(
run_hot_pipeline_merge_blocks_refinalize_lowered_null_instrs(body),
),
),
)
@lib.If(block_type, then_body, else_body) =>
out.push(
@lib.Instruction::if_(
block_type,
run_hot_pipeline_merge_blocks_refinalize_lowered_null_instrs(
then_body,
),
match else_body {
Some(body) =>
Some(
run_hot_pipeline_merge_blocks_refinalize_lowered_null_instrs(
body,
),
)
None => None
},
),
)
@lib.TryTable(block_type, catches, @lib.Expr(body)) =>
out.push(
@lib.Instruction::try_table(
block_type,
catches,
@lib.Expr::new(
run_hot_pipeline_merge_blocks_refinalize_lowered_null_instrs(body),
),
),
)
_ => out.push(instr)
}
}
out
}
///|
fn run_hot_pipeline_merge_blocks_refinalize_lowered_null_blocks(
func : @lib.Func,
) -> @lib.Func {
let @lib.Func(locals, @lib.Expr(body)) = func
@lib.Func::new(
locals,
@lib.Expr::new(
run_hot_pipeline_merge_blocks_refinalize_lowered_null_instrs(body),
),
)
}
///|
fn run_hot_pipeline_merge_blocks_scalar_result_block_type(
block_type : @lib.BlockType,
module_ctx : @ir.HotModuleContext,
) -> Bool {
match block_type {
@lib.VoidBlockType => false
@lib.ValTypeBlockType(_) => true
@lib.TypeIdxBlockType(type_idx) =>
match @ir.hot_module_resolve_type_func_type(module_ctx, type_idx) {
Some(@lib.FuncType(params, results)) =>
params.is_empty() && results.length() == 1
None => false
}
}
}
///|
fn run_hot_pipeline_merge_blocks_flatten_lowered_spill_instrs(
instrs : Array[@lib.Instruction],
module_ctx : @ir.HotModuleContext,
) -> (Array[@lib.Instruction], Bool) {
let out : Array[@lib.Instruction] = []
let mut changed = false
for instr in instrs {
let (current, nested_changed) = match instr {
@lib.Block(bt, @lib.Expr(body)) => {
let (next, child_changed) = run_hot_pipeline_merge_blocks_flatten_lowered_spill_instrs(
body, module_ctx,
)
(@lib.Instruction::block(bt, @lib.Expr::new(next)), child_changed)
}
@lib.Loop(bt, @lib.Expr(body)) => {
let (next, child_changed) = run_hot_pipeline_merge_blocks_flatten_lowered_spill_instrs(
body, module_ctx,
)
(@lib.Instruction::loop_(bt, @lib.Expr::new(next)), child_changed)
}
@lib.If(bt, then_body, else_body) => {
let (next_then, then_changed) = run_hot_pipeline_merge_blocks_flatten_lowered_spill_instrs(
then_body, module_ctx,
)
let (next_else, else_changed) = match else_body {
Some(body) => {
let (next, child_changed) = run_hot_pipeline_merge_blocks_flatten_lowered_spill_instrs(
body, module_ctx,
)
(Some(next), child_changed)
}
None => (None, false)
}
(
@lib.Instruction::if_(bt, next_then, next_else),
then_changed || else_changed,
)
}
@lib.TryTable(bt, catches, @lib.Expr(body)) => {
let (next, child_changed) = run_hot_pipeline_merge_blocks_flatten_lowered_spill_instrs(
body, module_ctx,
)
(
@lib.Instruction::try_table(bt, catches, @lib.Expr::new(next)),
child_changed,
)
}
_ => (instr, false)
}
changed = changed || nested_changed
match current {
@lib.Block(block_type, @lib.Expr(body)) if run_hot_pipeline_merge_blocks_scalar_result_block_type(
block_type, module_ctx,
) &&
body.length() >= 3 &&
body.length() % 2 == 1 &&
body[body.length() - 1] is @lib.LocalGet(_) => {
let mut spill_only = true
for idx = 0; idx + 1 < body.length() - 1; idx = idx + 2 {
if !run_hot_pipeline_raw_merge_blocks_context_free_leaf(body[idx]) ||
!(body[idx + 1] is @lib.LocalSet(_)) {
spill_only = false
break
}
}
if spill_only {
for item in body {
out.push(item)
}
changed = true
continue
}
}
_ => ()
}
out.push(current)
}
(out, changed)
}
///|
fn run_hot_pipeline_merge_blocks_flatten_lowered_spill_blocks(
func : @lib.Func,
module_ctx : @ir.HotModuleContext,
) -> @lib.Func {
let @lib.Func(locals, @lib.Expr(body)) = func
let (next, changed) = run_hot_pipeline_merge_blocks_flatten_lowered_spill_instrs(
body, module_ctx,
)
if changed {
@lib.Func::new(locals, @lib.Expr::new(next))
} else {
func
}
}
///|
fn run_hot_pipeline_precompute_has_void_self_branch_block(
func : @ir.HotFunc,
) -> Bool {
for node_id = 0; node_id < @ir.hot_node_count(func); node_id = node_id + 1 {
if !@ir.hot_node_is_live(func, node_id) {
continue
}
match @ir.hot_node_get(func, node_id).op {
@ir.HotOp::Block if @ir.hot_node_result_arity(func, node_id) == 0 => ()
_ => continue
}
let body_ref = @ir.hot_region_body_ref(func, node_id)
if @ir.hot_region_root_count(func, body_ref) != 1 {
continue
}
let branch_id = @ir.hot_region_get(func, body_ref, 0)
let branch = @ir.hot_node_get(func, branch_id)
match branch.op {
@ir.HotOp::Br if branch.child_count == 0 &&
branch.imm0 == @ir.hot_control_node_label(func, node_id) => return true
_ => ()
}
}
false
}
///|
fn run_hot_pipeline_precompute_drop_void_self_branch_blocks_instrs(
instrs : Array[@lib.Instruction],
) -> Array[@lib.Instruction] {
let out : Array[@lib.Instruction] = []
for instr in instrs {
match instr {
@lib.Block(@lib.VoidBlockType, @lib.Expr([@lib.Br(@lib.LabelIdx(0U))])) =>
()
@lib.Block(block_type, @lib.Expr(body)) =>
out.push(
@lib.Instruction::block(
block_type,
@lib.Expr::new(
run_hot_pipeline_precompute_drop_void_self_branch_blocks_instrs(
body,
),
),
),
)
@lib.Loop(block_type, @lib.Expr(body)) =>
out.push(
@lib.Instruction::loop_(
block_type,
@lib.Expr::new(
run_hot_pipeline_precompute_drop_void_self_branch_blocks_instrs(
body,
),
),
),
)
@lib.If(block_type, then_body, else_body) =>
out.push(
@lib.Instruction::if_(
block_type,
run_hot_pipeline_precompute_drop_void_self_branch_blocks_instrs(
then_body,
),
match else_body {
Some(body) =>
Some(
run_hot_pipeline_precompute_drop_void_self_branch_blocks_instrs(
body,
),
)
None => None
},
),
)
@lib.TryTable(block_type, catches, @lib.Expr(body)) =>
out.push(
@lib.Instruction::try_table(
block_type,
catches,
@lib.Expr::new(
run_hot_pipeline_precompute_drop_void_self_branch_blocks_instrs(
body,
),
),
),
)
_ => out.push(instr)
}
}
out
}
///|
fn run_hot_pipeline_precompute_drop_void_self_branch_blocks(
func : @lib.Func,
) -> @lib.Func {
match func {
@lib.Func(locals, @lib.Expr(body)) =>
@lib.Func::new(
locals,
@lib.Expr::new(
run_hot_pipeline_precompute_drop_void_self_branch_blocks_instrs(body),
),
)
}
}
///|
fn run_hot_pipeline_optimize_instructions_cleanup_tuple_wrappers(
func : @lib.Func,
original : @lib.Func?,
mod_ : @lib.Module,
module_ctx~ : @ir.HotModuleContext? = None,
module_env_provider~ : (() -> @validate.Env)? = None,
) -> (@lib.Func, Bool) {
match original {
Some(@lib.Func(_, @lib.Expr(body))) if run_hot_pipeline_raw_tuple_has_multivalue_producer(
body,
match module_ctx {
Some(ctx) => ctx
None => @ir.hot_module_context_from_module(mod_)
},
) =>
pass_lower_cleanup_branchless_blocks(
func,
mod_,
module_env=match module_env_provider {
Some(provide) => Some(provide())
None => None
},
)
_ => (func, false)
}
}
///|
fn run_hot_pipeline_canonicalize_lowered_func_for_descriptor(
func : @lib.Func,
descriptor_name : String,
original_func : @lib.Func?,
func_idx : @lib.FuncIdx,
mod_ : @lib.Module,
module_ctx~ : @ir.HotModuleContext? = None,
module_env~ : @validate.Env? = None,
module_env_provider~ : (() -> @validate.Env)? = None,
) -> @lib.Func {
let func = if descriptor_name == "constraint-analysis" ||
descriptor_name == "tuple-optimization" ||
descriptor_name == "optimize-instructions" {
match
(
original_func,
run_hot_pipeline_defined_func_params_from_sections(mod_, func_idx),
) {
(Some(original), Some(params)) =>
pass_lower_compact_captures(
if descriptor_name == "constraint-analysis" {
ca_lower_refinalize(
func,
mod_,
func_idx,
module_env=match module_env {
Some(env) => Some(env)
None =>
match module_env_provider {
Some(provide) => Some(provide())
None => None
}
},
)
} else {
func
},
original,
params,
)
_ => func
}
} else {
func
}
let func = if descriptor_name == "remove-unused-brs" {
remove_unused_brs_refinalize_lowered_null_blocks(func)
} else if descriptor_name == "merge-blocks" {
run_hot_pipeline_merge_blocks_refinalize_lowered_null_blocks(func)
} else {
func
}
let cleaned = run_hot_pipeline_simplify_locals_cleanup_lowered_func(
func, descriptor_name, original_func, mod_,
)
let cleaned = if descriptor_name == "simplify-locals" {
// Fold only after lowering has fixed evaluation order. The existing
// adjacent-pair rewrite preserves the producer, local write and value;
// applying it here also preserves the no-tee variants' contract.
run_hot_pipeline_simplify_locals_fold_adjacent_captures_func(cleaned).0
} else {
cleaned
}
// SimplifyLocals may consume a lowering capture while leaving its scratch
// declaration behind. Preserve source locals and compact only unused suffix
// declarations; this does not move or remove any instruction evaluation.
let cleaned = if run_hot_pipeline_descriptor_is_simplify_locals_family(
descriptor_name,
) {
match
(
original_func,
run_hot_pipeline_defined_func_params_from_sections(mod_, func_idx),
) {
(Some(@lib.Func(original_locals, _)), Some(params)) =>
run_hot_pipeline_raw_compact_unused_locals_after_prefix(
cleaned,
params,
original_locals.length(),
).0
_ => cleaned
}
} else {
cleaned
}
let cleaned = if descriptor_name == "merge-blocks" {
let refinalized = run_hot_pipeline_merge_blocks_refinalize_lowered_null_blocks(
cleaned,
)
let flattened = run_hot_pipeline_merge_blocks_flatten_lowered_spill_blocks(
refinalized,
@ir.hot_module_context_from_module(mod_),
)
match
(
original_func,
run_hot_pipeline_defined_func_params_from_sections(mod_, func_idx),
) {
(Some(@lib.Func(original_locals, _)), Some(params)) => {
let (compacted, _) = run_hot_pipeline_raw_compact_unused_locals_after_prefix(
flattened,
params,
original_locals.length(),
)
compacted
}
_ => flattened
}
} else {
cleaned
}
let cleaned = run_hot_pipeline_optimize_instructions_canonicalize_lowered_func(
cleaned,
descriptor_name,
mod_,
module_ctx~,
)
let cleaned = if descriptor_name == "optimize-instructions" {
// Preserve lane evaluation order while removing branchless wrappers that
// obstruct downstream local cleanup after multivalue lowering.
run_hot_pipeline_optimize_instructions_cleanup_tuple_wrappers(
cleaned,
original_func,
mod_,
module_ctx~,
module_env_provider~,
).0
} else {
cleaned
}
let cleaned = if descriptor_name == "flatten" {
let trimmed = flatten_cleanup_lowered_root_trap(cleaned)
match (original_func, trimmed) {
(
Some(@lib.Func(original_locals, _)),
@lib.Func(_, @lib.Expr([@lib.Unreachable])),
) =>
// No instruction can reference a scratch local in this body. Keep
// source declarations and their names; remove only lowering captures.
@lib.Func::new(
original_locals,
@lib.Expr::new([@lib.Instruction::unreachable_()]),
)
_ => trimmed
}
} else {
cleaned
}
let cleaned = if descriptor_name == "precompute-propagate" {
run_hot_pipeline_precompute_drop_void_self_branch_blocks(cleaned)
} else {
cleaned
}
let cleaned = if descriptor_name == "precompute" ||
descriptor_name == "precompute-propagate" {
pass_lower_cleanup_branchless_blocks(
cleaned,
mod_,
module_env~,
flatten_scalar_identity_blocks=true,
).0
} else {
cleaned
}
if descriptor_name != "vacuum" &&
descriptor_name != "precompute" &&
descriptor_name != "precompute-propagate" {
return cleaned
}
match cleaned {
@lib.Func(locals, @lib.Expr([])) =>
@lib.Func::new(locals, @lib.Expr::new([@lib.Instruction::nop()]))
_ => cleaned
}
}
///|
fn run_hot_pipeline_lower_options_for_descriptor(
descriptor_name : String,
) -> @ir.HotLowerOptions {
if descriptor_name == "flatten" {
@ir.HotLowerOptions::new(
preserve_if_arm_single_nops=true,
preserve_nontrailing_nops=true,
preserve_trailing_nops=true,
preserve_flat_ir_spills=true,
)
} else if descriptor_name == "ssa-nomerge" {
@ir.HotLowerOptions::new(preserve_dead_local_tee_drops=true)
} else if run_hot_pipeline_descriptor_is_simplify_locals_family(
descriptor_name,
) {
@ir.HotLowerOptions::new(
preserve_if_arm_single_nops=true,
preserve_dead_local_tee_drops=true,
preserve_nontrailing_nops=true,
preserve_trailing_nops=true,
)
} else if descriptor_name == "heap-store-optimization" {
@ir.HotLowerOptions::new(
preserve_flat_ir_spills=true,
preserve_struct_set_value_blocks=true,
)
} else if descriptor_name == "precompute" ||
descriptor_name == "precompute-propagate" {
@ir.HotLowerOptions::new(preserve_nontrailing_nops=true)
} else if descriptor_name == "remove-unused-brs" {
@ir.HotLowerOptions::new(
preserve_if_arm_single_nops=true,
preserve_dead_local_tee_drops=true,
preserve_nontrailing_nops=true,
flatten_unused_result_blocks=true,
)
} else if descriptor_name == "merge-blocks" {
@ir.HotLowerOptions::new(
preserve_if_arm_single_nops=true,
preserve_dead_local_tee_drops=true,
preserve_nontrailing_nops=true,
preserve_trailing_nops=true,
preserve_typed_loop_const_prefix_spills=true,
)
} else {
@ir.HotLowerOptions::new()
}
}
///|
fn run_hot_pipeline_unchecked_should_verify_before_lower(
descriptor_name : String,
) -> Bool {
run_hot_pipeline_descriptor_is_simplify_locals_family(descriptor_name)
}
///|
fn run_hot_pipeline_unchecked_verify_before_lower_skip_reason(
func : @ir.HotFunc,
descriptor_name : String,
options : HotPipelineOptions,
) -> String? {
if options.validate_final_module ||
!run_hot_pipeline_unchecked_should_verify_before_lower(descriptor_name) {
return None
}
match
pipeline_verify_checkpoint_with_perf(
options.perf,
HotPipelineVerifyPolicy::AfterEachPass,
"verify before lower",
HotPipelineVerifyTarget::HotFuncTarget(func),
"verify:before-lower",
) {
Ok(_) => None
Err(err) => Some(err)
}
}
///|
fn run_hot_pipeline_raw_merge_locals_has_candidate(
instrs : Array[@lib.Instruction],
) -> Bool {
for idx = 0; idx < instrs.length(); idx = idx + 1 {
if idx > 0 {
match (instrs[idx - 1], instrs[idx]) {
(
@lib.LocalGet(@lib.LocalIdx(source)),
@lib.LocalSet(@lib.LocalIdx(target))
| @lib.LocalTee(@lib.LocalIdx(target)),
) => if source != target { return true }
_ => ()
}
}
match instrs[idx] {
@lib.Block(_, @lib.Expr(body))
| @lib.Loop(_, @lib.Expr(body))
| @lib.TryTable(_, _, @lib.Expr(body)) =>
if run_hot_pipeline_raw_merge_locals_has_candidate(body) {
return true
}
@lib.Try(_, @lib.Expr(body), catches, _) => {
if run_hot_pipeline_raw_merge_locals_has_candidate(body) {
return true
}
for catch_ in catches {
let @lib.Expr(catch_body) = match catch_ {
@lib.LegacyCatch(_, catch_expr) | @lib.LegacyCatchAll(catch_expr) =>
catch_expr
}
if run_hot_pipeline_raw_merge_locals_has_candidate(catch_body) {
return true
}
}
}
@lib.If(_, then_body, else_body) => {
if run_hot_pipeline_raw_merge_locals_has_candidate(then_body) {
return true
}
match else_body {
Some(body) =>
if run_hot_pipeline_raw_merge_locals_has_candidate(body) {
return true
}
None => ()
}
}
_ => ()
}
}
false
}
///|
fn run_hot_pipeline_raw_merge_locals_call_store_shape(
instrs : Array[@lib.Instruction],
) -> (Bool, Bool) {
let mut has_call = false
let mut has_store = false
for instr in instrs {
match instr {
@lib.Call(_) | @lib.CallIndirect(_, _) | @lib.CallRef(_) =>
has_call = true
@lib.I32Store(_)
| @lib.I64Store(_)
| @lib.F32Store(_)
| @lib.F64Store(_)
| @lib.I32Store8(_)
| @lib.I32Store16(_)
| @lib.I64Store8(_)
| @lib.I64Store16(_)
| @lib.I64Store32(_) => has_store = true
@lib.Block(_, @lib.Expr(body))
| @lib.Loop(_, @lib.Expr(body))
| @lib.TryTable(_, _, @lib.Expr(body)) => {
let (child_call, child_store) = run_hot_pipeline_raw_merge_locals_call_store_shape(
body,
)
has_call = has_call || child_call
has_store = has_store || child_store
}
@lib.If(_, then_body, else_body) => {
let (then_call, then_store) = run_hot_pipeline_raw_merge_locals_call_store_shape(
then_body,
)
has_call = has_call || then_call
has_store = has_store || then_store
match else_body {
Some(body) => {
let (else_call, else_store) = run_hot_pipeline_raw_merge_locals_call_store_shape(
body,
)
has_call = has_call || else_call
has_store = has_store || else_store
}
None => ()
}
}
@lib.Try(_, @lib.Expr(body), catches, _) => {
let (body_call, body_store) = run_hot_pipeline_raw_merge_locals_call_store_shape(
body,
)
has_call = has_call || body_call
has_store = has_store || body_store
for catch_ in catches {
let @lib.Expr(catch_body) = match catch_ {
@lib.LegacyCatch(_, catch_expr) | @lib.LegacyCatchAll(catch_expr) =>
catch_expr
}
let (catch_call, catch_store) = run_hot_pipeline_raw_merge_locals_call_store_shape(
catch_body,
)
has_call = has_call || catch_call
has_store = has_store || catch_store
}
}
_ => ()
}
}
(has_call, has_store)
}
///|
fn run_hot_pipeline_raw_merge_locals_has_return(
instrs : Array[@lib.Instruction],
) -> Bool {
for instr in instrs {
match instr {
@lib.Return => return true
@lib.Block(_, @lib.Expr(body))
| @lib.Loop(_, @lib.Expr(body))
| @lib.TryTable(_, _, @lib.Expr(body)) =>
if run_hot_pipeline_raw_merge_locals_has_return(body) {
return true
}
@lib.Try(_, @lib.Expr(body), catches, _) => {
if run_hot_pipeline_raw_merge_locals_has_return(body) {
return true
}
for catch_ in catches {
let @lib.Expr(catch_body) = match catch_ {
@lib.LegacyCatch(_, catch_expr) | @lib.LegacyCatchAll(catch_expr) =>
catch_expr
}
if run_hot_pipeline_raw_merge_locals_has_return(catch_body) {
return true
}
}
}
@lib.If(_, then_body, else_body) => {
if run_hot_pipeline_raw_merge_locals_has_return(then_body) {
return true
}
match else_body {
Some(body) =>
if run_hot_pipeline_raw_merge_locals_has_return(body) {
return true
}
None => ()
}
}
_ => ()
}
}
false
}
///|
fn run_hot_pipeline_raw_merge_locals(
mod_ : @lib.Module,
func_idx : @lib.FuncIdx,
module_state : HotPipelineModuleState,
descriptor : @ir.HotPassDescriptor,
options : HotPipelineOptions,
) -> HotPipelineFuncResult? {
if descriptor.name != "merge-locals" {
return None
}
let original = run_hot_pipeline_original_func(mod_, func_idx)
let @lib.Func(_, @lib.Expr(instrs)) = original
let (has_call, has_store) = run_hot_pipeline_raw_merge_locals_call_store_shape(
instrs,
)
if has_call &&
has_store &&
run_hot_pipeline_raw_merge_locals_has_candidate(instrs) {
return Some({
reason: "call-store-local-copy-merge-locals-noop",
func: original,
unchanged_original: true,
trace_skip_raw: true,
})
}
if run_hot_pipeline_raw_merge_locals_has_return(instrs) &&
run_hot_pipeline_raw_merge_locals_has_candidate(instrs) {
return Some({
reason: "early-return-local-copy-merge-locals-noop",
func: original,
unchanged_original: true,
trace_skip_raw: true,
})
}
if !run_hot_pipeline_raw_merge_locals_has_candidate(instrs) {
return Some({
reason: "no-merge-locals-candidates",
func: original,
unchanged_original: true,
trace_skip_raw: true,
})
}
let module_ctx = run_hot_pipeline_require_module_ctx(
mod_, module_state, options,
)
let params = run_hot_pipeline_required_raw_func_params(
mod_, module_ctx, func_idx, module_state,
)
let rewrite = if pass_raw_instrs_have_legacy_try(instrs) {
merge_locals_run_raw_legacy_func(original, params)
} else {
merge_locals_run_raw_straight_line_func(original, params)
}
if !rewrite.supported {
return None
}
Some({
reason: if rewrite.changed {
"raw-localgraph-copy-balance"
} else {
"raw-localgraph-no-change"
},
func: rewrite.func,
unchanged_original: !rewrite.changed,
trace_skip_raw: true,
})
}
///|
fn run_hot_pipeline_raw_heap2local_has_supported_allocation(
instrs : Array[@lib.Instruction],
) -> Bool {
for instr in instrs {
match instr {
@lib.StructNew(_)
| @lib.StructNewDefault(_)
| @lib.StructNewDesc(_)
| @lib.StructNewDefaultDesc(_)
| @lib.ArrayNew(_)
| @lib.ArrayNewDefault(_)
| @lib.ArrayNewFixed(_, _) => return true
@lib.Block(_, @lib.Expr(body)) | @lib.Loop(_, @lib.Expr(body)) =>
if run_hot_pipeline_raw_heap2local_has_supported_allocation(body) {
return true
}
@lib.If(_, then_body, else_body) => {
if run_hot_pipeline_raw_heap2local_has_supported_allocation(then_body) {
return true
}
match else_body {
Some(body) =>
if run_hot_pipeline_raw_heap2local_has_supported_allocation(body) {
return true
}
None => ()
}
}
@lib.Try(_, @lib.Expr(body), catches, _) => {
if run_hot_pipeline_raw_heap2local_has_supported_allocation(body) {
return true
}
for catch_ in catches {
let @lib.Expr(catch_body) = match catch_ {
@lib.LegacyCatch(_, expr) | @lib.LegacyCatchAll(expr) => expr
}
if run_hot_pipeline_raw_heap2local_has_supported_allocation(
catch_body,
) {
return true
}
}
}
@lib.TryTable(_, _, @lib.Expr(body)) =>
if run_hot_pipeline_raw_heap2local_has_supported_allocation(body) {
return true
}
_ => ()
}
}
false
}
///|
fn run_hot_pipeline_raw_heap2local_no_candidates(
mod_ : @lib.Module,
func_idx : @lib.FuncIdx,
descriptor : @ir.HotPassDescriptor,
) -> HotPipelineFuncResult? {
if descriptor.name != "heap2local" {
return None
}
let original = run_hot_pipeline_original_func(mod_, func_idx)
let @lib.Func(_, @lib.Expr(instrs)) = original
if run_hot_pipeline_raw_heap2local_has_supported_allocation(instrs) {
return None
}
Some({
reason: "no-heap2local-candidates",
func: original,
unchanged_original: true,
trace_skip_raw: true,
})
}
///|
fn run_hot_pipeline_raw_heap2local_fresh_struct_null_test_instrs(
instrs : Array[@lib.Instruction],
module_ctx : @ir.HotModuleContext,
) -> (Array[@lib.Instruction], Bool) {
let out : Array[@lib.Instruction] = []
let mut changed = false
let mut idx = 0
while idx < instrs.length() {
if idx + 9 < instrs.length() {
match
(
instrs[idx],
instrs[idx + 1],
instrs[idx + 2],
instrs[idx + 3],
instrs[idx + 4],
instrs[idx + 5],
instrs[idx + 6],
instrs[idx + 7],
instrs[idx + 8],
instrs[idx + 9],
) {
(
@lib.StructNew(empty_type),
@lib.RefNull(_),
@lib.StructNew(first_pair_type),
@lib.I32Const(_),
@lib.RefI31,
@lib.StructNew(second_pair_type),
@lib.StructGet(first_get_type, @lib.U32(0U)),
@lib.RefCast(_, _, cast_heap_type),
@lib.StructGet(second_get_type, @lib.U32(0U)),
@lib.RefTest(_, _, test_heap_type),
) if first_pair_type == second_pair_type &&
first_get_type == first_pair_type &&
second_get_type == first_pair_type &&
cast_heap_type == @lib.HeapType::new(first_pair_type) &&
test_heap_type == @lib.HeapType::new(empty_type) =>
match
(
@ir.hot_module_resolve_struct_fields(module_ctx, empty_type),
@ir.hot_module_resolve_struct_fields(module_ctx, first_pair_type),
) {
(Some([]), Some([_, _])) => {
out.push(@lib.Instruction::i32_const(@lib.I32(1)))
idx += 10
changed = true
continue
}
_ => ()
}
_ => ()
}
}
if idx + 4 < instrs.length() {
match
(
instrs[idx],
instrs[idx + 1],
instrs[idx + 2],
instrs[idx + 3],
instrs[idx + 4],
) {
(
@lib.StructNewDefault(inner_type),
@lib.I32Const(_),
@lib.StructNew(outer_type),
@lib.StructGet(get_type, @lib.U32(0U)),
@lib.RefIsNull,
) if outer_type == get_type =>
match @ir.hot_module_resolve_struct_fields(module_ctx, outer_type) {
Some(
[
@lib.FieldType(
@lib.ValTypeStorageType(@lib.RefTypeValType(ref_type)),
_
),
_,
]
) =>
match ref_type.get_heap_type() {
@lib.HeapType(field_type) if field_type == inner_type => {
out.push(@lib.Instruction::i32_const(@lib.I32(0)))
idx += 5
changed = true
continue
}
_ => ()
}
_ => ()
}
_ => ()
}
}
match instrs[idx] {
@lib.Block(block_type, @lib.Expr(body)) => {
let (next_body, body_changed) = run_hot_pipeline_raw_heap2local_fresh_struct_null_test_instrs(
body, module_ctx,
)
out.push(
if body_changed {
@lib.Instruction::block(block_type, @lib.Expr::new(next_body))
} else {
instrs[idx]
},
)
changed = changed || body_changed
}
@lib.Loop(block_type, @lib.Expr(body)) => {
let (next_body, body_changed) = run_hot_pipeline_raw_heap2local_fresh_struct_null_test_instrs(
body, module_ctx,
)
out.push(
if body_changed {
@lib.Instruction::loop_(block_type, @lib.Expr::new(next_body))
} else {
instrs[idx]
},
)
changed = changed || body_changed
}
@lib.If(block_type, then_body, else_body) => {
let (next_then, then_changed) = run_hot_pipeline_raw_heap2local_fresh_struct_null_test_instrs(
then_body, module_ctx,
)
let (next_else, else_changed) = match else_body {
Some(body) => {
let (next_body, body_changed) = run_hot_pipeline_raw_heap2local_fresh_struct_null_test_instrs(
body, module_ctx,
)
(Some(next_body), body_changed)
}
None => (None, false)
}
out.push(
if then_changed || else_changed {
@lib.Instruction::if_(block_type, next_then, next_else)
} else {
instrs[idx]
},
)
changed = changed || then_changed || else_changed
}
@lib.TryTable(block_type, catches, @lib.Expr(body)) => {
let (next_body, body_changed) = run_hot_pipeline_raw_heap2local_fresh_struct_null_test_instrs(
body, module_ctx,
)
out.push(
if body_changed {
@lib.Instruction::try_table(
block_type,
catches,
@lib.Expr::new(next_body),
)
} else {
instrs[idx]
},
)
changed = changed || body_changed
}
_ => out.push(instrs[idx])
}
idx += 1
}
(out, changed)
}
///|
fn run_hot_pipeline_raw_heap2local_fresh_struct_null_test(
mod_ : @lib.Module,
func_idx : @lib.FuncIdx,
module_state : HotPipelineModuleState,
descriptor : @ir.HotPassDescriptor,
options : HotPipelineOptions,
) -> HotPipelineFuncResult? {
if descriptor.name != "heap2local" {
return None
}
let original = run_hot_pipeline_original_func(mod_, func_idx)
let @lib.Func(locals, @lib.Expr(instrs)) = original
let module_ctx = run_hot_pipeline_require_module_ctx(
mod_, module_state, options,
)
let (rewritten, changed) = run_hot_pipeline_raw_heap2local_fresh_struct_null_test_instrs(
instrs, module_ctx,
)
if changed {
Some({
reason: "fresh-struct-null-test",
func: @lib.Func::new(locals, @lib.Expr::new(rewritten)),
unchanged_original: false,
trace_skip_raw: true,
})
} else {
None
}
}
///|
fn run_hot_pipeline_raw_heap2local_v131_unreachable_array_flow(
mod_ : @lib.Module,
func_idx : @lib.FuncIdx,
module_state : HotPipelineModuleState,
descriptor : @ir.HotPassDescriptor,
options : HotPipelineOptions,
) -> HotPipelineFuncResult? {
if descriptor.name != "heap2local" {
return None
}
let original = run_hot_pipeline_original_func(mod_, func_idx)
let @lib.Func(locals, @lib.Expr(instrs)) = original
let (array_type, length) = match instrs {
[
@lib.Instruction::Block(
_,
@lib.Expr(
[
@lib.Instruction::I32Const(@lib.I32(raw_length)),
@lib.Instruction::ArrayNewDefault(array_type),
@lib.Instruction::Return,
@lib.Instruction::BrIf(@lib.LabelIdx(0U)),
]
)
),
@lib.Instruction::Drop,
] => (array_type, raw_length)
_ => return None
}
if length < 0 || length >= 20 {
return None
}
let module_ctx = run_hot_pipeline_require_module_ctx(
mod_, module_state, options,
)
// This shortcut emits a bare return and therefore requires a void function.
match @ir.hot_module_resolve_func_type(module_ctx, func_idx) {
Some(@lib.FuncType(_, results)) if results.is_empty() => ()
_ => return None
}
let field = match @ir.hot_module_resolve_array_field(module_ctx, array_type) {
Some(field) if field.unpack() == @lib.ValType::i32() => field
_ => return None
}
ignore(field)
let params = run_hot_pipeline_required_raw_func_params(
mod_, module_ctx, func_idx, module_state,
)
let base_local = params.length() + locals.length()
let rewritten_locals = locals.copy()
let rewritten_body : Array[@lib.Instruction] = []
for offset = 0; offset < length; offset = offset + 1 {
rewritten_locals.push(@lib.ValType::i32())
rewritten_body.push(@lib.Instruction::i32_const(@lib.I32(0)))
rewritten_body.push(
@lib.Instruction::local_set(
@lib.LocalIdx::new((base_local + offset).reinterpret_as_uint()),
),
)
}
rewritten_body.push(@lib.Instruction::return_())
Some({
reason: "v131-unreachable-array-flow",
func: @lib.Func::new(rewritten_locals, @lib.Expr::new(rewritten_body)),
unchanged_original: false,
trace_skip_raw: true,
})
}
///|
fn run_hot_pipeline_merge_blocks_preclean_value(
instr : @lib.Instruction,
) -> Bool {
match instr {
@lib.I32Const(_)
| @lib.I64Const(_)
| @lib.F32Const(_)
| @lib.F64Const(_)
| @lib.V128Const(_)
| @lib.LocalGet(_)
| @lib.GlobalGet(_)
| @lib.RefNull(_)
| @lib.RefFunc(_) => true
_ => false
}
}
///|
fn run_hot_pipeline_merge_blocks_preclean_dropped_payload(
body : Array[@lib.Instruction],
) -> @lib.Instruction? {
if body.length() == 2 && body[1] is @lib.Br(@lib.LabelIdx(0U)) {
return Some(body[0])
}
if body.length() == 3 &&
body[1] is @lib.Br(@lib.LabelIdx(0U)) &&
body[2] is @lib.Unreachable {
return Some(body[0])
}
if body.length() != 2 || !(body[1] is @lib.Unreachable) {
return None
}
match body[0] {
@lib.Block(@lib.VoidBlockType, @lib.Expr(inner)) => {
if inner.length() == 2 && inner[1] is @lib.Br(@lib.LabelIdx(1U)) {
return Some(inner[0])
}
if inner.length() == 3 &&
inner[1] is @lib.Br(@lib.LabelIdx(1U)) &&
inner[2] is @lib.Unreachable {
return Some(inner[0])
}
None
}
_ => None
}
}
///|
fn run_hot_pipeline_merge_blocks_preclean_instrs(
instrs : Array[@lib.Instruction],
) -> (Array[@lib.Instruction], Bool) {
let recursively_rewrite = fn(
instr : @lib.Instruction,
) -> (@lib.Instruction, Bool) {
match instr {
@lib.Block(bt, @lib.Expr(body)) => {
let (next, changed) = run_hot_pipeline_merge_blocks_preclean_instrs(
body,
)
(@lib.Instruction::block(bt, @lib.Expr::new(next)), changed)
}
@lib.Loop(bt, @lib.Expr(body)) => {
let (next, changed) = run_hot_pipeline_merge_blocks_preclean_instrs(
body,
)
(@lib.Instruction::loop_(bt, @lib.Expr::new(next)), changed)
}
@lib.If(bt, then_body, else_body) => {
let (next_then, then_changed) = run_hot_pipeline_merge_blocks_preclean_instrs(
then_body,
)
let (next_else, else_changed) = match else_body {
Some(body) => {
let (next, changed) = run_hot_pipeline_merge_blocks_preclean_instrs(
body,
)
(Some(next), changed)
}
None => (None, false)
}
(
@lib.Instruction::if_(bt, next_then, next_else),
then_changed || else_changed,
)
}
@lib.TryTable(bt, catches, @lib.Expr(body)) => {
let (next, changed) = run_hot_pipeline_merge_blocks_preclean_instrs(
body,
)
(
@lib.Instruction::try_table(bt, catches, @lib.Expr::new(next)),
changed,
)
}
_ => (instr, false)
}
}
let out : Array[@lib.Instruction] = []
let mut changed = false
let mut idx = 0
while idx < instrs.length() {
let (current, nested_changed) = recursively_rewrite(instrs[idx])
changed = changed || nested_changed
if idx + 1 < instrs.length() && instrs[idx + 1] is @lib.Drop {
match current {
@lib.Block(@lib.ValTypeBlockType(_), @lib.Expr(body)) => {
let payload = run_hot_pipeline_merge_blocks_preclean_dropped_payload(
body,
)
match payload {
Some(value) if run_hot_pipeline_merge_blocks_preclean_value(value) => {
out.push(
@lib.Instruction::block(
@lib.BlockType::void_(),
@lib.Expr::new([
value,
@lib.Instruction::drop(),
@lib.Instruction::br(@lib.LabelIdx::new(0U)),
]),
),
)
changed = true
idx += 2
continue
}
_ => ()
}
}
_ => ()
}
}
out.push(current)
idx += 1
}
(out, changed)
}
///|
fn run_hot_pipeline_merge_blocks_preclean_func(
func : @lib.Func,
) -> (@lib.Func, Bool) {
let @lib.Func(locals, @lib.Expr(body)) = func
let (next_body, changed) = run_hot_pipeline_merge_blocks_preclean_instrs(body)
if changed {
(@lib.Func::new(locals, @lib.Expr::new(next_body)), true)
} else {
(func, false)
}
}
///|
fn run_hot_pipeline_merge_blocks_preclean_ref_catches(
catches : Array[@lib.Catch],
module_ctx : @ir.HotModuleContext,
) -> Array[@lib.Catch]? {
if catches.is_empty() {
return None
}
let rewritten : Array[@lib.Catch] = []
for catch_ in catches {
match catch_ {
@lib.CatchRef(tag_idx, @lib.LabelIdx(0U)) => {
let tag_type_idx = match
@ir.hot_module_resolve_tag_type(module_ctx, tag_idx) {
Some(@lib.TagType(type_idx)) => type_idx
None => return None
}
match @ir.hot_module_resolve_type_func_type(module_ctx, tag_type_idx) {
Some(@lib.FuncType(params, _)) if params.is_empty() => ()
_ => return None
}
rewritten.push(@lib.Catch::new(tag_idx, @lib.LabelIdx::new(0U)))
}
@lib.CatchAllRef(@lib.LabelIdx(0U)) =>
rewritten.push(@lib.Catch::all(@lib.LabelIdx::new(0U)))
_ => return None
}
}
Some(rewritten)
}
///|
fn run_hot_pipeline_merge_blocks_preclean_ref_drops(
func : @lib.Func,
module_ctx : @ir.HotModuleContext,
) -> (@lib.Func, Bool) {
let @lib.Func(locals, @lib.Expr(body)) = func
let rewritten : Array[@lib.Instruction] = []
let mut changed = false
let mut idx = 0
while idx < body.length() {
if idx + 1 < body.length() && body[idx + 1] is @lib.Drop {
match body[idx] {
@lib.Block(
@lib.ValTypeBlockType(_),
@lib.Expr(
[
@lib.TryTable(block_type, catches, @lib.Expr(try_body)),
@lib.Unreachable,
]
)
) =>
match
run_hot_pipeline_merge_blocks_preclean_ref_catches(
catches, module_ctx,
) {
Some(next_catches) => {
rewritten.push(
@lib.Instruction::block(
@lib.BlockType::void_(),
@lib.Expr::new([
@lib.Instruction::try_table(
block_type,
next_catches,
@lib.Expr::new(try_body),
),
@lib.Instruction::unreachable_(),
]),
),
)
changed = true
idx += 2
continue
}
None => ()
}
_ => ()
}
}
rewritten.push(body[idx])
idx += 1
}
if changed {
(@lib.Func::new(locals, @lib.Expr::new(rewritten)), true)
} else {
(func, false)
}
}
///|
fn run_hot_pipeline_raw_optimize_casts_has_candidate(
instrs : Array[@lib.Instruction],
) -> Bool {
for instr in instrs {
match instr {
@lib.RefCast(_, _, _)
| @lib.RefCastDescEq(_, _, _)
| @lib.RefTest(_, _, _)
| @lib.RefTestDesc(_, _)
| @lib.RefAsNonNull
| @lib.BrOnCast(_, _, _, _)
| @lib.BrOnCastFail(_, _, _, _)
| @lib.BrOnCastDescEq(_, _, _)
| @lib.BrOnCastDescEqFail(_, _, _) => return true
@lib.Block(_, @lib.Expr(body))
| @lib.Loop(_, @lib.Expr(body))
| @lib.TryTable(_, _, @lib.Expr(body)) =>
if run_hot_pipeline_raw_optimize_casts_has_candidate(body) {
return true
}
@lib.If(_, then_body, else_body) => {
if run_hot_pipeline_raw_optimize_casts_has_candidate(then_body) {
return true
}
match else_body {
Some(else_body) =>
if run_hot_pipeline_raw_optimize_casts_has_candidate(else_body) {
return true
}
None => ()
}
}
_ => ()
}
}
false
}
///|
fn run_hot_pipeline_raw_optimize_casts_no_candidates(
mod_ : @lib.Module,
func_idx : @lib.FuncIdx,
descriptor : @ir.HotPassDescriptor,
) -> HotPipelineFuncResult? {
if descriptor.name != "optimize-casts" {
return None
}
let original = run_hot_pipeline_original_func(mod_, func_idx)
let @lib.Func(_, @lib.Expr(instrs)) = original
if run_hot_pipeline_raw_optimize_casts_has_candidate(instrs) {
None
} else {
Some({
reason: "no-optimize-casts-candidates",
func: original,
unchanged_original: true,
trace_skip_raw: true,
})
}
}
///|
fn run_hot_pipeline_raw_flatten_has_stack_carried_tee_parameter_alias(
instrs : Array[@lib.Instruction],
parameter_count : Int32,
) -> Bool {
if parameter_count <= 0 {
return false
}
for tee_idx = 0; tee_idx < instrs.length(); tee_idx = tee_idx + 1 {
let target = match instrs[tee_idx] {
@lib.LocalTee(target) => target
_ => continue
}
let limit = if tee_idx + 9 < instrs.length() {
tee_idx + 9
} else {
instrs.length()
}
for read_idx = tee_idx + 2; read_idx + 1 < limit; read_idx = read_idx + 1 {
match (instrs[read_idx], instrs[read_idx + 1]) {
(@lib.LocalGet(read), @lib.LocalSet(@lib.LocalIdx(raw_parameter))) =>
if read == target &&
raw_parameter.reinterpret_as_int() < parameter_count {
return true
}
_ => ()
}
}
}
for instr in instrs {
match instr {
@lib.Block(_, @lib.Expr(body))
| @lib.Loop(_, @lib.Expr(body))
| @lib.TryTable(_, _, @lib.Expr(body)) =>
if run_hot_pipeline_raw_flatten_has_stack_carried_tee_parameter_alias(
body, parameter_count,
) {
return true
}
@lib.If(_, then_body, else_body) => {
if run_hot_pipeline_raw_flatten_has_stack_carried_tee_parameter_alias(
then_body, parameter_count,
) {
return true
}
match else_body {
Some(body) =>
if run_hot_pipeline_raw_flatten_has_stack_carried_tee_parameter_alias(
body, parameter_count,
) {
return true
}
None => ()
}
}
_ => ()
}
}
false
}
///|
fn run_hot_pipeline_raw_i32_ctz_count(
instrs : Array[@lib.Instruction],
) -> Int32 {
let mut count = 0
for instr in instrs {
match instr {
@lib.Instruction::I32Ctz => count += 1
@lib.Block(_, @lib.Expr(body))
| @lib.Loop(_, @lib.Expr(body))
| @lib.TryTable(_, _, @lib.Expr(body)) =>
count += run_hot_pipeline_raw_i32_ctz_count(body)
@lib.If(_, then_body, else_body) => {
count += run_hot_pipeline_raw_i32_ctz_count(then_body)
match else_body {
Some(body) => count += run_hot_pipeline_raw_i32_ctz_count(body)
None => ()
}
}
_ => ()
}
}
count
}
///|
// The numeric 4/1 spine can retire a globally unread tee without entering HOT
// regions rejected by Vacuum's existing guards. Producers and stack values stay
// in place. The following module writeback validation and ReorderLocals owner
// remain responsible for validity, declaration removal and name remapping.
fn run_hot_pipeline_func(
mod_ : @lib.Module,
func_idx : @lib.FuncIdx,
descriptor : @ir.HotPassDescriptor,
module_state : HotPipelineModuleState,
lower_state : HotPipelineLowerState,
options : HotPipelineOptions,
) -> Result[HotPipelineFuncResult, String] {
let result = match
run_hot_pipeline_func_before_unread_tees(
mod_, func_idx, descriptor, module_state, lower_state, options,
) {
Ok(result) => result
Err(err) => return Err(err)
}
if descriptor.name != "vacuum" ||
options.optimize_level < 4 ||
options.shrink_level < 1 {
return Ok(result)
}
let subtypes = run_hot_pipeline_require_raw_types(mod_, module_state, None)
let indices = run_hot_pipeline_func_type_indices(mod_, module_state)
let @lib.FuncIdx(raw_func_idx) = func_idx
let params = match indices.get(raw_func_idx.reinterpret_as_int()) {
Some(@lib.TypeIdx(raw_type_idx)) =>
match subtypes.get(raw_type_idx.reinterpret_as_int()) {
Some(subtype) =>
match subtype.get_comptype() {
@lib.FuncCompType(params, _) => params
_ => return Ok(result)
}
None => return Ok(result)
}
_ => return Ok(result)
}
let @lib.Func(locals, @lib.Expr(body)) = result.func
let total_locals = match
run_hot_pipeline_vacuum_unread_tee_local_count(params.length(), locals) {
Some(count) => count
None => return Ok(result)
}
if total_locals == 0 {
return Ok(result)
}
let gets = Array::make(total_locals, 0)
let tees = Array::make(total_locals, 0)
// The read collector includes both arms and all legacy handler bodies. The
// removal helper deliberately leaves legacy Try regions opaque.
run_hot_pipeline_raw_collect_local_ref_counts(body, gets, [], tees)
let mut has_unread_tee = false
for idx = 0; idx < total_locals; idx = idx + 1 {
if tees[idx] > 0 && gets[idx] == 0 {
has_unread_tee = true
break
}
}
if !has_unread_tee {
return Ok(result)
}
let (rewritten, changes) = run_hot_pipeline_raw_simplify_locals_remove_unread_local_tees(
body, gets,
)
if changes == 0 {
return Ok(result)
}
Ok({
reason: "\{result.reason}-unread-tees",
func: @lib.Func::new(locals, @lib.Expr::new(rewritten)),
unchanged_original: false,
trace_skip_raw: result.trace_skip_raw,
})
}
///|
// Keep the two dense proof arrays bounded even when declarations are compressed.
// Bound the compressed run snapshot before Locals.runs() copies it. Count
// unsigned runs against the remaining budget before signed conversion/addition;
// larger functions retain the existing Vacuum path.
fn run_hot_pipeline_vacuum_unread_tee_local_count(
param_count : Int32,
locals : @lib.Locals,
) -> Int32? {
let budget = 65536
if param_count < 0 || param_count > budget || locals.run_count() > budget {
return None
}
let mut count = param_count
for run in locals.runs() {
if run.count() > (budget - count).reinterpret_as_uint() {
return None
}
count += run.count().reinterpret_as_int()
}
Some(count)
}
///|
fn run_hot_pipeline_func_before_unread_tees(
mod_ : @lib.Module,
func_idx : @lib.FuncIdx,
descriptor : @ir.HotPassDescriptor,
module_state : HotPipelineModuleState,
lower_state : HotPipelineLowerState,
options : HotPipelineOptions,
) -> Result[HotPipelineFuncResult, String] {
let original = run_hot_pipeline_original_func(mod_, func_idx)
let @lib.Func(_, @lib.Expr(original_instrs)) = original
if (
descriptor.name == "precompute" ||
descriptor.name == "precompute-propagate"
) &&
(
run_hot_pipeline_raw_try_table_catch_body_has_local_write(original_instrs) ||
run_hot_pipeline_raw_remove_unused_brs_has_try_table_catch_shell(
original_instrs,
)
) {
return Ok({
reason: "try-table-precompute-noop",
func: original,
unchanged_original: true,
trace_skip_raw: true,
})
}
let oi_index = if descriptor.name == "optimize-instructions" {
let raw_index_start_us = perf_start_timer_opt(
options.perf,
"detail:optimize-instructions:raw-revision-index",
)
let index = RawOiFunctionRevisionFactIndex::from_instrs(original_instrs)
perf_stop_timer_opt(
options.perf,
"detail:optimize-instructions:raw-revision-index",
raw_index_start_us,
)
Some(index)
} else {
None
}
if descriptor.name == "flatten" &&
run_hot_pipeline_module_has_assemblyscript_startup_exported_memory(mod_) &&
run_hot_pipeline_raw_i32_ctz_count(original_instrs) < 3 {
return Ok({
reason: "assemblyscript-startup-nonallocator-flatten-noop",
func: original,
unchanged_original: true,
trace_skip_raw: true,
})
}
if descriptor.name == "flatten" &&
run_hot_pipeline_module_has_assemblyscript_run_exported_memory_runtime(mod_) {
return Ok({
reason: "assemblyscript-exported-memory-flatten-noop",
func: original,
unchanged_original: true,
trace_skip_raw: true,
})
}
if descriptor.name == "optimize-instructions" &&
oi_index.unwrap().admission_guards.has_call_value_store_offset {
return Ok({
reason: "call-value-store-offset-optimize-instructions-noop",
func: original,
unchanged_original: true,
trace_skip_raw: true,
})
}
if descriptor.name == "flatten" ||
descriptor.name == "optimize-instructions" ||
descriptor.name == "merge-locals" {
let module_ctx = run_hot_pipeline_require_module_ctx(
mod_, module_state, options,
)
let params = run_hot_pipeline_required_raw_func_params(
mod_, module_ctx, func_idx, module_state,
)
let has_stack_carried_tee_parameter_alias = match oi_index {
Some(index) =>
index.has_stack_carried_tee_parameter_alias(params.length())
None =>
run_hot_pipeline_raw_flatten_has_stack_carried_tee_parameter_alias(
original_instrs,
params.length(),
)
}
if has_stack_carried_tee_parameter_alias {
let reason = match descriptor.name {
"flatten" => "stack-carried-tee-parameter-alias-flatten-noop"
"merge-locals" => "stack-carried-tee-parameter-alias-merge-locals-noop"
_ => "stack-carried-tee-parameter-alias-optimize-instructions-noop"
}
return Ok({
reason,
func: original,
unchanged_original: true,
trace_skip_raw: true,
})
}
}
if run_hot_pipeline_raw_instrs_have_function_exit_br_table(
original_instrs, 0U,
) {
let shape = RawRemoveUnusedBrsShape::new()
run_hot_pipeline_raw_remove_unused_brs_scan_shape(original_instrs, shape)
if shape.br_table_count > 0 &&
shape.local_set_count == 0 &&
shape.local_tee_count == 0 {
let module_ctx = run_hot_pipeline_require_module_ctx(
mod_, module_state, options,
)
match @ir.hot_module_resolve_func_type(module_ctx, func_idx) {
Some(@lib.FuncType(_, results)) =>
if results.length() > 1 {
return Ok({
reason: "multivalue-function-exit-br-table-hot-lift-noop",
func: original,
unchanged_original: true,
trace_skip_raw: true,
})
}
None => ()
}
}
}
match
run_hot_pipeline_raw_simplify_locals(
mod_, func_idx, module_state, descriptor, options,
) {
Some(result) => {
let simd_cleaned = if descriptor.name == "simplify-locals" {
run_hot_pipeline_simplify_locals_lowered_sink_v128_const_sets(
result.func,
)
} else {
result.func
}
// The SIMD sink returns the original function object when it does not
// rewrite anything, so identity is the complete change signal here.
// Avoid a derived equality walk over deeply nested control flow.
let simd_changed = !physical_equal(simd_cleaned, result.func)
let (folded, capture_changed) = if descriptor.name == "simplify-locals" &&
(!result.unchanged_original || simd_changed) {
run_hot_pipeline_simplify_locals_fold_adjacent_captures_func(
simd_cleaned,
)
} else {
(simd_cleaned, false)
}
let (finalized, finalized_changed) = run_hot_pipeline_simplify_locals_finalize_lowered_root_with_changed(
folded,
)
if !finalized_changed && !capture_changed && !simd_changed {
return Ok(result)
}
return Ok({
reason: if simd_changed {
"\{result.reason}-simd-result-select"
} else if finalized_changed {
"\{result.reason}-root-finalized"
} else {
"\{result.reason}-adjacent-capture"
},
func: finalized,
unchanged_original: false,
trace_skip_raw: result.trace_skip_raw,
})
}
None => ()
}
match
run_hot_pipeline_raw_merge_locals(
mod_, func_idx, module_state, descriptor, options,
) {
Some(result) => return Ok(result)
None => ()
}
match
run_hot_pipeline_raw_ssa_nomerge(
mod_, func_idx, module_state, descriptor, options,
) {
Some(result) => return Ok(result)
None => ()
}
match
run_hot_pipeline_raw_heap_store_optimization(
mod_, func_idx, descriptor, options,
) {
Some(result) => return Ok(result)
None => ()
}
match
run_hot_pipeline_raw_heap2local_no_candidates(mod_, func_idx, descriptor) {
Some(result) => return Ok(result)
None => ()
}
match
run_hot_pipeline_raw_heap2local_fresh_struct_null_test(
mod_, func_idx, module_state, descriptor, options,
) {
Some(result) => return Ok(result)
None => ()
}
match
run_hot_pipeline_raw_heap2local_v131_unreachable_array_flow(
mod_, func_idx, module_state, descriptor, options,
) {
Some(result) => return Ok(result)
None => ()
}
match run_hot_pipeline_raw_pick_load_signs(mod_, func_idx, descriptor) {
Some(result) => return Ok(result)
None => ()
}
match
run_hot_pipeline_raw_optimize_casts_no_candidates(
mod_, func_idx, descriptor,
) {
Some(result) => return Ok(result)
None => ()
}
match
run_hot_pipeline_raw_optimize_instructions_skip(
mod_,
func_idx,
module_state,
descriptor,
options,
oi_index~,
) {
Some(result) => {
let (precleaned, tee_changed) = if pass_module_has_offset_sensitive_metadata(
mod_,
) {
(result.func, false)
} else {
oi_tee_reuse_preclean(result.func)
}
let (cleaned, changed) = run_hot_pipeline_optimize_instructions_cleanup_tuple_wrappers(
precleaned,
Some(run_hot_pipeline_original_func(mod_, func_idx)),
mod_,
module_ctx=Some(
run_hot_pipeline_require_module_ctx(mod_, module_state, options),
),
module_env_provider=Some(fn() {
run_hot_pipeline_require_validation_module_env(
mod_, module_state, options,
)
}),
)
return Ok({
reason: result.reason,
func: cleaned,
unchanged_original: result.unchanged_original &&
!changed &&
!tee_changed,
trace_skip_raw: result.trace_skip_raw,
})
}
None => ()
}
let merge_blocks_module_ctx = if descriptor.name == "merge-blocks" {
Some(run_hot_pipeline_require_module_ctx(mod_, module_state, options))
} else {
None
}
match
run_hot_pipeline_raw_merge_blocks_dropped_const_multivalue(
mod_,
func_idx,
descriptor,
module_ctx=merge_blocks_module_ctx,
) {
Some(result) => return Ok(result)
None => ()
}
match
run_hot_pipeline_raw_merge_blocks_flat_call_drop_prefixes(
mod_,
func_idx,
descriptor,
module_ctx=merge_blocks_module_ctx,
) {
Some(result) => return Ok(result)
None => ()
}
match
run_hot_pipeline_raw_merge_blocks_ordered_atomic_call(
mod_, func_idx, descriptor,
) {
Some(result) => return Ok(result)
None => ()
}
match
run_hot_pipeline_raw_merge_blocks_flat_call_prefix(
mod_, func_idx, module_state, descriptor,
) {
Some(result) => return Ok(result)
None => ()
}
match run_hot_pipeline_raw_size_cleanup_skip(mod_, func_idx, descriptor) {
Some(result) => return Ok(result)
None => ()
}
match run_hot_pipeline_raw_code_pushing_skip(mod_, func_idx, descriptor) {
Some(result) => return Ok(result)
None => ()
}
match run_hot_pipeline_raw_code_folding_skip(mod_, func_idx, descriptor) {
Some(result) => return Ok(result)
None => ()
}
match
run_hot_pipeline_raw_tuple_optimization_skip(
mod_, func_idx, module_state, descriptor, options,
) {
Some(result) => return Ok(result)
None => ()
}
match
run_hot_pipeline_raw_precompute(
mod_, func_idx, module_state, descriptor, options,
) {
Some(result) => {
// Keep the established unsupported label-owner boundary unchanged.
if result.reason ==
"nested-empty-block-label-owner-precompute-propagate-noop" {
return Ok(result)
}
let (output, changed) = pass_lower_cleanup_branchless_blocks(
result.func,
mod_,
module_env=Some(
run_hot_pipeline_require_validation_module_env(
mod_, module_state, options,
),
),
flatten_scalar_identity_blocks=true,
)
return Ok({
reason: result.reason,
func: output,
unchanged_original: result.unchanged_original && !changed,
trace_skip_raw: result.trace_skip_raw,
})
}
None => ()
}
match
run_hot_pipeline_raw_rse(mod_, func_idx, module_state, descriptor, options) {
Some(result) => return Ok(result)
None => ()
}
if descriptor.name == "vacuum" {
let preclean_start_us = perf_start_timer_opt(
options.perf,
"raw:vacuum-preclean",
)
match
run_hot_pipeline_raw_vacuum_preclean(
mod_, func_idx, module_state, descriptor, options,
) {
Some(result) => {
module_state.vacuum_raw_preclean_elapsed_us += perf_elapsed_since_start_opt(
options.perf,
preclean_start_us,
)
module_state.vacuum_raw_preclean_count += 1
return Ok(result)
}
None => {
module_state.vacuum_raw_preclean_elapsed_us += perf_elapsed_since_start_opt(
options.perf,
preclean_start_us,
)
module_state.vacuum_raw_preclean_count += 1
}
}
let no_candidate_start_us = perf_start_timer_opt(
options.perf,
"raw:vacuum-no-candidates",
)
match
run_hot_pipeline_raw_vacuum_no_candidates(mod_, func_idx, descriptor) {
Some(result) => {
module_state.vacuum_no_candidate_elapsed_us += perf_elapsed_since_start_opt(
options.perf,
no_candidate_start_us,
)
module_state.vacuum_no_candidate_count += 1
return Ok(result)
}
None => {
module_state.vacuum_no_candidate_elapsed_us += perf_elapsed_since_start_opt(
options.perf,
no_candidate_start_us,
)
module_state.vacuum_no_candidate_count += 1
}
}
}
match
run_hot_pipeline_raw_remove_unused_names(
mod_, func_idx, descriptor, options,
) {
Some(result) => return Ok(result)
None => ()
}
if descriptor.name != "vacuum" {
pipeline_trace_hook(
options.trace,
descriptor.name,
"func",
payload="func_idx=\{func_idx}",
)
}
match
run_hot_pipeline_raw_remove_unused_brs(mod_, func_idx, descriptor, options) {
Some(result) => return Ok(result)
None => ()
}
let module_ctx = run_hot_pipeline_require_module_ctx(
mod_, module_state, options,
)
let (lift_mod, raw_preclean_changed) = if descriptor.name == "merge-blocks" {
let (branch_precleaned, branch_changed) = run_hot_pipeline_merge_blocks_preclean_func(
run_hot_pipeline_original_func(mod_, func_idx),
)
let (precleaned, ref_changed) = run_hot_pipeline_merge_blocks_preclean_ref_drops(
branch_precleaned, module_ctx,
)
let changed = branch_changed || ref_changed
if changed {
pipeline_trace_hook(
options.trace,
descriptor.name,
"raw-preclean",
payload="func_idx=\{func_idx} reason=dropped-self-branch-value",
)
(
run_hot_pipeline_module_with_rewritten_func(mod_, func_idx, precleaned),
true,
)
} else {
(mod_, false)
}
} else if descriptor.name == "optimize-instructions" &&
!pass_module_has_offset_sensitive_metadata(mod_) {
let (precleaned, changed) = oi_tee_reuse_preclean(
run_hot_pipeline_original_func(mod_, func_idx),
)
if changed {
(
run_hot_pipeline_module_with_rewritten_func(mod_, func_idx, precleaned),
true,
)
} else {
(mod_, false)
}
} else {
(mod_, false)
}
let lift_start_us = perf_start_timer_opt(options.perf, "lift")
let func = match
@ir.hot_lift_func_with_context_result(
lift_mod,
func_idx,
module_ctx,
capture_stack_locals=descriptor.name == "constraint-analysis",
record_local_accesses=false,
) {
Ok(func) => func
Err(err) => {
pipeline_trace_hook(
options.trace,
descriptor.name,
"lift-error",
payload="func_idx=\{func_idx} err=\{err}",
)
if descriptor.name == "optimize-instructions" {
let original = run_hot_pipeline_original_func(mod_, func_idx)
match oi_publish_rewrite_func(mod_, func_idx, original) {
Some(next) =>
return Ok({
reason: "publish-fresh-raw",
func: next,
unchanged_original: false,
trace_skip_raw: true,
})
None => ()
}
let @lib.Func(locals, @lib.Expr(body)) = original
let descriptor_graph = RawOiDescriptorCallValueGraphFact::from_module_body(
body, module_ctx,
)
let (exact_success_body, exact_success_changed) = run_hot_pipeline_raw_optimize_instructions_exact_descriptor_success_body(
descriptor_graph, module_ctx,
)
if exact_success_changed {
return Ok({
reason: "descriptor-exact-success-skip",
func: @lib.Func::new(locals, @lib.Expr::new(exact_success_body)),
unchanged_original: false,
trace_skip_raw: true,
})
}
let (exactification_body, exactification_changed) = run_hot_pipeline_raw_optimize_instructions_exact_descriptor_exactification_body(
descriptor_graph, module_ctx,
)
if exactification_changed {
return Ok({
reason: "descriptor-exactification",
func: @lib.Func::new(locals, @lib.Expr::new(exactification_body)),
unchanged_original: false,
trace_skip_raw: true,
})
}
let (exact_miss_body, exact_miss_changed) = run_hot_pipeline_raw_optimize_instructions_exact_descriptor_miss_body(
descriptor_graph, module_ctx, func_idx, locals,
)
if exact_miss_changed {
return Ok({
reason: "descriptor-exact-miss-fold",
func: @lib.Func::new(locals, @lib.Expr::new(exact_miss_body)),
unchanged_original: false,
trace_skip_raw: true,
})
}
let (rewritten_body, changed) = run_hot_pipeline_raw_optimize_instructions_effectful_sibling_body(
body, module_ctx,
)
// This is the mutually exclusive hot-lift-error fallback. It cannot
// reuse the normal raw-admission index because that path returned None
// before lift and its locals are out of scope here. Build one explicit
// immutable index for the same original body revision solely to prove
// whether the fallback rewrite included the bottom-null family.
let oi_index = RawOiFunctionRevisionFactIndex::from_instrs(body)
if changed &&
RawOiBottomNullDescriptorCastRegionFact::from_index(oi_index).has_fold {
return Ok({
reason: "descriptor-bottom-null-fold",
func: @lib.Func::new(locals, @lib.Expr::new(rewritten_body)),
unchanged_original: false,
trace_skip_raw: true,
})
}
}
if !options.validate_final_module {
return Ok({
reason: "hot-lift-error",
func: run_hot_pipeline_original_func(mod_, func_idx),
unchanged_original: true,
trace_skip_raw: true,
})
}
return Err("lift: \{err}")
}
}
let lifted_revision = @ir.hot_revision_current(func)
if run_hot_pipeline_debug_dump_enabled(options, func_idx, descriptor.name) &&
func_idx == @lib.FuncIdx::new(2944U) {
let use_def = @ir.use_def_build(func)
pass_manager_debug_dump_root_prefix(
options.trace,
func,
use_def,
"after-lift:\{descriptor.name}",
40,
)
pass_manager_debug_dump_node(
options.trace,
func,
use_def,
"after-lift:\{descriptor.name}",
0,
)
pass_manager_debug_dump_node(
options.trace,
func,
use_def,
"after-lift:\{descriptor.name}",
181,
)
pass_manager_debug_dump_node(
options.trace,
func,
use_def,
"after-lift:\{descriptor.name}",
182,
)
pass_manager_debug_dump_node(
options.trace,
func,
use_def,
"after-lift:\{descriptor.name}",
183,
)
pass_manager_debug_dump_node(
options.trace,
func,
use_def,
"after-lift:\{descriptor.name}",
557,
)
for node_id in [552, 553, 554, 555, 556, 558, 559, 560, 561, 562, 563] {
pass_manager_debug_dump_node(
options.trace,
func,
use_def,
"after-lift:\{descriptor.name}",
node_id,
)
}
}
if run_hot_pipeline_debug_dump_enabled(options, func_idx, descriptor.name) &&
func_idx == @lib.FuncIdx::new(3660U) {
let use_def = @ir.use_def_build(func)
let analysis = tuple_optimization_analyze(func, use_def)
let rewrite_mask = tuple_optimization_build_group_rewrite_mask(
func,
use_def,
analysis.query_summary,
analysis.groups,
)
(options.trace)(
"debug[after-lift:\{descriptor.name}]:group-count \{analysis.groups.length()} rewrite-mask=\{tuple_optimization_bitset_debug_string(rewrite_mask)}",
)
for group_id = 0
group_id < analysis.groups.length()
group_id = group_id + 1 {
(options.trace)(
"debug[after-lift:\{descriptor.name}]:group \{group_id} rewrite=\{rewrite_mask[group_id]} \{analysis.groups[group_id]}",
)
}
}
match options.perf {
Some(perf) => {
perf_count_hot_alloc_delta(
perf,
HotPerfHotSnapshot::empty(),
perf_snapshot_hot_func(func),
)
perf_dump_hot_func(perf, func, "after-lift")
}
None => ()
}
if descriptor.name == "vacuum" {
run_hot_pipeline_record_vacuum_hot_timer(
module_state,
"lift",
perf_elapsed_since_start_opt(options.perf, lift_start_us),
)
} else {
perf_stop_timer_opt(options.perf, "lift", lift_start_us)
}
match options.verify_policy {
HotPipelineVerifyPolicy::AfterEachPass =>
match
pipeline_verify_checkpoint_with_perf(
options.perf,
options.verify_policy,
"verify after lift",
HotPipelineVerifyTarget::HotFuncTarget(func),
"verify:after-lift",
) {
Ok(_) => ()
Err(err) => return Err(err)
}
HotPipelineVerifyPolicy::FinalModuleOnly => ()
}
let cache = @ir.HotAnalysisCache::new()
let ctx = HotPassContext::new(
descriptor,
cache,
options.verify_policy,
options.closed_world,
options.trace,
traps_never_happen=options.traps_never_happen,
ignore_implicit_traps=options.ignore_implicit_traps,
fast_math=options.fast_math,
perf=options.perf,
module_ctx=Some(module_ctx),
optimize_level=options.optimize_level,
shrink_level=options.shrink_level,
)
if descriptor.name == "optimize-instructions" {
ctx.allows_simd_introduction = run_hot_pipeline_allows_simd_introduction(
mod_, module_state,
)
}
let aggregate_pass_timer = if descriptor.name == "vacuum" {
Some(fn(name, elapsed_us) {
run_hot_pipeline_record_vacuum_hot_timer(module_state, name, elapsed_us)
})
} else {
None
}
let dce_requires_unchanged_lowering = if descriptor.name ==
"dead-code-elimination" {
match
run_hot_pipeline_dce_cached_requires_unchanged_lowering(
module_state, func_idx,
) {
Some(required) => required
None =>
run_hot_pipeline_dce_raw_requires_lowering_even_if_unchanged(original)
}
} else {
false
}
match
hot_pass_run_with_aggregate_perf_timer(ctx, func, aggregate_pass_timer) {
Ok(result) =>
if result.changed {
()
} else if !raw_preclean_changed &&
(
@ir.hot_revision_current(func) == lifted_revision ||
descriptor.name == "remove-unused-brs" ||
descriptor.name == "dead-code-elimination"
) &&
!(descriptor.name == "heap-store-optimization" &&
run_hot_pipeline_hso_original_requires_unchanged_lowering(
run_hot_pipeline_original_func(mod_, func_idx),
)) &&
!((
descriptor.name == "vacuum" ||
descriptor.name == "precompute" ||
descriptor.name == "precompute-propagate"
) &&
run_hot_pipeline_vacuum_needs_empty_func_nop_canonicalization(
run_hot_pipeline_original_func(mod_, func_idx),
)) &&
!dce_requires_unchanged_lowering {
let original = run_hot_pipeline_original_func(mod_, func_idx)
let (output, changed) = if descriptor.name == "constraint-analysis" {
ca_lower_cleanup_raw(
original,
mod_,
module_env=Some(
run_hot_pipeline_require_validation_module_env(
mod_, module_state, options,
),
),
)
} else if descriptor.name == "precompute" ||
descriptor.name == "precompute-propagate" {
pass_lower_cleanup_branchless_blocks(
original,
mod_,
module_env=Some(
run_hot_pipeline_require_validation_module_env(
mod_, module_state, options,
),
),
flatten_scalar_identity_blocks=true,
)
} else {
(original, false)
}
return Ok({
reason: "",
func: output,
unchanged_original: !changed,
trace_skip_raw: false,
})
}
Err(err) => return Err(err)
}
if run_hot_pipeline_debug_dump_enabled(options, func_idx, descriptor.name) &&
func_idx == @lib.FuncIdx::new(2944U) {
let use_def = @ir.use_def_build(func)
pass_manager_debug_dump_root_prefix(
options.trace,
func,
use_def,
"after-pass:\{descriptor.name}",
40,
)
pass_manager_debug_dump_node(
options.trace,
func,
use_def,
"after-pass:\{descriptor.name}",
0,
)
pass_manager_debug_dump_node(
options.trace,
func,
use_def,
"after-pass:\{descriptor.name}",
181,
)
pass_manager_debug_dump_node(
options.trace,
func,
use_def,
"after-pass:\{descriptor.name}",
182,
)
pass_manager_debug_dump_node(
options.trace,
func,
use_def,
"after-pass:\{descriptor.name}",
183,
)
pass_manager_debug_dump_node(
options.trace,
func,
use_def,
"after-pass:\{descriptor.name}",
557,
)
for node_id in [552, 553, 554, 555, 556, 558, 559, 560, 561, 562, 563] {
pass_manager_debug_dump_node(
options.trace,
func,
use_def,
"after-pass:\{descriptor.name}",
node_id,
)
}
}
if run_hot_pipeline_debug_dump_enabled(options, func_idx, descriptor.name) &&
func_idx == @lib.FuncIdx::new(3660U) {
(options.trace)(
"debug[after-pass:\{descriptor.name}]:core \{@ir.hot_debug_dump_core(func)}",
)
}
match options.verify_policy {
HotPipelineVerifyPolicy::AfterEachPass =>
match
pipeline_verify_checkpoint_with_perf(
options.perf,
options.verify_policy,
"verify before lower",
HotPipelineVerifyTarget::HotFuncTarget(func),
"verify:before-lower",
) {
Ok(_) => {
run_hot_pipeline_prepare_tuple_make_lowering(func, lower_state)
let lower_start_us = perf_start_timer_opt(options.perf, "lower")
let raw_lowered = @ir.hot_lower_func_with_options(
func,
run_hot_pipeline_lower_options_for_descriptor(descriptor.name),
)
let lowered = run_hot_pipeline_canonicalize_lowered_func_for_descriptor(
raw_lowered,
descriptor.name,
Some(run_hot_pipeline_original_func(mod_, func_idx)),
func_idx,
mod_,
module_env_provider=Some(fn() {
run_hot_pipeline_require_validation_module_env(
mod_, module_state, options,
)
}),
module_env=if descriptor.name == "precompute" ||
descriptor.name == "precompute-propagate" {
Some(
run_hot_pipeline_require_validation_module_env(
mod_, module_state, options,
),
)
} else {
None
},
module_ctx=if descriptor.name == "optimize-instructions" &&
!lower_state.type_sec_changed {
Some(
run_hot_pipeline_require_module_ctx(mod_, module_state, options),
)
} else {
None
},
)
if descriptor.name == "vacuum" {
run_hot_pipeline_record_vacuum_hot_timer(
module_state,
"lower",
perf_elapsed_since_start_opt(options.perf, lower_start_us),
)
} else {
perf_stop_timer_opt(options.perf, "lower", lower_start_us)
}
Ok({
reason: "",
func: lowered,
unchanged_original: false,
trace_skip_raw: false,
})
}
Err(err) => Err(err)
}
HotPipelineVerifyPolicy::FinalModuleOnly => {
match
run_hot_pipeline_unchecked_verify_before_lower_skip_reason(
func,
descriptor.name,
options,
) {
Some(err) => {
pipeline_trace_hook(
options.trace,
descriptor.name,
"verify-before-lower-error",
payload="func_idx=\{func_idx} err=\{err}",
)
return Ok({
reason: "hot-verify-before-lower-error",
func: run_hot_pipeline_original_func(mod_, func_idx),
unchanged_original: true,
trace_skip_raw: true,
})
}
None => ()
}
run_hot_pipeline_prepare_tuple_make_lowering(func, lower_state)
let lower_start_us = perf_start_timer_opt(options.perf, "lower")
let raw_lowered = @ir.hot_lower_func_unverified_with_options(
func,
run_hot_pipeline_lower_options_for_descriptor(descriptor.name),
)
let lowered = run_hot_pipeline_canonicalize_lowered_func_for_descriptor(
raw_lowered,
descriptor.name,
Some(run_hot_pipeline_original_func(mod_, func_idx)),
func_idx,
mod_,
module_env_provider=Some(fn() {
run_hot_pipeline_require_validation_module_env(
mod_, module_state, options,
)
}),
module_env=if descriptor.name == "precompute" ||
descriptor.name == "precompute-propagate" {
Some(
run_hot_pipeline_require_validation_module_env(
mod_, module_state, options,
),
)
} else {
None
},
module_ctx=if descriptor.name == "optimize-instructions" &&
!lower_state.type_sec_changed {
Some(run_hot_pipeline_require_module_ctx(mod_, module_state, options))
} else {
None
},
)
if descriptor.name == "vacuum" {
run_hot_pipeline_record_vacuum_hot_timer(
module_state,
"lower",
perf_elapsed_since_start_opt(options.perf, lower_start_us),
)
} else {
perf_stop_timer_opt(options.perf, "lower", lower_start_us)
}
Ok({
reason: "",
func: lowered,
unchanged_original: false,
trace_skip_raw: false,
})
}
}
}
///|
fn run_hot_pipeline_original_func(
mod_ : @lib.Module,
func_idx : @lib.FuncIdx,
) -> @lib.Func {
let imported_func_count = run_hot_pipeline_imported_func_count(mod_)
let @lib.FuncIdx(raw) = func_idx
let code_idx = raw.reinterpret_as_int() - imported_func_count
if code_idx < 0 {
abort("hot pipeline original func lookup requires a defined function index")
}
match mod_.code_sec {
Some(@lib.CodeSec(defs)) =>
match defs.get(code_idx) {
Some(def) => def
None =>
abort(
"hot pipeline original func lookup requires an existing code entry",
)
}
None => abort("hot pipeline original func lookup requires a code section")
}
}
///|
fn run_hot_pipeline_module_with_rewritten_func(
mod_ : @lib.Module,
func_idx : @lib.FuncIdx,
rewritten : @lib.Func,
) -> @lib.Module {
let imported_func_count = run_hot_pipeline_imported_func_count(mod_)
let @lib.FuncIdx(raw) = func_idx
let code_idx = raw.reinterpret_as_int() - imported_func_count
if code_idx < 0 {
abort("hot pipeline rewrite func requires a defined function index")
}
match mod_.code_sec {
Some(@lib.CodeSec(defs)) => {
if code_idx >= defs.length() {
abort("hot pipeline rewrite func requires an existing code entry")
}
let next = defs.copy()
next[code_idx] = rewritten
mod_.with_code_sec(@lib.CodeSec::new(next))
}
None => abort("hot pipeline rewrite func requires a code section")
}
}
///|
fn run_hot_pipeline_precompute_writeback_validation_error(
mod_ : @lib.Module,
code_idx : Int32,
rewritten : @lib.Func,
) -> String? {
let func_idx = @lib.FuncIdx::new(
(run_hot_pipeline_imported_func_count(mod_) + code_idx).reinterpret_as_uint(),
)
match
@validate.validate_defined_func_against_module(mod_, func_idx, rewritten) {
Ok(_) => None
Err(err) => Some(err)
}
}
///|
fn run_hot_pipeline_ssa_nomerge_nonvalidation_skip_invalid_lower_reason(
result : HotPipelineFuncResult,
) -> String? {
if result.unchanged_original {
return None
}
if run_hot_pipeline_precompute_lowered_func_has_invalid_escape_carrier(
result.func,
) {
return Some("invalid-escape-carrier")
}
if run_hot_pipeline_lowered_func_has_narrow_suspicious_escape_carrier(
result.func,
) {
return Some("suspicious-escape-carrier")
}
None
}
///|
fn run_hot_pipeline_dce_nonvalidation_skip_invalid_lower_reason(
result : HotPipelineFuncResult,
) -> String? {
if result.unchanged_original {
return None
}
if run_hot_pipeline_precompute_lowered_func_has_invalid_escape_carrier(
result.func,
) {
return Some("invalid-escape-carrier")
}
if run_hot_pipeline_lowered_func_exceeds_local_limit(result.func) {
return Some("local-limit")
}
None
}
///|
fn run_hot_pipeline_touched_nonvalidation_skip_invalid_lower_reason(
descriptor_name : String,
result : HotPipelineFuncResult,
) -> String? {
if result.unchanged_original {
return None
}
if descriptor_name == "precompute" ||
descriptor_name == "precompute-propagate" {
if run_hot_pipeline_precompute_lowered_func_has_invalid_escape_carrier(
result.func,
) {
return Some("invalid-escape-carrier")
}
return None
}
if descriptor_name == "dead-code-elimination" {
return run_hot_pipeline_dce_nonvalidation_skip_invalid_lower_reason(result)
}
if run_hot_pipeline_descriptor_is_simplify_locals_family(descriptor_name) {
if run_hot_pipeline_precompute_lowered_func_has_invalid_escape_carrier(
result.func,
) {
return Some("invalid-escape-carrier")
}
if run_hot_pipeline_lowered_func_has_suspicious_escape_carrier(result.func) {
return Some("suspicious-escape-carrier")
}
return None
}
if descriptor_name == "ssa-nomerge" {
return run_hot_pipeline_ssa_nomerge_nonvalidation_skip_invalid_lower_reason(
result,
)
}
None
}
///|
fn run_hot_pipeline_skip_invalid_lower_reason(
current_mod : @lib.Module,
validation_mod : @lib.Module,
code_idx : Int32,
descriptor_name : String,
result : HotPipelineFuncResult,
) -> String? {
if result.unchanged_original {
return None
}
if descriptor_name == "precompute" ||
descriptor_name == "precompute-propagate" {
if run_hot_pipeline_precompute_lowered_func_has_invalid_escape_carrier(
result.func,
) {
return Some("invalid-escape-carrier")
}
return match
run_hot_pipeline_precompute_writeback_validation_error(
validation_mod,
code_idx,
result.func,
) {
Some(msg) => Some("writeback-validate:\{msg}")
None => None
}
}
if descriptor_name == "dead-code-elimination" {
match run_hot_pipeline_dce_nonvalidation_skip_invalid_lower_reason(result) {
Some(reason) => return Some(reason)
None => ()
}
return match
run_hot_pipeline_precompute_writeback_validation_error(
validation_mod,
code_idx,
result.func,
) {
Some(msg) => Some("writeback-validate:\{msg}")
None => None
}
}
if run_hot_pipeline_descriptor_is_simplify_locals_family(descriptor_name) {
if run_hot_pipeline_precompute_lowered_func_has_invalid_escape_carrier(
result.func,
) {
return Some("invalid-escape-carrier")
}
if run_hot_pipeline_lowered_func_has_suspicious_escape_carrier(result.func) {
return Some("suspicious-escape-carrier")
}
return match
run_hot_pipeline_precompute_writeback_validation_error(
validation_mod,
code_idx,
result.func,
) {
Some(msg) => Some("writeback-validate:\{msg}")
None => None
}
}
if descriptor_name == "optimize-instructions" ||
descriptor_name == "tuple-optimization" {
return match
run_hot_pipeline_precompute_writeback_validation_error(
validation_mod,
code_idx,
result.func,
) {
Some(msg) => Some("writeback-validate:\{msg}")
None => None
}
}
if descriptor_name == "remove-unused-brs" {
return match
run_hot_pipeline_precompute_writeback_validation_error(
validation_mod,
code_idx,
result.func,
) {
Some(msg) => Some("writeback-validate:\{msg}")
None => None
}
}
if descriptor_name == "vacuum" {
return match
run_hot_pipeline_precompute_writeback_validation_error(
validation_mod,
code_idx,
result.func,
) {
Some(msg) => Some("writeback-validate:\{msg}")
None => None
}
}
if descriptor_name == "ssa-nomerge" {
match
run_hot_pipeline_ssa_nomerge_nonvalidation_skip_invalid_lower_reason(
result,
) {
Some(reason) => return Some(reason)
None => ()
}
return match
run_hot_pipeline_precompute_writeback_validation_error(
current_mod,
code_idx,
result.func,
) {
Some(msg) => Some("writeback-validate:\{msg}")
None => None
}
}
None
}
///|
fn run_hot_pipeline_precompute_branch_depth(instr : @lib.Instruction) -> Int32? {
match instr {
@lib.Instruction::Br(label_idx) => {
let @lib.LabelIdx(raw) = label_idx
Some(raw.reinterpret_as_int())
}
_ => None
}
}
///|
fn run_hot_pipeline_precompute_has_control_transfer(
instr : @lib.Instruction,
) -> Bool {
match instr {
@lib.Instruction::Block(_, _)
| @lib.Instruction::Loop(_, _)
| @lib.Instruction::If(_, _, _)
| @lib.Instruction::TryTable(_, _, _)
| @lib.Instruction::Br(_)
| @lib.Instruction::BrIf(_)
| @lib.Instruction::BrTable(_, _)
| @lib.Instruction::Return
| @lib.Instruction::Throw(_)
| @lib.Instruction::ThrowRef
| @lib.Instruction::Unreachable => true
_ => false
}
}
///|
fn run_hot_pipeline_precompute_has_mismatched_escape_carrier_block(
instr : @lib.Instruction,
) -> Bool {
let walk_body = fn(body : Array[@lib.Instruction]) -> Bool {
body.any(fn(child) {
run_hot_pipeline_precompute_has_mismatched_escape_carrier_block(child)
})
}
match instr {
@lib.Instruction::Block(
block_type,
@lib.Expr(
[
@lib.Instruction::Block(
@lib.VoidBlockType,
@lib.Expr(
[
.. prefix,
@lib.Instruction::If(@lib.VoidBlockType, then_, Some(else_)),
@lib.Instruction::Unreachable,
]
)
),
@lib.Instruction::LocalGet(_),
]
)
) => {
match block_type {
@lib.VoidBlockType => ()
_ =>
match
(
match then_.last() {
Some(last) => run_hot_pipeline_precompute_branch_depth(last)
None => None
},
match else_.last() {
Some(last) => run_hot_pipeline_precompute_branch_depth(last)
None => None
},
) {
(Some(then_depth), Some(else_depth)) if (
then_depth == 1 && else_depth == 2
) ||
(then_depth == 2 && else_depth == 1) => {
for item in prefix {
if run_hot_pipeline_precompute_has_control_transfer(item) {
return false
}
}
return true
}
_ => ()
}
}
if walk_body(then_) || walk_body(else_) {
return true
}
for item in prefix {
if run_hot_pipeline_precompute_has_mismatched_escape_carrier_block(item) {
return true
}
}
false
}
@lib.Instruction::Block(_, @lib.Expr(body))
| @lib.Instruction::Loop(_, @lib.Expr(body))
| @lib.Instruction::TryTable(_, _, @lib.Expr(body)) => walk_body(body)
@lib.Instruction::If(_, then_, Some(else_)) =>
walk_body(then_) || walk_body(else_)
@lib.Instruction::If(_, then_, None) => walk_body(then_)
_ => false
}
}
///|
fn run_hot_pipeline_has_hard_exit(instr : @lib.Instruction) -> Bool {
match instr {
@lib.Instruction::Return
| @lib.Instruction::Unreachable
| @lib.Instruction::Throw(_)
| @lib.Instruction::ThrowRef => true
@lib.Instruction::Block(_, @lib.Expr(body))
| @lib.Instruction::Loop(_, @lib.Expr(body))
| @lib.Instruction::TryTable(_, _, @lib.Expr(body)) =>
body.any(fn(child) { run_hot_pipeline_has_hard_exit(child) })
@lib.Instruction::If(_, then_, Some(else_)) =>
then_.any(fn(child) { run_hot_pipeline_has_hard_exit(child) }) ||
else_.any(fn(child) { run_hot_pipeline_has_hard_exit(child) })
@lib.Instruction::If(_, then_, None) =>
then_.any(fn(child) { run_hot_pipeline_has_hard_exit(child) })
_ => false
}
}
///|
fn run_hot_pipeline_has_immediate_hard_exit(instr : @lib.Instruction) -> Bool {
match instr {
@lib.Instruction::Return
| @lib.Instruction::Unreachable
| @lib.Instruction::Throw(_)
| @lib.Instruction::ThrowRef => true
_ => false
}
}
///|
fn run_hot_pipeline_has_invalid_hard_exit_escape_carrier_block(
instr : @lib.Instruction,
) -> Bool {
let walk_body = fn(body : Array[@lib.Instruction]) -> Bool {
body.any(fn(child) {
run_hot_pipeline_has_invalid_hard_exit_escape_carrier_block(child)
})
}
match instr {
@lib.Instruction::Block(
block_type,
@lib.Expr(
[
@lib.Instruction::Block(@lib.VoidBlockType, @lib.Expr(prefix)),
..,
@lib.Instruction::LocalGet(_),
]
)
) =>
match block_type {
@lib.VoidBlockType => walk_body(prefix)
_ =>
if prefix.any(fn(item) { run_hot_pipeline_has_hard_exit(item) }) {
true
} else {
walk_body(prefix)
}
}
@lib.Instruction::Block(_, @lib.Expr(body))
| @lib.Instruction::Loop(_, @lib.Expr(body))
| @lib.Instruction::TryTable(_, _, @lib.Expr(body)) => walk_body(body)
@lib.Instruction::If(_, then_, Some(else_)) =>
walk_body(then_) || walk_body(else_)
@lib.Instruction::If(_, then_, None) => walk_body(then_)
_ => false
}
}
///|
fn run_hot_pipeline_has_narrow_invalid_hard_exit_escape_carrier_block(
instr : @lib.Instruction,
) -> Bool {
let walk_body = fn(body : Array[@lib.Instruction]) -> Bool {
body.any(fn(child) {
run_hot_pipeline_has_narrow_invalid_hard_exit_escape_carrier_block(child)
})
}
match instr {
@lib.Instruction::Block(
block_type,
@lib.Expr(
[
@lib.Instruction::Block(@lib.VoidBlockType, @lib.Expr(prefix)),
..,
@lib.Instruction::LocalGet(_),
]
)
) =>
match block_type {
@lib.VoidBlockType => walk_body(prefix)
_ =>
if prefix.any(fn(item) {
run_hot_pipeline_has_immediate_hard_exit(item)
}) {
true
} else {
walk_body(prefix)
}
}
@lib.Instruction::Block(_, @lib.Expr(body))
| @lib.Instruction::Loop(_, @lib.Expr(body))
| @lib.Instruction::TryTable(_, _, @lib.Expr(body)) => walk_body(body)
@lib.Instruction::If(_, then_, Some(else_)) =>
walk_body(then_) || walk_body(else_)
@lib.Instruction::If(_, then_, None) => walk_body(then_)
_ => false
}
}
///|
fn run_hot_pipeline_precompute_lowered_func_has_invalid_escape_carrier(
func : @lib.Func,
) -> Bool {
let @lib.Func(_, @lib.Expr(body)) = func
body.any(fn(instr) {
run_hot_pipeline_precompute_has_mismatched_escape_carrier_block(instr)
})
}
///|
fn run_hot_pipeline_lowered_func_has_suspicious_escape_carrier(
func : @lib.Func,
) -> Bool {
let @lib.Func(_, @lib.Expr(body)) = func
body.any(fn(instr) {
run_hot_pipeline_has_invalid_hard_exit_escape_carrier_block(instr)
})
}
///|
fn run_hot_pipeline_lowered_func_has_narrow_suspicious_escape_carrier(
func : @lib.Func,
) -> Bool {
let @lib.Func(_, @lib.Expr(body)) = func
body.any(fn(instr) {
run_hot_pipeline_has_narrow_invalid_hard_exit_escape_carrier_block(instr)
})
}
///|
fn run_hot_pipeline_lowered_func_exceeds_local_limit(func : @lib.Func) -> Bool {
let @lib.Func(locals, _) = func
locals.length() > 50000
}
///|
fn run_hot_pipeline_instr_has_typeidx_loop(instr : @lib.Instruction) -> Bool {
match instr {
@lib.Loop(@lib.TypeIdxBlockType(_), _) => true
@lib.Loop(_, @lib.Expr(body))
| @lib.Block(_, @lib.Expr(body))
| @lib.TryTable(_, _, @lib.Expr(body)) =>
run_hot_pipeline_instrs_have_typeidx_loop(body)
@lib.If(_, then_body, else_body) => {
if run_hot_pipeline_instrs_have_typeidx_loop(then_body) {
return true
}
match else_body {
Some(body) => run_hot_pipeline_instrs_have_typeidx_loop(body)
None => false
}
}
_ => false
}
}
///|
fn run_hot_pipeline_instrs_have_typeidx_loop(
instrs : Array[@lib.Instruction],
) -> Bool {
instrs.any(fn(instr) { run_hot_pipeline_instr_has_typeidx_loop(instr) })
}
///|
fn run_hot_pipeline_lowered_func_has_typeidx_loop(func : @lib.Func) -> Bool {
let @lib.Func(_, @lib.Expr(body)) = func
run_hot_pipeline_instrs_have_typeidx_loop(body)
}
///|
fn run_hot_pipeline_module_has_typeidx_loop(mod_ : @lib.Module) -> Bool {
match mod_.code_sec {
Some(@lib.CodeSec(funcs)) =>
funcs.any(fn(func) {
run_hot_pipeline_lowered_func_has_typeidx_loop(func)
})
None => false
}
}
///|
fn run_hot_pipeline_pass_should_skip_typeidx_loop_func(name : String) -> Bool {
name == "coalesce-locals" ||
name == "local-cse" ||
name == "merge-locals" ||
name == "precompute" ||
name == "precompute-propagate" ||
name == "reorder-locals" ||
name == "simplify-locals" ||
name == "simplify-locals-nostructure" ||
name == "simplify-locals-no-structure" ||
name == "simplify-locals-notee-nostructure"
}
///|
fn run_hot_pipeline_typeidx_loop_skip_can_try_raw(
name : String,
func : @lib.Func,
) -> Bool {
if name != "simplify-locals" &&
!run_hot_pipeline_simplify_locals_descriptor_is_nostructure(name) {
return false
}
let @lib.Func(_, @lib.Expr(body)) = func
if !run_hot_pipeline_raw_simplify_locals_has_local_write(body) {
return true
}
if name != "simplify-locals" {
return false
}
let (_, adjacent_rewrites) = run_hot_pipeline_raw_simplify_locals_rewrite_adjacent_set_gets(
body,
)
adjacent_rewrites > 0
}
///|
fn run_hot_pipeline_drop_label_names(mod_ : @lib.Module) -> @lib.Module {
match mod_.name_sec {
Some(name_sec) => {
if name_sec.label_names is None {
return mod_
}
mod_.with_name_sec(
@lib.NameSec::new(
module_name=name_sec.module_name,
func_names=name_sec.func_names,
local_names=name_sec.local_names,
type_names=name_sec.type_names,
table_names=name_sec.table_names,
memory_names=name_sec.memory_names,
global_names=name_sec.global_names,
elem_names=name_sec.elem_names,
data_names=name_sec.data_names,
field_names=name_sec.field_names,
tag_names=name_sec.tag_names,
),
)
}
None => mod_
}
}
///|
fn run_hot_pipeline_drop_local_names(mod_ : @lib.Module) -> @lib.Module {
match mod_.name_sec {
Some(name_sec) => {
if name_sec.local_names is None {
return mod_
}
mod_.with_name_sec(
@lib.NameSec::new(
module_name=name_sec.module_name,
func_names=name_sec.func_names,
label_names=name_sec.label_names,
type_names=name_sec.type_names,
table_names=name_sec.table_names,
memory_names=name_sec.memory_names,
global_names=name_sec.global_names,
elem_names=name_sec.elem_names,
data_names=name_sec.data_names,
field_names=name_sec.field_names,
tag_names=name_sec.tag_names,
),
)
}
None => mod_
}
}
///|
fn run_hot_pipeline_vacuum_repair_batch_writeback(
original_mod : @lib.Module,
candidate_mod : @lib.Module,
options : HotPipelineOptions,
) -> @lib.Module? {
let imported_func_count = run_hot_pipeline_imported_func_count(original_mod)
match (original_mod.code_sec, candidate_mod.code_sec) {
(Some(@lib.CodeSec(original_defs)), Some(@lib.CodeSec(candidate_defs))) => {
let changed_funcs : Array[(@lib.FuncIdx, @lib.Func)] = []
for code_idx = 0
code_idx < candidate_defs.length()
code_idx = code_idx + 1 {
if code_idx < original_defs.length() &&
candidate_defs[code_idx] != original_defs[code_idx] {
changed_funcs.push(
(
@lib.FuncIdx::new(
(imported_func_count + code_idx).reinterpret_as_uint(),
),
candidate_defs[code_idx],
),
)
}
}
if changed_funcs.length() == 0 {
return Some(candidate_mod)
}
let invalid_funcs = match
@validate.validate_defined_funcs_against_module(
candidate_mod, changed_funcs,
) {
Ok(errors) => errors
Err(err) => {
pipeline_trace_hook(
options.trace,
"vacuum",
"writeback-batch-invalid",
payload="err=\{err}",
)
return None
}
}
if invalid_funcs.length() == 0 {
return Some(candidate_mod)
}
pipeline_trace_hook(
options.trace,
"vacuum",
"writeback-batch-invalid",
payload="changed=\{changed_funcs.length()} invalid=\{invalid_funcs.length()}",
)
let repaired_defs = candidate_defs.copy()
for item in invalid_funcs {
let (func_idx, err) = item
let @lib.FuncIdx(raw_func_idx) = func_idx
let code_idx = raw_func_idx.reinterpret_as_int() - imported_func_count
if code_idx < 0 ||
code_idx >= repaired_defs.length() ||
code_idx >= original_defs.length() {
return None
}
if repaired_defs[code_idx] == original_defs[code_idx] {
return None
}
repaired_defs[code_idx] = original_defs[code_idx]
pipeline_trace_hook(
options.trace,
"vacuum",
"skip-invalid-lower",
payload="func=\{func_idx} reason=writeback-batch-validate:\{err}",
)
}
Some(candidate_mod.with_code_sec(@lib.CodeSec::new(repaired_defs)))
}
_ => None
}
}
///|
fn run_hot_pipeline_ssa_repair_batch_writeback(
pass_name : String,
original_mod : @lib.Module,
candidate_mod : @lib.Module,
options : HotPipelineOptions,
) -> @lib.Module? {
let imported_func_count = run_hot_pipeline_imported_func_count(original_mod)
match (original_mod.code_sec, candidate_mod.code_sec) {
(Some(@lib.CodeSec(original_defs)), Some(@lib.CodeSec(candidate_defs))) => {
let changed_funcs : Array[(@lib.FuncIdx, @lib.Func)] = []
for code_idx = 0
code_idx < candidate_defs.length()
code_idx = code_idx + 1 {
if code_idx < original_defs.length() &&
candidate_defs[code_idx] != original_defs[code_idx] {
changed_funcs.push(
(
@lib.FuncIdx::new(
(imported_func_count + code_idx).reinterpret_as_uint(),
),
candidate_defs[code_idx],
),
)
}
}
if changed_funcs.length() == 0 {
return Some(candidate_mod)
}
let invalid_funcs = match
@validate.validate_defined_funcs_against_module(
original_mod, changed_funcs,
) {
Ok(errors) => errors
Err(err) => {
pipeline_trace_hook(
options.trace,
pass_name,
"writeback-batch-invalid",
payload="err=\{err}",
)
return None
}
}
if invalid_funcs.length() == 0 {
return Some(candidate_mod)
}
pipeline_trace_hook(
options.trace,
pass_name,
"writeback-batch-invalid",
payload="changed=\{changed_funcs.length()} invalid=\{invalid_funcs.length()}",
)
let repaired_defs = candidate_defs.copy()
for item in invalid_funcs {
let (func_idx, err) = item
let @lib.FuncIdx(raw_func_idx) = func_idx
let code_idx = raw_func_idx.reinterpret_as_int() - imported_func_count
if code_idx < 0 ||
code_idx >= repaired_defs.length() ||
code_idx >= original_defs.length() {
return None
}
if repaired_defs[code_idx] == original_defs[code_idx] {
return None
}
repaired_defs[code_idx] = original_defs[code_idx]
pipeline_trace_hook(
options.trace,
pass_name,
"skip-invalid-lower",
payload="func=\{func_idx} reason=writeback-batch-validate:\{err}",
)
}
Some(candidate_mod.with_code_sec(@lib.CodeSec::new(repaired_defs)))
}
_ => None
}
}
///|
fn run_hot_pipeline_repair_touched_batch_writeback(
original_mod : @lib.Module,
candidate_mod : @lib.Module,
touched : Array[Bool],
descriptor_name : String,
options : HotPipelineOptions,
touched_proves_changed~ : Bool = false,
) -> @lib.Module? {
let imported_func_count = run_hot_pipeline_imported_func_count(original_mod)
match (original_mod.code_sec, candidate_mod.code_sec) {
(Some(@lib.CodeSec(original_defs)), Some(@lib.CodeSec(candidate_defs))) => {
if original_defs.length() != candidate_defs.length() {
return None
}
let changed_funcs : Array[(@lib.FuncIdx, @lib.Func)] = []
for code_idx = 0
code_idx < candidate_defs.length()
code_idx = code_idx + 1 {
if code_idx < touched.length() &&
touched[code_idx] &&
(
touched_proves_changed ||
candidate_defs[code_idx] != original_defs[code_idx]
) {
changed_funcs.push(
(
@lib.FuncIdx::new(
(imported_func_count + code_idx).reinterpret_as_uint(),
),
candidate_defs[code_idx],
),
)
}
}
if changed_funcs.length() == 0 {
return Some(candidate_mod)
}
let invalid_funcs = match
@validate.validate_defined_funcs_against_module(
candidate_mod, changed_funcs,
) {
Ok(errors) => errors
Err(err) => {
pipeline_trace_hook(
options.trace,
descriptor_name,
"writeback-batch-invalid",
payload="err=\{err}",
)
return None
}
}
if invalid_funcs.length() == 0 {
return Some(candidate_mod)
}
pipeline_trace_hook(
options.trace,
descriptor_name,
"writeback-batch-invalid",
payload="changed=\{changed_funcs.length()} invalid=\{invalid_funcs.length()}",
)
let repaired_defs = candidate_defs.copy()
for item in invalid_funcs {
let (func_idx, err) = item
let @lib.FuncIdx(raw_func_idx) = func_idx
let code_idx = raw_func_idx.reinterpret_as_int() - imported_func_count
if code_idx < 0 ||
code_idx >= repaired_defs.length() ||
code_idx >= original_defs.length() ||
code_idx >= touched.length() ||
!touched[code_idx] ||
(
!touched_proves_changed &&
repaired_defs[code_idx] == original_defs[code_idx]
) {
return None
}
repaired_defs[code_idx] = original_defs[code_idx]
pipeline_trace_hook(
options.trace,
descriptor_name,
"skip-invalid-lower",
payload="func=\{func_idx} reason=writeback-batch-validate:\{err}",
)
}
Some(candidate_mod.with_code_sec(@lib.CodeSec::new(repaired_defs)))
}
_ => None
}
}
///|
fn run_hot_pipeline_precompute_repair_batch_writeback(
original_mod : @lib.Module,
candidate_mod : @lib.Module,
touched : Array[Bool],
descriptor_name : String,
options : HotPipelineOptions,
) -> @lib.Module? {
run_hot_pipeline_repair_touched_batch_writeback(
original_mod, candidate_mod, touched, descriptor_name, options,
)
}
///|
fn run_hot_pipeline_optimize_instructions_repair_batch_writeback(
original_mod : @lib.Module,
candidate_mod : @lib.Module,
options : HotPipelineOptions,
) -> @lib.Module? {
match original_mod.code_sec {
Some(@lib.CodeSec(defs)) =>
run_hot_pipeline_repair_touched_batch_writeback(
original_mod,
candidate_mod,
Array::make(defs.length(), true),
"optimize-instructions",
options,
)
None => Some(candidate_mod)
}
}
///|
fn run_hot_pipeline_simplify_locals_repair_batch_writeback(
original_mod : @lib.Module,
candidate_mod : @lib.Module,
touched : Array[Bool],
descriptor_name : String,
options : HotPipelineOptions,
) -> @lib.Module? {
run_hot_pipeline_repair_touched_batch_writeback(
original_mod,
candidate_mod,
touched,
descriptor_name,
options,
touched_proves_changed=true,
)
}
///|
fn run_hot_pipeline_remove_unused_brs_repair_batch_writeback(
original_mod : @lib.Module,
candidate_mod : @lib.Module,
options : HotPipelineOptions,
) -> @lib.Module? {
let imported_func_count = run_hot_pipeline_imported_func_count(original_mod)
match (original_mod.code_sec, candidate_mod.code_sec) {
(Some(@lib.CodeSec(original_defs)), Some(@lib.CodeSec(candidate_defs))) => {
let changed_funcs : Array[(@lib.FuncIdx, @lib.Func)] = []
for code_idx = 0
code_idx < candidate_defs.length()
code_idx = code_idx + 1 {
if code_idx < original_defs.length() &&
candidate_defs[code_idx] != original_defs[code_idx] {
changed_funcs.push(
(
@lib.FuncIdx::new(
(imported_func_count + code_idx).reinterpret_as_uint(),
),
candidate_defs[code_idx],
),
)
}
}
if changed_funcs.length() == 0 {
return Some(candidate_mod)
}
let invalid_funcs = match
@validate.validate_defined_funcs_against_module(
candidate_mod, changed_funcs,
) {
Ok(errors) => errors
Err(err) => {
pipeline_trace_hook(
options.trace,
"remove-unused-brs",
"writeback-batch-invalid",
payload="err=\{err}",
)
return None
}
}
if invalid_funcs.length() == 0 {
return Some(candidate_mod)
}
pipeline_trace_hook(
options.trace,
"remove-unused-brs",
"writeback-batch-invalid",
payload="changed=\{changed_funcs.length()} invalid=\{invalid_funcs.length()}",
)
let repaired_defs = candidate_defs.copy()
for item in invalid_funcs {
let (func_idx, err) = item
let @lib.FuncIdx(raw_func_idx) = func_idx
let code_idx = raw_func_idx.reinterpret_as_int() - imported_func_count
if code_idx < 0 ||
code_idx >= repaired_defs.length() ||
code_idx >= original_defs.length() {
return None
}
if repaired_defs[code_idx] == original_defs[code_idx] {
return None
}
repaired_defs[code_idx] = original_defs[code_idx]
pipeline_trace_hook(
options.trace,
"remove-unused-brs",
"skip-invalid-lower",
payload="func=\{func_idx} reason=writeback-batch-validate:\{err}",
)
}
Some(candidate_mod.with_code_sec(@lib.CodeSec::new(repaired_defs)))
}
_ => None
}
}
///|
fn run_hot_pipeline_dce_repair_batch_writeback(
original_mod : @lib.Module,
candidate_mod : @lib.Module,
options : HotPipelineOptions,
) -> @lib.Module? {
let imported_func_count = run_hot_pipeline_imported_func_count(original_mod)
match (original_mod.code_sec, candidate_mod.code_sec) {
(Some(@lib.CodeSec(original_defs)), Some(@lib.CodeSec(candidate_defs))) => {
let changed_funcs : Array[(@lib.FuncIdx, @lib.Func)] = []
for code_idx = 0
code_idx < candidate_defs.length()
code_idx = code_idx + 1 {
if code_idx < original_defs.length() &&
candidate_defs[code_idx] != original_defs[code_idx] {
changed_funcs.push(
(
@lib.FuncIdx::new(
(imported_func_count + code_idx).reinterpret_as_uint(),
),
candidate_defs[code_idx],
),
)
}
}
if changed_funcs.length() == 0 {
return Some(candidate_mod)
}
let invalid_funcs = match
@validate.validate_defined_funcs_against_module(
candidate_mod, changed_funcs,
) {
Ok(errors) => errors
Err(err) => {
pipeline_trace_hook(
options.trace,
"dead-code-elimination",
"writeback-batch-invalid",
payload="err=\{err}",
)
return None
}
}
if invalid_funcs.length() == 0 {
return Some(candidate_mod)
}
pipeline_trace_hook(
options.trace,
"dead-code-elimination",
"writeback-batch-invalid",
payload="changed=\{changed_funcs.length()} invalid=\{invalid_funcs.length()}",
)
let repaired_defs = candidate_defs.copy()
for item in invalid_funcs {
let (func_idx, err) = item
let @lib.FuncIdx(raw_func_idx) = func_idx
let code_idx = raw_func_idx.reinterpret_as_int() - imported_func_count
if code_idx < 0 ||
code_idx >= repaired_defs.length() ||
code_idx >= original_defs.length() {
return None
}
if repaired_defs[code_idx] == original_defs[code_idx] {
return None
}
repaired_defs[code_idx] = original_defs[code_idx]
pipeline_trace_hook(
options.trace,
"dead-code-elimination",
"skip-invalid-lower",
payload="func=\{func_idx} reason=writeback-batch-validate:\{err}",
)
}
Some(candidate_mod.with_code_sec(@lib.CodeSec::new(repaired_defs)))
}
_ => None
}
}
///|
fn run_hot_pipeline_apply_hot_pass(
mod_ : @lib.Module,
entry : HotPassRegistryEntry,
options : HotPipelineOptions,
defer_vacuum_writeback_guard~ : Bool = true,
defer_ssa_nomerge_writeback_guard~ : Bool = true,
defer_remove_unused_brs_writeback_guard~ : Bool = true,
defer_dce_writeback_guard~ : Bool = true,
defer_precompute_writeback_guard~ : Bool = true,
defer_simplify_locals_writeback_guard~ : Bool = true,
defer_optimize_instructions_writeback_guard~ : Bool = true,
) -> Result[@lib.Module, String] {
let descriptor = match entry.descriptor {
Some(descriptor) => descriptor
None => return Err("missing hot pass descriptor for \{entry.name}")
}
if pass_module_has_offset_sensitive_metadata(mod_) {
pipeline_trace_hook(
options.trace,
descriptor.name,
"fallback-noop",
payload="reason=offset-sensitive-metadata",
)
return Ok(mod_)
}
if descriptor.name == "pick-load-signs" &&
!run_hot_pipeline_module_has_memory(mod_) {
pipeline_trace_hook(options.trace, descriptor.name, "skip-no-memory")
return Ok(mod_)
}
if (descriptor.name == "simplify-locals" || descriptor.name == "merge-blocks") &&
o4z_size_candidate_has_unsafe_emscripten_main_runtime(mod_) {
pipeline_trace_hook(
options.trace,
descriptor.name,
"fallback-noop",
payload="reason=emscripten-main-stateful-runtime-\{descriptor.name}-noop",
)
return Ok(mod_)
}
if (descriptor.name == "flatten" || descriptor.name == "merge-locals") &&
dae_defined_func_count(mod_) >= 32 &&
run_hot_pipeline_module_has_typeidx_loop(mod_) {
pipeline_trace_hook(
options.trace,
descriptor.name,
"fallback-noop",
payload="reason=large-typed-loop-module-interference",
)
return Ok(mod_)
}
let module_state = HotPipelineModuleState::new()
let defer_this_vacuum_writeback_guard = defer_vacuum_writeback_guard &&
descriptor.name == "vacuum"
let defer_this_ssa_nomerge_writeback_guard = defer_ssa_nomerge_writeback_guard &&
(descriptor.name == "ssa-nomerge" || descriptor.name == "ssa")
let defer_this_remove_unused_brs_writeback_guard = defer_remove_unused_brs_writeback_guard &&
descriptor.name == "remove-unused-brs"
let defer_this_dce_writeback_guard = defer_dce_writeback_guard &&
descriptor.name == "dead-code-elimination"
let defer_this_precompute_writeback_guard = defer_precompute_writeback_guard &&
(
descriptor.name == "precompute" ||
descriptor.name == "precompute-propagate"
)
let defer_this_simplify_locals_writeback_guard = defer_simplify_locals_writeback_guard &&
run_hot_pipeline_descriptor_is_simplify_locals_family(descriptor.name)
let defer_this_optimize_instructions_writeback_guard = defer_optimize_instructions_writeback_guard &&
descriptor.name == "optimize-instructions"
let precompute_touched : Array[Bool]? = if defer_this_precompute_writeback_guard {
match mod_.code_sec {
Some(@lib.CodeSec(defs)) => Some(Array::make(defs.length(), false))
None => None
}
} else {
None
}
let mut simplify_locals_touched : Array[Bool]? = None
let mut current_mod = mod_
let imported_func_count = run_hot_pipeline_imported_func_count(mod_)
let code_section_start_us = perf_start_timer_opt(
options.perf,
"stage:hot-pass:code-section",
)
let next_code_sec = match mod_.code_sec {
Some(@lib.CodeSec(defs)) => {
let mut lowered : Array[@lib.Func]? = None
let mut hso_raw_skip_count = 0
let mut h2l_raw_skip_count = 0
let mut pls_raw_skip_count = 0
let mut run_raw_skip_count = 0
let mut vacuum_no_candidate_skip_count = 0
let mut vacuum_raw_preclean_skip_count = 0
let mut vacuum_hot_func_count = 0
let mut vacuum_hot_changed_count = 0
let mut vacuum_mutated_func_trace_count = 0
let mut rse_raw_changed_count = 0
let mut rse_raw_no_candidate_count = 0
let mut hot_function_total_us = 0UL
let pass_options = if descriptor.name == "vacuum" {
{
verify_policy: options.verify_policy,
validate_final_module: options.validate_final_module,
closed_world: options.closed_world,
traps_never_happen: options.traps_never_happen,
ignore_implicit_traps: options.ignore_implicit_traps,
zero_filled_memory: options.zero_filled_memory,
fast_math: options.fast_math,
compiler_fact_policy: options.compiler_fact_policy,
optimize_level: options.optimize_level,
shrink_level: options.shrink_level,
inlining_always_inline_max_size: options.inlining_always_inline_max_size,
inlining_one_caller_inline_max_size: options.inlining_one_caller_inline_max_size,
inlining_flexible_inline_max_size: options.inlining_flexible_inline_max_size,
inlining_max_combined_binary_size: options.inlining_max_combined_binary_size,
inlining_allow_functions_with_loops: options.inlining_allow_functions_with_loops,
inlining_partial_inlining_ifs: options.inlining_partial_inlining_ifs,
stack_function_passes: options.stack_function_passes,
trace_enabled: options.trace_enabled,
trace: fn(line) {
if line.contains("pass[vacuum]:mutated count=") {
vacuum_mutated_func_trace_count += 1
} else {
(options.trace)(line)
}
},
perf: options.perf,
dae_default_no_callback_trace: None,
}
} else {
options
}
for code_idx = 0; code_idx < defs.length(); code_idx = code_idx + 1 {
let func_idx = @lib.FuncIdx::new(
(imported_func_count + code_idx).reinterpret_as_uint(),
)
if run_hot_pipeline_pass_should_skip_typeidx_loop_func(descriptor.name) &&
run_hot_pipeline_lowered_func_has_typeidx_loop(defs[code_idx]) &&
!run_hot_pipeline_typeidx_loop_skip_can_try_raw(
descriptor.name,
defs[code_idx],
) {
pipeline_trace_hook(
options.trace,
descriptor.name,
"skip-raw",
payload="reason=typed-loop-param-control func=\{func_idx}",
)
continue
}
if descriptor.name == "dead-code-elimination" {
let @lib.Func(locals, @lib.Expr(instrs)) = defs[code_idx]
if run_hot_pipeline_lowered_func_has_suspicious_escape_carrier(
defs[code_idx],
) &&
!run_hot_pipeline_dce_raw_has_stack_polymorphic_if_before_br(instrs) {
pipeline_trace_hook(
options.trace,
descriptor.name,
"skip-invalid-lower",
payload="func=\{func_idx} reason=suspicious-escape-carrier-original",
)
continue
}
if locals.length() > 1000 {
pipeline_trace_hook(
options.trace,
descriptor.name,
"skip-raw",
payload="reason=large-local-dce-noop func=\{func_idx}",
)
continue
}
}
let lower_state = {
next_type_sec: current_mod.type_sec,
type_sec_changed: false,
}
let func_result_start_us = if descriptor.name ==
"redundant-set-elimination" {
perf_start_timer_opt(options.perf, "detail:rse:func-result")
} else {
0UL
}
let hot_function_start_us = perf_start_timer_opt(
options.perf,
"stage:hot-pass:function-total",
)
let result = match
run_hot_pipeline_func(
current_mod, func_idx, descriptor, module_state, lower_state, pass_options,
) {
Ok(result) => result
Err(err) => return Err("func[\{func_idx}]: \{err}")
}
hot_function_total_us = hot_function_total_us +
perf_elapsed_since_start_opt(options.perf, hot_function_start_us)
if descriptor.name == "redundant-set-elimination" {
perf_stop_timer_accumulate_opt(
options.perf,
"detail:rse:func-result",
func_result_start_us,
)
}
let validation_mod = if lower_state.type_sec_changed {
current_mod.with_type_sec(lower_state.next_type_sec.unwrap())
} else {
current_mod
}
let @lib.FuncIdx(raw_guard_func_idx) = func_idx
let guard_timer_name = "guard:\{descriptor.name}-writeback:func:\{raw_guard_func_idx.reinterpret_as_int()}"
let guard_start_us = if descriptor.name == "vacuum" &&
!defer_this_vacuum_writeback_guard {
perf_start_timer_opt(options.perf, guard_timer_name)
} else {
0UL
}
let rse_guard_start_us = if descriptor.name ==
"redundant-set-elimination" {
perf_start_timer_opt(options.perf, "detail:rse:writeback-guard")
} else {
0UL
}
let skip_invalid_lower_reason = if defer_this_vacuum_writeback_guard {
None
} else if defer_this_ssa_nomerge_writeback_guard {
run_hot_pipeline_ssa_nomerge_nonvalidation_skip_invalid_lower_reason(
result,
)
} else if defer_this_remove_unused_brs_writeback_guard {
None
} else if defer_this_dce_writeback_guard {
run_hot_pipeline_dce_nonvalidation_skip_invalid_lower_reason(result)
} else if defer_this_precompute_writeback_guard {
run_hot_pipeline_touched_nonvalidation_skip_invalid_lower_reason(
descriptor.name,
result,
)
} else if defer_this_simplify_locals_writeback_guard {
run_hot_pipeline_touched_nonvalidation_skip_invalid_lower_reason(
descriptor.name,
result,
)
} else if defer_this_optimize_instructions_writeback_guard {
None
} else {
run_hot_pipeline_skip_invalid_lower_reason(
current_mod,
validation_mod,
code_idx,
descriptor.name,
result,
)
}
if descriptor.name == "redundant-set-elimination" {
perf_stop_timer_accumulate_opt(
options.perf,
"detail:rse:writeback-guard",
rse_guard_start_us,
)
}
if descriptor.name == "vacuum" && !defer_this_vacuum_writeback_guard {
perf_stop_timer_opt(options.perf, guard_timer_name, guard_start_us)
}
if skip_invalid_lower_reason != None {
pipeline_trace_hook(
options.trace,
descriptor.name,
"skip-invalid-lower",
payload="func=\{func_idx} reason=\{skip_invalid_lower_reason.unwrap()}",
)
continue
}
if lower_state.type_sec_changed {
current_mod = validation_mod
// Later functions must see tuple/block types introduced by this
// function's lowering rather than the earlier cached module snapshot.
run_hot_pipeline_invalidate_type_dependent_caches(module_state)
}
if descriptor.name == "ssa-nomerge" &&
result.reason.contains("-mutated") &&
!result.unchanged_original {
pipeline_trace_hook(
options.trace,
descriptor.name,
"mutated",
payload="raw reason=\{result.reason}",
)
}
if descriptor.name == "vacuum" && !result.trace_skip_raw {
vacuum_hot_func_count += 1
if !result.unchanged_original {
vacuum_hot_changed_count += 1
}
}
if defer_this_precompute_writeback_guard && !result.unchanged_original {
precompute_touched.unwrap()[code_idx] = true
}
if defer_this_simplify_locals_writeback_guard &&
!result.unchanged_original {
// Batch validation starts from the original code section and only
// validates these marked slots. Every unchanged_original result must
// therefore retain the exact original Func value.
let touched = match simplify_locals_touched {
Some(touched) => touched
None => {
let touched = Array::make(defs.length(), false)
simplify_locals_touched = Some(touched)
touched
}
}
touched[code_idx] = true
}
let rse_trace_writeback_start_us = if descriptor.name ==
"redundant-set-elimination" {
perf_start_timer_opt(options.perf, "detail:rse:trace-writeback")
} else {
0UL
}
if result.trace_skip_raw {
if descriptor.name == "heap-store-optimization" &&
result.reason == "no-heap-store-candidates" {
hso_raw_skip_count += 1
} else if descriptor.name == "heap2local" &&
result.reason == "no-heap2local-candidates" {
h2l_raw_skip_count += 1
} else if descriptor.name == "pick-load-signs" &&
result.reason == "no-pick-load-signs-candidates" {
pls_raw_skip_count += 1
} else if descriptor.name == "remove-unused-names" &&
result.reason == "no-remove-unused-names-candidates" {
run_raw_skip_count += 1
} else if descriptor.name == "vacuum" &&
result.reason == "no-vacuum-candidates" {
vacuum_no_candidate_skip_count += 1
} else if descriptor.name == "vacuum" &&
result.reason == "raw-vacuum-preclean" {
vacuum_raw_preclean_skip_count += 1
} else if descriptor.name == "redundant-set-elimination" &&
result.reason == "raw-same-value-local-write" {
rse_raw_changed_count += 1
} else if descriptor.name == "redundant-set-elimination" &&
result.reason == "no-rse-candidates" {
rse_raw_no_candidate_count += 1
} else {
pipeline_trace_hook(
options.trace,
descriptor.name,
"skip-raw",
payload=run_hot_pipeline_threshold_trace_payload(
descriptor.name,
result.reason,
),
)
}
}
match lowered {
Some(defs_copy) =>
if !defer_this_precompute_writeback_guard ||
!result.unchanged_original {
// The copy starts from the original code section, so leaving an
// unchanged result in place makes `precompute_touched` complete
// by construction: only marked slots can differ at batch time.
defs_copy[code_idx] = result.func
}
None =>
if !result.unchanged_original {
let defs_copy = defs.copy()
defs_copy[code_idx] = result.func
lowered = Some(defs_copy)
}
}
if descriptor.name == "redundant-set-elimination" {
perf_stop_timer_accumulate_opt(
options.perf,
"detail:rse:trace-writeback",
rse_trace_writeback_start_us,
)
}
}
perf_record_timer_elapsed_opt(
options.perf,
"stage:hot-pass:function-total",
hot_function_total_us,
)
perf_emit_timer_total_opt(options.perf, "stage:hot-pass:pre-pass")
perf_emit_timer_total_opt(options.perf, "stage:hot-pass:post-pass")
if descriptor.name == "redundant-set-elimination" {
perf_emit_timer_total_opt(options.perf, "detail:rse:setup")
perf_emit_timer_total_opt(options.perf, "detail:rse:raw-func")
perf_emit_timer_total_opt(options.perf, "detail:rse:func-result")
perf_emit_timer_total_opt(options.perf, "detail:rse:writeback-guard")
perf_emit_timer_total_opt(options.perf, "detail:rse:trace-writeback")
}
if hso_raw_skip_count > 0 {
pipeline_trace_hook(
options.trace,
descriptor.name,
"skip-raw",
payload="reason=no-heap-store-candidates count=\{hso_raw_skip_count}",
)
}
if h2l_raw_skip_count > 0 {
pipeline_trace_hook(
options.trace,
descriptor.name,
"skip-raw",
payload="reason=no-heap2local-candidates count=\{h2l_raw_skip_count}",
)
}
if pls_raw_skip_count > 0 {
pipeline_trace_hook(
options.trace,
descriptor.name,
"skip-raw",
payload="reason=no-pick-load-signs-candidates count=\{pls_raw_skip_count}",
)
}
if run_raw_skip_count > 0 {
pipeline_trace_hook(
options.trace,
descriptor.name,
"skip-raw",
payload="reason=no-remove-unused-names-candidates count=\{run_raw_skip_count}",
)
}
if vacuum_no_candidate_skip_count > 0 {
pipeline_trace_hook(
options.trace,
descriptor.name,
"skip-raw",
payload="reason=no-vacuum-candidates count=\{vacuum_no_candidate_skip_count}",
)
}
if vacuum_raw_preclean_skip_count > 0 {
pipeline_trace_hook(
options.trace,
descriptor.name,
"skip-raw",
payload="reason=raw-vacuum-preclean count=\{vacuum_raw_preclean_skip_count}",
)
}
if rse_raw_changed_count > 0 {
pipeline_trace_hook(
options.trace,
descriptor.name,
"skip-raw",
payload="reason=raw-same-value-local-write count=\{rse_raw_changed_count}",
)
}
if rse_raw_no_candidate_count > 0 {
pipeline_trace_hook(
options.trace,
descriptor.name,
"skip-raw",
payload="reason=no-rse-candidates count=\{rse_raw_no_candidate_count}",
)
}
if vacuum_mutated_func_trace_count > 0 {
pipeline_trace_hook(
options.trace,
descriptor.name,
"mutated",
payload="funcs=\{vacuum_mutated_func_trace_count}",
)
}
if vacuum_hot_func_count > 0 {
pipeline_trace_hook(
options.trace,
descriptor.name,
"funcs",
payload="count=\{vacuum_hot_func_count} changed=\{vacuum_hot_changed_count}",
)
}
if descriptor.name == "vacuum" {
run_hot_pipeline_emit_vacuum_aggregate_timers(options, module_state)
}
let rse_code_sec_start_us = if descriptor.name ==
"redundant-set-elimination" {
perf_start_timer_opt(options.perf, "detail:rse:code-sec")
} else {
0UL
}
let built_code_sec = match lowered {
Some(lowered) => Some(@lib.CodeSec::new(lowered))
None => None
}
if descriptor.name == "redundant-set-elimination" {
perf_stop_timer_opt(
options.perf,
"detail:rse:code-sec",
rse_code_sec_start_us,
)
}
built_code_sec
}
None => None
}
perf_stop_timer_opt(
options.perf,
"stage:hot-pass:code-section",
code_section_start_us,
)
let module_rebuild_start_us = perf_start_timer_opt(
options.perf,
"stage:hot-pass:module-rebuild",
)
let rse_module_copy_start_us = if descriptor.name ==
"redundant-set-elimination" {
perf_start_timer_opt(options.perf, "detail:rse:module-copy")
} else {
0UL
}
let next_mod = match next_code_sec {
Some(code_sec) => {
let next_mod = run_hot_pipeline_drop_label_names(
current_mod.with_code_sec(code_sec),
)
if descriptor.name == "ssa-nomerge" {
dfe_prune_unused_simple_func_type_suffix(
dfe_canonicalize_duplicate_simple_type_indices(
run_hot_pipeline_raw_group_body_locals_by_type_module(
run_hot_pipeline_drop_local_names(next_mod),
),
),
)
} else {
next_mod
}
}
None => current_mod
}
let next_mod = if descriptor.name == "redundant-set-elimination" {
rse_canonicalize_plain_signatures(next_mod)
} else {
next_mod
}
let next_mod = if descriptor.name == "heap2local" {
heap2local_cleanup_module_types(next_mod)
} else {
next_mod
}
if descriptor.name == "redundant-set-elimination" {
perf_stop_timer_opt(
options.perf,
"detail:rse:module-copy",
rse_module_copy_start_us,
)
}
perf_stop_timer_opt(
options.perf,
"stage:hot-pass:module-rebuild",
module_rebuild_start_us,
)
if defer_this_precompute_writeback_guard && next_code_sec is Some(_) {
let batch_guard_start_us = perf_start_timer_opt(
options.perf,
"guard:\{descriptor.name}-writeback-batch",
)
match
run_hot_pipeline_precompute_repair_batch_writeback(
mod_,
next_mod,
precompute_touched.unwrap(),
descriptor.name,
options,
) {
Some(repaired_mod) => {
perf_stop_timer_opt(
options.perf,
"guard:\{descriptor.name}-writeback-batch",
batch_guard_start_us,
)
return Ok(
precompute_materialize_vectors_for_size(
precompute_cleanup_unused_gc_types(mod_, repaired_mod),
options.shrink_level,
),
)
}
None => {
perf_stop_timer_opt(
options.perf,
"guard:\{descriptor.name}-writeback-batch",
batch_guard_start_us,
)
return run_hot_pipeline_apply_hot_pass(
mod_,
entry,
options,
defer_precompute_writeback_guard=false,
)
}
}
}
if defer_this_optimize_instructions_writeback_guard &&
next_code_sec is Some(_) {
let batch_guard_start_us = perf_start_timer_opt(
options.perf,
"guard:optimize-instructions-writeback-batch",
)
match
run_hot_pipeline_optimize_instructions_repair_batch_writeback(
mod_, next_mod, options,
) {
Some(repaired_mod) => {
perf_stop_timer_opt(
options.perf,
"guard:optimize-instructions-writeback-batch",
batch_guard_start_us,
)
return Ok(oi_cleanup_module_encoding(repaired_mod))
}
None => {
perf_stop_timer_opt(
options.perf,
"guard:optimize-instructions-writeback-batch",
batch_guard_start_us,
)
return run_hot_pipeline_apply_hot_pass(
mod_,
entry,
options,
defer_optimize_instructions_writeback_guard=false,
)
}
}
}
if defer_this_simplify_locals_writeback_guard && next_code_sec is Some(_) {
let batch_guard_start_us = perf_start_timer_opt(
options.perf,
"guard:\{descriptor.name}-writeback-batch",
)
match
run_hot_pipeline_simplify_locals_repair_batch_writeback(
mod_,
next_mod,
simplify_locals_touched.unwrap(),
descriptor.name,
options,
) {
Some(repaired_mod) => {
perf_stop_timer_opt(
options.perf,
"guard:\{descriptor.name}-writeback-batch",
batch_guard_start_us,
)
return Ok(
if run_hot_pipeline_descriptor_is_simplify_locals_family(
descriptor.name,
) {
simplify_locals_cleanup_changed_module_encoding(
mod_,
repaired_mod,
allow_grouping=simplify_locals_grouping_descriptor_is_admitted(
descriptor.name,
),
)
} else {
repaired_mod
},
)
}
None => {
perf_stop_timer_opt(
options.perf,
"guard:\{descriptor.name}-writeback-batch",
batch_guard_start_us,
)
return run_hot_pipeline_apply_hot_pass(
mod_,
entry,
options,
defer_simplify_locals_writeback_guard=false,
)
}
}
}
if defer_this_vacuum_writeback_guard && next_code_sec is Some(_) {
let batch_guard_start_us = perf_start_timer_opt(
options.perf,
"guard:vacuum-writeback-batch",
)
match
run_hot_pipeline_vacuum_repair_batch_writeback(mod_, next_mod, options) {
Some(repaired_mod) => {
perf_stop_timer_opt(
options.perf,
"guard:vacuum-writeback-batch",
batch_guard_start_us,
)
return Ok(
vacuum_cleanup_unused_declarations_and_gc_types(mod_, repaired_mod),
)
}
None => {
perf_stop_timer_opt(
options.perf,
"guard:vacuum-writeback-batch",
batch_guard_start_us,
)
return run_hot_pipeline_apply_hot_pass(
mod_,
entry,
options,
defer_vacuum_writeback_guard=false,
)
}
}
}
if defer_this_dce_writeback_guard && next_code_sec is Some(_) {
let batch_guard_start_us = perf_start_timer_opt(
options.perf,
"guard:dead-code-elimination-writeback-batch",
)
match run_hot_pipeline_dce_repair_batch_writeback(mod_, next_mod, options) {
Some(repaired_mod) => {
perf_stop_timer_opt(
options.perf,
"guard:dead-code-elimination-writeback-batch",
batch_guard_start_us,
)
return Ok(repaired_mod)
}
None => {
perf_stop_timer_opt(
options.perf,
"guard:dead-code-elimination-writeback-batch",
batch_guard_start_us,
)
return run_hot_pipeline_apply_hot_pass(
mod_,
entry,
options,
defer_dce_writeback_guard=false,
)
}
}
}
if defer_this_remove_unused_brs_writeback_guard && next_code_sec is Some(_) {
let batch_guard_start_us = perf_start_timer_opt(
options.perf,
"guard:remove-unused-brs-writeback-batch",
)
match
run_hot_pipeline_remove_unused_brs_repair_batch_writeback(
mod_, next_mod, options,
) {
Some(repaired_mod) => {
perf_stop_timer_opt(
options.perf,
"guard:remove-unused-brs-writeback-batch",
batch_guard_start_us,
)
return Ok(repaired_mod)
}
None => {
perf_stop_timer_opt(
options.perf,
"guard:remove-unused-brs-writeback-batch",
batch_guard_start_us,
)
return run_hot_pipeline_apply_hot_pass(
mod_,
entry,
options,
defer_remove_unused_brs_writeback_guard=false,
)
}
}
}
if defer_this_ssa_nomerge_writeback_guard && next_code_sec is Some(_) {
let batch_guard_start_us = perf_start_timer_opt(
options.perf,
"guard:ssa-writeback-batch",
)
match
run_hot_pipeline_ssa_repair_batch_writeback(
descriptor.name,
mod_,
next_mod,
options,
) {
Some(repaired_mod) => {
perf_stop_timer_opt(
options.perf,
"guard:ssa-writeback-batch",
batch_guard_start_us,
)
match @validate.validate_module(repaired_mod) {
Ok(_) => return Ok(repaired_mod)
Err(err) => {
pipeline_trace_hook(
options.trace,
descriptor.name,
"writeback-batch-module-invalid",
payload="reason=\{err}",
)
return Ok(mod_)
}
}
}
None => {
perf_stop_timer_opt(
options.perf,
"guard:ssa-writeback-batch",
batch_guard_start_us,
)
return run_hot_pipeline_apply_hot_pass(
mod_,
entry,
options,
defer_ssa_nomerge_writeback_guard=false,
)
}
}
}
Ok(
if descriptor.name == "vacuum" {
vacuum_cleanup_unused_declarations_and_gc_types(mod_, next_mod)
} else if descriptor.name == "precompute" ||
descriptor.name == "precompute-propagate" {
precompute_materialize_vectors_for_size(
precompute_cleanup_unused_gc_types(mod_, next_mod),
options.shrink_level,
)
} else if descriptor.name == "optimize-instructions" {
oi_cleanup_module_encoding(next_mod)
} else if run_hot_pipeline_descriptor_is_simplify_locals_family(
descriptor.name,
) {
simplify_locals_cleanup_changed_module_encoding(
mod_,
next_mod,
allow_grouping=simplify_locals_grouping_descriptor_is_admitted(
descriptor.name,
),
)
} else if descriptor.name == "merge-blocks" {
pass_cleanup_control_and_type_encoding(next_mod)
} else {
next_mod
},
)
}
///|
fn run_hot_pipeline_apply_hot_pass_stack(
mod_ : @lib.Module,
entries : Array[HotPassRegistryEntry],
options : HotPipelineOptions,
) -> Result[@lib.Module, String] {
if pass_module_has_offset_sensitive_metadata(mod_) {
for entry in entries {
pipeline_trace_hook(
options.trace,
entry.name,
"fallback-noop",
payload="reason=offset-sensitive-metadata",
)
}
return Ok(mod_)
}
if entries.length() == 0 {
return Ok(mod_)
}
let descriptors : Array[@ir.HotPassDescriptor] = []
for entry in entries {
match entry.descriptor {
Some(descriptor) =>
if descriptor.name == "pick-load-signs" &&
!run_hot_pipeline_module_has_memory(mod_) {
pipeline_trace_hook(options.trace, descriptor.name, "skip-no-memory")
} else if (
descriptor.name == "simplify-locals" ||
descriptor.name == "merge-blocks"
) &&
o4z_size_candidate_has_unsafe_emscripten_main_runtime(mod_) {
pipeline_trace_hook(
options.trace,
descriptor.name,
"fallback-noop",
payload="reason=emscripten-main-stateful-runtime-\{descriptor.name}-noop",
)
} else {
descriptors.push(descriptor)
}
None => return Err("missing hot pass descriptor for \{entry.name}")
}
}
if descriptors.length() == 0 {
return Ok(mod_)
}
let module_state = HotPipelineModuleState::new()
let mut current_mod = mod_
let imported_func_count = run_hot_pipeline_imported_func_count(mod_)
let next_code_sec = match mod_.code_sec {
Some(@lib.CodeSec(defs)) => {
let mut lowered : Array[@lib.Func]? = None
for code_idx = 0; code_idx < defs.length(); code_idx = code_idx + 1 {
let func_idx = @lib.FuncIdx::new(
(imported_func_count + code_idx).reinterpret_as_uint(),
)
let module_ctx = run_hot_pipeline_require_module_ctx(
current_mod, module_state, options,
)
let lift_start_us = perf_start_timer_opt(options.perf, "lift")
let func = match
@ir.hot_lift_func_with_context_result(
current_mod,
func_idx,
module_ctx,
capture_stack_locals=descriptors.any(fn(d) {
d.name == "constraint-analysis"
}),
) {
Ok(func) => func
Err(err) => {
pipeline_trace_hook(
options.trace,
"stacked-hot",
"lift-error",
payload="func_idx=\{func_idx} err=\{err}",
)
if options.validate_final_module {
return Err("func[\{func_idx}]: lift: \{err}")
}
continue
}
}
perf_stop_timer_opt(options.perf, "lift", lift_start_us)
let lifted_revision = @ir.hot_revision_current(func)
let cache = @ir.HotAnalysisCache::new()
for descriptor in descriptors {
pipeline_trace_hook(
options.trace,
descriptor.name,
"func",
payload="func_idx=\{func_idx}",
)
let ctx = HotPassContext::new(
descriptor,
cache,
options.verify_policy,
options.closed_world,
options.trace,
traps_never_happen=options.traps_never_happen,
ignore_implicit_traps=options.ignore_implicit_traps,
fast_math=options.fast_math,
perf=options.perf,
module_ctx=Some(module_ctx),
optimize_level=options.optimize_level,
shrink_level=options.shrink_level,
)
if descriptor.name == "optimize-instructions" {
ctx.allows_simd_introduction = run_hot_pipeline_allows_simd_introduction(
current_mod, module_state,
)
}
match hot_pass_run(ctx, func) {
Ok(_) => ()
Err(err) => return Err("func[\{func_idx}]: \{err}")
}
}
let cleanup_module_env = if descriptors.any(fn(d) {
d.name == "constraint-analysis" ||
d.name == "precompute" ||
d.name == "precompute-propagate"
}) {
Some(
run_hot_pipeline_require_validation_module_env(
current_mod, module_state, options,
),
)
} else {
None
}
if @ir.hot_revision_current(func) == lifted_revision {
if descriptors.any(fn(d) {
d.name == "constraint-analysis" ||
d.name == "precompute" ||
d.name == "precompute-propagate"
}) {
let (output, changed) = if descriptors.any(fn(d) {
d.name == "constraint-analysis"
}) {
ca_lower_cleanup_raw(
defs[code_idx],
current_mod,
module_env=cleanup_module_env,
)
} else {
pass_lower_cleanup_branchless_blocks(
defs[code_idx],
current_mod,
module_env=cleanup_module_env,
flatten_scalar_identity_blocks=true,
)
}
if changed {
match lowered {
Some(defs_copy) => defs_copy[code_idx] = output
None => {
let defs_copy = defs.copy()
defs_copy[code_idx] = output
lowered = Some(defs_copy)
}
}
}
}
continue
}
let lower_state = {
next_type_sec: current_mod.type_sec,
type_sec_changed: false,
}
run_hot_pipeline_prepare_tuple_make_lowering(func, lower_state)
let lower_start_us = perf_start_timer_opt(options.perf, "lower")
let lowered_func = match options.verify_policy {
HotPipelineVerifyPolicy::AfterEachPass =>
run_hot_pipeline_canonicalize_lowered_func_for_descriptor(
@ir.hot_lower_func_with_options(
func,
run_hot_pipeline_lower_options_for_descriptor(
descriptors[descriptors.length() - 1].name,
),
),
descriptors[descriptors.length() - 1].name,
Some(run_hot_pipeline_original_func(mod_, func_idx)),
func_idx,
current_mod,
module_env_provider=Some(fn() {
run_hot_pipeline_require_validation_module_env(
current_mod, module_state, options,
)
}),
module_env=cleanup_module_env,
)
HotPipelineVerifyPolicy::FinalModuleOnly =>
run_hot_pipeline_canonicalize_lowered_func_for_descriptor(
@ir.hot_lower_func_unverified_with_options(
func,
run_hot_pipeline_lower_options_for_descriptor(
descriptors[descriptors.length() - 1].name,
),
),
descriptors[descriptors.length() - 1].name,
Some(run_hot_pipeline_original_func(mod_, func_idx)),
func_idx,
current_mod,
module_env_provider=Some(fn() {
run_hot_pipeline_require_validation_module_env(
current_mod, module_state, options,
)
}),
module_env=cleanup_module_env,
)
}
let lowered_func = if descriptors.any(fn(d) {
d.name == "constraint-analysis"
}) {
ca_lower_cleanup_raw(
lowered_func,
current_mod,
module_env=cleanup_module_env,
).0
} else if descriptors.any(fn(d) {
d.name == "precompute" || d.name == "precompute-propagate"
}) {
pass_lower_cleanup_branchless_blocks(
lowered_func,
current_mod,
module_env=cleanup_module_env,
flatten_scalar_identity_blocks=true,
).0
} else {
lowered_func
}
perf_stop_timer_opt(options.perf, "lower", lower_start_us)
if lower_state.type_sec_changed {
current_mod = current_mod.with_type_sec(
lower_state.next_type_sec.unwrap(),
)
// Both caches belong to the previous type snapshot.
run_hot_pipeline_invalidate_type_dependent_caches(module_state)
}
match lowered {
Some(defs_copy) => defs_copy[code_idx] = lowered_func
None => {
let defs_copy = defs.copy()
defs_copy[code_idx] = lowered_func
lowered = Some(defs_copy)
}
}
}
match lowered {
Some(lowered) => Some(@lib.CodeSec::new(lowered))
None => None
}
}
None => None
}
let output = match next_code_sec {
Some(code_sec) =>
run_hot_pipeline_drop_label_names(current_mod.with_code_sec(code_sec))
None => current_mod
}
let output = if descriptors.any(fn(d) { d.name == "vacuum" }) {
vacuum_cleanup_unused_declarations_and_gc_types(mod_, output)
} else {
output
}
let output = precompute_materialize_stack_vectors_for_size(
output,
descriptors,
options.shrink_level,
)
Ok(
if descriptors.any(fn(d) { d.name == "optimize-instructions" }) {
oi_cleanup_module_encoding(output)
} else if descriptors.any(fn(d) {
run_hot_pipeline_descriptor_is_simplify_locals_family(d.name)
}) {
simplify_locals_cleanup_changed_module_encoding(
mod_,
output,
allow_grouping=descriptors.any(fn(d) {
simplify_locals_grouping_descriptor_is_admitted(d.name)
}),
)
} else if descriptors.any(fn(d) { d.name == "merge-blocks" }) {
pass_cleanup_control_and_type_encoding(output)
} else {
output
},
)
}
///|
#warnings("-unused_value")
fn run_hot_pipeline_apply_hot_pass_stack_to_touched(
mod_ : @lib.Module,
requested_descriptors : Array[@ir.HotPassDescriptor],
touched : Array[Bool],
options : HotPipelineOptions,
) -> Result[@lib.Module, String] {
if pass_module_has_offset_sensitive_metadata(mod_) {
pipeline_trace_hook(
options.trace,
"filtered-hot-stack",
"fallback-noop",
payload="reason=offset-sensitive-metadata",
)
return Ok(mod_)
}
if requested_descriptors.length() == 0 {
return Ok(mod_)
}
let descriptors : Array[@ir.HotPassDescriptor] = []
for descriptor in requested_descriptors {
if descriptor.name == "pick-load-signs" &&
!run_hot_pipeline_module_has_memory(mod_) {
pipeline_trace_hook(options.trace, descriptor.name, "skip-no-memory")
} else {
descriptors.push(descriptor)
}
}
if descriptors.length() == 0 {
return Ok(mod_)
}
let module_state = HotPipelineModuleState::new()
let mut current_mod = mod_
let imported_func_count = run_hot_pipeline_imported_func_count(mod_)
let next_code_sec = match mod_.code_sec {
Some(@lib.CodeSec(defs)) => {
let mut lowered : Array[@lib.Func]? = None
for code_idx = 0; code_idx < defs.length(); code_idx = code_idx + 1 {
if code_idx >= touched.length() || !touched[code_idx] {
continue
}
let func_idx = @lib.FuncIdx::new(
(imported_func_count + code_idx).reinterpret_as_uint(),
)
let module_ctx = run_hot_pipeline_require_module_ctx(
current_mod, module_state, options,
)
let lift_start_us = perf_start_timer_opt(options.perf, "lift")
let func = match
@ir.hot_lift_func_with_context_result(
current_mod,
func_idx,
module_ctx,
capture_stack_locals=descriptors.any(fn(d) {
d.name == "constraint-analysis"
}),
) {
Ok(func) => func
Err(err) => {
pipeline_trace_hook(
options.trace,
"filtered-hot-stack",
"lift-error",
payload="func_idx=\{func_idx} err=\{err}",
)
if options.validate_final_module {
return Err("func[\{func_idx}]: lift: \{err}")
}
continue
}
}
perf_stop_timer_opt(options.perf, "lift", lift_start_us)
let lifted_revision = @ir.hot_revision_current(func)
let cache = @ir.HotAnalysisCache::new()
let mut last_descriptor : @ir.HotPassDescriptor? = None
for descriptor in descriptors {
if run_hot_pipeline_pass_should_skip_typeidx_loop_func(
descriptor.name,
) &&
run_hot_pipeline_lowered_func_has_typeidx_loop(defs[code_idx]) &&
!run_hot_pipeline_typeidx_loop_skip_can_try_raw(
descriptor.name,
defs[code_idx],
) {
pipeline_trace_hook(
options.trace,
descriptor.name,
"skip-raw",
payload="reason=typed-loop-param-control func=\{func_idx}",
)
continue
}
if descriptor.name == "dead-code-elimination" {
let @lib.Func(locals, @lib.Expr(instrs)) = defs[code_idx]
if run_hot_pipeline_lowered_func_has_suspicious_escape_carrier(
defs[code_idx],
) &&
!run_hot_pipeline_dce_raw_has_stack_polymorphic_if_before_br(
instrs,
) {
pipeline_trace_hook(
options.trace,
descriptor.name,
"skip-invalid-lower",
payload="func=\{func_idx} reason=suspicious-escape-carrier-original",
)
continue
}
if locals.length() > 1000 {
pipeline_trace_hook(
options.trace,
descriptor.name,
"skip-raw",
payload="reason=large-local-dce-noop func=\{func_idx}",
)
continue
}
}
pipeline_trace_hook(
options.trace,
descriptor.name,
"func",
payload="func_idx=\{func_idx}",
)
let ctx = HotPassContext::new(
descriptor,
cache,
options.verify_policy,
options.closed_world,
options.trace,
traps_never_happen=options.traps_never_happen,
ignore_implicit_traps=options.ignore_implicit_traps,
fast_math=options.fast_math,
perf=options.perf,
module_ctx=Some(module_ctx),
optimize_level=options.optimize_level,
shrink_level=options.shrink_level,
)
if descriptor.name == "optimize-instructions" {
ctx.allows_simd_introduction = run_hot_pipeline_allows_simd_introduction(
current_mod, module_state,
)
}
match hot_pass_run(ctx, func) {
Ok(_) => last_descriptor = Some(descriptor)
Err(err) => return Err("func[\{func_idx}]: \{err}")
}
}
let cleanup_module_env = if descriptors.any(fn(d) {
d.name == "constraint-analysis" ||
d.name == "precompute" ||
d.name == "precompute-propagate"
}) {
Some(
run_hot_pipeline_require_validation_module_env(
current_mod, module_state, options,
),
)
} else {
None
}
if @ir.hot_revision_current(func) == lifted_revision {
if descriptors.any(fn(d) {
d.name == "constraint-analysis" ||
d.name == "precompute" ||
d.name == "precompute-propagate"
}) {
let (output, changed) = if descriptors.any(fn(d) {
d.name == "constraint-analysis"
}) {
ca_lower_cleanup_raw(
defs[code_idx],
current_mod,
module_env=cleanup_module_env,
)
} else {
pass_lower_cleanup_branchless_blocks(
defs[code_idx],
current_mod,
module_env=cleanup_module_env,
flatten_scalar_identity_blocks=true,
)
}
if changed {
match lowered {
Some(defs_copy) => defs_copy[code_idx] = output
None => {
let defs_copy = defs.copy()
defs_copy[code_idx] = output
lowered = Some(defs_copy)
}
}
}
}
continue
}
let final_descriptor = match last_descriptor {
Some(descriptor) => descriptor
None => continue
}
let lower_state = {
next_type_sec: current_mod.type_sec,
type_sec_changed: false,
}
run_hot_pipeline_prepare_tuple_make_lowering(func, lower_state)
let lower_start_us = perf_start_timer_opt(options.perf, "lower")
let lowered_func = match options.verify_policy {
HotPipelineVerifyPolicy::AfterEachPass =>
run_hot_pipeline_canonicalize_lowered_func_for_descriptor(
@ir.hot_lower_func_with_options(
func,
run_hot_pipeline_lower_options_for_descriptor(
final_descriptor.name,
),
),
final_descriptor.name,
Some(run_hot_pipeline_original_func(mod_, func_idx)),
func_idx,
current_mod,
module_env_provider=Some(fn() {
run_hot_pipeline_require_validation_module_env(
current_mod, module_state, options,
)
}),
module_env=cleanup_module_env,
)
HotPipelineVerifyPolicy::FinalModuleOnly =>
run_hot_pipeline_canonicalize_lowered_func_for_descriptor(
@ir.hot_lower_func_unverified_with_options(
func,
run_hot_pipeline_lower_options_for_descriptor(
final_descriptor.name,
),
),
final_descriptor.name,
Some(run_hot_pipeline_original_func(mod_, func_idx)),
func_idx,
current_mod,
module_env_provider=Some(fn() {
run_hot_pipeline_require_validation_module_env(
current_mod, module_state, options,
)
}),
module_env=cleanup_module_env,
)
}
let lowered_func = if descriptors.any(fn(d) {
d.name == "constraint-analysis"
}) {
ca_lower_cleanup_raw(
lowered_func,
current_mod,
module_env=cleanup_module_env,
).0
} else if descriptors.any(fn(d) {
d.name == "precompute" || d.name == "precompute-propagate"
}) {
pass_lower_cleanup_branchless_blocks(
lowered_func,
current_mod,
module_env=cleanup_module_env,
flatten_scalar_identity_blocks=true,
).0
} else {
lowered_func
}
perf_stop_timer_opt(options.perf, "lower", lower_start_us)
if lower_state.type_sec_changed {
current_mod = current_mod.with_type_sec(
lower_state.next_type_sec.unwrap(),
)
// Both caches belong to the previous type snapshot.
run_hot_pipeline_invalidate_type_dependent_caches(module_state)
}
match lowered {
Some(defs_copy) => defs_copy[code_idx] = lowered_func
None => {
let defs_copy = defs.copy()
defs_copy[code_idx] = lowered_func
lowered = Some(defs_copy)
}
}
}
match lowered {
Some(lowered) => Some(@lib.CodeSec::new(lowered))
None => None
}
}
None => None
}
let candidate = match next_code_sec {
Some(code_sec) =>
run_hot_pipeline_drop_label_names(current_mod.with_code_sec(code_sec))
None => current_mod
}
if next_code_sec is None {
let candidate = precompute_materialize_stack_vectors_for_size(
candidate,
descriptors,
options.shrink_level,
touched=Some(touched),
)
return Ok(
if descriptors.any(fn(d) { d.name == "optimize-instructions" }) {
oi_cleanup_module_encoding(candidate, touched=Some(touched))
} else if descriptors.any(fn(d) {
run_hot_pipeline_descriptor_is_simplify_locals_family(d.name)
}) {
simplify_locals_cleanup_changed_module_encoding(
mod_,
candidate,
touched=Some(touched),
allow_grouping=descriptors.any(fn(d) {
simplify_locals_grouping_descriptor_is_admitted(d.name)
}),
)
} else {
candidate
},
)
}
match
run_hot_pipeline_repair_touched_batch_writeback(
mod_, candidate, touched, "filtered-hot-stack", options,
) {
Some(repaired) => {
let repaired = precompute_materialize_stack_vectors_for_size(
repaired,
descriptors,
options.shrink_level,
touched=Some(touched),
)
Ok(
if descriptors.any(fn(d) { d.name == "optimize-instructions" }) {
oi_cleanup_module_encoding(repaired, touched=Some(touched))
} else if descriptors.any(fn(d) {
run_hot_pipeline_descriptor_is_simplify_locals_family(d.name)
}) {
simplify_locals_cleanup_changed_module_encoding(
mod_,
repaired,
touched=Some(touched),
allow_grouping=descriptors.any(fn(d) {
simplify_locals_grouping_descriptor_is_admitted(d.name)
}),
)
} else {
repaired
},
)
}
None => {
pipeline_trace_hook(
options.trace,
"filtered-hot-stack",
"writeback-batch-rollback",
payload="reason=batch-validation-failed",
)
Ok(mod_)
}
}
}
///|
fn run_hot_pipeline_dae_nested_cleanup_passes(
mod_ : @lib.Module,
options : HotPipelineOptions,
) -> Array[String] {
// Binaryen v131's optimizeAfterInlining lane prepends one
// precompute-propagate pass, then runs the default function pipeline with the
// parent runner's active levels and module features.
let passes = ["precompute-propagate"]
passes.append(
default_function_optimization_passes_with_features(
options.optimize_level,
options.shrink_level,
hot_pipeline_module_feature_facts(mod_),
),
)
passes
}
///|
fn run_hot_pipeline_dae_nested_cleanup_descriptor(
pass_name : String,
) -> Result[@ir.HotPassDescriptor, String] {
match pass_registry_lookup_cached(pass_name) {
Some(entry) =>
match entry.descriptor {
Some(descriptor) => Ok(descriptor)
None => Err("DAE nested cleanup pass \{pass_name} is not a hot pass")
}
None => Err("DAE nested cleanup expanded to unknown pass \{pass_name}")
}
}
///|
fn run_hot_pipeline_apply_hot_pass_to_touched(
mod_ : @lib.Module,
descriptor : @ir.HotPassDescriptor,
touched : Array[Bool],
options : HotPipelineOptions,
) -> Result[@lib.Module, String] {
if pass_module_has_offset_sensitive_metadata(mod_) {
pipeline_trace_hook(
options.trace,
descriptor.name,
"fallback-noop",
payload="reason=offset-sensitive-metadata",
)
return Ok(mod_)
}
if descriptor.name == "pick-load-signs" &&
!run_hot_pipeline_module_has_memory(mod_) {
pipeline_trace_hook(options.trace, descriptor.name, "skip-no-memory")
return Ok(mod_)
}
if (descriptor.name == "simplify-locals" || descriptor.name == "merge-blocks") &&
o4z_size_candidate_has_unsafe_emscripten_main_runtime(mod_) {
pipeline_trace_hook(
options.trace,
descriptor.name,
"fallback-noop",
payload="reason=emscripten-main-stateful-runtime-\{descriptor.name}-noop",
)
return Ok(mod_)
}
if (descriptor.name == "flatten" || descriptor.name == "merge-locals") &&
dae_defined_func_count(mod_) >= 32 &&
run_hot_pipeline_module_has_typeidx_loop(mod_) {
pipeline_trace_hook(
options.trace,
descriptor.name,
"fallback-noop",
payload="reason=large-typed-loop-module-interference",
)
return Ok(mod_)
}
let module_state = HotPipelineModuleState::new()
let mut current_mod = mod_
let imported_func_count = run_hot_pipeline_imported_func_count(mod_)
let next_code_sec = match mod_.code_sec {
Some(@lib.CodeSec(defs)) => {
let mut lowered : Array[@lib.Func]? = None
let mut hso_raw_skip_count = 0
let mut pls_raw_skip_count = 0
let mut run_raw_skip_count = 0
let mut vacuum_no_candidate_skip_count = 0
let mut vacuum_raw_preclean_skip_count = 0
let mut vacuum_hot_func_count = 0
let mut vacuum_hot_changed_count = 0
for code_idx = 0; code_idx < defs.length(); code_idx = code_idx + 1 {
if code_idx >= touched.length() || !touched[code_idx] {
continue
}
let func_idx = @lib.FuncIdx::new(
(imported_func_count + code_idx).reinterpret_as_uint(),
)
if run_hot_pipeline_pass_should_skip_typeidx_loop_func(descriptor.name) &&
run_hot_pipeline_lowered_func_has_typeidx_loop(defs[code_idx]) &&
!run_hot_pipeline_typeidx_loop_skip_can_try_raw(
descriptor.name,
defs[code_idx],
) {
pipeline_trace_hook(
options.trace,
descriptor.name,
"skip-raw",
payload="reason=typed-loop-param-control func=\{func_idx}",
)
continue
}
if descriptor.name == "dead-code-elimination" {
let @lib.Func(locals, @lib.Expr(instrs)) = defs[code_idx]
if run_hot_pipeline_lowered_func_has_suspicious_escape_carrier(
defs[code_idx],
) &&
!run_hot_pipeline_dce_raw_has_stack_polymorphic_if_before_br(instrs) {
pipeline_trace_hook(
options.trace,
descriptor.name,
"skip-invalid-lower",
payload="func=\{func_idx} reason=suspicious-escape-carrier-original",
)
continue
}
if locals.length() > 1000 {
pipeline_trace_hook(
options.trace,
descriptor.name,
"skip-raw",
payload="reason=large-local-dce-noop func=\{func_idx}",
)
continue
}
}
let lower_state = {
next_type_sec: current_mod.type_sec,
type_sec_changed: false,
}
let result = match
run_hot_pipeline_func(
current_mod, func_idx, descriptor, module_state, lower_state, options,
) {
Ok(result) => result
Err(err) => return Err("func[\{func_idx}]: \{err}")
}
let validation_mod = if lower_state.type_sec_changed {
current_mod.with_type_sec(lower_state.next_type_sec.unwrap())
} else {
current_mod
}
let skip_invalid_lower_reason = run_hot_pipeline_touched_nonvalidation_skip_invalid_lower_reason(
descriptor.name,
result,
)
if skip_invalid_lower_reason != None {
pipeline_trace_hook(
options.trace,
descriptor.name,
"skip-invalid-lower",
payload="func=\{func_idx} reason=\{skip_invalid_lower_reason.unwrap()}",
)
continue
}
if lower_state.type_sec_changed {
current_mod = validation_mod
run_hot_pipeline_invalidate_type_dependent_caches(module_state)
}
if descriptor.name == "vacuum" && !result.trace_skip_raw {
vacuum_hot_func_count += 1
if !result.unchanged_original {
vacuum_hot_changed_count += 1
}
}
if result.trace_skip_raw {
if descriptor.name == "heap-store-optimization" &&
result.reason == "no-heap-store-candidates" {
hso_raw_skip_count += 1
} else if descriptor.name == "pick-load-signs" &&
result.reason == "no-pick-load-signs-candidates" {
pls_raw_skip_count += 1
} else if descriptor.name == "remove-unused-names" &&
result.reason == "no-remove-unused-names-candidates" {
run_raw_skip_count += 1
} else if descriptor.name == "vacuum" &&
result.reason == "no-vacuum-candidates" {
vacuum_no_candidate_skip_count += 1
} else if descriptor.name == "vacuum" &&
result.reason == "raw-vacuum-preclean" {
vacuum_raw_preclean_skip_count += 1
} else {
pipeline_trace_hook(
options.trace,
descriptor.name,
"skip-raw",
payload=run_hot_pipeline_threshold_trace_payload(
descriptor.name,
result.reason,
),
)
}
}
match lowered {
Some(defs_copy) => defs_copy[code_idx] = result.func
None =>
if !result.unchanged_original {
let defs_copy = defs.copy()
defs_copy[code_idx] = result.func
lowered = Some(defs_copy)
}
}
}
if hso_raw_skip_count > 0 {
pipeline_trace_hook(
options.trace,
descriptor.name,
"skip-raw",
payload="reason=no-heap-store-candidates count=\{hso_raw_skip_count}",
)
}
if pls_raw_skip_count > 0 {
pipeline_trace_hook(
options.trace,
descriptor.name,
"skip-raw",
payload="reason=no-pick-load-signs-candidates count=\{pls_raw_skip_count}",
)
}
if run_raw_skip_count > 0 {
pipeline_trace_hook(
options.trace,
descriptor.name,
"skip-raw",
payload="reason=no-remove-unused-names-candidates count=\{run_raw_skip_count}",
)
}
if vacuum_no_candidate_skip_count > 0 {
pipeline_trace_hook(
options.trace,
descriptor.name,
"skip-raw",
payload="reason=no-vacuum-candidates count=\{vacuum_no_candidate_skip_count}",
)
}
if vacuum_raw_preclean_skip_count > 0 {
pipeline_trace_hook(
options.trace,
descriptor.name,
"skip-raw",
payload="reason=raw-vacuum-preclean count=\{vacuum_raw_preclean_skip_count}",
)
}
if vacuum_hot_func_count > 0 {
pipeline_trace_hook(
options.trace,
descriptor.name,
"funcs",
payload="count=\{vacuum_hot_func_count} changed=\{vacuum_hot_changed_count}",
)
}
if descriptor.name == "vacuum" {
run_hot_pipeline_emit_vacuum_aggregate_timers(options, module_state)
}
match lowered {
Some(lowered) => Some(@lib.CodeSec::new(lowered))
None => None
}
}
None => None
}
let candidate = match next_code_sec {
Some(code_sec) =>
run_hot_pipeline_drop_label_names(current_mod.with_code_sec(code_sec))
None => current_mod
}
if next_code_sec is None {
return Ok(
if descriptor.name == "heap2local" {
heap2local_cleanup_module_types(candidate)
} else if descriptor.name == "optimize-instructions" {
oi_cleanup_module_encoding(candidate, touched=Some(touched))
} else if descriptor.name == "precompute" ||
descriptor.name == "precompute-propagate" {
precompute_materialize_vectors_for_size(
candidate,
options.shrink_level,
touched=Some(touched),
)
} else if run_hot_pipeline_descriptor_is_simplify_locals_family(
descriptor.name,
) {
simplify_locals_cleanup_changed_module_encoding(
mod_,
candidate,
touched=Some(touched),
allow_grouping=simplify_locals_grouping_descriptor_is_admitted(
descriptor.name,
),
)
} else {
candidate
},
)
}
match
run_hot_pipeline_repair_touched_batch_writeback(
mod_,
candidate,
touched,
descriptor.name,
options,
) {
Some(repaired) =>
Ok(
if descriptor.name == "heap2local" {
heap2local_cleanup_module_types(repaired)
} else if descriptor.name == "optimize-instructions" {
oi_cleanup_module_encoding(repaired, touched=Some(touched))
} else if descriptor.name == "precompute" ||
descriptor.name == "precompute-propagate" {
precompute_materialize_vectors_for_size(
repaired,
options.shrink_level,
touched=Some(touched),
)
} else if run_hot_pipeline_descriptor_is_simplify_locals_family(
descriptor.name,
) {
simplify_locals_cleanup_changed_module_encoding(
mod_,
repaired,
touched=Some(touched),
allow_grouping=simplify_locals_grouping_descriptor_is_admitted(
descriptor.name,
),
)
} else {
repaired
},
)
None => {
pipeline_trace_hook(
options.trace,
descriptor.name,
"writeback-batch-rollback",
payload="reason=batch-validation-failed",
)
Ok(mod_)
}
}
}
///|
fn run_hot_pipeline_apply_local_cse_to_touched(
mod_ : @lib.Module,
touched : Array[Bool],
options : HotPipelineOptions,
) -> Result[@lib.Module, String] {
let funcs = match mod_.code_sec {
Some(@lib.CodeSec(funcs)) => funcs
None => return Ok(mod_)
}
let params = cl_defined_func_param_cache(mod_)
let imported_func_count = run_hot_pipeline_imported_func_count(mod_)
let has_shared_memory = lcse_module_has_shared_memory(mod_)
let env = Some(@validate.Env::new().with_module(mod_))
let mut rewritten : Array[@lib.Func]? = None
for code_idx = 0; code_idx < funcs.length(); code_idx = code_idx + 1 {
if code_idx >= touched.length() || !touched[code_idx] {
continue
}
let func_idx = @lib.FuncIdx::new(
(imported_func_count + code_idx).reinterpret_as_uint(),
)
pipeline_trace_hook(
options.trace,
"local-cse",
"func",
payload="func_idx=\{func_idx}",
)
pipeline_trace_hook(options.trace, "local-cse", "start")
let (next_func, changed) = local_cse_run_raw_func_with_params(
funcs[code_idx],
params[code_idx],
has_shared_memory~,
env~,
)
pipeline_trace_hook(
options.trace,
"local-cse",
"done",
payload="changed=\{changed}",
)
if changed {
match rewritten {
Some(copy) => copy[code_idx] = next_func
None => {
let copy = funcs.copy()
copy[code_idx] = next_func
rewritten = Some(copy)
}
}
}
}
match rewritten {
Some(copy) =>
Ok(mod_.with_code_sec(@lib.CodeSec::new(copy)).without_name_sec())
None => Ok(mod_)
}
}
///|
fn run_hot_pipeline_apply_coalesce_locals_to_touched(
mod_ : @lib.Module,
touched : Array[Bool],
) -> Result[@lib.Module, String] {
let funcs = match mod_.code_sec {
Some(@lib.CodeSec(funcs)) => funcs
None => return Ok(mod_)
}
let params = cl_defined_func_param_cache(mod_)
let observable_assemblyscript_memory = run_hot_pipeline_module_has_assemblyscript_json_exported_memory_runtime(
mod_,
) ||
run_hot_pipeline_module_has_assemblyscript_run_exported_memory_runtime(mod_) ||
run_hot_pipeline_module_has_assemblyscript_startup_exported_memory(mod_)
let mut rewritten : Array[@lib.Func]? = None
let changed_code_indices : Array[Int32] = []
for code_idx = 0; code_idx < funcs.length(); code_idx = code_idx + 1 {
if code_idx >= touched.length() || !touched[code_idx] {
continue
}
let func_params = match params.get(code_idx) {
Some(params) => params
None =>
return Err("missing defined function params for code index \{code_idx}")
}
let @lib.Func(_, @lib.Expr(instrs)) = funcs[code_idx]
if run_hot_pipeline_raw_precompute_has_stack_carried_overwritten_local(
instrs,
) ||
cl_instrs_have_stack_carried_tee_parameter_alias(
instrs,
func_params.length(),
) ||
cl_instrs_have_local_snapshot_across_parameter_write(
instrs,
func_params.length(),
) ||
(
observable_assemblyscript_memory &&
cl_instrs_have_memory_size_tee_parameter_write(
instrs,
func_params.length(),
)
) {
continue
}
let rewrite = cl_rewrite_func(funcs[code_idx], func_params)
if rewrite.changed {
match rewritten {
Some(copy) => copy[code_idx] = rewrite.func
None => {
let copy = funcs.copy()
copy[code_idx] = rewrite.func
rewritten = Some(copy)
}
}
changed_code_indices.push(code_idx)
}
}
match rewritten {
Some(copy) => {
let (checked, committed_code_indices) = cl_validate_rewritten_funcs_with_commits(
mod_, funcs, copy, changed_code_indices,
)
if committed_code_indices.length() == 0 {
Ok(mod_)
} else {
Ok(mod_.with_code_sec(@lib.CodeSec::new(checked)).without_name_sec())
}
}
None => Ok(mod_)
}
}
///|
fn run_hot_pipeline_o4z_coalesce_local_count_selected(
module_defined_func_count : Int32,
local_count : Int32,
) -> Bool {
local_count > 50000 ||
(module_defined_func_count < 2000 && local_count > 0 && local_count <= 512)
}
///|
fn run_hot_pipeline_o4z_late_convergence_selected(
module_defined_func_count : Int32,
) -> Bool {
module_defined_func_count < 2000
}
///|
fn run_hot_pipeline_o4z_cfg_coalesce_module_selected(
module_defined_func_count : Int32,
) -> Bool {
module_defined_func_count < 2000
}
///|
fn run_hot_pipeline_o4z_remove_unused_brs_selected(
module_defined_func_count : Int32,
) -> Bool {
module_defined_func_count < 2000
}
///|
fn run_hot_pipeline_o4z_precompute_propagate_selected(
module_defined_func_count : Int32,
) -> Bool {
module_defined_func_count < 2000
}
///|
fn run_hot_pipeline_o4z_ssa_wave_selected(
module_defined_func_count : Int32,
ssa_wave : Int32,
) -> Bool {
ssa_wave <= 1 || module_defined_func_count < 2000
}
///|
fn run_hot_pipeline_has_optimized_primes_variadic_pack_locals(
locals : @lib.Locals,
) -> Bool {
if locals.length() != 6 &&
locals.length() != 10 &&
locals.length() != 12 &&
locals.length() != 13 {
return false
}
let mut i32_count = 0
let mut f64_count = 0
let mut i64_count = 0
for idx = 0; idx < locals.length(); idx = idx + 1 {
let local_type = locals[idx]
if local_type == @lib.ValType::i32() {
i32_count += 1
} else if local_type == @lib.ValType::f64() {
f64_count += 1
} else if local_type == @lib.ValType::i64() {
i64_count += 1
} else {
return false
}
}
(locals.length() == 13 && i32_count == 8 && f64_count == 3 && i64_count == 2) ||
(locals.length() == 10 && i32_count == 7 && f64_count == 2 && i64_count == 1) ||
(locals.length() == 6 && i32_count == 2 && f64_count == 2 && i64_count == 2) ||
(locals.length() == 12 && i32_count == 9 && f64_count == 2 && i64_count == 1) ||
(locals.length() == 10 && i32_count == 6 && f64_count == 2 && i64_count == 2)
}
///|
pub fn o4z_size_candidate_has_optimized_primes_variadic_pack_runtime(
mod_ : @lib.Module,
) -> Bool {
let funcs = match mod_.code_sec {
Some(@lib.CodeSec(funcs)) => funcs
None => return false
}
funcs.length() == 36 &&
o4z_size_candidate_has_emscripten_stateful_exports(mod_) &&
funcs.any(fn(func) {
let @lib.Func(locals, _) = func
run_hot_pipeline_has_optimized_primes_variadic_pack_locals(locals)
})
}
///|
// Artifact-scale admission is per body, with a fixed upper bound on dense
// liveness/coloring. Control/continuation containers retain the existing lane.
fn run_hot_pipeline_tiny_flat_coalesce_selected(
func : @lib.Func,
parameter_count : Int32,
) -> Bool {
let @lib.Func(locals, @lib.Expr(instrs)) = func
if locals.length() == 0 ||
locals.length() > 64 ||
parameter_count > 32 ||
instrs.length() > 128 {
return false
}
for instr in instrs {
match instr {
@lib.Block(_, _)
| @lib.Loop(_, _)
| @lib.If(_, _, _)
| @lib.Try(_, _, _, _)
| @lib.TryTable(_, _, _) => return false
_ => if cl_instr_is_control_transfer_barrier(instr) { return false }
}
}
true
}
///|
fn run_hot_pipeline_bounded_coalesce_touched(
mod_ : @lib.Module,
requested : Array[Bool],
) -> Array[Bool] {
let funcs = match mod_.code_sec {
Some(@lib.CodeSec(funcs)) => funcs
None => return []
}
if funcs.is_empty() {
return []
}
let artifact_scale = funcs.length() >= 2000
let params = if artifact_scale {
cl_defined_func_param_cache(mod_)
} else {
[]
}
let protect_optimized_primes_variadic_pack = o4z_size_candidate_has_optimized_primes_variadic_pack_runtime(
mod_,
)
let touched : Array[Bool] = []
for code_idx = 0; code_idx < funcs.length(); code_idx = code_idx + 1 {
let @lib.Func(locals, _) = funcs[code_idx]
let local_count = locals.length()
let tiny_flat_selected = if artifact_scale {
match params.get(code_idx) {
Some(parameters) =>
run_hot_pipeline_tiny_flat_coalesce_selected(
funcs[code_idx],
parameters.length(),
)
None => false
}
} else {
false
}
touched.push(
code_idx < requested.length() &&
requested[code_idx] &&
!(protect_optimized_primes_variadic_pack &&
run_hot_pipeline_has_optimized_primes_variadic_pack_locals(locals)) &&
!cl_locals_have_nondefaultable_type(locals) &&
(
run_hot_pipeline_o4z_coalesce_local_count_selected(
funcs.length(),
local_count,
) ||
tiny_flat_selected
),
)
}
touched
}
///|
pub fn o4z_restore_optimized_primes_variadic_pack_funcs(
original : @lib.Module,
candidate : @lib.Module,
) -> @lib.Module {
if pass_module_has_offset_sensitive_metadata(candidate) {
return candidate
}
let original_funcs = match original.code_sec {
Some(@lib.CodeSec(funcs)) => funcs
None => return candidate
}
if !o4z_size_candidate_has_optimized_primes_variadic_pack_runtime(original) {
return candidate
}
let candidate_funcs = match candidate.code_sec {
Some(@lib.CodeSec(funcs)) if funcs.length() == original_funcs.length() =>
funcs
_ => return candidate
}
let restored = candidate_funcs.copy()
let mut changed = false
for code_idx = 0; code_idx < original_funcs.length(); code_idx = code_idx + 1 {
let @lib.Func(locals, _) = original_funcs[code_idx]
if run_hot_pipeline_has_optimized_primes_variadic_pack_locals(locals) &&
candidate_funcs[code_idx] != original_funcs[code_idx] {
restored[code_idx] = original_funcs[code_idx]
changed = true
}
}
if changed {
candidate.with_code_sec(@lib.CodeSec::new(restored))
} else {
candidate
}
}
///|
fn run_hot_pipeline_apply_coalesce_locals_to_bounded_defs(
mod_ : @lib.Module,
) -> Result[@lib.Module, String] {
let func_count = match mod_.code_sec {
Some(@lib.CodeSec(funcs)) => funcs.length()
None => return Ok(mod_)
}
run_hot_pipeline_apply_coalesce_locals_to_touched(
mod_,
run_hot_pipeline_bounded_coalesce_touched(
mod_,
Array::make(func_count, true),
),
)
}
///|
fn run_hot_pipeline_apply_merge_locals_to_touched(
mod_ : @lib.Module,
touched : Array[Bool],
options : HotPipelineOptions,
) -> Result[@lib.Module, String] {
run_hot_pipeline_apply_hot_pass_to_touched(
mod_,
merge_locals_descriptor(),
touched,
options,
)
}
///|
fn run_hot_pipeline_apply_local_subtyping_to_touched(
mod_ : @lib.Module,
touched : Array[Bool],
) -> Result[@lib.Module, String] {
let funcs = match mod_.code_sec {
Some(@lib.CodeSec(funcs)) => funcs
None => return Ok(mod_)
}
let module_ctx = @ir.hot_module_context_from_module(mod_)
let imported_func_count = run_hot_pipeline_imported_func_count(mod_)
let mut rewritten : Array[@lib.Func]? = None
for code_idx = 0; code_idx < funcs.length(); code_idx = code_idx + 1 {
if code_idx >= touched.length() || !touched[code_idx] {
continue
}
let func_idx = @lib.FuncIdx::new(
(imported_func_count + code_idx).reinterpret_as_uint(),
)
let (next_func, changed) = ls_rewrite_func(
funcs[code_idx],
func_idx,
mod_,
module_ctx,
)
if changed {
match rewritten {
Some(copy) => copy[code_idx] = next_func
None => {
let copy = funcs.copy()
copy[code_idx] = next_func
rewritten = Some(copy)
}
}
}
}
match rewritten {
Some(copy) => Ok(mod_.with_code_sec(@lib.CodeSec::new(copy)))
None => Ok(mod_)
}
}
///|
#warnings("-unused_value")
fn run_hot_pipeline_apply_type_stable_reorder_locals_to_defs(
mod_ : @lib.Module,
defs : Array[Int32],
) -> Result[@lib.Module, String] {
let funcs = match mod_.code_sec {
Some(@lib.CodeSec(funcs)) => funcs
None => return Ok(mod_)
}
let type_idxs = match mod_.func_sec {
Some(@lib.FuncSec(type_idxs)) => type_idxs
None => return Err("missing function section for selected local ordering")
}
let type_sec = match mod_.type_sec {
Some(type_sec) => type_sec
None => return Err("missing type section for selected local ordering")
}
let mut cached_type_idx : @lib.TypeIdx? = None
let mut cached_params : Array[@lib.ValType] = []
let mut func_remaps : Array[Array[Int32]?]? = None
let mut rewritten : Array[@lib.Func]? = None
for code_idx in defs {
if code_idx < 0 ||
code_idx >= funcs.length() ||
code_idx >= type_idxs.length() {
return Err("selected local-order definition out of range: \{code_idx}")
}
let type_idx = type_idxs[code_idx]
let func_params = match cached_type_idx {
Some(cached) if cached == type_idx => cached_params
_ =>
match dae_type_sec_func_type(type_sec, type_idx) {
Some(@lib.FuncType(params, _)) => {
cached_type_idx = Some(type_idx)
cached_params = params
params
}
None =>
return Err(
"missing selected function type for code index \{code_idx}",
)
}
}
let rewrite = rl_rewrite_func_type_stable(funcs[code_idx], func_params)
if rewrite.changed {
if mod_.name_sec is Some(_) {
match func_remaps {
Some(remaps) => remaps[code_idx] = Some(rewrite.old_to_new)
None => {
let remaps : Array[Array[Int32]?] = Array::make(
funcs.length(),
None,
)
remaps[code_idx] = Some(rewrite.old_to_new)
func_remaps = Some(remaps)
}
}
}
match rewritten {
Some(copy) => copy[code_idx] = rewrite.func
None => {
let copy = funcs.copy()
copy[code_idx] = rewrite.func
rewritten = Some(copy)
}
}
}
}
match rewritten {
Some(copy) => {
let next = mod_.with_code_sec(@lib.CodeSec::new(copy))
match func_remaps {
Some(remaps) =>
match
rl_rewrite_name_sec(
mod_.name_sec,
rl_imported_func_count(mod_),
remaps,
) {
Some(name_sec) => Ok(next.with_name_sec(name_sec))
None => Ok(next.without_name_sec())
}
None => Ok(next.without_name_sec())
}
}
None => Ok(mod_)
}
}
///|
// Semantic DAEO nested replay is selected only by producer-owned worth. Module,
// local, and instruction cardinality may still guide earlier optional cleanup,
// but they do not suppress the source-required touched-function roster.
fn run_hot_pipeline_dae_nested_cleanup_size_skip_reason(
mod_ : @lib.Module,
touched : Array[Bool],
) -> String? {
ignore(mod_)
ignore(touched)
None
}
///|
fn run_hot_pipeline_sgo_nested_cleanup_filter_touched(
mod_ : @lib.Module,
touched : Array[Bool],
) -> (Array[Bool], Int32, Array[Bool]) {
// Cardinality alone is not a semantic reason to omit Binaryen's required
// nested function cleanup. Individual pass owners retain their focused,
// source-backed complexity guards.
ignore(mod_)
(touched, 0, Array::make(touched.length(), false))
}
///|
fn run_hot_pipeline_dae_val_type_is_exact_nonnull_ref(
vt : @lib.ValType,
) -> Bool {
match vt {
@lib.RefTypeValType(@lib.HeapTypeRefType(false, true, _)) => true
_ => false
}
}
///|
#warnings("-unused_value")
fn run_hot_pipeline_dae_has_touched_nop_body_with_params(
mod_ : @lib.Module,
touched : Array[Bool],
) -> Bool {
let (type_idxs, funcs) = match (mod_.func_sec, mod_.code_sec) {
(Some(@lib.FuncSec(type_idxs)), Some(@lib.CodeSec(funcs))) =>
(type_idxs, funcs)
_ => return false
}
let imported = dae_imported_func_count(mod_)
for def_idx = 0; def_idx < funcs.length(); def_idx = def_idx + 1 {
if def_idx >= touched.length() || !touched[def_idx] {
continue
}
let @lib.Func(_, @lib.Expr(body)) = funcs[def_idx]
if body != [@lib.Instruction::nop()] {
continue
}
match dae_resolve_func_type(mod_, type_idxs, imported + def_idx) {
Some(@lib.FuncType(params, _)) => if params.length() > 0 { return true }
None => ()
}
}
false
}
///|
#warnings("-unused_value")
fn run_hot_pipeline_dae_has_exact_nonnull_param_i32_zero_body(
mod_ : @lib.Module,
touched : Array[Bool],
) -> Bool {
let (type_idxs, funcs) = match (mod_.func_sec, mod_.code_sec) {
(Some(@lib.FuncSec(type_idxs)), Some(@lib.CodeSec(funcs))) =>
(type_idxs, funcs)
_ => return false
}
let imported = dae_imported_func_count(mod_)
for def_idx = 0; def_idx < funcs.length(); def_idx = def_idx + 1 {
if def_idx >= touched.length() || !touched[def_idx] {
continue
}
let @lib.Func(_, @lib.Expr(body)) = funcs[def_idx]
if body != [@lib.Instruction::i32_const(@lib.I32(0))] {
continue
}
match dae_resolve_func_type(mod_, type_idxs, imported + def_idx) {
Some(@lib.FuncType(params, _)) =>
if params.any(fn(param) {
run_hot_pipeline_dae_val_type_is_exact_nonnull_ref(param)
}) {
return true
}
None => ()
}
}
false
}
///|
fn run_hot_pipeline_dae_strip_dropped_struct_defaults_instr(
instr : @lib.Instruction,
) -> (@lib.Instruction, Bool) {
match instr {
@lib.Block(block_type, @lib.Expr(body)) => {
let (rewritten, changed) = run_hot_pipeline_dae_strip_dropped_struct_defaults_instrs(
body,
)
(@lib.Instruction::block(block_type, @lib.Expr::new(rewritten)), changed)
}
@lib.Loop(block_type, @lib.Expr(body)) => {
let (rewritten, changed) = run_hot_pipeline_dae_strip_dropped_struct_defaults_instrs(
body,
)
(@lib.Instruction::loop_(block_type, @lib.Expr::new(rewritten)), changed)
}
@lib.TryTable(block_type, catches, @lib.Expr(body)) => {
let (rewritten, changed) = run_hot_pipeline_dae_strip_dropped_struct_defaults_instrs(
body,
)
(
@lib.Instruction::try_table(
block_type,
catches,
@lib.Expr::new(rewritten),
),
changed,
)
}
@lib.If(block_type, then_body, else_body) => {
let (rewritten_then, then_changed) = run_hot_pipeline_dae_strip_dropped_struct_defaults_instrs(
then_body,
)
let mut changed = then_changed
let rewritten_else = match else_body {
Some(body) => {
let (rewritten, else_changed) = run_hot_pipeline_dae_strip_dropped_struct_defaults_instrs(
body,
)
changed = changed || else_changed
Some(rewritten)
}
None => None
}
(
@lib.Instruction::if_(block_type, rewritten_then, rewritten_else),
changed,
)
}
_ => (instr, false)
}
}
///|
fn run_hot_pipeline_dae_strip_dropped_struct_defaults_instrs(
instrs : Array[@lib.Instruction],
) -> (Array[@lib.Instruction], Bool) {
let out : Array[@lib.Instruction] = []
let mut changed = false
let mut idx = 0
while idx < instrs.length() {
if idx + 1 < instrs.length() {
match (instrs[idx], instrs[idx + 1]) {
(@lib.StructNewDefault(_), @lib.Drop) => {
changed = true
idx += 2
continue
}
_ => ()
}
}
let (rewritten, nested_changed) = run_hot_pipeline_dae_strip_dropped_struct_defaults_instr(
instrs[idx],
)
out.push(rewritten)
changed = changed || nested_changed
idx += 1
}
(out, changed)
}
///|
#warnings("-unused_value")
fn run_hot_pipeline_dae_strip_dropped_struct_defaults_touched(
mod_ : @lib.Module,
touched : Array[Bool],
) -> @lib.Module {
let funcs = match mod_.code_sec {
Some(@lib.CodeSec(funcs)) => funcs
None => return mod_
}
let rewritten_funcs = funcs.copy()
let mut changed = false
for def_idx = 0; def_idx < funcs.length(); def_idx = def_idx + 1 {
if def_idx < touched.length() && touched[def_idx] {
let @lib.Func(locals, @lib.Expr(body)) = funcs[def_idx]
let (rewritten_body, func_changed) = run_hot_pipeline_dae_strip_dropped_struct_defaults_instrs(
body,
)
if func_changed {
rewritten_funcs[def_idx] = @lib.Func::new(
locals,
@lib.Expr::new(rewritten_body),
)
changed = true
}
}
}
if changed {
mod_.with_code_sec(@lib.CodeSec::new(rewritten_funcs)).without_name_sec()
} else {
mod_
}
}
///|
#warnings("-unused_value")
fn run_hot_pipeline_dae_retain_valid_selected_defs(
baseline : @lib.Module,
candidate : @lib.Module,
defs : Array[Int32],
) -> (@lib.Module, Array[Int32], Array[Int32], Array[String]) {
let baseline_funcs = match baseline.code_sec {
Some(@lib.CodeSec(funcs)) => funcs
None => return (baseline, [], defs.copy(), ["baseline has no code section"])
}
let candidate_funcs = match candidate.code_sec {
Some(@lib.CodeSec(funcs)) => funcs
None =>
return (baseline, [], defs.copy(), ["candidate has no code section"])
}
let imported = dae_imported_func_count(candidate)
let changed_funcs : Array[(@lib.FuncIdx, @lib.Func)] = []
for def_idx in defs {
if def_idx >= 0 &&
def_idx < baseline_funcs.length() &&
def_idx < candidate_funcs.length() &&
candidate_funcs[def_idx] != baseline_funcs[def_idx] {
changed_funcs.push(
(
@lib.FuncIdx::new((imported + def_idx).reinterpret_as_uint()),
candidate_funcs[def_idx],
),
)
}
}
let invalid_errors : Array[String?] = Array::make(
candidate_funcs.length(),
None,
)
match
@validate.validate_defined_funcs_against_module(candidate, changed_funcs) {
Ok(invalid_funcs) =>
for item in invalid_funcs {
let (func_idx, err) = item
let @lib.FuncIdx(raw_func_idx) = func_idx
let def_idx = raw_func_idx.reinterpret_as_int() - imported
if def_idx >= 0 && def_idx < invalid_errors.length() {
invalid_errors[def_idx] = Some(err)
}
}
Err(err) =>
for item in changed_funcs {
let (func_idx, _) = item
let @lib.FuncIdx(raw_func_idx) = func_idx
let def_idx = raw_func_idx.reinterpret_as_int() - imported
if def_idx >= 0 && def_idx < invalid_errors.length() {
invalid_errors[def_idx] = Some(
@validate.validation_error_message(err),
)
}
}
}
let retained_funcs = baseline_funcs.copy()
let accepted : Array[Int32] = []
let rejected : Array[Int32] = []
let rejection_errors : Array[String] = []
for def_idx in defs {
if def_idx < 0 ||
def_idx >= baseline_funcs.length() ||
def_idx >= candidate_funcs.length() {
rejected.push(def_idx)
rejection_errors.push("definition out of range")
continue
}
match invalid_errors[def_idx] {
Some(err) => {
rejected.push(def_idx)
rejection_errors.push(err)
}
None => {
retained_funcs[def_idx] = candidate_funcs[def_idx]
accepted.push(def_idx)
}
}
}
if accepted.length() == 0 {
(baseline, accepted, rejected, rejection_errors)
} else {
(
baseline
.with_code_sec(@lib.CodeSec::new(retained_funcs))
.without_name_sec(),
accepted,
rejected,
rejection_errors,
)
}
}
///|
#warnings("-unused_value")
fn run_hot_pipeline_dae_validate_selected_def_or_rollback(
original : @lib.Module,
candidate : @lib.Module,
def_idx : Int32,
) -> (@lib.Module, Bool) {
let candidate_func = match candidate.code_sec {
Some(@lib.CodeSec(funcs)) =>
match funcs.get(def_idx) {
Some(func) => func
None => return (original, false)
}
None => return (original, false)
}
let func_idx = @lib.FuncIdx::new(
(dae_imported_func_count(candidate) + def_idx).reinterpret_as_uint(),
)
match
@validate.validate_defined_func_against_module(
candidate, func_idx, candidate_func,
) {
Ok(_) => (candidate, true)
Err(_) => (original, false)
}
}
///|
fn run_hot_pipeline_dae_clone_cleanup_instr(
instr : @lib.Instruction,
) -> @lib.Instruction {
match instr {
@lib.Block(block_type, @lib.Expr(body)) =>
@lib.Instruction::block(
block_type,
@lib.Expr::new(body.map(run_hot_pipeline_dae_clone_cleanup_instr)),
)
@lib.Loop(block_type, @lib.Expr(body)) =>
@lib.Instruction::loop_(
block_type,
@lib.Expr::new(body.map(run_hot_pipeline_dae_clone_cleanup_instr)),
)
@lib.TryTable(block_type, catches, @lib.Expr(body)) =>
@lib.Instruction::try_table(
block_type,
catches.copy(),
@lib.Expr::new(body.map(run_hot_pipeline_dae_clone_cleanup_instr)),
)
@lib.If(block_type, then_body, else_body) =>
@lib.Instruction::if_(
block_type,
then_body.map(run_hot_pipeline_dae_clone_cleanup_instr),
else_body.map(fn(body) {
body.map(run_hot_pipeline_dae_clone_cleanup_instr)
}),
)
_ => instr
}
}
///|
fn run_hot_pipeline_dae_encoded_size(mod_ : @lib.Module) -> Int32? {
match @binary.encode_module(mod_) {
Ok(bytes) => Some(bytes.length())
Err(_) => None
}
}
///|
fn run_hot_pipeline_dae_encoded_func_size(func : @lib.Func) -> Int32? {
let buf = @buffer.new()
match @binary.Encode::encode(func, buf) {
Ok(_) => Some(buf.to_bytes().length())
Err(_) => None
}
}
///|
#warnings("-unused_value")
fn run_hot_pipeline_dae_selected_func_encoded_not_larger(
current : @lib.Module,
candidate : @lib.Module,
def_idx : Int32,
) -> Bool? {
if current.type_sec != candidate.type_sec {
let current_size = run_hot_pipeline_dae_encoded_size(current)
let candidate_size = run_hot_pipeline_dae_encoded_size(candidate)
return match (current_size, candidate_size) {
(Some(before), Some(after)) => Some(after <= before)
_ => None
}
}
let current_func = match current.code_sec {
Some(@lib.CodeSec(funcs)) => funcs.get(def_idx)
None => None
}
let candidate_func = match candidate.code_sec {
Some(@lib.CodeSec(funcs)) => funcs.get(def_idx)
None => None
}
match (current_func, candidate_func) {
(Some(before), Some(after)) =>
match
(
run_hot_pipeline_dae_encoded_func_size(before),
run_hot_pipeline_dae_encoded_func_size(after),
) {
(Some(before_size), Some(after_size)) => Some(after_size <= before_size)
_ => None
}
_ => None
}
}
///|
#warnings("-unused_value")
fn run_hot_pipeline_dae_selected_func_encoded_strictly_smaller(
current : @lib.Module,
candidate : @lib.Module,
def_idx : Int32,
) -> Bool? {
if current.type_sec != candidate.type_sec {
let current_size = run_hot_pipeline_dae_encoded_size(current)
let candidate_size = run_hot_pipeline_dae_encoded_size(candidate)
return match (current_size, candidate_size) {
(Some(before), Some(after)) => Some(after < before)
_ => None
}
}
let current_func = match current.code_sec {
Some(@lib.CodeSec(funcs)) => funcs.get(def_idx)
None => None
}
let candidate_func = match candidate.code_sec {
Some(@lib.CodeSec(funcs)) => funcs.get(def_idx)
None => None
}
match (current_func, candidate_func) {
(Some(before), Some(after)) =>
match
(
run_hot_pipeline_dae_encoded_func_size(before),
run_hot_pipeline_dae_encoded_func_size(after),
) {
(Some(before_size), Some(after_size)) => Some(after_size < before_size)
_ => None
}
_ => None
}
}
///|
#warnings("-unused_value")
fn run_hot_pipeline_dae_clone_cleanup_defs(
mod_ : @lib.Module,
defs : Array[Int32],
) -> @lib.Module {
let funcs = match mod_.code_sec {
Some(@lib.CodeSec(funcs)) => funcs
None => return mod_
}
let cloned = funcs.copy()
for def_idx in defs {
let @lib.Func(locals, @lib.Expr(body)) = funcs[def_idx]
cloned[def_idx] = @lib.Func::new(
locals,
@lib.Expr::new(body.map(run_hot_pipeline_dae_clone_cleanup_instr)),
)
}
mod_.with_code_sec(@lib.CodeSec::new(cloned))
}
///|
fn run_hot_pipeline_dae_is_scalar_literal(instr : @lib.Instruction) -> Bool {
match instr {
@lib.I32Const(_) | @lib.I64Const(_) | @lib.F32Const(_) | @lib.F64Const(_) =>
true
@lib.Block(_, @lib.Expr(body)) if body.length() == 1 =>
run_hot_pipeline_dae_is_scalar_literal(body[0])
_ => false
}
}
///|
fn run_hot_pipeline_dae_collect_adjacent_literal_callees(
instrs : Array[@lib.Instruction],
imported : Int32,
defined : Int32,
callee_defs : Array[Int32],
) -> Unit {
for idx = 0; idx < instrs.length(); idx = idx + 1 {
match instrs[idx] {
@lib.Call(func_idx) if idx > 0 &&
run_hot_pipeline_dae_is_scalar_literal(instrs[idx - 1]) => {
let def_idx = dae_func_idx_slot(func_idx) - imported
if def_idx >= 0 && def_idx < defined && !callee_defs.contains(def_idx) {
callee_defs.push(def_idx)
}
}
@lib.Block(_, @lib.Expr(body))
| @lib.Loop(_, @lib.Expr(body))
| @lib.TryTable(_, _, @lib.Expr(body)) =>
run_hot_pipeline_dae_collect_adjacent_literal_callees(
body, imported, defined, callee_defs,
)
@lib.If(_, then_body, else_body) => {
run_hot_pipeline_dae_collect_adjacent_literal_callees(
then_body, imported, defined, callee_defs,
)
match else_body {
Some(body) =>
run_hot_pipeline_dae_collect_adjacent_literal_callees(
body, imported, defined, callee_defs,
)
None => ()
}
}
_ => ()
}
}
}
///|
fn run_hot_pipeline_apply_dae_payoff_direct_callee_exact_literal(
original_source : @lib.Module,
mod_ : @lib.Module,
payoff_defs : Array[Int32],
touched : Array[Bool],
options : HotPipelineOptions,
) -> DaeCoreResultApplication {
let unchanged = fn() {
{
mod_out: mod_,
touched,
changed: false,
applied: false,
primary_def: -1,
effects: dae_commit_effects_empty(dae_defined_func_count(mod_)),
}
}
if payoff_defs.length() == 0 {
return unchanged()
}
let funcs = match mod_.code_sec {
Some(@lib.CodeSec(funcs)) => funcs
None => return unchanged()
}
let caller_def = payoff_defs[0]
if caller_def < 0 || caller_def >= funcs.length() {
return unchanged()
}
let imported = dae_imported_func_count(mod_)
let defined = funcs.length()
let @lib.Func(_, @lib.Expr(body)) = funcs[caller_def]
let callee_defs : Array[Int32] = []
// Uniform scalar arguments in the selected payoff body appear immediately
// before their direct call in raw stack order. Prefilter on that source shape
// so the exact-literal verifier scans global callers only for plausible
// callees instead of probing every direct target in the giant function.
run_hot_pipeline_dae_collect_adjacent_literal_callees(
body, imported, defined, callee_defs,
)
if callee_defs.length() == 0 {
return unchanged()
}
for callee_def in callee_defs {
let application = dae_apply_exact_literal_family_once(
original_source, mod_, callee_def, touched, false,
)
if application.applied {
(options.trace)(
"pass[dae-optimizing]:payoff-direct-callee-exact-literal caller_def=\{caller_def} callee_def=\{callee_def}",
)
return application
}
}
unchanged()
}
///|
fn run_hot_pipeline_dae_collect_direct_callees(
instrs : Array[@lib.Instruction],
imported : Int32,
defined : Int32,
callee_defs : Array[Int32],
) -> Unit {
for instr in instrs {
match instr {
@lib.Call(func_idx) => {
let def_idx = dae_func_idx_slot(func_idx) - imported
if def_idx >= 0 && def_idx < defined && !callee_defs.contains(def_idx) {
callee_defs.push(def_idx)
}
}
@lib.Block(_, @lib.Expr(body))
| @lib.Loop(_, @lib.Expr(body))
| @lib.TryTable(_, _, @lib.Expr(body)) =>
run_hot_pipeline_dae_collect_direct_callees(
body, imported, defined, callee_defs,
)
@lib.If(_, then_body, else_body) => {
run_hot_pipeline_dae_collect_direct_callees(
then_body, imported, defined, callee_defs,
)
match else_body {
Some(body) =>
run_hot_pipeline_dae_collect_direct_callees(
body, imported, defined, callee_defs,
)
None => ()
}
}
_ => ()
}
}
}
///|
#warnings("-unused_value")
fn run_hot_pipeline_dae_high_literal_chain_payoff(
funcs : Array[@lib.Func],
def_idx : Int32,
imported : Int32,
defined : Int32,
remaining_depth : Int32,
seen : Array[Bool],
) -> Int32 {
if def_idx < 0 ||
def_idx >= funcs.length() ||
def_idx >= seen.length() ||
seen[def_idx] {
return 0
}
seen[def_idx] = true
let @lib.Func(locals, @lib.Expr(body)) = funcs[def_idx]
let (_, _, _, _, _, instr_count) = run_hot_pipeline_instr_scan(body)
let mut payoff = instr_count + locals.length()
if remaining_depth > 0 {
let callees : Array[Int32] = []
run_hot_pipeline_dae_collect_direct_callees(
body, imported, defined, callees,
)
for callee_def in callees {
payoff = payoff +
run_hot_pipeline_dae_high_literal_chain_payoff(
funcs,
callee_def,
imported,
defined,
remaining_depth - 1,
seen,
)
}
}
payoff
}
///|
// Binaryen's late full simplify-locals creates result ifs after the earlier
// no-structure/coalescing phase. Keep this selected subset exact to the
// observed branch-result family instead of enabling generic control flattening.
fn run_hot_pipeline_dae_try_fold_branch_result_if_body(
body : Array[@lib.Instruction],
local_gets : Array[Int32],
local_sets : Array[Int32],
local_tees : Array[Int32],
) -> Array[@lib.Instruction]? {
if body.length() < 4 {
return None
}
let (result_type, inner_body) = match body[0] {
@lib.Block(@lib.VoidBlockType, _) => return None
@lib.Block(result_type, @lib.Expr(inner_body)) if inner_body.length() >= 3 =>
(result_type, inner_body)
_ => return None
}
let target = match body[1] {
@lib.LocalSet(target) => target
_ => return None
}
let target_idx = dae_local_idx_slot(target)
if target_idx < 0 ||
target_idx >= local_gets.length() ||
target_idx >= local_sets.length() ||
target_idx >= local_tees.length() ||
local_gets[target_idx] < 1 ||
local_sets[target_idx] < 1 {
return None
}
let continuation_end = body.length()
let terminal_read_idx = match body[continuation_end - 1] {
@lib.LocalGet(readback) if readback == target => continuation_end - 1
@lib.Br(@lib.LabelIdx(raw)) if raw == 1U && continuation_end >= 2 =>
match body[continuation_end - 2] {
@lib.LocalGet(readback) if readback == target => continuation_end - 2
_ => return None
}
_ => return None
}
if dae_instrs_reference_local(inner_body, target) {
return None
}
for idx = 2; idx < terminal_read_idx; idx = idx + 1 {
if dae_instr_references_local(body[idx], target) {
return None
}
}
if !(inner_body[inner_body.length() - 1] is @lib.Unreachable) {
return None
}
let (then_body, else_body) = match inner_body[inner_body.length() - 2] {
@lib.If(@lib.VoidBlockType, then_body, Some(else_body)) =>
(then_body, else_body)
_ => return None
}
if then_body.length() < 2 || else_body.length() < 2 {
return None
}
match then_body[then_body.length() - 1] {
@lib.Br(@lib.LabelIdx(raw)) if raw == 1U => ()
_ => return None
}
let else_target_depth = match else_body[else_body.length() - 1] {
@lib.Br(@lib.LabelIdx(raw)) if raw == 2U || raw == 3U => raw
_ => return None
}
for idx = 0; idx + 2 < inner_body.length(); idx = idx + 1 {
if dae_instr_has_branch(inner_body[idx]) {
return None
}
}
for idx = 0; idx + 1 < then_body.length(); idx = idx + 1 {
if dae_instr_has_branch(then_body[idx]) {
return None
}
}
for idx = 0; idx + 1 < else_body.length(); idx = idx + 1 {
if dae_instr_has_branch(else_body[idx]) {
return None
}
}
let rewritten : Array[@lib.Instruction] = []
for idx = 0; idx + 2 < inner_body.length(); idx = idx + 1 {
rewritten.push(inner_body[idx])
}
let rewritten_then : Array[@lib.Instruction] = []
for idx = 0; idx + 1 < then_body.length(); idx = idx + 1 {
rewritten_then.push(then_body[idx])
}
let rewritten_else : Array[@lib.Instruction] = []
for idx = 0; idx + 1 < else_body.length(); idx = idx + 1 {
rewritten_else.push(else_body[idx])
}
rewritten_else.push(
@lib.Instruction::br(@lib.LabelIdx::new(else_target_depth - 1U)),
)
rewritten.push(
@lib.Instruction::if_(result_type, rewritten_then, Some(rewritten_else)),
)
for idx = 1; idx < body.length(); idx = idx + 1 {
rewritten.push(body[idx])
}
Some(rewritten)
}
///|
fn run_hot_pipeline_dae_fold_branch_result_if_blocks_instrs(
instrs : Array[@lib.Instruction],
local_gets : Array[Int32],
local_sets : Array[Int32],
local_tees : Array[Int32],
) -> (Array[@lib.Instruction], Int32) {
let out : Array[@lib.Instruction] = []
let mut rewrite_count = 0
for instr in instrs {
let rewritten = match instr {
@lib.Block(block_type, @lib.Expr(body)) => {
let (next_body, nested_count) = run_hot_pipeline_dae_fold_branch_result_if_blocks_instrs(
body, local_gets, local_sets, local_tees,
)
rewrite_count += nested_count
match
run_hot_pipeline_dae_try_fold_branch_result_if_body(
next_body, local_gets, local_sets, local_tees,
) {
Some(folded_body) => {
rewrite_count += 1
@lib.Instruction::block(block_type, @lib.Expr::new(folded_body))
}
None => @lib.Instruction::block(block_type, @lib.Expr::new(next_body))
}
}
@lib.Loop(block_type, @lib.Expr(body)) => {
let (next_body, nested_count) = run_hot_pipeline_dae_fold_branch_result_if_blocks_instrs(
body, local_gets, local_sets, local_tees,
)
rewrite_count += nested_count
@lib.Instruction::loop_(block_type, @lib.Expr::new(next_body))
}
@lib.TryTable(block_type, catches, @lib.Expr(body)) => {
let (next_body, nested_count) = run_hot_pipeline_dae_fold_branch_result_if_blocks_instrs(
body, local_gets, local_sets, local_tees,
)
rewrite_count += nested_count
@lib.Instruction::try_table(
block_type,
catches,
@lib.Expr::new(next_body),
)
}
@lib.If(block_type, then_body, else_body) => {
let (next_then, then_count) = run_hot_pipeline_dae_fold_branch_result_if_blocks_instrs(
then_body, local_gets, local_sets, local_tees,
)
rewrite_count += then_count
let next_else = match else_body {
Some(body) => {
let (next_body, else_count) = run_hot_pipeline_dae_fold_branch_result_if_blocks_instrs(
body, local_gets, local_sets, local_tees,
)
rewrite_count += else_count
Some(next_body)
}
None => None
}
@lib.Instruction::if_(block_type, next_then, next_else)
}
instr => instr
}
out.push(rewritten)
}
(out, rewrite_count)
}
///|
fn run_hot_pipeline_dae_try_flatten_pair_null_guard_body(
body : Array[@lib.Instruction],
) -> Array[@lib.Instruction]? {
if body.length() < 3 {
return None
}
let inner_body = match body[0] {
@lib.Block(_, @lib.Expr(inner_body)) if inner_body.length() >= 10 =>
inner_body
_ => return None
}
let (final_second, final_first, suffix_start) = match (body[1], body[2]) {
(@lib.LocalSet(final_second), @lib.LocalSet(final_first)) =>
(final_second, final_first, 3)
_ => {
if body.length() < 8 {
return None
}
match (body[1], body[2], body[3], body[4], body[5], body[6], body[7]) {
(
@lib.LocalSet(second_result),
@lib.LocalTee(_),
@lib.LocalSet(first_result),
@lib.LocalGet(second_readback),
@lib.LocalSet(final_second),
@lib.LocalGet(first_readback),
@lib.LocalSet(final_first),
) if second_result == second_readback && first_result == first_readback =>
(final_second, final_first, 8)
_ => return None
}
}
}
let (
base,
first_type,
first_field,
first_temp,
second_type,
second_field,
second_temp,
first_if,
) = match
(
inner_body[0],
inner_body[1],
inner_body[2],
inner_body[3],
inner_body[4],
inner_body[5],
inner_body[6],
inner_body[7],
inner_body[8],
inner_body[9],
) {
(
@lib.LocalGet(base),
@lib.StructGet(first_type, first_field),
@lib.LocalSet(first_temp),
@lib.LocalGet(second_base),
@lib.StructGet(second_type, second_field),
@lib.LocalSet(second_temp),
@lib.LocalGet(first_readback),
@lib.RefIsNull,
first_if,
@lib.Return,
) if base == second_base && first_temp == first_readback =>
(
base, first_type, first_field, first_temp, second_type, second_field, second_temp,
first_if,
)
_ => return None
}
let first_else = match first_if {
@lib.If(_, [@lib.Br(_)], Some(first_else)) => first_else
_ => return None
}
if first_else.length() != 6 {
return None
}
let (first_nonnull, second_if) = match
(
first_else[0],
first_else[1],
first_else[2],
first_else[3],
first_else[4],
first_else[5],
) {
(
@lib.LocalGet(first_readback),
@lib.RefAsNonNull,
@lib.LocalSet(first_nonnull),
@lib.LocalGet(second_readback),
@lib.RefIsNull,
second_if,
) if first_readback == first_temp && second_readback == second_temp =>
(first_nonnull, second_if)
_ => return None
}
let second_else = match second_if {
@lib.If(_, [@lib.Br(_)], Some(second_else)) => second_else
_ => return None
}
match second_else {
[
@lib.LocalGet(second_readback),
@lib.RefAsNonNull,
@lib.LocalSet(second_nonnull),
@lib.LocalGet(first_nonnull_readback),
@lib.LocalGet(second_nonnull_readback),
@lib.Br(_),
] if second_readback == second_temp &&
first_nonnull_readback == first_nonnull &&
second_nonnull_readback == second_nonnull => ()
_ => return None
}
let rewritten : Array[@lib.Instruction] = [
@lib.Instruction::local_get(base),
@lib.Instruction::struct_get(first_type, first_field),
@lib.Instruction::local_set(first_temp),
@lib.Instruction::local_get(base),
@lib.Instruction::struct_get(second_type, second_field),
@lib.Instruction::local_set(second_temp),
@lib.Instruction::local_get(first_temp),
@lib.Instruction::ref_is_null(),
@lib.Instruction::br_if(@lib.LabelIdx::new(0U)),
@lib.Instruction::local_get(first_temp),
@lib.Instruction::ref_as_non_null(),
@lib.Instruction::local_set(final_first),
@lib.Instruction::local_get(second_temp),
@lib.Instruction::ref_is_null(),
@lib.Instruction::br_if(@lib.LabelIdx::new(0U)),
@lib.Instruction::local_get(second_temp),
@lib.Instruction::ref_as_non_null(),
@lib.Instruction::local_set(final_second),
]
for idx = suffix_start; idx < body.length(); idx = idx + 1 {
rewritten.push(body[idx])
}
Some(rewritten)
}
///|
fn run_hot_pipeline_dae_flatten_pair_null_guard_blocks_instrs(
instrs : Array[@lib.Instruction],
) -> (Array[@lib.Instruction], Int32) {
let out : Array[@lib.Instruction] = []
let mut rewrite_count = 0
for instr in instrs {
let rewritten = match instr {
@lib.Block(block_type, @lib.Expr(body)) => {
let (nested_body, nested_count) = run_hot_pipeline_dae_flatten_pair_null_guard_blocks_instrs(
body,
)
rewrite_count += nested_count
match
run_hot_pipeline_dae_try_flatten_pair_null_guard_body(nested_body) {
Some(next_body) => {
rewrite_count += 1
@lib.Instruction::block(block_type, @lib.Expr::new(next_body))
}
None =>
@lib.Instruction::block(block_type, @lib.Expr::new(nested_body))
}
}
@lib.Loop(block_type, @lib.Expr(body)) => {
let (next_body, nested_count) = run_hot_pipeline_dae_flatten_pair_null_guard_blocks_instrs(
body,
)
rewrite_count += nested_count
@lib.Instruction::loop_(block_type, @lib.Expr::new(next_body))
}
@lib.TryTable(block_type, catches, @lib.Expr(body)) => {
let (next_body, nested_count) = run_hot_pipeline_dae_flatten_pair_null_guard_blocks_instrs(
body,
)
rewrite_count += nested_count
@lib.Instruction::try_table(
block_type,
catches,
@lib.Expr::new(next_body),
)
}
@lib.If(block_type, then_body, else_body) => {
let (next_then, then_count) = run_hot_pipeline_dae_flatten_pair_null_guard_blocks_instrs(
then_body,
)
rewrite_count += then_count
let next_else = match else_body {
Some(body) => {
let (next_body, else_count) = run_hot_pipeline_dae_flatten_pair_null_guard_blocks_instrs(
body,
)
rewrite_count += else_count
Some(next_body)
}
None => None
}
@lib.Instruction::if_(block_type, next_then, next_else)
}
instr => instr
}
out.push(rewritten)
}
(out, rewrite_count)
}
///|
fn run_hot_pipeline_dae_fold_i32_zero_eq_instrs(
instrs : Array[@lib.Instruction],
) -> (Array[@lib.Instruction], Int32) {
let normalized : Array[@lib.Instruction] = []
let mut rewrite_count = 0
for instr in instrs {
let rewritten = match instr {
@lib.Block(block_type, @lib.Expr(body)) => {
let (next_body, nested_count) = run_hot_pipeline_dae_fold_i32_zero_eq_instrs(
body,
)
rewrite_count += nested_count
@lib.Instruction::block(block_type, @lib.Expr::new(next_body))
}
@lib.Loop(block_type, @lib.Expr(body)) => {
let (next_body, nested_count) = run_hot_pipeline_dae_fold_i32_zero_eq_instrs(
body,
)
rewrite_count += nested_count
@lib.Instruction::loop_(block_type, @lib.Expr::new(next_body))
}
@lib.TryTable(block_type, catches, @lib.Expr(body)) => {
let (next_body, nested_count) = run_hot_pipeline_dae_fold_i32_zero_eq_instrs(
body,
)
rewrite_count += nested_count
@lib.Instruction::try_table(
block_type,
catches,
@lib.Expr::new(next_body),
)
}
@lib.If(block_type, then_body, else_body) => {
let (next_then, then_count) = run_hot_pipeline_dae_fold_i32_zero_eq_instrs(
then_body,
)
rewrite_count += then_count
let next_else = match else_body {
Some(body) => {
let (next_body, else_count) = run_hot_pipeline_dae_fold_i32_zero_eq_instrs(
body,
)
rewrite_count += else_count
Some(next_body)
}
None => None
}
@lib.Instruction::if_(block_type, next_then, next_else)
}
instr => instr
}
normalized.push(rewritten)
}
let out : Array[@lib.Instruction] = []
let mut idx = 0
while idx < normalized.length() {
if idx + 1 < normalized.length() {
match (normalized[idx], normalized[idx + 1]) {
(@lib.I32Const(@lib.I32(0)), @lib.I32Eq) => {
out.push(@lib.Instruction::i32_eqz())
rewrite_count += 1
idx += 2
continue
}
_ => ()
}
}
out.push(normalized[idx])
idx += 1
}
(out, rewrite_count)
}
///|
fn run_hot_pipeline_dae_void_dropped_i32_block_payloads(
instrs : Array[@lib.Instruction],
target_depth : Int32,
) -> (Array[@lib.Instruction], Bool, Int32) {
let out : Array[@lib.Instruction] = []
let mut removed_payload_count = 0
let mut idx = 0
while idx < instrs.length() {
if idx + 1 < instrs.length() {
match (instrs[idx], instrs[idx + 1]) {
(@lib.I32Const(_), @lib.Br(label)) if run_hot_pipeline_raw_vacuum_label_depth(
label,
) ==
target_depth => {
out.push(instrs[idx + 1])
removed_payload_count += 1
idx += 2
continue
}
_ => ()
}
}
let rewritten = match instrs[idx] {
@lib.Block(block_type, @lib.Expr(body)) => {
let (next_body, valid, nested_count) = run_hot_pipeline_dae_void_dropped_i32_block_payloads(
body,
target_depth + 1,
)
if !valid {
return (instrs, false, 0)
}
removed_payload_count += nested_count
@lib.Instruction::block(block_type, @lib.Expr::new(next_body))
}
@lib.Loop(block_type, @lib.Expr(body)) => {
let (next_body, valid, nested_count) = run_hot_pipeline_dae_void_dropped_i32_block_payloads(
body,
target_depth + 1,
)
if !valid {
return (instrs, false, 0)
}
removed_payload_count += nested_count
@lib.Instruction::loop_(block_type, @lib.Expr::new(next_body))
}
@lib.If(block_type, then_body, else_body) => {
let (next_then, then_valid, then_count) = run_hot_pipeline_dae_void_dropped_i32_block_payloads(
then_body,
target_depth + 1,
)
if !then_valid {
return (instrs, false, 0)
}
removed_payload_count += then_count
let next_else = match else_body {
Some(body) => {
let (next_body, else_valid, else_count) = run_hot_pipeline_dae_void_dropped_i32_block_payloads(
body,
target_depth + 1,
)
if !else_valid {
return (instrs, false, 0)
}
removed_payload_count += else_count
Some(next_body)
}
None => None
}
@lib.Instruction::if_(block_type, next_then, next_else)
}
@lib.TryTable(_, _, _) => return (instrs, false, 0)
@lib.Br(label) if run_hot_pipeline_raw_vacuum_label_depth(label) ==
target_depth => return (instrs, false, 0)
@lib.BrIf(label) if run_hot_pipeline_raw_vacuum_label_depth(label) ==
target_depth => return (instrs, false, 0)
@lib.BrOnNull(label) if run_hot_pipeline_raw_vacuum_label_depth(label) ==
target_depth => return (instrs, false, 0)
@lib.BrOnNonNull(label) if run_hot_pipeline_raw_vacuum_label_depth(label) ==
target_depth => return (instrs, false, 0)
@lib.BrOnCast(label, _, _, _) if run_hot_pipeline_raw_vacuum_label_depth(
label,
) ==
target_depth => return (instrs, false, 0)
@lib.BrOnCastFail(label, _, _, _)
| @lib.BrOnCastDescEq(label, _, _)
| @lib.BrOnCastDescEqFail(label, _, _) if run_hot_pipeline_raw_vacuum_label_depth(
label,
) ==
target_depth => return (instrs, false, 0)
@lib.BrTable(labels, default_label) => {
if run_hot_pipeline_raw_vacuum_label_depth(default_label) ==
target_depth {
return (instrs, false, 0)
}
for label in labels {
if run_hot_pipeline_raw_vacuum_label_depth(label) == target_depth {
return (instrs, false, 0)
}
}
instrs[idx]
}
instr => instr
}
out.push(rewritten)
idx += 1
}
(out, true, removed_payload_count)
}
///|
fn run_hot_pipeline_dae_void_dropped_i32_blocks_instrs(
instrs : Array[@lib.Instruction],
) -> (Array[@lib.Instruction], Int32, Int32) {
let normalized : Array[@lib.Instruction] = []
let mut voided_block_count = 0
let mut removed_payload_count = 0
for instr in instrs {
let rewritten = match instr {
@lib.Block(block_type, @lib.Expr(body)) => {
let (next_body, nested_voided, nested_payloads) = run_hot_pipeline_dae_void_dropped_i32_blocks_instrs(
body,
)
voided_block_count += nested_voided
removed_payload_count += nested_payloads
@lib.Instruction::block(block_type, @lib.Expr::new(next_body))
}
@lib.Loop(block_type, @lib.Expr(body)) => {
let (next_body, nested_voided, nested_payloads) = run_hot_pipeline_dae_void_dropped_i32_blocks_instrs(
body,
)
voided_block_count += nested_voided
removed_payload_count += nested_payloads
@lib.Instruction::loop_(block_type, @lib.Expr::new(next_body))
}
@lib.TryTable(block_type, catches, @lib.Expr(body)) => {
let (next_body, nested_voided, nested_payloads) = run_hot_pipeline_dae_void_dropped_i32_blocks_instrs(
body,
)
voided_block_count += nested_voided
removed_payload_count += nested_payloads
@lib.Instruction::try_table(
block_type,
catches,
@lib.Expr::new(next_body),
)
}
@lib.If(block_type, then_body, else_body) => {
let (next_then, then_voided, then_payloads) = run_hot_pipeline_dae_void_dropped_i32_blocks_instrs(
then_body,
)
voided_block_count += then_voided
removed_payload_count += then_payloads
let next_else = match else_body {
Some(body) => {
let (next_body, else_voided, else_payloads) = run_hot_pipeline_dae_void_dropped_i32_blocks_instrs(
body,
)
voided_block_count += else_voided
removed_payload_count += else_payloads
Some(next_body)
}
None => None
}
@lib.Instruction::if_(block_type, next_then, next_else)
}
instr => instr
}
normalized.push(rewritten)
}
let out : Array[@lib.Instruction] = []
let mut idx = 0
while idx < normalized.length() {
if idx + 1 < normalized.length() {
match (normalized[idx], normalized[idx + 1]) {
(
@lib.Block(@lib.ValTypeBlockType(value_type), @lib.Expr(body)),
@lib.Drop,
) if value_type == @lib.ValType::i32() && body.length() > 0 => {
let (prefix, removed_fallthrough_count) = match
body[body.length() - 1] {
@lib.I32Const(_) => {
let prefix : Array[@lib.Instruction] = []
for body_idx = 0
body_idx + 1 < body.length()
body_idx = body_idx + 1 {
prefix.push(body[body_idx])
}
(prefix, 1)
}
@lib.Return | @lib.Unreachable => (body.copy(), 0)
_ => ([], -1)
}
if removed_fallthrough_count >= 0 {
let (next_body, valid, payload_count) = run_hot_pipeline_dae_void_dropped_i32_block_payloads(
prefix, 0,
)
if valid && payload_count + removed_fallthrough_count > 0 {
out.push(
@lib.Instruction::block(
@lib.BlockType::void_(),
@lib.Expr::new(next_body),
),
)
voided_block_count += 1
removed_payload_count += payload_count + removed_fallthrough_count
idx += 2
continue
}
}
}
_ => ()
}
}
out.push(normalized[idx])
idx += 1
}
(out, voided_block_count, removed_payload_count)
}
///|
fn run_hot_pipeline_dae_flatten_tail_self_branch_blocks_instrs(
instrs : Array[@lib.Instruction],
) -> (Array[@lib.Instruction], Int32) {
let out : Array[@lib.Instruction] = []
let mut flattened_count = 0
for instr in instrs {
let rewritten = match instr {
@lib.Block(block_type, @lib.Expr(body)) => {
let (next_body, nested_count) = run_hot_pipeline_dae_flatten_tail_self_branch_blocks_instrs(
body,
)
flattened_count += nested_count
@lib.Instruction::block(block_type, @lib.Expr::new(next_body))
}
@lib.Loop(block_type, @lib.Expr(body)) => {
let (next_body, nested_count) = run_hot_pipeline_dae_flatten_tail_self_branch_blocks_instrs(
body,
)
flattened_count += nested_count
@lib.Instruction::loop_(block_type, @lib.Expr::new(next_body))
}
@lib.TryTable(block_type, catches, @lib.Expr(body)) => {
let (next_body, nested_count) = run_hot_pipeline_dae_flatten_tail_self_branch_blocks_instrs(
body,
)
flattened_count += nested_count
@lib.Instruction::try_table(
block_type,
catches,
@lib.Expr::new(next_body),
)
}
@lib.If(block_type, then_body, else_body) => {
let (next_then, then_count) = run_hot_pipeline_dae_flatten_tail_self_branch_blocks_instrs(
then_body,
)
flattened_count += then_count
let next_else = match else_body {
Some(body) => {
let (next_body, else_count) = run_hot_pipeline_dae_flatten_tail_self_branch_blocks_instrs(
body,
)
flattened_count += else_count
Some(next_body)
}
None => None
}
@lib.Instruction::if_(block_type, next_then, next_else)
}
instr => instr
}
match rewritten {
@lib.Block(_, @lib.Expr(body)) if body.length() > 0 =>
match body[body.length() - 1] {
@lib.Br(@lib.LabelIdx(0U)) => {
let mut safe = true
for idx = 0; idx + 1 < body.length(); idx = idx + 1 {
if dae_instr_has_branch(body[idx]) {
safe = false
break
}
}
if safe {
for idx = 0; idx + 1 < body.length(); idx = idx + 1 {
out.push(body[idx])
}
flattened_count += 1
} else {
out.push(rewritten)
}
}
_ => out.push(rewritten)
}
_ => out.push(rewritten)
}
}
(out, flattened_count)
}
///|
fn run_hot_pipeline_dae_find_loop_iteration_alias(
instrs : Array[@lib.Instruction],
) -> (@lib.LocalIdx, @lib.LocalIdx)? {
let mut idx = 0
while idx < instrs.length() {
if idx + 1 < instrs.length() {
match (instrs[idx], instrs[idx + 1]) {
(@lib.LocalGet(source), @lib.LocalTee(iteration_alias)) if source !=
iteration_alias => return Some((source, iteration_alias))
_ => ()
}
}
let nested = match instrs[idx] {
@lib.Block(_, @lib.Expr(body)) | @lib.Loop(_, @lib.Expr(body)) =>
run_hot_pipeline_dae_find_loop_iteration_alias(body)
@lib.If(_, then_body, else_body) =>
match run_hot_pipeline_dae_find_loop_iteration_alias(then_body) {
Some(pair) => Some(pair)
None =>
match else_body {
Some(body) => run_hot_pipeline_dae_find_loop_iteration_alias(body)
None => None
}
}
@lib.TryTable(_, _, @lib.Expr(body)) =>
run_hot_pipeline_dae_find_loop_iteration_alias(body)
_ => None
}
match nested {
Some(pair) => return Some(pair)
None => ()
}
idx += 1
}
None
}
///|
fn run_hot_pipeline_dae_rewrite_loop_iteration_alias(
instrs : Array[@lib.Instruction],
source : @lib.LocalIdx,
iteration_alias : @lib.LocalIdx,
) -> Array[@lib.Instruction] {
let out : Array[@lib.Instruction] = []
let mut idx = 0
while idx < instrs.length() {
if idx + 1 < instrs.length() {
match (instrs[idx], instrs[idx + 1]) {
(@lib.LocalGet(get_source), @lib.LocalTee(get_alias)) if get_source ==
source &&
get_alias == iteration_alias => {
out.push(@lib.Instruction::local_get(source))
idx += 2
continue
}
_ => ()
}
}
let rewritten = match instrs[idx] {
@lib.LocalGet(readback) if readback == iteration_alias =>
@lib.Instruction::local_get(source)
@lib.Block(block_type, @lib.Expr(body)) =>
@lib.Instruction::block(
block_type,
@lib.Expr::new(
run_hot_pipeline_dae_rewrite_loop_iteration_alias(
body, source, iteration_alias,
),
),
)
@lib.Loop(block_type, @lib.Expr(body)) =>
@lib.Instruction::loop_(
block_type,
@lib.Expr::new(
run_hot_pipeline_dae_rewrite_loop_iteration_alias(
body, source, iteration_alias,
),
),
)
@lib.TryTable(block_type, catches, @lib.Expr(body)) =>
@lib.Instruction::try_table(
block_type,
catches,
@lib.Expr::new(
run_hot_pipeline_dae_rewrite_loop_iteration_alias(
body, source, iteration_alias,
),
),
)
@lib.If(block_type, then_body, else_body) =>
@lib.Instruction::if_(
block_type,
run_hot_pipeline_dae_rewrite_loop_iteration_alias(
then_body, source, iteration_alias,
),
match else_body {
Some(body) =>
Some(
run_hot_pipeline_dae_rewrite_loop_iteration_alias(
body, source, iteration_alias,
),
)
None => None
},
)
instr => instr
}
out.push(rewritten)
idx += 1
}
out
}
///|
fn run_hot_pipeline_dae_try_coalesce_loop_iteration_alias_body(
body : Array[@lib.Instruction],
local_gets : Array[Int32],
local_sets : Array[Int32],
local_tees : Array[Int32],
) -> Array[@lib.Instruction]? {
if body.length() < 2 || !(body[body.length() - 1] is @lib.Br(_)) {
return None
}
let source = match body[body.length() - 2] {
@lib.LocalSet(source) => source
_ => return None
}
let prefix : Array[@lib.Instruction] = []
for idx = 0; idx + 2 < body.length(); idx = idx + 1 {
prefix.push(body[idx])
}
let (get_source, alias_local) = match
run_hot_pipeline_dae_find_loop_iteration_alias(prefix) {
Some(pair) => pair
None => return None
}
if get_source != source {
return None
}
let source_idx = dae_local_idx_slot(source)
let alias_idx = dae_local_idx_slot(alias_local)
for prefix_instr in prefix {
if dae_instr_writes_local(prefix_instr, source) {
return None
}
}
if source_idx < 0 ||
alias_idx < 0 ||
source_idx >= local_gets.length() ||
source_idx >= local_sets.length() ||
source_idx >= local_tees.length() ||
alias_idx >= local_gets.length() ||
alias_idx >= local_sets.length() ||
alias_idx >= local_tees.length() ||
local_gets[alias_idx] != 2 ||
local_sets[alias_idx] != 0 ||
local_tees[alias_idx] != 1 ||
local_sets[source_idx] != 2 ||
local_tees[source_idx] != 0 {
return None
}
let rewritten = run_hot_pipeline_dae_rewrite_loop_iteration_alias(
prefix, source, alias_local,
)
rewritten.push(body[body.length() - 2])
rewritten.push(body[body.length() - 1])
local_gets[source_idx] += local_gets[alias_idx]
local_gets[alias_idx] = 0
local_tees[alias_idx] = 0
Some(rewritten)
}
///|
fn run_hot_pipeline_dae_coalesce_loop_iteration_aliases_instrs(
instrs : Array[@lib.Instruction],
local_gets : Array[Int32],
local_sets : Array[Int32],
local_tees : Array[Int32],
) -> (Array[@lib.Instruction], Int32) {
let out : Array[@lib.Instruction] = []
let mut rewrite_count = 0
for instr in instrs {
let rewritten = match instr {
@lib.Block(block_type, @lib.Expr(body)) => {
let (next_body, nested_count) = run_hot_pipeline_dae_coalesce_loop_iteration_aliases_instrs(
body, local_gets, local_sets, local_tees,
)
rewrite_count += nested_count
match
run_hot_pipeline_dae_try_coalesce_loop_iteration_alias_body(
next_body, local_gets, local_sets, local_tees,
) {
Some(rewritten_body) => {
rewrite_count += 1
@lib.Instruction::block(block_type, @lib.Expr::new(rewritten_body))
}
None => @lib.Instruction::block(block_type, @lib.Expr::new(next_body))
}
}
@lib.Loop(block_type, @lib.Expr(body)) => {
let (next_body, nested_count) = run_hot_pipeline_dae_coalesce_loop_iteration_aliases_instrs(
body, local_gets, local_sets, local_tees,
)
rewrite_count += nested_count
match
run_hot_pipeline_dae_try_coalesce_loop_iteration_alias_body(
next_body, local_gets, local_sets, local_tees,
) {
Some(rewritten_body) => {
rewrite_count += 1
@lib.Instruction::loop_(block_type, @lib.Expr::new(rewritten_body))
}
None => @lib.Instruction::loop_(block_type, @lib.Expr::new(next_body))
}
}
@lib.TryTable(block_type, catches, @lib.Expr(body)) => {
let (next_body, nested_count) = run_hot_pipeline_dae_coalesce_loop_iteration_aliases_instrs(
body, local_gets, local_sets, local_tees,
)
rewrite_count += nested_count
@lib.Instruction::try_table(
block_type,
catches,
@lib.Expr::new(next_body),
)
}
@lib.If(block_type, then_body, else_body) => {
let (next_then, then_count) = run_hot_pipeline_dae_coalesce_loop_iteration_aliases_instrs(
then_body, local_gets, local_sets, local_tees,
)
rewrite_count += then_count
let next_else = match else_body {
Some(body) => {
let (next_body, else_count) = run_hot_pipeline_dae_coalesce_loop_iteration_aliases_instrs(
body, local_gets, local_sets, local_tees,
)
rewrite_count += else_count
Some(next_body)
}
None => None
}
@lib.Instruction::if_(block_type, next_then, next_else)
}
instr => instr
}
out.push(rewritten)
}
(out, rewrite_count)
}
///|
fn run_hot_pipeline_dae_replace_leading_block_local_get(
instr : @lib.Instruction,
target : @lib.LocalIdx,
replacement : Array[@lib.Instruction],
) -> @lib.Instruction? {
match instr {
@lib.Block(block_type, @lib.Expr(body)) if body.length() > 0 => {
let mut leading_idx = 0
while leading_idx < body.length() &&
dae_instr_is_materializable_const(body[leading_idx]) {
leading_idx += 1
}
if leading_idx >= body.length() {
return None
}
match body[leading_idx] {
@lib.LocalGet(readback) if readback == target => {
let next_body : Array[@lib.Instruction] = []
for idx = 0; idx < leading_idx; idx = idx + 1 {
next_body.push(body[idx])
}
for producer_instr in replacement {
next_body.push(producer_instr)
}
for idx = leading_idx + 1; idx < body.length(); idx = idx + 1 {
next_body.push(body[idx])
}
Some(@lib.Instruction::block(block_type, @lib.Expr::new(next_body)))
}
first =>
match
run_hot_pipeline_dae_replace_leading_block_local_get(
first, target, replacement,
) {
Some(rewritten_first) => {
let next_body = body.copy()
next_body[leading_idx] = rewritten_first
Some(
@lib.Instruction::block(block_type, @lib.Expr::new(next_body)),
)
}
None => None
}
}
}
_ => None
}
}
///|
fn run_hot_pipeline_dae_sink_single_use_local_spills_instrs(
instrs : Array[@lib.Instruction],
local_gets : Array[Int32],
local_sets : Array[Int32],
local_tees : Array[Int32],
mod_ : @lib.Module,
func_type_indices : Array[@lib.TypeIdx],
) -> (Array[@lib.Instruction], Int32) {
let normalized : Array[@lib.Instruction] = []
let mut rewrite_count = 0
for instr in instrs {
let rewritten = match instr {
@lib.Block(block_type, @lib.Expr(body)) => {
let (next_body, nested_count) = run_hot_pipeline_dae_sink_single_use_local_spills_instrs(
body, local_gets, local_sets, local_tees, mod_, func_type_indices,
)
rewrite_count += nested_count
@lib.Instruction::block(block_type, @lib.Expr::new(next_body))
}
@lib.Loop(block_type, @lib.Expr(body)) => {
let (next_body, nested_count) = run_hot_pipeline_dae_sink_single_use_local_spills_instrs(
body, local_gets, local_sets, local_tees, mod_, func_type_indices,
)
rewrite_count += nested_count
@lib.Instruction::loop_(block_type, @lib.Expr::new(next_body))
}
@lib.TryTable(block_type, catches, @lib.Expr(body)) => {
let (next_body, nested_count) = run_hot_pipeline_dae_sink_single_use_local_spills_instrs(
body, local_gets, local_sets, local_tees, mod_, func_type_indices,
)
rewrite_count += nested_count
@lib.Instruction::try_table(
block_type,
catches,
@lib.Expr::new(next_body),
)
}
@lib.If(block_type, then_body, else_body) => {
let (next_then, then_count) = run_hot_pipeline_dae_sink_single_use_local_spills_instrs(
then_body, local_gets, local_sets, local_tees, mod_, func_type_indices,
)
rewrite_count += then_count
let next_else = match else_body {
Some(body) => {
let (next_body, else_count) = run_hot_pipeline_dae_sink_single_use_local_spills_instrs(
body, local_gets, local_sets, local_tees, mod_, func_type_indices,
)
rewrite_count += else_count
Some(next_body)
}
None => None
}
@lib.Instruction::if_(block_type, next_then, next_else)
}
instr => instr
}
normalized.push(rewritten)
}
let out : Array[@lib.Instruction] = []
let mut idx = 0
while idx < normalized.length() {
match normalized[idx] {
@lib.LocalSet(@lib.LocalIdx(raw) as target) => {
let target_idx = raw.reinterpret_as_int()
if target_idx >= 0 &&
target_idx < local_gets.length() &&
target_idx < local_sets.length() &&
target_idx < local_tees.length() &&
local_gets[target_idx] == 1 &&
local_sets[target_idx] == 1 &&
local_tees[target_idx] == 0 {
let shape = match
dae_find_value_slice(normalized, idx, 0, mod_, func_type_indices) {
Some((shape, _, _)) if shape.start >= 0 && shape.start < idx =>
Some(shape)
_ => None
}
match shape {
Some(shape) => {
let replacement : Array[@lib.Instruction] = []
for slice_idx = shape.start
slice_idx < idx
slice_idx = slice_idx + 1 {
replacement.push(normalized[slice_idx])
}
let mut movable_under_block = true
for producer_instr in replacement {
if dae_instr_has_escaping_branch(producer_instr, 0) {
movable_under_block = false
break
}
}
if movable_under_block && idx + 1 < normalized.length() {
match
run_hot_pipeline_dae_replace_leading_block_local_get(
normalized[idx + 1],
target,
replacement,
) {
Some(rewritten_block) => {
while out.length() > shape.start {
ignore(out.pop())
}
out.push(rewritten_block)
local_gets[target_idx] = 0
local_sets[target_idx] = 0
for suffix_idx = idx + 2
suffix_idx < normalized.length()
suffix_idx = suffix_idx + 1 {
out.push(normalized[suffix_idx])
}
return (out, rewrite_count + 1)
}
None => ()
}
}
let mut read_idx = idx + 1
while read_idx < normalized.length() && read_idx <= idx + 6 {
match normalized[read_idx] {
@lib.LocalGet(readback) if readback == target => break
instr if dae_instr_is_materializable_const(instr) =>
read_idx += 1
_ => {
read_idx = normalized.length()
break
}
}
}
if read_idx < normalized.length() && read_idx <= idx + 6 {
match normalized[read_idx] {
@lib.LocalGet(readback) if readback == target => {
while out.length() > shape.start {
ignore(out.pop())
}
for gap_idx = idx + 1
gap_idx < read_idx
gap_idx = gap_idx + 1 {
out.push(normalized[gap_idx])
}
for producer_instr in replacement {
out.push(producer_instr)
}
local_gets[target_idx] = 0
local_sets[target_idx] = 0
for suffix_idx = read_idx + 1
suffix_idx < normalized.length()
suffix_idx = suffix_idx + 1 {
out.push(normalized[suffix_idx])
}
return (out, rewrite_count + 1)
}
_ => ()
}
}
}
None => ()
}
}
}
_ => ()
}
out.push(normalized[idx])
idx += 1
}
(out, rewrite_count)
}
///|
fn run_hot_pipeline_dae_copy_retarget_is_control_boundary(
instr : @lib.Instruction,
) -> Bool {
if run_hot_pipeline_raw_simplify_locals_instr_is_structured(instr) ||
run_hot_pipeline_raw_simplify_locals_instr_is_early_terminator(instr) {
return true
}
match instr {
@lib.BrOnNull(_)
| @lib.BrOnNonNull(_)
| @lib.BrOnCast(_, _, _, _)
| @lib.BrOnCastFail(_, _, _, _)
| @lib.BrOnCastDescEq(_, _, _)
| @lib.BrOnCastDescEqFail(_, _, _) => true
_ => false
}
}
///|
fn run_hot_pipeline_dae_retarget_copy_producers_once(
instrs : Array[@lib.Instruction],
local_types : Array[@lib.ValType],
param_count : Int32,
local_gets : Array[Int32],
local_sets : Array[Int32],
local_tees : Array[Int32],
) -> (Array[@lib.Instruction], Bool) {
for idx = 0; idx < instrs.length(); idx = idx + 1 {
let nested = match instrs[idx] {
@lib.Block(block_type, @lib.Expr(body)) => {
let (next_body, changed) = run_hot_pipeline_dae_retarget_copy_producers_once(
body, local_types, param_count, local_gets, local_sets, local_tees,
)
if changed {
Some(@lib.Instruction::block(block_type, @lib.Expr::new(next_body)))
} else {
None
}
}
@lib.Loop(block_type, @lib.Expr(body)) => {
let (next_body, changed) = run_hot_pipeline_dae_retarget_copy_producers_once(
body, local_types, param_count, local_gets, local_sets, local_tees,
)
if changed {
Some(@lib.Instruction::loop_(block_type, @lib.Expr::new(next_body)))
} else {
None
}
}
@lib.TryTable(block_type, catches, @lib.Expr(body)) => {
let (next_body, changed) = run_hot_pipeline_dae_retarget_copy_producers_once(
body, local_types, param_count, local_gets, local_sets, local_tees,
)
if changed {
Some(
@lib.Instruction::try_table(
block_type,
catches,
@lib.Expr::new(next_body),
),
)
} else {
None
}
}
@lib.If(block_type, then_body, else_body) => {
let (next_then, then_changed) = run_hot_pipeline_dae_retarget_copy_producers_once(
then_body, local_types, param_count, local_gets, local_sets, local_tees,
)
if then_changed {
Some(@lib.Instruction::if_(block_type, next_then, else_body))
} else {
match else_body {
Some(body) => {
let (next_body, else_changed) = run_hot_pipeline_dae_retarget_copy_producers_once(
body, local_types, param_count, local_gets, local_sets, local_tees,
)
if else_changed {
Some(
@lib.Instruction::if_(block_type, then_body, Some(next_body)),
)
} else {
None
}
}
None => None
}
}
}
_ => None
}
match nested {
Some(rewritten_instr) => {
let rewritten = instrs.copy()
rewritten[idx] = rewritten_instr
return (rewritten, true)
}
None => ()
}
}
let mut copy_idx = 0
while copy_idx + 1 < instrs.length() {
match (instrs[copy_idx], instrs[copy_idx + 1]) {
(
@lib.LocalGet(@lib.LocalIdx(source_raw)),
@lib.LocalSet(@lib.LocalIdx(destination_raw)),
) => {
let source = source_raw.reinterpret_as_int()
let destination = destination_raw.reinterpret_as_int()
if source >= param_count &&
destination >= param_count &&
source >= 0 &&
destination >= 0 &&
source != destination &&
source < local_types.length() &&
destination < local_types.length() &&
source < local_gets.length() &&
source < local_sets.length() &&
source < local_tees.length() &&
local_types[source] == local_types[destination] &&
local_gets[source] == 1 &&
local_sets[source] == 1 &&
local_tees[source] == 0 {
let mut producer_idx = copy_idx - 1
let mut blocked = false
while producer_idx >= 0 {
let candidate = instrs[producer_idx]
if run_hot_pipeline_dae_copy_retarget_is_control_boundary(candidate) {
blocked = true
break
}
match candidate {
@lib.LocalSet(@lib.LocalIdx(raw)) if raw.reinterpret_as_int() ==
source => break
_ =>
if dae_instr_references_local(
candidate,
@lib.LocalIdx::new(destination.reinterpret_as_uint()),
) {
blocked = true
break
}
}
producer_idx -= 1
}
if !blocked && producer_idx >= 0 {
let rewritten : Array[@lib.Instruction] = []
for idx = 0; idx < instrs.length(); idx = idx + 1 {
if idx == producer_idx {
rewritten.push(
@lib.Instruction::local_set(
@lib.LocalIdx::new(destination.reinterpret_as_uint()),
),
)
} else if idx != copy_idx && idx != copy_idx + 1 {
rewritten.push(instrs[idx])
}
}
return (rewritten, true)
}
}
}
_ => ()
}
copy_idx += 1
}
(instrs, false)
}
///|
fn run_hot_pipeline_dae_retarget_copy_producers_instrs(
instrs : Array[@lib.Instruction],
local_types : Array[@lib.ValType],
param_count~ : Int32 = 0,
) -> (Array[@lib.Instruction], Int32) {
let mut current = instrs
let mut rewrite_count = 0
let max_rounds = local_types.length()
while rewrite_count < max_rounds {
let local_gets : Array[Int32] = Array::make(local_types.length(), 0)
let local_sets : Array[Int32] = Array::make(local_types.length(), 0)
let local_tees : Array[Int32] = Array::make(local_types.length(), 0)
run_hot_pipeline_raw_collect_local_ref_counts(
current, local_gets, local_sets, local_tees,
)
let (next, changed) = run_hot_pipeline_dae_retarget_copy_producers_once(
current, local_types, param_count, local_gets, local_sets, local_tees,
)
if !changed {
break
}
current = next
rewrite_count += 1
}
(current, rewrite_count)
}
///|
fn run_hot_pipeline_dae_sink_global_nonnull_call_args_instrs(
instrs : Array[@lib.Instruction],
total_local_count : Int32,
param_count : Int32,
) -> (Array[@lib.Instruction], Int32) {
if total_local_count <= param_count {
return (instrs, 0)
}
let local_gets : Array[Int32] = Array::make(total_local_count, 0)
let local_sets : Array[Int32] = Array::make(total_local_count, 0)
let local_tees : Array[Int32] = Array::make(total_local_count, 0)
run_hot_pipeline_raw_collect_local_ref_counts(
instrs, local_gets, local_sets, local_tees,
)
let rewritten : Array[@lib.Instruction] = []
let mut rewrite_count = 0
let mut idx = 0
while idx < instrs.length() {
if idx + 5 < instrs.length() {
match
(
instrs[idx],
instrs[idx + 1],
instrs[idx + 2],
instrs[idx + 3],
instrs[idx + 4],
instrs[idx + 5],
) {
(
@lib.GlobalGet(global_idx),
@lib.RefAsNonNull,
@lib.LocalSet(@lib.LocalIdx(temp_raw)),
@lib.LocalGet(argument_idx),
@lib.LocalGet(@lib.LocalIdx(read_raw)),
@lib.Call(call_idx),
) => {
let temp_idx = temp_raw.reinterpret_as_int()
let argument_raw = match argument_idx {
@lib.LocalIdx(raw) => raw.reinterpret_as_int()
}
if temp_raw == read_raw &&
temp_idx >= param_count &&
temp_idx < total_local_count &&
argument_raw >= 0 &&
argument_raw < total_local_count &&
argument_raw != temp_idx &&
local_gets[temp_idx] == 1 &&
local_sets[temp_idx] == 1 &&
local_tees[temp_idx] == 0 {
rewritten.push(@lib.Instruction::local_get(argument_idx))
rewritten.push(@lib.Instruction::global_get(global_idx))
rewritten.push(@lib.Instruction::ref_as_non_null())
rewritten.push(@lib.Instruction::call(call_idx))
rewrite_count += 1
idx += 6
continue
}
}
_ => ()
}
}
rewritten.push(instrs[idx])
idx += 1
}
(rewritten, rewrite_count)
}
///|
fn run_hot_pipeline_dae_fold_ref_null_is_null_if_instrs(
instrs : Array[@lib.Instruction],
) -> (Array[@lib.Instruction], Int32) {
let rewritten : Array[@lib.Instruction] = []
let mut rewrite_count = 0
let mut idx = 0
while idx < instrs.length() {
if idx + 2 < instrs.length() {
match (instrs[idx], instrs[idx + 1], instrs[idx + 2]) {
(@lib.RefNull(_), @lib.RefIsNull, @lib.If(_, then_body, Some(_))) => {
for instr in then_body {
rewritten.push(instr)
}
rewrite_count += 1
idx += 3
continue
}
_ => ()
}
}
rewritten.push(instrs[idx])
idx += 1
}
(rewritten, rewrite_count)
}
///|
fn run_hot_pipeline_dae_remove_final_return_instrs(
instrs : Array[@lib.Instruction],
) -> (Array[@lib.Instruction], Int32) {
if instrs.length() == 0 {
return (instrs, 0)
}
match instrs[instrs.length() - 1] {
@lib.Return => {
ignore(instrs.pop())
(instrs, 1)
}
_ => (instrs, 0)
}
}
///|
fn run_hot_pipeline_dae_drop_selected_local_sets_instrs(
instrs : Array[@lib.Instruction],
drop_locals : Array[Bool],
) -> (Array[@lib.Instruction], Int32) {
let rewritten : Array[@lib.Instruction] = []
let mut rewrite_count = 0
for instr in instrs {
match instr {
@lib.LocalSet(@lib.LocalIdx(raw)) => {
let local_idx = raw.reinterpret_as_int()
if local_idx >= 0 &&
local_idx < drop_locals.length() &&
drop_locals[local_idx] {
rewritten.push(@lib.Instruction::drop())
rewrite_count += 1
} else {
rewritten.push(instr)
}
}
@lib.Block(block_type, @lib.Expr(body)) => {
let (next_body, nested_count) = run_hot_pipeline_dae_drop_selected_local_sets_instrs(
body, drop_locals,
)
rewritten.push(
@lib.Instruction::block(block_type, @lib.Expr::new(next_body)),
)
rewrite_count += nested_count
}
@lib.Loop(block_type, @lib.Expr(body)) => {
let (next_body, nested_count) = run_hot_pipeline_dae_drop_selected_local_sets_instrs(
body, drop_locals,
)
rewritten.push(
@lib.Instruction::loop_(block_type, @lib.Expr::new(next_body)),
)
rewrite_count += nested_count
}
@lib.TryTable(block_type, catches, @lib.Expr(body)) => {
let (next_body, nested_count) = run_hot_pipeline_dae_drop_selected_local_sets_instrs(
body, drop_locals,
)
rewritten.push(
@lib.Instruction::try_table(
block_type,
catches,
@lib.Expr::new(next_body),
),
)
rewrite_count += nested_count
}
@lib.If(block_type, then_body, else_body) => {
let (next_then, then_count) = run_hot_pipeline_dae_drop_selected_local_sets_instrs(
then_body, drop_locals,
)
let next_else = match else_body {
Some(body) => {
let (next_body, else_count) = run_hot_pipeline_dae_drop_selected_local_sets_instrs(
body, drop_locals,
)
rewrite_count += else_count
Some(next_body)
}
None => None
}
rewritten.push(@lib.Instruction::if_(block_type, next_then, next_else))
rewrite_count += then_count
}
_ => rewritten.push(instr)
}
}
(rewritten, rewrite_count)
}
///|
fn run_hot_pipeline_dae_drop_two_write_never_read_locals_instrs(
instrs : Array[@lib.Instruction],
total_local_count : Int32,
param_count : Int32,
) -> (Array[@lib.Instruction], Int32) {
if total_local_count <= param_count {
return (instrs, 0)
}
let local_gets : Array[Int32] = Array::make(total_local_count, 0)
let local_sets : Array[Int32] = Array::make(total_local_count, 0)
let local_tees : Array[Int32] = Array::make(total_local_count, 0)
run_hot_pipeline_raw_collect_local_ref_counts(
instrs, local_gets, local_sets, local_tees,
)
let drop_locals = Array::make(total_local_count, false)
let mut candidate_count = 0
for local_idx = param_count
local_idx < total_local_count
local_idx = local_idx + 1 {
if local_gets[local_idx] == 0 &&
local_sets[local_idx] == 2 &&
local_tees[local_idx] == 0 {
drop_locals[local_idx] = true
candidate_count += 1
}
}
if candidate_count == 0 {
return (instrs, 0)
}
run_hot_pipeline_dae_drop_selected_local_sets_instrs(instrs, drop_locals)
}
///|
fn run_hot_pipeline_dae_remove_nops_instrs(
instrs : Array[@lib.Instruction],
) -> (Array[@lib.Instruction], Int32) {
let rewritten : Array[@lib.Instruction] = []
let mut removed = 0
for instr in instrs {
match instr {
@lib.Nop => removed += 1
@lib.Block(block_type, @lib.Expr(body)) => {
let (next_body, nested_removed) = run_hot_pipeline_dae_remove_nops_instrs(
body,
)
removed += nested_removed
rewritten.push(
@lib.Instruction::block(block_type, @lib.Expr::new(next_body)),
)
}
@lib.Loop(block_type, @lib.Expr(body)) => {
let (next_body, nested_removed) = run_hot_pipeline_dae_remove_nops_instrs(
body,
)
removed += nested_removed
rewritten.push(
@lib.Instruction::loop_(block_type, @lib.Expr::new(next_body)),
)
}
@lib.TryTable(block_type, catches, @lib.Expr(body)) => {
let (next_body, nested_removed) = run_hot_pipeline_dae_remove_nops_instrs(
body,
)
removed += nested_removed
rewritten.push(
@lib.Instruction::try_table(
block_type,
catches,
@lib.Expr::new(next_body),
),
)
}
@lib.If(block_type, then_body, else_body) => {
let (next_then, then_removed) = run_hot_pipeline_dae_remove_nops_instrs(
then_body,
)
removed += then_removed
let next_else = match else_body {
Some(body) => {
let (next_body, else_removed) = run_hot_pipeline_dae_remove_nops_instrs(
body,
)
removed += else_removed
Some(next_body)
}
None => None
}
rewritten.push(@lib.Instruction::if_(block_type, next_then, next_else))
}
_ => rewritten.push(instr)
}
}
(rewritten, removed)
}
///|
#warnings("-unused_value")
fn run_hot_pipeline_dae_broad_high_structural_cleanup(
mod_ : @lib.Module,
def_idx : Int32,
) -> (@lib.Module, Int32) {
let funcs = match mod_.code_sec {
Some(@lib.CodeSec(funcs)) => funcs
None => return (mod_, 0)
}
let func = match funcs.get(def_idx) {
Some(func) => func
None => return (mod_, 0)
}
let @lib.Func(locals, @lib.Expr(body)) = func
let func_idx = @lib.FuncIdx::new(
(dae_imported_func_count(mod_) + def_idx).reinterpret_as_uint(),
)
let params = match
run_hot_pipeline_defined_func_params_from_sections(mod_, func_idx) {
Some(params) => params
None => return (mod_, 0)
}
let total_local_count = params.length() + locals.length()
let local_types = params.copy()
for local_idx = 0; local_idx < locals.length(); local_idx = local_idx + 1 {
local_types.push(locals.get(local_idx))
}
let local_gets : Array[Int32] = Array::make(total_local_count, 0)
let local_sets : Array[Int32] = Array::make(total_local_count, 0)
let local_tees : Array[Int32] = Array::make(total_local_count, 0)
run_hot_pipeline_raw_collect_local_ref_counts(
body, local_gets, local_sets, local_tees,
)
let (pair_guard_body, pair_guard_count) = run_hot_pipeline_dae_flatten_pair_null_guard_blocks_instrs(
body,
)
let (zero_eq_body, zero_eq_count) = run_hot_pipeline_dae_fold_i32_zero_eq_instrs(
pair_guard_body,
)
// DAE can materialize a removed nullable parameter as ref.null. Its exact
// adjacent ref.is_null condition is always true, so retain only the typed
// then arm without moving any surviving producer or effect.
let (null_guard_body, null_guard_count) = run_hot_pipeline_dae_fold_ref_null_is_null_if_instrs(
zero_eq_body,
)
let (branch_result_body, branch_result_count) = run_hot_pipeline_dae_fold_branch_result_if_blocks_instrs(
null_guard_body, local_gets, local_sets, local_tees,
)
let (voided_body, voided_block_count, removed_payload_count) = run_hot_pipeline_dae_void_dropped_i32_blocks_instrs(
branch_result_body,
)
let (next_body, flattened_count) = run_hot_pipeline_dae_flatten_tail_self_branch_blocks_instrs(
voided_body,
)
let mut structural_count = pair_guard_count +
zero_eq_count +
null_guard_count +
branch_result_count +
voided_block_count +
removed_payload_count +
flattened_count
let func_type_indices = dae_collect_func_type_indices(mod_)
let mut sunk_body = next_body
for sink_round = 0; sink_round < 32; sink_round = sink_round + 1 {
let total_local_count = params.length() + locals.length()
let local_gets : Array[Int32] = Array::make(total_local_count, 0)
let local_sets : Array[Int32] = Array::make(total_local_count, 0)
let local_tees : Array[Int32] = Array::make(total_local_count, 0)
run_hot_pipeline_raw_collect_local_ref_counts(
sunk_body, local_gets, local_sets, local_tees,
)
// Sink this exact single-use non-null global call argument only across the
// other argument's pure local.get. The possibly trapping ref.as_non_null
// remains before the call and crosses no effectful or trapping instruction.
let (global_arg_body, global_arg_count) = run_hot_pipeline_dae_sink_global_nonnull_call_args_instrs(
sunk_body,
total_local_count,
params.length(),
)
if global_arg_count > 0 {
sunk_body = global_arg_body
structural_count += global_arg_count
continue
}
let (aliased_body, alias_count) = run_hot_pipeline_dae_coalesce_loop_iteration_aliases_instrs(
sunk_body, local_gets, local_sets, local_tees,
)
let (next, sunk_count) = run_hot_pipeline_dae_sink_single_use_local_spills_instrs(
aliased_body, local_gets, local_sets, local_tees, mod_, func_type_indices,
)
if alias_count + sunk_count == 0 {
break
}
sunk_body = next
structural_count += alias_count + sunk_count
}
// Binaryen SimplifyLocals retargets one-set/one-get copy producers before
// later vacuum/compaction. Keep every producer in place: only redirect its
// same-typed local.set and delete the later flat get/set copy pair, rejecting
// destination accesses and all structured or terminal boundaries.
let (retargeted_body, retargeted_copy_count) = run_hot_pipeline_dae_retarget_copy_producers_instrs(
sunk_body,
local_types,
param_count=params.length(),
)
sunk_body = retargeted_body
structural_count += retargeted_copy_count
// The same Binaryen SimplifyLocals probe replaces the exact two writes to a
// never-read result local with drops. Preserve each producer's evaluation,
// effects, and traps while exposing the now-unused declaration to the
// existing selected compaction step.
let (dropped_write_body, dropped_write_count) = run_hot_pipeline_dae_drop_two_write_never_read_locals_instrs(
sunk_body,
total_local_count,
params.length(),
)
sunk_body = dropped_write_body
structural_count += dropped_write_count
// Binaryen's filtered optimizeAfterInlining suffix ends in vacuum. Earlier
// selected coalescing intentionally leaves nop placeholders to preserve raw
// instruction positions; once all structural rewrites are complete those
// placeholders carry no values, effects, traps, labels, or local accesses.
let (denopped_body, removed_nop_count) = run_hot_pipeline_dae_remove_nops_instrs(
sunk_body,
)
sunk_body = denopped_body
structural_count += removed_nop_count
// A top-level final return is equivalent to the function body's implicit
// return. Remove only that exact trailing instruction; nested and nonfinal
// returns retain their control semantics.
let (without_final_return, removed_final_return_count) = run_hot_pipeline_dae_remove_final_return_instrs(
sunk_body,
)
sunk_body = without_final_return
structural_count += removed_final_return_count
if structural_count == 0 {
return (mod_, 0)
}
let shaped_func = @lib.Func::new(locals, @lib.Expr::new(sunk_body))
let (cleaned_func, _) = run_hot_pipeline_simplify_locals_cleanup_exact_func(
shaped_func,
)
let (compacted_func, _) = run_hot_pipeline_raw_compact_unused_locals_after_prefix(
cleaned_func, params, 0,
)
let next_funcs = funcs.copy()
next_funcs[def_idx] = compacted_func
let candidate = mod_
.with_code_sec(@lib.CodeSec::new(next_funcs))
.without_name_sec()
(candidate, structural_count)
}
///|
#warnings("-unused_value")
fn run_hot_pipeline_dae_broad_high_coalesce_linear_subset(
mod_ : @lib.Module,
def_idx : Int32,
) -> (@lib.Module, Int32) {
let funcs = match mod_.code_sec {
Some(@lib.CodeSec(funcs)) => funcs
None => return (mod_, 0)
}
let func = match funcs.get(def_idx) {
Some(func) => func
None => return (mod_, 0)
}
let func_idx = @lib.FuncIdx::new(
(dae_imported_func_count(mod_) + def_idx).reinterpret_as_uint(),
)
let params = match
run_hot_pipeline_defined_func_params_from_sections(mod_, func_idx) {
Some(params) => params
None => return (mod_, 0)
}
// Reuse coalesce-locals' conservative linear fallback directly. It keeps
// read locals distinct except for exact same-typed one-set/one-get/no-tee
// copy-through chains with no early destination access or intervening
// structured/terminal boundary, and it shares only same-typed body locals
// that are written but never read. Unlike dense structured coloring, this
// stays bounded and does not recolor unrelated live locals in the large body.
let rewrite = cl_rewrite_loop_func_unused_locals(func, params)
if !rewrite.changed {
return (mod_, 0)
}
let @lib.Func(locals, _) = func
let @lib.Func(rewritten_locals, _) = rewrite.func
let next_funcs = funcs.copy()
next_funcs[def_idx] = rewrite.func
let removed = if rewritten_locals.length() < locals.length() {
locals.length() - rewritten_locals.length()
} else {
0
}
(
mod_.with_code_sec(@lib.CodeSec::new(next_funcs)).without_name_sec(),
removed,
)
}
///|
#warnings("-unused_value")
fn run_hot_pipeline_dae_broad_high_dead_write_cleanup(
mod_ : @lib.Module,
def_idx : Int32,
) -> (@lib.Module, Int32) {
let (candidate, removed_tee_count, _) = run_hot_pipeline_dae_remove_dead_tees_and_compact_selected_locals(
mod_, def_idx,
)
if removed_tee_count == 0 {
return (mod_, 0)
}
(candidate, removed_tee_count)
}
///|
#warnings("-unused_value")
fn run_hot_pipeline_dae_broad_high_compact_selected_locals(
mod_ : @lib.Module,
def_idx : Int32,
) -> (@lib.Module, Int32) {
let funcs = match mod_.code_sec {
Some(@lib.CodeSec(funcs)) => funcs
None => return (mod_, 0)
}
let func = match funcs.get(def_idx) {
Some(func) => func
None => return (mod_, 0)
}
let @lib.Func(locals, _) = func
let func_idx = @lib.FuncIdx::new(
(dae_imported_func_count(mod_) + def_idx).reinterpret_as_uint(),
)
let params = match
run_hot_pipeline_defined_func_params_from_sections(mod_, func_idx) {
Some(params) => params
None => return (mod_, 0)
}
let (compacted_func, compacted) = run_hot_pipeline_raw_compact_unused_locals_after_prefix(
func, params, 0,
)
if !compacted {
return (mod_, 0)
}
let @lib.Func(compacted_locals, _) = compacted_func
let next_funcs = funcs.copy()
next_funcs[def_idx] = compacted_func
(
mod_.with_code_sec(@lib.CodeSec::new(next_funcs)).without_name_sec(),
locals.length() - compacted_locals.length(),
)
}
///|
#warnings("-unused_value")
fn run_hot_pipeline_dae_bounded_instr_count(
instrs : Array[@lib.Instruction],
limit : Int32,
) -> Int32 {
let mut count = 0
for instr in instrs {
count += 1
if count > limit {
return count
}
let nested_count = match instr {
@lib.Block(_, @lib.Expr(body))
| @lib.Loop(_, @lib.Expr(body))
| @lib.TryTable(_, _, @lib.Expr(body)) =>
run_hot_pipeline_dae_bounded_instr_count(body, limit - count)
@lib.If(_, then_body, else_body) => {
let then_count = run_hot_pipeline_dae_bounded_instr_count(
then_body,
limit - count,
)
if count + then_count > limit {
then_count
} else {
let else_count = match else_body {
Some(body) =>
run_hot_pipeline_dae_bounded_instr_count(
body,
limit - count - then_count,
)
None => 0
}
then_count + else_count
}
}
_ => 0
}
count += nested_count
if count > limit {
return count
}
}
count
}
///|
fn run_hot_pipeline_dae_remove_never_read_local_tees_with_counts(
instrs : Array[@lib.Instruction],
local_gets : Array[Int32],
) -> (Array[@lib.Instruction], Int32) {
let rewritten : Array[@lib.Instruction] = []
let mut removed_count = 0
for instr in instrs {
match instr {
@lib.LocalTee(@lib.LocalIdx(raw)) => {
let local_idx = raw.reinterpret_as_int()
if local_idx >= 0 &&
local_idx < local_gets.length() &&
local_gets[local_idx] == 0 {
removed_count = removed_count + 1
} else {
rewritten.push(instr)
}
}
@lib.Block(block_type, @lib.Expr(body)) => {
let (next_body, nested_count) = run_hot_pipeline_dae_remove_never_read_local_tees_with_counts(
body, local_gets,
)
rewritten.push(
@lib.Instruction::block(block_type, @lib.Expr::new(next_body)),
)
removed_count = removed_count + nested_count
}
@lib.Loop(block_type, @lib.Expr(body)) => {
let (next_body, nested_count) = run_hot_pipeline_dae_remove_never_read_local_tees_with_counts(
body, local_gets,
)
rewritten.push(
@lib.Instruction::loop_(block_type, @lib.Expr::new(next_body)),
)
removed_count = removed_count + nested_count
}
@lib.TryTable(block_type, catches, @lib.Expr(body)) => {
let (next_body, nested_count) = run_hot_pipeline_dae_remove_never_read_local_tees_with_counts(
body, local_gets,
)
rewritten.push(
@lib.Instruction::try_table(
block_type,
catches,
@lib.Expr::new(next_body),
),
)
removed_count = removed_count + nested_count
}
@lib.If(block_type, then_body, else_body) => {
let (next_then, then_count) = run_hot_pipeline_dae_remove_never_read_local_tees_with_counts(
then_body, local_gets,
)
removed_count = removed_count + then_count
let next_else = match else_body {
Some(body) => {
let (next_body, else_count) = run_hot_pipeline_dae_remove_never_read_local_tees_with_counts(
body, local_gets,
)
removed_count = removed_count + else_count
Some(next_body)
}
None => None
}
rewritten.push(@lib.Instruction::if_(block_type, next_then, next_else))
}
_ => rewritten.push(instr)
}
}
(rewritten, removed_count)
}
///|
fn run_hot_pipeline_dae_remove_never_read_local_tees(
instrs : Array[@lib.Instruction],
) -> (Array[@lib.Instruction], Int32) {
let max_local_idx = run_hot_pipeline_simplify_locals_lowered_max_local_idx_in_body(
instrs,
)
if max_local_idx < 0 {
return (instrs, 0)
}
let local_gets : Array[Int32] = Array::make(max_local_idx + 1, 0)
let local_sets : Array[Int32] = Array::make(max_local_idx + 1, 0)
let local_tees : Array[Int32] = Array::make(max_local_idx + 1, 0)
run_hot_pipeline_raw_collect_local_ref_counts(
instrs, local_gets, local_sets, local_tees,
)
run_hot_pipeline_dae_remove_never_read_local_tees_with_counts(
instrs, local_gets,
)
}
///|
fn run_hot_pipeline_dae_tee_struct_get_readbacks_with_counts_instr(
instr : @lib.Instruction,
local_gets : Array[Int32],
local_sets : Array[Int32],
local_tees : Array[Int32],
local_types : Array[@lib.ValType],
type_sec~ : @lib.TypeSec? = None,
) -> (@lib.Instruction, Int32) {
match instr {
@lib.Block(block_type, @lib.Expr(body)) => {
let (rewritten, count) = run_hot_pipeline_dae_tee_struct_get_readbacks_with_counts_and_types(
body,
local_gets,
local_sets,
local_tees,
local_types,
type_sec~,
)
(@lib.Instruction::block(block_type, @lib.Expr::new(rewritten)), count)
}
@lib.Loop(block_type, @lib.Expr(body)) => {
let (rewritten, count) = run_hot_pipeline_dae_tee_struct_get_readbacks_with_counts_and_types(
body,
local_gets,
local_sets,
local_tees,
local_types,
type_sec~,
)
(@lib.Instruction::loop_(block_type, @lib.Expr::new(rewritten)), count)
}
@lib.TryTable(block_type, catches, @lib.Expr(body)) => {
let (rewritten, count) = run_hot_pipeline_dae_tee_struct_get_readbacks_with_counts_and_types(
body,
local_gets,
local_sets,
local_tees,
local_types,
type_sec~,
)
(
@lib.Instruction::try_table(
block_type,
catches,
@lib.Expr::new(rewritten),
),
count,
)
}
@lib.If(block_type, then_body, else_body) => {
let (rewritten_then, then_count) = run_hot_pipeline_dae_tee_struct_get_readbacks_with_counts_and_types(
then_body,
local_gets,
local_sets,
local_tees,
local_types,
type_sec~,
)
let mut count = then_count
let rewritten_else = match else_body {
Some(body) => {
let (rewritten, else_count) = run_hot_pipeline_dae_tee_struct_get_readbacks_with_counts_and_types(
body,
local_gets,
local_sets,
local_tees,
local_types,
type_sec~,
)
count = count + else_count
Some(rewritten)
}
None => None
}
(@lib.Instruction::if_(block_type, rewritten_then, rewritten_else), count)
}
_ => (instr, 0)
}
}
///|
fn run_hot_pipeline_dae_readback_producer_can_tee(
instr : @lib.Instruction,
) -> Bool {
match instr {
@lib.StructGet(_, _) => true
@lib.Block(@lib.ValTypeBlockType(_), _)
| @lib.Block(@lib.TypeIdxBlockType(_), _) => true
_ => false
}
}
///|
// A receiver read can move before its own pure field projection, allowing the
// projected call argument to remain on the stack instead of using a spill.
fn run_hot_pipeline_dae_try_reread_receiver_before_spilled_field(
instrs : Array[@lib.Instruction],
idx : Int32,
local_gets : Array[Int32],
local_sets : Array[Int32],
local_tees : Array[Int32],
) -> (Array[@lib.Instruction], Int32)? {
if idx + 5 >= instrs.length() {
return None
}
match
(
instrs[idx],
instrs[idx + 1],
instrs[idx + 2],
instrs[idx + 3],
instrs[idx + 4],
instrs[idx + 5],
) {
(
@lib.LocalGet(@lib.LocalIdx(aggregate_raw) as aggregate),
@lib.StructGet(struct_type, field),
@lib.LocalSet(@lib.LocalIdx(spill_raw) as spill),
@lib.LocalGet(second_aggregate),
@lib.LocalGet(spill_readback),
@lib.Call(consumer),
) if aggregate == second_aggregate && spill == spill_readback => {
let aggregate_idx = aggregate_raw.reinterpret_as_int()
let spill_idx = spill_raw.reinterpret_as_int()
if aggregate_idx < 0 ||
spill_idx < 0 ||
aggregate_idx == spill_idx ||
aggregate_idx >= local_gets.length() ||
aggregate_idx >= local_sets.length() ||
aggregate_idx >= local_tees.length() ||
spill_idx >= local_gets.length() ||
spill_idx >= local_sets.length() ||
spill_idx >= local_tees.length() ||
local_gets[spill_idx] < 1 ||
local_sets[spill_idx] != 1 ||
local_tees[spill_idx] != 0 {
return None
}
local_gets[spill_idx] = local_gets[spill_idx] - 1
local_sets[spill_idx] = 0
local_tees[spill_idx] = 1
Some(
(
[
@lib.Instruction::local_get(aggregate),
@lib.Instruction::local_get(aggregate),
@lib.Instruction::struct_get(struct_type, field),
@lib.Instruction::local_tee(spill),
@lib.Instruction::call(consumer),
],
6,
),
)
}
_ => None
}
}
///|
// A single aggregate value can be reread to preserve the exact field-result
// order without spilling the first field. Struct reads are pure, and the same
// aggregate is tested before any intervening effect or control instruction.
fn run_hot_pipeline_dae_try_reread_reordered_aggregate_fields(
instrs : Array[@lib.Instruction],
idx : Int32,
local_gets : Array[Int32],
local_sets : Array[Int32],
local_tees : Array[Int32],
) -> (Array[@lib.Instruction], Int32)? {
if idx + 8 >= instrs.length() {
return None
}
match
(
instrs[idx],
instrs[idx + 1],
instrs[idx + 2],
instrs[idx + 3],
instrs[idx + 4],
instrs[idx + 5],
instrs[idx + 6],
instrs[idx + 7],
instrs[idx + 8],
) {
(
@lib.LocalTee(@lib.LocalIdx(aggregate_raw) as aggregate),
@lib.StructGet(struct_type, first_field),
@lib.LocalSet(@lib.LocalIdx(spill_raw) as spill),
@lib.LocalGet(second_aggregate),
@lib.StructGet(second_type, second_field),
@lib.LocalGet(spill_readback),
@lib.LocalGet(third_aggregate),
@lib.StructGet(third_type, third_field),
@lib.Br(label),
) if aggregate == second_aggregate &&
aggregate == third_aggregate &&
spill == spill_readback &&
struct_type == second_type &&
struct_type == third_type => {
let aggregate_idx = aggregate_raw.reinterpret_as_int()
let spill_idx = spill_raw.reinterpret_as_int()
if aggregate_idx < 0 ||
spill_idx < 0 ||
aggregate_idx == spill_idx ||
aggregate_idx >= local_gets.length() ||
aggregate_idx >= local_sets.length() ||
aggregate_idx >= local_tees.length() ||
spill_idx >= local_gets.length() ||
spill_idx >= local_sets.length() ||
spill_idx >= local_tees.length() ||
local_gets[spill_idx] != 1 ||
local_sets[spill_idx] != 1 ||
local_tees[spill_idx] != 0 {
return None
}
local_gets[spill_idx] = 0
local_sets[spill_idx] = 0
Some(
(
[
@lib.Instruction::local_tee(aggregate),
@lib.Instruction::struct_get(struct_type, second_field),
@lib.Instruction::local_get(aggregate),
@lib.Instruction::struct_get(struct_type, first_field),
@lib.Instruction::local_get(aggregate),
@lib.Instruction::struct_get(struct_type, third_field),
@lib.Instruction::br(label),
],
9,
),
)
}
_ => None
}
}
///|
fn run_hot_pipeline_dae_try_store_first_terminal_pair_result_arm(
arm : Array[@lib.Instruction],
first_object : @lib.LocalIdx,
store_type : @lib.TypeIdx,
store_field : @lib.U32,
) -> Array[@lib.Instruction]? {
if arm.length() < 5 {
return None
}
let start = arm.length() - 5
match
(arm[start], arm[start + 1], arm[start + 2], arm[start + 3], arm[start + 4]) {
(
@lib.LocalTee(@lib.LocalIdx(aggregate_raw) as aggregate),
@lib.StructGet(first_type, first_field),
@lib.LocalGet(second_aggregate),
@lib.StructGet(second_type, second_field),
@lib.Br(@lib.LabelIdx(1U)),
) if aggregate == second_aggregate && first_type == second_type => {
let aggregate_idx = aggregate_raw.reinterpret_as_int()
if aggregate_idx < 0 {
return None
}
let rewritten : Array[@lib.Instruction] = []
for idx = 0; idx < start; idx = idx + 1 {
rewritten.push(arm[idx])
}
rewritten.push(@lib.Instruction::local_tee(aggregate))
rewritten.push(@lib.Instruction::local_get(first_object))
rewritten.push(@lib.Instruction::local_get(aggregate))
rewritten.push(@lib.Instruction::struct_get(first_type, first_field))
rewritten.push(@lib.Instruction::struct_set(store_type, store_field))
rewritten.push(@lib.Instruction::struct_get(second_type, second_field))
rewritten.push(@lib.Instruction::br(@lib.LabelIdx::new(1U)))
Some(rewritten)
}
_ => None
}
}
///|
fn run_hot_pipeline_dae_try_store_first_terminal_pair_result_producer(
producer_body : Array[@lib.Instruction],
first_object : @lib.LocalIdx,
store_type : @lib.TypeIdx,
store_field : @lib.U32,
) -> Array[@lib.Instruction]? {
if producer_body.length() < 2 ||
!(producer_body[producer_body.length() - 1] is @lib.Unreachable) {
return None
}
let if_idx = producer_body.length() - 2
match producer_body[if_idx] {
@lib.If(@lib.VoidBlockType, then_body, Some(else_body)) => {
let rewritten_then = run_hot_pipeline_dae_try_store_first_terminal_pair_result_arm(
then_body, first_object, store_type, store_field,
)
let rewritten_else = run_hot_pipeline_dae_try_store_first_terminal_pair_result_arm(
else_body, first_object, store_type, store_field,
)
if (rewritten_then is Some(_)) == (rewritten_else is Some(_)) {
return None
}
let rewritten : Array[@lib.Instruction] = []
for idx = 0; idx < if_idx; idx = idx + 1 {
rewritten.push(producer_body[idx])
}
rewritten.push(
@lib.Instruction::if_(
@lib.BlockType::void_(),
match rewritten_then {
Some(body) => body
None => then_body
},
Some(
match rewritten_else {
Some(body) => body
None => else_body
},
),
),
)
rewritten.push(@lib.Instruction::unreachable_())
Some(rewritten)
}
_ => None
}
}
///|
// A two-result producer can feed paired struct stores without a temporary for
// the first result. Hoisting only the first receiver's nontrapping local read
// leaves producer effects, traps, result order, and the second spill unchanged.
fn run_hot_pipeline_dae_try_forward_remapped_pair_store_carrier(
instrs : Array[@lib.Instruction],
idx : Int32,
local_types : Array[@lib.ValType],
local_gets : Array[Int32],
local_sets : Array[Int32],
local_tees : Array[Int32],
expected_producer_calls : Int32,
type_sec~ : @lib.TypeSec? = None,
) -> (Array[@lib.Instruction], Int32)? {
if idx + 9 >= instrs.length() {
return None
}
match
(
instrs[idx],
instrs[idx + 1],
instrs[idx + 2],
instrs[idx + 3],
instrs[idx + 4],
instrs[idx + 5],
instrs[idx + 6],
instrs[idx + 7],
instrs[idx + 8],
instrs[idx + 9],
) {
(
@lib.Block(@lib.TypeIdxBlockType(source_type), @lib.Expr(producer_body)) as producer,
@lib.LocalSet(@lib.LocalIdx(second_raw) as second),
@lib.LocalSet(@lib.LocalIdx(target_raw) as target),
@lib.LocalGet(@lib.LocalIdx(first_object_raw) as first_object),
@lib.LocalGet(target_readback),
@lib.StructSet(first_type, first_field),
@lib.LocalGet(@lib.LocalIdx(second_object_raw) as second_object),
@lib.LocalGet(second_readback),
@lib.StructSet(second_type, second_field),
@lib.I32Const(@lib.I32(0)),
) if target == target_readback && second == second_readback => {
let target_idx = target_raw.reinterpret_as_int()
let second_idx = second_raw.reinterpret_as_int()
let first_object_idx = first_object_raw.reinterpret_as_int()
if run_hot_pipeline_raw_count_call_like(producer_body) !=
expected_producer_calls ||
target_idx < 0 ||
second_idx < 0 ||
first_object_idx < 0 ||
target_idx == second_idx ||
target_idx == first_object_idx ||
second_idx == first_object_idx ||
target_idx >= local_types.length() ||
second_idx >= local_types.length() ||
first_object_idx >= local_types.length() ||
target_idx >= local_gets.length() ||
target_idx >= local_sets.length() ||
target_idx >= local_tees.length() ||
second_idx >= local_gets.length() ||
second_idx >= local_sets.length() ||
second_idx >= local_tees.length() ||
local_gets[target_idx] != 1 ||
local_sets[target_idx] != 1 ||
local_tees[target_idx] != 0 ||
local_gets[second_idx] != 1 ||
local_sets[second_idx] != 1 ||
local_tees[second_idx] != 0 ||
run_hot_pipeline_raw_branch_writes_local(
producer_body, first_object_idx,
) {
return None
}
let second_object_idx = second_object_raw.reinterpret_as_int()
let stored_first = match type_sec {
Some(type_sec) =>
match dae_type_sec_func_type(type_sec, source_type) {
Some(@lib.FuncType([], [first_result, second_result])) if local_types[target_idx] ==
first_result &&
local_types[second_idx] == second_result &&
second_object_idx >= 0 &&
second_object_idx < local_types.length() &&
second_object_idx != target_idx &&
second_object_idx != second_idx &&
second_object_idx != first_object_idx &&
!run_hot_pipeline_raw_branch_writes_local(
producer_body, second_object_idx,
) =>
match
run_hot_pipeline_dae_try_store_first_terminal_pair_result_producer(
producer_body, first_object, first_type, first_field,
) {
Some(rewritten_producer) =>
Some((second_result, rewritten_producer))
None => None
}
_ => None
}
None => None
}
local_gets[target_idx] = 0
local_sets[target_idx] = 0
match stored_first {
Some((second_result, rewritten_producer)) => {
local_gets[second_idx] = 0
local_sets[second_idx] = 0
Some(
(
[
@lib.Instruction::local_get(second_object),
@lib.Instruction::block(
@lib.BlockType::val_type(second_result),
@lib.Expr::new(rewritten_producer),
),
@lib.Instruction::struct_set(second_type, second_field),
@lib.Instruction::i32_const(@lib.I32(0)),
],
10,
),
)
}
None =>
Some(
(
[
@lib.Instruction::local_get(first_object),
producer,
@lib.Instruction::local_set(second),
@lib.Instruction::struct_set(first_type, first_field),
@lib.Instruction::local_get(second_object),
@lib.Instruction::local_get(second),
@lib.Instruction::struct_set(second_type, second_field),
@lib.Instruction::i32_const(@lib.I32(0)),
],
10,
),
)
}
}
_ => None
}
}
///|
fn run_hot_pipeline_dae_try_forward_remapped_two_call_pair_store_carrier(
instrs : Array[@lib.Instruction],
idx : Int32,
local_types : Array[@lib.ValType],
local_gets : Array[Int32],
local_sets : Array[Int32],
local_tees : Array[Int32],
type_sec~ : @lib.TypeSec? = None,
) -> (Array[@lib.Instruction], Int32)? {
run_hot_pipeline_dae_try_forward_remapped_pair_store_carrier(
instrs,
idx,
local_types,
local_gets,
local_sets,
local_tees,
2,
type_sec~,
)
}
///|
fn run_hot_pipeline_dae_try_forward_remapped_one_call_pair_store_carrier(
instrs : Array[@lib.Instruction],
idx : Int32,
local_types : Array[@lib.ValType],
local_gets : Array[Int32],
local_sets : Array[Int32],
local_tees : Array[Int32],
type_sec~ : @lib.TypeSec? = None,
) -> (Array[@lib.Instruction], Int32)? {
run_hot_pipeline_dae_try_forward_remapped_pair_store_carrier(
instrs,
idx,
local_types,
local_gets,
local_sets,
local_tees,
1,
type_sec~,
)
}
///|
// An exact `block (result T) { local.get; br 0 }` snapshots no state beyond the
// caller local. Across a direct-call-only prefix, reading that same local at the
// one use preserves value, call effects, traps, and control while removing the
// redundant result carrier.
fn run_hot_pipeline_dae_try_forward_remapped_identity_block_carrier(
instrs : Array[@lib.Instruction],
idx : Int32,
local_types : Array[@lib.ValType],
local_gets : Array[Int32],
local_sets : Array[Int32],
local_tees : Array[Int32],
) -> (Array[@lib.Instruction], Int32)? {
if idx + 11 >= instrs.length() {
return None
}
match
(
instrs[idx],
instrs[idx + 1],
instrs[idx + 2],
instrs[idx + 3],
instrs[idx + 4],
instrs[idx + 5],
instrs[idx + 6],
instrs[idx + 7],
instrs[idx + 8],
instrs[idx + 9],
instrs[idx + 10],
instrs[idx + 11],
) {
(
@lib.Block(
@lib.ValTypeBlockType(result_type),
@lib.Expr(
[
@lib.LocalGet(@lib.LocalIdx(source_raw) as source),
@lib.Br(@lib.LabelIdx(0U)),
]
)
),
@lib.LocalSet(@lib.LocalIdx(target_raw) as target),
@lib.LocalGet(first_prefix),
@lib.LocalGet(second_prefix),
@lib.I32Const(prefix_const),
@lib.LocalGet(third_prefix),
@lib.Call(prefix_call),
@lib.LocalGet(readback),
@lib.I32Const(@lib.I32(0)),
@lib.LocalGet(producer_arg),
@lib.Call(producer),
@lib.Call(consumer),
) if target == readback => {
let source_idx = source_raw.reinterpret_as_int()
let target_idx = target_raw.reinterpret_as_int()
if source_idx < 0 ||
target_idx < 0 ||
source_idx == target_idx ||
source_idx >= local_types.length() ||
target_idx >= local_types.length() ||
target_idx >= local_gets.length() ||
target_idx >= local_sets.length() ||
target_idx >= local_tees.length() ||
local_types[source_idx] != result_type ||
local_types[target_idx] != result_type ||
local_gets[target_idx] != 1 ||
local_sets[target_idx] != 1 ||
local_tees[target_idx] != 0 {
return None
}
local_gets[target_idx] = 0
local_sets[target_idx] = 0
Some(
(
[
@lib.Instruction::local_get(first_prefix),
@lib.Instruction::local_get(second_prefix),
@lib.Instruction::i32_const(prefix_const),
@lib.Instruction::local_get(third_prefix),
@lib.Instruction::call(prefix_call),
@lib.Instruction::local_get(source),
@lib.Instruction::i32_const(@lib.I32(0)),
@lib.Instruction::local_get(producer_arg),
@lib.Instruction::call(producer),
@lib.Instruction::call(consumer),
],
12,
),
)
}
_ => None
}
}
///|
// Binaryen's terminal dispatch blocks place four caller-local arguments before
// an exact one-use block result. The local reads are nontrapping and direct
// calls cannot observe them; producer-local writes remain an explicit bailout.
fn run_hot_pipeline_dae_try_sink_remapped_terminal_four_arg_block_carrier(
instrs : Array[@lib.Instruction],
idx : Int32,
local_types : Array[@lib.ValType],
local_gets : Array[Int32],
local_sets : Array[Int32],
local_tees : Array[Int32],
) -> (Array[@lib.Instruction], Int32)? {
if idx + 8 >= instrs.length() {
return None
}
match
(
instrs[idx],
instrs[idx + 1],
instrs[idx + 2],
instrs[idx + 3],
instrs[idx + 4],
instrs[idx + 5],
instrs[idx + 6],
instrs[idx + 7],
instrs[idx + 8],
) {
(
@lib.Block(@lib.ValTypeBlockType(result_type), @lib.Expr(producer_body)) as producer,
@lib.LocalSet(@lib.LocalIdx(target_raw) as target),
@lib.LocalGet(@lib.LocalIdx(first_raw) as first),
@lib.LocalGet(@lib.LocalIdx(second_raw) as second),
@lib.LocalGet(@lib.LocalIdx(third_raw) as third),
@lib.LocalGet(@lib.LocalIdx(fourth_raw) as fourth),
@lib.LocalGet(readback),
@lib.Call(consumer),
@lib.Br(label),
) if target == readback => {
let target_idx = target_raw.reinterpret_as_int()
let moved_indices = [
first_raw.reinterpret_as_int(),
second_raw.reinterpret_as_int(),
third_raw.reinterpret_as_int(),
fourth_raw.reinterpret_as_int(),
]
if target_idx < 0 ||
target_idx >= local_types.length() ||
target_idx >= local_gets.length() ||
target_idx >= local_sets.length() ||
target_idx >= local_tees.length() ||
local_types[target_idx] != result_type ||
local_gets[target_idx] != 1 ||
local_sets[target_idx] != 1 ||
local_tees[target_idx] != 0 {
return None
}
for moved_idx in moved_indices {
if moved_idx < 0 ||
moved_idx == target_idx ||
moved_idx >= local_gets.length() ||
moved_idx >= local_sets.length() ||
moved_idx >= local_tees.length() ||
(
(local_sets[moved_idx] > 0 || local_tees[moved_idx] > 0) &&
run_hot_pipeline_raw_branch_writes_local(producer_body, moved_idx)
) {
return None
}
}
local_gets[target_idx] = 0
local_sets[target_idx] = 0
Some(
(
[
@lib.Instruction::local_get(first),
@lib.Instruction::local_get(second),
@lib.Instruction::local_get(third),
@lib.Instruction::local_get(fourth),
producer,
@lib.Instruction::call(consumer),
@lib.Instruction::br(label),
],
9,
),
)
}
_ => None
}
}
///|
// Binaryen's filtered SimplifyLocals replay carries an exact one-use block
// result through one pure caller-local argument. Moving only the local read
// before the complete block preserves every effect and trap in the producer.
fn run_hot_pipeline_dae_try_sink_remapped_one_arg_block_carrier(
instrs : Array[@lib.Instruction],
idx : Int32,
local_types : Array[@lib.ValType],
local_gets : Array[Int32],
local_sets : Array[Int32],
local_tees : Array[Int32],
) -> (Array[@lib.Instruction], Int32)? {
if idx + 4 >= instrs.length() {
return None
}
match
(
instrs[idx],
instrs[idx + 1],
instrs[idx + 2],
instrs[idx + 3],
instrs[idx + 4],
) {
(
@lib.Block(@lib.ValTypeBlockType(result_type), @lib.Expr(producer_body)) as producer,
@lib.LocalSet(@lib.LocalIdx(target_raw) as target),
@lib.LocalGet(@lib.LocalIdx(argument_raw) as argument),
@lib.LocalGet(readback),
@lib.Call(consumer),
) if target == readback => {
let target_idx = target_raw.reinterpret_as_int()
let argument_idx = argument_raw.reinterpret_as_int()
if target_idx < 0 ||
argument_idx < 0 ||
target_idx == argument_idx ||
target_idx >= local_types.length() ||
target_idx >= local_gets.length() ||
target_idx >= local_sets.length() ||
target_idx >= local_tees.length() ||
argument_idx >= local_gets.length() ||
argument_idx >= local_sets.length() ||
argument_idx >= local_tees.length() ||
local_types[target_idx] != result_type ||
local_gets[target_idx] != 1 ||
local_sets[target_idx] != 1 ||
local_tees[target_idx] != 0 ||
(
(local_sets[argument_idx] > 0 || local_tees[argument_idx] > 0) &&
run_hot_pipeline_raw_branch_writes_local(producer_body, argument_idx)
) {
return None
}
local_gets[target_idx] = 0
local_sets[target_idx] = 0
Some(
(
[
@lib.Instruction::local_get(argument),
producer,
@lib.Instruction::call(consumer),
],
5,
),
)
}
_ => None
}
}
///|
// Binaryen v130 SimplifyLocals sinks an exact one-use block result into the
// final argument of a four-argument call after a nontrapping aggregate read and
// empty GC constructors. The aggregate is non-null and producer-local writes
// remain an explicit bailout, matching the direct artifact's evaluated shape.
fn run_hot_pipeline_dae_try_sink_remapped_composite_call_arg_block_carrier(
instrs : Array[@lib.Instruction],
idx : Int32,
local_types : Array[@lib.ValType],
local_gets : Array[Int32],
local_sets : Array[Int32],
local_tees : Array[Int32],
type_sec~ : @lib.TypeSec? = None,
) -> (Array[@lib.Instruction], Int32)? {
if idx + 9 >= instrs.length() {
return None
}
match
(
instrs[idx],
instrs[idx + 1],
instrs[idx + 2],
instrs[idx + 3],
instrs[idx + 4],
instrs[idx + 5],
instrs[idx + 6],
instrs[idx + 7],
instrs[idx + 8],
instrs[idx + 9],
) {
(
@lib.Block(block_type, @lib.Expr(producer_body)) as producer,
@lib.LocalSet(@lib.LocalIdx(target_raw) as target),
@lib.LocalGet(@lib.LocalIdx(aggregate_raw) as aggregate),
@lib.StructGet(struct_type, field),
@lib.ArrayNewFixed(array_type, @lib.U32(0U)),
@lib.I32Const(@lib.I32(0)),
@lib.StructNew(wrapper_type),
@lib.I32Const(@lib.I32(0)),
@lib.LocalGet(readback),
@lib.Call(consumer),
) if target == readback => {
let target_idx = target_raw.reinterpret_as_int()
let aggregate_idx = aggregate_raw.reinterpret_as_int()
let result_type = match
run_hot_pipeline_dae_zero_param_single_block_result_type(
block_type, type_sec,
) {
Some(result_type) => result_type
None => return None
}
let aggregate_nonnull = if aggregate_idx >= 0 &&
aggregate_idx < local_types.length() {
match local_types[aggregate_idx] {
@lib.RefTypeValType(ref_type) => ref_type.is_non_nullable()
_ => false
}
} else {
false
}
if target_idx < 0 ||
aggregate_idx < 0 ||
target_idx == aggregate_idx ||
target_idx >= local_types.length() ||
aggregate_idx >= local_types.length() ||
target_idx >= local_gets.length() ||
target_idx >= local_sets.length() ||
target_idx >= local_tees.length() ||
local_types[target_idx] != result_type ||
local_gets[target_idx] != 1 ||
local_sets[target_idx] != 1 ||
local_tees[target_idx] != 0 ||
!aggregate_nonnull ||
run_hot_pipeline_raw_branch_writes_local(producer_body, aggregate_idx) {
return None
}
local_gets[target_idx] = 0
local_sets[target_idx] = 0
Some(
(
[
@lib.Instruction::local_get(aggregate),
@lib.Instruction::struct_get(struct_type, field),
@lib.Instruction::array_new_fixed(array_type, @lib.U32(0U)),
@lib.Instruction::i32_const(@lib.I32(0)),
@lib.Instruction::struct_new(wrapper_type),
@lib.Instruction::i32_const(@lib.I32(0)),
producer,
@lib.Instruction::call(consumer),
],
10,
),
)
}
_ => None
}
}
///|
// Binaryen v130 sinks this complete one-use block producer after the exact
// terminal prefix call, with the result as the first of three final arguments.
// The selected transaction requires exact counts/types and rejects producer
// writes to every caller local moved before it.
fn run_hot_pipeline_dae_try_sink_target_first_terminal_call_block_carrier(
instrs : Array[@lib.Instruction],
idx : Int32,
local_types : Array[@lib.ValType],
local_gets : Array[Int32],
local_sets : Array[Int32],
local_tees : Array[Int32],
type_sec~ : @lib.TypeSec? = None,
) -> (Array[@lib.Instruction], Int32)? {
if idx + 12 >= instrs.length() {
return None
}
match
(
instrs[idx],
instrs[idx + 1],
instrs[idx + 2],
instrs[idx + 3],
instrs[idx + 4],
instrs[idx + 5],
instrs[idx + 6],
instrs[idx + 7],
instrs[idx + 8],
instrs[idx + 9],
instrs[idx + 10],
instrs[idx + 11],
instrs[idx + 12],
) {
(
@lib.Block(block_type, @lib.Expr(producer_body)) as producer,
@lib.LocalSet(@lib.LocalIdx(target_raw) as target),
@lib.LocalGet(@lib.LocalIdx(first_raw) as first),
@lib.LocalGet(@lib.LocalIdx(second_raw) as second),
@lib.I32Const(prefix_const),
@lib.LocalGet(@lib.LocalIdx(third_raw) as third),
@lib.Call(prefix_call),
@lib.LocalGet(readback),
@lib.I32Const(@lib.I32(0)),
@lib.I32Const(@lib.I32(0)),
@lib.Call(consumer),
@lib.Call(outer),
@lib.Drop,
) if target == readback => {
let target_idx = target_raw.reinterpret_as_int()
let result_type = match
run_hot_pipeline_dae_zero_param_single_block_result_type(
block_type, type_sec,
) {
Some(result_type) => result_type
None => return None
}
if target_idx < 0 ||
target_idx >= local_types.length() ||
target_idx >= local_gets.length() ||
target_idx >= local_sets.length() ||
target_idx >= local_tees.length() ||
local_types[target_idx] != result_type ||
local_gets[target_idx] != 1 ||
local_sets[target_idx] != 1 ||
local_tees[target_idx] != 0 {
return None
}
let moved = [
first_raw.reinterpret_as_int(),
second_raw.reinterpret_as_int(),
third_raw.reinterpret_as_int(),
]
for moved_idx in moved {
if moved_idx < 0 ||
moved_idx == target_idx ||
moved_idx >= local_types.length() ||
run_hot_pipeline_raw_branch_writes_local(producer_body, moved_idx) {
return None
}
}
local_gets[target_idx] = 0
local_sets[target_idx] = 0
Some(
(
[
@lib.Instruction::local_get(first),
@lib.Instruction::local_get(second),
@lib.Instruction::i32_const(prefix_const),
@lib.Instruction::local_get(third),
@lib.Instruction::call(prefix_call),
producer,
@lib.Instruction::i32_const(@lib.I32(0)),
@lib.Instruction::i32_const(@lib.I32(0)),
@lib.Instruction::call(consumer),
@lib.Instruction::call(outer),
@lib.Instruction::drop(),
],
13,
),
)
}
_ => None
}
}
///|
// Binaryen v130 also sinks complete one-use block producers into the last
// argument of the sibling terminal-call shape. Only a pure scalar first final
// argument and zero move before the producer, with caller-local writes guarded.
fn run_hot_pipeline_dae_try_sink_target_last_terminal_call_block_carrier(
instrs : Array[@lib.Instruction],
idx : Int32,
local_types : Array[@lib.ValType],
local_gets : Array[Int32],
local_sets : Array[Int32],
local_tees : Array[Int32],
type_sec~ : @lib.TypeSec? = None,
) -> (Array[@lib.Instruction], Int32)? {
if idx + 12 >= instrs.length() {
return None
}
match
(
instrs[idx],
instrs[idx + 1],
instrs[idx + 2],
instrs[idx + 3],
instrs[idx + 4],
instrs[idx + 5],
instrs[idx + 6],
instrs[idx + 7],
instrs[idx + 8],
instrs[idx + 9],
instrs[idx + 10],
instrs[idx + 11],
instrs[idx + 12],
) {
(
@lib.Block(block_type, @lib.Expr(producer_body)) as producer,
@lib.LocalSet(@lib.LocalIdx(target_raw) as target),
@lib.LocalGet(@lib.LocalIdx(first_raw) as first),
@lib.LocalGet(@lib.LocalIdx(second_raw) as second),
@lib.I32Const(prefix_const),
@lib.LocalGet(@lib.LocalIdx(third_raw) as third),
@lib.Call(prefix_call),
first_final_arg,
@lib.I32Const(@lib.I32(0)),
@lib.LocalGet(readback),
@lib.Call(consumer),
@lib.Call(outer),
@lib.Drop,
) if target == readback &&
run_hot_pipeline_optimize_instructions_instr_is_pure_stack_call_operand(
first_final_arg,
) => {
let target_idx = target_raw.reinterpret_as_int()
let result_type = match
run_hot_pipeline_dae_zero_param_single_block_result_type(
block_type, type_sec,
) {
Some(result_type) => result_type
None => return None
}
if target_idx < 0 ||
target_idx >= local_types.length() ||
target_idx >= local_gets.length() ||
target_idx >= local_sets.length() ||
target_idx >= local_tees.length() ||
local_types[target_idx] != result_type ||
local_gets[target_idx] != 1 ||
local_sets[target_idx] != 1 ||
local_tees[target_idx] != 0 {
return None
}
let moved = [
first_raw.reinterpret_as_int(),
second_raw.reinterpret_as_int(),
third_raw.reinterpret_as_int(),
]
for moved_idx in moved {
if moved_idx < 0 ||
moved_idx == target_idx ||
moved_idx >= local_types.length() ||
run_hot_pipeline_raw_branch_writes_local(producer_body, moved_idx) {
return None
}
}
match first_final_arg {
@lib.LocalGet(@lib.LocalIdx(moved_raw)) => {
let moved_idx = moved_raw.reinterpret_as_int()
if moved_idx < 0 ||
moved_idx == target_idx ||
moved_idx >= local_types.length() ||
run_hot_pipeline_raw_branch_writes_local(producer_body, moved_idx) {
return None
}
}
_ => ()
}
local_gets[target_idx] = 0
local_sets[target_idx] = 0
Some(
(
[
@lib.Instruction::local_get(first),
@lib.Instruction::local_get(second),
@lib.Instruction::i32_const(prefix_const),
@lib.Instruction::local_get(third),
@lib.Instruction::call(prefix_call),
first_final_arg,
@lib.Instruction::i32_const(@lib.I32(0)),
producer,
@lib.Instruction::call(consumer),
@lib.Instruction::call(outer),
@lib.Instruction::drop(),
],
13,
),
)
}
_ => None
}
}
///|
// A terminal wrapper call consumes a pure leading zero and an exact one-use
// block result. Hoisting only the constant keeps producer effects and traps in
// place while deleting the set/readback carrier before construction/return.
fn run_hot_pipeline_dae_try_sink_remapped_terminal_call_block_carrier(
instrs : Array[@lib.Instruction],
idx : Int32,
local_types : Array[@lib.ValType],
local_gets : Array[Int32],
local_sets : Array[Int32],
local_tees : Array[Int32],
type_sec~ : @lib.TypeSec? = None,
) -> (Array[@lib.Instruction], Int32, Int32)? {
if idx + 6 >= instrs.length() {
return None
}
match
(
instrs[idx],
instrs[idx + 1],
instrs[idx + 2],
instrs[idx + 3],
instrs[idx + 4],
instrs[idx + 5],
instrs[idx + 6],
) {
(
@lib.Block(@lib.ValTypeBlockType(result_type), @lib.Expr(producer_body)),
@lib.LocalSet(@lib.LocalIdx(target_raw) as target),
@lib.I32Const(@lib.I32(0)),
@lib.LocalGet(readback),
@lib.Call(wrapper),
@lib.StructNew(struct_type),
@lib.Return,
) if target == readback => {
let target_idx = target_raw.reinterpret_as_int()
if target_idx < 0 ||
target_idx >= local_types.length() ||
target_idx >= local_gets.length() ||
target_idx >= local_sets.length() ||
target_idx >= local_tees.length() ||
local_types[target_idx] != result_type ||
local_gets[target_idx] != 1 ||
local_sets[target_idx] != 1 ||
local_tees[target_idx] != 0 {
return None
}
let (rewritten_producer_body, nested_count) = run_hot_pipeline_dae_tee_struct_get_readbacks_with_counts_and_types(
producer_body,
local_gets,
local_sets,
local_tees,
local_types,
type_sec~,
)
local_gets[target_idx] = 0
local_sets[target_idx] = 0
Some(
(
[
@lib.Instruction::i32_const(@lib.I32(0)),
@lib.Instruction::block(
@lib.BlockType::val_type(result_type),
@lib.Expr::new(rewritten_producer_body),
),
@lib.Instruction::call(wrapper),
@lib.Instruction::struct_new(struct_type),
@lib.Instruction::return_(),
],
7,
nested_count,
),
)
}
_ => None
}
}
///|
fn run_hot_pipeline_dae_try_sink_remapped_terminal_block_carrier(
instrs : Array[@lib.Instruction],
idx : Int32,
local_types : Array[@lib.ValType],
local_gets : Array[Int32],
local_sets : Array[Int32],
local_tees : Array[Int32],
) -> (Array[@lib.Instruction], Int32)? {
if idx + 5 >= instrs.length() {
return None
}
match
(
instrs[idx],
instrs[idx + 1],
instrs[idx + 2],
instrs[idx + 3],
instrs[idx + 4],
instrs[idx + 5],
) {
(
@lib.Block(@lib.ValTypeBlockType(result_type), _) as producer,
@lib.LocalSet(@lib.LocalIdx(target_raw) as target),
@lib.I32Const(@lib.I32(0)),
@lib.LocalGet(readback),
@lib.StructNew(struct_type),
@lib.Return,
) if target == readback => {
let target_idx = target_raw.reinterpret_as_int()
if target_idx < 0 ||
target_idx >= local_types.length() ||
target_idx >= local_gets.length() ||
target_idx >= local_sets.length() ||
target_idx >= local_tees.length() ||
local_types[target_idx] != result_type ||
local_gets[target_idx] != 1 ||
local_sets[target_idx] != 1 ||
local_tees[target_idx] != 0 {
return None
}
local_gets[target_idx] = 0
local_sets[target_idx] = 0
Some(
(
[
@lib.Instruction::i32_const(@lib.I32(0)),
producer,
@lib.Instruction::struct_new(struct_type),
@lib.Instruction::return_(),
],
6,
),
)
}
_ => None
}
}
///|
fn run_hot_pipeline_dae_struct_field_is_immutable(
type_sec : @lib.TypeSec?,
type_idx : @lib.TypeIdx,
field_idx : @lib.U32,
) -> Bool {
let target = match type_idx {
@lib.TypeIdx(raw) => raw.reinterpret_as_int()
@lib.RecIdx(_) => return false
}
let @lib.U32(field_raw) = field_idx
let field = field_raw.reinterpret_as_int()
let rec_types = match type_sec {
Some(@lib.TypeSec(rec_types)) => rec_types
None => return false
}
let mut flat = 0
for rec_type in rec_types {
match rec_type {
@lib.SingleRecType(subtype) => {
if flat == target {
return match subtype.get_comptype() {
@lib.StructCompType(fields) =>
match fields.get(field) {
Some(field_type) => !field_type.is_mutable()
None => false
}
_ => false
}
}
flat = flat + 1
}
@lib.GroupRecType(subtypes) =>
for subtype in subtypes {
if flat == target {
return match subtype.get_comptype() {
@lib.StructCompType(fields) =>
match fields.get(field) {
Some(field_type) => !field_type.is_mutable()
None => false
}
_ => false
}
}
flat = flat + 1
}
}
}
false
}
///|
fn run_hot_pipeline_dae_rewrite_delayed_struct_get_readback_in_region(
instrs : Array[@lib.Instruction],
source : @lib.LocalIdx,
target : @lib.LocalIdx,
struct_type : @lib.TypeIdx,
field : @lib.U32,
keep_target : Bool,
) -> (Array[@lib.Instruction], Bool) {
for idx = 0; idx < instrs.length(); idx = idx + 1 {
match instrs[idx] {
@lib.LocalGet(readback) if readback == target => {
let rewritten : Array[@lib.Instruction] = []
for prefix_idx = 0; prefix_idx < idx; prefix_idx = prefix_idx + 1 {
rewritten.push(instrs[prefix_idx])
}
rewritten.push(@lib.Instruction::local_get(source))
rewritten.push(@lib.Instruction::struct_get(struct_type, field))
if keep_target {
rewritten.push(@lib.Instruction::local_tee(target))
}
for suffix_idx = idx + 1
suffix_idx < instrs.length()
suffix_idx = suffix_idx + 1 {
rewritten.push(instrs[suffix_idx])
}
return (rewritten, true)
}
@lib.Block(block_type, @lib.Expr(body)) => {
let (next_body, changed) = run_hot_pipeline_dae_rewrite_delayed_struct_get_readback_in_region(
body, source, target, struct_type, field, keep_target,
)
if !changed {
return (instrs, false)
}
let rewritten = instrs.copy()
rewritten[idx] = @lib.Instruction::block(
block_type,
@lib.Expr::new(next_body),
)
return (rewritten, true)
}
candidate =>
if run_hot_pipeline_dae_copy_retarget_is_control_boundary(candidate) {
return (instrs, false)
}
}
}
(instrs, false)
}
///|
fn run_hot_pipeline_dae_delay_immutable_struct_get_readbacks_once(
instrs : Array[@lib.Instruction],
local_types : Array[@lib.ValType],
param_count : Int32,
local_gets : Array[Int32],
local_sets : Array[Int32],
local_tees : Array[Int32],
type_sec : @lib.TypeSec?,
) -> (Array[@lib.Instruction], Bool) {
for idx = 0; idx < instrs.length(); idx = idx + 1 {
let nested = match instrs[idx] {
@lib.Block(block_type, @lib.Expr(body)) => {
let (next_body, changed) = run_hot_pipeline_dae_delay_immutable_struct_get_readbacks_once(
body, local_types, param_count, local_gets, local_sets, local_tees, type_sec,
)
if changed {
Some(@lib.Instruction::block(block_type, @lib.Expr::new(next_body)))
} else {
None
}
}
@lib.Loop(block_type, @lib.Expr(body)) => {
let (next_body, changed) = run_hot_pipeline_dae_delay_immutable_struct_get_readbacks_once(
body, local_types, param_count, local_gets, local_sets, local_tees, type_sec,
)
if changed {
Some(@lib.Instruction::loop_(block_type, @lib.Expr::new(next_body)))
} else {
None
}
}
@lib.TryTable(block_type, catches, @lib.Expr(body)) => {
let (next_body, changed) = run_hot_pipeline_dae_delay_immutable_struct_get_readbacks_once(
body, local_types, param_count, local_gets, local_sets, local_tees, type_sec,
)
if changed {
Some(
@lib.Instruction::try_table(
block_type,
catches,
@lib.Expr::new(next_body),
),
)
} else {
None
}
}
@lib.If(block_type, then_body, else_body) => {
let (next_then, then_changed) = run_hot_pipeline_dae_delay_immutable_struct_get_readbacks_once(
then_body, local_types, param_count, local_gets, local_sets, local_tees,
type_sec,
)
if then_changed {
Some(@lib.Instruction::if_(block_type, next_then, else_body))
} else {
match else_body {
Some(body) => {
let (next_body, else_changed) = run_hot_pipeline_dae_delay_immutable_struct_get_readbacks_once(
body, local_types, param_count, local_gets, local_sets, local_tees,
type_sec,
)
if else_changed {
Some(
@lib.Instruction::if_(block_type, then_body, Some(next_body)),
)
} else {
None
}
}
None => None
}
}
}
_ => None
}
match nested {
Some(rewritten_instr) => {
let rewritten = instrs.copy()
rewritten[idx] = rewritten_instr
return (rewritten, true)
}
None => ()
}
}
let mut producer_idx = 0
while producer_idx + 2 < instrs.length() {
match
(instrs[producer_idx], instrs[producer_idx + 1], instrs[producer_idx + 2]) {
(
@lib.LocalGet(@lib.LocalIdx(source_raw) as source),
@lib.StructGet(struct_type, field),
@lib.LocalSet(@lib.LocalIdx(target_raw) as target),
) => {
let source_idx = source_raw.reinterpret_as_int()
let target_idx = target_raw.reinterpret_as_int()
let source_nonnull = if source_idx >= 0 &&
source_idx < local_types.length() {
match local_types[source_idx] {
@lib.RefTypeValType(ref_type) => ref_type.is_non_nullable()
_ => false
}
} else {
false
}
if source_idx >= 0 &&
source_idx < param_count &&
target_idx >= param_count &&
target_idx < local_types.length() &&
source_idx != target_idx &&
source_idx < local_gets.length() &&
source_idx < local_sets.length() &&
source_idx < local_tees.length() &&
target_idx < local_gets.length() &&
target_idx < local_sets.length() &&
target_idx < local_tees.length() &&
source_nonnull &&
local_gets[source_idx] >= 1 &&
local_sets[source_idx] == 0 &&
local_tees[source_idx] == 0 &&
local_gets[target_idx] == 4 &&
local_sets[target_idx] == 1 &&
local_tees[target_idx] == 0 &&
run_hot_pipeline_dae_struct_field_is_immutable(
type_sec, struct_type, field,
) {
let suffix : Array[@lib.Instruction] = []
for suffix_idx = producer_idx + 3
suffix_idx < instrs.length()
suffix_idx = suffix_idx + 1 {
suffix.push(instrs[suffix_idx])
}
let (rewritten_suffix, changed) = run_hot_pipeline_dae_rewrite_delayed_struct_get_readback_in_region(
suffix, source, target, struct_type, field, true,
)
if changed {
let rewritten : Array[@lib.Instruction] = []
for prefix_idx = 0
prefix_idx < producer_idx
prefix_idx = prefix_idx + 1 {
rewritten.push(instrs[prefix_idx])
}
for instr in rewritten_suffix {
rewritten.push(instr)
}
return (rewritten, true)
}
}
}
_ => ()
}
producer_idx += 1
}
(instrs, false)
}
///|
// Delay only immutable, nontrapping struct reads from unwritten non-null
// parameter references. The exact artifact one-set/four-get carrier moves to
// its first use as local.tee so the three later reads keep the same value. Other
// read counts are excluded: the broader one-read family caused a canonical-size
// regression, and unrelated two-read matches changed a different function.
// Flat effects may remain between the old and new positions; only unconditional
// block descent is
// allowed, and all other structured or terminal boundaries reject.
#warnings("-unused_value")
fn run_hot_pipeline_dae_delay_immutable_struct_get_readbacks_instrs(
instrs : Array[@lib.Instruction],
local_types : Array[@lib.ValType],
param_count : Int32,
type_sec : @lib.TypeSec?,
) -> (Array[@lib.Instruction], Int32) {
let mut current = instrs
let mut rewrite_count = 0
let max_rounds = local_types.length()
while rewrite_count < max_rounds {
let local_gets : Array[Int32] = Array::make(local_types.length(), 0)
let local_sets : Array[Int32] = Array::make(local_types.length(), 0)
let local_tees : Array[Int32] = Array::make(local_types.length(), 0)
run_hot_pipeline_raw_collect_local_ref_counts(
current, local_gets, local_sets, local_tees,
)
let (next, changed) = run_hot_pipeline_dae_delay_immutable_struct_get_readbacks_once(
current, local_types, param_count, local_gets, local_sets, local_tees, type_sec,
)
if !changed {
break
}
current = next
rewrite_count += 1
}
(current, rewrite_count)
}
///|
// Binaryen v130 delays a constant-arm selector across the sibling side call
// because its only heap read is from an immutable struct field. Exact counts,
// non-null source typing, and field mutability evidence keep the reordering
// local while deleting the one-use selector carrier.
fn run_hot_pipeline_dae_try_delay_immutable_selector_past_side_call(
instrs : Array[@lib.Instruction],
idx : Int32,
local_types : Array[@lib.ValType],
local_gets : Array[Int32],
local_sets : Array[Int32],
local_tees : Array[Int32],
type_sec : @lib.TypeSec?,
) -> (Array[@lib.Instruction], Int32)? {
if idx + 22 >= instrs.length() {
return None
}
match
(
instrs[idx],
instrs[idx + 1],
instrs[idx + 2],
instrs[idx + 3],
instrs[idx + 4],
instrs[idx + 5],
instrs[idx + 6],
instrs[idx + 7],
instrs[idx + 8],
instrs[idx + 9],
instrs[idx + 10],
instrs[idx + 11],
instrs[idx + 12],
instrs[idx + 13],
instrs[idx + 14],
instrs[idx + 15],
instrs[idx + 16],
instrs[idx + 17],
instrs[idx + 18],
instrs[idx + 19],
instrs[idx + 20],
instrs[idx + 21],
instrs[idx + 22],
) {
(
@lib.LocalGet(@lib.LocalIdx(source_raw) as source),
@lib.StructGet(struct_type, field),
@lib.I32Const(selector_tag),
@lib.I32Eq,
@lib.If(
@lib.ValTypeBlockType(@lib.NumTypeValType(@lib.I32NumType)),
[@lib.I32Const(_)],
Some([@lib.I32Const(_)])
) as selector_producer,
@lib.LocalSet(@lib.LocalIdx(selector_raw) as selector),
@lib.LocalGet(side_arg0),
@lib.LocalGet(side_arg1),
@lib.Call(side_prefix),
@lib.Call(side_call),
@lib.LocalSet(side_result),
@lib.LocalGet(consumer_arg0),
@lib.LocalGet(consumer_arg1),
@lib.LocalGet(consumer_arg2),
@lib.LocalGet(prefix_arg0),
@lib.LocalGet(selector_readback),
@lib.LocalGet(prefix_arg2),
@lib.LocalGet(prefix_arg3),
@lib.Call(prefix_call),
@lib.LocalGet(producer_arg0),
@lib.LocalGet(side_readback),
@lib.LocalGet(producer_arg2),
@lib.Call(producer),
) if selector == selector_readback && side_result == side_readback => {
let source_idx = source_raw.reinterpret_as_int()
let selector_idx = selector_raw.reinterpret_as_int()
if source_idx < 0 ||
selector_idx < 0 ||
source_idx == selector_idx ||
source_idx >= local_types.length() ||
selector_idx >= local_types.length() ||
selector_idx >= local_gets.length() ||
selector_idx >= local_sets.length() ||
selector_idx >= local_tees.length() ||
local_types[selector_idx] != @lib.ValType::i32() ||
local_gets[selector_idx] != 1 ||
local_sets[selector_idx] != 1 ||
local_tees[selector_idx] != 0 ||
!run_hot_pipeline_dae_struct_field_is_immutable(
type_sec, struct_type, field,
) {
return None
}
let source_nonnull = match local_types[source_idx] {
@lib.RefTypeValType(ref_type) => ref_type.is_non_nullable()
_ => false
}
if !source_nonnull {
return None
}
local_gets[selector_idx] = 0
local_sets[selector_idx] = 0
Some(
(
[
@lib.Instruction::local_get(side_arg0),
@lib.Instruction::local_get(side_arg1),
@lib.Instruction::call(side_prefix),
@lib.Instruction::call(side_call),
@lib.Instruction::local_set(side_result),
@lib.Instruction::local_get(consumer_arg0),
@lib.Instruction::local_get(consumer_arg1),
@lib.Instruction::local_get(consumer_arg2),
@lib.Instruction::local_get(prefix_arg0),
@lib.Instruction::local_get(source),
@lib.Instruction::struct_get(struct_type, field),
@lib.Instruction::i32_const(selector_tag),
@lib.Instruction::i32_eq(),
selector_producer,
@lib.Instruction::local_get(prefix_arg2),
@lib.Instruction::local_get(prefix_arg3),
@lib.Instruction::call(prefix_call),
@lib.Instruction::local_get(producer_arg0),
@lib.Instruction::local_get(side_result),
@lib.Instruction::local_get(producer_arg2),
@lib.Instruction::call(producer),
],
23,
),
)
}
_ => None
}
}
///|
fn run_hot_pipeline_dae_tee_struct_get_readbacks_with_counts_and_types(
instrs : Array[@lib.Instruction],
local_gets : Array[Int32],
local_sets : Array[Int32],
local_tees : Array[Int32],
local_types : Array[@lib.ValType],
type_sec~ : @lib.TypeSec? = None,
) -> (Array[@lib.Instruction], Int32) {
let rewritten : Array[@lib.Instruction] = []
let mut rewrite_count = 0
let mut idx = 0
while idx < instrs.length() {
match
run_hot_pipeline_dae_try_delay_immutable_selector_past_side_call(
instrs, idx, local_types, local_gets, local_sets, local_tees, type_sec,
) {
Some((replacement, consumed_len)) => {
for instr in replacement {
rewritten.push(instr)
}
rewrite_count = rewrite_count + 1
idx += consumed_len
continue
}
None => ()
}
match
run_hot_pipeline_dae_try_reread_receiver_before_spilled_field(
instrs, idx, local_gets, local_sets, local_tees,
) {
Some((replacement, consumed_len)) => {
for instr in replacement {
rewritten.push(instr)
}
rewrite_count = rewrite_count + 1
idx += consumed_len
continue
}
None => ()
}
match
run_hot_pipeline_dae_try_reread_reordered_aggregate_fields(
instrs, idx, local_gets, local_sets, local_tees,
) {
Some((replacement, consumed_len)) => {
for instr in replacement {
rewritten.push(instr)
}
rewrite_count = rewrite_count + 1
idx += consumed_len
continue
}
None => ()
}
match
run_hot_pipeline_dae_try_forward_remapped_two_call_pair_store_carrier(
instrs,
idx,
local_types,
local_gets,
local_sets,
local_tees,
type_sec~,
) {
Some((replacement, consumed_len)) => {
for instr in replacement {
rewritten.push(instr)
}
rewrite_count = rewrite_count + 1
idx += consumed_len
continue
}
None => ()
}
match
run_hot_pipeline_dae_try_forward_remapped_one_call_pair_store_carrier(
instrs,
idx,
local_types,
local_gets,
local_sets,
local_tees,
type_sec~,
) {
Some((replacement, consumed_len)) => {
for instr in replacement {
rewritten.push(instr)
}
rewrite_count = rewrite_count + 1
idx += consumed_len
continue
}
None => ()
}
match
run_hot_pipeline_dae_try_forward_remapped_identity_block_carrier(
instrs, idx, local_types, local_gets, local_sets, local_tees,
) {
Some((replacement, consumed_len)) => {
for instr in replacement {
rewritten.push(instr)
}
rewrite_count = rewrite_count + 1
idx += consumed_len
continue
}
None => ()
}
match
run_hot_pipeline_dae_try_sink_remapped_terminal_four_arg_block_carrier(
instrs, idx, local_types, local_gets, local_sets, local_tees,
) {
Some((replacement, consumed_len)) => {
for instr in replacement {
rewritten.push(instr)
}
rewrite_count = rewrite_count + 1
idx += consumed_len
continue
}
None => ()
}
match
run_hot_pipeline_dae_try_sink_remapped_one_arg_block_carrier(
instrs, idx, local_types, local_gets, local_sets, local_tees,
) {
Some((replacement, consumed_len)) => {
for instr in replacement {
rewritten.push(instr)
}
rewrite_count = rewrite_count + 1
idx += consumed_len
continue
}
None => ()
}
match
run_hot_pipeline_dae_try_sink_target_last_terminal_call_block_carrier(
instrs,
idx,
local_types,
local_gets,
local_sets,
local_tees,
type_sec~,
) {
Some((replacement, consumed_len)) => {
for instr in replacement {
rewritten.push(instr)
}
rewrite_count = rewrite_count + 1
idx += consumed_len
continue
}
None => ()
}
match
run_hot_pipeline_dae_try_sink_target_first_terminal_call_block_carrier(
instrs,
idx,
local_types,
local_gets,
local_sets,
local_tees,
type_sec~,
) {
Some((replacement, consumed_len)) => {
for instr in replacement {
rewritten.push(instr)
}
rewrite_count = rewrite_count + 1
idx += consumed_len
continue
}
None => ()
}
match
run_hot_pipeline_dae_try_sink_remapped_composite_call_arg_block_carrier(
instrs,
idx,
local_types,
local_gets,
local_sets,
local_tees,
type_sec~,
) {
Some((replacement, consumed_len)) => {
for instr in replacement {
rewritten.push(instr)
}
rewrite_count = rewrite_count + 1
idx += consumed_len
continue
}
None => ()
}
match
run_hot_pipeline_dae_try_sink_remapped_terminal_call_block_carrier(
instrs,
idx,
local_types,
local_gets,
local_sets,
local_tees,
type_sec~,
) {
Some((replacement, consumed_len, nested_count)) => {
for instr in replacement {
rewritten.push(instr)
}
rewrite_count = rewrite_count + nested_count + 1
idx += consumed_len
continue
}
None => ()
}
match
run_hot_pipeline_dae_try_sink_remapped_terminal_block_carrier(
instrs, idx, local_types, local_gets, local_sets, local_tees,
) {
Some((replacement, consumed_len)) => {
for instr in replacement {
rewritten.push(instr)
}
rewrite_count = rewrite_count + 1
idx += consumed_len
continue
}
None => ()
}
if idx + 18 < instrs.length() {
match
(
instrs[idx],
instrs[idx + 1],
instrs[idx + 2],
instrs[idx + 3],
instrs[idx + 4],
instrs[idx + 5],
instrs[idx + 6],
instrs[idx + 7],
instrs[idx + 8],
instrs[idx + 9],
instrs[idx + 10],
instrs[idx + 11],
instrs[idx + 12],
instrs[idx + 13],
instrs[idx + 14],
instrs[idx + 15],
instrs[idx + 16],
instrs[idx + 17],
instrs[idx + 18],
) {
(
@lib.LocalGet(first_arg0),
@lib.LocalGet(first_arg1),
@lib.Call(first_producer),
@lib.LocalSet(@lib.LocalIdx(first_raw) as first),
@lib.LocalGet(second_arg0),
@lib.LocalGet(second_arg1),
@lib.Call(second_producer),
@lib.LocalSet(@lib.LocalIdx(second_raw) as second),
@lib.LocalGet(consumer_arg0),
@lib.LocalGet(consumer_arg1),
@lib.LocalGet(consumer_arg2),
@lib.LocalGet(consumer_arg3),
@lib.LocalGet(third_arg0),
@lib.LocalGet(third_arg1),
@lib.Call(third_producer),
@lib.LocalGet(second_readback),
@lib.LocalGet(first_readback),
@lib.Call(consumer),
@lib.Br(label),
) if first == first_readback &&
second == second_readback &&
first != second &&
first_arg0 == second_arg0 &&
first_arg0 == third_arg0 &&
first_arg1 == second_arg1 &&
first_arg1 == third_arg1 &&
first_producer == second_producer &&
first_producer == third_producer => {
let first_idx = first_raw.reinterpret_as_int()
let second_idx = second_raw.reinterpret_as_int()
if first_idx >= 0 &&
second_idx >= 0 &&
first_idx < local_gets.length() &&
second_idx < local_gets.length() &&
first_idx < local_sets.length() &&
second_idx < local_sets.length() &&
first_idx < local_tees.length() &&
second_idx < local_tees.length() &&
local_gets[first_idx] == 1 &&
local_sets[first_idx] == 1 &&
local_tees[first_idx] == 0 &&
local_gets[second_idx] == 1 &&
local_sets[second_idx] == 1 &&
local_tees[second_idx] == 0 {
local_gets[first_idx] = 0
local_sets[first_idx] = 0
local_gets[second_idx] = 0
local_sets[second_idx] = 0
rewritten.push(@lib.Instruction::local_get(consumer_arg0))
rewritten.push(@lib.Instruction::local_get(consumer_arg1))
rewritten.push(@lib.Instruction::local_get(consumer_arg2))
rewritten.push(@lib.Instruction::local_get(consumer_arg3))
rewritten.push(@lib.Instruction::local_get(first_arg0))
rewritten.push(@lib.Instruction::local_get(first_arg1))
rewritten.push(@lib.Instruction::call(first_producer))
rewritten.push(@lib.Instruction::local_get(second_arg0))
rewritten.push(@lib.Instruction::local_get(second_arg1))
rewritten.push(@lib.Instruction::call(second_producer))
rewritten.push(@lib.Instruction::local_get(third_arg0))
rewritten.push(@lib.Instruction::local_get(third_arg1))
rewritten.push(@lib.Instruction::call(third_producer))
rewritten.push(@lib.Instruction::call(consumer))
rewritten.push(@lib.Instruction::br(label))
rewrite_count = rewrite_count + 1
idx += 19
continue
}
}
_ => ()
}
}
if idx + 13 < instrs.length() {
match
(
instrs[idx],
instrs[idx + 1],
instrs[idx + 2],
instrs[idx + 3],
instrs[idx + 4],
instrs[idx + 5],
instrs[idx + 6],
instrs[idx + 7],
instrs[idx + 8],
instrs[idx + 9],
instrs[idx + 10],
instrs[idx + 11],
instrs[idx + 12],
instrs[idx + 13],
) {
(
@lib.LocalGet(first_arg0),
@lib.LocalGet(first_arg1),
@lib.Call(first_producer),
@lib.LocalSet(@lib.LocalIdx(target_raw) as target),
@lib.LocalGet(consumer_arg0),
@lib.LocalGet(consumer_arg1),
@lib.LocalGet(consumer_arg2),
@lib.LocalGet(consumer_arg3),
@lib.LocalGet(second_arg0),
@lib.LocalGet(second_arg1),
@lib.Call(second_producer),
@lib.LocalGet(readback),
@lib.Call(consumer),
@lib.Br(label),
) if target == readback &&
first_arg0 == second_arg0 &&
first_arg1 == second_arg1 &&
first_producer == second_producer => {
let target_idx = target_raw.reinterpret_as_int()
if target_idx >= 0 &&
target_idx < local_gets.length() &&
target_idx < local_sets.length() &&
target_idx < local_tees.length() &&
local_gets[target_idx] == 1 &&
local_sets[target_idx] == 1 &&
local_tees[target_idx] == 0 {
local_gets[target_idx] = 0
local_sets[target_idx] = 0
rewritten.push(@lib.Instruction::local_get(consumer_arg0))
rewritten.push(@lib.Instruction::local_get(consumer_arg1))
rewritten.push(@lib.Instruction::local_get(consumer_arg2))
rewritten.push(@lib.Instruction::local_get(consumer_arg3))
rewritten.push(@lib.Instruction::local_get(first_arg0))
rewritten.push(@lib.Instruction::local_get(first_arg1))
rewritten.push(@lib.Instruction::call(first_producer))
rewritten.push(@lib.Instruction::local_get(second_arg0))
rewritten.push(@lib.Instruction::local_get(second_arg1))
rewritten.push(@lib.Instruction::call(second_producer))
rewritten.push(@lib.Instruction::call(consumer))
rewritten.push(@lib.Instruction::br(label))
rewrite_count = rewrite_count + 1
idx += 14
continue
}
}
_ => ()
}
}
if idx + 20 < instrs.length() && instrs[idx] is @lib.LocalTee(_) {
match
(
instrs[idx],
instrs[idx + 1],
instrs[idx + 2],
instrs[idx + 3],
instrs[idx + 4],
instrs[idx + 5],
instrs[idx + 6],
instrs[idx + 7],
instrs[idx + 8],
instrs[idx + 9],
instrs[idx + 10],
instrs[idx + 11],
instrs[idx + 12],
instrs[idx + 13],
instrs[idx + 14],
instrs[idx + 15],
instrs[idx + 16],
instrs[idx + 17],
instrs[idx + 18],
instrs[idx + 19],
instrs[idx + 20],
) {
(
@lib.LocalTee(@lib.LocalIdx(aggregate_raw) as aggregate),
@lib.StructGet(first_type, first_field),
@lib.LocalGet(second_aggregate),
@lib.StructGet(second_type, second_field),
@lib.LocalGet(third_aggregate),
@lib.StructGet(third_type, third_field),
@lib.LocalGet(fourth_aggregate),
@lib.StructGet(fourth_type, fourth_field),
@lib.LocalSet(@lib.LocalIdx(fourth_raw) as fourth),
@lib.LocalSet(@lib.LocalIdx(third_raw) as third),
@lib.LocalSet(@lib.LocalIdx(second_raw) as second),
@lib.LocalSet(@lib.LocalIdx(first_raw) as first),
@lib.LocalGet(consumer_arg0),
@lib.LocalGet(second_read),
@lib.LocalGet(third_read),
@lib.LocalGet(first_read),
@lib.I64ExtendI32S,
@lib.LocalGet(fourth_read),
@lib.I64ExtendI32S,
@lib.Call(consumer),
@lib.Br(label),
) if aggregate == second_aggregate &&
aggregate == third_aggregate &&
aggregate == fourth_aggregate &&
first == first_read &&
second == second_read &&
third == third_read &&
fourth == fourth_read &&
first_type == second_type &&
first_type == third_type &&
first_type == fourth_type => {
let aggregate_idx = aggregate_raw.reinterpret_as_int()
let first_idx = first_raw.reinterpret_as_int()
let second_idx = second_raw.reinterpret_as_int()
let third_idx = third_raw.reinterpret_as_int()
let fourth_idx = fourth_raw.reinterpret_as_int()
if aggregate_idx >= 0 &&
aggregate_idx < local_gets.length() &&
aggregate_idx < local_sets.length() &&
aggregate_idx < local_tees.length() &&
first_idx >= 0 &&
second_idx >= 0 &&
third_idx >= 0 &&
fourth_idx >= 0 &&
first_idx < local_gets.length() &&
second_idx < local_gets.length() &&
third_idx < local_gets.length() &&
fourth_idx < local_gets.length() &&
aggregate_idx != first_idx &&
aggregate_idx != second_idx &&
aggregate_idx != third_idx &&
aggregate_idx != fourth_idx &&
first_idx != second_idx &&
first_idx != third_idx &&
first_idx != fourth_idx &&
second_idx != third_idx &&
second_idx != fourth_idx &&
third_idx != fourth_idx &&
local_gets[first_idx] == 1 &&
local_sets[first_idx] == 1 &&
local_tees[first_idx] == 0 &&
local_gets[second_idx] == 1 &&
local_sets[second_idx] == 1 &&
local_tees[second_idx] == 0 &&
local_gets[third_idx] == 1 &&
local_sets[third_idx] == 1 &&
local_tees[third_idx] == 0 &&
local_gets[fourth_idx] == 1 &&
local_sets[fourth_idx] == 1 &&
local_tees[fourth_idx] == 0 {
local_gets[aggregate_idx] = local_gets[aggregate_idx] + 1
local_sets[aggregate_idx] = local_sets[aggregate_idx] + 1
local_tees[aggregate_idx] = local_tees[aggregate_idx] - 1
local_gets[first_idx] = 0
local_sets[first_idx] = 0
local_gets[second_idx] = 0
local_sets[second_idx] = 0
local_gets[third_idx] = 0
local_sets[third_idx] = 0
local_gets[fourth_idx] = 0
local_sets[fourth_idx] = 0
rewritten.push(@lib.Instruction::local_set(aggregate))
rewritten.push(@lib.Instruction::local_get(consumer_arg0))
rewritten.push(@lib.Instruction::local_get(aggregate))
rewritten.push(
@lib.Instruction::struct_get(second_type, second_field),
)
rewritten.push(@lib.Instruction::local_get(aggregate))
rewritten.push(
@lib.Instruction::struct_get(third_type, third_field),
)
rewritten.push(@lib.Instruction::local_get(aggregate))
rewritten.push(
@lib.Instruction::struct_get(first_type, first_field),
)
rewritten.push(@lib.Instruction::i64_extend_i32s())
rewritten.push(@lib.Instruction::local_get(aggregate))
rewritten.push(
@lib.Instruction::struct_get(fourth_type, fourth_field),
)
rewritten.push(@lib.Instruction::i64_extend_i32s())
rewritten.push(@lib.Instruction::call(consumer))
rewritten.push(@lib.Instruction::br(label))
rewrite_count = rewrite_count + 1
idx += 21
continue
}
}
_ => ()
}
}
if idx + 2 < instrs.length() {
match (instrs[idx], instrs[idx + 1], instrs[idx + 2]) {
(producer, @lib.LocalSet(target), @lib.LocalGet(readback)) if target ==
readback &&
run_hot_pipeline_dae_readback_producer_can_tee(producer) => {
rewritten.push(producer)
rewritten.push(@lib.Instruction::local_tee(target))
rewrite_count = rewrite_count + 1
idx += 3
continue
}
_ => ()
}
}
let (next, nested_count) = run_hot_pipeline_dae_tee_struct_get_readbacks_with_counts_instr(
instrs[idx],
local_gets,
local_sets,
local_tees,
local_types,
type_sec~,
)
rewritten.push(next)
rewrite_count = rewrite_count + nested_count
idx += 1
}
(rewritten, rewrite_count)
}
///|
fn run_hot_pipeline_dae_tee_struct_get_readbacks_with_counts(
instrs : Array[@lib.Instruction],
local_gets : Array[Int32],
local_sets : Array[Int32],
local_tees : Array[Int32],
) -> (Array[@lib.Instruction], Int32) {
run_hot_pipeline_dae_tee_struct_get_readbacks_with_counts_and_types(
instrs,
local_gets,
local_sets,
local_tees,
[],
)
}
///|
fn run_hot_pipeline_dae_tee_struct_get_readbacks(
instrs : Array[@lib.Instruction],
) -> (Array[@lib.Instruction], Int32) {
run_hot_pipeline_dae_tee_struct_get_readbacks_with_counts(instrs, [], [], [])
}
///|
fn run_hot_pipeline_dae_replace_local_get_with_i64_zero(
instr : @lib.Instruction,
target : @lib.LocalIdx,
) -> (@lib.Instruction, Int32) {
match instr {
@lib.LocalGet(idx) if idx == target =>
(@lib.Instruction::i64_const(@lib.I64(0)), 1)
@lib.Block(block_type, @lib.Expr(body)) => {
let (rewritten, count) = run_hot_pipeline_dae_replace_local_gets_with_i64_zero(
body, target,
)
(@lib.Instruction::block(block_type, @lib.Expr::new(rewritten)), count)
}
@lib.Loop(block_type, @lib.Expr(body)) => {
let (rewritten, count) = run_hot_pipeline_dae_replace_local_gets_with_i64_zero(
body, target,
)
(@lib.Instruction::loop_(block_type, @lib.Expr::new(rewritten)), count)
}
@lib.TryTable(block_type, catches, @lib.Expr(body)) => {
let (rewritten, count) = run_hot_pipeline_dae_replace_local_gets_with_i64_zero(
body, target,
)
(
@lib.Instruction::try_table(
block_type,
catches,
@lib.Expr::new(rewritten),
),
count,
)
}
@lib.If(block_type, then_body, else_body) => {
let (rewritten_then, then_count) = run_hot_pipeline_dae_replace_local_gets_with_i64_zero(
then_body, target,
)
let mut count = then_count
let rewritten_else = match else_body {
Some(body) => {
let (rewritten, else_count) = run_hot_pipeline_dae_replace_local_gets_with_i64_zero(
body, target,
)
count = count + else_count
Some(rewritten)
}
None => None
}
(@lib.Instruction::if_(block_type, rewritten_then, rewritten_else), count)
}
_ => (instr, 0)
}
}
///|
fn run_hot_pipeline_dae_replace_local_gets_with_i64_zero(
instrs : Array[@lib.Instruction],
target : @lib.LocalIdx,
) -> (Array[@lib.Instruction], Int32) {
let rewritten : Array[@lib.Instruction] = []
let mut count = 0
for instr in instrs {
let (next, next_count) = run_hot_pipeline_dae_replace_local_get_with_i64_zero(
instr, target,
)
rewritten.push(next)
count = count + next_count
}
(rewritten, count)
}
///|
// Materialize only the artifact's exact one-set/sixteen-get i64.zero carrier.
// The constant is pure and nontrapping, so duplicating it at each read removes
// the carrier without moving any effect or trap. Other counts and parameters
// reject instead of generalizing SimplifyLocals behavior.
#warnings("-unused_value")
fn run_hot_pipeline_dae_materialize_exact_i64_zero_local_instrs(
instrs : Array[@lib.Instruction],
local_types : Array[@lib.ValType],
param_count : Int32,
) -> (Array[@lib.Instruction], Int32) {
let local_gets : Array[Int32] = Array::make(local_types.length(), 0)
let local_sets : Array[Int32] = Array::make(local_types.length(), 0)
let local_tees : Array[Int32] = Array::make(local_types.length(), 0)
run_hot_pipeline_raw_collect_local_ref_counts(
instrs, local_gets, local_sets, local_tees,
)
for target_idx = param_count
target_idx < local_types.length()
target_idx = target_idx + 1 {
if local_types[target_idx] != @lib.ValType::i64() ||
local_gets[target_idx] != 16 ||
local_sets[target_idx] != 1 ||
local_tees[target_idx] != 0 {
continue
}
let target = @lib.LocalIdx::new(target_idx.reinterpret_as_uint())
let mut producer_idx = 0
while producer_idx + 1 < instrs.length() {
match (instrs[producer_idx], instrs[producer_idx + 1]) {
(@lib.I64Const(@lib.I64(0)), @lib.LocalSet(idx)) if idx == target => {
let without_producer : Array[@lib.Instruction] = []
for idx = 0; idx < instrs.length(); idx = idx + 1 {
if idx != producer_idx && idx != producer_idx + 1 {
without_producer.push(instrs[idx])
}
}
let (rewritten, replacement_count) = run_hot_pipeline_dae_replace_local_gets_with_i64_zero(
without_producer, target,
)
if replacement_count == 16 {
return (rewritten, 1)
}
}
_ => ()
}
producer_idx += 1
}
}
(instrs, 0)
}
///|
fn run_hot_pipeline_dae_replace_local_get(
instr : @lib.Instruction,
target : @lib.LocalIdx,
source : @lib.LocalIdx,
) -> (@lib.Instruction, Int32) {
match instr {
@lib.LocalGet(idx) if idx == target =>
(@lib.Instruction::local_get(source), 1)
@lib.Block(block_type, @lib.Expr(body)) => {
let (rewritten, count) = run_hot_pipeline_dae_replace_local_gets(
body, target, source,
)
(@lib.Instruction::block(block_type, @lib.Expr::new(rewritten)), count)
}
@lib.Loop(block_type, @lib.Expr(body)) => {
let (rewritten, count) = run_hot_pipeline_dae_replace_local_gets(
body, target, source,
)
(@lib.Instruction::loop_(block_type, @lib.Expr::new(rewritten)), count)
}
@lib.TryTable(block_type, catches, @lib.Expr(body)) => {
let (rewritten, count) = run_hot_pipeline_dae_replace_local_gets(
body, target, source,
)
(
@lib.Instruction::try_table(
block_type,
catches,
@lib.Expr::new(rewritten),
),
count,
)
}
@lib.If(block_type, then_body, else_body) => {
let (rewritten_then, then_count) = run_hot_pipeline_dae_replace_local_gets(
then_body, target, source,
)
let mut count = then_count
let rewritten_else = match else_body {
Some(body) => {
let (rewritten, else_count) = run_hot_pipeline_dae_replace_local_gets(
body, target, source,
)
count = count + else_count
Some(rewritten)
}
None => None
}
(@lib.Instruction::if_(block_type, rewritten_then, rewritten_else), count)
}
_ => (instr, 0)
}
}
///|
fn run_hot_pipeline_dae_replace_local_gets(
instrs : Array[@lib.Instruction],
target : @lib.LocalIdx,
source : @lib.LocalIdx,
) -> (Array[@lib.Instruction], Int32) {
let rewritten : Array[@lib.Instruction] = []
let mut count = 0
for instr in instrs {
let (next, next_count) = run_hot_pipeline_dae_replace_local_get(
instr, target, source,
)
rewritten.push(next)
count = count + next_count
}
(rewritten, count)
}
///|
// Binaryen v130 SimplifyLocals/structured cleanup leaves the four exact
// aggregate fields as block results. Once the only exit is the terminal self
// branch, falling through carries the same values without changing effects.
fn run_hot_pipeline_dae_try_fold_four_result_terminal_self_branch(
block_type : @lib.BlockType,
body : Array[@lib.Instruction],
type_sec : @lib.TypeSec?,
local_types : Array[@lib.ValType],
local_gets : Array[Int32],
local_sets : Array[Int32],
local_tees : Array[Int32],
) -> Array[@lib.Instruction]? {
if dae_block_type_zero_param_result_count(block_type, type_sec) != Some(4) ||
body.length() != 14 {
return None
}
match
(
body[0],
body[1],
body[2],
body[3],
body[4],
body[5],
body[6],
body[7],
body[8],
body[9],
body[10],
body[11],
body[12],
body[13],
) {
(
@lib.LocalGet(_),
@lib.LocalGet(_),
@lib.LocalGet(_),
@lib.LocalGet(_),
@lib.Call(_),
@lib.LocalTee(@lib.LocalIdx(aggregate_raw) as aggregate),
@lib.StructGet(struct_type, @lib.U32(2U)),
@lib.LocalGet(second_aggregate),
@lib.StructGet(second_type, @lib.U32(0U)),
@lib.LocalGet(third_aggregate),
@lib.StructGet(third_type, @lib.U32(1U)),
@lib.LocalGet(fourth_aggregate),
@lib.StructGet(fourth_type, @lib.U32(3U)),
@lib.Br(@lib.LabelIdx(0U)),
) if aggregate == second_aggregate &&
aggregate == third_aggregate &&
aggregate == fourth_aggregate &&
struct_type == second_type &&
struct_type == third_type &&
struct_type == fourth_type => {
let aggregate_idx = aggregate_raw.reinterpret_as_int()
if aggregate_idx < 0 ||
aggregate_idx >= local_gets.length() ||
!run_hot_pipeline_dae_local_is_nonnullable_ref(
local_types, aggregate_idx,
) ||
local_gets[aggregate_idx] != 3 ||
local_sets[aggregate_idx] != 0 ||
local_tees[aggregate_idx] != 1 {
return None
}
let rewritten = body.copy()
ignore(rewritten.pop())
Some(rewritten)
}
_ => None
}
}
///|
// After the exact terminal branch is folded, the zero-parameter result block
// has no remaining control purpose. Splicing its exact aggregate-field body
// preserves the four fallthrough values and avoids carrying Type 309.
fn run_hot_pipeline_dae_try_flatten_four_result_fallthrough_block(
block_type : @lib.BlockType,
body : Array[@lib.Instruction],
type_sec : @lib.TypeSec?,
local_types : Array[@lib.ValType],
local_gets : Array[Int32],
local_sets : Array[Int32],
local_tees : Array[Int32],
) -> Array[@lib.Instruction]? {
if dae_block_type_zero_param_result_count(block_type, type_sec) != Some(4) ||
body.length() != 13 {
return None
}
match
(
body[0],
body[1],
body[2],
body[3],
body[4],
body[5],
body[6],
body[7],
body[8],
body[9],
body[10],
body[11],
body[12],
) {
(
@lib.LocalGet(_),
@lib.LocalGet(_),
@lib.LocalGet(_),
@lib.LocalGet(_),
@lib.Call(_),
@lib.LocalTee(@lib.LocalIdx(aggregate_raw) as aggregate),
@lib.StructGet(struct_type, @lib.U32(2U)),
@lib.LocalGet(second_aggregate),
@lib.StructGet(second_type, @lib.U32(0U)),
@lib.LocalGet(third_aggregate),
@lib.StructGet(third_type, @lib.U32(1U)),
@lib.LocalGet(fourth_aggregate),
@lib.StructGet(fourth_type, @lib.U32(3U)),
) if aggregate == second_aggregate &&
aggregate == third_aggregate &&
aggregate == fourth_aggregate &&
struct_type == second_type &&
struct_type == third_type &&
struct_type == fourth_type => {
let aggregate_idx = aggregate_raw.reinterpret_as_int()
if aggregate_idx < 0 ||
aggregate_idx >= local_gets.length() ||
!run_hot_pipeline_dae_local_is_nonnullable_ref(
local_types, aggregate_idx,
) ||
local_gets[aggregate_idx] != 3 ||
local_sets[aggregate_idx] != 0 ||
local_tees[aggregate_idx] != 1 {
return None
}
Some(body)
}
_ => None
}
}
///|
fn run_hot_pipeline_dae_forward_one_use_immutable_param_copy_instr(
instr : @lib.Instruction,
param_count : Int32,
local_types : Array[@lib.ValType],
local_gets : Array[Int32],
local_sets : Array[Int32],
local_tees : Array[Int32],
type_sec : @lib.TypeSec?,
) -> (@lib.Instruction, Int32) {
match instr {
@lib.Block(block_type, @lib.Expr(body)) => {
let (rewritten, count) = run_hot_pipeline_dae_forward_one_use_immutable_param_copies(
body, param_count, local_types, local_gets, local_sets, local_tees, type_sec,
)
match
run_hot_pipeline_dae_try_fold_four_result_terminal_self_branch(
block_type, rewritten, type_sec, local_types, local_gets, local_sets, local_tees,
) {
Some(folded) =>
(
@lib.Instruction::block(block_type, @lib.Expr::new(folded)),
count + 1,
)
None =>
(
@lib.Instruction::block(block_type, @lib.Expr::new(rewritten)),
count,
)
}
}
@lib.Loop(block_type, @lib.Expr(body)) => {
let (rewritten, count) = run_hot_pipeline_dae_forward_one_use_immutable_param_copies(
body, param_count, local_types, local_gets, local_sets, local_tees, type_sec,
)
(@lib.Instruction::loop_(block_type, @lib.Expr::new(rewritten)), count)
}
@lib.TryTable(block_type, catches, @lib.Expr(body)) => {
let (rewritten, count) = run_hot_pipeline_dae_forward_one_use_immutable_param_copies(
body, param_count, local_types, local_gets, local_sets, local_tees, type_sec,
)
(
@lib.Instruction::try_table(
block_type,
catches,
@lib.Expr::new(rewritten),
),
count,
)
}
@lib.If(block_type, then_body, else_body) => {
let (rewritten_then, then_count) = run_hot_pipeline_dae_forward_one_use_immutable_param_copies(
then_body, param_count, local_types, local_gets, local_sets, local_tees,
type_sec,
)
let mut count = then_count
let rewritten_else = match else_body {
Some(body) => {
let (rewritten, else_count) = run_hot_pipeline_dae_forward_one_use_immutable_param_copies(
body, param_count, local_types, local_gets, local_sets, local_tees, type_sec,
)
count = count + else_count
Some(rewritten)
}
None => None
}
(@lib.Instruction::if_(block_type, rewritten_then, rewritten_else), count)
}
_ => (instr, 0)
}
}
///|
fn run_hot_pipeline_dae_local_is_nonnullable_ref(
local_types : Array[@lib.ValType],
local_idx : Int32,
) -> Bool {
if local_idx < 0 || local_idx >= local_types.length() {
return false
}
match local_types[local_idx] {
@lib.RefTypeValType(ref_type) => !ref_type.is_nullable()
_ => false
}
}
///|
fn run_hot_pipeline_dae_try_collapse_two_field_aggregate_spill(
instrs : Array[@lib.Instruction],
idx : Int32,
local_types : Array[@lib.ValType],
local_gets : Array[Int32],
local_sets : Array[Int32],
local_tees : Array[Int32],
) -> Array[@lib.Instruction]? {
if idx + 7 >= instrs.length() {
return None
}
match
(
instrs[idx],
instrs[idx + 1],
instrs[idx + 2],
instrs[idx + 3],
instrs[idx + 4],
instrs[idx + 5],
instrs[idx + 6],
instrs[idx + 7],
) {
(
@lib.LocalTee(@lib.LocalIdx(aggregate_raw) as aggregate),
@lib.StructGet(first_type, first_field),
@lib.LocalSet(@lib.LocalIdx(first_raw) as first),
@lib.LocalGet(next_aggregate),
@lib.StructGet(second_type, second_field),
@lib.LocalSet(@lib.LocalIdx(second_raw) as second),
@lib.LocalGet(first_read),
@lib.LocalGet(second_read),
) if aggregate == next_aggregate &&
first == first_read &&
second == second_read &&
first_type == second_type => {
let aggregate_idx = aggregate_raw.reinterpret_as_int()
let first_idx = first_raw.reinterpret_as_int()
let second_idx = second_raw.reinterpret_as_int()
if first_idx == second_idx ||
first_idx == aggregate_idx ||
second_idx == aggregate_idx ||
first_idx < 0 ||
second_idx < 0 ||
first_idx >= local_gets.length() ||
second_idx >= local_gets.length() ||
!run_hot_pipeline_dae_local_is_nonnullable_ref(
local_types, aggregate_idx,
) ||
local_gets[first_idx] != 1 ||
local_sets[first_idx] != 1 ||
local_tees[first_idx] != 0 ||
local_gets[second_idx] != 1 ||
local_sets[second_idx] != 1 ||
local_tees[second_idx] != 0 {
return None
}
local_gets[first_idx] = 0
local_sets[first_idx] = 0
local_gets[second_idx] = 0
local_sets[second_idx] = 0
Some([
@lib.Instruction::local_tee(aggregate),
@lib.Instruction::struct_get(first_type, first_field),
@lib.Instruction::local_get(aggregate),
@lib.Instruction::struct_get(second_type, second_field),
])
}
_ => None
}
}
///|
fn run_hot_pipeline_dae_try_forward_four_field_aggregate_leader(
instrs : Array[@lib.Instruction],
idx : Int32,
local_types : Array[@lib.ValType],
local_gets : Array[Int32],
local_sets : Array[Int32],
local_tees : Array[Int32],
) -> Array[@lib.Instruction]? {
if idx + 15 >= instrs.length() {
return None
}
match
(
instrs[idx],
instrs[idx + 1],
instrs[idx + 2],
instrs[idx + 3],
instrs[idx + 4],
instrs[idx + 5],
instrs[idx + 6],
instrs[idx + 7],
instrs[idx + 8],
instrs[idx + 9],
instrs[idx + 10],
instrs[idx + 11],
instrs[idx + 12],
instrs[idx + 13],
instrs[idx + 14],
instrs[idx + 15],
) {
(
@lib.LocalTee(@lib.LocalIdx(aggregate_raw) as aggregate),
@lib.StructGet(first_type, first_field),
@lib.LocalSet(@lib.LocalIdx(first_raw) as first),
@lib.LocalGet(second_aggregate),
@lib.StructGet(second_type, second_field),
@lib.LocalSet(@lib.LocalIdx(second_raw) as second),
@lib.LocalGet(third_aggregate),
@lib.StructGet(leader_type, leader_field),
@lib.LocalSet(@lib.LocalIdx(leader_raw) as leader),
@lib.LocalGet(fourth_aggregate),
@lib.StructGet(fourth_type, fourth_field),
@lib.LocalSet(@lib.LocalIdx(fourth_raw) as fourth),
@lib.LocalGet(leader_read),
@lib.LocalGet(first_read),
@lib.LocalGet(second_read),
@lib.LocalGet(fourth_read),
) if aggregate == second_aggregate &&
aggregate == third_aggregate &&
aggregate == fourth_aggregate &&
first == first_read &&
second == second_read &&
leader == leader_read &&
fourth == fourth_read &&
first_type == second_type &&
first_type == leader_type &&
first_type == fourth_type => {
let aggregate_idx = aggregate_raw.reinterpret_as_int()
let first_idx = first_raw.reinterpret_as_int()
let second_idx = second_raw.reinterpret_as_int()
let leader_idx = leader_raw.reinterpret_as_int()
let fourth_idx = fourth_raw.reinterpret_as_int()
if first_idx < 0 ||
second_idx < 0 ||
leader_idx < 0 ||
fourth_idx < 0 ||
first_idx >= local_gets.length() ||
second_idx >= local_gets.length() ||
leader_idx >= local_gets.length() ||
fourth_idx >= local_gets.length() ||
aggregate_idx == first_idx ||
aggregate_idx == second_idx ||
aggregate_idx == leader_idx ||
aggregate_idx == fourth_idx ||
first_idx == second_idx ||
first_idx == leader_idx ||
first_idx == fourth_idx ||
second_idx == leader_idx ||
second_idx == fourth_idx ||
leader_idx == fourth_idx ||
!run_hot_pipeline_dae_local_is_nonnullable_ref(
local_types, aggregate_idx,
) ||
local_gets[first_idx] != 1 ||
local_sets[first_idx] != 1 ||
local_tees[first_idx] != 0 ||
local_gets[second_idx] != 1 ||
local_sets[second_idx] != 1 ||
local_tees[second_idx] != 0 ||
local_gets[leader_idx] != 1 ||
local_sets[leader_idx] != 1 ||
local_tees[leader_idx] != 0 ||
local_gets[fourth_idx] != 1 ||
local_sets[fourth_idx] != 1 ||
local_tees[fourth_idx] != 0 {
return None
}
local_gets[leader_idx] = 0
local_sets[leader_idx] = 0
Some([
@lib.Instruction::local_tee(aggregate),
@lib.Instruction::struct_get(first_type, first_field),
@lib.Instruction::local_set(first),
@lib.Instruction::local_get(aggregate),
@lib.Instruction::struct_get(second_type, second_field),
@lib.Instruction::local_set(second),
@lib.Instruction::local_get(aggregate),
@lib.Instruction::struct_get(fourth_type, fourth_field),
@lib.Instruction::local_set(fourth),
@lib.Instruction::local_get(aggregate),
@lib.Instruction::struct_get(leader_type, leader_field),
@lib.Instruction::local_get(first),
@lib.Instruction::local_get(second),
@lib.Instruction::local_get(fourth),
])
}
_ => None
}
}
///|
fn run_hot_pipeline_dae_try_collapse_residual_four_field_aggregate_spill(
instrs : Array[@lib.Instruction],
idx : Int32,
local_types : Array[@lib.ValType],
local_gets : Array[Int32],
local_sets : Array[Int32],
local_tees : Array[Int32],
) -> Array[@lib.Instruction]? {
if idx + 13 >= instrs.length() {
return None
}
match
(
instrs[idx],
instrs[idx + 1],
instrs[idx + 2],
instrs[idx + 3],
instrs[idx + 4],
instrs[idx + 5],
instrs[idx + 6],
instrs[idx + 7],
instrs[idx + 8],
instrs[idx + 9],
instrs[idx + 10],
instrs[idx + 11],
instrs[idx + 12],
instrs[idx + 13],
) {
(
@lib.LocalTee(@lib.LocalIdx(aggregate_raw) as aggregate),
@lib.StructGet(first_type, first_field),
@lib.LocalSet(@lib.LocalIdx(first_raw) as first),
@lib.LocalGet(second_aggregate),
@lib.StructGet(second_type, second_field),
@lib.LocalSet(@lib.LocalIdx(second_raw) as second),
@lib.LocalGet(fourth_aggregate),
@lib.StructGet(fourth_type, fourth_field),
@lib.LocalSet(@lib.LocalIdx(fourth_raw) as fourth),
@lib.LocalGet(leader_aggregate),
@lib.StructGet(leader_type, leader_field),
@lib.LocalGet(first_read),
@lib.LocalGet(second_read),
@lib.LocalGet(fourth_read),
) if aggregate == second_aggregate &&
aggregate == fourth_aggregate &&
aggregate == leader_aggregate &&
first == first_read &&
second == second_read &&
fourth == fourth_read &&
first_type == second_type &&
first_type == fourth_type &&
first_type == leader_type => {
let aggregate_idx = aggregate_raw.reinterpret_as_int()
let first_idx = first_raw.reinterpret_as_int()
let second_idx = second_raw.reinterpret_as_int()
let fourth_idx = fourth_raw.reinterpret_as_int()
if first_idx < 0 ||
second_idx < 0 ||
fourth_idx < 0 ||
first_idx >= local_gets.length() ||
second_idx >= local_gets.length() ||
fourth_idx >= local_gets.length() ||
aggregate_idx == first_idx ||
aggregate_idx == second_idx ||
aggregate_idx == fourth_idx ||
first_idx == second_idx ||
first_idx == fourth_idx ||
second_idx == fourth_idx ||
!run_hot_pipeline_dae_local_is_nonnullable_ref(
local_types, aggregate_idx,
) ||
local_gets[first_idx] != 1 ||
local_sets[first_idx] != 1 ||
local_tees[first_idx] != 0 ||
local_gets[second_idx] != 1 ||
local_sets[second_idx] != 1 ||
local_tees[second_idx] != 0 ||
local_gets[fourth_idx] != 1 ||
local_sets[fourth_idx] != 1 ||
local_tees[fourth_idx] != 0 {
return None
}
local_gets[first_idx] = 0
local_sets[first_idx] = 0
local_gets[second_idx] = 0
local_sets[second_idx] = 0
local_gets[fourth_idx] = 0
local_sets[fourth_idx] = 0
Some([
@lib.Instruction::local_tee(aggregate),
@lib.Instruction::struct_get(leader_type, leader_field),
@lib.Instruction::local_get(aggregate),
@lib.Instruction::struct_get(first_type, first_field),
@lib.Instruction::local_get(aggregate),
@lib.Instruction::struct_get(second_type, second_field),
@lib.Instruction::local_get(aggregate),
@lib.Instruction::struct_get(fourth_type, fourth_field),
])
}
_ => None
}
}
///|
fn run_hot_pipeline_dae_zero_param_single_block_result_type(
block_type : @lib.BlockType,
type_sec : @lib.TypeSec?,
) -> @lib.ValType? {
match block_type {
@lib.ValTypeBlockType(result) => Some(result)
@lib.TypeIdxBlockType(type_idx) =>
match type_sec {
Some(type_sec) =>
match dae_type_sec_func_type(type_sec, type_idx) {
Some(@lib.FuncType(params, results)) =>
if params.length() == 0 && results.length() == 1 {
Some(results[0])
} else {
None
}
_ => None
}
None => None
}
@lib.VoidBlockType => None
}
}
///|
// Binaryen's structured SimplifyLocals path carries exact one-use block results
// directly into their consumers. This fused matcher keeps the existing selected
// traversal cheap: zero arguments move before zero-parameter blocks, while pure
// caller-local call arguments move only when the block cannot rewrite them.
fn run_hot_pipeline_dae_try_sink_selected_block_result_transport(
instrs : Array[@lib.Instruction],
idx : Int32,
param_count : Int32,
local_types : Array[@lib.ValType],
local_gets : Array[Int32],
local_sets : Array[Int32],
local_tees : Array[Int32],
type_sec : @lib.TypeSec?,
) -> (Array[@lib.Instruction], Int32)? {
if idx + 1 >= instrs.length() {
return None
}
match (instrs[idx], instrs[idx + 1]) {
(
@lib.Block(block_type, @lib.Expr(producer_body)) as producer,
@lib.LocalSet(@lib.LocalIdx(target_raw) as target),
) => {
let target_idx = target_raw.reinterpret_as_int()
let result_type = match
run_hot_pipeline_dae_zero_param_single_block_result_type(
block_type, type_sec,
) {
Some(result_type) => result_type
None => return None
}
if target_idx < param_count ||
target_idx < 0 ||
target_idx >= local_types.length() ||
target_idx >= local_gets.length() ||
local_types[target_idx] != result_type ||
local_gets[target_idx] != 1 ||
local_sets[target_idx] != 1 ||
local_tees[target_idx] != 0 {
return None
}
if idx + 6 < instrs.length() {
match
(
instrs[idx + 2],
instrs[idx + 3],
instrs[idx + 4],
instrs[idx + 5],
instrs[idx + 6],
) {
(
@lib.I32Const(@lib.I32(0)),
@lib.LocalGet(readback),
@lib.Call(unary_call),
@lib.StructNew(struct_type),
@lib.Return,
) if target == readback => {
local_gets[target_idx] = 0
local_sets[target_idx] = 0
return Some(
(
[
@lib.Instruction::i32_const(@lib.I32(0)),
producer,
@lib.Instruction::call(unary_call),
@lib.Instruction::struct_new(struct_type),
@lib.Instruction::return_(),
],
7,
),
)
}
_ => ()
}
}
if idx + 5 >= instrs.length() {
return None
}
match
(instrs[idx + 2], instrs[idx + 3], instrs[idx + 4], instrs[idx + 5]) {
(
@lib.I32Const(@lib.I32(0)),
@lib.LocalGet(readback),
@lib.StructNew(struct_type),
@lib.Return,
) if target == readback => {
local_gets[target_idx] = 0
local_sets[target_idx] = 0
Some(
(
[
@lib.Instruction::i32_const(@lib.I32(0)),
producer,
@lib.Instruction::struct_new(struct_type),
@lib.Instruction::return_(),
],
6,
),
)
}
(
@lib.LocalGet(@lib.LocalIdx(first_arg_raw) as first_arg),
@lib.LocalGet(@lib.LocalIdx(second_arg_raw) as second_arg),
@lib.LocalGet(readback),
@lib.Call(consumer),
) if target == readback => {
let first_arg_idx = first_arg_raw.reinterpret_as_int()
let second_arg_idx = second_arg_raw.reinterpret_as_int()
if first_arg_idx < 0 ||
second_arg_idx < 0 ||
first_arg_idx >= local_gets.length() ||
second_arg_idx >= local_gets.length() ||
first_arg_idx >= local_sets.length() ||
second_arg_idx >= local_sets.length() ||
first_arg_idx >= local_tees.length() ||
second_arg_idx >= local_tees.length() ||
target_idx == first_arg_idx ||
target_idx == second_arg_idx ||
(
(local_sets[first_arg_idx] > 0 || local_tees[first_arg_idx] > 0) &&
run_hot_pipeline_raw_branch_writes_local(
producer_body, first_arg_idx,
)
) ||
(
second_arg_idx != first_arg_idx &&
(local_sets[second_arg_idx] > 0 || local_tees[second_arg_idx] > 0) &&
run_hot_pipeline_raw_branch_writes_local(
producer_body, second_arg_idx,
)
) {
return None
}
local_gets[target_idx] = 0
local_sets[target_idx] = 0
Some(
(
[
@lib.Instruction::local_get(first_arg),
@lib.Instruction::local_get(second_arg),
producer,
@lib.Instruction::call(consumer),
],
6,
),
)
}
_ => None
}
}
_ => None
}
}
///|
// In a three-producer dispatch arm, Binaryen keeps the first two results local
// but sinks the final one through four pure consumer arguments. All producer
// effects stay ordered, and direct calls cannot observe caller-local reads.
fn run_hot_pipeline_dae_try_sink_final_three_producer_call_result(
instrs : Array[@lib.Instruction],
idx : Int32,
local_gets : Array[Int32],
local_sets : Array[Int32],
local_tees : Array[Int32],
) -> Array[@lib.Instruction]? {
if idx + 20 >= instrs.length() {
return None
}
match
(
instrs[idx],
instrs[idx + 1],
instrs[idx + 2],
instrs[idx + 3],
instrs[idx + 4],
instrs[idx + 5],
instrs[idx + 6],
instrs[idx + 7],
instrs[idx + 8],
instrs[idx + 9],
instrs[idx + 10],
instrs[idx + 11],
instrs[idx + 12],
instrs[idx + 13],
instrs[idx + 14],
instrs[idx + 15],
instrs[idx + 16],
instrs[idx + 17],
instrs[idx + 18],
instrs[idx + 19],
instrs[idx + 20],
) {
(
@lib.LocalGet(first_arg0),
@lib.LocalGet(first_arg1),
@lib.Call(first_producer),
@lib.LocalSet(first_result),
@lib.LocalGet(second_arg0),
@lib.LocalGet(second_arg1),
@lib.Call(second_producer),
@lib.LocalSet(second_result),
@lib.LocalGet(final_arg0),
@lib.LocalGet(final_arg1),
@lib.Call(final_producer),
@lib.LocalSet(@lib.LocalIdx(final_raw) as final_result),
@lib.LocalGet(consumer_arg0),
@lib.LocalGet(consumer_arg1),
@lib.LocalGet(consumer_arg2),
@lib.LocalGet(consumer_arg3),
@lib.LocalGet(final_readback),
@lib.LocalGet(second_readback),
@lib.LocalGet(first_readback),
@lib.Call(consumer),
@lib.Br(label),
) if first_result == first_readback &&
second_result == second_readback &&
final_result == final_readback => {
let final_idx = final_raw.reinterpret_as_int()
if first_result == second_result ||
final_result == first_result ||
final_result == second_result ||
final_idx < 0 ||
final_idx >= local_gets.length() ||
local_gets[final_idx] != 1 ||
local_sets[final_idx] != 1 ||
local_tees[final_idx] != 0 {
return None
}
local_gets[final_idx] = 0
local_sets[final_idx] = 0
Some([
@lib.Instruction::local_get(first_arg0),
@lib.Instruction::local_get(first_arg1),
@lib.Instruction::call(first_producer),
@lib.Instruction::local_set(first_result),
@lib.Instruction::local_get(second_arg0),
@lib.Instruction::local_get(second_arg1),
@lib.Instruction::call(second_producer),
@lib.Instruction::local_set(second_result),
@lib.Instruction::local_get(consumer_arg0),
@lib.Instruction::local_get(consumer_arg1),
@lib.Instruction::local_get(consumer_arg2),
@lib.Instruction::local_get(consumer_arg3),
@lib.Instruction::local_get(final_arg0),
@lib.Instruction::local_get(final_arg1),
@lib.Instruction::call(final_producer),
@lib.Instruction::local_get(second_result),
@lib.Instruction::local_get(first_result),
@lib.Instruction::call(consumer),
@lib.Instruction::br(label),
])
}
_ => None
}
}
///|
// In a two-producer dispatch arm, Binaryen keeps the first result local but
// sinks the final one through four pure consumer arguments. Producer order is
// unchanged, and direct calls cannot observe or mutate the caller's locals.
fn run_hot_pipeline_dae_try_sink_final_two_producer_call_result(
instrs : Array[@lib.Instruction],
idx : Int32,
local_gets : Array[Int32],
local_sets : Array[Int32],
local_tees : Array[Int32],
) -> Array[@lib.Instruction]? {
if idx + 15 >= instrs.length() {
return None
}
match
(
instrs[idx],
instrs[idx + 1],
instrs[idx + 2],
instrs[idx + 3],
instrs[idx + 4],
instrs[idx + 5],
instrs[idx + 6],
instrs[idx + 7],
instrs[idx + 8],
instrs[idx + 9],
instrs[idx + 10],
instrs[idx + 11],
instrs[idx + 12],
instrs[idx + 13],
instrs[idx + 14],
instrs[idx + 15],
) {
(
@lib.LocalGet(first_arg0),
@lib.LocalGet(first_arg1),
@lib.Call(first_producer),
@lib.LocalSet(first_result),
@lib.LocalGet(final_arg0),
@lib.LocalGet(final_arg1),
@lib.Call(final_producer),
@lib.LocalSet(@lib.LocalIdx(final_raw) as final_result),
@lib.LocalGet(consumer_arg0),
@lib.LocalGet(consumer_arg1),
@lib.LocalGet(consumer_arg2),
@lib.LocalGet(consumer_arg3),
@lib.LocalGet(final_readback),
@lib.LocalGet(first_readback),
@lib.Call(consumer),
@lib.Br(label),
) if first_result == first_readback && final_result == final_readback => {
let final_idx = final_raw.reinterpret_as_int()
if final_result == first_result ||
final_idx < 0 ||
final_idx >= local_gets.length() ||
local_gets[final_idx] != 1 ||
local_sets[final_idx] != 1 ||
local_tees[final_idx] != 0 {
return None
}
local_gets[final_idx] = 0
local_sets[final_idx] = 0
Some([
@lib.Instruction::local_get(first_arg0),
@lib.Instruction::local_get(first_arg1),
@lib.Instruction::call(first_producer),
@lib.Instruction::local_set(first_result),
@lib.Instruction::local_get(consumer_arg0),
@lib.Instruction::local_get(consumer_arg1),
@lib.Instruction::local_get(consumer_arg2),
@lib.Instruction::local_get(consumer_arg3),
@lib.Instruction::local_get(final_arg0),
@lib.Instruction::local_get(final_arg1),
@lib.Instruction::call(final_producer),
@lib.Instruction::local_get(first_result),
@lib.Instruction::call(consumer),
@lib.Instruction::br(label),
])
}
_ => None
}
}
///|
// SimplifyLocals can sink one-use direct-call results through intervening pure
// caller arguments. Exact complete producer slices keep every nested call and
// operand ordered while direct calls remain unable to observe caller locals.
fn run_hot_pipeline_dae_try_sink_one_use_call_result_through_pure_args(
instrs : Array[@lib.Instruction],
idx : Int32,
local_gets : Array[Int32],
local_sets : Array[Int32],
local_tees : Array[Int32],
) -> (Array[@lib.Instruction], Int32)? {
if idx + 10 >= instrs.length() {
return None
}
if instrs[idx] is @lib.LocalGet(_) &&
instrs[idx + 1] is @lib.LocalGet(_) &&
instrs[idx + 2] is @lib.Call(_) {
match
(
instrs[idx],
instrs[idx + 1],
instrs[idx + 2],
instrs[idx + 3],
instrs[idx + 4],
instrs[idx + 5],
instrs[idx + 6],
instrs[idx + 7],
instrs[idx + 8],
instrs[idx + 9],
instrs[idx + 10],
) {
(
@lib.LocalGet(producer_arg0),
@lib.LocalGet(producer_arg1),
@lib.Call(producer),
@lib.LocalSet(@lib.LocalIdx(target_raw) as target),
@lib.LocalGet(consumer_arg0),
@lib.LocalGet(consumer_arg1),
@lib.LocalGet(consumer_arg2),
@lib.LocalGet(consumer_arg3),
@lib.LocalGet(readback),
@lib.Call(consumer),
@lib.Br(label),
) if target == readback => {
let target_idx = target_raw.reinterpret_as_int()
if target_idx < 0 ||
target_idx >= local_gets.length() ||
local_gets[target_idx] != 1 ||
local_sets[target_idx] != 1 ||
local_tees[target_idx] != 0 {
return None
}
local_gets[target_idx] = 0
local_sets[target_idx] = 0
return Some(
(
[
@lib.Instruction::local_get(consumer_arg0),
@lib.Instruction::local_get(consumer_arg1),
@lib.Instruction::local_get(consumer_arg2),
@lib.Instruction::local_get(consumer_arg3),
@lib.Instruction::local_get(producer_arg0),
@lib.Instruction::local_get(producer_arg1),
@lib.Instruction::call(producer),
@lib.Instruction::call(consumer),
@lib.Instruction::br(label),
],
11,
),
)
}
_ => ()
}
}
if instrs[idx] is @lib.If(_, _, _) &&
idx + 27 < instrs.length() &&
instrs[idx + 1] is @lib.LocalSet(_) {
match
(
instrs[idx],
instrs[idx + 1],
instrs[idx + 2],
instrs[idx + 3],
instrs[idx + 4],
instrs[idx + 5],
instrs[idx + 6],
instrs[idx + 7],
instrs[idx + 8],
instrs[idx + 9],
instrs[idx + 10],
instrs[idx + 11],
instrs[idx + 12],
instrs[idx + 13],
instrs[idx + 14],
instrs[idx + 15],
instrs[idx + 16],
instrs[idx + 17],
instrs[idx + 18],
instrs[idx + 19],
instrs[idx + 20],
instrs[idx + 21],
instrs[idx + 22],
instrs[idx + 23],
instrs[idx + 24],
instrs[idx + 25],
instrs[idx + 26],
instrs[idx + 27],
) {
(
@lib.If(_, _, _) as selector_producer,
@lib.LocalSet(selector),
@lib.LocalGet(side_arg0),
@lib.LocalGet(side_arg1),
@lib.Call(side_prefix),
@lib.Call(side_call),
@lib.LocalSet(side_result),
@lib.LocalGet(prefix_arg0),
@lib.LocalGet(selector_readback),
@lib.LocalGet(prefix_arg2),
@lib.LocalGet(prefix_arg3),
@lib.Call(prefix_call),
@lib.LocalGet(producer_arg0),
@lib.LocalGet(side_readback),
@lib.LocalGet(producer_arg2),
@lib.Call(producer),
@lib.LocalSet(@lib.LocalIdx(target_raw) as target),
@lib.LocalGet(@lib.LocalIdx(consumer_arg0_raw) as consumer_arg0),
@lib.LocalGet(@lib.LocalIdx(consumer_arg1_raw) as consumer_arg1),
@lib.LocalGet(@lib.LocalIdx(consumer_arg2_raw) as consumer_arg2),
@lib.LocalGet(readback),
@lib.LocalGet(_),
@lib.Call(_),
@lib.Drop,
@lib.I32Const(@lib.I32(1)),
@lib.LocalGet(_),
@lib.StructNew(_),
@lib.Return,
) if selector == selector_readback &&
side_result == side_readback &&
target == readback => {
let target_idx = target_raw.reinterpret_as_int()
let consumer_arg0_idx = consumer_arg0_raw.reinterpret_as_int()
let consumer_arg1_idx = consumer_arg1_raw.reinterpret_as_int()
let consumer_arg2_idx = consumer_arg2_raw.reinterpret_as_int()
if target_idx < 0 ||
target_idx >= local_gets.length() ||
local_gets[target_idx] != 1 ||
local_sets[target_idx] != 1 ||
local_tees[target_idx] != 0 ||
consumer_arg0_idx < 0 ||
consumer_arg1_idx < 0 ||
consumer_arg2_idx < 0 ||
consumer_arg0_idx >= local_sets.length() ||
consumer_arg1_idx >= local_sets.length() ||
consumer_arg2_idx >= local_sets.length() ||
consumer_arg0_idx >= local_tees.length() ||
consumer_arg1_idx >= local_tees.length() ||
consumer_arg2_idx >= local_tees.length() ||
local_sets[consumer_arg0_idx] != 0 ||
local_sets[consumer_arg1_idx] != 0 ||
local_sets[consumer_arg2_idx] != 0 ||
local_tees[consumer_arg0_idx] != 0 ||
local_tees[consumer_arg1_idx] != 0 ||
local_tees[consumer_arg2_idx] != 0 {
return None
}
local_gets[target_idx] = 0
local_sets[target_idx] = 0
return Some(
(
[
selector_producer,
@lib.Instruction::local_set(selector),
@lib.Instruction::local_get(side_arg0),
@lib.Instruction::local_get(side_arg1),
@lib.Instruction::call(side_prefix),
@lib.Instruction::call(side_call),
@lib.Instruction::local_set(side_result),
@lib.Instruction::local_get(consumer_arg0),
@lib.Instruction::local_get(consumer_arg1),
@lib.Instruction::local_get(consumer_arg2),
@lib.Instruction::local_get(prefix_arg0),
@lib.Instruction::local_get(selector),
@lib.Instruction::local_get(prefix_arg2),
@lib.Instruction::local_get(prefix_arg3),
@lib.Instruction::call(prefix_call),
@lib.Instruction::local_get(producer_arg0),
@lib.Instruction::local_get(side_result),
@lib.Instruction::local_get(producer_arg2),
@lib.Instruction::call(producer),
],
21,
),
)
}
_ => ()
}
}
if !(instrs[idx + 1] is @lib.I32Const(_)) {
return None
}
if idx + 3 >= instrs.length() {
return None
}
if idx + 20 < instrs.length() && instrs[idx + 3] is @lib.LocalGet(_) {
match
(
instrs[idx],
instrs[idx + 1],
instrs[idx + 2],
instrs[idx + 3],
instrs[idx + 4],
instrs[idx + 5],
instrs[idx + 6],
instrs[idx + 7],
instrs[idx + 8],
instrs[idx + 9],
instrs[idx + 10],
instrs[idx + 11],
instrs[idx + 12],
instrs[idx + 13],
instrs[idx + 14],
instrs[idx + 15],
instrs[idx + 16],
instrs[idx + 17],
instrs[idx + 18],
instrs[idx + 19],
instrs[idx + 20],
) {
(
@lib.LocalGet(prefix_arg0),
@lib.I32Const(prefix_const),
@lib.LocalGet(prefix_arg2),
@lib.LocalGet(prefix_arg3),
@lib.Call(prefix_call),
@lib.LocalGet(producer_arg0),
@lib.I32Const(@lib.I32(0)),
@lib.LocalGet(producer_arg2),
@lib.Call(producer),
@lib.LocalSet(@lib.LocalIdx(target_raw) as target),
@lib.LocalGet(consumer_arg0),
@lib.LocalGet(consumer_arg1),
@lib.LocalGet(consumer_arg2),
@lib.LocalGet(readback),
@lib.LocalGet(_),
@lib.Call(_),
@lib.Drop,
@lib.I32Const(@lib.I32(1)),
@lib.LocalGet(_),
@lib.StructNew(_),
@lib.Return,
) if target == readback => {
let target_idx = target_raw.reinterpret_as_int()
if target_idx < 0 ||
target_idx >= local_gets.length() ||
local_gets[target_idx] != 1 ||
local_sets[target_idx] != 1 ||
local_tees[target_idx] != 0 {
return None
}
local_gets[target_idx] = 0
local_sets[target_idx] = 0
return Some(
(
[
@lib.Instruction::local_get(consumer_arg0),
@lib.Instruction::local_get(consumer_arg1),
@lib.Instruction::local_get(consumer_arg2),
@lib.Instruction::local_get(prefix_arg0),
@lib.Instruction::i32_const(prefix_const),
@lib.Instruction::local_get(prefix_arg2),
@lib.Instruction::local_get(prefix_arg3),
@lib.Instruction::call(prefix_call),
@lib.Instruction::local_get(producer_arg0),
@lib.Instruction::i32_const(@lib.I32(0)),
@lib.Instruction::local_get(producer_arg2),
@lib.Instruction::call(producer),
],
14,
),
)
}
_ => ()
}
}
if instrs[idx + 3] is @lib.LocalGet(_) {
return None
}
if !(instrs[idx + 3] is @lib.Call(_)) || idx + 22 >= instrs.length() {
return None
}
match
(
instrs[idx],
instrs[idx + 1],
instrs[idx + 2],
instrs[idx + 3],
instrs[idx + 4],
instrs[idx + 5],
instrs[idx + 6],
instrs[idx + 7],
instrs[idx + 8],
instrs[idx + 9],
instrs[idx + 10],
instrs[idx + 11],
instrs[idx + 12],
instrs[idx + 13],
instrs[idx + 14],
instrs[idx + 15],
instrs[idx + 16],
instrs[idx + 17],
instrs[idx + 18],
instrs[idx + 19],
instrs[idx + 20],
instrs[idx + 21],
instrs[idx + 22],
) {
(
@lib.LocalGet(prefix_arg0),
@lib.I32Const(prefix_const),
@lib.LocalGet(prefix_arg2),
@lib.Call(prefix_call),
@lib.LocalGet(producer_arg0),
@lib.I32Const(@lib.I32(0)),
@lib.LocalGet(producer_arg2),
@lib.Call(producer),
@lib.LocalSet(@lib.LocalIdx(target_raw) as target),
@lib.LocalGet(consumer_arg0),
@lib.LocalGet(readback),
@lib.Call(_),
@lib.Drop,
@lib.LocalGet(_),
@lib.Call(_),
@lib.Drop,
@lib.LocalGet(_),
@lib.Call(_),
@lib.Drop,
@lib.I32Const(@lib.I32(1)),
@lib.LocalGet(_),
@lib.StructNew(_),
@lib.Return,
) if target == readback => {
let target_idx = target_raw.reinterpret_as_int()
if target_idx < 0 ||
target_idx >= local_gets.length() ||
local_gets[target_idx] != 1 ||
local_sets[target_idx] != 1 ||
local_tees[target_idx] != 0 {
return None
}
local_gets[target_idx] = 0
local_sets[target_idx] = 0
Some(
(
[
@lib.Instruction::local_get(consumer_arg0),
@lib.Instruction::local_get(prefix_arg0),
@lib.Instruction::i32_const(prefix_const),
@lib.Instruction::local_get(prefix_arg2),
@lib.Instruction::call(prefix_call),
@lib.Instruction::local_get(producer_arg0),
@lib.Instruction::i32_const(@lib.I32(0)),
@lib.Instruction::local_get(producer_arg2),
@lib.Instruction::call(producer),
],
11,
),
)
}
_ => None
}
}
///|
fn run_hot_pipeline_dae_forward_one_use_immutable_param_copies(
instrs : Array[@lib.Instruction],
param_count : Int32,
local_types : Array[@lib.ValType],
local_gets : Array[Int32],
local_sets : Array[Int32],
local_tees : Array[Int32],
type_sec : @lib.TypeSec?,
) -> (Array[@lib.Instruction], Int32) {
let rewritten : Array[@lib.Instruction] = []
let mut rewrite_count = 0
let mut idx = 0
while idx < instrs.length() {
if idx + 2 < instrs.length() {
match (instrs[idx], instrs[idx + 1], instrs[idx + 2]) {
(
@lib.I32WrapI64,
@lib.LocalSet(@lib.LocalIdx(target_raw) as target),
@lib.LocalGet(readback),
) if target == readback => {
let target_idx = target_raw.reinterpret_as_int()
if target_idx >= 0 &&
target_idx < local_gets.length() &&
target_idx < local_sets.length() &&
target_idx < local_tees.length() &&
local_gets[target_idx] >= 1 &&
local_sets[target_idx] == 1 &&
local_tees[target_idx] == 0 {
local_gets[target_idx] = local_gets[target_idx] - 1
local_sets[target_idx] = 0
local_tees[target_idx] = 1
rewritten.push(@lib.Instruction::i32_wrap_i64())
rewritten.push(@lib.Instruction::local_tee(target))
rewrite_count = rewrite_count + 1
idx += 3
continue
}
}
_ => ()
}
}
match
run_hot_pipeline_dae_try_sink_selected_block_result_transport(
instrs, idx, param_count, local_types, local_gets, local_sets, local_tees,
type_sec,
) {
Some((replacement, consumed_len)) => {
for instr in replacement {
rewritten.push(instr)
}
rewrite_count = rewrite_count + 1
idx += consumed_len
continue
}
None => ()
}
match
run_hot_pipeline_dae_try_sink_final_three_producer_call_result(
instrs, idx, local_gets, local_sets, local_tees,
) {
Some(replacement) => {
for instr in replacement {
rewritten.push(instr)
}
rewrite_count = rewrite_count + 1
idx += 21
continue
}
None => ()
}
match
run_hot_pipeline_dae_try_sink_final_two_producer_call_result(
instrs, idx, local_gets, local_sets, local_tees,
) {
Some(replacement) => {
for instr in replacement {
rewritten.push(instr)
}
rewrite_count = rewrite_count + 1
idx += 16
continue
}
None => ()
}
match
run_hot_pipeline_dae_try_sink_one_use_call_result_through_pure_args(
instrs, idx, local_gets, local_sets, local_tees,
) {
Some((replacement, consumed_len)) => {
for instr in replacement {
rewritten.push(instr)
}
rewrite_count = rewrite_count + 1
idx += consumed_len
continue
}
None => ()
}
match
run_hot_pipeline_dae_try_collapse_two_field_aggregate_spill(
instrs, idx, local_types, local_gets, local_sets, local_tees,
) {
Some(replacement) => {
for instr in replacement {
rewritten.push(instr)
}
rewrite_count = rewrite_count + 1
idx += 8
continue
}
None => ()
}
match
run_hot_pipeline_dae_try_forward_four_field_aggregate_leader(
instrs, idx, local_types, local_gets, local_sets, local_tees,
) {
Some(replacement) => {
let final_replacement = match
run_hot_pipeline_dae_try_collapse_residual_four_field_aggregate_spill(
replacement, 0, local_types, local_gets, local_sets, local_tees,
) {
Some(collapsed) => {
rewrite_count = rewrite_count + 1
collapsed
}
None => replacement
}
for instr in final_replacement {
rewritten.push(instr)
}
rewrite_count = rewrite_count + 1
idx += 16
continue
}
None => ()
}
if idx + 1 < instrs.length() {
match (instrs[idx], instrs[idx + 1]) {
(
@lib.LocalGet(@lib.LocalIdx(source_raw) as source),
@lib.LocalSet(@lib.LocalIdx(target_raw) as target),
) => {
let source_idx = source_raw.reinterpret_as_int()
let target_idx = target_raw.reinterpret_as_int()
if source_idx >= 0 &&
source_idx < param_count &&
target_idx >= param_count &&
target_idx < local_types.length() &&
source_idx < local_types.length() &&
local_types[source_idx] == local_types[target_idx] &&
local_sets[source_idx] == 0 &&
local_tees[source_idx] == 0 &&
local_gets[target_idx] == 1 &&
local_sets[target_idx] == 1 &&
local_tees[target_idx] == 0 {
let suffix : Array[@lib.Instruction] = []
for suffix_idx = idx + 2
suffix_idx < instrs.length()
suffix_idx = suffix_idx + 1 {
suffix.push(instrs[suffix_idx])
}
let (forwarded_suffix, replacement_count) = run_hot_pipeline_dae_replace_local_gets(
suffix, target, source,
)
if replacement_count == 1 {
local_gets[target_idx] = 0
local_sets[target_idx] = 0
let (final_suffix, nested_count) = run_hot_pipeline_dae_forward_one_use_immutable_param_copies(
forwarded_suffix, param_count, local_types, local_gets, local_sets,
local_tees, type_sec,
)
for suffix_instr in final_suffix {
rewritten.push(suffix_instr)
}
return (rewritten, rewrite_count + nested_count + 1)
}
}
}
_ => ()
}
}
let (next, nested_count) = run_hot_pipeline_dae_forward_one_use_immutable_param_copy_instr(
instrs[idx],
param_count,
local_types,
local_gets,
local_sets,
local_tees,
type_sec,
)
match next {
@lib.Block(block_type, @lib.Expr(body)) =>
match
run_hot_pipeline_dae_try_flatten_four_result_fallthrough_block(
block_type, body, type_sec, local_types, local_gets, local_sets, local_tees,
) {
Some(flattened) => {
for flattened_instr in flattened {
rewritten.push(flattened_instr)
}
rewrite_count = rewrite_count + nested_count + 1
}
None => {
rewritten.push(next)
rewrite_count = rewrite_count + nested_count
}
}
_ => {
rewritten.push(next)
rewrite_count = rewrite_count + nested_count
}
}
idx += 1
}
(rewritten, rewrite_count)
}
///|
fn run_hot_pipeline_dae_rewrite_dead_tees_with_local_remap(
instrs : Array[@lib.Instruction],
local_gets : Array[Int32],
local_remap : Array[Int32],
) -> Array[@lib.Instruction] {
let rewritten : Array[@lib.Instruction] = []
for instr in instrs {
match instr {
@lib.LocalGet(@lib.LocalIdx(raw)) => {
let local_idx = raw.reinterpret_as_int()
rewritten.push(
@lib.Instruction::local_get(
@lib.LocalIdx::new(local_remap[local_idx].reinterpret_as_uint()),
),
)
}
@lib.LocalSet(@lib.LocalIdx(raw)) => {
let local_idx = raw.reinterpret_as_int()
if local_gets[local_idx] > 0 {
rewritten.push(
@lib.Instruction::local_set(
@lib.LocalIdx::new(local_remap[local_idx].reinterpret_as_uint()),
),
)
} else {
// A never-read local write is observationally identical to dropping
// the produced value; retain producer traps/effects and free the slot.
rewritten.push(@lib.Instruction::drop())
}
}
@lib.LocalTee(@lib.LocalIdx(raw)) => {
let local_idx = raw.reinterpret_as_int()
if local_gets[local_idx] > 0 {
rewritten.push(
@lib.Instruction::local_tee(
@lib.LocalIdx::new(local_remap[local_idx].reinterpret_as_uint()),
),
)
}
}
@lib.Block(block_type, @lib.Expr(body)) =>
rewritten.push(
@lib.Instruction::block(
block_type,
@lib.Expr::new(
run_hot_pipeline_dae_rewrite_dead_tees_with_local_remap(
body, local_gets, local_remap,
),
),
),
)
@lib.Loop(block_type, @lib.Expr(body)) =>
rewritten.push(
@lib.Instruction::loop_(
block_type,
@lib.Expr::new(
run_hot_pipeline_dae_rewrite_dead_tees_with_local_remap(
body, local_gets, local_remap,
),
),
),
)
@lib.TryTable(block_type, catches, @lib.Expr(body)) =>
rewritten.push(
@lib.Instruction::try_table(
block_type,
catches,
@lib.Expr::new(
run_hot_pipeline_dae_rewrite_dead_tees_with_local_remap(
body, local_gets, local_remap,
),
),
),
)
@lib.If(block_type, then_body, else_body) =>
rewritten.push(
@lib.Instruction::if_(
block_type,
run_hot_pipeline_dae_rewrite_dead_tees_with_local_remap(
then_body, local_gets, local_remap,
),
match else_body {
Some(body) =>
Some(
run_hot_pipeline_dae_rewrite_dead_tees_with_local_remap(
body, local_gets, local_remap,
),
)
None => None
},
),
)
_ => rewritten.push(instr)
}
}
rewritten
}
///|
fn run_hot_pipeline_dae_collect_consumed_call_slot_reuse_at(
instrs : Array[@lib.Instruction],
call_idx : Int32,
local_types : Array[@lib.ValType],
mod_ : @lib.Module,
func_type_indices : Array[@lib.TypeIdx],
entry_stack_values : Int32,
slot_reuse : Array[Int32],
) -> Unit {
if entry_stack_values < 0 ||
call_idx + 1 >= instrs.length() ||
!(instrs[instrs.length() - 1] is @lib.Return) {
return
}
match (instrs[call_idx], instrs[call_idx + 1]) {
(@lib.Call(producer), @lib.LocalSet(@lib.LocalIdx(target_raw))) => {
let target_idx = target_raw.reinterpret_as_int()
if target_idx < 128 || target_idx >= slot_reuse.length() {
return
}
let @lib.FuncType(params, results) = match
dae_resolve_func_type(
mod_,
func_type_indices,
dae_func_idx_slot(producer),
) {
Some(func_type) => func_type
None => return
}
let result_type = match results {
[result_type] => result_type
_ => return
}
if params.length() == 0 {
return
}
let evidence = match
dae_callsite_value_slice_evidence(
instrs,
call_idx,
params.length(),
entry_stack_values,
mod_,
func_type_indices,
) {
Some(evidence) => evidence
None => return
}
let source_instr_idx = match
dae_value_slice_single_instruction_index(evidence.slices[0]) {
Some(idx) => idx
None => return
}
let source_idx = match instrs[source_instr_idx] {
@lib.LocalGet(@lib.LocalIdx(raw)) => raw.reinterpret_as_int()
_ => return
}
if source_idx < 0 ||
target_idx <= source_idx ||
source_idx >= local_types.length() ||
target_idx >= local_types.length() ||
params[0] != local_types[source_idx] ||
result_type != local_types[target_idx] ||
local_types[source_idx] != local_types[target_idx] {
return
}
let suffix : Array[@lib.Instruction] = []
for suffix_idx = call_idx + 2
suffix_idx < instrs.length()
suffix_idx = suffix_idx + 1 {
suffix.push(instrs[suffix_idx])
}
if !run_hot_pipeline_raw_instrs_reference_local(suffix, source_idx) {
slot_reuse[target_idx] = source_idx
}
}
_ => ()
}
}
///|
// Collect the selected-function local counts and exact terminal call-result slot
// reuse evidence in one body walk. Full callsite value-slice attribution proves
// the source is the complete first argument before the result overwrites its
// slot, and the containing region returns before that source can rejoin.
fn run_hot_pipeline_dae_collect_local_counts_and_consumed_call_slot_reuse(
instrs : Array[@lib.Instruction],
local_gets : Array[Int32],
local_sets : Array[Int32],
local_tees : Array[Int32],
local_types : Array[@lib.ValType],
mod_ : @lib.Module,
func_type_indices : Array[@lib.TypeIdx],
entry_stack_values : Int32,
slot_reuse : Array[Int32],
) -> Unit {
let mut instr_idx = 0
while instr_idx < instrs.length() {
if instr_idx + 1 < instrs.length() {
match (instrs[instr_idx], instrs[instr_idx + 1]) {
(@lib.Call(_), @lib.LocalSet(@lib.LocalIdx(target_raw))) if target_raw.reinterpret_as_int() >=
128 =>
run_hot_pipeline_dae_collect_consumed_call_slot_reuse_at(
instrs, instr_idx, local_types, mod_, func_type_indices, entry_stack_values,
slot_reuse,
)
_ => ()
}
}
match instrs[instr_idx] {
@lib.LocalGet(@lib.LocalIdx(raw)) => {
let local_idx = raw.reinterpret_as_int()
if local_idx >= 0 && local_idx < local_gets.length() {
local_gets[local_idx] = local_gets[local_idx] + 1
}
}
@lib.LocalSet(@lib.LocalIdx(raw)) => {
let local_idx = raw.reinterpret_as_int()
if local_idx >= 0 && local_idx < local_sets.length() {
local_sets[local_idx] = local_sets[local_idx] + 1
}
}
@lib.LocalTee(@lib.LocalIdx(raw)) => {
let local_idx = raw.reinterpret_as_int()
if local_idx >= 0 && local_idx < local_tees.length() {
local_tees[local_idx] = local_tees[local_idx] + 1
}
}
@lib.Block(block_type, @lib.Expr(body))
| @lib.Loop(block_type, @lib.Expr(body))
| @lib.TryTable(block_type, _, @lib.Expr(body)) =>
run_hot_pipeline_dae_collect_local_counts_and_consumed_call_slot_reuse(
body,
local_gets,
local_sets,
local_tees,
local_types,
mod_,
func_type_indices,
match dae_control_entry_stack_values(block_type, mod_) {
Some(count) => count
None => -1
},
slot_reuse,
)
@lib.If(block_type, then_body, else_body) => {
let child_entry_stack_values = match
dae_control_entry_stack_values(block_type, mod_) {
Some(count) => count
None => -1
}
run_hot_pipeline_dae_collect_local_counts_and_consumed_call_slot_reuse(
then_body, local_gets, local_sets, local_tees, local_types, mod_, func_type_indices,
child_entry_stack_values, slot_reuse,
)
match else_body {
Some(body) =>
run_hot_pipeline_dae_collect_local_counts_and_consumed_call_slot_reuse(
body, local_gets, local_sets, local_tees, local_types, mod_, func_type_indices,
child_entry_stack_values, slot_reuse,
)
None => ()
}
}
_ => ()
}
instr_idx += 1
}
}
///|
fn run_hot_pipeline_dae_remove_dead_tees_and_compact_selected_locals(
mod_ : @lib.Module,
def_idx : Int32,
) -> (@lib.Module, Int32, Bool) {
let funcs = match mod_.code_sec {
Some(@lib.CodeSec(funcs)) => funcs
None => return (mod_, 0, false)
}
let func = match funcs.get(def_idx) {
Some(func) => func
None => return (mod_, 0, false)
}
let func_idx = @lib.FuncIdx::new(
(dae_imported_func_count(mod_) + def_idx).reinterpret_as_uint(),
)
let params = match
run_hot_pipeline_defined_func_params_from_sections(mod_, func_idx) {
Some(params) => params
None => return (mod_, 0, false)
}
let @lib.Func(locals, @lib.Expr(body)) = func
if locals.length() < 16 {
let (next_body, removed_tee_count) = run_hot_pipeline_dae_remove_never_read_local_tees(
body,
)
if removed_tee_count == 0 {
return (mod_, 0, false)
}
let (readback_body, _) = run_hot_pipeline_dae_tee_struct_get_readbacks(
next_body,
)
let next_funcs = funcs.copy()
next_funcs[def_idx] = @lib.Func::new(locals, @lib.Expr::new(readback_body))
return (
mod_.with_code_sec(@lib.CodeSec::new(next_funcs)),
removed_tee_count,
false,
)
}
let total_local_count = params.length() + locals.length()
let local_gets : Array[Int32] = Array::make(total_local_count, 0)
let local_sets : Array[Int32] = Array::make(total_local_count, 0)
let local_tees : Array[Int32] = Array::make(total_local_count, 0)
let local_types = params.copy()
for local_idx = 0; local_idx < locals.length(); local_idx = local_idx + 1 {
local_types.push(locals.get(local_idx))
}
let slot_reuse : Array[Int32] = Array::make(total_local_count, -1)
let func_type_indices = dae_collect_func_type_indices(mod_)
run_hot_pipeline_dae_collect_local_counts_and_consumed_call_slot_reuse(
body, local_gets, local_sets, local_tees, local_types, mod_, func_type_indices,
0, slot_reuse,
)
let (forwarded_body, _) = run_hot_pipeline_dae_forward_one_use_immutable_param_copies(
body,
params.length(),
local_types,
local_gets,
local_sets,
local_tees,
mod_.type_sec,
)
let mut removed_tee_count = 0
for local_idx = 0; local_idx < total_local_count; local_idx = local_idx + 1 {
if local_gets[local_idx] == 0 {
removed_tee_count = removed_tee_count + local_tees[local_idx]
}
}
if removed_tee_count == 0 {
return (mod_, 0, false)
}
let local_remap : Array[Int32] = []
for local_idx = 0; local_idx < total_local_count; local_idx = local_idx + 1 {
local_remap.push(local_idx)
}
let remapped_gets : Array[Int32] = []
let remapped_sets : Array[Int32] = []
let remapped_tees : Array[Int32] = []
for param_idx = 0; param_idx < params.length(); param_idx = param_idx + 1 {
remapped_gets.push(local_gets[param_idx])
remapped_sets.push(local_sets[param_idx])
remapped_tees.push(local_tees[param_idx])
}
let compacted_local_types : Array[@lib.ValType] = []
let remapped_local_types = params.copy()
for local_idx = 0; local_idx < locals.length(); local_idx = local_idx + 1 {
let local_id = params.length() + local_idx
let reuse_source = slot_reuse[local_id]
if reuse_source >= 0 &&
reuse_source < local_id &&
local_gets[local_id] == 1 &&
local_sets[local_id] == 1 &&
local_tees[local_id] == 0 &&
local_remap[reuse_source] >= 0 {
let mapped_source = local_remap[reuse_source]
local_remap[local_id] = mapped_source
remapped_gets[mapped_source] = remapped_gets[mapped_source] + 1
remapped_sets[mapped_source] = remapped_sets[mapped_source] + 1
} else if local_gets[local_id] > 0 {
local_remap[local_id] = remapped_gets.length()
let retained_type = locals.get(local_idx)
compacted_local_types.push(retained_type)
remapped_local_types.push(retained_type)
remapped_gets.push(local_gets[local_id])
remapped_sets.push(local_sets[local_id])
remapped_tees.push(local_tees[local_id])
} else {
local_remap[local_id] = -1
}
}
let remapped_body = run_hot_pipeline_dae_rewrite_dead_tees_with_local_remap(
forwarded_body, local_gets, local_remap,
)
let (readback_body, _) = run_hot_pipeline_dae_tee_struct_get_readbacks_with_counts_and_types(
remapped_body,
remapped_gets,
remapped_sets,
remapped_tees,
remapped_local_types,
type_sec=mod_.type_sec,
)
let next_func = @lib.Func::new(
@lib.Locals::from_types(compacted_local_types),
@lib.Expr::new(readback_body),
)
let next_funcs = funcs.copy()
next_funcs[def_idx] = next_func
(
mod_.with_code_sec(@lib.CodeSec::new(next_funcs)),
removed_tee_count,
compacted_local_types.length() < locals.length(),
)
}
///|
fn run_hot_pipeline_dae_effects_own_worth_for_def(
effects : DaeCommitEffects,
def_idx : Int32,
) -> Bool {
def_idx >= 0 &&
def_idx < effects.worth_decided.length() &&
effects.worth_decided[def_idx]
}
///|
fn run_hot_pipeline_dae_worth_optimizing_touched(
_before : @lib.Module,
after : @lib.Module,
_changed_touched : Array[Bool],
producer_effects~ : DaeCommitEffects? = None,
) -> Array[Bool] {
let defined = match after.code_sec {
Some(@lib.CodeSec(funcs)) => funcs.length()
None => return []
}
let worth = Array::make(defined, false)
match producer_effects {
Some(effects) if effects.worth_decided.length() == defined &&
effects.worth_optimizing.length() == defined =>
for def_idx = 0; def_idx < defined; def_idx = def_idx + 1 {
if run_hot_pipeline_dae_effects_own_worth_for_def(effects, def_idx) {
worth[def_idx] = effects.worth_optimizing[def_idx]
}
}
_ => ()
}
worth
}
///|
fn run_hot_pipeline_dae_expand_frontier_seed_callees(
seed_defs : Array[Bool],
before_dependencies : DaeBoundaryDependencyGraph,
after_dependencies : DaeBoundaryDependencyGraph,
) -> Array[Bool] {
let expanded = seed_defs.copy()
let initial_seed = seed_defs.copy()
for def_idx = 0; def_idx < initial_seed.length(); def_idx = def_idx + 1 {
if !initial_seed[def_idx] {
continue
}
if def_idx < before_dependencies.callees_by_def.length() {
for callee_def in before_dependencies.callees_by_def[def_idx] {
if callee_def >= 0 && callee_def < expanded.length() {
expanded[callee_def] = true
}
}
}
if def_idx < after_dependencies.callees_by_def.length() {
for callee_def in after_dependencies.callees_by_def[def_idx] {
if callee_def >= 0 && callee_def < expanded.length() {
expanded[callee_def] = true
}
}
}
}
expanded
}
///|
fn run_hot_pipeline_dae_strip_touched_terminal_returns(
mod_ : @lib.Module,
touched : Array[Bool],
) -> @lib.Module {
let funcs = match mod_.code_sec {
Some(@lib.CodeSec(funcs)) => funcs
None => return mod_
}
let mut rewritten : Array[@lib.Func]? = None
for def_idx = 0; def_idx < funcs.length(); def_idx = def_idx + 1 {
if def_idx >= touched.length() || !touched[def_idx] {
continue
}
let @lib.Func(locals, @lib.Expr(body)) = funcs[def_idx]
if body.length() == 0 ||
body[body.length() - 1] != @lib.Instruction::return_() {
continue
}
if rewritten == None {
rewritten = Some(funcs.copy())
}
let next_body = body.copy()
ignore(next_body.pop())
rewritten.unwrap()[def_idx] = @lib.Func::new(
locals,
@lib.Expr::new(next_body),
)
}
match rewritten {
Some(funcs) => {
let candidate = mod_.with_code_sec(@lib.CodeSec::new(funcs))
match @validate.validate_module(candidate) {
Ok(_) => candidate
Err(_) => mod_
}
}
None => mod_
}
}
///|
fn run_hot_pipeline_apply_dae_nested_cleanup(
mod_ : @lib.Module,
touched : Array[Bool],
options : HotPipelineOptions,
bypass_size_guards~ : Bool = false,
pass_names~ : Array[String]? = None,
diagnostics~ : DaeDiagnostics? = None,
) -> Result[@lib.Module, String] {
let touched_count = dae_count_touched(touched)
if touched_count == 0 {
return Ok(mod_)
}
(options.trace)(
"pass[dae-optimizing]:nested-cleanup touched=\{touched_count} prefix=precompute-propagate",
)
let nested_total_start_us = perf_start_timer_opt(
options.perf,
"detail:dae:nested-total",
)
if !bypass_size_guards {
let size_skip_reason = run_hot_pipeline_dae_nested_cleanup_size_skip_reason(
mod_, touched,
)
if size_skip_reason != None {
pipeline_trace_hook(
options.trace,
"dae-optimizing",
"nested-cleanup-skip",
payload="reason=\{size_skip_reason.unwrap()} guard=performance optional=true touched=\{touched_count}",
)
perf_stop_timer_opt(
options.perf,
"detail:dae:nested-total",
nested_total_start_us,
)
return Ok(mod_)
}
}
let nested_passes = match pass_names {
Some(names) => names
None => run_hot_pipeline_dae_nested_cleanup_passes(mod_, options)
}
match diagnostics {
Some(diagnostics) => diagnostics.nested_functions_touched += touched_count
None => ()
}
let mut current = mod_
for pass_name in nested_passes {
match diagnostics {
Some(diagnostics) => diagnostics.nested_passes_executed += 1
None => ()
}
let nested_pass_start_us = perf_start_timer_opt(
options.perf,
"detail:dae:nested:\{pass_name}",
)
pipeline_trace_hook(
options.trace,
"dae-optimizing",
"nested-pass",
payload="name=\{pass_name}",
)
if pass_name == "precompute-propagate" {
// Proved flat legacy scopes must expose their local writes before the
// shared propagation stage; this keeps folding in the ordered roster.
current = dae_cleanup_touched_legacy_constants(
current,
touched,
fold_constants=false,
)
current = match
run_hot_pipeline_apply_hot_pass_to_touched(
current,
precompute_propagate_descriptor(),
touched,
options,
) {
Ok(next) => next
Err(err) => {
perf_stop_timer_opt(
options.perf,
"detail:dae:nested:\{pass_name}",
nested_pass_start_us,
)
perf_stop_timer_opt(
options.perf,
"detail:dae:nested-total",
nested_total_start_us,
)
return Err("DAE nested cleanup pass \{pass_name}: \{err}")
}
}
perf_stop_timer_opt(
options.perf,
"detail:dae:nested:\{pass_name}",
nested_pass_start_us,
)
continue
}
if pass_name == "local-cse" {
current = match
run_hot_pipeline_apply_local_cse_to_touched(current, touched, options) {
Ok(next) => next
Err(err) => {
perf_stop_timer_opt(
options.perf,
"detail:dae:nested:\{pass_name}",
nested_pass_start_us,
)
perf_stop_timer_opt(
options.perf,
"detail:dae:nested-total",
nested_total_start_us,
)
return Err("DAE nested cleanup pass \{pass_name}: \{err}")
}
}
perf_stop_timer_opt(
options.perf,
"detail:dae:nested:\{pass_name}",
nested_pass_start_us,
)
continue
}
if pass_name == "coalesce-locals" {
current = match
run_hot_pipeline_apply_coalesce_locals_to_touched(current, touched) {
Ok(next) => next
Err(err) => {
perf_stop_timer_opt(
options.perf,
"detail:dae:nested:\{pass_name}",
nested_pass_start_us,
)
perf_stop_timer_opt(
options.perf,
"detail:dae:nested-total",
nested_total_start_us,
)
return Err("DAE nested cleanup pass \{pass_name}: \{err}")
}
}
perf_stop_timer_opt(
options.perf,
"detail:dae:nested:\{pass_name}",
nested_pass_start_us,
)
continue
}
if pass_name == "reorder-locals" {
current = match
run_hot_pipeline_apply_reorder_locals_to_touched(current, touched) {
Ok(next) => next
Err(err) => {
perf_stop_timer_opt(
options.perf,
"detail:dae:nested:\{pass_name}",
nested_pass_start_us,
)
perf_stop_timer_opt(
options.perf,
"detail:dae:nested-total",
nested_total_start_us,
)
return Err("DAE nested cleanup pass \{pass_name}: \{err}")
}
}
perf_stop_timer_opt(
options.perf,
"detail:dae:nested:\{pass_name}",
nested_pass_start_us,
)
continue
}
if pass_name == "merge-locals" {
current = match
run_hot_pipeline_apply_merge_locals_to_touched(
current, touched, options,
) {
Ok(next) => next
Err(err) => {
perf_stop_timer_opt(
options.perf,
"detail:dae:nested:\{pass_name}",
nested_pass_start_us,
)
perf_stop_timer_opt(
options.perf,
"detail:dae:nested-total",
nested_total_start_us,
)
return Err("DAE nested cleanup pass \{pass_name}: \{err}")
}
}
perf_stop_timer_opt(
options.perf,
"detail:dae:nested:\{pass_name}",
nested_pass_start_us,
)
continue
}
if pass_name == "local-subtyping" {
pipeline_trace_hook(options.trace, "local-subtyping", "start")
current = match
run_hot_pipeline_apply_local_subtyping_to_touched(current, touched) {
Ok(next) => next
Err(err) => {
perf_stop_timer_opt(
options.perf,
"detail:dae:nested:\{pass_name}",
nested_pass_start_us,
)
perf_stop_timer_opt(
options.perf,
"detail:dae:nested-total",
nested_total_start_us,
)
return Err("DAE nested cleanup pass \{pass_name}: \{err}")
}
}
pipeline_trace_hook(options.trace, "local-subtyping", "done")
perf_stop_timer_opt(
options.perf,
"detail:dae:nested:\{pass_name}",
nested_pass_start_us,
)
continue
}
let descriptor = match
run_hot_pipeline_dae_nested_cleanup_descriptor(pass_name) {
Ok(descriptor) => descriptor
Err(err) => {
perf_stop_timer_opt(
options.perf,
"detail:dae:nested:\{pass_name}",
nested_pass_start_us,
)
perf_stop_timer_opt(
options.perf,
"detail:dae:nested-total",
nested_total_start_us,
)
return Err(err)
}
}
current = match
run_hot_pipeline_apply_hot_pass_to_touched(
current, descriptor, touched, options,
) {
Ok(next) => next
Err(err) => {
perf_stop_timer_opt(
options.perf,
"detail:dae:nested:\{pass_name}",
nested_pass_start_us,
)
perf_stop_timer_opt(
options.perf,
"detail:dae:nested-total",
nested_total_start_us,
)
return Err("DAE nested cleanup pass \{pass_name}: \{err}")
}
}
if pass_name == "tuple-optimization" {
// DAE's earlier raw cleanup can lower a tuple spill to a pure multivalue
// block followed by one drop per lane before the nested Binaryen stage
// runs. Recognize that lowered equivalent on touched functions so the
// tuple stage still reaches Binaryen's all-lanes-dead result.
current = dae_cleanup_touched_dropped_pure_multivalue_blocks(
current, touched,
)
}
perf_stop_timer_opt(
options.perf,
"detail:dae:nested:\{pass_name}",
nested_pass_start_us,
)
}
// The shared flatten/default-function sequence can leave the equivalent
// dropped multivalue block form until its final cleanup slots. Reapply DAE's
// effect/trap-preserving tuple projection once after the complete roster so
// the nested result matches Binaryen's optimizeAfterInlining fixpoint.
current = dae_cleanup_touched_dropped_pure_multivalue_blocks(current, touched)
current = run_hot_pipeline_dae_strip_touched_terminal_returns(
current, touched,
)
perf_stop_timer_opt(
options.perf,
"detail:dae:nested-total",
nested_total_start_us,
)
Ok(current)
}
///|
fn run_hot_pipeline_sgo_nested_cleanup_passes(
mod_ : @lib.Module,
options : HotPipelineOptions,
) -> Array[String] {
// Binaryen's simplify-globals-optimizing reruns ordinary default function
// optimization on changed functions, without DAE's extra
// precompute-propagate prefix. Share the parent runner's active levels and
// module feature gates instead of maintaining an artifact-specific subset.
default_function_optimization_passes_with_features(
options.optimize_level,
options.shrink_level,
hot_pipeline_module_feature_facts(mod_),
)
}
///|
fn run_hot_pipeline_sgo_touched_instr_has_local(
instr : @lib.Instruction,
) -> Bool {
match instr {
@lib.LocalGet(_) | @lib.LocalSet(_) | @lib.LocalTee(_) => true
@lib.Block(_, @lib.Expr(body))
| @lib.Loop(_, @lib.Expr(body))
| @lib.TryTable(_, _, @lib.Expr(body)) =>
run_hot_pipeline_sgo_touched_body_has_local(body)
@lib.If(_, then_body, else_body) => {
let else_has = match else_body {
Some(body) => run_hot_pipeline_sgo_touched_body_has_local(body)
None => false
}
run_hot_pipeline_sgo_touched_body_has_local(then_body) || else_has
}
_ => false
}
}
///|
fn run_hot_pipeline_sgo_touched_body_has_local(
body : Array[@lib.Instruction],
) -> Bool {
body.any(run_hot_pipeline_sgo_touched_instr_has_local)
}
///|
fn run_hot_pipeline_sgo_touched_instr_has_load(
instr : @lib.Instruction,
) -> Bool {
match instr {
@lib.I32Load(_)
| @lib.I64Load(_)
| @lib.F32Load(_)
| @lib.F64Load(_)
| @lib.I32Load8S(_)
| @lib.I32Load8U(_)
| @lib.I32Load16S(_)
| @lib.I32Load16U(_)
| @lib.I64Load8S(_)
| @lib.I64Load8U(_)
| @lib.I64Load16S(_)
| @lib.I64Load16U(_)
| @lib.I64Load32S(_)
| @lib.I64Load32U(_) => true
@lib.Block(_, @lib.Expr(body))
| @lib.Loop(_, @lib.Expr(body))
| @lib.TryTable(_, _, @lib.Expr(body)) =>
run_hot_pipeline_sgo_touched_body_has_load(body)
@lib.If(_, then_body, else_body) => {
let else_has = match else_body {
Some(body) => run_hot_pipeline_sgo_touched_body_has_load(body)
None => false
}
run_hot_pipeline_sgo_touched_body_has_load(then_body) || else_has
}
_ => false
}
}
///|
fn run_hot_pipeline_sgo_touched_body_has_load(
body : Array[@lib.Instruction],
) -> Bool {
body.any(run_hot_pipeline_sgo_touched_instr_has_load)
}
///|
fn run_hot_pipeline_sgo_touched_instr_has_block(
instr : @lib.Instruction,
) -> Bool {
match instr {
@lib.Block(_, _) => true
@lib.Loop(_, @lib.Expr(body)) | @lib.TryTable(_, _, @lib.Expr(body)) =>
run_hot_pipeline_sgo_touched_body_has_block(body)
@lib.If(_, then_body, else_body) => {
let else_has = match else_body {
Some(body) => run_hot_pipeline_sgo_touched_body_has_block(body)
None => false
}
run_hot_pipeline_sgo_touched_body_has_block(then_body) || else_has
}
_ => false
}
}
///|
fn run_hot_pipeline_sgo_touched_body_has_block(
body : Array[@lib.Instruction],
) -> Bool {
body.any(run_hot_pipeline_sgo_touched_instr_has_block)
}
///|
fn run_hot_pipeline_sgo_touched_instr_has_set(instr : @lib.Instruction) -> Bool {
match instr {
@lib.LocalSet(_) | @lib.LocalTee(_) | @lib.GlobalSet(_) => true
@lib.Block(_, @lib.Expr(body))
| @lib.Loop(_, @lib.Expr(body))
| @lib.TryTable(_, _, @lib.Expr(body)) =>
run_hot_pipeline_sgo_touched_body_has_set(body)
@lib.If(_, then_body, else_body) => {
let else_has = match else_body {
Some(body) => run_hot_pipeline_sgo_touched_body_has_set(body)
None => false
}
run_hot_pipeline_sgo_touched_body_has_set(then_body) || else_has
}
_ => false
}
}
///|
fn run_hot_pipeline_sgo_touched_body_has_set(
body : Array[@lib.Instruction],
) -> Bool {
body.any(run_hot_pipeline_sgo_touched_instr_has_set)
}
///|
fn run_hot_pipeline_sgo_touched_instr_has_vacuum(
instr : @lib.Instruction,
) -> Bool {
match instr {
@lib.Drop | @lib.Nop | @lib.Unreachable => true
@lib.Block(_, @lib.Expr(body))
| @lib.Loop(_, @lib.Expr(body))
| @lib.TryTable(_, _, @lib.Expr(body)) =>
run_hot_pipeline_sgo_touched_body_has_vacuum(body)
@lib.If(_, then_body, else_body) => {
let else_has = match else_body {
Some(body) => run_hot_pipeline_sgo_touched_body_has_vacuum(body)
None => false
}
run_hot_pipeline_sgo_touched_body_has_vacuum(then_body) || else_has
}
_ => false
}
}
///|
fn run_hot_pipeline_sgo_touched_body_has_vacuum(
body : Array[@lib.Instruction],
) -> Bool {
body.any(run_hot_pipeline_sgo_touched_instr_has_vacuum)
}
///|
fn run_hot_pipeline_sgo_touched_any_declared_local(
mod_ : @lib.Module,
touched : Array[Bool],
) -> Bool {
match mod_.code_sec {
Some(@lib.CodeSec(defs)) => {
for idx = 0; idx < defs.length(); idx = idx + 1 {
if idx < touched.length() && touched[idx] {
let @lib.Func(locals, _) = defs[idx]
if locals.length() > 0 {
return true
}
}
}
false
}
None => false
}
}
///|
fn run_hot_pipeline_sgo_touched_any(
mod_ : @lib.Module,
touched : Array[Bool],
pred : (Array[@lib.Instruction]) -> Bool,
) -> Bool {
match mod_.code_sec {
Some(@lib.CodeSec(defs)) => {
for idx = 0; idx < defs.length(); idx = idx + 1 {
if idx < touched.length() && touched[idx] {
let @lib.Func(_, @lib.Expr(body)) = defs[idx]
if pred(body) {
return true
}
}
}
false
}
None => false
}
}
///|
fn run_hot_pipeline_sgo_nested_cleanup_has_candidates(
mod_ : @lib.Module,
touched : Array[Bool],
pass_name : String,
) -> Bool {
match pass_name {
"local-cse" | "coalesce-locals" =>
run_hot_pipeline_sgo_touched_any(
mod_, touched, run_hot_pipeline_sgo_touched_body_has_local,
)
"reorder-locals" =>
run_hot_pipeline_sgo_touched_any_declared_local(mod_, touched)
"pick-load-signs" =>
run_hot_pipeline_sgo_touched_any(
mod_, touched, run_hot_pipeline_sgo_touched_body_has_load,
)
"merge-blocks" =>
run_hot_pipeline_sgo_touched_any(
mod_, touched, run_hot_pipeline_sgo_touched_body_has_block,
)
"redundant-set-elimination" =>
run_hot_pipeline_sgo_touched_any(
mod_, touched, run_hot_pipeline_sgo_touched_body_has_set,
)
"vacuum" =>
run_hot_pipeline_sgo_touched_any(
mod_, touched, run_hot_pipeline_sgo_touched_body_has_vacuum,
)
"remove-unused-names" =>
mod_.name_sec != None || mod_.raw_name_sec_payload != None
_ => true
}
}
///|
fn run_hot_pipeline_sgo_label_idx_is_zero(label : @lib.LabelIdx) -> Bool {
let @lib.LabelIdx(raw) = label
raw == 0U
}
///|
fn run_hot_pipeline_sgo_label_idx_array_all_zero(
labels : Array[@lib.LabelIdx],
) -> Bool {
labels.all(run_hot_pipeline_sgo_label_idx_is_zero)
}
///|
fn run_hot_pipeline_sgo_instr_is_const12_safe_trunc_prefix(
value_instr : @lib.Instruction,
trunc_instr : @lib.Instruction,
) -> Bool {
let f32_12 = @lib.Instruction::f32_const(@lib.F32(Float::from_double(12.0)))
let f64_12 = @lib.Instruction::f64_const(@lib.F64(12.0))
if value_instr == f32_12 {
return trunc_instr == @lib.Instruction::i32_trunc_f32s() ||
trunc_instr == @lib.Instruction::i32_trunc_f32u() ||
trunc_instr == @lib.Instruction::i64_trunc_f32s() ||
trunc_instr == @lib.Instruction::i64_trunc_f32u()
}
if value_instr == f64_12 {
return trunc_instr == @lib.Instruction::i32_trunc_f64s() ||
trunc_instr == @lib.Instruction::i32_trunc_f64u() ||
trunc_instr == @lib.Instruction::i64_trunc_f64s() ||
trunc_instr == @lib.Instruction::i64_trunc_f64u()
}
false
}
///|
fn run_hot_pipeline_sgo_instr_is_void_block_exit_to_next(
instr : @lib.Instruction,
) -> Bool {
match instr {
@lib.Block(@lib.VoidBlockType, @lib.Expr(body)) =>
match body {
[@lib.Br(label)] => run_hot_pipeline_sgo_label_idx_is_zero(label)
[@lib.I32Const(_), @lib.BrTable(labels, default_label)] =>
run_hot_pipeline_sgo_label_idx_is_zero(default_label) &&
run_hot_pipeline_sgo_label_idx_array_all_zero(labels)
_ => false
}
_ => false
}
}
///|
fn run_hot_pipeline_sgo_instr_is_unconditional_unreachable(
instr : @lib.Instruction,
) -> Bool {
match instr {
@lib.Unreachable => true
@lib.Block(@lib.VoidBlockType, @lib.Expr([@lib.Unreachable])) => true
_ => false
}
}
///|
fn run_hot_pipeline_sgo_large_unreachable_tail_start(
body : Array[@lib.Instruction],
) -> Int32? {
let mut idx = 0
while idx < body.length() && body[idx] == @lib.Instruction::nop() {
idx = idx + 1
}
while idx + 2 < body.length() && body[idx + 2] == @lib.Instruction::drop() {
if run_hot_pipeline_sgo_instr_is_const12_safe_trunc_prefix(
body[idx],
body[idx + 1],
) {
idx = idx + 3
continue
}
break
}
if idx < body.length() &&
run_hot_pipeline_sgo_instr_is_unconditional_unreachable(body[idx]) {
return Some(idx)
}
if idx + 1 < body.length() &&
run_hot_pipeline_sgo_instr_is_void_block_exit_to_next(body[idx]) &&
run_hot_pipeline_sgo_instr_is_unconditional_unreachable(body[idx + 1]) {
return Some(idx)
}
None
}
///|
fn run_hot_pipeline_sgo_collapse_large_unreachable_tails(
mod_ : @lib.Module,
touched : Array[Bool],
) -> @lib.Module {
match mod_.code_sec {
Some(@lib.CodeSec(funcs)) => {
let out = funcs.copy()
let mut changed = false
for code_idx = 0; code_idx < funcs.length(); code_idx = code_idx + 1 {
if code_idx >= touched.length() || !touched[code_idx] {
continue
}
let @lib.Func(locals, @lib.Expr(body)) = funcs[code_idx]
match run_hot_pipeline_sgo_large_unreachable_tail_start(body) {
Some(start_idx) if start_idx > 0 || body.length() > 1 => {
ignore(locals)
out[code_idx] = @lib.Func::new(
@lib.Locals::empty(),
@lib.Expr::new([@lib.Instruction::unreachable_()]),
)
changed = true
}
_ => ()
}
}
if changed {
mod_.with_code_sec(@lib.CodeSec::new(out))
} else {
mod_
}
}
None => mod_
}
}
///|
fn run_hot_pipeline_sgo_validate_nested_cleanup_output(
candidate : @lib.Module,
pass_name : String,
trace : (String) -> Unit,
) -> Result[@lib.Module, String] {
match @validate.validate_module(candidate) {
Ok(_) => Ok(candidate)
Err(err) => {
pipeline_trace_hook(
trace,
"simplify-globals-optimizing",
"nested-pass-invalid",
payload="name=\{pass_name} reason=\{err}",
)
Err(
"SGO nested cleanup pass \{pass_name} produced invalid module: \{err}",
)
}
}
}
///|
fn run_hot_pipeline_sgo_nested_cleanup_descriptor(
pass_name : String,
) -> Result[@ir.HotPassDescriptor, String] {
match pass_registry_lookup_cached(pass_name) {
Some(entry) =>
match entry.descriptor {
Some(descriptor) => Ok(descriptor)
None => Err("SGO nested cleanup pass \{pass_name} is not a hot pass")
}
None => Err("SGO nested cleanup expanded to unknown pass \{pass_name}")
}
}
///|
fn run_hot_pipeline_sgo_deep_copy_instrs(
instrs : Array[@lib.Instruction],
) -> Array[@lib.Instruction] {
let out : Array[@lib.Instruction] = Array::new(capacity=instrs.length())
for instr in instrs {
out.push(
match instr {
@lib.Block(block_type, @lib.Expr(body)) =>
@lib.Instruction::block(
block_type,
@lib.Expr::new(run_hot_pipeline_sgo_deep_copy_instrs(body)),
)
@lib.Loop(block_type, @lib.Expr(body)) =>
@lib.Instruction::loop_(
block_type,
@lib.Expr::new(run_hot_pipeline_sgo_deep_copy_instrs(body)),
)
@lib.If(block_type, then_body, else_body) =>
@lib.Instruction::if_(
block_type,
run_hot_pipeline_sgo_deep_copy_instrs(then_body),
match else_body {
Some(body) => Some(run_hot_pipeline_sgo_deep_copy_instrs(body))
None => None
},
)
@lib.Try(block_type, @lib.Expr(body), catches, delegate) => {
let next_catches : Array[@lib.LegacyCatch] = []
for catch_ in catches {
next_catches.push(
match catch_ {
@lib.LegacyCatch(tag_idx, @lib.Expr(catch_body)) =>
@lib.LegacyCatch::tagged(
tag_idx,
@lib.Expr::new(
run_hot_pipeline_sgo_deep_copy_instrs(catch_body),
),
)
@lib.LegacyCatchAll(@lib.Expr(catch_body)) =>
@lib.LegacyCatch::all(
@lib.Expr::new(
run_hot_pipeline_sgo_deep_copy_instrs(catch_body),
),
)
},
)
}
@lib.Instruction::try_(
block_type,
@lib.Expr::new(run_hot_pipeline_sgo_deep_copy_instrs(body)),
next_catches,
delegate~,
)
}
@lib.TryTable(block_type, catches, @lib.Expr(body)) =>
@lib.Instruction::try_table(
block_type,
catches,
@lib.Expr::new(run_hot_pipeline_sgo_deep_copy_instrs(body)),
)
_ => instr
},
)
}
out
}
///|
fn run_hot_pipeline_sgo_snapshot_touched_funcs(
mod_ : @lib.Module,
touched : Array[Bool],
) -> @lib.Module {
match mod_.code_sec {
Some(@lib.CodeSec(funcs)) => {
let snapshot = funcs.copy()
for code_idx = 0; code_idx < funcs.length(); code_idx = code_idx + 1 {
if code_idx < touched.length() && touched[code_idx] {
let @lib.Func(locals, @lib.Expr(body)) = funcs[code_idx]
snapshot[code_idx] = @lib.Func::new(
locals,
@lib.Expr::new(run_hot_pipeline_sgo_deep_copy_instrs(body)),
)
}
}
mod_.with_code_sec(@lib.CodeSec::new(snapshot))
}
None => mod_
}
}
///|
fn run_hot_pipeline_apply_sgo_nested_cleanup(
mod_ : @lib.Module,
touched : Array[Bool],
options : HotPipelineOptions,
) -> Result[@lib.Module, String] {
let touched_count = sgo_count_touched(touched)
if touched_count == 0 {
return Ok(mod_)
}
// Nested function passes can mutate shared local/body storage before their
// batch writeback validator rejects a candidate. Deep-copy touched instruction
// forests so invalid candidates fail closed without encoding the whole module.
let snapshot_start_us = perf_start_timer_opt(
options.perf,
"detail:sgo:nested:snapshot",
)
let rollback = run_hot_pipeline_sgo_snapshot_touched_funcs(mod_, touched)
perf_stop_timer_opt(
options.perf,
"detail:sgo:nested:snapshot",
snapshot_start_us,
)
let (cleanup_touched, filtered_large_count, large_unreachable_touched) = run_hot_pipeline_sgo_nested_cleanup_filter_touched(
mod_, touched,
)
let cleanup_touched_count = sgo_count_touched(cleanup_touched)
let large_unreachable_count = sgo_count_touched(large_unreachable_touched)
(options.trace)(
"pass[simplify-globals-optimizing]:nested-cleanup touched=\{touched_count} prefix=default",
)
(options.trace)(
"pass[simplify-globals-optimizing]:nested-cleanup-stats touched=\{touched_count} cleaned=\{cleanup_touched_count} filtered-large=\{filtered_large_count}",
)
let mut current = mod_
if large_unreachable_count > 0 {
pipeline_trace_hook(
options.trace,
"simplify-globals-optimizing",
"nested-large-vacuum",
payload="touched=\{large_unreachable_count}",
)
let vacuum_descriptor = match
run_hot_pipeline_sgo_nested_cleanup_descriptor("vacuum") {
Ok(descriptor) => descriptor
Err(err) => return Err(err)
}
current = match
run_hot_pipeline_apply_hot_pass_to_touched(
current, vacuum_descriptor, large_unreachable_touched, options,
) {
Ok(next) => next
Err(err) => return Err("SGO nested large vacuum cleanup: \{err}")
}
current = run_hot_pipeline_sgo_collapse_large_unreachable_tails(
current, large_unreachable_touched,
)
}
if cleanup_touched_count == 0 {
if filtered_large_count > large_unreachable_count {
pipeline_trace_hook(
options.trace,
"simplify-globals-optimizing",
"nested-cleanup-skip",
payload="reason=large-touched-function touched=\{touched_count}",
)
}
return Ok(current)
}
let nested_total_start_us = perf_start_timer_opt(
options.perf,
"detail:sgo:nested-total",
)
for pass_name in run_hot_pipeline_sgo_nested_cleanup_passes(current, options) {
if !run_hot_pipeline_sgo_nested_cleanup_has_candidates(
current, cleanup_touched, pass_name,
) {
pipeline_trace_hook(
options.trace,
"simplify-globals-optimizing",
"nested-pass-skip",
payload="name=\{pass_name} reason=no-touched-candidates",
)
continue
}
pipeline_trace_hook(
options.trace,
"simplify-globals-optimizing",
"nested-pass",
payload="name=\{pass_name}",
)
let nested_pass_start_us = perf_start_timer_opt(
options.perf,
"detail:sgo:nested:\{pass_name}",
)
if pass_name == "local-cse" {
current = match
run_hot_pipeline_apply_local_cse_to_touched(
current, cleanup_touched, options,
) {
Ok(next) => next
Err(err) => {
perf_stop_timer_opt(
options.perf,
"detail:sgo:nested:\{pass_name}",
nested_pass_start_us,
)
perf_stop_timer_opt(
options.perf,
"detail:sgo:nested-total",
nested_total_start_us,
)
return Err("SGO nested cleanup pass \{pass_name}: \{err}")
}
}
perf_stop_timer_opt(
options.perf,
"detail:sgo:nested:\{pass_name}",
nested_pass_start_us,
)
continue
}
if pass_name == "local-subtyping" {
pipeline_trace_hook(options.trace, "local-subtyping", "start")
current = match
run_hot_pipeline_apply_local_subtyping_to_touched(
current, cleanup_touched,
) {
Ok(next) => next
Err(err) => {
perf_stop_timer_opt(
options.perf,
"detail:sgo:nested:\{pass_name}",
nested_pass_start_us,
)
perf_stop_timer_opt(
options.perf,
"detail:sgo:nested-total",
nested_total_start_us,
)
return Err("SGO nested cleanup pass \{pass_name}: \{err}")
}
}
pipeline_trace_hook(options.trace, "local-subtyping", "done")
perf_stop_timer_opt(
options.perf,
"detail:sgo:nested:\{pass_name}",
nested_pass_start_us,
)
continue
}
if pass_name == "coalesce-locals" {
current = match
run_hot_pipeline_apply_coalesce_locals_to_touched(
current,
run_hot_pipeline_bounded_coalesce_touched(current, cleanup_touched),
) {
Ok(next) => next
Err(err) => {
perf_stop_timer_opt(
options.perf,
"detail:sgo:nested:\{pass_name}",
nested_pass_start_us,
)
perf_stop_timer_opt(
options.perf,
"detail:sgo:nested-total",
nested_total_start_us,
)
return Err("SGO nested cleanup pass \{pass_name}: \{err}")
}
}
perf_stop_timer_opt(
options.perf,
"detail:sgo:nested:\{pass_name}",
nested_pass_start_us,
)
continue
}
if pass_name == "reorder-locals" {
current = match
run_hot_pipeline_apply_reorder_locals_to_touched(
current, cleanup_touched,
) {
Ok(next) => next
Err(err) => {
perf_stop_timer_opt(
options.perf,
"detail:sgo:nested:\{pass_name}",
nested_pass_start_us,
)
perf_stop_timer_opt(
options.perf,
"detail:sgo:nested-total",
nested_total_start_us,
)
return Err("SGO nested cleanup pass \{pass_name}: \{err}")
}
}
perf_stop_timer_opt(
options.perf,
"detail:sgo:nested:\{pass_name}",
nested_pass_start_us,
)
continue
}
let descriptor = match
run_hot_pipeline_sgo_nested_cleanup_descriptor(pass_name) {
Ok(descriptor) => descriptor
Err(err) => {
perf_stop_timer_opt(
options.perf,
"detail:sgo:nested:\{pass_name}",
nested_pass_start_us,
)
perf_stop_timer_opt(
options.perf,
"detail:sgo:nested-total",
nested_total_start_us,
)
return Err(err)
}
}
let candidate = match
run_hot_pipeline_apply_hot_pass_to_touched(
current, descriptor, cleanup_touched, options,
) {
Ok(next) => next
Err(err) => {
perf_stop_timer_opt(
options.perf,
"detail:sgo:nested:\{pass_name}",
nested_pass_start_us,
)
perf_stop_timer_opt(
options.perf,
"detail:sgo:nested-total",
nested_total_start_us,
)
return Err("SGO nested cleanup pass \{pass_name}: \{err}")
}
}
// The touched-pass writeback path already validates every changed
// function against the current module and repairs invalid definitions.
// Keep the broader transaction check, but pay for it once after the
// complete nested roster instead of revalidating the whole module after
// every individual hot pass.
current = candidate
perf_stop_timer_opt(
options.perf,
"detail:sgo:nested:\{pass_name}",
nested_pass_start_us,
)
}
current = run_hot_pipeline_sgo_collapse_large_unreachable_tails(
current, cleanup_touched,
)
let final_validate_start_us = perf_start_timer_opt(
options.perf,
"detail:sgo:nested:validate-final",
)
let validated = run_hot_pipeline_sgo_validate_nested_cleanup_output(
current,
"final",
options.trace,
)
perf_stop_timer_opt(
options.perf,
"detail:sgo:nested:validate-final",
final_validate_start_us,
)
match validated {
Ok(validated) => {
perf_stop_timer_opt(
options.perf,
"detail:sgo:nested-total",
nested_total_start_us,
)
Ok(validated)
}
Err(_) => {
perf_stop_timer_opt(
options.perf,
"detail:sgo:nested-total",
nested_total_start_us,
)
Ok(rollback)
}
}
}
///|
fn run_hot_pipeline_apply_sgo_final_cleanup_suffix(
cleaned : @lib.Module,
options : HotPipelineOptions,
) -> Result[@lib.Module, String] {
let func_count = match cleaned.code_sec {
Some(@lib.CodeSec(funcs)) => funcs.length()
None => return Ok(cleaned)
}
// Final SGO cleanup can expose constant/local and branch opportunities after
// the shared nested cleanup slots have already run. Reclaim only
// medium-module debris here; keep the artifact-scale self-optimized CLI
// outside this extra wave.
if func_count >= 1000 {
return Ok(cleaned)
}
let propagated = match pass_registry_lookup_cached("precompute-propagate") {
Some(entry) =>
match run_hot_pipeline_apply_hot_pass(cleaned, entry, options) {
Ok(candidate) => candidate
Err(err) => return Err("SGO final precompute cleanup: \{err}")
}
None => return Err("SGO final cleanup missing precompute-propagate")
}
let blocks_cleaned = match pass_registry_lookup_cached("merge-blocks") {
Some(entry) =>
match run_hot_pipeline_apply_hot_pass(propagated, entry, options) {
Ok(candidate) => candidate
Err(err) => return Err("SGO final merge-blocks cleanup: \{err}")
}
None => return Err("SGO final cleanup missing merge-blocks")
}
let locals_cleaned = match pass_registry_lookup_cached("simplify-locals") {
Some(entry) =>
match run_hot_pipeline_apply_hot_pass(blocks_cleaned, entry, options) {
Ok(candidate) => candidate
Err(err) => return Err("SGO final simplify-locals cleanup: \{err}")
}
None => return Err("SGO final cleanup missing simplify-locals")
}
let branch_cleaned = match pass_registry_lookup_cached("remove-unused-brs") {
Some(entry) =>
match run_hot_pipeline_apply_hot_pass(locals_cleaned, entry, options) {
Ok(candidate) => candidate
Err(err) => return Err("SGO final remove-unused-brs cleanup: \{err}")
}
None => return Err("SGO final cleanup missing remove-unused-brs")
}
let candidate = match
run_hot_pipeline_apply_coalesce_locals_to_bounded_defs(branch_cleaned) {
Ok(candidate) => sgo_apply_final_cleanup(candidate)
Err(err) => return Err(err)
}
if candidate == cleaned {
return Ok(cleaned)
}
match
(
run_hot_pipeline_dae_encoded_size(cleaned),
run_hot_pipeline_dae_encoded_size(candidate),
@validate.validate_module(candidate),
) {
(Some(before), Some(after), Ok(_)) if after < before => {
pipeline_trace_hook(
options.trace,
"simplify-globals-optimizing",
"final-cleanup-suffix",
payload="accepted=true before=\{before} after=\{after}",
)
Ok(candidate)
}
(before, after, validation) => {
let reason = match validation {
Ok(_) => "non-smaller-or-unencodable"
Err(_) => "validation"
}
pipeline_trace_hook(
options.trace,
"simplify-globals-optimizing",
"final-cleanup-suffix",
payload="accepted=false reason=\{reason} before=\{before.to_string()} after=\{after.to_string()}",
)
Ok(cleaned)
}
}
}
///|
fn run_hot_pipeline_sgo_instrs_have_gc_allocation_set(
instrs : Array[@lib.Instruction],
) -> Bool {
for pos = 0; pos < instrs.length(); pos = pos + 1 {
let instr = instrs[pos]
match instr {
@lib.Block(_, @lib.Expr(body))
| @lib.Loop(_, @lib.Expr(body))
| @lib.TryTable(_, _, @lib.Expr(body)) =>
if run_hot_pipeline_sgo_instrs_have_gc_allocation_set(body) {
return true
}
@lib.If(_, then_body, else_body) => {
if run_hot_pipeline_sgo_instrs_have_gc_allocation_set(then_body) {
return true
}
match else_body {
Some(body) =>
if run_hot_pipeline_sgo_instrs_have_gc_allocation_set(body) {
return true
}
None => ()
}
}
@lib.GlobalSet(_) if pos > 0 =>
match instrs[pos - 1] {
@lib.ArrayNew(_)
| @lib.ArrayNewDefault(_)
| @lib.ArrayNewFixed(_, _)
| @lib.ArrayNewData(_, _)
| @lib.ArrayNewElem(_, _)
| @lib.StructNew(_)
| @lib.StructNewDefault(_)
| @lib.StructNewDesc(_)
| @lib.StructNewDefaultDesc(_) => return true
_ => ()
}
_ => ()
}
}
false
}
///|
fn run_hot_pipeline_sgo_has_gc_allocation_set(mod_ : @lib.Module) -> Bool {
match mod_.code_sec {
Some(@lib.CodeSec(funcs)) =>
funcs.any(fn(func) {
let @lib.Func(_, @lib.Expr(body)) = func
run_hot_pipeline_sgo_instrs_have_gc_allocation_set(body)
})
None => false
}
}
///|
fn run_hot_pipeline_apply_sgo_optimizing_module_pass(
mod_ : @lib.Module,
options : HotPipelineOptions,
) -> Result[@lib.Module, String] {
if run_hot_pipeline_sgo_has_gc_allocation_set(mod_) {
pipeline_trace_hook(
options.trace,
"simplify-globals-optimizing",
"fallback-noop",
payload="reason=trapping-gc-allocation-global-set",
)
return Ok(mod_)
}
if run_hot_pipeline_module_has_typeidx_loop(mod_) {
pipeline_trace_hook(
options.trace,
"simplify-globals-optimizing",
"fallback-noop",
payload="reason=typed-loop-param-control",
)
return Ok(mod_)
}
let has_legacy_try = pass_module_has_legacy_try(mod_)
let result = sgo_run_core(
mod_,
options.trace,
perf=options.perf,
legacy_try=Some(has_legacy_try),
)
if has_legacy_try {
// This route changes only literal reads in ordinary sibling functions.
// The generic cleanup roster must not change legacy handler bodies.
return Ok(result.mod_out)
}
let cleaned = match
run_hot_pipeline_apply_sgo_nested_cleanup(
result.mod_out,
result.touched,
options,
) {
Ok(cleaned) => cleaned
Err(err) => return Err(err)
}
if result.mod_out == mod_ {
Ok(cleaned)
} else {
let cleaned = sgo_apply_final_cleanup(cleaned)
if options.optimize_level >= 4 &&
options.shrink_level >= 1 &&
o4z_size_candidate_has_unsafe_emscripten_main_runtime(mod_) {
pipeline_trace_hook(
options.trace,
"simplify-globals-optimizing",
"final-cleanup-suffix",
payload="skipped=true reason=o4z-stateful-emscripten-later-schedule",
)
Ok(cleaned)
} else {
run_hot_pipeline_apply_sgo_final_cleanup_suffix(cleaned, options)
}
}
}
///|
fn run_hot_pipeline_dae_immutable_global_struct_field_literal(
mod_ : @lib.Module,
global_idx : @lib.GlobalIdx,
type_idx : @lib.TypeIdx,
field_idx : @lib.U32,
) -> @lib.Instruction? {
if !dae_global_get_is_immutable(mod_, global_idx) {
return None
}
let global_slot = dae_global_idx_slot(global_idx)
let mut imported_globals = 0
match mod_.import_sec {
Some(@lib.ImportSec(imports)) =>
for import_ in imports {
match import_ {
@lib.Import(_, _, @lib.GlobalExternType(_)) => imported_globals += 1
_ => ()
}
}
None => ()
}
let defined_slot = global_slot - imported_globals
let init = match mod_.global_sec {
Some(@lib.GlobalSec(globals)) if defined_slot >= 0 &&
defined_slot < globals.length() =>
match globals[defined_slot] {
@lib.Global(@lib.GlobalType(_, false), @lib.Expr(init)) => init
_ => return None
}
_ => return None
}
if init.length() < 2 {
return None
}
match init[init.length() - 1] {
@lib.StructNew(init_type_idx) if init_type_idx == type_idx => ()
_ => return None
}
let @lib.U32(raw_field_idx) = field_idx
let field_slot = raw_field_idx.reinterpret_as_int()
if field_slot < 0 || field_slot + 1 >= init.length() {
return None
}
let mut idx = 0
while idx + 1 < init.length() {
if !dae_instr_is_materializable_const(init[idx]) {
return None
}
idx += 1
}
Some(init[field_slot])
}
///|
fn run_hot_pipeline_dae_fold_immutable_global_struct_fields_instrs(
mod_ : @lib.Module,
instrs : Array[@lib.Instruction],
) -> (Array[@lib.Instruction], Bool) {
let out : Array[@lib.Instruction] = []
let mut changed = false
let mut idx = 0
while idx < instrs.length() {
if idx + 1 < instrs.length() {
match (instrs[idx], instrs[idx + 1]) {
(@lib.GlobalGet(global_idx), @lib.StructGet(type_idx, field_idx)) =>
match
run_hot_pipeline_dae_immutable_global_struct_field_literal(
mod_, global_idx, type_idx, field_idx,
) {
Some(literal) => {
out.push(literal)
changed = true
idx += 2
continue
}
None => ()
}
_ => ()
}
}
let rewritten = match instrs[idx] {
@lib.Block(block_type, @lib.Expr(body)) => {
let (next_body, nested_changed) = run_hot_pipeline_dae_fold_immutable_global_struct_fields_instrs(
mod_, body,
)
changed = changed || nested_changed
@lib.Instruction::block(block_type, @lib.Expr::new(next_body))
}
@lib.Loop(block_type, @lib.Expr(body)) => {
let (next_body, nested_changed) = run_hot_pipeline_dae_fold_immutable_global_struct_fields_instrs(
mod_, body,
)
changed = changed || nested_changed
@lib.Instruction::loop_(block_type, @lib.Expr::new(next_body))
}
@lib.TryTable(block_type, catches, @lib.Expr(body)) => {
let (next_body, nested_changed) = run_hot_pipeline_dae_fold_immutable_global_struct_fields_instrs(
mod_, body,
)
changed = changed || nested_changed
@lib.Instruction::try_table(
block_type,
catches,
@lib.Expr::new(next_body),
)
}
@lib.If(block_type, then_body, else_body) => {
let (next_then, then_changed) = run_hot_pipeline_dae_fold_immutable_global_struct_fields_instrs(
mod_, then_body,
)
let mut nested_changed = then_changed
let next_else = match else_body {
Some(body) => {
let (next_body, else_changed) = run_hot_pipeline_dae_fold_immutable_global_struct_fields_instrs(
mod_, body,
)
nested_changed = nested_changed || else_changed
Some(next_body)
}
None => None
}
changed = changed || nested_changed
@lib.Instruction::if_(block_type, next_then, next_else)
}
instr => instr
}
out.push(rewritten)
idx += 1
}
(out, changed)
}
///|
#warnings("-unused_value")
fn run_hot_pipeline_dae_fold_immutable_global_struct_fields_touched(
mod_ : @lib.Module,
touched : Array[Bool],
) -> @lib.Module {
let funcs = match mod_.code_sec {
Some(@lib.CodeSec(funcs)) => funcs
None => return mod_
}
let rewritten = funcs.copy()
let mut changed = false
for def_idx = 0; def_idx < funcs.length(); def_idx = def_idx + 1 {
if def_idx >= touched.length() || !touched[def_idx] {
continue
}
let @lib.Func(locals, @lib.Expr(body)) = funcs[def_idx]
let (next_body, body_changed) = run_hot_pipeline_dae_fold_immutable_global_struct_fields_instrs(
mod_, body,
)
if body_changed {
rewritten[def_idx] = @lib.Func::new(locals, @lib.Expr::new(next_body))
changed = true
}
}
if changed {
mod_.with_code_sec(@lib.CodeSec::new(rewritten)).without_name_sec()
} else {
mod_
}
}
///|
fn run_hot_pipeline_dae_fold_i32_tee_compare_ifs_instrs(
instrs : Array[@lib.Instruction],
) -> (Array[@lib.Instruction], Bool) {
let out : Array[@lib.Instruction] = []
let mut changed = false
let mut idx = 0
while idx < instrs.length() {
if idx + 4 < instrs.length() {
match
(
instrs[idx],
instrs[idx + 1],
instrs[idx + 2],
instrs[idx + 3],
instrs[idx + 4],
) {
(
@lib.I32Const(lhs),
@lib.LocalTee(local_idx),
@lib.I32Const(rhs),
compare,
@lib.If(block_type, then_body, Some(else_body)),
) => {
let supported_block_type = match block_type {
@lib.VoidBlockType | @lib.ValTypeBlockType(_) => true
_ => false
}
let condition = match compare {
@lib.I32Eq => Some(lhs == rhs)
@lib.I32Ne => Some(lhs != rhs)
_ => None
}
match (supported_block_type, condition) {
(true, Some(take_then)) => {
out.push(@lib.Instruction::i32_const(lhs))
out.push(@lib.Instruction::local_set(local_idx))
let selected = if take_then { then_body } else { else_body }
for selected_instr in selected {
out.push(selected_instr)
}
changed = true
idx += 5
continue
}
_ => ()
}
}
_ => ()
}
}
let rewritten = match instrs[idx] {
@lib.Block(block_type, @lib.Expr(body)) => {
let (next_body, nested_changed) = run_hot_pipeline_dae_fold_i32_tee_compare_ifs_instrs(
body,
)
changed = changed || nested_changed
@lib.Instruction::block(block_type, @lib.Expr::new(next_body))
}
@lib.Loop(block_type, @lib.Expr(body)) => {
let (next_body, nested_changed) = run_hot_pipeline_dae_fold_i32_tee_compare_ifs_instrs(
body,
)
changed = changed || nested_changed
@lib.Instruction::loop_(block_type, @lib.Expr::new(next_body))
}
@lib.TryTable(block_type, catches, @lib.Expr(body)) => {
let (next_body, nested_changed) = run_hot_pipeline_dae_fold_i32_tee_compare_ifs_instrs(
body,
)
changed = changed || nested_changed
@lib.Instruction::try_table(
block_type,
catches,
@lib.Expr::new(next_body),
)
}
@lib.If(block_type, then_body, else_body) => {
let (next_then, then_changed) = run_hot_pipeline_dae_fold_i32_tee_compare_ifs_instrs(
then_body,
)
let mut nested_changed = then_changed
let next_else = match else_body {
Some(body) => {
let (next_body, else_changed) = run_hot_pipeline_dae_fold_i32_tee_compare_ifs_instrs(
body,
)
nested_changed = nested_changed || else_changed
Some(next_body)
}
None => None
}
changed = changed || nested_changed
@lib.Instruction::if_(block_type, next_then, next_else)
}
instr => instr
}
out.push(rewritten)
idx += 1
}
(out, changed)
}
///|
#warnings("-unused_value")
fn run_hot_pipeline_dae_fold_i32_tee_compare_ifs_touched(
mod_ : @lib.Module,
touched : Array[Bool],
) -> @lib.Module {
let funcs = match mod_.code_sec {
Some(@lib.CodeSec(funcs)) => funcs
None => return mod_
}
let rewritten = funcs.copy()
let mut changed = false
for def_idx = 0; def_idx < funcs.length(); def_idx = def_idx + 1 {
if def_idx >= touched.length() || !touched[def_idx] {
continue
}
let @lib.Func(locals, @lib.Expr(body)) = funcs[def_idx]
let (next_body, body_changed) = run_hot_pipeline_dae_fold_i32_tee_compare_ifs_instrs(
body,
)
if body_changed {
rewritten[def_idx] = @lib.Func::new(locals, @lib.Expr::new(next_body))
changed = true
}
}
if changed {
mod_.with_code_sec(@lib.CodeSec::new(rewritten)).without_name_sec()
} else {
mod_
}
}
///|
#warnings("-unused_value")
fn run_hot_pipeline_dae_broad_changed_boundary_local_cleanup(
original : @lib.Module,
current : @lib.Module,
touched : Array[Bool],
) -> (@lib.Module, Array[Int32], Array[Int32], Array[Bool]) {
let original_type_idxs = dae_collect_func_type_indices(original)
let current_type_idxs = dae_collect_func_type_indices(current)
let imported = dae_imported_func_count(current)
let funcs = match current.code_sec {
Some(@lib.CodeSec(funcs)) => funcs
None => return (current, [], [], [])
}
let cleaned_defs : Array[Int32] = []
let removed_local_counts : Array[Int32] = []
let used_fallbacks : Array[Bool] = []
let mut rewritten : Array[@lib.Func]? = None
for def_idx = 0; def_idx < funcs.length(); def_idx = def_idx + 1 {
if def_idx >= touched.length() || !touched[def_idx] {
continue
}
let func = funcs[def_idx]
let @lib.Func(locals, _) = func
let abs_idx = imported + def_idx
let (original_params, original_results) = match
dae_resolve_func_type(original, original_type_idxs, abs_idx) {
Some(@lib.FuncType(params, results)) => (params, results)
None => continue
}
let (current_params, current_results) = match
dae_resolve_func_type(current, current_type_idxs, abs_idx) {
Some(@lib.FuncType(params, results)) => (params, results)
None => continue
}
if original_params == current_params && original_results == current_results {
continue
}
if locals.length() == 0 {
cleaned_defs.push(def_idx)
removed_local_counts.push(0)
used_fallbacks.push(false)
continue
}
let (direct_compacted, direct_changed) = run_hot_pipeline_raw_compact_unused_locals_after_prefix(
func, current_params, 0,
)
let @lib.Func(direct_locals, _) = direct_compacted
let direct_removed = if direct_changed {
locals.length() - direct_locals.length()
} else {
0
}
let (compacted, removed_locals, used_fallback) = if direct_removed > 0 {
(direct_compacted, direct_removed, false)
} else {
let (cleaned, _) = run_hot_pipeline_simplify_locals_cleanup_exact_func(
func,
)
let (fallback_compacted, fallback_changed) = run_hot_pipeline_raw_compact_unused_locals_after_prefix(
cleaned, current_params, 0,
)
let @lib.Func(fallback_locals, _) = fallback_compacted
let fallback_removed = if fallback_changed {
locals.length() - fallback_locals.length()
} else {
0
}
(fallback_compacted, fallback_removed, true)
}
if removed_locals <= 0 {
cleaned_defs.push(def_idx)
removed_local_counts.push(0)
used_fallbacks.push(used_fallback)
continue
}
match rewritten {
Some(copy) => copy[def_idx] = compacted
None => {
let copy = funcs.copy()
copy[def_idx] = compacted
rewritten = Some(copy)
}
}
cleaned_defs.push(def_idx)
removed_local_counts.push(removed_locals)
used_fallbacks.push(used_fallback)
}
match rewritten {
Some(copy) =>
(
current.with_code_sec(@lib.CodeSec::new(copy)).without_name_sec(),
cleaned_defs,
removed_local_counts,
used_fallbacks,
)
None => (current, cleaned_defs, removed_local_counts, used_fallbacks)
}
}
///|
#warnings("-unused_value")
fn run_hot_pipeline_dae_admit_body_only_worth_defs(
worth_touched : Array[Bool],
cleaned_defs : Array[Int32],
removed_local_counts : Array[Int32],
used_fallbacks : Array[Bool],
trace : (String) -> Unit,
) -> Array[Int32] {
let admitted : Array[Int32] = []
for def_idx = 0; def_idx < worth_touched.length(); def_idx = def_idx + 1 {
if !worth_touched[def_idx] || cleaned_defs.contains(def_idx) {
continue
}
cleaned_defs.push(def_idx)
removed_local_counts.push(0)
used_fallbacks.push(false)
admitted.push(def_idx)
pipeline_trace_hook(
trace,
"dae-optimizing",
"broad-worth-touched-admit",
payload="def=\{def_idx}",
)
}
admitted
}
///|
fn run_hot_pipeline_dae_report_single_caller_throttle(
report : Array[Bool],
consumed : Bool,
) -> Unit {
if report.length() == 0 {
report.push(consumed)
} else {
report[0] = consumed
}
}
///|
fn run_hot_pipeline_module_has_assemblyscript_exported_memory_runtime(
mod_ : @lib.Module,
) -> Bool {
let mut has_memory = false
let mut has_new = false
let mut has_pin = false
let mut has_collect = false
let mut has_initialize = false
match mod_.export_sec {
Some(@lib.ExportSec(exports)) =>
for export_ in exports {
let @lib.Export(name, idx) = export_
match idx {
@lib.MemExternIdx(_) =>
if name == @lib.Name::from_string("memory") {
has_memory = true
}
@lib.FuncExternIdx(_) =>
if name == @lib.Name::from_string("__new") {
has_new = true
} else if name == @lib.Name::from_string("__pin") {
has_pin = true
} else if name == @lib.Name::from_string("__collect") {
has_collect = true
} else if name == @lib.Name::from_string("_initialize") {
has_initialize = true
}
_ => ()
}
}
None => ()
}
has_memory && has_new && has_pin && has_collect && has_initialize
}
///|
fn run_hot_pipeline_module_has_assemblyscript_json_exported_memory_runtime(
mod_ : @lib.Module,
) -> Bool {
if !run_hot_pipeline_module_has_assemblyscript_exported_memory_runtime(mod_) {
return false
}
match mod_.export_sec {
Some(@lib.ExportSec(exports)) =>
for export_ in exports {
match export_ {
@lib.Export(name, @lib.FuncExternIdx(_)) =>
if name == @lib.Name::from_string("serializeN") ||
name == @lib.Name::from_string("deserializeN") {
return true
}
_ => ()
}
}
None => ()
}
false
}
///|
fn run_hot_pipeline_module_has_assemblyscript_run_exported_memory_runtime(
mod_ : @lib.Module,
) -> Bool {
if !run_hot_pipeline_module_has_assemblyscript_exported_memory_runtime(mod_) {
return false
}
match mod_.export_sec {
Some(@lib.ExportSec(exports)) =>
for export_ in exports {
match export_ {
@lib.Export(name, @lib.FuncExternIdx(_)) =>
if name == @lib.Name::from_string("run") {
return true
}
_ => ()
}
}
None => ()
}
false
}
///|
fn run_hot_pipeline_module_has_assemblyscript_startup_exported_memory(
mod_ : @lib.Module,
) -> Bool {
let mut has_memory = false
let mut has_start = false
let mut has_result = false
match mod_.export_sec {
Some(@lib.ExportSec(exports)) =>
for export_ in exports {
let @lib.Export(name, idx) = export_
match idx {
@lib.MemExternIdx(_) =>
if name == @lib.Name::from_string("memory") {
has_memory = true
}
@lib.FuncExternIdx(_) =>
if name == @lib.Name::from_string("_start") {
has_start = true
} else if name == @lib.Name::from_string("result") {
has_result = true
}
_ => ()
}
}
None => ()
}
has_memory && has_start && has_result
}
///|
fn run_hot_pipeline_apply_dae_optimizing_module_pass(
mod_ : @lib.Module,
options : HotPipelineOptions,
single_caller_throttle_consumed~ : Bool = false,
single_caller_throttle_report~ : Array[Bool] = [],
) -> Result[@lib.Module, String] {
if o4z_size_candidate_has_unsafe_emscripten_main_runtime(mod_) {
pipeline_trace_hook(
options.trace,
"dae-optimizing",
"fallback-noop",
payload="reason=emscripten-main-stateful-runtime-dae-noop",
)
return Ok(mod_)
}
if run_hot_pipeline_module_has_assemblyscript_startup_exported_memory(mod_) {
pipeline_trace_hook(
options.trace,
"dae-optimizing",
"fallback-noop",
payload="reason=assemblyscript-startup-exported-memory-dae-noop",
)
return Ok(mod_)
}
if run_hot_pipeline_module_has_assemblyscript_json_exported_memory_runtime(
mod_,
) ||
run_hot_pipeline_module_has_assemblyscript_run_exported_memory_runtime(mod_) {
pipeline_trace_hook(
options.trace,
"dae-optimizing",
"fallback-noop",
payload="reason=assemblyscript-exported-memory-dae-noop",
)
return Ok(mod_)
}
if dae_defined_func_count(mod_) >= 32 &&
run_hot_pipeline_module_has_typeidx_loop(mod_) {
pipeline_trace_hook(
options.trace,
"dae-optimizing",
"fallback-typed-loop-safe-batch",
payload="reason=typed-loop-param-control",
)
let (bounded, _, changed, cleanup_touched) = dae_run_typed_loop_safe_batch_result(
mod_,
options.trace,
perf=options.perf,
cleanup_dropped_pure_leaves=true,
)
if !changed {
return Ok(mod_)
}
let simplify_local_touched = Array::make(cleanup_touched.length(), false)
let coalesce_local_touched = Array::make(cleanup_touched.length(), false)
let high_local_touched = Array::make(cleanup_touched.length(), false)
match bounded.code_sec {
Some(@lib.CodeSec(funcs)) =>
for def_idx = 0; def_idx < funcs.length(); def_idx = def_idx + 1 {
if def_idx < cleanup_touched.length() && cleanup_touched[def_idx] {
let @lib.Func(locals, _) = funcs[def_idx]
if locals.length() >= 384 {
simplify_local_touched[def_idx] = true
}
if locals.length() >= 32 && locals.length() <= 512 {
coalesce_local_touched[def_idx] = true
}
if locals.length() >= 128 {
high_local_touched[def_idx] = true
}
}
}
None => ()
}
pipeline_trace_hook(
options.trace,
"dae-optimizing",
"nested-pass",
payload="name=simplify-locals-nostructure touched=\{dae_count_touched(simplify_local_touched)} min-body-locals=384",
)
let simplify_start_us = perf_start_timer_opt(
options.perf,
"detail:dae:typed-loop-safe:simplify-locals-nostructure",
)
let simplified = match
run_hot_pipeline_apply_hot_pass_to_touched(
bounded,
simplify_locals_nostructure_descriptor(),
simplify_local_touched,
options,
) {
Ok(next) => next
Err(err) => {
perf_stop_timer_opt(
options.perf,
"detail:dae:typed-loop-safe:simplify-locals-nostructure",
simplify_start_us,
)
return Err(err)
}
}
perf_stop_timer_opt(
options.perf,
"detail:dae:typed-loop-safe:simplify-locals-nostructure",
simplify_start_us,
)
pipeline_trace_hook(
options.trace,
"dae-optimizing",
"nested-pass",
payload="name=coalesce-locals touched=\{dae_count_touched(coalesce_local_touched)} min-body-locals=32 max-body-locals=512",
)
let coalesce_start_us = perf_start_timer_opt(
options.perf,
"detail:dae:typed-loop-safe:coalesce-locals",
)
let coalesced = match
run_hot_pipeline_apply_coalesce_locals_to_touched(
simplified, coalesce_local_touched,
) {
Ok(next) => next
Err(err) => {
perf_stop_timer_opt(
options.perf,
"detail:dae:typed-loop-safe:coalesce-locals",
coalesce_start_us,
)
return Err(err)
}
}
perf_stop_timer_opt(
options.perf,
"detail:dae:typed-loop-safe:coalesce-locals",
coalesce_start_us,
)
pipeline_trace_hook(
options.trace,
"dae-optimizing",
"nested-pass",
payload="name=reorder-locals touched=\{dae_count_touched(high_local_touched)} min-body-locals=128",
)
return run_hot_pipeline_apply_reorder_locals_to_touched(
coalesced, high_local_touched,
)
}
let core_start_us = perf_start_timer_opt(options.perf, "detail:dae:core")
let result = dae_run_core(
mod_,
options.trace,
optimize=true,
perf=options.perf,
single_caller_throttle_consumed~,
traps_never_happen=options.traps_never_happen,
no_callback_trace=hot_pipeline_dae_no_callback_trace(options),
)
let mut core_effects = result.effects
let throttle_consumed = result.single_caller_throttle_consumed ||
dae_modules_form_single_caller_param_wave(mod_, result.mod_out)
perf_stop_timer_opt(options.perf, "detail:dae:core", core_start_us)
let (core_repaired, core_repair_changed) = dae_cleanup_dropped_unreachable_debris_touched(
result.mod_out,
result.touched,
)
if core_repair_changed {
pipeline_trace_hook(
options.trace,
"dae-optimizing",
"mandatory-boundary-repair",
payload="reason=unreachable-tail-after-signature-change",
)
}
let mut core_mod = core_repaired
let mut core_touched = result.touched
let call_sink_uniform = dae_run_call_sink_uniform_parameter_flow(
core_mod,
mod_,
core_touched,
result.changed,
)
core_mod = call_sink_uniform.mod_out
core_touched = call_sink_uniform.touched
core_effects = dae_commit_effects_merge(
core_effects,
call_sink_uniform.effects,
)
let call_sink_frontier = dae_collect_applied_call_sink_uniform_defs(
mod_, core_mod,
)
for primary_def in call_sink_uniform.productive_defs {
(options.trace)(
"pass[dae-optimizing]:call-sink-uniform primary_def=\{primary_def}",
)
}
let broad_adjacent = dae_run_broad_exact_param_adjacent_cleanup_root(
mod_,
core_mod,
call_sink_frontier,
core_touched,
result.changed,
options.trace,
)
core_mod = broad_adjacent.mod_out
core_touched = broad_adjacent.touched
core_effects = dae_commit_effects_merge(core_effects, broad_adjacent.effects)
match broad_adjacent.candidate {
Some(candidate) =>
(options.trace)(
"pass[dae-optimizing]:broad-exact-param-adjacent root_def=\{candidate.root_def} defs=[\{candidate.first_def}, \{candidate.second_def}] payoff=\{candidate.payoff}",
)
None => ()
}
// Forwarding SCC/component work is owned by the epoch-bearing effect
// frontier after the shared nested roster; do not run a pre-roster phase loop.
let broad_uniform = dae_run_broad_uniform_parameter_flow(
core_mod,
mod_,
core_touched,
result.changed,
)
core_mod = broad_uniform.mod_out
core_touched = broad_uniform.touched
core_effects = dae_commit_effects_merge(core_effects, broad_uniform.effects)
for primary_def in broad_uniform.productive_defs {
(options.trace)(
"pass[dae-optimizing]:broad-exact-uniform primary_def=\{primary_def}",
)
}
let payoff_chain = dae_run_broad_dropped_result_payoff_chain(
core_mod,
core_touched,
result.changed,
perf=options.perf,
)
core_mod = payoff_chain.mod_out
core_touched = payoff_chain.touched
core_effects = dae_commit_effects_merge(core_effects, payoff_chain.effects)
let payoff_cleanup_candidates = dae_collect_dropped_result_payoff_cleanup_candidates(
mod_, core_mod,
)
let payoff_final_reorder_defs : Array[Int32] = []
for candidate in payoff_cleanup_candidates {
payoff_final_reorder_defs.push(candidate.callee_def)
}
let payoff_literal = run_hot_pipeline_apply_dae_payoff_direct_callee_exact_literal(
mod_, core_mod, payoff_final_reorder_defs, core_touched, options,
)
core_mod = payoff_literal.mod_out
core_touched = payoff_literal.touched
core_effects = dae_commit_effects_merge(core_effects, payoff_literal.effects)
if payoff_literal.applied {
let post_literal_direct_gc = dae_apply_direct_gc_batch_transaction(
dae_collect_original_boundary_snapshot(mod_),
core_mod,
core_touched,
true,
)
if post_literal_direct_gc.applied {
core_mod = post_literal_direct_gc.mod_out
core_touched = post_literal_direct_gc.touched
core_effects = dae_commit_effects_merge(
core_effects,
post_literal_direct_gc.effects,
)
(options.trace)(
"pass[dae-optimizing]:payoff-post-literal-direct-gc primary_def=\{post_literal_direct_gc.primary_def}",
)
}
let post_literal_adjacent = dae_run_broad_exact_param_adjacent_cleanup_root(
mod_,
core_mod,
payoff_final_reorder_defs,
core_touched,
true,
options.trace,
)
core_mod = post_literal_adjacent.mod_out
core_touched = post_literal_adjacent.touched
core_effects = dae_commit_effects_merge(
core_effects,
post_literal_adjacent.effects,
)
match post_literal_adjacent.candidate {
Some(candidate) =>
(options.trace)(
"pass[dae-optimizing]:payoff-post-literal-adjacent root_def=\{candidate.root_def} defs=[\{candidate.first_def}, \{candidate.second_def}] payoff=\{candidate.payoff}",
)
None => ()
}
}
// Finish the committed boundary's local read/write debris on its touched
// functions. This erases unread storage without running a private pass
// roster or moving effectful producers, including callers that correctly
// do not qualify for the more expensive nested optimization roster.
core_mod = dae_cleanup_touched_unread_locals(core_mod, core_touched)
// Payoff/result producers already publish exact worth and touched effects.
// The shared roster below is the sole nested cleanup owner; do not replay
// simplify/vacuum/coalesce/reorder in a payoff-private fixed-round loop.
// Feed producer-owned worth directly to the sole shared cleanup roster.
// Any body cleanup it exposes is detected from the shared before/after
// product and drives the boundary effect frontier below.
let worth_optimizing_touched = run_hot_pipeline_dae_worth_optimizing_touched(
mod_,
core_mod,
core_touched,
producer_effects=Some(core_effects),
)
let pre_cleanup_dependencies = dae_boundary_dependency_graph_new(
dae_current_boundary_graph_new(core_mod),
)
let cleaned = match
run_hot_pipeline_apply_dae_nested_cleanup(
core_mod,
worth_optimizing_touched,
options,
diagnostics=Some(result.diagnostics),
) {
Ok(next) => next
Err(err) => return Err(err)
}
let broad_cleanup_touched = worth_optimizing_touched
// Shared nested cleanup feeds all newly exposed boundaries to the common
// epoch-bearing effect frontier below. Do not recursively invoke dae_run_core
// or apply a post-roster DAE-private body cleanup phase.
let post_cleanup_touched : Array[Bool] = []
dae_trace_diagnostics(options.trace, "dae-optimizing", result.diagnostics)
let post_cleanup_stripped = cleaned
let prune_start_us = perf_start_timer_opt(
options.perf,
"detail:dae:prune-unused-simple-func-types",
)
let pruned = dae_prune_unused_simple_func_types(post_cleanup_stripped)
perf_stop_timer_opt(
options.perf,
"detail:dae:prune-unused-simple-func-types",
prune_start_us,
)
// Final cleanup may expose another owned boundary, but convergence must stay
// on the producer-owned dependency frontier. Do not recursively replay the
// whole DAEO pipeline. Drain all represented boundary families for the exact
// worth/cleanup seed, run the shared nested cleanup only for committed
// effects, and retain each wave only when it validates and strictly shrinks.
let original = dae_collect_original_boundary_snapshot(mod_)
let mut converged = pruned
let mut frontier_seed = dae_merge_touched(
broad_cleanup_touched, worth_optimizing_touched,
)
frontier_seed = dae_merge_touched(frontier_seed, post_cleanup_touched)
let forwarding_graph = dae_current_boundary_graph_new(converged)
let forwarding_dependencies = dae_boundary_dependency_graph_new(
forwarding_graph,
)
frontier_seed = run_hot_pipeline_dae_expand_frontier_seed_callees(
frontier_seed, pre_cleanup_dependencies, forwarding_dependencies,
)
for members in forwarding_dependencies.forwarding_component_members {
if members.length() < 2 {
continue
}
for def_idx in members {
if def_idx >= 0 && def_idx < frontier_seed.length() {
frontier_seed[def_idx] = true
}
}
}
let mut convergence_wave = 0
while true {
(options.trace)(
"pass[dae-optimizing]:effect-frontier wave=\{convergence_wave} throttle_consumed=\{throttle_consumed} seeds=\{dae_count_touched(frontier_seed)}",
)
let frontier = dae_run_effect_frontier_worklist(
original,
converged,
frontier_seed,
!throttle_consumed,
options.trace,
)
dae_trace_diagnostics(
options.trace,
"dae-optimizing-effect-frontier",
frontier.diagnostics,
)
if !frontier.changed {
(options.trace)(
"pass[dae-optimizing]:effect-frontier-result wave=\{convergence_wave} accepted=false reason=empty",
)
run_hot_pipeline_dae_report_single_caller_throttle(
single_caller_throttle_report, throttle_consumed,
)
return Ok(converged)
}
let (repaired, _) = dae_cleanup_dropped_unreachable_debris_touched(
frontier.mod_out,
frontier.touched,
)
let frontier_worth = run_hot_pipeline_dae_worth_optimizing_touched(
converged,
repaired,
frontier.touched,
producer_effects=Some(frontier.effects),
)
let frontier_cleaned = match
run_hot_pipeline_apply_dae_nested_cleanup(
repaired, frontier_worth, options,
) {
Ok(next) => next
Err(err) => {
(options.trace)(
"pass[dae-optimizing]:effect-frontier-result wave=\{convergence_wave} accepted=false reason=nested-error err=\{err}",
)
run_hot_pipeline_dae_report_single_caller_throttle(
single_caller_throttle_report, throttle_consumed,
)
return Ok(converged)
}
}
let candidate = dae_prune_unused_simple_func_types(frontier_cleaned)
match
(
run_hot_pipeline_dae_encoded_size(converged),
run_hot_pipeline_dae_encoded_size(candidate),
@validate.validate_module(candidate),
) {
(Some(before), Some(after), Ok(_)) if after < before => {
(options.trace)(
"pass[dae-optimizing]:effect-frontier-result wave=\{convergence_wave} accepted=true before=\{before} after=\{after} defs=\{dae_count_touched(frontier_worth)}",
)
converged = candidate
frontier_seed = frontier_worth
convergence_wave += 1
}
(before, after, validation) => {
let reason = match validation {
Ok(_) => "non-smaller-or-unencodable"
Err(_) => "validation"
}
(options.trace)(
"pass[dae-optimizing]:effect-frontier-result wave=\{convergence_wave} accepted=false reason=\{reason} before=\{before.to_string()} after=\{after.to_string()}",
)
run_hot_pipeline_dae_report_single_caller_throttle(
single_caller_throttle_report, throttle_consumed,
)
return Ok(converged)
}
}
}
Ok(converged)
}
///|
fn run_hot_pipeline_should_defer_call_bulk_memory_module(
mod_ : @lib.Module,
max_defined_funcs_exclusive : Int32,
) -> Bool {
match mod_.code_sec {
Some(@lib.CodeSec(funcs)) => {
if funcs.length() < 286 ||
(
max_defined_funcs_exclusive >= 0 &&
funcs.length() >= max_defined_funcs_exclusive
) {
return false
}
for func in funcs {
let @lib.Func(_, @lib.Expr(body)) = func
if run_hot_pipeline_raw_simplify_locals_has_call_bulk_memory_effect_mesh(
body,
) {
return true
}
}
false
}
_ => false
}
}
///|
fn run_hot_pipeline_cleanup_medium_inlining_fallback_locals(
mod_ : @lib.Module,
) -> Result[@lib.Module, String] {
let func_count = match mod_.code_sec {
Some(@lib.CodeSec(funcs)) => funcs.length()
None => return Ok(mod_)
}
// The caller entered the >=286-definition guarded path before inlining;
// helper deletion may move the rewritten module just below that boundary.
// Keep cleanup below the artifact-scale lane and reuse the existing bounded,
// defaultable, validated coalescing policy.
if func_count >= 1000 {
return Ok(mod_)
}
run_hot_pipeline_apply_coalesce_locals_to_bounded_defs(mod_)
}
///|
fn run_hot_pipeline_apply_module_pass(
mod_ : @lib.Module,
entry : HotPassRegistryEntry,
options : HotPipelineOptions,
) -> Result[@lib.Module, String] {
pipeline_trace_hook(options.trace, entry.name, "start")
let metadata_safe = entry.name == "strip-debug" ||
no_inline_pass_pattern(entry.name) is Some(_)
if !metadata_safe && pass_module_has_offset_sensitive_metadata(mod_) {
pipeline_trace_hook(
options.trace,
entry.name,
"fallback-noop",
payload="reason=offset-sensitive-metadata",
)
return Ok(mod_)
}
let module_pass_stage_start_us = perf_start_timer_opt(
options.perf,
"stage:module-pass",
)
let pass_timer_start = perf_start_timer_opt(
options.perf,
"pass:\{entry.name}",
)
let next = match entry.name {
"avoid-reinterprets" => avoid_reinterprets_run_module_pass(mod_)
"untee" => untee_run_module_pass(mod_)
"make-shared-objects" => make_shared_objects_run_module_pass(mod_)
"duplicate-function-elimination" =>
dfe_run_module_pass_with_perf(mod_, options.perf)
"merge-similar-functions" =>
msf_run_module_pass_with_perf(mod_, options.perf)
"remove-unused-module-elements" =>
rume_run_module_pass(
mod_,
traps_never_happen=options.traps_never_happen,
closed_world=options.closed_world,
)
"remove-empty-function-exports" =>
remove_empty_function_exports_run_module_pass(mod_)
"intrinsic-lowering" => intrinsic_lowering_run_module_pass(mod_)
"global-effects" => global_effects_run_module_pass(mod_)
"global-type-optimization" =>
global_type_optimization_run_module_pass(mod_, options.closed_world)
"i64-trunc-sat-float-lowering" =>
i64_trunc_sat_float_lowering_run_module_pass(mod_)
"remove-unused-nonfunction-module-elements" =>
rume_run_nonfunction_module_pass(
mod_,
traps_never_happen=options.traps_never_happen,
closed_world=options.closed_world,
)
"memory-packing" =>
memory_packing_run_module_pass(
mod_,
traps_never_happen=options.traps_never_happen,
zero_filled_memory=options.zero_filled_memory,
)
"dealign" => dealign_run_module_pass(mod_)
"tail-call" => tail_call_run_module_pass(mod_)
"once-reduction" => once_reduction_run_module_pass(mod_)
"global-refining" =>
global_refining_run_module_pass(mod_, closed_world=options.closed_world)
"global-struct-inference" =>
global_struct_inference_run_module_pass(mod_, options.closed_world)
"global-struct-inference-desc-cast" =>
global_struct_inference_desc_cast_run_module_pass(
mod_,
options.closed_world,
)
"reorder-locals" => reorder_locals_run_module_pass(mod_)
"local-subtyping" => local_subtyping_run_module_pass(mod_)
"coalesce-locals" =>
if options.optimize_level >= 4 && options.shrink_level >= 1 {
match run_hot_pipeline_apply_coalesce_locals_to_bounded_defs(mod_) {
Ok(next) => next
Err(err) => return Err(err)
}
} else {
coalesce_locals_run_module_pass(mod_)
}
"coalesce-locals-cfg" => {
let defined_func_count = match mod_.code_sec {
Some(@lib.CodeSec(funcs)) => funcs.length()
None => 0
}
if o4z_size_candidate_has_unsafe_emscripten_main_runtime(mod_) {
(options.trace)(
"pass[coalesce-locals-cfg]:skip reason=emscripten-main-stateful-runtime-coalesce-locals-cfg-noop",
)
mod_
} else if run_hot_pipeline_module_has_assemblyscript_json_exported_memory_runtime(
mod_,
) ||
run_hot_pipeline_module_has_assemblyscript_run_exported_memory_runtime(
mod_,
) ||
run_hot_pipeline_module_has_assemblyscript_startup_exported_memory(mod_) {
(options.trace)(
"pass[coalesce-locals-cfg]:skip reason=assemblyscript-exported-memory-cfg-coalesce-noop",
)
mod_
} else if run_hot_pipeline_module_has_try_table_catch_body_local_write(
mod_,
) {
(options.trace)(
"pass[coalesce-locals-cfg]:skip reason=try-table-local-write-cfg-coalesce-noop",
)
mod_
} else if options.optimize_level >= 4 &&
options.shrink_level >= 1 &&
!run_hot_pipeline_o4z_cfg_coalesce_module_selected(defined_func_count) {
(options.trace)(
"pass[coalesce-locals-cfg]:skip reason=artifact-scale-defined-functions count=\{defined_func_count}",
)
mod_
} else {
o4z_restore_optimized_primes_variadic_pack_funcs(
mod_,
coalesce_locals_run_module_pass(mod_),
)
}
}
"local-cse" => local_cse_run_module_pass(mod_)
"duplicate-import-elimination" => die_run_module_pass(mod_)
"duplicate-import-elimination-assume-stable-bindings" =>
die_run_module_pass_assume_stable_bindings(mod_)
"strip-debug" => strip_debug_run_module_pass(mod_)
"simplify-globals-optimizing" =>
match run_hot_pipeline_apply_sgo_optimizing_module_pass(mod_, options) {
Ok(next) => next
Err(err) => return Err(err)
}
"propagate-globals-globally" =>
propagate_globals_globally_run_module_pass(mod_)
"dae2" | "dae2-optimizing" =>
match
dae2_run_module_pass(mod_, options.closed_world, perf=options.perf) {
Ok(next) =>
if entry.name == "dae2-optimizing" {
match
run_hot_pipeline(next, options, ["simplify-locals", "vacuum"]) {
Ok(cleaned) => dae2_cleanup_captures(cleaned)
Err(err) => return Err(err)
}
} else {
next
}
Err(err) => return Err(err)
}
"dead-argument-elimination" | "dae" =>
if dae_defined_func_count(mod_) >= 32 &&
run_hot_pipeline_module_has_typeidx_loop(mod_) {
(options.trace)(
"pass[dae]:fallback-typed-loop-safe-batch reason=typed-loop-param-control",
)
dae_run_typed_loop_safe_batch(mod_, options.trace, perf=options.perf)
} else {
dae_run_module_pass_with_trap_policy(
mod_,
trace=Some(options.trace),
traps_never_happen=options.traps_never_happen,
perf=options.perf,
no_callback_trace=hot_pipeline_dae_no_callback_trace(options),
)
}
"dae-optimizing" | "dead-argument-elimination-optimizing" =>
if run_hot_pipeline_should_defer_call_bulk_memory_module(mod_, 1000) {
(options.trace)(
"pass[dae-optimizing]:fallback-plain-results-only reason=medium-call-bulk-memory-param-control",
)
dae_run_dropped_result_batch_only(
mod_,
options.trace,
perf=options.perf,
)
} else {
match run_hot_pipeline_apply_dae_optimizing_module_pass(mod_, options) {
Ok(next) => next
Err(err) => return Err(err)
}
}
"inline-main" => inline_main_run_module_pass(mod_)
"inlining" =>
inlining_run_module_pass_with_perf(
mod_,
optimize=false,
trace=Some(options.trace),
pass_name="inlining",
optimize_level=options.optimize_level,
shrink_level=options.shrink_level,
always_inline_max_size=options.inlining_always_inline_max_size,
one_caller_inline_max_size=options.inlining_one_caller_inline_max_size,
flexible_inline_max_size=options.inlining_flexible_inline_max_size,
max_combined_binary_size=options.inlining_max_combined_binary_size,
allow_functions_with_loops=options.inlining_allow_functions_with_loops,
partial_inlining_ifs=options.inlining_partial_inlining_ifs,
perf=options.perf,
)
"inlining-optimizing" =>
if o4z_size_candidate_has_unsafe_emscripten_main_runtime(mod_) {
(options.trace)(
"pass[inlining-optimizing]:skip reason=emscripten-main-stateful-runtime-inlining-noop",
)
mod_
} else if run_hot_pipeline_should_defer_call_bulk_memory_module(mod_, -1) {
(options.trace)(
"pass[inlining-optimizing]:fallback-plain-tiny reason=large-call-bulk-memory-param-control",
)
let inlined = inlining_run_module_pass_with_perf(
mod_,
optimize=false,
trace=Some(options.trace),
pass_name="inlining-optimizing",
optimize_level=options.optimize_level,
shrink_level=options.shrink_level,
always_inline_max_size=2,
one_caller_inline_max_size=4,
flexible_inline_max_size=0,
max_combined_binary_size=options.inlining_max_combined_binary_size,
allow_functions_with_loops=false,
partial_inlining_ifs=0,
perf=options.perf,
)
match
run_hot_pipeline_cleanup_medium_inlining_fallback_locals(inlined) {
Ok(next) => next
Err(err) => return Err(err)
}
} else {
inlining_run_module_pass_with_perf(
mod_,
optimize=true,
trace=Some(options.trace),
pass_name="inlining-optimizing",
optimize_level=options.optimize_level,
shrink_level=options.shrink_level,
always_inline_max_size=options.inlining_always_inline_max_size,
one_caller_inline_max_size=options.inlining_one_caller_inline_max_size,
flexible_inline_max_size=options.inlining_flexible_inline_max_size,
max_combined_binary_size=options.inlining_max_combined_binary_size,
allow_functions_with_loops=options.inlining_allow_functions_with_loops,
partial_inlining_ifs=options.inlining_partial_inlining_ifs,
perf=options.perf,
)
}
_ =>
match no_inline_pass_pattern(entry.name) {
Some((base, pattern)) =>
match base {
"no-inline" =>
no_inline_run_module_pass(
mod_,
pattern,
mark_full=true,
mark_partial=true,
)
"no-full-inline" =>
no_inline_run_module_pass(
mod_,
pattern,
mark_full=true,
mark_partial=false,
)
"no-partial-inline" =>
no_inline_run_module_pass(
mod_,
pattern,
mark_full=false,
mark_partial=true,
)
_ => return Err("unsupported module pass \{entry.name}")
}
None =>
match entry.name {
"string-gathering" => string_gathering_run_module_pass(mod_)
"reorder-globals" => reorder_globals_run_module_pass(mod_)
"apply-compiler-facts" =>
match apply_compiler_facts(mod_, options.compiler_fact_policy) {
Ok(result) => {
if result.stats.fact_sections_seen > 0 {
(options.trace)(
"pass[apply-compiler-facts]:summary " +
compiler_fact_stats_summary(result.stats),
)
}
result.module_
}
Err(err) => return Err(err)
}
"directize" => directize_run_module_pass(mod_)
_ => return Err("unsupported module pass \{entry.name}")
}
}
}
perf_stop_timer_opt(options.perf, "pass:\{entry.name}", pass_timer_start)
match options.verify_policy {
HotPipelineVerifyPolicy::AfterEachPass =>
match
pipeline_verify_checkpoint_with_perf(
options.perf,
HotPipelineVerifyPolicy::AfterEachPass,
"verify after module pass \{entry.name}",
HotPipelineVerifyTarget::ModuleTarget(next),
"verify:module-pass:\{entry.name}",
) {
Ok(_) => ()
Err(err) => return Err(err)
}
HotPipelineVerifyPolicy::FinalModuleOnly => ()
}
perf_stop_timer_opt(
options.perf,
"stage:module-pass",
module_pass_stage_start_us,
)
pipeline_trace_hook(options.trace, entry.name, "done")
Ok(next)
}
///|
pub fn hot_pass_run(
ctx : HotPassContext,
func : @ir.HotFunc,
) -> Result[HotPassResult, String] {
hot_pass_run_with_aggregate_perf_timer(ctx, func, None)
}
///|
fn hot_pass_run_with_aggregate_perf_timer(
ctx : HotPassContext,
func : @ir.HotFunc,
aggregate_perf_timer : ((String, UInt64) -> Unit)?,
) -> Result[HotPassResult, String] {
let descriptor = ctx.descriptor
let pre_pass_start_us = perf_start_timer_opt(
ctx.perf,
"stage:hot-pass:pre-pass",
)
pass_prepare_requirements(ctx, func)
if descriptor.name != "vacuum" {
pipeline_trace_hook(ctx.trace, descriptor.name, "start")
}
perf_dump_hot_func_opt(ctx.perf, func, "before-pass:\{descriptor.name}")
let before_snapshot = perf_snapshot_hot_func(func)
let before_revision = @ir.hot_revision_current(func)
perf_stop_timer_accumulate_opt(
ctx.perf,
"stage:hot-pass:pre-pass",
pre_pass_start_us,
)
let pass_timer_start = perf_start_timer_opt(
ctx.perf,
"pass:\{descriptor.name}",
)
let result = match descriptor.name {
"flatten" => flatten_run(ctx, func)
"ssa" => ssa_full_run(ctx, func)
"ssa-nomerge" => ssa_nomerge_run(ctx, func)
"merge-locals" => merge_locals_run(ctx, func)
"vacuum" => {
let dropped_node_workspace = VacuumDroppedNodeWorkspace::new(
@ir.hot_node_count(func),
)
if hot_pass_vacuum_should_skip_root_local_set_stack_hazard(func) {
pipeline_trace_hook(
ctx.trace,
descriptor.name,
"skip-hot",
payload="reason=root-local-set-stack-hazard-noop",
)
HotPassResult::unchanged()
} else if hot_pass_vacuum_remove_local_only_void_body(ctx, func) {
HotPassResult::changed()
} else if hot_pass_vacuum_should_skip_branchy_structured_writes(func) {
if hot_pass_vacuum_remove_dropped_local_gets_in_region_with_workspace(
ctx,
func,
@ir.HotRegionRef::root(),
dropped_node_workspace,
) {
HotPassResult::changed()
} else {
HotPassResult::unchanged()
}
} else if hot_pass_remove_region_nops_with_workspace(
ctx,
func,
@ir.HotRegionRef::root(),
Array::make(@ir.hot_node_count(func), false),
dropped_node_workspace,
) {
HotPassResult::changed()
} else {
HotPassResult::unchanged()
}
}
"dead-code-elimination" => dead_code_elimination_run(ctx, func)
"remove-unused-names" => remove_unused_names_run(ctx, func)
"remove-unused-brs" => remove_unused_brs_run(ctx, func)
"optimize-instructions" => optimize_instructions_run(ctx, func)
"heap-store-optimization" => heap_store_optimization_run(ctx, func)
"heap2local" => heap2local_run(ctx, func)
"optimize-casts" => optimize_casts_run(ctx, func)
"pick-load-signs" => pick_load_signs_run(ctx, func)
"precompute" => precompute_run(ctx, func)
"constraint-analysis" => constraint_analysis_run(ctx, func)
"precompute-propagate" => {
let propagated = precompute_propagate_run(ctx, func)
if !propagated.changed &&
run_hot_pipeline_precompute_has_void_self_branch_block(func) {
HotPassResult::changed()
} else {
propagated
}
}
"code-pushing" => code_pushing_run(ctx, func)
"code-folding" => code_folding_run(ctx, func)
"local-cse" => local_cse_run(ctx, func)
"tuple-optimization" => tuple_optimization_run(ctx, func)
"simplify-locals" => simplify_locals_run(ctx, func)
"simplify-locals-notee" | "simplify-locals-no-tee" =>
simplify_locals_notee_run(ctx, func)
"simplify-locals-nonesting" | "simplify-locals-no-nesting" =>
simplify_locals_nonesting_run(ctx, func)
"simplify-locals-nostructure" | "simplify-locals-no-structure" =>
simplify_locals_nostructure_run(ctx, func)
"simplify-locals-notee-nostructure" =>
simplify_locals_notee_nostructure_run(ctx, func)
"merge-blocks" => merge_blocks_run(ctx, func)
"redundant-set-elimination" => rse_run(ctx, func)
_ => return Err("unsupported hot pass \{descriptor.name}")
}
match aggregate_perf_timer {
Some(record) =>
record(
"pass:\{descriptor.name}",
perf_elapsed_since_start_opt(ctx.perf, pass_timer_start),
)
None =>
perf_stop_timer_opt(ctx.perf, "pass:\{descriptor.name}", pass_timer_start)
}
let post_pass_start_us = perf_start_timer_opt(
ctx.perf,
"stage:hot-pass:post-pass",
)
match ctx.perf {
Some(perf) =>
perf_count_hot_alloc_delta(
perf,
before_snapshot,
perf_snapshot_hot_func(func),
)
None => ()
}
perf_dump_hot_func_opt(ctx.perf, func, "after-pass:\{descriptor.name}")
if @ir.hot_revision_current(func) != before_revision {
@ir.cache_invalidate_analyses(
ctx.cache,
@ir.hot_pass_invalidates(descriptor),
)
}
match pass_verify_before_after(ctx, func) {
Ok(_) => ()
Err(err) => return Err(err)
}
let after_revision = @ir.hot_revision_current(func)
if descriptor.name == "vacuum" && after_revision != before_revision {
pipeline_trace_hook(
ctx.trace,
descriptor.name,
"mutated",
payload="count=\{after_revision - before_revision} revision=\{after_revision}",
)
}
if descriptor.name != "vacuum" {
pipeline_trace_hook(
ctx.trace,
descriptor.name,
"done",
payload="changed=\{result.changed}",
)
}
perf_stop_timer_accumulate_opt(
ctx.perf,
"stage:hot-pass:post-pass",
post_pass_start_us,
)
Ok(result)
}
///|
// Arity is only a rejection precheck. Every possible split still uses the
// original checker, including operand types, local initialization and escapes.
// Unknown controls abandon the precheck for the rest of this query.
fn sl_value_suffix_stack_effect(
instr : @lib.Instruction,
env : @validate.Env,
) -> (Int32, Int32)? {
match instr {
@lib.Block(_, _) | @lib.Loop(_, _) => None
@lib.Select(Some(types)) => Some((types.length() * 2 + 1, types.length()))
@lib.Call(index) =>
match env.get_functype_by_funcidx(index) {
Some(@lib.FuncType(params, results)) =>
Some((params.length(), results.length()))
None => None
}
@lib.CallIndirect(index, _) | @lib.CallRef(index) =>
match env.resolve_functype(index) {
Some(@lib.FuncType(params, results)) =>
Some((params.length() + 1, results.length()))
None => None
}
_ => precompute_raw_terminal_prefix_stack_effect(instr)
}
}
///|
test "cleanup reachable counts preserve owner branches and dead suffix boundaries" {
for
(body, expected) in [
(
"local.get 0 drop block local.get 1 drop br 0 local.get 2 drop end local.get 3 drop",
[1, 1, 0, 1],
),
(
"block loop local.get 0 drop br 1 end local.get 1 drop end local.get 2 drop",
[1, 0, 1, 0],
),
("loop local.get 0 drop br 0 end local.get 1 drop", [1, 0, 0, 0]),
(
"local.get 0 if local.get 1 drop return else local.get 2 drop br 0 end local.get 3 drop",
[1, 1, 1, 1],
),
(
"local.get 0 if local.get 1 drop return else local.get 2 drop return end local.get 3 drop",
[1, 1, 1, 0],
),
// The existing conservative branch proof also sees syntactically dead
// branches; retaining that boundary preserves exact cleanup decisions.
("block unreachable br 0 end local.get 3 drop", [0, 0, 0, 1]),
(
"block block local.get 0 br_table 0 1 end local.get 1 drop end local.get 2 drop",
[1, 1, 1, 0],
),
(
"block block return end local.get 0 drop end local.get 1 drop",
[0, 0, 0, 0],
),
] {
let input = @wast.wast_to_binary_module(
"(module (func (param i32 i32 i32 i32) " + body + "))",
).unwrap()
assert_true(@validate.validate_module(input) is Ok(_))
let @lib.Func(_, @lib.Expr(instrs)) = input.code_sec.unwrap().0[0]
let counts = Array::make(4, 0)
run_hot_pipeline_simplify_locals_lowered_count_local_gets_in_body(
instrs, counts,
)
assert_eq(counts, expected)
}
}
///|
test "cleanup reachable counts retain try-table targets and legacy handler boundary" {
for
(body, expected) in [
(
"(block (try_table (catch_all 0) local.get 0 drop br 1) local.get 1 drop) local.get 2 drop",
[1, 0, 1, 0],
),
(
"(block (try (do local.get 0 drop br 1) (catch_all local.get 1 drop)) local.get 2 drop) local.get 3 drop",
[1, 1, 1, 1],
),
(
"(block return (try (do br 1) (catch_all br 1))) local.get 3 drop",
[0, 0, 0, 0],
),
] {
let input = @wast.wast_to_binary_module(
"(module (func (param i32 i32 i32 i32) " + body + "))",
).unwrap()
let @lib.Func(_, @lib.Expr(instrs)) = input.code_sec.unwrap().0[0]
let counts = Array::make(4, 0)
run_hot_pipeline_simplify_locals_lowered_count_local_gets_in_body(
instrs, counts,
)
assert_eq(counts, expected)
}
}
///|
test "suffix probe retains typed-block operands after rejected shorter candidates" {
let input = @wast.wast_to_binary_module(
"(module (func (param i32) (result i32) nop local.get 0 block (param i32) (result i32) i32.const 7 i32.add end))",
).unwrap()
assert_true(@validate.validate_module(input) is Ok(_))
let @lib.Func(_, @lib.Expr(body)) = input.code_sec.unwrap().0[0]
let env = @validate.Env::new()
.with_module(input)
.with_locals(@lib.Locals::from_types([@lib.ValType::i32()]))
let saved = body.copy()
let visits = [0]
let suffix = sl_value_suffix_at(
body,
body.length(),
1,
env,
typed_instructions=Some(visits),
)
assert_eq(suffix, [body[1], body[2]])
assert_eq(visits[0], 3)
assert_eq(body, saved)
assert_eq(sl_value_suffix_at([body[2]], 1, 1, env), [])
}
///|
test "nop hoist preserves homogeneous runs and independent output ownership" {
for
instruction in [
@lib.Instruction::nop(),
@lib.Instruction::i32_const(@lib.I32::new(7)),
] {
let body = Array::make(128, instruction)
let (output, changed) = run_hot_pipeline_simplify_locals_lowered_hoist_nops_across_dupable_values_with_changed(
body,
)
assert_eq(output, body)
assert_false(changed)
assert_false(physical_equal(output, body))
output[0] = @lib.Instruction::unreachable_()
assert_eq(body[0], instruction)
}
}
///|
test "nop hoist retains barrier order and exact changed flag" {
let nop = @lib.Instruction::nop()
let seven = @lib.Instruction::i32_const(@lib.I32::new(7))
let nine = @lib.Instruction::i32_const(@lib.I32::new(9))
let effect = @lib.Instruction::call(@lib.FuncIdx::new(0U))
let trap = @lib.Instruction::i32_div_s()
let body = [seven, nop, nine, effect, seven, nine, trap, nop, nop, seven]
let (output, changed) = run_hot_pipeline_simplify_locals_lowered_hoist_nops_across_dupable_values_with_changed(
body,
)
assert_eq(output, [
nop, seven, nine, effect, seven, nine, trap, nop, nop, seven,
])
assert_true(changed)
let (again, again_changed) = run_hot_pipeline_simplify_locals_lowered_hoist_nops_across_dupable_values_with_changed(
output,
)
assert_eq(again, output)
assert_false(again_changed)
}
///|
test "cleanup subtree facts retain original reads removed from later siblings" {
let input = @wast.wast_to_binary_module(
"(module (func (local i32) block i32.const 7 local.set 0 local.get 0 drop end block block nop end i32.const 9 local.set 0 local.get 0 drop end))",
).unwrap()
let @lib.Func(locals, @lib.Expr(body)) = input.code_sec.unwrap().0[0]
let saved = @binary.encode_module(input).unwrap()
let output = sl_cleanup_drop_dead_pairs_with_reads(
body,
SlCleanupReadSet::new([]),
)
// The later child's rewritten pair has no read, but its original read must
// still keep the earlier child's write alive under the same conservative rule.
assert_true(physical_equal(output[0], body[0]))
guard output[1] is @lib.Block(_, @lib.Expr(later)) else {
fail("expected later block")
}
assert_eq(
later[1:],
[@lib.Instruction::i32_const(@lib.I32::new(9)), @lib.Instruction::drop()][:],
)
assert_true(
@validate.validate_module(
input.with_code_sec(
@lib.CodeSec::new([@lib.Func::new(locals, @lib.Expr::new(output))]),
),
)
is Ok(_),
)
assert_eq(@binary.encode_module(input).unwrap(), saved)
}
///|
test "cleanup subtree facts do not leak then-arm reads into else cleanup" {
let input = @wast.wast_to_binary_module(
"(module (func (param i32) (local i32) local.get 0 if block local.get 1 drop end else block nop end i32.const 7 local.set 1 local.get 1 drop end))",
).unwrap()
let @lib.Func(locals, @lib.Expr(body)) = input.code_sec.unwrap().0[0]
let continuation = SlCleanupReadSet::new([])
let output = sl_cleanup_drop_dead_pairs_with_reads(body, continuation)
guard output[1] is @lib.If(_, _, Some(else_body)) else {
fail("expected conditional")
}
assert_eq(
else_body[1:],
[@lib.Instruction::i32_const(@lib.I32::new(7)), @lib.Instruction::drop()][:],
)
assert_eq(continuation.reads, [])
assert_true(
@validate.validate_module(
input.with_code_sec(
@lib.CodeSec::new([@lib.Func::new(locals, @lib.Expr::new(output))]),
),
)
is Ok(_),
)
}
///|
test "cleanup subtree facts retain loop self reads and isolate caller continuation" {
let input = @wast.wast_to_binary_module(
"(module (func (local i32) loop block local.get 0 drop end block i32.const 7 local.set 0 local.get 0 drop end br 0 end))",
).unwrap()
let @lib.Func(_, @lib.Expr(body)) = input.code_sec.unwrap().0[0]
let continuation = SlCleanupReadSet::new([])
let output = sl_cleanup_drop_dead_pairs_with_reads(body, continuation)
assert_eq(output, body)
assert_true(physical_equal(output, body))
assert_eq(continuation.reads, [])
assert_true(@validate.validate_module(input) is Ok(_))
}
///|
test "cleanup subtree facts retain handler reads and original wide membership" {
for
tail in [
"(block (try (do nop) (catch_all local.get 65 drop)))", "(block (try_table (catch_all 0) local.get 65 drop))",
"(block (block local.get 65 drop) local.get 64 drop)",
] {
let locals = Array::make(128, "i32").join(" ")
let input = @wast.wast_to_binary_module(
"(module (func (local \{locals}) (block i32.const 7 local.set 65 local.get 65 drop) \{tail}))",
).unwrap()
@validate.validate_module(input).unwrap()
let @lib.Func(_, @lib.Expr(body)) = input.code_sec.unwrap().0[0]
let saved = @binary.encode_module(input).unwrap()
let incoming = Array::makei(32, fn(index) { index }) + [64, 128]
let inherited = SlCleanupReadSet::new(incoming.copy())
assert_true(inherited.contains(128))
let actual = sl_cleanup_drop_dead_pairs_with_reads(body, inherited)
assert_eq(actual, body)
assert_true(physical_equal(actual[0], body[0]))
assert_eq(inherited.reads, incoming)
assert_false(inherited.contains(65))
assert_eq(@binary.encode_module(input).unwrap(), saved)
}
}