feat(control_tree): Phase 129-B join_k as-last lowering
This commit is contained in:
@ -215,22 +215,19 @@ impl MirBuilder {
|
|||||||
match shadow_result {
|
match shadow_result {
|
||||||
Ok(Some((module, _meta))) => {
|
Ok(Some((module, _meta))) => {
|
||||||
// Phase 122: Verify Normalized JoinModule structure
|
// Phase 122: Verify Normalized JoinModule structure
|
||||||
let expected_env_fields = tree.contract.writes.len();
|
let expected_env_fields =
|
||||||
let verify_result = parity::verify_normalized_structure(&module, expected_env_fields);
|
StepTreeNormalizedShadowLowererBox::expected_env_field_count(
|
||||||
|
&tree,
|
||||||
if !verify_result.ok {
|
&available_inputs,
|
||||||
let msg = format!(
|
|
||||||
"phase122/emit: structure verification failed for {}: {}",
|
|
||||||
func_name,
|
|
||||||
verify_result.hint.unwrap_or_else(|| "<no hint>".to_string())
|
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if let Err(err) =
|
||||||
|
parity::verify_normalized_structure(&module, expected_env_fields)
|
||||||
|
{
|
||||||
if strict {
|
if strict {
|
||||||
return Err(format!(
|
return Err(err);
|
||||||
"Phase122 Normalized structure verification failed (strict mode): {}",
|
|
||||||
msg
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
trace.dev("phase122/emit/error", &msg);
|
trace.dev("phase122/emit/error", &err);
|
||||||
} else {
|
} else {
|
||||||
// Shadow lowering succeeded + structure verified
|
// Shadow lowering succeeded + structure verified
|
||||||
let status = format!(
|
let status = format!(
|
||||||
|
|||||||
@ -14,7 +14,7 @@
|
|||||||
//! - Single responsibility: structure → JoinIR conversion
|
//! - Single responsibility: structure → JoinIR conversion
|
||||||
|
|
||||||
use crate::mir::control_tree::step_tree::StepTree;
|
use crate::mir::control_tree::step_tree::StepTree;
|
||||||
use crate::mir::join_ir::lowering::carrier_info::{ExitMeta, JoinFragmentMeta};
|
use crate::mir::join_ir::lowering::carrier_info::JoinFragmentMeta;
|
||||||
use crate::mir::join_ir::JoinModule;
|
use crate::mir::join_ir::JoinModule;
|
||||||
use crate::mir::ValueId; // Phase 126
|
use crate::mir::ValueId; // Phase 126
|
||||||
use std::collections::BTreeMap; // Phase 126
|
use std::collections::BTreeMap; // Phase 126
|
||||||
@ -56,8 +56,6 @@ impl EnvLayout {
|
|||||||
contract: &crate::mir::control_tree::step_tree_contract_box::StepTreeContract,
|
contract: &crate::mir::control_tree::step_tree_contract_box::StepTreeContract,
|
||||||
available_inputs: &std::collections::BTreeMap<String, crate::mir::ValueId>,
|
available_inputs: &std::collections::BTreeMap<String, crate::mir::ValueId>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
use std::collections::BTreeSet;
|
|
||||||
|
|
||||||
// Phase 125 P2: writes from contract
|
// Phase 125 P2: writes from contract
|
||||||
let writes: Vec<String> = contract.writes.iter().cloned().collect();
|
let writes: Vec<String> = contract.writes.iter().cloned().collect();
|
||||||
|
|
||||||
@ -77,6 +75,26 @@ impl EnvLayout {
|
|||||||
pub struct StepTreeNormalizedShadowLowererBox;
|
pub struct StepTreeNormalizedShadowLowererBox;
|
||||||
|
|
||||||
impl StepTreeNormalizedShadowLowererBox {
|
impl StepTreeNormalizedShadowLowererBox {
|
||||||
|
/// Phase 129-B: Expected env field count (writes + inputs)
|
||||||
|
pub fn expected_env_field_count(
|
||||||
|
step_tree: &StepTree,
|
||||||
|
available_inputs: &BTreeMap<String, ValueId>,
|
||||||
|
) -> usize {
|
||||||
|
let env_layout = EnvLayout::from_contract(&step_tree.contract, available_inputs);
|
||||||
|
env_layout.writes.len() + env_layout.inputs.len()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Phase 129-B: If-as-last shape detection (no post-if)
|
||||||
|
pub fn expects_join_k_as_last(step_tree: &StepTree) -> bool {
|
||||||
|
use crate::mir::control_tree::step_tree::StepNode;
|
||||||
|
|
||||||
|
match &step_tree.root {
|
||||||
|
StepNode::If { .. } => true,
|
||||||
|
StepNode::Block(nodes) => matches!(nodes.last(), Some(StepNode::If { .. })),
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Try to lower an if-only StepTree to normalized form
|
/// Try to lower an if-only StepTree to normalized form
|
||||||
///
|
///
|
||||||
/// ## Returns
|
/// ## Returns
|
||||||
@ -150,12 +168,19 @@ impl StepTreeNormalizedShadowLowererBox {
|
|||||||
step_tree: &StepTree,
|
step_tree: &StepTree,
|
||||||
available_inputs: &BTreeMap<String, ValueId>,
|
available_inputs: &BTreeMap<String, ValueId>,
|
||||||
) -> Result<Option<(JoinModule, JoinFragmentMeta)>, String> {
|
) -> Result<Option<(JoinModule, JoinFragmentMeta)>, String> {
|
||||||
use crate::mir::join_ir::{JoinFunction, JoinFuncId, JoinInst};
|
use crate::mir::join_ir::{JoinFunction, JoinFuncId};
|
||||||
use crate::mir::ValueId;
|
use crate::mir::ValueId;
|
||||||
|
|
||||||
// Phase 126: EnvLayout 生成(available_inputs を使用)
|
// Phase 126: EnvLayout 生成(available_inputs を使用)
|
||||||
let env_layout = EnvLayout::from_contract(&step_tree.contract, available_inputs);
|
let env_layout = EnvLayout::from_contract(&step_tree.contract, available_inputs);
|
||||||
|
|
||||||
|
// Phase 129-B: If-as-last join_k lowering (dev-only)
|
||||||
|
if let Some((module, meta)) =
|
||||||
|
Self::lower_if_node_as_last_stmt(step_tree, &env_layout)?
|
||||||
|
{
|
||||||
|
return Ok(Some((module, meta)));
|
||||||
|
}
|
||||||
|
|
||||||
let main_func_id = JoinFuncId::new(0);
|
let main_func_id = JoinFuncId::new(0);
|
||||||
|
|
||||||
// Phase 125 P2: writes 用の ValueId 生成
|
// Phase 125 P2: writes 用の ValueId 生成
|
||||||
@ -170,20 +195,14 @@ impl StepTreeNormalizedShadowLowererBox {
|
|||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
// Phase 125 P2: inputs 用の ValueId 生成(今は空だが、P3 で available_inputs から参照)
|
// Phase 129-B: inputs 用の ValueId 生成(writes + inputs が env params のSSOT)
|
||||||
let inputs_params: Vec<ValueId> = env_layout
|
let inputs_params: Vec<ValueId> = env_layout
|
||||||
.inputs
|
.inputs
|
||||||
.iter()
|
.iter()
|
||||||
.map(|name| {
|
.map(|_| {
|
||||||
// Phase 125 P3: available_inputs から取得
|
let vid = ValueId(next_value_id);
|
||||||
// 今は空なので到達しないはずだが、念のため placeholder
|
next_value_id += 1;
|
||||||
available_inputs
|
vid
|
||||||
.get(name)
|
|
||||||
.copied()
|
|
||||||
.unwrap_or_else(|| {
|
|
||||||
// Should not reach here (inputs は available_inputs にある前提)
|
|
||||||
ValueId(0) // placeholder
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
@ -196,9 +215,9 @@ impl StepTreeNormalizedShadowLowererBox {
|
|||||||
env.insert(name.clone(), *vid);
|
env.insert(name.clone(), *vid);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Phase 125 P2: 関数パラメータは writes のみ(inputs は外側から来る前提)
|
// Phase 129-B: 関数パラメータは writes + inputs(env params SSOT)
|
||||||
// TODO P3: inputs も params に含める必要があるか検討
|
let mut env_params = writes_params;
|
||||||
let env_params = writes_params;
|
env_params.extend(inputs_params);
|
||||||
|
|
||||||
// main 関数生成
|
// main 関数生成
|
||||||
let mut main_func = JoinFunction::new(
|
let mut main_func = JoinFunction::new(
|
||||||
@ -246,6 +265,379 @@ impl StepTreeNormalizedShadowLowererBox {
|
|||||||
Ok(Some((module, meta)))
|
Ok(Some((module, meta)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Phase 129-B: Lower if node as last statement using join_k (dev-only)
|
||||||
|
///
|
||||||
|
/// Scope: "if-as-last" only (no post-if). If it doesn't match, return Ok(None).
|
||||||
|
fn lower_if_node_as_last_stmt(
|
||||||
|
step_tree: &StepTree,
|
||||||
|
env_layout: &EnvLayout,
|
||||||
|
) -> Result<Option<(JoinModule, JoinFragmentMeta)>, String> {
|
||||||
|
use crate::mir::control_tree::step_tree::{StepNode, StepStmtKind};
|
||||||
|
use crate::mir::join_ir::{ConstValue, JoinFunction, JoinFuncId, JoinInst, MirLikeInst};
|
||||||
|
use crate::mir::join_ir::lowering::error_tags;
|
||||||
|
use crate::mir::ValueId;
|
||||||
|
use std::collections::BTreeMap;
|
||||||
|
|
||||||
|
let (prefix_nodes, if_node) = match &step_tree.root {
|
||||||
|
StepNode::If { .. } => (&[][..], &step_tree.root),
|
||||||
|
StepNode::Block(nodes) => {
|
||||||
|
let last = nodes.last();
|
||||||
|
if matches!(last, Some(StepNode::If { .. })) {
|
||||||
|
(&nodes[..nodes.len() - 1], nodes.last().unwrap())
|
||||||
|
} else {
|
||||||
|
return Ok(None);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => return Ok(None),
|
||||||
|
};
|
||||||
|
|
||||||
|
let if_node = match if_node {
|
||||||
|
StepNode::If { .. } => if_node,
|
||||||
|
_ => return Ok(None),
|
||||||
|
};
|
||||||
|
|
||||||
|
let env_fields: Vec<String> = env_layout
|
||||||
|
.writes
|
||||||
|
.iter()
|
||||||
|
.chain(env_layout.inputs.iter())
|
||||||
|
.cloned()
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
fn alloc_value_id(next_value_id: &mut u32) -> ValueId {
|
||||||
|
let vid = ValueId(*next_value_id);
|
||||||
|
*next_value_id += 1;
|
||||||
|
vid
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut next_value_id: u32 = 1;
|
||||||
|
|
||||||
|
let alloc_env_params =
|
||||||
|
|fields: &[String], next_value_id: &mut u32| -> Vec<ValueId> {
|
||||||
|
fields
|
||||||
|
.iter()
|
||||||
|
.map(|_| alloc_value_id(next_value_id))
|
||||||
|
.collect()
|
||||||
|
};
|
||||||
|
|
||||||
|
let build_env_map = |fields: &[String], params: &[ValueId]| -> BTreeMap<String, ValueId> {
|
||||||
|
let mut env = BTreeMap::new();
|
||||||
|
for (name, vid) in fields.iter().zip(params.iter()) {
|
||||||
|
env.insert(name.clone(), *vid);
|
||||||
|
}
|
||||||
|
env
|
||||||
|
};
|
||||||
|
|
||||||
|
let collect_env_args = |fields: &[String], env: &BTreeMap<String, ValueId>| -> Result<Vec<ValueId>, String> {
|
||||||
|
let mut args = Vec::with_capacity(fields.len());
|
||||||
|
for name in fields {
|
||||||
|
let vid = env.get(name).copied().ok_or_else(|| {
|
||||||
|
error_tags::freeze_with_hint(
|
||||||
|
"phase129/join_k/env_missing",
|
||||||
|
&format!("env missing required field '{name}'"),
|
||||||
|
"ensure env layout and env map are built from the same SSOT field list",
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
args.push(vid);
|
||||||
|
}
|
||||||
|
Ok(args)
|
||||||
|
};
|
||||||
|
|
||||||
|
// IDs (stable, dev-only)
|
||||||
|
let main_id = JoinFuncId::new(0);
|
||||||
|
let k_then_id = JoinFuncId::new(1);
|
||||||
|
let k_else_id = JoinFuncId::new(2);
|
||||||
|
let join_k_id = JoinFuncId::new(3);
|
||||||
|
|
||||||
|
// main(env)
|
||||||
|
let main_params = alloc_env_params(&env_fields, &mut next_value_id);
|
||||||
|
let mut env_main = build_env_map(&env_fields, &main_params);
|
||||||
|
let mut main_func = JoinFunction::new(main_id, "main".to_string(), main_params);
|
||||||
|
|
||||||
|
// Lower prefix (pre-if) statements into main
|
||||||
|
for n in prefix_nodes {
|
||||||
|
match n {
|
||||||
|
StepNode::Stmt { kind, .. } => match kind {
|
||||||
|
StepStmtKind::Assign { target, value_ast } => {
|
||||||
|
if Self::lower_assign_stmt(
|
||||||
|
target,
|
||||||
|
value_ast,
|
||||||
|
&mut main_func.body,
|
||||||
|
&mut next_value_id,
|
||||||
|
&mut env_main,
|
||||||
|
)
|
||||||
|
.is_err()
|
||||||
|
{
|
||||||
|
return Ok(None);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
StepStmtKind::LocalDecl { .. } => {}
|
||||||
|
_ => {
|
||||||
|
return Ok(None);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
_ => {
|
||||||
|
return Ok(None);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract return variable and branch bodies.
|
||||||
|
fn split_branch_for_as_last(
|
||||||
|
branch: &StepNode,
|
||||||
|
) -> Result<(&[StepNode], &crate::mir::control_tree::step_tree::AstNodeHandle), String> {
|
||||||
|
use crate::mir::control_tree::step_tree::StepNode;
|
||||||
|
use crate::mir::control_tree::step_tree::StepStmtKind;
|
||||||
|
use crate::mir::join_ir::lowering::error_tags;
|
||||||
|
|
||||||
|
match branch {
|
||||||
|
StepNode::Stmt { kind, .. } => match kind {
|
||||||
|
StepStmtKind::Return { value_ast } => {
|
||||||
|
let ast_handle = value_ast.as_ref().ok_or_else(|| {
|
||||||
|
error_tags::freeze_with_hint(
|
||||||
|
"phase129/join_k/branch_return_void",
|
||||||
|
"branch return must return a variable (not void)",
|
||||||
|
"use `return x` in both then/else branches",
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
Ok((&[][..], ast_handle))
|
||||||
|
}
|
||||||
|
_ => Err(error_tags::freeze_with_hint(
|
||||||
|
"phase129/join_k/branch_not_return",
|
||||||
|
"branch must end with return",
|
||||||
|
"use `return x` in both then/else branches",
|
||||||
|
)),
|
||||||
|
},
|
||||||
|
StepNode::Block(nodes) => {
|
||||||
|
let last = nodes.last().ok_or_else(|| {
|
||||||
|
error_tags::freeze_with_hint(
|
||||||
|
"phase129/join_k/branch_empty",
|
||||||
|
"branch is empty",
|
||||||
|
"add `return x` as the last statement of the branch",
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
match last {
|
||||||
|
StepNode::Stmt { kind, .. } => match kind {
|
||||||
|
StepStmtKind::Return { value_ast } => {
|
||||||
|
let ast_handle = value_ast.as_ref().ok_or_else(|| {
|
||||||
|
error_tags::freeze_with_hint(
|
||||||
|
"phase129/join_k/branch_return_void",
|
||||||
|
"branch return must return a variable (not void)",
|
||||||
|
"use `return x` in both then/else branches",
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
Ok((&nodes[..nodes.len() - 1], ast_handle))
|
||||||
|
}
|
||||||
|
_ => Err(error_tags::freeze_with_hint(
|
||||||
|
"phase129/join_k/branch_not_return",
|
||||||
|
"branch must end with return",
|
||||||
|
"add `return x` as the last statement of the branch",
|
||||||
|
)),
|
||||||
|
},
|
||||||
|
_ => Err(error_tags::freeze_with_hint(
|
||||||
|
"phase129/join_k/branch_last_not_stmt",
|
||||||
|
"branch last node must be a statement return",
|
||||||
|
"ensure the branch ends with `return x`",
|
||||||
|
)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => Err(error_tags::freeze_with_hint(
|
||||||
|
"phase129/join_k/branch_node_unsupported",
|
||||||
|
"unsupported branch node",
|
||||||
|
"Phase 129-B supports only Block/Return branches",
|
||||||
|
)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn extract_return_var_name(ast_handle: &crate::mir::control_tree::step_tree::AstNodeHandle) -> Result<String, String> {
|
||||||
|
use crate::ast::ASTNode;
|
||||||
|
use crate::mir::join_ir::lowering::error_tags;
|
||||||
|
match ast_handle.0.as_ref() {
|
||||||
|
ASTNode::Variable { name, .. } => Ok(name.clone()),
|
||||||
|
_ => Err(error_tags::freeze_with_hint(
|
||||||
|
"phase129/join_k/return_expr_unsupported",
|
||||||
|
"branch return expression must be a variable",
|
||||||
|
"use `return x` (variable) in both then/else branches",
|
||||||
|
)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let (cond_ast, then_branch, else_branch) = match if_node {
|
||||||
|
StepNode::If {
|
||||||
|
cond_ast,
|
||||||
|
then_branch,
|
||||||
|
else_branch,
|
||||||
|
..
|
||||||
|
} => (cond_ast, then_branch.as_ref(), else_branch.as_deref()),
|
||||||
|
_ => unreachable!(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let else_branch = match else_branch {
|
||||||
|
Some(b) => b,
|
||||||
|
None => return Ok(None),
|
||||||
|
};
|
||||||
|
|
||||||
|
let (then_prefix, then_ret_ast) = match split_branch_for_as_last(then_branch) {
|
||||||
|
Ok(v) => v,
|
||||||
|
Err(_msg) => return Ok(None),
|
||||||
|
};
|
||||||
|
let (else_prefix, else_ret_ast) = match split_branch_for_as_last(else_branch) {
|
||||||
|
Ok(v) => v,
|
||||||
|
Err(_msg) => return Ok(None),
|
||||||
|
};
|
||||||
|
|
||||||
|
let then_ret_var = match extract_return_var_name(then_ret_ast) {
|
||||||
|
Ok(v) => v,
|
||||||
|
Err(_msg) => return Ok(None),
|
||||||
|
};
|
||||||
|
let else_ret_var = match extract_return_var_name(else_ret_ast) {
|
||||||
|
Ok(v) => v,
|
||||||
|
Err(_msg) => return Ok(None),
|
||||||
|
};
|
||||||
|
|
||||||
|
if then_ret_var != else_ret_var {
|
||||||
|
return Ok(None);
|
||||||
|
}
|
||||||
|
|
||||||
|
let ret_var = then_ret_var;
|
||||||
|
if !env_layout.writes.iter().any(|w| w == &ret_var) {
|
||||||
|
return Ok(None);
|
||||||
|
}
|
||||||
|
|
||||||
|
// join_k(env_phi): return env_phi[ret_var]
|
||||||
|
let join_k_params = alloc_env_params(&env_fields, &mut next_value_id);
|
||||||
|
let env_join_k = build_env_map(&env_fields, &join_k_params);
|
||||||
|
let ret_vid = env_join_k.get(&ret_var).copied().ok_or_else(|| {
|
||||||
|
error_tags::freeze_with_hint(
|
||||||
|
"phase129/join_k/ret_vid_missing",
|
||||||
|
"return variable not found in join_k env",
|
||||||
|
"ensure env layout includes the return variable in writes",
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
let mut join_k_func = JoinFunction::new(join_k_id, "join_k".to_string(), join_k_params);
|
||||||
|
join_k_func.body.push(JoinInst::Ret { value: Some(ret_vid) });
|
||||||
|
|
||||||
|
// k_then(env_in): <prefix> ; tailcall join_k(env_out)
|
||||||
|
let then_params = alloc_env_params(&env_fields, &mut next_value_id);
|
||||||
|
let mut env_then = build_env_map(&env_fields, &then_params);
|
||||||
|
let mut then_func = JoinFunction::new(k_then_id, "k_then".to_string(), then_params);
|
||||||
|
for n in then_prefix {
|
||||||
|
match n {
|
||||||
|
StepNode::Stmt { kind, .. } => match kind {
|
||||||
|
StepStmtKind::Assign { target, value_ast } => {
|
||||||
|
if Self::lower_assign_stmt(
|
||||||
|
target,
|
||||||
|
value_ast,
|
||||||
|
&mut then_func.body,
|
||||||
|
&mut next_value_id,
|
||||||
|
&mut env_then,
|
||||||
|
)
|
||||||
|
.is_err()
|
||||||
|
{
|
||||||
|
return Ok(None);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
StepStmtKind::LocalDecl { .. } => {}
|
||||||
|
_ => {
|
||||||
|
return Ok(None);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
_ => {
|
||||||
|
return Ok(None);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let then_args = collect_env_args(&env_fields, &env_then)?;
|
||||||
|
then_func.body.push(JoinInst::Call {
|
||||||
|
func: join_k_id,
|
||||||
|
args: then_args,
|
||||||
|
k_next: None,
|
||||||
|
dst: None,
|
||||||
|
});
|
||||||
|
|
||||||
|
// k_else(env_in): <prefix> ; tailcall join_k(env_out)
|
||||||
|
let else_params = alloc_env_params(&env_fields, &mut next_value_id);
|
||||||
|
let mut env_else = build_env_map(&env_fields, &else_params);
|
||||||
|
let mut else_func = JoinFunction::new(k_else_id, "k_else".to_string(), else_params);
|
||||||
|
for n in else_prefix {
|
||||||
|
match n {
|
||||||
|
StepNode::Stmt { kind, .. } => match kind {
|
||||||
|
StepStmtKind::Assign { target, value_ast } => {
|
||||||
|
if Self::lower_assign_stmt(
|
||||||
|
target,
|
||||||
|
value_ast,
|
||||||
|
&mut else_func.body,
|
||||||
|
&mut next_value_id,
|
||||||
|
&mut env_else,
|
||||||
|
)
|
||||||
|
.is_err()
|
||||||
|
{
|
||||||
|
return Ok(None);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
StepStmtKind::LocalDecl { .. } => {}
|
||||||
|
_ => {
|
||||||
|
return Ok(None);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
_ => {
|
||||||
|
return Ok(None);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let else_args = collect_env_args(&env_fields, &env_else)?;
|
||||||
|
else_func.body.push(JoinInst::Call {
|
||||||
|
func: join_k_id,
|
||||||
|
args: else_args,
|
||||||
|
k_next: None,
|
||||||
|
dst: None,
|
||||||
|
});
|
||||||
|
|
||||||
|
// main: cond compare + conditional jump to k_then, else to k_else
|
||||||
|
let (lhs_var, op, rhs_literal) = Self::parse_minimal_compare(&cond_ast.0)?;
|
||||||
|
let lhs_vid = env_main.get(&lhs_var).copied().ok_or_else(|| {
|
||||||
|
error_tags::freeze_with_hint(
|
||||||
|
"phase129/join_k/cond_lhs_missing",
|
||||||
|
&format!("condition lhs var '{lhs_var}' not found in env"),
|
||||||
|
"ensure the if condition uses a variable from writes or captured inputs",
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
let rhs_vid = alloc_value_id(&mut next_value_id);
|
||||||
|
main_func.body.push(JoinInst::Compute(MirLikeInst::Const {
|
||||||
|
dst: rhs_vid,
|
||||||
|
value: ConstValue::Integer(rhs_literal),
|
||||||
|
}));
|
||||||
|
let cond_vid = alloc_value_id(&mut next_value_id);
|
||||||
|
main_func.body.push(JoinInst::Compute(MirLikeInst::Compare {
|
||||||
|
dst: cond_vid,
|
||||||
|
op,
|
||||||
|
lhs: lhs_vid,
|
||||||
|
rhs: rhs_vid,
|
||||||
|
}));
|
||||||
|
|
||||||
|
let main_args = collect_env_args(&env_fields, &env_main)?;
|
||||||
|
main_func.body.push(JoinInst::Jump {
|
||||||
|
cont: k_then_id.as_cont(),
|
||||||
|
args: main_args.clone(),
|
||||||
|
cond: Some(cond_vid),
|
||||||
|
});
|
||||||
|
main_func.body.push(JoinInst::Jump {
|
||||||
|
cont: k_else_id.as_cont(),
|
||||||
|
args: main_args,
|
||||||
|
cond: None,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Build module
|
||||||
|
let mut module = JoinModule::new();
|
||||||
|
module.add_function(main_func);
|
||||||
|
module.add_function(then_func);
|
||||||
|
module.add_function(else_func);
|
||||||
|
module.add_function(join_k_func);
|
||||||
|
module.entry = Some(main_id);
|
||||||
|
module.mark_normalized();
|
||||||
|
|
||||||
|
Ok(Some((module, JoinFragmentMeta::empty())))
|
||||||
|
}
|
||||||
|
|
||||||
/// Phase 123-128 P1-P3: Lower node from StepTree
|
/// Phase 123-128 P1-P3: Lower node from StepTree
|
||||||
///
|
///
|
||||||
/// ## Support (Phase 123-128)
|
/// ## Support (Phase 123-128)
|
||||||
@ -263,10 +655,8 @@ impl StepTreeNormalizedShadowLowererBox {
|
|||||||
env: &mut std::collections::BTreeMap<String, crate::mir::ValueId>,
|
env: &mut std::collections::BTreeMap<String, crate::mir::ValueId>,
|
||||||
contract: &crate::mir::control_tree::step_tree_contract_box::StepTreeContract,
|
contract: &crate::mir::control_tree::step_tree_contract_box::StepTreeContract,
|
||||||
) -> Result<(), String> {
|
) -> Result<(), String> {
|
||||||
use crate::ast::{ASTNode, LiteralValue};
|
|
||||||
use crate::mir::control_tree::step_tree::{StepNode, StepStmtKind};
|
use crate::mir::control_tree::step_tree::{StepNode, StepStmtKind};
|
||||||
use crate::mir::join_ir::JoinInst;
|
use crate::mir::join_ir::JoinInst;
|
||||||
use crate::mir::ValueId;
|
|
||||||
|
|
||||||
match node {
|
match node {
|
||||||
StepNode::Block(nodes) => {
|
StepNode::Block(nodes) => {
|
||||||
@ -402,9 +792,8 @@ impl StepTreeNormalizedShadowLowererBox {
|
|||||||
env: &mut std::collections::BTreeMap<String, crate::mir::ValueId>,
|
env: &mut std::collections::BTreeMap<String, crate::mir::ValueId>,
|
||||||
contract: &crate::mir::control_tree::step_tree_contract_box::StepTreeContract,
|
contract: &crate::mir::control_tree::step_tree_contract_box::StepTreeContract,
|
||||||
) -> Result<(), String> {
|
) -> Result<(), String> {
|
||||||
use crate::ast::{ASTNode, BinaryOperator};
|
|
||||||
use crate::mir::control_tree::step_tree::StepNode;
|
use crate::mir::control_tree::step_tree::StepNode;
|
||||||
use crate::mir::join_ir::{CompareOp, ConstValue, JoinInst, MirLikeInst};
|
use crate::mir::join_ir::{ConstValue, JoinInst, MirLikeInst};
|
||||||
use crate::mir::ValueId;
|
use crate::mir::ValueId;
|
||||||
|
|
||||||
if let StepNode::If {
|
if let StepNode::If {
|
||||||
@ -995,7 +1384,6 @@ mod tests {
|
|||||||
// Phase 124 P3: Test Return(Variable) when variable is in env (writes)
|
// Phase 124 P3: Test Return(Variable) when variable is in env (writes)
|
||||||
use crate::ast::{ASTNode, Span};
|
use crate::ast::{ASTNode, Span};
|
||||||
use crate::mir::control_tree::step_tree::AstNodeHandle;
|
use crate::mir::control_tree::step_tree::AstNodeHandle;
|
||||||
use std::collections::BTreeSet;
|
|
||||||
|
|
||||||
// Create StepTree with "local x; return x"
|
// Create StepTree with "local x; return x"
|
||||||
let mut tree = make_if_only_tree();
|
let mut tree = make_if_only_tree();
|
||||||
|
|||||||
@ -13,8 +13,6 @@
|
|||||||
//! - Focus on "did we extract the same information?"
|
//! - Focus on "did we extract the same information?"
|
||||||
|
|
||||||
use crate::mir::control_tree::step_tree_contract_box::StepTreeContract;
|
use crate::mir::control_tree::step_tree_contract_box::StepTreeContract;
|
||||||
use std::collections::BTreeSet;
|
|
||||||
|
|
||||||
/// Mismatch classification
|
/// Mismatch classification
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
pub enum MismatchKind {
|
pub enum MismatchKind {
|
||||||
@ -143,54 +141,164 @@ pub fn check_full_parity(
|
|||||||
///
|
///
|
||||||
/// - Checks function count, continuation count, tail-call form, env args
|
/// - Checks function count, continuation count, tail-call form, env args
|
||||||
/// - Does NOT check actual execution (RC comparison is optional)
|
/// - Does NOT check actual execution (RC comparison is optional)
|
||||||
/// - Returns mismatch with hint if structure is invalid
|
/// - Returns Err(freeze_with_hint) if structure is invalid
|
||||||
pub fn verify_normalized_structure(
|
pub fn verify_normalized_structure(
|
||||||
module: &crate::mir::join_ir::JoinModule,
|
module: &crate::mir::join_ir::JoinModule,
|
||||||
expected_env_fields: usize,
|
expected_env_fields: usize,
|
||||||
) -> ShadowParityResult {
|
) -> Result<(), String> {
|
||||||
use crate::mir::join_ir::JoinIrPhase;
|
use crate::mir::join_ir::{JoinFuncId, JoinInst};
|
||||||
|
use crate::mir::join_ir::lowering::error_tags;
|
||||||
|
|
||||||
// Check phase
|
// Check phase
|
||||||
if !module.is_normalized() {
|
if !module.is_normalized() {
|
||||||
let hint = format!(
|
return Err(error_tags::freeze_with_hint(
|
||||||
"module phase is not Normalized: {:?}",
|
"phase129/join_k/not_normalized",
|
||||||
module.phase
|
&format!("module phase is not Normalized: {:?}", module.phase),
|
||||||
);
|
"ensure the shadow lowering marks module as Normalized",
|
||||||
return ShadowParityResult::mismatch(MismatchKind::UnsupportedKind, hint);
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check function count (Phase 122 minimal: 1 main function)
|
|
||||||
if module.functions.is_empty() {
|
if module.functions.is_empty() {
|
||||||
let hint = "no functions in module".to_string();
|
return Err(error_tags::freeze_with_hint(
|
||||||
return ShadowParityResult::mismatch(MismatchKind::UnsupportedKind, hint);
|
"phase129/join_k/no_functions",
|
||||||
|
"no functions in module",
|
||||||
|
"ensure the shadow lowering emits at least the entry function",
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check entry point
|
// Check entry point
|
||||||
if module.entry.is_none() {
|
let entry_id = module.entry.ok_or_else(|| {
|
||||||
let hint = "no entry point in module".to_string();
|
error_tags::freeze_with_hint(
|
||||||
return ShadowParityResult::mismatch(MismatchKind::UnsupportedKind, hint);
|
"phase129/join_k/no_entry",
|
||||||
|
"no entry point in module",
|
||||||
|
"ensure the shadow lowering sets JoinModule.entry",
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
|
||||||
|
// Check entry function exists
|
||||||
|
let entry_func = module.functions.get(&entry_id).ok_or_else(|| {
|
||||||
|
error_tags::freeze_with_hint(
|
||||||
|
"phase129/join_k/entry_missing",
|
||||||
|
&format!("entry function {:?} not found", entry_id),
|
||||||
|
"ensure the emitted module includes the entry function id",
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
|
||||||
|
// Env layout: writes + inputs (SSOT)
|
||||||
|
if entry_func.params.len() != expected_env_fields {
|
||||||
|
return Err(error_tags::freeze_with_hint(
|
||||||
|
"phase129/join_k/env_arity_mismatch",
|
||||||
|
&format!(
|
||||||
|
"env args mismatch: expected {}, got {}",
|
||||||
|
expected_env_fields,
|
||||||
|
entry_func.params.len()
|
||||||
|
),
|
||||||
|
"ensure env params are built from (writes + inputs) SSOT",
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check main function exists
|
// All functions in this shadow module must share the same env param arity.
|
||||||
let entry_id = module.entry.unwrap();
|
for (fid, func) in &module.functions {
|
||||||
let main_func = module.functions.get(&entry_id);
|
if func.params.len() != expected_env_fields {
|
||||||
if main_func.is_none() {
|
return Err(error_tags::freeze_with_hint(
|
||||||
let hint = format!("entry function {:?} not found", entry_id);
|
"phase129/join_k/env_arity_mismatch",
|
||||||
return ShadowParityResult::mismatch(MismatchKind::UnsupportedKind, hint);
|
&format!(
|
||||||
|
"env args mismatch in {:?}: expected {}, got {}",
|
||||||
|
fid,
|
||||||
|
expected_env_fields,
|
||||||
|
func.params.len()
|
||||||
|
),
|
||||||
|
"ensure all continuations share the same env layout (writes + inputs)",
|
||||||
|
));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check env args count (Phase 122: writes only)
|
// PHI prohibition (Phase 129-B scope): no IfMerge/NestedIfMerge in shadow output.
|
||||||
let main_func = main_func.unwrap();
|
for (fid, func) in &module.functions {
|
||||||
if main_func.params.len() != expected_env_fields {
|
for inst in &func.body {
|
||||||
let hint = format!(
|
if matches!(inst, JoinInst::IfMerge { .. } | JoinInst::NestedIfMerge { .. }) {
|
||||||
"env args mismatch: expected {}, got {}",
|
return Err(error_tags::freeze_with_hint(
|
||||||
expected_env_fields,
|
"phase129/join_k/phi_forbidden",
|
||||||
main_func.params.len()
|
&format!("PHI-like merge instruction found in {:?}", fid),
|
||||||
);
|
"Phase 129-B join_k path forbids IfMerge/NestedIfMerge; use join_k tailcall merge instead",
|
||||||
return ShadowParityResult::mismatch(MismatchKind::UnsupportedKind, hint);
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ShadowParityResult::ok()
|
// Detect join_k tailcall form (if present) and validate it.
|
||||||
|
fn tailcall_target(func: &crate::mir::join_ir::JoinFunction) -> Option<(JoinFuncId, usize)> {
|
||||||
|
match func.body.last()? {
|
||||||
|
JoinInst::Call {
|
||||||
|
func,
|
||||||
|
args,
|
||||||
|
k_next: None,
|
||||||
|
dst: None,
|
||||||
|
} => Some((*func, args.len())),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut tailcall_targets: Vec<(JoinFuncId, usize)> = Vec::new();
|
||||||
|
for func in module.functions.values() {
|
||||||
|
if let Some((target, argc)) = tailcall_target(func) {
|
||||||
|
tailcall_targets.push((target, argc));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if tailcall_targets.is_empty() {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
// join_k merge should have at least two branch continuations tailcalling the same target.
|
||||||
|
if tailcall_targets.len() < 2 {
|
||||||
|
return Err(error_tags::freeze_with_hint(
|
||||||
|
"phase129/join_k/tailcall_count",
|
||||||
|
&format!(
|
||||||
|
"join_k tailcall form requires >=2 tailcalls, got {}",
|
||||||
|
tailcall_targets.len()
|
||||||
|
),
|
||||||
|
"ensure both then/else branches tailcall join_k as the last instruction",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
let first_target = tailcall_targets[0].0;
|
||||||
|
for (target, argc) in &tailcall_targets {
|
||||||
|
if *target != first_target {
|
||||||
|
return Err(error_tags::freeze_with_hint(
|
||||||
|
"phase129/join_k/tailcall_target_mismatch",
|
||||||
|
"tailcalls do not target a single join_k function",
|
||||||
|
"ensure then/else both tailcall the same join_k function id",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
if *argc != expected_env_fields {
|
||||||
|
return Err(error_tags::freeze_with_hint(
|
||||||
|
"phase129/join_k/tailcall_arg_arity_mismatch",
|
||||||
|
&format!(
|
||||||
|
"tailcall env arg count mismatch: expected {}, got {}",
|
||||||
|
expected_env_fields, argc
|
||||||
|
),
|
||||||
|
"ensure join_k is called with the full env fields list (writes + inputs)",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let join_k_func = module.functions.get(&first_target).ok_or_else(|| {
|
||||||
|
error_tags::freeze_with_hint(
|
||||||
|
"phase129/join_k/join_k_missing",
|
||||||
|
"tailcall target join_k function not found in module",
|
||||||
|
"ensure join_k is registered in JoinModule.functions",
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
|
||||||
|
match join_k_func.body.last() {
|
||||||
|
Some(JoinInst::Ret { value: Some(_) }) => Ok(()),
|
||||||
|
_ => Err(error_tags::freeze_with_hint(
|
||||||
|
"phase129/join_k/join_k_not_ret",
|
||||||
|
"join_k must end with Ret(Some(value))",
|
||||||
|
"ensure join_k returns the merged env variable and has no post-if continuation in Phase 129-B",
|
||||||
|
)),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
|||||||
Reference in New Issue
Block a user