refactor(joinir): Phase 82-83 - Debug flag SSOT + Fallback verification

Phase 82: Centralized JoinIR debug flag reading
- Added is_joinir_debug() SSOT function in joinir_flags.rs
- Replaced 16 direct env::var() calls across 8 files
- Updated docs to recommend HAKO_JOINIR_DEBUG (NYASH_ deprecated)
- Backward compat: Both env vars work

Phase 83: Verified promoted carrier fallback behavior
- Confirmed NO fallback to name-based lookup for DigitPos/Trim
- Documented fallback expectations in Phase 80/81 docs
- Added verification commands and expected output

Changes:
- src/config/env/joinir_flags.rs: +187 lines (new SSOT module)
- 8 files: env var reads → is_joinir_debug() calls
- 3 docs: HAKO_JOINIR_DEBUG examples + fallback sections
- 1 summary doc: phase82-83-debug-flag-ssot-summary.md

Tests: 970/970 lib PASS, 58/58 normalized_dev PASS
Impact: Dev-only (zero production changes)
This commit is contained in:
nyash-codex
2025-12-13 19:01:14 +09:00
parent 3ff032ead5
commit 9e32807a96
12 changed files with 589 additions and 22 deletions

186
src/config/env/joinir_flags.rs vendored Normal file
View File

