docs(phase-20.33): update Gate-C(Core) status (v1→MIR interpreter), mark parity smokes done; clean up wording\nchore: remove unused bak/ (external backup kept)\nsmokes: add Gate-C v1 file/pipe opt-in canaries; env toggles documented\nrunner: include json_v1_bridge + bridge toggles (singleton/phi) wiring

This commit is contained in:
nyash-codex
2025-11-01 07:02:04 +09:00
parent cac22c1a87
commit eabeb69d77
12 changed files with 930 additions and 11 deletions

View File

@ -0,0 +1,355 @@
use crate::mir::{
function::{FunctionSignature, MirFunction, MirModule},
BasicBlock, BasicBlockId, ConstValue, EffectMask, MirInstruction, MirType, ValueId,
};
use serde_json::Value;
/// Try to parse MIR JSON v1 schema into a MIR module.
/// Returns Ok(None) when the input is not v1 (schema_version missing).
/// Currently supports a minimal subset required for Gate-C parity tests:
/// - const (integer)
/// - copy
/// - ret
#[allow(dead_code)]
pub fn try_parse_v1_to_module(json: &str) -> Result<Option<MirModule>, String> {
let value: Value = match serde_json::from_str(json) {
Ok(v) => v,
Err(e) => return Err(format!("invalid JSON: {}", e)),
};
let schema = match value.get("schema_version") {
Some(Value::String(s)) => s.clone(),
Some(other) => {
return Err(format!(
"expected schema_version string, found {}",
other
))
}
None => return Ok(None),
};
if !schema.starts_with('1') {
return Err(format!(
"unsupported schema_version '{}': expected 1.x",
schema
));
}
let functions = value
.get("functions")
.and_then(|f| f.as_array())
.ok_or_else(|| "v1 JSON missing functions array".to_string())?;
let mut module = MirModule::new("ny_json_v1".to_string());
for func in functions {
let func_name = func
.get("name")
.and_then(|n| n.as_str())
.unwrap_or("main")
.to_string();
let blocks = func
.get("blocks")
.and_then(|b| b.as_array())
.ok_or_else(|| format!("function '{}' missing blocks array", func_name))?;
if blocks.is_empty() {
return Err(format!("function '{}' has no blocks", func_name));
}
let entry_id = blocks
.get(0)
.and_then(|b| b.get("id"))
.and_then(|id| id.as_u64())
.ok_or_else(|| format!("function '{}' entry block missing id", func_name))?;
let entry_bb = BasicBlockId::new(entry_id as u32);
let mut signature = FunctionSignature {
name: func_name.clone(),
params: Vec::new(),
return_type: MirType::Unknown,
effects: EffectMask::PURE,
};
let mut mir_fn = MirFunction::new(signature.clone(), entry_bb);
let mut max_value_id: u32 = 0;
for block in blocks {
let block_id = block
.get("id")
.and_then(|id| id.as_u64())
.ok_or_else(|| format!("function '{}' block missing id", func_name))? as u32;
let bb_id = BasicBlockId::new(block_id);
if mir_fn.get_block(bb_id).is_none() {
mir_fn.add_block(BasicBlock::new(bb_id));
}
let block_ref = mir_fn
.get_block_mut(bb_id)
.expect("block must exist after insertion");
let instructions = block
.get("instructions")
.and_then(|insts| insts.as_array())
.ok_or_else(|| {
format!(
"function '{}' block {} missing instructions array",
func_name, block_id
)
})?;
for inst in instructions {
let op = inst
.get("op")
.and_then(|o| o.as_str())
.ok_or_else(|| {
format!(
"function '{}' block {} missing op field",
func_name, block_id
)
})?;
match op {
"const" => {
let dst = inst
.get("dst")
.and_then(|d| d.as_u64())
.ok_or_else(|| {
format!(
"const instruction missing dst in function '{}'",
func_name
)
})? as u32;
let value_obj = inst
.get("value")
.ok_or_else(|| {
format!(
"const instruction missing value in function '{}'",
func_name
)
})?;
let const_val = parse_const_value(value_obj)?;
block_ref.add_instruction(MirInstruction::Const {
dst: ValueId::new(dst),
value: const_val,
});
if dst >= max_value_id {
max_value_id = dst + 1;
}
}
"copy" => {
let dst = inst
.get("dst")
.and_then(|d| d.as_u64())
.ok_or_else(|| {
format!(
"copy instruction missing dst in function '{}'",
func_name
)
})? as u32;
let src = inst
.get("src")
.and_then(|d| d.as_u64())
.ok_or_else(|| {
format!(
"copy instruction missing src in function '{}'",
func_name
)
})? as u32;
block_ref.add_instruction(MirInstruction::Copy {
dst: ValueId::new(dst),
src: ValueId::new(src),
});
if dst >= max_value_id {
max_value_id = dst + 1;
}
}
"binop" => {
let dst = require_u64(inst, "dst", "binop dst")? as u32;
let lhs = require_u64(inst, "lhs", "binop lhs")? as u32;
let rhs = require_u64(inst, "rhs", "binop rhs")? as u32;
let operation = inst
.get("operation")
.and_then(Value::as_str)
.ok_or_else(|| format!("binop operation missing in function '{}'", func_name))?;
let bop = parse_binop(operation)?;
block_ref.add_instruction(MirInstruction::BinOp {
dst: ValueId::new(dst),
op: bop,
lhs: ValueId::new(lhs),
rhs: ValueId::new(rhs),
});
max_value_id = max_value_id.max(dst + 1);
}
"compare" => {
let dst = require_u64(inst, "dst", "compare dst")? as u32;
let lhs = require_u64(inst, "lhs", "compare lhs")? as u32;
let rhs = require_u64(inst, "rhs", "compare rhs")? as u32;
let operation = inst
.get("operation")
.and_then(Value::as_str)
.ok_or_else(|| format!("compare operation missing in function '{}'", func_name))?;
let cop = parse_compare(operation)?;
block_ref.add_instruction(MirInstruction::Compare {
dst: ValueId::new(dst),
op: cop,
lhs: ValueId::new(lhs),
rhs: ValueId::new(rhs),
});
max_value_id = max_value_id.max(dst + 1);
}
"branch" => {
let cond = require_u64(inst, "cond", "branch cond")? as u32;
let then_bb = require_u64(inst, "then", "branch then")? as u32;
let else_bb = require_u64(inst, "else", "branch else")? as u32;
block_ref.add_instruction(MirInstruction::Branch {
condition: ValueId::new(cond),
then_bb: BasicBlockId::new(then_bb),
else_bb: BasicBlockId::new(else_bb),
});
}
"jump" => {
let target = require_u64(inst, "target", "jump target")? as u32;
block_ref.add_instruction(MirInstruction::Jump {
target: BasicBlockId::new(target),
});
}
"phi" => {
let dst = require_u64(inst, "dst", "phi dst")? as u32;
let incoming = inst
.get("incoming")
.and_then(Value::as_array)
.ok_or_else(|| format!("phi incoming missing in function '{}'", func_name))?;
let mut pairs = Vec::with_capacity(incoming.len());
for entry in incoming {
let pair = entry
.as_array()
.ok_or_else(|| format!("phi incoming entry must be array in function '{}'", func_name))?;
if pair.len() != 2 {
return Err("phi incoming entry must have 2 elements".into());
}
let val = pair[0]
.as_u64()
.ok_or_else(|| "phi incoming value must be integer".to_string())?;
let bb = pair[1]
.as_u64()
.ok_or_else(|| "phi incoming block must be integer".to_string())?;
pairs.push((BasicBlockId::new(bb as u32), ValueId::new(val as u32)));
}
block_ref.add_instruction(MirInstruction::Phi {
dst: ValueId::new(dst),
inputs: pairs,
});
max_value_id = max_value_id.max(dst + 1);
}
"ret" => {
let value = inst
.get("value")
.and_then(|v| v.as_u64())
.map(|v| ValueId::new(v as u32));
block_ref.add_instruction(MirInstruction::Return { value });
if let Some(val) = value {
signature.return_type = MirType::Integer;
max_value_id = max_value_id.max(val.as_u32() + 1);
} else {
signature.return_type = MirType::Void;
}
}
other => {
return Err(format!(
"unsupported instruction '{}' in function '{}' (Gate-C v1 bridge)",
other, func_name
));
}
}
}
}
mir_fn.signature = signature;
mir_fn.next_value_id = max_value_id;
module.add_function(mir_fn);
}
Ok(Some(module))
}
#[allow(dead_code)]
fn parse_const_value(value_obj: &Value) -> Result<ConstValue, String> {
let (type_str, raw_val) = if let Some(t) = value_obj.get("type") {
(
t.clone(),
value_obj
.get("value")
.cloned()
.ok_or_else(|| "const value missing numeric value".to_string())?,
)
} else {
(Value::String("i64".to_string()), value_obj.clone())
};
match type_str {
Value::String(s) => match s.as_str() {
"i64" | "int" => {
let val = raw_val
.as_i64()
.ok_or_else(|| "const value expected integer".to_string())?;
Ok(ConstValue::Integer(val))
}
other => Err(format!(
"unsupported const type '{}' in Gate-C v1 bridge",
other
)),
},
Value::Object(obj) => {
if let Some(Value::String(kind)) = obj.get("kind") {
if kind == "handle" {
if let Some(Value::String(box_type)) = obj.get("box_type") {
return Err(format!(
"unsupported const handle type '{}' in Gate-C v1 bridge",
box_type
));
}
}
}
Err("unsupported const type object in Gate-C v1 bridge".to_string())
}
_ => Err("const value has unsupported type descriptor".to_string()),
}
}
fn require_u64(node: &Value, key: &str, context: &str) -> Result<u64, String> {
node.get(key)
.and_then(Value::as_u64)
.ok_or_else(|| format!("{} missing field '{}'", context, key))
}
fn parse_binop(op: &str) -> Result<crate::mir::types::BinaryOp, String> {
use crate::mir::types::BinaryOp;
let bop = match op {
"+" => BinaryOp::Add,
"-" => BinaryOp::Sub,
"*" => BinaryOp::Mul,
"/" => BinaryOp::Div,
"%" => BinaryOp::Mod,
"&" | "bitand" => BinaryOp::BitAnd,
"|" | "bitor" => BinaryOp::BitOr,
"^" | "bitxor" => BinaryOp::BitXor,
"shl" => BinaryOp::Shl,
"shr" => BinaryOp::Shr,
"and" => BinaryOp::And,
"or" => BinaryOp::Or,
other => return Err(format!("unsupported binop '{}'", other)),
};
Ok(bop)
}
fn parse_compare(op: &str) -> Result<crate::mir::types::CompareOp, String> {
use crate::mir::types::CompareOp;
let cop = match op {
"==" => CompareOp::Eq,
"!=" => CompareOp::Ne,
"<" => CompareOp::Lt,
"<=" => CompareOp::Le,
">" => CompareOp::Gt,
">=" => CompareOp::Ge,
other => return Err(format!("unsupported compare op '{}'", other)),
};
Ok(cop)
}

