stage3: unify to cleanup; MIR return-defer; docs+smokes updated; LLVM(harness): finalize_phis ownership, ret.py simplified, uses-predeclare; cleanup return override green; method-postfix cleanup return WIP (PHI head)
This commit is contained in:
108
src/archive/interpreter_legacy/mod.rs
Normal file
108
src/archive/interpreter_legacy/mod.rs
Normal file
@ -0,0 +1,108 @@
|
||||
/*!
|
||||
* Nyash Interpreter - Modular Rust Implementation
|
||||
*
|
||||
* Refactored from massive 2,633-line interpreter.rs into logical modules
|
||||
* Everything is Box philosophy with clean separation of concerns
|
||||
*/
|
||||
|
||||
// Import all necessary dependencies
|
||||
use crate::ast::{ASTNode, CatchClause};
|
||||
use crate::box_trait::{BoolBox, BoxCore, ErrorBox, NyashBox, StringBox, VoidBox};
|
||||
use crate::boxes::debug_box::DebugBox;
|
||||
use crate::boxes::math_box::MathBox;
|
||||
use crate::boxes::random_box::RandomBox;
|
||||
use crate::boxes::time_box::TimerBox;
|
||||
use crate::boxes::FutureBox;
|
||||
use crate::channel_box::ChannelBox;
|
||||
use crate::instance_v2::InstanceBox;
|
||||
|
||||
// WASM-specific Box types (conditionally included)
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
use crate::boxes::web::{WebCanvasBox, WebConsoleBox, WebDisplayBox};
|
||||
use crate::exception_box;
|
||||
use std::collections::HashMap;
|
||||
|
||||
// Module declarations
|
||||
mod async_methods;
|
||||
mod box_methods;
|
||||
mod calls;
|
||||
mod core;
|
||||
pub mod errors;
|
||||
mod eval;
|
||||
mod expressions;
|
||||
mod functions;
|
||||
mod io;
|
||||
mod math_methods;
|
||||
mod methods;
|
||||
mod methods_dispatch;
|
||||
pub mod objects;
|
||||
mod objects_basic_constructors;
|
||||
mod special_methods;
|
||||
pub mod state;
|
||||
mod statements;
|
||||
mod system_methods;
|
||||
pub mod utils;
|
||||
mod web_methods;
|
||||
|
||||
// Main interpreter implementation - will be moved from interpreter.rs
|
||||
pub use core::NyashInterpreter;
|
||||
pub use errors::RuntimeError;
|
||||
pub use state::SharedState;
|
||||
|
||||
/// 実行制御フロー
|
||||
#[derive(Debug)]
|
||||
pub enum ControlFlow {
|
||||
None,
|
||||
Break,
|
||||
Continue,
|
||||
Return(Box<dyn NyashBox>),
|
||||
Throw(Box<dyn NyashBox>),
|
||||
}
|
||||
|
||||
/// コンストラクタ実行コンテキスト
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ConstructorContext {
|
||||
pub class_name: String,
|
||||
pub parent_class: Option<String>,
|
||||
}
|
||||
|
||||
// Re-export core model so existing interpreter modules keep working
|
||||
pub use crate::core::model::BoxDeclaration;
|
||||
|
||||
/// 🔥 Static Box定義を保持する構造体
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct StaticBoxDefinition {
|
||||
pub name: String,
|
||||
pub fields: Vec<String>,
|
||||
pub methods: HashMap<String, ASTNode>,
|
||||
pub init_fields: Vec<String>,
|
||||
pub weak_fields: Vec<String>, // 🔗 weak修飾子が付いたフィールドのリスト
|
||||
pub static_init: Option<Vec<ASTNode>>, // static { } ブロック
|
||||
pub extends: Vec<String>, // 🚀 Multi-delegation: Changed from Option<String> to Vec<String>
|
||||
pub implements: Vec<String>,
|
||||
pub type_parameters: Vec<String>,
|
||||
/// 初期化状態
|
||||
pub initialization_state: StaticBoxState,
|
||||
}
|
||||
|
||||
/// 🔥 Static Box初期化状態
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum StaticBoxState {
|
||||
NotInitialized, // 未初期化
|
||||
Initializing, // 初期化中(循環参照検出用)
|
||||
Initialized, // 初期化完了
|
||||
}
|
||||
|
||||
/// 関数宣言を保持する構造体
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct FunctionDeclaration {
|
||||
pub name: String,
|
||||
pub params: Vec<String>,
|
||||
pub body: Vec<ASTNode>,
|
||||
}
|
||||
|
||||
// Re-export core interpreter types
|
||||
pub use core::*;
|
||||
|
||||
// Import and re-export stdlib for interpreter modules
|
||||
pub use crate::stdlib::BuiltinStdlib;
|
||||
@ -5,18 +5,24 @@
|
||||
// VM core types are always available
|
||||
pub mod vm_types;
|
||||
|
||||
// Legacy VM execution pipeline (feature-gated)
|
||||
// Legacy VM execution pipeline (feature-gated) — loaded from archive path
|
||||
#[cfg(feature = "vm-legacy")]
|
||||
#[path = "../archive/vm_legacy/vm.rs"]
|
||||
pub mod vm;
|
||||
#[cfg(feature = "vm-legacy")]
|
||||
#[path = "../archive/vm_legacy/vm_boxcall.rs"]
|
||||
pub mod vm_boxcall;
|
||||
#[cfg(feature = "vm-legacy")]
|
||||
#[path = "../archive/vm_legacy/vm_instructions/mod.rs"]
|
||||
pub mod vm_instructions;
|
||||
#[cfg(feature = "vm-legacy")]
|
||||
#[path = "../archive/vm_legacy/vm_phi.rs"]
|
||||
pub mod vm_phi;
|
||||
#[cfg(feature = "vm-legacy")]
|
||||
#[path = "../archive/vm_legacy/vm_stats.rs"]
|
||||
pub mod vm_stats;
|
||||
#[cfg(feature = "vm-legacy")]
|
||||
#[path = "../archive/vm_legacy/vm_values.rs"]
|
||||
pub mod vm_values;
|
||||
|
||||
// When vm-legacy is disabled, provide a compatibility shim module so
|
||||
@ -28,22 +34,30 @@ pub mod vm {
|
||||
// Phase 9.78h: VM split scaffolding (control_flow/dispatch/frame)
|
||||
pub mod abi_util; // Shared ABI/utility helpers
|
||||
#[cfg(feature = "vm-legacy")]
|
||||
#[path = "../archive/vm_legacy/control_flow.rs"]
|
||||
pub mod control_flow;
|
||||
#[cfg(feature = "vm-legacy")]
|
||||
#[path = "../archive/vm_legacy/dispatch.rs"]
|
||||
pub mod dispatch;
|
||||
#[cfg(feature = "vm-legacy")]
|
||||
#[path = "../archive/vm_legacy/frame.rs"]
|
||||
pub mod frame;
|
||||
pub mod gc_helpers;
|
||||
pub mod mir_interpreter;
|
||||
#[cfg(feature = "vm-legacy")]
|
||||
#[path = "../archive/vm_legacy/vm_control_flow.rs"]
|
||||
pub mod vm_control_flow;
|
||||
#[cfg(feature = "vm-legacy")]
|
||||
#[path = "../archive/vm_legacy/vm_exec.rs"]
|
||||
mod vm_exec; // A3: execution loop extracted
|
||||
#[cfg(feature = "vm-legacy")]
|
||||
#[path = "../archive/vm_legacy/vm_gc.rs"]
|
||||
mod vm_gc; // A3: GC roots & diagnostics extracted
|
||||
#[cfg(feature = "vm-legacy")]
|
||||
#[path = "../archive/vm_legacy/vm_methods.rs"]
|
||||
mod vm_methods; // A3-S1: method dispatch wrappers extracted
|
||||
#[cfg(feature = "vm-legacy")]
|
||||
#[path = "../archive/vm_legacy/vm_state.rs"]
|
||||
mod vm_state; // A3: state & basic helpers extracted // Lightweight MIR interpreter
|
||||
|
||||
#[cfg(feature = "wasm-backend")]
|
||||
|
||||
@ -34,6 +34,7 @@ use once_cell::sync::OnceCell;
|
||||
use std::sync::RwLock;
|
||||
|
||||
static GLOBAL_ENV: OnceCell<RwLock<NyashEnv>> = OnceCell::new();
|
||||
static PHI_ON_GATED_WARNED: OnceCell<()> = OnceCell::new();
|
||||
|
||||
pub fn current() -> NyashEnv {
|
||||
if let Some(lock) = GLOBAL_ENV.get() {
|
||||
@ -111,7 +112,24 @@ pub fn mir_no_phi() -> bool {
|
||||
match std::env::var("NYASH_MIR_NO_PHI").ok() {
|
||||
Some(v) => {
|
||||
let lv = v.to_ascii_lowercase();
|
||||
!(lv == "0" || lv == "false" || lv == "off")
|
||||
let requested_no_phi = !(lv == "0" || lv == "false" || lv == "off");
|
||||
if requested_no_phi {
|
||||
return true;
|
||||
}
|
||||
// PHI-on requested
|
||||
#[cfg(feature = "phi-legacy")]
|
||||
{
|
||||
return false;
|
||||
}
|
||||
#[cfg(not(feature = "phi-legacy"))]
|
||||
{
|
||||
if PHI_ON_GATED_WARNED.set(()).is_ok() {
|
||||
eprintln!(
|
||||
"[nyash] PHI-on requested but disabled in this build (missing 'phi-legacy' feature). Falling back to PHI-off."
|
||||
);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
// Default: ON for MIR13 stability (PHI generation off by default)
|
||||
None => true,
|
||||
@ -123,6 +141,13 @@ pub fn verify_allow_no_phi() -> bool {
|
||||
std::env::var("NYASH_VERIFY_ALLOW_NO_PHI").ok().as_deref() == Some("1") || mir_no_phi()
|
||||
}
|
||||
|
||||
/// Enable strict edge-copy policy verification in PHI-off mode.
|
||||
/// When enabled, merge blocks must receive merged values via predecessor copies only,
|
||||
/// and the merge block itself must not introduce a self-copy to the merged destination.
|
||||
pub fn verify_edge_copy_strict() -> bool {
|
||||
std::env::var("NYASH_VERIFY_EDGE_COPY_STRICT").ok().as_deref() == Some("1")
|
||||
}
|
||||
|
||||
// ---- LLVM harness toggle (llvmlite) ----
|
||||
pub fn llvm_use_harness() -> bool {
|
||||
std::env::var("NYASH_LLVM_USE_HARNESS").ok().as_deref() == Some("1")
|
||||
@ -237,6 +262,29 @@ pub fn gc_alloc_threshold() -> Option<u64> {
|
||||
std::env::var("NYASH_GC_ALLOC_THRESHOLD").ok()?.parse().ok()
|
||||
}
|
||||
|
||||
// ---- Cleanup (method-level postfix) policy toggles ----
|
||||
/// Allow `return` inside a cleanup block. Default: false (0)
|
||||
pub fn cleanup_allow_return() -> bool {
|
||||
match std::env::var("NYASH_CLEANUP_ALLOW_RETURN").ok() {
|
||||
Some(v) => {
|
||||
let lv = v.to_ascii_lowercase();
|
||||
!(lv == "0" || lv == "false" || lv == "off")
|
||||
}
|
||||
None => false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Allow `throw` inside a cleanup block. Default: false (0)
|
||||
pub fn cleanup_allow_throw() -> bool {
|
||||
match std::env::var("NYASH_CLEANUP_ALLOW_THROW").ok() {
|
||||
Some(v) => {
|
||||
let lv = v.to_ascii_lowercase();
|
||||
!(lv == "0" || lv == "false" || lv == "off")
|
||||
}
|
||||
None => false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Run a collection every N safepoints (if Some)
|
||||
pub fn gc_collect_sp_interval() -> Option<u64> {
|
||||
std::env::var("NYASH_GC_COLLECT_SP").ok()?.parse().ok()
|
||||
@ -332,6 +380,35 @@ pub fn selfhost_read_tmp() -> bool {
|
||||
pub fn ny_compiler_stage3() -> bool {
|
||||
std::env::var("NYASH_NY_COMPILER_STAGE3").ok().as_deref() == Some("1")
|
||||
}
|
||||
/// Core (Rust) parser Stage-3 gate
|
||||
/// When enabled, the Rust parser accepts Stage-3 surface (try/catch/finally, throw).
|
||||
/// Default is OFF to keep Stage-2 stable.
|
||||
pub fn parser_stage3() -> bool {
|
||||
std::env::var("NYASH_PARSER_STAGE3").ok().as_deref() == Some("1")
|
||||
}
|
||||
|
||||
/// Parser gate for Block‑Postfix Catch acceptance
|
||||
/// Enabled when either NYASH_BLOCK_CATCH=1 or Stage‑3 gate is on.
|
||||
/// Phase 15.5 allows parsing a standalone `{ ... }` block optionally followed by
|
||||
/// a single `catch (...) { ... }` and/or `finally { ... }`, which is folded into
|
||||
/// ASTNode::TryCatch with the preceding block as the try body.
|
||||
pub fn block_postfix_catch() -> bool {
|
||||
std::env::var("NYASH_BLOCK_CATCH").ok().as_deref() == Some("1") || parser_stage3()
|
||||
}
|
||||
|
||||
/// Bridge lowering: use Result-style try/throw lowering instead of MIR Catch/Throw
|
||||
/// When on, try/catch is lowered using structured blocks and direct jumps,
|
||||
/// without emitting MIR Throw/Catch. The thrown value is routed to catch via
|
||||
/// block parameters (PHI-off uses edge-copy).
|
||||
pub fn try_result_mode() -> bool {
|
||||
std::env::var("NYASH_TRY_RESULT_MODE").ok().as_deref() == Some("1")
|
||||
}
|
||||
|
||||
/// Parser gate for method-level postfix catch/finally acceptance on method definitions.
|
||||
/// Enabled when either NYASH_METHOD_CATCH=1 or Stage‑3 gate is on.
|
||||
pub fn method_catch() -> bool {
|
||||
std::env::var("NYASH_METHOD_CATCH").ok().as_deref() == Some("1") || parser_stage3()
|
||||
}
|
||||
pub fn ny_compiler_child_args() -> Option<String> {
|
||||
std::env::var("NYASH_NY_COMPILER_CHILD_ARGS").ok()
|
||||
}
|
||||
|
||||
@ -1,108 +1,6 @@
|
||||
/*!
|
||||
* Nyash Interpreter - Modular Rust Implementation
|
||||
*
|
||||
* Refactored from massive 2,633-line interpreter.rs into logical modules
|
||||
* Everything is Box philosophy with clean separation of concerns
|
||||
*/
|
||||
#![cfg(feature = "interpreter-legacy")]
|
||||
// Shim module to re-export legacy interpreter from archive path
|
||||
#[path = "../archive/interpreter_legacy/mod.rs"]
|
||||
mod legacy_interpreter_mod;
|
||||
pub use legacy_interpreter_mod::*;
|
||||
|
||||
// Import all necessary dependencies
|
||||
use crate::ast::{ASTNode, CatchClause};
|
||||
use crate::box_trait::{BoolBox, BoxCore, ErrorBox, NyashBox, StringBox, VoidBox};
|
||||
use crate::boxes::debug_box::DebugBox;
|
||||
use crate::boxes::math_box::MathBox;
|
||||
use crate::boxes::random_box::RandomBox;
|
||||
use crate::boxes::time_box::TimerBox;
|
||||
use crate::boxes::FutureBox;
|
||||
use crate::channel_box::ChannelBox;
|
||||
use crate::instance_v2::InstanceBox;
|
||||
|
||||
// WASM-specific Box types (conditionally included)
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
use crate::boxes::web::{WebCanvasBox, WebConsoleBox, WebDisplayBox};
|
||||
use crate::exception_box;
|
||||
use std::collections::HashMap;
|
||||
|
||||
// Module declarations
|
||||
mod async_methods;
|
||||
mod box_methods;
|
||||
mod calls;
|
||||
mod core;
|
||||
pub mod errors;
|
||||
mod eval;
|
||||
mod expressions;
|
||||
mod functions;
|
||||
mod io;
|
||||
mod math_methods;
|
||||
mod methods;
|
||||
mod methods_dispatch;
|
||||
pub mod objects;
|
||||
mod objects_basic_constructors;
|
||||
mod special_methods;
|
||||
pub mod state;
|
||||
mod statements;
|
||||
mod system_methods;
|
||||
pub mod utils;
|
||||
mod web_methods;
|
||||
|
||||
// Main interpreter implementation - will be moved from interpreter.rs
|
||||
pub use core::NyashInterpreter;
|
||||
pub use errors::RuntimeError;
|
||||
pub use state::SharedState;
|
||||
|
||||
/// 実行制御フロー
|
||||
#[derive(Debug)]
|
||||
pub enum ControlFlow {
|
||||
None,
|
||||
Break,
|
||||
Continue,
|
||||
Return(Box<dyn NyashBox>),
|
||||
Throw(Box<dyn NyashBox>),
|
||||
}
|
||||
|
||||
/// コンストラクタ実行コンテキスト
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ConstructorContext {
|
||||
pub class_name: String,
|
||||
pub parent_class: Option<String>,
|
||||
}
|
||||
|
||||
// Re-export core model so existing interpreter modules keep working
|
||||
pub use crate::core::model::BoxDeclaration;
|
||||
|
||||
/// 🔥 Static Box定義を保持する構造体
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct StaticBoxDefinition {
|
||||
pub name: String,
|
||||
pub fields: Vec<String>,
|
||||
pub methods: HashMap<String, ASTNode>,
|
||||
pub init_fields: Vec<String>,
|
||||
pub weak_fields: Vec<String>, // 🔗 weak修飾子が付いたフィールドのリスト
|
||||
pub static_init: Option<Vec<ASTNode>>, // static { } ブロック
|
||||
pub extends: Vec<String>, // 🚀 Multi-delegation: Changed from Option<String> to Vec<String>
|
||||
pub implements: Vec<String>,
|
||||
pub type_parameters: Vec<String>,
|
||||
/// 初期化状態
|
||||
pub initialization_state: StaticBoxState,
|
||||
}
|
||||
|
||||
/// 🔥 Static Box初期化状態
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum StaticBoxState {
|
||||
NotInitialized, // 未初期化
|
||||
Initializing, // 初期化中(循環参照検出用)
|
||||
Initialized, // 初期化完了
|
||||
}
|
||||
|
||||
/// 関数宣言を保持する構造体
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct FunctionDeclaration {
|
||||
pub name: String,
|
||||
pub params: Vec<String>,
|
||||
pub body: Vec<ASTNode>,
|
||||
}
|
||||
|
||||
// Re-export core interpreter types
|
||||
pub use core::*;
|
||||
|
||||
// Import and re-export stdlib for interpreter modules
|
||||
pub use crate::stdlib::BuiltinStdlib;
|
||||
|
||||
@ -9,7 +9,8 @@ from typing import Dict, Optional
|
||||
def lower_barrier(
|
||||
builder: ir.IRBuilder,
|
||||
barrier_type: str,
|
||||
ordering: Optional[str] = None
|
||||
ordering: Optional[str] = None,
|
||||
ctx=None,
|
||||
) -> None:
|
||||
"""
|
||||
Lower MIR Barrier instruction
|
||||
|
||||
@ -5,6 +5,7 @@ Experimental loop normalization following paper-e-loop-signal-ir
|
||||
|
||||
import os
|
||||
import llvmlite.ir as ir
|
||||
from phi_wiring import phi_at_block_head
|
||||
from dataclasses import dataclass
|
||||
from typing import Dict, Tuple, List, Optional, Any
|
||||
from instructions.safepoint import insert_automatic_safepoint
|
||||
@ -109,9 +110,9 @@ def lower_while_loopform(
|
||||
i8 = ir.IntType(8)
|
||||
i64 = ir.IntType(64)
|
||||
|
||||
# Create PHI nodes
|
||||
tag_phi = builder.phi(i8, name=f"lf{loop_id}_tag")
|
||||
payload_phi = builder.phi(i64, name=f"lf{loop_id}_payload")
|
||||
# Create PHI nodes at the block head (LLVM requires PHIs grouped at top)
|
||||
tag_phi = phi_at_block_head(lf.dispatch, i8, name=f"lf{loop_id}_tag")
|
||||
payload_phi = phi_at_block_head(lf.dispatch, i64, name=f"lf{loop_id}_payload")
|
||||
|
||||
# Add incoming values
|
||||
# From header (condition false): Break signal
|
||||
|
||||
@ -4,6 +4,7 @@ Critical for SSA form - handles value merging from different control flow paths
|
||||
"""
|
||||
|
||||
import llvmlite.ir as ir
|
||||
from phi_wiring import phi_at_block_head
|
||||
from typing import Dict, List, Tuple, Optional
|
||||
|
||||
def lower_phi(
|
||||
@ -123,8 +124,8 @@ def lower_phi(
|
||||
vmap[dst_vid] = ir.Constant(phi_type, 0)
|
||||
return
|
||||
|
||||
# Create PHI instruction now and add incoming
|
||||
phi = builder.phi(phi_type, name=f"phi_{dst_vid}")
|
||||
# Create PHI instruction at the block head and add incoming
|
||||
phi = phi_at_block_head(current_block, phi_type, name=f"phi_{dst_vid}")
|
||||
for block, val in incoming_pairs:
|
||||
phi.add_incoming(val, block)
|
||||
|
||||
|
||||
@ -45,83 +45,73 @@ def lower_return(
|
||||
else:
|
||||
# Get return value (prefer resolver)
|
||||
ret_val = None
|
||||
if resolver is not None and preds is not None and block_end_values is not None and bb_map is not None:
|
||||
# Fast path: if vmap has a concrete non-PHI value defined in this block, use it directly
|
||||
if isinstance(value_id, int):
|
||||
tmp0 = vmap.get(value_id)
|
||||
try:
|
||||
# If this block has a declared PHI for the return value, force using the
|
||||
# local PHI placeholder to ensure dominance and let finalize_phis wire it.
|
||||
try:
|
||||
block_name = builder.block.name
|
||||
cur_bid = int(str(block_name).replace('bb',''))
|
||||
except Exception:
|
||||
cur_bid = -1
|
||||
try:
|
||||
bm = getattr(resolver, 'block_phi_incomings', {}) or {}
|
||||
except Exception:
|
||||
bm = {}
|
||||
if isinstance(value_id, int) and isinstance(bm.get(cur_bid), dict) and value_id in bm.get(cur_bid):
|
||||
# Reuse predeclared ret-phi when available
|
||||
cur = None
|
||||
is_phi0 = hasattr(tmp0, 'add_incoming')
|
||||
except Exception:
|
||||
is_phi0 = False
|
||||
if tmp0 is not None and not is_phi0:
|
||||
ret_val = tmp0
|
||||
if ret_val is None:
|
||||
if resolver is not None and preds is not None and block_end_values is not None and bb_map is not None:
|
||||
# Multi-pred special case: construct a PHI at block head and wire per-pred values
|
||||
# to keep PHIs grouped at top and avoid late synthesis that violates ordering.
|
||||
cur_bid = int(str(builder.block.name).replace('bb','')) if hasattr(builder.block, 'name') else -1
|
||||
pred_ids = [p for p in preds.get(cur_bid, []) if p != cur_bid] if isinstance(preds, dict) else []
|
||||
if isinstance(value_id, int) and len(pred_ids) > 1 and isinstance(return_type, ir.IntType):
|
||||
# Create PHI at block head
|
||||
btop = ir.IRBuilder(builder.block)
|
||||
try:
|
||||
rp = getattr(resolver, 'ret_phi_map', {}) or {}
|
||||
key = (int(cur_bid), int(value_id))
|
||||
if key in rp:
|
||||
cur = rp[key]
|
||||
except Exception:
|
||||
cur = None
|
||||
if cur is None:
|
||||
btop = ir.IRBuilder(builder.block)
|
||||
try:
|
||||
btop.position_at_start(builder.block)
|
||||
except Exception:
|
||||
pass
|
||||
# Reuse existing local phi if present; otherwise create
|
||||
cur = vmap.get(value_id)
|
||||
need_new = True
|
||||
try:
|
||||
need_new = not (cur is not None and hasattr(cur, 'add_incoming') and getattr(getattr(cur, 'basic_block', None), 'name', None) == builder.block.name)
|
||||
except Exception:
|
||||
need_new = True
|
||||
if need_new:
|
||||
cur = btop.phi(ir.IntType(64), name=f"phi_ret_{value_id}")
|
||||
# Bind to maps
|
||||
vmap[value_id] = cur
|
||||
try:
|
||||
if hasattr(resolver, 'global_vmap') and isinstance(resolver.global_vmap, dict):
|
||||
resolver.global_vmap[value_id] = cur
|
||||
btop.position_at_start(builder.block)
|
||||
except Exception:
|
||||
pass
|
||||
ret_val = cur
|
||||
if ret_val is not None:
|
||||
builder.ret(ret_val)
|
||||
return
|
||||
if isinstance(return_type, ir.PointerType):
|
||||
ret_val = resolver.resolve_ptr(value_id, builder.block, preds, block_end_values, vmap)
|
||||
ph = btop.phi(return_type, name=f"res_phi_{value_id}_{cur_bid}")
|
||||
# Wire per-pred end values
|
||||
for pred_bid in pred_ids:
|
||||
pred_bb = bb_map.get(pred_bid) if isinstance(bb_map, dict) else None
|
||||
if pred_bb is None:
|
||||
continue
|
||||
val = resolver._value_at_end_i64(value_id, pred_bid, preds, block_end_values, vmap, bb_map)
|
||||
if not hasattr(val, 'type'):
|
||||
val = ir.Constant(return_type, 0)
|
||||
ph.add_incoming(val, pred_bb)
|
||||
ret_val = ph
|
||||
else:
|
||||
# Prefer pointer→handle reboxing for string-ish returns even if function return type is i64
|
||||
is_stringish = False
|
||||
if hasattr(resolver, 'is_stringish'):
|
||||
try:
|
||||
is_stringish = resolver.is_stringish(int(value_id))
|
||||
except Exception:
|
||||
is_stringish = False
|
||||
if is_stringish and hasattr(resolver, 'string_ptrs') and int(value_id) in getattr(resolver, 'string_ptrs'):
|
||||
# Re-box known string pointer to handle
|
||||
p = resolver.string_ptrs[int(value_id)]
|
||||
i8p = ir.IntType(8).as_pointer()
|
||||
i64 = ir.IntType(64)
|
||||
boxer = None
|
||||
for f in builder.module.functions:
|
||||
if f.name == 'nyash.box.from_i8_string':
|
||||
boxer = f; break
|
||||
if boxer is None:
|
||||
boxer = ir.Function(builder.module, ir.FunctionType(i64, [i8p]), name='nyash.box.from_i8_string')
|
||||
ret_val = builder.call(boxer, [p], name='ret_ptr2h')
|
||||
# Resolve direct value
|
||||
if isinstance(return_type, ir.PointerType):
|
||||
ret_val = resolver.resolve_ptr(value_id, builder.block, preds, block_end_values, vmap)
|
||||
else:
|
||||
ret_val = resolver.resolve_i64(value_id, builder.block, preds, block_end_values, vmap, bb_map)
|
||||
except Exception:
|
||||
ret_val = None
|
||||
is_stringish = False
|
||||
if hasattr(resolver, 'is_stringish'):
|
||||
try:
|
||||
is_stringish = resolver.is_stringish(int(value_id))
|
||||
except Exception:
|
||||
is_stringish = False
|
||||
if is_stringish and hasattr(resolver, 'string_ptrs') and int(value_id) in getattr(resolver, 'string_ptrs'):
|
||||
p = resolver.string_ptrs[int(value_id)]
|
||||
i8p = ir.IntType(8).as_pointer()
|
||||
i64 = ir.IntType(64)
|
||||
boxer = None
|
||||
for f in builder.module.functions:
|
||||
if f.name == 'nyash.box.from_i8_string':
|
||||
boxer = f; break
|
||||
if boxer is None:
|
||||
boxer = ir.Function(builder.module, ir.FunctionType(i64, [i8p]), name='nyash.box.from_i8_string')
|
||||
ret_val = builder.call(boxer, [p], name='ret_ptr2h')
|
||||
else:
|
||||
ret_val = resolver.resolve_i64(value_id, builder.block, preds, block_end_values, vmap, bb_map)
|
||||
|
||||
if ret_val is None:
|
||||
ret_val = vmap.get(value_id)
|
||||
# Default to vmap (non-PHI) if available
|
||||
tmp = vmap.get(value_id)
|
||||
try:
|
||||
is_phi = hasattr(tmp, 'add_incoming')
|
||||
except Exception:
|
||||
is_phi = False
|
||||
if tmp is not None and not is_phi:
|
||||
ret_val = tmp
|
||||
if not ret_val:
|
||||
# Default based on return type
|
||||
if isinstance(return_type, ir.IntType):
|
||||
|
||||
@ -32,6 +32,12 @@ from instructions.controlflow.while_ import lower_while_regular
|
||||
from phi_wiring import setup_phi_placeholders as _setup_phi_placeholders, finalize_phis as _finalize_phis
|
||||
from trace import debug as trace_debug
|
||||
from trace import phi as trace_phi
|
||||
try:
|
||||
# Structured JSON trace for PHI wiring (shared with phi_wiring)
|
||||
from phi_wiring.common import trace as trace_phi_json
|
||||
except Exception:
|
||||
def trace_phi_json(_msg):
|
||||
pass
|
||||
from prepass.loops import detect_simple_while
|
||||
from prepass.if_merge import plan_ret_phi_predeclare
|
||||
from build_ctx import BuildCtx
|
||||
@ -397,6 +403,90 @@ class NyashLLVMBuilder:
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# Predeclare PHIs for values used in a block but defined in predecessors (multi-pred only).
|
||||
# This keeps PHI nodes grouped at the top and avoids late synthesis during operand resolution.
|
||||
try:
|
||||
from cfg.utils import build_preds_succs
|
||||
local_preds, _ = build_preds_succs(block_by_id)
|
||||
def _collect_defs(block):
|
||||
defs = set()
|
||||
for ins in block.get('instructions') or []:
|
||||
try:
|
||||
dstv = ins.get('dst')
|
||||
if isinstance(dstv, int):
|
||||
defs.add(int(dstv))
|
||||
except Exception:
|
||||
pass
|
||||
return defs
|
||||
def _collect_uses(block):
|
||||
uses = set()
|
||||
for ins in block.get('instructions') or []:
|
||||
# Minimal keys: lhs/rhs (binop), value (ret/copy), cond (branch), box_val (boxcall)
|
||||
for k in ('lhs','rhs','value','cond','box_val'):
|
||||
try:
|
||||
v = ins.get(k)
|
||||
if isinstance(v, int):
|
||||
uses.add(int(v))
|
||||
except Exception:
|
||||
pass
|
||||
return uses
|
||||
# Ensure map for declared incomings exists
|
||||
if not hasattr(self, 'block_phi_incomings') or self.block_phi_incomings is None:
|
||||
self.block_phi_incomings = {}
|
||||
for bid, blk in block_by_id.items():
|
||||
# Only multi-pred blocks need PHIs
|
||||
try:
|
||||
preds_raw = [p for p in local_preds.get(int(bid), []) if p != int(bid)]
|
||||
except Exception:
|
||||
preds_raw = []
|
||||
# Dedup preds preserve order
|
||||
seen = set(); preds_list = []
|
||||
for p in preds_raw:
|
||||
if p not in seen: preds_list.append(p); seen.add(p)
|
||||
if len(preds_list) <= 1:
|
||||
continue
|
||||
defs = _collect_defs(blk)
|
||||
uses = _collect_uses(blk)
|
||||
need = [u for u in uses if u not in defs]
|
||||
if not need:
|
||||
continue
|
||||
bb0 = self.bb_map.get(int(bid))
|
||||
if bb0 is None:
|
||||
continue
|
||||
b0 = ir.IRBuilder(bb0)
|
||||
try:
|
||||
b0.position_at_start(bb0)
|
||||
except Exception:
|
||||
pass
|
||||
for vid in need:
|
||||
# Skip if we already have a PHI mapped for (bid, vid)
|
||||
cur = self.vmap.get(int(vid))
|
||||
has_phi_here = False
|
||||
try:
|
||||
has_phi_here = (
|
||||
cur is not None and hasattr(cur, 'add_incoming') and
|
||||
getattr(getattr(cur, 'basic_block', None), 'name', None) == bb0.name
|
||||
)
|
||||
except Exception:
|
||||
has_phi_here = False
|
||||
if not has_phi_here:
|
||||
ph = b0.phi(self.i64, name=f"phi_{vid}")
|
||||
self.vmap[int(vid)] = ph
|
||||
# Record incoming metadata for finalize_phis (pred -> same vid)
|
||||
try:
|
||||
self.block_phi_incomings.setdefault(int(bid), {}).setdefault(int(vid), [])
|
||||
# Overwrite with dedup list of (pred, vid)
|
||||
self.block_phi_incomings[int(bid)][int(vid)] = [(int(p), int(vid)) for p in preds_list]
|
||||
except Exception:
|
||||
pass
|
||||
# Expose to resolver
|
||||
try:
|
||||
self.resolver.block_phi_incomings = self.block_phi_incomings
|
||||
except Exception:
|
||||
pass
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# Optional: simple loop prepass → synthesize a structured while body
|
||||
loop_plan = None
|
||||
try:
|
||||
@ -444,9 +534,19 @@ class NyashLLVMBuilder:
|
||||
try:
|
||||
# Use a clean per-while vmap context seeded from global placeholders
|
||||
self._current_vmap = dict(self.vmap)
|
||||
ok = lower_while_loopform(builder, func, cond_vid, body_insts,
|
||||
self.loop_count, self.vmap, self.bb_map,
|
||||
self.resolver, self.preds, self.block_end_values)
|
||||
ok = lower_while_loopform(
|
||||
builder,
|
||||
func,
|
||||
cond_vid,
|
||||
body_insts,
|
||||
self.loop_count,
|
||||
self.vmap,
|
||||
self.bb_map,
|
||||
self.resolver,
|
||||
self.preds,
|
||||
self.block_end_values,
|
||||
getattr(self, 'ctx', None),
|
||||
)
|
||||
except Exception:
|
||||
ok = False
|
||||
if not ok:
|
||||
@ -810,7 +910,11 @@ class NyashLLVMBuilder:
|
||||
try:
|
||||
import os
|
||||
keys = sorted(list(snap.keys()))
|
||||
trace_phi(f"[builder] snapshot bb{bid} keys={keys[:20]}...")
|
||||
# Emit structured snapshot event for up to first 20 keys
|
||||
try:
|
||||
trace_phi_json({"phi": "snapshot", "block": int(bid), "keys": [int(k) for k in keys[:20]]})
|
||||
except Exception:
|
||||
pass
|
||||
except Exception:
|
||||
pass
|
||||
# Record block-local definitions for lifetime hinting
|
||||
@ -941,7 +1045,7 @@ class NyashLLVMBuilder:
|
||||
|
||||
elif op == "barrier":
|
||||
barrier_type = inst.get("type", "memory")
|
||||
lower_barrier(builder, barrier_type)
|
||||
lower_barrier(builder, barrier_type, ctx=getattr(self, 'ctx', None))
|
||||
|
||||
elif op == "while":
|
||||
# Experimental LoopForm lowering
|
||||
@ -1001,7 +1105,10 @@ class NyashLLVMBuilder:
|
||||
for fr in from_list:
|
||||
succs.setdefault(fr, []).append(to_bid)
|
||||
for block_id, dst_map in (getattr(self, 'block_phi_incomings', {}) or {}).items():
|
||||
trace_phi(f"[finalize] bb{block_id} dsts={list(dst_map.keys())}")
|
||||
try:
|
||||
trace_phi_json({"phi": "finalize_begin", "block": int(block_id), "dsts": [int(k) for k in (dst_map or {}).keys()]})
|
||||
except Exception:
|
||||
pass
|
||||
bb = self.bb_map.get(block_id)
|
||||
if bb is None:
|
||||
continue
|
||||
@ -1011,7 +1118,10 @@ class NyashLLVMBuilder:
|
||||
except Exception:
|
||||
pass
|
||||
for dst_vid, incoming in (dst_map or {}).items():
|
||||
trace_phi(f"[finalize] dst v{dst_vid} incoming={incoming}")
|
||||
try:
|
||||
trace_phi_json({"phi": "finalize_dst", "block": int(block_id), "dst": int(dst_vid), "incoming": [(int(v), int(b)) for (b, v) in [(b, v) for (v, b) in (incoming or [])]]})
|
||||
except Exception:
|
||||
pass
|
||||
# Ensure placeholder exists at block head
|
||||
# Prefer predeclared ret-phi when available and force using it.
|
||||
predecl = getattr(self, 'predeclared_ret_phis', {}) if hasattr(self, 'predeclared_ret_phis') else {}
|
||||
@ -1038,7 +1148,10 @@ class NyashLLVMBuilder:
|
||||
phi = b.phi(self.i64, name=f"phi_{dst_vid}")
|
||||
self.vmap[dst_vid] = phi
|
||||
n = getattr(phi, 'name', b'').decode() if hasattr(getattr(phi, 'name', None), 'decode') else str(getattr(phi, 'name', ''))
|
||||
trace_phi(f"[finalize] target phi={n}")
|
||||
try:
|
||||
trace_phi_json({"phi": "finalize_target", "block": int(block_id), "dst": int(dst_vid), "ir": str(n)})
|
||||
except Exception:
|
||||
pass
|
||||
# Wire incoming per CFG predecessor; map src_vid when provided
|
||||
preds_raw = [p for p in self.preds.get(block_id, []) if p != block_id]
|
||||
# Deduplicate while preserving order
|
||||
@ -1214,8 +1327,7 @@ def main():
|
||||
if dummy:
|
||||
# Emit dummy ny_main
|
||||
ir_text = builder._create_dummy_main()
|
||||
if os.environ.get('NYASH_CLI_VERBOSE') == '1':
|
||||
print(f"[Python LLVM] Generated dummy IR:\n{ir_text}")
|
||||
trace_debug(f"[Python LLVM] Generated dummy IR:\n{ir_text}")
|
||||
builder.compile_to_object(output_file)
|
||||
print(f"Compiled to {output_file}")
|
||||
return
|
||||
@ -1229,8 +1341,7 @@ def main():
|
||||
mir_json = json.load(f)
|
||||
|
||||
llvm_ir = builder.build_from_mir(mir_json)
|
||||
if os.environ.get('NYASH_CLI_VERBOSE') == '1':
|
||||
print(f"[Python LLVM] Generated LLVM IR (see NYASH_LLVM_DUMP_IR or tmp/nyash_harness.ll)")
|
||||
trace_debug("[Python LLVM] Generated LLVM IR (see NYASH_LLVM_DUMP_IR or tmp/nyash_harness.ll)")
|
||||
|
||||
builder.compile_to_object(output_file)
|
||||
print(f"Compiled to {output_file}")
|
||||
|
||||
@ -1,270 +0,0 @@
|
||||
"""
|
||||
PHI wiring helpers
|
||||
|
||||
- setup_phi_placeholders: Predeclare PHIs and collect incoming metadata
|
||||
- finalize_phis: Wire PHI incomings using end-of-block snapshots and resolver
|
||||
|
||||
These operate on the NyashLLVMBuilder instance to keep changes minimal.
|
||||
|
||||
Refactor note: core responsibilities are split into smaller helpers so they
|
||||
can be unit-tested in isolation.
|
||||
"""
|
||||
|
||||
from typing import Dict, List, Any, Optional, Tuple
|
||||
import os
|
||||
import json
|
||||
import llvmlite.ir as ir
|
||||
|
||||
# ---- Small helpers (analyzable/testable) ----
|
||||
|
||||
def _collect_produced_stringish(blocks: List[Dict[str, Any]]) -> Dict[int, bool]:
|
||||
produced_str: Dict[int, bool] = {}
|
||||
for block_data in blocks:
|
||||
for inst in block_data.get("instructions", []) or []:
|
||||
try:
|
||||
opx = inst.get("op")
|
||||
dstx = inst.get("dst")
|
||||
if dstx is None:
|
||||
continue
|
||||
is_str = False
|
||||
if opx == "const":
|
||||
v = inst.get("value", {}) or {}
|
||||
t = v.get("type")
|
||||
if t == "string" or (isinstance(t, dict) and t.get("kind") in ("handle", "ptr") and t.get("box_type") == "StringBox"):
|
||||
is_str = True
|
||||
elif opx in ("binop", "boxcall", "externcall"):
|
||||
t = inst.get("dst_type")
|
||||
if isinstance(t, dict) and t.get("kind") == "handle" and t.get("box_type") == "StringBox":
|
||||
is_str = True
|
||||
if is_str:
|
||||
produced_str[int(dstx)] = True
|
||||
except Exception:
|
||||
pass
|
||||
return produced_str
|
||||
|
||||
def _trace(msg: Any):
|
||||
if os.environ.get("NYASH_LLVM_TRACE_PHI", "0") == "1":
|
||||
out = os.environ.get("NYASH_LLVM_TRACE_OUT")
|
||||
# Format as single-line JSON for machine parsing
|
||||
if not isinstance(msg, (str, bytes)):
|
||||
try:
|
||||
msg = json.dumps(msg, ensure_ascii=False, separators=(",", ":"))
|
||||
except Exception:
|
||||
msg = str(msg)
|
||||
if out:
|
||||
try:
|
||||
with open(out, "a", encoding="utf-8") as f:
|
||||
f.write(msg.rstrip() + "\n")
|
||||
except Exception:
|
||||
pass
|
||||
else:
|
||||
try:
|
||||
print(msg)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
def analyze_incomings(blocks: List[Dict[str, Any]]) -> Dict[int, Dict[int, List[Tuple[int, int]]]]:
|
||||
"""Return block_phi_incomings map: block_id -> { dst_vid -> [(decl_b, v_src), ...] }"""
|
||||
result: Dict[int, Dict[int, List[Tuple[int, int]]]] = {}
|
||||
for block_data in blocks:
|
||||
bid0 = block_data.get("id", 0)
|
||||
for inst in block_data.get("instructions", []) or []:
|
||||
if inst.get("op") == "phi":
|
||||
try:
|
||||
dst0 = int(inst.get("dst"))
|
||||
incoming0 = inst.get("incoming", []) or []
|
||||
except Exception:
|
||||
dst0 = None; incoming0 = []
|
||||
if dst0 is None:
|
||||
continue
|
||||
try:
|
||||
pairs = [(int(b), int(v)) for (v, b) in incoming0]
|
||||
result.setdefault(int(bid0), {})[dst0] = pairs
|
||||
_trace({
|
||||
"phi": "analyze",
|
||||
"block": int(bid0),
|
||||
"dst": dst0,
|
||||
"incoming": pairs,
|
||||
})
|
||||
except Exception:
|
||||
pass
|
||||
return result
|
||||
|
||||
def ensure_phi(builder, block_id: int, dst_vid: int, bb: ir.Block) -> ir.Instruction:
|
||||
"""Ensure a PHI placeholder exists at the block head for dst_vid and return it."""
|
||||
b = ir.IRBuilder(bb)
|
||||
try:
|
||||
b.position_at_start(bb)
|
||||
except Exception:
|
||||
pass
|
||||
# Prefer predeclared PHIs (e.g., from if-merge prepass)
|
||||
predecl = getattr(builder, 'predeclared_ret_phis', {}) if hasattr(builder, 'predeclared_ret_phis') else {}
|
||||
phi = predecl.get((int(block_id), int(dst_vid))) if predecl else None
|
||||
if phi is not None:
|
||||
builder.vmap[dst_vid] = phi
|
||||
_trace({"phi": "ensure_predecl", "block": int(block_id), "dst": int(dst_vid)})
|
||||
return phi
|
||||
# Reuse current if it is a PHI in the correct block
|
||||
cur = builder.vmap.get(dst_vid)
|
||||
try:
|
||||
if cur is not None and hasattr(cur, 'add_incoming') and getattr(getattr(cur, 'basic_block', None), 'name', None) == bb.name:
|
||||
return cur
|
||||
except Exception:
|
||||
pass
|
||||
# Create a new placeholder
|
||||
ph = b.phi(builder.i64, name=f"phi_{dst_vid}")
|
||||
builder.vmap[dst_vid] = ph
|
||||
_trace({"phi": "ensure_create", "block": int(block_id), "dst": int(dst_vid)})
|
||||
return ph
|
||||
|
||||
def _build_succs(preds: Dict[int, List[int]]) -> Dict[int, List[int]]:
|
||||
succs: Dict[int, List[int]] = {}
|
||||
for to_bid, from_list in (preds or {}).items():
|
||||
for fr in from_list:
|
||||
succs.setdefault(fr, []).append(to_bid)
|
||||
return succs
|
||||
|
||||
def _nearest_pred_on_path(succs: Dict[int, List[int]], preds_list: List[int], decl_b: int, target_bid: int) -> Optional[int]:
|
||||
from collections import deque
|
||||
q = deque([decl_b])
|
||||
visited = set([decl_b])
|
||||
parent: Dict[int, Any] = {decl_b: None}
|
||||
while q:
|
||||
cur = q.popleft()
|
||||
if cur == target_bid:
|
||||
par = parent.get(target_bid)
|
||||
return par if par in preds_list else None
|
||||
for nx in succs.get(cur, []):
|
||||
if nx not in visited:
|
||||
visited.add(nx)
|
||||
parent[nx] = cur
|
||||
q.append(nx)
|
||||
return None
|
||||
|
||||
def wire_incomings(builder, block_id: int, dst_vid: int, incoming: List[Tuple[int, int]]):
|
||||
"""Wire PHI incoming edges for (block_id, dst_vid) using declared (decl_b, v_src) pairs."""
|
||||
bb = builder.bb_map.get(block_id)
|
||||
if bb is None:
|
||||
return
|
||||
phi = ensure_phi(builder, block_id, dst_vid, bb)
|
||||
# Normalize predecessor list
|
||||
preds_raw = [p for p in builder.preds.get(block_id, []) if p != block_id]
|
||||
seen = set()
|
||||
preds_list: List[int] = []
|
||||
for p in preds_raw:
|
||||
if p not in seen:
|
||||
preds_list.append(p)
|
||||
seen.add(p)
|
||||
succs = _build_succs(builder.preds)
|
||||
# Precompute a non-self initial source for self-carry
|
||||
init_src_vid = None
|
||||
for (_bd0, vs0) in incoming:
|
||||
try:
|
||||
vi = int(vs0)
|
||||
except Exception:
|
||||
continue
|
||||
if vi != int(dst_vid):
|
||||
init_src_vid = vi
|
||||
break
|
||||
chosen: Dict[int, ir.Value] = {}
|
||||
for (b_decl, v_src) in incoming:
|
||||
try:
|
||||
bd = int(b_decl); vs = int(v_src)
|
||||
except Exception:
|
||||
continue
|
||||
pred_match = _nearest_pred_on_path(succs, preds_list, bd, block_id)
|
||||
if pred_match is None:
|
||||
_trace({
|
||||
"phi": "wire_skip_no_path",
|
||||
"decl_b": bd,
|
||||
"target": int(block_id),
|
||||
"src": vs,
|
||||
})
|
||||
continue
|
||||
if vs == int(dst_vid) and init_src_vid is not None:
|
||||
vs = int(init_src_vid)
|
||||
try:
|
||||
val = builder.resolver._value_at_end_i64(vs, pred_match, builder.preds, builder.block_end_values, builder.vmap, builder.bb_map)
|
||||
except Exception:
|
||||
val = None
|
||||
if val is None:
|
||||
val = ir.Constant(builder.i64, 0)
|
||||
chosen[pred_match] = val
|
||||
_trace({
|
||||
"phi": "wire_choose",
|
||||
"pred": int(pred_match),
|
||||
"dst": int(dst_vid),
|
||||
"src": int(vs),
|
||||
})
|
||||
for pred_bid, val in chosen.items():
|
||||
pred_bb = builder.bb_map.get(pred_bid)
|
||||
if pred_bb is None:
|
||||
continue
|
||||
phi.add_incoming(val, pred_bb)
|
||||
_trace({"phi": "add_incoming", "dst": int(dst_vid), "pred": int(pred_bid)})
|
||||
|
||||
# ---- Public API (used by llvm_builder) ----
|
||||
|
||||
def setup_phi_placeholders(builder, blocks: List[Dict[str, Any]]):
|
||||
"""Predeclare PHIs and collect incoming metadata for finalize_phis.
|
||||
|
||||
This pass is function-local and must be invoked after basic blocks are
|
||||
created and before lowering individual blocks. It also tags string-ish
|
||||
values eagerly to help downstream resolvers choose correct intrinsics.
|
||||
"""
|
||||
try:
|
||||
produced_str = _collect_produced_stringish(blocks)
|
||||
builder.block_phi_incomings = analyze_incomings(blocks)
|
||||
_trace({"phi": "setup", "produced_str_keys": list(produced_str.keys())})
|
||||
# Materialize placeholders and propagate stringish tags
|
||||
for block_data in blocks:
|
||||
bid0 = block_data.get("id", 0)
|
||||
bb0 = builder.bb_map.get(bid0)
|
||||
for inst in block_data.get("instructions", []) or []:
|
||||
if inst.get("op") != "phi":
|
||||
continue
|
||||
try:
|
||||
dst0 = int(inst.get("dst"))
|
||||
incoming0 = inst.get("incoming", []) or []
|
||||
except Exception:
|
||||
dst0 = None; incoming0 = []
|
||||
if dst0 is None or bb0 is None:
|
||||
continue
|
||||
_ = ensure_phi(builder, bid0, dst0, bb0)
|
||||
# Tag propagation
|
||||
try:
|
||||
dst_type0 = inst.get("dst_type")
|
||||
mark_str = isinstance(dst_type0, dict) and dst_type0.get("kind") == "handle" and dst_type0.get("box_type") == "StringBox"
|
||||
if not mark_str:
|
||||
for (_b_decl_i, v_src_i) in incoming0:
|
||||
try:
|
||||
if produced_str.get(int(v_src_i)):
|
||||
mark_str = True; break
|
||||
except Exception:
|
||||
pass
|
||||
if mark_str and hasattr(builder.resolver, 'mark_string'):
|
||||
builder.resolver.mark_string(int(dst0))
|
||||
except Exception:
|
||||
pass
|
||||
# Definition hint: PHI defines dst in this block
|
||||
try:
|
||||
builder.def_blocks.setdefault(int(dst0), set()).add(int(bid0))
|
||||
except Exception:
|
||||
pass
|
||||
# Sync to resolver
|
||||
try:
|
||||
builder.resolver.block_phi_incomings = builder.block_phi_incomings
|
||||
except Exception:
|
||||
pass
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def finalize_phis(builder):
|
||||
"""Finalize PHIs declared in JSON by wiring incoming edges at block heads.
|
||||
Uses resolver._value_at_end_i64 to materialize values at predecessor ends.
|
||||
"""
|
||||
for block_id, dst_map in (getattr(builder, 'block_phi_incomings', {}) or {}).items():
|
||||
for dst_vid, incoming in (dst_map or {}).items():
|
||||
wire_incomings(builder, int(block_id), int(dst_vid), incoming)
|
||||
_trace({"phi": "finalize", "block": int(block_id), "dst": int(dst_vid)})
|
||||
33
src/llvm_py/phi_wiring/__init__.py
Normal file
33
src/llvm_py/phi_wiring/__init__.py
Normal file
@ -0,0 +1,33 @@
|
||||
"""
|
||||
PHI wiring package
|
||||
|
||||
Submodules
|
||||
- analysis: analyze_incomings and stringish production scan
|
||||
- wiring: ensure_phi, wire_incomings, finalize_phis
|
||||
- tagging: setup_phi_placeholders (predeclare + tagging + sync)
|
||||
|
||||
This package re-exports the primary helpers for backward compatibility with
|
||||
`from phi_wiring import ...` and `from src.llvm_py import phi_wiring` usage.
|
||||
"""
|
||||
|
||||
from .analysis import analyze_incomings, collect_produced_stringish
|
||||
from .wiring import ensure_phi, wire_incomings, finalize_phis, build_succs, nearest_pred_on_path, phi_at_block_head
|
||||
from .tagging import setup_phi_placeholders
|
||||
|
||||
# Backward-compatible aliases for tests that used private helpers
|
||||
_build_succs = build_succs
|
||||
_nearest_pred_on_path = nearest_pred_on_path
|
||||
|
||||
__all__ = [
|
||||
"analyze_incomings",
|
||||
"collect_produced_stringish",
|
||||
"ensure_phi",
|
||||
"wire_incomings",
|
||||
"finalize_phis",
|
||||
"phi_at_block_head",
|
||||
"build_succs",
|
||||
"nearest_pred_on_path",
|
||||
"setup_phi_placeholders",
|
||||
"_build_succs",
|
||||
"_nearest_pred_on_path",
|
||||
]
|
||||
68
src/llvm_py/phi_wiring/analysis.py
Normal file
68
src/llvm_py/phi_wiring/analysis.py
Normal file
@ -0,0 +1,68 @@
|
||||
from __future__ import annotations
|
||||
from typing import Dict, List, Any, Tuple
|
||||
|
||||
from .common import trace
|
||||
|
||||
|
||||
def collect_produced_stringish(blocks: List[Dict[str, Any]]) -> Dict[int, bool]:
|
||||
produced_str: Dict[int, bool] = {}
|
||||
for block_data in blocks:
|
||||
for inst in block_data.get("instructions", []) or []:
|
||||
try:
|
||||
opx = inst.get("op")
|
||||
dstx = inst.get("dst")
|
||||
if dstx is None:
|
||||
continue
|
||||
is_str = False
|
||||
if opx == "const":
|
||||
v = inst.get("value", {}) or {}
|
||||
t = v.get("type")
|
||||
if t == "string" or (
|
||||
isinstance(t, dict)
|
||||
and t.get("kind") in ("handle", "ptr")
|
||||
and t.get("box_type") == "StringBox"
|
||||
):
|
||||
is_str = True
|
||||
elif opx in ("binop", "boxcall", "externcall"):
|
||||
t = inst.get("dst_type")
|
||||
if (
|
||||
isinstance(t, dict)
|
||||
and t.get("kind") == "handle"
|
||||
and t.get("box_type") == "StringBox"
|
||||
):
|
||||
is_str = True
|
||||
if is_str:
|
||||
produced_str[int(dstx)] = True
|
||||
except Exception:
|
||||
pass
|
||||
return produced_str
|
||||
|
||||
|
||||
def analyze_incomings(blocks: List[Dict[str, Any]]) -> Dict[int, Dict[int, List[Tuple[int, int]]]]:
|
||||
"""Return block_phi_incomings: block_id -> { dst_vid -> [(decl_b, v_src), ...] }"""
|
||||
result: Dict[int, Dict[int, List[Tuple[int, int]]]] = {}
|
||||
for block_data in blocks:
|
||||
bid0 = block_data.get("id", 0)
|
||||
for inst in block_data.get("instructions", []) or []:
|
||||
if inst.get("op") == "phi":
|
||||
try:
|
||||
dst0 = int(inst.get("dst"))
|
||||
incoming0 = inst.get("incoming", []) or []
|
||||
except Exception:
|
||||
dst0 = None
|
||||
incoming0 = []
|
||||
if dst0 is None:
|
||||
continue
|
||||
try:
|
||||
pairs = [(int(b), int(v)) for (v, b) in incoming0]
|
||||
result.setdefault(int(bid0), {})[dst0] = pairs
|
||||
trace({
|
||||
"phi": "analyze",
|
||||
"block": int(bid0),
|
||||
"dst": dst0,
|
||||
"incoming": pairs,
|
||||
})
|
||||
except Exception:
|
||||
pass
|
||||
return result
|
||||
|
||||
26
src/llvm_py/phi_wiring/common.py
Normal file
26
src/llvm_py/phi_wiring/common.py
Normal file
@ -0,0 +1,26 @@
|
||||
from __future__ import annotations
|
||||
from typing import Any
|
||||
import os
|
||||
import json
|
||||
|
||||
def trace(msg: Any):
|
||||
if os.environ.get("NYASH_LLVM_TRACE_PHI", "0") != "1":
|
||||
return
|
||||
out = os.environ.get("NYASH_LLVM_TRACE_OUT")
|
||||
if not isinstance(msg, (str, bytes)):
|
||||
try:
|
||||
msg = json.dumps(msg, ensure_ascii=False, separators=(",", ":"))
|
||||
except Exception:
|
||||
msg = str(msg)
|
||||
if out:
|
||||
try:
|
||||
with open(out, "a", encoding="utf-8") as f:
|
||||
f.write(msg.rstrip() + "\n")
|
||||
except Exception:
|
||||
pass
|
||||
else:
|
||||
try:
|
||||
print(msg)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
66
src/llvm_py/phi_wiring/tagging.py
Normal file
66
src/llvm_py/phi_wiring/tagging.py
Normal file
@ -0,0 +1,66 @@
|
||||
from __future__ import annotations
|
||||
from typing import Dict, List, Any
|
||||
|
||||
from .common import trace
|
||||
from .analysis import analyze_incomings, collect_produced_stringish
|
||||
from .wiring import ensure_phi
|
||||
|
||||
|
||||
def setup_phi_placeholders(builder, blocks: List[Dict[str, Any]]):
|
||||
"""Predeclare PHIs and collect incoming metadata for finalize_phis.
|
||||
|
||||
Function-local: must be invoked after basic blocks are created and before
|
||||
lowering individual blocks. Also tags string-ish values to help downstream
|
||||
resolvers.
|
||||
"""
|
||||
try:
|
||||
produced_str = collect_produced_stringish(blocks)
|
||||
builder.block_phi_incomings = analyze_incomings(blocks)
|
||||
trace({"phi": "setup", "produced_str_keys": list(produced_str.keys())})
|
||||
for block_data in blocks:
|
||||
bid0 = block_data.get("id", 0)
|
||||
bb0 = builder.bb_map.get(bid0)
|
||||
for inst in block_data.get("instructions", []) or []:
|
||||
if inst.get("op") != "phi":
|
||||
continue
|
||||
try:
|
||||
dst0 = int(inst.get("dst"))
|
||||
incoming0 = inst.get("incoming", []) or []
|
||||
except Exception:
|
||||
dst0 = None
|
||||
incoming0 = []
|
||||
if dst0 is None or bb0 is None:
|
||||
continue
|
||||
_ = ensure_phi(builder, bid0, dst0, bb0)
|
||||
# Tag propagation
|
||||
try:
|
||||
dst_type0 = inst.get("dst_type")
|
||||
mark_str = (
|
||||
isinstance(dst_type0, dict)
|
||||
and dst_type0.get("kind") == "handle"
|
||||
and dst_type0.get("box_type") == "StringBox"
|
||||
)
|
||||
if not mark_str:
|
||||
# JSON v0 incoming pairs are (value, block)
|
||||
for (v_src_i, _b_decl_i) in incoming0:
|
||||
try:
|
||||
if produced_str.get(int(v_src_i)):
|
||||
mark_str = True
|
||||
break
|
||||
except Exception:
|
||||
pass
|
||||
if mark_str and hasattr(builder.resolver, "mark_string"):
|
||||
builder.resolver.mark_string(int(dst0))
|
||||
except Exception:
|
||||
pass
|
||||
# Definition hint: PHI defines dst in this block
|
||||
try:
|
||||
builder.def_blocks.setdefault(int(dst0), set()).add(int(bid0))
|
||||
except Exception:
|
||||
pass
|
||||
try:
|
||||
builder.resolver.block_phi_incomings = builder.block_phi_incomings
|
||||
except Exception:
|
||||
pass
|
||||
except Exception:
|
||||
pass
|
||||
143
src/llvm_py/phi_wiring/wiring.py
Normal file
143
src/llvm_py/phi_wiring/wiring.py
Normal file
@ -0,0 +1,143 @@
|
||||
from __future__ import annotations
|
||||
from typing import Dict, List, Any, Optional, Tuple
|
||||
|
||||
import llvmlite.ir as ir
|
||||
|
||||
from .common import trace
|
||||
|
||||
|
||||
def ensure_phi(builder, block_id: int, dst_vid: int, bb: ir.Block) -> ir.Instruction:
|
||||
"""Ensure a PHI placeholder exists at the block head for dst_vid and return it."""
|
||||
b = ir.IRBuilder(bb)
|
||||
try:
|
||||
b.position_at_start(bb)
|
||||
except Exception:
|
||||
pass
|
||||
predecl = getattr(builder, "predeclared_ret_phis", {}) if hasattr(builder, "predeclared_ret_phis") else {}
|
||||
phi = predecl.get((int(block_id), int(dst_vid))) if predecl else None
|
||||
if phi is not None:
|
||||
builder.vmap[dst_vid] = phi
|
||||
trace({"phi": "ensure_predecl", "block": int(block_id), "dst": int(dst_vid)})
|
||||
return phi
|
||||
cur = builder.vmap.get(dst_vid)
|
||||
try:
|
||||
if cur is not None and hasattr(cur, "add_incoming") and getattr(getattr(cur, "basic_block", None), "name", None) == bb.name:
|
||||
return cur
|
||||
except Exception:
|
||||
pass
|
||||
ph = b.phi(builder.i64, name=f"phi_{dst_vid}")
|
||||
builder.vmap[dst_vid] = ph
|
||||
trace({"phi": "ensure_create", "block": int(block_id), "dst": int(dst_vid)})
|
||||
return ph
|
||||
|
||||
|
||||
def phi_at_block_head(block: ir.Block, ty: ir.Type, name: str | None = None) -> ir.Instruction:
|
||||
"""Create a PHI at the very start of `block` and return it.
|
||||
Keeps LLVM's requirement that PHI nodes are grouped at the top of a block.
|
||||
"""
|
||||
b = ir.IRBuilder(block)
|
||||
try:
|
||||
b.position_at_start(block)
|
||||
except Exception:
|
||||
pass
|
||||
return b.phi(ty, name=name) if name is not None else b.phi(ty)
|
||||
|
||||
|
||||
def build_succs(preds: Dict[int, List[int]]) -> Dict[int, List[int]]:
|
||||
succs: Dict[int, List[int]] = {}
|
||||
for to_bid, from_list in (preds or {}).items():
|
||||
for fr in from_list:
|
||||
succs.setdefault(fr, []).append(to_bid)
|
||||
return succs
|
||||
|
||||
|
||||
def nearest_pred_on_path(
|
||||
succs: Dict[int, List[int]], preds_list: List[int], decl_b: int, target_bid: int
|
||||
) -> Optional[int]:
|
||||
from collections import deque
|
||||
|
||||
q = deque([decl_b])
|
||||
visited = set([decl_b])
|
||||
parent: Dict[int, Any] = {decl_b: None}
|
||||
while q:
|
||||
cur = q.popleft()
|
||||
if cur == target_bid:
|
||||
par = parent.get(target_bid)
|
||||
return par if par in preds_list else None
|
||||
for nx in succs.get(cur, []):
|
||||
if nx not in visited:
|
||||
visited.add(nx)
|
||||
parent[nx] = cur
|
||||
q.append(nx)
|
||||
return None
|
||||
|
||||
|
||||
def wire_incomings(builder, block_id: int, dst_vid: int, incoming: List[Tuple[int, int]]):
|
||||
"""Wire PHI incoming edges for (block_id, dst_vid) using declared (decl_b, v_src) pairs."""
|
||||
bb = builder.bb_map.get(block_id)
|
||||
if bb is None:
|
||||
return
|
||||
phi = ensure_phi(builder, block_id, dst_vid, bb)
|
||||
preds_raw = [p for p in builder.preds.get(block_id, []) if p != block_id]
|
||||
seen = set()
|
||||
preds_list: List[int] = []
|
||||
for p in preds_raw:
|
||||
if p not in seen:
|
||||
preds_list.append(p)
|
||||
seen.add(p)
|
||||
succs = build_succs(builder.preds)
|
||||
init_src_vid = None
|
||||
for (_bd0, vs0) in incoming:
|
||||
try:
|
||||
vi = int(vs0)
|
||||
except Exception:
|
||||
continue
|
||||
if vi != int(dst_vid):
|
||||
init_src_vid = vi
|
||||
break
|
||||
chosen: Dict[int, ir.Value] = {}
|
||||
for (b_decl, v_src) in incoming:
|
||||
try:
|
||||
bd = int(b_decl)
|
||||
vs = int(v_src)
|
||||
except Exception:
|
||||
continue
|
||||
pred_match = nearest_pred_on_path(succs, preds_list, bd, block_id)
|
||||
if pred_match is None:
|
||||
trace({"phi": "wire_skip_no_path", "decl_b": bd, "target": int(block_id), "src": vs})
|
||||
continue
|
||||
if vs == int(dst_vid) and init_src_vid is not None:
|
||||
vs = int(init_src_vid)
|
||||
try:
|
||||
val = builder.resolver._value_at_end_i64(
|
||||
vs, pred_match, builder.preds, builder.block_end_values, builder.vmap, builder.bb_map
|
||||
)
|
||||
except Exception:
|
||||
val = None
|
||||
if val is None:
|
||||
val = ir.Constant(builder.i64, 0)
|
||||
chosen[pred_match] = val
|
||||
trace({"phi": "wire_choose", "pred": int(pred_match), "dst": int(dst_vid), "src": int(vs)})
|
||||
wired = 0
|
||||
for pred_bid, val in chosen.items():
|
||||
pred_bb = builder.bb_map.get(pred_bid)
|
||||
if pred_bb is None:
|
||||
continue
|
||||
phi.add_incoming(val, pred_bb)
|
||||
trace({"phi": "add_incoming", "dst": int(dst_vid), "pred": int(pred_bid)})
|
||||
wired += 1
|
||||
return wired
|
||||
|
||||
|
||||
def finalize_phis(builder):
|
||||
total_blocks = 0
|
||||
total_dsts = 0
|
||||
total_wired = 0
|
||||
for block_id, dst_map in (getattr(builder, "block_phi_incomings", {}) or {}).items():
|
||||
total_blocks += 1
|
||||
for dst_vid, incoming in (dst_map or {}).items():
|
||||
total_dsts += 1
|
||||
wired = wire_incomings(builder, int(block_id), int(dst_vid), incoming)
|
||||
total_wired += int(wired or 0)
|
||||
trace({"phi": "finalize", "block": int(block_id), "dst": int(dst_vid), "wired": int(wired or 0)})
|
||||
trace({"phi": "finalize_summary", "blocks": int(total_blocks), "dsts": int(total_dsts), "incoming_wired": int(total_wired)})
|
||||
@ -28,8 +28,20 @@ def plan_ret_phi_predeclare(block_by_id: Dict[int, Dict[str, Any]]) -> Optional[
|
||||
val = term.get('value')
|
||||
if not isinstance(val, int):
|
||||
continue
|
||||
# Heuristic: skip when the return value is freshly defined in this block
|
||||
# (e.g., returning a const computed in cleanup). Predeclaring a PHI for such
|
||||
# values is unnecessary and may violate PHI grouping/order.
|
||||
try:
|
||||
defined_here = False
|
||||
for ins in blk.get('instructions') or []:
|
||||
if isinstance(ins, dict) and ins.get('dst') == int(val):
|
||||
defined_here = True
|
||||
break
|
||||
if defined_here:
|
||||
continue
|
||||
except Exception:
|
||||
pass
|
||||
pred_list = [p for p in preds.get(int(bid), []) if p != int(bid)]
|
||||
if len(pred_list) > 1:
|
||||
plan[int(bid)] = int(val)
|
||||
return plan or None
|
||||
|
||||
|
||||
@ -222,37 +222,8 @@ class Resolver:
|
||||
placeholder = vmap.get(value_id)
|
||||
result = placeholder if (placeholder is not None and hasattr(placeholder, 'add_incoming')) else ir.Constant(self.i64, 0)
|
||||
else:
|
||||
# Synthesize a PHI to localize the value and dominate its uses.
|
||||
try:
|
||||
bb = bb_map.get(cur_bid) if isinstance(bb_map, dict) else current_block
|
||||
except Exception:
|
||||
bb = current_block
|
||||
b = ir.IRBuilder(bb)
|
||||
try:
|
||||
b.position_at_start(bb)
|
||||
except Exception:
|
||||
pass
|
||||
existing = vmap.get(value_id)
|
||||
if existing is not None and hasattr(existing, 'add_incoming'):
|
||||
phi = existing
|
||||
else:
|
||||
phi = b.phi(self.i64, name=f"res_phi_{value_id}_{cur_bid}")
|
||||
vmap[value_id] = phi
|
||||
# Wire end-of-block values from each predecessor
|
||||
for pred_bid in pred_ids:
|
||||
try:
|
||||
pred_bb = bb_map.get(pred_bid) if isinstance(bb_map, dict) else None
|
||||
except Exception:
|
||||
pred_bb = None
|
||||
val = self._value_at_end_i64(value_id, pred_bid, preds, block_end_values, vmap, bb_map)
|
||||
if pred_bb is None:
|
||||
# If we cannot map to a real basic block (shouldn't happen),
|
||||
# fallback to a zero to keep IR consistent.
|
||||
val = val if hasattr(val, 'type') else ir.Constant(self.i64, 0)
|
||||
# llvmlite requires a real BasicBlock; skip if missing.
|
||||
continue
|
||||
phi.add_incoming(val, pred_bb)
|
||||
result = phi
|
||||
# No declared PHI and multi-pred: do not synthesize; fallback to zero
|
||||
result = ir.Constant(self.i64, 0)
|
||||
|
||||
# Cache and return
|
||||
self.i64_cache[cache_key] = result
|
||||
|
||||
80
src/llvm_py/tests/test_phi_tagging.py
Normal file
80
src/llvm_py/tests/test_phi_tagging.py
Normal file
@ -0,0 +1,80 @@
|
||||
import unittest
|
||||
import llvmlite.ir as ir
|
||||
|
||||
from src.llvm_py.phi_wiring import setup_phi_placeholders
|
||||
|
||||
|
||||
class DummyResolver:
|
||||
def __init__(self):
|
||||
self.marked = set()
|
||||
|
||||
def mark_string(self, vid: int):
|
||||
self.marked.add(int(vid))
|
||||
|
||||
|
||||
class DummyBuilder:
|
||||
def __init__(self, bb_map):
|
||||
self.i64 = ir.IntType(64)
|
||||
self.vmap = {}
|
||||
self.def_blocks = {}
|
||||
self.resolver = DummyResolver()
|
||||
self.bb_map = bb_map
|
||||
|
||||
|
||||
class TestPhiTagging(unittest.TestCase):
|
||||
def _mk_blocks_and_bbs(self):
|
||||
mod = ir.Module(name="m")
|
||||
fnty = ir.FunctionType(ir.VoidType(), [])
|
||||
fn = ir.Function(mod, fnty, name="f")
|
||||
b0 = fn.append_basic_block(name="b0")
|
||||
b1 = fn.append_basic_block(name="b1")
|
||||
return mod, {0: b0, 1: b1}
|
||||
|
||||
def test_mark_by_dst_type(self):
|
||||
_mod, bb_map = self._mk_blocks_and_bbs()
|
||||
builder = DummyBuilder(bb_map)
|
||||
blocks = [
|
||||
{
|
||||
"id": 1,
|
||||
"instructions": [
|
||||
{
|
||||
"op": "phi",
|
||||
"dst": 42,
|
||||
"dst_type": {"kind": "handle", "box_type": "StringBox"},
|
||||
"incoming": [[7, 0]],
|
||||
}
|
||||
],
|
||||
}
|
||||
]
|
||||
setup_phi_placeholders(builder, blocks)
|
||||
self.assertIn(42, builder.resolver.marked)
|
||||
|
||||
def test_mark_by_incoming_stringish(self):
|
||||
_mod, bb_map = self._mk_blocks_and_bbs()
|
||||
builder = DummyBuilder(bb_map)
|
||||
blocks = [
|
||||
{
|
||||
"id": 0,
|
||||
"instructions": [
|
||||
{"op": "const", "dst": 7, "value": {"type": "string", "value": "hi"}}
|
||||
],
|
||||
},
|
||||
{
|
||||
"id": 1,
|
||||
"instructions": [
|
||||
{
|
||||
"op": "phi",
|
||||
"dst": 43,
|
||||
# no dst_type string; inference should happen via incoming
|
||||
"incoming": [[7, 0]],
|
||||
}
|
||||
],
|
||||
},
|
||||
]
|
||||
setup_phi_placeholders(builder, blocks)
|
||||
self.assertIn(43, builder.resolver.marked)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
||||
@ -14,28 +14,40 @@ Import and use:
|
||||
"""
|
||||
|
||||
import os
|
||||
import json
|
||||
|
||||
_TRACE_OUT = os.environ.get('NYASH_LLVM_TRACE_OUT')
|
||||
|
||||
def _write(msg: str) -> None:
|
||||
if _TRACE_OUT:
|
||||
try:
|
||||
with open(_TRACE_OUT, 'a', encoding='utf-8') as f:
|
||||
f.write(msg.rstrip() + "\n")
|
||||
return
|
||||
except Exception:
|
||||
pass
|
||||
try:
|
||||
print(msg, flush=True)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def _enabled(env_key: str) -> bool:
|
||||
return os.environ.get(env_key) == '1'
|
||||
|
||||
def debug(msg: str) -> None:
|
||||
if _enabled('NYASH_CLI_VERBOSE'):
|
||||
try:
|
||||
print(msg, flush=True)
|
||||
except Exception:
|
||||
pass
|
||||
_write(msg)
|
||||
|
||||
def phi(msg: str) -> None:
|
||||
def phi(msg) -> None:
|
||||
if _enabled('NYASH_LLVM_TRACE_PHI'):
|
||||
try:
|
||||
print(msg, flush=True)
|
||||
except Exception:
|
||||
pass
|
||||
# Accept raw strings or arbitrary objects; non-strings are JSON-encoded
|
||||
if not isinstance(msg, (str, bytes)):
|
||||
try:
|
||||
msg = json.dumps(msg, ensure_ascii=False, separators=(",", ":"))
|
||||
except Exception:
|
||||
msg = str(msg)
|
||||
_write(msg)
|
||||
|
||||
def values(msg: str) -> None:
|
||||
if _enabled('NYASH_LLVM_TRACE_VALUES'):
|
||||
try:
|
||||
print(msg, flush=True)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
_write(msg)
|
||||
|
||||
@ -25,6 +25,7 @@ mod fields; // field access/assignment lowering split
|
||||
pub(crate) mod loops;
|
||||
mod ops;
|
||||
mod phi;
|
||||
mod if_form;
|
||||
mod lifecycle; // prepare/lower_root/finalize split
|
||||
// legacy large-match remains inline for now (planned extraction)
|
||||
mod plugin_sigs; // plugin signature loader
|
||||
@ -89,8 +90,28 @@ pub struct MirBuilder {
|
||||
pub(super) loop_header_stack: Vec<BasicBlockId>,
|
||||
pub(super) loop_exit_stack: Vec<BasicBlockId>,
|
||||
|
||||
/// If/merge context stack (innermost first). Used to make merge targets explicit
|
||||
/// when lowering nested conditionals and to simplify jump generation.
|
||||
pub(super) if_merge_stack: Vec<BasicBlockId>,
|
||||
|
||||
/// Whether PHI emission is disabled (edge-copy mode)
|
||||
pub(super) no_phi_mode: bool,
|
||||
|
||||
// ---- Try/Catch/Cleanup lowering context ----
|
||||
/// When true, `return` statements are deferred: they assign to `return_defer_slot`
|
||||
/// and jump to `return_defer_target` (typically the cleanup/exit block).
|
||||
pub(super) return_defer_active: bool,
|
||||
/// Slot value to receive deferred return values (edge-copy mode friendly).
|
||||
pub(super) return_defer_slot: Option<ValueId>,
|
||||
/// Target block to jump to on deferred return.
|
||||
pub(super) return_defer_target: Option<BasicBlockId>,
|
||||
/// Set to true when a deferred return has been emitted in the current context.
|
||||
pub(super) return_deferred_emitted: bool,
|
||||
/// True while lowering the cleanup block.
|
||||
pub(super) in_cleanup_block: bool,
|
||||
/// Policy flags (snapshotted at entry of try/catch lowering)
|
||||
pub(super) cleanup_allow_return: bool,
|
||||
pub(super) cleanup_allow_throw: bool,
|
||||
}
|
||||
|
||||
impl MirBuilder {
|
||||
@ -117,10 +138,22 @@ impl MirBuilder {
|
||||
include_box_map: HashMap::new(),
|
||||
loop_header_stack: Vec::new(),
|
||||
loop_exit_stack: Vec::new(),
|
||||
if_merge_stack: Vec::new(),
|
||||
no_phi_mode,
|
||||
return_defer_active: false,
|
||||
return_defer_slot: None,
|
||||
return_defer_target: None,
|
||||
return_deferred_emitted: false,
|
||||
in_cleanup_block: false,
|
||||
cleanup_allow_return: false,
|
||||
cleanup_allow_throw: false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Push/pop helpers for If merge context (best-effort; optional usage)
|
||||
pub(super) fn push_if_merge(&mut self, bb: BasicBlockId) { self.if_merge_stack.push(bb); }
|
||||
pub(super) fn pop_if_merge(&mut self) { let _ = self.if_merge_stack.pop(); }
|
||||
|
||||
// moved to builder_calls.rs: lower_method_as_function
|
||||
|
||||
/// Build a complete MIR module from AST
|
||||
@ -795,6 +828,10 @@ impl MirBuilder {
|
||||
dst: ValueId,
|
||||
src: ValueId,
|
||||
) -> Result<(), String> {
|
||||
// No-op self copy guard to avoid useless instructions and accidental reordering issues
|
||||
if dst == src {
|
||||
return Ok(());
|
||||
}
|
||||
if let Some(ref mut function) = self.current_function {
|
||||
let block = function
|
||||
.get_block_mut(block_id)
|
||||
|
||||
102
src/mir/builder/if_form.rs
Normal file
102
src/mir/builder/if_form.rs
Normal file
@ -0,0 +1,102 @@
|
||||
use super::{ConstValue, MirBuilder, MirInstruction, ValueId};
|
||||
use crate::mir::loop_api::LoopBuilderApi; // for current_block()
|
||||
use crate::ast::ASTNode;
|
||||
|
||||
impl MirBuilder {
|
||||
/// Lower an if/else using a structured IfForm (header→then/else→merge).
|
||||
/// PHI-off: edge-copy only on predecessors; PHI-on: Phi at merge.
|
||||
pub(super) fn lower_if_form(
|
||||
&mut self,
|
||||
condition: ASTNode,
|
||||
then_branch: ASTNode,
|
||||
else_branch: Option<ASTNode>,
|
||||
) -> Result<ValueId, String> {
|
||||
let condition_val = self.build_expression(condition)?;
|
||||
|
||||
// Create blocks
|
||||
let then_block = self.block_gen.next();
|
||||
let else_block = self.block_gen.next();
|
||||
let merge_block = self.block_gen.next();
|
||||
|
||||
// Branch
|
||||
self.emit_instruction(MirInstruction::Branch {
|
||||
condition: condition_val,
|
||||
then_bb: then_block,
|
||||
else_bb: else_block,
|
||||
})?;
|
||||
|
||||
// Snapshot variables before entering branches
|
||||
let pre_if_var_map = self.variable_map.clone();
|
||||
|
||||
// then
|
||||
self.current_block = Some(then_block);
|
||||
self.ensure_block_exists(then_block)?;
|
||||
let then_ast_for_analysis = then_branch.clone();
|
||||
self.variable_map = pre_if_var_map.clone();
|
||||
let then_value_raw = self.build_expression(then_branch)?;
|
||||
let then_exit_block = self.current_block()?;
|
||||
let then_var_map_end = self.variable_map.clone();
|
||||
if !self.is_current_block_terminated() {
|
||||
self.emit_instruction(MirInstruction::Jump { target: merge_block })?;
|
||||
}
|
||||
|
||||
// else
|
||||
self.current_block = Some(else_block);
|
||||
self.ensure_block_exists(else_block)?;
|
||||
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();
|
||||
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 })?;
|
||||
(void_val, None, None)
|
||||
};
|
||||
let else_exit_block = self.current_block()?;
|
||||
if !self.is_current_block_terminated() {
|
||||
self.emit_instruction(MirInstruction::Jump { target: merge_block })?;
|
||||
}
|
||||
|
||||
// merge: primary result via helper, then delta-based variable merges
|
||||
self.current_block = Some(merge_block);
|
||||
self.ensure_block_exists(merge_block)?;
|
||||
self.push_if_merge(merge_block);
|
||||
|
||||
// Pre-analysis: identify then-assigned var for skip
|
||||
let assigned_then_pre = super::phi::extract_assigned_var(&then_ast_for_analysis);
|
||||
let pre_then_var_value = assigned_then_pre
|
||||
.as_ref()
|
||||
.and_then(|name| pre_if_var_map.get(name).copied());
|
||||
|
||||
let result_val = self.normalize_if_else_phi(
|
||||
then_block,
|
||||
else_block,
|
||||
Some(then_exit_block),
|
||||
Some(else_exit_block),
|
||||
then_value_raw,
|
||||
else_value_raw,
|
||||
&pre_if_var_map,
|
||||
&then_ast_for_analysis,
|
||||
&else_ast_for_analysis,
|
||||
&then_var_map_end,
|
||||
&else_var_map_end_opt,
|
||||
pre_then_var_value,
|
||||
)?;
|
||||
|
||||
// Merge other modified variables (skip the primary assignment if any)
|
||||
let skip_name = assigned_then_pre.as_deref();
|
||||
self.merge_modified_vars(
|
||||
then_block,
|
||||
else_block,
|
||||
then_exit_block,
|
||||
Some(else_exit_block),
|
||||
&pre_if_var_map,
|
||||
&then_var_map_end,
|
||||
&else_var_map_end_opt,
|
||||
skip_name,
|
||||
)?;
|
||||
|
||||
self.pop_if_merge();
|
||||
Ok(result_val)
|
||||
}
|
||||
}
|
||||
@ -67,6 +67,76 @@ pub(super) fn extract_assigned_var(ast: &ASTNode) -> Option<String> {
|
||||
}
|
||||
|
||||
impl MirBuilder {
|
||||
/// Merge all variables modified in then/else relative to pre_if_snapshot.
|
||||
/// In PHI-off mode inserts edge copies from branch exits to merge. In PHI-on mode emits Phi.
|
||||
/// `skip_var` allows skipping a variable already merged elsewhere (e.g., bound to an expression result).
|
||||
pub(super) fn merge_modified_vars(
|
||||
&mut self,
|
||||
then_block: super::BasicBlockId,
|
||||
else_block: super::BasicBlockId,
|
||||
then_exit_block: super::BasicBlockId,
|
||||
else_exit_block_opt: Option<super::BasicBlockId>,
|
||||
pre_if_snapshot: &std::collections::HashMap<String, super::ValueId>,
|
||||
then_map_end: &std::collections::HashMap<String, super::ValueId>,
|
||||
else_map_end_opt: &Option<std::collections::HashMap<String, super::ValueId>>,
|
||||
skip_var: Option<&str>,
|
||||
) -> Result<(), String> {
|
||||
use std::collections::HashSet;
|
||||
let mut names: HashSet<&str> = HashSet::new();
|
||||
for k in then_map_end.keys() { names.insert(k.as_str()); }
|
||||
if let Some(emap) = else_map_end_opt.as_ref() {
|
||||
for k in emap.keys() { names.insert(k.as_str()); }
|
||||
}
|
||||
// Only variables that changed against pre_if_snapshot
|
||||
let mut changed: Vec<&str> = Vec::new();
|
||||
for &name in &names {
|
||||
let pre = pre_if_snapshot.get(name);
|
||||
let t = then_map_end.get(name);
|
||||
let e = else_map_end_opt.as_ref().and_then(|m| m.get(name));
|
||||
// changed when either branch value differs from pre
|
||||
if (t.is_some() && Some(t.copied().unwrap()) != pre.copied())
|
||||
|| (e.is_some() && Some(e.copied().unwrap()) != pre.copied())
|
||||
{
|
||||
changed.push(name);
|
||||
}
|
||||
}
|
||||
for name in changed {
|
||||
if skip_var.map(|s| s == name).unwrap_or(false) {
|
||||
continue;
|
||||
}
|
||||
let pre = match pre_if_snapshot.get(name) {
|
||||
Some(v) => *v,
|
||||
None => continue, // unknown before-if; skip
|
||||
};
|
||||
let then_v = then_map_end.get(name).copied().unwrap_or(pre);
|
||||
let else_v = else_map_end_opt
|
||||
.as_ref()
|
||||
.and_then(|m| m.get(name).copied())
|
||||
.unwrap_or(pre);
|
||||
if self.is_no_phi_mode() {
|
||||
let merged = self.value_gen.next();
|
||||
// Insert edge copies from then/else exits into merge
|
||||
self.insert_edge_copy(then_exit_block, merged, then_v)?;
|
||||
if let Some(else_exit_block) = else_exit_block_opt {
|
||||
self.insert_edge_copy(else_exit_block, merged, else_v)?;
|
||||
} else {
|
||||
// Fallback: if else missing, copy pre value from then as both inputs already cover
|
||||
self.insert_edge_copy(then_exit_block, merged, then_v)?;
|
||||
}
|
||||
self.variable_map.insert(name.to_string(), merged);
|
||||
} else {
|
||||
let merged = self.value_gen.next();
|
||||
self.emit_instruction(
|
||||
MirInstruction::Phi {
|
||||
dst: merged,
|
||||
inputs: vec![(then_block, then_v), (else_block, else_v)],
|
||||
}
|
||||
)?;
|
||||
self.variable_map.insert(name.to_string(), merged);
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
/// Normalize Phi creation for if/else constructs.
|
||||
/// This handles variable reassignment patterns and ensures a single exit value.
|
||||
pub(super) fn normalize_if_else_phi(
|
||||
|
||||
@ -1,4 +1,3 @@
|
||||
use super::phi::extract_assigned_var;
|
||||
use super::{ConstValue, Effect, EffectMask, MirInstruction, ValueId};
|
||||
use crate::ast::{ASTNode, CallExpr};
|
||||
use crate::mir::loop_builder::LoopBuilder;
|
||||
@ -162,82 +161,7 @@ impl super::MirBuilder {
|
||||
then_branch: ASTNode,
|
||||
else_branch: Option<ASTNode>,
|
||||
) -> Result<ValueId, String> {
|
||||
let condition_val = self.build_expression(condition)?;
|
||||
let then_block = self.block_gen.next();
|
||||
let else_block = self.block_gen.next();
|
||||
let merge_block = self.block_gen.next();
|
||||
self.emit_instruction(MirInstruction::Branch {
|
||||
condition: condition_val,
|
||||
then_bb: then_block,
|
||||
else_bb: else_block,
|
||||
})?;
|
||||
|
||||
// Snapshot variable map before entering branches to avoid cross-branch pollution
|
||||
let pre_if_var_map = self.variable_map.clone();
|
||||
// Pre-analysis: detect then-branch assigned var and capture its pre-if value
|
||||
let assigned_then_pre = extract_assigned_var(&then_branch);
|
||||
let pre_then_var_value: Option<ValueId> = assigned_then_pre
|
||||
.as_ref()
|
||||
.and_then(|name| pre_if_var_map.get(name).copied());
|
||||
|
||||
// then
|
||||
self.current_block = Some(then_block);
|
||||
self.ensure_block_exists(then_block)?;
|
||||
let then_ast_for_analysis = then_branch.clone();
|
||||
// Build then with a clean snapshot of pre-if variables
|
||||
self.variable_map = pre_if_var_map.clone();
|
||||
let then_value_raw = self.build_expression(then_branch)?;
|
||||
let then_exit_block = Self::current_block(self)?;
|
||||
let then_var_map_end = self.variable_map.clone();
|
||||
if !self.is_current_block_terminated() {
|
||||
self.emit_instruction(MirInstruction::Jump {
|
||||
target: merge_block,
|
||||
})?;
|
||||
}
|
||||
|
||||
// else
|
||||
self.current_block = Some(else_block);
|
||||
self.ensure_block_exists(else_block)?;
|
||||
// Build else with a clean snapshot of pre-if variables
|
||||
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();
|
||||
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,
|
||||
})?;
|
||||
(void_val, None, None)
|
||||
};
|
||||
let else_exit_block = Self::current_block(self)?;
|
||||
if !self.is_current_block_terminated() {
|
||||
self.emit_instruction(MirInstruction::Jump {
|
||||
target: merge_block,
|
||||
})?;
|
||||
}
|
||||
|
||||
// merge + phi
|
||||
self.current_block = Some(merge_block);
|
||||
self.ensure_block_exists(merge_block)?;
|
||||
let result_val = self.normalize_if_else_phi(
|
||||
then_block,
|
||||
else_block,
|
||||
Some(then_exit_block),
|
||||
Some(else_exit_block),
|
||||
then_value_raw,
|
||||
else_value_raw,
|
||||
&pre_if_var_map,
|
||||
&then_ast_for_analysis,
|
||||
&else_ast_for_analysis,
|
||||
&then_var_map_end,
|
||||
&else_var_map_end_opt,
|
||||
pre_then_var_value,
|
||||
)?;
|
||||
|
||||
Ok(result_val)
|
||||
self.lower_if_form(condition, then_branch, else_branch)
|
||||
}
|
||||
|
||||
// Loop: delegate to LoopBuilder
|
||||
@ -278,6 +202,21 @@ impl super::MirBuilder {
|
||||
};
|
||||
let exit_block = self.block_gen.next();
|
||||
|
||||
// Snapshot and enable deferred-return mode for try/catch
|
||||
let saved_defer_active = self.return_defer_active;
|
||||
let saved_defer_slot = self.return_defer_slot;
|
||||
let saved_defer_target = self.return_defer_target;
|
||||
let saved_deferred_flag = self.return_deferred_emitted;
|
||||
let saved_in_cleanup = self.in_cleanup_block;
|
||||
let saved_allow_ret = self.cleanup_allow_return;
|
||||
let saved_allow_throw = self.cleanup_allow_throw;
|
||||
|
||||
let ret_slot = self.value_gen.next();
|
||||
self.return_defer_active = true;
|
||||
self.return_defer_slot = Some(ret_slot);
|
||||
self.return_deferred_emitted = false;
|
||||
self.return_defer_target = Some(finally_block.unwrap_or(exit_block));
|
||||
|
||||
if let Some(catch_clause) = catch_clauses.first() {
|
||||
if std::env::var("NYASH_DEBUG_TRYCATCH").ok().as_deref() == Some("1") {
|
||||
eprintln!(
|
||||
@ -331,27 +270,61 @@ impl super::MirBuilder {
|
||||
})?;
|
||||
}
|
||||
|
||||
let mut cleanup_terminated = false;
|
||||
if let (Some(finally_block_id), Some(finally_statements)) = (finally_block, finally_body) {
|
||||
self.start_new_block(finally_block_id)?;
|
||||
// Enter cleanup mode; returns may or may not be allowed by policy
|
||||
self.in_cleanup_block = true;
|
||||
self.cleanup_allow_return = crate::config::env::cleanup_allow_return();
|
||||
self.cleanup_allow_throw = crate::config::env::cleanup_allow_throw();
|
||||
// Inside cleanup, do not defer returns; either forbid or emit real Return
|
||||
self.return_defer_active = false;
|
||||
let finally_ast = ASTNode::Program {
|
||||
statements: finally_statements,
|
||||
span: crate::ast::Span::unknown(),
|
||||
};
|
||||
self.build_expression(finally_ast)?;
|
||||
self.emit_instruction(MirInstruction::Jump { target: exit_block })?;
|
||||
// Do not emit a Jump if the finally block already terminated (e.g., via return/throw)
|
||||
cleanup_terminated = self.is_current_block_terminated();
|
||||
if !cleanup_terminated {
|
||||
self.emit_instruction(MirInstruction::Jump { target: exit_block })?;
|
||||
}
|
||||
self.in_cleanup_block = false;
|
||||
}
|
||||
|
||||
self.start_new_block(exit_block)?;
|
||||
let result = self.value_gen.next();
|
||||
self.emit_instruction(MirInstruction::Const {
|
||||
dst: result,
|
||||
value: ConstValue::Void,
|
||||
})?;
|
||||
// If any deferred return occurred in try/catch and cleanup did not already return,
|
||||
// finalize with a Return of the slot; otherwise emit a dummy const/ret to satisfy structure.
|
||||
let result = if self.return_deferred_emitted && !cleanup_terminated {
|
||||
self.emit_instruction(MirInstruction::Return { value: Some(ret_slot) })?;
|
||||
// Emit a symbolic const to satisfy return type inference paths when inspecting non-terminated blocks (not used here)
|
||||
let r = self.value_gen.next();
|
||||
self.emit_instruction(MirInstruction::Const { dst: r, value: ConstValue::Void })?;
|
||||
r
|
||||
} else {
|
||||
let r = self.value_gen.next();
|
||||
self.emit_instruction(MirInstruction::Const { dst: r, value: ConstValue::Void })?;
|
||||
r
|
||||
};
|
||||
|
||||
// Restore deferred-return context
|
||||
self.return_defer_active = saved_defer_active;
|
||||
self.return_defer_slot = saved_defer_slot;
|
||||
self.return_defer_target = saved_defer_target;
|
||||
self.return_deferred_emitted = saved_deferred_flag;
|
||||
self.in_cleanup_block = saved_in_cleanup;
|
||||
self.cleanup_allow_return = saved_allow_ret;
|
||||
self.cleanup_allow_throw = saved_allow_throw;
|
||||
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
// Throw: emit Throw or fallback to env.debug.trace when disabled
|
||||
pub(super) fn build_throw_statement(&mut self, expression: ASTNode) -> Result<ValueId, String> {
|
||||
// Enforce cleanup policy
|
||||
if self.in_cleanup_block && !self.cleanup_allow_throw {
|
||||
return Err("throw is not allowed inside cleanup block (enable NYASH_CLEANUP_ALLOW_THROW=1 to permit)".to_string());
|
||||
}
|
||||
if std::env::var("NYASH_BUILDER_DISABLE_THROW").ok().as_deref() == Some("1") {
|
||||
let v = self.build_expression(expression)?;
|
||||
self.emit_instruction(MirInstruction::ExternCall {
|
||||
@ -396,20 +369,38 @@ impl super::MirBuilder {
|
||||
&mut self,
|
||||
value: Option<Box<ASTNode>>,
|
||||
) -> Result<ValueId, String> {
|
||||
// Enforce cleanup policy
|
||||
if self.in_cleanup_block && !self.cleanup_allow_return {
|
||||
return Err("return is not allowed inside cleanup block (enable NYASH_CLEANUP_ALLOW_RETURN=1 to permit)".to_string());
|
||||
}
|
||||
|
||||
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,
|
||||
})?;
|
||||
self.emit_instruction(MirInstruction::Const { dst: void_dst, value: ConstValue::Void })?;
|
||||
void_dst
|
||||
};
|
||||
self.emit_instruction(MirInstruction::Return {
|
||||
value: Some(return_value),
|
||||
})?;
|
||||
Ok(return_value)
|
||||
|
||||
if self.return_defer_active {
|
||||
// Defer: copy into slot and jump to target
|
||||
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 })?;
|
||||
if !self.is_current_block_terminated() {
|
||||
self.emit_instruction(MirInstruction::Jump { target })?;
|
||||
}
|
||||
Ok(return_value)
|
||||
} else {
|
||||
// Fallback: no configured slot/target; emit a real return
|
||||
self.emit_instruction(MirInstruction::Return { value: Some(return_value) })?;
|
||||
Ok(return_value)
|
||||
}
|
||||
} else {
|
||||
// Normal return
|
||||
self.emit_instruction(MirInstruction::Return { value: Some(return_value) })?;
|
||||
Ok(return_value)
|
||||
}
|
||||
}
|
||||
|
||||
// Nowait: prefer env.future.spawn_instance if method call; else FutureNew
|
||||
@ -493,4 +484,4 @@ impl super::MirBuilder {
|
||||
Ok(me_value)
|
||||
}
|
||||
}
|
||||
use crate::mir::loop_api::LoopBuilderApi; // for current_block()
|
||||
// use crate::mir::loop_api::LoopBuilderApi; // no longer needed here
|
||||
|
||||
@ -10,72 +10,7 @@ use crate::ast::ASTNode;
|
||||
impl MirBuilder {
|
||||
/// Build if statement with conditional branches
|
||||
pub(super) fn build_if_statement(&mut self, condition: ASTNode, then_branch: ASTNode, else_branch: Option<ASTNode>) -> Result<ValueId, String> {
|
||||
let condition_val = self.build_expression(condition)?;
|
||||
|
||||
// Create basic blocks for then/else/merge
|
||||
let then_block = self.block_gen.next();
|
||||
let else_block = self.block_gen.next();
|
||||
let merge_block = self.block_gen.next();
|
||||
|
||||
// Emit branch instruction in current block
|
||||
self.emit_instruction(MirInstruction::Branch {
|
||||
condition: condition_val,
|
||||
then_bb: then_block,
|
||||
else_bb: else_block,
|
||||
})?;
|
||||
|
||||
// Build then branch
|
||||
self.current_block = Some(then_block);
|
||||
self.ensure_block_exists(then_block)?;
|
||||
// Keep a copy of AST for analysis (phi for variable reassignment)
|
||||
let then_ast_for_analysis = then_branch.clone();
|
||||
let then_value = self.build_expression(then_branch)?;
|
||||
if !self.is_current_block_terminated() {
|
||||
self.emit_instruction(MirInstruction::Jump { target: merge_block })?;
|
||||
}
|
||||
|
||||
// Build else branch
|
||||
self.current_block = Some(else_block);
|
||||
self.ensure_block_exists(else_block)?;
|
||||
let (else_value, else_ast_for_analysis) = if let Some(else_ast) = else_branch {
|
||||
let val = self.build_expression(else_ast.clone())?;
|
||||
(val, Some(else_ast))
|
||||
} else {
|
||||
// No else branch, use void
|
||||
let void_val = self.value_gen.next();
|
||||
self.emit_instruction(MirInstruction::Const {
|
||||
dst: void_val,
|
||||
value: ConstValue::Void,
|
||||
})?;
|
||||
(void_val, None)
|
||||
};
|
||||
if !self.is_current_block_terminated() {
|
||||
self.emit_instruction(MirInstruction::Jump { target: merge_block })?;
|
||||
}
|
||||
|
||||
// Create merge block with phi function
|
||||
self.current_block = Some(merge_block);
|
||||
self.ensure_block_exists(merge_block)?;
|
||||
let result_val = self.value_gen.next();
|
||||
|
||||
self.emit_instruction(MirInstruction::Phi {
|
||||
dst: result_val,
|
||||
inputs: vec![
|
||||
(then_block, then_value),
|
||||
(else_block, else_value),
|
||||
],
|
||||
})?;
|
||||
|
||||
// Heuristic: If both branches assign the same variable name, bind that variable to the phi result
|
||||
let assigned_var_then = Self::extract_assigned_var(&then_ast_for_analysis);
|
||||
let assigned_var_else = else_ast_for_analysis.as_ref().and_then(|a| Self::extract_assigned_var(a));
|
||||
if let (Some(a), Some(b)) = (assigned_var_then, assigned_var_else) {
|
||||
if a == b {
|
||||
self.variable_map.insert(a, result_val);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(result_val)
|
||||
self.lower_if_form(condition, then_branch, else_branch)
|
||||
}
|
||||
|
||||
/// Extract assigned variable name from an AST node if it represents an assignment to a variable.
|
||||
|
||||
@ -82,6 +82,48 @@ fn extract_assigned_var_local(ast: &ASTNode) -> Option<String> {
|
||||
}
|
||||
|
||||
impl<'a> LoopBuilder<'a> {
|
||||
// --- Small helpers for continue/break commonization ---
|
||||
|
||||
/// Emit a jump to `target` from the current block and record predecessor metadata.
|
||||
fn jump_with_pred(&mut self, target: BasicBlockId) -> Result<(), String> {
|
||||
let cur_block = self.current_block()?;
|
||||
self.emit_jump(target)?;
|
||||
let _ = crate::mir::builder::loops::add_predecessor(self.parent_builder, target, cur_block);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Switch insertion to a fresh (unreachable) block and place a Void const to keep callers satisfied.
|
||||
fn switch_to_unreachable_block_with_void(&mut self) -> Result<ValueId, String> {
|
||||
let next_block = self.new_block();
|
||||
self.set_current_block(next_block)?;
|
||||
let void_id = self.new_value();
|
||||
self.emit_const(void_id, ConstValue::Void)?;
|
||||
Ok(void_id)
|
||||
}
|
||||
|
||||
/// Handle a `break` statement: jump to loop exit and continue in a fresh unreachable block.
|
||||
fn do_break(&mut self) -> Result<ValueId, String> {
|
||||
if let Some(exit_bb) = crate::mir::builder::loops::current_exit(self.parent_builder) {
|
||||
self.jump_with_pred(exit_bb)?;
|
||||
}
|
||||
self.switch_to_unreachable_block_with_void()
|
||||
}
|
||||
|
||||
/// Handle a `continue` statement: snapshot vars, jump to loop header, then continue in a fresh unreachable block.
|
||||
fn do_continue(&mut self) -> Result<ValueId, String> {
|
||||
// Snapshot variables at current block to be considered as a predecessor input
|
||||
let snapshot = self.get_current_variable_map();
|
||||
let cur_block = self.current_block()?;
|
||||
self.block_var_maps.insert(cur_block, snapshot.clone());
|
||||
self.continue_snapshots.push((cur_block, snapshot));
|
||||
|
||||
if let Some(header) = self.loop_header {
|
||||
self.jump_with_pred(header)?;
|
||||
}
|
||||
|
||||
self.switch_to_unreachable_block_with_void()
|
||||
}
|
||||
|
||||
/// 新しいループビルダーを作成
|
||||
pub fn new(parent: &'a mut super::builder::MirBuilder) -> Self {
|
||||
let no_phi_mode = parent.is_no_phi_mode();
|
||||
@ -528,38 +570,8 @@ impl<'a> LoopBuilder<'a> {
|
||||
self.emit_const(void_id, ConstValue::Void)?;
|
||||
Ok(void_id)
|
||||
}
|
||||
ASTNode::Break { .. } => {
|
||||
// Jump to loop exit (after_loop_id) if available
|
||||
let cur_block = self.current_block()?;
|
||||
if let Some(exit_bb) = crate::mir::builder::loops::current_exit(self.parent_builder) {
|
||||
self.emit_jump(exit_bb)?;
|
||||
let _ = crate::mir::builder::loops::add_predecessor(self.parent_builder, exit_bb, cur_block);
|
||||
}
|
||||
// Continue building in a fresh (unreachable) block to satisfy callers
|
||||
let next_block = self.new_block();
|
||||
self.set_current_block(next_block)?;
|
||||
let void_id = self.new_value();
|
||||
self.emit_const(void_id, ConstValue::Void)?;
|
||||
Ok(void_id)
|
||||
}
|
||||
ASTNode::Continue { .. } => {
|
||||
let snapshot = self.get_current_variable_map();
|
||||
let cur_block = self.current_block()?;
|
||||
self.block_var_maps.insert(cur_block, snapshot.clone());
|
||||
self.continue_snapshots.push((cur_block, snapshot));
|
||||
|
||||
if let Some(header) = self.loop_header {
|
||||
self.emit_jump(header)?;
|
||||
let _ = crate::mir::builder::loops::add_predecessor(self.parent_builder, header, cur_block);
|
||||
}
|
||||
|
||||
let next_block = self.new_block();
|
||||
self.set_current_block(next_block)?;
|
||||
|
||||
let void_id = self.new_value();
|
||||
self.emit_const(void_id, ConstValue::Void)?;
|
||||
Ok(void_id)
|
||||
}
|
||||
ASTNode::Break { .. } => self.do_break(),
|
||||
ASTNode::Continue { .. } => self.do_continue(),
|
||||
other => self.parent_builder.build_expression(other),
|
||||
}
|
||||
}
|
||||
|
||||
@ -90,6 +90,13 @@ impl MirVerifier {
|
||||
local_errors.append(&mut await_cp);
|
||||
}
|
||||
|
||||
// 9. PHI-off strict edge-copy policy (optional)
|
||||
if crate::config::env::mir_no_phi() && crate::config::env::verify_edge_copy_strict() {
|
||||
if let Err(mut ecs) = self.verify_edge_copy_strict(function) {
|
||||
local_errors.append(&mut ecs);
|
||||
}
|
||||
}
|
||||
|
||||
if local_errors.is_empty() {
|
||||
Ok(())
|
||||
} else {
|
||||
@ -173,6 +180,80 @@ impl MirVerifier {
|
||||
}
|
||||
}
|
||||
|
||||
/// When PHI-off strict mode is enabled, enforce that merge blocks do not
|
||||
/// introduce self-copies and that each predecessor provides a Copy into the
|
||||
/// merged destination for values used in the merge block that do not dominate it.
|
||||
fn verify_edge_copy_strict(
|
||||
&self,
|
||||
function: &MirFunction,
|
||||
) -> Result<(), Vec<VerificationError>> {
|
||||
let mut errors = Vec::new();
|
||||
let preds = utils::compute_predecessors(function);
|
||||
let def_block = utils::compute_def_blocks(function);
|
||||
let dominators = utils::compute_dominators(function);
|
||||
|
||||
for (merge_bid, merge_bb) in &function.blocks {
|
||||
let p = preds.get(merge_bid).cloned().unwrap_or_default();
|
||||
if p.len() < 2 {
|
||||
continue; // Only enforce on real merges (>=2 predecessors)
|
||||
}
|
||||
|
||||
// Collect values used in merge block
|
||||
let mut used_values = std::collections::HashSet::new();
|
||||
for inst in merge_bb.all_instructions() {
|
||||
for v in inst.used_values() {
|
||||
used_values.insert(v);
|
||||
}
|
||||
}
|
||||
|
||||
// For each used value that doesn't dominate the merge block, enforce edge-copy policy
|
||||
for &u in &used_values {
|
||||
if let Some(&def_bb) = def_block.get(&u) {
|
||||
// If the def dominates the merge block, edge copies are not required
|
||||
let dominated = dominators
|
||||
.get(merge_bid)
|
||||
.map(|set| set.contains(&def_bb))
|
||||
.unwrap_or(false);
|
||||
if dominated && def_bb != *merge_bid {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// Merge block itself must not contain a Copy to the merged value
|
||||
let has_self_copy_in_merge = merge_bb
|
||||
.instructions
|
||||
.iter()
|
||||
.any(|inst| matches!(inst, super::MirInstruction::Copy { dst, .. } if *dst == u));
|
||||
if has_self_copy_in_merge {
|
||||
errors.push(VerificationError::EdgeCopyStrictViolation {
|
||||
block: *merge_bid,
|
||||
value: u,
|
||||
pred_block: None,
|
||||
reason: "merge block contains Copy to merged value; use predecessor copies only".to_string(),
|
||||
});
|
||||
}
|
||||
|
||||
// Each predecessor must provide a Copy into the merged destination
|
||||
for pred in &p {
|
||||
let Some(pbb) = function.blocks.get(pred) else { continue; };
|
||||
let has_copy = pbb.instructions.iter().any(|inst| matches!(
|
||||
inst,
|
||||
super::MirInstruction::Copy { dst, .. } if *dst == u
|
||||
));
|
||||
if !has_copy {
|
||||
errors.push(VerificationError::MergeUsesPredecessorValue {
|
||||
value: u,
|
||||
merge_block: *merge_bid,
|
||||
pred_block: *pred,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if errors.is_empty() { Ok(()) } else { Err(errors) }
|
||||
}
|
||||
|
||||
/// Reject legacy instructions that should be rewritten to Core-15 equivalents
|
||||
/// Skips check when NYASH_VERIFY_ALLOW_LEGACY=1
|
||||
fn verify_no_legacy_ops(&self, function: &MirFunction) -> Result<(), Vec<VerificationError>> {
|
||||
@ -527,6 +608,21 @@ impl std::fmt::Display for VerificationError {
|
||||
position, block, instruction_index
|
||||
)
|
||||
}
|
||||
VerificationError::EdgeCopyStrictViolation { block, value, pred_block, reason } => {
|
||||
if let Some(pb) = pred_block {
|
||||
write!(
|
||||
f,
|
||||
"EdgeCopyStrictViolation for value {} at merge block {} from pred {}: {}",
|
||||
value, block, pb, reason
|
||||
)
|
||||
} else {
|
||||
write!(
|
||||
f,
|
||||
"EdgeCopyStrictViolation for value {} at merge block {}: {}",
|
||||
value, block, reason
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -66,4 +66,11 @@ pub enum VerificationError {
|
||||
instruction_index: usize,
|
||||
position: &'static str,
|
||||
},
|
||||
/// PHI-off strict policy violation (edge-copy rules)
|
||||
EdgeCopyStrictViolation {
|
||||
block: BasicBlockId,
|
||||
value: ValueId,
|
||||
pred_block: Option<BasicBlockId>,
|
||||
reason: String,
|
||||
},
|
||||
}
|
||||
|
||||
@ -144,9 +144,42 @@ impl NyashParser {
|
||||
let mut init_fields = Vec::new();
|
||||
let mut weak_fields = Vec::new(); // 🔗 Track weak fields
|
||||
|
||||
let mut last_method_name: Option<String> = None;
|
||||
while !self.match_token(&TokenType::RBRACE) && !self.is_at_end() {
|
||||
self.skip_newlines(); // ループ開始時に改行をスキップ
|
||||
|
||||
// Fallback: method-level postfix catch/cleanup after a method (non-static box)
|
||||
if (self.match_token(&TokenType::CATCH) || self.match_token(&TokenType::CLEANUP)) && last_method_name.is_some() {
|
||||
let mname = last_method_name.clone().unwrap();
|
||||
let mut catch_clauses: Vec<crate::ast::CatchClause> = Vec::new();
|
||||
if self.match_token(&TokenType::CATCH) {
|
||||
self.advance();
|
||||
self.consume(TokenType::LPAREN)?;
|
||||
let (exc_ty, exc_var) = self.parse_catch_param()?;
|
||||
self.consume(TokenType::RPAREN)?;
|
||||
let catch_body = self.parse_block_statements()?;
|
||||
catch_clauses.push(crate::ast::CatchClause { exception_type: exc_ty, variable_name: exc_var, body: catch_body, span: crate::ast::Span::unknown() });
|
||||
self.skip_newlines();
|
||||
if self.match_token(&TokenType::CATCH) {
|
||||
let line = self.current_token().line;
|
||||
return Err(ParseError::UnexpectedToken { found: self.current_token().token_type.clone(), expected: "single catch only after method body".to_string(), line });
|
||||
}
|
||||
}
|
||||
let finally_body = if self.match_token(&TokenType::CLEANUP) { self.advance(); Some(self.parse_block_statements()?) } else { None };
|
||||
if let Some(mnode) = methods.get_mut(&mname) {
|
||||
if let crate::ast::ASTNode::FunctionDeclaration { body, .. } = mnode {
|
||||
let already = body.iter().any(|n| matches!(n, crate::ast::ASTNode::TryCatch{..}));
|
||||
if already {
|
||||
let line = self.current_token().line;
|
||||
return Err(ParseError::UnexpectedToken { found: self.current_token().token_type.clone(), expected: "duplicate postfix catch/cleanup after method".to_string(), line });
|
||||
}
|
||||
let old = std::mem::take(body);
|
||||
*body = vec![crate::ast::ASTNode::TryCatch { try_body: old, catch_clauses, finally_body, span: crate::ast::Span::unknown() }];
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// RBRACEに到達していればループを抜ける
|
||||
if self.match_token(&TokenType::RBRACE) {
|
||||
break;
|
||||
@ -244,21 +277,50 @@ impl NyashParser {
|
||||
}
|
||||
|
||||
self.consume(TokenType::RPAREN)?;
|
||||
self.consume(TokenType::LBRACE)?;
|
||||
let mut body = self.parse_block_statements()?;
|
||||
self.skip_newlines();
|
||||
|
||||
let mut body = Vec::new();
|
||||
while !self.match_token(&TokenType::RBRACE) && !self.is_at_end() {
|
||||
must_advance!(self, _unused, "constructor body parsing");
|
||||
|
||||
self.skip_newlines();
|
||||
if self.match_token(&TokenType::RBRACE) {
|
||||
break;
|
||||
// Method-level postfix catch/cleanup (gate)
|
||||
if self.match_token(&TokenType::CATCH) || self.match_token(&TokenType::CLEANUP)
|
||||
{
|
||||
let mut catch_clauses: Vec<crate::ast::CatchClause> = Vec::new();
|
||||
if self.match_token(&TokenType::CATCH) {
|
||||
self.advance(); // consume 'catch'
|
||||
self.consume(TokenType::LPAREN)?;
|
||||
let (exc_ty, exc_var) = self.parse_catch_param()?;
|
||||
self.consume(TokenType::RPAREN)?;
|
||||
let catch_body = self.parse_block_statements()?;
|
||||
catch_clauses.push(crate::ast::CatchClause {
|
||||
exception_type: exc_ty,
|
||||
variable_name: exc_var,
|
||||
body: catch_body,
|
||||
span: crate::ast::Span::unknown(),
|
||||
});
|
||||
self.skip_newlines();
|
||||
if self.match_token(&TokenType::CATCH) {
|
||||
let line = self.current_token().line;
|
||||
return Err(ParseError::UnexpectedToken {
|
||||
found: self.current_token().token_type.clone(),
|
||||
expected: "single catch only after method body".to_string(),
|
||||
line,
|
||||
});
|
||||
}
|
||||
}
|
||||
body.push(self.parse_statement()?);
|
||||
let finally_body = if self.match_token(&TokenType::CLEANUP) {
|
||||
self.advance();
|
||||
Some(self.parse_block_statements()?)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
// Wrap original body with TryCatch
|
||||
body = vec![ASTNode::TryCatch {
|
||||
try_body: body,
|
||||
catch_clauses,
|
||||
finally_body,
|
||||
span: crate::ast::Span::unknown(),
|
||||
}];
|
||||
}
|
||||
|
||||
self.consume(TokenType::RBRACE)?;
|
||||
|
||||
let constructor = ASTNode::FunctionDeclaration {
|
||||
name: field_or_method.clone(),
|
||||
params: params.clone(),
|
||||
@ -307,20 +369,7 @@ impl NyashParser {
|
||||
}
|
||||
|
||||
self.consume(TokenType::RPAREN)?;
|
||||
self.consume(TokenType::LBRACE)?;
|
||||
|
||||
let mut body = Vec::new();
|
||||
while !self.match_token(&TokenType::RBRACE) && !self.is_at_end() {
|
||||
must_advance!(self, _unused, "pack body parsing");
|
||||
|
||||
self.skip_newlines();
|
||||
if self.match_token(&TokenType::RBRACE) {
|
||||
break;
|
||||
}
|
||||
body.push(self.parse_statement()?);
|
||||
}
|
||||
|
||||
self.consume(TokenType::RBRACE)?;
|
||||
let body = self.parse_block_statements()?;
|
||||
|
||||
let constructor = ASTNode::FunctionDeclaration {
|
||||
name: field_or_method.clone(),
|
||||
@ -369,20 +418,7 @@ impl NyashParser {
|
||||
}
|
||||
|
||||
self.consume(TokenType::RPAREN)?;
|
||||
self.consume(TokenType::LBRACE)?;
|
||||
|
||||
let mut body = Vec::new();
|
||||
while !self.match_token(&TokenType::RBRACE) && !self.is_at_end() {
|
||||
must_advance!(self, _unused, "birth body parsing");
|
||||
|
||||
self.skip_newlines();
|
||||
if self.match_token(&TokenType::RBRACE) {
|
||||
break;
|
||||
}
|
||||
body.push(self.parse_statement()?);
|
||||
}
|
||||
|
||||
self.consume(TokenType::RBRACE)?;
|
||||
let body = self.parse_block_statements()?;
|
||||
|
||||
let constructor = ASTNode::FunctionDeclaration {
|
||||
name: field_or_method.clone(),
|
||||
@ -513,20 +549,7 @@ impl NyashParser {
|
||||
}
|
||||
|
||||
self.consume(TokenType::RPAREN)?;
|
||||
self.consume(TokenType::LBRACE)?;
|
||||
|
||||
let mut body = Vec::new();
|
||||
while !self.match_token(&TokenType::RBRACE) && !self.is_at_end() {
|
||||
must_advance!(self, _unused, "method body parsing");
|
||||
|
||||
self.skip_newlines();
|
||||
if self.match_token(&TokenType::RBRACE) {
|
||||
break;
|
||||
}
|
||||
body.push(self.parse_statement()?);
|
||||
}
|
||||
|
||||
self.consume(TokenType::RBRACE)?;
|
||||
let body = self.parse_block_statements()?;
|
||||
|
||||
let method = ASTNode::FunctionDeclaration {
|
||||
name: field_or_method.clone(),
|
||||
@ -537,6 +560,7 @@ impl NyashParser {
|
||||
span: Span::unknown(),
|
||||
};
|
||||
|
||||
last_method_name = Some(field_or_method.clone());
|
||||
methods.insert(field_or_method, method);
|
||||
} else {
|
||||
// フィールド定義(P0: 型注釈 name: Type を受理して破棄)
|
||||
|
||||
@ -131,9 +131,46 @@ impl NyashParser {
|
||||
let mut weak_fields = Vec::new(); // 🔗 Track weak fields for static box
|
||||
let mut static_init = None;
|
||||
|
||||
// Track last inserted method name to allow postfix catch/cleanup fallback parsing
|
||||
let mut last_method_name: Option<String> = None;
|
||||
while !self.match_token(&TokenType::RBRACE) && !self.is_at_end() {
|
||||
self.skip_newlines(); // ループ開始時に改行をスキップ
|
||||
|
||||
// Fallback: method-level postfix catch/cleanup immediately following a method
|
||||
if (self.match_token(&TokenType::CATCH) || self.match_token(&TokenType::CLEANUP)) && last_method_name.is_some() {
|
||||
let mname = last_method_name.clone().unwrap();
|
||||
// Parse optional catch then optional cleanup
|
||||
let mut catch_clauses: Vec<crate::ast::CatchClause> = Vec::new();
|
||||
if self.match_token(&TokenType::CATCH) {
|
||||
self.advance();
|
||||
self.consume(TokenType::LPAREN)?;
|
||||
let (exc_ty, exc_var) = self.parse_catch_param()?;
|
||||
self.consume(TokenType::RPAREN)?;
|
||||
let catch_body = self.parse_block_statements()?;
|
||||
catch_clauses.push(crate::ast::CatchClause { exception_type: exc_ty, variable_name: exc_var, body: catch_body, span: crate::ast::Span::unknown() });
|
||||
self.skip_newlines();
|
||||
if self.match_token(&TokenType::CATCH) {
|
||||
let line = self.current_token().line;
|
||||
return Err(ParseError::UnexpectedToken { found: self.current_token().token_type.clone(), expected: "single catch only after method body".to_string(), line });
|
||||
}
|
||||
}
|
||||
let finally_body = if self.match_token(&TokenType::CLEANUP) { self.advance(); Some(self.parse_block_statements()?) } else { None };
|
||||
// Wrap existing method body
|
||||
if let Some(mnode) = methods.get_mut(&mname) {
|
||||
if let crate::ast::ASTNode::FunctionDeclaration { body, .. } = mnode {
|
||||
// If already TryCatch present, disallow duplicate postfix
|
||||
let already = body.iter().any(|n| matches!(n, crate::ast::ASTNode::TryCatch{..}));
|
||||
if already {
|
||||
let line = self.current_token().line;
|
||||
return Err(ParseError::UnexpectedToken { found: self.current_token().token_type.clone(), expected: "duplicate postfix catch/cleanup after method".to_string(), line });
|
||||
}
|
||||
let old = std::mem::take(body);
|
||||
*body = vec![crate::ast::ASTNode::TryCatch { try_body: old, catch_clauses, finally_body, span: crate::ast::Span::unknown() }];
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// RBRACEに到達していればループを抜ける
|
||||
if self.match_token(&TokenType::RBRACE) {
|
||||
break;
|
||||
@ -142,17 +179,7 @@ impl NyashParser {
|
||||
// 🔥 static { } ブロックの処理
|
||||
if self.match_token(&TokenType::STATIC) {
|
||||
self.advance(); // consume 'static'
|
||||
self.consume(TokenType::LBRACE)?;
|
||||
|
||||
let mut static_body = Vec::new();
|
||||
while !self.match_token(&TokenType::RBRACE) && !self.is_at_end() {
|
||||
self.skip_newlines();
|
||||
if !self.match_token(&TokenType::RBRACE) {
|
||||
static_body.push(self.parse_statement()?);
|
||||
}
|
||||
}
|
||||
|
||||
self.consume(TokenType::RBRACE)?;
|
||||
let static_body = self.parse_block_statements()?;
|
||||
static_init = Some(static_body);
|
||||
continue;
|
||||
}
|
||||
@ -230,18 +257,50 @@ impl NyashParser {
|
||||
}
|
||||
|
||||
self.consume(TokenType::RPAREN)?;
|
||||
self.consume(TokenType::LBRACE)?;
|
||||
let mut body = self.parse_block_statements()?;
|
||||
self.skip_newlines();
|
||||
|
||||
let mut body = Vec::new();
|
||||
while !self.match_token(&TokenType::RBRACE) && !self.is_at_end() {
|
||||
self.skip_newlines();
|
||||
if !self.match_token(&TokenType::RBRACE) {
|
||||
body.push(self.parse_statement()?);
|
||||
// Method-level postfix catch/cleanup (gate)
|
||||
if self.match_token(&TokenType::CATCH) || self.match_token(&TokenType::CLEANUP)
|
||||
{
|
||||
let mut catch_clauses: Vec<crate::ast::CatchClause> = Vec::new();
|
||||
if self.match_token(&TokenType::CATCH) {
|
||||
self.advance(); // consume 'catch'
|
||||
self.consume(TokenType::LPAREN)?;
|
||||
let (exc_ty, exc_var) = self.parse_catch_param()?;
|
||||
self.consume(TokenType::RPAREN)?;
|
||||
let catch_body = self.parse_block_statements()?;
|
||||
catch_clauses.push(crate::ast::CatchClause {
|
||||
exception_type: exc_ty,
|
||||
variable_name: exc_var,
|
||||
body: catch_body,
|
||||
span: crate::ast::Span::unknown(),
|
||||
});
|
||||
self.skip_newlines();
|
||||
if self.match_token(&TokenType::CATCH) {
|
||||
let line = self.current_token().line;
|
||||
return Err(ParseError::UnexpectedToken {
|
||||
found: self.current_token().token_type.clone(),
|
||||
expected: "single catch only after method body".to_string(),
|
||||
line,
|
||||
});
|
||||
}
|
||||
}
|
||||
let finally_body = if self.match_token(&TokenType::CLEANUP) {
|
||||
self.advance();
|
||||
Some(self.parse_block_statements()?)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
// Wrap original body with TryCatch
|
||||
body = vec![ASTNode::TryCatch {
|
||||
try_body: body,
|
||||
catch_clauses,
|
||||
finally_body,
|
||||
span: crate::ast::Span::unknown(),
|
||||
}];
|
||||
}
|
||||
|
||||
self.consume(TokenType::RBRACE)?;
|
||||
|
||||
let method = ASTNode::FunctionDeclaration {
|
||||
name: field_or_method.clone(),
|
||||
params,
|
||||
@ -251,6 +310,7 @@ impl NyashParser {
|
||||
span: Span::unknown(),
|
||||
};
|
||||
|
||||
last_method_name = Some(field_or_method.clone());
|
||||
methods.insert(field_or_method, method);
|
||||
} else {
|
||||
// フィールド定義
|
||||
|
||||
@ -53,19 +53,8 @@ impl NyashParser {
|
||||
|
||||
self.consume(TokenType::RPAREN)?;
|
||||
|
||||
// 関数本体をパース
|
||||
self.consume(TokenType::LBRACE)?;
|
||||
self.skip_newlines();
|
||||
|
||||
let mut body = Vec::new();
|
||||
while !self.match_token(&TokenType::RBRACE) && !self.is_at_end() {
|
||||
self.skip_newlines();
|
||||
if !self.match_token(&TokenType::RBRACE) {
|
||||
body.push(self.parse_statement()?);
|
||||
}
|
||||
}
|
||||
|
||||
self.consume(TokenType::RBRACE)?;
|
||||
// 関数本体をパース(共通ブロックヘルパー)
|
||||
let body = self.parse_block_statements()?;
|
||||
|
||||
Ok(ASTNode::FunctionDeclaration {
|
||||
name,
|
||||
|
||||
@ -91,19 +91,8 @@ impl NyashParser {
|
||||
|
||||
self.consume(TokenType::RPAREN)?;
|
||||
|
||||
// 関数本体をパース
|
||||
self.consume(TokenType::LBRACE)?;
|
||||
self.skip_newlines();
|
||||
|
||||
let mut body = Vec::new();
|
||||
while !self.match_token(&TokenType::RBRACE) && !self.is_at_end() {
|
||||
self.skip_newlines();
|
||||
if !self.match_token(&TokenType::RBRACE) {
|
||||
body.push(self.parse_statement()?);
|
||||
}
|
||||
}
|
||||
|
||||
self.consume(TokenType::RBRACE)?;
|
||||
// 関数本体をパース(共通ブロックヘルパー)
|
||||
let body = self.parse_block_statements()?;
|
||||
|
||||
Ok(ASTNode::FunctionDeclaration {
|
||||
name,
|
||||
|
||||
@ -11,11 +11,133 @@ use crate::ast::{ASTNode, CatchClause, Span};
|
||||
use crate::tokenizer::TokenType;
|
||||
|
||||
impl NyashParser {
|
||||
/// Helper: parse a block `{ stmt* }` and return its statements
|
||||
pub(super) fn parse_block_statements(&mut self) -> Result<Vec<ASTNode>, ParseError> {
|
||||
self.consume(TokenType::LBRACE)?;
|
||||
let mut body = Vec::new();
|
||||
while !self.match_token(&TokenType::RBRACE) && !self.is_at_end() {
|
||||
self.skip_newlines();
|
||||
if !self.match_token(&TokenType::RBRACE) {
|
||||
body.push(self.parse_statement()?);
|
||||
}
|
||||
}
|
||||
self.consume(TokenType::RBRACE)?;
|
||||
Ok(body)
|
||||
}
|
||||
|
||||
/// Helper: parse catch parameter inside parentheses (after '(' consumed)
|
||||
/// Forms: (Type ident) | (ident) | ()
|
||||
pub(super) fn parse_catch_param(&mut self) -> Result<(Option<String>, Option<String>), ParseError> {
|
||||
if self.match_token(&TokenType::RPAREN) {
|
||||
return Ok((None, None));
|
||||
}
|
||||
match &self.current_token().token_type {
|
||||
TokenType::IDENTIFIER(first) => {
|
||||
let first_str = first.clone();
|
||||
let two_idents = matches!(self.peek_token(), TokenType::IDENTIFIER(_));
|
||||
if two_idents {
|
||||
self.advance(); // consume type ident
|
||||
if let TokenType::IDENTIFIER(var_name) = &self.current_token().token_type {
|
||||
let var = var_name.clone();
|
||||
self.advance();
|
||||
Ok((Some(first_str), Some(var)))
|
||||
} else {
|
||||
let line = self.current_token().line;
|
||||
Err(ParseError::UnexpectedToken { found: self.current_token().token_type.clone(), expected: "exception variable name".to_string(), line })
|
||||
}
|
||||
} else {
|
||||
self.advance();
|
||||
Ok((None, Some(first_str)))
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
if self.match_token(&TokenType::RPAREN) {
|
||||
Ok((None, None))
|
||||
} else {
|
||||
let line = self.current_token().line;
|
||||
Err(ParseError::UnexpectedToken { found: self.current_token().token_type.clone(), expected: ") or identifier".to_string(), line })
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 文をパース
|
||||
pub(super) fn parse_statement(&mut self) -> Result<ASTNode, ParseError> {
|
||||
// For grammar diff: capture starting token to classify statement keyword
|
||||
let start_tok = self.current_token().token_type.clone();
|
||||
let result = match &start_tok {
|
||||
TokenType::LBRACE => {
|
||||
// Standalone block (Phase 15.5): may be followed by block‑postfix catch/finally
|
||||
// Only enabled under gate; otherwise treat as error via expression fallback
|
||||
// Parse the block body first
|
||||
let try_body = self.parse_block_statements()?;
|
||||
|
||||
// Allow whitespace/newlines between block and postfix keywords
|
||||
self.skip_newlines();
|
||||
|
||||
if crate::config::env::block_postfix_catch()
|
||||
&& (self.match_token(&TokenType::CATCH) || self.match_token(&TokenType::CLEANUP))
|
||||
{
|
||||
// Parse at most one catch, then optional cleanup
|
||||
let mut catch_clauses: Vec<CatchClause> = Vec::new();
|
||||
if self.match_token(&TokenType::CATCH) {
|
||||
self.advance(); // consume 'catch'
|
||||
self.consume(TokenType::LPAREN)?;
|
||||
let (exception_type, exception_var) = self.parse_catch_param()?;
|
||||
self.consume(TokenType::RPAREN)?;
|
||||
let catch_body = self.parse_block_statements()?;
|
||||
catch_clauses.push(CatchClause {
|
||||
exception_type,
|
||||
variable_name: exception_var,
|
||||
body: catch_body,
|
||||
span: Span::unknown(),
|
||||
});
|
||||
|
||||
// Single‑catch policy (MVP): disallow multiple catch in postfix form
|
||||
self.skip_newlines();
|
||||
if self.match_token(&TokenType::CATCH) {
|
||||
let line = self.current_token().line;
|
||||
return Err(ParseError::UnexpectedToken {
|
||||
found: self.current_token().token_type.clone(),
|
||||
expected: "single catch only after standalone block".to_string(),
|
||||
line,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Optional cleanup
|
||||
let finally_body = if self.match_token(&TokenType::CLEANUP) {
|
||||
self.advance(); // consume 'cleanup'
|
||||
Some(self.parse_block_statements()?)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
Ok(ASTNode::TryCatch {
|
||||
try_body,
|
||||
catch_clauses,
|
||||
finally_body,
|
||||
span: Span::unknown(),
|
||||
})
|
||||
} else {
|
||||
// No postfix keywords. If gate is on, enforce MVP static check:
|
||||
// direct top-level `throw` inside the standalone block must be followed by catch
|
||||
if crate::config::env::block_postfix_catch()
|
||||
&& try_body.iter().any(|n| matches!(n, ASTNode::Throw { .. }))
|
||||
{
|
||||
let line = self.current_token().line;
|
||||
return Err(ParseError::UnexpectedToken {
|
||||
found: self.current_token().token_type.clone(),
|
||||
expected: "block with direct 'throw' must be followed by 'catch'".to_string(),
|
||||
line,
|
||||
});
|
||||
}
|
||||
Ok(ASTNode::Program {
|
||||
statements: try_body,
|
||||
span: Span::unknown(),
|
||||
})
|
||||
}
|
||||
}
|
||||
TokenType::BOX => self.parse_box_declaration(),
|
||||
TokenType::IMPORT => self.parse_import(),
|
||||
TokenType::INTERFACE => self.parse_interface_box_declaration(),
|
||||
@ -34,8 +156,59 @@ impl NyashParser {
|
||||
TokenType::INCLUDE => self.parse_include(),
|
||||
TokenType::LOCAL => self.parse_local(),
|
||||
TokenType::OUTBOX => self.parse_outbox(),
|
||||
TokenType::TRY => self.parse_try_catch(),
|
||||
TokenType::THROW => self.parse_throw(),
|
||||
TokenType::TRY => {
|
||||
if crate::config::env::parser_stage3() {
|
||||
self.parse_try_catch()
|
||||
} else {
|
||||
Err(ParseError::UnexpectedToken {
|
||||
found: self.current_token().token_type.clone(),
|
||||
expected: "enable NYASH_PARSER_STAGE3=1 to use 'try'".to_string(),
|
||||
line: self.current_token().line,
|
||||
})
|
||||
}
|
||||
}
|
||||
TokenType::THROW => {
|
||||
if crate::config::env::parser_stage3() {
|
||||
self.parse_throw()
|
||||
} else {
|
||||
Err(ParseError::UnexpectedToken {
|
||||
found: self.current_token().token_type.clone(),
|
||||
expected: "enable NYASH_PARSER_STAGE3=1 to use 'throw'".to_string(),
|
||||
line: self.current_token().line,
|
||||
})
|
||||
}
|
||||
}
|
||||
TokenType::CATCH => {
|
||||
// Provide a friendlier error when someone writes: if { .. } catch { .. }
|
||||
if crate::config::env::block_postfix_catch() {
|
||||
Err(ParseError::UnexpectedToken {
|
||||
found: self.current_token().token_type.clone(),
|
||||
expected: "postfix 'catch' is only allowed immediately after a standalone block: { ... } catch (...) { ... } (wrap if/else/loop in a standalone block)".to_string(),
|
||||
line: self.current_token().line,
|
||||
})
|
||||
} else {
|
||||
Err(ParseError::UnexpectedToken {
|
||||
found: self.current_token().token_type.clone(),
|
||||
expected: "enable NYASH_BLOCK_CATCH=1 (or NYASH_PARSER_STAGE3=1) to use postfix 'catch' after a standalone block".to_string(),
|
||||
line: self.current_token().line,
|
||||
})
|
||||
}
|
||||
}
|
||||
TokenType::CLEANUP => {
|
||||
if crate::config::env::block_postfix_catch() {
|
||||
Err(ParseError::UnexpectedToken {
|
||||
found: self.current_token().token_type.clone(),
|
||||
expected: "postfix 'cleanup' is only allowed immediately after a standalone block: { ... } cleanup { ... }".to_string(),
|
||||
line: self.current_token().line,
|
||||
})
|
||||
} else {
|
||||
Err(ParseError::UnexpectedToken {
|
||||
found: self.current_token().token_type.clone(),
|
||||
expected: "enable NYASH_BLOCK_CATCH=1 (or NYASH_PARSER_STAGE3=1) to use postfix 'cleanup' after a standalone block".to_string(),
|
||||
line: self.current_token().line,
|
||||
})
|
||||
}
|
||||
}
|
||||
TokenType::USING => self.parse_using(),
|
||||
TokenType::FROM => {
|
||||
// 🔥 from構文: from Parent.method(args) または from Parent.constructor(args)
|
||||
@ -136,16 +309,8 @@ impl NyashParser {
|
||||
// 条件部分を取得
|
||||
let condition = Box::new(self.parse_expression()?);
|
||||
|
||||
// then部分を取得
|
||||
self.consume(TokenType::LBRACE)?;
|
||||
let mut then_body = Vec::new();
|
||||
while !self.match_token(&TokenType::RBRACE) && !self.is_at_end() {
|
||||
self.skip_newlines();
|
||||
if !self.match_token(&TokenType::RBRACE) {
|
||||
then_body.push(self.parse_statement()?);
|
||||
}
|
||||
}
|
||||
self.consume(TokenType::RBRACE)?;
|
||||
// then部分を取得(共通ブロックヘルパー)
|
||||
let then_body = self.parse_block_statements()?;
|
||||
|
||||
// else if/else部分を処理
|
||||
let else_body = if self.match_token(&TokenType::ELSE) {
|
||||
@ -156,17 +321,8 @@ impl NyashParser {
|
||||
let nested_if = self.parse_if()?;
|
||||
Some(vec![nested_if])
|
||||
} else {
|
||||
// plain else
|
||||
self.consume(TokenType::LBRACE)?;
|
||||
let mut else_stmts = Vec::new();
|
||||
while !self.match_token(&TokenType::RBRACE) && !self.is_at_end() {
|
||||
self.skip_newlines();
|
||||
if !self.match_token(&TokenType::RBRACE) {
|
||||
else_stmts.push(self.parse_statement()?);
|
||||
}
|
||||
}
|
||||
self.consume(TokenType::RBRACE)?;
|
||||
Some(else_stmts)
|
||||
// plain else(共通ブロックヘルパー)
|
||||
Some(self.parse_block_statements()?)
|
||||
}
|
||||
} else {
|
||||
None
|
||||
@ -198,16 +354,8 @@ impl NyashParser {
|
||||
})
|
||||
};
|
||||
|
||||
// body部分を取得
|
||||
self.consume(TokenType::LBRACE)?;
|
||||
let mut body = Vec::new();
|
||||
while !self.match_token(&TokenType::RBRACE) && !self.is_at_end() {
|
||||
self.skip_newlines();
|
||||
if !self.match_token(&TokenType::RBRACE) {
|
||||
body.push(self.parse_statement()?);
|
||||
}
|
||||
}
|
||||
self.consume(TokenType::RBRACE)?;
|
||||
// body部分を取得(共通ブロックヘルパー)
|
||||
let body = self.parse_block_statements()?;
|
||||
|
||||
Ok(ASTNode::Loop {
|
||||
condition,
|
||||
@ -422,17 +570,7 @@ impl NyashParser {
|
||||
/// try-catch文をパース
|
||||
pub(super) fn parse_try_catch(&mut self) -> Result<ASTNode, ParseError> {
|
||||
self.advance(); // consume 'try'
|
||||
self.consume(TokenType::LBRACE)?;
|
||||
|
||||
let mut try_body = Vec::new();
|
||||
while !self.match_token(&TokenType::RBRACE) && !self.is_at_end() {
|
||||
self.skip_newlines();
|
||||
if !self.match_token(&TokenType::RBRACE) {
|
||||
try_body.push(self.parse_statement()?);
|
||||
}
|
||||
}
|
||||
|
||||
self.consume(TokenType::RBRACE)?;
|
||||
let try_body = self.parse_block_statements()?;
|
||||
|
||||
let mut catch_clauses = Vec::new();
|
||||
|
||||
@ -440,68 +578,22 @@ impl NyashParser {
|
||||
while self.match_token(&TokenType::CATCH) {
|
||||
self.advance(); // consume 'catch'
|
||||
self.consume(TokenType::LPAREN)?;
|
||||
|
||||
// 例外型 (オプション)
|
||||
let exception_type =
|
||||
if let TokenType::IDENTIFIER(type_name) = &self.current_token().token_type {
|
||||
let type_name = type_name.clone();
|
||||
self.advance();
|
||||
Some(type_name)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
// 例外変数名
|
||||
let exception_var =
|
||||
if let TokenType::IDENTIFIER(var_name) = &self.current_token().token_type {
|
||||
let var_name = var_name.clone();
|
||||
self.advance();
|
||||
var_name
|
||||
} else {
|
||||
let line = self.current_token().line;
|
||||
return Err(ParseError::UnexpectedToken {
|
||||
found: self.current_token().token_type.clone(),
|
||||
expected: "exception variable name".to_string(),
|
||||
line,
|
||||
});
|
||||
};
|
||||
|
||||
let (exception_type, exception_var) = self.parse_catch_param()?;
|
||||
self.consume(TokenType::RPAREN)?;
|
||||
self.consume(TokenType::LBRACE)?;
|
||||
|
||||
let mut catch_body = Vec::new();
|
||||
while !self.match_token(&TokenType::RBRACE) && !self.is_at_end() {
|
||||
self.skip_newlines();
|
||||
if !self.match_token(&TokenType::RBRACE) {
|
||||
catch_body.push(self.parse_statement()?);
|
||||
}
|
||||
}
|
||||
|
||||
self.consume(TokenType::RBRACE)?;
|
||||
let catch_body = self.parse_block_statements()?;
|
||||
|
||||
catch_clauses.push(CatchClause {
|
||||
exception_type,
|
||||
variable_name: Some(exception_var),
|
||||
variable_name: exception_var,
|
||||
body: catch_body,
|
||||
span: Span::unknown(),
|
||||
});
|
||||
}
|
||||
|
||||
// finally節をパース (オプション)
|
||||
let finally_body = if self.match_token(&TokenType::FINALLY) {
|
||||
self.advance(); // consume 'finally'
|
||||
self.consume(TokenType::LBRACE)?;
|
||||
|
||||
let mut body = Vec::new();
|
||||
while !self.match_token(&TokenType::RBRACE) && !self.is_at_end() {
|
||||
self.skip_newlines();
|
||||
if !self.match_token(&TokenType::RBRACE) {
|
||||
body.push(self.parse_statement()?);
|
||||
}
|
||||
}
|
||||
|
||||
self.consume(TokenType::RBRACE)?;
|
||||
Some(body)
|
||||
// cleanup節をパース (オプション)
|
||||
let finally_body = if self.match_token(&TokenType::CLEANUP) {
|
||||
self.advance(); // consume 'cleanup'
|
||||
Some(self.parse_block_statements()?)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
@ -13,8 +13,8 @@ pub(crate) fn execute_file_with_backend(runner: &NyashRunner, filename: &str) {
|
||||
if std::env::var("NYASH_USE_NY_COMPILER").ok().as_deref() == Some("1") {
|
||||
if runner.try_run_selfhost_pipeline(filename) {
|
||||
return;
|
||||
} else if std::env::var("NYASH_CLI_VERBOSE").ok().as_deref() == Some("1") {
|
||||
eprintln!("[ny-compiler] fallback to default path (MVP unavailable for this input)");
|
||||
} else {
|
||||
crate::cli_v!("[ny-compiler] fallback to default path (MVP unavailable for this input)");
|
||||
}
|
||||
}
|
||||
|
||||
@ -31,12 +31,7 @@ pub(crate) fn execute_file_with_backend(runner: &NyashRunner, filename: &str) {
|
||||
};
|
||||
match json_v0_bridge::parse_source_v0_to_module(&code) {
|
||||
Ok(module) => {
|
||||
if std::env::var("NYASH_CLI_VERBOSE").ok().as_deref() == Some("1") {
|
||||
println!(
|
||||
"🚀 Nyash MIR Interpreter - (parser=ny) Executing file: {} 🚀",
|
||||
filename
|
||||
);
|
||||
}
|
||||
crate::cli_v!("🚀 Nyash MIR Interpreter - (parser=ny) Executing file: {} 🚀", filename);
|
||||
runner.execute_mir_module(&module);
|
||||
return;
|
||||
}
|
||||
@ -70,9 +65,7 @@ pub(crate) fn execute_file_with_backend(runner: &NyashRunner, filename: &str) {
|
||||
|
||||
// MIR dump/verify
|
||||
if runner.config.dump_mir || runner.config.verify_mir {
|
||||
if std::env::var("NYASH_CLI_VERBOSE").ok().as_deref() == Some("1") {
|
||||
println!("🚀 Nyash MIR Compiler - Processing file: {} 🚀", filename);
|
||||
}
|
||||
crate::cli_v!("🚀 Nyash MIR Compiler - Processing file: {} 🚀", filename);
|
||||
runner.execute_mir_mode(filename);
|
||||
return;
|
||||
}
|
||||
@ -106,15 +99,11 @@ pub(crate) fn execute_file_with_backend(runner: &NyashRunner, filename: &str) {
|
||||
// Backend selection
|
||||
match runner.config.backend.as_str() {
|
||||
"mir" => {
|
||||
if std::env::var("NYASH_CLI_VERBOSE").ok().as_deref() == Some("1") {
|
||||
println!("🚀 Nyash MIR Interpreter - Executing file: {} 🚀", filename);
|
||||
}
|
||||
crate::cli_v!("🚀 Nyash MIR Interpreter - Executing file: {} 🚀", filename);
|
||||
runner.execute_mir_mode(filename);
|
||||
}
|
||||
"vm" => {
|
||||
if std::env::var("NYASH_CLI_VERBOSE").ok().as_deref() == Some("1") {
|
||||
println!("🚀 Nyash VM Backend - Executing file: {} 🚀", filename);
|
||||
}
|
||||
crate::cli_v!("🚀 Nyash VM Backend - Executing file: {} 🚀", filename);
|
||||
#[cfg(feature = "vm-legacy")]
|
||||
{
|
||||
runner.execute_vm_mode(filename);
|
||||
@ -139,12 +128,7 @@ pub(crate) fn execute_file_with_backend(runner: &NyashRunner, filename: &str) {
|
||||
}
|
||||
#[cfg(feature = "cranelift-jit")]
|
||||
"jit-direct" => {
|
||||
if std::env::var("NYASH_CLI_VERBOSE").ok().as_deref() == Some("1") {
|
||||
println!(
|
||||
"⚡ Nyash JIT-Direct Backend - Executing file: {} ⚡",
|
||||
filename
|
||||
);
|
||||
}
|
||||
crate::cli_v!("⚡ Nyash JIT-Direct Backend - Executing file: {} ⚡", filename);
|
||||
#[cfg(feature = "cranelift-jit")]
|
||||
{
|
||||
// Use independent JIT-direct runner method (no VM execute loop)
|
||||
@ -157,9 +141,7 @@ pub(crate) fn execute_file_with_backend(runner: &NyashRunner, filename: &str) {
|
||||
}
|
||||
}
|
||||
"llvm" => {
|
||||
if std::env::var("NYASH_CLI_VERBOSE").ok().as_deref() == Some("1") {
|
||||
println!("⚡ Nyash LLVM Backend - Executing file: {} ⚡", filename);
|
||||
}
|
||||
crate::cli_v!("⚡ Nyash LLVM Backend - Executing file: {} ⚡", filename);
|
||||
runner.execute_llvm_mode(filename);
|
||||
}
|
||||
other => {
|
||||
@ -183,41 +165,13 @@ impl NyashRunner {
|
||||
}
|
||||
// If CLI requested EXE emit, generate JSON then invoke ny-llvmc to link NyRT and exit.
|
||||
if let Some(exe_out) = self.config.emit_exe.as_ref() {
|
||||
let tmp_dir = std::path::Path::new("tmp");
|
||||
let _ = std::fs::create_dir_all(tmp_dir);
|
||||
let json_path = tmp_dir.join("nyash_cli_emit.json");
|
||||
if let Err(e) = crate::runner::mir_json_emit::emit_mir_json_for_harness_bin(module, &json_path) {
|
||||
eprintln!("❌ MIR JSON emit error: {}", e);
|
||||
std::process::exit(1);
|
||||
}
|
||||
// Resolve ny-llvmc
|
||||
let ny_llvmc = std::env::var("NYASH_NY_LLVM_COMPILER")
|
||||
.ok()
|
||||
.and_then(|s| if !s.is_empty() { Some(std::path::PathBuf::from(s)) } else { None })
|
||||
.or_else(|| which::which("ny-llvmc").ok())
|
||||
.unwrap_or_else(|| std::path::PathBuf::from("target/release/ny-llvmc"));
|
||||
// Build command
|
||||
let mut cmd = std::process::Command::new(ny_llvmc);
|
||||
cmd.arg("--in").arg(&json_path)
|
||||
.arg("--emit").arg("exe")
|
||||
.arg("--out").arg(exe_out);
|
||||
if let Some(dir) = self.config.emit_exe_nyrt.as_ref() {
|
||||
cmd.arg("--nyrt").arg(dir);
|
||||
} else {
|
||||
// default hint
|
||||
cmd.arg("--nyrt").arg("target/release");
|
||||
}
|
||||
if let Some(flags) = self.config.emit_exe_libs.as_ref() {
|
||||
if !flags.trim().is_empty() {
|
||||
cmd.arg("--libs").arg(flags);
|
||||
}
|
||||
}
|
||||
let status = cmd.status().unwrap_or_else(|e| {
|
||||
eprintln!("❌ failed to spawn ny-llvmc: {}", e);
|
||||
std::process::exit(1);
|
||||
});
|
||||
if !status.success() {
|
||||
eprintln!("❌ ny-llvmc failed with status: {:?}", status.code());
|
||||
if let Err(e) = crate::runner::modes::common_util::exec::ny_llvmc_emit_exe_bin(
|
||||
module,
|
||||
exe_out,
|
||||
self.config.emit_exe_nyrt.as_deref(),
|
||||
self.config.emit_exe_libs.as_deref(),
|
||||
) {
|
||||
eprintln!("❌ {}", e);
|
||||
std::process::exit(1);
|
||||
}
|
||||
println!("EXE written: {}", exe_out);
|
||||
|
||||
@ -15,6 +15,7 @@ pub(super) mod try_catch;
|
||||
pub(super) mod expr;
|
||||
pub(super) mod ternary; // placeholder (not wired)
|
||||
pub(super) mod peek; // placeholder (not wired)
|
||||
pub(super) mod throw_ctx; // thread-local ctx for Result-mode throw routing
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub(super) struct LoopContext {
|
||||
@ -28,19 +29,96 @@ pub(super) struct BridgeEnv {
|
||||
pub(super) mir_no_phi: bool,
|
||||
pub(super) allow_me_dummy: bool,
|
||||
pub(super) me_class: String,
|
||||
pub(super) try_result_mode: bool,
|
||||
}
|
||||
|
||||
impl BridgeEnv {
|
||||
pub(super) fn load() -> Self {
|
||||
let trm = crate::config::env::try_result_mode();
|
||||
let no_phi = crate::config::env::mir_no_phi();
|
||||
if crate::config::env::cli_verbose() {
|
||||
eprintln!("[Bridge] load: try_result_mode={} mir_no_phi={}", trm, no_phi);
|
||||
}
|
||||
Self {
|
||||
throw_enabled: std::env::var("NYASH_BRIDGE_THROW_ENABLE").ok().as_deref() == Some("1"),
|
||||
mir_no_phi: crate::config::env::mir_no_phi(),
|
||||
mir_no_phi: no_phi,
|
||||
allow_me_dummy: std::env::var("NYASH_BRIDGE_ME_DUMMY").ok().as_deref() == Some("1"),
|
||||
me_class: std::env::var("NYASH_BRIDGE_ME_CLASS").unwrap_or_else(|_| "Main".to_string()),
|
||||
try_result_mode: trm,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Small helper: set Jump terminator and record predecessor on the target.
|
||||
fn jump_with_pred(f: &mut MirFunction, cur_bb: BasicBlockId, target: BasicBlockId) {
|
||||
if let Some(bb) = f.get_block_mut(cur_bb) {
|
||||
bb.set_terminator(MirInstruction::Jump { target });
|
||||
}
|
||||
if let Some(succ) = f.get_block_mut(target) {
|
||||
succ.add_predecessor(cur_bb);
|
||||
}
|
||||
}
|
||||
|
||||
/// Strip Phi instructions by inserting edge copies on each predecessor.
|
||||
/// This normalizes MIR to PHI-off form for downstream harnesses that synthesize PHIs.
|
||||
fn strip_phi_functions(f: &mut MirFunction) {
|
||||
// Collect block ids to avoid borrow issues while mutating
|
||||
let block_ids: Vec<BasicBlockId> = f.blocks.keys().copied().collect();
|
||||
for bbid in block_ids {
|
||||
// Snapshot phi instructions at the head
|
||||
let mut phi_entries: Vec<(ValueId, Vec<(BasicBlockId, ValueId)>)> = Vec::new();
|
||||
if let Some(bb) = f.blocks.get(&bbid) {
|
||||
for inst in &bb.instructions {
|
||||
if let MirInstruction::Phi { dst, inputs } = inst {
|
||||
phi_entries.push((*dst, inputs.clone()));
|
||||
} else {
|
||||
// PHIs must be at the beginning; once we see non-Phi, stop
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if phi_entries.is_empty() {
|
||||
continue;
|
||||
}
|
||||
// Insert copies on predecessors
|
||||
for (dst, inputs) in &phi_entries {
|
||||
for (pred, val) in inputs {
|
||||
if let Some(pbb) = f.blocks.get_mut(pred) {
|
||||
pbb.add_instruction(MirInstruction::Copy { dst: *dst, src: *val });
|
||||
}
|
||||
}
|
||||
}
|
||||
// Remove Phi instructions from the merge block
|
||||
if let Some(bb) = f.blocks.get_mut(&bbid) {
|
||||
let non_phi: Vec<MirInstruction> = bb
|
||||
.instructions
|
||||
.iter()
|
||||
.cloned()
|
||||
.skip_while(|inst| matches!(inst, MirInstruction::Phi { .. }))
|
||||
.collect();
|
||||
bb.instructions = non_phi;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn lower_break_stmt(f: &mut MirFunction, cur_bb: BasicBlockId, exit_bb: BasicBlockId) {
|
||||
jump_with_pred(f, cur_bb, exit_bb);
|
||||
crate::jit::events::emit_lower(
|
||||
serde_json::json!({ "id": "loop_break","exit_bb": exit_bb.0,"decision": "lower" }),
|
||||
"loop",
|
||||
"<json_v0>",
|
||||
);
|
||||
}
|
||||
|
||||
fn lower_continue_stmt(f: &mut MirFunction, cur_bb: BasicBlockId, cond_bb: BasicBlockId) {
|
||||
jump_with_pred(f, cur_bb, cond_bb);
|
||||
crate::jit::events::emit_lower(
|
||||
serde_json::json!({ "id": "loop_continue","cond_bb": cond_bb.0,"decision": "lower" }),
|
||||
"loop",
|
||||
"<json_v0>",
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
pub(super) fn lower_stmt_with_vars(
|
||||
f: &mut MirFunction,
|
||||
@ -86,31 +164,13 @@ pub(super) fn lower_stmt_with_vars(
|
||||
}
|
||||
StmtV0::Break => {
|
||||
if let Some(ctx) = loop_stack.last().copied() {
|
||||
if let Some(bb) = f.get_block_mut(cur_bb) {
|
||||
bb.set_terminator(MirInstruction::Jump {
|
||||
target: ctx.exit_bb,
|
||||
});
|
||||
}
|
||||
crate::jit::events::emit_lower(
|
||||
serde_json::json!({ "id": "loop_break","exit_bb": ctx.exit_bb.0,"decision": "lower" }),
|
||||
"loop",
|
||||
"<json_v0>",
|
||||
);
|
||||
lower_break_stmt(f, cur_bb, ctx.exit_bb);
|
||||
}
|
||||
Ok(cur_bb)
|
||||
}
|
||||
StmtV0::Continue => {
|
||||
if let Some(ctx) = loop_stack.last().copied() {
|
||||
if let Some(bb) = f.get_block_mut(cur_bb) {
|
||||
bb.set_terminator(MirInstruction::Jump {
|
||||
target: ctx.cond_bb,
|
||||
});
|
||||
}
|
||||
crate::jit::events::emit_lower(
|
||||
serde_json::json!({ "id": "loop_continue","cond_bb": ctx.cond_bb.0,"decision": "lower" }),
|
||||
"loop",
|
||||
"<json_v0>",
|
||||
);
|
||||
lower_continue_stmt(f, cur_bb, ctx.cond_bb);
|
||||
}
|
||||
Ok(cur_bb)
|
||||
}
|
||||
@ -194,12 +254,16 @@ pub(super) fn lower_program(prog: ProgramV0) -> Result<MirModule, String> {
|
||||
}
|
||||
}
|
||||
f.signature.return_type = MirType::Unknown;
|
||||
// PHI-off normalization for Bridge output
|
||||
if env.mir_no_phi {
|
||||
strip_phi_functions(&mut f);
|
||||
}
|
||||
module.add_function(f);
|
||||
Ok(module)
|
||||
}
|
||||
|
||||
pub(super) fn maybe_dump_mir(module: &MirModule) {
|
||||
if std::env::var("NYASH_CLI_VERBOSE").ok().as_deref() == Some("1") {
|
||||
if crate::config::env::cli_verbose() {
|
||||
let p = MirPrinter::new();
|
||||
println!("{}", p.print_module(module));
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user