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 covered: usize,
|
||||
/// 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)
|
||||
known_f64: std::collections::HashMap<ValueId, f64>,
|
||||
/// Parameter index mapping for ValueId
|
||||
@ -17,7 +17,7 @@ pub struct LowerCore {
|
||||
/// Map (block, phi dst) -> param index in that block (for multi-PHI)
|
||||
phi_param_index: std::collections::HashMap<(crate::mir::BasicBlockId, ValueId), usize>,
|
||||
/// 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)
|
||||
bool_phi_values: std::collections::HashSet<ValueId>,
|
||||
/// 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.
|
||||
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) {
|
||||
// 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.
|
||||
@ -531,51 +531,10 @@ impl LowerCore {
|
||||
if self.bool_values.contains(src) { self.bool_values.insert(*dst); }
|
||||
// Otherwise no-op for codegen (stack-machine handles sources directly later)
|
||||
}
|
||||
I::BinOp { dst, 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 {
|
||||
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::BinOp { dst, op, lhs, rhs } => { self.lower_binop(b, op, lhs, rhs, dst); }
|
||||
I::Compare { op, lhs, rhs, dst } => { self.lower_compare(b, op, lhs, rhs, dst); }
|
||||
I::Jump { .. } => self.lower_jump(b),
|
||||
I::Branch { .. } => self.lower_branch(b),
|
||||
I::Return { value } => {
|
||||
if let Some(v) = value { self.push_value_if_known_or_param(b, v); }
|
||||
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::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, .. } => {
|
||||
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() {
|
||||
"len" | "length" => {
|
||||
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 cfg_dot;
|
||||
pub mod core_hostcall;
|
||||
pub mod core_ops;
|
||||
|
||||
Reference in New Issue
Block a user