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:
nyash-codex
2025-09-26 14:34:42 +09:00
parent ecd46161b3
commit cdf826cbe7
44 changed files with 6264 additions and 576 deletions

View File

@ -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

View File

@ -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)

View File

@ -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();

View File

@ -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();