mir: implement proper short-circuit lowering (&&/||) via branch+phi; vm: add NYASH_VM_TRACE exec/phi logs and reg_load diagnostics; vm-fallback: minimal Void guards (push/get_position/line/column), MapBox.birth no-op; smokes: filter builtin Array/Map plugin notices; docs: CURRENT_TASK updated
This commit is contained in:
@ -373,6 +373,22 @@ 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);
|
||||
}
|
||||
}
|
||||
// Propagate original error
|
||||
return Err(format!("Unresolved function: '{}'. {}", name, super::call_resolution::suggest_resolution(&name)));
|
||||
}
|
||||
|
||||
@ -46,15 +46,9 @@ pub fn resolve_call_target(
|
||||
return Ok(Callee::Extern(name.to_string()));
|
||||
}
|
||||
|
||||
// 5. Fallback: when inside a static box, treat bare `name()` as a static method of the box.
|
||||
// This helps scripts that omit the box qualifier inside the same static box scope.
|
||||
if let Some(box_name) = current_static_box {
|
||||
return Ok(Callee::Method {
|
||||
box_name: box_name.clone(),
|
||||
method: name.to_string(),
|
||||
receiver: None,
|
||||
});
|
||||
}
|
||||
// 5. Do not assume bare `name()` refers to current static box.
|
||||
// Leave it unresolved so caller can try static_method_index fallback
|
||||
// or report a clear unresolved error.
|
||||
|
||||
// 6. Resolution failed - prevent runtime string-based resolution
|
||||
Err(format!("Unresolved function: '{}'. {}", name, suggest_resolution(name)))
|
||||
|
||||
@ -3,15 +3,20 @@ use crate::ast::ASTNode;
|
||||
|
||||
// Lifecycle routines extracted from builder.rs
|
||||
impl super::MirBuilder {
|
||||
fn preindex_static_methods_from_ast(&mut self, node: &ASTNode) {
|
||||
/// Unified declaration indexing (Phase A): collect symbols before lowering
|
||||
/// - user_defined_boxes: non-static Box names (for NewBox birth() skip)
|
||||
/// - static_method_index: name -> [(BoxName, arity)] (for bare-call fallback)
|
||||
fn index_declarations(&mut self, node: &ASTNode) {
|
||||
match node {
|
||||
ASTNode::Program { statements, .. } => {
|
||||
for st in statements {
|
||||
self.preindex_static_methods_from_ast(st);
|
||||
self.index_declarations(st);
|
||||
}
|
||||
}
|
||||
ASTNode::BoxDeclaration { name, methods, is_static, .. } => {
|
||||
if *is_static {
|
||||
if !*is_static {
|
||||
self.user_defined_boxes.insert(name.clone());
|
||||
} else {
|
||||
for (mname, mast) in methods {
|
||||
if let ASTNode::FunctionDeclaration { params, .. } = mast {
|
||||
self.static_method_index
|
||||
@ -59,8 +64,88 @@ impl super::MirBuilder {
|
||||
pub(super) fn lower_root(&mut self, ast: ASTNode) -> Result<ValueId, String> {
|
||||
// Pre-index static methods to enable safe fallback for bare calls in using-prepended code
|
||||
let snapshot = ast.clone();
|
||||
self.preindex_static_methods_from_ast(&snapshot);
|
||||
self.build_expression(ast)
|
||||
// Phase A: collect declarations in one pass (symbols available to lowering)
|
||||
self.index_declarations(&snapshot);
|
||||
// Phase B: top-level program lowering with declaration-first pass
|
||||
match ast {
|
||||
ASTNode::Program { statements, .. } => {
|
||||
use crate::ast::ASTNode as N;
|
||||
// First pass: lower declarations (static boxes except Main, and instance boxes)
|
||||
let mut main_static: Option<(String, std::collections::HashMap<String, ASTNode>)> = None;
|
||||
for st in &statements {
|
||||
if let N::BoxDeclaration {
|
||||
name,
|
||||
methods,
|
||||
is_static,
|
||||
fields,
|
||||
constructors,
|
||||
weak_fields,
|
||||
..
|
||||
} = st
|
||||
{
|
||||
if *is_static {
|
||||
if name == "Main" {
|
||||
main_static = Some((name.clone(), methods.clone()));
|
||||
} else {
|
||||
// Lower all static methods into standalone functions: BoxName.method/Arity
|
||||
for (mname, mast) in methods.iter() {
|
||||
if let N::FunctionDeclaration { params, body, .. } = mast {
|
||||
let func_name = format!("{}.{}{}", name, mname, format!("/{}", params.len()));
|
||||
self.lower_static_method_as_function(func_name, params.clone(), body.clone())?;
|
||||
self.static_method_index
|
||||
.entry(mname.clone())
|
||||
.or_insert_with(Vec::new)
|
||||
.push((name.clone(), params.len()));
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Instance box: register type and lower instance methods/ctors as functions
|
||||
self.user_defined_boxes.insert(name.clone());
|
||||
self.build_box_declaration(
|
||||
name.clone(),
|
||||
methods.clone(),
|
||||
fields.clone(),
|
||||
weak_fields.clone(),
|
||||
)?;
|
||||
for (ctor_key, ctor_ast) in constructors.iter() {
|
||||
if let N::FunctionDeclaration { params, body, .. } = ctor_ast {
|
||||
let func_name = format!("{}.{}", name, ctor_key);
|
||||
self.lower_method_as_function(
|
||||
func_name,
|
||||
name.clone(),
|
||||
params.clone(),
|
||||
body.clone(),
|
||||
)?;
|
||||
}
|
||||
}
|
||||
for (mname, mast) in methods.iter() {
|
||||
if let N::FunctionDeclaration { params, body, is_static, .. } = mast {
|
||||
if !*is_static {
|
||||
let func_name = format!("{}.{}{}", name, mname, format!("/{}", params.len()));
|
||||
self.lower_method_as_function(
|
||||
func_name,
|
||||
name.clone(),
|
||||
params.clone(),
|
||||
body.clone(),
|
||||
)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Second pass: lower Main.main body as Program (entry). If absent, fall back to sequential block.
|
||||
if let Some((box_name, methods)) = main_static {
|
||||
self.build_static_main_box(box_name, methods)
|
||||
} else {
|
||||
// Fallback: sequential lowering (keeps legacy behavior for scripts without Main)
|
||||
self.cf_block(statements)
|
||||
}
|
||||
}
|
||||
other => self.build_expression(other),
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn finalize_module(
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
use super::{MirInstruction, MirType, ValueId};
|
||||
use crate::mir::loop_api::LoopBuilderApi; // for current_block()
|
||||
use crate::ast::{ASTNode, BinaryOperator};
|
||||
use crate::mir::{BinaryOp, CompareOp, TypeOpKind, UnaryOp};
|
||||
|
||||
@ -17,6 +18,11 @@ impl super::MirBuilder {
|
||||
operator: BinaryOperator,
|
||||
right: ASTNode,
|
||||
) -> Result<ValueId, String> {
|
||||
// Short-circuit logical ops: lower to control-flow so RHS is evaluated conditionally
|
||||
if matches!(operator, BinaryOperator::And | BinaryOperator::Or) {
|
||||
return self.build_logical_shortcircuit(left, operator, right);
|
||||
}
|
||||
|
||||
let lhs = self.build_expression(left)?;
|
||||
let rhs = self.build_expression(right)?;
|
||||
let dst = self.value_gen.next();
|
||||
@ -94,6 +100,169 @@ impl super::MirBuilder {
|
||||
Ok(dst)
|
||||
}
|
||||
|
||||
/// Lower logical && / || with proper short-circuit semantics.
|
||||
/// Result is a Bool, and RHS is only evaluated if needed.
|
||||
fn build_logical_shortcircuit(
|
||||
&mut self,
|
||||
left: ASTNode,
|
||||
operator: BinaryOperator,
|
||||
right: ASTNode,
|
||||
) -> Result<ValueId, String> {
|
||||
let is_and = matches!(operator, BinaryOperator::And);
|
||||
|
||||
// Evaluate LHS only once
|
||||
let lhs_val = self.build_expression(left)?;
|
||||
|
||||
// Prepare blocks
|
||||
let then_block = self.block_gen.next();
|
||||
let else_block = self.block_gen.next();
|
||||
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,
|
||||
})?;
|
||||
|
||||
// Snapshot variables before entering branches
|
||||
let pre_if_var_map = self.variable_map.clone();
|
||||
|
||||
// ---- THEN branch ----
|
||||
self.current_block = Some(then_block);
|
||||
self.ensure_block_exists(then_block)?;
|
||||
self.hint_scope_enter(0);
|
||||
// Reset scope to pre-if snapshot for clean deltas
|
||||
self.variable_map = pre_if_var_map.clone();
|
||||
|
||||
// AND: then → evaluate RHS and reduce to bool
|
||||
// OR: then → constant true
|
||||
let then_value_raw = if is_and {
|
||||
// Reduce arbitrary RHS to bool by branching on its truthiness and returning consts
|
||||
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.clone())?;
|
||||
self.emit_instruction(MirInstruction::Branch {
|
||||
condition: rhs_val,
|
||||
then_bb: rhs_true,
|
||||
else_bb: 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 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 rhs_false_exit = self.current_block()?;
|
||||
// join rhs result into a single bool
|
||||
self.start_new_block(rhs_join)?;
|
||||
let rhs_bool = self.value_gen.next();
|
||||
let inputs = vec![(rhs_true_exit, t_id), (rhs_false_exit, f_id)];
|
||||
if let (Some(func), Some(cur_bb)) = (&self.current_function, self.current_block) {
|
||||
crate::mir::phi_core::common::debug_verify_phi_inputs(func, cur_bb, &inputs);
|
||||
}
|
||||
self.emit_instruction(MirInstruction::Phi { dst: rhs_bool, inputs })?;
|
||||
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) })?;
|
||||
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 })?;
|
||||
}
|
||||
|
||||
// ---- ELSE branch ----
|
||||
self.current_block = Some(else_block);
|
||||
self.ensure_block_exists(else_block)?;
|
||||
self.hint_scope_enter(0);
|
||||
self.variable_map = pre_if_var_map.clone();
|
||||
// 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) })?;
|
||||
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,
|
||||
})?;
|
||||
// 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 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 rhs_false_exit = self.current_block()?;
|
||||
// join rhs result into a single bool
|
||||
self.start_new_block(rhs_join)?;
|
||||
let rhs_bool = self.value_gen.next();
|
||||
let inputs = vec![(rhs_true_exit, t_id), (rhs_false_exit, f_id)];
|
||||
if let (Some(func), Some(cur_bb)) = (&self.current_function, self.current_block) {
|
||||
crate::mir::phi_core::common::debug_verify_phi_inputs(func, cur_bb, &inputs);
|
||||
}
|
||||
self.emit_instruction(MirInstruction::Phi { dst: rhs_bool, inputs })?;
|
||||
self.value_types.insert(rhs_bool, MirType::Bool);
|
||||
rhs_bool
|
||||
};
|
||||
let else_exit_block = self.current_block()?;
|
||||
let else_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 })?;
|
||||
}
|
||||
|
||||
// ---- MERGE ----
|
||||
self.current_block = Some(merge_block);
|
||||
self.ensure_block_exists(merge_block)?;
|
||||
self.push_if_merge(merge_block);
|
||||
|
||||
// Result PHI (bool)
|
||||
let result_val = self.value_gen.next();
|
||||
let inputs = vec![(then_exit_block, then_value_raw), (else_exit_block, else_value_raw)];
|
||||
if let (Some(func), Some(cur_bb)) = (&self.current_function, self.current_block) {
|
||||
crate::mir::phi_core::common::debug_verify_phi_inputs(func, cur_bb, &inputs);
|
||||
}
|
||||
self.emit_instruction(MirInstruction::Phi { dst: result_val, inputs })?;
|
||||
self.value_types.insert(result_val, MirType::Bool);
|
||||
|
||||
// Merge modified vars from both branches back into current scope
|
||||
self.merge_modified_vars(
|
||||
then_block,
|
||||
else_block,
|
||||
then_exit_block,
|
||||
Some(else_exit_block),
|
||||
&pre_if_var_map,
|
||||
&then_var_map_end,
|
||||
&Some(else_var_map_end),
|
||||
None,
|
||||
)?;
|
||||
|
||||
self.pop_if_merge();
|
||||
Ok(result_val)
|
||||
}
|
||||
|
||||
// Build a unary operation
|
||||
pub(super) fn build_unary_op(
|
||||
&mut self,
|
||||
|
||||
Reference in New Issue
Block a user