docs: JIT→EXE実現のための革新的アプローチを文書化

- プラグインBox統一化によるC ABI活用案
- すべてのBoxをプラグイン化する提案
- 既存のBID-FFIシステムを再利用
- Gemini/Codex先生の技術的助言も統合

関連: Phase 10.x, 自己ホスティング計画
This commit is contained in:
Moe Charm
2025-08-29 03:28:42 +09:00
parent 91f2ee11d6
commit 4167491e92
6 changed files with 573 additions and 49 deletions

View File

@ -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: 最適化(継続的)
- [ ] インライン展開
- [ ] LTOLink 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/ - プラグイン仕様書

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

View File

@ -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() {

View File

@ -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
View 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(); }
}

View File

@ -4,3 +4,4 @@ pub mod builder;
pub mod extern_thunks;
pub mod cfg_dot;
pub mod core_hostcall;
pub mod core_ops;