feat(control_tree): Phase 130 assign var/add in if-only normalized
This commit is contained in:
@ -27,12 +27,15 @@ impl EnvLayout {
|
|||||||
) -> Self {
|
) -> Self {
|
||||||
// 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();
|
||||||
|
let writes_set: std::collections::BTreeSet<&String> = contract.writes.iter().collect();
|
||||||
|
|
||||||
// Phase 125 P2: inputs = reads ∩ available_inputs (deterministic order)
|
// Phase 125 P2: inputs = (reads ∩ available_inputs) \ writes
|
||||||
|
// inputs are read-only by definition; if a variable is written, it must not be treated as an input.
|
||||||
let inputs: Vec<String> = contract
|
let inputs: Vec<String> = contract
|
||||||
.reads
|
.reads
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|name| available_inputs.contains_key(*name))
|
.filter(|name| available_inputs.contains_key(*name))
|
||||||
|
.filter(|name| !writes_set.contains(name))
|
||||||
.cloned()
|
.cloned()
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
|
|||||||
@ -161,7 +161,7 @@ impl LegacyLowerer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Phase 128: Lower assign statement (int literal only)
|
/// Phase 128-130: Lower assign statement (int literal, variable, add)
|
||||||
pub fn lower_assign_stmt(
|
pub fn lower_assign_stmt(
|
||||||
target: &Option<String>,
|
target: &Option<String>,
|
||||||
value_ast: &Option<crate::mir::control_tree::step_tree::AstNodeHandle>,
|
value_ast: &Option<crate::mir::control_tree::step_tree::AstNodeHandle>,
|
||||||
@ -169,8 +169,9 @@ impl LegacyLowerer {
|
|||||||
next_value_id: &mut u32,
|
next_value_id: &mut u32,
|
||||||
env: &mut BTreeMap<String, ValueId>,
|
env: &mut BTreeMap<String, ValueId>,
|
||||||
) -> Result<(), String> {
|
) -> Result<(), String> {
|
||||||
use crate::ast::{ASTNode, LiteralValue};
|
use crate::ast::{ASTNode, BinaryOperator, LiteralValue};
|
||||||
use crate::mir::join_ir::{ConstValue, JoinInst, MirLikeInst};
|
use crate::mir::join_ir::{BinOpKind, ConstValue, JoinInst, MirLikeInst};
|
||||||
|
use crate::mir::join_ir::lowering::error_tags;
|
||||||
|
|
||||||
// Check target
|
// Check target
|
||||||
let target_name = target
|
let target_name = target
|
||||||
@ -182,8 +183,9 @@ impl LegacyLowerer {
|
|||||||
"[phase128/assign/value] Assign value AST is missing".to_string()
|
"[phase128/assign/value] Assign value AST is missing".to_string()
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
// Parse value - Phase 128: int literal only
|
// Parse value - Phase 128: int literal, Phase 130 P1: variable, Phase 130 P2: add
|
||||||
match value_ast.0.as_ref() {
|
match value_ast.0.as_ref() {
|
||||||
|
// Phase 128: Integer literal
|
||||||
ASTNode::Literal {
|
ASTNode::Literal {
|
||||||
value: LiteralValue::Integer(i),
|
value: LiteralValue::Integer(i),
|
||||||
..
|
..
|
||||||
@ -202,10 +204,116 @@ impl LegacyLowerer {
|
|||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
// Phase 130 P1: Variable (x = y)
|
||||||
|
ASTNode::Variable { name, .. } => {
|
||||||
|
// RHS must resolve from env (writes or inputs)
|
||||||
|
let src_vid = env.get(name).copied().ok_or_else(|| {
|
||||||
|
error_tags::freeze_with_hint(
|
||||||
|
"phase130/assign/var/rhs_missing",
|
||||||
|
&format!("RHS variable '{name}' not found in env"),
|
||||||
|
"ensure the variable is defined before assignment (in writes or inputs)",
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
|
||||||
|
// Phase 130 P1: Variable assignment in Normalized IR
|
||||||
|
// In the continuation-passing style, we just update the env map.
|
||||||
|
// The actual SSA phi/merge happens at function boundaries via env args.
|
||||||
|
// No instruction emission needed here.
|
||||||
|
env.insert(target_name.clone(), src_vid);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
// Phase 130 P2: BinaryOp (x = x + <int literal> only)
|
||||||
|
ASTNode::BinaryOp {
|
||||||
|
operator,
|
||||||
|
left,
|
||||||
|
right,
|
||||||
|
..
|
||||||
|
} => {
|
||||||
|
// Phase 130 P2 contract: only Add with specific shape
|
||||||
|
if !matches!(operator, BinaryOperator::Add) {
|
||||||
|
return Err(error_tags::freeze_with_hint(
|
||||||
|
"phase130/assign/add/unsupported_op",
|
||||||
|
&format!("Phase 130 only supports Add operator, got {:?}", operator),
|
||||||
|
"use x = x + <literal> pattern or wait for future phases",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Left must be variable
|
||||||
|
let lhs_var = match &**left {
|
||||||
|
ASTNode::Variable { name, .. } => name.clone(),
|
||||||
|
_ => {
|
||||||
|
return Err(error_tags::freeze_with_hint(
|
||||||
|
"phase130/assign/add/lhs_not_var",
|
||||||
|
"Phase 130 Add: LHS must be a variable",
|
||||||
|
"use pattern x = x + <literal>",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Right must be integer literal
|
||||||
|
let rhs_int = match &**right {
|
||||||
|
ASTNode::Literal {
|
||||||
|
value: LiteralValue::Integer(i),
|
||||||
|
..
|
||||||
|
} => *i,
|
||||||
|
_ => {
|
||||||
|
return Err(error_tags::freeze_with_hint(
|
||||||
|
"phase130/assign/add/rhs_not_int_literal",
|
||||||
|
"Phase 130 Add: RHS must be integer literal",
|
||||||
|
"use pattern x = x + <literal>",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Phase 130 P2 contract: dst must equal lhs (x = x + 3, not y = x + 3)
|
||||||
|
if target_name != &lhs_var {
|
||||||
|
return Err(error_tags::freeze_with_hint(
|
||||||
|
"phase130/assign/add/dst_neq_lhs",
|
||||||
|
&format!(
|
||||||
|
"Phase 130 Add: dst '{}' must equal lhs '{}' (x = x + 3 pattern)",
|
||||||
|
target_name, lhs_var
|
||||||
|
),
|
||||||
|
"use pattern x = x + <literal> where dst == lhs",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load lhs from env
|
||||||
|
let lhs_vid = env.get(&lhs_var).copied().ok_or_else(|| {
|
||||||
|
error_tags::freeze_with_hint(
|
||||||
|
"phase130/assign/add/lhs_missing",
|
||||||
|
&format!("Add LHS variable '{}' not found in env", lhs_var),
|
||||||
|
"ensure the variable is defined before the add operation",
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
|
||||||
|
// Create constant for RHS
|
||||||
|
let rhs_vid = ValueId(*next_value_id);
|
||||||
|
*next_value_id += 1;
|
||||||
|
body.push(JoinInst::Compute(MirLikeInst::Const {
|
||||||
|
dst: rhs_vid,
|
||||||
|
value: ConstValue::Integer(rhs_int),
|
||||||
|
}));
|
||||||
|
|
||||||
|
// Generate BinOp Add
|
||||||
|
let result_vid = ValueId(*next_value_id);
|
||||||
|
*next_value_id += 1;
|
||||||
|
body.push(JoinInst::Compute(MirLikeInst::BinOp {
|
||||||
|
dst: result_vid,
|
||||||
|
op: BinOpKind::Add,
|
||||||
|
lhs: lhs_vid,
|
||||||
|
rhs: rhs_vid,
|
||||||
|
}));
|
||||||
|
|
||||||
|
// Update env
|
||||||
|
env.insert(target_name.clone(), result_vid);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
_ => {
|
_ => {
|
||||||
// Phase 128 limitation: only int literal supported
|
// Out of scope
|
||||||
Err(format!(
|
Err(format!(
|
||||||
"[phase128/assign/unsupported] Phase128 supports int literal only Hint: Assign RHS must be an integer literal (e.g., x = 42)"
|
"[phase130/assign/unsupported] Phase 130 supports: int literal, variable, or x = x + <int literal> Hint: Use supported pattern or wait for future phases"
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,6 +5,8 @@
|
|||||||
|
|
||||||
use crate::mir::join_ir::{JoinFuncId, JoinInst, JoinModule};
|
use crate::mir::join_ir::{JoinFuncId, JoinInst, JoinModule};
|
||||||
use crate::mir::join_ir::lowering::error_tags;
|
use crate::mir::join_ir::lowering::error_tags;
|
||||||
|
use super::env_layout::EnvLayout;
|
||||||
|
use std::collections::BTreeMap;
|
||||||
|
|
||||||
/// Verify Normalized JoinModule structure emitted by the shadow lowerer.
|
/// Verify Normalized JoinModule structure emitted by the shadow lowerer.
|
||||||
///
|
///
|
||||||
@ -224,3 +226,38 @@ pub fn verify_normalized_structure(
|
|||||||
)),
|
)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Phase 130 P3: Verify env map keyset stays within env layout
|
||||||
|
///
|
||||||
|
/// ## Contract
|
||||||
|
/// - The env map must not introduce variables outside the env layout (`writes + inputs`)
|
||||||
|
///
|
||||||
|
/// ## Implementation Note
|
||||||
|
/// This is a structural check only. It does not (yet) prove that `inputs` are never
|
||||||
|
/// reassigned; it only ensures the env map does not grow beyond the declared layout.
|
||||||
|
pub fn verify_env_writes_discipline(
|
||||||
|
env_map: &BTreeMap<String, crate::mir::ValueId>,
|
||||||
|
env_layout: &EnvLayout,
|
||||||
|
context: &str,
|
||||||
|
) -> Result<(), String> {
|
||||||
|
let expected_fields: std::collections::BTreeSet<&String> = env_layout
|
||||||
|
.writes
|
||||||
|
.iter()
|
||||||
|
.chain(env_layout.inputs.iter())
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
for var_name in env_map.keys() {
|
||||||
|
if !expected_fields.contains(var_name) {
|
||||||
|
return Err(error_tags::freeze_with_hint(
|
||||||
|
"phase130/verifier/env_unexpected_var",
|
||||||
|
&format!(
|
||||||
|
"{}: unexpected variable '{}' in env map (not in writes or inputs)",
|
||||||
|
context, var_name
|
||||||
|
),
|
||||||
|
"ensure env updates only reference variables from the env layout (writes + inputs)",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user