public: publish selfhost snapshot to public repo (SSOT using + AST merge + JSON VM fixes)
- SSOT using profiles (aliases/packages via nyash.toml), AST prelude merge - Parser/member guards; Builder pin/PHI and instance→function rewrite (dev on) - VM refactors (handlers split) and JSON roundtrip/nested stabilization - CURRENT_TASK.md updated with scope and acceptance criteria Notes: dev-only guards remain togglable via env; no default behavior changes for prod.
This commit is contained in:
@ -10,6 +10,7 @@ use super::{
|
||||
FunctionSignature, MirFunction, MirInstruction, MirModule, MirType, ValueId, ValueIdGenerator,
|
||||
};
|
||||
use crate::ast::{ASTNode, LiteralValue};
|
||||
use crate::mir::builder::builder_calls::CallTarget;
|
||||
use std::collections::HashMap;
|
||||
use std::collections::HashSet;
|
||||
mod calls; // Call system modules (refactored from builder_calls)
|
||||
@ -85,6 +86,8 @@ pub struct MirBuilder {
|
||||
|
||||
/// Remember class of object fields after assignments: (base_id, field) -> class_name
|
||||
pub(super) field_origin_class: HashMap<(ValueId, String), String>,
|
||||
/// Class-level field origin (cross-function heuristic): (BaseBoxName, field) -> FieldBoxName
|
||||
pub(super) field_origin_by_box: HashMap<(String, String), String>,
|
||||
|
||||
/// Optional per-value type annotations (MIR-level): ValueId -> MirType
|
||||
pub(super) value_types: HashMap<ValueId, super::MirType>,
|
||||
@ -152,6 +155,7 @@ impl MirBuilder {
|
||||
weak_fields_by_box: HashMap::new(),
|
||||
property_getters_by_box: HashMap::new(),
|
||||
field_origin_class: HashMap::new(),
|
||||
field_origin_by_box: HashMap::new(),
|
||||
value_types: HashMap::new(),
|
||||
plugin_method_sigs,
|
||||
current_static_box: None,
|
||||
@ -270,7 +274,12 @@ impl MirBuilder {
|
||||
var_name: String,
|
||||
value: ASTNode,
|
||||
) -> Result<ValueId, String> {
|
||||
let value_id = self.build_expression(value)?;
|
||||
let raw_value_id = self.build_expression(value)?;
|
||||
// Correctness-first: assignment results may be used across control-flow joins.
|
||||
// Pin to a slot so the value has a block-local def and participates in PHI merges.
|
||||
let value_id = self
|
||||
.pin_to_slot(raw_value_id, "@assign")
|
||||
.unwrap_or(raw_value_id);
|
||||
|
||||
// In SSA form, each assignment creates a new value
|
||||
self.variable_map.insert(var_name.clone(), value_id);
|
||||
@ -430,18 +439,37 @@ impl MirBuilder {
|
||||
// Record origin for optimization: dst was created by NewBox of class
|
||||
self.value_origin_newbox.insert(dst, class.clone());
|
||||
|
||||
// Call birth(...) for all boxes except StringBox (special-cased in LLVM path)
|
||||
// User-defined boxes require birth to initialize fields (scanner/tokens etc.)
|
||||
// birth 呼び出し(Builder 正規化)
|
||||
// 優先: 低下済みグローバル関数 `<Class>.birth/Arity`(Arity は me を含まない)
|
||||
// 代替: 既存互換として BoxCall("birth")(プラグイン/ビルトインの初期化に対応)
|
||||
if class != "StringBox" {
|
||||
let birt_mid = resolve_slot_by_type_name(&class, "birth");
|
||||
self.emit_box_or_plugin_call(
|
||||
None,
|
||||
dst,
|
||||
"birth".to_string(),
|
||||
birt_mid,
|
||||
arg_values,
|
||||
EffectMask::READ.add(Effect::ReadHeap),
|
||||
)?;
|
||||
let arity = arg_values.len();
|
||||
let lowered = crate::mir::builder::calls::function_lowering::generate_method_function_name(
|
||||
&class,
|
||||
"birth",
|
||||
arity,
|
||||
);
|
||||
let use_lowered = if let Some(ref module) = self.current_module {
|
||||
module.functions.contains_key(&lowered)
|
||||
} else { false };
|
||||
if use_lowered {
|
||||
// Call Global("Class.birth/Arity") with argv = [me, args...]
|
||||
let mut argv: Vec<ValueId> = Vec::with_capacity(1 + arity);
|
||||
argv.push(dst);
|
||||
argv.extend(arg_values.iter().copied());
|
||||
self.emit_legacy_call(None, CallTarget::Global(lowered), argv)?;
|
||||
} else {
|
||||
// Fallback: instance method BoxCall("birth")
|
||||
let birt_mid = resolve_slot_by_type_name(&class, "birth");
|
||||
self.emit_box_or_plugin_call(
|
||||
None,
|
||||
dst,
|
||||
"birth".to_string(),
|
||||
birt_mid,
|
||||
arg_values,
|
||||
EffectMask::READ.add(Effect::ReadHeap),
|
||||
)?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(dst)
|
||||
|
||||
@ -10,6 +10,63 @@ use super::calls::*;
|
||||
pub use super::calls::call_target::CallTarget;
|
||||
|
||||
impl super::MirBuilder {
|
||||
/// Annotate a call result `dst` with the return type and origin if the callee
|
||||
/// is a known user/static function in the current module.
|
||||
pub(super) fn annotate_call_result_from_func_name<S: AsRef<str>>(&mut self, dst: super::ValueId, func_name: S) {
|
||||
let name = func_name.as_ref();
|
||||
// 1) Prefer module signature when available
|
||||
if let Some(ref module) = self.current_module {
|
||||
if let Some(func) = module.functions.get(name) {
|
||||
let mut ret = func.signature.return_type.clone();
|
||||
// Targeted stabilization: JsonParser.parse/1 should produce JsonNode
|
||||
// If signature is Unknown/Void, normalize to Box("JsonNode")
|
||||
if name == "JsonParser.parse/1" {
|
||||
if matches!(ret, super::MirType::Unknown | super::MirType::Void) {
|
||||
ret = super::MirType::Box("JsonNode".into());
|
||||
}
|
||||
}
|
||||
// Token path: JsonParser.current_token/0 should produce JsonToken
|
||||
if name == "JsonParser.current_token/0" {
|
||||
if matches!(ret, super::MirType::Unknown | super::MirType::Void) {
|
||||
ret = super::MirType::Box("JsonToken".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") {
|
||||
let bx = self.value_origin_newbox.get(&dst).cloned().unwrap_or_default();
|
||||
super::utils::builder_debug_log(&format!("annotate call dst={} from {} -> Box({})", dst.0, name, bx));
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
// 2) No module signature—apply minimal heuristic for known functions
|
||||
if name == "JsonParser.parse/1" {
|
||||
let ret = super::MirType::Box("JsonNode".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(JsonNode)", dst.0, name));
|
||||
}
|
||||
} else if name == "JsonParser.current_token/0" {
|
||||
let ret = super::MirType::Box("JsonToken".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(JsonToken)", dst.0, name));
|
||||
}
|
||||
} else if name == "JsonTokenizer.tokenize/0" {
|
||||
// Tokenize returns an ArrayBox of tokens
|
||||
let ret = super::MirType::Box("ArrayBox".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(ArrayBox)", dst.0, name));
|
||||
}
|
||||
}
|
||||
}
|
||||
/// Unified call emission - replaces all emit_*_call methods
|
||||
/// ChatGPT5 Pro A++ design for complete call unification
|
||||
pub fn emit_unified_call(
|
||||
@ -86,13 +143,18 @@ impl super::MirBuilder {
|
||||
let mut call_args = Vec::with_capacity(arity);
|
||||
call_args.push(receiver); // pass 'me' first
|
||||
call_args.extend(args.into_iter());
|
||||
return self.emit_instruction(MirInstruction::Call {
|
||||
// Allocate a destination if not provided
|
||||
let actual_dst = if let Some(d) = dst { d } else { self.value_gen.next() };
|
||||
self.emit_instruction(MirInstruction::Call {
|
||||
dst,
|
||||
func: name_const,
|
||||
callee: None,
|
||||
args: call_args,
|
||||
effects: EffectMask::READ.add(Effect::ReadHeap),
|
||||
});
|
||||
})?;
|
||||
// Annotate result type/origin using lowered function signature
|
||||
if let Some(d) = dst.or(Some(actual_dst)) { self.annotate_call_result_from_func_name(d, super::calls::function_lowering::generate_method_function_name(&cls, &method, arity)); }
|
||||
return Ok(());
|
||||
}
|
||||
// Else fall back to plugin/boxcall path (StringBox/ArrayBox/MapBox etc.)
|
||||
self.emit_box_or_plugin_call(dst, receiver, method, None, args, EffectMask::IO)
|
||||
@ -128,16 +190,20 @@ impl super::MirBuilder {
|
||||
let name_const = self.value_gen.next();
|
||||
self.emit_instruction(MirInstruction::Const {
|
||||
dst: name_const,
|
||||
value: super::ConstValue::String(name),
|
||||
value: super::ConstValue::String(name.clone()),
|
||||
})?;
|
||||
|
||||
// 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() };
|
||||
self.emit_instruction(MirInstruction::Call {
|
||||
dst,
|
||||
dst: Some(actual_dst),
|
||||
func: name_const,
|
||||
callee: None, // Legacy mode
|
||||
args,
|
||||
effects: EffectMask::IO,
|
||||
})
|
||||
})?;
|
||||
// Annotate from module signature (if present)
|
||||
self.annotate_call_result_from_func_name(actual_dst, name);
|
||||
Ok(())
|
||||
},
|
||||
CallTarget::Value(func_val) => {
|
||||
self.emit_instruction(MirInstruction::Call {
|
||||
@ -303,7 +369,7 @@ 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) }) { return Some(Err(e)); }
|
||||
if let Err(e) = self.emit_instruction(MirInstruction::Const { dst: fun_val, value: super::ConstValue::String(fun_name.clone()) }) { return Some(Err(e)); }
|
||||
if let Err(e) = self.emit_instruction(MirInstruction::Call {
|
||||
dst: Some(result_id),
|
||||
func: fun_val,
|
||||
@ -311,6 +377,8 @@ impl super::MirBuilder {
|
||||
args: arg_values,
|
||||
effects: EffectMask::READ.add(Effect::ReadHeap)
|
||||
}) { return Some(Err(e)); }
|
||||
// Annotate from lowered function signature if present
|
||||
self.annotate_call_result_from_func_name(result_id, &fun_name);
|
||||
Some(Ok(result_id))
|
||||
}
|
||||
|
||||
@ -409,20 +477,23 @@ impl super::MirBuilder {
|
||||
return Ok(dst);
|
||||
}
|
||||
}
|
||||
// Secondary fallback: search already-materialized functions in the current module
|
||||
if let Some(ref module) = self.current_module {
|
||||
let tail = format!(".{}{}", name, format!("/{}", arg_values.len()));
|
||||
let mut cands: Vec<String> = module
|
||||
.functions
|
||||
.keys()
|
||||
.filter(|k| k.ends_with(&tail))
|
||||
.cloned()
|
||||
.collect();
|
||||
if cands.len() == 1 {
|
||||
let func_name = cands.remove(0);
|
||||
let dst = self.value_gen.next();
|
||||
self.emit_legacy_call(Some(dst), CallTarget::Global(func_name), arg_values)?;
|
||||
return Ok(dst);
|
||||
// Secondary fallback (tail-based) is disabled by default to avoid ambiguous resolution.
|
||||
// Enable only when explicitly requested: NYASH_BUILDER_TAIL_RESOLVE=1
|
||||
if std::env::var("NYASH_BUILDER_TAIL_RESOLVE").ok().as_deref() == Some("1") {
|
||||
if let Some(ref module) = self.current_module {
|
||||
let tail = format!(".{}{}", name, format!("/{}", arg_values.len()));
|
||||
let mut cands: Vec<String> = module
|
||||
.functions
|
||||
.keys()
|
||||
.filter(|k| k.ends_with(&tail))
|
||||
.cloned()
|
||||
.collect();
|
||||
if cands.len() == 1 {
|
||||
let func_name = cands.remove(0);
|
||||
let dst = self.value_gen.next();
|
||||
self.emit_legacy_call(Some(dst), CallTarget::Global(func_name), arg_values)?;
|
||||
return Ok(dst);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Propagate original error
|
||||
@ -483,9 +554,47 @@ impl super::MirBuilder {
|
||||
|
||||
// 3. Handle me.method() calls
|
||||
if let ASTNode::Me { .. } = object {
|
||||
// 3-a) Static box fast path (already handled)
|
||||
if let Some(res) = self.handle_me_method_call(&method, &arguments)? {
|
||||
return Ok(res);
|
||||
}
|
||||
// 3-b) Instance box: prefer enclosing box method explicitly to avoid cross-box name collisions
|
||||
{
|
||||
// Capture enclosing class name without holding an active borrow
|
||||
let enclosing_cls: Option<String> = self
|
||||
.current_function
|
||||
.as_ref()
|
||||
.and_then(|f| f.signature.name.split('.').next().map(|s| s.to_string()));
|
||||
if let Some(cls) = enclosing_cls.as_ref() {
|
||||
// Build arg values (avoid overlapping borrows by collecting first)
|
||||
let built_args: Vec<ASTNode> = arguments.clone();
|
||||
let mut arg_values = Vec::with_capacity(built_args.len());
|
||||
for a in built_args.into_iter() { arg_values.push(self.build_expression(a)?); }
|
||||
let arity = arg_values.len();
|
||||
let fname = crate::mir::builder::calls::function_lowering::generate_method_function_name(cls, &method, arity);
|
||||
let exists = if let Some(ref module) = self.current_module { module.functions.contains_key(&fname) } else { false };
|
||||
if exists {
|
||||
// Pass 'me' as first arg
|
||||
let me_id = self.build_me_expression()?;
|
||||
let mut call_args = Vec::with_capacity(arity + 1);
|
||||
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()) })?;
|
||||
self.emit_instruction(MirInstruction::Call {
|
||||
dst: Some(dst),
|
||||
func: c,
|
||||
callee: None,
|
||||
args: call_args,
|
||||
effects: crate::mir::EffectMask::READ.add(crate::mir::Effect::ReadHeap),
|
||||
})?;
|
||||
self.annotate_call_result_from_func_name(dst, &fname);
|
||||
return Ok(dst);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 4. Build object value for remaining cases
|
||||
|
||||
@ -45,13 +45,25 @@ impl super::MirBuilder {
|
||||
effects: EffectMask::READ,
|
||||
})?;
|
||||
|
||||
// Propagate recorded origin class for this field if any
|
||||
// Propagate recorded origin class for this field if any (ValueId-scoped)
|
||||
if let Some(class_name) = self
|
||||
.field_origin_class
|
||||
.get(&(object_value, field.clone()))
|
||||
.cloned()
|
||||
{
|
||||
self.value_origin_newbox.insert(field_val, class_name);
|
||||
} else if let Some(base_cls) = self.value_origin_newbox.get(&object_value).cloned() {
|
||||
// Cross-function heuristic: use class-level field origin mapping
|
||||
if let Some(fcls) = self
|
||||
.field_origin_by_box
|
||||
.get(&(base_cls.clone(), field.clone()))
|
||||
.cloned()
|
||||
{
|
||||
if super::utils::builder_debug_enabled() || std::env::var("NYASH_BUILDER_DEBUG").ok().as_deref() == Some("1") {
|
||||
super::utils::builder_debug_log(&format!("field-origin hit by box-level map: base={} .{} -> {}", base_cls, field, fcls));
|
||||
}
|
||||
self.value_origin_newbox.insert(field_val, fcls);
|
||||
}
|
||||
}
|
||||
|
||||
// If base is a known newbox and field is weak, emit WeakLoad (+ optional barrier)
|
||||
@ -85,7 +97,10 @@ impl super::MirBuilder {
|
||||
}
|
||||
}
|
||||
|
||||
Ok(field_val)
|
||||
// Correctness-first: slotify field values so they have block-local defs
|
||||
// and participate in PHI merges when reused across branches.
|
||||
let pinned = self.pin_to_slot(field_val, "@field")?;
|
||||
Ok(pinned)
|
||||
}
|
||||
|
||||
/// Build field assignment: object.field = value
|
||||
@ -133,9 +148,14 @@ impl super::MirBuilder {
|
||||
}
|
||||
|
||||
// Record origin class for this field value if known
|
||||
if let Some(class_name) = self.value_origin_newbox.get(&value_result).cloned() {
|
||||
if let Some(val_cls) = self.value_origin_newbox.get(&value_result).cloned() {
|
||||
self.field_origin_class
|
||||
.insert((object_value, field.clone()), class_name);
|
||||
.insert((object_value, field.clone()), val_cls.clone());
|
||||
// Also record class-level mapping if base object class is known
|
||||
if let Some(base_cls) = self.value_origin_newbox.get(&object_value).cloned() {
|
||||
self.field_origin_by_box
|
||||
.insert((base_cls, field.clone()), val_cls);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(value_result)
|
||||
|
||||
@ -53,6 +53,8 @@ impl MirBuilder {
|
||||
// Snapshot variables before entering branches
|
||||
let pre_if_var_map = self.variable_map.clone();
|
||||
|
||||
let trace_if = std::env::var("NYASH_IF_TRACE").ok().as_deref() == Some("1");
|
||||
|
||||
// then
|
||||
self.start_new_block(then_block)?;
|
||||
// Scope enter for then-branch
|
||||
@ -65,6 +67,12 @@ impl MirBuilder {
|
||||
let inputs = vec![(pre_branch_bb, pre_v)];
|
||||
self.emit_instruction(MirInstruction::Phi { dst: phi_val, inputs })?;
|
||||
self.variable_map.insert(name.clone(), phi_val);
|
||||
if trace_if {
|
||||
eprintln!(
|
||||
"[if-trace] then-entry phi var={} pre={:?} -> dst={:?}",
|
||||
name, pre_v, phi_val
|
||||
);
|
||||
}
|
||||
}
|
||||
let then_value_raw = self.build_expression(then_branch)?;
|
||||
let then_exit_block = self.current_block()?;
|
||||
@ -85,6 +93,12 @@ impl MirBuilder {
|
||||
let inputs = vec![(pre_branch_bb, pre_v)];
|
||||
self.emit_instruction(MirInstruction::Phi { dst: phi_val, inputs })?;
|
||||
self.variable_map.insert(name.clone(), phi_val);
|
||||
if trace_if {
|
||||
eprintln!(
|
||||
"[if-trace] else-entry phi var={} pre={:?} -> dst={:?}",
|
||||
name, pre_v, phi_val
|
||||
);
|
||||
}
|
||||
}
|
||||
let (else_value_raw, else_ast_for_analysis, else_var_map_end_opt) = if let Some(else_ast) = else_branch {
|
||||
self.variable_map = pre_if_var_map.clone();
|
||||
|
||||
@ -91,6 +91,11 @@ impl MirBuilder {
|
||||
method: String,
|
||||
arguments: &[ASTNode],
|
||||
) -> Result<ValueId, String> {
|
||||
// Correctness-first: pin receiver so it has a block-local def and can safely
|
||||
// flow across branches/merges when method calls are used in conditions.
|
||||
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 {
|
||||
@ -99,28 +104,58 @@ impl MirBuilder {
|
||||
|
||||
// If receiver is a user-defined box, lower to function call: "Box.method/(1+arity)"
|
||||
let mut class_name_opt: Option<String> = None;
|
||||
if let Some(cn) = self.value_origin_newbox.get(&object_value) { class_name_opt = Some(cn.clone()); }
|
||||
// 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()); }
|
||||
}
|
||||
}
|
||||
// Optional dev/ci gate: enable builder-side instance→function rewrite by default
|
||||
// in dev/ci profiles, keep OFF in prod. Allow explicit override via env:
|
||||
// NYASH_BUILDER_REWRITE_INSTANCE={1|true|on} → force enable
|
||||
// NYASH_BUILDER_REWRITE_INSTANCE={0|false|off} → force disable
|
||||
let rewrite_enabled = {
|
||||
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,
|
||||
_ => {
|
||||
// Default: ON for dev/ci, OFF for prod
|
||||
!crate::config::env::using_is_prod()
|
||||
}
|
||||
}
|
||||
};
|
||||
if rewrite_enabled {
|
||||
if let Some(cls) = class_name_opt.clone() {
|
||||
if self.user_defined_boxes.contains(&cls) {
|
||||
let arity = arg_values.len(); // function name arity excludes 'me'
|
||||
let fname = crate::mir::builder::calls::function_lowering::generate_method_function_name(&cls, &method, arity);
|
||||
// Only use userbox path if such a function actually exists in the module
|
||||
let has_fn = if let Some(ref module) = self.current_module {
|
||||
// Gate: only rewrite when the lowered function actually exists (prevents false rewrites like JsonScanner.length/0)
|
||||
let exists = if let Some(ref module) = self.current_module {
|
||||
module.functions.contains_key(&fname)
|
||||
} else { false };
|
||||
if has_fn {
|
||||
if exists {
|
||||
if super::utils::builder_debug_enabled() || std::env::var("NYASH_BUILDER_DEBUG").ok().as_deref() == Some("1") {
|
||||
super::utils::builder_debug_log(&format!("userbox method-call cls={} method={} fname={}", cls, method, fname));
|
||||
}
|
||||
let name_const = self.value_gen.next();
|
||||
self.emit_instruction(MirInstruction::Const {
|
||||
dst: name_const,
|
||||
value: crate::mir::builder::ConstValue::String(fname),
|
||||
value: crate::mir::builder::ConstValue::String(fname.clone()),
|
||||
})?;
|
||||
let mut call_args = Vec::with_capacity(arity + 1);
|
||||
call_args.push(object_value); // 'me'
|
||||
@ -133,12 +168,74 @@ impl MirBuilder {
|
||||
args: call_args,
|
||||
effects: crate::mir::EffectMask::READ.add(crate::mir::Effect::ReadHeap),
|
||||
})?;
|
||||
// Annotate return type/origin from lowered function signature
|
||||
self.annotate_call_result_from_func_name(dst, &format!("{}.{}{}", cls, method, format!("/{}", arity)));
|
||||
return Ok(dst);
|
||||
} else {
|
||||
// Special-case: treat toString as stringify when method not present
|
||||
if method == "toString" && arity == 0 {
|
||||
if let Some(ref module) = self.current_module {
|
||||
let stringify_name = crate::mir::builder::calls::function_lowering::generate_method_function_name(&cls, "stringify", 0);
|
||||
if module.functions.contains_key(&stringify_name) {
|
||||
let name_const = self.value_gen.next();
|
||||
self.emit_instruction(MirInstruction::Const {
|
||||
dst: name_const,
|
||||
value: crate::mir::builder::ConstValue::String(stringify_name.clone()),
|
||||
})?;
|
||||
let mut call_args = Vec::with_capacity(1);
|
||||
call_args.push(object_value);
|
||||
let dst = self.value_gen.next();
|
||||
self.emit_instruction(MirInstruction::Call {
|
||||
dst: Some(dst),
|
||||
func: name_const,
|
||||
callee: None,
|
||||
args: call_args,
|
||||
effects: crate::mir::EffectMask::READ.add(crate::mir::Effect::ReadHeap),
|
||||
})?;
|
||||
self.annotate_call_result_from_func_name(dst, &stringify_name);
|
||||
return Ok(dst);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Try alternate naming: <Class>Instance.method/Arity
|
||||
let alt_cls = format!("{}Instance", cls);
|
||||
let alt_fname = crate::mir::builder::calls::function_lowering::generate_method_function_name(&alt_cls, &method, arity);
|
||||
let alt_exists = if let Some(ref module) = self.current_module {
|
||||
module.functions.contains_key(&alt_fname)
|
||||
} else { false };
|
||||
if alt_exists {
|
||||
if super::utils::builder_debug_enabled() || std::env::var("NYASH_BUILDER_DEBUG").ok().as_deref() == Some("1") {
|
||||
super::utils::builder_debug_log(&format!("userbox method-call alt cls={} method={} fname={}", alt_cls, method, alt_fname));
|
||||
}
|
||||
let name_const = self.value_gen.next();
|
||||
self.emit_instruction(MirInstruction::Const {
|
||||
dst: name_const,
|
||||
value: crate::mir::builder::ConstValue::String(alt_fname.clone()),
|
||||
})?;
|
||||
let mut call_args = Vec::with_capacity(arity + 1);
|
||||
call_args.push(object_value); // 'me'
|
||||
call_args.extend(arg_values.into_iter());
|
||||
let dst = self.value_gen.next();
|
||||
self.emit_instruction(MirInstruction::Call {
|
||||
dst: Some(dst),
|
||||
func: name_const,
|
||||
callee: None,
|
||||
args: call_args,
|
||||
effects: crate::mir::EffectMask::READ.add(crate::mir::Effect::ReadHeap),
|
||||
})?;
|
||||
self.annotate_call_result_from_func_name(dst, &alt_fname);
|
||||
return Ok(dst);
|
||||
} else if super::utils::builder_debug_enabled() || std::env::var("NYASH_BUILDER_DEBUG").ok().as_deref() == Some("1") {
|
||||
super::utils::builder_debug_log(&format!("skip rewrite (no fn): cls={} method={} fname={} alt={} (missing)", cls, method, fname, alt_fname));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback: if exactly one user-defined method matches by name/arity across module, resolve to that
|
||||
// Fallback (narrowed): only when receiver class is known, and exactly one
|
||||
// user-defined method matches by name/arity across module, resolve to that.
|
||||
if rewrite_enabled && class_name_opt.is_some() {
|
||||
if let Some(ref module) = self.current_module {
|
||||
let tail = format!(".{}{}", method, format!("/{}", arg_values.len()));
|
||||
let mut cands: Vec<String> = module
|
||||
@ -155,7 +252,7 @@ impl MirBuilder {
|
||||
let name_const = self.value_gen.next();
|
||||
self.emit_instruction(MirInstruction::Const {
|
||||
dst: name_const,
|
||||
value: crate::mir::builder::ConstValue::String(fname),
|
||||
value: crate::mir::builder::ConstValue::String(fname.clone()),
|
||||
})?;
|
||||
let mut call_args = Vec::with_capacity(arg_values.len() + 1);
|
||||
call_args.push(object_value); // 'me'
|
||||
@ -168,11 +265,14 @@ impl MirBuilder {
|
||||
args: call_args,
|
||||
effects: crate::mir::EffectMask::READ.add(crate::mir::Effect::ReadHeap),
|
||||
})?;
|
||||
// Annotate from signature if present
|
||||
self.annotate_call_result_from_func_name(dst, &fname);
|
||||
return Ok(dst);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Else fall back to plugin/boxcall path
|
||||
let result_id = self.value_gen.next();
|
||||
|
||||
@ -17,20 +17,11 @@ impl MirBuilder {
|
||||
|
||||
/// Build a loop statement: loop(condition) { body }
|
||||
///
|
||||
/// Uses the shared LoopBuilder facade to avoid tight coupling.
|
||||
/// Force structured Loop-Form lowering (preheader → header(φ) → body → latch → header|exit)
|
||||
/// to ensure PHI correctness for loop-carried values.
|
||||
pub(super) fn build_loop_statement(&mut self, condition: ASTNode, body: Vec<ASTNode>) -> Result<ValueId, String> {
|
||||
// Evaluate condition first (boolean-ish value)
|
||||
let cond_val = self.build_expression(condition)?;
|
||||
|
||||
// Use loop_api helper with a closure that builds the loop body
|
||||
let mut body_builder = |lb: &mut Self| -> Result<(), String> {
|
||||
for stmt in &body {
|
||||
let _ = lb.build_expression(stmt.clone())?;
|
||||
}
|
||||
Ok(())
|
||||
};
|
||||
|
||||
crate::mir::loop_api::build_simple_loop(self, cond_val, &mut body_builder)
|
||||
// Delegate to the unified control-flow entry which uses LoopBuilder
|
||||
self.cf_loop(condition, body)
|
||||
}
|
||||
|
||||
/// Build a try/catch statement
|
||||
|
||||
@ -129,8 +129,15 @@ impl<'a> LoopBuilder<'a> {
|
||||
|
||||
// 1. ブロックの準備
|
||||
let preheader_id = self.current_block()?;
|
||||
let trace = std::env::var("NYASH_LOOP_TRACE").ok().as_deref() == Some("1");
|
||||
let (header_id, body_id, after_loop_id) =
|
||||
crate::mir::builder::loops::create_loop_blocks(self.parent_builder);
|
||||
if trace {
|
||||
eprintln!(
|
||||
"[loop] blocks preheader={:?} header={:?} body={:?} exit={:?}",
|
||||
preheader_id, header_id, body_id, after_loop_id
|
||||
);
|
||||
}
|
||||
self.loop_header = Some(header_id);
|
||||
self.continue_snapshots.clear();
|
||||
|
||||
@ -175,6 +182,12 @@ impl<'a> LoopBuilder<'a> {
|
||||
self.emit_branch(condition_value, body_id, after_loop_id)?;
|
||||
let _ = crate::mir::builder::loops::add_predecessor(self.parent_builder, body_id, header_id);
|
||||
let _ = crate::mir::builder::loops::add_predecessor(self.parent_builder, after_loop_id, header_id);
|
||||
if trace {
|
||||
eprintln!(
|
||||
"[loop] header branched to body={:?} and exit={:?}",
|
||||
body_id, after_loop_id
|
||||
);
|
||||
}
|
||||
|
||||
// 7. ループボディの構築
|
||||
self.set_current_block(body_id)?;
|
||||
@ -243,6 +256,12 @@ impl<'a> LoopBuilder<'a> {
|
||||
|
||||
// 9. Headerブロックをシール(全predecessors確定)
|
||||
self.seal_block(header_id, latch_id)?;
|
||||
if trace {
|
||||
eprintln!(
|
||||
"[loop] sealed header={:?} with latch={:?}",
|
||||
header_id, latch_id
|
||||
);
|
||||
}
|
||||
|
||||
// 10. ループ後の処理 - Exit PHI生成
|
||||
self.set_current_block(after_loop_id)?;
|
||||
@ -256,7 +275,9 @@ impl<'a> LoopBuilder<'a> {
|
||||
// void値を返す
|
||||
let void_dst = self.new_value();
|
||||
self.emit_const(void_dst, ConstValue::Void)?;
|
||||
|
||||
if trace {
|
||||
eprintln!("[loop] exit={:?} return void=%{:?}", after_loop_id, void_dst);
|
||||
}
|
||||
Ok(void_dst)
|
||||
}
|
||||
|
||||
@ -506,17 +527,31 @@ impl<'a> LoopBuilder<'a> {
|
||||
|
||||
// Capture pre-if variable map (used for phi normalization)
|
||||
let pre_if_var_map = self.get_current_variable_map();
|
||||
let trace_if = std::env::var("NYASH_IF_TRACE").ok().as_deref() == Some("1");
|
||||
// (legacy) kept for earlier merge style; now unified helpers compute deltas directly.
|
||||
|
||||
// then branch
|
||||
self.set_current_block(then_bb)?;
|
||||
// Materialize all variables at entry via single-pred Phi (correctness-first)
|
||||
let names_then: Vec<String> = self.parent_builder.variable_map.keys().cloned().collect();
|
||||
let names_then: Vec<String> = self
|
||||
.parent_builder
|
||||
.variable_map
|
||||
.keys()
|
||||
.filter(|n| !n.starts_with("__pin$"))
|
||||
.cloned()
|
||||
.collect();
|
||||
for name in names_then {
|
||||
if let Some(&pre_v) = self.parent_builder.variable_map.get(&name) {
|
||||
if let Some(&pre_v) = pre_if_var_map.get(&name) {
|
||||
let phi_val = self.new_value();
|
||||
self.emit_phi_at_block_start(then_bb, phi_val, vec![(pre_branch_bb, pre_v)])?;
|
||||
let name_for_log = name.clone();
|
||||
self.update_variable(name, phi_val);
|
||||
if trace_if {
|
||||
eprintln!(
|
||||
"[if-trace] then-entry phi var={} pre={:?} -> dst={:?}",
|
||||
name_for_log, pre_v, phi_val
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
for s in then_body.iter().cloned() {
|
||||
@ -536,12 +571,25 @@ impl<'a> LoopBuilder<'a> {
|
||||
// else branch
|
||||
self.set_current_block(else_bb)?;
|
||||
// Materialize all variables at entry via single-pred Phi (correctness-first)
|
||||
let names2: Vec<String> = self.parent_builder.variable_map.keys().cloned().collect();
|
||||
let names2: Vec<String> = self
|
||||
.parent_builder
|
||||
.variable_map
|
||||
.keys()
|
||||
.filter(|n| !n.starts_with("__pin$"))
|
||||
.cloned()
|
||||
.collect();
|
||||
for name in names2 {
|
||||
if let Some(&pre_v) = self.parent_builder.variable_map.get(&name) {
|
||||
if let Some(&pre_v) = pre_if_var_map.get(&name) {
|
||||
let phi_val = self.new_value();
|
||||
self.emit_phi_at_block_start(else_bb, phi_val, vec![(pre_branch_bb, pre_v)])?;
|
||||
let name_for_log = name.clone();
|
||||
self.update_variable(name, phi_val);
|
||||
if trace_if {
|
||||
eprintln!(
|
||||
"[if-trace] else-entry phi var={} pre={:?} -> dst={:?}",
|
||||
name_for_log, pre_v, phi_val
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
let mut else_var_map_end_opt: Option<HashMap<String, ValueId>> = None;
|
||||
|
||||
@ -153,6 +153,7 @@ pub fn merge_modified_at_merge_with<O: PhiMergeOps>(
|
||||
else_map_end_opt: &Option<HashMap<String, ValueId>>,
|
||||
skip_var: Option<&str>,
|
||||
) -> Result<(), String> {
|
||||
let trace = std::env::var("NYASH_IF_TRACE").ok().as_deref() == Some("1");
|
||||
let changed = compute_modified_names(pre_if_snapshot, then_map_end, else_map_end_opt);
|
||||
for name in changed {
|
||||
if skip_var.map(|s| s == name).unwrap_or(false) {
|
||||
@ -168,6 +169,13 @@ pub fn merge_modified_at_merge_with<O: PhiMergeOps>(
|
||||
.and_then(|m| m.get(name.as_str()).copied())
|
||||
.unwrap_or(pre);
|
||||
|
||||
if trace {
|
||||
eprintln!(
|
||||
"[if-trace] merge var={} pre={:?} then_v={:?} else_v={:?} then_pred={:?} else_pred={:?}",
|
||||
name, pre, then_v, else_v, then_pred_opt, else_pred_opt
|
||||
);
|
||||
}
|
||||
|
||||
// Build incoming pairs from reachable predecessors only
|
||||
let mut inputs: Vec<(crate::mir::BasicBlockId, ValueId)> = Vec::new();
|
||||
if let Some(tp) = then_pred_opt { inputs.push((tp, then_v)); }
|
||||
@ -177,12 +185,24 @@ pub fn merge_modified_at_merge_with<O: PhiMergeOps>(
|
||||
0 => {}
|
||||
1 => {
|
||||
let (_pred, v) = inputs[0];
|
||||
if trace {
|
||||
eprintln!(
|
||||
"[if-trace] merge bind var={} v={:?} (single pred)",
|
||||
name, v
|
||||
);
|
||||
}
|
||||
ops.update_var(name, v);
|
||||
}
|
||||
_ => {
|
||||
ops.debug_verify_phi_inputs(merge_bb, &inputs);
|
||||
let dst = ops.new_value();
|
||||
ops.emit_phi_at_block_start(merge_bb, dst, inputs)?;
|
||||
if trace {
|
||||
eprintln!(
|
||||
"[if-trace] merge phi var={} dst={:?}",
|
||||
name, dst
|
||||
);
|
||||
}
|
||||
ops.update_var(name, dst);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user