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:
Moe Charm
2025-08-29 02:05:39 +09:00
parent d67f27f4b8
commit 25fbebd650
34 changed files with 1631 additions and 353 deletions

85
.github/workflows/smoke.yml vendored Normal file
View 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

View File

@ -101,13 +101,15 @@ cargo xwin build --target x86_64-pc-windows-msvc --release
target/x86_64-pc-windows-msvc/release/nyash.exe
```
### 🌐 WebAssembly版
### 🌐 WebAssembly版2種類あるので注意
#### 1⃣ **Rust→WASMブラウザでNyashインタープリター実行**
```bash
# WASMビルドルートディレクトリで実行
wasm-pack build --target web
# ビルド結果は pkg/ ディレクトリに生成される
# - pkg/nyash_rust_bg.wasm
# - pkg/nyash_rust_bg.wasm # Rustで書かれたNyashインタープリター全体
# - pkg/nyash_rust.js
# - pkg/nyash_rust.d.ts
@ -121,6 +123,32 @@ python3 -m http.server 8010
**注意**: WASMビルドでは一部のBoxTimerBox、AudioBox等は除外されます。
#### 2⃣ **Nyash→MIR→WASMNyashプログラムをコンパイル**
```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運用メモ最小
- 方針: 当面は read-only書き込み命令はjit-directで拒否
- 失敗の見える化: `NYASH_JIT_STATS_JSON=1` または `NYASH_JIT_ERROR_JSON=1` でエラーを1行JSON出力

View File

@ -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 哲学
Nyashのすべての値は **Box** - 統一された、メモリ安全なコンテナです:

2
compile_only.jsonl Normal file
View 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"}

View File

@ -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での実装に活かします。

View File

@ -231,6 +231,19 @@ NYASH_JIT_EXEC=1 NYASH_JIT_THRESHOLD=1 ./target/release/nyash --backend vm examp
## 現在の地図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-inpolicy_denied_mutatingイベント
- **Math関数**: sin/cos/abs/min/max等のF64署名一致時のallow実装
### ✅ 完了Phase 9.79b
- TypeMeta/Thunk正式化・Poly-PIC2〜4・Plugin TLV拡張bool/i64/f64/bytes
- 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.nyashmutatingのopt-in
- gc_counting_demo.nyashCountingGcの可視化、VMパス
次の着手Restart後すぐ
1) Runner統合DebugConfigの反映優先度/CLI連携の整
- 目標: CLI→DebugConfigBox→env→JIT/VM の一本化既存JitConfigとの整合
2) GCドキュメントに GcConfigBox 使用例を追記(短文 + コマンド)
3) examples/README.md最小手順)
- HH直実行、mutating opt-in、CountingGc demo の3本だけ掲載
### ✅ 完了2025-08-28 late 整理
- Runner統合DebugConfig/CLI/Envの整
- DOT指定時にdump暗黙ON、観測系ON時のしきい値auto=1未指定時
- GCドキュメントに GcConfigBox 使用例を追記(短文 + コマンド)
- examples/README.md 最小手順を整備HH直実行・mutating opt-in・CountingGc
- 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→whitelistevents分離
- runtime trapイベント出力JIT実行失敗時に1行JSONL
- CIスモーク導入: runtime系3本 compile-eventsphase:"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 で型特化最適化検討時に導入可
### 🔍 SmokePhase 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を明示
```
# BuildCranelift込み推奨
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-inpolicy whitelist
NYASH_JIT_THRESHOLD=1 NYASH_JIT_HOSTCALL=1 \
./target/release/nyash --backend vm examples/jit_policy_whitelist_demo.nyash
# GC countingVMパス
./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切替とバリアサイト観測が可能: OKCountingGc/TRACE/STRICT
- HostCallRO/一部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

View File

