fn mut_step_block(box_idx: u32, sub_idx: u32, cross_idx: u32) -> FragBlock {
let mut b = RecBlockBuilder::new();
let n = b.push(FragTy::IntCarrier, FragNodeKind::Local { index: 0 });
let one = b.push(FragTy::I64, FragNodeKind::ConstI64(1));
let boxed = b.push(
FragTy::IntCarrier,
FragNodeKind::HostCall {
role: FragHostRole::Box,
func_idx: box_idx,
args: vec![one],
},
);
let dec = b.push(
FragTy::IntCarrier,
FragNodeKind::HostCall {
role: FragHostRole::Sub,
func_idx: sub_idx,
args: vec![n, boxed],
},
);
let call = b.push(
FragTy::IntCarrier,
FragNodeKind::SelfCall {
tail: true,
func_idx: cross_idx,
args: vec![dec],
},
);
b.finish(call)
}
fn mutual_member_plan(box_idx: u32, sub_idx: u32, base_k: i64, cross_idx: u32) -> ExprFragmentPlan {
let mut top = RecBlockBuilder::new();
let sign = rec_push_sign_predicate(&mut top);
let value = top.push(
FragTy::IntCarrier,
FragNodeKind::If {
cond: sign,
then_block: Box::new(rec_base_const_block(base_k, box_idx)),
else_block: Box::new(mut_step_block(box_idx, sub_idx, cross_idx)),
},
);
ExprFragmentPlan {
params: vec![FragTy::IntCarrier],
result: FragTy::IntCarrier,
body: top.finish(value),
}
}
fn mutual_plan_from_cert(c: &Cert) -> Option<ExprFragmentPlan> {
let Cert::MutualRecursion {
carrier,
box_idx,
sub_idx,
position,
scc,
..
} = c.inner()
else {
return None;
};
let m = scc.get(*position)?;
let plan = mutual_member_plan(*box_idx, *sub_idx, m.base_k, m.cross_idx);
let lowered = lower_expr_fragment_plan_code_entry_bytes(&plan, *carrier).ok()?;
if lowered != m.code_entry_bytes {
return None;
}
Some(plan)
}
fn mutual_host_table_lean_value(box_idx: u32, sub_idx: u32) -> String {
format!("[(.box, {box_idx}), (.sub, {sub_idx})]")
}
fn mutual_member_set_lean_value(scc: &[MutualMember]) -> String {
format!(
"[{}]",
scc.iter()
.map(|m| m.self_idx.to_string())
.collect::<Vec<_>>()
.join(", ")
)
}
fn mutual_plan_lean_value(plan: &ExprFragmentPlan) -> String {
format!(
"{{ profile := \"mutual-plan-v1\", params := [{}], result := {}, body := {} }}",
plan.params
.iter()
.map(|ty| ty.lean_plan_ctor())
.collect::<Vec<_>>()
.join(", "),
plan.result.lean_plan_ctor(),
expr_fragment_block_lean_value(&plan.body)
)
}
#[cfg(test)]
mod mutual_plan_gate_tests {
use super::*;
fn mutual_cert(position: usize, member_bytes: Vec<Vec<u8>>) -> Cert {
let scc = vec![
MutualMember {
name: "isEven".to_string(),
self_idx: 1,
type_idx: 4,
nlocals: 1,
base_k: 1,
cross_idx: 2,
code_entry_bytes: member_bytes[0].clone(),
},
MutualMember {
name: "isOdd".to_string(),
self_idx: 2,
type_idx: 4,
nlocals: 1,
base_k: 0,
cross_idx: 1,
code_entry_bytes: member_bytes[1].clone(),
},
];
Cert::MutualRecursion {
name: scc[position].name.clone(),
self_idx: scc[position].self_idx,
carrier: 2,
box_idx: 7,
sub_idx: 9,
position,
scc,
}
}
#[test]
fn mutual_plan_requires_exact_code_entry_bytes() {
let even_plan = mutual_member_plan(7, 9, 1, 2);
let odd_plan = mutual_member_plan(7, 9, 0, 1);
let even_bytes =
lower_expr_fragment_plan_code_entry_bytes(&even_plan, 2).expect("isEven lowering");
let odd_bytes =
lower_expr_fragment_plan_code_entry_bytes(&odd_plan, 2).expect("isOdd lowering");
assert!(
mutual_plan_from_cert(&mutual_cert(0, vec![even_bytes.clone(), odd_bytes.clone()]))
.is_some(),
"byte-exact isEven member must carry a plan claim"
);
assert!(
mutual_plan_from_cert(&mutual_cert(1, vec![even_bytes.clone(), odd_bytes.clone()]))
.is_some(),
"byte-exact isOdd member must carry a plan claim"
);
assert!(
even_bytes.windows(2).any(|w| w == [0x12, 0x02]),
"isEven step tail must be return_call isOdd (2): {even_bytes:02x?}"
);
assert!(
odd_bytes.windows(2).any(|w| w == [0x12, 0x01]),
"isOdd step tail must be return_call isEven (1): {odd_bytes:02x?}"
);
let mut noisy = even_bytes.clone();
noisy.push(0x00);
noisy[0] += 1;
assert!(
mutual_plan_from_cert(&mutual_cert(0, vec![noisy, odd_bytes])).is_none(),
"a member the canonical plan cannot reproduce must not carry a claim"
);
}
#[test]
fn mutual_total_promotion_is_atomic_per_scc() {
for position in 0..2 {
let mut cert = mutual_cert(position, vec![Vec::new(), Vec::new()]);
let Cert::MutualRecursion { scc, .. } = &mut cert else {
unreachable!()
};
scc[0].cross_idx = 99;
assert_eq!(cert.termination_witness(), None);
assert_eq!(cert.policy(), CertificationPolicy::SimulatesModel);
}
}
}