docs: AOT/ネイティブコンパイル情報をexecution-backends.mdに追加
- 4つ目の実行方式としてAOT(Ahead-of-Time)コンパイルを文書化 - MIR→WASM→.cwasm のコンパイルパイプラインを説明 - wasm-backend featureでのビルド方法を明記 - 現在の実装状況(完全なスタンドアロン実行ファイルはTODO)を記載 - CLAUDE.mdのWASM説明も3種類(Rust→WASM、Nyash→WASM、Nyash→AOT)に更新 - CURRENT_TASK.mdにPhase 10.9/10.10の完了項目を追加 ChatGPT5さんのAOT試行に対応した適切なドキュメント配置を実施 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
85
.github/workflows/smoke.yml
vendored
Normal file
85
.github/workflows/smoke.yml
vendored
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
name: Smoke (Phase 10.10)
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
paths:
|
||||||
|
- 'src/**'
|
||||||
|
- 'examples/**'
|
||||||
|
- 'tools/**'
|
||||||
|
- 'Cargo.toml'
|
||||||
|
- 'Cargo.lock'
|
||||||
|
- 'docs/**'
|
||||||
|
- '.github/workflows/smoke.yml'
|
||||||
|
pull_request:
|
||||||
|
paths:
|
||||||
|
- 'src/**'
|
||||||
|
- 'examples/**'
|
||||||
|
- 'tools/**'
|
||||||
|
- 'Cargo.toml'
|
||||||
|
- 'Cargo.lock'
|
||||||
|
- 'docs/**'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
smoke:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
env:
|
||||||
|
CARGO_TERM_COLOR: always
|
||||||
|
# Disable external plugins to keep CI deterministic
|
||||||
|
NYASH_DISABLE_PLUGINS: '1'
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Setup Rust (stable)
|
||||||
|
uses: dtolnay/rust-toolchain@stable
|
||||||
|
|
||||||
|
- name: Cache cargo registry and build
|
||||||
|
uses: actions/cache@v4
|
||||||
|
with:
|
||||||
|
path: |
|
||||||
|
~/.cargo/registry
|
||||||
|
~/.cargo/git
|
||||||
|
target
|
||||||
|
key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
|
||||||
|
restore-keys: |
|
||||||
|
${{ runner.os }}-cargo-
|
||||||
|
|
||||||
|
- name: Run smoke script
|
||||||
|
run: bash tools/smoke_phase_10_10.sh
|
||||||
|
|
||||||
|
smoke-compile-events:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
env:
|
||||||
|
CARGO_TERM_COLOR: always
|
||||||
|
NYASH_DISABLE_PLUGINS: '1'
|
||||||
|
NYASH_JIT_EVENTS_COMPILE: '1'
|
||||||
|
NYASH_JIT_HOSTCALL: '1'
|
||||||
|
NYASH_JIT_EVENTS_PATH: events.jsonl
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Setup Rust (stable)
|
||||||
|
uses: dtolnay/rust-toolchain@stable
|
||||||
|
|
||||||
|
- name: Cache cargo registry and build
|
||||||
|
uses: actions/cache@v4
|
||||||
|
with:
|
||||||
|
path: |
|
||||||
|
~/.cargo/registry
|
||||||
|
~/.cargo/git
|
||||||
|
target
|
||||||
|
key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
|
||||||
|
restore-keys: |
|
||||||
|
${{ runner.os }}-cargo-
|
||||||
|
|
||||||
|
- name: Build
|
||||||
|
run: cargo build --release -j2 --features cranelift-jit
|
||||||
|
|
||||||
|
- name: Run HH example (compile events)
|
||||||
|
run: ./target/release/nyash --backend vm examples/jit_map_get_param_hh.nyash
|
||||||
|
|
||||||
|
- name: Verify events contain phase:lower
|
||||||
|
run: |
|
||||||
|
test -f events.jsonl
|
||||||
|
grep -q '"phase":"lower"' events.jsonl
|
||||||
32
CLAUDE.md
32
CLAUDE.md
@ -101,13 +101,15 @@ cargo xwin build --target x86_64-pc-windows-msvc --release
|
|||||||
target/x86_64-pc-windows-msvc/release/nyash.exe
|
target/x86_64-pc-windows-msvc/release/nyash.exe
|
||||||
```
|
```
|
||||||
|
|
||||||
### 🌐 WebAssembly版
|
### 🌐 WebAssembly版(2種類あるので注意!)
|
||||||
|
|
||||||
|
#### 1️⃣ **Rust→WASM(ブラウザでNyashインタープリター実行)**
|
||||||
```bash
|
```bash
|
||||||
# WASMビルド(ルートディレクトリで実行)
|
# WASMビルド(ルートディレクトリで実行)
|
||||||
wasm-pack build --target web
|
wasm-pack build --target web
|
||||||
|
|
||||||
# ビルド結果は pkg/ ディレクトリに生成される
|
# ビルド結果は pkg/ ディレクトリに生成される
|
||||||
# - pkg/nyash_rust_bg.wasm
|
# - pkg/nyash_rust_bg.wasm # Rustで書かれたNyashインタープリター全体
|
||||||
# - pkg/nyash_rust.js
|
# - pkg/nyash_rust.js
|
||||||
# - pkg/nyash_rust.d.ts
|
# - pkg/nyash_rust.d.ts
|
||||||
|
|
||||||
@ -121,6 +123,32 @@ python3 -m http.server 8010
|
|||||||
|
|
||||||
**注意**: WASMビルドでは一部のBox(TimerBox、AudioBox等)は除外されます。
|
**注意**: WASMビルドでは一部のBox(TimerBox、AudioBox等)は除外されます。
|
||||||
|
|
||||||
|
#### 2️⃣ **Nyash→MIR→WASM(Nyashプログラムをコンパイル)**
|
||||||
|
```bash
|
||||||
|
# NyashコードをWASMにコンパイル(WAT形式で出力)
|
||||||
|
./target/release/nyash --compile-wasm program.nyash
|
||||||
|
|
||||||
|
# ファイルに出力
|
||||||
|
./target/release/nyash --compile-wasm program.nyash -o output.wat
|
||||||
|
|
||||||
|
# 現在の実装状況:
|
||||||
|
# - 基本的なMIR→WASM変換は動作
|
||||||
|
# - print文などの基本機能のみ
|
||||||
|
# - 実行にはwasmtimeなどのランタイムが必要
|
||||||
|
```
|
||||||
|
|
||||||
|
**違いのまとめ:**
|
||||||
|
- **Rust→WASM**: Nyashインタープリター自体をブラウザで動かす(フル機能)
|
||||||
|
- **Nyash→WASM**: Nyashプログラムを単体WASMに変換(限定機能)
|
||||||
|
|
||||||
|
#### 3️⃣ **Nyash→AOT/Native(将来実装予定)**
|
||||||
|
```bash
|
||||||
|
# NyashコードをネイティブバイナリにAOTコンパイル(現在開発中)
|
||||||
|
./target/release/nyash --compile-native program.nyash -o program.exe
|
||||||
|
# または
|
||||||
|
./target/release/nyash --aot program.nyash -o program.exe
|
||||||
|
```
|
||||||
|
|
||||||
### 🔧 JIT-direct(独立JIT)運用メモ(最小)
|
### 🔧 JIT-direct(独立JIT)運用メモ(最小)
|
||||||
- 方針: 当面は read-only(書き込み命令はjit-directで拒否)
|
- 方針: 当面は read-only(書き込み命令はjit-directで拒否)
|
||||||
- 失敗の見える化: `NYASH_JIT_STATS_JSON=1` または `NYASH_JIT_ERROR_JSON=1` でエラーを1行JSON出力
|
- 失敗の見える化: `NYASH_JIT_STATS_JSON=1` または `NYASH_JIT_ERROR_JSON=1` でエラーを1行JSON出力
|
||||||
|
|||||||
@ -88,6 +88,11 @@ result2 = await future2
|
|||||||
|
|
||||||
## 🏗️ **革命的アーキテクチャ**
|
## 🏗️ **革命的アーキテクチャ**
|
||||||
|
|
||||||
|
### 例とスモーク(開発者向けクイック)
|
||||||
|
- 代表例: `examples/README.md`(HH直実行・mutating opt-in・GCデモ)
|
||||||
|
- スモーク実行: `bash tools/smoke_phase_10_10.sh`
|
||||||
|
- JITイベント最小スキーマ: `docs/reference/jit/jit_events_json_v0_1.md`
|
||||||
|
|
||||||
### Everything is Box 哲学
|
### Everything is Box 哲学
|
||||||
Nyashのすべての値は **Box** - 統一された、メモリ安全なコンテナです:
|
Nyashのすべての値は **Box** - 統一された、メモリ安全なコンテナです:
|
||||||
|
|
||||||
@ -311,4 +316,4 @@ Nyashは最先端のAI協業で開発されています!
|
|||||||
|
|
||||||
*❤️、🤖 Claude Code、そしてEverything is Box哲学で構築*
|
*❤️、🤖 Claude Code、そしてEverything is Box哲学で構築*
|
||||||
|
|
||||||
**Nyash - すべての値がBoxであり、すべてのBoxが物語を語る場所。**
|
**Nyash - すべての値がBoxであり、すべてのBoxが物語を語る場所。**
|
||||||
|
|||||||
2
compile_only.jsonl
Normal file
2
compile_only.jsonl
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
{"kind":"hostcall","function":"<jit>","arg_types":["Handle","I64","I64"],"argc":3,"decision":"fallback","id":"nyash.map.set_h","phase":"lower","reason":"receiver_not_param"}
|
||||||
|
{"kind":"hostcall","function":"<jit>","arg_types":["Handle","Handle"],"argc":2,"decision":"allow","id":"nyash.map.get_hh","phase":"lower","reason":"sig_ok"}
|
||||||
@ -0,0 +1,137 @@
|
|||||||
|
# AI-Nyash Compact Notation Protocol (ANCP) - Gemini & Codex Analysis
|
||||||
|
Date: 2025-08-29
|
||||||
|
Topic: ANCP深層技術分析
|
||||||
|
|
||||||
|
## Geminiさんの分析
|
||||||
|
|
||||||
|
### 圧縮記法の設計改善点
|
||||||
|
|
||||||
|
**記号の衝突と曖昧性**
|
||||||
|
- `$` や `@` は多くの言語で特別な意味を持つため、確認が必要
|
||||||
|
- 字句解析器が記号と識別子を明確に分離できる設計が重要
|
||||||
|
- 演算子前後の空白処理に注意(`return-1` → `r-1` の曖昧性)
|
||||||
|
|
||||||
|
**マッピングの体系化**
|
||||||
|
- 型・オブジェクト操作系: `$` `&` `*`
|
||||||
|
- 制御フロー系: `!` `?`
|
||||||
|
- スコープ・可視性系: `@` `:`
|
||||||
|
- カテゴリ別の一貫性が重要
|
||||||
|
|
||||||
|
### 他言語での類似事例
|
||||||
|
|
||||||
|
**成功例**
|
||||||
|
1. **JavaScript Minification**: 変数名短縮、空白削除で劇的なサイズ削減
|
||||||
|
2. **Protocol Buffers/MessagePack**: スキーマ共有でキー名をIDに置換
|
||||||
|
3. **WebAssembly**: .wat(テキスト)⇔ .wasm(バイナリ)の双方向変換
|
||||||
|
|
||||||
|
**失敗例・注意点**
|
||||||
|
- **APL/J/K言語**: 極度の記号化により「書き込み専用言語」と揶揄される
|
||||||
|
- ANCPは「AI専用」と割り切ることでこの問題を回避
|
||||||
|
|
||||||
|
### AIの学習効率向上のための記号選択
|
||||||
|
|
||||||
|
**トークナイザの基本**
|
||||||
|
- BPE (Byte Pair Encoding) による頻出文字列の1トークン化
|
||||||
|
- 単一ASCII記号(`$` `@` `#` など)は1トークンになりやすい
|
||||||
|
|
||||||
|
**最適な記号の優先順位**
|
||||||
|
1. 単一ASCII記号: 最も効率的
|
||||||
|
2. 1文字アルファベット: 良い選択だが空白区切り推奨
|
||||||
|
3. 避けるべき: Unicode記号(複数バイト消費)
|
||||||
|
|
||||||
|
**実践的検証方法**
|
||||||
|
```python
|
||||||
|
import tiktoken
|
||||||
|
enc = tiktoken.get_encoding("cl100k_base")
|
||||||
|
# トークン数の実測実験
|
||||||
|
```
|
||||||
|
|
||||||
|
### パーサー統合の技術要点
|
||||||
|
|
||||||
|
- **字句解析器の拡張**: ANCP記号→予約語トークンへの変換ルール
|
||||||
|
- **双方向性の保証**: ANCPジェネレータとPretty Printer
|
||||||
|
- **エラー報告**: Source Map的な仕組みで元コード位置を保持
|
||||||
|
|
||||||
|
### 将来の拡張性確保
|
||||||
|
|
||||||
|
- **マッピングテーブルの外部化**: JSON/TOML設定ファイル
|
||||||
|
- **記号枯渇への備え**: 2文字組み合わせルール(`$b`, `$f`)
|
||||||
|
- **プロトコルバージョニング**: `ancp/1.0;` ヘッダー
|
||||||
|
|
||||||
|
## Codexさんの実装設計
|
||||||
|
|
||||||
|
### Lexer設計(Rust)
|
||||||
|
|
||||||
|
**Dialect検出**
|
||||||
|
- 明示ヘッダ: `;ancp:1 nyash:0.5;`
|
||||||
|
- 自動判別: ANCP記号ヒット率による判定
|
||||||
|
|
||||||
|
**Token統合**
|
||||||
|
- `TokenKind` で統一(通常/ANCP記号は同じTokenKindへ)
|
||||||
|
- `LosslessToken` 層でトリビア(空白・コメント)保持
|
||||||
|
|
||||||
|
**データ構造**
|
||||||
|
```rust
|
||||||
|
enum Dialect { Nyash, ANCP(Version) }
|
||||||
|
struct Token {
|
||||||
|
kind: TokenKind,
|
||||||
|
span: Span,
|
||||||
|
raw: RawLexemeId,
|
||||||
|
leading: Trivia,
|
||||||
|
trailing: Trivia
|
||||||
|
}
|
||||||
|
struct Transcoder {
|
||||||
|
to_ancp,
|
||||||
|
to_nyash,
|
||||||
|
span_map: SpanMap
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 双方向変換のテスト戦略
|
||||||
|
|
||||||
|
1. **同値往復**: `decode(encode(src)) == src`
|
||||||
|
2. **プロパティテスト**: proptestでランダム構文木往復
|
||||||
|
3. **パーサー同値**: AST正規化後の同型性assert
|
||||||
|
4. **差分回帰**: cargo instaでスナップショット
|
||||||
|
5. **ファジング**: libFuzzer/AFLでクラッシュ監視
|
||||||
|
6. **境界網羅**: 演算子隣接、Unicode識別子など
|
||||||
|
|
||||||
|
### デバッグ体験の維持
|
||||||
|
|
||||||
|
- **診断の二面表示**: エラーで両表記併記 `@f (fn)`
|
||||||
|
- **IDE統合**: LSPでホバー相互表示、CodeLens
|
||||||
|
- **条件付きダンプ**: `RUST_LOG=ancp=trace`
|
||||||
|
- **最小侵襲**: パーサー以降はTokenKindベースで動作
|
||||||
|
|
||||||
|
### パフォーマンス最適化
|
||||||
|
|
||||||
|
- **静的マップ**: `phf` でO(1)ルックアップ
|
||||||
|
- **ゼロコピー**: `&str` スライスと軽量Span
|
||||||
|
- **トリビア表現**: `SmallVec<[TriviaPiece; 2]>`
|
||||||
|
- **ストリーミング**: 大規模ファイル対応
|
||||||
|
|
||||||
|
### AIファインチューニング戦略
|
||||||
|
|
||||||
|
- **並列コーパス**: (Nyash, ANCP)対訳データ生成
|
||||||
|
- **タスク定義**: 翻訳、完成補完、修正適用
|
||||||
|
- **評価指標**: 往復一致率、パース成功率、トークン削減率
|
||||||
|
- **プロンプト指針**: ヘッダーと記号表を冒頭に付与
|
||||||
|
|
||||||
|
### 実装計画
|
||||||
|
|
||||||
|
**P1**: 固定辞書でTranscoder作成
|
||||||
|
**P2**: Lexer統合、AST同値テスト
|
||||||
|
**P3**: ヘッダー検出、エラーマッピング
|
||||||
|
**P4**: ベンチと辞書最適化
|
||||||
|
**P5**: LSP/CLI統合
|
||||||
|
|
||||||
|
## 統合された知見
|
||||||
|
|
||||||
|
両先生の分析から、ANCPは単なる圧縮ツールではなく、AI時代のプログラミング言語インフラストラクチャとして設計すべきことが明確になりました。特に重要なのは:
|
||||||
|
|
||||||
|
1. **トークン効率の実測主義** - 理論より実測で最適化
|
||||||
|
2. **双方向完全性の保証** - テスト駆動で信頼性確保
|
||||||
|
3. **デバッグ体験の維持** - 人間にも配慮した設計
|
||||||
|
4. **将来拡張性の確保** - バージョニングと外部設定
|
||||||
|
|
||||||
|
これらの知見をPhase 11での実装に活かします。
|
||||||
@ -231,6 +231,19 @@ NYASH_JIT_EXEC=1 NYASH_JIT_THRESHOLD=1 ./target/release/nyash --backend vm examp
|
|||||||
|
|
||||||
## 現在の地図(Done / Next)
|
## 現在の地図(Done / Next)
|
||||||
|
|
||||||
|
### ✅ 完了(Phase 10.10)
|
||||||
|
- **GC Switchable Runtime**: GcConfigBox実装(counting/trace/barrier_strictフラグ制御)
|
||||||
|
- **Unified Debug System**: DebugConfigBox実装(JIT events/stats/dump/dot設定の一元管理)
|
||||||
|
- **Box Method Dispatch**: GcConfigBox/DebugConfigBoxのメソッドディスパッチ実装
|
||||||
|
- **JitPolicyBox**: ホワイトリストとプリセット機能実装(mutating_minimal/common)
|
||||||
|
- **ANCP設計**: AI-Nyash Compact Notation Protocol設計完了(50-90%トークン削減)
|
||||||
|
|
||||||
|
### ✅ 完了(Phase 10.9)
|
||||||
|
- **HostCall統合**: Registry v0実装、Policy/Events連携
|
||||||
|
- **HH経路**: Map.get_hh実装(Handle,Handle引数でJIT直実行)
|
||||||
|
- **RO/WO分離**: read-only既定、mutatingはopt-in(policy_denied_mutatingイベント)
|
||||||
|
- **Math関数**: sin/cos/abs/min/max等のF64署名一致時のallow実装
|
||||||
|
|
||||||
### ✅ 完了(Phase 9.79b)
|
### ✅ 完了(Phase 9.79b)
|
||||||
- TypeMeta/Thunk正式化・Poly-PIC(2〜4)・Plugin TLV拡張(bool/i64/f64/bytes)
|
- TypeMeta/Thunk正式化・Poly-PIC(2〜4)・Plugin TLV拡張(bool/i64/f64/bytes)
|
||||||
- VM fast-path整備(Instance/Plugin/Builtin)と統計サマリ強化
|
- VM fast-path整備(Instance/Plugin/Builtin)と統計サマリ強化
|
||||||
@ -397,9 +410,55 @@ NYASH_JIT_EXEC=1 NYASH_JIT_THRESHOLD=1 ./target/release/nyash --backend vm examp
|
|||||||
- jit_policy_optin_mutating.nyash(mutatingのopt-in)
|
- jit_policy_optin_mutating.nyash(mutatingのopt-in)
|
||||||
- gc_counting_demo.nyash(CountingGcの可視化、VMパス)
|
- gc_counting_demo.nyash(CountingGcの可視化、VMパス)
|
||||||
|
|
||||||
次の着手(Restart後すぐ)
|
### ✅ 完了(2025-08-28 late 整理)
|
||||||
1) Runner統合(DebugConfigの反映優先度/CLI連携の整理)
|
- Runner統合(DebugConfig/CLI/Envの整合)
|
||||||
- 目標: CLI→DebugConfigBox→env→JIT/VM の一本化(既存JitConfigとの整合)
|
- DOT指定時にdump暗黙ON、観測系ON時のしきい値auto=1(未指定時)
|
||||||
2) GCドキュメントに GcConfigBox の使用例を追記(短文 + コマンド)
|
- GCドキュメントに GcConfigBox 使用例を追記(短文 + コマンド)
|
||||||
3) examples/README.md(最小手順)
|
- examples/README.md 最小手順を整備(HH直実行・mutating opt-in・CountingGc)
|
||||||
- HH直実行、mutating opt-in、CountingGc demo の3本だけ掲載
|
- DebugConfigBox拡張(最小): `jit_events_compile/runtime`, `jit_events_path` を追加(`apply()`でenv反映)
|
||||||
|
- JIT Eventsのphase分離(lower/execute)とAPI導入(compileは明示opt-in)
|
||||||
|
- サンプル追加: `examples/jit_policy_whitelist_demo.nyash`(read_only→whitelist/events分離)
|
||||||
|
- runtime trapイベント出力(JIT実行失敗時に1行JSONL)
|
||||||
|
- CIスモーク導入: runtime系(3本)+ compile-events(phase:"lower"検証)
|
||||||
|
|
||||||
|
⏭️ 次(Phase 10.10 続行・箱は増やさない)
|
||||||
|
- ANYヘルパ運用で十分(length_h / is_empty_h)→ 追加なし、Box-Firstの最小原則を維持
|
||||||
|
- whitelist回帰の軽量確認は `jit_policy_whitelist_demo.nyash` で実施(CI移行は後段)
|
||||||
|
- 将来候補: `nyash.any.type_h` は Phase 11 で型特化最適化検討時に導入可
|
||||||
|
|
||||||
|
### 🔍 Smoke(Phase 10.10 最小確認)
|
||||||
|
- スクリプト: `tools/smoke_phase_10_10.sh`
|
||||||
|
- 実行: `bash tools/smoke_phase_10_10.sh`
|
||||||
|
- 内容: Build → HH直実行 → mutating opt-in → GCデモ
|
||||||
|
|
||||||
|
代表コマンド(HostCallはNYASH_JIT_HOSTCALL=1を明示)
|
||||||
|
```
|
||||||
|
# Build(Cranelift込み推奨)
|
||||||
|
cargo build --release -j32 --features cranelift-jit
|
||||||
|
|
||||||
|
# HH直実行(Map.get_hh)
|
||||||
|
NYASH_JIT_EXEC=1 NYASH_JIT_THRESHOLD=1 NYASH_JIT_HOSTCALL=1 NYASH_JIT_EVENTS=1 \
|
||||||
|
./target/release/nyash --backend vm examples/jit_map_get_param_hh.nyash
|
||||||
|
|
||||||
|
# mutating opt-in(policy whitelist)
|
||||||
|
NYASH_JIT_THRESHOLD=1 NYASH_JIT_HOSTCALL=1 \
|
||||||
|
./target/release/nyash --backend vm examples/jit_policy_whitelist_demo.nyash
|
||||||
|
|
||||||
|
# GC counting(VMパス)
|
||||||
|
./target/release/nyash --backend vm examples/gc_counting_demo.nyash
|
||||||
|
|
||||||
|
# compileのみ(必要時)
|
||||||
|
NYASH_JIT_EVENTS_COMPILE=1 NYASH_JIT_HOSTCALL=1 NYASH_JIT_EVENTS_PATH=events.jsonl \
|
||||||
|
./target/release/nyash --backend vm examples/jit_map_get_param_hh.nyash
|
||||||
|
```
|
||||||
|
|
||||||
|
## ✅ Phase 10.10 完了(DoD確認)
|
||||||
|
- DebugConfig/Box/CLIの一貫挙動(apply後の即時反映): OK
|
||||||
|
- GC切替とバリアサイト観測が可能: OK(CountingGc/TRACE/STRICT)
|
||||||
|
- HostCall(RO/一部WO): paramでallow/非paramでfallback(イベントで確認): OK
|
||||||
|
- 代表サンプルが examples/README の手順で成功(CIスモークもgreen): OK
|
||||||
|
- イベントスキーマ(v0.1)文書化・trap出力: OK
|
||||||
|
|
||||||
|
⏭️ 次(Phase 10.1 着手)
|
||||||
|
- Python統合(chatgpt5_integrated_plan.md)に沿って Week1 を開始
|
||||||
|
- 10.10の観測・回帰はCIスモークで継続監視(問題検知時は即fix)
|
||||||
|
|||||||
@ -80,5 +80,15 @@ NYASH_JIT_EXEC=1 NYASH_JIT_THRESHOLD=1 NYASH_JIT_HOSTCALL=1 NYASH_JIT_EVENTS=1 \
|
|||||||
# Mutating opt-in(Array.push)
|
# Mutating opt-in(Array.push)
|
||||||
NYASH_JIT_EXEC=1 NYASH_JIT_THRESHOLD=1 NYASH_JIT_HOSTCALL=1 NYASH_JIT_EVENTS=1 \
|
NYASH_JIT_EXEC=1 NYASH_JIT_THRESHOLD=1 NYASH_JIT_HOSTCALL=1 NYASH_JIT_EVENTS=1 \
|
||||||
./target/release/nyash --backend vm examples/jit_policy_optin_mutating.nyash
|
./target/release/nyash --backend vm examples/jit_policy_optin_mutating.nyash
|
||||||
```
|
|
||||||
|
|
||||||
|
## 例とスモーク(開発者向けクイック)
|
||||||
|
- 例一覧: `examples/README.md`(HH直実行・mutating opt-in・GCデモ)
|
||||||
|
- スモーク: `bash tools/smoke_phase_10_10.sh`
|
||||||
|
- JITイベント最小スキーマ: `docs/reference/jit/jit_events_json_v0_1.md`
|
||||||
|
|
||||||
|
### Quick Note(運用の勘所)
|
||||||
|
- phase分離: compile→`phase:"lower"`(opt-in), runtime→`phase:"execute"`(既定ON可)
|
||||||
|
- しきい値: 観測ONかつ未指定なら `NYASH_JIT_THRESHOLD=1`(Runner/DebugConfigが補助)
|
||||||
|
- HostCall: 実例では `NYASH_JIT_HOSTCALL=1` を明示(HH直実行/ANYヘルパ)
|
||||||
|
- ANYヘルパ: `nyash.any.length_h / is_empty_h` でROは十分カバー(追加不要)
|
||||||
|
```
|
||||||
|
|||||||
@ -0,0 +1,412 @@
|
|||||||
|
# AI-Nyash Compact Notation Protocol (ANCP)
|
||||||
|
Status: Idea → Design Complete
|
||||||
|
Created: 2025-08-29
|
||||||
|
Updated: 2025-08-29 (Gemini & Codex分析統合、スモークテスト統合追加)
|
||||||
|
Priority: High
|
||||||
|
Category: AI Integration / Code Compression / Testing Infrastructure
|
||||||
|
|
||||||
|
## 概要
|
||||||
|
AIとNyashの効率的な通信のための圧縮記法プロトコル。予約語を1-2文字の記号に変換し、トークン数を大幅削減しながら、副次的にコード整形機能も提供する。さらに、既存のスモークテスト基盤との統合により、品質保証も自動化する。
|
||||||
|
|
||||||
|
## 動機
|
||||||
|
- AIのコンテキストウィンドウ制限への対応(50-90%削減目標)
|
||||||
|
- 人間が書いたコードをAIに効率的に渡す必要性
|
||||||
|
- Nyashパーサーとの100%同期による確実性
|
||||||
|
- 圧縮→展開プロセスでの自動コード整形
|
||||||
|
- 既存テスト基盤との統合による品質保証
|
||||||
|
|
||||||
|
## 設計詳細
|
||||||
|
|
||||||
|
### 基本的な変換例
|
||||||
|
```nyash
|
||||||
|
// 元のコード
|
||||||
|
box Cat from Animal {
|
||||||
|
init { name, age }
|
||||||
|
birth() {
|
||||||
|
me.name = "Tama"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ANCP圧縮後
|
||||||
|
$Cat@Animal{#{name,age}b(){m.name="Tama"}}
|
||||||
|
|
||||||
|
// プロトコルヘッダー付き
|
||||||
|
;ancp:1.0 nyash:0.5;
|
||||||
|
$Cat@Animal{#{name,age}b(){m.name="Tama"}}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 記号マッピング体系
|
||||||
|
```
|
||||||
|
【型・オブジェクト操作系】
|
||||||
|
box → $ # Box定義
|
||||||
|
new → n # インスタンス生成
|
||||||
|
me → m # 自己参照
|
||||||
|
|
||||||
|
【構造・スコープ系】
|
||||||
|
from → @ # 継承/デリゲーション
|
||||||
|
init → # # フィールド初期化
|
||||||
|
static → S # 静的定義
|
||||||
|
local → l # ローカル変数
|
||||||
|
|
||||||
|
【制御フロー系】
|
||||||
|
if → ? # 条件分岐
|
||||||
|
else → : # else節
|
||||||
|
loop → L # ループ
|
||||||
|
return → r # 戻り値
|
||||||
|
|
||||||
|
【メソッド系】
|
||||||
|
birth → b # コンストラクタ
|
||||||
|
override → O # オーバーライド
|
||||||
|
```
|
||||||
|
|
||||||
|
## 技術的実装設計(Rust)
|
||||||
|
|
||||||
|
### 1. Lexer拡張設計
|
||||||
|
```rust
|
||||||
|
// 双方向トランスコーダー
|
||||||
|
pub struct AncpTranscoder {
|
||||||
|
// 静的マップ(phf使用でO(1)ルックアップ)
|
||||||
|
nyash_to_ancp: phf::Map<&'static str, &'static str>,
|
||||||
|
ancp_to_nyash: phf::Map<&'static str, &'static str>,
|
||||||
|
|
||||||
|
// 位置情報マッピング(Source Map相当)
|
||||||
|
span_map: SpanMap,
|
||||||
|
|
||||||
|
// バージョン情報
|
||||||
|
version: AncpVersion,
|
||||||
|
}
|
||||||
|
|
||||||
|
// LosslessToken層(空白・コメント保持)
|
||||||
|
struct Token {
|
||||||
|
kind: TokenKind,
|
||||||
|
span: Span,
|
||||||
|
raw: RawLexemeId,
|
||||||
|
leading_trivia: Trivia,
|
||||||
|
trailing_trivia: Trivia,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dialect検出
|
||||||
|
enum Dialect {
|
||||||
|
Nyash,
|
||||||
|
ANCP(Version),
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. トークン効率最適化
|
||||||
|
```python
|
||||||
|
# tiktoken実測スクリプト
|
||||||
|
import tiktoken
|
||||||
|
enc = tiktoken.get_encoding("cl100k_base") # GPT-4等
|
||||||
|
|
||||||
|
def measure_tokens(code):
|
||||||
|
return len(enc.encode(code))
|
||||||
|
|
||||||
|
# 実測して最適な記号を選定
|
||||||
|
original = 'box Cat from Animal { init { name } }'
|
||||||
|
ancp = '$Cat@Animal{#{name}}'
|
||||||
|
|
||||||
|
reduction = 1 - (measure_tokens(ancp) / measure_tokens(original))
|
||||||
|
print(f"Token reduction: {reduction:.1%}")
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. 双方向変換保証テスト戦略
|
||||||
|
```rust
|
||||||
|
// プロパティベーステスト
|
||||||
|
#[test]
|
||||||
|
fn roundtrip_property(code in any_valid_nyash()) {
|
||||||
|
let transcoder = AncpTranscoder::new();
|
||||||
|
let (ancp, _) = transcoder.encode(&code)?;
|
||||||
|
let (restored, _) = transcoder.decode(&ancp)?;
|
||||||
|
assert_eq!(code, restored); // 完全一致
|
||||||
|
}
|
||||||
|
|
||||||
|
// AST同値性テスト
|
||||||
|
#[test]
|
||||||
|
fn ast_equivalence(code: &str) {
|
||||||
|
let ast_original = parse_nyash(code);
|
||||||
|
let ancp = transcode_to_ancp(code);
|
||||||
|
let ast_ancp = parse_ancp(ancp);
|
||||||
|
assert_ast_eq!(ast_original, ast_ancp);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ファジングテスト
|
||||||
|
#[cfg(fuzzing)]
|
||||||
|
fuzz_target!(|data: &[u8]| {
|
||||||
|
if let Ok(s) = std::str::from_utf8(data) {
|
||||||
|
let _ = transcode_roundtrip(s);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. デバッグ体験の維持
|
||||||
|
```rust
|
||||||
|
// ハイブリッドエラー表示
|
||||||
|
fn format_error(span: Span, ancp_src: &str, nyash_src: &str) {
|
||||||
|
let ancp_token = get_token_at(ancp_src, span);
|
||||||
|
let nyash_token = map_to_original(ancp_token);
|
||||||
|
eprintln!("Error at {} ({})", ancp_token, nyash_token);
|
||||||
|
// 例: "Error at @f (from)"
|
||||||
|
}
|
||||||
|
|
||||||
|
// IDE統合(LSP)
|
||||||
|
impl LanguageServer for NyashLsp {
|
||||||
|
fn hover(&self, position: Position) -> Hover {
|
||||||
|
// ANCP記号にホバーすると元の予約語を表示
|
||||||
|
let original = self.map_to_original(position);
|
||||||
|
Hover { contents: format!("ANCP: {} → Nyash: {}", ...) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## スモークテストとの統合
|
||||||
|
|
||||||
|
### 統一検証フレームワーク
|
||||||
|
```nyash
|
||||||
|
// 検証用Box - スモークテストとANCP検証を統合
|
||||||
|
box TestValidatorBox {
|
||||||
|
init { patterns, mode, transcoder }
|
||||||
|
|
||||||
|
birth() {
|
||||||
|
me.patterns = new ArrayBox()
|
||||||
|
me.mode = "smoke" // smoke/full/ancp/roundtrip
|
||||||
|
me.transcoder = new AncpTranscoder()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 共通のパターンチェック(既存スモークテストの仕組み)
|
||||||
|
checkPatterns(output) {
|
||||||
|
loop(me.patterns.hasNext()) {
|
||||||
|
local pattern
|
||||||
|
pattern = me.patterns.next()
|
||||||
|
|
||||||
|
if (not output.contains(pattern)) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Nyashコード実行して検証(既存)
|
||||||
|
validateScript(scriptPath) {
|
||||||
|
local output
|
||||||
|
output = me.runNyash(scriptPath)
|
||||||
|
return me.checkPatterns(output)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ANCP変換して実行・検証(新規)
|
||||||
|
validateAncp(ancpPath) {
|
||||||
|
// 1. ANCP → Nyash変換
|
||||||
|
local nyashCode
|
||||||
|
nyashCode = me.transcoder.decode(ancpPath)
|
||||||
|
|
||||||
|
// 2. 実行(既存の実行パスを再利用)
|
||||||
|
local output
|
||||||
|
output = me.runNyashCode(nyashCode)
|
||||||
|
|
||||||
|
// 3. パターン検証(既存のcheckPatternsを再利用!)
|
||||||
|
return me.checkPatterns(output)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 双方向テスト(ANCP特有)
|
||||||
|
validateRoundtrip(scriptPath) {
|
||||||
|
// 元の実行結果
|
||||||
|
local originalOutput
|
||||||
|
originalOutput = me.runNyash(scriptPath)
|
||||||
|
|
||||||
|
// Nyash → ANCP → Nyash → 実行
|
||||||
|
local ancp
|
||||||
|
ancp = me.transcoder.encode(scriptPath)
|
||||||
|
local restored
|
||||||
|
restored = me.transcoder.decode(ancp)
|
||||||
|
local roundtripOutput
|
||||||
|
roundtripOutput = me.runNyashCode(restored)
|
||||||
|
|
||||||
|
// 実行結果が同一か検証
|
||||||
|
return originalOutput == roundtripOutput
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 統合スモークテストスクリプト
|
||||||
|
```bash
|
||||||
|
#!/bin/bash
|
||||||
|
# 統一スモークテスト(Nyash & ANCP対応)
|
||||||
|
|
||||||
|
# 共通の検証関数
|
||||||
|
run_test() {
|
||||||
|
local mode=$1 # nyash/ancp/roundtrip
|
||||||
|
local file=$2 # ファイルパス
|
||||||
|
local pattern=$3 # 期待パターン
|
||||||
|
|
||||||
|
case "$mode" in
|
||||||
|
"ancp")
|
||||||
|
# ANCP → Nyash変換 → 実行
|
||||||
|
output=$(./target/release/nyash --from-ancp "$file" 2>&1)
|
||||||
|
;;
|
||||||
|
"roundtrip")
|
||||||
|
# Nyash → ANCP → Nyash → 実行
|
||||||
|
ancp=$(./target/release/nyash --to-ancp "$file")
|
||||||
|
output=$(echo "$ancp" | ./target/release/nyash --from-ancp - 2>&1)
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
# 通常のNyash実行
|
||||||
|
output=$(./target/release/nyash "$file" 2>&1)
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
# 共通のパターン検証(既存のスモークテストと同じ!)
|
||||||
|
echo "$output" | grep -q "$pattern"
|
||||||
|
}
|
||||||
|
|
||||||
|
# 自動テスト生成
|
||||||
|
generate_ancp_tests() {
|
||||||
|
for test in examples/*.nyash; do
|
||||||
|
# Nyashテストから自動的にANCPテストを生成
|
||||||
|
./tools/nyash2ancp "$test" > "${test%.nyash}.ancp"
|
||||||
|
|
||||||
|
# 同じ期待値パターンで両方をテスト
|
||||||
|
pattern=$(extract_expected_pattern "$test")
|
||||||
|
|
||||||
|
echo "Testing $test (Nyash)..."
|
||||||
|
run_test nyash "$test" "$pattern"
|
||||||
|
|
||||||
|
echo "Testing ${test%.nyash}.ancp (ANCP)..."
|
||||||
|
run_test ancp "${test%.nyash}.ancp" "$pattern"
|
||||||
|
|
||||||
|
echo "Testing roundtrip for $test..."
|
||||||
|
run_test roundtrip "$test" "$pattern"
|
||||||
|
done
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### CI/CD統合
|
||||||
|
```yaml
|
||||||
|
# .github/workflows/smoke.yml への追加
|
||||||
|
jobs:
|
||||||
|
smoke-ancp:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs: smoke # 通常のスモークテスト後に実行
|
||||||
|
env:
|
||||||
|
CARGO_TERM_COLOR: always
|
||||||
|
NYASH_DISABLE_PLUGINS: '1'
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Build with ANCP support
|
||||||
|
run: cargo build --release -j32 --features ancp
|
||||||
|
|
||||||
|
- name: Run ANCP roundtrip tests
|
||||||
|
run: |
|
||||||
|
# すべてのサンプルでANCP往復テスト
|
||||||
|
for f in examples/*.nyash; do
|
||||||
|
echo "Testing ANCP roundtrip: $f"
|
||||||
|
./tools/test_ancp_roundtrip.sh "$f"
|
||||||
|
done
|
||||||
|
|
||||||
|
- name: Run ANCP smoke tests
|
||||||
|
run: |
|
||||||
|
# 既存のスモークテストをANCPモードでも実行
|
||||||
|
ANCP_MODE=1 bash tools/smoke_phase_10_10.sh
|
||||||
|
|
||||||
|
- name: Measure token reduction
|
||||||
|
run: |
|
||||||
|
# 実際のトークン削減率を測定・報告
|
||||||
|
./tools/measure_ancp_efficiency.sh examples/*.nyash
|
||||||
|
```
|
||||||
|
|
||||||
|
### 共通化のメリット
|
||||||
|
|
||||||
|
1. **コード再利用**:
|
||||||
|
- 既存のパターン検証ロジックをそのまま活用
|
||||||
|
- `grep`ベースの柔軟な検証方法を継承
|
||||||
|
|
||||||
|
2. **テスト網羅性**:
|
||||||
|
- すべてのNyashテストが自動的にANCPテストにもなる
|
||||||
|
- 双方向変換の正確性を継続的に検証
|
||||||
|
|
||||||
|
3. **開発効率**:
|
||||||
|
- 新機能追加時、Nyashテストを書けばANCPテストも自動生成
|
||||||
|
- スモークテストの期待値パターンを共有
|
||||||
|
|
||||||
|
4. **品質保証**:
|
||||||
|
- ANCP変換によるセマンティクスの保持を自動検証
|
||||||
|
- AIが生成したANCPコードも同じ基準で検証可能
|
||||||
|
|
||||||
|
5. **将来の拡張性**:
|
||||||
|
- WebAssembly出力との統合も同じフレームワークで可能
|
||||||
|
- 他の中間表現(MIR等)も同様に統合可能
|
||||||
|
|
||||||
|
## 実装ロードマップ(更新版)
|
||||||
|
|
||||||
|
### Phase 1: 基礎実装(2週間)
|
||||||
|
- [ ] tiktoken実測による最適記号選定
|
||||||
|
- [ ] 固定辞書(20語)でTranscoder実装
|
||||||
|
- [ ] 基本的な往復テスト
|
||||||
|
- [ ] **既存スモークテストとの基本統合**
|
||||||
|
|
||||||
|
### Phase 2: パーサー統合(3週間)
|
||||||
|
- [ ] Lexer拡張(Dialect検出)
|
||||||
|
- [ ] TokenKind統一化
|
||||||
|
- [ ] SpanMapによるエラー位置マッピング
|
||||||
|
- [ ] **TestValidatorBox実装**
|
||||||
|
|
||||||
|
### Phase 3: 品質保証(2週間)
|
||||||
|
- [ ] プロパティベーステスト
|
||||||
|
- [ ] ファジングテスト
|
||||||
|
- [ ] ベンチマーク(性能測定)
|
||||||
|
- [ ] **自動ANCP テスト生成スクリプト**
|
||||||
|
|
||||||
|
### Phase 4: ツール統合(3週間)
|
||||||
|
- [ ] CLI: --view=ancp|nyash|hybrid
|
||||||
|
- [ ] VSCode拡張(ホバー変換)
|
||||||
|
- [ ] プロトコルヘッダー処理
|
||||||
|
- [ ] **CI/CDパイプライン統合**
|
||||||
|
|
||||||
|
### Phase 5: AI統合(4週間)
|
||||||
|
- [ ] 並列コーパス生成
|
||||||
|
- [ ] ファインチューニング実験
|
||||||
|
- [ ] 実運用評価
|
||||||
|
- [ ] **AIコード検証の自動化**
|
||||||
|
|
||||||
|
## リスクと対策
|
||||||
|
|
||||||
|
### 技術的リスク
|
||||||
|
1. **記号衝突**: 既存演算子との衝突 → 慎重な記号選定
|
||||||
|
2. **デバッグ困難**: 圧縮コードの可読性 → ハイブリッド表示
|
||||||
|
3. **バージョン互換**: 将来の拡張 → ヘッダーによるバージョニング
|
||||||
|
4. **テスト複雑性**: ANCP特有のバグ → スモークテスト統合で早期発見
|
||||||
|
|
||||||
|
### 運用リスク
|
||||||
|
1. **AIの誤解釈**: 記号の誤認識 → 明示的なヘッダー
|
||||||
|
2. **人間の混乱**: 2つの記法混在 → 明確な使い分け
|
||||||
|
3. **ツール対応**: エコシステム分断 → 段階的移行
|
||||||
|
4. **テスト負荷**: 2倍のテスト実行 → 並列実行とキャッシュ活用
|
||||||
|
|
||||||
|
## 成功指標(更新版)
|
||||||
|
- トークン削減率: 50%以上(目標70%)
|
||||||
|
- 往復変換成功率: 100%
|
||||||
|
- パース時間増加: 10%以内
|
||||||
|
- AIエラー率改善: 30%以上
|
||||||
|
- **スモークテスト合格率: 100%(Nyash/ANCP両方)**
|
||||||
|
- **CI実行時間増加: 20%以内**
|
||||||
|
|
||||||
|
## 関連技術・参考
|
||||||
|
- JavaScript Minification (Terser, UglifyJS)
|
||||||
|
- WebAssembly (.wat ⇔ .wasm)
|
||||||
|
- Protocol Buffers (スキーマベース圧縮)
|
||||||
|
- Source Maps (位置情報マッピング)
|
||||||
|
- **Nyashスモークテスト基盤**(パターン検証方式)
|
||||||
|
|
||||||
|
## 専門家レビュー
|
||||||
|
- Gemini: トークナイザ最適化、記号体系化の重要性を指摘
|
||||||
|
- Codex: Rust実装設計、テスト戦略の詳細を提供
|
||||||
|
- Claude: スモークテストとの統合可能性を発見
|
||||||
|
|
||||||
|
## 次のアクション
|
||||||
|
1. tiktoken実測実験の実施
|
||||||
|
2. 最小プロトタイプの作成
|
||||||
|
3. ChatGPTさんとの実装協議
|
||||||
|
4. **既存スモークテストへのANCPモード追加**
|
||||||
|
|
||||||
|
---
|
||||||
|
*このアイデアは2025-08-29にGeminiさん、Codexさんとの深い技術討論を経て、実装可能な設計として成熟し、さらにスモークテストとの統合により品質保証も含めた完全なソリューションとなりました。*
|
||||||
@ -1,6 +1,6 @@
|
|||||||
# Nyash実行バックエンド完全ガイド
|
# Nyash実行バックエンド完全ガイド
|
||||||
|
|
||||||
Nyashプログラミング言語は、**Everything is Box**哲学を維持しながら、3つの異なる実行方式をサポートしています。用途に応じて最適な実行方式を選択できます。
|
Nyashプログラミング言語は、**Everything is Box**哲学を維持しながら、4つの異なる実行方式をサポートしています。用途に応じて最適な実行方式を選択できます。
|
||||||
|
|
||||||
## 🚀 実行方式一覧
|
## 🚀 実行方式一覧
|
||||||
|
|
||||||
@ -9,6 +9,7 @@ Nyashプログラミング言語は、**Everything is Box**哲学を維持しな
|
|||||||
| **インタープリター** | 開発・デバッグ | 直接AST実行、詳細ログ | 低速・高機能 |
|
| **インタープリター** | 開発・デバッグ | 直接AST実行、詳細ログ | 低速・高機能 |
|
||||||
| **VM** | 本番・高速実行 | MIR→VM実行 | 中速・最適化 |
|
| **VM** | 本番・高速実行 | MIR→VM実行 | 中速・最適化 |
|
||||||
| **WASM** | Web・サンドボックス | MIR→WASM変換 | 高速・移植性 |
|
| **WASM** | Web・サンドボックス | MIR→WASM変換 | 高速・移植性 |
|
||||||
|
| **AOT** (実験的) | 高速起動 | MIR→WASM→ネイティブ | 最高速・要wasmtime |
|
||||||
|
|
||||||
## 📋 CLIオプション
|
## 📋 CLIオプション
|
||||||
|
|
||||||
@ -54,6 +55,20 @@ nyash --compile-wasm program.nyash -o output.wat
|
|||||||
nyash --compile-wasm program.nyash -o public/app.wat
|
nyash --compile-wasm program.nyash -o public/app.wat
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### AOT/ネイティブコンパイル(実験的機能)
|
||||||
|
```bash
|
||||||
|
# ビルド時に wasm-backend feature が必要
|
||||||
|
cargo build --release --features wasm-backend
|
||||||
|
|
||||||
|
# AOTコンパイル(.cwasm生成)
|
||||||
|
nyash --compile-native program.nyash -o program
|
||||||
|
# または
|
||||||
|
nyash --aot program.nyash -o program
|
||||||
|
|
||||||
|
# 注意: 現在は完全なスタンドアロン実行ファイルではなく、
|
||||||
|
# wasmtime用のプリコンパイル済みWASM(.cwasm)が生成されます
|
||||||
|
```
|
||||||
|
|
||||||
### ⚡ ベンチマーク(パフォーマンス測定)
|
### ⚡ ベンチマーク(パフォーマンス測定)
|
||||||
```bash
|
```bash
|
||||||
# 全バックエンド性能比較(デフォルト5回実行)
|
# 全バックエンド性能比較(デフォルト5回実行)
|
||||||
@ -186,6 +201,28 @@ NYASH_GC_COUNTING=1 NYASH_GC_TRACE=2 \
|
|||||||
./target/release/nyash --backend vm examples/scheduler_demo.nyash
|
./target/release/nyash --backend vm examples/scheduler_demo.nyash
|
||||||
```
|
```
|
||||||
|
|
||||||
|
#### Boxからの切替(GcConfigBox)
|
||||||
|
環境変数ではなくNyashスクリプトから切り替える場合は `GcConfigBox` を使います。`apply()` で環境に反映され、その後の実行に適用されます。
|
||||||
|
|
||||||
|
```nyash
|
||||||
|
// 最小例: CountingGc + trace をオン
|
||||||
|
static box Main {
|
||||||
|
main() {
|
||||||
|
local G
|
||||||
|
G = new GcConfigBox()
|
||||||
|
G = G.setFlag("counting", true)
|
||||||
|
G = G.setFlag("trace", true) // 1/2/3 は環境。Box では on/off を切替
|
||||||
|
G.apply() // ← ここで反映
|
||||||
|
return "ok"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
代表デモ(書き込みバリア/カウンタの可視化):
|
||||||
|
```bash
|
||||||
|
./target/release/nyash --backend vm examples/gc_counting_demo.nyash
|
||||||
|
```
|
||||||
|
|
||||||
## 🌐 WASM実行(Web対応)
|
## 🌐 WASM実行(Web対応)
|
||||||
|
|
||||||
### 特徴
|
### 特徴
|
||||||
@ -248,6 +285,58 @@ async function loadNyashWasm() {
|
|||||||
</script>
|
</script>
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## 🚀 AOT/ネイティブコンパイル(実験的)
|
||||||
|
|
||||||
|
### 特徴
|
||||||
|
- **用途**: 高速起動・配布用実行ファイル
|
||||||
|
- **実行**: AST→MIR→WASM→プリコンパイル済みネイティブ
|
||||||
|
- **速度**: 最高速(JIT起動オーバーヘッドなし)
|
||||||
|
- **制約**: wasmtimeランタイムが必要
|
||||||
|
|
||||||
|
### コンパイルパイプライン
|
||||||
|
```
|
||||||
|
Nyashソース → AST → MIR → WASM → .cwasm(プリコンパイル済み)
|
||||||
|
```
|
||||||
|
|
||||||
|
### ビルド方法
|
||||||
|
```bash
|
||||||
|
# 1. wasm-backend feature付きでNyashをビルド
|
||||||
|
cargo build --release --features wasm-backend
|
||||||
|
|
||||||
|
# 2. AOTコンパイル実行
|
||||||
|
./target/release/nyash --compile-native hello.nyash -o hello
|
||||||
|
# または短縮形
|
||||||
|
./target/release/nyash --aot hello.nyash -o hello
|
||||||
|
|
||||||
|
# 3. 生成されたファイル
|
||||||
|
# hello.cwasm が生成される(wasmtimeプリコンパイル形式)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 現在の実装状況
|
||||||
|
- ✅ MIR→WASM変換
|
||||||
|
- ✅ WASM→.cwasm(wasmtimeプリコンパイル)
|
||||||
|
- ❌ 完全なスタンドアロン実行ファイル生成(TODO)
|
||||||
|
- ❌ ランタイム埋め込み(将来実装予定)
|
||||||
|
|
||||||
|
### 使用例
|
||||||
|
```bash
|
||||||
|
# コンパイル
|
||||||
|
./target/release/nyash --aot examples/hello_world.nyash -o hello_aot
|
||||||
|
|
||||||
|
# 実行(将来的な目標)
|
||||||
|
# ./hello_aot # 現在は未実装
|
||||||
|
|
||||||
|
# 現在は wasmtime で実行
|
||||||
|
# wasmtime --precompiled hello_aot.cwasm
|
||||||
|
```
|
||||||
|
|
||||||
|
### 技術的詳細
|
||||||
|
AOTバックエンドは内部的に以下の処理を行います:
|
||||||
|
1. **MirCompiler**: NyashコードをMIRに変換
|
||||||
|
2. **WasmBackend**: MIRをWASMバイトコードに変換
|
||||||
|
3. **wasmtime::Engine::precompile**: WASMをネイティブコードにプリコンパイル
|
||||||
|
4. **出力**: .cwasm形式で保存(wasmtime独自形式)
|
||||||
|
|
||||||
## 📊 パフォーマンス比較
|
## 📊 パフォーマンス比較
|
||||||
|
|
||||||
### 🚀 実際のベンチマーク結果(2025-08-14測定・修正)
|
### 🚀 実際のベンチマーク結果(2025-08-14測定・修正)
|
||||||
|
|||||||
52
docs/reference/jit/jit_events_json_v0_1.md
Normal file
52
docs/reference/jit/jit_events_json_v0_1.md
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
# JIT Events JSON (v0.1)
|
||||||
|
|
||||||
|
最小のイベントJSONスキーマ。観測の足場として、値は安定キーのみを出力します。
|
||||||
|
|
||||||
|
- 出力形態: 1行 = 1 JSON(JSONL)
|
||||||
|
- 出力先: 標準出力 or `NYASH_JIT_EVENTS_PATH` で指定したファイル
|
||||||
|
- phase分離:
|
||||||
|
- compile: `phase: "lower"`(明示opt-in: `NYASH_JIT_EVENTS_COMPILE=1`)
|
||||||
|
- runtime: `phase: "execute"`(既定ON可: `NYASH_JIT_EVENTS=1` または `NYASH_JIT_EVENTS_RUNTIME=1`)
|
||||||
|
|
||||||
|
## フィールド(必須)
|
||||||
|
- kind: 文字列(例: "hostcall")
|
||||||
|
- function: 文字列(現状は "<jit>" で固定)
|
||||||
|
- phase: 文字列("lower" | "execute")
|
||||||
|
- id: シンボル名(例: "nyash.map.get_hh")
|
||||||
|
- decision: 文字列("allow" | "fallback")
|
||||||
|
- reason: 文字列("sig_ok" | "receiver_not_param" | "policy_denied_mutating" | "sig_mismatch" など)
|
||||||
|
- argc: 数値(観測引数数)
|
||||||
|
- arg_types: 文字列配列(例: ["Handle","I64"])
|
||||||
|
|
||||||
|
任意フィールド(存在時のみ)
|
||||||
|
- handle: 数値(JITハンドル)
|
||||||
|
- ms: 数値(処理時間ミリ秒)
|
||||||
|
|
||||||
|
## 出力例
|
||||||
|
|
||||||
|
compile(lower):
|
||||||
|
```json
|
||||||
|
{"kind":"hostcall","function":"<jit>","id":"nyash.map.get_hh","decision":"allow","reason":"sig_ok","argc":2,"arg_types":["Handle","Handle"],"phase":"lower"}
|
||||||
|
```
|
||||||
|
|
||||||
|
runtime(execute):
|
||||||
|
```json
|
||||||
|
{"kind":"hostcall","function":"<jit>","id":"nyash.array.push_h","decision":"fallback","reason":"policy_denied_mutating","argc":2,"arg_types":["Handle","I64"],"phase":"execute"}
|
||||||
|
```
|
||||||
|
|
||||||
|
trap(execute 中の失敗):
|
||||||
|
```json
|
||||||
|
{"kind":"trap","function":"<jit>","reason":"jit_execute_failed","ms":0,"phase":"execute"}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 環境変数(抜粋)
|
||||||
|
- NYASH_JIT_EVENTS=1: 既定のruntime出力
|
||||||
|
- NYASH_JIT_EVENTS_COMPILE=1: compile(lower)出力
|
||||||
|
- NYASH_JIT_EVENTS_RUNTIME=1: runtime出力
|
||||||
|
- NYASH_JIT_EVENTS_PATH=path.jsonl: ファイルに追記
|
||||||
|
- NYASH_JIT_THRESHOLD(未設定時): 観測ONで自動的に1が補われます(Runner/DebugConfigBoxが補助)
|
||||||
|
|
||||||
|
## 推奨の最小運用
|
||||||
|
- 現象確認: `NYASH_JIT_EVENTS=1`(runtimeのみ)
|
||||||
|
- 解析時のみcompile出力: `NYASH_JIT_EVENTS_COMPILE=1 NYASH_JIT_EVENTS_PATH=events.jsonl`
|
||||||
|
- HostCall系の例では `NYASH_JIT_HOSTCALL=1` を明示
|
||||||
6
events.jsonl
Normal file
6
events.jsonl
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
{"kind":"hostcall","function":"<jit>","arg_types":["Handle","I64","I64"],"argc":3,"decision":"fallback","id":"nyash.map.set_h","reason":"receiver_not_param"}
|
||||||
|
{"kind":"hostcall","function":"<jit>","arg_types":["Handle","I64","I64"],"argc":3,"decision":"fallback","id":"nyash.map.set_h","phase":"lower","reason":"receiver_not_param"}
|
||||||
|
{"kind":"hostcall","function":"<jit>","arg_types":["Handle","I64"],"argc":2,"decision":"allow","id":"nyash.array.push_h","reason":"sig_ok"}
|
||||||
|
{"kind":"hostcall","function":"<jit>","arg_types":["Handle"],"argc":1,"decision":"allow","id":"nyash.any.length_h","reason":"sig_ok"}
|
||||||
|
{"kind":"hostcall","function":"<jit>","arg_types":["Handle","I64"],"argc":2,"decision":"allow","id":"nyash.array.push_h","reason":"sig_ok"}
|
||||||
|
{"kind":"hostcall","function":"<jit>","arg_types":["Handle"],"argc":1,"decision":"allow","id":"nyash.any.length_h","reason":"sig_ok"}
|
||||||
5
events_2.jsonl
Normal file
5
events_2.jsonl
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
{"kind":"hostcall","function":"<jit>","arg_types":["Handle","I64","I64"],"argc":3,"decision":"fallback","id":"nyash.map.set_h","reason":"receiver_not_param"}
|
||||||
|
{"kind":"hostcall","function":"<jit>","arg_types":["Handle","I64"],"argc":2,"decision":"allow","id":"nyash.array.push_h","reason":"sig_ok"}
|
||||||
|
{"kind":"hostcall","function":"<jit>","arg_types":["Handle"],"argc":1,"decision":"allow","id":"nyash.any.length_h","reason":"sig_ok"}
|
||||||
|
{"kind":"hostcall","function":"<jit>","arg_types":["Handle","I64"],"argc":2,"decision":"allow","id":"nyash.array.push_h","reason":"sig_ok"}
|
||||||
|
{"kind":"hostcall","function":"<jit>","arg_types":["Handle"],"argc":1,"decision":"allow","id":"nyash.any.length_h","reason":"sig_ok"}
|
||||||
5
events_3.jsonl
Normal file
5
events_3.jsonl
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
{"kind":"hostcall","function":"<jit>","arg_types":["Handle","I64","I64"],"argc":3,"decision":"fallback","id":"nyash.map.set_h","phase":"lower","reason":"receiver_not_param"}
|
||||||
|
{"kind":"hostcall","function":"<jit>","arg_types":["Handle","I64"],"argc":2,"decision":"fallback","id":"nyash.array.push_h","reason":"policy_denied_mutating"}
|
||||||
|
{"kind":"hostcall","function":"<jit>","arg_types":["Handle"],"argc":1,"decision":"allow","id":"nyash.any.length_h","reason":"sig_ok"}
|
||||||
|
{"kind":"hostcall","function":"<jit>","arg_types":["Handle","I64"],"argc":2,"decision":"fallback","id":"nyash.array.push_h","reason":"policy_denied_mutating"}
|
||||||
|
{"kind":"hostcall","function":"<jit>","arg_types":["Handle"],"argc":1,"decision":"allow","id":"nyash.any.length_h","reason":"sig_ok"}
|
||||||
57
examples/README.md
Normal file
57
examples/README.md
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
# Examples Quick Start (Minimal)
|
||||||
|
|
||||||
|
このページはPhase 10.10の再起動用ミニ手順です。3つだけ確かめればOK。
|
||||||
|
|
||||||
|
- 事前ビルド: `cargo build --release -j32`
|
||||||
|
- 実行は `./target/release/nyash` を使用
|
||||||
|
|
||||||
|
## 1) HH直実行(Map.get_hh)
|
||||||
|
- 目的: 受け手/キーが関数パラメータのとき、JIT HostCallを許可(HH経路)。
|
||||||
|
- 実行:
|
||||||
|
```
|
||||||
|
NYASH_JIT_EXEC=1 NYASH_JIT_THRESHOLD=1 NYASH_JIT_HOSTCALL=1 NYASH_JIT_EVENTS=1 \
|
||||||
|
./target/release/nyash --backend vm examples/jit_map_get_param_hh.nyash
|
||||||
|
```
|
||||||
|
- 期待: `allow id: nyash.map.get_hh` イベントが出る。戻り値は `value1`。
|
||||||
|
|
||||||
|
## 2) mutating opt-in(JitPolicyBox)
|
||||||
|
- 目的: 既定 read_only。必要最小の書き込みだけホワイトリストで許可。
|
||||||
|
- 実行:
|
||||||
|
```
|
||||||
|
NYASH_JIT_EXEC=1 NYASH_JIT_THRESHOLD=1 NYASH_JIT_HOSTCALL=1 NYASH_JIT_EVENTS=1 \
|
||||||
|
./target/release/nyash --backend vm examples/jit_policy_optin_mutating.nyash
|
||||||
|
```
|
||||||
|
- 期待: 1回目は `policy_denied_mutating` でfallback、whitelist後の2回目はallow。
|
||||||
|
|
||||||
|
イベントの見やすさ(任意):
|
||||||
|
```
|
||||||
|
# コンパイル時(lower)のみ: phase="lower" が付与(compileは明示opt-in)
|
||||||
|
NYASH_JIT_EVENTS_COMPILE=1 NYASH_JIT_EVENTS_PATH=events.jsonl ...
|
||||||
|
|
||||||
|
# 実行時(runtime)のみ: phase="execute" が付与される
|
||||||
|
NYASH_JIT_EVENTS_RUNTIME=1 NYASH_JIT_EVENTS_PATH=events.jsonl ...
|
||||||
|
```
|
||||||
|
|
||||||
|
## 3) CountingGc デモ
|
||||||
|
- 目的: GCのカウント/トレース/バリア観測の導線確認(VM経路)。
|
||||||
|
- 実行:
|
||||||
|
```
|
||||||
|
./target/release/nyash --backend vm examples/gc_counting_demo.nyash
|
||||||
|
```
|
||||||
|
- Tips: 詳細ログは `NYASH_GC_COUNTING=1 NYASH_GC_TRACE=2` を併用。
|
||||||
|
|
||||||
|
## 4) Policy whitelist(events分離)
|
||||||
|
- 目的: read_only下でのfallback→allow(whitelist)と、compile/runtimeのphase分離をイベントで確認。
|
||||||
|
- 実行(しきい値=1を明示/またはDebugConfigBoxでapply後にRunnerが自動設定):
|
||||||
|
```
|
||||||
|
NYASH_JIT_THRESHOLD=1 NYASH_JIT_HOSTCALL=1 \
|
||||||
|
./target/release/nyash --backend vm examples/jit_policy_whitelist_demo.nyash
|
||||||
|
```
|
||||||
|
- 期待: `policy_events.jsonl` に `phase:"lower"`(計画)と `phase:"execute"`(実績)が出る。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
補足
|
||||||
|
- DebugConfigBox(events/stats/dump/dot)と GcConfigBox は Box から `apply()` で環境へ反映できます。
|
||||||
|
- `--emit-cfg path.dot` または `DebugConfigBox.setPath("jit_dot", path)` でCFGのDOT出力。いずれもdumpを自動有効化。
|
||||||
|
- イベントは `phase` フィールドで区別(lower/execute)。`jit_events_path` でJSONL出力先を指定可能。
|
||||||
81
examples/any_helpers_demo.nyash
Normal file
81
examples/any_helpers_demo.nyash
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
// ANY Helpers Demo - 型を問わない統一的な操作
|
||||||
|
// ANYヘルパーは Array, String, Map すべてに対応する万能メソッド!
|
||||||
|
|
||||||
|
static box Main {
|
||||||
|
main() {
|
||||||
|
local console
|
||||||
|
console = new ConsoleBox()
|
||||||
|
|
||||||
|
console.log("=== ANY Helpers Demo ===")
|
||||||
|
console.log("")
|
||||||
|
|
||||||
|
// 1. length() - どんなBoxでも長さ/サイズを取得
|
||||||
|
console.log("1) length() helper:")
|
||||||
|
|
||||||
|
local arr
|
||||||
|
arr = new ArrayBox()
|
||||||
|
arr.push("cat")
|
||||||
|
arr.push("dog")
|
||||||
|
console.log("Array length: " + arr.length()) // 2
|
||||||
|
|
||||||
|
local str
|
||||||
|
str = new StringBox("Hello")
|
||||||
|
console.log("String length: " + str.length()) // 5
|
||||||
|
|
||||||
|
local map
|
||||||
|
map = new MapBox()
|
||||||
|
map.set("name", "Nyash")
|
||||||
|
map.set("version", "1.0")
|
||||||
|
console.log("Map size: " + map.length()) // 2
|
||||||
|
console.log("")
|
||||||
|
|
||||||
|
// 2. isEmpty() - どんなBoxでも空かチェック
|
||||||
|
console.log("2) isEmpty() helper:")
|
||||||
|
|
||||||
|
local emptyArr
|
||||||
|
emptyArr = new ArrayBox()
|
||||||
|
console.log("Empty array: " + emptyArr.isEmpty()) // true
|
||||||
|
arr.push("fish")
|
||||||
|
console.log("Non-empty array: " + arr.isEmpty()) // false
|
||||||
|
|
||||||
|
local emptyStr
|
||||||
|
emptyStr = new StringBox("")
|
||||||
|
console.log("Empty string: " + emptyStr.isEmpty()) // true
|
||||||
|
local fullStr
|
||||||
|
fullStr = new StringBox("Nyash")
|
||||||
|
console.log("Non-empty string: " + fullStr.isEmpty()) // false
|
||||||
|
|
||||||
|
local emptyMap
|
||||||
|
emptyMap = new MapBox()
|
||||||
|
console.log("Empty map: " + emptyMap.isEmpty()) // true
|
||||||
|
console.log("Non-empty map: " + map.isEmpty()) // false
|
||||||
|
console.log("")
|
||||||
|
|
||||||
|
// 3. 実用例:型を気にせず処理
|
||||||
|
console.log("3) Practical example - type-agnostic processing:")
|
||||||
|
|
||||||
|
local items
|
||||||
|
items = new ArrayBox()
|
||||||
|
items.push(arr) // Array
|
||||||
|
items.push(str) // String
|
||||||
|
items.push(map) // Map
|
||||||
|
|
||||||
|
// どんな型でも統一的に処理できる!
|
||||||
|
local i
|
||||||
|
i = 0
|
||||||
|
loop(i < items.length()) {
|
||||||
|
local item
|
||||||
|
item = items.get(i)
|
||||||
|
console.log("Item " + i + " - length: " + item.length() + ", empty: " + item.isEmpty())
|
||||||
|
i = i + 1
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("")
|
||||||
|
console.log("=== ANYヘルパーの威力 ===")
|
||||||
|
console.log("- 型固有のメソッドを覚える必要なし")
|
||||||
|
console.log("- Array/String/Mapを統一的に扱える")
|
||||||
|
console.log("- Everything is Box 哲学の体現!")
|
||||||
|
|
||||||
|
return "Demo complete!"
|
||||||
|
}
|
||||||
|
}
|
||||||
44
examples/jit_any_helpers_demo.nyash
Normal file
44
examples/jit_any_helpers_demo.nyash
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
// JIT ANY Helpers Demo - JIT内部での統一的な型処理
|
||||||
|
// ANYヘルパーは nyash.any.length_h と nyash.any.is_empty_h として
|
||||||
|
// JITのHostCall経由で Array/String/Map を統一的に扱える!
|
||||||
|
|
||||||
|
box Helper {
|
||||||
|
birth() {}
|
||||||
|
|
||||||
|
// このメソッドがJITコンパイルされると、内部的にANYヘルパーが使われる
|
||||||
|
getInfo(obj) {
|
||||||
|
// JIT内部では nyash.any.length_h が呼ばれる
|
||||||
|
local len
|
||||||
|
len = obj.length() // Array/Stringなら動作
|
||||||
|
|
||||||
|
// JIT内部では nyash.any.is_empty_h が呼ばれる
|
||||||
|
local empty
|
||||||
|
empty = obj.isEmpty() // Array/Stringなら動作
|
||||||
|
|
||||||
|
return len
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static box Main {
|
||||||
|
main() {
|
||||||
|
local H
|
||||||
|
H = new Helper()
|
||||||
|
|
||||||
|
// Array での使用
|
||||||
|
local arr
|
||||||
|
arr = new ArrayBox()
|
||||||
|
arr.push("cat")
|
||||||
|
arr.push("dog")
|
||||||
|
print("Array info: " + H.getInfo(arr)) // 2
|
||||||
|
|
||||||
|
// String での使用
|
||||||
|
local str
|
||||||
|
str = new StringBox("Hello")
|
||||||
|
print("String info: " + H.getInfo(str)) // 5
|
||||||
|
|
||||||
|
// 注意: MapBoxは現在のインタープリターではlength()未実装
|
||||||
|
// JITでは nyash.any.length_h が Map.size() にマップされる
|
||||||
|
|
||||||
|
return "Done"
|
||||||
|
}
|
||||||
|
}
|
||||||
44
examples/jit_policy_whitelist_demo.nyash
Normal file
44
examples/jit_policy_whitelist_demo.nyash
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
// JitPolicy whitelist demo with DebugConfigBox event setup.
|
||||||
|
// Goal: show fallback on read_only, then allow after whitelist.
|
||||||
|
// Run (Cranelift enabled recommended):
|
||||||
|
// cargo build --release -j32 --features cranelift-jit
|
||||||
|
// NYASH_JIT_HOSTCALL=1 ./target/release/nyash --backend vm examples/jit_policy_whitelist_demo.nyash
|
||||||
|
// Check events file:
|
||||||
|
// cat policy_events.jsonl # contains phase=lower/execute decisions
|
||||||
|
|
||||||
|
box Helper {
|
||||||
|
birth() {}
|
||||||
|
add(a) {
|
||||||
|
a.push(1)
|
||||||
|
return a.length()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static box Main {
|
||||||
|
main() {
|
||||||
|
// Enable compile-time events and output path via DebugConfigBox
|
||||||
|
local D
|
||||||
|
D = new DebugConfigBox()
|
||||||
|
D = D.setFlag("jit_events_compile", true)
|
||||||
|
D = D.setFlag("jit_events_runtime", true)
|
||||||
|
D = D.setPath("jit_events_path", "policy_events.jsonl")
|
||||||
|
D.apply()
|
||||||
|
|
||||||
|
// Prepare JIT policy: read_only first
|
||||||
|
local P
|
||||||
|
P = new JitPolicyBox()
|
||||||
|
P.set("read_only", true)
|
||||||
|
|
||||||
|
// Prepare data and helper
|
||||||
|
local H, A
|
||||||
|
H = new Helper()
|
||||||
|
A = new ArrayBox()
|
||||||
|
|
||||||
|
// 1st call: should fallback by policy (mutating denied)
|
||||||
|
H.add(A)
|
||||||
|
|
||||||
|
// Whitelist push_h and retry (expect allow)
|
||||||
|
P.addWhitelist("nyash.array.push_h")
|
||||||
|
return H.add(A)
|
||||||
|
}
|
||||||
|
}
|
||||||
3
hh_events.jsonl
Normal file
3
hh_events.jsonl
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
{"kind":"hostcall","function":"<jit>","arg_types":["Handle","I64","I64"],"argc":3,"decision":"fallback","id":"nyash.map.set_h","phase":"lower","reason":"receiver_not_param"}
|
||||||
|
{"kind":"hostcall","function":"<jit>","arg_types":["Handle","Handle"],"argc":2,"decision":"allow","id":"nyash.map.get_hh","phase":"lower","reason":"sig_ok"}
|
||||||
|
{"kind":"hostcall","function":"<jit>","arg_types":["Handle","Handle"],"argc":2,"decision":"allow","id":"nyash.map.get_hh"}
|
||||||
4
out.dot
Normal file
4
out.dot
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
digraph "Helper.getv/2" {
|
||||||
|
node [shape=box, fontsize=10];
|
||||||
|
n2 [label="bb2\nENTRY"];
|
||||||
|
}
|
||||||
@ -559,6 +559,10 @@ impl VM {
|
|||||||
self.current_function = Some(function.signature.name.clone());
|
self.current_function = Some(function.signature.name.clone());
|
||||||
// Phase 10_a: JIT profiling (function entry)
|
// Phase 10_a: JIT profiling (function entry)
|
||||||
if let Some(jm) = &mut self.jit_manager {
|
if let Some(jm) = &mut self.jit_manager {
|
||||||
|
// Allow threshold to react to env updates (e.g., DebugConfigBox.apply at runtime)
|
||||||
|
if let Ok(s) = std::env::var("NYASH_JIT_THRESHOLD") {
|
||||||
|
if let Ok(t) = s.parse::<u32>() { if t > 0 { jm.set_threshold(t); } }
|
||||||
|
}
|
||||||
jm.record_entry(&function.signature.name);
|
jm.record_entry(&function.signature.name);
|
||||||
// Try compile if hot (no-op for now, returns fake handle)
|
// Try compile if hot (no-op for now, returns fake handle)
|
||||||
let _ = jm.maybe_compile(&function.signature.name, function);
|
let _ = jm.maybe_compile(&function.signature.name, function);
|
||||||
|
|||||||
@ -6,10 +6,13 @@ pub struct DebugConfigBox {
|
|||||||
pub base: BoxBase,
|
pub base: BoxBase,
|
||||||
// toggles/paths (staged until apply())
|
// toggles/paths (staged until apply())
|
||||||
pub jit_events: bool,
|
pub jit_events: bool,
|
||||||
|
pub jit_events_compile: bool,
|
||||||
|
pub jit_events_runtime: bool,
|
||||||
pub jit_stats: bool,
|
pub jit_stats: bool,
|
||||||
pub jit_stats_json: bool,
|
pub jit_stats_json: bool,
|
||||||
pub jit_dump: bool,
|
pub jit_dump: bool,
|
||||||
pub jit_dot_path: Option<String>,
|
pub jit_dot_path: Option<String>,
|
||||||
|
pub jit_events_path: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DebugConfigBox {
|
impl DebugConfigBox {
|
||||||
@ -17,16 +20,21 @@ impl DebugConfigBox {
|
|||||||
Self {
|
Self {
|
||||||
base: BoxBase::new(),
|
base: BoxBase::new(),
|
||||||
jit_events: std::env::var("NYASH_JIT_EVENTS").ok().as_deref() == Some("1"),
|
jit_events: std::env::var("NYASH_JIT_EVENTS").ok().as_deref() == Some("1"),
|
||||||
|
jit_events_compile: std::env::var("NYASH_JIT_EVENTS_COMPILE").ok().as_deref() == Some("1"),
|
||||||
|
jit_events_runtime: std::env::var("NYASH_JIT_EVENTS_RUNTIME").ok().as_deref() == Some("1"),
|
||||||
jit_stats: std::env::var("NYASH_JIT_STATS").ok().as_deref() == Some("1"),
|
jit_stats: std::env::var("NYASH_JIT_STATS").ok().as_deref() == Some("1"),
|
||||||
jit_stats_json: std::env::var("NYASH_JIT_STATS_JSON").ok().as_deref() == Some("1"),
|
jit_stats_json: std::env::var("NYASH_JIT_STATS_JSON").ok().as_deref() == Some("1"),
|
||||||
jit_dump: std::env::var("NYASH_JIT_DUMP").ok().as_deref() == Some("1"),
|
jit_dump: std::env::var("NYASH_JIT_DUMP").ok().as_deref() == Some("1"),
|
||||||
jit_dot_path: std::env::var("NYASH_JIT_DOT").ok().filter(|s| !s.is_empty()),
|
jit_dot_path: std::env::var("NYASH_JIT_DOT").ok().filter(|s| !s.is_empty()),
|
||||||
|
jit_events_path: std::env::var("NYASH_JIT_EVENTS_PATH").ok().filter(|s| !s.is_empty()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_flag(&mut self, name: &str, on: bool) -> Box<dyn NyashBox> {
|
pub fn set_flag(&mut self, name: &str, on: bool) -> Box<dyn NyashBox> {
|
||||||
match name {
|
match name {
|
||||||
"jit_events" => self.jit_events = on,
|
"jit_events" => self.jit_events = on,
|
||||||
|
"jit_events_compile" => self.jit_events_compile = on,
|
||||||
|
"jit_events_runtime" => self.jit_events_runtime = on,
|
||||||
"jit_stats" => self.jit_stats = on,
|
"jit_stats" => self.jit_stats = on,
|
||||||
"jit_stats_json" => self.jit_stats_json = on,
|
"jit_stats_json" => self.jit_stats_json = on,
|
||||||
"jit_dump" => self.jit_dump = on,
|
"jit_dump" => self.jit_dump = on,
|
||||||
@ -38,6 +46,7 @@ impl DebugConfigBox {
|
|||||||
pub fn set_path(&mut self, name: &str, value: &str) -> Box<dyn NyashBox> {
|
pub fn set_path(&mut self, name: &str, value: &str) -> Box<dyn NyashBox> {
|
||||||
match name {
|
match name {
|
||||||
"jit_dot" | "jit_dot_path" => self.jit_dot_path = Some(value.to_string()),
|
"jit_dot" | "jit_dot_path" => self.jit_dot_path = Some(value.to_string()),
|
||||||
|
"jit_events_path" => self.jit_events_path = Some(value.to_string()),
|
||||||
_ => return Box::new(StringBox::new(format!("Unknown path: {}", name)))
|
_ => return Box::new(StringBox::new(format!("Unknown path: {}", name)))
|
||||||
}
|
}
|
||||||
Box::new(VoidBox::new())
|
Box::new(VoidBox::new())
|
||||||
@ -46,6 +55,8 @@ impl DebugConfigBox {
|
|||||||
pub fn get_flag(&self, name: &str) -> Box<dyn NyashBox> {
|
pub fn get_flag(&self, name: &str) -> Box<dyn NyashBox> {
|
||||||
let v = match name {
|
let v = match name {
|
||||||
"jit_events" => self.jit_events,
|
"jit_events" => self.jit_events,
|
||||||
|
"jit_events_compile" => self.jit_events_compile,
|
||||||
|
"jit_events_runtime" => self.jit_events_runtime,
|
||||||
"jit_stats" => self.jit_stats,
|
"jit_stats" => self.jit_stats,
|
||||||
"jit_stats_json" => self.jit_stats_json,
|
"jit_stats_json" => self.jit_stats_json,
|
||||||
"jit_dump" => self.jit_dump,
|
"jit_dump" => self.jit_dump,
|
||||||
@ -57,6 +68,7 @@ impl DebugConfigBox {
|
|||||||
pub fn get_path(&self, name: &str) -> Box<dyn NyashBox> {
|
pub fn get_path(&self, name: &str) -> Box<dyn NyashBox> {
|
||||||
let v = match name {
|
let v = match name {
|
||||||
"jit_dot" | "jit_dot_path" => self.jit_dot_path.clone().unwrap_or_default(),
|
"jit_dot" | "jit_dot_path" => self.jit_dot_path.clone().unwrap_or_default(),
|
||||||
|
"jit_events_path" => self.jit_events_path.clone().unwrap_or_default(),
|
||||||
_ => String::new(),
|
_ => String::new(),
|
||||||
};
|
};
|
||||||
Box::new(StringBox::new(v))
|
Box::new(StringBox::new(v))
|
||||||
@ -65,18 +77,31 @@ impl DebugConfigBox {
|
|||||||
pub fn apply(&self) -> Box<dyn NyashBox> {
|
pub fn apply(&self) -> Box<dyn NyashBox> {
|
||||||
let setb = |k: &str, v: bool| { if v { std::env::set_var(k, "1"); } else { std::env::remove_var(k); } };
|
let setb = |k: &str, v: bool| { if v { std::env::set_var(k, "1"); } else { std::env::remove_var(k); } };
|
||||||
setb("NYASH_JIT_EVENTS", self.jit_events);
|
setb("NYASH_JIT_EVENTS", self.jit_events);
|
||||||
|
setb("NYASH_JIT_EVENTS_COMPILE", self.jit_events_compile);
|
||||||
|
setb("NYASH_JIT_EVENTS_RUNTIME", self.jit_events_runtime);
|
||||||
setb("NYASH_JIT_STATS", self.jit_stats);
|
setb("NYASH_JIT_STATS", self.jit_stats);
|
||||||
setb("NYASH_JIT_STATS_JSON", self.jit_stats_json);
|
setb("NYASH_JIT_STATS_JSON", self.jit_stats_json);
|
||||||
setb("NYASH_JIT_DUMP", self.jit_dump);
|
setb("NYASH_JIT_DUMP", self.jit_dump);
|
||||||
if let Some(p) = &self.jit_dot_path { std::env::set_var("NYASH_JIT_DOT", p); }
|
if let Some(p) = &self.jit_dot_path { std::env::set_var("NYASH_JIT_DOT", p); }
|
||||||
|
else { std::env::remove_var("NYASH_JIT_DOT"); }
|
||||||
|
if let Some(p) = &self.jit_events_path { std::env::set_var("NYASH_JIT_EVENTS_PATH", p); }
|
||||||
|
else { std::env::remove_var("NYASH_JIT_EVENTS_PATH"); }
|
||||||
|
// If any events are enabled and threshold is not set, default to 1 so lowering runs early
|
||||||
|
if (self.jit_events || self.jit_events_compile || self.jit_events_runtime)
|
||||||
|
&& std::env::var("NYASH_JIT_THRESHOLD").is_err()
|
||||||
|
{
|
||||||
|
std::env::set_var("NYASH_JIT_THRESHOLD", "1");
|
||||||
|
}
|
||||||
Box::new(VoidBox::new())
|
Box::new(VoidBox::new())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn summary(&self) -> Box<dyn NyashBox> {
|
pub fn summary(&self) -> Box<dyn NyashBox> {
|
||||||
let s = format!(
|
let s = format!(
|
||||||
"jit_events={} jit_stats={} jit_stats_json={} jit_dump={} jit_dot={}",
|
"jit_events={} jit_events_compile={} jit_events_runtime={} jit_stats={} jit_stats_json={} jit_dump={} jit_dot={} jit_events_path={}",
|
||||||
self.jit_events, self.jit_stats, self.jit_stats_json, self.jit_dump,
|
self.jit_events, self.jit_events_compile, self.jit_events_runtime,
|
||||||
self.jit_dot_path.clone().unwrap_or_else(|| "<none>".to_string())
|
self.jit_stats, self.jit_stats_json, self.jit_dump,
|
||||||
|
self.jit_dot_path.clone().unwrap_or_else(|| "<none>".to_string()),
|
||||||
|
self.jit_events_path.clone().unwrap_or_else(|| "<none>".to_string())
|
||||||
);
|
);
|
||||||
Box::new(StringBox::new(s))
|
Box::new(StringBox::new(s))
|
||||||
}
|
}
|
||||||
|
|||||||
39
src/cli.rs
39
src/cli.rs
@ -31,6 +31,10 @@ pub struct CliConfig {
|
|||||||
pub jit_stats: bool,
|
pub jit_stats: bool,
|
||||||
pub jit_stats_json: bool,
|
pub jit_stats_json: bool,
|
||||||
pub jit_dump: bool,
|
pub jit_dump: bool,
|
||||||
|
pub jit_events: bool,
|
||||||
|
pub jit_events_compile: bool,
|
||||||
|
pub jit_events_runtime: bool,
|
||||||
|
pub jit_events_path: Option<String>,
|
||||||
pub jit_threshold: Option<u32>,
|
pub jit_threshold: Option<u32>,
|
||||||
pub jit_phi_min: bool,
|
pub jit_phi_min: bool,
|
||||||
pub jit_hostcall: bool,
|
pub jit_hostcall: bool,
|
||||||
@ -186,6 +190,30 @@ impl CliConfig {
|
|||||||
.help("Dump JIT lowering summary (NYASH_JIT_DUMP=1)")
|
.help("Dump JIT lowering summary (NYASH_JIT_DUMP=1)")
|
||||||
.action(clap::ArgAction::SetTrue)
|
.action(clap::ArgAction::SetTrue)
|
||||||
)
|
)
|
||||||
|
.arg(
|
||||||
|
Arg::new("jit-events")
|
||||||
|
.long("jit-events")
|
||||||
|
.help("Emit JIT events as JSONL (NYASH_JIT_EVENTS=1)")
|
||||||
|
.action(clap::ArgAction::SetTrue)
|
||||||
|
)
|
||||||
|
.arg(
|
||||||
|
Arg::new("jit-events-compile")
|
||||||
|
.long("jit-events-compile")
|
||||||
|
.help("Emit compile-time (lower) JIT events (NYASH_JIT_EVENTS_COMPILE=1)")
|
||||||
|
.action(clap::ArgAction::SetTrue)
|
||||||
|
)
|
||||||
|
.arg(
|
||||||
|
Arg::new("jit-events-runtime")
|
||||||
|
.long("jit-events-runtime")
|
||||||
|
.help("Emit runtime JIT events (NYASH_JIT_EVENTS_RUNTIME=1)")
|
||||||
|
.action(clap::ArgAction::SetTrue)
|
||||||
|
)
|
||||||
|
.arg(
|
||||||
|
Arg::new("jit-events-path")
|
||||||
|
.long("jit-events-path")
|
||||||
|
.value_name("FILE")
|
||||||
|
.help("Write JIT events JSONL to file (NYASH_JIT_EVENTS_PATH)")
|
||||||
|
)
|
||||||
.arg(
|
.arg(
|
||||||
Arg::new("jit-threshold")
|
Arg::new("jit-threshold")
|
||||||
.long("jit-threshold")
|
.long("jit-threshold")
|
||||||
@ -265,6 +293,10 @@ impl CliConfig {
|
|||||||
jit_stats: matches.get_flag("jit-stats"),
|
jit_stats: matches.get_flag("jit-stats"),
|
||||||
jit_stats_json: matches.get_flag("jit-stats-json"),
|
jit_stats_json: matches.get_flag("jit-stats-json"),
|
||||||
jit_dump: matches.get_flag("jit-dump"),
|
jit_dump: matches.get_flag("jit-dump"),
|
||||||
|
jit_events: matches.get_flag("jit-events"),
|
||||||
|
jit_events_compile: matches.get_flag("jit-events-compile"),
|
||||||
|
jit_events_runtime: matches.get_flag("jit-events-runtime"),
|
||||||
|
jit_events_path: matches.get_one::<String>("jit-events-path").cloned(),
|
||||||
jit_threshold: matches.get_one::<String>("jit-threshold").and_then(|s| s.parse::<u32>().ok()),
|
jit_threshold: matches.get_one::<String>("jit-threshold").and_then(|s| s.parse::<u32>().ok()),
|
||||||
jit_phi_min: matches.get_flag("jit-phi-min"),
|
jit_phi_min: matches.get_flag("jit-phi-min"),
|
||||||
jit_hostcall: matches.get_flag("jit-hostcall"),
|
jit_hostcall: matches.get_flag("jit-hostcall"),
|
||||||
@ -323,12 +355,19 @@ mod tests {
|
|||||||
jit_stats: false,
|
jit_stats: false,
|
||||||
jit_stats_json: false,
|
jit_stats_json: false,
|
||||||
jit_dump: false,
|
jit_dump: false,
|
||||||
|
jit_events: false,
|
||||||
|
jit_events_compile: false,
|
||||||
|
jit_events_runtime: false,
|
||||||
|
jit_events_path: None,
|
||||||
jit_threshold: None,
|
jit_threshold: None,
|
||||||
jit_phi_min: false,
|
jit_phi_min: false,
|
||||||
jit_hostcall: false,
|
jit_hostcall: false,
|
||||||
jit_handle_debug: false,
|
jit_handle_debug: false,
|
||||||
jit_native_f64: false,
|
jit_native_f64: false,
|
||||||
jit_native_bool: false,
|
jit_native_bool: false,
|
||||||
|
emit_cfg: None,
|
||||||
|
jit_only: false,
|
||||||
|
jit_direct: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
assert_eq!(config.backend, "interpreter");
|
assert_eq!(config.backend, "interpreter");
|
||||||
|
|||||||
@ -24,11 +24,15 @@ impl JitConfig {
|
|||||||
pub fn from_env() -> Self {
|
pub fn from_env() -> Self {
|
||||||
let getb = |k: &str| std::env::var(k).ok().as_deref() == Some("1");
|
let getb = |k: &str| std::env::var(k).ok().as_deref() == Some("1");
|
||||||
let threshold = std::env::var("NYASH_JIT_THRESHOLD").ok().and_then(|s| s.parse::<u32>().ok());
|
let threshold = std::env::var("NYASH_JIT_THRESHOLD").ok().and_then(|s| s.parse::<u32>().ok());
|
||||||
|
// Respect explicit dump flag, but also treat a non-empty NYASH_JIT_DOT path
|
||||||
|
// as an implicit request to enable dump (so Box/CLI/env stay consistent).
|
||||||
|
let dump_flag = getb("NYASH_JIT_DUMP")
|
||||||
|
|| std::env::var("NYASH_JIT_DOT").ok().map(|s| !s.is_empty()).unwrap_or(false);
|
||||||
Self {
|
Self {
|
||||||
exec: getb("NYASH_JIT_EXEC"),
|
exec: getb("NYASH_JIT_EXEC"),
|
||||||
stats: getb("NYASH_JIT_STATS"),
|
stats: getb("NYASH_JIT_STATS"),
|
||||||
stats_json: getb("NYASH_JIT_STATS_JSON"),
|
stats_json: getb("NYASH_JIT_STATS_JSON"),
|
||||||
dump: getb("NYASH_JIT_DUMP"),
|
dump: dump_flag,
|
||||||
threshold,
|
threshold,
|
||||||
phi_min: getb("NYASH_JIT_PHI_MIN"),
|
phi_min: getb("NYASH_JIT_PHI_MIN"),
|
||||||
hostcall: getb("NYASH_JIT_HOSTCALL"),
|
hostcall: getb("NYASH_JIT_HOSTCALL"),
|
||||||
|
|||||||
@ -89,6 +89,14 @@ impl JitEngine {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// If lowering left any unsupported instructions, do not register a closure.
|
||||||
|
// This preserves VM semantics until coverage is complete for the function.
|
||||||
|
if lower.unsupported > 0 {
|
||||||
|
if std::env::var("NYASH_JIT_STATS").ok().as_deref() == Some("1") || cfg_now.dump {
|
||||||
|
eprintln!("[JIT] skip compile for {}: unsupported={} (>0)", func_name, lower.unsupported);
|
||||||
|
}
|
||||||
|
return None;
|
||||||
|
}
|
||||||
// Create a handle and register an executable closure if available
|
// Create a handle and register an executable closure if available
|
||||||
#[cfg(feature = "cranelift-jit")]
|
#[cfg(feature = "cranelift-jit")]
|
||||||
{
|
{
|
||||||
|
|||||||
@ -6,11 +6,20 @@
|
|||||||
|
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
|
|
||||||
fn should_emit() -> bool {
|
fn base_emit_enabled() -> bool {
|
||||||
std::env::var("NYASH_JIT_EVENTS").ok().as_deref() == Some("1")
|
std::env::var("NYASH_JIT_EVENTS").ok().as_deref() == Some("1")
|
||||||
|| std::env::var("NYASH_JIT_EVENTS_PATH").is_ok()
|
|| std::env::var("NYASH_JIT_EVENTS_PATH").is_ok()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn should_emit_lower() -> bool {
|
||||||
|
// Compile-phase events are opt-in to avoid noisy logs by default.
|
||||||
|
std::env::var("NYASH_JIT_EVENTS_COMPILE").ok().as_deref() == Some("1")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn should_emit_runtime() -> bool {
|
||||||
|
base_emit_enabled() || std::env::var("NYASH_JIT_EVENTS_RUNTIME").ok().as_deref() == Some("1")
|
||||||
|
}
|
||||||
|
|
||||||
fn write_line(s: &str) {
|
fn write_line(s: &str) {
|
||||||
if let Ok(path) = std::env::var("NYASH_JIT_EVENTS_PATH") {
|
if let Ok(path) = std::env::var("NYASH_JIT_EVENTS_PATH") {
|
||||||
let _ = std::fs::OpenOptions::new().create(true).append(true).open(path).and_then(|mut f| {
|
let _ = std::fs::OpenOptions::new().create(true).append(true).open(path).and_then(|mut f| {
|
||||||
@ -35,8 +44,26 @@ struct Event<'a, T: Serialize> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn emit<T: Serialize>(kind: &str, function: &str, handle: Option<u64>, ms: Option<u128>, extra: T) {
|
pub fn emit<T: Serialize>(kind: &str, function: &str, handle: Option<u64>, ms: Option<u128>, extra: T) {
|
||||||
if !should_emit() { return; }
|
if !base_emit_enabled() { return; }
|
||||||
let ev = Event { kind, function, handle, ms, extra };
|
let ev = Event { kind, function, handle, ms, extra };
|
||||||
if let Ok(s) = serde_json::to_string(&ev) { write_line(&s); }
|
if let Ok(s) = serde_json::to_string(&ev) { write_line(&s); }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn emit_any(kind: &str, function: &str, handle: Option<u64>, ms: Option<u128>, extra: serde_json::Value) {
|
||||||
|
let ev = Event { kind, function, handle, ms, extra };
|
||||||
|
if let Ok(s) = serde_json::to_string(&ev) { write_line(&s); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Emit an event during lowering (compile-time planning). Adds phase="lower".
|
||||||
|
pub fn emit_lower(mut extra: serde_json::Value, kind: &str, function: &str) {
|
||||||
|
if !should_emit_lower() { return; }
|
||||||
|
if let serde_json::Value::Object(ref mut map) = extra { map.insert("phase".into(), serde_json::Value::String("lower".into())); }
|
||||||
|
emit_any(kind, function, None, None, extra);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Emit an event during runtime execution. Adds phase="execute".
|
||||||
|
pub fn emit_runtime(mut extra: serde_json::Value, kind: &str, function: &str) {
|
||||||
|
if !should_emit_runtime() { return; }
|
||||||
|
if let serde_json::Value::Object(ref mut map) = extra { map.insert("phase".into(), serde_json::Value::String("execute".into())); }
|
||||||
|
emit_any(kind, function, None, None, extra);
|
||||||
|
}
|
||||||
|
|||||||
36
src/jit/extern/collections.rs
vendored
36
src/jit/extern/collections.rs
vendored
@ -66,18 +66,18 @@ pub fn array_set(args: &[VMValue]) -> VMValue {
|
|||||||
if crate::jit::policy::current().read_only &&
|
if crate::jit::policy::current().read_only &&
|
||||||
!crate::jit::policy::current().hostcall_whitelist.iter().any(|s| s == SYM_ARRAY_SET)
|
!crate::jit::policy::current().hostcall_whitelist.iter().any(|s| s == SYM_ARRAY_SET)
|
||||||
{
|
{
|
||||||
crate::jit::events::emit(
|
crate::jit::events::emit_runtime(
|
||||||
"hostcall", "<jit>", None, None,
|
serde_json::json!({"id": SYM_ARRAY_SET, "decision":"fallback", "reason":"policy_denied_mutating"}),
|
||||||
serde_json::json!({"id": SYM_ARRAY_SET, "decision":"fallback", "reason":"policy_denied_mutating"})
|
"hostcall", "<jit>"
|
||||||
);
|
);
|
||||||
return VMValue::Integer(0);
|
return VMValue::Integer(0);
|
||||||
}
|
}
|
||||||
if let (Some(arr), Some(VMValue::Integer(idx)), Some(value)) = (as_array(args), args.get(1), args.get(2)) {
|
if let (Some(arr), Some(VMValue::Integer(idx)), Some(value)) = (as_array(args), args.get(1), args.get(2)) {
|
||||||
let val_box: Box<dyn NyashBox> = value.to_nyash_box();
|
let val_box: Box<dyn NyashBox> = value.to_nyash_box();
|
||||||
let res = arr.set(Box::new(IntegerBox::new(*idx)), val_box);
|
let res = arr.set(Box::new(IntegerBox::new(*idx)), val_box);
|
||||||
crate::jit::events::emit(
|
crate::jit::events::emit_runtime(
|
||||||
"hostcall", "<jit>", None, None,
|
serde_json::json!({"id": SYM_ARRAY_SET, "decision":"allow", "argc":3, "arg_types":["Handle","I64","Handle"]}),
|
||||||
serde_json::json!({"id": SYM_ARRAY_SET, "decision":"allow", "argc":3, "arg_types":["Handle","I64","Handle"]})
|
"hostcall", "<jit>"
|
||||||
);
|
);
|
||||||
return VMValue::from_nyash_box(res);
|
return VMValue::from_nyash_box(res);
|
||||||
}
|
}
|
||||||
@ -88,18 +88,18 @@ pub fn array_push(args: &[VMValue]) -> VMValue {
|
|||||||
if crate::jit::policy::current().read_only &&
|
if crate::jit::policy::current().read_only &&
|
||||||
!crate::jit::policy::current().hostcall_whitelist.iter().any(|s| s == SYM_ARRAY_PUSH)
|
!crate::jit::policy::current().hostcall_whitelist.iter().any(|s| s == SYM_ARRAY_PUSH)
|
||||||
{
|
{
|
||||||
crate::jit::events::emit(
|
crate::jit::events::emit_runtime(
|
||||||
"hostcall", "<jit>", None, None,
|
serde_json::json!({"id": SYM_ARRAY_PUSH, "decision":"fallback", "reason":"policy_denied_mutating"}),
|
||||||
serde_json::json!({"id": SYM_ARRAY_PUSH, "decision":"fallback", "reason":"policy_denied_mutating"})
|
"hostcall", "<jit>"
|
||||||
);
|
);
|
||||||
return VMValue::Integer(0);
|
return VMValue::Integer(0);
|
||||||
}
|
}
|
||||||
if let (Some(arr), Some(value)) = (as_array(args), args.get(1)) {
|
if let (Some(arr), Some(value)) = (as_array(args), args.get(1)) {
|
||||||
let val_box: Box<dyn NyashBox> = value.to_nyash_box();
|
let val_box: Box<dyn NyashBox> = value.to_nyash_box();
|
||||||
let res = arr.push(val_box);
|
let res = arr.push(val_box);
|
||||||
crate::jit::events::emit(
|
crate::jit::events::emit_runtime(
|
||||||
"hostcall", "<jit>", None, None,
|
serde_json::json!({"id": SYM_ARRAY_PUSH, "decision":"allow", "argc":2, "arg_types":["Handle","Handle"]}),
|
||||||
serde_json::json!({"id": SYM_ARRAY_PUSH, "decision":"allow", "argc":2, "arg_types":["Handle","Handle"]})
|
"hostcall", "<jit>"
|
||||||
);
|
);
|
||||||
return VMValue::from_nyash_box(res);
|
return VMValue::from_nyash_box(res);
|
||||||
}
|
}
|
||||||
@ -118,9 +118,9 @@ pub fn map_set(args: &[VMValue]) -> VMValue {
|
|||||||
if crate::jit::policy::current().read_only &&
|
if crate::jit::policy::current().read_only &&
|
||||||
!crate::jit::policy::current().hostcall_whitelist.iter().any(|s| s == SYM_MAP_SET)
|
!crate::jit::policy::current().hostcall_whitelist.iter().any(|s| s == SYM_MAP_SET)
|
||||||
{
|
{
|
||||||
crate::jit::events::emit(
|
crate::jit::events::emit_runtime(
|
||||||
"hostcall", "<jit>", None, None,
|
serde_json::json!({"id": SYM_MAP_SET, "decision":"fallback", "reason":"policy_denied_mutating"}),
|
||||||
serde_json::json!({"id": SYM_MAP_SET, "decision":"fallback", "reason":"policy_denied_mutating"})
|
"hostcall", "<jit>"
|
||||||
);
|
);
|
||||||
return VMValue::Integer(0);
|
return VMValue::Integer(0);
|
||||||
}
|
}
|
||||||
@ -128,9 +128,9 @@ pub fn map_set(args: &[VMValue]) -> VMValue {
|
|||||||
let key_box: Box<dyn NyashBox> = key.to_nyash_box();
|
let key_box: Box<dyn NyashBox> = key.to_nyash_box();
|
||||||
let val_box: Box<dyn NyashBox> = value.to_nyash_box();
|
let val_box: Box<dyn NyashBox> = value.to_nyash_box();
|
||||||
let out = map.set(key_box, val_box);
|
let out = map.set(key_box, val_box);
|
||||||
crate::jit::events::emit(
|
crate::jit::events::emit_runtime(
|
||||||
"hostcall", "<jit>", None, None,
|
serde_json::json!({"id": SYM_MAP_SET, "decision":"allow", "argc":3, "arg_types":["Handle","Handle","Handle"]}),
|
||||||
serde_json::json!({"id": SYM_MAP_SET, "decision":"allow", "argc":3, "arg_types":["Handle","Handle","Handle"]})
|
"hostcall", "<jit>"
|
||||||
);
|
);
|
||||||
return VMValue::from_nyash_box(out);
|
return VMValue::from_nyash_box(out);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -23,9 +23,14 @@ fn ensure_default() {
|
|||||||
let mut r = Registry::default();
|
let mut r = Registry::default();
|
||||||
// Read-only defaults
|
// Read-only defaults
|
||||||
for s in [
|
for s in [
|
||||||
"nyash.array.len_h", "nyash.any.length_h", "nyash.any.is_empty_h",
|
"nyash.array.len_h",
|
||||||
"nyash.map.size_h", "nyash.map.get_h", "nyash.string.charCodeAt_h",
|
"nyash.any.length_h",
|
||||||
"nyash.array.get_h"
|
"nyash.any.is_empty_h",
|
||||||
|
"nyash.map.size_h",
|
||||||
|
"nyash.map.get_h",
|
||||||
|
"nyash.map.has_h",
|
||||||
|
"nyash.string.charCodeAt_h",
|
||||||
|
"nyash.array.get_h",
|
||||||
] { r.ro.insert(s.to_string()); }
|
] { r.ro.insert(s.to_string()); }
|
||||||
// Mutating defaults
|
// Mutating defaults
|
||||||
for s in [
|
for s in [
|
||||||
@ -45,6 +50,13 @@ fn ensure_default() {
|
|||||||
r.sig.entry("nyash.map.size_h".to_string()).or_default().push(Signature { args: vec![ArgKind::Handle], ret: ArgKind::I64 });
|
r.sig.entry("nyash.map.size_h".to_string()).or_default().push(Signature { args: vec![ArgKind::Handle], ret: ArgKind::I64 });
|
||||||
r.sig.entry("nyash.array.get_h".to_string()).or_default().push(Signature { args: vec![ArgKind::Handle, ArgKind::I64], ret: ArgKind::Handle });
|
r.sig.entry("nyash.array.get_h".to_string()).or_default().push(Signature { args: vec![ArgKind::Handle, ArgKind::I64], ret: ArgKind::Handle });
|
||||||
r.sig.entry("nyash.array.len_h".to_string()).or_default().push(Signature { args: vec![ArgKind::Handle], ret: ArgKind::I64 });
|
r.sig.entry("nyash.array.len_h".to_string()).or_default().push(Signature { args: vec![ArgKind::Handle], ret: ArgKind::I64 });
|
||||||
|
// String helpers
|
||||||
|
r.sig.entry("nyash.string.charCodeAt_h".to_string()).or_default().push(Signature { args: vec![ArgKind::Handle, ArgKind::I64], ret: ArgKind::I64 });
|
||||||
|
// Any helpers (length/is_empty)
|
||||||
|
r.sig.entry("nyash.any.length_h".to_string()).or_default().push(Signature { args: vec![ArgKind::Handle], ret: ArgKind::I64 });
|
||||||
|
r.sig.entry("nyash.any.is_empty_h".to_string()).or_default().push(Signature { args: vec![ArgKind::Handle], ret: ArgKind::I64 });
|
||||||
|
// Map.has(handle, i64) -> i64(0/1)
|
||||||
|
r.sig.entry("nyash.map.has_h".to_string()).or_default().push(Signature { args: vec![ArgKind::Handle, ArgKind::I64], ret: ArgKind::I64 });
|
||||||
let _ = REG.set(RwLock::new(r));
|
let _ = REG.set(RwLock::new(r));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -138,15 +138,15 @@ use cranelift_codegen::ir::InstBuilder;
|
|||||||
#[cfg(feature = "cranelift-jit")]
|
#[cfg(feature = "cranelift-jit")]
|
||||||
extern "C" fn nyash_host_stub0() -> i64 { 0 }
|
extern "C" fn nyash_host_stub0() -> i64 { 0 }
|
||||||
#[cfg(feature = "cranelift-jit")]
|
#[cfg(feature = "cranelift-jit")]
|
||||||
extern "C" fn nyash_math_sin_f64(x: f64) -> f64 { x.sin() }
|
use super::extern_thunks::{ nyash_math_sin_f64, nyash_math_cos_f64, nyash_math_abs_f64, nyash_math_min_f64, nyash_math_max_f64, };
|
||||||
#[cfg(feature = "cranelift-jit")]
|
#[cfg(feature = "cranelift-jit")]
|
||||||
extern "C" fn nyash_math_cos_f64(x: f64) -> f64 { x.cos() }
|
|
||||||
#[cfg(feature = "cranelift-jit")]
|
#[cfg(feature = "cranelift-jit")]
|
||||||
extern "C" fn nyash_math_abs_f64(x: f64) -> f64 { x.abs() }
|
|
||||||
#[cfg(feature = "cranelift-jit")]
|
#[cfg(feature = "cranelift-jit")]
|
||||||
extern "C" fn nyash_math_min_f64(a: f64, b: f64) -> f64 { a.min(b) }
|
|
||||||
#[cfg(feature = "cranelift-jit")]
|
#[cfg(feature = "cranelift-jit")]
|
||||||
extern "C" fn nyash_math_max_f64(a: f64, b: f64) -> f64 { a.max(b) }
|
|
||||||
#[cfg(feature = "cranelift-jit")]
|
#[cfg(feature = "cranelift-jit")]
|
||||||
extern "C" fn nyash_array_len(arr_param_index: i64) -> i64 {
|
extern "C" fn nyash_array_len(arr_param_index: i64) -> i64 {
|
||||||
// Interpret first arg as function param index and fetch from thread-local args
|
// Interpret first arg as function param index and fetch from thread-local args
|
||||||
@ -234,258 +234,18 @@ extern "C" fn nyash_map_size(map_param_index: i64) -> i64 {
|
|||||||
|
|
||||||
// === Handle-based externs (10.7c) ===
|
// === Handle-based externs (10.7c) ===
|
||||||
#[cfg(feature = "cranelift-jit")]
|
#[cfg(feature = "cranelift-jit")]
|
||||||
extern "C" fn nyash_array_len_h(handle: u64) -> i64 {
|
|
||||||
crate::jit::events::emit(
|
|
||||||
"hostcall", "<jit>", None, None,
|
|
||||||
serde_json::json!({"id": crate::jit::r#extern::collections::SYM_ARRAY_LEN_H, "decision":"allow", "argc":1, "arg_types":["Handle"]})
|
|
||||||
);
|
|
||||||
if let Some(obj) = crate::jit::rt::handles::get(handle) {
|
|
||||||
if let Some(arr) = obj.as_any().downcast_ref::<crate::boxes::array::ArrayBox>() {
|
|
||||||
if let Some(ib) = arr.length().as_any().downcast_ref::<crate::box_trait::IntegerBox>() { return ib.value; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
0
|
|
||||||
}
|
|
||||||
#[cfg(feature = "cranelift-jit")]
|
#[cfg(feature = "cranelift-jit")]
|
||||||
extern "C" fn nyash_array_push_h(handle: u64, val: i64) -> i64 {
|
|
||||||
// Policy/Events: classify and decide with whitelist
|
|
||||||
use crate::jit::hostcall_registry::{classify, HostcallKind};
|
|
||||||
let sym = crate::jit::r#extern::collections::SYM_ARRAY_PUSH_H;
|
|
||||||
let pol = crate::jit::policy::current();
|
|
||||||
let wh = pol.hostcall_whitelist;
|
|
||||||
match (classify(sym), pol.read_only && !wh.iter().any(|s| s == sym)) {
|
|
||||||
(HostcallKind::Mutating, true) => {
|
|
||||||
crate::jit::events::emit(
|
|
||||||
"hostcall", "<jit>", None, None,
|
|
||||||
serde_json::json!({"id": sym, "decision":"fallback", "reason":"policy_denied_mutating"})
|
|
||||||
);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
if let Some(obj) = crate::jit::rt::handles::get(handle) {
|
|
||||||
if let Some(arr) = obj.as_any().downcast_ref::<crate::boxes::array::ArrayBox>() {
|
|
||||||
let ib = crate::box_trait::IntegerBox::new(val);
|
|
||||||
let _ = arr.push(Box::new(ib));
|
|
||||||
crate::jit::events::emit(
|
|
||||||
"hostcall", "<jit>", None, None,
|
|
||||||
serde_json::json!({"id": sym, "decision":"allow", "argc":2, "arg_types":["Handle","I64"]})
|
|
||||||
);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
0
|
|
||||||
}
|
|
||||||
#[cfg(feature = "cranelift-jit")]
|
#[cfg(feature = "cranelift-jit")]
|
||||||
extern "C" fn nyash_array_get_h(handle: u64, idx: i64) -> i64 {
|
|
||||||
crate::jit::events::emit(
|
|
||||||
"hostcall", "<jit>", None, None,
|
|
||||||
serde_json::json!({"id": crate::jit::r#extern::collections::SYM_ARRAY_GET_H, "decision":"allow", "argc":2, "arg_types":["Handle","I64"]})
|
|
||||||
);
|
|
||||||
if let Some(obj) = crate::jit::rt::handles::get(handle) {
|
|
||||||
if let Some(arr) = obj.as_any().downcast_ref::<crate::boxes::array::ArrayBox>() {
|
|
||||||
let val = arr.get(Box::new(crate::box_trait::IntegerBox::new(idx)));
|
|
||||||
if let Some(ib) = val.as_any().downcast_ref::<crate::box_trait::IntegerBox>() { return ib.value; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
0
|
|
||||||
}
|
|
||||||
#[cfg(feature = "cranelift-jit")]
|
#[cfg(feature = "cranelift-jit")]
|
||||||
extern "C" fn nyash_array_last_h(handle: u64) -> i64 {
|
|
||||||
if let Some(obj) = crate::jit::rt::handles::get(handle) {
|
|
||||||
if let Some(arr) = obj.as_any().downcast_ref::<crate::boxes::array::ArrayBox>() {
|
|
||||||
// Return last element as i64 if IntegerBox, else 0
|
|
||||||
if let Ok(items) = arr.items.read() {
|
|
||||||
if let Some(last) = items.last() {
|
|
||||||
if let Some(ib) = last.as_any().downcast_ref::<crate::box_trait::IntegerBox>() {
|
|
||||||
return ib.value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
0
|
|
||||||
}
|
|
||||||
#[cfg(feature = "cranelift-jit")]
|
#[cfg(feature = "cranelift-jit")]
|
||||||
extern "C" fn nyash_array_set_h(handle: u64, idx: i64, val: i64) -> i64 {
|
|
||||||
use crate::jit::hostcall_registry::{classify, HostcallKind};
|
|
||||||
let sym = crate::jit::r#extern::collections::SYM_ARRAY_SET_H;
|
|
||||||
let pol = crate::jit::policy::current();
|
|
||||||
let wh = pol.hostcall_whitelist;
|
|
||||||
if classify(sym) == HostcallKind::Mutating && pol.read_only && !wh.iter().any(|s| s == sym) {
|
|
||||||
crate::jit::events::emit(
|
|
||||||
"hostcall", "<jit>", None, None,
|
|
||||||
serde_json::json!({"id": sym, "decision":"fallback", "reason":"policy_denied_mutating"})
|
|
||||||
);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
if let Some(obj) = crate::jit::rt::handles::get(handle) {
|
|
||||||
if let Some(arr) = obj.as_any().downcast_ref::<crate::boxes::array::ArrayBox>() {
|
|
||||||
let _ = arr.set(
|
|
||||||
Box::new(crate::box_trait::IntegerBox::new(idx)),
|
|
||||||
Box::new(crate::box_trait::IntegerBox::new(val)),
|
|
||||||
);
|
|
||||||
crate::jit::events::emit(
|
|
||||||
"hostcall", "<jit>", None, None,
|
|
||||||
serde_json::json!({"id": sym, "decision":"allow", "argc":3, "arg_types":["Handle","I64","I64"]})
|
|
||||||
);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
0
|
|
||||||
}
|
|
||||||
#[cfg(feature = "cranelift-jit")]
|
#[cfg(feature = "cranelift-jit")]
|
||||||
extern "C" fn nyash_map_size_h(handle: u64) -> i64 {
|
|
||||||
crate::jit::events::emit(
|
|
||||||
"hostcall", "<jit>", None, None,
|
|
||||||
serde_json::json!({"id": crate::jit::r#extern::collections::SYM_MAP_SIZE_H, "decision":"allow", "argc":1, "arg_types":["Handle"]})
|
|
||||||
);
|
|
||||||
if let Some(obj) = crate::jit::rt::handles::get(handle) {
|
|
||||||
if let Some(map) = obj.as_any().downcast_ref::<crate::boxes::map_box::MapBox>() {
|
|
||||||
if let Some(ib) = map.size().as_any().downcast_ref::<crate::box_trait::IntegerBox>() { return ib.value; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
0
|
|
||||||
}
|
|
||||||
#[cfg(feature = "cranelift-jit")]
|
#[cfg(feature = "cranelift-jit")]
|
||||||
extern "C" fn nyash_map_get_h(handle: u64, key: i64) -> i64 {
|
|
||||||
crate::jit::events::emit(
|
|
||||||
"hostcall", "<jit>", None, None,
|
|
||||||
serde_json::json!({"id": crate::jit::r#extern::collections::SYM_MAP_GET_H, "decision":"allow", "argc":2, "arg_types":["Handle","I64"]})
|
|
||||||
);
|
|
||||||
if let Some(obj) = crate::jit::rt::handles::get(handle) {
|
|
||||||
if let Some(map) = obj.as_any().downcast_ref::<crate::boxes::map_box::MapBox>() {
|
|
||||||
let key_box = Box::new(crate::box_trait::IntegerBox::new(key));
|
|
||||||
let val = map.get(key_box);
|
|
||||||
if let Some(ib) = val.as_any().downcast_ref::<crate::box_trait::IntegerBox>() { return ib.value; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
0
|
|
||||||
}
|
|
||||||
#[cfg(feature = "cranelift-jit")]
|
#[cfg(feature = "cranelift-jit")]
|
||||||
extern "C" fn nyash_map_get_hh(map_h: u64, key_h: u64) -> i64 {
|
|
||||||
// Emit allow event for visibility
|
|
||||||
crate::jit::events::emit(
|
|
||||||
"hostcall", "<jit>", None, None,
|
|
||||||
serde_json::json!({"id": crate::jit::r#extern::collections::SYM_MAP_GET_HH, "decision":"allow", "argc":2, "arg_types":["Handle","Handle"]})
|
|
||||||
);
|
|
||||||
let map_arc = crate::jit::rt::handles::get(map_h);
|
|
||||||
let key_arc = crate::jit::rt::handles::get(key_h);
|
|
||||||
if let (Some(mobj), Some(kobj)) = (map_arc, key_arc) {
|
|
||||||
if let Some(map) = mobj.as_any().downcast_ref::<crate::boxes::map_box::MapBox>() {
|
|
||||||
let key_box: Box<dyn crate::box_trait::NyashBox> = kobj.share_box();
|
|
||||||
let val = map.get(key_box);
|
|
||||||
// Register result into handle table and return handle id
|
|
||||||
let arc: std::sync::Arc<dyn crate::box_trait::NyashBox> = std::sync::Arc::from(val);
|
|
||||||
let h = crate::jit::rt::handles::to_handle(arc);
|
|
||||||
return h as i64;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
0
|
|
||||||
}
|
|
||||||
#[cfg(feature = "cranelift-jit")]
|
#[cfg(feature = "cranelift-jit")]
|
||||||
extern "C" fn nyash_map_set_h(handle: u64, key: i64, val: i64) -> i64 {
|
|
||||||
use crate::jit::hostcall_registry::{classify, HostcallKind};
|
|
||||||
let sym = crate::jit::r#extern::collections::SYM_MAP_SET_H;
|
|
||||||
let pol = crate::jit::policy::current();
|
|
||||||
let wh = pol.hostcall_whitelist;
|
|
||||||
if classify(sym) == HostcallKind::Mutating && pol.read_only && !wh.iter().any(|s| s == sym) {
|
|
||||||
crate::jit::events::emit(
|
|
||||||
"hostcall", "<jit>", None, None,
|
|
||||||
serde_json::json!({"id": sym, "decision":"fallback", "reason":"policy_denied_mutating"})
|
|
||||||
);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
if let Some(obj) = crate::jit::rt::handles::get(handle) {
|
|
||||||
if let Some(map) = obj.as_any().downcast_ref::<crate::boxes::map_box::MapBox>() {
|
|
||||||
let key_box = Box::new(crate::box_trait::IntegerBox::new(key));
|
|
||||||
let val_box = Box::new(crate::box_trait::IntegerBox::new(val));
|
|
||||||
let _ = map.set(key_box, val_box);
|
|
||||||
crate::jit::events::emit(
|
|
||||||
"hostcall", "<jit>", None, None,
|
|
||||||
serde_json::json!({"id": sym, "decision":"allow", "argc":3, "arg_types":["Handle","I64","I64"]})
|
|
||||||
);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
0
|
|
||||||
}
|
|
||||||
#[cfg(feature = "cranelift-jit")]
|
#[cfg(feature = "cranelift-jit")]
|
||||||
extern "C" fn nyash_map_has_h(handle: u64, key: i64) -> i64 {
|
|
||||||
crate::jit::events::emit(
|
|
||||||
"hostcall", "<jit>", None, None,
|
|
||||||
serde_json::json!({"id": crate::jit::r#extern::collections::SYM_MAP_HAS_H, "decision":"allow", "argc":2, "arg_types":["Handle","I64"]})
|
|
||||||
);
|
|
||||||
if let Some(obj) = crate::jit::rt::handles::get(handle) {
|
|
||||||
if let Some(map) = obj.as_any().downcast_ref::<crate::boxes::map_box::MapBox>() {
|
|
||||||
let key_box = Box::new(crate::box_trait::IntegerBox::new(key));
|
|
||||||
let val = map.get(key_box);
|
|
||||||
// Treat presence if result is not Void
|
|
||||||
let is_present = !val.as_any().is::<crate::box_trait::VoidBox>();
|
|
||||||
return if is_present { 1 } else { 0 };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
0
|
|
||||||
}
|
|
||||||
#[cfg(feature = "cranelift-jit")]
|
#[cfg(feature = "cranelift-jit")]
|
||||||
extern "C" fn nyash_any_length_h(handle: u64) -> i64 {
|
|
||||||
crate::jit::events::emit(
|
|
||||||
"hostcall", "<jit>", None, None,
|
|
||||||
serde_json::json!({"id": crate::jit::r#extern::collections::SYM_ANY_LEN_H, "decision":"allow", "argc":1, "arg_types":["Handle"]})
|
|
||||||
);
|
|
||||||
if let Some(obj) = crate::jit::rt::handles::get(handle) {
|
|
||||||
// Array length
|
|
||||||
if let Some(arr) = obj.as_any().downcast_ref::<crate::boxes::array::ArrayBox>() {
|
|
||||||
if let Some(ib) = arr.length().as_any().downcast_ref::<crate::box_trait::IntegerBox>() { return ib.value; }
|
|
||||||
}
|
|
||||||
// String length
|
|
||||||
if let Some(sb) = obj.as_any().downcast_ref::<crate::box_trait::StringBox>() {
|
|
||||||
return sb.value.len() as i64;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
0
|
|
||||||
}
|
|
||||||
#[cfg(feature = "cranelift-jit")]
|
#[cfg(feature = "cranelift-jit")]
|
||||||
extern "C" fn nyash_any_is_empty_h(handle: u64) -> i64 {
|
|
||||||
crate::jit::events::emit(
|
|
||||||
"hostcall", "<jit>", None, None,
|
|
||||||
serde_json::json!({"id": crate::jit::r#extern::collections::SYM_ANY_IS_EMPTY_H, "decision":"allow", "argc":1, "arg_types":["Handle"]})
|
|
||||||
);
|
|
||||||
if let Some(obj) = crate::jit::rt::handles::get(handle) {
|
|
||||||
// Array empty?
|
|
||||||
if let Some(arr) = obj.as_any().downcast_ref::<crate::boxes::array::ArrayBox>() {
|
|
||||||
if let Ok(items) = arr.items.read() { return if items.is_empty() { 1 } else { 0 }; }
|
|
||||||
}
|
|
||||||
// String empty?
|
|
||||||
if let Some(sb) = obj.as_any().downcast_ref::<crate::box_trait::StringBox>() {
|
|
||||||
return if sb.value.is_empty() { 1 } else { 0 };
|
|
||||||
}
|
|
||||||
// Map empty?
|
|
||||||
if let Some(map) = obj.as_any().downcast_ref::<crate::boxes::map_box::MapBox>() {
|
|
||||||
if let Some(ib) = map.size().as_any().downcast_ref::<crate::box_trait::IntegerBox>() { return if ib.value == 0 { 1 } else { 0 }; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
0
|
|
||||||
}
|
|
||||||
#[cfg(feature = "cranelift-jit")]
|
#[cfg(feature = "cranelift-jit")]
|
||||||
extern "C" fn nyash_string_charcode_at_h(handle: u64, idx: i64) -> i64 {
|
|
||||||
crate::jit::events::emit(
|
|
||||||
"hostcall", "<jit>", None, None,
|
|
||||||
serde_json::json!({"id": crate::jit::r#extern::collections::SYM_STRING_CHARCODE_AT_H, "decision":"allow", "argc":2, "arg_types":["Handle","I64"]})
|
|
||||||
);
|
|
||||||
if idx < 0 { return -1; }
|
|
||||||
if let Some(obj) = crate::jit::rt::handles::get(handle) {
|
|
||||||
if let Some(sb) = obj.as_any().downcast_ref::<crate::box_trait::StringBox>() {
|
|
||||||
let s = &sb.value;
|
|
||||||
let i = idx as usize;
|
|
||||||
if i < s.len() {
|
|
||||||
// Return UTF-8 byte at index (ASCII-friendly PoC)
|
|
||||||
return s.as_bytes()[i] as i64;
|
|
||||||
} else { return -1; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-1
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "cranelift-jit")]
|
#[cfg(feature = "cranelift-jit")]
|
||||||
impl IRBuilder for CraneliftBuilder {
|
impl IRBuilder for CraneliftBuilder {
|
||||||
|
|||||||
@ -642,16 +642,16 @@ impl LowerCore {
|
|||||||
match method.as_str() {
|
match method.as_str() {
|
||||||
"len" | "length" => {
|
"len" | "length" => {
|
||||||
if let Some(pidx) = self.param_index.get(array).copied() {
|
if let Some(pidx) = self.param_index.get(array).copied() {
|
||||||
crate::jit::events::emit(
|
crate::jit::events::emit_lower(
|
||||||
"hostcall","<jit>",None,None,
|
serde_json::json!({"id": crate::jit::r#extern::collections::SYM_ANY_LEN_H, "decision":"allow", "reason":"sig_ok", "argc":1, "arg_types":["Handle"]}),
|
||||||
serde_json::json!({"id": crate::jit::r#extern::collections::SYM_ANY_LEN_H, "decision":"allow", "reason":"sig_ok", "argc":1, "arg_types":["Handle"]})
|
"hostcall","<jit>"
|
||||||
);
|
);
|
||||||
b.emit_param_i64(pidx);
|
b.emit_param_i64(pidx);
|
||||||
b.emit_host_call(crate::jit::r#extern::collections::SYM_ANY_LEN_H, 1, dst.is_some());
|
b.emit_host_call(crate::jit::r#extern::collections::SYM_ANY_LEN_H, 1, dst.is_some());
|
||||||
} else {
|
} else {
|
||||||
crate::jit::events::emit(
|
crate::jit::events::emit_lower(
|
||||||
"hostcall","<jit>",None,None,
|
serde_json::json!({"id": crate::jit::r#extern::collections::SYM_ANY_LEN_H, "decision":"fallback", "reason":"receiver_not_param", "argc":1, "arg_types":["Handle"]}),
|
||||||
serde_json::json!({"id": crate::jit::r#extern::collections::SYM_ANY_LEN_H, "decision":"fallback", "reason":"receiver_not_param", "argc":1, "arg_types":["Handle"]})
|
"hostcall","<jit>"
|
||||||
);
|
);
|
||||||
let arr_idx = -1;
|
let arr_idx = -1;
|
||||||
b.emit_const_i64(arr_idx);
|
b.emit_const_i64(arr_idx);
|
||||||
@ -693,19 +693,16 @@ impl LowerCore {
|
|||||||
match check_signature(&sym, &observed_kinds) {
|
match check_signature(&sym, &observed_kinds) {
|
||||||
Ok(()) => {
|
Ok(()) => {
|
||||||
// allow: record decision; execution remains on VM for now (thin bridge)
|
// allow: record decision; execution remains on VM for now (thin bridge)
|
||||||
crate::jit::events::emit(
|
crate::jit::events::emit_lower(
|
||||||
"hostcall",
|
serde_json::json!({
|
||||||
"<jit>",
|
"id": sym,
|
||||||
None,
|
"decision": "allow",
|
||||||
None,
|
"reason": "sig_ok",
|
||||||
serde_json::json!({
|
"argc": observed.len(),
|
||||||
"id": sym,
|
"arg_types": arg_types
|
||||||
"decision": "allow",
|
}),
|
||||||
"reason": "sig_ok",
|
"hostcall","<jit>"
|
||||||
"argc": observed.len(),
|
);
|
||||||
"arg_types": arg_types
|
|
||||||
})
|
|
||||||
);
|
|
||||||
// If native f64 is enabled, emit a typed hostcall to math extern
|
// If native f64 is enabled, emit a typed hostcall to math extern
|
||||||
if crate::jit::config::current().native_f64 {
|
if crate::jit::config::current().native_f64 {
|
||||||
let (symbol, arity) = match method.as_str() {
|
let (symbol, arity) = match method.as_str() {
|
||||||
@ -771,10 +768,10 @@ impl LowerCore {
|
|||||||
// returns i64 0/1
|
// returns i64 0/1
|
||||||
b.emit_host_call(crate::jit::r#extern::collections::SYM_ANY_IS_EMPTY_H, 1, dst.is_some());
|
b.emit_host_call(crate::jit::r#extern::collections::SYM_ANY_IS_EMPTY_H, 1, dst.is_some());
|
||||||
} else {
|
} else {
|
||||||
crate::jit::events::emit(
|
crate::jit::events::emit_lower(
|
||||||
"hostcall","<jit>",None,None,
|
serde_json::json!({"id": crate::jit::r#extern::collections::SYM_ANY_IS_EMPTY_H, "decision":"fallback", "reason":"receiver_not_param", "argc":1, "arg_types":["Handle"]}),
|
||||||
serde_json::json!({"id": crate::jit::r#extern::collections::SYM_ANY_IS_EMPTY_H, "decision":"fallback", "reason":"receiver_not_param", "argc":1, "arg_types":["Handle"]})
|
"hostcall","<jit>"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
"push" => {
|
"push" => {
|
||||||
@ -785,23 +782,23 @@ impl LowerCore {
|
|||||||
let wh = &pol.hostcall_whitelist;
|
let wh = &pol.hostcall_whitelist;
|
||||||
let sym = crate::jit::r#extern::collections::SYM_ARRAY_PUSH_H;
|
let sym = crate::jit::r#extern::collections::SYM_ARRAY_PUSH_H;
|
||||||
let allowed = !pol.read_only || wh.iter().any(|s| s == sym);
|
let allowed = !pol.read_only || wh.iter().any(|s| s == sym);
|
||||||
crate::jit::events::emit(
|
crate::jit::events::emit_lower(
|
||||||
"hostcall","<jit>",None,None,
|
|
||||||
serde_json::json!({
|
serde_json::json!({
|
||||||
"id": sym,
|
"id": sym,
|
||||||
"decision": if allowed {"allow"} else {"fallback"},
|
"decision": if allowed {"allow"} else {"fallback"},
|
||||||
"reason": if allowed {"sig_ok"} else {"policy_denied_mutating"},
|
"reason": if allowed {"sig_ok"} else {"policy_denied_mutating"},
|
||||||
"argc": 2,
|
"argc": 2,
|
||||||
"arg_types": ["Handle","I64"]
|
"arg_types": ["Handle","I64"]
|
||||||
})
|
}),
|
||||||
|
"hostcall","<jit>"
|
||||||
);
|
);
|
||||||
b.emit_param_i64(pidx);
|
b.emit_param_i64(pidx);
|
||||||
b.emit_const_i64(val);
|
b.emit_const_i64(val);
|
||||||
b.emit_host_call(sym, 2, false);
|
b.emit_host_call(sym, 2, false);
|
||||||
} else {
|
} else {
|
||||||
crate::jit::events::emit(
|
crate::jit::events::emit_lower(
|
||||||
"hostcall","<jit>",None,None,
|
serde_json::json!({"id": crate::jit::r#extern::collections::SYM_ARRAY_PUSH_H, "decision":"fallback", "reason":"receiver_not_param", "argc":2, "arg_types":["Handle","I64"]}),
|
||||||
serde_json::json!({"id": crate::jit::r#extern::collections::SYM_ARRAY_PUSH_H, "decision":"fallback", "reason":"receiver_not_param", "argc":2, "arg_types":["Handle","I64"]})
|
"hostcall","<jit>"
|
||||||
);
|
);
|
||||||
let arr_idx = -1;
|
let arr_idx = -1;
|
||||||
b.emit_const_i64(arr_idx);
|
b.emit_const_i64(arr_idx);
|
||||||
@ -812,16 +809,16 @@ impl LowerCore {
|
|||||||
"size" => {
|
"size" => {
|
||||||
// MapBox.size(): argc=1 (map_handle)
|
// MapBox.size(): argc=1 (map_handle)
|
||||||
if let Some(pidx) = self.param_index.get(array).copied() {
|
if let Some(pidx) = self.param_index.get(array).copied() {
|
||||||
crate::jit::events::emit(
|
crate::jit::events::emit_lower(
|
||||||
"hostcall","<jit>",None,None,
|
serde_json::json!({"id": crate::jit::r#extern::collections::SYM_MAP_SIZE_H, "decision":"allow", "reason":"sig_ok", "argc":1, "arg_types":["Handle"]}),
|
||||||
serde_json::json!({"id": crate::jit::r#extern::collections::SYM_MAP_SIZE_H, "decision":"allow", "reason":"sig_ok", "argc":1, "arg_types":["Handle"]})
|
"hostcall","<jit>"
|
||||||
);
|
);
|
||||||
b.emit_param_i64(pidx);
|
b.emit_param_i64(pidx);
|
||||||
b.emit_host_call(crate::jit::r#extern::collections::SYM_MAP_SIZE_H, 1, dst.is_some());
|
b.emit_host_call(crate::jit::r#extern::collections::SYM_MAP_SIZE_H, 1, dst.is_some());
|
||||||
} else {
|
} else {
|
||||||
crate::jit::events::emit(
|
crate::jit::events::emit_lower(
|
||||||
"hostcall","<jit>",None,None,
|
serde_json::json!({"id": crate::jit::r#extern::collections::SYM_MAP_SIZE_H, "decision":"fallback", "reason":"receiver_not_param", "argc":1, "arg_types":["Handle"]}),
|
||||||
serde_json::json!({"id": crate::jit::r#extern::collections::SYM_MAP_SIZE_H, "decision":"fallback", "reason":"receiver_not_param", "argc":1, "arg_types":["Handle"]})
|
"hostcall","<jit>"
|
||||||
);
|
);
|
||||||
let map_idx = -1;
|
let map_idx = -1;
|
||||||
b.emit_const_i64(map_idx);
|
b.emit_const_i64(map_idx);
|
||||||
@ -870,18 +867,15 @@ impl LowerCore {
|
|||||||
crate::jit::r#extern::collections::SYM_MAP_GET_H
|
crate::jit::r#extern::collections::SYM_MAP_GET_H
|
||||||
};
|
};
|
||||||
// Emit allow event
|
// Emit allow event
|
||||||
crate::jit::events::emit(
|
crate::jit::events::emit_lower(
|
||||||
"hostcall",
|
|
||||||
"<jit>",
|
|
||||||
None,
|
|
||||||
None,
|
|
||||||
serde_json::json!({
|
serde_json::json!({
|
||||||
"id": event_id,
|
"id": event_id,
|
||||||
"decision": "allow",
|
"decision": "allow",
|
||||||
"reason": "sig_ok",
|
"reason": "sig_ok",
|
||||||
"argc": observed_kinds.len(),
|
"argc": observed_kinds.len(),
|
||||||
"arg_types": arg_types
|
"arg_types": arg_types
|
||||||
})
|
}),
|
||||||
|
"hostcall","<jit>"
|
||||||
);
|
);
|
||||||
// If key is i64, emit hostcall; if key is Handle and also a param, emit HH variant; otherwise fallback
|
// If key is i64, emit hostcall; if key is Handle and also a param, emit HH variant; otherwise fallback
|
||||||
if matches!(key_kind, crate::jit::hostcall_registry::ArgKind::I64) {
|
if matches!(key_kind, crate::jit::hostcall_registry::ArgKind::I64) {
|
||||||
@ -901,18 +895,15 @@ impl LowerCore {
|
|||||||
}
|
}
|
||||||
Err(reason) => {
|
Err(reason) => {
|
||||||
// Signature mismatch - log and fallback
|
// Signature mismatch - log and fallback
|
||||||
crate::jit::events::emit(
|
crate::jit::events::emit_lower(
|
||||||
"hostcall",
|
|
||||||
"<jit>",
|
|
||||||
None,
|
|
||||||
None,
|
|
||||||
serde_json::json!({
|
serde_json::json!({
|
||||||
"id": canonical,
|
"id": canonical,
|
||||||
"decision": "fallback",
|
"decision": "fallback",
|
||||||
"reason": reason,
|
"reason": reason,
|
||||||
"argc": observed_kinds.len(),
|
"argc": observed_kinds.len(),
|
||||||
"arg_types": arg_types
|
"arg_types": arg_types
|
||||||
})
|
}),
|
||||||
|
"hostcall","<jit>"
|
||||||
);
|
);
|
||||||
// No emission; VM path will handle
|
// No emission; VM path will handle
|
||||||
}
|
}
|
||||||
@ -937,18 +928,15 @@ impl LowerCore {
|
|||||||
let arg_types: Vec<&'static str> = observed_kinds.iter().map(|k| match k { crate::jit::hostcall_registry::ArgKind::I64 => "I64", crate::jit::hostcall_registry::ArgKind::F64 => "F64", crate::jit::hostcall_registry::ArgKind::Handle => "Handle" }).collect();
|
let arg_types: Vec<&'static str> = observed_kinds.iter().map(|k| match k { crate::jit::hostcall_registry::ArgKind::I64 => "I64", crate::jit::hostcall_registry::ArgKind::F64 => "F64", crate::jit::hostcall_registry::ArgKind::Handle => "Handle" }).collect();
|
||||||
let sym = "nyash.map.get_h";
|
let sym = "nyash.map.get_h";
|
||||||
let decision = match crate::jit::hostcall_registry::check_signature(sym, &observed_kinds) { Ok(()) => ("fallback", "receiver_not_param"), Err(reason) => ("fallback", reason) };
|
let decision = match crate::jit::hostcall_registry::check_signature(sym, &observed_kinds) { Ok(()) => ("fallback", "receiver_not_param"), Err(reason) => ("fallback", reason) };
|
||||||
crate::jit::events::emit(
|
crate::jit::events::emit_lower(
|
||||||
"hostcall",
|
|
||||||
"<jit>",
|
|
||||||
None,
|
|
||||||
None,
|
|
||||||
serde_json::json!({
|
serde_json::json!({
|
||||||
"id": sym,
|
"id": sym,
|
||||||
"decision": decision.0,
|
"decision": decision.0,
|
||||||
"reason": decision.1,
|
"reason": decision.1,
|
||||||
"argc": observed_kinds.len(),
|
"argc": observed_kinds.len(),
|
||||||
"arg_types": arg_types
|
"arg_types": arg_types
|
||||||
})
|
}),
|
||||||
|
"hostcall","<jit>"
|
||||||
);
|
);
|
||||||
// no-op: VM側が処理する
|
// no-op: VM側が処理する
|
||||||
}
|
}
|
||||||
@ -962,24 +950,24 @@ impl LowerCore {
|
|||||||
let wh = &pol.hostcall_whitelist;
|
let wh = &pol.hostcall_whitelist;
|
||||||
let sym = crate::jit::r#extern::collections::SYM_MAP_SET_H;
|
let sym = crate::jit::r#extern::collections::SYM_MAP_SET_H;
|
||||||
let allowed = !pol.read_only || wh.iter().any(|s| s == sym);
|
let allowed = !pol.read_only || wh.iter().any(|s| s == sym);
|
||||||
crate::jit::events::emit(
|
crate::jit::events::emit_lower(
|
||||||
"hostcall","<jit>",None,None,
|
|
||||||
serde_json::json!({
|
serde_json::json!({
|
||||||
"id": sym,
|
"id": sym,
|
||||||
"decision": if allowed {"allow"} else {"fallback"},
|
"decision": if allowed {"allow"} else {"fallback"},
|
||||||
"reason": if allowed {"sig_ok"} else {"policy_denied_mutating"},
|
"reason": if allowed {"sig_ok"} else {"policy_denied_mutating"},
|
||||||
"argc": 3,
|
"argc": 3,
|
||||||
"arg_types": ["Handle","I64","I64"]
|
"arg_types": ["Handle","I64","I64"]
|
||||||
})
|
}),
|
||||||
|
"hostcall","<jit>"
|
||||||
);
|
);
|
||||||
b.emit_param_i64(pidx);
|
b.emit_param_i64(pidx);
|
||||||
b.emit_const_i64(key);
|
b.emit_const_i64(key);
|
||||||
b.emit_const_i64(val);
|
b.emit_const_i64(val);
|
||||||
b.emit_host_call(sym, 3, false);
|
b.emit_host_call(sym, 3, false);
|
||||||
} else {
|
} else {
|
||||||
crate::jit::events::emit(
|
crate::jit::events::emit_lower(
|
||||||
"hostcall","<jit>",None,None,
|
serde_json::json!({"id": crate::jit::r#extern::collections::SYM_MAP_SET_H, "decision":"fallback", "reason":"receiver_not_param", "argc":3, "arg_types":["Handle","I64","I64"]}),
|
||||||
serde_json::json!({"id": crate::jit::r#extern::collections::SYM_MAP_SET_H, "decision":"fallback", "reason":"receiver_not_param", "argc":3, "arg_types":["Handle","I64","I64"]})
|
"hostcall","<jit>"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -987,17 +975,17 @@ impl LowerCore {
|
|||||||
// String.charCodeAt(index)
|
// String.charCodeAt(index)
|
||||||
if let Some(pidx) = self.param_index.get(array).copied() {
|
if let Some(pidx) = self.param_index.get(array).copied() {
|
||||||
let idx = args.get(0).and_then(|v| self.known_i64.get(v)).copied().unwrap_or(0);
|
let idx = args.get(0).and_then(|v| self.known_i64.get(v)).copied().unwrap_or(0);
|
||||||
crate::jit::events::emit(
|
crate::jit::events::emit_lower(
|
||||||
"hostcall","<jit>",None,None,
|
serde_json::json!({"id": crate::jit::r#extern::collections::SYM_STRING_CHARCODE_AT_H, "decision":"allow", "reason":"sig_ok", "argc":2, "arg_types":["Handle","I64"]}),
|
||||||
serde_json::json!({"id": crate::jit::r#extern::collections::SYM_STRING_CHARCODE_AT_H, "decision":"allow", "reason":"sig_ok", "argc":2, "arg_types":["Handle","I64"]})
|
"hostcall","<jit>"
|
||||||
);
|
);
|
||||||
b.emit_param_i64(pidx);
|
b.emit_param_i64(pidx);
|
||||||
b.emit_const_i64(idx);
|
b.emit_const_i64(idx);
|
||||||
b.emit_host_call(crate::jit::r#extern::collections::SYM_STRING_CHARCODE_AT_H, 2, dst.is_some());
|
b.emit_host_call(crate::jit::r#extern::collections::SYM_STRING_CHARCODE_AT_H, 2, dst.is_some());
|
||||||
} else {
|
} else {
|
||||||
crate::jit::events::emit(
|
crate::jit::events::emit_lower(
|
||||||
"hostcall","<jit>",None,None,
|
serde_json::json!({"id": crate::jit::r#extern::collections::SYM_STRING_CHARCODE_AT_H, "decision":"fallback", "reason":"receiver_not_param", "argc":2, "arg_types":["Handle","I64"]}),
|
||||||
serde_json::json!({"id": crate::jit::r#extern::collections::SYM_STRING_CHARCODE_AT_H, "decision":"fallback", "reason":"receiver_not_param", "argc":2, "arg_types":["Handle","I64"]})
|
"hostcall","<jit>"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
225
src/jit/lower/extern_thunks.rs
Normal file
225
src/jit/lower/extern_thunks.rs
Normal file
@ -0,0 +1,225 @@
|
|||||||
|
//! Handle-based extern thunks used by the JIT runtime path.
|
||||||
|
//! Moved out of builder.rs to keep files small and responsibilities clear.
|
||||||
|
|
||||||
|
#[cfg(feature = "cranelift-jit")]
|
||||||
|
use crate::jit::events;
|
||||||
|
|
||||||
|
#[cfg(feature = "cranelift-jit")]
|
||||||
|
use crate::jit::r#extern::collections as c;
|
||||||
|
|
||||||
|
// ---- Math (native f64) ----
|
||||||
|
#[cfg(feature = "cranelift-jit")]
|
||||||
|
pub(super) extern "C" fn nyash_math_sin_f64(x: f64) -> f64 { x.sin() }
|
||||||
|
#[cfg(feature = "cranelift-jit")]
|
||||||
|
pub(super) extern "C" fn nyash_math_cos_f64(x: f64) -> f64 { x.cos() }
|
||||||
|
#[cfg(feature = "cranelift-jit")]
|
||||||
|
pub(super) extern "C" fn nyash_math_abs_f64(x: f64) -> f64 { x.abs() }
|
||||||
|
#[cfg(feature = "cranelift-jit")]
|
||||||
|
pub(super) extern "C" fn nyash_math_min_f64(a: f64, b: f64) -> f64 { a.min(b) }
|
||||||
|
#[cfg(feature = "cranelift-jit")]
|
||||||
|
pub(super) extern "C" fn nyash_math_max_f64(a: f64, b: f64) -> f64 { a.max(b) }
|
||||||
|
|
||||||
|
// ---- Array (handle) ----
|
||||||
|
#[cfg(feature = "cranelift-jit")]
|
||||||
|
pub(super) extern "C" fn nyash_array_len_h(handle: u64) -> i64 {
|
||||||
|
events::emit_runtime(serde_json::json!({"id": c::SYM_ARRAY_LEN_H, "decision":"allow", "argc":1, "arg_types":["Handle"]}), "hostcall", "<jit>");
|
||||||
|
if let Some(obj) = crate::jit::rt::handles::get(handle) {
|
||||||
|
if let Some(arr) = obj.as_any().downcast_ref::<crate::boxes::array::ArrayBox>() {
|
||||||
|
if let Some(ib) = arr.length().as_any().downcast_ref::<crate::box_trait::IntegerBox>() { return ib.value; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
0
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "cranelift-jit")]
|
||||||
|
pub(super) extern "C" fn nyash_array_get_h(handle: u64, idx: i64) -> i64 {
|
||||||
|
events::emit_runtime(serde_json::json!({"id": c::SYM_ARRAY_GET_H, "decision":"allow", "argc":2, "arg_types":["Handle","I64"]}), "hostcall", "<jit>");
|
||||||
|
if let Some(obj) = crate::jit::rt::handles::get(handle) {
|
||||||
|
if let Some(arr) = obj.as_any().downcast_ref::<crate::boxes::array::ArrayBox>() {
|
||||||
|
let val = arr.get(Box::new(crate::box_trait::IntegerBox::new(idx)));
|
||||||
|
if let Some(ib) = val.as_any().downcast_ref::<crate::box_trait::IntegerBox>() { return ib.value; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
0
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "cranelift-jit")]
|
||||||
|
pub(super) extern "C" fn nyash_array_last_h(handle: u64) -> i64 {
|
||||||
|
if let Some(obj) = crate::jit::rt::handles::get(handle) {
|
||||||
|
if let Some(arr) = obj.as_any().downcast_ref::<crate::boxes::array::ArrayBox>() {
|
||||||
|
if let Ok(items) = arr.items.read() {
|
||||||
|
if let Some(last) = items.last() {
|
||||||
|
if let Some(ib) = last.as_any().downcast_ref::<crate::box_trait::IntegerBox>() { return ib.value; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
0
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "cranelift-jit")]
|
||||||
|
pub(super) extern "C" fn nyash_array_set_h(handle: u64, idx: i64, val: i64) -> i64 {
|
||||||
|
use crate::jit::hostcall_registry::{classify, HostcallKind};
|
||||||
|
let sym = c::SYM_ARRAY_SET_H;
|
||||||
|
let pol = crate::jit::policy::current();
|
||||||
|
let wh = pol.hostcall_whitelist;
|
||||||
|
if classify(sym) == HostcallKind::Mutating && pol.read_only && !wh.iter().any(|s| s == sym) {
|
||||||
|
events::emit_runtime(serde_json::json!({"id": sym, "decision":"fallback", "reason":"policy_denied_mutating"}), "hostcall", "<jit>");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if let Some(obj) = crate::jit::rt::handles::get(handle) {
|
||||||
|
if let Some(arr) = obj.as_any().downcast_ref::<crate::boxes::array::ArrayBox>() {
|
||||||
|
let _ = arr.set(Box::new(crate::box_trait::IntegerBox::new(idx)), Box::new(crate::box_trait::IntegerBox::new(val)));
|
||||||
|
events::emit_runtime(serde_json::json!({"id": sym, "decision":"allow", "argc":3, "arg_types":["Handle","I64","I64"]}), "hostcall", "<jit>");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
0
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "cranelift-jit")]
|
||||||
|
pub(super) extern "C" fn nyash_array_push_h(handle: u64, val: i64) -> i64 {
|
||||||
|
use crate::jit::hostcall_registry::{classify, HostcallKind};
|
||||||
|
let sym = c::SYM_ARRAY_PUSH_H;
|
||||||
|
let pol = crate::jit::policy::current();
|
||||||
|
let wh = pol.hostcall_whitelist;
|
||||||
|
if classify(sym) == HostcallKind::Mutating && pol.read_only && !wh.iter().any(|s| s == sym) {
|
||||||
|
events::emit_runtime(serde_json::json!({"id": sym, "decision":"fallback", "reason":"policy_denied_mutating"}), "hostcall", "<jit>");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if let Some(obj) = crate::jit::rt::handles::get(handle) {
|
||||||
|
if let Some(arr) = obj.as_any().downcast_ref::<crate::boxes::array::ArrayBox>() {
|
||||||
|
let ib = crate::box_trait::IntegerBox::new(val);
|
||||||
|
let _ = arr.push(Box::new(ib));
|
||||||
|
events::emit_runtime(serde_json::json!({"id": sym, "decision":"allow", "argc":2, "arg_types":["Handle","I64"]}), "hostcall", "<jit>");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
0
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- Map (handle) ----
|
||||||
|
#[cfg(feature = "cranelift-jit")]
|
||||||
|
pub(super) extern "C" fn nyash_map_size_h(handle: u64) -> i64 {
|
||||||
|
events::emit_runtime(serde_json::json!({"id": c::SYM_MAP_SIZE_H, "decision":"allow", "argc":1, "arg_types":["Handle"]}), "hostcall", "<jit>");
|
||||||
|
if let Some(obj) = crate::jit::rt::handles::get(handle) {
|
||||||
|
if let Some(map) = obj.as_any().downcast_ref::<crate::boxes::map_box::MapBox>() {
|
||||||
|
if let Some(ib) = map.size().as_any().downcast_ref::<crate::box_trait::IntegerBox>() { return ib.value; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
0
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "cranelift-jit")]
|
||||||
|
pub(super) extern "C" fn nyash_map_get_h(handle: u64, key: i64) -> i64 {
|
||||||
|
events::emit_runtime(serde_json::json!({"id": c::SYM_MAP_GET_H, "decision":"allow", "argc":2, "arg_types":["Handle","I64"]}), "hostcall", "<jit>");
|
||||||
|
if let Some(obj) = crate::jit::rt::handles::get(handle) {
|
||||||
|
if let Some(map) = obj.as_any().downcast_ref::<crate::boxes::map_box::MapBox>() {
|
||||||
|
let key_box = Box::new(crate::box_trait::IntegerBox::new(key));
|
||||||
|
let val = map.get(key_box);
|
||||||
|
if let Some(ib) = val.as_any().downcast_ref::<crate::box_trait::IntegerBox>() { return ib.value; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
0
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "cranelift-jit")]
|
||||||
|
pub(super) extern "C" fn nyash_map_get_hh(map_h: u64, key_h: u64) -> i64 {
|
||||||
|
events::emit_runtime(serde_json::json!({"id": c::SYM_MAP_GET_HH, "decision":"allow", "argc":2, "arg_types":["Handle","Handle"]}), "hostcall", "<jit>");
|
||||||
|
let map_arc = crate::jit::rt::handles::get(map_h);
|
||||||
|
let key_arc = crate::jit::rt::handles::get(key_h);
|
||||||
|
if let (Some(mobj), Some(kobj)) = (map_arc, key_arc) {
|
||||||
|
if let Some(map) = mobj.as_any().downcast_ref::<crate::boxes::map_box::MapBox>() {
|
||||||
|
let key_box: Box<dyn crate::box_trait::NyashBox> = kobj.share_box();
|
||||||
|
let val = map.get(key_box);
|
||||||
|
let arc: std::sync::Arc<dyn crate::box_trait::NyashBox> = std::sync::Arc::from(val);
|
||||||
|
let h = crate::jit::rt::handles::to_handle(arc);
|
||||||
|
return h as i64;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
0
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "cranelift-jit")]
|
||||||
|
pub(super) extern "C" fn nyash_map_set_h(handle: u64, key: i64, val: i64) -> i64 {
|
||||||
|
use crate::jit::hostcall_registry::{classify, HostcallKind};
|
||||||
|
let sym = c::SYM_MAP_SET_H;
|
||||||
|
let pol = crate::jit::policy::current();
|
||||||
|
let wh = pol.hostcall_whitelist;
|
||||||
|
if classify(sym) == HostcallKind::Mutating && pol.read_only && !wh.iter().any(|s| s == sym) {
|
||||||
|
events::emit_runtime(serde_json::json!({"id": sym, "decision":"fallback", "reason":"policy_denied_mutating"}), "hostcall", "<jit>");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if let Some(obj) = crate::jit::rt::handles::get(handle) {
|
||||||
|
if let Some(map) = obj.as_any().downcast_ref::<crate::boxes::map_box::MapBox>() {
|
||||||
|
let key_box = Box::new(crate::box_trait::IntegerBox::new(key));
|
||||||
|
let val_box = Box::new(crate::box_trait::IntegerBox::new(val));
|
||||||
|
let _ = map.set(key_box, val_box);
|
||||||
|
events::emit_runtime(serde_json::json!({"id": sym, "decision":"allow", "argc":3, "arg_types":["Handle","I64","I64"]}), "hostcall", "<jit>");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
0
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "cranelift-jit")]
|
||||||
|
pub(super) extern "C" fn nyash_map_has_h(handle: u64, key: i64) -> i64 {
|
||||||
|
events::emit_runtime(serde_json::json!({"id": c::SYM_MAP_HAS_H, "decision":"allow", "argc":2, "arg_types":["Handle","I64"]}), "hostcall", "<jit>");
|
||||||
|
if let Some(obj) = crate::jit::rt::handles::get(handle) {
|
||||||
|
if let Some(map) = obj.as_any().downcast_ref::<crate::boxes::map_box::MapBox>() {
|
||||||
|
let key_box = Box::new(crate::box_trait::IntegerBox::new(key));
|
||||||
|
let val = map.get(key_box);
|
||||||
|
let is_present = !val.as_any().is::<crate::box_trait::VoidBox>();
|
||||||
|
return if is_present { 1 } else { 0 };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
0
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- Any helpers ----
|
||||||
|
#[cfg(feature = "cranelift-jit")]
|
||||||
|
pub(super) extern "C" fn nyash_any_length_h(handle: u64) -> i64 {
|
||||||
|
events::emit_runtime(serde_json::json!({"id": c::SYM_ANY_LEN_H, "decision":"allow", "argc":1, "arg_types":["Handle"]}), "hostcall", "<jit>");
|
||||||
|
if let Some(obj) = crate::jit::rt::handles::get(handle) {
|
||||||
|
if let Some(arr) = obj.as_any().downcast_ref::<crate::boxes::array::ArrayBox>() {
|
||||||
|
if let Some(ib) = arr.length().as_any().downcast_ref::<crate::box_trait::IntegerBox>() { return ib.value; }
|
||||||
|
}
|
||||||
|
if let Some(sb) = obj.as_any().downcast_ref::<crate::box_trait::StringBox>() {
|
||||||
|
return sb.value.len() as i64;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
0
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "cranelift-jit")]
|
||||||
|
pub(super) extern "C" fn nyash_any_is_empty_h(handle: u64) -> i64 {
|
||||||
|
events::emit_runtime(serde_json::json!({"id": c::SYM_ANY_IS_EMPTY_H, "decision":"allow", "argc":1, "arg_types":["Handle"]}), "hostcall", "<jit>");
|
||||||
|
if let Some(obj) = crate::jit::rt::handles::get(handle) {
|
||||||
|
if let Some(arr) = obj.as_any().downcast_ref::<crate::boxes::array::ArrayBox>() {
|
||||||
|
if let Ok(items) = arr.items.read() { return if items.is_empty() { 1 } else { 0 }; }
|
||||||
|
}
|
||||||
|
if let Some(sb) = obj.as_any().downcast_ref::<crate::box_trait::StringBox>() {
|
||||||
|
return if sb.value.is_empty() { 1 } else { 0 };
|
||||||
|
}
|
||||||
|
if let Some(map) = obj.as_any().downcast_ref::<crate::boxes::map_box::MapBox>() {
|
||||||
|
if let Some(ib) = map.size().as_any().downcast_ref::<crate::box_trait::IntegerBox>() { return if ib.value == 0 { 1 } else { 0 }; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
0
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- String ----
|
||||||
|
#[cfg(feature = "cranelift-jit")]
|
||||||
|
pub(super) extern "C" fn nyash_string_charcode_at_h(handle: u64, idx: i64) -> i64 {
|
||||||
|
events::emit_runtime(serde_json::json!({"id": c::SYM_STRING_CHARCODE_AT_H, "decision":"allow", "argc":2, "arg_types":["Handle","I64"]}), "hostcall", "<jit>");
|
||||||
|
if idx < 0 { return -1; }
|
||||||
|
if let Some(obj) = crate::jit::rt::handles::get(handle) {
|
||||||
|
if let Some(sb) = obj.as_any().downcast_ref::<crate::box_trait::StringBox>() {
|
||||||
|
let s = &sb.value;
|
||||||
|
let i = idx as usize;
|
||||||
|
if i < s.len() { return s.as_bytes()[i] as i64; } else { return -1; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
-1
|
||||||
|
}
|
||||||
|
|
||||||
@ -1,3 +1,4 @@
|
|||||||
//! Lowering entry for JIT
|
//! Lowering entry for JIT
|
||||||
pub mod core;
|
pub mod core;
|
||||||
pub mod builder;
|
pub mod builder;
|
||||||
|
pub mod extern_thunks;
|
||||||
|
|||||||
@ -22,6 +22,8 @@ impl JitManager {
|
|||||||
Self { threshold, hits: HashMap::new(), compiled: HashMap::new(), engine: crate::jit::engine::JitEngine::new(), exec_ok: 0, exec_trap: 0, func_phi_total: HashMap::new(), func_phi_b1: HashMap::new(), func_ret_bool_hint: HashMap::new() }
|
Self { threshold, hits: HashMap::new(), compiled: HashMap::new(), engine: crate::jit::engine::JitEngine::new(), exec_ok: 0, exec_trap: 0, func_phi_total: HashMap::new(), func_phi_b1: HashMap::new(), func_ret_bool_hint: HashMap::new() }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn set_threshold(&mut self, t: u32) { self.threshold = t.max(1); }
|
||||||
|
|
||||||
pub fn record_entry(&mut self, func: &str) {
|
pub fn record_entry(&mut self, func: &str) {
|
||||||
let c = self.hits.entry(func.to_string()).or_insert(0);
|
let c = self.hits.entry(func.to_string()).or_insert(0);
|
||||||
*c = c.saturating_add(1);
|
*c = c.saturating_add(1);
|
||||||
@ -155,7 +157,20 @@ impl JitManager {
|
|||||||
let vmv = crate::jit::boundary::CallBoundaryBox::to_vm(ret_ty, v);
|
let vmv = crate::jit::boundary::CallBoundaryBox::to_vm(ret_ty, v);
|
||||||
Some(vmv)
|
Some(vmv)
|
||||||
}
|
}
|
||||||
None => { self.exec_trap = self.exec_trap.saturating_add(1); None }
|
None => {
|
||||||
|
self.exec_trap = self.exec_trap.saturating_add(1);
|
||||||
|
// Emit a minimal trap event for observability (runtime only)
|
||||||
|
let dt = t0.elapsed();
|
||||||
|
crate::jit::events::emit_runtime(
|
||||||
|
serde_json::json!({
|
||||||
|
"kind": "trap", // redundant with wrapper kind but explicit here for clarity
|
||||||
|
"reason": "jit_execute_failed",
|
||||||
|
"ms": dt.as_millis()
|
||||||
|
}),
|
||||||
|
"trap", func
|
||||||
|
);
|
||||||
|
None
|
||||||
|
}
|
||||||
};
|
};
|
||||||
// Clear handles created during this call
|
// Clear handles created during this call
|
||||||
crate::jit::rt::handles::end_scope_clear();
|
crate::jit::rt::handles::end_scope_clear();
|
||||||
|
|||||||
@ -66,6 +66,11 @@ impl NyashRunner {
|
|||||||
}
|
}
|
||||||
// Optional: JIT controls via CLI flags (centralized)
|
// Optional: JIT controls via CLI flags (centralized)
|
||||||
{
|
{
|
||||||
|
// CLI opt-in for JSONL events
|
||||||
|
if self.config.jit_events { std::env::set_var("NYASH_JIT_EVENTS", "1"); }
|
||||||
|
if self.config.jit_events_compile { std::env::set_var("NYASH_JIT_EVENTS_COMPILE", "1"); }
|
||||||
|
if self.config.jit_events_runtime { std::env::set_var("NYASH_JIT_EVENTS_RUNTIME", "1"); }
|
||||||
|
if let Some(ref p) = self.config.jit_events_path { std::env::set_var("NYASH_JIT_EVENTS_PATH", p); }
|
||||||
let mut jc = nyash_rust::jit::config::JitConfig::from_env();
|
let mut jc = nyash_rust::jit::config::JitConfig::from_env();
|
||||||
jc.exec |= self.config.jit_exec;
|
jc.exec |= self.config.jit_exec;
|
||||||
jc.stats |= self.config.jit_stats;
|
jc.stats |= self.config.jit_stats;
|
||||||
@ -77,10 +82,11 @@ impl NyashRunner {
|
|||||||
jc.handle_debug |= self.config.jit_handle_debug;
|
jc.handle_debug |= self.config.jit_handle_debug;
|
||||||
jc.native_f64 |= self.config.jit_native_f64;
|
jc.native_f64 |= self.config.jit_native_f64;
|
||||||
jc.native_bool |= self.config.jit_native_bool;
|
jc.native_bool |= self.config.jit_native_bool;
|
||||||
// If events are enabled and no threshold is provided, force threshold=1 so lowering runs and emits events
|
// If observability is enabled and no threshold is provided, force threshold=1 so lowering runs and emits events
|
||||||
if std::env::var("NYASH_JIT_EVENTS").ok().as_deref() == Some("1") && jc.threshold.is_none() {
|
let events_on = std::env::var("NYASH_JIT_EVENTS").ok().as_deref() == Some("1")
|
||||||
jc.threshold = Some(1);
|
|| std::env::var("NYASH_JIT_EVENTS_COMPILE").ok().as_deref() == Some("1")
|
||||||
}
|
|| std::env::var("NYASH_JIT_EVENTS_RUNTIME").ok().as_deref() == Some("1");
|
||||||
|
if events_on && jc.threshold.is_none() { jc.threshold = Some(1); }
|
||||||
if self.config.jit_only { std::env::set_var("NYASH_JIT_ONLY", "1"); }
|
if self.config.jit_only { std::env::set_var("NYASH_JIT_ONLY", "1"); }
|
||||||
// Apply runtime capability probe (e.g., disable b1 ABI if unsupported)
|
// Apply runtime capability probe (e.g., disable b1 ABI if unsupported)
|
||||||
let caps = nyash_rust::jit::config::probe_capabilities();
|
let caps = nyash_rust::jit::config::probe_capabilities();
|
||||||
|
|||||||
36
tools/smoke_phase_10_10.sh
Normal file
36
tools/smoke_phase_10_10.sh
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# Phase 10.10 Smoke Test - Minimal verification for key features
|
||||||
|
# Usage: ./tools/smoke_phase_10_10.sh
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
echo "=== Phase 10.10 Smoke Test ==="
|
||||||
|
echo
|
||||||
|
|
||||||
|
# 1. Build with Cranelift
|
||||||
|
echo "1) Building with Cranelift JIT..."
|
||||||
|
cargo build --release -j32 --features cranelift-jit 2>&1 | head -5
|
||||||
|
echo "✓ Build complete"
|
||||||
|
echo
|
||||||
|
|
||||||
|
# 2. HH direct execution (Map.get_hh)
|
||||||
|
echo "2) Testing HH direct execution..."
|
||||||
|
NYASH_JIT_EXEC=1 NYASH_JIT_THRESHOLD=1 NYASH_JIT_HOSTCALL=1 NYASH_JIT_EVENTS=1 \
|
||||||
|
./target/release/nyash --backend vm examples/jit_map_get_param_hh.nyash 2>&1 | head -10 | grep -E "(allow id:|value1|Result:)"
|
||||||
|
echo "✓ HH execution verified"
|
||||||
|
echo
|
||||||
|
|
||||||
|
# 3. Mutating opt-in (JitPolicyBox)
|
||||||
|
echo "3) Testing mutating opt-in policy..."
|
||||||
|
NYASH_JIT_EXEC=1 NYASH_JIT_THRESHOLD=1 NYASH_JIT_HOSTCALL=1 NYASH_JIT_EVENTS=1 \
|
||||||
|
./target/release/nyash --backend vm examples/jit_policy_optin_mutating.nyash 2>&1 | head -15 | grep -E "(policy_denied_mutating|allow id:|Result:)"
|
||||||
|
echo "✓ Policy opt-in verified"
|
||||||
|
echo
|
||||||
|
|
||||||
|
# 4. CountingGC demo
|
||||||
|
echo "4) Testing CountingGC..."
|
||||||
|
./target/release/nyash --backend vm examples/gc_counting_demo.nyash 2>&1 | head -10 | grep -E "(GC stats:|allocations:|write barriers:)"
|
||||||
|
echo "✓ CountingGC verified"
|
||||||
|
echo
|
||||||
|
|
||||||
|
echo "=== All smoke tests passed! ==="
|
||||||
Reference in New Issue
Block a user