@ -0,0 +1,186 @@
//! JoinIR-related environment flags
//!
//! This module groups all JoinIR feature flags and environment variable controls.
//! Use this for IDE autocomplete to discover JoinIR flags easily.
use super::{env_bool, env_flag, warn_alias_once};
// ---- Phase 29/30 JoinIR toggles ----
/// JoinIR experiment mode. Required for JoinIR-related experimental paths.
/// Set NYASH_JOINIR_EXPERIMENT=1 to enable.
pub fn joinir_experiment_enabled() -> bool {
env_bool("NYASH_JOINIR_EXPERIMENT")
}
/// JoinIR core policy: **always ON** after LoopBuilder removal.
/// - `NYASH_JOINIR_CORE` is deprecated0 を指定しても警告して無視する)
/// - JoinIR を OFF にするモードは提供しないFail-Fast 原則、フォールバックなし)
pub fn joinir_core_enabled() -> bool {
if let Some(v) = env_flag("NYASH_JOINIR_CORE") {
if !v {
warn_joinir_core_off_ignored();
}
}
true
}
fn warn_joinir_core_off_ignored() {
use std::sync::Once;
static WARNED_JOINIR_CORE_OFF: Once = Once::new();
WARNED_JOINIR_CORE_OFF.call_once(|| {
eprintln!(
"[deprecate/env] NYASH_JOINIR_CORE=0 is ignored; JoinIR core is always on (LoopBuilder is removed)"
);
});
}
/// JoinIR VM bridge mode. When enabled with NYASH_JOINIR_EXPERIMENT=1,
/// specific functions can be executed via JoinIR → VM bridge instead of direct MIR → VM.
/// Set NYASH_JOINIR_VM_BRIDGE=1 to enable.
pub fn joinir_vm_bridge_enabled() -> bool {
joinir_core_enabled() && env_bool("NYASH_JOINIR_VM_BRIDGE")
}
/// JoinIR strict mode: when enabled, JoinIR 対象のフォールバックを禁止する。
/// 既定OFF。NYASH_JOINIR_STRICT=1 のときのみ有効。
pub fn joinir_strict_enabled() -> bool {
env_flag("NYASH_JOINIR_STRICT").unwrap_or(false)
}
/// JoinIR VM bridge debug output. Enables verbose logging of JoinIR→MIR conversion.
/// Set NYASH_JOINIR_VM_BRIDGE_DEBUG=1 to enable.
pub fn joinir_vm_bridge_debug() -> bool {
env_bool("NYASH_JOINIR_VM_BRIDGE_DEBUG")
}
/// JoinIR LLVM experiment mode. When enabled with NYASH_JOINIR_EXPERIMENT=1,
/// enables experimental JoinIR→MIR'→LLVM path for specific functions (e.g., Main.skip/1).
/// This is a dev-only toggle for testing PHI normalization via JoinIR in the LLVM path.
/// Set NYASH_JOINIR_LLVM_EXPERIMENT=1 to enable.
pub fn joinir_llvm_experiment_enabled() -> bool {
joinir_core_enabled() && env_bool("NYASH_JOINIR_LLVM_EXPERIMENT")
}
/// Phase 33: JoinIR If Select 実験の有効化
/// Primary: HAKO_JOINIR_IF_SELECT (Phase 33-8+).
pub fn joinir_if_select_enabled() -> bool {
// Core ON なら既定で有効化JoinIR 本線化を優先)
if joinir_core_enabled() {
return true;
}
// Primary: HAKO_JOINIR_IF_SELECT
if let Some(v) = env_flag("HAKO_JOINIR_IF_SELECT") {
return v;
}
false
}
/// Phase 33-8: JoinIR Stage-1 rollout toggle
/// Set HAKO_JOINIR_STAGE1=1 to enable JoinIR lowering for Stage-1 functions.
pub fn joinir_stage1_enabled() -> bool {
// Primary: HAKO_JOINIR_STAGE1
if let Some(v) = env_flag("HAKO_JOINIR_STAGE1") {
return v;
}
false
}
/// Phase 33-8: JoinIR debug log level (0-3)
/// - 0: No logs (default)
/// - 1: Basic logs (which functions were lowered)
/// - 2: Pattern matching details (CFG analysis)
/// - 3: Full dump (all variables, all instructions)
pub fn joinir_debug_level() -> u8 {
// Primary: HAKO_JOINIR_DEBUG
if let Ok(v) = std::env::var("HAKO_JOINIR_DEBUG") {
return v.parse().unwrap_or(0);
}
// Fallback: NYASH_JOINIR_DEBUG (deprecated)
if let Ok(v) = std::env::var("NYASH_JOINIR_DEBUG") {
warn_alias_once("NYASH_JOINIR_DEBUG", "HAKO_JOINIR_DEBUG");
return v.parse().unwrap_or(0);
}
0
}
/// Dev-only convenience switch to bundle experimental JoinIR knobs.
/// - NYASH_JOINIR_DEV=1 enables
/// - Otherwise inherits from joinir_debug_level()>0 (opt-in debug)
pub fn joinir_dev_enabled() -> bool {
env_bool("NYASH_JOINIR_DEV") || joinir_debug_level() > 0
}
/// Phase 61-2: If-in-loop JoinIR dry-run有効化
///
/// `HAKO_JOINIR_IF_IN_LOOP_DRYRUN=1` でdry-runモードを有効化
///
/// dry-runモード:
/// - JoinIR経路でPHI仕様を計算
/// - PhiBuilderBox経路と比較
/// - 実際のPHI生成はPhiBuilderBoxを使用安全
pub fn joinir_if_in_loop_dryrun_enabled() -> bool {
env_bool("HAKO_JOINIR_IF_IN_LOOP_DRYRUN")
}
/// Phase 61-3: If-in-loop JoinIR本番経路有効化
///
/// `HAKO_JOINIR_IF_IN_LOOP_ENABLE=1` でJoinIR本番経路を有効化
///
/// 動作:
/// - ON: JoinIR + IfInLoopPhiEmitter経路PhiBuilderBox不使用
/// - OFF: PhiBuilderBox経路既存フォールバック
///
/// 前提条件:
/// - JoinIR IfSelect 基盤Phase 33の有効化
/// - dry-runモードとは独立HAKO_JOINIR_IF_IN_LOOP_DRYRUN
///
/// デフォルト: OFF安全第一
pub fn joinir_if_in_loop_enable() -> bool {
env_bool("HAKO_JOINIR_IF_IN_LOOP_ENABLE")
}
/// Phase 61-4: ループ外If JoinIR経路有効化
///
/// `HAKO_JOINIR_IF_TOPLEVEL=1` でループ外IfのJoinIR経路を有効化
///
/// 動作:
/// - ON: try_lower_if_to_joinir経路if_form.rsで使用
/// - OFF: PhiBuilderBox経路既存
///
/// 前提条件:
/// - HAKO_JOINIR_IF_SELECT=1Phase 33基盤
///
/// デフォルト: OFF安全第一
pub fn joinir_if_toplevel_enabled() -> bool {
env_bool("HAKO_JOINIR_IF_TOPLEVEL")
}
/// Phase 61-4: ループ外If JoinIR dry-run有効化
///
/// `HAKO_JOINIR_IF_TOPLEVEL_DRYRUN=1` でdry-runモードを有効化
///
/// dry-runモード:
/// - JoinIR経路を試行しログ出力
/// - 実際のPHI生成は既存経路を使用安全
pub fn joinir_if_toplevel_dryrun_enabled() -> bool {
env_bool("HAKO_JOINIR_IF_TOPLEVEL_DRYRUN")
}
/// LoopForm normalize flag (NYASH_LOOPFORM_NORMALIZE=1).
pub fn loopform_normalize() -> bool {
std::env::var("NYASH_LOOPFORM_NORMALIZE").ok().as_deref() == Some("1")
}
/// JoinIR debug logging enabled check (SSOT).
///
/// Checks both HAKO_JOINIR_DEBUG and NYASH_JOINIR_DEBUG (legacy).
/// Returns true if either env var is set to any value.
///
/// Recommended: Use HAKO_JOINIR_DEBUG=1
/// Legacy: NYASH_JOINIR_DEBUG=1 (deprecated, but still works)
///
/// For fine-grained control, use `joinir_debug_level()` which returns 0-3.
pub fn is_joinir_debug() -> bool {
std::env::var("HAKO_JOINIR_DEBUG").is_ok()
|| std::env::var("NYASH_JOINIR_DEBUG").is_ok()
}

