Phase 10.10: GC Switchable Runtime & Unified Debug System 実装完了
Phase 10.10の主要実装: - GcConfigBox: GC設定の実行時制御(counting/trace/barrier_strict) - DebugConfigBox: デバッグ設定の統一管理(JIT events/stats/dump/dot) - メソッドディスパッチ: system_methods.rsで両Boxのメソッド実装 - CountingGC動作確認: write_barriers正常カウント(VM実行時) 技術的詳細: - BoxCore/BoxBase統一アーキテクチャを活用 - setFlag/getFlag/apply/summaryメソッドで統一API提供 - 環境変数経由でVM/JITランタイムと連携 - GcConfigBox.apply()は次回実行から有効(ランタイム作成前に環境変数参照) テスト済み: - examples/gc_counting_demo.nyash: CountingGCの動作確認 - write_barriers=3でArray.push/set, Map.setを正しくカウント - NYASH_GC_TRACE=1でGC統計出力確認 Box-First哲学の体現: 設定も制御も観測もすべてBox! 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@ -336,3 +336,70 @@ NYASH_JIT_EXEC=1 NYASH_JIT_THRESHOLD=1 ./target/release/nyash --backend vm examp
|
||||
- `NYASH_JIT_EVENTS=1` でイベントJSONを確認(fallback/trap理由が出る)
|
||||
- `cargo clean -p nyash-rust` → 再ビルド
|
||||
- 数値緩和: `NYASH_JIT_HOSTCALL_RELAX_NUMERIC=1` で i64→f64 のコアーションを許容(既定は `native_f64=1` 時に有効)
|
||||
|
||||
### ✅ 10.9-β HostCall統合(完了)
|
||||
- math.*(関数スタイル): 署名(F64)一致時に allow/sig_ok をイベント出力。戻りは Float 表示で安定。
|
||||
- Map.get:
|
||||
- 受け手=param, key=I64 → `id: nyash.map.get_h`(Handle,I64)で allow かつ JIT実行
|
||||
- 受け手=param, key=Handle → `id: nyash.map.get_hh`(Handle,Handle)で allow かつ JIT実行(HH経路)
|
||||
- 受け手がparamでない場合 → fallback(`reason: receiver_not_param`)をイベントに記録し VM 実行
|
||||
- RO系(length/isEmpty/charCodeAt/size): 受け手=param は allow/sig_ok。受け手≠param は fallback/receiver_not_param を必ず記録。
|
||||
- Quick flags: `NYASH_JIT_EVENTS=1` のとき Runner が未指定の `NYASH_JIT_THRESHOLD` を自動で `1` に設定(Lowerが1回目から走ってイベントが出る)。
|
||||
- 代表コマンド:
|
||||
```bash
|
||||
# math.min(関数スタイル)
|
||||
NYASH_JIT_EXEC=1 NYASH_JIT_THRESHOLD=1 NYASH_JIT_NATIVE_F64=1 NYASH_JIT_EVENTS=1 \
|
||||
./target/release/nyash --backend vm examples/jit_math_function_style_min_float.nyash
|
||||
|
||||
# Map.get(パラメータ受け+Handleキー → 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
|
||||
|
||||
# Map.get(非パラメータ受け → fallback記録)
|
||||
NYASH_JIT_EXEC=1 NYASH_JIT_THRESHOLD=1 NYASH_JIT_HOSTCALL=1 NYASH_JIT_EVENTS=1 \
|
||||
./target/release/nyash --backend vm examples/jit_hostcall_map_get_handle.nyash
|
||||
```
|
||||
|
||||
### ⏭️ 10.9-δ(書き込みの導線のみ:次)
|
||||
- 方針: 既定は read-only(`policy.read_only=true`)。書き込みは fallback(`reason: policy_denied_mutating`)をイベントで可視化。(実装済)
|
||||
- 対象: Array.push/set, Map.set/delete など。イベントIDは `*_h`(Handle受け)に統一。
|
||||
- 仕上げ: `JitPolicyBox` に opt-in 追加(完了)
|
||||
- `set("read_only", true|false)` / `setWhitelistCsv("id1,id2")` / `addWhitelist("id")` / `clearWhitelist()`
|
||||
- プリセット: `enablePreset("mutating_minimal")`(Array.push_h) / `enablePreset("mutating_common")`(Array.push_h, Array.set_h, Map.set_h)
|
||||
|
||||
---
|
||||
|
||||
## 🚀 Phase 10.10 – Python→Nyash→MIR→VM/Native 実用化整備(着手)
|
||||
|
||||
目標(DoD 抜粋)
|
||||
- DebugConfig(Box+Rust)で dump/events/stats/dot/phi_min 等の切替を一本化(CLI/env/Box同期)
|
||||
- GC切替(Null/Counting)をCLI/Boxから操作可能、root/bARRIERサイト観測が動く
|
||||
- HostCall(RO/一部WO)が param でallow、非paramでfallback(イベントで確認可)
|
||||
- 代表サンプル最小集合でE2E安定
|
||||
|
||||
着手タスク(In Progress)
|
||||
1) DebugConfigBox(新規)
|
||||
- setFlag(name,bool)/setPath(name,string)/apply()/summary()(env反映)
|
||||
- 対応: NYASH_JIT_EVENTS/…/DUMP/…/DOT(最小)
|
||||
2) GC切替のCLI/Box連携(Pending)
|
||||
3) 代表サンプル/README整備(Pending)
|
||||
|
||||
---
|
||||
|
||||
### ⏸️ Checkpoint(2025-08-28 再起動用メモ)
|
||||
- 10.9-β/δ 完了(RO allow+fallback可視化、WOはpolicy/whitelistでopt-in)
|
||||
- HH経路(Map.get_hh)実装済み。戻りはCallBoundaryBoxでBoxRef復元。
|
||||
- JitPolicyBox: addWhitelist/clearWhitelist/enablePreset(mutating_minimal/common 等)
|
||||
- DebugConfigBox: JIT観測系(events/stats/dump/dot)をBoxで一元制御→apply()でenv反映
|
||||
- GcConfigBox: counting/trace/barrier_strict をBoxで操作→apply()でenv反映
|
||||
- 例:
|
||||
- jit_map_get_param_hh.nyash(HH直実行)
|
||||
- jit_policy_optin_mutating.nyash(mutatingのopt-in)
|
||||
- gc_counting_demo.nyash(CountingGcの可視化、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本だけ掲載
|
||||
|
||||
@ -0,0 +1,84 @@
|
||||
# Phase 10.10 – Python→Nyash→MIR→VM/Native ラインの実用化整備(Box-First 継続)
|
||||
|
||||
目的: Nyash→MIR→VM/Native の実行ラインを日常運用レベルに引き上げ、GC/デバッグ/HostCallの柱を整備する。
|
||||
|
||||
## ゴール(DoD)
|
||||
- エンドツーエンド実行ライン(Parser→AST→MIR→VM→JIT)がビルトインBoxで安定(RO/一部WO)
|
||||
- GC切替(Null/Counting)をCLI/Boxから操作可能、root領域APIが一箇所化
|
||||
- デバッグ/可視化の旗振り(DebugConfig/Box)でJIT/VM/イベント/DOTを一本化
|
||||
- HostCall: 読み取り系はparam受けでJIT直実行(allow)。書き込み系はポリシー/whitelistでopt-in可能
|
||||
- 最小ベンチと回帰(サンプル)でラインの劣化を検知
|
||||
|
||||
## 事前整備(現状)
|
||||
- HostCall基盤: Registry/Policy/Events/Boundary(10.9-β/δ完了)
|
||||
- JITイベント: `NYASH_JIT_EVENTS=1` 時に `threshold=1` 自動適用でLower確実実行
|
||||
- 戻り境界: CallBoundaryBox で JitValue→VMValue を一元化(ハンドル復元含む)
|
||||
|
||||
## ワークストリーム
|
||||
1) GC Switchable Runtime(phase_10_4_gc_switchable_runtime.md)
|
||||
- 目標: NullGc/CountingGc の切替、root領域/バリアAPIの一本化
|
||||
- タスク:
|
||||
- NyashRuntimeBuilder: GC選択をCLI/Box反映(NYASH_GC=none|counting など)
|
||||
- ScopeTracker/enter_root_region()/pin_roots() の公開インターフェース確認
|
||||
- CountingGcの統計出力(roots/reads/writes/safepoints)
|
||||
- 書き込み系HostCallにバリアサイトのフック(Map/Array set/push)
|
||||
- 受入: GC切替コマンドで統計差分が取れる/HostCall書き込みでバリアサイトが加算される
|
||||
|
||||
2) Unified Debug System(phase_10_8_unified_debug_system.md)
|
||||
- 目標: デバッグ/観測フラグを DebugConfig/Box に統合(CLI/env/Boxの単一路)
|
||||
- タスク:
|
||||
- DebugConfig(Rust側): dump/events/stats/dot/phi_min 等を集約
|
||||
- DebugConfigBox: Boxから get/set/apply/toJson/fromJson
|
||||
- Runner: CLI→DebugConfig→env/Box の一本化(env直読み排除)
|
||||
- イベント出力先: stdout/file 切替の設定(NYASH_JIT_EVENTS_PATH のBox反映)
|
||||
- 受入: Boxから apply 後、JIT/VM/DOTの挙動が即時反映/JSONLが指定先に出力
|
||||
|
||||
3) E2Eラインの実用化(builtin→pluginの足場)
|
||||
- 目標: ビルトインBoxで日常運用レベル、プラグインBoxはTLV HostCallの足場を準備
|
||||
- タスク:
|
||||
- Lowerカバレッジの整理(BoxCall/RO/WO・param/非paramの分岐ダンプ)
|
||||
- 署名管理: レジストリのオーバーロード運用方針(canonical idと派生idの整理)
|
||||
- 返り型推論: MIR Builderのreturn_type推定を確認(main/補助関数とも)
|
||||
- Plugin PoC: TLV/handle経由のread-onlyメソッド1つをHostCall経由で通す(allowログまで)
|
||||
- 受入: 代表サンプル(math/map/array/string)でallow/fallbackが意図通り、plugin PoCでallowイベントが出る
|
||||
|
||||
4) ドキュメントと例の整理
|
||||
- 目標: 例の最小集合化(param/非param/RO/WO/HH/Hの代表)、手順の簡潔化
|
||||
- タスク:
|
||||
- examples/: 重複の削減、README(実行コマンド付き)
|
||||
- phase_10_9/10_10 のガイドをCURRENT_TASKと相互参照
|
||||
- 受入: 主要ケースが examples/README からそのまま実行可
|
||||
|
||||
5) ベンチと回帰(最小)
|
||||
- 目標: ラインの性能/退行の早期検知
|
||||
- タスク:
|
||||
- ny_bench.nyash のケース整理(関数呼出/Map set-get/branch)
|
||||
- compare: VM vs JIT(ウォームアップ付き)
|
||||
- 受入: ベンチ出力に JIT/VM の比較が出る(改善/退行が見える)
|
||||
|
||||
## リスクと対策
|
||||
- param/非param 分岐の混乱: イベントに reason を必ず出す/docsにベストプラクティス(受け手をparam化)
|
||||
- mutatingの誤許可: JitPolicyBox の whitelist/プリセットのみで許可、既定はread_only
|
||||
- 署名の散逸: canonical id(例: nyash.map.get_h)と派生(_hh)の方針を明示
|
||||
|
||||
## 受け入れ基準(サマリ)
|
||||
- DebugConfig/Box/CLIの一貫挙動(apply後の即時反映)
|
||||
- GC切替とバリアサイト観測が可能
|
||||
- HostCall(RO/一部WO)が param でallow、非paramでfallback(イベントで確認可)
|
||||
- 代表サンプルが examples/README の手順で成功
|
||||
|
||||
## すぐ試せるコマンド(抜粋)
|
||||
```bash
|
||||
# math.min(関数スタイル)
|
||||
NYASH_JIT_EXEC=1 NYASH_JIT_THRESHOLD=1 NYASH_JIT_NATIVE_F64=1 NYASH_JIT_EVENTS=1 \
|
||||
./target/release/nyash --backend vm examples/jit_math_function_style_min_float.nyash
|
||||
|
||||
# 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(Array.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
|
||||
```
|
||||
|
||||
@ -85,6 +85,35 @@
|
||||
- b1正規化カウンタ: `b1_norm_count`(分岐条件/PHI)
|
||||
- HostCallイベント: `argc`/`arg_types`/`reason`でデバッグ容易化(mutatingは `policy_denied_mutating`)
|
||||
|
||||
### 🔎 HostCallイベントの基準(10.9-β)
|
||||
- 受け手が関数パラメータ(param)の場合は JIT直実行(allow/sig_ok)を基本にイベント出力
|
||||
- Map.get(Handle, I64): `id: nyash.map.get_h`, `arg_types: ["Handle","I64"]`
|
||||
- Map.get(Handle, Handle): `id: nyash.map.get_hh`, `arg_types: ["Handle","Handle"]`
|
||||
- length/isEmpty/charCodeAt/size 等も `*_h`(Handle受け)でallow
|
||||
- 受け手がparamでない場合は VMへフォールバック(fallback/receiver_not_param)をイベントで記録(読み取り系の可視化を保証)
|
||||
- 例: `id: nyash.any.length_h`, `decision: fallback`, `reason: receiver_not_param`
|
||||
- 数値緩和: `NYASH_JIT_HOSTCALL_RELAX_NUMERIC=1` または `NYASH_JIT_NATIVE_F64=1` で `I64→F64` コアーションを許容(sig_okに影響)
|
||||
|
||||
### 🧪 代表サンプル(E2E)
|
||||
```bash
|
||||
# math.*(関数スタイル): 署名一致でallow、戻りFloat表示
|
||||
NYASH_JIT_EXEC=1 NYASH_JIT_THRESHOLD=1 NYASH_JIT_NATIVE_F64=1 NYASH_JIT_EVENTS=1 \
|
||||
./target/release/nyash --backend vm examples/jit_math_function_style_min_float.nyash
|
||||
|
||||
# Map.get(パラメータ受け+Handleキー → 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
|
||||
|
||||
# Map.get(非パラメータ受け → fallback記録)
|
||||
NYASH_JIT_EXEC=1 NYASH_JIT_THRESHOLD=1 NYASH_JIT_HOSTCALL=1 NYASH_JIT_EVENTS=1 \
|
||||
./target/release/nyash --backend vm examples/jit_hostcall_map_get_handle.nyash
|
||||
```
|
||||
|
||||
### ⚙️ Quick flags(イベント観測を確実に)
|
||||
- `NYASH_JIT_EVENTS=1` のとき Runner が `NYASH_JIT_THRESHOLD=1` を自動適用(未指定の場合)
|
||||
- 1回目からLowerが走り、allow/fallbackのイベントが必ず出る
|
||||
- 明示的に `NYASH_JIT_THRESHOLD` を指定した場合はそちらを優先
|
||||
|
||||
## ⚠️ リスクとその箱での緩和
|
||||
- 署名不一致(args/ret)
|
||||
- HostcallRegistryBox で一元検査。不一致は `sig_mismatch` でイベント記録→VMへ
|
||||
@ -107,6 +136,20 @@
|
||||
- HostCallブリッジの拡大(Map.getの多型キー、String操作の追加)
|
||||
- CallBoundaryBox経由の `new`/副作用命令の段階的JIT化
|
||||
|
||||
## ✳️ 10.9-δ 書き込みの導線(運用)
|
||||
- 既定ポリシー: read_only(`NYASH_JIT_READ_ONLY=1`)で mutating はフォールバック(`reason: policy_denied_mutating`)。
|
||||
- JitPolicyBox でopt-in:
|
||||
```nyash
|
||||
P = new JitPolicyBox()
|
||||
P.set("read_only", true)
|
||||
P.addWhitelist("nyash.array.push_h") // 個別に許可
|
||||
// またはプリセット:
|
||||
P.enablePreset("mutating_minimal") // Array.push_h を許可
|
||||
```
|
||||
- イベント方針:
|
||||
- 受け手=param: allow/sig_ok(whitelist/オフ時はfallback/policy_denied_mutating)
|
||||
- 受け手≠param: fallback/receiver_not_param(可視化を保証)
|
||||
|
||||
---
|
||||
|
||||
最短ルート: 箱(Policy/Events/Registry/Boundary)を先に置き、読み取り系でJITを安全に通す→観測を増やす→署名とポリシーの一本化で切替点を固定→必要最小限のネイティブ型(f64/b1)を段階導入。
|
||||
|
||||
34
examples/gc_counting_demo.nyash
Normal file
34
examples/gc_counting_demo.nyash
Normal file
@ -0,0 +1,34 @@
|
||||
// GC Counting demo (VM path) — verifies CountingGc counters and barrier sites
|
||||
// Run:
|
||||
// ./target/release/nyash --backend vm examples/gc_counting_demo.nyash
|
||||
// Expect (with trace): [GC] counters: safepoints>0 read_barriers>=0 write_barriers>=0
|
||||
|
||||
static box Main {
|
||||
main() {
|
||||
// Turn on Counting GC + trace via Box-First path
|
||||
local G
|
||||
G = new GcConfigBox()
|
||||
G = G.setFlag("counting", true)
|
||||
G = G.setFlag("trace", true)
|
||||
G.apply()
|
||||
|
||||
// Make sure we don't engage JIT path for this demo (VM baseline)
|
||||
// If needed, you can set NYASH_JIT_EXEC=0 in the environment.
|
||||
|
||||
// Perform some mutating ops to hit write barriers on the VM path
|
||||
local A, M
|
||||
A = new ArrayBox()
|
||||
A.push(1)
|
||||
A.set(0, 2)
|
||||
|
||||
M = new MapBox()
|
||||
M.set("k", "v")
|
||||
|
||||
// Simple read ops (should register read barriers on some implementations)
|
||||
print(A.length())
|
||||
print(M.size())
|
||||
|
||||
return "done"
|
||||
}
|
||||
}
|
||||
|
||||
30
examples/jit_map_get_param_hh.nyash
Normal file
30
examples/jit_map_get_param_hh.nyash
Normal file
@ -0,0 +1,30 @@
|
||||
// Map.get with both receiver and key as function parameters
|
||||
// Expect: JIT hostcall allow for nyash.map.get_hh (Handle,Handle)
|
||||
// Run:
|
||||
// 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
|
||||
|
||||
box Helper {
|
||||
birth() {
|
||||
// no-op constructor
|
||||
}
|
||||
getv(m, k) {
|
||||
return m.get(k)
|
||||
}
|
||||
}
|
||||
|
||||
static box Main {
|
||||
main() {
|
||||
local m, k, v
|
||||
m = new MapBox()
|
||||
k = "key1"
|
||||
v = "value1"
|
||||
// Mutating ops fall back to VM by policy (read-only JIT)
|
||||
m.set(k, v)
|
||||
// Use user-defined box Helper so that getv is lowered into a MIR function
|
||||
local h
|
||||
h = new Helper()
|
||||
// Both m and k are parameters of Helper.getv/2 → JIT can use HH path
|
||||
return h.getv(m, k)
|
||||
}
|
||||
}
|
||||
35
examples/jit_policy_optin_mutating.nyash
Normal file
35
examples/jit_policy_optin_mutating.nyash
Normal file
@ -0,0 +1,35 @@
|
||||
// Demonstrate JIT mutating opt-in via JitPolicyBox whitelist
|
||||
// 1) Enable read_only → mutating hostcalls fallback (policy_denied_mutating)
|
||||
// 2) Whitelist nyash.array.push_h → allow + execute
|
||||
// Run:
|
||||
// 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
|
||||
|
||||
box Helper {
|
||||
birth() {}
|
||||
add(a) {
|
||||
a.push(1)
|
||||
return a.length()
|
||||
}
|
||||
}
|
||||
|
||||
static box Main {
|
||||
main() {
|
||||
local P, H, A
|
||||
P = new JitPolicyBox()
|
||||
// Enable read-only first (deny mutating)
|
||||
P.set("read_only", true)
|
||||
|
||||
H = new Helper()
|
||||
A = new ArrayBox()
|
||||
// This call will fallback by policy (observe events)
|
||||
H.add(A)
|
||||
|
||||
// Opt-in: allow array.push_h through whitelist
|
||||
P.setWhitelistCsv("nyash.array.push_h")
|
||||
|
||||
// This call will be allowed (observe allow event)
|
||||
return H.add(A)
|
||||
}
|
||||
}
|
||||
|
||||
@ -91,7 +91,7 @@ impl VMValue {
|
||||
pub fn to_nyash_box(&self) -> Box<dyn NyashBox> {
|
||||
match self {
|
||||
VMValue::Integer(i) => Box::new(IntegerBox::new(*i)),
|
||||
VMValue::Float(f) => Box::new(StringBox::new(&f.to_string())), // Simplified for now
|
||||
VMValue::Float(f) => Box::new(crate::boxes::FloatBox::new(*f)),
|
||||
VMValue::Bool(b) => Box::new(BoolBox::new(*b)),
|
||||
VMValue::String(s) => Box::new(StringBox::new(s)),
|
||||
VMValue::Future(f) => Box::new(f.clone()),
|
||||
@ -591,7 +591,7 @@ impl VM {
|
||||
self.pin_roots(args_vec.iter());
|
||||
if let Some(jm_mut) = self.jit_manager.as_mut() {
|
||||
if jm_mut.is_compiled(&function.signature.name) {
|
||||
if let Some(val) = jm_mut.execute_compiled(&function.signature.name, &args_vec) {
|
||||
if let Some(val) = jm_mut.execute_compiled(&function.signature.name, &function.signature.return_type, &args_vec) {
|
||||
// Exit scope before returning
|
||||
self.leave_root_region();
|
||||
self.scope_tracker.pop_scope();
|
||||
@ -609,7 +609,7 @@ impl VM {
|
||||
// Try to compile now and execute; if not possible, error out
|
||||
let _ = jm_mut.maybe_compile(&function.signature.name, function);
|
||||
if jm_mut.is_compiled(&function.signature.name) {
|
||||
if let Some(val) = jm_mut.execute_compiled(&function.signature.name, &args_vec) {
|
||||
if let Some(val) = jm_mut.execute_compiled(&function.signature.name, &function.signature.return_type, &args_vec) {
|
||||
self.leave_root_region();
|
||||
self.scope_tracker.pop_scope();
|
||||
return Ok(val);
|
||||
@ -1018,6 +1018,50 @@ impl VM {
|
||||
}
|
||||
}
|
||||
|
||||
// MathBox methods (minimal set)
|
||||
if let Some(math_box) = box_value.as_any().downcast_ref::<crate::boxes::math_box::MathBox>() {
|
||||
// Coerce numeric-like StringBox to FloatBox for function-style lowering path
|
||||
let mut coerce_num = |b: &Box<dyn NyashBox>| -> Box<dyn NyashBox> {
|
||||
if let Some(sb) = b.as_any().downcast_ref::<StringBox>() {
|
||||
let s = sb.value.trim();
|
||||
if let Ok(f) = s.parse::<f64>() { return Box::new(crate::boxes::FloatBox::new(f)); }
|
||||
if let Ok(i) = s.parse::<i64>() { return Box::new(IntegerBox::new(i)); }
|
||||
}
|
||||
b.clone_or_share()
|
||||
};
|
||||
match method {
|
||||
"min" => {
|
||||
if _args.len() >= 2 {
|
||||
let a = coerce_num(&_args[0]);
|
||||
let b = coerce_num(&_args[1]);
|
||||
return Ok(math_box.min(a, b));
|
||||
}
|
||||
return Ok(Box::new(StringBox::new("Error: min(a,b) requires 2 args")));
|
||||
}
|
||||
"max" => {
|
||||
if _args.len() >= 2 {
|
||||
let a = coerce_num(&_args[0]);
|
||||
let b = coerce_num(&_args[1]);
|
||||
return Ok(math_box.max(a, b));
|
||||
}
|
||||
return Ok(Box::new(StringBox::new("Error: max(a,b) requires 2 args")));
|
||||
}
|
||||
"abs" => {
|
||||
if let Some(v) = _args.get(0) { return Ok(math_box.abs(coerce_num(v))); }
|
||||
return Ok(Box::new(StringBox::new("Error: abs(x) requires 1 arg")));
|
||||
}
|
||||
"sin" => {
|
||||
if let Some(v) = _args.get(0) { return Ok(math_box.sin(coerce_num(v))); }
|
||||
return Ok(Box::new(StringBox::new("Error: sin(x) requires 1 arg")));
|
||||
}
|
||||
"cos" => {
|
||||
if let Some(v) = _args.get(0) { return Ok(math_box.cos(coerce_num(v))); }
|
||||
return Ok(Box::new(StringBox::new("Error: cos(x) requires 1 arg")));
|
||||
}
|
||||
_ => return Ok(Box::new(VoidBox::new())),
|
||||
}
|
||||
}
|
||||
|
||||
// SocketBox methods (minimal set + timeout variants)
|
||||
if let Some(sock) = box_value.as_any().downcast_ref::<crate::boxes::socket_box::SocketBox>() {
|
||||
match method {
|
||||
|
||||
@ -13,6 +13,32 @@ use super::vm::{VM, VMError, VMValue};
|
||||
impl VM {
|
||||
/// Call a method on a Box - simplified version of interpreter method dispatch
|
||||
pub(super) fn call_box_method_impl(&self, box_value: Box<dyn NyashBox>, method: &str, _args: Vec<Box<dyn NyashBox>>) -> Result<Box<dyn NyashBox>, VMError> {
|
||||
// MathBox methods (minimal set used in 10.9)
|
||||
if let Some(math) = box_value.as_any().downcast_ref::<crate::boxes::math_box::MathBox>() {
|
||||
match method {
|
||||
"min" => {
|
||||
if _args.len() >= 2 { return Ok(math.min(_args[0].clone_or_share(), _args[1].clone_or_share())); }
|
||||
return Ok(Box::new(StringBox::new("Error: min(a, b) requires 2 args")));
|
||||
}
|
||||
"max" => {
|
||||
if _args.len() >= 2 { return Ok(math.max(_args[0].clone_or_share(), _args[1].clone_or_share())); }
|
||||
return Ok(Box::new(StringBox::new("Error: max(a, b) requires 2 args")));
|
||||
}
|
||||
"abs" => {
|
||||
if let Some(v) = _args.get(0) { return Ok(math.abs(v.clone_or_share())); }
|
||||
return Ok(Box::new(StringBox::new("Error: abs(x) requires 1 arg")));
|
||||
}
|
||||
"sin" => {
|
||||
if let Some(v) = _args.get(0) { return Ok(math.sin(v.clone_or_share())); }
|
||||
return Ok(Box::new(StringBox::new("Error: sin(x) requires 1 arg")));
|
||||
}
|
||||
"cos" => {
|
||||
if let Some(v) = _args.get(0) { return Ok(math.cos(v.clone_or_share())); }
|
||||
return Ok(Box::new(StringBox::new("Error: cos(x) requires 1 arg")));
|
||||
}
|
||||
_ => { return Ok(Box::new(VoidBox::new())); }
|
||||
}
|
||||
}
|
||||
// ResultBox (NyashResultBox - new)
|
||||
if let Some(result_box) = box_value.as_any().downcast_ref::<crate::boxes::result::NyashResultBox>() {
|
||||
match method {
|
||||
@ -73,6 +99,39 @@ impl VM {
|
||||
}
|
||||
}
|
||||
|
||||
// JitPolicyBox methods
|
||||
if let Some(jpb) = box_value.as_any().downcast_ref::<crate::boxes::jit_policy_box::JitPolicyBox>() {
|
||||
match method {
|
||||
"get" => {
|
||||
if let Some(k) = _args.get(0) { return Ok(jpb.get_flag(&k.to_string_box().value)); }
|
||||
return Ok(Box::new(StringBox::new("get(name) requires 1 arg")));
|
||||
}
|
||||
"set" => {
|
||||
if _args.len() >= 2 {
|
||||
let k = _args[0].to_string_box().value;
|
||||
let v = _args[1].to_string_box().value;
|
||||
let on = matches!(v.as_str(), "1" | "true" | "True" | "on" | "ON");
|
||||
return Ok(jpb.set_flag(&k, on));
|
||||
}
|
||||
return Ok(Box::new(StringBox::new("set(name, bool) requires 2 args")));
|
||||
}
|
||||
"setWhitelistCsv" | "set_whitelist_csv" => {
|
||||
if let Some(s) = _args.get(0) { return Ok(jpb.set_whitelist_csv(&s.to_string_box().value)); }
|
||||
return Ok(Box::new(StringBox::new("setWhitelistCsv(csv) requires 1 arg")));
|
||||
}
|
||||
"addWhitelist" | "add_whitelist" => {
|
||||
if let Some(s) = _args.get(0) { return Ok(jpb.add_whitelist(&s.to_string_box().value)); }
|
||||
return Ok(Box::new(StringBox::new("addWhitelist(name) requires 1 arg")));
|
||||
}
|
||||
"clearWhitelist" | "clear_whitelist" => { return Ok(jpb.clear_whitelist()); }
|
||||
"enablePreset" | "enable_preset" => {
|
||||
if let Some(s) = _args.get(0) { return Ok(jpb.enable_preset(&s.to_string_box().value)); }
|
||||
return Ok(Box::new(StringBox::new("enablePreset(name) requires 1 arg")));
|
||||
}
|
||||
_ => { return Ok(Box::new(VoidBox::new())); }
|
||||
}
|
||||
}
|
||||
|
||||
// JitStatsBox methods
|
||||
if let Some(jsb) = box_value.as_any().downcast_ref::<crate::boxes::jit_stats_box::JitStatsBox>() {
|
||||
match method {
|
||||
|
||||
@ -292,6 +292,36 @@ impl BuiltinBoxFactory {
|
||||
}
|
||||
Ok(Box::new(crate::boxes::jit_config_box::JitConfigBox::new()))
|
||||
});
|
||||
|
||||
// JitPolicyBox (runtime JIT policy as a Box)
|
||||
self.register("JitPolicyBox", |args| {
|
||||
if !args.is_empty() {
|
||||
return Err(RuntimeError::InvalidOperation {
|
||||
message: format!("JitPolicyBox constructor expects 0 arguments, got {}", args.len()),
|
||||
});
|
||||
}
|
||||
Ok(Box::new(crate::boxes::jit_policy_box::JitPolicyBox::new()))
|
||||
});
|
||||
|
||||
// DebugConfigBox (runtime debug/observability switches)
|
||||
self.register("DebugConfigBox", |args| {
|
||||
if !args.is_empty() {
|
||||
return Err(RuntimeError::InvalidOperation {
|
||||
message: format!("DebugConfigBox constructor expects 0 arguments, got {}", args.len()),
|
||||
});
|
||||
}
|
||||
Ok(Box::new(crate::boxes::debug_config_box::DebugConfigBox::new()))
|
||||
});
|
||||
|
||||
// GcConfigBox (runtime GC switches)
|
||||
self.register("GcConfigBox", |args| {
|
||||
if !args.is_empty() {
|
||||
return Err(RuntimeError::InvalidOperation {
|
||||
message: format!("GcConfigBox constructor expects 0 arguments, got {}", args.len()),
|
||||
});
|
||||
}
|
||||
Ok(Box::new(crate::boxes::gc_config_box::GcConfigBox::new()))
|
||||
});
|
||||
}
|
||||
|
||||
/// Register I/O types
|
||||
|
||||
99
src/boxes/debug_config_box.rs
Normal file
99
src/boxes/debug_config_box.rs
Normal file
@ -0,0 +1,99 @@
|
||||
use crate::box_trait::{NyashBox, StringBox, BoolBox, VoidBox, BoxCore, BoxBase};
|
||||
use std::any::Any;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct DebugConfigBox {
|
||||
pub base: BoxBase,
|
||||
// toggles/paths (staged until apply())
|
||||
pub jit_events: bool,
|
||||
pub jit_stats: bool,
|
||||
pub jit_stats_json: bool,
|
||||
pub jit_dump: bool,
|
||||
pub jit_dot_path: Option<String>,
|
||||
}
|
||||
|
||||
impl DebugConfigBox {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
base: BoxBase::new(),
|
||||
jit_events: std::env::var("NYASH_JIT_EVENTS").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()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_flag(&mut self, name: &str, on: bool) -> Box<dyn NyashBox> {
|
||||
match name {
|
||||
"jit_events" => self.jit_events = on,
|
||||
"jit_stats" => self.jit_stats = on,
|
||||
"jit_stats_json" => self.jit_stats_json = on,
|
||||
"jit_dump" => self.jit_dump = on,
|
||||
_ => return Box::new(StringBox::new(format!("Unknown flag: {}", name)))
|
||||
}
|
||||
Box::new(VoidBox::new())
|
||||
}
|
||||
|
||||
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()),
|
||||
_ => return Box::new(StringBox::new(format!("Unknown path: {}", name)))
|
||||
}
|
||||
Box::new(VoidBox::new())
|
||||
}
|
||||
|
||||
pub fn get_flag(&self, name: &str) -> Box<dyn NyashBox> {
|
||||
let v = match name {
|
||||
"jit_events" => self.jit_events,
|
||||
"jit_stats" => self.jit_stats,
|
||||
"jit_stats_json" => self.jit_stats_json,
|
||||
"jit_dump" => self.jit_dump,
|
||||
_ => false,
|
||||
};
|
||||
Box::new(BoolBox::new(v))
|
||||
}
|
||||
|
||||
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(),
|
||||
_ => String::new(),
|
||||
};
|
||||
Box::new(StringBox::new(v))
|
||||
}
|
||||
|
||||
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_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); }
|
||||
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())
|
||||
);
|
||||
Box::new(StringBox::new(s))
|
||||
}
|
||||
}
|
||||
|
||||
impl BoxCore for DebugConfigBox {
|
||||
fn box_id(&self) -> u64 { self.base.id }
|
||||
fn parent_type_id(&self) -> Option<std::any::TypeId> { self.base.parent_type_id }
|
||||
fn fmt_box(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "DebugConfigBox") }
|
||||
fn as_any(&self) -> &dyn Any { self }
|
||||
fn as_any_mut(&mut self) -> &mut dyn Any { self }
|
||||
}
|
||||
|
||||
impl NyashBox for DebugConfigBox {
|
||||
fn equals(&self, other: &dyn NyashBox) -> BoolBox { BoolBox::new(other.as_any().is::<DebugConfigBox>()) }
|
||||
fn type_name(&self) -> &'static str { "DebugConfigBox" }
|
||||
fn clone_box(&self) -> Box<dyn NyashBox> { Box::new(Self { base: self.base.clone(), ..self.clone() }) }
|
||||
fn share_box(&self) -> Box<dyn NyashBox> { self.clone_box() }
|
||||
fn to_string_box(&self) -> StringBox { StringBox::new("DebugConfigBox".to_string()) }
|
||||
}
|
||||
62
src/boxes/gc_config_box.rs
Normal file
62
src/boxes/gc_config_box.rs
Normal file
@ -0,0 +1,62 @@
|
||||
use crate::box_trait::{NyashBox, StringBox, BoolBox, VoidBox, BoxCore, BoxBase};
|
||||
use std::any::Any;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct GcConfigBox { base: BoxBase, counting: bool, trace: bool, barrier_strict: bool }
|
||||
|
||||
impl GcConfigBox {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
base: BoxBase::new(),
|
||||
counting: std::env::var("NYASH_GC_COUNTING").ok().as_deref() == Some("1"),
|
||||
trace: std::env::var("NYASH_GC_TRACE").ok().as_deref() == Some("1"),
|
||||
barrier_strict: std::env::var("NYASH_GC_BARRIER_STRICT").ok().as_deref() == Some("1"),
|
||||
}
|
||||
}
|
||||
pub fn set_flag(&mut self, name: &str, on: bool) -> Box<dyn NyashBox> {
|
||||
match name {
|
||||
"counting" => self.counting = on,
|
||||
"trace" => self.trace = on,
|
||||
"barrier_strict" | "strict" => self.barrier_strict = on,
|
||||
_ => return Box::new(StringBox::new(format!("Unknown flag: {}", name)))
|
||||
}
|
||||
Box::new(VoidBox::new())
|
||||
}
|
||||
pub fn get_flag(&self, name: &str) -> Box<dyn NyashBox> {
|
||||
let v = match name {
|
||||
"counting" => self.counting,
|
||||
"trace" => self.trace,
|
||||
"barrier_strict" | "strict" => self.barrier_strict,
|
||||
_ => false,
|
||||
};
|
||||
Box::new(BoolBox::new(v))
|
||||
}
|
||||
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_GC_COUNTING", self.counting);
|
||||
setb("NYASH_GC_TRACE", self.trace);
|
||||
setb("NYASH_GC_BARRIER_STRICT", self.barrier_strict);
|
||||
Box::new(VoidBox::new())
|
||||
}
|
||||
pub fn summary(&self) -> Box<dyn NyashBox> {
|
||||
let s = format!("counting={} trace={} barrier_strict={}", self.counting, self.trace, self.barrier_strict);
|
||||
Box::new(StringBox::new(s))
|
||||
}
|
||||
}
|
||||
|
||||
impl BoxCore for GcConfigBox {
|
||||
fn box_id(&self) -> u64 { self.base.id }
|
||||
fn parent_type_id(&self) -> Option<std::any::TypeId> { self.base.parent_type_id }
|
||||
fn fmt_box(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "GcConfigBox") }
|
||||
fn as_any(&self) -> &dyn Any { self }
|
||||
fn as_any_mut(&mut self) -> &mut dyn Any { self }
|
||||
}
|
||||
|
||||
impl NyashBox for GcConfigBox {
|
||||
fn equals(&self, other: &dyn NyashBox) -> BoolBox { BoolBox::new(other.as_any().is::<GcConfigBox>()) }
|
||||
fn type_name(&self) -> &'static str { "GcConfigBox" }
|
||||
fn clone_box(&self) -> Box<dyn NyashBox> { Box::new(self.clone()) }
|
||||
fn share_box(&self) -> Box<dyn NyashBox> { self.clone_box() }
|
||||
fn to_string_box(&self) -> StringBox { StringBox::new(self.summary().to_string_box().value) }
|
||||
}
|
||||
|
||||
@ -48,5 +48,52 @@ impl JitPolicyBox {
|
||||
crate::jit::policy::set_current(cur);
|
||||
Box::new(VoidBox::new())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add_whitelist(&self, name: &str) -> Box<dyn NyashBox> {
|
||||
let mut cur = crate::jit::policy::current();
|
||||
if !cur.hostcall_whitelist.iter().any(|s| s == name) {
|
||||
cur.hostcall_whitelist.push(name.to_string());
|
||||
}
|
||||
crate::jit::policy::set_current(cur);
|
||||
Box::new(VoidBox::new())
|
||||
}
|
||||
|
||||
pub fn clear_whitelist(&self) -> Box<dyn NyashBox> {
|
||||
let mut cur = crate::jit::policy::current();
|
||||
cur.hostcall_whitelist.clear();
|
||||
crate::jit::policy::set_current(cur);
|
||||
Box::new(VoidBox::new())
|
||||
}
|
||||
|
||||
pub fn enable_preset(&self, name: &str) -> Box<dyn NyashBox> {
|
||||
let mut cur = crate::jit::policy::current();
|
||||
match name {
|
||||
// 最小: Array.push_h のみ許可(読み取り以外は変えない)
|
||||
"mutating_minimal" | "mutating_array_push" => {
|
||||
let id = crate::jit::r#extern::collections::SYM_ARRAY_PUSH_H;
|
||||
if !cur.hostcall_whitelist.iter().any(|s| s == id) {
|
||||
cur.hostcall_whitelist.push(id.to_string());
|
||||
}
|
||||
}
|
||||
// 例: Map.set_h も追加許可(必要に応じて拡張)
|
||||
"mutating_map_set" => {
|
||||
let id = crate::jit::r#extern::collections::SYM_MAP_SET_H;
|
||||
if !cur.hostcall_whitelist.iter().any(|s| s == id) {
|
||||
cur.hostcall_whitelist.push(id.to_string());
|
||||
}
|
||||
}
|
||||
// よく使う: Array.push_h + Array.set_h + Map.set_h を許可
|
||||
"mutating_common" => {
|
||||
let ids = [
|
||||
crate::jit::r#extern::collections::SYM_ARRAY_PUSH_H,
|
||||
crate::jit::r#extern::collections::SYM_ARRAY_SET_H,
|
||||
crate::jit::r#extern::collections::SYM_MAP_SET_H,
|
||||
];
|
||||
for id in ids { if !cur.hostcall_whitelist.iter().any(|s| s == id) { cur.hostcall_whitelist.push(id.to_string()); } }
|
||||
}
|
||||
_ => { return Box::new(StringBox::new(format!("Unknown preset: {}", name))); }
|
||||
}
|
||||
crate::jit::policy::set_current(cur);
|
||||
Box::new(VoidBox::new())
|
||||
}
|
||||
}
|
||||
|
||||
@ -79,6 +79,8 @@ pub mod jit_stats_box;
|
||||
pub mod jit_policy_box;
|
||||
pub mod jit_events_box;
|
||||
pub mod jit_hostcall_registry_box;
|
||||
pub mod debug_config_box;
|
||||
pub mod gc_config_box;
|
||||
|
||||
// Web専用Box群(ブラウザ環境でのみ利用可能)
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
|
||||
@ -23,5 +23,6 @@ pub mod data_methods; // BufferBox, JSONBox, RegexBox
|
||||
pub mod network_methods; // HttpClientBox, StreamBox
|
||||
pub mod p2p_methods; // IntentBox, P2PBox
|
||||
pub mod http_methods; // SocketBox, HTTPServerBox, HTTPRequestBox, HTTPResponseBox
|
||||
pub mod system_methods; // GcConfigBox, DebugConfigBox
|
||||
|
||||
// Re-export methods for easy access
|
||||
|
||||
171
src/interpreter/methods/system_methods.rs
Normal file
171
src/interpreter/methods/system_methods.rs
Normal file
@ -0,0 +1,171 @@
|
||||
/*!
|
||||
* System Methods Module
|
||||
*
|
||||
* Contains system-level Box type method implementations:
|
||||
* - GcConfigBox: Garbage collector configuration
|
||||
* - DebugConfigBox: Debug and observability configuration
|
||||
*/
|
||||
|
||||
use crate::ast::ASTNode;
|
||||
use crate::box_trait::{NyashBox, VoidBox, StringBox, BoolBox};
|
||||
use crate::boxes::gc_config_box::GcConfigBox;
|
||||
use crate::boxes::debug_config_box::DebugConfigBox;
|
||||
use crate::interpreter::{NyashInterpreter, RuntimeError};
|
||||
|
||||
impl NyashInterpreter {
|
||||
/// Execute GcConfigBox methods
|
||||
pub(crate) fn execute_gc_config_method(
|
||||
&mut self,
|
||||
gc_box: &GcConfigBox,
|
||||
method: &str,
|
||||
arguments: &[ASTNode],
|
||||
) -> Result<Box<dyn NyashBox>, RuntimeError> {
|
||||
match method {
|
||||
"setFlag" => {
|
||||
if arguments.len() != 2 {
|
||||
return Err(RuntimeError::InvalidOperation {
|
||||
message: format!("GcConfigBox.setFlag expects 2 arguments, got {}", arguments.len()),
|
||||
});
|
||||
}
|
||||
let name = self.execute_expression(&arguments[0])?;
|
||||
let on = self.execute_expression(&arguments[1])?;
|
||||
|
||||
let name_str = name.to_string_box().value;
|
||||
let on_bool = if let Some(b) = on.as_any().downcast_ref::<BoolBox>() {
|
||||
b.value
|
||||
} else {
|
||||
on.to_string_box().value.to_lowercase() == "true"
|
||||
};
|
||||
|
||||
let mut gc_clone = gc_box.clone();
|
||||
gc_clone.set_flag(&name_str, on_bool);
|
||||
Ok(Box::new(gc_clone))
|
||||
}
|
||||
|
||||
"getFlag" => {
|
||||
if arguments.len() != 1 {
|
||||
return Err(RuntimeError::InvalidOperation {
|
||||
message: format!("GcConfigBox.getFlag expects 1 argument, got {}", arguments.len()),
|
||||
});
|
||||
}
|
||||
let name = self.execute_expression(&arguments[0])?;
|
||||
let name_str = name.to_string_box().value;
|
||||
Ok(gc_box.get_flag(&name_str))
|
||||
}
|
||||
|
||||
"apply" => {
|
||||
if !arguments.is_empty() {
|
||||
return Err(RuntimeError::InvalidOperation {
|
||||
message: format!("GcConfigBox.apply expects 0 arguments, got {}", arguments.len()),
|
||||
});
|
||||
}
|
||||
Ok(gc_box.apply())
|
||||
}
|
||||
|
||||
"summary" => {
|
||||
if !arguments.is_empty() {
|
||||
return Err(RuntimeError::InvalidOperation {
|
||||
message: format!("GcConfigBox.summary expects 0 arguments, got {}", arguments.len()),
|
||||
});
|
||||
}
|
||||
Ok(gc_box.summary())
|
||||
}
|
||||
|
||||
_ => Err(RuntimeError::InvalidOperation {
|
||||
message: format!("GcConfigBox has no method '{}'", method),
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
/// Execute DebugConfigBox methods
|
||||
pub(crate) fn execute_debug_config_method(
|
||||
&mut self,
|
||||
debug_box: &DebugConfigBox,
|
||||
method: &str,
|
||||
arguments: &[ASTNode],
|
||||
) -> Result<Box<dyn NyashBox>, RuntimeError> {
|
||||
match method {
|
||||
"setFlag" => {
|
||||
if arguments.len() != 2 {
|
||||
return Err(RuntimeError::InvalidOperation {
|
||||
message: format!("DebugConfigBox.setFlag expects 2 arguments, got {}", arguments.len()),
|
||||
});
|
||||
}
|
||||
let name = self.execute_expression(&arguments[0])?;
|
||||
let on = self.execute_expression(&arguments[1])?;
|
||||
|
||||
let name_str = name.to_string_box().value;
|
||||
let on_bool = if let Some(b) = on.as_any().downcast_ref::<BoolBox>() {
|
||||
b.value
|
||||
} else {
|
||||
on.to_string_box().value.to_lowercase() == "true"
|
||||
};
|
||||
|
||||
let mut debug_clone = debug_box.clone();
|
||||
debug_clone.set_flag(&name_str, on_bool);
|
||||
Ok(Box::new(debug_clone))
|
||||
}
|
||||
|
||||
"setPath" => {
|
||||
if arguments.len() != 2 {
|
||||
return Err(RuntimeError::InvalidOperation {
|
||||
message: format!("DebugConfigBox.setPath expects 2 arguments, got {}", arguments.len()),
|
||||
});
|
||||
}
|
||||
let name = self.execute_expression(&arguments[0])?;
|
||||
let path = self.execute_expression(&arguments[1])?;
|
||||
|
||||
let name_str = name.to_string_box().value;
|
||||
let path_str = path.to_string_box().value;
|
||||
|
||||
let mut debug_clone = debug_box.clone();
|
||||
debug_clone.set_path(&name_str, &path_str);
|
||||
Ok(Box::new(debug_clone))
|
||||
}
|
||||
|
||||
"getFlag" => {
|
||||
if arguments.len() != 1 {
|
||||
return Err(RuntimeError::InvalidOperation {
|
||||
message: format!("DebugConfigBox.getFlag expects 1 argument, got {}", arguments.len()),
|
||||
});
|
||||
}
|
||||
let name = self.execute_expression(&arguments[0])?;
|
||||
let name_str = name.to_string_box().value;
|
||||
Ok(debug_box.get_flag(&name_str))
|
||||
}
|
||||
|
||||
"getPath" => {
|
||||
if arguments.len() != 1 {
|
||||
return Err(RuntimeError::InvalidOperation {
|
||||
message: format!("DebugConfigBox.getPath expects 1 argument, got {}", arguments.len()),
|
||||
});
|
||||
}
|
||||
let name = self.execute_expression(&arguments[0])?;
|
||||
let name_str = name.to_string_box().value;
|
||||
Ok(debug_box.get_path(&name_str))
|
||||
}
|
||||
|
||||
"apply" => {
|
||||
if !arguments.is_empty() {
|
||||
return Err(RuntimeError::InvalidOperation {
|
||||
message: format!("DebugConfigBox.apply expects 0 arguments, got {}", arguments.len()),
|
||||
});
|
||||
}
|
||||
Ok(debug_box.apply())
|
||||
}
|
||||
|
||||
"summary" => {
|
||||
if !arguments.is_empty() {
|
||||
return Err(RuntimeError::InvalidOperation {
|
||||
message: format!("DebugConfigBox.summary expects 0 arguments, got {}", arguments.len()),
|
||||
});
|
||||
}
|
||||
Ok(debug_box.summary())
|
||||
}
|
||||
|
||||
_ => Err(RuntimeError::InvalidOperation {
|
||||
message: format!("DebugConfigBox has no method '{}'", method),
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -4,6 +4,7 @@ use crate::ast::ASTNode;
|
||||
use crate::box_trait::{NyashBox, StringBox, IntegerBox, BoolBox, BoxCore};
|
||||
use crate::boxes::{ArrayBox, FloatBox, BufferBox, ResultBox, FutureBox, JSONBox, HttpClientBox, StreamBox, RegexBox, MathBox};
|
||||
use crate::boxes::{null_box, time_box, map_box, random_box, sound_box, debug_box, console_box};
|
||||
use crate::boxes::{gc_config_box::GcConfigBox, debug_config_box::DebugConfigBox};
|
||||
use crate::boxes::file;
|
||||
use crate::channel_box::ChannelBox;
|
||||
use super::{NyashInterpreter, RuntimeError};
|
||||
@ -109,6 +110,14 @@ impl NyashInterpreter {
|
||||
if let Some(b) = obj.as_any().downcast_ref::<console_box::ConsoleBox>() {
|
||||
return Some(self.execute_console_method(b, method, arguments));
|
||||
}
|
||||
// GcConfigBox
|
||||
if let Some(b) = obj.as_any().downcast_ref::<GcConfigBox>() {
|
||||
return Some(self.execute_gc_config_method(b, method, arguments));
|
||||
}
|
||||
// DebugConfigBox
|
||||
if let Some(b) = obj.as_any().downcast_ref::<DebugConfigBox>() {
|
||||
return Some(self.execute_debug_config_method(b, method, arguments));
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
62
src/jit/boundary.rs
Normal file
62
src/jit/boundary.rs
Normal file
@ -0,0 +1,62 @@
|
||||
//! CallBoundaryBox: unify JIT→VM return conversion in one place
|
||||
use crate::backend::vm::VMValue;
|
||||
use super::abi::JitValue;
|
||||
|
||||
pub struct CallBoundaryBox;
|
||||
|
||||
impl CallBoundaryBox {
|
||||
pub fn to_vm(ret_ty: &crate::mir::MirType, v: JitValue) -> VMValue {
|
||||
match ret_ty {
|
||||
crate::mir::MirType::Float => match v {
|
||||
JitValue::F64(f) => VMValue::Float(f),
|
||||
JitValue::I64(i) => VMValue::Float(i as f64),
|
||||
JitValue::Bool(b) => VMValue::Float(if b {1.0} else {0.0}),
|
||||
JitValue::Handle(h) => {
|
||||
if let Some(_) = crate::jit::rt::handles::get(h) { VMValue::Float(0.0) } else { VMValue::Float(0.0) }
|
||||
}
|
||||
},
|
||||
crate::mir::MirType::Integer => match v {
|
||||
JitValue::I64(i) => VMValue::Integer(i),
|
||||
JitValue::F64(f) => VMValue::Integer(f as i64),
|
||||
JitValue::Bool(b) => VMValue::Integer(if b {1} else {0}),
|
||||
JitValue::Handle(h) => {
|
||||
if let Some(_) = crate::jit::rt::handles::get(h) { VMValue::Integer(0) } else { VMValue::Integer(0) }
|
||||
}
|
||||
},
|
||||
crate::mir::MirType::Bool => match v {
|
||||
JitValue::Bool(b) => VMValue::Bool(b),
|
||||
JitValue::I64(i) => VMValue::Bool(i != 0),
|
||||
JitValue::F64(f) => VMValue::Bool(f != 0.0),
|
||||
JitValue::Handle(h) => {
|
||||
if let Some(_) = crate::jit::rt::handles::get(h) { VMValue::Bool(true) } else { VMValue::Bool(false) }
|
||||
}
|
||||
},
|
||||
// Box-like returns: if we received a handle id (encoded as I64), resolve to BoxRef; also honor explicit Handle
|
||||
crate::mir::MirType::Box(_) | crate::mir::MirType::String | crate::mir::MirType::Array(_) | crate::mir::MirType::Future(_) => {
|
||||
match v {
|
||||
JitValue::I64(i) => {
|
||||
let h = i as u64;
|
||||
if let Some(arc) = crate::jit::rt::handles::get(h) { VMValue::BoxRef(arc) }
|
||||
else { VMValue::Integer(i) }
|
||||
}
|
||||
JitValue::Handle(h) => {
|
||||
if let Some(arc) = crate::jit::rt::handles::get(h) { VMValue::BoxRef(arc) } else { VMValue::Void }
|
||||
}
|
||||
JitValue::F64(f) => VMValue::Float(f),
|
||||
JitValue::Bool(b) => VMValue::Bool(b),
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
// Default adapter with heuristic: treat I64 matching a known handle as BoxRef
|
||||
match v {
|
||||
JitValue::I64(i) => {
|
||||
let h = i as u64;
|
||||
if let Some(arc) = crate::jit::rt::handles::get(h) { VMValue::BoxRef(arc) }
|
||||
else { super::abi::adapter::from_jit_value(JitValue::I64(i)) }
|
||||
}
|
||||
_ => super::abi::adapter::from_jit_value(v)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
44
src/jit/extern/collections.rs
vendored
44
src/jit/extern/collections.rs
vendored
@ -21,6 +21,7 @@ pub const SYM_ARRAY_PUSH_H: &str = "nyash.array.push_h";
|
||||
pub const SYM_ARRAY_LAST_H: &str = "nyash.array.last_h";
|
||||
pub const SYM_MAP_SIZE_H: &str = "nyash.map.size_h";
|
||||
pub const SYM_MAP_GET_H: &str = "nyash.map.get_h";
|
||||
pub const SYM_MAP_GET_HH: &str = "nyash.map.get_hh";
|
||||
pub const SYM_MAP_SET_H: &str = "nyash.map.set_h";
|
||||
pub const SYM_MAP_HAS_H: &str = "nyash.map.has_h";
|
||||
// Generic read-only helper
|
||||
@ -61,18 +62,45 @@ pub fn array_get(args: &[VMValue]) -> VMValue {
|
||||
}
|
||||
|
||||
pub fn array_set(args: &[VMValue]) -> VMValue {
|
||||
// Enforce policy for mutating operation
|
||||
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"})
|
||||
);
|
||||
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"]})
|
||||
);
|
||||
return VMValue::from_nyash_box(res);
|
||||
}
|
||||
VMValue::BoxRef(Arc::new(StringBox::new("Error: array.set expects (ArrayBox, i64, value)")))
|
||||
}
|
||||
|
||||
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"})
|
||||
);
|
||||
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"]})
|
||||
);
|
||||
return VMValue::from_nyash_box(res);
|
||||
}
|
||||
VMValue::BoxRef(Arc::new(StringBox::new("Error: array.push expects (ArrayBox, value)")))
|
||||
@ -87,10 +115,24 @@ pub fn map_get(args: &[VMValue]) -> VMValue {
|
||||
}
|
||||
|
||||
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"})
|
||||
);
|
||||
return VMValue::Integer(0);
|
||||
}
|
||||
if let (Some(map), Some(key), Some(value)) = (as_map(args), args.get(1), args.get(2)) {
|
||||
let key_box: Box<dyn NyashBox> = key.to_nyash_box();
|
||||
let val_box: Box<dyn NyashBox> = value.to_nyash_box();
|
||||
return VMValue::from_nyash_box(map.set(key_box, val_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"]})
|
||||
);
|
||||
return VMValue::from_nyash_box(out);
|
||||
}
|
||||
VMValue::BoxRef(Arc::new(StringBox::new("Error: map.set expects (MapBox, key, value)")))
|
||||
}
|
||||
|
||||
@ -12,7 +12,8 @@ pub enum HostcallKind { ReadOnly, Mutating }
|
||||
struct Registry {
|
||||
ro: HashSet<String>,
|
||||
mu: HashSet<String>,
|
||||
sig: HashMap<String, Signature>,
|
||||
// Allow multiple signatures per symbol (overloads)
|
||||
sig: HashMap<String, Vec<Signature>>,
|
||||
}
|
||||
|
||||
static REG: OnceCell<RwLock<Registry>> = OnceCell::new();
|
||||
@ -32,16 +33,18 @@ fn ensure_default() {
|
||||
] { r.mu.insert(s.to_string()); }
|
||||
// Signatures (v0): register known symbols with simple arg/ret kinds
|
||||
// math.* thin bridge: f64 signatures only (allow when args match exactly)
|
||||
r.sig.insert("nyash.math.sin".to_string(), Signature { args: vec![ArgKind::F64], ret: ArgKind::F64 });
|
||||
r.sig.insert("nyash.math.cos".to_string(), Signature { args: vec![ArgKind::F64], ret: ArgKind::F64 });
|
||||
r.sig.insert("nyash.math.abs".to_string(), Signature { args: vec![ArgKind::F64], ret: ArgKind::F64 });
|
||||
r.sig.insert("nyash.math.min".to_string(), Signature { args: vec![ArgKind::F64, ArgKind::F64], ret: ArgKind::F64 });
|
||||
r.sig.insert("nyash.math.max".to_string(), Signature { args: vec![ArgKind::F64, ArgKind::F64], ret: ArgKind::F64 });
|
||||
r.sig.entry("nyash.math.sin".to_string()).or_default().push(Signature { args: vec![ArgKind::F64], ret: ArgKind::F64 });
|
||||
r.sig.entry("nyash.math.cos".to_string()).or_default().push(Signature { args: vec![ArgKind::F64], ret: ArgKind::F64 });
|
||||
r.sig.entry("nyash.math.abs".to_string()).or_default().push(Signature { args: vec![ArgKind::F64], ret: ArgKind::F64 });
|
||||
r.sig.entry("nyash.math.min".to_string()).or_default().push(Signature { args: vec![ArgKind::F64, ArgKind::F64], ret: ArgKind::F64 });
|
||||
r.sig.entry("nyash.math.max".to_string()).or_default().push(Signature { args: vec![ArgKind::F64, ArgKind::F64], ret: ArgKind::F64 });
|
||||
// Collections (handle-based)
|
||||
r.sig.insert("nyash.map.get_h".to_string(), Signature { args: vec![ArgKind::Handle, ArgKind::I64], ret: ArgKind::Handle });
|
||||
r.sig.insert("nyash.map.size_h".to_string(), Signature { args: vec![ArgKind::Handle], ret: ArgKind::I64 });
|
||||
r.sig.insert("nyash.array.get_h".to_string(), Signature { args: vec![ArgKind::Handle, ArgKind::I64], ret: ArgKind::Handle });
|
||||
r.sig.insert("nyash.array.len_h".to_string(), Signature { args: vec![ArgKind::Handle], ret: ArgKind::I64 });
|
||||
// Map get: support both integer and handle keys (overload)
|
||||
r.sig.entry("nyash.map.get_h".to_string()).or_default().push(Signature { args: vec![ArgKind::Handle, ArgKind::I64], ret: ArgKind::Handle });
|
||||
r.sig.entry("nyash.map.get_h".to_string()).or_default().push(Signature { args: vec![ArgKind::Handle, ArgKind::Handle], ret: ArgKind::Handle });
|
||||
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 });
|
||||
let _ = REG.set(RwLock::new(r));
|
||||
}
|
||||
|
||||
@ -124,30 +127,40 @@ pub fn set_signature_csv(symbol: &str, args_csv: &str, ret_str: &str) -> bool {
|
||||
if !ok { return false; }
|
||||
let sig = Signature { args, ret };
|
||||
if let Some(lock) = REG.get() {
|
||||
if let Ok(mut w) = lock.write() { w.sig.insert(symbol.to_string(), sig); return true; }
|
||||
if let Ok(mut w) = lock.write() {
|
||||
w.sig.entry(symbol.to_string()).or_default().push(sig);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
pub fn get_signature(symbol: &str) -> Option<Signature> {
|
||||
ensure_default();
|
||||
REG.get().and_then(|lock| lock.read().ok()).and_then(|g| g.sig.get(symbol).cloned())
|
||||
}
|
||||
|
||||
/// Check observed args against a registered signature.
|
||||
/// - If no signature is registered for the symbol, returns Ok(()) to be permissive in v0.
|
||||
/// - Returns Err("sig_mismatch") when arg length or kinds differ.
|
||||
pub fn check_signature(symbol: &str, observed_args: &[ArgKind]) -> Result<(), &'static str> {
|
||||
ensure_default();
|
||||
if let Some(sig) = get_signature(symbol) {
|
||||
if sig.args.len() != observed_args.len() { return Err("sig_mismatch"); }
|
||||
let cfg_now = crate::jit::config::current();
|
||||
let relax = cfg_now.relax_numeric || cfg_now.native_f64;
|
||||
for (expected, observed) in sig.args.iter().zip(observed_args.iter()) {
|
||||
if expected == observed { continue; }
|
||||
// v0 coercion: allow I64 → F64 only when relaxed numeric is enabled
|
||||
if relax && matches!(expected, ArgKind::F64) && matches!(observed, ArgKind::I64) { continue; }
|
||||
return Err("sig_mismatch");
|
||||
if let Some(lock) = REG.get() {
|
||||
if let Ok(g) = lock.read() {
|
||||
if let Some(sigs) = g.sig.get(symbol) {
|
||||
let cfg_now = crate::jit::config::current();
|
||||
let relax = cfg_now.relax_numeric || cfg_now.native_f64;
|
||||
// Match against any one of the overload signatures
|
||||
'outer: for sig in sigs.iter() {
|
||||
if sig.args.len() != observed_args.len() { continue; }
|
||||
for (expected, observed) in sig.args.iter().zip(observed_args.iter()) {
|
||||
if expected == observed { continue; }
|
||||
// v0 coercion: allow I64 → F64 only when relaxed numeric is enabled
|
||||
if relax && matches!(expected, ArgKind::F64) && matches!(observed, ArgKind::I64) { continue; }
|
||||
// Mismatch for this candidate signature
|
||||
continue 'outer;
|
||||
}
|
||||
// All args matched for this signature
|
||||
return Ok(());
|
||||
}
|
||||
// No overload matched
|
||||
return Err("sig_mismatch");
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
|
||||
@ -248,10 +248,12 @@ extern "C" fn nyash_array_len_h(handle: u64) -> i64 {
|
||||
}
|
||||
#[cfg(feature = "cranelift-jit")]
|
||||
extern "C" fn nyash_array_push_h(handle: u64, val: i64) -> i64 {
|
||||
// Policy/Events: classify and decide
|
||||
// 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;
|
||||
match (classify(sym), crate::jit::policy::current().read_only) {
|
||||
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,
|
||||
@ -308,7 +310,9 @@ extern "C" fn nyash_array_last_h(handle: u64) -> i64 {
|
||||
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;
|
||||
if classify(sym) == HostcallKind::Mutating && crate::jit::policy::current().read_only {
|
||||
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"})
|
||||
@ -359,10 +363,33 @@ extern "C" fn nyash_map_get_h(handle: u64, key: i64) -> i64 {
|
||||
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;
|
||||
if classify(sym) == HostcallKind::Mutating && crate::jit::policy::current().read_only {
|
||||
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"})
|
||||
@ -385,6 +412,10 @@ extern "C" fn nyash_map_set_h(handle: u64, key: i64, val: i64) -> i64 {
|
||||
}
|
||||
#[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));
|
||||
@ -1153,6 +1184,7 @@ impl CraneliftBuilder {
|
||||
builder.symbol(c::SYM_ARRAY_LAST_H, nyash_array_last_h as *const u8);
|
||||
builder.symbol(c::SYM_MAP_SIZE_H, nyash_map_size_h as *const u8);
|
||||
builder.symbol(c::SYM_MAP_GET_H, nyash_map_get_h as *const u8);
|
||||
builder.symbol(c::SYM_MAP_GET_HH, nyash_map_get_hh as *const u8);
|
||||
builder.symbol(c::SYM_MAP_SET_H, nyash_map_set_h as *const u8);
|
||||
builder.symbol(c::SYM_MAP_HAS_H, nyash_map_has_h as *const u8);
|
||||
builder.symbol(c::SYM_ANY_LEN_H, nyash_any_length_h as *const u8);
|
||||
|
||||
@ -278,7 +278,7 @@ impl LowerCore {
|
||||
}
|
||||
for instr in bb.instructions.iter() {
|
||||
self.cover_if_supported(instr);
|
||||
self.try_emit(builder, instr, *bb_id, func);
|
||||
if let Err(e) = self.try_emit(builder, instr, *bb_id, func) { return Err(e); }
|
||||
// Track FloatBox creations for later arg classification
|
||||
if let crate::mir::MirInstruction::NewBox { dst, box_type, .. } = instr { if box_type == "FloatBox" { self.float_box_values.insert(*dst); } }
|
||||
if let crate::mir::MirInstruction::Copy { dst, src } = instr { if self.float_box_values.contains(src) { self.float_box_values.insert(*dst); } }
|
||||
@ -375,7 +375,7 @@ impl LowerCore {
|
||||
_ => { /* other terminators handled via generic emission below */ }
|
||||
}
|
||||
// Also allow other terminators to be emitted if needed
|
||||
self.try_emit(builder, term, *bb_id, func);
|
||||
if let Err(e) = self.try_emit(builder, term, *bb_id, func) { return Err(e); }
|
||||
}
|
||||
}
|
||||
builder.end_function();
|
||||
@ -469,7 +469,7 @@ impl LowerCore {
|
||||
if supported { self.covered += 1; } else { self.unsupported += 1; }
|
||||
}
|
||||
|
||||
fn try_emit(&mut self, b: &mut dyn IRBuilder, instr: &MirInstruction, cur_bb: crate::mir::BasicBlockId, func: &crate::mir::MirFunction) {
|
||||
fn try_emit(&mut self, b: &mut dyn IRBuilder, instr: &MirInstruction, cur_bb: crate::mir::BasicBlockId, func: &crate::mir::MirFunction) -> Result<(), String> {
|
||||
use crate::mir::MirInstruction as I;
|
||||
match instr {
|
||||
I::NewBox { dst, box_type, args } => {
|
||||
@ -543,7 +543,7 @@ impl LowerCore {
|
||||
BinaryOp::Mod => BinOpKind::Mod,
|
||||
// Not yet supported in Core-1
|
||||
BinaryOp::And | BinaryOp::Or
|
||||
| BinaryOp::BitAnd | BinaryOp::BitOr | BinaryOp::BitXor | BinaryOp::Shl | BinaryOp::Shr => { return; }
|
||||
| BinaryOp::BitAnd | BinaryOp::BitOr | BinaryOp::BitXor | BinaryOp::Shl | BinaryOp::Shr => { return Ok(()); }
|
||||
};
|
||||
b.emit_binop(kind);
|
||||
if let (Some(a), Some(b)) = (self.known_i64.get(lhs), self.known_i64.get(rhs)) {
|
||||
@ -642,10 +642,17 @@ impl LowerCore {
|
||||
match method.as_str() {
|
||||
"len" | "length" => {
|
||||
if let Some(pidx) = self.param_index.get(array).copied() {
|
||||
// Handle-based generic length: supports ArrayBox and StringBox
|
||||
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"]})
|
||||
);
|
||||
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"]})
|
||||
);
|
||||
let arr_idx = -1;
|
||||
b.emit_const_i64(arr_idx);
|
||||
b.emit_host_call(crate::jit::r#extern::collections::SYM_ARRAY_LEN, 1, dst.is_some());
|
||||
@ -662,21 +669,24 @@ impl LowerCore {
|
||||
else { observed.push(ArgKind::I64); }
|
||||
}
|
||||
// Prepare arg_types for event payload
|
||||
// Classify argument kinds using known maps and FloatBox tracking; as a last resort, scan for NewBox(FloatBox)
|
||||
// Classify argument kinds using TyEnv when available; fallback to known maps/FloatBox tracking
|
||||
let mut observed_kinds: Vec<crate::jit::hostcall_registry::ArgKind> = Vec::new();
|
||||
for v in args.iter() {
|
||||
let mut kind = if self.known_f64.contains_key(v) || self.float_box_values.contains(v) {
|
||||
crate::jit::hostcall_registry::ArgKind::F64
|
||||
} else { crate::jit::hostcall_registry::ArgKind::I64 };
|
||||
if let crate::jit::hostcall_registry::ArgKind::I64 = kind {
|
||||
'scanv: for (_bb_id, bb) in func.blocks.iter() {
|
||||
for ins in bb.instructions.iter() {
|
||||
if let crate::mir::MirInstruction::NewBox { dst, box_type, .. } = ins {
|
||||
if *dst == *v && box_type == "FloatBox" { kind = crate::jit::hostcall_registry::ArgKind::F64; break 'scanv; }
|
||||
}
|
||||
let kind = if let Some(mt) = func.metadata.value_types.get(v) {
|
||||
match mt {
|
||||
crate::mir::MirType::Float => crate::jit::hostcall_registry::ArgKind::F64,
|
||||
crate::mir::MirType::Integer => crate::jit::hostcall_registry::ArgKind::I64,
|
||||
crate::mir::MirType::Bool => crate::jit::hostcall_registry::ArgKind::I64, // b1はI64 0/1に正規化
|
||||
crate::mir::MirType::String | crate::mir::MirType::Box(_) => crate::jit::hostcall_registry::ArgKind::Handle,
|
||||
_ => {
|
||||
if self.known_f64.contains_key(v) || self.float_box_values.contains(v) { crate::jit::hostcall_registry::ArgKind::F64 }
|
||||
else { crate::jit::hostcall_registry::ArgKind::I64 }
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if self.known_f64.contains_key(v) || self.float_box_values.contains(v) { crate::jit::hostcall_registry::ArgKind::F64 }
|
||||
else { crate::jit::hostcall_registry::ArgKind::I64 }
|
||||
};
|
||||
observed_kinds.push(kind);
|
||||
}
|
||||
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();
|
||||
@ -753,19 +763,46 @@ impl LowerCore {
|
||||
}
|
||||
"isEmpty" | "empty" => {
|
||||
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_IS_EMPTY_H, "decision":"allow", "reason":"sig_ok", "argc":1, "arg_types":["Handle"]})
|
||||
);
|
||||
b.emit_param_i64(pidx);
|
||||
// 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"]})
|
||||
);
|
||||
}
|
||||
}
|
||||
"push" => {
|
||||
// argc=2: (array_handle, value)
|
||||
let val = args.get(0).and_then(|v| self.known_i64.get(v)).copied().unwrap_or(0);
|
||||
if let Some(pidx) = self.param_index.get(array).copied() {
|
||||
let pol = crate::jit::policy::current();
|
||||
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,
|
||||
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"]
|
||||
})
|
||||
);
|
||||
b.emit_param_i64(pidx);
|
||||
b.emit_const_i64(val);
|
||||
b.emit_host_call(crate::jit::r#extern::collections::SYM_ARRAY_PUSH_H, 2, false);
|
||||
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"]})
|
||||
);
|
||||
let arr_idx = -1;
|
||||
b.emit_const_i64(arr_idx);
|
||||
b.emit_const_i64(val);
|
||||
@ -775,21 +812,145 @@ 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"]})
|
||||
);
|
||||
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"]})
|
||||
);
|
||||
let map_idx = -1;
|
||||
b.emit_const_i64(map_idx);
|
||||
b.emit_host_call(crate::jit::r#extern::collections::SYM_MAP_SIZE, 1, dst.is_some());
|
||||
}
|
||||
}
|
||||
"get" => {
|
||||
// MapBox.get(key): (map_handle, key_i64)
|
||||
// MapBox.get(key): check TyEnv to choose signature (handle|i64)
|
||||
if let Some(pidx) = self.param_index.get(array).copied() {
|
||||
let key = args.get(0).and_then(|v| self.known_i64.get(v)).copied().unwrap_or(0);
|
||||
b.emit_param_i64(pidx);
|
||||
b.emit_const_i64(key);
|
||||
b.emit_host_call(crate::jit::r#extern::collections::SYM_MAP_GET_H, 2, dst.is_some());
|
||||
// Build observed arg kinds using TyEnv when available
|
||||
let mut observed_kinds: Vec<crate::jit::hostcall_registry::ArgKind> = Vec::new();
|
||||
// First arg = map handle
|
||||
observed_kinds.push(crate::jit::hostcall_registry::ArgKind::Handle);
|
||||
// Second arg = key (classify from TyEnv; fallback to I64 if known integer literal)
|
||||
let key_kind = if let Some(key_vid) = args.get(0) {
|
||||
if let Some(mt) = func.metadata.value_types.get(key_vid) {
|
||||
match mt {
|
||||
crate::mir::MirType::Float => crate::jit::hostcall_registry::ArgKind::I64, // coerced via VM path
|
||||
crate::mir::MirType::Integer => crate::jit::hostcall_registry::ArgKind::I64,
|
||||
crate::mir::MirType::Bool => crate::jit::hostcall_registry::ArgKind::I64,
|
||||
crate::mir::MirType::String | crate::mir::MirType::Box(_) => crate::jit::hostcall_registry::ArgKind::Handle,
|
||||
_ => {
|
||||
if let Some(_) = self.known_i64.get(key_vid) { crate::jit::hostcall_registry::ArgKind::I64 } else { crate::jit::hostcall_registry::ArgKind::Handle }
|
||||
}
|
||||
}
|
||||
} else if let Some(_) = self.known_i64.get(key_vid) {
|
||||
crate::jit::hostcall_registry::ArgKind::I64
|
||||
} else {
|
||||
crate::jit::hostcall_registry::ArgKind::Handle
|
||||
}
|
||||
} else { crate::jit::hostcall_registry::ArgKind::I64 };
|
||||
observed_kinds.push(key_kind);
|
||||
|
||||
// Prepare arg_types strings for events
|
||||
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();
|
||||
|
||||
// Signature check against registry (supports overloads) using canonical id
|
||||
let canonical = "nyash.map.get_h";
|
||||
match crate::jit::hostcall_registry::check_signature(canonical, &observed_kinds) {
|
||||
Ok(()) => {
|
||||
// Choose symbol id for event/emit
|
||||
let event_id = if matches!(key_kind, crate::jit::hostcall_registry::ArgKind::Handle)
|
||||
&& args.get(0).and_then(|v| self.param_index.get(v)).is_some() {
|
||||
crate::jit::r#extern::collections::SYM_MAP_GET_HH
|
||||
} else {
|
||||
crate::jit::r#extern::collections::SYM_MAP_GET_H
|
||||
};
|
||||
// Emit allow event
|
||||
crate::jit::events::emit(
|
||||
"hostcall",
|
||||
"<jit>",
|
||||
None,
|
||||
None,
|
||||
serde_json::json!({
|
||||
"id": event_id,
|
||||
"decision": "allow",
|
||||
"reason": "sig_ok",
|
||||
"argc": observed_kinds.len(),
|
||||
"arg_types": arg_types
|
||||
})
|
||||
);
|
||||
// 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) {
|
||||
let key_i = args.get(0).and_then(|v| self.known_i64.get(v)).copied().unwrap_or(0);
|
||||
b.emit_param_i64(pidx);
|
||||
b.emit_const_i64(key_i);
|
||||
b.emit_host_call(crate::jit::r#extern::collections::SYM_MAP_GET_H, 2, dst.is_some());
|
||||
} else if let Some(kp) = args.get(0).and_then(|v| self.param_index.get(v)).copied() {
|
||||
// key is a function parameter (handle), use HH variant
|
||||
b.emit_param_i64(pidx);
|
||||
b.emit_param_i64(kp);
|
||||
b.emit_host_call(crate::jit::r#extern::collections::SYM_MAP_GET_HH, 2, dst.is_some());
|
||||
} else {
|
||||
// Not a param: fall back (receiver_not_param or key_not_param already logged)
|
||||
// no emission; VM will execute
|
||||
}
|
||||
}
|
||||
Err(reason) => {
|
||||
// Signature mismatch - log and fallback
|
||||
crate::jit::events::emit(
|
||||
"hostcall",
|
||||
"<jit>",
|
||||
None,
|
||||
None,
|
||||
serde_json::json!({
|
||||
"id": canonical,
|
||||
"decision": "fallback",
|
||||
"reason": reason,
|
||||
"argc": observed_kinds.len(),
|
||||
"arg_types": arg_types
|
||||
})
|
||||
);
|
||||
// No emission; VM path will handle
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Receiver is not a function parameter; we cannot obtain a stable runtime handle.
|
||||
// Still classify and emit an event for visibility, then fallback to VM.
|
||||
let mut observed_kinds: Vec<crate::jit::hostcall_registry::ArgKind> = Vec::new();
|
||||
observed_kinds.push(crate::jit::hostcall_registry::ArgKind::Handle); // Map receiver (conceptually a handle)
|
||||
let key_kind = if let Some(key_vid) = args.get(0) {
|
||||
if let Some(mt) = func.metadata.value_types.get(key_vid) {
|
||||
match mt {
|
||||
crate::mir::MirType::Integer => crate::jit::hostcall_registry::ArgKind::I64,
|
||||
crate::mir::MirType::Float => crate::jit::hostcall_registry::ArgKind::I64,
|
||||
crate::mir::MirType::Bool => crate::jit::hostcall_registry::ArgKind::I64,
|
||||
crate::mir::MirType::String | crate::mir::MirType::Box(_) => crate::jit::hostcall_registry::ArgKind::Handle,
|
||||
_ => crate::jit::hostcall_registry::ArgKind::Handle,
|
||||
}
|
||||
} else { crate::jit::hostcall_registry::ArgKind::Handle }
|
||||
} else { crate::jit::hostcall_registry::ArgKind::Handle };
|
||||
observed_kinds.push(key_kind);
|
||||
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,
|
||||
serde_json::json!({
|
||||
"id": sym,
|
||||
"decision": decision.0,
|
||||
"reason": decision.1,
|
||||
"argc": observed_kinds.len(),
|
||||
"arg_types": arg_types
|
||||
})
|
||||
);
|
||||
// no-op: VM側が処理する
|
||||
}
|
||||
}
|
||||
"set" => {
|
||||
@ -797,19 +958,47 @@ impl LowerCore {
|
||||
if let Some(pidx) = self.param_index.get(array).copied() {
|
||||
let key = args.get(0).and_then(|v| self.known_i64.get(v)).copied().unwrap_or(0);
|
||||
let val = args.get(1).and_then(|v| self.known_i64.get(v)).copied().unwrap_or(0);
|
||||
let pol = crate::jit::policy::current();
|
||||
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,
|
||||
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"]
|
||||
})
|
||||
);
|
||||
b.emit_param_i64(pidx);
|
||||
b.emit_const_i64(key);
|
||||
b.emit_const_i64(val);
|
||||
b.emit_host_call(crate::jit::r#extern::collections::SYM_MAP_SET_H, 3, false);
|
||||
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"]})
|
||||
);
|
||||
}
|
||||
}
|
||||
"charCodeAt" => {
|
||||
// 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"]})
|
||||
);
|
||||
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"]})
|
||||
);
|
||||
}
|
||||
}
|
||||
"has" => {
|
||||
@ -827,6 +1016,7 @@ impl LowerCore {
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -134,7 +134,7 @@ impl JitManager {
|
||||
}
|
||||
|
||||
/// 10_c: execute compiled function if present (stub: empty args). Returns Some(VMValue) if JIT path was taken.
|
||||
pub fn execute_compiled(&mut self, func: &str, args: &[crate::backend::vm::VMValue]) -> Option<crate::backend::vm::VMValue> {
|
||||
pub fn execute_compiled(&mut self, func: &str, ret_ty: &crate::mir::MirType, args: &[crate::backend::vm::VMValue]) -> Option<crate::backend::vm::VMValue> {
|
||||
if let Some(h) = self.handle_of(func) {
|
||||
// Expose args to both legacy VM hostcalls and new JIT ABI TLS
|
||||
crate::jit::rt::set_legacy_vm_args(args);
|
||||
@ -149,7 +149,12 @@ impl JitManager {
|
||||
eprintln!("[JIT] exec_time_ms={} for {}", dt.as_millis(), func);
|
||||
}
|
||||
let res = match out {
|
||||
Some(v) => { self.exec_ok = self.exec_ok.saturating_add(1); Some(crate::jit::abi::adapter::from_jit_value(v)) }
|
||||
Some(v) => {
|
||||
self.exec_ok = self.exec_ok.saturating_add(1);
|
||||
// Use CallBoundaryBox to convert JitValue → VMValue with MIR ret type hint
|
||||
let vmv = crate::jit::boundary::CallBoundaryBox::to_vm(ret_ty, v);
|
||||
Some(vmv)
|
||||
}
|
||||
None => { self.exec_trap = self.exec_trap.saturating_add(1); None }
|
||||
};
|
||||
// Clear handles created during this call
|
||||
|
||||
@ -10,3 +10,4 @@ pub mod config;
|
||||
pub mod policy;
|
||||
pub mod events;
|
||||
pub mod hostcall_registry;
|
||||
pub mod boundary;
|
||||
|
||||
@ -61,6 +61,9 @@ pub struct MirBuilder {
|
||||
|
||||
/// Remember class of object fields after assignments: (base_id, field) -> class_name
|
||||
pub(super) field_origin_class: HashMap<(ValueId, String), String>,
|
||||
|
||||
/// Optional per-value type annotations (MIR-level): ValueId -> MirType
|
||||
pub(super) value_types: HashMap<ValueId, super::MirType>,
|
||||
}
|
||||
|
||||
impl MirBuilder {
|
||||
@ -78,6 +81,7 @@ impl MirBuilder {
|
||||
user_defined_boxes: HashSet::new(),
|
||||
weak_fields_by_box: HashMap::new(),
|
||||
field_origin_class: HashMap::new(),
|
||||
value_types: HashMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
@ -254,13 +258,19 @@ impl MirBuilder {
|
||||
value: Some(result_value),
|
||||
});
|
||||
}
|
||||
// Infer return type from TyEnv (value_types)
|
||||
if let Some(mt) = self.value_types.get(&result_value).cloned() {
|
||||
function.signature.return_type = mt;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Finalize and return module
|
||||
let mut module = self.current_module.take().unwrap();
|
||||
let function = self.current_function.take().unwrap();
|
||||
let mut function = self.current_function.take().unwrap();
|
||||
// Flush value_types (TyEnv) into function metadata
|
||||
function.metadata.value_types = self.value_types.clone();
|
||||
module.add_function(function);
|
||||
|
||||
Ok(module)
|
||||
@ -451,6 +461,15 @@ impl MirBuilder {
|
||||
|
||||
/// Build a literal value
|
||||
fn build_literal(&mut self, literal: LiteralValue) -> Result<ValueId, String> {
|
||||
// Determine type without moving literal
|
||||
let ty_for_dst = match &literal {
|
||||
LiteralValue::Integer(_) => Some(super::MirType::Integer),
|
||||
LiteralValue::Float(_) => Some(super::MirType::Float),
|
||||
LiteralValue::Bool(_) => Some(super::MirType::Bool),
|
||||
LiteralValue::String(_) => Some(super::MirType::String),
|
||||
_ => None,
|
||||
};
|
||||
|
||||
let const_value = match literal {
|
||||
LiteralValue::Integer(n) => ConstValue::Integer(n),
|
||||
LiteralValue::Float(f) => ConstValue::Float(f),
|
||||
@ -465,6 +484,8 @@ impl MirBuilder {
|
||||
dst,
|
||||
value: const_value,
|
||||
})?;
|
||||
// Annotate type
|
||||
if let Some(ty) = ty_for_dst { self.value_types.insert(dst, ty); }
|
||||
|
||||
Ok(dst)
|
||||
}
|
||||
@ -483,6 +504,8 @@ impl MirBuilder {
|
||||
self.emit_instruction(MirInstruction::BinOp {
|
||||
dst, op, lhs, rhs
|
||||
})?;
|
||||
// Arithmetic results are integers for now (Core-1)
|
||||
self.value_types.insert(dst, super::MirType::Integer);
|
||||
},
|
||||
|
||||
// Comparison operations
|
||||
@ -497,6 +520,7 @@ impl MirBuilder {
|
||||
(li, ri)
|
||||
} else { (lhs, rhs) };
|
||||
self.emit_instruction(MirInstruction::Compare { dst, op, lhs: lhs2, rhs: rhs2 })?;
|
||||
self.value_types.insert(dst, super::MirType::Bool);
|
||||
},
|
||||
}
|
||||
|
||||
@ -515,6 +539,8 @@ impl MirBuilder {
|
||||
op: mir_op,
|
||||
operand: operand_val,
|
||||
})?;
|
||||
// Unary op result type heuristic (neg/not): keep integer/bool for now
|
||||
// Leave unset to avoid mislabel
|
||||
|
||||
Ok(dst)
|
||||
}
|
||||
@ -610,6 +636,8 @@ impl MirBuilder {
|
||||
args: math_args,
|
||||
effects: EffectMask::READ.add(Effect::ReadHeap),
|
||||
})?;
|
||||
// math.* returns Float
|
||||
self.value_types.insert(dst, super::MirType::Float);
|
||||
return Ok(dst);
|
||||
}
|
||||
|
||||
@ -1098,6 +1126,14 @@ impl MirBuilder {
|
||||
box_type: class.clone(),
|
||||
args: arg_values.clone(),
|
||||
})?;
|
||||
// Annotate primitive boxes
|
||||
match class.as_str() {
|
||||
"IntegerBox" => { self.value_types.insert(dst, super::MirType::Integer); },
|
||||
"FloatBox" => { self.value_types.insert(dst, super::MirType::Float); },
|
||||
"BoolBox" => { self.value_types.insert(dst, super::MirType::Bool); },
|
||||
"StringBox" => { self.value_types.insert(dst, super::MirType::String); },
|
||||
other => { self.value_types.insert(dst, super::MirType::Box(other.to_string())); }
|
||||
}
|
||||
|
||||
// Record origin for optimization: dst was created by NewBox of class
|
||||
self.value_origin_newbox.insert(dst, class.clone());
|
||||
@ -1360,6 +1396,8 @@ impl MirBuilder {
|
||||
args: math_args,
|
||||
effects: EffectMask::READ.add(Effect::ReadHeap),
|
||||
})?;
|
||||
// math.* returns Float
|
||||
self.value_types.insert(result_id, super::MirType::Float);
|
||||
return Ok(result_id);
|
||||
}
|
||||
}
|
||||
|
||||
@ -66,6 +66,9 @@ pub struct FunctionMetadata {
|
||||
|
||||
/// Optimization hints
|
||||
pub optimization_hints: Vec<String>,
|
||||
|
||||
/// Optional per-value type map (for builders that annotate ValueId types)
|
||||
pub value_types: std::collections::HashMap<ValueId, MirType>,
|
||||
}
|
||||
|
||||
impl MirFunction {
|
||||
|
||||
@ -77,6 +77,10 @@ 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 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();
|
||||
@ -321,7 +325,42 @@ impl NyashRunner {
|
||||
match vm.execute_module(&compile_result.module) {
|
||||
Ok(result) => {
|
||||
println!("✅ VM execution completed successfully!");
|
||||
println!("Result: {:?}", result);
|
||||
if let Some(func) = compile_result.module.functions.get("main") {
|
||||
use nyash_rust::mir::MirType;
|
||||
use nyash_rust::box_trait::{NyashBox, IntegerBox, BoolBox, StringBox};
|
||||
use nyash_rust::boxes::FloatBox;
|
||||
let (ety, sval) = match &func.signature.return_type {
|
||||
MirType::Float => {
|
||||
if let Some(fb) = result.as_any().downcast_ref::<FloatBox>() {
|
||||
("Float", format!("{}", fb.value))
|
||||
} else if let Some(ib) = result.as_any().downcast_ref::<IntegerBox>() {
|
||||
("Float", format!("{}", ib.value as f64))
|
||||
} else { ("Float", result.to_string_box().value) }
|
||||
}
|
||||
MirType::Integer => {
|
||||
if let Some(ib) = result.as_any().downcast_ref::<IntegerBox>() {
|
||||
("Integer", ib.value.to_string())
|
||||
} else { ("Integer", result.to_string_box().value) }
|
||||
}
|
||||
MirType::Bool => {
|
||||
if let Some(bb) = result.as_any().downcast_ref::<BoolBox>() {
|
||||
("Bool", bb.value.to_string())
|
||||
} else if let Some(ib) = result.as_any().downcast_ref::<IntegerBox>() {
|
||||
("Bool", (ib.value != 0).to_string())
|
||||
} else { ("Bool", result.to_string_box().value) }
|
||||
}
|
||||
MirType::String => {
|
||||
if let Some(sb) = result.as_any().downcast_ref::<StringBox>() {
|
||||
("String", sb.value.clone())
|
||||
} else { ("String", result.to_string_box().value) }
|
||||
}
|
||||
_ => { (result.type_name(), result.to_string_box().value) }
|
||||
};
|
||||
println!("ResultType(MIR): {}", ety);
|
||||
println!("Result: {}", sval);
|
||||
} else {
|
||||
println!("Result: {:?}", result);
|
||||
}
|
||||
},
|
||||
Err(e) => {
|
||||
eprintln!("❌ VM execution error: {}", e);
|
||||
|
||||
@ -61,7 +61,43 @@ impl NyashRunner {
|
||||
match vm.execute_module(&compile_result.module) {
|
||||
Ok(result) => {
|
||||
println!("✅ VM execution completed successfully!");
|
||||
println!("Result: {:?}", result);
|
||||
// Pretty-print using MIR return type when available to avoid Void-looking floats/bools
|
||||
if let Some(func) = compile_result.module.functions.get("main") {
|
||||
use nyash_rust::mir::MirType;
|
||||
use nyash_rust::box_trait::{NyashBox, IntegerBox, BoolBox, StringBox};
|
||||
use nyash_rust::boxes::FloatBox;
|
||||
let (ety, sval) = match &func.signature.return_type {
|
||||
MirType::Float => {
|
||||
if let Some(fb) = result.as_any().downcast_ref::<FloatBox>() {
|
||||
("Float", format!("{}", fb.value))
|
||||
} else if let Some(ib) = result.as_any().downcast_ref::<IntegerBox>() {
|
||||
("Float", format!("{}", ib.value as f64))
|
||||
} else { ("Float", result.to_string_box().value) }
|
||||
}
|
||||
MirType::Integer => {
|
||||
if let Some(ib) = result.as_any().downcast_ref::<IntegerBox>() {
|
||||
("Integer", ib.value.to_string())
|
||||
} else { ("Integer", result.to_string_box().value) }
|
||||
}
|
||||
MirType::Bool => {
|
||||
if let Some(bb) = result.as_any().downcast_ref::<BoolBox>() {
|
||||
("Bool", bb.value.to_string())
|
||||
} else if let Some(ib) = result.as_any().downcast_ref::<IntegerBox>() {
|
||||
("Bool", (ib.value != 0).to_string())
|
||||
} else { ("Bool", result.to_string_box().value) }
|
||||
}
|
||||
MirType::String => {
|
||||
if let Some(sb) = result.as_any().downcast_ref::<StringBox>() {
|
||||
("String", sb.value.clone())
|
||||
} else { ("String", result.to_string_box().value) }
|
||||
}
|
||||
_ => { (result.type_name(), result.to_string_box().value) }
|
||||
};
|
||||
println!("ResultType(MIR): {}", ety);
|
||||
println!("Result: {}", sval);
|
||||
} else {
|
||||
println!("Result: {:?}", result);
|
||||
}
|
||||
},
|
||||
Err(e) => { eprintln!("❌ VM execution error: {}", e); process::exit(1); }
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user