///|
priv struct HotLocalScopeScan {
initialized : Array[Bool]
writes : Array[Int32]
needs_default : Array[Bool]
visited_frame : Array[Int32]
mut next_frame : Int32
}
///|
fn hot_local_scope_visit(
func : HotFunc,
node_id : NodeId,
frame : Int32,
scan : HotLocalScopeScan,
) -> Unit {
if node_id < 0 ||
!hot_node_is_live(func, node_id) ||
scan.visited_frame[node_id] == frame {
return
}
scan.visited_frame[node_id] = frame
let node = hot_node_get(func, node_id)
match node.op {
HotOp::Block | HotOp::Loop => {
// Loop arguments execute in the enclosing scope. Its body, like a
// block body, cannot export local initialization to that scope.
let base = hot_region_body_base(func, node_id)
for slot = 0; slot < base; slot = slot + 1 {
hot_local_scope_visit(
func,
hot_child_get(func, node_id, slot),
frame,
scan,
)
}
let saved = scan.writes.length()
scan.next_frame += 1
let child_frame = scan.next_frame
for slot = base; slot < node.child_count; slot = slot + 1 {
hot_local_scope_visit(
func,
hot_child_get(func, node_id, slot),
child_frame,
scan,
)
}
while scan.writes.length() > saved {
scan.initialized[scan.writes.pop().unwrap()] = false
}
return
}
_ => ()
}
// If and exception arm holders are Block regions, so each arm restores
// its entry state. Conditions and ordinary operands share the current frame.
for slot = 0; slot < node.child_count; slot = slot + 1 {
hot_local_scope_visit(func, hot_child_get(func, node_id, slot), frame, scan)
}
match node.op {
HotOp::LocalGet =>
if !scan.initialized[node.imm0] {
scan.needs_default[node.imm0] = true
}
HotOp::LocalSet | HotOp::LocalTee =>
if !scan.initialized[node.imm0] {
scan.initialized[node.imm0] = true
scan.writes.push(node.imm0)
}
_ => ()
}
}
///|
/// Preserve result types when code motion moves a local read outside the
/// control scope that initialized its non-defaultable reference storage.
/// Call only after a semantics-preserving transform on a valid input.
pub fn hot_repair_nondefaultable_local_scopes(func : HotFunc) -> Bool {
let initialized = Array::make(hot_local_count(func), true)
let mut has_nondefaultable = false
for local_id = hot_local_param_count(func)
local_id < hot_local_count(func)
local_id = local_id + 1 {
match hot_local_type(func, local_id) {
Some(RefTypeValType(ref_type)) if ref_type.is_non_nullable() => {
initialized[local_id] = false
has_nondefaultable = true
}
_ => ()
}
}
if !has_nondefaultable {
return false
}
let node_count = hot_node_count(func)
let scan : HotLocalScopeScan = {
initialized,
writes: [],
needs_default: Array::make(hot_local_count(func), false),
visited_frame: Array::make(node_count, -1),
next_frame: 0,
}
for slot = 0; slot < hot_root_count(func); slot = slot + 1 {
hot_local_scope_visit(func, hot_root_get(func, slot), 0, scan)
}
let mut changed = false
for local_id, needs_default in scan.needs_default {
if needs_default {
if !hot_weaken_body_local_to_nullable_ref(func, local_id) {
abort("local scope repair requires a non-defaultable body local")
}
changed = true
}
}
if !changed {
return false
}
for node_id = 0; node_id < node_count; node_id = node_id + 1 {
if !hot_node_is_live(func, node_id) {
continue
}
let node = hot_node_get(func, node_id)
match node.op {
HotOp::LocalGet | HotOp::LocalTee if scan.needs_default[node.imm0] => {
match hot_type_results(func, node.ty) {
[RefTypeValType(ref_type)] if ref_type.is_non_nullable() => ()
_ =>
abort(
"local scope repair expected one non-null reference result at node \{node_id}",
)
}
let storage_type = hot_local_type(func, node.imm0).unwrap()
let nullable_type = hot_type_intern_value(func, storage_type)
let copy = hot_alloc_node(func, { ..node, ty: nullable_type, })
// This is the same read/write at its old execution point, not a new
// effect after later roots. Preserve the source position on both nodes.
func.nodes[copy] = { ..func.nodes[copy], order: node.order, }
ignore(hot_replace_child_span(func, node_id, [copy]))
hot_node_set(func, node_id, {
..hot_node_get(func, node_id),
op: HotOp::Unary,
flags: hot_default_flags_for_op(HotOp::Unary),
imm0: 0,
imm1: 0,
})
hot_node_exact_instr_set(func, node_id, Instruction::ref_as_non_null())
}
_ => ()
}
}
true
}