jit: ops_ext delegation + M3 syntax scaffolding; unify BoxCall execution path

This commit is contained in:
Moe Charm
2025-09-02 17:12:51 +09:00
parent 5a5e09b69a
commit d52779dc10
33 changed files with 2499 additions and 423 deletions

View File

@ -19,6 +19,64 @@ impl VM {
pub(super) fn execute_binary_op(&self, op: &BinaryOp, left: &VMValue, right: &VMValue) -> Result<VMValue, VMError> {
let debug_bin = std::env::var("NYASH_VM_DEBUG_BIN").ok().as_deref() == Some("1");
if debug_bin { eprintln!("[VM] binop {:?} {:?} {:?}", op, left, right); }
if std::env::var("NYASH_GRAMMAR_DIFF").ok().as_deref() == Some("1") {
let lty = match left { VMValue::String(_) => "String", VMValue::Integer(_) => "Integer", VMValue::Float(_) => "Float", VMValue::Bool(_) => "Bool", _ => "Other" };
let rty = match right { VMValue::String(_) => "String", VMValue::Integer(_) => "Integer", VMValue::Float(_) => "Float", VMValue::Bool(_) => "Bool", _ => "Other" };
match *op {
BinaryOp::Add => {
let strat = crate::grammar::engine::get().add_coercion_strategy();
let rule = crate::grammar::engine::get().decide_add_result(lty, rty);
eprintln!("[GRAMMAR-DIFF][VM] add.coercion_strategy={} left={} right={} rule={:?}", strat, lty, rty, rule);
}
BinaryOp::Sub => {
let strat = crate::grammar::engine::get().sub_coercion_strategy();
let rule = crate::grammar::engine::get().decide_sub_result(lty, rty);
eprintln!("[GRAMMAR-DIFF][VM] sub.coercion_strategy={} left={} right={} rule={:?}", strat, lty, rty, rule);
}
BinaryOp::Mul => {
let strat = crate::grammar::engine::get().mul_coercion_strategy();
let rule = crate::grammar::engine::get().decide_mul_result(lty, rty);
eprintln!("[GRAMMAR-DIFF][VM] mul.coercion_strategy={} left={} right={} rule={:?}", strat, lty, rty, rule);
}
BinaryOp::Div => {
let strat = crate::grammar::engine::get().div_coercion_strategy();
let rule = crate::grammar::engine::get().decide_div_result(lty, rty);
eprintln!("[GRAMMAR-DIFF][VM] div.coercion_strategy={} left={} right={} rule={:?}", strat, lty, rty, rule);
}
_ => {}
}
}
if matches!(*op, BinaryOp::Add) && std::env::var("NYASH_GRAMMAR_ENFORCE_ADD").ok().as_deref() == Some("1") {
let lty = match left { VMValue::String(_) => "String", VMValue::Integer(_) => "Integer", VMValue::Float(_) => "Float", VMValue::Bool(_) => "Bool", _ => "Other" };
let rty = match right { VMValue::String(_) => "String", VMValue::Integer(_) => "Integer", VMValue::Float(_) => "Float", VMValue::Bool(_) => "Bool", _ => "Other" };
if let Some((res, _)) = crate::grammar::engine::get().decide_add_result(lty, rty) {
match res {
"String" => {
// Best-effort toString concat
fn vmv_to_string(v: &VMValue) -> String {
match v {
VMValue::String(s) => s.clone(),
VMValue::Integer(i) => i.to_string(),
VMValue::Float(f) => f.to_string(),
VMValue::Bool(b) => b.to_string(),
VMValue::Void => "void".to_string(),
VMValue::BoxRef(b) => b.to_string_box().value,
VMValue::Future(_) => "<future>".to_string(),
}
}
let ls = vmv_to_string(left);
let rs = vmv_to_string(right);
return Ok(VMValue::String(format!("{}{}", ls, rs)));
}
"Integer" => {
if let (VMValue::Integer(l), VMValue::Integer(r)) = (left, right) {
return Ok(VMValue::Integer(l + r));
}
}
_ => {}
}
}
}
// Fast path: logical AND/OR accept any truthy via as_bool
if matches!(*op, BinaryOp::And | BinaryOp::Or) {
let l = left.as_bool()?;

59
src/grammar/engine.rs Normal file
View File

@ -0,0 +1,59 @@
use once_cell::sync::Lazy;
use super::generated;
pub struct UnifiedGrammarEngine;
impl UnifiedGrammarEngine {
pub fn load() -> Self { Self }
pub fn is_keyword_str(&self, word: &str) -> Option<&'static str> {
generated::lookup_keyword(word)
}
pub fn add_coercion_strategy(&self) -> &'static str {
generated::OPERATORS_ADD_COERCION
}
pub fn add_rules(&self) -> &'static [(&'static str, &'static str, &'static str, &'static str)] {
generated::OPERATORS_ADD_RULES
}
pub fn decide_add_result(&self, left_ty: &str, right_ty: &str) -> Option<(&'static str, &'static str)> {
for (l, r, res, act) in self.add_rules() {
if *l == left_ty && *r == right_ty { return Some((*res, *act)); }
}
None
}
pub fn sub_coercion_strategy(&self) -> &'static str { generated::OPERATORS_SUB_COERCION }
pub fn sub_rules(&self) -> &'static [(&'static str, &'static str, &'static str, &'static str)] { generated::OPERATORS_SUB_RULES }
pub fn decide_sub_result(&self, left_ty: &str, right_ty: &str) -> Option<(&'static str, &'static str)> {
for (l, r, res, act) in self.sub_rules() { if *l == left_ty && *r == right_ty { return Some((*res, *act)); } }
None
}
pub fn mul_coercion_strategy(&self) -> &'static str { generated::OPERATORS_MUL_COERCION }
pub fn mul_rules(&self) -> &'static [(&'static str, &'static str, &'static str, &'static str)] { generated::OPERATORS_MUL_RULES }
pub fn decide_mul_result(&self, left_ty: &str, right_ty: &str) -> Option<(&'static str, &'static str)> {
for (l, r, res, act) in self.mul_rules() { if *l == left_ty && *r == right_ty { return Some((*res, *act)); } }
None
}
pub fn div_coercion_strategy(&self) -> &'static str { generated::OPERATORS_DIV_COERCION }
pub fn div_rules(&self) -> &'static [(&'static str, &'static str, &'static str, &'static str)] { generated::OPERATORS_DIV_RULES }
pub fn decide_div_result(&self, left_ty: &str, right_ty: &str) -> Option<(&'static str, &'static str)> {
for (l, r, res, act) in self.div_rules() { if *l == left_ty && *r == right_ty { return Some((*res, *act)); } }
None
}
}
pub static ENGINE: Lazy<UnifiedGrammarEngine> = Lazy::new(UnifiedGrammarEngine::load);
pub fn get() -> &'static UnifiedGrammarEngine { &ENGINE }
// --- Syntax rule helpers (generated-backed) ---
impl UnifiedGrammarEngine {
pub fn syntax_is_allowed_statement(&self, keyword: &str) -> bool {
super::generated::SYNTAX_ALLOWED_STATEMENTS.iter().any(|k| *k == keyword)
}
pub fn syntax_is_allowed_binop(&self, op: &str) -> bool {
super::generated::SYNTAX_ALLOWED_BINOPS.iter().any(|k| *k == op)
}
}

69
src/grammar/generated.rs Normal file
View File

@ -0,0 +1,69 @@
// Auto-generated from grammar/unified-grammar.toml
pub static KEYWORDS: &[(&str, &str)] = &[
("me", "ME"),
("from", "FROM"),
("loop", "LOOP"),
];
pub static OPERATORS_ADD_COERCION: &str = "string_priority";
pub static OPERATORS_SUB_COERCION: &str = "numeric_only";
pub static OPERATORS_MUL_COERCION: &str = "numeric_only";
pub static OPERATORS_DIV_COERCION: &str = "numeric_only";
pub static OPERATORS_ADD_RULES: &[(&str, &str, &str, &str)] = &[
("String", "String", "String", "concat"),
("String", "Integer", "String", "concat"),
("Integer", "String", "String", "concat"),
("String", "Bool", "String", "concat"),
("Bool", "String", "String", "concat"),
("String", "Other", "String", "concat"),
("Other", "String", "String", "concat"),
("Integer", "Integer", "Integer", "add_i64"),
("Float", "Float", "Float", "add_f64"),
];
pub static OPERATORS_SUB_RULES: &[(&str, &str, &str, &str)] = &[
("Integer", "Integer", "Integer", "sub_i64"),
("Float", "Float", "Float", "sub_f64"),
];
pub static OPERATORS_MUL_RULES: &[(&str, &str, &str, &str)] = &[
("Integer", "Integer", "Integer", "mul_i64"),
("Float", "Float", "Float", "mul_f64"),
];
pub static OPERATORS_DIV_RULES: &[(&str, &str, &str, &str)] = &[
("Integer", "Integer", "Integer", "div_i64"),
("Float", "Float", "Float", "div_f64"),
];
pub fn lookup_keyword(word: &str) -> Option<&'static str> {
for (k, t) in KEYWORDS {
if *k == word { return Some(*t); }
}
None
}
pub static SYNTAX_ALLOWED_STATEMENTS: &[&str] = &[
"box",
"global",
"function",
"static",
"if",
"loop",
"break",
"return",
"print",
"nowait",
"include",
"local",
"outbox",
"try",
"throw",
"using",
"from",
];
pub static SYNTAX_ALLOWED_BINOPS: &[&str] = &[
"add",
"sub",
"mul",
"div",
"and",
"or",
"eq",
"ne",
];

6
src/grammar/mod.rs Normal file
View File

@ -0,0 +1,6 @@
pub mod engine;
// Generated tables from grammar/unified-grammar.toml
#[path = "generated.rs"]
mod generated;
pub use generated::*;

View File

