// Licensing scope: see ../../LICENSE and this directory's LICENSE and NOTICE.
// Copyright 2023 WebAssembly Community Group participants.
// Retained historical upstream notice; current derivation remains unconfirmed.
// Conservative attribution from pinned Binaryen version_130 counterpart:
// Copyright 2015 WebAssembly Community Group participants
// Counterpart: src/passes/RemoveUnusedBrs.cpp (5d704ad5); origin remains unconfirmed.
// Potentially incorporated Binaryen expression retains Apache-2.0 terms.
// Original Starshine contributions use the scoped MIT grant in ../../LICENSE.
// Modified for Starshine through 2026: implementation and validation use
// Starshine's representations and APIs; this is not an unchanged upstream file.
// Conservative notice retention does not establish copying or independence.
///|
pub fn remove_unused_brs_descriptor() -> @ir.HotPassDescriptor {
@ir.HotPassDescriptor::new("remove-unused-brs", invalidates=[
@ir.HotAnalysis::cfg(),
@ir.HotAnalysis::dominance(),
@ir.HotAnalysis::liveness(),
@ir.HotAnalysis::use_def(),
@ir.HotAnalysis::effects(),
@ir.HotAnalysis::loop_info(),
@ir.HotAnalysis::ssa(),
])
}
///|
pub fn remove_unused_brs_summary() -> String {
"Remove tail branches and returns that already flow to the surrounding continuation."
}
///|
priv enum RemoveUnusedBrsTailTarget {
NoTarget
ReturnTarget
BranchTarget(Int32)
}
///|
priv struct RemoveUnusedBrsValueIfSelectRewrite {
replacement : Int32
then_holder : Int32
else_holder : Int32
}
///|
priv struct RemoveUnusedBrsRootSite {
region_ref : @ir.HotRegionRef
idx : Int32
}
///|
priv struct RemoveUnusedBrsCycleScan {
label_refs : Array[Int32]
branch_payload_children : @bitset.BitSet
has_br_table : Bool
}
///|
priv enum RemoveUnusedBrsCaughtThrowMatch {
NoCaughtThrowMatch
CaughtThrowExact(Int32)
CaughtThrowAll(Int32)
CaughtThrowRef
}
///|
priv struct RemoveUnusedBrsHotVoidReturnLadderShape {
mut call_count : Int32
mut stack_effect_count : Int32
mut local_set_count : Int32
mut local_tee_count : Int32
mut void_if_count : Int32
mut value_if_count : Int32
mut return_count : Int32
mut select_count : Int32
mut br_if_count : Int32
mut block_count : Int32
mut multivalue_block_count : Int32
mut multivalue_control_count : Int32
mut loop_count : Int32
mut br_count : Int32
mut br_table_count : Int32
mut drop_count : Int32
mut result_block_non_block_prefix_count : Int32
}
///|
fn remove_unused_brs_scan_hot_void_return_ladder_shape(
func : @ir.HotFunc,
shape : RemoveUnusedBrsHotVoidReturnLadderShape,
) -> 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
}
match @ir.hot_node_get(func, node_id).op {
@ir.HotOp::Call | @ir.HotOp::CallIndirect | @ir.HotOp::CallRef => {
shape.call_count = shape.call_count + 1
shape.stack_effect_count = shape.stack_effect_count + 1
}
@ir.HotOp::ReturnCall
| @ir.HotOp::ReturnCallIndirect
| @ir.HotOp::ReturnCallRef => shape.call_count = shape.call_count + 1
@ir.HotOp::MemoryCopy | @ir.HotOp::MemoryFill =>
shape.stack_effect_count = shape.stack_effect_count + 1
@ir.HotOp::LocalSet => shape.local_set_count = shape.local_set_count + 1
@ir.HotOp::LocalTee => shape.local_tee_count = shape.local_tee_count + 1
@ir.HotOp::Return => shape.return_count = shape.return_count + 1
@ir.HotOp::Select => shape.select_count = shape.select_count + 1
@ir.HotOp::BrIf => shape.br_if_count = shape.br_if_count + 1
@ir.HotOp::Block => {
shape.block_count = shape.block_count + 1
let result_arity = @ir.hot_node_result_arity(func, node_id)
if result_arity > 1 {
shape.multivalue_block_count = shape.multivalue_block_count + 1
shape.multivalue_control_count = shape.multivalue_control_count + 1
}
if result_arity > 0 {
let body_ref = @ir.hot_region_body_ref(func, node_id)
if @ir.hot_region_root_count(func, body_ref) >= 2 {
let prefix_id = @ir.hot_region_get(func, body_ref, 0)
if prefix_id >= 0 && @ir.hot_node_is_live(func, prefix_id) {
match @ir.hot_node_get(func, prefix_id).op {
@ir.HotOp::Block => ()
_ =>
shape.result_block_non_block_prefix_count = shape.result_block_non_block_prefix_count +
1
}
}
}
}
}
@ir.HotOp::Loop => {
shape.loop_count = shape.loop_count + 1
if @ir.hot_node_result_arity(func, node_id) > 1 {
shape.multivalue_control_count = shape.multivalue_control_count + 1
}
}
@ir.HotOp::Try | @ir.HotOp::TryTable =>
if @ir.hot_node_result_arity(func, node_id) > 1 {
shape.multivalue_control_count = shape.multivalue_control_count + 1
}
@ir.HotOp::Br => shape.br_count = shape.br_count + 1
@ir.HotOp::BrTable => shape.br_table_count = shape.br_table_count + 1
@ir.HotOp::Drop => shape.drop_count = shape.drop_count + 1
@ir.HotOp::If => {
let node = @ir.hot_node_get(func, node_id)
let result_arity = @ir.hot_type_result_arity(func, node.ty)
if result_arity == 0 {
shape.void_if_count = shape.void_if_count + 1
} else {
shape.value_if_count = shape.value_if_count + 1
if result_arity > 1 {
shape.multivalue_control_count = shape.multivalue_control_count + 1
}
}
}
_ => ()
}
}
}
///|
priv struct RemoveUnusedBrsHotVoidReturnLadderSummary {
local_count : Int32
root_count : Int32
node_count : Int32
shape : RemoveUnusedBrsHotVoidReturnLadderShape
}
///|
fn remove_unused_brs_new_hot_void_return_ladder_shape() -> RemoveUnusedBrsHotVoidReturnLadderShape {
{
call_count: 0,
stack_effect_count: 0,
local_set_count: 0,
local_tee_count: 0,
void_if_count: 0,
value_if_count: 0,
return_count: 0,
select_count: 0,
br_if_count: 0,
block_count: 0,
multivalue_block_count: 0,
multivalue_control_count: 0,
loop_count: 0,
br_count: 0,
br_table_count: 0,
drop_count: 0,
result_block_non_block_prefix_count: 0,
}
}
///|
fn remove_unused_brs_collect_hot_void_return_ladder_summary(
func : @ir.HotFunc,
) -> RemoveUnusedBrsHotVoidReturnLadderSummary {
let shape = remove_unused_brs_new_hot_void_return_ladder_shape()
remove_unused_brs_scan_hot_void_return_ladder_shape(func, shape)
{
local_count: @ir.hot_local_count(func),
root_count: @ir.hot_root_count(func),
node_count: @ir.hot_node_count(func),
shape,
}
}
///|
fn remove_unused_brs_can_skip_large_tagged_result_prefix_ladder(
summary : RemoveUnusedBrsHotVoidReturnLadderSummary,
) -> Bool {
let local_count = summary.local_count
let root_count = summary.root_count
let node_count = summary.node_count
let shape = summary.shape
if local_count < 450 || local_count > 700 {
return false
}
if root_count < 1 || root_count > 2 {
return false
}
if node_count < 800 || node_count > 4500 {
return false
}
let total_if_count = shape.void_if_count + shape.value_if_count
let non_block_prefix_count = shape.result_block_non_block_prefix_count
let matches_reduced_lock = local_count >= 450 &&
local_count <= 700 &&
root_count >= 1 &&
root_count <= 2 &&
node_count >= 800 &&
shape.call_count >= 180 &&
shape.call_count <= 500 &&
shape.local_set_count >= 120 &&
shape.local_set_count <= 360 &&
shape.local_tee_count >= 120 &&
shape.local_tee_count <= 360 &&
total_if_count >= 180 &&
total_if_count <= 400 &&
shape.void_if_count >= 100 &&
shape.void_if_count <= 260 &&
shape.value_if_count >= 60 &&
shape.value_if_count <= 160 &&
shape.select_count <= 32 &&
shape.br_if_count == 0 &&
shape.block_count >= 60 &&
shape.block_count <= 400 &&
shape.loop_count == 0 &&
shape.br_table_count == 0 &&
non_block_prefix_count >= 16
let matches_artifact_dispatch = local_count >= 560 &&
local_count <= 620 &&
root_count == 2 &&
node_count >= 3000 &&
node_count <= 4000 &&
shape.call_count >= 280 &&
shape.call_count <= 360 &&
shape.local_set_count >= 250 &&
shape.local_set_count <= 340 &&
shape.local_tee_count >= 250 &&
shape.local_tee_count <= 340 &&
shape.void_if_count >= 140 &&
shape.void_if_count <= 200 &&
shape.value_if_count >= 60 &&
shape.value_if_count <= 100 &&
shape.return_count >= 60 &&
shape.return_count <= 100 &&
shape.select_count >= 8 &&
shape.select_count <= 24 &&
shape.br_if_count == 0 &&
shape.block_count >= 420 &&
shape.block_count <= 560 &&
shape.loop_count == 0 &&
shape.br_count >= 100 &&
shape.br_count <= 160 &&
shape.br_table_count == 0 &&
shape.drop_count <= 8 &&
non_block_prefix_count >= 16 &&
non_block_prefix_count <= 24
matches_reduced_lock || matches_artifact_dispatch
}
///|
fn remove_unused_brs_can_skip_medium_branchy_block_ladder(
summary : RemoveUnusedBrsHotVoidReturnLadderSummary,
) -> Bool {
let local_count = summary.local_count
let node_count = summary.node_count
let shape = summary.shape
if local_count < 200 || local_count > 500 {
return false
}
if node_count < 1300 || node_count > 2300 {
return false
}
shape.call_count >= 130 &&
shape.call_count <= 380 &&
shape.local_set_count >= 90 &&
shape.local_set_count <= 220 &&
shape.local_tee_count >= 120 &&
shape.local_tee_count <= 280 &&
shape.void_if_count >= 50 &&
shape.void_if_count <= 160 &&
shape.value_if_count <= 12 &&
shape.return_count <= 24 &&
shape.select_count <= 4 &&
shape.br_if_count == 0 &&
shape.block_count >= 100 &&
shape.block_count <= 240 &&
shape.loop_count <= 16 &&
shape.br_count >= 30 &&
shape.br_count <= 60 &&
shape.br_table_count == 0 &&
shape.drop_count <= 80 &&
shape.result_block_non_block_prefix_count <= 4
}
///|
fn remove_unused_brs_can_skip_call_heavy_mixed_if_mesh(
summary : RemoveUnusedBrsHotVoidReturnLadderSummary,
) -> Bool {
let local_count = summary.local_count
let root_count = summary.root_count
let node_count = summary.node_count
let shape = summary.shape
let total_if_count = shape.void_if_count + shape.value_if_count
local_count >= 190 &&
local_count <= 650 &&
root_count >= 2 &&
root_count <= 60 &&
node_count >= 1100 &&
node_count <= 3200 &&
shape.call_count >= 100 &&
shape.call_count <= 650 &&
shape.local_set_count >= 90 &&
shape.local_set_count <= 350 &&
shape.local_tee_count >= 75 &&
shape.local_tee_count <= 300 &&
total_if_count >= 50 &&
total_if_count <= 140 &&
shape.void_if_count <= 90 &&
shape.return_count <= 20 &&
shape.select_count <= 1 &&
shape.br_if_count <= 1 &&
shape.block_count >= 90 &&
shape.block_count <= 230 &&
shape.loop_count <= 5 &&
shape.br_count >= 13 &&
shape.br_count <= 50 &&
shape.br_table_count == 0 &&
shape.drop_count <= 90
}
///|
fn remove_unused_brs_can_skip_localset_heavy_value_if_mesh(
summary : RemoveUnusedBrsHotVoidReturnLadderSummary,
) -> Bool {
let local_count = summary.local_count
let root_count = summary.root_count
let node_count = summary.node_count
let shape = summary.shape
local_count >= 220 &&
local_count <= 700 &&
root_count >= 5 &&
root_count <= 50 &&
node_count >= 1050 &&
node_count <= 3200 &&
shape.call_count >= 45 &&
shape.call_count <= 110 &&
shape.local_set_count >= 130 &&
shape.local_set_count <= 650 &&
shape.local_tee_count >= 79 &&
shape.local_tee_count <= 320 &&
shape.void_if_count <= 24 &&
shape.value_if_count >= 14 &&
shape.value_if_count <= 100 &&
shape.return_count <= 21 &&
shape.select_count <= 13 &&
shape.br_if_count <= 5 &&
shape.block_count >= 70 &&
shape.block_count <= 231 &&
shape.loop_count <= 2 &&
shape.br_count >= 12 &&
shape.br_count <= 83 &&
shape.br_table_count == 0 &&
shape.drop_count <= 25
}
///|
fn remove_unused_brs_can_skip_large_void_return_ladder(
summary : RemoveUnusedBrsHotVoidReturnLadderSummary,
) -> Bool {
let local_count = summary.local_count
let root_count = summary.root_count
let node_count = summary.node_count
let shape = summary.shape
let matches_wide_void_ladder = local_count >= 700 &&
root_count >= 64 &&
shape.call_count >= 900 &&
shape.local_set_count >= 350 &&
shape.local_tee_count >= 350 &&
shape.void_if_count >= 150 &&
shape.value_if_count == 0 &&
shape.return_count >= 90 &&
shape.return_count * 5 >= shape.void_if_count &&
shape.select_count <= 4 &&
shape.br_if_count <= 2
let matches_compact_decision_ladder = local_count >= 700 &&
root_count <= 8 &&
node_count >= 8000 &&
shape.call_count >= 1200 &&
shape.local_set_count >= 500 &&
shape.local_tee_count >= 350 &&
shape.void_if_count >= 150 &&
(shape.value_if_count <= 2 || shape.value_if_count >= 200) &&
shape.return_count >= 120 &&
shape.block_count >= 900 &&
shape.loop_count <= 4 &&
shape.br_count >= 150 &&
shape.select_count <= 2 &&
shape.br_if_count <= 2
let matches_single_root_value_if_ladder = local_count >= 700 &&
root_count == 1 &&
node_count >= 1500 &&
shape.call_count >= 300 &&
shape.local_set_count >= 150 &&
shape.local_tee_count >= 150 &&
shape.void_if_count == 0 &&
shape.value_if_count >= 150 &&
shape.return_count == 0 &&
shape.select_count == 0 &&
shape.br_if_count == 0 &&
shape.block_count >= 600 &&
shape.loop_count == 0 &&
shape.br_count == 0
let matches_nested_constructor_return_ladder = local_count >= 20 &&
local_count <= 40 &&
shape.call_count >= 6 &&
shape.call_count <= 16 &&
shape.local_set_count >= 4 &&
shape.local_set_count <= 20 &&
shape.local_tee_count >= 4 &&
shape.local_tee_count <= 16 &&
shape.void_if_count >= 3 &&
shape.void_if_count <= 8 &&
shape.value_if_count == 0 &&
shape.return_count == 0 &&
shape.select_count == 0 &&
shape.br_if_count == 0 &&
shape.block_count >= 6 &&
shape.block_count <= 12 &&
shape.loop_count == 0 &&
shape.br_count == 2
let matches_mixed_branch_value_ladder = local_count >= 450 &&
local_count <= 900 &&
node_count >= 4000 &&
shape.call_count >= 350 &&
shape.local_set_count >= 250 &&
shape.local_tee_count >= 250 &&
shape.void_if_count >= 120 &&
shape.value_if_count >= 20 &&
shape.value_if_count <= 120 &&
shape.return_count >= 15 &&
shape.return_count <= 100 &&
shape.select_count <= 12 &&
shape.br_if_count <= 12 &&
shape.block_count >= 400 &&
shape.loop_count <= 4 &&
shape.br_count >= 70 &&
shape.br_count <= 150
matches_wide_void_ladder ||
matches_compact_decision_ladder ||
matches_single_root_value_if_ladder ||
matches_nested_constructor_return_ladder ||
matches_mixed_branch_value_ladder
}
///|
fn remove_unused_brs_can_skip_large_br_table_return_ladder(
summary : RemoveUnusedBrsHotVoidReturnLadderSummary,
) -> Bool {
let local_count = summary.local_count
let node_count = summary.node_count
let shape = summary.shape
let total_if_count = shape.void_if_count + shape.value_if_count
local_count >= 80 &&
node_count >= 500 &&
shape.call_count >= 100 &&
shape.local_set_count >= 50 &&
shape.local_tee_count >= 20 &&
total_if_count >= 60 &&
total_if_count <= 400 &&
shape.return_count >= 20 &&
shape.return_count <= 80 &&
shape.select_count <= 8 &&
shape.br_if_count == 0 &&
shape.block_count >= 20 &&
shape.block_count <= 800 &&
shape.loop_count <= 32 &&
shape.br_count >= 5 &&
shape.br_count <= 300 &&
shape.br_table_count >= 1 &&
shape.br_table_count <= 4 &&
shape.drop_count <= 200
}
///|
fn remove_unused_brs_node_has_stack_effect_hazard(
func : @ir.HotFunc,
node_id : Int32,
cache : Array[Int32],
) -> Bool {
if node_id < 0 || !@ir.hot_node_is_live(func, node_id) {
return false
}
if cache[node_id] >= 0 {
return cache[node_id] == 1
}
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 => {
cache[node_id] = 1
return true
}
_ => ()
}
for slot = 0; slot < @ir.hot_child_count(func, node_id); slot = slot + 1 {
if remove_unused_brs_is_region_child_slot(node.op, slot) {
continue
}
if remove_unused_brs_node_has_stack_effect_hazard(
func,
@ir.hot_child_get(func, node_id, slot),
cache,
) {
cache[node_id] = 1
return true
}
}
cache[node_id] = 0
false
}
///|
fn remove_unused_brs_node_has_giant_br_table(
func : @ir.HotFunc,
node_id : Int32,
seen : Array[Bool],
) -> Bool {
if node_id < 0 || !@ir.hot_node_is_live(func, node_id) {
return false
}
while seen.length() <= node_id {
seen.push(false)
}
if seen[node_id] {
return false
}
seen[node_id] = true
let node = @ir.hot_node_get(func, node_id)
match node.op {
@ir.HotOp::BrTable =>
if @ir.hot_branch_table_targets_for_node(func, node_id).length() >= 128 {
return true
}
_ => ()
}
for info in @ir.hot_control_region_slot_info(node.op) {
let child_ref = match info.role {
@ir.HotControlRegionRole::BodyRegion =>
Some(@ir.hot_region_body_ref(func, node_id))
@ir.HotControlRegionRole::ThenRegion =>
Some(@ir.hot_region_then_ref(func, node_id))
@ir.HotControlRegionRole::ElseRegion =>
@ir.hot_region_else_ref(func, node_id)
@ir.HotControlRegionRole::CatchRegion =>
@ir.hot_region_catch_ref(func, node_id)
@ir.HotControlRegionRole::CatchListRegion =>
Some(@ir.hot_region_catch_list_ref(func, node_id))
}
match child_ref {
Some(child_ref) =>
for idx = 0
idx < @ir.hot_region_root_count(func, child_ref)
idx = idx + 1 {
if remove_unused_brs_node_has_giant_br_table(
func,
@ir.hot_region_get(func, child_ref, idx),
seen,
) {
return true
}
}
None => ()
}
}
for slot = 0; slot < @ir.hot_child_count(func, node_id); slot = slot + 1 {
if remove_unused_brs_is_region_child_slot(node.op, slot) {
continue
}
if remove_unused_brs_node_has_giant_br_table(
func,
@ir.hot_child_get(func, node_id, slot),
seen,
) {
return true
}
}
false
}
///|
fn remove_unused_brs_has_giant_br_table(func : @ir.HotFunc) -> Bool {
let seen : Array[Bool] = []
for idx = 0
idx < @ir.hot_region_root_count(func, @ir.HotRegionRef::root())
idx = idx + 1 {
if remove_unused_brs_node_has_giant_br_table(
func,
@ir.hot_region_get(func, @ir.HotRegionRef::root(), idx),
seen,
) {
return true
}
}
false
}
///|
fn remove_unused_brs_result_types_have_nullable_return_hazard(
control_results : Array[@lib.ValType],
body_results : Array[@lib.ValType],
) -> Bool {
match (control_results, body_results) {
([@lib.RefTypeValType(control_ref)], [@lib.RefTypeValType(body_ref)]) =>
control_ref.is_non_nullable() && body_ref.is_nullable()
_ => false
}
}
///|
fn remove_unused_brs_node_has_result_type_return_hazard(
func : @ir.HotFunc,
node_id : Int32,
body_result_type : Int32,
inside_mismatched_result : Bool,
seen : Array[Int32],
) -> Bool {
if node_id < 0 || !@ir.hot_node_is_live(func, node_id) {
return false
}
while seen.length() <= node_id {
seen.push(0)
}
let seen_state = seen[node_id]
if inside_mismatched_result {
if seen_state == 2 || seen_state == 3 {
return false
}
seen[node_id] = if seen_state == 1 { 3 } else { 2 }
} else {
if seen_state == 1 || seen_state == 3 {
return false
}
seen[node_id] = if seen_state == 2 { 3 } else { 1 }
}
let node = @ir.hot_node_get(func, node_id)
let enters_mismatched_result = match node.op {
@ir.HotOp::Block
| @ir.HotOp::Loop
| @ir.HotOp::If
| @ir.HotOp::Try
| @ir.HotOp::TryTable =>
remove_unused_brs_result_types_have_nullable_return_hazard(
@ir.hot_type_results(func, @ir.hot_node_type(func, node_id)),
@ir.hot_type_results(func, body_result_type),
)
_ => false
}
let inside_mismatched_result = inside_mismatched_result ||
enters_mismatched_result
if inside_mismatched_result {
match node.op {
@ir.HotOp::Return => return true
_ => ()
}
}
for info in @ir.hot_control_region_slot_info(node.op) {
let child_ref = match info.role {
@ir.HotControlRegionRole::BodyRegion =>
Some(@ir.hot_region_body_ref(func, node_id))
@ir.HotControlRegionRole::ThenRegion =>
Some(@ir.hot_region_then_ref(func, node_id))
@ir.HotControlRegionRole::ElseRegion =>
@ir.hot_region_else_ref(func, node_id)
@ir.HotControlRegionRole::CatchRegion =>
@ir.hot_region_catch_ref(func, node_id)
@ir.HotControlRegionRole::CatchListRegion =>
Some(@ir.hot_region_catch_list_ref(func, node_id))
}
match child_ref {
Some(child_ref) =>
for idx = 0
idx < @ir.hot_region_root_count(func, child_ref)
idx = idx + 1 {
if remove_unused_brs_node_has_result_type_return_hazard(
func,
@ir.hot_region_get(func, child_ref, idx),
body_result_type,
inside_mismatched_result,
seen,
) {
return true
}
}
None => ()
}
}
for slot = 0; slot < @ir.hot_child_count(func, node_id); slot = slot + 1 {
if remove_unused_brs_is_region_child_slot(node.op, slot) {
continue
}
if remove_unused_brs_node_has_result_type_return_hazard(
func,
@ir.hot_child_get(func, node_id, slot),
body_result_type,
inside_mismatched_result,
seen,
) {
return true
}
}
false
}
///|
fn remove_unused_brs_has_result_type_return_hazard(func : @ir.HotFunc) -> Bool {
let body_result_type = @ir.hot_body_result_type(func)
match @ir.hot_type_results(func, body_result_type) {
[@lib.RefTypeValType(_)] => ()
_ => return false
}
let seen : Array[Int32] = []
for idx = 0
idx < @ir.hot_region_root_count(func, @ir.HotRegionRef::root())
idx = idx + 1 {
if remove_unused_brs_node_has_result_type_return_hazard(
func,
@ir.hot_region_get(func, @ir.HotRegionRef::root(), idx),
body_result_type,
false,
seen,
) {
return true
}
}
false
}
///|
fn remove_unused_brs_node_has_result_local_set_stack_hazard(
func : @ir.HotFunc,
node_id : Int32,
seen : @bitset.BitSet,
stack_effect_cache : Array[Int32],
) -> Bool {
if node_id < 0 || !@ir.hot_node_is_live(func, node_id) || seen[node_id] {
return false
}
seen[node_id] = true
let node = @ir.hot_node_get(func, node_id)
for info in @ir.hot_control_region_slot_info(node.op) {
let child_ref = match info.role {
@ir.HotControlRegionRole::BodyRegion =>
Some(@ir.hot_region_body_ref(func, node_id))
@ir.HotControlRegionRole::ThenRegion =>
Some(@ir.hot_region_then_ref(func, node_id))
@ir.HotControlRegionRole::ElseRegion =>
@ir.hot_region_else_ref(func, node_id)
@ir.HotControlRegionRole::CatchRegion =>
@ir.hot_region_catch_ref(func, node_id)
@ir.HotControlRegionRole::CatchListRegion =>
Some(@ir.hot_region_catch_list_ref(func, node_id))
}
match child_ref {
Some(child_ref) =>
if remove_unused_brs_region_has_result_local_set_stack_hazard_with_caches(
func, child_ref, seen, stack_effect_cache,
) {
return true
}
None => ()
}
}
for slot = 0; slot < @ir.hot_child_count(func, node_id); slot = slot + 1 {
if remove_unused_brs_is_region_child_slot(node.op, slot) {
continue
}
if remove_unused_brs_node_has_result_local_set_stack_hazard(
func,
@ir.hot_child_get(func, node_id, slot),
seen,
stack_effect_cache,
) {
return true
}
}
false
}
///|
fn remove_unused_brs_region_has_result_local_set_stack_hazard_with_caches(
func : @ir.HotFunc,
region_ref : @ir.HotRegionRef,
seen : @bitset.BitSet,
stack_effect_cache : Array[Int32],
) -> Bool {
let result_arity = if region_ref == @ir.HotRegionRef::root() {
@ir.hot_type_result_arity(func, @ir.hot_body_result_type(func))
} else if region_ref.holder >= 0 &&
@ir.hot_node_is_live(func, region_ref.holder) {
@ir.hot_node_result_arity(func, region_ref.holder)
} else {
@ir.hot_region_result_arity(func, region_ref)
}
if (region_ref == @ir.HotRegionRef::root() || result_arity > 0) &&
@ir.hot_region_root_count(func, region_ref) > 1 {
let mut has_local_set = false
let mut has_stack_effect = false
for idx = 0
idx < @ir.hot_region_root_count(func, region_ref)
idx = idx + 1 {
let root_id = @ir.hot_region_get(func, region_ref, idx)
if root_id >= 0 && @ir.hot_node_is_live(func, root_id) {
match @ir.hot_node_get(func, root_id).op {
@ir.HotOp::LocalSet => has_local_set = true
_ => ()
}
if remove_unused_brs_node_has_stack_effect_hazard(
func, root_id, stack_effect_cache,
) {
has_stack_effect = true
}
}
}
let nested_multivalue_carrier = region_ref != @ir.HotRegionRef::root() &&
result_arity > 1
if has_local_set && (nested_multivalue_carrier || has_stack_effect) {
return true
}
}
for idx = 0; idx < @ir.hot_region_root_count(func, region_ref); idx = idx + 1 {
if remove_unused_brs_node_has_result_local_set_stack_hazard(
func,
@ir.hot_region_get(func, region_ref, idx),
seen,
stack_effect_cache,
) {
return true
}
}
false
}
///|
fn remove_unused_brs_region_has_result_local_set_stack_hazard(
func : @ir.HotFunc,
region_ref : @ir.HotRegionRef,
) -> Bool {
let node_count = @ir.hot_node_count(func)
remove_unused_brs_region_has_result_local_set_stack_hazard_with_caches(
func,
region_ref,
@bitset.BitSet::new(node_count),
Array::make(node_count, -1),
)
}
///|
fn remove_unused_brs_tail_target(
func : @ir.HotFunc,
region_ref : @ir.HotRegionRef,
) -> RemoveUnusedBrsTailTarget {
if region_ref == @ir.HotRegionRef::root() {
return ReturnTarget
}
match @ir.hot_region_holder_block_label(func, region_ref) {
Some(target) => BranchTarget(target)
None => NoTarget
}
}
///|
fn remove_unused_brs_region_enclosing_branch_target(
func : @ir.HotFunc,
region_ref : @ir.HotRegionRef,
) -> Int32? {
if region_ref == @ir.HotRegionRef::root() {
return None
}
let holder = region_ref.holder
if holder < 0 ||
!@ir.hot_node_is_live(func, holder) ||
!@ir.hot_is_control_node(func, holder) {
return None
}
Some(@ir.hot_control_node_label(func, holder))
}
///|
fn remove_unused_brs_region_tail_id(
func : @ir.HotFunc,
region_ref : @ir.HotRegionRef,
) -> Int32 {
if @ir.hot_region_root_count(func, region_ref) == 0 {
return -1
}
@ir.hot_region_get(
func,
region_ref,
@ir.hot_region_root_count(func, region_ref) - 1,
)
}
///|
fn remove_unused_brs_region_has_removable_tail_target(
func : @ir.HotFunc,
region_ref : @ir.HotRegionRef,
) -> Bool {
match remove_unused_brs_tail_target(func, region_ref) {
NoTarget => false
_ => true
}
}
///|
fn remove_unused_brs_compute_cycle_scan(
func : @ir.HotFunc,
) -> RemoveUnusedBrsCycleScan {
let label_refs = Array::make(func.labels.length(), 0)
let payload_children = @bitset.BitSet::new(@ir.hot_node_count(func))
let mut has_br_table = 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_branch_target(func, node_id) {
Some(label_id) if label_id >= 0 && label_id < label_refs.length() =>
label_refs[label_id] = label_refs[label_id] + 1
_ => ()
}
let node = @ir.hot_node_get(func, node_id)
match node.op {
@ir.HotOp::BrTable => {
has_br_table = true
for label_id in @ir.hot_branch_table_targets(func, node.imm0) {
if label_id >= 0 && label_id < label_refs.length() {
label_refs[label_id] = label_refs[label_id] + 1
}
}
}
@ir.HotOp::Continuation =>
for label_id in @ir.hot_continuation_targets(func, node_id) {
if label_id >= 0 && label_id < label_refs.length() {
label_refs[label_id] = label_refs[label_id] + 1
}
}
@ir.HotOp::TryTable => {
let info = @ir.hot_catch_info_get(func, node.imm1)
for catch_ in info.catches {
let label_id = match catch_ {
@ir.HotCatchArm::CatchArm(_, label_id)
| @ir.HotCatchArm::CatchRefArm(_, label_id)
| @ir.HotCatchArm::CatchAllArm(label_id)
| @ir.HotCatchArm::CatchAllRefArm(label_id) => label_id
}
if label_id >= 0 && label_id < label_refs.length() {
label_refs[label_id] = label_refs[label_id] + 1
}
}
}
_ => ()
}
let payload_slots = match node.op {
@ir.HotOp::Br | @ir.HotOp::Return => node.child_count
@ir.HotOp::BrIf | @ir.HotOp::BrTable =>
if node.child_count > 0 {
node.child_count - 1
} else {
0
}
_ => 0
}
for slot = 0; slot < payload_slots; slot = slot + 1 {
let child_id = @ir.hot_child_get(func, node_id, slot)
if child_id >= 0 && @ir.hot_node_is_live(func, child_id) {
payload_children[child_id] = true
}
}
}
{ label_refs, branch_payload_children: payload_children, has_br_table, }
}
///|
fn remove_unused_brs_cleanup_detached_nodes(
ctx : HotPassContext,
func : @ir.HotFunc,
detached : Array[Int32],
) -> Unit {
// Large detached subgraphs do not affect lowered output once roots/child
// edges are gone; eagerly collecting them here is disproportionately
// expensive on real RUB artifact functions.
if detached.length() > 32 {
return
}
let node_count = @ir.hot_node_count(func)
let candidates : Array[Int32] = []
let in_candidate = @bitset.BitSet::new(node_count)
for node_id in detached {
if node_id < 0 ||
node_id >= node_count ||
!@ir.hot_node_is_live(func, node_id) ||
@ir.hot_is_control_node(func, node_id) ||
in_candidate[node_id] {
continue
}
in_candidate[node_id] = true
candidates.push(node_id)
}
if candidates.is_empty() {
return
}
let references = Array::make(node_count, 0)
for root in func.roots {
if root >= 0 && root < node_count && in_candidate[root] {
references[root] += 1
}
}
for owner = 0; owner < node_count; owner = owner + 1 {
if !@ir.hot_node_is_live(func, owner) {
continue
}
let node = @ir.hot_node_get(func, owner)
for slot = 0; slot < node.child_count; slot = slot + 1 {
let child = @ir.hot_child_get(func, owner, slot)
if child >= 0 &&
child < node_count &&
child != owner &&
in_candidate[child] {
references[child] += 1
}
}
}
let mut changed = false
let mut progress = true
while progress {
progress = false
for node_id in candidates {
if node_id < 0 ||
node_id >= node_count ||
!in_candidate[node_id] ||
!@ir.hot_node_is_live(func, node_id) {
continue
}
if references[node_id] != 0 {
continue
}
for slot = 0; slot < @ir.hot_child_count(func, node_id); slot = slot + 1 {
let child = @ir.hot_child_get(func, node_id, slot)
if child >= 0 &&
child < node_count &&
child != node_id &&
in_candidate[child] {
references[child] -= 1
}
}
@ir.hot_delete_node(func, node_id)
changed = true
progress = true
in_candidate[node_id] = false
}
}
if changed {
remove_unused_brs_mark_mutated(ctx, func, "cleanup-detached-nodes")
}
}
///|
fn remove_unused_brs_delete_unused_nodes(
ctx : HotPassContext,
func : @ir.HotFunc,
node_ids : Array[Int32],
) -> Unit {
remove_unused_brs_cleanup_detached_nodes(ctx, func, node_ids)
}
///|
fn remove_unused_brs_tag_idx_raw(tag_idx : @lib.TagIdx) -> Int32 {
let @lib.TagIdx(raw) = tag_idx
raw.reinterpret_as_int()
}
///|
fn remove_unused_brs_single_ref_type(
func : @ir.HotFunc,
node_id : Int32,
) -> @lib.RefType? {
if node_id < 0 || !@ir.hot_node_is_live(func, node_id) {
return None
}
match @ir.hot_type_results(func, @ir.hot_node_type(func, node_id)) {
[@lib.RefTypeValType(rt)] => Some(rt)
_ => None
}
}
///|
fn remove_unused_brs_node_is_ref_null(
func : @ir.HotFunc,
node_id : Int32,
) -> Bool {
if node_id < 0 || !@ir.hot_node_is_live(func, node_id) {
return false
}
match @ir.hot_node_get(func, node_id).op {
@ir.HotOp::RefNull => true
_ => false
}
}
///|
fn remove_unused_brs_ref_matches(
actual : @lib.RefType,
expected : @lib.RefType,
) -> Bool {
@validate.Match::matches(
@lib.ValType::ref_type(actual),
@lib.ValType::ref_type(expected),
@validate.Env::new(),
)
}
///|
fn remove_unused_brs_abs_heap_disjoint(
actual : @lib.AbsHeapType,
target : @lib.AbsHeapType,
) -> Bool {
if actual == target {
return false
}
match (actual, target) {
(@lib.I31AbsHeapType, @lib.StructAbsHeapType)
| (@lib.StructAbsHeapType, @lib.I31AbsHeapType)
| (@lib.I31AbsHeapType, @lib.ArrayAbsHeapType)
| (@lib.ArrayAbsHeapType, @lib.I31AbsHeapType)
| (@lib.StructAbsHeapType, @lib.ArrayAbsHeapType)
| (@lib.ArrayAbsHeapType, @lib.StructAbsHeapType)
| (@lib.FuncAbsHeapType, @lib.ExternAbsHeapType)
| (@lib.ExternAbsHeapType, @lib.FuncAbsHeapType)
| (@lib.FuncAbsHeapType, @lib.ExnAbsHeapType)
| (@lib.ExnAbsHeapType, @lib.FuncAbsHeapType)
| (@lib.ExternAbsHeapType, @lib.ExnAbsHeapType)
| (@lib.ExnAbsHeapType, @lib.ExternAbsHeapType) => true
_ => false
}
}
///|
fn remove_unused_brs_ref_cast_definitely_fails(
actual : @lib.RefType,
target : @lib.RefType,
) -> Bool {
if actual.is_nullable() {
return false
}
match (actual.get_heap_type(), target.get_heap_type()) {
(@lib.AbsHeapTypeHeapType(actual_abs), @lib.AbsHeapTypeHeapType(target_abs)) =>
remove_unused_brs_abs_heap_disjoint(actual_abs, target_abs)
_ => false
}
}
///|
fn remove_unused_brs_try_rewrite_gc_br_on_root(
ctx : HotPassContext,
func : @ir.HotFunc,
region_ref : @ir.HotRegionRef,
idx : Int32,
) -> Bool {
let root_id = @ir.hot_region_get(func, region_ref, idx)
if root_id < 0 || !@ir.hot_node_is_live(func, root_id) {
return false
}
let mut br_id = root_id
let mut dropped_context = false
match @ir.hot_node_get(func, root_id).op {
@ir.HotOp::Drop => {
if @ir.hot_child_count(func, root_id) != 1 {
return false
}
br_id = @ir.hot_child_get(func, root_id, 0)
dropped_context = true
}
_ => ()
}
if br_id < 0 || !@ir.hot_node_is_live(func, br_id) {
return false
}
let br_node = @ir.hot_node_get(func, br_id)
match br_node.op {
@ir.HotOp::BrOnNull
| @ir.HotOp::BrOnNonNull
| @ir.HotOp::BrOnCast
| @ir.HotOp::BrOnCastFail => ()
_ => return false
}
let child_count = @ir.hot_child_count(func, br_id)
if child_count < 1 {
return false
}
let ref_id = @ir.hot_child_get(func, br_id, child_count - 1)
let payloads : Array[Int32] = []
for child_idx = 0; child_idx < child_count - 1; child_idx = child_idx + 1 {
payloads.push(@ir.hot_child_get(func, br_id, child_idx))
}
let has_payloads = payloads.length() > 0
let target = br_node.imm0
let replacement : Array[Int32] = []
let mut changed = false
match br_node.op {
@ir.HotOp::BrOnCast | @ir.HotOp::BrOnCastFail =>
match @ir.hot_node_get(func, ref_id).op {
@ir.HotOp::Unreachable => {
for payload in payloads {
replacement.push(@ir.hot_build_drop(func, payload))
}
replacement.push(ref_id)
let removed = pass_splice_region(
ctx, func, region_ref, idx, 1, replacement,
)
removed.push(br_id)
remove_unused_brs_delete_unused_nodes(ctx, func, removed)
remove_unused_brs_mark_mutated(
ctx, func, "gc-br-on-unreachable-input",
)
return true
}
_ => ()
}
_ => ()
}
let ref_type = match remove_unused_brs_single_ref_type(func, ref_id) {
Some(rt) => rt
None => return false
}
match br_node.op {
@ir.HotOp::BrOnNull =>
if remove_unused_brs_node_is_ref_null(func, ref_id) {
if has_payloads {
replacement.push(@ir.hot_build_br(func, target, values=payloads))
} else {
replacement.push(@ir.hot_build_drop(func, ref_id))
replacement.push(@ir.hot_build_br(func, target))
}
changed = true
} else if !has_payloads && ref_type.is_non_nullable() && dropped_context {
replacement.push(@ir.hot_build_drop(func, ref_id))
changed = true
} else if !has_payloads && ref_type.is_non_nullable() && !dropped_context {
replacement.push(ref_id)
changed = true
}
@ir.HotOp::BrOnNonNull =>
if ref_type.is_non_nullable() {
let branch_values = payloads.copy()
branch_values.push(ref_id)
replacement.push(@ir.hot_build_br(func, target, values=branch_values))
changed = true
} else if !has_payloads &&
remove_unused_brs_node_is_ref_null(func, ref_id) {
replacement.push(@ir.hot_build_drop(func, ref_id))
changed = true
}
@ir.HotOp::BrOnCast | @ir.HotOp::BrOnCastFail => {
let target_ref = match @ir.hot_node_exact_instr(func, br_id) {
Some(
@lib.BrOnCast(
_,
@lib.CastOp(_, target_nullable, _, target_exact),
_,
target_heap
)
)
| Some(
@lib.BrOnCastFail(
_,
@lib.CastOp(_, target_nullable, _, target_exact),
_,
target_heap
)
) => @lib.RefType::new(target_nullable, target_heap, exact=target_exact)
_ => return false
}
if remove_unused_brs_ref_matches(ref_type, target_ref) {
match br_node.op {
@ir.HotOp::BrOnCast => {
let branch_values = payloads.copy()
branch_values.push(ref_id)
replacement.push(
@ir.hot_build_br(func, target, values=branch_values),
)
changed = true
}
@ir.HotOp::BrOnCastFail if !has_payloads && dropped_context => {
replacement.push(@ir.hot_build_drop(func, ref_id))
changed = true
}
@ir.HotOp::BrOnCastFail if !has_payloads => {
replacement.push(ref_id)
changed = true
}
_ => ()
}
} else {
match br_node.op {
@ir.HotOp::BrOnCast =>
if !has_payloads &&
remove_unused_brs_node_is_ref_null(func, ref_id) &&
target_ref.is_non_nullable() {
if dropped_context {
replacement.push(@ir.hot_build_drop(func, ref_id))
} else {
replacement.push(ref_id)
}
changed = true
} else if !has_payloads &&
remove_unused_brs_ref_cast_definitely_fails(ref_type, target_ref) {
if dropped_context {
replacement.push(@ir.hot_build_drop(func, ref_id))
} else {
replacement.push(ref_id)
}
changed = true
} else if !has_payloads &&
ref_type.is_nullable() &&
target_ref.is_non_nullable() {
let non_null_ref_type = @lib.RefType::new(
false,
ref_type.get_heap_type(),
exact=ref_type.is_exact(),
)
if remove_unused_brs_ref_matches(non_null_ref_type, target_ref) {
let nullable_target = @lib.RefType::new(
true,
target_ref.get_heap_type(),
exact=target_ref.is_exact(),
)
let br_on_non_null = @ir.hot_build_node(
func,
@ir.HotOp::br_on_non_null(),
@ir.hot_type_void(func),
imm0=target,
children=[ref_id],
)
let split_block = @ir.hot_build_block(
func,
@lib.BlockType::val_type(
@lib.ValType::ref_type(nullable_target),
),
[
br_on_non_null,
@ir.hot_build_ref_null(func, nullable_target),
],
)
if dropped_context {
replacement.push(@ir.hot_build_drop(func, split_block))
} else {
replacement.push(split_block)
}
changed = true
}
}
@ir.HotOp::BrOnCastFail =>
if remove_unused_brs_ref_cast_definitely_fails(ref_type, target_ref) {
let branch_values = payloads.copy()
branch_values.push(ref_id)
replacement.push(
@ir.hot_build_br(func, target, values=branch_values),
)
changed = true
}
_ => ()
}
}
}
_ => ()
}
if !changed {
return false
}
let removed = pass_splice_region(ctx, func, region_ref, idx, 1, replacement)
removed.push(br_id)
remove_unused_brs_delete_unused_nodes(ctx, func, removed)
remove_unused_brs_mark_mutated(ctx, func, "gc-br-on-cleanup")
true
}
///|
fn remove_unused_brs_match_caught_throw(
func : @ir.HotFunc,
throw_tag : Int32,
catchers : Array[Int32],
imported_tags : Int32,
) -> RemoveUnusedBrsCaughtThrowMatch {
for i = catchers.length() - 1; i >= 0; i = i - 1 {
let catcher_id = catchers[i]
if catcher_id < 0 || !@ir.hot_node_is_live(func, catcher_id) {
continue
}
let catcher = @ir.hot_node_get(func, catcher_id)
match catcher.op {
@ir.HotOp::Try => return CaughtThrowRef
@ir.HotOp::TryTable => {
let info = @ir.hot_catch_info_get(func, catcher.imm1)
for catch_ in info.catches {
match catch_ {
@ir.HotCatchArm::CatchArm(tag_idx, label_id) =>
if remove_unused_brs_tag_idx_raw(tag_idx) == throw_tag {
return CaughtThrowExact(label_id)
} else if imported_tags < 0 ||
(
throw_tag < imported_tags &&
remove_unused_brs_tag_idx_raw(tag_idx) < imported_tags
) {
return CaughtThrowRef
}
@ir.HotCatchArm::CatchRefArm(tag_idx, _) =>
if remove_unused_brs_tag_idx_raw(tag_idx) == throw_tag {
return CaughtThrowRef
} else if imported_tags < 0 ||
(
throw_tag < imported_tags &&
remove_unused_brs_tag_idx_raw(tag_idx) < imported_tags
) {
return CaughtThrowRef
}
@ir.HotCatchArm::CatchAllArm(label_id) =>
return CaughtThrowAll(label_id)
@ir.HotCatchArm::CatchAllRefArm(_) => return CaughtThrowRef
}
}
}
_ => return CaughtThrowRef
}
}
NoCaughtThrowMatch
}
///|
fn remove_unused_brs_try_rewrite_caught_throw_root(
ctx : HotPassContext,
func : @ir.HotFunc,
region_ref : @ir.HotRegionRef,
idx : Int32,
throw_id : Int32,
catchers : Array[Int32],
) -> Bool {
if !@ir.hot_node_is_live(func, throw_id) {
return false
}
let node = @ir.hot_node_get(func, throw_id)
match node.op {
@ir.HotOp::Throw => ()
_ => return false
}
let imported_tags = match ctx.module_ctx {
Some(module_ctx) => module_ctx.imported_tag_count
None => -1
}
match
remove_unused_brs_match_caught_throw(
func,
node.imm0,
catchers,
imported_tags,
) {
NoCaughtThrowMatch | CaughtThrowRef => false
CaughtThrowExact(label_id) => {
let payloads : Array[Int32] = []
for child_idx = 0; child_idx < node.child_count; child_idx = child_idx + 1 {
payloads.push(@ir.hot_child_get(func, throw_id, child_idx))
}
let br = @ir.hot_build_br(func, label_id, values=payloads)
let removed = pass_splice_region(ctx, func, region_ref, idx, 1, [br])
remove_unused_brs_delete_unused_nodes(ctx, func, removed)
true
}
CaughtThrowAll(label_id) => {
let replacement : Array[Int32] = []
for child_idx = 0; child_idx < node.child_count; child_idx = child_idx + 1 {
let child = @ir.hot_child_get(func, throw_id, child_idx)
if child >= 0 && @ir.hot_node_is_live(func, child) {
replacement.push(@ir.hot_build_drop(func, child))
}
}
replacement.push(@ir.hot_build_br(func, label_id))
let removed = pass_splice_region(
ctx, func, region_ref, idx, 1, replacement,
)
remove_unused_brs_delete_unused_nodes(ctx, func, removed)
true
}
}
}
///|
fn remove_unused_brs_try_rewrite_caught_throws_in_node(
ctx : HotPassContext,
func : @ir.HotFunc,
node_id : Int32,
catchers : Array[Int32],
) -> Bool {
if node_id < 0 || !@ir.hot_node_is_live(func, node_id) {
return false
}
match @ir.hot_node_get(func, node_id).op {
@ir.HotOp::Block | @ir.HotOp::Loop =>
remove_unused_brs_try_rewrite_caught_throws_in_region(
ctx,
func,
@ir.hot_region_body_ref(func, node_id),
catchers,
)
@ir.HotOp::If => {
if remove_unused_brs_try_rewrite_caught_throws_in_region(
ctx,
func,
@ir.hot_region_then_ref(func, node_id),
catchers,
) {
return true
}
match @ir.hot_region_else_ref(func, node_id) {
Some(else_ref) =>
remove_unused_brs_try_rewrite_caught_throws_in_region(
ctx, func, else_ref, catchers,
)
None => false
}
}
@ir.HotOp::Try => {
let nested = catchers.copy()
nested.push(node_id)
if remove_unused_brs_try_rewrite_caught_throws_in_region(
ctx,
func,
@ir.hot_region_body_ref(func, node_id),
nested,
) {
return true
}
match @ir.hot_region_catch_ref(func, node_id) {
Some(catch_ref) =>
remove_unused_brs_try_rewrite_caught_throws_in_region(
ctx, func, catch_ref, nested,
)
None => false
}
}
@ir.HotOp::TryTable => {
let nested = catchers.copy()
nested.push(node_id)
remove_unused_brs_try_rewrite_caught_throws_in_region(
ctx,
func,
@ir.hot_region_body_ref(func, node_id),
nested,
)
}
_ => {
for child_idx = 0
child_idx < @ir.hot_child_count(func, node_id)
child_idx = child_idx + 1 {
let child = @ir.hot_child_get(func, node_id, child_idx)
if remove_unused_brs_try_rewrite_caught_throws_in_node(
ctx, func, child, catchers,
) {
return true
}
}
false
}
}
}
///|
fn remove_unused_brs_try_rewrite_caught_throws_in_region(
ctx : HotPassContext,
func : @ir.HotFunc,
region_ref : @ir.HotRegionRef,
catchers : Array[Int32],
) -> Bool {
let roots : Array[Int32] = []
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))
}
for idx = 0; idx < roots.length(); idx = idx + 1 {
let node_id = roots[idx]
if !@ir.hot_node_is_live(func, node_id) {
continue
}
let current_idx = remove_unused_brs_find_region_root_index(
func, region_ref, node_id,
)
if current_idx < 0 {
continue
}
if remove_unused_brs_try_rewrite_caught_throw_root(
ctx, func, region_ref, current_idx, node_id, catchers,
) {
return true
}
if remove_unused_brs_try_rewrite_caught_throws_in_node(
ctx, func, node_id, catchers,
) {
return true
}
}
false
}
///|
fn remove_unused_brs_simplify_dead_local_tees(
ctx : HotPassContext,
func : @ir.HotFunc,
) -> Bool {
let local_reads = @bitset.BitSet::new(@ir.hot_local_count(func))
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
}
let node = @ir.hot_node_get(func, node_id)
match node.op {
@ir.HotOp::LocalGet =>
if node.imm0 >= 0 && node.imm0 < local_reads.length() {
local_reads[node.imm0] = true
}
_ => ()
}
}
let candidates : Array[Int32] = []
for root_idx = 0; root_idx < @ir.hot_root_count(func); root_idx = root_idx + 1 {
let root_id = @ir.hot_root_get(func, root_idx)
if !@ir.hot_node_is_live(func, root_id) {
continue
}
candidates.push(root_id)
let root = @ir.hot_node_get(func, root_id)
match root.op {
@ir.HotOp::Drop =>
if root.child_count == 1 {
candidates.push(@ir.hot_child_get(func, root_id, 0))
}
_ => ()
}
}
let detached : Array[Int32] = []
let mut changed = false
for node_id in candidates {
if !@ir.hot_node_is_live(func, node_id) {
continue
}
let node = @ir.hot_node_get(func, node_id)
match node.op {
@ir.HotOp::LocalTee => {
let local_id = node.imm0
if local_id < 0 ||
local_id >= local_reads.length() ||
local_reads[local_id] ||
node.child_count != 1 {
continue
}
let child_id = @ir.hot_child_get(func, node_id, 0)
if child_id < 0 || !@ir.hot_node_is_live(func, child_id) {
continue
}
@ir.hot_replace_node(func, node_id, @ir.hot_node_get(func, child_id))
detached.push(child_id)
changed = true
}
_ => ()
}
}
if changed {
remove_unused_brs_mark_mutated(ctx, func, "simplify-dead-local-tees")
remove_unused_brs_delete_unused_nodes(ctx, func, detached)
}
changed
}
///|
fn remove_unused_brs_mark_mutated(
ctx : HotPassContext,
func : @ir.HotFunc,
reason : String,
) -> Unit {
ignore(reason)
pass_mark_mutated(ctx, func)
}
///|
fn remove_unused_brs_replace_with_nop(
ctx : HotPassContext,
func : @ir.HotFunc,
node_id : Int32,
) -> Bool {
let detached : Array[Int32] = []
let replacement = @ir.hot_build_nop(func)
detached.push(replacement)
@ir.hot_replace_node(func, node_id, @ir.hot_node_get(func, replacement))
remove_unused_brs_mark_mutated(ctx, func, "replace-with-nop")
remove_unused_brs_delete_unused_nodes(ctx, func, detached)
true
}
///|
fn remove_unused_brs_replace_with_child(
ctx : HotPassContext,
func : @ir.HotFunc,
node_id : Int32,
child_id : Int32,
) -> Bool {
if child_id < 0 || !@ir.hot_node_is_live(func, child_id) {
return false
}
let detached : Array[Int32] = [child_id]
@ir.hot_replace_node(func, node_id, @ir.hot_node_get(func, child_id))
remove_unused_brs_mark_mutated(ctx, func, "replace-with-child")
remove_unused_brs_delete_unused_nodes(ctx, func, detached)
true
}
///|
fn remove_unused_brs_splice_tail_with_children(
ctx : HotPassContext,
func : @ir.HotFunc,
region_ref : @ir.HotRegionRef,
tail_id : Int32,
) -> Bool {
let replacement : Array[Int32] = []
for child_idx = 0
child_idx < @ir.hot_child_count(func, tail_id)
child_idx = child_idx + 1 {
let child_id = @ir.hot_child_get(func, tail_id, child_idx)
if child_id < 0 || !@ir.hot_node_is_live(func, child_id) {
return false
}
replacement.push(child_id)
}
remove_unused_brs_splice_tail_roots(
ctx, func, region_ref, tail_id, replacement,
)
}
///|
fn remove_unused_brs_splice_tail_roots(
ctx : HotPassContext,
func : @ir.HotFunc,
region_ref : @ir.HotRegionRef,
tail_id : Int32,
replacement : Array[Int32],
) -> Bool {
let root_count = @ir.hot_region_root_count(func, region_ref)
if root_count == 0 {
return false
}
let tail_idx = root_count - 1
if @ir.hot_region_get(func, region_ref, tail_idx) != tail_id {
return false
}
let replacement_writes = @bitset.BitSet::new(0)
for replacement_id in replacement {
remove_unused_brs_mark_local_writes_in_value_expr(
func, replacement_id, replacement_writes,
)
}
if replacement_writes.length() > 0 {
let prefix_reads = @bitset.BitSet::new(0)
for idx = 0; idx < tail_idx; idx = idx + 1 {
remove_unused_brs_mark_local_reads_in_value_expr(
func,
@ir.hot_region_get(func, region_ref, idx),
prefix_reads,
)
}
let overlap = if replacement_writes.length() < prefix_reads.length() {
replacement_writes.length()
} else {
prefix_reads.length()
}
for local_id = 0; local_id < overlap; local_id = local_id + 1 {
if replacement_writes[local_id] && prefix_reads[local_id] {
return false
}
}
}
let removed = pass_splice_region(
ctx, func, region_ref, tail_idx, 1, replacement,
)
let nop_node = @ir.hot_node_get(func, @ir.hot_build_nop(func))
for removed_id in removed {
@ir.hot_replace_node(func, removed_id, nop_node)
}
true
}
///|
fn remove_unused_brs_reorder_cache_resize(
cache : Array[Int32],
node_id : Int32,
) -> Unit {
while cache.length() <= node_id {
cache.push(-1)
}
}
///|
fn remove_unused_brs_i32_const_node(
func : @ir.HotFunc,
node_id : Int32,
) -> Int32? {
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(value) => Some(value.0)
_ => None
}
_ => None
}
}
///|
fn remove_unused_brs_exact_numeric_may_trap(
func : @ir.HotFunc,
node_id : Int32,
) -> Bool {
match @ir.hot_node_exact_instr(func, node_id) {
Some(@lib.Instruction::I32RemS)
| Some(@lib.Instruction::I32RemU)
| Some(@lib.Instruction::I32DivU) => {
if @ir.hot_child_count(func, node_id) != 2 {
return true
}
match
remove_unused_brs_i32_const_node(
func,
@ir.hot_child_get(func, node_id, 1),
) {
Some(rhs) => rhs == 0
None => true
}
}
Some(@lib.Instruction::I32DivS) => {
if @ir.hot_child_count(func, node_id) != 2 {
return true
}
let rhs = match
remove_unused_brs_i32_const_node(
func,
@ir.hot_child_get(func, node_id, 1),
) {
Some(rhs) => rhs
None => return true
}
if rhs == 0 {
return true
}
if rhs != -1 {
return false
}
match
remove_unused_brs_i32_const_node(
func,
@ir.hot_child_get(func, node_id, 0),
) {
Some(lhs) => lhs == -2147483648
None => true
}
}
Some(@lib.Instruction::I64DivS)
| Some(@lib.Instruction::I64DivU)
| Some(@lib.Instruction::I64RemS)
| Some(@lib.Instruction::I64RemU)
| Some(@lib.Instruction::I32TruncF32S)
| Some(@lib.Instruction::I32TruncF32U)
| Some(@lib.Instruction::I32TruncF64S)
| Some(@lib.Instruction::I32TruncF64U)
| Some(@lib.Instruction::I64TruncF32S)
| Some(@lib.Instruction::I64TruncF32U)
| Some(@lib.Instruction::I64TruncF64S)
| Some(@lib.Instruction::I64TruncF64U) => true
_ => false
}
}
///|
fn remove_unused_brs_node_is_reorder_safe(
ctx : HotPassContext,
func : @ir.HotFunc,
node_id : Int32,
cache : Array[Int32],
) -> Bool {
let _ = ctx
if node_id < 0 || !@ir.hot_node_is_live(func, node_id) {
return false
}
remove_unused_brs_reorder_cache_resize(cache, node_id)
match cache[node_id] {
0 => return false
1 => return true
_ => ()
}
let node = @ir.hot_node_get(func, node_id)
// The coarse heap effect class is conservative, but ref.i31 is an exact
// nontrapping pure wrapper and is safe to speculate into a select arm.
let exact_ref_i31 = match @ir.hot_node_exact_instr(func, node_id) {
Some(@lib.Instruction::RefI31) => true
_ => false
}
if (!exact_ref_i31 && @ir.hot_may_trap(func, node_id)) ||
remove_unused_brs_exact_numeric_may_trap(func, node_id) {
cache[node_id] = 0
return false
}
match node.op {
@ir.HotOp::Const
| @ir.HotOp::LocalGet
| @ir.HotOp::GlobalGet
| @ir.HotOp::RefNull
| @ir.HotOp::RefIsNull
| @ir.HotOp::RefFunc
| @ir.HotOp::Nop
| @ir.HotOp::Drop => ()
@ir.HotOp::Unary
| @ir.HotOp::Binary
| @ir.HotOp::Compare
| @ir.HotOp::Convert
| @ir.HotOp::Select
| @ir.HotOp::TupleMake
| @ir.HotOp::TupleExtract => ()
@ir.HotOp::If =>
if @ir.hot_node_result_arity(func, node_id) != 1 {
return false
}
@ir.HotOp::Heap =>
match @ir.hot_node_exact_instr(func, node_id) {
Some(@lib.Instruction::RefI31) => ()
_ => return false
}
@ir.HotOp::Simd =>
// RUB-S: exact v128 constants are side-effect-free payload values and may
// move across a pure switch selector for one-target br_table collapse.
match @ir.hot_node_exact_instr(func, node_id) {
Some(
@lib.Instruction::V128Const(
_,
_,
_,
_,
_,
_,
_,
_,
_,
_,
_,
_,
_,
_,
_,
_
)
) => ()
_ => return false
}
_ => return false
}
let child_limit = match node.op {
@ir.HotOp::If => 1
_ => @ir.hot_child_count(func, node_id)
}
for slot = 0; slot < child_limit; slot = slot + 1 {
let child_id = @ir.hot_child_get(func, node_id, slot)
if child_id < 0 || !@ir.hot_node_is_live(func, child_id) {
continue
}
if !remove_unused_brs_node_is_reorder_safe(ctx, func, child_id, cache) {
cache[node_id] = 0
return false
}
}
let safe = match node.op {
@ir.HotOp::If => {
let then_ref = @ir.hot_region_then_ref(func, node_id)
let else_ref = match @ir.hot_region_else_ref(func, node_id) {
Some(else_ref) => else_ref
None => {
cache[node_id] = 0
return false
}
}
if @ir.hot_region_root_count(func, then_ref) != 1 ||
@ir.hot_region_root_count(func, else_ref) != 1 {
cache[node_id] = 0
return false
}
let then_root = @ir.hot_region_get(func, then_ref, 0)
let else_root = @ir.hot_region_get(func, else_ref, 0)
remove_unused_brs_node_is_reorder_safe(ctx, func, then_root, cache) &&
remove_unused_brs_node_is_reorder_safe(ctx, func, else_root, cache)
}
_ => true
}
cache[node_id] = if safe { 1 } else { 0 }
safe
}
///|
fn remove_unused_brs_build_select(
func : @ir.HotFunc,
result_type : @lib.ValType,
if_true : Int32,
if_false : Int32,
condition : Int32,
) -> Int32 {
@ir.hot_build_select(func, result_type, if_true, if_false, condition)
}
///|
fn remove_unused_brs_selectify_binary_base_cost(
func : @ir.HotFunc,
node_id : Int32,
) -> Int32 {
match @ir.hot_node_exact_instr(func, node_id) {
Some(instr) =>
if instr == @lib.Instruction::i32_div_s() ||
instr == @lib.Instruction::i32_div_u() ||
instr == @lib.Instruction::i32_rem_s() ||
instr == @lib.Instruction::i32_rem_u() ||
instr == @lib.Instruction::i64_div_s() ||
instr == @lib.Instruction::i64_div_u() ||
instr == @lib.Instruction::i64_rem_s() ||
instr == @lib.Instruction::i64_rem_u() {
5
} else {
1
}
None => 1
}
}
///|
fn remove_unused_brs_selectify_convert_base_cost(
func : @ir.HotFunc,
node_id : Int32,
) -> Int32 {
match @ir.hot_node_exact_instr(func, node_id) {
Some(instr) =>
if instr == @lib.Instruction::i32_trunc_f32s() ||
instr == @lib.Instruction::i32_trunc_f32u() ||
instr == @lib.Instruction::i32_trunc_f64s() ||
instr == @lib.Instruction::i32_trunc_f64u() ||
instr == @lib.Instruction::i64_trunc_f32s() ||
instr == @lib.Instruction::i64_trunc_f32u() ||
instr == @lib.Instruction::i64_trunc_f64s() ||
instr == @lib.Instruction::i64_trunc_f64u() {
5
} else {
1
}
None => 1
}
}
///|
fn remove_unused_brs_selectify_cost(
func : @ir.HotFunc,
node_id : Int32,
) -> Int32 {
if node_id < 0 || !@ir.hot_node_is_live(func, node_id) {
return 0
}
let node = @ir.hot_node_get(func, node_id)
let mut cost = match node.op {
@ir.HotOp::Const
| @ir.HotOp::LocalGet
| @ir.HotOp::GlobalGet
| @ir.HotOp::RefNull
| @ir.HotOp::RefFunc
| @ir.HotOp::Nop => 0
@ir.HotOp::Binary =>
remove_unused_brs_selectify_binary_base_cost(func, node_id)
@ir.HotOp::Convert =>
remove_unused_brs_selectify_convert_base_cost(func, node_id)
@ir.HotOp::RefTest | @ir.HotOp::RefTestDesc => 5
@ir.HotOp::Heap =>
match @ir.hot_node_exact_instr(func, node_id) {
Some(@lib.Instruction::RefI31) => 1
_ => 8
}
@ir.HotOp::RefCast
| @ir.HotOp::RefCastDescEq
| @ir.HotOp::Atomic
| @ir.HotOp::Throw
| @ir.HotOp::ThrowRef => 8
@ir.HotOp::Block => 1
_ => 1
}
for slot = 0; slot < @ir.hot_child_count(func, node_id); slot = slot + 1 {
cost += remove_unused_brs_selectify_cost(
func,
@ir.hot_child_get(func, node_id, slot),
)
}
match node.op {
@ir.HotOp::Block => {
let body_ref = @ir.hot_region_body_ref(func, node_id)
for idx = 0
idx < @ir.hot_region_root_count(func, body_ref)
idx = idx + 1 {
cost += remove_unused_brs_selectify_cost(
func,
@ir.hot_region_get(func, body_ref, idx),
)
}
}
_ => ()
}
cost
}
///|
fn remove_unused_brs_unconditional_cost_too_high(
ctx : HotPassContext,
cost : Int32,
) -> Bool {
if ctx.shrink_level <= 0 {
cost > 4
} else {
ctx.shrink_level < 2 && cost >= 8
}
}
///|
fn remove_unused_brs_branch_payload_too_costly(
ctx : HotPassContext,
func : @ir.HotFunc,
branch_id : Int32,
) -> Bool {
if ctx.shrink_level >= 2 {
return false
}
let mut cost = 0
for slot = 0; slot < @ir.hot_child_count(func, branch_id); slot = slot + 1 {
cost += remove_unused_brs_selectify_cost(
func,
@ir.hot_child_get(func, branch_id, slot),
)
}
remove_unused_brs_unconditional_cost_too_high(ctx, cost)
}
///|
fn remove_unused_brs_selectify_too_costly(
ctx : HotPassContext,
func : @ir.HotFunc,
then_root : Int32,
else_root : Int32,
) -> Bool {
if ctx.shrink_level >= 2 {
return false
}
let then_cost = remove_unused_brs_selectify_cost(func, then_root)
let else_cost = remove_unused_brs_selectify_cost(func, else_root)
let max_cost = if then_cost > else_cost { then_cost } else { else_cost }
remove_unused_brs_unconditional_cost_too_high(ctx, max_cost)
}
///|
fn remove_unused_brs_build_i32_eqz(
func : @ir.HotFunc,
operand : Int32,
) -> Int32 {
let temp = @ir.hot_build_unary(func, @lib.ValType::i32(), operand)
@ir.hot_node_exact_instr_set(func, temp, @lib.Instruction::i32_eqz())
temp
}
///|
fn remove_unused_brs_build_i32_sub(
func : @ir.HotFunc,
lhs : Int32,
rhs : Int32,
) -> Int32 {
let temp = @ir.hot_build_binary(func, @lib.ValType::i32(), lhs, rhs)
@ir.hot_node_exact_instr_set(func, temp, @lib.Instruction::i32_sub())
temp
}
///|
fn remove_unused_brs_build_i32_eq(
func : @ir.HotFunc,
lhs : Int32,
rhs : Int32,
) -> Int32 {
let temp = @ir.hot_build_compare(func, lhs, rhs)
@ir.hot_node_exact_instr_set(func, temp, @lib.Instruction::i32_eq())
temp
}
///|
fn remove_unused_brs_local_read_mask_resize(
reads : @bitset.BitSet,
local_id : Int32,
) -> Unit {
if local_id >= reads.length() {
reads.grow(local_id + 1)
}
}
///|
fn remove_unused_brs_mark_local_reads_in_value_expr(
func : @ir.HotFunc,
node_id : Int32,
reads : @bitset.BitSet,
) -> Unit {
if node_id < 0 || !@ir.hot_node_is_live(func, node_id) {
return
}
let node = @ir.hot_node_get(func, node_id)
match node.op {
@ir.HotOp::LocalGet => {
let local_id = node.imm0
if local_id >= 0 {
remove_unused_brs_local_read_mask_resize(reads, local_id)
reads[local_id] = true
}
}
@ir.HotOp::If => {
remove_unused_brs_mark_local_reads_in_value_expr(
func,
@ir.hot_child_get(func, node_id, 0),
reads,
)
let then_ref = @ir.hot_region_then_ref(func, node_id)
for idx = 0
idx < @ir.hot_region_root_count(func, then_ref)
idx = idx + 1 {
remove_unused_brs_mark_local_reads_in_value_expr(
func,
@ir.hot_region_get(func, then_ref, idx),
reads,
)
}
match @ir.hot_region_else_ref(func, node_id) {
Some(else_ref) =>
for idx = 0
idx < @ir.hot_region_root_count(func, else_ref)
idx = idx + 1 {
remove_unused_brs_mark_local_reads_in_value_expr(
func,
@ir.hot_region_get(func, else_ref, idx),
reads,
)
}
None => ()
}
}
_ =>
for slot = 0; slot < @ir.hot_child_count(func, node_id); slot = slot + 1 {
remove_unused_brs_mark_local_reads_in_value_expr(
func,
@ir.hot_child_get(func, node_id, slot),
reads,
)
}
}
}
///|
fn remove_unused_brs_mark_local_writes_in_value_expr(
func : @ir.HotFunc,
node_id : Int32,
writes : @bitset.BitSet,
) -> Unit {
if node_id < 0 || !@ir.hot_node_is_live(func, node_id) {
return
}
let node = @ir.hot_node_get(func, node_id)
match node.op {
@ir.HotOp::LocalSet | @ir.HotOp::LocalTee => {
let local_id = node.imm0
if local_id >= 0 {
remove_unused_brs_local_read_mask_resize(writes, local_id)
writes[local_id] = true
}
}
@ir.HotOp::If => {
remove_unused_brs_mark_local_writes_in_value_expr(
func,
@ir.hot_child_get(func, node_id, 0),
writes,
)
let then_ref = @ir.hot_region_then_ref(func, node_id)
for idx = 0
idx < @ir.hot_region_root_count(func, then_ref)
idx = idx + 1 {
remove_unused_brs_mark_local_writes_in_value_expr(
func,
@ir.hot_region_get(func, then_ref, idx),
writes,
)
}
match @ir.hot_region_else_ref(func, node_id) {
Some(else_ref) =>
for idx = 0
idx < @ir.hot_region_root_count(func, else_ref)
idx = idx + 1 {
remove_unused_brs_mark_local_writes_in_value_expr(
func,
@ir.hot_region_get(func, else_ref, idx),
writes,
)
}
None => ()
}
return
}
_ => ()
}
for slot = 0; slot < @ir.hot_child_count(func, node_id); slot = slot + 1 {
remove_unused_brs_mark_local_writes_in_value_expr(
func,
@ir.hot_child_get(func, node_id, slot),
writes,
)
}
}
///|
fn remove_unused_brs_value_expr_reads_global(
func : @ir.HotFunc,
node_id : Int32,
) -> 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::GlobalGet => return true
@ir.HotOp::If => {
let then_ref = @ir.hot_region_then_ref(func, node_id)
for idx = 0
idx < @ir.hot_region_root_count(func, then_ref)
idx = idx + 1 {
if remove_unused_brs_value_expr_reads_global(
func,
@ir.hot_region_get(func, then_ref, idx),
) {
return true
}
}
match @ir.hot_region_else_ref(func, node_id) {
Some(else_ref) =>
for idx = 0
idx < @ir.hot_region_root_count(func, else_ref)
idx = idx + 1 {
if remove_unused_brs_value_expr_reads_global(
func,
@ir.hot_region_get(func, else_ref, idx),
) {
return true
}
}
None => ()
}
}
_ => ()
}
for slot = 0; slot < @ir.hot_child_count(func, node_id); slot = slot + 1 {
if remove_unused_brs_value_expr_reads_global(
func,
@ir.hot_child_get(func, node_id, slot),
) {
return true
}
}
false
}
///|
fn remove_unused_brs_call_condition_preserves_arm_locals(
func : @ir.HotFunc,
node_id : Int32,
arm_reads : @bitset.BitSet,
) -> 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 => ()
_ => return false
}
for slot = 0; slot < @ir.hot_child_count(func, node_id); slot = slot + 1 {
if !remove_unused_brs_condition_is_select_safe_over_value_arms(
func,
@ir.hot_child_get(func, node_id, slot),
arm_reads,
) {
return false
}
}
true
}
///|
fn remove_unused_brs_condition_is_select_safe_over_value_arms(
func : @ir.HotFunc,
node_id : Int32,
arm_reads : @bitset.BitSet,
) -> 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::Const
| @ir.HotOp::LocalGet
| @ir.HotOp::GlobalGet
| @ir.HotOp::RefNull
| @ir.HotOp::RefIsNull
| @ir.HotOp::RefFunc
| @ir.HotOp::TableSize
| @ir.HotOp::Nop
| @ir.HotOp::Drop
| @ir.HotOp::Unary
| @ir.HotOp::Binary
| @ir.HotOp::Compare
| @ir.HotOp::Convert
| @ir.HotOp::Select
| @ir.HotOp::TupleMake
| @ir.HotOp::TupleExtract => {
for slot = 0; slot < @ir.hot_child_count(func, node_id); slot = slot + 1 {
if !remove_unused_brs_condition_is_select_safe_over_value_arms(
func,
@ir.hot_child_get(func, node_id, slot),
arm_reads,
) {
return false
}
}
true
}
@ir.HotOp::Heap => {
match @ir.hot_node_exact_instr(func, node_id) {
Some(@lib.Instruction::StructGet(_, _))
| Some(@lib.Instruction::StructGetS(_, _))
| Some(@lib.Instruction::StructGetU(_, _)) => ()
_ => return false
}
for slot = 0; slot < @ir.hot_child_count(func, node_id); slot = slot + 1 {
if !remove_unused_brs_condition_is_select_safe_over_value_arms(
func,
@ir.hot_child_get(func, node_id, slot),
arm_reads,
) {
return false
}
}
true
}
@ir.HotOp::LocalTee => {
let local_id = node.imm0
if local_id >= 0 && local_id < arm_reads.length() && arm_reads[local_id] {
return false
}
remove_unused_brs_condition_is_select_safe_over_value_arms(
func,
@ir.hot_child_get(func, node_id, 0),
arm_reads,
)
}
@ir.HotOp::If => {
if @ir.hot_node_result_arity(func, node_id) != 1 {
return false
}
let else_ref = match @ir.hot_region_else_ref(func, node_id) {
Some(else_ref) => else_ref
None => return false
}
if !remove_unused_brs_condition_is_select_safe_over_value_arms(
func,
@ir.hot_child_get(func, node_id, 0),
arm_reads,
) {
return false
}
let then_ref = @ir.hot_region_then_ref(func, node_id)
for idx = 0
idx < @ir.hot_region_root_count(func, then_ref)
idx = idx + 1 {
if !remove_unused_brs_condition_is_select_safe_over_value_arms(
func,
@ir.hot_region_get(func, then_ref, idx),
arm_reads,
) {
return false
}
}
for idx = 0
idx < @ir.hot_region_root_count(func, else_ref)
idx = idx + 1 {
if !remove_unused_brs_condition_is_select_safe_over_value_arms(
func,
@ir.hot_region_get(func, else_ref, idx),
arm_reads,
) {
return false
}
}
true
}
_ => false
}
}
///|
fn remove_unused_brs_match_i32_const_value(
func : @ir.HotFunc,
node_id : Int32,
) -> Int32? {
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(value) => Some(value.0)
_ => None
}
_ => None
}
}
///|
fn remove_unused_brs_match_br_if_ladder_selector(
func : @ir.HotFunc,
node_id : Int32,
) -> (Int32, Int32)? {
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::LocalGet => Some((node_id, node.imm0))
@ir.HotOp::LocalTee =>
if node.child_count == 1 {
Some((node_id, node.imm0))
} else {
None
}
_ => None
}
}
///|
fn remove_unused_brs_match_br_if_ladder_eq_const(
func : @ir.HotFunc,
node_id : Int32,
) -> (Int32, Int32)? {
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::Unary => {
if node.child_count != 1 ||
@ir.hot_node_exact_instr(func, node_id) !=
Some(@lib.Instruction::i32_eqz()) {
return None
}
return Some((@ir.hot_child_get(func, node_id, 0), 0))
}
@ir.HotOp::Compare =>
if node.child_count != 2 ||
@ir.hot_node_exact_instr(func, node_id) !=
Some(@lib.Instruction::i32_eq()) {
return None
}
_ => return None
}
let lhs = @ir.hot_child_get(func, node_id, 0)
let rhs = @ir.hot_child_get(func, node_id, 1)
match remove_unused_brs_match_i32_const_value(func, rhs) {
Some(value) => Some((lhs, value))
None =>
match remove_unused_brs_match_i32_const_value(func, lhs) {
Some(value) => Some((rhs, value))
None => None
}
}
}
///|
fn remove_unused_brs_int_array_contains(
values : Array[Int32],
needle : Int32,
) -> Bool {
for value in values {
if value == needle {
return true
}
}
false
}
///|
fn remove_unused_brs_ladder_selector_matches(
func : @ir.HotFunc,
selector_id : Int32,
selector_local : Int32,
) -> Bool {
if selector_id < 0 || !@ir.hot_node_is_live(func, selector_id) {
return false
}
let selector = @ir.hot_node_get(func, selector_id)
match selector.op {
@ir.HotOp::LocalGet if selector.imm0 == selector_local => true
_ => false
}
}
///|
fn remove_unused_brs_try_drop_void_self_branch_block(
ctx : HotPassContext,
func : @ir.HotFunc,
block_id : Int32,
) -> Bool {
let block_node = @ir.hot_node_get(func, block_id)
match block_node.op {
@ir.HotOp::Block => ()
_ => return false
}
if @ir.hot_type_result_arity(func, block_node.ty) != 0 {
return false
}
let body_ref = @ir.hot_region_body_ref(func, block_id)
if @ir.hot_region_root_count(func, body_ref) != 1 {
return false
}
let branch_id = @ir.hot_region_get(func, body_ref, 0)
let branch_node = @ir.hot_node_get(func, branch_id)
match branch_node.op {
@ir.HotOp::Br => ()
_ => return false
}
if branch_node.child_count != 0 ||
branch_node.imm0 != @ir.hot_control_node_label(func, block_id) {
return false
}
@ir.hot_region_replace_body(func, body_ref, [])
remove_unused_brs_delete_unused_nodes(ctx, func, [branch_id])
remove_unused_brs_mark_mutated(ctx, func, "drop-void-self-branch-block")
true
}
///|
fn remove_unused_brs_try_fold_constant_br_if(
ctx : HotPassContext,
func : @ir.HotFunc,
region_ref : @ir.HotRegionRef,
idx : Int32,
) -> Bool {
let branch_id = @ir.hot_region_get(func, region_ref, idx)
let branch_node = @ir.hot_node_get(func, branch_id)
match branch_node.op {
@ir.HotOp::BrIf => ()
_ => return false
}
if branch_node.child_count < 1 {
return false
}
let condition_slot = branch_node.child_count - 1
let condition = @ir.hot_child_get(func, branch_id, condition_slot)
let value = match remove_unused_brs_match_i32_const_value(func, condition) {
Some(value) => value
None => return false
}
let payloads : Array[Int32] = []
for child_idx = 0; child_idx < condition_slot; child_idx = child_idx + 1 {
let payload = @ir.hot_child_get(func, branch_id, child_idx)
if payload < 0 || !@ir.hot_node_is_live(func, payload) {
return false
}
payloads.push(payload)
}
let replacement = if value == 0 {
payloads
} else {
[@ir.hot_build_br(func, branch_node.imm0, values=payloads)]
}
let removed = pass_splice_region(ctx, func, region_ref, idx, 1, replacement)
removed.push(condition)
remove_unused_brs_delete_unused_nodes(ctx, func, removed)
remove_unused_brs_mark_mutated(ctx, func, "fold-constant-br-if")
true
}
///|
fn remove_unused_brs_try_rewrite_br_if_eq_ladder_to_br_table(
ctx : HotPassContext,
func : @ir.HotFunc,
region_ref : @ir.HotRegionRef,
idx : Int32,
) -> Bool {
if idx < 0 || idx >= @ir.hot_region_root_count(func, region_ref) {
return false
}
let first_branch = @ir.hot_region_get(func, region_ref, idx)
if first_branch < 0 || !@ir.hot_node_is_live(func, first_branch) {
return false
}
let first_branch_node = @ir.hot_node_get(func, first_branch)
match first_branch_node.op {
@ir.HotOp::BrIf => if first_branch_node.child_count != 1 { return false }
_ => return false
}
let first_target = match @ir.hot_branch_target(func, first_branch) {
Some(label_id) => label_id
None => return false
}
let first_condition = @ir.hot_child_get(func, first_branch, 0)
let (first_selector, first_const) = match
remove_unused_brs_match_br_if_ladder_eq_const(func, first_condition) {
Some(matched) => matched
None => return false
}
let (selector_node, selector_local) = match
remove_unused_brs_match_br_if_ladder_selector(func, first_selector) {
Some(matched) => matched
None => return false
}
if first_const < 0 || first_const >= 2147483647 {
return false
}
let branch_ids : Array[Int32] = [first_branch]
let constants : Array[Int32] = [first_const]
let target_labels : Array[Int32] = [first_target]
let mut scan_idx = idx + 1
while scan_idx < @ir.hot_region_root_count(func, region_ref) {
let branch_id = @ir.hot_region_get(func, region_ref, scan_idx)
if branch_id < 0 || !@ir.hot_node_is_live(func, branch_id) {
break
}
let branch_node = @ir.hot_node_get(func, branch_id)
match branch_node.op {
@ir.HotOp::BrIf => if branch_node.child_count != 1 { break }
_ => break
}
let target_label = match @ir.hot_branch_target(func, branch_id) {
Some(label_id) => label_id
None => break
}
let condition = @ir.hot_child_get(func, branch_id, 0)
let (selector_id, const_value) = match
remove_unused_brs_match_br_if_ladder_eq_const(func, condition) {
Some(matched) => matched
None => break
}
if !remove_unused_brs_ladder_selector_matches(
func, selector_id, selector_local,
) {
break
}
if const_value < 0 || const_value >= 2147483647 {
break
}
if remove_unused_brs_int_array_contains(constants, const_value) {
break
}
branch_ids.push(branch_id)
constants.push(const_value)
target_labels.push(target_label)
scan_idx = scan_idx + 1
}
if branch_ids.length() < 3 {
return false
}
let mut min_const = constants[0]
let mut max_const = constants[0]
for const_value in constants {
if const_value < min_const {
min_const = const_value
}
if const_value > max_const {
max_const = const_value
}
}
let range = max_const - min_const
if range > 1024 || range > branch_ids.length() * 3 {
return false
}
let inner_block = @ir.hot_build_block(func, @lib.BlockType::void_(), [])
let inner_label = @ir.hot_control_node_label(func, inner_block)
let table_targets = Array::make(range + 1, inner_label)
for i = 0; i < constants.length(); i = i + 1 {
table_targets[constants[i] - min_const] = target_labels[i]
}
let selector = if min_const == 0 {
selector_node
} else {
remove_unused_brs_build_i32_sub(
func,
selector_node,
@ir.hot_build_const_i32(func, @lib.I32(min_const)),
)
}
let br_table = @ir.hot_build_br_table(
func, table_targets, inner_label, selector,
)
@ir.hot_region_replace_body(
func,
@ir.hot_region_body_ref(func, inner_block),
[br_table],
)
let replacement : Array[Int32] = []
let mut nop_count = 0
while nop_count < branch_ids.length() - 1 {
replacement.push(@ir.hot_build_nop(func))
nop_count = nop_count + 1
}
replacement.push(inner_block)
let removed = pass_splice_region(
ctx,
func,
region_ref,
idx,
branch_ids.length(),
replacement,
)
remove_unused_brs_delete_unused_nodes(ctx, func, removed)
true
}
///|
fn remove_unused_brs_trim_trailing_nops(
ctx : HotPassContext,
func : @ir.HotFunc,
region_ref : @ir.HotRegionRef,
preserve_single_if_arm_nop~ : Bool = false,
) -> Bool {
let mut changed = false
let initial_root_count = @ir.hot_region_root_count(func, region_ref)
let preserve_single_root_nop = region_ref == @ir.HotRegionRef::root() &&
initial_root_count == 1
let preserve_single_if_arm_nop = preserve_single_if_arm_nop &&
initial_root_count == 1
while @ir.hot_region_root_count(func, region_ref) > 0 {
if preserve_single_root_nop &&
@ir.hot_region_root_count(func, region_ref) == 1 {
break
}
if preserve_single_if_arm_nop &&
@ir.hot_region_root_count(func, region_ref) == 1 {
break
}
let idx = @ir.hot_region_root_count(func, region_ref) - 1
let node_id = @ir.hot_region_get(func, region_ref, idx)
match @ir.hot_node_get(func, node_id).op {
@ir.HotOp::Nop => ()
_ => break
}
let removed = pass_splice_region(ctx, func, region_ref, idx, 1, [])
remove_unused_brs_delete_unused_nodes(ctx, func, removed)
changed = true
}
changed
}
///|
fn remove_unused_brs_trim_leading_nops(
ctx : HotPassContext,
func : @ir.HotFunc,
region_ref : @ir.HotRegionRef,
) -> Bool {
let mut changed = false
while @ir.hot_region_root_count(func, region_ref) > 0 {
let node_id = @ir.hot_region_get(func, region_ref, 0)
match @ir.hot_node_get(func, node_id).op {
@ir.HotOp::Nop => ()
_ => break
}
let removed = pass_splice_region(ctx, func, region_ref, 0, 1, [])
remove_unused_brs_delete_unused_nodes(ctx, func, removed)
changed = true
}
changed
}
///|
fn remove_unused_brs_tail_is_nonfallthrough(
func : @ir.HotFunc,
region_ref : @ir.HotRegionRef,
) -> Bool {
if @ir.hot_region_root_count(func, region_ref) == 0 {
return false
}
let tail_id = @ir.hot_region_get(
func,
region_ref,
@ir.hot_region_root_count(func, region_ref) - 1,
)
let tail = @ir.hot_node_get(func, tail_id)
match tail.op {
@ir.HotOp::If => {
let else_ref = match @ir.hot_region_else_ref(func, tail_id) {
Some(else_ref) => else_ref
None => return false
}
remove_unused_brs_tail_is_nonfallthrough(
func,
@ir.hot_region_then_ref(func, tail_id),
) &&
remove_unused_brs_tail_is_nonfallthrough(func, else_ref)
}
_ => {
let edge_kinds = @ir.cfg_terminator_edge_kinds(tail.op)
if edge_kinds.length() == 0 {
return false
}
for edge_kind in edge_kinds {
match edge_kind {
@ir.CfgEdgeKind::FallthroughEdge => return false
_ => ()
}
}
true
}
}
}
///|
fn remove_unused_brs_node_is_nonfallthrough_root(
func : @ir.HotFunc,
node_id : Int32,
label_refs : Array[Int32],
) -> 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 => {
let else_ref = match @ir.hot_region_else_ref(func, node_id) {
Some(else_ref) => else_ref
None => return false
}
remove_unused_brs_tail_is_nonfallthrough(
func,
@ir.hot_region_then_ref(func, node_id),
) &&
remove_unused_brs_tail_is_nonfallthrough(func, else_ref)
}
@ir.HotOp::Block => {
let label_id = @ir.hot_control_node_label(func, node_id)
if label_id >= 0 &&
label_id < label_refs.length() &&
label_refs[label_id] > 0 {
return false
}
remove_unused_brs_tail_is_nonfallthrough(
func,
@ir.hot_region_body_ref(func, node_id),
)
}
_ => {
let edge_kinds = @ir.cfg_terminator_edge_kinds(node.op)
if edge_kinds.length() == 0 {
return false
}
for edge_kind in edge_kinds {
match edge_kind {
@ir.CfgEdgeKind::FallthroughEdge => return false
_ => ()
}
}
true
}
}
}
///|
fn remove_unused_brs_root_suffix_required_after_void_structured_terminal(
func : @ir.HotFunc,
node_id : Int32,
) -> Bool {
if @ir.hot_type_result_arity(func, @ir.hot_body_result_type(func)) == 0 {
return false
}
if @ir.hot_node_result_arity(func, node_id) != 0 {
return false
}
match @ir.hot_node_get(func, node_id).op {
@ir.HotOp::If => true
@ir.HotOp::Block => {
let body_ref = @ir.hot_region_body_ref(func, node_id)
if @ir.hot_region_root_count(func, body_ref) == 1 {
let body_root = @ir.hot_region_get(func, body_ref, 0)
if body_root >= 0 && @ir.hot_node_is_live(func, body_root) {
match @ir.hot_node_get(func, body_root).op {
@ir.HotOp::Unreachable => return false
_ => ()
}
}
}
true
}
_ => false
}
}
///|
fn remove_unused_brs_prune_dead_suffix_after_nonfallthrough(
ctx : HotPassContext,
func : @ir.HotFunc,
region_ref : @ir.HotRegionRef,
label_refs : Array[Int32],
) -> Bool {
// Intentional Starshine optimization: after RUB folds a side-effect-free
// branch/table to a nonfallthrough root, delete the now-dead root suffix
// rather than preserving Binaryen's structured trap/control shell. Note
// 1392 and RUB-R limit this to root dead suffixes whose preceding root is
// proven nonfallthrough; result-typed functions keep required suffix values.
if region_ref != @ir.HotRegionRef::root() {
return false
}
let root_count = @ir.hot_region_root_count(func, region_ref)
if root_count <= 1 {
return false
}
for idx = 0; idx < root_count - 1; idx = idx + 1 {
let node_id = @ir.hot_region_get(func, region_ref, idx)
if !remove_unused_brs_node_is_nonfallthrough_root(func, node_id, label_refs) {
continue
}
if remove_unused_brs_root_suffix_required_after_void_structured_terminal(
func, node_id,
) {
continue
}
let replacement = match @ir.hot_node_get(func, node_id).op {
@ir.HotOp::Block => {
let body_ref = @ir.hot_region_body_ref(func, node_id)
if @ir.hot_region_root_count(func, body_ref) == 1 {
let body_root = @ir.hot_region_get(func, body_ref, 0)
if @ir.hot_node_is_live(func, body_root) {
match @ir.hot_node_get(func, body_root).op {
@ir.HotOp::Unreachable => [body_root]
_ => [node_id]
}
} else {
[node_id]
}
} else {
[node_id]
}
}
_ => [node_id]
}
let removed = pass_splice_region(
ctx,
func,
region_ref,
idx,
root_count - idx,
replacement,
)
remove_unused_brs_delete_unused_nodes(ctx, func, removed)
return true
}
false
}
///|
fn remove_unused_brs_block_is_direct_result_wrapper_before_unreachable(
func : @ir.HotFunc,
block_id : Int32,
) -> Bool {
for parent_id = 0
parent_id < @ir.hot_node_count(func)
parent_id = parent_id + 1 {
if !@ir.hot_node_is_live(func, parent_id) ||
@ir.hot_node_result_arity(func, parent_id) == 0 {
continue
}
match @ir.hot_node_get(func, parent_id).op {
@ir.HotOp::Block => ()
_ => continue
}
let body_ref = @ir.hot_region_body_ref(func, parent_id)
let root_count = @ir.hot_region_root_count(func, body_ref)
for idx = 0; idx < root_count - 1; idx = idx + 1 {
if @ir.hot_region_get(func, body_ref, idx) != block_id {
continue
}
let suffix = @ir.hot_region_get(func, body_ref, idx + 1)
if suffix >= 0 && @ir.hot_node_is_live(func, suffix) {
match @ir.hot_node_get(func, suffix).op {
@ir.HotOp::Unreachable => return true
_ => ()
}
}
}
}
false
}
///|
priv struct RemoveUnusedBrsPruneCfg {
values : Array[@ir.HotCfg]
}
///|
fn RemoveUnusedBrsPruneCfg::new() -> RemoveUnusedBrsPruneCfg {
{ values: [], }
}
///|
fn remove_unused_brs_prune_cfg_get(
prune_cfg : RemoveUnusedBrsPruneCfg,
ctx : HotPassContext,
func : @ir.HotFunc,
) -> @ir.HotCfg {
if prune_cfg.values.length() == 0 {
prune_cfg.values.push(pass_require_cfg(ctx, func))
}
prune_cfg.values[0]
}
///|
fn remove_unused_brs_prune_one_nested_dead_suffix_node(
ctx : HotPassContext,
func : @ir.HotFunc,
prune_cfg : RemoveUnusedBrsPruneCfg,
seen : @bitset.BitSet,
node_id : Int32,
) -> Bool {
if node_id < 0 || !@ir.hot_node_is_live(func, node_id) || seen[node_id] {
return false
}
seen[node_id] = true
match @ir.hot_node_get(func, node_id).op {
@ir.HotOp::Block | @ir.HotOp::Loop =>
remove_unused_brs_prune_one_nested_dead_suffix_with_seen(
ctx,
func,
prune_cfg,
seen,
@ir.hot_region_body_ref(func, node_id),
)
@ir.HotOp::If => {
if remove_unused_brs_prune_one_nested_dead_suffix_node(
ctx,
func,
prune_cfg,
seen,
@ir.hot_child_get(func, node_id, 0),
) {
return true
}
if remove_unused_brs_prune_one_nested_dead_suffix_with_seen(
ctx,
func,
prune_cfg,
seen,
@ir.hot_region_then_ref(func, node_id),
) {
return true
}
match @ir.hot_region_else_ref(func, node_id) {
Some(else_ref) =>
remove_unused_brs_prune_one_nested_dead_suffix_with_seen(
ctx, func, prune_cfg, seen, else_ref,
)
None => false
}
}
@ir.HotOp::Try => {
if remove_unused_brs_prune_one_nested_dead_suffix_with_seen(
ctx,
func,
prune_cfg,
seen,
@ir.hot_region_body_ref(func, node_id),
) {
return true
}
match @ir.hot_region_catch_ref(func, node_id) {
Some(catch_ref) =>
remove_unused_brs_prune_one_nested_dead_suffix_with_seen(
ctx, func, prune_cfg, seen, catch_ref,
)
None => false
}
}
@ir.HotOp::TryTable => {
if remove_unused_brs_prune_one_nested_dead_suffix_with_seen(
ctx,
func,
prune_cfg,
seen,
@ir.hot_region_body_ref(func, node_id),
) {
return true
}
remove_unused_brs_prune_one_nested_dead_suffix_with_seen(
ctx,
func,
prune_cfg,
seen,
@ir.hot_region_catch_list_ref(func, node_id),
)
}
_ => {
for child in @ir.hot_child_span(func, node_id) {
if remove_unused_brs_prune_one_nested_dead_suffix_node(
ctx, func, prune_cfg, seen, child,
) {
return true
}
}
false
}
}
}
///|
fn remove_unused_brs_prune_one_nested_dead_suffix_with_seen(
ctx : HotPassContext,
func : @ir.HotFunc,
prune_cfg : RemoveUnusedBrsPruneCfg,
seen : @bitset.BitSet,
region_ref : @ir.HotRegionRef,
) -> Bool {
// Run postorder so ordinary RUB rewrites and inner structured cleanup retain
// their original inputs. Only the suffix is removed; the cutoff control root
// and its branch payload remain intact.
let roots : Array[Int32] = []
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))
}
for node_id in roots {
if remove_unused_brs_prune_one_nested_dead_suffix_node(
ctx, func, prune_cfg, seen, node_id,
) {
return true
}
}
if region_ref == @ir.HotRegionRef::root() {
return false
}
// Start with the exact lowering filler emitted by the known wrapper family:
// a direct nonfallthrough root followed by one unreachable placeholder in an
// ordinary block body. Earlier roots are preserved as ordered branch-payload
// or effect prefixes. Broader structured suffixes remain conservative until
// they have separate regressions.
let root_count = @ir.hot_region_root_count(func, region_ref)
if @ir.hot_region_holder_block_root(func, region_ref) is None ||
root_count < 2 {
return false
}
let cutoff_idx = root_count - 2
let cutoff = @ir.hot_region_get(func, region_ref, cutoff_idx)
let suffix = @ir.hot_region_get(func, region_ref, root_count - 1)
if cutoff < 0 ||
suffix < 0 ||
!@ir.hot_node_is_live(func, cutoff) ||
!@ir.hot_node_is_live(func, suffix) {
return false
}
match @ir.hot_node_get(func, suffix).op {
@ir.HotOp::Unreachable => ()
_ => return false
}
let holder = @ir.hot_region_holder_block_root(func, region_ref).unwrap()
let holder_label = @ir.hot_control_node_label(func, holder)
let is_direct_result_wrapper = remove_unused_brs_block_is_direct_result_wrapper_before_unreachable(
func, holder,
)
let eligible_cutoff = match @ir.hot_node_get(func, cutoff).op {
@ir.HotOp::Return => cutoff_idx == 0
@ir.HotOp::Br =>
if !is_direct_result_wrapper {
false
} else {
match @ir.hot_branch_target(func, cutoff) {
Some(target) => target != holder_label
None => false
}
}
@ir.HotOp::BrTable => {
if !is_direct_result_wrapper {
return false
}
let targets = @ir.hot_branch_table_targets_for_node(func, cutoff)
if targets.length() == 0 {
false
} else {
let mut all_outward = true
for target in targets {
if target == holder_label {
all_outward = false
break
}
}
all_outward
}
}
_ => false
}
if !eligible_cutoff ||
@ir.cfg_region_root_can_reach_next(
func,
remove_unused_brs_prune_cfg_get(prune_cfg, ctx, func),
region_ref,
cutoff_idx,
) {
return false
}
let removed = pass_splice_region(ctx, func, region_ref, root_count - 1, 1, [])
remove_unused_brs_delete_unused_nodes(ctx, func, removed)
true
}
///|
fn remove_unused_brs_prune_one_nested_dead_suffix(
ctx : HotPassContext,
func : @ir.HotFunc,
prune_cfg : RemoveUnusedBrsPruneCfg,
region_ref : @ir.HotRegionRef,
) -> Bool {
remove_unused_brs_prune_one_nested_dead_suffix_with_seen(
ctx,
func,
prune_cfg,
@bitset.BitSet::new(@ir.hot_node_count(func)),
region_ref,
)
}
///|
fn remove_unused_brs_region_roots(
func : @ir.HotFunc,
region_ref : @ir.HotRegionRef,
) -> Array[Int32] {
let roots : Array[Int32] = []
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 remove_unused_brs_expand_payload_children(
func : @ir.HotFunc,
payload_roots : Array[Int32],
result_arity : Int32,
) -> Array[Int32]? {
let values : Array[Int32] = []
let mut seen_arity = 0
for root_id in payload_roots {
let root_arity = @ir.hot_node_result_arity(func, root_id)
if root_arity <= 0 {
return None
}
for dup_idx = 0; dup_idx < root_arity; dup_idx = dup_idx + 1 {
let _ = dup_idx
values.push(root_id)
}
seen_arity += root_arity
}
if seen_arity != result_arity {
return None
}
Some(values)
}
///|
fn remove_unused_brs_region_roots_are_void_context_safe(
func : @ir.HotFunc,
region_ref : @ir.HotRegionRef,
) -> Bool {
for idx = 0; idx < @ir.hot_region_root_count(func, region_ref); idx = idx + 1 {
let root_id = @ir.hot_region_get(func, region_ref, idx)
if root_id >= 0 &&
@ir.hot_node_is_live(func, root_id) &&
@ir.hot_node_result_arity(func, root_id) != 0 {
return false
}
}
true
}
///|
fn remove_unused_brs_build_tail_return_if_arm_body(
func : @ir.HotFunc,
arm_ref : @ir.HotRegionRef,
return_arity : Int32,
) -> Array[Int32]? {
if remove_unused_brs_tail_is_nonfallthrough(func, arm_ref) {
if !remove_unused_brs_region_roots_are_void_context_safe(func, arm_ref) {
return None
}
return Some(remove_unused_brs_region_roots(func, arm_ref))
}
let (prefix, payload) = match
@ir.hot_region_split_trailing_payload_roots(func, arm_ref, return_arity) {
Some(split) => split
None => return None
}
let return_values = match
remove_unused_brs_expand_payload_children(func, payload, return_arity) {
Some(values) => values
None => return None
}
let body = prefix.copy()
body.push(@ir.hot_build_return(func, return_values))
Some(body)
}
///|
fn remove_unused_brs_try_voidify_tail_return_if(
ctx : HotPassContext,
func : @ir.HotFunc,
region_ref : @ir.HotRegionRef,
label_refs : Array[Int32],
) -> Bool {
if @ir.hot_region_root_count(func, region_ref) == 0 {
return false
}
let tail_idx = @ir.hot_region_root_count(func, region_ref) - 1
let tail_id = @ir.hot_region_get(func, region_ref, tail_idx)
let tail = @ir.hot_node_get(func, tail_id)
let (idx, remove_count, if_id, return_arity) = match tail.op {
@ir.HotOp::Return if tail.child_count >= 1 => {
let candidate = @ir.hot_child_get(func, tail_id, 0)
for child_idx = 1; child_idx < tail.child_count; child_idx = child_idx + 1 {
if @ir.hot_child_get(func, tail_id, child_idx) != candidate {
return false
}
}
(tail_idx, 1, candidate, tail.child_count)
}
@ir.HotOp::Return if tail.child_count == 0 && tail_idx > 0 => {
let value_id = @ir.hot_region_get(func, region_ref, tail_idx - 1)
(
tail_idx - 1,
2,
value_id,
@ir.hot_type_result_arity(func, @ir.hot_body_result_type(func)),
)
}
_ => return false
}
if if_id < 0 || !@ir.hot_node_is_live(func, if_id) {
return false
}
let if_node = @ir.hot_node_get(func, if_id)
match if_node.op {
@ir.HotOp::If =>
if @ir.hot_node_result_arity(func, if_id) == 0 {
return false
}
_ => return false
}
let else_ref = match @ir.hot_region_else_ref(func, if_id) {
Some(else_ref) => else_ref
None => return false
}
let then_ref = @ir.hot_region_then_ref(func, if_id)
let if_label = @ir.hot_control_node_label(func, if_id)
if remove_unused_brs_label_is_used(label_refs, if_label) {
return false
}
if return_arity == 1 &&
(
!remove_unused_brs_tail_is_nonfallthrough(func, then_ref) ||
!remove_unused_brs_tail_is_nonfallthrough(func, else_ref)
) {
return false
}
let then_body = match
remove_unused_brs_build_tail_return_if_arm_body(
func, then_ref, return_arity,
) {
Some(body) => body
None => return false
}
let else_body = match
remove_unused_brs_build_tail_return_if_arm_body(
func, else_ref, return_arity,
) {
Some(body) => body
None => return false
}
if then_body.is_empty() || else_body.is_empty() {
return false
}
if return_arity > 1 {
let return_cache : Array[Int32] = []
for root_id in then_body {
if remove_unused_brs_subtree_has_return(func, root_id, return_cache) {
return false
}
}
for root_id in else_body {
if remove_unused_brs_subtree_has_return(func, root_id, return_cache) {
return false
}
}
}
let replacement = @ir.hot_build_if(
func,
@lib.BlockType::void_(),
@ir.hot_child_get(func, if_id, 0),
then_body,
else_body=Some(else_body),
)
let unreachable_id = @ir.hot_build_unreachable(func)
let removed = pass_splice_region(ctx, func, region_ref, idx, remove_count, [
replacement, unreachable_id,
])
removed.push(if_id)
removed.push(then_ref.holder)
removed.push(else_ref.holder)
remove_unused_brs_delete_unused_nodes(ctx, func, removed)
true
}
///|
fn remove_unused_brs_try_voidify_exit_only_value_if(
ctx : HotPassContext,
func : @ir.HotFunc,
node_id : Int32,
) -> Bool {
if node_id < 0 || !@ir.hot_node_is_live(func, node_id) {
return false
}
let if_node = @ir.hot_node_get(func, node_id)
match if_node.op {
@ir.HotOp::If =>
if @ir.hot_node_result_arity(func, node_id) != 1 {
return false
}
_ => return false
}
let else_ref = match @ir.hot_region_else_ref(func, node_id) {
Some(else_ref) => else_ref
None => return false
}
let then_ref = @ir.hot_region_then_ref(func, node_id)
if @ir.hot_region_root_count(func, then_ref) != 1 ||
@ir.hot_region_root_count(func, else_ref) != 1 {
return false
}
let then_root = @ir.hot_region_get(func, then_ref, 0)
let else_root = @ir.hot_region_get(func, else_ref, 0)
let arm_is_carried_result_wrapper = fn(root_id : Int32) -> Bool {
if root_id < 0 || !@ir.hot_node_is_live(func, root_id) {
return false
}
if @ir.hot_node_result_arity(func, root_id) <= 0 {
return false
}
match @ir.hot_node_get(func, root_id).op {
@ir.HotOp::Block | @ir.HotOp::Loop => true
_ => false
}
}
if arm_is_carried_result_wrapper(then_root) ||
arm_is_carried_result_wrapper(else_root) {
return false
}
if @ir.hot_region_single_exit_root(func, then_ref) == None {
return false
}
if @ir.hot_region_single_exit_root(func, else_ref) == None {
return false
}
if !remove_unused_brs_has_safe_exit_only_value_if_use_chain(
ctx, func, node_id,
) {
return false
}
let void_if = @ir.hot_build_if(
func,
@lib.BlockType::void_(),
@ir.hot_child_get(func, node_id, 0),
remove_unused_brs_region_roots(func, then_ref),
else_body=Some(remove_unused_brs_region_roots(func, else_ref)),
)
let replacement = @ir.hot_build_block(
func,
@lib.BlockType::val_type(
@ir.hot_type_results(func, @ir.hot_node_type(func, node_id))[0],
),
[void_if, @ir.hot_build_unreachable(func)],
)
@ir.hot_control_demote_if_to_block(func, node_id, [
void_if,
@ir.hot_child_get(func, replacement, 1),
])
remove_unused_brs_mark_mutated(
ctx, func, "rewrite-value-if-single-exit-use-chain",
)
remove_unused_brs_delete_unused_nodes(ctx, func, [
replacement,
then_ref.holder,
else_ref.holder,
])
true
}
///|
fn remove_unused_brs_root_use_region_ref(
func : @ir.HotFunc,
node_id : Int32,
holder_id : Int32,
slot : Int32,
) -> @ir.HotRegionRef? {
let user = @ir.hot_node_get(func, holder_id)
for info in @ir.hot_control_region_slot_info(user.op) {
let region_ref = match info.role {
@ir.HotControlRegionRole::BodyRegion =>
Some(@ir.hot_region_body_ref(func, holder_id))
@ir.HotControlRegionRole::ThenRegion =>
Some(@ir.hot_region_then_ref(func, holder_id))
@ir.HotControlRegionRole::ElseRegion =>
@ir.hot_region_else_ref(func, holder_id)
@ir.HotControlRegionRole::CatchRegion =>
@ir.hot_region_catch_ref(func, holder_id)
@ir.HotControlRegionRole::CatchListRegion =>
Some(@ir.hot_region_catch_list_ref(func, holder_id))
}
match region_ref {
Some(region_ref) =>
if slot < @ir.hot_region_root_count(func, region_ref) &&
@ir.hot_region_get(func, region_ref, slot) == node_id {
return Some(region_ref)
}
None => ()
}
}
None
}
///|
fn remove_unused_brs_has_safe_exit_only_value_if_use_chain(
ctx : HotPassContext,
func : @ir.HotFunc,
node_id : Int32,
) -> Bool {
let use_def = pass_require_use_def(ctx, func)
let mut current = node_id
for hops = 0; hops < 64; hops = hops + 1 {
if @ir.node_use_count(use_def, current) != 1 {
return false
}
let sites = @ir.node_use_sites_borrowed(use_def, current)
if sites.length() != 1 {
return false
}
let site = sites[0]
if site.user == -1 {
return false
}
let user = @ir.hot_node_get(func, site.user)
match site.kind {
@ir.HotUseSlotKind::ChildUseSlot =>
match user.op {
@ir.HotOp::LocalTee => {
if site.slot != 0 {
return false
}
current = site.user
}
@ir.HotOp::Drop | @ir.HotOp::LocalSet => return site.slot == 0
@ir.HotOp::Br | @ir.HotOp::Return => return true
@ir.HotOp::BrIf | @ir.HotOp::BrTable =>
return site.slot < @ir.hot_child_count(func, site.user) - 1
_ => return false
}
@ir.HotUseSlotKind::RootUseSlot => {
let region_ref = match
remove_unused_brs_root_use_region_ref(
func,
current,
site.user,
site.slot,
) {
Some(region_ref) => region_ref
None => return false
}
if @ir.hot_node_result_arity(func, site.user) != 1 ||
site.slot != @ir.hot_region_root_count(func, region_ref) - 1 {
return false
}
current = site.user
}
}
}
false
}
///|
fn remove_unused_brs_try_remove_region_tail(
ctx : HotPassContext,
func : @ir.HotFunc,
region_ref : @ir.HotRegionRef,
reorder_cache : Array[Int32],
) -> Bool {
if @ir.hot_region_root_count(func, region_ref) == 0 {
return false
}
let tail_id = @ir.hot_region_get(
func,
region_ref,
@ir.hot_region_root_count(func, region_ref) - 1,
)
let tail = @ir.hot_node_get(func, tail_id)
let rewrite_tail_child = fn(child_id : Int32) -> Bool {
match
remove_unused_brs_match_value_if_to_select(
ctx, func, child_id, reorder_cache,
) {
Some(rewrite) => {
if !remove_unused_brs_splice_tail_roots(
ctx,
func,
region_ref,
tail_id,
[rewrite.replacement],
) {
return false
}
let nop_node = @ir.hot_node_get(func, @ir.hot_build_nop(func))
@ir.hot_replace_node(func, child_id, nop_node)
@ir.hot_replace_node(func, rewrite.then_holder, nop_node)
@ir.hot_replace_node(func, rewrite.else_holder, nop_node)
true
}
None =>
remove_unused_brs_splice_tail_roots(ctx, func, region_ref, tail_id, [
child_id,
])
}
}
match remove_unused_brs_tail_target(func, region_ref) {
ReturnTarget =>
match tail.op {
@ir.HotOp::Return => {
let result_arity = if region_ref == @ir.HotRegionRef::root() {
@ir.hot_type_result_arity(func, @ir.hot_body_result_type(func))
} else {
@ir.hot_region_result_arity(func, region_ref)
}
if result_arity > 0 &&
region_ref == @ir.HotRegionRef::root() &&
@ir.hot_region_root_count(func, region_ref) > 1 {
return false
}
if tail.child_count == 0 {
if result_arity > 0 &&
@ir.hot_region_root_count(func, region_ref) > result_arity + 1 {
return false
}
let changed = remove_unused_brs_splice_tail_roots(
ctx,
func,
region_ref,
tail_id,
[],
)
changed
} else if tail.child_count == 1 {
rewrite_tail_child(@ir.hot_child_get(func, tail_id, 0))
} else {
let changed = remove_unused_brs_splice_tail_with_children(
ctx, func, region_ref, tail_id,
)
changed
}
}
_ => false
}
BranchTarget(target) => {
if tail.child_count > 0 &&
@ir.hot_region_result_arity(func, region_ref) == 0 {
return false
}
match tail.op {
@ir.HotOp::Br if tail.imm0 == target => {
let result_arity = @ir.hot_region_result_arity(func, region_ref)
if result_arity > 0 && @ir.hot_region_root_count(func, region_ref) > 1 {
return false
}
if tail.child_count == 0 {
if result_arity == 0 {
return false
}
let changed = remove_unused_brs_splice_tail_roots(
ctx,
func,
region_ref,
tail_id,
[],
)
changed
} else if tail.child_count == 1 {
rewrite_tail_child(@ir.hot_child_get(func, tail_id, 0))
} else {
let changed = remove_unused_brs_splice_tail_with_children(
ctx, func, region_ref, tail_id,
)
changed
}
}
_ => false
}
}
NoTarget => false
}
}
///|
fn remove_unused_brs_region_tail_control_payload_count(
func : @ir.HotFunc,
region_ref : @ir.HotRegionRef,
target : RemoveUnusedBrsTailTarget,
expected_payloads : Int32,
) -> Int32? {
if @ir.hot_region_root_count(func, region_ref) == 0 {
return None
}
let tail_id = @ir.hot_region_get(
func,
region_ref,
@ir.hot_region_root_count(func, region_ref) - 1,
)
let tail = @ir.hot_node_get(func, tail_id)
let stack_payloads = fn() -> Int32? {
if expected_payloads == 0 {
return Some(0)
}
let roots = remove_unused_brs_region_roots(func, region_ref)
if roots.is_empty() {
return None
}
let payload_roots : Array[Int32] = []
for idx = 0; idx < roots.length() - 1; idx = idx + 1 {
payload_roots.push(roots[idx])
}
match
@ir.hot_split_trailing_payload_roots(
func, payload_roots, expected_payloads,
) {
Some(_) => Some(expected_payloads)
None => None
}
}
let child_payloads = fn() -> Int32? {
if tail.child_count > 1 {
match @ir.hot_node_repeated_live_child(func, tail_id) {
Some(first_id) =>
if @ir.hot_node_result_arity(func, first_id) == expected_payloads {
return Some(expected_payloads)
}
None => ()
}
}
let mut payloads = 0
for child_idx = 0; child_idx < tail.child_count; child_idx = child_idx + 1 {
let child_id = @ir.hot_child_get(func, tail_id, child_idx)
if child_id < 0 || !@ir.hot_node_is_live(func, child_id) {
return None
}
payloads += @ir.hot_node_result_arity(func, child_id)
}
Some(payloads)
}
match target {
ReturnTarget =>
match tail.op {
@ir.HotOp::Return =>
if tail.child_count > 0 {
child_payloads()
} else {
stack_payloads()
}
_ => None
}
BranchTarget(target) =>
match tail.op {
@ir.HotOp::Br if tail.imm0 == target =>
if tail.child_count > 0 {
child_payloads()
} else {
stack_payloads()
}
_ => None
}
NoTarget => None
}
}
///|
fn remove_unused_brs_strip_tail_control(
ctx : HotPassContext,
func : @ir.HotFunc,
region_ref : @ir.HotRegionRef,
target : RemoveUnusedBrsTailTarget,
) -> Bool {
let tail_id = @ir.hot_region_get(
func,
region_ref,
@ir.hot_region_root_count(func, region_ref) - 1,
)
let tail = @ir.hot_node_get(func, tail_id)
match target {
ReturnTarget =>
match tail.op {
@ir.HotOp::Return => ()
_ => return false
}
BranchTarget(label_id) =>
match tail.op {
@ir.HotOp::Br if tail.imm0 == label_id => ()
_ => return false
}
NoTarget => return false
}
if tail.child_count == 0 {
remove_unused_brs_replace_with_nop(ctx, func, tail_id)
} else if tail.child_count == 1 {
remove_unused_brs_replace_with_child(
ctx,
func,
tail_id,
@ir.hot_child_get(func, tail_id, 0),
)
} else {
remove_unused_brs_splice_tail_with_children(ctx, func, region_ref, tail_id)
}
}
///|
fn remove_unused_brs_try_strip_tail_if_exits(
ctx : HotPassContext,
func : @ir.HotFunc,
region_ref : @ir.HotRegionRef,
) -> Bool {
if @ir.hot_region_root_count(func, region_ref) == 0 {
return false
}
let tail_id = @ir.hot_region_get(
func,
region_ref,
@ir.hot_region_root_count(func, region_ref) - 1,
)
let if_node = @ir.hot_node_get(func, tail_id)
match if_node.op {
@ir.HotOp::If => ()
_ => return false
}
let then_ref = @ir.hot_region_then_ref(func, tail_id)
let else_ref = match @ir.hot_region_else_ref(func, tail_id) {
Some(else_ref) => else_ref
None => return false
}
let target = remove_unused_brs_tail_target(func, region_ref)
match target {
NoTarget => return false
_ => ()
}
let result_arity = @ir.hot_node_result_arity(func, tail_id)
let then_payloads = match
remove_unused_brs_region_tail_control_payload_count(
func, then_ref, target, result_arity,
) {
Some(count) => count
None => return false
}
let else_payloads = match
remove_unused_brs_region_tail_control_payload_count(
func, else_ref, target, result_arity,
) {
Some(count) => count
None => return false
}
if then_payloads != else_payloads || then_payloads != result_arity {
return false
}
let mut changed = remove_unused_brs_strip_tail_control(
ctx, func, then_ref, target,
)
changed = remove_unused_brs_strip_tail_control(ctx, func, else_ref, target) ||
changed
changed = remove_unused_brs_trim_trailing_nops(
ctx,
func,
then_ref,
preserve_single_if_arm_nop=true,
) ||
changed
changed = remove_unused_brs_trim_trailing_nops(
ctx,
func,
else_ref,
preserve_single_if_arm_nop=true,
) ||
changed
changed
}
///|
fn remove_unused_brs_region_result_arity(
func : @ir.HotFunc,
region_ref : @ir.HotRegionRef,
) -> Int32? {
if region_ref == @ir.HotRegionRef::root() {
return Some(@ir.hot_type_result_arity(func, @ir.hot_body_result_type(func)))
}
Some(@ir.hot_region_result_arity(func, region_ref))
}
///|
fn remove_unused_brs_try_strip_return_context_tail(
ctx : HotPassContext,
func : @ir.HotFunc,
region_ref : @ir.HotRegionRef,
) -> Bool {
let expected_payloads = match
remove_unused_brs_region_result_arity(func, region_ref) {
Some(arity) => arity
None => return false
}
let tail_id = remove_unused_brs_region_tail_id(func, region_ref)
if tail_id < 0 || !@ir.hot_node_is_live(func, tail_id) {
return false
}
let tail = @ir.hot_node_get(func, tail_id)
if expected_payloads > 0 &&
region_ref == @ir.HotRegionRef::root() &&
@ir.hot_region_root_count(func, region_ref) > 1 {
return false
}
if expected_payloads > 0 &&
tail.child_count == 0 &&
region_ref == @ir.HotRegionRef::root() {
return false
}
if expected_payloads > 0 &&
tail.child_count == 0 &&
@ir.hot_region_root_count(func, region_ref) > expected_payloads + 1 {
return false
}
let payloads = match
remove_unused_brs_region_tail_control_payload_count(
func,
region_ref,
ReturnTarget,
expected_payloads,
) {
Some(payloads) => payloads
None => return false
}
if payloads != expected_payloads {
return false
}
let changed = remove_unused_brs_strip_tail_control(
ctx,
func,
region_ref,
ReturnTarget,
)
changed
}
///|
fn remove_unused_brs_try_rewrite_if_br(
ctx : HotPassContext,
func : @ir.HotFunc,
node_id : Int32,
reorder_cache : Array[Int32],
) -> Bool {
let node = @ir.hot_node_get(func, node_id)
match node.op {
@ir.HotOp::If =>
if @ir.hot_node_result_arity(func, node_id) != 0 {
return false
}
_ => return false
}
match @ir.hot_region_else_ref(func, node_id) {
Some(_) => return false
None => ()
}
let then_region = @ir.hot_region_then_ref(func, node_id)
if @ir.hot_region_root_count(func, then_region) != 1 {
return false
}
let then_root = @ir.hot_region_get(func, then_region, 0)
let then_node = @ir.hot_node_get(func, then_root)
let condition = @ir.hot_child_get(func, node_id, 0)
let replacement = match then_node.op {
@ir.HotOp::Br => {
if then_node.child_count != 0 {
return false
}
if @ir.hot_node_count(func) >= 256 &&
!remove_unused_brs_node_is_reorder_safe(
ctx, func, condition, reorder_cache,
) {
return false
}
@ir.hot_build_br_if(func, then_node.imm0, condition)
}
@ir.HotOp::BrIf => {
if then_node.child_count != 1 {
return false
}
let inner_condition = @ir.hot_child_get(func, then_root, 0)
if !remove_unused_brs_node_is_reorder_safe(
ctx, func, inner_condition, reorder_cache,
) ||
!remove_unused_brs_node_is_reorder_safe(
ctx, func, condition, reorder_cache,
) {
return false
}
let zero = @ir.hot_build_const_i32(func, @lib.I32(0))
let combined = remove_unused_brs_build_select(
func,
@lib.ValType::i32(),
inner_condition,
zero,
condition,
)
@ir.hot_build_br_if(func, then_node.imm0, combined)
}
_ => return false
}
@ir.hot_control_demote_if_to_block(func, node_id, [replacement])
remove_unused_brs_mark_mutated(
ctx, func, "rewrite-return-child-br_if-to-combined-guard",
)
remove_unused_brs_delete_unused_nodes(ctx, func, [
then_region.holder,
then_root,
])
true
}
///|
fn remove_unused_brs_try_inline_single_br_if_block(
ctx : HotPassContext,
func : @ir.HotFunc,
region_ref : @ir.HotRegionRef,
idx : Int32,
label_refs : Array[Int32],
) -> Bool {
if idx < 0 || idx >= @ir.hot_region_root_count(func, region_ref) {
return false
}
let block_id = @ir.hot_region_get(func, region_ref, idx)
if block_id < 0 || !@ir.hot_node_is_live(func, block_id) {
return false
}
match @ir.hot_node_get(func, block_id).op {
@ir.HotOp::Block =>
if @ir.hot_node_result_arity(func, block_id) != 0 {
return false
}
_ => return false
}
let block_label = @ir.hot_control_node_label(func, block_id)
if remove_unused_brs_label_is_used(label_refs, block_label) {
return false
}
let body_ref = @ir.hot_region_body_ref(func, block_id)
if @ir.hot_region_root_count(func, body_ref) != 1 {
return false
}
let body_root = @ir.hot_region_get(func, body_ref, 0)
match @ir.hot_node_get(func, body_root).op {
@ir.HotOp::Br | @ir.HotOp::BrIf => ()
_ => return false
}
ignore(pass_splice_region(ctx, func, region_ref, idx, 1, [body_root]))
remove_unused_brs_mark_mutated(ctx, func, "inline-single-br-if-block")
true
}
///|
priv enum RemoveUnusedBrsLargeSeen {
Dense(Array[Bool])
Sparse(@hashmap.HashMap[Int32, Bool])
}
///|
priv struct RemoveUnusedBrsSparseSeen {
node_count : Int32
small : Array[Int32]
mut large : RemoveUnusedBrsLargeSeen?
}
///|
fn RemoveUnusedBrsSparseSeen::new(
node_count : Int32,
) -> RemoveUnusedBrsSparseSeen {
{ node_count, small: [], large: None, }
}
///|
fn RemoveUnusedBrsSparseSeen::mark_if_new(
seen : RemoveUnusedBrsSparseSeen,
node_id : Int32,
) -> Bool {
match seen.large {
Some(RemoveUnusedBrsLargeSeen::Dense(flags)) => {
if flags[node_id] {
return false
}
flags[node_id] = true
true
}
Some(RemoveUnusedBrsLargeSeen::Sparse(index)) => {
if index.get(node_id) is Some(_) {
return false
}
index[node_id] = true
true
}
None => {
if seen.small.contains(node_id) {
return false
}
seen.small.push(node_id)
if seen.small.length() == 16 {
if seen.node_count <= 16384 {
let flags = Array::make(seen.node_count, false)
for prior in seen.small {
flags[prior] = true
}
seen.large = Some(RemoveUnusedBrsLargeSeen::Dense(flags))
} else {
let index : @hashmap.HashMap[Int32, Bool] = @hashmap.new()
for prior in seen.small {
index[prior] = true
}
seen.large = Some(RemoveUnusedBrsLargeSeen::Sparse(index))
}
}
true
}
}
}
///|
fn remove_unused_brs_collect_simple_branches_to_label_dense(
func : @ir.HotFunc,
node_id : Int32,
target_label : Int32,
seen : Array[Bool],
out : Array[Int32],
) -> Unit {
if node_id < 0 || node_id >= seen.length() || seen[node_id] {
return
}
if !@ir.hot_node_is_live(func, node_id) {
return
}
seen[node_id] = true
let node = @ir.hot_node_get(func, node_id)
match node.op {
@ir.HotOp::Br => {
if node.imm0 == target_label && node.child_count == 0 {
out.push(node_id)
}
return
}
_ => ()
}
for slot = 0; slot < @ir.hot_child_count(func, node_id); slot = slot + 1 {
if remove_unused_brs_is_region_child_slot(node.op, slot) {
continue
}
remove_unused_brs_collect_simple_branches_to_label_dense(
func,
@ir.hot_child_get(func, node_id, slot),
target_label,
seen,
out,
)
}
let scan_region = fn(region_ref : @ir.HotRegionRef) -> Unit {
for idx = 0
idx < @ir.hot_region_root_count(func, region_ref)
idx = idx + 1 {
remove_unused_brs_collect_simple_branches_to_label_dense(
func,
@ir.hot_region_get(func, region_ref, idx),
target_label,
seen,
out,
)
}
}
for info in @ir.hot_control_region_slot_info(node.op) {
match info.role {
@ir.HotControlRegionRole::BodyRegion =>
scan_region(@ir.hot_region_body_ref(func, node_id))
@ir.HotControlRegionRole::ThenRegion =>
scan_region(@ir.hot_region_then_ref(func, node_id))
@ir.HotControlRegionRole::ElseRegion =>
match @ir.hot_region_else_ref(func, node_id) {
Some(else_ref) => scan_region(else_ref)
None => ()
}
@ir.HotControlRegionRole::CatchRegion =>
match @ir.hot_region_catch_ref(func, node_id) {
Some(catch_ref) => scan_region(catch_ref)
None => ()
}
@ir.HotControlRegionRole::CatchListRegion =>
scan_region(@ir.hot_region_catch_list_ref(func, node_id))
}
}
}
///|
fn remove_unused_brs_collect_simple_branches_to_label_sparse(
func : @ir.HotFunc,
node_id : Int32,
target_label : Int32,
seen : RemoveUnusedBrsSparseSeen,
out : Array[Int32],
) -> Unit {
if node_id < 0 || node_id >= @ir.hot_node_count(func) {
return
}
if !@ir.hot_node_is_live(func, node_id) {
return
}
if !seen.mark_if_new(node_id) {
return
}
let node = @ir.hot_node_get(func, node_id)
match node.op {
@ir.HotOp::Br => {
if node.imm0 == target_label && node.child_count == 0 {
out.push(node_id)
}
return
}
_ => ()
}
for slot = 0; slot < @ir.hot_child_count(func, node_id); slot = slot + 1 {
if remove_unused_brs_is_region_child_slot(node.op, slot) {
continue
}
remove_unused_brs_collect_simple_branches_to_label_sparse(
func,
@ir.hot_child_get(func, node_id, slot),
target_label,
seen,
out,
)
}
let scan_region = fn(region_ref : @ir.HotRegionRef) -> Unit {
for idx = 0
idx < @ir.hot_region_root_count(func, region_ref)
idx = idx + 1 {
remove_unused_brs_collect_simple_branches_to_label_sparse(
func,
@ir.hot_region_get(func, region_ref, idx),
target_label,
seen,
out,
)
}
}
for info in @ir.hot_control_region_slot_info(node.op) {
match info.role {
@ir.HotControlRegionRole::BodyRegion =>
scan_region(@ir.hot_region_body_ref(func, node_id))
@ir.HotControlRegionRole::ThenRegion =>
scan_region(@ir.hot_region_then_ref(func, node_id))
@ir.HotControlRegionRole::ElseRegion =>
match @ir.hot_region_else_ref(func, node_id) {
Some(else_ref) => scan_region(else_ref)
None => ()
}
@ir.HotControlRegionRole::CatchRegion =>
match @ir.hot_region_catch_ref(func, node_id) {
Some(catch_ref) => scan_region(catch_ref)
None => ()
}
@ir.HotControlRegionRole::CatchListRegion =>
scan_region(@ir.hot_region_catch_list_ref(func, node_id))
}
}
}
///|
fn remove_unused_brs_collect_simple_branches_to_label(
func : @ir.HotFunc,
node_id : Int32,
target_label : Int32,
out : Array[Int32],
) -> Unit {
let node_count = @ir.hot_node_count(func)
let broad_block = node_id >= 0 &&
node_id < node_count &&
@ir.hot_node_get(func, node_id).op is @ir.HotOp::Block &&
@ir.hot_region_root_count(func, @ir.hot_region_body_ref(func, node_id)) >=
16
if node_count <= 256 || broad_block {
let seen = Array::make(node_count, false)
remove_unused_brs_collect_simple_branches_to_label_dense(
func, node_id, target_label, seen, out,
)
} else {
let seen = RemoveUnusedBrsSparseSeen::new(node_count)
remove_unused_brs_collect_simple_branches_to_label_sparse(
func, node_id, target_label, seen, out,
)
}
}
///|
fn remove_unused_brs_branch_table_has_mostly_default_switch_shape(
func : @ir.HotFunc,
node_id : Int32,
) -> Bool {
let node = @ir.hot_node_get(func, node_id)
match node.op {
@ir.HotOp::BrTable => ()
_ => return false
}
let default_target = @ir.hot_branch_table_default_target(func, node.imm0)
let mut targets = @ir.hot_branch_table_targets(func, node.imm0).copy()
while targets.length() > 0 && targets[targets.length() - 1] == default_target {
ignore(targets.pop())
}
let mut leading_defaults = 0
while leading_defaults < targets.length() &&
targets[leading_defaults] == default_target {
leading_defaults = leading_defaults + 1
}
if leading_defaults > 0 {
let shifted : Array[Int32] = []
for i = leading_defaults; i < targets.length(); i = i + 1 {
shifted.push(targets[i])
}
targets = shifted
}
if targets.length() < 3 {
return false
}
for i = 1; i < targets.length() - 1; i = i + 1 {
if targets[i] != default_target {
return false
}
}
true
}
///|
fn remove_unused_brs_branch_table_can_retarget_without_payload(
func : @ir.HotFunc,
node_id : Int32,
) -> Bool {
let node = @ir.hot_node_get(func, node_id)
match node.op {
@ir.HotOp::BrTable =>
if node.child_count != 0 && node.child_count != 1 {
return false
}
_ => return false
}
let table_targets = @ir.hot_branch_table_targets(func, node.imm0)
if table_targets.length() > 9 &&
remove_unused_brs_branch_table_has_mostly_default_switch_shape(
func, node_id,
) {
return false
}
let default_target = @ir.hot_branch_table_default_target(func, node.imm0)
if @ir.hot_label_branch_arity(func, default_target) != 0 {
return false
}
for target in table_targets {
if @ir.hot_label_branch_arity(func, target) != 0 {
return false
}
}
true
}
///|
fn remove_unused_brs_branch_table_references_label(
func : @ir.HotFunc,
node_id : Int32,
target_label : Int32,
) -> Bool {
let node = @ir.hot_node_get(func, node_id)
if @ir.hot_branch_table_default_target(func, node.imm0) == target_label {
return true
}
for target in @ir.hot_branch_table_targets(func, node.imm0) {
if target == target_label {
return true
}
}
false
}
///|
fn remove_unused_brs_try_table_references_label(
func : @ir.HotFunc,
node_id : Int32,
target_label : Int32,
) -> Bool {
let node = @ir.hot_node_get(func, node_id)
let info = @ir.hot_catch_info_get(func, node.imm1)
for catch_ in info.catches {
let label_id = match catch_ {
@ir.HotCatchArm::CatchArm(_, label_id)
| @ir.HotCatchArm::CatchRefArm(_, label_id)
| @ir.HotCatchArm::CatchAllArm(label_id)
| @ir.HotCatchArm::CatchAllRefArm(label_id) => label_id
}
if label_id == target_label {
return true
}
}
false
}
///|
fn remove_unused_brs_collect_retargetable_branches_to_label(
func : @ir.HotFunc,
node_id : Int32,
target_label : Int32,
seen : Array[Bool],
out : Array[Int32],
allow_direct_br : Bool,
allow_branch_table : Bool,
) -> Unit {
if node_id < 0 || node_id >= seen.length() || seen[node_id] {
return
}
if !@ir.hot_node_is_live(func, node_id) {
return
}
seen[node_id] = true
let node = @ir.hot_node_get(func, node_id)
match node.op {
@ir.HotOp::Br => {
if allow_direct_br && node.imm0 == target_label && node.child_count == 0 {
out.push(node_id)
}
return
}
@ir.HotOp::BrIf =>
if node.imm0 == target_label && node.child_count == 1 {
out.push(node_id)
}
@ir.HotOp::BrTable =>
if allow_branch_table &&
remove_unused_brs_branch_table_can_retarget_without_payload(
func, node_id,
) &&
remove_unused_brs_branch_table_references_label(
func, node_id, target_label,
) {
out.push(node_id)
}
@ir.HotOp::TryTable =>
if allow_branch_table &&
remove_unused_brs_try_table_references_label(
func, node_id, target_label,
) {
out.push(node_id)
}
_ => ()
}
for slot = 0; slot < @ir.hot_child_count(func, node_id); slot = slot + 1 {
if remove_unused_brs_is_region_child_slot(node.op, slot) {
continue
}
remove_unused_brs_collect_retargetable_branches_to_label(
func,
@ir.hot_child_get(func, node_id, slot),
target_label,
seen,
out,
allow_direct_br,
allow_branch_table,
)
}
let scan_region = fn(
region_ref : @ir.HotRegionRef,
region_allow_direct_br : Bool,
region_allow_branch_table : Bool,
) -> Unit {
for idx = 0
idx < @ir.hot_region_root_count(func, region_ref)
idx = idx + 1 {
remove_unused_brs_collect_retargetable_branches_to_label(
func,
@ir.hot_region_get(func, region_ref, idx),
target_label,
seen,
out,
region_allow_direct_br,
region_allow_branch_table,
)
}
}
for info in @ir.hot_control_region_slot_info(node.op) {
match info.role {
@ir.HotControlRegionRole::BodyRegion =>
scan_region(
@ir.hot_region_body_ref(func, node_id),
allow_direct_br,
allow_branch_table,
)
@ir.HotControlRegionRole::ThenRegion =>
scan_region(
@ir.hot_region_then_ref(func, node_id),
false,
allow_branch_table,
)
@ir.HotControlRegionRole::ElseRegion =>
match @ir.hot_region_else_ref(func, node_id) {
Some(else_ref) => scan_region(else_ref, false, allow_branch_table)
None => ()
}
@ir.HotControlRegionRole::CatchRegion =>
match @ir.hot_region_catch_ref(func, node_id) {
Some(catch_ref) => scan_region(catch_ref, false, false)
None => ()
}
@ir.HotControlRegionRole::CatchListRegion =>
scan_region(@ir.hot_region_catch_list_ref(func, node_id), false, false)
}
}
}
///|
fn remove_unused_brs_retarget_try_table_catches_in_subtree(
func : @ir.HotFunc,
node_id : Int32,
from_label : Int32,
to_label : Int32,
seen : Array[Bool],
) -> Int32 {
if node_id < 0 || node_id >= seen.length() || seen[node_id] {
return 0
}
if !@ir.hot_node_is_live(func, node_id) {
return 0
}
seen[node_id] = true
let node = @ir.hot_node_get(func, node_id)
let mut retargeted = 0
match node.op {
@ir.HotOp::TryTable => {
let info = @ir.hot_catch_info_get(func, node.imm1)
let rewritten_catches : Array[@ir.HotCatchArm] = []
let mut changed = false
for catch_ in info.catches {
match catch_ {
@ir.HotCatchArm::CatchArm(tag_idx, label_id) => {
let next_label = if label_id == from_label {
changed = true
to_label
} else {
label_id
}
rewritten_catches.push(@ir.HotCatchArm::catch_(tag_idx, next_label))
}
@ir.HotCatchArm::CatchRefArm(tag_idx, label_id) => {
let next_label = if label_id == from_label {
changed = true
to_label
} else {
label_id
}
rewritten_catches.push(
@ir.HotCatchArm::catch_ref(tag_idx, next_label),
)
}
@ir.HotCatchArm::CatchAllArm(label_id) => {
let next_label = if label_id == from_label {
changed = true
to_label
} else {
label_id
}
rewritten_catches.push(@ir.HotCatchArm::all(next_label))
}
@ir.HotCatchArm::CatchAllRefArm(label_id) => {
let next_label = if label_id == from_label {
changed = true
to_label
} else {
label_id
}
rewritten_catches.push(@ir.HotCatchArm::all_ref(next_label))
}
}
}
if changed {
@ir.hot_try_table_catches_set(func, node_id, rewritten_catches)
retargeted = retargeted + 1
}
}
_ => ()
}
for slot = 0; slot < @ir.hot_child_count(func, node_id); slot = slot + 1 {
if remove_unused_brs_is_region_child_slot(node.op, slot) {
continue
}
retargeted = retargeted +
remove_unused_brs_retarget_try_table_catches_in_subtree(
func,
@ir.hot_child_get(func, node_id, slot),
from_label,
to_label,
seen,
)
}
let scan_region = fn(region_ref : @ir.HotRegionRef) -> Unit {
for idx = 0
idx < @ir.hot_region_root_count(func, region_ref)
idx = idx + 1 {
retargeted = retargeted +
remove_unused_brs_retarget_try_table_catches_in_subtree(
func,
@ir.hot_region_get(func, region_ref, idx),
from_label,
to_label,
seen,
)
}
}
for info in @ir.hot_control_region_slot_info(node.op) {
match info.role {
@ir.HotControlRegionRole::BodyRegion =>
scan_region(@ir.hot_region_body_ref(func, node_id))
@ir.HotControlRegionRole::ThenRegion =>
scan_region(@ir.hot_region_then_ref(func, node_id))
@ir.HotControlRegionRole::ElseRegion =>
match @ir.hot_region_else_ref(func, node_id) {
Some(else_ref) => scan_region(else_ref)
None => ()
}
@ir.HotControlRegionRole::CatchRegion =>
match @ir.hot_region_catch_ref(func, node_id) {
Some(catch_ref) => scan_region(catch_ref)
None => ()
}
@ir.HotControlRegionRole::CatchListRegion =>
scan_region(@ir.hot_region_catch_list_ref(func, node_id))
}
}
retargeted
}
///|
fn remove_unused_brs_retarget_retargetable_branches(
func : @ir.HotFunc,
from_label : Int32,
to_label : Int32,
) -> Int32 {
if from_label < 0 ||
to_label < 0 ||
from_label == to_label ||
@ir.hot_label_branch_arity(func, from_label) != 0 ||
@ir.hot_label_branch_arity(func, to_label) != 0 {
return 0
}
let targets : Array[Int32] = []
let seen = Array::make(@ir.hot_node_count(func), false)
for idx = 0
idx < @ir.hot_region_root_count(func, @ir.HotRegionRef::root())
idx = idx + 1 {
remove_unused_brs_collect_retargetable_branches_to_label(
func,
@ir.hot_region_get(func, @ir.HotRegionRef::root(), idx),
from_label,
seen,
targets,
true,
true,
)
}
let mut retargeted = 0
for target in targets {
if target < 0 || !@ir.hot_node_is_live(func, target) {
continue
}
let node = @ir.hot_node_get(func, target)
match node.op {
@ir.HotOp::Br | @ir.HotOp::BrIf => {
@ir.hot_branch_target_set(func, target, to_label)
retargeted = retargeted + 1
}
@ir.HotOp::BrTable => {
let rewritten_targets : Array[Int32] = []
let mut changed = false
for label in @ir.hot_branch_table_targets(func, node.imm0) {
if label == from_label {
rewritten_targets.push(to_label)
changed = true
} else {
rewritten_targets.push(label)
}
}
let old_default = @ir.hot_branch_table_default_target(func, node.imm0)
let new_default = if old_default == from_label {
changed = true
to_label
} else {
old_default
}
if changed {
@ir.hot_branch_table_set(func, target, rewritten_targets, new_default)
retargeted = retargeted + 1
}
}
@ir.HotOp::TryTable => {
let info = @ir.hot_catch_info_get(func, node.imm1)
let rewritten_catches : Array[@ir.HotCatchArm] = []
let mut changed = false
for catch_ in info.catches {
match catch_ {
@ir.HotCatchArm::CatchArm(tag_idx, label_id) => {
let next_label = if label_id == from_label {
changed = true
to_label
} else {
label_id
}
rewritten_catches.push(
@ir.HotCatchArm::catch_(tag_idx, next_label),
)
}
@ir.HotCatchArm::CatchRefArm(tag_idx, label_id) => {
let next_label = if label_id == from_label {
changed = true
to_label
} else {
label_id
}
rewritten_catches.push(
@ir.HotCatchArm::catch_ref(tag_idx, next_label),
)
}
@ir.HotCatchArm::CatchAllArm(label_id) => {
let next_label = if label_id == from_label {
changed = true
to_label
} else {
label_id
}
rewritten_catches.push(@ir.HotCatchArm::all(next_label))
}
@ir.HotCatchArm::CatchAllRefArm(label_id) => {
let next_label = if label_id == from_label {
changed = true
to_label
} else {
label_id
}
rewritten_catches.push(@ir.HotCatchArm::all_ref(next_label))
}
}
}
if changed {
@ir.hot_try_table_catches_set(func, target, rewritten_catches)
retargeted = retargeted + 1
}
}
_ => ()
}
}
let catch_seen = Array::make(@ir.hot_node_count(func), false)
for idx = 0
idx < @ir.hot_region_root_count(func, @ir.HotRegionRef::root())
idx = idx + 1 {
retargeted = retargeted +
remove_unused_brs_retarget_try_table_catches_in_subtree(
func,
@ir.hot_region_get(func, @ir.HotRegionRef::root(), idx),
from_label,
to_label,
catch_seen,
)
}
retargeted
}
///|
fn remove_unused_brs_try_thread_try_table_catches_through_one_child_block(
ctx : HotPassContext,
func : @ir.HotFunc,
block_id : Int32,
) -> Bool {
if block_id < 0 || !@ir.hot_node_is_live(func, block_id) {
return false
}
match @ir.hot_node_get(func, block_id).op {
@ir.HotOp::Block =>
if @ir.hot_node_result_arity(func, block_id) != 0 {
return false
}
_ => return false
}
let block_label = @ir.hot_control_node_label(func, block_id)
if block_label < 0 || @ir.hot_label_branch_arity(func, block_label) != 0 {
return false
}
let body_ref = @ir.hot_region_body_ref(func, block_id)
if @ir.hot_region_root_count(func, body_ref) != 1 {
return false
}
let child_id = @ir.hot_region_get(func, body_ref, 0)
if child_id < 0 || !@ir.hot_node_is_live(func, child_id) {
return false
}
match @ir.hot_node_get(func, child_id).op {
@ir.HotOp::Block =>
if @ir.hot_node_result_arity(func, child_id) != 0 {
return false
}
_ => return false
}
let child_label = @ir.hot_control_node_label(func, child_id)
if child_label < 0 || @ir.hot_label_branch_arity(func, child_label) != 0 {
return false
}
let seen = Array::make(@ir.hot_node_count(func), false)
if remove_unused_brs_retarget_try_table_catches_in_subtree(
func, block_id, child_label, block_label, seen,
) ==
0 {
return false
}
remove_unused_brs_mark_mutated(ctx, func, "jump-thread-catch-one-child-block")
true
}
///|
fn remove_unused_brs_try_thread_jump_through_block(
ctx : HotPassContext,
func : @ir.HotFunc,
region_ref : @ir.HotRegionRef,
idx : Int32,
label_refs : Array[Int32],
) -> Bool {
if idx < 0 || idx >= @ir.hot_region_root_count(func, region_ref) {
return false
}
let block_id = @ir.hot_region_get(func, region_ref, idx)
if block_id < 0 || !@ir.hot_node_is_live(func, block_id) {
return false
}
match @ir.hot_node_get(func, block_id).op {
@ir.HotOp::Block =>
if @ir.hot_node_result_arity(func, block_id) != 0 {
return false
}
_ => return false
}
let block_label = @ir.hot_control_node_label(func, block_id)
if block_label < 0 ||
block_label >= label_refs.length() ||
@ir.hot_label_branch_arity(func, block_label) != 0 {
return false
}
let body_ref = @ir.hot_region_body_ref(func, block_id)
let body_count = @ir.hot_region_root_count(func, body_ref)
if body_count == 1 {
let child_id = @ir.hot_region_get(func, body_ref, 0)
if child_id < 0 || !@ir.hot_node_is_live(func, child_id) {
return false
}
match @ir.hot_node_get(func, child_id).op {
@ir.HotOp::Block =>
if @ir.hot_node_result_arity(func, child_id) != 0 {
return false
}
_ => return false
}
let child_label = @ir.hot_control_node_label(func, child_id)
if child_label < 0 || child_label >= label_refs.length() {
return false
}
if remove_unused_brs_retarget_retargetable_branches(
func, child_label, block_label,
) >
0 {
remove_unused_brs_mark_mutated(ctx, func, "jump-thread-one-child-block")
return true
}
} else if body_count == 2 {
let child_id = @ir.hot_region_get(func, body_ref, 0)
let jump_id = @ir.hot_region_get(func, body_ref, 1)
if child_id < 0 ||
jump_id < 0 ||
!@ir.hot_node_is_live(func, child_id) ||
!@ir.hot_node_is_live(func, jump_id) {
return false
}
match @ir.hot_node_get(func, child_id).op {
@ir.HotOp::Block =>
if @ir.hot_node_result_arity(func, child_id) != 0 {
return false
}
_ => return false
}
let jump_node = @ir.hot_node_get(func, jump_id)
match jump_node.op {
@ir.HotOp::Br if jump_node.child_count == 0 => ()
_ => return false
}
let child_label = @ir.hot_control_node_label(func, child_id)
let jump_target = match @ir.hot_branch_target(func, jump_id) {
Some(label_id) => label_id
None => return false
}
if child_label < 0 || child_label >= label_refs.length() {
return false
}
if remove_unused_brs_retarget_retargetable_branches(
func, child_label, jump_target,
) >
0 {
remove_unused_brs_mark_mutated(ctx, func, "jump-thread-child-to-jump")
return true
}
}
false
}
///|
fn remove_unused_brs_try_rewrite_branches_to_trap_block(
ctx : HotPassContext,
func : @ir.HotFunc,
region_ref : @ir.HotRegionRef,
idx : Int32,
) -> Bool {
if idx < 0 || idx + 1 >= @ir.hot_region_root_count(func, region_ref) {
return false
}
let block_id = @ir.hot_region_get(func, region_ref, idx)
let trap_id = @ir.hot_region_get(func, region_ref, idx + 1)
if block_id < 0 ||
trap_id < 0 ||
!@ir.hot_node_is_live(func, block_id) ||
!@ir.hot_node_is_live(func, trap_id) {
return false
}
match @ir.hot_node_get(func, block_id).op {
@ir.HotOp::Block =>
if @ir.hot_node_result_arity(func, block_id) != 0 {
return false
}
_ => return false
}
match @ir.hot_node_get(func, trap_id).op {
@ir.HotOp::Unreachable => ()
_ => return false
}
let block_label = @ir.hot_control_node_label(func, block_id)
if block_label < 0 || @ir.hot_label_branch_arity(func, block_label) != 0 {
return false
}
let targets : Array[Int32] = []
remove_unused_brs_collect_simple_branches_to_label(
func, block_id, block_label, targets,
)
if targets.is_empty() {
return false
}
let detached : Array[Int32] = []
for target in targets {
if target >= 0 && @ir.hot_node_is_live(func, target) {
let unreachable_id = @ir.hot_build_unreachable(func)
detached.push(unreachable_id)
@ir.hot_replace_node(func, target, @ir.hot_node_get(func, unreachable_id))
}
}
remove_unused_brs_mark_mutated(ctx, func, "rewrite-branch-to-trap")
remove_unused_brs_delete_unused_nodes(ctx, func, detached)
true
}
///|
fn remove_unused_brs_switch_selector_can_move_before_values(
ctx : HotPassContext,
func : @ir.HotFunc,
selector : Int32,
values : Array[Int32],
reorder_cache : Array[Int32],
) -> Bool {
if !remove_unused_brs_node_is_reorder_safe(ctx, func, selector, reorder_cache) {
return false
}
match @ir.hot_node_get(func, selector).op {
@ir.HotOp::Const | @ir.HotOp::LocalGet => ()
_ => return false
}
for value in values {
if !remove_unused_brs_node_is_reorder_safe(ctx, func, value, reorder_cache) {
return false
}
}
true
}
///|
fn remove_unused_brs_try_optimize_switch(
ctx : HotPassContext,
func : @ir.HotFunc,
region_ref : @ir.HotRegionRef,
idx : Int32,
reorder_cache : Array[Int32],
) -> Bool {
if idx < 0 || idx >= @ir.hot_region_root_count(func, region_ref) {
return false
}
let br_table_id = @ir.hot_region_get(func, region_ref, idx)
if br_table_id < 0 || !@ir.hot_node_is_live(func, br_table_id) {
return false
}
let node = @ir.hot_node_get(func, br_table_id)
match node.op {
@ir.HotOp::BrTable => ()
_ => return false
}
let stack_selector = node.child_count == 0
if stack_selector && idx == 0 {
return false
}
let default_target = @ir.hot_branch_table_default_target(func, node.imm0)
let mut targets = @ir.hot_branch_table_targets(func, node.imm0).copy()
let original_targets_len = targets.length()
while targets.length() > 0 && targets[targets.length() - 1] == default_target {
ignore(targets.pop())
}
let mut leading_defaults = 0
while leading_defaults < targets.length() &&
targets[leading_defaults] == default_target {
leading_defaults = leading_defaults + 1
}
if leading_defaults > 0 {
let shifted : Array[Int32] = []
for i = leading_defaults; i < targets.length(); i = i + 1 {
shifted.push(targets[i])
}
targets = shifted
}
let selector_slot = node.child_count - 1
let selector = if stack_selector {
@ir.hot_region_get(func, region_ref, idx - 1)
} else {
@ir.hot_child_get(func, br_table_id, selector_slot)
}
let default_arity = @ir.hot_label_branch_arity(func, default_target)
let values : Array[Int32] = []
if stack_selector {
if default_arity > 0 {
if idx < default_arity + 1 {
return false
}
let first_value_idx = idx - 1 - default_arity
for value_idx = first_value_idx
value_idx < idx - 1
value_idx = value_idx + 1 {
let value = @ir.hot_region_get(func, region_ref, value_idx)
if value < 0 ||
!@ir.hot_node_is_live(func, value) ||
@ir.hot_node_result_arity(func, value) != 1 {
return false
}
values.push(value)
}
}
} else {
for i = 0; i < selector_slot; i = i + 1 {
let value = @ir.hot_child_get(func, br_table_id, i)
if value < 0 || !@ir.hot_node_is_live(func, value) {
return false
}
values.push(value)
}
}
let value_carrying = values.length() > 0 || default_arity != 0
if stack_selector && value_carrying && values.length() != default_arity {
return false
}
for target in targets {
if @ir.hot_label_branch_arity(func, target) != default_arity {
return false
}
}
let mut condition = selector
if leading_defaults > 0 {
condition = remove_unused_brs_build_i32_sub(
func,
condition,
@ir.hot_build_const_i32(func, @lib.I32(leading_defaults)),
)
}
if value_carrying {
if targets.length() == 0 &&
remove_unused_brs_switch_selector_can_move_before_values(
ctx, func, condition, values, reorder_cache,
) {
let drop_selector = @ir.hot_build_drop(func, condition)
let br = @ir.hot_build_br(func, default_target, values~)
let block = @ir.hot_build_block(func, @lib.BlockType::void_(), [
drop_selector, br,
])
if stack_selector {
ignore(
@ir.hot_region_splice(
func,
region_ref,
idx - 1 - values.length(),
values.length() + 2,
[block],
),
)
} else {
let detached : Array[Int32] = [block]
@ir.hot_replace_node(func, br_table_id, @ir.hot_node_get(func, block))
remove_unused_brs_delete_unused_nodes(ctx, func, detached)
}
remove_unused_brs_mark_mutated(
ctx, func, "optimize-switch-one-target-value",
)
return true
}
if targets.length() != original_targets_len || leading_defaults > 0 {
let replacement = @ir.hot_build_br_table(
func,
targets,
default_target,
condition,
values~,
)
if stack_selector {
ignore(
@ir.hot_region_splice(
func,
region_ref,
idx - 1 - values.length(),
values.length() + 2,
[replacement],
),
)
} else {
let detached : Array[Int32] = [replacement]
@ir.hot_replace_node(
func,
br_table_id,
@ir.hot_node_get(func, replacement),
)
remove_unused_brs_delete_unused_nodes(ctx, func, detached)
}
remove_unused_brs_mark_mutated(ctx, func, "optimize-switch-value-targets")
return true
}
return false
}
if targets.length() == 0 {
let drop_selector = @ir.hot_build_drop(func, condition)
let br = @ir.hot_build_br(func, default_target)
let block = @ir.hot_build_block(func, @lib.BlockType::void_(), [
drop_selector, br,
])
if stack_selector {
ignore(@ir.hot_region_splice(func, region_ref, idx - 1, 2, [block]))
} else {
let detached : Array[Int32] = [block]
@ir.hot_replace_node(func, br_table_id, @ir.hot_node_get(func, block))
remove_unused_brs_delete_unused_nodes(ctx, func, detached)
}
remove_unused_brs_mark_mutated(ctx, func, "optimize-switch-default-only")
return true
}
if targets.length() == 1 {
let if_ = @ir.hot_build_if(
func,
@lib.BlockType::void_(),
condition,
[@ir.hot_build_br(func, default_target)],
else_body=Some([@ir.hot_build_br(func, targets[0])]),
)
if stack_selector {
ignore(@ir.hot_region_splice(func, region_ref, idx - 1, 2, [if_]))
} else {
let detached : Array[Int32] = [if_]
@ir.hot_replace_node(func, br_table_id, @ir.hot_node_get(func, if_))
remove_unused_brs_delete_unused_nodes(ctx, func, detached)
}
remove_unused_brs_mark_mutated(ctx, func, "optimize-switch-two-options")
return true
}
let min_shrink = 13
let threshold_met = targets.length() >= min_shrink
if !threshold_met {
if targets.length() != original_targets_len || leading_defaults > 0 {
let replacement = @ir.hot_build_br_table(
func, targets, default_target, condition,
)
if stack_selector {
ignore(
@ir.hot_region_splice(func, region_ref, idx - 1, 2, [replacement]),
)
} else {
let detached : Array[Int32] = [replacement]
@ir.hot_replace_node(
func,
br_table_id,
@ir.hot_node_get(func, replacement),
)
remove_unused_brs_delete_unused_nodes(ctx, func, detached)
}
remove_unused_brs_mark_mutated(ctx, func, "optimize-switch-trim-targets")
return true
}
return false
}
for i = 1; i < targets.length() - 1; i = i + 1 {
if targets[i] != default_target {
return false
}
}
let temp = @ir.hot_append_body_local(func, @lib.ValType::i32())
let tee = @ir.hot_build_local_tee(func, temp, condition)
let last_eq = remove_unused_brs_build_i32_eq(
func,
@ir.hot_build_local_get(func, temp),
@ir.hot_build_const_i32(func, @lib.I32(targets.length() - 1)),
)
let inner_if = @ir.hot_build_if(
func,
@lib.BlockType::void_(),
last_eq,
[@ir.hot_build_br(func, targets[targets.length() - 1])],
else_body=Some([@ir.hot_build_br(func, default_target)]),
)
let outer_if = @ir.hot_build_if(
func,
@lib.BlockType::void_(),
tee,
[inner_if],
else_body=Some([@ir.hot_build_br(func, targets[0])]),
)
if stack_selector {
ignore(@ir.hot_region_splice(func, region_ref, idx - 1, 2, [outer_if]))
} else {
let detached : Array[Int32] = [outer_if]
@ir.hot_replace_node(func, br_table_id, @ir.hot_node_get(func, outer_if))
remove_unused_brs_delete_unused_nodes(ctx, func, detached)
}
remove_unused_brs_mark_mutated(ctx, func, "optimize-switch-large-default-run")
true
}
///|
fn remove_unused_brs_try_drop_self_targeting_br_table_block(
ctx : HotPassContext,
func : @ir.HotFunc,
region_ref : @ir.HotRegionRef,
idx : Int32,
label_refs : Array[Int32],
) -> Bool {
if idx < 0 || idx >= @ir.hot_region_root_count(func, region_ref) {
return false
}
let block_id = @ir.hot_region_get(func, region_ref, idx)
if block_id < 0 || !@ir.hot_node_is_live(func, block_id) {
return false
}
match @ir.hot_node_get(func, block_id).op {
@ir.HotOp::Block =>
if @ir.hot_node_result_arity(func, block_id) != 0 {
return false
}
_ => return false
}
let block_label = @ir.hot_control_node_label(func, block_id)
if block_label < 0 ||
block_label >= label_refs.length() ||
@ir.hot_label_branch_arity(func, block_label) != 0 {
return false
}
let body_ref = @ir.hot_region_body_ref(func, block_id)
if @ir.hot_region_root_count(func, body_ref) != 1 {
return false
}
let br_table_id = @ir.hot_region_get(func, body_ref, 0)
if br_table_id < 0 || !@ir.hot_node_is_live(func, br_table_id) {
return false
}
let br_table_node = @ir.hot_node_get(func, br_table_id)
match br_table_node.op {
@ir.HotOp::BrTable => if br_table_node.child_count != 1 { return false }
_ => return false
}
let targets = @ir.hot_branch_table_targets(func, br_table_node.imm0)
for target in targets {
if target != block_label {
return false
}
}
if @ir.hot_branch_table_default_target(func, br_table_node.imm0) !=
block_label {
return false
}
let expected_refs = targets.length() + 1
if label_refs[block_label] != expected_refs {
return false
}
let selector = @ir.hot_child_get(func, br_table_id, 0)
if selector < 0 || !@ir.hot_node_is_live(func, selector) {
return false
}
let replacement = @ir.hot_build_drop(func, selector)
let removed = pass_splice_region(ctx, func, region_ref, idx, 1, [replacement])
removed.push(replacement)
remove_unused_brs_mark_mutated(
ctx, func, "drop-self-targeting-br-table-block",
)
remove_unused_brs_delete_unused_nodes(ctx, func, removed)
true
}
///|
fn remove_unused_brs_condition_is_safe_to_speculate(
func : @ir.HotFunc,
node_id : Int32,
) -> 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::Const
| @ir.HotOp::LocalGet
| @ir.HotOp::GlobalGet
| @ir.HotOp::MemorySize
| @ir.HotOp::TableSize
| @ir.HotOp::RefNull
| @ir.HotOp::RefFunc => true
@ir.HotOp::RefIsNull => {
if node.child_count != 1 {
return false
}
remove_unused_brs_condition_is_safe_to_speculate(
func,
@ir.hot_child_get(func, node_id, 0),
)
}
@ir.HotOp::Unary | @ir.HotOp::Compare => {
for slot = 0; slot < node.child_count; slot = slot + 1 {
if !remove_unused_brs_condition_is_safe_to_speculate(
func,
@ir.hot_child_get(func, node_id, slot),
) {
return false
}
}
true
}
@ir.HotOp::Binary => {
match @ir.hot_node_exact_instr(func, node_id) {
Some(
@lib.Instruction::I32Add
| @lib.Instruction::I32Sub
| @lib.Instruction::I32Mul
| @lib.Instruction::I32And
| @lib.Instruction::I32Or
| @lib.Instruction::I32Xor
| @lib.Instruction::I32Shl
| @lib.Instruction::I32ShrS
| @lib.Instruction::I32ShrU
| @lib.Instruction::I32Rotl
| @lib.Instruction::I32Rotr
| @lib.Instruction::I64Add
| @lib.Instruction::I64Sub
| @lib.Instruction::I64Mul
| @lib.Instruction::I64And
| @lib.Instruction::I64Or
| @lib.Instruction::I64Xor
| @lib.Instruction::I64Shl
| @lib.Instruction::I64ShrS
| @lib.Instruction::I64ShrU
| @lib.Instruction::I64Rotl
| @lib.Instruction::I64Rotr
) => ()
_ => return false
}
for slot = 0; slot < node.child_count; slot = slot + 1 {
if !remove_unused_brs_condition_is_safe_to_speculate(
func,
@ir.hot_child_get(func, node_id, slot),
) {
return false
}
}
true
}
_ => false
}
}
///|
fn remove_unused_brs_nodes_are_known_equal_for_redundant_br_if_value(
func : @ir.HotFunc,
left : Int32,
right : Int32,
) -> Bool {
if left < 0 ||
right < 0 ||
!@ir.hot_node_is_live(func, left) ||
!@ir.hot_node_is_live(func, right) {
return false
}
let left_node = @ir.hot_node_get(func, left)
let right_node = @ir.hot_node_get(func, right)
match (left_node.op, right_node.op) {
(@ir.HotOp::LocalGet, @ir.HotOp::LocalGet) =>
left_node.imm0 == right_node.imm0
(@ir.HotOp::Const, @ir.HotOp::Const) =>
pass_hot_const_bits_equal(
@ir.hot_const_get(func, left_node.imm0),
@ir.hot_const_get(func, right_node.imm0),
)
_ => false
}
}
///|
fn remove_unused_brs_condition_preserves_redundant_payload(
func : @ir.HotFunc,
payload : Int32,
condition : Int32,
) -> Bool {
// The matching constant payload and fallthrough have no state dependency.
// The condition remains evaluated once in place, including traps and exits.
if @ir.hot_node_get(func, payload).op is @ir.HotOp::Const {
return true
}
let payload_reads = @bitset.BitSet::new(0)
remove_unused_brs_mark_local_reads_in_value_expr(func, payload, payload_reads)
remove_unused_brs_condition_is_select_safe_over_value_arms(
func, condition, payload_reads,
)
}
///|
fn remove_unused_brs_try_drop_redundant_self_br_if_value(
ctx : HotPassContext,
func : @ir.HotFunc,
region_ref : @ir.HotRegionRef,
idx : Int32,
) -> Bool {
let root_count = @ir.hot_region_root_count(func, region_ref)
if idx < 0 || idx + 1 != root_count - 1 || region_ref.holder < 0 {
return false
}
let holder = @ir.hot_node_get(func, region_ref.holder)
match holder.op {
@ir.HotOp::Block => ()
_ => return false
}
if @ir.hot_type_result_arity(func, @ir.hot_node_type(func, region_ref.holder)) !=
1 {
return false
}
let tail_id = @ir.hot_region_get(func, region_ref, idx)
let last = @ir.hot_region_get(func, region_ref, idx + 1)
if tail_id < 0 ||
last < 0 ||
!@ir.hot_node_is_live(func, tail_id) ||
!@ir.hot_node_is_live(func, last) {
return false
}
let tail_node = @ir.hot_node_get(func, tail_id)
let (branch_id, payload, condition, splice_start, splice_count) = match
tail_node.op {
@ir.HotOp::BrIf if tail_node.child_count == 0 && idx >= 2 =>
(
tail_id,
@ir.hot_region_get(func, region_ref, idx - 2),
@ir.hot_region_get(func, region_ref, idx - 1),
idx - 2,
3,
)
@ir.HotOp::Drop =>
match tail_node.child_count {
1 => {
let branch_id = @ir.hot_child_get(func, tail_id, 0)
if branch_id < 0 || !@ir.hot_node_is_live(func, branch_id) {
return false
}
let branch_node = @ir.hot_node_get(func, branch_id)
match branch_node.op {
@ir.HotOp::BrIf => ()
_ => return false
}
match branch_node.child_count {
2 => {
let (selected_branch, splice_start, splice_count) = if idx > 0 {
let previous = @ir.hot_region_get(func, region_ref, idx - 1)
if previous >= 0 && @ir.hot_node_is_live(func, previous) {
match @ir.hot_node_get(func, previous).op {
@ir.HotOp::BrIf => (previous, idx - 1, 2)
_ => (branch_id, idx, 1)
}
} else {
(branch_id, idx, 1)
}
} else {
(branch_id, idx, 1)
}
(
selected_branch,
@ir.hot_child_get(func, selected_branch, 0),
@ir.hot_child_get(func, selected_branch, 1),
splice_start,
splice_count,
)
}
0 if idx >= 2 =>
(
branch_id,
@ir.hot_region_get(func, region_ref, idx - 2),
@ir.hot_region_get(func, region_ref, idx - 1),
idx - 2,
3,
)
_ => return false
}
}
0 if idx >= 3 => {
let branch_id = @ir.hot_region_get(func, region_ref, idx - 1)
if branch_id < 0 || !@ir.hot_node_is_live(func, branch_id) {
return false
}
match @ir.hot_node_get(func, branch_id).op {
@ir.HotOp::BrIf => ()
_ => return false
}
(
branch_id,
@ir.hot_region_get(func, region_ref, idx - 3),
@ir.hot_region_get(func, region_ref, idx - 2),
idx - 3,
4,
)
}
_ => return false
}
_ => return false
}
if @ir.hot_branch_target(func, branch_id) !=
Some(@ir.hot_control_node_label(func, region_ref.holder)) {
return false
}
if payload < 0 ||
condition < 0 ||
!@ir.hot_node_is_live(func, payload) ||
!@ir.hot_node_is_live(func, condition) {
return false
}
if !remove_unused_brs_nodes_are_known_equal_for_redundant_br_if_value(
func, payload, last,
) {
return false
}
if !remove_unused_brs_condition_preserves_redundant_payload(
func, payload, condition,
) {
return false
}
let dropped_condition = @ir.hot_build_drop(func, condition)
let removed = pass_splice_region(
ctx,
func,
region_ref,
splice_start,
splice_count,
[dropped_condition],
)
removed.push(branch_id)
removed.push(payload)
remove_unused_brs_mark_mutated(ctx, func, "drop-redundant-self-br-if-value")
remove_unused_brs_delete_unused_nodes(ctx, func, removed)
true
}
///|
fn remove_unused_brs_try_drop_redundant_self_br_if_value_block(
ctx : HotPassContext,
func : @ir.HotFunc,
block_id : Int32,
) -> Bool {
if block_id < 0 || !@ir.hot_node_is_live(func, block_id) {
return false
}
match @ir.hot_node_get(func, block_id).op {
@ir.HotOp::Block => ()
_ => return false
}
let body_ref = @ir.hot_region_body_ref(func, block_id)
let root_count = @ir.hot_region_root_count(func, body_ref)
if root_count < 2 {
return false
}
if root_count >= 3 {
let branch_idx = root_count - 3
let drop_idx = root_count - 2
let last_idx = root_count - 1
let branch_id = @ir.hot_region_get(func, body_ref, branch_idx)
let drop_id = @ir.hot_region_get(func, body_ref, drop_idx)
let last = @ir.hot_region_get(func, body_ref, last_idx)
if branch_id >= 0 &&
drop_id >= 0 &&
last >= 0 &&
@ir.hot_node_is_live(func, branch_id) &&
@ir.hot_node_is_live(func, drop_id) &&
@ir.hot_node_is_live(func, last) {
let branch_node = @ir.hot_node_get(func, branch_id)
let drop_node = @ir.hot_node_get(func, drop_id)
let branch_is_br_if = match branch_node.op {
@ir.HotOp::BrIf => true
_ => false
}
let drop_is_drop = match drop_node.op {
@ir.HotOp::Drop => true
_ => false
}
if branch_is_br_if &&
branch_node.child_count == 2 &&
drop_is_drop &&
@ir.hot_branch_target(func, branch_id) ==
Some(@ir.hot_control_node_label(func, block_id)) {
let payload = @ir.hot_child_get(func, branch_id, 0)
let condition = @ir.hot_child_get(func, branch_id, 1)
if remove_unused_brs_nodes_are_known_equal_for_redundant_br_if_value(
func, payload, last,
) &&
remove_unused_brs_condition_preserves_redundant_payload(
func, payload, condition,
) {
let dropped_condition = @ir.hot_build_drop(func, condition)
let removed = pass_splice_region(ctx, func, body_ref, branch_idx, 2, [
dropped_condition,
])
removed.push(payload)
remove_unused_brs_mark_mutated(
ctx, func, "drop-redundant-self-br-if-value",
)
remove_unused_brs_delete_unused_nodes(ctx, func, removed)
return true
}
}
}
}
remove_unused_brs_try_drop_redundant_self_br_if_value(
ctx,
func,
body_ref,
root_count - 2,
)
}
///|
fn remove_unused_brs_try_merge_adjacent_br_ifs(
ctx : HotPassContext,
func : @ir.HotFunc,
region_ref : @ir.HotRegionRef,
idx : Int32,
) -> Bool {
if idx < 0 || idx + 1 >= @ir.hot_region_root_count(func, region_ref) {
return false
}
let br1 = @ir.hot_region_get(func, region_ref, idx)
let br2 = @ir.hot_region_get(func, region_ref, idx + 1)
if br1 < 0 ||
br2 < 0 ||
!@ir.hot_node_is_live(func, br1) ||
!@ir.hot_node_is_live(func, br2) {
return false
}
let br1_node = @ir.hot_node_get(func, br1)
match br1_node.op {
@ir.HotOp::BrIf => ()
_ => return false
}
let br2_node = @ir.hot_node_get(func, br2)
let br2_is_plain_br = match br2_node.op {
@ir.HotOp::Br => true
_ => false
}
if br1_node.child_count == 1 &&
br2_is_plain_br &&
br2_node.child_count == 0 &&
@ir.hot_branch_target(func, br1) == @ir.hot_branch_target(func, br2) {
let condition = @ir.hot_child_get(func, br1, 0)
if condition < 0 || !@ir.hot_node_is_live(func, condition) {
return false
}
let dropped_condition = @ir.hot_build_drop(func, condition)
let removed = pass_splice_region(ctx, func, region_ref, idx, 1, [
dropped_condition,
])
remove_unused_brs_mark_mutated(
ctx, func, "drop-adjacent-br-if-before-same-target-br",
)
remove_unused_brs_delete_unused_nodes(ctx, func, removed)
return true
}
if br1_node.child_count == 0 &&
br2_is_plain_br &&
br2_node.child_count == 0 &&
idx > 0 &&
@ir.hot_branch_target(func, br1) == @ir.hot_branch_target(func, br2) {
let condition = @ir.hot_region_get(func, region_ref, idx - 1)
if condition < 0 || !@ir.hot_node_is_live(func, condition) {
return false
}
let dropped_condition = @ir.hot_build_drop(func, condition)
let removed = pass_splice_region(ctx, func, region_ref, idx - 1, 2, [
dropped_condition,
])
remove_unused_brs_mark_mutated(
ctx, func, "drop-stack-adjacent-br-if-before-same-target-br",
)
remove_unused_brs_delete_unused_nodes(ctx, func, removed)
return true
}
if ctx.shrink_level <= 0 {
return false
}
if br1_node.child_count == 0 &&
idx > 0 &&
idx + 2 < @ir.hot_region_root_count(func, region_ref) {
let cond1 = @ir.hot_region_get(func, region_ref, idx - 1)
let cond2 = @ir.hot_region_get(func, region_ref, idx + 1)
let br2 = @ir.hot_region_get(func, region_ref, idx + 2)
if cond1 < 0 ||
cond2 < 0 ||
br2 < 0 ||
!@ir.hot_node_is_live(func, cond1) ||
!@ir.hot_node_is_live(func, cond2) ||
!@ir.hot_node_is_live(func, br2) {
return false
}
let br2_node = @ir.hot_node_get(func, br2)
match br2_node.op {
@ir.HotOp::BrIf => ()
_ => return false
}
if br2_node.child_count != 0 ||
@ir.hot_branch_target(func, br1) != @ir.hot_branch_target(func, br2) ||
!remove_unused_brs_condition_is_safe_to_speculate(func, cond2) {
return false
}
let target = match @ir.hot_branch_target(func, br1) {
Some(label) => label
None => return false
}
let merged = @ir.hot_build_binary(func, @lib.ValType::i32(), cond1, cond2)
@ir.hot_node_exact_instr_set(func, merged, @lib.Instruction::i32_or())
let branch = @ir.hot_build_br_if(func, target, merged)
let removed = pass_splice_region(ctx, func, region_ref, idx - 1, 4, [branch])
remove_unused_brs_mark_mutated(ctx, func, "merge-adjacent-stack-br-if-or")
remove_unused_brs_delete_unused_nodes(ctx, func, removed)
return true
}
let br2_node = @ir.hot_node_get(func, br2)
match br2_node.op {
@ir.HotOp::BrIf => ()
_ => return false
}
if br1_node.child_count != 1 ||
br2_node.child_count != 1 ||
@ir.hot_branch_target(func, br1) != @ir.hot_branch_target(func, br2) {
return false
}
let cond1 = @ir.hot_child_get(func, br1, 0)
let cond2 = @ir.hot_child_get(func, br2, 0)
if !remove_unused_brs_condition_is_safe_to_speculate(func, cond2) {
return false
}
let merged = @ir.hot_build_binary(func, @lib.ValType::i32(), cond1, cond2)
@ir.hot_node_exact_instr_set(func, merged, @lib.Instruction::i32_or())
@ir.hot_child_set(func, br1, 0, merged)
let removed = pass_splice_region(ctx, func, region_ref, idx + 1, 1, [])
remove_unused_brs_mark_mutated(ctx, func, "merge-adjacent-br-if-or")
remove_unused_brs_delete_unused_nodes(ctx, func, removed)
true
}
///|
fn remove_unused_brs_try_rewrite_br_table_continuation_wrappers(
ctx : HotPassContext,
func : @ir.HotFunc,
region_ref : @ir.HotRegionRef,
idx : Int32,
label_refs : Array[Int32],
) -> Bool {
if idx < 0 || idx >= @ir.hot_region_root_count(func, region_ref) {
return false
}
let outer_block = @ir.hot_region_get(func, region_ref, idx)
if outer_block < 0 || !@ir.hot_node_is_live(func, outer_block) {
return false
}
match @ir.hot_node_get(func, outer_block).op {
@ir.HotOp::Block =>
if @ir.hot_node_result_arity(func, outer_block) != 0 {
return false
}
_ => return false
}
let outer_body = @ir.hot_region_body_ref(func, outer_block)
if @ir.hot_region_root_count(func, outer_body) < 2 {
return false
}
let outer_tail = @ir.hot_region_get(
func,
outer_body,
@ir.hot_region_root_count(func, outer_body) - 1,
)
if outer_tail < 0 || !@ir.hot_node_is_live(func, outer_tail) {
return false
}
match @ir.hot_node_get(func, outer_tail).op {
@ir.HotOp::Br =>
if @ir.hot_node_get(func, outer_tail).child_count == 0 {
return false
}
_ => return false
}
let wrapper_blocks : Array[Int32] = []
let wrapper_tails : Array[Int32] = []
let forwarded_labels : Array[Int32] = []
let forwarded_label_uses : Array[Int32] = []
let mut outer_target : Int32? = None
let mut current = @ir.hot_region_get(func, outer_body, 0)
while true {
if current < 0 || !@ir.hot_node_is_live(func, current) {
return false
}
match @ir.hot_node_get(func, current).op {
@ir.HotOp::Block =>
if @ir.hot_node_result_arity(func, current) != 0 {
break
}
_ => break
}
let current_body = @ir.hot_region_body_ref(func, current)
if @ir.hot_region_root_count(func, current_body) != 2 {
break
}
let child_block = @ir.hot_region_get(func, current_body, 0)
if child_block < 0 || !@ir.hot_node_is_live(func, child_block) {
return false
}
match @ir.hot_node_get(func, child_block).op {
@ir.HotOp::Block =>
if @ir.hot_node_result_arity(func, child_block) != 0 {
return false
}
_ => return false
}
let tail_id = @ir.hot_region_get(func, current_body, 1)
if tail_id < 0 || !@ir.hot_node_is_live(func, tail_id) {
return false
}
let tail_node = @ir.hot_node_get(func, tail_id)
match tail_node.op {
@ir.HotOp::Br => if tail_node.child_count != 0 { return false }
_ => return false
}
if @ir.hot_label_branch_arity(func, tail_node.imm0) != 0 {
return false
}
match outer_target {
Some(saved) => if saved != tail_node.imm0 { return false }
None => outer_target = Some(tail_node.imm0)
}
wrapper_blocks.push(current)
wrapper_tails.push(tail_id)
forwarded_labels.push(@ir.hot_control_node_label(func, child_block))
forwarded_label_uses.push(0)
current = child_block
}
let outer_target = match outer_target {
Some(target) => target
None => return false
}
if wrapper_blocks.is_empty() ||
@ir.hot_label_branch_arity(func, outer_target) != 0 {
return false
}
let leaf_block = current
if leaf_block < 0 || !@ir.hot_node_is_live(func, leaf_block) {
return false
}
match @ir.hot_node_get(func, leaf_block).op {
@ir.HotOp::Block =>
if @ir.hot_node_result_arity(func, leaf_block) != 0 {
return false
}
_ => return false
}
let leaf_body = @ir.hot_region_body_ref(func, leaf_block)
if @ir.hot_region_root_count(func, leaf_body) != 1 {
return false
}
let br_table_id = @ir.hot_region_get(func, leaf_body, 0)
if br_table_id < 0 || !@ir.hot_node_is_live(func, br_table_id) {
return false
}
let br_table_node = @ir.hot_node_get(func, br_table_id)
match br_table_node.op {
@ir.HotOp::BrTable => if br_table_node.child_count != 1 { return false }
_ => return false
}
let rewritten_targets = @ir.hot_branch_table_targets(func, br_table_node.imm0).copy()
let mut rewritten_default = @ir.hot_branch_table_default_target(
func,
br_table_node.imm0,
)
let mut rewrote_any = false
for idx = 0; idx < rewritten_targets.length(); idx = idx + 1 {
let target = rewritten_targets[idx]
for label_idx = 0
label_idx < forwarded_labels.length()
label_idx = label_idx + 1 {
if target == forwarded_labels[label_idx] {
rewritten_targets[idx] = outer_target
forwarded_label_uses[label_idx] = forwarded_label_uses[label_idx] + 1
rewrote_any = true
break
}
}
}
for label_idx = 0
label_idx < forwarded_labels.length()
label_idx = label_idx + 1 {
if rewritten_default == forwarded_labels[label_idx] {
rewritten_default = outer_target
forwarded_label_uses[label_idx] = forwarded_label_uses[label_idx] + 1
rewrote_any = true
break
}
}
if !rewrote_any {
return false
}
for label_idx = 0
label_idx < forwarded_labels.length()
label_idx = label_idx + 1 {
let label = forwarded_labels[label_idx]
if label < 0 ||
label >= label_refs.length() ||
label_refs[label] != forwarded_label_uses[label_idx] {
return false
}
}
let selector = @ir.hot_child_get(
func,
br_table_id,
br_table_node.child_count - 1,
)
let replacement = @ir.hot_build_br_table(
func, rewritten_targets, rewritten_default, selector,
)
let detached : Array[Int32] = [replacement]
@ir.hot_replace_node(func, br_table_id, @ir.hot_node_get(func, replacement))
for tail_id in wrapper_tails {
let unreachable_id = @ir.hot_build_unreachable(func)
detached.push(unreachable_id)
@ir.hot_replace_node(func, tail_id, @ir.hot_node_get(func, unreachable_id))
}
remove_unused_brs_mark_mutated(
ctx, func, "rewrite-br-table-continuation-wrappers",
)
remove_unused_brs_delete_unused_nodes(ctx, func, detached)
(ctx.trace)(
"rub-br-table-forward rewrite block=\{outer_block} wrappers=\{wrapper_blocks.length()} target=\{outer_target}",
)
true
}
///|
fn remove_unused_brs_condition_writes_feed_preceding_roots(
ctx : HotPassContext,
func : @ir.HotFunc,
if_id : Int32,
condition : Int32,
) -> Bool {
let writes = @bitset.BitSet::new(0)
remove_unused_brs_mark_local_writes_in_value_expr(func, condition, writes)
if writes.length() == 0 {
return false
}
let use_def = pass_require_use_def(ctx, func)
for site in @ir.node_use_sites_borrowed(use_def, if_id) {
match site.kind {
@ir.HotUseSlotKind::RootUseSlot => ()
_ => continue
}
if site.user < 0 {
continue
}
let region_ref = match
remove_unused_brs_root_use_region_ref(func, if_id, site.user, site.slot) {
Some(region_ref) => region_ref
None => continue
}
let reads = @bitset.BitSet::new(0)
for idx = 0; idx < site.slot; idx = idx + 1 {
remove_unused_brs_mark_local_reads_in_value_expr(
func,
@ir.hot_region_get(func, region_ref, idx),
reads,
)
}
let overlap = if writes.length() < reads.length() {
writes.length()
} else {
reads.length()
}
for local_id = 0; local_id < overlap; local_id = local_id + 1 {
if writes[local_id] && reads[local_id] {
return true
}
}
}
false
}
///|
fn remove_unused_brs_match_value_if_to_select(
ctx : HotPassContext,
func : @ir.HotFunc,
node_id : Int32,
reorder_cache : Array[Int32],
allow_nonreorder_condition_with_reorder_safe_arms~ : Bool = false,
apply_selectify_cost_model~ : Bool = false,
) -> RemoveUnusedBrsValueIfSelectRewrite? {
let node = @ir.hot_node_get(func, node_id)
match node.op {
@ir.HotOp::If =>
if @ir.hot_node_result_arity(func, node_id) != 1 {
return None
}
_ => return None
}
let then_ref = @ir.hot_region_then_ref(func, node_id)
let else_ref = match @ir.hot_region_else_ref(func, node_id) {
Some(else_ref) => else_ref
None => return None
}
let condition = @ir.hot_child_get(func, node_id, 0)
if remove_unused_brs_condition_writes_feed_preceding_roots(
ctx, func, node_id, condition,
) {
return None
}
let result_type = @ir.hot_type_results(func, @ir.hot_node_type(func, node_id))[0]
let then_root = match
remove_unused_brs_build_region_value_expr(
ctx,
func,
remove_unused_brs_region_roots(func, then_ref),
result_type,
reorder_cache,
) {
Some(expr) => expr
None => return None
}
let else_root = match
remove_unused_brs_build_region_value_expr(
ctx,
func,
remove_unused_brs_region_roots(func, else_ref),
result_type,
reorder_cache,
) {
Some(expr) => expr
None => return None
}
if apply_selectify_cost_model &&
remove_unused_brs_selectify_too_costly(ctx, func, then_root, else_root) {
return None
}
if !remove_unused_brs_node_is_reorder_safe(
ctx, func, condition, reorder_cache,
) {
let arm_reads = @bitset.BitSet::new(0)
remove_unused_brs_mark_local_reads_in_value_expr(func, then_root, arm_reads)
remove_unused_brs_mark_local_reads_in_value_expr(func, else_root, arm_reads)
let mut safe_nonreorder_condition = remove_unused_brs_condition_is_select_safe_over_value_arms(
func, condition, arm_reads,
)
if !safe_nonreorder_condition &&
allow_nonreorder_condition_with_reorder_safe_arms &&
!remove_unused_brs_value_expr_reads_global(func, then_root) &&
!remove_unused_brs_value_expr_reads_global(func, else_root) {
safe_nonreorder_condition = remove_unused_brs_call_condition_preserves_arm_locals(
func, condition, arm_reads,
)
}
if !safe_nonreorder_condition {
let then_node = @ir.hot_node_get(func, then_root)
let else_node = @ir.hot_node_get(func, else_root)
match then_node.op {
@ir.HotOp::Const => ()
_ => return None
}
match else_node.op {
@ir.HotOp::Const => ()
_ => return None
}
}
}
let then_results = @ir.hot_type_results(
func,
@ir.hot_node_type(func, then_root),
)
let else_results = @ir.hot_type_results(
func,
@ir.hot_node_type(func, else_root),
)
let select_result_type = if then_results.length() == 1 &&
else_results.length() == 1 &&
then_results[0] == else_results[0] {
then_results[0]
} else {
result_type
}
Some({
replacement: remove_unused_brs_build_select(
func, select_result_type, then_root, else_root, condition,
),
then_holder: then_ref.holder,
else_holder: else_ref.holder,
})
}
///|
fn remove_unused_brs_replace_unused_if_with_value_expr(
ctx : HotPassContext,
func : @ir.HotFunc,
if_id : Int32,
replacement_id : Int32,
detached_holders : Array[Int32],
) -> Bool {
if if_id < 0 ||
!@ir.hot_node_is_live(func, if_id) ||
replacement_id < 0 ||
!@ir.hot_node_is_live(func, replacement_id) {
return false
}
match @ir.hot_node_get(func, if_id).op {
@ir.HotOp::If => ()
_ => return false
}
let block_children : Array[Int32] = [replacement_id]
for holder_id in detached_holders {
if holder_id < 0 || !@ir.hot_node_is_live(func, holder_id) {
continue
}
ignore(@ir.hot_replace_child_span(func, holder_id, []))
block_children.push(holder_id)
}
@ir.hot_control_demote_if_to_block(func, if_id, block_children)
remove_unused_brs_mark_mutated(ctx, func, "replace-unused-if-with-value-expr")
true
}
///|
fn remove_unused_brs_try_rewrite_value_if_to_select(
ctx : HotPassContext,
func : @ir.HotFunc,
node_id : Int32,
reorder_cache : Array[Int32],
) -> Bool {
match
remove_unused_brs_match_value_if_to_select(
ctx,
func,
node_id,
reorder_cache,
apply_selectify_cost_model=true,
) {
Some(rewrite) =>
remove_unused_brs_replace_unused_if_with_value_expr(
ctx,
func,
node_id,
rewrite.replacement,
[rewrite.then_holder, rewrite.else_holder],
)
None => false
}
}
///|
fn remove_unused_brs_try_rewrite_condition_child_value_if_to_select(
ctx : HotPassContext,
func : @ir.HotFunc,
owner_id : Int32,
reorder_cache : Array[Int32],
) -> Bool {
let owner = @ir.hot_node_get(func, owner_id)
match owner.op {
@ir.HotOp::If =>
if @ir.hot_node_result_arity(func, owner_id) != 0 {
return false
}
_ => return false
}
match @ir.hot_region_else_ref(func, owner_id) {
Some(_) => return false
None => ()
}
if owner.child_count == 0 {
return false
}
let condition_id = @ir.hot_child_get(func, owner_id, 0)
if condition_id < 0 || !@ir.hot_node_is_live(func, condition_id) {
return false
}
match @ir.hot_node_get(func, condition_id).op {
@ir.HotOp::If => ()
_ => return false
}
match
remove_unused_brs_match_value_if_to_select(
ctx,
func,
condition_id,
reorder_cache,
allow_nonreorder_condition_with_reorder_safe_arms=true,
) {
Some(rewrite) =>
remove_unused_brs_replace_unused_if_with_value_expr(
ctx,
func,
condition_id,
rewrite.replacement,
[rewrite.then_holder, rewrite.else_holder],
)
None => false
}
}
///|
fn remove_unused_brs_try_merge_nested_if_conditions(
ctx : HotPassContext,
func : @ir.HotFunc,
node_id : Int32,
reorder_cache : Array[Int32],
) -> Bool {
let node = @ir.hot_node_get(func, node_id)
match node.op {
@ir.HotOp::If =>
if @ir.hot_node_result_arity(func, node_id) != 0 {
return false
}
_ => return false
}
match @ir.hot_region_else_ref(func, node_id) {
Some(_) => return false
None => ()
}
let then_ref = @ir.hot_region_then_ref(func, node_id)
if @ir.hot_region_root_count(func, then_ref) != 1 {
return false
}
let child_if = @ir.hot_region_get(func, then_ref, 0)
let child = @ir.hot_node_get(func, child_if)
match child.op {
@ir.HotOp::If =>
if @ir.hot_node_result_arity(func, child_if) != 0 {
return false
}
_ => return false
}
match @ir.hot_region_else_ref(func, child_if) {
Some(_) => return false
None => ()
}
let child_then_ref = @ir.hot_region_then_ref(func, child_if)
let child_condition = @ir.hot_child_get(func, child_if, 0)
if !remove_unused_brs_node_is_reorder_safe(
ctx, func, child_condition, reorder_cache,
) ||
!remove_unused_brs_node_is_reorder_safe(
ctx,
func,
@ir.hot_child_get(func, node_id, 0),
reorder_cache,
) {
return false
}
let zero = @ir.hot_build_const_i32(func, @lib.I32(0))
let combined = remove_unused_brs_build_select(
func,
@lib.ValType::i32(),
child_condition,
zero,
@ir.hot_child_get(func, node_id, 0),
)
let child_then_roots : Array[Int32] = []
for idx = 0
idx < @ir.hot_region_root_count(func, child_then_ref)
idx = idx + 1 {
child_then_roots.push(@ir.hot_region_get(func, child_then_ref, idx))
}
@ir.hot_child_set(func, node_id, 0, combined)
@ir.hot_region_replace_body(func, then_ref, child_then_roots)
@ir.hot_region_replace_body(func, child_then_ref, [])
remove_unused_brs_mark_mutated(ctx, func, "merge-nested-if-conditions")
true
}
///|
fn remove_unused_brs_try_rewrite_region_local_set_copy_arm(
ctx : HotPassContext,
func : @ir.HotFunc,
region_ref : @ir.HotRegionRef,
idx : Int32,
) -> Bool {
if idx < 0 || idx >= @ir.hot_region_root_count(func, region_ref) {
return false
}
let node_id = @ir.hot_region_get(func, region_ref, idx)
let node = @ir.hot_node_get(func, node_id)
let is_tee = match node.op {
@ir.HotOp::LocalSet => false
@ir.HotOp::LocalTee => true
_ => return false
}
if node.child_count != 1 {
return false
}
let value_id = @ir.hot_child_get(func, node_id, 0)
if value_id < 0 || !@ir.hot_node_is_live(func, value_id) {
return false
}
let value = @ir.hot_node_get(func, value_id)
match value.op {
@ir.HotOp::If =>
if @ir.hot_node_result_arity(func, value_id) != 1 {
return false
}
_ => return false
}
let then_ref = @ir.hot_region_then_ref(func, value_id)
let else_ref = match @ir.hot_region_else_ref(func, value_id) {
Some(else_ref) => else_ref
None => return false
}
if @ir.hot_region_root_count(func, then_ref) != 1 ||
@ir.hot_region_root_count(func, else_ref) != 1 {
return false
}
let then_root = @ir.hot_region_get(func, then_ref, 0)
let else_root = @ir.hot_region_get(func, else_ref, 0)
let then_node = @ir.hot_node_get(func, then_root)
let else_node = @ir.hot_node_get(func, else_root)
let (set_value, copy_root, condition) = match then_node.op {
@ir.HotOp::LocalGet if then_node.imm0 == node.imm0 =>
(
else_root,
then_root,
remove_unused_brs_build_i32_eqz(
func,
@ir.hot_child_get(func, value_id, 0),
),
)
_ =>
match else_node.op {
@ir.HotOp::LocalGet if else_node.imm0 == node.imm0 =>
(then_root, else_root, @ir.hot_child_get(func, value_id, 0))
_ => return false
}
}
let replacement_set = @ir.hot_build_local_set(func, node.imm0, set_value)
let replacement_if = @ir.hot_build_if(
func,
@lib.BlockType::void_(),
condition,
[replacement_set],
)
let replacements = if is_tee {
let results = @ir.hot_type_results(func, @ir.hot_node_type(func, node_id))
if results.length() != 1 {
return false
}
let replacement_get = @ir.hot_build_local_get(func, node.imm0)
let block = @ir.hot_build_block(
func,
@lib.BlockType::val_type(results[0]),
[replacement_if, replacement_get],
)
[block]
} else {
[replacement_if]
}
let removed = pass_splice_region(ctx, func, region_ref, idx, 1, replacements)
removed.push(value_id)
removed.push(then_ref.holder)
removed.push(else_ref.holder)
removed.push(copy_root)
remove_unused_brs_delete_unused_nodes(ctx, func, removed)
true
}
///|
fn remove_unused_brs_try_rewrite_region_local_set_br_arm(
ctx : HotPassContext,
func : @ir.HotFunc,
region_ref : @ir.HotRegionRef,
idx : Int32,
) -> Bool {
if idx < 0 || idx >= @ir.hot_region_root_count(func, region_ref) {
return false
}
let node_id = @ir.hot_region_get(func, region_ref, idx)
let node = @ir.hot_node_get(func, node_id)
let is_tee = match node.op {
@ir.HotOp::LocalSet => false
@ir.HotOp::LocalTee => true
_ => return false
}
if node.child_count != 1 {
return false
}
let value_id = @ir.hot_child_get(func, node_id, 0)
if value_id < 0 || !@ir.hot_node_is_live(func, value_id) {
return false
}
let value = @ir.hot_node_get(func, value_id)
match value.op {
@ir.HotOp::If =>
if @ir.hot_node_result_arity(func, value_id) != 1 {
return false
}
_ => return false
}
let then_ref = @ir.hot_region_then_ref(func, value_id)
let else_ref = match @ir.hot_region_else_ref(func, value_id) {
Some(else_ref) => else_ref
None => return false
}
if @ir.hot_region_root_count(func, then_ref) != 1 ||
@ir.hot_region_root_count(func, else_ref) != 1 {
return false
}
let condition_id = @ir.hot_child_get(func, value_id, 0)
let (branch_root, branch_target, set_value, condition) = match
@ir.hot_region_single_plain_br_root(func, then_ref) {
Some(then_root) => {
let then_target = match
@ir.hot_region_single_plain_br_target(func, then_ref) {
Some(then_target) => then_target
None => return false
}
(
then_root,
then_target,
@ir.hot_region_get(func, else_ref, 0),
condition_id,
)
}
None => {
let else_root = match
@ir.hot_region_single_plain_br_root(func, else_ref) {
Some(else_root) => else_root
None => return false
}
let else_target = match
@ir.hot_region_single_plain_br_target(func, else_ref) {
Some(else_target) => else_target
None => return false
}
(
else_root,
else_target,
@ir.hot_region_get(func, then_ref, 0),
remove_unused_brs_build_i32_eqz(func, condition_id),
)
}
}
let branch = @ir.hot_build_br_if(func, branch_target, condition)
let replacement_store = if is_tee {
@ir.hot_build_local_tee(func, node.imm0, set_value)
} else {
@ir.hot_build_local_set(func, node.imm0, set_value)
}
let removed = pass_splice_region(ctx, func, region_ref, idx, 1, [
branch, replacement_store,
])
removed.push(value_id)
removed.push(then_ref.holder)
removed.push(else_ref.holder)
removed.push(branch_root)
remove_unused_brs_delete_unused_nodes(ctx, func, removed)
true
}
///|
fn remove_unused_brs_try_rewrite_two_arm_branch_if(
ctx : HotPassContext,
func : @ir.HotFunc,
region_ref : @ir.HotRegionRef,
idx : Int32,
) -> Bool {
if idx < 0 || idx >= @ir.hot_region_root_count(func, region_ref) {
return false
}
let if_id = @ir.hot_region_get(func, region_ref, idx)
let if_node = @ir.hot_node_get(func, if_id)
match if_node.op {
@ir.HotOp::If => ()
_ => return false
}
let else_ref = match @ir.hot_region_else_ref(func, if_id) {
Some(else_ref) => else_ref
None => return false
}
let then_ref = @ir.hot_region_then_ref(func, if_id)
if @ir.hot_region_root_count(func, then_ref) != 1 ||
@ir.hot_region_root_count(func, else_ref) != 1 {
return false
}
let then_root = match @ir.hot_region_single_plain_br_root(func, then_ref) {
Some(then_root) => then_root
None => return false
}
let else_root = match @ir.hot_region_single_plain_br_root(func, else_ref) {
Some(else_root) => else_root
None => return false
}
let then_target = match
@ir.hot_region_single_plain_br_target(func, then_ref) {
Some(then_target) => then_target
None => return false
}
let else_target = match
@ir.hot_region_single_plain_br_target(func, else_ref) {
Some(else_target) => else_target
None => return false
}
let if_label = @ir.hot_control_node_label(func, if_id)
if then_target == if_label || else_target == if_label {
return false
}
if then_target == else_target {
let condition = @ir.hot_child_get(func, if_id, 0)
let dropped_condition = @ir.hot_build_drop(func, condition)
let replacement = @ir.hot_build_br(func, then_target)
let removed = pass_splice_region(ctx, func, region_ref, idx, 1, [
dropped_condition, replacement,
])
removed.push(if_id)
removed.push(then_ref.holder)
removed.push(then_root)
removed.push(else_ref.holder)
removed.push(else_root)
remove_unused_brs_delete_unused_nodes(ctx, func, removed)
return true
}
let condition = @ir.hot_child_get(func, if_id, 0)
let branch_if = @ir.hot_build_br_if(func, then_target, condition)
let branch = @ir.hot_build_br(func, else_target)
let removed = pass_splice_region(ctx, func, region_ref, idx, 1, [
branch_if, branch,
])
remove_unused_brs_delete_unused_nodes(
ctx,
func,
removed + [if_id, then_ref.holder, then_root, else_ref.holder, else_root],
)
true
}
///|
fn remove_unused_brs_try_rewrite_one_arm_payload_branch_if(
ctx : HotPassContext,
func : @ir.HotFunc,
region_ref : @ir.HotRegionRef,
idx : Int32,
label_refs : Array[Int32],
func_has_br_table : Bool,
) -> Bool {
if func_has_br_table {
return false
}
if idx < 0 || idx >= @ir.hot_region_root_count(func, region_ref) {
return false
}
let if_id = @ir.hot_region_get(func, region_ref, idx)
if if_id < 0 || !@ir.hot_node_is_live(func, if_id) {
return false
}
let if_node = @ir.hot_node_get(func, if_id)
match if_node.op {
@ir.HotOp::If =>
if @ir.hot_node_result_arity(func, if_id) != 0 {
return false
}
_ => return false
}
let if_label = @ir.hot_control_node_label(func, if_id)
if remove_unused_brs_label_is_used(label_refs, if_label) {
return false
}
let condition = @ir.hot_child_get(func, if_id, 0)
let then_ref = @ir.hot_region_then_ref(func, if_id)
let else_ref = match @ir.hot_region_else_ref(func, if_id) {
Some(else_ref) => else_ref
None => return false
}
let try_build = fn(
branch_ref : @ir.HotRegionRef,
body_ref : @ir.HotRegionRef,
negate_condition : Bool,
) -> Bool {
let branch_root = match @ir.hot_region_single_live_root(func, branch_ref) {
Some(branch_root) => branch_root
None => return false
}
let branch_node = @ir.hot_node_get(func, branch_root)
match branch_node.op {
@ir.HotOp::Br => if branch_node.child_count == 0 { return false }
_ => return false
}
let target = match @ir.hot_branch_target(func, branch_root) {
Some(target) => target
None => return false
}
if target == if_label {
return false
}
match @ir.hot_region_holder_block_root(func, region_ref) {
Some(holder) =>
if idx == 0 &&
@ir.hot_node_result_arity(func, holder) == 1 &&
@ir.hot_region_root_count(func, body_ref) == 0 &&
@ir.hot_region_root_count(func, region_ref) >= 2 &&
target == @ir.hot_control_node_label(func, holder) {
return false
}
None => ()
}
if @ir.hot_label_branch_arity(func, target) != 1 {
return false
}
if remove_unused_brs_branch_payload_too_costly(ctx, func, branch_root) {
return false
}
let payloads : Array[Int32] = []
for child_idx = 0
child_idx < @ir.hot_child_count(func, branch_root)
child_idx = child_idx + 1 {
let child_id = @ir.hot_child_get(func, branch_root, child_idx)
if child_id < 0 || !@ir.hot_node_is_live(func, child_id) {
return false
}
payloads.push(child_id)
}
let guard_condition = if negate_condition {
remove_unused_brs_build_i32_eqz(func, condition)
} else {
condition
}
let guard_branch = @ir.hot_build_br_if(
func,
target,
guard_condition,
values=payloads,
)
let dropped_guard = @ir.hot_build_drop(func, guard_branch)
let replacement = [dropped_guard]
for root_idx = 0
root_idx < @ir.hot_region_root_count(func, body_ref)
root_idx = root_idx + 1 {
replacement.push(@ir.hot_region_get(func, body_ref, root_idx))
}
ignore(pass_splice_region(ctx, func, region_ref, idx, 1, replacement))
remove_unused_brs_delete_unused_nodes(ctx, func, [
if_id,
then_ref.holder,
else_ref.holder,
branch_root,
])
true
}
if try_build(then_ref, else_ref, false) {
return true
}
try_build(else_ref, then_ref, true)
}
///|
fn remove_unused_brs_try_rewrite_prefixed_one_arm_payload_branch_if_suffix(
ctx : HotPassContext,
func : @ir.HotFunc,
region_ref : @ir.HotRegionRef,
idx : Int32,
label_refs : Array[Int32],
reorder_cache : Array[Int32],
) -> Bool {
if @ir.hot_region_result_arity(func, region_ref) != 1 {
return false
}
let root_count = @ir.hot_region_root_count(func, region_ref)
if idx <= 0 || idx >= root_count - 1 {
return false
}
let holder = match @ir.hot_region_holder_block_root(func, region_ref) {
Some(holder) => holder
None => return false
}
if @ir.hot_node_result_arity(func, holder) != 1 {
return false
}
let if_id = @ir.hot_region_get(func, region_ref, idx)
if if_id < 0 || !@ir.hot_node_is_live(func, if_id) {
return false
}
let if_node = @ir.hot_node_get(func, if_id)
match if_node.op {
@ir.HotOp::If =>
if @ir.hot_node_result_arity(func, if_id) != 0 {
return false
}
_ => return false
}
match @ir.hot_region_else_ref(func, if_id) {
Some(_) => return false
None => ()
}
let if_label = @ir.hot_control_node_label(func, if_id)
if remove_unused_brs_label_is_used(label_refs, if_label) {
return false
}
let target = @ir.hot_control_node_label(func, holder)
let then_ref = @ir.hot_region_then_ref(func, if_id)
if @ir.hot_region_root_count(func, then_ref) != 1 {
return false
}
let branch_root = @ir.hot_region_get(func, then_ref, 0)
if branch_root < 0 || !@ir.hot_node_is_live(func, branch_root) {
return false
}
let branch_node = @ir.hot_node_get(func, branch_root)
match branch_node.op {
@ir.HotOp::Br if branch_node.imm0 == target => ()
_ => return false
}
// Check existing payload children before the value builder can allocate.
if remove_unused_brs_branch_payload_too_costly(ctx, func, branch_root) {
return false
}
let result_type = @ir.hot_type_results(func, @ir.hot_node_type(func, holder))[0]
let payload_value = match
remove_unused_brs_build_stack_exit_region_value_expr(
ctx,
func,
then_ref,
BranchTarget(target),
result_type,
reorder_cache,
) {
Some(payload_value) => payload_value
None => return false
}
let guard_branch = @ir.hot_build_br_if(
func,
target,
@ir.hot_child_get(func, if_id, 0),
values=[payload_value],
)
let dropped_guard = @ir.hot_build_drop(func, guard_branch)
ignore(pass_splice_region(ctx, func, region_ref, idx, 1, [dropped_guard]))
remove_unused_brs_delete_unused_nodes(ctx, func, [if_id, then_ref.holder])
true
}
///|
fn remove_unused_brs_try_rewrite_result_block_prefix_payload_branch(
ctx : HotPassContext,
func : @ir.HotFunc,
region_ref : @ir.HotRegionRef,
label_refs : Array[Int32],
branch_payload_children : @bitset.BitSet,
) -> Bool {
let holder = match @ir.hot_region_holder_block_root(func, region_ref) {
Some(holder) => holder
None => return false
}
if @ir.hot_node_result_arity(func, holder) <= 0 ||
@ir.hot_region_root_count(func, region_ref) <= 1 {
return false
}
let inner_id = @ir.hot_region_get(func, region_ref, 0)
if inner_id < 0 || !@ir.hot_node_is_live(func, inner_id) {
return false
}
match @ir.hot_node_get(func, inner_id).op {
@ir.HotOp::Block =>
if @ir.hot_node_result_arity(func, inner_id) != 0 {
(ctx.trace)(
"rub-result-prefix reject=inner-result block=\{inner_id} holder=\{holder}",
)
return false
}
_ => {
(ctx.trace)(
"rub-result-prefix reject=inner-op block=\{inner_id} op=\{@ir.hot_node_get(func, inner_id).op}",
)
return false
}
}
let inner_body_ref = @ir.hot_region_body_ref(func, inner_id)
let inner_root_count = @ir.hot_region_root_count(func, inner_body_ref)
if inner_root_count < 2 {
return false
}
let guard_id = @ir.hot_region_get(func, inner_body_ref, 0)
let branch_id = @ir.hot_region_get(func, inner_body_ref, inner_root_count - 1)
if guard_id < 0 ||
branch_id < 0 ||
!@ir.hot_node_is_live(func, guard_id) ||
!@ir.hot_node_is_live(func, branch_id) {
return false
}
let guard_node = @ir.hot_node_get(func, guard_id)
match guard_node.op {
@ir.HotOp::BrIf => if guard_node.child_count != 1 { return false }
_ => return false
}
let inner_label = @ir.hot_control_node_label(func, inner_id)
if inner_label < 0 || inner_label >= label_refs.length() {
return false
}
if @ir.hot_branch_target(func, guard_id) != Some(inner_label) {
(ctx.trace)(
"rub-result-prefix reject=guard-target inner=\{inner_id} guard=\{guard_id}",
)
return false
}
let outer_label = @ir.hot_control_node_label(func, holder)
let branch_node = @ir.hot_node_get(func, branch_id)
match branch_node.op {
@ir.HotOp::Br =>
if branch_node.child_count !=
@ir.hot_label_branch_arity(func, outer_label) {
(ctx.trace)(
"rub-result-prefix reject=branch-arity holder=\{holder} inner=\{inner_id} child_count=\{branch_node.child_count} outer_arity=\{@ir.hot_label_branch_arity(func, outer_label)}",
)
return false
}
_ => {
(ctx.trace)(
"rub-result-prefix reject=branch-op holder=\{holder} inner=\{inner_id} op=\{branch_node.op}",
)
return false
}
}
if @ir.hot_branch_target(func, branch_id) != Some(outer_label) {
(ctx.trace)(
"rub-result-prefix reject=branch-target holder=\{holder} inner=\{inner_id} branch=\{branch_id}",
)
return false
}
if holder >= 0 &&
holder < branch_payload_children.length() &&
branch_payload_children[holder] &&
inner_root_count == 2 {
(ctx.trace)(
"rub-result-prefix reject=payload-holder holder=\{holder} inner=\{inner_id}",
)
return false
}
if label_refs[inner_label] != 1 {
(ctx.trace)(
"rub-result-prefix reject=label-refs inner=\{inner_id} refs=\{label_refs[inner_label]}",
)
return false
}
let nop_id = @ir.hot_build_nop(func)
let replacement_body = [nop_id]
for root_idx = 1; root_idx < inner_root_count - 1; root_idx = root_idx + 1 {
replacement_body.push(@ir.hot_region_get(func, inner_body_ref, root_idx))
}
replacement_body.push(branch_id)
@ir.hot_region_replace_body(func, inner_body_ref, replacement_body)
let if_id = @ir.hot_build_if(
func,
@lib.BlockType::void_(),
remove_unused_brs_build_i32_eqz(func, @ir.hot_child_get(func, guard_id, 0)),
[inner_id],
)
ignore(pass_splice_region(ctx, func, region_ref, 0, 1, [if_id]))
remove_unused_brs_delete_unused_nodes(ctx, func, [guard_id])
(ctx.trace)(
"rub-result-prefix rewrite holder=\{holder} inner=\{inner_id} roots=\{inner_root_count}",
)
true
}
///|
fn remove_unused_brs_try_rewrite_drop_result_block_if_tail_branch(
ctx : HotPassContext,
func : @ir.HotFunc,
region_ref : @ir.HotRegionRef,
idx : Int32,
label_refs : Array[Int32],
) -> Bool {
if idx < 0 || idx >= @ir.hot_region_root_count(func, region_ref) {
return false
}
let drop_id = @ir.hot_region_get(func, region_ref, idx)
if drop_id < 0 || !@ir.hot_node_is_live(func, drop_id) {
return false
}
let drop_node = @ir.hot_node_get(func, drop_id)
match drop_node.op {
@ir.HotOp::Drop => if drop_node.child_count != 1 { return false }
_ => return false
}
let holder = @ir.hot_child_get(func, drop_id, 0)
if holder < 0 || !@ir.hot_node_is_live(func, holder) {
return false
}
match @ir.hot_node_get(func, holder).op {
@ir.HotOp::Block =>
if @ir.hot_node_result_arity(func, holder) != 1 {
return false
}
_ => return false
}
let holder_label = @ir.hot_control_node_label(func, holder)
if holder_label < 0 ||
holder_label >= label_refs.length() ||
label_refs[holder_label] != 0 {
return false
}
let body_ref = @ir.hot_region_body_ref(func, holder)
if @ir.hot_region_root_count(func, body_ref) != 2 {
return false
}
let if_id = @ir.hot_region_get(func, body_ref, 0)
let branch_id = @ir.hot_region_get(func, body_ref, 1)
if if_id < 0 ||
branch_id < 0 ||
!@ir.hot_node_is_live(func, if_id) ||
!@ir.hot_node_is_live(func, branch_id) {
return false
}
match @ir.hot_node_get(func, if_id).op {
@ir.HotOp::If =>
if @ir.hot_node_result_arity(func, if_id) != 0 {
return false
}
_ => return false
}
match @ir.hot_region_else_ref(func, if_id) {
Some(_) => return false
None => ()
}
let branch_node = @ir.hot_node_get(func, branch_id)
match branch_node.op {
@ir.HotOp::Br =>
if branch_node.child_count != @ir.hot_node_result_arity(func, holder) {
return false
}
_ => return false
}
let branch_target = match @ir.hot_branch_target(func, branch_id) {
Some(target) => target
None => return false
}
if branch_target == holder_label {
return false
}
let dropped_branch = @ir.hot_build_drop(func, branch_id)
let removed = pass_splice_region(ctx, func, region_ref, idx, 1, [
if_id, dropped_branch,
])
removed.push(holder)
remove_unused_brs_delete_unused_nodes(ctx, func, removed)
true
}
///|
fn remove_unused_brs_root_branch_tail(
func : @ir.HotFunc,
root_id : Int32,
) -> (Int32, Int32)? {
if root_id < 0 || !@ir.hot_node_is_live(func, root_id) {
return None
}
let root = @ir.hot_node_get(func, root_id)
match root.op {
@ir.HotOp::Br =>
match @ir.hot_branch_target(func, root_id) {
Some(target) => Some((root_id, target))
None => None
}
@ir.HotOp::Drop => {
if root.child_count != 1 {
return None
}
let child_id = @ir.hot_child_get(func, root_id, 0)
if child_id < 0 || !@ir.hot_node_is_live(func, child_id) {
return None
}
match @ir.hot_node_get(func, child_id).op {
@ir.HotOp::Br =>
match @ir.hot_branch_target(func, child_id) {
Some(target) => Some((child_id, target))
None => None
}
_ => None
}
}
_ => None
}
}
///|
fn remove_unused_brs_try_rewrite_block_prefix_payload_branch_root(
ctx : HotPassContext,
func : @ir.HotFunc,
region_ref : @ir.HotRegionRef,
idx : Int32,
label_refs : Array[Int32],
) -> Bool {
if idx < 0 || idx >= @ir.hot_region_root_count(func, region_ref) {
return false
}
let block_id = @ir.hot_region_get(func, region_ref, idx)
if block_id < 0 || !@ir.hot_node_is_live(func, block_id) {
return false
}
match @ir.hot_node_get(func, block_id).op {
@ir.HotOp::Block =>
if @ir.hot_node_result_arity(func, block_id) != 0 {
return false
}
_ => return false
}
let block_label = @ir.hot_control_node_label(func, block_id)
if block_label < 0 || block_label >= label_refs.length() {
return false
}
let body_ref = @ir.hot_region_body_ref(func, block_id)
let root_count = @ir.hot_region_root_count(func, body_ref)
if root_count < 2 {
return false
}
let guard_id = @ir.hot_region_get(func, body_ref, 0)
if guard_id < 0 || !@ir.hot_node_is_live(func, guard_id) {
return false
}
let guard_node = @ir.hot_node_get(func, guard_id)
match guard_node.op {
@ir.HotOp::BrIf => if guard_node.child_count != 1 { return false }
_ => return false
}
let label_ref_count = label_refs[block_label]
if label_ref_count != 1 {
if label_ref_count != 2 {
(ctx.trace)(
"rub-prefix-root reject=label-refs block=\{block_id} refs=\{label_ref_count}",
)
return false
}
let body_tail = @ir.hot_region_get(func, body_ref, root_count - 1)
match remove_unused_brs_root_branch_tail(func, body_tail) {
Some((tail_branch, target)) =>
if target != block_label ||
@ir.hot_node_get(func, tail_branch).child_count != 0 {
(ctx.trace)(
"rub-prefix-root reject=self-tail block=\{block_id} refs=\{label_ref_count} target=\{target}",
)
return false
}
None => {
(ctx.trace)(
"rub-prefix-root reject=missing-self-tail block=\{block_id} refs=\{label_ref_count}",
)
return false
}
}
}
if @ir.hot_branch_target(func, guard_id) != Some(block_label) {
(ctx.trace)(
"rub-prefix-root reject=guard-target block=\{block_id} guard=\{guard_id}",
)
return false
}
let mut matched_payload_tail = false
let tail_root = @ir.hot_region_get(func, body_ref, root_count - 1)
match remove_unused_brs_root_branch_tail(func, tail_root) {
Some((branch_id, target)) =>
if target != block_label {
let target_arity = @ir.hot_label_branch_arity(func, target)
let branch_child_count = @ir.hot_node_get(func, branch_id).child_count
if (target_arity > 0 && branch_child_count == target_arity) ||
branch_child_count == 0 {
matched_payload_tail = true
}
}
None => ()
}
if !matched_payload_tail &&
idx + 1 < @ir.hot_region_root_count(func, region_ref) {
let sibling_root = @ir.hot_region_get(func, region_ref, idx + 1)
match remove_unused_brs_root_branch_tail(func, sibling_root) {
Some((branch_id, target)) =>
if target != block_label {
let target_arity = @ir.hot_label_branch_arity(func, target)
let branch_child_count = @ir.hot_node_get(func, branch_id).child_count
if (target_arity > 0 && branch_child_count == target_arity) ||
branch_child_count == 0 {
matched_payload_tail = true
}
}
None => ()
}
}
if !matched_payload_tail &&
idx + 1 < @ir.hot_region_root_count(func, region_ref) {
let suffix_tail_root = @ir.hot_region_get(
func,
region_ref,
@ir.hot_region_root_count(func, region_ref) - 1,
)
match remove_unused_brs_root_branch_tail(func, suffix_tail_root) {
Some((branch_id, target)) =>
if target != block_label {
let target_arity = @ir.hot_label_branch_arity(func, target)
let branch_child_count = @ir.hot_node_get(func, branch_id).child_count
if (target_arity > 0 && branch_child_count == target_arity) ||
branch_child_count == 0 {
matched_payload_tail = true
}
}
None => ()
}
}
if !matched_payload_tail {
(ctx.trace)(
"rub-prefix-root reject=payload-tail block=\{block_id} roots=\{root_count}",
)
return false
}
let condition = remove_unused_brs_build_i32_eqz(
func,
@ir.hot_child_get(func, guard_id, 0),
)
let nop_id = @ir.hot_build_nop(func)
let replacement_body = [nop_id]
for root_idx = 1; root_idx < root_count; root_idx = root_idx + 1 {
replacement_body.push(@ir.hot_region_get(func, body_ref, root_idx))
}
@ir.hot_region_replace_body(func, body_ref, replacement_body)
let replacement = @ir.hot_build_if(func, @lib.BlockType::void_(), condition, [
block_id,
])
ignore(pass_splice_region(ctx, func, region_ref, idx, 1, [replacement]))
remove_unused_brs_delete_unused_nodes(ctx, func, [guard_id])
(ctx.trace)(
"rub-prefix-root rewrite block=\{block_id} roots=\{root_count} refs=\{label_ref_count}",
)
true
}
///|
fn remove_unused_brs_try_rewrite_result_block_one_arm_payload_if(
ctx : HotPassContext,
func : @ir.HotFunc,
current_site : RemoveUnusedBrsRootSite?,
block_id : Int32,
reorder_cache : Array[Int32],
label_refs : Array[Int32],
branch_payload_children : @bitset.BitSet,
) -> Bool {
if block_id < 0 || !@ir.hot_node_is_live(func, block_id) {
return false
}
match @ir.hot_node_get(func, block_id).op {
@ir.HotOp::Block =>
if @ir.hot_node_result_arity(func, block_id) != 1 {
return false
}
_ => return false
}
if block_id >= 0 &&
block_id < branch_payload_children.length() &&
branch_payload_children[block_id] {
return false
}
let block_label = @ir.hot_control_node_label(func, block_id)
if block_label < 0 ||
block_label >= label_refs.length() ||
label_refs[block_label] != 1 {
return false
}
let body_ref = @ir.hot_region_body_ref(func, block_id)
if @ir.hot_region_root_count(func, body_ref) < 2 {
return false
}
let result_arity = @ir.hot_node_result_arity(func, block_id)
if result_arity != 1 {
return false
}
let result_type = @ir.hot_type_results(
func,
@ir.hot_node_type(func, block_id),
)[0]
let fallthrough_roots : Array[Int32] = []
for root_idx = 1
root_idx < @ir.hot_region_root_count(func, body_ref)
root_idx = root_idx + 1 {
fallthrough_roots.push(@ir.hot_region_get(func, body_ref, root_idx))
}
match
@ir.hot_split_trailing_payload_roots(func, fallthrough_roots, result_arity) {
Some(_) => ()
None => return false
}
let direct_if_id = @ir.hot_region_get(func, body_ref, 0)
if direct_if_id >= 0 && @ir.hot_node_is_live(func, direct_if_id) {
match @ir.hot_node_get(func, direct_if_id).op {
@ir.HotOp::If =>
if @ir.hot_node_result_arity(func, direct_if_id) == 0 {
let then_ref = @ir.hot_region_then_ref(func, direct_if_id)
match @ir.hot_region_else_ref(func, direct_if_id) {
Some(_) => ()
None => {
let branch_value = match
remove_unused_brs_build_stack_exit_region_value_expr(
ctx,
func,
then_ref,
BranchTarget(block_label),
result_type,
reorder_cache,
) {
Some(branch_value) => branch_value
None =>
match
remove_unused_brs_build_stack_exit_region_value_body(
func,
then_ref,
BranchTarget(block_label),
result_arity,
) {
Some(branch_body) =>
if branch_body.length() > 1 &&
remove_unused_brs_roots_have_embedded_control(
func, branch_body,
) {
return false
} else {
@ir.hot_build_block(
func,
@lib.BlockType::val_type(result_type),
branch_body,
)
}
None => return false
}
}
let replacement = @ir.hot_build_if(
func,
@lib.BlockType::val_type(result_type),
@ir.hot_child_get(func, direct_if_id, 0),
[branch_value],
else_body=Some(fallthrough_roots),
)
let site = match current_site {
Some(site) => site
None => return false
}
let removed = pass_splice_region(
ctx,
func,
site.region_ref,
site.idx,
1,
[replacement],
)
removed.push(direct_if_id)
removed.push(then_ref.holder)
remove_unused_brs_delete_unused_nodes(ctx, func, removed)
return true
}
}
}
_ => ()
}
}
// Binaryen keeps carried wrapper blocks for the remaining one-arm payload
// families here, so RUB leaves them intact for parity.
return false
}
///|
fn remove_unused_brs_region_roots_are_reorder_safe(
ctx : HotPassContext,
func : @ir.HotFunc,
roots : Array[Int32],
reorder_cache : Array[Int32],
) -> Bool {
for root_id in roots {
if @ir.cfg_terminator_edge_kinds(@ir.hot_node_get(func, root_id).op).length() >
0 {
return false
}
if !remove_unused_brs_node_is_reorder_safe(
ctx, func, root_id, reorder_cache,
) {
return false
}
}
true
}
///|
fn remove_unused_brs_roots_have_embedded_control(
func : @ir.HotFunc,
roots : Array[Int32],
) -> Bool {
let control_cache : Array[Int32] = []
for root_id in roots {
if remove_unused_brs_subtree_has_embedded_control(
func, root_id, control_cache,
) {
return true
}
}
false
}
///|
fn remove_unused_brs_build_region_value_expr(
ctx : HotPassContext,
func : @ir.HotFunc,
roots : Array[Int32],
result_type : @lib.ValType,
reorder_cache : Array[Int32],
) -> Int32? {
let (prefix, payload) = match
@ir.hot_split_trailing_payload_roots(func, roots, 1) {
Some(split) => split
None => return None
}
if !remove_unused_brs_region_roots_are_reorder_safe(
ctx,
func,
prefix + payload,
reorder_cache,
) {
return None
}
if prefix.is_empty() && payload.length() == 1 {
return Some(payload[0])
}
let block_roots : Array[Int32] = []
for root_id in prefix {
block_roots.push(root_id)
}
for root_id in payload {
block_roots.push(root_id)
}
Some(
@ir.hot_build_block(
func,
@lib.BlockType::val_type(result_type),
block_roots,
),
)
}
///|
fn remove_unused_brs_build_stack_exit_region_value_body(
func : @ir.HotFunc,
region_ref : @ir.HotRegionRef,
target : RemoveUnusedBrsTailTarget,
result_arity : Int32,
) -> Array[Int32]? {
if result_arity <= 0 {
return None
}
let roots = remove_unused_brs_region_roots(func, region_ref)
if roots.is_empty() {
return None
}
let tail_id = roots[roots.length() - 1]
let tail = @ir.hot_node_get(func, tail_id)
match target {
ReturnTarget =>
match tail.op {
@ir.HotOp::Return => ()
_ => return None
}
BranchTarget(label_id) =>
match tail.op {
@ir.HotOp::Br if tail.imm0 == label_id => ()
_ => return None
}
NoTarget => return None
}
if roots.length() > 1 {
return None
}
let body_roots : Array[Int32] = []
for idx = 0; idx < roots.length() - 1; idx = idx + 1 {
body_roots.push(roots[idx])
}
for child_idx = 0; child_idx < tail.child_count; child_idx = child_idx + 1 {
let child_id = @ir.hot_child_get(func, tail_id, child_idx)
if child_id < 0 || !@ir.hot_node_is_live(func, child_id) {
return None
}
body_roots.push(child_id)
}
match @ir.hot_split_trailing_payload_roots(func, body_roots, result_arity) {
Some(_) => Some(body_roots)
None => None
}
}
///|
fn remove_unused_brs_build_stack_exit_region_value_expr(
ctx : HotPassContext,
func : @ir.HotFunc,
region_ref : @ir.HotRegionRef,
target : RemoveUnusedBrsTailTarget,
result_type : @lib.ValType,
reorder_cache : Array[Int32],
) -> Int32? {
let roots = remove_unused_brs_region_roots(func, region_ref)
if roots.is_empty() {
return None
}
let tail_id = roots[roots.length() - 1]
let tail = @ir.hot_node_get(func, tail_id)
match target {
ReturnTarget =>
match tail.op {
@ir.HotOp::Return => ()
_ => return None
}
BranchTarget(label_id) =>
match tail.op {
@ir.HotOp::Br if tail.imm0 == label_id => ()
_ => return None
}
NoTarget => return None
}
if roots.length() > 1 {
return None
}
let payload_roots : Array[Int32] = []
for idx = 0; idx < roots.length() - 1; idx = idx + 1 {
payload_roots.push(roots[idx])
}
for child_idx = 0; child_idx < tail.child_count; child_idx = child_idx + 1 {
let child_id = @ir.hot_child_get(func, tail_id, child_idx)
if child_id >= 0 && @ir.hot_node_is_live(func, child_id) {
payload_roots.push(child_id)
}
}
if payload_roots.length() > 1 &&
remove_unused_brs_roots_have_embedded_control(func, payload_roots) {
return None
}
remove_unused_brs_build_region_value_expr(
ctx, func, payload_roots, result_type, reorder_cache,
)
}
///|
fn remove_unused_brs_try_rewrite_one_sided_stack_tail_if_to_select(
ctx : HotPassContext,
func : @ir.HotFunc,
region_ref : @ir.HotRegionRef,
reorder_cache : Array[Int32],
label_refs : Array[Int32],
) -> Bool {
let root_count = @ir.hot_region_root_count(func, region_ref)
if root_count == 0 {
return false
}
let tail_idx = root_count - 1
let if_id = @ir.hot_region_get(func, region_ref, tail_idx)
if if_id < 0 || !@ir.hot_node_is_live(func, if_id) {
return false
}
let if_node = @ir.hot_node_get(func, if_id)
match if_node.op {
@ir.HotOp::If =>
if @ir.hot_node_result_arity(func, if_id) != 1 {
return false
}
_ => return false
}
let if_label = @ir.hot_control_node_label(func, if_id)
if remove_unused_brs_label_is_used(label_refs, if_label) {
return false
}
let condition = @ir.hot_child_get(func, if_id, 0)
let target = remove_unused_brs_tail_target(func, region_ref)
match target {
NoTarget => return false
_ => ()
}
let then_ref = @ir.hot_region_then_ref(func, if_id)
let else_ref = match @ir.hot_region_else_ref(func, if_id) {
Some(else_ref) => else_ref
None => return false
}
let result_type = @ir.hot_type_results(func, @ir.hot_node_type(func, if_id))[0]
let try_build = fn(then_value : Int32?, else_value : Int32?) -> Bool {
let then_value = match then_value {
Some(expr) => expr
None => return false
}
let else_value = match else_value {
Some(expr) => expr
None => return false
}
let replacement = remove_unused_brs_build_select(
func, result_type, then_value, else_value, condition,
)
remove_unused_brs_replace_unused_if_with_value_expr(
ctx,
func,
if_id,
replacement,
[then_ref.holder, else_ref.holder],
)
}
if try_build(
remove_unused_brs_build_stack_exit_region_value_expr(
ctx, func, then_ref, target, result_type, reorder_cache,
),
remove_unused_brs_build_region_value_expr(
ctx,
func,
remove_unused_brs_region_roots(func, else_ref),
result_type,
reorder_cache,
),
) {
return true
}
try_build(
remove_unused_brs_build_region_value_expr(
ctx,
func,
remove_unused_brs_region_roots(func, then_ref),
result_type,
reorder_cache,
),
remove_unused_brs_build_stack_exit_region_value_expr(
ctx, func, else_ref, target, result_type, reorder_cache,
),
)
}
///|
fn remove_unused_brs_label_is_used(
label_refs : Array[Int32],
label_id : Int32,
) -> Bool {
label_id >= 0 && label_id < label_refs.length() && label_refs[label_id] > 0
}
///|
fn remove_unused_brs_try_rewrite_branch_payload_if(
ctx : HotPassContext,
func : @ir.HotFunc,
region_ref : @ir.HotRegionRef,
idx : Int32,
label_refs : Array[Int32],
) -> Bool {
if idx < 0 || idx >= @ir.hot_region_root_count(func, region_ref) {
return false
}
let br_id = @ir.hot_region_get(func, region_ref, idx)
let br_node = @ir.hot_node_get(func, br_id)
let if_id = match br_node.op {
@ir.HotOp::Br =>
match @ir.hot_node_repeated_live_child(func, br_id) {
Some(candidate) => candidate
None => return false
}
_ => return false
}
if if_id < 0 || !@ir.hot_node_is_live(func, if_id) {
return false
}
let if_node = @ir.hot_node_get(func, if_id)
match if_node.op {
@ir.HotOp::If =>
if @ir.hot_node_result_arity(func, if_id) <= 0 {
return false
}
_ => return false
}
let else_ref = match @ir.hot_region_else_ref(func, if_id) {
Some(else_ref) => else_ref
None => return false
}
let then_ref = @ir.hot_region_then_ref(func, if_id)
let result_arity = @ir.hot_node_result_arity(func, if_id)
if br_node.child_count != result_arity {
return false
}
let if_label = @ir.hot_control_node_label(func, if_id)
if remove_unused_brs_label_is_used(label_refs, if_label) {
return false
}
let try_build = fn(
branch_ref : @ir.HotRegionRef,
body_ref : @ir.HotRegionRef,
negate_condition : Bool,
) -> Bool {
ignore(
match @ir.hot_region_single_plain_br_root(func, branch_ref) {
Some(branch_root) => branch_root
None => return false
},
)
let branch_target = match
@ir.hot_region_single_plain_br_target(func, branch_ref) {
Some(branch_target) => branch_target
None => return false
}
if branch_target == if_label {
return false
}
let (body_prefix, body_payload) = match
@ir.hot_region_split_trailing_payload_roots(func, body_ref, result_arity) {
Some(split) => split
None => return false
}
if body_payload.length() != br_node.child_count {
return false
}
let guard_condition = if negate_condition {
remove_unused_brs_build_i32_eqz(func, @ir.hot_child_get(func, if_id, 0))
} else {
@ir.hot_child_get(func, if_id, 0)
}
let guard_branch = @ir.hot_build_br_if(func, branch_target, guard_condition)
let exit_branch = @ir.hot_build_br(func, br_node.imm0, values=body_payload)
let replacement = [guard_branch]
for root_id in body_prefix {
replacement.push(root_id)
}
replacement.push(exit_branch)
let removed = pass_splice_region(ctx, func, region_ref, idx, 1, replacement)
removed.push(br_id)
removed.push(if_id)
removed.push(then_ref.holder)
removed.push(else_ref.holder)
remove_unused_brs_delete_unused_nodes(ctx, func, removed)
true
}
if try_build(else_ref, then_ref, true) {
return true
}
try_build(then_ref, else_ref, false)
}
///|
fn remove_unused_brs_try_rewrite_tail_value_if_branch_exit(
ctx : HotPassContext,
func : @ir.HotFunc,
region_ref : @ir.HotRegionRef,
label_refs : Array[Int32],
) -> Bool {
if @ir.hot_region_root_count(func, region_ref) == 0 {
return false
}
let tail_idx = @ir.hot_region_root_count(func, region_ref) - 1
let if_id = @ir.hot_region_get(func, region_ref, tail_idx)
if if_id < 0 || !@ir.hot_node_is_live(func, if_id) {
return false
}
let if_node = @ir.hot_node_get(func, if_id)
match if_node.op {
@ir.HotOp::If =>
if @ir.hot_node_result_arity(func, if_id) <= 0 {
return false
}
_ => return false
}
let else_ref = match @ir.hot_region_else_ref(func, if_id) {
Some(else_ref) => else_ref
None => return false
}
let then_ref = @ir.hot_region_then_ref(func, if_id)
let result_arity = @ir.hot_node_result_arity(func, if_id)
let if_label = @ir.hot_control_node_label(func, if_id)
if remove_unused_brs_label_is_used(label_refs, if_label) {
return false
}
let try_build = fn(
branch_ref : @ir.HotRegionRef,
body_ref : @ir.HotRegionRef,
negate_condition : Bool,
) -> Bool {
let branch_target = match
@ir.hot_region_single_plain_br_target(func, branch_ref) {
Some(branch_target) => branch_target
None => return false
}
if branch_target == if_label {
return false
}
let (body_prefix, body_payload) = match
@ir.hot_region_split_trailing_payload_roots(func, body_ref, result_arity) {
Some(split) => split
None => return false
}
let guard_condition = if negate_condition {
remove_unused_brs_build_i32_eqz(func, @ir.hot_child_get(func, if_id, 0))
} else {
@ir.hot_child_get(func, if_id, 0)
}
let guard_branch = @ir.hot_build_br_if(func, branch_target, guard_condition)
let replacement = [guard_branch]
for root_id in body_prefix {
replacement.push(root_id)
}
for payload_id in body_payload {
replacement.push(payload_id)
}
let removed = pass_splice_region(
ctx, func, region_ref, tail_idx, 1, replacement,
)
remove_unused_brs_delete_unused_nodes(
ctx,
func,
removed + [if_id, then_ref.holder, else_ref.holder],
)
true
}
if try_build(else_ref, then_ref, true) {
return true
}
try_build(then_ref, else_ref, false)
}
///|
fn remove_unused_brs_try_rewrite_tail_value_if_simple_payload_arm(
ctx : HotPassContext,
func : @ir.HotFunc,
region_ref : @ir.HotRegionRef,
label_refs : Array[Int32],
) -> Bool {
ignore(ctx)
ignore(func)
ignore(region_ref)
ignore(label_refs)
// Generated ordered -O4z slot40 still corrupts stack state after this rewrite
// composes with later lowering/restructuring steps. Keep it retired until the
// valid-context contract is reduced and re-landed under focused oracle-backed
// coverage.
false
}
///|
fn remove_unused_brs_try_rewrite_return_child_if_branch_exit(
ctx : HotPassContext,
func : @ir.HotFunc,
if_id : Int32,
label_refs : Array[Int32],
) -> Bool {
if if_id < 0 || !@ir.hot_node_is_live(func, if_id) {
return false
}
let if_node = @ir.hot_node_get(func, if_id)
match if_node.op {
@ir.HotOp::If =>
if @ir.hot_node_result_arity(func, if_id) != 1 {
return false
}
_ => return false
}
let then_ref = @ir.hot_region_then_ref(func, if_id)
let mut changed = false
if remove_unused_brs_region_maybe_has_tail_value_if_branch_exit(
func, then_ref,
) {
changed = remove_unused_brs_try_rewrite_tail_value_if_branch_exit(
ctx, func, then_ref, label_refs,
)
}
match @ir.hot_region_else_ref(func, if_id) {
Some(else_ref) =>
if remove_unused_brs_region_maybe_has_tail_value_if_branch_exit(
func, else_ref,
) {
changed = remove_unused_brs_try_rewrite_tail_value_if_branch_exit(
ctx, func, else_ref, label_refs,
) ||
changed
}
None => ()
}
changed
}
///|
fn remove_unused_brs_region_maybe_has_tail_value_if_branch_exit(
func : @ir.HotFunc,
region_ref : @ir.HotRegionRef,
) -> Bool {
if @ir.hot_region_root_count(func, region_ref) <= 1 {
return false
}
let tail_id = remove_unused_brs_region_tail_id(func, region_ref)
if tail_id < 0 || !@ir.hot_node_is_live(func, tail_id) {
return false
}
let tail_node = @ir.hot_node_get(func, tail_id)
match tail_node.op {
@ir.HotOp::If =>
if @ir.hot_node_result_arity(func, tail_id) != 1 {
return false
}
_ => return false
}
let else_ref = match @ir.hot_region_else_ref(func, tail_id) {
Some(else_ref) => else_ref
None => return false
}
let then_ref = @ir.hot_region_then_ref(func, tail_id)
let has_plain_branch_arm = fn(arm_ref : @ir.HotRegionRef) -> Bool {
match @ir.hot_region_single_plain_br_root(func, arm_ref) {
Some(_) => true
None => false
}
}
has_plain_branch_arm(then_ref) || has_plain_branch_arm(else_ref)
}
///|
fn remove_unused_brs_try_flatten_block_if_chain(
ctx : HotPassContext,
func : @ir.HotFunc,
region_ref : @ir.HotRegionRef,
idx : Int32,
label_refs : Array[Int32],
) -> Bool {
if region_ref == @ir.HotRegionRef::root() ||
idx < 0 ||
idx >= @ir.hot_region_root_count(func, region_ref) {
return false
}
let holder = match @ir.hot_region_holder_block_root(func, region_ref) {
Some(holder) => holder
None => return false
}
let if_id = @ir.hot_region_get(func, region_ref, idx)
let if_node = @ir.hot_node_get(func, if_id)
match if_node.op {
@ir.HotOp::If =>
if @ir.hot_node_result_arity(func, if_id) != 0 {
return false
}
_ => return false
}
let else_ref = match @ir.hot_region_else_ref(func, if_id) {
Some(else_ref) => else_ref
None => return false
}
let then_ref = @ir.hot_region_then_ref(func, if_id)
let if_label = @ir.hot_control_node_label(func, if_id)
if remove_unused_brs_label_is_used(label_refs, if_label) {
return false
}
let target = @ir.hot_control_node_label(func, holder)
let condition = @ir.hot_child_get(func, if_id, 0)
match @ir.hot_region_single_plain_br_root(func, then_ref) {
Some(then_root) =>
if @ir.hot_region_single_plain_br_target(func, then_ref) == Some(target) {
let branch = @ir.hot_build_br_if(func, target, condition)
let replacement = [branch]
for root_idx = 0
root_idx < @ir.hot_region_root_count(func, else_ref)
root_idx = root_idx + 1 {
replacement.push(@ir.hot_region_get(func, else_ref, root_idx))
}
ignore(pass_splice_region(ctx, func, region_ref, idx, 1, replacement))
remove_unused_brs_delete_unused_nodes(ctx, func, [
if_id,
then_ref.holder,
then_root,
else_ref.holder,
])
return true
}
None => ()
}
match @ir.hot_region_single_plain_br_root(func, else_ref) {
Some(else_root) =>
if @ir.hot_region_single_plain_br_target(func, else_ref) == Some(target) {
let branch = @ir.hot_build_br_if(
func,
target,
remove_unused_brs_build_i32_eqz(func, condition),
)
let replacement = [branch]
for root_idx = 0
root_idx < @ir.hot_region_root_count(func, then_ref)
root_idx = root_idx + 1 {
replacement.push(@ir.hot_region_get(func, then_ref, root_idx))
}
ignore(pass_splice_region(ctx, func, region_ref, idx, 1, replacement))
remove_unused_brs_delete_unused_nodes(ctx, func, [
if_id,
then_ref.holder,
else_ref.holder,
else_root,
])
return true
}
None => ()
}
false
}
///|
fn remove_unused_brs_flatten_block_if_chains(
ctx : HotPassContext,
func : @ir.HotFunc,
region_ref : @ir.HotRegionRef,
label_refs : Array[Int32],
) -> Bool {
match @ir.hot_region_holder_block_root(func, region_ref) {
Some(_) => ()
None => return false
}
let mut changed = false
let mut idx = 0
while idx < @ir.hot_region_root_count(func, region_ref) {
if remove_unused_brs_try_flatten_block_if_chain(
ctx, func, region_ref, idx, label_refs,
) {
changed = true
continue
}
idx += 1
}
changed
}
///|
fn remove_unused_brs_tail_fallthrough_last_live_root_idx(
func : @ir.HotFunc,
region_ref : @ir.HotRegionRef,
) -> Int32 {
let mut idx = @ir.hot_region_root_count(func, region_ref) - 1
while idx >= 0 {
let node_id = @ir.hot_region_get(func, region_ref, idx)
if node_id >= 0 && @ir.hot_node_is_live(func, node_id) {
match @ir.hot_node_get(func, node_id).op {
@ir.HotOp::Unreachable => idx = idx - 1
_ => return idx
}
} else {
idx = idx - 1
}
}
-1
}
///|
fn remove_unused_brs_strip_region_suffix_after(
ctx : HotPassContext,
func : @ir.HotFunc,
region_ref : @ir.HotRegionRef,
idx : Int32,
) -> Bool {
let root_count = @ir.hot_region_root_count(func, region_ref)
if idx < 0 || idx + 1 >= root_count {
return false
}
for suffix_idx = idx + 1; suffix_idx < root_count; suffix_idx = suffix_idx + 1 {
let node_id = @ir.hot_region_get(func, region_ref, suffix_idx)
if node_id < 0 || !@ir.hot_node_is_live(func, node_id) {
continue
}
match @ir.hot_node_get(func, node_id).op {
@ir.HotOp::Unreachable => ()
_ => return false
}
}
let removed = pass_splice_region(
ctx,
func,
region_ref,
idx + 1,
root_count - idx - 1,
[],
)
remove_unused_brs_delete_unused_nodes(ctx, func, removed)
true
}
///|
fn remove_unused_brs_try_void_tail_if_return_to_fallthrough(
ctx : HotPassContext,
func : @ir.HotFunc,
if_id : Int32,
label_refs : Array[Int32],
) -> Bool {
if @ir.hot_type_result_arity(func, @ir.hot_body_result_type(func)) != 0 {
return false
}
if if_id < 0 || !@ir.hot_node_is_live(func, if_id) {
return false
}
match @ir.hot_node_get(func, if_id).op {
@ir.HotOp::If =>
if @ir.hot_node_result_arity(func, if_id) != 0 {
return false
}
_ => return false
}
let if_label = @ir.hot_control_node_label(func, if_id)
if remove_unused_brs_label_is_used(label_refs, if_label) {
return false
}
let else_ref = match @ir.hot_region_else_ref(func, if_id) {
Some(else_ref) => else_ref
None => return false
}
let then_ref = @ir.hot_region_then_ref(func, if_id)
let try_arm = fn(
return_ref : @ir.HotRegionRef,
exit_ref : @ir.HotRegionRef,
) -> Bool {
if @ir.hot_region_root_count(func, return_ref) != 1 {
return false
}
let return_id = @ir.hot_region_get(func, return_ref, 0)
if return_id < 0 || !@ir.hot_node_is_live(func, return_id) {
return false
}
let return_node = @ir.hot_node_get(func, return_id)
match return_node.op {
@ir.HotOp::Return => if return_node.child_count != 0 { return false }
_ => return false
}
if !remove_unused_brs_tail_is_nonfallthrough(func, exit_ref) {
return false
}
let nop_id = @ir.hot_build_nop(func)
let removed = pass_splice_region(ctx, func, return_ref, 0, 1, [nop_id])
removed.push(return_id)
remove_unused_brs_delete_unused_nodes(ctx, func, removed)
true
}
try_arm(then_ref, else_ref) || try_arm(else_ref, then_ref)
}
///|
fn remove_unused_brs_try_tail_loop_return_to_fallthrough(
ctx : HotPassContext,
func : @ir.HotFunc,
region_ref : @ir.HotRegionRef,
label_refs : Array[Int32],
) -> Bool {
let idx = remove_unused_brs_tail_fallthrough_last_live_root_idx(
func, region_ref,
)
if idx < 0 {
return false
}
let node_id = @ir.hot_region_get(func, region_ref, idx)
if node_id < 0 || !@ir.hot_node_is_live(func, node_id) {
return false
}
let mut changed = false
match @ir.hot_node_get(func, node_id).op {
@ir.HotOp::Loop => {
if @ir.hot_node_result_arity(func, node_id) != 0 {
return false
}
changed = remove_unused_brs_try_tail_loop_return_to_fallthrough(
ctx,
func,
@ir.hot_region_body_ref(func, node_id),
label_refs,
)
}
@ir.HotOp::If =>
changed = remove_unused_brs_try_void_tail_if_return_to_fallthrough(
ctx, func, node_id, label_refs,
)
_ => return false
}
if changed {
ignore(
remove_unused_brs_strip_region_suffix_after(ctx, func, region_ref, idx),
)
}
changed
}
///|
fn remove_unused_brs_try_invert_empty_then_if(
ctx : HotPassContext,
func : @ir.HotFunc,
node_id : Int32,
label_refs : Array[Int32],
) -> Bool {
if node_id < 0 || !@ir.hot_node_is_live(func, node_id) {
return false
}
match @ir.hot_node_get(func, node_id).op {
@ir.HotOp::If =>
if @ir.hot_node_result_arity(func, node_id) != 0 {
return false
}
_ => return false
}
let if_label = @ir.hot_control_node_label(func, node_id)
if remove_unused_brs_label_is_used(label_refs, if_label) {
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(else_ref) => else_ref
None => return false
}
let else_roots = remove_unused_brs_region_roots(func, else_ref)
if else_roots.is_empty() {
return false
}
let inverted = remove_unused_brs_build_i32_eqz(
func,
@ir.hot_child_get(func, node_id, 0),
)
@ir.hot_region_replace_body(func, then_ref, else_roots)
@ir.hot_region_replace_body(func, else_ref, [])
@ir.hot_child_set(func, node_id, 0, inverted)
@ir.hot_child_set(func, node_id, 2, -1)
remove_unused_brs_mark_mutated(ctx, func, "invert-empty-then-if")
true
}
///|
fn remove_unused_brs_trim_plain_block_exit_arm(
func : @ir.HotFunc,
arm_ref : @ir.HotRegionRef,
target_label : Int32,
) -> Array[Int32]? {
let root_count = @ir.hot_region_root_count(func, arm_ref)
if root_count == 0 {
return None
}
let tail_id = @ir.hot_region_get(func, arm_ref, root_count - 1)
if tail_id < 0 || !@ir.hot_node_is_live(func, tail_id) {
return None
}
let tail = @ir.hot_node_get(func, tail_id)
match tail.op {
@ir.HotOp::Br if @ir.hot_branch_target(func, tail_id) == Some(target_label) &&
tail.child_count == 0 => ()
_ => return None
}
let body : Array[Int32] = []
for idx = 0; idx < root_count - 1; idx = idx + 1 {
let root_id = @ir.hot_region_get(func, arm_ref, idx)
if root_id < 0 || !@ir.hot_node_is_live(func, root_id) {
continue
}
body.push(root_id)
}
Some(body)
}
///|
fn remove_unused_brs_return_arm_roots_are_void_safe(
func : @ir.HotFunc,
arm_ref : @ir.HotRegionRef,
return_tail : Int32,
) -> Bool {
for idx = 0; idx < @ir.hot_region_root_count(func, arm_ref); idx = idx + 1 {
let root_id = @ir.hot_region_get(func, arm_ref, idx)
if root_id < 0 || !@ir.hot_node_is_live(func, root_id) {
continue
}
if root_id == return_tail {
continue
}
if @ir.hot_node_result_arity(func, root_id) != 0 {
return false
}
}
true
}
///|
fn remove_unused_brs_region_suffix_is_unreachable_only(
func : @ir.HotFunc,
region_ref : @ir.HotRegionRef,
start_idx : Int32,
) -> Bool {
let root_count = @ir.hot_region_root_count(func, region_ref)
if start_idx < 0 || start_idx >= root_count {
return false
}
let mut saw_unreachable = false
for idx = start_idx; idx < root_count; idx = idx + 1 {
let node_id = @ir.hot_region_get(func, region_ref, idx)
if node_id < 0 || !@ir.hot_node_is_live(func, node_id) {
continue
}
match @ir.hot_node_get(func, node_id).op {
@ir.HotOp::Nop => ()
@ir.HotOp::Unreachable => saw_unreachable = true
_ => return false
}
}
saw_unreachable
}
///|
fn remove_unused_brs_region_suffix_is_fallthrough_debris(
func : @ir.HotFunc,
region_ref : @ir.HotRegionRef,
start_idx : Int32,
) -> Bool {
let root_count = @ir.hot_region_root_count(func, region_ref)
if start_idx < 0 || start_idx > root_count {
return false
}
for idx = start_idx; idx < root_count; idx = idx + 1 {
let node_id = @ir.hot_region_get(func, region_ref, idx)
if node_id < 0 || !@ir.hot_node_is_live(func, node_id) {
continue
}
match @ir.hot_node_get(func, node_id).op {
@ir.HotOp::Nop | @ir.HotOp::Unreachable => ()
_ => return false
}
}
true
}
///|
fn remove_unused_brs_try_fallthrough_caught_try_table_outer_exit(
ctx : HotPassContext,
func : @ir.HotFunc,
region_ref : @ir.HotRegionRef,
idx : Int32,
) -> Bool {
let root_count = @ir.hot_region_root_count(func, region_ref)
if idx < 0 || idx >= root_count {
return false
}
let try_table_id = @ir.hot_region_get(func, region_ref, idx)
if try_table_id < 0 ||
!@ir.hot_node_is_live(func, try_table_id) ||
@ir.hot_node_result_arity(func, try_table_id) != 0 {
return false
}
let try_table = @ir.hot_node_get(func, try_table_id)
match try_table.op {
@ir.HotOp::TryTable => ()
_ => return false
}
let catch_info = @ir.hot_catch_info_get(func, try_table.imm1)
let catch_label = match catch_info.catches {
[@ir.HotCatchArm::CatchArm(_, catch_label)] => catch_label
_ => return false
}
let body_ref = @ir.hot_region_body_ref(func, try_table_id)
if @ir.hot_region_root_count(func, body_ref) != 1 {
return false
}
let branch_id = @ir.hot_region_get(func, body_ref, 0)
if branch_id < 0 || !@ir.hot_node_is_live(func, branch_id) {
return false
}
let branch = @ir.hot_node_get(func, branch_id)
match branch.op {
@ir.HotOp::Br if branch.child_count == 0 => ()
_ => return false
}
if @ir.hot_branch_target(func, branch_id) != Some(catch_label) {
return false
}
let use_def = pass_require_use_def(ctx, func)
let debris_regions : Array[@ir.HotRegionRef] = []
let debris_starts : Array[Int32] = []
let mut exit_region = region_ref
let mut exit_root_idx = idx
let mut reached_target = false
for _hop = 0; _hop < 64; _hop = _hop + 1 {
let exit_root_count = @ir.hot_region_root_count(func, exit_region)
if exit_root_idx < 0 ||
exit_root_idx >= exit_root_count ||
!remove_unused_brs_region_suffix_is_fallthrough_debris(
func,
exit_region,
exit_root_idx + 1,
) {
return false
}
if exit_root_idx + 1 < exit_root_count {
debris_regions.push(exit_region)
debris_starts.push(exit_root_idx + 1)
}
let holder = match @ir.hot_region_holder_block_root(func, exit_region) {
Some(holder) => holder
None => return false
}
if @ir.hot_node_result_arity(func, holder) != 0 {
return false
}
if @ir.hot_control_node_label(func, holder) == catch_label {
reached_target = true
break
}
if @ir.node_use_count(use_def, holder) != 1 {
return false
}
let sites = @ir.node_use_sites_borrowed(use_def, holder)
if sites.length() != 1 {
return false
}
let site = sites[0]
if site.user < 0 {
return false
}
let parent_region = match site.kind {
@ir.HotUseSlotKind::RootUseSlot =>
remove_unused_brs_root_use_region_ref(
func,
holder,
site.user,
site.slot,
)
@ir.HotUseSlotKind::ChildUseSlot => None
}
exit_region = match parent_region {
Some(parent_region) => parent_region
None => return false
}
exit_root_idx = site.slot
}
if !reached_target {
return false
}
let removed_body = pass_splice_region(ctx, func, body_ref, 0, 1, [])
remove_unused_brs_delete_unused_nodes(ctx, func, removed_body)
for debris_idx = 0
debris_idx < debris_regions.length()
debris_idx = debris_idx + 1 {
let debris_region = debris_regions[debris_idx]
let debris_start = debris_starts[debris_idx]
let removed_suffix = pass_splice_region(
ctx,
func,
debris_region,
debris_start,
@ir.hot_region_root_count(func, debris_region) - debris_start,
[],
)
remove_unused_brs_delete_unused_nodes(ctx, func, removed_suffix)
}
true
}
///|
fn remove_unused_brs_try_voidify_if_block_exit_unreachable_suffix(
ctx : HotPassContext,
func : @ir.HotFunc,
region_ref : @ir.HotRegionRef,
idx : Int32,
label_refs : Array[Int32],
) -> Bool {
let root_count = @ir.hot_region_root_count(func, region_ref)
if idx < 0 || idx >= root_count - 1 {
return false
}
if !remove_unused_brs_region_suffix_is_unreachable_only(
func,
region_ref,
idx + 1,
) {
return false
}
let if_id = @ir.hot_region_get(func, region_ref, idx)
if if_id < 0 || !@ir.hot_node_is_live(func, if_id) {
return false
}
match @ir.hot_node_get(func, if_id).op {
@ir.HotOp::If =>
if @ir.hot_node_result_arity(func, if_id) != 0 {
return false
}
_ => return false
}
let target_label = match remove_unused_brs_tail_target(func, region_ref) {
BranchTarget(label_id) => label_id
_ => return false
}
let _ = label_refs
let then_ref = @ir.hot_region_then_ref(func, if_id)
let else_ref = match @ir.hot_region_else_ref(func, if_id) {
Some(else_ref) => else_ref
None => return false
}
let arm_keep_body = fn(arm_ref : @ir.HotRegionRef) -> Array[Int32]? {
if !remove_unused_brs_tail_is_nonfallthrough(func, arm_ref) {
return None
}
let tail_id = remove_unused_brs_region_tail_id(func, arm_ref)
if tail_id < 0 || !@ir.hot_node_is_live(func, tail_id) {
return None
}
let tail = @ir.hot_node_get(func, tail_id)
match tail.op {
@ir.HotOp::Return if tail.child_count > 0 => ()
_ => return None
}
if !remove_unused_brs_return_arm_roots_are_void_safe(func, arm_ref, tail_id) {
return None
}
Some(remove_unused_brs_region_roots(func, arm_ref))
}
let try_build = fn(
keep_ref : @ir.HotRegionRef,
exit_ref : @ir.HotRegionRef,
keep_is_then : Bool,
) -> Bool {
let keep_body = match arm_keep_body(keep_ref) {
Some(body) => body
None => return false
}
let exit_body = match
remove_unused_brs_trim_plain_block_exit_arm(func, exit_ref, target_label) {
Some(body) => body
None => return false
}
let replacement = if keep_is_then {
@ir.hot_build_if(
func,
@lib.BlockType::void_(),
@ir.hot_child_get(func, if_id, 0),
keep_body,
else_body=Some(exit_body),
)
} else {
@ir.hot_build_if(
func,
@lib.BlockType::void_(),
@ir.hot_child_get(func, if_id, 0),
exit_body,
else_body=Some(keep_body),
)
}
let removed = pass_splice_region(
ctx,
func,
region_ref,
idx,
root_count - idx,
[replacement],
)
remove_unused_brs_delete_unused_nodes(
ctx,
func,
[if_id, then_ref.holder, else_ref.holder] + removed,
)
true
}
try_build(then_ref, else_ref, true) || try_build(else_ref, then_ref, false)
}
///|
fn remove_unused_brs_try_voidify_tail_value_if_block_exit(
ctx : HotPassContext,
func : @ir.HotFunc,
region_ref : @ir.HotRegionRef,
label_refs : Array[Int32],
) -> Bool {
let root_count = @ir.hot_region_root_count(func, region_ref)
if root_count < 2 {
return false
}
let if_idx = root_count - 1
let if_id = @ir.hot_region_get(func, region_ref, if_idx)
if if_id < 0 || !@ir.hot_node_is_live(func, if_id) {
return false
}
match @ir.hot_node_get(func, if_id).op {
@ir.HotOp::If => ()
_ => return false
}
if @ir.hot_node_result_arity(func, if_id) != 1 {
return false
}
let payload_idx = if_idx - 1
let payload_id = @ir.hot_region_get(func, region_ref, payload_idx)
if payload_id < 0 || !@ir.hot_node_is_live(func, payload_id) {
return false
}
match @ir.hot_node_get(func, payload_id).op {
@ir.HotOp::Unreachable => ()
_ => return false
}
let target_label = match remove_unused_brs_tail_target(func, region_ref) {
BranchTarget(label_id) => label_id
_ => return false
}
let _ = label_refs
let then_ref = @ir.hot_region_then_ref(func, if_id)
let else_ref = match @ir.hot_region_else_ref(func, if_id) {
Some(else_ref) => else_ref
None => return false
}
let arm_keep_body = fn(arm_ref : @ir.HotRegionRef) -> Array[Int32]? {
if !remove_unused_brs_tail_is_nonfallthrough(func, arm_ref) {
return None
}
let tail_id = remove_unused_brs_region_tail_id(func, arm_ref)
if tail_id < 0 || !@ir.hot_node_is_live(func, tail_id) {
return None
}
let tail = @ir.hot_node_get(func, tail_id)
match tail.op {
@ir.HotOp::Return if tail.child_count > 0 => ()
_ => return None
}
if !remove_unused_brs_return_arm_roots_are_void_safe(func, arm_ref, tail_id) {
return None
}
Some(remove_unused_brs_region_roots(func, arm_ref))
}
let try_build = fn(
keep_ref : @ir.HotRegionRef,
exit_ref : @ir.HotRegionRef,
keep_is_then : Bool,
) -> Bool {
let keep_body = match arm_keep_body(keep_ref) {
Some(body) => body
None => return false
}
let exit_body = match
remove_unused_brs_trim_plain_block_exit_arm(func, exit_ref, target_label) {
Some(body) => body
None => return false
}
let replacement = if keep_is_then {
@ir.hot_build_if(
func,
@lib.BlockType::void_(),
@ir.hot_child_get(func, if_id, 0),
keep_body,
else_body=Some(exit_body),
)
} else {
@ir.hot_build_if(
func,
@lib.BlockType::void_(),
@ir.hot_child_get(func, if_id, 0),
exit_body,
else_body=Some(keep_body),
)
}
let removed = pass_splice_region(ctx, func, region_ref, payload_idx, 2, [
replacement,
])
remove_unused_brs_delete_unused_nodes(
ctx,
func,
[payload_id, if_id, then_ref.holder, else_ref.holder] + removed,
)
true
}
try_build(then_ref, else_ref, true) || try_build(else_ref, then_ref, false)
}
///|
fn remove_unused_brs_try_restructure_one_arm_return_if_suffix(
ctx : HotPassContext,
func : @ir.HotFunc,
region_ref : @ir.HotRegionRef,
idx : Int32,
label_refs : Array[Int32],
) -> Bool {
let root_count = @ir.hot_region_root_count(func, region_ref)
if idx < 0 || idx >= root_count - 1 {
return false
}
let if_id = @ir.hot_region_get(func, region_ref, idx)
if if_id < 0 || !@ir.hot_node_is_live(func, if_id) {
return false
}
let if_node = @ir.hot_node_get(func, if_id)
match if_node.op {
@ir.HotOp::If =>
if @ir.hot_node_result_arity(func, if_id) != 0 {
return false
}
_ => return false
}
match @ir.hot_region_else_ref(func, if_id) {
Some(_) => return false
None => ()
}
let if_label = @ir.hot_control_node_label(func, if_id)
if remove_unused_brs_label_is_used(label_refs, if_label) {
return false
}
let then_ref = @ir.hot_region_then_ref(func, if_id)
if @ir.hot_region_root_count(func, then_ref) != 1 {
return false
}
let then_root = @ir.hot_region_get(func, then_ref, 0)
let then_node = @ir.hot_node_get(func, then_root)
match then_node.op {
@ir.HotOp::Return => if then_node.child_count != 0 { return false }
_ => return false
}
let suffix_roots : Array[Int32] = []
for root_idx = idx + 1; root_idx < root_count; root_idx = root_idx + 1 {
suffix_roots.push(@ir.hot_region_get(func, region_ref, root_idx))
}
let suffix_target = match
remove_unused_brs_region_enclosing_branch_target(func, region_ref) {
Some(label_id) => label_id
None => return false
}
if suffix_roots.is_empty() {
return false
}
for root_idx = 0
root_idx < suffix_roots.length() - 1
root_idx = root_idx + 1 {
if @ir.cfg_terminator_edge_kinds(
@ir.hot_node_get(func, suffix_roots[root_idx]).op,
).length() >
0 {
return false
}
}
let suffix_tail = @ir.hot_node_get(
func,
suffix_roots[suffix_roots.length() - 1],
)
match suffix_tail.op {
@ir.HotOp::Br if suffix_tail.imm0 == suffix_target &&
suffix_tail.child_count == 0 => ()
_ => return false
}
let replacement = @ir.hot_build_if(
func,
@lib.BlockType::void_(),
@ir.hot_child_get(func, if_id, 0),
[then_root],
else_body=Some(suffix_roots),
)
let unreachable_id = @ir.hot_build_unreachable(func)
ignore(
pass_splice_region(ctx, func, region_ref, idx, root_count - idx, [
replacement, unreachable_id,
]),
)
remove_unused_brs_delete_unused_nodes(ctx, func, [if_id, then_ref.holder])
true
}
///|
fn remove_unused_brs_try_restructure_one_exit_arm_if_suffix(
ctx : HotPassContext,
func : @ir.HotFunc,
region_ref : @ir.HotRegionRef,
idx : Int32,
label_refs : Array[Int32],
) -> Bool {
let root_count = @ir.hot_region_root_count(func, region_ref)
if idx < 0 || idx >= root_count - 1 {
return false
}
let if_id = @ir.hot_region_get(func, region_ref, idx)
if if_id < 0 || !@ir.hot_node_is_live(func, if_id) {
return false
}
let if_node = @ir.hot_node_get(func, if_id)
match if_node.op {
@ir.HotOp::If =>
if @ir.hot_node_result_arity(func, if_id) != 0 {
return false
}
_ => return false
}
let else_ref = match @ir.hot_region_else_ref(func, if_id) {
Some(else_ref) => else_ref
None => return false
}
let if_label = @ir.hot_control_node_label(func, if_id)
if remove_unused_brs_label_is_used(label_refs, if_label) {
return false
}
let then_ref = @ir.hot_region_then_ref(func, if_id)
let then_nonfallthrough = remove_unused_brs_tail_is_nonfallthrough(
func, then_ref,
)
let else_nonfallthrough = remove_unused_brs_tail_is_nonfallthrough(
func, else_ref,
)
if then_nonfallthrough == else_nonfallthrough {
return false
}
let suffix_target = match
remove_unused_brs_region_enclosing_branch_target(func, region_ref) {
Some(label_id) => label_id
None => return false
}
let suffix_roots : Array[Int32] = []
for root_idx = idx + 1; root_idx < root_count; root_idx = root_idx + 1 {
suffix_roots.push(@ir.hot_region_get(func, region_ref, root_idx))
}
if suffix_roots.is_empty() {
return false
}
for root_idx = 0
root_idx < suffix_roots.length() - 1
root_idx = root_idx + 1 {
if @ir.cfg_terminator_edge_kinds(
@ir.hot_node_get(func, suffix_roots[root_idx]).op,
).length() >
0 {
return false
}
}
let suffix_tail = @ir.hot_node_get(
func,
suffix_roots[suffix_roots.length() - 1],
)
match suffix_tail.op {
@ir.HotOp::Br if suffix_tail.imm0 == suffix_target &&
suffix_tail.child_count == 0 => ()
_ => return false
}
let then_body = remove_unused_brs_region_roots(func, then_ref)
let else_body = remove_unused_brs_region_roots(func, else_ref)
let moved_suffix : Array[Int32] = []
for root_id in suffix_roots {
moved_suffix.push(root_id)
}
let replacement = if then_nonfallthrough {
@ir.hot_build_if(
func,
@lib.BlockType::void_(),
@ir.hot_child_get(func, if_id, 0),
then_body,
else_body=Some(else_body + moved_suffix),
)
} else {
@ir.hot_build_if(
func,
@lib.BlockType::void_(),
@ir.hot_child_get(func, if_id, 0),
then_body + moved_suffix,
else_body=Some(else_body),
)
}
let unreachable_id = @ir.hot_build_unreachable(func)
ignore(
pass_splice_region(ctx, func, region_ref, idx, root_count - idx, [
replacement, unreachable_id,
]),
)
remove_unused_brs_delete_unused_nodes(ctx, func, [
if_id,
then_ref.holder,
else_ref.holder,
])
true
}
///|
fn remove_unused_brs_count_label_uses_in_roots(
func : @ir.HotFunc,
roots : Array[Int32],
target_label : Int32,
) -> Int32 {
if target_label < 0 || target_label >= func.labels.length() {
return 0
}
let seen = @bitset.BitSet::new(@ir.hot_node_count(func))
let work : Array[Int32] = []
for root in roots {
work.push(root)
}
let mut count = 0
while !work.is_empty() {
let node_id = work.pop().unwrap()
if node_id < 0 ||
node_id >= @ir.hot_node_count(func) ||
!@ir.hot_node_is_live(func, node_id) ||
seen[node_id] {
continue
}
seen[node_id] = true
let node = @ir.hot_node_get(func, node_id)
// BrTable's generic branch target is its default target. Count that target
// only in the table-specific arm below so exact-one-use transforms do not
// conservatively reject a default-only table.
match node.op {
@ir.HotOp::BrTable => ()
_ =>
match @ir.hot_branch_target(func, node_id) {
Some(label_id) if label_id == target_label => count += 1
_ => ()
}
}
match node.op {
@ir.HotOp::BrTable => {
if @ir.hot_branch_table_default_target(func, node.imm0) == target_label {
count += 1
}
for label_id in @ir.hot_branch_table_targets(func, node.imm0) {
if label_id == target_label {
count += 1
}
}
}
@ir.HotOp::Continuation =>
for label_id in @ir.hot_continuation_targets(func, node_id) {
if label_id == target_label {
count += 1
}
}
@ir.HotOp::TryTable => {
let info = @ir.hot_catch_info_get(func, node.imm1)
for catch_ in info.catches {
let label_id = match catch_ {
@ir.HotCatchArm::CatchArm(_, label_id)
| @ir.HotCatchArm::CatchRefArm(_, label_id)
| @ir.HotCatchArm::CatchAllArm(label_id)
| @ir.HotCatchArm::CatchAllRefArm(label_id) => label_id
}
if label_id == target_label {
count += 1
}
}
}
_ => ()
}
for slot = 0; slot < @ir.hot_child_count(func, node_id); slot = slot + 1 {
work.push(@ir.hot_child_get(func, node_id, slot))
}
}
count
}
///|
fn remove_unused_brs_count_label_uses_in_region(
func : @ir.HotFunc,
region_ref : @ir.HotRegionRef,
target_label : Int32,
) -> Int32 {
remove_unused_brs_count_label_uses_in_roots(
func,
remove_unused_brs_region_roots(func, region_ref),
target_label,
)
}
///|
fn remove_unused_brs_region_roots_are_pure_select_arm(
ctx : HotPassContext,
func : @ir.HotFunc,
roots : Array[Int32],
reorder_cache : Array[Int32],
) -> Bool {
remove_unused_brs_region_roots_are_reorder_safe(
ctx, func, roots, reorder_cache,
)
}
///|
fn remove_unused_brs_try_restructure_self_branch_block_prefix(
ctx : HotPassContext,
func : @ir.HotFunc,
region_ref : @ir.HotRegionRef,
idx : Int32,
label_refs : Array[Int32],
reorder_cache : Array[Int32],
) -> Bool {
if idx < 0 || idx >= @ir.hot_region_root_count(func, region_ref) {
return false
}
let block_id = @ir.hot_region_get(func, region_ref, idx)
if block_id < 0 || !@ir.hot_node_is_live(func, block_id) {
return false
}
match @ir.hot_node_get(func, block_id).op {
@ir.HotOp::Block => ()
_ => return false
}
let block_label = @ir.hot_control_node_label(func, block_id)
if block_label < 0 || block_label >= label_refs.length() {
return false
}
let body_ref = @ir.hot_region_body_ref(func, block_id)
let body_count = @ir.hot_region_root_count(func, body_ref)
if body_count < 2 {
return false
}
if remove_unused_brs_count_label_uses_in_region(func, body_ref, block_label) !=
1 {
return false
}
let first_id = @ir.hot_region_get(func, body_ref, 0)
if first_id < 0 || !@ir.hot_node_is_live(func, first_id) {
return false
}
let first_node = @ir.hot_node_get(func, first_id)
let mut drop_id = -1
let mut consumed_end = 0
let mut stack_value = -1
let mut stack_condition = -1
let br_if_id = match first_node.op {
@ir.HotOp::Drop => {
if first_node.child_count != 1 {
return false
}
let candidate = @ir.hot_child_get(func, first_id, 0)
if candidate < 0 || !@ir.hot_node_is_live(func, candidate) {
return false
}
drop_id = first_id
candidate
}
@ir.HotOp::BrIf => {
if first_node.child_count == 2 && body_count >= 2 {
let maybe_drop = @ir.hot_region_get(func, body_ref, 1)
if maybe_drop >= 0 && @ir.hot_node_is_live(func, maybe_drop) {
match @ir.hot_node_get(func, maybe_drop).op {
@ir.HotOp::Drop => {
drop_id = maybe_drop
consumed_end = 1
}
_ => ()
}
}
}
first_id
}
_ => {
if body_count < 2 {
return false
}
let second_id = @ir.hot_region_get(func, body_ref, 1)
if second_id < 0 || !@ir.hot_node_is_live(func, second_id) {
return false
}
match @ir.hot_node_get(func, second_id).op {
@ir.HotOp::BrIf => {
stack_condition = first_id
consumed_end = 1
second_id
}
_ => {
if body_count < 4 {
return false
}
let third_id = @ir.hot_region_get(func, body_ref, 2)
let fourth_id = @ir.hot_region_get(func, body_ref, 3)
if third_id < 0 ||
fourth_id < 0 ||
!@ir.hot_node_is_live(func, third_id) ||
!@ir.hot_node_is_live(func, fourth_id) {
return false
}
match
(
@ir.hot_node_get(func, third_id).op,
@ir.hot_node_get(func, fourth_id).op,
) {
(@ir.HotOp::BrIf, @ir.HotOp::Drop) => {
stack_value = first_id
stack_condition = second_id
drop_id = fourth_id
consumed_end = 3
third_id
}
_ => return false
}
}
}
}
}
let br_if_node = @ir.hot_node_get(func, br_if_id)
match br_if_node.op {
@ir.HotOp::BrIf => ()
_ => return false
}
if @ir.hot_branch_target(func, br_if_id) != Some(block_label) {
return false
}
let condition = if stack_condition >= 0 {
stack_condition
} else {
if br_if_node.child_count <= 0 {
return false
}
@ir.hot_child_get(func, br_if_id, br_if_node.child_count - 1)
}
if condition < 0 || !@ir.hot_node_is_live(func, condition) {
return false
}
if !remove_unused_brs_node_is_reorder_safe(
ctx, func, condition, reorder_cache,
) {
return false
}
let result_types = @ir.hot_type_results(
func,
@ir.hot_node_type(func, block_id),
)
if drop_id < 0 {
if !result_types.is_empty() {
return false
}
if stack_condition < 0 && br_if_node.child_count != 1 {
return false
}
if stack_condition >= 0 &&
br_if_node.child_count != 0 &&
br_if_node.child_count != 1 {
return false
}
for root_idx = 0; root_idx <= consumed_end; root_idx = root_idx + 1 {
ignore(
@ir.hot_region_set(func, body_ref, root_idx, @ir.hot_build_nop(func)),
)
}
let replacement = @ir.hot_build_if(
func,
@lib.BlockType::void_(),
remove_unused_brs_build_i32_eqz(func, condition),
[block_id],
)
ignore(@ir.hot_region_set(func, region_ref, idx, replacement))
if ctx.shrink_level > 0 {
ignore(remove_unused_brs_trim_leading_nops(ctx, func, body_ref))
}
remove_unused_brs_mark_mutated(
ctx, func, "restructure-self-branch-block-prefix",
)
return true
}
if result_types.length() != 1 {
return false
}
if stack_value < 0 && br_if_node.child_count != 2 {
return false
}
if stack_value >= 0 && br_if_node.child_count > 2 {
return false
}
let value = if stack_value >= 0 {
stack_value
} else {
@ir.hot_child_get(func, br_if_id, 0)
}
if value < 0 || !@ir.hot_node_is_live(func, value) {
return false
}
let result_type = result_types[0]
if @ir.hot_node_result_arity(func, value) != 1 {
return false
}
let value_types = @ir.hot_type_results(func, @ir.hot_node_type(func, value))
if value_types.length() != 1 || value_types[0] != result_type {
return false
}
let suffix_roots : Array[Int32] = []
for root_idx = consumed_end + 1
root_idx < body_count
root_idx = root_idx + 1 {
suffix_roots.push(@ir.hot_region_get(func, body_ref, root_idx))
}
if remove_unused_brs_node_is_reorder_safe(ctx, func, value, reorder_cache) {
for root_idx = 0; root_idx <= consumed_end; root_idx = root_idx + 1 {
ignore(
@ir.hot_region_set(func, body_ref, root_idx, @ir.hot_build_nop(func)),
)
}
let replacement = @ir.hot_build_if(
func,
@lib.BlockType::val_type(result_type),
condition,
[value],
else_body=Some([block_id]),
)
ignore(@ir.hot_region_set(func, region_ref, idx, replacement))
if ctx.shrink_level > 0 {
ignore(remove_unused_brs_trim_leading_nops(ctx, func, body_ref))
}
remove_unused_brs_mark_mutated(
ctx, func, "restructure-self-branch-block-prefix-value-if",
)
return true
}
if !remove_unused_brs_region_roots_are_pure_select_arm(
ctx, func, suffix_roots, reorder_cache,
) {
return false
}
for root_idx = 0; root_idx <= consumed_end; root_idx = root_idx + 1 {
ignore(
@ir.hot_region_set(func, body_ref, root_idx, @ir.hot_build_nop(func)),
)
}
let replacement = remove_unused_brs_build_select(
func, result_type, value, block_id, condition,
)
ignore(@ir.hot_region_set(func, region_ref, idx, replacement))
if ctx.shrink_level > 0 {
ignore(remove_unused_brs_trim_leading_nops(ctx, func, body_ref))
}
remove_unused_brs_mark_mutated(
ctx, func, "restructure-self-branch-block-prefix-select",
)
true
}
///|
fn remove_unused_brs_try_sink_single_if_exit_block(
ctx : HotPassContext,
func : @ir.HotFunc,
region_ref : @ir.HotRegionRef,
idx : Int32,
label_refs : Array[Int32],
) -> Bool {
if idx < 0 || idx >= @ir.hot_region_root_count(func, region_ref) {
return false
}
let block_id = @ir.hot_region_get(func, region_ref, idx)
if block_id < 0 || !@ir.hot_node_is_live(func, block_id) {
return false
}
match @ir.hot_node_get(func, block_id).op {
@ir.HotOp::Block => ()
_ => return false
}
let block_label = @ir.hot_control_node_label(func, block_id)
if block_label < 0 ||
block_label >= label_refs.length() ||
label_refs[block_label] <= 0 {
return false
}
let body_ref = @ir.hot_region_body_ref(func, block_id)
let body_root_count = @ir.hot_region_root_count(func, body_ref)
if body_root_count <= 0 {
return false
}
let mut stack_condition_root = -1
let first_root = @ir.hot_region_get(func, body_ref, 0)
let first_root_is_if = first_root >= 0 &&
@ir.hot_node_is_live(func, first_root) &&
(match @ir.hot_node_get(func, first_root).op {
@ir.HotOp::If => true
_ => false
})
let mut all_roots_are_first = true
for root_idx = 1; root_idx < body_root_count; root_idx = root_idx + 1 {
if @ir.hot_region_get(func, body_ref, root_idx) != first_root {
all_roots_are_first = false
}
}
let if_id = if first_root_is_if && all_roots_are_first {
first_root
} else if body_root_count == 2 {
let second = @ir.hot_region_get(func, body_ref, 1)
if first_root_is_if {
stack_condition_root = second
first_root
} else if second >= 0 &&
@ir.hot_node_is_live(func, second) &&
(match @ir.hot_node_get(func, second).op {
@ir.HotOp::If => true
_ => false
}) {
stack_condition_root = first_root
second
} else {
return false
}
} else {
return false
}
if if_id < 0 || !@ir.hot_node_is_live(func, if_id) {
return false
}
match @ir.hot_node_get(func, if_id).op {
@ir.HotOp::If => ()
_ => return false
}
let block_results = @ir.hot_type_results(
func,
@ir.hot_node_type(func, block_id),
)
let if_results = @ir.hot_type_results(func, @ir.hot_node_type(func, if_id))
if block_results.length() != if_results.length() {
return false
}
for result_idx = 0
result_idx < block_results.length()
result_idx = result_idx + 1 {
if block_results[result_idx] != if_results[result_idx] {
return false
}
}
let condition = @ir.hot_child_get(func, if_id, 0)
if condition < 0 || !@ir.hot_node_is_live(func, condition) {
return false
}
if stack_condition_root >= 0 && stack_condition_root != condition {
return false
}
match @ir.hot_node_get(func, condition).op {
@ir.HotOp::Unreachable => return false
_ => ()
}
if remove_unused_brs_count_label_uses_in_roots(func, [condition], block_label) !=
0 {
return false
}
let then_ref = @ir.hot_region_then_ref(func, if_id)
let else_ref = @ir.hot_region_else_ref(func, if_id)
let then_uses = remove_unused_brs_count_label_uses_in_region(
func, then_ref, block_label,
)
let else_uses = match else_ref {
Some(ref_) =>
remove_unused_brs_count_label_uses_in_region(func, ref_, block_label)
None => 0
}
let target_ref = if else_uses == 0 && then_uses > 0 {
then_ref
} else if then_uses == 0 && else_uses > 0 {
match else_ref {
Some(ref_) => ref_
None => return false
}
} else {
return false
}
let moved_roots = remove_unused_brs_region_roots(func, target_ref)
if moved_roots.length() < 2 {
return false
}
if !moved_roots.is_empty() {
let tail_id = moved_roots[moved_roots.length() - 1]
if tail_id >= 0 && @ir.hot_node_is_live(func, tail_id) {
let tail_node = @ir.hot_node_get(func, tail_id)
match tail_node.op {
@ir.HotOp::Br if @ir.hot_branch_target(func, tail_id) ==
Some(block_label) &&
tail_node.child_count == 0 => return false
@ir.HotOp::BrIf if @ir.hot_branch_target(func, tail_id) ==
Some(block_label) => return false
_ => ()
}
}
}
@ir.hot_region_replace_body(func, body_ref, moved_roots)
@ir.hot_region_replace_body(func, target_ref, [block_id])
let root_count = @ir.hot_region_root_count(func, region_ref)
let mut replace_start = idx
while replace_start > 0 &&
@ir.hot_region_get(func, region_ref, replace_start - 1) == block_id {
replace_start = replace_start - 1
}
let mut replace_end = idx + 1
while replace_end < root_count &&
@ir.hot_region_get(func, region_ref, replace_end) == block_id {
replace_end = replace_end + 1
}
let replacements : Array[Int32] = []
for _root_idx = replace_start
_root_idx < replace_end
_root_idx = _root_idx + 1 {
replacements.push(if_id)
}
ignore(
pass_splice_region(
ctx,
func,
region_ref,
replace_start,
replace_end - replace_start,
replacements,
),
)
remove_unused_brs_mark_mutated(ctx, func, "sink-single-if-exit-block")
true
}
///|
fn remove_unused_brs_try_sink_if_arm_self_branch_block(
ctx : HotPassContext,
func : @ir.HotFunc,
region_ref : @ir.HotRegionRef,
idx : Int32,
label_refs : Array[Int32],
) -> Bool {
if idx < 0 || idx >= @ir.hot_region_root_count(func, region_ref) {
return false
}
let block_id = @ir.hot_region_get(func, region_ref, idx)
if block_id < 0 || !@ir.hot_node_is_live(func, block_id) {
return false
}
match @ir.hot_node_get(func, block_id).op {
@ir.HotOp::Block =>
if @ir.hot_node_result_arity(func, block_id) != 0 {
return false
}
_ => return false
}
let block_label = @ir.hot_control_node_label(func, block_id)
if block_label < 0 ||
block_label >= label_refs.length() ||
label_refs[block_label] != 1 {
return false
}
let body_ref = @ir.hot_region_body_ref(func, block_id)
if @ir.hot_region_root_count(func, body_ref) != 1 {
return false
}
let if_id = @ir.hot_region_get(func, body_ref, 0)
if if_id < 0 || !@ir.hot_node_is_live(func, if_id) {
return false
}
let if_node = @ir.hot_node_get(func, if_id)
match if_node.op {
@ir.HotOp::If =>
if @ir.hot_node_result_arity(func, if_id) != 0 {
return false
}
_ => return false
}
let if_label = @ir.hot_control_node_label(func, if_id)
if remove_unused_brs_label_is_used(label_refs, if_label) {
return false
}
let else_ref = match @ir.hot_region_else_ref(func, if_id) {
Some(else_ref) => else_ref
None => return false
}
let then_ref = @ir.hot_region_then_ref(func, if_id)
let arm_without_self_branch = fn(
arm_ref : @ir.HotRegionRef,
) -> Array[Int32]? {
let arm_root_count = @ir.hot_region_root_count(func, arm_ref)
if arm_root_count == 0 {
return None
}
let tail_id = @ir.hot_region_get(func, arm_ref, arm_root_count - 1)
let tail_node = @ir.hot_node_get(func, tail_id)
match tail_node.op {
@ir.HotOp::Br if tail_node.imm0 == block_label &&
tail_node.child_count == 0 => {
let roots : Array[Int32] = []
for root_idx = 0; root_idx < arm_root_count - 1; root_idx = root_idx + 1 {
roots.push(@ir.hot_region_get(func, arm_ref, root_idx))
}
if roots.is_empty() {
return None
}
Some(roots)
}
_ => None
}
}
let wrapped_arm = fn(roots : Array[Int32]) -> Array[Int32] {
let inner = @ir.hot_build_block(func, @lib.BlockType::void_(), roots)
[@ir.hot_build_block(func, @lib.BlockType::void_(), [inner])]
}
let then_self = arm_without_self_branch(then_ref)
let else_self = arm_without_self_branch(else_ref)
if then_self == None && else_self == None {
return false
}
let replacement = @ir.hot_build_if(
func,
@lib.BlockType::void_(),
@ir.hot_child_get(func, if_id, 0),
match then_self {
Some(roots) => wrapped_arm(roots)
None => remove_unused_brs_region_roots(func, then_ref)
},
else_body=Some(
match else_self {
Some(roots) => wrapped_arm(roots)
None => remove_unused_brs_region_roots(func, else_ref)
},
),
)
let removed = pass_splice_region(ctx, func, region_ref, idx, 1, [replacement])
removed.push(block_id)
removed.push(if_id)
removed.push(then_ref.holder)
removed.push(else_ref.holder)
remove_unused_brs_delete_unused_nodes(ctx, func, removed)
true
}
///|
fn remove_unused_brs_try_flip_loop_eqz_backedge_if_to_else(
ctx : HotPassContext,
func : @ir.HotFunc,
region_ref : @ir.HotRegionRef,
idx : Int32,
label_refs : Array[Int32],
) -> Bool {
let loop_label = match
remove_unused_brs_region_enclosing_branch_target(func, region_ref) {
Some(label_id) => label_id
None => return false
}
if idx < 0 || idx >= @ir.hot_region_root_count(func, region_ref) {
return false
}
let if_id = @ir.hot_region_get(func, region_ref, idx)
if if_id < 0 || !@ir.hot_node_is_live(func, if_id) {
return false
}
match @ir.hot_node_get(func, if_id).op {
@ir.HotOp::If =>
if @ir.hot_node_result_arity(func, if_id) != 0 {
return false
}
_ => return false
}
match @ir.hot_region_else_ref(func, if_id) {
Some(_) => return false
None => ()
}
let if_label = @ir.hot_control_node_label(func, if_id)
if remove_unused_brs_label_is_used(label_refs, if_label) {
return false
}
let condition = @ir.hot_child_get(func, if_id, 0)
match @ir.hot_node_get(func, condition).op {
@ir.HotOp::Unary =>
if @ir.hot_node_exact_instr(func, condition) !=
Some(@lib.Instruction::i32_eqz()) {
return false
}
_ => return false
}
if @ir.hot_child_count(func, condition) != 1 {
return false
}
let then_ref = @ir.hot_region_then_ref(func, if_id)
let then_count = @ir.hot_region_root_count(func, then_ref)
if then_count == 0 {
return false
}
let moved_delete : Array[Int32] = []
let mut moved_ref = then_ref
if then_count == 1 {
let wrapper_id = @ir.hot_region_get(func, then_ref, 0)
if wrapper_id >= 0 && @ir.hot_node_is_live(func, wrapper_id) {
match @ir.hot_node_get(func, wrapper_id).op {
@ir.HotOp::Block =>
if @ir.hot_node_result_arity(func, wrapper_id) == 0 {
let wrapper_label = @ir.hot_control_node_label(func, wrapper_id)
if !remove_unused_brs_label_is_used(label_refs, wrapper_label) {
moved_ref = @ir.hot_region_body_ref(func, wrapper_id)
moved_delete.push(wrapper_id)
}
}
_ => ()
}
}
}
let moved_count = @ir.hot_region_root_count(func, moved_ref)
if moved_count == 0 {
return false
}
for root_idx = 0; root_idx < moved_count; root_idx = root_idx + 1 {
let root_id = @ir.hot_region_get(func, moved_ref, root_idx)
if root_id < 0 || !@ir.hot_node_is_live(func, root_id) {
return false
}
if @ir.hot_node_result_arity(func, root_id) != 0 {
return false
}
}
let moved_tail = @ir.hot_region_get(func, moved_ref, moved_count - 1)
match @ir.hot_node_get(func, moved_tail).op {
@ir.HotOp::Br if @ir.hot_branch_target(func, moved_tail) == Some(loop_label) &&
@ir.hot_child_count(func, moved_tail) == 0 => ()
_ => return false
}
let moved_roots = remove_unused_brs_region_roots(func, moved_ref)
let empty_exit = @ir.hot_build_block(func, @lib.BlockType::void_(), [])
let replacement = @ir.hot_build_if(
func,
@lib.BlockType::void_(),
@ir.hot_child_get(func, condition, 0),
[empty_exit],
else_body=Some(moved_roots),
)
let removed = pass_splice_region(ctx, func, region_ref, idx, 1, [replacement])
removed.push(if_id)
removed.push(then_ref.holder)
removed.push(condition)
for node_id in moved_delete {
removed.push(node_id)
}
remove_unused_brs_delete_unused_nodes(ctx, func, removed)
true
}
///|
fn remove_unused_brs_try_move_loop_backedge_suffix_into_block_exit_br_if(
ctx : HotPassContext,
func : @ir.HotFunc,
region_ref : @ir.HotRegionRef,
idx : Int32,
label_refs : Array[Int32],
) -> Bool {
let loop_label = match
remove_unused_brs_region_enclosing_branch_target(func, region_ref) {
Some(label_id) => label_id
None => return false
}
if idx < 0 || idx >= @ir.hot_region_root_count(func, region_ref) {
return false
}
let block_id = @ir.hot_region_get(func, region_ref, idx)
if block_id < 0 || !@ir.hot_node_is_live(func, block_id) {
return false
}
match @ir.hot_node_get(func, block_id).op {
@ir.HotOp::Block =>
if @ir.hot_node_result_arity(func, block_id) != 0 {
return false
}
_ => return false
}
let block_label = @ir.hot_control_node_label(func, block_id)
if block_label < 0 ||
block_label >= label_refs.length() ||
label_refs[block_label] != 1 {
return false
}
let body_ref = @ir.hot_region_body_ref(func, block_id)
let root_count = @ir.hot_region_root_count(func, body_ref)
if root_count <= 1 {
return false
}
let tail_id = @ir.hot_region_get(func, body_ref, root_count - 1)
if tail_id < 0 || !@ir.hot_node_is_live(func, tail_id) {
return false
}
match @ir.hot_node_get(func, tail_id).op {
@ir.HotOp::Br if @ir.hot_branch_target(func, tail_id) == Some(loop_label) &&
@ir.hot_child_count(func, tail_id) == 0 => ()
_ => return false
}
let mut scan_idx = root_count - 2
while scan_idx >= 0 {
let candidate_id = @ir.hot_region_get(func, body_ref, scan_idx)
if candidate_id < 0 || !@ir.hot_node_is_live(func, candidate_id) {
return false
}
match @ir.hot_node_get(func, candidate_id).op {
@ir.HotOp::BrIf if @ir.hot_branch_target(func, candidate_id) ==
Some(block_label) &&
@ir.hot_child_count(func, candidate_id) == 1 => {
let suffix_roots : Array[Int32] = []
for suffix_idx = scan_idx + 1
suffix_idx < root_count
suffix_idx = suffix_idx + 1 {
let suffix_id = @ir.hot_region_get(func, body_ref, suffix_idx)
if suffix_id < 0 || !@ir.hot_node_is_live(func, suffix_id) {
return false
}
if suffix_idx < root_count - 1 {
if @ir.hot_node_result_arity(func, suffix_id) != 0 {
return false
}
if @ir.cfg_terminator_edge_kinds(
@ir.hot_node_get(func, suffix_id).op,
).length() >
0 {
return false
}
}
suffix_roots.push(suffix_id)
}
if suffix_roots.is_empty() {
return false
}
let empty_exit = @ir.hot_build_block(func, @lib.BlockType::void_(), [])
let replacement = @ir.hot_build_if(
func,
@lib.BlockType::void_(),
@ir.hot_child_get(func, candidate_id, 0),
[empty_exit],
else_body=Some(suffix_roots),
)
let replacement_roots : Array[Int32] = []
for prefix_idx = 0; prefix_idx < scan_idx; prefix_idx = prefix_idx + 1 {
replacement_roots.push(@ir.hot_region_get(func, body_ref, prefix_idx))
}
replacement_roots.push(replacement)
let removed = pass_splice_region(
ctx, func, region_ref, idx, 1, replacement_roots,
)
removed.push(block_id)
removed.push(candidate_id)
remove_unused_brs_delete_unused_nodes(ctx, func, removed)
return true
}
_ => {
if @ir.cfg_terminator_edge_kinds(
@ir.hot_node_get(func, candidate_id).op,
).length() >
0 {
return false
}
if scan_idx == 0 {
return false
}
scan_idx = scan_idx - 1
continue
}
}
}
false
}
///|
fn remove_unused_brs_try_flip_single_loop_adjacent_br_if_backedge(
ctx : HotPassContext,
func : @ir.HotFunc,
region_ref : @ir.HotRegionRef,
idx : Int32,
) -> Bool {
if idx < 0 || idx >= @ir.hot_region_root_count(func, region_ref) {
return false
}
let block_id = @ir.hot_region_get(func, region_ref, idx)
if block_id < 0 || !@ir.hot_node_is_live(func, block_id) {
return false
}
match @ir.hot_node_get(func, block_id).op {
@ir.HotOp::Block =>
if @ir.hot_node_result_arity(func, block_id) != 0 {
return false
}
_ => return false
}
let block_body_ref = @ir.hot_region_body_ref(func, block_id)
if @ir.hot_region_root_count(func, block_body_ref) != 1 {
return false
}
let loop_id = @ir.hot_region_get(func, block_body_ref, 0)
if loop_id < 0 || !@ir.hot_node_is_live(func, loop_id) {
return false
}
match @ir.hot_node_get(func, loop_id).op {
@ir.HotOp::Loop =>
if @ir.hot_node_result_arity(func, loop_id) != 0 {
return false
}
_ => return false
}
let loop_label = @ir.hot_control_node_label(func, loop_id)
let loop_body_ref = @ir.hot_region_body_ref(func, loop_id)
if @ir.hot_region_root_count(func, loop_body_ref) != 2 {
return false
}
let br_if_id = @ir.hot_region_get(func, loop_body_ref, 0)
let backedge_id = @ir.hot_region_get(func, loop_body_ref, 1)
if br_if_id < 0 ||
backedge_id < 0 ||
!@ir.hot_node_is_live(func, br_if_id) ||
!@ir.hot_node_is_live(func, backedge_id) {
return false
}
let br_if_node = @ir.hot_node_get(func, br_if_id)
match br_if_node.op {
@ir.HotOp::BrIf => ()
_ => return false
}
if @ir.hot_child_count(func, br_if_id) != 1 {
return false
}
let exit_label = match @ir.hot_branch_target(func, br_if_id) {
Some(label_id) => label_id
None => return false
}
if exit_label == loop_label {
return false
}
let backedge_node = @ir.hot_node_get(func, backedge_id)
match backedge_node.op {
@ir.HotOp::Br if @ir.hot_branch_target(func, backedge_id) ==
Some(loop_label) &&
@ir.hot_child_count(func, backedge_id) == 0 => ()
_ => return false
}
let inverted = remove_unused_brs_build_i32_eqz(
func,
@ir.hot_child_get(func, br_if_id, 0),
)
@ir.hot_child_set(func, br_if_id, 0, inverted)
@ir.hot_branch_target_set(func, br_if_id, loop_label)
let removed_tail = pass_splice_region(ctx, func, loop_body_ref, 1, 1, [])
let removed_block = pass_splice_region(ctx, func, region_ref, idx, 1, [
loop_id,
])
removed_tail.append(removed_block)
removed_tail.push(block_id)
removed_tail.push(backedge_id)
remove_unused_brs_delete_unused_nodes(ctx, func, removed_tail)
remove_unused_brs_mark_mutated(ctx, func, "flip-loop-adjacent-br-if")
true
}
///|
fn remove_unused_brs_try_rotate_void_block_single_loop(
ctx : HotPassContext,
func : @ir.HotFunc,
region_ref : @ir.HotRegionRef,
idx : Int32,
) -> Bool {
if idx < 0 || idx >= @ir.hot_region_root_count(func, region_ref) {
return false
}
let block_id = @ir.hot_region_get(func, region_ref, idx)
if block_id < 0 || !@ir.hot_node_is_live(func, block_id) {
return false
}
match @ir.hot_node_get(func, block_id).op {
@ir.HotOp::Block =>
if @ir.hot_node_result_arity(func, block_id) != 0 {
return false
}
_ => return false
}
let block_body_ref = @ir.hot_region_body_ref(func, block_id)
if @ir.hot_region_root_count(func, block_body_ref) != 1 {
return false
}
let loop_id = @ir.hot_region_get(func, block_body_ref, 0)
if loop_id < 0 || !@ir.hot_node_is_live(func, loop_id) {
return false
}
match @ir.hot_node_get(func, loop_id).op {
@ir.HotOp::Loop =>
if @ir.hot_node_result_arity(func, loop_id) != 0 {
return false
}
_ => return false
}
let loop_body_ref = @ir.hot_region_body_ref(func, loop_id)
if !remove_unused_brs_tail_is_nonfallthrough(func, loop_body_ref) {
return false
}
let loop_body_roots : Array[Int32] = []
for root_idx = 0
root_idx < @ir.hot_region_root_count(func, loop_body_ref)
root_idx = root_idx + 1 {
loop_body_roots.push(@ir.hot_region_get(func, loop_body_ref, root_idx))
}
let work : Array[Int32] = []
for root in loop_body_roots {
work.push(root)
}
while !work.is_empty() {
let node_id = work.pop().unwrap()
if node_id < 0 || !@ir.hot_node_is_live(func, node_id) {
continue
}
if @ir.hot_is_control_node(func, node_id) &&
@ir.hot_node_result_arity(func, node_id) > 0 {
return false
}
for slot = 0; slot < @ir.hot_child_count(func, node_id); slot = slot + 1 {
work.push(@ir.hot_child_get(func, node_id, slot))
}
}
let wrapped_body = @ir.hot_build_block(
func,
@lib.BlockType::void_(),
loop_body_roots,
)
let unreachable_id = @ir.hot_build_unreachable(func)
@ir.hot_region_replace_body(func, block_body_ref, [
wrapped_body, unreachable_id,
])
@ir.hot_region_replace_body(func, loop_body_ref, [block_id])
ignore(@ir.hot_region_set(func, region_ref, idx, loop_id))
remove_unused_brs_mark_mutated(ctx, func, "rewrite-loop-leading-break")
true
}
///|
fn remove_unused_brs_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 remove_unused_brs_preserves_payload_context(op : @ir.HotOp) -> Bool {
match op {
@ir.HotOp::LocalTee | @ir.HotOp::TupleExtract => true
_ => false
}
}
///|
fn remove_unused_brs_prepare_int_cache(
cache : Array[Int32],
size : Int32,
fill : Int32,
) -> Unit {
while cache.length() < size {
cache.push(fill)
}
for idx = 0; idx < size; idx = idx + 1 {
cache[idx] = fill
}
}
///|
fn remove_unused_brs_mark_seen(
seen : Array[Int32],
node_id : Int32,
payload_context : Bool,
return_context : Bool,
) -> Bool {
while seen.length() <= node_id {
seen.push(0)
}
let mut mode = 1
if payload_context {
mode = mode | 2
}
if return_context {
mode = mode | 4
}
if (seen[node_id] & mode) == mode {
return false
}
seen[node_id] = seen[node_id] | mode
true
}
///|
fn remove_unused_brs_control_cache_resize(
cache : Array[Int32],
node_id : Int32,
) -> Unit {
while cache.length() <= node_id {
cache.push(-1)
}
}
///|
fn remove_unused_brs_return_cache_resize(
cache : Array[Int32],
node_id : Int32,
) -> Unit {
while cache.length() <= node_id {
cache.push(-1)
}
}
///|
fn remove_unused_brs_subtree_has_return(
func : @ir.HotFunc,
node_id : Int32,
cache : Array[Int32],
) -> Bool {
if node_id < 0 || !@ir.hot_node_is_live(func, node_id) {
return false
}
remove_unused_brs_return_cache_resize(cache, node_id)
match cache[node_id] {
0 => return false
1 => return true
_ => ()
}
let node = @ir.hot_node_get(func, node_id)
let mut has_return = match node.op {
@ir.HotOp::Return => true
_ => false
}
if !has_return {
for slot = 0; slot < @ir.hot_child_count(func, node_id); slot = slot + 1 {
if remove_unused_brs_is_region_child_slot(node.op, slot) {
continue
}
let child_id = @ir.hot_child_get(func, node_id, slot)
if remove_unused_brs_subtree_has_return(func, child_id, cache) {
has_return = true
break
}
}
}
if !has_return {
let scan_region = fn(region_ref : @ir.HotRegionRef) -> Bool {
for idx = 0
idx < @ir.hot_region_root_count(func, region_ref)
idx = idx + 1 {
if remove_unused_brs_subtree_has_return(
func,
@ir.hot_region_get(func, region_ref, idx),
cache,
) {
return true
}
}
false
}
match node.op {
@ir.HotOp::Block | @ir.HotOp::Loop =>
has_return = scan_region(@ir.hot_region_body_ref(func, node_id))
@ir.HotOp::If => {
has_return = scan_region(@ir.hot_region_then_ref(func, node_id))
if !has_return {
match @ir.hot_region_else_ref(func, node_id) {
Some(else_ref) => has_return = scan_region(else_ref)
None => ()
}
}
}
@ir.HotOp::Try => {
has_return = scan_region(@ir.hot_region_body_ref(func, node_id))
if !has_return {
match @ir.hot_region_catch_ref(func, node_id) {
Some(catch_ref) => has_return = scan_region(catch_ref)
None => ()
}
}
}
@ir.HotOp::TryTable => {
has_return = scan_region(@ir.hot_region_body_ref(func, node_id))
if !has_return {
has_return = scan_region(@ir.hot_region_catch_list_ref(func, node_id))
}
}
_ => ()
}
}
cache[node_id] = if has_return { 1 } else { 0 }
has_return
}
///|
fn remove_unused_brs_subtree_has_embedded_control(
func : @ir.HotFunc,
node_id : Int32,
cache : Array[Int32],
) -> Bool {
if node_id < 0 || !@ir.hot_node_is_live(func, node_id) {
return false
}
remove_unused_brs_control_cache_resize(cache, node_id)
match cache[node_id] {
0 => return false
1 => return true
_ => ()
}
let node = @ir.hot_node_get(func, node_id)
let mut has_control = match node.op {
@ir.HotOp::If
| @ir.HotOp::Block
| @ir.HotOp::Loop
| @ir.HotOp::Try
| @ir.HotOp::TryTable => true
_ => false
}
if !has_control {
for slot = 0; slot < @ir.hot_child_count(func, node_id); slot = slot + 1 {
if remove_unused_brs_is_region_child_slot(node.op, slot) {
continue
}
let child_id = @ir.hot_child_get(func, node_id, slot)
if remove_unused_brs_subtree_has_embedded_control(func, child_id, cache) {
has_control = true
break
}
}
}
cache[node_id] = if has_control { 1 } else { 0 }
has_control
}
///|
fn remove_unused_brs_visit_embedded_subtree(
ctx : HotPassContext,
func : @ir.HotFunc,
node_id : Int32,
seen : Array[Int32],
reorder_cache : Array[Int32],
label_refs : Array[Int32],
branch_payload_children : @bitset.BitSet,
func_has_br_table : Bool,
payload_context : Bool,
return_context : Bool,
control_cache : Array[Int32],
return_cache : Array[Int32],
) -> Bool {
if !@ir.hot_node_is_live(func, node_id) {
return false
}
if !remove_unused_brs_subtree_has_embedded_control(
func, node_id, control_cache,
) {
return false
}
let node = @ir.hot_node_get(func, node_id)
match node.op {
@ir.HotOp::If
| @ir.HotOp::Block
| @ir.HotOp::Loop
| @ir.HotOp::Try
| @ir.HotOp::TryTable =>
return remove_unused_brs_visit_node(
ctx,
func,
None,
node_id,
seen,
reorder_cache,
label_refs,
branch_payload_children,
func_has_br_table,
payload_context,
return_context &&
(
@ir.hot_node_result_arity(func, node_id) > 0 ||
remove_unused_brs_subtree_has_return(func, node_id, return_cache)
),
control_cache,
return_cache,
)
_ => ()
}
let mut changed = false
for slot = 0; slot < @ir.hot_child_count(func, node_id); slot = slot + 1 {
if remove_unused_brs_is_region_child_slot(node.op, slot) {
continue
}
let child_id = @ir.hot_child_get(func, node_id, slot)
if child_id < 0 || !@ir.hot_node_is_live(func, child_id) {
continue
}
let child_payload_context = payload_context &&
remove_unused_brs_preserves_payload_context(node.op)
changed = remove_unused_brs_visit_embedded_subtree(
ctx, func, child_id, seen, reorder_cache, label_refs, branch_payload_children,
func_has_br_table, child_payload_context, false, control_cache, return_cache,
) ||
changed
}
changed
}
///|
fn remove_unused_brs_visit_generic_children(
ctx : HotPassContext,
func : @ir.HotFunc,
node_id : Int32,
seen : Array[Int32],
reorder_cache : Array[Int32],
label_refs : Array[Int32],
branch_payload_children : @bitset.BitSet,
func_has_br_table : Bool,
control_cache : Array[Int32],
return_cache : Array[Int32],
) -> Bool {
let node = @ir.hot_node_get(func, node_id)
let mut changed = false
for slot = 0; slot < @ir.hot_child_count(func, node_id); slot = slot + 1 {
if remove_unused_brs_is_region_child_slot(node.op, slot) {
continue
}
let child_id = @ir.hot_child_get(func, node_id, slot)
if child_id < 0 || !@ir.hot_node_is_live(func, child_id) {
continue
}
changed = remove_unused_brs_visit_embedded_subtree(
ctx, func, child_id, seen, reorder_cache, label_refs, branch_payload_children,
func_has_br_table, false, false, control_cache, return_cache,
) ||
changed
}
changed
}
///|
fn remove_unused_brs_try_refinalize_single_result_block(
ctx : HotPassContext,
func : @ir.HotFunc,
block_id : Int32,
label_refs : Array[Int32],
) -> Bool {
if block_id < 0 || !@ir.hot_node_is_live(func, block_id) {
return false
}
match @ir.hot_node_get(func, block_id).op {
@ir.HotOp::Block => ()
_ => return false
}
if @ir.hot_node_result_arity(func, block_id) != 1 {
return false
}
let label_id = @ir.hot_control_node_label(func, block_id)
if label_id < 0 {
return false
}
let mut candidate : @lib.ValType? = None
let admit_value = fn(value_id : Int32) -> Bool {
if value_id < 0 || !@ir.hot_node_is_live(func, value_id) {
return false
}
let results = @ir.hot_type_results(func, @ir.hot_node_type(func, value_id))
if results.length() != 1 {
return false
}
let value_type = results[0]
match candidate {
Some(existing) => existing == value_type
None => {
candidate = Some(value_type)
true
}
}
}
let label_ref_count = if label_id < label_refs.length() {
label_refs[label_id]
} else {
0
}
if label_ref_count > 0 {
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
}
let node = @ir.hot_node_get(func, node_id)
match @ir.hot_branch_target(func, node_id) {
Some(target) if target == label_id =>
match node.op {
@ir.HotOp::Br =>
if node.child_count != 1 ||
!admit_value(@ir.hot_child_get(func, node_id, 0)) {
return false
}
_ => return false
}
_ => ()
}
match node.op {
@ir.HotOp::BrTable => {
if @ir.hot_branch_table_default_target(func, node.imm0) == label_id {
return false
}
for target in @ir.hot_branch_table_targets(func, node.imm0) {
if target == label_id {
return false
}
}
}
@ir.HotOp::TryTable => {
let info = @ir.hot_catch_info_get(func, node.imm1)
for catch_ in info.catches {
let target = match catch_ {
@ir.HotCatchArm::CatchArm(_, target)
| @ir.HotCatchArm::CatchRefArm(_, target)
| @ir.HotCatchArm::CatchAllArm(target)
| @ir.HotCatchArm::CatchAllRefArm(target) => target
}
if target == label_id {
return false
}
}
}
@ir.HotOp::Continuation =>
for target in @ir.hot_continuation_targets(func, node_id) {
if target == label_id {
return false
}
}
_ => ()
}
}
}
let body_ref = @ir.hot_region_body_ref(func, block_id)
let body_count = @ir.hot_region_root_count(func, body_ref)
if body_count == 0 {
return false
}
let tail_id = @ir.hot_region_get(func, body_ref, body_count - 1)
let tail_is_nonfallthrough = @ir.cfg_terminator_edge_kinds(
@ir.hot_node_get(func, tail_id).op,
).length() >
0
if !tail_is_nonfallthrough && !admit_value(tail_id) {
return false
}
let refined = match candidate {
Some(refined) => refined
None => return false
}
let current = @ir.hot_type_results(func, @ir.hot_node_type(func, block_id))[0]
if refined == current {
return false
}
@ir.hot_control_result_type_set(
func,
block_id,
@ir.hot_type_intern_block_result(func, @lib.BlockType::val_type(refined)),
)
remove_unused_brs_mark_mutated(ctx, func, "refinalize-single-result-block")
true
}
///|
fn remove_unused_brs_visit_node(
ctx : HotPassContext,
func : @ir.HotFunc,
current_site : RemoveUnusedBrsRootSite?,
node_id : Int32,
seen : Array[Int32],
reorder_cache : Array[Int32],
label_refs : Array[Int32],
branch_payload_children : @bitset.BitSet,
func_has_br_table : Bool,
payload_context : Bool,
return_context : Bool,
control_cache : Array[Int32],
return_cache : Array[Int32],
) -> Bool {
if !@ir.hot_node_is_live(func, node_id) {
return false
}
if !remove_unused_brs_mark_seen(
seen, node_id, payload_context, return_context,
) {
return false
}
let mut changed = false
match @ir.hot_node_get(func, node_id).op {
@ir.HotOp::Block | @ir.HotOp::Loop => {
let body_ref = @ir.hot_region_body_ref(func, node_id)
let allow_payload_rewrites = payload_context && !return_context
changed = remove_unused_brs_visit_region(
ctx, func, body_ref, seen, reorder_cache, label_refs, branch_payload_children,
func_has_br_table, return_context, control_cache, return_cache,
) ||
changed
if remove_unused_brs_try_rewrite_result_block_one_arm_payload_if(
ctx, func, current_site, node_id, reorder_cache, label_refs, branch_payload_children,
) {
return true
}
if remove_unused_brs_try_rewrite_result_block_prefix_payload_branch(
ctx, func, body_ref, label_refs, branch_payload_children,
) {
return true
}
let body_tail_id_for_return = remove_unused_brs_region_tail_id(
func, body_ref,
)
if return_context && body_tail_id_for_return >= 0 {
let body_tail_op = @ir.hot_node_get(func, body_tail_id_for_return).op
match body_tail_op {
@ir.HotOp::Return =>
changed = remove_unused_brs_try_strip_return_context_tail(
ctx, func, body_ref,
) ||
changed
_ => ()
}
}
if allow_payload_rewrites {
changed = remove_unused_brs_try_rewrite_tail_value_if_branch_exit(
ctx, func, body_ref, label_refs,
) ||
changed
changed = remove_unused_brs_try_rewrite_tail_value_if_simple_payload_arm(
ctx, func, body_ref, label_refs,
) ||
changed
}
changed = remove_unused_brs_flatten_block_if_chains(
ctx, func, body_ref, label_refs,
) ||
changed
let body_tail_id = remove_unused_brs_region_tail_id(func, body_ref)
if body_tail_id >= 0 {
let body_tail_op = @ir.hot_node_get(func, body_tail_id).op
match body_tail_op {
@ir.HotOp::If => {
changed = remove_unused_brs_try_voidify_tail_value_if_block_exit(
ctx, func, body_ref, label_refs,
) ||
changed
changed = remove_unused_brs_try_strip_tail_if_exits(
ctx, func, body_ref,
) ||
changed
changed = remove_unused_brs_try_rewrite_one_sided_stack_tail_if_to_select(
ctx, func, body_ref, reorder_cache, label_refs,
) ||
changed
}
@ir.HotOp::Return =>
changed = remove_unused_brs_try_voidify_tail_return_if(
ctx, func, body_ref, label_refs,
) ||
changed
@ir.HotOp::Nop =>
changed = remove_unused_brs_trim_trailing_nops(ctx, func, body_ref) ||
changed
_ => ()
}
}
if body_tail_id >= 0 &&
remove_unused_brs_region_has_removable_tail_target(func, body_ref) {
changed = remove_unused_brs_try_remove_region_tail(
ctx, func, body_ref, reorder_cache,
) ||
changed
}
if @ir.hot_node_is_live(func, node_id) {
changed = remove_unused_brs_try_refinalize_single_result_block(
ctx, func, node_id, label_refs,
) ||
changed
}
}
@ir.HotOp::If => {
changed = remove_unused_brs_visit_embedded_subtree(
ctx,
func,
@ir.hot_child_get(func, node_id, 0),
seen,
reorder_cache,
label_refs,
branch_payload_children,
func_has_br_table,
false,
false,
control_cache,
return_cache,
) ||
changed
changed = remove_unused_brs_try_rewrite_condition_child_value_if_to_select(
ctx, func, node_id, reorder_cache,
) ||
changed
let then_ref = @ir.hot_region_then_ref(func, node_id)
changed = remove_unused_brs_visit_region(
ctx, func, then_ref, seen, reorder_cache, label_refs, branch_payload_children,
func_has_br_table, return_context, control_cache, return_cache,
) ||
changed
let then_tail_id_for_return = remove_unused_brs_region_tail_id(
func, then_ref,
)
if return_context && then_tail_id_for_return >= 0 {
let then_tail_op = @ir.hot_node_get(func, then_tail_id_for_return).op
match then_tail_op {
@ir.HotOp::Return =>
if remove_unused_brs_try_strip_return_context_tail(
ctx, func, then_ref,
) {
changed = true
}
_ => ()
}
}
if payload_context && !return_context {
if remove_unused_brs_try_rewrite_tail_value_if_branch_exit(
ctx, func, then_ref, label_refs,
) {
changed = true
}
if remove_unused_brs_try_rewrite_tail_value_if_simple_payload_arm(
ctx, func, then_ref, label_refs,
) {
changed = true
}
}
let then_tail_id = remove_unused_brs_region_tail_id(func, then_ref)
if then_tail_id >= 0 {
let then_tail_op = @ir.hot_node_get(func, then_tail_id).op
match then_tail_op {
@ir.HotOp::If => {
changed = remove_unused_brs_try_strip_tail_if_exits(
ctx, func, then_ref,
) ||
changed
changed = remove_unused_brs_try_rewrite_one_sided_stack_tail_if_to_select(
ctx, func, then_ref, reorder_cache, label_refs,
) ||
changed
}
@ir.HotOp::Return =>
changed = remove_unused_brs_try_voidify_tail_return_if(
ctx, func, then_ref, label_refs,
) ||
changed
@ir.HotOp::Nop =>
changed = remove_unused_brs_trim_trailing_nops(
ctx,
func,
then_ref,
preserve_single_if_arm_nop=true,
) ||
changed
_ => ()
}
}
if then_tail_id >= 0 &&
remove_unused_brs_region_has_removable_tail_target(func, then_ref) {
changed = remove_unused_brs_try_remove_region_tail(
ctx, func, then_ref, reorder_cache,
) ||
changed
}
match @ir.hot_region_else_ref(func, node_id) {
Some(else_ref) => {
changed = remove_unused_brs_visit_region(
ctx, func, else_ref, seen, reorder_cache, label_refs, branch_payload_children,
func_has_br_table, return_context, control_cache, return_cache,
) ||
changed
let else_tail_id_for_return = remove_unused_brs_region_tail_id(
func, else_ref,
)
if return_context && else_tail_id_for_return >= 0 {
let else_tail_op = @ir.hot_node_get(func, else_tail_id_for_return).op
match else_tail_op {
@ir.HotOp::Return =>
changed = remove_unused_brs_try_strip_return_context_tail(
ctx, func, else_ref,
) ||
changed
_ => ()
}
}
if payload_context && !return_context {
changed = remove_unused_brs_try_rewrite_tail_value_if_branch_exit(
ctx, func, else_ref, label_refs,
) ||
changed
changed = remove_unused_brs_try_rewrite_tail_value_if_simple_payload_arm(
ctx, func, else_ref, label_refs,
) ||
changed
}
let else_tail_id = remove_unused_brs_region_tail_id(func, else_ref)
if else_tail_id >= 0 {
let else_tail_op = @ir.hot_node_get(func, else_tail_id).op
match else_tail_op {
@ir.HotOp::If => {
if remove_unused_brs_try_strip_tail_if_exits(
ctx, func, else_ref,
) {
changed = true
}
if remove_unused_brs_try_rewrite_one_sided_stack_tail_if_to_select(
ctx, func, else_ref, reorder_cache, label_refs,
) {
changed = true
}
}
@ir.HotOp::Return =>
if remove_unused_brs_try_voidify_tail_return_if(
ctx, func, else_ref, label_refs,
) {
changed = true
}
@ir.HotOp::Nop =>
if remove_unused_brs_trim_trailing_nops(
ctx,
func,
else_ref,
preserve_single_if_arm_nop=true,
) {
changed = true
}
_ => ()
}
}
if else_tail_id >= 0 &&
remove_unused_brs_region_has_removable_tail_target(func, else_ref) {
if remove_unused_brs_try_remove_region_tail(
ctx, func, else_ref, reorder_cache,
) {
changed = true
}
}
}
None => ()
}
if payload_context &&
remove_unused_brs_try_voidify_exit_only_value_if(ctx, func, node_id) {
return true
}
if remove_unused_brs_try_rewrite_value_if_to_select(
ctx, func, node_id, reorder_cache,
) {
changed = true
}
if remove_unused_brs_try_merge_nested_if_conditions(
ctx, func, node_id, reorder_cache,
) {
changed = true
}
if remove_unused_brs_try_rewrite_if_br(ctx, func, node_id, reorder_cache) {
changed = true
}
}
@ir.HotOp::LocalSet =>
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 child_id < 0 || !@ir.hot_node_is_live(func, child_id) {
continue
}
changed = remove_unused_brs_visit_embedded_subtree(
ctx, func, child_id, seen, reorder_cache, label_refs, branch_payload_children,
func_has_br_table, true, false, control_cache, return_cache,
) ||
changed
}
@ir.HotOp::Drop =>
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 child_id < 0 || !@ir.hot_node_is_live(func, child_id) {
continue
}
changed = remove_unused_brs_visit_embedded_subtree(
ctx, func, child_id, seen, reorder_cache, label_refs, branch_payload_children,
func_has_br_table, true, false, control_cache, return_cache,
) ||
changed
}
@ir.HotOp::Br | @ir.HotOp::BrIf | @ir.HotOp::BrTable | @ir.HotOp::Return => {
let branch_like_op = @ir.hot_node_get(func, node_id).op
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 child_id < 0 || !@ir.hot_node_is_live(func, child_id) {
continue
}
let child_payload_context = match @ir.hot_node_get(func, node_id).op {
@ir.HotOp::Br => true
@ir.HotOp::Return => true
@ir.HotOp::BrIf => slot < @ir.hot_child_count(func, node_id) - 1
@ir.HotOp::BrTable => slot < @ir.hot_child_count(func, node_id) - 1
_ => false
}
let child_return_context = match @ir.hot_node_get(func, node_id).op {
@ir.HotOp::Return => true
_ => false
}
changed = remove_unused_brs_visit_embedded_subtree(
ctx, func, child_id, seen, reorder_cache, label_refs, branch_payload_children,
func_has_br_table, child_payload_context, child_return_context, control_cache,
return_cache,
) ||
changed
match branch_like_op {
@ir.HotOp::Return =>
if slot == 0 && child_return_context {
changed = remove_unused_brs_try_rewrite_return_child_if_branch_exit(
ctx, func, child_id, label_refs,
) ||
changed
}
_ => ()
}
}
}
@ir.HotOp::Try => {
let body_ref = @ir.hot_region_body_ref(func, node_id)
changed = remove_unused_brs_visit_region(
ctx, func, body_ref, seen, reorder_cache, label_refs, branch_payload_children,
func_has_br_table, return_context, control_cache, return_cache,
) ||
changed
let body_tail_id_for_return = remove_unused_brs_region_tail_id(
func, body_ref,
)
if return_context && body_tail_id_for_return >= 0 {
let body_tail_op = @ir.hot_node_get(func, body_tail_id_for_return).op
match body_tail_op {
@ir.HotOp::Return =>
changed = remove_unused_brs_try_strip_return_context_tail(
ctx, func, body_ref,
) ||
changed
_ => ()
}
}
let body_tail_id = remove_unused_brs_region_tail_id(func, body_ref)
if body_tail_id >= 0 {
let body_tail_op = @ir.hot_node_get(func, body_tail_id).op
match body_tail_op {
@ir.HotOp::If => {
changed = remove_unused_brs_try_voidify_tail_value_if_block_exit(
ctx, func, body_ref, label_refs,
) ||
changed
changed = remove_unused_brs_try_strip_tail_if_exits(
ctx, func, body_ref,
) ||
changed
changed = remove_unused_brs_try_rewrite_one_sided_stack_tail_if_to_select(
ctx, func, body_ref, reorder_cache, label_refs,
) ||
changed
}
@ir.HotOp::Return =>
changed = remove_unused_brs_try_voidify_tail_return_if(
ctx, func, body_ref, label_refs,
) ||
changed
@ir.HotOp::Nop =>
changed = remove_unused_brs_trim_trailing_nops(ctx, func, body_ref) ||
changed
_ => ()
}
}
if body_tail_id >= 0 &&
remove_unused_brs_region_has_removable_tail_target(func, body_ref) {
changed = remove_unused_brs_try_remove_region_tail(
ctx, func, body_ref, reorder_cache,
) ||
changed
}
match @ir.hot_region_catch_ref(func, node_id) {
Some(catch_ref) => {
changed = remove_unused_brs_visit_region(
ctx, func, catch_ref, seen, reorder_cache, label_refs, branch_payload_children,
func_has_br_table, return_context, control_cache, return_cache,
) ||
changed
let catch_tail_id_for_return = remove_unused_brs_region_tail_id(
func, catch_ref,
)
if return_context && catch_tail_id_for_return >= 0 {
let catch_tail_op = @ir.hot_node_get(func, catch_tail_id_for_return).op
match catch_tail_op {
@ir.HotOp::Return =>
changed = remove_unused_brs_try_strip_return_context_tail(
ctx, func, catch_ref,
) ||
changed
_ => ()
}
}
let catch_tail_id = remove_unused_brs_region_tail_id(func, catch_ref)
if catch_tail_id >= 0 {
let catch_tail_op = @ir.hot_node_get(func, catch_tail_id).op
match catch_tail_op {
@ir.HotOp::If => {
changed = remove_unused_brs_try_strip_tail_if_exits(
ctx, func, catch_ref,
) ||
changed
changed = remove_unused_brs_try_rewrite_one_sided_stack_tail_if_to_select(
ctx, func, catch_ref, reorder_cache, label_refs,
) ||
changed
}
@ir.HotOp::Return =>
changed = remove_unused_brs_try_voidify_tail_return_if(
ctx, func, catch_ref, label_refs,
) ||
changed
@ir.HotOp::Nop =>
changed = remove_unused_brs_trim_trailing_nops(
ctx, func, catch_ref,
) ||
changed
_ => ()
}
}
if catch_tail_id >= 0 &&
remove_unused_brs_region_has_removable_tail_target(func, catch_ref) {
changed = remove_unused_brs_try_remove_region_tail(
ctx, func, catch_ref, reorder_cache,
) ||
changed
}
}
None => ()
}
}
@ir.HotOp::TryTable => {
if remove_unused_brs_try_rewrite_caught_throws_in_node(
ctx,
func,
node_id,
[],
) {
return true
}
let body_ref = @ir.hot_region_body_ref(func, node_id)
changed = remove_unused_brs_visit_region(
ctx, func, body_ref, seen, reorder_cache, label_refs, branch_payload_children,
func_has_br_table, return_context, control_cache, return_cache,
) ||
changed
let body_tail_id_for_return = remove_unused_brs_region_tail_id(
func, body_ref,
)
if return_context && body_tail_id_for_return >= 0 {
let body_tail_op = @ir.hot_node_get(func, body_tail_id_for_return).op
match body_tail_op {
@ir.HotOp::Return =>
changed = remove_unused_brs_try_strip_return_context_tail(
ctx, func, body_ref,
) ||
changed
_ => ()
}
}
let body_tail_id = remove_unused_brs_region_tail_id(func, body_ref)
if body_tail_id >= 0 {
let body_tail_op = @ir.hot_node_get(func, body_tail_id).op
match body_tail_op {
@ir.HotOp::If => {
changed = remove_unused_brs_try_voidify_tail_value_if_block_exit(
ctx, func, body_ref, label_refs,
) ||
changed
changed = remove_unused_brs_try_strip_tail_if_exits(
ctx, func, body_ref,
) ||
changed
changed = remove_unused_brs_try_rewrite_one_sided_stack_tail_if_to_select(
ctx, func, body_ref, reorder_cache, label_refs,
) ||
changed
}
@ir.HotOp::Return =>
changed = remove_unused_brs_try_voidify_tail_return_if(
ctx, func, body_ref, label_refs,
) ||
changed
@ir.HotOp::Nop =>
changed = remove_unused_brs_trim_trailing_nops(ctx, func, body_ref) ||
changed
_ => ()
}
}
if body_tail_id >= 0 &&
remove_unused_brs_region_has_removable_tail_target(func, body_ref) {
changed = remove_unused_brs_try_remove_region_tail(
ctx, func, body_ref, reorder_cache,
) ||
changed
}
let catch_list_ref = @ir.hot_region_catch_list_ref(func, node_id)
changed = remove_unused_brs_visit_region(
ctx, func, catch_list_ref, seen, reorder_cache, label_refs, branch_payload_children,
func_has_br_table, return_context, control_cache, return_cache,
) ||
changed
let catch_list_tail_id_for_return = remove_unused_brs_region_tail_id(
func, catch_list_ref,
)
if return_context && catch_list_tail_id_for_return >= 0 {
let catch_list_tail_op = @ir.hot_node_get(
func, catch_list_tail_id_for_return,
).op
match catch_list_tail_op {
@ir.HotOp::Return =>
changed = remove_unused_brs_try_strip_return_context_tail(
ctx, func, catch_list_ref,
) ||
changed
_ => ()
}
}
let catch_list_tail_id = remove_unused_brs_region_tail_id(
func, catch_list_ref,
)
if catch_list_tail_id >= 0 {
let catch_list_tail_op = @ir.hot_node_get(func, catch_list_tail_id).op
match catch_list_tail_op {
@ir.HotOp::If => {
changed = remove_unused_brs_try_strip_tail_if_exits(
ctx, func, catch_list_ref,
) ||
changed
changed = remove_unused_brs_try_rewrite_one_sided_stack_tail_if_to_select(
ctx, func, catch_list_ref, reorder_cache, label_refs,
) ||
changed
}
@ir.HotOp::Return =>
changed = remove_unused_brs_try_voidify_tail_return_if(
ctx, func, catch_list_ref, label_refs,
) ||
changed
@ir.HotOp::Nop =>
changed = remove_unused_brs_trim_trailing_nops(
ctx, func, catch_list_ref,
) ||
changed
_ => ()
}
}
if catch_list_tail_id >= 0 &&
remove_unused_brs_region_has_removable_tail_target(func, catch_list_ref) {
changed = remove_unused_brs_try_remove_region_tail(
ctx, func, catch_list_ref, reorder_cache,
) ||
changed
}
}
_ =>
changed = remove_unused_brs_visit_generic_children(
ctx, func, node_id, seen, reorder_cache, label_refs, branch_payload_children,
func_has_br_table, control_cache, return_cache,
) ||
changed
}
changed
}
///|
fn remove_unused_brs_find_region_root_index(
func : @ir.HotFunc,
region_ref : @ir.HotRegionRef,
node_id : Int32,
) -> Int32 {
for idx = 0; idx < @ir.hot_region_root_count(func, region_ref); idx = idx + 1 {
if @ir.hot_region_get(func, region_ref, idx) == node_id {
return idx
}
}
-1
}
///|
fn remove_unused_brs_visit_region(
ctx : HotPassContext,
func : @ir.HotFunc,
region_ref : @ir.HotRegionRef,
seen : Array[Int32],
reorder_cache : Array[Int32],
label_refs : Array[Int32],
branch_payload_children : @bitset.BitSet,
func_has_br_table : Bool,
return_context : Bool,
control_cache : Array[Int32],
return_cache : Array[Int32],
) -> Bool {
if remove_unused_brs_prune_dead_suffix_after_nonfallthrough(
ctx, func, region_ref, label_refs,
) {
return true
}
let mut changed = false
let roots : Array[Int32] = []
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))
}
for idx = 0; idx < roots.length(); idx = idx + 1 {
let node_id = roots[idx]
if !@ir.hot_node_is_live(func, node_id) {
continue
}
let current_idx = remove_unused_brs_find_region_root_index(
func, region_ref, node_id,
)
if current_idx < 0 {
continue
}
if remove_unused_brs_try_fallthrough_caught_try_table_outer_exit(
ctx, func, region_ref, current_idx,
) {
return true
}
if remove_unused_brs_try_rewrite_gc_br_on_root(
ctx, func, region_ref, current_idx,
) {
changed = true
continue
}
match @ir.hot_node_get(func, node_id).op {
@ir.HotOp::BrIf => {
if remove_unused_brs_try_fold_constant_br_if(
ctx, func, region_ref, current_idx,
) {
changed = true
continue
}
if remove_unused_brs_try_merge_adjacent_br_ifs(
ctx, func, region_ref, current_idx,
) {
changed = true
continue
}
if remove_unused_brs_try_rewrite_br_if_eq_ladder_to_br_table(
ctx, func, region_ref, current_idx,
) {
changed = true
continue
}
}
@ir.HotOp::BrTable =>
if remove_unused_brs_try_optimize_switch(
ctx, func, region_ref, current_idx, reorder_cache,
) {
changed = true
continue
}
_ => ()
}
if !remove_unused_brs_subtree_has_embedded_control(
func, node_id, control_cache,
) {
continue
}
match @ir.hot_node_get(func, node_id).op {
@ir.HotOp::Block => {
if remove_unused_brs_try_drop_void_self_branch_block(ctx, func, node_id) {
changed = true
continue
}
if remove_unused_brs_try_drop_redundant_self_br_if_value_block(
ctx, func, node_id,
) {
changed = true
continue
}
if remove_unused_brs_try_thread_try_table_catches_through_one_child_block(
ctx, func, node_id,
) {
changed = true
continue
}
if remove_unused_brs_try_thread_jump_through_block(
ctx, func, region_ref, current_idx, label_refs,
) {
changed = true
continue
}
if remove_unused_brs_try_rewrite_branches_to_trap_block(
ctx, func, region_ref, current_idx,
) {
changed = true
continue
}
if remove_unused_brs_try_sink_single_if_exit_block(
ctx, func, region_ref, current_idx, label_refs,
) {
changed = true
continue
}
if remove_unused_brs_try_sink_if_arm_self_branch_block(
ctx, func, region_ref, current_idx, label_refs,
) {
changed = true
continue
}
if remove_unused_brs_try_inline_single_br_if_block(
ctx, func, region_ref, current_idx, label_refs,
) {
changed = true
continue
}
if remove_unused_brs_try_drop_self_targeting_br_table_block(
ctx, func, region_ref, current_idx, label_refs,
) {
changed = true
continue
}
if remove_unused_brs_try_rewrite_br_table_continuation_wrappers(
ctx, func, region_ref, current_idx, label_refs,
) {
return true
}
if remove_unused_brs_try_rewrite_block_prefix_payload_branch_root(
ctx, func, region_ref, current_idx, label_refs,
) {
changed = true
continue
}
if remove_unused_brs_try_move_loop_backedge_suffix_into_block_exit_br_if(
ctx, func, region_ref, current_idx, label_refs,
) {
changed = true
continue
}
if remove_unused_brs_try_flip_single_loop_adjacent_br_if_backedge(
ctx, func, region_ref, current_idx,
) {
changed = true
continue
}
if remove_unused_brs_try_rotate_void_block_single_loop(
ctx, func, region_ref, current_idx,
) {
changed = true
continue
}
if remove_unused_brs_try_restructure_self_branch_block_prefix(
ctx, func, region_ref, current_idx, label_refs, reorder_cache,
) {
changed = true
continue
}
}
@ir.HotOp::Drop => {
if remove_unused_brs_try_drop_redundant_self_br_if_value(
ctx, func, region_ref, current_idx,
) {
changed = true
continue
}
if remove_unused_brs_try_rewrite_drop_result_block_if_tail_branch(
ctx, func, region_ref, current_idx, label_refs,
) {
changed = true
continue
}
}
@ir.HotOp::Br =>
if remove_unused_brs_try_rewrite_branch_payload_if(
ctx, func, region_ref, current_idx, label_refs,
) {
changed = true
continue
}
@ir.HotOp::If => {
if remove_unused_brs_try_flip_loop_eqz_backedge_if_to_else(
ctx, func, region_ref, current_idx, label_refs,
) {
changed = true
continue
}
if remove_unused_brs_try_invert_empty_then_if(
ctx, func, node_id, label_refs,
) {
changed = true
continue
}
if remove_unused_brs_try_voidify_if_block_exit_unreachable_suffix(
ctx, func, region_ref, current_idx, label_refs,
) {
changed = true
continue
}
if remove_unused_brs_try_restructure_one_exit_arm_if_suffix(
ctx, func, region_ref, current_idx, label_refs,
) {
changed = true
continue
}
if remove_unused_brs_try_restructure_one_arm_return_if_suffix(
ctx, func, region_ref, current_idx, label_refs,
) {
changed = true
continue
}
if remove_unused_brs_try_rewrite_prefixed_one_arm_payload_branch_if_suffix(
ctx, func, region_ref, current_idx, label_refs, reorder_cache,
) {
changed = true
continue
}
if remove_unused_brs_try_rewrite_one_arm_payload_branch_if(
ctx, func, region_ref, current_idx, label_refs, func_has_br_table,
) {
changed = true
continue
}
if remove_unused_brs_try_rewrite_two_arm_branch_if(
ctx, func, region_ref, current_idx,
) {
changed = true
continue
}
}
@ir.HotOp::LocalSet | @ir.HotOp::LocalTee => {
if @ir.hot_child_count(func, node_id) == 1 {
let value_id = @ir.hot_child_get(func, node_id, 0)
let value_is_if = if value_id >= 0 &&
@ir.hot_node_is_live(func, value_id) {
match @ir.hot_node_get(func, value_id).op {
@ir.HotOp::If => true
_ => false
}
} else {
false
}
if value_is_if &&
remove_unused_brs_try_rewrite_value_if_to_select(
ctx, func, value_id, reorder_cache,
) {
changed = true
continue
}
}
if remove_unused_brs_try_rewrite_region_local_set_br_arm(
ctx, func, region_ref, current_idx,
) {
changed = true
continue
}
if remove_unused_brs_try_rewrite_region_local_set_copy_arm(
ctx, func, region_ref, current_idx,
) {
changed = true
continue
}
}
_ => ()
}
let node_return_context = return_context && idx == roots.length() - 1
changed = remove_unused_brs_visit_node(
ctx,
func,
Some({ region_ref, idx: current_idx, }),
node_id,
seen,
reorder_cache,
label_refs,
branch_payload_children,
func_has_br_table,
false,
node_return_context,
control_cache,
return_cache,
) ||
changed
}
changed
}
///|
fn remove_unused_brs_null_bottom_ref_type(
ref_type : @lib.RefType,
) -> @lib.RefType? {
let heap_type = match ref_type.get_heap_type() {
@lib.AbsHeapTypeHeapType(@lib.FuncAbsHeapType | @lib.NoFuncAbsHeapType) =>
@lib.HeapType::abs(@lib.AbsHeapType::no_func())
@lib.AbsHeapTypeHeapType(@lib.ExternAbsHeapType | @lib.NoExternAbsHeapType) =>
@lib.HeapType::abs(@lib.AbsHeapType::no_extern())
@lib.AbsHeapTypeHeapType(@lib.ExnAbsHeapType | @lib.NoExnAbsHeapType) =>
@lib.HeapType::abs(@lib.AbsHeapType::no_exn())
@lib.AbsHeapTypeHeapType(@lib.ContAbsHeapType | @lib.NoContAbsHeapType) =>
@lib.HeapType::abs(@lib.AbsHeapType::no_cont())
@lib.AbsHeapTypeHeapType(
@lib.WaitqueueAbsHeapType
| @lib.NoWaitqueueAbsHeapType
) => @lib.HeapType::abs(@lib.AbsHeapType::no_waitqueue())
@lib.AbsHeapTypeHeapType(_) => @lib.HeapType::abs(@lib.AbsHeapType::none())
_ => return None
}
Some(@lib.RefType::new(true, heap_type))
}
///|
fn remove_unused_brs_refinalize_lowered_null_block_instr(
instr : @lib.Instruction,
) -> @lib.Instruction {
match instr {
@lib.Block(block_type, @lib.Expr(body)) => {
let body = remove_unused_brs_refinalize_lowered_null_block_body(body)
match block_type {
@lib.ValTypeBlockType(@lib.RefTypeValType(current_ref)) if body.length() ==
2 =>
match (body[body.length() - 2], body[body.length() - 1]) {
(@lib.RefNull(ref_type), @lib.Br(@lib.LabelIdx(0U))) =>
match remove_unused_brs_null_bottom_ref_type(ref_type) {
Some(bottom_ref) if bottom_ref != current_ref => {
let rewritten = body.copy()
rewritten[rewritten.length() - 2] = @lib.Instruction::ref_null_type(
bottom_ref,
)
return @lib.Instruction::block(
@lib.BlockType::val_type(@lib.ValType::ref_type(bottom_ref)),
@lib.Expr::new(rewritten),
)
}
_ => ()
}
_ => ()
}
_ => ()
}
@lib.Instruction::block(block_type, @lib.Expr::new(body))
}
@lib.Loop(block_type, @lib.Expr(body)) =>
@lib.Instruction::loop_(
block_type,
@lib.Expr::new(
remove_unused_brs_refinalize_lowered_null_block_body(body),
),
)
@lib.If(block_type, then_body, else_body) =>
@lib.Instruction::if_(
block_type,
remove_unused_brs_refinalize_lowered_null_block_body(then_body),
match else_body {
Some(body) =>
Some(remove_unused_brs_refinalize_lowered_null_block_body(body))
None => None
},
)
@lib.TryTable(block_type, catches, @lib.Expr(body)) =>
@lib.Instruction::try_table(
block_type,
catches,
@lib.Expr::new(
remove_unused_brs_refinalize_lowered_null_block_body(body),
),
)
_ => instr
}
}
///|
fn remove_unused_brs_refinalize_lowered_null_block_body(
body : Array[@lib.Instruction],
) -> Array[@lib.Instruction] {
let rewritten : Array[@lib.Instruction] = []
for instr in body {
rewritten.push(remove_unused_brs_refinalize_lowered_null_block_instr(instr))
}
rewritten
}
///|
fn remove_unused_brs_refinalize_lowered_null_blocks(
func : @lib.Func,
) -> @lib.Func {
match func {
@lib.Func(locals, @lib.Expr(body)) =>
@lib.Func::new(
locals,
@lib.Expr::new(
remove_unused_brs_refinalize_lowered_null_block_body(body),
),
)
}
}
///|
fn remove_unused_brs_has_literal_multivalue_drop_lowering(
func : @ir.HotFunc,
) -> Bool {
let scan = remove_unused_brs_compute_cycle_scan(func)
let node_count = @ir.hot_node_count(func)
let drop_uses = Array::make(node_count, 0)
let use_counts = @ir.hot_node_use_counts_build(func)
for user_id = 0; user_id < node_count; user_id = user_id + 1 {
if !@ir.hot_node_is_live(func, user_id) {
continue
}
match @ir.hot_node_get(func, user_id).op {
@ir.HotOp::Drop if @ir.hot_child_count(func, user_id) == 1 => {
let child_id = @ir.hot_child_get(func, user_id, 0)
if child_id >= 0 && child_id < node_count {
drop_uses[child_id] = drop_uses[child_id] + 1
}
}
_ => ()
}
}
for block_id = 0; block_id < node_count; block_id = block_id + 1 {
if !@ir.hot_node_is_live(func, block_id) {
continue
}
match @ir.hot_node_get(func, block_id).op {
@ir.HotOp::Block => ()
_ => continue
}
let result_arity = @ir.hot_node_result_arity(func, block_id)
if result_arity <= 1 {
continue
}
let label_id = @ir.hot_control_node_label(func, block_id)
if label_id < 0 ||
label_id >= scan.label_refs.length() ||
scan.label_refs[label_id] != 0 {
continue
}
match @ir.hot_type_get(func, @ir.hot_node_type(func, block_id)) {
@ir.HotTypeInfo::BlockResultResolved(_, params, _) if params.length() == 0 =>
()
_ => continue
}
let body_ref = @ir.hot_region_body_ref(func, block_id)
let mut seen_arity = 0
let mut literal_roots = true
for idx = 0; idx < @ir.hot_region_root_count(func, body_ref); idx = idx + 1 {
let root_id = @ir.hot_region_get(func, body_ref, idx)
if root_id < 0 || !@ir.hot_node_is_live(func, root_id) {
literal_roots = false
break
}
match @ir.hot_node_get(func, root_id).op {
@ir.HotOp::Const | @ir.HotOp::RefNull => ()
_ => {
literal_roots = false
break
}
}
seen_arity += @ir.hot_node_result_arity(func, root_id)
}
if !literal_roots || seen_arity != result_arity {
continue
}
if drop_uses[block_id] == result_arity &&
@ir.hot_node_use_count(use_counts, block_id) == drop_uses[block_id] {
return true
}
}
false
}
///|
fn remove_unused_brs_only_literal_multivalue_consumers(
func : @ir.HotFunc,
) -> Bool {
let mut saw_multivalue_block = 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::Block => {
if @ir.hot_node_result_arity(func, node_id) <= 1 {
return false
}
let body_ref = @ir.hot_region_body_ref(func, node_id)
for idx = 0
idx < @ir.hot_region_root_count(func, body_ref)
idx = idx + 1 {
let root_id = @ir.hot_region_get(func, body_ref, idx)
if root_id < 0 || !@ir.hot_node_is_live(func, root_id) {
return false
}
match @ir.hot_node_get(func, root_id).op {
@ir.HotOp::Const | @ir.HotOp::RefNull => ()
_ => return false
}
}
saw_multivalue_block = true
}
@ir.HotOp::Const
| @ir.HotOp::RefNull
| @ir.HotOp::LocalSet
| @ir.HotOp::Drop
| @ir.HotOp::Nop => ()
_ => return false
}
}
saw_multivalue_block
}
///|
fn remove_unused_brs_has_function_label_br_table_target(
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
}
let node = @ir.hot_node_get(func, node_id)
match node.op {
@ir.HotOp::BrTable => {
let default_target = @ir.hot_branch_table_default_target(
func,
node.imm0,
)
if default_target < 0 || default_target >= func.labels.length() {
return true
}
for target in @ir.hot_branch_table_targets(func, node.imm0) {
if target < 0 || target >= func.labels.length() {
return true
}
}
}
_ => ()
}
}
false
}
///|
fn remove_unused_brs_run(
ctx : HotPassContext,
func : @ir.HotFunc,
) -> HotPassResult {
if remove_unused_brs_has_function_label_br_table_target(func) {
pipeline_trace_hook(
ctx.trace,
ctx.descriptor.name,
"skip-hot",
payload="reason=function-label-br-table-target-noop",
)
return HotPassResult::unchanged()
}
let skip_summary = remove_unused_brs_collect_hot_void_return_ladder_summary(
func,
)
if remove_unused_brs_can_skip_large_br_table_return_ladder(skip_summary) {
pipeline_trace_hook(
ctx.trace,
ctx.descriptor.name,
"skip-hot",
payload="reason=large-br-table-return-ladder-noop",
)
return HotPassResult::unchanged()
}
if remove_unused_brs_can_skip_large_tagged_result_prefix_ladder(skip_summary) {
pipeline_trace_hook(
ctx.trace,
ctx.descriptor.name,
"skip-hot",
payload="reason=large-tagged-result-prefix-ladder-noop",
)
return HotPassResult::unchanged()
}
if remove_unused_brs_can_skip_medium_branchy_block_ladder(skip_summary) {
pipeline_trace_hook(
ctx.trace,
ctx.descriptor.name,
"skip-hot",
payload="reason=medium-branchy-block-ladder-noop",
)
return HotPassResult::unchanged()
}
if remove_unused_brs_can_skip_call_heavy_mixed_if_mesh(skip_summary) {
pipeline_trace_hook(
ctx.trace,
ctx.descriptor.name,
"skip-hot",
payload="reason=call-heavy-mixed-if-mesh-noop",
)
return HotPassResult::unchanged()
}
if remove_unused_brs_can_skip_localset_heavy_value_if_mesh(skip_summary) {
pipeline_trace_hook(
ctx.trace,
ctx.descriptor.name,
"skip-hot",
payload="reason=localset-heavy-value-if-mesh-noop",
)
return HotPassResult::unchanged()
}
if remove_unused_brs_can_skip_large_void_return_ladder(skip_summary) {
let reason = if @ir.hot_local_count(func) >= 20 &&
@ir.hot_local_count(func) <= 40 {
"nested-constructor-return-ladder-noop"
} else {
"large-void-if-return-ladder-noop"
}
pipeline_trace_hook(
ctx.trace,
ctx.descriptor.name,
"skip-hot",
payload="reason=\{reason}",
)
return HotPassResult::unchanged()
}
if remove_unused_brs_has_giant_br_table(func) {
pipeline_trace_hook(
ctx.trace,
ctx.descriptor.name,
"skip-hot",
payload="reason=giant-br-table-convergence-noop",
)
return HotPassResult::unchanged()
}
if remove_unused_brs_has_result_type_return_hazard(func) {
pipeline_trace_hook(
ctx.trace,
ctx.descriptor.name,
"skip-hot",
payload="reason=return-bypasses-result-type-noop",
)
return HotPassResult::unchanged()
}
let has_result_local_set_stack_hazard = if skip_summary.shape.local_set_count ==
0 ||
(
skip_summary.shape.multivalue_control_count == 0 &&
skip_summary.shape.stack_effect_count == 0
) {
false
} else {
remove_unused_brs_region_has_result_local_set_stack_hazard(
func,
@ir.HotRegionRef::root(),
)
}
if has_result_local_set_stack_hazard {
pipeline_trace_hook(
ctx.trace,
ctx.descriptor.name,
"skip-hot",
payload="reason=result-local-set-stack-hazard-noop",
)
return HotPassResult::unchanged()
}
let literal_multivalue_drop_lowering = if skip_summary.shape.multivalue_block_count ==
0 {
false
} else {
remove_unused_brs_has_literal_multivalue_drop_lowering(func)
}
if literal_multivalue_drop_lowering &&
remove_unused_brs_only_literal_multivalue_consumers(func) {
pipeline_trace_hook(
ctx.trace,
ctx.descriptor.name,
"fast-path",
payload="reason=literal-multivalue-consumers",
)
return HotPassResult::changed()
}
let mut any_changed = false
let mut changed = true
let mut cycles = 0
let seen : Array[Int32] = []
let reorder_cache : Array[Int32] = []
let control_cache : Array[Int32] = []
let return_cache : Array[Int32] = []
while changed && cycles < 8 {
cycles += 1
changed = false
let node_count = @ir.hot_node_count(func)
remove_unused_brs_prepare_int_cache(seen, node_count, 0)
remove_unused_brs_prepare_int_cache(reorder_cache, node_count, -1)
remove_unused_brs_prepare_int_cache(control_cache, node_count, -1)
remove_unused_brs_prepare_int_cache(return_cache, node_count, -1)
let cycle_scan = remove_unused_brs_compute_cycle_scan(func)
let label_refs = cycle_scan.label_refs
changed = remove_unused_brs_visit_region(
ctx,
func,
@ir.HotRegionRef::root(),
seen,
reorder_cache,
label_refs,
cycle_scan.branch_payload_children,
cycle_scan.has_br_table,
true,
control_cache,
return_cache,
) ||
changed
changed = remove_unused_brs_flatten_block_if_chains(
ctx,
func,
@ir.HotRegionRef::root(),
label_refs,
) ||
changed
changed = remove_unused_brs_try_strip_tail_if_exits(
ctx,
func,
@ir.HotRegionRef::root(),
) ||
changed
changed = remove_unused_brs_try_rewrite_one_sided_stack_tail_if_to_select(
ctx,
func,
@ir.HotRegionRef::root(),
reorder_cache,
label_refs,
) ||
changed
changed = remove_unused_brs_try_tail_loop_return_to_fallthrough(
ctx,
func,
@ir.HotRegionRef::root(),
label_refs,
) ||
changed
changed = remove_unused_brs_try_voidify_tail_return_if(
ctx,
func,
@ir.HotRegionRef::root(),
label_refs,
) ||
changed
changed = remove_unused_brs_try_remove_region_tail(
ctx,
func,
@ir.HotRegionRef::root(),
reorder_cache,
) ||
changed
changed = remove_unused_brs_simplify_dead_local_tees(ctx, func) || changed
changed = remove_unused_brs_trim_trailing_nops(
ctx,
func,
@ir.HotRegionRef::root(),
) ||
changed
// Run the narrow nested-filler cleanup after this cycle's ordinary RUB
// rewrites. It may expose another wrapper simplification for the next cycle.
// Build CFG only after the traversal finds an exact eligible suffix.
let prune_cfg = RemoveUnusedBrsPruneCfg::new()
changed = remove_unused_brs_prune_one_nested_dead_suffix(
ctx,
func,
prune_cfg,
@ir.HotRegionRef::root(),
) ||
changed
any_changed = any_changed || changed
}
if any_changed || literal_multivalue_drop_lowering {
HotPassResult::changed()
} else {
HotPassResult::unchanged()
}
}