diff --git a/src/runner/json_v0_bridge/lowering.rs b/src/runner/json_v0_bridge/lowering.rs index b1eeb12e..608c818d 100644 --- a/src/runner/json_v0_bridge/lowering.rs +++ b/src/runner/json_v0_bridge/lowering.rs @@ -4,6 +4,7 @@ use crate::mir::{ MirPrinter, MirType, ValueId, }; use std::collections::HashMap; +use std::cell::RefCell; // Split out merge/new_block helpers for readability (no behavior change) mod merge; @@ -23,6 +24,41 @@ pub(super) struct LoopContext { pub(super) exit_bb: BasicBlockId, } +// Snapshot stacks for loop break/continue (per-nested-loop frame) +thread_local! { + static EXIT_SNAPSHOT_STACK: RefCell)>>> = RefCell::new(Vec::new()); + static CONT_SNAPSHOT_STACK: RefCell)>>> = RefCell::new(Vec::new()); +} + +pub(super) fn push_loop_snapshot_frames() { + EXIT_SNAPSHOT_STACK.with(|s| s.borrow_mut().push(Vec::new())); + CONT_SNAPSHOT_STACK.with(|s| s.borrow_mut().push(Vec::new())); +} + +pub(super) fn pop_exit_snapshots() -> Vec<(BasicBlockId, HashMap)> { + EXIT_SNAPSHOT_STACK.with(|s| s.borrow_mut().pop().unwrap_or_default()) +} + +pub(super) fn pop_continue_snapshots() -> Vec<(BasicBlockId, HashMap)> { + CONT_SNAPSHOT_STACK.with(|s| s.borrow_mut().pop().unwrap_or_default()) +} + +fn record_exit_snapshot(cur_bb: BasicBlockId, vars: &HashMap) { + EXIT_SNAPSHOT_STACK.with(|s| { + if let Some(top) = s.borrow_mut().last_mut() { + top.push((cur_bb, vars.clone())); + } + }); +} + +fn record_continue_snapshot(cur_bb: BasicBlockId, vars: &HashMap) { + CONT_SNAPSHOT_STACK.with(|s| { + if let Some(top) = s.borrow_mut().last_mut() { + top.push((cur_bb, vars.clone())); + } + }); +} + #[derive(Clone)] pub(super) struct BridgeEnv { pub(super) throw_enabled: bool, @@ -128,12 +164,16 @@ pub(super) fn lower_stmt_with_vars( } StmtV0::Break => { if let Some(ctx) = loop_stack.last().copied() { + // snapshot variables at break + record_exit_snapshot(cur_bb, vars); lower_break_stmt(f, cur_bb, ctx.exit_bb); } Ok(cur_bb) } StmtV0::Continue => { if let Some(ctx) = loop_stack.last().copied() { + // snapshot variables at continue + record_continue_snapshot(cur_bb, vars); lower_continue_stmt(f, cur_bb, ctx.cond_bb); } Ok(cur_bb) diff --git a/src/runner/json_v0_bridge/lowering/loop_.rs b/src/runner/json_v0_bridge/lowering/loop_.rs index b4ce0227..4205e927 100644 --- a/src/runner/json_v0_bridge/lowering/loop_.rs +++ b/src/runner/json_v0_bridge/lowering/loop_.rs @@ -30,7 +30,7 @@ pub(super) fn lower_loop_stmt( } } - // 3) LoopPhiOps アダプタ + // 3) LoopPhiOps アダプタ(準備用: preheader seed 専用) struct Ops<'a> { f: &'a mut MirFunction, vars: &'a mut HashMap, @@ -81,6 +81,8 @@ pub(super) fn lower_loop_stmt( let mut incomplete = crate::mir::phi_core::loop_phi::prepare_loop_variables_with( &mut ops, cond_bb, cur_bb, &base_vars, )?; + // Header snapshot for exit PHIs + let header_vars_snapshot = ops.vars.clone(); let (cval, _cend) = super::expr::lower_expr_with_vars(env, ops.f, cond_bb, cond, ops.vars)?; if let Some(bb) = ops.f.get_block_mut(cond_bb) { @@ -91,12 +93,19 @@ pub(super) fn lower_loop_stmt( }); } let mut body_vars = ops.vars.clone(); + // open snapshot frames for nested break/continue + super::push_loop_snapshot_frames(); loop_stack.push(LoopContext { cond_bb, exit_bb }); let bend_res = lower_stmt_list_with_vars(ops.f, body_bb, body, &mut body_vars, loop_stack, env); loop_stack.pop(); let bend = bend_res?; - // latch(body末尾)スナップショット - block_var_maps.insert(bend, body_vars.clone()); + // collect snapshots for this loop level + let continue_snaps = super::pop_continue_snapshots(); + let exit_snaps = super::pop_exit_snapshots(); + // latch(body末尾)スナップショットを別マップに構築 + let mut block_var_maps2: HashMap> = HashMap::new(); + block_var_maps2.insert(cur_bb, base_vars.clone()); + block_var_maps2.insert(bend, body_vars.clone()); if let Some(bb) = ops.f.get_block_mut(bend) { if !bb.is_terminated() { bb.set_terminator(MirInstruction::Jump { target: cond_bb }); @@ -110,14 +119,23 @@ pub(super) fn lower_loop_stmt( ); if backedge_to_cond { // 5) header の不完全PHIを完成(latch + continue スナップショット集約) - let continue_snaps: Vec<(BasicBlockId, HashMap)> = Vec::new(); + let mut ops_seal = Ops { f: ops.f, vars: ops.vars, block_var_maps: &block_var_maps2 }; crate::mir::phi_core::loop_phi::seal_incomplete_phis_with( - &mut ops, + &mut ops_seal, cond_bb, bend, std::mem::take(&mut incomplete), &continue_snaps, )?; } + // 6) exit PHIs(breakの合流 + headerからのフォールスルー) + let mut ops_exit = Ops { f: ops.f, vars: ops.vars, block_var_maps: &block_var_maps2 }; + crate::mir::phi_core::loop_phi::build_exit_phis_with( + &mut ops_exit, + cond_bb, + exit_bb, + &header_vars_snapshot, + &exit_snaps, + )?; Ok(exit_bb) } diff --git a/tools/smokes/v2/profiles/quick/core/loops/bridge_loop_break_vm.sh b/tools/smokes/v2/profiles/quick/core/loops/bridge_loop_break_vm.sh new file mode 100644 index 00000000..6ff82437 --- /dev/null +++ b/tools/smokes/v2/profiles/quick/core/loops/bridge_loop_break_vm.sh @@ -0,0 +1,40 @@ +#!/bin/bash +# Bridge(JSON v0) → MIR Interpreter での loop+break 検証 +# 既定は SKIP(opt-in: SMOKES_ENABLE_LOOP_BRIDGE=1) + +source "$(dirname "$0")/../../../lib/test_runner.sh" +source "$(dirname "$0")/../../../lib/result_checker.sh" + +require_env || exit 2 +preflight_plugins || exit 2 + +if [ "${SMOKES_ENABLE_LOOP_BRIDGE:-0}" != "1" ]; then + test_skip "bridge_loop_break_vm" "opt-in (set SMOKES_ENABLE_LOOP_BRIDGE=1)" && exit 0 +fi + +test_bridge_loop_break() { + local tmp_json="/tmp/nyash_bridge_loop_break_$$.json" + cat > "$tmp_json" <<'JSON' +{"version":0,"kind":"Program","body":[ + {"type":"Local","name":"i","expr":{"type":"Int","value":0}}, + {"type":"Local","name":"result","expr":{"type":"Int","value":0}}, + {"type":"Loop", + "cond":{"type":"Compare","op":"<","lhs":{"type":"Var","name":"i"},"rhs":{"type":"Int","value":100}}, + "body":[ + {"type":"If","cond":{"type":"Compare","op":"==","lhs":{"type":"Var","name":"i"},"rhs":{"type":"Int","value":5}}, + "then":[{"type":"Break"}],"else":[]}, + {"type":"Local","name":"result","expr":{"type":"Binary","op":"+","lhs":{"type":"Var","name":"result"},"rhs":{"type":"Var","name":"i"}}}, + {"type":"Local","name":"i","expr":{"type":"Binary","op":"+","lhs":{"type":"Var","name":"i"},"rhs":{"type":"Int","value":1}}} + ] + }, + {"type":"Extern","iface":"env.console","method":"log","args":[{"type":"Var","name":"result"}]} +]} +JSON + local output + output=$(NYASH_ENTRY_ALLOW_TOPLEVEL_MAIN=1 "$NYASH_BIN" --backend vm --json-file "$tmp_json" 2>&1 | filter_noise) + rm -f "$tmp_json" + check_exact "10" "$output" "bridge_loop_break" +} + +run_test "bridge_loop_break" test_bridge_loop_break + diff --git a/tools/smokes/v2/profiles/quick/core/loops/bridge_loop_continue_vm.sh b/tools/smokes/v2/profiles/quick/core/loops/bridge_loop_continue_vm.sh new file mode 100644 index 00000000..a14af7fb --- /dev/null +++ b/tools/smokes/v2/profiles/quick/core/loops/bridge_loop_continue_vm.sh @@ -0,0 +1,40 @@ +#!/bin/bash +# Bridge(JSON v0) → MIR Interpreter での loop+continue 検証 +# 既定は SKIP(opt-in: SMOKES_ENABLE_LOOP_BRIDGE=1) + +source "$(dirname "$0")/../../../lib/test_runner.sh" +source "$(dirname "$0")/../../../lib/result_checker.sh" + +require_env || exit 2 +preflight_plugins || exit 2 + +if [ "${SMOKES_ENABLE_LOOP_BRIDGE:-0}" != "1" ]; then + test_skip "bridge_loop_continue_vm" "opt-in (set SMOKES_ENABLE_LOOP_BRIDGE=1)" && exit 0 +fi + +test_bridge_loop_continue() { + local tmp_json="/tmp/nyash_bridge_loop_continue_$$.json" + cat > "$tmp_json" <<'JSON' +{"version":0,"kind":"Program","body":[ + {"type":"Local","name":"i","expr":{"type":"Int","value":0}}, + {"type":"Local","name":"sum","expr":{"type":"Int","value":0}}, + {"type":"Loop", + "cond":{"type":"Compare","op":"<","lhs":{"type":"Var","name":"i"},"rhs":{"type":"Int","value":5}}, + "body":[ + {"type":"Local","name":"i","expr":{"type":"Binary","op":"+","lhs":{"type":"Var","name":"i"},"rhs":{"type":"Int","value":1}}}, + {"type":"If","cond":{"type":"Compare","op":"==","lhs":{"type":"Var","name":"i"},"rhs":{"type":"Int","value":3}}, + "then":[{"type":"Continue"}],"else":[]}, + {"type":"Local","name":"sum","expr":{"type":"Binary","op":"+","lhs":{"type":"Var","name":"sum"},"rhs":{"type":"Var","name":"i"}}} + ] + }, + {"type":"Extern","iface":"env.console","method":"log","args":[{"type":"Var","name":"sum"}]} +]} +JSON + local output + output=$(NYASH_ENTRY_ALLOW_TOPLEVEL_MAIN=1 "$NYASH_BIN" --backend vm --json-file "$tmp_json" 2>&1 | filter_noise) + rm -f "$tmp_json" + check_exact "12" "$output" "bridge_loop_continue" +} + +run_test "bridge_loop_continue" test_bridge_loop_continue +