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-28 19:29:45 +09:00
|
|
|
use crate::ast::{ASTNode, Span};
|
2025-09-19 08:34:29 +09:00
|
|
|
|
|
|
|
|
impl super::MirBuilder {
|
|
|
|
|
/// 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
|
|
|
|
|
pub(super) fn cf_if(
|
|
|
|
|
&mut self,
|
|
|
|
|
condition: ASTNode,
|
|
|
|
|
then_branch: ASTNode,
|
|
|
|
|
else_branch: Option<ASTNode>,
|
|
|
|
|
) -> Result<ValueId, String> {
|
|
|
|
|
// current impl is a simple forward to canonical lowering
|
|
|
|
|
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
|
|
|
|
|
/// when enabled via dev flags:
|
|
|
|
|
///
|
|
|
|
|
/// - `HAKO_JOINIR_PRINT_TOKENS_MAIN=1`: JsonTokenizer.print_tokens/0
|
|
|
|
|
/// - `HAKO_JOINIR_ARRAY_FILTER_MAIN=1`: ArrayExtBox.filter/2
|
|
|
|
|
///
|
|
|
|
|
/// 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-11-28 19:12:59 +09:00
|
|
|
// Phase 49: Try JoinIR Frontend route for mainline targets
|
|
|
|
|
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-09-19 08:34:29 +09:00
|
|
|
// Delegate to LoopBuilder for consistent handling
|
|
|
|
|
let mut loop_builder = crate::mir::loop_builder::LoopBuilder::new(self);
|
|
|
|
|
loop_builder.build_loop(condition, body)
|
|
|
|
|
}
|
|
|
|
|
|
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-11-28 20:12:39 +09:00
|
|
|
// Phase 49-4: Multi-target routing with separate dev flags
|
|
|
|
|
// Note: Arity does NOT include implicit `me` receiver
|
|
|
|
|
let is_target = match func_name.as_str() {
|
|
|
|
|
"JsonTokenizer.print_tokens/0" => {
|
|
|
|
|
std::env::var("HAKO_JOINIR_PRINT_TOKENS_MAIN").ok().as_deref() == Some("1")
|
|
|
|
|
}
|
|
|
|
|
"ArrayExtBox.filter/2" => {
|
|
|
|
|
std::env::var("HAKO_JOINIR_ARRAY_FILTER_MAIN").ok().as_deref() == Some("1")
|
|
|
|
|
}
|
|
|
|
|
_ => 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();
|
|
|
|
|
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::r#macro::ast_json::ast_to_json;
|
|
|
|
|
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-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={:?}",
|
|
|
|
|
binding.counter_var,
|
|
|
|
|
binding.accumulator_var,
|
|
|
|
|
binding.pattern
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
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-28 20:59:54 +09:00
|
|
|
let mut body_json: Vec<serde_json::Value> =
|
|
|
|
|
body.iter().map(|s| ast_to_json(s)).collect();
|
|
|
|
|
|
|
|
|
|
// 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 04:42:16 +09:00
|
|
|
// Phase 52: Check if `me` receiver is needed
|
|
|
|
|
// Instance methods (like print_tokens) need `me` to be passed as a parameter
|
|
|
|
|
let params: Vec<serde_json::Value> = if binding.needs_me_receiver() {
|
|
|
|
|
if debug {
|
|
|
|
|
eprintln!("[cf_loop/joinir] Adding 'me' to params (instance method)");
|
|
|
|
|
}
|
|
|
|
|
vec![serde_json::json!("me")]
|
|
|
|
|
} else {
|
|
|
|
|
vec![]
|
|
|
|
|
};
|
|
|
|
|
|
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()
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 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
|
|
|
|
|
self.merge_joinir_mir_blocks(&mir_module, debug)?;
|
|
|
|
|
|
|
|
|
|
// 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-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-11-28 19:45:23 +09:00
|
|
|
/// This merges JoinIR-generated blocks by:
|
|
|
|
|
/// 1. Remapping all block IDs to avoid conflicts
|
|
|
|
|
/// 2. Remapping all value IDs to avoid conflicts
|
|
|
|
|
/// 3. Adding all blocks to current_function
|
|
|
|
|
/// 4. Jumping from current_block to the entry block
|
|
|
|
|
/// 5. Converting Return → Jump to exit block
|
2025-11-28 19:29:45 +09:00
|
|
|
fn merge_joinir_mir_blocks(
|
|
|
|
|
&mut self,
|
|
|
|
|
mir_module: &crate::mir::MirModule,
|
|
|
|
|
debug: bool,
|
|
|
|
|
) -> Result<(), String> {
|
2025-11-28 19:45:23 +09:00
|
|
|
use crate::mir::{BasicBlock, BasicBlockId, MirInstruction, ValueId};
|
|
|
|
|
use std::collections::HashMap;
|
|
|
|
|
|
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
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Get the first (and typically only) function from JoinIR output
|
|
|
|
|
let join_func = mir_module
|
|
|
|
|
.functions
|
|
|
|
|
.values()
|
|
|
|
|
.next()
|
|
|
|
|
.ok_or("JoinIR module has no functions")?;
|
|
|
|
|
|
|
|
|
|
if debug {
|
|
|
|
|
eprintln!(
|
|
|
|
|
"[cf_loop/joinir] Merging function with {} blocks, entry={:?}",
|
|
|
|
|
join_func.blocks.len(),
|
|
|
|
|
join_func.entry_block
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Phase 49-3.2: Block ID and Value ID remapping
|
|
|
|
|
let mut block_map: HashMap<BasicBlockId, BasicBlockId> = HashMap::new();
|
|
|
|
|
let mut value_map: HashMap<ValueId, ValueId> = HashMap::new();
|
|
|
|
|
|
|
|
|
|
// 1. Allocate new block IDs for all JoinIR blocks
|
|
|
|
|
for old_block_id in join_func.blocks.keys() {
|
|
|
|
|
let new_block_id = self.block_gen.next();
|
|
|
|
|
block_map.insert(*old_block_id, new_block_id);
|
|
|
|
|
if debug {
|
2025-11-28 19:29:45 +09:00
|
|
|
eprintln!(
|
2025-11-28 19:45:23 +09:00
|
|
|
"[cf_loop/joinir] Block remap: {:?} → {:?}",
|
|
|
|
|
old_block_id, new_block_id
|
2025-11-28 19:29:45 +09:00
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-28 19:45:23 +09:00
|
|
|
// 2. Create exit block for Return conversion
|
|
|
|
|
let exit_block_id = self.block_gen.next();
|
|
|
|
|
if debug {
|
|
|
|
|
eprintln!("[cf_loop/joinir] Exit block: {:?}", exit_block_id);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 3. Collect all ValueIds used in JoinIR function
|
|
|
|
|
let mut used_values: std::collections::BTreeSet<ValueId> = std::collections::BTreeSet::new();
|
|
|
|
|
for block in join_func.blocks.values() {
|
|
|
|
|
Self::collect_values_in_block(block, &mut used_values);
|
|
|
|
|
}
|
|
|
|
|
// Also collect parameter ValueIds
|
|
|
|
|
for param in &join_func.params {
|
|
|
|
|
used_values.insert(*param);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 4. Allocate new ValueIds
|
|
|
|
|
for old_value in used_values {
|
|
|
|
|
let new_value = self.next_value_id();
|
|
|
|
|
value_map.insert(old_value, new_value);
|
|
|
|
|
if debug {
|
|
|
|
|
eprintln!(
|
|
|
|
|
"[cf_loop/joinir] Value remap: {:?} → {:?}",
|
|
|
|
|
old_value, new_value
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 5. Clone and remap all blocks
|
|
|
|
|
for (old_block_id, old_block) in &join_func.blocks {
|
|
|
|
|
let new_block_id = block_map[old_block_id];
|
|
|
|
|
let mut new_block = BasicBlock::new(new_block_id);
|
|
|
|
|
|
|
|
|
|
// Remap instructions
|
|
|
|
|
for inst in &old_block.instructions {
|
|
|
|
|
let remapped = Self::remap_instruction(inst, &value_map, &block_map);
|
|
|
|
|
new_block.instructions.push(remapped);
|
|
|
|
|
}
|
|
|
|
|
new_block.instruction_spans = old_block.instruction_spans.clone();
|
|
|
|
|
|
|
|
|
|
// Remap terminator (convert Return → Jump to exit)
|
|
|
|
|
if let Some(ref term) = old_block.terminator {
|
|
|
|
|
let remapped_term = match term {
|
|
|
|
|
MirInstruction::Return { value } => {
|
|
|
|
|
// Convert Return to Jump to exit block
|
|
|
|
|
// If there's a return value, we need to store it first
|
|
|
|
|
if let Some(ret_val) = value {
|
|
|
|
|
let remapped_val = value_map.get(ret_val).copied().unwrap_or(*ret_val);
|
|
|
|
|
// Store the return value for later use
|
|
|
|
|
// For now, just jump to exit (value handling in Phase 49-4)
|
|
|
|
|
if debug {
|
|
|
|
|
eprintln!(
|
|
|
|
|
"[cf_loop/joinir] Return({:?}) → Jump to exit",
|
|
|
|
|
remapped_val
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
MirInstruction::Jump { target: exit_block_id }
|
|
|
|
|
}
|
|
|
|
|
_ => Self::remap_instruction(term, &value_map, &block_map),
|
|
|
|
|
};
|
|
|
|
|
new_block.terminator = Some(remapped_term);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Add block to current function
|
|
|
|
|
if let Some(ref mut func) = self.current_function {
|
|
|
|
|
func.add_block(new_block);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 6. Create exit block (empty for now, will be populated after loop)
|
|
|
|
|
if let Some(ref mut func) = self.current_function {
|
|
|
|
|
let exit_block = BasicBlock::new(exit_block_id);
|
|
|
|
|
func.add_block(exit_block);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 7. Jump from current block to JoinIR entry
|
|
|
|
|
let entry_block = block_map[&join_func.entry_block];
|
|
|
|
|
crate::mir::builder::emission::branch::emit_jump(self, entry_block)?;
|
|
|
|
|
|
|
|
|
|
// 8. Switch to exit block for subsequent code
|
|
|
|
|
self.start_new_block(exit_block_id)?;
|
|
|
|
|
|
|
|
|
|
if debug {
|
|
|
|
|
eprintln!(
|
|
|
|
|
"[cf_loop/joinir] Merge complete: {} blocks added, continuing from {:?}",
|
|
|
|
|
join_func.blocks.len(),
|
|
|
|
|
exit_block_id
|
|
|
|
|
);
|
|
|
|
|
}
|
2025-11-28 19:29:45 +09:00
|
|
|
|
|
|
|
|
Ok(())
|
2025-11-28 19:12:59 +09:00
|
|
|
}
|
|
|
|
|
|
2025-11-28 19:45:23 +09:00
|
|
|
/// Collect all ValueIds used in a block
|
|
|
|
|
fn collect_values_in_block(
|
|
|
|
|
block: &crate::mir::BasicBlock,
|
|
|
|
|
values: &mut std::collections::BTreeSet<super::ValueId>,
|
|
|
|
|
) {
|
|
|
|
|
for inst in &block.instructions {
|
|
|
|
|
Self::collect_values_in_instruction(inst, values);
|
|
|
|
|
}
|
|
|
|
|
if let Some(ref term) = block.terminator {
|
|
|
|
|
Self::collect_values_in_instruction(term, values);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Collect all ValueIds used in an instruction
|
|
|
|
|
fn collect_values_in_instruction(
|
|
|
|
|
inst: &crate::mir::MirInstruction,
|
|
|
|
|
values: &mut std::collections::BTreeSet<super::ValueId>,
|
|
|
|
|
) {
|
|
|
|
|
use crate::mir::MirInstruction;
|
|
|
|
|
|
|
|
|
|
match inst {
|
|
|
|
|
MirInstruction::Const { dst, .. } => {
|
|
|
|
|
values.insert(*dst);
|
|
|
|
|
}
|
|
|
|
|
MirInstruction::BinOp { dst, lhs, rhs, .. } => {
|
|
|
|
|
values.insert(*dst);
|
|
|
|
|
values.insert(*lhs);
|
|
|
|
|
values.insert(*rhs);
|
|
|
|
|
}
|
|
|
|
|
MirInstruction::UnaryOp { dst, operand, .. } => {
|
|
|
|
|
values.insert(*dst);
|
|
|
|
|
values.insert(*operand);
|
|
|
|
|
}
|
|
|
|
|
MirInstruction::Compare { dst, lhs, rhs, .. } => {
|
|
|
|
|
values.insert(*dst);
|
|
|
|
|
values.insert(*lhs);
|
|
|
|
|
values.insert(*rhs);
|
|
|
|
|
}
|
|
|
|
|
MirInstruction::Load { dst, ptr } => {
|
|
|
|
|
values.insert(*dst);
|
|
|
|
|
values.insert(*ptr);
|
|
|
|
|
}
|
|
|
|
|
MirInstruction::Store { value, ptr } => {
|
|
|
|
|
values.insert(*value);
|
|
|
|
|
values.insert(*ptr);
|
|
|
|
|
}
|
|
|
|
|
MirInstruction::Call { dst, func, args, .. } => {
|
|
|
|
|
if let Some(d) = dst {
|
|
|
|
|
values.insert(*d);
|
|
|
|
|
}
|
|
|
|
|
values.insert(*func);
|
|
|
|
|
for arg in args {
|
|
|
|
|
values.insert(*arg);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
MirInstruction::BoxCall { dst, box_val, args, .. } => {
|
|
|
|
|
if let Some(d) = dst {
|
|
|
|
|
values.insert(*d);
|
|
|
|
|
}
|
|
|
|
|
values.insert(*box_val);
|
|
|
|
|
for arg in args {
|
|
|
|
|
values.insert(*arg);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
MirInstruction::Branch { condition, .. } => {
|
|
|
|
|
values.insert(*condition);
|
|
|
|
|
}
|
|
|
|
|
MirInstruction::Return { value } => {
|
|
|
|
|
if let Some(v) = value {
|
|
|
|
|
values.insert(*v);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
MirInstruction::Phi { dst, inputs } => {
|
|
|
|
|
values.insert(*dst);
|
|
|
|
|
for (_, val) in inputs {
|
|
|
|
|
values.insert(*val);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
MirInstruction::Copy { dst, src } => {
|
|
|
|
|
values.insert(*dst);
|
|
|
|
|
values.insert(*src);
|
|
|
|
|
}
|
|
|
|
|
MirInstruction::NewBox { dst, args, .. } => {
|
|
|
|
|
values.insert(*dst);
|
|
|
|
|
for arg in args {
|
|
|
|
|
values.insert(*arg);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
MirInstruction::Print { value, .. } => {
|
|
|
|
|
values.insert(*value);
|
|
|
|
|
}
|
|
|
|
|
_ => {
|
|
|
|
|
// Other instructions: skip for now
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Remap an instruction's ValueIds and BlockIds
|
|
|
|
|
fn remap_instruction(
|
|
|
|
|
inst: &crate::mir::MirInstruction,
|
|
|
|
|
value_map: &std::collections::HashMap<super::ValueId, super::ValueId>,
|
|
|
|
|
block_map: &std::collections::HashMap<crate::mir::BasicBlockId, crate::mir::BasicBlockId>,
|
|
|
|
|
) -> crate::mir::MirInstruction {
|
|
|
|
|
use crate::mir::MirInstruction;
|
|
|
|
|
|
|
|
|
|
let remap_value = |v: super::ValueId| value_map.get(&v).copied().unwrap_or(v);
|
|
|
|
|
let remap_block = |b: crate::mir::BasicBlockId| block_map.get(&b).copied().unwrap_or(b);
|
|
|
|
|
|
|
|
|
|
match inst {
|
|
|
|
|
MirInstruction::Const { dst, value } => MirInstruction::Const {
|
|
|
|
|
dst: remap_value(*dst),
|
|
|
|
|
value: value.clone(),
|
|
|
|
|
},
|
|
|
|
|
MirInstruction::BinOp { dst, op, lhs, rhs } => MirInstruction::BinOp {
|
|
|
|
|
dst: remap_value(*dst),
|
|
|
|
|
op: *op,
|
|
|
|
|
lhs: remap_value(*lhs),
|
|
|
|
|
rhs: remap_value(*rhs),
|
|
|
|
|
},
|
|
|
|
|
MirInstruction::UnaryOp { dst, op, operand } => MirInstruction::UnaryOp {
|
|
|
|
|
dst: remap_value(*dst),
|
|
|
|
|
op: *op,
|
|
|
|
|
operand: remap_value(*operand),
|
|
|
|
|
},
|
|
|
|
|
MirInstruction::Compare { dst, op, lhs, rhs } => MirInstruction::Compare {
|
|
|
|
|
dst: remap_value(*dst),
|
|
|
|
|
op: *op,
|
|
|
|
|
lhs: remap_value(*lhs),
|
|
|
|
|
rhs: remap_value(*rhs),
|
|
|
|
|
},
|
|
|
|
|
MirInstruction::Load { dst, ptr } => MirInstruction::Load {
|
|
|
|
|
dst: remap_value(*dst),
|
|
|
|
|
ptr: remap_value(*ptr),
|
|
|
|
|
},
|
|
|
|
|
MirInstruction::Store { value, ptr } => MirInstruction::Store {
|
|
|
|
|
value: remap_value(*value),
|
|
|
|
|
ptr: remap_value(*ptr),
|
|
|
|
|
},
|
|
|
|
|
MirInstruction::Call { dst, func, callee, args, effects } => MirInstruction::Call {
|
|
|
|
|
dst: dst.map(remap_value),
|
|
|
|
|
func: remap_value(*func),
|
|
|
|
|
callee: callee.clone(),
|
|
|
|
|
args: args.iter().map(|a| remap_value(*a)).collect(),
|
|
|
|
|
effects: *effects,
|
|
|
|
|
},
|
|
|
|
|
MirInstruction::BoxCall { dst, box_val, method, method_id, args, effects } => {
|
|
|
|
|
MirInstruction::BoxCall {
|
|
|
|
|
dst: dst.map(remap_value),
|
|
|
|
|
box_val: remap_value(*box_val),
|
|
|
|
|
method: method.clone(),
|
|
|
|
|
method_id: *method_id,
|
|
|
|
|
args: args.iter().map(|a| remap_value(*a)).collect(),
|
|
|
|
|
effects: *effects,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
MirInstruction::Branch { condition, then_bb, else_bb } => MirInstruction::Branch {
|
|
|
|
|
condition: remap_value(*condition),
|
|
|
|
|
then_bb: remap_block(*then_bb),
|
|
|
|
|
else_bb: remap_block(*else_bb),
|
|
|
|
|
},
|
|
|
|
|
MirInstruction::Jump { target } => MirInstruction::Jump {
|
|
|
|
|
target: remap_block(*target),
|
|
|
|
|
},
|
|
|
|
|
MirInstruction::Return { value } => MirInstruction::Return {
|
|
|
|
|
value: value.map(remap_value),
|
|
|
|
|
},
|
|
|
|
|
MirInstruction::Phi { dst, inputs } => MirInstruction::Phi {
|
|
|
|
|
dst: remap_value(*dst),
|
|
|
|
|
inputs: inputs
|
|
|
|
|
.iter()
|
|
|
|
|
.map(|(bb, val)| (remap_block(*bb), remap_value(*val)))
|
|
|
|
|
.collect(),
|
|
|
|
|
},
|
|
|
|
|
MirInstruction::Copy { dst, src } => MirInstruction::Copy {
|
|
|
|
|
dst: remap_value(*dst),
|
|
|
|
|
src: remap_value(*src),
|
|
|
|
|
},
|
|
|
|
|
MirInstruction::NewBox { dst, box_type, args } => MirInstruction::NewBox {
|
|
|
|
|
dst: remap_value(*dst),
|
|
|
|
|
box_type: box_type.clone(),
|
|
|
|
|
args: args.iter().map(|a| remap_value(*a)).collect(),
|
|
|
|
|
},
|
|
|
|
|
MirInstruction::Print { value, effects } => MirInstruction::Print {
|
|
|
|
|
value: remap_value(*value),
|
|
|
|
|
effects: *effects,
|
|
|
|
|
},
|
|
|
|
|
// Pass through other instructions unchanged
|
|
|
|
|
other => other.clone(),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
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)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// 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)
|
|
|
|
|
}
|
|
|
|
|
}
|