Files
hakorune/src/mir/builder.rs

350 lines
15 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*!
* MIR Builder - Converts AST to MIR/SSA form
*
* Implements AST → MIR conversion with SSA construction
*/
use super::{
BasicBlock, BasicBlockId, CompareOp, ConstValue, Effect, EffectMask, FunctionSignature,
MirFunction, MirInstruction, MirModule, MirType, ValueId,
};
pub(crate) use builder_calls::CallTarget;
use std::collections::HashMap;
mod binding_context; // Phase 136 follow-up (Step 4/7): BindingContext extraction
mod builder_build;
mod builder_debug;
mod builder_emit;
mod builder_init;
mod builder_metadata;
mod builder_method_index;
mod builder_value_kind;
#[cfg(test)]
mod builder_test_api;
#[cfg(test)]
mod phi_observation_tests;
mod builder_calls;
mod call_resolution; // ChatGPT5 Pro: Type-safe call resolution utilities
mod calls; // Call system modules (refactored from builder_calls)
mod compilation_context; // Phase 136 follow-up (Step 7/7): CompilationContext extraction
mod context; // BoxCompilationContext - 箱理論による静的Boxコンパイル時のコンテキスト分離
mod core_context; // Phase 136 follow-up (Step 2/7): CoreContext extraction
mod decls; // declarations lowering split
mod exprs; // expression lowering split
mod exprs_call;
mod metadata_context; // Phase 136 follow-up (Step 6/7): MetadataContext extraction
mod method_call_handlers;
mod variable_context; // Phase 136 follow-up (Step 5/7): VariableContext extraction // Method call handler separation (Phase 3) // call(expr)
// include lowering removed (using is handled in runner)
mod control_flow; // thin wrappers to centralize control-flow entrypoints
// Phase 140-P4-A: Re-export for loop_canonicalizer SSOT (crate-wide visibility)
pub(crate) use control_flow::detect_skip_whitespace_pattern;
// Phase 104: Re-export read_digits(loop(true)) detection for loop_canonicalizer
pub(crate) use control_flow::detect_read_digits_loop_true_pattern;
// Phase 142-P1: Re-export continue pattern detection for loop_canonicalizer
pub(crate) use control_flow::detect_continue_pattern;
// Phase 143-P0: Re-export parse_number pattern detection for loop_canonicalizer
pub(crate) use control_flow::detect_parse_number_pattern;
pub(crate) use control_flow::detect_parse_string_pattern;
// Phase 91 P5b: Re-export escape skip pattern detection for loop_canonicalizer
pub(crate) use control_flow::detect_escape_skip_pattern;
/// Phase 129: Public (crate) wrapper for StepTree capability guard.
///
/// `control_flow` is intentionally private to keep control-flow entrypoints centralized.
/// Shadow pipelines outside `mir::builder` must call this wrapper instead of reaching into
/// `control_flow::*` directly.
pub(crate) fn check_step_tree_capabilities(
tree: &crate::mir::control_tree::StepTree,
func_name: &str,
strict: bool,
dev: bool,
) -> Result<(), String> {
control_flow::joinir::control_tree_capability_guard::check(tree, func_name, strict, dev)
}
mod exprs_lambda; // lambda lowering
mod exprs_peek; // peek expression
mod exprs_qmark; // ?-propagate
mod fields; // field access/assignment lowering split
mod weak_field_validator; // Phase 285A1: Weak field contract validator
mod if_form;
pub mod joinir_id_remapper; // Phase 189: JoinIR ID remapping (ValueId/BlockId translation) - Public for tests
mod joinir_inline_boundary_injector; // Phase 189: JoinInlineBoundary Copy instruction injector
mod lifecycle;
mod loop_frontend_binding; // Phase 50: Loop Frontend Binding (JoinIR variable mapping)
pub(crate) mod loops;
mod ops;
mod phi;
mod phi_merge; // Phase 25.1q: Unified PHI merge helper // prepare/lower_root/finalize split
// legacy large-match remains inline for now (planned extraction)
mod emission; // emission::*Const/Compare/Branch の薄い発行箱)
mod emit_guard; // EmitGuardBoxemit直前の最終関所
mod metadata; // MetadataPropagationBoxtype/originの伝播
mod name_const; // NameConstBox関数名Const生成
mod observe; // P0: dev-only observability helpersssa/resolve
mod origin; // P0: origin inferenceme/Knownと PHI 伝播(軽量)
mod plugin_sigs; // plugin signature loader
mod receiver; // ReceiverMaterializationBoxMethod recv の pin+LocalSSA 集約)
mod rewrite; // P1: Known rewrite & special consolidation
mod router; // RouterPolicyBoxUnified vs BoxCall
mod schedule; // BlockScheduleBox物理順序: PHI→materialize→body
mod scope_context; // Phase 136 follow-up (Step 3/7): ScopeContext extraction
mod ssa; // LocalSSA helpers (in-block materialization)
mod stmts;
mod type_context; // Phase 136 follow-up: TypeContext extraction
mod type_facts; // Phase 136 follow-up: Type inference facts box
pub(crate) mod type_registry;
mod types; // types::annotation / inference型注釈/推論の箱: 推論は後段)
mod utils;
mod vars; // variables/scope helpers // small loop helpers (header/exit context) // TypeRegistryBox型情報管理の一元化
// Phase 288 Box化: repl_session moved to src/runner/repl/repl_session.rs
// Unified member property kinds for computed/once/birth_once
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub(crate) enum PropertyKind {
Computed,
Once,
BirthOnce,
}
/// MIR builder for converting AST to SSA form
pub struct MirBuilder {
/// Current module being built
pub(super) current_module: Option<MirModule>,
/// Current basic block being built
pub(super) current_block: Option<BasicBlockId>,
/// Phase 136 follow-up (Step 2/7): Core ID generation context
/// Consolidates value_gen, block_gen, next_binding_id, temp_slot_counter, debug_join_counter.
/// Direct field access for backward compatibility (migration in progress).
pub(super) core_ctx: core_context::CoreContext,
/// Phase 136 follow-up: Type information context
/// Consolidates value_types, value_kinds, value_origin_newbox for better organization.
/// Direct field access for backward compatibility (migration in progress).
pub(super) type_ctx: type_context::TypeContext,
/// Phase 136 follow-up (Step 3/7): Scope and control flow context
/// Consolidates lexical_scope_stack, loop stacks, if_merge_stack, current_function,
/// function_param_names, debug_scope_stack for better organization.
/// Direct field access for backward compatibility (migration in progress).
pub(super) scope_ctx: scope_context::ScopeContext,
/// Phase 136 follow-up (Step 4/7): Binding context
/// Consolidates binding_map (String -> BindingId mapping).
/// Direct field access for backward compatibility (migration in progress).
pub(super) binding_ctx: binding_context::BindingContext,
/// Phase 136 follow-up (Step 5/7): Variable context
/// Consolidates variable_map (String -> ValueId mapping for SSA conversion).
/// Direct field access for backward compatibility (migration in progress).
pub(super) variable_ctx: variable_context::VariableContext,
/// Phase 136 follow-up (Step 6/7): Metadata context
/// Consolidates current_span, source_file, hint_sink, current_region_stack.
/// Direct field access for backward compatibility (migration in progress).
pub(super) metadata_ctx: metadata_context::MetadataContext,
/// Phase 136 follow-up (Step 7/7): Compilation context
/// Consolidates compilation_context, current_static_box, user_defined_boxes, reserved_value_ids,
/// fn_body_ast, weak_fields_by_box, property_getters_by_box, field_origin_class, field_origin_by_box,
/// static_method_index, method_tail_index, type_registry, current_slot_registry, plugin_method_sigs.
/// Direct field access for backward compatibility (migration in progress).
pub(super) comp_ctx: compilation_context::CompilationContext,
/// Pending phi functions to be inserted
#[allow(dead_code)]
pub(super) pending_phis: Vec<(BasicBlockId, ValueId, String)>,
// Phase 2-5: binding_map removed - use binding_ctx.binding_map instead
// include guards removed
// フェーズM: no_phi_modeフィールド削除常にPHI使用
// ---- Try/Catch/Cleanup lowering context ----
/// When true, `return` statements are deferred: they assign to `return_defer_slot`
/// and jump to `return_defer_target` (typically the cleanup/exit block).
pub(super) return_defer_active: bool,
/// Slot value to receive deferred return values (edge-copy mode friendly).
pub(super) return_defer_slot: Option<ValueId>,
/// Target block to jump to on deferred return.
pub(super) return_defer_target: Option<BasicBlockId>,
/// Set to true when a deferred return has been emitted in the current context.
pub(super) return_deferred_emitted: bool,
/// True while lowering the cleanup block.
pub(super) in_cleanup_block: bool,
/// Policy flags (snapshotted at entry of try/catch lowering)
pub(super) cleanup_allow_return: bool,
pub(super) cleanup_allow_throw: bool,
/// If true, skip entry materialization of pinned slots on the next start_new_block call.
suppress_pin_entry_copy_next: bool,
// ----------------------
// Debug scope context (dev only; zero-cost when unused)
// ----------------------
/// Local SSA cache: ensure per-block materialization for critical operands (e.g., recv)
/// Key: (bb, original ValueId, kind) -> local ValueId
/// kind: 0=recv, 1+ reserved for future (args etc.)
pub(super) local_ssa_map: HashMap<(BasicBlockId, ValueId, u8), ValueId>,
/// BlockSchedule cache: deduplicate materialize copies per (bb, src)
pub(super) schedule_mat_map: HashMap<(BasicBlockId, ValueId), ValueId>,
/// Mapping from ValueId to its pin slot name (e.g., "__pin$3$@recv")
/// Used by LocalSSA to redirect old pinned values to the latest slot value.
pub(super) pin_slot_names: HashMap<ValueId, String>,
/// Guard flag to prevent re-entering emit_unified_call from BoxCall fallback.
/// Used when RouterPolicyBox in emit_unified_call has already decided to
/// route a given Method call to BoxCall; emit_box_or_plugin_call must not
/// bounce back into the unified path for the same call, otherwise an
/// infinite recursion (emit_unified_call → emit_box_or_plugin_call →
/// emit_unified_call …) can occur when routing decisions disagree.
pub(super) in_unified_boxcall_fallback: bool,
/// Recursion depth counter for debugging stack overflow
/// Tracks the depth of build_expression calls to detect infinite loops
pub(super) recursion_depth: usize,
/// Root lowering mode: how to treat top-level Program
/// - None: not decided yet (lower_root not called)
/// - Some(true): App mode (static box Main.main is entry)
/// - Some(false): Script/Test mode (top-level Program runs sequentially)
pub(super) root_is_app_mode: Option<bool>,
/// 🎯 Phase 21.7: Static box singleton instances for methodization
/// Maps BoxName → ValueId of singleton instance (created on demand)
/// Used when HAKO_MIR_BUILDER_METHODIZE=1 to convert Global("BoxName.method/arity")
/// to Method{receiver=singleton} calls
pub(super) static_box_singletons: HashMap<String, ValueId>,
/// Phase 288 P2: REPL mode flag - enables implicit local declarations
/// File mode: false (explicit local required)
/// REPL mode: true (暗黙 local 許可)
pub(crate) repl_mode: bool,
}
impl Default for MirBuilder {
fn default() -> Self {
Self::new()
}
}
// Phase 79: BindingMapProvider implementation
// Centralizes feature-gated binding_map access for promoters
use crate::mir::loop_pattern_detection::BindingMapProvider;
impl BindingMapProvider for MirBuilder {
#[cfg(feature = "normalized_dev")]
fn get_binding_map(
&self,
) -> Option<&std::collections::BTreeMap<String, crate::mir::BindingId>> {
// Phase 136 Step 4/7: Use binding_ctx (SSOT)
Some(self.binding_ctx.binding_map())
}
#[cfg(not(feature = "normalized_dev"))]
fn get_binding_map(
&self,
) -> Option<&std::collections::BTreeMap<String, crate::mir::BindingId>> {
None
}
}
#[cfg(test)]
mod binding_id_tests {
use super::*;
#[test]
fn test_binding_map_initialization() {
let builder = MirBuilder::new();
assert_eq!(builder.core_ctx.next_binding_id, 0);
// Phase 2-6: binding_ctx is now SSOT (legacy field removed)
assert!(builder.binding_ctx.binding_map.is_empty());
}
#[test]
fn test_binding_allocation_sequential() {
let mut builder = MirBuilder::new();
let bid0 = builder.allocate_binding_id();
let bid1 = builder.allocate_binding_id();
let bid2 = builder.allocate_binding_id();
assert_eq!(bid0.raw(), 0);
assert_eq!(bid1.raw(), 1);
assert_eq!(bid2.raw(), 2);
assert_eq!(builder.core_ctx.next_binding_id, 3);
}
#[test]
fn test_shadowing_binding_restore() {
let mut builder = MirBuilder::new();
// Simulate function entry scope
builder.push_lexical_scope();
// Declare outer x
// Phase 136 P0: Use SSOT allocator for function scope simulation
let outer_vid = builder.next_value_id();
builder
.declare_local_in_current_scope("x", outer_vid)
.unwrap();
// Phase 2-6: Check binding_ctx (SSOT)
let outer_bid = builder.binding_ctx.lookup("x").unwrap();
assert_eq!(outer_bid.raw(), 0);
// Enter inner scope and shadow x
builder.push_lexical_scope();
// Phase 136 P0: Use SSOT allocator for function scope simulation
let inner_vid = builder.next_value_id();
builder
.declare_local_in_current_scope("x", inner_vid)
.unwrap();
// Phase 2-6: Check binding_ctx (SSOT)
let inner_bid = builder.binding_ctx.lookup("x").unwrap();
assert_eq!(inner_bid.raw(), 1);
// Exit inner scope - should restore outer binding
builder.pop_lexical_scope();
// Phase 2-6: Check binding_ctx (SSOT)
let restored_bid = builder.binding_ctx.lookup("x").unwrap();
assert_eq!(restored_bid, outer_bid);
assert_eq!(restored_bid.raw(), 0);
// Cleanup
builder.pop_lexical_scope();
}
#[test]
fn test_valueid_binding_parallel_allocation() {
let mut builder = MirBuilder::new();
// Phase 136 P0: Use SSOT allocator (next_value_id)
// Note: Without current_function, next_value_id() falls back to value_gen.next()
// so this test still validates ValueId/BindingId independence
// Allocate ValueIds and BindingIds in parallel
let vid0 = builder.next_value_id();
let bid0 = builder.allocate_binding_id();
let vid1 = builder.next_value_id();
let bid1 = builder.allocate_binding_id();
// ValueId and BindingId should be independent
assert_eq!(vid0.0, 0);
assert_eq!(bid0.raw(), 0);
assert_eq!(vid1.0, 1);
assert_eq!(bid1.raw(), 1);
// Allocating more ValueIds should not affect BindingId counter
let _ = builder.next_value_id();
let _ = builder.next_value_id();
let bid2 = builder.allocate_binding_id();
assert_eq!(bid2.raw(), 2); // Still sequential
// Allocating more BindingIds should not affect ValueId counter
let _ = builder.allocate_binding_id();
let _ = builder.allocate_binding_id();
let vid2 = builder.next_value_id();
assert_eq!(vid2.0, 4); // Continues from where we left off
}
}