docs: JIT→EXE実現のための革新的アプローチを文書化
- プラグインBox統一化によるC ABI活用案 - すべてのBoxをプラグイン化する提案 - 既存のBID-FFIシステムを再利用 - Gemini/Codex先生の技術的助言も統合 関連: Phase 10.x, 自己ホスティング計画
This commit is contained in:
@ -0,0 +1,164 @@
|
|||||||
|
# JIT→EXE実現: プラグインBox統一化による革新的アプローチ
|
||||||
|
|
||||||
|
Status: Pending
|
||||||
|
Created: 2025-08-28
|
||||||
|
Priority: High
|
||||||
|
Related: Phase 10.x (JIT), Plugin System (BID-FFI)
|
||||||
|
|
||||||
|
## 💡 核心的洞察
|
||||||
|
|
||||||
|
既存のプラグインシステム(C ABI)を活用することで、JIT→EXE変換の道が開ける。
|
||||||
|
|
||||||
|
## 現状分析
|
||||||
|
|
||||||
|
### JIT実行の仕組み
|
||||||
|
```rust
|
||||||
|
// 現在: 2つの異なる呼び出し方法
|
||||||
|
JIT → HostCall → Rustビルトイン (ArrayBox, StringBox等)
|
||||||
|
JIT → PluginInvoke → プラグインBox (FileBox, NetBox等)
|
||||||
|
```
|
||||||
|
|
||||||
|
### プラグインシステムの特徴
|
||||||
|
```c
|
||||||
|
// すでに完全なC FFI!
|
||||||
|
extern "C" fn nyash_plugin_invoke(
|
||||||
|
type_id: u32,
|
||||||
|
method_id: u32,
|
||||||
|
instance_id: u32,
|
||||||
|
args: *const u8, // TLV encoded
|
||||||
|
args_len: usize,
|
||||||
|
result: *mut u8, // TLV encoded
|
||||||
|
result_len: *mut usize,
|
||||||
|
) -> i32
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🚀 提案: すべてをプラグイン化
|
||||||
|
|
||||||
|
### 1. ビルトインBoxもプラグインとして実装
|
||||||
|
|
||||||
|
```rust
|
||||||
|
// ArrayBoxをプラグイン化
|
||||||
|
plugins/nyash-array-plugin/
|
||||||
|
├── Cargo.toml
|
||||||
|
├── src/
|
||||||
|
│ └── lib.rs // nyash_plugin_invoke_array実装
|
||||||
|
└── tests/
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 統一されたJIT呼び出し
|
||||||
|
|
||||||
|
```rust
|
||||||
|
// LowerCore内で統一
|
||||||
|
match box_type {
|
||||||
|
"ArrayBox" | "StringBox" | "FileBox" => {
|
||||||
|
// すべて同じプラグインAPIを使用
|
||||||
|
b.emit_plugin_invoke(type_id, method_id, ...);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. スタティックリンクによるEXE生成
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# プラグインを静的ライブラリとしてビルド
|
||||||
|
cargo build --release --crate-type=staticlib
|
||||||
|
|
||||||
|
# すべてをリンク
|
||||||
|
ld -o nyash.exe \
|
||||||
|
compiled.o \ # JIT生成コード
|
||||||
|
libnyash_array_plugin.a \ # ArrayBox
|
||||||
|
libnyash_string_plugin.a \ # StringBox
|
||||||
|
libnyash_file_plugin.a \ # FileBox
|
||||||
|
libnyash_runtime_minimal.a # 最小ランタイム
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🎯 実装ステップ
|
||||||
|
|
||||||
|
### Phase 1: プロトタイプ(1週間)
|
||||||
|
- [ ] ArrayBoxのプラグイン版実装
|
||||||
|
- [ ] JITからプラグイン呼び出しテスト
|
||||||
|
- [ ] 性能比較(HostCall vs Plugin)
|
||||||
|
|
||||||
|
### Phase 2: 段階的移行(1ヶ月)
|
||||||
|
- [ ] 主要ビルトインBoxのプラグイン化
|
||||||
|
- StringBox
|
||||||
|
- IntegerBox
|
||||||
|
- BoolBox
|
||||||
|
- MapBox
|
||||||
|
- [ ] JIT lowering層の統一
|
||||||
|
|
||||||
|
### Phase 3: EXE生成(2ヶ月)
|
||||||
|
- [ ] プラグインの静的リンク対応
|
||||||
|
- [ ] 最小ランタイム作成
|
||||||
|
- [ ] リンカースクリプト整備
|
||||||
|
|
||||||
|
### Phase 4: 最適化(継続的)
|
||||||
|
- [ ] インライン展開
|
||||||
|
- [ ] LTO(Link Time Optimization)
|
||||||
|
- [ ] プロファイルガイド最適化
|
||||||
|
|
||||||
|
## 📊 利点
|
||||||
|
|
||||||
|
1. **既存資産の活用**
|
||||||
|
- プラグインシステムは実績あり
|
||||||
|
- C ABIは安定している
|
||||||
|
- TLVエンコーディング確立済み
|
||||||
|
|
||||||
|
2. **段階的移行可能**
|
||||||
|
- ビルトインを一つずつ移行
|
||||||
|
- 既存コードとの共存
|
||||||
|
- リスク分散
|
||||||
|
|
||||||
|
3. **統一されたアーキテクチャ**
|
||||||
|
- Everything is Box → Everything is Plugin
|
||||||
|
- JIT/AOT/インタープリターで同じAPI
|
||||||
|
- メンテナンス性向上
|
||||||
|
|
||||||
|
4. **自然なEXE生成**
|
||||||
|
- プラグインは元々ネイティブコード
|
||||||
|
- リンク処理が簡単
|
||||||
|
- デバッグ情報も保持
|
||||||
|
|
||||||
|
## 🚨 検討事項
|
||||||
|
|
||||||
|
### パフォーマンス
|
||||||
|
- TLVエンコード/デコードのオーバーヘッド
|
||||||
|
- → 頻繁に呼ばれるメソッドは最適化版を用意
|
||||||
|
|
||||||
|
### メモリ管理
|
||||||
|
- ハンドル(instance_id)による間接参照
|
||||||
|
- → JIT側でキャッシュ機構を実装
|
||||||
|
|
||||||
|
### 互換性
|
||||||
|
- 既存のHostCall方式との共存期間
|
||||||
|
- → フラグで切り替え可能に
|
||||||
|
|
||||||
|
## 💡 将来展望
|
||||||
|
|
||||||
|
### 自己ホスティング
|
||||||
|
```nyash
|
||||||
|
// NyashでNyashコンパイラを書く
|
||||||
|
box NyashCompiler {
|
||||||
|
compile(source) {
|
||||||
|
local ast = Parser.parse(source)
|
||||||
|
local mir = MirBuilder.build(ast)
|
||||||
|
local obj = CraneliftBackend.emit(mir)
|
||||||
|
return Linker.link(obj, plugins)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### プラグインエコシステム
|
||||||
|
- ユーザーが作ったBoxもEXEに含められる
|
||||||
|
- プラグインマーケットプレイス
|
||||||
|
- 動的/静的リンクの選択
|
||||||
|
|
||||||
|
## 結論
|
||||||
|
|
||||||
|
プラグインシステムの**C ABI統一**により、JIT→EXE変換が現実的に。
|
||||||
|
「Everything is Plugin」という新たな設計哲学で、Nyashの未来が開ける。
|
||||||
|
|
||||||
|
## 参考リンク
|
||||||
|
- src/bid/plugin_api.rs - プラグインFFI定義
|
||||||
|
- plugins/ - 既存プラグイン実装
|
||||||
|
- docs/reference/plugin-system/ - プラグイン仕様書
|
||||||
33
examples/jit_math_native_f64.nyash
Normal file
33
examples/jit_math_native_f64.nyash
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
// JIT HostCall PoC: math.* with native f64
|
||||||
|
// Run:
|
||||||
|
// NYASH_JIT_EXEC=1 NYASH_JIT_THRESHOLD=1 NYASH_JIT_HOSTCALL=1 \
|
||||||
|
// NYASH_JIT_EVENTS=1 NYASH_JIT_NATIVE_F64=1 \
|
||||||
|
// ./target/release/nyash --backend vm examples/jit_math_native_f64.nyash
|
||||||
|
|
||||||
|
box Runner {
|
||||||
|
birth() {
|
||||||
|
// no-op constructor
|
||||||
|
}
|
||||||
|
calc_sin(x) {
|
||||||
|
local m
|
||||||
|
m = new MathBox()
|
||||||
|
return m.sin(x)
|
||||||
|
}
|
||||||
|
calc_min(a, b) {
|
||||||
|
local m
|
||||||
|
m = new MathBox()
|
||||||
|
return m.min(a, b)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static box Main {
|
||||||
|
main() {
|
||||||
|
local r, m, pi2
|
||||||
|
r = new Runner()
|
||||||
|
// pi/2 ≒ 1.5707963267948966
|
||||||
|
pi2 = 1.5707963267948966
|
||||||
|
print(r.calc_sin(pi2))
|
||||||
|
print(r.calc_min(3.0, 5.0))
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -7,7 +7,7 @@ pub struct LowerCore {
|
|||||||
pub unsupported: usize,
|
pub unsupported: usize,
|
||||||
pub covered: usize,
|
pub covered: usize,
|
||||||
/// Minimal constant propagation for i64 to feed host-call args
|
/// Minimal constant propagation for i64 to feed host-call args
|
||||||
known_i64: std::collections::HashMap<ValueId, i64>,
|
pub(super) known_i64: std::collections::HashMap<ValueId, i64>,
|
||||||
/// Minimal constant propagation for f64 (math.* signature checks)
|
/// Minimal constant propagation for f64 (math.* signature checks)
|
||||||
known_f64: std::collections::HashMap<ValueId, f64>,
|
known_f64: std::collections::HashMap<ValueId, f64>,
|
||||||
/// Parameter index mapping for ValueId
|
/// Parameter index mapping for ValueId
|
||||||
@ -17,7 +17,7 @@ pub struct LowerCore {
|
|||||||
/// Map (block, phi dst) -> param index in that block (for multi-PHI)
|
/// Map (block, phi dst) -> param index in that block (for multi-PHI)
|
||||||
phi_param_index: std::collections::HashMap<(crate::mir::BasicBlockId, ValueId), usize>,
|
phi_param_index: std::collections::HashMap<(crate::mir::BasicBlockId, ValueId), usize>,
|
||||||
/// Track values that are boolean (b1) results, e.g., Compare destinations
|
/// Track values that are boolean (b1) results, e.g., Compare destinations
|
||||||
bool_values: std::collections::HashSet<ValueId>,
|
pub(super) bool_values: std::collections::HashSet<ValueId>,
|
||||||
/// Track PHI destinations that are boolean (all inputs derived from bool_values)
|
/// Track PHI destinations that are boolean (all inputs derived from bool_values)
|
||||||
bool_phi_values: std::collections::HashSet<ValueId>,
|
bool_phi_values: std::collections::HashSet<ValueId>,
|
||||||
/// Track values that are FloatBox instances (for arg type classification)
|
/// Track values that are FloatBox instances (for arg type classification)
|
||||||
@ -427,7 +427,7 @@ impl LowerCore {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Push a value onto the builder stack if it is a known i64 const or a parameter.
|
/// Push a value onto the builder stack if it is a known i64 const or a parameter.
|
||||||
fn push_value_if_known_or_param(&self, b: &mut dyn IRBuilder, id: &ValueId) {
|
pub(super) fn push_value_if_known_or_param(&self, b: &mut dyn IRBuilder, id: &ValueId) {
|
||||||
if self.phi_values.contains(id) {
|
if self.phi_values.contains(id) {
|
||||||
// Multi-PHI: find the param index for this phi in the current block
|
// Multi-PHI: find the param index for this phi in the current block
|
||||||
// We don't have the current block id here; rely on builder's current block context and our stored index being positional.
|
// We don't have the current block id here; rely on builder's current block context and our stored index being positional.
|
||||||
@ -531,51 +531,10 @@ impl LowerCore {
|
|||||||
if self.bool_values.contains(src) { self.bool_values.insert(*dst); }
|
if self.bool_values.contains(src) { self.bool_values.insert(*dst); }
|
||||||
// Otherwise no-op for codegen (stack-machine handles sources directly later)
|
// Otherwise no-op for codegen (stack-machine handles sources directly later)
|
||||||
}
|
}
|
||||||
I::BinOp { dst, op, lhs, rhs } => {
|
I::BinOp { dst, op, lhs, rhs } => { self.lower_binop(b, op, lhs, rhs, dst); }
|
||||||
// Ensure operands are on stack when available (param or known const)
|
I::Compare { op, lhs, rhs, dst } => { self.lower_compare(b, op, lhs, rhs, dst); }
|
||||||
self.push_value_if_known_or_param(b, lhs);
|
I::Jump { .. } => self.lower_jump(b),
|
||||||
self.push_value_if_known_or_param(b, rhs);
|
I::Branch { .. } => self.lower_branch(b),
|
||||||
let kind = match op {
|
|
||||||
BinaryOp::Add => BinOpKind::Add,
|
|
||||||
BinaryOp::Sub => BinOpKind::Sub,
|
|
||||||
BinaryOp::Mul => BinOpKind::Mul,
|
|
||||||
BinaryOp::Div => BinOpKind::Div,
|
|
||||||
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 Ok(()); }
|
|
||||||
};
|
|
||||||
b.emit_binop(kind);
|
|
||||||
if let (Some(a), Some(b)) = (self.known_i64.get(lhs), self.known_i64.get(rhs)) {
|
|
||||||
let res = match op {
|
|
||||||
BinaryOp::Add => a.wrapping_add(*b),
|
|
||||||
BinaryOp::Sub => a.wrapping_sub(*b),
|
|
||||||
BinaryOp::Mul => a.wrapping_mul(*b),
|
|
||||||
BinaryOp::Div => if *b != 0 { a.wrapping_div(*b) } else { 0 },
|
|
||||||
BinaryOp::Mod => if *b != 0 { a.wrapping_rem(*b) } else { 0 },
|
|
||||||
_ => 0,
|
|
||||||
};
|
|
||||||
self.known_i64.insert(*dst, res);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
I::Compare { op, lhs, rhs, .. } => {
|
|
||||||
// Ensure operands are on stack when available (param or known const)
|
|
||||||
self.push_value_if_known_or_param(b, lhs);
|
|
||||||
self.push_value_if_known_or_param(b, rhs);
|
|
||||||
let kind = match op {
|
|
||||||
CompareOp::Eq => CmpKind::Eq,
|
|
||||||
CompareOp::Ne => CmpKind::Ne,
|
|
||||||
CompareOp::Lt => CmpKind::Lt,
|
|
||||||
CompareOp::Le => CmpKind::Le,
|
|
||||||
CompareOp::Gt => CmpKind::Gt,
|
|
||||||
CompareOp::Ge => CmpKind::Ge,
|
|
||||||
};
|
|
||||||
b.emit_compare(kind);
|
|
||||||
// Mark the last dst (compare produces a boolean)
|
|
||||||
if let MirInstruction::Compare { dst, .. } = instr { self.bool_values.insert(*dst); }
|
|
||||||
}
|
|
||||||
I::Jump { .. } => b.emit_jump(),
|
|
||||||
I::Branch { .. } => b.emit_branch(),
|
|
||||||
I::Return { value } => {
|
I::Return { value } => {
|
||||||
if let Some(v) = value { self.push_value_if_known_or_param(b, v); }
|
if let Some(v) = value { self.push_value_if_known_or_param(b, v); }
|
||||||
b.emit_return()
|
b.emit_return()
|
||||||
@ -605,7 +564,24 @@ impl LowerCore {
|
|||||||
I::ArrayGet { array, index, .. } => { super::core_hostcall::lower_array_get(b, &self.param_index, &self.known_i64, array, index); }
|
I::ArrayGet { array, index, .. } => { super::core_hostcall::lower_array_get(b, &self.param_index, &self.known_i64, array, index); }
|
||||||
I::ArraySet { array, index, value } => { super::core_hostcall::lower_array_set(b, &self.param_index, &self.known_i64, array, index, value); }
|
I::ArraySet { array, index, value } => { super::core_hostcall::lower_array_set(b, &self.param_index, &self.known_i64, array, index, value); }
|
||||||
I::BoxCall { box_val: array, method, args, dst, .. } => {
|
I::BoxCall { box_val: array, method, args, dst, .. } => {
|
||||||
if crate::jit::config::current().hostcall {
|
if super::core_hostcall::lower_boxcall_simple_reads(b, &self.param_index, &self.known_i64, array, method.as_str(), args, dst.clone()) {
|
||||||
|
// handled in helper (read-only simple methods)
|
||||||
|
} else if method.as_str() == "get" {
|
||||||
|
super::core_hostcall::lower_map_get(func, b, &self.param_index, &self.known_i64, array, args, dst.clone());
|
||||||
|
} else if method.as_str() == "has" {
|
||||||
|
super::core_hostcall::lower_map_has(b, &self.param_index, &self.known_i64, array, args, dst.clone());
|
||||||
|
} else if matches!(method.as_str(), "sin" | "cos" | "abs" | "min" | "max") {
|
||||||
|
super::core_hostcall::lower_math_call(
|
||||||
|
func,
|
||||||
|
b,
|
||||||
|
&self.known_i64,
|
||||||
|
&self.known_f64,
|
||||||
|
&self.float_box_values,
|
||||||
|
method.as_str(),
|
||||||
|
args,
|
||||||
|
dst.clone(),
|
||||||
|
);
|
||||||
|
} else if crate::jit::config::current().hostcall {
|
||||||
match method.as_str() {
|
match method.as_str() {
|
||||||
"len" | "length" => {
|
"len" | "length" => {
|
||||||
if let Some(pidx) = self.param_index.get(array).copied() {
|
if let Some(pidx) = self.param_index.get(array).copied() {
|
||||||
|
|||||||
@ -267,3 +267,303 @@ pub fn lower_box_call(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Handle simple read-only BoxCall methods. Returns true if handled.
|
||||||
|
pub fn lower_boxcall_simple_reads(
|
||||||
|
b: &mut dyn IRBuilder,
|
||||||
|
param_index: &HashMap<ValueId, usize>,
|
||||||
|
known_i64: &HashMap<ValueId, i64>,
|
||||||
|
recv: &ValueId,
|
||||||
|
method: &str,
|
||||||
|
args: &Vec<ValueId>,
|
||||||
|
dst: Option<ValueId>,
|
||||||
|
) -> bool {
|
||||||
|
if !crate::jit::config::current().hostcall { return false; }
|
||||||
|
match method {
|
||||||
|
// Any.length / Array.length
|
||||||
|
"len" | "length" => {
|
||||||
|
if let Some(pidx) = param_index.get(recv).copied() {
|
||||||
|
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_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);
|
||||||
|
b.emit_host_call(crate::jit::r#extern::collections::SYM_ARRAY_LEN, 1, dst.is_some());
|
||||||
|
}
|
||||||
|
true
|
||||||
|
}
|
||||||
|
// Any.isEmpty
|
||||||
|
"isEmpty" | "empty" | "is_empty" => {
|
||||||
|
if let Some(pidx) = param_index.get(recv).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);
|
||||||
|
b.emit_host_call(crate::jit::r#extern::collections::SYM_ANY_IS_EMPTY_H, 1, dst.is_some());
|
||||||
|
} else {
|
||||||
|
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>"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
true
|
||||||
|
}
|
||||||
|
// Map.size
|
||||||
|
"size" => {
|
||||||
|
if let Some(pidx) = param_index.get(recv).copied() {
|
||||||
|
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_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);
|
||||||
|
b.emit_host_call(crate::jit::r#extern::collections::SYM_MAP_SIZE, 1, dst.is_some());
|
||||||
|
}
|
||||||
|
true
|
||||||
|
}
|
||||||
|
// String.charCodeAt(index)
|
||||||
|
"charCodeAt" => {
|
||||||
|
if let Some(pidx) = param_index.get(recv).copied() {
|
||||||
|
let idx = args.get(0).and_then(|v| known_i64.get(v).copied()).unwrap_or(0);
|
||||||
|
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_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>"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
true
|
||||||
|
}
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Map.get(key): handle I64 and HH variants with registry check and events
|
||||||
|
pub fn lower_map_get(
|
||||||
|
func: &MirFunction,
|
||||||
|
b: &mut dyn IRBuilder,
|
||||||
|
param_index: &HashMap<ValueId, usize>,
|
||||||
|
known_i64: &HashMap<ValueId, i64>,
|
||||||
|
recv: &ValueId,
|
||||||
|
args: &Vec<ValueId>,
|
||||||
|
dst: Option<ValueId>,
|
||||||
|
) {
|
||||||
|
if let Some(pidx) = param_index.get(recv).copied() {
|
||||||
|
// Build observed arg kinds using TyEnv when available
|
||||||
|
let mut observed_kinds: Vec<crate::jit::hostcall_registry::ArgKind> = Vec::new();
|
||||||
|
observed_kinds.push(crate::jit::hostcall_registry::ArgKind::Handle); // receiver
|
||||||
|
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(_) = args.get(0).and_then(|v| known_i64.get(v)) { crate::jit::hostcall_registry::ArgKind::I64 }
|
||||||
|
else { crate::jit::hostcall_registry::ArgKind::Handle }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if let Some(_) = args.get(0).and_then(|v| known_i64.get(v)) {
|
||||||
|
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);
|
||||||
|
|
||||||
|
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 canonical = "nyash.map.get_h";
|
||||||
|
match crate::jit::hostcall_registry::check_signature(canonical, &observed_kinds) {
|
||||||
|
Ok(()) => {
|
||||||
|
let event_id = if matches!(key_kind, crate::jit::hostcall_registry::ArgKind::Handle)
|
||||||
|
&& args.get(0).and_then(|v| 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 };
|
||||||
|
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 matches!(key_kind, crate::jit::hostcall_registry::ArgKind::I64) {
|
||||||
|
let key_i = args.get(0).and_then(|v| 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| param_index.get(v)).copied() {
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(reason) => {
|
||||||
|
crate::jit::events::emit_lower(
|
||||||
|
serde_json::json!({
|
||||||
|
"id": canonical,
|
||||||
|
"decision": "fallback",
|
||||||
|
"reason": reason,
|
||||||
|
"argc": observed_kinds.len(),
|
||||||
|
"arg_types": arg_types
|
||||||
|
}),
|
||||||
|
"hostcall","<jit>"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// receiver not a param; emit info and fallback
|
||||||
|
let mut observed_kinds: Vec<crate::jit::hostcall_registry::ArgKind> = Vec::new();
|
||||||
|
observed_kinds.push(crate::jit::hostcall_registry::ArgKind::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_lower(
|
||||||
|
serde_json::json!({
|
||||||
|
"id": sym,
|
||||||
|
"decision": decision.0,
|
||||||
|
"reason": decision.1,
|
||||||
|
"argc": observed_kinds.len(),
|
||||||
|
"arg_types": arg_types
|
||||||
|
}),
|
||||||
|
"hostcall","<jit>"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn lower_map_has(
|
||||||
|
b: &mut dyn IRBuilder,
|
||||||
|
param_index: &HashMap<ValueId, usize>,
|
||||||
|
known_i64: &HashMap<ValueId, i64>,
|
||||||
|
recv: &ValueId,
|
||||||
|
args: &Vec<ValueId>,
|
||||||
|
dst: Option<ValueId>,
|
||||||
|
) {
|
||||||
|
if let Some(pidx) = param_index.get(recv).copied() {
|
||||||
|
let key = args.get(0).and_then(|v| 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_HAS_H, 2, dst.is_some());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// math.*: decide allow/fallback via registry; on allow + native_f64, emit typed hostcall
|
||||||
|
pub fn lower_math_call(
|
||||||
|
func: &MirFunction,
|
||||||
|
b: &mut dyn IRBuilder,
|
||||||
|
known_i64: &HashMap<ValueId, i64>,
|
||||||
|
known_f64: &HashMap<ValueId, f64>,
|
||||||
|
float_box_values: &std::collections::HashSet<ValueId>,
|
||||||
|
method: &str,
|
||||||
|
args: &Vec<ValueId>,
|
||||||
|
dst: Option<ValueId>,
|
||||||
|
) {
|
||||||
|
use crate::jit::hostcall_registry::{check_signature, ArgKind};
|
||||||
|
let sym = format!("nyash.math.{}", method);
|
||||||
|
|
||||||
|
// Build observed kinds using TyEnv when available; fallback to known maps / FloatBox tracking
|
||||||
|
let mut observed_kinds: Vec<ArgKind> = Vec::new();
|
||||||
|
for v in args.iter() {
|
||||||
|
let kind = if let Some(mt) = func.metadata.value_types.get(v) {
|
||||||
|
match mt {
|
||||||
|
crate::mir::MirType::Float => ArgKind::F64,
|
||||||
|
crate::mir::MirType::Integer => ArgKind::I64,
|
||||||
|
crate::mir::MirType::Bool => ArgKind::I64,
|
||||||
|
crate::mir::MirType::String | crate::mir::MirType::Box(_) => ArgKind::Handle,
|
||||||
|
_ => {
|
||||||
|
if known_f64.contains_key(v) || float_box_values.contains(v) { ArgKind::F64 } else { ArgKind::I64 }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if known_f64.contains_key(v) || float_box_values.contains(v) { ArgKind::F64 } else { ArgKind::I64 }
|
||||||
|
};
|
||||||
|
observed_kinds.push(kind);
|
||||||
|
}
|
||||||
|
let arg_types: Vec<&'static str> = observed_kinds.iter().map(|k| match k { ArgKind::I64 => "I64", ArgKind::F64 => "F64", ArgKind::Handle => "Handle" }).collect();
|
||||||
|
|
||||||
|
match check_signature(&sym, &observed_kinds) {
|
||||||
|
Ok(()) => {
|
||||||
|
crate::jit::events::emit_lower(
|
||||||
|
serde_json::json!({"id": sym, "decision":"allow", "reason":"sig_ok", "argc": observed_kinds.len(), "arg_types": arg_types}),
|
||||||
|
"hostcall","<jit>"
|
||||||
|
);
|
||||||
|
if crate::jit::config::current().native_f64 {
|
||||||
|
let (symbol, arity) = match method {
|
||||||
|
"sin" => ("nyash.math.sin_f64", 1),
|
||||||
|
"cos" => ("nyash.math.cos_f64", 1),
|
||||||
|
"abs" => ("nyash.math.abs_f64", 1),
|
||||||
|
"min" => ("nyash.math.min_f64", 2),
|
||||||
|
"max" => ("nyash.math.max_f64", 2),
|
||||||
|
_ => ("nyash.math.sin_f64", 1),
|
||||||
|
};
|
||||||
|
for i in 0..arity {
|
||||||
|
if let Some(v) = args.get(i) {
|
||||||
|
if let Some(fv) = known_f64.get(v).copied() { b.emit_const_f64(fv); continue; }
|
||||||
|
if let Some(iv) = known_i64.get(v).copied() { b.emit_const_f64(iv as f64); continue; }
|
||||||
|
let mut emitted = false;
|
||||||
|
'scan: for (_bb_id, bb) in func.blocks.iter() {
|
||||||
|
for ins in bb.instructions.iter() {
|
||||||
|
if let crate::mir::MirInstruction::NewBox { dst, box_type, args: nb_args } = ins {
|
||||||
|
if *dst == *v && box_type == "FloatBox" {
|
||||||
|
if let Some(srcv) = nb_args.get(0) {
|
||||||
|
if let Some(fv) = known_f64.get(srcv).copied() { b.emit_const_f64(fv); emitted = true; break 'scan; }
|
||||||
|
if let Some(iv) = known_i64.get(srcv).copied() { b.emit_const_f64(iv as f64); emitted = true; break 'scan; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !emitted { b.emit_const_f64(0.0); }
|
||||||
|
} else { b.emit_const_f64(0.0); }
|
||||||
|
}
|
||||||
|
let kinds: Vec<super::builder::ParamKind> = (0..arity).map(|_| super::builder::ParamKind::F64).collect();
|
||||||
|
b.emit_host_call_typed(symbol, &kinds, dst.is_some(), true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(reason) => {
|
||||||
|
crate::jit::events::emit(
|
||||||
|
"hostcall","<jit>",None,None,
|
||||||
|
serde_json::json!({"id": sym, "decision":"fallback", "reason": reason, "argc": observed_kinds.len(), "arg_types": arg_types})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
50
src/jit/lower/core_ops.rs
Normal file
50
src/jit/lower/core_ops.rs
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
//! Core ops lowering (non-hostcall): BinOp, Compare, Branch, Jump
|
||||||
|
use super::builder::{IRBuilder, BinOpKind, CmpKind};
|
||||||
|
use crate::mir::{BinaryOp, CompareOp, ValueId};
|
||||||
|
|
||||||
|
use super::core::LowerCore;
|
||||||
|
|
||||||
|
impl LowerCore {
|
||||||
|
pub fn lower_binop(&mut self, b: &mut dyn IRBuilder, op: &BinaryOp, lhs: &ValueId, rhs: &ValueId, dst: &ValueId) {
|
||||||
|
self.push_value_if_known_or_param(b, lhs);
|
||||||
|
self.push_value_if_known_or_param(b, rhs);
|
||||||
|
let kind = match op {
|
||||||
|
BinaryOp::Add => BinOpKind::Add,
|
||||||
|
BinaryOp::Sub => BinOpKind::Sub,
|
||||||
|
BinaryOp::Mul => BinOpKind::Mul,
|
||||||
|
BinaryOp::Div => BinOpKind::Div,
|
||||||
|
BinaryOp::Mod => BinOpKind::Mod,
|
||||||
|
_ => { return; }
|
||||||
|
};
|
||||||
|
b.emit_binop(kind);
|
||||||
|
if let (Some(a), Some(bv)) = (self.known_i64.get(lhs), self.known_i64.get(rhs)) {
|
||||||
|
let res = match op {
|
||||||
|
BinaryOp::Add => a.wrapping_add(*bv),
|
||||||
|
BinaryOp::Sub => a.wrapping_sub(*bv),
|
||||||
|
BinaryOp::Mul => a.wrapping_mul(*bv),
|
||||||
|
BinaryOp::Div => if *bv != 0 { a.wrapping_div(*bv) } else { 0 },
|
||||||
|
BinaryOp::Mod => if *bv != 0 { a.wrapping_rem(*bv) } else { 0 },
|
||||||
|
_ => 0,
|
||||||
|
};
|
||||||
|
self.known_i64.insert(*dst, res);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn lower_compare(&mut self, b: &mut dyn IRBuilder, op: &CompareOp, lhs: &ValueId, rhs: &ValueId, dst: &ValueId) {
|
||||||
|
self.push_value_if_known_or_param(b, lhs);
|
||||||
|
self.push_value_if_known_or_param(b, rhs);
|
||||||
|
let kind = match op {
|
||||||
|
CompareOp::Eq => CmpKind::Eq,
|
||||||
|
CompareOp::Ne => CmpKind::Ne,
|
||||||
|
CompareOp::Lt => CmpKind::Lt,
|
||||||
|
CompareOp::Le => CmpKind::Le,
|
||||||
|
CompareOp::Gt => CmpKind::Gt,
|
||||||
|
CompareOp::Ge => CmpKind::Ge,
|
||||||
|
};
|
||||||
|
b.emit_compare(kind);
|
||||||
|
self.bool_values.insert(*dst);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn lower_jump(&mut self, b: &mut dyn IRBuilder) { b.emit_jump(); }
|
||||||
|
pub fn lower_branch(&mut self, b: &mut dyn IRBuilder) { b.emit_branch(); }
|
||||||
|
}
|
||||||
@ -4,3 +4,4 @@ pub mod builder;
|
|||||||
pub mod extern_thunks;
|
pub mod extern_thunks;
|
||||||
pub mod cfg_dot;
|
pub mod cfg_dot;
|
||||||
pub mod core_hostcall;
|
pub mod core_hostcall;
|
||||||
|
pub mod core_ops;
|
||||||
|
|||||||
Reference in New Issue
Block a user