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`フィールドを使用)
|
**根本原因:** Claudeのコマンド再構築機能のバグ(`pattern`ではなく`op`フィールドを使用)
|
||||||
|
|
||||||
### 💡 実践的な使い分けガイド
|
|
||||||
|
|
||||||
**`bash -c`を使うべき時:**
|
|
||||||
- glob展開 + パイプを使う時(`ls *.rs | wc -l`)
|
|
||||||
- 複雑なシェル機能を使う時(リダイレクト、サブシェル等)
|
|
||||||
- 「glob: そのようなファイル...」エラーが出た時
|
|
||||||
|
|
||||||
**findを使うべき時:**
|
|
||||||
- ディレクトリを再帰的に検索する時
|
|
||||||
- より複雑なファイル条件(サイズ、更新日時等)を使う時
|
|
||||||
- 確実性を最優先する時
|
|
||||||
|
|
||||||
**直接ファイル名を書くべき時:**
|
|
||||||
- ファイル数が少ない時(5個以下)
|
|
||||||
- ファイル名が確定している時
|
|
||||||
- スクリプトで使う時(可読性重視)
|
|
||||||
|
|
||||||
## 🔧 開発サポート
|
## 🔧 開発サポート
|
||||||
|
|
||||||
### 🤖 AI相談
|
### 🤖 AI相談
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
# 🎯 CURRENT TASK - 2025年8月25日(状況整理)
|
# 🎯 CURRENT TASK - 2025年8月26日(状況整理&再起動ショートカット)
|
||||||
|
|
||||||
## ⏱️ 再開ショートカット(今日のフォーカス)
|
## ⏱️ 再開ショートカット(今日のフォーカス)
|
||||||
- フォーカス: VM比較経路の安定化後片付け + 1000行分解の下準備
|
- フォーカス: 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`
|
- 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`
|
- 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)
|
## 🚨 現在の状況(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` と連携。
|
- Optimizer: 未lowering検知(is/as/isType/asType)をBoxCall/Call両経路で検出、`NYASH_OPT_DIAG_FAIL=1` と連携。
|
||||||
- 代表スナップショット: extern_call/loop/boxcall/typeop_mixed をCIに追加、全件緑。
|
- 代表スナップショット: extern_call/loop/boxcall/typeop_mixed をCIに追加、全件緑。
|
||||||
- 注: WeakRef/Barrier の“統合”はPoCフラグで切替可能(レガシー命令も支援)—MIR26はドキュメントの正典、実装は互換を維持。
|
- 注: 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 {
|
impl VM {
|
||||||
/// Helper: execute phi selection based on previous_block (borrow-safe minimal)
|
/// 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> {
|
pub(super) fn loop_execute_phi(&mut self, dst: ValueId, inputs: &[(BasicBlockId, ValueId)]) -> Result<VMValue, VMError> {
|
||||||
if inputs.is_empty() {
|
if inputs.is_empty() {
|
||||||
return Err(VMError::InvalidInstruction("Phi node has no inputs".to_string()));
|
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")
|
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");
|
|| std::env::var("NYASH_VM_DEBUG_PHI").ok().as_deref() == Some("1");
|
||||||
let prev = self.previous_block;
|
if debug_phi { eprintln!("[VM] phi-select (delegated) prev={:?} inputs={:?}", self.previous_block, inputs); }
|
||||||
if debug_phi { eprintln!("[VM] phi-select prev={:?} inputs={:?}", prev, inputs); }
|
// Borrow just the values storage immutably to avoid borrowing entire &self in closure
|
||||||
if let Some(prev_bb) = prev {
|
let values_ref = &self.values;
|
||||||
if let Some((_, val_id)) = inputs.iter().find(|(bb, _)| *bb == prev_bb) {
|
let res = self.loop_executor.execute_phi(dst, inputs, |val_id| {
|
||||||
if debug_phi { eprintln!("[VM] phi-select hit prev={:?} -> {:?}", prev_bb, val_id); }
|
let index = val_id.to_usize();
|
||||||
return self.get_value(*val_id);
|
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
|
res
|
||||||
let (_, val_id) = inputs[0];
|
|
||||||
if debug_phi { eprintln!("[VM] phi-select fallback first -> {:?}", val_id); }
|
|
||||||
self.get_value(val_id)
|
|
||||||
}
|
}
|
||||||
/// Create a new VM instance
|
/// Create a new VM instance
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
@ -395,7 +405,7 @@ impl VM {
|
|||||||
.ok_or_else(|| VMError::InvalidBasicBlock(format!("Block {} not found", current_block)))?;
|
.ok_or_else(|| VMError::InvalidBasicBlock(format!("Block {} not found", current_block)))?;
|
||||||
|
|
||||||
self.frame.current_block = Some(current_block);
|
self.frame.current_block = Some(current_block);
|
||||||
self.pc = 0;
|
self.frame.pc = 0;
|
||||||
|
|
||||||
let mut next_block = None;
|
let mut next_block = None;
|
||||||
let mut should_return = None;
|
let mut should_return = None;
|
||||||
@ -403,7 +413,7 @@ impl VM {
|
|||||||
// Execute instructions in this block (including terminator)
|
// Execute instructions in this block (including terminator)
|
||||||
let all_instructions: Vec<_> = block.all_instructions().collect();
|
let all_instructions: Vec<_> = block.all_instructions().collect();
|
||||||
for (index, instruction) in all_instructions.iter().enumerate() {
|
for (index, instruction) in all_instructions.iter().enumerate() {
|
||||||
self.pc = index;
|
self.frame.pc = index;
|
||||||
|
|
||||||
match self.execute_instruction(instruction)? {
|
match self.execute_instruction(instruction)? {
|
||||||
ControlFlow::Continue => continue,
|
ControlFlow::Continue => continue,
|
||||||
@ -550,24 +560,7 @@ impl VM {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ResultBox (box_trait::ResultBox - legacy)
|
// Legacy box_trait::ResultBox is no longer handled here (migration complete)
|
||||||
{
|
|
||||||
#[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())),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Generic fallback: toString for any Box type
|
// Generic fallback: toString for any Box type
|
||||||
if method == "toString" {
|
if method == "toString" {
|
||||||
|
|||||||
@ -23,18 +23,7 @@ impl VM {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ResultBox (box_trait::ResultBox - legacy)
|
// Legacy box_trait::ResultBox path removed (migration complete)
|
||||||
{
|
|
||||||
#[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())),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Generic fallback: toString for any Box type
|
// Generic fallback: toString for any Box type
|
||||||
if method == "toString" {
|
if method == "toString" {
|
||||||
|
|||||||
@ -335,6 +335,8 @@ impl VM {
|
|||||||
|
|
||||||
/// Execute RefGet instruction
|
/// Execute RefGet instruction
|
||||||
pub(super) fn execute_ref_get(&mut self, dst: ValueId, reference: ValueId, field: &str) -> Result<ControlFlow, VMError> {
|
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.
|
// Visibility check (if class known and visibility declared). Skip for internal refs.
|
||||||
let is_internal = self.object_internal.contains(&reference);
|
let is_internal = self.object_internal.contains(&reference);
|
||||||
if !is_internal {
|
if !is_internal {
|
||||||
@ -352,13 +354,16 @@ impl VM {
|
|||||||
// Get field value from object
|
// Get field value from object
|
||||||
let field_value = if let Some(fields) = self.object_fields.get(&reference) {
|
let field_value = if let Some(fields) = self.object_fields.get(&reference) {
|
||||||
if let Some(value) = fields.get(field) {
|
if let Some(value) = fields.get(field) {
|
||||||
|
if debug_ref { eprintln!("[VM] RefGet hit: {} -> {:?}", field, value); }
|
||||||
value.clone()
|
value.clone()
|
||||||
} else {
|
} else {
|
||||||
// Field not set yet, return default
|
// Field not set yet, return default
|
||||||
|
if debug_ref { eprintln!("[VM] RefGet miss: {} -> default 0", field); }
|
||||||
VMValue::Integer(0)
|
VMValue::Integer(0)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Object has no fields yet, return default
|
// Object has no fields yet, return default
|
||||||
|
if debug_ref { eprintln!("[VM] RefGet no fields: -> default 0"); }
|
||||||
VMValue::Integer(0)
|
VMValue::Integer(0)
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -368,8 +373,10 @@ impl VM {
|
|||||||
|
|
||||||
/// Execute RefSet instruction
|
/// Execute RefSet instruction
|
||||||
pub(super) fn execute_ref_set(&mut self, reference: ValueId, field: &str, value: ValueId) -> Result<ControlFlow, VMError> {
|
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
|
// Get the value to set
|
||||||
let new_value = self.get_value(value)?;
|
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)
|
// Visibility check (Skip for internal refs; otherwise enforce public)
|
||||||
let is_internal = self.object_internal.contains(&reference);
|
let is_internal = self.object_internal.contains(&reference);
|
||||||
if !is_internal {
|
if !is_internal {
|
||||||
@ -393,6 +400,7 @@ impl VM {
|
|||||||
// Set the field
|
// Set the field
|
||||||
if let Some(fields) = self.object_fields.get_mut(&reference) {
|
if let Some(fields) = self.object_fields.get_mut(&reference) {
|
||||||
fields.insert(field.to_string(), new_value);
|
fields.insert(field.to_string(), new_value);
|
||||||
|
if debug_ref { eprintln!("[VM] RefSet stored: {}", field); }
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(ControlFlow::Continue)
|
Ok(ControlFlow::Continue)
|
||||||
@ -515,19 +523,28 @@ impl VM {
|
|||||||
// Call the method based on receiver type
|
// Call the method based on receiver type
|
||||||
let result = match &recv {
|
let result = match &recv {
|
||||||
VMValue::BoxRef(arc_box) => {
|
VMValue::BoxRef(arc_box) => {
|
||||||
// Direct box method call
|
// If this is a user InstanceBox, redirect to lowered function: Class.method/arity
|
||||||
if debug_boxcall {
|
if let Some(inst) = arc_box.as_any().downcast_ref::<crate::instance_v2::InstanceBox>() {
|
||||||
eprintln!("[BoxCall] Taking BoxRef path for method '{}'", method);
|
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();
|
let cloned_box = arc_box.share_box();
|
||||||
self.call_box_method(cloned_box, method, nyash_args)?
|
self.call_box_method(cloned_box, method, nyash_args)?
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
// Convert primitive to box and call
|
// Convert primitive to box and call
|
||||||
if debug_boxcall {
|
if debug_boxcall { eprintln!("[BoxCall] Converting primitive to box for method '{}'", method); }
|
||||||
eprintln!("[BoxCall] Converting primitive to box for method '{}'", method);
|
|
||||||
}
|
|
||||||
|
|
||||||
let box_value = recv.to_nyash_box();
|
let box_value = recv.to_nyash_box();
|
||||||
self.call_box_method(box_value, method, nyash_args)?
|
self.call_box_method(box_value, method, nyash_args)?
|
||||||
|
|||||||
@ -13,6 +13,8 @@ use super::vm::{VM, VMError, VMValue};
|
|||||||
impl VM {
|
impl VM {
|
||||||
/// Execute binary operation
|
/// Execute binary operation
|
||||||
pub(super) fn execute_binary_op(&self, op: &BinaryOp, left: &VMValue, right: &VMValue) -> Result<VMValue, VMError> {
|
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
|
// Fast path: logical AND/OR accept any truthy via as_bool
|
||||||
if matches!(*op, BinaryOp::And | BinaryOp::Or) {
|
if matches!(*op, BinaryOp::And | BinaryOp::Or) {
|
||||||
let l = left.as_bool()?;
|
let l = left.as_bool()?;
|
||||||
@ -121,6 +123,43 @@ impl VM {
|
|||||||
Ok(VMValue::Integer(res))
|
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))),
|
_ => Err(VMError::TypeError(format!("Unsupported binary operation: {:?} on {:?} and {:?}", op, left, right))),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -199,9 +199,12 @@ mod tests {
|
|||||||
let config = CliConfig {
|
let config = CliConfig {
|
||||||
file: None,
|
file: None,
|
||||||
debug_fuel: Some(100000),
|
debug_fuel: Some(100000),
|
||||||
|
dump_ast: false,
|
||||||
dump_mir: false,
|
dump_mir: false,
|
||||||
verify_mir: false,
|
verify_mir: false,
|
||||||
mir_verbose: false,
|
mir_verbose: false,
|
||||||
|
mir_verbose_effects: false,
|
||||||
|
no_optimize: false,
|
||||||
backend: "interpreter".to_string(),
|
backend: "interpreter".to_string(),
|
||||||
compile_wasm: false,
|
compile_wasm: false,
|
||||||
compile_native: 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.)
|
// Runtime system (plugins, registry, etc.)
|
||||||
pub mod runtime;
|
pub mod runtime;
|
||||||
pub mod runner_plugin_init;
|
pub mod runner_plugin_init;
|
||||||
|
pub mod debug;
|
||||||
|
|
||||||
#[cfg(target_arch = "wasm32")]
|
#[cfg(target_arch = "wasm32")]
|
||||||
pub mod wasm_test;
|
pub mod wasm_test;
|
||||||
|
|||||||
@ -53,6 +53,7 @@ pub mod config;
|
|||||||
|
|
||||||
// Runtime system (plugins, registry, etc.)
|
// Runtime system (plugins, registry, etc.)
|
||||||
pub mod runtime;
|
pub mod runtime;
|
||||||
|
pub mod debug;
|
||||||
|
|
||||||
use nyash_rust::cli::CliConfig;
|
use nyash_rust::cli::CliConfig;
|
||||||
use runner::NyashRunner;
|
use runner::NyashRunner;
|
||||||
|
|||||||
@ -844,6 +844,9 @@ impl MirBuilder {
|
|||||||
|
|
||||||
// Set up exception handler for the try block (before we enter it)
|
// Set up exception handler for the try block (before we enter it)
|
||||||
if let Some(catch_clause) = catch_clauses.first() {
|
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();
|
let exception_value = self.value_gen.next();
|
||||||
|
|
||||||
// Register catch handler for exceptions that may occur in try block
|
// Register catch handler for exceptions that may occur in try block
|
||||||
@ -874,9 +877,15 @@ impl MirBuilder {
|
|||||||
|
|
||||||
// Build catch block (reachable via exception handling)
|
// Build catch block (reachable via exception handling)
|
||||||
self.start_new_block(catch_block)?;
|
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
|
// Handle catch clause
|
||||||
if let Some(catch_clause) = catch_clauses.first() {
|
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
|
// Build catch body
|
||||||
let catch_ast = ASTNode::Program {
|
let catch_ast = ASTNode::Program {
|
||||||
statements: catch_clause.body.clone(),
|
statements: catch_clause.body.clone(),
|
||||||
|
|||||||
@ -5,6 +5,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
|
use crate::debug::log as dlog;
|
||||||
|
|
||||||
/// Effect flags for tracking side effects and enabling optimizations
|
/// Effect flags for tracking side effects and enabling optimizations
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||||
@ -129,8 +130,16 @@ impl EffectMask {
|
|||||||
|
|
||||||
/// Check if the computation is pure (no side effects)
|
/// Check if the computation is pure (no side effects)
|
||||||
pub fn is_pure(self) -> bool {
|
pub fn is_pure(self) -> bool {
|
||||||
// 純粋性は一次カテゴリで判定(Pureビットが立っていてもIO/WRITE/CONTROL等があれば純粋ではない)
|
// READ/WRITE/IO/CONTROLがあれば純粋ではない(READはreadonly扱い)
|
||||||
self.primary_category() == Effect::Pure
|
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)
|
/// Check if the computation is mutable (modifies state)
|
||||||
|
|||||||
@ -412,7 +412,7 @@ impl MirInstruction {
|
|||||||
|
|
||||||
// Phase 5: Control flow & exception handling
|
// Phase 5: Control flow & exception handling
|
||||||
MirInstruction::Throw { effects, .. } => *effects,
|
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
|
MirInstruction::Safepoint => EffectMask::PURE, // No-op for now
|
||||||
|
|
||||||
// Phase 6: Box reference operations
|
// 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;
|
||||||
pub mod instruction_v2; // New 25-instruction specification
|
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 basic_block;
|
||||||
pub mod function;
|
pub mod function;
|
||||||
pub mod builder;
|
pub mod builder;
|
||||||
@ -190,13 +191,10 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn test_lowering_extern_console_log() {
|
fn test_lowering_extern_console_log() {
|
||||||
// Build AST: console.log("hi") → ExternCall env.console.log
|
// Build AST: console.log("hi") → ExternCall env.console.log
|
||||||
let ast = ASTNode::Expression {
|
let ast = ASTNode::MethodCall {
|
||||||
expr: Box::new(ASTNode::MethodCall {
|
object: Box::new(ASTNode::Variable { name: "console".to_string(), span: crate::ast::Span::unknown() }),
|
||||||
object: Box::new(ASTNode::Variable { name: "console".to_string(), span: crate::ast::Span::unknown() }),
|
method: "log".to_string(),
|
||||||
method: "log".to_string(),
|
arguments: vec![ ASTNode::Literal { value: LiteralValue::String("hi".to_string()), span: crate::ast::Span::unknown() } ],
|
||||||
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(),
|
span: crate::ast::Span::unknown(),
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -210,13 +208,10 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn test_lowering_boxcall_array_push() {
|
fn test_lowering_boxcall_array_push() {
|
||||||
// Build AST: (new ArrayBox()).push(1)
|
// Build AST: (new ArrayBox()).push(1)
|
||||||
let ast = ASTNode::Expression {
|
let ast = ASTNode::MethodCall {
|
||||||
expr: Box::new(ASTNode::MethodCall {
|
object: Box::new(ASTNode::New { class: "ArrayBox".to_string(), arguments: vec![], type_arguments: vec![], span: crate::ast::Span::unknown() }),
|
||||||
object: Box::new(ASTNode::New { class: "ArrayBox".to_string(), arguments: vec![], type_arguments: vec![], span: crate::ast::Span::unknown() }),
|
method: "push".to_string(),
|
||||||
method: "push".to_string(),
|
arguments: vec![ ASTNode::Literal { value: LiteralValue::Integer(1), span: crate::ast::Span::unknown() } ],
|
||||||
arguments: vec![ ASTNode::Literal { value: LiteralValue::Integer(1), span: crate::ast::Span::unknown() } ],
|
|
||||||
span: crate::ast::Span::unknown(),
|
|
||||||
}),
|
|
||||||
span: crate::ast::Span::unknown(),
|
span: crate::ast::Span::unknown(),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -6,6 +6,7 @@
|
|||||||
|
|
||||||
use super::{MirModule, MirFunction, BasicBlock, MirInstruction};
|
use super::{MirModule, MirFunction, BasicBlock, MirInstruction};
|
||||||
use std::fmt::Write;
|
use std::fmt::Write;
|
||||||
|
use crate::debug::log as dlog;
|
||||||
|
|
||||||
/// MIR printer for debug output and visualization
|
/// MIR printer for debug output and visualization
|
||||||
pub struct MirPrinter {
|
pub struct MirPrinter {
|
||||||
@ -148,6 +149,8 @@ impl MirPrinter {
|
|||||||
for block in function.blocks.values() {
|
for block in function.blocks.values() {
|
||||||
for inst in &block.instructions {
|
for inst in &block.instructions {
|
||||||
match inst {
|
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::TypeCheck { .. } => type_check += 1,
|
||||||
MirInstruction::Cast { .. } => type_cast += 1,
|
MirInstruction::Cast { .. } => type_cast += 1,
|
||||||
MirInstruction::TypeOp { op, .. } => match op {
|
MirInstruction::TypeOp { op, .. } => match op {
|
||||||
@ -171,6 +174,8 @@ impl MirPrinter {
|
|||||||
}
|
}
|
||||||
if let Some(term) = &block.terminator {
|
if let Some(term) = &block.terminator {
|
||||||
match term {
|
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::TypeCheck { .. } => type_check += 1,
|
||||||
MirInstruction::Cast { .. } => type_cast += 1,
|
MirInstruction::Cast { .. } => type_cast += 1,
|
||||||
MirInstruction::TypeOp { op, .. } => match op {
|
MirInstruction::TypeOp { op, .. } => match op {
|
||||||
|
|||||||
@ -5,6 +5,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
use super::{MirModule, MirFunction, BasicBlockId, ValueId};
|
use super::{MirModule, MirFunction, BasicBlockId, ValueId};
|
||||||
|
use crate::debug::log as dlog;
|
||||||
use std::collections::{HashSet, HashMap};
|
use std::collections::{HashSet, HashMap};
|
||||||
|
|
||||||
/// Verification error types
|
/// Verification error types
|
||||||
@ -145,6 +146,10 @@ impl MirVerifier {
|
|||||||
if local_errors.is_empty() {
|
if local_errors.is_empty() {
|
||||||
Ok(())
|
Ok(())
|
||||||
} else {
|
} 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)
|
Err(local_errors)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -51,7 +51,10 @@ impl NyashRunner {
|
|||||||
runtime::init_global_unified_registry();
|
runtime::init_global_unified_registry();
|
||||||
|
|
||||||
// Try to initialize BID plugins from nyash.toml (best-effort)
|
// Try to initialize BID plugins from nyash.toml (best-effort)
|
||||||
runner_plugin_init::init_bid_plugins();
|
// 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
|
// Optional: enable VM stats via CLI flags
|
||||||
if self.config.vm_stats {
|
if self.config.vm_stats {
|
||||||
@ -114,7 +117,8 @@ impl NyashRunner {
|
|||||||
println!("Memory safety guaranteed by Rust's borrow checker! 🛡️");
|
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) {
|
fn execute_nyash_file(&self, filename: &str) {
|
||||||
// Read the file
|
// Read the file
|
||||||
let code = match fs::read_to_string(filename) {
|
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) {
|
fn execute_mir_mode(&self, filename: &str) {
|
||||||
// Read the file
|
// Read the file
|
||||||
let code = match fs::read_to_string(filename) {
|
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) {
|
fn execute_vm_mode(&self, filename: &str) {
|
||||||
// Read the file
|
// Read the file
|
||||||
let code = match fs::read_to_string(filename) {
|
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 collect_box_declarations(&self, ast: &ASTNode, runtime: &NyashRuntime) {
|
||||||
fn walk(node: &ASTNode, runtime: &NyashRuntime) {
|
fn walk(node: &ASTNode, runtime: &NyashRuntime) {
|
||||||
match node {
|
match node {
|
||||||
@ -342,7 +349,8 @@ impl NyashRunner {
|
|||||||
|
|
||||||
// execute_aot_mode moved to runner::modes::aot
|
// 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) {
|
fn execute_llvm_mode(&self, filename: &str) {
|
||||||
// Read the file
|
// Read the file
|
||||||
let code = match fs::read_to_string(filename) {
|
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) {
|
fn execute_benchmark_mode(&self) {
|
||||||
println!("🏁 Running benchmark mode with {} iterations", self.config.iterations);
|
println!("🏁 Running benchmark mode with {} iterations", self.config.iterations);
|
||||||
|
|
||||||
@ -728,9 +737,12 @@ mod tests {
|
|||||||
let config = CliConfig {
|
let config = CliConfig {
|
||||||
file: None,
|
file: None,
|
||||||
debug_fuel: Some(100000),
|
debug_fuel: Some(100000),
|
||||||
|
dump_ast: false,
|
||||||
dump_mir: false,
|
dump_mir: false,
|
||||||
verify_mir: false,
|
verify_mir: false,
|
||||||
mir_verbose: false,
|
mir_verbose: false,
|
||||||
|
mir_verbose_effects: false,
|
||||||
|
no_optimize: false,
|
||||||
backend: "interpreter".to_string(),
|
backend: "interpreter".to_string(),
|
||||||
compile_wasm: false,
|
compile_wasm: false,
|
||||||
compile_native: 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
|
if [ -n "$OUTFILE" ]; then
|
||||||
mkdir -p "$(dirname "$OUTFILE")"
|
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"
|
echo "Wrote MIR snapshot: $OUTFILE"
|
||||||
else
|
else
|
||||||
"${CMD[@]}"
|
NYASH_CLI_VERBOSE=1 "${CMD[@]}"
|
||||||
fi
|
fi
|
||||||
|
|||||||
Reference in New Issue
Block a user