@ -80,5 +80,15 @@ NYASH_JIT_EXEC=1 NYASH_JIT_THRESHOLD=1 NYASH_JIT_HOSTCALL=1 NYASH_JIT_EVENTS=1 \
# Mutating opt-inArray.push
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
```
## 例とスモーク(開発者向けクイック)
- 例一覧: `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は十分カバー追加不要
```

View File

@ -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さんとの深い技術討論を経て、実装可能な設計として成熟し、さらにスモークテストとの統合により品質保証も含めた完全なソリューションとなりました。*

View File

@ -1,6 +1,6 @@
# Nyash実行バックエンド完全ガイド
Nyashプログラミング言語は、**Everything is Box**哲学を維持しながら、3つの異なる実行方式をサポートしています。用途に応じて最適な実行方式を選択できます。
Nyashプログラミング言語は、**Everything is Box**哲学を維持しながら、4つの異なる実行方式をサポートしています。用途に応じて最適な実行方式を選択できます。
## 🚀 実行方式一覧
@ -9,6 +9,7 @@ Nyashプログラミング言語は、**Everything is Box**哲学を維持しな
| **インタープリター** | 開発・デバッグ | 直接AST実行、詳細ログ | 低速・高機能 |
| **VM** | 本番・高速実行 | MIR→VM実行 | 中速・最適化 |
| **WASM** | Web・サンドボックス | MIR→WASM変換 | 高速・移植性 |
| **AOT** (実験的) | 高速起動 | MIR→WASM→ネイティブ | 最高速・要wasmtime |
## 📋 CLIオプション
@ -54,6 +55,20 @@ nyash --compile-wasm program.nyash -o output.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
# 全バックエンド性能比較デフォルト5回実行
@ -186,6 +201,28 @@ NYASH_GC_COUNTING=1 NYASH_GC_TRACE=2 \
./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対応
### 特徴
@ -248,6 +285,58 @@ async function loadNyashWasm() {
</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→.cwasmwasmtimeプリコンパイル
- ❌ 完全なスタンドアロン実行ファイル生成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測定・修正

View File

@ -0,0 +1,52 @@
# JIT Events JSON (v0.1)
最小のイベントJSONスキーマ。観測の足場として、値は安定キーのみを出力します。
- 出力形態: 1行 = 1 JSONJSONL
- 出力先: 標準出力 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: 数値(処理時間ミリ秒)
## 出力例
compilelower:
```json
{"kind":"hostcall","function":"<jit>","id":"nyash.map.get_hh","decision":"allow","reason":"sig_ok","argc":2,"arg_types":["Handle","Handle"],"phase":"lower"}
```
runtimeexecute:
```json
{"kind":"hostcall","function":"<jit>","id":"nyash.array.push_h","decision":"fallback","reason":"policy_denied_mutating","argc":2,"arg_types":["Handle","I64"],"phase":"execute"}
```
trapexecute 中の失敗):
```json
{"kind":"trap","function":"<jit>","reason":"jit_execute_failed","ms":0,"phase":"execute"}
```
## 環境変数(抜粋)
- NYASH_JIT_EVENTS=1: 既定のruntime出力
- NYASH_JIT_EVENTS_COMPILE=1: compilelower出力
- 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
View 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
View 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
View 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
View 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-inJitPolicyBox
- 目的: 既定 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 whitelistevents分離
- 目的: read_only下でのfallback→allowwhitelistと、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"`(実績)が出る。
---
補足
- DebugConfigBoxevents/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出力先を指定可能。

View 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!"
}
}

View 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"
}
}

View 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
View 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
View File

@ -0,0 +1,4 @@
digraph "Helper.getv/2" {
node [shape=box, fontsize=10];
n2 [label="bb2\nENTRY"];
}

View File

@ -559,6 +559,10 @@ impl VM {
self.current_function = Some(function.signature.name.clone());
// Phase 10_a: JIT profiling (function entry)
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);
// Try compile if hot (no-op for now, returns fake handle)
let _ = jm.maybe_compile(&function.signature.name, function);

View File

