fix(mir): PHI検証panic修正 - update_cfg()を検証前に呼び出し
A案実装: debug_verify_phi_inputs呼び出し前にCFG predecessorを更新
修正箇所(7箇所):
- src/mir/builder/phi.rs:50, 73, 132, 143
- src/mir/builder/ops.rs:273, 328, 351
根本原因:
- Branch/Jump命令でsuccessorは即座に更新
- predecessorはupdate_cfg()で遅延再構築
- PHI検証が先に実行されてpredecessor未更新でpanic
解決策:
- 各debug_verify_phi_inputs呼び出し前に
if let Some(func) = self.current_function.as_mut() {
func.update_cfg();
}
を挿入してCFGを同期
影響: if/else文、論理演算子(&&/||)のPHI生成が正常動作
This commit is contained in:
@ -19,10 +19,18 @@ impl MirInterpreter {
|
||||
let saved_fn = self.cur_fn.clone();
|
||||
self.cur_fn = Some(func.signature.name.clone());
|
||||
|
||||
if let Some(args) = arg_vals {
|
||||
for (i, pid) in func.params.iter().enumerate() {
|
||||
let v = args.get(i).cloned().unwrap_or(VMValue::Void);
|
||||
self.regs.insert(*pid, v);
|
||||
match arg_vals {
|
||||
Some(args) => {
|
||||
for (i, pid) in func.params.iter().enumerate() {
|
||||
let v = args.get(i).cloned().unwrap_or(VMValue::Void);
|
||||
self.regs.insert(*pid, v);
|
||||
}
|
||||
}
|
||||
None => {
|
||||
// Seed all parameters with Void so SSA consumers observe defined values.
|
||||
for pid in &func.params {
|
||||
self.regs.insert(*pid, VMValue::Void);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -23,6 +23,34 @@ pub(super) fn try_handle_map_box(
|
||||
if let Some(d) = dst { this.regs.insert(d, VMValue::Void); }
|
||||
return Ok(true);
|
||||
}
|
||||
// Field bridge: treat getField/setField as get/set with string key
|
||||
"getField" => {
|
||||
if args.len() != 1 {
|
||||
return Err(VMError::InvalidInstruction(
|
||||
"MapBox.getField expects 1 arg".into(),
|
||||
));
|
||||
}
|
||||
let k = this.reg_load(args[0])?.to_nyash_box();
|
||||
let ret = mb.get(k);
|
||||
if let Some(d) = dst {
|
||||
this.regs.insert(d, VMValue::from_nyash_box(ret));
|
||||
}
|
||||
return Ok(true);
|
||||
}
|
||||
"setField" => {
|
||||
if args.len() != 2 {
|
||||
return Err(VMError::InvalidInstruction(
|
||||
"MapBox.setField expects 2 args".into(),
|
||||
));
|
||||
}
|
||||
let k = this.reg_load(args[0])?.to_nyash_box();
|
||||
let v = this.reg_load(args[1])?.to_nyash_box();
|
||||
let ret = mb.set(k, v);
|
||||
if let Some(d) = dst {
|
||||
this.regs.insert(d, VMValue::from_nyash_box(ret));
|
||||
}
|
||||
return Ok(true);
|
||||
}
|
||||
"set" => {
|
||||
if args.len() != 2 { return Err(VMError::InvalidInstruction("MapBox.set expects 2 args".into())); }
|
||||
let k = this.reg_load(args[0])?.to_nyash_box();
|
||||
|
||||
@ -839,8 +839,8 @@ impl super::MirBuilder {
|
||||
self.variable_map.insert(p.clone(), pid);
|
||||
}
|
||||
}
|
||||
let program_ast = function_lowering::wrap_in_program(body);
|
||||
let _last = self.build_expression(program_ast)?;
|
||||
// Lower statements in sequence to preserve def→use order
|
||||
let _last = self.cf_block(body)?;
|
||||
if !returns_value && !self.is_current_block_terminated() {
|
||||
let void_val = crate::mir::builder::emission::constant::emit_void(self);
|
||||
self.emit_instruction(MirInstruction::Return {
|
||||
|
||||
@ -54,6 +54,15 @@ impl super::MirBuilder {
|
||||
self
|
||||
.value_types
|
||||
.insert(pid, super::MirType::Box("ArrayBox".to_string()));
|
||||
// Explicitly call birth() to initialize internal state
|
||||
self.emit_instruction(MirInstruction::BoxCall {
|
||||
dst: None,
|
||||
box_val: pid,
|
||||
method: "birth".to_string(),
|
||||
method_id: None,
|
||||
args: vec![],
|
||||
effects: super::EffectMask::MUT,
|
||||
})?;
|
||||
if let Some(args) = script_args.as_ref() {
|
||||
for arg in args {
|
||||
let val = crate::mir::builder::emission::constant::emit_string(self, arg.clone());
|
||||
@ -75,7 +84,8 @@ impl super::MirBuilder {
|
||||
}
|
||||
self.variable_map.insert(p.clone(), pid);
|
||||
}
|
||||
let lowered = self.build_expression(program_ast);
|
||||
// Lower statements in order to preserve def→use
|
||||
let lowered = self.cf_block(body.clone());
|
||||
self.variable_map = saved_var_map;
|
||||
lowered
|
||||
} else {
|
||||
|
||||
@ -219,6 +219,15 @@ impl super::MirBuilder {
|
||||
box_type: "ArrayBox".to_string(),
|
||||
args: vec![],
|
||||
})?;
|
||||
// Explicit birth() to satisfy runtime invariant (NewBox→birth)
|
||||
self.emit_instruction(MirInstruction::BoxCall {
|
||||
dst: None,
|
||||
box_val: arr_id,
|
||||
method: "birth".to_string(),
|
||||
method_id: None,
|
||||
args: vec![],
|
||||
effects: super::EffectMask::MUT,
|
||||
})?;
|
||||
self.value_origin_newbox
|
||||
.insert(arr_id, "ArrayBox".to_string());
|
||||
self
|
||||
@ -244,6 +253,15 @@ impl super::MirBuilder {
|
||||
box_type: "MapBox".to_string(),
|
||||
args: vec![],
|
||||
})?;
|
||||
// Explicit birth() to satisfy runtime invariant (NewBox→birth)
|
||||
self.emit_instruction(MirInstruction::BoxCall {
|
||||
dst: None,
|
||||
box_val: map_id,
|
||||
method: "birth".to_string(),
|
||||
method_id: None,
|
||||
args: vec![],
|
||||
effects: super::EffectMask::MUT,
|
||||
})?;
|
||||
self
|
||||
.value_origin_newbox
|
||||
.insert(map_id, "MapBox".to_string());
|
||||
|
||||
@ -269,6 +269,9 @@ impl super::MirBuilder {
|
||||
self.start_new_block(rhs_join)?;
|
||||
let rhs_bool = self.value_gen.next();
|
||||
let inputs = vec![(rhs_true_exit, t_id), (rhs_false_exit, f_id)];
|
||||
if let Some(func) = self.current_function.as_mut() {
|
||||
func.update_cfg();
|
||||
}
|
||||
if let (Some(func), Some(cur_bb)) = (&self.current_function, self.current_block) {
|
||||
crate::mir::phi_core::common::debug_verify_phi_inputs(func, cur_bb, &inputs);
|
||||
}
|
||||
@ -324,6 +327,9 @@ impl super::MirBuilder {
|
||||
self.start_new_block(rhs_join)?;
|
||||
let rhs_bool = self.value_gen.next();
|
||||
let inputs = vec![(rhs_true_exit, t_id), (rhs_false_exit, f_id)];
|
||||
if let Some(func) = self.current_function.as_mut() {
|
||||
func.update_cfg();
|
||||
}
|
||||
if let (Some(func), Some(cur_bb)) = (&self.current_function, self.current_block) {
|
||||
crate::mir::phi_core::common::debug_verify_phi_inputs(func, cur_bb, &inputs);
|
||||
}
|
||||
@ -347,6 +353,9 @@ impl super::MirBuilder {
|
||||
// Result PHI (bool)
|
||||
let result_val = self.value_gen.next();
|
||||
let inputs = vec![(then_exit_block, then_value_raw), (else_exit_block, else_value_raw)];
|
||||
if let Some(func) = self.current_function.as_mut() {
|
||||
func.update_cfg();
|
||||
}
|
||||
if let (Some(func), Some(cur_bb)) = (&self.current_function, self.current_block) {
|
||||
crate::mir::phi_core::common::debug_verify_phi_inputs(func, cur_bb, &inputs);
|
||||
}
|
||||
|
||||
@ -46,6 +46,9 @@ impl MirBuilder {
|
||||
let else_pred = else_exit_block_opt.unwrap_or(else_block);
|
||||
let merged = self.value_gen.next();
|
||||
let inputs = vec![(then_pred, then_v), (else_pred, else_v)];
|
||||
if let Some(func) = self.current_function.as_mut() {
|
||||
func.update_cfg();
|
||||
}
|
||||
if let (Some(func), Some(cur_bb)) = (&self.current_function, self.current_block) {
|
||||
crate::mir::phi_core::common::debug_verify_phi_inputs(func, cur_bb, &inputs);
|
||||
}
|
||||
@ -69,6 +72,9 @@ impl MirBuilder {
|
||||
let else_pred = else_exit_block_opt.unwrap_or(else_block);
|
||||
let merged = self.value_gen.next();
|
||||
let inputs = vec![(then_pred, then_v), (else_pred, else_v)];
|
||||
if let Some(func) = self.current_function.as_mut() {
|
||||
func.update_cfg();
|
||||
}
|
||||
if let (Some(func), Some(cur_bb)) = (&self.current_function, self.current_block) {
|
||||
crate::mir::phi_core::common::debug_verify_phi_inputs(func, cur_bb, &inputs);
|
||||
}
|
||||
@ -128,6 +134,9 @@ impl MirBuilder {
|
||||
let else_pred = else_exit_block_opt.unwrap_or(else_block);
|
||||
// Emit Phi for the assigned variable and bind it
|
||||
let inputs = vec![(then_pred, then_value_for_var), (else_pred, else_value_for_var)];
|
||||
if let Some(func) = self.current_function.as_mut() {
|
||||
func.update_cfg();
|
||||
}
|
||||
if let (Some(func), Some(cur_bb)) = (&self.current_function, self.current_block) {
|
||||
crate::mir::phi_core::common::debug_verify_phi_inputs(func, cur_bb, &inputs);
|
||||
}
|
||||
@ -139,6 +148,9 @@ impl MirBuilder {
|
||||
let then_pred = then_exit_block_opt.unwrap_or(then_block);
|
||||
let else_pred = else_exit_block_opt.unwrap_or(else_block);
|
||||
let inputs = vec![(then_pred, then_value_raw), (else_pred, else_value_raw)];
|
||||
if let Some(func) = self.current_function.as_mut() {
|
||||
func.update_cfg();
|
||||
}
|
||||
if let (Some(func), Some(cur_bb)) = (&self.current_function, self.current_block) {
|
||||
crate::mir::phi_core::common::debug_verify_phi_inputs(func, cur_bb, &inputs);
|
||||
}
|
||||
|
||||
@ -8,6 +8,11 @@ pub fn check_ssa_form(function: &MirFunction) -> Result<(), Vec<VerificationErro
|
||||
let mut errors = Vec::new();
|
||||
let mut definitions: HashMap<ValueId, (crate::mir::BasicBlockId, usize)> = HashMap::new();
|
||||
|
||||
// Treat parameters as defined at the entry block.
|
||||
for pid in &function.params {
|
||||
definitions.insert(*pid, (function.entry_block, 0));
|
||||
}
|
||||
|
||||
for (block_id, block) in &function.blocks {
|
||||
for (inst_idx, instruction) in block.all_instructions().enumerate() {
|
||||
if let Some(dst) = instruction.dst_value() {
|
||||
@ -38,4 +43,3 @@ pub fn check_ssa_form(function: &MirFunction) -> Result<(), Vec<VerificationErro
|
||||
|
||||
if errors.is_empty() { Ok(()) } else { Err(errors) }
|
||||
}
|
||||
|
||||
|
||||
@ -13,6 +13,9 @@ pub fn compute_predecessors(function: &MirFunction) -> HashMap<BasicBlockId, Vec
|
||||
|
||||
pub fn compute_def_blocks(function: &MirFunction) -> HashMap<ValueId, BasicBlockId> {
|
||||
let mut def_block: HashMap<ValueId, BasicBlockId> = HashMap::new();
|
||||
for pid in &function.params {
|
||||
def_block.insert(*pid, function.entry_block);
|
||||
}
|
||||
for (bid, block) in &function.blocks {
|
||||
for inst in block.all_instructions() {
|
||||
if let Some(dst) = inst.dst_value() {
|
||||
|
||||
@ -60,8 +60,8 @@ pub trait ParserUtils {
|
||||
if !cursor_on {
|
||||
let allow_sc = std::env::var("NYASH_PARSER_ALLOW_SEMICOLON").ok().map(|v| {
|
||||
let lv = v.to_ascii_lowercase();
|
||||
lv == "1" || lv == "true" || lv == "on"
|
||||
}).unwrap_or(false);
|
||||
!(lv == "0" || lv == "false" || lv == "off")
|
||||
}).unwrap_or(true);
|
||||
loop {
|
||||
let is_nl = matches!(self.current_token().token_type, TokenType::NEWLINE);
|
||||
let is_sc = allow_sc && matches!(self.current_token().token_type, TokenType::SEMICOLON);
|
||||
|
||||
@ -232,8 +232,8 @@ impl NyashParser {
|
||||
|
||||
let allow_sc = std::env::var("NYASH_PARSER_ALLOW_SEMICOLON").ok().map(|v| {
|
||||
let lv = v.to_ascii_lowercase();
|
||||
lv == "1" || lv == "true" || lv == "on"
|
||||
}).unwrap_or(false);
|
||||
!(lv == "0" || lv == "false" || lv == "off")
|
||||
}).unwrap_or(true);
|
||||
|
||||
while !self.is_at_end() {
|
||||
// EOF tokenはスキップ
|
||||
|
||||
@ -64,7 +64,8 @@ pub fn collect_using_and_strip(
|
||||
let is_path = target.starts_with('"')
|
||||
|| target.starts_with("./")
|
||||
|| target.starts_with('/')
|
||||
|| target.ends_with(".nyash");
|
||||
|| target.ends_with(".nyash")
|
||||
|| target.ends_with(".hako");
|
||||
if is_path {
|
||||
// SSOT: Disallow file-using at top-level; allow only for sources located
|
||||
// under a declared package root (internal package wiring), so that packages
|
||||
@ -169,12 +170,12 @@ pub fn collect_using_and_strip(
|
||||
PackageKind::Package => {
|
||||
let base = std::path::Path::new(&pkg.path);
|
||||
let out = if let Some(m) = &pkg.main {
|
||||
if base.extension().and_then(|s| s.to_str()) == Some("nyash") {
|
||||
if matches!(base.extension().and_then(|s| s.to_str()), Some("nyash") | Some("hako")) {
|
||||
pkg.path.clone()
|
||||
} else {
|
||||
base.join(m).to_string_lossy().to_string()
|
||||
}
|
||||
} else if base.extension().and_then(|s| s.to_str()) == Some("nyash") {
|
||||
} else if matches!(base.extension().and_then(|s| s.to_str()), Some("nyash") | Some("hako")) {
|
||||
pkg.path.clone()
|
||||
} else {
|
||||
let leaf = base
|
||||
|
||||
@ -101,33 +101,66 @@ impl NyashRunner {
|
||||
}
|
||||
};
|
||||
|
||||
// Using handling: AST-prelude collection (legacy inlining removed)
|
||||
let code = if crate::config::env::enable_using() {
|
||||
match crate::runner::modes::common_util::resolve::resolve_prelude_paths_profiled(self, &code, filename) {
|
||||
// Using handling: collect/merge preludes when enabled
|
||||
let using_ast = crate::config::env::using_ast_enabled();
|
||||
let mut code_ref: &str = &code;
|
||||
let cleaned_owned;
|
||||
let mut prelude_asts: Vec<nyash_rust::ast::ASTNode> = Vec::new();
|
||||
if crate::config::env::enable_using() {
|
||||
match crate::runner::modes::common_util::resolve::resolve_prelude_paths_profiled(
|
||||
self,
|
||||
&code,
|
||||
filename,
|
||||
) {
|
||||
Ok((clean, paths)) => {
|
||||
if !paths.is_empty() && !crate::config::env::using_ast_enabled() {
|
||||
cleaned_owned = clean;
|
||||
code_ref = &cleaned_owned;
|
||||
if !paths.is_empty() && !using_ast {
|
||||
eprintln!("❌ using: AST prelude merge is disabled in this profile. Enable NYASH_USING_AST=1 or remove 'using' lines.");
|
||||
process::exit(1);
|
||||
}
|
||||
// VM path currently does not merge prelude ASTs; rely on compile pipeline path for that.
|
||||
clean
|
||||
if using_ast && !paths.is_empty() {
|
||||
match crate::runner::modes::common_util::resolve::parse_preludes_to_asts(
|
||||
self, &paths,
|
||||
) {
|
||||
Ok(v) => prelude_asts = v,
|
||||
Err(e) => {
|
||||
eprintln!("❌ {}", e);
|
||||
process::exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!("❌ {}", e);
|
||||
process::exit(1);
|
||||
}
|
||||
Err(e) => { eprintln!("❌ {}", e); process::exit(1); }
|
||||
}
|
||||
} else { code };
|
||||
}
|
||||
|
||||
// Pre-expand '@name[:T] = expr' sugar at line-head (same as common/llvm/pyvm paths)
|
||||
let code = crate::runner::modes::common_util::resolve::preexpand_at_local(&code);
|
||||
let preexpanded_owned =
|
||||
crate::runner::modes::common_util::resolve::preexpand_at_local(code_ref);
|
||||
code_ref = &preexpanded_owned;
|
||||
|
||||
// Parse to AST
|
||||
let ast = match NyashParser::parse_from_string(&code) {
|
||||
let main_ast = match NyashParser::parse_from_string(code_ref) {
|
||||
Ok(ast) => ast,
|
||||
Err(e) => {
|
||||
eprintln!("❌ Parse error: {}", e);
|
||||
process::exit(1);
|
||||
}
|
||||
};
|
||||
let ast = crate::r#macro::maybe_expand_and_dump(&ast, false);
|
||||
// Merge prelude ASTs (opt-in)
|
||||
let merged_ast = if using_ast && !prelude_asts.is_empty() {
|
||||
crate::runner::modes::common_util::resolve::merge_prelude_asts_with_main(
|
||||
prelude_asts,
|
||||
&main_ast,
|
||||
)
|
||||
} else {
|
||||
main_ast
|
||||
};
|
||||
let ast = crate::r#macro::maybe_expand_and_dump(&merged_ast, false);
|
||||
|
||||
// Prepare runtime and collect Box declarations for VM user-defined types
|
||||
let runtime = {
|
||||
@ -480,6 +513,13 @@ impl NyashRunner {
|
||||
type_parameters: type_parameters.clone(),
|
||||
};
|
||||
if let Ok(mut map) = runtime.box_declarations.write() {
|
||||
if std::env::var("NYASH_BOX_DECL_TRACE")
|
||||
.ok()
|
||||
.as_deref()
|
||||
== Some("1")
|
||||
{
|
||||
eprintln!("[box-decl] register {}", name);
|
||||
}
|
||||
map.insert(name.clone(), decl);
|
||||
}
|
||||
}
|
||||
|
||||
@ -206,18 +206,21 @@ pub(super) fn resolve_using_target(
|
||||
// Compute entry: main or <dir_last>.nyash
|
||||
let base = std::path::Path::new(&pkg.path);
|
||||
let out = if let Some(m) = &pkg.main {
|
||||
if base.extension().and_then(|s| s.to_str()) == Some("nyash") {
|
||||
if matches!(base.extension().and_then(|s| s.to_str()), Some("nyash") | Some("hako")) {
|
||||
// path is a file; ignore main and use as-is
|
||||
pkg.path.clone()
|
||||
} else {
|
||||
base.join(m).to_string_lossy().to_string()
|
||||
}
|
||||
} else {
|
||||
if base.extension().and_then(|s| s.to_str()) == Some("nyash") {
|
||||
if matches!(base.extension().and_then(|s| s.to_str()), Some("nyash") | Some("hako")) {
|
||||
pkg.path.clone()
|
||||
} else {
|
||||
let leaf = base.file_name().and_then(|s| s.to_str()).unwrap_or(tgt);
|
||||
base.join(format!("{}.nyash", leaf)).to_string_lossy().to_string()
|
||||
// prefer .hako when package path points to a directory without explicit main
|
||||
let hako = base.join(format!("{}.hako", leaf));
|
||||
if hako.exists() { hako.to_string_lossy().to_string() }
|
||||
else { base.join(format!("{}.nyash", leaf)).to_string_lossy().to_string() }
|
||||
}
|
||||
};
|
||||
if trace {
|
||||
|
||||
26
src/tests/parser_semicolon.rs
Normal file
26
src/tests/parser_semicolon.rs
Normal file
@ -0,0 +1,26 @@
|
||||
use crate::parser::NyashParser;
|
||||
|
||||
#[test]
|
||||
fn parse_top_level_semicolons_optional() {
|
||||
let src = r#"
|
||||
local a = 1; local b = 2
|
||||
return a + b;
|
||||
"#;
|
||||
let ast = NyashParser::parse_from_string(src).expect("parser should accept semicolons by default");
|
||||
// Smoke: just ensure it parses into a Program
|
||||
match ast { crate::ast::ASTNode::Program { .. } => {}, _ => panic!("expected Program") }
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_block_with_semicolons() {
|
||||
let src = r#"
|
||||
static box Main {
|
||||
static method main() {
|
||||
local out = ""; local digits = "0123456789"; return 0
|
||||
}
|
||||
}
|
||||
"#;
|
||||
let ast = NyashParser::parse_from_string(src).expect("parser should accept semicolons inside blocks");
|
||||
match ast { crate::ast::ASTNode::Program { .. } => {}, _ => panic!("expected Program") }
|
||||
}
|
||||
|
||||
@ -3,12 +3,15 @@ use super::{NyashTokenizer, Token, TokenType, TokenizeError};
|
||||
impl NyashTokenizer {
|
||||
#[inline]
|
||||
pub(crate) fn allow_semicolon() -> bool {
|
||||
// Default: ON (semicolon is an optional statement separator)
|
||||
// Allow opt-out via NYASH_PARSER_ALLOW_SEMICOLON=0|false|off
|
||||
match std::env::var("NYASH_PARSER_ALLOW_SEMICOLON").ok() {
|
||||
Some(v) => {
|
||||
let lv = v.to_ascii_lowercase();
|
||||
lv == "1" || lv == "true" || lv == "on"
|
||||
if lv == "0" || lv == "false" || lv == "off" { return false; }
|
||||
true
|
||||
}
|
||||
None => false,
|
||||
None => true,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user