loopform(hints): detect up to 2 assigned vars in loop body (no break/continue) and emit LoopCarrier hint; add smoke for two-vars case

This commit is contained in:
Selfhosting Dev
2025-09-20 06:24:33 +09:00
parent 334b7e83af
commit f50f79994f
3 changed files with 62 additions and 0 deletions

View File

@ -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<S: Into<String>>(&mut self, var: S) { self.hint_sink.join_result(var.into()); }
#[inline]
pub(crate) fn hint_loop_carrier<S: Into<String>>(&mut self, vars: impl IntoIterator<Item = S>) {
self.hint_sink.loop_carrier(vars.into_iter().map(|s| s.into()).collect::<Vec<_>>());
}
// moved to builder_calls.rs: lower_method_as_function

View File

@ -143,6 +143,39 @@ impl<'a> LoopBuilder<'a> {
condition: ASTNode,
body: Vec<ASTNode>,
) -> Result<ValueId, String> {
// Pre-scan body for simple carrier pattern (up to 2 assigned variables, no break/continue)
fn collect_assigns(n: &ASTNode, vars: &mut Vec<String>, 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<String> = 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) =

View File

@ -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"