Phase 12 TypeBox統合ABI設計完了: C ABI + Nyash ABI革命的統合

主な成果:
- TypeBox(型情報をBoxとして扱う)による統合ABI設計
- C ABI + Nyash ABIの完全統合仕様書作成
- 3大AI専門家(Gemini/Codex/ChatGPT5)による検証済み
- ChatGPT5の10個の安全性改善提案を反映
- README.mdのドキュメント更新(全起点から到達可能)

MapBox拡張:
- string型キーサポート(従来のi64に加えて)
- remove/clear/getOr/keysStr/valuesStr/toJson実装
- keys()/values()のランタイムシムサポート(TypeBox待ち)

その他の改善:
- Phase 11.9(文法統一化)ドキュメント追加
- Phase 16(FoldLang)ドキュメント追加
- 非同期タイムアウトテスト追加
- 各種ビルド警告(未使用import等)は次のリファクタリングで対応予定

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

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Moe Charm
2025-09-02 09:26:09 +09:00
parent da96bcb906
commit de99b40bee
40 changed files with 4017 additions and 1014 deletions

View File

@ -112,6 +112,9 @@ impl NyashInterpreter {
discard_context: false,
};
// Bind runtime services (GC/scheduler/token scope) to global hooks for async/safepoints
crate::runtime::global_hooks::set_from_runtime(&this.runtime);
// Register MethodBox invoker once (idempotent)
self::register_methodbox_invoker();
@ -149,6 +152,8 @@ impl NyashInterpreter {
runtime,
discard_context: false,
};
// Bind runtime services (GC/scheduler/token scope) to global hooks for async/safepoints
crate::runtime::global_hooks::set_from_runtime(&this.runtime);
self::register_methodbox_invoker();
this
}

View File

@ -61,18 +61,27 @@ impl NyashInterpreter {
// Main static boxを初期化
self.ensure_static_box_initialized("Main")?;
// Main.main() を呼び出し
// Main.main(args?) を呼び出し引数が1つなら空配列をデフォルト注入
let mut default_args: Vec<ASTNode> = Vec::new();
if let Ok(defs) = self.shared.static_box_definitions.read() {
if let Some(main_def) = defs.get("Main") {
if let Some(m) = main_def.methods.get("main") {
if let ASTNode::FunctionDeclaration { params, .. } = m {
if params.len() == 1 {
default_args.push(ASTNode::New { class: "ArrayBox".to_string(), arguments: vec![], type_arguments: vec![], span: crate::ast::Span::unknown() });
}
}
}
}
}
let main_call_ast = ASTNode::MethodCall {
object: Box::new(ASTNode::FieldAccess {
object: Box::new(ASTNode::Variable {
name: "statics".to_string(),
span: crate::ast::Span::unknown(),
}),
object: Box::new(ASTNode::Variable { name: "statics".to_string(), span: crate::ast::Span::unknown() }),
field: "Main".to_string(),
span: crate::ast::Span::unknown(),
}),
method: "main".to_string(),
arguments: vec![],
arguments: default_args,
span: crate::ast::Span::unknown(),
};
@ -86,4 +95,3 @@ impl NyashInterpreter {
}
}
}

View File

