archive: Move JIT/Cranelift to archive during Phase 15 focus

Phase 15 requires concentrated development on PyVM and LLVM backends only.
JIT/Cranelift was causing build confusion and distracting AI developers.

## Archived Components
- src/jit/ → archive/jit-cranelift/src/jit/
- src/backend/cranelift/ → archive/jit-cranelift/src/backend/cranelift/
- JIT Box modules → archive/jit-cranelift/src/boxes/
- JIT scripts → archive/jit-cranelift/scripts/, tools/
- clif_adapter.rs → archive/jit-cranelift/src/semantics/

## Build Changes
- Cargo.toml: Comment out cranelift-jit feature and dependencies
- src/lib.rs: Disable JIT module declaration
- src/boxes/mod.rs: Disable JIT Box module declarations
- src/semantics/mod.rs: Disable clif_adapter module
- debug_box.rs: Replace JIT calls with archive stubs

## Documentation
- archive/jit-cranelift/ARCHIVE_NOTES.md: Complete restoration guide
- Reason: Phase 15 selfhosting focus (80k→20k line reduction)
- Restoration: Full procedure documented for future revival

This eliminates build errors and AI developer confusion, enabling
focused Phase 15 development on PyVM/LLVM backends only.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Selfhosting Dev
2025-09-23 02:15:27 +09:00
parent 0d443dd6fa
commit a60d840b47
59 changed files with 145 additions and 27 deletions

View File

@ -0,0 +1,96 @@
# JIT/Cranelift アーカイブ - Phase 15
## 📅 アーカイブ日時
2025-09-23 - Phase 15 セルフホスティング集中開発期間中
## 🎯 アーカイブ理由
### 現在の開発焦点
- **Phase 15**: Nyashセルフホスティング80k→20k行革命
- **開発対象**: PyVMとLLVMバックエンドのみ
- **JIT/Cranelift**: 現在未使用、ビルドエラーや混乱の原因
### 具体的な問題
1. **ビルドエラー**: `cranelift-jit` フィーチャーでのビルド失敗
2. **AI開発者の混乱**: JSON開発Claude Codeが誤ってJITルートを参照
3. **リソース分散**: メンテナンスコストが高い
4. **Phase 15集中**: PyVM/LLVM開発に集中したい
## 📂 アーカイブ内容
### 移動されたディレクトリ・ファイル
```
archive/jit-cranelift/
├── src/
│ ├── jit/ # JIT実装コアABI、エンジン、ローワリング等
│ └── backend/cranelift/ # Craneliftバックエンド実装
├── scripts/
│ └── build_jit.sh # JITビルドスクリプト
└── tools/
├── jit_compare_smoke.sh # JIT比較スモークテスト
└── smokes/
├── jit_smoke.sh # JITスモークテスト
└── smoke_vm_jit.sh # VM-JIT比較テスト
```
### Cargo.toml変更
- `cranelift-jit` フィーチャーをコメントアウト
- JIT関連の依存関係を無効化
## 🔄 復活手順(将来用)
### 1. ファイル復元
```bash
# アーカイブから復元
mv archive/jit-cranelift/src/jit src/
mv archive/jit-cranelift/src/backend/cranelift src/backend/
mv archive/jit-cranelift/scripts/build_jit.sh .
mv archive/jit-cranelift/tools/* tools/
```
### 2. Cargo.toml復元
```toml
[features]
cranelift-jit = ["dep:cranelift", "dep:cranelift-jit", "dep:cranelift-module"]
[dependencies]
cranelift = { version = "0.103", optional = true }
cranelift-jit = { version = "0.103", optional = true }
cranelift-module = { version = "0.103", optional = true }
```
### 3. ビルド確認
```bash
# JITビルドテスト
cargo build --release --features cranelift-jit
# スモークテスト実行
./tools/jit_smoke.sh
```
### 4. 統合テスト
- PyVM vs JIT性能比較
- LLVM vs JIT出力比較
- 全バックエンド統合テスト
## 💡 設計ノート
### JIT実装の特徴
- **Cranelift統合**: Wasmtime/Craneliftエコシステム活用
- **ホストコール最適化**: Rustネイティブ関数との高速ブリッジ
- **メモリ管理**: GCとJITの協調動作
- **デバッグ支援**: JIT統計・トレース機能
### 将来的な価値
- **高速実行**: 本格運用時の性能向上
- **AOTコンパイル**: ネイティブバイナリ生成
- **WebAssembly統合**: WASM実行環境との統一
## 📋 関連Issue・PR
- JIT/Craneliftビルドエラー修正が必要
- AIエージェント向けドキュメント整備
- Phase 15完了後の復活検討
---
**Note**: このアーカイブは一時的な措置です。Phase 15完了後、JIT/Craneliftの復活と最新化を検討します。

View File

@ -0,0 +1,5 @@
#!/bin/bash
# JIT (Cranelift) ビルド - 24スレッド並列
echo "🚀 JIT (Cranelift) ビルドを開始します..."
cargo build --release --features cranelift-jit -j 24
echo "✅ JIT ビルド完了!"

View File

@ -0,0 +1,201 @@
/*!
* ClifBuilder - IRBuilder implementation for Cranelift (skeleton)
*
* This satisfies the IRBuilder trait so LowerCore can target it.
* Actual CLIF emission will be added incrementally.
*/
#![cfg(feature = "cranelift-jit")]
use crate::jit::lower::builder::{BinOpKind, CmpKind, IRBuilder, ParamKind};
use cranelift_codegen::ir::InstBuilder;
// Minimal recorded opcodes for Const/Add/Return first
enum RecOp {
ConstI64(i64),
ConstF64(f64),
BinOp(BinOpKind),
Return,
}
pub struct ClifBuilder {
pub consts: usize,
pub binops: usize,
pub cmps: usize,
pub branches: usize,
pub rets: usize,
ops: Vec<RecOp>,
}
impl ClifBuilder {
pub fn new() -> Self {
Self {
consts: 0,
binops: 0,
cmps: 0,
branches: 0,
rets: 0,
ops: Vec::new(),
}
}
/// Build and execute the recorded ops as a native function using Cranelift
pub fn finish_and_execute(&self) -> Result<i64, String> {
use cranelift_codegen::ir::{types, AbiParam, Signature};
use cranelift_frontend::{FunctionBuilder, FunctionBuilderContext};
use cranelift_module::{Linkage, Module};
// JIT setup
let isa_builder = cranelift_native::builder().map_err(|e| e.to_string())?;
let flag_builder = cranelift_codegen::settings::builder();
let flags = cranelift_codegen::settings::Flags::new(flag_builder);
let isa = isa_builder.finish(flags).map_err(|e| e.to_string())?;
let jit_builder =
cranelift_jit::JITBuilder::with_isa(isa, cranelift_module::default_libcall_names());
let mut module = cranelift_jit::JITModule::new(jit_builder);
// Signature ()->i64
let mut sig = Signature::new(module.target_config().default_call_conv);
sig.returns.push(AbiParam::new(types::I64));
let func_id = module
.declare_function("ny_lowercore_main", Linkage::Export, &sig)
.map_err(|e| e.to_string())?;
let mut ctx = module.make_context();
ctx.func.signature = sig;
let mut fbc = FunctionBuilderContext::new();
let mut fb = FunctionBuilder::new(&mut ctx.func, &mut fbc);
let entry = fb.create_block();
fb.switch_to_block(entry);
// Interpret ops with a small value stack of CLIF Values
let mut vs: Vec<cranelift_codegen::ir::Value> = Vec::new();
let mut did_return = false;
for op in &self.ops {
match *op {
RecOp::ConstI64(i) => {
vs.push(fb.ins().iconst(types::I64, i));
}
RecOp::ConstF64(f) => {
let fv = fb.ins().f64const(f);
let iv = fb.ins().fcvt_to_sint(types::I64, fv);
vs.push(iv);
}
RecOp::BinOp(BinOpKind::Add) => {
if vs.len() < 2 {
vs.clear();
vs.push(fb.ins().iconst(types::I64, 0));
} else {
let r = vs.pop().unwrap();
let l = vs.pop().unwrap();
vs.push(fb.ins().iadd(l, r));
}
}
RecOp::BinOp(_) => { /* ignore others for now */ }
RecOp::Return => {
let retv = if let Some(v) = vs.last().copied() {
v
} else {
fb.ins().iconst(types::I64, 0)
};
fb.ins().return_(&[retv]);
did_return = true;
}
}
}
// Ensure function ends with return
if !did_return {
let retv = if let Some(v) = vs.last().copied() {
v
} else {
fb.ins().iconst(types::I64, 0)
};
fb.ins().return_(&[retv]);
}
fb.seal_block(entry);
fb.finalize();
module
.define_function(func_id, &mut ctx)
.map_err(|e| e.to_string())?;
module.clear_context(&mut ctx);
let _ = module.finalize_definitions();
let code = module.get_finalized_function(func_id);
let func = unsafe { std::mem::transmute::<_, extern "C" fn() -> i64>(code) };
Ok(func())
}
}
impl IRBuilder for ClifBuilder {
fn begin_function(&mut self, _name: &str) {}
fn end_function(&mut self) {}
fn prepare_signature_i64(&mut self, _argc: usize, _has_ret: bool) {}
fn prepare_signature_typed(&mut self, _params: &[ParamKind], _ret_is_f64: bool) {}
fn emit_param_i64(&mut self, _index: usize) {}
fn emit_const_i64(&mut self, val: i64) {
self.consts += 1;
self.ops.push(RecOp::ConstI64(val));
}
fn emit_const_f64(&mut self, val: f64) {
self.consts += 1;
self.ops.push(RecOp::ConstF64(val));
}
fn emit_binop(&mut self, op: BinOpKind) {
self.binops += 1;
self.ops.push(RecOp::BinOp(op));
}
fn emit_compare(&mut self, _op: CmpKind) {
self.cmps += 1;
}
fn emit_jump(&mut self) {}
fn emit_branch(&mut self) {
self.branches += 1;
}
fn emit_return(&mut self) {
self.rets += 1;
self.ops.push(RecOp::Return);
}
fn emit_host_call(&mut self, _symbol: &str, _argc: usize, _has_ret: bool) {}
fn emit_host_call_typed(
&mut self,
_symbol: &str,
_params: &[ParamKind],
_has_ret: bool,
_ret_is_f64: bool,
) {
}
fn emit_plugin_invoke(&mut self, _type_id: u32, _method_id: u32, _argc: usize, _has_ret: bool) {
}
fn emit_plugin_invoke_by_name(&mut self, _method: &str, _argc: usize, _has_ret: bool) {}
fn prepare_blocks(&mut self, _count: usize) {}
fn switch_to_block(&mut self, _index: usize) {}
fn seal_block(&mut self, _index: usize) {}
fn br_if_top_is_true(&mut self, _then_index: usize, _else_index: usize) {}
fn jump_to(&mut self, _target_index: usize) {}
fn ensure_block_params_i64(&mut self, _index: usize, _count: usize) {}
fn ensure_block_params_b1(&mut self, index: usize, count: usize) {
self.ensure_block_params_i64(index, count);
}
fn ensure_block_param_i64(&mut self, index: usize) {
self.ensure_block_params_i64(index, 1);
}
fn push_block_param_i64_at(&mut self, _pos: usize) {}
fn push_block_param_b1_at(&mut self, pos: usize) {
self.push_block_param_i64_at(pos);
}
fn push_block_param_i64(&mut self) {
self.push_block_param_i64_at(0);
}
fn br_if_with_args(
&mut self,
_then_index: usize,
_else_index: usize,
_then_n: usize,
_else_n: usize,
) {
self.emit_branch();
}
fn jump_with_args(&mut self, _target_index: usize, _n: usize) {
self.emit_jump();
}
fn hint_ret_bool(&mut self, _is_b1: bool) {}
fn ensure_local_i64(&mut self, _index: usize) {}
fn store_local_i64(&mut self, _index: usize) {}
fn load_local_i64(&mut self, _index: usize) {}
}

View File

@ -0,0 +1,114 @@
#![cfg(feature = "cranelift-jit")]
use std::collections::HashMap;
use cranelift_codegen::ir::{types, AbiParam, Block, Signature};
use cranelift_frontend::{FunctionBuilder, FunctionBuilderContext};
use cranelift_jit::{JITBuilder, JITModule};
use cranelift_module::{FuncId, Linkage, Module};
use crate::mir::{BasicBlockId, MirFunction, ValueId};
/// Simple block map (MIR BB -> CLIF Block)
pub struct BlockMap(pub HashMap<BasicBlockId, Block>);
impl BlockMap {
pub fn new() -> Self {
Self(HashMap::new())
}
pub fn get(&self, bb: &BasicBlockId) -> Option<&Block> {
self.0.get(bb)
}
pub fn insert(&mut self, bb: BasicBlockId, blk: Block) {
self.0.insert(bb, blk);
}
/// Create a CLIF block for each MIR block id
pub fn create_for_function(func: &MirFunction, builder: &mut FunctionBuilder) -> Self {
let mut m = HashMap::new();
for (bb_id, _) in &func.blocks {
m.insert(*bb_id, builder.create_block());
}
Self(m)
}
}
/// Value environment for CLIF lowering: holds current SSA values and pseudo-memory for Load/Store
pub struct ValueEnv {
vals: HashMap<ValueId, cranelift_codegen::ir::Value>,
mem: HashMap<ValueId, cranelift_codegen::ir::Value>,
}
impl ValueEnv {
pub fn new() -> Self {
Self {
vals: HashMap::new(),
mem: HashMap::new(),
}
}
pub fn get_val(&self, id: &ValueId) -> Result<cranelift_codegen::ir::Value, String> {
self.vals
.get(id)
.cloned()
.ok_or_else(|| format!("undef {:?}", id))
}
pub fn set_val(&mut self, id: ValueId, v: cranelift_codegen::ir::Value) {
self.vals.insert(id, v);
}
pub fn get_mem_or(
&self,
id: &ValueId,
default: cranelift_codegen::ir::Value,
) -> cranelift_codegen::ir::Value {
*self.mem.get(id).unwrap_or(&default)
}
pub fn set_mem(&mut self, id: ValueId, v: cranelift_codegen::ir::Value) {
self.mem.insert(id, v);
}
}
/// Cranelift JIT module wrapper (context)
pub struct ClifContext {
pub module: JITModule,
}
impl ClifContext {
pub fn new() -> Result<Self, String> {
let isa_builder = cranelift_native::builder().map_err(|e| e.to_string())?;
let flag_builder = cranelift_codegen::settings::builder();
let flags = cranelift_codegen::settings::Flags::new(flag_builder);
let isa = isa_builder.finish(flags).map_err(|e| e.to_string())?;
let jit_builder = JITBuilder::with_isa(isa, cranelift_module::default_libcall_names());
Ok(Self {
module: JITModule::new(jit_builder),
})
}
/// Declare an exported i64-return function and return its id and Cranelift context/signature
pub fn declare_i64_fn(
&mut self,
name: &str,
) -> Result<(FuncId, cranelift_codegen::Context, Signature), String> {
let mut sig = Signature::new(self.module.target_config().default_call_conv);
sig.returns.push(AbiParam::new(types::I64));
let func_id = self
.module
.declare_function(name, Linkage::Export, &sig)
.map_err(|e| e.to_string())?;
let mut ctx = self.module.make_context();
ctx.func.signature = sig.clone();
Ok((func_id, ctx, sig))
}
pub fn finalize(
&mut self,
func_id: FuncId,
ctx: &mut cranelift_codegen::Context,
) -> Result<*const u8, String> {
self.module
.define_function(func_id, ctx)
.map_err(|e| e.to_string())?;
self.module.clear_context(ctx);
let _ = self.module.finalize_definitions();
Ok(self.module.get_finalized_function(func_id))
}
}

View File

@ -0,0 +1,185 @@
#![cfg(feature = "cranelift-jit")]
use cranelift_codegen::ir::{
condcodes::IntCC, types, AbiParam, InstBuilder, Signature, StackSlot, StackSlotData,
StackSlotKind,
};
use cranelift_codegen::isa;
use cranelift_frontend::{FunctionBuilder, FunctionBuilderContext};
use cranelift_jit::{JITBuilder, JITModule};
use cranelift_module::{Linkage, Module};
use crate::mir::{BasicBlockId, CompareOp, ConstValue, MirFunction, MirInstruction, ValueId};
/// Compile a minimal subset of MIR(main) to a native function and execute it.
/// Supported: Const(Integer), BinOp(Add for integers), Return(Integer or default 0).
pub fn compile_and_execute_minimal(main: &MirFunction) -> Result<i64, String> {
// ISA (native)
let isa_builder = cranelift_native::builder().map_err(|e| e.to_string())?;
let flag_builder = cranelift_codegen::settings::builder();
let flags = cranelift_codegen::settings::Flags::new(flag_builder);
let isa = isa_builder.finish(flags).map_err(|e| e.to_string())?;
let jit_builder = JITBuilder::with_isa(isa, cranelift_module::default_libcall_names());
let mut module = JITModule::new(jit_builder);
// Signature: () -> i64
let mut sig = Signature::new(module.target_config().default_call_conv);
sig.returns
.push(AbiParam::new(cranelift_codegen::ir::types::I64));
let func_id = module
.declare_function("ny_main", Linkage::Export, &sig)
.map_err(|e| e.to_string())?;
let mut ctx = module.make_context();
ctx.func.signature = sig;
let mut fb_ctx = FunctionBuilderContext::new();
let mut builder = FunctionBuilder::new(&mut ctx.func, &mut fb_ctx);
// Prepare blocks for the entire MIR function
use std::collections::HashMap;
let mut clif_blocks: HashMap<BasicBlockId, cranelift_codegen::ir::Block> = HashMap::new();
let mut vals: HashMap<ValueId, cranelift_codegen::ir::Value> = HashMap::new();
let mut slots: HashMap<ValueId, StackSlot> = HashMap::new();
for (bb_id, _) in &main.blocks {
clif_blocks.insert(*bb_id, builder.create_block());
}
// Switch to entry
let entry = *clif_blocks.get(&main.entry_block).unwrap();
builder.switch_to_block(entry);
builder.append_block_params_for_function_params(entry);
// Emit each block
// Deterministic order by id
let mut bb_ids: Vec<_> = main.blocks.keys().copied().collect();
bb_ids.sort_by_key(|b| b.0);
for bb_id in bb_ids {
let bb = main.blocks.get(&bb_id).unwrap();
let cb = *clif_blocks.get(&bb_id).unwrap();
builder.switch_to_block(cb);
for inst in &bb.instructions {
match inst {
MirInstruction::Const { dst, value } => {
let v = match value {
ConstValue::Integer(i) => builder.ins().iconst(types::I64, *i),
ConstValue::Bool(b) => {
builder.ins().iconst(types::I64, if *b { 1 } else { 0 })
}
ConstValue::Float(f) => {
let fv = builder.ins().f64const(*f);
builder.ins().fcvt_to_sint(types::I64, fv)
}
ConstValue::String(_) | ConstValue::Null | ConstValue::Void => {
builder.ins().iconst(types::I64, 0)
}
};
vals.insert(*dst, v);
}
MirInstruction::BinOp { dst, op, lhs, rhs } => {
use crate::mir::BinaryOp;
let l = *vals.get(lhs).ok_or_else(|| format!("undef {:?}", lhs))?;
let r = *vals.get(rhs).ok_or_else(|| format!("undef {:?}", rhs))?;
let out = match op {
BinaryOp::Add => builder.ins().iadd(l, r),
BinaryOp::Sub => builder.ins().isub(l, r),
BinaryOp::Mul => builder.ins().imul(l, r),
BinaryOp::Div => builder.ins().sdiv(l, r),
BinaryOp::Mod => builder.ins().srem(l, r),
_ => builder.ins().iconst(types::I64, 0),
};
vals.insert(*dst, out);
}
MirInstruction::Compare { dst, op, lhs, rhs } => {
let l = *vals.get(lhs).ok_or_else(|| format!("undef {:?}", lhs))?;
let r = *vals.get(rhs).ok_or_else(|| format!("undef {:?}", rhs))?;
let cc = match op {
CompareOp::Eq => IntCC::Equal,
CompareOp::Ne => IntCC::NotEqual,
CompareOp::Lt => IntCC::SignedLessThan,
CompareOp::Le => IntCC::SignedLessThanOrEqual,
CompareOp::Gt => IntCC::SignedGreaterThan,
CompareOp::Ge => IntCC::SignedGreaterThanOrEqual,
};
let b1 = builder.ins().icmp(cc, l, r);
let one = builder.ins().iconst(types::I64, 1);
let zero = builder.ins().iconst(types::I64, 0);
let i64v = builder.ins().select(b1, one, zero);
vals.insert(*dst, i64v);
}
MirInstruction::Load { dst, ptr } => {
if let Some(ss) = slots.get(ptr).copied() {
let v = builder.ins().stack_load(types::I64, ss, 0);
vals.insert(*dst, v);
} else {
vals.insert(*dst, builder.ins().iconst(types::I64, 0));
}
}
MirInstruction::Store { value, ptr } => {
let v = *vals
.get(value)
.ok_or_else(|| format!("undef {:?}", value))?;
let ss = *slots.entry(*ptr).or_insert_with(|| {
builder.create_sized_stack_slot(StackSlotData::new(
StackSlotKind::ExplicitSlot,
8,
))
});
builder.ins().stack_store(v, ss, 0);
}
MirInstruction::Copy { dst, src } => {
let v = *vals.get(src).ok_or_else(|| format!("undef {:?}", src))?;
vals.insert(*dst, v);
}
_ => { /* ignore unhandled for now */ }
}
}
// Terminator
match &bb.terminator {
Some(MirInstruction::Return { value }) => {
let retv = if let Some(v) = value {
*vals.get(v).unwrap_or(&builder.ins().iconst(types::I64, 0))
} else {
builder.ins().iconst(types::I64, 0)
};
builder.ins().return_(&[retv]);
}
Some(MirInstruction::Jump { target }) => {
let t = *clif_blocks.get(target).unwrap();
builder.ins().jump(t, &[]);
}
Some(MirInstruction::Branch {
condition,
then_bb,
else_bb,
}) => {
let cond_i64 = *vals
.get(condition)
.unwrap_or(&builder.ins().iconst(types::I64, 0));
let is_true = builder.ins().icmp_imm(IntCC::NotEqual, cond_i64, 0);
let tb = *clif_blocks.get(then_bb).unwrap();
let eb = *clif_blocks.get(else_bb).unwrap();
builder.ins().brif(is_true, tb, &[], eb, &[]);
}
_ => {
/* fallthrough not allowed: insert return 0 to keep verifier happy */
let z = builder.ins().iconst(types::I64, 0);
builder.ins().return_(&[z]);
}
}
builder.seal_block(cb);
}
builder.finalize();
module
.define_function(func_id, &mut ctx)
.map_err(|e| e.to_string())?;
module.clear_context(&mut ctx);
let _ = module.finalize_definitions();
let code = module.get_finalized_function(func_id);
let func = unsafe { std::mem::transmute::<_, extern "C" fn() -> i64>(code) };
let result = func();
Ok(result)
}

View File

