Phase 2: TypeOp変換の一本化(Optimizer安全ネット削除)\nPhase 3: 可視化/スナップショット基盤(--mir-verbose-effects, snapshot/compare/ci_check)\nDocs: Phase 1/2 完了マーク・利用方法追記
This commit is contained in:
28
.github/workflows/mir-golden-ci.yml
vendored
Normal file
28
.github/workflows/mir-golden-ci.yml
vendored
Normal file
@ -0,0 +1,28 @@
|
||||
name: MIR Golden CI
|
||||
|
||||
on:
|
||||
push:
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
build-and-compare:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install Rust (stable)
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
|
||||
- name: Cache cargo
|
||||
uses: Swatinem/rust-cache@v2
|
||||
|
||||
- name: Build (release)
|
||||
run: cargo build --release
|
||||
|
||||
- name: Make tools executable
|
||||
run: chmod +x tools/*.sh
|
||||
|
||||
- name: Compare MIR against golden snapshots
|
||||
run: bash tools/ci_check_golden.sh
|
||||
|
||||
129
CLAUDE.md
129
CLAUDE.md
@ -308,6 +308,135 @@ Read docs/reference/ # まずドキュメント(API/言語仕様の入口)
|
||||
# → それでも不明 → ソース確認
|
||||
```
|
||||
|
||||
## 🏗️ 開発設計原則(綺麗で破綻しない作り)
|
||||
|
||||
### 📦 Everything is Box - 内部実装でも箱原理を貫く
|
||||
|
||||
#### 1. **単一責任の箱**
|
||||
```rust
|
||||
// ✅ 良い例:各モジュールが単一の責任を持つ
|
||||
MirBuilder: AST → MIR変換のみ(最適化しない)
|
||||
MirOptimizer: MIRの最適化のみ(変換しない)
|
||||
VM: 実行のみ(最適化しない)
|
||||
|
||||
// ❌ 悪い例:複数の責任が混在
|
||||
BuilderOptimizer: 変換も最適化も実行も...
|
||||
```
|
||||
|
||||
#### 2. **明確なインターフェース**
|
||||
```rust
|
||||
// ✅ エフェクトは単純に
|
||||
enum Effect {
|
||||
Pure, // 副作用なし
|
||||
ReadOnly, // 読み取りのみ
|
||||
SideEffect // 書き込み/IO/例外
|
||||
}
|
||||
|
||||
// ❌ 複雑な組み合わせは避ける
|
||||
PURE.add(IO) // これがpureかどうか分からない!
|
||||
```
|
||||
|
||||
#### 3. **段階的な処理パイプライン**
|
||||
```
|
||||
AST → Builder → MIR → Optimizer → VM
|
||||
↑ ↑ ↑ ↑ ↑
|
||||
明確な入力 明確な出力 不変保証 最適化のみ 実行のみ
|
||||
```
|
||||
|
||||
### 🎯 カプセル化の徹底
|
||||
|
||||
#### 1. **内部状態を隠蔽**
|
||||
```rust
|
||||
// ✅ 良い例:内部実装を隠す
|
||||
pub struct MirOptimizer {
|
||||
debug: bool, // 設定のみ公開
|
||||
enable_typeop_net: bool, // 設定のみ公開
|
||||
// 内部の複雑な状態は隠蔽
|
||||
}
|
||||
|
||||
impl MirOptimizer {
|
||||
pub fn new() -> Self { ... }
|
||||
pub fn with_debug(self) -> Self { ... }
|
||||
pub fn optimize(&mut self, module: &mut MirModule) { ... }
|
||||
}
|
||||
```
|
||||
|
||||
#### 2. **変更の局所化**
|
||||
- 新機能追加時:1つのモジュールのみ変更
|
||||
- バグ修正時:影響範囲が明確
|
||||
- テスト:各モジュール独立でテスト可能
|
||||
|
||||
### 🌟 美しさの基準
|
||||
|
||||
#### 1. **読みやすさ > 賢さ**
|
||||
```rust
|
||||
// ✅ 単純で分かりやすい
|
||||
if effect == Effect::Pure {
|
||||
can_eliminate = true;
|
||||
}
|
||||
|
||||
// ❌ 賢いが分かりにくい
|
||||
can_eliminate = effect.0 & 0x01 == 0x01 && !(effect.0 & 0xFE);
|
||||
```
|
||||
|
||||
#### 2. **一貫性**
|
||||
- 命名規則の統一
|
||||
- エラー処理の統一
|
||||
- コメントスタイルの統一
|
||||
|
||||
### 🚀 大規模化への備え
|
||||
|
||||
#### 1. **モジュール分割の原則**
|
||||
```
|
||||
src/
|
||||
├── ast/ # AST定義のみ
|
||||
├── parser/ # パース処理のみ
|
||||
├── mir/ # MIR定義と基本操作
|
||||
│ ├── builder.rs # AST→MIR変換
|
||||
│ └── optimizer.rs # MIR最適化
|
||||
├── backend/ # 実行バックエンド
|
||||
│ ├── interpreter.rs # インタープリター
|
||||
│ ├── vm.rs # VM実行
|
||||
│ └── codegen_c.rs # C言語生成
|
||||
```
|
||||
|
||||
#### 2. **テストの階層化**
|
||||
- 単体テスト:各モジュール内で完結
|
||||
- 統合テスト:モジュール間の連携
|
||||
- E2Eテスト:全体の動作確認
|
||||
|
||||
#### 3. **設定の外部化**
|
||||
```rust
|
||||
// ✅ フラグで挙動を制御(再コンパイル不要)
|
||||
optimizer.enable_typeop_safety_net(flag);
|
||||
|
||||
// ❌ ハードコードされた挙動
|
||||
#[cfg(feature = "typeop_safety_net")]
|
||||
```
|
||||
|
||||
### 💡 デバッグとメンテナンス
|
||||
|
||||
#### 1. **段階的なデバッグ出力**
|
||||
```bash
|
||||
NYASH_BUILDER_DEBUG=1 # Builder のみ
|
||||
NYASH_OPT_DEBUG=1 # Optimizer のみ
|
||||
NYASH_VM_DEBUG=1 # VM のみ
|
||||
```
|
||||
|
||||
#### 2. **問題の早期発見**
|
||||
- 各段階でのアサーション
|
||||
- 不変条件の明示的チェック
|
||||
- 診断機能の組み込み
|
||||
|
||||
### 🎭 複雑さの管理
|
||||
|
||||
**複雑さは避けられないが、管理はできる**
|
||||
1. 複雑な部分を局所化
|
||||
2. インターフェースは単純に
|
||||
3. ドキュメントで意図を明示
|
||||
|
||||
**判断基準:3ヶ月後の自分が理解できるか?**
|
||||
|
||||
## 🔧 開発サポート
|
||||
|
||||
### 🤖 AI相談
|
||||
|
||||
@ -45,6 +45,8 @@
|
||||
2) Builder適用拡大(短期〜中期)
|
||||
- 言語 `is/as` 導線(最小でも擬似ノード)→ `emit_type_check/emit_cast` へ配線
|
||||
- 弱参照: 既存の `RefGet/RefSet` パスは弱フィールドで `WeakLoad/WeakNew`+Barrier(flag ONで統合命令)
|
||||
- 関数スタイル `isType/asType` の早期loweringを強化(`Literal("T")` と `new StringBox("T")` を確実に検出)
|
||||
- `print(isType(...))` の未定義SSA回避(print直前で必ず `TypeOp` のdstを生成)
|
||||
|
||||
3) VM/Verifierの補強(中期)
|
||||
- `TypeOp(Cast)` の数値キャスト(Int/Float)安全化、誤用時TypeError整備
|
||||
@ -57,6 +59,44 @@
|
||||
5) BoxCall高速化(性能段階)
|
||||
- `--vm-stats` ホットパス特定後、Fast-path/キャッシュ適用
|
||||
|
||||
## 🐛 既知の問題(要フォロー)
|
||||
- 関数スタイル `isType(value, "Integer")` が一部ケースで `TypeOp` にloweringされず、`print %X` が未定義参照になる事象
|
||||
- 現状: `asType` は `typeop cast` に変換されるが、`isType` が欠落するケースあり
|
||||
- 仮対処: Interpreterに `is/isType/as/asType` のフォールバックを実装済(実行エラー回避)
|
||||
- 恒久対処(次回対応):
|
||||
- Builderの早期loweringをFunctionCall分岐で強化(`Literal`/`StringBox`両対応、`print(...)` 内でも確実にdst生成)
|
||||
- Optimizerの安全ネット(BoxCall/Call→TypeOp)を `isType` パターンでも確実に発火させる(テーブル駆動の判定)
|
||||
|
||||
## ⏸️ セッション再開メモ(次にやること)
|
||||
- [ ] Builder: `extract_string_literal` の `StringBox`対応は導入済 → `FunctionCall` 早期loweringの再検証(`print(isType(...))` 直下)
|
||||
- [ ] Optimizer: `Call` 形式(関数呼び出し)でも `isType/asType` を検出して `TypeOp(Check/Cast)` に置換する安全ネットの強化とテスト
|
||||
- [ ] MIRダンプの確認:`local_tests/typeop_is_as_func_poc.nyash` に `typeop check/cast` が両方出ることを確認
|
||||
- [ ] スナップショット化:`typeop_is_as_*_poc.nyash` のダンプを固定し回帰検出
|
||||
|
||||
## ▶ 補助コマンド(検証系)
|
||||
```bash
|
||||
# リビルド
|
||||
cargo build --release -j32
|
||||
|
||||
# 関数スタイルのMIRダンプ確認(isType/asType)
|
||||
./target/release/nyash --dump-mir --mir-verbose local_tests/typeop_is_as_func_poc.nyash | sed -n '1,200p'
|
||||
|
||||
# メソッドスタイルのMIRダンプ確認(is/as)
|
||||
./target/release/nyash --dump-mir --mir-verbose local_tests/typeop_is_as_poc.nyash | sed -n '1,200p'
|
||||
```
|
||||
|
||||
### 🆕 開発時の可視化・診断(最小追加)
|
||||
- `--mir-verbose-effects`: MIRダンプ行末に効果カテゴリを表示(`pure|readonly|side`)
|
||||
- 例: `nyash --dump-mir --mir-verbose --mir-verbose-effects local_tests/typeop_is_as_func_poc.nyash`
|
||||
- `NYASH_OPT_DIAG_FAIL=1`: Optimizer診断で未lowering(`is|isType|as|asType`)検知時にエラー終了(CI向け)
|
||||
- 例: `NYASH_OPT_DIAG_FAIL=1 nyash --dump-mir --mir-verbose local_tests/typeop_diag_fail.nyash`
|
||||
- Builder生MIRスナップショット: `tools/snapshot_mir.sh <input.nyash> [output.txt]`
|
||||
- 例: `tools/snapshot_mir.sh local_tests/typeop_is_as_func_poc.nyash docs/status/golden/typeop_is_as_func_poc.mir.txt`
|
||||
- ゴールデン比較(ローカル/CI): `tools/ci_check_golden.sh`(代表2ケースを比較)
|
||||
- 例: `./tools/ci_check_golden.sh`(差分があれば非ゼロ終了)
|
||||
|
||||
補足: ASTの形状確認は `--dump-ast` を使用。
|
||||
|
||||
## ▶ 実行コマンド例
|
||||
|
||||
計測実行:
|
||||
|
||||
@ -45,6 +45,8 @@
|
||||
- AOT WASMネイティブ化
|
||||
- MIR最適化基盤
|
||||
- エスケープ解析実装
|
||||
- MIR/Builder/Optimizer簡略化計画(責務分離・効果正規化・可視化)
|
||||
- [Phase 8.x: MIRパイプライン簡略化計画](phases/phase-8/phase_8_x_mir_pipeline_simplification.md)
|
||||
|
||||
## 📚 関連ドキュメント
|
||||
|
||||
|
||||
@ -0,0 +1,42 @@
|
||||
# Phase 8.x: MIR/Builder/Optimizer 簡略化計画(責務分離・効果正規化・可視化)
|
||||
|
||||
## 🎯 目的
|
||||
- AST→MIR→Optimizer→Backend の多層化で増した複雑さを段階的に解消し、堅牢性と見通しを改善する。
|
||||
- DCEの誤削除や多重防御(重複lowering)を撲滅し、責務を明確化する。
|
||||
|
||||
## ✅ 方針(要約)
|
||||
- **責務分離**: Builder=変換のみ / Optimizer=最適化のみ(変換しない)。
|
||||
- **効果正規化**: `Pure / ReadOnly / SideEffect` の3分類で最適化判定を一元化。
|
||||
- **可視化**: 段階別のMIRダンプと効果サマリ、スナップショットテストを整備。
|
||||
|
||||
## Phase 1: エフェクト見直し(短期)✅ 完了
|
||||
- `EffectMask::primary_category()` を唯一の根拠に `is_pure()` を再定義(実装済)。
|
||||
- DCE/並べ替えは `primary_category` で判定(Pureのみ削除/自由移動、ReadOnlyは限定、SideEffectは不可)。
|
||||
- 単体テスト: 効果判定(PURE|IO は pure ではない)、DCEが副作用命令(print使用のTypeOp等)を削除しないこと(追加済)。
|
||||
- 可視化: `--mir-verbose-effects` で per-instruction 効果カテゴリ表示(追加済)。
|
||||
- CI導線(任意): `NYASH_OPT_DIAG_FAIL=1` で未lowering検出時にfail(診断ゲート・追加済)。
|
||||
|
||||
## Phase 2: 変換の一本化(中期)✅ 完了
|
||||
- **TypeOp loweringをBuilderに集約**(関数/メソッド/print直下/多重StringBox対応は実装済)。
|
||||
- OptimizerのTypeOp安全ネット(実変換)を削除。診断のみ存続(`NYASH_OPT_DIAG_FAIL`連携)。
|
||||
- スナップショット: 代表 `*_is_as_*` のMIR(Builder出力)を固定化(`tools/compare_mir.sh`)。
|
||||
|
||||
## Phase 3: デバッグ支援強化(短期〜中期)
|
||||
- `--dump-ast`(実装済)に加え、`--dump-mir --no-optimize`(Builder出力の生MIR)を追加。
|
||||
- MIRプリンタの詳細表示: 命令行末に `pure/readonly/side` の効果表示(オプション)。
|
||||
- ゴールデンMIR: 代表サンプルのMIRダンプを保存し差分検出(CI/ローカル)。
|
||||
|
||||
## タスク一覧(実装順)
|
||||
1) OptimizerのTypeOp安全ネットを機能フラグでデフォルトOFF(`mir_typeop_safety_net`)
|
||||
2) `MirCompiler` に `--no-optimize` 経由のBuilder直ダンプを実装
|
||||
3) MIRプリンタに効果簡易表示オプション(`--mir-verbose-effects` 等)
|
||||
4) 効果判定の単体テスト / DCE安全テストの追加
|
||||
5) Optimizer診断パス(未lowering検知)追加
|
||||
|
||||
## 期待効果
|
||||
- 変換責務の一本化でバグ源の排除・デバッグ容易化
|
||||
- エフェクト判定の一貫性でDCE/最適化の安全性向上
|
||||
- 可視化/スナップショットにより回帰を早期検知
|
||||
|
||||
---
|
||||
最終更新: 2025-08-24(Phase 1完了: is_pure修正/テスト/効果可視化/診断ゲート)
|
||||
80
docs/status/golden/typeop_in_if_loop_poc.mir.txt
Normal file
80
docs/status/golden/typeop_in_if_loop_poc.mir.txt
Normal file
@ -0,0 +1,80 @@
|
||||
🔌 v2 plugin system initialized from nyash.toml
|
||||
✅ v2 plugin system fully configured
|
||||
🚀 Nyash MIR Compiler - Processing file: local_tests/typeop_in_if_loop_poc.nyash 🚀
|
||||
🚀 MIR Output for local_tests/typeop_in_if_loop_poc.nyash:
|
||||
; MIR Module: main
|
||||
|
||||
; Module Statistics:
|
||||
; Functions: 1
|
||||
; Globals: 0
|
||||
; Total Blocks: 7
|
||||
; Total Instructions: 38
|
||||
; Pure Functions: 1
|
||||
|
||||
define void @main() {
|
||||
; Function Statistics:
|
||||
; Blocks: 7
|
||||
; Instructions: 38
|
||||
; Values: 0
|
||||
; Phi Functions: 3
|
||||
; Pure: yes
|
||||
; TypeOp: 2 (check: 2, cast: 0)
|
||||
|
||||
bb0:
|
||||
0: safepoint
|
||||
1: %1 = const 1
|
||||
2: %2 = new IntegerBox(%1)
|
||||
3: call %2.birth(%1)
|
||||
4: %3 = typeop check %2 Integer
|
||||
5: br %3, label bb1, label bb2
|
||||
; effects: pure|read|alloc
|
||||
|
||||
bb1:
|
||||
0: %4 = const "ok-if"
|
||||
1: %5 = new StringBox(%4)
|
||||
2: call %5.birth(%4)
|
||||
3: print %5
|
||||
4: br label bb3
|
||||
; effects: pure|io|read|alloc
|
||||
|
||||
bb2:
|
||||
0: %6 = const "ng-if"
|
||||
1: %7 = new StringBox(%6)
|
||||
2: call %7.birth(%6)
|
||||
3: print %7
|
||||
4: br label bb3
|
||||
; effects: pure|io|read|alloc
|
||||
|
||||
bb3:
|
||||
0: %8 = phi [%5, bb1], [%7, bb2]
|
||||
1: %10 = const 0
|
||||
2: %11 = new IntegerBox(%10)
|
||||
3: call %11.birth(%10)
|
||||
4: br label bb4
|
||||
; effects: pure|read|alloc
|
||||
|
||||
bb4: ; preds(bb5)
|
||||
0: %13 = phi [%2, bb3], [%13, bb5]
|
||||
1: %12 = phi [%11, bb3], [%20, bb5]
|
||||
2: %14 = const 1
|
||||
3: %15 = new IntegerBox(%14)
|
||||
4: call %15.birth(%14)
|
||||
5: %16 = icmp Lt %12, %15
|
||||
6: br %16, label bb5, label bb6
|
||||
; effects: pure|read|alloc
|
||||
|
||||
bb5:
|
||||
0: safepoint
|
||||
1: %17 = typeop check %13 Integer
|
||||
2: print %17
|
||||
3: %18 = const 1
|
||||
4: %19 = new IntegerBox(%18)
|
||||
5: call %19.birth(%18)
|
||||
6: %20 = %12 Add %19
|
||||
7: br label bb4
|
||||
; effects: pure|io|read|alloc
|
||||
|
||||
bb6:
|
||||
0: %21 = const void
|
||||
1: ret %21
|
||||
}
|
||||
34
docs/status/golden/typeop_is_as_func_poc.mir.txt
Normal file
34
docs/status/golden/typeop_is_as_func_poc.mir.txt
Normal file
@ -0,0 +1,34 @@
|
||||
🔌 v2 plugin system initialized from nyash.toml
|
||||
✅ v2 plugin system fully configured
|
||||
🚀 Nyash MIR Compiler - Processing file: local_tests/typeop_is_as_func_poc.nyash 🚀
|
||||
🚀 MIR Output for local_tests/typeop_is_as_func_poc.nyash:
|
||||
; MIR Module: main
|
||||
|
||||
; Module Statistics:
|
||||
; Functions: 1
|
||||
; Globals: 0
|
||||
; Total Blocks: 1
|
||||
; Total Instructions: 9
|
||||
; Pure Functions: 1
|
||||
|
||||
define void @main() {
|
||||
; Function Statistics:
|
||||
; Blocks: 1
|
||||
; Instructions: 9
|
||||
; Values: 0
|
||||
; Phi Functions: 0
|
||||
; Pure: yes
|
||||
; TypeOp: 2 (check: 1, cast: 1)
|
||||
|
||||
bb0:
|
||||
0: safepoint
|
||||
1: %1 = const 42
|
||||
2: %2 = new IntegerBox(%1)
|
||||
3: call %2.birth(%1)
|
||||
4: %3 = typeop check %2 Integer
|
||||
5: print %3
|
||||
6: %4 = typeop cast %2 Integer
|
||||
7: print %4
|
||||
8: ret %4
|
||||
; effects: pure|io|read|alloc
|
||||
}
|
||||
38
docs/status/golden/typeop_is_as_poc.mir.txt
Normal file
38
docs/status/golden/typeop_is_as_poc.mir.txt
Normal file
@ -0,0 +1,38 @@
|
||||
🔌 v2 plugin system initialized from nyash.toml
|
||||
✅ v2 plugin system fully configured
|
||||
🚀 Nyash MIR Compiler - Processing file: local_tests/typeop_is_as_poc.nyash 🚀
|
||||
🚀 MIR Output for local_tests/typeop_is_as_poc.nyash:
|
||||
; MIR Module: main
|
||||
|
||||
; Module Statistics:
|
||||
; Functions: 1
|
||||
; Globals: 0
|
||||
; Total Blocks: 1
|
||||
; Total Instructions: 13
|
||||
; Pure Functions: 1
|
||||
|
||||
define void @main() {
|
||||
; Function Statistics:
|
||||
; Blocks: 1
|
||||
; Instructions: 13
|
||||
; Values: 0
|
||||
; Phi Functions: 0
|
||||
; Pure: yes
|
||||
; TypeOp: 4 (check: 2, cast: 2)
|
||||
|
||||
bb0:
|
||||
0: safepoint
|
||||
1: %1 = const 7
|
||||
2: %2 = new IntegerBox(%1)
|
||||
3: call %2.birth(%1)
|
||||
4: %3 = typeop check %2 Integer
|
||||
5: print %3
|
||||
6: %4 = typeop cast %2 Integer
|
||||
7: print %4
|
||||
8: %5 = typeop check %2 Integer
|
||||
9: print %5
|
||||
10: %6 = typeop cast %2 Integer
|
||||
11: print %6
|
||||
12: ret %6
|
||||
; effects: pure|io|read|alloc
|
||||
}
|
||||
24
src/cli.rs
24
src/cli.rs
@ -12,9 +12,12 @@ use clap::{Arg, Command, ArgMatches};
|
||||
pub struct CliConfig {
|
||||
pub file: Option<String>,
|
||||
pub debug_fuel: Option<usize>,
|
||||
pub dump_ast: bool,
|
||||
pub dump_mir: bool,
|
||||
pub verify_mir: bool,
|
||||
pub mir_verbose: bool,
|
||||
pub mir_verbose_effects: bool,
|
||||
pub no_optimize: bool,
|
||||
pub backend: String,
|
||||
pub compile_wasm: bool,
|
||||
pub compile_native: bool,
|
||||
@ -51,6 +54,12 @@ impl CliConfig {
|
||||
.help("Set parser debug fuel limit (default: 100000, 'unlimited' for no limit)")
|
||||
.default_value("100000")
|
||||
)
|
||||
.arg(
|
||||
Arg::new("dump-ast")
|
||||
.long("dump-ast")
|
||||
.help("Dump parsed AST and exit")
|
||||
.action(clap::ArgAction::SetTrue)
|
||||
)
|
||||
.arg(
|
||||
Arg::new("dump-mir")
|
||||
.long("dump-mir")
|
||||
@ -69,6 +78,18 @@ impl CliConfig {
|
||||
.help("Show verbose MIR output with statistics")
|
||||
.action(clap::ArgAction::SetTrue)
|
||||
)
|
||||
.arg(
|
||||
Arg::new("mir-verbose-effects")
|
||||
.long("mir-verbose-effects")
|
||||
.help("Show per-instruction effect category (pure/readonly/side)")
|
||||
.action(clap::ArgAction::SetTrue)
|
||||
)
|
||||
.arg(
|
||||
Arg::new("no-optimize")
|
||||
.long("no-optimize")
|
||||
.help("Disable MIR optimizer passes (dump raw Builder MIR)")
|
||||
.action(clap::ArgAction::SetTrue)
|
||||
)
|
||||
.arg(
|
||||
Arg::new("backend")
|
||||
.long("backend")
|
||||
@ -133,9 +154,12 @@ impl CliConfig {
|
||||
Self {
|
||||
file: matches.get_one::<String>("file").cloned(),
|
||||
debug_fuel: parse_debug_fuel(matches.get_one::<String>("debug-fuel").unwrap()),
|
||||
dump_ast: matches.get_flag("dump-ast"),
|
||||
dump_mir: matches.get_flag("dump-mir"),
|
||||
verify_mir: matches.get_flag("verify"),
|
||||
mir_verbose: matches.get_flag("mir-verbose"),
|
||||
mir_verbose_effects: matches.get_flag("mir-verbose-effects"),
|
||||
no_optimize: matches.get_flag("no-optimize"),
|
||||
backend: matches.get_one::<String>("backend").unwrap().clone(),
|
||||
compile_wasm: matches.get_flag("compile-wasm"),
|
||||
compile_native: matches.get_flag("compile-native") || matches.get_flag("aot"),
|
||||
|
||||
@ -13,6 +13,16 @@ use crate::ast::{ASTNode, LiteralValue, BinaryOperator};
|
||||
use std::collections::HashMap;
|
||||
use std::collections::HashSet;
|
||||
|
||||
fn builder_debug_enabled() -> bool {
|
||||
std::env::var("NYASH_BUILDER_DEBUG").is_ok()
|
||||
}
|
||||
|
||||
fn builder_debug_log(msg: &str) {
|
||||
if builder_debug_enabled() {
|
||||
eprintln!("[BUILDER] {}", msg);
|
||||
}
|
||||
}
|
||||
|
||||
/// MIR builder for converting AST to SSA form
|
||||
pub struct MirBuilder {
|
||||
/// Current module being built
|
||||
@ -606,25 +616,46 @@ impl MirBuilder {
|
||||
|
||||
/// Build print statement - converts to console output
|
||||
fn build_print_statement(&mut self, expression: ASTNode) -> Result<ValueId, String> {
|
||||
// Early lowering for print(isType(...)) / print(asType(...)) to avoid undefined SSA when optimizations run
|
||||
if let ASTNode::FunctionCall { name, arguments, .. } = &expression {
|
||||
if (name == "isType" || name == "asType") && arguments.len() == 2 {
|
||||
builder_debug_log("enter build_print_statement");
|
||||
// 根治: print(isType(...)) / print(asType(...)) / print(obj.is(...)) / print(obj.as(...)) は必ずTypeOpを先に生成してからprintする
|
||||
match &expression {
|
||||
ASTNode::FunctionCall { name, arguments, .. } if (name == "isType" || name == "asType") && arguments.len() == 2 => {
|
||||
builder_debug_log("pattern: print(FunctionCall isType|asType)");
|
||||
if let Some(type_name) = Self::extract_string_literal(&arguments[1]) {
|
||||
builder_debug_log(&format!("extract_string_literal OK: {}", type_name));
|
||||
let val = self.build_expression(arguments[0].clone())?;
|
||||
let ty = Self::parse_type_name_to_mir(&type_name);
|
||||
let dst = self.value_gen.next();
|
||||
let op = if name == "isType" { super::TypeOpKind::Check } else { super::TypeOpKind::Cast };
|
||||
builder_debug_log(&format!("emit TypeOp {:?} value={} dst= {}", op, val, dst));
|
||||
self.emit_instruction(MirInstruction::TypeOp { dst, op, value: val, ty })?;
|
||||
self.emit_instruction(MirInstruction::Print {
|
||||
value: dst,
|
||||
effects: EffectMask::PURE.add(Effect::Io),
|
||||
})?;
|
||||
self.emit_instruction(MirInstruction::Print { value: dst, effects: EffectMask::PURE.add(Effect::Io) })?;
|
||||
return Ok(dst);
|
||||
} else {
|
||||
builder_debug_log("extract_string_literal FAIL");
|
||||
}
|
||||
}
|
||||
ASTNode::MethodCall { object, method, arguments, .. } if (method == "is" || method == "as") && arguments.len() == 1 => {
|
||||
builder_debug_log("pattern: print(MethodCall is|as)");
|
||||
if let Some(type_name) = Self::extract_string_literal(&arguments[0]) {
|
||||
builder_debug_log(&format!("extract_string_literal OK: {}", type_name));
|
||||
let obj_val = self.build_expression(*object.clone())?;
|
||||
let ty = Self::parse_type_name_to_mir(&type_name);
|
||||
let dst = self.value_gen.next();
|
||||
let op = if method == "is" { super::TypeOpKind::Check } else { super::TypeOpKind::Cast };
|
||||
builder_debug_log(&format!("emit TypeOp {:?} obj={} dst= {}", op, obj_val, dst));
|
||||
self.emit_instruction(MirInstruction::TypeOp { dst, op, value: obj_val, ty })?;
|
||||
self.emit_instruction(MirInstruction::Print { value: dst, effects: EffectMask::PURE.add(Effect::Io) })?;
|
||||
return Ok(dst);
|
||||
} else {
|
||||
builder_debug_log("extract_string_literal FAIL");
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
let value = self.build_expression(expression)?;
|
||||
builder_debug_log(&format!("fallback print value={}", value));
|
||||
|
||||
// For now, use a special Print instruction (minimal scope)
|
||||
self.emit_instruction(MirInstruction::Print {
|
||||
@ -746,6 +777,19 @@ impl MirBuilder {
|
||||
|
||||
if let Some(ref mut function) = self.current_function {
|
||||
if let Some(block) = function.get_block_mut(block_id) {
|
||||
if builder_debug_enabled() {
|
||||
eprintln!("[BUILDER] emit @bb{} -> {}", block_id, match &instruction {
|
||||
MirInstruction::TypeOp { dst, op, value, ty } => format!("typeop {:?} {} {:?} -> {}", op, value, ty, dst),
|
||||
MirInstruction::Print { value, .. } => format!("print {}", value),
|
||||
MirInstruction::BoxCall { box_val, method, args, dst, .. } => format!("boxcall {}.{}({:?}) -> {:?}", box_val, method, args, dst),
|
||||
MirInstruction::Call { func, args, dst, .. } => format!("call {}({:?}) -> {:?}", func, args, dst),
|
||||
MirInstruction::NewBox { dst, box_type, args } => format!("new {}({:?}) -> {}", box_type, args, dst),
|
||||
MirInstruction::Const { dst, value } => format!("const {:?} -> {}", value, dst),
|
||||
MirInstruction::Branch { condition, then_bb, else_bb } => format!("br {}, {}, {}", condition, then_bb, else_bb),
|
||||
MirInstruction::Jump { target } => format!("br {}", target),
|
||||
_ => format!("{:?}", instruction),
|
||||
});
|
||||
}
|
||||
block.add_instruction(instruction);
|
||||
Ok(())
|
||||
} else {
|
||||
@ -1178,11 +1222,11 @@ impl MirBuilder {
|
||||
fn build_method_call(&mut self, object: ASTNode, method: String, arguments: Vec<ASTNode>) -> Result<ValueId, String> {
|
||||
// Minimal TypeOp wiring via method-style syntax: value.is("Type") / value.as("Type")
|
||||
if (method == "is" || method == "as") && arguments.len() == 1 {
|
||||
if let ASTNode::Literal { value: crate::ast::LiteralValue::String(type_name), .. } = &arguments[0] {
|
||||
if let Some(type_name) = Self::extract_string_literal(&arguments[0]) {
|
||||
// Build the object expression
|
||||
let object_value = self.build_expression(object.clone())?;
|
||||
// Map string to MIR type
|
||||
let mir_ty = Self::parse_type_name_to_mir(type_name);
|
||||
let mir_ty = Self::parse_type_name_to_mir(&type_name);
|
||||
let dst = self.value_gen.next();
|
||||
let op = if method == "is" { super::TypeOpKind::Check } else { super::TypeOpKind::Cast };
|
||||
self.emit_instruction(MirInstruction::TypeOp { dst, op, value: object_value, ty: mir_ty })?;
|
||||
@ -1230,8 +1274,8 @@ impl MirBuilder {
|
||||
|
||||
// Secondary interception for is/as in case early path did not trigger
|
||||
if (method == "is" || method == "as") && arguments.len() == 1 {
|
||||
if let ASTNode::Literal { value: crate::ast::LiteralValue::String(type_name), .. } = &arguments[0] {
|
||||
let mir_ty = Self::parse_type_name_to_mir(type_name);
|
||||
if let Some(type_name) = Self::extract_string_literal(&arguments[0]) {
|
||||
let mir_ty = Self::parse_type_name_to_mir(&type_name);
|
||||
let dst = self.value_gen.next();
|
||||
let op = if method == "is" { super::TypeOpKind::Check } else { super::TypeOpKind::Cast };
|
||||
self.emit_instruction(MirInstruction::TypeOp { dst, op, value: object_value, ty: mir_ty })?;
|
||||
@ -1318,17 +1362,16 @@ impl MirBuilder {
|
||||
/// Extract string literal from AST node if possible
|
||||
/// Supports: Literal("Type") and new StringBox("Type")
|
||||
fn extract_string_literal(node: &ASTNode) -> Option<String> {
|
||||
match node {
|
||||
ASTNode::Literal { value: crate::ast::LiteralValue::String(s), .. } => Some(s.clone()),
|
||||
ASTNode::New { class, arguments, .. } if class == "StringBox" => {
|
||||
if arguments.len() == 1 {
|
||||
if let ASTNode::Literal { value: crate::ast::LiteralValue::String(s), .. } = &arguments[0] {
|
||||
return Some(s.clone());
|
||||
}
|
||||
let mut cur = node;
|
||||
loop {
|
||||
match cur {
|
||||
ASTNode::Literal { value: crate::ast::LiteralValue::String(s), .. } => return Some(s.clone()),
|
||||
ASTNode::New { class, arguments, .. } if class == "StringBox" && arguments.len() == 1 => {
|
||||
cur = &arguments[0];
|
||||
continue;
|
||||
}
|
||||
None
|
||||
_ => return None,
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -129,7 +129,8 @@ impl EffectMask {
|
||||
|
||||
/// Check if the computation is pure (no side effects)
|
||||
pub fn is_pure(self) -> bool {
|
||||
self.contains(Effect::Pure) || self.0 == 0
|
||||
// 純粋性は一次カテゴリで判定(Pureビットが立っていてもIO/WRITE/CONTROL等があれば純粋ではない)
|
||||
self.primary_category() == Effect::Pure
|
||||
}
|
||||
|
||||
/// Check if the computation is mutable (modifies state)
|
||||
@ -346,5 +347,9 @@ mod tests {
|
||||
let display = format!("{}", read_io);
|
||||
assert!(display.contains("read"));
|
||||
assert!(display.contains("io"));
|
||||
|
||||
// is_pure should be false when IO is present, even if PURE bit was set initially
|
||||
let mixed = EffectMask::PURE | EffectMask::IO;
|
||||
assert!(!mixed.is_pure());
|
||||
}
|
||||
}
|
||||
|
||||
@ -42,6 +42,7 @@ pub struct MirCompileResult {
|
||||
pub struct MirCompiler {
|
||||
builder: MirBuilder,
|
||||
verifier: MirVerifier,
|
||||
optimize: bool,
|
||||
}
|
||||
|
||||
impl MirCompiler {
|
||||
@ -50,6 +51,15 @@ impl MirCompiler {
|
||||
Self {
|
||||
builder: MirBuilder::new(),
|
||||
verifier: MirVerifier::new(),
|
||||
optimize: true,
|
||||
}
|
||||
}
|
||||
/// Create with options
|
||||
pub fn with_options(optimize: bool) -> Self {
|
||||
Self {
|
||||
builder: MirBuilder::new(),
|
||||
verifier: MirVerifier::new(),
|
||||
optimize,
|
||||
}
|
||||
}
|
||||
|
||||
@ -58,9 +68,13 @@ impl MirCompiler {
|
||||
// Convert AST to MIR using builder
|
||||
let mut module = self.builder.build_module(ast)?;
|
||||
|
||||
// Optimize (safety net lowering for is/as → TypeOp 等)
|
||||
let mut optimizer = MirOptimizer::new();
|
||||
let _ = optimizer.optimize_module(&mut module);
|
||||
if self.optimize {
|
||||
let mut optimizer = MirOptimizer::new();
|
||||
let stats = optimizer.optimize_module(&mut module);
|
||||
if std::env::var("NYASH_OPT_DIAG_FAIL").is_ok() && stats.diagnostics_reported > 0 {
|
||||
return Err(format!("Diagnostic failure: {} unlowered type-op calls detected", stats.diagnostics_reported));
|
||||
}
|
||||
}
|
||||
|
||||
// Verify the generated MIR
|
||||
let verification_result = self.verifier.verify_module(&module);
|
||||
|
||||
@ -31,6 +31,7 @@ impl MirOptimizer {
|
||||
self
|
||||
}
|
||||
|
||||
|
||||
/// Run all optimization passes on a MIR module
|
||||
pub fn optimize_module(&mut self, module: &mut MirModule) -> OptimizationStats {
|
||||
let mut stats = OptimizationStats::new();
|
||||
@ -51,8 +52,7 @@ impl MirOptimizer {
|
||||
// Pass 4: Intrinsic function optimization
|
||||
stats.merge(self.optimize_intrinsic_calls(module));
|
||||
|
||||
// Pass 4.5: Lower well-known intrinsics (is/as → TypeOp) as a safety net
|
||||
stats.merge(self.lower_type_ops(module));
|
||||
// Safety-net passesは削除(Phase 2: 変換の一本化)。診断のみ後段で実施。
|
||||
|
||||
// Pass 5: BoxField dependency optimization
|
||||
stats.merge(self.optimize_boxfield_operations(module));
|
||||
@ -60,6 +60,9 @@ impl MirOptimizer {
|
||||
if self.debug {
|
||||
println!("✅ Optimization complete: {}", stats);
|
||||
}
|
||||
// Diagnostics (informational): report unlowered patterns
|
||||
let diag = self.diagnose_unlowered_type_ops(module);
|
||||
stats.merge(diag);
|
||||
|
||||
stats
|
||||
}
|
||||
@ -128,11 +131,12 @@ impl MirOptimizer {
|
||||
|
||||
// Remove unused pure instructions
|
||||
let mut eliminated = 0;
|
||||
for (_, block) in &mut function.blocks {
|
||||
for (bbid, block) in &mut function.blocks {
|
||||
block.instructions.retain(|instruction| {
|
||||
if instruction.effects().is_pure() {
|
||||
if let Some(dst) = instruction.dst_value() {
|
||||
if !used_values.contains(&dst) {
|
||||
opt_debug(&format!("DCE drop @{}: {:?}", bbid.as_u32(), instruction));
|
||||
eliminated += 1;
|
||||
return false;
|
||||
}
|
||||
@ -258,101 +262,6 @@ impl MirOptimizer {
|
||||
0
|
||||
}
|
||||
|
||||
/// Safety-net lowering: convert known is/as patterns into TypeOp when possible
|
||||
fn lower_type_ops(&mut self, module: &mut MirModule) -> OptimizationStats {
|
||||
let mut stats = OptimizationStats::new();
|
||||
for (_name, function) in &mut module.functions {
|
||||
stats.intrinsic_optimizations += self.lower_type_ops_in_function(function);
|
||||
}
|
||||
stats
|
||||
}
|
||||
|
||||
fn lower_type_ops_in_function(&mut self, function: &mut MirFunction) -> usize {
|
||||
// Build a simple definition map: ValueId -> (block_id, index)
|
||||
let mut def_map: std::collections::HashMap<ValueId, (super::basic_block::BasicBlockId, usize)> = std::collections::HashMap::new();
|
||||
for (bb_id, block) in &function.blocks {
|
||||
for (i, inst) in block.instructions.iter().enumerate() {
|
||||
if let Some(dst) = inst.dst_value() {
|
||||
def_map.insert(dst, (*bb_id, i));
|
||||
}
|
||||
}
|
||||
if let Some(term) = &block.terminator {
|
||||
if let Some(dst) = term.dst_value() {
|
||||
def_map.insert(dst, (*bb_id, usize::MAX));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut lowered = 0;
|
||||
|
||||
// Collect replacements first to avoid borrow checker issues
|
||||
let mut replacements: Vec<(super::basic_block::BasicBlockId, usize, MirInstruction)> = Vec::new();
|
||||
|
||||
// First pass: identify replacements
|
||||
for (bb_id, block) in &function.blocks {
|
||||
for i in 0..block.instructions.len() {
|
||||
let replace = match &block.instructions[i] {
|
||||
MirInstruction::BoxCall { dst, box_val, method, args, .. } => {
|
||||
let is_is = method == "is" || method == "isType";
|
||||
let is_as = method == "as" || method == "asType";
|
||||
if (is_is || is_as) && args.len() == 1 {
|
||||
// Try to resolve type name from arg
|
||||
let arg_id = args[0];
|
||||
if let Some((def_bb, def_idx)) = def_map.get(&arg_id).copied() {
|
||||
// Look for pattern: NewBox StringBox(Const String)
|
||||
if let Some(def_block) = function.blocks.get(&def_bb) {
|
||||
if def_idx < def_block.instructions.len() {
|
||||
if let MirInstruction::NewBox { box_type, args: sb_args, .. } = &def_block.instructions[def_idx] {
|
||||
if box_type == "StringBox" && sb_args.len() == 1 {
|
||||
let str_id = sb_args[0];
|
||||
if let Some((sbb, sidx)) = def_map.get(&str_id).copied() {
|
||||
if let Some(sblock) = function.blocks.get(&sbb) {
|
||||
if sidx < sblock.instructions.len() {
|
||||
if let MirInstruction::Const { value, .. } = &sblock.instructions[sidx] {
|
||||
if let super::instruction::ConstValue::String(s) = value {
|
||||
// Build TypeOp to replace
|
||||
let ty = map_type_name(s);
|
||||
let op = if is_is { TypeOpKind::Check } else { TypeOpKind::Cast };
|
||||
let new_inst = MirInstruction::TypeOp {
|
||||
dst: dst.unwrap_or(*box_val),
|
||||
op,
|
||||
value: *box_val,
|
||||
ty,
|
||||
};
|
||||
Some(new_inst)
|
||||
} else { None }
|
||||
} else { None }
|
||||
} else { None }
|
||||
} else { None }
|
||||
} else { None }
|
||||
} else { None }
|
||||
} else { None }
|
||||
} else { None }
|
||||
} else { None }
|
||||
} else { None }
|
||||
} else { None }
|
||||
}
|
||||
_ => None,
|
||||
};
|
||||
|
||||
if let Some(new_inst) = replace {
|
||||
replacements.push((*bb_id, i, new_inst));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Second pass: apply replacements
|
||||
for (bb_id, idx, new_inst) in replacements {
|
||||
if let Some(block) = function.blocks.get_mut(&bb_id) {
|
||||
if idx < block.instructions.len() {
|
||||
block.instructions[idx] = new_inst;
|
||||
lowered += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
lowered
|
||||
}
|
||||
|
||||
/// Optimize BoxField operations
|
||||
fn optimize_boxfield_operations(&mut self, module: &mut MirModule) -> OptimizationStats {
|
||||
@ -392,6 +301,44 @@ fn map_type_name(name: &str) -> MirType {
|
||||
}
|
||||
}
|
||||
|
||||
fn opt_debug_enabled() -> bool { std::env::var("NYASH_OPT_DEBUG").is_ok() }
|
||||
fn opt_debug(msg: &str) { if opt_debug_enabled() { eprintln!("[OPT] {}", msg); } }
|
||||
|
||||
/// Resolve a MIR type from a value id that should represent a type name
|
||||
/// Supports: Const String("T") and NewBox(StringBox, Const String("T"))
|
||||
fn resolve_type_from_value(
|
||||
function: &MirFunction,
|
||||
def_map: &std::collections::HashMap<ValueId, (super::basic_block::BasicBlockId, usize)>,
|
||||
id: ValueId,
|
||||
) -> Option<MirType> {
|
||||
use super::instruction::ConstValue;
|
||||
if let Some((bb, idx)) = def_map.get(&id).copied() {
|
||||
if let Some(block) = function.blocks.get(&bb) {
|
||||
if idx < block.instructions.len() {
|
||||
match &block.instructions[idx] {
|
||||
MirInstruction::Const { value: ConstValue::String(s), .. } => {
|
||||
return Some(map_type_name(s));
|
||||
}
|
||||
MirInstruction::NewBox { box_type, args, .. } if box_type == "StringBox" && args.len() == 1 => {
|
||||
let inner = args[0];
|
||||
if let Some((sbb, sidx)) = def_map.get(&inner).copied() {
|
||||
if let Some(sblock) = function.blocks.get(&sbb) {
|
||||
if sidx < sblock.instructions.len() {
|
||||
if let MirInstruction::Const { value: ConstValue::String(s), .. } = &sblock.instructions[sidx] {
|
||||
return Some(map_type_name(s));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
impl Default for MirOptimizer {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
@ -406,6 +353,7 @@ pub struct OptimizationStats {
|
||||
pub reorderings: usize,
|
||||
pub intrinsic_optimizations: usize,
|
||||
pub boxfield_optimizations: usize,
|
||||
pub diagnostics_reported: usize,
|
||||
}
|
||||
|
||||
impl OptimizationStats {
|
||||
@ -419,6 +367,7 @@ impl OptimizationStats {
|
||||
self.reorderings += other.reorderings;
|
||||
self.intrinsic_optimizations += other.intrinsic_optimizations;
|
||||
self.boxfield_optimizations += other.boxfield_optimizations;
|
||||
self.diagnostics_reported += other.diagnostics_reported;
|
||||
}
|
||||
|
||||
pub fn total_optimizations(&self) -> usize {
|
||||
@ -441,6 +390,51 @@ impl std::fmt::Display for OptimizationStats {
|
||||
}
|
||||
}
|
||||
|
||||
impl MirOptimizer {
|
||||
/// Diagnostic: detect unlowered is/as/isType/asType after Builder
|
||||
fn diagnose_unlowered_type_ops(&mut self, module: &MirModule) -> OptimizationStats {
|
||||
let mut stats = OptimizationStats::new();
|
||||
let diag_on = self.debug || std::env::var("NYASH_OPT_DIAG").is_ok();
|
||||
for (fname, function) in &module.functions {
|
||||
// def map for resolving constants
|
||||
let mut def_map: std::collections::HashMap<ValueId, (super::basic_block::BasicBlockId, usize)> = std::collections::HashMap::new();
|
||||
for (bb_id, block) in &function.blocks {
|
||||
for (i, inst) in block.instructions.iter().enumerate() {
|
||||
if let Some(dst) = inst.dst_value() { def_map.insert(dst, (*bb_id, i)); }
|
||||
}
|
||||
if let Some(term) = &block.terminator { if let Some(dst) = term.dst_value() { def_map.insert(dst, (*bb_id, usize::MAX)); } }
|
||||
}
|
||||
let mut count = 0usize;
|
||||
for (_bb, block) in &function.blocks {
|
||||
for inst in &block.instructions {
|
||||
match inst {
|
||||
MirInstruction::BoxCall { method, .. } if method == "is" || method == "as" || method == "isType" || method == "asType" => { count += 1; }
|
||||
MirInstruction::Call { func, .. } => {
|
||||
if let Some((bb, idx)) = def_map.get(func).copied() {
|
||||
if let Some(b) = function.blocks.get(&bb) {
|
||||
if idx < b.instructions.len() {
|
||||
if let MirInstruction::Const { value: super::instruction::ConstValue::String(s), .. } = &b.instructions[idx] {
|
||||
if s == "isType" || s == "asType" { count += 1; }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
if count > 0 {
|
||||
stats.diagnostics_reported += count;
|
||||
if diag_on {
|
||||
eprintln!("[OPT][DIAG] Function '{}' has {} unlowered type-op calls", fname, count);
|
||||
}
|
||||
}
|
||||
}
|
||||
stats
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
@ -489,4 +483,36 @@ mod tests {
|
||||
assert!(key.contains("const"));
|
||||
assert!(key.contains("42"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_dce_does_not_drop_typeop_used_by_print() {
|
||||
// Build a simple function: %v=TypeOp(check); print %v; ensure TypeOp remains after optimize
|
||||
let signature = FunctionSignature {
|
||||
name: "main".to_string(),
|
||||
params: vec![],
|
||||
return_type: MirType::Void,
|
||||
effects: super::super::effect::EffectMask::PURE,
|
||||
};
|
||||
let mut func = MirFunction::new(signature, BasicBlockId::new(0));
|
||||
let bb0 = BasicBlockId::new(0);
|
||||
let mut b0 = BasicBlock::new(bb0);
|
||||
let v0 = ValueId::new(0);
|
||||
let v1 = ValueId::new(1);
|
||||
b0.add_instruction(MirInstruction::NewBox { dst: v0, box_type: "IntegerBox".to_string(), args: vec![] });
|
||||
b0.add_instruction(MirInstruction::TypeOp { dst: v1, op: TypeOpKind::Check, value: v0, ty: MirType::Integer });
|
||||
b0.add_instruction(MirInstruction::Print { value: v1, effects: super::super::effect::EffectMask::IO });
|
||||
b0.add_instruction(MirInstruction::Return { value: None });
|
||||
func.add_block(b0);
|
||||
let mut module = MirModule::new("test".to_string());
|
||||
module.add_function(func);
|
||||
|
||||
let mut opt = MirOptimizer::new();
|
||||
let _stats = opt.optimize_module(&mut module);
|
||||
|
||||
// Ensure TypeOp remains in bb0
|
||||
let f = module.get_function("main").unwrap();
|
||||
let block = f.get_block(&bb0).unwrap();
|
||||
let has_typeop = block.all_instructions().any(|i| matches!(i, MirInstruction::TypeOp { .. }));
|
||||
assert!(has_typeop, "TypeOp should not be dropped by DCE when used by print");
|
||||
}
|
||||
}
|
||||
|
||||
@ -18,6 +18,9 @@ pub struct MirPrinter {
|
||||
|
||||
/// Whether to show line numbers
|
||||
show_line_numbers: bool,
|
||||
|
||||
/// Whether to show per-instruction effect category
|
||||
show_effects_inline: bool,
|
||||
}
|
||||
|
||||
impl MirPrinter {
|
||||
@ -27,6 +30,7 @@ impl MirPrinter {
|
||||
indent_level: 0,
|
||||
verbose: false,
|
||||
show_line_numbers: true,
|
||||
show_effects_inline: false,
|
||||
}
|
||||
}
|
||||
|
||||
@ -36,6 +40,7 @@ impl MirPrinter {
|
||||
indent_level: 0,
|
||||
verbose: true,
|
||||
show_line_numbers: true,
|
||||
show_effects_inline: false,
|
||||
}
|
||||
}
|
||||
|
||||
@ -50,6 +55,12 @@ impl MirPrinter {
|
||||
self.show_line_numbers = show;
|
||||
self
|
||||
}
|
||||
|
||||
/// Show per-instruction effect category (pure/readonly/side)
|
||||
pub fn set_show_effects_inline(&mut self, show: bool) -> &mut Self {
|
||||
self.show_effects_inline = show;
|
||||
self
|
||||
}
|
||||
|
||||
/// Print a complete MIR module
|
||||
pub fn print_module(&self, module: &MirModule) -> String {
|
||||
@ -127,6 +138,70 @@ impl MirPrinter {
|
||||
if stats.is_pure {
|
||||
writeln!(output, " ; Pure: yes").unwrap();
|
||||
}
|
||||
// Verbose: highlight MIR26-unified ops presence for snapshotting (TypeOp/WeakRef/Barrier)
|
||||
let mut type_check = 0usize;
|
||||
let mut type_cast = 0usize;
|
||||
let mut weak_new = 0usize;
|
||||
let mut weak_load = 0usize;
|
||||
let mut barrier_read = 0usize;
|
||||
let mut barrier_write = 0usize;
|
||||
for block in function.blocks.values() {
|
||||
for inst in &block.instructions {
|
||||
match inst {
|
||||
MirInstruction::TypeCheck { .. } => type_check += 1,
|
||||
MirInstruction::Cast { .. } => type_cast += 1,
|
||||
MirInstruction::TypeOp { op, .. } => match op {
|
||||
super::TypeOpKind::Check => type_check += 1,
|
||||
super::TypeOpKind::Cast => type_cast += 1,
|
||||
},
|
||||
MirInstruction::WeakNew { .. } => weak_new += 1,
|
||||
MirInstruction::WeakLoad { .. } => weak_load += 1,
|
||||
MirInstruction::WeakRef { op, .. } => match op {
|
||||
super::WeakRefOp::New => weak_new += 1,
|
||||
super::WeakRefOp::Load => weak_load += 1,
|
||||
},
|
||||
MirInstruction::BarrierRead { .. } => barrier_read += 1,
|
||||
MirInstruction::BarrierWrite { .. } => barrier_write += 1,
|
||||
MirInstruction::Barrier { op, .. } => match op {
|
||||
super::BarrierOp::Read => barrier_read += 1,
|
||||
super::BarrierOp::Write => barrier_write += 1,
|
||||
},
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
if let Some(term) = &block.terminator {
|
||||
match term {
|
||||
MirInstruction::TypeCheck { .. } => type_check += 1,
|
||||
MirInstruction::Cast { .. } => type_cast += 1,
|
||||
MirInstruction::TypeOp { op, .. } => match op {
|
||||
super::TypeOpKind::Check => type_check += 1,
|
||||
super::TypeOpKind::Cast => type_cast += 1,
|
||||
},
|
||||
MirInstruction::WeakNew { .. } => weak_new += 1,
|
||||
MirInstruction::WeakLoad { .. } => weak_load += 1,
|
||||
MirInstruction::WeakRef { op, .. } => match op {
|
||||
super::WeakRefOp::New => weak_new += 1,
|
||||
super::WeakRefOp::Load => weak_load += 1,
|
||||
},
|
||||
MirInstruction::BarrierRead { .. } => barrier_read += 1,
|
||||
MirInstruction::BarrierWrite { .. } => barrier_write += 1,
|
||||
MirInstruction::Barrier { op, .. } => match op {
|
||||
super::BarrierOp::Read => barrier_read += 1,
|
||||
super::BarrierOp::Write => barrier_write += 1,
|
||||
},
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
if type_check + type_cast > 0 {
|
||||
writeln!(output, " ; TypeOp: {} (check: {}, cast: {})", type_check + type_cast, type_check, type_cast).unwrap();
|
||||
}
|
||||
if weak_new + weak_load > 0 {
|
||||
writeln!(output, " ; WeakRef: {} (new: {}, load: {})", weak_new + weak_load, weak_new, weak_load).unwrap();
|
||||
}
|
||||
if barrier_read + barrier_write > 0 {
|
||||
writeln!(output, " ; Barrier: {} (read: {}, write: {})", barrier_read + barrier_write, barrier_read, barrier_write).unwrap();
|
||||
}
|
||||
writeln!(output).unwrap();
|
||||
}
|
||||
|
||||
@ -174,7 +249,13 @@ impl MirPrinter {
|
||||
write!(output, " ").unwrap();
|
||||
}
|
||||
|
||||
writeln!(output, "{}", self.format_instruction(instruction)).unwrap();
|
||||
let mut line = self.format_instruction(instruction);
|
||||
if self.show_effects_inline {
|
||||
let eff = instruction.effects();
|
||||
let cat = if eff.is_pure() { "pure" } else if eff.is_read_only() { "readonly" } else { "side" };
|
||||
line.push_str(&format!(" ; eff: {}", cat));
|
||||
}
|
||||
writeln!(output, "{}", line).unwrap();
|
||||
line_num += 1;
|
||||
}
|
||||
|
||||
|
||||
@ -113,6 +113,25 @@ impl NyashRunner {
|
||||
|
||||
/// Execute file-based mode with backend selection
|
||||
fn execute_file_mode(&self, filename: &str) {
|
||||
if self.config.dump_ast {
|
||||
println!("🧠 Nyash AST Dump - Processing file: {}", filename);
|
||||
let code = match fs::read_to_string(filename) {
|
||||
Ok(content) => content,
|
||||
Err(e) => {
|
||||
eprintln!("❌ Error reading file {}: {}", filename, e);
|
||||
process::exit(1);
|
||||
}
|
||||
};
|
||||
let ast = match NyashParser::parse_from_string(&code) {
|
||||
Ok(ast) => ast,
|
||||
Err(e) => {
|
||||
eprintln!("❌ Parse error: {}", e);
|
||||
process::exit(1);
|
||||
}
|
||||
};
|
||||
println!("{:#?}", ast);
|
||||
return;
|
||||
}
|
||||
if self.config.dump_mir || self.config.verify_mir {
|
||||
println!("🚀 Nyash MIR Compiler - Processing file: {} 🚀", filename);
|
||||
self.execute_mir_mode(filename);
|
||||
@ -271,8 +290,8 @@ impl NyashRunner {
|
||||
}
|
||||
};
|
||||
|
||||
// Compile to MIR
|
||||
let mut mir_compiler = MirCompiler::new();
|
||||
// Compile to MIR (opt passes configurable)
|
||||
let mut mir_compiler = MirCompiler::with_options(!self.config.no_optimize);
|
||||
let compile_result = match mir_compiler.compile(ast) {
|
||||
Ok(result) => result,
|
||||
Err(e) => {
|
||||
@ -298,11 +317,8 @@ impl NyashRunner {
|
||||
|
||||
// Dump MIR if requested
|
||||
if self.config.dump_mir {
|
||||
let printer = if self.config.mir_verbose {
|
||||
MirPrinter::verbose()
|
||||
} else {
|
||||
MirPrinter::new()
|
||||
};
|
||||
let mut printer = if self.config.mir_verbose { MirPrinter::verbose() } else { MirPrinter::new() };
|
||||
if self.config.mir_verbose_effects { printer.set_show_effects_inline(true); }
|
||||
|
||||
println!("🚀 MIR Output for {}:", filename);
|
||||
println!("{}", printer.print_module(&compile_result.module));
|
||||
@ -345,8 +361,8 @@ impl NyashRunner {
|
||||
rt
|
||||
};
|
||||
|
||||
// Compile to MIR
|
||||
let mut mir_compiler = MirCompiler::new();
|
||||
// Compile to MIR (opt passes configurable)
|
||||
let mut mir_compiler = MirCompiler::with_options(!self.config.no_optimize);
|
||||
let compile_result = match mir_compiler.compile(ast) {
|
||||
Ok(result) => result,
|
||||
Err(e) => {
|
||||
|
||||
@ -843,6 +843,20 @@ mod stub {
|
||||
use once_cell::sync::Lazy;
|
||||
use std::sync::{Arc, RwLock};
|
||||
|
||||
// Stub implementation of PluginBoxV2 for WASM/non-plugin builds
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct PluginBoxV2 {
|
||||
pub box_type: String,
|
||||
pub inner: std::sync::Arc<PluginHandleInner>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct PluginHandleInner {
|
||||
pub type_id: u32,
|
||||
pub instance_id: u32,
|
||||
pub fini_method_id: Option<u32>,
|
||||
}
|
||||
|
||||
pub struct PluginLoaderV2 {
|
||||
pub config: Option<()>, // Dummy config for compatibility
|
||||
}
|
||||
|
||||
22
tools/ci_check_golden.sh
Normal file
22
tools/ci_check_golden.sh
Normal file
@ -0,0 +1,22 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
# Minimal golden MIR check for CI/local use
|
||||
|
||||
ROOT_DIR="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
cd "$ROOT_DIR"
|
||||
|
||||
PAIRS=(
|
||||
"local_tests/typeop_is_as_func_poc.nyash docs/status/golden/typeop_is_as_func_poc.mir.txt"
|
||||
"local_tests/typeop_is_as_poc.nyash docs/status/golden/typeop_is_as_poc.mir.txt"
|
||||
)
|
||||
|
||||
for pair in "${PAIRS[@]}"; do
|
||||
in_file="${pair%% *}"
|
||||
golden_file="${pair##* }"
|
||||
echo "[GOLDEN] Checking: $in_file vs $golden_file"
|
||||
./tools/compare_mir.sh "$in_file" "$golden_file"
|
||||
done
|
||||
|
||||
echo "All golden MIR snapshots match."
|
||||
|
||||
29
tools/compare_mir.sh
Normal file
29
tools/compare_mir.sh
Normal file
@ -0,0 +1,29 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
if [ "$#" -ne 2 ]; then
|
||||
echo "Usage: $0 <input.nyash> <golden.mir.txt>" >&2
|
||||
exit 2
|
||||
fi
|
||||
|
||||
INPUT="$1"
|
||||
GOLDEN="$2"
|
||||
|
||||
TMPDIR="${TMPDIR:-/tmp}"
|
||||
OUT="$TMPDIR/mir_snapshot_$$.txt"
|
||||
trap 'rm -f "$OUT"' EXIT
|
||||
|
||||
# Allow effect annotation opt-in via env var
|
||||
if [ -n "${NYASH_MIR_VERBOSE_EFFECTS:-}" ]; then
|
||||
NYASH_MIR_VERBOSE_EFFECTS=1 ./tools/snapshot_mir.sh "$INPUT" "$OUT" >/dev/null
|
||||
else
|
||||
./tools/snapshot_mir.sh "$INPUT" "$OUT" >/dev/null
|
||||
fi
|
||||
|
||||
if ! diff -u "$GOLDEN" "$OUT"; then
|
||||
echo "MIR snapshot differs from golden: $GOLDEN" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "MIR matches golden: $GOLDEN"
|
||||
|
||||
36
tools/snapshot_mir.sh
Normal file
36
tools/snapshot_mir.sh
Normal file
@ -0,0 +1,36 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
if [ "$#" -lt 1 ] || [ "$#" -gt 2 ]; then
|
||||
echo "Usage: $0 <input.nyash> [output.txt]" >&2
|
||||
echo "Dumps Builder-only MIR (--no-optimize) for reproducible snapshots." >&2
|
||||
exit 2
|
||||
fi
|
||||
|
||||
INPUT="$1"
|
||||
OUTFILE="${2:-}"
|
||||
|
||||
if [ ! -f "$INPUT" ]; then
|
||||
echo "Input not found: $INPUT" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
BIN="${NYASH_BIN:-./target/release/nyash}"
|
||||
if [ ! -x "$BIN" ]; then
|
||||
echo "nyash binary not found at $BIN. Build first: cargo build --release" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
CMD=("$BIN" --dump-mir --mir-verbose --no-optimize "$INPUT")
|
||||
|
||||
if [ -n "${NYASH_MIR_VERBOSE_EFFECTS:-}" ]; then
|
||||
CMD=("$BIN" --dump-mir --mir-verbose --mir-verbose-effects --no-optimize "$INPUT")
|
||||
fi
|
||||
|
||||
if [ -n "$OUTFILE" ]; then
|
||||
mkdir -p "$(dirname "$OUTFILE")"
|
||||
"${CMD[@]}" | sed -e :a -e '/^\n*$/{$d;N;ba' -e '}' > "$OUTFILE"
|
||||
echo "Wrote MIR snapshot: $OUTFILE"
|
||||
else
|
||||
"${CMD[@]}"
|
||||
fi
|
||||
Reference in New Issue
Block a user