feat: Add dual-mode design doc and VM/MIR improvements
- Add comprehensive dual-mode design document (script vs application mode) - Update phase 9.8 documentation with implementation progress - Enhance VM with improved error handling and debugging - Add MIR verification improvements - Add test cases for MIR/VM POC 🤖 Generated with Claude Code Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
目的(What/Why)
|
目的(What/Why)
|
||||||
- 外部ライブラリをBox(BID)として配布・発見・利用するための基盤を用意する。
|
- 外部ライブラリをBox(BID)として配布・発見・利用するための基盤を用意する。
|
||||||
- BID(Box Interface Definition)から各ターゲット(WASM/VM/LLVM/TS/Python)のスタブや宣言を自動生成し、開発者の負担を最小化する。
|
- 当面は nyash.toml にBID情報を“埋め込む”方式で回し、将来は外部BID(manifest)参照+自動生成へ段階拡張する。
|
||||||
|
|
||||||
成果物(Deliverables)
|
成果物(Deliverables)
|
||||||
- BIDレジストリ仕様(YAML/JSON スキーマ定義・バージョニング・依存関係・権限メタ)
|
- BIDレジストリ仕様(YAML/JSON スキーマ定義・バージョニング・依存関係・権限メタ)
|
||||||
@ -14,22 +14,21 @@
|
|||||||
- TypeScript/Python: ラッパ(FFI呼び出しAPIのプロキシ)
|
- TypeScript/Python: ラッパ(FFI呼び出しAPIのプロキシ)
|
||||||
- サンプルBIDからの生成例(console/canvas)
|
- サンプルBIDからの生成例(console/canvas)
|
||||||
|
|
||||||
範囲(Scope)
|
範囲(Scope:段階的)
|
||||||
1) スキーマ
|
A) すぐやる(埋め込みBID)
|
||||||
- `version`, `interfaces[]`, `methods[]`, `params`, `returns`, `effect`, `permissions`(9.9の権限連携)
|
- nyash.toml に最小BID情報(署名・効果・権限)を記述し、ランタイムローダが読み込む
|
||||||
- 例: `docs/nyir/bid_samples/console.yaml`, `docs/nyir/bid_samples/canvas.yaml`
|
- ExternCall/Plugin呼び出し時にBIDの`effects/permissions`を参照して実行可否を判定
|
||||||
2) CLI
|
B) 次にやる(参照BID)
|
||||||
- `nyash bid gen --target <t> <bid.yaml>` → `out/<t>/<name>/...` に生成
|
- nyash.toml から外部BID(bid.yaml 等)を参照・マージ可能にする(アグリゲータ)
|
||||||
- オプション: `--out`, `--force`, `--dry-run`
|
C) 自動生成(安定後)
|
||||||
3) テンプレート
|
- CLI: `nyash bid gen --target <t> <bid.yaml>` → `out/<t>/<name>/...` に生成
|
||||||
- 各ターゲット用のMustache/Handlebars相当のテンプレート群
|
- テンプレート: WASM(importObject), VM(関数テーブル), LLVM(declare), TS/Python(RTEラッパ)
|
||||||
4) ドキュメント
|
- ドキュメント: `docs/予定/native-plan/box_ffi_abi.md` にBID→生成の流れを追記
|
||||||
- `docs/予定/native-plan/box_ffi_abi.md` にBID→生成の流れを追記
|
|
||||||
|
|
||||||
受け入れ基準(Acceptance)
|
受け入れ基準(Acceptance:段階的)
|
||||||
- console/canvas のBIDから、WASM/VM/LLVM/TS/Python の最小スタブが生成される
|
- A: nyash.toml の BID 情報だけでランタイム実行・権限判定が可能(外部BIDなしでも動作)
|
||||||
- 生成物を用いて、9.7 のE2E(console.log / canvas.fillRect)が通る
|
- B: 外部BID(manifest)を nyash.toml から参照・マージできる
|
||||||
- `--dry-run` で生成前にプレビューが確認できる
|
- C: console/canvas のBIDから、WASM/VM/LLVM/TS/Python の最小スタブが生成される(`--dry-run` 対応)
|
||||||
|
|
||||||
非スコープ(Out of Scope)
|
非スコープ(Out of Scope)
|
||||||
- 高度な最適化生成、双方向同期、型高級機能(ジェネリクス/オーバーロード)
|
- 高度な最適化生成、双方向同期、型高級機能(ジェネリクス/オーバーロード)
|
||||||
@ -42,6 +41,6 @@
|
|||||||
- 計画: `docs/予定/native-plan/copilot_issues.txt`(9.7/9.8/9.9)
|
- 計画: `docs/予定/native-plan/copilot_issues.txt`(9.7/9.8/9.9)
|
||||||
|
|
||||||
メモ(運用)
|
メモ(運用)
|
||||||
|
- 対応する形式が増えたら、まず nyash.toml にBIDを追記(“その都度対応”の方針)
|
||||||
- 将来的に「BID→RuntimeImports/ExternCall宣言」の自動接続まで拡張予定(WASM/VM/LLVM)。
|
- 将来的に「BID→RuntimeImports/ExternCall宣言」の自動接続まで拡張予定(WASM/VM/LLVM)。
|
||||||
- 権限メタ(permissions)は 9.9 のモデルに合わせて必須化を検討。
|
- 権限メタ(permissions)は 9.9 のモデルに合わせて必須化を検討。
|
||||||
|
|
||||||
|
|||||||
361
docs/ideas/dual-mode-design.md
Normal file
361
docs/ideas/dual-mode-design.md
Normal file
@ -0,0 +1,361 @@
|
|||||||
|
# Nyash 2モード設計:スクリプトモード vs アプリケーションモード
|
||||||
|
|
||||||
|
## 🎯 設計理念
|
||||||
|
|
||||||
|
Nyashは「明示性重視」と「使いやすさ」の両方を実現するため、**2つの実行モード**を永続的にサポートする。移行や非推奨化は行わず、それぞれのモードが異なる用途で輝く設計とする。
|
||||||
|
|
||||||
|
## 📊 モード比較
|
||||||
|
|
||||||
|
### スクリプトモード(Script Mode)
|
||||||
|
```nyash
|
||||||
|
// ファイル:hello.nyash
|
||||||
|
name = "Nyash"
|
||||||
|
count = 42
|
||||||
|
print("Hello, " + name + "!")
|
||||||
|
print("The answer is " + count)
|
||||||
|
|
||||||
|
// 関数も自由に定義
|
||||||
|
function greet(who) {
|
||||||
|
print("Greetings, " + who)
|
||||||
|
}
|
||||||
|
|
||||||
|
greet("World")
|
||||||
|
```
|
||||||
|
|
||||||
|
### アプリケーションモード(Application Mode)
|
||||||
|
```nyash
|
||||||
|
// ファイル:app.nyash
|
||||||
|
static box Main {
|
||||||
|
init { name, count }
|
||||||
|
|
||||||
|
main() {
|
||||||
|
me.name = "Nyash"
|
||||||
|
me.count = 42
|
||||||
|
me.greet()
|
||||||
|
}
|
||||||
|
|
||||||
|
greet() {
|
||||||
|
print("Hello, " + me.name + "!")
|
||||||
|
print("The answer is " + me.count)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🔍 自動検出ルール
|
||||||
|
|
||||||
|
### 基本原則:「見た目で分かる」
|
||||||
|
1. **`static box`が1つでもある** → アプリケーションモード
|
||||||
|
2. **`static box`が1つもない** → スクリプトモード
|
||||||
|
3. **明示的な指定は不要**(ファイル内容で自動判定)
|
||||||
|
|
||||||
|
### 検出アルゴリズム
|
||||||
|
```rust
|
||||||
|
fn detect_mode(ast: &AST) -> ExecutionMode {
|
||||||
|
// ASTを走査してstatic boxの存在をチェック
|
||||||
|
for node in ast.walk() {
|
||||||
|
match node {
|
||||||
|
ASTNode::StaticBoxDeclaration { .. } => {
|
||||||
|
return ExecutionMode::Application;
|
||||||
|
}
|
||||||
|
_ => continue,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ExecutionMode::Script
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🌟 各モードの特徴
|
||||||
|
|
||||||
|
### スクリプトモード
|
||||||
|
|
||||||
|
#### 利点
|
||||||
|
- **素早いプロトタイピング** - アイデアをすぐ試せる
|
||||||
|
- **学習曲線が緩やか** - 初心者に優しい
|
||||||
|
- **REPL完全互換** - コピペで動く
|
||||||
|
- **ワンライナー対応** - `nyash -e "print(2+2)"`
|
||||||
|
|
||||||
|
#### 変数スコープ
|
||||||
|
```nyash
|
||||||
|
// グローバルスコープに直接定義
|
||||||
|
x = 10
|
||||||
|
y = 20
|
||||||
|
|
||||||
|
// 関数内でもアクセス可能
|
||||||
|
function sum() {
|
||||||
|
return x + y // グローバル変数を参照
|
||||||
|
}
|
||||||
|
|
||||||
|
// ローカル変数
|
||||||
|
function calculate() {
|
||||||
|
local temp = 100 // 関数ローカル
|
||||||
|
return temp + x
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### エラーハンドリング
|
||||||
|
```nyash
|
||||||
|
// 未定義変数アクセス → 実行時エラー
|
||||||
|
print(undefined_var) // Runtime Error: undefined variable 'undefined_var'
|
||||||
|
|
||||||
|
// 型エラーも実行時
|
||||||
|
x = "hello"
|
||||||
|
y = x + 10 // Runtime Error: cannot add String and Integer
|
||||||
|
```
|
||||||
|
|
||||||
|
### アプリケーションモード
|
||||||
|
|
||||||
|
#### 利点
|
||||||
|
- **明示的な変数管理** - どこで何が定義されているか明確
|
||||||
|
- **静的解析可能** - IDEサポートが充実
|
||||||
|
- **大規模開発対応** - チーム開発で威力を発揮
|
||||||
|
- **名前空間の分離** - static box単位でスコープ管理
|
||||||
|
|
||||||
|
#### 変数スコープ
|
||||||
|
```nyash
|
||||||
|
static box Calculator {
|
||||||
|
init { x, y, result } // 必ず宣言
|
||||||
|
|
||||||
|
calculate() {
|
||||||
|
// me.を通じてアクセス(明示的)
|
||||||
|
me.result = me.x + me.y
|
||||||
|
|
||||||
|
// ローカル変数
|
||||||
|
local temp = me.result * 2
|
||||||
|
return temp
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ❌ エラー:グローバル変数は定義できない
|
||||||
|
global_var = 10 // Error: Global variables not allowed in application mode
|
||||||
|
```
|
||||||
|
|
||||||
|
#### コンパイル時チェック
|
||||||
|
```nyash
|
||||||
|
static box TypedCalc {
|
||||||
|
init { value }
|
||||||
|
|
||||||
|
setValue(v) {
|
||||||
|
// 将来的に型推論や型チェックも可能
|
||||||
|
me.value = v
|
||||||
|
}
|
||||||
|
|
||||||
|
calculate() {
|
||||||
|
// 未定義フィールドアクセス → コンパイルエラー
|
||||||
|
return me.undefined_field // Error: 'undefined_field' not declared in init
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🚀 REPL統合
|
||||||
|
|
||||||
|
### REPLの動作
|
||||||
|
```bash
|
||||||
|
$ nyash
|
||||||
|
Nyash REPL v1.0 (Script Mode)
|
||||||
|
> x = 42
|
||||||
|
> print(x)
|
||||||
|
42
|
||||||
|
> function double(n) { return n * 2 }
|
||||||
|
> print(double(x))
|
||||||
|
84
|
||||||
|
|
||||||
|
# アプリケーションモードのコードも貼り付け可能
|
||||||
|
> static box Counter {
|
||||||
|
. init { count }
|
||||||
|
. increment() { me.count = me.count + 1 }
|
||||||
|
. }
|
||||||
|
> c = new Counter()
|
||||||
|
> c.increment()
|
||||||
|
> print(c.count)
|
||||||
|
1
|
||||||
|
```
|
||||||
|
|
||||||
|
### モード切り替え
|
||||||
|
```bash
|
||||||
|
# 通常起動(スクリプトモード)
|
||||||
|
$ nyash
|
||||||
|
|
||||||
|
# アプリケーションモードでREPL起動(将来実装)
|
||||||
|
$ nyash --app-mode
|
||||||
|
Nyash REPL v1.0 (Application Mode)
|
||||||
|
> x = 42 # Error: Global variables not allowed
|
||||||
|
> static box Main { ... } # OK
|
||||||
|
```
|
||||||
|
|
||||||
|
## 📝 実装詳細
|
||||||
|
|
||||||
|
### パーサーの拡張
|
||||||
|
```rust
|
||||||
|
pub struct Parser {
|
||||||
|
mode: ExecutionMode,
|
||||||
|
// スクリプトモードでは緩い解析
|
||||||
|
// アプリケーションモードでは厳格な解析
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Parser {
|
||||||
|
pub fn parse(&mut self, source: &str) -> Result<AST, ParseError> {
|
||||||
|
let ast = self.parse_initial(source)?;
|
||||||
|
self.mode = detect_mode(&ast);
|
||||||
|
|
||||||
|
match self.mode {
|
||||||
|
ExecutionMode::Script => self.validate_script_mode(&ast),
|
||||||
|
ExecutionMode::Application => self.validate_app_mode(&ast),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### インタープリターの対応
|
||||||
|
```rust
|
||||||
|
impl Interpreter {
|
||||||
|
pub fn execute(&mut self, ast: &AST, mode: ExecutionMode) -> Result<Value, RuntimeError> {
|
||||||
|
match mode {
|
||||||
|
ExecutionMode::Script => {
|
||||||
|
// グローバル変数を許可
|
||||||
|
self.allow_globals = true;
|
||||||
|
self.execute_script(ast)
|
||||||
|
}
|
||||||
|
ExecutionMode::Application => {
|
||||||
|
// グローバル変数を禁止
|
||||||
|
self.allow_globals = false;
|
||||||
|
self.execute_application(ast)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🎨 ベストプラクティス
|
||||||
|
|
||||||
|
### いつスクリプトモードを使うか
|
||||||
|
1. **データ処理スクリプト**
|
||||||
|
```nyash
|
||||||
|
data = readFile("input.csv")
|
||||||
|
lines = data.split("\n")
|
||||||
|
for line in lines {
|
||||||
|
fields = line.split(",")
|
||||||
|
print(fields[0] + ": " + fields[1])
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **設定ファイル**
|
||||||
|
```nyash
|
||||||
|
# config.nyash
|
||||||
|
server_host = "localhost"
|
||||||
|
server_port = 8080
|
||||||
|
debug_mode = true
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **テストコード**
|
||||||
|
```nyash
|
||||||
|
# test_math.nyash
|
||||||
|
assert(2 + 2 == 4)
|
||||||
|
assert(10 / 2 == 5)
|
||||||
|
print("All tests passed!")
|
||||||
|
```
|
||||||
|
|
||||||
|
### いつアプリケーションモードを使うか
|
||||||
|
1. **Webアプリケーション**
|
||||||
|
```nyash
|
||||||
|
static box WebServer {
|
||||||
|
init { port, routes }
|
||||||
|
|
||||||
|
start() {
|
||||||
|
me.port = 8080
|
||||||
|
me.routes = new MapBox()
|
||||||
|
me.setupRoutes()
|
||||||
|
me.listen()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **ゲーム開発**
|
||||||
|
```nyash
|
||||||
|
static box Game {
|
||||||
|
init { player, enemies, score }
|
||||||
|
|
||||||
|
main() {
|
||||||
|
me.initialize()
|
||||||
|
loop(me.isRunning()) {
|
||||||
|
me.update()
|
||||||
|
me.render()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **ライブラリ開発**
|
||||||
|
```nyash
|
||||||
|
static box MathLib {
|
||||||
|
init { precision }
|
||||||
|
|
||||||
|
static factorial(n) {
|
||||||
|
if n <= 1 { return 1 }
|
||||||
|
return n * MathLib.factorial(n - 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🔮 将来の拡張
|
||||||
|
|
||||||
|
### 混在モード(検討中)
|
||||||
|
```nyash
|
||||||
|
#!script
|
||||||
|
// ファイルの最初の部分はスクリプトモード
|
||||||
|
config = loadConfig()
|
||||||
|
debug = true
|
||||||
|
|
||||||
|
#!application
|
||||||
|
// ここからアプリケーションモード
|
||||||
|
static box Main {
|
||||||
|
init { config }
|
||||||
|
|
||||||
|
main() {
|
||||||
|
// スクリプト部分の変数を参照できる?
|
||||||
|
me.config = ::config // グローバルスコープ参照構文
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### プロジェクト設定
|
||||||
|
```toml
|
||||||
|
# nyash.toml
|
||||||
|
[project]
|
||||||
|
name = "my-app"
|
||||||
|
default_mode = "application" # プロジェクト全体のデフォルト
|
||||||
|
|
||||||
|
[scripts]
|
||||||
|
# 特定のファイルはスクリプトモードで実行
|
||||||
|
mode = "script"
|
||||||
|
files = ["scripts/*.nyash", "tests/*.nyash"]
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🌈 他言語との比較
|
||||||
|
|
||||||
|
### Python
|
||||||
|
- **類似点**: REPLとファイル実行で同じ動作
|
||||||
|
- **相違点**: Nyashはモードを明確に分離
|
||||||
|
|
||||||
|
### JavaScript/TypeScript
|
||||||
|
- **類似点**: strictモードの概念
|
||||||
|
- **相違点**: Nyashは見た目で判定(`"use strict"`不要)
|
||||||
|
|
||||||
|
### C#
|
||||||
|
- **優位性**:
|
||||||
|
- トップレベルステートメントより柔軟
|
||||||
|
- 明示的なモード分離で混乱なし
|
||||||
|
- REPLとの完全な統合
|
||||||
|
|
||||||
|
### Ruby
|
||||||
|
- **類似点**: スクリプトとクラスベースの両対応
|
||||||
|
- **相違点**: Nyashはより明確なモード分離
|
||||||
|
|
||||||
|
## 🎯 まとめ
|
||||||
|
|
||||||
|
Nyashの2モード設計は:
|
||||||
|
1. **移行不要** - 両モード永続サポート
|
||||||
|
2. **自動判定** - ファイル内容で自動切り替え
|
||||||
|
3. **REPL対応** - 同じルールで直感的
|
||||||
|
4. **いいとこ取り** - 用途に応じて最適なモードを選択
|
||||||
|
|
||||||
|
この設計により、Nyashは「**初心者に優しく、プロに強力**」な言語となる。
|
||||||
@ -477,7 +477,67 @@ impl VM {
|
|||||||
}
|
}
|
||||||
crate::mir::TypeOpKind::Cast => {
|
crate::mir::TypeOpKind::Cast => {
|
||||||
let v = self.get_value(*value)?;
|
let v = self.get_value(*value)?;
|
||||||
self.set_value(*dst, v);
|
let casted = match ty {
|
||||||
|
crate::mir::MirType::Integer => match v {
|
||||||
|
VMValue::Integer(_) => v,
|
||||||
|
VMValue::Float(f) => VMValue::Integer(f as i64),
|
||||||
|
other => {
|
||||||
|
return Err(VMError::TypeError(format!(
|
||||||
|
"Cannot cast {:?} to Integer",
|
||||||
|
other
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
crate::mir::MirType::Float => match v {
|
||||||
|
VMValue::Float(_) => v,
|
||||||
|
VMValue::Integer(i) => VMValue::Float(i as f64),
|
||||||
|
other => {
|
||||||
|
return Err(VMError::TypeError(format!(
|
||||||
|
"Cannot cast {:?} to Float",
|
||||||
|
other
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// For other types, only allow no-op cast when already same category
|
||||||
|
crate::mir::MirType::Bool => match v {
|
||||||
|
VMValue::Bool(_) => v,
|
||||||
|
other => {
|
||||||
|
return Err(VMError::TypeError(format!(
|
||||||
|
"Cannot cast {:?} to Bool",
|
||||||
|
other
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
crate::mir::MirType::String => match v {
|
||||||
|
VMValue::String(_) => v,
|
||||||
|
other => {
|
||||||
|
return Err(VMError::TypeError(format!(
|
||||||
|
"Cannot cast {:?} to String",
|
||||||
|
other
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
crate::mir::MirType::Void => match v {
|
||||||
|
VMValue::Void => v,
|
||||||
|
other => {
|
||||||
|
return Err(VMError::TypeError(format!(
|
||||||
|
"Cannot cast {:?} to Void",
|
||||||
|
other
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
crate::mir::MirType::Box(name) => match v {
|
||||||
|
VMValue::BoxRef(ref arc) if arc.type_name() == name => v,
|
||||||
|
other => {
|
||||||
|
return Err(VMError::TypeError(format!(
|
||||||
|
"Cannot cast {:?} to Box<{}>",
|
||||||
|
other, name
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
_ => v,
|
||||||
|
};
|
||||||
|
self.set_value(*dst, casted);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(ControlFlow::Continue)
|
Ok(ControlFlow::Continue)
|
||||||
@ -640,8 +700,11 @@ impl VM {
|
|||||||
return Ok(ControlFlow::Continue);
|
return Ok(ControlFlow::Continue);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fast-path for ArrayBox methods using original BoxRef (preserve state)
|
// Fast-path for common Box methods (Array/Map/String-like)
|
||||||
|
let fastpath_disabled = std::env::var("NYASH_VM_DISABLE_FASTPATH").is_ok();
|
||||||
|
if !fastpath_disabled {
|
||||||
if let VMValue::BoxRef(ref arc_any) = box_vm_value {
|
if let VMValue::BoxRef(ref arc_any) = box_vm_value {
|
||||||
|
// ArrayBox: get/set/push
|
||||||
if let Some(arr) = arc_any.as_any().downcast_ref::<crate::boxes::array::ArrayBox>() {
|
if let Some(arr) = arc_any.as_any().downcast_ref::<crate::boxes::array::ArrayBox>() {
|
||||||
match method.as_str() {
|
match method.as_str() {
|
||||||
"get" => {
|
"get" => {
|
||||||
@ -660,9 +723,52 @@ impl VM {
|
|||||||
return Ok(ControlFlow::Continue);
|
return Ok(ControlFlow::Continue);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
"push" => {
|
||||||
|
if let Some(arg0) = arg_values.get(0) {
|
||||||
|
let res = arr.push((*arg0).clone_or_share());
|
||||||
|
if let Some(dst_id) = dst { let v = VMValue::from_nyash_box(res); self.debug_log_boxcall(&box_vm_value, method, &arg_values, "fastpath", Some(&v)); self.set_value(*dst_id, v); }
|
||||||
|
return Ok(ControlFlow::Continue);
|
||||||
|
}
|
||||||
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// MapBox: get/set/has
|
||||||
|
if let Some(map) = arc_any.as_any().downcast_ref::<crate::boxes::map_box::MapBox>() {
|
||||||
|
match method.as_str() {
|
||||||
|
"get" => {
|
||||||
|
if let Some(arg0) = arg_values.get(0) {
|
||||||
|
let res = map.get((*arg0).clone_or_share());
|
||||||
|
if let Some(dst_id) = dst { let v = VMValue::from_nyash_box(res); self.debug_log_boxcall(&box_vm_value, method, &arg_values, "fastpath", Some(&v)); self.set_value(*dst_id, v); }
|
||||||
|
return Ok(ControlFlow::Continue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"set" => {
|
||||||
|
if arg_values.len() >= 2 {
|
||||||
|
let k = (*arg_values.get(0).unwrap()).clone_or_share();
|
||||||
|
let vval = (*arg_values.get(1).unwrap()).clone_or_share();
|
||||||
|
let res = map.set(k, vval);
|
||||||
|
if let Some(dst_id) = dst { let v = VMValue::from_nyash_box(res); self.debug_log_boxcall(&box_vm_value, method, &arg_values, "fastpath", Some(&v)); self.set_value(*dst_id, v); }
|
||||||
|
return Ok(ControlFlow::Continue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"has" => {
|
||||||
|
if let Some(arg0) = arg_values.get(0) {
|
||||||
|
let res = map.has((*arg0).clone_or_share());
|
||||||
|
if let Some(dst_id) = dst { let v = VMValue::from_nyash_box(res); self.debug_log_boxcall(&box_vm_value, method, &arg_values, "fastpath", Some(&v)); self.set_value(*dst_id, v); }
|
||||||
|
return Ok(ControlFlow::Continue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// toString(): generic fast-path for any BoxRef (0-arg)
|
||||||
|
if method == "toString" && arg_values.is_empty() {
|
||||||
|
let res = box_nyash.to_string_box();
|
||||||
|
if let Some(dst_id) = dst { let v = VMValue::from_nyash_box(Box::new(res)); self.debug_log_boxcall(&box_vm_value, method, &arg_values, "fastpath", Some(&v)); self.set_value(*dst_id, v); }
|
||||||
|
return Ok(ControlFlow::Continue);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Call the method - unified dispatch for all Box types
|
// Call the method - unified dispatch for all Box types
|
||||||
|
|||||||
@ -54,6 +54,20 @@ pub enum VerificationError {
|
|||||||
merge_block: BasicBlockId,
|
merge_block: BasicBlockId,
|
||||||
pred_block: BasicBlockId,
|
pred_block: BasicBlockId,
|
||||||
},
|
},
|
||||||
|
/// WeakRef(load) must originate from a WeakNew/WeakRef(new)
|
||||||
|
InvalidWeakRefSource {
|
||||||
|
weak_ref: ValueId,
|
||||||
|
block: BasicBlockId,
|
||||||
|
instruction_index: usize,
|
||||||
|
reason: String,
|
||||||
|
},
|
||||||
|
/// Barrier pointer must not be a void constant
|
||||||
|
InvalidBarrierPointer {
|
||||||
|
ptr: ValueId,
|
||||||
|
block: BasicBlockId,
|
||||||
|
instruction_index: usize,
|
||||||
|
reason: String,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
/// MIR verifier for SSA form and semantic correctness
|
/// MIR verifier for SSA form and semantic correctness
|
||||||
@ -113,6 +127,10 @@ impl MirVerifier {
|
|||||||
if let Err(mut merge_errors) = self.verify_merge_uses(function) {
|
if let Err(mut merge_errors) = self.verify_merge_uses(function) {
|
||||||
local_errors.append(&mut merge_errors);
|
local_errors.append(&mut merge_errors);
|
||||||
}
|
}
|
||||||
|
// 5. Minimal checks for WeakRef/Barrier
|
||||||
|
if let Err(mut weak_barrier_errors) = self.verify_weakref_and_barrier(function) {
|
||||||
|
local_errors.append(&mut weak_barrier_errors);
|
||||||
|
}
|
||||||
|
|
||||||
if local_errors.is_empty() {
|
if local_errors.is_empty() {
|
||||||
Ok(())
|
Ok(())
|
||||||
@ -120,6 +138,89 @@ impl MirVerifier {
|
|||||||
Err(local_errors)
|
Err(local_errors)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Verify WeakRef/Barrier minimal semantics
|
||||||
|
fn verify_weakref_and_barrier(&self, function: &MirFunction) -> Result<(), Vec<VerificationError>> {
|
||||||
|
use super::MirInstruction;
|
||||||
|
let mut errors = Vec::new();
|
||||||
|
// Build def map value -> (block, idx, &inst)
|
||||||
|
let mut def_map: HashMap<ValueId, (BasicBlockId, usize, &MirInstruction)> = HashMap::new();
|
||||||
|
for (bid, block) in &function.blocks {
|
||||||
|
for (idx, inst) in block.all_instructions().enumerate() {
|
||||||
|
if let Some(dst) = inst.dst_value() {
|
||||||
|
def_map.insert(dst, (*bid, idx, inst));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (bid, block) in &function.blocks {
|
||||||
|
for (idx, inst) in block.all_instructions().enumerate() {
|
||||||
|
match inst {
|
||||||
|
MirInstruction::WeakRef { op: super::WeakRefOp::Load, value, .. } => {
|
||||||
|
match def_map.get(value) {
|
||||||
|
Some((_db, _di, def_inst)) => match def_inst {
|
||||||
|
MirInstruction::WeakRef { op: super::WeakRefOp::New, .. } | MirInstruction::WeakNew { .. } => {}
|
||||||
|
_ => {
|
||||||
|
errors.push(VerificationError::InvalidWeakRefSource {
|
||||||
|
weak_ref: *value,
|
||||||
|
block: *bid,
|
||||||
|
instruction_index: idx,
|
||||||
|
reason: "weakref.load source is not a weakref.new/weak_new".to_string(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
None => {
|
||||||
|
errors.push(VerificationError::InvalidWeakRefSource {
|
||||||
|
weak_ref: *value,
|
||||||
|
block: *bid,
|
||||||
|
instruction_index: idx,
|
||||||
|
reason: "weakref.load source is undefined".to_string(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
MirInstruction::WeakLoad { weak_ref, .. } => {
|
||||||
|
match def_map.get(weak_ref) {
|
||||||
|
Some((_db, _di, def_inst)) => match def_inst {
|
||||||
|
MirInstruction::WeakNew { .. } | MirInstruction::WeakRef { op: super::WeakRefOp::New, .. } => {}
|
||||||
|
_ => {
|
||||||
|
errors.push(VerificationError::InvalidWeakRefSource {
|
||||||
|
weak_ref: *weak_ref,
|
||||||
|
block: *bid,
|
||||||
|
instruction_index: idx,
|
||||||
|
reason: "weak_load source is not a weak_new/weakref.new".to_string(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
None => {
|
||||||
|
errors.push(VerificationError::InvalidWeakRefSource {
|
||||||
|
weak_ref: *weak_ref,
|
||||||
|
block: *bid,
|
||||||
|
instruction_index: idx,
|
||||||
|
reason: "weak_load source is undefined".to_string(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
MirInstruction::Barrier { ptr, .. } | MirInstruction::BarrierRead { ptr } | MirInstruction::BarrierWrite { ptr } => {
|
||||||
|
if let Some((_db, _di, def_inst)) = def_map.get(ptr) {
|
||||||
|
if let MirInstruction::Const { value: super::ConstValue::Void, .. } = def_inst {
|
||||||
|
errors.push(VerificationError::InvalidBarrierPointer {
|
||||||
|
ptr: *ptr,
|
||||||
|
block: *bid,
|
||||||
|
instruction_index: idx,
|
||||||
|
reason: "barrier pointer is void".to_string(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if errors.is_empty() { Ok(()) } else { Err(errors) }
|
||||||
|
}
|
||||||
|
|
||||||
/// Verify SSA form properties
|
/// Verify SSA form properties
|
||||||
fn verify_ssa_form(&self, function: &MirFunction) -> Result<(), Vec<VerificationError>> {
|
fn verify_ssa_form(&self, function: &MirFunction) -> Result<(), Vec<VerificationError>> {
|
||||||
@ -419,6 +520,12 @@ impl std::fmt::Display for VerificationError {
|
|||||||
write!(f, "Merge block {} uses predecessor-defined value {} from block {} without Phi",
|
write!(f, "Merge block {} uses predecessor-defined value {} from block {} without Phi",
|
||||||
merge_block, value, pred_block)
|
merge_block, value, pred_block)
|
||||||
},
|
},
|
||||||
|
VerificationError::InvalidWeakRefSource { weak_ref, block, instruction_index, reason } => {
|
||||||
|
write!(f, "Invalid WeakRef source {} in block {} at {}: {}", weak_ref, block, instruction_index, reason)
|
||||||
|
},
|
||||||
|
VerificationError::InvalidBarrierPointer { ptr, block, instruction_index, reason } => {
|
||||||
|
write!(f, "Invalid Barrier pointer {} in block {} at {}: {}", ptr, block, instruction_index, reason)
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -44,6 +44,61 @@ mod tests {
|
|||||||
let _ = vm.execute_module(&module).expect("VM should execute module");
|
let _ = vm.execute_module(&module).expect("VM should execute module");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn vm_exec_typeop_cast_int_float() {
|
||||||
|
let mut func = make_main();
|
||||||
|
let bb = func.entry_block;
|
||||||
|
|
||||||
|
let v0 = func.next_value_id();
|
||||||
|
func.get_block_mut(bb).unwrap().add_instruction(MirInstruction::Const { dst: v0, value: ConstValue::Integer(3) });
|
||||||
|
|
||||||
|
let v1 = func.next_value_id();
|
||||||
|
func.get_block_mut(bb).unwrap().add_instruction(MirInstruction::TypeOp { dst: v1, op: crate::mir::TypeOpKind::Cast, value: v0, ty: MirType::Float });
|
||||||
|
func.get_block_mut(bb).unwrap().add_instruction(MirInstruction::Return { value: None });
|
||||||
|
|
||||||
|
let mut module = MirModule::new("test".to_string());
|
||||||
|
module.add_function(func);
|
||||||
|
let mut vm = VM::new();
|
||||||
|
let _ = vm.execute_module(&module).expect("int->float cast should succeed");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn vm_exec_typeop_cast_float_int() {
|
||||||
|
let mut func = make_main();
|
||||||
|
let bb = func.entry_block;
|
||||||
|
|
||||||
|
let v0 = func.next_value_id();
|
||||||
|
func.get_block_mut(bb).unwrap().add_instruction(MirInstruction::Const { dst: v0, value: ConstValue::Float(3.7) });
|
||||||
|
|
||||||
|
let v1 = func.next_value_id();
|
||||||
|
func.get_block_mut(bb).unwrap().add_instruction(MirInstruction::TypeOp { dst: v1, op: crate::mir::TypeOpKind::Cast, value: v0, ty: MirType::Integer });
|
||||||
|
func.get_block_mut(bb).unwrap().add_instruction(MirInstruction::Return { value: None });
|
||||||
|
|
||||||
|
let mut module = MirModule::new("test".to_string());
|
||||||
|
module.add_function(func);
|
||||||
|
let mut vm = VM::new();
|
||||||
|
let _ = vm.execute_module(&module).expect("float->int cast should succeed");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn vm_exec_typeop_cast_invalid_should_error() {
|
||||||
|
let mut func = make_main();
|
||||||
|
let bb = func.entry_block;
|
||||||
|
|
||||||
|
let v0 = func.next_value_id();
|
||||||
|
func.get_block_mut(bb).unwrap().add_instruction(MirInstruction::Const { dst: v0, value: ConstValue::String("x".to_string()) });
|
||||||
|
|
||||||
|
let v1 = func.next_value_id();
|
||||||
|
func.get_block_mut(bb).unwrap().add_instruction(MirInstruction::TypeOp { dst: v1, op: crate::mir::TypeOpKind::Cast, value: v0, ty: MirType::Integer });
|
||||||
|
func.get_block_mut(bb).unwrap().add_instruction(MirInstruction::Return { value: None });
|
||||||
|
|
||||||
|
let mut module = MirModule::new("test".to_string());
|
||||||
|
module.add_function(func);
|
||||||
|
let mut vm = VM::new();
|
||||||
|
let res = vm.execute_module(&module);
|
||||||
|
assert!(res.is_err(), "invalid cast should return error");
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn vm_exec_legacy_typecheck_cast() {
|
fn vm_exec_legacy_typecheck_cast() {
|
||||||
let mut func = make_main();
|
let mut func = make_main();
|
||||||
|
|||||||
Reference in New Issue
Block a user