@ -0,0 +1,259 @@
/*!
* Cranelift Backend (skeleton) - Compile MIR to native code for JIT/AOT
*
* Phase 11.7 kick-off: minimal stubs behind the `cranelift-jit` feature.
*/
#![cfg(feature = "cranelift-jit")]
use crate::box_trait::{BoolBox, IntegerBox, NyashBox, StringBox, VoidBox};
use crate::jit::lower::{
builder::{IRBuilder, NoopBuilder},
core::LowerCore,
};
use crate::jit::semantics::clif::ClifSemanticsSkeleton;
use crate::mir::{function::MirModule, BinaryOp, ConstValue, MirInstruction, ValueId};
#[cfg(feature = "cranelift-jit")]
use crate::semantics::Semantics;
use std::collections::HashMap;
pub mod builder;
pub mod context; // Context/Block/Value env wrappers // Clif IRBuilder implementation (skeleton)
pub mod lower {}
pub mod jit; // JIT compile/execute using Cranelift (minimal)
pub mod object {}
/// JIT: compile and execute a MIR module (skeleton)
pub fn compile_and_execute(
mir_module: &MirModule,
_temp_name: &str,
) -> Result<Box<dyn NyashBox>, String> {
// Minimal semantics: Const/Return/Add only (straight-line code)
let main = mir_module
.functions
.get("main")
.ok_or("missing main function")?;
// Minimal ClifSem lowering pass (NoopBuilder): Const/Return/Add カバレッジ確認
if std::env::var("NYASH_JIT_DUMP").ok().as_deref() == Some("1") {
let mut builder = NoopBuilder::new();
let mut lower = LowerCore::new();
let _ = lower.lower_function(main, &mut builder);
eprintln!(
"[CLIF-LOWER] covered={} unsupported={}",
lower.covered, lower.unsupported
);
}
// まずは新JITエンジン経路を試すLowerCore -> CraneliftBuilder -> 実行)
#[cfg(feature = "cranelift-jit")]
{
let mut engine = crate::jit::engine::JitEngine::new();
if let Some(h) = engine.compile_function(&main.signature.name, main) {
// 実行(引数なし)。戻り値は MIR の型に合わせて変換
let out = engine.execute_handle(h, &[]);
if let Some(jv) = out {
let vmv =
crate::jit::boundary::CallBoundaryBox::to_vm(&main.signature.return_type, jv);
let boxed: Box<dyn NyashBox> = match vmv {
crate::backend::vm::VMValue::Integer(i) => Box::new(IntegerBox::new(i)),
crate::backend::vm::VMValue::Float(f) => {
Box::new(crate::boxes::FloatBox::new(f))
}
crate::backend::vm::VMValue::Bool(b) => Box::new(BoolBox::new(b)),
crate::backend::vm::VMValue::String(s) => Box::new(StringBox::new(&s)),
crate::backend::vm::VMValue::BoxRef(b) => b.share_box(),
crate::backend::vm::VMValue::Future(fu) => Box::new(fu),
crate::backend::vm::VMValue::Void => Box::new(VoidBox::new()),
};
return Ok(boxed);
}
}
// 失敗した場合はミニマルJITへフォールバック
if let Ok(i) = crate::backend::cranelift::jit::compile_and_execute_minimal(main) {
return Ok(Box::new(IntegerBox::new(i)));
}
}
let mut regs: HashMap<ValueId, crate::backend::vm::VMValue> = HashMap::new();
let mut cur = main.entry_block;
let mut last_pred: Option<crate::mir::BasicBlockId> = None;
loop {
let bb = main
.blocks
.get(&cur)
.ok_or_else(|| format!("invalid bb {:?}", cur))?;
// PHI (very minimal): choose first input or predecessor match
for inst in &bb.instructions {
if let MirInstruction::Phi { dst, inputs } = inst {
if let Some(pred) = last_pred {
if let Some((_, v)) = inputs.iter().find(|(b, _)| *b == pred) {
if let Some(val) = regs.get(v).cloned() {
regs.insert(*dst, val);
}
}
} else if let Some((_, v)) = inputs.first() {
if let Some(val) = regs.get(v).cloned() {
regs.insert(*dst, val);
}
}
}
}
let mut sem = ClifSemanticsSkeleton::new();
for inst in &bb.instructions {
match inst {
MirInstruction::Const { dst, value } => {
let vv = match value {
ConstValue::Integer(i) => sem.const_i64(*i),
ConstValue::Float(f) => sem.const_f64(*f),
ConstValue::Bool(b) => sem.const_bool(*b),
ConstValue::String(s) => sem.const_str(s),
ConstValue::Null | ConstValue::Void => sem.const_null(),
};
regs.insert(*dst, vv);
}
MirInstruction::BinOp { dst, op, lhs, rhs } if matches!(op, BinaryOp::Add) => {
use crate::backend::vm::VMValue as V;
let a = regs
.get(lhs)
.cloned()
.ok_or_else(|| format!("undef {:?}", lhs))?;
let b = regs
.get(rhs)
.cloned()
.ok_or_else(|| format!("undef {:?}", rhs))?;
let out = sem.add(a, b);
regs.insert(*dst, out);
}
MirInstruction::Copy { dst, src } => {
if let Some(v) = regs.get(src).cloned() {
regs.insert(*dst, v);
}
}
MirInstruction::Debug { .. }
| MirInstruction::Print { .. }
| MirInstruction::Barrier { .. }
| MirInstruction::BarrierRead { .. }
| MirInstruction::BarrierWrite { .. }
| MirInstruction::Safepoint
| MirInstruction::Load { .. }
| MirInstruction::Store { .. }
| MirInstruction::TypeOp { .. }
| MirInstruction::Compare { .. }
| MirInstruction::NewBox { .. }
| MirInstruction::PluginInvoke { .. }
| MirInstruction::BoxCall { .. }
| MirInstruction::RefGet { .. }
| MirInstruction::RefSet { .. }
| MirInstruction::WeakRef { .. }
| MirInstruction::FutureNew { .. }
| MirInstruction::FutureSet { .. }
| MirInstruction::Await { .. }
| MirInstruction::Throw { .. }
| MirInstruction::Catch { .. } => {
// ignore for minimal path
}
MirInstruction::ExternCall {
dst,
iface_name,
method_name,
args,
..
} => {
use crate::backend::vm::VMValue as V;
match (iface_name.as_str(), method_name.as_str()) {
("env.local", "get") => {
if let Some(d) = dst {
if let Some(a0) = args.get(0) {
if let Some(v) = regs.get(a0).cloned() {
regs.insert(*d, v);
}
}
}
}
("env.local", "set") => {
if args.len() >= 2 {
if let Some(v) = regs.get(&args[1]).cloned() {
regs.insert(args[0], v);
}
}
// dst ignored
}
("env.box", "new") => {
if let Some(d) = dst {
if let Some(a0) = args.get(0) {
if let Some(V::String(ty)) = regs.get(a0).cloned() {
let reg =
crate::runtime::box_registry::get_global_registry();
// Collect args as NyashBox
let mut ny_args: Vec<Box<dyn crate::box_trait::NyashBox>> =
Vec::new();
for vid in args.iter().skip(1) {
if let Some(v) = regs.get(vid).cloned() {
ny_args.push(v.to_nyash_box());
}
}
if let Ok(b) = reg.create_box(&ty, &ny_args) {
regs.insert(*d, V::from_nyash_box(b));
}
}
}
}
}
_ => { /* ignore other externs in skeleton */ }
}
}
MirInstruction::Phi { .. } => { /* handled above */ }
_ => {}
}
}
match &bb.terminator {
Some(MirInstruction::Return { value }) => {
use crate::backend::vm::VMValue as V;
let vb = match value {
Some(v) => regs.get(v).cloned().unwrap_or(V::Void),
None => V::Void,
};
// Box to NyashBox
let out: Box<dyn NyashBox> = match vb {
V::Integer(i) => Box::new(IntegerBox::new(i)),
V::Bool(b) => Box::new(BoolBox::new(b)),
V::String(s) => Box::new(StringBox::new(&s)),
V::Float(f) => Box::new(crate::boxes::FloatBox::new(f)),
V::Void => Box::new(VoidBox::new()),
V::Future(fu) => Box::new(fu),
V::BoxRef(b) => b.share_box(),
};
return Ok(out);
}
Some(MirInstruction::Jump { target }) => {
last_pred = Some(bb.id);
cur = *target;
}
Some(MirInstruction::Branch {
condition,
then_bb,
else_bb,
}) => {
// Minimal: integer/bool truthiness
let c = regs
.get(condition)
.cloned()
.unwrap_or(crate::backend::vm::VMValue::Void);
let t = match c {
crate::backend::vm::VMValue::Bool(b) => b,
crate::backend::vm::VMValue::Integer(i) => i != 0,
_ => false,
};
last_pred = Some(bb.id);
cur = if t { *then_bb } else { *else_bb };
}
Some(other) => return Err(format!("unsupported terminator {:?}", other)),
None => return Err(format!("unterminated block {:?}", bb.id)),
}
}
}
/// AOT: compile to object file (not yet implemented in skeleton)
pub fn compile_to_object(_mir_module: &MirModule, _out_path: &str) -> Result<(), String> {
Err("Cranelift AOT emit not implemented (skeleton)".to_string())
}

View File

@ -0,0 +1,213 @@
use crate::box_trait::{BoolBox, BoxBase, BoxCore, IntegerBox, NyashBox, StringBox, VoidBox};
use crate::interpreter::RuntimeError;
use crate::jit::config::JitConfig;
use std::any::Any;
use std::sync::RwLock;
#[derive(Debug)]
pub struct JitConfigBox {
base: BoxBase,
pub config: RwLock<JitConfig>,
}
impl JitConfigBox {
pub fn new() -> Self {
Self {
base: BoxBase::new(),
config: RwLock::new(JitConfig::from_env()),
}
}
/// Update internal config flags from runtime capability probe
pub fn from_runtime_probe(&self) -> Box<dyn NyashBox> {
let caps = crate::jit::config::probe_capabilities();
let mut cfg = self.config.write().unwrap();
if cfg.native_bool_abi && !caps.supports_b1_sig {
cfg.native_bool_abi = false;
}
Box::new(VoidBox::new())
}
pub fn set_flag(&self, name: &str, on: bool) -> Result<Box<dyn NyashBox>, RuntimeError> {
let mut cfg = self.config.write().unwrap();
match name {
"exec" => cfg.exec = on,
"stats" => cfg.stats = on,
"stats_json" => cfg.stats_json = on,
"dump" => cfg.dump = on,
"phi_min" => cfg.phi_min = on,
"hostcall" => cfg.hostcall = on,
"handle_debug" => cfg.handle_debug = on,
"native_f64" => cfg.native_f64 = on,
"native_bool" => cfg.native_bool = on,
"bool_abi" | "native_bool_abi" => cfg.native_bool_abi = on,
"ret_b1" | "ret_bool_b1" => cfg.ret_bool_b1 = on,
"relax_numeric" | "hostcall_relax_numeric" => cfg.relax_numeric = on,
_ => {
return Err(RuntimeError::InvalidOperation {
message: format!("Unknown flag: {}", name),
})
}
}
Ok(Box::new(VoidBox::new()))
}
pub fn get_flag(&self, name: &str) -> Result<Box<dyn NyashBox>, RuntimeError> {
let cfg = self.config.read().unwrap();
let b = match name {
"exec" => cfg.exec,
"stats" => cfg.stats,
"stats_json" => cfg.stats_json,
"dump" => cfg.dump,
"phi_min" => cfg.phi_min,
"hostcall" => cfg.hostcall,
"handle_debug" => cfg.handle_debug,
"native_f64" => cfg.native_f64,
"native_bool" => cfg.native_bool,
"bool_abi" | "native_bool_abi" => cfg.native_bool_abi,
"ret_b1" | "ret_bool_b1" => cfg.ret_bool_b1,
"relax_numeric" | "hostcall_relax_numeric" => cfg.relax_numeric,
_ => {
return Err(RuntimeError::InvalidOperation {
message: format!("Unknown flag: {}", name),
})
}
};
Ok(Box::new(BoolBox::new(b)))
}
pub fn set_threshold(&self, v: i64) -> Result<Box<dyn NyashBox>, RuntimeError> {
let mut cfg = self.config.write().unwrap();
if v <= 0 {
cfg.threshold = None;
} else {
cfg.threshold = Some(v as u32);
}
Ok(Box::new(VoidBox::new()))
}
pub fn get_threshold(&self) -> Box<dyn NyashBox> {
let cfg = self.config.read().unwrap();
Box::new(IntegerBox::new(
cfg.threshold.map(|v| v as i64).unwrap_or(0),
))
}
pub fn to_json(&self) -> Box<dyn NyashBox> {
let cfg = self.config.read().unwrap();
let val = serde_json::json!({
"exec": cfg.exec,
"stats": cfg.stats,
"stats_json": cfg.stats_json,
"dump": cfg.dump,
"threshold": cfg.threshold,
"phi_min": cfg.phi_min,
"hostcall": cfg.hostcall,
"handle_debug": cfg.handle_debug,
"native_f64": cfg.native_f64,
"native_bool": cfg.native_bool,
"native_bool_abi": cfg.native_bool_abi,
"ret_bool_b1": cfg.ret_bool_b1,
"relax_numeric": cfg.relax_numeric,
});
Box::new(StringBox::new(val.to_string()))
}
pub fn from_json(&self, s: &str) -> Result<Box<dyn NyashBox>, RuntimeError> {
let mut cfg = self.config.write().unwrap();
let v: serde_json::Value =
serde_json::from_str(s).map_err(|e| RuntimeError::InvalidOperation {
message: format!("Invalid JSON: {}", e),
})?;
if let Some(b) = v.get("exec").and_then(|x| x.as_bool()) {
cfg.exec = b;
}
if let Some(b) = v.get("stats").and_then(|x| x.as_bool()) {
cfg.stats = b;
}
if let Some(b) = v.get("stats_json").and_then(|x| x.as_bool()) {
cfg.stats_json = b;
}
if let Some(b) = v.get("dump").and_then(|x| x.as_bool()) {
cfg.dump = b;
}
if let Some(n) = v.get("threshold").and_then(|x| x.as_u64()) {
cfg.threshold = Some(n as u32);
}
if let Some(b) = v.get("phi_min").and_then(|x| x.as_bool()) {
cfg.phi_min = b;
}
if let Some(b) = v.get("hostcall").and_then(|x| x.as_bool()) {
cfg.hostcall = b;
}
if let Some(b) = v.get("handle_debug").and_then(|x| x.as_bool()) {
cfg.handle_debug = b;
}
if let Some(b) = v.get("native_f64").and_then(|x| x.as_bool()) {
cfg.native_f64 = b;
}
if let Some(b) = v.get("native_bool").and_then(|x| x.as_bool()) {
cfg.native_bool = b;
}
if let Some(b) = v.get("native_bool_abi").and_then(|x| x.as_bool()) {
cfg.native_bool_abi = b;
}
if let Some(b) = v.get("ret_bool_b1").and_then(|x| x.as_bool()) {
cfg.ret_bool_b1 = b;
}
if let Some(b) = v.get("relax_numeric").and_then(|x| x.as_bool()) {
cfg.relax_numeric = b;
}
Ok(Box::new(VoidBox::new()))
}
pub fn apply(&self) -> Box<dyn NyashBox> {
let cfg = self.config.read().unwrap().clone();
// Apply to env for CLI parity
cfg.apply_env();
// Also set global current JIT config for hot paths (env-less)
crate::jit::config::set_current(cfg);
Box::new(VoidBox::new())
}
pub fn summary(&self) -> Box<dyn NyashBox> {
let cfg = self.config.read().unwrap();
let s = format!(
"exec={} stats={} json={} dump={} thr={:?} phi_min={} hostcall={} hdbg={} f64={} bool={} relax_numeric={}",
cfg.exec, cfg.stats, cfg.stats_json, cfg.dump, cfg.threshold,
cfg.phi_min, cfg.hostcall, cfg.handle_debug, cfg.native_f64, cfg.native_bool, cfg.relax_numeric
);
Box::new(StringBox::new(s))
}
}
impl BoxCore for JitConfigBox {
fn box_id(&self) -> u64 {
self.base.id
}
fn parent_type_id(&self) -> Option<std::any::TypeId> {
self.base.parent_type_id
}
fn fmt_box(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "JitConfigBox")
}
fn as_any(&self) -> &dyn Any {
self
}
fn as_any_mut(&mut self) -> &mut dyn Any {
self
}
}
impl NyashBox for JitConfigBox {
fn to_string_box(&self) -> StringBox {
StringBox::new(self.summary().to_string_box().value)
}
fn equals(&self, other: &dyn NyashBox) -> BoolBox {
BoolBox::new(other.as_any().is::<JitConfigBox>())
}
fn type_name(&self) -> &'static str {
"JitConfigBox"
}
fn clone_box(&self) -> Box<dyn NyashBox> {
let cfg = self.config.read().unwrap().clone();
Box::new(JitConfigBox {
base: self.base.clone(),
config: RwLock::new(cfg),
})
}
fn share_box(&self) -> Box<dyn NyashBox> {
self.clone_box()
}
}

View File

@ -0,0 +1,74 @@
use crate::box_trait::{BoolBox, BoxBase, BoxCore, NyashBox, StringBox, VoidBox};
use std::any::Any;
#[derive(Debug, Clone)]
pub struct JitEventsBox {
base: BoxBase,
}
impl JitEventsBox {
pub fn new() -> Self {
Self {
base: BoxBase::new(),
}
}
}
impl BoxCore for JitEventsBox {
fn box_id(&self) -> u64 {
self.base.id
}
fn parent_type_id(&self) -> Option<std::any::TypeId> {
self.base.parent_type_id
}
fn fmt_box(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "JitEventsBox")
}
fn as_any(&self) -> &dyn Any {
self
}
fn as_any_mut(&mut self) -> &mut dyn Any {
self
}
}
impl NyashBox for JitEventsBox {
fn to_string_box(&self) -> StringBox {
StringBox::new("JitEventsBox")
}
fn equals(&self, other: &dyn NyashBox) -> BoolBox {
BoolBox::new(other.as_any().is::<JitEventsBox>())
}
fn type_name(&self) -> &'static str {
"JitEventsBox"
}
fn clone_box(&self) -> Box<dyn NyashBox> {
Box::new(Self {
base: self.base.clone(),
})
}
fn share_box(&self) -> Box<dyn NyashBox> {
self.clone_box()
}
}
impl JitEventsBox {
pub fn set_path(&self, path: &str) -> Box<dyn NyashBox> {
std::env::set_var("NYASH_JIT_EVENTS_PATH", path);
Box::new(VoidBox::new())
}
pub fn enable(&self, on: bool) -> Box<dyn NyashBox> {
if on {
std::env::set_var("NYASH_JIT_EVENTS", "1");
} else {
std::env::remove_var("NYASH_JIT_EVENTS");
}
Box::new(VoidBox::new())
}
pub fn log(&self, kind: &str, function: &str, note_json: &str) -> Box<dyn NyashBox> {
let extra = serde_json::from_str::<serde_json::Value>(note_json)
.unwrap_or_else(|_| serde_json::json!({"note": note_json}));
crate::jit::events::emit(kind, function, None, None, extra);
Box::new(VoidBox::new())
}
}

View File

@ -0,0 +1,78 @@
use crate::box_trait::{BoolBox, BoxBase, BoxCore, NyashBox, StringBox, VoidBox};
use std::any::Any;
#[derive(Debug, Clone)]
pub struct JitHostcallRegistryBox {
base: BoxBase,
}
impl JitHostcallRegistryBox {
pub fn new() -> Self {
Self {
base: BoxBase::new(),
}
}
}
impl BoxCore for JitHostcallRegistryBox {
fn box_id(&self) -> u64 {
self.base.id
}
fn parent_type_id(&self) -> Option<std::any::TypeId> {
self.base.parent_type_id
}
fn fmt_box(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "JitHostcallRegistryBox")
}
fn as_any(&self) -> &dyn Any {
self
}
fn as_any_mut(&mut self) -> &mut dyn Any {
self
}
}
impl NyashBox for JitHostcallRegistryBox {
fn to_string_box(&self) -> StringBox {
let (ro, mu) = crate::jit::hostcall_registry::snapshot();
let payload = serde_json::json!({ "readonly": ro, "mutating": mu });
StringBox::new(payload.to_string())
}
fn equals(&self, other: &dyn NyashBox) -> BoolBox {
BoolBox::new(other.as_any().is::<JitHostcallRegistryBox>())
}
fn type_name(&self) -> &'static str {
"JitHostcallRegistryBox"
}
fn clone_box(&self) -> Box<dyn NyashBox> {
Box::new(Self {
base: self.base.clone(),
})
}
fn share_box(&self) -> Box<dyn NyashBox> {
self.clone_box()
}
}
impl JitHostcallRegistryBox {
pub fn add_readonly(&self, sym: &str) -> Box<dyn NyashBox> {
crate::jit::hostcall_registry::add_readonly(sym);
Box::new(VoidBox::new())
}
pub fn add_mutating(&self, sym: &str) -> Box<dyn NyashBox> {
crate::jit::hostcall_registry::add_mutating(sym);
Box::new(VoidBox::new())
}
pub fn set_from_csv(&self, ro_csv: &str, mu_csv: &str) -> Box<dyn NyashBox> {
crate::jit::hostcall_registry::set_from_csv(ro_csv, mu_csv);
Box::new(VoidBox::new())
}
pub fn add_signature(&self, sym: &str, args_csv: &str, ret_str: &str) -> Box<dyn NyashBox> {
let ok = crate::jit::hostcall_registry::set_signature_csv(sym, args_csv, ret_str);
if ok {
Box::new(VoidBox::new())
} else {
Box::new(StringBox::new("Invalid signature"))
}
}
}

View File

@ -0,0 +1,144 @@
use crate::box_trait::{BoolBox, BoxBase, BoxCore, NyashBox, StringBox, VoidBox};
use std::any::Any;
#[derive(Debug, Clone)]
pub struct JitPolicyBox {
base: BoxBase,
}
impl JitPolicyBox {
pub fn new() -> Self {
Self {
base: BoxBase::new(),
}
}
}
impl BoxCore for JitPolicyBox {
fn box_id(&self) -> u64 {
self.base.id
}
fn parent_type_id(&self) -> Option<std::any::TypeId> {
self.base.parent_type_id
}
fn fmt_box(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "JitPolicyBox")
}
fn as_any(&self) -> &dyn Any {
self
}
fn as_any_mut(&mut self) -> &mut dyn Any {
self
}
}
impl NyashBox for JitPolicyBox {
fn to_string_box(&self) -> StringBox {
let p = crate::jit::policy::current();
let s = format!(
"read_only={} whitelist={}",
p.read_only,
p.hostcall_whitelist.join(",")
);
StringBox::new(s)
}
fn equals(&self, other: &dyn NyashBox) -> BoolBox {
BoolBox::new(other.as_any().is::<JitPolicyBox>())
}
fn type_name(&self) -> &'static str {
"JitPolicyBox"
}
fn clone_box(&self) -> Box<dyn NyashBox> {
Box::new(Self {
base: self.base.clone(),
})
}
fn share_box(&self) -> Box<dyn NyashBox> {
self.clone_box()
}
}
// Methods (exposed via VM dispatch):
impl JitPolicyBox {
pub fn set_flag(&self, name: &str, on: bool) -> Box<dyn NyashBox> {
let mut cur = crate::jit::policy::current();
match name {
"read_only" | "readonly" => cur.read_only = on,
_ => return Box::new(StringBox::new(format!("Unknown flag: {}", name))),
}
crate::jit::policy::set_current(cur);
Box::new(VoidBox::new())
}
pub fn get_flag(&self, name: &str) -> Box<dyn NyashBox> {
let cur = crate::jit::policy::current();
let v = match name {
"read_only" | "readonly" => cur.read_only,
_ => false,
};
Box::new(BoolBox::new(v))
}
pub fn set_whitelist_csv(&self, csv: &str) -> Box<dyn NyashBox> {
let mut cur = crate::jit::policy::current();
cur.hostcall_whitelist = csv
.split(',')
.map(|t| t.trim().to_string())
.filter(|s| !s.is_empty())
.collect();
crate::jit::policy::set_current(cur);
Box::new(VoidBox::new())
}
pub fn add_whitelist(&self, name: &str) -> Box<dyn NyashBox> {
let mut cur = crate::jit::policy::current();
if !cur.hostcall_whitelist.iter().any(|s| s == name) {
cur.hostcall_whitelist.push(name.to_string());
}
crate::jit::policy::set_current(cur);
Box::new(VoidBox::new())
}
pub fn clear_whitelist(&self) -> Box<dyn NyashBox> {
let mut cur = crate::jit::policy::current();
cur.hostcall_whitelist.clear();
crate::jit::policy::set_current(cur);
Box::new(VoidBox::new())
}
pub fn enable_preset(&self, name: &str) -> Box<dyn NyashBox> {
let mut cur = crate::jit::policy::current();
match name {
// 最小: Array.push_h のみ許可(読み取り以外は変えない)
"mutating_minimal" | "mutating_array_push" => {
let id = crate::jit::r#extern::collections::SYM_ARRAY_PUSH_H;
if !cur.hostcall_whitelist.iter().any(|s| s == id) {
cur.hostcall_whitelist.push(id.to_string());
}
}
// 例: Map.set_h も追加許可(必要に応じて拡張)
"mutating_map_set" => {
let id = crate::jit::r#extern::collections::SYM_MAP_SET_H;
if !cur.hostcall_whitelist.iter().any(|s| s == id) {
cur.hostcall_whitelist.push(id.to_string());
}
}
// よく使う: Array.push_h + Array.set_h + Map.set_h を許可
"mutating_common" => {
let ids = [
crate::jit::r#extern::collections::SYM_ARRAY_PUSH_H,
crate::jit::r#extern::collections::SYM_ARRAY_SET_H,
crate::jit::r#extern::collections::SYM_MAP_SET_H,
];
for id in ids {
if !cur.hostcall_whitelist.iter().any(|s| s == id) {
cur.hostcall_whitelist.push(id.to_string());
}
}
}
_ => {
return Box::new(StringBox::new(format!("Unknown preset: {}", name)));
}
}
crate::jit::policy::set_current(cur);
Box::new(VoidBox::new())
}
}

View File

@ -0,0 +1,71 @@
use crate::box_trait::{BoolBox, BoxBase, BoxCore, NyashBox, StringBox};
use std::any::Any;
#[derive(Debug, Clone)]
pub struct JitStatsBox {
base: BoxBase,
}
impl JitStatsBox {
pub fn new() -> Self {
Self {
base: BoxBase::new(),
}
}
pub fn to_json(&self) -> Box<dyn NyashBox> {
let cfg = crate::jit::config::current();
let caps = crate::jit::config::probe_capabilities();
let mode = if cfg.native_bool_abi && caps.supports_b1_sig {
"b1_bool"
} else {
"i64_bool"
};
let payload = serde_json::json!({
"version": 1,
"abi_mode": mode,
"abi_b1_enabled": cfg.native_bool_abi,
"abi_b1_supported": caps.supports_b1_sig,
"b1_norm_count": crate::jit::rt::b1_norm_get(),
"ret_bool_hint_count": crate::jit::rt::ret_bool_hint_get(),
"phi_total_slots": crate::jit::rt::phi_total_get(),
"phi_b1_slots": crate::jit::rt::phi_b1_get(),
});
Box::new(StringBox::new(payload.to_string()))
}
}
impl BoxCore for JitStatsBox {
fn box_id(&self) -> u64 {
self.base.id
}
fn parent_type_id(&self) -> Option<std::any::TypeId> {
self.base.parent_type_id
}
fn fmt_box(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "JitStatsBox")
}
fn as_any(&self) -> &dyn Any {
self
}
fn as_any_mut(&mut self) -> &mut dyn Any {
self
}
}
impl NyashBox for JitStatsBox {
fn to_string_box(&self) -> StringBox {
StringBox::new(self.to_json().to_string_box().value)
}
fn equals(&self, other: &dyn NyashBox) -> BoolBox {
BoolBox::new(other.as_any().is::<JitStatsBox>())
}
fn type_name(&self) -> &'static str {
"JitStatsBox"
}
fn clone_box(&self) -> Box<dyn NyashBox> {
Box::new(self.clone())
}
fn share_box(&self) -> Box<dyn NyashBox> {
self.clone_box()
}
}

View File

@ -0,0 +1,104 @@
use crate::box_trait::{BoolBox, BoxBase, BoxCore, NyashBox, StringBox, VoidBox};
use std::any::Any;
#[derive(Debug, Clone)]
pub struct JitStrictBox {
base: BoxBase,
}
impl JitStrictBox {
pub fn new() -> Self {
Self {
base: BoxBase::new(),
}
}
}
impl BoxCore for JitStrictBox {
fn box_id(&self) -> u64 {
self.base.id
}
fn parent_type_id(&self) -> Option<std::any::TypeId> {
self.base.parent_type_id
}
fn fmt_box(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "JitStrictBox")
}
fn as_any(&self) -> &dyn Any {
self
}
fn as_any_mut(&mut self) -> &mut dyn Any {
self
}
}
impl NyashBox for JitStrictBox {
fn to_string_box(&self) -> StringBox {
StringBox::new("JitStrictBox".to_string())
}
fn equals(&self, other: &dyn NyashBox) -> BoolBox {
BoolBox::new(other.as_any().is::<JitStrictBox>())
}
fn type_name(&self) -> &'static str {
"JitStrictBox"
}
fn clone_box(&self) -> Box<dyn NyashBox> {
Box::new(Self {
base: self.base.clone(),
})
}
fn share_box(&self) -> Box<dyn NyashBox> {
self.clone_box()
}
}
impl JitStrictBox {
/// Enable/disable strict mode. When enabling, also set JIT-only and args-handle-only by default.
pub fn enable(&self, on: bool) -> Box<dyn NyashBox> {
if on {
std::env::set_var("NYASH_JIT_STRICT", "1");
if std::env::var("NYASH_JIT_ONLY").ok().is_none() {
std::env::set_var("NYASH_JIT_ONLY", "1");
}
if std::env::var("NYASH_JIT_ARGS_HANDLE_ONLY").ok().is_none() {
std::env::set_var("NYASH_JIT_ARGS_HANDLE_ONLY", "1");
}
} else {
std::env::remove_var("NYASH_JIT_STRICT");
}
Box::new(VoidBox::new())
}
pub fn set_only(&self, on: bool) -> Box<dyn NyashBox> {
if on {
std::env::set_var("NYASH_JIT_ONLY", "1");
} else {
std::env::remove_var("NYASH_JIT_ONLY");
}
Box::new(VoidBox::new())
}
pub fn set_handle_only(&self, on: bool) -> Box<dyn NyashBox> {
if on {
std::env::set_var("NYASH_JIT_ARGS_HANDLE_ONLY", "1");
} else {
std::env::remove_var("NYASH_JIT_ARGS_HANDLE_ONLY");
}
Box::new(VoidBox::new())
}
pub fn status(&self) -> Box<dyn NyashBox> {
let s = serde_json::json!({
"strict": std::env::var("NYASH_JIT_STRICT").ok().as_deref() == Some("1"),
"jit_only": std::env::var("NYASH_JIT_ONLY").ok().as_deref() == Some("1"),
"args_handle_only": std::env::var("NYASH_JIT_ARGS_HANDLE_ONLY").ok().as_deref() == Some("1"),
"lower_fallbacks": crate::jit::events::lower_fallbacks_get(),
});
Box::new(StringBox::new(s.to_string()))
}
/// Reset compile-time counters (e.g., lower fallback count) before next compile.
pub fn reset_counters(&self) -> Box<dyn NyashBox> {
crate::jit::events::lower_counters_reset();
Box::new(VoidBox::new())
}
}

View File

