// 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 2022 WebAssembly Community Group participants
// Counterpart: src/passes/OptimizeCasts.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 optimize_casts_descriptor() -> @ir.HotPassDescriptor {
@ir.HotPassDescriptor::new("optimize-casts", 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 optimize_casts_summary() -> String {
"Remove provably redundant GC casts and fold ref.test outcomes from static reference types."
}
///|
fn oc_abs_heap_matches(
source : @lib.AbsHeapType,
target : @lib.AbsHeapType,
) -> Bool {
if source == target {
return true
}
match (source, target) {
(@lib.NoneAbsHeapType, @lib.StructAbsHeapType)
| (@lib.NoneAbsHeapType, @lib.ArrayAbsHeapType)
| (@lib.NoneAbsHeapType, @lib.I31AbsHeapType)
| (@lib.NoneAbsHeapType, @lib.EqAbsHeapType)
| (@lib.NoneAbsHeapType, @lib.AnyAbsHeapType)
| (@lib.NoFuncAbsHeapType, @lib.FuncAbsHeapType)
| (@lib.NoContAbsHeapType, @lib.ContAbsHeapType)
| (@lib.NoWaitqueueAbsHeapType, @lib.WaitqueueAbsHeapType)
| (@lib.StringAbsHeapType, @lib.ExternAbsHeapType)
| (@lib.NoExternAbsHeapType, @lib.ExternAbsHeapType)
| (@lib.NoExternAbsHeapType, @lib.StringAbsHeapType)
| (@lib.NoExnAbsHeapType, @lib.ExnAbsHeapType)
| (@lib.I31AbsHeapType, @lib.EqAbsHeapType)
| (@lib.I31AbsHeapType, @lib.AnyAbsHeapType)
| (@lib.StructAbsHeapType, @lib.EqAbsHeapType)
| (@lib.StructAbsHeapType, @lib.AnyAbsHeapType)
| (@lib.ArrayAbsHeapType, @lib.EqAbsHeapType)
| (@lib.ArrayAbsHeapType, @lib.AnyAbsHeapType)
| (@lib.EqAbsHeapType, @lib.AnyAbsHeapType) => true
_ => false
}
}
///|
fn oc_comptype_matches_abs(ct : @lib.CompType, abs : @lib.AbsHeapType) -> Bool {
match ct {
@lib.StructCompType(_) =>
abs == @lib.AbsHeapType::struct_() ||
abs == @lib.AbsHeapType::eq() ||
abs == @lib.AbsHeapType::any()
@lib.ArrayCompType(_) =>
abs == @lib.AbsHeapType::array() ||
abs == @lib.AbsHeapType::eq() ||
abs == @lib.AbsHeapType::any()
@lib.FuncCompType(_, _) =>
abs == @lib.AbsHeapType::func() || abs == @lib.AbsHeapType::any()
@lib.ContCompType(_) => abs == @lib.AbsHeapType::cont()
}
}
///|
fn oc_subtype_as_heap_matches(
module_ctx : @ir.HotModuleContext,
subtype : @lib.SubType,
target : @lib.HeapType,
fuel : Int32,
) -> Bool {
if fuel <= 0 {
return false
}
let (is_final, supers, ct) = match subtype {
@lib.CompTypeSubType(_, ct) => (false, [], ct)
@lib.SubType(is_final, supers, _, ct) => (is_final, supers, ct)
}
match target {
@lib.AbsHeapTypeHeapType(abs) => {
if subtype.metadata().is_shared() {
return false
}
if oc_comptype_matches_abs(ct, abs) {
return true
}
if is_final {
return false
}
}
@lib.SharedAbsHeapTypeHeapType(abs) => {
if !subtype.metadata().is_shared() {
return false
}
if oc_comptype_matches_abs(ct, abs) {
return true
}
if is_final {
return false
}
}
_ => ()
}
for super_idx in supers {
if oc_heap_matches(
module_ctx,
@lib.HeapType::new(super_idx),
target,
fuel - 1,
) {
return true
}
}
false
}
///|
fn oc_heap_identical(
module_ctx : @ir.HotModuleContext,
source : @lib.HeapType,
target : @lib.HeapType,
) -> Bool {
if source == target {
return true
}
match (source, target) {
(@lib.HeapType(_), @lib.HeapType(_)) => {
let base = @validate.Env::new().with_types(module_ctx.subtypes)
let env = @validate.Env::{ ..base, rec_stack: module_ctx.type_groups, }
@validate.Match::matches(
@lib.RefType::new(false, source, exact=true),
@lib.RefType::new(false, target, exact=true),
env,
)
}
_ => false
}
}
///|
fn oc_heap_matches(
module_ctx : @ir.HotModuleContext,
source : @lib.HeapType,
target : @lib.HeapType,
fuel : Int32,
) -> Bool {
if oc_heap_identical(module_ctx, source, target) {
return true
}
if fuel <= 0 {
return false
}
match (source, target) {
(@lib.AbsHeapTypeHeapType(source_abs), @lib.AbsHeapTypeHeapType(target_abs)) =>
oc_abs_heap_matches(source_abs, target_abs)
(
@lib.SharedAbsHeapTypeHeapType(source_abs),
@lib.SharedAbsHeapTypeHeapType(target_abs),
) => oc_abs_heap_matches(source_abs, target_abs)
(@lib.HeapType(source_idx), _) =>
match @ir.hot_module_resolve_subtype(module_ctx, source_idx) {
Some(subtype) =>
oc_subtype_as_heap_matches(module_ctx, subtype, target, fuel - 1)
None => false
}
(_, @lib.HeapType(target_idx)) =>
match source {
@lib.AbsHeapTypeHeapType(@lib.NoneAbsHeapType) =>
match @ir.hot_module_resolve_subtype(module_ctx, target_idx) {
Some(subtype) =>
match subtype.get_comptype() {
@lib.StructCompType(_) | @lib.ArrayCompType(_) => true
_ => false
}
None => false
}
@lib.AbsHeapTypeHeapType(@lib.NoFuncAbsHeapType) =>
match @ir.hot_module_resolve_subtype(module_ctx, target_idx) {
Some(subtype) =>
match subtype.get_comptype() {
@lib.FuncCompType(_, _) => true
_ => false
}
None => false
}
_ => false
}
_ => false
}
}
///|
fn oc_ref_type_from_node(func : @ir.HotFunc, node_id : Int32) -> @lib.RefType? {
let results = @ir.hot_type_results(func, @ir.hot_node_type(func, node_id))
if results.length() != 1 {
return None
}
match results[0] {
@lib.RefTypeValType(ref_type) => Some(ref_type)
_ => None
}
}
///|
fn oc_refinement_type_from_node(
func : @ir.HotFunc,
node_id : Int32,
) -> @lib.RefType? {
match @ir.hot_node_exact_instr(func, node_id) {
Some(@lib.Instruction::RefCast(_, _, _))
| Some(@lib.Instruction::RefAsNonNull) =>
oc_ref_type_from_node(func, node_id)
_ => {
let node = @ir.hot_node_get(func, node_id)
match node.op {
@ir.HotOp::LocalTee =>
if @ir.hot_child_count(func, node_id) == 1 {
let child_id = @ir.hot_child_get(func, node_id, 0)
match @ir.hot_node_exact_instr(func, child_id) {
Some(@lib.Instruction::RefCast(_, _, _))
| Some(@lib.Instruction::RefAsNonNull) =>
oc_ref_type_from_node(func, child_id)
_ => oc_ref_type_from_node(func, node_id)
}
} else {
oc_ref_type_from_node(func, node_id)
}
_ => oc_ref_type_from_node(func, node_id)
}
}
}
}
///|
fn oc_target_ref_type(
nullable : Bool,
exact : Bool,
heap_type : @lib.HeapType,
) -> @lib.RefType {
@lib.RefType::new(nullable, heap_type, exact~)
}
///|
fn oc_ref_always_matches(
module_ctx : @ir.HotModuleContext,
source : @lib.RefType,
target : @lib.RefType,
) -> Bool {
if source.is_nullable() && !target.is_nullable() {
return false
}
if target.is_exact() {
return source.is_exact() &&
oc_heap_identical(
module_ctx,
source.get_heap_type(),
target.get_heap_type(),
)
}
oc_heap_matches(
module_ctx,
source.get_heap_type(),
target.get_heap_type(),
512,
)
}
///|
fn oc_comptype_may_overlap(a : @lib.CompType, b : @lib.CompType) -> Bool {
match (a, b) {
(@lib.StructCompType(_), @lib.StructCompType(_)) => true
(@lib.ArrayCompType(_), @lib.ArrayCompType(_)) => true
(@lib.FuncCompType(_, _), @lib.FuncCompType(_, _)) => true
(@lib.ContCompType(_), @lib.ContCompType(_)) => true
_ => false
}
}
///|
fn oc_heap_never_matches(
module_ctx : @ir.HotModuleContext,
source : @lib.HeapType,
target : @lib.HeapType,
) -> Bool {
if oc_heap_matches(module_ctx, source, target, 512) {
return false
}
match (source, target) {
(@lib.AbsHeapTypeHeapType(source_abs), @lib.AbsHeapTypeHeapType(target_abs)) =>
!oc_abs_heap_matches(source_abs, target_abs) &&
!oc_abs_heap_matches(target_abs, source_abs)
(@lib.HeapType(source_idx), @lib.HeapType(target_idx)) =>
match
(
@ir.hot_module_resolve_subtype(module_ctx, source_idx),
@ir.hot_module_resolve_subtype(module_ctx, target_idx),
) {
(Some(source_subtype), Some(target_subtype)) =>
!oc_comptype_may_overlap(
source_subtype.get_comptype(),
target_subtype.get_comptype(),
)
_ => false
}
(@lib.HeapType(source_idx), @lib.AbsHeapTypeHeapType(target_abs)) =>
match @ir.hot_module_resolve_subtype(module_ctx, source_idx) {
Some(source_subtype) =>
!oc_comptype_matches_abs(source_subtype.get_comptype(), target_abs)
None => false
}
(@lib.AbsHeapTypeHeapType(source_abs), @lib.HeapType(target_idx)) =>
match @ir.hot_module_resolve_subtype(module_ctx, target_idx) {
Some(target_subtype) =>
!oc_comptype_matches_abs(target_subtype.get_comptype(), source_abs)
None => false
}
_ => false
}
}
///|
fn oc_ref_never_matches(
module_ctx : @ir.HotModuleContext,
source : @lib.RefType,
target : @lib.RefType,
) -> Bool {
if source.is_nullable() && target.is_nullable() {
return false
}
if target.is_exact() {
return source.is_exact() &&
!oc_heap_identical(
module_ctx,
source.get_heap_type(),
target.get_heap_type(),
)
}
if source.is_exact() &&
!oc_heap_matches(
module_ctx,
source.get_heap_type(),
target.get_heap_type(),
512,
) {
return true
}
oc_heap_never_matches(
module_ctx,
source.get_heap_type(),
target.get_heap_type(),
)
}
///|
fn oc_replace_with_child(
ctx : HotPassContext,
func : @ir.HotFunc,
node_id : Int32,
) -> Unit {
if @ir.hot_child_count(func, node_id) != 1 {
return
}
let child_id = @ir.hot_child_get(func, node_id, 0)
pass_replace_node(
ctx,
func,
node_id,
@ir.hot_node_get(func, child_id),
preserve_value_order=true,
)
}
///|
fn oc_is_known_null_value(
func : @ir.HotFunc,
node_id : Int32,
depth : Int32,
) -> Bool {
if depth <= 0 || !@ir.hot_node_is_live(func, node_id) {
return false
}
match @ir.hot_node_get(func, node_id).op {
@ir.HotOp::RefNull => true
@ir.HotOp::Block => {
if !oc_block_is_plain_fallthrough(func, node_id) {
return false
}
let body = @ir.hot_region_body_ref(func, node_id)
let root_count = @ir.hot_region_root_count(func, body)
if root_count == 0 {
return false
}
oc_is_known_null_value(
func,
@ir.hot_region_get(func, body, root_count - 1),
depth - 1,
)
}
_ => false
}
}
///|
fn oc_replace_nullable_known_null_desc_eq(
ctx : HotPassContext,
func : @ir.HotFunc,
node_id : Int32,
target_ref : @lib.RefType,
) -> Bool {
if !target_ref.is_nullable() || @ir.hot_child_count(func, node_id) != 2 {
return false
}
let source_id = @ir.hot_child_get(func, node_id, 0)
if !oc_is_known_null_value(func, source_id, 64) {
return false
}
let descriptor_id = @ir.hot_child_get(func, node_id, 1)
let descriptor_eval = match oc_ref_type_from_node(func, descriptor_id) {
Some(descriptor_ref) if descriptor_ref.is_nullable() => {
let checked_ref = @lib.RefType::new(
false,
descriptor_ref.get_heap_type(),
exact=descriptor_ref.is_exact(),
)
@ir.hot_build_heap(
func,
[@lib.ValType::ref_type(checked_ref)],
@lib.Instruction::ref_as_non_null(),
[descriptor_id],
)
}
Some(_) => descriptor_id
None => return false
}
let typed_null = @ir.hot_build_ref_null(func, target_ref)
let temp = @ir.hot_build_block(
func,
@lib.BlockType::val_type(@lib.ValType::ref_type(target_ref)),
[
@ir.hot_build_drop(func, source_id),
@ir.hot_build_drop(func, descriptor_eval),
typed_null,
],
)
let replacement = @ir.hot_node_get(func, temp)
@ir.hot_delete_node(func, temp)
pass_replace_node(ctx, func, node_id, replacement)
true
}
///|
fn oc_replace_with_const_i32(
ctx : HotPassContext,
func : @ir.HotFunc,
node_id : Int32,
value : Int32,
) -> Unit {
let roots : Array[Int32] = []
for child in oc_node_children(func, node_id) {
let pure_read = match @ir.hot_node_get(func, child).op {
@ir.HotOp::LocalGet
| @ir.HotOp::GlobalGet
| @ir.HotOp::RefNull
| @ir.HotOp::RefFunc => true
_ =>
@ir.effects_is_pure(@ir.effects_for_node(func, child)) &&
!@ir.hot_may_trap(func, child)
}
if !pure_read {
roots.push(@ir.hot_build_drop(func, child))
}
}
roots.push(@ir.hot_build_const_i32(func, @lib.I32(value)))
let temp = if roots.length() == 1 {
roots[0]
} else {
@ir.hot_build_block(
func,
@lib.BlockType::val_type(@lib.ValType::i32()),
roots,
)
}
let replacement = @ir.hot_node_get(func, temp)
@ir.hot_delete_node(func, temp)
pass_replace_node(ctx, func, node_id, replacement)
}
///|
fn oc_replace_with_unconditional_br(
ctx : HotPassContext,
func : @ir.HotFunc,
node_id : Int32,
) -> Unit {
let node = @ir.hot_node_get(func, node_id)
let values : Array[Int32] = []
for i = 0; i < @ir.hot_child_count(func, node_id); i = i + 1 {
values.push(@ir.hot_child_get(func, node_id, i))
}
let temp = @ir.hot_build_br(func, node.imm0, values~)
let replacement = @ir.hot_node_get(func, temp)
@ir.hot_delete_node(func, temp)
pass_replace_node(ctx, func, node_id, replacement)
}
///|
fn oc_node_children(func : @ir.HotFunc, node_id : Int32) -> Array[Int32] {
let children : Array[Int32] = []
for i = 0; i < @ir.hot_child_count(func, node_id); i = i + 1 {
children.push(@ir.hot_child_get(func, node_id, i))
}
children
}
///|
fn oc_node_is_local_get(
func : @ir.HotFunc,
node_id : Int32,
local_id : Int32,
) -> Bool {
let node = @ir.hot_node_get(func, node_id)
match node.op {
@ir.HotOp::LocalGet => node.imm0 == local_id
_ => false
}
}
///|
fn oc_collect_local_get_counts(
func : @ir.HotFunc,
node_id : Int32,
counts : Array[Int32],
touched : Array[Int32],
) -> Unit {
if !@ir.hot_node_is_live(func, node_id) {
return
}
let node = @ir.hot_node_get(func, node_id)
if node.op is @ir.HotOp::LocalGet &&
node.imm0 >= 0 &&
node.imm0 < counts.length() {
if counts[node.imm0] == 0 {
touched.push(node.imm0)
}
counts[node.imm0] += 1
}
for slot = 0; slot < @ir.hot_child_count(func, node_id); slot = slot + 1 {
let child_id = @ir.hot_child_get(func, node_id, slot)
oc_collect_local_get_counts(func, child_id, counts, touched)
}
}
///|
/// Test helper for measuring a single-local walk independently of batching.
fn oc_count_local_gets(
func : @ir.HotFunc,
node_id : Int32,
local_id : Int32,
) -> Int32 {
if !@ir.hot_node_is_live(func, node_id) {
return 0
}
let mut count = if oc_node_is_local_get(func, node_id, local_id) {
1
} else {
0
}
for slot = 0; slot < @ir.hot_child_count(func, node_id); slot = slot + 1 {
count += oc_count_local_gets(
func,
@ir.hot_child_get(func, node_id, slot),
local_id,
)
}
count
}
///|
// Restore a proven non-null result only when its storage was weakened.
fn oc_restore_capture_type(
func : @ir.HotFunc,
value_id : Int32,
ref_type : @lib.RefType,
) -> Int32 {
if ref_type.is_nullable() {
return value_id
}
match oc_ref_type_from_node(func, value_id) {
Some(value_type) if value_type == ref_type => value_id
_ =>
@ir.hot_build_heap(
func,
[@lib.ValType::ref_type(ref_type)],
@lib.Instruction::ref_as_non_null(),
[value_id],
)
}
}
///|
fn oc_retarget_local_gets_from_map(
ctx : HotPassContext,
func : @ir.HotFunc,
node_id : Int32,
retarget : Array[Bool],
fresh_locals : Array[Int32],
best_casts : Array[Int32],
) -> Int32 {
if !@ir.hot_node_is_live(func, node_id) {
return 0
}
let mut count = 0
let node = @ir.hot_node_get(func, node_id)
if node.op is @ir.HotOp::LocalGet &&
node.imm0 >= 0 &&
node.imm0 < retarget.length() &&
retarget[node.imm0] {
let fresh_local = fresh_locals[node.imm0]
if fresh_local >= 0 {
let ref_type = match oc_ref_type_from_node(func, best_casts[node.imm0]) {
Some(ref_type) => ref_type
None => return 0
}
let temp = oc_restore_capture_type(
func,
@ir.hot_build_local_get(func, fresh_local),
ref_type,
)
let replacement = @ir.hot_node_get(func, temp)
@ir.hot_delete_node(func, temp)
pass_replace_node(ctx, func, node_id, replacement)
count += 1
}
}
for slot = 0; slot < @ir.hot_child_count(func, node_id); slot = slot + 1 {
let child_id = @ir.hot_child_get(func, node_id, slot)
count += oc_retarget_local_gets_from_map(
ctx, func, child_id, retarget, fresh_locals, best_casts,
)
}
count
}
///|
/// Test helper for measuring a single-local walk independently of batching.
fn oc_retarget_local_gets(
ctx : HotPassContext,
func : @ir.HotFunc,
node_id : Int32,
local_id : Int32,
fresh_local : Int32,
) -> Int32 {
if !@ir.hot_node_is_live(func, node_id) {
return 0
}
let mut count = 0
if oc_node_is_local_get(func, node_id, local_id) {
let temp = @ir.hot_build_local_get(func, fresh_local)
let replacement = @ir.hot_node_get(func, temp)
@ir.hot_delete_node(func, temp)
pass_replace_node(ctx, func, node_id, replacement)
count += 1
}
for slot = 0; slot < @ir.hot_child_count(func, node_id); slot = slot + 1 {
count += oc_retarget_local_gets(
ctx,
func,
@ir.hot_child_get(func, node_id, slot),
local_id,
fresh_local,
)
}
count
}
///|
fn oc_collect_written_locals(
func : @ir.HotFunc,
node_id : Int32,
writes : Array[Int32],
write_mask : Array[Bool],
) -> Unit {
if !@ir.hot_node_is_live(func, node_id) {
return
}
let node = @ir.hot_node_get(func, node_id)
match node.op {
@ir.HotOp::LocalSet | @ir.HotOp::LocalTee =>
if node.imm0 >= 0 &&
node.imm0 < write_mask.length() &&
!write_mask[node.imm0] {
write_mask[node.imm0] = true
writes.push(node.imm0)
}
_ => ()
}
for slot = 0; slot < @ir.hot_child_count(func, node_id); slot = slot + 1 {
let child_id = @ir.hot_child_get(func, node_id, slot)
oc_collect_written_locals(func, child_id, writes, write_mask)
}
}
///|
fn oc_source_local_requires_nullable_check(
func : @ir.HotFunc,
source_id : Int32,
requires_nullable_source : Bool,
) -> Bool {
if !requires_nullable_source {
return true
}
match oc_ref_type_from_node(func, source_id) {
Some(ref_type) => ref_type.is_nullable()
None => false
}
}
///|
fn oc_plain_block_source_local(
func : @ir.HotFunc,
block_id : Int32,
requires_nullable_source : Bool,
) -> Int32? {
if !oc_block_is_plain_fallthrough(func, block_id) {
return None
}
let body = @ir.hot_region_body_ref(func, block_id)
let root_count = @ir.hot_region_root_count(func, body)
if root_count <= 0 {
return None
}
for idx = 0; idx < root_count - 1; idx = idx + 1 {
let prefix_root = @ir.hot_region_get(func, body, idx)
if !oc_dropped_nontrapping_pure_root(func, prefix_root) {
return None
}
}
let root_id = @ir.hot_region_get(func, body, root_count - 1)
let root = @ir.hot_node_get(func, root_id)
match root.op {
@ir.HotOp::LocalGet =>
if oc_source_local_requires_nullable_check(
func, root_id, requires_nullable_source,
) {
Some(root.imm0)
} else {
None
}
_ => None
}
}
///|
fn oc_refinement_child_source_local(
func : @ir.HotFunc,
child_id : Int32,
requires_nullable_source : Bool,
) -> Int32? {
let child = @ir.hot_node_get(func, child_id)
match child.op {
@ir.HotOp::LocalGet | @ir.HotOp::LocalTee =>
if oc_source_local_requires_nullable_check(
func, child_id, requires_nullable_source,
) {
Some(child.imm0)
} else {
None
}
@ir.HotOp::Block =>
oc_plain_block_source_local(func, child_id, requires_nullable_source)
_ => None
}
}
///|
fn oc_is_nested_ref_as_nullable_cast(
func : @ir.HotFunc,
node_id : Int32,
) -> Bool {
match @ir.hot_node_exact_instr(func, node_id) {
Some(@lib.Instruction::RefAsNonNull) => ()
_ => return false
}
if @ir.hot_child_count(func, node_id) != 1 {
return false
}
let child_id = @ir.hot_child_get(func, node_id, 0)
match @ir.hot_node_exact_instr(func, child_id) {
Some(@lib.Instruction::RefCast(true, _, _)) => true
_ => false
}
}
///|
fn oc_nested_ref_as_cast_source_local(
func : @ir.HotFunc,
node_id : Int32,
) -> Int32? {
if !oc_is_nested_ref_as_nullable_cast(func, node_id) {
return None
}
let cast_id = @ir.hot_child_get(func, node_id, 0)
if @ir.hot_child_count(func, cast_id) != 1 {
return None
}
let source_id = @ir.hot_child_get(func, cast_id, 0)
oc_refinement_child_source_local(func, source_id, false)
}
///|
fn oc_refinement_source_local(func : @ir.HotFunc, node_id : Int32) -> Int32? {
let requires_nullable_source = match @ir.hot_node_exact_instr(func, node_id) {
Some(@lib.Instruction::RefCast(_, _, _)) => false
Some(@lib.Instruction::RefAsNonNull) => true
_ => return None
}
if @ir.hot_child_count(func, node_id) != 1 {
return None
}
let child_id = @ir.hot_child_get(func, node_id, 0)
match
oc_refinement_child_source_local(func, child_id, requires_nullable_source) {
Some(local_id) => Some(local_id)
None => oc_nested_ref_as_cast_source_local(func, node_id)
}
}
///|
fn oc_early_refinement_source_local(
func : @ir.HotFunc,
node_id : Int32,
) -> Int32? {
let requires_nullable_source = match @ir.hot_node_exact_instr(func, node_id) {
Some(@lib.Instruction::RefCast(_, _, _)) => false
Some(@lib.Instruction::RefAsNonNull) => true
_ => return None
}
if @ir.hot_child_count(func, node_id) != 1 {
return None
}
let child_id = @ir.hot_child_get(func, node_id, 0)
let child = @ir.hot_node_get(func, child_id)
match child.op {
@ir.HotOp::LocalGet =>
if oc_source_local_requires_nullable_check(
func, child_id, requires_nullable_source,
) {
Some(child.imm0)
} else {
None
}
@ir.HotOp::LocalTee =>
if @ir.hot_child_count(func, child_id) == 1 {
let tee_source = @ir.hot_child_get(func, child_id, 0)
let source = @ir.hot_node_get(func, tee_source)
match source.op {
@ir.HotOp::LocalGet =>
if source.imm0 != child.imm0 &&
oc_source_local_requires_nullable_check(
func, tee_source, requires_nullable_source,
) {
Some(source.imm0)
} else {
None
}
_ => None
}
} else {
None
}
_ => oc_nested_ref_as_cast_source_local(func, node_id)
}
}
///|
fn oc_contains_local(locals : Array[Int32], local_id : Int32) -> Bool {
for written in locals {
if written == local_id {
return true
}
}
false
}
///|
fn oc_op_is_structured_control(op : @ir.HotOp) -> Bool {
match op {
@ir.HotOp::Block
| @ir.HotOp::Loop
| @ir.HotOp::If
| @ir.HotOp::Try
| @ir.HotOp::TryTable => true
_ => false
}
}
///|
fn oc_op_is_linear_block_barrier(op : @ir.HotOp) -> Bool {
match op {
@ir.HotOp::Unreachable
| @ir.HotOp::Block
| @ir.HotOp::Loop
| @ir.HotOp::If
| @ir.HotOp::Try
| @ir.HotOp::TryTable
| @ir.HotOp::Br
| @ir.HotOp::BrIf
| @ir.HotOp::BrTable
| @ir.HotOp::BrOnNull
| @ir.HotOp::BrOnNonNull
| @ir.HotOp::BrOnCast
| @ir.HotOp::BrOnCastFail
| @ir.HotOp::Return
| @ir.HotOp::ReturnCall
| @ir.HotOp::ReturnCallIndirect
| @ir.HotOp::ReturnCallRef
| @ir.HotOp::Throw
| @ir.HotOp::Rethrow
| @ir.HotOp::ThrowRef
| @ir.HotOp::Delegate => true
_ => false
}
}
///|
fn oc_node_has_linear_block_barrier(
func : @ir.HotFunc,
node_id : Int32,
) -> Bool {
if !@ir.hot_node_is_live(func, node_id) {
return false
}
let node = @ir.hot_node_get(func, node_id)
if oc_op_is_linear_block_barrier(node.op) {
return true
}
for slot = 0; slot < @ir.hot_child_count(func, node_id); slot = slot + 1 {
let child_id = @ir.hot_child_get(func, node_id, slot)
if oc_node_has_linear_block_barrier(func, child_id) {
return true
}
}
false
}
///|
fn oc_block_is_plain_fallthrough(func : @ir.HotFunc, block_id : Int32) -> Bool {
let body = @ir.hot_region_body_ref(func, block_id)
for idx = 0; idx < @ir.hot_region_root_count(func, body); idx = idx + 1 {
if oc_node_has_linear_block_barrier(
func,
@ir.hot_region_get(func, body, idx),
) {
return false
}
}
true
}
///|
fn oc_plain_fallthrough_block_for_root(
func : @ir.HotFunc,
root_id : Int32,
) -> Int32? {
let root = @ir.hot_node_get(func, root_id)
match root.op {
@ir.HotOp::Block => Some(root_id)
@ir.HotOp::Drop =>
if @ir.hot_child_count(func, root_id) == 1 {
let child_id = @ir.hot_child_get(func, root_id, 0)
match @ir.hot_node_get(func, child_id).op {
@ir.HotOp::Block => Some(child_id)
_ => None
}
} else {
None
}
_ => None
}
}
///|
// A refinement describes the value at the cast, so a later write to the same
// local in this expression kills it. Visit operands in evaluation order.
fn oc_forget_refinements_for_local(
out : Array[(Int32, Int32)],
local_id : Int32,
) -> Unit {
let mut kept = 0
for idx = 0; idx < out.length(); idx = idx + 1 {
if out[idx].0 != local_id {
out[kept] = out[idx]
kept += 1
}
}
while out.length() > kept {
ignore(out.pop())
}
}
///|
fn oc_collect_refinements(
func : @ir.HotFunc,
node_id : Int32,
out : Array[(Int32, Int32)],
) -> Unit {
if !@ir.hot_node_is_live(func, node_id) {
return
}
let node = @ir.hot_node_get(func, node_id)
if oc_op_is_structured_control(node.op) {
return
}
for slot = 0; slot < @ir.hot_child_count(func, node_id); slot = slot + 1 {
let child_id = @ir.hot_child_get(func, node_id, slot)
oc_collect_refinements(func, child_id, out)
}
match node.op {
@ir.HotOp::LocalSet | @ir.HotOp::LocalTee => {
oc_forget_refinements_for_local(out, node.imm0)
if @ir.hot_child_count(func, node_id) == 1 {
let value_id = @ir.hot_child_get(func, node_id, 0)
match @ir.hot_node_exact_instr(func, value_id) {
Some(@lib.RefCast(_, _, _)) | Some(@lib.RefAsNonNull) =>
out.push((node.imm0, value_id))
_ => ()
}
}
}
_ => ()
}
match oc_refinement_source_local(func, node_id) {
Some(local_id) => out.push((local_id, node_id))
None => ()
}
}
///|
fn oc_collect_refinement_tee_source_aliases(
func : @ir.HotFunc,
node_id : Int32,
out : Array[(Int32, Int32)],
) -> Unit {
if !@ir.hot_node_is_live(func, node_id) {
return
}
let node = @ir.hot_node_get(func, node_id)
if oc_op_is_structured_control(node.op) {
return
}
for slot = 0; slot < @ir.hot_child_count(func, node_id); slot = slot + 1 {
let child_id = @ir.hot_child_get(func, node_id, slot)
oc_collect_refinement_tee_source_aliases(func, child_id, out)
}
match node.op {
@ir.HotOp::LocalSet | @ir.HotOp::LocalTee => {
oc_forget_refinements_for_local(out, node.imm0)
return
}
_ => ()
}
let requires_nullable_source = match @ir.hot_node_exact_instr(func, node_id) {
Some(@lib.Instruction::RefCast(_, _, _)) => false
Some(@lib.Instruction::RefAsNonNull) => true
_ => return
}
if @ir.hot_child_count(func, node_id) == 1 {
let child_id = @ir.hot_child_get(func, node_id, 0)
let child = @ir.hot_node_get(func, child_id)
match child.op {
@ir.HotOp::LocalTee =>
if @ir.hot_child_count(func, child_id) == 1 {
let tee_source = @ir.hot_child_get(func, child_id, 0)
let source = @ir.hot_node_get(func, tee_source)
match source.op {
@ir.HotOp::LocalGet =>
if source.imm0 != child.imm0 &&
oc_source_local_requires_nullable_check(
func, tee_source, requires_nullable_source,
) {
out.push((source.imm0, node_id))
}
_ => ()
}
}
_ => ()
}
}
}
///|
fn oc_existing_fresh_can_feed_refinement_source(
func : @ir.HotFunc,
candidate_id : Int32,
current_id : Int32,
) -> Bool {
match
(
oc_refinement_type_from_node(func, candidate_id),
oc_refinement_type_from_node(func, current_id),
) {
(Some(candidate), Some(current)) => candidate == current
_ => false
}
}
///|
fn oc_indexed_refinement_blocks_retarget(
module_ctx : @ir.HotModuleContext,
func : @ir.HotFunc,
refinements : Array[(Int32, Int32)],
refinement_heads : Array[Int32],
refinement_next : Array[Int32],
local_id : Int32,
best_cast : Int32,
has_existing_fresh : Bool,
moved_refinalized_refinements : Array[Int32],
) -> Bool {
let mut at = refinement_heads[local_id]
while at >= 0 {
let refinement = refinements[at]
let can_feed_existing_equal = has_existing_fresh &&
oc_existing_fresh_can_feed_refinement_source(
func,
best_cast,
refinement.1,
)
let can_feed_refinalized_equal = oc_contains_local(
moved_refinalized_refinements,
refinement.1,
) &&
oc_existing_fresh_can_feed_refinement_source(
func,
best_cast,
refinement.1,
)
let can_feed_existing_source = has_existing_fresh &&
oc_ref_cast_source_can_read_best_cast(
module_ctx,
func,
best_cast,
refinement.1,
)
if !oc_best_cast_can_feed_refinement_source(
module_ctx,
func,
best_cast,
refinement.1,
) &&
!can_feed_existing_equal &&
!can_feed_refinalized_equal &&
!can_feed_existing_source {
return true
}
at = refinement_next[at]
}
false
}
///|
fn oc_marked_equal_refinement_source_get(
func : @ir.HotFunc,
refinements : Array[(Int32, Int32)],
local_id : Int32,
best_cast : Int32,
moved_refinalized_refinements : Array[Int32],
) -> Int32? {
for refinement in refinements {
if refinement.0 == local_id &&
oc_contains_local(moved_refinalized_refinements, refinement.1) &&
oc_existing_fresh_can_feed_refinement_source(
func,
best_cast,
refinement.1,
) {
match oc_direct_refinement_source_get(func, refinement.1) {
Some((_, source_get)) => return Some(source_get)
None => ()
}
}
}
None
}
///|
fn oc_ref_type_as_non_null(ref_type : @lib.RefType) -> @lib.RefType {
@lib.RefType::new(false, ref_type.get_heap_type(), exact=ref_type.is_exact())
}
///|
fn oc_refinalize_marked_ref_cast_to_best(
ctx : HotPassContext,
func : @ir.HotFunc,
module_ctx : @ir.HotModuleContext,
root_id : Int32,
best_cast : Int32,
moved_refinalized_refinements : Array[Int32],
) -> Bool {
let cast_id = match oc_single_dropped_child(func, root_id) {
Some(child_id) => child_id
None => return false
}
if !oc_contains_local(moved_refinalized_refinements, cast_id) {
return false
}
match @ir.hot_node_exact_instr(func, cast_id) {
Some(@lib.Instruction::RefCast(_, _, _)) => ()
_ => return false
}
if @ir.hot_child_count(func, cast_id) != 1 {
return false
}
if !oc_cast_is_more_refined(module_ctx, func, best_cast, cast_id) {
return false
}
let best_target = match oc_refinement_type_from_node(func, best_cast) {
Some(ref_type) => ref_type
None => return false
}
let cast_node = @ir.hot_node_get(func, cast_id)
let payload_id = @ir.hot_alloc_instr_payload(
func,
@ir.HotInstrPayload::new(@lib.Instruction::ref_cast_type(best_target)),
)
let replacement_id = @ir.hot_build_node(
func,
cast_node.op,
@ir.hot_type_intern_value(func, @lib.ValType::ref_type(best_target)),
imm0=cast_node.imm0,
imm1=payload_id,
children=oc_node_children(func, cast_id),
)
let replacement = @ir.hot_node_get(func, replacement_id)
@ir.hot_delete_node(func, replacement_id)
pass_replace_node(ctx, func, cast_id, replacement)
true
}
///|
fn oc_ref_cast_source_can_read_best_cast(
module_ctx : @ir.HotModuleContext,
func : @ir.HotFunc,
candidate_id : Int32,
current_id : Int32,
) -> Bool {
match @ir.hot_node_exact_instr(func, current_id) {
Some(@lib.Instruction::RefCast(_, _, _)) => ()
_ => return false
}
if @ir.hot_child_count(func, current_id) != 1 {
return false
}
let candidate = match oc_refinement_type_from_node(func, candidate_id) {
Some(ref_type) => ref_type
None => return false
}
let source_id = @ir.hot_child_get(func, current_id, 0)
let source = match oc_ref_type_from_node(func, source_id) {
Some(ref_type) => ref_type
None => return false
}
let target = match oc_refinement_type_from_node(func, current_id) {
Some(ref_type) => ref_type
None => return false
}
candidate != source &&
oc_ref_always_matches(module_ctx, candidate, source) &&
!oc_ref_never_matches(module_ctx, candidate, target)
}
///|
fn oc_nullable_best_cast_feeds_ref_as_non_null(
module_ctx : @ir.HotModuleContext,
func : @ir.HotFunc,
candidate_id : Int32,
current_id : Int32,
) -> Bool {
match @ir.hot_node_exact_instr(func, current_id) {
Some(@lib.Instruction::RefAsNonNull) => ()
_ => return false
}
let candidate = match oc_refinement_type_from_node(func, candidate_id) {
Some(ref_type) => ref_type
None => return false
}
if !candidate.is_nullable() {
return false
}
let current = match oc_refinement_type_from_node(func, current_id) {
Some(ref_type) => ref_type
None => return false
}
let candidate_after_as_non_null = oc_ref_type_as_non_null(candidate)
candidate_after_as_non_null != current &&
oc_ref_always_matches(module_ctx, candidate_after_as_non_null, current)
}
///|
fn oc_cast_is_more_refined(
module_ctx : @ir.HotModuleContext,
func : @ir.HotFunc,
candidate_id : Int32,
current_id : Int32,
) -> Bool {
let candidate = match oc_refinement_type_from_node(func, candidate_id) {
Some(ref_type) => ref_type
None => return false
}
let current = match oc_refinement_type_from_node(func, current_id) {
Some(ref_type) => ref_type
None => return true
}
candidate != current && oc_ref_always_matches(module_ctx, candidate, current)
}
///|
fn oc_best_cast_can_feed_refinement_source(
module_ctx : @ir.HotModuleContext,
func : @ir.HotFunc,
candidate_id : Int32,
current_id : Int32,
) -> Bool {
oc_cast_is_more_refined(module_ctx, func, candidate_id, current_id) ||
oc_nullable_best_cast_feeds_ref_as_non_null(
module_ctx, func, candidate_id, current_id,
)
}
///|
fn oc_wrap_refinement_with_tee(
ctx : HotPassContext,
func : @ir.HotFunc,
cast_id : Int32,
fresh_local : Int32,
) -> Bool {
if !@ir.hot_node_is_live(func, cast_id) {
return false
}
let ref_type = match oc_ref_type_from_node(func, cast_id) {
Some(ref_type) => ref_type
None => return false
}
let cast_node = @ir.hot_node_get(func, cast_id)
let clone = @ir.hot_build_node(
func,
cast_node.op,
cast_node.ty,
imm0=cast_node.imm0,
imm1=cast_node.imm1,
children=oc_node_children(func, cast_id),
)
let tee = @ir.hot_build_local_tee(func, fresh_local, clone)
let temp = oc_restore_capture_type(func, tee, ref_type)
let replacement = @ir.hot_node_get(func, temp)
@ir.hot_delete_node(func, temp)
pass_replace_node(ctx, func, cast_id, replacement)
true
}
///|
fn oc_dropped_local_get(func : @ir.HotFunc, root_id : Int32) -> (Int32, Int32)? {
if !@ir.hot_node_is_live(func, root_id) {
return None
}
match @ir.hot_node_get(func, root_id).op {
@ir.HotOp::Drop =>
if @ir.hot_child_count(func, root_id) == 1 {
let child_id = @ir.hot_child_get(func, root_id, 0)
let child = @ir.hot_node_get(func, child_id)
match child.op {
@ir.HotOp::LocalGet => Some((child.imm0, child_id))
_ => None
}
} else {
None
}
_ => None
}
}
///|
fn oc_nontrapping_pure_local_set(func : @ir.HotFunc, root_id : Int32) -> Int32? {
if !@ir.hot_node_is_live(func, root_id) {
return None
}
let root = @ir.hot_node_get(func, root_id)
match root.op {
@ir.HotOp::LocalSet =>
if @ir.hot_child_count(func, root_id) == 1 {
let child_id = @ir.hot_child_get(func, root_id, 0)
if oc_i32_nontrapping_pure_tree(func, child_id, 64) {
Some(root.imm0)
} else {
None
}
} else {
None
}
_ => None
}
}
///|
fn oc_nontrapping_pure_tee_value(func : @ir.HotFunc, node_id : Int32) -> Bool {
if !@ir.hot_node_is_live(func, node_id) {
return false
}
match @ir.hot_node_get(func, node_id).op {
@ir.HotOp::LocalGet => true
_ => oc_i32_nontrapping_pure_tree(func, node_id, 64)
}
}
///|
fn oc_dropped_nontrapping_pure_local_tee(
func : @ir.HotFunc,
root_id : Int32,
) -> Int32? {
if !@ir.hot_node_is_live(func, root_id) {
return None
}
match @ir.hot_node_get(func, root_id).op {
@ir.HotOp::Drop =>
if @ir.hot_child_count(func, root_id) == 1 {
let tee_id = @ir.hot_child_get(func, root_id, 0)
let tee = @ir.hot_node_get(func, tee_id)
match tee.op {
@ir.HotOp::LocalTee =>
if @ir.hot_child_count(func, tee_id) == 1 {
let value_id = @ir.hot_child_get(func, tee_id, 0)
if oc_nontrapping_pure_tee_value(func, value_id) {
Some(tee.imm0)
} else {
None
}
} else {
None
}
_ => None
}
} else {
None
}
_ => None
}
}
///|
fn oc_direct_dropped_refinement(
func : @ir.HotFunc,
root_id : Int32,
) -> (Int32, Int32)? {
if !@ir.hot_node_is_live(func, root_id) {
return None
}
match @ir.hot_node_get(func, root_id).op {
@ir.HotOp::Drop =>
if @ir.hot_child_count(func, root_id) == 1 {
let child_id = @ir.hot_child_get(func, root_id, 0)
match oc_early_refinement_source_local(func, child_id) {
Some(local_id) => Some((local_id, child_id))
None => None
}
} else {
None
}
_ => None
}
}
///|
fn oc_direct_refinement_source_get(
func : @ir.HotFunc,
cast_id : Int32,
) -> (Int32, Int32)? {
let requires_nullable_source = match @ir.hot_node_exact_instr(func, cast_id) {
Some(@lib.Instruction::RefCast(_, _, _)) => false
Some(@lib.Instruction::RefAsNonNull) => true
_ => return None
}
if @ir.hot_child_count(func, cast_id) != 1 {
return None
}
let child_id = @ir.hot_child_get(func, cast_id, 0)
let child = @ir.hot_node_get(func, child_id)
match child.op {
@ir.HotOp::LocalGet =>
if oc_source_local_requires_nullable_check(
func, child_id, requires_nullable_source,
) {
Some((child.imm0, child_id))
} else {
None
}
_ => None
}
}
///|
fn oc_single_dropped_child(func : @ir.HotFunc, root_id : Int32) -> Int32? {
if !@ir.hot_node_is_live(func, root_id) {
return None
}
match @ir.hot_node_get(func, root_id).op {
@ir.HotOp::Drop =>
if @ir.hot_child_count(func, root_id) == 1 {
Some(@ir.hot_child_get(func, root_id, 0))
} else {
None
}
_ => None
}
}
///|
fn oc_nullable_ref_cast_early_source_local(
func : @ir.HotFunc,
node_id : Int32,
) -> Int32? {
match @ir.hot_node_exact_instr(func, node_id) {
Some(@lib.Instruction::RefCast(true, _, _)) => ()
_ => return None
}
oc_early_refinement_source_local(func, node_id)
}
///|
fn oc_ref_as_non_null_early_source_local(
func : @ir.HotFunc,
node_id : Int32,
) -> Int32? {
match @ir.hot_node_exact_instr(func, node_id) {
Some(@lib.Instruction::RefAsNonNull) => ()
_ => return None
}
oc_early_refinement_source_local(func, node_id)
}
///|
fn oc_separate_ref_as_cast_pair(
func : @ir.HotFunc,
first_root : Int32,
second_root : Int32,
) -> (Int32, Int32, Int32)? {
let first_child = match oc_single_dropped_child(func, first_root) {
Some(child_id) => child_id
None => return None
}
let second_child = match oc_single_dropped_child(func, second_root) {
Some(child_id) => child_id
None => return None
}
match
(
oc_nullable_ref_cast_early_source_local(func, first_child),
oc_ref_as_non_null_early_source_local(func, second_child),
) {
(Some(cast_local), Some(as_local)) =>
if cast_local == as_local {
return Some((cast_local, first_child, second_child))
}
_ => ()
}
match
(
oc_ref_as_non_null_early_source_local(func, first_child),
oc_nullable_ref_cast_early_source_local(func, second_child),
) {
(Some(as_local), Some(cast_local)) =>
if as_local == cast_local {
return Some((as_local, second_child, first_child))
}
_ => ()
}
None
}
///|
fn oc_build_nested_ref_as_cast_from_pair(
func : @ir.HotFunc,
source_local : Int32,
cast_id : Int32,
ref_as_id : Int32,
) -> Int32? {
match @ir.hot_node_exact_instr(func, cast_id) {
Some(@lib.Instruction::RefCast(true, _, _)) => ()
_ => return None
}
match @ir.hot_node_exact_instr(func, ref_as_id) {
Some(@lib.Instruction::RefAsNonNull) => ()
_ => return None
}
let cast_ref = match oc_ref_type_from_node(func, cast_id) {
Some(ref_type) => ref_type
None => return None
}
let cast_node = @ir.hot_node_get(func, cast_id)
let ref_as_node = @ir.hot_node_get(func, ref_as_id)
let cloned_get = @ir.hot_build_local_get(func, source_local)
let cloned_cast = @ir.hot_build_node(
func,
cast_node.op,
cast_node.ty,
imm0=cast_node.imm0,
imm1=cast_node.imm1,
children=[cloned_get],
)
let ref_as_ty = @ir.hot_type_intern_value(
func,
@lib.ValType::ref_type(oc_ref_type_as_non_null(cast_ref)),
)
Some(
@ir.hot_build_node(
func,
ref_as_node.op,
ref_as_ty,
imm0=ref_as_node.imm0,
imm1=ref_as_node.imm1,
children=[cloned_cast],
),
)
}
///|
fn oc_clone_refinement_replacing_source_get(
func : @ir.HotFunc,
node_id : Int32,
source_local : Int32,
) -> Int32 {
let node = @ir.hot_node_get(func, node_id)
match node.op {
@ir.HotOp::LocalGet =>
if node.imm0 == source_local {
return @ir.hot_build_local_get(func, source_local)
}
_ => ()
}
let children = oc_node_children(func, node_id)
let cloned_children = Array::new(capacity=children.length())
for child_id in children {
cloned_children.push(
oc_clone_refinement_replacing_source_get(func, child_id, source_local),
)
}
@ir.hot_build_node(
func,
node.op,
node.ty,
imm0=node.imm0,
imm1=node.imm1,
children=cloned_children,
)
}
///|
fn oc_duplicate_refinement_at_get(
ctx : HotPassContext,
func : @ir.HotFunc,
get_id : Int32,
cast_id : Int32,
) -> Bool {
if !@ir.hot_node_is_live(func, get_id) || !@ir.hot_node_is_live(func, cast_id) {
return false
}
let get_node = @ir.hot_node_get(func, get_id)
match get_node.op {
@ir.HotOp::LocalGet => ()
_ => return false
}
let cloned_cast = if oc_is_nested_ref_as_nullable_cast(func, cast_id) {
oc_clone_refinement_replacing_source_get(func, cast_id, get_node.imm0)
} else {
let cast_node = @ir.hot_node_get(func, cast_id)
let cloned_get = @ir.hot_build_local_get(func, get_node.imm0)
@ir.hot_build_node(
func,
cast_node.op,
cast_node.ty,
imm0=cast_node.imm0,
imm1=cast_node.imm1,
children=[cloned_get],
)
}
let replacement = @ir.hot_node_get(func, cloned_cast)
@ir.hot_delete_node(func, cloned_cast)
pass_replace_node(ctx, func, get_id, replacement)
true
}
///|
fn oc_duplicate_separate_ref_as_cast_at_get(
ctx : HotPassContext,
func : @ir.HotFunc,
get_id : Int32,
cast_id : Int32,
ref_as_id : Int32,
) -> Bool {
if !@ir.hot_node_is_live(func, get_id) {
return false
}
let get_node = @ir.hot_node_get(func, get_id)
match get_node.op {
@ir.HotOp::LocalGet => ()
_ => return false
}
let nested = match
oc_build_nested_ref_as_cast_from_pair(
func,
get_node.imm0,
cast_id,
ref_as_id,
) {
Some(node_id) => node_id
None => return false
}
let replacement = @ir.hot_node_get(func, nested)
@ir.hot_delete_node(func, nested)
pass_replace_node(ctx, func, get_id, replacement)
true
}
///|
fn oc_clear_pending_get(
pending_gets : Array[Int32],
pending_get_best_refinements : Array[Int32],
local_id : Int32,
) -> Unit {
pending_gets[local_id] = -1
pending_get_best_refinements[local_id] = -1
}
///|
fn oc_clear_pending_gets(
pending_gets : Array[Int32],
pending_get_best_refinements : Array[Int32],
) -> Unit {
for i = 0; i < pending_gets.length(); i = i + 1 {
oc_clear_pending_get(pending_gets, pending_get_best_refinements, i)
}
}
///|
fn oc_flush_pending_get(
ctx : HotPassContext,
func : @ir.HotFunc,
pending_gets : Array[Int32],
pending_get_best_refinements : Array[Int32],
moved_refinalized_refinements : Array[Int32],
local_id : Int32,
) -> Bool {
let get_id = pending_gets[local_id]
let best_refinement = pending_get_best_refinements[local_id]
if get_id < 0 || best_refinement < 0 {
return false
}
let changed = oc_duplicate_refinement_at_get(
ctx, func, get_id, best_refinement,
)
if changed &&
!oc_contains_local(moved_refinalized_refinements, best_refinement) {
moved_refinalized_refinements.push(best_refinement)
}
changed
}
///|
fn oc_flush_pending_gets(
ctx : HotPassContext,
func : @ir.HotFunc,
pending_gets : Array[Int32],
pending_get_best_refinements : Array[Int32],
moved_refinalized_refinements : Array[Int32],
) -> Bool {
let mut changed = false
for local_id = 0; local_id < pending_gets.length(); local_id = local_id + 1 {
if oc_flush_pending_get(
ctx, func, pending_gets, pending_get_best_refinements, moved_refinalized_refinements,
local_id,
) {
changed = true
}
}
changed
}
///|
fn oc_clear_pending_refinements(
pending_refinement_casts : Array[Int32],
pending_refinement_gets : Array[Int32],
) -> Unit {
for i = 0; i < pending_refinement_casts.length(); i = i + 1 {
pending_refinement_casts[i] = -1
pending_refinement_gets[i] = -1
}
}
///|
fn oc_clear_later_reuse(
best_casts : Array[Int32],
fresh_locals : Array[Int32],
active_best_mask : Array[Bool],
active_best_locals : Array[Int32],
fresh_by_cast : Array[Int32],
fresh_cast_ids : Array[Int32],
) -> Unit {
for local_id in active_best_locals {
best_casts[local_id] = -1
fresh_locals[local_id] = -1
active_best_mask[local_id] = false
}
active_best_locals.clear()
for cast_id in fresh_cast_ids {
fresh_by_cast[cast_id] = -1
}
fresh_cast_ids.clear()
}
///|
// -2 marks an inner-frame cast without an allocated carrier.
// The site mark can remain across barriers; it does not retain a reuse fact.
fn oc_mark_scope_limited_cast(
fresh_by_cast : Array[Int32],
cast_id : Int32,
) -> Unit {
if cast_id >= 0 &&
cast_id < fresh_by_cast.length() &&
fresh_by_cast[cast_id] == -1 {
fresh_by_cast[cast_id] = -2
}
}
///|
fn oc_find_fresh_local_for_cast(
fresh_by_cast : Array[Int32],
cast_id : Int32,
) -> Int32? {
if cast_id >= 0 &&
cast_id < fresh_by_cast.length() &&
fresh_by_cast[cast_id] >= 0 {
return Some(fresh_by_cast[cast_id])
}
None
}
///|
fn oc_i32_nontrapping_pure_tree(
func : @ir.HotFunc,
node_id : Int32,
fuel : Int32,
) -> Bool {
if fuel <= 0 || !@ir.hot_node_is_live(func, node_id) {
return false
}
match @ir.hot_node_get(func, node_id).op {
@ir.HotOp::Const | @ir.HotOp::LocalGet => true
@ir.HotOp::Unary =>
if @ir.hot_child_count(func, node_id) == 1 {
match @ir.hot_node_exact_instr(func, node_id) {
Some(@lib.I32Eqz) => {
let child_id = @ir.hot_child_get(func, node_id, 0)
oc_i32_nontrapping_pure_tree(func, child_id, fuel - 1)
}
_ => false
}
} else {
false
}
@ir.HotOp::Binary =>
if @ir.hot_child_count(func, node_id) == 2 {
match @ir.hot_node_exact_instr(func, node_id) {
Some(@lib.I32Add)
| Some(@lib.I32Sub)
| Some(@lib.I32Mul)
| Some(@lib.I32And)
| Some(@lib.I32Or)
| Some(@lib.I32Xor)
| Some(@lib.I32Shl)
| Some(@lib.I32ShrS)
| Some(@lib.I32ShrU)
| Some(@lib.I32Rotl)
| Some(@lib.I32Rotr) => {
let lhs_id = @ir.hot_child_get(func, node_id, 0)
let rhs_id = @ir.hot_child_get(func, node_id, 1)
oc_i32_nontrapping_pure_tree(func, lhs_id, fuel - 1) &&
oc_i32_nontrapping_pure_tree(func, rhs_id, fuel - 1)
}
_ => false
}
} else {
false
}
_ => false
}
}
///|
fn oc_dropped_nontrapping_pure_root(
func : @ir.HotFunc,
root_id : Int32,
) -> Bool {
if !@ir.hot_node_is_live(func, root_id) {
return false
}
match @ir.hot_node_get(func, root_id).op {
@ir.HotOp::Nop => true
@ir.HotOp::Drop =>
if @ir.hot_child_count(func, root_id) == 1 {
let child_id = @ir.hot_child_get(func, root_id, 0)
oc_i32_nontrapping_pure_tree(func, child_id, 64)
} else {
false
}
_ => false
}
}
///|
fn oc_early_motion_scan_region(
ctx : HotPassContext,
func : @ir.HotFunc,
module_ctx : @ir.HotModuleContext,
local_count : Int32,
pending_gets : Array[Int32],
pending_get_best_refinements : Array[Int32],
pending_refinement_casts : Array[Int32],
pending_refinement_gets : Array[Int32],
seen_refinements : Array[Bool],
moved_refinalized_refinements : Array[Int32],
region : @ir.HotRegionRef,
) -> Bool {
let mut changed = false
let root_count = @ir.hot_region_root_count(func, region)
for root_idx = 0; root_idx < root_count; root_idx = root_idx + 1 {
let root_id = @ir.hot_region_get(func, region, root_idx)
if root_idx + 1 < root_count {
let next_root = @ir.hot_region_get(func, region, root_idx + 1)
match oc_separate_ref_as_cast_pair(func, root_id, next_root) {
Some((local_id, cast_id, ref_as_id)) =>
if local_id >= 0 &&
local_id < local_count &&
!seen_refinements[local_id] &&
pending_gets[local_id] >= 0 {
if oc_duplicate_separate_ref_as_cast_at_get(
ctx,
func,
pending_gets[local_id],
cast_id,
ref_as_id,
) {
changed = true
seen_refinements[local_id] = true
if !oc_contains_local(moved_refinalized_refinements, cast_id) {
moved_refinalized_refinements.push(cast_id)
}
oc_clear_pending_gets(pending_gets, pending_get_best_refinements)
continue
}
}
None => ()
}
}
match oc_direct_dropped_refinement(func, root_id) {
Some((local_id, cast_id)) => {
if local_id >= 0 && local_id < local_count {
if pending_gets[local_id] >= 0 {
let pending_best = pending_get_best_refinements[local_id]
if !seen_refinements[local_id] || pending_best >= 0 {
if pending_best < 0 ||
oc_cast_is_more_refined(module_ctx, func, cast_id, pending_best) {
pending_get_best_refinements[local_id] = cast_id
} else if oc_existing_fresh_can_feed_refinement_source(
func, pending_best, cast_id,
) &&
!oc_contains_local(moved_refinalized_refinements, cast_id) {
moved_refinalized_refinements.push(cast_id)
}
}
}
let pending_cast = pending_refinement_casts[local_id]
if pending_cast >= 0 &&
pending_refinement_gets[local_id] >= 0 &&
oc_cast_is_more_refined(module_ctx, func, cast_id, pending_cast) {
if oc_duplicate_refinement_at_get(
ctx,
func,
pending_refinement_gets[local_id],
cast_id,
) {
changed = true
if !oc_contains_local(moved_refinalized_refinements, cast_id) {
moved_refinalized_refinements.push(cast_id)
}
if !oc_contains_local(moved_refinalized_refinements, pending_cast) {
moved_refinalized_refinements.push(pending_cast)
}
}
} else if pending_cast >= 0 &&
pending_refinement_gets[local_id] >= 0 &&
oc_cast_is_more_refined(module_ctx, func, pending_cast, cast_id) {
match oc_direct_refinement_source_get(func, cast_id) {
Some((_, source_get)) =>
if oc_duplicate_refinement_at_get(
ctx, func, source_get, pending_cast,
) {
changed = true
moved_refinalized_refinements.push(cast_id)
}
None => ()
}
}
seen_refinements[local_id] = true
}
oc_clear_pending_refinements(
pending_refinement_casts, pending_refinement_gets,
)
match oc_direct_refinement_source_get(func, cast_id) {
Some((source_local, source_get)) =>
if source_local >= 0 && source_local < local_count {
pending_refinement_casts[source_local] = cast_id
pending_refinement_gets[source_local] = source_get
}
None => ()
}
continue
}
None => ()
}
match oc_dropped_local_get(func, root_id) {
Some((local_id, get_id)) => {
if local_id >= 0 && local_id < local_count {
if pending_gets[local_id] < 0 {
pending_gets[local_id] = get_id
pending_get_best_refinements[local_id] = -1
}
}
continue
}
None => ()
}
match oc_nontrapping_pure_local_set(func, root_id) {
Some(local_id) => {
if local_id >= 0 && local_id < local_count {
if oc_flush_pending_get(
ctx, func, pending_gets, pending_get_best_refinements, moved_refinalized_refinements,
local_id,
) {
changed = true
}
oc_clear_pending_get(
pending_gets, pending_get_best_refinements, local_id,
)
pending_refinement_casts[local_id] = -1
pending_refinement_gets[local_id] = -1
seen_refinements[local_id] = false
}
continue
}
None => ()
}
match oc_dropped_nontrapping_pure_local_tee(func, root_id) {
Some(local_id) => {
if local_id >= 0 && local_id < local_count {
if oc_flush_pending_get(
ctx, func, pending_gets, pending_get_best_refinements, moved_refinalized_refinements,
local_id,
) {
changed = true
}
oc_clear_pending_get(
pending_gets, pending_get_best_refinements, local_id,
)
pending_refinement_casts[local_id] = -1
pending_refinement_gets[local_id] = -1
seen_refinements[local_id] = false
}
continue
}
None => ()
}
if oc_dropped_nontrapping_pure_root(func, root_id) {
continue
}
if oc_flush_pending_gets(
ctx, func, pending_gets, pending_get_best_refinements, moved_refinalized_refinements,
) {
changed = true
}
oc_clear_pending_gets(pending_gets, pending_get_best_refinements)
oc_clear_pending_refinements(
pending_refinement_casts, pending_refinement_gets,
)
for local_id = 0; local_id < local_count; local_id = local_id + 1 {
seen_refinements[local_id] = false
}
}
if oc_flush_pending_gets(
ctx, func, pending_gets, pending_get_best_refinements, moved_refinalized_refinements,
) {
changed = true
}
changed
}
///|
fn oc_early_motion_scan_fresh_region(
ctx : HotPassContext,
func : @ir.HotFunc,
module_ctx : @ir.HotModuleContext,
moved_refinalized_refinements : Array[Int32],
region : @ir.HotRegionRef,
) -> Bool {
let local_count = @ir.hot_local_count(func)
let pending_gets = Array::make(local_count, -1)
let pending_get_best_refinements = Array::make(local_count, -1)
let pending_refinement_casts = Array::make(local_count, -1)
let pending_refinement_gets = Array::make(local_count, -1)
let seen_refinements = Array::make(local_count, false)
oc_early_motion_scan_region(
ctx, func, module_ctx, local_count, pending_gets, pending_get_best_refinements,
pending_refinement_casts, pending_refinement_gets, seen_refinements, moved_refinalized_refinements,
region,
)
}
///|
fn oc_early_motion_scan_nested_regions(
ctx : HotPassContext,
func : @ir.HotFunc,
module_ctx : @ir.HotModuleContext,
moved_refinalized_refinements : Array[Int32],
region : @ir.HotRegionRef,
) -> Bool {
let mut changed = oc_early_motion_scan_fresh_region(
ctx, func, module_ctx, moved_refinalized_refinements, region,
)
let root_count = @ir.hot_region_root_count(func, region)
for idx = 0; idx < root_count; idx = idx + 1 {
let root_id = @ir.hot_region_get(func, region, idx)
if root_id < 0 || !@ir.hot_node_is_live(func, root_id) {
continue
}
match @ir.hot_node_get(func, root_id).op {
@ir.HotOp::Block | @ir.HotOp::Loop =>
if oc_early_motion_scan_nested_regions(
ctx,
func,
module_ctx,
moved_refinalized_refinements,
@ir.hot_region_body_ref(func, root_id),
) {
changed = true
}
@ir.HotOp::If => {
if oc_early_motion_scan_nested_regions(
ctx,
func,
module_ctx,
moved_refinalized_refinements,
@ir.hot_region_then_ref(func, root_id),
) {
changed = true
}
match @ir.hot_region_else_ref(func, root_id) {
Some(else_ref) =>
if oc_early_motion_scan_nested_regions(
ctx, func, module_ctx, moved_refinalized_refinements, else_ref,
) {
changed = true
}
None => ()
}
}
@ir.HotOp::Try => {
if oc_early_motion_scan_nested_regions(
ctx,
func,
module_ctx,
moved_refinalized_refinements,
@ir.hot_region_body_ref(func, root_id),
) {
changed = true
}
match @ir.hot_region_catch_ref(func, root_id) {
Some(catch_ref) =>
if oc_early_motion_scan_nested_regions(
ctx, func, module_ctx, moved_refinalized_refinements, catch_ref,
) {
changed = true
}
None => ()
}
}
@ir.HotOp::TryTable => {
if oc_early_motion_scan_nested_regions(
ctx,
func,
module_ctx,
moved_refinalized_refinements,
@ir.hot_region_body_ref(func, root_id),
) {
changed = true
}
if oc_early_motion_scan_nested_regions(
ctx,
func,
module_ctx,
moved_refinalized_refinements,
@ir.hot_region_catch_list_ref(func, root_id),
) {
changed = true
}
}
_ => ()
}
}
changed
}
///|
fn oc_early_motion_run(
ctx : HotPassContext,
func : @ir.HotFunc,
module_ctx : @ir.HotModuleContext,
moved_refinalized_refinements : Array[Int32],
) -> Bool {
oc_early_motion_scan_nested_regions(
ctx,
func,
module_ctx,
moved_refinalized_refinements,
@ir.HotRegionRef::root(),
)
}
///|
// A fixed visit budget keeps the additional proof independent of large
// structured bodies. Nested control, calls and continuation edges stay closed.
fn oc_flat_fallthrough_node_cost(
func : @ir.HotFunc,
node_id : Int32,
budget : Int32,
) -> Int32 {
if budget <= 0 || !@ir.hot_node_is_live(func, node_id) {
return -1
}
let node = @ir.hot_node_get(func, node_id)
if oc_op_is_linear_block_barrier(node.op) ||
(
node.flags &
(@ir.HOT_FLAG_CALL | @ir.HOT_FLAG_BRANCH | @ir.HOT_FLAG_TERMINATOR)
) !=
0U {
return -1
}
let mut cost = 1
for slot = 0; slot < node.child_count; slot = slot + 1 {
let child_cost = oc_flat_fallthrough_node_cost(
func,
@ir.hot_child_get(func, node_id, slot),
budget - cost,
)
if child_cost < 0 {
return -1
}
cost += child_cost
}
cost
}
///|
fn oc_flat_fallthrough_region_cost(
func : @ir.HotFunc,
region : @ir.HotRegionRef,
budget : Int32,
) -> Int32 {
let mut cost = 0
for idx = 0; idx < @ir.hot_region_root_count(func, region); idx = idx + 1 {
let root_cost = oc_flat_fallthrough_node_cost(
func,
@ir.hot_region_get(func, region, idx),
budget - cost,
)
if root_cost < 0 {
return -1
}
cost += root_cost
}
cost
}
///|
fn oc_if_has_flat_fallthrough(func : @ir.HotFunc, node_id : Int32) -> Bool {
let node = @ir.hot_node_get(func, node_id)
if !(node.op is @ir.HotOp::If) {
return false
}
let mut budget = 256
// Include the condition and typed entry operands without treating the arm
// region containers as nested control frames.
for idx = 0; idx < pass_value_operand_count(func, node_id); idx = idx + 1 {
let cost = oc_flat_fallthrough_node_cost(
func,
@ir.hot_child_get(func, node_id, pass_value_operand_slot(node, idx)),
budget,
)
if cost < 0 {
return false
}
budget -= cost
}
let then_cost = oc_flat_fallthrough_region_cost(
func,
@ir.hot_region_then_ref(func, node_id),
budget,
)
if then_cost < 0 {
return false
}
budget -= then_cost
match @ir.hot_region_else_ref(func, node_id) {
Some(region) => oc_flat_fallthrough_region_cost(func, region, budget) >= 0
None => true
}
}
///|
fn oc_later_reuse_scan_region(
ctx : HotPassContext,
func : @ir.HotFunc,
module_ctx : @ir.HotModuleContext,
local_count : Int32,
best_casts : Array[Int32],
fresh_locals : Array[Int32],
moved_refinalized_refinements : Array[Int32],
local_get_counts : Array[Int32],
touched_get_locals : Array[Int32],
retarget_mask : Array[Bool],
retarget_locals : Array[Int32],
write_mask : Array[Bool],
writes : Array[Int32],
refinements : Array[(Int32, Int32)],
refinement_aliases : Array[(Int32, Int32)],
refinement_heads : Array[Int32],
refinement_next : Array[Int32],
refinement_head_touched : Array[Int32],
active_best_mask : Array[Bool],
active_best_locals : Array[Int32],
fresh_by_cast : Array[Int32],
fresh_cast_ids : Array[Int32],
region : @ir.HotRegionRef,
inside_control_frame : Bool,
) -> Bool {
let mut changed = false
let mut previous_flat_if = false
let root_count = @ir.hot_region_root_count(func, region)
for root_idx = 0; root_idx < root_count; root_idx = root_idx + 1 {
let root_id = @ir.hot_region_get(func, region, root_idx)
let follows_flat_if = previous_flat_if
previous_flat_if = false
match oc_plain_fallthrough_block_for_root(func, root_id) {
Some(block_id) =>
if oc_block_is_plain_fallthrough(func, block_id) {
if oc_later_reuse_scan_region(
ctx,
func,
module_ctx,
local_count,
best_casts,
fresh_locals,
moved_refinalized_refinements,
local_get_counts,
touched_get_locals,
retarget_mask,
retarget_locals,
write_mask,
writes,
refinements,
refinement_aliases,
refinement_heads,
refinement_next,
refinement_head_touched,
active_best_mask,
active_best_locals,
fresh_by_cast,
fresh_cast_ids,
@ir.hot_region_body_ref(func, block_id),
true,
) {
changed = true
}
continue
}
None => ()
}
for local_id in writes {
write_mask[local_id] = false
}
writes.clear()
oc_collect_written_locals(func, root_id, writes, write_mask)
if !inside_control_frame &&
!active_best_locals.is_empty() &&
oc_if_has_flat_fallthrough(func, root_id) {
for local_id in writes {
best_casts[local_id] = -1
fresh_locals[local_id] = -1
}
// Preserve only incoming facts. Do not rewrite arm reads or publish
// refinements that execute on just one branch.
previous_flat_if = true
continue
}
if follows_flat_if {
match oc_single_dropped_child(func, root_id) {
Some(cast_id) =>
match @ir.hot_node_exact_instr(func, cast_id) {
Some(@lib.RefCast(_, _, _)) =>
match oc_direct_refinement_source_get(func, cast_id) {
Some((local_id, _)) if local_id >= 0 && local_id < local_count => {
let best_cast = best_casts[local_id]
if best_cast >= 0 &&
!write_mask[local_id] &&
oc_existing_fresh_can_feed_refinement_source(
func, best_cast, cast_id,
) {
// The preceding cast already proved this same unchanged
// value. Change only the discarded root occurrence so a
// shared cast node keeps its earlier trap. No carrier is needed.
@ir.hot_root_set(func, root_idx, @ir.hot_build_nop(func))
pass_mark_mutated(ctx, func)
changed = true
continue
}
}
_ => ()
}
_ => ()
}
None => ()
}
}
for local_id in refinement_head_touched {
refinement_heads[local_id] = -1
}
refinement_head_touched.clear()
refinements.clear()
refinement_next.clear()
oc_collect_refinements(func, root_id, refinements)
for idx = 0; idx < refinements.length(); idx = idx + 1 {
let local_id = refinements[idx].0
if local_id >= 0 && local_id < local_count {
if refinement_heads[local_id] < 0 {
refinement_head_touched.push(local_id)
}
refinement_next.push(refinement_heads[local_id])
refinement_heads[local_id] = idx
} else {
refinement_next.push(-1)
}
}
refinement_aliases.clear()
oc_collect_refinement_tee_source_aliases(func, root_id, refinement_aliases)
for local_id in touched_get_locals {
local_get_counts[local_id] = 0
}
touched_get_locals.clear()
oc_collect_local_get_counts(
func, root_id, local_get_counts, touched_get_locals,
)
for local_id in retarget_locals {
retarget_mask[local_id] = false
}
retarget_locals.clear()
let has_linear_barrier = oc_node_has_linear_block_barrier(func, root_id)
if has_linear_barrier {
oc_clear_later_reuse(
best_casts, fresh_locals, active_best_mask, active_best_locals, fresh_by_cast,
fresh_cast_ids,
)
} else {
for local_id in touched_get_locals {
let best_cast = best_casts[local_id]
if best_cast >= 0 &&
!write_mask[local_id] &&
!oc_indexed_refinement_blocks_retarget(
module_ctx,
func,
refinements,
refinement_heads,
refinement_next,
local_id,
best_cast,
fresh_locals[local_id] >= 0,
moved_refinalized_refinements,
) {
let fresh_local = if fresh_locals[local_id] >= 0 {
fresh_locals[local_id]
} else {
match oc_find_fresh_local_for_cast(fresh_by_cast, best_cast) {
Some(fresh) => {
fresh_locals[local_id] = fresh
fresh
}
None => {
let ref_type = match oc_ref_type_from_node(func, best_cast) {
Some(ref_type) => ref_type
None => continue
}
// An inner-frame write cannot initialize a parent-frame local.
let can_escape_frame = best_cast >= fresh_by_cast.length() ||
fresh_by_cast[best_cast] == -2
let fresh_ref_type = @lib.RefType::new(
ref_type.is_nullable() || can_escape_frame,
ref_type.get_heap_type(),
exact=ref_type.is_exact(),
)
let fresh = @ir.hot_append_body_local(
func,
@lib.ValType::ref_type(fresh_ref_type),
)
fresh_locals[local_id] = fresh
ignore(oc_wrap_refinement_with_tee(ctx, func, best_cast, fresh))
if best_cast >= 0 && best_cast < fresh_by_cast.length() {
fresh_by_cast[best_cast] = fresh
fresh_cast_ids.push(best_cast)
}
fresh
}
}
}
ignore(fresh_local)
retarget_mask[local_id] = true
retarget_locals.push(local_id)
}
}
if !retarget_locals.is_empty() &&
oc_retarget_local_gets_from_map(
ctx, func, root_id, retarget_mask, fresh_locals, best_casts,
) >
0 {
changed = true
for local_id in retarget_locals {
if oc_refinalize_marked_ref_cast_to_best(
ctx,
func,
module_ctx,
root_id,
best_casts[local_id],
moved_refinalized_refinements,
) {
changed = true
}
}
}
}
for local_id in writes {
if local_id >= 0 && local_id < local_count {
best_casts[local_id] = -1
fresh_locals[local_id] = -1
}
}
for refinement in refinements {
let (local_id, cast_id) = refinement
if local_id >= 0 && local_id < local_count {
if !active_best_mask[local_id] {
active_best_mask[local_id] = true
active_best_locals.push(local_id)
}
if best_casts[local_id] < 0 ||
oc_cast_is_more_refined(
module_ctx,
func,
cast_id,
best_casts[local_id],
) {
best_casts[local_id] = cast_id
fresh_locals[local_id] = -1
if inside_control_frame {
oc_mark_scope_limited_cast(fresh_by_cast, cast_id)
}
}
}
}
for refinement in refinement_aliases {
let (local_id, cast_id) = refinement
if local_id >= 0 && local_id < local_count {
if !active_best_mask[local_id] {
active_best_mask[local_id] = true
active_best_locals.push(local_id)
}
if best_casts[local_id] < 0 ||
oc_cast_is_more_refined(
module_ctx,
func,
cast_id,
best_casts[local_id],
) {
best_casts[local_id] = cast_id
fresh_locals[local_id] = -1
if inside_control_frame {
oc_mark_scope_limited_cast(fresh_by_cast, cast_id)
}
}
}
}
}
changed
}
///|
fn oc_later_reuse_run(
ctx : HotPassContext,
func : @ir.HotFunc,
module_ctx : @ir.HotModuleContext,
moved_refinalized_refinements : Array[Int32],
) -> Bool {
let local_count = @ir.hot_local_count(func)
let best_casts = Array::make(local_count, -1)
let fresh_locals = Array::make(local_count, -1)
let local_get_counts = Array::make(local_count, 0)
let touched_get_locals : Array[Int32] = []
let retarget_mask = Array::make(local_count, false)
let retarget_locals : Array[Int32] = []
let write_mask = Array::make(local_count, false)
let writes : Array[Int32] = []
let refinements : Array[(Int32, Int32)] = []
let refinement_aliases : Array[(Int32, Int32)] = []
let refinement_heads = Array::make(local_count, -1)
let refinement_next : Array[Int32] = []
let refinement_head_touched : Array[Int32] = []
let active_best_mask = Array::make(local_count, false)
let active_best_locals : Array[Int32] = []
let fresh_by_cast = Array::make(@ir.hot_node_count(func), -1)
let fresh_cast_ids : Array[Int32] = []
oc_later_reuse_scan_region(
ctx,
func,
module_ctx,
local_count,
best_casts,
fresh_locals,
moved_refinalized_refinements,
local_get_counts,
touched_get_locals,
retarget_mask,
retarget_locals,
write_mask,
writes,
refinements,
refinement_aliases,
refinement_heads,
refinement_next,
refinement_head_touched,
active_best_mask,
active_best_locals,
fresh_by_cast,
fresh_cast_ids,
@ir.HotRegionRef::root(),
false,
)
}
///|
fn oc_rewrite_node(
ctx : HotPassContext,
func : @ir.HotFunc,
module_ctx : @ir.HotModuleContext,
node_id : Int32,
) -> Bool {
if !@ir.hot_node_is_live(func, node_id) {
return false
}
match @ir.hot_node_exact_instr(func, node_id) {
Some(@lib.Instruction::RefCast(nullable, exact, heap_type)) => {
if @ir.hot_child_count(func, node_id) != 1 {
return false
}
let child_id = @ir.hot_child_get(func, node_id, 0)
match oc_ref_type_from_node(func, child_id) {
Some(source_ref) => {
let target_ref = oc_target_ref_type(nullable, exact, heap_type)
if oc_ref_always_matches(module_ctx, source_ref, target_ref) {
oc_replace_with_child(ctx, func, node_id)
return true
}
}
None => ()
}
false
}
Some(@lib.Instruction::RefCastDescEq(nullable, exact, heap_type)) => {
let target_ref = oc_target_ref_type(nullable, exact, heap_type)
if @ir.hot_child_count(func, node_id) == 2 {
return oc_replace_nullable_known_null_desc_eq(
ctx, func, node_id, target_ref,
)
}
if @ir.hot_child_count(func, node_id) != 1 {
return false
}
let child_id = @ir.hot_child_get(func, node_id, 0)
match oc_ref_type_from_node(func, child_id) {
Some(source_ref) =>
if oc_ref_always_matches(module_ctx, source_ref, target_ref) {
oc_replace_with_child(ctx, func, node_id)
return true
}
None => ()
}
false
}
Some(@lib.Instruction::RefTest(nullable, false, heap_type))
| Some(@lib.Instruction::RefTestDesc(nullable, heap_type)) => {
if @ir.hot_child_count(func, node_id) != 1 {
return false
}
let child_id = @ir.hot_child_get(func, node_id, 0)
match oc_ref_type_from_node(func, child_id) {
Some(source_ref) => {
let target_ref = oc_target_ref_type(nullable, false, heap_type)
if oc_ref_always_matches(module_ctx, source_ref, target_ref) {
oc_replace_with_const_i32(ctx, func, node_id, 1)
return true
}
if oc_ref_never_matches(module_ctx, source_ref, target_ref) {
oc_replace_with_const_i32(ctx, func, node_id, 0)
return true
}
}
None => ()
}
false
}
Some(
@lib.Instruction::BrOnCast(
_,
@lib.CastOp(_, nullable, _, exact),
_,
heap_type
)
) => {
if @ir.hot_child_count(func, node_id) == 0 {
return false
}
let child_id = @ir.hot_child_get(
func,
node_id,
@ir.hot_child_count(func, node_id) - 1,
)
match oc_ref_type_from_node(func, child_id) {
Some(source_ref) => {
let target_ref = oc_target_ref_type(nullable, exact, heap_type)
if oc_ref_always_matches(module_ctx, source_ref, target_ref) {
oc_replace_with_unconditional_br(ctx, func, node_id)
return true
}
if oc_ref_never_matches(module_ctx, source_ref, target_ref) &&
@ir.hot_child_count(func, node_id) == 1 {
oc_replace_with_child(ctx, func, node_id)
return true
}
}
None => ()
}
false
}
Some(
@lib.Instruction::BrOnCastFail(
_,
@lib.CastOp(_, nullable, _, exact),
_,
heap_type
)
) => {
if @ir.hot_child_count(func, node_id) != 1 {
return false
}
let child_id = @ir.hot_child_get(func, node_id, 0)
match oc_ref_type_from_node(func, child_id) {
Some(source_ref) => {
let target_ref = oc_target_ref_type(nullable, exact, heap_type)
if oc_ref_always_matches(module_ctx, source_ref, target_ref) {
oc_replace_with_child(ctx, func, node_id)
return true
}
if oc_ref_never_matches(module_ctx, source_ref, target_ref) {
oc_replace_with_unconditional_br(ctx, func, node_id)
return true
}
}
None => ()
}
false
}
_ => false
}
}
///|
fn oc_static_fold_run(
ctx : HotPassContext,
func : @ir.HotFunc,
module_ctx : @ir.HotModuleContext,
) -> Bool {
let mut changed = false
let node_count = func.nodes.length()
for node_id = 0; node_id < node_count; node_id = node_id + 1 {
if oc_rewrite_node(ctx, func, module_ctx, node_id) {
changed = true
}
}
changed
}
///|
pub fn optimize_casts_run(
ctx : HotPassContext,
func : @ir.HotFunc,
) -> HotPassResult {
let module_ctx = match ctx.module_ctx {
Some(module_ctx) => module_ctx
None => return HotPassResult::unchanged()
}
let mut changed = false
let moved_refinalized_refinements : Array[Int32] = []
if oc_static_fold_run(ctx, func, module_ctx) {
changed = true
}
if oc_early_motion_run(ctx, func, module_ctx, moved_refinalized_refinements) {
changed = true
if oc_static_fold_run(ctx, func, module_ctx) {
changed = true
}
}
if oc_later_reuse_run(ctx, func, module_ctx, moved_refinalized_refinements) {
changed = true
}
if changed {
HotPassResult::changed()
} else {
HotPassResult::unchanged()
}
}