@ -165,8 +165,39 @@ impl NyashInterpreter {
match op {
BinaryOperator::Add => {
// Optional: enforce grammar rule for add (behind env)
if std::env::var("NYASH_GRAMMAR_ENFORCE_ADD").ok().as_deref() == Some("1") {
let lty = if crate::runtime::semantics::coerce_to_string(left_val.as_ref()).is_some() { "String" } else if crate::runtime::semantics::coerce_to_i64(left_val.as_ref()).is_some() { "Integer" } else { "Other" };
let rty = if crate::runtime::semantics::coerce_to_string(right_val.as_ref()).is_some() { "String" } else if crate::runtime::semantics::coerce_to_i64(right_val.as_ref()).is_some() { "Integer" } else { "Other" };
if let Some((res, _act)) = crate::grammar::engine::get().decide_add_result(lty, rty) {
match res {
"String" => {
let ls = crate::runtime::semantics::coerce_to_string(left_val.as_ref()).unwrap_or_else(|| left_val.to_string_box().value);
let rs = crate::runtime::semantics::coerce_to_string(right_val.as_ref()).unwrap_or_else(|| right_val.to_string_box().value);
return Ok(Box::new(StringBox::new(format!("{}{}", ls, rs))));
}
"Integer" => {
if let (Some(li), Some(ri)) = (crate::runtime::semantics::coerce_to_i64(left_val.as_ref()), crate::runtime::semantics::coerce_to_i64(right_val.as_ref())) {
return Ok(Box::new(IntegerBox::new(li + ri)));
}
}
_ => {}
}
}
}
let (strat, lty, rty, expect) = if std::env::var("NYASH_GRAMMAR_DIFF").ok().as_deref() == Some("1") {
let strat = crate::grammar::engine::get().add_coercion_strategy();
let lty = if crate::runtime::semantics::coerce_to_string(left_val.as_ref()).is_some() { "String" } else if crate::runtime::semantics::coerce_to_i64(left_val.as_ref()).is_some() { "Integer" } else { "Other" };
let rty = if crate::runtime::semantics::coerce_to_string(right_val.as_ref()).is_some() { "String" } else if crate::runtime::semantics::coerce_to_i64(right_val.as_ref()).is_some() { "Integer" } else { "Other" };
let rule = crate::grammar::engine::get().decide_add_result(lty, rty);
(Some(strat.to_string()), Some(lty.to_string()), Some(rty.to_string()), rule.map(|(res, act)| (res.to_string(), act.to_string())))
} else { (None, None, None, None) };
// 1) Intrinsic fast-paths (Integer+Integer, String+*, Bool+Bool)
if let Some(result) = try_add_operation(left_val.as_ref(), right_val.as_ref()) {
if let (Some(s), Some(l), Some(r)) = (strat.as_ref(), lty.as_ref(), rty.as_ref()) {
let actual = if result.as_any().downcast_ref::<StringBox>().is_some() { "String" } else if result.as_any().downcast_ref::<IntegerBox>().is_some() { "Integer" } else { "Other" };
eprintln!("[GRAMMAR-DIFF][Interp] add strat={} lty={} rty={} expect={:?} actual={} match={}", s, l, r, expect, actual, expect.as_ref().map(|(res,_)| res.as_str())==Some(actual));
}
return Ok(result);
}
// 2) Concatenation if either side is string-like (semantics)
@ -175,13 +206,22 @@ impl NyashInterpreter {
if ls_opt.is_some() || rs_opt.is_some() {
let ls = ls_opt.unwrap_or_else(|| left_val.to_string_box().value);
let rs = rs_opt.unwrap_or_else(|| right_val.to_string_box().value);
if let (Some(s), Some(l), Some(r)) = (strat.as_ref(), lty.as_ref(), rty.as_ref()) {
eprintln!("[GRAMMAR-DIFF][Interp] add strat={} lty={} rty={} expect={:?} actual=String match={}", s, l, r, expect, expect.as_ref().map(|(res,_)| res=="String").unwrap_or(false));
}
return Ok(Box::new(StringBox::new(format!("{}{}", ls, rs))));
}
// 3) Numeric fallback via coerce_to_i64
if let (Some(li), Some(ri)) = (crate::runtime::semantics::coerce_to_i64(left_val.as_ref()), crate::runtime::semantics::coerce_to_i64(right_val.as_ref())) {
if let (Some(s), Some(l), Some(r)) = (strat.as_ref(), lty.as_ref(), rty.as_ref()) {
eprintln!("[GRAMMAR-DIFF][Interp] add strat={} lty={} rty={} expect={:?} actual=Integer match={}", s, l, r, expect, expect.as_ref().map(|(res,_)| res=="Integer").unwrap_or(false));
}
return Ok(Box::new(IntegerBox::new(li + ri)));
}
// 4) Final error
if let (Some(s), Some(l), Some(r)) = (strat.as_ref(), lty.as_ref(), rty.as_ref()) {
eprintln!("[GRAMMAR-DIFF][Interp] add strat={} lty={} rty={} expect={:?} actual=Error", s, l, r, expect);
}
Err(RuntimeError::InvalidOperation {
message: format!("Addition not supported between {} and {}",
left_val.type_name(), right_val.type_name())
@ -219,6 +259,13 @@ impl NyashInterpreter {
}
BinaryOperator::Subtract => {
if std::env::var("NYASH_GRAMMAR_DIFF").ok().as_deref() == Some("1") {
let strat = crate::grammar::engine::get().sub_coercion_strategy();
let lty = if crate::runtime::semantics::coerce_to_string(left_val.as_ref()).is_some() { "String" } else if crate::runtime::semantics::coerce_to_i64(left_val.as_ref()).is_some() { "Integer" } else { "Other" };
let rty = if crate::runtime::semantics::coerce_to_string(right_val.as_ref()).is_some() { "String" } else if crate::runtime::semantics::coerce_to_i64(right_val.as_ref()).is_some() { "Integer" } else { "Other" };
let rule = crate::grammar::engine::get().decide_sub_result(lty, rty);
eprintln!("[GRAMMAR-DIFF][Interp] sub strat={} lty={} rty={} expect={:?}", strat, lty, rty, rule);
}
// Use helper function instead of trait methods
if let Some(result) = try_sub_operation(left_val.as_ref(), right_val.as_ref()) {
return Ok(result);
@ -231,6 +278,13 @@ impl NyashInterpreter {
}
BinaryOperator::Multiply => {
if std::env::var("NYASH_GRAMMAR_DIFF").ok().as_deref() == Some("1") {
let strat = crate::grammar::engine::get().mul_coercion_strategy();
let lty = if crate::runtime::semantics::coerce_to_string(left_val.as_ref()).is_some() { "String" } else if crate::runtime::semantics::coerce_to_i64(left_val.as_ref()).is_some() { "Integer" } else { "Other" };
let rty = if crate::runtime::semantics::coerce_to_string(right_val.as_ref()).is_some() { "String" } else if crate::runtime::semantics::coerce_to_i64(right_val.as_ref()).is_some() { "Integer" } else { "Other" };
let rule = crate::grammar::engine::get().decide_mul_result(lty, rty);
eprintln!("[GRAMMAR-DIFF][Interp] mul strat={} lty={} rty={} expect={:?}", strat, lty, rty, rule);
}
// Use helper function instead of trait methods
if let Some(result) = try_mul_operation(left_val.as_ref(), right_val.as_ref()) {
return Ok(result);
@ -243,6 +297,13 @@ impl NyashInterpreter {
}
BinaryOperator::Divide => {
if std::env::var("NYASH_GRAMMAR_DIFF").ok().as_deref() == Some("1") {
let strat = crate::grammar::engine::get().div_coercion_strategy();
let lty = if crate::runtime::semantics::coerce_to_string(left_val.as_ref()).is_some() { "String" } else if crate::runtime::semantics::coerce_to_i64(left_val.as_ref()).is_some() { "Integer" } else { "Other" };
let rty = if crate::runtime::semantics::coerce_to_string(right_val.as_ref()).is_some() { "String" } else if crate::runtime::semantics::coerce_to_i64(right_val.as_ref()).is_some() { "Integer" } else { "Other" };
let rule = crate::grammar::engine::get().decide_div_result(lty, rty);
eprintln!("[GRAMMAR-DIFF][Interp] div strat={} lty={} rty={} expect={:?}", strat, lty, rty, rule);
}
// Use helper function instead of trait methods
match try_div_operation(left_val.as_ref(), right_val.as_ref()) {
Ok(result) => Ok(result),

View File

@ -1,27 +1,31 @@
use crate::mir::{MirFunction, MirInstruction, ConstValue, BinaryOp, CompareOp, ValueId};
use super::builder::{IRBuilder, BinOpKind, CmpKind};
mod analysis;
mod cfg;
mod ops_ext;
/// Lower(Core-1): Minimal lowering skeleton for Const/Move/BinOp/Cmp/Branch/Ret
/// This does not emit real CLIF yet; it only walks MIR and validates coverage.
pub struct LowerCore {
pub unsupported: usize,
pub covered: usize,
pub(crate) unsupported: usize,
pub(crate) covered: usize,
/// Minimal constant propagation for i64 to feed host-call args
pub(super) known_i64: std::collections::HashMap<ValueId, i64>,
/// Minimal constant propagation for f64 (math.* signature checks)
known_f64: std::collections::HashMap<ValueId, f64>,
pub(super) known_f64: std::collections::HashMap<ValueId, f64>,
/// Parameter index mapping for ValueId
pub(super) param_index: std::collections::HashMap<ValueId, usize>,
/// Track values produced by Phi (for minimal PHI path)
phi_values: std::collections::HashSet<ValueId>,
pub(super) phi_values: std::collections::HashSet<ValueId>,
/// Map (block, phi dst) -> param index in that block (for multi-PHI)
phi_param_index: std::collections::HashMap<(crate::mir::BasicBlockId, ValueId), usize>,
pub(super) phi_param_index: std::collections::HashMap<(crate::mir::BasicBlockId, ValueId), usize>,
/// Track values that are boolean (b1) results, e.g., Compare destinations
pub(super) bool_values: std::collections::HashSet<ValueId>,
/// Track PHI destinations that are boolean (all inputs derived from bool_values)
bool_phi_values: std::collections::HashSet<ValueId>,
pub(super) bool_phi_values: std::collections::HashSet<ValueId>,
/// Track values that are FloatBox instances (for arg type classification)
float_box_values: std::collections::HashSet<ValueId>,
pub(super) float_box_values: std::collections::HashSet<ValueId>,
/// Track values that are plugin handles (generic box/handle, type unknown at compile time)
pub(super) handle_values: std::collections::HashSet<ValueId>,
// Per-function statistics (last lowered)
@ -56,169 +60,13 @@ impl LowerCore {
let mut bb_ids: Vec<_> = func.blocks.keys().copied().collect();
bb_ids.sort_by_key(|b| b.0);
builder.prepare_blocks(bb_ids.len());
// Seed boolean lattice with boolean parameters from MIR signature
if !func.signature.params.is_empty() {
for (idx, vid) in func.params.iter().copied().enumerate() {
if let Some(mt) = func.signature.params.get(idx) {
if matches!(mt, crate::mir::MirType::Bool) {
self.bool_values.insert(vid);
}
}
}
}
// Pre-scan to classify boolean-producing values and propagate via Copy/Phi/Load-Store heuristics.
self.bool_values.clear();
let mut copy_edges: Vec<(crate::mir::ValueId, crate::mir::ValueId)> = Vec::new();
let mut phi_defs: Vec<(crate::mir::ValueId, Vec<crate::mir::ValueId>)> = Vec::new();
let mut stores: Vec<(crate::mir::ValueId, crate::mir::ValueId)> = Vec::new(); // (ptr, value)
let mut loads: Vec<(crate::mir::ValueId, crate::mir::ValueId)> = Vec::new(); // (dst, ptr)
for bb in bb_ids.iter() {
if let Some(block) = func.blocks.get(bb) {
for ins in block.instructions.iter() {
match ins {
crate::mir::MirInstruction::Compare { dst, .. } => { self.bool_values.insert(*dst); }
crate::mir::MirInstruction::Const { dst, value } => {
if let ConstValue::Bool(_) = value { self.bool_values.insert(*dst); }
}
crate::mir::MirInstruction::Cast { dst, target_type, .. } => {
if matches!(target_type, crate::mir::MirType::Bool) { self.bool_values.insert(*dst); }
}
crate::mir::MirInstruction::TypeOp { dst, op, ty, .. } => {
// Check and cast-to-bool produce boolean
if matches!(op, crate::mir::TypeOpKind::Check) || matches!(ty, crate::mir::MirType::Bool) { self.bool_values.insert(*dst); }
}
crate::mir::MirInstruction::Copy { dst, src } => { copy_edges.push((*dst, *src)); }
crate::mir::MirInstruction::Phi { dst, inputs } => {
let vs: Vec<_> = inputs.iter().map(|(_, v)| *v).collect();
phi_defs.push((*dst, vs));
}
crate::mir::MirInstruction::Store { value, ptr } => { stores.push((*ptr, *value)); }
crate::mir::MirInstruction::Load { dst, ptr } => { loads.push((*dst, *ptr)); }
_ => {}
}
}
if let Some(term) = &block.terminator {
match term {
crate::mir::MirInstruction::Compare { dst, .. } => { self.bool_values.insert(*dst); }
crate::mir::MirInstruction::Const { dst, value } => {
if let ConstValue::Bool(_) = value { self.bool_values.insert(*dst); }
}
crate::mir::MirInstruction::Cast { dst, target_type, .. } => {
if matches!(target_type, crate::mir::MirType::Bool) { self.bool_values.insert(*dst); }
}
crate::mir::MirInstruction::TypeOp { dst, op, ty, .. } => {
if matches!(op, crate::mir::TypeOpKind::Check) || matches!(ty, crate::mir::MirType::Bool) { self.bool_values.insert(*dst); }
}
crate::mir::MirInstruction::Copy { dst, src } => { copy_edges.push((*dst, *src)); }
crate::mir::MirInstruction::Phi { dst, inputs } => {
let vs: Vec<_> = inputs.iter().map(|(_, v)| *v).collect();
phi_defs.push((*dst, vs));
}
crate::mir::MirInstruction::Branch { condition, .. } => { self.bool_values.insert(*condition); }
crate::mir::MirInstruction::Store { value, ptr } => { stores.push((*ptr, *value)); }
crate::mir::MirInstruction::Load { dst, ptr } => { loads.push((*dst, *ptr)); }
_ => {}
}
}
}
}
// Fixed-point boolean lattice propagation
let mut changed = true;
let mut store_bool_ptrs: std::collections::HashSet<crate::mir::ValueId> = std::collections::HashSet::new();
while changed {
changed = false;
// Copy propagation
for (dst, src) in copy_edges.iter().copied() {
if self.bool_values.contains(&src) && !self.bool_values.contains(&dst) {
self.bool_values.insert(dst);
changed = true;
}
// Pointer alias propagation for Store/Load lattice
if store_bool_ptrs.contains(&src) && !store_bool_ptrs.contains(&dst) {
store_bool_ptrs.insert(dst);
changed = true;
}
}
// Store marking
for (ptr, val) in stores.iter().copied() {
if self.bool_values.contains(&val) && !store_bool_ptrs.contains(&ptr) {
store_bool_ptrs.insert(ptr);
changed = true;
}
}
// Load propagation
for (dst, ptr) in loads.iter().copied() {
if store_bool_ptrs.contains(&ptr) && !self.bool_values.contains(&dst) {
self.bool_values.insert(dst);
changed = true;
}
}
// PHI closure for value booleans
for (dst, inputs) in phi_defs.iter() {
if inputs.iter().all(|v| self.bool_values.contains(v)) && !self.bool_values.contains(dst) {
self.bool_values.insert(*dst);
self.bool_phi_values.insert(*dst);
changed = true;
}
}
// PHI closure for pointer aliases: if all inputs are bool-storing pointers, mark dst pointer as such
for (dst, inputs) in phi_defs.iter() {
if inputs.iter().all(|v| store_bool_ptrs.contains(v)) && !store_bool_ptrs.contains(dst) {
store_bool_ptrs.insert(*dst);
changed = true;
}
}
}
// Always-on PHI statistics: count total/b1 phi slots using current heuristics
{
use crate::mir::MirInstruction;
let mut total_phi_slots: usize = 0;
let mut total_phi_b1_slots: usize = 0;
for (dst, inputs) in phi_defs.iter() {
total_phi_slots += 1;
// Heuristics consistent with dump path
let used_as_branch = func.blocks.values().any(|bbx| {
if let Some(MirInstruction::Branch { condition, .. }) = &bbx.terminator { condition == dst } else { false }
});
let is_b1 = self.bool_phi_values.contains(dst)
|| inputs.iter().all(|v| {
self.bool_values.contains(v) || self.known_i64.get(v).map(|&iv| iv == 0 || iv == 1).unwrap_or(false)
})
|| used_as_branch;
if is_b1 { total_phi_b1_slots += 1; }
}
if total_phi_slots > 0 {
crate::jit::rt::phi_total_inc(total_phi_slots as u64);
crate::jit::rt::phi_b1_inc(total_phi_b1_slots as u64);
self.last_phi_total = total_phi_slots as u64;
self.last_phi_b1 = total_phi_b1_slots as u64;
}
}
self.analyze(func, &bb_ids);
// Optional: collect PHI targets and ordering per successor for minimal/multi PHI path
let cfg_now = crate::jit::config::current();
let enable_phi_min = cfg_now.phi_min;
// For each successor block, store ordered list of phi dst and a map pred->input for each phi
let mut succ_phi_order: std::collections::HashMap<crate::mir::BasicBlockId, Vec<crate::mir::ValueId>> = std::collections::HashMap::new();
let mut succ_phi_inputs: std::collections::HashMap<crate::mir::BasicBlockId, Vec<(crate::mir::BasicBlockId, crate::mir::ValueId)>> = std::collections::HashMap::new();
if enable_phi_min {
for (bb_id, bb) in func.blocks.iter() {
let mut order: Vec<crate::mir::ValueId> = Vec::new();
for ins in bb.instructions.iter() {
if let crate::mir::MirInstruction::Phi { dst, inputs } = ins {
order.push(*dst);
// store all (pred,val) pairs in flat vec grouped by succ
for (pred, val) in inputs.iter() { succ_phi_inputs.entry(*bb_id).or_default().push((*pred, *val)); }
}
}
if !order.is_empty() { succ_phi_order.insert(*bb_id, order); }
}
// Pre-declare block parameter counts per successor to avoid late appends
for (succ, order) in succ_phi_order.iter() {
if let Some(idx) = bb_ids.iter().position(|x| x == succ) {
builder.ensure_block_params_i64(idx, order.len());
}
}
}
// Build successor → phi order and predeclare block params
let succ_phi_order: std::collections::HashMap<crate::mir::BasicBlockId, Vec<crate::mir::ValueId>> =
self.build_phi_succords(func, &bb_ids, builder, enable_phi_min);
// Decide ABI: typed or i64-only
let native_f64 = cfg_now.native_f64;
let native_bool = cfg_now.native_bool;
@ -370,7 +218,7 @@ impl LowerCore {
if let crate::mir::MirInstruction::Phi { dst: d2, inputs } = ins {
if d2 == dst {
if let Some((_, val)) = inputs.iter().find(|(pred, _)| pred == bb_id) {
ops::push_value_if_known_or_param(self, builder, val);
self.push_value_if_known_or_param(builder, val);
cnt += 1;
}
}
@ -389,7 +237,7 @@ impl LowerCore {
if let crate::mir::MirInstruction::Phi { dst: d2, inputs } = ins {
if d2 == dst {
if let Some((_, val)) = inputs.iter().find(|(pred, _)| pred == bb_id) {
ops::push_value_if_known_or_param(self, builder, val);
self.push_value_if_known_or_param(builder, val);
cnt += 1;
}
}
@ -421,7 +269,7 @@ impl LowerCore {
if let crate::mir::MirInstruction::Phi { dst: d2, inputs } = ins {
if d2 == dst {
if let Some((_, val)) = inputs.iter().find(|(pred, _)| pred == bb_id) {
ops::push_value_if_known_or_param(self, builder, val);
self.push_value_if_known_or_param(builder, val);
cnt += 1;
}
}
@ -445,115 +293,11 @@ impl LowerCore {
}
}
builder.end_function();
if std::env::var("NYASH_JIT_DUMP").ok().as_deref() == Some("1") {
let succs = succ_phi_order.len();
eprintln!("[JIT] cfg: blocks={} phi_succ={} (phi_min={})", bb_ids.len(), succs, enable_phi_min);
if enable_phi_min {
let mut total_phi_slots: usize = 0;
let mut total_phi_b1_slots: usize = 0;
for (succ, order) in succ_phi_order.iter() {
let mut preds_set: std::collections::BTreeSet<i64> = std::collections::BTreeSet::new();
let mut phi_lines: Vec<String> = Vec::new();
if let Some(bb_succ) = func.blocks.get(succ) {
for ins in bb_succ.instructions.iter() {
if let crate::mir::MirInstruction::Phi { dst, inputs } = ins {
// collect preds for block-level summary
for (pred, _) in inputs.iter() { preds_set.insert(pred.0 as i64); }
// build detailed mapping text: dst<-pred:val,...
let mut pairs: Vec<String> = Vec::new();
for (pred, val) in inputs.iter() {
pairs.push(format!("{}:{}", pred.0, val.0));
}
// Heuristics: boolean PHI if (1) pre-analysis marked it, or
// (2) all inputs look boolean-like (from bool producers or 0/1 const), or
// (3) used as a branch condition somewhere.
let used_as_branch = func.blocks.values().any(|bbx| {
if let Some(MirInstruction::Branch { condition, .. }) = &bbx.terminator { condition == dst } else { false }
});
let is_b1 = self.bool_phi_values.contains(dst)
|| inputs.iter().all(|(_, v)| {
self.bool_values.contains(v) || self.known_i64.get(v).map(|&iv| iv == 0 || iv == 1).unwrap_or(false)
})
|| used_as_branch;
let tag = if is_b1 { " (b1)" } else { "" };
phi_lines.push(format!(" dst v{}{} <- {}", dst.0, tag, pairs.join(", ")));
total_phi_slots += 1;
if is_b1 { total_phi_b1_slots += 1; }
}
}
}
let preds_list: Vec<String> = preds_set.into_iter().map(|p| p.to_string()).collect();
eprintln!("[JIT] phi: bb={} slots={} preds={}", succ.0, order.len(), preds_list.join("|"));
for ln in phi_lines { eprintln!("[JIT]{}", ln); }
}
eprintln!("[JIT] phi_summary: total_slots={} b1_slots={}", total_phi_slots, total_phi_b1_slots);
}
}
// Dump CFG/PHI diagnostics
self.dump_phi_cfg(&succ_phi_order, func, bb_ids.len(), enable_phi_min);
Ok(())
}
/// Push a value onto the builder stack if it is a known i64 const or a parameter.
pub(super) fn push_value_if_known_or_param(&self, b: &mut dyn IRBuilder, id: &ValueId) {
// Prefer materialized locals first (e.g., PHI stored into a local slot)
if let Some(slot) = self.local_index.get(id).copied() {
b.load_local_i64(slot);
return;
}
if self.phi_values.contains(id) {
// Multi-PHI: find the param index for this phi in the current block
// We don't have the current block id here; rely on builder's current block context and our stored index being positional.
// As an approximation, prefer position 0 if unknown.
let pos = self.phi_param_index.iter().find_map(|((_, vid), idx)| if vid == id { Some(*idx) } else { None }).unwrap_or(0);
// Use b1 loader for boolean PHIs when enabled
if crate::jit::config::current().native_bool && self.bool_phi_values.contains(id) {
b.push_block_param_b1_at(pos);
} else {
b.push_block_param_i64_at(pos);
}
return;
}
if let Some(pidx) = self.param_index.get(id).copied() {
b.emit_param_i64(pidx);
return;
}
if let Some(v) = self.known_i64.get(id).copied() {
b.emit_const_i64(v);
return;
}
}
fn cover_if_supported(&mut self, instr: &MirInstruction) {
use crate::mir::MirInstruction as I;
let supported = matches!(
instr,
I::Const { .. }
| I::Copy { .. }
| I::Cast { .. }
| I::TypeCheck { .. }
| I::TypeOp { .. }
| I::BinOp { .. }
| I::Compare { .. }
| I::Jump { .. }
| I::Branch { .. }
| I::Return { .. }
| I::Call { .. }
| I::BoxCall { .. }
| I::ArrayGet { .. }
| I::ArraySet { .. }
| I::NewBox { .. }
| I::Store { .. }
| I::Load { .. }
| I::Phi { .. }
// PrintはJIT経路では未対応VMにフォールバックしてコンソール出力を保持
// | I::Print { .. }
| I::Debug { .. }
| I::ExternCall { .. }
| I::Safepoint
| I::Nop
| I::PluginInvoke { .. }
);
if supported { self.covered += 1; } else { self.unsupported += 1; }
}
fn try_emit(&mut self, b: &mut dyn IRBuilder, instr: &MirInstruction, cur_bb: crate::mir::BasicBlockId, func: &crate::mir::MirFunction) -> Result<(), String> {
use crate::mir::MirInstruction as I;
@ -696,144 +440,10 @@ impl LowerCore {
}
}
I::PluginInvoke { dst, box_val, method, args, .. } => {
// Minimal PluginInvoke footing (AOT strict path):
// - Python3メソッドimport/getattr/callは実Emitする型/引数はシム側でTLV化
// - PyRuntimeBox.birth/eval と IntegerBox.birth は no-op許容
let bt = self.box_type_map.get(box_val).cloned().unwrap_or_default();
let m = method.as_str();
// import/getattr/call 実Emit
if (bt == "PyRuntimeBox" && (m == "import")) {
let argc = 1 + args.len();
// push receiver param index (a0) if known
if let Some(pidx) = self.param_index.get(box_val).copied() { b.emit_param_i64(pidx); } else { b.emit_const_i64(-1); }
let decision = crate::jit::policy::invoke::decide_box_method(&bt, m, argc, dst.is_some());
if let crate::jit::policy::invoke::InvokeDecision::PluginInvoke { type_id, method_id, .. } = decision {
b.emit_plugin_invoke(type_id, method_id, argc, dst.is_some());
if let Some(d) = dst { self.handle_values.insert(*d); }
} else { if dst.is_some() { b.emit_const_i64(0); } }
} else if (bt == "PyRuntimeBox" && (m == "getattr" || m == "call")) {
// getattr/call invoked via PyRuntimeBox helper形式 → by-nameで解決
let argc = 1 + args.len();
// push receiver param index (a0) if known
if let Some(pidx) = self.param_index.get(box_val).copied() { b.emit_param_i64(pidx); } else { b.emit_const_i64(-1); }
// push primary arguments if availablea1, a2 ...
for a in args.iter() { self.push_value_if_known_or_param(b, a); }
b.emit_plugin_invoke_by_name(m, argc, dst.is_some());
if let Some(d) = dst {
self.handle_values.insert(*d);
// Store handle result into a local slot so it can be used as argument later
let slot = *self.local_index.entry(*d).or_insert_with(|| { let id = self.next_local; self.next_local += 1; id });
b.store_local_i64(slot);
}
} else if self.handle_values.contains(box_val) && (m == "getattr" || m == "call") {
let argc = 1 + args.len();
// push receiver handle/param index if possible (here receiver is a handle result previously returned)
// We cannot reconstruct handle here; pass -1 to allow shim fallback.
b.emit_const_i64(-1);
for a in args.iter() { self.push_value_if_known_or_param(b, a); }
b.emit_plugin_invoke_by_name(m, argc, dst.is_some());
if let Some(d) = dst {
self.handle_values.insert(*d);
let slot = *self.local_index.entry(*d).or_insert_with(|| { let id = self.next_local; self.next_local += 1; id });
b.store_local_i64(slot);
}
} else if (bt == "PyRuntimeBox" && (m == "birth" || m == "eval"))
|| (bt == "IntegerBox" && m == "birth")
|| (bt == "StringBox" && m == "birth")
|| (bt == "ConsoleBox" && m == "birth") {
if dst.is_some() { b.emit_const_i64(0); }
} else {
self.unsupported += 1;
}
self.lower_plugin_invoke(b, &dst, &box_val, method.as_str(), args, func)?;
}
I::ExternCall { dst, iface_name, method_name, args, .. } => {
// Minimal extern→plugin bridge: env.console.log/println を ConsoleBox に委譲
if iface_name == "env.console" && (method_name == "log" || method_name == "println") {
// Ensure we have a ConsoleBox handle on the stack
b.emit_host_call("nyash.console.birth_h", 0, true);
// Push first argument if known/param
if let Some(arg0) = args.get(0) { self.push_value_if_known_or_param(b, arg0); }
// Resolve and emit plugin_invoke for ConsoleBox.method
let decision = crate::jit::policy::invoke::decide_box_method("ConsoleBox", method_name, 2, dst.is_some());
if let crate::jit::policy::invoke::InvokeDecision::PluginInvoke { type_id, method_id, .. } = decision {
b.emit_plugin_invoke(type_id, method_id, 2, dst.is_some());
} else {
// Fallback: drop result if any
if dst.is_some() { b.emit_const_i64(0); }
}
} else {
// Await bridge: env.future.await(fut) → await_h + ok_h/err_h select
if iface_name == "env.future" && method_name == "await" {
// Load future: prefer param, then local, then known const, else -1 scan
if let Some(arg0) = args.get(0) {
if let Some(pidx) = self.param_index.get(arg0).copied() {
b.emit_param_i64(pidx);
} else if let Some(slot) = self.local_index.get(arg0).copied() {
b.load_local_i64(slot);
} else if let Some(v) = self.known_i64.get(arg0).copied() {
b.emit_const_i64(v);
} else {
b.emit_const_i64(-1);
}
} else {
b.emit_const_i64(-1);
}
// await_h → handle (0 on timeout)
b.emit_host_call(crate::jit::r#extern::r#async::SYM_FUTURE_AWAIT_H, 1, true);
let hslot = { let id = self.next_local; self.next_local += 1; id };
b.store_local_i64(hslot);
// ok_h(handle)
b.load_local_i64(hslot);
b.emit_host_call(crate::jit::r#extern::result::SYM_RESULT_OK_H, 1, true);
let ok_slot = { let id = self.next_local; self.next_local += 1; id };
b.store_local_i64(ok_slot);
// err_h(0) => Timeout
b.emit_const_i64(0);
b.emit_host_call(crate::jit::r#extern::result::SYM_RESULT_ERR_H, 1, true);
let err_slot = { let id = self.next_local; self.next_local += 1; id };
b.store_local_i64(err_slot);
// Select by (handle==0)
b.load_local_i64(hslot);
b.emit_const_i64(0);
b.emit_compare(crate::jit::lower::builder::CmpKind::Eq);
b.load_local_i64(err_slot);
b.load_local_i64(ok_slot);
b.emit_select_i64();
if let Some(d) = dst {
self.handle_values.insert(*d);
let slot = *self.local_index.entry(*d).or_insert_with(|| { let id = self.next_local; self.next_local += 1; id });
b.store_local_i64(slot);
} else {
// drop
}
return Ok(());
}
// Async spawn bridge: env.future.spawn_instance(recv, method_name, args...)
if iface_name == "env.future" && method_name == "spawn_instance" {
// Stack layout for hostcall: argc_total, a0(recv), a1(method_name), a2(first payload)
// 1) receiver
if let Some(recv) = args.get(0) {
if let Some(pidx) = self.param_index.get(recv).copied() { b.emit_param_i64(pidx); } else { b.emit_const_i64(-1); }
} else { b.emit_const_i64(-1); }
// 2) method name (best-effort)
if let Some(meth) = args.get(1) { self.push_value_if_known_or_param(b, meth); } else { b.emit_const_i64(0); }
// 3) first payload argument if present
if let Some(arg2) = args.get(2) { self.push_value_if_known_or_param(b, arg2); } else { b.emit_const_i64(0); }
// argc_total = explicit args including method name and payload (exclude receiver)
let argc_total = args.len().saturating_sub(1).max(0);
b.emit_const_i64(argc_total as i64);
// Call spawn shim; it returns Future handle
b.emit_host_call(crate::jit::r#extern::r#async::SYM_FUTURE_SPAWN_INSTANCE3_I64, 4, true);
if let Some(d) = dst {
self.handle_values.insert(*d);
let slot = *self.local_index.entry(*d).or_insert_with(|| { let id = self.next_local; self.next_local += 1; id });
b.store_local_i64(slot);
}
return Ok(());
}
// Unknown extern: strictではno-opにしてfailを避ける
if dst.is_some() { b.emit_const_i64(0); }
}
self.lower_extern_call(b, &dst, iface_name.as_str(), method_name.as_str(), args, func)?;
}
I::Cast { dst, value, target_type } => {
// Minimal cast footing: materialize source when param/known
@ -968,6 +578,9 @@ impl LowerCore {
}
}
I::BoxCall { box_val: array, method, args, dst, .. } => {
// Clean path: delegate to ops_ext and return
let _ = self.lower_box_call(func, b, &array, method.as_str(), args, dst.clone())?;
return Ok(());
if super::core_hostcall::lower_boxcall_simple_reads(b, &self.param_index, &self.known_i64, array, method.as_str(), args, dst.clone()) {
// handled in helper (read-only simple methods)
} else if matches!(method.as_str(), "sin" | "cos" | "abs" | "min" | "max") {
@ -981,7 +594,7 @@ impl LowerCore {
args,
dst.clone(),
);
} else if std::env::var("NYASH_USE_PLUGIN_BUILTINS").ok().as_deref() == Some("1") {
} else if false /* moved to ops_ext: NYASH_USE_PLUGIN_BUILTINS */ {
// StringBoxlength/is_empty/charCodeAt: policy+observe経由に統一
if matches!(method.as_str(), "length" | "is_empty" | "charCodeAt") {
// receiver

View File

@ -0,0 +1,117 @@
use std::collections::{HashMap, HashSet, BTreeSet};
use crate::mir::{BasicBlockId, MirFunction, MirInstruction, ValueId};
use super::super::builder::IRBuilder;
use super::super::core_ops; // ensure module link remains
use super::LowerCore;
impl LowerCore {
pub(crate) fn analyze(&mut self, func: &MirFunction, bb_ids: &Vec<BasicBlockId>) {
// Seed boolean lattice with boolean parameters from MIR signature
if !func.signature.params.is_empty() {
for (idx, vid) in func.params.iter().copied().enumerate() {
if let Some(mt) = func.signature.params.get(idx) {
if matches!(mt, crate::mir::MirType::Bool) {
self.bool_values.insert(vid);
}
}
}
}
// Pre-scan to classify boolean-producing values and propagate via Copy/Phi/Load-Store heuristics.
self.bool_values.clear();
let mut copy_edges: Vec<(ValueId, ValueId)> = Vec::new();
let mut phi_defs: Vec<(ValueId, Vec<ValueId>)> = Vec::new();
let mut stores: Vec<(ValueId, ValueId)> = Vec::new(); // (ptr, value)
let mut loads: Vec<(ValueId, ValueId)> = Vec::new(); // (dst, ptr)
for bb in bb_ids.iter() {
if let Some(block) = func.blocks.get(bb) {
for ins in block.instructions.iter() {
match ins {
MirInstruction::Compare { dst, .. } => { self.bool_values.insert(*dst); }
MirInstruction::Const { dst, value } => {
if let crate::mir::ConstValue::Bool(_) = value { self.bool_values.insert(*dst); }
}
MirInstruction::Copy { dst, src } => { copy_edges.push((*dst, *src)); }
MirInstruction::Phi { dst, inputs } => {
self.phi_values.insert(*dst);
let ins: Vec<ValueId> = inputs.iter().map(|(_, v)| *v).collect();
phi_defs.push((*dst, ins));
}
MirInstruction::Store { ptr, value } => { stores.push((*ptr, *value)); }
MirInstruction::Load { dst, ptr } => { loads.push((*dst, *ptr)); }
_ => {}
}
}
}
}
// Fixed-point propagation
let mut store_bool_ptrs: HashSet<ValueId> = HashSet::new();
let mut changed = true;
while changed {
changed = false;
// Copy propagation
for (dst, src) in copy_edges.iter().copied() {
if self.bool_values.contains(&src) && !self.bool_values.contains(&dst) {
self.bool_values.insert(dst);
changed = true;
}
if store_bool_ptrs.contains(&src) && !store_bool_ptrs.contains(&dst) {
store_bool_ptrs.insert(dst);
changed = true;
}
}
// Store marking
for (ptr, val) in stores.iter().copied() {
if self.bool_values.contains(&val) && !store_bool_ptrs.contains(&ptr) {
store_bool_ptrs.insert(ptr);
changed = true;
}
}
// Load propagation
for (dst, ptr) in loads.iter().copied() {
if store_bool_ptrs.contains(&ptr) && !self.bool_values.contains(&dst) {
self.bool_values.insert(dst);
changed = true;
}
}
// PHI closure for value booleans
for (dst, inputs) in phi_defs.iter() {
if inputs.iter().all(|v| self.bool_values.contains(v)) && !self.bool_values.contains(dst) {
self.bool_values.insert(*dst);
self.bool_phi_values.insert(*dst);
changed = true;
}
}
// PHI closure for pointer aliases
for (dst, inputs) in phi_defs.iter() {
if inputs.iter().all(|v| store_bool_ptrs.contains(v)) && !store_bool_ptrs.contains(dst) {
store_bool_ptrs.insert(*dst);
changed = true;
}
}
}
// PHI statistics
let mut total_phi_slots: usize = 0;
let mut total_phi_b1_slots: usize = 0;
for (dst, inputs) in phi_defs.iter() {
total_phi_slots += 1;
let used_as_branch = func.blocks.values().any(|bbx| {
if let Some(MirInstruction::Branch { condition, .. }) = &bbx.terminator { condition == dst } else { false }
});
let is_b1 = self.bool_phi_values.contains(dst)
|| inputs.iter().all(|v| {
self.bool_values.contains(v) || self.known_i64.get(v).map(|&iv| iv == 0 || iv == 1).unwrap_or(false)
})
|| used_as_branch;
if is_b1 { total_phi_b1_slots += 1; }
}
if total_phi_slots > 0 {
crate::jit::rt::phi_total_inc(total_phi_slots as u64);
crate::jit::rt::phi_b1_inc(total_phi_b1_slots as u64);
self.last_phi_total = total_phi_slots as u64;
self.last_phi_b1 = total_phi_b1_slots as u64;
}
}
}

78
src/jit/lower/core/cfg.rs Normal file
View File

@ -0,0 +1,78 @@
use std::collections::HashMap;
use crate::mir::{BasicBlockId, MirFunction, MirInstruction};
use super::super::builder::IRBuilder;
use super::LowerCore;
impl LowerCore {
pub(crate) fn build_phi_succords(
&mut self,
func: &MirFunction,
bb_ids: &Vec<BasicBlockId>,
builder: &mut dyn IRBuilder,
enable_phi_min: bool,
) -> HashMap<BasicBlockId, Vec<crate::mir::ValueId>> {
let mut succ_phi_order: HashMap<BasicBlockId, Vec<crate::mir::ValueId>> = HashMap::new();
if !enable_phi_min { return succ_phi_order; }
for (bb_id, bb) in func.blocks.iter() {
let mut order: Vec<crate::mir::ValueId> = Vec::new();
for ins in bb.instructions.iter() {
if let MirInstruction::Phi { dst, .. } = ins { order.push(*dst); }
}
if !order.is_empty() { succ_phi_order.insert(*bb_id, order); }
}
// Pre-declare block parameter counts per successor to avoid late appends
for (succ, order) in succ_phi_order.iter() {
if let Some(idx) = bb_ids.iter().position(|x| x == succ) {
builder.ensure_block_params_i64(idx, order.len());
}
}
succ_phi_order
}
pub(crate) fn dump_phi_cfg(
&self,
succ_phi_order: &HashMap<BasicBlockId, Vec<crate::mir::ValueId>>,
func: &MirFunction,
blocks_len: usize,
enable_phi_min: bool,
) {
if std::env::var("NYASH_JIT_DUMP").ok().as_deref() != Some("1") { return; }
let succs = succ_phi_order.len();
eprintln!("[JIT] cfg: blocks={} phi_succ={} (phi_min={})", blocks_len, succs, enable_phi_min);
if enable_phi_min {
let mut total_phi_slots: usize = 0;
let mut total_phi_b1_slots: usize = 0;
for (succ, order) in succ_phi_order.iter() {
let mut preds_set: std::collections::BTreeSet<i64> = std::collections::BTreeSet::new();
let mut phi_lines: Vec<String> = Vec::new();
if let Some(bb_succ) = func.blocks.get(succ) {
for ins in bb_succ.instructions.iter() {
if let MirInstruction::Phi { dst, inputs } = ins {
for (pred, _) in inputs.iter() { preds_set.insert(pred.0 as i64); }
let mut pairs: Vec<String> = Vec::new();
for (pred, val) in inputs.iter() { pairs.push(format!("{}:{}", pred.0, val.0)); }
let used_as_branch = func.blocks.values().any(|bbx| {
if let Some(MirInstruction::Branch { condition, .. }) = &bbx.terminator { condition == dst } else { false }
});
let is_b1 = self.bool_phi_values.contains(dst)
|| inputs.iter().all(|(_, v)| {
self.bool_values.contains(v) || self.known_i64.get(v).map(|&iv| iv == 0 || iv == 1).unwrap_or(false)
})
|| used_as_branch;
if is_b1 { total_phi_b1_slots += 1; }
total_phi_slots += 1;
phi_lines.push(format!(" phi: bb={} dst={} inputs=[{}] (b1={})",
succ.0, dst.0, pairs.join(","), is_b1));
}
}
}
let preds_list: Vec<String> = preds_set.into_iter().map(|p| p.to_string()).collect();
eprintln!("[JIT] phi: bb={} slots={} preds={}", succ.0, order.len(), preds_list.join("|"));
for ln in phi_lines { eprintln!("[JIT]{}", ln); }
}
eprintln!("[JIT] phi_summary: total_slots={} b1_slots={}", total_phi_slots, total_phi_b1_slots);
}
}
}

View File

@ -0,0 +1,331 @@
use super::super::builder::IRBuilder;
use super::super::core::LowerCore;
use crate::mir::{MirFunction, ValueId};
impl LowerCore {
pub fn lower_plugin_invoke(
&mut self,
b: &mut dyn IRBuilder,
dst: &Option<ValueId>,
box_val: &ValueId,
method: &str,
args: &Vec<ValueId>,
_func: &MirFunction,
) -> Result<(), String> {
// Copied logic from core.rs PluginInvoke arm (scoped to PyRuntimeBox path)
let bt = self.box_type_map.get(box_val).cloned().unwrap_or_default();
let m = method;
if (bt == "PyRuntimeBox" && (m == "import")) {
let argc = 1 + args.len();
if let Some(pidx) = self.param_index.get(box_val).copied() { b.emit_param_i64(pidx); } else { b.emit_const_i64(-1); }
let decision = crate::jit::policy::invoke::decide_box_method(&bt, m, argc, dst.is_some());
if let crate::jit::policy::invoke::InvokeDecision::PluginInvoke { type_id, method_id, .. } = decision {
b.emit_plugin_invoke(type_id, method_id, argc, dst.is_some());
if let Some(d) = dst { self.handle_values.insert(*d); }
} else { if dst.is_some() { b.emit_const_i64(0); } }
} else if (bt == "PyRuntimeBox" && (m == "getattr" || m == "call")) {
let argc = 1 + args.len();
if let Some(pidx) = self.param_index.get(box_val).copied() { b.emit_param_i64(pidx); } else { b.emit_const_i64(-1); }
for a in args.iter() { self.push_value_if_known_or_param(b, a); }
b.emit_plugin_invoke_by_name(m, argc, dst.is_some());
if let Some(d) = dst {
self.handle_values.insert(*d);
let slot = *self.local_index.entry(*d).or_insert_with(|| { let id = self.next_local; self.next_local += 1; id });
b.store_local_i64(slot);
}
} else if self.handle_values.contains(box_val) && (m == "getattr" || m == "call") {
let argc = 1 + args.len();
b.emit_const_i64(-1);
for a in args.iter() { self.push_value_if_known_or_param(b, a); }
b.emit_plugin_invoke_by_name(m, argc, dst.is_some());
if let Some(d) = dst {
self.handle_values.insert(*d);
let slot = *self.local_index.entry(*d).or_insert_with(|| { let id = self.next_local; self.next_local += 1; id });
b.store_local_i64(slot);
}
} else if (bt == "PyRuntimeBox" && (m == "birth" || m == "eval"))
|| (bt == "IntegerBox" && m == "birth")
|| (bt == "StringBox" && m == "birth")
|| (bt == "ConsoleBox" && m == "birth") {
if dst.is_some() { b.emit_const_i64(0); }
} else {
self.unsupported += 1;
}
Ok(())
}
pub fn lower_extern_call(
&mut self,
b: &mut dyn IRBuilder,
dst: &Option<ValueId>,
iface_name: &str,
method_name: &str,
args: &Vec<ValueId>,
_func: &MirFunction,
) -> Result<(), String> {
// env.console.log/println → ConsoleBox に委譲
if iface_name == "env.console" && (method_name == "log" || method_name == "println") {
// Ensure we have a Console handle (hostcall birth shim)
b.emit_host_call("nyash.console.birth_h", 0, true);
// a1: first argument best-effort
if let Some(arg0) = args.get(0) { self.push_value_if_known_or_param(b, arg0); }
// Resolve plugin invoke for ConsoleBox.method
let decision = crate::jit::policy::invoke::decide_box_method("ConsoleBox", method_name, 2, dst.is_some());
if let crate::jit::policy::invoke::InvokeDecision::PluginInvoke { type_id, method_id, .. } = decision {
b.emit_plugin_invoke(type_id, method_id, 2, dst.is_some());
} else if dst.is_some() { b.emit_const_i64(0); }
return Ok(());
}
// env.future.await(fut) → await_h + ok_h/err_h select
if iface_name == "env.future" && method_name == "await" {
if let Some(arg0) = args.get(0) {
if let Some(pidx) = self.param_index.get(arg0).copied() { b.emit_param_i64(pidx); }
else if let Some(slot) = self.local_index.get(arg0).copied() { b.load_local_i64(slot); }
else if let Some(v) = self.known_i64.get(arg0).copied() { b.emit_const_i64(v); }
else { b.emit_const_i64(-1); }
} else { b.emit_const_i64(-1); }
// await_h → handle(0 timeout)
b.emit_host_call(crate::jit::r#extern::r#async::SYM_FUTURE_AWAIT_H, 1, true);
let hslot = { let id = self.next_local; self.next_local += 1; id };
b.store_local_i64(hslot);
// ok_h(handle)
b.load_local_i64(hslot);
b.emit_host_call(crate::jit::r#extern::result::SYM_RESULT_OK_H, 1, true);
let ok_slot = { let id = self.next_local; self.next_local += 1; id };
b.store_local_i64(ok_slot);
// err_h(0)
b.emit_const_i64(0);
b.emit_host_call(crate::jit::r#extern::result::SYM_RESULT_ERR_H, 1, true);
let err_slot = { let id = self.next_local; self.next_local += 1; id };
b.store_local_i64(err_slot);
// select(handle==0 ? err : ok)
b.load_local_i64(hslot);
b.emit_const_i64(0);
b.emit_compare(crate::jit::lower::builder::CmpKind::Eq);
b.load_local_i64(err_slot);
b.load_local_i64(ok_slot);
b.emit_select_i64();
if let Some(d) = dst {
self.handle_values.insert(*d);
let slot = *self.local_index.entry(*d).or_insert_with(|| { let id = self.next_local; self.next_local += 1; id });
b.store_local_i64(slot);
}
return Ok(());
}
// env.future.spawn_instance(recv, method_name, args...)
if iface_name == "env.future" && method_name == "spawn_instance" {
// a0 receiver
if let Some(recv) = args.get(0) {
if let Some(pidx) = self.param_index.get(recv).copied() { b.emit_param_i64(pidx); } else { b.emit_const_i64(-1); }
} else { b.emit_const_i64(-1); }
// a1 method name (best-effort)
if let Some(meth) = args.get(1) { self.push_value_if_known_or_param(b, meth); } else { b.emit_const_i64(0); }
// a2 first payload (optional)
if let Some(a2) = args.get(2) { self.push_value_if_known_or_param(b, a2); } else { b.emit_const_i64(0); }
// argc_total = explicit args including method name and payload (exclude receiver)
let argc_total = args.len().saturating_sub(1).max(0);
b.emit_const_i64(argc_total as i64);
// call spawn shim → Future handle
b.emit_host_call(crate::jit::r#extern::r#async::SYM_FUTURE_SPAWN_INSTANCE3_I64, 4, true);
if let Some(d) = dst {
self.handle_values.insert(*d);
let slot = *self.local_index.entry(*d).or_insert_with(|| { let id = self.next_local; self.next_local += 1; id });
b.store_local_i64(slot);
}
return Ok(());
}
// Unhandled extern path
self.unsupported += 1;
Ok(())
}
pub fn lower_box_call(
&mut self,
func: &MirFunction,
b: &mut dyn IRBuilder,
array: &ValueId,
method: &str,
args: &Vec<ValueId>,
dst: Option<ValueId>,
) -> Result<bool, String> {
// Delegate to existing helpers first
if super::super::core_hostcall::lower_boxcall_simple_reads(b, &self.param_index, &self.known_i64, array, method, args, dst.clone()) {
return Ok(true);
}
if matches!(method, "sin" | "cos" | "abs" | "min" | "max") {
super::super::core_hostcall::lower_math_call(
func,
b,
&self.known_i64,
&self.known_f64,
&self.float_box_values,
method,
args,
dst.clone(),
);
return Ok(true);
}
// Builtins-to-plugin path (subset for String/Array/Map critical ops)
if std::env::var("NYASH_USE_PLUGIN_BUILTINS").ok().as_deref() == Some("1") {
// StringBox (length/is_empty/charCodeAt)
if matches!(method, "length" | "is_empty" | "charCodeAt") {
if let Some(pidx) = self.param_index.get(array).copied() { b.emit_param_i64(pidx); } else { b.emit_const_i64(-1); }
let mut argc = 1usize;
if method == "charCodeAt" {
if let Some(v) = args.get(0) { self.push_value_if_known_or_param(b, v); } else { b.emit_const_i64(0); }
argc = 2;
}
if method == "is_empty" { b.hint_ret_bool(true); }
let decision = crate::jit::policy::invoke::decide_box_method("StringBox", method, argc, dst.is_some());
match decision {
crate::jit::policy::invoke::InvokeDecision::PluginInvoke { type_id, method_id, box_type, .. } => {
b.emit_plugin_invoke(type_id, method_id, argc, dst.is_some());
crate::jit::observe::lower_plugin_invoke(&box_type, method, type_id, method_id, argc);
return Ok(true);
}
crate::jit::policy::invoke::InvokeDecision::HostCall { symbol, .. } => {
crate::jit::observe::lower_hostcall(&symbol, argc, &if argc==1 { ["Handle"][..].to_vec() } else { ["Handle","I64"][..].to_vec() }, "allow", "mapped_symbol");
b.emit_host_call(&symbol, argc, dst.is_some());
return Ok(true);
}
_ => {}
}
}
}
// Array/Map minimal handling
match method {
// Array length variants (length/len)
"len" | "length" => {
if let Ok(ph) = crate::runtime::plugin_loader_unified::get_global_plugin_host().read() {
if let Ok(h) = ph.resolve_method("ArrayBox", "length") {
if let Some(pidx) = self.param_index.get(array).copied() { b.emit_param_i64(pidx); } else { b.emit_const_i64(-1); }
b.emit_plugin_invoke(h.type_id, h.method_id, 1, dst.is_some());
return Ok(true);
}
}
// Hostcall fallback
if let Some(pidx) = self.param_index.get(array).copied() {
crate::jit::observe::lower_hostcall(crate::jit::r#extern::collections::SYM_ANY_LEN_H, 1, &["Handle"], "allow", "mapped_symbol");
b.emit_param_i64(pidx);
b.emit_host_call(crate::jit::r#extern::collections::SYM_ANY_LEN_H, 1, dst.is_some());
} else {
crate::jit::observe::lower_hostcall(crate::jit::r#extern::collections::SYM_ARRAY_LEN, 1, &["I64"], "fallback", "receiver_not_param");
b.emit_const_i64(-1);
b.emit_host_call(crate::jit::r#extern::collections::SYM_ARRAY_LEN, 1, dst.is_some());
}
return Ok(true);
}
// Array push
"push" => {
let argc = 2usize;
// receiver
if let Some(pidx) = self.param_index.get(array).copied() { b.emit_param_i64(pidx); } else { b.emit_const_i64(-1); }
// value
if let Some(v) = args.get(0).and_then(|vid| self.known_i64.get(vid)).copied() { b.emit_const_i64(v); }
else if let Some(v) = args.get(0) { self.push_value_if_known_or_param(b, v); } else { b.emit_const_i64(0); }
// policy decide → plugin / hostcall fallback
let decision = crate::jit::policy::invoke::decide_box_method("ArrayBox", "push", argc, false);
match decision {
crate::jit::policy::invoke::InvokeDecision::PluginInvoke { type_id, method_id, box_type, .. } => {
b.emit_plugin_invoke(type_id, method_id, argc, false);
crate::jit::observe::lower_plugin_invoke(&box_type, "push", type_id, method_id, argc);
}
crate::jit::policy::invoke::InvokeDecision::HostCall { symbol, .. } => {
crate::jit::observe::lower_hostcall(&symbol, argc, &["Handle","I64"], "allow", "mapped_symbol");
b.emit_host_call(&symbol, argc, false);
}
_ => {
// Fallback hostcall
let sym = if self.param_index.get(array).is_some() { crate::jit::r#extern::collections::SYM_ARRAY_PUSH_H } else { crate::jit::r#extern::collections::SYM_ARRAY_PUSH };
let arg_types = if self.param_index.get(array).is_some() { &["Handle","I64"][..] } else { &["I64","I64"][..] };
crate::jit::observe::lower_hostcall(sym, argc, arg_types, "fallback", "policy_or_unknown");
b.emit_host_call(sym, argc, false);
}
}
return Ok(true);
}
// Map ops
"size" | "get" | "has" | "set" => {
let is_set = method == "set";
if is_set && crate::jit::policy::current().read_only { // deny under read-only policy
if let Some(_) = dst { b.emit_const_i64(0); }
return Ok(true);
}
let argc = match method { "size" => 1, "get" | "has" => 2, "set" => 3, _ => 1 };
if let Ok(ph) = crate::runtime::plugin_loader_unified::get_global_plugin_host().read() {
if let Ok(h) = ph.resolve_method("MapBox", method) {
// receiver
if let Some(pidx) = self.param_index.get(array).copied() { b.emit_param_i64(pidx); } else { b.emit_const_i64(-1); }
// args
match method {
"size" => {}
"get" | "has" => {
if let Some(v) = args.get(0) { self.push_value_if_known_or_param(b, v); } else { b.emit_const_i64(0); }
}
"set" => {
if let Some(k) = args.get(0) { self.push_value_if_known_or_param(b, k); } else { b.emit_const_i64(0); }
if let Some(v) = args.get(1) { self.push_value_if_known_or_param(b, v); } else { b.emit_const_i64(0); }
}
_ => {}
}
b.emit_plugin_invoke(h.type_id, h.method_id, argc, dst.is_some());
crate::jit::events::emit_lower(
serde_json::json!({
"id": format!("plugin:{}:{}", h.box_type, method),
"decision":"allow","reason":"plugin_invoke","argc": argc,
"type_id": h.type_id, "method_id": h.method_id
}),
"plugin","<jit>"
);
return Ok(true);
}
}
// Hostcall fallback symbols
if let Some(pidx) = self.param_index.get(array).copied() {
b.emit_param_i64(pidx);
match method {
"size" => b.emit_host_call(crate::jit::r#extern::collections::SYM_MAP_SIZE_H, argc, dst.is_some()),
"get" => {
if let Some(v) = args.get(0) { self.push_value_if_known_or_param(b, v); } else { b.emit_const_i64(0); }
b.emit_host_call(crate::jit::r#extern::collections::SYM_MAP_GET_H, argc, dst.is_some())
}
"has" => {
if let Some(v) = args.get(0) { self.push_value_if_known_or_param(b, v); } else { b.emit_const_i64(0); }
b.emit_host_call(crate::jit::r#extern::collections::SYM_MAP_HAS_H, argc, dst.is_some())
}
"set" => {
if let Some(k) = args.get(0) { self.push_value_if_known_or_param(b, k); } else { b.emit_const_i64(0); }
if let Some(v) = args.get(1) { self.push_value_if_known_or_param(b, v); } else { b.emit_const_i64(0); }
b.emit_host_call(crate::jit::r#extern::collections::SYM_MAP_SET_H, argc, dst.is_some())
}
_ => {}
}
} else {
// receiver unknown
b.emit_const_i64(-1);
match method {
"size" => b.emit_host_call(crate::jit::r#extern::collections::SYM_MAP_SIZE, argc, dst.is_some()),
"get" => {
if let Some(v) = args.get(0) { self.push_value_if_known_or_param(b, v); } else { b.emit_const_i64(0); }
b.emit_host_call(crate::jit::r#extern::collections::SYM_MAP_GET_H, argc, dst.is_some())
}
"has" => {
if let Some(v) = args.get(0) { self.push_value_if_known_or_param(b, v); } else { b.emit_const_i64(0); }
b.emit_host_call(crate::jit::r#extern::collections::SYM_MAP_HAS_H, argc, dst.is_some())
}
"set" => {
if let Some(k) = args.get(0) { self.push_value_if_known_or_param(b, k); } else { b.emit_const_i64(0); }
if let Some(v) = args.get(1) { self.push_value_if_known_or_param(b, v); } else { b.emit_const_i64(0); }
b.emit_host_call(crate::jit::r#extern::collections::SYM_MAP_SET, argc, dst.is_some())
}
_ => {}
}
}
return Ok(true);
}
_ => {}
}
// Not handled here
Ok(false)
}
}

View File

@ -24,6 +24,28 @@ impl LowerCore {
}
pub fn lower_binop(&mut self, b: &mut dyn IRBuilder, op: &BinaryOp, lhs: &ValueId, rhs: &ValueId, dst: &ValueId, func: &MirFunction) {
// Optional: consult unified grammar for operator strategy (non-invasive logging)
if std::env::var("NYASH_GRAMMAR_DIFF").ok().as_deref() == Some("1") {
match op {
BinaryOp::Add => {
let strat = crate::grammar::engine::get().add_coercion_strategy();
crate::jit::events::emit("grammar","add", None, None, serde_json::json!({"coercion": strat}));
}
BinaryOp::Sub => {
let strat = crate::grammar::engine::get().sub_coercion_strategy();
crate::jit::events::emit("grammar","sub", None, None, serde_json::json!({"coercion": strat}));
}
BinaryOp::Mul => {
let strat = crate::grammar::engine::get().mul_coercion_strategy();
crate::jit::events::emit("grammar","mul", None, None, serde_json::json!({"coercion": strat}));
}
BinaryOp::Div => {
let strat = crate::grammar::engine::get().div_coercion_strategy();
crate::jit::events::emit("grammar","div", None, None, serde_json::json!({"coercion": strat}));
}
_ => {}
}
}
// Route string-like addition to hostcall (handle,handle)
if crate::jit::config::current().hostcall {
if matches!(op, BinaryOp::Add) {
@ -111,3 +133,55 @@ impl LowerCore {
pub fn lower_jump(&mut self, b: &mut dyn IRBuilder) { b.emit_jump(); }
pub fn lower_branch(&mut self, b: &mut dyn IRBuilder) { b.emit_branch(); }
}
// Methods moved from core.rs to reduce file size and centralize op helpers
impl LowerCore {
// Push a value if known or param/local/phi
pub(super) fn push_value_if_known_or_param(&self, b: &mut dyn IRBuilder, id: &ValueId) {
if let Some(slot) = self.local_index.get(id).copied() { b.load_local_i64(slot); return; }
if self.phi_values.contains(id) {
let pos = self.phi_param_index.iter().find_map(|((_, vid), idx)| if vid == id { Some(*idx) } else { None }).unwrap_or(0);
if crate::jit::config::current().native_bool && self.bool_phi_values.contains(id) {
b.push_block_param_b1_at(pos);
} else {
b.push_block_param_i64_at(pos);
}
return;
}
if let Some(pidx) = self.param_index.get(id).copied() { b.emit_param_i64(pidx); return; }
if let Some(v) = self.known_i64.get(id).copied() { b.emit_const_i64(v); return; }
}
// Coverage helper: increments covered/unsupported counts
pub(super) fn cover_if_supported(&mut self, instr: &crate::mir::MirInstruction) {
use crate::mir::MirInstruction as I;
let supported = matches!(
instr,
I::Const { .. }
| I::Copy { .. }
| I::Cast { .. }
| I::TypeCheck { .. }
| I::TypeOp { .. }
| I::BinOp { .. }
| I::Compare { .. }
| I::Jump { .. }
| I::Branch { .. }
| I::Return { .. }
| I::Call { .. }
| I::BoxCall { .. }
| I::ArrayGet { .. }
| I::ArraySet { .. }
| I::NewBox { .. }
| I::Store { .. }
| I::Load { .. }
| I::Phi { .. }
| I::Debug { .. }
| I::ExternCall { .. }
| I::Safepoint
| I::Nop
| I::PluginInvoke { .. }
);
if supported { self.covered += 1; } else { self.unsupported += 1; }
}
}

View File

@ -60,6 +60,8 @@ pub mod cli;
pub mod runtime;
pub mod runner_plugin_init;
pub mod debug;
// Unified Grammar (Phase 11.9 scaffolding)
pub mod grammar;
#[cfg(target_arch = "wasm32")]
pub mod wasm_test;

View File

@ -57,6 +57,7 @@ pub mod config;
// Runtime system (plugins, registry, etc.)
pub mod runtime;
pub mod debug;
pub mod grammar; // Phase 11.9 unified grammar scaffolding
use nyash_rust::cli::CliConfig;
use runner::NyashRunner;

View File

@ -27,6 +27,11 @@ impl NyashParser {
let operator = BinaryOperator::Or;
self.advance();
let right = self.parse_and()?;
// Non-invasive syntax diff: record binop
if std::env::var("NYASH_GRAMMAR_DIFF").ok().as_deref() == Some("1") {
let ok = crate::grammar::engine::get().syntax_is_allowed_binop("or");
if !ok { eprintln!("[GRAMMAR-DIFF][Parser] binop 'or' not allowed by syntax rules"); }
}
expr = ASTNode::BinaryOp {
operator,
left: Box::new(expr),
@ -46,6 +51,10 @@ impl NyashParser {
let operator = BinaryOperator::And;
self.advance();
let right = self.parse_equality()?;
if std::env::var("NYASH_GRAMMAR_DIFF").ok().as_deref() == Some("1") {
let ok = crate::grammar::engine::get().syntax_is_allowed_binop("and");
if !ok { eprintln!("[GRAMMAR-DIFF][Parser] binop 'and' not allowed by syntax rules"); }
}
expr = ASTNode::BinaryOp {
operator,
left: Box::new(expr),
@ -69,6 +78,11 @@ impl NyashParser {
};
self.advance();
let right = self.parse_comparison()?;
if std::env::var("NYASH_GRAMMAR_DIFF").ok().as_deref() == Some("1") {
let name = match operator { BinaryOperator::Equal=>"eq", BinaryOperator::NotEqual=>"ne", _=>"cmp" };
let ok = crate::grammar::engine::get().syntax_is_allowed_binop(name);
if !ok { eprintln!("[GRAMMAR-DIFF][Parser] binop '{}' not allowed by syntax rules", name); }
}
expr = ASTNode::BinaryOp {
operator,
left: Box::new(expr),
@ -130,6 +144,11 @@ impl NyashParser {
};
self.advance();
let right = self.parse_factor()?;
if std::env::var("NYASH_GRAMMAR_DIFF").ok().as_deref() == Some("1") {
let name = match operator { BinaryOperator::Add=>"add", BinaryOperator::Subtract=>"sub", _=>"term" };
let ok = crate::grammar::engine::get().syntax_is_allowed_binop(name);
if !ok { eprintln!("[GRAMMAR-DIFF][Parser] binop '{}' not allowed by syntax rules", name); }
}
expr = ASTNode::BinaryOp {
operator,
left: Box::new(expr),
@ -155,6 +174,11 @@ impl NyashParser {
};
self.advance();
let right = self.parse_unary()?;
if std::env::var("NYASH_GRAMMAR_DIFF").ok().as_deref() == Some("1") {
let name = match operator { BinaryOperator::Multiply=>"mul", BinaryOperator::Divide=>"div", _=>"mod" };
let ok = crate::grammar::engine::get().syntax_is_allowed_binop(name);
if !ok { eprintln!("[GRAMMAR-DIFF][Parser] binop '{}' not allowed by syntax rules", name); }
}
expr = ASTNode::BinaryOp {
operator,
left: Box::new(expr),

View File

@ -13,8 +13,9 @@ use super::common::ParserUtils;
impl NyashParser {
/// 文をパース
pub(super) fn parse_statement(&mut self) -> Result<ASTNode, ParseError> {
let result = match &self.current_token().token_type {
// 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::BOX => {
self.parse_box_declaration()
},
@ -84,6 +85,33 @@ impl NyashParser {
}
};
// Non-invasive syntax rule check
if std::env::var("NYASH_GRAMMAR_DIFF").ok().as_deref() == Some("1") {
let kw = match start_tok {
TokenType::BOX => Some("box"),
TokenType::GLOBAL => Some("global"),
TokenType::FUNCTION => Some("function"),
TokenType::STATIC => Some("static"),
TokenType::IF => Some("if"),
TokenType::LOOP => Some("loop"),
TokenType::BREAK => Some("break"),
TokenType::RETURN => Some("return"),
TokenType::PRINT => Some("print"),
TokenType::NOWAIT => Some("nowait"),
TokenType::INCLUDE => Some("include"),
TokenType::LOCAL => Some("local"),
TokenType::OUTBOX => Some("outbox"),
TokenType::TRY => Some("try"),
TokenType::THROW => Some("throw"),
TokenType::USING => Some("using"),
TokenType::FROM => Some("from"),
_ => None,
};
if let Some(k) = kw {
let ok = crate::grammar::engine::get().syntax_is_allowed_statement(k);
if !ok { eprintln!("[GRAMMAR-DIFF][Parser] statement '{}' not allowed by syntax rules", k); }
}
}
result
}

View File

@ -6,6 +6,7 @@
*/
use thiserror::Error;
use crate::grammar::engine;
/// トークンの種類を表すenum
#[derive(Debug, Clone, PartialEq)]
@ -385,7 +386,7 @@ impl NyashTokenizer {
}
// キーワードチェック
match identifier.as_str() {
let tok = match identifier.as_str() {
"box" => TokenType::BOX,
"global" => TokenType::GLOBAL,
"singleton" => TokenType::SINGLETON,
@ -425,8 +426,27 @@ impl NyashTokenizer {
"true" => TokenType::TRUE,
"false" => TokenType::FALSE,
"null" => TokenType::NULL,
_ => TokenType::IDENTIFIER(identifier),
_ => TokenType::IDENTIFIER(identifier.clone()),
};
// 統一文法エンジンとの差分チェック(動作は変更しない)
if std::env::var("NYASH_GRAMMAR_DIFF").ok().as_deref() == Some("1") {
// 安全に参照(初期導入のため、存在しない場合は無視)
let kw = engine::get().is_keyword_str(&identifier);
match (&tok, kw) {
(TokenType::IDENTIFIER(_), Some(name)) => {
eprintln!("[GRAMMAR-DIFF] tokenizer=IDENT, grammar=KEYWORD({}) word='{}'", name, identifier);
}
(TokenType::IDENTIFIER(_), None) => {}
// tokenizerがキーワード、grammarが未定義
(t, None) if !matches!(t, TokenType::IDENTIFIER(_)) => {
eprintln!("[GRAMMAR-DIFF] tokenizer=KEYWORD, grammar=IDENT word='{}'", identifier);
}
_ => {}
}
}
tok
}
/// 行コメントをスキップ