@ -0,0 +1,72 @@
//! JIT minimal ABI types independent from VM internals
#[derive(Debug, Clone, Copy)]
pub enum JitValue {
I64(i64),
F64(f64),
Bool(bool),
/// Opaque handle for host objects (future use)
Handle(u64),
}
impl JitValue {
pub fn as_i64(&self) -> Option<i64> {
if let JitValue::I64(v) = self {
Some(*v)
} else {
None
}
}
}
/// Adapter between VMValue and JitValue — keeps JIT decoupled from VM internals
pub mod adapter {
use super::JitValue;
use crate::backend::vm::VMValue;
pub fn to_jit_values(args: &[VMValue]) -> Vec<JitValue> {
args.iter()
.map(|v| match v {
VMValue::Integer(i) => JitValue::I64(*i),
VMValue::Float(f) => JitValue::F64(*f),
VMValue::Bool(b) => JitValue::Bool(*b),
VMValue::BoxRef(arc) => {
let h = crate::jit::rt::handles::to_handle(arc.clone());
JitValue::Handle(h)
}
// For now, map others to handle via boxing where reasonable
VMValue::String(s) => {
let bx = Box::new(crate::box_trait::StringBox::new(s));
let bx_dyn: Box<dyn crate::box_trait::NyashBox> = bx;
let arc: std::sync::Arc<dyn crate::box_trait::NyashBox> =
std::sync::Arc::from(bx_dyn);
let h = crate::jit::rt::handles::to_handle(arc);
JitValue::Handle(h)
}
VMValue::Void => JitValue::Handle(0),
VMValue::Future(f) => {
let bx_dyn: Box<dyn crate::box_trait::NyashBox> = Box::new(f.clone());
let arc: std::sync::Arc<dyn crate::box_trait::NyashBox> =
std::sync::Arc::from(bx_dyn);
let h = crate::jit::rt::handles::to_handle(arc);
JitValue::Handle(h)
}
})
.collect()
}
pub fn from_jit_value(v: JitValue) -> VMValue {
match v {
JitValue::I64(i) => VMValue::Integer(i),
JitValue::F64(f) => VMValue::Float(f),
JitValue::Bool(b) => VMValue::Bool(b),
JitValue::Handle(h) => {
if let Some(arc) = crate::jit::rt::handles::get(h) {
VMValue::BoxRef(arc)
} else {
VMValue::Void
}
}
}
}
}

View File

@ -0,0 +1,85 @@
//! CallBoundaryBox: unify JIT→VM return conversion in one place
use super::abi::JitValue;
use crate::backend::vm::VMValue;
pub struct CallBoundaryBox;
impl CallBoundaryBox {
pub fn to_vm(ret_ty: &crate::mir::MirType, v: JitValue) -> VMValue {
match ret_ty {
crate::mir::MirType::Float => match v {
JitValue::F64(f) => VMValue::Float(f),
JitValue::I64(i) => VMValue::Float(i as f64),
JitValue::Bool(b) => VMValue::Float(if b { 1.0 } else { 0.0 }),
JitValue::Handle(h) => {
if let Some(_) = crate::jit::rt::handles::get(h) {
VMValue::Float(0.0)
} else {
VMValue::Float(0.0)
}
}
},
crate::mir::MirType::Integer => match v {
JitValue::I64(i) => VMValue::Integer(i),
JitValue::F64(f) => VMValue::Integer(f as i64),
JitValue::Bool(b) => VMValue::Integer(if b { 1 } else { 0 }),
JitValue::Handle(h) => {
if let Some(_) = crate::jit::rt::handles::get(h) {
VMValue::Integer(0)
} else {
VMValue::Integer(0)
}
}
},
crate::mir::MirType::Bool => match v {
JitValue::Bool(b) => VMValue::Bool(b),
JitValue::I64(i) => VMValue::Bool(i != 0),
JitValue::F64(f) => VMValue::Bool(f != 0.0),
JitValue::Handle(h) => {
if let Some(_) = crate::jit::rt::handles::get(h) {
VMValue::Bool(true)
} else {
VMValue::Bool(false)
}
}
},
// Box-like returns: if we received a handle id (encoded as I64), resolve to BoxRef; also honor explicit Handle
crate::mir::MirType::Box(_)
| crate::mir::MirType::String
| crate::mir::MirType::Array(_)
| crate::mir::MirType::Future(_) => match v {
JitValue::I64(i) => {
let h = i as u64;
if let Some(arc) = crate::jit::rt::handles::get(h) {
VMValue::BoxRef(arc)
} else {
VMValue::Integer(i)
}
}
JitValue::Handle(h) => {
if let Some(arc) = crate::jit::rt::handles::get(h) {
VMValue::BoxRef(arc)
} else {
VMValue::Void
}
}
JitValue::F64(f) => VMValue::Float(f),
JitValue::Bool(b) => VMValue::Bool(b),
},
_ => {
// Default adapter with heuristic: treat I64 matching a known handle as BoxRef
match v {
JitValue::I64(i) => {
let h = i as u64;
if let Some(arc) = crate::jit::rt::handles::get(h) {
VMValue::BoxRef(arc)
} else {
super::abi::adapter::from_jit_value(JitValue::I64(i))
}
}
_ => super::abi::adapter::from_jit_value(v),
}
}
}
}
}

View File

@ -0,0 +1,129 @@
//! JIT configuration aggregator
//!
//! Centralizes JIT-related flags so callers and tests can use a single
//! source of truth instead of scattering env access across modules.
#[derive(Debug, Clone, Default)]
pub struct JitConfig {
pub exec: bool, // NYASH_JIT_EXEC
pub stats: bool, // NYASH_JIT_STATS
pub stats_json: bool, // NYASH_JIT_STATS_JSON
pub dump: bool, // NYASH_JIT_DUMP
pub threshold: Option<u32>, // NYASH_JIT_THRESHOLD
pub phi_min: bool, // NYASH_JIT_PHI_MIN
pub hostcall: bool, // NYASH_JIT_HOSTCALL
pub handle_debug: bool, // NYASH_JIT_HANDLE_DEBUG
pub native_f64: bool, // NYASH_JIT_NATIVE_F64
pub native_bool: bool, // NYASH_JIT_NATIVE_BOOL
pub native_bool_abi: bool, // NYASH_JIT_ABI_B1 (experimental)
pub ret_bool_b1: bool, // NYASH_JIT_RET_B1 (footing; currently returns i64 0/1)
pub relax_numeric: bool, // NYASH_JIT_HOSTCALL_RELAX_NUMERIC (i64->f64 coercion)
}
impl JitConfig {
pub fn from_env() -> Self {
let getb = |k: &str| std::env::var(k).ok().as_deref() == Some("1");
let threshold = std::env::var("NYASH_JIT_THRESHOLD")
.ok()
.and_then(|s| s.parse::<u32>().ok());
// Respect explicit dump flag, but also treat a non-empty NYASH_JIT_DOT path
// as an implicit request to enable dump (so Box/CLI/env stay consistent).
let dump_flag = getb("NYASH_JIT_DUMP")
|| std::env::var("NYASH_JIT_DOT")
.ok()
.map(|s| !s.is_empty())
.unwrap_or(false);
Self {
exec: getb("NYASH_JIT_EXEC"),
stats: getb("NYASH_JIT_STATS"),
stats_json: getb("NYASH_JIT_STATS_JSON"),
dump: dump_flag,
threshold,
phi_min: getb("NYASH_JIT_PHI_MIN"),
hostcall: getb("NYASH_JIT_HOSTCALL"),
handle_debug: getb("NYASH_JIT_HANDLE_DEBUG"),
native_f64: getb("NYASH_JIT_NATIVE_F64"),
native_bool: getb("NYASH_JIT_NATIVE_BOOL"),
native_bool_abi: getb("NYASH_JIT_ABI_B1"),
ret_bool_b1: getb("NYASH_JIT_RET_B1"),
relax_numeric: getb("NYASH_JIT_HOSTCALL_RELAX_NUMERIC"),
}
}
/// Apply current struct values into environment variables.
/// This keeps existing env untouched unless the value is explicitly set here.
pub fn apply_env(&self) {
let setb = |k: &str, v: bool| {
if v {
std::env::set_var(k, "1");
}
};
setb("NYASH_JIT_EXEC", self.exec);
setb("NYASH_JIT_STATS", self.stats);
setb("NYASH_JIT_STATS_JSON", self.stats_json);
setb("NYASH_JIT_DUMP", self.dump);
if let Some(t) = self.threshold {
std::env::set_var("NYASH_JIT_THRESHOLD", t.to_string());
}
setb("NYASH_JIT_PHI_MIN", self.phi_min);
setb("NYASH_JIT_HOSTCALL", self.hostcall);
setb("NYASH_JIT_HANDLE_DEBUG", self.handle_debug);
setb("NYASH_JIT_NATIVE_F64", self.native_f64);
setb("NYASH_JIT_NATIVE_BOOL", self.native_bool);
setb("NYASH_JIT_ABI_B1", self.native_bool_abi);
setb("NYASH_JIT_RET_B1", self.ret_bool_b1);
setb("NYASH_JIT_HOSTCALL_RELAX_NUMERIC", self.relax_numeric);
}
}
// Global current JIT config (thread-safe), defaults to env when unset
use once_cell::sync::OnceCell;
use std::sync::RwLock;
static GLOBAL_JIT_CONFIG: OnceCell<RwLock<JitConfig>> = OnceCell::new();
/// Get current JIT config (falls back to env-derived default if unset)
pub fn current() -> JitConfig {
if let Some(lock) = GLOBAL_JIT_CONFIG.get() {
if let Ok(cfg) = lock.read() {
return cfg.clone();
}
}
JitConfig::from_env()
}
/// Set current JIT config (overrides env lookups in hot paths)
pub fn set_current(cfg: JitConfig) {
if let Some(lock) = GLOBAL_JIT_CONFIG.get() {
if let Ok(mut w) = lock.write() {
*w = cfg;
return;
}
}
let _ = GLOBAL_JIT_CONFIG.set(RwLock::new(cfg));
}
// --- Runtime capability probing (minimal, safe defaults) ---
#[derive(Debug, Clone, Copy, Default)]
pub struct JitCapabilities {
pub supports_b1_sig: bool,
}
/// Probe JIT backend capabilities once. Safe default: b1 signatures are unsupported.
pub fn probe_capabilities() -> JitCapabilities {
// Current toolchain: allow forcing via env for experiments; otherwise false.
// When upgrading Cranelift to a version with B1 signature support, set NYASH_JIT_ABI_B1_SUPPORT=1
let forced = std::env::var("NYASH_JIT_ABI_B1_SUPPORT").ok().as_deref() == Some("1");
JitCapabilities {
supports_b1_sig: forced,
}
}
/// Apply runtime capabilities onto a JitConfig (e.g., disable b1 ABI when unsupported)
pub fn apply_runtime_caps(mut cfg: JitConfig, caps: JitCapabilities) -> JitConfig {
if cfg.native_bool_abi && !caps.supports_b1_sig {
cfg.native_bool_abi = false;
}
cfg
}

View File

@ -0,0 +1,343 @@
//! JIT Engine skeleton
//!
//! Phase 10_a: Provide a placeholder engine interface that later hosts
//! Cranelift contexts and compiled function handles.
use std::collections::HashMap;
use std::sync::Arc;
#[derive(Default)]
pub struct JitEngine {
// In the future: isa, module, context, fn table, etc.
#[allow(dead_code)]
initialized: bool,
#[allow(dead_code)]
next_handle: u64,
/// Stub function table: handle -> callable closure
fntab: HashMap<
u64,
Arc<dyn Fn(&[crate::jit::abi::JitValue]) -> crate::jit::abi::JitValue + Send + Sync>,
>,
/// Host externs by symbol name (Phase 10_d)
externs: HashMap<
String,
Arc<dyn Fn(&[crate::backend::vm::VMValue]) -> crate::backend::vm::VMValue + Send + Sync>,
>,
#[cfg(feature = "cranelift-jit")]
isa: Option<cranelift_codegen::isa::OwnedTargetIsa>,
// Last lower stats (per function)
last_phi_total: u64,
last_phi_b1: u64,
last_ret_bool_hint: bool,
}
impl JitEngine {
pub fn new() -> Self {
let mut this = Self {
initialized: true,
next_handle: 1,
fntab: HashMap::new(),
externs: HashMap::new(),
#[cfg(feature = "cranelift-jit")]
isa: None,
last_phi_total: 0,
last_phi_b1: 0,
last_ret_bool_hint: false,
};
#[cfg(feature = "cranelift-jit")]
{
this.isa = None;
}
this.register_default_externs();
this
}
/// Compile a function if supported; returns an opaque handle id
pub fn compile_function(
&mut self,
func_name: &str,
mir: &crate::mir::MirFunction,
) -> Option<u64> {
let t0 = std::time::Instant::now();
// Phase 10_b skeleton: walk MIR with LowerCore and report coverage
// Reset compile-phase counters (e.g., fallback decisions) before lowering this function
crate::jit::events::lower_counters_reset();
let mut lower = crate::jit::lower::core::LowerCore::new();
#[cfg(feature = "cranelift-jit")]
let mut builder = crate::jit::lower::builder::CraneliftBuilder::new();
#[cfg(not(feature = "cranelift-jit"))]
let mut builder = crate::jit::lower::builder::NoopBuilder::new();
if let Err(e) = lower.lower_function(mir, &mut builder) {
eprintln!("[JIT] lower failed for {}: {}", func_name, e);
return None;
}
// Strict: fail compile if any fallback decisions were taken during lowering
let lower_fallbacks = crate::jit::events::lower_fallbacks_get();
if lower_fallbacks > 0 && std::env::var("NYASH_JIT_STRICT").ok().as_deref() == Some("1") {
eprintln!(
"[JIT][strict] lower produced fallback decisions for {}: {} — failing compile",
func_name, lower_fallbacks
);
return None;
}
// Capture per-function lower stats for manager to query later
let (phi_t, phi_b1, ret_b) = lower.last_stats();
self.last_phi_total = phi_t;
self.last_phi_b1 = phi_b1;
self.last_ret_bool_hint = ret_b;
// Record per-function stats into manager via callback if available (handled by caller)
let cfg_now = crate::jit::config::current();
// Strict mode: any unsupported lowering must fail-fast
if lower.unsupported > 0 && std::env::var("NYASH_JIT_STRICT").ok().as_deref() == Some("1") {
eprintln!(
"[JIT][strict] unsupported lowering ops for {}: {} — failing compile",
func_name, lower.unsupported
);
return None;
}
if cfg_now.dump {
let phi_min = cfg_now.phi_min;
let native_f64 = cfg_now.native_f64;
let native_bool = cfg_now.native_bool;
#[cfg(feature = "cranelift-jit")]
{
let s = builder.stats;
eprintln!("[JIT] lower {}: argc={} phi_min={} f64={} bool={} covered={} unsupported={} (consts={}, binops={}, cmps={}, branches={}, rets={})",
func_name, mir.params.len(), phi_min, native_f64, native_bool,
lower.covered, lower.unsupported,
s.0, s.1, s.2, s.3, s.4);
}
#[cfg(not(feature = "cranelift-jit"))]
{
eprintln!("[JIT] lower {}: argc={} phi_min={} f64={} bool={} covered={} unsupported={} (consts={}, binops={}, cmps={}, branches={}, rets={})",
func_name, mir.params.len(), phi_min, native_f64, native_bool,
lower.covered, lower.unsupported,
builder.consts, builder.binops, builder.cmps, builder.branches, builder.rets);
}
// Optional DOT export
if let Ok(path) = std::env::var("NYASH_JIT_DOT") {
if !path.is_empty() {
if let Err(e) = crate::jit::lower::core::dump_cfg_dot(mir, &path, phi_min) {
eprintln!("[JIT] DOT export failed: {}", e);
} else {
eprintln!("[JIT] DOT written to {}", path);
}
}
}
}
// If lowering left any unsupported instructions, do not register a closure.
// This preserves VM semantics until coverage is complete for the function.
if lower.unsupported > 0
&& std::env::var("NYASH_AOT_ALLOW_UNSUPPORTED").ok().as_deref() != Some("1")
{
if std::env::var("NYASH_JIT_STATS").ok().as_deref() == Some("1") || cfg_now.dump {
eprintln!(
"[JIT] skip compile for {}: unsupported={} (>0)",
func_name, lower.unsupported
);
}
return None;
}
// Create a handle and register an executable closure if available
#[cfg(feature = "cranelift-jit")]
{
let h = self.next_handle;
self.next_handle = self.next_handle.saturating_add(1);
if let Some(closure) = builder.take_compiled_closure() {
self.fntab.insert(h, closure);
if std::env::var("NYASH_JIT_STATS").ok().as_deref() == Some("1") {
let dt = t0.elapsed();
eprintln!("[JIT] compile_time_ms={} for {}", dt.as_millis(), func_name);
}
// Optional: also emit an object file for AOT if requested via env
if let Ok(path) = std::env::var("NYASH_AOT_OBJECT_OUT") {
if !path.is_empty() {
let mut lower2 = crate::jit::lower::core::LowerCore::new();
let mut objb = crate::jit::lower::builder::ObjectBuilder::new();
if let Err(e) = lower2.lower_function(mir, &mut objb) {
eprintln!("[AOT] lower failed for {}: {}", func_name, e);
} else if let Some(bytes) = objb.take_object_bytes() {
use std::path::Path;
let p = Path::new(&path);
let out_path = if p.is_dir() || path.ends_with('/') {
p.join(format!("{}.o", func_name))
} else {
p.to_path_buf()
};
if let Some(parent) = out_path.parent() {
let _ = std::fs::create_dir_all(parent);
}
match std::fs::write(&out_path, bytes) {
Ok(_) => {
eprintln!("[AOT] wrote object: {}", out_path.display());
}
Err(e) => {
eprintln!(
"[AOT] failed to write object {}: {}",
out_path.display(),
e
);
}
}
}
}
}
return Some(h);
}
// If Cranelift path did not produce a closure, treat as not compiled
// Even if a closure was not produced, attempt AOT object emission when requested
if let Ok(path) = std::env::var("NYASH_AOT_OBJECT_OUT") {
if !path.is_empty() {
let mut lower2 = crate::jit::lower::core::LowerCore::new();
let mut objb = crate::jit::lower::builder::ObjectBuilder::new();
match lower2.lower_function(mir, &mut objb) {
Err(e) => eprintln!("[AOT] lower failed for {}: {}", func_name, e),
Ok(()) => {
if let Some(bytes) = objb.take_object_bytes() {
use std::path::Path;
let p = Path::new(&path);
let out_path = if p.is_dir() || path.ends_with('/') {
p.join(format!("{}.o", func_name))
} else {
p.to_path_buf()
};
if let Some(parent) = out_path.parent() {
let _ = std::fs::create_dir_all(parent);
}
match std::fs::write(&out_path, bytes) {
Ok(_) => {
eprintln!("[AOT] wrote object: {}", out_path.display())
}
Err(e) => eprintln!(
"[AOT] failed to write object {}: {}",
out_path.display(),
e
),
}
} else {
eprintln!("[AOT] no object bytes available for {}", func_name);
}
}
}
}
}
return None;
}
#[cfg(not(feature = "cranelift-jit"))]
{
// Without Cranelift, do not register a stub that alters program semantics.
// Report as not compiled so VM path remains authoritative.
if std::env::var("NYASH_JIT_STATS").ok().as_deref() == Some("1") {
let dt = t0.elapsed();
eprintln!(
"[JIT] compile skipped (no cranelift) for {} after {}ms",
func_name,
dt.as_millis()
);
}
return None;
}
}
/// Get statistics from the last lowered function
pub fn last_lower_stats(&self) -> (u64, u64, bool) {
(
self.last_phi_total,
self.last_phi_b1,
self.last_ret_bool_hint,
)
}
/// Execute compiled function by handle with trap fallback.
/// Returns Some(VMValue) if executed successfully; None on missing handle or trap (panic).
pub fn execute_handle(
&self,
handle: u64,
args: &[crate::jit::abi::JitValue],
) -> Option<crate::jit::abi::JitValue> {
let f = match self.fntab.get(&handle) {
Some(f) => f,
None => return None,
};
let res = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| (f)(args)));
match res {
Ok(v) => Some(v),
Err(_) => {
if std::env::var("NYASH_JIT_STATS").ok().as_deref() == Some("1")
|| std::env::var("NYASH_JIT_TRAP_LOG").ok().as_deref() == Some("1")
{
eprintln!(
"[JIT] trap: panic during handle={} execution — falling back to VM",
handle
);
}
None
}
}
}
/// Register built-in externs (collections)
fn register_default_externs(&mut self) {
use crate::jit::r#extern::collections as c;
use crate::jit::r#extern::host_bridge as hb;
self.register_extern(c::SYM_ARRAY_LEN, Arc::new(|args| c::array_len(args)));
self.register_extern(c::SYM_ARRAY_GET, Arc::new(|args| c::array_get(args)));
self.register_extern(c::SYM_ARRAY_SET, Arc::new(|args| c::array_set(args)));
self.register_extern(c::SYM_ARRAY_PUSH, Arc::new(|args| c::array_push(args)));
self.register_extern(c::SYM_MAP_GET, Arc::new(|args| c::map_get(args)));
self.register_extern(c::SYM_MAP_SET, Arc::new(|args| c::map_set(args)));
self.register_extern(c::SYM_MAP_SIZE, Arc::new(|args| c::map_size(args)));
// Host-bridge variants (by-slot via C symbol). Guarded by env opt-in for now.
if std::env::var("NYASH_JIT_HOST_BRIDGE").ok().as_deref() == Some("1") {
self.register_extern(hb::SYM_HOST_ARRAY_LEN, Arc::new(|args| hb::array_len(args)));
self.register_extern(hb::SYM_HOST_ARRAY_GET, Arc::new(|args| hb::array_get(args)));
self.register_extern(hb::SYM_HOST_ARRAY_SET, Arc::new(|args| hb::array_set(args)));
self.register_extern(hb::SYM_HOST_MAP_SIZE, Arc::new(|args| hb::map_size(args)));
self.register_extern(hb::SYM_HOST_MAP_GET, Arc::new(|args| hb::map_get(args)));
self.register_extern(hb::SYM_HOST_MAP_SET, Arc::new(|args| hb::map_set(args)));
self.register_extern(hb::SYM_HOST_MAP_HAS, Arc::new(|args| hb::map_has(args)));
self.register_extern(
hb::SYM_HOST_CONSOLE_LOG,
Arc::new(|args| hb::console_log(args)),
);
self.register_extern(
hb::SYM_HOST_CONSOLE_WARN,
Arc::new(|args| hb::console_warn(args)),
);
self.register_extern(
hb::SYM_HOST_CONSOLE_ERROR,
Arc::new(|args| hb::console_error(args)),
);
self.register_extern(
hb::SYM_HOST_INSTANCE_GETFIELD,
Arc::new(|args| hb::instance_getfield(args)),
);
self.register_extern(
hb::SYM_HOST_INSTANCE_SETFIELD,
Arc::new(|args| hb::instance_setfield(args)),
);
self.register_extern(
hb::SYM_HOST_STRING_LEN,
Arc::new(|args| hb::string_len(args)),
);
}
}
pub fn register_extern(
&mut self,
name: &str,
f: Arc<dyn Fn(&[crate::backend::vm::VMValue]) -> crate::backend::vm::VMValue + Send + Sync>,
) {
self.externs.insert(name.to_string(), f);
}
/// Lookup an extern symbol (to be used by the lowering once call emission is added)
pub fn lookup_extern(
&self,
name: &str,
) -> Option<
Arc<dyn Fn(&[crate::backend::vm::VMValue]) -> crate::backend::vm::VMValue + Send + Sync>,
> {
self.externs.get(name).cloned()
}
}

View File

@ -0,0 +1,138 @@
//! JIT Events (v0): minimal JSONL appender for compile/execute/fallback/trap
//!
//! Emission is opt-in via env:
//! - NYASH_JIT_EVENTS=1 prints to stdout (one JSON per line)
//! - NYASH_JIT_EVENTS_PATH=/path/to/file.jsonl appends to file
use serde::Serialize;
use std::sync::atomic::{AtomicU64, Ordering};
// Compile-phase counters (process-local)
static LOWER_FALLBACK_COUNT: AtomicU64 = AtomicU64::new(0);
/// Reset compile-phase counters (call at the beginning of each lower/compile)
pub fn lower_counters_reset() {
LOWER_FALLBACK_COUNT.store(0, Ordering::Relaxed);
}
/// Get number of fallback decisions observed during lowering
pub fn lower_fallbacks_get() -> u64 {
LOWER_FALLBACK_COUNT.load(Ordering::Relaxed)
}
fn record_lower_decision(extra: &serde_json::Value) {
// We record even when emission is disabled, to allow strict-mode checks.
if let serde_json::Value::Object(map) = extra {
if let Some(serde_json::Value::String(dec)) = map.get("decision") {
if dec == "fallback" {
LOWER_FALLBACK_COUNT.fetch_add(1, Ordering::Relaxed);
}
}
}
}
fn base_emit_enabled() -> bool {
std::env::var("NYASH_JIT_EVENTS").ok().as_deref() == Some("1")
|| std::env::var("NYASH_JIT_EVENTS_PATH").is_ok()
}
fn should_emit_lower() -> bool {
// Unify observability: if base events are on (stdout/file) or explicit compile flag, emit.
base_emit_enabled() || std::env::var("NYASH_JIT_EVENTS_COMPILE").ok().as_deref() == Some("1")
}
fn should_emit_runtime() -> bool {
base_emit_enabled() || std::env::var("NYASH_JIT_EVENTS_RUNTIME").ok().as_deref() == Some("1")
}
fn write_line(s: &str) {
if let Ok(path) = std::env::var("NYASH_JIT_EVENTS_PATH") {
let _ = std::fs::OpenOptions::new()
.create(true)
.append(true)
.open(path)
.and_then(|mut f| {
use std::io::Write;
writeln!(f, "{}", s)
});
} else {
println!("{}", s);
}
}
#[derive(Serialize)]
struct Event<'a, T: Serialize> {
kind: &'a str,
function: &'a str,
#[serde(skip_serializing_if = "Option::is_none")]
handle: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
ms: Option<u128>,
#[serde(flatten)]
extra: T,
}
pub fn emit<T: Serialize>(
kind: &str,
function: &str,
handle: Option<u64>,
ms: Option<u128>,
extra: T,
) {
if !base_emit_enabled() {
return;
}
let ev = Event {
kind,
function,
handle,
ms,
extra,
};
if let Ok(s) = serde_json::to_string(&ev) {
write_line(&s);
}
}
fn emit_any(
kind: &str,
function: &str,
handle: Option<u64>,
ms: Option<u128>,
extra: serde_json::Value,
) {
let ev = Event {
kind,
function,
handle,
ms,
extra,
};
if let Ok(s) = serde_json::to_string(&ev) {
write_line(&s);
}
}
/// Emit an event during lowering (compile-time planning). Adds phase="lower".
pub fn emit_lower(mut extra: serde_json::Value, kind: &str, function: &str) {
// Always record decisions for strict-mode enforcement
record_lower_decision(&extra);
if !should_emit_lower() {
return;
}
if let serde_json::Value::Object(ref mut map) = extra {
map.insert("phase".into(), serde_json::Value::String("lower".into()));
}
emit_any(kind, function, None, None, extra);
}
/// Emit an event during runtime execution. Adds phase="execute".
pub fn emit_runtime(mut extra: serde_json::Value, kind: &str, function: &str) {
if !should_emit_runtime() {
return;
}
if let serde_json::Value::Object(ref mut map) = extra {
map.insert("phase".into(), serde_json::Value::String("execute".into()));
}
emit_any(kind, function, None, None, extra);
}

View File

