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).
This commit is contained in:
nyash-codex
2025-09-28 20:38:09 +09:00
parent e442e5f612
commit dd65cf7e4c
60 changed files with 2523 additions and 471 deletions

View File

@ -79,6 +79,33 @@ pub(super) fn try_handle_string_box(
if let Some(d) = dst { this.regs.insert(d, VMValue::from_nyash_box(Box::new(crate::box_trait::StringBox::new(new_s)))) ; }
return Ok(true);
}
"is_digit_char" => {
// Accept either 0-arg (use first char of receiver) or 1-arg (string/char to test)
let ch_opt = if args.is_empty() {
sb.value.chars().next()
} else if args.len() == 1 {
let s = this.reg_load(args[0])?.to_string();
s.chars().next()
} else {
return Err(VMError::InvalidInstruction("is_digit_char expects 0 or 1 arg".into()));
};
let is_digit = ch_opt.map(|c| c.is_ascii_digit()).unwrap_or(false);
if let Some(d) = dst { this.regs.insert(d, VMValue::Bool(is_digit)); }
return Ok(true);
}
"is_hex_digit_char" => {
let ch_opt = if args.is_empty() {
sb.value.chars().next()
} else if args.len() == 1 {
let s = this.reg_load(args[0])?.to_string();
s.chars().next()
} else {
return Err(VMError::InvalidInstruction("is_hex_digit_char expects 0 or 1 arg".into()));
};
let is_hex = ch_opt.map(|c| c.is_ascii_hexdigit()).unwrap_or(false);
if let Some(d) = dst { this.regs.insert(d, VMValue::Bool(is_hex)); }
return Ok(true);
}
_ => {}
}
}

View File

