feat(mir): Phase 25.1f完了 - Conservative PHI + ControlForm観測レイヤー

🎉 Conservative PHI Box理論による完全SSA構築

**Phase 7-B: Conservative PHI実装**
- 片方branchのみ定義変数に対応(emit_void使用)
- 全変数にPHI生成(Conservative Box理論)
- Stage-1 resolver全テスト緑化(3/3 PASS)

**Phase 25.1f: ControlForm観測レイヤー**
- LoopShape/IfShape/ControlForm構造定義
- Loop/If統一インターフェース実装
- debug_dump/debug_validate機能追加
- NYASH_CONTROL_FORM_TRACE環境変数対応

**主な変更**:
- src/mir/builder/phi.rs: Conservative PHI実装
- src/mir/control_form.rs: ControlForm構造(NEW)
- src/mir/loop_builder.rs: LoopForm v2デフォルト化

**テスト結果**:
 mir_stage1_using_resolver_min_fragment_verifies
 mir_stage1_using_resolver_full_collect_entries_verifies
 mir_parserbox_parse_program2_harness_parses_minimal_source

🤖 Generated with Claude Code
Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: ChatGPT <chatgpt@openai.com>
This commit is contained in:
nyash-codex
2025-11-18 18:56:35 +09:00
parent 8b37e9711d
commit d3cbc71c9b
81 changed files with 907 additions and 147 deletions

View File

