Files
hakorune/src/mir/builder/lifecycle.rs
nyash-codex dd65cf7e4c builder+vm: unify method calls via emit_unified_call; add RouterPolicy trace; finalize LocalSSA/BlockSchedule guards; docs + selfhost quickstart
- Unify standard method calls to emit_unified_call; route via RouterPolicy and apply rewrite::{special,known} at a single entry.\n- Stabilize emit-time invariants: LocalSSA finalize + BlockSchedule PHI→Copy→Call ordering; metadata propagation on copies.\n- Known rewrite default ON (userbox only, strict guards) with opt-out flag NYASH_REWRITE_KNOWN_DEFAULT=0.\n- Expand TypeAnnotation whitelist (is_digit_char/is_hex_digit_char/is_alpha_char/Map.has).\n- Docs: unified-method-resolution design note; Quick Reference normalization note; selfhosting/quickstart.\n- Tools: add tools/selfhost_smoke.sh (dev-only).\n- Keep behavior unchanged for Unknown/core/user-instance via BoxCall fallback; all tests green (quick/integration).
2025-09-28 20:38:09 +09:00

291 lines
14 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

use super::{EffectMask, FunctionSignature, MirFunction, MirInstruction, MirModule, MirType, ValueId, BasicBlockId, ConstValue};
use crate::ast::ASTNode;
// Lifecycle routines extracted from builder.rs
impl super::MirBuilder {
/// Unified declaration indexing (Phase A): collect symbols before lowering
/// - user_defined_boxes: non-static Box names (for NewBox birth() skip)
/// - static_method_index: name -> [(BoxName, arity)] (for bare-call fallback)
fn index_declarations(&mut self, node: &ASTNode) {
match node {
ASTNode::Program { statements, .. } => {
for st in statements {
self.index_declarations(st);
}
}
ASTNode::BoxDeclaration { name, methods, is_static, .. } => {
if !*is_static {
self.user_defined_boxes.insert(name.clone());
} else {
for (mname, mast) in methods {
if let ASTNode::FunctionDeclaration { params, .. } = mast {
self.static_method_index
.entry(mname.clone())
.or_insert_with(Vec::new)
.push((name.clone(), params.len()));
}
}
}
}
_ => {}
}
}
pub(super) fn prepare_module(&mut self) -> Result<(), String> {
let module = MirModule::new("main".to_string());
let main_signature = FunctionSignature {
name: "main".to_string(),
params: vec![],
return_type: MirType::Void,
effects: EffectMask::PURE,
};
let entry_block = self.block_gen.next();
let mut main_function = MirFunction::new(main_signature, entry_block);
main_function.metadata.is_entry_point = true;
self.current_module = Some(module);
self.current_function = Some(main_function);
self.current_block = Some(entry_block);
// Hint: scope enter at function entry (id=0 for main)
self.hint_scope_enter(0);
if std::env::var("NYASH_BUILDER_SAFEPOINT_ENTRY")
.ok()
.as_deref()
== Some("1")
{
self.emit_instruction(MirInstruction::Safepoint)?;
}
Ok(())
}
pub(super) fn lower_root(&mut self, ast: ASTNode) -> Result<ValueId, String> {
// Pre-index static methods to enable safe fallback for bare calls in using-prepended code
let snapshot = ast.clone();
// Phase A: collect declarations in one pass (symbols available to lowering)
self.index_declarations(&snapshot);
// Phase B: top-level program lowering with declaration-first pass
match ast {
ASTNode::Program { statements, .. } => {
use crate::ast::ASTNode as N;
// First pass: lower declarations (static boxes except Main, and instance boxes)
let mut main_static: Option<(String, std::collections::HashMap<String, ASTNode>)> = None;
for st in &statements {
if let N::BoxDeclaration {
name,
methods,
is_static,
fields,
constructors,
weak_fields,
..
} = st
{
if *is_static {
if name == "Main" {
main_static = Some((name.clone(), methods.clone()));
} else {
// Lower all static methods into standalone functions: BoxName.method/Arity
for (mname, mast) in methods.iter() {
if let N::FunctionDeclaration { params, body, .. } = mast {
let func_name = format!("{}.{}{}", name, mname, format!("/{}", params.len()));
self.lower_static_method_as_function(func_name, params.clone(), body.clone())?;
self.static_method_index
.entry(mname.clone())
.or_insert_with(Vec::new)
.push((name.clone(), params.len()));
}
}
}
} else {
// Instance box: register type and lower instance methods/ctors as functions
self.user_defined_boxes.insert(name.clone());
self.build_box_declaration(
name.clone(),
methods.clone(),
fields.clone(),
weak_fields.clone(),
)?;
for (ctor_key, ctor_ast) in constructors.iter() {
if let N::FunctionDeclaration { params, body, .. } = ctor_ast {
// Keep constructor function name as "Box.birth/N" where ctor_key already encodes arity.
// ctor_key format comes from parser as "birth/<arity>".
let func_name = format!("{}.{}", name, ctor_key);
self.lower_method_as_function(
func_name,
name.clone(),
params.clone(),
body.clone(),
)?;
}
}
for (mname, mast) in methods.iter() {
if let N::FunctionDeclaration { params, body, is_static, .. } = mast {
if !*is_static {
let func_name = format!("{}.{}{}", name, mname, format!("/{}", params.len()));
self.lower_method_as_function(
func_name,
name.clone(),
params.clone(),
body.clone(),
)?;
}
}
}
}
}
}
// Second pass: lower Main.main body as Program (entry). If absent, fall back to sequential block.
if let Some((box_name, methods)) = main_static {
self.build_static_main_box(box_name, methods)
} else {
// Fallback: sequential lowering (keeps legacy behavior for scripts without Main)
self.cf_block(statements)
}
}
other => self.build_expression(other),
}
}
pub(super) fn finalize_module(
&mut self,
result_value: ValueId,
) -> Result<MirModule, String> {
// Hint: scope leave at function end (id=0 for main)
self.hint_scope_leave(0);
if let Some(block_id) = self.current_block {
if let Some(ref mut function) = self.current_function {
if let Some(block) = function.get_block_mut(block_id) {
if !block.is_terminated() {
block.add_instruction(MirInstruction::Return {
value: Some(result_value),
});
}
if let Some(mt) = self.value_types.get(&result_value).cloned() {
function.signature.return_type = mt;
}
}
}
}
let mut module = self.current_module.take().unwrap();
let mut function = self.current_function.take().unwrap();
function.metadata.value_types = self.value_types.clone();
if matches!(
function.signature.return_type,
super::MirType::Void | super::MirType::Unknown
) {
let mut inferred: Option<super::MirType> = None;
'outer: for (_bid, bb) in function.blocks.iter() {
for inst in bb.instructions.iter() {
if let super::MirInstruction::Return { value: Some(v) } = inst {
if let Some(mt) = self.value_types.get(v).cloned() {
inferred = Some(mt);
break 'outer;
}
if let Some(mt) = crate::mir::phi_core::if_phi::infer_type_from_phi(
&function,
*v,
&self.value_types,
) {
inferred = Some(mt);
break 'outer;
}
}
}
if let Some(super::MirInstruction::Return { value: Some(v) }) = &bb.terminator {
if let Some(mt) = self.value_types.get(v).cloned() {
inferred = Some(mt);
break;
}
if let Some(mt) = crate::mir::phi_core::if_phi::infer_type_from_phi(
&function,
*v,
&self.value_types,
) {
inferred = Some(mt);
break;
}
}
}
if let Some(mt) = inferred {
function.signature.return_type = mt;
}
}
// Dev-only verify: NewBox → birth() invariant (warn if missing)
if crate::config::env::using_is_dev() {
let mut warn_count = 0usize;
for (_bid, bb) in function.blocks.iter() {
let insns = &bb.instructions;
let mut idx = 0usize;
while idx < insns.len() {
if let MirInstruction::NewBox { dst, box_type, args } = &insns[idx] {
// Skip StringBox (literal optimization path)
if box_type != "StringBox" {
let expect_tail = format!("{}.birth/{}", box_type, args.len());
// Look ahead up to 3 instructions for either BoxCall("birth") on dst or Global(expect_tail)
let mut ok = false;
let mut j = idx + 1;
let mut last_const_name: Option<String> = None;
while j < insns.len() && j <= idx + 3 {
match &insns[j] {
MirInstruction::BoxCall { box_val, method, .. } => {
if method == "birth" && box_val == dst { ok = true; break; }
}
MirInstruction::Const { value, .. } => {
if let super::ConstValue::String(s) = value { last_const_name = Some(s.clone()); }
}
MirInstruction::Call { func, .. } => {
// If immediately preceded by matching Const String, accept
if let Some(prev) = last_const_name.as_ref() {
if prev == &expect_tail { ok = true; break; }
}
// Heuristic: in some forms, builder may reuse a shared const; best-effort only
}
_ => {}
}
j += 1;
}
if !ok {
eprintln!("[warn] dev verify: NewBox {} at v{} not followed by birth() call (expect {})", box_type, dst, expect_tail);
warn_count += 1;
}
}
}
idx += 1;
}
}
if warn_count > 0 {
eprintln!("[warn] dev verify: NewBox→birth invariant warnings: {}", warn_count);
}
}
module.add_function(function);
// Dev stub: provide condition_fn when missing to satisfy predicate calls in JSON lexers
// Returns integer 1 (truthy) and accepts one argument (unused).
if module.functions.get("condition_fn").is_none() {
let mut sig = FunctionSignature {
name: "condition_fn".to_string(),
params: vec![MirType::Integer], // accept one i64-like arg
return_type: MirType::Integer,
effects: EffectMask::PURE,
};
let entry = BasicBlockId::new(0);
let mut f = MirFunction::new(sig, entry);
// parameter slot (unused in body)
let _param = f.next_value_id();
f.params.push(_param);
// body: const 1; return itFunctionEmissionBox を使用)
let one = crate::mir::function_emission::emit_const_integer(&mut f, entry, 1);
crate::mir::function_emission::emit_return_value(&mut f, entry, one);
module.add_function(f);
}
Ok(module)
}
}