Phase 22.1 WIP: SSOT resolver + TLV infrastructure + Hako MIR builder setup
Setup infrastructure for Phase 22.1 (TLV C shim & Resolver SSOT):
Core changes:
- Add nyash_tlv, nyash_c_core, nyash_kernel_min_c crates (opt-in)
- Implement SSOT resolver bridge (src/using/ssot_bridge.rs)
- Add HAKO_USING_SSOT=1 / HAKO_USING_SSOT_HAKO=1 env support
- Add HAKO_TLV_SHIM=1 infrastructure (requires --features tlv-shim)
MIR builder improvements:
- Fix using/alias consistency in Hako MIR builder
- Add hako.mir.builder.internal.{prog_scan,pattern_util} to nyash.toml
- Normalize LLVM extern calls: nyash.console.* → nyash_console_*
Smoke tests:
- Add phase2211 tests (using_ssot_hako_parity_canary_vm.sh)
- Add phase2220, phase2230, phase2231 test structure
- Add phase2100 S3 backend selector tests
- Improve test_runner.sh with quiet/timeout controls
Documentation:
- Add docs/ENV_VARS.md (Phase 22.1 env vars reference)
- Add docs/development/runtime/C_CORE_ABI.md
- Update de-rust-roadmap.md with Phase 22.x details
Tools:
- Add tools/hakorune_emit_mir.sh (Hako-first MIR emission wrapper)
- Add tools/tlv_roundtrip_smoke.sh placeholder
- Improve ny_mir_builder.sh with better backend selection
Known issues (to be fixed):
- Parser infinite loop in static method parameter parsing
- Stage-B output contamination with "RC: 0" (needs NYASH_JSON_ONLY=1)
- phase2211/using_ssot_hako_parity_canary_vm.sh fork bomb (needs recursion guard)
Next steps: Fix parser infinite loop + Stage-B quiet mode for green tests
This commit is contained in:
@ -3,6 +3,19 @@ use super::super::utils::*;
|
||||
use serde_json::Value as JsonValue;
|
||||
|
||||
impl MirInterpreter {
|
||||
#[inline]
|
||||
fn should_trace_call_extern(target: &str, method: &str) -> bool {
|
||||
if let Ok(flt) = std::env::var("HAKO_CALL_TRACE_FILTER") {
|
||||
let key = format!("{}.{}", target, method);
|
||||
for pat in flt.split(',') {
|
||||
let p = pat.trim();
|
||||
if p.is_empty() { continue; }
|
||||
if p == method || p == key { return true; }
|
||||
}
|
||||
return false;
|
||||
}
|
||||
true
|
||||
}
|
||||
fn patch_mir_json_version(s: &str) -> String {
|
||||
match serde_json::from_str::<JsonValue>(s) {
|
||||
Ok(mut v) => {
|
||||
@ -26,6 +39,20 @@ impl MirInterpreter {
|
||||
extern_name: &str,
|
||||
args: &[ValueId],
|
||||
) -> Option<Result<VMValue, VMError>> {
|
||||
// Unified call trace (optional)
|
||||
if std::env::var("HAKO_CALL_TRACE").ok().as_deref() == Some("1") {
|
||||
// Split iface.method for filtering
|
||||
if let Some((iface, method)) = extern_name.rsplit_once('.') {
|
||||
if Self::should_trace_call_extern(iface, method) {
|
||||
eprintln!("[call:{}.{}]", iface, method);
|
||||
}
|
||||
} else {
|
||||
// Fallback: no dot in extern name (e.g., 'print')
|
||||
if Self::should_trace_call_extern("", extern_name) {
|
||||
eprintln!("[call:{}]", extern_name);
|
||||
}
|
||||
}
|
||||
}
|
||||
match extern_name {
|
||||
// Console family (minimal)
|
||||
"nyash.console.log" | "env.console.log" | "print" | "nyash.builtin.print" => {
|
||||
|
||||
39
src/llvm_py/instructions/extern_normalize.py
Normal file
39
src/llvm_py/instructions/extern_normalize.py
Normal file
@ -0,0 +1,39 @@
|
||||
"""
|
||||
extern_normalize.py — Single point of truth for extern name normalization.
|
||||
|
||||
Policy (MVP):
|
||||
- Map bare "print"/"println" → "nyash.console.log"
|
||||
- Map "env.console.*" (println/log/print/warn/error) → "nyash.console.<method>"
|
||||
* println is normalized to log (pointer API).
|
||||
- Keep already-qualified "nyash.console.*" as-is, but normalize ...println → ...log
|
||||
|
||||
This module is imported by both instructions.externcall and instructions.mir_call
|
||||
to avoid duplication and drift.
|
||||
"""
|
||||
|
||||
from typing import Optional
|
||||
|
||||
|
||||
def normalize_extern_name(name: Optional[str]) -> str:
|
||||
if not name:
|
||||
return ""
|
||||
try:
|
||||
n = str(name)
|
||||
except Exception:
|
||||
return ""
|
||||
|
||||
try:
|
||||
if n.startswith("env.console."):
|
||||
method = n.split(".")[-1]
|
||||
if method == "println":
|
||||
method = "log"
|
||||
return f"nyash.console.{method}"
|
||||
if n in ("println", "print"):
|
||||
return "nyash.console.log"
|
||||
if n.startswith("nyash.console.") and n.endswith("println"):
|
||||
return "nyash.console.log"
|
||||
except Exception:
|
||||
# Fallthrough to original if anything odd happens
|
||||
pass
|
||||
return n
|
||||
|
||||
@ -6,6 +6,7 @@ Minimal mapping for NyRT-exported symbols (console/log family等)
|
||||
import llvmlite.ir as ir
|
||||
from typing import Dict, List, Optional, Any
|
||||
from instructions.safepoint import insert_automatic_safepoint
|
||||
from instructions.extern_normalize import normalize_extern_name
|
||||
|
||||
def lower_externcall(
|
||||
builder: ir.IRBuilder,
|
||||
@ -45,26 +46,17 @@ def lower_externcall(
|
||||
bb_map = ctx.bb_map
|
||||
except Exception:
|
||||
pass
|
||||
# Normalize extern target names
|
||||
# Accept full symbol names (e.g., "nyash.console.log", "nyash.string.len_h").
|
||||
# Also accept legacy/environment names and map them to kernel exports.
|
||||
llvm_name = func_name
|
||||
# Normalize extern target names through shared policy
|
||||
llvm_name = normalize_extern_name(func_name)
|
||||
# For C linkage, map dot-qualified console names to underscore symbols.
|
||||
# This keeps the logical name (nyash.console.log) stable at the MIR level
|
||||
# while emitting a C-friendly symbol (nyash_console_log) for linkage.
|
||||
c_symbol_name = llvm_name
|
||||
try:
|
||||
if func_name.startswith("env.console."):
|
||||
# Map env.console.* → nyash.console.* (kernel exports)
|
||||
method = func_name.split(".")[-1]
|
||||
# println maps to log for now
|
||||
if method == "println":
|
||||
method = "log"
|
||||
llvm_name = f"nyash.console.{method}"
|
||||
elif func_name == "println" or func_name == "print":
|
||||
# Bare println/print fallback
|
||||
llvm_name = "nyash.console.log"
|
||||
elif func_name.startswith("nyash.console.") and func_name.endswith("println"):
|
||||
# Normalize nyash.console.println → nyash.console.log
|
||||
llvm_name = "nyash.console.log"
|
||||
if llvm_name.startswith("nyash.console."):
|
||||
c_symbol_name = llvm_name.replace(".", "_")
|
||||
except Exception:
|
||||
pass
|
||||
c_symbol_name = llvm_name
|
||||
|
||||
i8 = ir.IntType(8)
|
||||
i64 = ir.IntType(64)
|
||||
@ -95,22 +87,22 @@ def lower_externcall(
|
||||
# Find or declare function with appropriate prototype
|
||||
func = None
|
||||
for f in module.functions:
|
||||
if f.name == llvm_name:
|
||||
if f.name == c_symbol_name:
|
||||
func = f
|
||||
break
|
||||
if not func:
|
||||
if llvm_name in sig_map:
|
||||
ret_ty, arg_tys = sig_map[llvm_name]
|
||||
fnty = ir.FunctionType(ret_ty, arg_tys)
|
||||
func = ir.Function(module, fnty, name=llvm_name)
|
||||
func = ir.Function(module, fnty, name=c_symbol_name)
|
||||
elif llvm_name.startswith("nyash.console."):
|
||||
# console.*: (i8*) -> i64
|
||||
fnty = ir.FunctionType(i64, [i8p])
|
||||
func = ir.Function(module, fnty, name=llvm_name)
|
||||
func = ir.Function(module, fnty, name=c_symbol_name)
|
||||
else:
|
||||
# Unknown extern: declare as void(...no args...) and call without args
|
||||
fnty = ir.FunctionType(void, [])
|
||||
func = ir.Function(module, fnty, name=llvm_name)
|
||||
func = ir.Function(module, fnty, name=c_symbol_name)
|
||||
|
||||
# Prepare/coerce arguments
|
||||
call_args: List[ir.Value] = []
|
||||
|
||||
@ -588,6 +588,10 @@ def lower_extern_call(builder, module, extern_name, args, dst_vid, vmap, resolve
|
||||
pass
|
||||
return vmap.get(vid)
|
||||
|
||||
# Normalize extern target names via shared normalizer
|
||||
from instructions.extern_normalize import normalize_extern_name
|
||||
extern_name = normalize_extern_name(extern_name)
|
||||
|
||||
# Look up extern function in module
|
||||
func = None
|
||||
for f in module.functions:
|
||||
|
||||
@ -10,6 +10,7 @@
|
||||
use super::*;
|
||||
use std::collections::HashMap;
|
||||
use crate::using::spec::{UsingPackage, PackageKind};
|
||||
use crate::using::ssot_bridge::{call_using_resolve_ssot, SsotCtx};
|
||||
|
||||
/// Using/module resolution context accumulated from config/env/nyash.toml
|
||||
pub(super) struct UsingContext {
|
||||
@ -129,6 +130,19 @@ pub(super) fn resolve_using_target(
|
||||
strict: bool,
|
||||
verbose: bool,
|
||||
) -> Result<String, String> {
|
||||
// Phase 22.1: Thin SSOT hook (future wiring). No behavior change for now.
|
||||
if std::env::var("HAKO_USING_SSOT").ok().as_deref() == Some("1")
|
||||
&& std::env::var("HAKO_USING_SSOT_INVOKING")
|
||||
.ok()
|
||||
.as_deref()
|
||||
!= Some("1")
|
||||
{
|
||||
if let Some(ssot_res) = try_resolve_using_target_ssot(
|
||||
tgt, is_path, modules, using_paths, aliases, packages, context_dir, strict, verbose,
|
||||
) {
|
||||
return Ok(ssot_res);
|
||||
}
|
||||
}
|
||||
// Invalidate and rebuild index/cache if env or nyash.toml changed
|
||||
super::box_index::rebuild_if_env_changed();
|
||||
if is_path {
|
||||
@ -330,6 +344,96 @@ pub(super) fn resolve_using_target(
|
||||
Ok(out)
|
||||
}
|
||||
|
||||
/// Thin SSOT wrapper — returns Some(resolved) when an alternative SSOT path is available.
|
||||
/// MVP: return None to keep current behavior. Future: call into Hako `UsingResolveSSOTBox`.
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn try_resolve_using_target_ssot(
|
||||
tgt: &str,
|
||||
is_path: bool,
|
||||
modules: &[(String, String)],
|
||||
using_paths: &[String],
|
||||
aliases: &HashMap<String, String>,
|
||||
packages: &HashMap<String, UsingPackage>,
|
||||
context_dir: Option<&std::path::Path>,
|
||||
strict: bool,
|
||||
verbose: bool,
|
||||
) -> Option<String> {
|
||||
// Phase 22.1 MVP: Build context and consult SSOT bridge (modules-only).
|
||||
let trace = verbose || crate::config::env::env_bool("NYASH_RESOLVE_TRACE");
|
||||
let mut map: HashMap<String, String> = HashMap::new();
|
||||
for (k, v) in modules {
|
||||
map.insert(k.clone(), v.clone());
|
||||
}
|
||||
let cwd_str = context_dir.and_then(|p| p.to_str()).map(|s| s.to_string());
|
||||
let ctx = SsotCtx { modules: map, using_paths: using_paths.to_vec(), cwd: cwd_str };
|
||||
if let Some(hit) = call_using_resolve_ssot(tgt, &ctx) {
|
||||
if trace {
|
||||
crate::runner::trace::log(format!("[using/ssot] '{}' -> '{}'", tgt, hit));
|
||||
}
|
||||
return Some(hit);
|
||||
}
|
||||
// Optional relative inference (Runner-side, guarded): prefer cwd > using_paths
|
||||
if std::env::var("HAKO_USING_SSOT_RELATIVE").ok().as_deref() == Some("1") {
|
||||
let rel_hako = tgt.replace('.', "/") + ".hako";
|
||||
let rel_ny = tgt.replace('.', "/") + ".nyash";
|
||||
let mut try_paths: Vec<std::path::PathBuf> = Vec::new();
|
||||
if let Some(dir) = context_dir {
|
||||
try_paths.push(dir.join(&rel_hako));
|
||||
try_paths.push(dir.join(&rel_ny));
|
||||
}
|
||||
for base in using_paths {
|
||||
let p = std::path::Path::new(base);
|
||||
try_paths.push(p.join(&rel_hako));
|
||||
try_paths.push(p.join(&rel_ny));
|
||||
}
|
||||
let mut found: Vec<String> = Vec::new();
|
||||
for p in try_paths {
|
||||
if p.exists() {
|
||||
found.push(p.to_string_lossy().to_string());
|
||||
}
|
||||
}
|
||||
if !found.is_empty() {
|
||||
if found.len() > 1 && strict {
|
||||
if trace {
|
||||
let total = found.len();
|
||||
// Allow customizing the number of shown candidates via env (bounded 1..=10)
|
||||
let n_show: usize = std::env::var("HAKO_USING_SSOT_RELATIVE_AMBIG_FIRST_N")
|
||||
.ok()
|
||||
.and_then(|s| s.parse::<usize>().ok())
|
||||
.map(|n| n.clamp(1, 10))
|
||||
.unwrap_or(3);
|
||||
let shown: Vec<String> = found.iter().take(n_show).cloned().collect();
|
||||
// Standardized message: count + first N + explicit delegation policy
|
||||
crate::runner::trace::log(format!(
|
||||
"[using/ssot:relative ambiguous] name='{}' count={} first=[{}] -> delegate=legacy(strict)",
|
||||
tgt,
|
||||
total,
|
||||
shown.join(", ")
|
||||
));
|
||||
}
|
||||
// Strict ambiguity: delegate to legacy resolver (behavior unchanged)
|
||||
} else {
|
||||
let out = found.remove(0);
|
||||
if trace {
|
||||
crate::runner::trace::log(format!(
|
||||
"[using/ssot:relative] '{}' -> '{}' (priority=cwd>using_paths)",
|
||||
tgt, out
|
||||
));
|
||||
}
|
||||
return Some(out);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Fallback: keep parity by delegating to existing resolver within the same gate
|
||||
let prev = std::env::var("HAKO_USING_SSOT_INVOKING").ok();
|
||||
std::env::set_var("HAKO_USING_SSOT_INVOKING", "1");
|
||||
let res = resolve_using_target(
|
||||
tgt, is_path, modules, using_paths, aliases, packages, context_dir, strict, verbose,
|
||||
);
|
||||
if let Some(val) = prev { std::env::set_var("HAKO_USING_SSOT_INVOKING", val); } else { let _ = std::env::remove_var("HAKO_USING_SSOT_INVOKING"); }
|
||||
res.ok()
|
||||
}
|
||||
|
||||
/// Lint: enforce "fields must be at the top of box" rule.
|
||||
/// - Warns by default (when verbose); when `strict` is true, returns Err on any violation.
|
||||
pub(super) fn lint_fields_top(code: &str, strict: bool, verbose: bool) -> Result<(), String> {
|
||||
|
||||
@ -29,7 +29,53 @@ pub fn encode_args(args: &[Box<dyn NyashBox>]) -> Vec<u8> {
|
||||
encode::string(&mut buf, &a.to_string_box().value);
|
||||
}
|
||||
}
|
||||
buf
|
||||
maybe_tlv_roundtrip(buf)
|
||||
}
|
||||
|
||||
/// Optional TLV shim round‑trip (feature/env gated).
|
||||
///
|
||||
/// Behavior:
|
||||
/// - When compiled with feature `tlv-shim` AND env `HAKO_TLV_SHIM=1`,
|
||||
/// the encoded TLV buffer is passed through `nyash-tlv` identity round‑trip.
|
||||
/// - Otherwise, returns the original buffer unchanged.
|
||||
pub fn maybe_tlv_roundtrip(buf: Vec<u8>) -> Vec<u8> {
|
||||
if std::env::var("HAKO_TLV_SHIM").ok().as_deref() != Some("1") {
|
||||
return buf;
|
||||
}
|
||||
#[cfg(feature = "tlv-shim")]
|
||||
{
|
||||
return nyash_tlv::tlv_roundtrip_identity(&buf);
|
||||
}
|
||||
#[cfg(not(feature = "tlv-shim"))]
|
||||
{
|
||||
// Feature disabled: keep behavior identical
|
||||
buf
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn tlv_roundtrip_off_by_default() {
|
||||
std::env::remove_var("HAKO_TLV_SHIM");
|
||||
let src = vec![1u8, 2, 3, 4, 5];
|
||||
let out = maybe_tlv_roundtrip(src.clone());
|
||||
assert_eq!(out, src);
|
||||
}
|
||||
|
||||
#[cfg(feature = "tlv-shim")]
|
||||
#[test]
|
||||
fn tlv_roundtrip_env_feature_on() {
|
||||
std::env::set_var("HAKO_TLV_SHIM", "1");
|
||||
let src = vec![9u8, 8, 7, 6, 5, 4, 3];
|
||||
let out = maybe_tlv_roundtrip(src.clone());
|
||||
// Identity roundtrip returns the same bytes
|
||||
assert_eq!(out, src);
|
||||
// Cleanup
|
||||
std::env::remove_var("HAKO_TLV_SHIM");
|
||||
}
|
||||
}
|
||||
|
||||
/// Simple helpers for common primitive returns
|
||||
|
||||
@ -17,6 +17,11 @@ pub fn extern_call(
|
||||
method_name: &str,
|
||||
args: &[Box<dyn NyashBox>],
|
||||
) -> BidResult<Option<Box<dyn NyashBox>>> {
|
||||
if std::env::var("HAKO_CALL_TRACE").ok().as_deref() == Some("1") {
|
||||
if should_trace_call_extern(iface_name, method_name) {
|
||||
eprintln!("[call:{}.{}]", iface_name, method_name);
|
||||
}
|
||||
}
|
||||
match iface_name {
|
||||
"env.console" => handle_console(method_name, args),
|
||||
"env.result" => handle_result(method_name, args),
|
||||
@ -31,6 +36,19 @@ pub fn extern_call(
|
||||
}
|
||||
}
|
||||
|
||||
fn should_trace_call_extern(target: &str, method: &str) -> bool {
|
||||
if let Ok(flt) = std::env::var("HAKO_CALL_TRACE_FILTER") {
|
||||
let key = format!("{}.{}", target, method);
|
||||
for pat in flt.split(',') {
|
||||
let p = pat.trim();
|
||||
if p.is_empty() { continue; }
|
||||
if p == method || p == key { return true; }
|
||||
}
|
||||
return false;
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
/// Handle env.console.* methods
|
||||
fn handle_console(method_name: &str, args: &[Box<dyn NyashBox>]) -> BidResult<Option<Box<dyn NyashBox>>> {
|
||||
match method_name {
|
||||
|
||||
@ -4,6 +4,7 @@ use crate::bid::{BidError, BidResult};
|
||||
use crate::box_trait::NyashBox;
|
||||
use crate::runtime::plugin_loader_v2::enabled::PluginLoaderV2;
|
||||
use std::sync::Arc;
|
||||
use std::env;
|
||||
|
||||
fn dbg_on() -> bool {
|
||||
std::env::var("PLUGIN_DEBUG").is_ok()
|
||||
@ -39,9 +40,60 @@ impl PluginLoaderV2 {
|
||||
let plugins = self.plugins.read().map_err(|_| BidError::PluginError)?;
|
||||
let _plugin = plugins.get(&lib_name).ok_or(BidError::PluginError)?;
|
||||
|
||||
// Optional C wrapper (Phase 22.2: design insertion point; default OFF)
|
||||
if env::var("HAKO_PLUGIN_LOADER_C_WRAP").ok().as_deref() == Some("1") {
|
||||
if should_trace_cwrap(box_type, method_name) {
|
||||
eprintln!("[cwrap:invoke:{}.{}]", box_type, method_name);
|
||||
}
|
||||
// Future: route into a thin C shim here. For now, fall through to normal path.
|
||||
}
|
||||
|
||||
// Optional C-core probe (design): emit tag and optionally call into c-core when enabled
|
||||
if env::var("HAKO_C_CORE_ENABLE").ok().as_deref() == Some("1") && should_route_ccore(box_type, method_name) {
|
||||
eprintln!("[c-core:invoke:{}.{}]", box_type, method_name);
|
||||
#[cfg(feature = "c-core")]
|
||||
{
|
||||
// MapBox.set: call C-core stub (no-op) with available info
|
||||
if box_type == "MapBox" && method_name == "set" {
|
||||
let key = args.get(0).map(|b| b.to_string_box().value).unwrap_or_default();
|
||||
let val = args.get(1).map(|b| b.to_string_box().value).unwrap_or_default();
|
||||
let _ = nyash_c_core::core_map_set(type_id as i32, instance_id, &key, &val);
|
||||
} else if box_type == "ArrayBox" && method_name == "push" {
|
||||
// For design stage, pass 0 (we don't rely on c-core result)
|
||||
let _ = nyash_c_core::core_array_push(type_id as i32, instance_id, 0);
|
||||
} else if box_type == "ArrayBox" && method_name == "get" {
|
||||
let _ = nyash_c_core::core_array_get(type_id as i32, instance_id, 0);
|
||||
} else if box_type == "ArrayBox" && (method_name == "size" || method_name == "len" || method_name == "length") {
|
||||
let _ = nyash_c_core::core_array_len(type_id as i32, instance_id);
|
||||
} else {
|
||||
// Generic probe
|
||||
let _ = nyash_c_core::core_probe_invoke(box_type, method_name, args.len() as i32);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Encode TLV args via shared helper (numeric→string→toString)
|
||||
let tlv = crate::runtime::plugin_ffi_common::encode_args(args);
|
||||
|
||||
// Unified call trace (optional): plugin calls
|
||||
if env::var("HAKO_CALL_TRACE").ok().as_deref() == Some("1") {
|
||||
if should_trace_call(box_type, method_name) {
|
||||
eprintln!("[call:{}.{}]", box_type, method_name);
|
||||
}
|
||||
}
|
||||
|
||||
// Optional trace for TLV shim path (debug only; default OFF)
|
||||
if env::var("HAKO_TLV_SHIM_TRACE").ok().as_deref() == Some("1")
|
||||
&& env::var("HAKO_TLV_SHIM").ok().as_deref() == Some("1")
|
||||
{
|
||||
if should_trace_tlv_shim(box_type, method_name) {
|
||||
eprintln!("[tlv/shim:{}.{}]", box_type, method_name);
|
||||
if env::var("HAKO_TLV_SHIM_TRACE_DETAIL").ok().as_deref() == Some("1") {
|
||||
eprintln!("[tlv/shim:detail argc={}]", args.len());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if dbg_on() {
|
||||
eprintln!(
|
||||
"[PluginLoaderV2] call {}.{}: type_id={} method_id={} instance_id={}",
|
||||
@ -62,6 +114,63 @@ impl PluginLoaderV2 {
|
||||
}
|
||||
}
|
||||
|
||||
fn should_trace_tlv_shim(box_type: &str, method: &str) -> bool {
|
||||
// Filter provided → honor it
|
||||
if let Ok(flt) = env::var("HAKO_TLV_SHIM_FILTER") {
|
||||
let key = format!("{}.{}", box_type, method);
|
||||
for pat in flt.split(',') {
|
||||
let p = pat.trim();
|
||||
if p.is_empty() { continue; }
|
||||
if p == method || p == key { return true; }
|
||||
}
|
||||
return false;
|
||||
}
|
||||
// Default (minimal noise): only trace MapBox.set to begin with
|
||||
box_type == "MapBox" && method == "set"
|
||||
}
|
||||
|
||||
fn should_trace_cwrap(box_type: &str, method: &str) -> bool {
|
||||
// Filter provided → honor it
|
||||
if let Ok(flt) = env::var("HAKO_PLUGIN_LOADER_C_WRAP_FILTER") {
|
||||
let key = format!("{}.{}", box_type, method);
|
||||
for pat in flt.split(',') {
|
||||
let p = pat.trim();
|
||||
if p.is_empty() { continue; }
|
||||
if p == method || p == key { return true; }
|
||||
}
|
||||
return false;
|
||||
}
|
||||
// Default (minimal noise): only trace MapBox.set to begin with
|
||||
box_type == "MapBox" && method == "set"
|
||||
}
|
||||
|
||||
fn should_trace_call(target: &str, method: &str) -> bool {
|
||||
if let Ok(flt) = env::var("HAKO_CALL_TRACE_FILTER") {
|
||||
let key = format!("{}.{}", target, method);
|
||||
for pat in flt.split(',') {
|
||||
let p = pat.trim();
|
||||
if p.is_empty() { continue; }
|
||||
if p == method || p == key { return true; }
|
||||
}
|
||||
return false;
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
fn should_route_ccore(box_type: &str, method: &str) -> bool {
|
||||
if let Ok(flt) = env::var("HAKO_C_CORE_TARGETS") {
|
||||
let key = format!("{}.{}", box_type, method);
|
||||
for pat in flt.split(',') {
|
||||
let p = pat.trim();
|
||||
if p.is_empty() { continue; }
|
||||
if p == method || p == key { return true; }
|
||||
}
|
||||
return false;
|
||||
}
|
||||
// Default minimal scope: MapBox.set only
|
||||
box_type == "MapBox" && method == "set"
|
||||
}
|
||||
|
||||
/// Resolve type information for a box
|
||||
fn resolve_type_info(loader: &PluginLoaderV2, box_type: &str) -> BidResult<(String, u32)> {
|
||||
if let Some(cfg) = loader.config.as_ref() {
|
||||
@ -156,4 +265,4 @@ fn decode_tlv_result(box_type: &str, data: &[u8]) -> BidResult<Option<Box<dyn Ny
|
||||
return Ok(Some(bx));
|
||||
}
|
||||
Ok(Some(Box::new(crate::box_trait::VoidBox::new())))
|
||||
}
|
||||
}
|
||||
|
||||
@ -17,3 +17,4 @@ pub mod spec;
|
||||
pub mod policy;
|
||||
pub mod errors;
|
||||
pub mod simple_registry;
|
||||
pub mod ssot_bridge;
|
||||
|
||||
107
src/using/ssot_bridge.rs
Normal file
107
src/using/ssot_bridge.rs
Normal file
@ -0,0 +1,107 @@
|
||||
//! SSOT bridge — thin callable shim from Rust to Hako resolver (Phase 22.1)
|
||||
//!
|
||||
//! MVP: does not invoke Hako VM yet. It mirrors the Hako box logic for modules-only
|
||||
//! resolution, returning the mapped path when present. Callers must keep behavior
|
||||
//! identical to existing resolver and use this only under an explicit env toggle.
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::io::Write;
|
||||
use std::process::Command;
|
||||
|
||||
#[derive(Default, Debug, Clone)]
|
||||
pub struct SsotCtx {
|
||||
pub modules: HashMap<String, String>,
|
||||
pub using_paths: Vec<String>,
|
||||
pub cwd: Option<String>,
|
||||
}
|
||||
|
||||
/// Attempt to resolve via SSOT bridge. Returns Some(path) if found; otherwise None.
|
||||
///
|
||||
/// Behavior (MVP):
|
||||
/// - Only consults `modules` map (exact match).
|
||||
/// - Does not access filesystem nor invoke Hako VM.
|
||||
pub fn call_using_resolve_ssot(name: &str, ctx: &SsotCtx) -> Option<String> {
|
||||
if name.is_empty() { return None; }
|
||||
// Optional: delegate to Hako resolver when explicitly requested.
|
||||
if std::env::var("HAKO_USING_SSOT_HAKO").ok().as_deref() == Some("1") {
|
||||
if let Some(hit) = call_hako_box(name, ctx) { return Some(hit); }
|
||||
}
|
||||
// MVP: modules-only
|
||||
ctx.modules.get(name).cloned()
|
||||
}
|
||||
|
||||
/// Try resolving via Hako `UsingResolveSSOTBox.resolve(name, ctx)` by spawning the nyash VM.
|
||||
/// Guarded by `HAKO_USING_SSOT_HAKO=1`. Returns Some(path) on success; otherwise None.
|
||||
fn call_hako_box(name: &str, ctx: &SsotCtx) -> Option<String> {
|
||||
// Build inline Hako code that constructs a minimal ctx with modules map.
|
||||
let mut code = String::new();
|
||||
code.push_str("using hako.using.resolve.ssot as UsingResolveSSOTBox\n");
|
||||
code.push_str("static box Main {\n main() {\n local modules = new MapBox()\n");
|
||||
for (k, v) in ctx.modules.iter() {
|
||||
// Escape quotes conservatively
|
||||
let kk = k.replace('\"', "\\\"");
|
||||
let vv = v.replace('\"', "\\\"");
|
||||
code.push_str(&format!(" modules.set(\"{}\", \"{}\")\n", kk, vv));
|
||||
}
|
||||
code.push_str(" local ctx = new MapBox()\n ctx.set(\"modules\", modules)\n");
|
||||
// relative_hint: opt-in via parent env HAKO_USING_SSOT_RELATIVE=1
|
||||
if std::env::var("HAKO_USING_SSOT_RELATIVE").ok().as_deref() == Some("1") {
|
||||
code.push_str(" ctx.set(\\\"relative_hint\\\", \\\"1\\\")\\n");
|
||||
}
|
||||
// using_paths
|
||||
if !ctx.using_paths.is_empty() {
|
||||
code.push_str(" local ups = new ArrayBox()\n");
|
||||
for up in ctx.using_paths.iter() {
|
||||
let upq = up.replace('\"', "\\\"");
|
||||
code.push_str(&format!(" ups.push(\"{}\")\n", upq));
|
||||
}
|
||||
code.push_str(" ctx.set(\\\"using_paths\\\", ups)\n");
|
||||
}
|
||||
// cwd
|
||||
if let Some(cwd) = &ctx.cwd {
|
||||
let cwq = cwd.replace('\"', "\\\"");
|
||||
code.push_str(&format!(" ctx.set(\\\"cwd\\\", \"{}\")\n", cwq));
|
||||
}
|
||||
let nn = name.replace('\"', "\\\"");
|
||||
code.push_str(&format!(
|
||||
" local r = UsingResolveSSOTBox.resolve(\"{}\", ctx)\n if r == null {{ return 0 }}\n print(r)\n return 0\n }}\n",
|
||||
nn
|
||||
));
|
||||
|
||||
// Write to a temp file
|
||||
// Write ephemeral file; any failure → None (delegate to legacy)
|
||||
let mut tf = tempfile::Builder::new()
|
||||
.prefix("ny_ssot_")
|
||||
.suffix(".hako")
|
||||
.tempfile()
|
||||
.ok()?;
|
||||
let _ = write!(tf, "{}", code);
|
||||
let path = tf.path().to_path_buf();
|
||||
// Resolve nyash binary; fallback to current exe or default path on failure
|
||||
let bin = std::env::var("NYASH_BIN").ok().unwrap_or_else(|| {
|
||||
if let Ok(p) = std::env::current_exe() { p.to_string_lossy().to_string() }
|
||||
else { "target/release/nyash".to_string() }
|
||||
});
|
||||
|
||||
// Stage‑3 + tolerance (matches smokes wrappers)
|
||||
let mut cmd = Command::new(bin);
|
||||
cmd.arg("--backend").arg("vm").arg(&path)
|
||||
// Parser/entry tolerances (same as smokes "safe" mode)
|
||||
.env("NYASH_PARSER_STAGE3", "1")
|
||||
.env("HAKO_PARSER_STAGE3", "1")
|
||||
.env("NYASH_PARSER_ALLOW_SEMICOLON", "1")
|
||||
.env("NYASH_ENTRY_ALLOW_TOPLEVEL_MAIN", "1")
|
||||
// Disable inline compiler for stability
|
||||
.env("NYASH_DISABLE_NY_COMPILER", "1")
|
||||
.env("HAKO_DISABLE_NY_COMPILER", "1")
|
||||
// Hard-disable SSOT in the child to avoid recursion; mark invoking guard
|
||||
.env("HAKO_USING_SSOT", "0")
|
||||
.env("HAKO_USING_SSOT_HAKO", "0")
|
||||
.env("HAKO_USING_SSOT_RELATIVE", "0")
|
||||
.env("HAKO_USING_SSOT_INVOKING", "1");
|
||||
// Any spawn/IO error → None (fail-safe to legacy)
|
||||
let out = cmd.output().ok()?;
|
||||
if !out.status.success() { return None; }
|
||||
let s = String::from_utf8_lossy(&out.stdout).trim().to_string();
|
||||
if s.is_empty() { None } else { Some(s) }
|
||||
}
|
||||
Reference in New Issue
Block a user