View File

@ -21,6 +21,7 @@ mod cli_directives;
mod demos;
mod dispatch;
mod json_v0_bridge;
mod json_v1_bridge;
mod mir_json_emit;
pub mod modes;
mod pipe_io;

View File

@ -1,23 +1,230 @@
/*!
* core_bridge.rs — NyVM wrapper bridge helpers
*
* Provides a minimal JSON canonicalizer for NyVmDispatcher wrapper path.
* Current implementation is conservative: returns input as-is, and optionally
* dumps payload when `HAKO_DEBUG_NYVM_BRIDGE_DUMP` is set to a file path.
* Provides a JSON canonicalizer for NyVmDispatcher wrapper path.
* Optional env toggles:
* - HAKO_BRIDGE_INJECT_SINGLETON / NYASH_BRIDGE_INJECT_SINGLETON:
* Rewrite ModuleFunction Array/Map len calls into Method form.
* - HAKO_BRIDGE_EARLY_PHI_MATERIALIZE / NYASH_BRIDGE_EARLY_PHI_MATERIALIZE:
* Move phi instructions to block head (order-preserving).
* Dumps payload when `HAKO_DEBUG_NYVM_BRIDGE_DUMP` is set to a file path.
*/
use std::fs;
use serde_json::Value;
use std::{env, fs};
/// Canonicalize JSON to module shape expected by NyVmDispatcher.
/// For now, this is a passthrough with optional debug dump.
pub fn canonicalize_module_json(input: &str) -> Result<String, String> {
if let Ok(path) = std::env::var("HAKO_DEBUG_NYVM_BRIDGE_DUMP") {
let mut output = input.to_string();
if let Ok(path) = env::var("HAKO_DEBUG_NYVM_BRIDGE_DUMP") {
if !path.trim().is_empty() {
if let Err(e) = fs::write(&path, input.as_bytes()) {
eprintln!("[bridge/dump] write error: {}", e);
}
}
}
Ok(input.to_string())
let inject_singleton = env_flag("HAKO_BRIDGE_INJECT_SINGLETON")
|| env_flag("NYASH_BRIDGE_INJECT_SINGLETON");
let materialize_phi = env_flag("HAKO_BRIDGE_EARLY_PHI_MATERIALIZE")
|| env_flag("NYASH_BRIDGE_EARLY_PHI_MATERIALIZE");
if inject_singleton || materialize_phi {
let mut json: Value = serde_json::from_str(input)
.map_err(|e| format!("bridge canonicalize: invalid JSON ({})", e))?;
let mut mutated = false;
if inject_singleton {
mutated |= inject_singleton_methods(&mut json)?;
}
if materialize_phi {
mutated |= materialize_phi_blocks(&mut json)?;
}
if mutated {
output = serde_json::to_string(&json)
.map_err(|e| format!("bridge canonicalize: serialize error ({})", e))?;
}
}
Ok(output)
}
fn env_flag(name: &str) -> bool {
env::var(name)
.ok()
.map(|v| matches!(v.trim().to_ascii_lowercase().as_str(), "1" | "true" | "on"))
.unwrap_or(false)
}
fn inject_singleton_methods(root: &mut Value) -> Result<bool, String> {
let mut changed = false;
let functions = match root.as_object_mut() {
Some(obj) => obj.get_mut("functions"),
None => return Err("bridge canonicalize: expected JSON object at root".into()),
};
let functions = match functions {
Some(Value::Array(arr)) => arr,
Some(_) => return Err("bridge canonicalize: functions must be array".into()),
None => return Ok(false),
};
for func in functions.iter_mut() {
let blocks = func
.get_mut("blocks")
.and_then(Value::as_array_mut);
let Some(blocks) = blocks else { continue };
for block in blocks.iter_mut() {
let insts = block
.get_mut("instructions")
.and_then(Value::as_array_mut);
let Some(insts) = insts else { continue };
for inst in insts.iter_mut() {
if transform_module_function(inst)? {
changed = true;
}
}
}
}
Ok(changed)
}
fn transform_module_function(inst: &mut Value) -> Result<bool, String> {
let obj = match inst.as_object_mut() {
Some(map) => map,
None => return Ok(false),
};
match obj.get("op").and_then(Value::as_str) {
Some("mir_call") => {}
_ => return Ok(false),
}
let mir_call = match obj.get_mut("mir_call").and_then(Value::as_object_mut) {
Some(mc) => mc,
None => return Ok(false),
};
let name_owned = {
let callee_obj = match mir_call.get("callee").and_then(Value::as_object) {
Some(c) => c,
None => return Ok(false),
};
match callee_obj.get("type").and_then(Value::as_str) {
Some("ModuleFunction") => {}
_ => return Ok(false),
}
callee_obj
.get("name")
.and_then(Value::as_str)
.ok_or_else(|| "bridge canonicalize: ModuleFunction missing name".to_string())?
.to_string()
};
let name = name_owned.as_str();
let (box_name, method) = match name {
"ArrayBox.len" => ("ArrayBox", "size"),
"MapBox.len" => ("MapBox", "len"),
_ => return Ok(false),
};
let receiver_value = mir_call
.get_mut("args")
.and_then(Value::as_array_mut)
.ok_or_else(|| "bridge canonicalize: mir_call.args missing".to_string())?;
if receiver_value.is_empty() {
return Err(format!(
"bridge canonicalize: {} requires receiver argument",
name
));
}
let receiver_value = receiver_value.remove(0);
let receiver = receiver_value
.as_u64()
.ok_or_else(|| format!("bridge canonicalize: {} receiver must be integer", name))?;
let num = serde_json::Number::from_u128(receiver as u128)
.ok_or_else(|| "bridge canonicalize: receiver out of range".to_string())?;
let callee = mir_call
.get_mut("callee")
.and_then(Value::as_object_mut)
.ok_or_else(|| "bridge canonicalize: callee missing".to_string())?;
callee.insert("type".to_string(), Value::String("Method".into()));
callee.insert("method".to_string(), Value::String(method.into()));
callee.insert("box_name".to_string(), Value::String(box_name.into()));
callee.insert("receiver".to_string(), Value::Number(num));
callee.remove("name");
if !callee.contains_key("certainty") {
callee.insert("certainty".to_string(), Value::String("Known".into()));
}
Ok(true)
}
fn materialize_phi_blocks(root: &mut Value) -> Result<bool, String> {
let mut changed = false;
let functions = match root.as_object_mut() {
Some(obj) => obj.get_mut("functions"),
None => return Err("bridge canonicalize: expected JSON object at root".into()),
};
let functions = match functions {
Some(Value::Array(arr)) => arr,
Some(_) => return Err("bridge canonicalize: functions must be array".into()),
None => return Ok(false),
};
for func in functions.iter_mut() {
let blocks = func
.get_mut("blocks")
.and_then(Value::as_array_mut);
let Some(blocks) = blocks else { continue };
for block in blocks.iter_mut() {
let insts = block
.get_mut("instructions")
.and_then(Value::as_array_mut);
let Some(insts) = insts else { continue };
if reorder_block_phi(insts)? {
changed = true;
}
}
}
Ok(changed)
}
fn reorder_block_phi(insts: &mut Vec<Value>) -> Result<bool, String> {
let mut seen_non_phi = false;
let mut needs_reorder = false;
for inst in insts.iter() {
if is_phi(inst) {
if seen_non_phi {
needs_reorder = true;
break;
}
} else {
seen_non_phi = true;
}
}
if !needs_reorder {
return Ok(false);
}
let original = std::mem::take(insts);
let mut phis = Vec::new();
let mut others = Vec::new();
for inst in original.into_iter() {
if is_phi(&inst) {
phis.push(inst);
} else {
others.push(inst);
}
}
insts.extend(phis);
insts.extend(others);
Ok(true)
}
fn is_phi(inst: &Value) -> bool {
inst.as_object()
.and_then(|obj| obj.get("op"))
.and_then(Value::as_str)
.map(|op| op == "phi")
.unwrap_or(false)
}

View File

@ -36,11 +36,25 @@ impl NyashRunner {
}
buf
};
if crate::config::env::nyvm_core_wrapper() {
let use_core_wrapper = crate::config::env::nyvm_core_wrapper();
let use_downconvert = crate::config::env::nyvm_v1_downconvert();
if use_core_wrapper || use_downconvert {
match crate::runner::modes::common_util::core_bridge::canonicalize_module_json(&json) {
Ok(j) => json = j,
Err(e) => eprintln!("[bridge] canonicalize warning: {}", e),
}
match crate::runner::json_v1_bridge::try_parse_v1_to_module(&json) {
Ok(Some(module)) => {
super::json_v0_bridge::maybe_dump_mir(&module);
self.execute_mir_module(&module);
return true;
}
Ok(None) => {}
Err(e) => {
eprintln!("❌ JSON v1 bridge error: {}", e);
std::process::exit(1);
}
}
}
match super::json_v0_bridge::parse_json_v0_to_module(&json) {
Ok(module) => {