@ -6,10 +6,13 @@ pub struct DebugConfigBox {
pub base: BoxBase,
// toggles/paths (staged until apply())
pub jit_events: bool,
pub jit_events_compile: bool,
pub jit_events_runtime: bool,
pub jit_stats: bool,
pub jit_stats_json: bool,
pub jit_dump: bool,
pub jit_dot_path: Option<String>,
pub jit_events_path: Option<String>,
}
impl DebugConfigBox {
@ -17,16 +20,21 @@ impl DebugConfigBox {
Self {
base: BoxBase::new(),
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_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_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> {
match name {
"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_json" => self.jit_stats_json = 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> {
match name {
"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)))
}
Box::new(VoidBox::new())
@ -46,6 +55,8 @@ impl DebugConfigBox {
pub fn get_flag(&self, name: &str) -> Box<dyn NyashBox> {
let v = match name {
"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_json" => self.jit_stats_json,
"jit_dump" => self.jit_dump,
@ -57,6 +68,7 @@ impl DebugConfigBox {
pub fn get_path(&self, name: &str) -> Box<dyn NyashBox> {
let v = match name {
"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(),
};
Box::new(StringBox::new(v))
@ -65,18 +77,31 @@ impl DebugConfigBox {
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); } };
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_JSON", self.jit_stats_json);
setb("NYASH_JIT_DUMP", self.jit_dump);
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())
}
pub fn summary(&self) -> Box<dyn NyashBox> {
let s = format!(
"jit_events={} jit_stats={} jit_stats_json={} jit_dump={} jit_dot={}",
self.jit_events, self.jit_stats, self.jit_stats_json, self.jit_dump,
self.jit_dot_path.clone().unwrap_or_else(|| "<none>".to_string())
"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_events_compile, self.jit_events_runtime,
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))
}

View File

@ -31,6 +31,10 @@ pub struct CliConfig {
pub jit_stats: bool,
pub jit_stats_json: 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_phi_min: bool,
pub jit_hostcall: bool,
@ -186,6 +190,30 @@ impl CliConfig {
.help("Dump JIT lowering summary (NYASH_JIT_DUMP=1)")
.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::new("jit-threshold")
.long("jit-threshold")
@ -265,6 +293,10 @@ impl CliConfig {
jit_stats: matches.get_flag("jit-stats"),
jit_stats_json: matches.get_flag("jit-stats-json"),
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_phi_min: matches.get_flag("jit-phi-min"),
jit_hostcall: matches.get_flag("jit-hostcall"),
@ -323,12 +355,19 @@ mod tests {
jit_stats: false,
jit_stats_json: false,
jit_dump: false,
jit_events: false,
jit_events_compile: false,
jit_events_runtime: false,
jit_events_path: None,
jit_threshold: None,
jit_phi_min: false,
jit_hostcall: false,
jit_handle_debug: false,
jit_native_f64: false,
jit_native_bool: false,
emit_cfg: None,
jit_only: false,
jit_direct: false,
};
assert_eq!(config.backend, "interpreter");

View File

@ -24,11 +24,15 @@ impl JitConfig {
pub fn from_env() -> Self {
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());
// 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 {
exec: getb("NYASH_JIT_EXEC"),
stats: getb("NYASH_JIT_STATS"),
stats_json: getb("NYASH_JIT_STATS_JSON"),
dump: getb("NYASH_JIT_DUMP"),
dump: dump_flag,
threshold,
phi_min: getb("NYASH_JIT_PHI_MIN"),
hostcall: getb("NYASH_JIT_HOSTCALL"),

View File

@ -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
#[cfg(feature = "cranelift-jit")]
{

View File

@ -6,11 +6,20 @@
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_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) {
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| {
@ -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) {
if !should_emit() { return; }
if !base_emit_enabled() { return; }
let ev = Event { kind, function, handle, ms, extra };
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);
}

View File

@ -66,18 +66,18 @@ pub fn array_set(args: &[VMValue]) -> VMValue {
if crate::jit::policy::current().read_only &&
!crate::jit::policy::current().hostcall_whitelist.iter().any(|s| s == SYM_ARRAY_SET)
{
crate::jit::events::emit(
"hostcall", "<jit>", None, None,
serde_json::json!({"id": SYM_ARRAY_SET, "decision":"fallback", "reason":"policy_denied_mutating"})
crate::jit::events::emit_runtime(
serde_json::json!({"id": SYM_ARRAY_SET, "decision":"fallback", "reason":"policy_denied_mutating"}),
"hostcall", "<jit>"
);
return VMValue::Integer(0);
}
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 res = arr.set(Box::new(IntegerBox::new(*idx)), val_box);
crate::jit::events::emit(
"hostcall", "<jit>", None, None,
serde_json::json!({"id": SYM_ARRAY_SET, "decision":"allow", "argc":3, "arg_types":["Handle","I64","Handle"]})
crate::jit::events::emit_runtime(
serde_json::json!({"id": SYM_ARRAY_SET, "decision":"allow", "argc":3, "arg_types":["Handle","I64","Handle"]}),
"hostcall", "<jit>"
);
return VMValue::from_nyash_box(res);
}
@ -88,18 +88,18 @@ pub fn array_push(args: &[VMValue]) -> VMValue {
if crate::jit::policy::current().read_only &&
!crate::jit::policy::current().hostcall_whitelist.iter().any(|s| s == SYM_ARRAY_PUSH)
{
crate::jit::events::emit(
"hostcall", "<jit>", None, None,
serde_json::json!({"id": SYM_ARRAY_PUSH, "decision":"fallback", "reason":"policy_denied_mutating"})
crate::jit::events::emit_runtime(
serde_json::json!({"id": SYM_ARRAY_PUSH, "decision":"fallback", "reason":"policy_denied_mutating"}),
"hostcall", "<jit>"
);
return VMValue::Integer(0);
}
if let (Some(arr), Some(value)) = (as_array(args), args.get(1)) {
let val_box: Box<dyn NyashBox> = value.to_nyash_box();
let res = arr.push(val_box);
crate::jit::events::emit(
"hostcall", "<jit>", None, None,
serde_json::json!({"id": SYM_ARRAY_PUSH, "decision":"allow", "argc":2, "arg_types":["Handle","Handle"]})
crate::jit::events::emit_runtime(
serde_json::json!({"id": SYM_ARRAY_PUSH, "decision":"allow", "argc":2, "arg_types":["Handle","Handle"]}),
"hostcall", "<jit>"
);
return VMValue::from_nyash_box(res);
}
@ -118,9 +118,9 @@ pub fn map_set(args: &[VMValue]) -> VMValue {
if crate::jit::policy::current().read_only &&
!crate::jit::policy::current().hostcall_whitelist.iter().any(|s| s == SYM_MAP_SET)
{
crate::jit::events::emit(
"hostcall", "<jit>", None, None,
serde_json::json!({"id": SYM_MAP_SET, "decision":"fallback", "reason":"policy_denied_mutating"})
crate::jit::events::emit_runtime(
serde_json::json!({"id": SYM_MAP_SET, "decision":"fallback", "reason":"policy_denied_mutating"}),
"hostcall", "<jit>"
);
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 val_box: Box<dyn NyashBox> = value.to_nyash_box();
let out = map.set(key_box, val_box);
crate::jit::events::emit(
"hostcall", "<jit>", None, None,
serde_json::json!({"id": SYM_MAP_SET, "decision":"allow", "argc":3, "arg_types":["Handle","Handle","Handle"]})
crate::jit::events::emit_runtime(
serde_json::json!({"id": SYM_MAP_SET, "decision":"allow", "argc":3, "arg_types":["Handle","Handle","Handle"]}),
"hostcall", "<jit>"
);
return VMValue::from_nyash_box(out);
}

View File

@ -23,9 +23,14 @@ fn ensure_default() {
let mut r = Registry::default();
// Read-only defaults
for s in [
"nyash.array.len_h", "nyash.any.length_h", "nyash.any.is_empty_h",
"nyash.map.size_h", "nyash.map.get_h", "nyash.string.charCodeAt_h",
"nyash.array.get_h"
"nyash.array.len_h",
"nyash.any.length_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()); }
// Mutating defaults
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.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 });
// 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));
}

View File

@ -138,15 +138,15 @@ use cranelift_codegen::ir::InstBuilder;
#[cfg(feature = "cranelift-jit")]
extern "C" fn nyash_host_stub0() -> i64 { 0 }
#[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")]
extern "C" fn nyash_math_cos_f64(x: f64) -> f64 { x.cos() }
#[cfg(feature = "cranelift-jit")]
extern "C" fn nyash_math_abs_f64(x: f64) -> f64 { x.abs() }
#[cfg(feature = "cranelift-jit")]
extern "C" fn nyash_math_min_f64(a: f64, b: f64) -> f64 { a.min(b) }
#[cfg(feature = "cranelift-jit")]
extern "C" fn nyash_math_max_f64(a: f64, b: f64) -> f64 { a.max(b) }
#[cfg(feature = "cranelift-jit")]
extern "C" fn nyash_array_len(arr_param_index: i64) -> i64 {
// 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) ===
#[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")]
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")]
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")]
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")]
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")]
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")]
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")]
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")]
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")]
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")]
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")]
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")]
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")]
impl IRBuilder for CraneliftBuilder {

View File

@ -642,16 +642,16 @@ impl LowerCore {
match method.as_str() {
"len" | "length" => {
if let Some(pidx) = self.param_index.get(array).copied() {
crate::jit::events::emit(
"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"]})
crate::jit::events::emit_lower(
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_host_call(crate::jit::r#extern::collections::SYM_ANY_LEN_H, 1, dst.is_some());
} else {
crate::jit::events::emit(
"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"]})
crate::jit::events::emit_lower(
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;
b.emit_const_i64(arr_idx);
@ -693,19 +693,16 @@ impl LowerCore {
match check_signature(&sym, &observed_kinds) {
Ok(()) => {
// allow: record decision; execution remains on VM for now (thin bridge)
crate::jit::events::emit(
"hostcall",
"<jit>",
None,
None,
serde_json::json!({
"id": sym,
"decision": "allow",
"reason": "sig_ok",
"argc": observed.len(),
"arg_types": arg_types
})
);
crate::jit::events::emit_lower(
serde_json::json!({
"id": sym,
"decision": "allow",
"reason": "sig_ok",
"argc": observed.len(),
"arg_types": arg_types
}),
"hostcall","<jit>"
);
// If native f64 is enabled, emit a typed hostcall to math extern
if crate::jit::config::current().native_f64 {
let (symbol, arity) = match method.as_str() {
@ -771,10 +768,10 @@ impl LowerCore {
// returns i64 0/1
b.emit_host_call(crate::jit::r#extern::collections::SYM_ANY_IS_EMPTY_H, 1, dst.is_some());
} else {
crate::jit::events::emit(
"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"]})
);
crate::jit::events::emit_lower(
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" => {
@ -785,23 +782,23 @@ impl LowerCore {
let wh = &pol.hostcall_whitelist;
let sym = crate::jit::r#extern::collections::SYM_ARRAY_PUSH_H;
let allowed = !pol.read_only || wh.iter().any(|s| s == sym);
crate::jit::events::emit(
"hostcall","<jit>",None,None,
crate::jit::events::emit_lower(
serde_json::json!({
"id": sym,
"decision": if allowed {"allow"} else {"fallback"},
"reason": if allowed {"sig_ok"} else {"policy_denied_mutating"},
"argc": 2,
"arg_types": ["Handle","I64"]
})
}),
"hostcall","<jit>"
);
b.emit_param_i64(pidx);
b.emit_const_i64(val);
b.emit_host_call(sym, 2, false);
} else {
crate::jit::events::emit(
"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"]})
crate::jit::events::emit_lower(
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;
b.emit_const_i64(arr_idx);
@ -812,16 +809,16 @@ impl LowerCore {
"size" => {
// MapBox.size(): argc=1 (map_handle)
if let Some(pidx) = self.param_index.get(array).copied() {
crate::jit::events::emit(
"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"]})
crate::jit::events::emit_lower(
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_host_call(crate::jit::r#extern::collections::SYM_MAP_SIZE_H, 1, dst.is_some());
} else {
crate::jit::events::emit(
"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"]})
crate::jit::events::emit_lower(
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;
b.emit_const_i64(map_idx);
@ -870,18 +867,15 @@ impl LowerCore {
crate::jit::r#extern::collections::SYM_MAP_GET_H
};
// Emit allow event
crate::jit::events::emit(
"hostcall",
"<jit>",
None,
None,
crate::jit::events::emit_lower(
serde_json::json!({
"id": event_id,
"decision": "allow",
"reason": "sig_ok",
"argc": observed_kinds.len(),
"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 matches!(key_kind, crate::jit::hostcall_registry::ArgKind::I64) {
@ -901,18 +895,15 @@ impl LowerCore {
}
Err(reason) => {
// Signature mismatch - log and fallback
crate::jit::events::emit(
"hostcall",
"<jit>",
None,
None,
crate::jit::events::emit_lower(
serde_json::json!({
"id": canonical,
"decision": "fallback",
"reason": reason,
"argc": observed_kinds.len(),
"arg_types": arg_types
})
}),
"hostcall","<jit>"
);
// 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 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) };
crate::jit::events::emit(
"hostcall",
"<jit>",
None,
None,
crate::jit::events::emit_lower(
serde_json::json!({
"id": sym,
"decision": decision.0,
"reason": decision.1,
"argc": observed_kinds.len(),
"arg_types": arg_types
})
}),
"hostcall","<jit>"
);
// no-op: VM側が処理する
}
@ -962,24 +950,24 @@ impl LowerCore {
let wh = &pol.hostcall_whitelist;
let sym = crate::jit::r#extern::collections::SYM_MAP_SET_H;
let allowed = !pol.read_only || wh.iter().any(|s| s == sym);
crate::jit::events::emit(
"hostcall","<jit>",None,None,
crate::jit::events::emit_lower(
serde_json::json!({
"id": sym,
"decision": if allowed {"allow"} else {"fallback"},
"reason": if allowed {"sig_ok"} else {"policy_denied_mutating"},
"argc": 3,
"arg_types": ["Handle","I64","I64"]
})
}),
"hostcall","<jit>"
);
b.emit_param_i64(pidx);
b.emit_const_i64(key);
b.emit_const_i64(val);
b.emit_host_call(sym, 3, false);
} else {
crate::jit::events::emit(
"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"]})
crate::jit::events::emit_lower(
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)
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);
crate::jit::events::emit(
"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"]})
crate::jit::events::emit_lower(
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_const_i64(idx);
b.emit_host_call(crate::jit::r#extern::collections::SYM_STRING_CHARCODE_AT_H, 2, dst.is_some());
} else {
crate::jit::events::emit(
"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"]})
crate::jit::events::emit_lower(
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>"
);
}
}

View 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
}

View File

@ -1,3 +1,4 @@
//! Lowering entry for JIT
pub mod core;
pub mod builder;
pub mod extern_thunks;

View File

@ -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() }
}
pub fn set_threshold(&mut self, t: u32) { self.threshold = t.max(1); }
pub fn record_entry(&mut self, func: &str) {
let c = self.hits.entry(func.to_string()).or_insert(0);
*c = c.saturating_add(1);
@ -155,7 +157,20 @@ impl JitManager {
let vmv = crate::jit::boundary::CallBoundaryBox::to_vm(ret_ty, v);
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
crate::jit::rt::handles::end_scope_clear();

View File

@ -66,6 +66,11 @@ impl NyashRunner {
}
// 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();
jc.exec |= self.config.jit_exec;
jc.stats |= self.config.jit_stats;
@ -77,10 +82,11 @@ impl NyashRunner {
jc.handle_debug |= self.config.jit_handle_debug;
jc.native_f64 |= self.config.jit_native_f64;
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 std::env::var("NYASH_JIT_EVENTS").ok().as_deref() == Some("1") && jc.threshold.is_none() {
jc.threshold = Some(1);
}
// If observability is enabled and no threshold is provided, force threshold=1 so lowering runs and emits events
let events_on = std::env::var("NYASH_JIT_EVENTS").ok().as_deref() == 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"); }
// Apply runtime capability probe (e.g., disable b1 ABI if unsupported)
let caps = nyash_rust::jit::config::probe_capabilities();

View 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! ==="