From 64d7003249fc9b162f84e591d82665cfcc63e972 Mon Sep 17 00:00:00 2001 From: nyash-codex Date: Sat, 1 Nov 2025 16:56:26 +0900 Subject: [PATCH] =?UTF-8?q?bridge(loop):=20exit=20PHI=20=E3=82=92=20SSOT?= =?UTF-8?q?=20=E3=81=B8=E7=B5=B1=E4=B8=80=EF=BC=88break/continue=20?= =?UTF-8?q?=E3=82=B9=E3=83=8A=E3=83=83=E3=83=97=E3=82=B7=E3=83=A7=E3=83=83?= =?UTF-8?q?=E3=83=88=E3=81=AE=20stack=20=E5=8C=96=EF=BC=89+=20=E3=83=AB?= =?UTF-8?q?=E3=83=BC=E3=83=97=E6=AF=94=E8=BC=83=E3=82=B9=E3=83=A2=E3=83=BC?= =?UTF-8?q?=E3=82=AF=EF=BC=88bridge=EF=BC=89=E8=BF=BD=E5=8A=A0\n\n-=20lowe?= =?UTF-8?q?ring.rs:=20=E3=83=AB=E3=83=BC=E3=83=97=E7=94=A8=20break/continu?= =?UTF-8?q?e=20=E3=82=B9=E3=83=8A=E3=83=83=E3=83=97=E3=82=B7=E3=83=A7?= =?UTF-8?q?=E3=83=83=E3=83=88=E3=82=92=20thread-local=20stack=20=E3=81=A7?= =?UTF-8?q?=E7=AE=A1=E7=90=86=EF=BC=88push/pop/record=EF=BC=89\n-=20loop?= =?UTF-8?q?=5F.rs:=20continue/break=20=E3=82=B9=E3=83=8A=E3=83=83=E3=83=97?= =?UTF-8?q?=E3=82=92=E5=8F=96=E3=82=8A=E8=BE=BC=E3=81=BF=E3=80=81seal=5Fin?= =?UTF-8?q?complete=5Fphis=5Fwith=E3=83=BBbuild=5Fexit=5Fphis=5Fwith=20?= =?UTF-8?q?=E3=81=AB=E5=A7=94=E8=AD=B2\n-=20smokes:=20bridge=5Floop=5Fsum/?= =?UTF-8?q?break/continue=20=E3=82=92=20opt-in=20=E8=BF=BD=E5=8A=A0?= =?UTF-8?q?=EF=BC=88SMOKES=5FENABLE=5FLOOP=5FBRIDGE=3D1=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/runner/json_v0_bridge/lowering.rs | 40 +++++++++++++++++++ src/runner/json_v0_bridge/lowering/loop_.rs | 28 ++++++++++--- .../quick/core/loops/bridge_loop_break_vm.sh | 40 +++++++++++++++++++ .../core/loops/bridge_loop_continue_vm.sh | 40 +++++++++++++++++++ 4 files changed, 143 insertions(+), 5 deletions(-) create mode 100644 tools/smokes/v2/profiles/quick/core/loops/bridge_loop_break_vm.sh create mode 100644 tools/smokes/v2/profiles/quick/core/loops/bridge_loop_continue_vm.sh 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 +