use crate::mir::{MirFunction, MirInstruction, ConstValue, BinaryOp, CompareOp, ValueId}; use super::builder::{IRBuilder, BinOpKind, CmpKind}; mod analysis; mod cfg; mod ops_ext; /// Lower(Core-1): Minimal lowering skeleton for Const/Move/BinOp/Cmp/Branch/Ret /// This does not emit real CLIF yet; it only walks MIR and validates coverage. pub struct LowerCore { pub(crate) unsupported: usize, pub(crate) covered: usize, /// Minimal constant propagation for i64 to feed host-call args pub(super) known_i64: std::collections::HashMap, /// Minimal constant propagation for f64 (math.* signature checks) pub(super) known_f64: std::collections::HashMap, /// Minimal constant propagation for String literals pub(super) known_str: std::collections::HashMap, /// Parameter index mapping for ValueId pub(super) param_index: std::collections::HashMap, /// Track values produced by Phi (for minimal PHI path) pub(super) phi_values: std::collections::HashSet, /// Map (block, phi dst) -> param index in that block (for multi-PHI) pub(super) phi_param_index: std::collections::HashMap<(crate::mir::BasicBlockId, ValueId), usize>, /// Track values that are boolean (b1) results, e.g., Compare destinations pub(super) bool_values: std::collections::HashSet, /// Track PHI destinations that are boolean (all inputs derived from bool_values) pub(super) bool_phi_values: std::collections::HashSet, /// Track values that are FloatBox instances (for arg type classification) pub(super) float_box_values: std::collections::HashSet, /// Track values that are plugin handles (generic box/handle, type unknown at compile time) pub(super) handle_values: std::collections::HashSet, // Per-function statistics (last lowered) last_phi_total: u64, last_phi_b1: u64, last_ret_bool_hint_used: bool, // Minimal local slot mapping for Load/Store (ptr ValueId -> slot index) pub(super) local_index: std::collections::HashMap, pub(super) next_local: usize, /// Track NewBox origins: ValueId -> box type name (e.g., "PyRuntimeBox") pub(super) box_type_map: std::collections::HashMap, } impl LowerCore { pub fn new() -> Self { Self { unsupported: 0, covered: 0, known_i64: std::collections::HashMap::new(), known_f64: std::collections::HashMap::new(), known_str: std::collections::HashMap::new(), param_index: std::collections::HashMap::new(), phi_values: std::collections::HashSet::new(), phi_param_index: std::collections::HashMap::new(), bool_values: std::collections::HashSet::new(), bool_phi_values: std::collections::HashSet::new(), float_box_values: std::collections::HashSet::new(), handle_values: std::collections::HashSet::new(), last_phi_total: 0, last_phi_b1: 0, last_ret_bool_hint_used: false, local_index: std::collections::HashMap::new(), next_local: 0, box_type_map: std::collections::HashMap::new() } } /// Get statistics for the last lowered function pub fn last_stats(&self) -> (u64, u64, bool) { (self.last_phi_total, self.last_phi_b1, self.last_ret_bool_hint_used) } /// Walk the MIR function and count supported/unsupported instructions. /// In the future, this will build CLIF via Cranelift builders. pub fn lower_function(&mut self, func: &MirFunction, builder: &mut dyn IRBuilder) -> Result<(), String> { // Prepare ABI based on MIR signature // Reset per-function stats self.last_phi_total = 0; self.last_phi_b1 = 0; self.last_ret_bool_hint_used = false; // Build param index map self.param_index.clear(); for (i, v) in func.params.iter().copied().enumerate() { self.param_index.insert(v, i); } // Prepare block mapping (Phase 10.7): deterministic ordering by sorted keys let mut bb_ids: Vec<_> = func.blocks.keys().copied().collect(); bb_ids.sort_by_key(|b| b.0); builder.prepare_blocks(bb_ids.len()); self.analyze(func, &bb_ids); // Optional: collect PHI targets and ordering per successor for minimal/multi PHI path let cfg_now = crate::jit::config::current(); let enable_phi_min = cfg_now.phi_min; // Build successor → phi order and predeclare block params let succ_phi_order: std::collections::HashMap> = self.build_phi_succords(func, &bb_ids, builder, enable_phi_min); // Decide ABI: typed or i64-only let native_f64 = cfg_now.native_f64; let native_bool = cfg_now.native_bool; let mut use_typed = false; let mut kinds: Vec = Vec::new(); for mt in func.signature.params.iter() { let k = match mt { crate::mir::MirType::Float if native_f64 => { use_typed = true; super::builder::ParamKind::F64 } crate::mir::MirType::Bool if native_bool => { use_typed = true; super::builder::ParamKind::B1 } _ => super::builder::ParamKind::I64, }; kinds.push(k); } let ret_is_f64 = native_f64 && matches!(func.signature.return_type, crate::mir::MirType::Float); // Hint return bool footing (no-op in current backend; keeps switch point centralized) let ret_is_bool = matches!(func.signature.return_type, crate::mir::MirType::Bool); if ret_is_bool { builder.hint_ret_bool(true); // Track how many functions are lowered with boolean return hint (for stats) crate::jit::rt::ret_bool_hint_inc(1); self.last_ret_bool_hint_used = true; } let has_ret = !matches!(func.signature.return_type, crate::mir::MirType::Void); if use_typed || ret_is_f64 { builder.prepare_signature_typed(&kinds, ret_is_f64 && has_ret); } else { builder.prepare_signature_i64(func.params.len(), has_ret); } // Pre-scan FloatBox creations across all blocks for arg classification self.float_box_values.clear(); for bb in bb_ids.iter() { if let Some(block) = func.blocks.get(bb) { for ins in block.instructions.iter() { if let crate::mir::MirInstruction::NewBox { dst, box_type, .. } = ins { if box_type == "FloatBox" { self.float_box_values.insert(*dst); } } if let crate::mir::MirInstruction::Copy { dst, src } = ins { if self.float_box_values.contains(src) { self.float_box_values.insert(*dst); } } } } } // Pre-scan to map NewBox origins: ValueId -> box type name; propagate via Copy self.box_type_map.clear(); for bb in bb_ids.iter() { if let Some(block) = func.blocks.get(bb) { for ins in block.instructions.iter() { if let crate::mir::MirInstruction::NewBox { dst, box_type, .. } = ins { self.box_type_map.insert(*dst, box_type.clone()); } if let crate::mir::MirInstruction::Copy { dst, src } = ins { if let Some(name) = self.box_type_map.get(src).cloned() { self.box_type_map.insert(*dst, name); } } } } } builder.begin_function(&func.signature.name); // Iterate blocks in the sorted order to keep indices stable self.phi_values.clear(); self.phi_param_index.clear(); self.float_box_values.clear(); self.handle_values.clear(); for (idx, bb_id) in bb_ids.iter().enumerate() { let bb = func.blocks.get(bb_id).unwrap(); builder.switch_to_block(idx); // Pre-scan PHIs in this block and ensure block parameters count (multi-PHI) if enable_phi_min { let mut local_phi_order: Vec = Vec::new(); // Also detect boolean PHIs: inputs all from boolean-producing values for ins in bb.instructions.iter() { if let crate::mir::MirInstruction::Phi { dst, inputs } = ins { local_phi_order.push(*dst); // decide if this phi is boolean if inputs.iter().all(|(_, v)| self.bool_values.contains(v)) && !inputs.is_empty() { self.bool_phi_values.insert(*dst); } } } if !local_phi_order.is_empty() { builder.ensure_block_params_i64(idx, local_phi_order.len()); for (i, v) in local_phi_order.into_iter().enumerate() { self.phi_values.insert(v); self.phi_param_index.insert((*bb_id, v), i); } } } for instr in bb.instructions.iter() { self.cover_if_supported(instr); if let Err(e) = self.try_emit(builder, instr, *bb_id, func) { return Err(e); } // Track FloatBox creations for later arg classification if let crate::mir::MirInstruction::NewBox { dst, box_type, .. } = instr { if box_type == "FloatBox" { self.float_box_values.insert(*dst); } } if let crate::mir::MirInstruction::Copy { dst, src } = instr { if self.float_box_values.contains(src) { self.float_box_values.insert(*dst); } } } if let Some(term) = &bb.terminator { self.cover_if_supported(term); // Branch/Jump need block mapping: pass indices match term { crate::mir::MirInstruction::Branch { condition, then_bb, else_bb } => { self.lower_branch_terminator(builder, func, &bb_ids, *bb_id, condition, then_bb, else_bb, &succ_phi_order, enable_phi_min); } crate::mir::MirInstruction::Jump { target } => { self.lower_jump_terminator(builder, func, &bb_ids, *bb_id, target, &succ_phi_order, enable_phi_min); } _ => { /* other terminators handled via generic emission below */ } } // Also allow other terminators to be emitted if needed if let Err(e) = self.try_emit(builder, term, *bb_id, func) { return Err(e); } } } builder.end_function(); // Dump CFG/PHI diagnostics self.dump_phi_cfg(&succ_phi_order, func, bb_ids.len(), enable_phi_min); Ok(()) } /// 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. fn emit_len_with_fallback_param(&mut self, b: &mut dyn IRBuilder, pidx: usize) { use 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("nyash.handle.of", 1, true); b.store_local_i64(hslot); // String.len_h b.load_local_i64(hslot); b.emit_host_call("nyash.string.len_h", 1, true); b.store_local_i64(t_string); // Any.length_h 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); // 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); // 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(); } fn emit_len_with_fallback_local_handle(&mut self, b: &mut dyn IRBuilder, slot: usize) { use 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 b.load_local_i64(slot); b.emit_host_call("nyash.string.len_h", 1, true); b.store_local_i64(t_string); // Any.length_h 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); // 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); // 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(); } fn emit_len_with_fallback_literal(&mut self, b: &mut dyn IRBuilder, s: &str) { use 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 b.emit_string_handle_from_literal(s); b.emit_host_call("nyash.string.len_h", 1, true); b.store_local_i64(t_string); // Any.length_h on literal handle (recreate handle; safe in v0) 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); // 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); // 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(); } fn try_emit(&mut self, b: &mut dyn IRBuilder, instr: &MirInstruction, cur_bb: crate::mir::BasicBlockId, func: &crate::mir::MirFunction) -> Result<(), String> { use crate::mir::MirInstruction as I; match instr { I::Call { dst, func, args, .. } => { // FunctionBox call shim: emit hostcall nyash_fn_callN(func_h, args...) // Push function operand (param or known) self.push_value_if_known_or_param(b, func); // Push up to 4 args (unknown become iconst 0 via helper) for a in args.iter() { self.push_value_if_known_or_param(b, a); } // Choose symbol by arity let argc = args.len(); let sym = match argc { 0 => "nyash_fn_call0", 1 => "nyash_fn_call1", 2 => "nyash_fn_call2", 3 => "nyash_fn_call3", 4 => "nyash_fn_call4", 5 => "nyash_fn_call5", 6 => "nyash_fn_call6", 7 => "nyash_fn_call7", _ => "nyash_fn_call8", }; // Emit typed call: all params as I64, returning I64 handle // Build param kinds vector: 1 (func) + argc (args) let mut params: Vec = Vec::new(); params.push(crate::jit::lower::builder::ParamKind::I64); for _ in 0..core::cmp::min(argc, 8) { params.push(crate::jit::lower::builder::ParamKind::I64); } b.emit_host_call_typed(sym, ¶ms, true, false); // Mark destination as handle-like if let Some(d) = dst { self.handle_values.insert(*d); } } I::Await { dst, future } => { // Push future param index when known; otherwise -1 to trigger legacy search in shim if let Some(pidx) = self.param_index.get(future).copied() { b.emit_param_i64(pidx); } else { b.emit_const_i64(-1); } // Call await_h to obtain a handle to the value (0 on timeout) b.emit_host_call(crate::jit::r#extern::r#async::SYM_FUTURE_AWAIT_H, 1, true); // Store the awaited handle temporarily let hslot = { let id = self.next_local; self.next_local += 1; id }; b.store_local_i64(hslot); // Build Ok result: 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); // Build Err result: 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); // Cond: (handle == 0) b.load_local_i64(hslot); b.emit_const_i64(0); b.emit_compare(crate::jit::lower::builder::CmpKind::Eq); // Stack for select: cond, then(err), else(ok) b.load_local_i64(err_slot); b.load_local_i64(ok_slot); b.emit_select_i64(); // Store selected Result handle to destination let 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); } I::Safepoint => { // Emit a runtime checkpoint (safepoint + scheduler poll via NyRT/JIT stubs) b.emit_host_call(crate::jit::r#extern::runtime::SYM_RT_CHECKPOINT, 0, false); } I::RefGet { dst, reference: _, field } => { // Minimal: env.console をハンドル化(hostcall) if field == "console" { // Emit hostcall to create/get ConsoleBox handle // Symbol exported by nyrt: nyash.console.birth_h b.emit_host_call("nyash.console.birth_h", 0, true); } else { // Unknown RefGet: treat as no-op const 0 to avoid strict fail for now b.emit_const_i64(0); } // Record as covered; do not increment unsupported let _ = dst; // keep signature parity } I::UnaryOp { dst: _, op, operand } => { match op { crate::mir::UnaryOp::Neg => { // i64-only minimal: 0 - operand // Try known const or param // push 0 b.emit_const_i64(0); // push operand (known/param) self.push_value_if_known_or_param(b, operand); b.emit_binop(BinOpKind::Sub); } _ => { self.unsupported += 1; } } } I::NewBox { dst, box_type, args } => { // 最適化は後段へ(現状は汎用・安全な実装に徹する) // 通常経路: // - 引数なし: 汎用 birth_h(type_idのみ)でハンドル生成 // - 引数あり: 既存のチェーン(直後の plugin_invoke birth で初期化)を維持(段階的導入) if args.is_empty() { // 文字列の型名からインスタンスを生成(グローバルUnifiedRegistry経由) // name → u64x2 パックで渡す let name = box_type.clone(); { let name_bytes = name.as_bytes(); let mut lo: u64 = 0; let mut hi: u64 = 0; let take = core::cmp::min(16, name_bytes.len()); for i in 0..take.min(8) { lo |= (name_bytes[i] as u64) << (8 * i as u32); } for i in 8..take { hi |= (name_bytes[i] as u64) << (8 * (i - 8) as u32); } // Push immediates b.emit_const_i64(lo as i64); b.emit_const_i64(hi as i64); b.emit_const_i64(name_bytes.len() as i64); // Call import (lo, hi, len) -> handle // Use typed hostcall (I64,I64,I64)->I64 b.emit_host_call_typed("nyash.instance.birth_name_u64x2", &[crate::jit::lower::builder::ParamKind::I64, crate::jit::lower::builder::ParamKind::I64, crate::jit::lower::builder::ParamKind::I64], true, false); 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); } } else { // 引数あり: 安全なパターンから段階的に birth_i64 に切替 // 1) IntegerBox(const i64) if box_type == "IntegerBox" && args.len() == 1 { if let Some(src) = args.get(0) { if let Some(iv) = self.known_i64.get(src).copied() { // 汎用 birth_i64(type_id, argc=1, a1=iv) if let crate::jit::policy::invoke::InvokeDecision::PluginInvoke { type_id, .. } = crate::jit::policy::invoke::decide_box_method(box_type, "birth", 1, true) { b.emit_const_i64(type_id as i64); b.emit_const_i64(1); b.emit_const_i64(iv); b.emit_host_call("nyash.box.birth_i64", 3, 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); // 値伝搬も継続 self.known_i64.insert(*dst, iv); return Ok(()); } } } } // 2) StringBox(const string) → 文字列リテラルから直接ハンドル生成 if box_type == "StringBox" && args.len() == 1 { if let Some(src) = args.get(0) { // 探索: 同一関数内で src を定義する Const(String) let mut lit: Option = None; for (_bid, bb) in func.blocks.iter() { for ins in bb.instructions.iter() { if let crate::mir::MirInstruction::Const { dst: cdst, value } = ins { if cdst == src { if let crate::mir::ConstValue::String(s) = value { lit = Some(s.clone()); } break; } } } if lit.is_some() { break; } } if let Some(s) = lit { b.emit_string_handle_from_literal(&s); 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 Ok(()); } } } // 2) 引数がハンドル(StringBox等)で既に存在する場合(最大2引数) if args.len() <= 2 && args.iter().all(|a| self.handle_values.contains(a)) { if let crate::jit::policy::invoke::InvokeDecision::PluginInvoke { type_id, .. } = crate::jit::policy::invoke::decide_box_method(box_type, "birth", args.len(), true) { b.emit_const_i64(type_id as i64); b.emit_const_i64(args.len() as i64); // a1, a2 を push(ローカルに保存済みのハンドルをロード) for a in args.iter().take(2) { self.push_value_if_known_or_param(b, a); } b.emit_host_call("nyash.box.birth_i64", 2 + args.len(), 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 Ok(()); } } // フォールバック: 既存チェーンに委譲(互換)+ 既知値伝搬のみ if box_type == "IntegerBox" { if let Some(src) = args.get(0) { if let Some(iv) = self.known_i64.get(src).copied() { self.known_i64.insert(*dst, iv); } } } } // Track boxed numeric literals to aid signature checks (FloatBox/IntegerBox) if box_type == "FloatBox" { if let Some(src) = args.get(0) { if let Some(fv) = self.known_f64.get(src).copied() { self.known_f64.insert(*dst, fv); } else if let Some(iv) = self.known_i64.get(src).copied() { self.known_f64.insert(*dst, iv as f64); } } } else if box_type == "IntegerBox" { if let Some(src) = args.get(0) { if let Some(iv) = self.known_i64.get(src).copied() { self.known_i64.insert(*dst, iv); } } } } I::PluginInvoke { dst, box_val, method, args, .. } => { self.lower_plugin_invoke(b, &dst, &box_val, method.as_str(), args, func)?; } I::ExternCall { dst, iface_name, method_name, args, .. } => { self.lower_extern_call(b, &dst, iface_name.as_str(), method_name.as_str(), args, func)?; } I::Cast { dst, value, target_type } => { // Minimal cast footing: materialize source when param/known // Bool→Int: rely on producers (compare) and branch/b1 loaders; here we just reuse integer path self.push_value_if_known_or_param(b, value); // Track known i64 if source known if let Some(v) = self.known_i64.get(value).copied() { self.known_i64.insert(*dst, v); } // Track known f64 for float casts if matches!(target_type, crate::mir::MirType::Float) { if let Some(iv) = self.known_i64.get(value).copied() { self.known_f64.insert(*dst, iv as f64); } } } I::Const { dst, value } => match value { ConstValue::Integer(i) => { b.emit_const_i64(*i); self.known_i64.insert(*dst, *i); } ConstValue::Float(f) => { b.emit_const_f64(*f); self.known_f64.insert(*dst, *f); } ConstValue::Bool(bv) => { let iv = if *bv { 1 } else { 0 }; b.emit_const_i64(iv); self.known_i64.insert(*dst, iv); // Mark this value as boolean producer self.bool_values.insert(*dst); } ConstValue::String(sv) => { self.known_str.insert(*dst, sv.clone()); } ConstValue::Null | ConstValue::Void => { } }, I::Copy { dst, src } => { if let Some(v) = self.known_i64.get(src).copied() { self.known_i64.insert(*dst, v); } if let Some(v) = self.known_f64.get(src).copied() { self.known_f64.insert(*dst, v); } if let Some(v) = self.known_str.get(src).cloned() { self.known_str.insert(*dst, v); } // Propagate boolean classification through Copy if self.bool_values.contains(src) { self.bool_values.insert(*dst); } // If source is a parameter, materialize it on the stack for downstream ops and persist into dst slot if let Some(pidx) = self.param_index.get(src).copied() { b.emit_param_i64(pidx); let slot = *self.local_index.entry(*dst).or_insert_with(|| { let id = self.next_local; self.next_local += 1; id }); b.ensure_local_i64(slot); b.store_local_i64(slot); } else if let Some(src_slot) = self.local_index.get(src).copied() { // If source already has a local slot (e.g., a handle), copy into dst's slot b.load_local_i64(src_slot); let dst_slot = *self.local_index.entry(*dst).or_insert_with(|| { let id = self.next_local; self.next_local += 1; id }); b.ensure_local_i64(dst_slot); b.store_local_i64(dst_slot); } } I::BinOp { dst, op, lhs, rhs } => { self.lower_binop(b, op, lhs, rhs, dst, func); } I::Compare { op, lhs, rhs, dst } => { self.lower_compare(b, op, lhs, rhs, dst, func); } I::Jump { .. } => self.lower_jump(b), I::Branch { .. } => self.lower_branch(b), I::Return { value } => { if let Some(v) = value { // Prefer known/param/materialized path if self.known_i64.get(v).is_some() || self.param_index.get(v).is_some() || self.local_index.get(v).is_some() { self.push_value_if_known_or_param(b, v); } else { // Fallback: search a Const definition for this value in the current block and emit directly if let Some(bb) = func.blocks.get(&cur_bb) { for ins in bb.instructions.iter() { if let crate::mir::MirInstruction::Const { dst, value: cval } = ins { if dst == v { match cval { crate::mir::ConstValue::Integer(i) => { b.emit_const_i64(*i); } crate::mir::ConstValue::Bool(bv) => { b.emit_const_i64(if *bv {1} else {0}); } crate::mir::ConstValue::Float(f) => { b.emit_const_f64(*f); } _ => {} } break; } } } } } } b.emit_return() } I::Store { value, ptr } => { // Minimal lowering: materialize value if known/param and store to a local slot keyed by ptr self.push_value_if_known_or_param(b, value); let slot = *self.local_index.entry(*ptr).or_insert_with(|| { let id = self.next_local; self.next_local += 1; id }); b.ensure_local_i64(slot); b.store_local_i64(slot); } I::Load { dst, ptr } => { // Minimal lowering: load from local slot keyed by ptr, then materialize into dst's own slot let src_slot = *self.local_index.entry(*ptr).or_insert_with(|| { let id = self.next_local; self.next_local += 1; id }); b.ensure_local_i64(src_slot); b.load_local_i64(src_slot); // Persist into dst's slot to make subsequent uses find it via local_index let dst_slot = *self.local_index.entry(*dst).or_insert_with(|| { let id = self.next_local; self.next_local += 1; id }); b.ensure_local_i64(dst_slot); b.store_local_i64(dst_slot); } I::Phi { dst, .. } => { // PHI をローカルに materialize して後続の Return で安定参照 let pos = self.phi_param_index.get(&(cur_bb, *dst)).copied().unwrap_or(0); if self.bool_phi_values.contains(dst) { b.push_block_param_b1_at(pos); } else { b.push_block_param_i64_at(pos); } let slot = *self.local_index.entry(*dst).or_insert_with(|| { let id = self.next_local; self.next_local += 1; id }); b.ensure_local_i64(slot); b.store_local_i64(slot); } I::ArrayGet { array, index, .. } => { // Prepare receiver + index on stack let argc = 2usize; if let Some(pidx) = self.param_index.get(array).copied() { b.emit_param_i64(pidx); } else { b.emit_const_i64(-1); } if let Some(iv) = self.known_i64.get(index).copied() { b.emit_const_i64(iv); } else { self.push_value_if_known_or_param(b, index); } // Decide policy let decision = crate::jit::policy::invoke::decide_box_method("ArrayBox", "get", argc, true); match decision { crate::jit::policy::invoke::InvokeDecision::PluginInvoke { type_id, method_id, box_type, .. } => { b.emit_plugin_invoke(type_id, method_id, argc, true); crate::jit::observe::lower_plugin_invoke(&box_type, "get", type_id, method_id, argc); } crate::jit::policy::invoke::InvokeDecision::HostCall { symbol, .. } => { crate::jit::observe::lower_hostcall(&symbol, argc, &["Handle","I64"], "allow", "mapped_symbol"); b.emit_host_call(&symbol, argc, true); } _ => super::core_hostcall::lower_array_get(b, &self.param_index, &self.known_i64, array, index), } } I::ArraySet { array, index, value } => { let argc = 3usize; if let Some(pidx) = self.param_index.get(array).copied() { b.emit_param_i64(pidx); } else { b.emit_const_i64(-1); } // GC write barrier hint for mutating array operations (pass receiver handle/index as site id: receiver preferred) b.emit_host_call(crate::jit::r#extern::runtime::SYM_GC_BARRIER_WRITE, 1, false); if let Some(iv) = self.known_i64.get(index).copied() { b.emit_const_i64(iv); } else { self.push_value_if_known_or_param(b, index); } if let Some(vv) = self.known_i64.get(value).copied() { b.emit_const_i64(vv); } else { self.push_value_if_known_or_param(b, value); } let decision = crate::jit::policy::invoke::decide_box_method("ArrayBox", "set", argc, false); match decision { crate::jit::policy::invoke::InvokeDecision::PluginInvoke { type_id, method_id, box_type, .. } => { b.emit_plugin_invoke(type_id, method_id, argc, false); crate::jit::observe::lower_plugin_invoke(&box_type, "set", type_id, method_id, argc); } crate::jit::policy::invoke::InvokeDecision::HostCall { symbol, .. } => { crate::jit::observe::lower_hostcall(&symbol, argc, &["Handle","I64","I64"], "allow", "mapped_symbol"); b.emit_host_call(&symbol, argc, false); } _ => super::core_hostcall::lower_array_set(b, &self.param_index, &self.known_i64, array, index, value), } } I::BoxCall { box_val: array, method, args, dst, .. } => { // Prefer ops_ext; if not handled, fall back to legacy path below if self.lower_box_call(func, b, &array, method.as_str(), args, dst.clone())? { return Ok(()); } } /* legacy BoxCall branch removed (now handled in ops_ext) // handled in helper (read-only simple methods) } else if matches!(method.as_str(), "sin" | "cos" | "abs" | "min" | "max") { super::core_hostcall::lower_math_call( func, b, &self.known_i64, &self.known_f64, &self.float_box_values, method.as_str(), args, dst.clone(), ); } else if false /* moved to ops_ext: NYASH_USE_PLUGIN_BUILTINS */ { // StringBox(length/is_empty/charCodeAt): policy+observe経由に統一 if matches!(method.as_str(), "length" | "is_empty" | "charCodeAt") { // receiver if let Some(pidx) = self.param_index.get(array).copied() { b.emit_param_i64(pidx); } else { b.emit_const_i64(-1); } let mut argc = 1usize; if method.as_str() == "charCodeAt" { if let Some(v) = args.get(0) { self.push_value_if_known_or_param(b, v); } else { b.emit_const_i64(0); } argc = 2; } if method.as_str() == "is_empty" { b.hint_ret_bool(true); } let decision = crate::jit::policy::invoke::decide_box_method("StringBox", method.as_str(), argc, dst.is_some()); match decision { crate::jit::policy::invoke::InvokeDecision::PluginInvoke { type_id, method_id, box_type, .. } => { b.emit_plugin_invoke(type_id, method_id, argc, dst.is_some()); crate::jit::observe::lower_plugin_invoke(&box_type, method.as_str(), type_id, method_id, argc); return Ok(()); } crate::jit::policy::invoke::InvokeDecision::HostCall { symbol, .. } => { crate::jit::observe::lower_hostcall(&symbol, argc, &if argc==1 { ["Handle"][..].to_vec() } else { ["Handle","I64"][..].to_vec() }, "allow", "mapped_symbol"); b.emit_host_call(&symbol, argc, dst.is_some()); return Ok(()); } _ => {} } } // Integer.get/set specialized when receiver is Integer (avoid Map collision) if matches!(method.as_str(), "get" | "set") { let recv_is_int = func.metadata.value_types.get(array).map(|mt| matches!(mt, crate::mir::MirType::Integer)).unwrap_or(false); if recv_is_int { if let Ok(ph) = crate::runtime::plugin_loader_unified::get_global_plugin_host().read() { if let Ok(h) = ph.resolve_method("IntegerBox", method.as_str()) { if let Some(pidx) = self.param_index.get(array).copied() { b.emit_param_i64(pidx); } else { b.emit_const_i64(-1); } let mut argc = 1usize; if method.as_str() == "set" { if let Some(v) = args.get(0) { self.push_value_if_known_or_param(b, v); } else { b.emit_const_i64(0); } argc = 2; } b.emit_plugin_invoke(h.type_id, h.method_id, argc, dst.is_some()); crate::jit::events::emit_lower( serde_json::json!({ "id": format!("plugin:{}:{}", h.box_type, method.as_str()), "decision":"allow","reason":"plugin_invoke","argc": argc, "type_id": h.type_id, "method_id": h.method_id }), "plugin","" ); return Ok(()); } } } } match method.as_str() { "len" | "length" => { // Resolve ArrayBox plugin method and emit plugin_invoke (symbolic) if let Ok(ph) = crate::runtime::plugin_loader_unified::get_global_plugin_host().read() { let mname = "length"; if let Ok(h) = ph.resolve_method("ArrayBox", mname) { // Receiver if let Some(pidx) = self.param_index.get(array).copied() { b.emit_param_i64(pidx); } else { b.emit_const_i64(-1); } let mut argc = 1usize; // length only b.emit_plugin_invoke(h.type_id, h.method_id, argc, dst.is_some()); crate::jit::events::emit_lower( serde_json::json!({ "id": format!("plugin:{}:{}", h.box_type, mname), "decision":"allow","reason":"plugin_invoke","argc": argc, "type_id": h.type_id, "method_id": h.method_id }), "plugin","" ); } } } // Map: size/get/has (RO) and set (mutating; allowed only when policy.read_only=false) "size" | "get" | "has" | "set" => { if let Ok(ph) = crate::runtime::plugin_loader_unified::get_global_plugin_host().read() { if let Ok(h) = ph.resolve_method("MapBox", method.as_str()) { if method.as_str() == "set" && crate::jit::policy::current().read_only { // Deny mutating under read-only policy crate::jit::events::emit_lower( serde_json::json!({ "id": format!("plugin:{}:{}", "MapBox", "set"), "decision":"fallback","reason":"policy_denied_mutating" }), "plugin","" ); // Do not emit plugin call; VM path will handle return Ok(()); } if let Some(pidx) = self.param_index.get(array).copied() { b.emit_param_i64(pidx); } else { b.emit_const_i64(-1); } // Insert GC write barrier before mutating Map.set if method.as_str() == "set" { b.emit_host_call(crate::jit::r#extern::runtime::SYM_GC_BARRIER_WRITE, 1, false); } let mut argc = 1usize; if matches!(method.as_str(), "get" | "has") { if let Some(v) = args.get(0) { self.push_value_if_known_or_param(b, v); } else { b.emit_const_i64(0); } argc += 1; } else if method.as_str() == "set" { if let Some(k) = args.get(0) { self.push_value_if_known_or_param(b, k); } else { b.emit_const_i64(0); } if let Some(v) = args.get(1) { self.push_value_if_known_or_param(b, v); } else { b.emit_const_i64(0); } argc += 2; } b.emit_plugin_invoke(h.type_id, h.method_id, argc, dst.is_some()); crate::jit::events::emit_lower( serde_json::json!({ "id": format!("plugin:{}:{}", h.box_type, method.as_str()), "decision":"allow","reason":"plugin_invoke","argc": argc, "type_id": h.type_id, "method_id": h.method_id }), "plugin","" ); } } } _ => { /* other BoxCalls handled below */ } } } else if crate::jit::config::current().hostcall { match method.as_str() { "len" | "length" => { // Constant fold: if receiver is NewBox(StringBox, Const String), return its length directly if let Some(did) = dst.as_ref() { let mut lit_len: Option = None; for (_bid, bb) in func.blocks.iter() { for ins in bb.instructions.iter() { if let crate::mir::MirInstruction::NewBox { dst: ndst, box_type, args } = ins { if ndst == array && box_type == "StringBox" && args.len() == 1 { let src = args[0]; if let Some(s) = self.known_str.get(&src) { lit_len = Some(s.len() as i64); break; } // scan Const directly for (_b2, bb2) in func.blocks.iter() { for ins2 in bb2.instructions.iter() { if let crate::mir::MirInstruction::Const { dst: cdst, value } = ins2 { if *cdst == src { if let crate::mir::ConstValue::String(sv) = value { lit_len = Some(sv.len() as i64); break; } } } } if lit_len.is_some() { break; } } } } } if lit_len.is_some() { break; } } if let Some(n) = lit_len { b.emit_const_i64(n); self.known_i64.insert(*did, n); return Ok(()); } } if let Some(pidx) = self.param_index.get(array).copied() { // Param 経路: string.len_h → 0 の場合 any.length_h へフォールバック self.emit_len_with_fallback_param(b, pidx); } else { crate::jit::events::emit_lower( serde_json::json!({"id": crate::jit::r#extern::collections::SYM_ANY_LEN_H, "decision":"fallback", "reason":"receiver_not_param", "argc":1, "arg_types":["Handle"]}), "hostcall","" ); // Try local handle (AOT/JIT-AOT) before legacy index fallback if let Some(slot) = self.local_index.get(array).copied() { // ローカルハンドル: string.len_h → any.length_h フォールバック self.emit_len_with_fallback_local_handle(b, slot); } else if self.box_type_map.get(array).map(|s| s == "StringBox").unwrap_or(false) { // Attempt reconstruction for StringBox literal: scan NewBox(StringBox, Const String) let mut lit: Option = None; for (_bid, bb) in func.blocks.iter() { for ins in bb.instructions.iter() { if let crate::mir::MirInstruction::NewBox { dst, box_type, args } = ins { if dst == array && box_type == "StringBox" && args.len() == 1 { if let Some(src) = args.get(0) { if let Some(s) = self.known_str.get(src).cloned() { lit = Some(s); break; } // Also scan Const directly for (_bid2, bb2) in func.blocks.iter() { for ins2 in bb2.instructions.iter() { if let crate::mir::MirInstruction::Const { dst: cdst, value } = ins2 { if cdst == src { if let crate::mir::ConstValue::String(sv) = value { lit = Some(sv.clone()); break; } } } } if lit.is_some() { break; } } } } } } if lit.is_some() { break; } } if let Some(s) = lit { // リテラル復元: string.len_h → any.length_h フォールバック self.emit_len_with_fallback_literal(b, &s); } else { let arr_idx = -1; b.emit_const_i64(arr_idx); b.emit_host_call(crate::jit::r#extern::collections::SYM_ARRAY_LEN, 1, dst.is_some()); } } else { let arr_idx = -1; b.emit_const_i64(arr_idx); b.emit_host_call(crate::jit::r#extern::collections::SYM_ARRAY_LEN, 1, dst.is_some()); } } } // math.* minimal boundary: use registry signature to decide allow/fallback (no actual hostcall yet) "sin" | "cos" | "abs" | "min" | "max" => { use crate::jit::hostcall_registry::{check_signature, ArgKind}; // Build symbol and observed arg kinds (f64 if known float, else i64) let sym = format!("nyash.math.{}", method); let mut observed: Vec = Vec::new(); for v in args.iter() { if self.known_f64.contains_key(v) { observed.push(ArgKind::F64); } else { observed.push(ArgKind::I64); } } // Prepare arg_types for event payload // Classify argument kinds using TyEnv when available; fallback to known maps/FloatBox tracking let mut observed_kinds: Vec = Vec::new(); for v in args.iter() { let kind = if let Some(mt) = func.metadata.value_types.get(v) { match mt { crate::mir::MirType::Float => crate::jit::hostcall_registry::ArgKind::F64, crate::mir::MirType::Integer => crate::jit::hostcall_registry::ArgKind::I64, crate::mir::MirType::Bool => crate::jit::hostcall_registry::ArgKind::I64, // b1はI64 0/1に正規化 crate::mir::MirType::String | crate::mir::MirType::Box(_) => crate::jit::hostcall_registry::ArgKind::Handle, _ => { if self.known_f64.contains_key(v) || self.float_box_values.contains(v) { crate::jit::hostcall_registry::ArgKind::F64 } else { crate::jit::hostcall_registry::ArgKind::I64 } } } } else { if self.known_f64.contains_key(v) || self.float_box_values.contains(v) { crate::jit::hostcall_registry::ArgKind::F64 } else { crate::jit::hostcall_registry::ArgKind::I64 } }; observed_kinds.push(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(); match check_signature(&sym, &observed_kinds) { Ok(()) => { // allow: record decision; execution remains on VM for now (thin bridge) crate::jit::events::emit_lower( serde_json::json!({ "id": sym, "decision": "allow", "reason": "sig_ok", "argc": observed.len(), "arg_types": arg_types }), "hostcall","" ); // If native f64 is enabled, emit a typed hostcall to math extern if crate::jit::config::current().native_f64 { let (symbol, arity) = match method.as_str() { "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), }; // Push f64 args from known_f64 or coerce known_i64 for i in 0..arity { if let Some(v) = args.get(i) { // Try direct known values if let Some(fv) = self.known_f64.get(v).copied() { b.emit_const_f64(fv); continue; } if let Some(iv) = self.known_i64.get(v).copied() { b.emit_const_f64(iv as f64); continue; } // Try unwrap FloatBox: scan blocks to find NewBox FloatBox { args: [src] } and reuse src const 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) = self.known_f64.get(srcv).copied() { b.emit_const_f64(fv); emitted = true; break 'scan; } if let Some(iv) = self.known_i64.get(srcv).copied() { b.emit_const_f64(iv as f64); emitted = true; break 'scan; } } } } } } if !emitted { b.emit_const_f64(0.0); } } else { b.emit_const_f64(0.0); } } let kinds: Vec = (0..arity).map(|_| super::builder::ParamKind::F64).collect(); b.emit_host_call_typed(symbol, &kinds, dst.is_some(), true); } } Err(reason) => { crate::jit::events::emit_lower( serde_json::json!({ "id": sym, "decision": "fallback", "reason": reason, "argc": observed.len(), "arg_types": arg_types }), "hostcall", "" ); } } // no-op: VM側で実行される } "isEmpty" | "empty" => { if let Some(pidx) = self.param_index.get(array).copied() { crate::jit::events::emit( "hostcall","",None,None, serde_json::json!({"id": crate::jit::r#extern::collections::SYM_ANY_IS_EMPTY_H, "decision":"allow", "reason":"sig_ok", "argc":1, "arg_types":["Handle"]}) ); b.emit_param_i64(pidx); // returns i64 0/1 b.emit_host_call(crate::jit::r#extern::collections::SYM_ANY_IS_EMPTY_H, 1, dst.is_some()); } else { crate::jit::events::emit_lower( serde_json::json!({"id": crate::jit::r#extern::collections::SYM_ANY_IS_EMPTY_H, "decision":"fallback", "reason":"receiver_not_param", "argc":1, "arg_types":["Handle"]}), "hostcall","" ); } } "push" => { // argc=2: (array, value) let argc = 2usize; let val = args.get(0).and_then(|v| self.known_i64.get(v)).copied().unwrap_or(0); if let Some(pidx) = self.param_index.get(array).copied() { // Prepare args b.emit_param_i64(pidx); b.emit_const_i64(val); // Decide policy let decision = crate::jit::policy::invoke::decide_box_method("ArrayBox", "push", argc, false); match decision { crate::jit::policy::invoke::InvokeDecision::PluginInvoke { type_id, method_id, box_type, .. } => { b.emit_plugin_invoke(type_id, method_id, argc, false); crate::jit::observe::lower_plugin_invoke(&box_type, "push", type_id, method_id, argc); } crate::jit::policy::invoke::InvokeDecision::HostCall { symbol, .. } => { crate::jit::observe::lower_hostcall(&symbol, argc, &["Handle","I64"], "allow", "mapped_symbol"); b.emit_host_call(&symbol, argc, false); } _ => { // Fallback to existing hostcall path let sym = crate::jit::r#extern::collections::SYM_ARRAY_PUSH_H; crate::jit::observe::lower_hostcall(sym, argc, &["Handle","I64"], "fallback", "policy_or_unknown"); b.emit_host_call(sym, argc, false); } } } else { // No receiver param index let arr_idx = -1; b.emit_const_i64(arr_idx); b.emit_const_i64(val); let sym = crate::jit::r#extern::collections::SYM_ARRAY_PUSH; crate::jit::observe::lower_hostcall(sym, argc, &["I64","I64"], "fallback", "receiver_not_param"); b.emit_host_call(sym, argc, false); } } "size" => { let argc = 1usize; if let Some(pidx) = self.param_index.get(array).copied() { b.emit_param_i64(pidx); let decision = crate::jit::policy::invoke::decide_box_method("MapBox", "size", argc, dst.is_some()); match decision { crate::jit::policy::invoke::InvokeDecision::PluginInvoke { type_id, method_id, box_type, .. } => { b.emit_plugin_invoke(type_id, method_id, argc, dst.is_some()); crate::jit::observe::lower_plugin_invoke(&box_type, "size", type_id, method_id, argc); } crate::jit::policy::invoke::InvokeDecision::HostCall { symbol, .. } => { crate::jit::observe::lower_hostcall(&symbol, argc, &["Handle"], "allow", "mapped_symbol"); b.emit_host_call(&symbol, argc, dst.is_some()); } _ => { let sym = crate::jit::r#extern::collections::SYM_MAP_SIZE_H; crate::jit::observe::lower_hostcall(sym, argc, &["Handle"], "fallback", "policy_or_unknown"); b.emit_host_call(sym, argc, dst.is_some()); } } } else { let map_idx = -1; b.emit_const_i64(map_idx); let sym = crate::jit::r#extern::collections::SYM_MAP_SIZE; crate::jit::observe::lower_hostcall(sym, argc, &["I64"], "fallback", "receiver_not_param"); b.emit_host_call(sym, argc, dst.is_some()); } } "get" => { let argc = 2usize; if let Some(pidx) = self.param_index.get(array).copied() { b.emit_param_i64(pidx); if let Some(k) = args.get(0).and_then(|v| self.known_i64.get(v)).copied() { b.emit_const_i64(k); } else if let Some(kvid) = args.get(0) { self.push_value_if_known_or_param(b, kvid); } else { b.emit_const_i64(0); } let decision = crate::jit::policy::invoke::decide_box_method("MapBox", "get", argc, dst.is_some()); match decision { crate::jit::policy::invoke::InvokeDecision::PluginInvoke { type_id, method_id, box_type, .. } => { b.emit_plugin_invoke(type_id, method_id, argc, dst.is_some()); crate::jit::observe::lower_plugin_invoke(&box_type, "get", type_id, method_id, argc); } crate::jit::policy::invoke::InvokeDecision::HostCall { symbol, .. } => { crate::jit::observe::lower_hostcall(&symbol, argc, &["Handle","I64"], "allow", "mapped_symbol"); b.emit_host_call(&symbol, argc, dst.is_some()); } _ => { let sym = crate::jit::r#extern::collections::SYM_MAP_GET_H; crate::jit::observe::lower_hostcall(sym, argc, &["Handle","I64"], "fallback", "policy_or_unknown"); b.emit_host_call(sym, argc, dst.is_some()); } } } else { let sym = crate::jit::r#extern::collections::SYM_MAP_GET_H; crate::jit::observe::lower_hostcall(sym, argc, &["I64","I64"], "fallback", "receiver_not_param"); b.emit_host_call(sym, argc, dst.is_some()); } } "set" => { let argc = 3usize; if let Some(pidx) = self.param_index.get(array).copied() { let key = args.get(0).and_then(|v| self.known_i64.get(v)).copied().unwrap_or(0); let val = args.get(1).and_then(|v| self.known_i64.get(v)).copied().unwrap_or(0); b.emit_param_i64(pidx); b.emit_const_i64(key); b.emit_const_i64(val); let decision = crate::jit::policy::invoke::decide_box_method("MapBox", "set", argc, false); match decision { crate::jit::policy::invoke::InvokeDecision::PluginInvoke { type_id, method_id, box_type, .. } => { b.emit_plugin_invoke(type_id, method_id, argc, false); crate::jit::observe::lower_plugin_invoke(&box_type, "set", type_id, method_id, argc); } crate::jit::policy::invoke::InvokeDecision::HostCall { symbol, .. } => { crate::jit::observe::lower_hostcall(&symbol, argc, &["Handle","I64","I64"], "allow", "mapped_symbol"); b.emit_host_call(&symbol, argc, false); } _ => { let sym = crate::jit::r#extern::collections::SYM_MAP_SET_H; crate::jit::observe::lower_hostcall(sym, argc, &["Handle","I64","I64"], "fallback", "policy_or_unknown"); b.emit_host_call(sym, argc, false); } } } else { let sym = crate::jit::r#extern::collections::SYM_MAP_SET; crate::jit::observe::lower_hostcall(sym, argc, &["I64","I64","I64"], "fallback", "receiver_not_param"); b.emit_host_call(sym, argc, false); } } "charCodeAt" => { // String.charCodeAt(index) if let Some(pidx) = self.param_index.get(array).copied() { let idx = args.get(0).and_then(|v| self.known_i64.get(v)).copied().unwrap_or(0); crate::jit::events::emit_lower( serde_json::json!({"id": crate::jit::r#extern::collections::SYM_STRING_CHARCODE_AT_H, "decision":"allow", "reason":"sig_ok", "argc":2, "arg_types":["Handle","I64"]}), "hostcall","" ); b.emit_param_i64(pidx); b.emit_const_i64(idx); b.emit_host_call(crate::jit::r#extern::collections::SYM_STRING_CHARCODE_AT_H, 2, dst.is_some()); } else { crate::jit::events::emit_lower( serde_json::json!({"id": crate::jit::r#extern::collections::SYM_STRING_CHARCODE_AT_H, "decision":"fallback", "reason":"receiver_not_param", "argc":2, "arg_types":["Handle","I64"]}), "hostcall","" ); } } "has" => { let argc = 2usize; if let Some(pidx) = self.param_index.get(array).copied() { let key = args.get(0).and_then(|v| self.known_i64.get(v)).copied().unwrap_or(0); b.emit_param_i64(pidx); b.emit_const_i64(key); let decision = crate::jit::policy::invoke::decide_box_method("MapBox", "has", argc, dst.is_some()); match decision { crate::jit::policy::invoke::InvokeDecision::PluginInvoke { type_id, method_id, box_type, .. } => { b.emit_plugin_invoke(type_id, method_id, argc, dst.is_some()); crate::jit::observe::lower_plugin_invoke(&box_type, "has", type_id, method_id, argc); } crate::jit::policy::invoke::InvokeDecision::HostCall { symbol, .. } => { crate::jit::observe::lower_hostcall(&symbol, argc, &["Handle","I64"], "allow", "mapped_symbol"); b.emit_host_call(&symbol, argc, dst.is_some()); } _ => { let sym = crate::jit::r#extern::collections::SYM_MAP_HAS_H; crate::jit::observe::lower_hostcall(sym, argc, &["Handle","I64"], "fallback", "policy_or_unknown"); b.emit_host_call(sym, argc, dst.is_some()); } } } } _ => {} } */ _ => {} } Ok(()) } } pub use super::cfg_dot::dump_cfg_dot;