@ -219,33 +219,50 @@ impl NyashInterpreter {
/// nowait文を実行 - 非同期実行(真の非同期実装) - Async execution
pub(super) fn execute_nowait(&mut self, variable: &str, expression: &ASTNode) -> Result<Box<dyn NyashBox>, RuntimeError> {
use crate::boxes::FutureBox;
use std::thread;
// FutureBoxを作成
let future_box = FutureBox::new();
let future_box_clone = future_box.clone();
// 個別のクローンを用意(スケジュール経路とフォールバック経路で別々に使う)
let future_for_sched = future_box.clone();
let future_for_thread = future_box.clone();
// 式をクローンして別スレッドで実行
let expr_clone = expression.clone();
let shared_state = self.shared.clone();
// 別スレッドで非同期実行
thread::spawn(move || {
// 新しいインタープリタインスタンスを作成SharedStateを使用
let mut async_interpreter = NyashInterpreter::with_shared(shared_state);
// 式を評価
match async_interpreter.execute_expression(&expr_clone) {
Ok(result) => {
future_box_clone.set_result(result);
// 式をクローンしてスケジューラ(なければフォールバック)で実行
// それぞれの経路で独立に所有させるためクローンを分けておく
let expr_for_sched = expression.clone();
let expr_for_thread = expression.clone();
let shared_for_sched = self.shared.clone();
let shared_for_thread = self.shared.clone();
// Phase-2: try scheduler first (bound to current TaskGroup token); fallback to thread
let token = crate::runtime::global_hooks::current_group_token();
let scheduled = crate::runtime::global_hooks::spawn_task_with_token(
"nowait",
token,
Box::new(move || {
// 新しいインタープリタインスタンスを作成SharedStateを使用
let mut async_interpreter = NyashInterpreter::with_shared(shared_for_sched);
// 式を評価
match async_interpreter.execute_expression(&expr_for_sched) {
Ok(result) => { future_for_sched.set_result(result); }
Err(e) => {
// エラーをErrorBoxとして設定
let error_box = Box::new(ErrorBox::new("RuntimeError", &format!("{:?}", e)));
future_for_sched.set_result(error_box);
}
}
Err(e) => {
// エラーをErrorBoxとして設定
let error_box = Box::new(ErrorBox::new("RuntimeError", &format!("{:?}", e)));
future_box_clone.set_result(error_box);
})
);
if !scheduled {
std::thread::spawn(move || {
let mut async_interpreter = NyashInterpreter::with_shared(shared_for_thread);
match async_interpreter.execute_expression(&expr_for_thread) {
Ok(result) => { future_for_thread.set_result(result); }
Err(e) => {
let error_box = Box::new(ErrorBox::new("RuntimeError", &format!("{:?}", e)));
future_for_thread.set_result(error_box);
}
}
}
});
});
}
// FutureBoxを現在のTaskGroupに登録暗黙グループ best-effort
crate::runtime::global_hooks::register_future_to_current_group(&future_box);

View File

@ -141,6 +141,10 @@ pub struct CraneliftBuilder {
// Single-exit epilogue (jit-direct stability): ret block + i64 slot
ret_block: Option<cranelift_codegen::ir::Block>,
ret_slot: Option<cranelift_codegen::ir::StackSlot>,
// Blocks requested before begin_function (to avoid TLS usage early)
pending_blocks: usize,
// Whether current block needs a terminator before switching away
cur_needs_term: bool,
}
#[cfg(feature = "cranelift-jit")]
@ -148,6 +152,83 @@ use cranelift_module::Module;
#[cfg(feature = "cranelift-jit")]
use cranelift_codegen::ir::InstBuilder;
// TLS: 単一関数あたり1つの FunctionBuilder を保持jit-direct 専用)
#[cfg(feature = "cranelift-jit")]
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>,
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();
}
}
}
}
// Small TLS helpers to call imported functions via the single FunctionBuilder
#[cfg(feature = "cranelift-jit")]
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| {
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 }
})
})
}
#[cfg(feature = "cranelift-jit")]
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 }
})
})
}
#[cfg(feature = "cranelift-jit")]
extern "C" fn nyash_host_stub0() -> i64 { 0 }
#[cfg(feature = "cranelift-jit")]
@ -723,126 +804,111 @@ impl IRBuilder for CraneliftBuilder {
}
fn begin_function(&mut self, name: &str) {
use cranelift_codegen::ir::{AbiParam, Signature, types};
use cranelift_frontend::FunctionBuilder;
self.current_name = Some(name.to_string());
self.value_stack.clear();
// Keep any pre-created blocks (from prepare_blocks or typed signature)
// Build default signature only if a typed one wasn't prepared
if !self.typed_sig_prepared {
// Minimal signature: (i64 x argc) -> i64? (Core-1 integer path)
// Create TLS context + single FB
clif_tls::FB.with(|cell| {
let mut tls = clif_tls::TlsCtx::new();
let call_conv = self.module.isa().default_call_conv();
let mut sig = Signature::new(call_conv);
for _ in 0..self.desired_argc { sig.params.push(AbiParam::new(types::I64)); }
if self.desired_has_ret {
if self.desired_ret_is_f64 { sig.returns.push(AbiParam::new(types::F64)); }
else {
let mut used_b1 = false;
#[cfg(feature = "jit-b1-abi")]
{
let cfg_now = crate::jit::config::current();
if crate::jit::config::probe_capabilities().supports_b1_sig && cfg_now.native_bool_abi && self.ret_hint_is_b1 {
sig.returns.push(AbiParam::new(types::B1));
used_b1 = true;
if !self.typed_sig_prepared {
for _ in 0..self.desired_argc { sig.params.push(AbiParam::new(types::I64)); }
if self.desired_has_ret {
if self.desired_ret_is_f64 { sig.returns.push(AbiParam::new(types::F64)); }
else { sig.returns.push(AbiParam::new(types::I64)); }
}
} else {
for _ in 0..self.desired_argc { sig.params.push(AbiParam::new(types::I64)); }
if self.desired_has_ret {
if self.desired_ret_is_f64 { sig.returns.push(AbiParam::new(types::F64)); }
else {
let mut used_b1 = false;
#[cfg(feature = "jit-b1-abi")]
{
let cfg_now = crate::jit::config::current();
if crate::jit::config::probe_capabilities().supports_b1_sig && cfg_now.native_bool_abi && self.ret_hint_is_b1 {
sig.returns.push(AbiParam::new(types::B1));
used_b1 = true;
}
}
if !used_b1 { sig.returns.push(AbiParam::new(types::I64)); }
}
if !used_b1 { sig.returns.push(AbiParam::new(types::I64)); }
}
}
self.ctx.func.signature = sig;
}
self.ctx.func.name = cranelift_codegen::ir::UserFuncName::user(0, 0);
let mut fb = FunctionBuilder::new(&mut self.ctx.func, &mut self.fbc);
// Prepare entry block: use pre-created block[0] if present, otherwise create
if self.blocks.is_empty() {
let block = fb.create_block();
self.blocks.push(block);
}
let entry = self.blocks[0];
fb.append_block_params_for_function_params(entry);
fb.switch_to_block(entry);
// Seal entry immediately (no predecessors by definition)
fb.seal_block(entry);
self.entry_block = Some(entry);
self.current_block_index = Some(0);
// Prepare single-exit epilogue artifacts (ret_block with one i64 param)
{
use cranelift_codegen::ir::types;
let rb = fb.create_block();
self.ret_block = Some(rb);
// Single i64 param to carry merged return value
fb.append_block_param(rb, types::I64);
// Stop using ret_slot in block-param mode
self.ret_slot = None;
}
fb.finalize();
tls.ctx.func.signature = sig;
tls.ctx.func.name = cranelift_codegen::ir::UserFuncName::user(0, 0);
unsafe { tls.create(); }
tls.with(|fb| {
if self.blocks.is_empty() {
let block = fb.create_block();
self.blocks.push(block);
}
if self.pending_blocks > self.blocks.len() {
let to_create = self.pending_blocks - self.blocks.len();
for _ in 0..to_create { self.blocks.push(fb.create_block()); }
}
let entry = self.blocks[0];
fb.append_block_params_for_function_params(entry);
fb.switch_to_block(entry);
fb.seal_block(entry);
self.entry_block = Some(entry);
self.current_block_index = Some(0);
self.cur_needs_term = true;
let rb = fb.create_block();
self.ret_block = Some(rb);
fb.append_block_param(rb, types::I64);
self.ret_slot = None;
});
cell.replace(Some(tls));
});
}
fn end_function(&mut self) {
// Define and finalize into the module, create an invocable closure
use cranelift_module::{Linkage, Module};
if self.entry_block.is_none() {
return;
}
// Build single-exit epilogue before defining function
{
use cranelift_frontend::FunctionBuilder;
use cranelift_codegen::ir::types;
if let Some(rb) = self.ret_block {
let mut fb = FunctionBuilder::new(&mut self.ctx.func, &mut self.fbc);
fb.switch_to_block(rb);
if fb.func.signature.returns.is_empty() {
fb.ins().return_(&[]);
} else {
let params = fb.func.dfg.block_params(rb).to_vec();
let mut v = params.get(0).copied().unwrap_or_else(|| fb.ins().iconst(types::I64, 0));
// Optional debug
if std::env::var("NYASH_JIT_TRACE_RET").ok().as_deref() == Some("1") {
use cranelift_codegen::ir::{AbiParam, Signature}; use cranelift_module::Linkage;
let mut sig = Signature::new(self.module.isa().default_call_conv());
sig.params.push(AbiParam::new(types::I64));
sig.params.push(AbiParam::new(types::I64));
sig.returns.push(AbiParam::new(types::I64));
let fid = self.module.declare_function("nyash.jit.dbg_i64", Linkage::Import, &sig).expect("declare dbg_i64");
let fref = self.module.declare_func_in_func(fid, fb.func);
let tag = fb.ins().iconst(types::I64, 200);
let _ = fb.ins().call(fref, &[tag, v]);
if self.entry_block.is_none() { return; }
// Epilogue + seal + finalize builder in TLS, then take Context out
let mut ctx_opt: Option<cranelift_codegen::Context> = None;
clif_tls::FB.with(|cell| {
if let Some(mut tls) = cell.take() {
tls.with(|fb| {
use cranelift_codegen::ir::types;
if let Some(rb) = self.ret_block {
fb.switch_to_block(rb);
if fb.func.signature.returns.is_empty() {
fb.ins().return_(&[]);
} else {
let params = fb.func.dfg.block_params(rb).to_vec();
let mut v = params.get(0).copied().unwrap_or_else(|| fb.ins().iconst(types::I64, 0));
if std::env::var("NYASH_JIT_TRACE_RET").ok().as_deref() == Some("1") {
use cranelift_codegen::ir::{AbiParam, Signature};
let mut sig = Signature::new(self.module.isa().default_call_conv());
sig.params.push(AbiParam::new(types::I64));
sig.params.push(AbiParam::new(types::I64));
sig.returns.push(AbiParam::new(types::I64));
let fid = self.module.declare_function("nyash.jit.dbg_i64", Linkage::Import, &sig).expect("declare dbg_i64");
let fref = self.module.declare_func_in_func(fid, fb.func);
let tag = fb.ins().iconst(types::I64, 200);
let _ = fb.ins().call(fref, &[tag, v]);
}
let ret_ty = fb.func.signature.returns.get(0).map(|p| p.value_type).unwrap_or(types::I64);
if ret_ty == types::F64 { v = fb.ins().fcvt_from_sint(types::F64, v); }
fb.ins().return_(&[v]);
}
}
let ret_ty = fb.func.signature.returns.get(0).map(|p| p.value_type).unwrap_or(types::I64);
if ret_ty == types::F64 { v = fb.ins().fcvt_from_sint(types::F64, v); }
fb.ins().return_(&[v]);
}
fb.finalize();
if let Some(en) = self.entry_block { fb.seal_block(en); }
for b in &self.blocks { fb.seal_block(*b); }
if let Some(rb) = self.ret_block { fb.seal_block(rb); }
});
unsafe { tls.finalize_drop(); }
ctx_opt = Some(*tls.ctx);
}
}
// Seal all blocks including entry and ret to satisfy builder constraints
{
use cranelift_frontend::FunctionBuilder;
let mut fb = FunctionBuilder::new(&mut self.ctx.func, &mut self.fbc);
if let Some(en) = self.entry_block { fb.seal_block(en); }
for b in &self.blocks { fb.seal_block(*b); }
if let Some(rb) = self.ret_block { fb.seal_block(rb); }
fb.finalize();
}
// Declare a unique function symbol for JIT
});
let mut ctx = ctx_opt.expect("missing TLS context");
let sym_name = self.current_name.clone().unwrap_or_else(|| "jit_fn".to_string());
let func_id = self.module.declare_function(&sym_name, Linkage::Local, &self.ctx.func.signature)
.expect("declare_function failed");
// Define
self.module.define_function(func_id, &mut self.ctx)
.expect("define_function failed");
// Clear context for next compilation and finalize definitions
self.module.clear_context(&mut self.ctx);
let func_id = self.module.declare_function(&sym_name, Linkage::Local, &ctx.func.signature).expect("declare_function failed");
self.module.define_function(func_id, &mut ctx).expect("define_function failed");
self.module.clear_context(&mut ctx);
let _ = self.module.finalize_definitions();
// Get finalized code pointer and wrap into a safe closure
let code = self.module.get_finalized_function(func_id);
// SAFETY: We compiled a function with simple (i64 x N) -> i64/f64 というABIだよ。
@ -974,135 +1040,101 @@ impl IRBuilder for CraneliftBuilder {
fn emit_const_i64(&mut self, val: i64) {
use cranelift_codegen::ir::types;
use cranelift_frontend::FunctionBuilder;
// Recreate FunctionBuilder each emit (lightweight wrapper around ctx+fbc)
let mut fb = FunctionBuilder::new(&mut self.ctx.func, &mut self.fbc);
if let Some(idx) = self.current_block_index { fb.switch_to_block(self.blocks[idx]); }
else if let Some(b) = self.entry_block { fb.switch_to_block(b); }
let v = fb.ins().iconst(types::I64, val);
let v = CraneliftBuilder::with_fb(|fb| fb.ins().iconst(types::I64, val));
self.value_stack.push(v);
self.stats.0 += 1;
fb.finalize();
}
fn emit_const_f64(&mut self, val: f64) {
self.stats.0 += 1;
if !crate::jit::config::current().native_f64 { return; }
use cranelift_codegen::ir::types;
use cranelift_frontend::FunctionBuilder;
let mut fb = FunctionBuilder::new(&mut self.ctx.func, &mut self.fbc);
if let Some(idx) = self.current_block_index { fb.switch_to_block(self.blocks[idx]); }
else if let Some(b) = self.entry_block { fb.switch_to_block(b); }
let v = fb.ins().f64const(val);
let v = CraneliftBuilder::with_fb(|fb| fb.ins().f64const(val));
self.value_stack.push(v);
fb.finalize();
}
fn emit_binop(&mut self, op: BinOpKind) {
use cranelift_frontend::FunctionBuilder;
use cranelift_codegen::ir::types;
if self.value_stack.len() < 2 { return; }
let mut rhs = self.value_stack.pop().unwrap();
let mut lhs = self.value_stack.pop().unwrap();
let mut fb = FunctionBuilder::new(&mut self.ctx.func, &mut self.fbc);
if let Some(idx) = self.current_block_index { fb.switch_to_block(self.blocks[idx]); }
else if let Some(b) = self.entry_block { fb.switch_to_block(b); }
// Choose op by operand type (I64 vs F64). If mixed and native_f64, promote to F64.
let lty = fb.func.dfg.value_type(lhs);
let rty = fb.func.dfg.value_type(rhs);
let native_f64 = crate::jit::config::current().native_f64;
let mut use_f64 = native_f64 && (lty == types::F64 || rty == types::F64);
if use_f64 {
if lty != types::F64 { lhs = fb.ins().fcvt_from_sint(types::F64, lhs); }
if rty != types::F64 { rhs = fb.ins().fcvt_from_sint(types::F64, rhs); }
}
let res = if use_f64 {
match op {
BinOpKind::Add => fb.ins().fadd(lhs, rhs),
BinOpKind::Sub => fb.ins().fsub(lhs, rhs),
BinOpKind::Mul => fb.ins().fmul(lhs, rhs),
BinOpKind::Div => fb.ins().fdiv(lhs, rhs),
BinOpKind::Mod => {
// Minimal path: produce 0.0 (fmod未実装)。将来はホストコール/Libcallに切替
fb.ins().f64const(0.0)
}
let res = CraneliftBuilder::with_fb(|fb| {
let lty = fb.func.dfg.value_type(lhs);
let rty = fb.func.dfg.value_type(rhs);
let native_f64 = crate::jit::config::current().native_f64;
let mut use_f64 = native_f64 && (lty == types::F64 || rty == types::F64);
if use_f64 {
if lty != types::F64 { lhs = fb.ins().fcvt_from_sint(types::F64, lhs); }
if rty != types::F64 { rhs = fb.ins().fcvt_from_sint(types::F64, rhs); }
}
} else {
match op {
BinOpKind::Add => fb.ins().iadd(lhs, rhs),
BinOpKind::Sub => fb.ins().isub(lhs, rhs),
BinOpKind::Mul => fb.ins().imul(lhs, rhs),
BinOpKind::Div => fb.ins().sdiv(lhs, rhs),
BinOpKind::Mod => fb.ins().srem(lhs, rhs),
if use_f64 {
match op { BinOpKind::Add => fb.ins().fadd(lhs, rhs), BinOpKind::Sub => fb.ins().fsub(lhs, rhs), BinOpKind::Mul => fb.ins().fmul(lhs, rhs), BinOpKind::Div => fb.ins().fdiv(lhs, rhs), BinOpKind::Mod => fb.ins().f64const(0.0) }
} else {
match op { BinOpKind::Add => fb.ins().iadd(lhs, rhs), BinOpKind::Sub => fb.ins().isub(lhs, rhs), BinOpKind::Mul => fb.ins().imul(lhs, rhs), BinOpKind::Div => fb.ins().sdiv(lhs, rhs), BinOpKind::Mod => fb.ins().srem(lhs, rhs) }
}
};
});
self.value_stack.push(res);
self.stats.1 += 1;
fb.finalize();
}
fn emit_compare(&mut self, op: CmpKind) {
use cranelift_codegen::ir::{condcodes::{IntCC, FloatCC}, types};
use cranelift_frontend::FunctionBuilder;
if self.value_stack.len() < 2 { return; }
let mut rhs = self.value_stack.pop().unwrap();
let mut lhs = self.value_stack.pop().unwrap();
let mut fb = FunctionBuilder::new(&mut self.ctx.func, &mut self.fbc);
if let Some(idx) = self.current_block_index { fb.switch_to_block(self.blocks[idx]); }
else if let Some(b) = self.entry_block { fb.switch_to_block(b); }
let lty = fb.func.dfg.value_type(lhs);
let rty = fb.func.dfg.value_type(rhs);
let native_f64 = crate::jit::config::current().native_f64;
let use_f64 = native_f64 && (lty == types::F64 || rty == types::F64);
let b1 = if use_f64 {
if lty != types::F64 { lhs = fb.ins().fcvt_from_sint(types::F64, lhs); }
if rty != types::F64 { rhs = fb.ins().fcvt_from_sint(types::F64, rhs); }
let cc = match op {
CmpKind::Eq => FloatCC::Equal,
CmpKind::Ne => FloatCC::NotEqual,
CmpKind::Lt => FloatCC::LessThan,
CmpKind::Le => FloatCC::LessThanOrEqual,
CmpKind::Gt => FloatCC::GreaterThan,
CmpKind::Ge => FloatCC::GreaterThanOrEqual,
CraneliftBuilder::with_fb(|fb| {
if let Some(idx) = self.current_block_index { fb.switch_to_block(self.blocks[idx]); }
else if let Some(b) = self.entry_block { fb.switch_to_block(b); }
let lty = fb.func.dfg.value_type(lhs);
let rty = fb.func.dfg.value_type(rhs);
let native_f64 = crate::jit::config::current().native_f64;
let use_f64 = native_f64 && (lty == types::F64 || rty == types::F64);
let b1 = if use_f64 {
if lty != types::F64 { lhs = fb.ins().fcvt_from_sint(types::F64, lhs); }
if rty != types::F64 { rhs = fb.ins().fcvt_from_sint(types::F64, rhs); }
let cc = match op {
CmpKind::Eq => FloatCC::Equal,
CmpKind::Ne => FloatCC::NotEqual,
CmpKind::Lt => FloatCC::LessThan,
CmpKind::Le => FloatCC::LessThanOrEqual,
CmpKind::Gt => FloatCC::GreaterThan,
CmpKind::Ge => FloatCC::GreaterThanOrEqual,
};
fb.ins().fcmp(cc, lhs, rhs)
} else {
let cc = match op {
CmpKind::Eq => IntCC::Equal,
CmpKind::Ne => IntCC::NotEqual,
CmpKind::Lt => IntCC::SignedLessThan,
CmpKind::Le => IntCC::SignedLessThanOrEqual,
CmpKind::Gt => IntCC::SignedGreaterThan,
CmpKind::Ge => IntCC::SignedGreaterThanOrEqual,
};
fb.ins().icmp(cc, lhs, rhs)
};
fb.ins().fcmp(cc, lhs, rhs)
} else {
let cc = match op {
CmpKind::Eq => IntCC::Equal,
CmpKind::Ne => IntCC::NotEqual,
CmpKind::Lt => IntCC::SignedLessThan,
CmpKind::Le => IntCC::SignedLessThanOrEqual,
CmpKind::Gt => IntCC::SignedGreaterThan,
CmpKind::Ge => IntCC::SignedGreaterThanOrEqual,
};
fb.ins().icmp(cc, lhs, rhs)
};
// Keep b1 on the stack; users (branch) can consume directly
self.value_stack.push(b1);
self.stats.2 += 1;
fb.finalize();
self.value_stack.push(b1);
self.stats.2 += 1;
});
}
fn emit_select_i64(&mut self) {
use cranelift_frontend::FunctionBuilder; use cranelift_codegen::ir::{types, condcodes::IntCC};
use cranelift_codegen::ir::{types, condcodes::IntCC};
// Expect stack: ... cond then else
if self.value_stack.len() < 3 { return; }
let mut else_v = self.value_stack.pop().unwrap();
let mut then_v = self.value_stack.pop().unwrap();
let mut cond_v = self.value_stack.pop().unwrap();
let mut fb = FunctionBuilder::new(&mut self.ctx.func, &mut self.fbc);
if let Some(idx) = self.current_block_index { fb.switch_to_block(self.blocks[idx]); }
else if let Some(b) = self.entry_block { fb.switch_to_block(b); }
// Normalize types
let cty = fb.func.dfg.value_type(cond_v);
if cty == types::I64 {
cond_v = fb.ins().icmp_imm(IntCC::NotEqual, cond_v, 0);
crate::jit::rt::b1_norm_inc(1);
}
let tty = fb.func.dfg.value_type(then_v);
if tty != types::I64 { then_v = fb.ins().fcvt_to_sint(types::I64, then_v); }
let ety = fb.func.dfg.value_type(else_v);
if ety != types::I64 { else_v = fb.ins().fcvt_to_sint(types::I64, else_v); }
// Optional runtime debug
let sel = CraneliftBuilder::with_fb(|fb| {
// Normalize types
let cty = fb.func.dfg.value_type(cond_v);
if cty == types::I64 {
cond_v = fb.ins().icmp_imm(IntCC::NotEqual, cond_v, 0);
crate::jit::rt::b1_norm_inc(1);
}
let tty = fb.func.dfg.value_type(then_v);
if tty != types::I64 { then_v = fb.ins().fcvt_to_sint(types::I64, then_v); }
let ety = fb.func.dfg.value_type(else_v);
if ety != types::I64 { else_v = fb.ins().fcvt_to_sint(types::I64, else_v); }
// Optional runtime debug
if std::env::var("NYASH_JIT_TRACE_SEL").ok().as_deref() == Some("1") {
use cranelift_codegen::ir::{AbiParam, Signature};
use cranelift_module::Linkage;
@ -1124,44 +1156,38 @@ impl IRBuilder for CraneliftBuilder {
let t_else = fb.ins().iconst(types::I64, 102);
let _ = fb.ins().call(fref, &[t_else, else_v]);
}
let sel = fb.ins().select(cond_v, then_v, else_v);
fb.ins().select(cond_v, then_v, else_v)
});
self.value_stack.push(sel);
fb.finalize();
}
fn emit_jump(&mut self) { self.stats.3 += 1; }
fn emit_branch(&mut self) { self.stats.3 += 1; }
fn emit_return(&mut self) {
use cranelift_frontend::FunctionBuilder;
use cranelift_codegen::ir::{types, condcodes::IntCC};
self.stats.4 += 1;
let mut fb = FunctionBuilder::new(&mut self.ctx.func, &mut self.fbc);
if let Some(idx) = self.current_block_index { fb.switch_to_block(self.blocks[idx]); }
else if let Some(b) = self.entry_block { fb.switch_to_block(b); }
if fb.func.signature.returns.is_empty() {
fb.ins().return_(&[]);
fb.finalize();
return;
}
let mut v = if let Some(x) = self.value_stack.pop() { x } else { fb.ins().iconst(types::I64, 0) };
let v_ty = fb.func.dfg.value_type(v);
if v_ty != types::I64 {
if v_ty == types::F64 { v = fb.ins().fcvt_to_sint(types::I64, v); }
else { let one = fb.ins().iconst(types::I64, 1); let zero = fb.ins().iconst(types::I64, 0); v = fb.ins().select(v, one, zero); }
}
if std::env::var("NYASH_JIT_TRACE_RET").ok().as_deref() == Some("1") {
use cranelift_codegen::ir::{AbiParam, Signature}; use cranelift_module::Linkage;
let mut sig = Signature::new(self.module.isa().default_call_conv());
sig.params.push(AbiParam::new(types::I64));
sig.params.push(AbiParam::new(types::I64));
sig.returns.push(AbiParam::new(types::I64));
let fid = self.module.declare_function("nyash.jit.dbg_i64", Linkage::Import, &sig).expect("declare dbg_i64");
let fref = self.module.declare_func_in_func(fid, fb.func);
let tag = fb.ins().iconst(types::I64, 201);
let _ = fb.ins().call(fref, &[tag, v]);
}
if let Some(rb) = self.ret_block { fb.ins().jump(rb, &[v]); }
fb.finalize();
CraneliftBuilder::with_fb(|fb| {
if fb.func.signature.returns.is_empty() { fb.ins().return_(&[]); return; }
let mut v = if let Some(x) = self.value_stack.pop() { x } else { fb.ins().iconst(types::I64, 0) };
let v_ty = fb.func.dfg.value_type(v);
if v_ty != types::I64 {
if v_ty == types::F64 { v = fb.ins().fcvt_to_sint(types::I64, v); }
else { let one = fb.ins().iconst(types::I64, 1); let zero = fb.ins().iconst(types::I64, 0); v = fb.ins().select(v, one, zero); }
}
if std::env::var("NYASH_JIT_TRACE_RET").ok().as_deref() == Some("1") {
use cranelift_codegen::ir::{AbiParam, Signature}; use cranelift_module::Linkage;
let mut sig = Signature::new(self.module.isa().default_call_conv());
sig.params.push(AbiParam::new(types::I64));
sig.params.push(AbiParam::new(types::I64));
sig.returns.push(AbiParam::new(types::I64));
let fid = self.module.declare_function("nyash.jit.dbg_i64", Linkage::Import, &sig).expect("declare dbg_i64");
let fref = self.module.declare_func_in_func(fid, fb.func);
let tag = fb.ins().iconst(types::I64, 201);
let _ = fb.ins().call(fref, &[tag, v]);
}
if let Some(rb) = self.ret_block { fb.ins().jump(rb, &[v]); }
});
self.cur_needs_term = false;
}
fn emit_host_call(&mut self, symbol: &str, _argc: usize, has_ret: bool) {
@ -1184,19 +1210,8 @@ impl IRBuilder for CraneliftBuilder {
let func_id = self.module
.declare_function(symbol, Linkage::Import, &sig)
.expect("declare import failed");
let mut fb = FunctionBuilder::new(&mut self.ctx.func, &mut self.fbc);
if let Some(idx) = self.current_block_index { fb.switch_to_block(self.blocks[idx]); }
else if let Some(b) = self.entry_block { fb.switch_to_block(b); }
let fref = self.module.declare_func_in_func(func_id, fb.func);
let call_inst = fb.ins().call(fref, &args);
if has_ret {
let results = fb.inst_results(call_inst).to_vec();
if let Some(v) = results.get(0).copied() {
self.value_stack.push(v);
}
}
fb.finalize();
let ret = tls_call_import_ret(&mut self.module, func_id, &args, has_ret);
if let Some(v) = ret { self.value_stack.push(v); }
}
fn emit_host_call_typed(&mut self, symbol: &str, params: &[ParamKind], has_ret: bool, ret_is_f64: bool) {
@ -1233,16 +1248,8 @@ impl IRBuilder for CraneliftBuilder {
.declare_function(symbol, Linkage::Import, &sig)
.expect("declare typed import failed");
let mut fb = FunctionBuilder::new(&mut self.ctx.func, &mut self.fbc);
if let Some(idx) = self.current_block_index { fb.switch_to_block(self.blocks[idx]); }
else if let Some(b) = self.entry_block { fb.switch_to_block(b); }
let fref = self.module.declare_func_in_func(func_id, fb.func);
let call_inst = fb.ins().call(fref, &args);
if has_ret {
let results = fb.inst_results(call_inst).to_vec();
if let Some(v) = results.get(0).copied() { self.value_stack.push(v); }
}
fb.finalize();
let ret = tls_call_import_ret(&mut self.module, func_id, &args, has_ret);
if let Some(v) = ret { self.value_stack.push(v); }
}
fn emit_plugin_invoke(&mut self, type_id: u32, method_id: u32, argc: usize, has_ret: bool) {
@ -1250,24 +1257,17 @@ impl IRBuilder for CraneliftBuilder {
use cranelift_frontend::FunctionBuilder;
use cranelift_module::{Linkage, Module};
// Use a single FunctionBuilder to construct all IR in this method to avoid dominance issues
let mut fb = FunctionBuilder::new(&mut self.ctx.func, &mut self.fbc);
if let Some(idx) = self.current_block_index { fb.switch_to_block(self.blocks[idx]); }
else if let Some(b) = self.entry_block { fb.switch_to_block(b); }
// Pop argc values (right-to-left): receiver + up to 2 args
let mut arg_vals: Vec<cranelift_codegen::ir::Value> = Vec::new();
let take_n = argc.min(self.value_stack.len());
for _ in 0..take_n { if let Some(v) = self.value_stack.pop() { arg_vals.push(v); } }
arg_vals.reverse();
// Pad to 3 values (receiver + a1 + a2) using the same builder
while arg_vals.len() < 3 {
let z = fb.ins().iconst(types::I64, 0);
arg_vals.push(z);
}
let mut arg_vals: Vec<cranelift_codegen::ir::Value> = {
let take_n = argc.min(self.value_stack.len());
let mut tmp = Vec::new();
for _ in 0..take_n { if let Some(v) = self.value_stack.pop() { tmp.push(v); } }
tmp.reverse();
tmp
};
// Ensure receiver (a0) is a runtime handle via nyash.handle.of (Handle-First)
{
let a0_handle = {
use cranelift_module::Linkage;
use crate::jit::r#extern::handles as h;
let call_conv_h = self.module.isa().default_call_conv();
@ -1277,10 +1277,10 @@ impl IRBuilder for CraneliftBuilder {
let func_id_h = self.module
.declare_function(h::SYM_HANDLE_OF, Linkage::Import, &sig_h)
.expect("declare handle.of failed");
let fref_h = self.module.declare_func_in_func(func_id_h, fb.func);
let call_h = fb.ins().call(fref_h, &[arg_vals[0]]);
if let Some(rv) = fb.inst_results(call_h).get(0).copied() { arg_vals[0] = rv; }
}
// call handle.of(a0)
tls_call_import_ret(&mut self.module, func_id_h, &arg_vals[0..1], true).expect("handle.of ret")
};
arg_vals[0] = a0_handle;
// Choose f64 shim if allowlisted
let use_f64 = if has_ret {
@ -1301,18 +1301,34 @@ impl IRBuilder for CraneliftBuilder {
let func_id = self.module
.declare_function(symbol, Linkage::Import, &sig)
.expect("declare plugin shim failed");
let fref = self.module.declare_func_in_func(func_id, fb.func);
let c_type = fb.ins().iconst(types::I64, type_id as i64);
let c_meth = fb.ins().iconst(types::I64, method_id as i64);
let c_argc = fb.ins().iconst(types::I64, argc as i64);
// Pass receiver param index (a0) when known; shim will fallback-scan if invalid (<0)
let call_inst = fb.ins().call(fref, &[c_type, c_meth, c_argc, arg_vals[0], arg_vals[1], arg_vals[2]]);
if has_ret {
let results = fb.inst_results(call_inst).to_vec();
if let Some(v) = results.get(0).copied() { self.value_stack.push(v); }
}
fb.finalize();
// Now emit calls via the single FB
let ret_val = CraneliftBuilder::with_fb(|fb| {
if let Some(idx) = self.current_block_index { fb.switch_to_block(self.blocks[idx]); }
else if let Some(b) = self.entry_block { fb.switch_to_block(b); }
while arg_vals.len() < 3 { let z = fb.ins().iconst(types::I64, 0); arg_vals.push(z); }
// Handle-of transform for receiver
{
use cranelift_module::Linkage;
use crate::jit::r#extern::handles as h;
let call_conv_h = self.module.isa().default_call_conv();
let mut sig_h = Signature::new(call_conv_h);
sig_h.params.push(AbiParam::new(types::I64));
sig_h.returns.push(AbiParam::new(types::I64));
let func_id_h = self.module
.declare_function(h::SYM_HANDLE_OF, Linkage::Import, &sig_h)
.expect("declare handle.of failed");
let fref_h = self.module.declare_func_in_func(func_id_h, fb.func);
let call_h = fb.ins().call(fref_h, &[arg_vals[0]]);
if let Some(rv) = fb.inst_results(call_h).get(0).copied() { arg_vals[0] = rv; }
}
let fref = self.module.declare_func_in_func(func_id, fb.func);
let c_type = fb.ins().iconst(types::I64, type_id as i64);
let c_meth = fb.ins().iconst(types::I64, method_id as i64);
let c_argc = fb.ins().iconst(types::I64, argc as i64);
let call_inst = fb.ins().call(fref, &[c_type, c_meth, c_argc, arg_vals[0], arg_vals[1], arg_vals[2]]);
if has_ret { fb.inst_results(call_inst).get(0).copied() } else { None }
});
if let Some(v) = ret_val { self.value_stack.push(v); }
}
fn emit_plugin_invoke_by_name(&mut self, method: &str, argc: usize, has_ret: bool) {
@ -1320,7 +1336,7 @@ impl IRBuilder for CraneliftBuilder {
use cranelift_frontend::FunctionBuilder;
use cranelift_module::{Linkage, Module};
let mut fb = FunctionBuilder::new(&mut self.ctx.func, &mut self.fbc);
CraneliftBuilder::with_fb(|fb| {
if let Some(idx) = self.current_block_index { fb.switch_to_block(self.blocks[idx]); }
else if let Some(b) = self.entry_block { fb.switch_to_block(b); }
@ -1365,148 +1381,118 @@ impl IRBuilder for CraneliftBuilder {
let c_argc = fb.ins().iconst(types::I64, argc as i64);
let call_inst = fb.ins().call(fref, &[c_argc, arg_vals[0], arg_vals[1], arg_vals[2]]);
if has_ret { let results = fb.inst_results(call_inst).to_vec(); if let Some(v) = results.get(0).copied() { self.value_stack.push(v); } }
fb.finalize();
});
}
// ==== Phase 10.7 block APIs ====
fn prepare_blocks(&mut self, count: usize) {
use cranelift_frontend::FunctionBuilder;
if count == 0 { return; }
let mut fb = FunctionBuilder::new(&mut self.ctx.func, &mut self.fbc);
// Only create if not already created
if self.blocks.len() < count {
let to_create = count - self.blocks.len();
for _ in 0..to_create { self.blocks.push(fb.create_block()); }
}
fb.finalize();
// Do not touch TLS before begin_function; just remember the desired count.
if count > self.pending_blocks { self.pending_blocks = count; }
}
fn switch_to_block(&mut self, index: usize) {
use cranelift_frontend::FunctionBuilder;
if index >= self.blocks.len() { return; }
let mut fb = FunctionBuilder::new(&mut self.ctx.func, &mut self.fbc);
fb.switch_to_block(self.blocks[index]);
CraneliftBuilder::with_fb(|fb| { fb.switch_to_block(self.blocks[index]); });
self.current_block_index = Some(index);
fb.finalize();
}
fn seal_block(&mut self, index: usize) {
use cranelift_frontend::FunctionBuilder;
if index >= self.blocks.len() { return; }
let mut fb = FunctionBuilder::new(&mut self.ctx.func, &mut self.fbc);
fb.seal_block(self.blocks[index]);
fb.finalize();
CraneliftBuilder::with_fb(|fb| { fb.seal_block(self.blocks[index]); });
}
fn br_if_top_is_true(&mut self, then_index: usize, else_index: usize) {
use cranelift_codegen::ir::{types, condcodes::IntCC};
use cranelift_frontend::FunctionBuilder;
if then_index >= self.blocks.len() || else_index >= self.blocks.len() { return; }
let mut fb = FunctionBuilder::new(&mut self.ctx.func, &mut self.fbc);
// Ensure we are in a block
if let Some(idx) = self.current_block_index { fb.switch_to_block(self.blocks[idx]); }
else if let Some(b) = self.entry_block { fb.switch_to_block(b); }
// Take top-of-stack as cond; if it's i64, normalize to b1 via icmp_imm != 0
let had_cond = !self.value_stack.is_empty();
let cond_b1 = if let Some(v) = self.value_stack.pop() {
let ty = fb.func.dfg.value_type(v);
if ty == types::I64 {
let out = fb.ins().icmp_imm(IntCC::NotEqual, v, 0);
let popped = self.value_stack.pop();
CraneliftBuilder::with_fb(|fb| {
if let Some(idx) = self.current_block_index { fb.switch_to_block(self.blocks[idx]); }
else if let Some(b) = self.entry_block { fb.switch_to_block(b); }
let cond_b1 = if let Some(v) = popped {
let ty = fb.func.dfg.value_type(v);
if ty == types::I64 {
let out = fb.ins().icmp_imm(IntCC::NotEqual, v, 0);
crate::jit::rt::b1_norm_inc(1);
out
} else { v }
} else {
let zero = fb.ins().iconst(types::I64, 0);
let out = fb.ins().icmp_imm(IntCC::NotEqual, zero, 0);
crate::jit::rt::b1_norm_inc(1);
out
} else {
// assume already b1
v
};
if std::env::var("NYASH_JIT_TRACE_BR").ok().as_deref() == Some("1") {
eprintln!("[JIT-CLIF] br_if cond_present={} then={} else={}", had_cond, then_index, else_index);
}
} else {
let zero = fb.ins().iconst(types::I64, 0);
let out = fb.ins().icmp_imm(IntCC::NotEqual, zero, 0);
crate::jit::rt::b1_norm_inc(1);
out
};
if std::env::var("NYASH_JIT_TRACE_BR").ok().as_deref() == Some("1") {
eprintln!("[JIT-CLIF] br_if cond_present={} then={} else={}", had_cond, then_index, else_index);
}
// NOTE: If branch direction appears inverted, swap targets here to validate mapping.
let swap = std::env::var("NYASH_JIT_SWAP_THEN_ELSE").ok().as_deref() == Some("1");
if swap {
fb.ins().brif(cond_b1, self.blocks[else_index], &[], self.blocks[then_index], &[]);
} else {
fb.ins().brif(cond_b1, self.blocks[then_index], &[], self.blocks[else_index], &[]);
}
let swap = std::env::var("NYASH_JIT_SWAP_THEN_ELSE").ok().as_deref() == Some("1");
if swap {
fb.ins().brif(cond_b1, self.blocks[else_index], &[], self.blocks[then_index], &[]);
} else {
fb.ins().brif(cond_b1, self.blocks[then_index], &[], self.blocks[else_index], &[]);
}
});
self.stats.3 += 1;
fb.finalize();
}
fn jump_to(&mut self, target_index: usize) {
use cranelift_frontend::FunctionBuilder;
if target_index >= self.blocks.len() { return; }
let mut fb = FunctionBuilder::new(&mut self.ctx.func, &mut self.fbc);
if let Some(idx) = self.current_block_index { fb.switch_to_block(self.blocks[idx]); }
else if let Some(b) = self.entry_block { fb.switch_to_block(b); }
fb.ins().jump(self.blocks[target_index], &[]);
CraneliftBuilder::with_fb(|fb| {
if let Some(idx) = self.current_block_index { fb.switch_to_block(self.blocks[idx]); }
else if let Some(b) = self.entry_block { fb.switch_to_block(b); }
fb.ins().jump(self.blocks[target_index], &[]);
});
self.stats.3 += 1;
fb.finalize();
}
fn ensure_block_param_i64(&mut self, index: usize) {
self.ensure_block_params_i64(index, 1);
}
fn ensure_block_params_i64(&mut self, index: usize, needed: usize) {
use cranelift_codegen::ir::types;
use cranelift_frontend::FunctionBuilder;
if index >= self.blocks.len() { return; }
let mut fb = FunctionBuilder::new(&mut self.ctx.func, &mut self.fbc);
let have = self.block_param_counts.get(&index).copied().unwrap_or(0);
if needed > have {
let b = self.blocks[index];
for _ in have..needed {
let _v = fb.append_block_param(b, types::I64);
}
CraneliftBuilder::with_fb(|fb| {
let b = self.blocks[index];
for _ in have..needed {
let _v = fb.append_block_param(b, types::I64);
}
});
self.block_param_counts.insert(index, needed);
if std::env::var("NYASH_JIT_DUMP").ok().as_deref() == Some("1") {
eprintln!("[JIT-CLIF] ensure_block_params bb={} now={}", index, needed);
}
}
fb.finalize();
}
fn ensure_block_params_b1(&mut self, index: usize, needed: usize) {
// Store as i64 block params for ABI stability; consumers can convert to b1
self.ensure_block_params_i64(index, needed);
}
fn push_block_param_i64_at(&mut self, pos: usize) {
use cranelift_frontend::FunctionBuilder;
use cranelift_codegen::ir::types;
let mut fb = FunctionBuilder::new(&mut self.ctx.func, &mut self.fbc);
let b = if let Some(idx) = self.current_block_index { self.blocks[idx] } else if let Some(b) = self.entry_block { b } else { fb.create_block() };
// Ensure we have an active insertion point before emitting any instructions
fb.switch_to_block(b);
let params = fb.func.dfg.block_params(b).to_vec();
if std::env::var("NYASH_JIT_DUMP").ok().as_deref() == Some("1") {
eprintln!("[JIT-CLIF] push_block_param_i64_at pos={} available={}", pos, params.len());
}
if let Some(v) = params.get(pos).copied() { self.value_stack.push(v); }
else {
// defensive fallback
let zero = fb.ins().iconst(types::I64, 0);
self.value_stack.push(zero);
}
fb.finalize();
let v = CraneliftBuilder::with_fb(|fb| {
let b_opt = if let Some(idx) = self.current_block_index { Some(self.blocks[idx]) } else { self.entry_block };
let params = if let Some(b) = b_opt { fb.switch_to_block(b); fb.func.dfg.block_params(b).to_vec() } else { Vec::new() };
if std::env::var("NYASH_JIT_DUMP").ok().as_deref() == Some("1") {
eprintln!("[JIT-CLIF] push_block_param_i64_at pos={} available={}", pos, params.len());
}
if let Some(v) = params.get(pos).copied() { v } else { fb.ins().iconst(types::I64, 0) }
});
self.value_stack.push(v);
}
fn push_block_param_b1_at(&mut self, pos: usize) {
use cranelift_frontend::FunctionBuilder;
use cranelift_codegen::ir::{types, condcodes::IntCC};
let mut fb = FunctionBuilder::new(&mut self.ctx.func, &mut self.fbc);
let b = if let Some(idx) = self.current_block_index { self.blocks[idx] } else if let Some(b) = self.entry_block { b } else { fb.create_block() };
let params = fb.func.dfg.block_params(b).to_vec();
if std::env::var("NYASH_JIT_DUMP").ok().as_deref() == Some("1") {
eprintln!("[JIT-CLIF] push_block_param_b1_at pos={} available={}", pos, params.len());
}
if let Some(v) = params.get(pos).copied() {
let ty = fb.func.dfg.value_type(v);
let b1 = if ty == types::I64 { fb.ins().icmp_imm(IntCC::NotEqual, v, 0) } else { v };
self.value_stack.push(b1);
} else {
let zero = fb.ins().iconst(types::I64, 0);
let b1 = fb.ins().icmp_imm(IntCC::NotEqual, zero, 0);
self.value_stack.push(b1);
}
fb.finalize();
let b1 = CraneliftBuilder::with_fb(|fb| {
let b_opt = if let Some(idx) = self.current_block_index { Some(self.blocks[idx]) } else { self.entry_block };
let params = if let Some(b) = b_opt { fb.func.dfg.block_params(b).to_vec() } else { Vec::new() };
if std::env::var("NYASH_JIT_DUMP").ok().as_deref() == Some("1") {
eprintln!("[JIT-CLIF] push_block_param_b1_at pos={} available={}", pos, params.len());
}
if let Some(v) = params.get(pos).copied() {
let ty = fb.func.dfg.value_type(v);
if ty == types::I64 { fb.ins().icmp_imm(IntCC::NotEqual, v, 0) } else { v }
} else {
let zero = fb.ins().iconst(types::I64, 0);
fb.ins().icmp_imm(IntCC::NotEqual, zero, 0)
}
});
self.value_stack.push(b1);
}
fn br_if_with_args(&mut self, then_index: usize, else_index: usize, then_n: usize, else_n: usize) {
use cranelift_codegen::ir::{types, condcodes::IntCC};
@ -1611,13 +1597,18 @@ impl IRBuilder for CraneliftBuilder {
#[cfg(feature = "cranelift-jit")]
impl CraneliftBuilder {
#[cfg(feature = "cranelift-jit")]
fn mk_fb(&mut self) -> cranelift_frontend::FunctionBuilder {
// Reset FunctionBuilderContext to satisfy Cranelift's requirement when instantiating a new builder.
self.fbc = cranelift_frontend::FunctionBuilderContext::new();
cranelift_frontend::FunctionBuilder::new(&mut self.ctx.func, &mut self.fbc)
}
fn entry_param(&mut self, index: usize) -> Option<cranelift_codegen::ir::Value> {
use cranelift_frontend::FunctionBuilder;
let mut fb = FunctionBuilder::new(&mut self.ctx.func, &mut self.fbc);
if let Some(b) = self.entry_block {
fb.switch_to_block(b);
let params = fb.func.dfg.block_params(b).to_vec();
if let Some(v) = params.get(index).copied() { return Some(v); }
return CraneliftBuilder::with_fb(|fb| {
fb.switch_to_block(b);
fb.func.dfg.block_params(b).get(index).copied()
});
}
None
}
@ -1920,7 +1911,6 @@ impl IRBuilder for ObjectBuilder {
}
if let Some(v) = params.get(pos).copied() { self.value_stack.push(v); }
else { let z = fb.ins().iconst(types::I64, 0); self.value_stack.push(z); }
fb.finalize();
}
fn jump_to(&mut self, target_index: usize) {
use cranelift_frontend::FunctionBuilder;
@ -1930,18 +1920,25 @@ impl IRBuilder for ObjectBuilder {
else if let Some(b) = self.entry_block { fb.switch_to_block(b); }
fb.ins().jump(self.blocks[target_index], &[]);
self.stats.3 += 1;
fb.finalize();
}
fn hint_ret_bool(&mut self, is_b1: bool) { self.ret_hint_is_b1 = is_b1; }
fn ensure_local_i64(&mut self, index: usize) { use cranelift_codegen::ir::{StackSlotData, StackSlotKind}; use cranelift_frontend::FunctionBuilder; if self.local_slots.contains_key(&index) { return; } let mut fb = FunctionBuilder::new(&mut self.ctx.func, &mut self.fbc); let slot = fb.create_sized_stack_slot(StackSlotData::new(StackSlotKind::ExplicitSlot, 8)); self.local_slots.insert(index, slot); fb.finalize(); }
fn store_local_i64(&mut self, index: usize) { use cranelift_codegen::ir::{types, condcodes::IntCC}; use cranelift_frontend::FunctionBuilder; if let Some(mut v) = self.value_stack.pop() { if !self.local_slots.contains_key(&index) { self.ensure_local_i64(index); } let slot = self.local_slots.get(&index).copied(); let mut fb = FunctionBuilder::new(&mut self.ctx.func, &mut self.fbc); if let Some(idx) = self.current_block_index { fb.switch_to_block(self.blocks[idx]); } else if let Some(b) = self.entry_block { fb.switch_to_block(b); } let ty = fb.func.dfg.value_type(v); if ty != types::I64 { if ty == types::F64 { v = fb.ins().fcvt_to_sint(types::I64, v); } else { let one = fb.ins().iconst(types::I64, 1); let zero = fb.ins().iconst(types::I64, 0); let b1 = fb.ins().icmp_imm(IntCC::NotEqual, v, 0); v = fb.ins().select(b1, one, zero); } } if let Some(slot) = slot { fb.ins().stack_store(v, slot, 0); } fb.finalize(); } }
fn load_local_i64(&mut self, index: usize) { use cranelift_codegen::ir::types; use cranelift_frontend::FunctionBuilder; if !self.local_slots.contains_key(&index) { self.ensure_local_i64(index); } if let Some(&slot) = self.local_slots.get(&index) { let mut fb = FunctionBuilder::new(&mut self.ctx.func, &mut self.fbc); if let Some(idx) = self.current_block_index { fb.switch_to_block(self.blocks[idx]); } else if let Some(b) = self.entry_block { fb.switch_to_block(b); } let v = fb.ins().stack_load(types::I64, slot, 0); self.value_stack.push(v); self.stats.0 += 1; fb.finalize(); } }
fn ensure_local_i64(&mut self, index: usize) { use cranelift_codegen::ir::{StackSlotData, StackSlotKind}; use cranelift_frontend::FunctionBuilder; if self.local_slots.contains_key(&index) { return; } let mut fb = FunctionBuilder::new(&mut self.ctx.func, &mut self.fbc); let slot = fb.create_sized_stack_slot(StackSlotData::new(StackSlotKind::ExplicitSlot, 8)); self.local_slots.insert(index, slot); }
fn store_local_i64(&mut self, index: usize) { use cranelift_codegen::ir::{types, condcodes::IntCC}; use cranelift_frontend::FunctionBuilder; if let Some(mut v) = self.value_stack.pop() { if !self.local_slots.contains_key(&index) { self.ensure_local_i64(index); } let slot = self.local_slots.get(&index).copied(); let mut fb = FunctionBuilder::new(&mut self.ctx.func, &mut self.fbc); if let Some(idx) = self.current_block_index { fb.switch_to_block(self.blocks[idx]); } else if let Some(b) = self.entry_block { fb.switch_to_block(b); } let ty = fb.func.dfg.value_type(v); if ty != types::I64 { if ty == types::F64 { v = fb.ins().fcvt_to_sint(types::I64, v); } else { let one = fb.ins().iconst(types::I64, 1); let zero = fb.ins().iconst(types::I64, 0); let b1 = fb.ins().icmp_imm(IntCC::NotEqual, v, 0); v = fb.ins().select(b1, one, zero); } } if let Some(slot) = slot { fb.ins().stack_store(v, slot, 0); } } }
fn load_local_i64(&mut self, index: usize) { use cranelift_codegen::ir::types; use cranelift_frontend::FunctionBuilder; if !self.local_slots.contains_key(&index) { self.ensure_local_i64(index); } if let Some(&slot) = self.local_slots.get(&index) { let mut fb = FunctionBuilder::new(&mut self.ctx.func, &mut self.fbc); if let Some(idx) = self.current_block_index { fb.switch_to_block(self.blocks[idx]); } else if let Some(b) = self.entry_block { fb.switch_to_block(b); } let v = fb.ins().stack_load(types::I64, slot, 0); self.value_stack.push(v); self.stats.0 += 1; } }
}
// removed duplicate impl IRBuilder for CraneliftBuilder (emit_param_i64 moved into main impl)
#[cfg(feature = "cranelift-jit")]
impl CraneliftBuilder {
#[cfg(feature = "cranelift-jit")]
fn with_fb<R>(f: impl FnOnce(&mut cranelift_frontend::FunctionBuilder<'static>) -> R) -> R {
clif_tls::FB.with(|cell| {
let mut opt = cell.borrow_mut();
let tls = opt.as_mut().expect("FunctionBuilder TLS not initialized");
tls.with(f)
})
}
pub fn new() -> Self {
// Initialize a minimal JITModule to validate linking; not used yet
let mut builder = cranelift_jit::JITBuilder::new(cranelift_module::default_libcall_names())
@ -2039,6 +2036,8 @@ impl CraneliftBuilder {
ret_hint_is_b1: false,
ret_block: None,
ret_slot: None,
pending_blocks: 0,
cur_needs_term: false,
}
}

View File

@ -779,6 +779,52 @@ impl LowerCore {
if dst.is_some() { b.emit_const_i64(0); }
}
} else {
// Await bridge: env.future.await(fut) → await_h + ok_h/err_h select
if iface_name == "env.future" && method_name == "await" {
// Load future: prefer param, then local, then known const, else -1 scan
if let Some(arg0) = args.get(0) {
if let Some(pidx) = self.param_index.get(arg0).copied() {
b.emit_param_i64(pidx);
} else if let Some(slot) = self.local_index.get(arg0).copied() {
b.load_local_i64(slot);
} else if let Some(v) = self.known_i64.get(arg0).copied() {
b.emit_const_i64(v);
} else {
b.emit_const_i64(-1);
}
} else {
b.emit_const_i64(-1);
}
// await_h → handle (0 on timeout)
b.emit_host_call(crate::jit::r#extern::r#async::SYM_FUTURE_AWAIT_H, 1, true);
let hslot = { let id = self.next_local; self.next_local += 1; id };
b.store_local_i64(hslot);
// ok_h(handle)
b.load_local_i64(hslot);
b.emit_host_call(crate::jit::r#extern::result::SYM_RESULT_OK_H, 1, true);
let ok_slot = { let id = self.next_local; self.next_local += 1; id };
b.store_local_i64(ok_slot);
// err_h(0) => Timeout
b.emit_const_i64(0);
b.emit_host_call(crate::jit::r#extern::result::SYM_RESULT_ERR_H, 1, true);
let err_slot = { let id = self.next_local; self.next_local += 1; id };
b.store_local_i64(err_slot);
// Select by (handle==0)
b.load_local_i64(hslot);
b.emit_const_i64(0);
b.emit_compare(crate::jit::lower::builder::CmpKind::Eq);
b.load_local_i64(err_slot);
b.load_local_i64(ok_slot);
b.emit_select_i64();
if let Some(d) = dst {
self.handle_values.insert(*d);
let slot = *self.local_index.entry(*d).or_insert_with(|| { let id = self.next_local; self.next_local += 1; id });
b.store_local_i64(slot);
} else {
// drop
}
return Ok(());
}
// Async spawn bridge: env.future.spawn_instance(recv, method_name, args...)
if iface_name == "env.future" && method_name == "spawn_instance" {
// Stack layout for hostcall: argc_total, a0(recv), a1(method_name), a2(first payload)

View File

@ -1225,15 +1225,34 @@ impl MirBuilder {
self.current_static_box = Some(box_name.clone());
// Look for the main() method
let out = if let Some(main_method) = methods.get("main") {
if let ASTNode::FunctionDeclaration { body, .. } = main_method {
if let ASTNode::FunctionDeclaration { params, body, .. } = main_method {
// Convert the method body to a Program AST node and lower it
let program_ast = ASTNode::Program {
statements: body.clone(),
span: crate::ast::Span::unknown(),
};
// Bind default parameters if present (e.g., args=[])
// Save current var map; inject defaults; restore after lowering
let saved_var_map = std::mem::take(&mut self.variable_map);
// Prepare defaults for known patterns
for p in params.iter() {
let pid = self.value_gen.next();
// Heuristic: for parameter named "args", create new ArrayBox(); else use Void
if p == "args" {
// new ArrayBox() -> pid
// Emit NewBox for ArrayBox with no args
self.emit_instruction(MirInstruction::NewBox { dst: pid, box_type: "ArrayBox".to_string(), args: vec![] })?;
} else {
self.emit_instruction(MirInstruction::Const { dst: pid, value: ConstValue::Void })?;
}
self.variable_map.insert(p.clone(), pid);
}
// Use existing Program lowering logic
self.build_expression(program_ast)
let lowered = self.build_expression(program_ast);
// Restore variable map
self.variable_map = saved_var_map;
lowered
} else {
Err("main method in static box is not a FunctionDeclaration".to_string())
}
@ -1526,6 +1545,65 @@ impl MirBuilder {
return Ok(dst);
}
}
// ExternCall: env.X.* pattern via field access (e.g., env.future.delay)
if let ASTNode::FieldAccess { object: env_obj, field: env_field, .. } = object.clone() {
if let ASTNode::Variable { name: env_name, .. } = *env_obj {
if env_name == "env" {
// Build args first (extern uses evaluated args only)
let mut arg_values = Vec::new();
for arg in &arguments { arg_values.push(self.build_expression(arg.clone())?); }
match (env_field.as_str(), method.as_str()) {
("future", "delay") => {
let result_id = self.value_gen.next();
self.emit_instruction(MirInstruction::ExternCall {
dst: Some(result_id),
iface_name: "env.future".to_string(),
method_name: "delay".to_string(),
args: arg_values,
effects: EffectMask::READ.add(Effect::Io),
})?;
return Ok(result_id);
}
("task", "currentToken") => {
let result_id = self.value_gen.next();
self.emit_instruction(MirInstruction::ExternCall {
dst: Some(result_id),
iface_name: "env.task".to_string(),
method_name: "currentToken".to_string(),
args: arg_values,
effects: EffectMask::READ,
})?;
return Ok(result_id);
}
("task", "cancelCurrent") => {
self.emit_instruction(MirInstruction::ExternCall {
dst: None,
iface_name: "env.task".to_string(),
method_name: "cancelCurrent".to_string(),
args: arg_values,
effects: EffectMask::IO,
})?;
let void_id = self.value_gen.next();
self.emit_instruction(MirInstruction::Const { dst: void_id, value: ConstValue::Void })?;
return Ok(void_id);
}
("console", "log") => {
self.emit_instruction(MirInstruction::ExternCall {
dst: None,
iface_name: "env.console".to_string(),
method_name: "log".to_string(),
args: arg_values,
effects: EffectMask::IO,
})?;
let void_id = self.value_gen.next();
self.emit_instruction(MirInstruction::Const { dst: void_id, value: ConstValue::Void })?;
return Ok(void_id);
}
_ => { /* fallthrough to normal method lowering */ }
}
}
}
}
// ExternCall判定はobjectの変数解決より先に行う未定義変数で落とさない
if let ASTNode::Variable { name: object_name, .. } = object.clone() {
// Build argument expressions first (externはobject自体を使わない)

View File

@ -583,13 +583,14 @@ impl MirBuilder {
pub(super) fn build_await_expression(&mut self, expression: ASTNode) -> Result<ValueId, String> {
// Evaluate the expression (should be a Future)
let future_value = self.build_expression(expression)?;
// Insert checkpoint before await (safepoint)
self.emit_instruction(MirInstruction::Safepoint)?;
// Create result value for the await
let result_id = self.value_gen.next();
// Emit await instruction
self.emit_instruction(MirInstruction::Await { dst: result_id, future: future_value })?;
// Insert checkpoint after await (safepoint)
self.emit_instruction(MirInstruction::Safepoint)?;
Ok(result_id)
}
}

View File

@ -258,6 +258,39 @@ mod tests {
let dump = MirPrinter::new().print_module(&result.module);
assert!(dump.contains("await"), "Expected await in MIR dump. Got:\n{}", dump);
}
#[test]
fn test_await_has_checkpoints() {
use crate::ast::{LiteralValue, Span};
// Build: await 1
let ast = ASTNode::AwaitExpression { expression: Box::new(ASTNode::Literal { value: LiteralValue::Integer(1), span: Span::unknown() }), span: Span::unknown() };
let mut compiler = MirCompiler::new();
let result = compiler.compile(ast).expect("compile");
// Verifier should pass (await flanked by safepoints)
assert!(result.verification_result.is_ok(), "Verifier failed for await checkpoints: {:?}", result.verification_result);
let dump = compiler.dump_mir(&result.module);
// Expect at least two safepoints in the function (before/after await)
let sp_count = dump.matches("safepoint").count();
assert!(sp_count >= 2, "Expected >=2 safepoints around await, got {}. Dump:\n{}", sp_count, dump);
}
#[test]
fn test_rewritten_await_still_checkpoints() {
use crate::ast::{LiteralValue, Span};
// Enable rewrite so Await → ExternCall(env.future.await)
std::env::set_var("NYASH_REWRITE_FUTURE", "1");
let ast = ASTNode::AwaitExpression { expression: Box::new(ASTNode::Literal { value: LiteralValue::Integer(1), span: Span::unknown() }), span: Span::unknown() };
let mut compiler = MirCompiler::new();
let result = compiler.compile(ast).expect("compile");
// Verifier should still pass (checkpoint verification includes ExternCall await)
assert!(result.verification_result.is_ok(), "Verifier failed for rewritten await checkpoints: {:?}", result.verification_result);
let dump = compiler.dump_mir(&result.module);
assert!(dump.contains("env.future.await"), "Expected rewritten await extern call. Dump:\n{}", dump);
let sp_count = dump.matches("safepoint").count();
assert!(sp_count >= 2, "Expected >=2 safepoints around rewritten await, got {}. Dump:\n{}", sp_count, dump);
// Cleanup env
std::env::remove_var("NYASH_REWRITE_FUTURE");
}
#[test]
fn test_throw_compilation() {

View File

@ -256,7 +256,8 @@ impl MirVerifier {
if errors.is_empty() { Ok(()) } else { Err(errors) }
}
/// Ensure that each Await instruction is immediately preceded and followed by a checkpoint
/// Ensure that each Await instruction (or ExternCall(env.future.await)) is immediately
/// preceded and followed by a checkpoint.
/// A checkpoint is either MirInstruction::Safepoint or ExternCall("env.runtime", "checkpoint").
fn verify_await_checkpoints(&self, function: &MirFunction) -> Result<(), Vec<VerificationError>> {
use super::MirInstruction as I;
@ -269,7 +270,12 @@ impl MirVerifier {
for (bid, block) in &function.blocks {
let instrs = &block.instructions;
for (idx, inst) in instrs.iter().enumerate() {
if let I::Await { .. } = inst {
let is_await_like = match inst {
I::Await { .. } => true,
I::ExternCall { iface_name, method_name, .. } => iface_name == "env.future" && method_name == "await",
_ => false,
};
if is_await_like {
// Check immediate previous
if idx == 0 || !is_cp(&instrs[idx - 1]) {
errors.push(VerificationError::MissingCheckpointAroundAwait { block: *bid, instruction_index: idx, position: "before" });