Files
hakorune/src/jit/hostcall_registry.rs
Moe Charm 4e1b595796 AI協調開発研究ドキュメントの完成と Phase 10.9-β 進捗
【AI協調開発研究】
- AI二重化モデルの学術論文draft完成(workshop_paper_draft.md)
- 「隠れた危機」分析とbirthの原則哲学化
- TyEnv「唯一の真実」協調会話を保存・研究資料に統合
- papers管理構造の整備(wip/under-review/published分離)

【Phase 10.9-β HostCall進捗】
- JitConfigBox: relax_numeric フラグ追加(i64→f64コアーション制御)
- HostcallRegistryBox: 署名検証・白黒リスト・コアーション対応
- JitHostcallRegistryBox: Nyash側レジストリ操作API
- Lower統合: env直読 → jit::config::current() 参照に統一
- 数値緩和設定: NYASH_JIT_HOSTCALL_RELAX_NUMERIC/Config.set_flag

【検証サンプル拡充】
- math.sin/cos/abs/min/max 関数スタイル(examples/jit_math_function_style_*.nyash)
- 境界ケース: 署名不一致・コアーション許可・mutating拒否サンプル
- E2E実証: String.length→allow, Array.push→fallback, math関数の署名一致観測

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-08-28 12:09:09 +09:00

155 lines
6.0 KiB
Rust

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