using: safer seam defaults (fix_braces OFF by default) + path-alias handling; json_native: robust integer parse + EscapeUtils unquote; add JsonCompat layer; builder: preindex static methods + fallback for bare calls; diagnostics: seam dump + function-call trace
This commit is contained in:
49
src/mir/phi_core/common.rs
Normal file
49
src/mir/phi_core/common.rs
Normal file
@ -0,0 +1,49 @@
|
||||
/*!
|
||||
* phi_core::common – shared types and invariants (scaffold)
|
||||
*
|
||||
* Phase 1 keeps this minimal; future phases may move debug asserts and
|
||||
* predicate set checks here for both if/loop PHI normalization.
|
||||
*/
|
||||
|
||||
/// Placeholder for future shared PHI input type alias.
|
||||
/// Using the same tuple form as MIR Phi instruction inputs.
|
||||
pub type PhiInput = (crate::mir::BasicBlockId, crate::mir::ValueId);
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
pub fn debug_verify_phi_inputs(
|
||||
function: &crate::mir::MirFunction,
|
||||
merge_bb: crate::mir::BasicBlockId,
|
||||
inputs: &[(crate::mir::BasicBlockId, crate::mir::ValueId)],
|
||||
) {
|
||||
use std::collections::HashSet;
|
||||
let mut seen = HashSet::new();
|
||||
for (pred, _v) in inputs.iter() {
|
||||
debug_assert_ne!(
|
||||
*pred, merge_bb,
|
||||
"PHI incoming predecessor must not be the merge block itself"
|
||||
);
|
||||
debug_assert!(
|
||||
seen.insert(*pred),
|
||||
"Duplicate PHI incoming predecessor detected: {:?}",
|
||||
pred
|
||||
);
|
||||
}
|
||||
if let Some(block) = function.blocks.get(&merge_bb) {
|
||||
for (pred, _v) in inputs.iter() {
|
||||
debug_assert!(
|
||||
block.predecessors.contains(pred),
|
||||
"PHI incoming pred {:?} is not a predecessor of merge bb {:?}",
|
||||
pred,
|
||||
merge_bb
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(debug_assertions))]
|
||||
pub fn debug_verify_phi_inputs(
|
||||
_function: &crate::mir::MirFunction,
|
||||
_merge_bb: crate::mir::BasicBlockId,
|
||||
_inputs: &[(crate::mir::BasicBlockId, crate::mir::ValueId)],
|
||||
) {
|
||||
}
|
||||
222
src/mir/phi_core/if_phi.rs
Normal file
222
src/mir/phi_core/if_phi.rs
Normal file
@ -0,0 +1,222 @@
|
||||
/*!
|
||||
* phi_core::if_phi – if/else PHI helpers (Phase 2)
|
||||
*
|
||||
* Public thin wrappers that mirror the semantics of existing builder::phi
|
||||
* helpers. Implemented locally to avoid depending on private submodules.
|
||||
* Behavior is identical to the current in-tree logic.
|
||||
*/
|
||||
|
||||
use crate::ast::ASTNode;
|
||||
use crate::mir::{MirFunction, MirInstruction, MirType, ValueId};
|
||||
use std::collections::HashMap;
|
||||
|
||||
/// Infer return type by scanning for a Phi that defines `ret_val` and
|
||||
/// verifying that all incoming values have the same type in `types`.
|
||||
pub fn infer_type_from_phi(
|
||||
function: &MirFunction,
|
||||
ret_val: ValueId,
|
||||
types: &HashMap<ValueId, MirType>,
|
||||
) -> Option<MirType> {
|
||||
for (_bid, bb) in function.blocks.iter() {
|
||||
for inst in bb.instructions.iter() {
|
||||
if let MirInstruction::Phi { dst, inputs } = inst {
|
||||
if *dst == ret_val {
|
||||
let mut it = inputs.iter().filter_map(|(_, v)| types.get(v));
|
||||
if let Some(first) = it.next() {
|
||||
if it.all(|mt| mt == first) {
|
||||
return Some(first.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
/// Extract the assigned variable name from an AST fragment commonly used in
|
||||
/// if/else analysis. Same logic as builder::phi::extract_assigned_var.
|
||||
pub fn extract_assigned_var(ast: &ASTNode) -> Option<String> {
|
||||
match ast {
|
||||
ASTNode::Assignment { target, .. } => {
|
||||
if let ASTNode::Variable { name, .. } = target.as_ref() {
|
||||
Some(name.clone())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
ASTNode::Program { statements, .. } => {
|
||||
statements.last().and_then(|st| extract_assigned_var(st))
|
||||
}
|
||||
ASTNode::If { then_body, else_body, .. } => {
|
||||
let then_prog = ASTNode::Program {
|
||||
statements: then_body.clone(),
|
||||
span: crate::ast::Span::unknown(),
|
||||
};
|
||||
let tvar = extract_assigned_var(&then_prog);
|
||||
let evar = else_body.as_ref().and_then(|eb| {
|
||||
let ep = ASTNode::Program {
|
||||
statements: eb.clone(),
|
||||
span: crate::ast::Span::unknown(),
|
||||
};
|
||||
extract_assigned_var(&ep)
|
||||
});
|
||||
match (tvar, evar) {
|
||||
(Some(tv), Some(ev)) if tv == ev => Some(tv),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Collect all variable names that are assigned within the given AST subtree.
|
||||
/// Useful for computing PHI merge candidates across branches/blocks.
|
||||
pub fn collect_assigned_vars(ast: &ASTNode, out: &mut std::collections::HashSet<String>) {
|
||||
match ast {
|
||||
ASTNode::Assignment { target, .. } => {
|
||||
if let ASTNode::Variable { name, .. } = target.as_ref() {
|
||||
out.insert(name.clone());
|
||||
}
|
||||
}
|
||||
ASTNode::Program { statements, .. } => {
|
||||
for s in statements { collect_assigned_vars(s, out); }
|
||||
}
|
||||
ASTNode::If { then_body, else_body, .. } => {
|
||||
let tp = ASTNode::Program { statements: then_body.clone(), span: crate::ast::Span::unknown() };
|
||||
collect_assigned_vars(&tp, out);
|
||||
if let Some(eb) = else_body {
|
||||
let ep = ASTNode::Program { statements: eb.clone(), span: crate::ast::Span::unknown() };
|
||||
collect_assigned_vars(&ep, out);
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
/// Compute the set of variable names whose values changed in either branch
|
||||
/// relative to the pre-if snapshot.
|
||||
pub fn compute_modified_names(
|
||||
pre_if_snapshot: &HashMap<String, ValueId>,
|
||||
then_map_end: &HashMap<String, ValueId>,
|
||||
else_map_end_opt: &Option<HashMap<String, ValueId>>,
|
||||
) -> Vec<String> {
|
||||
use std::collections::HashSet;
|
||||
let mut names: HashSet<&str> = HashSet::new();
|
||||
for k in then_map_end.keys() { names.insert(k.as_str()); }
|
||||
if let Some(emap) = else_map_end_opt.as_ref() {
|
||||
for k in emap.keys() { names.insert(k.as_str()); }
|
||||
}
|
||||
let mut changed: Vec<String> = Vec::new();
|
||||
for &name in &names {
|
||||
let pre = pre_if_snapshot.get(name);
|
||||
let t = then_map_end.get(name);
|
||||
let e = else_map_end_opt.as_ref().and_then(|m| m.get(name));
|
||||
if (t.is_some() && Some(*t.unwrap()) != pre.copied())
|
||||
|| (e.is_some() && Some(*e.unwrap()) != pre.copied())
|
||||
{
|
||||
changed.push(name.to_string());
|
||||
}
|
||||
}
|
||||
changed
|
||||
}
|
||||
|
||||
/// Operations required for emitting a PHI or direct binding at a merge point.
|
||||
pub trait PhiMergeOps {
|
||||
fn new_value(&mut self) -> ValueId;
|
||||
fn emit_phi_at_block_start(
|
||||
&mut self,
|
||||
block: crate::mir::BasicBlockId,
|
||||
dst: ValueId,
|
||||
inputs: Vec<(crate::mir::BasicBlockId, ValueId)>,
|
||||
) -> Result<(), String>;
|
||||
fn update_var(&mut self, name: String, value: ValueId);
|
||||
fn debug_verify_phi_inputs(
|
||||
&mut self,
|
||||
_merge_bb: crate::mir::BasicBlockId,
|
||||
_inputs: &[(crate::mir::BasicBlockId, ValueId)],
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
||||
/// Merge variables modified in branches at the merge block using provided ops.
|
||||
/// Handles both two-pred and single-pred (reachable) cases gracefully.
|
||||
pub fn merge_modified_at_merge_with<O: PhiMergeOps>(
|
||||
ops: &mut O,
|
||||
merge_bb: crate::mir::BasicBlockId,
|
||||
_then_block: crate::mir::BasicBlockId,
|
||||
else_block: crate::mir::BasicBlockId,
|
||||
then_pred_opt: Option<crate::mir::BasicBlockId>,
|
||||
else_pred_opt: Option<crate::mir::BasicBlockId>,
|
||||
pre_if_snapshot: &HashMap<String, ValueId>,
|
||||
then_map_end: &HashMap<String, ValueId>,
|
||||
else_map_end_opt: &Option<HashMap<String, ValueId>>,
|
||||
skip_var: Option<&str>,
|
||||
) -> Result<(), String> {
|
||||
let changed = compute_modified_names(pre_if_snapshot, then_map_end, else_map_end_opt);
|
||||
for name in changed {
|
||||
if skip_var.map(|s| s == name).unwrap_or(false) {
|
||||
continue;
|
||||
}
|
||||
let pre = match pre_if_snapshot.get(name.as_str()) {
|
||||
Some(v) => *v,
|
||||
None => continue,
|
||||
};
|
||||
let then_v = then_map_end.get(name.as_str()).copied().unwrap_or(pre);
|
||||
let else_v = else_map_end_opt
|
||||
.as_ref()
|
||||
.and_then(|m| m.get(name.as_str()).copied())
|
||||
.unwrap_or(pre);
|
||||
|
||||
// Build incoming pairs from reachable predecessors only
|
||||
let mut inputs: Vec<(crate::mir::BasicBlockId, ValueId)> = Vec::new();
|
||||
if let Some(tp) = then_pred_opt { inputs.push((tp, then_v)); }
|
||||
if let Some(ep) = else_pred_opt.or(Some(else_block)) { inputs.push((ep, else_v)); }
|
||||
|
||||
match inputs.len() {
|
||||
0 => {}
|
||||
1 => {
|
||||
let (_pred, v) = inputs[0];
|
||||
ops.update_var(name, v);
|
||||
}
|
||||
_ => {
|
||||
ops.debug_verify_phi_inputs(merge_bb, &inputs);
|
||||
let dst = ops.new_value();
|
||||
ops.emit_phi_at_block_start(merge_bb, dst, inputs)?;
|
||||
ops.update_var(name, dst);
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Convenience wrapper: reset variable map (via a caller-provided closure)
|
||||
/// then perform merge at the merge block. Keeps caller simple while
|
||||
/// avoiding tying phi_core to concrete builder internals.
|
||||
pub fn merge_with_reset_at_merge_with<O: PhiMergeOps>(
|
||||
ops: &mut O,
|
||||
merge_bb: crate::mir::BasicBlockId,
|
||||
then_block: crate::mir::BasicBlockId,
|
||||
else_block: crate::mir::BasicBlockId,
|
||||
then_pred_opt: Option<crate::mir::BasicBlockId>,
|
||||
else_pred_opt: Option<crate::mir::BasicBlockId>,
|
||||
pre_if_snapshot: &HashMap<String, ValueId>,
|
||||
then_map_end: &HashMap<String, ValueId>,
|
||||
else_map_end_opt: &Option<HashMap<String, ValueId>>,
|
||||
reset_vars: impl FnOnce(),
|
||||
skip_var: Option<&str>,
|
||||
) -> Result<(), String> {
|
||||
reset_vars();
|
||||
merge_modified_at_merge_with(
|
||||
ops,
|
||||
merge_bb,
|
||||
then_block,
|
||||
else_block,
|
||||
then_pred_opt,
|
||||
else_pred_opt,
|
||||
pre_if_snapshot,
|
||||
then_map_end,
|
||||
else_map_end_opt,
|
||||
skip_var,
|
||||
)
|
||||
}
|
||||
181
src/mir/phi_core/loop_phi.rs
Normal file
181
src/mir/phi_core/loop_phi.rs
Normal file
@ -0,0 +1,181 @@
|
||||
/*!
|
||||
* phi_core::loop_phi – loop-specific PHI management (scaffold)
|
||||
*
|
||||
* Phase 1 defines minimal types only. The concrete logic remains in
|
||||
* `mir::loop_builder` and will be delegated/moved here in later phases.
|
||||
*/
|
||||
|
||||
use crate::mir::{BasicBlockId, ValueId};
|
||||
use crate::ast::ASTNode;
|
||||
|
||||
/// Loop-local placeholder of an incomplete PHI (header-time declaration).
|
||||
/// Moved from loop_builder to centralize PHI-related types.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct IncompletePhi {
|
||||
pub phi_id: ValueId,
|
||||
pub var_name: String,
|
||||
pub known_inputs: Vec<(BasicBlockId, ValueId)>,
|
||||
}
|
||||
|
||||
/// Common snapshot type used for continue/exit points
|
||||
pub type VarSnapshot = std::collections::HashMap<String, ValueId>;
|
||||
pub type SnapshotAt = (BasicBlockId, VarSnapshot);
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct LoopPhiManager;
|
||||
|
||||
impl LoopPhiManager {
|
||||
pub fn new() -> Self { Self::default() }
|
||||
}
|
||||
|
||||
/// Operations required from a loop builder to finalize PHIs.
|
||||
pub trait LoopPhiOps {
|
||||
fn new_value(&mut self) -> ValueId;
|
||||
fn emit_phi_at_block_start(
|
||||
&mut self,
|
||||
block: BasicBlockId,
|
||||
dst: ValueId,
|
||||
inputs: Vec<(BasicBlockId, ValueId)>,
|
||||
) -> Result<(), String>;
|
||||
fn update_var(&mut self, name: String, value: ValueId);
|
||||
fn get_variable_at_block(&mut self, name: &str, block: BasicBlockId) -> Option<ValueId>;
|
||||
fn debug_verify_phi_inputs(&mut self, _merge_bb: BasicBlockId, _inputs: &[(BasicBlockId, ValueId)]) {}
|
||||
}
|
||||
|
||||
/// Finalize PHIs at loop exit (merge of break points and header fall-through).
|
||||
/// Behavior mirrors loop_builder's create_exit_phis using the provided ops.
|
||||
pub fn build_exit_phis_with<O: LoopPhiOps>(
|
||||
ops: &mut O,
|
||||
header_id: BasicBlockId,
|
||||
exit_id: BasicBlockId,
|
||||
header_vars: &std::collections::HashMap<String, ValueId>,
|
||||
exit_snapshots: &[(BasicBlockId, VarSnapshot)],
|
||||
) -> Result<(), String> {
|
||||
// 1) Collect all variable names possibly participating in exit PHIs
|
||||
let mut all_vars = std::collections::HashSet::new();
|
||||
for var_name in header_vars.keys() {
|
||||
all_vars.insert(var_name.clone());
|
||||
}
|
||||
for (_bid, snapshot) in exit_snapshots.iter() {
|
||||
for var_name in snapshot.keys() {
|
||||
all_vars.insert(var_name.clone());
|
||||
}
|
||||
}
|
||||
|
||||
// 2) For each variable, gather incoming values
|
||||
for var_name in all_vars {
|
||||
let mut phi_inputs: Vec<(BasicBlockId, ValueId)> = Vec::new();
|
||||
|
||||
if let Some(&hv) = header_vars.get(&var_name) {
|
||||
phi_inputs.push((header_id, hv));
|
||||
}
|
||||
for (block_id, snapshot) in exit_snapshots.iter() {
|
||||
if let Some(&v) = snapshot.get(&var_name) {
|
||||
phi_inputs.push((*block_id, v));
|
||||
}
|
||||
}
|
||||
|
||||
match phi_inputs.len() {
|
||||
0 => {} // nothing to do
|
||||
1 => {
|
||||
// single predecessor: direct binding
|
||||
ops.update_var(var_name, phi_inputs[0].1);
|
||||
}
|
||||
_ => {
|
||||
let dst = ops.new_value();
|
||||
ops.debug_verify_phi_inputs(exit_id, &phi_inputs);
|
||||
ops.emit_phi_at_block_start(exit_id, dst, phi_inputs)?;
|
||||
ops.update_var(var_name, dst);
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Seal a header block by completing its incomplete PHIs with values from
|
||||
/// continue snapshots and the latch block.
|
||||
pub fn seal_incomplete_phis_with<O: LoopPhiOps>(
|
||||
ops: &mut O,
|
||||
block_id: BasicBlockId,
|
||||
latch_id: BasicBlockId,
|
||||
mut incomplete_phis: Vec<IncompletePhi>,
|
||||
continue_snapshots: &[(BasicBlockId, VarSnapshot)],
|
||||
) -> Result<(), String> {
|
||||
for mut phi in incomplete_phis.drain(..) {
|
||||
// from continue points
|
||||
for (cid, snapshot) in continue_snapshots.iter() {
|
||||
if let Some(&v) = snapshot.get(&phi.var_name) {
|
||||
phi.known_inputs.push((*cid, v));
|
||||
}
|
||||
}
|
||||
// from latch
|
||||
let value_after = ops
|
||||
.get_variable_at_block(&phi.var_name, latch_id)
|
||||
.ok_or_else(|| format!("Variable {} not found at latch block", phi.var_name))?;
|
||||
phi.known_inputs.push((latch_id, value_after));
|
||||
|
||||
ops.debug_verify_phi_inputs(block_id, &phi.known_inputs);
|
||||
ops.emit_phi_at_block_start(block_id, phi.phi_id, phi.known_inputs)?;
|
||||
ops.update_var(phi.var_name.clone(), phi.phi_id);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Prepare loop header PHIs by declaring one IncompletePhi per variable found
|
||||
/// in `current_vars` (preheader snapshot), seeding each with (preheader_id, val)
|
||||
/// and rebinding the variable to the newly allocated Phi result in the builder.
|
||||
pub fn prepare_loop_variables_with<O: LoopPhiOps>(
|
||||
ops: &mut O,
|
||||
_header_id: BasicBlockId,
|
||||
preheader_id: BasicBlockId,
|
||||
current_vars: &std::collections::HashMap<String, ValueId>,
|
||||
) -> Result<Vec<IncompletePhi>, String> {
|
||||
let mut incomplete_phis: Vec<IncompletePhi> = Vec::new();
|
||||
for (var_name, &value_before) in current_vars.iter() {
|
||||
let phi_id = ops.new_value();
|
||||
let inc = IncompletePhi {
|
||||
phi_id,
|
||||
var_name: var_name.clone(),
|
||||
known_inputs: vec![(preheader_id, value_before)],
|
||||
};
|
||||
incomplete_phis.push(inc);
|
||||
ops.update_var(var_name.clone(), phi_id);
|
||||
}
|
||||
Ok(incomplete_phis)
|
||||
}
|
||||
|
||||
/// Collect variables assigned within a loop body and detect control-flow
|
||||
/// statements (break/continue). Used for lightweight carrier hinting.
|
||||
pub fn collect_carrier_assigns(node: &ASTNode, vars: &mut Vec<String>, has_ctrl: &mut bool) {
|
||||
match node {
|
||||
ASTNode::Assignment { target, .. } => {
|
||||
if let ASTNode::Variable { name, .. } = target.as_ref() {
|
||||
if !vars.iter().any(|v| v == name) {
|
||||
vars.push(name.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
ASTNode::Break { .. } | ASTNode::Continue { .. } => { *has_ctrl = true; }
|
||||
ASTNode::If { then_body, else_body, .. } => {
|
||||
let tp = ASTNode::Program { statements: then_body.clone(), span: crate::ast::Span::unknown() };
|
||||
collect_carrier_assigns(&tp, vars, has_ctrl);
|
||||
if let Some(eb) = else_body {
|
||||
let ep = ASTNode::Program { statements: eb.clone(), span: crate::ast::Span::unknown() };
|
||||
collect_carrier_assigns(&ep, vars, has_ctrl);
|
||||
}
|
||||
}
|
||||
ASTNode::Program { statements, .. } => {
|
||||
for s in statements { collect_carrier_assigns(s, vars, has_ctrl); }
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
/// Save a block-local variable snapshot into the provided store.
|
||||
pub fn save_block_snapshot(
|
||||
store: &mut std::collections::HashMap<BasicBlockId, VarSnapshot>,
|
||||
block: BasicBlockId,
|
||||
snapshot: &VarSnapshot,
|
||||
) {
|
||||
store.insert(block, snapshot.clone());
|
||||
}
|
||||
17
src/mir/phi_core/mod.rs
Normal file
17
src/mir/phi_core/mod.rs
Normal file
@ -0,0 +1,17 @@
|
||||
/*!
|
||||
* phi_core – Unified PHI management scaffold (Phase 1)
|
||||
*
|
||||
* Purpose:
|
||||
* - Provide a single, stable entry point for PHI-related helpers.
|
||||
* - Start with re-exports of existing, verified logic (zero behavior change).
|
||||
* - Prepare ground for gradual consolidation of loop PHI handling.
|
||||
*/
|
||||
|
||||
pub mod common;
|
||||
pub mod if_phi;
|
||||
pub mod loop_phi;
|
||||
|
||||
// Public surface for callers that want a stable path:
|
||||
// Phase 1: No re-exports to avoid touching private builder internals.
|
||||
// Callers should continue using existing paths. Future phases may expose
|
||||
// stable wrappers here once migrated.
|
||||
Reference in New Issue
Block a user