View File

@ -64,9 +64,10 @@ pub struct JoinLoopTrace {
impl JoinLoopTrace {
/// Create a new tracer, reading environment variables.
pub fn new() -> Self {
use crate::config::env::is_joinir_debug;
Self {
varmap_enabled: std::env::var("NYASH_TRACE_VARMAP").is_ok(),
joinir_enabled: std::env::var("NYASH_JOINIR_DEBUG").is_ok(),
joinir_enabled: is_joinir_debug(),
phi_enabled: std::env::var("NYASH_OPTION_C_DEBUG").is_ok(),
mainline_enabled: std::env::var("NYASH_JOINIR_MAINLINE_DEBUG").is_ok(),
loopform_enabled: std::env::var("NYASH_LOOPFORM_DEBUG").is_ok(),

View File

@ -138,9 +138,8 @@ impl CarrierBindingAssigner {
};
carrier.binding_id = Some(promoted_bid);
if std::env::var("NYASH_JOINIR_DEBUG").is_ok()
|| std::env::var("JOINIR_TEST_DEBUG").is_ok()
{
use crate::config::env::is_joinir_debug;
if is_joinir_debug() || std::env::var("JOINIR_TEST_DEBUG").is_ok() {
eprintln!(
"[phase78/carrier_assigner] '{}' (BindingId({})) → '{}' (BindingId({}))",
original_name,

View File

@ -650,7 +650,8 @@ impl CarrierInfo {
/// we integrate BindingId tracking into the promotion pipeline.
#[cfg(feature = "normalized_dev")]
pub fn record_promoted_binding(&mut self, original_binding: BindingId, promoted_binding: BindingId) {
if std::env::var("NYASH_JOINIR_DEBUG").is_ok() || std::env::var("JOINIR_TEST_DEBUG").is_ok() {
use crate::config::env::is_joinir_debug;
if is_joinir_debug() || std::env::var("JOINIR_TEST_DEBUG").is_ok() {
eprintln!(
"[binding_pilot/promoted_bindings] {}{}",
original_binding, promoted_binding

View File

@ -307,10 +307,11 @@ impl ConditionEnv {
binding_id: Option<BindingId>,
name: &str,
) -> Option<ValueId> {
use crate::config::env::is_joinir_debug;
if let Some(bid) = binding_id {
// Try BindingId lookup first
if let Some(&value_id) = self.binding_id_map.get(&bid) {
if std::env::var("NYASH_JOINIR_DEBUG").is_ok() {
if is_joinir_debug() {
eprintln!(
"[binding_pilot/hit] BindingId({}) -> ValueId({}) for '{}'",
bid.0, value_id.0, name
@ -320,7 +321,7 @@ impl ConditionEnv {
} else {
// BindingId miss, fall back to name
let result = self.get(name);
if std::env::var("NYASH_JOINIR_DEBUG").is_ok() {
if is_joinir_debug() {
eprintln!(
"[binding_pilot/fallback] BindingId({}) miss, name '{}' -> {:?}",
bid.0, name, result
@ -331,7 +332,7 @@ impl ConditionEnv {
} else {
// Legacy: no BindingId, use name lookup
let result = self.get(name);
if std::env::var("NYASH_JOINIR_DEBUG").is_ok() {
if is_joinir_debug() {
eprintln!(
"[binding_pilot/legacy] No BindingId, name '{}' -> {:?}",
name, result

View File

@ -266,10 +266,11 @@ impl<'a> ScopeManager for Pattern2ScopeManager<'a> {
/// promoters populate promoted_bindings map and all call sites provide BindingId.
#[cfg(feature = "normalized_dev")]
fn lookup_with_binding(&self, binding_id: Option<BindingId>, name: &str) -> Option<ValueId> {
use crate::config::env::is_joinir_debug;
if let Some(bid) = binding_id {
// Step 1: Try direct BindingId lookup in ConditionEnv (Phase 75)
if let Some(value_id) = self.condition_env.resolve_var_with_binding(Some(bid), name) {
if std::env::var("NYASH_JOINIR_DEBUG").is_ok() {
if is_joinir_debug() {
eprintln!(
"[phase76/direct] BindingId({}) -> ValueId({}) for '{}'",
bid.0, value_id.0, name
@ -282,7 +283,7 @@ impl<'a> ScopeManager for Pattern2ScopeManager<'a> {
if let Some(promoted_bid) = self.carrier_info.resolve_promoted_with_binding(bid) {
// Promoted BindingId found, lookup in ConditionEnv
if let Some(value_id) = self.condition_env.resolve_var_with_binding(Some(promoted_bid), name) {
if std::env::var("NYASH_JOINIR_DEBUG").is_ok() {
if is_joinir_debug() {
eprintln!(
"[phase76/promoted] BindingId({}) promoted to BindingId({}) -> ValueId({}) for '{}'",
bid.0, promoted_bid.0, value_id.0, name
@ -300,7 +301,7 @@ impl<'a> ScopeManager for Pattern2ScopeManager<'a> {
bid.0, name
);
#[cfg(not(feature = "normalized_dev"))]
if std::env::var("NYASH_JOINIR_DEBUG").is_ok() {
if is_joinir_debug() {
eprintln!(
"[phase76/fallback] BindingId({}) miss, falling back to name '{}' lookup",
bid.0, name

View File

@ -173,7 +173,8 @@ impl LoopBodyCarrierPromoter {
};
}
if std::env::var("NYASH_JOINIR_DEBUG").is_ok() || std::env::var("JOINIR_TEST_DEBUG").is_ok() {
use crate::config::env::is_joinir_debug;
if is_joinir_debug() || std::env::var("JOINIR_TEST_DEBUG").is_ok() {
eprintln!(
"[promoter/pattern5] Phase 171-C: Found {} LoopBodyLocal variables: {:?}",
body_locals.len(),
@ -196,7 +197,7 @@ impl LoopBodyCarrierPromoter {
// Phase 79: Use TrimDetector for pure detection logic
if let Some(detection) = TrimDetector::detect(break_cond, request.loop_body, var_name)
{
if std::env::var("NYASH_JOINIR_DEBUG").is_ok() || std::env::var("JOINIR_TEST_DEBUG").is_ok() {
if is_joinir_debug() || std::env::var("JOINIR_TEST_DEBUG").is_ok() {
eprintln!(
"[promoter/pattern5] Trim pattern detected! var='{}', literals={:?}",
detection.match_var, detection.comparison_literals
@ -231,7 +232,8 @@ impl LoopBodyCarrierPromoter {
/// Phase 78: Log promotion errors with clear messages (for Trim pattern, gated)
#[cfg(feature = "normalized_dev")]
fn log_trim_promotion_error(error: &BindingRecordError) {
if std::env::var("NYASH_JOINIR_DEBUG").is_ok() || std::env::var("JOINIR_TEST_DEBUG").is_ok() {
use crate::config::env::is_joinir_debug;
if is_joinir_debug() || std::env::var("JOINIR_TEST_DEBUG").is_ok() {
match error {
BindingRecordError::OriginalNotFound(name) => {
eprintln!(

View File

@ -129,7 +129,8 @@ impl DigitPosPromoter {
};
}
if std::env::var("NYASH_JOINIR_DEBUG").is_ok() || std::env::var("JOINIR_TEST_DEBUG").is_ok() {
use crate::config::env::is_joinir_debug;
if is_joinir_debug() || std::env::var("JOINIR_TEST_DEBUG").is_ok() {
eprintln!(
"[digitpos_promoter] Phase 224: Found {} LoopBodyLocal variables: {:?}",
body_locals.len(),
@ -162,7 +163,7 @@ impl DigitPosPromoter {
}
let detection = detection.unwrap();
if std::env::var("NYASH_JOINIR_DEBUG").is_ok() || std::env::var("JOINIR_TEST_DEBUG").is_ok() {
if is_joinir_debug() || std::env::var("JOINIR_TEST_DEBUG").is_ok() {
eprintln!(
"[digitpos_promoter] Pattern detected: {}{} (bool) + {} (int)",
detection.var_name, detection.bool_carrier_name, detection.int_carrier_name
@ -222,7 +223,7 @@ impl DigitPosPromoter {
log_promotion_error(&e);
}
if std::env::var("NYASH_JOINIR_DEBUG").is_ok() || std::env::var("JOINIR_TEST_DEBUG").is_ok() {
if is_joinir_debug() || std::env::var("JOINIR_TEST_DEBUG").is_ok() {
eprintln!(
"[digitpos_promoter] Phase 247-EX: A-4 DigitPos pattern promoted: {}{} (bool) + {} (i64)",
detection.var_name, detection.bool_carrier_name, detection.int_carrier_name
@ -252,7 +253,8 @@ impl DigitPosPromoter {
/// Phase 78: Log promotion errors with clear messages (gated)
#[cfg(feature = "normalized_dev")]
fn log_promotion_error(error: &BindingRecordError) {
if std::env::var("NYASH_JOINIR_DEBUG").is_ok() || std::env::var("JOINIR_TEST_DEBUG").is_ok() {
use crate::config::env::is_joinir_debug;
if is_joinir_debug() || std::env::var("JOINIR_TEST_DEBUG").is_ok() {
match error {
BindingRecordError::OriginalNotFound(name) => {
eprintln!(