2025-09-19 08:34:29 +09:00
|
|
|
|
//! Control-flow entrypoints (if/loop/try/throw) centralized here.
|
2025-11-13 16:40:58 +09:00
|
|
|
|
use super::{Effect, EffectMask, MirInstruction, ValueId};
|
2025-11-29 06:51:43 +09:00
|
|
|
|
use crate::ast::ASTNode;
|
2025-09-19 08:34:29 +09:00
|
|
|
|
|
|
|
|
|
|
impl super::MirBuilder {
|
2025-12-05 19:35:30 +09:00
|
|
|
|
/// Trace variable_map state for debugging
|
|
|
|
|
|
/// Enable with NYASH_TRACE_VARMAP=1
|
|
|
|
|
|
fn trace_varmap(&self, context: &str) {
|
|
|
|
|
|
if std::env::var("NYASH_TRACE_VARMAP").is_ok() {
|
|
|
|
|
|
let vars: Vec<_> = self.variable_map.iter()
|
|
|
|
|
|
.map(|(k, v)| format!("{}={:?}", k, v))
|
|
|
|
|
|
.collect();
|
|
|
|
|
|
eprintln!("[varmap/{}] {{{}}}", context, vars.join(", "));
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-09-19 08:34:29 +09:00
|
|
|
|
/// Control-flow: block
|
|
|
|
|
|
pub(super) fn cf_block(&mut self, statements: Vec<ASTNode>) -> Result<ValueId, String> {
|
|
|
|
|
|
// identical to build_block; kept here for future policy hooks
|
|
|
|
|
|
self.build_block(statements)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// Control-flow: if
|
2025-12-04 06:17:10 +09:00
|
|
|
|
///
|
2025-12-04 06:29:54 +09:00
|
|
|
|
/// # Phase 124: JoinIR-Only (hako_check専用化完了)
|
2025-12-04 06:17:10 +09:00
|
|
|
|
///
|
2025-12-04 06:29:54 +09:00
|
|
|
|
/// If statements are now always routed through the canonical lowering path
|
|
|
|
|
|
/// (lower_if_form), which internally uses JoinIR-based PHI generation.
|
2025-12-04 06:17:10 +09:00
|
|
|
|
///
|
2025-12-04 06:29:54 +09:00
|
|
|
|
/// Phase 123 の環境変数による分岐は削除済み。
|
2025-09-19 08:34:29 +09:00
|
|
|
|
pub(super) fn cf_if(
|
|
|
|
|
|
&mut self,
|
|
|
|
|
|
condition: ASTNode,
|
|
|
|
|
|
then_branch: ASTNode,
|
|
|
|
|
|
else_branch: Option<ASTNode>,
|
|
|
|
|
|
) -> Result<ValueId, String> {
|
2025-12-04 06:29:54 +09:00
|
|
|
|
// Phase 124: JoinIR-only path (環境変数分岐削除)
|
|
|
|
|
|
// lower_if_form は JoinIR ベースの PHI 生成を使用
|
2025-09-19 08:34:29 +09:00
|
|
|
|
self.lower_if_form(condition, then_branch, else_branch)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// Control-flow: loop
|
2025-11-28 19:12:59 +09:00
|
|
|
|
///
|
|
|
|
|
|
/// # Phase 49: JoinIR Frontend Mainline Integration
|
|
|
|
|
|
///
|
2025-11-28 20:12:39 +09:00
|
|
|
|
/// This is the unified entry point for all loop lowering. Specific functions
|
|
|
|
|
|
/// are routed through JoinIR Frontend instead of the traditional LoopBuilder path
|
2025-12-01 11:10:46 +09:00
|
|
|
|
/// when enabled via dev flags (Phase 49) or Core policy (Phase 80):
|
2025-11-28 20:12:39 +09:00
|
|
|
|
///
|
2025-12-01 11:10:46 +09:00
|
|
|
|
/// - Core ON (`joinir_core_enabled()`): print_tokens / ArrayExt.filter はまず JoinIR Frontend を試す
|
|
|
|
|
|
/// - Dev フラグ(既存):
|
|
|
|
|
|
/// - `HAKO_JOINIR_PRINT_TOKENS_MAIN=1`: JsonTokenizer.print_tokens/0
|
|
|
|
|
|
/// - `HAKO_JOINIR_ARRAY_FILTER_MAIN=1`: ArrayExtBox.filter/2
|
2025-11-28 20:12:39 +09:00
|
|
|
|
///
|
|
|
|
|
|
/// Note: Arity does NOT include implicit `me` receiver.
|
2025-09-19 08:34:29 +09:00
|
|
|
|
pub(super) fn cf_loop(
|
|
|
|
|
|
&mut self,
|
|
|
|
|
|
condition: ASTNode,
|
|
|
|
|
|
body: Vec<ASTNode>,
|
|
|
|
|
|
) -> Result<ValueId, String> {
|
2025-12-01 11:10:46 +09:00
|
|
|
|
// Phase 49/80: Try JoinIR Frontend route for mainline targets
|
2025-11-28 19:12:59 +09:00
|
|
|
|
if let Some(result) = self.try_cf_loop_joinir(&condition, &body)? {
|
|
|
|
|
|
return Ok(result);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-18 06:39:45 +09:00
|
|
|
|
if std::env::var("NYASH_LOOPFORM_DEBUG").is_ok() {
|
|
|
|
|
|
eprintln!("[cf_loop] CALLED from somewhere");
|
|
|
|
|
|
eprintln!("[cf_loop] Current stack (simulated): check build_statement vs build_expression_impl");
|
|
|
|
|
|
}
|
2025-12-04 23:35:33 +09:00
|
|
|
|
|
2025-12-04 23:51:49 +09:00
|
|
|
|
// Phase 186: LoopBuilder Hard Freeze - Legacy path disabled
|
|
|
|
|
|
// Phase 187-2: LoopBuilder module removed - all loops must use JoinIR
|
|
|
|
|
|
return Err(format!(
|
|
|
|
|
|
"[joinir/freeze] Loop lowering failed: JoinIR does not support this pattern, and LoopBuilder has been removed.\n\
|
|
|
|
|
|
Function: {}\n\
|
|
|
|
|
|
Hint: This loop pattern is not supported. All loops must use JoinIR lowering.",
|
|
|
|
|
|
self.current_function.as_ref().map(|f| f.signature.name.as_str()).unwrap_or("<unknown>")
|
|
|
|
|
|
));
|
2025-09-19 08:34:29 +09:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-28 19:12:59 +09:00
|
|
|
|
/// Phase 49: Try JoinIR Frontend for mainline integration
|
|
|
|
|
|
///
|
|
|
|
|
|
/// Returns `Ok(Some(value))` if the current function should use JoinIR Frontend,
|
|
|
|
|
|
/// `Ok(None)` to fall through to the legacy LoopBuilder path.
|
2025-11-28 20:12:39 +09:00
|
|
|
|
///
|
|
|
|
|
|
/// # Phase 49-4: Multi-target support
|
|
|
|
|
|
///
|
|
|
|
|
|
/// Targets are enabled via separate dev flags:
|
|
|
|
|
|
/// - `HAKO_JOINIR_PRINT_TOKENS_MAIN=1`: JsonTokenizer.print_tokens/0
|
|
|
|
|
|
/// - `HAKO_JOINIR_ARRAY_FILTER_MAIN=1`: ArrayExtBox.filter/2
|
|
|
|
|
|
///
|
|
|
|
|
|
/// Note: Arity in function names does NOT include implicit `me` receiver.
|
|
|
|
|
|
/// - Instance method `print_tokens()` → `/0` (no explicit params)
|
|
|
|
|
|
/// - Static method `filter(arr, pred)` → `/2` (two params)
|
2025-11-28 19:12:59 +09:00
|
|
|
|
fn try_cf_loop_joinir(
|
|
|
|
|
|
&mut self,
|
2025-11-28 19:29:45 +09:00
|
|
|
|
condition: &ASTNode,
|
|
|
|
|
|
body: &[ASTNode],
|
2025-11-28 19:12:59 +09:00
|
|
|
|
) -> Result<Option<ValueId>, String> {
|
|
|
|
|
|
// Get current function name
|
|
|
|
|
|
let func_name = self
|
|
|
|
|
|
.current_function
|
|
|
|
|
|
.as_ref()
|
2025-11-28 19:29:45 +09:00
|
|
|
|
.map(|f| f.signature.name.clone())
|
|
|
|
|
|
.unwrap_or_default();
|
2025-11-28 19:12:59 +09:00
|
|
|
|
|
2025-12-05 19:27:52 +09:00
|
|
|
|
eprintln!("[cf_loop/joinir/router] try_cf_loop_joinir called for function: '{}'", func_name);
|
|
|
|
|
|
|
2025-12-05 16:04:15 +09:00
|
|
|
|
// Phase 188-Impl-3: Debug logging for function name routing
|
|
|
|
|
|
if std::env::var("NYASH_JOINIR_MAINLINE_DEBUG").is_ok() {
|
|
|
|
|
|
eprintln!("[cf_loop/joinir/router] Current function name: '{}'", func_name);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-01 11:10:46 +09:00
|
|
|
|
// Phase 49-4 + Phase 80: Multi-target routing
|
|
|
|
|
|
// - Core ON なら代表2本(print_tokens / ArrayExt.filter)は JoinIR を優先し、失敗したら LoopBuilder へフォールバック
|
|
|
|
|
|
// - Core OFF では従来通り dev フラグで opt-in
|
2025-11-28 20:12:39 +09:00
|
|
|
|
// Note: Arity does NOT include implicit `me` receiver
|
2025-12-05 07:47:22 +09:00
|
|
|
|
// Phase 188: Add "main" routing for loop pattern expansion
|
2025-12-01 11:10:46 +09:00
|
|
|
|
let core_on = crate::config::env::joinir_core_enabled();
|
2025-11-28 20:12:39 +09:00
|
|
|
|
let is_target = match func_name.as_str() {
|
2025-12-05 15:28:54 +09:00
|
|
|
|
"main" => true, // Phase 188-Impl-1: Enable JoinIR for main function (Pattern 1)
|
|
|
|
|
|
"JoinIrMin.main/0" => true, // Phase 188-Impl-2: Enable JoinIR for JoinIrMin.main/0 (Pattern 2)
|
2025-11-28 20:12:39 +09:00
|
|
|
|
"JsonTokenizer.print_tokens/0" => {
|
2025-12-01 11:10:46 +09:00
|
|
|
|
if core_on {
|
|
|
|
|
|
true
|
|
|
|
|
|
} else {
|
|
|
|
|
|
std::env::var("HAKO_JOINIR_PRINT_TOKENS_MAIN")
|
|
|
|
|
|
.ok()
|
|
|
|
|
|
.as_deref()
|
|
|
|
|
|
== Some("1")
|
|
|
|
|
|
}
|
2025-11-28 20:12:39 +09:00
|
|
|
|
}
|
|
|
|
|
|
"ArrayExtBox.filter/2" => {
|
2025-12-01 11:10:46 +09:00
|
|
|
|
if core_on {
|
|
|
|
|
|
true
|
|
|
|
|
|
} else {
|
|
|
|
|
|
std::env::var("HAKO_JOINIR_ARRAY_FILTER_MAIN")
|
|
|
|
|
|
.ok()
|
|
|
|
|
|
.as_deref()
|
|
|
|
|
|
== Some("1")
|
|
|
|
|
|
}
|
2025-11-28 20:12:39 +09:00
|
|
|
|
}
|
|
|
|
|
|
_ => false,
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
if !is_target {
|
2025-11-28 19:12:59 +09:00
|
|
|
|
return Ok(None);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Debug log when routing through JoinIR Frontend
|
2025-11-28 19:29:45 +09:00
|
|
|
|
let debug = std::env::var("NYASH_LOOPFORM_DEBUG").is_ok()
|
|
|
|
|
|
|| std::env::var("NYASH_JOINIR_MAINLINE_DEBUG").is_ok();
|
2025-12-05 19:27:52 +09:00
|
|
|
|
eprintln!("[cf_loop/joinir] DEBUG FLAG STATUS: debug={}, NYASH_LOOPFORM_DEBUG={:?}, NYASH_JOINIR_MAINLINE_DEBUG={:?}",
|
|
|
|
|
|
debug,
|
|
|
|
|
|
std::env::var("NYASH_LOOPFORM_DEBUG"),
|
|
|
|
|
|
std::env::var("NYASH_JOINIR_MAINLINE_DEBUG"));
|
2025-11-28 19:29:45 +09:00
|
|
|
|
if debug {
|
2025-11-28 19:12:59 +09:00
|
|
|
|
eprintln!(
|
|
|
|
|
|
"[cf_loop/joinir] Routing {} through JoinIR Frontend mainline",
|
|
|
|
|
|
func_name
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-28 19:29:45 +09:00
|
|
|
|
// Phase 49-3: Implement JoinIR Frontend integration
|
|
|
|
|
|
self.cf_loop_joinir_impl(condition, body, &func_name, debug)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// Phase 49-3: JoinIR Frontend integration implementation
|
|
|
|
|
|
///
|
|
|
|
|
|
/// # Pipeline
|
2025-11-28 20:12:39 +09:00
|
|
|
|
/// 1. Build Loop AST → JSON v0 format (with "defs" array)
|
2025-11-28 19:29:45 +09:00
|
|
|
|
/// 2. AstToJoinIrLowerer::lower_program_json() → JoinModule
|
|
|
|
|
|
/// 3. convert_join_module_to_mir_with_meta() → MirModule
|
|
|
|
|
|
/// 4. Merge MIR blocks into current_function
|
2025-11-28 20:12:39 +09:00
|
|
|
|
///
|
|
|
|
|
|
/// # Phase 49-4 Note
|
|
|
|
|
|
///
|
|
|
|
|
|
/// JoinIR Frontend expects a complete function definition with:
|
|
|
|
|
|
/// - local variable initializations
|
|
|
|
|
|
/// - loop body
|
|
|
|
|
|
/// - return statement
|
|
|
|
|
|
///
|
|
|
|
|
|
/// Since cf_loop only has access to the loop condition and body,
|
|
|
|
|
|
/// we construct a minimal JSON v0 wrapper with function name "simple"
|
|
|
|
|
|
/// to match the JoinIR Frontend's expected pattern.
|
2025-11-28 19:29:45 +09:00
|
|
|
|
fn cf_loop_joinir_impl(
|
|
|
|
|
|
&mut self,
|
|
|
|
|
|
condition: &ASTNode,
|
|
|
|
|
|
body: &[ASTNode],
|
|
|
|
|
|
func_name: &str,
|
|
|
|
|
|
debug: bool,
|
|
|
|
|
|
) -> Result<Option<ValueId>, String> {
|
2025-11-28 20:59:54 +09:00
|
|
|
|
use super::loop_frontend_binding::LoopFrontendBinding;
|
2025-11-28 19:29:45 +09:00
|
|
|
|
use crate::mir::join_ir::frontend::{AstToJoinIrLowerer, JoinFuncMetaMap};
|
|
|
|
|
|
use crate::mir::join_ir_vm_bridge::convert_join_module_to_mir_with_meta;
|
|
|
|
|
|
use crate::mir::types::ConstValue;
|
2025-11-29 12:44:40 +09:00
|
|
|
|
use crate::r#macro::ast_json::ast_to_json;
|
2025-11-28 19:29:45 +09:00
|
|
|
|
|
2025-12-05 16:04:15 +09:00
|
|
|
|
// Phase 188-Impl-3: Route Pattern 3 (If-Else PHI) - detect by 'sum' variable presence
|
|
|
|
|
|
// This MUST come before Pattern 1 check to avoid incorrect routing
|
|
|
|
|
|
// Pattern 3 test (loop_if_phi.hako) uses "main" with 'sum' accumulator variable
|
|
|
|
|
|
if func_name == "main" && self.variable_map.contains_key("sum") {
|
|
|
|
|
|
if debug {
|
|
|
|
|
|
eprintln!("[cf_loop/joinir] Routing 'main' (with sum var) through Pattern 3 minimal lowerer");
|
|
|
|
|
|
}
|
|
|
|
|
|
return self.cf_loop_pattern3_with_if_phi(condition, body, func_name, debug);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Phase 188-Impl-1-F: Route Pattern 1 (Simple While) - "main" without 'sum' variable
|
2025-12-05 12:50:05 +09:00
|
|
|
|
if func_name == "main" {
|
|
|
|
|
|
if debug {
|
2025-12-05 16:04:15 +09:00
|
|
|
|
eprintln!("[cf_loop/joinir] Routing '{}' through Pattern 1 minimal lowerer", func_name);
|
2025-12-05 12:50:05 +09:00
|
|
|
|
}
|
|
|
|
|
|
return self.cf_loop_pattern1_minimal(condition, body, func_name, debug);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-05 16:04:15 +09:00
|
|
|
|
// Phase 188-Impl-2: Route "JoinIrMin.main/0" through Pattern 2 minimal lowerer (Loop with Break)
|
2025-12-05 15:28:54 +09:00
|
|
|
|
if func_name == "JoinIrMin.main/0" {
|
|
|
|
|
|
if debug {
|
|
|
|
|
|
eprintln!("[cf_loop/joinir] Routing 'JoinIrMin.main/0' through Pattern 2 minimal lowerer");
|
|
|
|
|
|
}
|
|
|
|
|
|
return self.cf_loop_pattern2_with_break(condition, body, func_name, debug);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-28 20:59:54 +09:00
|
|
|
|
// Phase 50: Create appropriate binding based on function name
|
|
|
|
|
|
let binding = match func_name {
|
|
|
|
|
|
"JsonTokenizer.print_tokens/0" => LoopFrontendBinding::for_print_tokens(),
|
|
|
|
|
|
"ArrayExtBox.filter/2" => LoopFrontendBinding::for_array_filter(),
|
|
|
|
|
|
_ => {
|
|
|
|
|
|
if debug {
|
|
|
|
|
|
eprintln!(
|
|
|
|
|
|
"[cf_loop/joinir] No binding defined for {}, falling back",
|
|
|
|
|
|
func_name
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
return Ok(None);
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
if debug {
|
|
|
|
|
|
eprintln!(
|
|
|
|
|
|
"[cf_loop/joinir] Using binding: counter={}, acc={:?}, pattern={:?}",
|
2025-11-29 12:44:40 +09:00
|
|
|
|
binding.counter_var, binding.accumulator_var, binding.pattern
|
2025-11-28 20:59:54 +09:00
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-28 20:12:39 +09:00
|
|
|
|
// Step 1: Convert condition and body to JSON
|
|
|
|
|
|
let condition_json = ast_to_json(condition);
|
2025-11-29 12:44:40 +09:00
|
|
|
|
let mut body_json: Vec<serde_json::Value> = body.iter().map(|s| ast_to_json(s)).collect();
|
2025-11-28 20:59:54 +09:00
|
|
|
|
|
|
|
|
|
|
// Phase 50: Rename variables in body (e.g., "out" → "acc" for filter)
|
|
|
|
|
|
binding.rename_body_variables(&mut body_json);
|
|
|
|
|
|
|
|
|
|
|
|
// Phase 50: Generate Local declarations from binding
|
|
|
|
|
|
let (i_local, acc_local, n_local) = binding.generate_local_declarations();
|
2025-11-28 20:12:39 +09:00
|
|
|
|
|
2025-11-29 06:51:43 +09:00
|
|
|
|
// Phase 52/56: Build params from external_refs
|
|
|
|
|
|
// Instance methods need `me`, static methods need their parameters (arr, pred, etc.)
|
|
|
|
|
|
let mut params: Vec<serde_json::Value> = Vec::new();
|
|
|
|
|
|
|
|
|
|
|
|
// Phase 52: Add 'me' for instance methods
|
|
|
|
|
|
if binding.needs_me_receiver() {
|
2025-11-29 04:42:16 +09:00
|
|
|
|
if debug {
|
|
|
|
|
|
eprintln!("[cf_loop/joinir] Adding 'me' to params (instance method)");
|
|
|
|
|
|
}
|
2025-11-29 06:51:43 +09:00
|
|
|
|
params.push(serde_json::json!("me"));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Phase 56: Add external_refs as parameters (arr, pred for filter)
|
|
|
|
|
|
for ext_ref in &binding.external_refs {
|
|
|
|
|
|
// Skip "me" and "me.*" as they're handled above
|
|
|
|
|
|
if ext_ref == "me" || ext_ref.starts_with("me.") {
|
|
|
|
|
|
continue;
|
|
|
|
|
|
}
|
|
|
|
|
|
if debug {
|
2025-11-29 12:44:40 +09:00
|
|
|
|
eprintln!(
|
|
|
|
|
|
"[cf_loop/joinir] Adding '{}' to params (external_ref)",
|
|
|
|
|
|
ext_ref
|
|
|
|
|
|
);
|
2025-11-29 06:51:43 +09:00
|
|
|
|
}
|
|
|
|
|
|
params.push(serde_json::json!(ext_ref));
|
|
|
|
|
|
}
|
2025-11-29 04:42:16 +09:00
|
|
|
|
|
2025-11-28 20:12:39 +09:00
|
|
|
|
// Step 2: Construct JSON v0 format with "defs" array
|
|
|
|
|
|
// The function is named "simple" to match JoinIR Frontend's pattern matching
|
2025-11-28 20:59:54 +09:00
|
|
|
|
// Phase 50: Include i/acc/n Local declarations to satisfy JoinIR Frontend expectations
|
2025-11-28 20:12:39 +09:00
|
|
|
|
let program_json = serde_json::json!({
|
|
|
|
|
|
"defs": [
|
|
|
|
|
|
{
|
|
|
|
|
|
"name": "simple",
|
2025-11-29 04:42:16 +09:00
|
|
|
|
"params": params,
|
2025-11-28 20:12:39 +09:00
|
|
|
|
"body": {
|
|
|
|
|
|
"type": "Block",
|
|
|
|
|
|
"body": [
|
2025-11-28 20:59:54 +09:00
|
|
|
|
// Phase 50: Inject i/acc/n Local declarations
|
|
|
|
|
|
i_local,
|
|
|
|
|
|
acc_local,
|
|
|
|
|
|
n_local,
|
2025-11-28 20:12:39 +09:00
|
|
|
|
{
|
|
|
|
|
|
"type": "Loop",
|
2025-11-29 04:42:16 +09:00
|
|
|
|
"cond": condition_json, // JoinIR Frontend expects "cond" not "condition"
|
2025-11-28 20:12:39 +09:00
|
|
|
|
"body": body_json
|
|
|
|
|
|
},
|
2025-11-28 20:59:54 +09:00
|
|
|
|
// Return the accumulator (or null for side-effect loops)
|
2025-11-28 20:12:39 +09:00
|
|
|
|
{
|
|
|
|
|
|
"type": "Return",
|
2025-11-28 20:59:54 +09:00
|
|
|
|
"value": { "kind": "Variable", "name": "acc" }
|
2025-11-28 20:12:39 +09:00
|
|
|
|
}
|
|
|
|
|
|
]
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
]
|
|
|
|
|
|
});
|
2025-11-28 19:29:45 +09:00
|
|
|
|
|
|
|
|
|
|
if debug {
|
|
|
|
|
|
eprintln!(
|
2025-11-28 20:12:39 +09:00
|
|
|
|
"[cf_loop/joinir] Generated JSON v0 for {}: {}",
|
2025-11-28 19:29:45 +09:00
|
|
|
|
func_name,
|
|
|
|
|
|
serde_json::to_string_pretty(&program_json).unwrap_or_default()
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Step 3: Lower to JoinIR
|
2025-11-28 20:12:39 +09:00
|
|
|
|
// Phase 49-4: Use catch_unwind for graceful fallback on unsupported patterns
|
|
|
|
|
|
// The JoinIR Frontend may panic if the loop doesn't match expected patterns
|
|
|
|
|
|
// (e.g., missing variable initializations like "i must be initialized")
|
|
|
|
|
|
let join_module = {
|
|
|
|
|
|
let json_clone = program_json.clone();
|
|
|
|
|
|
let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
|
|
|
|
|
|
let mut lowerer = AstToJoinIrLowerer::new();
|
|
|
|
|
|
lowerer.lower_program_json(&json_clone)
|
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
|
|
match result {
|
|
|
|
|
|
Ok(module) => module,
|
|
|
|
|
|
Err(e) => {
|
|
|
|
|
|
// Extract panic message for debugging
|
|
|
|
|
|
let panic_msg = if let Some(s) = e.downcast_ref::<&str>() {
|
|
|
|
|
|
s.to_string()
|
|
|
|
|
|
} else if let Some(s) = e.downcast_ref::<String>() {
|
|
|
|
|
|
s.clone()
|
|
|
|
|
|
} else {
|
|
|
|
|
|
"unknown panic".to_string()
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
if debug {
|
|
|
|
|
|
eprintln!(
|
|
|
|
|
|
"[cf_loop/joinir] JoinIR lowering failed for {}: {}, falling back to legacy",
|
|
|
|
|
|
func_name, panic_msg
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
// Return None to fall back to legacy LoopBuilder
|
|
|
|
|
|
return Ok(None);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
2025-11-28 19:29:45 +09:00
|
|
|
|
// Phase 49-3 MVP: Use empty meta map (full if-analysis is Phase 40+ territory)
|
|
|
|
|
|
let join_meta = JoinFuncMetaMap::new();
|
|
|
|
|
|
|
|
|
|
|
|
if debug {
|
|
|
|
|
|
eprintln!(
|
|
|
|
|
|
"[cf_loop/joinir] JoinModule has {} functions, entry={:?}",
|
|
|
|
|
|
join_module.functions.len(),
|
|
|
|
|
|
join_module.entry
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Step 4: Convert JoinModule to MIR
|
|
|
|
|
|
let mir_module = convert_join_module_to_mir_with_meta(&join_module, &join_meta)
|
|
|
|
|
|
.map_err(|e| format!("JoinIR→MIR conversion failed: {}", e.message))?;
|
|
|
|
|
|
|
|
|
|
|
|
if debug {
|
|
|
|
|
|
eprintln!(
|
|
|
|
|
|
"[cf_loop/joinir] MirModule has {} functions",
|
|
|
|
|
|
mir_module.functions.len()
|
|
|
|
|
|
);
|
2025-12-05 11:08:08 +09:00
|
|
|
|
for (name, func) in &mir_module.functions {
|
|
|
|
|
|
eprintln!(
|
|
|
|
|
|
"[cf_loop/joinir] - {}: {} blocks, entry={:?}",
|
|
|
|
|
|
name,
|
|
|
|
|
|
func.blocks.len(),
|
|
|
|
|
|
func.entry_block
|
|
|
|
|
|
);
|
|
|
|
|
|
// Phase 189: Debug - show block contents
|
|
|
|
|
|
for (block_id, block) in &func.blocks {
|
|
|
|
|
|
eprintln!("[cf_loop/joinir] Block {:?}: {} instructions", block_id, block.instructions.len());
|
|
|
|
|
|
for (i, inst) in block.instructions.iter().enumerate() {
|
|
|
|
|
|
eprintln!("[cf_loop/joinir] [{}] {:?}", i, inst);
|
|
|
|
|
|
}
|
|
|
|
|
|
if let Some(ref term) = block.terminator {
|
|
|
|
|
|
eprintln!("[cf_loop/joinir] terminator: {:?}", term);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-11-28 19:29:45 +09:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Step 5: Merge MIR blocks into current_function
|
|
|
|
|
|
// For Phase 49-3, we'll use a simplified approach:
|
|
|
|
|
|
// - Add generated blocks to current_function
|
|
|
|
|
|
// - Jump from current_block to the entry of generated loop
|
|
|
|
|
|
// - The loop exit becomes the new current_block
|
2025-12-05 13:46:44 +09:00
|
|
|
|
// Phase 188-Impl-3: Pass None for boundary (legacy path without boundary)
|
2025-12-05 19:27:52 +09:00
|
|
|
|
// Phase 189: Discard exit PHI result (legacy path doesn't need it)
|
|
|
|
|
|
let _ = self.merge_joinir_mir_blocks(&mir_module, None, debug)?;
|
2025-11-28 19:29:45 +09:00
|
|
|
|
|
|
|
|
|
|
// Return void for now (loop doesn't have a meaningful return value in this context)
|
|
|
|
|
|
let void_val = self.next_value_id();
|
|
|
|
|
|
self.emit_instruction(MirInstruction::Const {
|
|
|
|
|
|
dst: void_val,
|
|
|
|
|
|
value: ConstValue::Void,
|
|
|
|
|
|
})?;
|
|
|
|
|
|
|
|
|
|
|
|
Ok(Some(void_val))
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-05 12:50:05 +09:00
|
|
|
|
/// Phase 188-Impl-1-F: Pattern 1 (Simple While Loop) minimal lowerer
|
|
|
|
|
|
///
|
|
|
|
|
|
/// This bypasses the LoopFrontendBinding JSON path and directly calls
|
|
|
|
|
|
/// the Pattern 1 minimal lowerer for apps/tests/loop_min_while.hako
|
|
|
|
|
|
///
|
2025-12-05 13:03:48 +09:00
|
|
|
|
/// # Phase 188-Impl-2: Host Variable Integration
|
|
|
|
|
|
///
|
|
|
|
|
|
/// Extracts the loop variable from the host function (e.g., `i` from `i < 3`)
|
|
|
|
|
|
/// and passes it to the Pattern 1 lowerer along with a ValueId allocator.
|
|
|
|
|
|
///
|
2025-12-05 12:50:05 +09:00
|
|
|
|
/// # Pipeline
|
2025-12-05 13:03:48 +09:00
|
|
|
|
/// 1. Extract loop variable name from condition
|
|
|
|
|
|
/// 2. Look up ValueId in host variable_map
|
|
|
|
|
|
/// 3. Create Pattern1Context with host variable + allocator
|
|
|
|
|
|
/// 4. Call simple_while_minimal::lower_simple_while_minimal() → JoinModule
|
|
|
|
|
|
/// 5. convert_join_module_to_mir_with_meta() → MirModule
|
|
|
|
|
|
/// 6. Merge MIR blocks into current_function
|
2025-12-05 12:50:05 +09:00
|
|
|
|
fn cf_loop_pattern1_minimal(
|
|
|
|
|
|
&mut self,
|
2025-12-05 13:03:48 +09:00
|
|
|
|
condition: &ASTNode,
|
2025-12-05 12:50:05 +09:00
|
|
|
|
_body: &[ASTNode],
|
|
|
|
|
|
_func_name: &str,
|
|
|
|
|
|
debug: bool,
|
|
|
|
|
|
) -> Result<Option<ValueId>, String> {
|
2025-12-05 13:03:48 +09:00
|
|
|
|
use crate::mir::join_ir::lowering::simple_while_minimal::{
|
|
|
|
|
|
lower_simple_while_minimal, Pattern1Context,
|
|
|
|
|
|
};
|
2025-12-05 12:50:05 +09:00
|
|
|
|
use crate::mir::join_ir_vm_bridge::convert_join_module_to_mir_with_meta;
|
|
|
|
|
|
use crate::mir::BasicBlockId;
|
|
|
|
|
|
use std::collections::{BTreeMap, BTreeSet};
|
|
|
|
|
|
|
|
|
|
|
|
if debug {
|
|
|
|
|
|
eprintln!("[cf_loop/joinir/pattern1] Calling Pattern 1 minimal lowerer");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-05 13:03:48 +09:00
|
|
|
|
// Phase 188-Impl-2: Extract loop variable from condition
|
|
|
|
|
|
// For `i < 3`, extract `i` and look up its ValueId in variable_map
|
|
|
|
|
|
let loop_var_name = self.extract_loop_variable_from_condition(condition)?;
|
|
|
|
|
|
let loop_var_id = self
|
|
|
|
|
|
.variable_map
|
|
|
|
|
|
.get(&loop_var_name)
|
|
|
|
|
|
.copied()
|
|
|
|
|
|
.ok_or_else(|| {
|
|
|
|
|
|
format!(
|
|
|
|
|
|
"[cf_loop/pattern1] Loop variable '{}' not found in variable_map",
|
|
|
|
|
|
loop_var_name
|
|
|
|
|
|
)
|
|
|
|
|
|
})?;
|
|
|
|
|
|
|
|
|
|
|
|
if debug {
|
|
|
|
|
|
eprintln!(
|
|
|
|
|
|
"[cf_loop/joinir/pattern1] Loop variable '{}' → {:?}",
|
|
|
|
|
|
loop_var_name, loop_var_id
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-05 12:50:05 +09:00
|
|
|
|
// Create a minimal LoopScopeShape (Phase 188: hardcoded for loop_min_while.hako)
|
|
|
|
|
|
// Pattern 1 lowerer ignores the scope anyway, so this is just a placeholder
|
|
|
|
|
|
use crate::mir::join_ir::lowering::loop_scope_shape::LoopScopeShape;
|
|
|
|
|
|
let scope = LoopScopeShape {
|
|
|
|
|
|
header: BasicBlockId(0),
|
|
|
|
|
|
body: BasicBlockId(0),
|
|
|
|
|
|
latch: BasicBlockId(0),
|
|
|
|
|
|
exit: BasicBlockId(0),
|
|
|
|
|
|
pinned: BTreeSet::new(),
|
|
|
|
|
|
carriers: BTreeSet::new(),
|
|
|
|
|
|
body_locals: BTreeSet::new(),
|
|
|
|
|
|
exit_live: BTreeSet::new(),
|
|
|
|
|
|
progress_carrier: None,
|
|
|
|
|
|
variable_definitions: BTreeMap::new(),
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2025-12-05 13:03:48 +09:00
|
|
|
|
// Phase 188-Impl-2: Create Pattern1Context with host variable + allocator
|
|
|
|
|
|
// Clone value_gen to move into closure
|
|
|
|
|
|
let mut value_gen_clone = self.value_gen.clone();
|
|
|
|
|
|
let ctx = Pattern1Context {
|
|
|
|
|
|
loop_var: loop_var_id,
|
|
|
|
|
|
value_allocator: Box::new(move || value_gen_clone.next()),
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// Call Pattern 1 lowerer with host context
|
|
|
|
|
|
let join_module = match lower_simple_while_minimal(scope, Some(ctx)) {
|
2025-12-05 12:50:05 +09:00
|
|
|
|
Some(module) => module,
|
|
|
|
|
|
None => {
|
|
|
|
|
|
if debug {
|
|
|
|
|
|
eprintln!("[cf_loop/joinir/pattern1] Pattern 1 lowerer returned None");
|
|
|
|
|
|
}
|
|
|
|
|
|
return Ok(None);
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
if debug {
|
|
|
|
|
|
eprintln!(
|
|
|
|
|
|
"[cf_loop/joinir/pattern1] JoinModule generated with {} functions",
|
|
|
|
|
|
join_module.functions.len()
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Convert JoinModule to MirModule
|
|
|
|
|
|
// Phase 188: Pass empty meta map since Pattern 1 lowerer doesn't use metadata
|
|
|
|
|
|
use crate::mir::join_ir::frontend::JoinFuncMetaMap;
|
|
|
|
|
|
let empty_meta: JoinFuncMetaMap = BTreeMap::new();
|
|
|
|
|
|
|
|
|
|
|
|
let mir_module = convert_join_module_to_mir_with_meta(&join_module, &empty_meta)
|
|
|
|
|
|
.map_err(|e| format!("[cf_loop/joinir/pattern1] MIR conversion failed: {:?}", e))?;
|
|
|
|
|
|
|
|
|
|
|
|
if debug {
|
|
|
|
|
|
eprintln!(
|
|
|
|
|
|
"[cf_loop/joinir/pattern1] MirModule generated with {} functions",
|
|
|
|
|
|
mir_module.functions.len()
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Merge JoinIR blocks into current function
|
2025-12-05 13:46:44 +09:00
|
|
|
|
// Phase 188-Impl-3: Create and pass JoinInlineBoundary for Pattern 1
|
|
|
|
|
|
let boundary = crate::mir::join_ir::lowering::inline_boundary::JoinInlineBoundary::new_inputs_only(
|
|
|
|
|
|
vec![ValueId(0)], // JoinIR's main() parameter (loop variable)
|
|
|
|
|
|
vec![loop_var_id], // Host's loop variable
|
|
|
|
|
|
);
|
2025-12-05 19:27:52 +09:00
|
|
|
|
// Phase 189: Discard exit PHI result (Pattern 1 returns void)
|
|
|
|
|
|
let _ = self.merge_joinir_mir_blocks(&mir_module, Some(&boundary), debug)?;
|
2025-12-05 13:46:44 +09:00
|
|
|
|
|
|
|
|
|
|
// Phase 188-Impl-4-FIX: Return Void instead of trying to emit Const
|
|
|
|
|
|
//
|
|
|
|
|
|
// PROBLEM: Emitting instructions after merge_joinir_mir_blocks is fragile because:
|
|
|
|
|
|
// 1. merge creates exit block and switches to it
|
|
|
|
|
|
// 2. We try to add Const to exit block
|
|
|
|
|
|
// 3. But subsequent code (return statement) might overwrite the block
|
|
|
|
|
|
//
|
|
|
|
|
|
// SOLUTION: Loops don't produce values - they return Void.
|
|
|
|
|
|
// The subsequent "return 0" statement will emit its own Const + Return.
|
|
|
|
|
|
//
|
|
|
|
|
|
// This is cleaner because:
|
|
|
|
|
|
// - Loop lowering doesn't need to know about the return value
|
|
|
|
|
|
// - The return statement handles its own code generation
|
|
|
|
|
|
// - No risk of instructions being lost due to block management issues
|
|
|
|
|
|
|
|
|
|
|
|
let void_val = crate::mir::builder::emission::constant::emit_void(self);
|
2025-12-05 12:50:05 +09:00
|
|
|
|
|
2025-12-05 13:46:44 +09:00
|
|
|
|
if debug {
|
|
|
|
|
|
eprintln!(
|
|
|
|
|
|
"[cf_loop/joinir/pattern1] Loop complete, returning Void {:?}",
|
|
|
|
|
|
void_val
|
|
|
|
|
|
);
|
2025-12-05 12:50:05 +09:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-05 13:46:44 +09:00
|
|
|
|
Ok(Some(void_val))
|
2025-12-05 12:50:05 +09:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-05 15:28:54 +09:00
|
|
|
|
/// Phase 188-Impl-2: Pattern 2 (Loop with Conditional Break) minimal lowerer
|
|
|
|
|
|
///
|
|
|
|
|
|
/// Similar to Pattern 1, but handles loops with break statements.
|
|
|
|
|
|
///
|
|
|
|
|
|
/// # Steps
|
|
|
|
|
|
/// 1. Extract loop variable from condition
|
|
|
|
|
|
/// 2. Generate JoinIR using loop_with_break_minimal
|
|
|
|
|
|
/// 3. Convert JoinModule → MirModule
|
|
|
|
|
|
/// 4. Create JoinInlineBoundary for input mapping
|
|
|
|
|
|
/// 5. Merge MIR blocks into current_function
|
|
|
|
|
|
/// 6. Return Void (loop doesn't produce values)
|
|
|
|
|
|
fn cf_loop_pattern2_with_break(
|
|
|
|
|
|
&mut self,
|
|
|
|
|
|
condition: &ASTNode,
|
|
|
|
|
|
_body: &[ASTNode],
|
|
|
|
|
|
_func_name: &str,
|
|
|
|
|
|
debug: bool,
|
|
|
|
|
|
) -> Result<Option<ValueId>, String> {
|
|
|
|
|
|
use crate::mir::join_ir::lowering::loop_with_break_minimal::lower_loop_with_break_minimal;
|
|
|
|
|
|
use crate::mir::join_ir_vm_bridge::convert_join_module_to_mir_with_meta;
|
|
|
|
|
|
use crate::mir::BasicBlockId;
|
|
|
|
|
|
use std::collections::{BTreeMap, BTreeSet};
|
|
|
|
|
|
|
|
|
|
|
|
if debug {
|
|
|
|
|
|
eprintln!("[cf_loop/joinir/pattern2] Calling Pattern 2 minimal lowerer");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Phase 188-Impl-2: Extract loop variable from condition
|
|
|
|
|
|
// For `i < 3`, extract `i` and look up its ValueId in variable_map
|
|
|
|
|
|
let loop_var_name = self.extract_loop_variable_from_condition(condition)?;
|
|
|
|
|
|
let loop_var_id = self
|
|
|
|
|
|
.variable_map
|
|
|
|
|
|
.get(&loop_var_name)
|
|
|
|
|
|
.copied()
|
|
|
|
|
|
.ok_or_else(|| {
|
|
|
|
|
|
format!(
|
|
|
|
|
|
"[cf_loop/pattern2] Loop variable '{}' not found in variable_map",
|
|
|
|
|
|
loop_var_name
|
|
|
|
|
|
)
|
|
|
|
|
|
})?;
|
|
|
|
|
|
|
|
|
|
|
|
if debug {
|
|
|
|
|
|
eprintln!(
|
|
|
|
|
|
"[cf_loop/joinir/pattern2] Loop variable '{}' → {:?}",
|
|
|
|
|
|
loop_var_name, loop_var_id
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Create a minimal LoopScopeShape (Phase 188: hardcoded for joinir_min_loop.hako)
|
|
|
|
|
|
// Pattern 2 lowerer ignores the scope anyway, so this is just a placeholder
|
|
|
|
|
|
use crate::mir::join_ir::lowering::loop_scope_shape::LoopScopeShape;
|
|
|
|
|
|
let scope = LoopScopeShape {
|
|
|
|
|
|
header: BasicBlockId(0),
|
|
|
|
|
|
body: BasicBlockId(0),
|
|
|
|
|
|
latch: BasicBlockId(0),
|
|
|
|
|
|
exit: BasicBlockId(0),
|
|
|
|
|
|
pinned: BTreeSet::new(),
|
|
|
|
|
|
carriers: BTreeSet::new(),
|
|
|
|
|
|
body_locals: BTreeSet::new(),
|
|
|
|
|
|
exit_live: BTreeSet::new(),
|
|
|
|
|
|
progress_carrier: None,
|
|
|
|
|
|
variable_definitions: BTreeMap::new(),
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// Call Pattern 2 lowerer
|
|
|
|
|
|
let join_module = match lower_loop_with_break_minimal(scope) {
|
|
|
|
|
|
Some(module) => module,
|
|
|
|
|
|
None => {
|
|
|
|
|
|
if debug {
|
|
|
|
|
|
eprintln!("[cf_loop/joinir/pattern2] Pattern 2 lowerer returned None");
|
|
|
|
|
|
}
|
|
|
|
|
|
return Ok(None);
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
if debug {
|
|
|
|
|
|
eprintln!(
|
|
|
|
|
|
"[cf_loop/joinir/pattern2] JoinModule generated with {} functions",
|
|
|
|
|
|
join_module.functions.len()
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Convert JoinModule to MirModule
|
|
|
|
|
|
// Phase 188: Pass empty meta map since Pattern 2 lowerer doesn't use metadata
|
|
|
|
|
|
use crate::mir::join_ir::frontend::JoinFuncMetaMap;
|
|
|
|
|
|
let empty_meta: JoinFuncMetaMap = BTreeMap::new();
|
|
|
|
|
|
|
|
|
|
|
|
let mir_module = convert_join_module_to_mir_with_meta(&join_module, &empty_meta)
|
|
|
|
|
|
.map_err(|e| format!("[cf_loop/joinir/pattern2] MIR conversion failed: {:?}", e))?;
|
|
|
|
|
|
|
|
|
|
|
|
if debug {
|
|
|
|
|
|
eprintln!(
|
|
|
|
|
|
"[cf_loop/joinir/pattern2] MirModule generated with {} functions",
|
|
|
|
|
|
mir_module.functions.len()
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Merge JoinIR blocks into current function
|
|
|
|
|
|
// Phase 188-Impl-2: Create and pass JoinInlineBoundary for Pattern 2
|
|
|
|
|
|
let boundary = crate::mir::join_ir::lowering::inline_boundary::JoinInlineBoundary::new_inputs_only(
|
|
|
|
|
|
vec![ValueId(0)], // JoinIR's main() parameter (loop variable init)
|
|
|
|
|
|
vec![loop_var_id], // Host's loop variable
|
|
|
|
|
|
);
|
2025-12-05 19:27:52 +09:00
|
|
|
|
// Phase 189: Discard exit PHI result (Pattern 2 returns void)
|
|
|
|
|
|
let _ = self.merge_joinir_mir_blocks(&mir_module, Some(&boundary), debug)?;
|
2025-12-05 15:28:54 +09:00
|
|
|
|
|
|
|
|
|
|
// Phase 188-Impl-2: Return Void (loops don't produce values)
|
|
|
|
|
|
// The subsequent "return i" statement will emit its own Load + Return
|
|
|
|
|
|
let void_val = crate::mir::builder::emission::constant::emit_void(self);
|
|
|
|
|
|
|
|
|
|
|
|
if debug {
|
|
|
|
|
|
eprintln!(
|
|
|
|
|
|
"[cf_loop/joinir/pattern2] Loop complete, returning Void {:?}",
|
|
|
|
|
|
void_val
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
Ok(Some(void_val))
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-05 16:04:15 +09:00
|
|
|
|
/// Phase 188-Impl-3: Pattern 3 (Loop with If-Else PHI) minimal lowerer
|
|
|
|
|
|
///
|
|
|
|
|
|
/// Handles loops with if-else statements that assign to carrier variables.
|
|
|
|
|
|
///
|
|
|
|
|
|
/// # Steps
|
|
|
|
|
|
/// 1. Extract loop variables (carriers: i + sum)
|
|
|
|
|
|
/// 2. Generate JoinIR using loop_with_if_phi_minimal
|
|
|
|
|
|
/// 3. Convert JoinModule → MirModule
|
|
|
|
|
|
/// 4. Create JoinInlineBoundary for input mapping
|
|
|
|
|
|
/// 5. Merge MIR blocks into current_function
|
|
|
|
|
|
/// 6. Return Void (loop doesn't produce values)
|
|
|
|
|
|
fn cf_loop_pattern3_with_if_phi(
|
|
|
|
|
|
&mut self,
|
|
|
|
|
|
condition: &ASTNode,
|
|
|
|
|
|
_body: &[ASTNode],
|
|
|
|
|
|
_func_name: &str,
|
|
|
|
|
|
debug: bool,
|
|
|
|
|
|
) -> Result<Option<ValueId>, String> {
|
|
|
|
|
|
use crate::mir::join_ir::lowering::loop_with_if_phi_minimal::lower_loop_with_if_phi_pattern;
|
|
|
|
|
|
use crate::mir::join_ir_vm_bridge::convert_join_module_to_mir_with_meta;
|
|
|
|
|
|
use crate::mir::BasicBlockId;
|
|
|
|
|
|
use std::collections::{BTreeMap, BTreeSet};
|
|
|
|
|
|
|
|
|
|
|
|
if debug {
|
|
|
|
|
|
eprintln!("[cf_loop/joinir/pattern3] Calling Pattern 3 minimal lowerer");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Phase 188-Impl-3: Extract loop variable from condition
|
|
|
|
|
|
// For `i <= 5`, extract `i` and look up its ValueId in variable_map
|
|
|
|
|
|
let loop_var_name = self.extract_loop_variable_from_condition(condition)?;
|
|
|
|
|
|
let loop_var_id = self
|
|
|
|
|
|
.variable_map
|
|
|
|
|
|
.get(&loop_var_name)
|
|
|
|
|
|
.copied()
|
|
|
|
|
|
.ok_or_else(|| {
|
|
|
|
|
|
format!(
|
|
|
|
|
|
"[cf_loop/pattern3] Loop variable '{}' not found in variable_map",
|
|
|
|
|
|
loop_var_name
|
|
|
|
|
|
)
|
|
|
|
|
|
})?;
|
|
|
|
|
|
|
|
|
|
|
|
// Phase 188-Impl-3: Also get the accumulator variable (sum)
|
|
|
|
|
|
// For Pattern 3, we need both i and sum
|
|
|
|
|
|
let sum_var_id = self
|
|
|
|
|
|
.variable_map
|
|
|
|
|
|
.get("sum")
|
|
|
|
|
|
.copied()
|
|
|
|
|
|
.ok_or_else(|| {
|
|
|
|
|
|
format!(
|
|
|
|
|
|
"[cf_loop/pattern3] Accumulator variable 'sum' not found in variable_map"
|
|
|
|
|
|
)
|
|
|
|
|
|
})?;
|
|
|
|
|
|
|
|
|
|
|
|
if debug {
|
|
|
|
|
|
eprintln!(
|
|
|
|
|
|
"[cf_loop/joinir/pattern3] Loop variables: '{}' → {:?}, 'sum' → {:?}",
|
|
|
|
|
|
loop_var_name, loop_var_id, sum_var_id
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Create a minimal LoopScopeShape (Phase 188: hardcoded for loop_if_phi.hako)
|
|
|
|
|
|
// Pattern 3 lowerer ignores the scope anyway, so this is just a placeholder
|
|
|
|
|
|
use crate::mir::join_ir::lowering::loop_scope_shape::LoopScopeShape;
|
|
|
|
|
|
let scope = LoopScopeShape {
|
|
|
|
|
|
header: BasicBlockId(0),
|
|
|
|
|
|
body: BasicBlockId(0),
|
|
|
|
|
|
latch: BasicBlockId(0),
|
|
|
|
|
|
exit: BasicBlockId(0),
|
|
|
|
|
|
pinned: BTreeSet::new(),
|
|
|
|
|
|
carriers: BTreeSet::new(),
|
|
|
|
|
|
body_locals: BTreeSet::new(),
|
|
|
|
|
|
exit_live: BTreeSet::new(),
|
|
|
|
|
|
progress_carrier: None,
|
|
|
|
|
|
variable_definitions: BTreeMap::new(),
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// Call Pattern 3 lowerer
|
|
|
|
|
|
let join_module = match lower_loop_with_if_phi_pattern(scope) {
|
|
|
|
|
|
Some(module) => module,
|
|
|
|
|
|
None => {
|
|
|
|
|
|
if debug {
|
|
|
|
|
|
eprintln!("[cf_loop/joinir/pattern3] Pattern 3 lowerer returned None");
|
|
|
|
|
|
}
|
|
|
|
|
|
return Ok(None);
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
if debug {
|
|
|
|
|
|
eprintln!(
|
|
|
|
|
|
"[cf_loop/joinir/pattern3] JoinModule generated with {} functions",
|
|
|
|
|
|
join_module.functions.len()
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Convert JoinModule to MirModule
|
|
|
|
|
|
// Phase 188: Pass empty meta map since Pattern 3 lowerer doesn't use metadata
|
|
|
|
|
|
use crate::mir::join_ir::frontend::JoinFuncMetaMap;
|
|
|
|
|
|
let empty_meta: JoinFuncMetaMap = BTreeMap::new();
|
|
|
|
|
|
|
|
|
|
|
|
let mir_module = convert_join_module_to_mir_with_meta(&join_module, &empty_meta)
|
|
|
|
|
|
.map_err(|e| format!("[cf_loop/joinir/pattern3] MIR conversion failed: {:?}", e))?;
|
|
|
|
|
|
|
|
|
|
|
|
if debug {
|
|
|
|
|
|
eprintln!(
|
|
|
|
|
|
"[cf_loop/joinir/pattern3] MirModule generated with {} functions",
|
|
|
|
|
|
mir_module.functions.len()
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Merge JoinIR blocks into current function
|
2025-12-05 19:59:40 +09:00
|
|
|
|
// Phase 190: Use explicit LoopExitBinding for Pattern 3
|
2025-12-05 16:04:15 +09:00
|
|
|
|
// Pattern 3 has TWO carriers: i and sum
|
2025-12-05 19:35:30 +09:00
|
|
|
|
self.trace_varmap("pattern3_before_merge");
|
2025-12-05 19:59:40 +09:00
|
|
|
|
let boundary = crate::mir::join_ir::lowering::inline_boundary::JoinInlineBoundary::new_with_exit_bindings(
|
2025-12-05 16:04:15 +09:00
|
|
|
|
vec![ValueId(0), ValueId(1)], // JoinIR's main() parameters (i, sum init)
|
|
|
|
|
|
vec![loop_var_id, sum_var_id], // Host's loop variables
|
2025-12-05 19:59:40 +09:00
|
|
|
|
vec![
|
|
|
|
|
|
crate::mir::join_ir::lowering::inline_boundary::LoopExitBinding {
|
|
|
|
|
|
carrier_name: "sum".to_string(),
|
|
|
|
|
|
join_exit_value: ValueId(18), // k_exit's parameter (sum_final)
|
|
|
|
|
|
host_slot: sum_var_id, // variable_map["sum"]
|
|
|
|
|
|
}
|
|
|
|
|
|
],
|
2025-12-05 16:04:15 +09:00
|
|
|
|
);
|
2025-12-05 19:27:52 +09:00
|
|
|
|
let exit_phi_result = self.merge_joinir_mir_blocks(&mir_module, Some(&boundary), debug)?;
|
2025-12-05 19:35:30 +09:00
|
|
|
|
self.trace_varmap("pattern3_after_merge");
|
2025-12-05 19:27:52 +09:00
|
|
|
|
|
2025-12-05 19:39:54 +09:00
|
|
|
|
// Phase 189-Refine: variable_map の更新は merge_joinir_mir_blocks 内で
|
|
|
|
|
|
// JoinInlineBoundary.host_outputs を用いて行われる。
|
|
|
|
|
|
// この関数では Void を返すだけでよい(戻り値は後続の `return sum` が扱う)。
|
2025-12-05 16:04:15 +09:00
|
|
|
|
let void_val = crate::mir::builder::emission::constant::emit_void(self);
|
|
|
|
|
|
|
|
|
|
|
|
if debug {
|
|
|
|
|
|
eprintln!(
|
|
|
|
|
|
"[cf_loop/joinir/pattern3] Loop complete, returning Void {:?}",
|
|
|
|
|
|
void_val
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
Ok(Some(void_val))
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-28 19:45:23 +09:00
|
|
|
|
/// Phase 49-3.2: Merge JoinIR-generated MIR blocks into current_function
|
2025-11-28 19:29:45 +09:00
|
|
|
|
///
|
2025-12-05 11:08:08 +09:00
|
|
|
|
/// # Phase 189: Multi-Function MIR Merge
|
|
|
|
|
|
///
|
2025-11-28 19:45:23 +09:00
|
|
|
|
/// This merges JoinIR-generated blocks by:
|
2025-12-05 11:08:08 +09:00
|
|
|
|
/// 1. Remapping all block IDs across ALL functions to avoid conflicts
|
|
|
|
|
|
/// 2. Remapping all value IDs across ALL functions to avoid conflicts
|
|
|
|
|
|
/// 3. Adding all blocks from all functions to current_function
|
2025-11-28 19:45:23 +09:00
|
|
|
|
/// 4. Jumping from current_block to the entry block
|
2025-12-05 11:08:08 +09:00
|
|
|
|
/// 5. Converting Return → Jump to exit block for all functions
|
|
|
|
|
|
///
|
|
|
|
|
|
/// **Multi-Function Support** (Phase 189):
|
|
|
|
|
|
/// - Pattern 1 (Simple While) generates 3 functions: entry + loop_step + k_exit
|
|
|
|
|
|
/// - All functions are flattened into current_function with global ID remapping
|
|
|
|
|
|
/// - Single exit block receives all Return instructions from all functions
|
2025-12-05 13:46:44 +09:00
|
|
|
|
///
|
|
|
|
|
|
/// # Phase 188-Impl-3: JoinInlineBoundary Support
|
|
|
|
|
|
///
|
|
|
|
|
|
/// When `boundary` is provided, injects Copy instructions at the entry block
|
|
|
|
|
|
/// to connect host ValueIds to JoinIR local ValueIds:
|
|
|
|
|
|
///
|
|
|
|
|
|
/// ```text
|
|
|
|
|
|
/// entry_block:
|
|
|
|
|
|
/// // Injected by boundary
|
|
|
|
|
|
/// ValueId(100) = Copy ValueId(4) // join_input → host_input
|
|
|
|
|
|
/// // Original JoinIR instructions follow...
|
|
|
|
|
|
/// ```
|
|
|
|
|
|
///
|
|
|
|
|
|
/// This enables clean separation: JoinIR uses local IDs (0,1,2...),
|
|
|
|
|
|
/// host uses its own IDs, and Copy instructions bridge the gap.
|
2025-12-05 19:27:52 +09:00
|
|
|
|
///
|
|
|
|
|
|
/// # Returns
|
|
|
|
|
|
///
|
|
|
|
|
|
/// Returns `Ok(Some(exit_phi_id))` if the merged JoinIR functions have return values
|
2025-12-05 19:39:54 +09:00
|
|
|
|
/// that were collected into an exit block PHI. さらに、`boundary` に
|
|
|
|
|
|
/// host_outputs が指定されている場合は、exit PHI の結果をホスト側の
|
|
|
|
|
|
/// SSA スロットへ再接続する(variable_map 内の ValueId を更新する)。
|
2025-11-28 19:29:45 +09:00
|
|
|
|
fn merge_joinir_mir_blocks(
|
|
|
|
|
|
&mut self,
|
|
|
|
|
|
mir_module: &crate::mir::MirModule,
|
2025-12-05 13:46:44 +09:00
|
|
|
|
boundary: Option<&crate::mir::join_ir::lowering::inline_boundary::JoinInlineBoundary>,
|
2025-11-28 19:29:45 +09:00
|
|
|
|
debug: bool,
|
2025-12-05 19:27:52 +09:00
|
|
|
|
) -> Result<Option<ValueId>, String> {
|
2025-11-28 19:45:23 +09:00
|
|
|
|
use crate::mir::{BasicBlock, BasicBlockId, MirInstruction, ValueId};
|
|
|
|
|
|
use std::collections::HashMap;
|
2025-12-05 14:41:24 +09:00
|
|
|
|
// Phase 189: Use new ID remapper Box
|
|
|
|
|
|
use super::joinir_id_remapper::JoinIrIdRemapper;
|
2025-11-28 19:45:23 +09:00
|
|
|
|
|
2025-11-28 19:29:45 +09:00
|
|
|
|
if debug {
|
|
|
|
|
|
eprintln!(
|
|
|
|
|
|
"[cf_loop/joinir] merge_joinir_mir_blocks called with {} functions",
|
|
|
|
|
|
mir_module.functions.len()
|
|
|
|
|
|
);
|
2025-11-28 19:45:23 +09:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-05 14:41:24 +09:00
|
|
|
|
// Phase 189: Create ID remapper for ValueId/BlockId translation
|
|
|
|
|
|
let mut remapper = JoinIrIdRemapper::new();
|
|
|
|
|
|
|
2025-12-05 11:08:08 +09:00
|
|
|
|
// Phase 189: Map function names to their entry blocks (for Call→Jump conversion)
|
|
|
|
|
|
let mut function_entry_map: HashMap<String, BasicBlockId> = HashMap::new();
|
2025-11-28 19:45:23 +09:00
|
|
|
|
|
2025-12-05 11:08:08 +09:00
|
|
|
|
// 1. Allocate new block IDs for ALL functions (Phase 189)
|
2025-12-05 11:46:21 +09:00
|
|
|
|
// DETERMINISM FIX: Sort functions by name to ensure consistent iteration order
|
2025-11-28 19:45:23 +09:00
|
|
|
|
if debug {
|
2025-12-05 11:08:08 +09:00
|
|
|
|
eprintln!("[cf_loop/joinir] Phase 189: Allocating block IDs for all functions");
|
2025-11-28 19:45:23 +09:00
|
|
|
|
}
|
2025-12-05 11:46:21 +09:00
|
|
|
|
let mut functions: Vec<_> = mir_module.functions.iter().collect();
|
|
|
|
|
|
functions.sort_by_key(|(name, _)| name.as_str());
|
|
|
|
|
|
for (func_name, func) in functions {
|
2025-12-05 11:08:08 +09:00
|
|
|
|
if debug {
|
|
|
|
|
|
eprintln!("[cf_loop/joinir] Function: {}", func_name);
|
|
|
|
|
|
}
|
2025-12-05 11:46:21 +09:00
|
|
|
|
// DETERMINISM FIX: Sort blocks by ID to ensure consistent iteration order
|
|
|
|
|
|
let mut blocks: Vec<_> = func.blocks.iter().collect();
|
|
|
|
|
|
blocks.sort_by_key(|(id, _)| id.0);
|
|
|
|
|
|
for (old_block_id, _) in blocks {
|
2025-12-05 11:08:08 +09:00
|
|
|
|
let new_block_id = self.block_gen.next();
|
2025-12-05 14:41:24 +09:00
|
|
|
|
// Use remapper to store composite key mapping
|
|
|
|
|
|
remapper.set_block(func_name.clone(), *old_block_id, new_block_id);
|
2025-12-05 11:08:08 +09:00
|
|
|
|
if debug {
|
|
|
|
|
|
eprintln!(
|
|
|
|
|
|
"[cf_loop/joinir] Block remap: {}:{:?} → {:?}",
|
|
|
|
|
|
func_name, old_block_id, new_block_id
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
// Map function entry blocks for Call→Jump conversion
|
2025-12-05 14:41:24 +09:00
|
|
|
|
let entry_block_new = remapper.get_block(func_name, func.entry_block)
|
|
|
|
|
|
.ok_or_else(|| format!("Entry block not found for {}", func_name))?;
|
2025-12-05 11:08:08 +09:00
|
|
|
|
function_entry_map.insert(func_name.clone(), entry_block_new);
|
2025-11-28 19:45:23 +09:00
|
|
|
|
if debug {
|
2025-11-28 19:29:45 +09:00
|
|
|
|
eprintln!(
|
2025-12-05 11:08:08 +09:00
|
|
|
|
"[cf_loop/joinir] Entry map: {} → {:?}",
|
|
|
|
|
|
func_name, entry_block_new
|
2025-11-28 19:29:45 +09:00
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-05 11:08:08 +09:00
|
|
|
|
// 2. Create exit block for Return conversion (single for all functions)
|
2025-11-28 19:45:23 +09:00
|
|
|
|
let exit_block_id = self.block_gen.next();
|
|
|
|
|
|
if debug {
|
|
|
|
|
|
eprintln!("[cf_loop/joinir] Exit block: {:?}", exit_block_id);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-05 11:08:08 +09:00
|
|
|
|
// 3. Collect all ValueIds used across ALL functions (Phase 189)
|
|
|
|
|
|
// Also build a map of ValueId → function name for Call→Jump conversion
|
2025-12-05 13:46:44 +09:00
|
|
|
|
// Phase 188-Impl-3: Also collect function parameters for tail call conversion
|
2025-12-05 11:08:08 +09:00
|
|
|
|
if debug {
|
|
|
|
|
|
eprintln!("[cf_loop/joinir] Phase 189: Collecting value IDs from all functions");
|
|
|
|
|
|
}
|
2025-11-29 12:44:40 +09:00
|
|
|
|
let mut used_values: std::collections::BTreeSet<ValueId> =
|
|
|
|
|
|
std::collections::BTreeSet::new();
|
2025-12-05 11:08:08 +09:00
|
|
|
|
let mut value_to_func_name: HashMap<ValueId, String> = HashMap::new();
|
2025-12-05 13:46:44 +09:00
|
|
|
|
let mut function_params: HashMap<String, Vec<ValueId>> = HashMap::new();
|
|
|
|
|
|
|
|
|
|
|
|
for (func_name, func) in &mir_module.functions {
|
|
|
|
|
|
// Phase 188-Impl-3: Collect function parameters for tail call conversion
|
|
|
|
|
|
function_params.insert(func_name.clone(), func.params.clone());
|
2025-12-05 11:08:08 +09:00
|
|
|
|
|
|
|
|
|
|
for block in func.blocks.values() {
|
2025-12-05 14:41:24 +09:00
|
|
|
|
// Phase 189: Use remapper to collect values
|
|
|
|
|
|
let block_values = remapper.collect_values_in_block(block);
|
|
|
|
|
|
used_values.extend(block_values);
|
|
|
|
|
|
|
2025-12-05 11:08:08 +09:00
|
|
|
|
// Phase 189: Track Const String instructions that define function names
|
|
|
|
|
|
for inst in &block.instructions {
|
|
|
|
|
|
if let MirInstruction::Const { dst, value } = inst {
|
|
|
|
|
|
if let crate::mir::types::ConstValue::String(s) = value {
|
|
|
|
|
|
if function_entry_map.contains_key(s) {
|
|
|
|
|
|
value_to_func_name.insert(*dst, s.clone());
|
2025-12-05 19:27:52 +09:00
|
|
|
|
// Phase 189 FIX: Also add to used_values so it gets remapped!
|
|
|
|
|
|
// Without this, subsequent instructions referencing dst will fail
|
|
|
|
|
|
used_values.insert(*dst);
|
2025-12-05 11:08:08 +09:00
|
|
|
|
if debug {
|
|
|
|
|
|
eprintln!(
|
|
|
|
|
|
"[cf_loop/joinir] Found function name constant: {:?} = '{}'",
|
|
|
|
|
|
dst, s
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
// Also collect parameter ValueIds
|
|
|
|
|
|
for param in &func.params {
|
|
|
|
|
|
used_values.insert(*param);
|
|
|
|
|
|
}
|
2025-11-28 19:45:23 +09:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-05 11:08:08 +09:00
|
|
|
|
// 4. Allocate new ValueIds for all collected values
|
2025-11-28 19:45:23 +09:00
|
|
|
|
for old_value in used_values {
|
|
|
|
|
|
let new_value = self.next_value_id();
|
2025-12-05 14:41:24 +09:00
|
|
|
|
remapper.set_value(old_value, new_value);
|
2025-11-28 19:45:23 +09:00
|
|
|
|
if debug {
|
|
|
|
|
|
eprintln!(
|
|
|
|
|
|
"[cf_loop/joinir] Value remap: {:?} → {:?}",
|
|
|
|
|
|
old_value, new_value
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-05 11:08:08 +09:00
|
|
|
|
// 5. Merge ALL functions (Phase 189: iterate over all, not just first)
|
2025-12-05 11:46:21 +09:00
|
|
|
|
// DETERMINISM FIX: Sort functions by name to ensure consistent iteration order
|
2025-12-05 11:08:08 +09:00
|
|
|
|
if debug {
|
|
|
|
|
|
eprintln!("[cf_loop/joinir] Phase 189: Merging {} functions", mir_module.functions.len());
|
|
|
|
|
|
}
|
|
|
|
|
|
// Phase 189: Iterate over both names and functions (need name for composite keys)
|
2025-12-05 11:46:21 +09:00
|
|
|
|
let mut functions_merge: Vec<_> = mir_module.functions.iter().collect();
|
|
|
|
|
|
functions_merge.sort_by_key(|(name, _)| name.as_str());
|
2025-12-05 19:27:52 +09:00
|
|
|
|
|
|
|
|
|
|
// Phase 189 FIX: Build set of boundary join_inputs to skip their Const initializers
|
|
|
|
|
|
// When BoundaryInjector provides values via Copy, entry function shouldn't also set them via Const
|
|
|
|
|
|
let boundary_input_set: std::collections::HashSet<ValueId> = boundary
|
|
|
|
|
|
.map(|b| b.join_inputs.iter().copied().collect())
|
|
|
|
|
|
.unwrap_or_default();
|
|
|
|
|
|
let entry_func_name = functions_merge.first().map(|(name, _)| name.as_str());
|
|
|
|
|
|
|
|
|
|
|
|
// Phase 189-Fix: Collect return values from JoinIR functions for exit PHI
|
|
|
|
|
|
// Each Return → Jump conversion records (from_block, return_value) here
|
|
|
|
|
|
let mut exit_phi_inputs: Vec<(BasicBlockId, ValueId)> = Vec::new();
|
|
|
|
|
|
|
2025-12-05 11:46:21 +09:00
|
|
|
|
for (func_name, func) in functions_merge {
|
2025-12-05 11:08:08 +09:00
|
|
|
|
if debug {
|
|
|
|
|
|
eprintln!(
|
|
|
|
|
|
"[cf_loop/joinir] Merging function '{}' with {} blocks, entry={:?}",
|
|
|
|
|
|
func_name,
|
|
|
|
|
|
func.blocks.len(),
|
|
|
|
|
|
func.entry_block
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Build a local block map for this function (for remap_instruction compatibility)
|
|
|
|
|
|
let mut local_block_map: HashMap<BasicBlockId, BasicBlockId> = HashMap::new();
|
|
|
|
|
|
for old_block_id in func.blocks.keys() {
|
2025-12-05 14:41:24 +09:00
|
|
|
|
let new_block_id = remapper.get_block(func_name, *old_block_id)
|
|
|
|
|
|
.ok_or_else(|| format!("Block {:?} not found for {}", old_block_id, func_name))?;
|
2025-12-05 11:08:08 +09:00
|
|
|
|
local_block_map.insert(*old_block_id, new_block_id);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Clone and remap all blocks from this function
|
2025-12-05 11:46:21 +09:00
|
|
|
|
// DETERMINISM FIX: Sort blocks by ID to ensure consistent iteration order
|
|
|
|
|
|
let mut blocks_merge: Vec<_> = func.blocks.iter().collect();
|
|
|
|
|
|
blocks_merge.sort_by_key(|(id, _)| id.0);
|
|
|
|
|
|
for (old_block_id, old_block) in blocks_merge {
|
2025-12-05 14:41:24 +09:00
|
|
|
|
// Use remapper to get correct mapping for this function's block
|
|
|
|
|
|
let new_block_id = remapper.get_block(func_name, *old_block_id)
|
|
|
|
|
|
.ok_or_else(|| format!("Block {:?} not found for {}", old_block_id, func_name))?;
|
2025-12-05 11:08:08 +09:00
|
|
|
|
let mut new_block = BasicBlock::new(new_block_id);
|
|
|
|
|
|
|
|
|
|
|
|
// Remap instructions (Phase 189: Convert inter-function Calls to control flow)
|
|
|
|
|
|
let mut found_tail_call = false;
|
2025-12-05 11:40:05 +09:00
|
|
|
|
let mut tail_call_target: Option<(BasicBlockId, Vec<ValueId>)> = None;
|
|
|
|
|
|
|
2025-12-05 19:27:52 +09:00
|
|
|
|
// DEBUG: Print block being processed
|
|
|
|
|
|
if debug {
|
|
|
|
|
|
eprintln!("[cf_loop/joinir] === Processing block {:?} (from func '{}') ===", old_block_id, func_name);
|
|
|
|
|
|
eprintln!("[cf_loop/joinir] Original block has {} instructions:", old_block.instructions.len());
|
|
|
|
|
|
for (idx, inst) in old_block.instructions.iter().enumerate() {
|
|
|
|
|
|
eprintln!("[cf_loop/joinir] [{}] {:?}", idx, inst);
|
|
|
|
|
|
}
|
|
|
|
|
|
eprintln!("[cf_loop/joinir] Original block terminator: {:?}", old_block.terminator);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Phase 189 FIX: Check if this is entry function's entry block (for boundary input skipping)
|
|
|
|
|
|
let is_entry_func_entry_block = entry_func_name == Some(func_name.as_str())
|
|
|
|
|
|
&& *old_block_id == func.entry_block;
|
|
|
|
|
|
|
2025-12-05 11:40:05 +09:00
|
|
|
|
// First pass: Process all instructions, identify tail calls
|
2025-12-05 11:08:08 +09:00
|
|
|
|
for inst in &old_block.instructions {
|
|
|
|
|
|
// Phase 189: Skip Const String instructions that define function names
|
|
|
|
|
|
if let MirInstruction::Const { dst, value } = inst {
|
|
|
|
|
|
if let crate::mir::types::ConstValue::String(_) = value {
|
|
|
|
|
|
if value_to_func_name.contains_key(dst) {
|
|
|
|
|
|
if debug {
|
|
|
|
|
|
eprintln!("[cf_loop/joinir] Skipping function name const: {:?}", inst);
|
|
|
|
|
|
}
|
|
|
|
|
|
continue; // Skip this instruction
|
2025-11-28 19:45:23 +09:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-12-05 19:27:52 +09:00
|
|
|
|
// Phase 189 FIX: Skip Const instructions in entry function's entry block
|
|
|
|
|
|
// that initialize boundary inputs. BoundaryInjector provides these values via Copy.
|
|
|
|
|
|
if is_entry_func_entry_block && boundary_input_set.contains(dst) {
|
|
|
|
|
|
if debug {
|
|
|
|
|
|
eprintln!("[cf_loop/joinir] Skipping boundary input const (replaced by BoundaryInjector Copy): {:?}", inst);
|
|
|
|
|
|
}
|
|
|
|
|
|
continue; // Skip - BoundaryInjector will provide the value
|
|
|
|
|
|
}
|
2025-12-05 11:08:08 +09:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-05 11:40:05 +09:00
|
|
|
|
// Phase 189: Detect tail calls and save parameters
|
|
|
|
|
|
if let MirInstruction::Call { func, args, .. } = inst {
|
2025-12-05 11:08:08 +09:00
|
|
|
|
if let Some(func_name) = value_to_func_name.get(func) {
|
|
|
|
|
|
if let Some(&target_block) = function_entry_map.get(func_name) {
|
2025-12-05 11:40:05 +09:00
|
|
|
|
// This is a tail call - save info and skip the Call instruction itself
|
|
|
|
|
|
let remapped_args: Vec<ValueId> = args
|
|
|
|
|
|
.iter()
|
2025-12-05 14:41:24 +09:00
|
|
|
|
.map(|&v| remapper.get_value(v).unwrap_or(v))
|
2025-12-05 11:40:05 +09:00
|
|
|
|
.collect();
|
|
|
|
|
|
tail_call_target = Some((target_block, remapped_args));
|
|
|
|
|
|
found_tail_call = true;
|
|
|
|
|
|
|
2025-12-05 11:08:08 +09:00
|
|
|
|
if debug {
|
|
|
|
|
|
eprintln!(
|
2025-12-05 11:40:05 +09:00
|
|
|
|
"[cf_loop/joinir] Detected tail call to '{}' (args={:?}), will convert to Jump",
|
|
|
|
|
|
func_name, args
|
2025-12-05 11:08:08 +09:00
|
|
|
|
);
|
|
|
|
|
|
}
|
2025-12-05 11:40:05 +09:00
|
|
|
|
continue; // Skip the Call instruction itself
|
2025-12-05 11:08:08 +09:00
|
|
|
|
}
|
2025-11-29 12:44:40 +09:00
|
|
|
|
}
|
2025-11-28 19:45:23 +09:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-05 14:41:24 +09:00
|
|
|
|
// Process regular instructions - Phase 189: Use remapper.remap_instruction() + manual block remapping
|
|
|
|
|
|
let remapped = remapper.remap_instruction(inst);
|
|
|
|
|
|
|
|
|
|
|
|
// Phase 189 FIX: Manual block remapping for Branch/Phi (JoinIrIdRemapper doesn't know func_name)
|
|
|
|
|
|
let remapped_with_blocks = match remapped {
|
|
|
|
|
|
MirInstruction::Branch { condition, then_bb, else_bb } => {
|
|
|
|
|
|
MirInstruction::Branch {
|
|
|
|
|
|
condition,
|
|
|
|
|
|
then_bb: local_block_map.get(&then_bb).copied().unwrap_or(then_bb),
|
|
|
|
|
|
else_bb: local_block_map.get(&else_bb).copied().unwrap_or(else_bb),
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
MirInstruction::Phi { dst, inputs, type_hint: None } => {
|
|
|
|
|
|
MirInstruction::Phi {
|
|
|
|
|
|
dst,
|
|
|
|
|
|
inputs: inputs.iter().map(|(bb, val)| {
|
|
|
|
|
|
(local_block_map.get(bb).copied().unwrap_or(*bb), *val)
|
|
|
|
|
|
}).collect(),
|
|
|
|
|
|
type_hint: None,
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
other => other,
|
|
|
|
|
|
};
|
2025-12-05 11:40:05 +09:00
|
|
|
|
|
|
|
|
|
|
if debug {
|
|
|
|
|
|
match inst {
|
|
|
|
|
|
MirInstruction::BoxCall { .. } => {
|
|
|
|
|
|
eprintln!("[cf_loop/joinir] Adding BoxCall to block {:?}: {:?}", new_block_id, inst);
|
|
|
|
|
|
}
|
|
|
|
|
|
_ => {}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-05 14:41:24 +09:00
|
|
|
|
new_block.instructions.push(remapped_with_blocks);
|
2025-12-05 11:08:08 +09:00
|
|
|
|
}
|
2025-12-05 11:40:05 +09:00
|
|
|
|
|
2025-12-05 19:27:52 +09:00
|
|
|
|
// DEBUG: Print what was added to the block after first pass
|
|
|
|
|
|
if debug {
|
|
|
|
|
|
eprintln!("[cf_loop/joinir] After first pass, new_block has {} instructions", new_block.instructions.len());
|
|
|
|
|
|
for (idx, inst) in new_block.instructions.iter().enumerate() {
|
|
|
|
|
|
eprintln!("[cf_loop/joinir] [{}] {:?}", idx, inst);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-05 11:40:05 +09:00
|
|
|
|
// Second pass: Insert parameter bindings for tail calls
|
2025-12-05 13:46:44 +09:00
|
|
|
|
// Phase 188-Impl-3: Use actual parameter ValueIds from target function
|
2025-12-05 11:40:05 +09:00
|
|
|
|
if let Some((target_block, args)) = tail_call_target {
|
|
|
|
|
|
if debug {
|
|
|
|
|
|
eprintln!(
|
|
|
|
|
|
"[cf_loop/joinir] Inserting param bindings for tail call to {:?}",
|
|
|
|
|
|
target_block
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-05 13:46:44 +09:00
|
|
|
|
// Find the target function name from the target_block
|
|
|
|
|
|
// We need to reverse-lookup the function name from the entry block
|
|
|
|
|
|
let mut target_func_name: Option<String> = None;
|
|
|
|
|
|
for (fname, &entry_block) in &function_entry_map {
|
|
|
|
|
|
if entry_block == target_block {
|
|
|
|
|
|
target_func_name = Some(fname.clone());
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-12-05 11:40:05 +09:00
|
|
|
|
|
2025-12-05 13:46:44 +09:00
|
|
|
|
if let Some(target_func_name) = target_func_name {
|
|
|
|
|
|
if let Some(target_params) = function_params.get(&target_func_name) {
|
|
|
|
|
|
// Insert Copy instructions for parameter binding
|
|
|
|
|
|
for (i, arg_val_remapped) in args.iter().enumerate() {
|
|
|
|
|
|
if i < target_params.len() {
|
|
|
|
|
|
let param_val_original = target_params[i];
|
2025-12-05 14:41:24 +09:00
|
|
|
|
if let Some(param_val_remapped) = remapper.get_value(param_val_original) {
|
2025-12-05 13:46:44 +09:00
|
|
|
|
new_block.instructions.push(MirInstruction::Copy {
|
|
|
|
|
|
dst: param_val_remapped,
|
|
|
|
|
|
src: *arg_val_remapped,
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
if debug {
|
|
|
|
|
|
eprintln!(
|
|
|
|
|
|
"[cf_loop/joinir] Param binding: arg {:?} → param {:?}",
|
|
|
|
|
|
arg_val_remapped, param_val_remapped
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-12-05 11:40:05 +09:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Set terminator to Jump
|
|
|
|
|
|
new_block.terminator = Some(MirInstruction::Jump {
|
|
|
|
|
|
target: target_block,
|
|
|
|
|
|
});
|
2025-12-05 19:27:52 +09:00
|
|
|
|
|
|
|
|
|
|
// DEBUG: Print final state after adding parameter bindings
|
|
|
|
|
|
if debug {
|
|
|
|
|
|
eprintln!("[cf_loop/joinir] After adding param bindings, new_block has {} instructions", new_block.instructions.len());
|
|
|
|
|
|
for (idx, inst) in new_block.instructions.iter().enumerate() {
|
|
|
|
|
|
eprintln!("[cf_loop/joinir] [{}] {:?}", idx, inst);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-12-05 11:40:05 +09:00
|
|
|
|
}
|
2025-12-05 11:08:08 +09:00
|
|
|
|
new_block.instruction_spans = old_block.instruction_spans.clone();
|
|
|
|
|
|
|
2025-12-05 19:27:52 +09:00
|
|
|
|
if debug {
|
|
|
|
|
|
eprintln!("[cf_loop/joinir] Span sync: new_block.instructions.len()={}, old_block.instruction_spans.len()={}, new_block.instruction_spans.len()={}",
|
|
|
|
|
|
new_block.instructions.len(),
|
|
|
|
|
|
old_block.instruction_spans.len(),
|
|
|
|
|
|
new_block.instruction_spans.len()
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-05 11:08:08 +09:00
|
|
|
|
// Remap terminator (convert Return → Jump to exit) if not already set by tail call
|
|
|
|
|
|
if !found_tail_call {
|
|
|
|
|
|
if let Some(ref term) = old_block.terminator {
|
|
|
|
|
|
let remapped_term = match term {
|
|
|
|
|
|
MirInstruction::Return { value } => {
|
|
|
|
|
|
// Convert Return to Jump to exit block
|
|
|
|
|
|
// All functions return to same exit block (Phase 189)
|
2025-12-05 19:27:52 +09:00
|
|
|
|
// Phase 189-Fix: Add Copy instruction to pass return value to exit PHI
|
2025-12-05 11:08:08 +09:00
|
|
|
|
if let Some(ret_val) = value {
|
2025-12-05 14:41:24 +09:00
|
|
|
|
let remapped_val = remapper.get_value(*ret_val).unwrap_or(*ret_val);
|
2025-12-05 11:08:08 +09:00
|
|
|
|
if debug {
|
|
|
|
|
|
eprintln!(
|
|
|
|
|
|
"[cf_loop/joinir] Return({:?}) → Jump to exit",
|
|
|
|
|
|
remapped_val
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
2025-12-05 19:27:52 +09:00
|
|
|
|
// Collect (from_block, return_value) for exit PHI generation
|
|
|
|
|
|
exit_phi_inputs.push((new_block_id, remapped_val));
|
2025-12-05 11:08:08 +09:00
|
|
|
|
}
|
|
|
|
|
|
MirInstruction::Jump {
|
|
|
|
|
|
target: exit_block_id,
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-12-05 14:41:24 +09:00
|
|
|
|
MirInstruction::Jump { target } => {
|
|
|
|
|
|
// Phase 189 FIX: Remap block ID for Jump
|
|
|
|
|
|
MirInstruction::Jump {
|
|
|
|
|
|
target: local_block_map.get(target).copied().unwrap_or(*target),
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
MirInstruction::Branch { condition, then_bb, else_bb } => {
|
2025-12-05 19:27:52 +09:00
|
|
|
|
// Phase 189 FIX: Remap block IDs AND condition ValueId for Branch
|
|
|
|
|
|
MirInstruction::Branch {
|
|
|
|
|
|
condition: remapper.remap_value(*condition),
|
|
|
|
|
|
then_bb: local_block_map.get(then_bb).copied().unwrap_or(*then_bb),
|
|
|
|
|
|
else_bb: local_block_map.get(else_bb).copied().unwrap_or(*else_bb),
|
2025-12-05 14:41:24 +09:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
_ => remapper.remap_instruction(term),
|
2025-12-05 11:08:08 +09:00
|
|
|
|
};
|
|
|
|
|
|
new_block.terminator = Some(remapped_term);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-05 19:27:52 +09:00
|
|
|
|
// Phase 189 FIX: Ensure instruction_spans matches instructions length
|
|
|
|
|
|
// The original spans may not cover all instructions after remapping/adding
|
|
|
|
|
|
// (PHI instructions, tail call parameter bindings, etc.)
|
|
|
|
|
|
let inst_count = new_block.instructions.len();
|
|
|
|
|
|
let span_count = new_block.instruction_spans.len();
|
|
|
|
|
|
if inst_count > span_count {
|
|
|
|
|
|
// Use a default span for the extra instructions
|
|
|
|
|
|
let default_span = new_block.instruction_spans.last()
|
|
|
|
|
|
.copied()
|
|
|
|
|
|
.unwrap_or_else(crate::ast::Span::unknown);
|
|
|
|
|
|
new_block.instruction_spans.resize(inst_count, default_span);
|
|
|
|
|
|
} else if inst_count < span_count {
|
|
|
|
|
|
// Truncate spans to match instructions
|
|
|
|
|
|
new_block.instruction_spans.truncate(inst_count);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-05 11:08:08 +09:00
|
|
|
|
// Add block to current function
|
|
|
|
|
|
if let Some(ref mut current_func) = self.current_function {
|
|
|
|
|
|
current_func.add_block(new_block);
|
|
|
|
|
|
}
|
2025-11-28 19:45:23 +09:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-05 14:41:24 +09:00
|
|
|
|
// Phase 188-Impl-3: Inject Copy instructions for boundary inputs using BoundaryInjector
|
2025-12-05 13:46:44 +09:00
|
|
|
|
if let Some(boundary) = boundary {
|
2025-12-05 14:41:24 +09:00
|
|
|
|
use super::joinir_inline_boundary_injector::BoundaryInjector;
|
|
|
|
|
|
|
2025-12-05 13:46:44 +09:00
|
|
|
|
// Get entry function's entry block (first function by convention)
|
|
|
|
|
|
let (entry_func_name, entry_func) = mir_module
|
|
|
|
|
|
.functions
|
|
|
|
|
|
.iter()
|
|
|
|
|
|
.next()
|
|
|
|
|
|
.ok_or("JoinIR module has no functions")?;
|
2025-12-05 14:41:24 +09:00
|
|
|
|
let entry_block_remapped = remapper.get_block(entry_func_name, entry_func.entry_block)
|
|
|
|
|
|
.ok_or_else(|| format!("Entry block not found for {}", entry_func_name))?;
|
|
|
|
|
|
|
|
|
|
|
|
// Create HashMap from remapper for BoundaryInjector (temporary adapter)
|
|
|
|
|
|
let mut value_map_for_injector = HashMap::new();
|
|
|
|
|
|
for join_in in &boundary.join_inputs {
|
|
|
|
|
|
if let Some(remapped) = remapper.get_value(*join_in) {
|
|
|
|
|
|
value_map_for_injector.insert(*join_in, remapped);
|
|
|
|
|
|
}
|
2025-12-05 13:46:44 +09:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-05 14:41:24 +09:00
|
|
|
|
// Use BoundaryInjector to inject Copy instructions
|
2025-12-05 13:46:44 +09:00
|
|
|
|
if let Some(ref mut current_func) = self.current_function {
|
2025-12-05 14:41:24 +09:00
|
|
|
|
BoundaryInjector::inject_boundary_copies(
|
|
|
|
|
|
current_func,
|
|
|
|
|
|
entry_block_remapped,
|
|
|
|
|
|
boundary,
|
|
|
|
|
|
&value_map_for_injector,
|
|
|
|
|
|
debug,
|
|
|
|
|
|
)?;
|
2025-12-05 13:46:44 +09:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-05 19:27:52 +09:00
|
|
|
|
// 6. Create exit block with PHI for return values from JoinIR functions
|
|
|
|
|
|
// Phase 189-Fix: Generate exit PHI if there are multiple return values
|
|
|
|
|
|
let exit_phi_result_id = if let Some(ref mut func) = self.current_function {
|
|
|
|
|
|
let mut exit_block = BasicBlock::new(exit_block_id);
|
|
|
|
|
|
|
|
|
|
|
|
// Phase 189-Fix: If we collected return values, create a PHI in exit block
|
|
|
|
|
|
// This merges all return values from JoinIR functions into a single value
|
|
|
|
|
|
let phi_result = if !exit_phi_inputs.is_empty() {
|
|
|
|
|
|
// Allocate a new ValueId for the PHI result
|
|
|
|
|
|
let phi_dst = self.value_gen.next();
|
|
|
|
|
|
exit_block.instructions.push(MirInstruction::Phi {
|
|
|
|
|
|
dst: phi_dst,
|
|
|
|
|
|
inputs: exit_phi_inputs.clone(),
|
|
|
|
|
|
type_hint: None,
|
|
|
|
|
|
});
|
|
|
|
|
|
exit_block.instruction_spans.push(crate::ast::Span::unknown());
|
|
|
|
|
|
if debug {
|
|
|
|
|
|
eprintln!(
|
|
|
|
|
|
"[cf_loop/joinir] Exit block PHI: {:?} = phi {:?}",
|
|
|
|
|
|
phi_dst, exit_phi_inputs
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
Some(phi_dst)
|
|
|
|
|
|
} else {
|
|
|
|
|
|
None
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2025-11-28 19:45:23 +09:00
|
|
|
|
func.add_block(exit_block);
|
2025-12-05 19:27:52 +09:00
|
|
|
|
if debug {
|
|
|
|
|
|
eprintln!("[cf_loop/joinir] Created exit block: {:?}", exit_block_id);
|
|
|
|
|
|
}
|
|
|
|
|
|
phi_result
|
|
|
|
|
|
} else {
|
|
|
|
|
|
None
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2025-12-05 19:59:40 +09:00
|
|
|
|
// Phase 190: Use explicit LoopExitBinding to reconnect exit PHI to variable_map
|
|
|
|
|
|
// Each binding explicitly names the carrier variable and maps exit PHI to it.
|
2025-12-05 19:27:52 +09:00
|
|
|
|
if let Some(phi_result) = exit_phi_result_id {
|
|
|
|
|
|
if let Some(ref boundary) = boundary {
|
2025-12-05 19:59:40 +09:00
|
|
|
|
// Phase 190: Use exit_bindings for explicit carrier naming
|
|
|
|
|
|
// This eliminates ambiguity about which variable is being updated
|
|
|
|
|
|
for binding in &boundary.exit_bindings {
|
|
|
|
|
|
// Find the variable in variable_map that matches the binding's host_slot
|
|
|
|
|
|
for (var_name, vid) in self.variable_map.iter_mut() {
|
|
|
|
|
|
if *vid == binding.host_slot {
|
2025-12-05 19:39:54 +09:00
|
|
|
|
*vid = phi_result;
|
|
|
|
|
|
if debug {
|
|
|
|
|
|
eprintln!(
|
2025-12-05 19:59:40 +09:00
|
|
|
|
"[cf_loop/joinir] Phase 190: Reconnected exit PHI {:?} to variable_map['{}'] (carrier: {})",
|
|
|
|
|
|
phi_result, var_name, binding.carrier_name
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
// Validate carrier name matches
|
|
|
|
|
|
if var_name != &binding.carrier_name && debug {
|
|
|
|
|
|
eprintln!(
|
|
|
|
|
|
"[cf_loop/joinir] WARNING: Carrier name mismatch: expected '{}', found '{}'",
|
|
|
|
|
|
binding.carrier_name, var_name
|
2025-12-05 19:39:54 +09:00
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-12-05 19:27:52 +09:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-12-05 19:59:40 +09:00
|
|
|
|
|
|
|
|
|
|
// Phase 190: Backward compatibility - also check deprecated host_outputs
|
|
|
|
|
|
#[allow(deprecated)]
|
|
|
|
|
|
if !boundary.host_outputs.is_empty() && debug {
|
|
|
|
|
|
eprintln!(
|
|
|
|
|
|
"[cf_loop/joinir] WARNING: Using deprecated host_outputs. Migrate to exit_bindings."
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
2025-12-05 19:27:52 +09:00
|
|
|
|
}
|
2025-11-28 19:45:23 +09:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-05 11:08:08 +09:00
|
|
|
|
// 7. Jump from current block to entry function's entry block
|
|
|
|
|
|
// Entry function is first function by convention
|
|
|
|
|
|
let (entry_func_name, entry_func) = mir_module
|
|
|
|
|
|
.functions
|
|
|
|
|
|
.iter()
|
|
|
|
|
|
.next()
|
|
|
|
|
|
.ok_or("JoinIR module has no functions")?;
|
2025-12-05 14:41:24 +09:00
|
|
|
|
// Use remapper to get entry block mapping
|
|
|
|
|
|
let entry_block = remapper.get_block(entry_func_name, entry_func.entry_block)
|
|
|
|
|
|
.ok_or_else(|| format!("Entry block not found for {}", entry_func_name))?;
|
2025-12-05 11:08:08 +09:00
|
|
|
|
if debug {
|
2025-12-05 19:27:52 +09:00
|
|
|
|
eprintln!("[cf_loop/joinir] Entry function name: {}", entry_func_name);
|
|
|
|
|
|
eprintln!("[cf_loop/joinir] Entry function's entry_block (JoinIR local): {:?}", entry_func.entry_block);
|
|
|
|
|
|
eprintln!("[cf_loop/joinir] Entry block (remapped): {:?}", entry_block);
|
|
|
|
|
|
eprintln!("[cf_loop/joinir] Current block before emit_jump: {:?}", self.current_block);
|
2025-12-05 11:08:08 +09:00
|
|
|
|
eprintln!("[cf_loop/joinir] Jumping to entry block: {:?}", entry_block);
|
|
|
|
|
|
}
|
2025-11-28 19:45:23 +09:00
|
|
|
|
crate::mir::builder::emission::branch::emit_jump(self, entry_block)?;
|
2025-12-05 19:27:52 +09:00
|
|
|
|
if debug {
|
|
|
|
|
|
eprintln!("[cf_loop/joinir] After emit_jump, current_block: {:?}", self.current_block);
|
|
|
|
|
|
}
|
2025-11-28 19:45:23 +09:00
|
|
|
|
|
|
|
|
|
|
// 8. Switch to exit block for subsequent code
|
|
|
|
|
|
self.start_new_block(exit_block_id)?;
|
|
|
|
|
|
|
|
|
|
|
|
if debug {
|
|
|
|
|
|
eprintln!(
|
2025-12-05 11:08:08 +09:00
|
|
|
|
"[cf_loop/joinir] Phase 189: Merge complete: {} functions merged, continuing from {:?}",
|
|
|
|
|
|
mir_module.functions.len(),
|
2025-11-28 19:45:23 +09:00
|
|
|
|
exit_block_id
|
|
|
|
|
|
);
|
2025-12-05 19:27:52 +09:00
|
|
|
|
// DEBUG: Check bb0's terminator after merge
|
|
|
|
|
|
if let Some(ref func) = self.current_function {
|
|
|
|
|
|
if let Some(bb0) = func.get_block(BasicBlockId(0)) {
|
|
|
|
|
|
eprintln!("[cf_loop/joinir] bb0 terminator after merge: {:?}", bb0.terminator);
|
|
|
|
|
|
}
|
|
|
|
|
|
// DEBUG: Check bb9's PHI after merge (PHI merge block)
|
|
|
|
|
|
if let Some(bb9) = func.get_block(BasicBlockId(9)) {
|
|
|
|
|
|
let phi_count = bb9.instructions.iter().filter(|i| matches!(i, MirInstruction::Phi { .. })).count();
|
|
|
|
|
|
eprintln!("[cf_loop/joinir] bb9 after merge: {} instructions, {} PHI", bb9.instructions.len(), phi_count);
|
|
|
|
|
|
for (idx, inst) in bb9.instructions.iter().take(3).enumerate() {
|
|
|
|
|
|
eprintln!("[cf_loop/joinir] bb9[{}]: {:?}", idx, inst);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-11-28 19:45:23 +09:00
|
|
|
|
}
|
2025-11-28 19:29:45 +09:00
|
|
|
|
|
2025-12-05 19:27:52 +09:00
|
|
|
|
Ok(exit_phi_result_id)
|
2025-11-28 19:12:59 +09:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-05 14:41:24 +09:00
|
|
|
|
// Phase 189: collect_values_in_block/collect_values_in_instruction removed
|
|
|
|
|
|
// These functions are now provided by JoinIrIdRemapper::collect_values_in_block()
|
2025-11-28 19:45:23 +09:00
|
|
|
|
|
2025-12-05 14:41:24 +09:00
|
|
|
|
// Phase 189: remap_joinir_instruction/remap_instruction removed
|
|
|
|
|
|
// These functions are now provided by JoinIrIdRemapper::remap_instruction()
|
2025-11-28 19:45:23 +09:00
|
|
|
|
|
2025-09-19 08:34:29 +09:00
|
|
|
|
/// Control-flow: try/catch/finally
|
|
|
|
|
|
pub(super) fn cf_try_catch(
|
|
|
|
|
|
&mut self,
|
|
|
|
|
|
try_body: Vec<ASTNode>,
|
|
|
|
|
|
catch_clauses: Vec<crate::ast::CatchClause>,
|
|
|
|
|
|
finally_body: Option<Vec<ASTNode>>,
|
|
|
|
|
|
) -> Result<ValueId, String> {
|
|
|
|
|
|
if std::env::var("NYASH_BUILDER_DISABLE_TRYCATCH")
|
|
|
|
|
|
.ok()
|
|
|
|
|
|
.as_deref()
|
|
|
|
|
|
== Some("1")
|
|
|
|
|
|
{
|
|
|
|
|
|
let try_ast = ASTNode::Program {
|
|
|
|
|
|
statements: try_body,
|
|
|
|
|
|
span: crate::ast::Span::unknown(),
|
|
|
|
|
|
};
|
|
|
|
|
|
let result = self.build_expression(try_ast)?;
|
|
|
|
|
|
return Ok(result);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
let try_block = self.block_gen.next();
|
|
|
|
|
|
let catch_block = self.block_gen.next();
|
|
|
|
|
|
let finally_block = if finally_body.is_some() {
|
|
|
|
|
|
Some(self.block_gen.next())
|
|
|
|
|
|
} else {
|
|
|
|
|
|
None
|
|
|
|
|
|
};
|
|
|
|
|
|
let exit_block = self.block_gen.next();
|
|
|
|
|
|
|
|
|
|
|
|
// Snapshot deferred-return state
|
|
|
|
|
|
let saved_defer_active = self.return_defer_active;
|
|
|
|
|
|
let saved_defer_slot = self.return_defer_slot;
|
|
|
|
|
|
let saved_defer_target = self.return_defer_target;
|
|
|
|
|
|
let saved_deferred_flag = self.return_deferred_emitted;
|
|
|
|
|
|
let saved_in_cleanup = self.in_cleanup_block;
|
|
|
|
|
|
let saved_allow_ret = self.cleanup_allow_return;
|
|
|
|
|
|
let saved_allow_throw = self.cleanup_allow_throw;
|
|
|
|
|
|
|
2025-11-17 00:48:18 +09:00
|
|
|
|
let ret_slot = self.next_value_id();
|
2025-09-19 08:34:29 +09:00
|
|
|
|
self.return_defer_active = true;
|
|
|
|
|
|
self.return_defer_slot = Some(ret_slot);
|
|
|
|
|
|
self.return_deferred_emitted = false;
|
|
|
|
|
|
self.return_defer_target = Some(finally_block.unwrap_or(exit_block));
|
|
|
|
|
|
|
|
|
|
|
|
if let Some(catch_clause) = catch_clauses.first() {
|
|
|
|
|
|
if std::env::var("NYASH_DEBUG_TRYCATCH").ok().as_deref() == Some("1") {
|
|
|
|
|
|
eprintln!(
|
|
|
|
|
|
"[BUILDER] Emitting catch handler for {:?}",
|
|
|
|
|
|
catch_clause.exception_type
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
2025-11-17 00:48:18 +09:00
|
|
|
|
let exception_value = self.next_value_id();
|
2025-09-19 08:34:29 +09:00
|
|
|
|
self.emit_instruction(MirInstruction::Catch {
|
|
|
|
|
|
exception_type: catch_clause.exception_type.clone(),
|
|
|
|
|
|
exception_value,
|
|
|
|
|
|
handler_bb: catch_block,
|
|
|
|
|
|
})?;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Enter try block
|
2025-09-28 20:38:09 +09:00
|
|
|
|
crate::mir::builder::emission::branch::emit_jump(self, try_block)?;
|
2025-09-19 08:34:29 +09:00
|
|
|
|
self.start_new_block(try_block)?;
|
|
|
|
|
|
let try_ast = ASTNode::Program {
|
|
|
|
|
|
statements: try_body,
|
|
|
|
|
|
span: crate::ast::Span::unknown(),
|
|
|
|
|
|
};
|
|
|
|
|
|
let _try_result = self.build_expression(try_ast)?;
|
|
|
|
|
|
if !self.is_current_block_terminated() {
|
|
|
|
|
|
let next_target = finally_block.unwrap_or(exit_block);
|
2025-09-28 20:38:09 +09:00
|
|
|
|
crate::mir::builder::emission::branch::emit_jump(self, next_target)?;
|
2025-09-19 08:34:29 +09:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Catch block
|
|
|
|
|
|
self.start_new_block(catch_block)?;
|
|
|
|
|
|
if std::env::var("NYASH_DEBUG_TRYCATCH").ok().as_deref() == Some("1") {
|
|
|
|
|
|
eprintln!("[BUILDER] Enter catch block {:?}", catch_block);
|
|
|
|
|
|
}
|
|
|
|
|
|
if let Some(catch_clause) = catch_clauses.first() {
|
|
|
|
|
|
let catch_ast = ASTNode::Program {
|
|
|
|
|
|
statements: catch_clause.body.clone(),
|
|
|
|
|
|
span: crate::ast::Span::unknown(),
|
|
|
|
|
|
};
|
|
|
|
|
|
self.build_expression(catch_ast)?;
|
|
|
|
|
|
}
|
|
|
|
|
|
if !self.is_current_block_terminated() {
|
|
|
|
|
|
let next_target = finally_block.unwrap_or(exit_block);
|
2025-09-28 20:38:09 +09:00
|
|
|
|
crate::mir::builder::emission::branch::emit_jump(self, next_target)?;
|
2025-09-19 08:34:29 +09:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Finally
|
|
|
|
|
|
let mut cleanup_terminated = false;
|
|
|
|
|
|
if let (Some(finally_block_id), Some(finally_statements)) = (finally_block, finally_body) {
|
|
|
|
|
|
self.start_new_block(finally_block_id)?;
|
|
|
|
|
|
self.in_cleanup_block = true;
|
|
|
|
|
|
self.cleanup_allow_return = crate::config::env::cleanup_allow_return();
|
|
|
|
|
|
self.cleanup_allow_throw = crate::config::env::cleanup_allow_throw();
|
|
|
|
|
|
self.return_defer_active = false; // do not defer inside cleanup
|
|
|
|
|
|
|
|
|
|
|
|
let finally_ast = ASTNode::Program {
|
|
|
|
|
|
statements: finally_statements,
|
|
|
|
|
|
span: crate::ast::Span::unknown(),
|
|
|
|
|
|
};
|
|
|
|
|
|
self.build_expression(finally_ast)?;
|
|
|
|
|
|
cleanup_terminated = self.is_current_block_terminated();
|
|
|
|
|
|
if !cleanup_terminated {
|
2025-09-28 20:38:09 +09:00
|
|
|
|
crate::mir::builder::emission::branch::emit_jump(self, exit_block)?;
|
2025-09-19 08:34:29 +09:00
|
|
|
|
}
|
|
|
|
|
|
self.in_cleanup_block = false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Exit block
|
|
|
|
|
|
self.start_new_block(exit_block)?;
|
|
|
|
|
|
let result = if self.return_deferred_emitted && !cleanup_terminated {
|
2025-11-21 06:25:17 +09:00
|
|
|
|
self.emit_instruction(MirInstruction::Return {
|
|
|
|
|
|
value: Some(ret_slot),
|
|
|
|
|
|
})?;
|
2025-09-28 20:38:09 +09:00
|
|
|
|
crate::mir::builder::emission::constant::emit_void(self)
|
2025-09-19 08:34:29 +09:00
|
|
|
|
} else {
|
2025-09-28 20:38:09 +09:00
|
|
|
|
crate::mir::builder::emission::constant::emit_void(self)
|
2025-09-19 08:34:29 +09:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// Restore context
|
|
|
|
|
|
self.return_defer_active = saved_defer_active;
|
|
|
|
|
|
self.return_defer_slot = saved_defer_slot;
|
|
|
|
|
|
self.return_defer_target = saved_defer_target;
|
|
|
|
|
|
self.return_deferred_emitted = saved_deferred_flag;
|
|
|
|
|
|
self.in_cleanup_block = saved_in_cleanup;
|
|
|
|
|
|
self.cleanup_allow_return = saved_allow_ret;
|
|
|
|
|
|
self.cleanup_allow_throw = saved_allow_throw;
|
|
|
|
|
|
|
|
|
|
|
|
Ok(result)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-05 13:03:48 +09:00
|
|
|
|
/// Phase 188-Impl-2: Extract loop variable name from condition
|
|
|
|
|
|
///
|
|
|
|
|
|
/// For `i < 3`, extracts `i`.
|
|
|
|
|
|
/// For `arr.length() > 0`, extracts `arr`.
|
|
|
|
|
|
///
|
|
|
|
|
|
/// This is a minimal implementation that handles simple comparison patterns.
|
|
|
|
|
|
fn extract_loop_variable_from_condition(&self, condition: &ASTNode) -> Result<String, String> {
|
|
|
|
|
|
use crate::ast::BinaryOperator;
|
|
|
|
|
|
|
|
|
|
|
|
match condition {
|
|
|
|
|
|
ASTNode::BinaryOp {
|
|
|
|
|
|
operator, left, ..
|
|
|
|
|
|
} if matches!(
|
|
|
|
|
|
operator,
|
|
|
|
|
|
BinaryOperator::Less
|
|
|
|
|
|
| BinaryOperator::Greater
|
|
|
|
|
|
| BinaryOperator::LessEqual
|
|
|
|
|
|
| BinaryOperator::GreaterEqual
|
|
|
|
|
|
) =>
|
|
|
|
|
|
{
|
|
|
|
|
|
// Binary comparison: extract variable from left side
|
|
|
|
|
|
match &**left {
|
|
|
|
|
|
ASTNode::Variable { name, .. } => Ok(name.clone()),
|
|
|
|
|
|
_ => Err(format!(
|
|
|
|
|
|
"[cf_loop/pattern1] Cannot extract loop variable from condition: {:?}",
|
|
|
|
|
|
condition
|
|
|
|
|
|
)),
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
_ => Err(format!(
|
|
|
|
|
|
"[cf_loop/pattern1] Unsupported loop condition pattern: {:?}",
|
|
|
|
|
|
condition
|
|
|
|
|
|
)),
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-09-19 08:34:29 +09:00
|
|
|
|
/// Control-flow: throw
|
|
|
|
|
|
pub(super) fn cf_throw(&mut self, expression: ASTNode) -> Result<ValueId, String> {
|
|
|
|
|
|
if self.in_cleanup_block && !self.cleanup_allow_throw {
|
|
|
|
|
|
return Err("throw is not allowed inside cleanup block (enable NYASH_CLEANUP_ALLOW_THROW=1 to permit)".to_string());
|
|
|
|
|
|
}
|
|
|
|
|
|
if std::env::var("NYASH_BUILDER_DISABLE_THROW").ok().as_deref() == Some("1") {
|
|
|
|
|
|
let v = self.build_expression(expression)?;
|
|
|
|
|
|
self.emit_instruction(MirInstruction::ExternCall {
|
|
|
|
|
|
dst: None,
|
|
|
|
|
|
iface_name: "env.debug".to_string(),
|
|
|
|
|
|
method_name: "trace".to_string(),
|
|
|
|
|
|
args: vec![v],
|
|
|
|
|
|
effects: EffectMask::PURE.add(Effect::Debug),
|
|
|
|
|
|
})?;
|
|
|
|
|
|
return Ok(v);
|
|
|
|
|
|
}
|
|
|
|
|
|
let exception_value = self.build_expression(expression)?;
|
|
|
|
|
|
self.emit_instruction(MirInstruction::Throw {
|
|
|
|
|
|
exception: exception_value,
|
|
|
|
|
|
effects: EffectMask::PANIC,
|
|
|
|
|
|
})?;
|
|
|
|
|
|
Ok(exception_value)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|