// 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 2024 WebAssembly Community Group participants
// Counterpart: src/passes/HeapStoreOptimization.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 heap_store_optimization_descriptor() -> @ir.HotPassDescriptor {
@ir.HotPassDescriptor::new(
"heap-store-optimization",
// Effects are required lazily after the tiny pure root fast path misses.
requires=[],
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 heap_store_optimization_summary() -> String {
"Fold exact struct.set writes into nearby struct.new allocations when local and effect ordering stays safe."
}
///|
priv struct HsoTypeCache {
struct_field_counts : Array[Int32]
default_field_counts : Array[Int32]
default_field_types : Array[Array[@lib.ValType]?]
}
///|
priv struct HsoOrderEffects {
mut shared_memory_read : Bool
mut shared_memory_write : Bool
mut shared_struct_read : Bool
mut shared_struct_write : Bool
mut shared_array_read : Bool
mut shared_array_write : Bool
mut acquire_read : Bool
mut release_write : Bool
mut seq_cst : Bool
mut atomic_order_mask : @ir.EffectMask
}
///|
priv struct HsoPredicateCache {
readonly_revision : Array[Int32]
readonly_result : @bitset.BitSet
skip_revision : Array[Int32]
skip_result : @bitset.BitSet
order_revision : Array[Int32]
order_result : Array[HsoOrderEffects?]
}
///|
priv struct HsoExternalRoots {
root_arrays : Array[Array[Int32]]
starts : Array[Int32]
}
///|
priv struct HsoLocalOrderWorkspace {
maxima : Array[Int32]
stamps : Array[Int32]
mut generation : Int32
}
///|
fn hso_new_local_order_workspace(local_count : Int32) -> HsoLocalOrderWorkspace {
{
maxima: Array::make(local_count, -1),
stamps: Array::make(local_count, 0),
generation: 0,
}
}
///|
fn hso_local_order_workspace_begin(workspace : HsoLocalOrderWorkspace) -> Unit {
workspace.generation += 1
}
///|
fn hso_local_order_workspace_note(
workspace : HsoLocalOrderWorkspace,
local_id : Int32,
root_id : Int32,
) -> Unit {
if local_id < 0 || local_id >= workspace.maxima.length() {
return
}
if workspace.stamps[local_id] != workspace.generation {
workspace.stamps[local_id] = workspace.generation
workspace.maxima[local_id] = root_id
} else {
workspace.maxima[local_id] = Int32::max(workspace.maxima[local_id], root_id)
}
}
///|
fn hso_local_order_workspace_get(
workspace : HsoLocalOrderWorkspace,
local_id : Int32,
) -> Int32 {
if local_id < 0 ||
local_id >= workspace.maxima.length() ||
workspace.stamps[local_id] != workspace.generation {
-1
} else {
workspace.maxima[local_id]
}
}
///|
fn hso_new_order_effects() -> HsoOrderEffects {
{
shared_memory_read: false,
shared_memory_write: false,
shared_struct_read: false,
shared_struct_write: false,
shared_array_read: false,
shared_array_write: false,
acquire_read: false,
release_write: false,
seq_cst: false,
atomic_order_mask: 0U,
}
}
///|
fn hso_order_effects_accesses_shared(effects : HsoOrderEffects) -> Bool {
effects.shared_memory_read ||
effects.shared_memory_write ||
effects.shared_struct_read ||
effects.shared_struct_write ||
effects.shared_array_read ||
effects.shared_array_write
}
///|
fn hso_order_effects_merge(
effects : HsoOrderEffects,
other : HsoOrderEffects,
) -> Unit {
effects.shared_memory_read = effects.shared_memory_read ||
other.shared_memory_read
effects.shared_memory_write = effects.shared_memory_write ||
other.shared_memory_write
effects.shared_struct_read = effects.shared_struct_read ||
other.shared_struct_read
effects.shared_struct_write = effects.shared_struct_write ||
other.shared_struct_write
effects.shared_array_read = effects.shared_array_read ||
other.shared_array_read
effects.shared_array_write = effects.shared_array_write ||
other.shared_array_write
effects.acquire_read = effects.acquire_read || other.acquire_read
effects.release_write = effects.release_write || other.release_write
effects.seq_cst = effects.seq_cst || other.seq_cst
effects.atomic_order_mask = effects.atomic_order_mask |
other.atomic_order_mask
}
///|
fn hso_order_effects_note_read_order(
effects : HsoOrderEffects,
order : @lib.AtomicOrder,
) -> Unit {
effects.atomic_order_mask = effects.atomic_order_mask |
@ir.effects_atomic_order_mask(order, read=true, write=false)
match order {
@lib.Relaxed => ()
@lib.AcqRel => effects.acquire_read = true
@lib.SeqCst => effects.seq_cst = true
}
}
///|
fn hso_order_effects_note_write_order(
effects : HsoOrderEffects,
order : @lib.AtomicOrder,
) -> Unit {
effects.atomic_order_mask = effects.atomic_order_mask |
@ir.effects_atomic_order_mask(order, read=false, write=true)
match order {
@lib.Relaxed => ()
@lib.AcqRel => effects.release_write = true
@lib.SeqCst => effects.seq_cst = true
}
}
///|
fn hso_type_is_shared(
module_ctx : @ir.HotModuleContext?,
type_idx : @lib.TypeIdx,
) -> Bool {
match module_ctx {
Some(ctx) =>
match @ir.hot_module_resolve_subtype(ctx, type_idx) {
Some(subtype) => subtype.metadata().is_shared()
None => false
}
None => false
}
}
///|
fn hso_order_effects_note_exact_instr(
effects : HsoOrderEffects,
module_ctx : @ir.HotModuleContext?,
instr : @lib.Instruction,
) -> Unit {
match instr {
@lib.I32AtomicLoad(order, _)
| @lib.I64AtomicLoad(order, _)
| @lib.I32AtomicLoad8U(order, _)
| @lib.I32AtomicLoad16U(order, _)
| @lib.I64AtomicLoad8U(order, _)
| @lib.I64AtomicLoad16U(order, _)
| @lib.I64AtomicLoad32U(order, _) => {
effects.shared_memory_read = true
hso_order_effects_note_read_order(effects, order)
}
@lib.I32AtomicStore(order, _)
| @lib.I64AtomicStore(order, _)
| @lib.I32AtomicStore8(order, _)
| @lib.I32AtomicStore16(order, _)
| @lib.I64AtomicStore8(order, _)
| @lib.I64AtomicStore16(order, _)
| @lib.I64AtomicStore32(order, _) => {
effects.shared_memory_write = true
hso_order_effects_note_write_order(effects, order)
}
@lib.AtomicRmw(order, _, _) | @lib.AtomicCmpxchg(order, _, _) => {
effects.shared_memory_read = true
effects.shared_memory_write = true
hso_order_effects_note_read_order(effects, order)
hso_order_effects_note_write_order(effects, order)
}
@lib.WaitqueueNotify | @lib.StructWait(_, _) => {
effects.shared_struct_read = true
effects.shared_struct_write = true
hso_order_effects_note_read_order(effects, @lib.AtomicOrder::seq_cst())
hso_order_effects_note_write_order(effects, @lib.AtomicOrder::seq_cst())
}
@lib.MemoryAtomicWait32(_)
| @lib.MemoryAtomicWait64(_)
| @lib.MemoryAtomicNotify(_)
| @lib.AtomicFence(_) => {
effects.shared_memory_read = true
effects.shared_memory_write = true
effects.seq_cst = true
}
@lib.StructGet(type_idx, _)
| @lib.StructGetS(type_idx, _)
| @lib.StructGetU(type_idx, _) =>
if hso_type_is_shared(module_ctx, type_idx) {
effects.shared_struct_read = true
}
@lib.StructSet(type_idx, _) =>
if hso_type_is_shared(module_ctx, type_idx) {
effects.shared_struct_write = true
}
@lib.StructAtomicSet(order, _, _) => {
effects.shared_struct_write = true
hso_order_effects_note_write_order(effects, order)
}
@lib.StructAtomicGet(order, _, _)
| @lib.StructAtomicGetS(order, _, _)
| @lib.StructAtomicGetU(order, _, _) => {
effects.shared_struct_read = true
hso_order_effects_note_read_order(effects, order)
}
@lib.StructAtomicRmw(order, _, _, _)
| @lib.StructAtomicCmpxchg(order, _, _) => {
effects.shared_struct_read = true
effects.shared_struct_write = true
hso_order_effects_note_read_order(effects, order)
hso_order_effects_note_write_order(effects, order)
}
@lib.ArrayGet(type_idx)
| @lib.ArrayGetS(type_idx)
| @lib.ArrayGetU(type_idx) =>
if hso_type_is_shared(module_ctx, type_idx) {
effects.shared_array_read = true
}
@lib.ArraySet(type_idx) | @lib.ArrayFill(type_idx) =>
if hso_type_is_shared(module_ctx, type_idx) {
effects.shared_array_write = true
}
@lib.ArrayAtomicSet(order, _) => {
effects.shared_array_write = true
hso_order_effects_note_write_order(effects, order)
}
@lib.ArrayAtomicGet(order, _)
| @lib.ArrayAtomicGetS(order, _)
| @lib.ArrayAtomicGetU(order, _) => {
effects.shared_array_read = true
hso_order_effects_note_read_order(effects, order)
}
@lib.ArrayAtomicRmw(order, _, _) | @lib.ArrayAtomicCmpxchg(order, _) => {
effects.shared_array_read = true
effects.shared_array_write = true
hso_order_effects_note_read_order(effects, order)
hso_order_effects_note_write_order(effects, order)
}
_ => ()
}
}
///|
fn hso_collect_order_effects_region(
cache : HsoPredicateCache,
func : @ir.HotFunc,
module_ctx : @ir.HotModuleContext?,
region_ref : @ir.HotRegionRef,
effects : HsoOrderEffects,
) -> Unit {
for idx = 0; idx < @ir.hot_region_root_count(func, region_ref); idx = idx + 1 {
hso_order_effects_merge(
effects,
hso_order_effects_for_node(
cache,
func,
module_ctx,
@ir.hot_region_get(func, region_ref, idx),
),
)
}
}
///|
fn hso_order_effects_for_node(
cache : HsoPredicateCache,
func : @ir.HotFunc,
module_ctx : @ir.HotModuleContext?,
node_id : Int32,
) -> HsoOrderEffects {
if node_id < 0 ||
node_id >= @ir.hot_node_count(func) ||
!@ir.hot_node_is_live(func, node_id) {
return hso_new_order_effects()
}
hso_ensure_predicate_cache_capacity(cache, node_id)
let revision = @ir.hot_revision_current(func)
if cache.order_revision[node_id] == revision {
match cache.order_result[node_id] {
Some(effects) => return effects
None => return hso_new_order_effects()
}
}
let effects = hso_new_order_effects()
// Publish an empty in-progress summary before recursion so a malformed cyclic
// HOT graph terminates; verified HOT graphs are structurally acyclic.
cache.order_revision[node_id] = revision
cache.order_result[node_id] = Some(effects)
match @ir.hot_node_exact_instr(func, node_id) {
Some(instr) =>
hso_order_effects_note_exact_instr(effects, module_ctx, instr)
None => ()
}
// Descendant call barriers merge through the operand and region summaries
// below. Only this node's flag is needed here; a subtree effect query would
// repeat those walks and allocate arena-sized scratch for each non-leaf.
if @ir.hot_node_has_flag(func, node_id, @ir.HOT_FLAG_CALL) {
effects.shared_memory_read = true
effects.shared_memory_write = true
effects.shared_struct_read = true
effects.shared_struct_write = true
effects.shared_array_read = true
effects.shared_array_write = true
}
let operand_node = @ir.hot_node_get(func, node_id)
let operand_count = pass_value_operand_count(func, node_id)
for idx = 0; idx < operand_count; idx = idx + 1 {
hso_order_effects_merge(
effects,
hso_order_effects_for_node(
cache,
func,
module_ctx,
@ir.hot_child_get(
func,
node_id,
pass_value_operand_slot(operand_node, idx),
),
),
)
}
match @ir.hot_node_get(func, node_id).op {
@ir.HotOp::Block | @ir.HotOp::Loop =>
hso_collect_order_effects_region(
cache,
func,
module_ctx,
@ir.hot_region_body_ref(func, node_id),
effects,
)
@ir.HotOp::If => {
hso_collect_order_effects_region(
cache,
func,
module_ctx,
@ir.hot_region_then_ref(func, node_id),
effects,
)
match @ir.hot_region_else_ref(func, node_id) {
Some(region_ref) =>
hso_collect_order_effects_region(
cache, func, module_ctx, region_ref, effects,
)
None => ()
}
}
@ir.HotOp::Try => {
hso_collect_order_effects_region(
cache,
func,
module_ctx,
@ir.hot_region_body_ref(func, node_id),
effects,
)
match @ir.hot_region_catch_ref(func, node_id) {
Some(region_ref) =>
hso_collect_order_effects_region(
cache, func, module_ctx, region_ref, effects,
)
None => ()
}
}
@ir.HotOp::TryTable => {
hso_collect_order_effects_region(
cache,
func,
module_ctx,
@ir.hot_region_body_ref(func, node_id),
effects,
)
hso_collect_order_effects_region(
cache,
func,
module_ctx,
@ir.hot_region_catch_list_ref(func, node_id),
effects,
)
}
_ => ()
}
effects
}
///|
fn hso_order_effects_ordered_before(
first : HsoOrderEffects,
second : HsoOrderEffects,
) -> Bool {
let first_shared = hso_order_effects_accesses_shared(first)
let second_shared = hso_order_effects_accesses_shared(second)
@ir.effects_atomic_orders_conflict(
first.atomic_order_mask,
second.atomic_order_mask,
earlier_shared=first_shared,
later_shared=second_shared,
) ||
(second.release_write && first_shared) ||
(first.acquire_read && second_shared) ||
(first.seq_cst && second_shared) ||
(second.seq_cst && first_shared)
}
///|
fn hso_release_memory_before_shared_gc_read_can_reorder(
first : HsoOrderEffects,
second : HsoOrderEffects,
) -> Bool {
first.release_write &&
first.shared_memory_write &&
!first.shared_memory_read &&
!first.shared_struct_read &&
!first.shared_struct_write &&
!first.shared_array_read &&
!first.shared_array_write &&
!first.acquire_read &&
!first.seq_cst &&
(second.shared_struct_read || second.shared_array_read) &&
!second.shared_memory_read &&
!second.shared_memory_write &&
!second.shared_struct_write &&
!second.shared_array_write &&
!second.acquire_read &&
!second.release_write &&
!second.seq_cst
}
///|
fn hso_subtree_local_write_conflicts(
func : @ir.HotFunc,
node_id : Int32,
other_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::LocalSet | @ir.HotOp::LocalTee =>
if hso_subtree_touches_local(func, other_id, node.imm0) {
return true
}
_ => ()
}
// HOT child spans already include structured regions; visit each edge once.
for child in @ir.hot_child_span(func, node_id) {
if hso_subtree_local_write_conflicts(func, child, other_id) {
return true
}
}
false
}
///|
fn hso_local_order_conflicts(
func : @ir.HotFunc,
first_id : Int32,
second_id : Int32,
) -> Bool {
hso_subtree_local_write_conflicts(func, first_id, second_id) ||
hso_subtree_local_write_conflicts(func, second_id, first_id)
}
///|
fn hso_ordered_before(
cache : HsoPredicateCache,
func : @ir.HotFunc,
module_ctx : @ir.HotModuleContext?,
first_id : Int32,
first_mask : UInt32,
second_id : Int32,
second_mask : UInt32,
) -> Bool {
if hso_local_order_conflicts(func, first_id, second_id) ||
hso_mask_ordered_before(first_mask, second_mask) {
return true
}
hso_order_effects_ordered_before(
hso_order_effects_for_node(cache, func, module_ctx, first_id),
hso_order_effects_for_node(cache, func, module_ctx, second_id),
)
}
///|
fn hso_new_predicate_cache() -> HsoPredicateCache {
{
readonly_revision: [],
readonly_result: @bitset.BitSet::new(0),
skip_revision: [],
skip_result: @bitset.BitSet::new(0),
order_revision: [],
order_result: [],
}
}
///|
fn hso_new_type_cache() -> HsoTypeCache {
{
struct_field_counts: [],
default_field_counts: [],
default_field_types: [],
}
}
///|
fn hso_ensure_predicate_cache_capacity(
cache : HsoPredicateCache,
node_id : Int32,
) -> Unit {
while cache.readonly_revision.length() <= node_id {
cache.readonly_revision.push(-1)
cache.skip_revision.push(-1)
cache.order_revision.push(-1)
cache.order_result.push(None)
}
cache.readonly_result.grow(cache.readonly_revision.length())
cache.skip_result.grow(cache.skip_revision.length())
}
///|
fn hso_ensure_type_cache_capacity(
cache : HsoTypeCache,
type_slot : Int32,
) -> Unit {
while cache.default_field_counts.length() <= type_slot {
cache.struct_field_counts.push(-2)
cache.default_field_counts.push(-2)
cache.default_field_types.push(None)
}
}
///|
fn hso_exact_instr_can_trap(instr : @lib.Instruction?) -> Bool {
match instr {
Some(@lib.Instruction::I32DivS)
| Some(@lib.Instruction::I32DivU)
| Some(@lib.Instruction::I32RemS)
| Some(@lib.Instruction::I32RemU)
| 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 hso_i32_const_at(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(@lib.I32(raw)) => Some(raw)
_ => None
}
_ => None
}
}
///|
fn hso_i64_const_at(func : @ir.HotFunc, node_id : Int32) -> Int64? {
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::I64Const(@lib.I64(raw)) => Some(raw)
_ => None
}
_ => None
}
}
///|
fn hso_exact_div_rem_is_known_safe(func : @ir.HotFunc, node_id : Int32) -> Bool {
if @ir.hot_child_count(func, node_id) != 2 {
return false
}
let lhs_id = @ir.hot_child_get(func, node_id, 0)
let rhs_id = @ir.hot_child_get(func, node_id, 1)
match @ir.hot_node_exact_instr(func, node_id) {
Some(@lib.Instruction::I32DivS) =>
match (hso_i32_const_at(func, lhs_id), hso_i32_const_at(func, rhs_id)) {
(Some(lhs), Some(rhs)) =>
rhs != 0 && !(lhs == -2147483647 - 1 && rhs == -1)
_ => false
}
Some(@lib.Instruction::I32DivU)
| Some(@lib.Instruction::I32RemS)
| Some(@lib.Instruction::I32RemU) =>
match hso_i32_const_at(func, rhs_id) {
Some(rhs) => rhs != 0
None => false
}
Some(@lib.Instruction::I64DivS) =>
match (hso_i64_const_at(func, lhs_id), hso_i64_const_at(func, rhs_id)) {
(Some(lhs), Some(rhs)) =>
rhs != 0L && !(lhs == -9223372036854775808L && rhs == -1L)
_ => false
}
Some(@lib.Instruction::I64DivU)
| Some(@lib.Instruction::I64RemS)
| Some(@lib.Instruction::I64RemU) =>
match hso_i64_const_at(func, rhs_id) {
Some(rhs) => rhs != 0L
None => false
}
_ => false
}
}
///|
fn hso_node_may_trap(func : @ir.HotFunc, node_id : Int32) -> Bool {
if hso_exact_div_rem_is_known_safe(func, node_id) {
false
} else {
@ir.hot_may_trap(func, node_id) ||
hso_exact_instr_can_trap(@ir.hot_node_exact_instr(func, node_id))
}
}
///|
fn hso_effects_without_descendant_trap(
func : @ir.HotFunc,
node_id : Int32,
mask : UInt32,
) -> UInt32 {
let descendants_checked_separately = match
@ir.hot_node_get(func, node_id).op {
@ir.HotOp::Drop
| @ir.HotOp::Block
| @ir.HotOp::Loop
| @ir.HotOp::If
| @ir.HotOp::Try
| @ir.HotOp::TryTable => true
_ => false
}
if descendants_checked_separately {
mask ^ (mask & @ir.EFFECT_MASK_TRAP)
} else {
mask
}
}
///|
fn hso_effects_for_node(
func : @ir.HotFunc,
node_masks : Array[UInt32],
node_id : Int32,
) -> UInt32 {
let base_mask = if node_id >= 0 && node_id < node_masks.length() {
node_masks[node_id]
} else {
@ir.effects_for_node(func, node_id)
}
if node_id >= 0 &&
node_id < @ir.hot_node_count(func) &&
hso_exact_div_rem_is_known_safe(func, node_id) {
base_mask ^ (base_mask & @ir.EFFECT_MASK_TRAP)
} else if node_id >= 0 &&
node_id < @ir.hot_node_count(func) &&
hso_exact_instr_can_trap(@ir.hot_node_exact_instr(func, node_id)) {
base_mask | @ir.EFFECT_MASK_TRAP
} else {
base_mask
}
}
///|
fn hso_reorder_effect_mask(
cache : HsoPredicateCache,
func : @ir.HotFunc,
node_masks : Array[UInt32],
node_id : Int32,
) -> UInt32 {
let mask = hso_effects_for_node(func, node_masks, node_id)
if (mask & @ir.EFFECT_MASK_CONTROL) == 0U {
return mask
}
if hso_subtree_may_skip_local_set(cache, func, node_id) {
mask
} else {
mask ^ (mask & @ir.EFFECT_MASK_CONTROL)
}
}
///|
fn hso_mask_has_control_transfer(mask : UInt32) -> Bool {
(
mask &
(@ir.EFFECT_MASK_CONTROL | @ir.EFFECT_MASK_CALL | @ir.EFFECT_MASK_THROW)
) !=
0U
}
///|
fn hso_mask_ordered_before(barrier_mask : UInt32, moved_mask : UInt32) -> Bool {
if hso_mask_is_pure(barrier_mask) || hso_mask_is_pure(moved_mask) {
return false
}
if hso_mask_has_control_transfer(barrier_mask) ||
hso_mask_has_control_transfer(moved_mask) {
return true
}
let nonlocal_write_mask = @ir.EFFECT_MASK_GLOBAL_STATE |
@ir.EFFECT_MASK_MEMORY_WRITE |
@ir.EFFECT_MASK_TABLE_WRITE
let barrier_writes_nonlocal_state = (barrier_mask & nonlocal_write_mask) != 0U
let moved_writes_nonlocal_state = (moved_mask & nonlocal_write_mask) != 0U
if (
(barrier_mask & @ir.EFFECT_MASK_TRAP) != 0U && moved_writes_nonlocal_state
) ||
((moved_mask & @ir.EFFECT_MASK_TRAP) != 0U && barrier_writes_nonlocal_state) {
return true
}
if (barrier_mask & @ir.EFFECT_MASK_LOCAL_STATE) != 0U &&
(moved_mask & @ir.EFFECT_MASK_LOCAL_STATE) != 0U {
return true
}
if (barrier_mask & @ir.EFFECT_MASK_GLOBAL_STATE) != 0U &&
(moved_mask & @ir.EFFECT_MASK_GLOBAL_STATE) != 0U {
return true
}
let barrier_reads_memory = (barrier_mask & @ir.EFFECT_MASK_MEMORY_READ) != 0U
let barrier_writes_memory = (barrier_mask & @ir.EFFECT_MASK_MEMORY_WRITE) !=
0U
let moved_reads_memory = (moved_mask & @ir.EFFECT_MASK_MEMORY_READ) != 0U
let moved_writes_memory = (moved_mask & @ir.EFFECT_MASK_MEMORY_WRITE) != 0U
if barrier_writes_memory && (moved_reads_memory || moved_writes_memory) {
return true
}
if barrier_reads_memory && moved_writes_memory {
return true
}
let barrier_reads_table = (barrier_mask & @ir.EFFECT_MASK_TABLE_READ) != 0U
let barrier_writes_table = (barrier_mask & @ir.EFFECT_MASK_TABLE_WRITE) != 0U
let moved_reads_table = (moved_mask & @ir.EFFECT_MASK_TABLE_READ) != 0U
let moved_writes_table = (moved_mask & @ir.EFFECT_MASK_TABLE_WRITE) != 0U
if barrier_writes_table && (moved_reads_table || moved_writes_table) {
return true
}
if barrier_reads_table && moved_writes_table {
return true
}
false
}
///|
fn hso_mask_is_pure(mask : UInt32) -> Bool {
mask == 0U
}
///|
fn hso_new_external_roots() -> HsoExternalRoots {
{ root_arrays: [], starts: [], }
}
///|
fn hso_external_roots_push(
external_roots : HsoExternalRoots,
roots : Array[Int32],
start : Int32,
) -> Unit {
external_roots.root_arrays.push(roots)
external_roots.starts.push(start)
}
///|
fn hso_external_roots_pop(external_roots : HsoExternalRoots) -> Unit {
ignore(external_roots.root_arrays.pop())
ignore(external_roots.starts.pop())
}
///|
fn hso_external_roots_touch_local(
func : @ir.HotFunc,
external_roots : HsoExternalRoots,
local_id : Int32,
) -> Bool {
for segment_idx = 0
segment_idx < external_roots.root_arrays.length()
segment_idx = segment_idx + 1 {
let roots = external_roots.root_arrays[segment_idx]
let start = external_roots.starts[segment_idx]
for root_idx = start; root_idx < roots.length(); root_idx = root_idx + 1 {
if hso_subtree_touches_local(func, roots[root_idx], local_id) {
return true
}
}
}
false
}
///|
fn hso_region_touches_local(
func : @ir.HotFunc,
region_ref : @ir.HotRegionRef,
local_id : Int32,
) -> Bool {
for idx = 0; idx < @ir.hot_region_root_count(func, region_ref); idx = idx + 1 {
if hso_subtree_touches_local(
func,
@ir.hot_region_get(func, region_ref, idx),
local_id,
) {
return true
}
}
false
}
///|
fn hso_region_accesses_global(
module_ctx : @ir.HotModuleContext?,
func : @ir.HotFunc,
region_ref : @ir.HotRegionRef,
global_id : Int32,
want_write : Bool,
) -> Bool {
for idx = 0; idx < @ir.hot_region_root_count(func, region_ref); idx = idx + 1 {
if hso_subtree_accesses_global(
module_ctx,
func,
@ir.hot_region_get(func, region_ref, idx),
global_id,
want_write,
) {
return true
}
}
false
}
///|
fn hso_subtree_accesses_global(
module_ctx : @ir.HotModuleContext?,
func : @ir.HotFunc,
node_id : Int32,
global_id : Int32,
want_write : Bool,
) -> 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 =>
if !want_write &&
!globals_are_provably_disjoint(module_ctx, node.imm0, global_id) {
return true
}
@ir.HotOp::GlobalSet =>
if want_write &&
!globals_are_provably_disjoint(module_ctx, node.imm0, global_id) {
return true
}
_ => ()
}
let operand_node = @ir.hot_node_get(func, node_id)
let operand_count = pass_value_operand_count(func, node_id)
for idx = 0; idx < operand_count; idx = idx + 1 {
if hso_subtree_accesses_global(
module_ctx,
func,
@ir.hot_child_get(
func,
node_id,
pass_value_operand_slot(operand_node, idx),
),
global_id,
want_write,
) {
return true
}
}
match node.op {
@ir.HotOp::Block | @ir.HotOp::Loop =>
hso_region_accesses_global(
module_ctx,
func,
@ir.hot_region_body_ref(func, node_id),
global_id,
want_write,
)
@ir.HotOp::If =>
if hso_region_accesses_global(
module_ctx,
func,
@ir.hot_region_then_ref(func, node_id),
global_id,
want_write,
) {
true
} else {
match @ir.hot_region_else_ref(func, node_id) {
Some(region_ref) =>
hso_region_accesses_global(
module_ctx, func, region_ref, global_id, want_write,
)
None => false
}
}
@ir.HotOp::Try =>
if hso_region_accesses_global(
module_ctx,
func,
@ir.hot_region_body_ref(func, node_id),
global_id,
want_write,
) {
true
} else {
match @ir.hot_region_catch_ref(func, node_id) {
Some(region_ref) =>
hso_region_accesses_global(
module_ctx, func, region_ref, global_id, want_write,
)
None => false
}
}
@ir.HotOp::TryTable =>
hso_region_accesses_global(
module_ctx,
func,
@ir.hot_region_body_ref(func, node_id),
global_id,
want_write,
) ||
hso_region_accesses_global(
module_ctx,
func,
@ir.hot_region_catch_list_ref(func, node_id),
global_id,
want_write,
)
_ => false
}
}
///|
fn hso_subtree_global_order_conflicts(
module_ctx : @ir.HotModuleContext?,
func : @ir.HotFunc,
barrier_id : Int32,
moved_id : Int32,
) -> Bool {
if barrier_id < 0 || !@ir.hot_node_is_live(func, barrier_id) {
return false
}
let barrier = @ir.hot_node_get(func, barrier_id)
match barrier.op {
@ir.HotOp::GlobalGet =>
if hso_subtree_accesses_global(
module_ctx,
func,
moved_id,
barrier.imm0,
true,
) {
return true
}
@ir.HotOp::GlobalSet =>
if hso_subtree_accesses_global(
module_ctx,
func,
moved_id,
barrier.imm0,
false,
) ||
hso_subtree_accesses_global(
module_ctx,
func,
moved_id,
barrier.imm0,
true,
) {
return true
}
_ => ()
}
let operand_node = @ir.hot_node_get(func, barrier_id)
let operand_count = pass_value_operand_count(func, barrier_id)
for idx = 0; idx < operand_count; idx = idx + 1 {
if hso_subtree_global_order_conflicts(
module_ctx,
func,
@ir.hot_child_get(
func,
barrier_id,
pass_value_operand_slot(operand_node, idx),
),
moved_id,
) {
return true
}
}
match barrier.op {
@ir.HotOp::Block | @ir.HotOp::Loop =>
hso_region_global_order_conflicts(
module_ctx,
func,
@ir.hot_region_body_ref(func, barrier_id),
moved_id,
)
@ir.HotOp::If =>
hso_region_global_order_conflicts(
module_ctx,
func,
@ir.hot_region_then_ref(func, barrier_id),
moved_id,
) ||
(match @ir.hot_region_else_ref(func, barrier_id) {
Some(region_ref) =>
hso_region_global_order_conflicts(
module_ctx, func, region_ref, moved_id,
)
None => false
})
@ir.HotOp::Try =>
hso_region_global_order_conflicts(
module_ctx,
func,
@ir.hot_region_body_ref(func, barrier_id),
moved_id,
) ||
(match @ir.hot_region_catch_ref(func, barrier_id) {
Some(region_ref) =>
hso_region_global_order_conflicts(
module_ctx, func, region_ref, moved_id,
)
None => false
})
@ir.HotOp::TryTable =>
hso_region_global_order_conflicts(
module_ctx,
func,
@ir.hot_region_body_ref(func, barrier_id),
moved_id,
) ||
hso_region_global_order_conflicts(
module_ctx,
func,
@ir.hot_region_catch_list_ref(func, barrier_id),
moved_id,
)
_ => false
}
}
///|
fn hso_region_global_order_conflicts(
module_ctx : @ir.HotModuleContext?,
func : @ir.HotFunc,
region_ref : @ir.HotRegionRef,
moved_id : Int32,
) -> Bool {
for idx = 0; idx < @ir.hot_region_root_count(func, region_ref); idx = idx + 1 {
if hso_subtree_global_order_conflicts(
module_ctx,
func,
@ir.hot_region_get(func, region_ref, idx),
moved_id,
) {
return true
}
}
false
}
///|
fn hso_subtree_has_global_write(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::GlobalSet => return true
_ => ()
}
let operand_node = @ir.hot_node_get(func, node_id)
let operand_count = pass_value_operand_count(func, node_id)
for idx = 0; idx < operand_count; idx = idx + 1 {
if hso_subtree_has_global_write(
func,
@ir.hot_child_get(
func,
node_id,
pass_value_operand_slot(operand_node, idx),
),
) {
return true
}
}
match node.op {
@ir.HotOp::Block | @ir.HotOp::Loop =>
hso_region_has_global_write(func, @ir.hot_region_body_ref(func, node_id))
@ir.HotOp::If =>
hso_region_has_global_write(func, @ir.hot_region_then_ref(func, node_id)) ||
(match @ir.hot_region_else_ref(func, node_id) {
Some(region_ref) => hso_region_has_global_write(func, region_ref)
None => false
})
@ir.HotOp::Try =>
hso_region_has_global_write(func, @ir.hot_region_body_ref(func, node_id)) ||
(match @ir.hot_region_catch_ref(func, node_id) {
Some(region_ref) => hso_region_has_global_write(func, region_ref)
None => false
})
@ir.HotOp::TryTable =>
hso_region_has_global_write(func, @ir.hot_region_body_ref(func, node_id)) ||
hso_region_has_global_write(
func,
@ir.hot_region_catch_list_ref(func, node_id),
)
_ => false
}
}
///|
fn hso_region_has_global_write(
func : @ir.HotFunc,
region_ref : @ir.HotRegionRef,
) -> Bool {
for idx = 0; idx < @ir.hot_region_root_count(func, region_ref); idx = idx + 1 {
if hso_subtree_has_global_write(
func,
@ir.hot_region_get(func, region_ref, idx),
) {
return true
}
}
false
}
///|
fn hso_subtree_touches_local(
func : @ir.HotFunc,
node_id : Int32,
local_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::LocalGet | @ir.HotOp::LocalSet | @ir.HotOp::LocalTee =>
if node.imm0 == local_id {
return true
}
_ => ()
}
let operand_node = @ir.hot_node_get(func, node_id)
let operand_count = pass_value_operand_count(func, node_id)
for slot = 0; slot < operand_count; slot = slot + 1 {
if hso_subtree_touches_local(
func,
@ir.hot_child_get(
func,
node_id,
pass_value_operand_slot(operand_node, slot),
),
local_id,
) {
return true
}
}
match node.op {
@ir.HotOp::Block | @ir.HotOp::Loop =>
hso_region_touches_local(
func,
@ir.hot_region_body_ref(func, node_id),
local_id,
)
@ir.HotOp::If => {
let then_touches = hso_region_touches_local(
func,
@ir.hot_region_then_ref(func, node_id),
local_id,
)
if then_touches {
true
} else {
match @ir.hot_region_else_ref(func, node_id) {
Some(region_ref) =>
hso_region_touches_local(func, region_ref, local_id)
None => false
}
}
}
@ir.HotOp::Try => {
let body_touches = hso_region_touches_local(
func,
@ir.hot_region_body_ref(func, node_id),
local_id,
)
if body_touches {
true
} else {
match @ir.hot_region_catch_ref(func, node_id) {
Some(region_ref) =>
hso_region_touches_local(func, region_ref, local_id)
None => false
}
}
}
@ir.HotOp::TryTable =>
hso_region_touches_local(
func,
@ir.hot_region_body_ref(func, node_id),
local_id,
) ||
hso_region_touches_local(
func,
@ir.hot_region_catch_list_ref(func, node_id),
local_id,
)
_ => false
}
}
///|
fn hso_region_may_skip_local_set(
func : @ir.HotFunc,
region_ref : @ir.HotRegionRef,
active_owners : Array[Int32],
) -> Bool {
for idx = 0; idx < @ir.hot_region_root_count(func, region_ref); idx = idx + 1 {
if hso_subtree_may_skip_local_set_from_root(
func,
@ir.hot_region_get(func, region_ref, idx),
active_owners,
) {
return true
}
}
false
}
///|
fn hso_active_owners_contains(
active_owners : Array[Int32],
owner_id : Int32,
) -> Bool {
for active_owner_id in active_owners {
if active_owner_id == owner_id {
return true
}
}
false
}
///|
fn hso_branch_escapes_active_owners(
func : @ir.HotFunc,
active_owners : Array[Int32],
node_id : Int32,
) -> Bool {
let targets : Array[Int32] = []
match @ir.hot_branch_target(func, node_id) {
Some(label_id) => targets.push(label_id)
None => ()
}
for label_id in @ir.hot_branch_table_targets_for_node(func, node_id) {
targets.push(label_id)
}
if targets.is_empty() {
return true
}
for label_id in targets {
let owner_id = @ir.hot_label_owner(func, label_id)
if !hso_active_owners_contains(active_owners, owner_id) {
return true
}
}
false
}
///|
fn hso_subtree_may_skip_local_set(
cache : HsoPredicateCache,
func : @ir.HotFunc,
node_id : Int32,
) -> Bool {
if node_id < 0 || !@ir.hot_node_is_live(func, node_id) {
return false
}
hso_ensure_predicate_cache_capacity(cache, node_id)
let revision = @ir.hot_revision_current(func)
if cache.skip_revision[node_id] == revision {
return cache.skip_result[node_id]
}
let active_owners : Array[Int32] = []
let result = hso_subtree_may_skip_local_set_from_root(
func, node_id, active_owners,
)
cache.skip_revision[node_id] = revision
cache.skip_result[node_id] = result
result
}
///|
fn hso_subtree_may_escape_to_active_catch(
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::Call
| @ir.HotOp::CallIndirect
| @ir.HotOp::CallRef
| @ir.HotOp::Throw
| @ir.HotOp::Rethrow
| @ir.HotOp::ThrowRef
| @ir.HotOp::Delegate => return true
_ => ()
}
let operand_node = @ir.hot_node_get(func, node_id)
let operand_count = pass_value_operand_count(func, node_id)
for slot = 0; slot < operand_count; slot = slot + 1 {
if hso_subtree_may_escape_to_active_catch(
func,
@ir.hot_child_get(
func,
node_id,
pass_value_operand_slot(operand_node, slot),
),
) {
return true
}
}
match node.op {
@ir.HotOp::Block | @ir.HotOp::Loop =>
hso_region_may_escape_to_active_catch(
func,
@ir.hot_region_body_ref(func, node_id),
)
@ir.HotOp::If => {
let then_escapes = hso_region_may_escape_to_active_catch(
func,
@ir.hot_region_then_ref(func, node_id),
)
if then_escapes {
true
} else {
match @ir.hot_region_else_ref(func, node_id) {
Some(region_ref) =>
hso_region_may_escape_to_active_catch(func, region_ref)
None => false
}
}
}
@ir.HotOp::Try => {
let body_escapes = hso_region_may_escape_to_active_catch(
func,
@ir.hot_region_body_ref(func, node_id),
)
if body_escapes {
true
} else {
match @ir.hot_region_catch_ref(func, node_id) {
Some(region_ref) =>
hso_region_may_escape_to_active_catch(func, region_ref)
None => false
}
}
}
@ir.HotOp::TryTable =>
hso_region_may_escape_to_active_catch(
func,
@ir.hot_region_body_ref(func, node_id),
) ||
hso_region_may_escape_to_active_catch(
func,
@ir.hot_region_catch_list_ref(func, node_id),
)
_ => false
}
}
///|
fn hso_subtree_contains_try_table(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::TryTable => return true
_ => ()
}
let operand_node = @ir.hot_node_get(func, node_id)
let operand_count = pass_value_operand_count(func, node_id)
for slot = 0; slot < operand_count; slot = slot + 1 {
if hso_subtree_contains_try_table(
func,
@ir.hot_child_get(
func,
node_id,
pass_value_operand_slot(operand_node, slot),
),
) {
return true
}
}
match node.op {
@ir.HotOp::Block | @ir.HotOp::Loop =>
hso_region_contains_try_table(
func,
@ir.hot_region_body_ref(func, node_id),
)
@ir.HotOp::If => {
let then_has_try_table = hso_region_contains_try_table(
func,
@ir.hot_region_then_ref(func, node_id),
)
if then_has_try_table {
true
} else {
match @ir.hot_region_else_ref(func, node_id) {
Some(region_ref) => hso_region_contains_try_table(func, region_ref)
None => false
}
}
}
@ir.HotOp::Try => {
let body_has_try_table = hso_region_contains_try_table(
func,
@ir.hot_region_body_ref(func, node_id),
)
if body_has_try_table {
true
} else {
match @ir.hot_region_catch_ref(func, node_id) {
Some(region_ref) => hso_region_contains_try_table(func, region_ref)
None => false
}
}
}
_ => false
}
}
///|
fn hso_region_contains_try_table(
func : @ir.HotFunc,
region_ref : @ir.HotRegionRef,
) -> Bool {
for idx = 0; idx < @ir.hot_region_root_count(func, region_ref); idx = idx + 1 {
if hso_subtree_contains_try_table(
func,
@ir.hot_region_get(func, region_ref, idx),
) {
return true
}
}
false
}
///|
fn hso_subtree_has_catchable_try_table_escape(
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::TryTable =>
if hso_region_may_escape_to_active_catch(
func,
@ir.hot_region_body_ref(func, node_id),
) {
return true
}
_ => ()
}
let operand_node = @ir.hot_node_get(func, node_id)
let operand_count = pass_value_operand_count(func, node_id)
for slot = 0; slot < operand_count; slot = slot + 1 {
if hso_subtree_has_catchable_try_table_escape(
func,
@ir.hot_child_get(
func,
node_id,
pass_value_operand_slot(operand_node, slot),
),
) {
return true
}
}
match node.op {
@ir.HotOp::Block | @ir.HotOp::Loop =>
hso_region_has_catchable_try_table_escape(
func,
@ir.hot_region_body_ref(func, node_id),
)
@ir.HotOp::If => {
let then_has_escape = hso_region_has_catchable_try_table_escape(
func,
@ir.hot_region_then_ref(func, node_id),
)
if then_has_escape {
true
} else {
match @ir.hot_region_else_ref(func, node_id) {
Some(region_ref) =>
hso_region_has_catchable_try_table_escape(func, region_ref)
None => false
}
}
}
@ir.HotOp::Try => {
let body_has_escape = hso_region_has_catchable_try_table_escape(
func,
@ir.hot_region_body_ref(func, node_id),
)
if body_has_escape {
true
} else {
match @ir.hot_region_catch_ref(func, node_id) {
Some(region_ref) =>
hso_region_has_catchable_try_table_escape(func, region_ref)
None => false
}
}
}
@ir.HotOp::TryTable =>
hso_region_has_catchable_try_table_escape(
func,
@ir.hot_region_catch_list_ref(func, node_id),
)
_ => false
}
}
///|
fn hso_region_has_catchable_try_table_escape(
func : @ir.HotFunc,
region_ref : @ir.HotRegionRef,
) -> Bool {
for idx = 0; idx < @ir.hot_region_root_count(func, region_ref); idx = idx + 1 {
if hso_subtree_has_catchable_try_table_escape(
func,
@ir.hot_region_get(func, region_ref, idx),
) {
return true
}
}
false
}
///|
fn hso_region_may_escape_to_active_catch(
func : @ir.HotFunc,
region_ref : @ir.HotRegionRef,
) -> Bool {
for idx = 0; idx < @ir.hot_region_root_count(func, region_ref); idx = idx + 1 {
if hso_subtree_may_escape_to_active_catch(
func,
@ir.hot_region_get(func, region_ref, idx),
) {
return true
}
}
false
}
///|
fn hso_subtree_skip_is_branch_only(func : @ir.HotFunc, node_id : Int32) -> Bool {
if node_id < 0 || !@ir.hot_node_is_live(func, node_id) {
return true
}
let node = @ir.hot_node_get(func, node_id)
if @ir.hot_is_branch(func, node_id) {
if @ir.hot_branch_target(func, node_id).is_none() &&
@ir.hot_branch_table_targets_for_node(func, node_id).is_empty() {
return false
}
}
match node.op {
@ir.HotOp::ReturnCall
| @ir.HotOp::ReturnCallIndirect
| @ir.HotOp::ReturnCallRef
| @ir.HotOp::Throw
| @ir.HotOp::Rethrow
| @ir.HotOp::ThrowRef
| @ir.HotOp::Delegate
| @ir.HotOp::Unreachable => return false
_ => ()
}
let operand_node = @ir.hot_node_get(func, node_id)
let operand_count = pass_value_operand_count(func, node_id)
for slot = 0; slot < operand_count; slot = slot + 1 {
if !hso_subtree_skip_is_branch_only(
func,
@ir.hot_child_get(
func,
node_id,
pass_value_operand_slot(operand_node, slot),
),
) {
return false
}
}
match node.op {
@ir.HotOp::Block | @ir.HotOp::Loop =>
hso_region_skip_is_branch_only(
func,
@ir.hot_region_body_ref(func, node_id),
)
@ir.HotOp::If => {
let then_ok = hso_region_skip_is_branch_only(
func,
@ir.hot_region_then_ref(func, node_id),
)
if !then_ok {
false
} else {
match @ir.hot_region_else_ref(func, node_id) {
Some(region_ref) => hso_region_skip_is_branch_only(func, region_ref)
None => true
}
}
}
@ir.HotOp::Try => {
let body_ok = hso_region_skip_is_branch_only(
func,
@ir.hot_region_body_ref(func, node_id),
)
if !body_ok {
false
} else {
match @ir.hot_region_catch_ref(func, node_id) {
Some(region_ref) => hso_region_skip_is_branch_only(func, region_ref)
None => true
}
}
}
@ir.HotOp::TryTable =>
hso_region_skip_is_branch_only(
func,
@ir.hot_region_body_ref(func, node_id),
) &&
hso_region_skip_is_branch_only(
func,
@ir.hot_region_catch_list_ref(func, node_id),
)
_ => true
}
}
///|
fn hso_region_skip_is_branch_only(
func : @ir.HotFunc,
region_ref : @ir.HotRegionRef,
) -> Bool {
for idx = 0; idx < @ir.hot_region_root_count(func, region_ref); idx = idx + 1 {
if !hso_subtree_skip_is_branch_only(
func,
@ir.hot_region_get(func, region_ref, idx),
) {
return false
}
}
true
}
///|
fn hso_subtree_may_skip_local_set_from_root(
func : @ir.HotFunc,
node_id : Int32,
active_owners : 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)
if @ir.hot_is_branch(func, node_id) &&
hso_branch_escapes_active_owners(func, active_owners, node_id) {
return true
}
match node.op {
@ir.HotOp::Return
| @ir.HotOp::ReturnCall
| @ir.HotOp::ReturnCallIndirect
| @ir.HotOp::ReturnCallRef
| @ir.HotOp::Throw
| @ir.HotOp::Rethrow
| @ir.HotOp::ThrowRef
| @ir.HotOp::Delegate => return false
@ir.HotOp::Unreachable => return true
_ => ()
}
let pushes_owner = match node.op {
@ir.HotOp::Block
| @ir.HotOp::Loop
| @ir.HotOp::If
| @ir.HotOp::Try
| @ir.HotOp::TryTable => true
_ => false
}
if pushes_owner {
active_owners.push(node_id)
}
let operand_node = @ir.hot_node_get(func, node_id)
let operand_count = pass_value_operand_count(func, node_id)
for slot = 0; slot < operand_count; slot = slot + 1 {
if hso_subtree_may_skip_local_set_from_root(
func,
@ir.hot_child_get(
func,
node_id,
pass_value_operand_slot(operand_node, slot),
),
active_owners,
) {
if pushes_owner {
ignore(active_owners.pop())
}
return true
}
}
let result = match node.op {
@ir.HotOp::Block | @ir.HotOp::Loop =>
hso_region_may_skip_local_set(
func,
@ir.hot_region_body_ref(func, node_id),
active_owners,
)
@ir.HotOp::If => {
let then_skips = hso_region_may_skip_local_set(
func,
@ir.hot_region_then_ref(func, node_id),
active_owners,
)
if then_skips {
true
} else {
match @ir.hot_region_else_ref(func, node_id) {
Some(region_ref) =>
hso_region_may_skip_local_set(func, region_ref, active_owners)
None => false
}
}
}
@ir.HotOp::Try => {
let body_skips = hso_region_may_skip_local_set(
func,
@ir.hot_region_body_ref(func, node_id),
active_owners,
)
if body_skips {
true
} else {
match @ir.hot_region_catch_ref(func, node_id) {
Some(region_ref) =>
hso_region_may_skip_local_set(func, region_ref, active_owners)
None => false
}
}
}
@ir.HotOp::TryTable =>
hso_region_may_skip_local_set(
func,
@ir.hot_region_body_ref(func, node_id),
active_owners,
) ||
hso_region_may_skip_local_set(
func,
@ir.hot_region_catch_list_ref(func, node_id),
active_owners,
)
_ => false
}
if pushes_owner {
ignore(active_owners.pop())
}
result
}
///|
fn hso_region_is_trapless_readonly(
cache : HsoPredicateCache,
func : @ir.HotFunc,
node_masks : Array[UInt32],
region_ref : @ir.HotRegionRef,
) -> Bool {
for idx = 0; idx < @ir.hot_region_root_count(func, region_ref); idx = idx + 1 {
if !hso_subtree_is_trapless_readonly(
cache,
func,
node_masks,
@ir.hot_region_get(func, region_ref, idx),
) {
return false
}
}
true
}
///|
fn hso_compute_subtree_is_trapless_readonly(
cache : HsoPredicateCache,
func : @ir.HotFunc,
node_masks : Array[UInt32],
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)
let mask = hso_effects_without_descendant_trap(
func,
node_id,
hso_reorder_effect_mask(cache, func, node_masks, node_id),
)
let allowed_effects = @ir.EFFECT_MASK_LOCAL_STATE |
@ir.EFFECT_MASK_GLOBAL_STATE |
@ir.EFFECT_MASK_MEMORY_READ |
@ir.EFFECT_MASK_TABLE_READ
let disallowed_effects = mask ^ (mask & allowed_effects)
let allows_side_effect = match node.op {
@ir.HotOp::Drop => true
_ => false
}
if disallowed_effects != 0U ||
(!allows_side_effect && @ir.hot_has_side_effect(func, node_id)) ||
hso_node_may_trap(func, node_id) ||
@ir.hot_is_branch(func, node_id) ||
@ir.hot_is_terminator_node(func, node_id) ||
@ir.hot_node_may_throw_exception(func, node_id) {
return false
}
let operand_node = @ir.hot_node_get(func, node_id)
let operand_count = pass_value_operand_count(func, node_id)
for slot = 0; slot < operand_count; slot = slot + 1 {
if !hso_subtree_is_trapless_readonly(
cache,
func,
node_masks,
@ir.hot_child_get(
func,
node_id,
pass_value_operand_slot(operand_node, slot),
),
) {
return false
}
}
match node.op {
@ir.HotOp::Block | @ir.HotOp::Loop =>
hso_region_is_trapless_readonly(
cache,
func,
node_masks,
@ir.hot_region_body_ref(func, node_id),
)
@ir.HotOp::If => {
let then_is_readonly = hso_region_is_trapless_readonly(
cache,
func,
node_masks,
@ir.hot_region_then_ref(func, node_id),
)
if !then_is_readonly {
false
} else {
match @ir.hot_region_else_ref(func, node_id) {
Some(region_ref) =>
hso_region_is_trapless_readonly(cache, func, node_masks, region_ref)
None => true
}
}
}
@ir.HotOp::Try => {
let body_is_readonly = hso_region_is_trapless_readonly(
cache,
func,
node_masks,
@ir.hot_region_body_ref(func, node_id),
)
if !body_is_readonly {
false
} else {
match @ir.hot_region_catch_ref(func, node_id) {
Some(region_ref) =>
hso_region_is_trapless_readonly(cache, func, node_masks, region_ref)
None => true
}
}
}
@ir.HotOp::TryTable =>
hso_region_is_trapless_readonly(
cache,
func,
node_masks,
@ir.hot_region_body_ref(func, node_id),
) &&
hso_region_is_trapless_readonly(
cache,
func,
node_masks,
@ir.hot_region_catch_list_ref(func, node_id),
)
_ => true
}
}
///|
fn hso_subtree_is_trapless_readonly(
cache : HsoPredicateCache,
func : @ir.HotFunc,
node_masks : Array[UInt32],
node_id : Int32,
) -> Bool {
if node_id < 0 || !@ir.hot_node_is_live(func, node_id) {
return false
}
hso_ensure_predicate_cache_capacity(cache, node_id)
let revision = @ir.hot_revision_current(func)
if cache.readonly_revision[node_id] == revision {
return cache.readonly_result[node_id]
}
let result = hso_compute_subtree_is_trapless_readonly(
cache, func, node_masks, node_id,
)
cache.readonly_revision[node_id] = revision
cache.readonly_result[node_id] = result
result
}
///|
fn hso_region_is_trapless_reorderable(
cache : HsoPredicateCache,
func : @ir.HotFunc,
node_masks : Array[UInt32],
region_ref : @ir.HotRegionRef,
active_owners : Array[Int32],
) -> Bool {
for idx = 0; idx < @ir.hot_region_root_count(func, region_ref); idx = idx + 1 {
if !hso_subtree_is_trapless_reorderable(
cache,
func,
node_masks,
@ir.hot_region_get(func, region_ref, idx),
active_owners,
) {
return false
}
}
true
}
///|
fn hso_subtree_is_trapless_reorderable(
cache : HsoPredicateCache,
func : @ir.HotFunc,
node_masks : Array[UInt32],
node_id : Int32,
active_owners : 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)
if @ir.hot_is_branch(func, node_id) &&
hso_branch_escapes_active_owners(func, active_owners, node_id) {
return false
}
let mask = hso_effects_without_descendant_trap(
func,
node_id,
hso_effects_for_node(func, node_masks, node_id),
)
let allowed_effects = @ir.EFFECT_MASK_CONTROL |
@ir.EFFECT_MASK_LOCAL_STATE |
@ir.EFFECT_MASK_GLOBAL_STATE |
@ir.EFFECT_MASK_MEMORY_READ |
@ir.EFFECT_MASK_TABLE_READ
let disallowed_effects = mask ^ (mask & allowed_effects)
let allows_side_effect = match node.op {
@ir.HotOp::Drop
| @ir.HotOp::Block
| @ir.HotOp::Loop
| @ir.HotOp::If
| @ir.HotOp::Try
| @ir.HotOp::TryTable => true
_ => @ir.hot_is_branch(func, node_id)
}
if disallowed_effects != 0U ||
(!allows_side_effect && @ir.hot_has_side_effect(func, node_id)) ||
hso_node_may_trap(func, node_id) ||
@ir.hot_node_may_throw_exception(func, node_id) ||
(
!@ir.hot_is_branch(func, node_id) &&
@ir.hot_is_terminator_node(func, node_id)
) {
return false
}
let pushes_owner = match node.op {
@ir.HotOp::Block
| @ir.HotOp::Loop
| @ir.HotOp::If
| @ir.HotOp::Try
| @ir.HotOp::TryTable => true
_ => false
}
if pushes_owner {
active_owners.push(node_id)
}
let operand_node = @ir.hot_node_get(func, node_id)
let operand_count = pass_value_operand_count(func, node_id)
for slot = 0; slot < operand_count; slot = slot + 1 {
if !hso_subtree_is_trapless_reorderable(
cache,
func,
node_masks,
@ir.hot_child_get(
func,
node_id,
pass_value_operand_slot(operand_node, slot),
),
active_owners,
) {
if pushes_owner {
ignore(active_owners.pop())
}
return false
}
}
let regions_ok = match node.op {
@ir.HotOp::Block | @ir.HotOp::Loop =>
hso_region_is_trapless_reorderable(
cache,
func,
node_masks,
@ir.hot_region_body_ref(func, node_id),
active_owners,
)
@ir.HotOp::If => {
let then_is_reorderable = hso_region_is_trapless_reorderable(
cache,
func,
node_masks,
@ir.hot_region_then_ref(func, node_id),
active_owners,
)
if !then_is_reorderable {
false
} else {
match @ir.hot_region_else_ref(func, node_id) {
Some(region_ref) =>
hso_region_is_trapless_reorderable(
cache, func, node_masks, region_ref, active_owners,
)
None => true
}
}
}
@ir.HotOp::Try => {
let body_is_reorderable = hso_region_is_trapless_reorderable(
cache,
func,
node_masks,
@ir.hot_region_body_ref(func, node_id),
active_owners,
)
if !body_is_reorderable {
false
} else {
match @ir.hot_region_catch_ref(func, node_id) {
Some(region_ref) =>
hso_region_is_trapless_reorderable(
cache, func, node_masks, region_ref, active_owners,
)
None => true
}
}
}
@ir.HotOp::TryTable =>
hso_region_is_trapless_reorderable(
cache,
func,
node_masks,
@ir.hot_region_body_ref(func, node_id),
active_owners,
) &&
hso_region_is_trapless_reorderable(
cache,
func,
node_masks,
@ir.hot_region_catch_list_ref(func, node_id),
active_owners,
)
_ => true
}
if pushes_owner {
ignore(active_owners.pop())
}
regions_ok
}
///|
fn hso_region_is_trapless_readonly_contained_control(
cache : HsoPredicateCache,
func : @ir.HotFunc,
node_masks : Array[UInt32],
region_ref : @ir.HotRegionRef,
active_owners : Array[Int32],
) -> Bool {
for idx = 0; idx < @ir.hot_region_root_count(func, region_ref); idx = idx + 1 {
if !hso_subtree_is_trapless_readonly_contained_control(
cache,
func,
node_masks,
@ir.hot_region_get(func, region_ref, idx),
active_owners,
) {
return false
}
}
true
}
///|
fn hso_subtree_is_trapless_readonly_contained_control(
cache : HsoPredicateCache,
func : @ir.HotFunc,
node_masks : Array[UInt32],
node_id : Int32,
active_owners : 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)
if node.op is @ir.HotOp::Loop &&
hso_region_targets_owner_label(
func,
@ir.hot_region_body_ref(func, node_id),
node_id,
) {
return false
}
if @ir.hot_is_branch(func, node_id) &&
hso_branch_escapes_active_owners(func, active_owners, node_id) {
return false
}
let mask = hso_effects_without_descendant_trap(
func,
node_id,
hso_reorder_effect_mask(cache, func, node_masks, node_id),
)
let allowed_effects = @ir.EFFECT_MASK_CONTROL |
@ir.EFFECT_MASK_LOCAL_STATE |
@ir.EFFECT_MASK_GLOBAL_STATE |
@ir.EFFECT_MASK_MEMORY_READ |
@ir.EFFECT_MASK_TABLE_READ
let disallowed_effects = mask ^ (mask & allowed_effects)
let allows_side_effect = match node.op {
@ir.HotOp::Drop
| @ir.HotOp::Block
| @ir.HotOp::Loop
| @ir.HotOp::If
| @ir.HotOp::Try
| @ir.HotOp::TryTable => true
_ => @ir.hot_is_branch(func, node_id)
}
if disallowed_effects != 0U ||
(!allows_side_effect && @ir.hot_has_side_effect(func, node_id)) ||
hso_node_may_trap(func, node_id) ||
@ir.hot_node_may_throw_exception(func, node_id) ||
(
!@ir.hot_is_branch(func, node_id) &&
@ir.hot_is_terminator_node(func, node_id)
) {
return false
}
let pushes_owner = match node.op {
@ir.HotOp::Block
| @ir.HotOp::Loop
| @ir.HotOp::If
| @ir.HotOp::Try
| @ir.HotOp::TryTable => true
_ => false
}
if pushes_owner {
active_owners.push(node_id)
}
let operand_node = @ir.hot_node_get(func, node_id)
let operand_count = pass_value_operand_count(func, node_id)
for slot = 0; slot < operand_count; slot = slot + 1 {
if !hso_subtree_is_trapless_readonly_contained_control(
cache,
func,
node_masks,
@ir.hot_child_get(
func,
node_id,
pass_value_operand_slot(operand_node, slot),
),
active_owners,
) {
if pushes_owner {
ignore(active_owners.pop())
}
return false
}
}
let regions_ok = match node.op {
@ir.HotOp::Block | @ir.HotOp::Loop =>
hso_region_is_trapless_readonly_contained_control(
cache,
func,
node_masks,
@ir.hot_region_body_ref(func, node_id),
active_owners,
)
@ir.HotOp::If => {
let then_is_readonly = hso_region_is_trapless_readonly_contained_control(
cache,
func,
node_masks,
@ir.hot_region_then_ref(func, node_id),
active_owners,
)
if !then_is_readonly {
false
} else {
match @ir.hot_region_else_ref(func, node_id) {
Some(region_ref) =>
hso_region_is_trapless_readonly_contained_control(
cache, func, node_masks, region_ref, active_owners,
)
None => true
}
}
}
@ir.HotOp::Try => {
let body_is_readonly = hso_region_is_trapless_readonly_contained_control(
cache,
func,
node_masks,
@ir.hot_region_body_ref(func, node_id),
active_owners,
)
if !body_is_readonly {
false
} else {
match @ir.hot_region_catch_ref(func, node_id) {
Some(region_ref) =>
hso_region_is_trapless_readonly_contained_control(
cache, func, node_masks, region_ref, active_owners,
)
None => true
}
}
}
@ir.HotOp::TryTable =>
hso_region_is_trapless_readonly_contained_control(
cache,
func,
node_masks,
@ir.hot_region_body_ref(func, node_id),
active_owners,
) &&
hso_region_is_trapless_readonly_contained_control(
cache,
func,
node_masks,
@ir.hot_region_catch_list_ref(func, node_id),
active_owners,
)
_ => true
}
if pushes_owner {
ignore(active_owners.pop())
}
regions_ok
}
///|
fn hso_nodes_can_reorder(
cache : HsoPredicateCache,
func : @ir.HotFunc,
module_ctx : @ir.HotModuleContext?,
node_masks : Array[UInt32],
barrier_id : Int32,
barrier_mask : UInt32,
moved_id : Int32,
moved_mask : UInt32,
) -> Bool {
// Descriptor operand masks deliberately omit local reads. Keep their real
// read/write dependencies before applying the coarser effect relaxations.
if hso_local_order_conflicts(func, barrier_id, moved_id) {
return false
}
if ((barrier_mask | moved_mask) & @ir.EFFECT_MASK_GLOBAL_STATE) != 0U &&
hso_subtree_global_order_conflicts(module_ctx, func, barrier_id, moved_id) {
return false
}
let barrier_reorder_mask = if (barrier_mask & @ir.EFFECT_MASK_CONTROL) == 0U {
barrier_mask
} else if hso_subtree_may_skip_local_set(cache, func, barrier_id) {
barrier_mask
} else {
barrier_mask ^ (barrier_mask & @ir.EFFECT_MASK_CONTROL)
}
let moved_reorder_mask = if (moved_mask & @ir.EFFECT_MASK_CONTROL) == 0U {
moved_mask
} else if hso_subtree_may_skip_local_set(cache, func, moved_id) {
moved_mask
} else {
moved_mask ^ (moved_mask & @ir.EFFECT_MASK_CONTROL)
}
let barrier_without_global = barrier_reorder_mask ^
(barrier_reorder_mask & @ir.EFFECT_MASK_GLOBAL_STATE)
let moved_without_global = moved_reorder_mask ^
(moved_reorder_mask & @ir.EFFECT_MASK_GLOBAL_STATE)
if barrier_reorder_mask != 0U &&
moved_reorder_mask != 0U &&
barrier_without_global == 0U &&
moved_without_global == 0U {
return true
}
let barrier_order = hso_order_effects_for_node(
cache, func, module_ctx, barrier_id,
)
let moved_order = hso_order_effects_for_node(
cache, func, module_ctx, moved_id,
)
let release_gc_allowed_mask = @ir.EFFECT_MASK_LOCAL_STATE |
@ir.EFFECT_MASK_MEMORY_READ |
@ir.EFFECT_MASK_MEMORY_WRITE |
@ir.EFFECT_MASK_ATOMIC_READ |
@ir.EFFECT_MASK_ATOMIC_WRITE |
@ir.EFFECT_MASK_RELEASE |
@ir.EFFECT_MASK_TRAP
if (
(barrier_reorder_mask | moved_reorder_mask) ^
((barrier_reorder_mask | moved_reorder_mask) & release_gc_allowed_mask)
) ==
0U &&
hso_release_memory_before_shared_gc_read_can_reorder(
barrier_order, moved_order,
) {
return true
}
if hso_mask_ordered_before(barrier_reorder_mask, moved_reorder_mask) ||
hso_order_effects_ordered_before(barrier_order, moved_order) {
()
} else {
return true
}
let local_control_barrier_mask = @ir.EFFECT_MASK_LOCAL_STATE |
@ir.EFFECT_MASK_CONTROL
let local_only_barrier = (
barrier_reorder_mask ^ (barrier_reorder_mask & local_control_barrier_mask)
) ==
0U
if local_only_barrier &&
(moved_reorder_mask & @ir.EFFECT_MASK_LOCAL_STATE) == 0U {
return true
}
hso_subtree_is_trapless_readonly(cache, func, node_masks, barrier_id) &&
hso_subtree_is_trapless_readonly(cache, func, node_masks, moved_id)
}
///|
fn hso_desc_region_has_loop_control_barrier(
func : @ir.HotFunc,
region_ref : @ir.HotRegionRef,
active_loop_id : Int32,
) -> Bool {
for idx = 0; idx < @ir.hot_region_root_count(func, region_ref); idx = idx + 1 {
if hso_desc_node_has_loop_control_barrier(
func,
@ir.hot_region_get(func, region_ref, idx),
active_loop_id,
) {
return true
}
}
false
}
///|
fn hso_desc_node_has_loop_control_barrier(
func : @ir.HotFunc,
node_id : Int32,
active_loop_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::Br
| @ir.HotOp::BrIf
| @ir.HotOp::BrOnNull
| @ir.HotOp::BrOnNonNull
| @ir.HotOp::BrOnCast
| @ir.HotOp::BrOnCastFail =>
return active_loop_id >= 0 && node.imm0 == active_loop_id
@ir.HotOp::BrTable => return active_loop_id >= 0
_ => ()
}
if @ir.hot_is_terminator_node(func, node_id) {
return true
}
let operand_node = @ir.hot_node_get(func, node_id)
let operand_count = pass_value_operand_count(func, node_id)
for idx = 0; idx < operand_count; idx = idx + 1 {
if hso_desc_node_has_loop_control_barrier(
func,
@ir.hot_child_get(
func,
node_id,
pass_value_operand_slot(operand_node, idx),
),
active_loop_id,
) {
return true
}
}
match node.op {
@ir.HotOp::Block =>
hso_desc_region_has_loop_control_barrier(
func,
@ir.hot_region_body_ref(func, node_id),
active_loop_id,
)
@ir.HotOp::Loop =>
hso_desc_region_has_loop_control_barrier(
func,
@ir.hot_region_body_ref(func, node_id),
node.imm0,
)
@ir.HotOp::If =>
if hso_desc_region_has_loop_control_barrier(
func,
@ir.hot_region_then_ref(func, node_id),
active_loop_id,
) {
true
} else {
match @ir.hot_region_else_ref(func, node_id) {
Some(region_ref) =>
hso_desc_region_has_loop_control_barrier(
func, region_ref, active_loop_id,
)
None => false
}
}
@ir.HotOp::Try =>
if hso_desc_region_has_loop_control_barrier(
func,
@ir.hot_region_body_ref(func, node_id),
active_loop_id,
) {
true
} else {
match @ir.hot_region_catch_ref(func, node_id) {
Some(region_ref) =>
hso_desc_region_has_loop_control_barrier(
func, region_ref, active_loop_id,
)
None => false
}
}
@ir.HotOp::TryTable =>
hso_desc_region_has_loop_control_barrier(
func,
@ir.hot_region_body_ref(func, node_id),
active_loop_id,
) ||
hso_desc_region_has_loop_control_barrier(
func,
@ir.hot_region_catch_list_ref(func, node_id),
active_loop_id,
)
_ => false
}
}
///|
fn hso_desc_operand_effects_for_region(
func : @ir.HotFunc,
module_ctx : @ir.HotModuleContext?,
node_masks : Array[UInt32],
region_ref : @ir.HotRegionRef,
) -> UInt32 {
let mut mask = 0U
for idx = 0; idx < @ir.hot_region_root_count(func, region_ref); idx = idx + 1 {
mask = mask |
hso_desc_operand_effects_for_node(
func,
module_ctx,
node_masks,
@ir.hot_region_get(func, region_ref, idx),
)
}
mask
}
///|
fn hso_desc_operand_effects_for_node(
func : @ir.HotFunc,
module_ctx : @ir.HotModuleContext?,
node_masks : Array[UInt32],
node_id : Int32,
) -> UInt32 {
if node_id < 0 || !@ir.hot_node_is_live(func, node_id) {
return 0U
}
let node = @ir.hot_node_get(func, node_id)
match @ir.hot_node_exact_instr(func, node_id) {
Some(@lib.Instruction::StructNew(_))
| Some(@lib.Instruction::StructNewDefault(_))
| Some(@lib.Instruction::StructNewDesc(_))
| Some(@lib.Instruction::StructNewDefaultDesc(_)) => {
let mut mask = hso_struct_new_shallow_effects(func, node_id)
let operand_node = @ir.hot_node_get(func, node_id)
let operand_count = pass_value_operand_count(func, node_id)
for idx = 0; idx < operand_count; idx = idx + 1 {
mask = mask |
hso_desc_operand_effects_for_node(
func,
module_ctx,
node_masks,
@ir.hot_child_get(
func,
node_id,
pass_value_operand_slot(operand_node, idx),
),
)
}
return mask
}
_ => ()
}
if @ir.hot_is_branch(func, node_id) {
let mut mask = 0U
let operand_node = @ir.hot_node_get(func, node_id)
let operand_count = pass_value_operand_count(func, node_id)
for idx = 0; idx < operand_count; idx = idx + 1 {
mask = mask |
hso_desc_operand_effects_for_node(
func,
module_ctx,
node_masks,
@ir.hot_child_get(
func,
node_id,
pass_value_operand_slot(operand_node, idx),
),
)
}
return mask
}
match node.op {
@ir.HotOp::LocalGet => return 0U
@ir.HotOp::Drop => {
let mut mask = 0U
let operand_node = @ir.hot_node_get(func, node_id)
let operand_count = pass_value_operand_count(func, node_id)
for idx = 0; idx < operand_count; idx = idx + 1 {
mask = mask |
hso_desc_operand_effects_for_node(
func,
module_ctx,
node_masks,
@ir.hot_child_get(
func,
node_id,
pass_value_operand_slot(operand_node, idx),
),
)
}
return mask
}
@ir.HotOp::GlobalGet =>
match module_ctx {
Some(ctx) =>
match
@ir.hot_module_resolve_global_type(
ctx,
@lib.GlobalIdx::new(node.imm0.reinterpret_as_uint()),
) {
Some(@lib.GlobalType(_, false)) => return 0U
_ => ()
}
None => ()
}
@ir.HotOp::Block => {
let region_ref = @ir.hot_region_body_ref(func, node_id)
let mut mask = hso_desc_operand_effects_for_region(
func, module_ctx, node_masks, region_ref,
)
mask = mask ^ (mask & @ir.EFFECT_MASK_CONTROL)
return mask
}
@ir.HotOp::Loop => {
let region_ref = @ir.hot_region_body_ref(func, node_id)
if hso_desc_region_has_loop_control_barrier(func, region_ref, node.imm0) {
return hso_effects_for_node(func, node_masks, node_id)
}
let mut mask = hso_desc_operand_effects_for_region(
func, module_ctx, node_masks, region_ref,
)
mask = mask ^ (mask & @ir.EFFECT_MASK_CONTROL)
return mask
}
@ir.HotOp::If => {
let mut mask = 0U
let operand_node = @ir.hot_node_get(func, node_id)
let operand_count = pass_value_operand_count(func, node_id)
for idx = 0; idx < operand_count; idx = idx + 1 {
mask = mask |
hso_desc_operand_effects_for_node(
func,
module_ctx,
node_masks,
@ir.hot_child_get(
func,
node_id,
pass_value_operand_slot(operand_node, idx),
),
)
}
mask = mask |
hso_desc_operand_effects_for_region(
func,
module_ctx,
node_masks,
@ir.hot_region_then_ref(func, node_id),
)
match @ir.hot_region_else_ref(func, node_id) {
Some(region_ref) =>
mask = mask |
hso_desc_operand_effects_for_region(
func, module_ctx, node_masks, region_ref,
)
None => ()
}
return mask
}
@ir.HotOp::Select => {
let mut mask = 0U
let operand_node = @ir.hot_node_get(func, node_id)
let operand_count = pass_value_operand_count(func, node_id)
for idx = 0; idx < operand_count; idx = idx + 1 {
mask = mask |
hso_desc_operand_effects_for_node(
func,
module_ctx,
node_masks,
@ir.hot_child_get(
func,
node_id,
pass_value_operand_slot(operand_node, idx),
),
)
}
return mask
}
_ => ()
}
hso_effects_for_node(func, node_masks, node_id)
}
///|
fn hso_struct_new_operand_effect_mask(
func : @ir.HotFunc,
module_ctx : @ir.HotModuleContext?,
node_masks : Array[UInt32],
struct_new_id : Int32,
) -> UInt32 {
let mut mask = 0U
if struct_new_id < 0 || !@ir.hot_node_is_live(func, struct_new_id) {
return mask
}
for idx = 0; idx < @ir.hot_child_count(func, struct_new_id); idx = idx + 1 {
mask = mask |
hso_desc_operand_effects_for_node(
func,
module_ctx,
node_masks,
@ir.hot_child_get(func, struct_new_id, idx),
)
}
mask
}
///|
fn hso_struct_new_global_read_conflicts_with_write(
module_ctx : @ir.HotModuleContext?,
func : @ir.HotFunc,
struct_new_id : Int32,
blocker_id : Int32,
) -> Bool {
if struct_new_id < 0 || !@ir.hot_node_is_live(func, struct_new_id) {
return false
}
if blocker_id < 0 || !@ir.hot_node_is_live(func, blocker_id) {
return false
}
let blocker = @ir.hot_node_get(func, blocker_id)
match blocker.op {
@ir.HotOp::GlobalSet => {
for idx = 0; idx < @ir.hot_child_count(func, struct_new_id); idx = idx + 1 {
if hso_subtree_accesses_global(
module_ctx,
func,
@ir.hot_child_get(func, struct_new_id, idx),
blocker.imm0,
false,
) {
return true
}
}
false
}
_ => false
}
}
///|
fn hso_global_only_ordering_can_be_ignored(
module_ctx : @ir.HotModuleContext?,
func : @ir.HotFunc,
moved_struct_new_id : Int32,
moved_mask : UInt32,
blocker_id : Int32,
blocker_mask : UInt32,
) -> Bool {
let moved_without_global = moved_mask ^
(moved_mask & @ir.EFFECT_MASK_GLOBAL_STATE)
let blocker_without_global = blocker_mask ^
(blocker_mask & @ir.EFFECT_MASK_GLOBAL_STATE)
if moved_without_global != 0U || blocker_without_global != 0U {
return false
}
!hso_subtree_global_order_conflicts(
module_ctx, func, blocker_id, moved_struct_new_id,
) &&
!hso_struct_new_global_read_conflicts_with_write(
module_ctx, func, moved_struct_new_id, blocker_id,
)
}
///|
fn hso_subtree_has_tail_or_throw_escape(
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::Return
| @ir.HotOp::ReturnCall
| @ir.HotOp::ReturnCallIndirect
| @ir.HotOp::ReturnCallRef
| @ir.HotOp::Throw
| @ir.HotOp::Rethrow
| @ir.HotOp::ThrowRef
| @ir.HotOp::Delegate
| @ir.HotOp::Unreachable => return true
_ => ()
}
let operand_node = @ir.hot_node_get(func, node_id)
let operand_count = pass_value_operand_count(func, node_id)
for slot = 0; slot < operand_count; slot = slot + 1 {
if hso_subtree_has_tail_or_throw_escape(
func,
@ir.hot_child_get(
func,
node_id,
pass_value_operand_slot(operand_node, slot),
),
) {
return true
}
}
match node.op {
@ir.HotOp::Block | @ir.HotOp::Loop =>
hso_region_has_tail_or_throw_escape(
func,
@ir.hot_region_body_ref(func, node_id),
)
@ir.HotOp::If =>
if hso_region_has_tail_or_throw_escape(
func,
@ir.hot_region_then_ref(func, node_id),
) {
true
} else {
match @ir.hot_region_else_ref(func, node_id) {
Some(region_ref) =>
hso_region_has_tail_or_throw_escape(func, region_ref)
None => false
}
}
@ir.HotOp::Try =>
if hso_region_has_tail_or_throw_escape(
func,
@ir.hot_region_body_ref(func, node_id),
) {
true
} else {
match @ir.hot_region_catch_ref(func, node_id) {
Some(region_ref) =>
hso_region_has_tail_or_throw_escape(func, region_ref)
None => false
}
}
@ir.HotOp::TryTable =>
hso_region_has_tail_or_throw_escape(
func,
@ir.hot_region_body_ref(func, node_id),
) ||
hso_region_has_tail_or_throw_escape(
func,
@ir.hot_region_catch_list_ref(func, node_id),
)
_ => false
}
}
///|
fn hso_region_has_tail_or_throw_escape(
func : @ir.HotFunc,
region_ref : @ir.HotRegionRef,
) -> Bool {
for idx = 0; idx < @ir.hot_region_root_count(func, region_ref); idx = idx + 1 {
if hso_subtree_has_tail_or_throw_escape(
func,
@ir.hot_region_get(func, region_ref, idx),
) {
return true
}
}
false
}
///|
fn hso_try_table_allows_swap(func : @ir.HotFunc, try_id : Int32) -> Bool {
if try_id < 0 || !@ir.hot_node_is_live(func, try_id) {
return false
}
match @ir.hot_node_get(func, try_id).op {
@ir.HotOp::TryTable => ()
_ => return false
}
!hso_region_has_tail_or_throw_escape(
func,
@ir.hot_region_body_ref(func, try_id),
)
}
///|
fn hso_result_try_table_allows_drop_swap(
func : @ir.HotFunc,
try_id : Int32,
) -> Bool {
hso_try_table_allows_swap(func, try_id) &&
hso_sequence_result_type(func, try_id) != None
}
///|
fn hso_is_void_try_table_block(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::Block => ()
_ => return false
}
if hso_sequence_result_type(func, node_id) != None {
return false
}
let body_ref = @ir.hot_region_body_ref(func, node_id)
if @ir.hot_region_root_count(func, body_ref) != 1 {
return false
}
let try_id = @ir.hot_region_get(func, body_ref, 0)
if hso_sequence_result_type(func, try_id) != None {
return false
}
hso_try_table_allows_swap(func, try_id)
}
///|
fn hso_is_dropped_result_try_table_block(
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::Block => ()
_ => return false
}
if hso_sequence_result_type(func, node_id) != None {
return false
}
let body_ref = @ir.hot_region_body_ref(func, node_id)
let root_count = @ir.hot_region_root_count(func, body_ref)
if root_count == 1 {
let drop_id = @ir.hot_region_get(func, body_ref, 0)
if drop_id < 0 || !@ir.hot_node_is_live(func, drop_id) {
return false
}
match @ir.hot_node_get(func, drop_id).op {
@ir.HotOp::Drop => ()
_ => return false
}
if @ir.hot_child_count(func, drop_id) != 1 {
return false
}
let try_id = @ir.hot_child_get(func, drop_id, 0)
if try_id < 0 || !@ir.hot_node_is_live(func, try_id) {
return false
}
hso_result_try_table_allows_drop_swap(func, try_id)
} else if root_count == 2 {
let try_id = @ir.hot_region_get(func, body_ref, 0)
let drop_id = @ir.hot_region_get(func, body_ref, 1)
if try_id < 0 ||
drop_id < 0 ||
!@ir.hot_node_is_live(func, try_id) ||
!@ir.hot_node_is_live(func, drop_id) {
return false
}
if !hso_result_try_table_allows_drop_swap(func, try_id) {
return false
}
match @ir.hot_node_get(func, drop_id).op {
@ir.HotOp::Drop => true
_ => false
}
} else {
false
}
}
///|
fn hso_wrapped_try_table_swap_allowed(
cache : HsoPredicateCache,
func : @ir.HotFunc,
module_ctx : @ir.HotModuleContext?,
node_masks : Array[UInt32],
moved_struct_new_id : Int32,
blocker_id : Int32,
target_local_id : Int32,
) -> Bool {
if !hso_is_dropped_result_try_table_block(func, blocker_id) &&
!hso_is_void_try_table_block(func, blocker_id) {
return false
}
let raw_blocker_mask = hso_reorder_effect_mask(
cache, func, node_masks, blocker_id,
)
let blocker_mask = raw_blocker_mask ^
(raw_blocker_mask & @ir.EFFECT_MASK_CONTROL)
let moved_mask = hso_struct_new_operand_effect_mask(
func, module_ctx, node_masks, moved_struct_new_id,
) |
hso_struct_new_shallow_effects(func, moved_struct_new_id)
if hso_ordered_before(
cache, func, module_ctx, moved_struct_new_id, moved_mask, blocker_id, blocker_mask,
) &&
!hso_global_only_ordering_can_be_ignored(
module_ctx, func, moved_struct_new_id, moved_mask, blocker_id, blocker_mask,
) {
return false
}
if (blocker_mask & @ir.EFFECT_MASK_LOCAL_STATE) != 0U &&
hso_subtree_touches_local(func, blocker_id, target_local_id) {
return false
}
true
}
///|
fn hso_root_can_swap_before_local_struct_new(
cache : HsoPredicateCache,
func : @ir.HotFunc,
module_ctx : @ir.HotModuleContext?,
node_masks : Array[UInt32],
moved_struct_new_id : Int32,
blocker_id : Int32,
target_local_id : Int32,
) -> Bool {
if blocker_id < 0 || !@ir.hot_node_is_live(func, blocker_id) {
return false
}
// Binaryen optimizes tee-wrapped struct.set action roots before block-local
// chain swapping sees them. Treat an unprocessed tee-root as a constructor
// local-set barrier here so one fresh struct chain does not move across and
// consume a separate tee chain in the same region.
match hso_tee_wrapped_struct_set_local_id(func, blocker_id) {
Some(_) => return false
None => ()
}
let blocker_node = @ir.hot_node_get(func, blocker_id)
match blocker_node.op {
@ir.HotOp::LocalSet =>
if @ir.hot_child_count(func, blocker_id) == 1 {
match
@ir.hot_node_exact_instr(func, @ir.hot_child_get(func, blocker_id, 0)) {
Some(@lib.Instruction::StructNew(_))
| Some(@lib.Instruction::StructNewDefault(_))
| Some(@lib.Instruction::StructNewDesc(_))
| Some(@lib.Instruction::StructNewDefaultDesc(_)) => return false
_ => ()
}
}
_ => ()
}
let wrapped_try_table_swap = hso_wrapped_try_table_swap_allowed(
cache, func, module_ctx, node_masks, moved_struct_new_id, blocker_id, target_local_id,
)
if hso_subtree_may_escape_to_active_catch(func, blocker_id) &&
!wrapped_try_table_swap {
return false
}
if wrapped_try_table_swap {
return true
}
let blocker_mask = hso_reorder_effect_mask(
cache, func, node_masks, blocker_id,
)
let moved_mask = hso_struct_new_operand_effect_mask(
func, module_ctx, node_masks, moved_struct_new_id,
) |
hso_struct_new_shallow_effects(func, moved_struct_new_id)
if hso_ordered_before(
cache, func, module_ctx, moved_struct_new_id, moved_mask, blocker_id, blocker_mask,
) &&
!hso_global_only_ordering_can_be_ignored(
module_ctx, func, moved_struct_new_id, moved_mask, blocker_id, blocker_mask,
) {
return false
}
let disallowed_effects = @ir.EFFECT_MASK_CONTROL |
@ir.EFFECT_MASK_CALL |
@ir.EFFECT_MASK_THROW
if (blocker_mask & @ir.EFFECT_MASK_LOCAL_STATE) != 0U &&
hso_subtree_touches_local(func, blocker_id, target_local_id) {
return false
}
if moved_mask == 0U {
match blocker_node.op {
@ir.HotOp::Call | @ir.HotOp::CallIndirect | @ir.HotOp::CallRef =>
return true
_ => ()
}
}
if (blocker_mask & disallowed_effects) == 0U &&
!@ir.hot_is_terminator_node(func, blocker_id) {
return true
}
hso_subtree_is_trapless_reorderable(cache, func, node_masks, blocker_id, [])
}
///|
fn hso_swapped_readonly_roots_respect_ref_order(
cache : HsoPredicateCache,
func : @ir.HotFunc,
node_masks : Array[UInt32],
swapped_roots : Array[Int32],
ref_id : Int32,
) -> Bool {
let ref_node = @ir.hot_node_get(func, ref_id)
let ref_local = match ref_node.op {
@ir.HotOp::LocalGet => ref_node.imm0
_ => return false
}
for root_id in swapped_roots {
if root_id < 0 || !@ir.hot_node_is_live(func, root_id) {
continue
}
if ref_id < root_id &&
hso_subtree_is_trapless_reorderable(cache, func, node_masks, root_id, []) &&
hso_subtree_touches_local(func, root_id, ref_local) {
return false
}
}
true
}
///|
fn hso_note_region_local_max_root(
func : @ir.HotFunc,
region_ref : @ir.HotRegionRef,
root_id : Int32,
local_order : HsoLocalOrderWorkspace,
) -> Unit {
for idx = 0; idx < @ir.hot_region_root_count(func, region_ref); idx = idx + 1 {
hso_note_subtree_local_max_root(
func,
@ir.hot_region_get(func, region_ref, idx),
root_id,
local_order,
)
}
}
///|
fn hso_note_subtree_local_max_root(
func : @ir.HotFunc,
node_id : Int32,
root_id : Int32,
local_order : HsoLocalOrderWorkspace,
) -> 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 | @ir.HotOp::LocalSet | @ir.HotOp::LocalTee =>
hso_local_order_workspace_note(local_order, node.imm0, root_id)
_ => ()
}
let operand_count = pass_value_operand_count(func, node_id)
for slot = 0; slot < operand_count; slot = slot + 1 {
hso_note_subtree_local_max_root(
func,
@ir.hot_child_get(func, node_id, pass_value_operand_slot(node, slot)),
root_id,
local_order,
)
}
match node.op {
@ir.HotOp::Block | @ir.HotOp::Loop =>
hso_note_region_local_max_root(
func,
@ir.hot_region_body_ref(func, node_id),
root_id,
local_order,
)
@ir.HotOp::If => {
hso_note_region_local_max_root(
func,
@ir.hot_region_then_ref(func, node_id),
root_id,
local_order,
)
match @ir.hot_region_else_ref(func, node_id) {
Some(region_ref) =>
hso_note_region_local_max_root(func, region_ref, root_id, local_order)
None => ()
}
}
@ir.HotOp::Try => {
hso_note_region_local_max_root(
func,
@ir.hot_region_body_ref(func, node_id),
root_id,
local_order,
)
match @ir.hot_region_catch_ref(func, node_id) {
Some(region_ref) =>
hso_note_region_local_max_root(func, region_ref, root_id, local_order)
None => ()
}
}
@ir.HotOp::TryTable => {
hso_note_region_local_max_root(
func,
@ir.hot_region_body_ref(func, node_id),
root_id,
local_order,
)
hso_note_region_local_max_root(
func,
@ir.hot_region_catch_list_ref(func, node_id),
root_id,
local_order,
)
}
_ => ()
}
}
///|
fn hso_note_swapped_readonly_root(
cache : HsoPredicateCache,
func : @ir.HotFunc,
node_masks : Array[UInt32],
root_id : Int32,
local_order : HsoLocalOrderWorkspace,
) -> Unit {
if hso_subtree_is_trapless_reorderable(cache, func, node_masks, root_id, []) {
hso_note_subtree_local_max_root(func, root_id, root_id, local_order)
}
}
///|
fn hso_build_const_f32(func : @ir.HotFunc) -> Int32 {
@ir.hot_build_const_f32(func, @lib.F32(0.0))
}
///|
fn hso_build_const_f64(func : @ir.HotFunc) -> Int32 {
@ir.hot_build_const_f64(func, @lib.F64(0.0))
}
///|
fn hso_build_const_v128_zero(func : @ir.HotFunc) -> Int32 {
@ir.hot_build_const_v128_zero(func)
}
///|
fn hso_build_default_value(func : @ir.HotFunc, vt : @lib.ValType) -> Int32? {
match vt {
@lib.NumTypeValType(@lib.I32NumType) =>
Some(@ir.hot_build_const_i32(func, @lib.I32(0)))
@lib.NumTypeValType(@lib.I64NumType) =>
Some(@ir.hot_build_const_i64(func, @lib.I64(0L)))
@lib.NumTypeValType(@lib.F32NumType) => Some(hso_build_const_f32(func))
@lib.NumTypeValType(@lib.F64NumType) => Some(hso_build_const_f64(func))
@lib.VecTypeValType => Some(hso_build_const_v128_zero(func))
@lib.RefTypeValType(ref_type) =>
if ref_type.is_nullable() {
Some(@ir.hot_build_ref_null(func, ref_type))
} else {
None
}
@lib.BotValType => None
}
}
///|
fn hso_type_struct_field_count(
type_cache : HsoTypeCache,
module_ctx : @ir.HotModuleContext,
type_idx : @lib.TypeIdx,
) -> Int32? {
let type_slot = match type_idx {
@lib.TypeIdx(raw_idx) | @lib.RecIdx(raw_idx) => raw_idx.reinterpret_as_int()
}
hso_ensure_type_cache_capacity(type_cache, type_slot)
match type_cache.struct_field_counts[type_slot] {
count if count >= 0 => return Some(count)
-1 => return None
_ => ()
}
let fields = match
@ir.hot_module_resolve_struct_fields(module_ctx, type_idx) {
Some(fields) => fields
None => {
type_cache.struct_field_counts[type_slot] = -1
return None
}
}
type_cache.struct_field_counts[type_slot] = fields.length()
Some(fields.length())
}
///|
fn hso_type_defaultable_struct_field_types(
type_cache : HsoTypeCache,
module_ctx : @ir.HotModuleContext,
type_idx : @lib.TypeIdx,
) -> Array[@lib.ValType]? {
let type_slot = match type_idx {
@lib.TypeIdx(raw_idx) | @lib.RecIdx(raw_idx) => raw_idx.reinterpret_as_int()
}
hso_ensure_type_cache_capacity(type_cache, type_slot)
match type_cache.default_field_counts[type_slot] {
count if count >= 0 =>
match type_cache.default_field_types[type_slot] {
Some(field_types) => return Some(field_types)
None => ()
}
-1 => return None
_ => ()
}
let fields = match
@ir.hot_module_resolve_struct_fields(module_ctx, type_idx) {
Some(fields) => fields
None => {
type_cache.struct_field_counts[type_slot] = -1
return None
}
}
type_cache.struct_field_counts[type_slot] = fields.length()
let field_types : Array[@lib.ValType] = []
for field in fields {
let @lib.FieldType(stor, _) = field
let field_type = stor.unpack()
match field_type {
@lib.RefTypeValType(ref_type) =>
if !ref_type.is_nullable() {
type_cache.default_field_counts[type_slot] = -1
type_cache.default_field_types[type_slot] = None
return None
}
_ => ()
}
field_types.push(field_type)
}
type_cache.default_field_counts[type_slot] = field_types.length()
type_cache.default_field_types[type_slot] = Some(field_types)
Some(field_types)
}
///|
fn hso_type_has_defaultable_struct_fields(
type_cache : HsoTypeCache,
module_ctx : @ir.HotModuleContext,
type_idx : @lib.TypeIdx,
) -> Int32? {
match
hso_type_defaultable_struct_field_types(type_cache, module_ctx, type_idx) {
Some(field_types) => Some(field_types.length())
None => None
}
}
///|
fn hso_build_default_struct_new_children(
type_cache : HsoTypeCache,
func : @ir.HotFunc,
module_ctx : @ir.HotModuleContext,
type_idx : @lib.TypeIdx,
field_idx : Int32,
replacement_id : Int32,
desc_id : Int32?,
) -> Array[Int32]? {
let field_types = match
hso_type_defaultable_struct_field_types(type_cache, module_ctx, type_idx) {
Some(field_types) => field_types
None => return None
}
let children : Array[Int32] = []
for idx = 0; idx < field_types.length(); idx = idx + 1 {
if idx == field_idx {
children.push(replacement_id)
continue
}
match hso_build_default_value(func, field_types[idx]) {
Some(value_id) => children.push(value_id)
None => return None
}
}
match desc_id {
Some(desc_id) => children.push(desc_id)
None => ()
}
Some(children)
}
///|
fn hso_supported_struct_new(
type_cache : HsoTypeCache,
func : @ir.HotFunc,
module_ctx : @ir.HotModuleContext?,
node_id : Int32,
) -> (@lib.TypeIdx, Int32, Int32?, Bool)? {
if node_id < 0 || !@ir.hot_node_is_live(func, node_id) {
return None
}
let child_count = @ir.hot_child_count(func, node_id)
match @ir.hot_node_exact_instr(func, node_id) {
Some(@lib.Instruction::StructNew(type_idx)) =>
Some((type_idx, child_count, None, false))
Some(@lib.Instruction::StructNewDesc(type_idx)) =>
if child_count > 0 {
Some(
(
type_idx,
child_count - 1,
Some(@ir.hot_child_get(func, node_id, child_count - 1)),
false,
),
)
} else {
None
}
Some(@lib.Instruction::StructNewDefault(type_idx)) =>
match module_ctx {
Some(module_ctx) =>
match
hso_type_has_defaultable_struct_fields(
type_cache, module_ctx, type_idx,
) {
Some(field_count) => Some((type_idx, field_count, None, true))
None => None
}
None => None
}
Some(@lib.Instruction::StructNewDefaultDesc(type_idx)) =>
if child_count > 0 {
let desc_id = @ir.hot_child_get(func, node_id, child_count - 1)
match module_ctx {
Some(module_ctx) =>
match
hso_type_has_defaultable_struct_fields(
type_cache, module_ctx, type_idx,
) {
Some(field_count) =>
Some((type_idx, field_count, Some(desc_id), true))
None => None
}
None => None
}
} else {
None
}
_ => None
}
}
///|
fn hso_struct_new_shallow_effects(
func : @ir.HotFunc,
node_id : Int32,
) -> UInt32 {
match @ir.hot_node_exact_instr(func, node_id) {
Some(@lib.Instruction::StructNew(_))
| Some(@lib.Instruction::StructNewDefault(_)) => 0U
Some(@lib.Instruction::StructNewDesc(_))
| Some(@lib.Instruction::StructNewDefaultDesc(_)) => {
let child_count = @ir.hot_child_count(func, node_id)
if child_count <= 0 {
return @ir.EFFECT_MASK_TRAP
}
let desc_id = @ir.hot_child_get(func, node_id, child_count - 1)
let results = @ir.hot_type_results(
func,
@ir.hot_node_get(func, desc_id).ty,
)
if results.length() != 1 {
return @ir.EFFECT_MASK_TRAP
}
match results[0] {
@lib.RefTypeValType(ref_type) =>
if ref_type.is_nullable() {
@ir.EFFECT_MASK_TRAP
} else {
0U
}
_ => @ir.EFFECT_MASK_TRAP
}
}
_ => 0U
}
}
///|
fn hso_struct_set_parts(
func : @ir.HotFunc,
node_id : Int32,
) -> (@lib.TypeIdx, Int32, Int32, Int32)? {
if node_id < 0 || !@ir.hot_node_is_live(func, node_id) {
return None
}
if @ir.hot_child_count(func, node_id) != 2 {
return None
}
match @ir.hot_node_exact_instr(func, node_id) {
Some(@lib.Instruction::StructSet(type_idx, field_idx))
| Some(@lib.Instruction::StructAtomicSet(@lib.Relaxed, type_idx, field_idx)) => {
let @lib.U32(raw_field_idx) = field_idx
Some(
(
type_idx,
raw_field_idx.reinterpret_as_int(),
@ir.hot_child_get(func, node_id, 0),
@ir.hot_child_get(func, node_id, 1),
),
)
}
// A release or sequentially-consistent store to a fresh object can
// synchronize with a later acquire after another thread receives the
// object. Constructor initialization preserves the value but not that
// atomic ordering event. Relaxed stores carry no such ordering edge and
// remain foldable while the object is proven not to have escaped.
_ => None
}
}
///|
fn hso_sequence_result_type(
func : @ir.HotFunc,
value_id : Int32,
) -> @lib.BlockType? {
let results = @ir.hot_type_results(func, @ir.hot_node_get(func, value_id).ty)
if results.length() != 1 {
return None
}
Some(@lib.BlockType::val_type(results[0]))
}
///|
fn hso_build_preserved_replacement(
cache : HsoPredicateCache,
func : @ir.HotFunc,
node_masks : Array[UInt32],
old_value_id : Int32,
new_value_id : Int32,
) -> Int32? {
if hso_subtree_is_trapless_readonly(cache, func, node_masks, old_value_id) ||
hso_subtree_is_trapless_readonly_contained_control(
cache,
func,
node_masks,
old_value_id,
[],
) {
return Some(new_value_id)
}
match hso_sequence_result_type(func, new_value_id) {
Some(block_type) => {
let drop_old = @ir.hot_build_drop(func, old_value_id)
Some(@ir.hot_build_block(func, block_type, [drop_old, new_value_id]))
}
None => None
}
}
///|
fn hso_branch_exits_owner(
func : @ir.HotFunc,
owner_id : Int32,
active_owners : Array[Int32],
node_id : Int32,
) -> Bool {
let targets : Array[Int32] = []
match @ir.hot_branch_target(func, node_id) {
Some(label_id) => targets.push(label_id)
None => ()
}
for label_id in @ir.hot_branch_table_targets_for_node(func, node_id) {
targets.push(label_id)
}
if targets.is_empty() {
return false
}
for label_id in targets {
let target_owner_id = @ir.hot_label_owner(func, label_id)
if target_owner_id != owner_id &&
hso_active_owners_contains(active_owners, target_owner_id) {
return false
}
}
true
}
///|
fn hso_region_exits_owner(
func : @ir.HotFunc,
region_ref : @ir.HotRegionRef,
owner_id : Int32,
active_owners : Array[Int32],
) -> 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 hso_subtree_exits_owner(func, root_id, owner_id, active_owners) {
return true
}
}
false
}
///|
fn hso_subtree_exits_owner(
func : @ir.HotFunc,
node_id : Int32,
owner_id : Int32,
active_owners : 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)
if @ir.hot_is_branch(func, node_id) &&
@ir.hot_is_terminator_node(func, node_id) {
return hso_branch_exits_owner(func, owner_id, active_owners, node_id)
}
match node.op {
@ir.HotOp::Return
| @ir.HotOp::ReturnCall
| @ir.HotOp::ReturnCallIndirect
| @ir.HotOp::ReturnCallRef
| @ir.HotOp::Throw
| @ir.HotOp::Rethrow
| @ir.HotOp::ThrowRef
| @ir.HotOp::Delegate
| @ir.HotOp::Unreachable => return true
_ => ()
}
let pushes_owner = match node.op {
@ir.HotOp::Block
| @ir.HotOp::Loop
| @ir.HotOp::If
| @ir.HotOp::Try
| @ir.HotOp::TryTable => true
_ => false
}
if pushes_owner {
active_owners.push(node_id)
}
let exits_owner = match node.op {
@ir.HotOp::Block | @ir.HotOp::Loop =>
hso_region_exits_owner(
func,
@ir.hot_region_body_ref(func, node_id),
owner_id,
active_owners,
)
@ir.HotOp::If => {
let then_exits_owner = hso_region_exits_owner(
func,
@ir.hot_region_then_ref(func, node_id),
owner_id,
active_owners,
)
if !then_exits_owner {
false
} else {
match @ir.hot_region_else_ref(func, node_id) {
Some(region_ref) =>
hso_region_exits_owner(func, region_ref, owner_id, active_owners)
None => false
}
}
}
@ir.HotOp::Try => {
let body_exits_owner = hso_region_exits_owner(
func,
@ir.hot_region_body_ref(func, node_id),
owner_id,
active_owners,
)
if !body_exits_owner {
false
} else {
match @ir.hot_region_catch_ref(func, node_id) {
Some(region_ref) =>
hso_region_exits_owner(func, region_ref, owner_id, active_owners)
None => false
}
}
}
@ir.HotOp::TryTable =>
hso_region_exits_owner(
func,
@ir.hot_region_body_ref(func, node_id),
owner_id,
active_owners,
) &&
hso_region_exits_owner(
func,
@ir.hot_region_catch_list_ref(func, node_id),
owner_id,
active_owners,
)
_ => false
}
if pushes_owner {
ignore(active_owners.pop())
}
exits_owner
}
///|
fn hso_subtree_targets_owner_label(
func : @ir.HotFunc,
node_id : Int32,
owner_id : Int32,
) -> Bool {
if node_id < 0 || !@ir.hot_node_is_live(func, node_id) {
return false
}
match @ir.hot_branch_target(func, node_id) {
Some(label_id) =>
if @ir.hot_label_owner(func, label_id) == owner_id {
return true
}
None => ()
}
for label_id in @ir.hot_branch_table_targets_for_node(func, node_id) {
if @ir.hot_label_owner(func, label_id) == owner_id {
return true
}
}
let operand_node = @ir.hot_node_get(func, node_id)
let operand_count = pass_value_operand_count(func, node_id)
for slot = 0; slot < operand_count; slot = slot + 1 {
if hso_subtree_targets_owner_label(
func,
@ir.hot_child_get(
func,
node_id,
pass_value_operand_slot(operand_node, slot),
),
owner_id,
) {
return true
}
}
let node = @ir.hot_node_get(func, node_id)
match node.op {
@ir.HotOp::Block | @ir.HotOp::Loop =>
hso_region_targets_owner_label(
func,
@ir.hot_region_body_ref(func, node_id),
owner_id,
)
@ir.HotOp::If => {
let then_targets_owner = hso_region_targets_owner_label(
func,
@ir.hot_region_then_ref(func, node_id),
owner_id,
)
if then_targets_owner {
true
} else {
match @ir.hot_region_else_ref(func, node_id) {
Some(region_ref) =>
hso_region_targets_owner_label(func, region_ref, owner_id)
None => false
}
}
}
@ir.HotOp::Try => {
let body_targets_owner = hso_region_targets_owner_label(
func,
@ir.hot_region_body_ref(func, node_id),
owner_id,
)
if body_targets_owner {
true
} else {
match @ir.hot_region_catch_ref(func, node_id) {
Some(region_ref) =>
hso_region_targets_owner_label(func, region_ref, owner_id)
None => false
}
}
}
@ir.HotOp::TryTable =>
hso_region_targets_owner_label(
func,
@ir.hot_region_body_ref(func, node_id),
owner_id,
) ||
hso_region_targets_owner_label(
func,
@ir.hot_region_catch_list_ref(func, node_id),
owner_id,
)
_ => false
}
}
///|
fn hso_region_targets_owner_label(
func : @ir.HotFunc,
region_ref : @ir.HotRegionRef,
owner_id : Int32,
) -> Bool {
for idx = 0; idx < @ir.hot_region_root_count(func, region_ref); idx = idx + 1 {
if hso_subtree_targets_owner_label(
func,
@ir.hot_region_get(func, region_ref, idx),
owner_id,
) {
return true
}
}
false
}
///|
fn hso_try_flatten_block_wrapper(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::Block => ()
_ => return false
}
let body_ref = @ir.hot_region_body_ref(func, node_id)
if @ir.hot_region_root_count(func, body_ref) != 1 {
return false
}
let block_type = match hso_sequence_result_type(func, node_id) {
Some(block_type) => block_type
None => return false
}
let root_id = @ir.hot_region_get(func, body_ref, 0)
if root_id < 0 ||
!@ir.hot_node_is_live(func, root_id) ||
Some(block_type) != hso_sequence_result_type(func, root_id) {
return false
}
let root_node = @ir.hot_node_get(func, root_id)
if root_node.op is @ir.HotOp::Block {
// Keep the outer block and its entry operands. Peeling an inner block with
// entry operands would lose their evaluation, and changing a shared inner
// body would also change its other occurrences.
if pass_value_operand_count(func, root_id) != 0 {
return false
}
if !hso_wrapper_has_one_reference_bounded(func, root_id) {
return false
}
let inner_ref = @ir.hot_region_body_ref(func, root_id)
let roots = Array::makei(@ir.hot_region_root_count(func, inner_ref), fn(
index,
) {
@ir.hot_region_get(func, inner_ref, index)
})
let old_label_id = @ir.hot_control_node_label(func, root_id)
let new_label_id = @ir.hot_control_node_label(func, node_id)
if old_label_id < 0 || old_label_id >= 1024 {
return false
}
hso_retarget_subtree_label(func, root_id, old_label_id, new_label_id)
@ir.hot_region_replace_body(func, body_ref, roots)
@ir.hot_region_replace_body(func, inner_ref, [])
return true
}
// Forwarding a non-block root cannot discard the typed outer prefix.
if pass_value_operand_count(func, node_id) != 0 ||
hso_subtree_targets_owner_label(func, root_id, node_id) ||
hso_subtree_targets_owner_label(func, root_id, root_id) {
return false
}
@ir.hot_replace_node(func, node_id, root_node)
true
}
///|
// This new peel path requires an exclusive inner owner. Keep the proof bounded
// instead of building a whole-function use-count overlay for every wrapper.
// Incomplete ownership scans reject before any mutation.
fn hso_wrapper_has_one_reference_bounded(
func : @ir.HotFunc,
target : Int32,
) -> Bool {
let node_count = @ir.hot_node_count(func)
let root_count = @ir.hot_root_count(func)
if node_count > 1024 || root_count > 1024 - node_count {
return false
}
let mut budget = 1024 - node_count - root_count
let mut references = 0
for index in 0.. 1 {
return false
}
}
}
for owner in 0.. budget {
return false
}
budget -= child_count
let node = @ir.hot_node_get(func, owner)
let target_count = match node.op {
@ir.HotOp::BrTable =>
@ir.hot_branch_table_targets(func, node.imm0).length() + 1
@ir.HotOp::TryTable =>
@ir.hot_catch_info_get(func, node.imm1).catches.length()
@ir.HotOp::Continuation =>
@ir.hot_branch_table_targets(func, node.imm1).length()
_ => 0
}
if target_count > budget {
return false
}
budget -= target_count
for slot in 0.. 1 {
return false
}
}
}
}
references == 1
}
///|
fn hso_retarget_subtree_label(
func : @ir.HotFunc,
node_id : Int32,
from_label_id : Int32,
to_label_id : Int32,
) -> Unit {
if node_id < 0 ||
!@ir.hot_node_is_live(func, node_id) ||
from_label_id == to_label_id {
return
}
let removed_labels = @bitset.BitSet::new(from_label_id + 1)
removed_labels[from_label_id] = true
let seen = @bitset.BitSet::new(@ir.hot_node_count(func))
let pending = [node_id]
while !pending.is_empty() {
let current = pending.pop().unwrap()
if current < 0 || !@ir.hot_node_is_live(func, current) || seen[current] {
continue
}
seen[current] = true
// Use the existing complete node-label remap for branches, tables,
// continuation handlers, and try_table catch destinations.
ignore(
remove_unused_names_retarget_node_labels(
func, current, removed_labels, to_label_id,
),
)
for slot in 0..<@ir.hot_child_count(func, current) {
pending.push(@ir.hot_child_get(func, current, slot))
}
}
}
///|
fn hso_trim_unreachable_subtree(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::Block => {
let changed = hso_trim_unreachable_region_tails(
func,
@ir.hot_region_body_ref(func, node_id),
node_id,
)
hso_try_flatten_block_wrapper(func, node_id) || changed
}
@ir.HotOp::Loop =>
hso_trim_unreachable_region_tails(
func,
@ir.hot_region_body_ref(func, node_id),
node_id,
)
@ir.HotOp::If => {
let mut changed = hso_trim_unreachable_region_tails(
func,
@ir.hot_region_then_ref(func, node_id),
node_id,
)
match @ir.hot_region_else_ref(func, node_id) {
Some(region_ref) =>
changed = hso_trim_unreachable_region_tails(func, region_ref, node_id) ||
changed
None => ()
}
changed
}
@ir.HotOp::Try => {
let mut changed = hso_trim_unreachable_region_tails(
func,
@ir.hot_region_body_ref(func, node_id),
node_id,
)
match @ir.hot_region_catch_ref(func, node_id) {
Some(region_ref) =>
changed = hso_trim_unreachable_region_tails(func, region_ref, node_id) ||
changed
None => ()
}
changed
}
@ir.HotOp::TryTable =>
hso_trim_unreachable_region_tails(
func,
@ir.hot_region_body_ref(func, node_id),
node_id,
) ||
hso_trim_unreachable_region_tails(
func,
@ir.hot_region_catch_list_ref(func, node_id),
node_id,
)
_ => false
}
}
///|
fn hso_trim_unreachable_region_tails(
func : @ir.HotFunc,
region_ref : @ir.HotRegionRef,
owner_id : Int32,
) -> Bool {
let mut changed = false
let root_count = @ir.hot_region_root_count(func, region_ref)
let mut idx = 0
while idx < root_count {
let root_id = @ir.hot_region_get(func, region_ref, idx)
changed = hso_trim_unreachable_subtree(func, root_id) || changed
if @ir.hot_is_terminator_node(func, root_id) ||
hso_subtree_exits_owner(func, root_id, owner_id, [owner_id]) {
let removed = root_count - idx - 1
if removed > 0 {
ignore(@ir.hot_region_splice(func, region_ref, idx + 1, removed, []))
changed = true
}
return changed
}
idx = idx + 1
}
changed
}
///|
fn hso_array_insert_prefix_roots(
roots : Array[Int32],
idx : Int32,
inserted : Array[Int32],
count : Int32,
) -> Unit {
if count <= 0 {
return
}
let suffix_count = roots.length() - idx
let suffix : Array[Int32] = Array::make(suffix_count, 0)
for suffix_idx = 0; suffix_idx < suffix_count; suffix_idx = suffix_idx + 1 {
suffix[suffix_idx] = roots[idx + suffix_idx]
}
while roots.length() > idx {
ignore(roots.pop())
}
for inserted_idx = 0; inserted_idx < count; inserted_idx = inserted_idx + 1 {
roots.push(inserted[inserted_idx])
}
for suffix_root_id in suffix {
roots.push(suffix_root_id)
}
}
///|
fn hso_liftable_swapped_block_roots(
cache : HsoPredicateCache,
func : @ir.HotFunc,
module_ctx : @ir.HotModuleContext?,
node_masks : Array[UInt32],
moved_struct_new_id : Int32,
node_id : Int32,
local_id : Int32,
) -> Array[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::Block => ()
_ => return None
}
if hso_sequence_result_type(func, node_id) != None {
return None
}
let body_ref = @ir.hot_region_body_ref(func, node_id)
let roots = hso_region_roots(func, body_ref)
if roots.is_empty() {
return None
}
for root_id in roots {
if hso_subtree_contains_try_table(func, root_id) {
return None
}
if !hso_root_can_swap_before_local_struct_new(
cache, func, module_ctx, node_masks, moved_struct_new_id, root_id, local_id,
) {
return None
}
}
Some(roots)
}
///|
fn hso_peel_reorderable_value_prefix_roots(
cache : HsoPredicateCache,
func : @ir.HotFunc,
module_ctx : @ir.HotModuleContext?,
node_masks : Array[UInt32],
struct_new_id : Int32,
field_idx : Int32,
field_count : Int32,
desc_id : Int32?,
needs_default_materialization : Bool,
value_id : Int32,
local_id : Int32,
active_catch_depth : Int32,
) -> Array[Int32]? {
if value_id < 0 || !@ir.hot_node_is_live(func, value_id) {
return None
}
let node = @ir.hot_node_get(func, value_id)
match node.op {
@ir.HotOp::Block => ()
_ => return Some([])
}
let block_type = match hso_sequence_result_type(func, value_id) {
Some(block_type) => block_type
None => return Some([])
}
let body_ref = @ir.hot_region_body_ref(func, value_id)
let roots = hso_region_roots(func, body_ref)
if roots.length() < 2 {
return Some([])
}
let final_root_id = roots[roots.length() - 1]
if hso_sequence_result_type(func, final_root_id) != Some(block_type) {
return Some([])
}
let prefix_roots : Array[Int32] = Array::new(capacity=roots.length() - 1)
for idx = 0; idx < roots.length() - 1; idx = idx + 1 {
let root_id = roots[idx]
let uncaught_call_can_lift = if active_catch_depth == 0 &&
!hso_subtree_touches_local(func, root_id, local_id) {
match @ir.hot_node_get(func, root_id).op {
@ir.HotOp::Call | @ir.HotOp::CallIndirect | @ir.HotOp::CallRef => true
_ => false
}
} else {
false
}
if !uncaught_call_can_lift &&
!hso_root_can_swap_before_local_struct_new(
cache, func, module_ctx, node_masks, final_root_id, root_id, local_id,
) {
return Some([])
}
prefix_roots.push(root_id)
}
if prefix_roots.is_empty() {
return Some([])
}
let struct_new_shallow_mask = hso_struct_new_shallow_effects(
func, struct_new_id,
)
for prefix_id in prefix_roots {
if hso_ordered_before(
cache,
func,
module_ctx,
struct_new_id,
struct_new_shallow_mask,
prefix_id,
hso_reorder_effect_mask(cache, func, node_masks, prefix_id),
) &&
!hso_subtree_is_trapless_reorderable(cache, func, node_masks, prefix_id, []) {
return Some([])
}
}
for idx = field_idx + 1; idx < field_count; idx = idx + 1 {
let later_id = if needs_default_materialization {
-1
} else {
@ir.hot_child_get(func, struct_new_id, idx)
}
if later_id >= 0 {
if hso_subtree_has_tail_or_throw_escape(func, later_id) {
return Some([])
}
let later_mask = hso_effects_for_node(func, node_masks, later_id)
for prefix_id in prefix_roots {
if !hso_nodes_can_reorder(
cache,
func,
module_ctx,
node_masks,
later_id,
later_mask,
prefix_id,
hso_reorder_effect_mask(cache, func, node_masks, prefix_id),
) {
return Some([])
}
}
}
}
match desc_id {
Some(desc_id) => {
let desc_mask = hso_desc_operand_effects_for_node(
func, module_ctx, node_masks, desc_id,
)
for prefix_id in prefix_roots {
if !hso_nodes_can_reorder(
cache,
func,
module_ctx,
node_masks,
desc_id,
desc_mask,
prefix_id,
hso_reorder_effect_mask(cache, func, node_masks, prefix_id),
) {
return Some([])
}
}
}
None => ()
}
ignore(
@ir.hot_region_splice(func, body_ref, 0, roots.length(), [final_root_id]),
)
ignore(hso_try_flatten_block_wrapper(func, value_id))
Some(prefix_roots)
}
///|
fn hso_try_fold_into_struct_new(
cache : HsoPredicateCache,
type_cache : HsoTypeCache,
func : @ir.HotFunc,
module_ctx : @ir.HotModuleContext?,
node_masks : Array[UInt32],
struct_new_id : Int32,
type_idx : @lib.TypeIdx,
field_idx : Int32,
value_id : Int32,
local_id : Int32,
active_catch_depth : Int32,
skip_owner_id : Int32?,
external_roots : HsoExternalRoots,
) -> Array[Int32]? {
let (new_type_idx, field_count, desc_id, needs_default_materialization) = match
hso_supported_struct_new(type_cache, func, module_ctx, struct_new_id) {
Some(parts) => parts
_ => return None
}
if new_type_idx != type_idx || field_idx < 0 || field_idx >= field_count {
return None
}
for idx = field_idx + 1; idx < field_count; idx = idx + 1 {
if !needs_default_materialization {
let later_id = @ir.hot_child_get(func, struct_new_id, idx)
if hso_subtree_global_order_conflicts(
module_ctx, func, later_id, value_id,
) {
return None
}
}
}
match desc_id {
Some(desc_id) =>
if hso_subtree_global_order_conflicts(module_ctx, func, desc_id, value_id) {
return None
}
None => ()
}
if hso_subtree_has_catchable_try_table_escape(func, value_id) {
return None
}
ignore(hso_trim_unreachable_subtree(func, value_id))
let lifted_prefix_roots = match
hso_peel_reorderable_value_prefix_roots(
cache, func, module_ctx, node_masks, struct_new_id, field_idx, field_count,
desc_id, needs_default_materialization, value_id, local_id, active_catch_depth,
) {
Some(roots) => roots
None => return None
}
let value_mask = hso_effects_for_node(func, node_masks, value_id)
let value_reorder_mask = hso_reorder_effect_mask(
cache, func, node_masks, value_id,
)
if (value_mask & @ir.EFFECT_MASK_LOCAL_STATE) != 0U &&
hso_subtree_touches_local(func, value_id, local_id) {
return None
}
if (value_mask & @ir.EFFECT_MASK_CONTROL) != 0U &&
hso_subtree_may_skip_local_set(cache, func, value_id) {
let skip_is_safe = match skip_owner_id {
Some(_) =>
!hso_external_roots_touch_local(func, external_roots, local_id) &&
hso_subtree_skip_is_branch_only(func, value_id)
None => false
}
if !skip_is_safe {
return None
}
}
if active_catch_depth > 0 &&
hso_subtree_may_escape_to_active_catch(func, value_id) {
return None
}
let struct_new_shallow_mask = hso_struct_new_shallow_effects(
func, struct_new_id,
)
if hso_ordered_before(
cache, func, module_ctx, struct_new_id, struct_new_shallow_mask, value_id,
value_reorder_mask,
) &&
!hso_subtree_is_trapless_reorderable(cache, func, node_masks, value_id, []) {
return None
}
for prefix_id in lifted_prefix_roots {
if hso_ordered_before(
cache,
func,
module_ctx,
struct_new_id,
struct_new_shallow_mask,
prefix_id,
hso_reorder_effect_mask(cache, func, node_masks, prefix_id),
) &&
!hso_subtree_is_trapless_reorderable(cache, func, node_masks, prefix_id, []) {
return None
}
}
for idx = field_idx + 1; idx < field_count; idx = idx + 1 {
let later_id = if needs_default_materialization {
-1
} else {
@ir.hot_child_get(func, struct_new_id, idx)
}
let later_mask = if later_id >= 0 {
hso_effects_for_node(func, node_masks, later_id)
} else {
0U
}
if later_id >= 0 && hso_subtree_has_tail_or_throw_escape(func, later_id) {
return None
}
if !hso_nodes_can_reorder(
cache, func, module_ctx, node_masks, later_id, later_mask, value_id, value_reorder_mask,
) {
return None
}
for prefix_id in lifted_prefix_roots {
if !hso_nodes_can_reorder(
cache,
func,
module_ctx,
node_masks,
later_id,
later_mask,
prefix_id,
hso_reorder_effect_mask(cache, func, node_masks, prefix_id),
) {
return None
}
}
}
match desc_id {
Some(desc_id) => {
let desc_mask = hso_desc_operand_effects_for_node(
func, module_ctx, node_masks, desc_id,
)
if !hso_nodes_can_reorder(
cache, func, module_ctx, node_masks, desc_id, desc_mask, value_id, value_reorder_mask,
) {
return None
}
for prefix_id in lifted_prefix_roots {
if !hso_nodes_can_reorder(
cache,
func,
module_ctx,
node_masks,
desc_id,
desc_mask,
prefix_id,
hso_reorder_effect_mask(cache, func, node_masks, prefix_id),
) {
return None
}
}
}
None => ()
}
if needs_default_materialization {
let module_ctx = match module_ctx {
Some(module_ctx) => module_ctx
None => return None
}
let children = match
hso_build_default_struct_new_children(
type_cache, func, module_ctx, type_idx, field_idx, value_id, desc_id,
) {
Some(children) => children
None => return None
}
let instr = match desc_id {
Some(_) => @lib.Instruction::struct_new_desc(type_idx)
None => @lib.Instruction::struct_new(type_idx)
}
@ir.hot_node_exact_instr_set(func, struct_new_id, instr)
ignore(@ir.hot_replace_child_span(func, struct_new_id, children))
} else {
let old_value_id = @ir.hot_child_get(func, struct_new_id, field_idx)
let replacement = match
hso_build_preserved_replacement(
cache, func, node_masks, old_value_id, value_id,
) {
Some(replacement) => replacement
None => return None
}
@ir.hot_child_set(func, struct_new_id, field_idx, replacement)
}
Some(lifted_prefix_roots)
}
///|
fn hso_try_fold_tee_wrapped_struct_set(
cache : HsoPredicateCache,
type_cache : HsoTypeCache,
func : @ir.HotFunc,
module_ctx : @ir.HotModuleContext?,
node_masks : Array[UInt32],
node_id : Int32,
active_catch_depth : Int32,
) -> Array[Int32]? {
let (type_idx, field_idx, ref_id, value_id) = match
hso_struct_set_parts(func, node_id) {
Some(parts) => parts
None => return None
}
if !@ir.hot_node_is_live(func, ref_id) {
return None
}
let ref_node = @ir.hot_node_get(func, ref_id)
match ref_node.op {
@ir.HotOp::LocalTee => ()
_ => return None
}
if @ir.hot_child_count(func, ref_id) != 1 {
return None
}
let struct_new_id = @ir.hot_child_get(func, ref_id, 0)
let lifted_prefix_roots = match
hso_try_fold_into_struct_new(
cache,
type_cache,
func,
module_ctx,
node_masks,
struct_new_id,
type_idx,
field_idx,
value_id,
ref_node.imm0,
active_catch_depth,
None,
hso_new_external_roots(),
) {
Some(roots) => roots
None => return None
}
let local_set_id = @ir.hot_build_local_set(func, ref_node.imm0, struct_new_id)
let replacement_count = lifted_prefix_roots.length() + 1
let replacement_roots : Array[Int32] = Array::make(replacement_count, 0)
for idx = 0; idx < lifted_prefix_roots.length(); idx = idx + 1 {
replacement_roots[idx] = lifted_prefix_roots[idx]
}
replacement_roots[replacement_count - 1] = local_set_id
Some(replacement_roots)
}
///|
fn hso_tee_wrapped_struct_set_local_id(
func : @ir.HotFunc,
node_id : Int32,
) -> Int32? {
let (_, _, ref_id, _) = match hso_struct_set_parts(func, node_id) {
Some(parts) => parts
None => return None
}
if !@ir.hot_node_is_live(func, ref_id) {
return None
}
let ref_node = @ir.hot_node_get(func, ref_id)
match ref_node.op {
@ir.HotOp::LocalTee => Some(ref_node.imm0)
_ => None
}
}
///|
fn hso_tee_fold_has_adjacent_readonly_prefix(
cache : HsoPredicateCache,
func : @ir.HotFunc,
node_masks : Array[UInt32],
roots : Array[Int32],
idx : Int32,
local_id : Int32,
) -> Bool {
if idx <= 0 || idx + 1 >= roots.length() {
return false
}
let prefix_id = roots[idx - 1]
if !hso_subtree_is_trapless_readonly(cache, func, node_masks, prefix_id) {
return false
}
let next_id = roots[idx + 1]
if next_id < 0 || !@ir.hot_node_is_live(func, next_id) {
return false
}
let next_node = @ir.hot_node_get(func, next_id)
match next_node.op {
@ir.HotOp::LocalGet => next_node.imm0 == local_id
_ => false
}
}
///|
fn hso_local_set_struct_new(
type_cache : HsoTypeCache,
func : @ir.HotFunc,
module_ctx : @ir.HotModuleContext?,
node_id : Int32,
) -> (Int32, Int32, @lib.TypeIdx)? {
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::LocalSet => ()
_ => return None
}
if @ir.hot_child_count(func, node_id) != 1 {
return None
}
let struct_new_id = @ir.hot_child_get(func, node_id, 0)
match hso_supported_struct_new(type_cache, func, module_ctx, struct_new_id) {
Some((type_idx, _, _, _)) => Some((node.imm0, struct_new_id, type_idx))
None => None
}
}
///|
fn hso_struct_set_for_local(
func : @ir.HotFunc,
node_id : Int32,
local_id : Int32,
expected_type_idx : @lib.TypeIdx,
) -> (Int32, Int32, Int32)? {
let (type_idx, field_idx, ref_id, value_id) = match
hso_struct_set_parts(func, node_id) {
Some(parts) => parts
None => return None
}
if type_idx != expected_type_idx || !@ir.hot_node_is_live(func, ref_id) {
return None
}
let ref_node = @ir.hot_node_get(func, ref_id)
match ref_node.op {
@ir.HotOp::LocalGet => if ref_node.imm0 != local_id { return None }
_ => return None
}
Some((field_idx, ref_id, value_id))
}
///|
fn hso_process_node_regions(
cache : HsoPredicateCache,
type_cache : HsoTypeCache,
func : @ir.HotFunc,
module_ctx : @ir.HotModuleContext?,
node_masks : Array[UInt32],
node_id : Int32,
active_catch_depth : Int32,
external_roots : HsoExternalRoots,
local_order : HsoLocalOrderWorkspace,
) -> Bool {
if node_id < 0 || !@ir.hot_node_is_live(func, node_id) {
return false
}
// Nested HSO opportunities can only live below HOT control/region nodes.
// Avoid recursively walking straight-line value trees during the pre-pass
// region descent; the later root-chain scan still handles LocalSet/StructSet
// candidates in the current region.
if (hso_effects_for_node(func, node_masks, node_id) & @ir.EFFECT_MASK_CONTROL) ==
0U {
return false
}
let node = @ir.hot_node_get(func, node_id)
let mut child_changed = false
if !hso_node_may_have_regions(node.op) {
for slot = 0; slot < @ir.hot_child_count(func, node_id); slot = slot + 1 {
child_changed = hso_process_node_regions(
cache,
type_cache,
func,
module_ctx,
node_masks,
@ir.hot_child_get(func, node_id, slot),
active_catch_depth,
external_roots,
local_order,
) ||
child_changed
}
}
let region_changed = match node.op {
@ir.HotOp::Block | @ir.HotOp::Loop =>
hso_process_region(
cache,
type_cache,
func,
module_ctx,
node_masks,
@ir.hot_region_body_ref(func, node_id),
active_catch_depth,
Some(node_id),
external_roots,
local_order,
)
@ir.HotOp::If => {
let mut changed = hso_process_region(
cache,
type_cache,
func,
module_ctx,
node_masks,
@ir.hot_region_then_ref(func, node_id),
active_catch_depth,
Some(node_id),
external_roots,
local_order,
)
match @ir.hot_region_else_ref(func, node_id) {
Some(region_ref) =>
changed = hso_process_region(
cache,
type_cache,
func,
module_ctx,
node_masks,
region_ref,
active_catch_depth,
Some(node_id),
external_roots,
local_order,
) ||
changed
None => ()
}
changed
}
@ir.HotOp::Try => {
let body_catch_depth = if @ir.hot_region_catch_ref(func, node_id).is_some() {
active_catch_depth + 1
} else {
active_catch_depth
}
let mut changed = hso_process_region(
cache,
type_cache,
func,
module_ctx,
node_masks,
@ir.hot_region_body_ref(func, node_id),
body_catch_depth,
Some(node_id),
external_roots,
local_order,
)
match @ir.hot_region_catch_ref(func, node_id) {
Some(region_ref) =>
changed = hso_process_region(
cache,
type_cache,
func,
module_ctx,
node_masks,
region_ref,
active_catch_depth,
Some(node_id),
external_roots,
local_order,
) ||
changed
None => ()
}
changed
}
@ir.HotOp::TryTable =>
hso_process_region(
cache,
type_cache,
func,
module_ctx,
node_masks,
@ir.hot_region_body_ref(func, node_id),
active_catch_depth + 1,
Some(node_id),
external_roots,
local_order,
) ||
hso_process_region(
cache,
type_cache,
func,
module_ctx,
node_masks,
@ir.hot_region_catch_list_ref(func, node_id),
active_catch_depth,
Some(node_id),
external_roots,
local_order,
)
_ => false
}
child_changed || region_changed
}
///|
fn hso_node_may_have_regions(op : @ir.HotOp) -> Bool {
match op {
@ir.HotOp::Block
| @ir.HotOp::Loop
| @ir.HotOp::If
| @ir.HotOp::Try
| @ir.HotOp::TryTable => true
_ => false
}
}
///|
fn hso_region_roots(
func : @ir.HotFunc,
region_ref : @ir.HotRegionRef,
) -> Array[Int32] {
let root_count = @ir.hot_region_root_count(func, region_ref)
let roots : Array[Int32] = Array::make(root_count, 0)
for idx = 0; idx < root_count; idx = idx + 1 {
roots[idx] = @ir.hot_region_get(func, region_ref, idx)
}
roots
}
///|
fn hso_fast_chain_leaf(func : @ir.HotFunc, node_id : Int32) -> Bool {
if node_id < 0 || !@ir.hot_node_is_live(func, node_id) {
return false
}
if @ir.hot_child_count(func, node_id) != 0 {
return false
}
match @ir.hot_node_get(func, node_id).op {
@ir.HotOp::Const | @ir.HotOp::RefNull => true
_ => false
}
}
///|
fn hso_try_fold_pure_plain_struct_set_chain(
func : @ir.HotFunc,
roots : Array[Int32],
idx : Int32,
root_id : Int32,
local_id : Int32,
struct_new_id : Int32,
type_idx : @lib.TypeIdx,
) -> (Array[Int32], Int32)? {
let exact_type_idx = match @ir.hot_node_exact_instr(func, struct_new_id) {
Some(@lib.Instruction::StructNew(exact_type_idx)) => exact_type_idx
_ => return None
}
if exact_type_idx != type_idx {
return None
}
let field_count = @ir.hot_child_count(func, struct_new_id)
if field_count == 0 {
return None
}
for field_idx = 0; field_idx < field_count; field_idx = field_idx + 1 {
if !hso_fast_chain_leaf(
func,
@ir.hot_child_get(func, struct_new_id, field_idx),
) {
return None
}
}
let replacements : Array[Int32] = Array::make(field_count, -1)
let mut next_idx = idx + 1
let mut set_count = 0
while next_idx < roots.length() {
let (field_idx, _, value_id) = match
hso_struct_set_for_local(func, roots[next_idx], local_id, type_idx) {
Some(parts) => parts
None => break
}
if field_idx < 0 || field_idx >= field_count {
return None
}
if !hso_fast_chain_leaf(func, value_id) {
return None
}
replacements[field_idx] = value_id
set_count = set_count + 1
next_idx = next_idx + 1
}
if set_count < 2 {
return None
}
for field_idx = 0; field_idx < field_count; field_idx = field_idx + 1 {
if replacements[field_idx] >= 0 {
@ir.hot_child_set(func, struct_new_id, field_idx, replacements[field_idx])
}
}
Some(([root_id], next_idx))
}
///|
fn hso_try_fold_pure_default_struct_set_chain(
type_cache : HsoTypeCache,
func : @ir.HotFunc,
module_ctx : @ir.HotModuleContext?,
roots : Array[Int32],
idx : Int32,
root_id : Int32,
local_id : Int32,
struct_new_id : Int32,
type_idx : @lib.TypeIdx,
) -> (Array[Int32], Int32)? {
let module_ctx = match module_ctx {
Some(module_ctx) => module_ctx
None => return None
}
let exact_type_idx = match @ir.hot_node_exact_instr(func, struct_new_id) {
Some(@lib.Instruction::StructNewDefault(exact_type_idx)) => exact_type_idx
_ => return None
}
if exact_type_idx != type_idx {
return None
}
let field_count = match
hso_type_struct_field_count(type_cache, module_ctx, type_idx) {
Some(field_count) => field_count
None => return None
}
let replacements : Array[Int32] = Array::make(field_count, -1)
let mut next_idx = idx + 1
let mut set_count = 0
let mut missing_count = field_count
while next_idx < roots.length() {
let (field_idx, _, value_id) = match
hso_struct_set_for_local(func, roots[next_idx], local_id, type_idx) {
Some(parts) => parts
None => break
}
if field_idx < 0 || field_idx >= field_count {
return None
}
if !hso_fast_chain_leaf(func, value_id) {
return None
}
if replacements[field_idx] < 0 {
missing_count = missing_count - 1
}
replacements[field_idx] = value_id
set_count = set_count + 1
next_idx = next_idx + 1
}
if set_count < 2 {
return None
}
let children : Array[Int32] = if missing_count == 0 {
replacements
} else {
let field_types = match
hso_type_defaultable_struct_field_types(type_cache, module_ctx, type_idx) {
Some(field_types) => field_types
None => return None
}
let children : Array[Int32] = Array::make(field_count, 0)
for field_idx = 0; field_idx < field_count; field_idx = field_idx + 1 {
if replacements[field_idx] >= 0 {
children[field_idx] = replacements[field_idx]
} else {
match hso_build_default_value(func, field_types[field_idx]) {
Some(default_id) => children[field_idx] = default_id
None => return None
}
}
}
children
}
@ir.hot_node_exact_instr_set(
func,
struct_new_id,
@lib.Instruction::struct_new(type_idx),
)
ignore(@ir.hot_replace_child_span(func, struct_new_id, children))
Some(([root_id], next_idx))
}
///|
fn hso_plain_local_set_struct_new_fast(
func : @ir.HotFunc,
node_id : Int32,
) -> (Int32, Int32, @lib.TypeIdx, 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::LocalSet => ()
_ => return None
}
if @ir.hot_child_count(func, node_id) != 1 {
return None
}
let struct_new_id = @ir.hot_child_get(func, node_id, 0)
let type_idx = match @ir.hot_node_exact_instr(func, struct_new_id) {
Some(@lib.Instruction::StructNew(type_idx)) => type_idx
_ => return None
}
let field_count = @ir.hot_child_count(func, struct_new_id)
if field_count == 0 {
return None
}
for field_idx = 0; field_idx < field_count; field_idx = field_idx + 1 {
if !hso_fast_chain_leaf(
func,
@ir.hot_child_get(func, struct_new_id, field_idx),
) {
return None
}
}
Some((node.imm0, struct_new_id, type_idx, field_count))
}
///|
fn hso_try_fold_tiny_pure_plain_region(
func : @ir.HotFunc,
region_ref : @ir.HotRegionRef,
) -> Bool {
if @ir.hot_region_root_count(func, region_ref) != 3 {
return false
}
let root0 = @ir.hot_region_get(func, region_ref, 0)
let root1 = @ir.hot_region_get(func, region_ref, 1)
let root2 = @ir.hot_region_get(func, region_ref, 2)
let (local_id, struct_new_id, type_idx, field_count) = match
hso_plain_local_set_struct_new_fast(func, root0) {
Some(parts) => parts
None => return false
}
let (field_idx1, _, value_id1) = match
hso_struct_set_for_local(func, root1, local_id, type_idx) {
Some(parts) => parts
None => return false
}
let (field_idx2, _, value_id2) = match
hso_struct_set_for_local(func, root2, local_id, type_idx) {
Some(parts) => parts
None => return false
}
if field_idx1 < 0 ||
field_idx1 >= field_count ||
field_idx2 < 0 ||
field_idx2 >= field_count {
return false
}
if !hso_fast_chain_leaf(func, value_id1) ||
!hso_fast_chain_leaf(func, value_id2) {
return false
}
@ir.hot_child_set(func, struct_new_id, field_idx1, value_id1)
@ir.hot_child_set(func, struct_new_id, field_idx2, value_id2)
ignore(@ir.hot_region_splice(func, region_ref, 1, 2, []))
true
}
///|
fn hso_chain_scratch_capacity(remaining_root_count : Int32) -> Int32 {
if remaining_root_count <= 8 {
remaining_root_count
} else {
8
}
}
///|
fn hso_process_local_set_chain(
cache : HsoPredicateCache,
type_cache : HsoTypeCache,
func : @ir.HotFunc,
module_ctx : @ir.HotModuleContext?,
node_masks : Array[UInt32],
roots : Array[Int32],
root_id : Int32,
idx : Int32,
local_id : Int32,
struct_new_id : Int32,
type_idx : @lib.TypeIdx,
active_catch_depth : Int32,
owner_id : Int32?,
external_roots : HsoExternalRoots,
local_order : HsoLocalOrderWorkspace,
) -> (Array[Int32], Int32, Bool) {
match
hso_try_fold_pure_plain_struct_set_chain(
func, roots, idx, root_id, local_id, struct_new_id, type_idx,
) {
Some((segment_roots, next_idx)) => return (segment_roots, next_idx, true)
None => ()
}
match
hso_try_fold_pure_default_struct_set_chain(
type_cache, func, module_ctx, roots, idx, root_id, local_id, struct_new_id,
type_idx,
) {
Some((segment_roots, next_idx)) => return (segment_roots, next_idx, true)
None => ()
}
let scratch_capacity = hso_chain_scratch_capacity(roots.length() - idx)
let segment_roots : Array[Int32] = Array::new(capacity=scratch_capacity)
segment_roots.push(root_id)
hso_local_order_workspace_begin(local_order)
let mut segment_local_set_idx = 0
let mut segment_has_swapped_prefix = false
let mut changed = false
let mut next_idx = idx + 1
while next_idx < roots.length() {
match hso_struct_set_for_local(func, roots[next_idx], local_id, type_idx) {
Some((field_idx, ref_id, value_id)) => {
if segment_has_swapped_prefix {
let ref_node = @ir.hot_node_get(func, ref_id)
let ref_local = match ref_node.op {
@ir.HotOp::LocalGet => ref_node.imm0
_ => break
}
if ref_local < 0 ||
ref_local >= local_order.maxima.length() ||
hso_local_order_workspace_get(local_order, ref_local) > ref_id {
break
}
}
match
hso_try_fold_into_struct_new(
cache, type_cache, func, module_ctx, node_masks, struct_new_id, type_idx,
field_idx, value_id, local_id, active_catch_depth, owner_id, external_roots,
) {
Some(lifted_prefix_roots) => {
let nop_id = @ir.hot_build_nop(func)
if segment_has_swapped_prefix {
hso_array_insert_prefix_roots(
segment_roots,
segment_local_set_idx,
[nop_id],
1,
)
segment_local_set_idx = segment_local_set_idx + 1
}
if !lifted_prefix_roots.is_empty() {
hso_array_insert_prefix_roots(
segment_roots,
segment_local_set_idx,
lifted_prefix_roots,
lifted_prefix_roots.length(),
)
segment_local_set_idx = segment_local_set_idx +
lifted_prefix_roots.length()
}
changed = true
if !segment_has_swapped_prefix {
segment_roots.push(nop_id)
}
next_idx = next_idx + 1
continue
}
None => ()
}
}
None => ()
}
if next_idx < roots.length() - 1 {
let branch_skips_owner = match owner_id {
Some(_) =>
hso_subtree_skip_is_branch_only(func, roots[next_idx]) &&
!hso_subtree_has_catchable_try_table_escape(func, roots[next_idx]) &&
!hso_external_roots_touch_local(func, external_roots, local_id)
None => false
}
let next_is_matching_set = match
hso_struct_set_for_local(func, roots[next_idx + 1], local_id, type_idx) {
Some(_) => true
None => false
}
// The owner-skipping shortcut may bypass constructor evaluation. Only
// effect-free operands can bypass the ordinary dependency checks.
if branch_skips_owner &&
next_is_matching_set &&
(
(
hso_struct_new_operand_effect_mask(
func, module_ctx, node_masks, struct_new_id,
) |
hso_struct_new_shallow_effects(func, struct_new_id)
) ==
0U ||
hso_root_can_swap_before_local_struct_new(
cache,
func,
module_ctx,
node_masks,
struct_new_id,
roots[next_idx],
local_id,
)
) {
segment_roots.push(roots[next_idx])
let last_idx = segment_roots.length() - 1
let moved_local_set_id = segment_roots[segment_local_set_idx]
segment_roots[segment_local_set_idx] = segment_roots[last_idx]
segment_roots[last_idx] = moved_local_set_id
hso_note_swapped_readonly_root(
cache,
func,
node_masks,
roots[next_idx],
local_order,
)
segment_local_set_idx = last_idx
segment_has_swapped_prefix = true
changed = true
next_idx = next_idx + 1
continue
}
match
hso_liftable_swapped_block_roots(
cache,
func,
module_ctx,
node_masks,
struct_new_id,
roots[next_idx],
local_id,
) {
Some(lifted_roots) => {
hso_array_insert_prefix_roots(
segment_roots,
segment_local_set_idx,
lifted_roots,
lifted_roots.length(),
)
for lifted_root_id in lifted_roots {
hso_note_swapped_readonly_root(
cache, func, node_masks, lifted_root_id, local_order,
)
}
segment_local_set_idx = segment_local_set_idx + lifted_roots.length()
segment_has_swapped_prefix = true
changed = true
next_idx = next_idx + 1
continue
}
None =>
if hso_root_can_swap_before_local_struct_new(
cache,
func,
module_ctx,
node_masks,
struct_new_id,
roots[next_idx],
local_id,
) {
segment_roots.push(roots[next_idx])
let last_idx = segment_roots.length() - 1
let moved_local_set_id = segment_roots[segment_local_set_idx]
segment_roots[segment_local_set_idx] = segment_roots[last_idx]
segment_roots[last_idx] = moved_local_set_id
hso_note_swapped_readonly_root(
cache,
func,
node_masks,
roots[next_idx],
local_order,
)
segment_local_set_idx = last_idx
segment_has_swapped_prefix = true
changed = true
next_idx = next_idx + 1
continue
}
}
}
break
}
(segment_roots, next_idx, changed)
}
///|
fn hso_process_region(
cache : HsoPredicateCache,
type_cache : HsoTypeCache,
func : @ir.HotFunc,
module_ctx : @ir.HotModuleContext?,
node_masks : Array[UInt32],
region_ref : @ir.HotRegionRef,
active_catch_depth : Int32,
owner_id : Int32?,
external_roots : HsoExternalRoots,
local_order : HsoLocalOrderWorkspace,
) -> Bool {
if hso_try_fold_tiny_pure_plain_region(func, region_ref) {
return true
}
let roots = hso_region_roots(func, region_ref)
let mut changed = false
for idx = 0; idx < roots.length(); idx = idx + 1 {
let root_id = roots[idx]
// The nested-region descent can only do work for control-containing roots.
// Keep straight-line roots out before constructing suffix/external context.
if (
hso_effects_for_node(func, node_masks, root_id) &
@ir.EFFECT_MASK_CONTROL
) !=
0U {
hso_external_roots_push(external_roots, roots, idx + 1)
changed = hso_process_node_regions(
cache, type_cache, func, module_ctx, node_masks, root_id, active_catch_depth,
external_roots, local_order,
) ||
changed
hso_external_roots_pop(external_roots)
}
}
let next_roots : Array[Int32] = Array::new(capacity=roots.length())
let mut idx = 0
while idx < roots.length() {
let root_id = roots[idx]
let root_node = @ir.hot_node_get(func, root_id)
match root_node.op {
@ir.HotOp::Heap | @ir.HotOp::Atomic => {
let tee_local_id = hso_tee_wrapped_struct_set_local_id(func, root_id)
let skip_tee_fold = match tee_local_id {
Some(local_id) =>
hso_tee_fold_has_adjacent_readonly_prefix(
cache, func, node_masks, roots, idx, local_id,
)
None => false
}
if skip_tee_fold {
next_roots.push(root_id)
} else {
match
hso_try_fold_tee_wrapped_struct_set(
cache, type_cache, func, module_ctx, node_masks, root_id, active_catch_depth,
) {
Some(replacement_roots) => {
changed = true
let replacement_last_idx = replacement_roots.length() - 1
let replacement_local_set_id = replacement_roots[replacement_last_idx]
for replacement_idx = 0
replacement_idx < replacement_last_idx
replacement_idx = replacement_idx + 1 {
next_roots.push(replacement_roots[replacement_idx])
}
match
hso_local_set_struct_new(
type_cache, func, module_ctx, replacement_local_set_id,
) {
Some((local_id, struct_new_id, type_idx)) => {
let (segment_roots, next_idx, segment_changed) = hso_process_local_set_chain(
cache, type_cache, func, module_ctx, node_masks, roots, replacement_local_set_id,
idx, local_id, struct_new_id, type_idx, active_catch_depth, owner_id,
external_roots, local_order,
)
changed = segment_changed || changed
for segment_root_id in segment_roots {
next_roots.push(segment_root_id)
}
idx = next_idx - 1
}
None => next_roots.push(replacement_local_set_id)
}
}
None => next_roots.push(root_id)
}
}
idx = idx + 1
}
@ir.HotOp::LocalSet =>
match hso_local_set_struct_new(type_cache, func, module_ctx, root_id) {
Some((local_id, struct_new_id, type_idx)) => {
let (segment_roots, next_idx, segment_changed) = hso_process_local_set_chain(
cache, type_cache, func, module_ctx, node_masks, roots, root_id, idx,
local_id, struct_new_id, type_idx, active_catch_depth, owner_id, external_roots,
local_order,
)
changed = segment_changed || changed
for segment_root_id in segment_roots {
next_roots.push(segment_root_id)
}
idx = next_idx
}
None => {
next_roots.push(root_id)
idx = idx + 1
}
}
_ => {
next_roots.push(root_id)
idx = idx + 1
}
}
}
if !@lib.equals(roots, next_roots) {
ignore(
@ir.hot_region_splice(func, region_ref, 0, roots.length(), next_roots),
)
changed = true
}
changed
}
///|
fn heap_store_optimization_run(
ctx : HotPassContext,
func : @ir.HotFunc,
) -> HotPassResult {
if hso_try_fold_tiny_pure_plain_region(func, @ir.HotRegionRef::root()) {
pass_mark_mutated(ctx, func)
return HotPassResult::changed()
}
let effects = pass_require_effects(ctx, func)
let predicate_cache = hso_new_predicate_cache()
let type_cache = hso_new_type_cache()
let revision = @ir.hot_revision_current(func)
let changed = hso_process_region(
predicate_cache,
type_cache,
func,
ctx.module_ctx,
effects.node_masks,
@ir.HotRegionRef::root(),
0,
None,
hso_new_external_roots(),
hso_new_local_order_workspace(@ir.hot_local_count(func)),
)
if changed || @ir.hot_revision_current(func) != revision {
pass_mark_mutated(ctx, func)
HotPassResult::changed()
} else {
HotPassResult::unchanged()
}
}