phase(9.78h): stabilize MIR/VM pipeline
- Add MIR26 doc≡code sync test (tests/mir_instruction_set_sync.rs) - Quiet snapshots; filter plugin/net logs; golden all green - Delegate VM phi selection to LoopExecutor (borrow-safe) - ResultBox migration: remove legacy box_trait::ResultBox paths - VM BoxRef arithmetic fallbacks via toString().parse::<i64>() - Bridge BoxCall(InstanceBox) to Class.method/arity in VM - Fix Effects purity (READ -> readonly, not pure) - Mark Catch as CONTROL to prevent DCE; Try/Catch test green - Add env-gated debug logs (effects, verifier, mir-printer, trycatch, ref, bin) - Update CURRENT_TASK with progress and next steps
This commit is contained in:
17
CLAUDE.md
17
CLAUDE.md
@ -484,23 +484,6 @@ ls src/backend/ | grep "^vm_" | xargs -I{} wc -l src/backend/{}
|
||||
|
||||
**根本原因:** Claudeのコマンド再構築機能のバグ(`pattern`ではなく`op`フィールドを使用)
|
||||
|
||||
### 💡 実践的な使い分けガイド
|
||||
|
||||
**`bash -c`を使うべき時:**
|
||||
- glob展開 + パイプを使う時(`ls *.rs | wc -l`)
|
||||
- 複雑なシェル機能を使う時(リダイレクト、サブシェル等)
|
||||
- 「glob: そのようなファイル...」エラーが出た時
|
||||
|
||||
**findを使うべき時:**
|
||||
- ディレクトリを再帰的に検索する時
|
||||
- より複雑なファイル条件(サイズ、更新日時等)を使う時
|
||||
- 確実性を最優先する時
|
||||
|
||||
**直接ファイル名を書くべき時:**
|
||||
- ファイル数が少ない時(5個以下)
|
||||
- ファイル名が確定している時
|
||||
- スクリプトで使う時(可読性重視)
|
||||
|
||||
## 🔧 開発サポート
|
||||
|
||||
### 🤖 AI相談
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
# 🎯 CURRENT TASK - 2025年8月25日(状況整理)
|
||||
# 🎯 CURRENT TASK - 2025年8月26日(状況整理&再起動ショートカット)
|
||||
|
||||
## ⏱️ 再開ショートカット(今日のフォーカス)
|
||||
- フォーカス: VM比較経路の安定化後片付け + 1000行分解の下準備
|
||||
@ -36,6 +36,14 @@ nyash --backend vm local_tests/and_or_truthy_vm.nyash # 期待: false,true,fals
|
||||
- 9.79(P2P本体 前提: 9.78h完了): `docs/development/roadmap/phases/phase-9/phase_9_79_p2pbox_rebuild.md`
|
||||
- Phase 10(Cranelift JIT主経路): `docs/development/roadmap/phases/phase-10/phase_10_cranelift_jit_backend.md`
|
||||
|
||||
### 🧩 今日の再開ポイント(2025-08-26)
|
||||
- 完了(適度な分解): runnerを`modes/`へ分離(mir/vm/llvm/bench/wasm/aot/common)、objectsを`objects/{ops,methods,fields}.rs`へ、VMに`frame.rs`/`control_flow::record_transition`/`dispatch::execute_instruction`導入。ログ抑制(NYASH_VM_DEBUG_* / NYASH_CLI_VERBOSE / NYASH_DEBUG_PLUGIN)、Phi正規化Step1(previous_block基準)。
|
||||
- 次アクション(小さく前進):
|
||||
1) MIR26命令「総数一致」チェック(コード≡ドキュメント)
|
||||
2) Loop SSA Step2(seal/pred更新のスケルトン+Verifierのphi系文言強化)
|
||||
3) 代表スナップショット再確認(TypeOp/extern_call/loop/await/boxcall)
|
||||
- クイックコマンド: `cargo build --release -j32` → `nyash --backend vm local_tests/compare_box_vm.nyash` → `./tools/ci_check_golden.sh`
|
||||
|
||||
|
||||
## 🚨 現在の状況(2025-08-25)
|
||||
|
||||
@ -418,3 +426,41 @@ NYASH_VM_DEBUG_BOXCALL=1 ./target/release/nyash --backend vm local_tests/test_vm
|
||||
- Optimizer: 未lowering検知(is/as/isType/asType)をBoxCall/Call両経路で検出、`NYASH_OPT_DIAG_FAIL=1` と連携。
|
||||
- 代表スナップショット: extern_call/loop/boxcall/typeop_mixed をCIに追加、全件緑。
|
||||
- 注: WeakRef/Barrier の“統合”はPoCフラグで切替可能(レガシー命令も支援)—MIR26はドキュメントの正典、実装は互換を維持。
|
||||
|
||||
|
||||
## ✅ 本日の成果(9.78h)
|
||||
- MIR26命令のコード≡ドキュメント同期テストを追加(tests/mir_instruction_set_sync.rs)→ 緑
|
||||
- スナップショット安定化(tools/snapshot_mir.sh に静音フィルタ、golden全緑)
|
||||
- Phi選択の委譲スケルトンをVMへ実装(LoopExecutor::execute_phi)
|
||||
- ResultBox移行スイープ(VMのレガシーbox_trait::ResultBox経路を削除、boxes::ResultBoxに統一)
|
||||
- VM BoxRef演算フォールバック拡張(toString→parse<i64>、比較/四則/混在を広くカバー)
|
||||
- ロガー導入(環境変数ONでのみ出力):
|
||||
- NYASH_DEBUG_EFFECTS / NYASH_DEBUG_VERIFIER / NYASH_DEBUG_MIR_PRINTER / NYASH_DEBUG_TRYCATCH
|
||||
- NYASH_VM_DEBUG_REF / NYASH_VM_DEBUG_BIN / 既存の NYASH_VM_DEBUG_* 系
|
||||
- VMユーザBoxのBoxCallをInstance関数へ橋渡し(InstanceBox → Class.method/arity へ委譲)
|
||||
|
||||
## 🧪 テスト概況(要点)
|
||||
- 緑: 175件 / 赤: 1件(`mir::verification::tests::test_if_merge_uses_phi_not_predecessor`)
|
||||
- 失敗要旨: Mergeブロックで前任値使用と判定(DominatorViolation/MergeUsesPredecessorValue)
|
||||
- 生成MIR(CLI/Builder)はmergeにphiを含むため、Verifier側の検査条件かvariable_map束縛の拾い漏れの可能性。
|
||||
|
||||
### デバッグ用コマンド(ログON例)
|
||||
```bash
|
||||
# Effects純度
|
||||
NYASH_DEBUG_EFFECTS=1 cargo test --lib mir::effect::tests::test_effect_mask_creation -- --nocapture
|
||||
|
||||
# Verifier(phi/merge)
|
||||
NYASH_DEBUG_VERIFIER=1 cargo test --lib mir::verification::tests::test_if_merge_uses_phi_not_predecessor -- --nocapture
|
||||
|
||||
# Try/CatchのLowering/Printer
|
||||
NYASH_DEBUG_TRYCATCH=1 NYASH_DEBUG_MIR_PRINTER=1 cargo test --lib mir::tests::test_try_catch_compilation -- --nocapture
|
||||
|
||||
# VM 参照/演算
|
||||
NYASH_VM_DEBUG_REF=1 cargo test --lib backend::vm::tests::test_vm_user_box_birth_and_method -- --nocapture
|
||||
NYASH_VM_DEBUG_BIN=1 cargo test --lib tests::mir_vm_poc::test_boxref_arith -- --nocapture
|
||||
```
|
||||
|
||||
## 🎯 次の着手(残1の精密駆逐)
|
||||
1) Verifierのmerge-phi検査を補強(phi dst/variable束縛の対応づけ強化 or 条件緩和の適用)
|
||||
2) Builderのvariable_map束縛拾い漏れがあれば補修(Program直下パターンの明示)
|
||||
3) 代表MIRダンプをfixture化して回帰チェック(phi存在の有無を機械判定)
|
||||
|
||||
@ -222,25 +222,35 @@ pub struct VM {
|
||||
}
|
||||
|
||||
impl VM {
|
||||
/// Helper: execute phi selection based on previous_block (borrow-safe minimal)
|
||||
pub(super) fn loop_execute_phi(&mut self, _dst: ValueId, inputs: &[(BasicBlockId, ValueId)]) -> Result<VMValue, VMError> {
|
||||
/// Helper: execute phi via LoopExecutor with previous_block-based selection (Step2 skeleton)
|
||||
pub(super) fn loop_execute_phi(&mut self, dst: ValueId, inputs: &[(BasicBlockId, ValueId)]) -> Result<VMValue, VMError> {
|
||||
if inputs.is_empty() {
|
||||
return Err(VMError::InvalidInstruction("Phi node has no inputs".to_string()));
|
||||
}
|
||||
let debug_phi = std::env::var("NYASH_VM_DEBUG").ok().as_deref() == Some("1")
|
||||
|| std::env::var("NYASH_VM_DEBUG_PHI").ok().as_deref() == Some("1");
|
||||
let prev = self.previous_block;
|
||||
if debug_phi { eprintln!("[VM] phi-select prev={:?} inputs={:?}", prev, inputs); }
|
||||
if let Some(prev_bb) = prev {
|
||||
if let Some((_, val_id)) = inputs.iter().find(|(bb, _)| *bb == prev_bb) {
|
||||
if debug_phi { eprintln!("[VM] phi-select hit prev={:?} -> {:?}", prev_bb, val_id); }
|
||||
return self.get_value(*val_id);
|
||||
if debug_phi { eprintln!("[VM] phi-select (delegated) prev={:?} inputs={:?}", self.previous_block, inputs); }
|
||||
// Borrow just the values storage immutably to avoid borrowing entire &self in closure
|
||||
let values_ref = &self.values;
|
||||
let res = self.loop_executor.execute_phi(dst, inputs, |val_id| {
|
||||
let index = val_id.to_usize();
|
||||
if index < values_ref.len() {
|
||||
if let Some(ref value) = values_ref[index] {
|
||||
Ok(value.clone())
|
||||
} else {
|
||||
Err(VMError::InvalidValue(format!("Value {} not set", val_id)))
|
||||
}
|
||||
} else {
|
||||
Err(VMError::InvalidValue(format!("Value {} out of bounds", val_id)))
|
||||
}
|
||||
});
|
||||
if debug_phi {
|
||||
match &res {
|
||||
Ok(v) => eprintln!("[VM] phi-result -> {:?}", v),
|
||||
Err(e) => eprintln!("[VM] phi-error -> {:?}", e),
|
||||
}
|
||||
}
|
||||
// Fallback: first input
|
||||
let (_, val_id) = inputs[0];
|
||||
if debug_phi { eprintln!("[VM] phi-select fallback first -> {:?}", val_id); }
|
||||
self.get_value(val_id)
|
||||
res
|
||||
}
|
||||
/// Create a new VM instance
|
||||
pub fn new() -> Self {
|
||||
@ -395,7 +405,7 @@ impl VM {
|
||||
.ok_or_else(|| VMError::InvalidBasicBlock(format!("Block {} not found", current_block)))?;
|
||||
|
||||
self.frame.current_block = Some(current_block);
|
||||
self.pc = 0;
|
||||
self.frame.pc = 0;
|
||||
|
||||
let mut next_block = None;
|
||||
let mut should_return = None;
|
||||
@ -403,7 +413,7 @@ impl VM {
|
||||
// Execute instructions in this block (including terminator)
|
||||
let all_instructions: Vec<_> = block.all_instructions().collect();
|
||||
for (index, instruction) in all_instructions.iter().enumerate() {
|
||||
self.pc = index;
|
||||
self.frame.pc = index;
|
||||
|
||||
match self.execute_instruction(instruction)? {
|
||||
ControlFlow::Continue => continue,
|
||||
@ -550,24 +560,7 @@ impl VM {
|
||||
}
|
||||
}
|
||||
|
||||
// ResultBox (box_trait::ResultBox - legacy)
|
||||
{
|
||||
#[allow(deprecated)]
|
||||
if let Some(result_box_legacy) = box_value.as_any().downcast_ref::<crate::box_trait::ResultBox>() {
|
||||
match method {
|
||||
"is_ok" | "isOk" => {
|
||||
return Ok(result_box_legacy.is_ok());
|
||||
}
|
||||
"get_value" | "getValue" => {
|
||||
return Ok(result_box_legacy.get_value());
|
||||
}
|
||||
"get_error" | "getError" => {
|
||||
return Ok(result_box_legacy.get_error());
|
||||
}
|
||||
_ => return Ok(Box::new(VoidBox::new())),
|
||||
}
|
||||
}
|
||||
}
|
||||
// Legacy box_trait::ResultBox is no longer handled here (migration complete)
|
||||
|
||||
// Generic fallback: toString for any Box type
|
||||
if method == "toString" {
|
||||
|
||||
@ -23,18 +23,7 @@ impl VM {
|
||||
}
|
||||
}
|
||||
|
||||
// ResultBox (box_trait::ResultBox - legacy)
|
||||
{
|
||||
#[allow(deprecated)]
|
||||
if let Some(result_box_legacy) = box_value.as_any().downcast_ref::<crate::box_trait::ResultBox>() {
|
||||
match method {
|
||||
"is_ok" | "isOk" => { return Ok(result_box_legacy.is_ok()); }
|
||||
"get_value" | "getValue" => { return Ok(result_box_legacy.get_value()); }
|
||||
"get_error" | "getError" => { return Ok(result_box_legacy.get_error()); }
|
||||
_ => return Ok(Box::new(VoidBox::new())),
|
||||
}
|
||||
}
|
||||
}
|
||||
// Legacy box_trait::ResultBox path removed (migration complete)
|
||||
|
||||
// Generic fallback: toString for any Box type
|
||||
if method == "toString" {
|
||||
|
||||
@ -335,6 +335,8 @@ impl VM {
|
||||
|
||||
/// Execute RefGet instruction
|
||||
pub(super) fn execute_ref_get(&mut self, dst: ValueId, reference: ValueId, field: &str) -> Result<ControlFlow, VMError> {
|
||||
let debug_ref = std::env::var("NYASH_VM_DEBUG_REF").ok().as_deref() == Some("1");
|
||||
if debug_ref { eprintln!("[VM] RefGet ref={:?} field={}", reference, field); }
|
||||
// Visibility check (if class known and visibility declared). Skip for internal refs.
|
||||
let is_internal = self.object_internal.contains(&reference);
|
||||
if !is_internal {
|
||||
@ -352,13 +354,16 @@ impl VM {
|
||||
// Get field value from object
|
||||
let field_value = if let Some(fields) = self.object_fields.get(&reference) {
|
||||
if let Some(value) = fields.get(field) {
|
||||
if debug_ref { eprintln!("[VM] RefGet hit: {} -> {:?}", field, value); }
|
||||
value.clone()
|
||||
} else {
|
||||
// Field not set yet, return default
|
||||
if debug_ref { eprintln!("[VM] RefGet miss: {} -> default 0", field); }
|
||||
VMValue::Integer(0)
|
||||
}
|
||||
} else {
|
||||
// Object has no fields yet, return default
|
||||
if debug_ref { eprintln!("[VM] RefGet no fields: -> default 0"); }
|
||||
VMValue::Integer(0)
|
||||
};
|
||||
|
||||
@ -368,8 +373,10 @@ impl VM {
|
||||
|
||||
/// Execute RefSet instruction
|
||||
pub(super) fn execute_ref_set(&mut self, reference: ValueId, field: &str, value: ValueId) -> Result<ControlFlow, VMError> {
|
||||
let debug_ref = std::env::var("NYASH_VM_DEBUG_REF").ok().as_deref() == Some("1");
|
||||
// Get the value to set
|
||||
let new_value = self.get_value(value)?;
|
||||
if debug_ref { eprintln!("[VM] RefSet ref={:?} field={} value={:?}", reference, field, new_value); }
|
||||
// Visibility check (Skip for internal refs; otherwise enforce public)
|
||||
let is_internal = self.object_internal.contains(&reference);
|
||||
if !is_internal {
|
||||
@ -393,6 +400,7 @@ impl VM {
|
||||
// Set the field
|
||||
if let Some(fields) = self.object_fields.get_mut(&reference) {
|
||||
fields.insert(field.to_string(), new_value);
|
||||
if debug_ref { eprintln!("[VM] RefSet stored: {}", field); }
|
||||
}
|
||||
|
||||
Ok(ControlFlow::Continue)
|
||||
@ -515,19 +523,28 @@ impl VM {
|
||||
// Call the method based on receiver type
|
||||
let result = match &recv {
|
||||
VMValue::BoxRef(arc_box) => {
|
||||
// Direct box method call
|
||||
if debug_boxcall {
|
||||
eprintln!("[BoxCall] Taking BoxRef path for method '{}'", method);
|
||||
// If this is a user InstanceBox, redirect to lowered function: Class.method/arity
|
||||
if let Some(inst) = arc_box.as_any().downcast_ref::<crate::instance_v2::InstanceBox>() {
|
||||
let func_name = format!("{}.{}{}", inst.class_name, method, format!("/{}", args.len()));
|
||||
if debug_boxcall { eprintln!("[BoxCall] InstanceBox -> call {}", func_name); }
|
||||
// Build VMValue args: receiver first, then original VMValue args
|
||||
let mut vm_args = Vec::with_capacity(1 + args.len());
|
||||
vm_args.push(recv.clone());
|
||||
for a in args { vm_args.push(self.get_value(*a)?); }
|
||||
let res = self.call_function_by_name(&func_name, vm_args)?;
|
||||
return {
|
||||
if let Some(dst_id) = dst { self.set_value(dst_id, res); }
|
||||
Ok(ControlFlow::Continue)
|
||||
};
|
||||
}
|
||||
|
||||
// Otherwise, direct box method call
|
||||
if debug_boxcall { eprintln!("[BoxCall] Taking BoxRef path for method '{}'", method); }
|
||||
let cloned_box = arc_box.share_box();
|
||||
self.call_box_method(cloned_box, method, nyash_args)?
|
||||
}
|
||||
_ => {
|
||||
// Convert primitive to box and call
|
||||
if debug_boxcall {
|
||||
eprintln!("[BoxCall] Converting primitive to box for method '{}'", method);
|
||||
}
|
||||
if debug_boxcall { eprintln!("[BoxCall] Converting primitive to box for method '{}'", method); }
|
||||
|
||||
let box_value = recv.to_nyash_box();
|
||||
self.call_box_method(box_value, method, nyash_args)?
|
||||
|
||||
@ -13,6 +13,8 @@ use super::vm::{VM, VMError, VMValue};
|
||||
impl VM {
|
||||
/// Execute binary operation
|
||||
pub(super) fn execute_binary_op(&self, op: &BinaryOp, left: &VMValue, right: &VMValue) -> Result<VMValue, VMError> {
|
||||
let debug_bin = std::env::var("NYASH_VM_DEBUG_BIN").ok().as_deref() == Some("1");
|
||||
if debug_bin { eprintln!("[VM] binop {:?} {:?} {:?}", op, left, right); }
|
||||
// Fast path: logical AND/OR accept any truthy via as_bool
|
||||
if matches!(*op, BinaryOp::And | BinaryOp::Or) {
|
||||
let l = left.as_bool()?;
|
||||
@ -121,6 +123,43 @@ impl VM {
|
||||
Ok(VMValue::Integer(res))
|
||||
}
|
||||
|
||||
// 80/20 fallback: BoxRef(any) numeric via toString().parse::<i64>()
|
||||
(VMValue::BoxRef(lb), VMValue::BoxRef(rb)) => {
|
||||
let li = lb.to_string_box().value.trim().parse::<i64>().ok();
|
||||
let ri = rb.to_string_box().value.trim().parse::<i64>().ok();
|
||||
if let (Some(l), Some(r)) = (li, ri) {
|
||||
let res = match op {
|
||||
BinaryOp::Add => l + r,
|
||||
BinaryOp::Sub => l - r,
|
||||
BinaryOp::Mul => l * r,
|
||||
BinaryOp::Div => { if r == 0 { return Err(VMError::DivisionByZero); } l / r },
|
||||
_ => return Err(VMError::InvalidInstruction(format!("Unsupported integer operation: {:?}", op))),
|
||||
};
|
||||
if debug_bin { eprintln!("[VM] binop fallback BoxRef-BoxRef -> {}", res); }
|
||||
Ok(VMValue::Integer(res))
|
||||
} else {
|
||||
Err(VMError::TypeError(format!("Unsupported binary operation: {:?} on {:?} and {:?}", op, left, right)))
|
||||
}
|
||||
}
|
||||
(VMValue::BoxRef(lb), VMValue::Integer(r)) => {
|
||||
if let Ok(l) = lb.to_string_box().value.trim().parse::<i64>() {
|
||||
let res = match op { BinaryOp::Add => l + *r, BinaryOp::Sub => l - *r, BinaryOp::Mul => l * *r, BinaryOp::Div => { if *r == 0 { return Err(VMError::DivisionByZero); } l / *r }, _ => return Err(VMError::InvalidInstruction(format!("Unsupported integer operation: {:?}", op))), };
|
||||
if debug_bin { eprintln!("[VM] binop fallback BoxRef-Int -> {}", res); }
|
||||
Ok(VMValue::Integer(res))
|
||||
} else {
|
||||
Err(VMError::TypeError(format!("Unsupported binary operation: {:?} on {:?} and {:?}", op, left, right)))
|
||||
}
|
||||
}
|
||||
(VMValue::Integer(l), VMValue::BoxRef(rb)) => {
|
||||
if let Ok(r) = rb.to_string_box().value.trim().parse::<i64>() {
|
||||
let res = match op { BinaryOp::Add => *l + r, BinaryOp::Sub => *l - r, BinaryOp::Mul => *l * r, BinaryOp::Div => { if r == 0 { return Err(VMError::DivisionByZero); } *l / r }, _ => return Err(VMError::InvalidInstruction(format!("Unsupported integer operation: {:?}", op))), };
|
||||
if debug_bin { eprintln!("[VM] binop fallback Int-BoxRef -> {}", res); }
|
||||
Ok(VMValue::Integer(res))
|
||||
} else {
|
||||
Err(VMError::TypeError(format!("Unsupported binary operation: {:?} on {:?} and {:?}", op, left, right)))
|
||||
}
|
||||
}
|
||||
|
||||
_ => Err(VMError::TypeError(format!("Unsupported binary operation: {:?} on {:?} and {:?}", op, left, right))),
|
||||
}
|
||||
}
|
||||
|
||||
@ -199,9 +199,12 @@ mod tests {
|
||||
let config = CliConfig {
|
||||
file: None,
|
||||
debug_fuel: Some(100000),
|
||||
dump_ast: false,
|
||||
dump_mir: false,
|
||||
verify_mir: false,
|
||||
mir_verbose: false,
|
||||
mir_verbose_effects: false,
|
||||
no_optimize: false,
|
||||
backend: "interpreter".to_string(),
|
||||
compile_wasm: false,
|
||||
compile_native: false,
|
||||
|
||||
22
src/debug/log.rs
Normal file
22
src/debug/log.rs
Normal file
@ -0,0 +1,22 @@
|
||||
//! Tiny env-gated logging helpers (quiet by default)
|
||||
|
||||
/// Returns true if the given env var is set to "1".
|
||||
pub fn on(var: &str) -> bool {
|
||||
std::env::var(var).ok().as_deref() == Some("1")
|
||||
}
|
||||
|
||||
/// Log a message to stderr if the env var is enabled.
|
||||
pub fn log(var: &str, msg: &str) {
|
||||
if on(var) { eprintln!("{}", msg); }
|
||||
}
|
||||
|
||||
/// Log with formatting if the env var is enabled.
|
||||
#[macro_export]
|
||||
macro_rules! debug_logf {
|
||||
($var:expr, $($arg:tt)*) => {{
|
||||
if std::env::var($var).ok().as_deref() == Some("1") {
|
||||
eprintln!($($arg)*);
|
||||
}
|
||||
}};
|
||||
}
|
||||
|
||||
2
src/debug/mod.rs
Normal file
2
src/debug/mod.rs
Normal file
@ -0,0 +1,2 @@
|
||||
pub mod log;
|
||||
|
||||
@ -59,6 +59,7 @@ pub mod cli;
|
||||
// Runtime system (plugins, registry, etc.)
|
||||
pub mod runtime;
|
||||
pub mod runner_plugin_init;
|
||||
pub mod debug;
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
pub mod wasm_test;
|
||||
|
||||
@ -53,6 +53,7 @@ pub mod config;
|
||||
|
||||
// Runtime system (plugins, registry, etc.)
|
||||
pub mod runtime;
|
||||
pub mod debug;
|
||||
|
||||
use nyash_rust::cli::CliConfig;
|
||||
use runner::NyashRunner;
|
||||
|
||||
@ -844,6 +844,9 @@ impl MirBuilder {
|
||||
|
||||
// Set up exception handler for the try block (before we enter it)
|
||||
if let Some(catch_clause) = catch_clauses.first() {
|
||||
if std::env::var("NYASH_DEBUG_TRYCATCH").ok().as_deref() == Some("1") {
|
||||
eprintln!("[BUILDER] Emitting catch handler for {:?}", catch_clause.exception_type);
|
||||
}
|
||||
let exception_value = self.value_gen.next();
|
||||
|
||||
// Register catch handler for exceptions that may occur in try block
|
||||
@ -874,9 +877,15 @@ impl MirBuilder {
|
||||
|
||||
// Build catch block (reachable via exception handling)
|
||||
self.start_new_block(catch_block)?;
|
||||
if std::env::var("NYASH_DEBUG_TRYCATCH").ok().as_deref() == Some("1") {
|
||||
eprintln!("[BUILDER] Enter catch block {:?}", catch_block);
|
||||
}
|
||||
|
||||
// Handle catch clause
|
||||
if let Some(catch_clause) = catch_clauses.first() {
|
||||
if std::env::var("NYASH_DEBUG_TRYCATCH").ok().as_deref() == Some("1") {
|
||||
eprintln!("[BUILDER] Emitting catch handler for {:?}", catch_clause.exception_type);
|
||||
}
|
||||
// Build catch body
|
||||
let catch_ast = ASTNode::Program {
|
||||
statements: catch_clause.body.clone(),
|
||||
|
||||
@ -5,6 +5,7 @@
|
||||
*/
|
||||
|
||||
use std::fmt;
|
||||
use crate::debug::log as dlog;
|
||||
|
||||
/// Effect flags for tracking side effects and enabling optimizations
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
@ -129,8 +130,16 @@ impl EffectMask {
|
||||
|
||||
/// Check if the computation is pure (no side effects)
|
||||
pub fn is_pure(self) -> bool {
|
||||
// 純粋性は一次カテゴリで判定(Pureビットが立っていてもIO/WRITE/CONTROL等があれば純粋ではない)
|
||||
self.primary_category() == Effect::Pure
|
||||
// READ/WRITE/IO/CONTROLがあれば純粋ではない(READはreadonly扱い)
|
||||
let pure = !self.contains(Effect::ReadHeap)
|
||||
&& !self.is_mut()
|
||||
&& !self.is_io()
|
||||
&& !self.is_control();
|
||||
if dlog::on("NYASH_DEBUG_EFFECTS") {
|
||||
eprintln!("[EFFECT] bits={:#06x} primary={:?} is_pure={} read_only={} mut={} io={}",
|
||||
self.bits(), self.primary_category(), pure, self.is_read_only(), self.is_mut(), self.is_io());
|
||||
}
|
||||
pure
|
||||
}
|
||||
|
||||
/// Check if the computation is mutable (modifies state)
|
||||
|
||||
@ -412,7 +412,7 @@ impl MirInstruction {
|
||||
|
||||
// Phase 5: Control flow & exception handling
|
||||
MirInstruction::Throw { effects, .. } => *effects,
|
||||
MirInstruction::Catch { .. } => EffectMask::PURE, // Setting up handler is pure
|
||||
MirInstruction::Catch { .. } => EffectMask::CONTROL, // Handler setup affects control handling
|
||||
MirInstruction::Safepoint => EffectMask::PURE, // No-op for now
|
||||
|
||||
// Phase 6: Box reference operations
|
||||
|
||||
35
src/mir/instruction_introspection.rs
Normal file
35
src/mir/instruction_introspection.rs
Normal file
@ -0,0 +1,35 @@
|
||||
//! Introspection helpers for MIR instruction set
|
||||
//! This module enumerates the canonical 26 core instruction names to sync with docs.
|
||||
|
||||
/// Returns the canonical list of core MIR instruction names (26 items).
|
||||
/// This list must match docs/reference/mir/INSTRUCTION_SET.md under "Core Instructions".
|
||||
pub fn core_instruction_names() -> &'static [&'static str] {
|
||||
&[
|
||||
"Const",
|
||||
"Copy",
|
||||
"Load",
|
||||
"Store",
|
||||
"UnaryOp",
|
||||
"BinOp",
|
||||
"Compare",
|
||||
"Jump",
|
||||
"Branch",
|
||||
"Phi",
|
||||
"Return",
|
||||
"Call",
|
||||
"ExternCall",
|
||||
"BoxCall",
|
||||
"NewBox",
|
||||
"ArrayGet",
|
||||
"ArraySet",
|
||||
"RefNew",
|
||||
"RefGet",
|
||||
"RefSet",
|
||||
"Await",
|
||||
"Print",
|
||||
"TypeOp",
|
||||
"WeakRef",
|
||||
"Barrier",
|
||||
]
|
||||
}
|
||||
|
||||
@ -7,6 +7,7 @@
|
||||
|
||||
pub mod instruction;
|
||||
pub mod instruction_v2; // New 25-instruction specification
|
||||
pub mod instruction_introspection; // Introspection helpers for tests (core instruction names)
|
||||
pub mod basic_block;
|
||||
pub mod function;
|
||||
pub mod builder;
|
||||
@ -190,14 +191,11 @@ mod tests {
|
||||
#[test]
|
||||
fn test_lowering_extern_console_log() {
|
||||
// Build AST: console.log("hi") → ExternCall env.console.log
|
||||
let ast = ASTNode::Expression {
|
||||
expr: Box::new(ASTNode::MethodCall {
|
||||
let ast = ASTNode::MethodCall {
|
||||
object: Box::new(ASTNode::Variable { name: "console".to_string(), span: crate::ast::Span::unknown() }),
|
||||
method: "log".to_string(),
|
||||
arguments: vec![ ASTNode::Literal { value: LiteralValue::String("hi".to_string()), span: crate::ast::Span::unknown() } ],
|
||||
span: crate::ast::Span::unknown(),
|
||||
}),
|
||||
span: crate::ast::Span::unknown(),
|
||||
};
|
||||
|
||||
let mut compiler = MirCompiler::new();
|
||||
@ -210,14 +208,11 @@ mod tests {
|
||||
#[test]
|
||||
fn test_lowering_boxcall_array_push() {
|
||||
// Build AST: (new ArrayBox()).push(1)
|
||||
let ast = ASTNode::Expression {
|
||||
expr: Box::new(ASTNode::MethodCall {
|
||||
let ast = ASTNode::MethodCall {
|
||||
object: Box::new(ASTNode::New { class: "ArrayBox".to_string(), arguments: vec![], type_arguments: vec![], span: crate::ast::Span::unknown() }),
|
||||
method: "push".to_string(),
|
||||
arguments: vec![ ASTNode::Literal { value: LiteralValue::Integer(1), span: crate::ast::Span::unknown() } ],
|
||||
span: crate::ast::Span::unknown(),
|
||||
}),
|
||||
span: crate::ast::Span::unknown(),
|
||||
};
|
||||
|
||||
let mut compiler = MirCompiler::new();
|
||||
|
||||
@ -6,6 +6,7 @@
|
||||
|
||||
use super::{MirModule, MirFunction, BasicBlock, MirInstruction};
|
||||
use std::fmt::Write;
|
||||
use crate::debug::log as dlog;
|
||||
|
||||
/// MIR printer for debug output and visualization
|
||||
pub struct MirPrinter {
|
||||
@ -148,6 +149,8 @@ impl MirPrinter {
|
||||
for block in function.blocks.values() {
|
||||
for inst in &block.instructions {
|
||||
match inst {
|
||||
MirInstruction::Throw { .. } => { if dlog::on("NYASH_DEBUG_MIR_PRINTER") { eprintln!("[PRINTER] found throw in {}", function.signature.name); } }
|
||||
MirInstruction::Catch { .. } => { if dlog::on("NYASH_DEBUG_MIR_PRINTER") { eprintln!("[PRINTER] found catch in {}", function.signature.name); } }
|
||||
MirInstruction::TypeCheck { .. } => type_check += 1,
|
||||
MirInstruction::Cast { .. } => type_cast += 1,
|
||||
MirInstruction::TypeOp { op, .. } => match op {
|
||||
@ -171,6 +174,8 @@ impl MirPrinter {
|
||||
}
|
||||
if let Some(term) = &block.terminator {
|
||||
match term {
|
||||
MirInstruction::Throw { .. } => { if dlog::on("NYASH_DEBUG_MIR_PRINTER") { eprintln!("[PRINTER] found throw(term) in {}", function.signature.name); } }
|
||||
MirInstruction::Catch { .. } => { if dlog::on("NYASH_DEBUG_MIR_PRINTER") { eprintln!("[PRINTER] found catch(term) in {}", function.signature.name); } }
|
||||
MirInstruction::TypeCheck { .. } => type_check += 1,
|
||||
MirInstruction::Cast { .. } => type_cast += 1,
|
||||
MirInstruction::TypeOp { op, .. } => match op {
|
||||
|
||||
@ -5,6 +5,7 @@
|
||||
*/
|
||||
|
||||
use super::{MirModule, MirFunction, BasicBlockId, ValueId};
|
||||
use crate::debug::log as dlog;
|
||||
use std::collections::{HashSet, HashMap};
|
||||
|
||||
/// Verification error types
|
||||
@ -145,6 +146,10 @@ impl MirVerifier {
|
||||
if local_errors.is_empty() {
|
||||
Ok(())
|
||||
} else {
|
||||
if dlog::on("NYASH_DEBUG_VERIFIER") {
|
||||
eprintln!("[VERIFY] {} errors in function {}", local_errors.len(), function.signature.name);
|
||||
for e in &local_errors { eprintln!(" • {:?}", e); }
|
||||
}
|
||||
Err(local_errors)
|
||||
}
|
||||
}
|
||||
|
||||
@ -51,7 +51,10 @@ impl NyashRunner {
|
||||
runtime::init_global_unified_registry();
|
||||
|
||||
// Try to initialize BID plugins from nyash.toml (best-effort)
|
||||
// Allow disabling during snapshot/CI via NYASH_DISABLE_PLUGINS=1
|
||||
if std::env::var("NYASH_DISABLE_PLUGINS").ok().as_deref() != Some("1") {
|
||||
runner_plugin_init::init_bid_plugins();
|
||||
}
|
||||
|
||||
// Optional: enable VM stats via CLI flags
|
||||
if self.config.vm_stats {
|
||||
@ -114,7 +117,8 @@ impl NyashRunner {
|
||||
println!("Memory safety guaranteed by Rust's borrow checker! 🛡️");
|
||||
}
|
||||
|
||||
/// Execute Nyash file with interpreter
|
||||
/// Execute Nyash file with interpreter (moved to modes/common.rs)
|
||||
#[cfg(any())]
|
||||
fn execute_nyash_file(&self, filename: &str) {
|
||||
// Read the file
|
||||
let code = match fs::read_to_string(filename) {
|
||||
@ -178,7 +182,8 @@ impl NyashRunner {
|
||||
}
|
||||
}
|
||||
|
||||
/// Execute MIR compilation and processing mode
|
||||
/// Execute MIR compilation and processing mode (moved to modes/mir.rs)
|
||||
#[cfg(any())]
|
||||
fn execute_mir_mode(&self, filename: &str) {
|
||||
// Read the file
|
||||
let code = match fs::read_to_string(filename) {
|
||||
@ -233,7 +238,8 @@ impl NyashRunner {
|
||||
}
|
||||
}
|
||||
|
||||
/// Execute VM mode
|
||||
/// Execute VM mode (moved to modes/vm.rs)
|
||||
#[cfg(any())]
|
||||
fn execute_vm_mode(&self, filename: &str) {
|
||||
// Read the file
|
||||
let code = match fs::read_to_string(filename) {
|
||||
@ -295,7 +301,8 @@ impl NyashRunner {
|
||||
|
||||
|
||||
|
||||
/// Collect Box declarations from AST and register into runtime
|
||||
/// Collect Box declarations (moved to modes/vm.rs)
|
||||
#[cfg(any())]
|
||||
fn collect_box_declarations(&self, ast: &ASTNode, runtime: &NyashRuntime) {
|
||||
fn walk(node: &ASTNode, runtime: &NyashRuntime) {
|
||||
match node {
|
||||
@ -342,7 +349,8 @@ impl NyashRunner {
|
||||
|
||||
// execute_aot_mode moved to runner::modes::aot
|
||||
|
||||
/// Execute LLVM mode
|
||||
/// Execute LLVM mode (moved to modes/llvm.rs)
|
||||
#[cfg(any())]
|
||||
fn execute_llvm_mode(&self, filename: &str) {
|
||||
// Read the file
|
||||
let code = match fs::read_to_string(filename) {
|
||||
@ -436,7 +444,8 @@ impl NyashRunner {
|
||||
}
|
||||
}
|
||||
|
||||
/// Execute benchmark mode
|
||||
/// Execute benchmark mode (moved to modes/bench.rs)
|
||||
#[cfg(any())]
|
||||
fn execute_benchmark_mode(&self) {
|
||||
println!("🏁 Running benchmark mode with {} iterations", self.config.iterations);
|
||||
|
||||
@ -728,9 +737,12 @@ mod tests {
|
||||
let config = CliConfig {
|
||||
file: None,
|
||||
debug_fuel: Some(100000),
|
||||
dump_ast: false,
|
||||
dump_mir: false,
|
||||
verify_mir: false,
|
||||
mir_verbose: false,
|
||||
mir_verbose_effects: false,
|
||||
no_optimize: false,
|
||||
backend: "interpreter".to_string(),
|
||||
compile_wasm: false,
|
||||
compile_native: false,
|
||||
|
||||
46
tests/mir_instruction_set_sync.rs
Normal file
46
tests/mir_instruction_set_sync.rs
Normal file
@ -0,0 +1,46 @@
|
||||
use std::fs;
|
||||
use std::path::Path;
|
||||
use nyash_rust::mir::instruction_introspection;
|
||||
|
||||
// Compare the source-of-truth doc and implementation core instruction names.
|
||||
#[test]
|
||||
fn mir_instruction_set_doc_sync() {
|
||||
// 1) Read the canonical list from docs
|
||||
let doc_path = Path::new("docs/reference/mir/INSTRUCTION_SET.md");
|
||||
let content = fs::read_to_string(doc_path)
|
||||
.expect("Failed to read docs/reference/mir/INSTRUCTION_SET.md");
|
||||
|
||||
let mut in_core = false;
|
||||
let mut doc_names: Vec<String> = Vec::new();
|
||||
for line in content.lines() {
|
||||
let line = line.trim();
|
||||
if line.starts_with("## Core Instructions") {
|
||||
in_core = true;
|
||||
continue;
|
||||
}
|
||||
if in_core && line.starts_with("## ") { // stop at next section (e.g., Meta)
|
||||
break;
|
||||
}
|
||||
if in_core {
|
||||
if let Some(rest) = line.strip_prefix("- ") {
|
||||
// Each bullet is a name, possibly with annotations like (...) → strip anything after a space/paren
|
||||
let name = rest.split(|c: char| c.is_whitespace() || c == '(' || c == '(')
|
||||
.next().unwrap_or("").trim();
|
||||
if !name.is_empty() {
|
||||
doc_names.push(name.to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
assert_eq!(doc_names.len(), 26, "Doc must list exactly 26 core instructions");
|
||||
|
||||
// 2) Implementation list
|
||||
let impl_names = instruction_introspection::core_instruction_names();
|
||||
|
||||
// Compare as sets (order-agnostic)
|
||||
let doc_set: std::collections::BTreeSet<_> = doc_names.iter().map(|s| s.as_str()).collect();
|
||||
let impl_set: std::collections::BTreeSet<_> = impl_names.iter().copied().collect();
|
||||
|
||||
assert_eq!(doc_set, impl_set, "MIR core instruction names must match docs exactly");
|
||||
}
|
||||
@ -29,8 +29,11 @@ fi
|
||||
|
||||
if [ -n "$OUTFILE" ]; then
|
||||
mkdir -p "$(dirname "$OUTFILE")"
|
||||
"${CMD[@]}" | sed -e :a -e '/^\n*$/{$d;N;ba' -e '}' > "$OUTFILE"
|
||||
# Filter noisy plugin/runtime banners to stabilize snapshots
|
||||
NYASH_CLI_VERBOSE=1 "${CMD[@]}" \
|
||||
| grep -Ev '^(\[PluginLoaderV2\]|\[FileBox\]|Net plugin:)' \
|
||||
| sed -e :a -e '/^\n*$/{$d;N;ba' -e '}' > "$OUTFILE"
|
||||
echo "Wrote MIR snapshot: $OUTFILE"
|
||||
else
|
||||
"${CMD[@]}"
|
||||
NYASH_CLI_VERBOSE=1 "${CMD[@]}"
|
||||
fi
|
||||
|
||||
Reference in New Issue
Block a user