@ -28,7 +28,24 @@ impl MirInterpreter {
Callee::Global(func_name) => self.execute_global_function(func_name, args),
Callee::Method { box_name: _, method, receiver, certainty: _, } => {
if let Some(recv_id) = receiver {
let recv_val = self.reg_load(*recv_id)?;
// Primary: load receiver by id. Dev fallback: if undefined and env allows,
// use args[0] as a surrogate receiver (builder localization gap workaround).
let recv_val = match self.reg_load(*recv_id) {
Ok(v) => v,
Err(e) => {
let tolerate = std::env::var("NYASH_VM_RECV_ARG_FALLBACK").ok().as_deref() == Some("1")
|| std::env::var("NYASH_VM_TOLERATE_VOID").ok().as_deref() == Some("1");
if tolerate {
if let Some(a0) = args.get(0) {
self.reg_load(*a0)?
} else {
return Err(e);
}
} else {
return Err(e);
}
}
};
let dev_trace = std::env::var("NYASH_VM_TRACE").ok().as_deref() == Some("1");
// Fast bridge for builtin boxes (Array) and common methods.
// Preserve legacy semantics when plugins are absent.

View File

@ -36,6 +36,11 @@ impl MirInterpreter {
keys.join(", ")
);
}
// Dev-time safety valve: tolerate undefined registers as Void when enabled
let tolerate = std::env::var("NYASH_VM_TOLERATE_VOID").ok().as_deref() == Some("1");
if tolerate {
return Ok(VMValue::Void);
}
Err(VMError::InvalidValue(format!(
"use of undefined value {:?}",
id
@ -64,8 +69,7 @@ impl MirInterpreter {
use BinaryOp::*;
use VMValue::*;
// Dev-time: normalize BoxRef(VoidBox) → VMValue::Void when tolerance is enabled or in --dev mode.
let tolerate = std::env::var("NYASH_VM_TOLERATE_VOID").ok().as_deref() == Some("1")
|| std::env::var("NYASH_DEV").ok().as_deref() == Some("1");
let tolerate = std::env::var("NYASH_VM_TOLERATE_VOID").ok().as_deref() == Some("1");
let (a, b) = if tolerate {
let norm = |v: VMValue| -> VMValue {
if let VMValue::BoxRef(bx) = &v {
@ -131,8 +135,7 @@ impl MirInterpreter {
use CompareOp::*;
use VMValue::*;
// Dev-time: normalize BoxRef(VoidBox) → VMValue::Void when tolerance is enabled or in --dev.
let tolerate = std::env::var("NYASH_VM_TOLERATE_VOID").ok().as_deref() == Some("1")
|| std::env::var("NYASH_DEV").ok().as_deref() == Some("1");
let tolerate = std::env::var("NYASH_VM_TOLERATE_VOID").ok().as_deref() == Some("1");
let (a, b) = if tolerate {
let norm = |v: VMValue| -> VMValue {
if let VMValue::BoxRef(bx) = &v {

View File

@ -2,9 +2,9 @@
//! Feature-gated behind `aot-plan-import`.
use crate::mir::{
BasicBlockId, ConstValue, EffectMask, FunctionSignature, MirFunction, MirInstruction,
MirModule, MirType,
BasicBlockId, ConstValue, EffectMask, FunctionSignature, MirFunction, MirModule, MirType,
};
use crate::mir::function_emission as femit;
#[derive(Debug, serde::Deserialize)]
struct PlanV1 {
@ -88,27 +88,38 @@ pub fn import_from_str(plan_json: &str) -> Result<MirModule, String> {
// Body lowering (skeleton)
match &f.body {
Some(PlanBody::ConstReturn { value }) => {
let dst = mf.next_value_id();
let cst = const_from_json(value)
.ok_or_else(|| format!("unsupported const value in {}", f.name))?;
if let Some(b) = mf.get_block_mut(bb) {
b.add_instruction(MirInstruction::Const { dst, value: cst });
b.set_terminator(MirInstruction::Return { value: Some(dst) });
}
// If return_type is unspecified, set Unknown to allow VM dynamic display
// Otherwise retain declared type
let dst = match cst {
ConstValue::Integer(i) => femit::emit_const_integer(&mut mf, bb, i),
ConstValue::Bool(b) => femit::emit_const_bool(&mut mf, bb, b),
ConstValue::Float(fl) => {
// function_emission currently has no float helper; use manual emit via integer as placeholder is wrong.
// Fall back to direct const emission inline here to avoid adding a new helper unnecessarily.
let d = mf.next_value_id();
if let Some(block) = mf.get_block_mut(bb) {
block.add_instruction(crate::mir::MirInstruction::Const { dst: d, value: ConstValue::Float(fl) });
}
d
}
ConstValue::String(s) => femit::emit_const_string(&mut mf, bb, s),
other => {
// Null/Void are not expected in PlanBody::ConstReturn; still handle gracefully.
let d = mf.next_value_id();
if let Some(block) = mf.get_block_mut(bb) {
block.add_instruction(crate::mir::MirInstruction::Const { dst: d, value: other });
}
d
}
};
femit::emit_return_value(&mut mf, bb, dst);
// If return_type is unspecified, keep Unknown to allow VM dynamic display; otherwise retain declared type
if matches!(ret_ty, MirType::Unknown) { /* keep Unknown */ }
}
Some(PlanBody::Empty) | None => {
// Return void or default 0 for integer; choose Unknown for display stability
let dst = mf.next_value_id();
if let Some(b) = mf.get_block_mut(bb) {
b.add_instruction(MirInstruction::Const {
dst,
value: ConstValue::Integer(0),
});
b.set_terminator(MirInstruction::Return { value: Some(dst) });
}
// Return default 0 for display stability; mark signature Unknown for runtime display parity
let dst = femit::emit_const_integer(&mut mf, bb, 0);
femit::emit_return_value(&mut mf, bb, dst);
mf.signature.return_type = MirType::Unknown;
}
}

View File

@ -40,6 +40,14 @@ mod vars; // variables/scope helpers // small loop helpers (header/exit context)
mod origin; // P0: origin inferenceme/Knownと PHI 伝播(軽量)
mod observe; // P0: dev-only observability helpersssa/resolve
mod rewrite; // P1: Known rewrite & special consolidation
mod ssa; // LocalSSA helpers (in-block materialization)
mod schedule; // BlockScheduleBox物理順序: PHI→materialize→body
mod metadata; // MetadataPropagationBoxtype/originの伝播
mod emission; // emission::*Const/Compare/Branch の薄い発行箱)
mod types; // types::annotation / inference型注釈/推論の箱: 推論は後段)
mod router; // RouterPolicyBoxUnified vs BoxCall
mod emit_guard; // EmitGuardBoxemit直前の最終関所
mod name_const; // NameConstBox関数名Const生成
// Unified member property kinds for computed/once/birth_once
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
@ -152,6 +160,13 @@ pub struct MirBuilder {
/// Monotonic counters for region IDs (deterministic across a run).
debug_loop_counter: u32,
debug_join_counter: u32,
/// Local SSA cache: ensure per-block materialization for critical operands (e.g., recv)
/// Key: (bb, original ValueId, kind) -> local ValueId
/// kind: 0=recv, 1+ reserved for future (args etc.)
pub(super) local_ssa_map: HashMap<(BasicBlockId, ValueId, u8), ValueId>,
/// BlockSchedule cache: deduplicate materialize copies per (bb, src)
pub(super) schedule_mat_map: HashMap<(BasicBlockId, ValueId), ValueId>,
}
impl MirBuilder {
@ -199,6 +214,9 @@ impl MirBuilder {
debug_scope_stack: Vec::new(),
debug_loop_counter: 0,
debug_join_counter: 0,
local_ssa_map: HashMap::new(),
schedule_mat_map: HashMap::new(),
}
}
@ -343,20 +361,15 @@ impl MirBuilder {
_ => None,
};
let const_value = match literal {
LiteralValue::Integer(n) => ConstValue::Integer(n),
LiteralValue::Float(f) => ConstValue::Float(f),
LiteralValue::String(s) => ConstValue::String(s),
LiteralValue::Bool(b) => ConstValue::Bool(b),
LiteralValue::Null => ConstValue::Null,
LiteralValue::Void => ConstValue::Void,
// Emit via ConstantEmissionBox仕様不変の統一ルート
let dst = match literal {
LiteralValue::Integer(n) => crate::mir::builder::emission::constant::emit_integer(self, n),
LiteralValue::Float(f) => crate::mir::builder::emission::constant::emit_float(self, f),
LiteralValue::String(s) => crate::mir::builder::emission::constant::emit_string(self, s),
LiteralValue::Bool(b) => crate::mir::builder::emission::constant::emit_bool(self, b),
LiteralValue::Null => crate::mir::builder::emission::constant::emit_null(self),
LiteralValue::Void => crate::mir::builder::emission::constant::emit_void(self),
};
let dst = self.value_gen.next();
self.emit_instruction(MirInstruction::Const {
dst,
value: const_value,
})?;
// Annotate type
if let Some(ty) = ty_for_dst {
self.value_types.insert(dst, ty);
@ -494,12 +507,8 @@ impl MirBuilder {
// Phase 9.78a: Unified Box creation using NewBox instruction
// Core-13 pure mode: emit ExternCall(env.box.new) with type name const only
if crate::config::env::mir_core13_pure() {
// Emit Const String for type name
let ty_id = self.value_gen.next();
self.emit_instruction(MirInstruction::Const {
dst: ty_id,
value: ConstValue::String(class.clone()),
})?;
// Emit Const String for type nameConstantEmissionBox
let ty_id = crate::mir::builder::emission::constant::emit_string(self, class.clone());
// Evaluate arguments (pass through to env.box.new shim)
let mut arg_vals: Vec<ValueId> = Vec::with_capacity(arguments.len());
for a in arguments {

View File

@ -31,6 +31,11 @@ impl super::MirBuilder {
ret = super::MirType::Box("JsonToken".into());
}
}
// Parser factory: JsonParserModule.create_parser/0 returns JsonParser
if name == "JsonParserModule.create_parser/0" {
// Normalize to Known Box(JsonParser)
ret = super::MirType::Box("JsonParser".into());
}
self.value_types.insert(dst, ret.clone());
if let super::MirType::Box(bx) = ret {
self.value_origin_newbox.insert(dst, bx);
@ -65,6 +70,17 @@ impl super::MirBuilder {
if super::utils::builder_debug_enabled() || std::env::var("NYASH_BUILDER_DEBUG").ok().as_deref() == Some("1") {
super::utils::builder_debug_log(&format!("annotate call (fallback) dst={} from {} -> Box(ArrayBox)", dst.0, name));
}
} else if name == "JsonParserModule.create_parser/0" {
// Fallback path for parser factory
let ret = super::MirType::Box("JsonParser".into());
self.value_types.insert(dst, ret.clone());
if let super::MirType::Box(bx) = ret { self.value_origin_newbox.insert(dst, bx); }
if super::utils::builder_debug_enabled() || std::env::var("NYASH_BUILDER_DEBUG").ok().as_deref() == Some("1") {
super::utils::builder_debug_log(&format!("annotate call (fallback) dst={} from {} -> Box(JsonParser)", dst.0, name));
}
} else {
// Generic tiny whitelist for known primitive-like utilities (spec unchanged)
crate::mir::builder::types::annotation::annotate_from_function(self, dst, name);
}
}
/// Unified call emission - replaces all emit_*_call methods
@ -81,6 +97,22 @@ impl super::MirBuilder {
return self.emit_legacy_call(dst, target, args);
}
// Ensure method receiver is materialized in the current block.
// This avoids "use of undefined recv" across block boundaries for direct Method calls
// that bypass legacy BoxCall emission. Do this before any observation/rewrite.
let mut target = target;
let bb_before = self.current_block; // snapshot for later re-check
if let CallTarget::Method { box_type, method, receiver } = target {
if super::utils::builder_debug_enabled() && method == "advance" {
super::utils::builder_debug_log(&format!("unified-entry Method.advance recv=%{} (pre-pin)", receiver.0));
}
let receiver_pinned = match self.pin_to_slot(receiver, "@recv") {
Ok(v) => v,
Err(_) => receiver,
};
target = CallTarget::Method { box_type, method, receiver: receiver_pinned };
}
// Emit resolve.try for method targets (dev-only; default OFF)
let arity_for_try = args.len();
if let CallTarget::Method { ref box_type, ref method, receiver } = target {
@ -120,7 +152,7 @@ impl super::MirBuilder {
// Convert CallTarget to Callee using the new module
if let CallTarget::Global(ref _n) = target { /* dev trace removed */ }
// Fallback: if Global target is unknown, try unique static-method mapping (name/arity)
let callee = match call_unified::convert_target_to_callee(
let mut callee = match call_unified::convert_target_to_callee(
target.clone(),
&self.value_origin_newbox,
&self.value_types,
@ -131,15 +163,27 @@ impl super::MirBuilder {
// 0) Dev-only safety: treat condition_fn as always-true predicate when missing
if name == "condition_fn" {
let dstv = dst.unwrap_or_else(|| self.value_gen.next());
self.emit_instruction(MirInstruction::Const { dst: dstv, value: super::ConstValue::Integer(1) })?;
// Emit integer constant via ConstantEmissionBox
let one = crate::mir::builder::emission::constant::emit_integer(self, 1);
if dst.is_none() {
// If a destination was not provided, copy into the allocated dstv
self.emit_instruction(MirInstruction::Copy { dst: dstv, src: one })?;
crate::mir::builder::metadata::propagate::propagate(self, one, dstv);
} else {
// If caller provided dst, ensure the computed value lands there
self.emit_instruction(MirInstruction::Copy { dst: dstv, src: one })?;
crate::mir::builder::metadata::propagate::propagate(self, one, dstv);
}
return Ok(());
}
// 1) Direct module function fallback: call by name if present
if let Some(ref module) = self.current_module {
if module.functions.contains_key(name) {
let dstv = dst.unwrap_or_else(|| self.value_gen.next());
let name_const = self.value_gen.next();
self.emit_instruction(MirInstruction::Const { dst: name_const, value: super::ConstValue::String(name.clone()) })?;
let name_const = match crate::mir::builder::name_const::make_name_const_result(self, name) {
Ok(v) => v,
Err(e) => return Err(e),
};
self.emit_instruction(MirInstruction::Call { dst: Some(dstv), func: name_const, callee: None, args: args.clone(), effects: EffectMask::IO })?;
self.annotate_call_result_from_func_name(dstv, name);
return Ok(());
@ -157,11 +201,10 @@ impl super::MirBuilder {
let func_name = format!("{}.{}{}", bx, name, format!("/{}", arity_for_try));
// Emit legacy call directly to preserve behavior
let dstv = dst.unwrap_or_else(|| self.value_gen.next());
let name_const = self.value_gen.next();
self.emit_instruction(MirInstruction::Const {
dst: name_const,
value: super::ConstValue::String(func_name.clone()),
})?;
let name_const = match crate::mir::builder::name_const::make_name_const_result(self, &func_name) {
Ok(v) => v,
Err(e) => return Err(e),
};
self.emit_instruction(MirInstruction::Call {
dst: Some(dstv),
func: name_const,
@ -179,6 +222,40 @@ impl super::MirBuilder {
}
};
// Safety: ensure receiver is materialized even after callee conversion
// (covers rare paths where earlier pin did not take effect)
callee = match callee {
Callee::Method { box_name, method, receiver: Some(r), certainty } => {
// Prefer pinning to a slot so start_new_block can propagate it across entries.
let r_pinned = self.pin_to_slot(r, "@recv").unwrap_or(r);
Callee::Method { box_name, method, receiver: Some(r_pinned), certainty }
}
other => other,
};
// Final guard: if current block changed since entry, ensure receiver is copied again
if let (Some(bb0), Some(bb1)) = (bb_before, self.current_block) {
if bb0 != bb1 {
if let Callee::Method { box_name, method, receiver: Some(r), certainty } = callee {
if super::utils::builder_debug_enabled() {
super::utils::builder_debug_log(&format!(
"unified-call bb changed: {:?} -> {:?}; re-materialize recv=%{}",
bb0, bb1, r.0
));
}
let r2 = self.pin_to_slot(r, "@recv").unwrap_or(r);
callee = Callee::Method { box_name, method, receiver: Some(r2), certainty };
}
}
}
// Debug: trace unified method emission with pinned receiver (dev only)
if super::utils::builder_debug_enabled() {
if let Callee::Method { method, receiver: Some(r), .. } = &callee {
super::utils::builder_debug_log(&format!("unified-call method={} recv=%{} (pinned)", method, r.0));
}
}
// Emit resolve.choose for method callee (dev-only; default OFF)
if let Callee::Method { box_name, method, certainty, .. } = &callee {
let chosen = format!("{}.{}{}", box_name, method, format!("/{}", arity_for_try));
@ -196,19 +273,92 @@ impl super::MirBuilder {
// Validate call arguments
call_unified::validate_call_args(&callee, &args)?;
// Create MirCall instruction using the new module
let mir_call = call_unified::create_mir_call(dst, callee.clone(), args.clone());
// Stability guard: decide route via RouterPolicyBox (behavior-preserving rules)
if let Callee::Method { box_name, method, receiver: Some(r), certainty } = &callee {
let route = crate::mir::builder::router::policy::choose_route(box_name, method, *certainty, arity_for_try);
if let crate::mir::builder::router::policy::Route::BoxCall = route {
if super::utils::builder_debug_enabled() || std::env::var("NYASH_LOCAL_SSA_TRACE").ok().as_deref() == Some("1") {
eprintln!("[router-guard] {}.{} → BoxCall fallback (recv=%{})", box_name, method, r.0);
}
let effects = EffectMask::READ.add(Effect::ReadHeap);
return self.emit_box_or_plugin_call(dst, *r, method.clone(), None, args, effects);
}
}
// For Phase 2: Convert to legacy Call instruction with new callee field
// Before creating the call, enforce slot (pin) + LocalSSA for Method receiver in the current block
let callee = match callee {
Callee::Method { box_name, method, receiver: Some(r), certainty } => {
// Pin to a named slot so start_new_block can propagate across entries
let r_pinned = self.pin_to_slot(r, "@recv").unwrap_or(r);
// And ensure in-block materialization for this emission site
let r_local = self.local_recv(r_pinned);
Callee::Method { box_name, method, receiver: Some(r_local), certainty }
}
other => other,
};
// Finalize operands in current block (EmitGuardBox wrapper)
let mut callee = callee;
let mut args_local: Vec<ValueId> = args.clone();
crate::mir::builder::emit_guard::finalize_call_operands(self, &mut callee, &mut args_local);
// Create MirCall instruction using the new module (pure data composition)
let mir_call = call_unified::create_mir_call(dst, callee.clone(), args_local.clone());
// Final last-chance LocalSSA just before emission in case any block switch happened
let mut callee2 = callee.clone();
let mut args2 = args_local.clone();
crate::mir::builder::emit_guard::finalize_call_operands(self, &mut callee2, &mut args2);
// Dev trace: show final callee/recv right before emission (guarded)
if std::env::var("NYASH_LOCAL_SSA_TRACE").ok().as_deref() == Some("1") || super::utils::builder_debug_enabled() {
if let Callee::Method { method, receiver, box_name, .. } = &callee2 {
if let Some(r) = receiver {
eprintln!("[vm-call-final] bb={:?} method={} recv=%{} class={}",
self.current_block, method, r.0, box_name);
}
}
}
// Final forced in-block materialization for receiver just-before emission
// Rationale: ensure a Copy(def) immediately precedes the Call in the same block
let callee2 = match callee2 {
Callee::Method { box_name, method, receiver: Some(r), certainty } => {
if super::utils::builder_debug_enabled() || std::env::var("NYASH_LOCAL_SSA_TRACE").ok().as_deref() == Some("1") {
eprintln!("[call-forced-copy] bb={:?} recv=%{} -> (copy)", self.current_block, r.0);
}
// Insert immediately after PHIs to guarantee position and dominance
let r_forced = crate::mir::builder::schedule::block::BlockScheduleBox::ensure_after_phis_copy(self, r)?;
if super::utils::builder_debug_enabled() || std::env::var("NYASH_LOCAL_SSA_TRACE").ok().as_deref() == Some("1") {
eprintln!("[call-forced-copy] bb={:?} inserted Copy %{} := %{} (after PHIs)", self.current_block, r_forced.0, r.0);
}
Callee::Method { box_name, method, receiver: Some(r_forced), certainty }
}
other => other,
};
// Optional last-chance: emit a tail copy right before we emit Call (keeps order stable)
let callee2 = match callee2 {
Callee::Method { box_name, method, receiver: Some(r), certainty } => {
let r_tail = crate::mir::builder::schedule::block::BlockScheduleBox::emit_before_call_copy(self, r)?;
Callee::Method { box_name, method, receiver: Some(r_tail), certainty }
}
other => other,
};
// For Phase 2: Convert to legacy Call instruction with new callee field (use finalized operands)
let legacy_call = MirInstruction::Call {
dst: mir_call.dst,
func: ValueId::new(0), // Dummy value for legacy compatibility
callee: Some(mir_call.callee),
args: mir_call.args,
callee: Some(callee2),
args: args2,
effects: mir_call.effects,
};
self.emit_instruction(legacy_call)
let res = self.emit_instruction(legacy_call);
// Dev-only: verify block schedule invariants after emitting call
crate::mir::builder::emit_guard::verify_after_call(self);
res
}
/// Legacy call fallback - preserves existing behavior
@ -236,6 +386,8 @@ impl super::MirBuilder {
},
CallTarget::Extern(name) => {
// Use existing ExternCall
let mut args = args;
crate::mir::builder::ssa::local::finalize_args(self, &mut args);
let parts: Vec<&str> = name.splitn(2, '.').collect();
let (iface, method) = if parts.len() == 2 {
(parts[0].to_string(), parts[1].to_string())
@ -252,14 +404,12 @@ impl super::MirBuilder {
})
},
CallTarget::Global(name) => {
// Create a string constant for the function name
let name_const = self.value_gen.next();
self.emit_instruction(MirInstruction::Const {
dst: name_const,
value: super::ConstValue::String(name.clone()),
})?;
// Create a string constant for the function name via NameConstBox
let name_const = crate::mir::builder::name_const::make_name_const_result(self, &name)?;
// Allocate a destination if not provided so we can annotate it
let actual_dst = if let Some(d) = dst { d } else { self.value_gen.next() };
let mut args = args;
crate::mir::builder::ssa::local::finalize_args(self, &mut args);
self.emit_instruction(MirInstruction::Call {
dst: Some(actual_dst),
func: name_const,
@ -272,6 +422,8 @@ impl super::MirBuilder {
Ok(())
},
CallTarget::Value(func_val) => {
let mut args = args;
crate::mir::builder::ssa::local::finalize_args(self, &mut args);
self.emit_instruction(MirInstruction::Call {
dst,
func: func_val,
@ -404,8 +556,7 @@ impl super::MirBuilder {
if returns {
Ok(result_id)
} else {
let void_id = self.value_gen.next();
self.emit_instruction(MirInstruction::Const { dst: void_id, value: super::ConstValue::Void })?;
let void_id = crate::mir::builder::emission::constant::emit_void(self);
Ok(void_id)
}
};
@ -434,8 +585,10 @@ impl super::MirBuilder {
}
let result_id = self.value_gen.next();
let fun_name = format!("{}.{}{}", cls_name, method, format!("/{}", arg_values.len()));
let fun_val = self.value_gen.next();
if let Err(e) = self.emit_instruction(MirInstruction::Const { dst: fun_val, value: super::ConstValue::String(fun_name.clone()) }) { return Some(Err(e)); }
let fun_val = match crate::mir::builder::name_const::make_name_const_result(self, &fun_name) {
Ok(v) => v,
Err(e) => return Some(Err(e)),
};
if let Err(e) = self.emit_instruction(MirInstruction::Call {
dst: Some(result_id),
func: fun_val,
@ -571,11 +724,7 @@ impl super::MirBuilder {
};
// Legacy compatibility: Create dummy func value for old systems
let fun_val = self.value_gen.next();
self.emit_instruction(MirInstruction::Const {
dst: fun_val,
value: super::ConstValue::String(name.clone()),
})?;
let fun_val = crate::mir::builder::name_const::make_name_const_result(self, &name)?;
// Emit new-style Call with type-safe callee
self.emit_instruction(MirInstruction::Call {
@ -658,9 +807,11 @@ impl super::MirBuilder {
call_args.push(me_id);
call_args.extend(arg_values.into_iter());
let dst = self.value_gen.next();
// Emit Const for function name separately to avoid nested mutable borrows
let c = self.value_gen.next();
self.emit_instruction(MirInstruction::Const { dst: c, value: super::ConstValue::String(fname.clone()) })?;
// Emit function name via NameConstBox
let c = match crate::mir::builder::name_const::make_name_const_result(self, &fname) {
Ok(v) => v,
Err(e) => return Err(e),
};
self.emit_instruction(MirInstruction::Call {
dst: Some(dst),
func: c,
@ -709,11 +860,7 @@ impl super::MirBuilder {
for arg in arguments {
arg_values.push(self.build_expression(arg)?);
}
let parent_value = self.value_gen.next();
self.emit_instruction(MirInstruction::Const {
dst: parent_value,
value: super::ConstValue::String(parent),
})?;
let parent_value = crate::mir::builder::emission::constant::emit_string(self, parent);
let result_id = self.value_gen.next();
self.emit_box_or_plugin_call(
Some(result_id),
@ -765,11 +912,7 @@ impl super::MirBuilder {
let program_ast = function_lowering::wrap_in_program(body);
let _last = self.build_expression(program_ast)?;
if !returns_value && !self.is_current_block_terminated() {
let void_val = self.value_gen.next();
self.emit_instruction(MirInstruction::Const {
dst: void_val,
value: super::ConstValue::Void,
})?;
let void_val = crate::mir::builder::emission::constant::emit_void(self);
self.emit_instruction(MirInstruction::Return {
value: Some(void_val),
})?;
@ -855,11 +998,7 @@ impl super::MirBuilder {
if let Some(ref mut f) = self.current_function {
if let Some(block) = f.get_block(self.current_block.unwrap()) {
if !block.is_terminated() {
let void_val = self.value_gen.next();
self.emit_instruction(MirInstruction::Const {
dst: void_val,
value: super::ConstValue::Void,
})?;
let void_val = crate::mir::builder::emission::constant::emit_void(self);
self.emit_instruction(MirInstruction::Return {
value: Some(void_val),
})?;

View File

@ -91,7 +91,7 @@ impl super::MirBuilder {
}
// Enter try block
self.emit_instruction(MirInstruction::Jump { target: try_block })?;
crate::mir::builder::emission::branch::emit_jump(self, try_block)?;
self.start_new_block(try_block)?;
let try_ast = ASTNode::Program {
statements: try_body,
@ -100,7 +100,7 @@ impl super::MirBuilder {
let _try_result = self.build_expression(try_ast)?;
if !self.is_current_block_terminated() {
let next_target = finally_block.unwrap_or(exit_block);
self.emit_instruction(MirInstruction::Jump { target: next_target })?;
crate::mir::builder::emission::branch::emit_jump(self, next_target)?;
}
// Catch block
@ -117,7 +117,7 @@ impl super::MirBuilder {
}
if !self.is_current_block_terminated() {
let next_target = finally_block.unwrap_or(exit_block);
self.emit_instruction(MirInstruction::Jump { target: next_target })?;
crate::mir::builder::emission::branch::emit_jump(self, next_target)?;
}
// Finally
@ -136,7 +136,7 @@ impl super::MirBuilder {
self.build_expression(finally_ast)?;
cleanup_terminated = self.is_current_block_terminated();
if !cleanup_terminated {
self.emit_instruction(MirInstruction::Jump { target: exit_block })?;
crate::mir::builder::emission::branch::emit_jump(self, exit_block)?;
}
self.in_cleanup_block = false;
}
@ -145,13 +145,9 @@ impl super::MirBuilder {
self.start_new_block(exit_block)?;
let result = if self.return_deferred_emitted && !cleanup_terminated {
self.emit_instruction(MirInstruction::Return { value: Some(ret_slot) })?;
let r = self.value_gen.next();
self.emit_instruction(MirInstruction::Const { dst: r, value: ConstValue::Void })?;
r
crate::mir::builder::emission::constant::emit_void(self)
} else {
let r = self.value_gen.next();
self.emit_instruction(MirInstruction::Const { dst: r, value: ConstValue::Void })?;
r
crate::mir::builder::emission::constant::emit_void(self)
};
// Restore context

View File

@ -48,10 +48,10 @@ impl super::MirBuilder {
args: vec![],
})?;
} else {
self.emit_instruction(MirInstruction::Const {
dst: pid,
value: ConstValue::Void,
})?;
let v = crate::mir::builder::emission::constant::emit_void(self);
// ensure pid holds the emitted const id
self.emit_instruction(MirInstruction::Copy { dst: pid, src: v })?;
crate::mir::builder::metadata::propagate::propagate(self, v, pid);
}
self.variable_map.insert(p.clone(), pid);
}
@ -78,19 +78,11 @@ impl super::MirBuilder {
weak_fields: Vec<String>,
) -> Result<(), String> {
// Create a type registration constant (marker)
let type_id = self.value_gen.next();
self.emit_instruction(MirInstruction::Const {
dst: type_id,
value: ConstValue::String(format!("__box_type_{}", name)),
})?;
let type_id = crate::mir::builder::emission::constant::emit_string(self, format!("__box_type_{}", name));
// Emit field metadata markers
for field in fields {
let field_id = self.value_gen.next();
self.emit_instruction(MirInstruction::Const {
dst: field_id,
value: ConstValue::String(format!("__field_{}_{}", name, field)),
})?;
let _field_id = crate::mir::builder::emission::constant::emit_string(self, format!("__field_{}_{}", name, field));
}
// Record weak fields for this box
@ -120,11 +112,7 @@ impl super::MirBuilder {
// Emit markers for declared methods (kept as metadata hints)
for (method_name, method_ast) in methods {
if let ASTNode::FunctionDeclaration { .. } = method_ast {
let method_id = self.value_gen.next();
self.emit_instruction(MirInstruction::Const {
dst: method_id,
value: ConstValue::String(format!("__method_{}_{}", name, method_name)),
})?;
let _method_id = crate::mir::builder::emission::constant::emit_string(self, format!("__method_{}_{}", name, method_name));
// Track unified member getters: __get_<prop> | __get_once_<prop> | __get_birth_<prop>
let kind_and_prop: Option<(super::PropertyKind, String)> = if let Some(rest) = method_name.strip_prefix("__get_once_") {
Some((super::PropertyKind::Once, rest.to_string()))

View File

@ -0,0 +1,15 @@
//! BranchEmissionBox — 分岐/ジャンプ命令発行の薄いヘルパ(仕様不変)
use crate::mir::{BasicBlockId, MirInstruction};
use crate::mir::builder::MirBuilder;
#[inline]
pub fn emit_conditional(b: &mut MirBuilder, cond: crate::mir::ValueId, then_bb: BasicBlockId, else_bb: BasicBlockId) -> Result<(), String> {
b.emit_instruction(MirInstruction::Branch { condition: cond, then_bb, else_bb })
}
#[inline]
pub fn emit_jump(b: &mut MirBuilder, target: BasicBlockId) -> Result<(), String> {
b.emit_instruction(MirInstruction::Jump { target })
}

View File

@ -0,0 +1,24 @@
//! CompareEmissionBox — 比較命令発行の薄いヘルパ(仕様不変)
use crate::mir::{CompareOp, MirInstruction, MirType, ValueId};
use crate::mir::builder::MirBuilder;
#[inline]
pub fn emit_to(b: &mut MirBuilder, dst: ValueId, op: CompareOp, lhs: ValueId, rhs: ValueId) -> Result<(), String> {
b.emit_instruction(MirInstruction::Compare { dst, op, lhs, rhs })?;
// 比較結果は Bool 型(既存実装と同じ振る舞い)
b.value_types.insert(dst, MirType::Bool);
Ok(())
}
// Convenience wrappers (明示関数名が読みやすい箇所用)
#[inline]
pub fn emit_eq_to(b: &mut MirBuilder, dst: ValueId, lhs: ValueId, rhs: ValueId) -> Result<(), String> {
emit_to(b, dst, CompareOp::Eq, lhs, rhs)
}
#[inline]
pub fn emit_ne_to(b: &mut MirBuilder, dst: ValueId, lhs: ValueId, rhs: ValueId) -> Result<(), String> {
emit_to(b, dst, CompareOp::Ne, lhs, rhs)
}

View File

@ -0,0 +1,46 @@
//! ConstantEmissionBox — Const 命令の発行を集約(仕様不変)
use crate::mir::{ConstValue, MirInstruction, ValueId};
use crate::mir::builder::MirBuilder;
#[inline]
pub fn emit_integer(b: &mut MirBuilder, val: i64) -> ValueId {
let dst = b.value_gen.next();
let _ = b.emit_instruction(MirInstruction::Const { dst, value: ConstValue::Integer(val) });
dst
}
#[inline]
pub fn emit_bool(b: &mut MirBuilder, val: bool) -> ValueId {
let dst = b.value_gen.next();
let _ = b.emit_instruction(MirInstruction::Const { dst, value: ConstValue::Bool(val) });
dst
}
#[inline]
pub fn emit_float(b: &mut MirBuilder, val: f64) -> ValueId {
let dst = b.value_gen.next();
let _ = b.emit_instruction(MirInstruction::Const { dst, value: ConstValue::Float(val) });
dst
}
#[inline]
pub fn emit_string<S: Into<String>>(b: &mut MirBuilder, s: S) -> ValueId {
let dst = b.value_gen.next();
let _ = b.emit_instruction(MirInstruction::Const { dst, value: ConstValue::String(s.into()) });
dst
}
#[inline]
pub fn emit_null(b: &mut MirBuilder) -> ValueId {
let dst = b.value_gen.next();
let _ = b.emit_instruction(MirInstruction::Const { dst, value: ConstValue::Null });
dst
}
#[inline]
pub fn emit_void(b: &mut MirBuilder) -> ValueId {
let dst = b.value_gen.next();
let _ = b.emit_instruction(MirInstruction::Const { dst, value: ConstValue::Void });
dst
}

View File

@ -0,0 +1,8 @@
//! emission: MIR命令の薄い発行箱仕様不変
//! - constant.rs: Const発行を一箇所に集約
//! - compare.rs: Compare命令の薄い発行
//! - branch.rs: Branch/Jump 発行の薄い関数
pub mod constant;
pub mod compare;
pub mod branch;

View File

@ -0,0 +1,14 @@
use crate::mir::builder::MirBuilder;
use crate::mir::definitions::call_unified::Callee;
use crate::mir::ValueId;
/// Finalize call operands (receiver/args) using LocalSSA; thin wrapper to centralize usage.
pub fn finalize_call_operands(builder: &mut MirBuilder, callee: &mut Callee, args: &mut Vec<ValueId>) {
crate::mir::builder::ssa::local::finalize_callee_and_args(builder, callee, args);
}
/// Verify block schedule invariants after emitting a call (dev-only WARNs inside).
pub fn verify_after_call(builder: &mut MirBuilder) {
crate::mir::builder::schedule::block::BlockScheduleBox::verify_order(builder);
}

View File

@ -146,11 +146,7 @@ impl super::MirBuilder {
}
}
// Return void for declaration context
let void_val = self.value_gen.next();
self.emit_instruction(MirInstruction::Const {
dst: void_val,
value: ConstValue::Void,
})?;
let void_val = crate::mir::builder::emission::constant::emit_void(self);
Ok(void_val)
} else {
// Instance box: register type and lower instance methods/ctors as functions
@ -196,11 +192,7 @@ impl super::MirBuilder {
}
}
}
let void_val = self.value_gen.next();
self.emit_instruction(MirInstruction::Const {
dst: void_val,
value: ConstValue::Void,
})?;
let void_val = crate::mir::builder::emission::constant::emit_void(self);
Ok(void_val)
}
}
@ -243,11 +235,7 @@ impl super::MirBuilder {
})?;
for (k, expr) in entries {
// const string key
let k_id = self.value_gen.next();
self.emit_instruction(MirInstruction::Const {
dst: k_id,
value: ConstValue::String(k),
})?;
let k_id = crate::mir::builder::emission::constant::emit_string(self, k);
let v_id = self.build_expression_impl(expr)?;
self.emit_instruction(MirInstruction::BoxCall {
dst: None,

View File

@ -33,22 +33,18 @@ impl super::MirBuilder {
}
};
if need_jump {
self.emit_instruction(super::MirInstruction::Jump {
target: dispatch_block,
})?;
crate::mir::builder::emission::branch::emit_jump(self, dispatch_block)?;
}
self.start_new_block(dispatch_block)?;
// If there are no arms, fall through to else directly
if arms.is_empty() {
let else_block = self.block_gen.next();
self.emit_instruction(super::MirInstruction::Jump { target: else_block })?;
crate::mir::builder::emission::branch::emit_jump(self, else_block)?;
self.start_new_block(else_block)?;
let else_val = self.build_expression_impl(else_expr)?;
phi_inputs.push((else_block, else_val));
self.emit_instruction(super::MirInstruction::Jump {
target: merge_block,
})?;
crate::mir::builder::emission::branch::emit_jump(self, merge_block)?;
self.start_new_block(merge_block)?;
// フェーズM: 常にPHI命令を使用no_phi_mode撤廃
self.emit_instruction(super::MirInstruction::Phi {
@ -75,39 +71,23 @@ impl super::MirBuilder {
// In current dispatch block, compare and branch
self.start_new_block(cur_dispatch)?;
let lit_id = self.value_gen.next();
let const_value = match label {
LiteralValue::String(s) => super::ConstValue::String(s),
LiteralValue::Integer(i) => super::ConstValue::Integer(i),
LiteralValue::Bool(b) => super::ConstValue::Bool(b),
LiteralValue::Float(f) => super::ConstValue::Float(f),
LiteralValue::Null => super::ConstValue::Null,
LiteralValue::Void => super::ConstValue::Void,
let lit_id = match label {
LiteralValue::String(s) => crate::mir::builder::emission::constant::emit_string(self, s),
LiteralValue::Integer(i) => crate::mir::builder::emission::constant::emit_integer(self, i),
LiteralValue::Bool(b) => crate::mir::builder::emission::constant::emit_bool(self, b),
LiteralValue::Float(f) => crate::mir::builder::emission::constant::emit_float(self, f),
LiteralValue::Null => crate::mir::builder::emission::constant::emit_null(self),
LiteralValue::Void => crate::mir::builder::emission::constant::emit_void(self),
};
self.emit_instruction(super::MirInstruction::Const {
dst: lit_id,
value: const_value,
})?;
let cond_id = self.value_gen.next();
self.emit_instruction(super::MirInstruction::Compare {
dst: cond_id,
op: super::CompareOp::Eq,
lhs: scr_val,
rhs: lit_id,
})?;
self.emit_instruction(super::MirInstruction::Branch {
condition: cond_id,
then_bb: then_block,
else_bb: else_target,
})?;
crate::mir::builder::emission::compare::emit_to(self, cond_id, super::CompareOp::Eq, scr_val, lit_id)?;
crate::mir::builder::emission::branch::emit_conditional(self, cond_id, then_block, else_target)?;
// then arm
self.start_new_block(then_block)?;
let then_val = self.build_expression_impl(arm_expr)?;
phi_inputs.push((then_block, then_val));
self.emit_instruction(super::MirInstruction::Jump {
target: merge_block,
})?;
crate::mir::builder::emission::branch::emit_jump(self, merge_block)?;
// Move to next dispatch or else block
cur_dispatch = else_target;
@ -117,9 +97,7 @@ impl super::MirBuilder {
self.start_new_block(else_block)?;
let else_val = self.build_expression_impl(else_expr)?;
phi_inputs.push((else_block, else_val));
self.emit_instruction(super::MirInstruction::Jump {
target: merge_block,
})?;
crate::mir::builder::emission::branch::emit_jump(self, merge_block)?;
// Merge and yield result
self.start_new_block(merge_block)?;

View File

@ -8,10 +8,11 @@ impl super::MirBuilder {
expression: ASTNode,
) -> Result<ValueId, String> {
let res_val = self.build_expression_impl(expression)?;
let res_local = self.local_ssa_ensure(res_val, 0);
let ok_id = self.value_gen.next();
self.emit_instruction(super::MirInstruction::BoxCall {
dst: Some(ok_id),
box_val: res_val,
box_val: res_local,
method: "isOk".to_string(),
args: vec![],
method_id: None,
@ -19,20 +20,17 @@ impl super::MirBuilder {
})?;
let then_block = self.block_gen.next();
let else_block = self.block_gen.next();
self.emit_instruction(super::MirInstruction::Branch {
condition: ok_id,
then_bb: then_block,
else_bb: else_block,
})?;
let ok_local = self.local_ssa_ensure(ok_id, 4);
crate::mir::builder::emission::branch::emit_conditional(self, ok_local, then_block, else_block)?;
self.start_new_block(then_block)?;
self.emit_instruction(super::MirInstruction::Return {
value: Some(res_val),
value: Some(res_local),
})?;
self.start_new_block(else_block)?;
let val_id = self.value_gen.next();
self.emit_instruction(super::MirInstruction::BoxCall {
dst: Some(val_id),
box_val: res_val,
box_val: res_local,
method: "getValue".to_string(),
args: vec![],
method_id: None,

View File

@ -12,6 +12,7 @@ impl super::MirBuilder {
) -> Result<ValueId, String> {
let object_clone = object.clone();
let object_value = self.build_expression(object.clone())?;
let object_value = self.local_field_base(object_value);
// Unified members: if object class is known and has a synthetic getter for `field`,
// rewrite to method call `__get_<field>()`.
@ -28,20 +29,20 @@ impl super::MirBuilder {
}
}
// Emit: field name const
let field_name_id = self.value_gen.next();
self.emit_instruction(MirInstruction::Const {
dst: field_name_id,
value: ConstValue::String(field.clone()),
})?;
// Emit: field name const (boxed)
let field_name_id = crate::mir::builder::emission::constant::emit_string(self, field.clone());
// Finalize operands (base + args) in current block
let mut base = object_value;
let mut args_vec = vec![field_name_id];
crate::mir::builder::ssa::local::finalize_field_base_and_args(self, &mut base, &mut args_vec);
// BoxCall: getField(name)
let field_val = self.value_gen.next();
self.emit_instruction(MirInstruction::BoxCall {
dst: Some(field_val),
box_val: object_value,
box_val: base,
method: "getField".to_string(),
method_id: slot_registry::resolve_slot_by_type_name("InstanceBox", "getField"),
args: vec![field_name_id],
args: args_vec,
effects: EffectMask::READ,
})?;
@ -111,7 +112,10 @@ impl super::MirBuilder {
value: ASTNode,
) -> Result<ValueId, String> {
let object_value = self.build_expression(object)?;
let object_value = self.local_field_base(object_value);
let mut value_result = self.build_expression(value)?;
// LocalSSA: argument in-block (optional safety)
value_result = self.local_arg(value_result);
// If base is known and field is weak, create WeakRef before store
if let Some(class_name) = self.value_origin_newbox.get(&object_value).cloned() {
@ -123,18 +127,18 @@ impl super::MirBuilder {
}
// Emit: field name const
let field_name_id = self.value_gen.next();
self.emit_instruction(MirInstruction::Const {
dst: field_name_id,
value: ConstValue::String(field.clone()),
})?;
let field_name_id = crate::mir::builder::emission::constant::emit_string(self, field.clone());
// Finalize operands (base + args) in current block
let mut base = object_value;
let mut args_vec = vec![field_name_id, value_result];
crate::mir::builder::ssa::local::finalize_field_base_and_args(self, &mut base, &mut args_vec);
// Set the field via BoxCall: setField(name, value)
self.emit_instruction(MirInstruction::BoxCall {
dst: None,
box_val: object_value,
box_val: base,
method: "setField".to_string(),
method_id: slot_registry::resolve_slot_by_type_name("InstanceBox", "setField"),
args: vec![field_name_id, value_result],
args: args_vec,
effects: EffectMask::WRITE,
})?;

View File

@ -38,6 +38,7 @@ impl MirBuilder {
}
let condition_val = self.build_expression(condition)?;
let condition_val = self.local_cond(condition_val);
// Create blocks
let then_block = self.block_gen.next();
@ -46,11 +47,9 @@ impl MirBuilder {
// Branch
let pre_branch_bb = self.current_block()?;
self.emit_instruction(MirInstruction::Branch {
condition: condition_val,
then_bb: then_block,
else_bb: else_block,
})?;
let mut condition_val = condition_val;
crate::mir::builder::ssa::local::finalize_branch_cond(self, &mut condition_val);
crate::mir::builder::emission::branch::emit_conditional(self, condition_val, then_block, else_block)?;
// Snapshot variables before entering branches
let pre_if_var_map = self.variable_map.clone();
@ -84,7 +83,7 @@ impl MirBuilder {
if !self.is_current_block_terminated() {
// Scope leave for then-branch
self.hint_scope_leave(0);
self.emit_instruction(MirInstruction::Jump { target: merge_block })?;
crate::mir::builder::emission::branch::emit_jump(self, merge_block)?;
}
// Pop then-branch debug region
self.debug_pop_region();
@ -113,8 +112,7 @@ impl MirBuilder {
let val = self.build_expression(else_ast.clone())?;
(val, Some(else_ast), Some(self.variable_map.clone()))
} else {
let void_val = self.value_gen.next();
self.emit_instruction(MirInstruction::Const { dst: void_val, value: ConstValue::Void })?;
let void_val = crate::mir::builder::emission::constant::emit_void(self);
(void_val, None, None)
};
let else_exit_block = self.current_block()?;

View File

@ -279,12 +279,9 @@ impl super::MirBuilder {
// parameter slot (unused in body)
let _param = f.next_value_id();
f.params.push(_param);
// body: const 1; return it
let one = f.next_value_id();
if let Some(bb) = f.get_block_mut(entry) {
bb.add_instruction(MirInstruction::Const { dst: one, value: ConstValue::Integer(1) });
bb.add_instruction(MirInstruction::Return { value: Some(one) });
}
// 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);
}

View File

@ -0,0 +1,5 @@
//! metadata: MIRメタデータ用の薄い箱仕様不変
//! - value_types と value_origin_newbox の伝播を一箇所に集約する。
pub mod propagate;

View File

@ -0,0 +1,24 @@
//! MetadataPropagationBox — MIR のメタデータ(型/起源)の伝播
//! 仕様不変・小粒。各所のコピペを置換するための薄い関数郡。
use crate::mir::{MirType, ValueId};
use crate::mir::builder::MirBuilder;
/// src から dst へ builder 内メタデータvalue_types / value_origin_newboxを伝播する。
#[inline]
pub fn propagate(builder: &mut MirBuilder, src: ValueId, dst: ValueId) {
if let Some(t) = builder.value_types.get(&src).cloned() {
builder.value_types.insert(dst, t);
}
if let Some(cls) = builder.value_origin_newbox.get(&src).cloned() {
builder.value_origin_newbox.insert(dst, cls);
}
}
/// dst に型注釈を明示的に設定し、必要ならば起源情報を消去/維持する。
/// 現状は型のみ設定(挙動不変)。
#[inline]
pub fn propagate_with_override(builder: &mut MirBuilder, dst: ValueId, ty: MirType) {
builder.value_types.insert(dst, ty);
}

View File

@ -91,49 +91,20 @@ impl MirBuilder {
method: String,
arguments: &[ASTNode],
) -> Result<ValueId, String> {
// 安全策: レシーバをピン留めしてブロック境界での未定義参照を防ぐ
let object_value = self
.pin_to_slot(object_value, "@recv")
.unwrap_or(object_value);
// Build argument values
let mut arg_values = Vec::new();
for arg in arguments {
arg_values.push(self.build_expression(arg.clone())?);
}
// If receiver is a user-defined box, lower to function call: "Box.method/(1+arity)"
let mut class_name_opt: Option<String> = None;
// Heuristic guard: if this receiver equals the current function's 'me',
// prefer the enclosing box name parsed from the function signature.
if class_name_opt.is_none() {
if let Some(&me_vid) = self.variable_map.get("me") {
if me_vid == object_value {
if let Some(ref fun) = self.current_function {
if let Some(dot) = fun.signature.name.find('.') {
class_name_opt = Some(fun.signature.name[..dot].to_string());
}
}
}
}
}
if class_name_opt.is_none() {
if let Some(cn) = self.value_origin_newbox.get(&object_value) { class_name_opt = Some(cn.clone()); }
}
if class_name_opt.is_none() {
if let Some(t) = self.value_types.get(&object_value) {
if let MirType::Box(bn) = t { class_name_opt = Some(bn.clone()); }
}
}
// レガシー経路BoxCall/Pluginへ送る安定優先・挙動不変
let result_id = self.value_gen.next();
self.emit_box_or_plugin_call(
Some(result_id),
object_value,
method.clone(),
None,
// Receiver class hintは emit_unified_call 側で起源/型から判断する(重複回避)
// 統一経路: emit_unified_call に委譲RouterPolicy と rewrite::* で安定化)
let dst = self.value_gen.next();
self.emit_unified_call(
Some(dst),
CallTarget::Method { box_type: None, method, receiver: object_value },
arg_values,
super::EffectMask::READ.add(super::Effect::ReadHeap),
)?;
Ok(result_id)
Ok(dst)
}
}

View File

@ -0,0 +1,10 @@
use crate::mir::ValueId;
use crate::mir::builder::MirBuilder;
/// Emit a string Const (function name const) and return its ValueId.
/// Behavior-preserving wrapper around Const emission with String value.
pub fn make_name_const_result(b: &mut MirBuilder, name: &str) -> Result<ValueId, String> {
// Delegate to ConstantEmissionBox to keep Const emission centralized
let dst = crate::mir::builder::emission::constant::emit_string(b, name.to_string());
Ok(dst)
}

View File

@ -154,8 +154,7 @@ impl super::MirBuilder {
CompareOp::Gt => "Gt",
CompareOp::Ge => "Ge",
};
let op_const = self.value_gen.next();
self.emit_instruction(MirInstruction::Const { dst: op_const, value: super::ConstValue::String(opname.into()) })?;
let op_const = crate::mir::builder::emission::constant::emit_string(self, opname);
// そのまま値を渡す(型変換/slot化は演算子内orVMで行う
let name = "CompareOperator.apply/3".to_string();
self.emit_legacy_call(Some(dst), super::builder_calls::CallTarget::Global(name), vec![op_const, lhs, rhs])?;
@ -191,10 +190,11 @@ impl super::MirBuilder {
} else {
(lhs, rhs)
};
let lhs2 = self.ensure_slotified_for_use(lhs2_raw, "@cmp_lhs")?;
let rhs2 = self.ensure_slotified_for_use(rhs2_raw, "@cmp_rhs")?;
self.emit_instruction(MirInstruction::Compare { dst, op, lhs: lhs2, rhs: rhs2 })?;
self.value_types.insert(dst, MirType::Bool);
// Finalize compare operands in current block via LocalSSA
let mut lhs2 = lhs2_raw;
let mut rhs2 = rhs2_raw;
crate::mir::builder::ssa::local::finalize_compare(self, &mut lhs2, &mut rhs2);
crate::mir::builder::emission::compare::emit_to(self, dst, op, lhs2, rhs2)?;
}
}
}
@ -222,11 +222,9 @@ impl super::MirBuilder {
let merge_block = self.block_gen.next();
// Branch on LHS truthiness (runtime to_bool semantics in interpreter/LLVM)
self.emit_instruction(MirInstruction::Branch {
condition: lhs_val,
then_bb: then_block,
else_bb: else_block,
})?;
let mut lhs_cond = self.local_cond(lhs_val);
crate::mir::builder::ssa::local::finalize_branch_cond(self, &mut lhs_cond);
crate::mir::builder::emission::branch::emit_conditional(self, lhs_cond, then_block, else_block)?;
// Record predecessor block for branch (for single-pred PHI materialization)
let pre_branch_bb = self.current_block()?;
@ -254,22 +252,18 @@ impl super::MirBuilder {
let rhs_false = self.block_gen.next();
let rhs_join = self.block_gen.next();
let rhs_val = self.build_expression(right.clone())?;
self.emit_instruction(MirInstruction::Branch {
condition: rhs_val,
then_bb: rhs_true,
else_bb: rhs_false,
})?;
let mut rhs_cond = self.local_cond(rhs_val);
crate::mir::builder::ssa::local::finalize_branch_cond(self, &mut rhs_cond);
crate::mir::builder::emission::branch::emit_conditional(self, rhs_cond, rhs_true, rhs_false)?;
// true path
self.start_new_block(rhs_true)?;
let t_id = self.value_gen.next();
self.emit_instruction(MirInstruction::Const { dst: t_id, value: crate::mir::ConstValue::Bool(true) })?;
self.emit_instruction(MirInstruction::Jump { target: rhs_join })?;
let t_id = crate::mir::builder::emission::constant::emit_bool(self, true);
crate::mir::builder::emission::branch::emit_jump(self, rhs_join)?;
let rhs_true_exit = self.current_block()?;
// false path
self.start_new_block(rhs_false)?;
let f_id = self.value_gen.next();
self.emit_instruction(MirInstruction::Const { dst: f_id, value: crate::mir::ConstValue::Bool(false) })?;
self.emit_instruction(MirInstruction::Jump { target: rhs_join })?;
let f_id = crate::mir::builder::emission::constant::emit_bool(self, false);
crate::mir::builder::emission::branch::emit_jump(self, rhs_join)?;
let rhs_false_exit = self.current_block()?;
// join rhs result into a single bool
self.start_new_block(rhs_join)?;
@ -282,15 +276,14 @@ impl super::MirBuilder {
self.value_types.insert(rhs_bool, MirType::Bool);
rhs_bool
} else {
let t_id = self.value_gen.next();
self.emit_instruction(MirInstruction::Const { dst: t_id, value: crate::mir::ConstValue::Bool(true) })?;
let t_id = crate::mir::builder::emission::constant::emit_bool(self, true);
t_id
};
let then_exit_block = self.current_block()?;
let then_var_map_end = self.variable_map.clone();
if !self.is_current_block_terminated() {
self.hint_scope_leave(0);
self.emit_instruction(MirInstruction::Jump { target: merge_block })?;
crate::mir::builder::emission::branch::emit_jump(self, merge_block)?;
}
// ---- ELSE branch ----
@ -307,30 +300,25 @@ impl super::MirBuilder {
// AND: else → false
// OR: else → evaluate RHS and reduce to bool
let else_value_raw = if is_and {
let f_id = self.value_gen.next();
self.emit_instruction(MirInstruction::Const { dst: f_id, value: crate::mir::ConstValue::Bool(false) })?;
let f_id = crate::mir::builder::emission::constant::emit_bool(self, false);
f_id
} else {
let rhs_true = self.block_gen.next();
let rhs_false = self.block_gen.next();
let rhs_join = self.block_gen.next();
let rhs_val = self.build_expression(right)?;
self.emit_instruction(MirInstruction::Branch {
condition: rhs_val,
then_bb: rhs_true,
else_bb: rhs_false,
})?;
let mut rhs_cond = self.local_cond(rhs_val);
crate::mir::builder::ssa::local::finalize_branch_cond(self, &mut rhs_cond);
crate::mir::builder::emission::branch::emit_conditional(self, rhs_cond, rhs_true, rhs_false)?;
// true path
self.start_new_block(rhs_true)?;
let t_id = self.value_gen.next();
self.emit_instruction(MirInstruction::Const { dst: t_id, value: crate::mir::ConstValue::Bool(true) })?;
self.emit_instruction(MirInstruction::Jump { target: rhs_join })?;
let t_id = crate::mir::builder::emission::constant::emit_bool(self, true);
crate::mir::builder::emission::branch::emit_jump(self, rhs_join)?;
let rhs_true_exit = self.current_block()?;
// false path
self.start_new_block(rhs_false)?;
let f_id = self.value_gen.next();
self.emit_instruction(MirInstruction::Const { dst: f_id, value: crate::mir::ConstValue::Bool(false) })?;
self.emit_instruction(MirInstruction::Jump { target: rhs_join })?;
let f_id = crate::mir::builder::emission::constant::emit_bool(self, false);
crate::mir::builder::emission::branch::emit_jump(self, rhs_join)?;
let rhs_false_exit = self.current_block()?;
// join rhs result into a single bool
self.start_new_block(rhs_join)?;
@ -410,11 +398,7 @@ impl super::MirBuilder {
if crate::config::env::mir_core13_pure() {
match operator.as_str() {
"-" => {
let zero = self.value_gen.next();
self.emit_instruction(MirInstruction::Const {
dst: zero,
value: crate::mir::ConstValue::Integer(0),
})?;
let zero = crate::mir::builder::emission::constant::emit_integer(self, 0);
let dst = self.value_gen.next();
self.emit_instruction(MirInstruction::BinOp {
dst,
@ -425,26 +409,19 @@ impl super::MirBuilder {
return Ok(dst);
}
"!" | "not" => {
let f = self.value_gen.next();
self.emit_instruction(MirInstruction::Const {
dst: f,
value: crate::mir::ConstValue::Bool(false),
})?;
let f = crate::mir::builder::emission::constant::emit_bool(self, false);
let dst = self.value_gen.next();
self.emit_instruction(MirInstruction::Compare {
crate::mir::builder::emission::compare::emit_to(
self,
dst,
op: crate::mir::CompareOp::Eq,
lhs: operand_val,
rhs: f,
})?;
crate::mir::CompareOp::Eq,
operand_val,
f,
)?;
return Ok(dst);
}
"~" => {
let all1 = self.value_gen.next();
self.emit_instruction(MirInstruction::Const {
dst: all1,
value: crate::mir::ConstValue::Integer(-1),
})?;
let all1 = crate::mir::builder::emission::constant::emit_integer(self, -1);
let dst = self.value_gen.next();
self.emit_instruction(MirInstruction::BinOp {
dst,

View File

@ -2,14 +2,18 @@ use super::super::{ConstValue, Effect, EffectMask, MirBuilder, MirInstruction, V
/// Gate: whether instance→function rewrite is enabled.
fn rewrite_enabled() -> bool {
match std::env::var("NYASH_BUILDER_REWRITE_INSTANCE")
.ok()
.as_deref()
.map(|v| v.to_ascii_lowercase())
{
// New primary flag (P4): NYASH_REWRITE_KNOWN_DEFAULT (default ON; allow explicit OFF)
if let Ok(v) = std::env::var("NYASH_REWRITE_KNOWN_DEFAULT") {
let s = v.to_ascii_lowercase();
if s == "0" || s == "false" || s == "off" { return false; }
if s == "1" || s == "true" || s == "on" { return true; }
// fallthrough to legacy if malformed
}
// Legacy flag (kept for compatibility): NYASH_BUILDER_REWRITE_INSTANCE (default ON)
match std::env::var("NYASH_BUILDER_REWRITE_INSTANCE").ok().as_deref().map(|v| v.to_ascii_lowercase()) {
Some(ref s) if s == "0" || s == "false" || s == "off" => false,
Some(ref s) if s == "1" || s == "true" || s == "on" => true,
_ => true, // default ON (spec unchanged; dev can opt out)
_ => true, // default ON (spec unchanged; can opt out by setting ...=0)
}
}
@ -45,11 +49,14 @@ pub(crate) fn try_known_rewrite(
return None;
}
// Materialize function call: pass 'me' first, then args
let name_const = builder.value_gen.next();
if let Err(e) = builder.emit_instruction(MirInstruction::Const { dst: name_const, value: ConstValue::String(fname.clone()) }) { return Some(Err(e)); }
let name_const = match crate::mir::builder::name_const::make_name_const_result(builder, &fname) {
Ok(v) => v,
Err(e) => return Some(Err(e)),
};
let mut call_args = Vec::with_capacity(arity + 1);
call_args.push(object_value);
call_args.append(&mut arg_values);
crate::mir::builder::ssa::local::finalize_args(builder, &mut call_args);
let dst = builder.value_gen.next();
if let Err(e) = builder.emit_instruction(MirInstruction::Call {
dst: Some(dst), func: name_const, callee: None, args: call_args, effects: EffectMask::READ.add(Effect::ReadHeap),
@ -88,11 +95,14 @@ pub(crate) fn try_known_rewrite_to_dst(
let fname = crate::mir::builder::calls::function_lowering::generate_method_function_name(cls, method, arity);
let module_has = if let Some(ref module) = builder.current_module { module.functions.contains_key(&fname) } else { false };
if !((module_has || allow_userbox_rewrite) || (from_new_origin && allow_new_origin)) { return None; }
let name_const = builder.value_gen.next();
if let Err(e) = builder.emit_instruction(MirInstruction::Const { dst: name_const, value: ConstValue::String(fname.clone()) }) { return Some(Err(e)); }
let name_const = match crate::mir::builder::name_const::make_name_const_result(builder, &fname) {
Ok(v) => v,
Err(e) => return Some(Err(e)),
};
let mut call_args = Vec::with_capacity(arity + 1);
call_args.push(object_value);
call_args.append(&mut arg_values);
crate::mir::builder::ssa::local::finalize_args(builder, &mut call_args);
let actual_dst = want_dst.unwrap_or_else(|| builder.value_gen.next());
if let Err(e) = builder.emit_instruction(MirInstruction::Call { dst: Some(actual_dst), func: name_const, callee: None, args: call_args, effects: EffectMask::READ.add(Effect::ReadHeap) }) { return Some(Err(e)); }
builder.annotate_call_result_from_func_name(actual_dst, &fname);
@ -135,12 +145,15 @@ pub(crate) fn try_unique_suffix_rewrite(
} else {
return None;
}
let name_const = builder.value_gen.next();
if let Err(e) = builder.emit_instruction(MirInstruction::Const { dst: name_const, value: ConstValue::String(fname.clone()) }) { return Some(Err(e)); }
let name_const = match crate::mir::builder::name_const::make_name_const_result(builder, &fname) {
Ok(v) => v,
Err(e) => return Some(Err(e)),
};
let mut call_args = Vec::with_capacity(arg_values.len() + 1);
call_args.push(object_value); // 'me'
let arity_us = arg_values.len();
call_args.append(&mut arg_values);
crate::mir::builder::ssa::local::finalize_args(builder, &mut call_args);
let dst = builder.value_gen.next();
if let Err(e) = builder.emit_instruction(MirInstruction::Call {
dst: Some(dst), func: name_const, callee: None, args: call_args, effects: EffectMask::READ.add(Effect::ReadHeap),
@ -172,12 +185,15 @@ pub(crate) fn try_unique_suffix_rewrite_to_dst(
if cands.len() != 1 { return None; }
let fname = cands.remove(0);
if let Some((bx, _)) = fname.split_once('.') { if !builder.user_defined_boxes.contains(bx) { return None; } } else { return None; }
let name_const = builder.value_gen.next();
if let Err(e) = builder.emit_instruction(MirInstruction::Const { dst: name_const, value: ConstValue::String(fname.clone()) }) { return Some(Err(e)); }
let name_const = match crate::mir::builder::name_const::make_name_const_result(builder, &fname) {
Ok(v) => v,
Err(e) => return Some(Err(e)),
};
let mut call_args = Vec::with_capacity(arg_values.len() + 1);
call_args.push(object_value);
let arity_us = arg_values.len();
call_args.append(&mut arg_values);
crate::mir::builder::ssa::local::finalize_args(builder, &mut call_args);
let actual_dst = want_dst.unwrap_or_else(|| builder.value_gen.next());
if let Err(e) = builder.emit_instruction(MirInstruction::Call { dst: Some(actual_dst), func: name_const, callee: None, args: call_args, effects: EffectMask::READ.add(Effect::ReadHeap) }) { return Some(Err(e)); }
builder.annotate_call_result_from_func_name(actual_dst, &fname);

View File

@ -31,10 +31,13 @@ pub(crate) fn try_early_str_like(
"certainty": "Known",
});
super::super::observe::resolve::emit_choose(builder, meta);
let name_const = builder.value_gen.next();
if let Err(e) = builder.emit_instruction(MirInstruction::Const { dst: name_const, value: ConstValue::String(chosen.clone()) }) { return Some(Err(e)); }
let name_const = match crate::mir::builder::name_const::make_name_const_result(builder, &chosen) {
Ok(v) => v,
Err(e) => return Some(Err(e)),
};
let mut call_args = Vec::with_capacity(1);
call_args.push(object_value);
crate::mir::builder::ssa::local::finalize_args(builder, &mut call_args);
let dst = builder.value_gen.next();
if let Err(e) = builder.emit_instruction(MirInstruction::Call {
dst: Some(dst), func: name_const, callee: None, args: call_args, effects: EffectMask::READ.add(Effect::ReadHeap),
@ -59,10 +62,13 @@ pub(crate) fn try_early_str_like(
"certainty": "Heuristic",
});
super::super::observe::resolve::emit_choose(builder, meta);
let name_const = builder.value_gen.next();
if let Err(e) = builder.emit_instruction(MirInstruction::Const { dst: name_const, value: ConstValue::String(fname.clone()) }) { return Some(Err(e)); }
let name_const = match crate::mir::builder::name_const::make_name_const_result(builder, &fname) {
Ok(v) => v,
Err(e) => return Some(Err(e)),
};
let mut call_args = Vec::with_capacity(1);
call_args.push(object_value);
crate::mir::builder::ssa::local::finalize_args(builder, &mut call_args);
let dst = builder.value_gen.next();
if let Err(e) = builder.emit_instruction(MirInstruction::Call { dst: Some(dst), func: name_const, callee: None, args: call_args, effects: EffectMask::READ.add(Effect::ReadHeap), }) { return Some(Err(e)); }
builder.annotate_call_result_from_func_name(dst, &fname);
@ -79,10 +85,13 @@ pub(crate) fn try_early_str_like(
"certainty": "Heuristic",
});
super::super::observe::resolve::emit_choose(builder, meta);
let name_const = builder.value_gen.next();
if let Err(e) = builder.emit_instruction(MirInstruction::Const { dst: name_const, value: ConstValue::String(fname.clone()) }) { return Some(Err(e)); }
let name_const = match crate::mir::builder::name_const::make_name_const_result(builder, &fname) {
Ok(v) => v,
Err(e) => return Some(Err(e)),
};
let mut call_args = Vec::with_capacity(1);
call_args.push(object_value);
crate::mir::builder::ssa::local::finalize_args(builder, &mut call_args);
let dst = builder.value_gen.next();
if let Err(e) = builder.emit_instruction(MirInstruction::Call { dst: Some(dst), func: name_const, callee: None, args: call_args, effects: EffectMask::READ.add(Effect::ReadHeap), }) { return Some(Err(e)); }
builder.annotate_call_result_from_func_name(dst, &fname);
@ -141,10 +150,13 @@ pub(crate) fn try_early_str_like_to_dst(
"certainty": "Known",
});
super::super::observe::resolve::emit_choose(builder, meta);
let name_const = builder.value_gen.next();
if let Err(e) = builder.emit_instruction(MirInstruction::Const { dst: name_const, value: ConstValue::String(chosen.clone()) }) { return Some(Err(e)); }
let name_const = match crate::mir::builder::name_const::make_name_const_result(builder, &chosen) {
Ok(v) => v,
Err(e) => return Some(Err(e)),
};
let mut call_args = Vec::with_capacity(1);
call_args.push(object_value);
crate::mir::builder::ssa::local::finalize_args(builder, &mut call_args);
let actual_dst = want_dst.unwrap_or_else(|| builder.value_gen.next());
if let Err(e) = builder.emit_instruction(MirInstruction::Call { dst: Some(actual_dst), func: name_const, callee: None, args: call_args, effects: EffectMask::READ.add(Effect::ReadHeap), }) { return Some(Err(e)); }
builder.annotate_call_result_from_func_name(actual_dst, &chosen);
@ -165,10 +177,13 @@ pub(crate) fn try_early_str_like_to_dst(
"certainty": "Heuristic",
});
super::super::observe::resolve::emit_choose(builder, meta);
let name_const = builder.value_gen.next();
if let Err(e) = builder.emit_instruction(MirInstruction::Const { dst: name_const, value: ConstValue::String(fname.clone()) }) { return Some(Err(e)); }
let name_const = match crate::mir::builder::name_const::make_name_const_result(builder, &fname) {
Ok(v) => v,
Err(e) => return Some(Err(e)),
};
let mut call_args = Vec::with_capacity(1);
call_args.push(object_value);
crate::mir::builder::ssa::local::finalize_args(builder, &mut call_args);
let actual_dst = want_dst.unwrap_or_else(|| builder.value_gen.next());
if let Err(e) = builder.emit_instruction(MirInstruction::Call { dst: Some(actual_dst), func: name_const, callee: None, args: call_args, effects: EffectMask::READ.add(Effect::ReadHeap), }) { return Some(Err(e)); }
builder.annotate_call_result_from_func_name(actual_dst, &fname);
@ -185,10 +200,13 @@ pub(crate) fn try_early_str_like_to_dst(
"certainty": "Heuristic",
});
super::super::observe::resolve::emit_choose(builder, meta);
let name_const = builder.value_gen.next();
if let Err(e) = builder.emit_instruction(MirInstruction::Const { dst: name_const, value: ConstValue::String(fname.clone()) }) { return Some(Err(e)); }
let mut call_args = Vec::with_capacity(1);
call_args.push(object_value);
let name_const = match crate::mir::builder::name_const::make_name_const_result(builder, &fname) {
Ok(v) => v,
Err(e) => return Some(Err(e)),
};
let mut call_args = Vec::with_capacity(1);
call_args.push(object_value);
crate::mir::builder::ssa::local::finalize_args(builder, &mut call_args);
let actual_dst = want_dst.unwrap_or_else(|| builder.value_gen.next());
if let Err(e) = builder.emit_instruction(MirInstruction::Call { dst: Some(actual_dst), func: name_const, callee: None, args: call_args, effects: EffectMask::READ.add(Effect::ReadHeap), }) { return Some(Err(e)); }
builder.annotate_call_result_from_func_name(actual_dst, &fname);

View File

@ -0,0 +1,3 @@
//! Router policy module
pub mod policy;

View File

@ -0,0 +1,48 @@
use crate::mir::definitions::call_unified::TypeCertainty;
use std::sync::OnceLock;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Route {
Unified,
BoxCall,
}
/// Decide routing policy for a method call (Unified vs BoxCall) without changing behavior.
/// Rules (behavior-preserving):
/// - UnknownBox → BoxCall (unified is unstable for unknown receivers)
/// - Core boxes: StringBox/ArrayBox/MapBox → BoxCall (legacy path preferred)
/// - User boxes: names not ending with "Box" → BoxCall
/// - Otherwise Unified
pub fn choose_route(box_name: &str, method: &str, certainty: TypeCertainty, arity: usize) -> Route {
let mut reason = "unified";
let route = if box_name == "UnknownBox" {
reason = "unknown_recv";
Route::BoxCall
} else if matches!(box_name, "StringBox" | "ArrayBox" | "MapBox") {
reason = "core_box";
Route::BoxCall
} else if !box_name.ends_with("Box") {
reason = "user_instance";
Route::BoxCall
} else {
Route::Unified
};
if router_trace_enabled() {
eprintln!(
"[router] route={:?} reason={} recv={} method={} arity={} certainty={:?}",
route, reason, box_name, method, arity, certainty
);
}
route
}
#[inline]
fn router_trace_enabled() -> bool {
static ON: OnceLock<bool> = OnceLock::new();
*ON.get_or_init(|| match std::env::var("NYASH_ROUTER_TRACE") {
Ok(val) => matches!(val.as_str(), "1" | "true" | "on" | "yes"),
Err(_) => false,
})
}

View File

@ -0,0 +1,87 @@
use crate::mir::builder::MirBuilder;
use crate::mir::{MirInstruction, ValueId};
/// BlockScheduleBox — manage physical insertion points within a block.
/// Contract: PHI group → materialize group (Copy/Id) → body (Call etc.)
pub struct BlockScheduleBox;
impl BlockScheduleBox {
/// Insert a Copy immediately after PHI nodes. Returns the local value id.
pub fn ensure_after_phis_copy(builder: &mut MirBuilder, src: ValueId) -> Result<ValueId, String> {
if let Some(bb) = builder.current_block {
if let Some(&cached) = builder.schedule_mat_map.get(&(bb, src)) {
return Ok(cached);
}
let dst = builder.value_gen.next();
builder.insert_copy_after_phis(dst, src)?;
builder.schedule_mat_map.insert((bb, src), dst);
return Ok(dst);
}
Err("No current block".into())
}
/// Emit a Copy right before the next emitted instruction (best-effort):
/// place it at the tail of the current block. Returns the local value id.
pub fn emit_before_call_copy(builder: &mut MirBuilder, src: ValueId) -> Result<ValueId, String> {
// Prefer to reuse the after-phis materialized id for this src in this block
let base = Self::ensure_after_phis_copy(builder, src)?;
let dst = builder.value_gen.next();
builder.emit_instruction(MirInstruction::Copy { dst, src: base })?;
// Propagate metadata to keep dst consistent with base
crate::mir::builder::metadata::propagate::propagate(builder, base, dst);
Ok(dst)
}
/// Dev-only: verify simple block order invariants.
/// - PHI group must be at the block head (no PHI after first non-PHI)
/// - If a Copy immediately precedes a Call-like instruction, prefer that Copy's src
/// to be the previously materialized after-PHIs id (best-effort warning only).
pub fn verify_order(builder: &mut MirBuilder) {
if std::env::var("NYASH_BLOCK_SCHEDULE_VERIFY").ok().as_deref() != Some("1") {
return;
}
let (f_opt, bb_opt) = (builder.current_function.as_ref(), builder.current_block);
let (Some(fun), Some(bb_id)) = (f_opt, bb_opt) else { return; };
let Some(bb) = fun.get_block(bb_id) else { return; };
// 1) PHI group must be at head
let mut seen_non_phi = false;
for (idx, inst) in bb.instructions.iter().enumerate() {
match inst {
MirInstruction::Phi { .. } => {
if seen_non_phi {
eprintln!("[block-schedule][verify] WARN: PHI found after non-PHI at bb={:?} idx={}", bb_id, idx);
}
}
_ => { seen_non_phi = true; }
}
}
// 2) If a Copy is immediately before a Call-like, prefer it to be derived from after-PHIs copy
let is_call_like = |mi: &MirInstruction| -> bool {
matches!(mi,
MirInstruction::Call { .. } |
MirInstruction::BoxCall { .. } |
MirInstruction::PluginInvoke { .. } |
MirInstruction::ExternCall { .. }
)
};
for w in bb.instructions.windows(2) {
if let [MirInstruction::Copy { dst: _, src }, call] = w {
if is_call_like(call) {
// best-effort: src should be one of the after-PHIs materialized ids for this bb
let derived_ok = builder
.schedule_mat_map
.values()
.any(|&v| v == *src);
if !derived_ok {
eprintln!(
"[block-schedule][verify] WARN: tail Copy src=%{} is not from after-PHIs in bb={:?}",
src.0, bb_id
);
}
}
}
}
}
}

View File

@ -0,0 +1,2 @@
pub mod block;

View File

@ -0,0 +1,121 @@
use crate::mir::builder::MirBuilder;
use crate::mir::{ValueId, Callee};
use std::collections::HashMap;
#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
pub enum LocalKind {
Recv,
Arg,
CompareOperand,
Cond,
FieldBase,
Other(u8),
}
impl LocalKind {
#[inline]
fn tag(self) -> u8 {
match self {
LocalKind::Recv => 0,
LocalKind::Arg => 1,
LocalKind::CompareOperand => 2,
LocalKind::Cond => 4,
LocalKind::FieldBase => 0, // share recv slot for bases
LocalKind::Other(k) => k,
}
}
}
/// Ensure a value has an in-block definition and cache it per (bb, orig, kind).
/// Always emits a Copy in the current block when not cached.
pub fn ensure(builder: &mut MirBuilder, v: ValueId, kind: LocalKind) -> ValueId {
let bb_opt = builder.current_block;
if let Some(bb) = bb_opt {
if std::env::var("NYASH_LOCAL_SSA_TRACE").ok().as_deref() == Some("1") {
eprintln!("[local-ssa] ensure bb={:?} kind={:?} v=%{}", bb, kind, v.0);
}
let key = (bb, v, kind.tag());
if let Some(&loc) = builder.local_ssa_map.get(&key) {
return loc;
}
let loc = builder.value_gen.next();
// Best-effort: errors are propagated by caller; we ignore here to keep helper infallible
let _ = builder.emit_instruction(crate::mir::MirInstruction::Copy { dst: loc, src: v });
if std::env::var("NYASH_LOCAL_SSA_TRACE").ok().as_deref() == Some("1") {
eprintln!("[local-ssa] copy bb={:?} kind={:?} %{} -> %{}", bb, kind, v.0, loc.0);
}
if let Some(t) = builder.value_types.get(&v).cloned() {
builder.value_types.insert(loc, t);
}
if let Some(cls) = builder.value_origin_newbox.get(&v).cloned() {
builder.value_origin_newbox.insert(loc, cls);
}
builder.local_ssa_map.insert(key, loc);
loc
} else {
v
}
}
#[inline]
pub fn recv(builder: &mut MirBuilder, v: ValueId) -> ValueId { ensure(builder, v, LocalKind::Recv) }
#[inline]
pub fn arg(builder: &mut MirBuilder, v: ValueId) -> ValueId { ensure(builder, v, LocalKind::Arg) }
#[inline]
pub fn cond(builder: &mut MirBuilder, v: ValueId) -> ValueId { ensure(builder, v, LocalKind::Cond) }
#[inline]
pub fn field_base(builder: &mut MirBuilder, v: ValueId) -> ValueId { ensure(builder, v, LocalKind::FieldBase) }
#[inline]
pub fn cmp_operand(builder: &mut MirBuilder, v: ValueId) -> ValueId { ensure(builder, v, LocalKind::CompareOperand) }
/// Finalize a callee+args just before emitting a Call instruction:
/// - If Method: ensure receiver is in the current block
/// - All args: ensure in the current block
pub fn finalize_callee_and_args(builder: &mut MirBuilder, callee: &mut Callee, args: &mut Vec<ValueId>) {
if let Callee::Method { receiver: Some(r), box_name, method, certainty } = callee.clone() {
let r_local = recv(builder, r);
*callee = Callee::Method { box_name, method, receiver: Some(r_local), certainty };
}
for a in args.iter_mut() {
*a = arg(builder, *a);
}
}
/// Finalize only the args (legacy Call paths)
pub fn finalize_args(builder: &mut MirBuilder, args: &mut Vec<ValueId>) {
for a in args.iter_mut() {
*a = arg(builder, *a);
}
}
/// Finalize a single branch condition just before emitting a Branch.
/// Ensures the condition has a definition in the current block.
pub fn finalize_branch_cond(builder: &mut MirBuilder, condition_v: &mut ValueId) {
*condition_v = cond(builder, *condition_v);
if std::env::var("NYASH_LOCAL_SSA_TRACE").ok().as_deref() == Some("1") {
if let Some(bb) = builder.current_block { eprintln!("[local-ssa] finalize-branch bb={:?} cond=%{}", bb, condition_v.0); }
}
}
/// Finalize compare operands just before emitting a Compare.
/// Applies in-block materialization to both lhs and rhs.
pub fn finalize_compare(builder: &mut MirBuilder, lhs: &mut ValueId, rhs: &mut ValueId) {
*lhs = cmp_operand(builder, *lhs);
*rhs = cmp_operand(builder, *rhs);
if std::env::var("NYASH_LOCAL_SSA_TRACE").ok().as_deref() == Some("1") {
if let Some(bb) = builder.current_block { eprintln!("[local-ssa] finalize-compare bb={:?} lhs=%{} rhs=%{}", bb, lhs.0, rhs.0); }
}
}
/// Finalize field use sites: ensure base and all args are in the current block.
pub fn finalize_field_base_and_args(builder: &mut MirBuilder, base: &mut ValueId, args: &mut Vec<ValueId>) {
*base = field_base(builder, *base);
for a in args.iter_mut() { *a = arg(builder, *a); }
if std::env::var("NYASH_LOCAL_SSA_TRACE").ok().as_deref() == Some("1") {
if let Some(bb) = builder.current_block { eprintln!("[local-ssa] finalize-field bb={:?} base=%{} argc={}", bb, base.0, args.len()); }
}
}

View File

@ -0,0 +1,2 @@
pub mod local;

View File

@ -161,13 +161,8 @@ impl super::MirBuilder {
}
}
let out = last_value.unwrap_or_else(|| {
let void_val = self.value_gen.next();
self.emit_instruction(MirInstruction::Const {
dst: void_val,
value: ConstValue::Void,
})
.unwrap();
void_val
// Use ConstantEmissionBox for Void
crate::mir::builder::emission::constant::emit_void(self)
});
// Scope leave only if block not already terminated
if !self.is_current_block_terminated() {
@ -192,9 +187,7 @@ impl super::MirBuilder {
init_val
} else {
// Create a concrete register for uninitialized locals (Void)
let vid = self.value_gen.next();
self.emit_instruction(MirInstruction::Const { dst: vid, value: ConstValue::Void })?;
vid
crate::mir::builder::emission::constant::emit_void(self)
};
self.variable_map.insert(var_name.clone(), var_id);
last_value = Some(var_id);
@ -215,9 +208,7 @@ impl super::MirBuilder {
let return_value = if let Some(expr) = value {
self.build_expression(*expr)?
} else {
let void_dst = self.value_gen.next();
self.emit_instruction(MirInstruction::Const { dst: void_dst, value: ConstValue::Void })?;
void_dst
crate::mir::builder::emission::constant::emit_void(self)
};
if self.return_defer_active {
@ -225,8 +216,9 @@ impl super::MirBuilder {
if let (Some(slot), Some(target)) = (self.return_defer_slot, self.return_defer_target) {
self.return_deferred_emitted = true;
self.emit_instruction(MirInstruction::Copy { dst: slot, src: return_value })?;
crate::mir::builder::metadata::propagate::propagate(self, return_value, slot);
if !self.is_current_block_terminated() {
self.emit_instruction(MirInstruction::Jump { target })?;
crate::mir::builder::emission::branch::emit_jump(self, target)?;
}
Ok(return_value)
} else {
@ -255,11 +247,7 @@ impl super::MirBuilder {
} = expression.clone()
{
let recv_val = self.build_expression(*object)?;
let mname_id = self.value_gen.next();
self.emit_instruction(MirInstruction::Const {
dst: mname_id,
value: super::ConstValue::String(method.clone()),
})?;
let mname_id = crate::mir::builder::emission::constant::emit_string(self, method.clone());
let mut arg_vals: Vec<ValueId> = Vec::with_capacity(2 + arguments.len());
arg_vals.push(recv_val);
arg_vals.push(mname_id);
@ -308,16 +296,12 @@ impl super::MirBuilder {
if let Some(id) = self.variable_map.get("me").cloned() {
return Ok(id);
}
let me_value = self.value_gen.next();
let me_tag = if let Some(ref cls) = self.current_static_box {
cls.clone()
} else {
"__me__".to_string()
};
self.emit_instruction(MirInstruction::Const {
dst: me_value,
value: ConstValue::String(me_tag),
})?;
let me_value = crate::mir::builder::emission::constant::emit_string(self, me_tag);
self.variable_map.insert("me".to_string(), me_value);
// P0: Known 化 — 分かる範囲で me の起源クラスを付与(挙動不変)。
super::origin::infer::annotate_me_origin(self, me_value);

View File

@ -0,0 +1,38 @@
//! TypeAnnotationBox — MIR 値への型注釈(仕様不変の最小)
use crate::mir::{MirType, ValueId};
use crate::mir::builder::MirBuilder;
/// 直接的に MirType を設定する(仕様不変)。
#[inline]
pub fn set_type(builder: &mut MirBuilder, dst: ValueId, ty: MirType) {
builder.value_types.insert(dst, ty);
}
/// 関数名から既知の戻り型を注釈する(最小ハードコード)。
/// 例: "StringBox.str/0" → MirType::String
#[inline]
pub fn annotate_from_function(builder: &mut MirBuilder, dst: ValueId, func_name: &str) {
if let Some(ty) = infer_return_type(func_name) {
builder.value_types.insert(dst, ty);
}
}
fn infer_return_type(func_name: &str) -> Option<MirType> {
// Very small whitelist; 仕様不変(既知の戻り型のみ)
// Normalize forms like "JsonNode.str/0" or "StringBox.length/0" if needed
if func_name.ends_with(".str/0") { return Some(MirType::String); }
if func_name.ends_with(".length/0") { return Some(MirType::Integer); }
if func_name.ends_with(".size/0") { return Some(MirType::Integer); }
if func_name.ends_with(".len/0") { return Some(MirType::Integer); }
if func_name.ends_with(".substring/2") { return Some(MirType::String); }
if func_name.ends_with(".esc_json/0") { return Some(MirType::String); }
if func_name.ends_with(".indexOf/1") { return Some(MirType::Integer); }
if func_name.ends_with(".lastIndexOf/1") { return Some(MirType::Integer); }
if func_name.ends_with(".is_digit_char/1") { return Some(MirType::Bool); }
if func_name.ends_with(".is_hex_digit_char/1") { return Some(MirType::Bool); }
if func_name.ends_with(".is_alpha_char/1") { return Some(MirType::Bool); }
if func_name.ends_with("MapBox.has/1") { return Some(MirType::Bool); }
// Fallback: none (変更なし)
None
}

View File

@ -0,0 +1,6 @@
//! types: 型注釈/推論の薄い箱。
//! - annotation.rs既知の戻り型などの注釈付け
//! - inference.rs後段、挙動不変の観測強化と最小推論
pub mod annotation;

View File

@ -25,6 +25,22 @@ pub(super) fn builder_debug_log(msg: &str) {
}
impl super::MirBuilder {
// ---- LocalSSA convenience (readability helpers) ----
#[allow(dead_code)]
#[inline]
pub(crate) fn local_recv(&mut self, v: super::ValueId) -> super::ValueId { super::ssa::local::recv(self, v) }
#[allow(dead_code)]
#[inline]
pub(crate) fn local_arg(&mut self, v: super::ValueId) -> super::ValueId { super::ssa::local::arg(self, v) }
#[allow(dead_code)]
#[inline]
pub(crate) fn local_cmp_operand(&mut self, v: super::ValueId) -> super::ValueId { super::ssa::local::cmp_operand(self, v) }
#[allow(dead_code)]
#[inline]
pub(crate) fn local_field_base(&mut self, v: super::ValueId) -> super::ValueId { super::ssa::local::field_base(self, v) }
#[allow(dead_code)]
#[inline]
pub(crate) fn local_cond(&mut self, v: super::ValueId) -> super::ValueId { super::ssa::local::cond(self, v) }
/// Ensure a basic block exists in the current function
pub(crate) fn ensure_block_exists(&mut self, block_id: BasicBlockId) -> Result<(), String> {
if let Some(ref mut function) = self.current_function {
@ -43,6 +59,10 @@ impl super::MirBuilder {
if let Some(ref mut function) = self.current_function {
function.add_block(BasicBlock::new(block_id));
self.current_block = Some(block_id);
// Local SSA cache is per-block; clear on block switch
self.local_ssa_map.clear();
// BlockSchedule materialize cache is per-block as well
self.schedule_mat_map.clear();
// Entry materialization for pinned slots only when not suppressed.
// This provides block-local defs in single-predecessor flows without touching user vars.
if !self.suppress_pin_entry_copy_next {
@ -54,6 +74,7 @@ impl super::MirBuilder {
if let Some(&src) = self.variable_map.get(name) {
let dst = self.value_gen.next();
self.emit_instruction(super::MirInstruction::Copy { dst, src })?;
crate::mir::builder::metadata::propagate::propagate(self, src, dst);
self.variable_map.insert(name.clone(), dst);
pin_renames.push((src, dst));
}
@ -95,8 +116,10 @@ impl super::MirBuilder {
) -> Result<(), String> {
// Ensure receiver has a definition in the current block to avoid undefined use across
// block boundaries (LoopForm/header, if-joins, etc.).
// Pinning creates a local Copy that participates in PHI when needed.
let box_val = self.pin_to_slot(box_val, "@recv").unwrap_or(box_val);
// LocalSSA: ensure receiver has an in-block definition (kind=0 = recv)
let box_val = self.local_recv(box_val);
// LocalSSA: ensure args are materialized in current block
let args: Vec<super::ValueId> = args.into_iter().map(|a| self.local_arg(a)).collect();
// Check environment variable for unified call usage, with safe overrides for core/user boxes
let use_unified_env = super::calls::call_unified::is_unified_call_enabled();
// First, try to determine the box type
@ -110,13 +133,27 @@ impl super::MirBuilder {
}
}
}
// Prefer legacy BoxCall for core collection boxes and user instance boxes (stability first)
let prefer_legacy = match box_type.as_deref() {
Some("ArrayBox") | Some("MapBox") | Some("StringBox") => true,
Some(bt) => !bt.ends_with("Box"), // user instance class name (e.g., JsonTokenizer)
None => false,
};
if use_unified_env && !prefer_legacy {
// Route decision is centralized in RouterPolicyBox仕様不変
let bx_name = box_type.clone().unwrap_or_else(|| "UnknownBox".to_string());
let route = crate::mir::builder::router::policy::choose_route(
&bx_name,
&method,
crate::mir::definitions::call_unified::TypeCertainty::Union,
args.len(),
);
if super::utils::builder_debug_enabled() || std::env::var("NYASH_LOCAL_SSA_TRACE").ok().as_deref() == Some("1") {
if matches!(method.as_str(), "parse" | "substring" | "has_errors" | "length") {
eprintln!(
"[boxcall-decision] method={} bb={:?} recv=%{} class_hint={:?} prefer_legacy={}",
method,
self.current_block,
box_val.0,
box_type,
matches!(route, crate::mir::builder::router::policy::Route::BoxCall)
);
}
}
if use_unified_env && matches!(route, crate::mir::builder::router::policy::Route::Unified) {
let target = super::builder_calls::CallTarget::Method {
box_type,
method: method.clone(),
@ -251,12 +288,7 @@ impl super::MirBuilder {
super::utils::builder_debug_log(&format!("pin slot={} src={} dst={}", slot_name, v.0, dst.0));
}
// Propagate lightweight metadata so downstream resolution/type inference remains stable
if let Some(t) = self.value_types.get(&v).cloned() {
self.value_types.insert(dst, t);
}
if let Some(cls) = self.value_origin_newbox.get(&v).cloned() {
self.value_origin_newbox.insert(dst, cls);
}
crate::mir::builder::metadata::propagate::propagate(self, v, dst);
self.variable_map.insert(slot_name, dst);
Ok(dst)
}
@ -265,12 +297,42 @@ impl super::MirBuilder {
pub(crate) fn materialize_local(&mut self, v: super::ValueId) -> Result<super::ValueId, String> {
let dst = self.value_gen.next();
self.emit_instruction(super::MirInstruction::Copy { dst, src: v })?;
// Propagate metadata (type/origin) from source to the new local copy
crate::mir::builder::metadata::propagate::propagate(self, v, dst);
Ok(dst)
}
/// Insert a Copy immediately after PHI nodes in the current block (position-stable).
pub(crate) fn insert_copy_after_phis(&mut self, dst: super::ValueId, src: super::ValueId) -> Result<(), String> {
if let (Some(ref mut function), Some(bb)) = (&mut self.current_function, self.current_block) {
if let Some(block) = function.get_block_mut(bb) {
// Propagate effects on the block
block.insert_instruction_after_phis(super::MirInstruction::Copy { dst, src });
// Lightweight metadata propagation (unified)
crate::mir::builder::metadata::propagate::propagate(self, src, dst);
return Ok(());
}
}
Err("No current function/block to insert copy".to_string())
}
/// Ensure a value is safe to use in the current block by slotifying (pinning) it.
/// Currently correctness-first: always pin to get a block-local def and PHI participation.
pub(crate) fn ensure_slotified_for_use(&mut self, v: super::ValueId, hint: &str) -> Result<super::ValueId, String> {
self.pin_to_slot(v, hint)
}
/// Local SSA: ensure a value has a definition in the current block and cache it per-block.
/// kind: 0 = recv (reserved for args in future)
pub(crate) fn local_ssa_ensure(&mut self, v: super::ValueId, kind: u8) -> super::ValueId {
use super::ssa::local::{ensure, LocalKind};
let lk = match kind {
0 => LocalKind::Recv,
1 => LocalKind::Arg,
2 => LocalKind::CompareOperand,
4 => LocalKind::Cond,
x => LocalKind::Other(x),
};
ensure(self, v, lk)
}
}

View File

@ -0,0 +1,57 @@
//! FunctionEmissionBox — MirFunction 直編集時の発行ヘルパ仕様不変・dev補助
//!
//! MirBuilder 経由ではなく MirFunction/BasicBlock を直接編集する箇所dev 補助)向けに、
//! よく使う Const/Return/Jump の発行を薄い関数で提供する。
use crate::mir::{BasicBlockId, ConstValue, MirFunction, MirInstruction, ValueId};
#[inline]
pub fn emit_const_integer(f: &mut MirFunction, bb: BasicBlockId, val: i64) -> ValueId {
let dst = f.next_value_id();
if let Some(block) = f.get_block_mut(bb) {
block.add_instruction(MirInstruction::Const { dst, value: ConstValue::Integer(val) });
}
dst
}
#[inline]
pub fn emit_const_bool(f: &mut MirFunction, bb: BasicBlockId, val: bool) -> ValueId {
let dst = f.next_value_id();
if let Some(block) = f.get_block_mut(bb) {
block.add_instruction(MirInstruction::Const { dst, value: ConstValue::Bool(val) });
}
dst
}
#[inline]
pub fn emit_const_string<S: Into<String>>(f: &mut MirFunction, bb: BasicBlockId, s: S) -> ValueId {
let dst = f.next_value_id();
if let Some(block) = f.get_block_mut(bb) {
block.add_instruction(MirInstruction::Const { dst, value: ConstValue::String(s.into()) });
}
dst
}
#[inline]
pub fn emit_const_void(f: &mut MirFunction, bb: BasicBlockId) -> ValueId {
let dst = f.next_value_id();
if let Some(block) = f.get_block_mut(bb) {
block.add_instruction(MirInstruction::Const { dst, value: ConstValue::Void });
}
dst
}
#[inline]
pub fn emit_return_value(f: &mut MirFunction, bb: BasicBlockId, value: ValueId) {
if let Some(block) = f.get_block_mut(bb) {
block.add_instruction(MirInstruction::Return { value: Some(value) });
}
}
#[inline]
pub fn emit_jump(f: &mut MirFunction, bb: BasicBlockId, target: BasicBlockId) {
if let Some(block) = f.get_block_mut(bb) {
block.add_instruction(MirInstruction::Jump { target });
}
}

View File

@ -382,9 +382,11 @@ impl<'a> LoopBuilder<'a> {
then_bb: BasicBlockId,
else_bb: BasicBlockId,
) -> Result<(), String> {
// LocalSSA: ensure condition is materialized in the current block
let condition_local = self.parent_builder.local_ssa_ensure(condition, 4);
self.parent_builder
.emit_instruction(MirInstruction::Branch {
condition,
condition: condition_local,
then_bb,
else_bb,
})

View File

@ -25,6 +25,7 @@ pub mod optimizer_passes; // optimizer passes (normalize/diagnostics)
pub mod optimizer_stats; // extracted stats struct
pub mod passes;
pub mod printer;
pub mod function_emission; // FunctionEmissionBoxMirFunction直編集の発行ヘルパ
mod printer_helpers; // internal helpers extracted from printer.rs
pub mod hints; // scaffold: zero-cost guidance (no-op)
pub mod slot_registry; // Phase 9.79b.1: method slot resolution (IDs)