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:
Selfhosting Dev
2025-09-25 10:23:14 +09:00
parent 2f306dd6a5
commit 9384c80623
30 changed files with 1786 additions and 527 deletions

View 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
View 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,
)
}

View 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
View 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.