From 4167491e929588ae828af3bce812979056e0ff84 Mon Sep 17 00:00:00 2001 From: Moe Charm Date: Fri, 29 Aug 2025 03:28:42 +0900 Subject: [PATCH] =?UTF-8?q?docs:=20JIT=E2=86=92EXE=E5=AE=9F=E7=8F=BE?= =?UTF-8?q?=E3=81=AE=E3=81=9F=E3=82=81=E3=81=AE=E9=9D=A9=E6=96=B0=E7=9A=84?= =?UTF-8?q?=E3=82=A2=E3=83=97=E3=83=AD=E3=83=BC=E3=83=81=E3=82=92=E6=96=87?= =?UTF-8?q?=E6=9B=B8=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - プラグインBox統一化によるC ABI活用案 - すべてのBoxをプラグイン化する提案 - 既存のBID-FFIシステムを再利用 - Gemini/Codex先生の技術的助言も統合 関連: Phase 10.x, 自己ホスティング計画 --- ...25-08-28-jit-exe-via-plugin-unification.md | 164 ++++++++++ examples/jit_math_native_f64.nyash | 33 ++ src/jit/lower/core.rs | 74 ++--- src/jit/lower/core_hostcall.rs | 300 ++++++++++++++++++ src/jit/lower/core_ops.rs | 50 +++ src/jit/lower/mod.rs | 1 + 6 files changed, 573 insertions(+), 49 deletions(-) create mode 100644 docs/ideas/new-features/2025-08-28-jit-exe-via-plugin-unification.md create mode 100644 examples/jit_math_native_f64.nyash create mode 100644 src/jit/lower/core_ops.rs diff --git a/docs/ideas/new-features/2025-08-28-jit-exe-via-plugin-unification.md b/docs/ideas/new-features/2025-08-28-jit-exe-via-plugin-unification.md new file mode 100644 index 00000000..22b3a479 --- /dev/null +++ b/docs/ideas/new-features/2025-08-28-jit-exe-via-plugin-unification.md @@ -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/ - プラグイン仕様書 \ No newline at end of file diff --git a/examples/jit_math_native_f64.nyash b/examples/jit_math_native_f64.nyash new file mode 100644 index 00000000..cfcb7179 --- /dev/null +++ b/examples/jit_math_native_f64.nyash @@ -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 + } +} diff --git a/src/jit/lower/core.rs b/src/jit/lower/core.rs index 1293b10b..ceac0027 100644 --- a/src/jit/lower/core.rs +++ b/src/jit/lower/core.rs @@ -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, + pub(super) known_i64: std::collections::HashMap, /// Minimal constant propagation for f64 (math.* signature checks) known_f64: std::collections::HashMap, /// 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, + pub(super) bool_values: std::collections::HashSet, /// Track PHI destinations that are boolean (all inputs derived from bool_values) bool_phi_values: std::collections::HashSet, /// 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() { diff --git a/src/jit/lower/core_hostcall.rs b/src/jit/lower/core_hostcall.rs index 95c69b53..20dcb459 100644 --- a/src/jit/lower/core_hostcall.rs +++ b/src/jit/lower/core_hostcall.rs @@ -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, + known_i64: &HashMap, + recv: &ValueId, + method: &str, + args: &Vec, + dst: Option, +) -> 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","" + ); + 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","" + ); + 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","",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","" + ); + } + 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","" + ); + 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","" + ); + 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","" + ); + 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","" + ); + } + 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, + known_i64: &HashMap, + recv: &ValueId, + args: &Vec, + dst: Option, +) { + if let Some(pidx) = param_index.get(recv).copied() { + // Build observed arg kinds using TyEnv when available + let mut observed_kinds: Vec = 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","" + ); + 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","" + ); + } + } + } else { + // receiver not a param; emit info and fallback + let mut observed_kinds: Vec = 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","" + ); + } +} + +pub fn lower_map_has( + b: &mut dyn IRBuilder, + param_index: &HashMap, + known_i64: &HashMap, + recv: &ValueId, + args: &Vec, + dst: Option, +) { + 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, + known_f64: &HashMap, + float_box_values: &std::collections::HashSet, + method: &str, + args: &Vec, + dst: Option, +) { + 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 = 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","" + ); + 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 = (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","",None,None, + serde_json::json!({"id": sym, "decision":"fallback", "reason": reason, "argc": observed_kinds.len(), "arg_types": arg_types}) + ); + } + } +} diff --git a/src/jit/lower/core_ops.rs b/src/jit/lower/core_ops.rs new file mode 100644 index 00000000..7ff9e336 --- /dev/null +++ b/src/jit/lower/core_ops.rs @@ -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(); } +} diff --git a/src/jit/lower/mod.rs b/src/jit/lower/mod.rs index 345458dd..2f71ab8f 100644 --- a/src/jit/lower/mod.rs +++ b/src/jit/lower/mod.rs @@ -4,3 +4,4 @@ pub mod builder; pub mod extern_thunks; pub mod cfg_dot; pub mod core_hostcall; +pub mod core_ops;