@ -0,0 +1,66 @@
//! Async/Future-related JIT extern symbols
#[allow(unused_imports)]
use crate::{
backend::vm::VMValue,
box_trait::{BoolBox, IntegerBox, NyashBox, StringBox},
};
/// Symbol name for awaiting a FutureBox and returning a value/handle (i64)
pub const SYM_FUTURE_AWAIT_H: &str = "nyash.future.await_h";
pub const SYM_FUTURE_SPAWN_INSTANCE3_I64: &str = "nyash.future.spawn_instance3_i64";
#[cfg(feature = "cranelift-jit")]
pub extern "C" fn nyash_future_await_h(arg0: i64) -> i64 {
use crate::jit::rt::handles;
// Resolve FutureBox from handle or legacy VM args
let mut fut_opt: Option<crate::boxes::future::FutureBox> = None;
if arg0 > 0 {
if let Some(obj) = handles::get(arg0 as u64) {
if let Some(fb) = obj
.as_any()
.downcast_ref::<crate::boxes::future::FutureBox>()
{
fut_opt = Some(fb.clone());
}
}
}
#[cfg(not(feature = "jit-direct-only"))]
if fut_opt.is_none() {
crate::jit::rt::with_legacy_vm_args(|args| {
let pick = if arg0 >= 0 {
(arg0 as usize)..(arg0 as usize + 1)
} else {
0..args.len()
};
for i in pick {
if let Some(VMValue::BoxRef(b)) = args.get(i) {
if let Some(fb) = b.as_any().downcast_ref::<crate::boxes::future::FutureBox>() {
fut_opt = Some(fb.clone());
break;
}
}
}
});
}
let Some(fut) = fut_opt else {
return 0;
};
// Cooperative wait with scheduler polling and timeout
let max_ms: u64 = crate::config::env::await_max_ms();
let start = std::time::Instant::now();
while !fut.ready() {
crate::runtime::global_hooks::safepoint_and_poll();
std::thread::yield_now();
if start.elapsed() >= std::time::Duration::from_millis(max_ms) {
// Timeout: return 0 (caller may handle as failure)
return 0;
}
}
// Get NyashBox result and always return a handle
let out_box: Box<dyn NyashBox> = fut.get();
let arc: std::sync::Arc<dyn NyashBox> = std::sync::Arc::from(out_box);
let h = handles::to_handle(arc);
h as i64
}

View File

@ -0,0 +1,5 @@
//! Generic birth hostcall symbol names
pub const SYM_BOX_BIRTH_H: &str = "nyash.box.birth_h";
/// Birth by type name encoded in two u64 words (lo,hi,len)
pub const SYM_INSTANCE_BIRTH_NAME_U64X2: &str = "nyash.instance.birth_name_u64x2";

View File

@ -0,0 +1,182 @@
use std::sync::Arc;
use crate::backend::vm::VMValue;
use crate::box_trait::{IntegerBox, NyashBox, StringBox};
/// Symbol names for host externs (stable ABI for JIT)
pub const SYM_ARRAY_LEN: &str = "nyash.array.len";
pub const SYM_ARRAY_GET: &str = "nyash.array.get";
pub const SYM_ARRAY_SET: &str = "nyash.array.set";
pub const SYM_ARRAY_PUSH: &str = "nyash.array.push";
pub const SYM_MAP_GET: &str = "nyash.map.get";
pub const SYM_MAP_SET: &str = "nyash.map.set";
pub const SYM_MAP_SIZE: &str = "nyash.map.size";
// Handle-based variants for direct JIT bridging
pub const SYM_ARRAY_LEN_H: &str = "nyash.array.len_h";
pub const SYM_ARRAY_GET_H: &str = "nyash.array.get_h";
pub const SYM_ARRAY_SET_H: &str = "nyash.array.set_h";
pub const SYM_ARRAY_SET_HH: &str = "nyash.array.set_hh";
pub const SYM_ARRAY_PUSH_H: &str = "nyash.array.push_h";
pub const SYM_ARRAY_LAST_H: &str = "nyash.array.last_h";
pub const SYM_MAP_SIZE_H: &str = "nyash.map.size_h";
pub const SYM_MAP_GET_H: &str = "nyash.map.get_h";
pub const SYM_MAP_GET_HH: &str = "nyash.map.get_hh";
pub const SYM_MAP_SET_H: &str = "nyash.map.set_h";
pub const SYM_MAP_HAS_H: &str = "nyash.map.has_h";
// Generic read-only helper
pub const SYM_ANY_LEN_H: &str = "nyash.any.length_h";
pub const SYM_ANY_IS_EMPTY_H: &str = "nyash.any.is_empty_h";
pub const SYM_STRING_CHARCODE_AT_H: &str = "nyash.string.charCodeAt_h";
pub const SYM_STRING_LEN_H: &str = "nyash.string.len_h";
pub const SYM_STRING_BIRTH_H: &str = "nyash.string.birth_h";
pub const SYM_STRING_FROM_U64X2: &str = "nyash.string.from_u64x2";
pub const SYM_INTEGER_BIRTH_H: &str = "nyash.integer.birth_h";
pub const SYM_CONSOLE_BIRTH_H: &str = "nyash.console.birth_h";
// String-like operations (handle, handle)
pub const SYM_STRING_CONCAT_HH: &str = "nyash.string.concat_hh";
pub const SYM_STRING_EQ_HH: &str = "nyash.string.eq_hh";
pub const SYM_STRING_LT_HH: &str = "nyash.string.lt_hh";
// Unified semantics: addition for dynamic boxes (handle,handle)
pub const SYM_SEMANTICS_ADD_HH: &str = "nyash.semantics.add_hh";
fn as_array(args: &[VMValue]) -> Option<&crate::boxes::array::ArrayBox> {
match args.get(0) {
Some(VMValue::BoxRef(b)) => b.as_any().downcast_ref::<crate::boxes::array::ArrayBox>(),
_ => None,
}
}
fn as_map(args: &[VMValue]) -> Option<&crate::boxes::map_box::MapBox> {
match args.get(0) {
Some(VMValue::BoxRef(b)) => b.as_any().downcast_ref::<crate::boxes::map_box::MapBox>(),
_ => None,
}
}
pub fn array_len(args: &[VMValue]) -> VMValue {
if let Some(arr) = as_array(args) {
if let Some(len_box) = arr.length().as_any().downcast_ref::<IntegerBox>() {
return VMValue::Integer(len_box.value);
}
}
VMValue::Integer(0)
}
pub fn array_get(args: &[VMValue]) -> VMValue {
if let (Some(arr), Some(VMValue::Integer(idx))) = (as_array(args), args.get(1)) {
// ArrayBox.get expects a NyashBox index
let val = arr.get(Box::new(IntegerBox::new(*idx)));
return VMValue::from_nyash_box(val);
}
VMValue::Void
}
pub fn array_set(args: &[VMValue]) -> VMValue {
// Enforce policy for mutating operation
if crate::jit::policy::current().read_only
&& !crate::jit::policy::current()
.hostcall_whitelist
.iter()
.any(|s| s == SYM_ARRAY_SET)
{
crate::jit::events::emit_runtime(
serde_json::json!({"id": SYM_ARRAY_SET, "decision":"fallback", "reason":"policy_denied_mutating"}),
"hostcall",
"<jit>",
);
return VMValue::Integer(0);
}
if let (Some(arr), Some(VMValue::Integer(idx)), Some(value)) =
(as_array(args), args.get(1), args.get(2))
{
let val_box: Box<dyn NyashBox> = value.to_nyash_box();
let res = arr.set(Box::new(IntegerBox::new(*idx)), val_box);
crate::jit::events::emit_runtime(
serde_json::json!({"id": SYM_ARRAY_SET, "decision":"allow", "argc":3, "arg_types":["Handle","I64","Handle"]}),
"hostcall",
"<jit>",
);
return VMValue::from_nyash_box(res);
}
VMValue::BoxRef(Arc::new(StringBox::new(
"Error: array.set expects (ArrayBox, i64, value)",
)))
}
pub fn array_push(args: &[VMValue]) -> VMValue {
if crate::jit::policy::current().read_only
&& !crate::jit::policy::current()
.hostcall_whitelist
.iter()
.any(|s| s == SYM_ARRAY_PUSH)
{
crate::jit::events::emit_runtime(
serde_json::json!({"id": SYM_ARRAY_PUSH, "decision":"fallback", "reason":"policy_denied_mutating"}),
"hostcall",
"<jit>",
);
return VMValue::Integer(0);
}
if let (Some(arr), Some(value)) = (as_array(args), args.get(1)) {
let val_box: Box<dyn NyashBox> = value.to_nyash_box();
let res = arr.push(val_box);
crate::jit::events::emit_runtime(
serde_json::json!({"id": SYM_ARRAY_PUSH, "decision":"allow", "argc":2, "arg_types":["Handle","Handle"]}),
"hostcall",
"<jit>",
);
return VMValue::from_nyash_box(res);
}
VMValue::BoxRef(Arc::new(StringBox::new(
"Error: array.push expects (ArrayBox, value)",
)))
}
pub fn map_get(args: &[VMValue]) -> VMValue {
if let (Some(map), Some(key)) = (as_map(args), args.get(1)) {
let key_box: Box<dyn NyashBox> = key.to_nyash_box();
return VMValue::from_nyash_box(map.get(key_box));
}
VMValue::Void
}
pub fn map_set(args: &[VMValue]) -> VMValue {
if crate::jit::policy::current().read_only
&& !crate::jit::policy::current()
.hostcall_whitelist
.iter()
.any(|s| s == SYM_MAP_SET)
{
crate::jit::events::emit_runtime(
serde_json::json!({"id": SYM_MAP_SET, "decision":"fallback", "reason":"policy_denied_mutating"}),
"hostcall",
"<jit>",
);
return VMValue::Integer(0);
}
if let (Some(map), Some(key), Some(value)) = (as_map(args), args.get(1), args.get(2)) {
let key_box: Box<dyn NyashBox> = key.to_nyash_box();
let val_box: Box<dyn NyashBox> = value.to_nyash_box();
let out = map.set(key_box, val_box);
crate::jit::events::emit_runtime(
serde_json::json!({"id": SYM_MAP_SET, "decision":"allow", "argc":3, "arg_types":["Handle","Handle","Handle"]}),
"hostcall",
"<jit>",
);
return VMValue::from_nyash_box(out);
}
VMValue::BoxRef(Arc::new(StringBox::new(
"Error: map.set expects (MapBox, key, value)",
)))
}
pub fn map_size(args: &[VMValue]) -> VMValue {
if let Some(map) = as_map(args) {
if let Some(sz) = map.size().as_any().downcast_ref::<IntegerBox>() {
return VMValue::Integer(sz.value);
}
}
VMValue::Integer(0)
}

View File

@ -0,0 +1,3 @@
//! Handle-related extern symbol names
pub const SYM_HANDLE_OF: &str = "nyash.handle.of";

View File

@ -0,0 +1,222 @@
#![allow(unused_unsafe)]
//! JIT externs bridging to NyRT host API (C symbols) via by-slot encoding.
//!
//! 目的: VM/JIT一致のため、JITからも host_api::nyrt_host_call_slot を使うPoC。
use crate::backend::vm::VMValue;
fn tlv_encode_values(args: &[VMValue]) -> Vec<u8> {
use crate::runtime::plugin_ffi_common::encode as enc;
let mut buf = crate::runtime::plugin_ffi_common::encode_tlv_header(args.len() as u16);
for a in args {
match a {
VMValue::Integer(i) => enc::i64(&mut buf, *i),
VMValue::Float(f) => enc::f64(&mut buf, *f),
VMValue::Bool(b) => enc::bool(&mut buf, *b),
VMValue::String(s) => enc::string(&mut buf, s),
VMValue::BoxRef(arc) => {
// Try to downcast common primitives for stable TLV
if let Some(sb) = arc.as_any().downcast_ref::<crate::box_trait::StringBox>() {
enc::string(&mut buf, &sb.value);
} else if let Some(ib) = arc.as_any().downcast_ref::<crate::box_trait::IntegerBox>()
{
enc::i64(&mut buf, ib.value);
} else if let Some(bb) = arc.as_any().downcast_ref::<crate::box_trait::BoolBox>() {
enc::bool(&mut buf, bb.value);
} else if let Some(fb) = arc
.as_any()
.downcast_ref::<crate::boxes::math_box::FloatBox>()
{
enc::f64(&mut buf, fb.value);
} else {
// Fallback: send HostHandle so host can operate on it if needed
let h = crate::runtime::host_handles::to_handle_arc(arc.clone());
enc::host_handle(&mut buf, h);
}
}
VMValue::Future(fu) => {
let bx: Box<dyn crate::box_trait::NyashBox> = Box::new(fu.clone());
let arc: std::sync::Arc<dyn crate::box_trait::NyashBox> = std::sync::Arc::from(bx);
let h = crate::runtime::host_handles::to_handle_arc(arc);
enc::host_handle(&mut buf, h);
}
VMValue::Void => enc::string(&mut buf, "void"),
}
}
buf
}
fn call_slot(handle: u64, slot: u64, argv: &[VMValue]) -> VMValue {
let tlv = tlv_encode_values(argv);
let mut out = vec![0u8; 256];
let mut out_len: usize = out.len();
let code = unsafe {
crate::runtime::host_api::nyrt_host_call_slot(
handle,
slot,
tlv.as_ptr(),
tlv.len(),
out.as_mut_ptr(),
&mut out_len,
)
};
if code != 0 {
return VMValue::Void;
}
if let Some((tag, _sz, payload)) =
crate::runtime::plugin_ffi_common::decode::tlv_first(&out[..out_len])
{
match tag {
6 | 7 => {
let s = crate::runtime::plugin_ffi_common::decode::string(payload);
let sb = crate::box_trait::StringBox::new(&s);
let arc: std::sync::Arc<dyn crate::box_trait::NyashBox> = std::sync::Arc::new(sb);
VMValue::BoxRef(arc)
}
1 => crate::runtime::plugin_ffi_common::decode::bool(payload)
.map(VMValue::Bool)
.unwrap_or(VMValue::Void),
2 => crate::runtime::plugin_ffi_common::decode::i32(payload)
.map(|v| VMValue::Integer(v as i64))
.unwrap_or(VMValue::Void),
3 => crate::runtime::plugin_ffi_common::decode::u64(payload)
.map(|v| VMValue::Integer(v as i64))
.unwrap_or(VMValue::Void),
5 => crate::runtime::plugin_ffi_common::decode::f64(payload)
.map(VMValue::Float)
.unwrap_or(VMValue::Void),
9 => {
if let Some(h) = crate::runtime::plugin_ffi_common::decode::u64(payload) {
if let Some(arc) = crate::runtime::host_handles::get(h) {
return VMValue::BoxRef(arc);
}
}
VMValue::Void
}
_ => VMValue::Void,
}
} else {
VMValue::Void
}
}
fn to_handle(recv: &VMValue) -> Option<u64> {
match recv {
VMValue::BoxRef(arc) => Some(crate::runtime::host_handles::to_handle_arc(arc.clone())),
_ => None,
}
}
// Public bridge helpers (symbol strings align with collections for PoC)
pub const SYM_HOST_ARRAY_GET: &str = "nyash.host.array.get"; // (ArrayBox, i64)
pub const SYM_HOST_ARRAY_SET: &str = "nyash.host.array.set"; // (ArrayBox, i64, val)
pub const SYM_HOST_ARRAY_LEN: &str = "nyash.host.array.len"; // (ArrayBox)
pub const SYM_HOST_MAP_GET: &str = "nyash.host.map.get"; // (MapBox, key)
pub const SYM_HOST_MAP_SET: &str = "nyash.host.map.set"; // (MapBox, key, val)
pub const SYM_HOST_MAP_SIZE: &str = "nyash.host.map.size"; // (MapBox)
pub const SYM_HOST_MAP_HAS: &str = "nyash.host.map.has"; // (MapBox, key)
pub const SYM_HOST_CONSOLE_LOG: &str = "nyash.host.console.log"; // (value)
pub const SYM_HOST_CONSOLE_WARN: &str = "nyash.host.console.warn"; // (value)
pub const SYM_HOST_CONSOLE_ERROR: &str = "nyash.host.console.error"; // (value)
pub const SYM_HOST_INSTANCE_GETFIELD: &str = "nyash.host.instance.getField"; // (InstanceBox, name)
pub const SYM_HOST_INSTANCE_SETFIELD: &str = "nyash.host.instance.setField"; // (InstanceBox, name, value)
// Arity-stable variants for Cranelift imports (avoid signature conflicts)
pub const SYM_HOST_INSTANCE_GETFIELD2: &str = "nyash.host.instance.getField2"; // (InstanceBox, name)
pub const SYM_HOST_INSTANCE_SETFIELD3: &str = "nyash.host.instance.setField3"; // (InstanceBox, name, value)
pub const SYM_HOST_INSTANCE_FIELD3: &str = "nyash.host.instance.field3"; // (recv,name,val or sentinel)
pub const SYM_HOST_STRING_LEN: &str = "nyash.host.string.len"; // (StringBox)
pub fn array_get(args: &[VMValue]) -> VMValue {
if let Some(h) = to_handle(args.get(0).unwrap_or(&VMValue::Void)) {
call_slot(h, 100, &args[1..])
} else {
VMValue::Void
}
}
pub fn array_set(args: &[VMValue]) -> VMValue {
if let Some(h) = to_handle(args.get(0).unwrap_or(&VMValue::Void)) {
call_slot(h, 101, &args[1..])
} else {
VMValue::Void
}
}
pub fn array_len(args: &[VMValue]) -> VMValue {
if let Some(h) = to_handle(args.get(0).unwrap_or(&VMValue::Void)) {
call_slot(h, 102, &[])
} else {
VMValue::Integer(0)
}
}
pub fn map_get(args: &[VMValue]) -> VMValue {
if let Some(h) = to_handle(args.get(0).unwrap_or(&VMValue::Void)) {
call_slot(h, 203, &args[1..])
} else {
VMValue::Void
}
}
pub fn map_set(args: &[VMValue]) -> VMValue {
if let Some(h) = to_handle(args.get(0).unwrap_or(&VMValue::Void)) {
call_slot(h, 204, &args[1..])
} else {
VMValue::Void
}
}
pub fn map_size(args: &[VMValue]) -> VMValue {
if let Some(h) = to_handle(args.get(0).unwrap_or(&VMValue::Void)) {
call_slot(h, 200, &[])
} else {
VMValue::Integer(0)
}
}
pub fn map_has(args: &[VMValue]) -> VMValue {
if let Some(h) = to_handle(args.get(0).unwrap_or(&VMValue::Void)) {
call_slot(h, 202, &args[1..])
} else {
VMValue::Bool(false)
}
}
pub fn console_log(args: &[VMValue]) -> VMValue {
// JIT host-bridge簡易版: 最初の引数を文字列化してstdoutへ
if let Some(a0) = args.get(0) {
println!("{}", a0.to_string());
}
VMValue::Void
}
pub fn console_warn(args: &[VMValue]) -> VMValue {
if let Some(a0) = args.get(0) {
eprintln!("[warn] {}", a0.to_string());
}
VMValue::Void
}
pub fn console_error(args: &[VMValue]) -> VMValue {
if let Some(a0) = args.get(0) {
eprintln!("[error] {}", a0.to_string());
}
VMValue::Void
}
pub fn instance_getfield(args: &[VMValue]) -> VMValue {
if let Some(h) = to_handle(args.get(0).unwrap_or(&VMValue::Void)) {
call_slot(h, 1, &args[1..])
} else {
VMValue::Void
}
}
pub fn instance_setfield(args: &[VMValue]) -> VMValue {
if let Some(h) = to_handle(args.get(0).unwrap_or(&VMValue::Void)) {
call_slot(h, 2, &args[1..])
} else {
VMValue::Void
}
}
pub fn string_len(args: &[VMValue]) -> VMValue {
if let Some(h) = to_handle(args.get(0).unwrap_or(&VMValue::Void)) {
call_slot(h, 300, &[])
} else {
VMValue::Integer(0)
}
}

View File

@ -0,0 +1,13 @@
//! Host-callable externs for JIT-compiled code
//!
//! Phase 10_d: Provide thin bridges for Array/Map hot operations that
//! JIT can call via symbol names. Lowering will resolve MIR ops into
//! these externs once call emission is added.
pub mod r#async;
pub mod birth;
pub mod collections;
pub mod handles;
pub mod host_bridge;
pub mod result;
pub mod runtime;

View File

@ -0,0 +1,44 @@
//! Result-related JIT extern symbols
#[cfg(feature = "cranelift-jit")]
use crate::box_trait::NyashBox;
/// Symbol name for wrapping a handle into Result.Ok(handle)
pub const SYM_RESULT_OK_H: &str = "nyash.result.ok_h";
/// Symbol name for wrapping a handle into Result.Err(handle)
pub const SYM_RESULT_ERR_H: &str = "nyash.result.err_h";
#[cfg(feature = "cranelift-jit")]
pub extern "C" fn nyash_result_ok_h(handle: i64) -> i64 {
use crate::boxes::result::NyashResultBox;
use crate::jit::rt::handles;
if handle <= 0 {
return 0;
}
if let Some(obj) = handles::get(handle as u64) {
let boxed = obj.clone_box();
let res = NyashResultBox::new_ok(boxed);
let arc: std::sync::Arc<dyn NyashBox> = std::sync::Arc::new(res);
let h = handles::to_handle(arc);
return h as i64;
}
0
}
#[cfg(feature = "cranelift-jit")]
pub extern "C" fn nyash_result_err_h(handle: i64) -> i64 {
use crate::boxes::result::NyashResultBox;
use crate::jit::rt::handles;
// If handle <= 0, synthesize a Timeout StringBox error for await paths.
let err_box: Box<dyn NyashBox> = if handle <= 0 {
Box::new(crate::box_trait::StringBox::new("Timeout".to_string()))
} else if let Some(obj) = handles::get(handle as u64) {
obj.clone_box()
} else {
Box::new(crate::box_trait::StringBox::new("UnknownError".to_string()))
};
let res = NyashResultBox::new_err(err_box);
let arc: std::sync::Arc<dyn NyashBox> = std::sync::Arc::new(res);
let h = handles::to_handle(arc);
h as i64
}

View File

@ -0,0 +1,7 @@
//! Runtime/GC related hostcall symbol names reserved for JIT/AOT.
/// Runtime safepoint checkpoint (no-op stub for now)
pub const SYM_RT_CHECKPOINT: &str = "nyash.rt.checkpoint";
/// Write barrier hint for GC (no-op stub for now)
pub const SYM_GC_BARRIER_WRITE: &str = "nyash.gc.barrier_write";

View File

@ -0,0 +1,357 @@
//! Minimal hostcall registry (v0): classify symbols as read-only or mutating
use once_cell::sync::OnceCell;
use std::collections::HashMap;
use std::collections::HashSet;
use std::sync::RwLock;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum HostcallKind {
ReadOnly,
Mutating,
}
#[derive(Debug, Default)]
struct Registry {
ro: HashSet<String>,
mu: HashSet<String>,
// Allow multiple signatures per symbol (overloads)
sig: HashMap<String, Vec<Signature>>,
}
static REG: OnceCell<RwLock<Registry>> = OnceCell::new();
fn ensure_default() {
if REG.get().is_some() {
return;
}
let mut r = Registry::default();
// Read-only defaults
for s in [
"nyash.array.len_h",
"nyash.string.len_h",
"nyash.any.length_h",
"nyash.any.is_empty_h",
"nyash.map.size_h",
"nyash.map.get_h",
"nyash.map.has_h",
"nyash.string.charCodeAt_h",
"nyash.string.concat_hh",
"nyash.string.eq_hh",
"nyash.string.lt_hh",
"nyash.array.get_h",
] {
r.ro.insert(s.to_string());
}
// Mutating defaults
for s in ["nyash.array.push_h", "nyash.array.set_h", "nyash.map.set_h"] {
r.mu.insert(s.to_string());
}
// Signatures (v0): register known symbols with simple arg/ret kinds
// math.* thin bridge: f64 signatures only (allow when args match exactly)
r.sig
.entry("nyash.math.sin".to_string())
.or_default()
.push(Signature {
args: vec![ArgKind::F64],
ret: ArgKind::F64,
});
r.sig
.entry("nyash.math.cos".to_string())
.or_default()
.push(Signature {
args: vec![ArgKind::F64],
ret: ArgKind::F64,
});
r.sig
.entry("nyash.math.abs".to_string())
.or_default()
.push(Signature {
args: vec![ArgKind::F64],
ret: ArgKind::F64,
});
r.sig
.entry("nyash.math.min".to_string())
.or_default()
.push(Signature {
args: vec![ArgKind::F64, ArgKind::F64],
ret: ArgKind::F64,
});
r.sig
.entry("nyash.math.max".to_string())
.or_default()
.push(Signature {
args: vec![ArgKind::F64, ArgKind::F64],
ret: ArgKind::F64,
});
// Collections (handle-based)
// Map get: support both integer and handle keys (overload)
r.sig
.entry("nyash.map.get_h".to_string())
.or_default()
.push(Signature {
args: vec![ArgKind::Handle, ArgKind::I64],
ret: ArgKind::Handle,
});
r.sig
.entry("nyash.map.get_h".to_string())
.or_default()
.push(Signature {
args: vec![ArgKind::Handle, ArgKind::Handle],
ret: ArgKind::Handle,
});
r.sig
.entry("nyash.map.size_h".to_string())
.or_default()
.push(Signature {
args: vec![ArgKind::Handle],
ret: ArgKind::I64,
});
r.sig
.entry("nyash.array.get_h".to_string())
.or_default()
.push(Signature {
args: vec![ArgKind::Handle, ArgKind::I64],
ret: ArgKind::Handle,
});
r.sig
.entry("nyash.array.len_h".to_string())
.or_default()
.push(Signature {
args: vec![ArgKind::Handle],
ret: ArgKind::I64,
});
// String helpers
r.sig
.entry("nyash.string.len_h".to_string())
.or_default()
.push(Signature {
args: vec![ArgKind::Handle],
ret: ArgKind::I64,
});
r.sig
.entry("nyash.string.charCodeAt_h".to_string())
.or_default()
.push(Signature {
args: vec![ArgKind::Handle, ArgKind::I64],
ret: ArgKind::I64,
});
r.sig
.entry("nyash.string.concat_hh".to_string())
.or_default()
.push(Signature {
args: vec![ArgKind::Handle, ArgKind::Handle],
ret: ArgKind::Handle,
});
r.sig
.entry("nyash.semantics.add_hh".to_string())
.or_default()
.push(Signature {
args: vec![ArgKind::Handle, ArgKind::Handle],
ret: ArgKind::Handle,
});
r.sig
.entry("nyash.string.eq_hh".to_string())
.or_default()
.push(Signature {
args: vec![ArgKind::Handle, ArgKind::Handle],
ret: ArgKind::I64,
});
r.sig
.entry("nyash.string.lt_hh".to_string())
.or_default()
.push(Signature {
args: vec![ArgKind::Handle, ArgKind::Handle],
ret: ArgKind::I64,
});
// Any helpers (length/is_empty)
r.sig
.entry("nyash.any.length_h".to_string())
.or_default()
.push(Signature {
args: vec![ArgKind::Handle],
ret: ArgKind::I64,
});
r.sig
.entry("nyash.any.is_empty_h".to_string())
.or_default()
.push(Signature {
args: vec![ArgKind::Handle],
ret: ArgKind::I64,
});
// Map.has(handle, i64) -> i64(0/1)
r.sig
.entry("nyash.map.has_h".to_string())
.or_default()
.push(Signature {
args: vec![ArgKind::Handle, ArgKind::I64],
ret: ArgKind::I64,
});
let _ = REG.set(RwLock::new(r));
}
pub fn classify(symbol: &str) -> HostcallKind {
ensure_default();
if let Some(lock) = REG.get() {
if let Ok(g) = lock.read() {
if g.ro.contains(symbol) {
return HostcallKind::ReadOnly;
}
if g.mu.contains(symbol) {
return HostcallKind::Mutating;
}
}
}
// Default to read-only to be permissive in v0
HostcallKind::ReadOnly
}
pub fn add_readonly(symbol: &str) {
ensure_default();
if let Some(lock) = REG.get() {
if let Ok(mut w) = lock.write() {
w.ro.insert(symbol.to_string());
}
}
}
pub fn add_mutating(symbol: &str) {
ensure_default();
if let Some(lock) = REG.get() {
if let Ok(mut w) = lock.write() {
w.mu.insert(symbol.to_string());
}
}
}
pub fn set_from_csv(ro_csv: &str, mu_csv: &str) {
ensure_default();
if let Some(lock) = REG.get() {
if let Ok(mut w) = lock.write() {
w.ro.clear();
w.mu.clear();
for s in ro_csv.split(',') {
let t = s.trim();
if !t.is_empty() {
w.ro.insert(t.to_string());
}
}
for s in mu_csv.split(',') {
let t = s.trim();
if !t.is_empty() {
w.mu.insert(t.to_string());
}
}
}
}
}
pub fn snapshot() -> (Vec<String>, Vec<String>) {
ensure_default();
if let Some(lock) = REG.get() {
if let Ok(g) = lock.read() {
let mut ro: Vec<String> = g.ro.iter().cloned().collect();
ro.sort();
let mut mu: Vec<String> = g.mu.iter().cloned().collect();
mu.sort();
return (ro, mu);
}
}
(Vec::new(), Vec::new())
}
// ==== Signature (v0 scaffolding) ====
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ArgKind {
I64,
F64,
Handle,
}
#[derive(Debug, Clone)]
pub struct Signature {
pub args: Vec<ArgKind>,
pub ret: ArgKind,
}
fn parse_kind(s: &str) -> Option<ArgKind> {
match s.trim().to_ascii_lowercase().as_str() {
"i64" | "int" | "integer" => Some(ArgKind::I64),
"f64" | "float" => Some(ArgKind::F64),
"handle" | "h" => Some(ArgKind::Handle),
_ => None,
}
}
pub fn set_signature_csv(symbol: &str, args_csv: &str, ret_str: &str) -> bool {
ensure_default();
let mut ok = true;
let parsed: Vec<Option<ArgKind>> = args_csv
.split(',')
.filter(|t| !t.trim().is_empty())
.map(|t| parse_kind(t))
.collect();
let mut args: Vec<ArgKind> = Vec::new();
for p in parsed {
if let Some(k) = p {
args.push(k)
} else {
ok = false;
}
}
let ret = match parse_kind(ret_str) {
Some(k) => k,
None => {
ok = false;
ArgKind::I64
}
};
if !ok {
return false;
}
let sig = Signature { args, ret };
if let Some(lock) = REG.get() {
if let Ok(mut w) = lock.write() {
w.sig.entry(symbol.to_string()).or_default().push(sig);
return true;
}
}
false
}
/// Check observed args against a registered signature.
/// - If no signature is registered for the symbol, returns Ok(()) to be permissive in v0.
/// - Returns Err("sig_mismatch") when arg length or kinds differ.
pub fn check_signature(symbol: &str, observed_args: &[ArgKind]) -> Result<(), &'static str> {
ensure_default();
if let Some(lock) = REG.get() {
if let Ok(g) = lock.read() {
if let Some(sigs) = g.sig.get(symbol) {
let cfg_now = crate::jit::config::current();
let relax = cfg_now.relax_numeric || cfg_now.native_f64;
// Match against any one of the overload signatures
'outer: for sig in sigs.iter() {
if sig.args.len() != observed_args.len() {
continue;
}
for (expected, observed) in sig.args.iter().zip(observed_args.iter()) {
if expected == observed {
continue;
}
// v0 coercion: allow I64 → F64 only when relaxed numeric is enabled
if relax
&& matches!(expected, ArgKind::F64)
&& matches!(observed, ArgKind::I64)
{
continue;
}
// Mismatch for this candidate signature
continue 'outer;
}
// All args matched for this signature
return Ok(());
}
// No overload matched
return Err("sig_mismatch");
}
}
}
Ok(())
}

