///|
pub enum HotVerifyError {
InvalidBodyResultType(TypeId)
MissingTypeKey(TypeId, String)
MismatchedTypeKey(TypeId, String, TypeId)
InvalidRoot(Int32, NodeId)
InvalidNodeType(NodeId, TypeId)
InvalidNodeFlags(NodeId, UInt32, UInt32)
InvalidChildSpan(NodeId, Int32, Int32)
InvalidChildRef(NodeId, Int32, NodeId)
InvalidLabelResultType(LabelId, TypeId)
InvalidLabelOwner(LabelId, NodeId)
InvalidLabelOwnerKind(LabelId, HotLabelKind, HotOp)
InvalidControlLabel(NodeId, LabelId)
InvalidSideTableRef(NodeId, HotSideTableKind, SideTableId)
InvalidControlChildCount(NodeId, Int32, Int32)
InvalidControlChild(NodeId, Int32, NodeId)
InvalidCatchPayload(NodeId, String)
InvalidBranchTarget(NodeId, LabelId)
InvalidBranchArity(NodeId, LabelId, Int32, Int32)
InvalidCfgEntry(BlockId)
InvalidCfgExit(BlockId)
InvalidCfgBlockId(BlockId, BlockId)
InvalidCfgEdge(BlockId, CfgEdge)
InvalidCfgEdgeSymmetry(BlockId, CfgEdge)
InvalidCfgNodeMapping(NodeId, BlockId)
InvalidCfgBlockNode(BlockId, NodeId)
InvalidCfgRegionRoot(NodeId, Int32, BlockId)
InvalidCfgShape(String, Int32, Int32)
InvalidDomEntry(BlockId, BlockId)
InvalidDomShape(String, Int32, Int32)
InvalidDomReachable(BlockId)
InvalidDomIdom(BlockId, BlockId)
InvalidDomChild(BlockId, BlockId)
InvalidDomFrontier(BlockId, BlockId)
InvalidSsaRevision(Int32, Int32)
InvalidSsaShape(String, Int32, Int32)
InvalidSsaValue(SsaValueId)
InvalidSsaPhi(PhiId)
InvalidSsaLocal(LocalId)
InvalidSsaNode(NodeId)
InvalidSsaValueOrigin(SsaValueId, HotSsaValueOrigin)
InvalidSsaPhiInput(PhiId, BlockId, SsaValueId)
InvalidSsaPhiAlignment(PhiId, BlockId, Int32, Int32)
InvalidSsaType(SsaValueId, ValType, ValType)
InvalidSsaUse(SsaValueId, HotSsaUseOrigin)
InvalidSsaDominance(SsaValueId, BlockId, BlockId)
InvalidAnalysisOwner
InvalidAnalysisRevision(HotAnalysis, Int32, Int32)
InvalidAnalysisDependency(HotAnalysis, HotAnalysis)
} derive(Eq, Debug)
///|
fn hot_verify_type_id_is_valid(func : HotFunc, ty : TypeId) -> Bool {
ty >= 0 && ty < func.types.length()
}
///|
fn hot_verify_label_id_is_valid(func : HotFunc, label_id : LabelId) -> Bool {
hot_label_is_live(func, label_id)
}
///|
fn hot_verify_side_table_id_is_valid(
func : HotFunc,
kind : HotSideTableKind,
side_table_id : SideTableId,
) -> Bool {
if side_table_id < 0 {
return false
}
match kind {
HotSideTableKind::ConstPayloadTable => side_table_id < func.consts.length()
HotSideTableKind::MemArgTable => side_table_id < func.memargs.length()
HotSideTableKind::BranchTableTable =>
side_table_id < func.branch_tables.length()
HotSideTableKind::CatchInfoTable => side_table_id < func.catches.length()
HotSideTableKind::CallSigTable => side_table_id < func.call_sigs.length()
HotSideTableKind::InstrPayloadTable =>
side_table_id < func.instr_payloads.length()
}
}
///|
fn hot_verify_region_holder(func : HotFunc, node_id : NodeId) -> Bool {
if !hot_node_is_live(func, node_id) {
return false
}
let node = hot_node_get(func, node_id)
if node.op != HotOp::Block {
return false
}
let label_id = hot_control_node_label(func, node_id)
hot_verify_label_id_is_valid(func, label_id) &&
hot_label_kind(func, label_id) == HotLabelKind::RegionLabel
}
///|
fn hot_verify_branch_payload_arity(func : HotFunc, node_id : NodeId) -> Int32? {
let node = hot_node_get(func, node_id)
match node.op {
HotOp::Br | HotOp::Delegate => Some(node.child_count)
HotOp::BrOnNonNull =>
if node.child_count == 0 {
None
} else {
Some(node.child_count)
}
HotOp::BrOnCast | HotOp::BrOnCastFail =>
if node.child_count == 0 {
None
} else {
// Descriptor casts carry an additional descriptor guard after the
// tested reference; only the reference contributes to branch arity.
match hot_node_exact_instr(func, node_id) {
Some(@lib.BrOnCastDescEq(_, _, _))
| Some(@lib.BrOnCastDescEqFail(_, _, _)) =>
if node.child_count < 2 {
None
} else {
Some(node.child_count - 1)
}
_ => Some(node.child_count)
}
}
HotOp::BrIf | HotOp::BrTable | HotOp::BrOnNull =>
if node.child_count == 0 {
None
} else {
Some(node.child_count - 1)
}
_ => Some(0)
}
}
///|
fn hot_verify_control_label_kind(
func : HotFunc,
node_id : NodeId,
node : HotNode,
) -> Result[Unit, HotVerifyError] {
let label_id = hot_control_node_label(func, node_id)
if !hot_verify_label_id_is_valid(func, label_id) {
return Err(HotVerifyError::InvalidControlLabel(node_id, label_id))
}
let expected = match node.op {
HotOp::Block => None
HotOp::Loop => Some(HotLabelKind::LoopLabel)
HotOp::If => Some(HotLabelKind::IfLabel)
HotOp::Try => Some(HotLabelKind::TryLabel)
HotOp::TryTable => Some(HotLabelKind::TryTableLabel)
_ => None
}
match expected {
Some(kind) if hot_label_kind(func, label_id) != kind =>
Err(HotVerifyError::InvalidLabelOwnerKind(label_id, kind, node.op))
_ => Ok(())
}
}
///|
fn hot_verify_branch_target(
func : HotFunc,
node_id : NodeId,
target : LabelId,
actual_arity : Int32,
) -> Result[Unit, HotVerifyError] {
if target != HOT_IMPLICIT_FUNCTION_LABEL &&
!hot_verify_label_id_is_valid(func, target) {
return Err(HotVerifyError::InvalidBranchTarget(node_id, target))
}
let expected_arity = if hot_node_get(func, node_id).op == HotOp::Delegate {
// A legacy delegate transfers an exception to the enclosing handler.
// It does not branch with the target label's ordinary result values.
0
} else if target == HOT_IMPLICIT_FUNCTION_LABEL {
hot_type_result_arity(func, hot_body_result_type(func))
} else {
hot_label_branch_arity(func, target)
}
if actual_arity != expected_arity {
return Err(
HotVerifyError::InvalidBranchArity(
node_id, target, actual_arity, expected_arity,
),
)
}
Ok(())
}
///|
fn hot_verify_branch_targets(
func : HotFunc,
node_id : NodeId,
targets : Array[LabelId],
actual_arity : Int32,
) -> Result[Unit, HotVerifyError] {
for target in targets {
match hot_verify_branch_target(func, node_id, target, actual_arity) {
Ok(_) => ()
Err(err) => return Err(err)
}
}
Ok(())
}
///|
pub fn hot_verify_core(func : HotFunc) -> Result[Unit, HotVerifyError] {
hot_verify_core_impl(func)
}
///|
fn hot_verify_core_impl(
func : HotFunc,
postorder~ : Array[NodeId]? = None,
) -> Result[Unit, HotVerifyError] {
if !hot_verify_type_id_is_valid(func, hot_body_result_type(func)) {
return Err(
HotVerifyError::InvalidBodyResultType(hot_body_result_type(func)),
)
}
for type_id = 0; type_id < func.types.length(); type_id = type_id + 1 {
let key = func.types[type_id].to_string()
match func.type_ids.get(key) {
Some(mapped) if mapped == type_id => ()
Some(mapped) =>
return Err(HotVerifyError::MismatchedTypeKey(type_id, key, mapped))
None => return Err(HotVerifyError::MissingTypeKey(type_id, key))
}
}
for root_idx = 0; root_idx < func.roots.length(); root_idx = root_idx + 1 {
let root_id = func.roots[root_idx]
if !hot_node_is_live(func, root_id) {
return Err(HotVerifyError::InvalidRoot(root_idx, root_id))
}
}
for label_id = 0; label_id < func.labels.length(); label_id = label_id + 1 {
if !hot_label_is_live(func, label_id) {
continue
}
let label = func.labels[label_id]
if !hot_verify_type_id_is_valid(func, label.result_type) {
return Err(
HotVerifyError::InvalidLabelResultType(label_id, label.result_type),
)
}
if !hot_node_is_live(func, label.owner) {
return Err(HotVerifyError::InvalidLabelOwner(label_id, label.owner))
}
let owner = hot_node_get(func, label.owner)
if hot_control_node_label(func, label.owner) != label_id {
return Err(HotVerifyError::InvalidLabelOwner(label_id, label.owner))
}
match label.kind {
HotLabelKind::BlockLabel if owner.op != HotOp::Block =>
return Err(
HotVerifyError::InvalidLabelOwnerKind(label_id, label.kind, owner.op),
)
HotLabelKind::LoopLabel if owner.op != HotOp::Loop =>
return Err(
HotVerifyError::InvalidLabelOwnerKind(label_id, label.kind, owner.op),
)
HotLabelKind::IfLabel if owner.op != HotOp::If =>
return Err(
HotVerifyError::InvalidLabelOwnerKind(label_id, label.kind, owner.op),
)
HotLabelKind::TryLabel if owner.op != HotOp::Try =>
return Err(
HotVerifyError::InvalidLabelOwnerKind(label_id, label.kind, owner.op),
)
HotLabelKind::TryTableLabel if owner.op != HotOp::TryTable =>
return Err(
HotVerifyError::InvalidLabelOwnerKind(label_id, label.kind, owner.op),
)
HotLabelKind::RegionLabel if owner.op != HotOp::Block =>
return Err(
HotVerifyError::InvalidLabelOwnerKind(label_id, label.kind, owner.op),
)
_ => ()
}
}
for node_id = 0; node_id < func.nodes.length(); node_id = node_id + 1 {
if !hot_node_is_live(func, node_id) {
continue
}
let node = func.nodes[node_id]
if !hot_verify_type_id_is_valid(func, node.ty) {
return Err(HotVerifyError::InvalidNodeType(node_id, node.ty))
}
let expected_flags = hot_flags_for_exact_payload(func, node.op, node.imm0)
if node.flags != expected_flags {
return Err(
HotVerifyError::InvalidNodeFlags(node_id, node.flags, expected_flags),
)
}
if node.child_base < 0 ||
node.child_count < 0 ||
node.child_base + node.child_count > func.children.length() {
return Err(
HotVerifyError::InvalidChildSpan(
node_id,
node.child_base,
node.child_count,
),
)
}
for slot = 0; slot < node.child_count; slot = slot + 1 {
let child_id = func.children[node.child_base + slot]
if child_id == HOT_NONE_NODE {
continue
}
if !hot_node_is_live(func, child_id) {
return Err(HotVerifyError::InvalidChildRef(node_id, slot, child_id))
}
}
match hot_side_table_kind_for_op(node.op) {
Some(kind) => {
let skip_side_table = match node.op {
HotOp::Atomic =>
if node.imm1 >= 0 && node.imm1 < func.instr_payloads.length() {
match hot_instr_payload_get(func, node.imm1).instr {
Instruction::AtomicFence(_)
| Instruction::WaitqueueNotify
| Instruction::StructWait(_, _)
| Instruction::StructAtomicSet(_, _, _)
| Instruction::ArrayAtomicSet(_, _)
| Instruction::StructAtomicGet(_, _, _)
| Instruction::StructAtomicGetS(_, _, _)
| Instruction::StructAtomicGetU(_, _, _)
| Instruction::StructAtomicRmw(_, _, _, _)
| Instruction::StructAtomicCmpxchg(_, _, _)
| Instruction::ArrayAtomicGet(_, _)
| Instruction::ArrayAtomicGetS(_, _)
| Instruction::ArrayAtomicGetU(_, _)
| Instruction::ArrayAtomicRmw(_, _, _)
| Instruction::ArrayAtomicCmpxchg(_, _) => true
_ => false
}
} else {
false
}
_ => false
}
if !skip_side_table {
let side_table_id = if node.op == HotOp::TryTable ||
node.op == HotOp::Continuation {
node.imm1
} else {
node.imm0
}
if !hot_verify_side_table_id_is_valid(func, kind, side_table_id) {
return Err(
HotVerifyError::InvalidSideTableRef(node_id, kind, side_table_id),
)
}
}
}
None => ()
}
if !hot_verify_node_exact_instr_payload(func, node_id) {
let payload_id = match hot_node_exact_instr_payload_id(func, node_id) {
Some(id) => id
None => -1
}
return Err(
HotVerifyError::InvalidSideTableRef(
node_id,
HotSideTableKind::InstrPayloadTable,
payload_id,
),
)
}
}
hot_verify_child_graph(func, postorder)
}
///|
// Label backedges are not child edges. The owned expression graph must remain
// acyclic, including live detached nodes that later edits can reattach.
fn hot_verify_child_graph(
func : HotFunc,
postorder : Array[NodeId]?,
) -> Result[Unit, HotVerifyError] {
// Core admission has checked every live node span and child reference.
// This traversal only reads the arena, including live detached nodes.
let marks = Array::make(hot_node_count(func), b'\x00')
let nodes : Array[NodeId] = []
let slots : Array[Int32] = []
for start in 0.. HotVerifyLabelScopes {
let mut levels = 1
let mut size = count
while size > 1 {
levels += 1
size = size / 2 + size % 2
}
{
parents: Array::make(count, -1),
depths: Array::make(count, -1),
levels,
jumps: None,
}
}
///|
// Ordinary trees need no jump storage. A shared node with different enclosing
// scopes uses their nearest common scope, with bounded logarithmic merge work.
fn HotVerifyLabelScopes::jump(
self : HotVerifyLabelScopes,
label : LabelId,
level : Int32,
) -> LabelId {
if label < 0 {
return -1
}
if level == 0 {
return self.parents[label]
}
let rows = match self.jumps {
Some(rows) => rows
None => {
let rows = Array::make(self.parents.length() * self.levels, -3)
self.jumps = Some(rows)
rows
}
}
let key = label * self.levels + level
if rows[key] == -3 {
let halfway = self.jump(label, level - 1)
rows[key] = self.jump(halfway, level - 1)
}
rows[key]
}
///|
fn HotVerifyLabelScopes::common(
self : HotVerifyLabelScopes,
first : LabelId,
second : LabelId,
) -> LabelId {
if first == second || first == -3 {
return second
}
if second == -3 {
return first
}
if first < 0 || second < 0 {
return -1
}
let mut left = first
let mut right = second
if self.depths[left] < self.depths[right] {
left = second
right = first
}
let mut difference = self.depths[left] - self.depths[right]
let mut level = 0
while difference > 0 {
if difference % 2 == 1 {
left = self.jump(left, level)
}
difference /= 2
level += 1
}
if left == right {
return left
}
for power = self.levels - 1; power >= 0; power = power - 1 {
let lhs = self.jump(left, power)
let rhs = self.jump(right, power)
if lhs != rhs {
left = lhs
right = rhs
}
}
self.parents[left]
}
///|
fn hot_verify_scope_body_slot(
func : HotFunc,
node : HotNode,
slot : Int32,
) -> Bool {
match node.op {
HotOp::Block => slot >= node.imm1
HotOp::Loop => slot >= hot_label_branch_arity(func, node.imm0)
HotOp::If => slot == 1 || slot == 2
HotOp::Try | HotOp::TryTable => true
_ => false
}
}
///|
fn hot_verify_branch_scopes(
func : HotFunc,
postorder : Array[NodeId],
) -> Result[Unit, HotVerifyError] {
// Successful core verification supplies a live postorder and valid spans.
// No node or child storage changes while lexical scopes are checked.
let contexts = Array::make(hot_node_count(func), -3)
for root in func.roots {
contexts[root] = -1
}
let scopes = HotVerifyLabelScopes::new(func.labels.length())
// Reverse child postorder processes every predecessor before a shared node.
// Its context is the intersection of its incoming lexical scope chains.
for index = postorder.length() - 1; index >= 0; index = index - 1 {
let node_id = postorder[index]
let context = contexts[node_id]
if context == -3 {
continue
}
let node = func.nodes[node_id]
let label = match node.op {
HotOp::Block | HotOp::Loop | HotOp::If | HotOp::Try | HotOp::TryTable =>
node.imm0
_ => -1
}
let active = if label >= 0 &&
hot_label_kind(func, label) != HotLabelKind::RegionLabel {
scopes.parents[label] = context
scopes.depths[label] = if context < 0 {
1
} else {
scopes.depths[context] + 1
}
label
} else {
context
}
for slot in 0..= 0 {
let parent = scopes.parents[label] + 1
next[label] = heads[parent]
heads[parent] = label
}
}
let starts = Array::make(count, -1)
let ends = Array::make(count, -1)
let stack : Array[Int32] = []
let mut root = heads[0]
let mut clock = 0
while root >= 0 || !stack.is_empty() {
if stack.is_empty() {
stack.push(root)
root = next[root]
}
let item = stack.pop().unwrap()
if item < 0 {
ends[-item - 1] = clock
continue
}
starts[item] = clock
clock += 1
stack.push(-item - 1)
let mut child = heads[item + 1]
while child >= 0 {
stack.push(child)
child = next[child]
}
}
fn check(node_id : NodeId, target : LabelId) -> Result[Unit, HotVerifyError] {
if target == HOT_IMPLICIT_FUNCTION_LABEL {
return Ok(())
}
let context = contexts[node_id]
if func.nodes[node_id].op == HotOp::Delegate && context == target {
return Err(HotVerifyError::InvalidBranchTarget(node_id, target))
}
if context < 0 ||
starts[target] < 0 ||
starts[target] > starts[context] ||
ends[context] > ends[target] {
return Err(HotVerifyError::InvalidBranchTarget(node_id, target))
}
Ok(())
}
for node_id in postorder {
if contexts[node_id] == -3 {
continue
}
let node = func.nodes[node_id]
match node.op {
HotOp::BrTable => {
for target in hot_branch_table_targets_for_node(func, node_id) {
match check(node_id, target) {
Ok(_) => ()
Err(err) => return Err(err)
}
}
match check(node_id, hot_branch_table_default_target(func, node.imm0)) {
Ok(_) => ()
Err(err) => return Err(err)
}
}
HotOp::Continuation =>
for target in hot_branch_table_targets(func, node.imm1) {
match check(node_id, target) {
Ok(_) => ()
Err(err) => return Err(err)
}
}
HotOp::TryTable =>
for arm in hot_catch_info_get(func, node.imm1).catches {
let target = match arm {
HotCatchArm::CatchArm(_, label)
| HotCatchArm::CatchRefArm(_, label)
| HotCatchArm::CatchAllArm(label)
| HotCatchArm::CatchAllRefArm(label) => label
}
// try_table catch targets are resolved in the enclosing scope,
// before the try_table's own label is pushed.
match check(node_id, target) {
Ok(_) => ()
Err(err) => return Err(err)
}
}
HotOp::Br
| HotOp::BrIf
| HotOp::BrOnNull
| HotOp::BrOnNonNull
| HotOp::BrOnCast
| HotOp::BrOnCastFail
| HotOp::Delegate =>
match check(node_id, node.imm0) {
Ok(_) => ()
Err(err) => return Err(err)
}
_ => ()
}
}
Ok(())
}
///|
fn hot_verify_catch_payload_layout(
func : HotFunc,
) -> Result[Unit, HotVerifyError] {
let admitted = Array::make(func.nodes.length(), false)
for try_id = 0; try_id < func.nodes.length(); try_id = try_id + 1 {
if !hot_node_is_live(func, try_id) ||
hot_query_op(func, try_id) != HotOp::Try {
continue
}
let catch_region = hot_child_get(func, try_id, 1)
if catch_region == HOT_NONE_NODE ||
!hot_verify_region_holder(func, catch_region) {
continue
}
let catch_ref = hot_region_body_ref(func, catch_region)
let mut saw_non_payload = false
let mut tag = -1
for slot = 0; slot < hot_region_root_count(func, catch_ref); slot = slot + 1 {
let root_id = hot_region_get(func, catch_ref, slot)
if hot_query_op(func, root_id) == HotOp::Catch {
if saw_non_payload {
return Err(
HotVerifyError::InvalidCatchPayload(
root_id, "typed catch payload must precede every catch-body root",
),
)
}
let node = hot_node_get(func, root_id)
if node.child_count != 0 || hot_node_result_arity(func, root_id) != 1 {
return Err(
HotVerifyError::InvalidCatchPayload(
root_id, "typed catch payload must be a childless scalar value",
),
)
}
if tag >= 0 && tag != node.imm0 {
return Err(
HotVerifyError::InvalidCatchPayload(
root_id, "one catch region cannot mix payload tags",
),
)
}
if admitted[root_id] {
return Err(
HotVerifyError::InvalidCatchPayload(
root_id, "typed catch payload must have one catch-region entry owner",
),
)
}
tag = node.imm0
admitted[root_id] = true
} else {
saw_non_payload = true
}
}
}
for node_id = 0; node_id < func.nodes.length(); node_id = node_id + 1 {
if hot_node_is_live(func, node_id) &&
hot_query_op(func, node_id) == HotOp::Catch &&
!admitted[node_id] {
return Err(
HotVerifyError::InvalidCatchPayload(
node_id, "typed catch payload must be a leading legacy catch-region root",
),
)
}
}
Ok(())
}
///|
fn hot_verify_explicit_block_prefix(
func : HotFunc,
id : NodeId,
node : HotNode,
) -> Result[Unit, HotVerifyError] {
if node.imm1 < 0 || node.imm1 > node.child_count {
return Err(
HotVerifyError::InvalidControlChildCount(id, node.child_count, node.imm1),
)
}
if node.imm1 == 0 {
return Ok(())
}
let params = match hot_type_get(func, node.ty) {
BlockResultResolved(_, params, _) => params
_ => return Err(HotVerifyError::InvalidNodeType(id, node.ty))
}
let mut param = 0
let mut slot = 0
while slot < node.imm1 {
let child = hot_child_get(func, id, slot)
let types = hot_type_results(func, hot_node_type(func, child))
let mut end = slot + 1
while end < node.imm1 && hot_child_get(func, id, end) == child {
end += 1
}
let lanes = end - slot
if types.is_empty() {
if lanes != 1 {
return Err(HotVerifyError::InvalidControlChild(id, slot, child))
}
} else {
if lanes > types.length() || param + lanes > params.length() {
return Err(HotVerifyError::InvalidControlChild(id, slot, child))
}
for lane in 0.. env
None => @validate.empty_env()
}
let matches = @validate.Match::matches(actual, expected, env)
if !matches {
return Err(
HotVerifyError::InvalidControlChild(id, slot + lane, child),
)
}
}
}
param += lanes
}
slot = end
}
if param != params.length() {
return Err(
HotVerifyError::InvalidControlChildCount(id, param, params.length()),
)
}
Ok(())
}
///|
pub fn hot_verify_control(func : HotFunc) -> Result[Unit, HotVerifyError] {
hot_verify_control_impl(func)
}
///|
fn hot_verify_control_impl(
func : HotFunc,
catch_layout_calls~ : Array[Int32]? = None,
core_verification_calls~ : Array[Int32]? = None,
) -> Result[Unit, HotVerifyError] {
match core_verification_calls {
Some(calls) => calls[0] = calls[0] + 1
None => ()
}
let needs_scopes = func.nodes.any(fn(node) {
node.op is HotOp::Br ||
node.op is HotOp::BrIf ||
node.op is HotOp::BrTable ||
node.op is HotOp::BrOnNull ||
node.op is HotOp::BrOnNonNull ||
node.op is HotOp::BrOnCast ||
node.op is HotOp::BrOnCastFail ||
node.op is HotOp::Delegate ||
node.op is HotOp::Continuation ||
node.op is HotOp::TryTable
})
let order : Array[NodeId] = []
match
hot_verify_core_impl(
func,
postorder=if needs_scopes { Some(order) } else { None },
) {
Ok(_) => ()
Err(err) => return Err(err)
}
// Core verification above admits all live nodes, spans, and child refs.
// Keep control-shape and sentinel checks in their original order.
let mut needs_catch_layout = false
for node_id = 0; node_id < func.nodes.length(); node_id = node_id + 1 {
if !hot_node_is_live(func, node_id) {
continue
}
let node = func.nodes[node_id]
match node.op {
HotOp::Block | HotOp::Loop | HotOp::If | HotOp::Try | HotOp::TryTable =>
match hot_verify_control_label_kind(func, node_id, node) {
Ok(_) => ()
Err(err) => return Err(err)
}
_ => ()
}
match node.op {
HotOp::Block | HotOp::Loop => {
for slot = 0; slot < node.child_count; slot = slot + 1 {
let child_id = func.children[node.child_base + slot]
if child_id == HOT_NONE_NODE {
return Err(
HotVerifyError::InvalidControlChild(node_id, slot, child_id),
)
}
}
if node.op == HotOp::Block {
match hot_verify_explicit_block_prefix(func, node_id, node) {
Ok(_) => ()
Err(err) => return Err(err)
}
}
}
HotOp::If => {
let entry_count = match hot_type_get(func, node.ty) {
BlockResultResolved(_, params, _) => params.length()
_ => 0
}
if node.child_count != 3 + entry_count {
return Err(
HotVerifyError::InvalidControlChildCount(
node_id,
node.child_count,
3 + entry_count,
),
)
}
let condition_id = func.children[node.child_base]
if condition_id == HOT_NONE_NODE ||
!hot_node_is_live(func, condition_id) {
return Err(
HotVerifyError::InvalidControlChild(node_id, 0, condition_id),
)
}
for slot in 3.. {
needs_catch_layout = true
if node.child_count != 2 {
return Err(
HotVerifyError::InvalidControlChildCount(
node_id,
node.child_count,
2,
),
)
}
for slot = 0; slot < 2; slot = slot + 1 {
let child_id = func.children[node.child_base + slot]
if slot == 1 && child_id == HOT_NONE_NODE {
continue
}
if !hot_verify_region_holder(func, child_id) {
return Err(
HotVerifyError::InvalidControlChild(node_id, slot, child_id),
)
}
}
}
HotOp::Continuation => {
for target in hot_continuation_targets(func, node_id) {
if target != HOT_IMPLICIT_FUNCTION_LABEL &&
!hot_verify_label_id_is_valid(func, target) {
return Err(HotVerifyError::InvalidBranchTarget(node_id, target))
}
}
let expected = hot_continuation_handlers(
hot_node_exact_instr(func, node_id).unwrap(),
)
.filter(fn(handler) { handler is @lib.ResumeOnLabel(_, _) })
.length()
if expected != hot_continuation_targets(func, node_id).length() {
return Err(
HotVerifyError::InvalidCatchPayload(
node_id, "continuation handler target count",
),
)
}
}
HotOp::Catch => needs_catch_layout = true
HotOp::TryTable => {
if node.child_count != 2 {
return Err(
HotVerifyError::InvalidControlChildCount(
node_id,
node.child_count,
2,
),
)
}
for slot = 0; slot < 2; slot = slot + 1 {
let child_id = func.children[node.child_base + slot]
if !hot_verify_region_holder(func, child_id) {
return Err(
HotVerifyError::InvalidControlChild(node_id, slot, child_id),
)
}
}
let catch_info = hot_catch_info_get(func, node.imm1)
for catch_arm in catch_info.catches {
let target = match catch_arm {
HotCatchArm::CatchArm(_, label_id)
| HotCatchArm::CatchRefArm(_, label_id)
| HotCatchArm::CatchAllArm(label_id)
| HotCatchArm::CatchAllRefArm(label_id) => label_id
}
if target != HOT_IMPLICIT_FUNCTION_LABEL &&
!hot_verify_label_id_is_valid(func, target) {
return Err(HotVerifyError::InvalidBranchTarget(node_id, target))
}
}
}
HotOp::Br
| HotOp::BrIf
| HotOp::BrTable
| HotOp::BrOnNull
| HotOp::BrOnNonNull
| HotOp::BrOnCast
| HotOp::BrOnCastFail
| HotOp::Delegate => {
let actual_arity = match
hot_verify_branch_payload_arity(func, node_id) {
Some(arity) => arity
None =>
return Err(
HotVerifyError::InvalidControlChildCount(
node_id,
node.child_count,
1,
),
)
}
match node.op {
HotOp::Br =>
match
hot_verify_branch_target(func, node_id, node.imm0, actual_arity) {
Ok(_) => ()
Err(err) => return Err(err)
}
HotOp::Delegate =>
match
hot_verify_branch_target(func, node_id, node.imm0, actual_arity) {
Ok(_) => ()
Err(err) => return Err(err)
}
HotOp::BrIf
| HotOp::BrOnNull
| HotOp::BrOnNonNull
| HotOp::BrOnCast
| HotOp::BrOnCastFail => {
let guard_id = func.children[node.child_base + node.child_count - 1]
if guard_id == HOT_NONE_NODE || !hot_node_is_live(func, guard_id) {
return Err(
HotVerifyError::InvalidControlChild(
node_id,
node.child_count - 1,
guard_id,
),
)
}
match
hot_verify_branch_target(func, node_id, node.imm0, actual_arity) {
Ok(_) => ()
Err(err) => return Err(err)
}
}
HotOp::BrTable => {
let selector_id = func.children[node.child_base +
node.child_count -
1]
if selector_id == HOT_NONE_NODE ||
!hot_node_is_live(func, selector_id) {
return Err(
HotVerifyError::InvalidControlChild(
node_id,
node.child_count - 1,
selector_id,
),
)
}
let targets = hot_branch_table_targets_for_node(func, node_id)
let all_targets = targets.copy()
all_targets.push(hot_branch_table_default_target(func, node.imm0))
match
hot_verify_branch_targets(
func, node_id, all_targets, actual_arity,
) {
Ok(_) => ()
Err(err) => return Err(err)
}
}
_ => ()
}
}
_ => ()
}
}
// The layout checker only examines live Try/Catch nodes. This flag comes
// from the complete control scan, so the remaining check is vacuous when
// neither operation exists. Live orphan payloads still take the full path.
if needs_catch_layout {
match catch_layout_calls {
Some(calls) => calls[0] = calls[0] + 1
None => ()
}
match hot_verify_catch_payload_layout(func) {
Ok(_) => ()
Err(err) => return Err(err)
}
}
if needs_scopes {
hot_verify_branch_scopes(func, order)
} else {
Ok(())
}
}
///|
fn hot_verify_block_id_is_valid(cfg : HotCfg, block_id : BlockId) -> Bool {
block_id >= 0 && block_id < cfg_block_count(cfg)
}
///|
fn hot_verify_cfg_edge_has_symmetry(
cfg : HotCfg,
from : BlockId,
edge : CfgEdge,
) -> Bool {
for pred in cfg_predecessor_edges(cfg, edge.block) {
if pred.block == from && pred.kind == edge.kind {
return true
}
}
false
}
///|
fn hot_verify_cfg_pred_has_symmetry(
cfg : HotCfg,
to : BlockId,
edge : CfgEdge,
) -> Bool {
for succ in cfg_successor_edges(cfg, edge.block) {
if succ.block == to && succ.kind == edge.kind {
return true
}
}
false
}
///|
fn hot_verify_cfg_block_contains_node(
cfg : HotCfg,
block_id : BlockId,
node_id : NodeId,
) -> Bool {
for mapped in cfg_block_get(cfg, block_id).nodes {
if mapped == node_id {
return true
}
}
false
}
///|
fn hot_verify_node_child_is_region_slot(node : HotNode, slot : Int32) -> Bool {
match node.op {
HotOp::Block => slot >= node.imm1
HotOp::Loop => true
HotOp::If => slot == 1 || slot == 2
HotOp::Try | HotOp::TryTable => slot == 0 || slot == 1
_ => false
}
}
///|
fn hot_verify_node_tree_contains(
func : HotFunc,
root_id : NodeId,
needle_id : NodeId,
) -> Bool {
if root_id == needle_id {
return true
}
if !hot_node_is_live(func, root_id) {
return false
}
let root = hot_node_get(func, root_id)
for slot = 0; slot < root.child_count; slot = slot + 1 {
if hot_verify_node_child_is_region_slot(root, slot) {
continue
}
let child_id = hot_child_get(func, root_id, slot)
if child_id == HOT_NONE_NODE || !hot_node_is_live(func, child_id) {
continue
}
if hot_verify_node_tree_contains(func, child_id, needle_id) {
return true
}
}
false
}
///|
fn hot_verify_cfg_block_for_node_tree(
func : HotFunc,
cfg : HotCfg,
node_id : NodeId,
) -> BlockId? {
match cfg_block_for_node(cfg, node_id) {
Some(block_id) => return Some(block_id)
None => ()
}
for block_id = 0; block_id < cfg_block_count(cfg); block_id = block_id + 1 {
for root_id in cfg_block_get(cfg, block_id).nodes {
if hot_verify_node_tree_contains(func, root_id, node_id) {
return Some(block_id)
}
}
}
None
}
///|
fn hot_verify_region_root_is_valid(
func : HotFunc,
entry : CfgRegionRootBlock,
) -> Bool {
let region_ref = if entry.region_holder == HOT_NONE_NODE {
HotRegionRef::root()
} else {
if !hot_node_is_live(func, entry.region_holder) {
return false
}
match hot_node_get(func, entry.region_holder).op {
HotOp::Block | HotOp::Loop => { holder: entry.region_holder, }
_ => return false
}
}
entry.slot >= 0 && entry.slot < hot_region_root_count(func, region_ref)
}
///|
pub fn hot_verify_cfg(
func : HotFunc,
cfg : HotCfg,
) -> Result[Unit, HotVerifyError] {
match hot_verify_control(func) {
Ok(_) => ()
Err(err) => return Err(err)
}
if !hot_verify_block_id_is_valid(cfg, cfg_entry_block(cfg)) {
return Err(HotVerifyError::InvalidCfgEntry(cfg_entry_block(cfg)))
}
if !hot_verify_block_id_is_valid(cfg, cfg_exit_block(cfg)) {
return Err(HotVerifyError::InvalidCfgExit(cfg_exit_block(cfg)))
}
if !cfg_block_get(cfg, cfg_entry_block(cfg)).is_synthetic {
return Err(HotVerifyError::InvalidCfgEntry(cfg_entry_block(cfg)))
}
if !cfg_block_get(cfg, cfg_exit_block(cfg)).is_synthetic {
return Err(HotVerifyError::InvalidCfgExit(cfg_exit_block(cfg)))
}
match cfg_exceptional_exit_block(cfg) {
Some(block_id) =>
if !hot_verify_block_id_is_valid(cfg, block_id) ||
!cfg_block_get(cfg, block_id).is_synthetic {
return Err(HotVerifyError::InvalidCfgExit(block_id))
}
None => ()
}
if cfg.node_to_block.length() != hot_node_count(func) {
return Err(
HotVerifyError::InvalidCfgShape(
"node_to_block",
cfg.node_to_block.length(),
hot_node_count(func),
),
)
}
for block_id = 0; block_id < cfg_block_count(cfg); block_id = block_id + 1 {
let block = cfg_block_get(cfg, block_id)
if block.id != block_id {
return Err(HotVerifyError::InvalidCfgBlockId(block_id, block.id))
}
for node_id in block.nodes {
if !hot_node_is_live(func, node_id) {
return Err(HotVerifyError::InvalidCfgBlockNode(block_id, node_id))
}
match cfg.node_to_block.get(node_id) {
Some(mapped) if mapped == block_id => ()
Some(mapped) =>
return Err(HotVerifyError::InvalidCfgNodeMapping(node_id, mapped))
None =>
return Err(HotVerifyError::InvalidCfgBlockNode(block_id, node_id))
}
}
for succ in block.succs {
if !hot_verify_block_id_is_valid(cfg, succ.block) {
return Err(HotVerifyError::InvalidCfgEdge(block_id, succ))
}
if !hot_verify_cfg_edge_has_symmetry(cfg, block_id, succ) {
return Err(HotVerifyError::InvalidCfgEdgeSymmetry(block_id, succ))
}
}
for pred in block.preds {
if !hot_verify_block_id_is_valid(cfg, pred.block) {
return Err(HotVerifyError::InvalidCfgEdge(block_id, pred))
}
if !hot_verify_cfg_pred_has_symmetry(cfg, block_id, pred) {
return Err(HotVerifyError::InvalidCfgEdgeSymmetry(block_id, pred))
}
}
}
for node_id = 0; node_id < cfg.node_to_block.length(); node_id = node_id + 1 {
let block_id = cfg.node_to_block[node_id]
if block_id < 0 {
continue
}
if !hot_node_is_live(func, node_id) ||
!hot_verify_block_id_is_valid(cfg, block_id) ||
!hot_verify_cfg_block_contains_node(cfg, block_id, node_id) {
return Err(HotVerifyError::InvalidCfgNodeMapping(node_id, block_id))
}
}
for entry in cfg.region_roots {
if !hot_verify_block_id_is_valid(cfg, entry.block) ||
!hot_verify_region_root_is_valid(func, entry) {
return Err(
HotVerifyError::InvalidCfgRegionRoot(
entry.region_holder,
entry.slot,
entry.block,
),
)
}
let block = cfg_block_get(cfg, entry.block)
if block.region_holder != entry.region_holder ||
entry.slot < block.start_slot ||
entry.slot >= block.end_slot {
return Err(
HotVerifyError::InvalidCfgRegionRoot(
entry.region_holder,
entry.slot,
entry.block,
),
)
}
}
Ok(())
}
///|
fn hot_verify_dom_reachable(dom : HotDominators, block_id : BlockId) -> Bool {
if block_id < 0 || block_id >= dom.reachable.size {
return false
}
bitset_contains(dom.reachable, block_id)
}
///|
pub fn hot_verify_dominators(
cfg : HotCfg,
dom : HotDominators,
) -> Result[Unit, HotVerifyError] {
let block_count = cfg_block_count(cfg)
if dom.entry_block != cfg_entry_block(cfg) {
return Err(
HotVerifyError::InvalidDomEntry(dom.entry_block, cfg_entry_block(cfg)),
)
}
if dom.reachable.size != block_count {
return Err(
HotVerifyError::InvalidDomShape(
"reachable",
dom.reachable.size,
block_count,
),
)
}
if dom.idoms.length() != block_count {
return Err(
HotVerifyError::InvalidDomShape("idoms", dom.idoms.length(), block_count),
)
}
if dom.children.length() != block_count {
return Err(
HotVerifyError::InvalidDomShape(
"children",
dom.children.length(),
block_count,
),
)
}
if dom.frontiers.length() != block_count {
return Err(
HotVerifyError::InvalidDomShape(
"frontiers",
dom.frontiers.length(),
block_count,
),
)
}
if dom.dom_preorder.length() != block_count {
return Err(
HotVerifyError::InvalidDomShape(
"dom_preorder",
dom.dom_preorder.length(),
block_count,
),
)
}
if dom.dom_postorder.length() != block_count {
return Err(
HotVerifyError::InvalidDomShape(
"dom_postorder",
dom.dom_postorder.length(),
block_count,
),
)
}
if !hot_verify_dom_reachable(dom, dom.entry_block) {
return Err(HotVerifyError::InvalidDomReachable(dom.entry_block))
}
let child_counts = Array::make(block_count, 0)
for block_id = 0; block_id < block_count; block_id = block_id + 1 {
if !hot_verify_dom_reachable(dom, block_id) {
if dom.idoms[block_id] != -1 {
return Err(
HotVerifyError::InvalidDomIdom(block_id, dom.idoms[block_id]),
)
}
continue
}
if dom.dom_preorder[block_id] < 0 || dom.dom_postorder[block_id] < 0 {
return Err(HotVerifyError::InvalidDomReachable(block_id))
}
if block_id == dom.entry_block {
if dom.idoms[block_id] != block_id {
return Err(
HotVerifyError::InvalidDomIdom(block_id, dom.idoms[block_id]),
)
}
} else {
let idom = dom.idoms[block_id]
if !hot_verify_block_id_is_valid(cfg, idom) ||
!hot_verify_dom_reachable(dom, idom) {
return Err(HotVerifyError::InvalidDomIdom(block_id, idom))
}
if !dom_dominates(dom, idom, block_id) {
return Err(HotVerifyError::InvalidDomIdom(block_id, idom))
}
}
}
for parent = 0; parent < block_count; parent = parent + 1 {
for child in dom.children[parent] {
if !hot_verify_block_id_is_valid(cfg, child) || dom.idoms[child] != parent {
return Err(HotVerifyError::InvalidDomChild(parent, child))
}
child_counts[child] = child_counts[child] + 1
}
for frontier in dom.frontiers[parent] {
if !hot_verify_block_id_is_valid(cfg, frontier) ||
!hot_verify_dom_reachable(dom, frontier) {
return Err(HotVerifyError::InvalidDomFrontier(parent, frontier))
}
}
}
for block_id = 0; block_id < block_count; block_id = block_id + 1 {
if !hot_verify_dom_reachable(dom, block_id) || block_id == dom.entry_block {
continue
}
if child_counts[block_id] != 1 {
return Err(HotVerifyError::InvalidDomChild(dom.idoms[block_id], block_id))
}
}
Ok(())
}
///|
fn hot_verify_ssa_value_is_valid(
ssa : HotLocalSsa,
value_id : SsaValueId,
) -> Bool {
value_id >= 0 && value_id < ssa.value_types.length()
}
///|
fn hot_verify_ssa_phi_is_valid(ssa : HotLocalSsa, phi_id : PhiId) -> Bool {
phi_id >= 0 && phi_id < ssa.phis.length()
}
///|
fn hot_verify_ssa_value_def_block(
func : HotFunc,
cfg : HotCfg,
ssa : HotLocalSsa,
value_id : SsaValueId,
) -> BlockId? {
match ssa.value_origins[value_id] {
HotSsaValueOrigin::EntryParamDef(_)
| HotSsaValueOrigin::EntryDefaultInitDef(_) => Some(cfg_entry_block(cfg))
HotSsaValueOrigin::LocalSetDef(node_id)
| HotSsaValueOrigin::LocalTeeDef(node_id) =>
hot_verify_cfg_block_for_node_tree(func, cfg, node_id)
HotSsaValueOrigin::PhiDef(phi_id) => Some(ssa.phis[phi_id].block)
}
}
///|
fn hot_verify_ssa_normal_predecessors(
cfg : HotCfg,
dom : HotDominators,
block_id : BlockId,
) -> Array[BlockId] {
let preds : Array[BlockId] = []
for pred in cfg_predecessor_edges(cfg, block_id) {
if pred.kind == CfgEdgeKind::ExceptionalEdge ||
!dom_dominates(dom, dom.entry_block, pred.block) {
continue
}
preds.push(pred.block)
}
preds
}
///|
fn hot_verify_ssa_type_for_local(
func : HotFunc,
local_id : LocalId,
) -> Result[ValType, HotVerifyError] {
match hot_local_type(func, local_id) {
Some(ty) => Ok(ty)
None => Err(HotVerifyError::InvalidSsaLocal(local_id))
}
}
///|
fn hot_verify_ssa_use_value(
func : HotFunc,
cfg : HotCfg,
dom : HotDominators,
ssa : HotLocalSsa,
value_id : SsaValueId,
use_block : BlockId,
) -> Result[Unit, HotVerifyError] {
if !hot_verify_ssa_value_is_valid(ssa, value_id) {
return Err(HotVerifyError::InvalidSsaValue(value_id))
}
match hot_verify_ssa_value_def_block(func, cfg, ssa, value_id) {
Some(def_block) =>
if !dom_dominates(dom, def_block, use_block) {
return Err(
HotVerifyError::InvalidSsaDominance(value_id, def_block, use_block),
)
}
None => return Err(HotVerifyError::InvalidSsaValue(value_id))
}
Ok(())
}
///|
pub fn hot_verify_ssa(
func : HotFunc,
cfg : HotCfg,
ssa : HotLocalSsa,
) -> Result[Unit, HotVerifyError] {
match hot_verify_cfg(func, cfg) {
Ok(_) => ()
Err(err) => return Err(err)
}
let dom = dom_build(cfg)
match hot_verify_dominators(cfg, dom) {
Ok(_) => ()
Err(err) => return Err(err)
}
if ssa.revision != hot_revision_current(func) {
return Err(
HotVerifyError::InvalidSsaRevision(
ssa.revision,
hot_revision_current(func),
),
)
}
let node_count = hot_node_count(func)
let local_count = hot_local_count(func)
let block_count = cfg_block_count(cfg)
if ssa.local_get_values.length() != node_count {
return Err(
HotVerifyError::InvalidSsaShape(
"local_get_values",
ssa.local_get_values.length(),
node_count,
),
)
}
if ssa.local_write_defs.length() != node_count {
return Err(
HotVerifyError::InvalidSsaShape(
"local_write_defs",
ssa.local_write_defs.length(),
node_count,
),
)
}
if ssa.block_phis.length() != block_count {
return Err(
HotVerifyError::InvalidSsaShape(
"block_phis",
ssa.block_phis.length(),
block_count,
),
)
}
if ssa.entry_defs.length() != local_count {
return Err(
HotVerifyError::InvalidSsaShape(
"entry_defs",
ssa.entry_defs.length(),
local_count,
),
)
}
if ssa.value_origins.length() != ssa.value_types.length() {
return Err(
HotVerifyError::InvalidSsaShape(
"value_origins",
ssa.value_origins.length(),
ssa.value_types.length(),
),
)
}
if ssa.value_uses.length() != ssa.value_types.length() {
return Err(
HotVerifyError::InvalidSsaShape(
"value_uses",
ssa.value_uses.length(),
ssa.value_types.length(),
),
)
}
if ssa.phi_value_ids.length() != ssa.phis.length() {
return Err(
HotVerifyError::InvalidSsaShape(
"phi_value_ids",
ssa.phi_value_ids.length(),
ssa.phis.length(),
),
)
}
if ssa.phi_inputs.length() != ssa.phis.length() {
return Err(
HotVerifyError::InvalidSsaShape(
"phi_inputs",
ssa.phi_inputs.length(),
ssa.phis.length(),
),
)
}
for local_id = 0; local_id < local_count; local_id = local_id + 1 {
let value_id = ssa.entry_defs[local_id]
if !hot_verify_ssa_value_is_valid(ssa, value_id) {
return Err(HotVerifyError::InvalidSsaValue(value_id))
}
let expected = match hot_verify_ssa_type_for_local(func, local_id) {
Ok(ty) => ty
Err(err) => return Err(err)
}
if ssa.value_types[value_id] != expected {
return Err(
HotVerifyError::InvalidSsaType(
value_id,
ssa.value_types[value_id],
expected,
),
)
}
}
for value_id = 0; value_id < ssa.value_types.length(); value_id = value_id + 1 {
match ssa.value_origins[value_id] {
HotSsaValueOrigin::EntryParamDef(local_id)
| HotSsaValueOrigin::EntryDefaultInitDef(local_id) => {
if local_id < 0 ||
local_id >= local_count ||
ssa.entry_defs[local_id] != value_id {
return Err(
HotVerifyError::InvalidSsaValueOrigin(
value_id,
ssa.value_origins[value_id],
),
)
}
let expected = match hot_verify_ssa_type_for_local(func, local_id) {
Ok(ty) => ty
Err(err) => return Err(err)
}
if ssa.value_types[value_id] != expected {
return Err(
HotVerifyError::InvalidSsaType(
value_id,
ssa.value_types[value_id],
expected,
),
)
}
}
HotSsaValueOrigin::LocalSetDef(node_id)
| HotSsaValueOrigin::LocalTeeDef(node_id) => {
if node_id < 0 ||
node_id >= node_count ||
!hot_node_is_live(func, node_id) {
return Err(
HotVerifyError::InvalidSsaValueOrigin(
value_id,
ssa.value_origins[value_id],
),
)
}
let node = hot_node_get(func, node_id)
let is_set_origin = match ssa.value_origins[value_id] {
HotSsaValueOrigin::LocalSetDef(_) => node.op == HotOp::LocalSet
HotSsaValueOrigin::LocalTeeDef(_) => node.op == HotOp::LocalTee
_ => false
}
if !is_set_origin || ssa.local_write_defs[node_id] != value_id {
return Err(
HotVerifyError::InvalidSsaValueOrigin(
value_id,
ssa.value_origins[value_id],
),
)
}
let expected = match hot_verify_ssa_type_for_local(func, node.imm0) {
Ok(ty) => ty
Err(err) => return Err(err)
}
if ssa.value_types[value_id] != expected {
return Err(
HotVerifyError::InvalidSsaType(
value_id,
ssa.value_types[value_id],
expected,
),
)
}
}
HotSsaValueOrigin::PhiDef(phi_id) =>
if !hot_verify_ssa_phi_is_valid(ssa, phi_id) ||
ssa.phi_value_ids[phi_id] != value_id {
return Err(
HotVerifyError::InvalidSsaValueOrigin(
value_id,
ssa.value_origins[value_id],
),
)
}
}
}
for block_id = 0; block_id < block_count; block_id = block_id + 1 {
let seen_locals = Array::make(local_count, false)
for phi_id in ssa.block_phis[block_id] {
if !hot_verify_ssa_phi_is_valid(ssa, phi_id) {
return Err(HotVerifyError::InvalidSsaPhi(phi_id))
}
let phi = ssa.phis[phi_id]
if phi.block != block_id ||
phi.local_id < 0 ||
phi.local_id >= local_count {
return Err(HotVerifyError::InvalidSsaPhi(phi_id))
}
if seen_locals[phi.local_id] {
return Err(HotVerifyError::InvalidSsaPhi(phi_id))
}
seen_locals[phi.local_id] = true
let value_id = ssa.phi_value_ids[phi_id]
if !hot_verify_ssa_value_is_valid(ssa, value_id) {
return Err(HotVerifyError::InvalidSsaValue(value_id))
}
let expected = match hot_verify_ssa_type_for_local(func, phi.local_id) {
Ok(ty) => ty
Err(err) => return Err(err)
}
if ssa.value_types[value_id] != expected {
return Err(
HotVerifyError::InvalidSsaType(
value_id,
ssa.value_types[value_id],
expected,
),
)
}
let preds = hot_verify_ssa_normal_predecessors(cfg, dom, block_id)
if ssa.phi_inputs[phi_id].length() != preds.length() {
return Err(
HotVerifyError::InvalidSsaPhiAlignment(
phi_id,
block_id,
ssa.phi_inputs[phi_id].length(),
preds.length(),
),
)
}
let seen_preds = Array::make(block_count, false)
for input in ssa.phi_inputs[phi_id] {
let mut found_pred = false
for pred in preds {
if pred == input.predecessor {
found_pred = true
break
}
}
if !found_pred ||
seen_preds[input.predecessor] ||
!hot_verify_ssa_value_is_valid(ssa, input.value_id) {
return Err(
HotVerifyError::InvalidSsaPhiInput(
phi_id,
input.predecessor,
input.value_id,
),
)
}
seen_preds[input.predecessor] = true
if ssa.value_types[input.value_id] != expected {
return Err(
HotVerifyError::InvalidSsaType(
input.value_id,
ssa.value_types[input.value_id],
expected,
),
)
}
let mut listed_use = false
for use_origin in ssa.value_uses[input.value_id] {
match use_origin {
HotSsaUseOrigin::PhiInputUse(listed_phi, listed_pred) =>
if listed_phi == phi_id && listed_pred == input.predecessor {
listed_use = true
break
}
_ => ()
}
}
if !listed_use {
return Err(
HotVerifyError::InvalidSsaUse(
input.value_id,
HotSsaUseOrigin::PhiInputUse(phi_id, input.predecessor),
),
)
}
match
hot_verify_ssa_use_value(
func,
cfg,
dom,
ssa,
input.value_id,
input.predecessor,
) {
Ok(_) => ()
Err(err) => return Err(err)
}
}
}
}
for phi_id = 0; phi_id < ssa.phis.length(); phi_id = phi_id + 1 {
let phi = ssa.phis[phi_id]
if !hot_verify_block_id_is_valid(cfg, phi.block) ||
phi.local_id < 0 ||
phi.local_id >= local_count {
return Err(HotVerifyError::InvalidSsaPhi(phi_id))
}
let mut found = false
for mapped in ssa.block_phis[phi.block] {
if mapped == phi_id {
found = true
break
}
}
if !found {
return Err(HotVerifyError::InvalidSsaPhi(phi_id))
}
}
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 => {
let value_id = ssa.local_get_values[node_id]
if !hot_verify_ssa_value_is_valid(ssa, value_id) {
if value_id == -1 {
match hot_verify_cfg_block_for_node_tree(func, cfg, node_id) {
Some(block_id) if !hot_verify_dom_reachable(dom, block_id) =>
continue
_ => ()
}
}
return Err(HotVerifyError::InvalidSsaValue(value_id))
}
let expected = match hot_verify_ssa_type_for_local(func, node.imm0) {
Ok(ty) => ty
Err(err) => return Err(err)
}
if ssa.value_types[value_id] != expected {
return Err(
HotVerifyError::InvalidSsaType(
value_id,
ssa.value_types[value_id],
expected,
),
)
}
let mut listed_use = false
for use_origin in ssa.value_uses[value_id] {
match use_origin {
HotSsaUseOrigin::LocalGetUse(listed_node) =>
if listed_node == node_id {
listed_use = true
break
}
_ => ()
}
}
if !listed_use {
return Err(
HotVerifyError::InvalidSsaUse(
value_id,
HotSsaUseOrigin::LocalGetUse(node_id),
),
)
}
match hot_verify_cfg_block_for_node_tree(func, cfg, node_id) {
Some(use_block) =>
match
hot_verify_ssa_use_value(func, cfg, dom, ssa, value_id, use_block) {
Ok(_) => ()
Err(err) => return Err(err)
}
None => return Err(HotVerifyError::InvalidSsaNode(node_id))
}
}
HotOp::LocalSet | HotOp::LocalTee => {
let value_id = ssa.local_write_defs[node_id]
if !hot_verify_ssa_value_is_valid(ssa, value_id) {
if value_id == -1 {
match hot_verify_cfg_block_for_node_tree(func, cfg, node_id) {
Some(block_id) if !hot_verify_dom_reachable(dom, block_id) =>
continue
_ => ()
}
}
return Err(HotVerifyError::InvalidSsaValue(value_id))
}
}
_ => ()
}
}
for value_id = 0; value_id < ssa.value_uses.length(); value_id = value_id + 1 {
for use_origin in ssa.value_uses[value_id] {
match use_origin {
HotSsaUseOrigin::LocalGetUse(node_id) =>
if node_id < 0 ||
node_id >= node_count ||
ssa.local_get_values[node_id] != value_id {
return Err(HotVerifyError::InvalidSsaUse(value_id, use_origin))
}
HotSsaUseOrigin::PhiInputUse(phi_id, predecessor) => {
if !hot_verify_ssa_phi_is_valid(ssa, phi_id) {
return Err(HotVerifyError::InvalidSsaUse(value_id, use_origin))
}
let mut found = false
for input in ssa.phi_inputs[phi_id] {
if input.predecessor == predecessor && input.value_id == value_id {
found = true
break
}
}
if !found {
return Err(HotVerifyError::InvalidSsaUse(value_id, use_origin))
}
}
}
}
}
Ok(())
}
///|
pub fn hot_verify_cached_analyses(
func : HotFunc,
cache : HotAnalysisCache,
) -> Result[Unit, HotVerifyError] {
match cache.owner_roots {
Some(roots) if !physical_equal(roots, func.roots) =>
return Err(HotVerifyError::InvalidAnalysisOwner)
_ => ()
}
let revision = hot_revision_current(func)
match cache.cfg {
Some(entry) => {
if entry.built_at_revision != revision {
return Err(
HotVerifyError::InvalidAnalysisRevision(
HotAnalysis::Cfg,
entry.built_at_revision,
revision,
),
)
}
match hot_verify_cfg(func, entry.value) {
Ok(_) => ()
Err(err) => return Err(err)
}
}
None => ()
}
match cache.orders {
Some(entry) if entry.built_at_revision != revision =>
return Err(
HotVerifyError::InvalidAnalysisRevision(
HotAnalysis::Cfg,
entry.built_at_revision,
revision,
),
)
_ => ()
}
match cache.dom {
Some(entry) => {
if entry.built_at_revision != revision {
return Err(
HotVerifyError::InvalidAnalysisRevision(
HotAnalysis::Dominance,
entry.built_at_revision,
revision,
),
)
}
let cfg = match cache.cfg {
Some(cfg_entry) if cfg_entry.built_at_revision == revision =>
cfg_entry.value
_ =>
return Err(
HotVerifyError::InvalidAnalysisDependency(
HotAnalysis::Dominance,
HotAnalysis::Cfg,
),
)
}
match hot_verify_dominators(cfg, entry.value) {
Ok(_) => ()
Err(err) => return Err(err)
}
}
None => ()
}
match cache.postdom {
Some(entry) if entry.built_at_revision != revision =>
return Err(
HotVerifyError::InvalidAnalysisRevision(
HotAnalysis::Dominance,
entry.built_at_revision,
revision,
),
)
_ => ()
}
match cache.loop_info {
Some(entry) if entry.built_at_revision != revision =>
return Err(
HotVerifyError::InvalidAnalysisRevision(
HotAnalysis::LoopInfo,
entry.built_at_revision,
revision,
),
)
_ => ()
}
match cache.use_def {
Some(entry) if entry.built_at_revision != revision =>
return Err(
HotVerifyError::InvalidAnalysisRevision(
HotAnalysis::UseDef,
entry.built_at_revision,
revision,
),
)
_ => ()
}
match cache.liveness {
Some(entry) if entry.built_at_revision != revision =>
return Err(
HotVerifyError::InvalidAnalysisRevision(
HotAnalysis::Liveness,
entry.built_at_revision,
revision,
),
)
_ => ()
}
match cache.effects {
Some(entry) if entry.built_at_revision != revision =>
return Err(
HotVerifyError::InvalidAnalysisRevision(
HotAnalysis::Effects,
entry.built_at_revision,
revision,
),
)
_ => ()
}
match cache.ssa {
Some(entry) => {
if entry.built_at_revision != revision {
return Err(
HotVerifyError::InvalidAnalysisRevision(
HotAnalysis::Ssa,
entry.built_at_revision,
revision,
),
)
}
let cfg = match cache.cfg {
Some(cfg_entry) if cfg_entry.built_at_revision == revision =>
cfg_entry.value
_ =>
return Err(
HotVerifyError::InvalidAnalysisDependency(
HotAnalysis::Ssa,
HotAnalysis::Cfg,
),
)
}
match hot_verify_ssa(func, cfg, entry.value) {
Ok(_) => ()
Err(err) => return Err(err)
}
}
None => ()
}
Ok(())
}
///|
pub fn[C] hot_verify_all(
func : HotFunc,
cache~ : C? = None,
) -> Result[Unit, HotVerifyError] {
let _ = cache
hot_verify_all_impl(func)
}
///|
fn hot_verify_all_impl(
func : HotFunc,
core_verification_calls~ : Array[Int32]? = None,
) -> Result[Unit, HotVerifyError] {
// Control verification begins with the complete core verifier. No mutation
// separates the two original calls, so repeating core adds no checks and
// does not change which error is returned first.
match core_verification_calls {
// An explicitly forwarded None would box Some(None) at this optional-
// argument boundary. Keep default verification on the uncounted entry.
None => hot_verify_control(func)
Some(calls) =>
hot_verify_control_impl(func, core_verification_calls=Some(calls))
}
}
///|
pub fn hot_verify_core_or_abort(func : HotFunc) -> Unit {
match hot_verify_core(func) {
Ok(_) => ()
Err(err) => abort(err.to_string())
}
}
///|
pub fn hot_verify_control_or_abort(func : HotFunc) -> Unit {
match hot_verify_control(func) {
Ok(_) => ()
Err(err) => abort(err.to_string())
}
}
///|
pub fn hot_verify_all_or_abort(func : HotFunc) -> Unit {
match hot_verify_all(func, cache=Some(())) {
Ok(_) => ()
Err(err) => abort(err.to_string())
}
}