Phase 21.6 solidification: chain green (return/binop/loop/call); add Phase 21.7 normalization plan (methodize static boxes). Update CURRENT_TASK.md and docs.

This commit is contained in:
nyash-codex
2025-11-11 22:35:45 +09:00
parent 52b62c5772
commit 9e2fa1e36e
19 changed files with 1309 additions and 35 deletions

View File

@ -802,6 +802,30 @@ impl MirInterpreter {
}
}
// Fallback: user-defined function dispatch for Global calls
// If none of the above extern/provider/global bridges matched,
// try to resolve and execute a user function present in this module.
{
// Use unique-tail resolver against snapshot of self.functions
let fname = call_resolution::resolve_function_name(
func_name,
args.len(),
&self.functions,
self.cur_fn.as_deref(),
);
if let Some(fname) = fname {
if let Some(func) = self.functions.get(&fname).cloned() {
// Load arguments and execute
let mut argv: Vec<VMValue> = Vec::new();
for a in args { argv.push(self.reg_load(*a)?); }
if std::env::var("NYASH_VM_CALL_TRACE").ok().as_deref() == Some("1") {
eprintln!("[vm] global-call resolved '{}' -> '{}'", func_name, fname);
}
return self.exec_function_inner(&func, Some(&argv));
}
}
}
fn execute_extern_function(
&mut self,
extern_name: &str,

View File

@ -196,8 +196,15 @@ def lower_blocks(builder, func: ir.Function, block_by_id: Dict[int, Dict[str, An
try:
dst = inst.get("dst")
if isinstance(dst, int):
if dst in builder.vmap:
# Prefer current vmap context (_current_vmap) updates; fallback to global vmap
_gval = None
try:
_gval = vmap_cur.get(dst)
except Exception:
_gval = None
if _gval is None and dst in builder.vmap:
_gval = builder.vmap[dst]
if _gval is not None:
try:
if hasattr(_gval, 'add_incoming'):
bb_of = getattr(getattr(_gval, 'basic_block', None), 'name', None)

View File

@ -30,6 +30,13 @@ def lower_function(builder, func_data: Dict[str, Any]):
# Default: i64(i64, ...) signature; derive arity from '/N' suffix when params missing
m = re.search(r"/(\d+)$", name)
arity = int(m.group(1)) if m else len(params)
# Dev fallback: when params are missing for global (Box.method) functions,
# use observed call-site arity if available (scanned in builder.build_from_mir)
if arity == 0 and '.' in name:
try:
arity = int(builder.call_arities.get(name, 0))
except Exception:
pass
param_types = [builder.i64] * arity
func_ty = ir.FunctionType(builder.i64, param_types)
@ -67,11 +74,38 @@ def lower_function(builder, func_data: Dict[str, Any]):
if func is None:
func = ir.Function(builder.module, func_ty, name=name)
# Map parameters to vmap (value_id: 0..arity-1)
# Map parameters to vmap. Prefer mapping by referenced value-ids that have no
# local definition (common in v0 JSON where params appear as lhs/rhs ids).
try:
arity = len(func.args)
# Collect defined and used ids
defs = set()
uses = set()
for bb in (blocks or []):
for ins in (bb.get('instructions') or []):
try:
dstv = ins.get('dst')
if isinstance(dstv, int):
defs.add(int(dstv))
except Exception:
pass
for k in ('lhs','rhs','value','cond','box_val'):
try:
v = ins.get(k)
if isinstance(v, int):
uses.add(int(v))
except Exception:
pass
cand = [vid for vid in uses if vid not in defs]
cand.sort()
mapped = 0
for i in range(min(arity, len(cand))):
builder.vmap[int(cand[i])] = func.args[i]
mapped += 1
# Fallback: also map positional 0..arity-1 to args if not already mapped
for i in range(arity):
builder.vmap[i] = func.args[i]
if i not in builder.vmap:
builder.vmap[i] = func.args[i]
except Exception:
pass

View File

@ -133,6 +133,46 @@ class NyashLLVMBuilder:
# Parse MIR
reader = MIRReader(mir_json)
functions = reader.get_functions()
# Pre-scan call sites to estimate arity for global functions when params are missing
def _scan_call_arities(funcs: List[Dict[str, Any]]):
ar: Dict[str, int] = {}
for f in funcs or []:
# Build map: const dst -> string name (per-function scope)
const_names: Dict[int, str] = {}
for bb in (f.get('blocks') or []):
for ins in (bb.get('instructions') or []):
try:
op = ins.get('op')
if op == 'const':
dst = ins.get('dst')
val = ins.get('value') or {}
name = None
if isinstance(val, dict):
v = val.get('value')
t = val.get('type')
if isinstance(v, str) and (
t == 'string' or (isinstance(t, dict) and t.get('box_type') == 'StringBox')
):
name = v
if isinstance(dst, int) and isinstance(name, str):
const_names[int(dst)] = name
elif op == 'call':
func_id = ins.get('func')
if isinstance(func_id, int) and func_id in const_names:
nm = const_names[func_id]
argc = len(ins.get('args') or [])
prev = ar.get(nm, 0)
if argc > prev:
ar[nm] = argc
except Exception:
continue
return ar
try:
self.call_arities = _scan_call_arities(functions)
except Exception:
self.call_arities = {}
if not functions:
# No functions - create dummy ny_main
@ -149,6 +189,12 @@ class NyashLLVMBuilder:
params_list = func_data.get("params", []) or []
if "." in name:
arity = len(params_list)
# Dev fallback: when params missing for Box.method, use call-site arity
if arity == 0:
try:
arity = int(self.call_arities.get(name, 0))
except Exception:
pass
else:
arity = int(m.group(1)) if m else len(params_list)
if name == "ny_main":

View File

@ -1,10 +1,21 @@
use serde::{Deserialize, Serialize};
#[derive(Debug, Deserialize, Serialize)]
#[derive(Debug, Deserialize, Serialize, Clone)]
pub(super) struct ProgramV0 {
pub(super) version: i32,
pub(super) kind: String,
pub(super) body: Vec<StmtV0>,
#[serde(default)]
pub(super) defs: Vec<FuncDefV0>,
}
#[derive(Debug, Deserialize, Serialize, Clone)]
pub(super) struct FuncDefV0 {
pub(super) name: String,
pub(super) params: Vec<String>,
pub(super) body: ProgramV0,
#[serde(rename = "box")]
pub(super) box_name: String,
}
#[derive(Debug, Deserialize, Serialize, Clone)]

View File

@ -185,6 +185,7 @@ pub(super) fn parse_source_v0_to_json(input: &str) -> Result<String, String> {
version: 0,
kind: "Program".into(),
body: vec![StmtV0::Return { expr }],
defs: vec![],
};
serde_json::to_string(&prog).map_err(|e| e.to_string())
}

View File

@ -293,6 +293,103 @@ pub(super) fn lower_program(prog: ProgramV0) -> Result<MirModule, String> {
f.signature.return_type = MirType::Unknown;
// フェーズM.2: PHI後処理削除 - MirBuilder/LoopBuilderでPHI統一済み
module.add_function(f);
// Phase 21.6: Process function definitions (defs)
// Toggle: HAKO_STAGEB_FUNC_SCAN=1 + HAKO_MIR_BUILDER_FUNCS=1
// Minimal support: Return(Int|Binary(+|-|*|/, Int|Var, Int|Var))
let mut func_map: HashMap<String, String> = HashMap::new();
if !prog.defs.is_empty() {
for func_def in prog.defs {
// Create function signature: Main.<name>
let func_name = format!("{}.{}", func_def.box_name, func_def.name);
// Register function in map for Call resolution
func_map.insert(func_def.name.clone(), func_name.clone());
let param_ids: Vec<ValueId> = (0..func_def.params.len())
.map(|i| ValueId::new(i as u32 + 1))
.collect();
let param_types: Vec<MirType> = (0..func_def.params.len())
.map(|_| MirType::Unknown)
.collect();
let sig = FunctionSignature {
name: func_name,
params: param_types,
return_type: MirType::Integer,
effects: EffectMask::PURE,
};
let entry = BasicBlockId::new(0);
let mut func = MirFunction::new(sig, entry);
// Map params to value IDs
let mut func_var_map: HashMap<String, ValueId> = HashMap::new();
for (i, param_name) in func_def.params.iter().enumerate() {
func_var_map.insert(param_name.clone(), param_ids[i]);
}
// Lower function body
let mut loop_stack: Vec<LoopContext> = Vec::new();
let start_bb = func.entry_block;
let _end_bb = lower_stmt_list_with_vars(
&mut func,
start_bb,
&func_def.body.body,
&mut func_var_map,
&mut loop_stack,
&env,
)?;
func.signature.return_type = MirType::Unknown;
module.add_function(func);
}
}
// Phase 21.6: Call resolution post-processing
// Toggle: HAKO_MIR_BUILDER_CALL_RESOLVE=1
// Resolve Call instructions to use qualified function names (e.g., "add" -> "Main.add")
if std::env::var("HAKO_MIR_BUILDER_CALL_RESOLVE").ok().as_deref() == Some("1") {
if !func_map.is_empty() {
for (_func_idx, func) in module.functions.iter_mut() {
for (_block_id, block) in func.blocks.iter_mut() {
let mut const_replacements: Vec<(ValueId, String)> = Vec::new();
// Find Call instructions and their associated Const values
for inst in &block.instructions {
if let MirInstruction::Call { func: func_reg, .. } = inst {
// Look for the Const instruction that defines func_reg
for const_inst in &block.instructions {
if let MirInstruction::Const { dst, value } = const_inst {
if dst == func_reg {
if let ConstValue::String(name) = value {
// Try to resolve the name
if let Some(resolved) = func_map.get(name) {
const_replacements.push((*dst, resolved.clone()));
if std::env::var("HAKO_MIR_BUILDER_DEBUG").ok().as_deref() == Some("1") {
eprintln!("[mirbuilder/call:resolve] {} => {}", name, resolved);
}
}
}
}
}
}
}
}
// Apply replacements
for (dst, new_name) in const_replacements {
for inst in &mut block.instructions {
if let MirInstruction::Const { dst: d, value } = inst {
if d == &dst {
*value = ConstValue::String(new_name.clone());
}
}
}
}
}
}
}
}
Ok(module)
}