View File

@ -0,0 +1,120 @@
//! IR builder abstraction (thin hub).
//!
//! Lowering targets the `IRBuilder` trait. Concrete backends live in
//! submodules and are enabled via feature flags.
#[derive(Debug, Clone, Copy)]
pub enum BinOpKind {
Add,
Sub,
Mul,
Div,
Mod,
}
#[derive(Debug, Clone, Copy)]
pub enum CmpKind {
Eq,
Ne,
Lt,
Le,
Gt,
Ge,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ParamKind {
I64,
F64,
B1,
}
pub trait IRBuilder {
fn begin_function(&mut self, name: &str);
fn end_function(&mut self);
fn prepare_signature_i64(&mut self, _argc: usize, _has_ret: bool) {}
fn prepare_signature_typed(&mut self, _params: &[ParamKind], _ret_is_f64: bool) {}
fn emit_param_i64(&mut self, _index: usize) {}
fn emit_const_i64(&mut self, _val: i64);
fn emit_const_f64(&mut self, _val: f64);
fn emit_binop(&mut self, _op: BinOpKind);
fn emit_compare(&mut self, _op: CmpKind);
fn emit_jump(&mut self);
fn emit_branch(&mut self);
fn emit_return(&mut self);
fn emit_select_i64(&mut self) {}
fn emit_host_call(&mut self, _symbol: &str, _argc: usize, _has_ret: bool) {}
fn emit_host_call_typed(
&mut self,
_symbol: &str,
_params: &[ParamKind],
_has_ret: bool,
_ret_is_f64: bool,
) {
}
fn emit_host_call_fixed3(&mut self, _symbol: &str, _has_ret: bool) {}
fn emit_plugin_invoke(&mut self, _type_id: u32, _method_id: u32, _argc: usize, _has_ret: bool) {
}
fn emit_plugin_invoke_by_name(&mut self, _method: &str, _argc: usize, _has_ret: bool) {}
// Create a StringBox handle from a string literal and push its handle (i64) onto the stack.
fn emit_string_handle_from_literal(&mut self, _s: &str) {}
fn prepare_blocks(&mut self, _count: usize) {}
fn switch_to_block(&mut self, _index: usize) {}
fn seal_block(&mut self, _index: usize) {}
fn br_if_top_is_true(&mut self, _then_index: usize, _else_index: usize) {}
fn jump_to(&mut self, _target_index: usize) {}
fn ensure_block_params_i64(&mut self, _index: usize, _count: usize) {}
fn ensure_block_params_b1(&mut self, index: usize, count: usize) {
self.ensure_block_params_i64(index, count);
}
fn ensure_block_param_i64(&mut self, index: usize) {
self.ensure_block_params_i64(index, 1);
}
fn push_block_param_i64_at(&mut self, _pos: usize) {}
fn push_block_param_b1_at(&mut self, _pos: usize) {
self.push_block_param_i64_at(_pos);
}
fn push_block_param_i64(&mut self) {
self.push_block_param_i64_at(0);
}
fn br_if_with_args(
&mut self,
_then_index: usize,
_else_index: usize,
_then_n: usize,
_else_n: usize,
) {
self.br_if_top_is_true(_then_index, _else_index);
}
fn jump_with_args(&mut self, _target_index: usize, _n: usize) {
self.jump_to(_target_index);
}
fn hint_ret_bool(&mut self, _is_b1: bool) {}
fn ensure_local_i64(&mut self, _index: usize) {}
fn store_local_i64(&mut self, _index: usize) {}
fn load_local_i64(&mut self, _index: usize) {}
// Optional debug hook: print a local i64 value with a tag (Cranelift JIT only)
fn emit_debug_i64_local(&mut self, _tag: i64, _slot: usize) {}
}
mod noop;
pub use noop::NoopBuilder;
// Backend modules (feature-gated)
#[cfg(feature = "cranelift-jit")]
mod cranelift;
#[cfg(feature = "cranelift-jit")]
pub use cranelift::CraneliftBuilder;
#[cfg(feature = "cranelift-jit")]
mod object;
#[cfg(feature = "cranelift-jit")]
pub use object::ObjectBuilder;
// TLS and runtime shim submodules used by Cranelift backend
#[cfg(feature = "cranelift-jit")]
mod tls;
#[cfg(feature = "cranelift-jit")]
pub(crate) use tls::clif_tls;
#[cfg(feature = "cranelift-jit")]
mod rt_shims;

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,89 @@
use super::{BinOpKind, CmpKind, IRBuilder, ParamKind};
pub struct NoopBuilder {
pub consts: usize,
pub binops: usize,
pub cmps: usize,
pub branches: usize,
pub rets: usize,
}
impl NoopBuilder {
pub fn new() -> Self {
Self {
consts: 0,
binops: 0,
cmps: 0,
branches: 0,
rets: 0,
}
}
}
impl IRBuilder for NoopBuilder {
fn begin_function(&mut self, _name: &str) {}
fn end_function(&mut self) {}
fn emit_param_i64(&mut self, _index: usize) {
self.consts += 1;
}
fn emit_const_i64(&mut self, _val: i64) {
self.consts += 1;
}
fn emit_const_f64(&mut self, _val: f64) {
self.consts += 1;
}
fn emit_binop(&mut self, _op: BinOpKind) {
self.binops += 1;
}
fn emit_compare(&mut self, _op: CmpKind) {
self.cmps += 1;
}
fn emit_jump(&mut self) {
self.branches += 1;
}
fn emit_branch(&mut self) {
self.branches += 1;
}
fn emit_return(&mut self) {
self.rets += 1;
}
fn emit_select_i64(&mut self) {
self.binops += 1;
}
fn emit_host_call_typed(
&mut self,
_symbol: &str,
_params: &[ParamKind],
has_ret: bool,
_ret_is_f64: bool,
) {
if has_ret {
self.consts += 1;
}
}
fn emit_host_call_fixed3(&mut self, _symbol: &str, has_ret: bool) {
if has_ret {
self.consts += 1;
}
}
fn emit_plugin_invoke(&mut self, _type_id: u32, _method_id: u32, _argc: usize, has_ret: bool) {
if has_ret {
self.consts += 1;
}
}
fn emit_plugin_invoke_by_name(&mut self, _method: &str, _argc: usize, has_ret: bool) {
if has_ret {
self.consts += 1;
}
}
fn emit_string_handle_from_literal(&mut self, _s: &str) {
self.consts += 1;
}
fn ensure_local_i64(&mut self, _index: usize) {}
fn store_local_i64(&mut self, _index: usize) {
self.consts += 1;
}
fn load_local_i64(&mut self, _index: usize) {
self.consts += 1;
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,414 @@
#![cfg(feature = "cranelift-jit")]
// Runtime shims and helpers used by the Cranelift JIT backend
pub(crate) extern "C" fn nyash_host_stub0() -> i64 {
0
}
pub(crate) extern "C" fn nyash_jit_dbg_i64(tag: i64, val: i64) -> i64 {
eprintln!("[JIT-DBG] tag={} val={}", tag, val);
val
}
pub(crate) extern "C" fn nyash_jit_block_enter(idx: i64) {
eprintln!("[JIT-BLOCK] enter={}", idx);
}
pub(crate) extern "C" fn nyash_plugin_invoke3_i64(
type_id: i64,
method_id: i64,
argc: i64,
a0: i64,
a1: i64,
a2: i64,
) -> i64 {
use crate::runtime::plugin_loader_v2::PluginBoxV2;
let trace = crate::jit::observe::trace_enabled();
crate::jit::events::emit_runtime(
serde_json::json!({ "id": "shim.enter.i64", "type_id": type_id, "method_id": method_id, "argc": argc }),
"shim",
"<jit>",
);
let mut instance_id: u32 = 0;
let mut invoke: Option<
unsafe extern "C" fn(u32, u32, u32, *const u8, usize, *mut u8, *mut usize) -> i32,
> = None;
if a0 > 0 {
if let Some(obj) = crate::jit::rt::handles::get(a0 as u64) {
if let Some(p) = obj.as_any().downcast_ref::<PluginBoxV2>() {
instance_id = p.instance_id();
invoke = Some(p.inner.invoke_fn);
} else if method_id as u32 == 1 {
if let Some(arr) = obj.as_any().downcast_ref::<crate::boxes::array::ArrayBox>() {
if let Some(ib) = arr
.length()
.as_any()
.downcast_ref::<crate::box_trait::IntegerBox>()
{
return ib.value;
}
}
if let Some(sb) = obj.as_any().downcast_ref::<crate::box_trait::StringBox>() {
return sb.value.len() as i64;
}
}
}
}
let mut native_array_len: Option<i64> = None;
#[cfg(not(feature = "jit-direct-only"))]
{
if a0 >= 0 && std::env::var("NYASH_JIT_ARGS_HANDLE_ONLY").ok().as_deref() != Some("1") {
crate::jit::rt::with_legacy_vm_args(|args| {
let idx = a0 as usize;
if let Some(crate::backend::vm::VMValue::BoxRef(b)) = args.get(idx) {
if let Some(p) = b.as_any().downcast_ref::<PluginBoxV2>() {
instance_id = p.instance_id();
invoke = Some(p.inner.invoke_fn);
} else if let Some(arr) =
b.as_any().downcast_ref::<crate::boxes::array::ArrayBox>()
{
if method_id as u32 == 1 {
if let Some(ib) = arr
.length()
.as_any()
.downcast_ref::<crate::box_trait::IntegerBox>()
{
native_array_len = Some(ib.value);
}
}
}
}
});
}
}
if invoke.is_none() {
if let Some(v) = native_array_len {
if trace {
eprintln!("[JIT-SHIM i64] native_fallback return {}", v);
}
crate::jit::events::emit_runtime(
serde_json::json!({ "id": "shim.native.i64", "type_id": type_id, "method_id": method_id, "argc": argc, "ret": v }),
"shim",
"<jit>",
);
return v;
}
}
#[cfg(not(feature = "jit-direct-only"))]
if invoke.is_none() {
crate::jit::rt::with_legacy_vm_args(|args| {
for v in args.iter() {
if let crate::backend::vm::VMValue::BoxRef(b) = v {
if let Some(p) = b.as_any().downcast_ref::<PluginBoxV2>() {
instance_id = p.instance_id();
invoke = Some(p.inner.invoke_fn);
break;
}
}
}
});
}
if invoke.is_none() {
return 0;
}
let mut buf = crate::runtime::plugin_ffi_common::encode_tlv_header(
(argc.saturating_sub(1).max(0) as u16),
);
let mut add_i64 = |v: i64| {
crate::runtime::plugin_ffi_common::encode::i64(&mut buf, v);
};
if argc >= 2 {
add_i64(a1);
}
if argc >= 3 {
add_i64(a2);
}
let mut out = vec![0xCDu8; 4096 + 32];
let canary_val = 0xABu8;
let canary_len = 16usize;
for i in 0..canary_len {
out[i] = canary_val;
}
for i in 0..canary_len {
out[4096 + canary_len + i] = canary_val;
}
let mut out_len: usize = 0;
let ok = unsafe {
(invoke.unwrap())(
instance_id,
type_id as u32,
method_id as u32,
buf.as_ptr(),
buf.len(),
out.as_mut_ptr().add(canary_len),
&mut out_len as *mut usize,
)
};
if ok != 0 {
let out_slice = &out[canary_len..(canary_len + out_len.min(4096))];
if let Some((tag, _sz, payload)) =
crate::runtime::plugin_ffi_common::decode::tlv_first(out_slice)
{
match tag {
3 => {
if let Some(v) = crate::runtime::plugin_ffi_common::decode::i32(payload) {
return v as i64;
}
if payload.len() == 8 {
let mut b = [0u8; 8];
b.copy_from_slice(payload);
return i64::from_le_bytes(b);
}
}
8 => {
if _sz == 8 {
let mut t = [0u8; 4];
t.copy_from_slice(&payload[0..4]);
let mut i = [0u8; 4];
i.copy_from_slice(&payload[4..8]);
let r_type = u32::from_le_bytes(t);
let r_inst = u32::from_le_bytes(i);
let meta_opt =
crate::runtime::plugin_loader_v2::metadata_for_type_id(r_type);
let (box_type_name, invoke_ptr) = if let Some(meta) = meta_opt {
(meta.box_type.clone(), meta.invoke_fn)
} else {
("PluginBox".to_string(), invoke.unwrap())
};
let pb = crate::runtime::plugin_loader_v2::make_plugin_box_v2(
box_type_name,
r_type,
r_inst,
invoke_ptr,
);
let arc: std::sync::Arc<dyn crate::box_trait::NyashBox> =
std::sync::Arc::new(pb);
let h = crate::jit::rt::handles::to_handle(arc);
return h as i64;
}
}
1 => {
return if crate::runtime::plugin_ffi_common::decode::bool(payload)
.unwrap_or(false)
{
1
} else {
0
};
}
5 => {
if std::env::var("NYASH_JIT_NATIVE_F64").ok().as_deref() == Some("1") {
if _sz == 8 {
let mut b = [0u8; 8];
b.copy_from_slice(payload);
let f = f64::from_le_bytes(b);
return f as i64;
}
}
}
_ => {}
}
}
}
0
}
pub(crate) extern "C" fn nyash_plugin_invoke3_f64(
_type_id: i64,
_method_id: i64,
_argc: i64,
_a0: i64,
_a1: i64,
_a2: i64,
) -> f64 {
0.0
}
// === By-name plugin shims (i64) ===
pub(crate) extern "C" fn nyash_plugin_invoke_name_getattr_i64(
argc: i64,
a0: i64,
a1: i64,
a2: i64,
) -> i64 {
nyash_plugin_invoke_name_common_i64("getattr", argc, a0, a1, a2)
}
pub(crate) extern "C" fn nyash_plugin_invoke_name_call_i64(
argc: i64,
a0: i64,
a1: i64,
a2: i64,
) -> i64 {
nyash_plugin_invoke_name_common_i64("call", argc, a0, a1, a2)
}
fn nyash_plugin_invoke_name_common_i64(method: &str, argc: i64, a0: i64, a1: i64, a2: i64) -> i64 {
use crate::runtime::plugin_loader_v2::PluginBoxV2;
let mut instance_id: u32 = 0;
let mut type_id: u32 = 0;
let mut box_type: Option<String> = None;
let mut invoke: Option<
unsafe extern "C" fn(u32, u32, u32, *const u8, usize, *mut u8, *mut usize) -> i32,
> = None;
if a0 > 0 {
if let Some(obj) = crate::jit::rt::handles::get(a0 as u64) {
if let Some(p) = obj.as_any().downcast_ref::<PluginBoxV2>() {
instance_id = p.instance_id();
type_id = p.inner.type_id;
box_type = Some(p.box_type.clone());
invoke = Some(p.inner.invoke_fn);
}
}
}
if invoke.is_none() && std::env::var("NYASH_JIT_ARGS_HANDLE_ONLY").ok().as_deref() != Some("1")
{
crate::jit::rt::with_legacy_vm_args(|args| {
let idx = a0.max(0) as usize;
if let Some(crate::backend::vm::VMValue::BoxRef(b)) = args.get(idx) {
if let Some(p) = b.as_any().downcast_ref::<PluginBoxV2>() {
instance_id = p.instance_id();
type_id = p.inner.type_id;
box_type = Some(p.box_type.clone());
invoke = Some(p.inner.invoke_fn);
}
}
});
}
if invoke.is_none() {
crate::jit::rt::with_legacy_vm_args(|args| {
for v in args.iter() {
if let crate::backend::vm::VMValue::BoxRef(b) = v {
if let Some(p) = b.as_any().downcast_ref::<PluginBoxV2>() {
instance_id = p.instance_id();
type_id = p.inner.type_id;
box_type = Some(p.box_type.clone());
invoke = Some(p.inner.invoke_fn);
break;
}
}
}
});
}
if invoke.is_none() {
return 0;
}
let box_type = box_type.unwrap_or_default();
let mh =
if let Ok(host) = crate::runtime::plugin_loader_unified::get_global_plugin_host().read() {
host.resolve_method(&box_type, method)
} else {
return 0;
};
let method_id = match mh {
Ok(h) => h.method_id,
Err(_) => return 0,
} as u32;
let mut buf = crate::runtime::plugin_ffi_common::encode_tlv_header(
(argc.saturating_sub(1).max(0) as u16),
);
let mut add_from_legacy = |pos: usize| {
crate::jit::rt::with_legacy_vm_args(|args| {
if let Some(v) = args.get(pos) {
match v {
crate::backend::vm::VMValue::Integer(i) => {
crate::runtime::plugin_ffi_common::encode::i64(&mut buf, *i)
}
crate::backend::vm::VMValue::Float(f) => {
crate::runtime::plugin_ffi_common::encode::f64(&mut buf, *f)
}
crate::backend::vm::VMValue::Bool(b) => {
crate::runtime::plugin_ffi_common::encode::bool(&mut buf, *b)
}
crate::backend::vm::VMValue::BoxRef(_) => {
crate::runtime::plugin_ffi_common::encode::i64(&mut buf, 0);
}
_ => {}
}
}
});
};
if argc >= 2 {
add_from_legacy(1);
}
if argc >= 3 {
add_from_legacy(2);
}
let mut out = vec![0u8; 4096];
let mut out_len: usize = 0;
let ok = unsafe {
(invoke.unwrap())(
instance_id,
type_id as u32,
method_id as u32,
buf.as_ptr(),
buf.len(),
out.as_mut_ptr(),
&mut out_len as *mut usize,
)
};
if ok != 0 {
let out_slice = &out[0..out_len.min(4096)];
if let Some((tag, _sz, payload)) =
crate::runtime::plugin_ffi_common::decode::tlv_first(out_slice)
{
match tag {
3 => {
if payload.len() == 8 {
let mut b = [0u8; 8];
b.copy_from_slice(payload);
return i64::from_le_bytes(b);
}
if let Some(v) = crate::runtime::plugin_ffi_common::decode::i32(payload) {
return v as i64;
}
}
8 => {
if _sz == 8 {
let mut t = [0u8; 4];
t.copy_from_slice(&payload[0..4]);
let mut i = [0u8; 4];
i.copy_from_slice(&payload[4..8]);
let r_type = u32::from_le_bytes(t);
let r_inst = u32::from_le_bytes(i);
let meta_opt =
crate::runtime::plugin_loader_v2::metadata_for_type_id(r_type);
let (meta_box, invoke_ptr) = if let Some(meta) = meta_opt {
(meta.box_type, meta.invoke_fn)
} else {
(box_type.clone(), invoke.unwrap())
};
let pb = crate::runtime::plugin_loader_v2::make_plugin_box_v2(
meta_box, r_type, r_inst, invoke_ptr,
);
let arc: std::sync::Arc<dyn crate::box_trait::NyashBox> =
std::sync::Arc::new(pb);
let h = crate::jit::rt::handles::to_handle(arc);
return h as i64;
}
}
1 => {
return if crate::runtime::plugin_ffi_common::decode::bool(payload)
.unwrap_or(false)
{
1
} else {
0
};
}
5 => {
if std::env::var("NYASH_JIT_NATIVE_F64").ok().as_deref() == Some("1") {
if _sz == 8 {
let mut b = [0u8; 8];
b.copy_from_slice(payload);
let f = f64::from_le_bytes(b);
return f as i64;
}
}
}
_ => {}
}
}
}
0
}

View File

@ -0,0 +1,119 @@
#![cfg(feature = "cranelift-jit")]
use cranelift_codegen::ir::InstBuilder;
use cranelift_module::Module;
// TLS: 単一関数あたり1つの FunctionBuilder を保持jit-direct 専用)
pub(crate) mod clif_tls {
use super::*;
thread_local! {
pub static FB: std::cell::RefCell<Option<TlsCtx>> = std::cell::RefCell::new(None);
}
pub struct TlsCtx {
pub ctx: Box<cranelift_codegen::Context>,
pub fbc: Box<cranelift_frontend::FunctionBuilderContext>,
pub(crate) fb: *mut cranelift_frontend::FunctionBuilder<'static>,
}
impl TlsCtx {
pub fn new() -> Self {
Self {
ctx: Box::new(cranelift_codegen::Context::new()),
fbc: Box::new(cranelift_frontend::FunctionBuilderContext::new()),
fb: core::ptr::null_mut(),
}
}
pub unsafe fn create(&mut self) {
let func_ptr: *mut cranelift_codegen::ir::Function = &mut self.ctx.func;
let fbc_ptr: *mut cranelift_frontend::FunctionBuilderContext = &mut *self.fbc;
let fb = Box::new(cranelift_frontend::FunctionBuilder::new(
&mut *func_ptr,
&mut *fbc_ptr,
));
self.fb = Box::into_raw(fb);
}
pub fn with<R>(
&mut self,
f: impl FnOnce(&mut cranelift_frontend::FunctionBuilder<'static>) -> R,
) -> R {
unsafe { f(&mut *self.fb) }
}
pub unsafe fn finalize_drop(&mut self) {
if !self.fb.is_null() {
let fb = Box::from_raw(self.fb);
fb.finalize();
self.fb = core::ptr::null_mut();
}
}
/// Finalize the current FunctionBuilder and take ownership of the underlying Context.
pub fn take_context(&mut self) -> cranelift_codegen::Context {
unsafe {
self.finalize_drop();
}
// Move the current context out and replace with a fresh one
let old = std::mem::replace(&mut self.ctx, Box::new(cranelift_codegen::Context::new()));
*old
}
}
}
// Small TLS helpers to call imported functions via the single FunctionBuilder
pub(crate) fn tls_call_import_ret(
module: &mut cranelift_jit::JITModule,
func_id: cranelift_module::FuncId,
args: &[cranelift_codegen::ir::Value],
has_ret: bool,
) -> Option<cranelift_codegen::ir::Value> {
clif_tls::FB.with(|cell| {
let mut opt = cell.borrow_mut();
let tls = opt.as_mut().expect("FunctionBuilder TLS not initialized");
tls.with(|fb| {
// Guard: avoid emitting a verifier-invalid call when args are unexpectedly empty.
// Some early shims (e.g., instrumentation) may have declared a 1-arity import;
// if lowering produced no arguments, synthesize a zero literal when a return is expected,
// and skip the call entirely to keep the IR valid.
if args.is_empty() {
if has_ret {
use cranelift_codegen::ir::types;
return Some(fb.ins().iconst(types::I64, 0));
} else {
return None;
}
}
let fref = module.declare_func_in_func(func_id, fb.func);
let call_inst = fb.ins().call(fref, args);
if has_ret {
fb.inst_results(call_inst).get(0).copied()
} else {
None
}
})
})
}
pub(crate) fn tls_call_import_with_iconsts(
module: &mut cranelift_jit::JITModule,
func_id: cranelift_module::FuncId,
iconsts: &[i64],
tail_args: &[cranelift_codegen::ir::Value],
has_ret: bool,
) -> Option<cranelift_codegen::ir::Value> {
use cranelift_codegen::ir::types;
clif_tls::FB.with(|cell| {
let mut opt = cell.borrow_mut();
let tls = opt.as_mut().expect("FunctionBuilder TLS not initialized");
tls.with(|fb| {
let mut all_args: Vec<cranelift_codegen::ir::Value> = Vec::new();
for &c in iconsts {
all_args.push(fb.ins().iconst(types::I64, c));
}
all_args.extend_from_slice(tail_args);
let fref = module.declare_func_in_func(func_id, fb.func);
let call_inst = fb.ins().call(fref, &all_args);
if has_ret {
fb.inst_results(call_inst).get(0).copied()
} else {
None
}
})
})
}

View File

@ -0,0 +1,91 @@
pub fn dump_cfg_dot(
func: &crate::mir::MirFunction,
path: &str,
phi_min: bool,
) -> std::io::Result<()> {
let mut out = String::new();
out.push_str(&format!("digraph \"{}\" {{\n", func.signature.name));
out.push_str(" node [shape=box, fontsize=10];\n");
// Derive simple bool sets: compare dsts are bool; phi of all-bool inputs are bool
let mut bool_values: std::collections::HashSet<crate::mir::ValueId> =
std::collections::HashSet::new();
for (_bb_id, bb) in func.blocks.iter() {
for ins in bb.instructions.iter() {
if let crate::mir::MirInstruction::Compare { dst, .. } = ins {
bool_values.insert(*dst);
}
}
}
let mut bool_phi: std::collections::HashSet<crate::mir::ValueId> =
std::collections::HashSet::new();
if phi_min {
for (_bb_id, bb) in func.blocks.iter() {
for ins in bb.instructions.iter() {
if let crate::mir::MirInstruction::Phi { dst, inputs } = ins {
if !inputs.is_empty() && inputs.iter().all(|(_, v)| bool_values.contains(v)) {
bool_phi.insert(*dst);
}
}
}
}
}
// Sort blocks for deterministic output
let mut bb_ids: Vec<_> = func.blocks.keys().copied().collect();
bb_ids.sort_by_key(|b| b.0);
// Emit nodes with labels
for bb_id in bb_ids.iter() {
let bb = func.blocks.get(bb_id).unwrap();
let phi_count = bb
.instructions
.iter()
.filter(|ins| matches!(ins, crate::mir::MirInstruction::Phi { .. }))
.count();
let phi_b1_count = bb
.instructions
.iter()
.filter(|ins| match ins {
crate::mir::MirInstruction::Phi { dst, .. } => bool_phi.contains(dst),
_ => false,
})
.count();
let mut label = format!("bb{}", bb_id.0);
if phi_min && phi_count > 0 {
if phi_b1_count > 0 {
label = format!("{}\\nphi:{} (b1:{})", label, phi_count, phi_b1_count);
} else {
label = format!("{}\\nphi:{}", label, phi_count);
}
}
if *bb_id == func.entry_block {
label = format!("{}\\nENTRY", label);
}
out.push_str(&format!(" n{} [label=\"{}\"];\n", bb_id.0, label));
}
// Emit edges based on terminators
for bb_id in bb_ids.iter() {
let bb = func.blocks.get(bb_id).unwrap();
if let Some(term) = &bb.terminator {
match term {
crate::mir::MirInstruction::Jump { target } => {
out.push_str(&format!(" n{} -> n{};\n", bb_id.0, target.0));
}
crate::mir::MirInstruction::Branch {
then_bb, else_bb, ..
} => {
// Branch condition is boolean (b1)
out.push_str(&format!(
" n{} -> n{} [label=\"then cond:b1\"];\n",
bb_id.0, then_bb.0
));
out.push_str(&format!(
" n{} -> n{} [label=\"else cond:b1\"];\n",
bb_id.0, else_bb.0
));
}
_ => {}
}
}
}
out.push_str("}\n");
std::fs::write(path, out)
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,140 @@
use std::collections::HashSet;
use crate::mir::{BasicBlockId, MirFunction, MirInstruction, ValueId};
// removed unused imports
use super::LowerCore;
impl LowerCore {
pub(crate) fn analyze(&mut self, func: &MirFunction, bb_ids: &Vec<BasicBlockId>) {
// Seed boolean lattice with boolean parameters from MIR signature
if !func.signature.params.is_empty() {
for (idx, vid) in func.params.iter().copied().enumerate() {
if let Some(mt) = func.signature.params.get(idx) {
if matches!(mt, crate::mir::MirType::Bool) {
self.bool_values.insert(vid);
}
}
}
}
// Pre-scan to classify boolean-producing values and propagate via Copy/Phi/Load-Store heuristics.
self.bool_values.clear();
let mut copy_edges: Vec<(ValueId, ValueId)> = Vec::new();
let mut phi_defs: Vec<(ValueId, Vec<ValueId>)> = Vec::new();
let mut stores: Vec<(ValueId, ValueId)> = Vec::new(); // (ptr, value)
let mut loads: Vec<(ValueId, ValueId)> = Vec::new(); // (dst, ptr)
for bb in bb_ids.iter() {
if let Some(block) = func.blocks.get(bb) {
for ins in block.instructions.iter() {
match ins {
MirInstruction::Compare { dst, .. } => {
self.bool_values.insert(*dst);
}
MirInstruction::Const { dst, value } => {
if let crate::mir::ConstValue::Bool(_) = value {
self.bool_values.insert(*dst);
}
}
MirInstruction::Copy { dst, src } => {
copy_edges.push((*dst, *src));
}
MirInstruction::Phi { dst, inputs } => {
self.phi_values.insert(*dst);
let ins: Vec<ValueId> = inputs.iter().map(|(_, v)| *v).collect();
phi_defs.push((*dst, ins));
}
MirInstruction::Store { ptr, value } => {
stores.push((*ptr, *value));
}
MirInstruction::Load { dst, ptr } => {
loads.push((*dst, *ptr));
}
_ => {}
}
}
}
}
// Fixed-point propagation
let mut store_bool_ptrs: HashSet<ValueId> = HashSet::new();
let mut changed = true;
while changed {
changed = false;
// Copy propagation
for (dst, src) in copy_edges.iter().copied() {
if self.bool_values.contains(&src) && !self.bool_values.contains(&dst) {
self.bool_values.insert(dst);
changed = true;
}
if store_bool_ptrs.contains(&src) && !store_bool_ptrs.contains(&dst) {
store_bool_ptrs.insert(dst);
changed = true;
}
}
// Store marking
for (ptr, val) in stores.iter().copied() {
if self.bool_values.contains(&val) && !store_bool_ptrs.contains(&ptr) {
store_bool_ptrs.insert(ptr);
changed = true;
}
}
// Load propagation
for (dst, ptr) in loads.iter().copied() {
if store_bool_ptrs.contains(&ptr) && !self.bool_values.contains(&dst) {
self.bool_values.insert(dst);
changed = true;
}
}
// PHI closure for value booleans
for (dst, inputs) in phi_defs.iter() {
if inputs.iter().all(|v| self.bool_values.contains(v))
&& !self.bool_values.contains(dst)
{
self.bool_values.insert(*dst);
self.bool_phi_values.insert(*dst);
changed = true;
}
}
// PHI closure for pointer aliases
for (dst, inputs) in phi_defs.iter() {
if inputs.iter().all(|v| store_bool_ptrs.contains(v))
&& !store_bool_ptrs.contains(dst)
{
store_bool_ptrs.insert(*dst);
changed = true;
}
}
}
// PHI statistics
let mut total_phi_slots: usize = 0;
let mut total_phi_b1_slots: usize = 0;
for (dst, inputs) in phi_defs.iter() {
total_phi_slots += 1;
let used_as_branch = func.blocks.values().any(|bbx| {
if let Some(MirInstruction::Branch { condition, .. }) = &bbx.terminator {
condition == dst
} else {
false
}
});
let is_b1 = self.bool_phi_values.contains(dst)
|| inputs.iter().all(|v| {
self.bool_values.contains(v)
|| self
.known_i64
.get(v)
.map(|&iv| iv == 0 || iv == 1)
.unwrap_or(false)
})
|| used_as_branch;
if is_b1 {
total_phi_b1_slots += 1;
}
}
if total_phi_slots > 0 {
crate::jit::rt::phi_total_inc(total_phi_slots as u64);
crate::jit::rt::phi_b1_inc(total_phi_b1_slots as u64);
self.last_phi_total = total_phi_slots as u64;
self.last_phi_b1 = total_phi_b1_slots as u64;
}
}
}

View File

@ -0,0 +1,284 @@
use super::super::builder::IRBuilder;
use super::LowerCore;
use crate::mir::{BasicBlockId, MirFunction, MirInstruction};
use std::collections::HashMap;
impl LowerCore {
pub(crate) fn build_phi_succords(
&mut self,
func: &MirFunction,
bb_ids: &Vec<BasicBlockId>,
builder: &mut dyn IRBuilder,
enable_phi_min: bool,
) -> HashMap<BasicBlockId, Vec<crate::mir::ValueId>> {
let mut succ_phi_order: HashMap<BasicBlockId, Vec<crate::mir::ValueId>> = HashMap::new();
if !enable_phi_min {
return succ_phi_order;
}
for (bb_id, bb) in func.blocks.iter() {
let mut order: Vec<crate::mir::ValueId> = Vec::new();
for ins in bb.instructions.iter() {
if let MirInstruction::Phi { dst, .. } = ins {
order.push(*dst);
}
}
if !order.is_empty() {
succ_phi_order.insert(*bb_id, order);
}
}
// Pre-declare block parameter counts per successor to avoid late appends
for (succ, order) in succ_phi_order.iter() {
if let Some(idx) = bb_ids.iter().position(|x| x == succ) {
builder.ensure_block_params_i64(idx, order.len());
}
}
succ_phi_order
}
pub(crate) fn dump_phi_cfg(
&self,
succ_phi_order: &HashMap<BasicBlockId, Vec<crate::mir::ValueId>>,
func: &MirFunction,
blocks_len: usize,
enable_phi_min: bool,
) {
if std::env::var("NYASH_JIT_DUMP").ok().as_deref() != Some("1") {
return;
}
let succs = succ_phi_order.len();
eprintln!(
"[JIT] cfg: blocks={} phi_succ={} (phi_min={})",
blocks_len, succs, enable_phi_min
);
if enable_phi_min {
let mut total_phi_slots: usize = 0;
let mut total_phi_b1_slots: usize = 0;
for (succ, order) in succ_phi_order.iter() {
let mut preds_set: std::collections::BTreeSet<i64> =
std::collections::BTreeSet::new();
let mut phi_lines: Vec<String> = Vec::new();
if let Some(bb_succ) = func.blocks.get(succ) {
for ins in bb_succ.instructions.iter() {
if let MirInstruction::Phi { dst, inputs } = ins {
for (pred, _) in inputs.iter() {
preds_set.insert(pred.0 as i64);
}
let mut pairs: Vec<String> = Vec::new();
for (pred, val) in inputs.iter() {
pairs.push(format!("{}:{}", pred.0, val.0));
}
let used_as_branch = func.blocks.values().any(|bbx| {
if let Some(MirInstruction::Branch { condition, .. }) =
&bbx.terminator
{
condition == dst
} else {
false
}
});
let is_b1 = self.bool_phi_values.contains(dst)
|| inputs.iter().all(|(_, v)| {
self.bool_values.contains(v)
|| self
.known_i64
.get(v)
.map(|&iv| iv == 0 || iv == 1)
.unwrap_or(false)
})
|| used_as_branch;
if is_b1 {
total_phi_b1_slots += 1;
}
total_phi_slots += 1;
phi_lines.push(format!(
" phi: bb={} dst={} inputs=[{}] (b1={})",
succ.0,
dst.0,
pairs.join(","),
is_b1
));
}
}
}
let preds_list: Vec<String> =
preds_set.into_iter().map(|p| p.to_string()).collect();
eprintln!(
"[JIT] phi: bb={} slots={} preds={}",
succ.0,
order.len(),
preds_list.join("|")
);
for ln in phi_lines {
eprintln!("[JIT]{}", ln);
}
}
eprintln!(
"[JIT] phi_summary: total_slots={} b1_slots={}",
total_phi_slots, total_phi_b1_slots
);
}
}
}
impl LowerCore {
/// Lower a Branch terminator, including fast-path select+return and PHI(min) argument wiring.
pub(crate) fn lower_branch_terminator(
&mut self,
builder: &mut dyn IRBuilder,
func: &MirFunction,
bb_ids: &Vec<BasicBlockId>,
bb_id: BasicBlockId,
condition: &crate::mir::ValueId,
then_bb: &BasicBlockId,
else_bb: &BasicBlockId,
succ_phi_order: &HashMap<BasicBlockId, Vec<crate::mir::ValueId>>,
enable_phi_min: bool,
) {
// Fast-path: if both successors immediately return known i64 constants, lower as select+return
let mut fastpath_done = false;
let succ_returns_const = |succ: &crate::mir::BasicBlock| -> Option<i64> {
use crate::mir::MirInstruction as I;
if let Some(I::Return { value: Some(v) }) = &succ.terminator {
for ins in succ.instructions.iter() {
if let I::Const { dst, value } = ins {
if dst == v {
if let crate::mir::ConstValue::Integer(k) = value {
return Some(*k);
}
}
}
}
}
None
};
if let (Some(bb_then), Some(bb_else)) = (func.blocks.get(then_bb), func.blocks.get(else_bb))
{
if let (Some(k_then), Some(k_else)) =
(succ_returns_const(bb_then), succ_returns_const(bb_else))
{
self.push_value_if_known_or_param(builder, condition);
builder.emit_const_i64(k_then);
builder.emit_const_i64(k_else);
builder.emit_select_i64();
builder.emit_return();
fastpath_done = true;
}
}
if fastpath_done {
return;
}
// Otherwise, emit CFG branch with optional PHI(min) argument wiring
self.push_value_if_known_or_param(builder, condition);
let then_index = bb_ids.iter().position(|x| x == then_bb).unwrap_or(0);
let else_index = bb_ids.iter().position(|x| x == else_bb).unwrap_or(0);
if std::env::var("NYASH_JIT_DUMP").ok().as_deref() == Some("1") {
eprintln!(
"[LowerCore] br_if: cur_bb={} then_idx={} else_idx={}",
bb_id.0, then_index, else_index
);
}
if enable_phi_min {
let mut then_n = 0usize;
let mut else_n = 0usize;
if let Some(order) = succ_phi_order.get(then_bb) {
let mut cnt = 0usize;
if let Some(bb_succ) = func.blocks.get(then_bb) {
for dst in order.iter() {
for ins in bb_succ.instructions.iter() {
if let crate::mir::MirInstruction::Phi { dst: d2, inputs } = ins {
if d2 == dst {
if let Some((_, val)) =
inputs.iter().find(|(pred, _)| pred == &bb_id)
{
self.push_value_if_known_or_param(builder, val);
cnt += 1;
}
}
}
}
}
}
if cnt > 0 {
builder.ensure_block_params_i64(then_index, cnt);
}
then_n = cnt;
}
if let Some(order) = succ_phi_order.get(else_bb) {
let mut cnt = 0usize;
if let Some(bb_succ) = func.blocks.get(else_bb) {
for dst in order.iter() {
for ins in bb_succ.instructions.iter() {
if let crate::mir::MirInstruction::Phi { dst: d2, inputs } = ins {
if d2 == dst {
if let Some((_, val)) =
inputs.iter().find(|(pred, _)| pred == &bb_id)
{
self.push_value_if_known_or_param(builder, val);
cnt += 1;
}
}
}
}
}
}
if cnt > 0 {
builder.ensure_block_params_i64(else_index, cnt);
}
else_n = cnt;
}
builder.br_if_with_args(then_index, else_index, then_n, else_n);
} else {
builder.br_if_top_is_true(then_index, else_index);
}
}
/// Lower a Jump terminator with optional PHI(min) argument wiring.
pub(crate) fn lower_jump_terminator(
&mut self,
builder: &mut dyn IRBuilder,
func: &MirFunction,
bb_ids: &Vec<BasicBlockId>,
bb_id: BasicBlockId,
target: &BasicBlockId,
succ_phi_order: &HashMap<BasicBlockId, Vec<crate::mir::ValueId>>,
enable_phi_min: bool,
) {
let target_index = bb_ids.iter().position(|x| x == target).unwrap_or(0);
if std::env::var("NYASH_JIT_DUMP").ok().as_deref() == Some("1") {
eprintln!(
"[LowerCore] jump: cur_bb={} target_idx={}",
bb_id.0, target_index
);
}
if enable_phi_min {
let mut n = 0usize;
if let Some(order) = succ_phi_order.get(target) {
let mut cnt = 0usize;
if let Some(bb_succ) = func.blocks.get(target) {
for dst in order.iter() {
for ins in bb_succ.instructions.iter() {
if let crate::mir::MirInstruction::Phi { dst: d2, inputs } = ins {
if d2 == dst {
if let Some((_, val)) =
inputs.iter().find(|(pred, _)| pred == &bb_id)
{
self.push_value_if_known_or_param(builder, val);
cnt += 1;
}
}
}
}
}
}
if cnt > 0 {
builder.ensure_block_params_i64(target_index, cnt);
}
n = cnt;
}
builder.jump_with_args(target_index, n);
} else {
builder.jump_to(target_index);
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,157 @@
use super::super::builder::IRBuilder;
use super::LowerCore;
impl LowerCore {
/// Emit robust length retrieval with fallback for String/Any:
/// 1) Prefer `nyash.string.len_h(recv)`
/// 2) If that yields 0 at runtime, select `nyash.any.length_h(recv)`
/// Returns: pushes selected length (i64) onto builder stack.
pub(super) fn emit_len_with_fallback_param(&mut self, b: &mut dyn IRBuilder, pidx: usize) {
use super::super::builder::CmpKind;
// Temp locals
let hslot = self.next_local;
self.next_local += 1; // receiver handle slot
let t_string = self.next_local;
self.next_local += 1;
let t_any = self.next_local;
self.next_local += 1;
let t_cond = self.next_local;
self.next_local += 1;
// Materialize receiver handle from param index
b.emit_param_i64(pidx);
b.emit_host_call(crate::jit::r#extern::handles::SYM_HANDLE_OF, 1, true);
b.store_local_i64(hslot);
// String.len_h
crate::jit::observe::lower_hostcall(
crate::jit::r#extern::collections::SYM_STRING_LEN_H,
1,
&["Handle"],
"allow",
"core_len_param",
);
b.load_local_i64(hslot);
b.emit_host_call(crate::jit::r#extern::collections::SYM_STRING_LEN_H, 1, true);
b.store_local_i64(t_string);
// debug: observe string len
b.emit_debug_i64_local(1100, t_string);
// Any.length_h
crate::jit::observe::lower_hostcall(
crate::jit::r#extern::collections::SYM_ANY_LEN_H,
1,
&["Handle"],
"allow",
"core_len_param",
);
b.load_local_i64(hslot);
b.emit_host_call(crate::jit::r#extern::collections::SYM_ANY_LEN_H, 1, true);
b.store_local_i64(t_any);
// debug: observe any len
b.emit_debug_i64_local(1101, t_any);
// cond = (string_len == 0)
b.load_local_i64(t_string);
b.emit_const_i64(0);
b.emit_compare(CmpKind::Eq);
b.store_local_i64(t_cond);
// debug: observe condition
b.emit_debug_i64_local(1102, t_cond);
// select(cond ? any_len : string_len)
b.load_local_i64(t_cond); // cond (bottom)
b.load_local_i64(t_any); // then
b.load_local_i64(t_string); // else
b.emit_select_i64();
}
pub(super) fn emit_len_with_fallback_local_handle(
&mut self,
b: &mut dyn IRBuilder,
slot: usize,
) {
use super::super::builder::CmpKind;
let t_string = self.next_local;
self.next_local += 1;
let t_any = self.next_local;
self.next_local += 1;
let t_cond = self.next_local;
self.next_local += 1;
// String.len_h
crate::jit::observe::lower_hostcall(
crate::jit::r#extern::collections::SYM_STRING_LEN_H,
1,
&["Handle"],
"allow",
"core_len_local",
);
b.load_local_i64(slot);
b.emit_host_call(crate::jit::r#extern::collections::SYM_STRING_LEN_H, 1, true);
b.store_local_i64(t_string);
b.emit_debug_i64_local(1200, t_string);
// Any.length_h
crate::jit::observe::lower_hostcall(
crate::jit::r#extern::collections::SYM_ANY_LEN_H,
1,
&["Handle"],
"allow",
"core_len_local",
);
b.load_local_i64(slot);
b.emit_host_call(crate::jit::r#extern::collections::SYM_ANY_LEN_H, 1, true);
b.store_local_i64(t_any);
b.emit_debug_i64_local(1201, t_any);
// cond = (string_len == 0)
b.load_local_i64(t_string);
b.emit_const_i64(0);
b.emit_compare(CmpKind::Eq);
b.store_local_i64(t_cond);
b.emit_debug_i64_local(1202, t_cond);
// select(cond ? any_len : string_len)
b.load_local_i64(t_cond);
b.load_local_i64(t_any);
b.load_local_i64(t_string);
b.emit_select_i64();
}
pub(super) fn emit_len_with_fallback_literal(&mut self, b: &mut dyn IRBuilder, s: &str) {
use super::super::builder::CmpKind;
let t_string = self.next_local;
self.next_local += 1;
let t_any = self.next_local;
self.next_local += 1;
let t_cond = self.next_local;
self.next_local += 1;
// String.len_h on literal handle
crate::jit::observe::lower_hostcall(
crate::jit::r#extern::collections::SYM_STRING_LEN_H,
1,
&["Handle"],
"allow",
"core_len_lit",
);
b.emit_string_handle_from_literal(s);
b.emit_host_call(crate::jit::r#extern::collections::SYM_STRING_LEN_H, 1, true);
b.store_local_i64(t_string);
b.emit_debug_i64_local(1300, t_string);
// Any.length_h on literal handle (recreate handle; safe in v0)
crate::jit::observe::lower_hostcall(
crate::jit::r#extern::collections::SYM_ANY_LEN_H,
1,
&["Handle"],
"allow",
"core_len_lit",
);
b.emit_string_handle_from_literal(s);
b.emit_host_call(crate::jit::r#extern::collections::SYM_ANY_LEN_H, 1, true);
b.store_local_i64(t_any);
b.emit_debug_i64_local(1301, t_any);
// cond = (string_len == 0)
b.load_local_i64(t_string);
b.emit_const_i64(0);
b.emit_compare(CmpKind::Eq);
b.store_local_i64(t_cond);
b.emit_debug_i64_local(1302, t_cond);
// select(cond ? any_len : string_len)
b.load_local_i64(t_cond);
b.load_local_i64(t_any);
b.load_local_i64(t_string);
b.emit_select_i64();
}
}

View File

@ -0,0 +1,803 @@
#![allow(unreachable_patterns, unused_variables)]
//! HostCall-related lowering helpers split from core.rs (no behavior change)
use super::builder::IRBuilder;
use crate::mir::{MirFunction, ValueId};
use std::collections::HashMap;
pub fn lower_array_get(
b: &mut dyn IRBuilder,
param_index: &HashMap<ValueId, usize>,
known_i64: &HashMap<ValueId, i64>,
array: &ValueId,
index: &ValueId,
) {
if crate::jit::config::current().hostcall {
let use_bridge = std::env::var("NYASH_JIT_HOST_BRIDGE").ok().as_deref() == Some("1");
let idx = known_i64.get(index).copied().unwrap_or(0);
if let Some(pidx) = param_index.get(array).copied() {
b.emit_param_i64(pidx);
b.emit_const_i64(idx);
let sym = if use_bridge {
crate::jit::r#extern::host_bridge::SYM_HOST_ARRAY_GET
} else {
crate::jit::r#extern::collections::SYM_ARRAY_GET_H
};
b.emit_host_call(sym, 2, true);
} else {
let arr_idx = -1;
b.emit_const_i64(arr_idx);
b.emit_const_i64(idx);
let sym = if use_bridge {
crate::jit::r#extern::host_bridge::SYM_HOST_ARRAY_GET
} else {
crate::jit::r#extern::collections::SYM_ARRAY_GET
};
b.emit_host_call(sym, 2, true);
}
}
}
pub fn lower_map_size_simple(
b: &mut dyn IRBuilder,
param_index: &HashMap<ValueId, usize>,
recv: &ValueId,
dst_is_some: bool,
) {
let use_bridge = std::env::var("NYASH_JIT_HOST_BRIDGE").ok().as_deref() == Some("1");
if let Some(pidx) = param_index.get(recv).copied() {
b.emit_param_i64(pidx);
let sym = if use_bridge {
crate::jit::r#extern::host_bridge::SYM_HOST_MAP_SIZE
} else {
crate::jit::r#extern::collections::SYM_MAP_SIZE_H
};
b.emit_host_call(sym, 1, dst_is_some);
}
}
pub fn lower_map_get_simple(
b: &mut dyn IRBuilder,
param_index: &HashMap<ValueId, usize>,
known_i64: &HashMap<ValueId, i64>,
recv: &ValueId,
key: &ValueId,
dst_is_some: bool,
) {
let use_bridge = std::env::var("NYASH_JIT_HOST_BRIDGE").ok().as_deref() == Some("1");
if let Some(pidx) = param_index.get(recv).copied() {
b.emit_param_i64(pidx);
if let Some(i) = known_i64.get(key).copied() {
b.emit_const_i64(i);
} else if let Some(kp) = param_index.get(key).copied() {
b.emit_param_i64(kp);
} else {
b.emit_const_i64(0);
}
let sym = if use_bridge {
crate::jit::r#extern::host_bridge::SYM_HOST_MAP_GET
} else {
crate::jit::r#extern::collections::SYM_MAP_GET_H
};
b.emit_host_call(sym, 2, dst_is_some);
}
}
pub fn lower_map_has_simple(
b: &mut dyn IRBuilder,
param_index: &HashMap<ValueId, usize>,
known_i64: &HashMap<ValueId, i64>,
recv: &ValueId,
key: &ValueId,
dst_is_some: bool,
) {
let use_bridge = std::env::var("NYASH_JIT_HOST_BRIDGE").ok().as_deref() == Some("1");
if let Some(pidx) = param_index.get(recv).copied() {
b.emit_param_i64(pidx);
if let Some(i) = known_i64.get(key).copied() {
b.emit_const_i64(i);
} else if let Some(kp) = param_index.get(key).copied() {
b.emit_param_i64(kp);
} else {
b.emit_const_i64(0);
}
let sym = if use_bridge {
crate::jit::r#extern::host_bridge::SYM_HOST_MAP_HAS
} else {
crate::jit::r#extern::collections::SYM_MAP_HAS_H
};
b.emit_host_call(sym, 2, dst_is_some);
}
}
pub fn lower_map_set_simple(
b: &mut dyn IRBuilder,
param_index: &HashMap<ValueId, usize>,
known_i64: &HashMap<ValueId, i64>,
recv: &ValueId,
key: &ValueId,
value: &ValueId,
) {
let use_bridge = std::env::var("NYASH_JIT_HOST_BRIDGE").ok().as_deref() == Some("1");
if let Some(pidx) = param_index.get(recv).copied() {
b.emit_param_i64(pidx);
if let Some(i) = known_i64.get(key).copied() {
b.emit_const_i64(i);
} else if let Some(kp) = param_index.get(key).copied() {
b.emit_param_i64(kp);
} else {
b.emit_const_i64(0);
}
if let Some(i) = known_i64.get(value).copied() {
b.emit_const_i64(i);
} else if let Some(vp) = param_index.get(value).copied() {
b.emit_param_i64(vp);
} else {
b.emit_const_i64(0);
}
let sym = if use_bridge {
crate::jit::r#extern::host_bridge::SYM_HOST_MAP_SET
} else {
crate::jit::r#extern::collections::SYM_MAP_SET_H
};
b.emit_host_call(sym, 3, false);
}
}
pub fn lower_array_set(
b: &mut dyn IRBuilder,
param_index: &HashMap<ValueId, usize>,
known_i64: &HashMap<ValueId, i64>,
array: &ValueId,
index: &ValueId,
value: &ValueId,
) {
if crate::jit::config::current().hostcall {
let use_bridge = std::env::var("NYASH_JIT_HOST_BRIDGE").ok().as_deref() == Some("1");
let idx = known_i64.get(index).copied().unwrap_or(0);
let val = known_i64.get(value).copied().unwrap_or(0);
if let Some(pidx) = param_index.get(array).copied() {
b.emit_param_i64(pidx);
b.emit_const_i64(idx);
b.emit_const_i64(val);
let sym = if use_bridge {
crate::jit::r#extern::host_bridge::SYM_HOST_ARRAY_SET
} else {
crate::jit::r#extern::collections::SYM_ARRAY_SET_H
};
b.emit_host_call(sym, 3, false);
} else {
let arr_idx = -1;
b.emit_const_i64(arr_idx);
b.emit_const_i64(idx);
b.emit_const_i64(val);
let sym = if use_bridge {
crate::jit::r#extern::host_bridge::SYM_HOST_ARRAY_SET
} else {
crate::jit::r#extern::collections::SYM_ARRAY_SET
};
b.emit_host_call(sym, 3, false);
}
}
}
pub fn lower_box_call(
func: &MirFunction,
b: &mut dyn IRBuilder,
param_index: &HashMap<ValueId, usize>,
known_i64: &HashMap<ValueId, i64>,
known_f64: &HashMap<ValueId, f64>,
float_box_values: &std::collections::HashSet<ValueId>,
recv: &ValueId,
method: &str,
args: &Vec<ValueId>,
dst: Option<ValueId>,
) {
if !crate::jit::config::current().hostcall {
return;
}
match method {
"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_ARRAY_LEN, "decision":"allow", "reason":"sig_ok", "argc":1, "arg_types":["I64(index)"]}),
"hostcall",
"<jit>",
);
// Pass parameter index directly (JIT thunks read legacy VM args by index)
b.emit_param_i64(pidx as i64 as usize);
b.emit_host_call(
crate::jit::r#extern::collections::SYM_ARRAY_LEN,
1,
dst.is_some(),
);
} else {
crate::jit::events::emit_lower(
serde_json::json!({
"id": crate::jit::r#extern::collections::SYM_ARRAY_LEN,
"decision": "fallback", "reason": "receiver_not_param",
"argc": 1, "arg_types": ["I64(index)"]
}),
"hostcall",
"<jit>",
);
b.emit_const_i64(-1);
b.emit_host_call(
crate::jit::r#extern::collections::SYM_ARRAY_LEN,
1,
dst.is_some(),
);
}
}
"isEmpty" | "is_empty" => {
crate::jit::events::emit_lower(
serde_json::json!({"id": crate::jit::r#extern::collections::SYM_ANY_IS_EMPTY_H, "decision":"allow", "reason":"sig_ok", "argc":1, "arg_types":["Handle"]}),
"hostcall",
"<jit>",
);
if let Some(pidx) = param_index.get(recv).copied() {
b.emit_param_i64(pidx);
b.emit_host_call(
crate::jit::r#extern::collections::SYM_ANY_IS_EMPTY_H,
1,
dst.is_some(),
);
}
}
// math.* family (read-only)
m if m.starts_with("math.") => {
let sym = format!("nyash.{}", m);
use crate::jit::hostcall_registry::{check_signature, ArgKind};
let mut observed: Vec<ArgKind> = Vec::new();
for (i, v) in args.iter().enumerate() {
let kind = if let Some(mt) = func.signature.params.get(i) {
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.push(kind);
}
let arg_types: Vec<&'static str> = observed
.iter()
.map(|k| match k {
ArgKind::I64 => "I64",
ArgKind::F64 => "F64",
ArgKind::Handle => "Handle",
})
.collect();
match check_signature(&sym, &observed) {
Ok(()) => {
crate::jit::events::emit_lower(
serde_json::json!({"id": sym, "decision":"allow", "reason":"sig_ok", "argc": observed.len(), "arg_types": arg_types}),
"hostcall",
"<jit>",
);
if crate::jit::config::current().native_f64 {
let (symbol, arity) = match method {
"math.sin" => ("nyash.math.sin_f64", 1),
"math.cos" => ("nyash.math.cos_f64", 1),
"math.abs" => ("nyash.math.abs_f64", 1),
"math.min" => ("nyash.math.min_f64", 2),
"math.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_lower(
serde_json::json!({"id": sym, "decision":"fallback", "reason": reason, "argc": observed.len(), "arg_types": arg_types}),
"hostcall",
"<jit>",
);
}
}
}
// Map/String/Array read methods and limited mutating (whitelist)
_ => {
// Whitelist-driven
let pol = crate::jit::policy::current();
match method {
// String
"charCodeAt" => {
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>",
);
// recvはHandle (param) を期待。indexはknown_i64でcoerce
if let Some(pidx) = param_index.get(recv).copied() {
b.emit_param_i64(pidx);
let idx = args
.get(0)
.and_then(|v| known_i64.get(v).copied())
.unwrap_or(0);
b.emit_const_i64(idx);
b.emit_host_call(
crate::jit::r#extern::collections::SYM_STRING_CHARCODE_AT_H,
2,
dst.is_some(),
);
}
}
// Map
"size" => {
lower_map_size_simple(b, param_index, recv, dst.is_some());
}
"get" => {
if let Some(k) = args.get(0) {
lower_map_get_simple(b, param_index, known_i64, recv, k, dst.is_some());
}
}
"has" => {
if let Some(k) = args.get(0) {
lower_map_has_simple(b, param_index, known_i64, recv, k, dst.is_some());
}
}
"set" => {
if args.len() >= 2 {
lower_map_set_simple(b, param_index, known_i64, recv, &args[0], &args[1]);
}
}
"has" => {
// Decide on key kind via registry and known values
use crate::jit::hostcall_registry::{check_signature, ArgKind};
let canonical = "nyash.map.has".to_string();
let mut observed_kinds: Vec<ArgKind> = Vec::new();
observed_kinds.push(ArgKind::Handle);
let key_vid = args.get(0).copied();
let key_kind = if let Some(kv) = key_vid {
if let Some(mt) = func.signature.params.iter().find(|_| true) {
// heuristic; signature may not align
match mt {
crate::mir::MirType::Float => ArgKind::I64,
crate::mir::MirType::Integer => ArgKind::I64,
crate::mir::MirType::Bool => ArgKind::I64,
crate::mir::MirType::String | crate::mir::MirType::Box(_) => {
ArgKind::Handle
}
_ => ArgKind::Handle,
}
} else if let Some(_) = known_i64.get(&kv) {
ArgKind::I64
} else {
ArgKind::Handle
}
} else {
ArgKind::Handle
};
observed_kinds.push(key_kind);
let arg_types: Vec<&'static str> = observed_kinds
.iter()
.map(|k| match k {
ArgKind::I64 => "I64",
ArgKind::F64 => "F64",
ArgKind::Handle => "Handle",
})
.collect();
let _ = check_signature(&canonical, &observed_kinds);
// HH fast-path if key is a Handle and also a param; otherwise H/I64
if let Some(pidx) = param_index.get(recv).copied() {
b.emit_param_i64(pidx);
if let Some(kv) = key_vid {
match key_kind {
ArgKind::I64 => {
let kval = known_i64.get(&kv).copied().unwrap_or(0);
b.emit_const_i64(kval);
b.emit_host_call(
crate::jit::r#extern::collections::SYM_MAP_GET_H,
2,
dst.is_some(),
);
}
ArgKind::Handle => {
if let Some(kp) = param_index.get(&kv).copied() {
b.emit_param_i64(kp);
b.emit_host_call(
crate::jit::r#extern::collections::SYM_MAP_GET_HH,
2,
dst.is_some(),
);
}
}
_ => {}
}
}
}
}
// Array (mutating)
"push" | "set" => {
let wh = &pol.hostcall_whitelist;
let sym = if method == "push" {
crate::jit::r#extern::collections::SYM_ARRAY_PUSH
} else {
crate::jit::r#extern::collections::SYM_ARRAY_SET
};
if wh.iter().any(|s| s == sym) {
crate::jit::events::emit_lower(
serde_json::json!({"id": sym, "decision":"allow", "reason":"whitelist", "argc": args.len()}),
"hostcall",
"<jit>",
);
b.emit_host_call(sym, 2, false);
} else {
crate::jit::events::emit_lower(
serde_json::json!({"id": sym, "decision":"fallback", "reason":"policy_denied_mutating", "argc": args.len()}),
"hostcall",
"<jit>",
);
}
}
_ => {}
}
}
}
}
// (was: lower_boxcall_simple_reads) Removed; logic consolidated in core.rs length/charCodeAt handlers.
// 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_lower(
serde_json::json!({"id": sym, "decision":"fallback", "reason": reason, "argc": observed_kinds.len(), "arg_types": arg_types}),
"hostcall",
"<jit>",
);
}
}
}

View File

@ -0,0 +1,306 @@
//! Core ops lowering (non-hostcall): BinOp, Compare, Branch, Jump
use super::builder::{BinOpKind, CmpKind, IRBuilder};
use crate::mir::{BinaryOp, CompareOp, MirFunction, MirType, ValueId};
use super::core::LowerCore;
impl LowerCore {
fn is_string_like(&self, func: &MirFunction, v: &ValueId) -> bool {
// Check per-value type metadata
if let Some(mt) = func.metadata.value_types.get(v) {
if matches!(mt, MirType::String) {
return true;
}
if let MirType::Box(ref name) = mt {
if name == "StringBox" {
return true;
}
}
}
// Check if this value is a parameter with String or StringBox type
if let Some(pidx) = self.param_index.get(v).copied() {
if let Some(pt) = func.signature.params.get(pidx) {
if matches!(pt, MirType::String) {
return true;
}
if let MirType::Box(ref name) = pt {
if name == "StringBox" {
return true;
}
}
}
}
// Check if it originates from a StringBox NewBox
if let Some(name) = self.box_type_map.get(v) {
if name == "StringBox" {
return true;
}
}
false
}
pub fn lower_binop(
&mut self,
b: &mut dyn IRBuilder,
op: &BinaryOp,
lhs: &ValueId,
rhs: &ValueId,
dst: &ValueId,
func: &MirFunction,
) {
// Optional: consult unified grammar for operator strategy (non-invasive logging)
if std::env::var("NYASH_GRAMMAR_DIFF").ok().as_deref() == Some("1") {
match op {
BinaryOp::Add => {
let strat = crate::grammar::engine::get().add_coercion_strategy();
crate::jit::events::emit(
"grammar",
"add",
None,
None,
serde_json::json!({"coercion": strat}),
);
}
BinaryOp::Sub => {
let strat = crate::grammar::engine::get().sub_coercion_strategy();
crate::jit::events::emit(
"grammar",
"sub",
None,
None,
serde_json::json!({"coercion": strat}),
);
}
BinaryOp::Mul => {
let strat = crate::grammar::engine::get().mul_coercion_strategy();
crate::jit::events::emit(
"grammar",
"mul",
None,
None,
serde_json::json!({"coercion": strat}),
);
}
BinaryOp::Div => {
let strat = crate::grammar::engine::get().div_coercion_strategy();
crate::jit::events::emit(
"grammar",
"div",
None,
None,
serde_json::json!({"coercion": strat}),
);
}
_ => {}
}
}
// Route string-like addition to hostcall (handle,handle)
if crate::jit::config::current().hostcall {
if matches!(op, BinaryOp::Add) {
if self.is_string_like(func, lhs) || self.is_string_like(func, rhs) {
self.push_value_if_known_or_param(b, lhs);
self.push_value_if_known_or_param(b, rhs);
b.emit_host_call(
crate::jit::r#extern::collections::SYM_STRING_CONCAT_HH,
2,
true,
);
// Track handle result for downstream usages
self.handle_values.insert(*dst);
let slot = *self.local_index.entry(*dst).or_insert_with(|| {
let id = self.next_local;
self.next_local += 1;
id
});
b.store_local_i64(slot);
return;
}
// If dynamic Box/Unknown types, route to unified semantics add (handle,handle)
let is_dynamic = match (
func.metadata.value_types.get(lhs),
func.metadata.value_types.get(rhs),
) {
(Some(MirType::Box(_)) | Some(MirType::Unknown) | None, _)
| (_, Some(MirType::Box(_)) | Some(MirType::Unknown) | None) => true,
_ => false,
};
if is_dynamic {
self.push_value_if_known_or_param(b, lhs);
self.push_value_if_known_or_param(b, rhs);
b.emit_host_call(
crate::jit::r#extern::collections::SYM_SEMANTICS_ADD_HH,
2,
true,
);
self.handle_values.insert(*dst);
let slot = *self.local_index.entry(*dst).or_insert_with(|| {
let id = self.next_local;
self.next_local += 1;
id
});
b.store_local_i64(slot);
return;
}
}
}
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,
func: &MirFunction,
) {
// Route string-like comparisons (Eq/Lt) to hostcalls (i64 0/1)
if crate::jit::config::current().hostcall {
if matches!(op, CompareOp::Eq | CompareOp::Lt) {
if self.is_string_like(func, lhs) || self.is_string_like(func, rhs) {
self.push_value_if_known_or_param(b, lhs);
self.push_value_if_known_or_param(b, rhs);
let sym = match op {
CompareOp::Eq => crate::jit::r#extern::collections::SYM_STRING_EQ_HH,
CompareOp::Lt => crate::jit::r#extern::collections::SYM_STRING_LT_HH,
_ => unreachable!(),
};
b.emit_host_call(sym, 2, true);
self.bool_values.insert(*dst);
return;
}
}
}
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);
// Persist compare result in a local slot so terminators (Branch) can reload it reliably
self.bool_values.insert(*dst);
let slot = *self.local_index.entry(*dst).or_insert_with(|| {
let id = self.next_local;
self.next_local += 1;
id
});
b.store_local_i64(slot);
}
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();
}
}
// Methods moved from core.rs to reduce file size and centralize op helpers
impl LowerCore {
// Push a value if known or param/local/phi
pub(super) fn push_value_if_known_or_param(&self, b: &mut dyn IRBuilder, id: &ValueId) {
// Prefer compile-time known constants to avoid stale local slots overshadowing folded values
if let Some(v) = self.known_i64.get(id).copied() {
b.emit_const_i64(v);
return;
}
if let Some(slot) = self.local_index.get(id).copied() {
b.load_local_i64(slot);
return;
}
if self.phi_values.contains(id) {
let pos = self
.phi_param_index
.iter()
.find_map(|((_, vid), idx)| if vid == id { Some(*idx) } else { None })
.unwrap_or(0);
if crate::jit::config::current().native_bool && self.bool_phi_values.contains(id) {
b.push_block_param_b1_at(pos);
} else {
b.push_block_param_i64_at(pos);
}
return;
}
if let Some(pidx) = self.param_index.get(id).copied() {
b.emit_param_i64(pidx);
return;
}
}
// Coverage helper: increments covered/unsupported counts
pub(super) fn cover_if_supported(&mut self, instr: &crate::mir::MirInstruction) {
use crate::mir::MirInstruction as I;
let supported = matches!(
instr,
I::Const { .. }
| I::Copy { .. }
| I::Cast { .. }
| I::TypeCheck { .. }
| I::TypeOp { .. }
| I::BinOp { .. }
| I::Compare { .. }
| I::Jump { .. }
| I::Branch { .. }
| I::Return { .. }
| I::Call { .. }
| I::BoxCall { .. }
| I::ArrayGet { .. }
| I::ArraySet { .. }
| I::NewBox { .. }
| I::Store { .. }
| I::Load { .. }
| I::Phi { .. }
| I::Debug { .. }
| I::ExternCall { .. }
| I::Safepoint
| I::Nop
| I::PluginInvoke { .. }
);
if supported {
self.covered += 1;
} else {
self.unsupported += 1;
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,7 @@
//! Lowering entry for JIT
pub mod builder;
pub mod cfg_dot;
pub mod core;
pub mod core_hostcall;
pub mod core_ops;
pub mod extern_thunks;

View File

@ -0,0 +1,319 @@
#[cfg(feature = "jit-direct-only")]
pub struct JitManager;
#[cfg(feature = "jit-direct-only")]
impl JitManager {
pub fn new(_threshold: u32) -> Self {
Self
}
pub fn set_threshold(&mut self, _t: u32) {}
pub fn record_entry(&mut self, _func: &str) {}
pub fn should_jit(&self, _func: &str) -> bool {
false
}
pub fn mark_compiled(&mut self, _func: &str, _handle: u64) {}
pub fn maybe_compile(&mut self, _func: &str, _mir: &crate::mir::MirFunction) -> bool {
false
}
pub fn is_compiled(&self, _func: &str) -> bool {
false
}
pub fn handle_of(&self, _func: &str) -> Option<u64> {
None
}
pub fn sites(&self) -> usize {
0
}
pub fn compiled_count(&self) -> usize {
0
}
pub fn total_hits(&self) -> u64 {
0
}
pub fn exec_ok_count(&self) -> u64 {
0
}
pub fn exec_trap_count(&self) -> u64 {
0
}
pub fn record_lower_stats(
&mut self,
_func: &str,
_phi_total: u64,
_phi_b1: u64,
_ret_bool_hint: bool,
) {
}
pub fn per_function_stats(&self) -> Vec<(String, u64, u64, u64, u32, bool, u64)> {
Vec::new()
}
pub fn top_hits(&self, _n: usize) -> Vec<(String, u32, bool, u64)> {
Vec::new()
}
pub fn print_summary(&self) {}
pub fn maybe_dispatch(&mut self, _func: &str, _argc: usize) -> bool {
false
}
pub fn execute_compiled(
&mut self,
_func: &str,
_ret_ty: &crate::mir::MirType,
_args: &[crate::backend::vm::VMValue],
) -> Option<crate::backend::vm::VMValue> {
None
}
}
#[cfg(not(feature = "jit-direct-only"))]
use std::collections::HashMap;
/// Minimal JIT manager skeleton for Phase 10_a
/// - Tracks per-function entry counts
/// - Decides when a function should be JIT-compiled (threshold)
/// - Records compiled functions for stats
#[cfg(not(feature = "jit-direct-only"))]
pub struct JitManager {
threshold: u32,
hits: HashMap<String, u32>,
compiled: HashMap<String, u64>,
engine: crate::jit::engine::JitEngine,
exec_ok: u64,
exec_trap: u64,
// Per-function lowering stats (accumulated)
func_phi_total: HashMap<String, u64>,
func_phi_b1: HashMap<String, u64>,
func_ret_bool_hint: HashMap<String, u64>,
}
#[cfg(not(feature = "jit-direct-only"))]
impl JitManager {
pub fn new(threshold: u32) -> Self {
Self {
threshold,
hits: HashMap::new(),
compiled: HashMap::new(),
engine: crate::jit::engine::JitEngine::new(),
exec_ok: 0,
exec_trap: 0,
func_phi_total: HashMap::new(),
func_phi_b1: HashMap::new(),
func_ret_bool_hint: HashMap::new(),
}
}
pub fn set_threshold(&mut self, t: u32) {
self.threshold = t.max(1);
}
pub fn record_entry(&mut self, func: &str) {
let c = self.hits.entry(func.to_string()).or_insert(0);
*c = c.saturating_add(1);
}
pub fn should_jit(&self, func: &str) -> bool {
let hot = self.hits.get(func).copied().unwrap_or(0) >= self.threshold;
hot && !self.compiled.contains_key(func)
}
pub fn mark_compiled(&mut self, func: &str, handle: u64) {
self.compiled.insert(func.to_string(), handle);
}
/// Ensure the function is compiled when hot; returns true if compiled now or already compiled
pub fn maybe_compile(&mut self, func: &str, mir: &crate::mir::MirFunction) -> bool {
if self.should_jit(func) {
if let Some(handle) = self.engine.compile_function(func, mir) {
self.mark_compiled(func, handle);
// Record per-function lower stats captured by engine
let (phi_t, phi_b1, ret_b) = self.engine.last_lower_stats();
self.record_lower_stats(func, phi_t, phi_b1, ret_b);
if std::env::var("NYASH_JIT_STATS").ok().as_deref() == Some("1") {
eprintln!("[JIT] compiled {} -> handle={}", func, handle);
}
return true;
}
}
self.compiled.contains_key(func)
}
pub fn is_compiled(&self, func: &str) -> bool {
self.compiled.contains_key(func)
}
pub fn handle_of(&self, func: &str) -> Option<u64> {
self.compiled.get(func).copied()
}
// --- Stats accessors for unified reporting ---
pub fn sites(&self) -> usize {
self.hits.len()
}
pub fn compiled_count(&self) -> usize {
self.compiled.len()
}
pub fn total_hits(&self) -> u64 {
self.hits.values().map(|v| *v as u64).sum()
}
pub fn exec_ok_count(&self) -> u64 {
self.exec_ok
}
pub fn exec_trap_count(&self) -> u64 {
self.exec_trap
}
// --- Per-function stats ---
pub fn record_lower_stats(
&mut self,
func: &str,
phi_total: u64,
phi_b1: u64,
ret_bool_hint: bool,
) {
if phi_total > 0 {
*self.func_phi_total.entry(func.to_string()).or_insert(0) += phi_total;
}
if phi_b1 > 0 {
*self.func_phi_b1.entry(func.to_string()).or_insert(0) += phi_b1;
}
if ret_bool_hint {
*self.func_ret_bool_hint.entry(func.to_string()).or_insert(0) += 1;
}
}
pub fn per_function_stats(&self) -> Vec<(String, u64, u64, u64, u32, bool, u64)> {
// name, phi_total, phi_b1, ret_bool_hint, hits, compiled, handle
let mut names: std::collections::BTreeSet<String> = std::collections::BTreeSet::new();
names.extend(self.hits.keys().cloned());
names.extend(self.func_phi_total.keys().cloned());
names.extend(self.func_phi_b1.keys().cloned());
names.extend(self.func_ret_bool_hint.keys().cloned());
let mut out = Vec::new();
for name in names {
let phi_t = self.func_phi_total.get(&name).copied().unwrap_or(0);
let phi_b1 = self.func_phi_b1.get(&name).copied().unwrap_or(0);
let rb = self.func_ret_bool_hint.get(&name).copied().unwrap_or(0);
let hits = self.hits.get(&name).copied().unwrap_or(0);
let compiled = self.compiled.contains_key(&name);
let handle = self.compiled.get(&name).copied().unwrap_or(0);
out.push((name, phi_t, phi_b1, rb, hits, compiled, handle));
}
out
}
/// Return top-N hot functions by hits, with compiled flag and handle
pub fn top_hits(&self, n: usize) -> Vec<(String, u32, bool, u64)> {
let mut v: Vec<(&String, &u32)> = self.hits.iter().collect();
v.sort_by(|a, b| b.1.cmp(a.1));
v.into_iter()
.take(n)
.map(|(k, h)| {
let compiled = self.compiled.contains_key(k);
let handle = self.compiled.get(k).copied().unwrap_or(0);
(k.clone(), *h, compiled, handle)
})
.collect()
}
pub fn print_summary(&self) {
if std::env::var("NYASH_JIT_STATS").ok().as_deref() != Some("1") {
return;
}
let sites = self.hits.len();
let total_hits: u64 = self.hits.values().map(|v| *v as u64).sum();
let compiled = self.compiled.len();
eprintln!(
"[JIT] sites={} compiled={} hits_total={} exec_ok={} exec_trap={}",
sites, compiled, total_hits, self.exec_ok, self.exec_trap
);
// Top 5 hot functions
let mut v: Vec<(&String, &u32)> = self.hits.iter().collect();
v.sort_by(|a, b| b.1.cmp(a.1));
for (i, (k, h)) in v.into_iter().take(5).enumerate() {
let comp = if self.compiled.contains_key(k) {
"*"
} else {
" "
};
let hdl = self.compiled.get(k).copied().unwrap_or(0);
eprintln!(" #{}{} {} hits={} handle={}", i + 1, comp, k, h, hdl);
}
}
/// Phase 10_c stub: attempt to dispatch to JIT if enabled; returns true if it would execute
pub fn maybe_dispatch(&mut self, func: &str, argc: usize) -> bool {
if std::env::var("NYASH_JIT_EXEC").ok().as_deref() == Some("1") {
if let Some(h) = self.handle_of(func) {
eprintln!(
"[JIT] executing handle={} argc={} (stub) for {}",
h, argc, func
);
// In 10_c proper, invoke engine with prepared args and return actual result
// For now, execute with empty args to exercise the path, ignore result
let _ = self.engine.execute_handle(h, &[]);
return false; // keep VM path active for now
}
}
false
}
/// 10_c: execute compiled function if present (stub: empty args). Returns Some(VMValue) if JIT path was taken.
pub fn execute_compiled(
&mut self,
func: &str,
ret_ty: &crate::mir::MirType,
args: &[crate::backend::vm::VMValue],
) -> Option<crate::backend::vm::VMValue> {
// Strict/FailFastモードではJITは"コンパイル専用"(実行しない)
if std::env::var("NYASH_JIT_STRICT").ok().as_deref() == Some("1") {
// 観測のためイベントだけ出す
crate::jit::events::emit_runtime(
serde_json::json!({
"id": "jit_skip_execute_strict",
"func": func
}),
"jit",
func,
);
return None;
}
if let Some(h) = self.handle_of(func) {
// Expose args to both legacy VM hostcalls and new JIT ABI TLS
crate::jit::rt::set_legacy_vm_args(args);
let jit_args = crate::jit::abi::adapter::to_jit_values(args);
crate::jit::rt::set_current_jit_args(&jit_args);
let t0 = std::time::Instant::now();
// Begin handle scope so temporary handles are reclaimed after the call
crate::jit::rt::handles::begin_scope();
let out = self.engine.execute_handle(h, &jit_args);
if std::env::var("NYASH_JIT_STATS").ok().as_deref() == Some("1") {
let dt = t0.elapsed();
eprintln!("[JIT] exec_time_ms={} for {}", dt.as_millis(), func);
}
let res = match out {
Some(v) => {
self.exec_ok = self.exec_ok.saturating_add(1);
// Use CallBoundaryBox to convert JitValue → VMValue with MIR ret type hint
let vmv = crate::jit::boundary::CallBoundaryBox::to_vm(ret_ty, v);
Some(vmv)
}
None => {
self.exec_trap = self.exec_trap.saturating_add(1);
// Emit a minimal trap event for observability (runtime only)
let dt = t0.elapsed();
crate::jit::events::emit_runtime(
serde_json::json!({
"kind": "trap", // redundant with wrapper kind but explicit here for clarity
"reason": "jit_execute_failed",
"ms": dt.as_millis()
}),
"trap",
func,
);
None
}
};
// Clear handles created during this call
crate::jit::rt::handles::end_scope_clear();
return res;
}
None
}
}

View File

@ -0,0 +1,16 @@
//! JIT subsystem: Cranelift-based JIT manager and lowering stubs
pub mod abi;
pub mod boundary;
pub mod config;
pub mod engine;
pub mod events;
pub mod r#extern;
pub mod hostcall_registry;
pub mod lower;
pub mod manager;
pub mod observe;
pub mod policy;
pub mod rt;
pub mod semantics;
pub mod shim_trace;

View File

@ -0,0 +1,55 @@
//! Observe facade: centralize compile/runtime/trace output rules.
//! Thin wrappers around jit::events and shim_trace to keep callsites tidy.
pub fn lower_plugin_invoke(
box_type: &str,
method: &str,
type_id: u32,
method_id: u32,
argc: usize,
) {
crate::jit::events::emit_lower(
serde_json::json!({
"id": format!("plugin:{}:{}", box_type, method),
"decision":"allow","reason":"plugin_invoke","argc": argc,
"type_id": type_id, "method_id": method_id
}),
"plugin",
"<jit>",
);
}
pub fn lower_hostcall(symbol: &str, argc: usize, arg_types: &[&str], decision: &str, reason: &str) {
crate::jit::events::emit_lower(
serde_json::json!({
"id": symbol,
"decision": decision,
"reason": reason,
"argc": argc,
"arg_types": arg_types
}),
"hostcall",
"<jit>",
);
}
pub fn runtime_plugin_shim_i64(type_id: i64, method_id: i64, argc: i64, inst: u32) {
crate::jit::events::emit_runtime(
serde_json::json!({
"id": "plugin_invoke.i64",
"type_id": type_id,
"method_id": method_id,
"argc": argc,
"inst": inst
}),
"plugin",
"<jit>",
);
}
pub fn trace_push(msg: String) {
crate::jit::shim_trace::push(msg);
}
pub fn trace_enabled() -> bool {
crate::jit::shim_trace::is_enabled()
}

View File

@ -0,0 +1,58 @@
//! JIT Policy (Box-First): centralizes runtime decisions
//!
//! Minimal v0:
//! - read_only: if true, deny write-effects in jit-direct and other independent paths
//! - hostcall_whitelist: symbolic names allowed (future use)
use once_cell::sync::OnceCell;
use std::sync::RwLock;
#[derive(Debug, Clone, Default)]
pub struct JitPolicy {
pub read_only: bool,
pub hostcall_whitelist: Vec<String>,
}
impl JitPolicy {
pub fn from_env() -> Self {
let ro = std::env::var("NYASH_JIT_READ_ONLY").ok().as_deref() == Some("1");
// Comma-separated hostcall names
let hc = std::env::var("NYASH_JIT_HOSTCALL_WHITELIST")
.ok()
.map(|s| {
s.split(',')
.map(|t| t.trim().to_string())
.filter(|s| !s.is_empty())
.collect::<Vec<_>>()
})
.unwrap_or_default();
Self {
read_only: ro,
hostcall_whitelist: hc,
}
}
}
static GLOBAL: OnceCell<RwLock<JitPolicy>> = OnceCell::new();
pub fn current() -> JitPolicy {
if let Some(l) = GLOBAL.get() {
if let Ok(g) = l.read() {
return g.clone();
}
}
JitPolicy::from_env()
}
pub fn set_current(p: JitPolicy) {
if let Some(l) = GLOBAL.get() {
if let Ok(mut w) = l.write() {
*w = p;
return;
}
}
let _ = GLOBAL.set(RwLock::new(p));
}
// Submodule: invoke decision policy
pub mod invoke;

View File

@ -0,0 +1,90 @@
//! InvokePolicyPass (minimal scaffold)
//! Centralizes decision for plugin/hostcall to keep lowerer slim.
//! HostCall優先Core-13方針。ENV `NYASH_USE_PLUGIN_BUILTINS=1` の場合のみ
//! plugin_invoke を試し、解決できない場合はHostCallへフォールバックする。
#[derive(Debug, Clone)]
pub enum InvokeDecision {
PluginInvoke {
type_id: u32,
method_id: u32,
box_type: String,
method: String,
argc: usize,
has_ret: bool,
},
HostCall {
symbol: String,
argc: usize,
has_ret: bool,
reason: &'static str,
},
Fallback {
reason: &'static str,
},
}
fn use_plugin_builtins() -> bool {
#[cfg(feature = "jit-direct-only")]
{
return false;
}
#[cfg(not(feature = "jit-direct-only"))]
{
return std::env::var("NYASH_USE_PLUGIN_BUILTINS").ok().as_deref() == Some("1");
}
}
/// Decide invocation policy for a known Box method.
pub fn decide_box_method(
box_type: &str,
method: &str,
argc: usize,
has_ret: bool,
) -> InvokeDecision {
// HostCall mapping for common collections/strings/instance ops
let symbol = match (box_type, method) {
("ArrayBox", "length") => crate::jit::r#extern::collections::SYM_ANY_LEN_H,
("StringBox", "length") | ("StringBox", "len") => "nyash.string.len_h",
("ArrayBox", "get") => crate::jit::r#extern::collections::SYM_ARRAY_GET_H,
("ArrayBox", "set") => crate::jit::r#extern::collections::SYM_ARRAY_SET_H,
("ArrayBox", "push") => crate::jit::r#extern::collections::SYM_ARRAY_PUSH_H,
("MapBox", "size") => crate::jit::r#extern::collections::SYM_MAP_SIZE_H,
("MapBox", "get") => crate::jit::r#extern::collections::SYM_MAP_GET_HH,
("MapBox", "has") => crate::jit::r#extern::collections::SYM_MAP_HAS_H,
("MapBox", "set") => crate::jit::r#extern::collections::SYM_MAP_SET_H,
("StringBox", "is_empty") => crate::jit::r#extern::collections::SYM_ANY_IS_EMPTY_H,
("StringBox", "charCodeAt") => crate::jit::r#extern::collections::SYM_STRING_CHARCODE_AT_H,
_ => "", // unknown
};
// Prefer HostCall when available
if !symbol.is_empty() {
InvokeDecision::HostCall {
symbol: symbol.to_string(),
argc,
has_ret,
reason: "mapped_symbol",
}
} else if use_plugin_builtins() {
// Try plugin_invoke as a secondary path when enabled
if let Ok(ph) = crate::runtime::plugin_loader_unified::get_global_plugin_host().read() {
if let Ok(h) = ph.resolve_method(box_type, method) {
return InvokeDecision::PluginInvoke {
type_id: h.type_id,
method_id: h.method_id,
box_type: h.box_type,
method: method.to_string(),
argc,
has_ret,
};
}
}
InvokeDecision::Fallback {
reason: "unknown_method",
}
} else {
InvokeDecision::Fallback {
reason: "unknown_method",
}
}
}

View File

@ -0,0 +1,210 @@
use std::cell::RefCell;
use crate::backend::vm::VMValue;
use crate::jit::abi::JitValue;
// Legacy TLS for hostcalls that still expect VMValue — keep for compatibility
// Legacy VM args TLS — disabled in jit-direct-only (no-op/empty)
#[cfg(not(feature = "jit-direct-only"))]
thread_local! { static LEGACY_VM_ARGS: RefCell<Vec<VMValue>> = RefCell::new(Vec::new()); }
#[cfg(not(feature = "jit-direct-only"))]
pub fn set_legacy_vm_args(args: &[VMValue]) {
LEGACY_VM_ARGS.with(|cell| {
let mut v = cell.borrow_mut();
v.clear();
v.extend_from_slice(args);
});
}
#[cfg(feature = "jit-direct-only")]
pub fn set_legacy_vm_args(_args: &[VMValue]) { /* no-op in jit-direct-only */
}
#[cfg(not(feature = "jit-direct-only"))]
pub fn with_legacy_vm_args<F, R>(f: F) -> R
where
F: FnOnce(&[VMValue]) -> R,
{
LEGACY_VM_ARGS.with(|cell| {
let v = cell.borrow();
f(&v)
})
}
#[cfg(feature = "jit-direct-only")]
pub fn with_legacy_vm_args<F, R>(f: F) -> R
where
F: FnOnce(&[VMValue]) -> R,
{
f(&[])
}
// New TLS for independent JIT ABI values
thread_local! {
static CURRENT_JIT_ARGS: RefCell<Vec<JitValue>> = RefCell::new(Vec::new());
}
pub fn set_current_jit_args(args: &[JitValue]) {
CURRENT_JIT_ARGS.with(|cell| {
let mut v = cell.borrow_mut();
v.clear();
v.extend_from_slice(args);
});
}
pub fn with_jit_args<F, R>(f: F) -> R
where
F: FnOnce(&[JitValue]) -> R,
{
CURRENT_JIT_ARGS.with(|cell| {
let v = cell.borrow();
f(&v)
})
}
// === JIT runtime counters (minimal) ===
use std::sync::atomic::{AtomicU64, Ordering};
static B1_NORM_COUNT: AtomicU64 = AtomicU64::new(0);
static RET_BOOL_HINT_COUNT: AtomicU64 = AtomicU64::new(0);
static PHI_TOTAL_SLOTS: AtomicU64 = AtomicU64::new(0);
static PHI_B1_SLOTS: AtomicU64 = AtomicU64::new(0);
pub fn b1_norm_inc(delta: u64) {
B1_NORM_COUNT.fetch_add(delta, Ordering::Relaxed);
}
pub fn b1_norm_get() -> u64 {
B1_NORM_COUNT.load(Ordering::Relaxed)
}
pub fn ret_bool_hint_inc(delta: u64) {
RET_BOOL_HINT_COUNT.fetch_add(delta, Ordering::Relaxed);
}
pub fn ret_bool_hint_get() -> u64 {
RET_BOOL_HINT_COUNT.load(Ordering::Relaxed)
}
pub fn phi_total_inc(delta: u64) {
PHI_TOTAL_SLOTS.fetch_add(delta, Ordering::Relaxed);
}
pub fn phi_total_get() -> u64 {
PHI_TOTAL_SLOTS.load(Ordering::Relaxed)
}
pub fn phi_b1_inc(delta: u64) {
PHI_B1_SLOTS.fetch_add(delta, Ordering::Relaxed);
}
pub fn phi_b1_get() -> u64 {
PHI_B1_SLOTS.load(Ordering::Relaxed)
}
// === 10.7c PoC: JIT Handle Registry (thread-local) ===
use std::collections::HashMap;
use std::sync::Arc;
pub mod handles {
use super::*;
thread_local! {
static REG: RefCell<HandleRegistry> = RefCell::new(HandleRegistry::new());
static CREATED: RefCell<Vec<u64>> = RefCell::new(Vec::new());
static SCOPES: RefCell<Vec<usize>> = RefCell::new(Vec::new());
}
struct HandleRegistry {
next: u64,
map: HashMap<u64, Arc<dyn crate::box_trait::NyashBox>>, // BoxRef-compatible
}
impl HandleRegistry {
fn new() -> Self {
Self {
next: 1,
map: HashMap::new(),
}
}
fn to_handle(&mut self, obj: Arc<dyn crate::box_trait::NyashBox>) -> u64 {
// Reuse existing handle if already present (pointer equality check)
// For PoC simplicity, always assign new handle
let h = self.next;
self.next = self.next.saturating_add(1);
self.map.insert(h, obj);
if std::env::var("NYASH_JIT_HANDLE_DEBUG").ok().as_deref() == Some("1") {
eprintln!("[JIT][handle] new h={}", h);
}
h
}
fn get(&self, h: u64) -> Option<Arc<dyn crate::box_trait::NyashBox>> {
self.map.get(&h).cloned()
}
#[allow(dead_code)]
fn drop_handle(&mut self, h: u64) {
self.map.remove(&h);
}
#[allow(dead_code)]
fn clear(&mut self) {
self.map.clear();
self.next = 1;
}
}
pub fn to_handle(obj: Arc<dyn crate::box_trait::NyashBox>) -> u64 {
let h = REG.with(|cell| cell.borrow_mut().to_handle(obj));
CREATED.with(|c| c.borrow_mut().push(h));
h
}
pub fn get(h: u64) -> Option<Arc<dyn crate::box_trait::NyashBox>> {
REG.with(|cell| cell.borrow().get(h))
}
#[allow(dead_code)]
pub fn clear() {
REG.with(|cell| cell.borrow_mut().clear());
}
pub fn len() -> usize {
REG.with(|cell| cell.borrow().map.len())
}
/// Tally handles by NyashBox type name (best-effort)
pub fn type_tally() -> Vec<(String, usize)> {
use std::collections::HashMap;
REG.with(|cell| {
let reg = cell.borrow();
let mut map: HashMap<String, usize> = HashMap::new();
for (_h, obj) in reg.map.iter() {
let tn = obj.type_name().to_string();
*map.entry(tn).or_insert(0) += 1;
}
let mut v: Vec<(String, usize)> = map.into_iter().collect();
v.sort_by(|a, b| b.1.cmp(&a.1).then_with(|| a.0.cmp(&b.0)));
v
})
}
/// Snapshot current handle objects (Arc clones)
pub fn snapshot_arcs() -> Vec<Arc<dyn crate::box_trait::NyashBox>> {
REG.with(|cell| {
let reg = cell.borrow();
reg.map.values().cloned().collect()
})
}
// Scope management: track and clear handles created within a JIT call
pub fn begin_scope() {
CREATED.with(|c| {
let cur_len = c.borrow().len();
SCOPES.with(|s| s.borrow_mut().push(cur_len));
});
}
pub fn end_scope_clear() {
let start = SCOPES.with(|s| s.borrow_mut().pop()).unwrap_or(0);
let to_drop: Vec<u64> = CREATED.with(|c| {
let mut v = c.borrow_mut();
let slice = v[start..].to_vec();
v.truncate(start);
slice
});
REG.with(|cell| {
let mut reg = cell.borrow_mut();
for h in to_drop {
reg.map.remove(&h);
}
});
}
}

View File

@ -0,0 +1,172 @@
use std::collections::HashMap;
use super::Semantics;
use crate::backend::vm::VMValue;
use crate::mir::{BasicBlockId, ValueId};
/// Minimal Semantics for Cranelift skeleton (Const/Add/Return)
pub struct ClifSemanticsSkeleton {
pub mem: HashMap<ValueId, VMValue>,
pub ret_val: Option<VMValue>,
}
impl ClifSemanticsSkeleton {
pub fn new() -> Self {
Self {
mem: HashMap::new(),
ret_val: None,
}
}
}
impl Semantics for ClifSemanticsSkeleton {
type Val = VMValue;
type Ptr = ValueId;
type BB = BasicBlockId;
// Constants
fn const_i64(&mut self, v: i64) -> Self::Val {
VMValue::Integer(v)
}
fn const_f64(&mut self, v: f64) -> Self::Val {
VMValue::Float(v)
}
fn const_bool(&mut self, v: bool) -> Self::Val {
VMValue::Bool(v)
}
fn const_null(&mut self) -> Self::Val {
VMValue::Void
}
fn const_str(&mut self, s: &str) -> Self::Val {
VMValue::String(s.to_string())
}
// Unary/Binary/Compare (minimal)
fn neg(&mut self, x: Self::Val) -> Self::Val {
match x {
VMValue::Integer(i) => VMValue::Integer(-i),
VMValue::Float(f) => VMValue::Float(-f),
v => v,
}
}
fn not(&mut self, x: Self::Val) -> Self::Val {
VMValue::Bool(matches!(
x,
VMValue::Bool(false) | VMValue::Integer(0) | VMValue::Void
))
}
fn bit_not(&mut self, x: Self::Val) -> Self::Val {
if let VMValue::Integer(i) = x {
VMValue::Integer(!i)
} else {
x
}
}
fn add(&mut self, a: Self::Val, b: Self::Val) -> Self::Val {
use VMValue as V;
match (a, b) {
(V::Integer(x), V::Integer(y)) => V::Integer(x + y),
(V::Float(x), V::Float(y)) => V::Float(x + y),
(V::Float(x), V::Integer(y)) => V::Float(x + y as f64),
(V::Integer(x), V::Float(y)) => V::Float(x as f64 + y),
(V::String(s), V::String(t)) => V::String(format!("{}{}", s, t)),
(V::String(s), V::Integer(y)) => V::String(format!("{}{}", s, y)),
(V::Integer(x), V::String(t)) => V::String(format!("{}{}", x, t)),
(l, r) => {
let s = format!("{}{}", l.to_string(), r.to_string());
V::String(s)
}
}
}
fn sub(&mut self, a: Self::Val, b: Self::Val) -> Self::Val {
match (a, b) {
(VMValue::Integer(x), VMValue::Integer(y)) => VMValue::Integer(x - y),
_ => VMValue::Void,
}
}
fn mul(&mut self, a: Self::Val, b: Self::Val) -> Self::Val {
match (a, b) {
(VMValue::Integer(x), VMValue::Integer(y)) => VMValue::Integer(x * y),
_ => VMValue::Void,
}
}
fn div(&mut self, a: Self::Val, b: Self::Val) -> Self::Val {
match (a, b) {
(VMValue::Integer(x), VMValue::Integer(y)) if y != 0 => VMValue::Integer(x / y),
_ => VMValue::Void,
}
}
fn modulo(&mut self, a: Self::Val, b: Self::Val) -> Self::Val {
match (a, b) {
(VMValue::Integer(x), VMValue::Integer(y)) if y != 0 => VMValue::Integer(x % y),
_ => VMValue::Void,
}
}
fn cmp_eq(&mut self, a: Self::Val, b: Self::Val) -> Self::Val {
VMValue::Bool(a.to_string() == b.to_string())
}
fn cmp_ne(&mut self, a: Self::Val, b: Self::Val) -> Self::Val {
VMValue::Bool(a.to_string() != b.to_string())
}
fn cmp_lt(&mut self, a: Self::Val, b: Self::Val) -> Self::Val {
match (a, b) {
(VMValue::Integer(x), VMValue::Integer(y)) => VMValue::Bool(x < y),
_ => VMValue::Bool(false),
}
}
fn cmp_le(&mut self, a: Self::Val, b: Self::Val) -> Self::Val {
match (a, b) {
(VMValue::Integer(x), VMValue::Integer(y)) => VMValue::Bool(x <= y),
_ => VMValue::Bool(false),
}
}
fn cmp_gt(&mut self, a: Self::Val, b: Self::Val) -> Self::Val {
match (a, b) {
(VMValue::Integer(x), VMValue::Integer(y)) => VMValue::Bool(x > y),
_ => VMValue::Bool(false),
}
}
fn cmp_ge(&mut self, a: Self::Val, b: Self::Val) -> Self::Val {
match (a, b) {
(VMValue::Integer(x), VMValue::Integer(y)) => VMValue::Bool(x >= y),
_ => VMValue::Bool(false),
}
}
// Memory & control (minimal)
fn alloca_ptr(&mut self, vid: ValueId) -> Self::Ptr {
vid
}
fn load(&mut self, ptr: &Self::Ptr) -> Self::Val {
self.mem.get(ptr).cloned().unwrap_or(VMValue::Void)
}
fn store(&mut self, ptr: &Self::Ptr, v: Self::Val) {
self.mem.insert(*ptr, v);
}
fn jump(&mut self, _target: BasicBlockId) {}
fn branch(&mut self, _cond: Self::Val, _then_bb: BasicBlockId, _else_bb: BasicBlockId) {}
fn phi_select(&mut self, _incoming: &[(BasicBlockId, Self::Val)]) -> Self::Val {
VMValue::Void
}
fn ret(&mut self, v: Option<Self::Val>) {
self.ret_val = v;
}
// Host/Box calls (unimplemented in skeleton)
fn new_box(&mut self, _type_id: i64, _args: &[Self::Val]) -> Self::Val {
VMValue::Void
}
fn box_call_tagged(
&mut self,
_type_id: i64,
_method_id: i64,
_recv: Self::Val,
_argv: &[Self::Val],
_tags: &[i64],
) -> Self::Val {
VMValue::Void
}
fn extern_call(&mut self, _iface: &str, _method: &str, _args: &[Self::Val]) -> Self::Val {
VMValue::Void
}
}

View File

@ -0,0 +1,6 @@
/*!
* JIT Semantics - Skeleton implementations used by Cranelift path
*/
pub mod clif;
pub use crate::semantics::Semantics; // re-export trait for local use

View File

@ -0,0 +1,53 @@
use once_cell::sync::Lazy;
use std::collections::VecDeque;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Mutex;
static TRACE_ENABLED: AtomicBool = AtomicBool::new(false);
static EVENTS: Lazy<Mutex<VecDeque<String>>> =
Lazy::new(|| Mutex::new(VecDeque::with_capacity(256)));
const MAX_EVENTS: usize = 256;
pub fn set_enabled(on: bool) {
TRACE_ENABLED.store(on, Ordering::Relaxed);
}
pub fn is_enabled() -> bool {
if TRACE_ENABLED.load(Ordering::Relaxed) {
return true;
}
std::env::var("NYASH_JIT_SHIM_TRACE").ok().as_deref() == Some("1")
}
pub fn push(event: String) {
if !is_enabled() {
return;
}
if let Ok(mut q) = EVENTS.lock() {
if q.len() >= MAX_EVENTS {
q.pop_front();
}
q.push_back(event);
}
}
pub fn snapshot_joined() -> String {
if let Ok(q) = EVENTS.lock() {
let mut out = String::new();
for (i, e) in q.iter().enumerate() {
if i > 0 {
out.push('\n');
}
out.push_str(e);
}
out
} else {
String::new()
}
}
pub fn clear() {
if let Ok(mut q) = EVENTS.lock() {
q.clear();
}
}

View File

@ -0,0 +1,124 @@
use super::Semantics;
use crate::jit::lower::builder::{BinOpKind, CmpKind, IRBuilder};
use crate::mir::{BasicBlockId, ValueId};
/// Adapter that translates Semantics operations into IRBuilder calls (Cranelift path)
pub struct ClifSemanticsAdapter<'a> {
pub builder: &'a mut dyn IRBuilder,
}
impl<'a> ClifSemanticsAdapter<'a> {
pub fn new(builder: &'a mut dyn IRBuilder) -> Self {
Self { builder }
}
}
impl<'a> Semantics for ClifSemanticsAdapter<'a> {
type Val = ();
type Ptr = ValueId;
type BB = BasicBlockId;
fn const_i64(&mut self, v: i64) -> Self::Val {
self.builder.emit_const_i64(v);
}
fn const_f64(&mut self, v: f64) -> Self::Val {
self.builder.emit_const_f64(v);
}
fn const_bool(&mut self, v: bool) -> Self::Val {
self.builder.emit_const_i64(if v { 1 } else { 0 });
}
fn const_null(&mut self) -> Self::Val {
self.builder.emit_const_i64(0);
}
fn const_str(&mut self, _s: &str) -> Self::Val {
self.builder.emit_const_i64(0);
}
fn neg(&mut self, _x: Self::Val) -> Self::Val {
self.builder.emit_binop(BinOpKind::Sub);
}
fn not(&mut self, _x: Self::Val) -> Self::Val { /* handled via compare/select in LowerCore */
}
fn bit_not(&mut self, _x: Self::Val) -> Self::Val { /* not used here */
}
fn add(&mut self, _a: Self::Val, _b: Self::Val) -> Self::Val {
self.builder.emit_binop(BinOpKind::Add);
}
fn sub(&mut self, _a: Self::Val, _b: Self::Val) -> Self::Val {
self.builder.emit_binop(BinOpKind::Sub);
}
fn mul(&mut self, _a: Self::Val, _b: Self::Val) -> Self::Val {
self.builder.emit_binop(BinOpKind::Mul);
}
fn div(&mut self, _a: Self::Val, _b: Self::Val) -> Self::Val {
self.builder.emit_binop(BinOpKind::Div);
}
fn modulo(&mut self, _a: Self::Val, _b: Self::Val) -> Self::Val {
self.builder.emit_binop(BinOpKind::Mod);
}
fn cmp_eq(&mut self, _a: Self::Val, _b: Self::Val) -> Self::Val {
self.builder.emit_compare(CmpKind::Eq);
}
fn cmp_ne(&mut self, _a: Self::Val, _b: Self::Val) -> Self::Val {
self.builder.emit_compare(CmpKind::Ne);
}
fn cmp_lt(&mut self, _a: Self::Val, _b: Self::Val) -> Self::Val {
self.builder.emit_compare(CmpKind::Lt);
}
fn cmp_le(&mut self, _a: Self::Val, _b: Self::Val) -> Self::Val {
self.builder.emit_compare(CmpKind::Le);
}
fn cmp_gt(&mut self, _a: Self::Val, _b: Self::Val) -> Self::Val {
self.builder.emit_compare(CmpKind::Gt);
}
fn cmp_ge(&mut self, _a: Self::Val, _b: Self::Val) -> Self::Val {
self.builder.emit_compare(CmpKind::Ge);
}
fn alloca_ptr(&mut self, _vid: ValueId) -> Self::Ptr {
_vid
}
fn load(&mut self, _ptr: &Self::Ptr) -> Self::Val {
self.builder.load_local_i64(_ptr.as_u32() as usize);
}
fn store(&mut self, _ptr: &Self::Ptr, _v: Self::Val) {
self.builder.store_local_i64(_ptr.as_u32() as usize);
}
fn jump(&mut self, _target: BasicBlockId) { /* handled by LowerCore */
}
fn branch(&mut self, _cond: Self::Val, _then_bb: BasicBlockId, _else_bb: BasicBlockId) {
/* handled by LowerCore */
}
fn phi_select(&mut self, _incoming: &[(BasicBlockId, Self::Val)]) -> Self::Val {
()
}
fn ret(&mut self, _v: Option<Self::Val>) {
self.builder.emit_return();
}
fn new_box(&mut self, _type_id: i64, _args: &[Self::Val]) -> Self::Val {
()
}
fn box_call_tagged(
&mut self,
_type_id: i64,
_method_id: i64,
_recv: Self::Val,
_argv: &[Self::Val],
_tags: &[i64],
) -> Self::Val {
()
}
fn extern_call(&mut self, _iface: &str, _method: &str, _args: &[Self::Val]) -> Self::Val {
()
}
fn barrier_read(&mut self, v: Self::Val) -> Self::Val {
v
}
fn barrier_write(&mut self, _ptr: &Self::Ptr, v: Self::Val) -> Self::Val {
v
}
fn safepoint(&mut self) { /* Lowered via explicit hostcall in LowerCore path */
}
}

View File

@ -0,0 +1,20 @@
#!/usr/bin/env bash
set -euo pipefail
ROOT_DIR=$(cd "$(dirname "$0")/.." && pwd)
cd "$ROOT_DIR"
echo "[build] nyash (cranelift-jit)"
cargo build --release --features cranelift-jit
run_case() {
local app="$1"
echo "[run] jit-direct: $app"
NYASH_JIT_THRESHOLD=1 ./target/release/nyash --jit-direct "$app"
}
run_case apps/tests/mir-branch-ret/main.nyash
run_case apps/tests/mir-compare-multi/main.nyash
echo "[ok] jit compare smokes completed"

View File

@ -0,0 +1,20 @@
#!/usr/bin/env bash
# Archived: JIT smoke (not maintained in current phase). Kept for reference.
set -euo pipefail
SCRIPT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
ROOT_DIR=$(CDPATH= cd -- "$SCRIPT_DIR/../../" && pwd)
BIN="$ROOT_DIR/target/release/nyash"
if [ ! -x "$BIN" ]; then
echo "Building nyash (release, JIT)..." >&2
cargo build --release --features cranelift-jit >/dev/null
fi
echo "[JIT Smoke] Core VM/JIT (plugins disabled)" >&2
NYASH_DISABLE_PLUGINS=1 NYASH_CLI_VERBOSE=1 "$ROOT_DIR/tools/smokes/archive/smoke_vm_jit.sh" >/tmp/nyash-jit-core.out
grep -q '^✅ smoke done' /tmp/nyash-jit-core.out || { echo "FAIL: core VM/JIT smoke" >&2; cat /tmp/nyash-jit-core.out; exit 1; }
echo "PASS: core VM/JIT smoke" >&2
echo "All PASS (archived JIT smoke)" >&2