///|
pub type EffectMask = UInt32
///|
pub const EFFECT_MASK_CONTROL : EffectMask = 1U
///|
pub const EFFECT_MASK_CALL : EffectMask = 2U
///|
pub const EFFECT_MASK_THROW : EffectMask = 4U
///|
pub const EFFECT_MASK_TRAP : EffectMask = 8U
///|
pub const EFFECT_MASK_LOCAL_STATE : EffectMask = 16U
///|
pub const EFFECT_MASK_GLOBAL_STATE : EffectMask = 32U
///|
pub const EFFECT_MASK_MEMORY_READ : EffectMask = 64U
///|
pub const EFFECT_MASK_MEMORY_WRITE : EffectMask = 128U
///|
pub const EFFECT_MASK_TABLE_READ : EffectMask = 256U
///|
pub const EFFECT_MASK_TABLE_WRITE : EffectMask = 512U
///|
pub const EFFECT_MASK_ATOMIC_READ : EffectMask = 1024U
///|
pub const EFFECT_MASK_ATOMIC_WRITE : EffectMask = 2048U
///|
pub const EFFECT_MASK_ACQUIRE : EffectMask = 4096U
///|
pub const EFFECT_MASK_RELEASE : EffectMask = 8192U
///|
pub const EFFECT_MASK_SEQ_CST : EffectMask = 16384U
///|
pub fn effects_atomic_order_mask(
order : @lib.AtomicOrder,
read~ : Bool,
write~ : Bool,
) -> EffectMask {
let mut mask = 0U
if read {
mask = mask | EFFECT_MASK_ATOMIC_READ
}
if write {
mask = mask | EFFECT_MASK_ATOMIC_WRITE
}
match order {
@lib.Relaxed => ()
@lib.AcqRel => {
if read {
mask = mask | EFFECT_MASK_ACQUIRE
}
if write {
mask = mask | EFFECT_MASK_RELEASE
}
}
@lib.SeqCst => mask = mask | EFFECT_MASK_SEQ_CST
}
mask
}
///|
// Whether the earlier expression must stay before the later expression due
// to synchronization. Alias and trap checks remain independently required.
// The v132 relaxed load/store edge applies even across disjoint heap classes.
pub fn effects_atomic_orders_conflict(
earlier : EffectMask,
later : EffectMask,
earlier_shared~ : Bool,
later_shared~ : Bool,
) -> Bool {
(
(earlier & EFFECT_MASK_ATOMIC_READ) != 0U &&
(later & EFFECT_MASK_ATOMIC_WRITE) != 0U
) ||
((earlier & EFFECT_MASK_ACQUIRE) != 0U && later_shared) ||
((later & EFFECT_MASK_RELEASE) != 0U && earlier_shared) ||
((earlier & EFFECT_MASK_SEQ_CST) != 0U && later_shared) ||
((later & EFFECT_MASK_SEQ_CST) != 0U && earlier_shared)
}
///|
fn effects_has(mask : EffectMask, bit : EffectMask) -> Bool {
(mask & bit) != 0U
}
///|
fn effects_mask_for_flags(flags : UInt32) -> EffectMask {
let mut mask = 0U
if (flags & HOT_FLAG_CONTROL) != 0U ||
(flags & HOT_FLAG_BRANCH) != 0U ||
(flags & HOT_FLAG_TERMINATOR) != 0U {
mask = mask | EFFECT_MASK_CONTROL
}
if (flags & HOT_FLAG_CALL) != 0U {
mask = mask | EFFECT_MASK_CALL | EFFECT_MASK_THROW
}
if (flags & HOT_FLAG_THROWS) != 0U ||
(flags & HOT_FLAG_HAS_EXCEPTIONAL_SUCC) != 0U {
mask = mask | EFFECT_MASK_THROW
}
if (flags & HOT_FLAG_MAY_TRAP) != 0U {
mask = mask | EFFECT_MASK_TRAP
}
mask
}
///|
fn effects_mask_for_atomic_exact(instr : Instruction?) -> EffectMask {
match instr {
Some(Instruction::AtomicFence(_)) =>
// The released fence is a barrier across shared memory, structs and
// arrays for every spelling. Match upstream's conservative SeqCst
// summary while preserving its encoded order as an exact instruction.
EFFECT_MASK_MEMORY_READ |
EFFECT_MASK_MEMORY_WRITE |
EFFECT_MASK_ATOMIC_READ |
EFFECT_MASK_ATOMIC_WRITE |
EFFECT_MASK_SEQ_CST
Some(Instruction::StructWait(_, _)) =>
EFFECT_MASK_MEMORY_READ |
EFFECT_MASK_MEMORY_WRITE |
EFFECT_MASK_TRAP |
EFFECT_MASK_CONTROL |
EFFECT_MASK_SEQ_CST |
EFFECT_MASK_ATOMIC_READ |
EFFECT_MASK_ATOMIC_WRITE
Some(Instruction::WaitqueueNotify) =>
EFFECT_MASK_MEMORY_READ |
EFFECT_MASK_MEMORY_WRITE |
EFFECT_MASK_TRAP |
EFFECT_MASK_SEQ_CST |
EFFECT_MASK_ATOMIC_READ |
EFFECT_MASK_ATOMIC_WRITE
Some(Instruction::MemoryAtomicNotify(_))
| Some(Instruction::MemoryAtomicWait32(_))
| Some(Instruction::MemoryAtomicWait64(_)) =>
// Shared wait/notify operations mutate the address's waiter list.
// The common summary has no module sharedness context, so retain the
// synchronization even where a more precise unshared-memory proof could
// classify the operation as just a trap or a constant result.
EFFECT_MASK_MEMORY_READ |
EFFECT_MASK_MEMORY_WRITE |
EFFECT_MASK_TRAP |
effects_atomic_order_mask(
@lib.AtomicOrder::seq_cst(),
read=true,
write=true,
)
Some(Instruction::I32AtomicLoad(order, _))
| Some(Instruction::I64AtomicLoad(order, _))
| Some(Instruction::I32AtomicLoad8U(order, _))
| Some(Instruction::I32AtomicLoad16U(order, _))
| Some(Instruction::I64AtomicLoad8U(order, _))
| Some(Instruction::I64AtomicLoad16U(order, _))
| Some(Instruction::I64AtomicLoad32U(order, _))
| Some(Instruction::StructAtomicGet(order, _, _))
| Some(Instruction::StructAtomicGetS(order, _, _))
| Some(Instruction::StructAtomicGetU(order, _, _))
| Some(Instruction::ArrayAtomicGet(order, _))
| Some(Instruction::ArrayAtomicGetS(order, _))
| Some(Instruction::ArrayAtomicGetU(order, _)) =>
EFFECT_MASK_MEMORY_READ |
EFFECT_MASK_TRAP |
effects_atomic_order_mask(order, read=true, write=false)
Some(Instruction::StructAtomicSet(order, _, _))
| Some(Instruction::ArrayAtomicSet(order, _)) =>
EFFECT_MASK_MEMORY_WRITE |
EFFECT_MASK_TRAP |
effects_atomic_order_mask(order, read=false, write=true)
Some(Instruction::I32AtomicStore(order, _))
| Some(Instruction::I64AtomicStore(order, _))
| Some(Instruction::I32AtomicStore8(order, _))
| Some(Instruction::I32AtomicStore16(order, _))
| Some(Instruction::I64AtomicStore8(order, _))
| Some(Instruction::I64AtomicStore16(order, _))
| Some(Instruction::I64AtomicStore32(order, _)) =>
EFFECT_MASK_MEMORY_WRITE |
EFFECT_MASK_TRAP |
effects_atomic_order_mask(order, read=false, write=true)
Some(Instruction::AtomicRmw(order, _, _))
| Some(Instruction::AtomicCmpxchg(order, _, _))
| Some(Instruction::StructAtomicRmw(order, _, _, _))
| Some(Instruction::StructAtomicCmpxchg(order, _, _))
| Some(Instruction::ArrayAtomicRmw(order, _, _))
| Some(Instruction::ArrayAtomicCmpxchg(order, _)) =>
EFFECT_MASK_MEMORY_READ |
EFFECT_MASK_MEMORY_WRITE |
EFFECT_MASK_TRAP |
effects_atomic_order_mask(order, read=true, write=true)
_ => EFFECT_MASK_MEMORY_READ | EFFECT_MASK_MEMORY_WRITE | EFFECT_MASK_TRAP
}
}
///|
fn effects_mask_for_numeric_exact(instr : Instruction?) -> EffectMask {
match instr {
Some(Instruction::I32DivS)
| Some(Instruction::I32DivU)
| Some(Instruction::I32RemS)
| Some(Instruction::I32RemU)
| Some(Instruction::I64DivS)
| Some(Instruction::I64DivU)
| Some(Instruction::I64RemS)
| Some(Instruction::I64RemU)
| Some(Instruction::I32TruncF32S)
| Some(Instruction::I32TruncF32U)
| Some(Instruction::I32TruncF64S)
| Some(Instruction::I32TruncF64U)
| Some(Instruction::I64TruncF32S)
| Some(Instruction::I64TruncF32U)
| Some(Instruction::I64TruncF64S)
| Some(Instruction::I64TruncF64U)
| Some(Instruction::RefAsNonNull) => EFFECT_MASK_TRAP
_ => 0U
}
}
///|
fn effects_mask_for_simd_exact(instr : Instruction?) -> EffectMask {
match instr {
Some(Instruction::V128Load(_))
| Some(Instruction::V128Load8x8S(_))
| Some(Instruction::V128Load8x8U(_))
| Some(Instruction::V128Load16x4S(_))
| Some(Instruction::V128Load16x4U(_))
| Some(Instruction::V128Load32x2S(_))
| Some(Instruction::V128Load32x2U(_))
| Some(Instruction::V128Load8Splat(_))
| Some(Instruction::V128Load16Splat(_))
| Some(Instruction::V128Load32Splat(_))
| Some(Instruction::V128Load64Splat(_))
| Some(Instruction::V128Load8Lane(_, _))
| Some(Instruction::V128Load16Lane(_, _))
| Some(Instruction::V128Load32Lane(_, _))
| Some(Instruction::V128Load64Lane(_, _))
| Some(Instruction::V128Load32Zero(_))
| Some(Instruction::V128Load64Zero(_)) =>
EFFECT_MASK_MEMORY_READ | EFFECT_MASK_TRAP
Some(Instruction::V128Store(_))
| Some(Instruction::V128Store8Lane(_, _))
| Some(Instruction::V128Store16Lane(_, _))
| Some(Instruction::V128Store32Lane(_, _))
| Some(Instruction::V128Store64Lane(_, _)) =>
EFFECT_MASK_MEMORY_WRITE | EFFECT_MASK_TRAP
_ => 0U
}
}
///|
fn effects_mask_for_string_exact(instr : Instruction?) -> EffectMask {
match instr {
Some(Instruction::StringEncodeUtf8Array)
| Some(Instruction::StringEncodeWtf16Array)
| Some(Instruction::StringEncodeLossyUtf8Array)
| Some(Instruction::StringEncodeWtf8Array) =>
EFFECT_MASK_MEMORY_WRITE | EFFECT_MASK_TRAP
Some(Instruction::StringNewUtf8Array)
| Some(Instruction::StringNewWtf16Array)
| Some(Instruction::StringNewLossyUtf8Array)
| Some(Instruction::StringNewWtf8Array) =>
EFFECT_MASK_MEMORY_READ | EFFECT_MASK_TRAP
Some(Instruction::StringEq) => 0U
_ => EFFECT_MASK_TRAP
}
}
///|
fn effects_mask_for_heap_exact(instr : Instruction?) -> EffectMask {
match instr {
Some(Instruction::StructGet(_, _))
| Some(Instruction::StructGetS(_, _))
| Some(Instruction::StructGetU(_, _))
| Some(Instruction::ArrayGet(_))
| Some(Instruction::ArrayGetS(_))
| Some(Instruction::ArrayGetU(_))
| Some(Instruction::ArrayLoad(_, _, _))
| Some(Instruction::ArrayLen)
| Some(Instruction::RefGetDesc(_)) =>
EFFECT_MASK_MEMORY_READ | EFFECT_MASK_TRAP
Some(Instruction::StructSet(_, _))
| Some(Instruction::ArraySet(_))
| Some(Instruction::ArrayStore(_, _, _))
| Some(Instruction::ArrayFill(_)) =>
EFFECT_MASK_MEMORY_WRITE | EFFECT_MASK_TRAP
Some(Instruction::WaitqueueNew)
| Some(Instruction::StructNew(_))
| Some(Instruction::ContNew(_))
| Some(Instruction::ContBind(_, _))
| Some(Instruction::StructNewDefault(_))
| Some(Instruction::StructNewDesc(_))
| Some(Instruction::StructNewDefaultDesc(_))
| Some(Instruction::ArrayNew(_))
| Some(Instruction::ArrayNewDefault(_))
| Some(Instruction::ArrayNewFixed(_, _))
| Some(Instruction::ArrayNewData(_, _))
| Some(Instruction::ArrayNewElem(_, _))
| Some(Instruction::ArrayCopy(_, _))
| Some(Instruction::ArrayInitData(_, _))
| Some(Instruction::ArrayInitElem(_, _)) =>
EFFECT_MASK_MEMORY_READ | EFFECT_MASK_MEMORY_WRITE | EFFECT_MASK_TRAP
Some(Instruction::RefAsNonNull)
| Some(Instruction::I31GetS)
| Some(Instruction::I31GetU) => EFFECT_MASK_TRAP
Some(Instruction::RefEq)
| Some(Instruction::AnyConvertExtern)
| Some(Instruction::ExternConvertAny)
| Some(Instruction::RefI31)
| Some(Instruction::RefI31Shared) => 0U
_ => EFFECT_MASK_TRAP
}
}
///|
fn effects_node_exact_instr_opt(
func : HotFunc,
node_id : NodeId,
) -> Instruction? {
if hot_verify_node_exact_instr_payload(func, node_id) {
hot_node_exact_instr(func, node_id)
} else {
None
}
}
///|
fn effects_mask_for_node_exact(func : HotFunc, node_id : NodeId) -> EffectMask {
let node = hot_node_get(func, node_id)
match node.op {
HotOp::LocalGet | HotOp::LocalSet | HotOp::LocalTee =>
EFFECT_MASK_LOCAL_STATE
HotOp::GlobalGet | HotOp::GlobalSet => EFFECT_MASK_GLOBAL_STATE
HotOp::Load => EFFECT_MASK_MEMORY_READ
HotOp::Store => EFFECT_MASK_MEMORY_WRITE
HotOp::MemorySize => EFFECT_MASK_MEMORY_READ
HotOp::MemoryGrow => EFFECT_MASK_MEMORY_READ | EFFECT_MASK_MEMORY_WRITE
HotOp::MemoryCopy | HotOp::MemoryInit =>
EFFECT_MASK_MEMORY_READ | EFFECT_MASK_MEMORY_WRITE
HotOp::MemoryFill | HotOp::DataDrop => EFFECT_MASK_MEMORY_WRITE
HotOp::TableGet | HotOp::TableSize => EFFECT_MASK_TABLE_READ
HotOp::TableSet | HotOp::TableGrow | HotOp::TableFill | HotOp::ElemDrop =>
EFFECT_MASK_TABLE_WRITE
HotOp::TableCopy | HotOp::TableInit =>
EFFECT_MASK_TABLE_READ | EFFECT_MASK_TABLE_WRITE
HotOp::Binary | HotOp::Unary | HotOp::Convert =>
effects_mask_for_numeric_exact(
effects_node_exact_instr_opt(func, node_id),
)
HotOp::Atomic =>
effects_mask_for_atomic_exact(effects_node_exact_instr_opt(func, node_id))
HotOp::Simd =>
effects_mask_for_simd_exact(effects_node_exact_instr_opt(func, node_id))
HotOp::StringOp =>
effects_mask_for_string_exact(effects_node_exact_instr_opt(func, node_id))
HotOp::Heap =>
effects_mask_for_heap_exact(effects_node_exact_instr_opt(func, node_id))
_ => 0U
}
}
///|
fn effects_accumulate_node_cached(
func : HotFunc,
node_id : NodeId,
node_masks : Array[EffectMask],
ready : @bitset.BitSet,
own_masks_ready~ : Bool = false,
) -> EffectMask {
if ready[node_id] {
return node_masks[node_id]
}
let mut mask = if own_masks_ready {
node_masks[node_id]
} else {
effects_mask_for_flags(hot_node_flags(func, node_id)) |
effects_mask_for_node_exact(func, node_id)
}
for slot = 0; slot < hot_child_count(func, node_id); slot = slot + 1 {
let child_id = hot_child_get(func, node_id, slot)
if child_id == HOT_NONE_NODE || !hot_node_is_live(func, child_id) {
continue
}
mask = mask |
effects_accumulate_node_cached(
func,
child_id,
node_masks,
ready,
own_masks_ready~,
)
}
node_masks[node_id] = mask
ready[node_id] = true
mask
}
///|
fn effects_build_node_masks(
func : HotFunc,
own_masks~ : Array[EffectMask]? = None,
) -> Array[EffectMask] {
// A supplied buffer is privately owned, covers this immutable arena, and
// starts with own-node contributions only. Consume it in place; completed
// subtree masks become visible exactly where the original walk published.
let node_masks = match own_masks {
Some(masks) => masks
None => Array::make(hot_node_count(func), 0U)
}
if node_masks.length() != hot_node_count(func) {
abort("own-effect masks must cover the complete hot node arena")
}
let own_masks_ready = own_masks is Some(_)
let ready = @bitset.BitSet::new(hot_node_count(func))
for node_id = 0; node_id < hot_node_count(func); node_id = node_id + 1 {
if !hot_node_is_live(func, node_id) {
continue
}
ignore(
effects_accumulate_node_cached(
func,
node_id,
node_masks,
ready,
own_masks_ready~,
),
)
}
node_masks
}
///|
pub fn effects_for_node(func : HotFunc, node_id : NodeId) -> EffectMask {
// A leaf has only its own effects. Avoid full-arena query scratch without
// retaining a revision-sensitive buffer or changing payload validation.
if hot_child_count(func, node_id) == 0 {
return effects_mask_for_flags(hot_node_flags(func, node_id)) |
effects_mask_for_node_exact(func, node_id)
}
effects_accumulate_node_cached(
func,
node_id,
Array::make(hot_node_count(func), 0U),
@bitset.BitSet::new(hot_node_count(func)),
)
}
///|
fn effects_for_block_with_node_masks(
cfg : HotCfg,
block_id : BlockId,
node_masks : Array[EffectMask],
) -> EffectMask {
let mut mask = 0U
for node_id in cfg_block_get(cfg, block_id).nodes {
mask = mask | node_masks[node_id]
}
mask
}
///|
pub fn effects_for_block(
func : HotFunc,
cfg : HotCfg,
block_id : BlockId,
) -> EffectMask {
effects_for_block_with_node_masks(
cfg,
block_id,
effects_build_node_masks(func),
)
}
///|
fn effects_for_region_with_node_masks(
func : HotFunc,
region_ref : HotRegionRef,
node_masks : Array[EffectMask],
) -> EffectMask {
let mut mask = 0U
for slot = 0; slot < hot_region_root_count(func, region_ref); slot = slot + 1 {
let node_id = hot_region_get(func, region_ref, slot)
if node_id == HOT_NONE_NODE || !hot_node_is_live(func, node_id) {
continue
}
mask = mask | node_masks[node_id]
}
mask
}
///|
pub fn effects_for_region(
func : HotFunc,
region_ref : HotRegionRef,
) -> EffectMask {
effects_for_region_with_node_masks(
func,
region_ref,
effects_build_node_masks(func),
)
}
///|
pub fn effects_is_pure(mask : EffectMask) -> Bool {
mask == 0U
}
///|
pub fn effects_may_throw(mask : EffectMask) -> Bool {
effects_has(mask, EFFECT_MASK_THROW)
}
///|
pub fn effects_reads_memory(mask : EffectMask) -> Bool {
effects_has(mask, EFFECT_MASK_MEMORY_READ)
}
///|
pub fn effects_writes_memory(mask : EffectMask) -> Bool {
effects_has(mask, EFFECT_MASK_MEMORY_WRITE)
}