@ -8,6 +8,7 @@ pub fn pre_run_reset_oob_if_strict() {
}
}
#[allow(dead_code)]
pub fn post_run_exit_if_oob_strict_triggered() -> ! {
if crate::config::env::oob_strict_fail() && crate::runtime::observe::oob_seen() {
eprintln!("[gate-c][oob-strict] Out-of-bounds observed → exit(1)");

View File

@ -90,6 +90,7 @@ pub(super) fn demo_parser_system() {
}
}
#[allow(dead_code)]
pub(super) fn demo_interpreter_system() {
println!("\n🎭 7. Interpreter System:");
println!(" ⚠️ Legacy interpreter removed - use VM or LLVM backends instead");

View File

@ -129,18 +129,17 @@ pub fn run_json_v1_inline(json: &str) -> i32 {
let sval = regs.get(&src).cloned();
let is_integer = sval.is_some(); // hv1 inline stores i64 only → integer
let mut out = 0i64;
if operation == "check" || operation == "is" {
if target == "i64" || target == "int" || target == "integer" {
out = if is_integer { 1 } else { 0 };
let out: i64 = if target == "i64" || target == "int" || target == "integer" {
if is_integer { 1 } else { 0 }
} else if target == "bool" {
// Inline model uses integer registers; treat 0/1 as bool when present
out = if let Some(v) = sval { if v == 0 || v == 1 { 1 } else { 0 } } else { 0 };
if let Some(v) = sval { if v == 0 || v == 1 { 1 } else { 0 } } else { 0 }
} else if target == "string" {
out = 0; // no string registers in inline model
0 // no string registers in inline model
} else {
out = 0;
}
0
};
regs.insert(dst, out);
} else {
// cast/as: pass-through (MVP)

View File

@ -2,7 +2,7 @@ use super::ast::{ProgramV0, StmtV0, ExprV0};
use crate::mir::Callee;
use crate::mir::{
BasicBlockId, ConstValue, EffectMask, FunctionSignature, MirFunction, MirInstruction, MirModule,
MirPrinter, MirType, ValueId, BinaryOp,
MirPrinter, MirType, ValueId,
};
use std::collections::HashMap;
use std::cell::RefCell;
@ -103,6 +103,7 @@ pub(super) struct BridgeEnv {
}
impl BridgeEnv {
#[allow(dead_code)]
pub(super) fn load() -> Self {
Self::with_imports(HashMap::new())
}

View File

@ -3,7 +3,7 @@ use super::BridgeEnv;
use super::ternary;
use super::match_expr;
use crate::mir::{
BasicBlockId, BinaryOp, ConstValue, EffectMask, MirFunction, MirInstruction, ValueId,
BasicBlockId, ConstValue, EffectMask, MirFunction, MirInstruction, ValueId,
};
use std::collections::HashMap;

View File

@ -1,5 +1,5 @@
use super::{lower_stmt_list_with_vars, merge_var_maps, new_block, BridgeEnv, LoopContext};
use crate::mir::{BasicBlockId, MirFunction, MirInstruction, ValueId};
use crate::mir::{BasicBlockId, MirFunction, ValueId};
use std::collections::HashMap;
use super::super::ast::StmtV0;
use super::super::ast::ExprV0;

View File

@ -5,7 +5,7 @@
use super::merge::new_block;
use super::BridgeEnv;
use crate::mir::{BasicBlockId, MirFunction, MirInstruction, ValueId};
use crate::mir::{BasicBlockId, MirFunction, ValueId};
use super::super::ast::ExprV0;
use super::expr::{lower_expr_with_scope, VarScope};
@ -35,7 +35,7 @@ pub(super) fn lower_ternary_expr_with_scope<S: VarScope>(
}
let out = f.next_value_id();
// フェーズM.2: PHI統一処理no_phi分岐削除
let mut inputs = vec![(tend, tval), (eend, eval)];
let inputs = vec![(tend, tval), (eend, eval)];
crate::mir::ssot::cf_common::insert_phi_at_head(f, merge_bb, out, inputs);
Ok((out, merge_bb))
}

View File

@ -1,4 +1,4 @@
use crate::mir::{BasicBlockId, MirFunction, MirInstruction, ValueId};
use crate::mir::{BasicBlockId, MirFunction, ValueId};
use std::cell::RefCell;
thread_local! {

View File

@ -511,27 +511,6 @@ pub fn try_parse_v1_to_module(json: &str) -> Result<Option<MirModule>, String> {
if let Some(d) = dst_opt { max_value_id = max_value_id.max(d.as_u32() + 1); }
}
}
"Constructor" => {
// box_type: string, dst: required
let dst = dst_opt.ok_or_else(|| format!(
"mir_call Constructor requires dst in function '{}'",
func_name
))?;
let bt = callee_obj
.get("box_type")
.and_then(Value::as_str)
.ok_or_else(|| format!(
"mir_call Constructor missing box_type in function '{}'",
func_name
))?
.to_string();
block_ref.add_instruction(MirInstruction::NewBox {
dst,
box_type: bt,
args: argv.clone(),
});
max_value_id = max_value_id.max(dst.as_u32() + 1);
}
"Extern" => {
let name = callee_obj
.get("name")

View File

@ -31,6 +31,7 @@ fn create_json_v1_root(functions: serde_json::Value) -> serde_json::Value {
/// Helper: detect residual numeric-core boxcalls that should have been lowered by AotPrepNumericCoreBox.
/// Currently we only check for `boxcall` with `method:"mul_naive"` which should become
/// `call("NyNumericMatI64.mul_naive", ...)` when NYASH_AOT_NUMERIC_CORE=1 is effective.
#[allow(dead_code)]
fn has_numeric_core_boxcall(root: &serde_json::Value) -> bool {
let funs = match root.get("functions") {
Some(v) => v.as_array().cloned().unwrap_or_default(),
@ -61,6 +62,7 @@ fn has_numeric_core_boxcall(root: &serde_json::Value) -> bool {
/// Helper: enforce numeric_core invariants when NYASH_AOT_NUMERIC_CORE=1 is set.
/// - Default: emit a warning if mul_naive boxcalls are still present.
/// - Strict: if NYASH_AOT_NUMERIC_CORE_STRICT=1, return Err to fail fast.
#[allow(dead_code)]
fn check_numeric_core_invariants(root: &serde_json::Value) -> Result<(), String> {
let numeric_on = matches!(std::env::var("NYASH_AOT_NUMERIC_CORE").ok().as_deref(), Some("1"));
if !numeric_on {

View File

@ -22,7 +22,7 @@ pub fn gather_required_providers() -> Vec<String> {
return v;
}
// Default conservative set
let mut v = vec![
let v = vec![
"FileBox".to_string(),
"ConsoleBox".to_string(),
"ArrayBox".to_string(),
@ -86,4 +86,3 @@ pub fn check_and_report(strict: bool, quiet_pipe: bool, label: &str) {
}
}
}

View File

@ -111,7 +111,7 @@ impl<'a> PreludeManagerBox<'a> {
fn build_text_merged(
&self,
source: &str,
filename: &str,
_filename: &str,
prelude_paths: &[String],
trace: bool,
) -> Result<String, String> {
@ -123,7 +123,7 @@ impl<'a> PreludeManagerBox<'a> {
.map_err(|e| format!("using: failed to read '{}': {}", path, e))?;
// using行を除去して正規化
let using_resolver = UsingResolutionBox::new(&self.runner, path)?;
let _using_resolver = UsingResolutionBox::new(&self.runner, path)?;
let (cleaned_raw, _nested) = self.collect_using_and_strip_internal(&content, path)?;
let cleaned = self.normalize_text_for_inline(&cleaned_raw);

View File

@ -129,7 +129,7 @@ impl<'a> SelfhostPipelineBox<'a> {
&self,
error: &str,
original_code: &str,
filename: &str,
_filename: &str,
) -> CompilationResult {
eprintln!("[selfhost-pipeline] ⚠️ Error: {}", error);
eprintln!("[selfhost-pipeline] 🔄 Falling back to original code");
@ -179,8 +179,8 @@ impl<'a> SelfhostPipelineBox<'a> {
/// 📊 パフォーマンスプロファイリングするにゃ!
pub fn profile_pipeline(
&mut self,
code: &str,
filename: &str,
_code: &str,
_filename: &str,
) -> Result<String, String> {
// プロファイル機能を実装(別途)
// TODO: プロファイル機能を追加

View File

@ -448,7 +448,7 @@ pub fn resolve_prelude_paths_profiled(
// must be discovered so that their definitions are present at runtime
// (e.g., runner_min -> lower_* boxes). Previously this only ran when
// NYASH_USING_AST=1, which caused unresolved calls in inline flows.
let ast_on = crate::config::env::env_bool("NYASH_USING_AST");
let _ast_on = crate::config::env::env_bool("NYASH_USING_AST");
let mut out: Vec<String> = Vec::new();
let mut seen: std::collections::HashSet<String> = std::collections::HashSet::new();
fn normalize_path(path: &str) -> (String, String) {

View File

@ -14,6 +14,7 @@ pub struct UsingResolutionBox<'a> {
runner: &'a NyashRunner,
config: UsingConfig,
ctx_dir: Option<PathBuf>,
#[allow(dead_code)]
filename_canon: Option<PathBuf>,
inside_pkg: bool,
seen_paths: HashMap<String, (String, usize)>, // canon_path -> (alias/label, first_line)

View File

@ -476,7 +476,7 @@ impl NyashRunner {
match vm.execute_module(&module_vm) {
Ok(ret) => {
use crate::box_trait::{NyashBox, IntegerBox, BoolBox};
use crate::box_trait::{IntegerBox, BoolBox};
// Extract exit code from return value
let exit_code = if let Some(ib) = ret.as_any().downcast_ref::<IntegerBox>() {

View File

@ -321,7 +321,7 @@ impl NyashRunner {
}
match vm.execute_module(&module_vm) {
Ok(ret) => {
use crate::box_trait::{NyashBox, IntegerBox, BoolBox};
use crate::box_trait::{IntegerBox, BoolBox};
// Extract exit code from return value
let exit_code = if let Some(ib) = ret.as_any().downcast_ref::<IntegerBox>() {
@ -346,7 +346,8 @@ impl NyashRunner {
impl NyashRunner {
/// Small helper to continue fallback execution once AST is prepared
fn execute_vm_fallback_from_ast(&self, filename: &str, ast: nyash_rust::ast::ASTNode) {
#[allow(dead_code)]
fn execute_vm_fallback_from_ast(&self, _filename: &str, ast: nyash_rust::ast::ASTNode) {
use crate::{
backend::MirInterpreter,
box_factory::{BoxFactory, RuntimeError},

View File

@ -10,7 +10,6 @@
*/
use super::*;
use crate::runner::child_env;
impl NyashRunner {
/// Try to handle `--ny-parser-pipe` / `--json-file` flow.
@ -20,7 +19,7 @@ impl NyashRunner {
if !(groups.parser.ny_parser_pipe || groups.parser.json_file.is_some()) {
return false;
}
let mut json = if let Some(path) = &groups.parser.json_file {
let json = if let Some(path) = &groups.parser.json_file {
match std::fs::read_to_string(path) {
Ok(s) => s,
Err(e) => {

View File

@ -84,6 +84,7 @@ impl NyashRunner {
}
/// Suggest candidate files by leaf name within limited bases (apps/lib/.)
#[allow(dead_code)]
pub(super) fn suggest_in_base(base: &str, leaf: &str, out: &mut Vec<String>) {
use std::fs;
fn walk(dir: &std::path::Path, leaf: &str, out: &mut Vec<String>, depth: usize) {

View File

@ -58,7 +58,7 @@ impl NyashRunner {
for p in list {
if list_only { println!("{}", p); continue; }
match std::fs::read_to_string(&p) {
Ok(code) => {
Ok(_code) => {
// Legacy interpreter removed - ny_plugins execution disabled
println!("[ny_plugins] {}: SKIP (legacy interpreter removed)", p);
}

View File

@ -403,21 +403,22 @@ impl NyashRunner {
// パイプラインtools/ny_selfhost_inline.sh など)を使う想定とし、ここでは常に Rust 既定
// パスへフォールバックする。
crate::cli_v!("[ny-compiler] inline selfhost pipeline disabled (Phase 25.1b); falling back to default path");
return false;
match super::json_v0_bridge::parse_json_v0_to_module("") {
Ok(module) => {
if crate::config::env::cli_verbose() {
// Dev-only escape hatch: allow forcing the old inline path when explicitly requested.
if std::env::var("NYASH_SELFHOST_INLINE_FORCE").ok().as_deref() == Some("1") {
match super::json_v0_bridge::parse_json_v0_to_module("") {
Ok(module) => {
if crate::config::env::cli_verbose() {
super::json_v0_bridge::maybe_dump_mir(&module);
if crate::config::env::cli_verbose() {
super::json_v0_bridge::maybe_dump_mir(&module);
}
}
let emit_only = std::env::var("NYASH_NY_COMPILER_EMIT_ONLY")
.unwrap_or_else(|_| "1".to_string())
== "1";
if emit_only {
return false;
}
}
let emit_only = std::env::var("NYASH_NY_COMPILER_EMIT_ONLY")
.unwrap_or_else(|_| "1".to_string())
== "1";
if emit_only {
return false;
}
// Phase-15 policy: when NYASH_VM_USE_PY=1, prefer PyVM as reference executor
// regardless of BoxCall presence to ensure semantics parity (e.g., PHI merges).
let prefer_pyvm = std::env::var("NYASH_VM_USE_PY").ok().as_deref() == Some("1");
@ -430,25 +431,29 @@ impl NyashRunner {
})
})
});
if prefer_pyvm || needs_pyvm {
let label = if prefer_pyvm { "selfhost" } else { "selfhost-fallback" };
if let Some(code) = crate::runner::modes::common_util::selfhost::json::run_pyvm_module(&module, label) {
println!("Result: {}", code);
std::process::exit(code);
if prefer_pyvm || needs_pyvm {
let label = if prefer_pyvm { "selfhost" } else { "selfhost-fallback" };
if let Some(code) = crate::runner::modes::common_util::selfhost::json::run_pyvm_module(&module, label) {
println!("Result: {}", code);
std::process::exit(code);
}
}
crate::runner::child_env::pre_run_reset_oob_if_strict();
self.execute_mir_module(&module);
if crate::config::env::oob_strict_fail() && crate::runtime::observe::oob_seen() {
eprintln!("[selfhost][oob-strict] Out-of-bounds observed → exit(1)");
std::process::exit(1);
}
return true;
}
crate::runner::child_env::pre_run_reset_oob_if_strict();
self.execute_mir_module(&module);
if crate::config::env::oob_strict_fail() && crate::runtime::observe::oob_seen() {
eprintln!("[selfhost][oob-strict] Out-of-bounds observed → exit(1)");
std::process::exit(1);
Err(e) => {
eprintln!("❌ JSON v0 bridge error: {}", e);
return false;
}
true
}
Err(e) => {
eprintln!("❌ JSON v0 bridge error: {}", e);
false
}
}
// Default path: always fall back to existing Rust runner.
return false;
}
}