// 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 2016 WebAssembly Community Group participants
// Counterpart: src/passes/Precompute.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 precompute_descriptor() -> @ir.HotPassDescriptor {
@ir.HotPassDescriptor::new("precompute", 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 precompute_summary() -> String {
"Evaluate exact scalar expressions, selected control and GC values, and safe dropped computations."
}
///|
pub fn precompute_propagate_descriptor() -> @ir.HotPassDescriptor {
@ir.HotPassDescriptor::new(
"precompute-propagate",
requires=[@ir.HotAnalysis::ssa()],
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 precompute_propagate_summary() -> String {
"Precompute constants, propagate agreeing reaching local writes, then rerun precompute once."
}
///|
fn precompute_replace_node_rehoming_control_label(
func : @ir.HotFunc,
node_id : @ir.NodeId,
replacement : @ir.HotNode,
) -> Unit {
@ir.hot_replace_node(func, node_id, replacement)
let label_id = @ir.hot_control_node_label(func, node_id)
if label_id >= 0 {
@ir.hot_label_owner_set(func, label_id, node_id)
}
}
///|
priv struct PrecomputePropagatePrefixReplacement {
node_id : Int32
payload : @ir.HotConstPayload
}
///|
priv struct PrecomputePropagateAllocationFold {
node_id : Int32
allocation_id : Int32
}
///|
priv struct PrecomputePropagateIdentityFold {
node_id : Int32
value : @lib.I32
}
///|
priv struct PrecomputePartialI32Select {
condition_id : Int32
true_value : Int32
false_value : Int32
}
///|
priv struct PrecomputePartialKnownHeapSelect {
condition_id : Int32
true_value : PrecomputeKnownHeapValue
false_value : PrecomputeKnownHeapValue
}
///|
priv enum PrecomputeHeapIdentity {
HeapNode(Int32)
HeapGlobal(@lib.GlobalIdx)
} derive(Eq)
///|
priv enum PrecomputeKnownHeapValue {
KnownEmit(@lib.Instruction)
KnownStructFields(@lib.TypeIdx, Array[PrecomputeKnownHeapValue])
KnownStructDefault(@lib.TypeIdx)
KnownArrayFixed(@lib.TypeIdx, Array[PrecomputeKnownHeapValue])
KnownArrayFill(@lib.TypeIdx, Array[PrecomputeKnownHeapValue], UInt32)
KnownArrayDefault(@lib.TypeIdx, UInt32)
}
///|
fn precompute_global_const(
ctx : HotPassContext,
func : @ir.HotFunc,
node_id : Int32,
) -> @ir.HotConstPayload? {
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::GlobalGet => ()
_ => return None
}
match ctx.module_ctx {
Some(module_ctx) =>
@ir.hot_module_resolve_global_const_init(
module_ctx,
@lib.GlobalIdx::new(node.imm0.reinterpret_as_uint()),
)
None => None
}
}
///|
fn precompute_i32_const(func : @ir.HotFunc, node_id : Int32) -> @lib.I32? {
if node_id < 0 || !@ir.hot_node_is_live(func, node_id) {
return None
}
let node = @ir.hot_node_get(func, node_id)
match node.op {
@ir.HotOp::Const =>
match @ir.hot_const_get(func, node.imm0) {
@ir.HotConstPayload::I32Const(value) => Some(value)
_ => None
}
@ir.HotOp::GlobalGet => None
_ => None
}
}
///|
fn precompute_i32_exact_const(
ctx : HotPassContext,
func : @ir.HotFunc,
node_id : Int32,
) -> @lib.I32? {
match precompute_i32_const(func, node_id) {
Some(value) => Some(value)
None =>
match precompute_global_const(ctx, func, node_id) {
Some(@ir.HotConstPayload::I32Const(value)) => Some(value)
_ => None
}
}
}
///|
fn precompute_i64_const(func : @ir.HotFunc, node_id : Int32) -> @lib.I64? {
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(value) => Some(value)
_ => None
}
@ir.HotOp::GlobalGet => None
_ => None
}
}
///|
fn precompute_i64_exact_const(
ctx : HotPassContext,
func : @ir.HotFunc,
node_id : Int32,
) -> @lib.I64? {
match precompute_i64_const(func, node_id) {
Some(value) => Some(value)
None =>
match precompute_global_const(ctx, func, node_id) {
Some(@ir.HotConstPayload::I64Const(value)) => Some(value)
_ => None
}
}
}
///|
fn precompute_exact_const_instr(
ctx : HotPassContext,
func : @ir.HotFunc,
node_id : Int32,
) -> @lib.Instruction? {
if node_id >= 0 && @ir.hot_node_is_live(func, node_id) {
match @ir.hot_node_exact_instr(func, node_id) {
Some(@lib.V128Const(_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _)) as value =>
return value
_ => ()
}
}
let payload = if node_id >= 0 && @ir.hot_node_is_live(func, node_id) {
let node = @ir.hot_node_get(func, node_id)
match node.op {
@ir.HotOp::Const | @ir.HotOp::RefNull =>
Some(@ir.hot_const_get(func, node.imm0))
@ir.HotOp::GlobalGet => precompute_global_const(ctx, func, node_id)
_ => None
}
} else {
None
}
match payload {
Some(@ir.HotConstPayload::I32Const(value)) =>
Some(@lib.Instruction::i32_const(value))
Some(@ir.HotConstPayload::I64Const(value)) =>
Some(@lib.Instruction::i64_const(value))
Some(@ir.HotConstPayload::F32Const(value)) =>
Some(@lib.Instruction::f32_const(value))
Some(@ir.HotConstPayload::F64Const(value)) =>
Some(@lib.Instruction::f64_const(value))
Some(@ir.HotConstPayload::RefNullConst(value)) =>
Some(@lib.Instruction::ref_null_type(value))
Some(@ir.HotConstPayload::StringConst(value)) =>
Some(@lib.Instruction::string_const(value))
None => None
}
}
///|
fn precompute_build_const_instr(
func : @ir.HotFunc,
instr : @lib.Instruction,
) -> Int32? {
match instr {
@lib.I32Const(value) => Some(@ir.hot_build_const_i32(func, value))
@lib.I64Const(value) => Some(@ir.hot_build_const_i64(func, value))
@lib.F32Const(value) => Some(@ir.hot_build_const_f32(func, value))
@lib.F64Const(value) => Some(@ir.hot_build_const_f64(func, value))
@lib.RefNull(ref_type) => Some(@ir.hot_build_ref_null(func, ref_type))
@lib.StringConst(value) => Some(@ir.hot_build_string_const(func, value))
@lib.V128Const(_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _) =>
Some(@ir.hot_build_simd(func, [@lib.ValType::v128()], instr, []))
_ => None
}
}
///|
fn precompute_replace_with_const_instr(
ctx : HotPassContext,
func : @ir.HotFunc,
node_id : Int32,
instr : @lib.Instruction,
) -> Bool {
let temp = match precompute_build_const_instr(func, instr) {
Some(node_id) => node_id
None => return false
}
let replacement = @ir.hot_node_get(func, temp)
@ir.hot_delete_node(func, temp)
precompute_replace_node_rehoming_control_label(func, node_id, replacement)
pass_mark_mutated(ctx, func)
true
}
///|
fn precompute_replace_with_const_instr_preserving_first_local_tee(
ctx : HotPassContext,
func : @ir.HotFunc,
node_id : Int32,
instr : @lib.Instruction,
) -> Bool {
if @ir.hot_child_count(func, node_id) == 0 {
return precompute_replace_with_const_instr(ctx, func, node_id, instr)
}
let reference_id = @ir.hot_child_get(func, node_id, 0)
if reference_id < 0 || !@ir.hot_node_is_live(func, reference_id) {
return false
}
let reference = @ir.hot_node_get(func, reference_id)
if !(reference.op is @ir.HotOp::LocalTee) &&
!precompute_is_discardable_value(func, reference_id) &&
!precompute_fresh_struct_allocation_is_discardable(func, reference_id) &&
!precompute_fresh_array_allocation_is_discardable(func, reference_id) {
// Knowing the final allocation does not make its receiver expression pure.
// Evaluate each operand in order before replacing the proven heap read.
let result = match
@ir.hot_type_results(func, @ir.hot_node_get(func, node_id).ty) {
[result] => result
_ => return false
}
let constant = match precompute_build_const_instr(func, instr) {
Some(constant) => constant
None => return false
}
let body : Array[Int32] = []
for slot = 0; slot < @ir.hot_child_count(func, node_id); slot = slot + 1 {
let operand = @ir.hot_child_get(func, node_id, slot)
if !precompute_is_discardable_value(func, operand) {
body.push(@ir.hot_build_drop(func, operand))
}
}
body.push(constant)
let block = @ir.hot_build_block(
func,
@lib.BlockType::val_type(result),
body,
)
let replacement = @ir.hot_node_get(func, block)
@ir.hot_delete_node(func, block)
precompute_replace_node_rehoming_control_label(func, node_id, replacement)
pass_mark_mutated(ctx, func)
return true
}
match reference.op {
@ir.HotOp::LocalTee => ()
_ => return precompute_replace_with_const_instr(ctx, func, node_id, instr)
}
if @ir.hot_child_count(func, reference_id) != 1 {
return false
}
let result = match
@ir.hot_type_results(func, @ir.hot_node_get(func, node_id).ty) {
[result] => result
_ => return false
}
let const_id = match precompute_build_const_instr(func, instr) {
Some(const_id) => const_id
None => return false
}
let set_id = @ir.hot_build_local_set(
func,
reference.imm0,
@ir.hot_child_get(func, reference_id, 0),
)
let block_id = @ir.hot_build_block(func, @lib.BlockType::val_type(result), [
set_id, const_id,
])
let replacement = @ir.hot_node_get(func, block_id)
@ir.hot_delete_node(func, block_id)
precompute_replace_node_rehoming_control_label(func, node_id, replacement)
pass_mark_mutated(ctx, func)
true
}
///|
fn precompute_replace_with_const_i32(
ctx : HotPassContext,
func : @ir.HotFunc,
node_id : Int32,
value : @lib.I32,
) -> Unit {
let temp = @ir.hot_build_const_i32(func, value)
let replacement = @ir.hot_node_get(func, temp)
@ir.hot_delete_node(func, temp)
precompute_replace_node_rehoming_control_label(func, node_id, replacement)
pass_mark_mutated(ctx, func)
}
///|
fn precompute_replace_with_const_i64(
ctx : HotPassContext,
func : @ir.HotFunc,
node_id : Int32,
value : @lib.I64,
) -> Unit {
let temp = @ir.hot_build_const_i64(func, value)
let replacement = @ir.hot_node_get(func, temp)
@ir.hot_delete_node(func, temp)
precompute_replace_node_rehoming_control_label(func, node_id, replacement)
pass_mark_mutated(ctx, func)
}
///|
fn precompute_try_fold_global_get(
ctx : HotPassContext,
func : @ir.HotFunc,
node_id : Int32,
) -> Bool {
match precompute_global_const(ctx, func, node_id) {
Some(payload) => {
let temp = match payload {
@ir.HotConstPayload::I32Const(value) =>
@ir.hot_build_const_i32(func, value)
@ir.HotConstPayload::I64Const(value) =>
@ir.hot_build_const_i64(func, value)
@ir.HotConstPayload::F32Const(value) =>
@ir.hot_build_const_f32(func, value)
@ir.HotConstPayload::F64Const(value) =>
@ir.hot_build_const_f64(func, value)
@ir.HotConstPayload::RefNullConst(value) =>
@ir.hot_build_ref_null(func, value)
@ir.HotConstPayload::StringConst(_) => return false
}
let replacement = @ir.hot_node_get(func, temp)
@ir.hot_delete_node(func, temp)
precompute_replace_node_rehoming_control_label(func, node_id, replacement)
pass_mark_mutated(ctx, func)
true
}
None => false
}
}
///|
fn precompute_try_fold_unary(
ctx : HotPassContext,
func : @ir.HotFunc,
node_id : Int32,
instr : @lib.Instruction,
operand_id : Int32,
) -> Bool {
match precompute_exact_const_instr(ctx, func, operand_id) {
Some(operand) =>
match precompute_raw_try_fold_unary(operand, instr) {
Some(folded) =>
if precompute_replace_with_const_instr(ctx, func, node_id, folded) {
return true
}
None => ()
}
None => ()
}
match (instr, precompute_i32_exact_const(ctx, func, operand_id)) {
(@lib.Instruction::I32Eqz, Some(v)) => {
precompute_replace_with_const_i32(
ctx,
func,
node_id,
if v == @lib.I32(0) {
@lib.I32(1)
} else {
@lib.I32(0)
},
)
return true
}
_ => ()
}
match (instr, precompute_i64_exact_const(ctx, func, operand_id)) {
(@lib.Instruction::I64Eqz, Some(v)) => {
precompute_replace_with_const_i32(
ctx,
func,
node_id,
if v == @lib.I64(0L) {
@lib.I32(1)
} else {
@lib.I32(0)
},
)
true
}
_ => false
}
}
///|
fn precompute_try_fold_binary(
ctx : HotPassContext,
func : @ir.HotFunc,
node_id : Int32,
instr : @lib.Instruction,
lhs_id : Int32,
rhs_id : Int32,
) -> Bool {
match
(
precompute_exact_const_instr(ctx, func, lhs_id),
precompute_exact_const_instr(ctx, func, rhs_id),
) {
(Some(lhs), Some(rhs)) =>
match precompute_raw_try_fold_binary(lhs, rhs, instr) {
Some(folded) =>
if precompute_replace_with_const_instr(ctx, func, node_id, folded) {
return true
}
None => ()
}
_ => ()
}
match
(
instr,
precompute_i32_exact_const(ctx, func, lhs_id),
precompute_i32_exact_const(ctx, func, rhs_id),
) {
(@lib.Instruction::I32Add, Some(@lib.I32(lhs)), Some(@lib.I32(rhs))) => {
precompute_replace_with_const_i32(
ctx,
func,
node_id,
@lib.I32(
(lhs.reinterpret_as_uint() + rhs.reinterpret_as_uint()).reinterpret_as_int(),
),
)
return true
}
(@lib.Instruction::I32Sub, Some(@lib.I32(lhs)), Some(@lib.I32(rhs))) => {
precompute_replace_with_const_i32(
ctx,
func,
node_id,
@lib.I32(
(lhs.reinterpret_as_uint() - rhs.reinterpret_as_uint()).reinterpret_as_int(),
),
)
return true
}
(@lib.Instruction::I32Mul, Some(@lib.I32(lhs)), Some(@lib.I32(rhs))) => {
precompute_replace_with_const_i32(
ctx,
func,
node_id,
@lib.I32(
(lhs.reinterpret_as_uint() * rhs.reinterpret_as_uint()).reinterpret_as_int(),
),
)
return true
}
(@lib.Instruction::I32And, Some(@lib.I32(lhs)), Some(@lib.I32(rhs))) => {
precompute_replace_with_const_i32(ctx, func, node_id, @lib.I32(lhs & rhs))
return true
}
(@lib.Instruction::I32Or, Some(@lib.I32(lhs)), Some(@lib.I32(rhs))) => {
precompute_replace_with_const_i32(ctx, func, node_id, @lib.I32(lhs | rhs))
return true
}
(@lib.Instruction::I32Xor, Some(@lib.I32(lhs)), Some(@lib.I32(rhs))) => {
precompute_replace_with_const_i32(ctx, func, node_id, @lib.I32(lhs ^ rhs))
return true
}
(@lib.Instruction::I32Shl, Some(@lib.I32(lhs)), Some(@lib.I32(rhs))) => {
let shift = rhs.reinterpret_as_uint() & 31U
precompute_replace_with_const_i32(
ctx,
func,
node_id,
@lib.I32(
(lhs.reinterpret_as_uint() << shift.reinterpret_as_int()).reinterpret_as_int(),
),
)
return true
}
(@lib.Instruction::I32ShrU, Some(@lib.I32(lhs)), Some(@lib.I32(rhs))) => {
let shift = rhs.reinterpret_as_uint() & 31U
precompute_replace_with_const_i32(
ctx,
func,
node_id,
@lib.I32(
(lhs.reinterpret_as_uint() >> shift.reinterpret_as_int()).reinterpret_as_int(),
),
)
return true
}
(@lib.Instruction::I32ShrS, Some(@lib.I32(lhs)), Some(@lib.I32(rhs))) => {
let shift = rhs.reinterpret_as_uint() & 31U
precompute_replace_with_const_i32(
ctx,
func,
node_id,
@lib.I32(lhs >> shift.reinterpret_as_int()),
)
return true
}
(@lib.Instruction::I32Eq, Some(lhs), Some(rhs)) => {
precompute_replace_with_const_i32(
ctx,
func,
node_id,
if lhs == rhs {
@lib.I32(1)
} else {
@lib.I32(0)
},
)
return true
}
(@lib.Instruction::I32Ne, Some(lhs), Some(rhs)) => {
precompute_replace_with_const_i32(
ctx,
func,
node_id,
if lhs != rhs {
@lib.I32(1)
} else {
@lib.I32(0)
},
)
return true
}
(@lib.Instruction::I32LtS, Some(@lib.I32(lhs)), Some(@lib.I32(rhs))) => {
precompute_replace_with_const_i32(
ctx,
func,
node_id,
if lhs < rhs {
@lib.I32(1)
} else {
@lib.I32(0)
},
)
return true
}
(@lib.Instruction::I32LtU, Some(@lib.I32(lhs)), Some(@lib.I32(rhs))) => {
precompute_replace_with_const_i32(
ctx,
func,
node_id,
if lhs.reinterpret_as_uint() < rhs.reinterpret_as_uint() {
@lib.I32(1)
} else {
@lib.I32(0)
},
)
return true
}
(@lib.Instruction::I32GtS, Some(@lib.I32(lhs)), Some(@lib.I32(rhs))) => {
precompute_replace_with_const_i32(
ctx,
func,
node_id,
if lhs > rhs {
@lib.I32(1)
} else {
@lib.I32(0)
},
)
return true
}
(@lib.Instruction::I32GtU, Some(@lib.I32(lhs)), Some(@lib.I32(rhs))) => {
precompute_replace_with_const_i32(
ctx,
func,
node_id,
if lhs.reinterpret_as_uint() > rhs.reinterpret_as_uint() {
@lib.I32(1)
} else {
@lib.I32(0)
},
)
return true
}
(@lib.Instruction::I32LeS, Some(@lib.I32(lhs)), Some(@lib.I32(rhs))) => {
precompute_replace_with_const_i32(
ctx,
func,
node_id,
if lhs <= rhs {
@lib.I32(1)
} else {
@lib.I32(0)
},
)
return true
}
(@lib.Instruction::I32LeU, Some(@lib.I32(lhs)), Some(@lib.I32(rhs))) => {
precompute_replace_with_const_i32(
ctx,
func,
node_id,
if lhs.reinterpret_as_uint() <= rhs.reinterpret_as_uint() {
@lib.I32(1)
} else {
@lib.I32(0)
},
)
return true
}
(@lib.Instruction::I32GeS, Some(@lib.I32(lhs)), Some(@lib.I32(rhs))) => {
precompute_replace_with_const_i32(
ctx,
func,
node_id,
if lhs >= rhs {
@lib.I32(1)
} else {
@lib.I32(0)
},
)
return true
}
(@lib.Instruction::I32GeU, Some(@lib.I32(lhs)), Some(@lib.I32(rhs))) => {
precompute_replace_with_const_i32(
ctx,
func,
node_id,
if lhs.reinterpret_as_uint() >= rhs.reinterpret_as_uint() {
@lib.I32(1)
} else {
@lib.I32(0)
},
)
return true
}
_ => ()
}
match
(
instr,
precompute_i64_exact_const(ctx, func, lhs_id),
precompute_i64_exact_const(ctx, func, rhs_id),
) {
(@lib.Instruction::I64Add, Some(@lib.I64(lhs)), Some(@lib.I64(rhs))) => {
precompute_replace_with_const_i64(
ctx,
func,
node_id,
@lib.I64(
(lhs.reinterpret_as_uint64() + rhs.reinterpret_as_uint64()).reinterpret_as_int64(),
),
)
true
}
(@lib.Instruction::I64Sub, Some(@lib.I64(lhs)), Some(@lib.I64(rhs))) => {
precompute_replace_with_const_i64(
ctx,
func,
node_id,
@lib.I64(
(lhs.reinterpret_as_uint64() - rhs.reinterpret_as_uint64()).reinterpret_as_int64(),
),
)
true
}
(@lib.Instruction::I64Mul, Some(@lib.I64(lhs)), Some(@lib.I64(rhs))) => {
precompute_replace_with_const_i64(
ctx,
func,
node_id,
@lib.I64(
(lhs.reinterpret_as_uint64() * rhs.reinterpret_as_uint64()).reinterpret_as_int64(),
),
)
true
}
(@lib.Instruction::I64And, Some(@lib.I64(lhs)), Some(@lib.I64(rhs))) => {
precompute_replace_with_const_i64(ctx, func, node_id, @lib.I64(lhs & rhs))
true
}
(@lib.Instruction::I64Or, Some(@lib.I64(lhs)), Some(@lib.I64(rhs))) => {
precompute_replace_with_const_i64(ctx, func, node_id, @lib.I64(lhs | rhs))
true
}
(@lib.Instruction::I64Xor, Some(@lib.I64(lhs)), Some(@lib.I64(rhs))) => {
precompute_replace_with_const_i64(ctx, func, node_id, @lib.I64(lhs ^ rhs))
true
}
(@lib.Instruction::I64Shl, Some(@lib.I64(lhs)), Some(@lib.I64(rhs))) => {
let shift = rhs.reinterpret_as_uint64() & 63UL
precompute_replace_with_const_i64(
ctx,
func,
node_id,
@lib.I64(
(lhs.reinterpret_as_uint64() << shift.to_int()).reinterpret_as_int64(),
),
)
true
}
(@lib.Instruction::I64ShrU, Some(@lib.I64(lhs)), Some(@lib.I64(rhs))) => {
let shift = rhs.reinterpret_as_uint64() & 63UL
precompute_replace_with_const_i64(
ctx,
func,
node_id,
@lib.I64(
(lhs.reinterpret_as_uint64() >> shift.to_int()).reinterpret_as_int64(),
),
)
true
}
(@lib.Instruction::I64ShrS, Some(@lib.I64(lhs)), Some(@lib.I64(rhs))) => {
let shift = rhs.reinterpret_as_uint64() & 63UL
precompute_replace_with_const_i64(
ctx,
func,
node_id,
@lib.I64(lhs >> shift.to_int()),
)
true
}
(@lib.Instruction::I64Eq, Some(lhs), Some(rhs)) => {
precompute_replace_with_const_i32(
ctx,
func,
node_id,
if lhs == rhs {
@lib.I32(1)
} else {
@lib.I32(0)
},
)
true
}
(@lib.Instruction::I64Ne, Some(lhs), Some(rhs)) => {
precompute_replace_with_const_i32(
ctx,
func,
node_id,
if lhs != rhs {
@lib.I32(1)
} else {
@lib.I32(0)
},
)
true
}
(@lib.Instruction::I64LtS, Some(@lib.I64(lhs)), Some(@lib.I64(rhs))) => {
precompute_replace_with_const_i32(
ctx,
func,
node_id,
if lhs < rhs {
@lib.I32(1)
} else {
@lib.I32(0)
},
)
true
}
(@lib.Instruction::I64LtU, Some(@lib.I64(lhs)), Some(@lib.I64(rhs))) => {
precompute_replace_with_const_i32(
ctx,
func,
node_id,
if lhs.reinterpret_as_uint64() < rhs.reinterpret_as_uint64() {
@lib.I32(1)
} else {
@lib.I32(0)
},
)
true
}
(@lib.Instruction::I64GtS, Some(@lib.I64(lhs)), Some(@lib.I64(rhs))) => {
precompute_replace_with_const_i32(
ctx,
func,
node_id,
if lhs > rhs {
@lib.I32(1)
} else {
@lib.I32(0)
},
)
true
}
(@lib.Instruction::I64GtU, Some(@lib.I64(lhs)), Some(@lib.I64(rhs))) => {
precompute_replace_with_const_i32(
ctx,
func,
node_id,
if lhs.reinterpret_as_uint64() > rhs.reinterpret_as_uint64() {
@lib.I32(1)
} else {
@lib.I32(0)
},
)
true
}
(@lib.Instruction::I64LeS, Some(@lib.I64(lhs)), Some(@lib.I64(rhs))) => {
precompute_replace_with_const_i32(
ctx,
func,
node_id,
if lhs <= rhs {
@lib.I32(1)
} else {
@lib.I32(0)
},
)
true
}
(@lib.Instruction::I64LeU, Some(@lib.I64(lhs)), Some(@lib.I64(rhs))) => {
precompute_replace_with_const_i32(
ctx,
func,
node_id,
if lhs.reinterpret_as_uint64() <= rhs.reinterpret_as_uint64() {
@lib.I32(1)
} else {
@lib.I32(0)
},
)
true
}
(@lib.Instruction::I64GeS, Some(@lib.I64(lhs)), Some(@lib.I64(rhs))) => {
precompute_replace_with_const_i32(
ctx,
func,
node_id,
if lhs >= rhs {
@lib.I32(1)
} else {
@lib.I32(0)
},
)
true
}
(@lib.Instruction::I64GeU, Some(@lib.I64(lhs)), Some(@lib.I64(rhs))) => {
precompute_replace_with_const_i32(
ctx,
func,
node_id,
if lhs.reinterpret_as_uint64() >= rhs.reinterpret_as_uint64() {
@lib.I32(1)
} else {
@lib.I32(0)
},
)
true
}
_ => false
}
}
///|
fn precompute_try_partial_select_unary_parent(
ctx : HotPassContext,
func : @ir.HotFunc,
node_id : Int32,
instr : @lib.Instruction,
) -> Bool {
if @ir.hot_child_count(func, node_id) != 1 ||
@ir.hot_node_result_arity(func, node_id) != 1 {
return false
}
let select_id = @ir.hot_child_get(func, node_id, 0)
if select_id < 0 ||
!@ir.hot_node_is_live(func, select_id) ||
@ir.hot_child_count(func, select_id) != 3 {
return false
}
match @ir.hot_node_get(func, select_id).op {
@ir.HotOp::Select => ()
_ => return false
}
let true_value = match
precompute_exact_const_instr(
ctx,
func,
@ir.hot_child_get(func, select_id, 0),
) {
Some(value) => value
None => return false
}
let false_value = match
precompute_exact_const_instr(
ctx,
func,
@ir.hot_child_get(func, select_id, 1),
) {
Some(value) => value
None => return false
}
let folded_true = match precompute_raw_try_fold_unary(true_value, instr) {
Some(value) => value
None => return false
}
let folded_false = match precompute_raw_try_fold_unary(false_value, instr) {
Some(value) => value
None => return false
}
let next_true = match precompute_build_const_instr(func, folded_true) {
Some(value) => value
None => return false
}
let next_false = match precompute_build_const_instr(func, folded_false) {
Some(value) => value
None => return false
}
let results = @ir.hot_type_results(func, @ir.hot_node_get(func, node_id).ty)
if results.length() != 1 {
return false
}
let temp = @ir.hot_build_select(
func,
results[0],
next_true,
next_false,
@ir.hot_child_get(func, select_id, 2),
)
let replacement = @ir.hot_node_get(func, temp)
@ir.hot_delete_node(func, temp)
precompute_replace_node_rehoming_control_label(func, node_id, replacement)
pass_mark_mutated(ctx, func)
true
}
///|
fn precompute_partial_i32_select_for_node(
ctx : HotPassContext,
func : @ir.HotFunc,
node_id : Int32,
depth : Int32,
) -> PrecomputePartialI32Select? {
if depth > 32 || node_id < 0 || !@ir.hot_node_is_live(func, node_id) {
return None
}
if @ir.hot_child_count(func, node_id) == 3 {
match @ir.hot_node_get(func, node_id).op {
@ir.HotOp::Select => {
let true_value = match
precompute_i32_exact_const(
ctx,
func,
@ir.hot_child_get(func, node_id, 0),
) {
Some(@lib.I32(value)) => value
None => return None
}
let false_value = match
precompute_i32_exact_const(
ctx,
func,
@ir.hot_child_get(func, node_id, 1),
) {
Some(@lib.I32(value)) => value
None => return None
}
return Some({
condition_id: @ir.hot_child_get(func, node_id, 2),
true_value,
false_value,
})
}
_ => ()
}
}
if @ir.hot_child_count(func, node_id) != 1 {
return None
}
let instr = match @ir.hot_node_exact_instr(func, node_id) {
Some(instr) => instr
None => return None
}
let inner = match
precompute_partial_i32_select_for_node(
ctx,
func,
@ir.hot_child_get(func, node_id, 0),
depth + 1,
) {
Some(value) => value
None => return None
}
let fold = fn(value : Int32) -> Int32? {
match
precompute_raw_try_fold_unary(
@lib.Instruction::i32_const(@lib.I32(value)),
instr,
) {
Some(@lib.I32Const(@lib.I32(value))) => Some(value)
_ => None
}
}
let true_value = match fold(inner.true_value) {
Some(value) => value
None => return None
}
let false_value = match fold(inner.false_value) {
Some(value) => value
None => return None
}
Some({ condition_id: inner.condition_id, true_value, false_value, })
}
///|
fn precompute_try_partial_select_condition(
ctx : HotPassContext,
func : @ir.HotFunc,
node_id : Int32,
) -> Bool {
if node_id < 0 ||
!@ir.hot_node_is_live(func, node_id) ||
@ir.hot_child_count(func, node_id) != 3 ||
@ir.hot_node_result_arity(func, node_id) != 1 {
return false
}
match @ir.hot_node_get(func, node_id).op {
@ir.HotOp::Select => ()
_ => return false
}
let condition = match
precompute_partial_i32_select_for_node(
ctx,
func,
@ir.hot_child_get(func, node_id, 2),
0,
) {
Some(value) => value
None => return false
}
let outer_true = match
precompute_exact_const_instr(ctx, func, @ir.hot_child_get(func, node_id, 0)) {
Some(value) => value
None => return false
}
let outer_false = match
precompute_exact_const_instr(ctx, func, @ir.hot_child_get(func, node_id, 1)) {
Some(value) => value
None => return false
}
let next_true = match
precompute_build_const_instr(
func,
if condition.true_value != 0 {
outer_true
} else {
outer_false
},
) {
Some(value) => value
None => return false
}
let next_false = match
precompute_build_const_instr(
func,
if condition.false_value != 0 {
outer_true
} else {
outer_false
},
) {
Some(value) => value
None => return false
}
let results = @ir.hot_type_results(func, @ir.hot_node_get(func, node_id).ty)
if results.length() != 1 {
return false
}
let temp = @ir.hot_build_select(
func,
results[0],
next_true,
next_false,
condition.condition_id,
)
let replacement = @ir.hot_node_get(func, temp)
@ir.hot_delete_node(func, temp)
precompute_replace_node_rehoming_control_label(func, node_id, replacement)
pass_mark_mutated(ctx, func)
true
}
///|
fn precompute_try_fold_constant_select(
ctx : HotPassContext,
func : @ir.HotFunc,
node_id : Int32,
) -> Bool {
if node_id < 0 || !@ir.hot_node_is_live(func, node_id) {
return false
}
match @ir.hot_node_get(func, node_id).op {
@ir.HotOp::Select => ()
_ => return false
}
if @ir.hot_child_count(func, node_id) != 3 ||
@ir.hot_node_result_arity(func, node_id) != 1 {
return false
}
let condition_id = @ir.hot_child_get(func, node_id, 2)
if !precompute_is_discardable_value(func, condition_id) {
return false
}
let select_true = match precompute_i32_exact_const(ctx, func, condition_id) {
Some(@lib.I32(0)) => false
Some(@lib.I32(_)) => true
None => return false
}
let selected_slot = if select_true { 0 } else { 1 }
let unselected_slot = if select_true { 1 } else { 0 }
let selected_id = @ir.hot_child_get(func, node_id, selected_slot)
let unselected_id = @ir.hot_child_get(func, node_id, unselected_slot)
if selected_id < 0 ||
!@ir.hot_node_is_live(func, selected_id) ||
!precompute_is_discardable_value(func, unselected_id) {
return false
}
let selected_is_control = @ir.hot_is_control_node(func, selected_id)
precompute_replace_node_rehoming_control_label(
func,
node_id,
@ir.hot_node_get(func, selected_id),
)
if selected_is_control {
@ir.hot_delete_node(func, selected_id)
}
pass_mark_mutated(ctx, func)
true
}
///|
fn precompute_try_partial_select_parent(
ctx : HotPassContext,
func : @ir.HotFunc,
node_id : Int32,
instr : @lib.Instruction,
) -> Bool {
if @ir.hot_child_count(func, node_id) == 1 {
return precompute_try_partial_select_unary_parent(ctx, func, node_id, instr)
}
if @ir.hot_child_count(func, node_id) != 2 ||
@ir.hot_node_result_arity(func, node_id) != 1 {
return false
}
let lhs_id = @ir.hot_child_get(func, node_id, 0)
let rhs_id = @ir.hot_child_get(func, node_id, 1)
let lhs_is_select = if lhs_id >= 0 && @ir.hot_node_is_live(func, lhs_id) {
match @ir.hot_node_get(func, lhs_id).op {
@ir.HotOp::Select => true
_ => false
}
} else {
false
}
let rhs_is_select = if rhs_id >= 0 && @ir.hot_node_is_live(func, rhs_id) {
match @ir.hot_node_get(func, rhs_id).op {
@ir.HotOp::Select => true
_ => false
}
} else {
false
}
let (select_id, other_id, select_is_lhs) = if lhs_is_select {
(lhs_id, rhs_id, true)
} else if rhs_is_select {
(rhs_id, lhs_id, false)
} else {
return false
}
if @ir.hot_child_count(func, select_id) != 3 {
return false
}
let other = match precompute_exact_const_instr(ctx, func, other_id) {
Some(value) => value
None => return false
}
let true_id = @ir.hot_child_get(func, select_id, 0)
let false_id = @ir.hot_child_get(func, select_id, 1)
let condition_id = @ir.hot_child_get(func, select_id, 2)
let true_value = match precompute_exact_const_instr(ctx, func, true_id) {
Some(value) => value
None => return false
}
let false_value = match precompute_exact_const_instr(ctx, func, false_id) {
Some(value) => value
None => return false
}
let folded_true = match
(if select_is_lhs {
precompute_raw_try_fold_binary(true_value, other, instr)
} else {
precompute_raw_try_fold_binary(other, true_value, instr)
}) {
Some(value) => value
None => return false
}
let folded_false = match
(if select_is_lhs {
precompute_raw_try_fold_binary(false_value, other, instr)
} else {
precompute_raw_try_fold_binary(other, false_value, instr)
}) {
Some(value) => value
None => return false
}
let next_true = match precompute_build_const_instr(func, folded_true) {
Some(value) => value
None => return false
}
let next_false = match precompute_build_const_instr(func, folded_false) {
Some(value) => value
None => return false
}
let results = @ir.hot_type_results(func, @ir.hot_node_get(func, node_id).ty)
if results.length() != 1 {
return false
}
let temp = @ir.hot_build_select(
func,
results[0],
next_true,
next_false,
condition_id,
)
let replacement = @ir.hot_node_get(func, temp)
@ir.hot_delete_node(func, temp)
precompute_replace_node_rehoming_control_label(func, node_id, replacement)
pass_mark_mutated(ctx, func)
true
}
///|
priv enum PrecomputeEffectfulChildKeep {
PrecomputeNoEffectKeep
PrecomputeKeepLocalSet(Int32, Int32)
PrecomputeKeepDrop(Int32)
}
///|
fn precompute_effectful_child_plan(
ctx : HotPassContext,
func : @ir.HotFunc,
child_id : Int32,
) -> (@lib.Instruction, PrecomputeEffectfulChildKeep)? {
match precompute_exact_const_instr(ctx, func, child_id) {
Some(value) => return Some((value, PrecomputeNoEffectKeep))
None => ()
}
if child_id < 0 || !@ir.hot_node_is_live(func, child_id) {
return None
}
let child = @ir.hot_node_get(func, child_id)
match child.op {
@ir.HotOp::LocalTee => {
if @ir.hot_child_count(func, child_id) != 1 {
return None
}
let value_id = @ir.hot_child_get(func, child_id, 0)
match precompute_exact_const_instr(ctx, func, value_id) {
Some(value) =>
Some((value, PrecomputeKeepLocalSet(child.imm0, value_id)))
None => None
}
}
@ir.HotOp::Block => {
if @ir.hot_node_result_arity(func, child_id) != 1 {
return None
}
let body_ref = @ir.hot_region_body_ref(func, child_id)
let root_count = @ir.hot_region_root_count(func, body_ref)
if root_count < 2 {
return None
}
let tail_id = @ir.hot_region_get(func, body_ref, root_count - 1)
let value = match precompute_exact_const_instr(ctx, func, tail_id) {
Some(value) => value
None => return None
}
for slot = 0; slot + 1 < root_count; slot = slot + 1 {
let root_id = @ir.hot_region_get(func, body_ref, slot)
if root_id < 0 || !@ir.hot_node_is_live(func, root_id) {
return None
}
match @ir.hot_node_get(func, root_id).op {
@ir.HotOp::Nop => ()
@ir.HotOp::LocalSet | @ir.HotOp::GlobalSet =>
if @ir.hot_child_count(func, root_id) != 1 ||
precompute_exact_const_instr(
ctx,
func,
@ir.hot_child_get(func, root_id, 0),
)
is None {
return None
}
_ => return None
}
}
Some((value, PrecomputeKeepDrop(child_id)))
}
_ => None
}
}
///|
fn precompute_try_fold_effectful_parent(
ctx : HotPassContext,
func : @ir.HotFunc,
node_id : Int32,
instr : @lib.Instruction,
) -> Bool {
let child_count = @ir.hot_child_count(func, node_id)
if (child_count != 1 && child_count != 2) ||
@ir.hot_node_result_arity(func, node_id) != 1 {
return false
}
let plans : Array[(@lib.Instruction, PrecomputeEffectfulChildKeep)] = []
let mut has_effect = false
for slot = 0; slot < child_count; slot = slot + 1 {
let child_id = @ir.hot_child_get(func, node_id, slot)
let plan = match precompute_effectful_child_plan(ctx, func, child_id) {
Some(plan) => plan
None => {
let child = if child_id >= 0 && @ir.hot_node_is_live(func, child_id) {
@ir.hot_node_get(func, child_id)
} else {
return false
}
match child.op {
@ir.HotOp::LocalGet => {
let mut prior_value : @lib.Instruction? = None
for prior_slot = 0; prior_slot < slot; prior_slot = prior_slot + 1 {
let prior_id = @ir.hot_child_get(func, node_id, prior_slot)
if prior_id >= 0 && @ir.hot_node_is_live(func, prior_id) {
let prior = @ir.hot_node_get(func, prior_id)
match prior.op {
@ir.HotOp::LocalTee if prior.imm0 == child.imm0 =>
prior_value = Some(plans[prior_slot].0)
_ => ()
}
}
}
match prior_value {
Some(value) => (value, PrecomputeNoEffectKeep)
None => return false
}
}
_ => return false
}
}
}
match plan.1 {
PrecomputeNoEffectKeep => ()
_ => has_effect = true
}
plans.push(plan)
}
if !has_effect {
return false
}
let folded = if child_count == 1 {
match precompute_raw_try_fold_unary(plans[0].0, instr) {
Some(value) => value
None => return false
}
} else if child_count == 2 {
match precompute_raw_try_fold_binary(plans[0].0, plans[1].0, instr) {
Some(value) => value
None => return false
}
} else if child_count == 3 {
match
precompute_raw_try_fold_ternary(plans[0].0, plans[1].0, plans[2].0, instr) {
Some(value) => value
None => return false
}
} else {
return false
}
let folded_id = match precompute_build_const_instr(func, folded) {
Some(value) => value
None => return false
}
let kept : Array[Int32] = []
for plan in plans {
match plan.1 {
PrecomputeNoEffectKeep => ()
PrecomputeKeepLocalSet(local_id, value_id) =>
kept.push(@ir.hot_build_local_set(func, local_id, value_id))
PrecomputeKeepDrop(child_id) =>
kept.push(@ir.hot_build_drop(func, child_id))
}
}
kept.push(folded_id)
let results = @ir.hot_type_results(func, @ir.hot_node_get(func, node_id).ty)
if results.length() != 1 {
return false
}
let temp = @ir.hot_build_block(
func,
@lib.BlockType::val_type(results[0]),
kept,
)
let replacement = @ir.hot_node_get(func, temp)
@ir.hot_delete_node(func, temp)
precompute_replace_node_rehoming_control_label(func, node_id, replacement)
pass_mark_mutated(ctx, func)
true
}
///|
fn precompute_fresh_struct_allocation_is_discardable(
func : @ir.HotFunc,
node_id : Int32,
) -> Bool {
if node_id < 0 || !@ir.hot_node_is_live(func, node_id) {
return false
}
match @ir.hot_node_exact_instr(func, node_id) {
Some(@lib.StructNew(_)) | Some(@lib.StructNewDefault(_)) => ()
Some(@lib.StructNewDesc(_)) | Some(@lib.StructNewDefaultDesc(_)) => {
let count = @ir.hot_child_count(func, node_id)
if count == 0 {
return false
}
let descriptor = @ir.hot_child_get(func, node_id, count - 1)
match @ir.hot_type_results(func, @ir.hot_node_type(func, descriptor)) {
[@lib.RefTypeValType(ref_type)] if ref_type.is_non_nullable() => ()
_ => return false
}
}
_ => return false
}
for slot = 0; slot < @ir.hot_child_count(func, node_id); slot = slot + 1 {
if !precompute_is_discardable_value(
func,
@ir.hot_child_get(func, node_id, slot),
) {
return false
}
}
true
}
///|
fn precompute_global_is_immutable(
module_ctx : @ir.HotModuleContext,
global_idx : @lib.GlobalIdx,
) -> Bool {
match @ir.hot_module_resolve_global_type(module_ctx, global_idx) {
Some(@lib.GlobalType(_, mutable)) => !mutable
None => false
}
}
///|
fn precompute_global_heap_identity(
module_ctx : @ir.HotModuleContext,
global_idx : @lib.GlobalIdx,
depth : Int32,
) -> PrecomputeHeapIdentity? {
if depth > 32 || !precompute_global_is_immutable(module_ctx, global_idx) {
return None
}
match @ir.hot_module_resolve_global_init(module_ctx, global_idx) {
Some(@lib.Expr([@lib.GlobalGet(global_alias)])) =>
precompute_global_heap_identity(module_ctx, global_alias, depth + 1)
Some(@lib.Expr(instrs)) if instrs.length() > 0 =>
match instrs[instrs.length() - 1] {
@lib.StructNew(_)
| @lib.StructNewDefault(_)
| @lib.StructNewDesc(_)
| @lib.StructNewDefaultDesc(_)
| @lib.ArrayNew(_)
| @lib.ArrayNewDefault(_)
| @lib.ArrayNewFixed(_) => Some(HeapGlobal(global_idx))
_ => None
}
_ => None
}
}
///|
fn precompute_known_heap_pop_values(
stack : Array[PrecomputeKnownHeapValue],
count : Int32,
) -> Array[PrecomputeKnownHeapValue]? {
if count < 0 || stack.length() < count {
return None
}
let start = stack.length() - count
let values : Array[PrecomputeKnownHeapValue] = []
for i = 0; i < count; i = i + 1 {
values.push(stack[start + i])
}
while stack.length() > start {
ignore(stack.pop())
}
Some(values)
}
///|
fn precompute_known_heap_value_from_global(
module_ctx : @ir.HotModuleContext,
global_idx : @lib.GlobalIdx,
depth : Int32,
) -> PrecomputeKnownHeapValue? {
if depth > 32 || !precompute_global_is_immutable(module_ctx, global_idx) {
return None
}
let instrs = match
@ir.hot_module_resolve_global_init(module_ctx, global_idx) {
Some(@lib.Expr(instrs)) => instrs
None => return None
}
let stack : Array[PrecomputeKnownHeapValue] = []
for instr in instrs {
match instr {
@lib.I32Const(_)
| @lib.I64Const(_)
| @lib.F32Const(_)
| @lib.F64Const(_)
| @lib.RefNull(_)
| @lib.RefFunc(_)
| @lib.StringConst(_) => stack.push(KnownEmit(instr))
@lib.GlobalGet(global_alias) =>
match
precompute_known_heap_value_from_global(
module_ctx,
global_alias,
depth + 1,
) {
Some(value) => stack.push(value)
None => return None
}
@lib.StructNew(type_idx) => {
let fields = match
@ir.hot_module_resolve_struct_fields(module_ctx, type_idx) {
Some(fields) => fields
None => return None
}
let values = match
precompute_known_heap_pop_values(stack, fields.length()) {
Some(values) => values
None => return None
}
stack.push(KnownStructFields(type_idx, values))
}
@lib.StructNewDefault(type_idx) =>
stack.push(KnownStructDefault(type_idx))
@lib.ArrayNewFixed(type_idx, @lib.U32(length)) => {
if !precompute_fresh_array_length_is_interpretable(length) {
return None
}
let values = match
precompute_known_heap_pop_values(stack, length.reinterpret_as_int()) {
Some(values) => values
None => return None
}
stack.push(KnownArrayFixed(type_idx, values))
}
@lib.ArrayNew(type_idx) => {
let operands = match precompute_known_heap_pop_values(stack, 2) {
Some(operands) => operands
None => return None
}
let length = match operands[1] {
KnownEmit(@lib.I32Const(@lib.I32(length))) if length >= 0 &&
precompute_fresh_array_length_is_interpretable(
length.reinterpret_as_uint(),
) => length.reinterpret_as_uint()
_ => return None
}
stack.push(KnownArrayFill(type_idx, [operands[0]], length))
}
@lib.ArrayNewDefault(type_idx) => {
let operands = match precompute_known_heap_pop_values(stack, 1) {
Some(operands) => operands
None => return None
}
let length = match operands[0] {
KnownEmit(@lib.I32Const(@lib.I32(length))) if length >= 0 &&
precompute_fresh_array_length_is_interpretable(
length.reinterpret_as_uint(),
) => length.reinterpret_as_uint()
_ => return None
}
stack.push(KnownArrayDefault(type_idx, length))
}
_ => return None
}
}
if stack.length() == 1 {
Some(stack[0])
} else {
None
}
}
///|
fn precompute_global_descriptor_identity(
module_ctx : @ir.HotModuleContext,
global_idx : @lib.GlobalIdx,
depth : Int32,
) -> PrecomputeHeapIdentity? {
if depth > 32 || !precompute_global_is_immutable(module_ctx, global_idx) {
return None
}
match @ir.hot_module_resolve_global_init(module_ctx, global_idx) {
Some(@lib.Expr([@lib.GlobalGet(global_alias)])) =>
precompute_global_descriptor_identity(module_ctx, global_alias, depth + 1)
Some(@lib.Expr(instrs)) if instrs.length() >= 2 =>
match instrs[instrs.length() - 1] {
@lib.StructNewDesc(_) | @lib.StructNewDefaultDesc(_) =>
match instrs[instrs.length() - 2] {
@lib.GlobalGet(descriptor) =>
precompute_global_heap_identity(module_ctx, descriptor, depth + 1)
_ => None
}
_ => None
}
_ => None
}
}
///|
fn precompute_heap_identity_for_node(
ctx : HotPassContext,
func : @ir.HotFunc,
node_id : Int32,
depth : Int32,
) -> PrecomputeHeapIdentity? {
if depth > 32 || node_id < 0 || !@ir.hot_node_is_live(func, node_id) {
return None
}
let node = @ir.hot_node_get(func, node_id)
match @ir.hot_node_exact_instr(func, node_id) {
Some(@lib.RefGetDesc(_)) => {
if @ir.hot_child_count(func, node_id) != 1 {
return None
}
let source_id = @ir.hot_child_get(func, node_id, 0)
match @ir.hot_node_exact_instr(func, source_id) {
Some(@lib.StructNewDesc(_)) | Some(@lib.StructNewDefaultDesc(_)) =>
if @ir.hot_child_count(func, source_id) > 0 {
precompute_heap_identity_for_node(
ctx,
func,
@ir.hot_child_get(
func,
source_id,
@ir.hot_child_count(func, source_id) - 1,
),
depth + 1,
)
} else {
None
}
_ => {
let source = @ir.hot_node_get(func, source_id)
match source.op {
@ir.HotOp::GlobalGet =>
match ctx.module_ctx {
Some(module_ctx) =>
precompute_global_descriptor_identity(
module_ctx,
@lib.GlobalIdx::new(source.imm0.reinterpret_as_uint()),
depth + 1,
)
None => None
}
_ => None
}
}
}
}
_ =>
match node.op {
@ir.HotOp::GlobalGet =>
match ctx.module_ctx {
Some(module_ctx) =>
precompute_global_heap_identity(
module_ctx,
@lib.GlobalIdx::new(node.imm0.reinterpret_as_uint()),
depth + 1,
)
None => None
}
@ir.HotOp::Block => {
let label = @ir.hot_control_node_label(func, node_id)
let body_ref = @ir.hot_region_body_ref(func, node_id)
for slot = 0
slot < @ir.hot_region_root_count(func, body_ref)
slot = slot + 1 {
let root_id = @ir.hot_region_get(func, body_ref, slot)
let mut branch_id = root_id
if root_id >= 0 && @ir.hot_node_is_live(func, root_id) {
let root = @ir.hot_node_get(func, root_id)
match root.op {
@ir.HotOp::Drop if root.child_count == 1 =>
branch_id = @ir.hot_child_get(func, root_id, 0)
_ => ()
}
}
if branch_id >= 0 && @ir.hot_node_is_live(func, branch_id) {
let branch = @ir.hot_node_get(func, branch_id)
match branch.op {
@ir.HotOp::Br if branch.imm0 == label && branch.child_count == 1 =>
return precompute_heap_identity_for_node(
ctx,
func,
@ir.hot_child_get(func, branch_id, 0),
depth + 1,
)
_ => ()
}
}
if precompute_heap_identity_has_alternative_exit(
func, root_id, label,
) {
return None
}
}
None
}
_ =>
if precompute_fresh_struct_allocation_is_discardable(func, node_id) ||
precompute_fresh_array_allocation_is_discardable(func, node_id) {
Some(HeapNode(node_id))
} else {
None
}
}
}
}
///|
fn precompute_descriptor_identity_for_node(
ctx : HotPassContext,
func : @ir.HotFunc,
node_id : Int32,
depth : Int32,
) -> PrecomputeHeapIdentity? {
if depth > 32 || node_id < 0 || !@ir.hot_node_is_live(func, node_id) {
return None
}
match @ir.hot_node_exact_instr(func, node_id) {
Some(@lib.StructNewDesc(_)) | Some(@lib.StructNewDefaultDesc(_)) =>
if @ir.hot_child_count(func, node_id) > 0 {
precompute_heap_identity_for_node(
ctx,
func,
@ir.hot_child_get(
func,
node_id,
@ir.hot_child_count(func, node_id) - 1,
),
depth + 1,
)
} else {
None
}
_ => {
let node = @ir.hot_node_get(func, node_id)
match node.op {
@ir.HotOp::GlobalGet =>
match ctx.module_ctx {
Some(module_ctx) =>
precompute_global_descriptor_identity(
module_ctx,
@lib.GlobalIdx::new(node.imm0.reinterpret_as_uint()),
depth + 1,
)
None => None
}
_ => None
}
}
}
}
///|
fn precompute_known_heap_value_for_plain_node(
ctx : HotPassContext,
func : @ir.HotFunc,
node_id : Int32,
depth : Int32,
) -> PrecomputeKnownHeapValue? {
if depth > 64 || node_id < 0 || !@ir.hot_node_is_live(func, node_id) {
return None
}
match precompute_exact_const_instr(ctx, func, node_id) {
Some(instr) => return Some(KnownEmit(instr))
None => ()
}
let node = @ir.hot_node_get(func, node_id)
match node.op {
@ir.HotOp::RefFunc =>
return Some(
KnownEmit(
@lib.Instruction::ref_func(
@lib.FuncIdx::new(node.imm0.reinterpret_as_uint()),
),
),
)
@ir.HotOp::GlobalGet =>
return match ctx.module_ctx {
Some(module_ctx) =>
precompute_known_heap_value_from_global(
module_ctx,
@lib.GlobalIdx::new(node.imm0.reinterpret_as_uint()),
depth + 1,
)
None => None
}
_ => ()
}
match @ir.hot_node_exact_instr(func, node_id) {
Some(@lib.StructNew(type_idx)) => {
let fields = match ctx.module_ctx {
Some(module_ctx) =>
match @ir.hot_module_resolve_struct_fields(module_ctx, type_idx) {
Some(fields) => fields
None => return None
}
None => return None
}
if @ir.hot_child_count(func, node_id) != fields.length() {
return None
}
let values : Array[PrecomputeKnownHeapValue] = []
for slot = 0; slot < fields.length(); slot = slot + 1 {
match
precompute_known_heap_value_for_plain_node(
ctx,
func,
@ir.hot_child_get(func, node_id, slot),
depth + 1,
) {
Some(value) => values.push(value)
None => return None
}
}
Some(KnownStructFields(type_idx, values))
}
Some(@lib.StructNewDefault(type_idx)) => Some(KnownStructDefault(type_idx))
Some(@lib.StructGet(type_idx, @lib.U32(field)))
| Some(@lib.StructGetS(type_idx, @lib.U32(field)))
| Some(@lib.StructGetU(type_idx, @lib.U32(field))) => {
if @ir.hot_child_count(func, node_id) != 1 {
return None
}
let source = match
precompute_known_heap_value_for_plain_node(
ctx,
func,
@ir.hot_child_get(func, node_id, 0),
depth + 1,
) {
Some(value) => value
None => return None
}
let signed = @ir.hot_node_exact_instr(func, node_id) ==
Some(@lib.Instruction::struct_get_s(type_idx, @lib.U32(field)))
let unsigned = @ir.hot_node_exact_instr(func, node_id) ==
Some(@lib.Instruction::struct_get_u(type_idx, @lib.U32(field)))
precompute_known_heap_struct_get(
ctx, source, type_idx, field, signed, unsigned,
)
}
Some(@lib.RefCast(nullable, _, target)) => {
if @ir.hot_child_count(func, node_id) != 1 {
return None
}
let source = match
precompute_known_heap_value_for_plain_node(
ctx,
func,
@ir.hot_child_get(func, node_id, 0),
depth + 1,
) {
Some(value) => value
None => return None
}
if precompute_known_heap_value_matches_cast(source, nullable, target) {
Some(source)
} else {
None
}
}
_ => None
}
}
///|
fn precompute_partial_known_heap_select_for_node(
ctx : HotPassContext,
func : @ir.HotFunc,
node_id : Int32,
depth : Int32,
) -> PrecomputePartialKnownHeapSelect? {
if depth > 64 || node_id < 0 || !@ir.hot_node_is_live(func, node_id) {
return None
}
if @ir.hot_child_count(func, node_id) == 3 {
match @ir.hot_node_get(func, node_id).op {
@ir.HotOp::Select => {
let true_value = match
precompute_known_heap_value_for_plain_node(
ctx,
func,
@ir.hot_child_get(func, node_id, 0),
depth + 1,
) {
Some(value) => value
None => return None
}
let false_value = match
precompute_known_heap_value_for_plain_node(
ctx,
func,
@ir.hot_child_get(func, node_id, 1),
depth + 1,
) {
Some(value) => value
None => return None
}
return Some({
condition_id: @ir.hot_child_get(func, node_id, 2),
true_value,
false_value,
})
}
_ => ()
}
}
match @ir.hot_node_exact_instr(func, node_id) {
Some(@lib.StructGet(type_idx, @lib.U32(field)))
| Some(@lib.StructGetS(type_idx, @lib.U32(field)))
| Some(@lib.StructGetU(type_idx, @lib.U32(field))) => {
if @ir.hot_child_count(func, node_id) != 1 {
return None
}
let inner = match
precompute_partial_known_heap_select_for_node(
ctx,
func,
@ir.hot_child_get(func, node_id, 0),
depth + 1,
) {
Some(value) => value
None => return None
}
let signed = @ir.hot_node_exact_instr(func, node_id) ==
Some(@lib.Instruction::struct_get_s(type_idx, @lib.U32(field)))
let unsigned = @ir.hot_node_exact_instr(func, node_id) ==
Some(@lib.Instruction::struct_get_u(type_idx, @lib.U32(field)))
let true_value = match
precompute_known_heap_struct_get(
ctx,
inner.true_value,
type_idx,
field,
signed,
unsigned,
) {
Some(value) => value
None => return None
}
let false_value = match
precompute_known_heap_struct_get(
ctx,
inner.false_value,
type_idx,
field,
signed,
unsigned,
) {
Some(value) => value
None => return None
}
Some({ condition_id: inner.condition_id, true_value, false_value, })
}
_ => None
}
}
///|
fn precompute_known_heap_ref_is_null(
value : PrecomputeKnownHeapValue,
) -> Int32? {
match value {
KnownEmit(@lib.RefNull(_)) => Some(1)
KnownEmit(@lib.RefFunc(_))
| KnownEmit(@lib.StringConst(_))
| KnownStructFields(_, _)
| KnownStructDefault(_)
| KnownArrayFixed(_, _)
| KnownArrayFill(_, _, _)
| KnownArrayDefault(_, _) => Some(0)
_ => None
}
}
///|
fn precompute_build_known_emit(
ctx : HotPassContext,
func : @ir.HotFunc,
instr : @lib.Instruction,
) -> Int32? {
match instr {
@lib.RefFunc(func_idx) => {
let type_idx = match ctx.module_ctx {
Some(module_ctx) =>
match @ir.hot_module_resolve_func_type_idx(module_ctx, func_idx) {
Some(type_idx) => type_idx
None => return None
}
None => return None
}
Some(
@ir.hot_build_ref_func(
func,
func_idx,
@lib.RefType::new(
false,
@lib.HeapType::new(type_idx),
exact=ctx.module_ctx.unwrap().uses_custom_descriptors,
),
),
)
}
_ => precompute_build_const_instr(func, instr)
}
}
///|
fn precompute_try_partial_heap_select_parent(
ctx : HotPassContext,
func : @ir.HotFunc,
node_id : Int32,
) -> Bool {
let partial : PrecomputePartialKnownHeapSelect = match
@ir.hot_node_get(func, node_id).op {
@ir.HotOp::RefIsNull => {
if @ir.hot_child_count(func, node_id) != 1 {
return false
}
let inner = match
precompute_partial_known_heap_select_for_node(
ctx,
func,
@ir.hot_child_get(func, node_id, 0),
0,
) {
Some(value) => value
None => return false
}
let true_value = match
precompute_known_heap_ref_is_null(inner.true_value) {
Some(value) => KnownEmit(@lib.Instruction::i32_const(@lib.I32(value)))
None => return false
}
let false_value = match
precompute_known_heap_ref_is_null(inner.false_value) {
Some(value) => KnownEmit(@lib.Instruction::i32_const(@lib.I32(value)))
None => return false
}
{ condition_id: inner.condition_id, true_value, false_value, }
}
_ => {
match @ir.hot_node_exact_instr(func, node_id) {
Some(@lib.StructGet(_, _))
| Some(@lib.StructGetS(_, _))
| Some(@lib.StructGetU(_, _)) => ()
_ => return false
}
match
precompute_partial_known_heap_select_for_node(ctx, func, node_id, 0) {
Some(value) => value
None => return false
}
}
}
let true_instr = match partial.true_value {
KnownEmit(instr) => instr
_ => return false
}
let false_instr = match partial.false_value {
KnownEmit(instr) => instr
_ => return false
}
let true_id = match precompute_build_known_emit(ctx, func, true_instr) {
Some(value) => value
None => return false
}
let false_id = match precompute_build_known_emit(ctx, func, false_instr) {
Some(value) => value
None => return false
}
let results = @ir.hot_type_results(func, @ir.hot_node_get(func, node_id).ty)
if results.length() != 1 {
return false
}
let temp = @ir.hot_build_select(
func,
results[0],
true_id,
false_id,
partial.condition_id,
)
let replacement = @ir.hot_node_get(func, temp)
@ir.hot_delete_node(func, temp)
precompute_replace_node_rehoming_control_label(func, node_id, replacement)
pass_mark_mutated(ctx, func)
true
}
///|
fn precompute_try_fold_descriptor_branch(
ctx : HotPassContext,
func : @ir.HotFunc,
node_id : Int32,
) -> Bool {
let (target, branch_on_equal) = match
@ir.hot_node_exact_instr(func, node_id) {
Some(@lib.BrOnCastDescEq(_, _, _)) =>
(@ir.hot_node_get(func, node_id).imm0, true)
Some(@lib.BrOnCastDescEqFail(_, _, _)) =>
(@ir.hot_node_get(func, node_id).imm0, false)
_ => return false
}
if @ir.hot_child_count(func, node_id) != 2 {
return false
}
let source_id = @ir.hot_child_get(func, node_id, 0)
let descriptor_id = @ir.hot_child_get(func, node_id, 1)
// Knowing the descriptor's identity does not make its evaluation erasable:
// a block can perform effects before yielding that same descriptor.
if !precompute_is_discardable_value(func, descriptor_id) {
return false
}
let source_descriptor = match
precompute_descriptor_identity_for_node(ctx, func, source_id, 0) {
Some(identity) => identity
None => return false
}
let supplied_descriptor = match
precompute_heap_identity_for_node(ctx, func, descriptor_id, 0) {
Some(identity) => identity
None => return false
}
let taken = (source_descriptor == supplied_descriptor) == branch_on_equal
if taken {
let owner = @ir.hot_label_owner(func, target)
if owner >= 0 &&
@ir.hot_node_is_live(func, owner) &&
(match @ir.hot_node_get(func, owner).op {
@ir.HotOp::Block => true
_ => false
}) {
let body_ref = @ir.hot_region_body_ref(func, owner)
if @ir.hot_region_root_count(func, body_ref) == 1 &&
@ir.hot_region_get(func, body_ref, 0) == node_id {
@ir.hot_region_replace_body(func, body_ref, [source_id])
@ir.hot_delete_node(func, node_id)
pass_mark_mutated(ctx, func)
return true
}
}
let replacement_id = @ir.hot_build_br(func, target, values=[source_id])
let replacement = @ir.hot_node_get(func, replacement_id)
@ir.hot_delete_node(func, replacement_id)
precompute_replace_node_rehoming_control_label(func, node_id, replacement)
} else {
let replacement = @ir.hot_node_get(func, source_id)
precompute_replace_node_rehoming_control_label(func, node_id, replacement)
}
pass_mark_mutated(ctx, func)
true
}
///|
fn precompute_replace_gc_identity_with_const(
ctx : HotPassContext,
func : @ir.HotFunc,
node_id : Int32,
value : @lib.I32,
) -> Bool {
let kept : Array[Int32] = []
for slot = 0; slot < @ir.hot_child_count(func, node_id); slot = slot + 1 {
let child_id = @ir.hot_child_get(func, node_id, slot)
if child_id < 0 || !@ir.hot_node_is_live(func, child_id) {
return false
}
match @ir.hot_node_get(func, child_id).op {
@ir.HotOp::LocalTee => kept.push(@ir.hot_build_drop(func, child_id))
_ =>
if !precompute_is_discardable_value(func, child_id) &&
!precompute_fresh_struct_allocation_is_discardable(func, child_id) &&
!precompute_fresh_array_allocation_is_discardable(func, child_id) {
return false
}
}
}
if kept.length() == 0 {
precompute_replace_with_const_i32(ctx, func, node_id, value)
return true
}
kept.push(@ir.hot_build_const_i32(func, value))
let temp = @ir.hot_build_block(
func,
@lib.BlockType::val_type(@lib.ValType::i32()),
kept,
)
let replacement = @ir.hot_node_get(func, temp)
@ir.hot_delete_node(func, temp)
precompute_replace_node_rehoming_control_label(func, node_id, replacement)
pass_mark_mutated(ctx, func)
true
}
///|
fn precompute_try_fold_null_ref_cast(
ctx : HotPassContext,
func : @ir.HotFunc,
node_id : Int32,
) -> Bool {
let (nullable, exact, heap_type) = match
@ir.hot_node_exact_instr(func, node_id) {
Some(@lib.RefCast(nullable, exact, heap_type)) =>
(nullable, exact, heap_type)
_ => return false
}
if !nullable || @ir.hot_child_count(func, node_id) != 1 {
return false
}
let child_id = @ir.hot_child_get(func, node_id, 0)
if child_id < 0 ||
!@ir.hot_node_is_live(func, child_id) ||
(match @ir.hot_node_get(func, child_id).op {
@ir.HotOp::RefNull => false
_ => true
}) {
return false
}
precompute_replace_with_const_instr(
ctx,
func,
node_id,
@lib.Instruction::ref_null_type(@lib.RefType::new(true, heap_type, exact~)),
)
}
///|
fn precompute_try_fold_null_aggregate_trap(
ctx : HotPassContext,
func : @ir.HotFunc,
node_id : Int32,
) -> Bool {
match @ir.hot_node_exact_instr(func, node_id) {
Some(@lib.StructGet(_, _))
| Some(@lib.StructGetS(_, _))
| Some(@lib.StructGetU(_, _))
| Some(@lib.ArrayGet(_))
| Some(@lib.ArrayGetS(_))
| Some(@lib.ArrayGetU(_))
| Some(@lib.ArrayLen) => ()
_ => return false
}
if @ir.hot_child_count(func, node_id) == 0 {
return false
}
let ref_id = @ir.hot_child_get(func, node_id, 0)
if ref_id < 0 ||
!@ir.hot_node_is_live(func, ref_id) ||
(match @ir.hot_node_get(func, ref_id).op {
@ir.HotOp::RefNull => false
_ => true
}) {
return false
}
for slot = 0; slot < @ir.hot_child_count(func, node_id); slot = slot + 1 {
if !precompute_is_discardable_value(
func,
@ir.hot_child_get(func, node_id, slot),
) {
return false
}
}
let temp = @ir.hot_build_unreachable(func)
let replacement = @ir.hot_node_get(func, temp)
@ir.hot_delete_node(func, temp)
precompute_replace_node_rehoming_control_label(func, node_id, replacement)
pass_mark_mutated(ctx, func)
true
}
///|
fn precompute_try_fold_ref_is_null(
ctx : HotPassContext,
func : @ir.HotFunc,
node_id : Int32,
) -> Bool {
match @ir.hot_node_get(func, node_id).op {
@ir.HotOp::RefIsNull => ()
_ => return false
}
if @ir.hot_child_count(func, node_id) != 1 {
return false
}
let ref_id = @ir.hot_child_get(func, node_id, 0)
if ref_id < 0 || !@ir.hot_node_is_live(func, ref_id) {
return false
}
if @ir.hot_node_get(func, ref_id).op is @ir.HotOp::RefNull {
return precompute_replace_gc_identity_with_const(
ctx,
func,
node_id,
@lib.I32(1),
)
}
if precompute_heap_identity_for_node(ctx, func, ref_id, 0) is Some(_) ||
precompute_fresh_struct_allocation_is_discardable(func, ref_id) ||
precompute_fresh_array_allocation_is_discardable(func, ref_id) {
return precompute_replace_gc_identity_with_const(
ctx,
func,
node_id,
@lib.I32(0),
)
}
false
}
///|
fn precompute_try_partial_ref_eq_select(
ctx : HotPassContext,
func : @ir.HotFunc,
node_id : Int32,
) -> Bool {
match @ir.hot_node_exact_instr(func, node_id) {
Some(@lib.RefEq) => ()
_ => return false
}
if @ir.hot_child_count(func, node_id) != 2 {
return false
}
let lhs = @ir.hot_child_get(func, node_id, 0)
let rhs = @ir.hot_child_get(func, node_id, 1)
let lhs_select = lhs >= 0 &&
@ir.hot_node_is_live(func, lhs) &&
(match @ir.hot_node_get(func, lhs).op {
@ir.HotOp::Select => true
_ => false
})
let rhs_select = rhs >= 0 &&
@ir.hot_node_is_live(func, rhs) &&
(match @ir.hot_node_get(func, rhs).op {
@ir.HotOp::Select => true
_ => false
})
let (select_id, other_id) = if lhs_select {
(lhs, rhs)
} else if rhs_select {
(rhs, lhs)
} else {
return false
}
if @ir.hot_child_count(func, select_id) != 3 {
return false
}
let other = match precompute_heap_identity_for_node(ctx, func, other_id, 0) {
Some(value) => value
None => return false
}
let arm_value = fn(slot : Int32) -> Int32? {
match
precompute_heap_identity_for_node(
ctx,
func,
@ir.hot_child_get(func, select_id, slot),
0,
) {
Some(value) => Some(if value == other { 1 } else { 0 })
None => None
}
}
let true_value = match arm_value(0) {
Some(value) => value
None => return false
}
let false_value = match arm_value(1) {
Some(value) => value
None => return false
}
let temp = @ir.hot_build_select(
func,
@lib.ValType::i32(),
@ir.hot_build_const_i32(func, @lib.I32(true_value)),
@ir.hot_build_const_i32(func, @lib.I32(false_value)),
@ir.hot_child_get(func, select_id, 2),
)
let replacement = @ir.hot_node_get(func, temp)
@ir.hot_delete_node(func, temp)
precompute_replace_node_rehoming_control_label(func, node_id, replacement)
pass_mark_mutated(ctx, func)
true
}
///|
fn precompute_try_fold_gc_identity(
ctx : HotPassContext,
func : @ir.HotFunc,
node_id : Int32,
) -> Bool {
match @ir.hot_node_exact_instr(func, node_id) {
Some(@lib.RefEq) => ()
_ => return false
}
if @ir.hot_child_count(func, node_id) != 2 {
return false
}
let lhs = @ir.hot_child_get(func, node_id, 0)
let rhs = @ir.hot_child_get(func, node_id, 1)
let lhs_null = lhs >= 0 &&
@ir.hot_node_is_live(func, lhs) &&
(match @ir.hot_node_get(func, lhs).op {
@ir.HotOp::RefNull => true
_ => false
})
let rhs_null = rhs >= 0 &&
@ir.hot_node_is_live(func, rhs) &&
(match @ir.hot_node_get(func, rhs).op {
@ir.HotOp::RefNull => true
_ => false
})
if lhs_null && rhs_null {
return precompute_replace_gc_identity_with_const(
ctx,
func,
node_id,
@lib.I32(1),
)
}
let lhs_identity = precompute_heap_identity_for_node(ctx, func, lhs, 0)
let rhs_identity = precompute_heap_identity_for_node(ctx, func, rhs, 0)
match (lhs_identity, rhs_identity) {
(Some(lhs_identity), Some(rhs_identity)) =>
return precompute_replace_gc_identity_with_const(
ctx,
func,
node_id,
@lib.I32(if lhs_identity == rhs_identity { 1 } else { 0 }),
)
_ => ()
}
let lhs_allocation = precompute_fresh_struct_allocation_is_discardable(
func, lhs,
) ||
precompute_fresh_array_allocation_is_discardable(func, lhs) ||
lhs_identity is Some(_)
let rhs_allocation = precompute_fresh_struct_allocation_is_discardable(
func, rhs,
) ||
precompute_fresh_array_allocation_is_discardable(func, rhs) ||
rhs_identity is Some(_)
if (lhs_null && rhs_allocation) || (rhs_null && lhs_allocation) {
return precompute_replace_gc_identity_with_const(
ctx,
func,
node_id,
@lib.I32(0),
)
}
if lhs_allocation && rhs_allocation {
return precompute_replace_gc_identity_with_const(
ctx,
func,
node_id,
@lib.I32(if lhs == rhs { 1 } else { 0 }),
)
}
false
}
///|
fn precompute_try_fold_fresh_ref_test(
ctx : HotPassContext,
func : @ir.HotFunc,
node_id : Int32,
) -> Bool {
let target = match @ir.hot_node_exact_instr(func, node_id) {
Some(@lib.RefTest(_, _, target)) => target
_ => return false
}
if @ir.hot_child_count(func, node_id) != 1 {
return false
}
let ref_id = @ir.hot_child_get(func, node_id, 0)
if !precompute_fresh_struct_allocation_is_discardable(func, ref_id) {
return false
}
let type_idx = match @ir.hot_node_exact_instr(func, ref_id) {
Some(@lib.StructNew(type_idx)) | Some(@lib.StructNewDefault(type_idx)) =>
type_idx
_ => return false
}
if target != @lib.HeapType::new(type_idx) {
return false
}
precompute_replace_with_const_i32(ctx, func, node_id, @lib.I32(1))
true
}
///|
fn precompute_atomic_aggregate_read_can_fold(
ctx : HotPassContext,
order : @lib.AtomicOrder,
type_idx : @lib.TypeIdx,
) -> Bool {
match order {
@lib.SeqCst => false
@lib.AcqRel | @lib.Relaxed => {
let module_ctx = match ctx.module_ctx {
Some(module_ctx) => module_ctx
None => return false
}
match @ir.hot_module_resolve_subtype(module_ctx, type_idx) {
Some(subtype) => !subtype.metadata().is_shared()
None => false
}
}
}
}
///|
fn precompute_try_fold_fresh_struct_get_from_allocation(
ctx : HotPassContext,
func : @ir.HotFunc,
node_id : Int32,
allocation_id : Int32,
) -> Bool {
let (type_idx, raw_field, signed, unsigned, atomic_order) = match
@ir.hot_node_exact_instr(func, node_id) {
Some(@lib.StructGet(type_idx, @lib.U32(raw_field))) =>
(type_idx, raw_field, false, false, None)
Some(@lib.StructGetS(type_idx, @lib.U32(raw_field))) =>
(type_idx, raw_field, true, false, None)
Some(@lib.StructGetU(type_idx, @lib.U32(raw_field))) =>
(type_idx, raw_field, false, true, None)
Some(@lib.StructAtomicGet(order, type_idx, @lib.U32(raw_field))) =>
(type_idx, raw_field, false, false, Some(order))
Some(@lib.StructAtomicGetS(order, type_idx, @lib.U32(raw_field))) =>
(type_idx, raw_field, true, false, Some(order))
Some(@lib.StructAtomicGetU(order, type_idx, @lib.U32(raw_field))) =>
(type_idx, raw_field, false, true, Some(order))
_ => return false
}
match atomic_order {
Some(order) =>
if !precompute_atomic_aggregate_read_can_fold(ctx, order, type_idx) {
return false
}
None => ()
}
if @ir.hot_child_count(func, node_id) != 1 {
return false
}
let ref_id = allocation_id
let defaulted = match @ir.hot_node_exact_instr(func, ref_id) {
Some(@lib.StructNew(new_type_idx)) if new_type_idx == type_idx => false
Some(@lib.StructNewDefault(new_type_idx)) if new_type_idx == type_idx =>
true
_ => return false
}
let module_ctx = match ctx.module_ctx {
Some(module_ctx) => module_ctx
None => return false
}
let fields = match
@ir.hot_module_resolve_struct_fields(module_ctx, type_idx) {
Some(fields) => fields
None => return false
}
let field_idx = raw_field.reinterpret_as_int()
let field = match fields.get(field_idx) {
Some(field) => field
None => return false
}
if field.is_mutable() {
return false
}
let storage = field.get_storage_type()
let value = if defaulted {
if @ir.hot_child_count(func, ref_id) != 0 {
return false
}
match precompute_default_const_for_storage(storage) {
Some(value) => value
None => return false
}
} else {
if @ir.hot_child_count(func, ref_id) != fields.length() {
return false
}
for slot = 0; slot < @ir.hot_child_count(func, ref_id); slot = slot + 1 {
let child_id = @ir.hot_child_get(func, ref_id, slot)
if slot != field_idx && !precompute_is_discardable_value(func, child_id) {
return false
}
}
match
precompute_exact_const_instr(
ctx,
func,
@ir.hot_child_get(func, ref_id, field_idx),
) {
Some(value) => value
None => return false
}
}
let unpacked = match
precompute_unpack_array_value(value, storage, signed, unsigned) {
Some(value) => value
None => return false
}
precompute_replace_with_const_instr_preserving_first_local_tee(
ctx, func, node_id, unpacked,
)
}
///|
fn precompute_try_fold_fresh_struct_get(
ctx : HotPassContext,
func : @ir.HotFunc,
node_id : Int32,
) -> Bool {
if @ir.hot_child_count(func, node_id) != 1 {
return false
}
precompute_try_fold_fresh_struct_get_from_allocation(
ctx,
func,
node_id,
@ir.hot_child_get(func, node_id, 0),
)
}
///|
fn precompute_fresh_array_allocation_is_discardable(
func : @ir.HotFunc,
node_id : Int32,
) -> Bool {
if node_id < 0 || !@ir.hot_node_is_live(func, node_id) {
return false
}
match @ir.hot_node_exact_instr(func, node_id) {
Some(@lib.ArrayNew(_)) | Some(@lib.ArrayNewDefault(_)) => {
let count = @ir.hot_child_count(func, node_id)
if count == 0 {
return false
}
let length = @ir.hot_child_get(func, node_id, count - 1)
match precompute_i32_const(func, length) {
Some(@lib.I32(value)) if value >= 0 &&
precompute_fresh_array_length_is_interpretable(
value.reinterpret_as_uint(),
) => ()
_ => return false
}
}
Some(@lib.ArrayNewFixed(_, _)) => ()
_ => return false
}
for slot = 0; slot < @ir.hot_child_count(func, node_id); slot = slot + 1 {
if !precompute_is_discardable_value(
func,
@ir.hot_child_get(func, node_id, slot),
) {
return false
}
}
true
}
///| Binaryen v130's interpreter rejects GC arrays whose literal count reaches
///| its 1 GiB `DataLimit`. The v130 64-bit oracle uses 24-byte `Literal`s, so
///|
/// 44,739,242 is the first rejected length.
fn precompute_fresh_array_length_is_interpretable(length : UInt32) -> Bool {
length < 44739242U
}
///|
fn precompute_fresh_array_length(
ctx : HotPassContext,
func : @ir.HotFunc,
node_id : Int32,
) -> @lib.I32? {
match @ir.hot_node_exact_instr(func, node_id) {
Some(@lib.ArrayNewFixed(_, @lib.U32(length))) if precompute_fresh_array_length_is_interpretable(
length,
) => Some(@lib.I32(length.reinterpret_as_int()))
Some(@lib.ArrayNew(_)) if @ir.hot_child_count(func, node_id) == 2 =>
match
precompute_i32_exact_const(
ctx,
func,
@ir.hot_child_get(func, node_id, 1),
) {
Some(@lib.I32(length)) if length >= 0 &&
precompute_fresh_array_length_is_interpretable(
length.reinterpret_as_uint(),
) => Some(@lib.I32(length))
_ => None
}
Some(@lib.ArrayNewDefault(_)) if @ir.hot_child_count(func, node_id) == 1 =>
match
precompute_i32_exact_const(
ctx,
func,
@ir.hot_child_get(func, node_id, 0),
) {
Some(@lib.I32(length)) if length >= 0 &&
precompute_fresh_array_length_is_interpretable(
length.reinterpret_as_uint(),
) => Some(@lib.I32(length))
_ => None
}
_ => None
}
}
///|
fn precompute_default_const_for_storage(
storage : @lib.StorageType,
) -> @lib.Instruction? {
match storage {
@lib.PackTypeStorageType(_)
| @lib.ValTypeStorageType(@lib.NumTypeValType(@lib.I32NumType)) =>
Some(@lib.Instruction::i32_const(@lib.I32(0)))
@lib.ValTypeStorageType(@lib.NumTypeValType(@lib.I64NumType)) =>
Some(@lib.Instruction::i64_const(@lib.I64(0L)))
@lib.ValTypeStorageType(@lib.NumTypeValType(@lib.F32NumType)) =>
Some(
@lib.Instruction::f32_const(@lib.F32(Float::reinterpret_from_uint(0U))),
)
@lib.ValTypeStorageType(@lib.NumTypeValType(@lib.F64NumType)) =>
Some(@lib.Instruction::f64_const(@lib.F64(0.0)))
@lib.ValTypeStorageType(@lib.RefTypeValType(ref_type)) if ref_type.is_nullable() =>
Some(@lib.Instruction::ref_null_type(ref_type))
_ => None
}
}
///|
fn precompute_fresh_array_get_value(
ctx : HotPassContext,
func : @ir.HotFunc,
allocation_id : Int32,
type_idx : @lib.TypeIdx,
index : UInt32,
storage : @lib.StorageType,
) -> @lib.Instruction? {
match @ir.hot_node_exact_instr(func, allocation_id) {
Some(@lib.ArrayNewFixed(new_type_idx, @lib.U32(length))) if new_type_idx ==
type_idx &&
precompute_fresh_array_length_is_interpretable(length) &&
index < length => {
let slot = index.reinterpret_as_int()
if @ir.hot_child_count(func, allocation_id) != length.reinterpret_as_int() {
return None
}
precompute_exact_const_instr(
ctx,
func,
@ir.hot_child_get(func, allocation_id, slot),
)
}
Some(@lib.ArrayNew(new_type_idx)) if new_type_idx == type_idx &&
@ir.hot_child_count(func, allocation_id) == 2 => {
let length = match
precompute_i32_exact_const(
ctx,
func,
@ir.hot_child_get(func, allocation_id, 1),
) {
Some(@lib.I32(length)) if length >= 0 &&
precompute_fresh_array_length_is_interpretable(
length.reinterpret_as_uint(),
) => length.reinterpret_as_uint()
_ => return None
}
if index >= length {
return None
}
precompute_exact_const_instr(
ctx,
func,
@ir.hot_child_get(func, allocation_id, 0),
)
}
Some(@lib.ArrayNewDefault(new_type_idx)) if new_type_idx == type_idx &&
@ir.hot_child_count(func, allocation_id) == 1 => {
let length = match
precompute_i32_exact_const(
ctx,
func,
@ir.hot_child_get(func, allocation_id, 0),
) {
Some(@lib.I32(length)) if length >= 0 &&
precompute_fresh_array_length_is_interpretable(
length.reinterpret_as_uint(),
) => length.reinterpret_as_uint()
_ => return None
}
if index >= length {
return None
}
precompute_default_const_for_storage(storage)
}
_ => None
}
}
///|
fn precompute_unpack_array_value(
value : @lib.Instruction,
storage : @lib.StorageType,
signed : Bool,
unsigned : Bool,
) -> @lib.Instruction? {
match storage {
@lib.ValTypeStorageType(_) => Some(value)
@lib.PackTypeStorageType(pack_type) => {
let raw = match value {
@lib.I32Const(@lib.I32(raw)) => raw
_ => return None
}
match (pack_type, signed, unsigned) {
(@lib.I8PackType, true, false) =>
Some(@lib.Instruction::i32_const(@lib.I32(raw << 24 >> 24)))
(@lib.I8PackType, false, true) =>
Some(@lib.Instruction::i32_const(@lib.I32(raw & 0xff)))
(@lib.I16PackType, true, false) =>
Some(@lib.Instruction::i32_const(@lib.I32(raw << 16 >> 16)))
(@lib.I16PackType, false, true) =>
Some(@lib.Instruction::i32_const(@lib.I32(raw & 0xffff)))
_ => None
}
}
}
}
///|
fn precompute_try_fold_fresh_array_len_from_allocation(
ctx : HotPassContext,
func : @ir.HotFunc,
node_id : Int32,
allocation_id : Int32,
) -> Bool {
match @ir.hot_node_exact_instr(func, node_id) {
Some(@lib.ArrayLen) => ()
_ => return false
}
if @ir.hot_child_count(func, node_id) != 1 {
return false
}
if !precompute_fresh_array_allocation_is_discardable(func, allocation_id) {
return false
}
let length = match precompute_fresh_array_length(ctx, func, allocation_id) {
Some(length) => length
None => return false
}
precompute_replace_with_const_instr_preserving_first_local_tee(
ctx,
func,
node_id,
@lib.Instruction::i32_const(length),
)
}
///|
fn precompute_try_fold_fresh_array_len(
ctx : HotPassContext,
func : @ir.HotFunc,
node_id : Int32,
) -> Bool {
if @ir.hot_child_count(func, node_id) != 1 {
return false
}
precompute_try_fold_fresh_array_len_from_allocation(
ctx,
func,
node_id,
@ir.hot_child_get(func, node_id, 0),
)
}
///|
fn precompute_try_fold_fresh_array_get_from_allocation(
ctx : HotPassContext,
func : @ir.HotFunc,
node_id : Int32,
allocation_id : Int32,
) -> Bool {
let (type_idx, signed, unsigned, atomic_order) = match
@ir.hot_node_exact_instr(func, node_id) {
Some(@lib.ArrayGet(type_idx)) => (type_idx, false, false, None)
Some(@lib.ArrayGetS(type_idx)) => (type_idx, true, false, None)
Some(@lib.ArrayGetU(type_idx)) => (type_idx, false, true, None)
Some(@lib.ArrayAtomicGet(order, type_idx)) =>
(type_idx, false, false, Some(order))
Some(@lib.ArrayAtomicGetS(order, type_idx)) =>
(type_idx, true, false, Some(order))
Some(@lib.ArrayAtomicGetU(order, type_idx)) =>
(type_idx, false, true, Some(order))
_ => return false
}
match atomic_order {
Some(order) =>
if !precompute_atomic_aggregate_read_can_fold(ctx, order, type_idx) {
return false
}
None => ()
}
if @ir.hot_child_count(func, node_id) != 2 {
return false
}
if !precompute_fresh_array_allocation_is_discardable(func, allocation_id) {
return false
}
let index = match
precompute_i32_exact_const(ctx, func, @ir.hot_child_get(func, node_id, 1)) {
Some(@lib.I32(index)) => index.reinterpret_as_uint()
None => return false
}
let module_ctx = match ctx.module_ctx {
Some(module_ctx) => module_ctx
None => return false
}
let field = match @ir.hot_module_resolve_array_field(module_ctx, type_idx) {
Some(field) => field
None => return false
}
if field.is_mutable() {
return false
}
let storage = field.get_storage_type()
let value = match
precompute_fresh_array_get_value(
ctx, func, allocation_id, type_idx, index, storage,
) {
Some(value) => value
None => return false
}
let unpacked = match
precompute_unpack_array_value(value, storage, signed, unsigned) {
Some(value) => value
None => return false
}
precompute_replace_with_const_instr_preserving_first_local_tee(
ctx, func, node_id, unpacked,
)
}
///|
fn precompute_try_fold_fresh_array_get(
ctx : HotPassContext,
func : @ir.HotFunc,
node_id : Int32,
) -> Bool {
if @ir.hot_child_count(func, node_id) != 2 {
return false
}
precompute_try_fold_fresh_array_get_from_allocation(
ctx,
func,
node_id,
@ir.hot_child_get(func, node_id, 0),
)
}
///|
fn precompute_try_fold_string_slice(
ctx : HotPassContext,
func : @ir.HotFunc,
node_id : Int32,
) -> Bool {
match @ir.hot_node_exact_instr(func, node_id) {
Some(@lib.StringViewWtf16Slice) => ()
_ => return false
}
if @ir.hot_child_count(func, node_id) != 3 {
return false
}
let bytes = match
precompute_exact_const_instr(ctx, func, @ir.hot_child_get(func, node_id, 0)) {
Some(@lib.StringConst(bytes)) => bytes
_ => return false
}
let start = match
precompute_i32_exact_const(ctx, func, @ir.hot_child_get(func, node_id, 1)) {
Some(@lib.I32(value)) => value
None => return false
}
let end = match
precompute_i32_exact_const(ctx, func, @ir.hot_child_get(func, node_id, 2)) {
Some(@lib.I32(value)) => value
None => return false
}
let units = match precompute_string_bytes_to_utf16(bytes) {
Some(units) => units
None => return false
}
if start < 0 || end < start || end > units.length() {
return false
}
let sliced = match
precompute_utf16_units_to_valid_utf8(units, start, end - start) {
Some(bytes) => bytes
None => return false
}
precompute_replace_with_const_instr(
ctx,
func,
node_id,
@lib.Instruction::string_const(sliced),
)
}
///|
fn precompute_try_fold_fresh_string_new_wtf16(
ctx : HotPassContext,
func : @ir.HotFunc,
node_id : Int32,
) -> Bool {
match @ir.hot_node_exact_instr(func, node_id) {
Some(@lib.StringNewWtf16Array) => ()
_ => return false
}
if @ir.hot_child_count(func, node_id) != 3 {
return false
}
let allocation = @ir.hot_child_get(func, node_id, 0)
let count = match @ir.hot_node_exact_instr(func, allocation) {
Some(@lib.ArrayNewFixed(_, @lib.U32(count))) => count.reinterpret_as_int()
_ => return false
}
if @ir.hot_child_count(func, allocation) != count {
return false
}
let start = match
precompute_i32_exact_const(ctx, func, @ir.hot_child_get(func, node_id, 1)) {
Some(@lib.I32(value)) => value
None => return false
}
let end = match
precompute_i32_exact_const(ctx, func, @ir.hot_child_get(func, node_id, 2)) {
Some(@lib.I32(value)) => value
None => return false
}
if start < 0 || end < start || end > count {
return false
}
let units : Array[Int32] = []
for slot = 0; slot < count; slot = slot + 1 {
match
precompute_i32_exact_const(
ctx,
func,
@ir.hot_child_get(func, allocation, slot),
) {
Some(@lib.I32(value)) => units.push(value & 0xffff)
None => return false
}
}
let bytes = match
precompute_utf16_units_to_valid_utf8(units, start, end - start) {
Some(bytes) => bytes
None => return false
}
precompute_replace_with_const_instr(
ctx,
func,
node_id,
@lib.Instruction::string_const(bytes),
)
}
///|
fn precompute_constant_if_chosen_region_can_inline(
func : @ir.HotFunc,
region_ref : @ir.HotRegionRef,
) -> Bool {
let root_count = @ir.hot_region_root_count(func, region_ref)
if root_count > 1 {
return false
}
if root_count == 1 {
let root = @ir.hot_region_get(func, region_ref, 0)
if root >= 0 && @ir.hot_node_is_live(func, root) {
match @ir.hot_node_get(func, root).op {
@ir.HotOp::Block | @ir.HotOp::Loop =>
return @ir.hot_region_root_count(
func,
@ir.hot_region_body_ref(func, root),
) <=
1
_ => ()
}
}
}
true
}
///|
fn precompute_node_is_integer_zero(func : @ir.HotFunc, node_id : Int32) -> Bool {
if node_id < 0 || !@ir.hot_node_is_live(func, node_id) {
return false
}
match @ir.hot_node_get(func, node_id).op {
@ir.HotOp::Const =>
match @ir.hot_const_get(func, @ir.hot_node_get(func, node_id).imm0) {
@ir.HotConstPayload::I32Const(@lib.I32(0))
| @ir.HotConstPayload::I64Const(@lib.I64(0L)) => true
_ => false
}
_ => false
}
}
///|
fn precompute_try_remove_exact_zero_memory_init(
ctx : HotPassContext,
func : @ir.HotFunc,
node_id : Int32,
) -> Bool {
if !@ir.hot_node_is_live(func, node_id) ||
@ir.hot_child_count(func, node_id) != 3 {
return false
}
match @ir.hot_node_get(func, node_id).op {
@ir.HotOp::MemoryInit => ()
_ => return false
}
for slot = 0; slot < 3; slot = slot + 1 {
if !precompute_node_is_integer_zero(
func,
@ir.hot_child_get(func, node_id, slot),
) {
return false
}
}
let temp = @ir.hot_build_nop(func)
let replacement = @ir.hot_node_get(func, temp)
@ir.hot_delete_node(func, temp)
@ir.hot_replace_node(func, node_id, replacement)
pass_mark_mutated(ctx, func)
true
}
///|
fn precompute_try_fold_constant_if(
ctx : HotPassContext,
func : @ir.HotFunc,
node_id : Int32,
) -> Bool {
let node = @ir.hot_node_get(func, node_id)
match node.op {
@ir.HotOp::If => ()
_ => return false
}
let cond_id = @ir.hot_child_get(func, node_id, 0)
let take_then = match precompute_i32_exact_const(ctx, func, cond_id) {
Some(@lib.I32(0)) => false
Some(@lib.I32(_)) => true
None => return false
}
let chosen_region = if take_then {
@ir.hot_region_then_ref(func, node_id)
} else {
match @ir.hot_region_else_ref(func, node_id) {
Some(region) => region
None => {
@ir.hot_control_demote_if_to_block(func, node_id, [])
pass_mark_mutated(ctx, func)
return true
}
}
}
let root_count = @ir.hot_region_root_count(func, chosen_region)
if !precompute_constant_if_chosen_region_can_inline(func, chosen_region) {
return false
}
let replacement : Array[Int32] = []
for i = 0; i < root_count; i = i + 1 {
replacement.push(@ir.hot_region_get(func, chosen_region, i))
}
// The selected arm's roots move to the former if node. Detach them from
// the old region holder so later subtree deletion sees one live owner.
@ir.hot_region_replace_body(func, chosen_region, [])
@ir.hot_control_demote_if_to_block(func, node_id, replacement)
pass_mark_mutated(ctx, func)
true
}
///|
fn precompute_try_collapse_first_unreachable_child(
ctx : HotPassContext,
func : @ir.HotFunc,
node_id : Int32,
) -> Bool {
if node_id < 0 ||
!@ir.hot_node_is_live(func, node_id) ||
@ir.hot_is_control_node(func, node_id) ||
@ir.hot_child_count(func, node_id) == 0 {
return false
}
let child_id = @ir.hot_child_get(func, node_id, 0)
if child_id < 0 || !@ir.hot_node_is_live(func, child_id) {
return false
}
if !@ir.hot_is_terminator_node(func, child_id) {
return false
}
precompute_replace_node_rehoming_control_label(
func,
node_id,
@ir.hot_node_get(func, child_id),
)
pass_mark_mutated(ctx, func)
true
}
///|
fn precompute_is_discardable_value(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)
let exact_pure_heap = match node.op {
@ir.HotOp::Heap =>
match @ir.hot_node_exact_instr(func, node_id) {
Some(@lib.RefEq)
| Some(@lib.AnyConvertExtern)
| Some(@lib.ExternConvertAny)
| Some(@lib.RefI31) => true
_ => false
}
_ => false
}
if (
!exact_pure_heap &&
(
@ir.hot_has_side_effect(func, node_id) ||
@ir.hot_may_trap(func, node_id)
)
) ||
@ir.hot_is_control_node(func, node_id) ||
@ir.hot_is_terminator_node(func, node_id) {
return false
}
if @ir.hot_node_result_arity(func, node_id) == 0 {
return false
}
match node.op {
@ir.HotOp::Const
| @ir.HotOp::LocalGet
| @ir.HotOp::GlobalGet
| @ir.HotOp::RefNull
| @ir.HotOp::RefFunc => true
@ir.HotOp::Unary | @ir.HotOp::Convert => {
let instr = match @ir.hot_node_exact_instr(func, node_id) {
Some(instr) => instr
None => return false
}
if !precompute_raw_instr_is_nontrapping_unary_op(instr) {
return false
}
for slot = 0; slot < node.child_count; slot = slot + 1 {
if !precompute_is_discardable_value(
func,
@ir.hot_child_get(func, node_id, slot),
) {
return false
}
}
true
}
@ir.HotOp::Binary | @ir.HotOp::Compare => {
let instr = match @ir.hot_node_exact_instr(func, node_id) {
Some(instr) => instr
None => return false
}
if !precompute_raw_instr_is_nontrapping_binary_op(instr) {
return false
}
for slot = 0; slot < node.child_count; slot = slot + 1 {
if !precompute_is_discardable_value(
func,
@ir.hot_child_get(func, node_id, slot),
) {
return false
}
}
true
}
@ir.HotOp::Heap => {
let expected_children = match @ir.hot_node_exact_instr(func, node_id) {
Some(@lib.RefEq) => 2
Some(@lib.AnyConvertExtern)
| Some(@lib.ExternConvertAny)
| Some(@lib.RefI31) => 1
_ => return false
}
if node.child_count != expected_children {
return false
}
for slot = 0; slot < node.child_count; slot = slot + 1 {
if !precompute_is_discardable_value(
func,
@ir.hot_child_get(func, node_id, slot),
) {
return false
}
}
true
}
@ir.HotOp::Select => {
for slot = 0; slot < node.child_count; slot = slot + 1 {
if !precompute_is_discardable_value(
func,
@ir.hot_child_get(func, node_id, slot),
) {
return false
}
}
true
}
_ => false
}
}
///|
fn precompute_try_refinalize_exact_cast_block(
ctx : HotPassContext,
func : @ir.HotFunc,
node_id : Int32,
) -> Bool {
if node_id < 0 || !@ir.hot_node_is_live(func, node_id) {
return false
}
match @ir.hot_node_get(func, node_id).op {
@ir.HotOp::Block => ()
_ => return false
}
let current_results = @ir.hot_type_results(
func,
@ir.hot_node_get(func, node_id).ty,
)
let current_ref = match current_results {
[@lib.RefTypeValType(result)] if !result.is_exact() => result
_ => return false
}
let body_ref = @ir.hot_region_body_ref(func, node_id)
if @ir.hot_region_root_count(func, body_ref) != 2 {
return false
}
let drop_id = @ir.hot_region_get(func, body_ref, 0)
let tail_id = @ir.hot_region_get(func, body_ref, 1)
match @ir.hot_node_get(func, tail_id).op {
@ir.HotOp::Unreachable => ()
_ => 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 branch_id = @ir.hot_child_get(func, drop_id, 0)
if branch_id < 0 || !@ir.hot_node_is_live(func, branch_id) {
return false
}
let (target_nullable, target_heap) = match
@ir.hot_node_exact_instr(func, branch_id) {
Some(
@lib.BrOnCast(_, @lib.CastOp(_, target_nullable, _, true), _, target_heap)
) => (target_nullable, target_heap)
_ => return false
}
if current_ref.is_nullable() != target_nullable ||
current_ref.get_heap_type() != target_heap {
return false
}
let label_id = @ir.hot_control_node_label(func, node_id)
if label_id < 0 || @ir.hot_branch_target(func, branch_id) != Some(label_id) {
return false
}
if pass_label_is_used(
label_id,
pass_compute_label_used_except(func, branch_id),
) {
return false
}
let exact_result = @lib.ValType::ref_type(
@lib.RefType::new(target_nullable, target_heap, exact=true),
)
@ir.hot_control_result_type_set(
func,
node_id,
@ir.hot_type_intern_block_result(
func,
@lib.BlockType::val_type(exact_result),
),
)
pass_mark_mutated(ctx, func)
true
}
///|
fn precompute_try_simplify_self_branch_value_block(
ctx : HotPassContext,
func : @ir.HotFunc,
node_id : Int32,
) -> Bool {
if node_id < 0 || !@ir.hot_node_is_live(func, node_id) {
return false
}
match @ir.hot_node_get(func, node_id).op {
@ir.HotOp::Block => ()
_ => 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 == 0 {
return false
}
let mut first_value = 0
while first_value < root_count {
let root_id = @ir.hot_region_get(func, body_ref, first_value)
if root_id >= 0 &&
@ir.hot_node_is_live(func, root_id) &&
(match @ir.hot_node_get(func, root_id).op {
@ir.HotOp::Nop => true
_ => false
}) {
first_value += 1
} else {
break
}
}
if first_value >= root_count {
return false
}
let branch_id = @ir.hot_region_get(func, body_ref, first_value)
if branch_id < 0 || !@ir.hot_node_is_live(func, branch_id) {
return false
}
let branch = @ir.hot_node_get(func, branch_id)
match branch.op {
@ir.HotOp::Br => ()
_ => {
if first_value + 1 == root_count && first_value > 0 {
@ir.hot_region_replace_body(func, body_ref, [branch_id])
pass_mark_mutated(ctx, func)
return true
}
return false
}
}
if branch.imm0 != @ir.hot_control_node_label(func, node_id) ||
branch.child_count != 1 {
return false
}
let value_id = @ir.hot_child_get(func, branch_id, 0)
@ir.hot_region_replace_body(func, body_ref, [value_id])
@ir.hot_delete_node(func, branch_id)
pass_mark_mutated(ctx, func)
true
}
///|
priv struct PrecomputeTransparentBlockCleanup {
holders : Array[Int32]
copied_values : Array[Int32]
}
///|
fn PrecomputeTransparentBlockCleanup::new() -> PrecomputeTransparentBlockCleanup {
{ holders: [], copied_values: [], }
}
///|
fn precompute_finish_transparent_block_cleanup(
func : @ir.HotFunc,
cleanup : PrecomputeTransparentBlockCleanup,
) -> Unit {
// Remove copied operands before checking the detached control forest. A
// copied value may retain edges to controls still owned by its replacement.
if !cleanup.copied_values.is_empty() {
@ir.hot_delete_detached_nodes(func, cleanup.copied_values)
}
if !cleanup.holders.is_empty() &&
!@ir.hot_delete_detached_control_forest(func, cleanup.holders) {
abort("precompute failed to delete detached transparent value blocks")
}
}
///|
fn precompute_try_inline_transparent_value_block_child(
ctx : HotPassContext,
func : @ir.HotFunc,
node_id : Int32,
label_used : @bitset.BitSet,
use_counts : @ir.HotNodeUseCounts,
cleanup~ : PrecomputeTransparentBlockCleanup? = None,
) -> Bool {
if @ir.hot_is_control_node(func, node_id) {
return false
}
let child_count = @ir.hot_child_count(func, node_id)
let mut changed = false
for slot = 0; slot < child_count; slot = slot + 1 {
let child_id = @ir.hot_child_get(func, node_id, slot)
if child_id < 0 || !@ir.hot_node_is_live(func, child_id) {
continue
}
match @ir.hot_node_get(func, child_id).op {
@ir.HotOp::Block => ()
_ => continue
}
if @ir.hot_node_result_arity(func, child_id) != 1 {
continue
}
let label_id = @ir.hot_control_node_label(func, child_id)
if pass_label_is_used(label_id, label_used) {
continue
}
if @ir.hot_node_use_count(use_counts, child_id) != 1 {
continue
}
let body_ref = @ir.hot_region_body_ref(func, child_id)
if @ir.hot_region_root_count(func, body_ref) != 1 {
continue
}
let value_id = @ir.hot_region_get(func, body_ref, 0)
if value_id < 0 || !@ir.hot_node_is_live(func, value_id) {
continue
}
let prefix_count = @ir.hot_node_get(func, child_id).imm1
if prefix_count > 0 &&
!(prefix_count == 1 &&
@ir.hot_child_count(func, child_id) == 2 &&
@ir.hot_child_get(func, child_id, 0) == value_id) {
// Only a scalar identity proves that removing its entry prefix cannot
// lose input effects. Other typed holders keep their represented entry.
continue
}
if slot == 0 && @ir.hot_is_terminator_node(func, value_id) {
if @ir.hot_node_use_count(use_counts, value_id) != 1 {
continue
}
precompute_replace_node_rehoming_control_label(
func,
node_id,
@ir.hot_node_get(func, value_id),
)
@ir.hot_region_replace_body(func, body_ref, [])
match cleanup {
Some(cleanup) => {
cleanup.holders.push(child_id)
cleanup.copied_values.push(value_id)
// These nodes are detached. Exclude them from later candidates in
// this owned phase snapshot while their checked deletion is pending.
use_counts.counts[child_id] = 0
use_counts.counts[value_id] = 0
}
None => {
if !@ir.hot_delete_detached_control_forest(func, [child_id]) {
abort(
"precompute failed to delete detached terminating value block",
)
}
@ir.hot_delete_node(func, value_id)
}
}
pass_mark_mutated(ctx, func)
return true
}
if @ir.hot_node_result_arity(func, value_id) != 1 {
continue
}
@ir.hot_child_set(func, node_id, slot, value_id)
if prefix_count == 0 {
@ir.hot_region_replace_body(func, body_ref, [])
} else {
// The prefix and result refer to the same scalar producer. The parent
// now owns its remaining reference, so retain that producer once.
ignore(@ir.hot_replace_child_span(func, child_id, []))
}
match cleanup {
Some(cleanup) => {
cleanup.holders.push(child_id)
use_counts.counts[child_id] = 0
}
None =>
if !@ir.hot_delete_detached_control_forest(func, [child_id]) {
abort("precompute failed to delete detached transparent value block")
}
}
pass_mark_mutated(ctx, func)
changed = true
}
changed
}
///|
fn precompute_try_fold_void_br_if(
ctx : HotPassContext,
func : @ir.HotFunc,
node_id : Int32,
) -> Bool {
if node_id < 0 || !@ir.hot_node_is_live(func, node_id) {
return false
}
let node = @ir.hot_node_get(func, node_id)
match node.op {
@ir.HotOp::BrIf => ()
_ => return false
}
let label_id = node.imm0
if label_id < 0 ||
@ir.hot_label_branch_arity(func, label_id) != 0 ||
@ir.hot_child_count(func, node_id) != 1 {
return false
}
let condition_id = @ir.hot_child_get(func, node_id, 0)
let replacement_id = match
precompute_i32_exact_const(ctx, func, condition_id) {
Some(@lib.I32(0)) => @ir.hot_build_nop(func)
Some(@lib.I32(_)) => @ir.hot_build_br(func, label_id)
None => return false
}
precompute_replace_node_rehoming_control_label(
func,
node_id,
@ir.hot_node_get(func, replacement_id),
)
@ir.hot_delete_node(func, replacement_id)
pass_mark_mutated(ctx, func)
true
}
///|
fn precompute_try_eliminate_dead_drop(
ctx : HotPassContext,
func : @ir.HotFunc,
node_id : Int32,
) -> Bool {
let node = @ir.hot_node_get(func, node_id)
match node.op {
@ir.HotOp::Drop => ()
_ => return false
}
if @ir.hot_child_count(func, node_id) != 1 {
return false
}
let value_id = @ir.hot_child_get(func, node_id, 0)
if !precompute_is_discardable_value(func, value_id) {
return false
}
let nop = @ir.hot_build_nop(func)
precompute_replace_node_rehoming_control_label(
func,
node_id,
@ir.hot_node_get(func, nop),
)
pass_mark_mutated(ctx, func)
true
}
///|
fn precompute_delete_removed_roots(
ctx : HotPassContext,
func : @ir.HotFunc,
removed : Array[Int32],
) -> Unit {
if removed.is_empty() {
return
}
// A removed region root may still supply a typed control entry or another
// region. Check physical references once for this batch, including detached
// live nodes, instead of assuming each removed root is an exclusive owner.
let pending = @bitset.BitSet::new(@ir.hot_node_count(func))
for node in removed {
if @ir.hot_node_is_live(func, node) {
pending[node] = true
}
}
for index in 0..<@ir.hot_root_count(func) {
let root = @ir.hot_root_get(func, index)
if root >= 0 {
pending[root] = false
}
}
for node in 0..<@ir.hot_node_count(func) {
if !@ir.hot_node_is_live(func, node) {
continue
}
let data = @ir.hot_node_get(func, node)
for slot in 0..= 0 {
pending[child] = false
}
}
}
let deletable : Array[Int32] = []
for node in removed {
if node >= 0 && node < pending.length() && pending[node] {
pending[node] = false
deletable.push(node)
}
}
if !deletable.is_empty() {
@ir.hot_delete_detached_nodes(func, deletable)
pass_mark_mutated(ctx, func)
}
}
///|
fn precompute_simplify_region_roots(
ctx : HotPassContext,
func : @ir.HotFunc,
region_ref : @ir.HotRegionRef,
label_used : @bitset.BitSet,
) -> Bool {
let mut changed = false
let mut idx = 0
while idx < @ir.hot_region_root_count(func, region_ref) {
let node_id = @ir.hot_region_get(func, region_ref, idx)
let node = @ir.hot_node_get(func, node_id)
match node.op {
@ir.HotOp::Block | @ir.HotOp::Loop =>
changed = precompute_simplify_region_roots(
ctx,
func,
@ir.hot_region_body_ref(func, node_id),
label_used,
) ||
changed
@ir.HotOp::If => {
changed = precompute_simplify_region_roots(
ctx,
func,
@ir.hot_region_then_ref(func, node_id),
label_used,
) ||
changed
match @ir.hot_region_else_ref(func, node_id) {
Some(else_region) =>
changed = precompute_simplify_region_roots(
ctx, func, else_region, label_used,
) ||
changed
None => ()
}
}
@ir.HotOp::Try => {
changed = precompute_simplify_region_roots(
ctx,
func,
@ir.hot_region_body_ref(func, node_id),
label_used,
) ||
changed
match @ir.hot_region_catch_ref(func, node_id) {
Some(catch_region) =>
changed = precompute_simplify_region_roots(
ctx, func, catch_region, label_used,
) ||
changed
None => ()
}
}
@ir.HotOp::TryTable => {
changed = precompute_simplify_region_roots(
ctx,
func,
@ir.hot_region_body_ref(func, node_id),
label_used,
) ||
changed
changed = precompute_simplify_region_roots(
ctx,
func,
@ir.hot_region_catch_list_ref(func, node_id),
label_used,
) ||
changed
}
_ => ()
}
if node_id >= 0 && @ir.hot_node_is_live(func, node_id) {
let terminates_region = match @ir.hot_node_get(func, node_id).op {
@ir.HotOp::Br | @ir.HotOp::Return | @ir.HotOp::Unreachable => true
_ => false
}
if terminates_region {
let root_count = @ir.hot_region_root_count(func, region_ref)
if idx + 1 < root_count {
let removed = pass_splice_region(
ctx,
func,
region_ref,
idx + 1,
root_count - idx - 1,
[],
)
precompute_delete_removed_roots(ctx, func, removed)
changed = true
}
break
}
match @ir.hot_node_get(func, node_id).op {
@ir.HotOp::Block | @ir.HotOp::Loop => {
if region_ref == @ir.HotRegionRef::root() &&
precompute_loop_is_terminal_self_branch_with_result_tail(
func, node_id, idx,
) {
let root_count = @ir.hot_region_root_count(func, region_ref)
let removed = pass_splice_region(
ctx,
func,
region_ref,
idx + 1,
root_count - idx - 1,
[@ir.hot_build_unreachable(func)],
)
precompute_delete_removed_roots(ctx, func, removed)
changed = true
continue
}
if region_ref == @ir.HotRegionRef::root() &&
precompute_loop_is_const_true_self_br_if_with_result_tail(
ctx, func, node_id, idx,
) {
let label = @ir.hot_control_node_label(func, node_id)
@ir.hot_region_replace_body(
func,
@ir.hot_region_body_ref(func, node_id),
[@ir.hot_build_br(func, label)],
)
let root_count = @ir.hot_region_root_count(func, region_ref)
let removed = pass_splice_region(
ctx,
func,
region_ref,
idx + 1,
root_count - idx - 1,
[@ir.hot_build_unreachable(func)],
)
precompute_delete_removed_roots(ctx, func, removed)
changed = true
continue
}
if region_ref == @ir.HotRegionRef::root() &&
precompute_control_is_const_self_br_if_noop(ctx, func, node_id) {
let removed = pass_splice_region(ctx, func, region_ref, idx, 1, [
@ir.hot_build_nop(func),
])
precompute_delete_removed_roots(ctx, func, removed)
changed = true
continue
}
if region_ref == @ir.HotRegionRef::root() &&
@ir.hot_node_result_arity(func, node_id) == 0 &&
precompute_region_is_all_nops(
func,
@ir.hot_region_body_ref(func, node_id),
) {
let removed = pass_splice_region(ctx, func, region_ref, idx, 1, [
@ir.hot_build_nop(func),
])
if !@ir.hot_delete_detached_control_forest(func, removed) {
abort("precompute failed to delete detached empty root control")
}
changed = true
continue
} else if region_ref == @ir.HotRegionRef::root() {
match @ir.hot_node_get(func, node_id).op {
@ir.HotOp::Block =>
if precompute_block_is_selector_only_self_targeting_br_table(
func, node_id,
) {
let removed = pass_splice_region(
ctx,
func,
region_ref,
idx,
1,
[@ir.hot_build_nop(func)],
)
precompute_delete_removed_roots(ctx, func, removed)
changed = true
continue
}
_ => ()
}
}
}
@ir.HotOp::Drop =>
if @ir.hot_child_count(func, node_id) == 1 &&
precompute_is_discardable_value(
func,
@ir.hot_child_get(func, node_id, 0),
) {
let replacement = if region_ref == @ir.HotRegionRef::root() ||
@ir.hot_region_root_count(func, region_ref) == 1 {
[@ir.hot_build_nop(func)]
} else {
[]
}
let removed = pass_splice_region(
ctx, func, region_ref, idx, 1, replacement,
)
precompute_delete_removed_roots(ctx, func, removed)
changed = true
continue
}
@ir.HotOp::If =>
if @ir.hot_node_result_arity(func, node_id) == 0 {
let cond_id = @ir.hot_child_get(func, node_id, 0)
let take_then = match
precompute_i32_exact_const(ctx, func, cond_id) {
Some(@lib.I32(0)) => Some(false)
Some(@lib.I32(_)) => Some(true)
None => None
}
match take_then {
Some(use_then) => {
let replacement : Array[Int32] = []
let chosen_region : @ir.HotRegionRef? = if use_then {
Some(@ir.hot_region_then_ref(func, node_id))
} else {
@ir.hot_region_else_ref(func, node_id)
}
// A chosen arm may branch to this if's own label. Splicing
// would detach that target before hot lowering resolves it.
let can_splice = if pass_label_is_used(
@ir.hot_control_node_label(func, node_id),
label_used,
) {
false
} else {
match chosen_region {
Some(chosen_region) =>
precompute_constant_if_chosen_region_can_inline(
func, chosen_region,
)
None => true
}
}
if can_splice {
match chosen_region {
Some(chosen_region) =>
for root_idx = 0
root_idx <
@ir.hot_region_root_count(func, chosen_region)
root_idx = root_idx + 1 {
replacement.push(
@ir.hot_region_get(func, chosen_region, root_idx),
)
}
None => ()
}
// Preserve the old if label on a detached empty block before
// splicing the chosen arm into the parent region. Deleting the
// detached owner would leave the label table pointing at a dead
// control node.
match chosen_region {
Some(region) =>
@ir.hot_region_replace_body(func, region, [])
None => ()
}
@ir.hot_control_demote_if_to_block(func, node_id, [])
ignore(
pass_splice_region(
ctx, func, region_ref, idx, 1, replacement,
),
)
changed = true
continue
}
}
None => ()
}
}
_ => ()
}
}
idx += 1
}
if changed {
pass_mark_mutated(ctx, func)
}
changed
}
///|
fn precompute_control_is_const_self_br_if_noop(
ctx : HotPassContext,
func : @ir.HotFunc,
control_id : Int32,
) -> Bool {
if @ir.hot_node_result_arity(func, control_id) != 0 {
return false
}
let op = @ir.hot_node_get(func, control_id).op
match op {
@ir.HotOp::Block | @ir.HotOp::Loop => ()
_ => return false
}
let label = @ir.hot_control_node_label(func, control_id)
if label < 0 || @ir.hot_label_branch_arity(func, label) != 0 {
return false
}
let body_ref = @ir.hot_region_body_ref(func, control_id)
if @ir.hot_region_root_count(func, body_ref) != 1 {
return false
}
let branch_id = @ir.hot_region_get(func, body_ref, 0)
let branch_node = @ir.hot_node_get(func, branch_id)
match branch_node.op {
@ir.HotOp::BrIf => ()
_ => return false
}
if branch_node.child_count != 1 || branch_node.imm0 != label {
return false
}
let cond_id = @ir.hot_child_get(func, branch_id, 0)
match precompute_i32_exact_const(ctx, func, cond_id) {
Some(@lib.I32(0)) => true
Some(@lib.I32(_)) =>
match op {
@ir.HotOp::Block => true
_ => false
}
None => false
}
}
///|
fn precompute_root_suffix_has_result(
func : @ir.HotFunc,
start_idx : Int32,
) -> Bool {
let region_ref = @ir.HotRegionRef::root()
let root_count = @ir.hot_region_root_count(func, region_ref)
let mut idx = start_idx
while idx < root_count {
let root_id = @ir.hot_region_get(func, region_ref, idx)
if @ir.hot_node_is_live(func, root_id) &&
@ir.hot_node_result_arity(func, root_id) > 0 {
return true
}
idx = idx + 1
}
false
}
///|
fn precompute_loop_is_terminal_self_branch_with_result_tail(
func : @ir.HotFunc,
loop_id : Int32,
root_idx : Int32,
) -> Bool {
match @ir.hot_node_get(func, loop_id).op {
@ir.HotOp::Loop => ()
_ => return false
}
if @ir.hot_node_result_arity(func, loop_id) != 0 ||
!precompute_root_suffix_has_result(func, root_idx + 1) {
return false
}
let label = @ir.hot_control_node_label(func, loop_id)
if label < 0 || @ir.hot_label_branch_arity(func, label) != 0 {
return false
}
let body_ref = @ir.hot_region_body_ref(func, loop_id)
let root_count = @ir.hot_region_root_count(func, body_ref)
if root_count == 0 {
return false
}
let branch_id = @ir.hot_region_get(func, body_ref, root_count - 1)
let branch = @ir.hot_node_get(func, branch_id)
match branch.op {
@ir.HotOp::Br => branch.child_count == 0 && branch.imm0 == label
_ => false
}
}
///|
fn precompute_loop_is_const_true_self_br_if_with_result_tail(
ctx : HotPassContext,
func : @ir.HotFunc,
loop_id : Int32,
root_idx : Int32,
) -> Bool {
match @ir.hot_node_get(func, loop_id).op {
@ir.HotOp::Loop => ()
_ => return false
}
if @ir.hot_node_result_arity(func, loop_id) != 0 {
return false
}
if !precompute_root_suffix_has_result(func, root_idx + 1) {
return false
}
let label = @ir.hot_control_node_label(func, loop_id)
if label < 0 || @ir.hot_label_branch_arity(func, label) != 0 {
return false
}
let body_ref = @ir.hot_region_body_ref(func, loop_id)
if @ir.hot_region_root_count(func, body_ref) != 1 {
return false
}
let branch_id = @ir.hot_region_get(func, body_ref, 0)
let branch_node = @ir.hot_node_get(func, branch_id)
match branch_node.op {
@ir.HotOp::BrIf => ()
_ => return false
}
if branch_node.child_count != 1 || branch_node.imm0 != label {
return false
}
let cond_id = @ir.hot_child_get(func, branch_id, 0)
match precompute_i32_exact_const(ctx, func, cond_id) {
Some(@lib.I32(0)) | None => false
Some(@lib.I32(_)) => true
}
}
///|
fn precompute_block_is_selector_only_self_targeting_br_table(
func : @ir.HotFunc,
block_id : Int32,
) -> Bool {
if @ir.hot_node_result_arity(func, block_id) != 0 {
return false
}
let block_label = @ir.hot_control_node_label(func, block_id)
if block_label < 0 || @ir.hot_label_branch_arity(func, block_label) != 0 {
return false
}
let body_ref = @ir.hot_region_body_ref(func, block_id)
if @ir.hot_region_root_count(func, body_ref) != 1 {
return false
}
let br_table_id = @ir.hot_region_get(func, body_ref, 0)
match @ir.hot_node_get(func, br_table_id).op {
@ir.HotOp::BrTable => ()
_ => return false
}
if @ir.hot_child_count(func, br_table_id) != 1 ||
!precompute_is_discardable_value(
func,
@ir.hot_child_get(func, br_table_id, 0),
) {
return false
}
if @ir.hot_branch_target(func, br_table_id) != Some(block_label) {
return false
}
for target in @ir.hot_branch_table_targets_for_node(func, br_table_id) {
if target != block_label {
return false
}
}
true
}
///|
fn precompute_region_is_all_nops(
func : @ir.HotFunc,
region_ref : @ir.HotRegionRef,
) -> Bool {
for idx = 0; idx < @ir.hot_region_root_count(func, region_ref); idx = idx + 1 {
let node_id = @ir.hot_region_get(func, region_ref, idx)
match @ir.hot_node_get(func, node_id).op {
@ir.HotOp::Nop => ()
_ => return false
}
}
true
}
///|
fn precompute_trim_region_nops(
ctx : HotPassContext,
func : @ir.HotFunc,
region_ref : @ir.HotRegionRef,
) -> Bool {
let mut changed = false
let mut idx = 0
while idx < @ir.hot_region_root_count(func, region_ref) {
let node_id = @ir.hot_region_get(func, region_ref, idx)
let node = @ir.hot_node_get(func, node_id)
match node.op {
@ir.HotOp::Block | @ir.HotOp::Loop =>
changed = precompute_trim_region_nops(
ctx,
func,
@ir.hot_region_body_ref(func, node_id),
) ||
changed
@ir.HotOp::If => {
changed = precompute_trim_region_nops(
ctx,
func,
@ir.hot_region_then_ref(func, node_id),
) ||
changed
match @ir.hot_region_else_ref(func, node_id) {
Some(else_region) =>
changed = precompute_trim_region_nops(ctx, func, else_region) ||
changed
None => ()
}
}
@ir.HotOp::Try => {
changed = precompute_trim_region_nops(
ctx,
func,
@ir.hot_region_body_ref(func, node_id),
) ||
changed
match @ir.hot_region_catch_ref(func, node_id) {
Some(catch_region) =>
changed = precompute_trim_region_nops(ctx, func, catch_region) ||
changed
None => ()
}
}
@ir.HotOp::TryTable => {
changed = precompute_trim_region_nops(
ctx,
func,
@ir.hot_region_body_ref(func, node_id),
) ||
changed
changed = precompute_trim_region_nops(
ctx,
func,
@ir.hot_region_catch_list_ref(func, node_id),
) ||
changed
}
_ => ()
}
if idx >= @ir.hot_region_root_count(func, region_ref) {
break
}
if region_ref == @ir.HotRegionRef::root() {
idx += 1
continue
}
let current_id = @ir.hot_region_get(func, region_ref, idx)
match @ir.hot_node_get(func, current_id).op {
@ir.HotOp::Nop => {
let mut run_end = idx + 1
let root_count = @ir.hot_region_root_count(func, region_ref)
while run_end < root_count {
let run_id = @ir.hot_region_get(func, region_ref, run_end)
match @ir.hot_node_get(func, run_id).op {
@ir.HotOp::Nop => run_end += 1
_ => break
}
}
ignore(
pass_splice_region(ctx, func, region_ref, idx, run_end - idx, []),
)
changed = true
continue
}
_ => ()
}
idx += 1
}
changed
}
///|
fn precompute_node_is_nop_tree(func : @ir.HotFunc, node_id : Int32) -> Bool {
if node_id < 0 || !@ir.hot_node_is_live(func, node_id) {
return false
}
match @ir.hot_node_get(func, node_id).op {
@ir.HotOp::Nop => true
@ir.HotOp::Block | @ir.HotOp::Loop => {
let body_ref = @ir.hot_region_body_ref(func, node_id)
// A control node's entry operands precede its body roots in the child
// span. They may trap or have effects even when the body is all nops.
@ir.hot_child_count(func, node_id) ==
@ir.hot_region_root_count(func, body_ref) &&
precompute_region_is_all_nops(func, body_ref)
}
_ => false
}
}
///|
fn precompute_coalesce_all_root_nops(
ctx : HotPassContext,
func : @ir.HotFunc,
) -> Bool {
let region_ref = @ir.HotRegionRef::root()
let root_count = @ir.hot_region_root_count(func, region_ref)
if root_count <= 1 {
return false
}
for idx = 0; idx < root_count; idx = idx + 1 {
let node_id = @ir.hot_region_get(func, region_ref, idx)
if !precompute_node_is_nop_tree(func, node_id) {
return false
}
}
ignore(pass_splice_region(ctx, func, region_ref, 1, root_count - 1, []))
true
}
///|
fn precompute_trim_root_nops_before_trailing_const(
ctx : HotPassContext,
func : @ir.HotFunc,
) -> Bool {
let region_ref = @ir.HotRegionRef::root()
let root_count = @ir.hot_region_root_count(func, region_ref)
if root_count <= 1 {
return false
}
let tail_id = @ir.hot_region_get(func, region_ref, root_count - 1)
if tail_id < 0 || !@ir.hot_node_is_live(func, tail_id) {
return false
}
match @ir.hot_node_get(func, tail_id).op {
@ir.HotOp::Const => ()
_ => return false
}
for idx = 0; idx + 1 < root_count; idx = idx + 1 {
let node_id = @ir.hot_region_get(func, region_ref, idx)
match @ir.hot_node_get(func, node_id).op {
@ir.HotOp::Nop => ()
_ => return false
}
}
ignore(pass_splice_region(ctx, func, region_ref, 0, root_count - 1, []))
true
}
///|
fn precompute_raw_i32_const(instr : @lib.Instruction) -> @lib.I32? {
match instr {
@lib.I32Const(value) => Some(value)
_ => None
}
}
///|
fn precompute_raw_i64_const(instr : @lib.Instruction) -> @lib.I64? {
match instr {
@lib.I64Const(value) => Some(value)
_ => None
}
}
///|
fn precompute_raw_f32_const(instr : @lib.Instruction) -> @lib.F32? {
match instr {
@lib.F32Const(value) => Some(value)
_ => None
}
}
///|
fn precompute_raw_f64_const(instr : @lib.Instruction) -> @lib.F64? {
match instr {
@lib.F64Const(value) => Some(value)
_ => None
}
}
///|
fn precompute_raw_global_const_instr(
module_ctx : @ir.HotModuleContext?,
global_idx : @lib.GlobalIdx,
) -> @lib.Instruction? {
let payload = match module_ctx {
Some(ctx) => @ir.hot_module_resolve_global_const_init(ctx, global_idx)
None => None
}
match payload {
Some(@ir.HotConstPayload::I32Const(value)) =>
Some(@lib.Instruction::i32_const(value))
Some(@ir.HotConstPayload::I64Const(value)) =>
Some(@lib.Instruction::i64_const(value))
Some(@ir.HotConstPayload::F32Const(value)) =>
Some(@lib.Instruction::f32_const(value))
Some(@ir.HotConstPayload::F64Const(value)) =>
Some(@lib.Instruction::f64_const(value))
Some(@ir.HotConstPayload::RefNullConst(value)) =>
Some(@lib.Instruction::ref_null_type(value))
Some(@ir.HotConstPayload::StringConst(_)) | None => None
}
}
///|
fn precompute_raw_bool(value : Bool) -> @lib.Instruction {
@lib.Instruction::i32_const(if value { @lib.I32(1) } else { @lib.I32(0) })
}
///|
fn precompute_raw_f32_nearest(value : Float) -> Float {
if value.is_nan() ||
value >= Float::from_double(8388608.0) ||
value <= Float::from_double(-8388608.0) {
return value
}
let lower = value.floor()
let fraction = value - lower
let rounded = if fraction < Float::from_double(0.5) {
lower
} else if fraction > Float::from_double(0.5) {
lower + Float::from_double(1.0)
} else if (lower.to_int() & 1) == 0 {
lower
} else {
lower + Float::from_double(1.0)
}
if rounded == Float::from_double(0.0) &&
(value.reinterpret_as_uint() & 0x80000000U) != 0U {
Float::reinterpret_from_uint(0x80000000U)
} else {
rounded
}
}
///|
fn precompute_raw_f64_nearest(value : Double) -> Double {
if value.is_nan() ||
value >= 4503599627370496.0 ||
value <= -4503599627370496.0 {
return value
}
let lower = value.floor()
let fraction = value - lower
let rounded = if fraction < 0.5 {
lower
} else if fraction > 0.5 {
lower + 1.0
} else if (lower.to_int64() & 1L) == 0L {
lower
} else {
lower + 1.0
}
if rounded == 0.0 &&
(value.reinterpret_as_uint64() & 0x8000000000000000UL) != 0UL {
0x8000000000000000UL.reinterpret_as_double()
} else {
rounded
}
}
///|
fn precompute_raw_try_fold_i32_trunc(
value : Double,
signed : Bool,
saturating : Bool,
) -> @lib.Instruction? {
if signed {
if saturating {
let result = if value.is_nan() {
0
} else if value <= -2147483648.0 {
-2147483647 - 1
} else if value >= 2147483648.0 {
2147483647
} else {
value.to_int()
}
return Some(@lib.Instruction::i32_const(@lib.I32(result)))
}
if value.is_nan() || value <= -2147483649.0 || value >= 2147483648.0 {
return None
}
// Truncation removes the fractional part before the range check.
let result = if value < -2147483648.0 {
-2147483647 - 1
} else {
value.to_int()
}
return Some(@lib.Instruction::i32_const(@lib.I32(result)))
}
if saturating {
let result = if value.is_nan() || value <= 0.0 {
0U
} else if value >= 4294967296.0 {
0xffffffffU
} else {
value.to_uint()
}
return Some(
@lib.Instruction::i32_const(@lib.I32(result.reinterpret_as_int())),
)
}
if value.is_nan() || value <= -1.0 || value >= 4294967296.0 {
return None
}
let result = if value < 0.0 { 0U } else { value.to_uint() }
Some(@lib.Instruction::i32_const(@lib.I32(result.reinterpret_as_int())))
}
///|
fn precompute_trunc_double_to_uint64(value : Double) -> UInt64 {
if value < 9223372036854775808.0 {
value.to_int64().reinterpret_as_uint64()
} else {
(value - 9223372036854775808.0).to_int64().reinterpret_as_uint64() |
0x8000000000000000UL
}
}
///|
fn precompute_raw_try_fold_i64_trunc(
value : Double,
signed : Bool,
saturating : Bool,
) -> @lib.Instruction? {
if signed {
if saturating {
let result = if value.is_nan() {
0L
} else if value <= -9223372036854775808.0 {
-9223372036854775807L - 1L
} else if value >= 9223372036854775808.0 {
9223372036854775807L
} else {
value.to_int64()
}
return Some(@lib.Instruction::i64_const(@lib.I64(result)))
}
if value.is_nan() ||
value < -9223372036854775808.0 ||
value >= 9223372036854775808.0 {
return None
}
return Some(@lib.Instruction::i64_const(@lib.I64(value.to_int64())))
}
if saturating {
let result = if value.is_nan() || value <= 0.0 {
0UL
} else if value >= 18446744073709551616.0 {
0xffffffffffffffffUL
} else {
precompute_trunc_double_to_uint64(value)
}
return Some(
@lib.Instruction::i64_const(@lib.I64(result.reinterpret_as_int64())),
)
}
if value.is_nan() || value <= -1.0 || value >= 18446744073709551616.0 {
return None
}
let result = if value < 0.0 {
0UL
} else {
precompute_trunc_double_to_uint64(value)
}
Some(@lib.Instruction::i64_const(@lib.I64(result.reinterpret_as_int64())))
}
///|
fn precompute_v128_const_bytes(instr : @lib.Instruction) -> Array[Byte]? {
match instr {
@lib.V128Const(
b0,
b1,
b2,
b3,
b4,
b5,
b6,
b7,
b8,
b9,
b10,
b11,
b12,
b13,
b14,
b15
) =>
Some([
b0, b1, b2, b3, b4, b5, b6, b7, b8, b9, b10, b11, b12, b13, b14, b15,
])
_ => None
}
}
///|
fn precompute_v128_read_u32(bytes : Array[Byte], offset : Int32) -> UInt32 {
bytes[offset].to_uint() |
(bytes[offset + 1].to_uint() << 8) |
(bytes[offset + 2].to_uint() << 16) |
(bytes[offset + 3].to_uint() << 24)
}
///|
fn precompute_v128_read_u64(bytes : Array[Byte], offset : Int32) -> UInt64 {
precompute_v128_read_u32(bytes, offset).to_uint64() |
(precompute_v128_read_u32(bytes, offset + 4).to_uint64() << 32)
}
///|
fn precompute_v128_write_u32(
bytes : Array[Byte],
offset : Int32,
value : UInt32,
) -> Unit {
bytes[offset] = value.to_byte()
bytes[offset + 1] = (value >> 8).to_byte()
bytes[offset + 2] = (value >> 16).to_byte()
bytes[offset + 3] = (value >> 24).to_byte()
}
///|
fn precompute_raw_try_fold_v128_lane_extract(
operand : @lib.Instruction,
instr : @lib.Instruction,
) -> @lib.Instruction? {
let bytes = match precompute_v128_const_bytes(operand) {
Some(bytes) => bytes
None => return None
}
match instr {
@lib.I8x16ExtractLaneS(lane) => {
let lane = lane.0.to_int()
if lane >= 16 {
return None
}
let value = (bytes[lane].to_uint() << 24).reinterpret_as_int() >> 24
Some(@lib.Instruction::i32_const(@lib.I32(value)))
}
@lib.I8x16ExtractLaneU(lane) => {
let lane = lane.0.to_int()
if lane >= 16 {
return None
}
Some(
@lib.Instruction::i32_const(
@lib.I32(bytes[lane].to_uint().reinterpret_as_int()),
),
)
}
@lib.I16x8ExtractLaneS(lane) => {
let lane = lane.0.to_int()
if lane >= 8 {
return None
}
let offset = lane * 2
let raw = bytes[offset].to_uint() | (bytes[offset + 1].to_uint() << 8)
Some(
@lib.Instruction::i32_const(
@lib.I32((raw << 16).reinterpret_as_int() >> 16),
),
)
}
@lib.I16x8ExtractLaneU(lane) => {
let lane = lane.0.to_int()
if lane >= 8 {
return None
}
let offset = lane * 2
let raw = bytes[offset].to_uint() | (bytes[offset + 1].to_uint() << 8)
Some(@lib.Instruction::i32_const(@lib.I32(raw.reinterpret_as_int())))
}
@lib.I32x4ExtractLane(lane) => {
let lane = lane.0.to_int()
if lane >= 4 {
return None
}
Some(
@lib.Instruction::i32_const(
@lib.I32(
precompute_v128_read_u32(bytes, lane * 4).reinterpret_as_int(),
),
),
)
}
@lib.I64x2ExtractLane(lane) => {
let lane = lane.0.to_int()
if lane >= 2 {
return None
}
Some(
@lib.Instruction::i64_const(
@lib.I64(
precompute_v128_read_u64(bytes, lane * 8).reinterpret_as_int64(),
),
),
)
}
@lib.F32x4ExtractLane(lane) => {
let lane = lane.0.to_int()
if lane >= 4 {
return None
}
Some(
@lib.Instruction::f32_const(
@lib.F32(
Float::reinterpret_from_uint(
precompute_v128_read_u32(bytes, lane * 4),
),
),
),
)
}
@lib.F64x2ExtractLane(lane) => {
let lane = lane.0.to_int()
if lane >= 2 {
return None
}
Some(
@lib.Instruction::f64_const(
@lib.F64(
precompute_v128_read_u64(bytes, lane * 8).reinterpret_as_double(),
),
),
)
}
_ => None
}
}
///|
fn precompute_raw_try_fold_v128_reduction(
operand : @lib.Instruction,
instr : @lib.Instruction,
) -> @lib.Instruction? {
let bytes = match precompute_v128_const_bytes(operand) {
Some(bytes) => bytes
None => return None
}
let result = match instr {
@lib.V128AnyTrue => {
let mut any = false
for byte in bytes {
if byte != b'\x00' {
any = true
}
}
if any {
1
} else {
0
}
}
@lib.I8x16AllTrue => {
let mut all = true
for byte in bytes {
if byte == b'\x00' {
all = false
}
}
if all {
1
} else {
0
}
}
@lib.I8x16Bitmask => {
let mut mask = 0
for lane = 0; lane < 16; lane = lane + 1 {
if (bytes[lane].to_uint() & 0x80U) != 0U {
mask = mask | (1 << lane)
}
}
mask
}
@lib.I16x8AllTrue => {
let mut all = true
for lane = 0; lane < 8; lane = lane + 1 {
let offset = lane * 2
if bytes[offset] == b'\x00' && bytes[offset + 1] == b'\x00' {
all = false
}
}
if all {
1
} else {
0
}
}
@lib.I16x8Bitmask => {
let mut mask = 0
for lane = 0; lane < 8; lane = lane + 1 {
if (bytes[lane * 2 + 1].to_uint() & 0x80U) != 0U {
mask = mask | (1 << lane)
}
}
mask
}
@lib.I32x4AllTrue => {
let mut all = true
for lane = 0; lane < 4; lane = lane + 1 {
if precompute_v128_read_u32(bytes, lane * 4) == 0U {
all = false
}
}
if all {
1
} else {
0
}
}
@lib.I32x4Bitmask => {
let mut mask = 0
for lane = 0; lane < 4; lane = lane + 1 {
if (precompute_v128_read_u32(bytes, lane * 4) & 0x80000000U) != 0U {
mask = mask | (1 << lane)
}
}
mask
}
@lib.I64x2AllTrue => {
let mut all = true
for lane = 0; lane < 2; lane = lane + 1 {
let offset = lane * 8
if precompute_v128_read_u32(bytes, offset) == 0U &&
precompute_v128_read_u32(bytes, offset + 4) == 0U {
all = false
}
}
if all {
1
} else {
0
}
}
@lib.I64x2Bitmask => {
let mut mask = 0
for lane = 0; lane < 2; lane = lane + 1 {
if (precompute_v128_read_u32(bytes, lane * 8 + 4) & 0x80000000U) != 0U {
mask = mask | (1 << lane)
}
}
mask
}
_ => return None
}
Some(@lib.Instruction::i32_const(@lib.I32(result)))
}
///|
fn precompute_v128_const_from_bytes(bytes : Array[Byte]) -> @lib.Instruction? {
if bytes.length() != 16 {
return None
}
Some(
@lib.Instruction::v128_const(
bytes[0],
bytes[1],
bytes[2],
bytes[3],
bytes[4],
bytes[5],
bytes[6],
bytes[7],
bytes[8],
bytes[9],
bytes[10],
bytes[11],
bytes[12],
bytes[13],
bytes[14],
bytes[15],
),
)
}
///|
fn precompute_v128_write_u64(
bytes : Array[Byte],
offset : Int32,
value : UInt64,
) -> Unit {
for byte_idx = 0; byte_idx < 8; byte_idx = byte_idx + 1 {
bytes[offset + byte_idx] = (value >> (byte_idx * 8)).to_byte()
}
}
///|
fn precompute_raw_try_fold_f64x2_unary(
operand : @lib.Instruction,
instr : @lib.Instruction,
) -> @lib.Instruction? {
let scalar_instr = match instr {
@lib.F64x2Floor => @lib.Instruction::f64_floor()
@lib.F64x2Nearest => @lib.Instruction::f64_nearest()
_ => return None
}
let bytes = match precompute_v128_const_bytes(operand) {
Some(bytes) => bytes
None => return None
}
let out = Array::make(16, b'\x00')
for lane = 0; lane < 2; lane = lane + 1 {
let offset = lane * 8
let value = precompute_v128_read_u64(bytes, offset).reinterpret_as_double()
let folded = match
precompute_raw_try_fold_unary(
@lib.Instruction::f64_const(@lib.F64(value)),
scalar_instr,
) {
Some(@lib.F64Const(@lib.F64(result))) => result.reinterpret_as_uint64()
_ => return None
}
precompute_v128_write_u64(out, offset, folded)
}
precompute_v128_const_from_bytes(out)
}
///|
fn precompute_raw_try_fold_f32x4_convert_i32x4_s(
operand : @lib.Instruction,
) -> @lib.Instruction? {
let bytes = match precompute_v128_const_bytes(operand) {
Some(bytes) => bytes
None => return None
}
let out = Array::make(16, b'\x00')
for lane = 0; lane < 4; lane = lane + 1 {
let offset = lane * 4
let value = precompute_v128_read_u32(bytes, offset).reinterpret_as_int()
precompute_v128_write_u32(
out,
offset,
Float::from_int(value).reinterpret_as_uint(),
)
}
precompute_v128_const_from_bytes(out)
}
///|
fn precompute_raw_try_fold_f32x4_convert_i32x4_u(
operand : @lib.Instruction,
) -> @lib.Instruction? {
let bytes = match precompute_v128_const_bytes(operand) {
Some(bytes) => bytes
None => return None
}
let out = Array::make(16, b'\x00')
for lane = 0; lane < 4; lane = lane + 1 {
let offset = lane * 4
let value = precompute_v128_read_u32(bytes, offset)
precompute_v128_write_u32(
out,
offset,
Float::from_uint(value).reinterpret_as_uint(),
)
}
precompute_v128_const_from_bytes(out)
}
///|
fn precompute_raw_try_fold_f64x2_eq(
lhs_instr : @lib.Instruction,
rhs_instr : @lib.Instruction,
) -> @lib.Instruction? {
let lhs = match precompute_v128_const_bytes(lhs_instr) {
Some(bytes) => bytes
None => return None
}
let rhs = match precompute_v128_const_bytes(rhs_instr) {
Some(bytes) => bytes
None => return None
}
let out = Array::make(16, b'\x00')
for lane = 0; lane < 2; lane = lane + 1 {
let offset = lane * 8
let lhs_value = precompute_v128_read_u64(lhs, offset).reinterpret_as_double()
let rhs_value = precompute_v128_read_u64(rhs, offset).reinterpret_as_double()
precompute_v128_write_u64(
out,
offset,
if lhs_value == rhs_value {
0xffffffffffffffffUL
} else {
0UL
},
)
}
precompute_v128_const_from_bytes(out)
}
///|
fn precompute_raw_try_fold_i8x16_shl(
lhs_instr : @lib.Instruction,
rhs_instr : @lib.Instruction,
) -> @lib.Instruction? {
let lhs = match precompute_v128_const_bytes(lhs_instr) {
Some(bytes) => bytes
None => return None
}
let shift = match rhs_instr {
@lib.I32Const(@lib.I32(value)) => value & 7
_ => return None
}
let out = Array::make(16, b'\x00')
for lane = 0; lane < 16; lane = lane + 1 {
out[lane] = (lhs[lane].to_uint() << shift).to_byte()
}
precompute_v128_const_from_bytes(out)
}
///|
fn precompute_raw_try_fold_i8x16_shr_s(
lhs_instr : @lib.Instruction,
rhs_instr : @lib.Instruction,
) -> @lib.Instruction? {
let lhs = match precompute_v128_const_bytes(lhs_instr) {
Some(bytes) => bytes
None => return None
}
let shift = match rhs_instr {
@lib.I32Const(@lib.I32(value)) => value & 7
_ => return None
}
let out = Array::make(16, b'\x00')
for lane = 0; lane < 16; lane = lane + 1 {
let signed = (lhs[lane].to_uint() << 24).reinterpret_as_int() >> 24
out[lane] = (signed >> shift).to_byte()
}
precompute_v128_const_from_bytes(out)
}
///|
fn precompute_raw_try_fold_v128_xor(
lhs_instr : @lib.Instruction,
rhs_instr : @lib.Instruction,
) -> @lib.Instruction? {
let lhs = match precompute_v128_const_bytes(lhs_instr) {
Some(bytes) => bytes
None => return None
}
let rhs = match precompute_v128_const_bytes(rhs_instr) {
Some(bytes) => bytes
None => return None
}
let out = Array::make(16, b'\x00')
for index = 0; index < 16; index = index + 1 {
out[index] = (lhs[index].to_uint() ^ rhs[index].to_uint()).to_byte()
}
precompute_v128_const_from_bytes(out)
}
///|
fn precompute_raw_try_fold_v128_bitselect(
first_instr : @lib.Instruction,
second_instr : @lib.Instruction,
mask_instr : @lib.Instruction,
) -> @lib.Instruction? {
let first = match precompute_v128_const_bytes(first_instr) {
Some(bytes) => bytes
None => return None
}
let second = match precompute_v128_const_bytes(second_instr) {
Some(bytes) => bytes
None => return None
}
let mask = match precompute_v128_const_bytes(mask_instr) {
Some(bytes) => bytes
None => return None
}
let out = Array::make(16, b'\x00')
for byte_idx = 0; byte_idx < 16; byte_idx = byte_idx + 1 {
out[byte_idx] = ((first[byte_idx].to_uint() & mask[byte_idx].to_uint()) |
(second[byte_idx].to_uint() & (mask[byte_idx].to_uint() ^ 0xffU))).to_byte()
}
precompute_v128_const_from_bytes(out)
}
///|
fn precompute_raw_try_fold_ternary(
first_instr : @lib.Instruction,
second_instr : @lib.Instruction,
third_instr : @lib.Instruction,
instr : @lib.Instruction,
) -> @lib.Instruction? {
match instr {
@lib.V128Bitselect =>
precompute_raw_try_fold_v128_bitselect(
first_instr, second_instr, third_instr,
)
_ => None
}
}
///|
fn precompute_raw_try_fold_f32x4_minmax(
lhs_instr : @lib.Instruction,
rhs_instr : @lib.Instruction,
scalar_op : @lib.Instruction,
) -> @lib.Instruction? {
let lhs = match precompute_v128_const_bytes(lhs_instr) {
Some(bytes) => bytes
None => return None
}
let rhs = match precompute_v128_const_bytes(rhs_instr) {
Some(bytes) => bytes
None => return None
}
let out = Array::make(16, b'\x00')
for lane = 0; lane < 4; lane = lane + 1 {
let offset = lane * 4
let lhs_value = @lib.Instruction::f32_const(
@lib.F32(
Float::reinterpret_from_uint(precompute_v128_read_u32(lhs, offset)),
),
)
let rhs_value = @lib.Instruction::f32_const(
@lib.F32(
Float::reinterpret_from_uint(precompute_v128_read_u32(rhs, offset)),
),
)
let folded = match
precompute_raw_try_fold_binary(lhs_value, rhs_value, scalar_op) {
Some(@lib.F32Const(@lib.F32(value))) => value.reinterpret_as_uint()
_ => return None
}
precompute_v128_write_u32(out, offset, folded)
}
precompute_v128_const_from_bytes(out)
}
///|
fn precompute_raw_try_fold_v128_replace_lane(
vector : @lib.Instruction,
scalar : @lib.Instruction,
instr : @lib.Instruction,
) -> @lib.Instruction? {
let bytes = match precompute_v128_const_bytes(vector) {
Some(bytes) => bytes
None => return None
}
match (instr, scalar) {
(@lib.I8x16ReplaceLane(lane), @lib.I32Const(@lib.I32(value))) => {
let lane = lane.0.to_int()
if lane >= 16 {
return None
}
bytes[lane] = value.to_byte()
}
(@lib.I16x8ReplaceLane(lane), @lib.I32Const(@lib.I32(value))) => {
let lane = lane.0.to_int()
if lane >= 8 {
return None
}
let offset = lane * 2
let bits = value.reinterpret_as_uint()
bytes[offset] = bits.to_byte()
bytes[offset + 1] = (bits >> 8).to_byte()
}
(@lib.I32x4ReplaceLane(lane), @lib.I32Const(@lib.I32(value))) => {
let lane = lane.0.to_int()
if lane >= 4 {
return None
}
precompute_v128_write_u32(bytes, lane * 4, value.reinterpret_as_uint())
}
(@lib.I64x2ReplaceLane(lane), @lib.I64Const(@lib.I64(value))) => {
let lane = lane.0.to_int()
if lane >= 2 {
return None
}
precompute_v128_write_u64(bytes, lane * 8, value.reinterpret_as_uint64())
}
(@lib.F32x4ReplaceLane(lane), @lib.F32Const(@lib.F32(value))) => {
let lane = lane.0.to_int()
if lane >= 4 {
return None
}
precompute_v128_write_u32(bytes, lane * 4, value.reinterpret_as_uint())
}
(@lib.F64x2ReplaceLane(lane), @lib.F64Const(@lib.F64(value))) => {
let lane = lane.0.to_int()
if lane >= 2 {
return None
}
precompute_v128_write_u64(bytes, lane * 8, value.reinterpret_as_uint64())
}
_ => return None
}
precompute_v128_const_from_bytes(bytes)
}
///|
fn precompute_raw_try_fold_f64x2_minmax(
lhs_instr : @lib.Instruction,
rhs_instr : @lib.Instruction,
scalar_op : @lib.Instruction,
) -> @lib.Instruction? {
let lhs = match precompute_v128_const_bytes(lhs_instr) {
Some(bytes) => bytes
None => return None
}
let rhs = match precompute_v128_const_bytes(rhs_instr) {
Some(bytes) => bytes
None => return None
}
let out = Array::make(16, b'\x00')
for lane = 0; lane < 2; lane = lane + 1 {
let offset = lane * 8
let lhs_value = @lib.Instruction::f64_const(
@lib.F64(precompute_v128_read_u64(lhs, offset).reinterpret_as_double()),
)
let rhs_value = @lib.Instruction::f64_const(
@lib.F64(precompute_v128_read_u64(rhs, offset).reinterpret_as_double()),
)
let folded = match
precompute_raw_try_fold_binary(lhs_value, rhs_value, scalar_op) {
Some(@lib.F64Const(@lib.F64(value))) => value.reinterpret_as_uint64()
_ => return None
}
precompute_v128_write_u64(out, offset, folded)
}
precompute_v128_const_from_bytes(out)
}
///|
fn precompute_raw_try_fold_i64x2_abs(
operand : @lib.Instruction,
) -> @lib.Instruction? {
let bytes = match precompute_v128_const_bytes(operand) {
Some(bytes) => bytes
None => return None
}
let out = Array::make(16, b'\x00')
for lane = 0; lane < 2; lane = lane + 1 {
let offset = lane * 8
let bits = precompute_v128_read_u64(bytes, offset)
precompute_v128_write_u64(
out,
offset,
if (bits & 0x8000000000000000UL) != 0UL {
0UL - bits
} else {
bits
},
)
}
precompute_v128_const_from_bytes(out)
}
///|
fn precompute_raw_try_fold_i64x2_neg(
operand : @lib.Instruction,
) -> @lib.Instruction? {
let bytes = match precompute_v128_const_bytes(operand) {
Some(bytes) => bytes
None => return None
}
let out = Array::make(16, b'\x00')
for lane = 0; lane < 2; lane = lane + 1 {
let offset = lane * 8
precompute_v128_write_u64(
out,
offset,
0UL - precompute_v128_read_u64(bytes, offset),
)
}
precompute_v128_const_from_bytes(out)
}
///|
fn precompute_raw_try_fold_f64x2_neg(
operand : @lib.Instruction,
) -> @lib.Instruction? {
let bytes = match precompute_v128_const_bytes(operand) {
Some(bytes) => bytes
None => return None
}
let out = bytes.copy()
out[7] = (out[7].to_uint() ^ 0x80U).to_byte()
out[15] = (out[15].to_uint() ^ 0x80U).to_byte()
precompute_v128_const_from_bytes(out)
}
///|
fn precompute_raw_try_fold_i64x2_extend_low_i32x4_s(
operand : @lib.Instruction,
) -> @lib.Instruction? {
let bytes = match precompute_v128_const_bytes(operand) {
Some(bytes) => bytes
None => return None
}
let out = Array::make(16, b'\x00')
for lane = 0; lane < 2; lane = lane + 1 {
let value = precompute_v128_read_u32(bytes, lane * 4)
.reinterpret_as_int()
.to_int64()
.reinterpret_as_uint64()
precompute_v128_write_u64(out, lane * 8, value)
}
precompute_v128_const_from_bytes(out)
}
///|
fn precompute_raw_try_fold_f32x4_abs(
operand : @lib.Instruction,
) -> @lib.Instruction? {
let bytes = match precompute_v128_const_bytes(operand) {
Some(bytes) => bytes
None => return None
}
let out = bytes.copy()
for lane = 0; lane < 4; lane = lane + 1 {
let sign_byte = lane * 4 + 3
out[sign_byte] = (out[sign_byte].to_uint() & 0x7fU).to_byte()
}
precompute_v128_const_from_bytes(out)
}
///|
fn precompute_raw_try_fold_f32x4_unary(
operand : @lib.Instruction,
scalar_op : @lib.Instruction,
) -> @lib.Instruction? {
let bytes = match precompute_v128_const_bytes(operand) {
Some(bytes) => bytes
None => return None
}
let out = Array::make(16, b'\x00')
for lane = 0; lane < 4; lane = lane + 1 {
let offset = lane * 4
let scalar = @lib.Instruction::f32_const(
@lib.F32(
Float::reinterpret_from_uint(precompute_v128_read_u32(bytes, offset)),
),
)
let folded = match precompute_raw_try_fold_unary(scalar, scalar_op) {
Some(@lib.F32Const(@lib.F32(value))) => value.reinterpret_as_uint()
_ => return None
}
precompute_v128_write_u32(out, offset, folded)
}
precompute_v128_const_from_bytes(out)
}
///|
fn precompute_raw_try_fold_i16x8_narrow_i32x4_u(
lhs_instr : @lib.Instruction,
rhs_instr : @lib.Instruction,
) -> @lib.Instruction? {
let lhs = match precompute_v128_const_bytes(lhs_instr) {
Some(bytes) => bytes
None => return None
}
let rhs = match precompute_v128_const_bytes(rhs_instr) {
Some(bytes) => bytes
None => return None
}
let out = Array::make(16, b'\x00')
for lane = 0; lane < 8; lane = lane + 1 {
let source = if lane < 4 { lhs } else { rhs }
let source_lane = if lane < 4 { lane } else { lane - 4 }
let value = precompute_v128_read_u32(source, source_lane * 4).reinterpret_as_int()
let narrowed : UInt32 = if value < 0 {
0U
} else if value > 0xffff {
0xffffU
} else {
value.reinterpret_as_uint()
}
out[lane * 2] = narrowed.to_byte()
out[lane * 2 + 1] = (narrowed >> 8).to_byte()
}
precompute_v128_const_from_bytes(out)
}
///|
fn precompute_raw_try_fold_i16x8_extadd_pairwise_i8x16_s(
operand : @lib.Instruction,
) -> @lib.Instruction? {
let bytes = match precompute_v128_const_bytes(operand) {
Some(bytes) => bytes
None => return None
}
let out = Array::make(16, b'\x00')
for lane = 0; lane < 8; lane = lane + 1 {
let lhs = (bytes[lane * 2].to_uint() << 24).reinterpret_as_int() >> 24
let rhs = (bytes[lane * 2 + 1].to_uint() << 24).reinterpret_as_int() >> 24
let value = lhs + rhs
out[lane * 2] = value.to_byte()
out[lane * 2 + 1] = (value >> 8).to_byte()
}
precompute_v128_const_from_bytes(out)
}
///|
fn precompute_raw_try_fold_i16x8_extadd_pairwise_i8x16_u(
operand : @lib.Instruction,
) -> @lib.Instruction? {
let bytes = match precompute_v128_const_bytes(operand) {
Some(bytes) => bytes
None => return None
}
let out = Array::make(16, b'\x00')
for lane = 0; lane < 8; lane = lane + 1 {
let value = bytes[lane * 2].to_uint() + bytes[lane * 2 + 1].to_uint()
out[lane * 2] = value.to_byte()
out[lane * 2 + 1] = (value >> 8).to_byte()
}
precompute_v128_const_from_bytes(out)
}
///|
fn precompute_raw_try_fold_v128_not(
operand : @lib.Instruction,
) -> @lib.Instruction? {
let bytes = match precompute_v128_const_bytes(operand) {
Some(bytes) => bytes
None => return None
}
let out = Array::make(16, b'\x00')
for index = 0; index < 16; index = index + 1 {
out[index] = (bytes[index].to_uint() ^ 0xffU).to_byte()
}
precompute_v128_const_from_bytes(out)
}
///|
fn precompute_raw_try_fold_f64x2_promote_low_f32x4(
operand : @lib.Instruction,
) -> @lib.Instruction? {
let bytes = match precompute_v128_const_bytes(operand) {
Some(bytes) => bytes
None => return None
}
let out = Array::make(16, b'\x00')
for lane = 0; lane < 2; lane = lane + 1 {
let scalar = @lib.Instruction::f32_const(
@lib.F32(
Float::reinterpret_from_uint(precompute_v128_read_u32(bytes, lane * 4)),
),
)
let folded = match
precompute_raw_try_fold_unary(scalar, @lib.Instruction::f64_promote_f32()) {
Some(@lib.F64Const(@lib.F64(value))) => value.reinterpret_as_uint64()
_ => return None
}
precompute_v128_write_u64(out, lane * 8, folded)
}
precompute_v128_const_from_bytes(out)
}
///|
fn precompute_raw_try_fold_f32x4_eq(
lhs_instr : @lib.Instruction,
rhs_instr : @lib.Instruction,
) -> @lib.Instruction? {
let lhs = match precompute_v128_const_bytes(lhs_instr) {
Some(bytes) => bytes
None => return None
}
let rhs = match precompute_v128_const_bytes(rhs_instr) {
Some(bytes) => bytes
None => return None
}
let out = Array::make(16, b'\x00')
for lane = 0; lane < 4; lane = lane + 1 {
let offset = lane * 4
let lhs_value = Float::reinterpret_from_uint(
precompute_v128_read_u32(lhs, offset),
)
let rhs_value = Float::reinterpret_from_uint(
precompute_v128_read_u32(rhs, offset),
)
precompute_v128_write_u32(
out,
offset,
if lhs_value == rhs_value {
0xffffffffU
} else {
0U
},
)
}
precompute_v128_const_from_bytes(out)
}
///|
fn precompute_raw_try_fold_i8x16_ne(
lhs_instr : @lib.Instruction,
rhs_instr : @lib.Instruction,
) -> @lib.Instruction? {
let lhs = match precompute_v128_const_bytes(lhs_instr) {
Some(bytes) => bytes
None => return None
}
let rhs = match precompute_v128_const_bytes(rhs_instr) {
Some(bytes) => bytes
None => return None
}
let out = Array::make(16, b'\x00')
for lane = 0; lane < 16; lane = lane + 1 {
out[lane] = if lhs[lane] != rhs[lane] { b'\xff' } else { b'\x00' }
}
precompute_v128_const_from_bytes(out)
}
///|
fn precompute_raw_try_fold_i64x2_ge_s(
lhs_instr : @lib.Instruction,
rhs_instr : @lib.Instruction,
) -> @lib.Instruction? {
let lhs = match precompute_v128_const_bytes(lhs_instr) {
Some(bytes) => bytes
None => return None
}
let rhs = match precompute_v128_const_bytes(rhs_instr) {
Some(bytes) => bytes
None => return None
}
let out = Array::make(16, b'\x00')
for lane = 0; lane < 2; lane = lane + 1 {
let offset = lane * 8
let folded = precompute_v128_read_u64(lhs, offset).reinterpret_as_int64() >=
precompute_v128_read_u64(rhs, offset).reinterpret_as_int64()
precompute_v128_write_u64(
out,
offset,
if folded {
0xffffffffffffffffUL
} else {
0UL
},
)
}
precompute_v128_const_from_bytes(out)
}
///|
fn precompute_raw_try_fold_f64x2_compare(
lhs_instr : @lib.Instruction,
rhs_instr : @lib.Instruction,
scalar_op : @lib.Instruction,
) -> @lib.Instruction? {
let lhs = match precompute_v128_const_bytes(lhs_instr) {
Some(bytes) => bytes
None => return None
}
let rhs = match precompute_v128_const_bytes(rhs_instr) {
Some(bytes) => bytes
None => return None
}
let out = Array::make(16, b'\x00')
for lane = 0; lane < 2; lane = lane + 1 {
let offset = lane * 8
let lhs_value = @lib.Instruction::f64_const(
@lib.F64(precompute_v128_read_u64(lhs, offset).reinterpret_as_double()),
)
let rhs_value = @lib.Instruction::f64_const(
@lib.F64(precompute_v128_read_u64(rhs, offset).reinterpret_as_double()),
)
let folded = match
precompute_raw_try_fold_binary(lhs_value, rhs_value, scalar_op) {
Some(@lib.I32Const(@lib.I32(value))) => value != 0
_ => return None
}
precompute_v128_write_u64(
out,
offset,
if folded {
0xffffffffffffffffUL
} else {
0UL
},
)
}
precompute_v128_const_from_bytes(out)
}
///|
fn precompute_raw_try_fold_unary(
operand : @lib.Instruction,
instr : @lib.Instruction,
) -> @lib.Instruction? {
match instr {
@lib.I64x2Abs =>
match precompute_raw_try_fold_i64x2_abs(operand) {
Some(folded) => return Some(folded)
None => ()
}
@lib.I64x2Neg =>
match precompute_raw_try_fold_i64x2_neg(operand) {
Some(folded) => return Some(folded)
None => ()
}
@lib.F64x2Neg =>
match precompute_raw_try_fold_f64x2_neg(operand) {
Some(folded) => return Some(folded)
None => ()
}
@lib.I64x2ExtendLowI32x4S =>
match precompute_raw_try_fold_i64x2_extend_low_i32x4_s(operand) {
Some(folded) => return Some(folded)
None => ()
}
@lib.F32x4Abs =>
match precompute_raw_try_fold_f32x4_abs(operand) {
Some(folded) => return Some(folded)
None => ()
}
@lib.F32x4Floor =>
match
precompute_raw_try_fold_f32x4_unary(
operand,
@lib.Instruction::f32_floor(),
) {
Some(folded) => return Some(folded)
None => ()
}
@lib.F32x4Ceil =>
match
precompute_raw_try_fold_f32x4_unary(
operand,
@lib.Instruction::f32_ceil(),
) {
Some(folded) => return Some(folded)
None => ()
}
@lib.F32x4Trunc =>
match
precompute_raw_try_fold_f32x4_unary(
operand,
@lib.Instruction::f32_trunc(),
) {
Some(folded) => return Some(folded)
None => ()
}
@lib.I16x8ExtaddPairwiseI8x16S =>
match precompute_raw_try_fold_i16x8_extadd_pairwise_i8x16_s(operand) {
Some(folded) => return Some(folded)
None => ()
}
@lib.I16x8ExtaddPairwiseI8x16U =>
match precompute_raw_try_fold_i16x8_extadd_pairwise_i8x16_u(operand) {
Some(folded) => return Some(folded)
None => ()
}
@lib.V128Not =>
match precompute_raw_try_fold_v128_not(operand) {
Some(folded) => return Some(folded)
None => ()
}
@lib.F64x2PromoteLowF32x4 =>
match precompute_raw_try_fold_f64x2_promote_low_f32x4(operand) {
Some(folded) => return Some(folded)
None => ()
}
_ => ()
}
match precompute_raw_try_fold_f64x2_unary(operand, instr) {
Some(folded) => return Some(folded)
None => ()
}
match instr {
@lib.F32x4ConvertI32x4S =>
match precompute_raw_try_fold_f32x4_convert_i32x4_s(operand) {
Some(folded) => return Some(folded)
None => ()
}
@lib.F32x4ConvertI32x4U =>
match precompute_raw_try_fold_f32x4_convert_i32x4_u(operand) {
Some(folded) => return Some(folded)
None => ()
}
_ => ()
}
match precompute_raw_try_fold_v128_lane_extract(operand, instr) {
Some(folded) => return Some(folded)
None => ()
}
match precompute_raw_try_fold_v128_reduction(operand, instr) {
Some(folded) => return Some(folded)
None => ()
}
match (instr, operand) {
(@lib.I8x16Splat, @lib.I32Const(@lib.I32(value))) => {
let bytes = Array::make(16, value.to_byte())
return precompute_v128_const_from_bytes(bytes)
}
(@lib.I16x8Splat, @lib.I32Const(@lib.I32(value))) => {
let bits = value.reinterpret_as_uint()
let bytes = Array::makei(16, fn(index) {
(bits >> (index % 2 * 8)).to_byte()
})
return precompute_v128_const_from_bytes(bytes)
}
(@lib.I64x2Splat, @lib.I64Const(@lib.I64(value))) => {
let bytes = Array::make(16, b'\x00')
for offset in [0, 8] {
precompute_v128_write_u64(bytes, offset, value.reinterpret_as_uint64())
}
return precompute_v128_const_from_bytes(bytes)
}
(@lib.F32x4Splat, @lib.F32Const(@lib.F32(value))) => {
let bytes = Array::make(16, b'\x00')
for offset in [0, 4, 8, 12] {
precompute_v128_write_u32(bytes, offset, value.reinterpret_as_uint())
}
return precompute_v128_const_from_bytes(bytes)
}
(@lib.F64x2Splat, @lib.F64Const(@lib.F64(value))) => {
let bytes = Array::make(16, b'\x00')
for offset in [0, 8] {
precompute_v128_write_u64(bytes, offset, value.reinterpret_as_uint64())
}
return precompute_v128_const_from_bytes(bytes)
}
(@lib.I32x4Splat, @lib.I32Const(@lib.I32(value))) => {
let bytes = Array::make(16, b'\x00')
let bits = value.reinterpret_as_uint()
for lane = 0; lane < 4; lane = lane + 1 {
precompute_v128_write_u32(bytes, lane * 4, bits)
}
return precompute_v128_const_from_bytes(bytes)
}
(@lib.RefCast(true, exact, heap_type), @lib.RefNull(_)) =>
return Some(
@lib.Instruction::ref_null_type(
@lib.RefType::new(true, heap_type, exact~),
),
)
(@lib.RefIsNull, @lib.RefNull(_)) =>
return Some(@lib.Instruction::i32_const(@lib.I32(1)))
(@lib.StringAsWtf16, @lib.StringConst(_)) => return Some(operand)
(@lib.StringMeasureWtf16, @lib.StringConst(bytes)) =>
match precompute_string_bytes_to_utf16(bytes) {
Some(units) =>
return Some(@lib.Instruction::i32_const(@lib.I32(units.length())))
None => return None
}
_ => ()
}
match (instr, precompute_raw_i32_const(operand)) {
(@lib.Instruction::I32Eqz, Some(v)) =>
return Some(precompute_raw_bool(v == @lib.I32(0)))
(@lib.Instruction::I32Clz, Some(@lib.I32(v))) =>
return Some(
@lib.Instruction::i32_const(@lib.I32(v.reinterpret_as_uint().clz())),
)
(@lib.Instruction::I32Ctz, Some(@lib.I32(v))) =>
return Some(
@lib.Instruction::i32_const(@lib.I32(v.reinterpret_as_uint().ctz())),
)
(@lib.Instruction::I32Popcnt, Some(@lib.I32(v))) =>
return Some(
@lib.Instruction::i32_const(@lib.I32(v.reinterpret_as_uint().popcnt())),
)
(@lib.Instruction::I64ExtendI32S, Some(@lib.I32(v))) =>
return Some(@lib.Instruction::i64_const(@lib.I64(v.to_int64())))
(@lib.Instruction::I64ExtendI32U, Some(@lib.I32(v))) =>
return Some(
@lib.Instruction::i64_const(
@lib.I64(v.reinterpret_as_uint().to_uint64().reinterpret_as_int64()),
),
)
(@lib.Instruction::F32ConvertI32S, Some(@lib.I32(v))) =>
return Some(@lib.Instruction::f32_const(@lib.F32(Float::from_int(v))))
(@lib.Instruction::F32ConvertI32U, Some(@lib.I32(v))) =>
return Some(
@lib.Instruction::f32_const(
@lib.F32(Float::from_uint(v.reinterpret_as_uint())),
),
)
(@lib.Instruction::F64ConvertI32S, Some(@lib.I32(v))) =>
return Some(@lib.Instruction::f64_const(@lib.F64(v.to_double())))
(@lib.Instruction::F64ConvertI32U, Some(@lib.I32(v))) =>
return Some(
@lib.Instruction::f64_const(
@lib.F64(v.reinterpret_as_uint().to_double()),
),
)
(@lib.Instruction::F32ReinterpretI32, Some(@lib.I32(v))) =>
return Some(
@lib.Instruction::f32_const(
@lib.F32(Float::reinterpret_from_uint(v.reinterpret_as_uint())),
),
)
(@lib.Instruction::I32Extend8S, Some(@lib.I32(v))) =>
return Some(@lib.Instruction::i32_const(@lib.I32(v << 24 >> 24)))
(@lib.Instruction::I32Extend16S, Some(@lib.I32(v))) =>
return Some(@lib.Instruction::i32_const(@lib.I32(v << 16 >> 16)))
_ => ()
}
match (instr, precompute_raw_i64_const(operand)) {
(@lib.Instruction::I64Eqz, Some(v)) =>
return Some(precompute_raw_bool(v == @lib.I64(0L)))
(@lib.Instruction::I64Clz, Some(@lib.I64(v))) =>
return Some(
@lib.Instruction::i64_const(
@lib.I64(v.reinterpret_as_uint64().clz().to_int64()),
),
)
(@lib.Instruction::I64Ctz, Some(@lib.I64(v))) =>
return Some(
@lib.Instruction::i64_const(
@lib.I64(v.reinterpret_as_uint64().ctz().to_int64()),
),
)
(@lib.Instruction::I64Popcnt, Some(@lib.I64(v))) =>
return Some(
@lib.Instruction::i64_const(
@lib.I64(v.reinterpret_as_uint64().popcnt().to_int64()),
),
)
(@lib.Instruction::I32WrapI64, Some(@lib.I64(v))) =>
return Some(
@lib.Instruction::i32_const(
@lib.I32(v.reinterpret_as_uint64().to_uint().reinterpret_as_int()),
),
)
(@lib.Instruction::F32ConvertI64S, Some(@lib.I64(v))) =>
return Some(
@lib.Instruction::f32_const(
@lib.F32(
precompute_u64_to_f32(
if v < 0L {
0UL - v.reinterpret_as_uint64()
} else {
v.reinterpret_as_uint64()
},
negative=v < 0L,
),
),
),
)
(@lib.Instruction::F32ConvertI64U, Some(@lib.I64(v))) =>
return Some(
@lib.Instruction::f32_const(
@lib.F32(precompute_u64_to_f32(v.reinterpret_as_uint64())),
),
)
(@lib.Instruction::F64ConvertI64S, Some(@lib.I64(v))) =>
return Some(@lib.Instruction::f64_const(@lib.F64(v.to_double())))
(@lib.Instruction::F64ConvertI64U, Some(@lib.I64(v))) =>
return Some(
@lib.Instruction::f64_const(
@lib.F64(v.reinterpret_as_uint64().to_double()),
),
)
(@lib.Instruction::F64ReinterpretI64, Some(@lib.I64(v))) =>
return Some(
@lib.Instruction::f64_const(
@lib.F64(v.reinterpret_as_uint64().reinterpret_as_double()),
),
)
(@lib.Instruction::I64Extend8S, Some(@lib.I64(v))) =>
return Some(@lib.Instruction::i64_const(@lib.I64(v << 56 >> 56)))
(@lib.Instruction::I64Extend16S, Some(@lib.I64(v))) =>
return Some(@lib.Instruction::i64_const(@lib.I64(v << 48 >> 48)))
(@lib.Instruction::I64Extend32S, Some(@lib.I64(v))) =>
return Some(@lib.Instruction::i64_const(@lib.I64(v << 32 >> 32)))
_ => ()
}
match (instr, precompute_raw_f32_const(operand)) {
(@lib.Instruction::F32Abs, Some(@lib.F32(v))) =>
return Some(
@lib.Instruction::f32_const(
@lib.F32(
Float::reinterpret_from_uint(v.reinterpret_as_uint() & 0x7fffffffU),
),
),
)
(@lib.Instruction::F32Neg, Some(@lib.F32(v))) =>
return Some(
@lib.Instruction::f32_const(
@lib.F32(
Float::reinterpret_from_uint(v.reinterpret_as_uint() ^ 0x80000000U),
),
),
)
(@lib.Instruction::F32Ceil, Some(@lib.F32(v))) =>
return Some(
@lib.Instruction::f32_const(
@lib.F32(
if v.is_nan() {
Float::reinterpret_from_uint(0x7fc00000U)
} else {
v.ceil()
},
),
),
)
(@lib.Instruction::F32Floor, Some(@lib.F32(v))) =>
return Some(
@lib.Instruction::f32_const(
@lib.F32(
if v.is_nan() {
Float::reinterpret_from_uint(0x7fc00000U)
} else {
v.floor()
},
),
),
)
(@lib.Instruction::F32Trunc, Some(@lib.F32(v))) =>
return Some(
@lib.Instruction::f32_const(
@lib.F32(
if v.is_nan() {
Float::reinterpret_from_uint(0x7fc00000U)
} else {
v.trunc()
},
),
),
)
(@lib.Instruction::F32Nearest, Some(@lib.F32(v))) =>
return Some(
@lib.Instruction::f32_const(
@lib.F32(
if v.is_nan() {
Float::reinterpret_from_uint(0x7fc00000U)
} else {
precompute_raw_f32_nearest(v)
},
),
),
)
(@lib.Instruction::F32Sqrt, Some(@lib.F32(v))) =>
return Some(
@lib.Instruction::f32_const(
@lib.F32(
if v.is_nan() || v < Float::from_double(0.0) {
Float::reinterpret_from_uint(0x7fc00000U)
} else {
v.sqrt()
},
),
),
)
(@lib.Instruction::F64PromoteF32, Some(@lib.F32(v))) =>
return Some(
@lib.Instruction::f64_const(
@lib.F64(
if v.is_nan() {
0x7ff8000000000000UL.reinterpret_as_double()
} else {
v.to_double()
},
),
),
)
(@lib.Instruction::I32ReinterpretF32, Some(@lib.F32(v))) =>
return Some(
@lib.Instruction::i32_const(
@lib.I32(v.reinterpret_as_uint().reinterpret_as_int()),
),
)
(@lib.Instruction::I32TruncF32S, Some(@lib.F32(v))) =>
return precompute_raw_try_fold_i32_trunc(v.to_double(), true, false)
(@lib.Instruction::I32TruncF32U, Some(@lib.F32(v))) =>
return precompute_raw_try_fold_i32_trunc(v.to_double(), false, false)
(@lib.Instruction::I64TruncF32S, Some(@lib.F32(v))) =>
return precompute_raw_try_fold_i64_trunc(v.to_double(), true, false)
(@lib.Instruction::I64TruncF32U, Some(@lib.F32(v))) =>
return precompute_raw_try_fold_i64_trunc(v.to_double(), false, false)
(@lib.Instruction::I32TruncSatF32S, Some(@lib.F32(v))) =>
return precompute_raw_try_fold_i32_trunc(v.to_double(), true, true)
(@lib.Instruction::I32TruncSatF32U, Some(@lib.F32(v))) =>
return precompute_raw_try_fold_i32_trunc(v.to_double(), false, true)
(@lib.Instruction::I64TruncSatF32S, Some(@lib.F32(v))) =>
return precompute_raw_try_fold_i64_trunc(v.to_double(), true, true)
(@lib.Instruction::I64TruncSatF32U, Some(@lib.F32(v))) =>
return precompute_raw_try_fold_i64_trunc(v.to_double(), false, true)
_ => ()
}
match (instr, precompute_raw_f64_const(operand)) {
(@lib.Instruction::F64Abs, Some(@lib.F64(v))) =>
Some(
@lib.Instruction::f64_const(
@lib.F64(
(v.reinterpret_as_uint64() & 0x7fffffffffffffffUL).reinterpret_as_double(),
),
),
)
(@lib.Instruction::F64Neg, Some(@lib.F64(v))) =>
Some(
@lib.Instruction::f64_const(
@lib.F64(
(v.reinterpret_as_uint64() ^ 0x8000000000000000UL).reinterpret_as_double(),
),
),
)
(@lib.Instruction::F64Ceil, Some(@lib.F64(v))) =>
Some(
@lib.Instruction::f64_const(
@lib.F64(
if v.is_nan() {
0x7ff8000000000000UL.reinterpret_as_double()
} else {
v.ceil()
},
),
),
)
(@lib.Instruction::F64Floor, Some(@lib.F64(v))) =>
Some(
@lib.Instruction::f64_const(
@lib.F64(
if v.is_nan() {
0x7ff8000000000000UL.reinterpret_as_double()
} else {
v.floor()
},
),
),
)
(@lib.Instruction::F64Trunc, Some(@lib.F64(v))) =>
Some(
@lib.Instruction::f64_const(
@lib.F64(
if v.is_nan() {
0x7ff8000000000000UL.reinterpret_as_double()
} else {
v.trunc()
},
),
),
)
(@lib.Instruction::F64Nearest, Some(@lib.F64(v))) =>
Some(
@lib.Instruction::f64_const(
@lib.F64(
if v.is_nan() {
0x7ff8000000000000UL.reinterpret_as_double()
} else {
precompute_raw_f64_nearest(v)
},
),
),
)
(@lib.Instruction::F64Sqrt, Some(@lib.F64(v))) =>
Some(
@lib.Instruction::f64_const(
@lib.F64(
if v.is_nan() || v < 0.0 {
0x7ff8000000000000UL.reinterpret_as_double()
} else {
v.sqrt()
},
),
),
)
(@lib.Instruction::F32DemoteF64, Some(@lib.F64(v))) =>
Some(
@lib.Instruction::f32_const(
@lib.F32(
if v.is_nan() {
Float::reinterpret_from_uint(0x7fc00000U)
} else {
Float::from_double(v)
},
),
),
)
(@lib.Instruction::I64ReinterpretF64, Some(@lib.F64(v))) =>
Some(
@lib.Instruction::i64_const(
@lib.I64(v.reinterpret_as_uint64().reinterpret_as_int64()),
),
)
(@lib.Instruction::I32TruncF64S, Some(@lib.F64(v))) =>
precompute_raw_try_fold_i32_trunc(v, true, false)
(@lib.Instruction::I32TruncF64U, Some(@lib.F64(v))) =>
precompute_raw_try_fold_i32_trunc(v, false, false)
(@lib.Instruction::I64TruncF64S, Some(@lib.F64(v))) =>
precompute_raw_try_fold_i64_trunc(v, true, false)
(@lib.Instruction::I64TruncF64U, Some(@lib.F64(v))) =>
precompute_raw_try_fold_i64_trunc(v, false, false)
(@lib.Instruction::I32TruncSatF64S, Some(@lib.F64(v))) =>
precompute_raw_try_fold_i32_trunc(v, true, true)
(@lib.Instruction::I32TruncSatF64U, Some(@lib.F64(v))) =>
precompute_raw_try_fold_i32_trunc(v, false, true)
(@lib.Instruction::I64TruncSatF64S, Some(@lib.F64(v))) =>
precompute_raw_try_fold_i64_trunc(v, true, true)
(@lib.Instruction::I64TruncSatF64U, Some(@lib.F64(v))) =>
precompute_raw_try_fold_i64_trunc(v, false, true)
_ => None
}
}
///|
fn precompute_raw_try_fold_binary(
lhs_instr : @lib.Instruction,
rhs_instr : @lib.Instruction,
instr : @lib.Instruction,
) -> @lib.Instruction? {
match precompute_raw_try_fold_v128_replace_lane(lhs_instr, rhs_instr, instr) {
Some(folded) => return Some(folded)
None => ()
}
match (instr, lhs_instr, rhs_instr) {
(@lib.F32x4Eq, _, _) =>
return precompute_raw_try_fold_f32x4_eq(lhs_instr, rhs_instr)
(@lib.F32x4Min, _, _) =>
return precompute_raw_try_fold_f32x4_minmax(
lhs_instr,
rhs_instr,
@lib.Instruction::f32_min(),
)
(@lib.F32x4Max, _, _) =>
return precompute_raw_try_fold_f32x4_minmax(
lhs_instr,
rhs_instr,
@lib.Instruction::f32_max(),
)
(@lib.F64x2Min, _, _) =>
return precompute_raw_try_fold_f64x2_minmax(
lhs_instr,
rhs_instr,
@lib.Instruction::f64_min(),
)
(@lib.F64x2Max, _, _) =>
return precompute_raw_try_fold_f64x2_minmax(
lhs_instr,
rhs_instr,
@lib.Instruction::f64_max(),
)
(@lib.F64x2Gt, _, _) =>
return precompute_raw_try_fold_f64x2_compare(
lhs_instr,
rhs_instr,
@lib.Instruction::f64_gt(),
)
(@lib.I64x2GeS, _, _) =>
return precompute_raw_try_fold_i64x2_ge_s(lhs_instr, rhs_instr)
(@lib.I8x16Ne, _, _) =>
return precompute_raw_try_fold_i8x16_ne(lhs_instr, rhs_instr)
(@lib.V128Xor, _, _) =>
return precompute_raw_try_fold_v128_xor(lhs_instr, rhs_instr)
(@lib.I16x8NarrowI32x4U, _, _) =>
return precompute_raw_try_fold_i16x8_narrow_i32x4_u(lhs_instr, rhs_instr)
(@lib.F64x2Eq, _, _) =>
return precompute_raw_try_fold_f64x2_eq(lhs_instr, rhs_instr)
(@lib.I8x16Shl, _, _) =>
return precompute_raw_try_fold_i8x16_shl(lhs_instr, rhs_instr)
(@lib.I8x16ShrS, _, _) =>
return precompute_raw_try_fold_i8x16_shr_s(lhs_instr, rhs_instr)
(@lib.StringConcat, @lib.StringConst(lhs), @lib.StringConst(rhs)) => {
let lhs_units = match precompute_string_bytes_to_utf16(lhs) {
Some(units) => units
None => return None
}
let rhs_units = match precompute_string_bytes_to_utf16(rhs) {
Some(units) => units
None => return None
}
let units = lhs_units + rhs_units
return match
precompute_utf16_units_to_valid_utf8(units, 0, units.length()) {
Some(bytes) => Some(@lib.Instruction::string_const(bytes))
None => None
}
}
(@lib.StringEq, @lib.StringConst(lhs), @lib.StringConst(rhs)) => {
let lhs_units = match precompute_string_bytes_to_utf16(lhs) {
Some(units) => units
None => return None
}
let rhs_units = match precompute_string_bytes_to_utf16(rhs) {
Some(units) => units
None => return None
}
return Some(precompute_raw_bool(lhs_units == rhs_units))
}
(
@lib.StringViewWtf16GetCodeunit,
@lib.StringConst(bytes),
@lib.I32Const(@lib.I32(index)),
) => {
let units = match precompute_string_bytes_to_utf16(bytes) {
Some(units) => units
None => return None
}
if index < 0 || index >= units.length() {
return None
}
return Some(@lib.Instruction::i32_const(@lib.I32(units[index])))
}
_ => ()
}
match
(
instr,
precompute_raw_i32_const(lhs_instr),
precompute_raw_i32_const(rhs_instr),
) {
(@lib.Instruction::I32Add, Some(@lib.I32(lhs)), Some(@lib.I32(rhs))) =>
return Some(
@lib.Instruction::i32_const(
@lib.I32(
(lhs.reinterpret_as_uint() + rhs.reinterpret_as_uint()).reinterpret_as_int(),
),
),
)
(@lib.Instruction::I32Sub, Some(@lib.I32(lhs)), Some(@lib.I32(rhs))) =>
return Some(
@lib.Instruction::i32_const(
@lib.I32(
(lhs.reinterpret_as_uint() - rhs.reinterpret_as_uint()).reinterpret_as_int(),
),
),
)
(@lib.Instruction::I32Mul, Some(@lib.I32(lhs)), Some(@lib.I32(rhs))) =>
return Some(
@lib.Instruction::i32_const(
@lib.I32(
(lhs.reinterpret_as_uint() * rhs.reinterpret_as_uint()).reinterpret_as_int(),
),
),
)
(@lib.Instruction::I32And, Some(@lib.I32(lhs)), Some(@lib.I32(rhs))) =>
return Some(@lib.Instruction::i32_const(@lib.I32(lhs & rhs)))
(@lib.Instruction::I32Or, Some(@lib.I32(lhs)), Some(@lib.I32(rhs))) =>
return Some(@lib.Instruction::i32_const(@lib.I32(lhs | rhs)))
(@lib.Instruction::I32Xor, Some(@lib.I32(lhs)), Some(@lib.I32(rhs))) =>
return Some(@lib.Instruction::i32_const(@lib.I32(lhs ^ rhs)))
(@lib.Instruction::I32Shl, Some(@lib.I32(lhs)), Some(@lib.I32(rhs))) => {
let shift = rhs.reinterpret_as_uint() & 31U
return Some(
@lib.Instruction::i32_const(
@lib.I32(
(lhs.reinterpret_as_uint() << shift.reinterpret_as_int()).reinterpret_as_int(),
),
),
)
}
(@lib.Instruction::I32ShrU, Some(@lib.I32(lhs)), Some(@lib.I32(rhs))) => {
let shift = rhs.reinterpret_as_uint() & 31U
return Some(
@lib.Instruction::i32_const(
@lib.I32(
(lhs.reinterpret_as_uint() >> shift.reinterpret_as_int()).reinterpret_as_int(),
),
),
)
}
(@lib.Instruction::I32ShrS, Some(@lib.I32(lhs)), Some(@lib.I32(rhs))) => {
let shift = rhs.reinterpret_as_uint() & 31U
return Some(
@lib.Instruction::i32_const(@lib.I32(lhs >> shift.reinterpret_as_int())),
)
}
(@lib.Instruction::I32Rotl, Some(@lib.I32(lhs)), Some(@lib.I32(rhs))) => {
let shift = rhs.reinterpret_as_uint() & 31U
let value = lhs.reinterpret_as_uint()
let rotated = if shift == 0U {
value
} else {
(value << shift.reinterpret_as_int()) |
(value >> (32 - shift.reinterpret_as_int()))
}
return Some(
@lib.Instruction::i32_const(@lib.I32(rotated.reinterpret_as_int())),
)
}
(@lib.Instruction::I32Rotr, Some(@lib.I32(lhs)), Some(@lib.I32(rhs))) => {
let shift = rhs.reinterpret_as_uint() & 31U
let value = lhs.reinterpret_as_uint()
let rotated = if shift == 0U {
value
} else {
(value >> shift.reinterpret_as_int()) |
(value << (32 - shift.reinterpret_as_int()))
}
return Some(
@lib.Instruction::i32_const(@lib.I32(rotated.reinterpret_as_int())),
)
}
(@lib.Instruction::I32Eq, Some(lhs), Some(rhs)) =>
return Some(precompute_raw_bool(lhs == rhs))
(@lib.Instruction::I32Ne, Some(lhs), Some(rhs)) =>
return Some(precompute_raw_bool(lhs != rhs))
(@lib.Instruction::I32LtS, Some(@lib.I32(lhs)), Some(@lib.I32(rhs))) =>
return Some(precompute_raw_bool(lhs < rhs))
(@lib.Instruction::I32LtU, Some(@lib.I32(lhs)), Some(@lib.I32(rhs))) =>
return Some(
precompute_raw_bool(
lhs.reinterpret_as_uint() < rhs.reinterpret_as_uint(),
),
)
(@lib.Instruction::I32GtS, Some(@lib.I32(lhs)), Some(@lib.I32(rhs))) =>
return Some(precompute_raw_bool(lhs > rhs))
(@lib.Instruction::I32GtU, Some(@lib.I32(lhs)), Some(@lib.I32(rhs))) =>
return Some(
precompute_raw_bool(
lhs.reinterpret_as_uint() > rhs.reinterpret_as_uint(),
),
)
(@lib.Instruction::I32LeS, Some(@lib.I32(lhs)), Some(@lib.I32(rhs))) =>
return Some(precompute_raw_bool(lhs <= rhs))
(@lib.Instruction::I32LeU, Some(@lib.I32(lhs)), Some(@lib.I32(rhs))) =>
return Some(
precompute_raw_bool(
lhs.reinterpret_as_uint() <= rhs.reinterpret_as_uint(),
),
)
(@lib.Instruction::I32GeS, Some(@lib.I32(lhs)), Some(@lib.I32(rhs))) =>
return Some(precompute_raw_bool(lhs >= rhs))
(@lib.Instruction::I32GeU, Some(@lib.I32(lhs)), Some(@lib.I32(rhs))) =>
return Some(
precompute_raw_bool(
lhs.reinterpret_as_uint() >= rhs.reinterpret_as_uint(),
),
)
(@lib.Instruction::I32DivS, Some(@lib.I32(lhs)), Some(@lib.I32(rhs))) => {
if rhs == 0 || (lhs == -2147483648 && rhs == -1) {
return None
}
return Some(@lib.Instruction::i32_const(@lib.I32(lhs / rhs)))
}
(@lib.Instruction::I32DivU, Some(@lib.I32(lhs)), Some(@lib.I32(rhs))) => {
let divisor = rhs.reinterpret_as_uint()
if divisor == 0U {
return None
}
return Some(
@lib.Instruction::i32_const(
@lib.I32((lhs.reinterpret_as_uint() / divisor).reinterpret_as_int()),
),
)
}
(@lib.Instruction::I32RemS, Some(@lib.I32(lhs)), Some(@lib.I32(rhs))) => {
if rhs == 0 {
return None
}
let value = if lhs == -2147483648 && rhs == -1 { 0 } else { lhs % rhs }
return Some(@lib.Instruction::i32_const(@lib.I32(value)))
}
(@lib.Instruction::I32RemU, Some(@lib.I32(lhs)), Some(@lib.I32(rhs))) => {
let divisor = rhs.reinterpret_as_uint()
if divisor == 0U {
return None
}
return Some(
@lib.Instruction::i32_const(
@lib.I32((lhs.reinterpret_as_uint() % divisor).reinterpret_as_int()),
),
)
}
_ => ()
}
match
(
instr,
precompute_raw_i64_const(lhs_instr),
precompute_raw_i64_const(rhs_instr),
) {
(@lib.Instruction::I64Add, Some(@lib.I64(lhs)), Some(@lib.I64(rhs))) =>
return Some(
@lib.Instruction::i64_const(
@lib.I64(
(lhs.reinterpret_as_uint64() + rhs.reinterpret_as_uint64()).reinterpret_as_int64(),
),
),
)
(@lib.Instruction::I64Sub, Some(@lib.I64(lhs)), Some(@lib.I64(rhs))) =>
return Some(
@lib.Instruction::i64_const(
@lib.I64(
(lhs.reinterpret_as_uint64() - rhs.reinterpret_as_uint64()).reinterpret_as_int64(),
),
),
)
(@lib.Instruction::I64Mul, Some(@lib.I64(lhs)), Some(@lib.I64(rhs))) =>
return Some(
@lib.Instruction::i64_const(
@lib.I64(
(lhs.reinterpret_as_uint64() * rhs.reinterpret_as_uint64()).reinterpret_as_int64(),
),
),
)
(@lib.Instruction::I64And, Some(@lib.I64(lhs)), Some(@lib.I64(rhs))) =>
return Some(@lib.Instruction::i64_const(@lib.I64(lhs & rhs)))
(@lib.Instruction::I64Or, Some(@lib.I64(lhs)), Some(@lib.I64(rhs))) =>
return Some(@lib.Instruction::i64_const(@lib.I64(lhs | rhs)))
(@lib.Instruction::I64Xor, Some(@lib.I64(lhs)), Some(@lib.I64(rhs))) =>
return Some(@lib.Instruction::i64_const(@lib.I64(lhs ^ rhs)))
(@lib.Instruction::I64Shl, Some(@lib.I64(lhs)), Some(@lib.I64(rhs))) => {
let shift = rhs.reinterpret_as_uint64() & 63UL
return Some(
@lib.Instruction::i64_const(
@lib.I64(
(lhs.reinterpret_as_uint64() << shift.to_int()).reinterpret_as_int64(),
),
),
)
}
(@lib.Instruction::I64ShrU, Some(@lib.I64(lhs)), Some(@lib.I64(rhs))) => {
let shift = rhs.reinterpret_as_uint64() & 63UL
return Some(
@lib.Instruction::i64_const(
@lib.I64(
(lhs.reinterpret_as_uint64() >> shift.to_int()).reinterpret_as_int64(),
),
),
)
}
(@lib.Instruction::I64ShrS, Some(@lib.I64(lhs)), Some(@lib.I64(rhs))) => {
let shift = rhs.reinterpret_as_uint64() & 63UL
return Some(@lib.Instruction::i64_const(@lib.I64(lhs >> shift.to_int())))
}
(@lib.Instruction::I64Rotl, Some(@lib.I64(lhs)), Some(@lib.I64(rhs))) => {
let shift = rhs.reinterpret_as_uint64() & 63UL
let value = lhs.reinterpret_as_uint64()
let rotated = if shift == 0UL {
value
} else {
(value << shift.to_int()) | (value >> (64 - shift.to_int()))
}
return Some(
@lib.Instruction::i64_const(@lib.I64(rotated.reinterpret_as_int64())),
)
}
(@lib.Instruction::I64Rotr, Some(@lib.I64(lhs)), Some(@lib.I64(rhs))) => {
let shift = rhs.reinterpret_as_uint64() & 63UL
let value = lhs.reinterpret_as_uint64()
let rotated = if shift == 0UL {
value
} else {
(value >> shift.to_int()) | (value << (64 - shift.to_int()))
}
return Some(
@lib.Instruction::i64_const(@lib.I64(rotated.reinterpret_as_int64())),
)
}
(@lib.Instruction::I64Eq, Some(lhs), Some(rhs)) =>
return Some(precompute_raw_bool(lhs == rhs))
(@lib.Instruction::I64Ne, Some(lhs), Some(rhs)) =>
return Some(precompute_raw_bool(lhs != rhs))
(@lib.Instruction::I64LtS, Some(@lib.I64(lhs)), Some(@lib.I64(rhs))) =>
return Some(precompute_raw_bool(lhs < rhs))
(@lib.Instruction::I64LtU, Some(@lib.I64(lhs)), Some(@lib.I64(rhs))) =>
return Some(
precompute_raw_bool(
lhs.reinterpret_as_uint64() < rhs.reinterpret_as_uint64(),
),
)
(@lib.Instruction::I64GtS, Some(@lib.I64(lhs)), Some(@lib.I64(rhs))) =>
return Some(precompute_raw_bool(lhs > rhs))
(@lib.Instruction::I64GtU, Some(@lib.I64(lhs)), Some(@lib.I64(rhs))) =>
return Some(
precompute_raw_bool(
lhs.reinterpret_as_uint64() > rhs.reinterpret_as_uint64(),
),
)
(@lib.Instruction::I64LeS, Some(@lib.I64(lhs)), Some(@lib.I64(rhs))) =>
return Some(precompute_raw_bool(lhs <= rhs))
(@lib.Instruction::I64LeU, Some(@lib.I64(lhs)), Some(@lib.I64(rhs))) =>
return Some(
precompute_raw_bool(
lhs.reinterpret_as_uint64() <= rhs.reinterpret_as_uint64(),
),
)
(@lib.Instruction::I64GeS, Some(@lib.I64(lhs)), Some(@lib.I64(rhs))) =>
return Some(precompute_raw_bool(lhs >= rhs))
(@lib.Instruction::I64GeU, Some(@lib.I64(lhs)), Some(@lib.I64(rhs))) =>
return Some(
precompute_raw_bool(
lhs.reinterpret_as_uint64() >= rhs.reinterpret_as_uint64(),
),
)
(@lib.Instruction::I64DivS, Some(@lib.I64(lhs)), Some(@lib.I64(rhs))) => {
if rhs == 0L || (lhs == -9223372036854775808L && rhs == -1L) {
return None
}
return Some(@lib.Instruction::i64_const(@lib.I64(lhs / rhs)))
}
(@lib.Instruction::I64DivU, Some(@lib.I64(lhs)), Some(@lib.I64(rhs))) => {
let divisor = rhs.reinterpret_as_uint64()
if divisor == 0UL {
return None
}
return Some(
@lib.Instruction::i64_const(
@lib.I64(
(lhs.reinterpret_as_uint64() / divisor).reinterpret_as_int64(),
),
),
)
}
(@lib.Instruction::I64RemS, Some(@lib.I64(lhs)), Some(@lib.I64(rhs))) => {
if rhs == 0L {
return None
}
let value = if lhs == -9223372036854775808L && rhs == -1L {
0L
} else {
lhs % rhs
}
return Some(@lib.Instruction::i64_const(@lib.I64(value)))
}
(@lib.Instruction::I64RemU, Some(@lib.I64(lhs)), Some(@lib.I64(rhs))) => {
let divisor = rhs.reinterpret_as_uint64()
if divisor == 0UL {
return None
}
return Some(
@lib.Instruction::i64_const(
@lib.I64(
(lhs.reinterpret_as_uint64() % divisor).reinterpret_as_int64(),
),
),
)
}
_ => ()
}
match
(
instr,
precompute_raw_f32_const(lhs_instr),
precompute_raw_f32_const(rhs_instr),
) {
(@lib.Instruction::F32Add, Some(@lib.F32(lhs)), Some(@lib.F32(rhs))) => {
let value = lhs + rhs
return Some(
@lib.Instruction::f32_const(
@lib.F32(
if value.is_nan() {
Float::reinterpret_from_uint(0x7fc00000U)
} else {
value
},
),
),
)
}
(@lib.Instruction::F32Sub, Some(@lib.F32(lhs)), Some(@lib.F32(rhs))) => {
let value = lhs - rhs
return Some(
@lib.Instruction::f32_const(
@lib.F32(
if value.is_nan() {
Float::reinterpret_from_uint(0x7fc00000U)
} else {
value
},
),
),
)
}
(@lib.Instruction::F32Mul, Some(@lib.F32(lhs)), Some(@lib.F32(rhs))) => {
let value = lhs * rhs
return Some(
@lib.Instruction::f32_const(
@lib.F32(
if value.is_nan() {
Float::reinterpret_from_uint(0x7fc00000U)
} else {
value
},
),
),
)
}
(@lib.Instruction::F32Div, Some(@lib.F32(lhs)), Some(@lib.F32(rhs))) => {
let value = lhs / rhs
return Some(
@lib.Instruction::f32_const(
@lib.F32(
if value.is_nan() {
Float::reinterpret_from_uint(0x7fc00000U)
} else {
value
},
),
),
)
}
(@lib.Instruction::F32Min, Some(@lib.F32(lhs)), Some(@lib.F32(rhs))) => {
if lhs.is_nan() || rhs.is_nan() {
return Some(
@lib.Instruction::f32_const(
@lib.F32(Float::reinterpret_from_uint(0x7fc00000U)),
),
)
}
let value = if lhs == rhs {
if lhs == Float::from_double(0.0) &&
(
(lhs.reinterpret_as_uint() | rhs.reinterpret_as_uint()) &
0x80000000U
) !=
0U {
Float::reinterpret_from_uint(0x80000000U)
} else {
lhs
}
} else if lhs < rhs {
lhs
} else {
rhs
}
return Some(@lib.Instruction::f32_const(@lib.F32(value)))
}
(@lib.Instruction::F32Max, Some(@lib.F32(lhs)), Some(@lib.F32(rhs))) => {
if lhs.is_nan() || rhs.is_nan() {
return Some(
@lib.Instruction::f32_const(
@lib.F32(Float::reinterpret_from_uint(0x7fc00000U)),
),
)
}
let value = if lhs == rhs {
if lhs == Float::from_double(0.0) {
if (
lhs.reinterpret_as_uint() &
rhs.reinterpret_as_uint() &
0x80000000U
) !=
0U {
Float::reinterpret_from_uint(0x80000000U)
} else {
Float::reinterpret_from_uint(0U)
}
} else {
lhs
}
} else if lhs > rhs {
lhs
} else {
rhs
}
return Some(@lib.Instruction::f32_const(@lib.F32(value)))
}
(@lib.Instruction::F32Copysign, Some(@lib.F32(lhs)), Some(@lib.F32(rhs))) =>
return Some(
@lib.Instruction::f32_const(
@lib.F32(
Float::reinterpret_from_uint(
(lhs.reinterpret_as_uint() & 0x7fffffffU) |
(rhs.reinterpret_as_uint() & 0x80000000U),
),
),
),
)
(@lib.Instruction::F32Eq, Some(@lib.F32(lhs)), Some(@lib.F32(rhs))) =>
return Some(precompute_raw_bool(lhs == rhs))
(@lib.Instruction::F32Ne, Some(@lib.F32(lhs)), Some(@lib.F32(rhs))) =>
return Some(precompute_raw_bool(lhs != rhs))
(@lib.Instruction::F32Lt, Some(@lib.F32(lhs)), Some(@lib.F32(rhs))) =>
return Some(precompute_raw_bool(lhs < rhs))
(@lib.Instruction::F32Gt, Some(@lib.F32(lhs)), Some(@lib.F32(rhs))) =>
return Some(precompute_raw_bool(lhs > rhs))
(@lib.Instruction::F32Le, Some(@lib.F32(lhs)), Some(@lib.F32(rhs))) =>
return Some(precompute_raw_bool(lhs <= rhs))
(@lib.Instruction::F32Ge, Some(@lib.F32(lhs)), Some(@lib.F32(rhs))) =>
return Some(precompute_raw_bool(lhs >= rhs))
_ => ()
}
match
(
instr,
precompute_raw_f64_const(lhs_instr),
precompute_raw_f64_const(rhs_instr),
) {
(@lib.Instruction::F64Add, Some(@lib.F64(lhs)), Some(@lib.F64(rhs))) => {
let value = lhs + rhs
Some(
@lib.Instruction::f64_const(
@lib.F64(
if value.is_nan() {
0x7ff8000000000000UL.reinterpret_as_double()
} else {
value
},
),
),
)
}
(@lib.Instruction::F64Sub, Some(@lib.F64(lhs)), Some(@lib.F64(rhs))) => {
let value = lhs - rhs
Some(
@lib.Instruction::f64_const(
@lib.F64(
if value.is_nan() {
0x7ff8000000000000UL.reinterpret_as_double()
} else {
value
},
),
),
)
}
(@lib.Instruction::F64Mul, Some(@lib.F64(lhs)), Some(@lib.F64(rhs))) => {
let value = lhs * rhs
Some(
@lib.Instruction::f64_const(
@lib.F64(
if value.is_nan() {
0x7ff8000000000000UL.reinterpret_as_double()
} else {
value
},
),
),
)
}
(@lib.Instruction::F64Div, Some(@lib.F64(lhs)), Some(@lib.F64(rhs))) => {
let value = lhs / rhs
Some(
@lib.Instruction::f64_const(
@lib.F64(
if value.is_nan() {
0x7ff8000000000000UL.reinterpret_as_double()
} else {
value
},
),
),
)
}
(@lib.Instruction::F64Min, Some(@lib.F64(lhs)), Some(@lib.F64(rhs))) => {
if lhs.is_nan() || rhs.is_nan() {
return Some(
@lib.Instruction::f64_const(
@lib.F64(0x7ff8000000000000UL.reinterpret_as_double()),
),
)
}
let value = if lhs == rhs {
if lhs == 0.0 &&
(
(lhs.reinterpret_as_uint64() | rhs.reinterpret_as_uint64()) &
0x8000000000000000UL
) !=
0UL {
0x8000000000000000UL.reinterpret_as_double()
} else {
lhs
}
} else if lhs < rhs {
lhs
} else {
rhs
}
Some(@lib.Instruction::f64_const(@lib.F64(value)))
}
(@lib.Instruction::F64Max, Some(@lib.F64(lhs)), Some(@lib.F64(rhs))) => {
if lhs.is_nan() || rhs.is_nan() {
return Some(
@lib.Instruction::f64_const(
@lib.F64(0x7ff8000000000000UL.reinterpret_as_double()),
),
)
}
let value = if lhs == rhs {
if lhs == 0.0 {
if (
lhs.reinterpret_as_uint64() &
rhs.reinterpret_as_uint64() &
0x8000000000000000UL
) !=
0UL {
0x8000000000000000UL.reinterpret_as_double()
} else {
0.0
}
} else {
lhs
}
} else if lhs > rhs {
lhs
} else {
rhs
}
Some(@lib.Instruction::f64_const(@lib.F64(value)))
}
(@lib.Instruction::F64Copysign, Some(@lib.F64(lhs)), Some(@lib.F64(rhs))) =>
Some(
@lib.Instruction::f64_const(
@lib.F64(
((lhs.reinterpret_as_uint64() & 0x7fffffffffffffffUL) |
(rhs.reinterpret_as_uint64() & 0x8000000000000000UL)).reinterpret_as_double(),
),
),
)
(@lib.Instruction::F64Eq, Some(@lib.F64(lhs)), Some(@lib.F64(rhs))) =>
Some(precompute_raw_bool(lhs == rhs))
(@lib.Instruction::F64Ne, Some(@lib.F64(lhs)), Some(@lib.F64(rhs))) =>
Some(precompute_raw_bool(lhs != rhs))
(@lib.Instruction::F64Lt, Some(@lib.F64(lhs)), Some(@lib.F64(rhs))) =>
Some(precompute_raw_bool(lhs < rhs))
(@lib.Instruction::F64Gt, Some(@lib.F64(lhs)), Some(@lib.F64(rhs))) =>
Some(precompute_raw_bool(lhs > rhs))
(@lib.Instruction::F64Le, Some(@lib.F64(lhs)), Some(@lib.F64(rhs))) =>
Some(precompute_raw_bool(lhs <= rhs))
(@lib.Instruction::F64Ge, Some(@lib.F64(lhs)), Some(@lib.F64(rhs))) =>
Some(precompute_raw_bool(lhs >= rhs))
_ => None
}
}
///|
fn precompute_raw_instr_is_flat_discardable_value(
instr : @lib.Instruction,
) -> Bool {
match instr {
@lib.I32Const(_)
| @lib.I64Const(_)
| @lib.F32Const(_)
| @lib.F64Const(_)
| @lib.LocalGet(_)
| @lib.GlobalGet(_)
| @lib.RefNull(_)
| @lib.RefFunc(_) => true
_ => false
}
}
///|
fn precompute_raw_instr_is_nontrapping_unary_op(
instr : @lib.Instruction,
) -> Bool {
match instr {
@lib.Instruction::I32Eqz
| @lib.Instruction::I64Eqz
| @lib.Instruction::I32Clz
| @lib.Instruction::I32Ctz
| @lib.Instruction::I32Popcnt
| @lib.Instruction::I64Clz
| @lib.Instruction::I64Ctz
| @lib.Instruction::I64Popcnt
| @lib.Instruction::F32Abs
| @lib.Instruction::F32Neg
| @lib.Instruction::F32Ceil
| @lib.Instruction::F32Floor
| @lib.Instruction::F32Trunc
| @lib.Instruction::F32Nearest
| @lib.Instruction::F32Sqrt
| @lib.Instruction::F64Abs
| @lib.Instruction::F64Neg
| @lib.Instruction::F64Ceil
| @lib.Instruction::F64Floor
| @lib.Instruction::F64Trunc
| @lib.Instruction::F64Nearest
| @lib.Instruction::F64Sqrt
| @lib.Instruction::I32WrapI64
| @lib.Instruction::I64ExtendI32S
| @lib.Instruction::I64ExtendI32U
| @lib.Instruction::F32ConvertI32S
| @lib.Instruction::F32ConvertI32U
| @lib.Instruction::F32ConvertI64S
| @lib.Instruction::F32ConvertI64U
| @lib.Instruction::F64ConvertI32S
| @lib.Instruction::F64ConvertI32U
| @lib.Instruction::F64ConvertI64S
| @lib.Instruction::F64ConvertI64U
| @lib.Instruction::F32DemoteF64
| @lib.Instruction::F64PromoteF32
| @lib.Instruction::I32ReinterpretF32
| @lib.Instruction::I64ReinterpretF64
| @lib.Instruction::F32ReinterpretI32
| @lib.Instruction::F64ReinterpretI64
| @lib.Instruction::I32Extend8S
| @lib.Instruction::I32Extend16S
| @lib.Instruction::I64Extend8S
| @lib.Instruction::I64Extend16S
| @lib.Instruction::I64Extend32S
| @lib.Instruction::I32TruncSatF32S
| @lib.Instruction::I32TruncSatF32U
| @lib.Instruction::I32TruncSatF64S
| @lib.Instruction::I32TruncSatF64U
| @lib.Instruction::I64TruncSatF32S
| @lib.Instruction::I64TruncSatF32U
| @lib.Instruction::I64TruncSatF64S
| @lib.Instruction::I64TruncSatF64U
| @lib.Instruction::RefIsNull
| @lib.Instruction::AnyConvertExtern
| @lib.Instruction::ExternConvertAny
| @lib.Instruction::RefI31 => true
_ => false
}
}
///|
fn precompute_raw_instr_is_nontrapping_binary_op(
instr : @lib.Instruction,
) -> Bool {
match instr {
@lib.Instruction::I32Add
| @lib.Instruction::I32Sub
| @lib.Instruction::I32Mul
| @lib.Instruction::I32And
| @lib.Instruction::I32Or
| @lib.Instruction::I32Xor
| @lib.Instruction::I32Shl
| @lib.Instruction::I32ShrU
| @lib.Instruction::I32ShrS
| @lib.Instruction::I32Eq
| @lib.Instruction::I32Ne
| @lib.Instruction::I32LtS
| @lib.Instruction::I32LtU
| @lib.Instruction::I32GtS
| @lib.Instruction::I32GtU
| @lib.Instruction::I32LeS
| @lib.Instruction::I32LeU
| @lib.Instruction::I32GeS
| @lib.Instruction::I32GeU
| @lib.Instruction::I64Add
| @lib.Instruction::I64Sub
| @lib.Instruction::I64Mul
| @lib.Instruction::I64And
| @lib.Instruction::I64Or
| @lib.Instruction::I64Xor
| @lib.Instruction::I64Shl
| @lib.Instruction::I64ShrU
| @lib.Instruction::I64ShrS
| @lib.Instruction::I64Eq
| @lib.Instruction::I64Ne
| @lib.Instruction::I64LtS
| @lib.Instruction::I64LtU
| @lib.Instruction::I64GtS
| @lib.Instruction::I64GtU
| @lib.Instruction::I64LeS
| @lib.Instruction::I64LeU
| @lib.Instruction::I64GeS
| @lib.Instruction::I64GeU
| @lib.Instruction::I32Rotl
| @lib.Instruction::I32Rotr
| @lib.Instruction::I64Rotl
| @lib.Instruction::I64Rotr
| @lib.Instruction::F32Add
| @lib.Instruction::F32Sub
| @lib.Instruction::F32Mul
| @lib.Instruction::F32Div
| @lib.Instruction::F32Min
| @lib.Instruction::F32Max
| @lib.Instruction::F32Copysign
| @lib.Instruction::F32Eq
| @lib.Instruction::F32Ne
| @lib.Instruction::F32Lt
| @lib.Instruction::F32Gt
| @lib.Instruction::F32Le
| @lib.Instruction::F32Ge
| @lib.Instruction::F64Add
| @lib.Instruction::F64Sub
| @lib.Instruction::F64Mul
| @lib.Instruction::F64Div
| @lib.Instruction::F64Min
| @lib.Instruction::F64Max
| @lib.Instruction::F64Copysign
| @lib.Instruction::F64Eq
| @lib.Instruction::F64Ne
| @lib.Instruction::F64Lt
| @lib.Instruction::F64Gt
| @lib.Instruction::F64Le
| @lib.Instruction::F64Ge
| @lib.Instruction::RefEq => true
_ => false
}
}
///|
fn precompute_raw_instrs_have_label_relative_branch(
instrs : Array[@lib.Instruction],
) -> Bool {
for instr in instrs {
match instr {
@lib.Br(_)
| @lib.BrIf(_)
| @lib.BrTable(_, _)
| @lib.BrOnNull(_)
| @lib.BrOnNonNull(_)
| @lib.BrOnCast(_, _, _, _)
| @lib.BrOnCastFail(_, _, _, _)
| @lib.BrOnCastDescEq(_, _, _)
| @lib.BrOnCastDescEqFail(_, _, _) => return true
@lib.Block(_, @lib.Expr(body)) | @lib.Loop(_, @lib.Expr(body)) =>
if precompute_raw_instrs_have_label_relative_branch(body) {
return true
}
@lib.If(_, then_body, else_body) => {
if precompute_raw_instrs_have_label_relative_branch(then_body) {
return true
}
match else_body {
Some(body) =>
if precompute_raw_instrs_have_label_relative_branch(body) {
return true
}
None => ()
}
}
@lib.TryTable(_, catches, @lib.Expr(body)) =>
if !catches.is_empty() ||
precompute_raw_instrs_have_label_relative_branch(body) {
return true
}
@lib.Try(_, _, _, _) => return true
_ => ()
}
}
false
}
///|
fn precompute_raw_label_matches_depth(
label_idx : @lib.LabelIdx,
depth : Int32,
) -> Bool {
let @lib.LabelIdx(raw) = label_idx
raw.reinterpret_as_int() == depth
}
///|
fn precompute_raw_instr_has_branch_to_depth(
instr : @lib.Instruction,
depth : Int32,
) -> Bool {
match instr {
@lib.Br(label_idx)
| @lib.BrIf(label_idx)
| @lib.BrOnNull(label_idx)
| @lib.BrOnNonNull(label_idx)
| @lib.BrOnCast(label_idx, _, _, _)
| @lib.BrOnCastFail(label_idx, _, _, _)
| @lib.BrOnCastDescEq(label_idx, _, _)
| @lib.BrOnCastDescEqFail(label_idx, _, _) =>
precompute_raw_label_matches_depth(label_idx, depth)
@lib.BrTable(labels, fallback) => {
for label_idx in labels {
if precompute_raw_label_matches_depth(label_idx, depth) {
return true
}
}
precompute_raw_label_matches_depth(fallback, depth)
}
@lib.Block(_, @lib.Expr(body)) | @lib.Loop(_, @lib.Expr(body)) =>
precompute_raw_instrs_have_branch_to_depth(body, depth + 1)
@lib.If(_, then_body, else_body) => {
if precompute_raw_instrs_have_branch_to_depth(then_body, depth + 1) {
return true
}
match else_body {
Some(body) =>
precompute_raw_instrs_have_branch_to_depth(body, depth + 1)
None => false
}
}
@lib.TryTable(_, _, @lib.Expr(_)) => true
_ => false
}
}
///|
fn precompute_raw_instrs_have_branch_to_depth(
instrs : Array[@lib.Instruction],
depth : Int32,
) -> Bool {
for instr in instrs {
if precompute_raw_instr_has_branch_to_depth(instr, depth) {
return true
}
}
false
}
///|
fn precompute_raw_instrs_have_branch_to_current_label(
instrs : Array[@lib.Instruction],
) -> Bool {
precompute_raw_instrs_have_branch_to_depth(instrs, 0)
}
///|
fn precompute_raw_try_fold_const_if_tail(
out : Array[@lib.Instruction],
module_ctx : @ir.HotModuleContext?,
label_arities : Array[Int32?],
) -> Bool {
if out.length() < 2 {
return false
}
let cond = match precompute_raw_i32_const(out[out.length() - 2]) {
Some(value) => value
None => return false
}
let (block_type, selected) = match out[out.length() - 1] {
@lib.If(block_type, then_body, else_body) =>
(
block_type,
if cond == @lib.I32(0) {
match else_body {
Some(body) => body
None => []
}
} else {
then_body
},
)
_ => return false
}
ignore(out.pop())
ignore(out.pop())
if precompute_raw_instrs_have_label_relative_branch(selected) {
match precompute_raw_block_terminal_branch_depth(selected) {
Some(depth) => {
if !precompute_raw_block_can_flatten_terminal(block_type, module_ctx) {
out.push(
@lib.Instruction::block(block_type, @lib.Expr::new(selected.copy())),
)
return true
}
let selected_label_arities = precompute_raw_append_label_arity(
label_arities,
precompute_raw_block_label_arity(block_type, module_ctx),
)
let payload_arity = match
precompute_raw_branch_target_arity(selected_label_arities, depth) {
Some(arity) => arity
None => {
out.push(
@lib.Instruction::block(
block_type,
@lib.Expr::new(selected.copy()),
),
)
return true
}
}
let prefix = match
precompute_raw_terminal_branch_prefix(selected, payload_arity) {
Some(prefix) => prefix
None => {
out.push(
@lib.Instruction::block(
block_type,
@lib.Expr::new(selected.copy()),
),
)
return true
}
}
for instr in prefix {
out.push(instr)
}
if depth > 0U {
out.push(@lib.Instruction::br(@lib.LabelIdx::new(depth - 1U)))
}
}
None =>
out.push(
@lib.Instruction::block(block_type, @lib.Expr::new(selected.copy())),
)
}
} else {
for instr in selected {
out.push(instr)
}
}
true
}
///|
fn precompute_raw_try_void_dropped_result_block(
instr : @lib.Instruction,
) -> @lib.Instruction? {
let body = match instr {
@lib.Block(@lib.ValTypeBlockType(_), @lib.Expr(body)) => body
_ => return None
}
if precompute_raw_instrs_have_branch_to_current_label(body) {
return None
}
let void_body = body.copy()
void_body.push(@lib.Instruction::drop())
let mut changed = false
while precompute_raw_fold_output_tail(void_body) {
changed = true
}
if !changed || precompute_raw_instrs_have_drop_needing_hot(void_body) {
return None
}
Some(
@lib.Instruction::block(@lib.BlockType::void_(), @lib.Expr::new(void_body)),
)
}
///|
fn precompute_raw_loop_is_unconditional_self_branch(
instr : @lib.Instruction,
) -> Bool {
match instr {
@lib.Loop(block_type, @lib.Expr([@lib.Br(@lib.LabelIdx(0U))])) =>
block_type == @lib.BlockType::void_()
_ => false
}
}
///|
fn precompute_raw_try_mark_value_tail_after_infinite_loop(
out : Array[@lib.Instruction],
visits~ : Array[Int32]? = None,
) -> Bool {
if out.length() < 2 {
return false
}
let mut idx = out.length() - 1
let mut has_value_tail = false
// Only the first non-value/non-nop instruction can be the loop: any
// earlier loop would have this instruction in its unsupported suffix.
while idx >= 0 {
match visits {
Some(count) => count[0] += 1
None => ()
}
match out[idx] {
@lib.Nop => ()
instr if precompute_raw_instr_is_flat_discardable_value(instr) =>
has_value_tail = true
instr => {
if !has_value_tail ||
!precompute_raw_loop_is_unconditional_self_branch(instr) {
return false
}
while out.length() > idx + 1 {
ignore(out.pop())
}
out.push(@lib.Instruction::unreachable_())
return true
}
}
idx -= 1
}
false
}
///|
fn precompute_raw_instrs_have_partial_heap_select(
instrs : Array[@lib.Instruction],
) -> Bool {
let mut saw_select = false
let mut saw_heap_parent = false
for instr in instrs {
match instr {
@lib.Select(_) => saw_select = true
@lib.StructGet(_, _)
| @lib.StructGetS(_, _)
| @lib.StructGetU(_, _)
| @lib.RefIsNull
| @lib.RefEq => saw_heap_parent = true
_ => ()
}
}
saw_select && saw_heap_parent
}
///|
fn precompute_raw_try_partial_select_condition_tail(
out : Array[@lib.Instruction],
) -> Bool {
if out.length() < 7 {
return false
}
let base = out.length() - 7
let outer_select = match out[base + 6] {
@lib.Select(_) => out[base + 6]
_ => return false
}
match out[base + 5] {
@lib.Select(_) => ()
_ => return false
}
let condition_true = match out[base + 2] {
@lib.I32Const(@lib.I32(value)) => value != 0
_ => return false
}
let condition_false = match out[base + 3] {
@lib.I32Const(@lib.I32(value)) => value != 0
_ => return false
}
if !precompute_raw_instr_is_flat_discardable_value(out[base + 4]) {
return false
}
let outer_true = out[base]
let outer_false = out[base + 1]
if !precompute_raw_is_literal_const(outer_true) ||
!precompute_raw_is_literal_const(outer_false) {
return false
}
let next_true = if condition_true { outer_true } else { outer_false }
let next_false = if condition_false { outer_true } else { outer_false }
let condition = out[base + 4]
while out.length() > base {
ignore(out.pop())
}
out.push(next_true)
out.push(next_false)
out.push(condition)
out.push(outer_select)
true
}
///|
fn precompute_raw_try_partial_select_unary_tail(
out : Array[@lib.Instruction],
) -> Bool {
if out.length() < 5 {
return false
}
let base = out.length() - 5
let select_instr = match out[base + 3] {
@lib.Select(_) => out[base + 3]
_ => return false
}
if !precompute_raw_instr_is_flat_discardable_value(out[base + 2]) {
return false
}
let folded_true = match
precompute_raw_try_fold_unary(out[base], out[base + 4]) {
Some(value) => value
None => return false
}
let folded_false = match
precompute_raw_try_fold_unary(out[base + 1], out[base + 4]) {
Some(value) => value
None => return false
}
let condition = out[base + 2]
while out.length() > base {
ignore(out.pop())
}
out.push(folded_true)
out.push(folded_false)
out.push(condition)
out.push(select_instr)
true
}
///|
fn precompute_raw_try_partial_select_tail(
out : Array[@lib.Instruction],
) -> Bool {
if out.length() < 6 {
return false
}
let base = out.length() - 6
let op = out[base + 5]
let select_lhs = match out[base + 3] {
@lib.Select(_) => true
_ => false
}
let select_rhs = match out[base + 4] {
@lib.Select(_) => true
_ => false
}
let (true_value, false_value, condition, select_instr, other, select_is_lhs) = if select_lhs {
(
out[base],
out[base + 1],
out[base + 2],
out[base + 3],
out[base + 4],
true,
)
} else if select_rhs {
(
out[base + 1],
out[base + 2],
out[base + 3],
out[base + 4],
out[base],
false,
)
} else {
return false
}
if !precompute_raw_instr_is_flat_discardable_value(condition) {
return false
}
let folded_true = match
(if select_is_lhs {
precompute_raw_try_fold_binary(true_value, other, op)
} else {
precompute_raw_try_fold_binary(other, true_value, op)
}) {
Some(value) => value
None => return false
}
let folded_false = match
(if select_is_lhs {
precompute_raw_try_fold_binary(false_value, other, op)
} else {
precompute_raw_try_fold_binary(other, false_value, op)
}) {
Some(value) => value
None => return false
}
while out.length() > base {
ignore(out.pop())
}
out.push(folded_true)
out.push(folded_false)
out.push(condition)
out.push(select_instr)
true
}
///|
fn precompute_raw_is_literal_const(instr : @lib.Instruction) -> Bool {
match instr {
@lib.I32Const(_)
| @lib.I64Const(_)
| @lib.F32Const(_)
| @lib.F64Const(_)
| @lib.RefNull(_)
| @lib.StringConst(_) => true
_ => false
}
}
///|
fn precompute_raw_is_single_result_branch_literal(
instr : @lib.Instruction,
) -> Bool {
match instr {
@lib.I32Const(_)
| @lib.I64Const(_)
| @lib.F32Const(_)
| @lib.F64Const(_)
| @lib.V128Const(_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _)
| @lib.RefNull(_)
| @lib.StringConst(_) => true
_ => false
}
}
///|
fn precompute_raw_fold_same_target_br_table_tail(
out : Array[@lib.Instruction],
label_arities : Array[Int32?],
) -> Bool {
// Keep the payload in place and erase only an effect-free, nontrapping
// literal selector. Requiring a one-result target excludes type-indexed
// multivalue stacks, while equal table targets make the selected index
// irrelevant. The ordinary terminal-block flattener preserves any ordered
// effect or possible trap before this three-instruction suffix.
if out.length() < 3 ||
!precompute_raw_is_single_result_branch_literal(out[out.length() - 3]) {
return false
}
match out[out.length() - 2] {
@lib.I32Const(_) => ()
_ => return false
}
let (targets, fallback) = match out[out.length() - 1] {
@lib.BrTable(targets, fallback) => (targets, fallback)
_ => return false
}
let @lib.LabelIdx(fallback_depth) = fallback
if precompute_raw_branch_target_arity(label_arities, fallback_depth) !=
Some(1) {
return false
}
for target in targets {
if target != fallback {
return false
}
}
ignore(out.pop())
ignore(out.pop())
out.push(@lib.Instruction::br(fallback))
true
}
///|
fn precompute_raw_effectful_block_const(
instr : @lib.Instruction,
) -> @lib.Instruction? {
let body = match instr {
@lib.Block(_, @lib.Expr(body)) => body
_ => return None
}
if body.length() < 3 ||
!precompute_raw_is_literal_const(body[body.length() - 1]) {
return None
}
let mut slot = 0
while slot + 1 < body.length() {
if !precompute_raw_is_literal_const(body[slot]) {
return None
}
match body[slot + 1] {
@lib.LocalSet(_) | @lib.GlobalSet(_) => ()
_ => return None
}
slot = slot + 2
}
if slot != body.length() - 1 {
return None
}
Some(body[body.length() - 1])
}
///|
fn precompute_string_bytes_to_utf16(bytes : Bytes) -> Array[Int32]? {
let units : Array[Int32] = []
let mut i = 0
while i < bytes.length() {
let first = bytes[i].to_int()
if first <= 0x7f {
units.push(first)
i = i + 1
continue
}
if first >= 0xc2 && first <= 0xdf {
if i + 1 >= bytes.length() {
return None
}
let second = bytes[i + 1].to_int()
if second < 0x80 || second > 0xbf {
return None
}
units.push(((first & 0x1f) << 6) | (second & 0x3f))
i = i + 2
continue
}
if first >= 0xe0 && first <= 0xef {
if i + 2 >= bytes.length() {
return None
}
let second = bytes[i + 1].to_int()
let third = bytes[i + 2].to_int()
if second < 0x80 ||
second > 0xbf ||
third < 0x80 ||
third > 0xbf ||
(first == 0xe0 && second < 0xa0) {
return None
}
units.push(
((first & 0x0f) << 12) | ((second & 0x3f) << 6) | (third & 0x3f),
)
i = i + 3
continue
}
if first >= 0xf0 && first <= 0xf4 {
if i + 3 >= bytes.length() {
return None
}
let second = bytes[i + 1].to_int()
let third = bytes[i + 2].to_int()
let fourth = bytes[i + 3].to_int()
if second < 0x80 ||
second > 0xbf ||
third < 0x80 ||
third > 0xbf ||
fourth < 0x80 ||
fourth > 0xbf ||
(first == 0xf0 && second < 0x90) ||
(first == 0xf4 && second > 0x8f) {
return None
}
let scalar = ((first & 0x07) << 18) |
((second & 0x3f) << 12) |
((third & 0x3f) << 6) |
(fourth & 0x3f)
let offset = scalar - 0x10000
units.push(0xd800 | (offset >> 10))
units.push(0xdc00 | (offset & 0x3ff))
i = i + 4
continue
}
return None
}
Some(units)
}
///|
fn precompute_utf16_units_to_valid_utf8(
units : Array[Int32],
start : Int32,
length : Int32,
) -> Bytes? {
if start < 0 ||
length < 0 ||
start > units.length() ||
length > units.length() - start {
return None
}
let bytes = @buffer.new(size_hint=length * 3)
let end = start + length
let mut slot = start
while slot < end {
let unit = units[slot] & 0xffff
if unit >= 0xd800 && unit <= 0xdbff {
if slot + 1 >= end {
return None
}
let low = units[slot + 1] & 0xffff
if low < 0xdc00 || low > 0xdfff {
return None
}
let scalar = 0x10000 + ((unit - 0xd800) << 10) + (low - 0xdc00)
precompute_write_utf8_scalar(bytes, scalar)
slot = slot + 2
} else if unit >= 0xdc00 && unit <= 0xdfff {
return None
} else {
precompute_write_utf8_scalar(bytes, unit)
slot = slot + 1
}
}
Some(bytes.to_bytes())
}
///|
fn precompute_write_utf8_scalar(buf : @buffer.Buffer, scalar : Int32) -> Unit {
if scalar <= 0x7f {
buf.write_byte(scalar.to_byte())
} else if scalar <= 0x7ff {
buf.write_byte((0xc0 | (scalar >> 6)).to_byte())
buf.write_byte((0x80 | (scalar & 0x3f)).to_byte())
} else if scalar <= 0xffff {
buf.write_byte((0xe0 | (scalar >> 12)).to_byte())
buf.write_byte((0x80 | ((scalar >> 6) & 0x3f)).to_byte())
buf.write_byte((0x80 | (scalar & 0x3f)).to_byte())
} else {
buf.write_byte((0xf0 | (scalar >> 18)).to_byte())
buf.write_byte((0x80 | ((scalar >> 12) & 0x3f)).to_byte())
buf.write_byte((0x80 | ((scalar >> 6) & 0x3f)).to_byte())
buf.write_byte((0x80 | (scalar & 0x3f)).to_byte())
}
}
///|
fn precompute_raw_try_fold_string_new_wtf16_tail(
out : Array[@lib.Instruction],
) -> Bool {
if out.length() < 4 ||
out[out.length() - 1] != @lib.Instruction::string_new_wtf16_array() {
return false
}
let (start, end) = match (out[out.length() - 3], out[out.length() - 2]) {
(@lib.I32Const(@lib.I32(start)), @lib.I32Const(@lib.I32(end))) =>
(start, end)
_ => return false
}
if start < 0 || end < start {
return false
}
let count = match out[out.length() - 4] {
@lib.ArrayNewFixed(_, @lib.U32(count)) => count.reinterpret_as_int()
_ => return false
}
let value_start = out.length() - 4 - count
if value_start < 0 || start > count || end > count {
return false
}
let units : Array[Int32] = []
for slot = 0; slot < count; slot = slot + 1 {
match out[value_start + slot] {
@lib.I32Const(@lib.I32(value)) => units.push(value & 0xffff)
_ => return false
}
}
let bytes = @buffer.new(size_hint=(end - start) * 3)
let mut slot = start
while slot < end {
let unit = units[slot]
if unit >= 0xd800 && unit <= 0xdbff {
if slot + 1 >= end {
return false
}
let low = units[slot + 1]
if low < 0xdc00 || low > 0xdfff {
return false
}
let scalar = 0x10000 + ((unit - 0xd800) << 10) + (low - 0xdc00)
precompute_write_utf8_scalar(bytes, scalar)
slot = slot + 2
} else if unit >= 0xdc00 && unit <= 0xdfff {
return false
} else {
precompute_write_utf8_scalar(bytes, unit)
slot = slot + 1
}
}
for pop_idx = 0; pop_idx < count + 4; pop_idx = pop_idx + 1 {
ignore(pop_idx)
ignore(out.pop())
}
out.push(@lib.Instruction::string_const(bytes.to_bytes()))
true
}
///|
fn precompute_raw_try_fold_string_slice_tail(
out : Array[@lib.Instruction],
) -> Bool {
if out.length() < 4 {
return false
}
let base = out.length() - 4
let (bytes, start, end) = match
(out[base], out[base + 1], out[base + 2], out[base + 3]) {
(
@lib.StringConst(bytes),
@lib.I32Const(@lib.I32(start)),
@lib.I32Const(@lib.I32(end)),
@lib.StringViewWtf16Slice,
) => (bytes, start, end)
_ => return false
}
let units = match precompute_string_bytes_to_utf16(bytes) {
Some(units) => units
None => return false
}
if start < 0 || end < start || end > units.length() {
return false
}
let sliced = match
precompute_utf16_units_to_valid_utf8(units, start, end - start) {
Some(bytes) => bytes
None => return false
}
for pop_idx = 0; pop_idx < 4; pop_idx = pop_idx + 1 {
ignore(pop_idx)
ignore(out.pop())
}
out.push(@lib.Instruction::string_const(sliced))
true
}
///|
fn precompute_raw_fold_output_tail(
out : Array[@lib.Instruction],
module_ctx~ : @ir.HotModuleContext? = None,
label_arities~ : Array[Int32?] = [],
full_tail_checks~ : Array[Int32]? = None,
loop_tail_visits~ : Array[Int32]? = None,
) -> Bool {
// The other tail folds end in an operator, control, or write. Ordinary
// reads and scalar literals can only trigger infinite-loop tail cleanup.
match out.last() {
None => return false
Some(@lib.Nop)
| Some(@lib.LocalGet(_))
| Some(@lib.GlobalGet(_))
| Some(@lib.I32Const(_))
| Some(@lib.I64Const(_))
| Some(@lib.F32Const(_))
| Some(@lib.F64Const(_)) =>
return precompute_raw_try_mark_value_tail_after_infinite_loop(
out,
visits=loop_tail_visits,
)
_ => ()
}
match full_tail_checks {
Some(checks) => checks[0] += 1
None => ()
}
if precompute_raw_fold_same_target_br_table_tail(out, label_arities) {
return true
}
if out.length() >= 4 {
let base = out.length() - 4
match (out[base], out[base + 1], out[base + 2], out[base + 3]) {
(
@lib.I32Const(@lib.I32(0))
| @lib.I64Const(@lib.I64(0L)),
@lib.I32Const(@lib.I32(0)),
@lib.I32Const(@lib.I32(0)),
@lib.MemoryInit(_, _),
) => {
for _ in 0..<4 {
ignore(out.pop())
}
return true
}
_ => ()
}
}
if out.length() >= 12 {
let base = out.length() - 12
match
(
out[base],
out[base + 1],
out[base + 2],
out[base + 3],
out[base + 4],
out[base + 5],
out[base + 6],
out[base + 7],
out[base + 8],
out[base + 9],
out[base + 10],
out[base + 11],
) {
(
first_value,
@lib.LocalSet(first_local),
second_value,
@lib.LocalSet(second_local),
@lib.LocalGet(first_compare_get),
@lib.LocalGet(second_compare_get),
compare_op,
@lib.LocalSet(mask_local),
@lib.LocalGet(first_select_get),
@lib.LocalGet(second_select_get),
@lib.LocalGet(mask_get),
@lib.V128Bitselect,
) if first_local == first_compare_get &&
first_compare_get == second_compare_get &&
first_local == first_select_get &&
second_local == second_select_get &&
mask_local == mask_get &&
first_local != second_local &&
first_local != mask_local &&
second_local != mask_local =>
match
precompute_raw_try_fold_binary(first_value, first_value, compare_op) {
Some(mask) =>
match
precompute_raw_try_fold_ternary(
first_value,
second_value,
mask,
@lib.Instruction::v128_bitselect(),
) {
Some(folded) => {
for pop_idx = 0; pop_idx < 12; pop_idx = pop_idx + 1 {
ignore(pop_idx)
ignore(out.pop())
}
out.push(first_value)
out.push(@lib.Instruction::local_set(first_local))
out.push(second_value)
out.push(@lib.Instruction::local_set(second_local))
out.push(mask)
out.push(@lib.Instruction::local_set(mask_local))
out.push(folded)
return true
}
None => ()
}
None => ()
}
_ => ()
}
}
if out.length() >= 7 {
let base = out.length() - 7
match
(
out[base],
out[base + 1],
out[base + 2],
out[base + 3],
out[base + 4],
out[base + 5],
out[base + 6],
) {
(
first_value,
@lib.LocalTee(tee_local),
second_value,
@lib.LocalGet(first_get),
@lib.LocalGet(second_get),
compare_op,
@lib.V128Bitselect,
) if tee_local == first_get && first_get == second_get =>
match
precompute_raw_try_fold_binary(first_value, first_value, compare_op) {
Some(mask) =>
match
precompute_raw_try_fold_ternary(
first_value,
second_value,
mask,
@lib.Instruction::v128_bitselect(),
) {
Some(folded) => {
for pop_idx = 0; pop_idx < 7; pop_idx = pop_idx + 1 {
ignore(pop_idx)
ignore(out.pop())
}
out.push(first_value)
out.push(@lib.Instruction::local_set(tee_local))
out.push(folded)
return true
}
None => ()
}
None => ()
}
_ => ()
}
}
if precompute_raw_try_fold_string_slice_tail(out) {
return true
}
if precompute_raw_try_fold_string_new_wtf16_tail(out) {
return true
}
if precompute_raw_try_fold_const_if_tail(out, module_ctx, label_arities) {
return true
}
if precompute_raw_try_partial_select_condition_tail(out) ||
precompute_raw_try_partial_select_unary_tail(out) ||
precompute_raw_try_partial_select_tail(out) {
return true
}
if precompute_raw_try_mark_value_tail_after_infinite_loop(
out,
visits=loop_tail_visits,
) {
return true
}
if out.length() >= 6 {
let base = out.length() - 6
match
(
out[base],
out[base + 1],
out[base + 2],
out[base + 3],
out[base + 4],
out[base + 5],
) {
(
@lib.I32Const(_) as scratch_const,
@lib.LocalSet(scratch_local),
@lib.LocalGet(accumulator_local),
@lib.LocalGet(read_scratch),
@lib.I32Add,
@lib.LocalTee(final_local),
)
| (
@lib.I32Const(_) as scratch_const,
@lib.LocalSet(scratch_local),
@lib.LocalGet(accumulator_local),
@lib.LocalGet(read_scratch),
@lib.I32Add,
@lib.LocalSet(final_local),
) if scratch_local == read_scratch &&
accumulator_local == final_local &&
scratch_local != accumulator_local => {
// Later reads can still need the scratch write. Replace only this read.
out[base + 3] = scratch_const
return true
}
_ => ()
}
}
if out.length() >= 7 {
let base = out.length() - 7
match
(
out[base],
out[base + 1],
out[base + 2],
out[base + 3],
out[base + 4],
out[base + 5],
out[base + 6],
) {
(
@lib.LocalGet(first_get),
@lib.I32Const(_) as first_const,
@lib.I32Add,
@lib.LocalTee(first_tee),
@lib.I32Const(_) as second_const,
@lib.I32Add,
@lib.LocalTee(final_local) as final_write,
)
| (
@lib.LocalGet(first_get),
@lib.I32Const(_) as first_const,
@lib.I32Add,
@lib.LocalTee(first_tee),
@lib.I32Const(_) as second_const,
@lib.I32Add,
@lib.LocalSet(final_local) as final_write,
) if first_get == first_tee && first_tee == final_local =>
match
precompute_raw_try_fold_binary(
first_const,
second_const,
@lib.Instruction::i32_add(),
) {
Some(folded_const) => {
for _ in 0..<7 {
ignore(out.pop())
}
out.push(@lib.Instruction::local_get(first_get))
out.push(folded_const)
out.push(@lib.Instruction::i32_add())
out.push(final_write)
return true
}
None => ()
}
_ => ()
}
}
if out.length() >= 8 {
let base = out.length() - 8
match
(
out[base],
out[base + 1],
out[base + 2],
out[base + 3],
out[base + 4],
out[base + 5],
out[base + 6],
out[base + 7],
) {
(
@lib.LocalGet(first_get),
@lib.I32Const(_) as first_const,
@lib.I32Add,
@lib.LocalSet(first_set),
@lib.LocalGet(second_get),
@lib.I32Const(_) as second_const,
@lib.I32Add,
@lib.LocalSet(second_set),
) if first_get == first_set &&
first_set == second_get &&
second_get == second_set =>
match
precompute_raw_try_fold_binary(
first_const,
second_const,
@lib.Instruction::i32_add(),
) {
Some(folded_const) => {
for _ in 0..<8 {
ignore(out.pop())
}
out.push(@lib.Instruction::local_get(first_get))
out.push(folded_const)
out.push(@lib.Instruction::i32_add())
out.push(@lib.Instruction::local_set(first_set))
return true
}
None => ()
}
_ => ()
}
}
if out.length() >= 5 {
match out[out.length() - 4] {
@lib.LocalTee(local_idx) =>
match
precompute_raw_try_fold_ternary(
out[out.length() - 5],
out[out.length() - 3],
out[out.length() - 2],
out[out.length() - 1],
) {
Some(folded) => {
let value = out[out.length() - 5]
for pop_idx = 0; pop_idx < 5; pop_idx = pop_idx + 1 {
ignore(pop_idx)
ignore(out.pop())
}
out.push(value)
out.push(@lib.Instruction::local_set(local_idx))
out.push(folded)
return true
}
None => ()
}
_ => ()
}
}
if out.length() >= 4 {
match
precompute_raw_try_fold_ternary(
out[out.length() - 4],
out[out.length() - 3],
out[out.length() - 2],
out[out.length() - 1],
) {
Some(folded) => {
for pop_idx = 0; pop_idx < 4; pop_idx = pop_idx + 1 {
ignore(pop_idx)
ignore(out.pop())
}
out.push(folded)
return true
}
None => ()
}
}
if out.length() >= 2 {
match (out[out.length() - 2], out[out.length() - 1]) {
(@lib.I32Const(@lib.I32(value)), @lib.BrIf(label_idx)) => {
ignore(out.pop())
ignore(out.pop())
if value != 0 {
out.push(@lib.Instruction::br(label_idx))
}
return true
}
_ => ()
}
match out[out.length() - 1] {
@lib.Drop => {
match
precompute_raw_try_void_dropped_result_block(out[out.length() - 2]) {
Some(rewritten) => {
ignore(out.pop())
ignore(out.pop())
out.push(rewritten)
return true
}
None => ()
}
if precompute_raw_instr_is_flat_discardable_value(out[out.length() - 2]) {
ignore(out.pop())
ignore(out.pop())
out.push(@lib.Instruction::nop())
return true
}
if out.length() >= 3 &&
dae_instr_is_const_safe_float_to_int_unary(
out[out.length() - 3],
out[out.length() - 2],
) {
ignore(out.pop())
ignore(out.pop())
ignore(out.pop())
out.push(@lib.Instruction::nop())
return true
}
if out.length() >= 3 &&
precompute_raw_instr_is_nontrapping_unary_op(out[out.length() - 2]) &&
precompute_raw_instr_is_flat_discardable_value(out[out.length() - 3]) {
ignore(out.pop())
ignore(out.pop())
ignore(out.pop())
out.push(@lib.Instruction::nop())
return true
}
if out.length() >= 4 &&
precompute_raw_instr_is_nontrapping_binary_op(out[out.length() - 2]) &&
precompute_raw_instr_is_flat_discardable_value(out[out.length() - 3]) &&
precompute_raw_instr_is_flat_discardable_value(out[out.length() - 4]) {
ignore(out.pop())
ignore(out.pop())
ignore(out.pop())
ignore(out.pop())
out.push(@lib.Instruction::nop())
return true
}
if out.length() >= 5 &&
precompute_raw_instr_is_flat_discardable_value(out[out.length() - 3]) &&
precompute_raw_instr_is_flat_discardable_value(out[out.length() - 4]) &&
precompute_raw_instr_is_flat_discardable_value(out[out.length() - 5]) {
match out[out.length() - 2] {
@lib.Select(_) => {
ignore(out.pop())
ignore(out.pop())
ignore(out.pop())
ignore(out.pop())
ignore(out.pop())
out.push(@lib.Instruction::nop())
return true
}
_ => ()
}
}
}
_ => ()
}
match
precompute_raw_try_fold_unary(
out[out.length() - 2],
out[out.length() - 1],
) {
Some(folded) => {
ignore(out.pop())
ignore(out.pop())
out.push(folded)
return true
}
None => ()
}
}
if out.length() >= 4 {
match (out[out.length() - 3], out[out.length() - 2]) {
(@lib.LocalTee(tee_local), @lib.LocalGet(get_local)) if tee_local ==
get_local =>
match
precompute_raw_try_fold_binary(
out[out.length() - 4],
out[out.length() - 4],
out[out.length() - 1],
) {
Some(folded) => {
let value = out[out.length() - 4]
ignore(out.pop())
ignore(out.pop())
ignore(out.pop())
ignore(out.pop())
out.push(value)
out.push(@lib.Instruction::local_set(tee_local))
out.push(folded)
return true
}
None => ()
}
_ => ()
}
}
if out.length() >= 5 {
match (out[out.length() - 4], out[out.length() - 2]) {
(@lib.LocalTee(first_local), @lib.LocalTee(second_local)) =>
match
precompute_raw_try_fold_binary(
out[out.length() - 5],
out[out.length() - 3],
out[out.length() - 1],
) {
Some(folded) => {
let first_value = out[out.length() - 5]
let second_value = out[out.length() - 3]
for i = 0; i < 5; i = i + 1 {
ignore(i)
ignore(out.pop())
}
out.push(first_value)
out.push(@lib.Instruction::local_set(first_local))
out.push(second_value)
out.push(@lib.Instruction::local_set(second_local))
out.push(folded)
return true
}
None => ()
}
_ => ()
}
}
if out.length() >= 3 {
match
(
precompute_raw_effectful_block_const(out[out.length() - 3]),
precompute_raw_effectful_block_const(out[out.length() - 2]),
) {
(Some(lhs), Some(rhs)) =>
match precompute_raw_try_fold_binary(lhs, rhs, out[out.length() - 1]) {
Some(folded) => {
let lhs_block = out[out.length() - 3]
let rhs_block = out[out.length() - 2]
ignore(out.pop())
ignore(out.pop())
ignore(out.pop())
out.push(lhs_block)
out.push(@lib.Instruction::drop())
out.push(rhs_block)
out.push(@lib.Instruction::drop())
out.push(folded)
return true
}
None => ()
}
_ => ()
}
}
if out.length() >= 3 {
match out[out.length() - 2] {
@lib.LocalTee(local_idx) =>
match
precompute_raw_try_fold_unary(
out[out.length() - 3],
out[out.length() - 1],
) {
Some(folded) => {
let value = out[out.length() - 3]
ignore(out.pop())
ignore(out.pop())
ignore(out.pop())
out.push(value)
out.push(@lib.Instruction::local_set(local_idx))
out.push(folded)
return true
}
None => ()
}
_ => ()
}
}
if out.length() >= 4 {
match out[out.length() - 3] {
@lib.LocalTee(local_idx) =>
match
precompute_raw_try_fold_binary(
out[out.length() - 4],
out[out.length() - 2],
out[out.length() - 1],
) {
Some(folded) => {
let value = out[out.length() - 4]
ignore(out.pop())
ignore(out.pop())
ignore(out.pop())
ignore(out.pop())
out.push(value)
out.push(@lib.Instruction::local_set(local_idx))
out.push(folded)
return true
}
None => ()
}
_ => ()
}
match out[out.length() - 2] {
@lib.LocalTee(local_idx) =>
match
precompute_raw_try_fold_binary(
out[out.length() - 4],
out[out.length() - 3],
out[out.length() - 1],
) {
Some(folded) => {
let value = out[out.length() - 3]
ignore(out.pop())
ignore(out.pop())
ignore(out.pop())
ignore(out.pop())
out.push(value)
out.push(@lib.Instruction::local_set(local_idx))
out.push(folded)
return true
}
None => ()
}
_ => ()
}
}
if out.length() >= 3 {
match
precompute_raw_try_fold_binary(
out[out.length() - 3],
out[out.length() - 2],
out[out.length() - 1],
) {
Some(folded) => {
ignore(out.pop())
ignore(out.pop())
ignore(out.pop())
out.push(folded)
return true
}
None => ()
}
}
false
}
///|
fn precompute_raw_fold_block_local_constants(
instrs : Array[@lib.Instruction],
) -> Bool {
let fact_locals : Array[Int32] = []
let fact_values : Array[@lib.Instruction?] = []
let mut changed = false
for idx = 0; idx < instrs.length(); idx = idx + 1 {
match instrs[idx] {
@lib.LocalSet(@lib.LocalIdx(raw_local))
| @lib.LocalTee(@lib.LocalIdx(raw_local)) => {
let local_id = raw_local.reinterpret_as_int()
let value = if idx > 0 &&
precompute_raw_is_literal_const(instrs[idx - 1]) {
Some(instrs[idx - 1])
} else {
None
}
fact_locals.push(local_id)
fact_values.push(value)
}
@lib.LocalGet(@lib.LocalIdx(raw_local)) => {
let local_id = raw_local.reinterpret_as_int()
let mut fact_idx = fact_locals.length() - 1
while fact_idx >= 0 {
if fact_locals[fact_idx] == local_id {
match fact_values[fact_idx] {
Some(value) => {
instrs[idx] = value
changed = true
}
None => ()
}
break
}
fact_idx = fact_idx - 1
}
}
@lib.Block(_, @lib.Expr(body)) => {
let mut straight_line = !precompute_raw_instrs_have_label_relative_branch(
body,
)
for nested in body {
match nested {
@lib.Block(_, _)
| @lib.Loop(_, _)
| @lib.If(_, _, _)
| @lib.Try(_, _, _, _)
| @lib.TryTable(_, _, _)
| @lib.Br(_)
| @lib.BrIf(_)
| @lib.BrTable(_, _)
| @lib.Return
| @lib.Unreachable => straight_line = false
_ => ()
}
}
if straight_line {
for nested_idx = 0
nested_idx < body.length()
nested_idx = nested_idx + 1 {
match body[nested_idx] {
@lib.LocalSet(@lib.LocalIdx(raw_local))
| @lib.LocalTee(@lib.LocalIdx(raw_local)) => {
fact_locals.push(raw_local.reinterpret_as_int())
fact_values.push(
if nested_idx > 0 &&
precompute_raw_is_literal_const(body[nested_idx - 1]) {
Some(body[nested_idx - 1])
} else {
None
},
)
}
_ => ()
}
}
} else {
fact_locals.clear()
fact_values.clear()
}
}
@lib.Loop(_, _)
| @lib.If(_, _, _)
| @lib.Try(_, _, _, _)
| @lib.TryTable(_, _, _)
| @lib.Br(_)
| @lib.BrIf(_)
| @lib.BrTable(_, _)
| @lib.Return
| @lib.Unreachable => {
fact_locals.clear()
fact_values.clear()
}
_ => ()
}
}
changed
}
///|
fn precompute_raw_void_block_is_selector_only_self_br_table(
instrs : Array[@lib.Instruction],
) -> Bool {
if instrs.length() != 2 {
return false
}
match instrs[0] {
@lib.I32Const(_) => ()
_ => return false
}
let (targets, fallback) = match instrs[1] {
@lib.BrTable(targets, fallback) => (targets, fallback)
_ => return false
}
if fallback != @lib.LabelIdx::new(0U) {
return false
}
for target in targets {
if target != @lib.LabelIdx::new(0U) {
return false
}
}
true
}
///|
fn precompute_raw_block_type_arities(
block_type : @lib.BlockType,
module_ctx : @ir.HotModuleContext?,
) -> (Int32, Int32)? {
match block_type {
@lib.VoidBlockType => Some((0, 0))
@lib.ValTypeBlockType(_) => Some((0, 1))
@lib.TypeIdxBlockType(type_idx) =>
match module_ctx {
Some(ctx) =>
match @ir.hot_module_resolve_type_func_type(ctx, type_idx) {
Some(@lib.FuncType(params, results)) =>
Some((params.length(), results.length()))
None => None
}
None => None
}
}
}
///|
fn precompute_raw_block_label_arity(
block_type : @lib.BlockType,
module_ctx : @ir.HotModuleContext?,
) -> Int32? {
match precompute_raw_block_type_arities(block_type, module_ctx) {
Some((_, results)) => Some(results)
None => None
}
}
///|
fn precompute_raw_loop_label_arity(
block_type : @lib.BlockType,
module_ctx : @ir.HotModuleContext?,
) -> Int32? {
match precompute_raw_block_type_arities(block_type, module_ctx) {
Some((params, _)) => Some(params)
None => None
}
}
///|
fn precompute_raw_block_can_flatten_terminal(
block_type : @lib.BlockType,
module_ctx : @ir.HotModuleContext?,
) -> Bool {
match precompute_raw_block_type_arities(block_type, module_ctx) {
Some((0, _)) => true
_ => false
}
}
///|
fn precompute_raw_append_label_arity(
label_arities : Array[Int32?],
arity : Int32?,
) -> Array[Int32?] {
let nested = label_arities.copy()
nested.push(arity)
nested
}
///|
fn precompute_raw_branch_target_arity(
label_arities : Array[Int32?],
depth : UInt32,
) -> Int32? {
let slot = label_arities.length() - 1 - depth.reinterpret_as_int()
if slot < 0 || slot >= label_arities.length() {
return None
}
label_arities[slot]
}
///|
fn precompute_raw_rewrite_instr(
instr : @lib.Instruction,
module_ctx : @ir.HotModuleContext?,
label_arities : Array[Int32?],
) -> (@lib.Instruction, Bool) {
match instr {
@lib.GlobalGet(global_idx) =>
match precompute_raw_global_const_instr(module_ctx, global_idx) {
Some(rewritten) => (rewritten, true)
None => (instr, false)
}
@lib.Block(block_type, @lib.Expr(body)) => {
let (next_body, nested_changed) = precompute_raw_rewrite_instrs(
body,
module_ctx,
label_arities=precompute_raw_append_label_arity(
label_arities,
precompute_raw_block_label_arity(block_type, module_ctx),
),
)
let local_changed = precompute_raw_fold_block_local_constants(next_body)
let changed = nested_changed || local_changed
match precompute_raw_block_type_arities(block_type, module_ctx) {
Some((0, 1)) if next_body.length() > 0 => {
let mut literal_only = true
for idx = 0; idx + 1 < next_body.length(); idx = idx + 1 {
if next_body[idx] != @lib.Instruction::nop() {
literal_only = false
}
}
if literal_only &&
precompute_raw_is_literal_const(next_body[next_body.length() - 1]) {
return (next_body[next_body.length() - 1], true)
}
}
_ => ()
}
match (block_type, next_body) {
(
@lib.ValTypeBlockType(_),
[
@lib.Loop(
@lib.VoidBlockType,
@lib.Expr([value, @lib.Br(@lib.LabelIdx(1U))])
),
],
) if precompute_raw_is_literal_const(value) => return (value, true)
(
@lib.ValTypeBlockType(_),
[
@lib.Loop(
@lib.VoidBlockType,
@lib.Expr([value, @lib.Br(@lib.LabelIdx(1U))])
),
@lib.Unreachable,
],
) if precompute_raw_is_literal_const(value) => return (value, true)
_ => ()
}
if block_type == @lib.BlockType::void_() {
if precompute_raw_void_block_is_selector_only_self_br_table(next_body) {
return (@lib.Instruction::nop(), true)
}
if next_body.length() == 1 {
match next_body[0] {
@lib.Br(@lib.LabelIdx(0U)) => return (@lib.Instruction::nop(), true)
_ => ()
}
}
if precompute_raw_instrs_are_all_nops(next_body) {
return (@lib.Instruction::nop(), true)
}
}
if changed {
(@lib.Instruction::block(block_type, @lib.Expr::new(next_body)), true)
} else {
(instr, false)
}
}
@lib.Loop(block_type, @lib.Expr(body)) => {
let (next_body, changed) = precompute_raw_rewrite_instrs(
body,
module_ctx,
label_arities=precompute_raw_append_label_arity(
label_arities,
precompute_raw_loop_label_arity(block_type, module_ctx),
),
)
if block_type == @lib.BlockType::void_() &&
precompute_raw_instrs_are_all_nops(next_body) {
return (@lib.Instruction::nop(), true)
}
if changed {
(@lib.Instruction::loop_(block_type, @lib.Expr::new(next_body)), true)
} else {
(instr, false)
}
}
@lib.If(block_type, then_body, else_body) => {
let arm_label_arities = precompute_raw_append_label_arity(
label_arities,
precompute_raw_block_label_arity(block_type, module_ctx),
)
let (next_then, then_changed) = precompute_raw_rewrite_instrs(
then_body,
module_ctx,
label_arities=arm_label_arities,
)
let mut changed = then_changed
let next_else = match else_body {
Some(body) => {
let (rewritten, else_changed) = precompute_raw_rewrite_instrs(
body,
module_ctx,
label_arities=arm_label_arities,
)
if else_changed {
changed = true
}
Some(rewritten)
}
None => None
}
if changed {
(@lib.Instruction::if_(block_type, next_then, next_else), true)
} else {
(instr, false)
}
}
@lib.Try(block_type, @lib.Expr(body), catches, delegate) => {
// Share one temporary label frame across the protected and catch bodies.
label_arities.push(
precompute_raw_block_label_arity(block_type, module_ctx),
)
let (next_body, body_changed) = precompute_raw_rewrite_instrs(
body,
module_ctx,
label_arities~,
)
let mut next_catches : Array[@lib.LegacyCatch]? = None
for idx = 0; idx < catches.length(); idx = idx + 1 {
let catch_ = catches[idx]
let catch_body = match catch_ {
@lib.LegacyCatch(_, @lib.Expr(body))
| @lib.LegacyCatchAll(@lib.Expr(body)) => body
}
let (next_catch_body, changed) = precompute_raw_rewrite_instrs(
catch_body,
module_ctx,
label_arities~,
)
if changed {
let out = match next_catches {
Some(out) => out
None => {
let out = catches.copy()
next_catches = Some(out)
out
}
}
out[idx] = match catch_ {
@lib.LegacyCatch(tag_idx, _) =>
@lib.LegacyCatch::tagged(tag_idx, @lib.Expr::new(next_catch_body))
@lib.LegacyCatchAll(_) =>
@lib.LegacyCatch::all(@lib.Expr::new(next_catch_body))
}
}
}
ignore(label_arities.pop())
if body_changed || next_catches is Some(_) {
(
@lib.Instruction::try_(
block_type,
@lib.Expr::new(next_body),
next_catches.unwrap_or(catches),
delegate~,
),
true,
)
} else {
(instr, false)
}
}
@lib.TryTable(block_type, catches, @lib.Expr(body)) => {
let (next_body, changed) = precompute_raw_rewrite_instrs(
body,
module_ctx,
label_arities=precompute_raw_append_label_arity(
label_arities,
precompute_raw_block_label_arity(block_type, module_ctx),
),
)
if next_body.length() == 0 {
(
@lib.Instruction::try_table(
block_type,
catches,
@lib.Expr::new([@lib.Instruction::nop()]),
),
true,
)
} else if changed {
(
@lib.Instruction::try_table(
block_type,
catches,
@lib.Expr::new(next_body),
),
true,
)
} else {
(instr, false)
}
}
_ => (instr, false)
}
}
///|
fn precompute_raw_instr_branches_out_of_enclosing_sequence(
instr : @lib.Instruction,
) -> Bool {
let body = match instr {
@lib.Block(_, @lib.Expr(body)) | @lib.Loop(_, @lib.Expr(body)) => body
_ => return false
}
match precompute_raw_block_terminal_branch_depth(body) {
Some(depth) => depth.reinterpret_as_int() > 0
None => false
}
}
///|
fn precompute_raw_instr_terminates_sequence(instr : @lib.Instruction) -> Bool {
match instr {
@lib.Br(_) | @lib.Return | @lib.Unreachable => true
_ => precompute_raw_instr_branches_out_of_enclosing_sequence(instr)
}
}
///|
fn precompute_raw_instr_is_transparent_before_unreachable(
instr : @lib.Instruction,
) -> Bool {
match instr {
@lib.Nop => true
@lib.TryTable(@lib.VoidBlockType, _, @lib.Expr(body)) =>
precompute_raw_instrs_are_all_nops(body)
_ => false
}
}
///|
fn precompute_raw_instrs_are_nops_then_unreachable(
instrs : Array[@lib.Instruction],
) -> Bool {
if instrs.length() == 0 ||
instrs[instrs.length() - 1] != @lib.Instruction::unreachable_() {
return false
}
for idx = 0; idx + 1 < instrs.length(); idx = idx + 1 {
if instrs[idx] != @lib.Instruction::nop() {
return false
}
}
true
}
///|
fn precompute_raw_terminal_prefix_stack_effect(
instr : @lib.Instruction,
) -> (Int32, Int32)? {
if precompute_raw_instr_is_flat_discardable_value(instr) {
return Some((0, 1))
}
if precompute_raw_instr_is_nontrapping_unary_op(instr) {
return Some((1, 1))
}
if precompute_raw_instr_is_nontrapping_binary_op(instr) {
return Some((2, 1))
}
match instr {
@lib.Nop => Some((0, 0))
@lib.Drop | @lib.LocalSet(_) | @lib.GlobalSet(_) => Some((1, 0))
@lib.LocalTee(_) => Some((1, 1))
@lib.Select(_) => Some((3, 1))
@lib.I32TruncF32S
| @lib.I32TruncF32U
| @lib.I32TruncF64S
| @lib.I32TruncF64U
| @lib.I64TruncF32S
| @lib.I64TruncF32U
| @lib.I64TruncF64S
| @lib.I64TruncF64U
| @lib.RefAsNonNull
| @lib.RefCast(_, _, _) => Some((1, 1))
@lib.I32DivS
| @lib.I32DivU
| @lib.I32RemS
| @lib.I32RemU
| @lib.I64DivS
| @lib.I64DivU
| @lib.I64RemS
| @lib.I64RemU
| @lib.RefEq => Some((2, 1))
@lib.Block(@lib.VoidBlockType, _) | @lib.Loop(@lib.VoidBlockType, _) =>
Some((0, 0))
@lib.Block(@lib.ValTypeBlockType(_), _)
| @lib.Loop(@lib.ValTypeBlockType(_), _) => Some((0, 1))
_ => None
}
}
///|
fn precompute_raw_terminal_branch_prefix(
instrs : Array[@lib.Instruction],
payload_arity : Int32,
) -> Array[@lib.Instruction]? {
if instrs.length() == 0 {
return None
}
let branch_idx = instrs.length() - 1
let starts : Array[Int32] = []
let ends : Array[Int32] = []
for i in 0.. effect
None => return None
}
if starts.length() < pops {
return None
}
let mut start = i
if pops > 0 {
start = starts[starts.length() - pops]
for _ in 0.. UInt32? {
if instrs.length() == 0 {
return None
}
let depth = match instrs[instrs.length() - 1] {
@lib.Br(@lib.LabelIdx(depth)) => depth
_ => return None
}
for i in 0..<(instrs.length() - 1) {
match instrs[i] {
@lib.Br(_)
| @lib.BrIf(_)
| @lib.BrTable(_, _)
| @lib.BrOnNull(_)
| @lib.BrOnNonNull(_)
| @lib.BrOnCast(_, _, _, _)
| @lib.BrOnCastFail(_, _, _, _)
| @lib.BrOnCastDescEq(_, _, _)
| @lib.BrOnCastDescEqFail(_, _, _)
| @lib.Resume(_, _)
| @lib.ResumeThrow(_, _, _)
| @lib.ResumeThrowRef(_, _)
| @lib.Try(_, _, _, _)
| @lib.Return
| @lib.Block(_, _)
| @lib.Loop(_, _)
| @lib.If(_, _, _)
| @lib.TryTable(_, _, _) => return None
_ => ()
}
}
Some(depth)
}
///|
fn precompute_raw_block_has_flat_return_flow(
instrs : Array[@lib.Instruction],
) -> Bool {
if instrs.length() == 0 ||
instrs[instrs.length() - 1] != @lib.Instruction::return_() {
return false
}
for instr in instrs {
match instr {
@lib.Br(_)
| @lib.BrIf(_)
| @lib.BrTable(_, _)
| @lib.BrOnNull(_)
| @lib.BrOnNonNull(_)
| @lib.BrOnCast(_, _, _, _)
| @lib.BrOnCastFail(_, _, _, _)
| @lib.BrOnCastDescEq(_, _, _)
| @lib.BrOnCastDescEqFail(_, _, _)
| @lib.Resume(_, _)
| @lib.ResumeThrow(_, _, _)
| @lib.ResumeThrowRef(_, _)
| @lib.Try(_, _, _, _)
| @lib.Block(_, _)
| @lib.Loop(_, _)
| @lib.If(_, _, _)
| @lib.TryTable(_, _, _) => return false
_ => ()
}
}
true
}
///|
fn precompute_raw_rewrite_instrs(
instrs : Array[@lib.Instruction],
module_ctx : @ir.HotModuleContext?,
label_arities~ : Array[Int32?] = [],
tail_scan_visits~ : Array[Int32]? = None,
) -> (Array[@lib.Instruction], Bool) {
let out : Array[@lib.Instruction] = []
let mut changed = false
for instr in instrs {
// An empty typed identity changes no stack value or effect. Remove it
// directly; routing this case through HOT or creating a nop adds work.
match instr {
@lib.Block(@lib.TypeIdxBlockType(type_idx), @lib.Expr(body)) =>
match module_ctx {
Some(ctx) =>
match @ir.hot_module_resolve_type_func_type(ctx, type_idx) {
Some(@lib.FuncType(params, results)) =>
if params.length() == 1 &&
results.length() == 1 &&
params[0] == results[0] &&
precompute_raw_instrs_are_all_nops(body) {
changed = true
continue
}
None => ()
}
None => ()
}
_ => ()
}
let (rewritten, nested_changed) = precompute_raw_rewrite_instr(
instr, module_ctx, label_arities,
)
if rewritten == @lib.Instruction::unreachable_() {
let mut trailing_idx = out.length() - 1
while trailing_idx >= 0 {
if precompute_raw_instr_is_flat_discardable_value(out[trailing_idx]) {
out[trailing_idx] = @lib.Instruction::nop()
trailing_idx = trailing_idx - 1
changed = true
} else if precompute_raw_instr_is_transparent_before_unreachable(
out[trailing_idx],
) {
trailing_idx = trailing_idx - 1
} else {
break
}
}
}
let mut terminates = false
match rewritten {
@lib.Block(_, @lib.Expr(body)) if precompute_raw_instrs_are_nops_then_unreachable(
body,
) => {
for child in body {
out.push(child)
}
terminates = true
changed = true
}
@lib.Block(block_type, @lib.Expr(body)) =>
match precompute_raw_block_terminal_branch_depth(body) {
Some(depth) => {
if !precompute_raw_block_can_flatten_terminal(
block_type, module_ctx,
) {
out.push(rewritten)
continue
}
let block_label_arities = precompute_raw_append_label_arity(
label_arities,
precompute_raw_block_label_arity(block_type, module_ctx),
)
let payload_arity = match
precompute_raw_branch_target_arity(block_label_arities, depth) {
Some(arity) => arity
None => {
out.push(rewritten)
continue
}
}
let prefix = match
precompute_raw_terminal_branch_prefix(body, payload_arity) {
Some(prefix) => prefix
None => {
out.push(rewritten)
continue
}
}
for child in prefix {
out.push(child)
if child != @lib.Instruction::nop() {
while precompute_raw_fold_output_tail(
out,
module_ctx~,
label_arities~,
loop_tail_visits=tail_scan_visits,
) {
changed = true
}
}
}
if depth > 0U {
out.push(@lib.Instruction::br(@lib.LabelIdx::new(depth - 1U)))
terminates = true
}
changed = true
}
None =>
if block_type == @lib.BlockType::void_() &&
precompute_raw_block_has_flat_return_flow(body) {
for child in body {
out.push(child)
}
changed = true
terminates = true
} else {
out.push(rewritten)
}
}
_ => out.push(rewritten)
}
if nested_changed {
changed = true
}
// A nop cannot create a new tail fold. The prior prefix was checked
// when its last non-nop instruction was appended.
if !(out.last() is Some(@lib.Nop)) {
while precompute_raw_fold_output_tail(
out,
module_ctx~,
label_arities~,
loop_tail_visits=tail_scan_visits,
) {
changed = true
}
}
if !terminates &&
out.length() > 0 &&
precompute_raw_instr_terminates_sequence(out[out.length() - 1]) {
terminates = true
if instr != instrs[instrs.length() - 1] {
changed = true
}
}
if terminates {
break
}
}
(out, changed)
}
///|
fn precompute_raw_instr_has_hot_only_candidate(
instr : @lib.Instruction,
module_ctx : @ir.HotModuleContext?,
) -> Bool {
match instr {
@lib.GlobalGet(_) => false
@lib.BrTable(_, _)
| @lib.StructGet(_, _)
| @lib.StructGetS(_, _)
| @lib.StructGetU(_, _)
| @lib.StructAtomicGet(_, _, _)
| @lib.StructAtomicGetS(_, _, _)
| @lib.StructAtomicGetU(_, _, _)
| @lib.ArrayGet(_)
| @lib.ArrayGetS(_)
| @lib.ArrayGetU(_)
| @lib.ArrayAtomicGet(_, _)
| @lib.ArrayAtomicGetS(_, _)
| @lib.ArrayAtomicGetU(_, _)
| @lib.ArrayLen
| @lib.RefEq
| @lib.RefIsNull
| @lib.RefTest(_, _, _)
| @lib.StringMeasureWtf16
| @lib.StringConcat
| @lib.StringEq
| @lib.StringAsWtf16
| @lib.StringViewWtf16GetCodeunit
| @lib.StringViewWtf16Slice
| @lib.StringNewWtf16Array => true
@lib.Nop => false
@lib.Block(_, @lib.Expr(body)) | @lib.Loop(_, @lib.Expr(body)) =>
precompute_raw_instrs_have_hot_only_candidate(body, module_ctx)
@lib.If(_, then_body, else_body) => {
if precompute_raw_instrs_have_hot_only_candidate(then_body, module_ctx) {
return true
}
match else_body {
Some(body) =>
precompute_raw_instrs_have_hot_only_candidate(body, module_ctx)
None => false
}
}
@lib.TryTable(_, _, @lib.Expr(body)) =>
precompute_raw_instrs_have_hot_only_candidate(body, module_ctx)
_ => false
}
}
///|
fn precompute_raw_instrs_have_hot_only_candidate(
instrs : Array[@lib.Instruction],
module_ctx : @ir.HotModuleContext?,
) -> Bool {
for instr in instrs {
if precompute_raw_instr_has_hot_only_candidate(instr, module_ctx) {
return true
}
}
false
}
///|
fn precompute_raw_instr_has_global_get(instr : @lib.Instruction) -> Bool {
match instr {
@lib.GlobalGet(_) => true
@lib.Block(_, @lib.Expr(body)) | @lib.Loop(_, @lib.Expr(body)) =>
precompute_raw_instrs_have_global_get(body)
@lib.If(_, then_body, else_body) => {
if precompute_raw_instrs_have_global_get(then_body) {
return true
}
match else_body {
Some(body) => precompute_raw_instrs_have_global_get(body)
None => false
}
}
@lib.TryTable(_, _, @lib.Expr(body)) =>
precompute_raw_instrs_have_global_get(body)
_ => false
}
}
///|
fn precompute_raw_instrs_have_global_get(
instrs : Array[@lib.Instruction],
) -> Bool {
for instr in instrs {
if precompute_raw_instr_has_global_get(instr) {
return true
}
}
false
}
///|
fn precompute_raw_func_has_global_get(func : @lib.Func) -> Bool {
let @lib.Func(_, @lib.Expr(instrs)) = func
precompute_raw_instrs_have_global_get(instrs)
}
///|
fn precompute_raw_instr_has_local_traffic(instr : @lib.Instruction) -> Bool {
match instr {
@lib.LocalGet(_) | @lib.LocalSet(_) | @lib.LocalTee(_) => true
@lib.Block(_, @lib.Expr(body))
| @lib.Loop(_, @lib.Expr(body))
| @lib.TryTable(_, _, @lib.Expr(body)) =>
precompute_raw_instrs_have_local_traffic(body)
@lib.Try(_, @lib.Expr(body), catches, _) => {
if precompute_raw_instrs_have_local_traffic(body) {
return true
}
for catch_ in catches {
let @lib.Expr(catch_body) = match catch_ {
@lib.LegacyCatch(_, catch_body) | @lib.LegacyCatchAll(catch_body) =>
catch_body
}
if precompute_raw_instrs_have_local_traffic(catch_body) {
return true
}
}
false
}
@lib.If(_, then_body, else_body) => {
if precompute_raw_instrs_have_local_traffic(then_body) {
return true
}
match else_body {
Some(body) => precompute_raw_instrs_have_local_traffic(body)
None => false
}
}
_ => false
}
}
///|
fn precompute_raw_instrs_have_local_traffic(
instrs : Array[@lib.Instruction],
) -> Bool {
for instr in instrs {
if precompute_raw_instr_has_local_traffic(instr) {
return true
}
}
false
}
///|
fn precompute_raw_func_has_local_traffic(func : @lib.Func) -> Bool {
let @lib.Func(_, @lib.Expr(instrs)) = func
precompute_raw_instrs_have_local_traffic(instrs)
}
///|
fn precompute_raw_instr_is_preserved_dropped_effect(
instr : @lib.Instruction,
) -> Bool {
match instr {
@lib.Call(_)
| @lib.CallIndirect(_, _)
| @lib.CallRef(_)
| @lib.LocalTee(_)
| @lib.TableGet(_)
| @lib.MemoryGrow(_)
| @lib.MemoryAtomicNotify(_)
| @lib.MemoryAtomicWait32(_)
| @lib.MemoryAtomicWait64(_)
| @lib.AtomicRmw(_, _, _)
| @lib.AtomicCmpxchg(_, _, _)
| @lib.I32DivS
| @lib.I32DivU
| @lib.I32RemS
| @lib.I32RemU
| @lib.I64DivS
| @lib.I64DivU
| @lib.I64RemS
| @lib.I64RemU
| @lib.RefAsNonNull
| @lib.RefCast(_, _, _)
| @lib.RefCastDescEq(_, _, _)
| @lib.StructGet(_, _)
| @lib.StructGetS(_, _)
| @lib.StructGetU(_, _)
| @lib.StructAtomicGet(_, _, _)
| @lib.StructAtomicGetS(_, _, _)
| @lib.StructAtomicGetU(_, _, _)
| @lib.ArrayGet(_)
| @lib.ArrayGetS(_)
| @lib.ArrayGetU(_)
| @lib.ArrayAtomicGet(_, _)
| @lib.ArrayAtomicGetS(_, _)
| @lib.ArrayAtomicGetU(_, _)
| @lib.ArrayLen
| @lib.I31GetS
| @lib.I31GetU
| @lib.I32Load(_)
| @lib.I64Load(_)
| @lib.F32Load(_)
| @lib.F64Load(_)
| @lib.I32Load8S(_)
| @lib.I32Load8U(_)
| @lib.I32Load16S(_)
| @lib.I32Load16U(_)
| @lib.I64Load8S(_)
| @lib.I64Load8U(_)
| @lib.I64Load16S(_)
| @lib.I64Load16U(_)
| @lib.I64Load32S(_)
| @lib.I64Load32U(_)
| @lib.I32AtomicLoad(_, _)
| @lib.I64AtomicLoad(_, _)
| @lib.I32AtomicLoad8U(_, _)
| @lib.I32AtomicLoad16U(_, _)
| @lib.I64AtomicLoad8U(_, _)
| @lib.I64AtomicLoad16U(_, _)
| @lib.I64AtomicLoad32U(_, _)
| @lib.V128Load(_)
| @lib.V128Load8x8S(_)
| @lib.V128Load8x8U(_)
| @lib.V128Load16x4S(_)
| @lib.V128Load16x4U(_)
| @lib.V128Load32x2S(_)
| @lib.V128Load32x2U(_)
| @lib.V128Load8Splat(_)
| @lib.V128Load16Splat(_)
| @lib.V128Load32Splat(_)
| @lib.V128Load64Splat(_) => true
_ => false
}
}
///|
fn precompute_raw_instrs_have_drop_needing_hot(
instrs : Array[@lib.Instruction],
) -> Bool {
for idx = 0; idx < instrs.length(); idx = idx + 1 {
let instr = instrs[idx]
match instr {
@lib.Drop =>
if idx == 0 ||
!precompute_raw_instr_is_preserved_dropped_effect(instrs[idx - 1]) {
return true
}
@lib.Block(_, @lib.Expr(body)) | @lib.Loop(_, @lib.Expr(body)) =>
if precompute_raw_instrs_have_drop_needing_hot(body) {
return true
}
@lib.If(_, then_body, else_body) => {
if precompute_raw_instrs_have_drop_needing_hot(then_body) {
return true
}
match else_body {
Some(body) =>
if precompute_raw_instrs_have_drop_needing_hot(body) {
return true
}
None => ()
}
}
@lib.TryTable(_, _, @lib.Expr(body)) =>
if precompute_raw_instrs_have_drop_needing_hot(body) {
return true
}
_ => ()
}
}
false
}
///|
fn precompute_raw_instrs_have_const_control_candidate(
instrs : Array[@lib.Instruction],
) -> Bool {
for idx = 0; idx < instrs.length(); idx = idx + 1 {
let instr = instrs[idx]
match instr {
@lib.If(_, then_body, else_body) => {
if idx > 0 {
match precompute_raw_i32_const(instrs[idx - 1]) {
Some(_) => return true
None => ()
}
}
if precompute_raw_instrs_have_const_control_candidate(then_body) {
return true
}
match else_body {
Some(body) =>
if precompute_raw_instrs_have_const_control_candidate(body) {
return true
}
None => ()
}
}
@lib.Select(_) =>
if idx > 0 {
match precompute_raw_i32_const(instrs[idx - 1]) {
Some(_) => return true
None => ()
}
}
@lib.Block(_, @lib.Expr(body)) | @lib.Loop(_, @lib.Expr(body)) =>
if precompute_raw_instrs_have_const_control_candidate(body) {
return true
}
@lib.TryTable(_, _, @lib.Expr(body)) =>
if precompute_raw_instrs_have_const_control_candidate(body) {
return true
}
_ => ()
}
}
false
}
///|
fn precompute_raw_instr_is_nop_tree(instr : @lib.Instruction) -> Bool {
match instr {
@lib.Nop => true
@lib.Block(@lib.VoidBlockType, @lib.Expr(body))
| @lib.Loop(@lib.VoidBlockType, @lib.Expr(body)) =>
precompute_raw_instrs_are_nop_trees(body)
_ => false
}
}
///|
fn precompute_raw_instrs_are_nop_trees(
instrs : Array[@lib.Instruction],
) -> Bool {
for instr in instrs {
if !precompute_raw_instr_is_nop_tree(instr) {
return false
}
}
true
}
///|
fn precompute_raw_instrs_are_all_nops(instrs : Array[@lib.Instruction]) -> Bool {
for instr in instrs {
match instr {
@lib.Nop => ()
_ => return false
}
}
true
}
///|
fn precompute_raw_trim_redundant_root_nops(
instrs : Array[@lib.Instruction],
) -> Bool {
if instrs.length() <= 1 {
return false
}
match instrs[instrs.length() - 1] {
@lib.I32Const(_)
| @lib.I64Const(_)
| @lib.F32Const(_)
| @lib.F64Const(_)
| @lib.LocalGet(_)
| @lib.Unreachable => return false
_ => ()
}
let mut changed = false
let mut idx = instrs.length() - 1
while idx >= 0 {
if instrs[idx] == @lib.Instruction::nop() {
ignore(instrs.remove(idx))
changed = true
}
idx = idx - 1
}
changed
}
///|
fn precompute_raw_root_nop_cleanup_candidate(
instrs : Array[@lib.Instruction],
) -> Bool {
if instrs.length() == 0 {
return true
}
if instrs.length() <= 1 {
return false
}
let mut all_nops = true
for instr in instrs {
match instr {
@lib.Nop => ()
_ => all_nops = false
}
}
if all_nops {
return true
}
match instrs[instrs.length() - 1] {
@lib.I32Const(_) | @lib.I64Const(_) | @lib.F32Const(_) | @lib.F64Const(_) =>
()
_ => return false
}
for idx = 0; idx + 1 < instrs.length(); idx = idx + 1 {
match instrs[idx] {
@lib.Nop => ()
_ => return false
}
}
true
}
///|
fn precompute_run_raw_func(
func : @lib.Func,
module_ctx : @ir.HotModuleContext?,
) -> (@lib.Func, Bool, String)? {
let @lib.Func(locals, @lib.Expr(instrs)) = func
if precompute_raw_root_nop_cleanup_candidate(instrs) {
return None
}
let (rewritten, initial_changed) = precompute_raw_rewrite_instrs(
instrs, module_ctx,
)
let changed = precompute_raw_trim_redundant_root_nops(rewritten) ||
initial_changed
if precompute_raw_instrs_are_nop_trees(rewritten) && rewritten.length() > 1 {
return Some(
(
@lib.Func::new(locals, @lib.Expr::new([@lib.Instruction::nop()])),
true,
"raw-all-nop-collapse",
),
)
}
if precompute_raw_instrs_have_hot_only_candidate(rewritten, module_ctx) {
return None
}
let retains_raw_control_flow = changed &&
precompute_raw_instrs_have_label_relative_branch(instrs)
if precompute_raw_root_nop_cleanup_candidate(rewritten) {
return None
}
if (
precompute_raw_instrs_have_drop_needing_hot(rewritten) ||
precompute_raw_instrs_have_const_control_candidate(rewritten)
) &&
!retains_raw_control_flow {
return None
}
Some(
(
if changed {
@lib.Func::new(locals, @lib.Expr::new(rewritten))
} else {
func
},
changed,
if retains_raw_control_flow {
"raw-control-flow-folds"
} else if changed {
"raw-scalar-folds"
} else {
"no-precompute-candidates"
},
),
)
}
///|
fn precompute_raw_instr_has_legacy_try(instr : @lib.Instruction) -> Bool {
match instr {
@lib.Try(_, _, _, _) => true
@lib.Block(_, @lib.Expr(body))
| @lib.Loop(_, @lib.Expr(body))
| @lib.TryTable(_, _, @lib.Expr(body)) =>
precompute_raw_instrs_have_legacy_try(body)
@lib.If(_, then_body, else_body) => {
if precompute_raw_instrs_have_legacy_try(then_body) {
return true
}
match else_body {
Some(body) => precompute_raw_instrs_have_legacy_try(body)
None => false
}
}
_ => false
}
}
///|
fn precompute_raw_instrs_have_legacy_try(
instrs : Array[@lib.Instruction],
) -> Bool {
for instr in instrs {
if precompute_raw_instr_has_legacy_try(instr) {
return true
}
}
false
}
///|
fn precompute_raw_instr_has_stack_switching(instr : @lib.Instruction) -> Bool {
match instr {
@lib.ContNew(_)
| @lib.ContBind(_, _)
| @lib.Suspend(_)
| @lib.Resume(_, _)
| @lib.ResumeThrow(_, _, _)
| @lib.ResumeThrowRef(_, _)
| @lib.StackSwitch(_, _) => true
@lib.Try(_, @lib.Expr(body), catches, _) => {
if precompute_raw_instrs_have_stack_switching(body) {
return true
}
for catch_ in catches {
let @lib.Expr(catch_body) = match catch_ {
@lib.LegacyCatch(_, catch_body) | @lib.LegacyCatchAll(catch_body) =>
catch_body
}
if precompute_raw_instrs_have_stack_switching(catch_body) {
return true
}
}
false
}
@lib.Block(_, @lib.Expr(body))
| @lib.Loop(_, @lib.Expr(body))
| @lib.TryTable(_, _, @lib.Expr(body)) =>
precompute_raw_instrs_have_stack_switching(body)
@lib.If(_, then_body, else_body) => {
if precompute_raw_instrs_have_stack_switching(then_body) {
return true
}
match else_body {
Some(body) => precompute_raw_instrs_have_stack_switching(body)
None => false
}
}
_ => false
}
}
///|
fn precompute_raw_instrs_have_stack_switching(
instrs : Array[@lib.Instruction],
) -> Bool {
for instr in instrs {
if precompute_raw_instr_has_stack_switching(instr) {
return true
}
}
false
}
///|
fn precompute_raw_instr_has_atomic_fence(instr : @lib.Instruction) -> Bool {
match instr {
@lib.AtomicFence(_) => true
@lib.Block(_, @lib.Expr(body))
| @lib.Loop(_, @lib.Expr(body))
| @lib.TryTable(_, _, @lib.Expr(body)) =>
precompute_raw_instrs_have_atomic_fence(body)
@lib.If(_, then_body, else_body) => {
if precompute_raw_instrs_have_atomic_fence(then_body) {
return true
}
match else_body {
Some(body) => precompute_raw_instrs_have_atomic_fence(body)
None => false
}
}
_ => false
}
}
///|
fn precompute_raw_instrs_have_atomic_fence(
instrs : Array[@lib.Instruction],
) -> Bool {
for instr in instrs {
if precompute_raw_instr_has_atomic_fence(instr) {
return true
}
}
false
}
///|
fn precompute_raw_instr_has_memory_grow(instr : @lib.Instruction) -> Bool {
match instr {
@lib.MemoryGrow(_) => true
@lib.Block(_, @lib.Expr(body)) | @lib.Loop(_, @lib.Expr(body)) =>
precompute_raw_instrs_have_memory_grow(body)
@lib.If(_, then_body, else_body) => {
if precompute_raw_instrs_have_memory_grow(then_body) {
return true
}
match else_body {
Some(body) => precompute_raw_instrs_have_memory_grow(body)
None => false
}
}
@lib.TryTable(_, _, @lib.Expr(body)) =>
precompute_raw_instrs_have_memory_grow(body)
_ => false
}
}
///|
fn precompute_raw_instrs_have_unreachable_suffix(
instrs : Array[@lib.Instruction],
) -> Bool {
for idx = 0; idx < instrs.length(); idx = idx + 1 {
let instr = instrs[idx]
match instr {
@lib.Unreachable if idx + 1 < instrs.length() => return true
@lib.Block(_, @lib.Expr(body)) | @lib.Loop(_, @lib.Expr(body)) =>
if precompute_raw_instrs_have_unreachable_suffix(body) {
return true
}
@lib.If(_, then_body, else_body) => {
if precompute_raw_instrs_have_unreachable_suffix(then_body) {
return true
}
match else_body {
Some(body) =>
if precompute_raw_instrs_have_unreachable_suffix(body) {
return true
}
None => ()
}
}
@lib.TryTable(_, _, @lib.Expr(body)) =>
if precompute_raw_instrs_have_unreachable_suffix(body) {
return true
}
_ => ()
}
}
false
}
///|
fn precompute_raw_instrs_have_memory_grow(
instrs : Array[@lib.Instruction],
) -> Bool {
for instr in instrs {
if precompute_raw_instr_has_memory_grow(instr) {
return true
}
}
false
}
///|
fn precompute_run_raw_around_atomic_fence(
func : @lib.Func,
module_ctx : @ir.HotModuleContext?,
) -> (@lib.Func, Bool)? {
let @lib.Func(locals, @lib.Expr(instrs)) = func
if !precompute_raw_instrs_have_atomic_fence(instrs) {
return None
}
let (rewritten, changed) = precompute_raw_rewrite_instrs(instrs, module_ctx)
Some((@lib.Func::new(locals, @lib.Expr::new(rewritten)), changed))
}
///|
fn precompute_raw_default_local_const(ty : @lib.ValType) -> @lib.Instruction? {
match ty {
@lib.NumTypeValType(@lib.I32NumType) =>
Some(@lib.Instruction::i32_const(@lib.I32(0)))
@lib.NumTypeValType(@lib.I64NumType) =>
Some(@lib.Instruction::i64_const(@lib.I64(0L)))
@lib.NumTypeValType(@lib.F32NumType) =>
Some(@lib.Instruction::f32_const(@lib.F32(0.0)))
@lib.NumTypeValType(@lib.F64NumType) =>
Some(@lib.Instruction::f64_const(@lib.F64(0.0)))
@lib.RefTypeValType(ref_type) =>
Some(@lib.Instruction::ref_null_type(ref_type))
_ => None
}
}
///|
fn precompute_raw_instr_is_foldable_binary(instr : @lib.Instruction) -> Bool {
if precompute_raw_instr_is_nontrapping_binary_op(instr) {
return true
}
match instr {
@lib.I32DivS
| @lib.I32DivU
| @lib.I32RemS
| @lib.I32RemU
| @lib.I64DivS
| @lib.I64DivU
| @lib.I64RemS
| @lib.I64RemU
| @lib.F32Add
| @lib.F32Sub
| @lib.F32Mul
| @lib.F32Div
| @lib.F32Eq
| @lib.F32Ne
| @lib.F32Lt
| @lib.F32Gt
| @lib.F32Le
| @lib.F32Ge
| @lib.F64Add
| @lib.F64Sub
| @lib.F64Mul
| @lib.F64Div
| @lib.F64Eq
| @lib.F64Ne
| @lib.F64Lt
| @lib.F64Gt
| @lib.F64Le
| @lib.F64Ge => true
_ => false
}
}
///|
fn precompute_raw_mark_written_locals(
instrs : Array[@lib.Instruction],
written : Array[Bool],
) -> Unit {
for instr in instrs {
match instr {
@lib.LocalSet(@lib.LocalIdx(raw_local))
| @lib.LocalTee(@lib.LocalIdx(raw_local)) => {
let local_id = raw_local.reinterpret_as_int()
if local_id >= 0 && local_id < written.length() {
written[local_id] = true
}
}
@lib.Block(_, @lib.Expr(body)) | @lib.Loop(_, @lib.Expr(body)) =>
precompute_raw_mark_written_locals(body, written)
@lib.If(_, then_body, else_body) => {
precompute_raw_mark_written_locals(then_body, written)
match else_body {
Some(body) => precompute_raw_mark_written_locals(body, written)
None => ()
}
}
@lib.TryTable(_, _, @lib.Expr(body)) =>
precompute_raw_mark_written_locals(body, written)
_ => ()
}
}
}
///|
fn precompute_propagate_same_literal(
left : @lib.Instruction?,
right : @lib.Instruction?,
) -> Bool {
match (left, right) {
(Some(@lib.F32Const(@lib.F32(a))), Some(@lib.F32Const(@lib.F32(b)))) =>
a.reinterpret_as_uint() == b.reinterpret_as_uint()
(Some(@lib.F64Const(@lib.F64(a))), Some(@lib.F64Const(@lib.F64(b)))) =>
a.reinterpret_as_uint64() == b.reinterpret_as_uint64()
_ => left == right
}
}
///|
fn precompute_propagate_run_raw_instrs(
instrs : Array[@lib.Instruction],
local_values : Array[@lib.Instruction?],
module_ctx : @ir.HotModuleContext?,
) -> (Array[@lib.Instruction], Array[@lib.Instruction?], Bool)? {
let stack : Array[@lib.Instruction?] = []
let rewritten : Array[@lib.Instruction] = []
let mut changed = false
for instr_idx = 0; instr_idx < instrs.length(); instr_idx = instr_idx + 1 {
let instr = instrs[instr_idx]
match instr {
@lib.I32Const(_)
| @lib.I64Const(_)
| @lib.F32Const(_)
| @lib.F64Const(_)
| @lib.RefNull(_)
| @lib.StringConst(_) => {
rewritten.push(instr)
stack.push(Some(instr))
}
@lib.LocalGet(@lib.LocalIdx(raw_local)) => {
let local_id = raw_local.reinterpret_as_int()
if local_id < 0 || local_id >= local_values.length() {
return None
}
match local_values[local_id] {
Some(value) => {
rewritten.push(value)
stack.push(Some(value))
changed = true
}
None => {
rewritten.push(instr)
stack.push(None)
}
}
}
@lib.LocalSet(@lib.LocalIdx(raw_local)) => {
let local_id = raw_local.reinterpret_as_int()
if local_id < 0 ||
local_id >= local_values.length() ||
stack.length() == 0 {
return None
}
local_values[local_id] = stack.pop().unwrap()
rewritten.push(instr)
}
@lib.LocalTee(@lib.LocalIdx(raw_local)) => {
let local_id = raw_local.reinterpret_as_int()
if local_id < 0 ||
local_id >= local_values.length() ||
stack.length() == 0 {
return None
}
local_values[local_id] = stack[stack.length() - 1]
rewritten.push(instr)
}
@lib.Loop(@lib.VoidBlockType, @lib.Expr(body)) => {
let before_values = local_values.copy()
let body_values_in = local_values.copy()
let written = Array::make(local_values.length(), false)
precompute_raw_mark_written_locals(body, written)
for local_id = 0
local_id < body_values_in.length()
local_id = local_id + 1 {
if written[local_id] {
body_values_in[local_id] = None
}
}
let (next_body, body_values, body_changed) = match
precompute_propagate_run_raw_instrs(body, body_values_in, module_ctx) {
Some(result) => result
None => return None
}
for local_id = 0
local_id < local_values.length()
local_id = local_id + 1 {
if precompute_propagate_same_literal(
before_values[local_id],
body_values[local_id],
) {
local_values[local_id] = before_values[local_id]
} else {
local_values[local_id] = None
}
}
rewritten.push(
@lib.Instruction::loop_(
@lib.BlockType::void_(),
@lib.Expr::new(next_body),
),
)
if body_changed {
changed = true
}
}
@lib.If(block_type, then_body, else_body) => {
if stack.length() == 0 {
return None
}
ignore(stack.pop())
let has_result = match block_type {
@lib.VoidBlockType => false
@lib.ValTypeBlockType(_) => true
@lib.TypeIdxBlockType(_) => return None
}
if has_result && else_body is None {
return None
}
let (next_then, then_values, then_changed) = match
precompute_propagate_run_raw_instrs(
then_body,
local_values.copy(),
module_ctx,
) {
Some(result) => result
None => return None
}
let (next_else, else_values, else_changed) = match else_body {
Some(body) =>
match
precompute_propagate_run_raw_instrs(
body,
local_values.copy(),
module_ctx,
) {
Some((next_body, values, body_changed)) =>
(Some(next_body), values, body_changed)
None => return None
}
None => (None, local_values.copy(), false)
}
for local_id = 0
local_id < local_values.length()
local_id = local_id + 1 {
if precompute_propagate_same_literal(
then_values[local_id],
else_values[local_id],
) {
local_values[local_id] = then_values[local_id]
} else {
local_values[local_id] = None
}
}
rewritten.push(@lib.Instruction::if_(block_type, next_then, next_else))
if has_result {
stack.push(None)
}
if then_changed || else_changed {
changed = true
}
}
@lib.I32Store(_)
| @lib.I64Store(_)
| @lib.F32Store(_)
| @lib.F64Store(_)
| @lib.I32Store8(_)
| @lib.I32Store16(_)
| @lib.I64Store8(_)
| @lib.I64Store16(_)
| @lib.I64Store32(_) => {
if stack.length() < 2 {
return None
}
ignore(stack.pop())
ignore(stack.pop())
rewritten.push(instr)
}
@lib.MemorySize(_) => {
rewritten.push(instr)
stack.push(None)
}
@lib.MemoryGrow(_) => {
if stack.length() == 0 {
return None
}
ignore(stack.pop())
rewritten.push(instr)
stack.push(None)
}
@lib.Drop => {
if stack.length() == 0 {
return None
}
ignore(stack.pop())
rewritten.push(instr)
}
@lib.GlobalSet(_) => {
if stack.length() == 0 {
return None
}
ignore(stack.pop())
rewritten.push(instr)
}
@lib.Call(_) => {
rewritten.push(instr)
stack.clear()
}
@lib.Br(_) => {
rewritten.push(instr)
for rest_idx = instr_idx + 1
rest_idx < instrs.length()
rest_idx = rest_idx + 1 {
rewritten.push(instrs[rest_idx])
}
return Some((rewritten, local_values, changed))
}
@lib.Nop => rewritten.push(instr)
@lib.Unreachable => {
rewritten.push(instr)
stack.clear()
}
op if precompute_raw_instr_is_nontrapping_unary_op(op) => {
if stack.length() == 0 {
return None
}
let operand = stack.pop().unwrap()
stack.push(
match operand {
Some(value) => precompute_raw_try_fold_unary(value, op)
None => None
},
)
rewritten.push(instr)
}
op if precompute_raw_instr_is_foldable_binary(op) => {
if stack.length() < 2 {
return None
}
let rhs = stack.pop().unwrap()
let lhs = stack.pop().unwrap()
stack.push(
match (lhs, rhs) {
(Some(lhs), Some(rhs)) =>
precompute_raw_try_fold_binary(lhs, rhs, op)
_ => None
},
)
rewritten.push(instr)
}
_ => {
if !changed {
return None
}
rewritten.push(instr)
for rest_idx = instr_idx + 1
rest_idx < instrs.length()
rest_idx = rest_idx + 1 {
rewritten.push(instrs[rest_idx])
}
for local_id = 0
local_id < local_values.length()
local_id = local_id + 1 {
local_values[local_id] = None
}
let (folded, folded_changed) = precompute_raw_rewrite_instrs(
rewritten, module_ctx,
)
return Some((folded, local_values, changed || folded_changed))
}
}
}
let (folded, folded_changed) = precompute_raw_rewrite_instrs(
rewritten, module_ctx,
)
Some((folded, local_values, changed || folded_changed))
}
///|
fn precompute_propagate_run_raw_unreachable_suffix(
func : @lib.Func,
params : Array[@lib.ValType],
module_ctx : @ir.HotModuleContext?,
) -> (@lib.Func, Bool)? {
let @lib.Func(_, @lib.Expr(original_instrs)) = func
if !precompute_raw_instrs_have_unreachable_suffix(original_instrs) {
return None
}
let (propagated, propagated_changed) = match
precompute_propagate_run_raw_straight_line_func(func, params, module_ctx) {
Some(result) => result
None => (func, false)
}
let @lib.Func(locals, @lib.Expr(propagated_instrs)) = propagated
let (rewritten, cleanup_changed) = precompute_raw_rewrite_instrs(
propagated_instrs, module_ctx,
)
Some(
(
@lib.Func::new(locals, @lib.Expr::new(rewritten)),
propagated_changed || cleanup_changed,
),
)
}
///|
fn precompute_propagate_run_raw_straight_line_func(
func : @lib.Func,
params : Array[@lib.ValType],
module_ctx : @ir.HotModuleContext?,
) -> (@lib.Func, Bool)? {
let @lib.Func(locals, @lib.Expr(instrs)) = func
let local_values : Array[@lib.Instruction?] = Array::make(
params.length() + locals.length(),
None,
)
for idx = 0; idx < locals.length(); idx = idx + 1 {
local_values[params.length() + idx] = precompute_raw_default_local_const(
locals.get(idx),
)
}
match precompute_propagate_run_raw_instrs(instrs, local_values, module_ctx) {
Some((rewritten, _, changed)) =>
Some((@lib.Func::new(locals, @lib.Expr::new(rewritten)), changed))
None => None
}
}
///|
fn precompute_has_transparent_value_block_child(
func : @ir.HotFunc,
node_id : Int32,
label_used : @bitset.BitSet,
) -> Bool {
if @ir.hot_is_control_node(func, node_id) {
return false
}
for slot in 0..<@ir.hot_child_count(func, node_id) {
let child = @ir.hot_child_get(func, node_id, slot)
if child >= 0 &&
@ir.hot_node_is_live(func, child) &&
@ir.hot_node_get(func, child).op is @ir.HotOp::Block &&
@ir.hot_node_result_arity(func, child) == 1 &&
!pass_label_is_used(@ir.hot_control_node_label(func, child), label_used) &&
@ir.hot_region_root_count(func, @ir.hot_region_body_ref(func, child)) == 1 {
return true
}
}
false
}
///|
fn precompute_run(ctx : HotPassContext, func : @ir.HotFunc) -> HotPassResult {
let mut changed_any = false
let mut changed_round = true
while changed_round {
changed_round = false
let entry_label_used = pass_compute_label_used(func)
let simplified = precompute_simplify_region_roots(
ctx,
func,
@ir.HotRegionRef::root(),
entry_label_used,
)
if simplified {
changed_round = true
changed_any = true
}
let label_used = if simplified {
pass_compute_label_used(func)
} else {
entry_label_used
}
let mut inline_candidates : Array[Int32]? = None
for node_id = 0; node_id < @ir.hot_node_count(func); node_id = node_id + 1 {
if !@ir.hot_node_is_live(func, node_id) {
continue
}
if precompute_try_refinalize_exact_cast_block(ctx, func, node_id) {
changed_round = true
changed_any = true
continue
}
if precompute_try_simplify_self_branch_value_block(ctx, func, node_id) {
changed_round = true
changed_any = true
continue
}
if precompute_has_transparent_value_block_child(func, node_id, label_used) {
match inline_candidates {
Some(candidates) => candidates.push(node_id)
None => inline_candidates = Some([node_id])
}
}
if precompute_try_fold_global_get(ctx, func, node_id) {
changed_round = true
changed_any = true
continue
}
if precompute_try_eliminate_dead_drop(ctx, func, node_id) {
changed_round = true
changed_any = true
continue
}
if precompute_try_fold_void_br_if(ctx, func, node_id) {
changed_round = true
changed_any = true
continue
}
if precompute_try_collapse_first_unreachable_child(ctx, func, node_id) {
changed_round = true
changed_any = true
continue
}
if precompute_try_fold_constant_if(ctx, func, node_id) ||
precompute_try_remove_exact_zero_memory_init(ctx, func, node_id) {
changed_round = true
changed_any = true
continue
}
if precompute_try_partial_select_condition(ctx, func, node_id) ||
precompute_try_partial_heap_select_parent(ctx, func, node_id) ||
precompute_try_fold_constant_select(ctx, func, node_id) ||
precompute_try_fold_string_slice(ctx, func, node_id) ||
precompute_try_fold_fresh_string_new_wtf16(ctx, func, node_id) ||
precompute_try_fold_descriptor_branch(ctx, func, node_id) ||
precompute_try_fold_null_ref_cast(ctx, func, node_id) ||
precompute_try_fold_null_aggregate_trap(ctx, func, node_id) ||
precompute_try_fold_ref_is_null(ctx, func, node_id) ||
precompute_try_partial_ref_eq_select(ctx, func, node_id) ||
precompute_try_fold_gc_identity(ctx, func, node_id) ||
precompute_try_fold_fresh_ref_test(ctx, func, node_id) ||
precompute_try_fold_fresh_struct_get(ctx, func, node_id) ||
precompute_try_fold_fresh_array_len(ctx, func, node_id) ||
precompute_try_fold_fresh_array_get(ctx, func, node_id) {
changed_round = true
changed_any = true
continue
}
let instr = match @ir.hot_node_exact_instr(func, node_id) {
Some(instr) => instr
None => continue
}
if precompute_try_partial_select_parent(ctx, func, node_id, instr) ||
precompute_try_fold_effectful_parent(ctx, func, node_id, instr) {
changed_round = true
changed_any = true
continue
}
let child_count = @ir.hot_child_count(func, node_id)
let changed = if child_count == 1 {
precompute_try_fold_unary(
ctx,
func,
node_id,
instr,
@ir.hot_child_get(func, node_id, 0),
)
} else if child_count == 2 {
precompute_try_fold_binary(
ctx,
func,
node_id,
instr,
@ir.hot_child_get(func, node_id, 0),
@ir.hot_child_get(func, node_id, 1),
)
} else {
false
}
if changed {
changed_round = true
changed_any = true
}
}
// Only this phase changes ownership while its count snapshot is live.
// An inline transfers the sole holder edge to the value; terminating
// replacement detaches the copied value's old edges. Counts can therefore
// only stay equal or decrease. Shared holders remain for later rounds.
// Check and delete all detached nodes once after the phase, rather than
// scanning the complete function for each empty holder.
match inline_candidates {
Some(candidates) => {
let label_used = pass_compute_label_used(func)
let mut counts : @ir.HotNodeUseCounts? = None
let cleanup = PrecomputeTransparentBlockCleanup::new()
for node_id in candidates {
if !@ir.hot_node_is_live(func, node_id) ||
!precompute_has_transparent_value_block_child(
func, node_id, label_used,
) {
continue
}
let use_counts = match counts {
Some(counts) => counts
None => {
let snapshot = @ir.hot_node_use_counts_build(func)
counts = Some(snapshot)
snapshot
}
}
if @ir.hot_node_use_count(use_counts, node_id) > 0 &&
precompute_try_inline_transparent_value_block_child(
ctx,
func,
node_id,
label_used,
use_counts,
cleanup=Some(cleanup),
) {
changed_round = true
changed_any = true
}
}
precompute_finish_transparent_block_cleanup(func, cleanup)
}
None => ()
}
if changed_round &&
precompute_trim_region_nops(ctx, func, @ir.HotRegionRef::root()) {
changed_round = true
changed_any = true
}
if precompute_coalesce_all_root_nops(ctx, func) {
changed_round = true
changed_any = true
}
if precompute_trim_root_nops_before_trailing_const(ctx, func) {
changed_round = true
changed_any = true
}
}
if changed_any {
HotPassResult::changed()
} else {
HotPassResult::unchanged()
}
}
///|
fn precompute_propagate_prefix_const_payload_from_node(
ctx : HotPassContext,
func : @ir.HotFunc,
node_id : Int32,
) -> @ir.HotConstPayload? {
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 | @ir.HotOp::RefNull =>
Some(@ir.hot_const_get(func, node.imm0))
@ir.HotOp::GlobalGet => precompute_global_const(ctx, func, node_id)
_ => None
}
}
///|
fn precompute_propagate_prefix_const_payload_matches_type(
payload : @ir.HotConstPayload,
ty : @lib.ValType,
) -> Bool {
match (payload, ty) {
(@ir.HotConstPayload::I32Const(_), @lib.NumTypeValType(@lib.I32NumType)) =>
true
(@ir.HotConstPayload::I64Const(_), @lib.NumTypeValType(@lib.I64NumType)) =>
true
(@ir.HotConstPayload::F32Const(_), @lib.NumTypeValType(@lib.F32NumType)) =>
true
(@ir.HotConstPayload::F64Const(_), @lib.NumTypeValType(@lib.F64NumType)) =>
true
(@ir.HotConstPayload::RefNullConst(_), @lib.RefTypeValType(expected)) =>
expected.is_nullable()
(@ir.HotConstPayload::StringConst(_), @lib.RefTypeValType(expected)) =>
match expected.get_heap_type() {
@lib.AbsHeapTypeHeapType(@lib.StringAbsHeapType)
| @lib.AbsHeapTypeHeapType(@lib.ExternAbsHeapType) => true
_ => false
}
_ => false
}
}
///|
fn precompute_propagate_prefix_const_instr(
payload : @ir.HotConstPayload,
) -> @lib.Instruction? {
match payload {
@ir.HotConstPayload::I32Const(value) =>
Some(@lib.Instruction::i32_const(value))
@ir.HotConstPayload::I64Const(value) =>
Some(@lib.Instruction::i64_const(value))
@ir.HotConstPayload::F32Const(value) =>
Some(@lib.Instruction::f32_const(value))
@ir.HotConstPayload::F64Const(value) =>
Some(@lib.Instruction::f64_const(value))
@ir.HotConstPayload::RefNullConst(ref_type) =>
Some(@lib.Instruction::ref_null(ref_type.get_heap_type()))
@ir.HotConstPayload::StringConst(value) =>
Some(@lib.Instruction::string_const(value))
}
}
///|
fn precompute_propagate_prefix_const_payload_from_instr(
instr : @lib.Instruction,
) -> @ir.HotConstPayload? {
match instr {
@lib.I32Const(value) => Some(@ir.HotConstPayload::i32_const(value))
@lib.I64Const(value) => Some(@ir.HotConstPayload::i64_const(value))
@lib.F32Const(value) => Some(@ir.HotConstPayload::f32_const(value))
@lib.F64Const(value) => Some(@ir.HotConstPayload::f64_const(value))
@lib.RefNull(ref_type) =>
Some(@ir.HotConstPayload::ref_null_const(ref_type))
@lib.StringConst(value) => Some(@ir.HotConstPayload::string_const(value))
_ => None
}
}
///|
fn precompute_propagate_prefix_default_local_const(
func : @ir.HotFunc,
local_id : Int32,
) -> @ir.HotConstPayload? {
match @ir.hot_local_type(func, local_id) {
Some(@lib.NumTypeValType(@lib.I32NumType)) =>
Some(@ir.HotConstPayload::i32_const(@lib.I32(0)))
Some(@lib.NumTypeValType(@lib.I64NumType)) =>
Some(@ir.HotConstPayload::i64_const(@lib.I64(0L)))
Some(@lib.NumTypeValType(@lib.F32NumType)) =>
Some(@ir.HotConstPayload::f32_const(@lib.F32(0.0)))
Some(@lib.NumTypeValType(@lib.F64NumType)) =>
Some(@ir.HotConstPayload::f64_const(@lib.F64(0.0)))
Some(@lib.RefTypeValType(ref_type)) =>
Some(@ir.HotConstPayload::ref_null_const(ref_type))
_ => None
}
}
///|
// Resolve every transitive local read from actual reaching writes. CFG SSA
// currently omits writes in nested condition operands; following its apparent
// entry defaults through a copy can turn a dynamic result into zero.
fn precompute_propagate_prefix_const_for_node(
ctx : HotPassContext,
func : @ir.HotFunc,
graph : @ir.HotLocalGraph,
label_used : @bitset.BitSet,
node_id : Int32,
states : Array[Int32],
value_consts : Array[@ir.HotConstPayload?],
) -> @ir.HotConstPayload? {
// Earlier allocation/identity folds can append constant nodes after the
// LocalGraph snapshot. Their literal payload needs no reaching-write fact.
match
precompute_propagate_prefix_const_payload_from_node(ctx, func, node_id) {
Some(payload) => return Some(payload)
None => ()
}
if node_id < 0 || node_id >= states.length() {
return None
}
match states[node_id] {
1 | 3 => return None
2 => return value_consts[node_id]
_ => ()
}
states[node_id] = 1
let value = precompute_propagate_prefix_const_for_node_impl(
ctx, func, graph, label_used, node_id, states, value_consts,
)
value_consts[node_id] = value
states[node_id] = if value.is_some() { 2 } else { 3 }
value
}
///|
fn precompute_propagate_prefix_const_for_node_impl(
ctx : HotPassContext,
func : @ir.HotFunc,
graph : @ir.HotLocalGraph,
label_used : @bitset.BitSet,
node_id : Int32,
states : Array[Int32],
value_consts : Array[@ir.HotConstPayload?],
) -> @ir.HotConstPayload? {
match
precompute_propagate_prefix_const_payload_from_node(ctx, func, node_id) {
Some(payload) => return Some(payload)
None => ()
}
if node_id < 0 || !@ir.hot_node_is_live(func, node_id) {
return None
}
let node = @ir.hot_node_get(func, node_id)
match node.op {
@ir.HotOp::LocalGet => {
let sources = @ir.local_graph_reaching_sources_for_get(graph, node_id)
let mut candidate : @ir.HotConstPayload? = None
for source in sources {
let value = match source {
@ir.HotLocalGraphSource::EntrySource(local_id) =>
if @ir.local_graph_entry_source_has_legal_default(graph, local_id) {
precompute_propagate_prefix_default_local_const(func, local_id)
} else {
None
}
@ir.HotLocalGraphSource::SetSource(write_id) =>
if @ir.hot_child_count(func, write_id) == 1 {
precompute_propagate_prefix_const_for_node(
ctx,
func,
graph,
label_used,
@ir.hot_child_get(func, write_id, 0),
states,
value_consts,
)
} else {
None
}
}
match value {
Some(payload) => {
let expected_type = @ir.hot_local_type(func, node.imm0).unwrap()
if !precompute_propagate_prefix_const_payload_matches_type(
payload, expected_type,
) {
return None
}
match candidate {
Some(previous) if !precompute_propagate_same_literal(
precompute_propagate_prefix_const_instr(previous),
precompute_propagate_prefix_const_instr(payload),
) => return None
None => candidate = Some(payload)
_ => ()
}
}
None => return None
}
}
return candidate
}
// Binaryen's propagate mode reasons from a set value's fallthrough value.
// Peel value-preserving tees and unbranched single-result blocks while
// leaving the original wrapper and its effects in place.
@ir.HotOp::LocalTee =>
if @ir.hot_child_count(func, node_id) == 1 {
return precompute_propagate_prefix_const_for_node(
ctx,
func,
graph,
label_used,
@ir.hot_child_get(func, node_id, 0),
states,
value_consts,
)
}
@ir.HotOp::Block => {
let label_id = @ir.hot_control_node_label(func, node_id)
let body_ref = @ir.hot_region_body_ref(func, node_id)
let body_count = @ir.hot_region_root_count(func, body_ref)
if @ir.hot_node_result_arity(func, node_id) == 1 &&
!pass_label_is_used(label_id, label_used) &&
body_count > 0 {
return precompute_propagate_prefix_const_for_node(
ctx,
func,
graph,
label_used,
@ir.hot_region_get(func, body_ref, body_count - 1),
states,
value_consts,
)
}
}
@ir.HotOp::If => {
let cond_id = @ir.hot_child_get(func, node_id, 0)
let take_then = match
precompute_propagate_prefix_const_for_node(
ctx, func, graph, label_used, cond_id, states, value_consts,
) {
Some(@ir.HotConstPayload::I32Const(@lib.I32(0))) => false
Some(@ir.HotConstPayload::I32Const(_)) => true
_ => return None
}
let chosen = if take_then {
Some(@ir.hot_region_then_ref(func, node_id))
} else {
@ir.hot_region_else_ref(func, node_id)
}
match chosen {
Some(region_ref) => {
let root_count = @ir.hot_region_root_count(func, region_ref)
if root_count > 0 {
return precompute_propagate_prefix_const_for_node(
ctx,
func,
graph,
label_used,
@ir.hot_region_get(func, region_ref, root_count - 1),
states,
value_consts,
)
}
}
None => ()
}
}
_ => ()
}
let instr = match @ir.hot_node_exact_instr(func, node_id) {
Some(instr) => instr
None => return None
}
let child_count = @ir.hot_child_count(func, node_id)
if child_count == 1 {
let operand = match
precompute_propagate_prefix_const_for_node(
ctx,
func,
graph,
label_used,
@ir.hot_child_get(func, node_id, 0),
states,
value_consts,
) {
Some(payload) => payload
None => return None
}
let operand_instr = match precompute_propagate_prefix_const_instr(operand) {
Some(instr) => instr
None => return None
}
return match precompute_raw_try_fold_unary(operand_instr, instr) {
Some(next) => precompute_propagate_prefix_const_payload_from_instr(next)
None => None
}
}
if child_count == 2 {
let lhs = match
precompute_propagate_prefix_const_for_node(
ctx,
func,
graph,
label_used,
@ir.hot_child_get(func, node_id, 0),
states,
value_consts,
) {
Some(payload) => payload
None => return None
}
let rhs = match
precompute_propagate_prefix_const_for_node(
ctx,
func,
graph,
label_used,
@ir.hot_child_get(func, node_id, 1),
states,
value_consts,
) {
Some(payload) => payload
None => return None
}
let lhs_instr = match precompute_propagate_prefix_const_instr(lhs) {
Some(instr) => instr
None => return None
}
let rhs_instr = match precompute_propagate_prefix_const_instr(rhs) {
Some(instr) => instr
None => return None
}
return match precompute_raw_try_fold_binary(lhs_instr, rhs_instr, instr) {
Some(next) => precompute_propagate_prefix_const_payload_from_instr(next)
None => None
}
}
None
}
///|
fn precompute_propagate_mark_result_if_arm_writes(
func : @ir.HotFunc,
node_id : Int32,
inside_result_if_arm : Bool,
unsafe_locals : Array[Bool],
) -> Unit {
if node_id < 0 || !@ir.hot_node_is_live(func, node_id) {
return
}
let node = @ir.hot_node_get(func, node_id)
match node.op {
@ir.HotOp::LocalSet | @ir.HotOp::LocalTee => {
let value_id = if @ir.hot_child_count(func, node_id) == 1 {
@ir.hot_child_get(func, node_id, 0)
} else {
-1
}
let result_if_write = value_id >= 0 &&
@ir.hot_node_is_live(func, value_id) &&
@ir.hot_node_result_arity(func, value_id) > 0 &&
(match @ir.hot_node_get(func, value_id).op {
@ir.HotOp::If => true
_ => false
})
if (inside_result_if_arm || result_if_write) &&
node.imm0 >= 0 &&
node.imm0 < unsafe_locals.length() {
unsafe_locals[node.imm0] = true
}
}
_ => ()
}
match node.op {
@ir.HotOp::If => {
precompute_propagate_mark_result_if_arm_writes(
func,
@ir.hot_child_get(func, node_id, 0),
inside_result_if_arm,
unsafe_locals,
)
let arm_is_unsafe = inside_result_if_arm ||
@ir.hot_node_result_arity(func, node_id) > 0
let then_ref = @ir.hot_region_then_ref(func, node_id)
for idx = 0
idx < @ir.hot_region_root_count(func, then_ref)
idx = idx + 1 {
precompute_propagate_mark_result_if_arm_writes(
func,
@ir.hot_region_get(func, then_ref, idx),
arm_is_unsafe,
unsafe_locals,
)
}
match @ir.hot_region_else_ref(func, node_id) {
Some(else_ref) =>
for idx = 0
idx < @ir.hot_region_root_count(func, else_ref)
idx = idx + 1 {
precompute_propagate_mark_result_if_arm_writes(
func,
@ir.hot_region_get(func, else_ref, idx),
arm_is_unsafe,
unsafe_locals,
)
}
None => ()
}
}
_ =>
for slot = 0; slot < @ir.hot_child_count(func, node_id); slot = slot + 1 {
precompute_propagate_mark_result_if_arm_writes(
func,
@ir.hot_child_get(func, node_id, slot),
inside_result_if_arm,
unsafe_locals,
)
}
}
}
///|
fn precompute_propagate_collect_condition_const_writes(
ctx : HotPassContext,
func : @ir.HotFunc,
node_id : Int32,
local_consts : Array[@ir.HotConstPayload?],
) -> Unit {
if node_id < 0 || !@ir.hot_node_is_live(func, node_id) {
return
}
let node = @ir.hot_node_get(func, node_id)
for slot = 0; slot < @ir.hot_child_count(func, node_id); slot = slot + 1 {
precompute_propagate_collect_condition_const_writes(
ctx,
func,
@ir.hot_child_get(func, node_id, slot),
local_consts,
)
}
match node.op {
@ir.HotOp::LocalSet | @ir.HotOp::LocalTee => {
let local_id = node.imm0
if local_id >= 0 &&
local_id < local_consts.length() &&
@ir.hot_child_count(func, node_id) == 1 {
local_consts[local_id] = precompute_propagate_prefix_const_payload_from_node(
ctx,
func,
@ir.hot_child_get(func, node_id, 0),
)
}
}
_ => ()
}
}
///|
fn precompute_propagate_mark_arm_direct_const_gets(
func : @ir.HotFunc,
node_id : Int32,
condition_consts : Array[@ir.HotConstPayload?],
arm_written : Array[Bool],
get_consts : Array[@ir.HotConstPayload?],
) -> Unit {
if node_id < 0 || !@ir.hot_node_is_live(func, node_id) {
return
}
let node = @ir.hot_node_get(func, node_id)
if node.op is @ir.HotOp::Loop {
precompute_propagate_mark_result_if_arm_writes(
func, node_id, true, arm_written,
)
}
match node.op {
@ir.HotOp::Block | @ir.HotOp::Loop => {
let body = @ir.hot_region_body_ref(func, node_id)
for idx = 0; idx < @ir.hot_region_root_count(func, body); idx = idx + 1 {
precompute_propagate_mark_arm_direct_const_gets(
func,
@ir.hot_region_get(func, body, idx),
condition_consts,
arm_written,
get_consts,
)
}
return
}
_ => ()
}
for slot = 0; slot < @ir.hot_child_count(func, node_id); slot = slot + 1 {
precompute_propagate_mark_arm_direct_const_gets(
func,
@ir.hot_child_get(func, node_id, slot),
condition_consts,
arm_written,
get_consts,
)
}
match node.op {
@ir.HotOp::LocalGet => {
let local_id = node.imm0
if local_id >= 0 &&
local_id < condition_consts.length() &&
!arm_written[local_id] {
get_consts[node_id] = condition_consts[local_id]
}
}
@ir.HotOp::LocalSet | @ir.HotOp::LocalTee => {
let local_id = node.imm0
if local_id >= 0 && local_id < arm_written.length() {
arm_written[local_id] = true
}
}
_ => ()
}
}
///|
fn precompute_propagate_mark_result_if_direct_const_gets(
ctx : HotPassContext,
func : @ir.HotFunc,
node_id : Int32,
get_consts : Array[@ir.HotConstPayload?],
) -> 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::If => {
let condition_id = @ir.hot_child_get(func, node_id, 0)
precompute_propagate_mark_result_if_direct_const_gets(
ctx, func, condition_id, get_consts,
)
let then_ref = @ir.hot_region_then_ref(func, node_id)
let else_ref = @ir.hot_region_else_ref(func, node_id)
if @ir.hot_node_result_arity(func, node_id) > 0 {
let condition_consts : Array[@ir.HotConstPayload?] = Array::make(
@ir.hot_local_count(func),
None,
)
precompute_propagate_collect_condition_const_writes(
ctx, func, condition_id, condition_consts,
)
let then_written = Array::make(@ir.hot_local_count(func), false)
for idx = 0
idx < @ir.hot_region_root_count(func, then_ref)
idx = idx + 1 {
precompute_propagate_mark_arm_direct_const_gets(
func,
@ir.hot_region_get(func, then_ref, idx),
condition_consts,
then_written,
get_consts,
)
}
match else_ref {
Some(region_ref) => {
let else_written = Array::make(@ir.hot_local_count(func), false)
for idx = 0
idx < @ir.hot_region_root_count(func, region_ref)
idx = idx + 1 {
precompute_propagate_mark_arm_direct_const_gets(
func,
@ir.hot_region_get(func, region_ref, idx),
condition_consts,
else_written,
get_consts,
)
}
}
None => ()
}
}
for idx = 0
idx < @ir.hot_region_root_count(func, then_ref)
idx = idx + 1 {
precompute_propagate_mark_result_if_direct_const_gets(
ctx,
func,
@ir.hot_region_get(func, then_ref, idx),
get_consts,
)
}
match else_ref {
Some(region_ref) =>
for idx = 0
idx < @ir.hot_region_root_count(func, region_ref)
idx = idx + 1 {
precompute_propagate_mark_result_if_direct_const_gets(
ctx,
func,
@ir.hot_region_get(func, region_ref, idx),
get_consts,
)
}
None => ()
}
}
@ir.HotOp::Block | @ir.HotOp::Loop => {
let body = @ir.hot_region_body_ref(func, node_id)
for idx = 0; idx < @ir.hot_region_root_count(func, body); idx = idx + 1 {
precompute_propagate_mark_result_if_direct_const_gets(
ctx,
func,
@ir.hot_region_get(func, body, idx),
get_consts,
)
}
}
_ =>
for slot = 0; slot < @ir.hot_child_count(func, node_id); slot = slot + 1 {
precompute_propagate_mark_result_if_direct_const_gets(
ctx,
func,
@ir.hot_child_get(func, node_id, slot),
get_consts,
)
}
}
}
///|
fn precompute_known_heap_struct_get(
ctx : HotPassContext,
value : PrecomputeKnownHeapValue,
type_idx : @lib.TypeIdx,
raw_field : UInt32,
signed : Bool,
unsigned : Bool,
) -> PrecomputeKnownHeapValue? {
let module_ctx = match ctx.module_ctx {
Some(module_ctx) => module_ctx
None => return None
}
let fields = match
@ir.hot_module_resolve_struct_fields(module_ctx, type_idx) {
Some(fields) => fields
None => return None
}
let field_idx = raw_field.reinterpret_as_int()
let field = match fields.get(field_idx) {
Some(field) if !field.is_mutable() => field
_ => return None
}
let raw_value = match value {
KnownStructFields(actual_type, values) if actual_type == type_idx =>
match values.get(field_idx) {
Some(value) => value
None => return None
}
KnownStructDefault(actual_type) if actual_type == type_idx =>
match precompute_default_const_for_storage(field.get_storage_type()) {
Some(value) => KnownEmit(value)
None => return None
}
_ => return None
}
match raw_value {
KnownEmit(instr) =>
match
precompute_unpack_array_value(
instr,
field.get_storage_type(),
signed,
unsigned,
) {
Some(instr) => Some(KnownEmit(instr))
None => None
}
_ =>
match field.get_storage_type() {
@lib.ValTypeStorageType(@lib.RefTypeValType(_)) if !signed && !unsigned =>
Some(raw_value)
_ => None
}
}
}
///|
fn precompute_known_heap_array_get(
ctx : HotPassContext,
value : PrecomputeKnownHeapValue,
type_idx : @lib.TypeIdx,
index : UInt32,
signed : Bool,
unsigned : Bool,
) -> PrecomputeKnownHeapValue? {
let module_ctx = match ctx.module_ctx {
Some(module_ctx) => module_ctx
None => return None
}
let field = match @ir.hot_module_resolve_array_field(module_ctx, type_idx) {
Some(field) if !field.is_mutable() => field
_ => return None
}
let raw_value = match value {
KnownArrayFixed(actual_type, values) if actual_type == type_idx =>
match values.get(index.reinterpret_as_int()) {
Some(value) => value
None => return None
}
KnownArrayFill(actual_type, values, length) if actual_type == type_idx &&
index < length &&
values.length() == 1 => values[0]
KnownArrayDefault(actual_type, length) if actual_type == type_idx &&
index < length =>
match precompute_default_const_for_storage(field.get_storage_type()) {
Some(value) => KnownEmit(value)
None => return None
}
_ => return None
}
match raw_value {
KnownEmit(instr) =>
match
precompute_unpack_array_value(
instr,
field.get_storage_type(),
signed,
unsigned,
) {
Some(instr) => Some(KnownEmit(instr))
None => None
}
_ =>
match field.get_storage_type() {
@lib.ValTypeStorageType(@lib.RefTypeValType(_)) if !signed && !unsigned =>
Some(raw_value)
_ => None
}
}
}
///|
fn precompute_known_heap_array_len(
value : PrecomputeKnownHeapValue,
) -> PrecomputeKnownHeapValue? {
let length = match value {
KnownArrayFixed(_, values) => values.length().reinterpret_as_uint()
KnownArrayFill(_, _, length) | KnownArrayDefault(_, length) => length
_ => return None
}
Some(
KnownEmit(
@lib.Instruction::i32_const(@lib.I32(length.reinterpret_as_int())),
),
)
}
///|
fn precompute_known_heap_value_matches_cast(
value : PrecomputeKnownHeapValue,
nullable : Bool,
target : @lib.HeapType,
) -> Bool {
match value {
KnownEmit(@lib.RefNull(_)) => nullable
KnownStructFields(type_idx, _)
| KnownStructDefault(type_idx)
| KnownArrayFixed(type_idx, _)
| KnownArrayFill(type_idx, _, _)
| KnownArrayDefault(type_idx, _) => target == @lib.HeapType::new(type_idx)
_ => false
}
}
///|
fn precompute_propagate_known_heap_value_for_node(
ctx : HotPassContext,
func : @ir.HotFunc,
ssa : @ir.HotLocalSsa,
label_used : @bitset.BitSet,
node_id : Int32,
states : Array[Int32],
allocations : Array[Int32],
depth : Int32,
) -> PrecomputeKnownHeapValue? {
if depth > 64 || node_id < 0 || !@ir.hot_node_is_live(func, node_id) {
return None
}
match precompute_exact_const_instr(ctx, func, node_id) {
Some(instr) => return Some(KnownEmit(instr))
None => ()
}
match @ir.hot_node_exact_instr(func, node_id) {
Some(@lib.RefFunc(func_idx)) =>
return Some(KnownEmit(@lib.Instruction::ref_func(func_idx)))
Some(@lib.StructNew(type_idx)) => {
let fields = match ctx.module_ctx {
Some(module_ctx) =>
match @ir.hot_module_resolve_struct_fields(module_ctx, type_idx) {
Some(fields) => fields
None => return None
}
None => return None
}
if @ir.hot_child_count(func, node_id) != fields.length() {
return None
}
let values : Array[PrecomputeKnownHeapValue] = []
for slot = 0; slot < fields.length(); slot = slot + 1 {
match
precompute_propagate_known_heap_value_for_node(
ctx,
func,
ssa,
label_used,
@ir.hot_child_get(func, node_id, slot),
states,
allocations,
depth + 1,
) {
Some(value) => values.push(value)
None => return None
}
}
return Some(KnownStructFields(type_idx, values))
}
Some(@lib.StructNewDefault(type_idx)) =>
return Some(KnownStructDefault(type_idx))
Some(@lib.ArrayNewFixed(type_idx, @lib.U32(length))) => {
if !precompute_fresh_array_length_is_interpretable(length) ||
@ir.hot_child_count(func, node_id) != length.reinterpret_as_int() {
return None
}
let values : Array[PrecomputeKnownHeapValue] = []
for slot = 0; slot < length.reinterpret_as_int(); slot = slot + 1 {
match
precompute_propagate_known_heap_value_for_node(
ctx,
func,
ssa,
label_used,
@ir.hot_child_get(func, node_id, slot),
states,
allocations,
depth + 1,
) {
Some(value) => values.push(value)
None => return None
}
}
return Some(KnownArrayFixed(type_idx, values))
}
Some(@lib.ArrayNew(type_idx)) if @ir.hot_child_count(func, node_id) == 2 => {
let value = match
precompute_propagate_known_heap_value_for_node(
ctx,
func,
ssa,
label_used,
@ir.hot_child_get(func, node_id, 0),
states,
allocations,
depth + 1,
) {
Some(value) => value
None => return None
}
let length = match
precompute_i32_exact_const(
ctx,
func,
@ir.hot_child_get(func, node_id, 1),
) {
Some(@lib.I32(length)) if length >= 0 &&
precompute_fresh_array_length_is_interpretable(
length.reinterpret_as_uint(),
) => length.reinterpret_as_uint()
_ => return None
}
return Some(KnownArrayFill(type_idx, [value], length))
}
Some(@lib.ArrayNewDefault(type_idx)) if @ir.hot_child_count(func, node_id) ==
1 => {
let length = match
precompute_i32_exact_const(
ctx,
func,
@ir.hot_child_get(func, node_id, 0),
) {
Some(@lib.I32(length)) if length >= 0 &&
precompute_fresh_array_length_is_interpretable(
length.reinterpret_as_uint(),
) => length.reinterpret_as_uint()
_ => return None
}
return Some(KnownArrayDefault(type_idx, length))
}
Some(@lib.StructGet(type_idx, @lib.U32(field)))
| Some(@lib.StructGetS(type_idx, @lib.U32(field)))
| Some(@lib.StructGetU(type_idx, @lib.U32(field))) => {
if @ir.hot_child_count(func, node_id) != 1 {
return None
}
let source = match
precompute_propagate_known_heap_value_for_node(
ctx,
func,
ssa,
label_used,
@ir.hot_child_get(func, node_id, 0),
states,
allocations,
depth + 1,
) {
Some(value) => value
None => return None
}
let signed = @ir.hot_node_exact_instr(func, node_id) ==
Some(@lib.Instruction::struct_get_s(type_idx, @lib.U32(field)))
let unsigned = @ir.hot_node_exact_instr(func, node_id) ==
Some(@lib.Instruction::struct_get_u(type_idx, @lib.U32(field)))
return precompute_known_heap_struct_get(
ctx, source, type_idx, field, signed, unsigned,
)
}
Some(@lib.ArrayGet(type_idx))
| Some(@lib.ArrayGetS(type_idx))
| Some(@lib.ArrayGetU(type_idx)) => {
if @ir.hot_child_count(func, node_id) != 2 {
return None
}
let source = match
precompute_propagate_known_heap_value_for_node(
ctx,
func,
ssa,
label_used,
@ir.hot_child_get(func, node_id, 0),
states,
allocations,
depth + 1,
) {
Some(value) => value
None => return None
}
let index = match
precompute_i32_exact_const(
ctx,
func,
@ir.hot_child_get(func, node_id, 1),
) {
Some(@lib.I32(index)) => index.reinterpret_as_uint()
None => return None
}
let signed = @ir.hot_node_exact_instr(func, node_id) ==
Some(@lib.Instruction::array_get_s(type_idx))
let unsigned = @ir.hot_node_exact_instr(func, node_id) ==
Some(@lib.Instruction::array_get_u(type_idx))
return precompute_known_heap_array_get(
ctx, source, type_idx, index, signed, unsigned,
)
}
Some(@lib.ArrayLen) => {
if @ir.hot_child_count(func, node_id) != 1 {
return None
}
let source = match
precompute_propagate_known_heap_value_for_node(
ctx,
func,
ssa,
label_used,
@ir.hot_child_get(func, node_id, 0),
states,
allocations,
depth + 1,
) {
Some(value) => value
None => return None
}
return precompute_known_heap_array_len(source)
}
Some(@lib.RefCast(nullable, _, target)) => {
if @ir.hot_child_count(func, node_id) != 1 {
return None
}
let source = match
precompute_propagate_known_heap_value_for_node(
ctx,
func,
ssa,
label_used,
@ir.hot_child_get(func, node_id, 0),
states,
allocations,
depth + 1,
) {
Some(value) => value
None => return None
}
if precompute_known_heap_value_matches_cast(source, nullable, target) {
return Some(source)
}
return None
}
_ => ()
}
let node = @ir.hot_node_get(func, node_id)
match node.op {
@ir.HotOp::RefFunc =>
Some(
KnownEmit(
@lib.Instruction::ref_func(
@lib.FuncIdx::new(node.imm0.reinterpret_as_uint()),
),
),
)
@ir.HotOp::GlobalGet =>
match ctx.module_ctx {
Some(module_ctx) =>
precompute_known_heap_value_from_global(
module_ctx,
@lib.GlobalIdx::new(node.imm0.reinterpret_as_uint()),
depth + 1,
)
None => None
}
@ir.HotOp::LocalGet => {
let allocation_id = precompute_propagate_allocation_for_node(
func, ssa, label_used, node_id, states, allocations,
)
if allocation_id >= 0 && allocation_id != node_id {
precompute_propagate_known_heap_value_for_node(
ctx,
func,
ssa,
label_used,
allocation_id,
states,
allocations,
depth + 1,
)
} else {
None
}
}
@ir.HotOp::LocalTee if @ir.hot_child_count(func, node_id) == 1 =>
precompute_propagate_known_heap_value_for_node(
ctx,
func,
ssa,
label_used,
@ir.hot_child_get(func, node_id, 0),
states,
allocations,
depth + 1,
)
_ => None
}
}
///|
fn precompute_replace_with_known_emit(
ctx : HotPassContext,
func : @ir.HotFunc,
node_id : Int32,
instr : @lib.Instruction,
) -> Bool {
match instr {
@lib.RefFunc(func_idx) => {
let type_idx = match ctx.module_ctx {
Some(module_ctx) =>
match @ir.hot_module_resolve_func_type_idx(module_ctx, func_idx) {
Some(type_idx) => type_idx
None => return false
}
None => return false
}
let ref_type = @lib.RefType::new(
false,
@lib.HeapType::new(type_idx),
exact=ctx.module_ctx.unwrap().uses_custom_descriptors,
)
let temp = @ir.hot_build_ref_func(func, func_idx, ref_type)
let replacement = @ir.hot_node_get(func, temp)
@ir.hot_delete_node(func, temp)
precompute_replace_node_rehoming_control_label(func, node_id, replacement)
pass_mark_mutated(ctx, func)
true
}
_ => precompute_replace_with_const_instr(ctx, func, node_id, instr)
}
}
///|
fn precompute_propagate_try_fold_known_heap_read(
ctx : HotPassContext,
func : @ir.HotFunc,
ssa : @ir.HotLocalSsa,
label_used : @bitset.BitSet,
node_id : Int32,
states : Array[Int32],
allocations : Array[Int32],
) -> Bool {
match @ir.hot_node_exact_instr(func, node_id) {
Some(@lib.StructGet(_, _))
| Some(@lib.StructGetS(_, _))
| Some(@lib.StructGetU(_, _))
| Some(@lib.ArrayGet(_))
| Some(@lib.ArrayGetS(_))
| Some(@lib.ArrayGetU(_))
| Some(@lib.ArrayLen) => ()
_ => return false
}
match
precompute_propagate_known_heap_value_for_node(
ctx, func, ssa, label_used, node_id, states, allocations, 0,
) {
Some(KnownEmit(instr)) => {
let value = match precompute_build_known_emit(ctx, func, instr) {
Some(value) => value
None => return false
}
let body : Array[Int32] = []
for slot = 0; slot < @ir.hot_child_count(func, node_id); slot = slot + 1 {
let child = @ir.hot_child_get(func, node_id, slot)
precompute_propagate_append_heap_operand_effects(
ctx, func, ssa, label_used, child, states, allocations, body, 0,
)
}
body.push(value)
let temp = if body.length() == 1 {
value
} else {
let result_type = @ir.hot_type_results(
func,
@ir.hot_node_type(func, value),
)[0]
@ir.hot_build_block(func, @lib.BlockType::val_type(result_type), body)
}
let replacement = @ir.hot_node_get(func, temp)
@ir.hot_delete_node(func, temp)
precompute_replace_node_rehoming_control_label(func, node_id, replacement)
pass_mark_mutated(ctx, func)
true
}
_ => false
}
}
///|
fn precompute_propagate_allocation_for_node(
func : @ir.HotFunc,
ssa : @ir.HotLocalSsa,
label_used : @bitset.BitSet,
node_id : Int32,
states : Array[Int32],
allocations : Array[Int32],
) -> Int32 {
if node_id < 0 || !@ir.hot_node_is_live(func, node_id) {
return -1
}
match @ir.hot_node_exact_instr(func, node_id) {
Some(@lib.StructNew(_))
| Some(@lib.StructNewDefault(_))
| Some(@lib.ArrayNew(_))
| Some(@lib.ArrayNewDefault(_))
| Some(@lib.ArrayNewFixed(_, _)) => return node_id
_ => ()
}
match @ir.hot_node_get(func, node_id).op {
@ir.HotOp::LocalGet => {
let value_id = ssa.local_get_values[node_id]
if value_id >= 0 {
return precompute_propagate_allocation_for_value(
func, ssa, label_used, value_id, states, allocations,
)
}
}
@ir.HotOp::LocalTee =>
if @ir.hot_child_count(func, node_id) == 1 {
return precompute_propagate_allocation_for_node(
func,
ssa,
label_used,
@ir.hot_child_get(func, node_id, 0),
states,
allocations,
)
}
@ir.HotOp::Block => {
let label_id = @ir.hot_control_node_label(func, node_id)
let body = @ir.hot_region_body_ref(func, node_id)
let count = @ir.hot_region_root_count(func, body)
if @ir.hot_node_result_arity(func, node_id) == 1 &&
!pass_label_is_used(label_id, label_used) &&
count > 0 {
return precompute_propagate_allocation_for_node(
func,
ssa,
label_used,
@ir.hot_region_get(func, body, count - 1),
states,
allocations,
)
}
}
_ => ()
}
-1
}
///|
fn precompute_propagate_allocation_for_value(
func : @ir.HotFunc,
ssa : @ir.HotLocalSsa,
label_used : @bitset.BitSet,
value_id : Int32,
states : Array[Int32],
allocations : Array[Int32],
) -> Int32 {
if value_id < 0 || value_id >= states.length() {
return -1
}
match states[value_id] {
1 => return -1
2 => return allocations[value_id]
_ => ()
}
states[value_id] = 1
let resolved = match @ir.ssa_value_origin(ssa, value_id) {
@ir.HotSsaValueOrigin::LocalSetDef(node_id)
| @ir.HotSsaValueOrigin::LocalTeeDef(node_id) =>
if @ir.hot_child_count(func, node_id) == 1 {
precompute_propagate_allocation_for_node(
func,
ssa,
label_used,
@ir.hot_child_get(func, node_id, 0),
states,
allocations,
)
} else {
-1
}
@ir.HotSsaValueOrigin::PhiDef(phi_id) => {
let inputs = @ir.ssa_phi_inputs(ssa, phi_id)
let mut candidate = -1
let mut ok = inputs.length() > 0
for input in inputs {
let allocation = precompute_propagate_allocation_for_value(
func,
ssa,
label_used,
input.value_id,
states,
allocations,
)
if allocation < 0 {
ok = false
} else if candidate < 0 {
candidate = allocation
} else if candidate != allocation {
ok = false
}
if !ok {
break
}
}
if ok {
candidate
} else {
-1
}
}
@ir.HotSsaValueOrigin::EntryParamDef(_)
| @ir.HotSsaValueOrigin::EntryDefaultInitDef(_) => -1
}
states[value_id] = 2
allocations[value_id] = resolved
resolved
}
///|
fn precompute_propagate_mark_loop_carried_default_relations(
func : @ir.HotFunc,
node_id : Int32,
inside_loop : Bool,
loop_written : Array[Bool],
get_inside_loop : Array[Bool],
visited_outside_loop : Array[Bool],
visited_inside_loop : Array[Bool],
) -> Unit {
if node_id < 0 || !@ir.hot_node_is_live(func, node_id) {
return
}
let visited = if inside_loop {
visited_inside_loop
} else {
visited_outside_loop
}
if visited[node_id] {
return
}
visited[node_id] = true
let node = @ir.hot_node_get(func, node_id)
match node.op {
@ir.HotOp::LocalGet =>
if inside_loop && node_id < get_inside_loop.length() {
get_inside_loop[node_id] = true
}
@ir.HotOp::LocalSet | @ir.HotOp::LocalTee =>
if inside_loop && node.imm0 >= 0 && node.imm0 < loop_written.length() {
loop_written[node.imm0] = true
}
_ => ()
}
for slot = 0; slot < @ir.hot_child_count(func, node_id); slot = slot + 1 {
precompute_propagate_mark_loop_carried_default_relations(
func,
@ir.hot_child_get(func, node_id, slot),
inside_loop,
loop_written,
get_inside_loop,
visited_outside_loop,
visited_inside_loop,
)
}
let visit_region = fn(region_ref, nested_inside_loop) {
for idx = 0
idx < @ir.hot_region_root_count(func, region_ref)
idx = idx + 1 {
precompute_propagate_mark_loop_carried_default_relations(
func,
@ir.hot_region_get(func, region_ref, idx),
nested_inside_loop,
loop_written,
get_inside_loop,
visited_outside_loop,
visited_inside_loop,
)
}
}
match node.op {
@ir.HotOp::Loop =>
visit_region(@ir.hot_region_body_ref(func, node_id), true)
@ir.HotOp::Block =>
visit_region(@ir.hot_region_body_ref(func, node_id), inside_loop)
@ir.HotOp::If => {
visit_region(@ir.hot_region_then_ref(func, node_id), inside_loop)
match @ir.hot_region_else_ref(func, node_id) {
Some(region_ref) => visit_region(region_ref, inside_loop)
None => ()
}
}
@ir.HotOp::Try | @ir.HotOp::TryTable => {
visit_region(@ir.hot_region_body_ref(func, node_id), inside_loop)
visit_region(@ir.hot_region_catch_list_ref(func, node_id), inside_loop)
}
_ => ()
}
}
///|
fn precompute_propagate_prefix_fold_local_gets(
ctx : HotPassContext,
func : @ir.HotFunc,
) -> Bool {
// A node-indexed SSA overlay cannot choose a different origin per repeated
// producer evaluation. Preserve this phase before it queries that overlay.
if pass_has_repeated_entry_evaluation(func) {
return false
}
let ssa = pass_require_ssa(ctx, func)
let label_used = pass_compute_label_used(func)
// Scalar reaching writes must include control nested in value operands.
// The root-only SSA graph can miss loop-body copies in those conditions.
let graph = @ir.local_graph_build(
func,
@ir.cfg_build(func, expand_operand_control=true),
)
let states = Array::make(@ir.hot_node_count(func), 0)
let value_consts : Array[@ir.HotConstPayload?] = Array::make(
@ir.hot_node_count(func),
None,
)
let allocation_states = Array::make(ssa.value_types.length(), 0)
let allocation_values = Array::make(ssa.value_types.length(), -1)
let allocation_folds : Array[PrecomputePropagateAllocationFold] = []
let known_heap_read_folds : Array[Int32] = []
let identity_folds : Array[PrecomputePropagateIdentityFold] = []
let result_if_arm_written = Array::make(@ir.hot_local_count(func), false)
let result_if_direct_get_consts : Array[@ir.HotConstPayload?] = Array::make(
@ir.hot_node_count(func),
None,
)
let loop_written = Array::make(@ir.hot_local_count(func), false)
let get_inside_loop = Array::make(@ir.hot_node_count(func), false)
let visited_outside_loop = Array::make(@ir.hot_node_count(func), false)
let visited_inside_loop = Array::make(@ir.hot_node_count(func), false)
for root_idx = 0; root_idx < @ir.hot_root_count(func); root_idx = root_idx + 1 {
precompute_propagate_mark_loop_carried_default_relations(
func,
@ir.hot_root_get(func, root_idx),
false,
loop_written,
get_inside_loop,
visited_outside_loop,
visited_inside_loop,
)
precompute_propagate_mark_result_if_direct_const_gets(
ctx,
func,
@ir.hot_root_get(func, root_idx),
result_if_direct_get_consts,
)
precompute_propagate_mark_result_if_arm_writes(
func,
@ir.hot_root_get(func, root_idx),
false,
result_if_arm_written,
)
}
let replacements : Array[PrecomputePropagatePrefixReplacement] = []
for node_id = 0; node_id < @ir.hot_node_count(func); node_id = node_id + 1 {
if !@ir.hot_node_is_live(func, node_id) {
continue
}
let is_ref_is_null = match @ir.hot_node_get(func, node_id).op {
@ir.HotOp::RefIsNull => true
_ => false
}
if is_ref_is_null && @ir.hot_child_count(func, node_id) == 1 {
let allocation = precompute_propagate_allocation_for_node(
func,
ssa,
label_used,
@ir.hot_child_get(func, node_id, 0),
allocation_states,
allocation_values,
)
if allocation >= 0 {
identity_folds.push({ node_id, value: @lib.I32(0), })
}
}
match @ir.hot_node_exact_instr(func, node_id) {
Some(@lib.StructGet(_, _))
| Some(@lib.StructGetS(_, _))
| Some(@lib.StructGetU(_, _))
| Some(@lib.ArrayGet(_))
| Some(@lib.ArrayGetS(_))
| Some(@lib.ArrayGetU(_))
| Some(@lib.ArrayLen) => {
known_heap_read_folds.push(node_id)
if @ir.hot_child_count(func, node_id) > 0 {
let allocation_id = precompute_propagate_allocation_for_node(
func,
ssa,
label_used,
@ir.hot_child_get(func, node_id, 0),
allocation_states,
allocation_values,
)
if allocation_id >= 0 {
allocation_folds.push({ node_id, allocation_id, })
}
}
}
Some(@lib.RefEq) =>
if @ir.hot_child_count(func, node_id) == 2 {
let lhs = @ir.hot_child_get(func, node_id, 0)
let rhs = @ir.hot_child_get(func, node_id, 1)
let lhs_allocation = precompute_propagate_allocation_for_node(
func, ssa, label_used, lhs, allocation_states, allocation_values,
)
let rhs_allocation = precompute_propagate_allocation_for_node(
func, ssa, label_used, rhs, allocation_states, allocation_values,
)
if lhs_allocation >= 0 && rhs_allocation >= 0 {
identity_folds.push({
node_id,
value: @lib.I32(
if lhs_allocation == rhs_allocation {
1
} else {
0
},
),
})
}
}
_ => ()
}
}
let mut changed = false
for fold in allocation_folds {
if !@ir.hot_node_is_live(func, fold.node_id) {
continue
}
if precompute_try_fold_fresh_struct_get_from_allocation(
ctx,
func,
fold.node_id,
fold.allocation_id,
) ||
precompute_try_fold_fresh_array_len_from_allocation(
ctx,
func,
fold.node_id,
fold.allocation_id,
) ||
precompute_try_fold_fresh_array_get_from_allocation(
ctx,
func,
fold.node_id,
fold.allocation_id,
) {
changed = true
}
}
for node_id in known_heap_read_folds {
if @ir.hot_node_is_live(func, node_id) &&
precompute_propagate_try_fold_known_heap_read(
ctx, func, ssa, label_used, node_id, allocation_states, allocation_values,
) {
changed = true
}
}
for fold in identity_folds {
if @ir.hot_node_is_live(func, fold.node_id) &&
precompute_replace_gc_identity_with_const(
ctx,
func,
fold.node_id,
fold.value,
) {
changed = true
}
}
// Apply evaluator-derived allocation facts before resolving scalar local
// reads. This still uses the single SSA solve above, but lets dependent local
// definitions observe constants produced by that same bounded fact solve.
for node_id = 0; node_id < states.length(); node_id = node_id + 1 {
if !@ir.hot_node_is_live(func, node_id) {
continue
}
match @ir.hot_node_get(func, node_id).op {
@ir.HotOp::LocalGet => {
let local_id = @ir.hot_node_get(func, node_id).imm0
match result_if_direct_get_consts[node_id] {
Some(payload) => {
replacements.push({ node_id, payload, })
continue
}
None => ()
}
let value_id = ssa.local_get_values[node_id]
if value_id < 0 {
continue
}
if local_id >= 0 &&
local_id < loop_written.length() &&
loop_written[local_id] &&
get_inside_loop[node_id] &&
(match @ir.ssa_value_origin(ssa, value_id) {
@ir.HotSsaValueOrigin::EntryDefaultInitDef(origin_local) =>
origin_local == local_id
_ => false
}) {
// The same lexical local can be default-valued on the first trip and
// loop-carried from a nested write on later trips. Until every such
// backedge receives a real SSA phi, retain the dynamic read.
continue
}
if local_id >= 0 &&
local_id < result_if_arm_written.length() &&
result_if_arm_written[local_id] {
// A real phi proves CFG/SSA observed both result-if arms. If SSA
// reports a stale entry or prior definition instead, retain the get.
match @ir.ssa_value_origin(ssa, value_id) {
@ir.HotSsaValueOrigin::PhiDef(_) => ()
_ => continue
}
}
// Outside result-producing if arms, trust the SSA origin directly.
// In particular, an entry-default read before a later write is a real
// constant, even when that local is written elsewhere in the function.
match
precompute_propagate_prefix_const_for_node(
ctx, func, graph, label_used, node_id, states, value_consts,
) {
Some(payload) => replacements.push({ node_id, payload, })
None => ()
}
}
_ => ()
}
}
for replacement in replacements {
if !@ir.hot_node_is_live(func, replacement.node_id) {
continue
}
let temp = match replacement.payload {
@ir.HotConstPayload::I32Const(value) =>
@ir.hot_build_const_i32(func, value)
@ir.HotConstPayload::I64Const(value) =>
@ir.hot_build_const_i64(func, value)
@ir.HotConstPayload::F32Const(value) =>
@ir.hot_build_const_f32(func, value)
@ir.HotConstPayload::F64Const(value) =>
@ir.hot_build_const_f64(func, value)
@ir.HotConstPayload::RefNullConst(value) =>
@ir.hot_build_ref_null(func, value)
@ir.HotConstPayload::StringConst(value) =>
@ir.hot_build_string_const(func, value)
}
let next_node = @ir.hot_node_get(func, temp)
@ir.hot_delete_node(func, temp)
precompute_replace_node_rehoming_control_label(
func,
replacement.node_id,
next_node,
)
pass_mark_mutated(ctx, func)
changed = true
}
changed
}
///|
pub fn precompute_propagate_run(
ctx : HotPassContext,
func : @ir.HotFunc,
) -> HotPassResult {
// Resolve the local graph before mutating control. The current plain
// precompute cleanup can temporarily leave detached label owners until
// lowering; requiring SSA after that mutation would verify the transient
// state. Applying the proven local facts first still preserves Binaryen's
// bounded contract: one local solve followed by one evaluator run.
let propagated = precompute_propagate_prefix_fold_local_gets(ctx, func)
let precomputed = precompute_run(ctx, func)
if propagated || precomputed.changed {
HotPassResult::changed()
} else {
HotPassResult::unchanged()
}
}
///|
fn precompute_propagate_append_heap_operand_effects(
ctx : HotPassContext,
func : @ir.HotFunc,
ssa : @ir.HotLocalSsa,
label_used : @bitset.BitSet,
node_id : Int32,
states : Array[Int32],
allocations : Array[Int32],
out : Array[Int32],
depth : Int32,
) -> Unit {
if precompute_is_discardable_value(func, node_id) {
return
}
let known_read = match @ir.hot_node_exact_instr(func, node_id) {
Some(
@lib.StructGet(_, _)
| @lib.StructGetS(_, _)
| @lib.StructGetU(_, _)
| @lib.ArrayGet(_)
| @lib.ArrayGetS(_)
| @lib.ArrayGetU(_)
| @lib.ArrayLen
) => true
_ => false
}
if depth < 64 &&
known_read &&
precompute_propagate_known_heap_value_for_node(
ctx, func, ssa, label_used, node_id, states, allocations, 0,
)
is Some(_) {
// Known immutable reads prove the dereference/bounds; only operands remain observable.
for slot = 0; slot < @ir.hot_child_count(func, node_id); slot = slot + 1 {
precompute_propagate_append_heap_operand_effects(
ctx,
func,
ssa,
label_used,
@ir.hot_child_get(func, node_id, slot),
states,
allocations,
out,
depth + 1,
)
}
} else {
out.push(@ir.hot_build_drop(func, node_id))
}
}
///|
// Integer arithmetic retains the sticky low bits that an intermediate f64
// would lose at an f32 halfway boundary. Round once, ties to even.
fn precompute_u64_to_f32(value : UInt64, negative~ : Bool = false) -> Float {
if value == 0UL {
return 0.0F
}
let mut exponent = 63 - value.clz()
let mut significand = if exponent <= 23 {
value << (23 - exponent)
} else {
let shift = exponent - 23
let high = value >> shift
let remainder = value & ((1UL << shift) - 1UL)
let halfway = 1UL << (shift - 1)
if remainder > halfway || (remainder == halfway && (high & 1UL) != 0UL) {
high + 1UL
} else {
high
}
}
if significand == 0x1000000UL {
significand = significand >> 1
exponent += 1
}
let sign = if negative { 0x80000000U } else { 0U }
let bits = sign |
((exponent + 127).to_uint() << 23) |
(significand.to_uint() & 0x7fffffU)
bits.reinterpret_as_int().reinterpret_as_float()
}
///|
fn precompute_heap_identity_has_alternative_exit(
func : @ir.HotFunc,
node_id : Int32,
label : Int32,
) -> Bool {
if node_id < 0 || !@ir.hot_node_is_live(func, node_id) {
return false
}
if @ir.hot_branch_target(func, node_id) == Some(label) ||
@ir.hot_branch_table_targets_for_node(func, node_id).contains(label) {
return true
}
match @ir.hot_node_get(func, node_id).op {
@ir.HotOp::Try | @ir.HotOp::TryTable => return true
@ir.HotOp::Continuation =>
if @ir.hot_continuation_targets(func, node_id).contains(label) {
return true
}
_ => ()
}
for slot in 0..<@ir.hot_child_count(func, node_id) {
if precompute_heap_identity_has_alternative_exit(
func,
@ir.hot_child_get(func, node_id, slot),
label,
) {
return true
}
}
false
}
///|
// Scalar constants have one opcode and a signed LEB; splats have two opcode
// bytes. No per-node module encoding is needed to compare them with 18 bytes.
fn precompute_materialization_sleb_size(input : Int64) -> Int32 {
let mut value = input
let mut size = 1
while value < -64L || value > 63L {
value = value >> 7
size += 1
}
size
}
///|
fn precompute_materialization_repeated(
bytes : Array[Byte],
width : Int32,
) -> Bool {
for index in width..<16 {
if bytes[index] != bytes[index % width] {
return false
}
}
true
}
///|
fn precompute_materialize_vector(
instr : @lib.Instruction,
) -> Array[@lib.Instruction]? {
let bytes = match precompute_v128_const_bytes(instr) {
Some(bytes) => bytes
None => return None
}
let mut best_size = 18
let mut best : Array[@lib.Instruction]? = None
for width in [1, 2, 4, 8] {
if !precompute_materialization_repeated(bytes, width) {
continue
}
let bits = precompute_v128_read_u64(bytes, 0)
let value = match width {
1 => ((bytes[0].to_uint() << 24).reinterpret_as_int() >> 24).to_int64()
2 => ((bits.to_uint() << 16).reinterpret_as_int() >> 16).to_int64()
4 => bits.to_uint().reinterpret_as_int().to_int64()
_ => bits.reinterpret_as_int64()
}
let size = 3 + precompute_materialization_sleb_size(value)
if size < best_size {
best_size = size
best = Some(
match width {
1 =>
[
@lib.Instruction::i32_const(@lib.I32(value.to_int())),
@lib.Instruction::i8x16_splat(),
]
2 =>
[
@lib.Instruction::i32_const(@lib.I32(value.to_int())),
@lib.Instruction::i16x8_splat(),
]
4 =>
[
@lib.Instruction::i32_const(@lib.I32(value.to_int())),
@lib.Instruction::i32x4_splat(),
]
_ =>
[
@lib.Instruction::i64_const(@lib.I64(value)),
@lib.Instruction::i64x2_splat(),
]
},
)
}
// f32 costs seven bytes and can beat a five-byte signed i32 immediate.
if width == 4 && 7 < best_size {
best_size = 7
best = Some([
@lib.Instruction::f32_const(
@lib.F32(Float::reinterpret_from_uint(bits.to_uint())),
),
@lib.Instruction::f32x4_splat(),
])
}
// f64 costs eleven and can beat a nine/ten-byte signed i64 immediate.
// Reinterpret only: retain every NaN payload and signed-zero bit.
if width == 8 && 11 < best_size {
best_size = 11
best = Some([
@lib.Instruction::f64_const(@lib.F64(bits.reinterpret_as_double())),
@lib.Instruction::f64x2_splat(),
])
}
}
best
}
///|
fn precompute_materialize_vector_children(
instr : @lib.Instruction,
) -> @lib.Instruction? {
match instr {
@lib.Block(bt, @lib.Expr(body)) =>
match precompute_materialize_vector_body(body) {
Some(next) => Some(@lib.Instruction::block(bt, @lib.Expr::new(next)))
None => None
}
@lib.Loop(bt, @lib.Expr(body)) =>
match precompute_materialize_vector_body(body) {
Some(next) => Some(@lib.Instruction::loop_(bt, @lib.Expr::new(next)))
None => None
}
@lib.TryTable(bt, catches, @lib.Expr(body)) =>
match precompute_materialize_vector_body(body) {
Some(next) =>
Some(@lib.Instruction::try_table(bt, catches, @lib.Expr::new(next)))
None => None
}
@lib.If(bt, yes, no) => {
let next_yes = precompute_materialize_vector_body(yes)
let next_no = match no {
Some(body) => precompute_materialize_vector_body(body)
None => None
}
if next_yes is None && next_no is None {
return None
}
let yes = match next_yes {
Some(next) => next
None => yes
}
let no = match no {
Some(body) =>
Some(
match next_no {
Some(next) => next
None => body
},
)
None => None
}
Some(@lib.Instruction::if_(bt, yes, no))
}
@lib.Try(bt, expr, catches, delegate) => {
let @lib.Expr(body) = expr
let next_body = precompute_materialize_vector_body(body)
let mut next_catches : Array[@lib.LegacyCatch]? = None
for index in 0.. child
}
match precompute_materialize_vector_body(child) {
Some(next) => {
if next_catches is None {
next_catches = Some(catches.copy())
}
next_catches.unwrap()[index] = match catches[index] {
@lib.LegacyCatch(tag, _) =>
@lib.LegacyCatch::tagged(tag, @lib.Expr::new(next))
@lib.LegacyCatchAll(_) =>
@lib.LegacyCatch::all(@lib.Expr::new(next))
}
}
None => ()
}
}
if next_body is None && next_catches is None {
return None
}
let expr = match next_body {
Some(next) => @lib.Expr::new(next)
None => expr
}
let catches = match next_catches {
Some(next) => next
None => catches
}
Some(@lib.Instruction::try_(bt, expr, catches, delegate~))
}
_ => None
}
}
///|
// None means unchanged: scalar leaves and unchanged control bodies need no
// result tuple, mutable capture, callback, or replacement control wrapper.
fn precompute_materialize_vector_body(
body : Array[@lib.Instruction],
) -> Array[@lib.Instruction]? {
let mut out : Array[@lib.Instruction]? = None
for index in 0.. next
None => original
}
let splat = precompute_materialize_vector(instr)
if nested is Some(_) || splat is Some(_) {
if out is None {
let prefix : Array[@lib.Instruction] = Array::new(
capacity=body.length() + 1,
)
for previous in 0..
match splat {
Some(pair) =>
for item in pair {
items.push(item)
}
None => items.push(instr)
}
None => ()
}
}
out
}
///|
// Run after folding and writeback repair, so vector users see literals while
// folding. Apply the smaller final representation only under a size policy.
fn precompute_materialize_vectors_for_size(
mod_ : @lib.Module,
shrink_level : Int32,
touched~ : Array[Bool]? = None,
) -> @lib.Module {
if shrink_level <= 0 || pass_module_has_offset_sensitive_metadata(mod_) {
return mod_
}
let funcs = match mod_.code_sec {
Some(@lib.CodeSec(funcs)) => funcs
None => return mod_
}
let mut next : Array[@lib.Func]? = None
for index in 0..= selected.length() || !selected[index]) {
continue
}
let @lib.Func(locals, @lib.Expr(body)) = funcs[index]
match precompute_materialize_vector_body(body) {
Some(body) => {
if next is None {
next = Some(funcs.copy())
}
next.unwrap()[index] = @lib.Func::new(locals, @lib.Expr::new(body))
}
None => ()
}
}
let next = match next {
Some(next) => next
None => return mod_
}
let candidate = mod_.with_code_sec(@lib.CodeSec::new(next))
let baseline = if mod_.compiler_fact_custom_section is Some(_) {
mod_.without_compiler_fact_custom_section()
} else {
mod_
}
// One full-module check includes body/section framing and all retained names.
match @binary.encoded_module_sizes(baseline, candidate) {
Ok((before, after)) if after < before => candidate
_ => mod_
}
}
///|
fn precompute_materialize_stack_vectors_for_size(
mod_ : @lib.Module,
descriptors : Array[@ir.HotPassDescriptor],
shrink_level : Int32,
touched~ : Array[Bool]? = None,
) -> @lib.Module {
if shrink_level <= 0 {
return mod_
}
for descriptor in descriptors {
if descriptor.name == "precompute" ||
descriptor.name == "precompute-propagate" {
return precompute_materialize_vectors_for_size(
mod_,
shrink_level,
touched~,
)
}
}
mod_
}