diff --git a/src/mir/builder.rs b/src/mir/builder.rs index 9d8be7e1..8355c047 100644 --- a/src/mir/builder.rs +++ b/src/mir/builder.rs @@ -182,6 +182,10 @@ impl MirBuilder { pub(crate) fn hint_scope_leave(&mut self, id: u32) { self.hint_sink.scope_leave(id); } #[inline] pub(crate) fn hint_join_result>(&mut self, var: S) { self.hint_sink.join_result(var.into()); } + #[inline] + pub(crate) fn hint_loop_carrier>(&mut self, vars: impl IntoIterator) { + self.hint_sink.loop_carrier(vars.into_iter().map(|s| s.into()).collect::>()); + } // moved to builder_calls.rs: lower_method_as_function diff --git a/src/mir/loop_builder.rs b/src/mir/loop_builder.rs index c124d098..dd1ddaa0 100644 --- a/src/mir/loop_builder.rs +++ b/src/mir/loop_builder.rs @@ -143,6 +143,39 @@ impl<'a> LoopBuilder<'a> { condition: ASTNode, body: Vec, ) -> Result { + // Pre-scan body for simple carrier pattern (up to 2 assigned variables, no break/continue) + fn collect_assigns(n: &ASTNode, vars: &mut Vec, has_ctrl: &mut bool) { + match n { + ASTNode::Assignment { target, .. } => { + if let ASTNode::Variable { name, .. } = target.as_ref() { + if !vars.iter().any(|v| v == name) { + vars.push(name.clone()); + } + } + } + ASTNode::Break { .. } | ASTNode::Continue { .. } => { *has_ctrl = true; } + ASTNode::If { then_body, else_body, .. } => { + let tp = ASTNode::Program { statements: then_body.clone(), span: crate::ast::Span::unknown() }; + collect_assigns(&tp, vars, has_ctrl); + if let Some(eb) = else_body { + let ep = ASTNode::Program { statements: eb.clone(), span: crate::ast::Span::unknown() }; + collect_assigns(&ep, vars, has_ctrl); + } + } + ASTNode::Program { statements, .. } => { + for s in statements { collect_assigns(s, vars, has_ctrl); } + } + _ => {} + } + } + let mut assigned_vars: Vec = Vec::new(); + let mut has_ctrl = false; + for st in &body { collect_assigns(st, &mut assigned_vars, &mut has_ctrl); } + if !has_ctrl && !assigned_vars.is_empty() && assigned_vars.len() <= 2 { + // Emit a carrier hint (no-op sink by default; visible with NYASH_MIR_TRACE_HINTS=1) + self.parent_builder.hint_loop_carrier(assigned_vars.clone()); + } + // 1. ブロックの準備 let preheader_id = self.current_block()?; let (header_id, body_id, after_loop_id) = diff --git a/tools/test/smoke/mir/hints_loop_carrier_two_vars_smoke.sh b/tools/test/smoke/mir/hints_loop_carrier_two_vars_smoke.sh new file mode 100644 index 00000000..7f0a2ae7 --- /dev/null +++ b/tools/test/smoke/mir/hints_loop_carrier_two_vars_smoke.sh @@ -0,0 +1,25 @@ +#!/usr/bin/env bash +set -euo pipefail + +root=$(cd "$(dirname "$0")"/../../../.. && pwd) +bin="$root/target/release/nyash" +src="apps/tests/macro/loopform/two_vars.nyash" + +if [ ! -x "$bin" ]; then + echo "nyash binary not found at $bin; build first (cargo build --release)" >&2 + exit 1 +fi + +export NYASH_MIR_TRACE_HINTS=1 + +out=$({ "$bin" --backend vm "$src" 1>/dev/null; } 2>&1 || true) + +# Check the LoopCarrier hint contains both variable names (order-agnostic) +echo "$out" | rg -q "\[mir\]\[hint\] LoopCarrier\((i,sum|sum,i)\)" || { + echo "[FAIL] missing LoopCarrier(i,sum) hint" >&2 + printf '%s\n' "$out" | tail -n 80 >&2 + exit 2 +} + +echo "[OK] MIR hints LoopCarrier(two vars) trace smoke passed" +