vm(hako): add v1 reader/dispatcher (flagged), commonize mir_call handler, share block scan; smokes: add v1 hakovm canary; docs: 20.37/20.38 plans, OOB policy; runner: v1 hakovm toggle; include SKIP summary
This commit is contained in:
@ -3,6 +3,26 @@ use crate::mir::{
|
||||
BasicBlock, BasicBlockId, ConstValue, EffectMask, MirInstruction, MirType, ValueId,
|
||||
};
|
||||
use serde_json::Value;
|
||||
use super::mir_json::common as mirjson_common;
|
||||
|
||||
fn parse_effects_from(node: &Value) -> EffectMask {
|
||||
if let Some(arr) = node.get("effects").and_then(Value::as_array) {
|
||||
let mut m = EffectMask::PURE;
|
||||
for e in arr {
|
||||
if let Some(s) = e.as_str() {
|
||||
match s {
|
||||
"write" | "mut" | "WriteHeap" => { m = m.union(EffectMask::WRITE); }
|
||||
"read" | "ReadHeap" => { m = m.union(EffectMask::READ); }
|
||||
"io" | "IO" | "ffi" | "FFI" | "debug" => { m = m.union(EffectMask::IO); }
|
||||
"control" | "Control" => { m = m.union(EffectMask::CONTROL); }
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
return m;
|
||||
}
|
||||
EffectMask::PURE
|
||||
}
|
||||
|
||||
/// Try to parse MIR JSON v1 schema into a MIR module.
|
||||
/// Returns Ok(None) when the input is not v1 (schema_version missing).
|
||||
@ -126,7 +146,7 @@ pub fn try_parse_v1_to_module(json: &str) -> Result<Option<MirModule>, String> {
|
||||
func_name
|
||||
)
|
||||
})?;
|
||||
let const_val = parse_const_value(value_obj)?;
|
||||
let const_val = mirjson_common::parse_const_value_generic(value_obj)?;
|
||||
block_ref.add_instruction(MirInstruction::Const {
|
||||
dst: ValueId::new(dst),
|
||||
value: const_val,
|
||||
@ -255,11 +275,19 @@ pub fn try_parse_v1_to_module(json: &str) -> Result<Option<MirModule>, String> {
|
||||
}
|
||||
"mir_call" => {
|
||||
// Minimal v1 mir_call support (Global/Method/Constructor/Extern/Value + Closure creation)
|
||||
// dst: optional
|
||||
// Accept both shapes:
|
||||
// - flat: { op:"mir_call", callee:{...}, args:[...], effects:[] }
|
||||
// - nested: { op:"mir_call", mir_call:{ callee:{...}, args:[...], effects:[] } }
|
||||
// dst remains at the instruction root level in both forms.
|
||||
let dst_opt = inst.get("dst").and_then(|d| d.as_u64()).map(|v| ValueId::new(v as u32));
|
||||
// args: array of value ids
|
||||
let effects = if let Some(sub) = inst.get("mir_call") { parse_effects_from(sub) } else { parse_effects_from(inst) };
|
||||
// args: support both flat/nested placement
|
||||
let mut argv: Vec<ValueId> = Vec::new();
|
||||
if let Some(arr) = inst.get("args").and_then(|a| a.as_array()) {
|
||||
if let Some(arr) = inst
|
||||
.get("args")
|
||||
.and_then(|a| a.as_array())
|
||||
.or_else(|| inst.get("mir_call").and_then(|m| m.get("args").and_then(|a| a.as_array())))
|
||||
{
|
||||
for a in arr {
|
||||
let id = a.as_u64().ok_or_else(|| format!(
|
||||
"mir_call arg must be integer value id in function '{}'",
|
||||
@ -268,8 +296,11 @@ pub fn try_parse_v1_to_module(json: &str) -> Result<Option<MirModule>, String> {
|
||||
argv.push(ValueId::new(id));
|
||||
}
|
||||
}
|
||||
// callee: only Global(name) supported here
|
||||
let callee_obj = inst.get("callee").ok_or_else(|| {
|
||||
// callee: support Global/Method/Extern/Value/Closure/Constructor (minimal)
|
||||
let callee_obj = inst
|
||||
.get("callee")
|
||||
.or_else(|| inst.get("mir_call").and_then(|m| m.get("callee")))
|
||||
.ok_or_else(|| {
|
||||
format!("mir_call missing callee in function '{}'", func_name)
|
||||
})?;
|
||||
let ctype = callee_obj
|
||||
@ -306,10 +337,31 @@ pub fn try_parse_v1_to_module(json: &str) -> Result<Option<MirModule>, String> {
|
||||
func: ValueId::new(0),
|
||||
callee: Some(crate::mir::definitions::Callee::Global(mapped)),
|
||||
args: argv,
|
||||
effects: EffectMask::PURE,
|
||||
effects,
|
||||
});
|
||||
if let Some(d) = dst_opt { max_value_id = max_value_id.max(d.as_u32() + 1); }
|
||||
}
|
||||
"Constructor" => {
|
||||
// new box instance: box_type required
|
||||
let bt = callee_obj
|
||||
.get("box_type")
|
||||
.and_then(Value::as_str)
|
||||
.ok_or_else(|| format!(
|
||||
"mir_call callee Constructor missing box_type in function '{}'",
|
||||
func_name
|
||||
))?;
|
||||
// dst required for Constructor
|
||||
let dst = dst_opt.ok_or_else(|| format!(
|
||||
"mir_call Constructor requires dst in function '{}'",
|
||||
func_name
|
||||
))?;
|
||||
block_ref.add_instruction(MirInstruction::NewBox {
|
||||
dst,
|
||||
box_type: bt.to_string(),
|
||||
args: argv.clone(),
|
||||
});
|
||||
max_value_id = max_value_id.max(dst.as_u32() + 1);
|
||||
}
|
||||
"Method" => {
|
||||
// receiver: required u64, method: string, box_name: optional
|
||||
let method = callee_obj
|
||||
@ -342,63 +394,96 @@ pub fn try_parse_v1_to_module(json: &str) -> Result<Option<MirModule>, String> {
|
||||
certainty: crate::mir::definitions::call_unified::TypeCertainty::Known,
|
||||
}),
|
||||
args: argv,
|
||||
effects: EffectMask::PURE,
|
||||
effects,
|
||||
});
|
||||
if let Some(d) = dst_opt { max_value_id = max_value_id.max(d.as_u32() + 1); }
|
||||
}
|
||||
"Closure" => {
|
||||
// Closure creation (NewClosure equivalent)
|
||||
// Requires dst; accepts optional params[], captures[[name, id]...], me_capture
|
||||
let dst = dst_opt.ok_or_else(|| format!(
|
||||
"mir_call Closure requires dst in function '{}'",
|
||||
func_name
|
||||
))?;
|
||||
// params: array of strings (optional)
|
||||
let mut params: Vec<String> = Vec::new();
|
||||
if let Some(arr) = callee_obj.get("params").and_then(Value::as_array) {
|
||||
for p in arr {
|
||||
let s = p.as_str().ok_or_else(|| format!(
|
||||
"mir_call Closure params must be strings in function '{}'",
|
||||
func_name
|
||||
))?;
|
||||
params.push(s.to_string());
|
||||
}
|
||||
}
|
||||
// captures: array of [name, id]
|
||||
let mut captures: Vec<(String, ValueId)> = Vec::new();
|
||||
if let Some(arr) = callee_obj.get("captures").and_then(Value::as_array) {
|
||||
for e in arr {
|
||||
let pair = e.as_array().ok_or_else(|| format!(
|
||||
"mir_call Closure capture entry must be array in function '{}'",
|
||||
func_name
|
||||
))?;
|
||||
if pair.len() != 2 {
|
||||
return Err("mir_call Closure capture entry must have 2 elements".into());
|
||||
// Two shapes are seen in the wild:
|
||||
// 1) NewClosure-style descriptor (params/captures/me_capture present) → NewClosure
|
||||
// 2) Value-style descriptor (func present, optionally captures array) → Call(Callee::Value)
|
||||
let has_new_fields = callee_obj.get("params").is_some()
|
||||
|| callee_obj.get("captures").is_some()
|
||||
|| callee_obj.get("me_capture").is_some();
|
||||
if has_new_fields {
|
||||
// Closure creation (NewClosure equivalent)
|
||||
let dst = dst_opt.ok_or_else(|| format!(
|
||||
"mir_call Closure requires dst in function '{}'",
|
||||
func_name
|
||||
))?;
|
||||
// params: array of strings (optional)
|
||||
let mut params: Vec<String> = Vec::new();
|
||||
if let Some(arr) = callee_obj.get("params").and_then(Value::as_array) {
|
||||
for p in arr {
|
||||
let s = p.as_str().ok_or_else(|| format!(
|
||||
"mir_call Closure params must be strings in function '{}'",
|
||||
func_name
|
||||
))?;
|
||||
params.push(s.to_string());
|
||||
}
|
||||
let name = pair[0].as_str().ok_or_else(|| {
|
||||
"mir_call Closure capture[0] must be string".to_string()
|
||||
})?;
|
||||
let id = pair[1].as_u64().ok_or_else(|| {
|
||||
"mir_call Closure capture[1] must be integer".to_string()
|
||||
})? as u32;
|
||||
captures.push((name.to_string(), ValueId::new(id)));
|
||||
}
|
||||
// captures: array of [name, id]
|
||||
let mut captures: Vec<(String, ValueId)> = Vec::new();
|
||||
if let Some(arr) = callee_obj.get("captures").and_then(Value::as_array) {
|
||||
for e in arr {
|
||||
let pair = e.as_array().ok_or_else(|| format!(
|
||||
"mir_call Closure capture entry must be array in function '{}'",
|
||||
func_name
|
||||
))?;
|
||||
if pair.len() != 2 {
|
||||
return Err("mir_call Closure capture entry must have 2 elements".into());
|
||||
}
|
||||
let name = pair[0].as_str().ok_or_else(|| {
|
||||
"mir_call Closure capture[0] must be string".to_string()
|
||||
})?;
|
||||
let id = pair[1].as_u64().ok_or_else(|| {
|
||||
"mir_call Closure capture[1] must be integer".to_string()
|
||||
})? as u32;
|
||||
captures.push((name.to_string(), ValueId::new(id)));
|
||||
}
|
||||
}
|
||||
// me_capture: optional u64
|
||||
let me_capture = callee_obj
|
||||
.get("me_capture")
|
||||
.and_then(Value::as_u64)
|
||||
.map(|v| ValueId::new(v as u32));
|
||||
// Body is not carried in v1; create empty body vector as placeholder
|
||||
block_ref.add_instruction(MirInstruction::NewClosure {
|
||||
dst,
|
||||
params,
|
||||
body: Vec::new(),
|
||||
captures,
|
||||
me: me_capture,
|
||||
});
|
||||
max_value_id = max_value_id.max(dst.as_u32() + 1);
|
||||
} else {
|
||||
// Value-style closure: treat like Value(func id)
|
||||
let fid = callee_obj
|
||||
.get("func")
|
||||
.and_then(Value::as_u64)
|
||||
.ok_or_else(|| format!(
|
||||
"mir_call callee Closure missing func in function '{}'",
|
||||
func_name
|
||||
))? as u32;
|
||||
// Captures array (if present) are appended to argv for minimal parity
|
||||
if let Some(caps) = callee_obj.get("captures").and_then(Value::as_array) {
|
||||
for c in caps {
|
||||
let id = c.as_u64().ok_or_else(|| format!(
|
||||
"mir_call Closure capture must be integer in function '{}'",
|
||||
func_name
|
||||
))? as u32;
|
||||
argv.push(ValueId::new(id));
|
||||
}
|
||||
}
|
||||
block_ref.add_instruction(MirInstruction::Call {
|
||||
dst: dst_opt,
|
||||
func: ValueId::new(0),
|
||||
callee: Some(crate::mir::definitions::Callee::Value(ValueId::new(fid))),
|
||||
args: argv,
|
||||
effects,
|
||||
});
|
||||
if let Some(d) = dst_opt { max_value_id = max_value_id.max(d.as_u32() + 1); }
|
||||
}
|
||||
// me_capture: optional u64
|
||||
let me_capture = callee_obj
|
||||
.get("me_capture")
|
||||
.and_then(Value::as_u64)
|
||||
.map(|v| ValueId::new(v as u32));
|
||||
|
||||
// Body is not carried in v1; create empty body vector as placeholder
|
||||
block_ref.add_instruction(MirInstruction::NewClosure {
|
||||
dst,
|
||||
params,
|
||||
body: Vec::new(),
|
||||
captures,
|
||||
me: me_capture,
|
||||
});
|
||||
max_value_id = max_value_id.max(dst.as_u32() + 1);
|
||||
}
|
||||
"Constructor" => {
|
||||
// box_type: string, dst: required
|
||||
@ -453,41 +538,11 @@ pub fn try_parse_v1_to_module(json: &str) -> Result<Option<MirModule>, String> {
|
||||
func: ValueId::new(0),
|
||||
callee: Some(crate::mir::definitions::Callee::Value(ValueId::new(fid))),
|
||||
args: argv,
|
||||
effects: EffectMask::PURE,
|
||||
});
|
||||
if let Some(d) = dst_opt { max_value_id = max_value_id.max(d.as_u32() + 1); }
|
||||
}
|
||||
"Closure" => {
|
||||
// Minimal closure support: treat as Value(func id) and ignore captures here.
|
||||
// Schema: { type: "Closure", func: <u64>, captures?: [u64, ...] }
|
||||
let fid = callee_obj
|
||||
.get("func")
|
||||
.and_then(Value::as_u64)
|
||||
.ok_or_else(|| format!(
|
||||
"mir_call callee Closure missing func in function '{}'",
|
||||
func_name
|
||||
))? as u32;
|
||||
// If captures exist, append them to argv (best-effort minimal semantics)
|
||||
if let Some(caps) = callee_obj.get("captures").and_then(Value::as_array) {
|
||||
for c in caps {
|
||||
let id = c.as_u64().ok_or_else(|| format!(
|
||||
"mir_call Closure capture must be integer in function '{}'",
|
||||
func_name
|
||||
))? as u32;
|
||||
argv.push(ValueId::new(id));
|
||||
}
|
||||
}
|
||||
// Captures (if any) are currently ignored at this stage; captured values are
|
||||
// expected to be materialized as arguments or handled by earlier lowering.
|
||||
block_ref.add_instruction(MirInstruction::Call {
|
||||
dst: dst_opt,
|
||||
func: ValueId::new(0),
|
||||
callee: Some(crate::mir::definitions::Callee::Value(ValueId::new(fid))),
|
||||
args: argv,
|
||||
effects: EffectMask::PURE,
|
||||
effects,
|
||||
});
|
||||
if let Some(d) = dst_opt { max_value_id = max_value_id.max(d.as_u32() + 1); }
|
||||
}
|
||||
// (no duplicate Closure arm; handled above)
|
||||
other => {
|
||||
return Err(format!(
|
||||
"unsupported callee type '{}' in mir_call (Gate-C v1 bridge)",
|
||||
@ -515,44 +570,101 @@ pub fn try_parse_v1_to_module(json: &str) -> Result<Option<MirModule>, String> {
|
||||
|
||||
#[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") {
|
||||
// Accept both shapes:
|
||||
// 1) { "type": "i64", "value": 123 }
|
||||
// 2) { "type": {"kind":"handle","box_type":"StringBox"}, "value": "str" }
|
||||
// 3) Minimal fallback: when "type" is omitted, assume integer/string directly
|
||||
let (type_desc, raw_val) = if let Some(t) = value_obj.get("type") {
|
||||
(
|
||||
t.clone(),
|
||||
Some(t.clone()),
|
||||
value_obj
|
||||
.get("value")
|
||||
.cloned()
|
||||
.ok_or_else(|| "const value missing numeric value".to_string())?,
|
||||
.ok_or_else(|| "const value missing 'value' field".to_string())?,
|
||||
)
|
||||
} else {
|
||||
(Value::String("i64".to_string()), value_obj.clone())
|
||||
(None, value_obj.clone())
|
||||
};
|
||||
|
||||
match type_str {
|
||||
Value::String(s) => match s.as_str() {
|
||||
// String type descriptor
|
||||
if let Some(Value::String(s)) = type_desc.as_ref() {
|
||||
match s.as_str() {
|
||||
// Integer
|
||||
"i64" | "int" => {
|
||||
let val = raw_val
|
||||
.as_i64()
|
||||
.ok_or_else(|| "const value expected integer".to_string())?;
|
||||
Ok(ConstValue::Integer(val))
|
||||
return 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
|
||||
));
|
||||
// Float
|
||||
"f64" | "float" => {
|
||||
let val = raw_val
|
||||
.as_f64()
|
||||
.ok_or_else(|| "const value expected float".to_string())?;
|
||||
return Ok(ConstValue::Float(val));
|
||||
}
|
||||
// Bool (allow explicit bool schema even if current emitter uses i64)
|
||||
"i1" | "bool" => {
|
||||
let b = match raw_val {
|
||||
Value::Bool(v) => v,
|
||||
Value::Number(n) => n.as_i64().unwrap_or(0) != 0,
|
||||
Value::String(ref s) => s == "true" || s == "1",
|
||||
_ => false,
|
||||
};
|
||||
return Ok(ConstValue::Bool(b));
|
||||
}
|
||||
// String explicit
|
||||
"string" | "String" => {
|
||||
let s = raw_val
|
||||
.as_str()
|
||||
.ok_or_else(|| "const value expected string".to_string())?;
|
||||
return Ok(ConstValue::String(s.to_string()));
|
||||
}
|
||||
// Void/Null
|
||||
"void" => {
|
||||
return Ok(ConstValue::Void);
|
||||
}
|
||||
other => {
|
||||
return Err(format!(
|
||||
"unsupported const type '{}' in Gate-C v1 bridge",
|
||||
other
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Object descriptor (e.g., handle/StringBox)
|
||||
if let Some(Value::Object(map)) = type_desc.as_ref() {
|
||||
if let Some(Value::String(kind)) = map.get("kind") {
|
||||
if kind == "handle" {
|
||||
if let Some(Value::String(box_type)) = map.get("box_type") {
|
||||
match box_type.as_str() {
|
||||
// StringBox handle is serialized with raw string payload
|
||||
"StringBox" => {
|
||||
let s = raw_val
|
||||
.as_str()
|
||||
.ok_or_else(|| "StringBox const expects string value".to_string())?;
|
||||
return Ok(ConstValue::String(s.to_string()));
|
||||
}
|
||||
// Other handle kinds are not yet supported in the bridge
|
||||
other => {
|
||||
return Err(format!(
|
||||
"unsupported const handle type '{}' in Gate-C v1 bridge",
|
||||
other
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Err("unsupported const type object in Gate-C v1 bridge".to_string())
|
||||
}
|
||||
return Err("unsupported const type object in Gate-C v1 bridge".to_string());
|
||||
}
|
||||
|
||||
// No explicit type: heuristics
|
||||
match raw_val {
|
||||
Value::Number(n) => Ok(ConstValue::Integer(n.as_i64().ok_or_else(|| "integer expected".to_string())?)),
|
||||
Value::Bool(b) => Ok(ConstValue::Bool(b)),
|
||||
Value::String(s) => Ok(ConstValue::String(s)),
|
||||
_ => Err("const value has unsupported type descriptor".to_string()),
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user