refactor(joinir): Split ast_lowerer and join_ir_vm_bridge into modules
ast_lowerer.rs → ast_lowerer/ (10 files): - mod.rs: public surface + entry dispatch - context.rs: ExtractCtx helpers - expr.rs: expression-to-JoinIR extraction - if_return.rs: simple if→Select lowering - loop_patterns.rs: loop variants (simple/break/continue) - read_quoted.rs: read_quoted_from lowering (Phase 45-46) - nested_if.rs: NestedIfMerge lowering - analysis.rs: loop if-var analysis + metadata helpers - tests.rs: frontend lowering tests - README.md: module documentation join_ir_vm_bridge.rs → join_ir_vm_bridge/ (5 files): - mod.rs: public surface + shared helpers - convert.rs: JoinIR→MIR lowering - runner.rs: VM execution entry (run_joinir_via_vm) - meta.rs: experimental metadata-aware hooks - tests.rs: bridge-specific unit tests - README.md: module documentation Benefits: - Clear separation of concerns per pattern - Easier navigation and maintenance - Each file has single responsibility - README documents module boundaries Co-authored-by: ChatGPT <noreply@openai.com> 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@ -155,7 +155,11 @@ pub(super) fn try_handle_instance_box(
|
||||
);
|
||||
}
|
||||
|
||||
let mut argv: Vec<VMValue> = Vec::with_capacity(if include_me { 1 + args.len() } else { args.len() });
|
||||
let mut argv: Vec<VMValue> = Vec::with_capacity(if include_me {
|
||||
1 + args.len()
|
||||
} else {
|
||||
args.len()
|
||||
});
|
||||
// Dev assert: forbid birth(me==Void)
|
||||
if method == "birth" && crate::config::env::using_is_dev() {
|
||||
if matches!(recv_vm, VMValue::Void) {
|
||||
@ -211,7 +215,11 @@ pub(super) fn try_handle_instance_box(
|
||||
true
|
||||
};
|
||||
|
||||
let mut argv: Vec<VMValue> = Vec::with_capacity(if include_me { 1 + args.len() } else { args.len() });
|
||||
let mut argv: Vec<VMValue> = Vec::with_capacity(if include_me {
|
||||
1 + args.len()
|
||||
} else {
|
||||
args.len()
|
||||
});
|
||||
if method == "birth" && crate::config::env::using_is_dev() {
|
||||
if matches!(recv_vm, VMValue::Void) {
|
||||
return Err(
|
||||
|
||||
@ -329,7 +329,9 @@ impl MirInterpreter {
|
||||
let func = self
|
||||
.functions
|
||||
.get(func_name)
|
||||
.ok_or_else(|| VMError::InvalidInstruction(format!("function not found: {}", func_name)))?
|
||||
.ok_or_else(|| {
|
||||
VMError::InvalidInstruction(format!("function not found: {}", func_name))
|
||||
})?
|
||||
.clone();
|
||||
|
||||
self.exec_function_inner(&func, Some(args))
|
||||
|
||||
@ -663,10 +663,7 @@ pub fn ny_compiler_use_tmp_only() -> bool {
|
||||
|
||||
/// Use Python MVP harness for Ny compiler (NYASH_NY_COMPILER_USE_PY=1).
|
||||
pub fn ny_compiler_use_py() -> bool {
|
||||
std::env::var("NYASH_NY_COMPILER_USE_PY")
|
||||
.ok()
|
||||
.as_deref()
|
||||
== Some("1")
|
||||
std::env::var("NYASH_NY_COMPILER_USE_PY").ok().as_deref() == Some("1")
|
||||
}
|
||||
|
||||
/// Macro pre-expand mode for selfhost (NYASH_MACRO_SELFHOST_PRE_EXPAND).
|
||||
@ -677,26 +674,17 @@ pub fn macro_selfhost_pre_expand() -> Option<String> {
|
||||
|
||||
/// ScopeBox enable flag (NYASH_SCOPEBOX_ENABLE=1).
|
||||
pub fn scopebox_enable() -> bool {
|
||||
std::env::var("NYASH_SCOPEBOX_ENABLE")
|
||||
.ok()
|
||||
.as_deref()
|
||||
== Some("1")
|
||||
std::env::var("NYASH_SCOPEBOX_ENABLE").ok().as_deref() == Some("1")
|
||||
}
|
||||
|
||||
/// LoopForm normalize flag (NYASH_LOOPFORM_NORMALIZE=1).
|
||||
pub fn loopform_normalize() -> bool {
|
||||
std::env::var("NYASH_LOOPFORM_NORMALIZE")
|
||||
.ok()
|
||||
.as_deref()
|
||||
== Some("1")
|
||||
std::env::var("NYASH_LOOPFORM_NORMALIZE").ok().as_deref() == Some("1")
|
||||
}
|
||||
|
||||
/// Dev-only escape hatch: force inline selfhost path (NYASH_SELFHOST_INLINE_FORCE=1).
|
||||
pub fn selfhost_inline_force() -> bool {
|
||||
std::env::var("NYASH_SELFHOST_INLINE_FORCE")
|
||||
.ok()
|
||||
.as_deref()
|
||||
== Some("1")
|
||||
std::env::var("NYASH_SELFHOST_INLINE_FORCE").ok().as_deref() == Some("1")
|
||||
}
|
||||
|
||||
/// Unicode decode toggle for string literals (\uXXXX, optional surrogate pairs).
|
||||
|
||||
@ -36,37 +36,15 @@ pub static OPERATORS_DIV_RULES: &[(&str, &str, &str, &str)] = &[
|
||||
];
|
||||
pub fn lookup_keyword(word: &str) -> Option<&'static str> {
|
||||
for (k, t) in KEYWORDS {
|
||||
if *k == word { return Some(*t); }
|
||||
if *k == word {
|
||||
return Some(*t);
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
pub static SYNTAX_ALLOWED_STATEMENTS: &[&str] = &[
|
||||
"box",
|
||||
"global",
|
||||
"function",
|
||||
"static",
|
||||
"if",
|
||||
"loop",
|
||||
"break",
|
||||
"return",
|
||||
"print",
|
||||
"nowait",
|
||||
"include",
|
||||
"local",
|
||||
"outbox",
|
||||
"try",
|
||||
"throw",
|
||||
"using",
|
||||
"from",
|
||||
];
|
||||
pub static SYNTAX_ALLOWED_BINOPS: &[&str] = &[
|
||||
"add",
|
||||
"sub",
|
||||
"mul",
|
||||
"div",
|
||||
"and",
|
||||
"or",
|
||||
"eq",
|
||||
"ne",
|
||||
"box", "global", "function", "static", "if", "loop", "break", "return", "print", "nowait",
|
||||
"include", "local", "outbox", "try", "throw", "using", "from",
|
||||
];
|
||||
pub static SYNTAX_ALLOWED_BINOPS: &[&str] = &["add", "sub", "mul", "div", "and", "or", "eq", "ne"];
|
||||
|
||||
@ -244,10 +244,7 @@ impl BasicBlock {
|
||||
self.instructions
|
||||
.get(idx)
|
||||
.zip(self.instruction_spans.get(idx))
|
||||
.map(|(inst, span)| SpannedInstRef {
|
||||
inst,
|
||||
span: *span,
|
||||
})
|
||||
.map(|(inst, span)| SpannedInstRef { inst, span: *span })
|
||||
}
|
||||
|
||||
/// Get span for terminator instruction
|
||||
@ -268,19 +265,12 @@ impl BasicBlock {
|
||||
self.instructions
|
||||
.iter()
|
||||
.zip(self.instruction_spans.iter())
|
||||
.map(|(inst, span)| SpannedInstRef {
|
||||
inst,
|
||||
span: *span,
|
||||
})
|
||||
.map(|(inst, span)| SpannedInstRef { inst, span: *span })
|
||||
}
|
||||
|
||||
/// Iterate instructions with index and span.
|
||||
pub fn iter_spanned_enumerated(
|
||||
&self,
|
||||
) -> impl Iterator<Item = (usize, SpannedInstRef<'_>)> {
|
||||
self.iter_spanned()
|
||||
.enumerate()
|
||||
.map(|(idx, sp)| (idx, sp))
|
||||
pub fn iter_spanned_enumerated(&self) -> impl Iterator<Item = (usize, SpannedInstRef<'_>)> {
|
||||
self.iter_spanned().enumerate().map(|(idx, sp)| (idx, sp))
|
||||
}
|
||||
|
||||
/// Iterate all instructions (including terminator) with spans.
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
22
src/mir/join_ir/frontend/ast_lowerer/README.md
Normal file
22
src/mir/join_ir/frontend/ast_lowerer/README.md
Normal file
@ -0,0 +1,22 @@
|
||||
AST/CFG → JoinIR frontend lowering layer
|
||||
|
||||
Scope:
|
||||
- Normalize tiny AST/CFG patterns into JoinIR instructions without touching MIR or runtime concerns.
|
||||
- Keep pattern-specific lowering isolated (if/return, loop variants, nested-if, read_quoted_from).
|
||||
- Centralize expression/value extraction and small analysis helpers (if-in-loop var tracking).
|
||||
|
||||
Boundaries:
|
||||
- No code generation beyond JoinIR; MIR/VM concerns belong to the bridge layer.
|
||||
- Dev-flagged paths stay opt-in (HAKO_JOINIR_NESTED_IF, HAKO_JOINIR_READ_QUOTED*).
|
||||
- Avoid hard-coded semantics; prefer structural pattern detection and reusable helpers.
|
||||
|
||||
Layout:
|
||||
- `mod.rs`: public surface + entry dispatch + shared counters
|
||||
- `context.rs`: `ExtractCtx` (var ids) helpers
|
||||
- `expr.rs`: expression-to-JoinIR value extraction
|
||||
- `if_return.rs`: simple if→Select lowering
|
||||
- `loop_patterns.rs`: loop variants (simple/break/continue)
|
||||
- `read_quoted.rs`: read_quoted_from lowering
|
||||
- `nested_if.rs`: NestedIfMerge lowering/detection
|
||||
- `analysis.rs`: loop if-var analysis + metadata helpers
|
||||
- `tests.rs`: frontend lowering tests gated by dev flags
|
||||
342
src/mir/join_ir/frontend/ast_lowerer/analysis.rs
Normal file
342
src/mir/join_ir/frontend/ast_lowerer/analysis.rs
Normal file
@ -0,0 +1,342 @@
|
||||
use super::{AstToJoinIrLowerer, HashSet, JoinModule};
|
||||
use crate::mir::join_ir::frontend::func_meta::{JoinFuncMeta, JoinFuncMetaMap};
|
||||
|
||||
impl AstToJoinIrLowerer {
|
||||
/// Phase 40-1で実装予定: ループ内if文の変数追跡
|
||||
///
|
||||
/// # Purpose
|
||||
///
|
||||
/// array_ext.filter等のif-in-loopパターンで、ループ内で修正される変数を
|
||||
/// 追跡し、ループ出口PHI生成に使用する。
|
||||
///
|
||||
/// # Implementation Plan (Phase 40-1)
|
||||
///
|
||||
/// ## Input
|
||||
/// - `loop_body`: ループ本体AST(JSON v0形式)
|
||||
/// - `loop_vars`: ループで使用される変数(Header PHIで定義)
|
||||
///
|
||||
/// ## Output
|
||||
/// - `HashSet<String>`: if分岐内で修正された変数名セット
|
||||
///
|
||||
/// ## Logic
|
||||
/// 1. Recursive AST walk (helper: `extract_assigned_vars_from_body`)
|
||||
/// 2. Detect assignments in if branches only
|
||||
/// 3. Filter for variables in `loop_vars` (loop-carried variables)
|
||||
/// 4. Return set of modified variable names
|
||||
///
|
||||
/// ## Integration Point
|
||||
/// - Call from: `lower_loop_case_a_simple()` or similar loop lowering
|
||||
/// - Use output for: Loop exit PHI generation in `create_exit_function()`
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```nyash,ignore
|
||||
/// local out = new ArrayBox() // loop_vars = {out, i}
|
||||
/// local i = 0
|
||||
/// loop(i < n) {
|
||||
/// if fn(arr[i]) { // ← この中の代入を検出
|
||||
/// out.push(arr[i]) // ← out修正検出
|
||||
/// }
|
||||
/// i = i + 1
|
||||
/// }
|
||||
/// // Result: extract_if_in_loop_modified_vars() = {out}
|
||||
/// // → Loop exit PHI: phi out_exit = (out_header, out_loop_modified)
|
||||
/// ```
|
||||
///
|
||||
/// # Replaces (Phase 40-1削除対象)
|
||||
///
|
||||
/// - `if_phi::collect_assigned_vars()` (32 lines)
|
||||
/// - Current callsites: loop_builder.rs:1069, 1075
|
||||
/// - この関数実装でcallsites削除可能
|
||||
///
|
||||
/// # See Also
|
||||
///
|
||||
/// - Design: `docs/.../phase-39-if-phi-level2/joinir_extension_design.md`
|
||||
/// - A/B test: array_ext.filter (Primary representative function)
|
||||
///
|
||||
/// # TODO(Phase 40-1)
|
||||
///
|
||||
/// ```rust,ignore
|
||||
/// fn extract_if_in_loop_modified_vars(
|
||||
/// &mut self,
|
||||
/// loop_body: &serde_json::Value,
|
||||
/// loop_vars: &HashSet<String>,
|
||||
/// ) -> HashSet<String> {
|
||||
/// // Step 1: Recursive AST walk
|
||||
/// let all_assigned = self.extract_assigned_vars_from_body(loop_body);
|
||||
///
|
||||
/// // Step 2: Filter for if-statement assignments only
|
||||
/// let if_assigned = all_assigned.iter()
|
||||
/// .filter(|var| self.is_assigned_in_if_branch(loop_body, var))
|
||||
/// .cloned()
|
||||
/// .collect::<HashSet<_>>();
|
||||
///
|
||||
/// // Step 3: Filter for loop-carried variables
|
||||
/// if_assigned.intersection(loop_vars).cloned().collect()
|
||||
/// }
|
||||
/// ```
|
||||
#[allow(dead_code)]
|
||||
pub fn extract_if_in_loop_modified_vars(
|
||||
&mut self,
|
||||
loop_body: &serde_json::Value,
|
||||
loop_vars: &HashSet<String>,
|
||||
) -> HashSet<String> {
|
||||
// Step 1: Recursive AST walk to collect all assigned variables
|
||||
let all_assigned = self.extract_assigned_vars_from_body(loop_body);
|
||||
|
||||
// Step 2: Filter for if-statement assignments only
|
||||
let if_assigned = self.extract_if_assigned_vars(loop_body);
|
||||
|
||||
// Step 3: Filter for loop-carried variables
|
||||
// Return intersection of (if_assigned ∩ loop_vars)
|
||||
if_assigned
|
||||
.intersection(loop_vars)
|
||||
.filter(|var| all_assigned.contains(*var))
|
||||
.cloned()
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Phase 40-1: if文内での代入変数抽出
|
||||
///
|
||||
/// # Purpose
|
||||
///
|
||||
/// loop body内のif文でのみ代入される変数を抽出する。
|
||||
/// これはloop exit PHI生成に必要。
|
||||
pub fn extract_if_assigned_vars(
|
||||
&mut self,
|
||||
body: &serde_json::Value,
|
||||
) -> std::collections::HashSet<String> {
|
||||
use std::collections::HashSet;
|
||||
let mut if_assigned = HashSet::new();
|
||||
|
||||
// Handle array of statements
|
||||
if let Some(stmts) = body.as_array() {
|
||||
for stmt in stmts {
|
||||
if stmt.get("type").and_then(|t| t.as_str()) == Some("If") {
|
||||
// Extract assignments from then/else branches
|
||||
if let Some(then_body) = stmt.get("then") {
|
||||
if_assigned.extend(self.extract_assigned_vars_from_body(then_body));
|
||||
}
|
||||
if let Some(else_body) = stmt.get("else") {
|
||||
if_assigned.extend(self.extract_assigned_vars_from_body(else_body));
|
||||
}
|
||||
}
|
||||
// Recursive: nested loops
|
||||
else if stmt.get("type").and_then(|t| t.as_str()) == Some("Loop") {
|
||||
if let Some(loop_body) = stmt.get("body") {
|
||||
if_assigned.extend(self.extract_if_assigned_vars(loop_body));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Handle Block node
|
||||
else if let Some(stmts) = body.get("body").and_then(|b| b.as_array()) {
|
||||
for stmt in stmts {
|
||||
if stmt.get("type").and_then(|t| t.as_str()) == Some("If") {
|
||||
if let Some(then_body) = stmt.get("then") {
|
||||
if_assigned.extend(self.extract_assigned_vars_from_body(then_body));
|
||||
}
|
||||
if let Some(else_body) = stmt.get("else") {
|
||||
if_assigned.extend(self.extract_assigned_vars_from_body(else_body));
|
||||
}
|
||||
} else if stmt.get("type").and_then(|t| t.as_str()) == Some("Loop") {
|
||||
if let Some(loop_body) = stmt.get("body") {
|
||||
if_assigned.extend(self.extract_if_assigned_vars(loop_body));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if_assigned
|
||||
}
|
||||
|
||||
/// Phase 40-1で実装予定: 再帰的AST走査(代入検出)
|
||||
///
|
||||
/// # Purpose
|
||||
///
|
||||
/// AST bodyを再帰的に走査し、代入文を検出する。
|
||||
///
|
||||
/// # Implementation Plan
|
||||
///
|
||||
/// ## Recursive Descent
|
||||
/// - Handle: "Local" assignments (`local x = ...` or `x = ...`)
|
||||
/// - Handle: Nested blocks (`{ ... }`)
|
||||
/// - Handle: If/Loop bodies (recursive call)
|
||||
///
|
||||
/// ## Return
|
||||
/// - `HashSet<String>`: 代入された変数名全て
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```json
|
||||
/// {
|
||||
/// "type": "Block",
|
||||
/// "body": [
|
||||
/// {"type": "Local", "name": "x", "expr": ...}, // x assigned
|
||||
/// {"type": "If", "cond": ..., "then": [
|
||||
/// {"type": "Local", "name": "y", "expr": ...} // y assigned
|
||||
/// ]}
|
||||
/// ]
|
||||
/// }
|
||||
/// ```
|
||||
/// Result: {x, y}
|
||||
///
|
||||
#[allow(dead_code)]
|
||||
pub fn extract_assigned_vars_from_body(
|
||||
&mut self,
|
||||
body: &serde_json::Value,
|
||||
) -> std::collections::HashSet<String> {
|
||||
use std::collections::HashSet;
|
||||
let mut assigned = HashSet::new();
|
||||
|
||||
// Handle array of statements
|
||||
if let Some(stmts) = body.as_array() {
|
||||
for stmt in stmts {
|
||||
self.extract_assigned_vars_from_stmt(stmt, &mut assigned);
|
||||
}
|
||||
}
|
||||
// Handle single statement (Block node)
|
||||
else if let Some(stmts) = body.get("body").and_then(|b| b.as_array()) {
|
||||
for stmt in stmts {
|
||||
self.extract_assigned_vars_from_stmt(stmt, &mut assigned);
|
||||
}
|
||||
}
|
||||
|
||||
assigned
|
||||
}
|
||||
|
||||
/// Phase 40-1: 再帰的AST走査ヘルパー(単一文処理)
|
||||
///
|
||||
/// # Purpose
|
||||
///
|
||||
/// 単一のAST文を処理し、代入された変数を収集する。
|
||||
pub(crate) fn extract_assigned_vars_from_stmt(
|
||||
&mut self,
|
||||
stmt: &serde_json::Value,
|
||||
assigned: &mut std::collections::HashSet<String>,
|
||||
) {
|
||||
match stmt.get("type").and_then(|t| t.as_str()) {
|
||||
Some("Local") => {
|
||||
// local x = ... または x = ...
|
||||
if let Some(name) = stmt.get("name").and_then(|n| n.as_str()) {
|
||||
assigned.insert(name.to_string());
|
||||
}
|
||||
}
|
||||
Some("If") => {
|
||||
// if 文の then/else 分岐を再帰処理
|
||||
if let Some(then_body) = stmt.get("then") {
|
||||
assigned.extend(self.extract_assigned_vars_from_body(then_body));
|
||||
}
|
||||
if let Some(else_body) = stmt.get("else") {
|
||||
assigned.extend(self.extract_assigned_vars_from_body(else_body));
|
||||
}
|
||||
}
|
||||
Some("Loop") => {
|
||||
// loop 文の body を再帰処理
|
||||
if let Some(loop_body) = stmt.get("body") {
|
||||
assigned.extend(self.extract_assigned_vars_from_body(loop_body));
|
||||
}
|
||||
}
|
||||
Some("Block") => {
|
||||
// { ... } ブロックの body を再帰処理
|
||||
if let Some(block_body) = stmt.get("body") {
|
||||
assigned.extend(self.extract_assigned_vars_from_body(block_body));
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
// その他の文は無視(Return, Call, etc.)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Phase 40-1実験用: array_ext.filter パターン専用lowering
|
||||
///
|
||||
/// 通常の lower_loop_case_a_simple() に加えて、
|
||||
/// if-in-loop modified varsをJoinFuncMetaMapとして返す。
|
||||
///
|
||||
/// # Returns
|
||||
/// - `JoinModule`: 通常のJoinIR module
|
||||
/// - `JoinFuncMetaMap`: loop_step関数のif_modified_vars情報
|
||||
///
|
||||
/// # Phase 40-1専用
|
||||
/// この関数はPhase 40-1 A/Bテスト専用。
|
||||
/// 本番パスでは使わない(従来のlower_loop_case_a_simple()を使う)。
|
||||
#[allow(dead_code)]
|
||||
pub fn lower_loop_with_if_meta(
|
||||
&mut self,
|
||||
program_json: &serde_json::Value,
|
||||
) -> (JoinModule, JoinFuncMetaMap) {
|
||||
// 1. 通常のJoinModule生成(既存ロジック流用)
|
||||
let module = self.lower_loop_case_a_simple(program_json);
|
||||
|
||||
// 2. loop body ASTからif-in-loop modified varsを抽出
|
||||
let loop_body = self.extract_loop_body_from_program(program_json);
|
||||
let loop_vars = self.get_loop_carried_vars_from_program(program_json);
|
||||
let if_modified = self.extract_if_in_loop_modified_vars(&loop_body, &loop_vars);
|
||||
|
||||
// 3. JoinFuncMetaMap組み立て
|
||||
// loop_step関数のIDを特定(通常は2番目の関数、名前に"loop_step"を含む)
|
||||
let mut meta_map = JoinFuncMetaMap::new();
|
||||
if let Some((loop_step_id, _)) = module
|
||||
.functions
|
||||
.iter()
|
||||
.find(|(_, func)| func.name.contains("loop_step"))
|
||||
{
|
||||
meta_map.insert(
|
||||
*loop_step_id,
|
||||
JoinFuncMeta {
|
||||
if_modified_vars: if if_modified.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(if_modified)
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
(module, meta_map)
|
||||
}
|
||||
|
||||
/// loop body ASTを抽出するヘルパー
|
||||
///
|
||||
/// # Purpose
|
||||
/// Program → defs[0] → body → Loop statement → body のパスでloop bodyを取得
|
||||
pub fn extract_loop_body_from_program(
|
||||
&self,
|
||||
program_json: &serde_json::Value,
|
||||
) -> serde_json::Value {
|
||||
// Program → defs[0] → body → Loop statement → body
|
||||
program_json["defs"][0]["body"]
|
||||
.as_array()
|
||||
.and_then(|stmts| stmts.iter().find(|s| s["type"] == "Loop"))
|
||||
.and_then(|loop_stmt| loop_stmt.get("body"))
|
||||
.cloned()
|
||||
.unwrap_or(serde_json::Value::Null)
|
||||
}
|
||||
|
||||
/// loop-carried varsを抽出するヘルパー
|
||||
///
|
||||
/// # Purpose
|
||||
/// loop前に宣言された変数を収集(簡易実装)
|
||||
/// より正確な実装はPhase 40-2で拡張
|
||||
pub fn get_loop_carried_vars_from_program(
|
||||
&self,
|
||||
program_json: &serde_json::Value,
|
||||
) -> HashSet<String> {
|
||||
let mut vars = HashSet::new();
|
||||
if let Some(body) = program_json["defs"][0]["body"].as_array() {
|
||||
for stmt in body {
|
||||
if stmt["type"] == "Local" {
|
||||
if let Some(name) = stmt["name"].as_str() {
|
||||
vars.insert(name.to_string());
|
||||
}
|
||||
}
|
||||
if stmt["type"] == "Loop" {
|
||||
break; // loop以降は無視
|
||||
}
|
||||
}
|
||||
}
|
||||
vars
|
||||
}
|
||||
}
|
||||
39
src/mir/join_ir/frontend/ast_lowerer/context.rs
Normal file
39
src/mir/join_ir/frontend/ast_lowerer/context.rs
Normal file
@ -0,0 +1,39 @@
|
||||
use super::VarId;
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
/// Phase 34-5: 式から値を抽出する際のコンテキスト
|
||||
///
|
||||
/// extract_value ヘルパ関数で使用する内部状態
|
||||
pub(crate) struct ExtractCtx {
|
||||
/// 次に使える ValueId カウンタ
|
||||
pub(crate) next_var_id: u32,
|
||||
/// 変数名 → ValueId のマップ(パラメータなど)
|
||||
pub(crate) var_map: BTreeMap<String, VarId>,
|
||||
}
|
||||
|
||||
impl ExtractCtx {
|
||||
/// 新しいコンテキストを作成
|
||||
pub(crate) fn new(start_var_id: u32) -> Self {
|
||||
Self {
|
||||
next_var_id: start_var_id,
|
||||
var_map: BTreeMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// パラメータを登録
|
||||
pub(crate) fn register_param(&mut self, name: String, var_id: VarId) {
|
||||
self.var_map.insert(name, var_id);
|
||||
}
|
||||
|
||||
/// 新しい ValueId を割り当て
|
||||
pub(crate) fn alloc_var(&mut self) -> VarId {
|
||||
let id = crate::mir::ValueId(self.next_var_id);
|
||||
self.next_var_id += 1;
|
||||
id
|
||||
}
|
||||
|
||||
/// 変数名から ValueId を取得
|
||||
pub(crate) fn get_var(&self, name: &str) -> Option<VarId> {
|
||||
self.var_map.get(name).copied()
|
||||
}
|
||||
}
|
||||
193
src/mir/join_ir/frontend/ast_lowerer/expr.rs
Normal file
193
src/mir/join_ir/frontend/ast_lowerer/expr.rs
Normal file
@ -0,0 +1,193 @@
|
||||
use super::{AstToJoinIrLowerer, BinOpKind, CompareOp, ConstValue, ExtractCtx, JoinInst, VarId};
|
||||
|
||||
impl AstToJoinIrLowerer {
|
||||
/// Phase 34-5: expr から「値を計算する JoinIR」と「結果を入れる ValueId」を返す
|
||||
///
|
||||
/// ## 設計方針
|
||||
///
|
||||
/// - **Int literal**: 新しい dst を割り当てて Const 命令を生成
|
||||
/// - **Var 参照**: ctx の var_map から既存 ValueId を引く(追加命令なし)
|
||||
/// - **Method 呼び出し**: pattern match のみ実装(JoinIR 出力はダミーでも可)
|
||||
///
|
||||
/// ## 戻り値
|
||||
///
|
||||
/// - `ValueId`: 結果が入る変数 ID
|
||||
/// - `Vec<JoinInst>`: 値を計算するための JoinIR 命令列
|
||||
///
|
||||
/// ## Phase 34-5 実装範囲
|
||||
///
|
||||
/// - **段階 1**: Int / Var 対応(確実に実装)
|
||||
/// - **段階 2**: Method 呼び出し pattern match(ダミー可)
|
||||
///
|
||||
/// ## Panics
|
||||
///
|
||||
/// - 未対応の expr 形式(Phase 34-5 は tiny テスト専用)
|
||||
#[allow(dead_code)] // Phase 34-5.4 で lower_if_return_pattern から呼ばれる
|
||||
pub(super) fn extract_value(
|
||||
&self,
|
||||
expr: &serde_json::Value,
|
||||
ctx: &mut ExtractCtx,
|
||||
) -> (VarId, Vec<JoinInst>) {
|
||||
let expr_type = expr["type"].as_str().expect("expr must have 'type' field");
|
||||
|
||||
match expr_type {
|
||||
// 段階 1: Int literal 対応
|
||||
"Int" => {
|
||||
let value = expr["value"].as_i64().expect("Int value must be i64");
|
||||
|
||||
let dst = ctx.alloc_var();
|
||||
let inst = JoinInst::Compute(crate::mir::join_ir::MirLikeInst::Const {
|
||||
dst,
|
||||
value: ConstValue::Integer(value),
|
||||
});
|
||||
|
||||
(dst, vec![inst])
|
||||
}
|
||||
|
||||
// Phase 34-8: Bool literal 対応
|
||||
"Bool" => {
|
||||
let value = expr["value"].as_bool().expect("Bool value must be boolean");
|
||||
|
||||
let dst = ctx.alloc_var();
|
||||
let inst = JoinInst::Compute(crate::mir::join_ir::MirLikeInst::Const {
|
||||
dst,
|
||||
value: ConstValue::Bool(value),
|
||||
});
|
||||
|
||||
(dst, vec![inst])
|
||||
}
|
||||
|
||||
// 段階 1: Var 参照対応
|
||||
"Var" => {
|
||||
let var_name = expr["name"].as_str().expect("Var must have 'name' field");
|
||||
|
||||
let var_id = ctx
|
||||
.get_var(var_name)
|
||||
.unwrap_or_else(|| panic!("Undefined variable: {}", var_name));
|
||||
|
||||
// Var 参照は追加命令なし(既存の ValueId を返すだけ)
|
||||
(var_id, vec![])
|
||||
}
|
||||
|
||||
// Phase 34-6: Method 呼び出し構造の完全実装
|
||||
"Method" => {
|
||||
// receiver.method(args...) の構造を抽出
|
||||
let receiver_expr = &expr["receiver"];
|
||||
let method_name = expr["method"]
|
||||
.as_str()
|
||||
.expect("Method must have 'method' field");
|
||||
let args_array = expr["args"]
|
||||
.as_array()
|
||||
.expect("Method must have 'args' array");
|
||||
|
||||
// receiver を extract_value で処理
|
||||
let (receiver_var, receiver_insts) = self.extract_value(receiver_expr, ctx);
|
||||
|
||||
// args を extract_value で処理
|
||||
let mut arg_vars = Vec::new();
|
||||
let mut arg_insts = Vec::new();
|
||||
for arg_expr in args_array {
|
||||
let (arg_var, arg_inst) = self.extract_value(arg_expr, ctx);
|
||||
arg_vars.push(arg_var);
|
||||
arg_insts.extend(arg_inst);
|
||||
}
|
||||
|
||||
// MethodCall 命令を生成
|
||||
let dst = ctx.alloc_var();
|
||||
let method_call_inst = JoinInst::MethodCall {
|
||||
dst,
|
||||
receiver: receiver_var,
|
||||
method: method_name.to_string(),
|
||||
args: arg_vars,
|
||||
};
|
||||
|
||||
// すべての命令を結合(receiver → args → MethodCall の順)
|
||||
let mut insts = receiver_insts;
|
||||
insts.extend(arg_insts);
|
||||
insts.push(method_call_inst);
|
||||
|
||||
(dst, insts)
|
||||
}
|
||||
|
||||
// Phase 34-7.4a: Binary 演算対応(i + 1 など)
|
||||
"Binary" => {
|
||||
let op_str = expr["op"].as_str().expect("Binary must have 'op' field");
|
||||
let lhs_expr = &expr["lhs"];
|
||||
let rhs_expr = &expr["rhs"];
|
||||
|
||||
// op 文字列を BinOpKind に変換
|
||||
let op = match op_str {
|
||||
"+" => BinOpKind::Add,
|
||||
"-" => BinOpKind::Sub,
|
||||
"*" => BinOpKind::Mul,
|
||||
"/" => BinOpKind::Div,
|
||||
_ => panic!("Unsupported binary op: {}", op_str),
|
||||
};
|
||||
|
||||
// lhs と rhs を再帰的に extract_value
|
||||
let (lhs_var, lhs_insts) = self.extract_value(lhs_expr, ctx);
|
||||
let (rhs_var, rhs_insts) = self.extract_value(rhs_expr, ctx);
|
||||
|
||||
// 結果変数を割り当て
|
||||
let dst = ctx.alloc_var();
|
||||
|
||||
// BinOp 命令を生成
|
||||
let binop_inst = JoinInst::Compute(crate::mir::join_ir::MirLikeInst::BinOp {
|
||||
dst,
|
||||
op,
|
||||
lhs: lhs_var,
|
||||
rhs: rhs_var,
|
||||
});
|
||||
|
||||
// すべての命令を結合(lhs → rhs → BinOp の順)
|
||||
let mut insts = lhs_insts;
|
||||
insts.extend(rhs_insts);
|
||||
insts.push(binop_inst);
|
||||
|
||||
(dst, insts)
|
||||
}
|
||||
|
||||
// Phase 34-7.4a: Compare 演算対応(i < n など)
|
||||
"Compare" => {
|
||||
let op_str = expr["op"].as_str().expect("Compare must have 'op' field");
|
||||
let lhs_expr = &expr["lhs"];
|
||||
let rhs_expr = &expr["rhs"];
|
||||
|
||||
// op 文字列を CompareOp に変換
|
||||
let op = match op_str {
|
||||
"<" => CompareOp::Lt,
|
||||
"<=" => CompareOp::Le,
|
||||
">" => CompareOp::Gt,
|
||||
">=" => CompareOp::Ge,
|
||||
"==" => CompareOp::Eq,
|
||||
"!=" => CompareOp::Ne,
|
||||
_ => panic!("Unsupported compare op: {}", op_str),
|
||||
};
|
||||
|
||||
// lhs と rhs を再帰的に extract_value
|
||||
let (lhs_var, lhs_insts) = self.extract_value(lhs_expr, ctx);
|
||||
let (rhs_var, rhs_insts) = self.extract_value(rhs_expr, ctx);
|
||||
|
||||
// 結果変数を割り当て
|
||||
let dst = ctx.alloc_var();
|
||||
|
||||
// Compare 命令を生成
|
||||
let compare_inst = JoinInst::Compute(crate::mir::join_ir::MirLikeInst::Compare {
|
||||
dst,
|
||||
op,
|
||||
lhs: lhs_var,
|
||||
rhs: rhs_var,
|
||||
});
|
||||
|
||||
// すべての命令を結合(lhs → rhs → Compare の順)
|
||||
let mut insts = lhs_insts;
|
||||
insts.extend(rhs_insts);
|
||||
insts.push(compare_inst);
|
||||
|
||||
(dst, insts)
|
||||
}
|
||||
|
||||
_ => panic!("Unsupported expr type: {}", expr_type),
|
||||
}
|
||||
}
|
||||
}
|
||||
135
src/mir/join_ir/frontend/ast_lowerer/if_return.rs
Normal file
135
src/mir/join_ir/frontend/ast_lowerer/if_return.rs
Normal file
@ -0,0 +1,135 @@
|
||||
use super::{AstToJoinIrLowerer, BTreeMap, ExtractCtx, JoinFunction, JoinInst, JoinModule};
|
||||
|
||||
impl AstToJoinIrLowerer {
|
||||
/// If Return pattern の共通 lowering
|
||||
///
|
||||
/// Phase 34-2/34-3/34-4: simple/local/json_shape 対応
|
||||
/// Phase 34-5: extract_value ベースに統一(Int/Var/Method 構造まで)
|
||||
///
|
||||
/// - simple: `if cond { return 10 } else { return 20 }`
|
||||
/// - local: `if cond { x=10 } else { x=20 }; return x` (意味論的)
|
||||
/// - json_shape: `if at { return v.substring(0, at) } else { return v }` (Var/Method)
|
||||
///
|
||||
/// すべて同じ JoinIR Select に正規化される
|
||||
pub(super) fn lower_if_return_pattern(
|
||||
&mut self,
|
||||
program_json: &serde_json::Value,
|
||||
) -> JoinModule {
|
||||
// 1. Program(JSON) から defs を取得
|
||||
let defs = program_json["defs"]
|
||||
.as_array()
|
||||
.expect("Program(JSON v0) must have 'defs' array");
|
||||
|
||||
// 2. 最初の関数定義を取得
|
||||
let func_def = defs
|
||||
.get(0)
|
||||
.expect("At least one function definition required");
|
||||
|
||||
let func_name = func_def["name"]
|
||||
.as_str()
|
||||
.expect("Function must have 'name'");
|
||||
|
||||
let params = func_def["params"]
|
||||
.as_array()
|
||||
.expect("Function must have 'params' array");
|
||||
|
||||
// 3. body 内の If statement を検索(Phase 34-2/34-3 共通)
|
||||
let body = &func_def["body"]["body"];
|
||||
let if_stmt = body
|
||||
.as_array()
|
||||
.and_then(|stmts| stmts.get(0))
|
||||
.expect("Function body must have at least one statement");
|
||||
|
||||
assert_eq!(
|
||||
if_stmt["type"].as_str(),
|
||||
Some("If"),
|
||||
"First statement must be If"
|
||||
);
|
||||
|
||||
// 4. then/else の Return から値を抽出
|
||||
let then_stmts = if_stmt["then"]
|
||||
.as_array()
|
||||
.expect("If must have 'then' array");
|
||||
let else_stmts = if_stmt["else"]
|
||||
.as_array()
|
||||
.expect("If must have 'else' array (simple pattern)");
|
||||
|
||||
let then_ret = then_stmts.get(0).expect("then branch must have Return");
|
||||
let else_ret = else_stmts.get(0).expect("else branch must have Return");
|
||||
|
||||
assert_eq!(
|
||||
then_ret["type"].as_str(),
|
||||
Some("Return"),
|
||||
"then branch must be Return"
|
||||
);
|
||||
assert_eq!(
|
||||
else_ret["type"].as_str(),
|
||||
Some("Return"),
|
||||
"else branch must be Return"
|
||||
);
|
||||
|
||||
// Phase 34-5: extract_value ベースの新実装
|
||||
// 5. ExtractCtx を作成し、パラメータを登録
|
||||
let func_id = self.next_func_id();
|
||||
|
||||
let mut ctx = ExtractCtx::new(params.len() as u32);
|
||||
|
||||
// パラメータを ExtractCtx に登録(cond, at など)
|
||||
for (i, param) in params.iter().enumerate() {
|
||||
let param_name = param
|
||||
.as_str()
|
||||
.expect("Parameter must be string")
|
||||
.to_string();
|
||||
ctx.register_param(param_name, crate::mir::ValueId(i as u32));
|
||||
}
|
||||
|
||||
// Phase 34-6: cond/then/else の expr を extract_value で処理
|
||||
let (cond_var, cond_insts) = self.extract_value(&if_stmt["cond"], &mut ctx);
|
||||
let (then_var, then_insts) = self.extract_value(&then_ret["expr"], &mut ctx);
|
||||
let (else_var, else_insts) = self.extract_value(&else_ret["expr"], &mut ctx);
|
||||
|
||||
// 7. Select 結果変数を割り当て
|
||||
let result_var = ctx.alloc_var();
|
||||
|
||||
// 8. JoinIR 命令列を組み立て(cond → then → else → Select の順)
|
||||
let mut insts = Vec::new();
|
||||
|
||||
// cond の計算命令を先頭に追加
|
||||
insts.extend(cond_insts);
|
||||
|
||||
// then/else の計算命令を追加
|
||||
insts.extend(then_insts);
|
||||
insts.extend(else_insts);
|
||||
|
||||
// Select: result = Select(cond, then_var, else_var)
|
||||
insts.push(JoinInst::Select {
|
||||
dst: result_var,
|
||||
cond: cond_var,
|
||||
then_val: then_var,
|
||||
else_val: else_var,
|
||||
});
|
||||
|
||||
// Ret result
|
||||
insts.push(JoinInst::Ret {
|
||||
value: Some(result_var),
|
||||
});
|
||||
|
||||
let func = JoinFunction {
|
||||
id: func_id,
|
||||
name: func_name.to_string(),
|
||||
params: (0..params.len())
|
||||
.map(|i| crate::mir::ValueId(i as u32))
|
||||
.collect(),
|
||||
body: insts,
|
||||
exit_cont: None, // Phase 34-2/34-3: ルート関数なので exit_cont は None
|
||||
};
|
||||
|
||||
let mut functions = BTreeMap::new();
|
||||
functions.insert(func_id, func);
|
||||
|
||||
JoinModule {
|
||||
functions,
|
||||
entry: Some(func_id),
|
||||
}
|
||||
}
|
||||
}
|
||||
821
src/mir/join_ir/frontend/ast_lowerer/loop_patterns.rs
Normal file
821
src/mir/join_ir/frontend/ast_lowerer/loop_patterns.rs
Normal file
@ -0,0 +1,821 @@
|
||||
use super::BTreeMap;
|
||||
use super::{AstToJoinIrLowerer, ConstValue, ExtractCtx, JoinFunction, JoinInst, JoinModule};
|
||||
|
||||
impl AstToJoinIrLowerer {
|
||||
/// Phase 34-8: Break/Continue 付きループの lowering(パターン検出)
|
||||
///
|
||||
/// Loop body を解析して Break/Continue を検出し、適切な lowering 関数にディスパッチ
|
||||
pub(super) fn lower_loop_with_break_continue(
|
||||
&mut self,
|
||||
program_json: &serde_json::Value,
|
||||
) -> JoinModule {
|
||||
// 1. Program(JSON) から defs を取得
|
||||
let defs = program_json["defs"]
|
||||
.as_array()
|
||||
.expect("Program(JSON v0) must have 'defs' array");
|
||||
|
||||
let func_def = defs
|
||||
.get(0)
|
||||
.expect("At least one function definition required");
|
||||
|
||||
// 2. body を解析: Local 初期化 + Loop + Return
|
||||
let body = &func_def["body"]["body"];
|
||||
let stmts = body.as_array().expect("Function body must be array");
|
||||
|
||||
// Loop ノードを探す
|
||||
let loop_node = stmts
|
||||
.iter()
|
||||
.find(|stmt| stmt["type"].as_str() == Some("Loop"))
|
||||
.expect("Loop node not found");
|
||||
|
||||
let loop_body = loop_node["body"]
|
||||
.as_array()
|
||||
.expect("Loop must have 'body' array");
|
||||
|
||||
// Break/Continue を検出
|
||||
let has_break = Self::has_break_in_loop_body(loop_body);
|
||||
let has_continue = Self::has_continue_in_loop_body(loop_body);
|
||||
|
||||
// パターンに応じてディスパッチ
|
||||
if has_break && !has_continue {
|
||||
self.lower_loop_break_pattern(program_json)
|
||||
} else if has_continue && !has_break {
|
||||
self.lower_loop_continue_pattern(program_json)
|
||||
} else if has_break && has_continue {
|
||||
panic!("Mixed Break/Continue pattern not yet supported in Phase 34-8");
|
||||
} else {
|
||||
// Phase 34-7 の simple pattern
|
||||
self.lower_loop_case_a_simple(program_json)
|
||||
}
|
||||
}
|
||||
|
||||
/// Loop body に Break があるかチェック
|
||||
pub(crate) fn has_break_in_loop_body(loop_body: &[serde_json::Value]) -> bool {
|
||||
loop_body.iter().any(|stmt| {
|
||||
if stmt["type"].as_str() == Some("If") {
|
||||
if let Some(then_body) = stmt["then"].as_array() {
|
||||
then_body
|
||||
.iter()
|
||||
.any(|s| s["type"].as_str() == Some("Break"))
|
||||
} else {
|
||||
false
|
||||
}
|
||||
} else {
|
||||
false
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Loop body に Continue があるかチェック
|
||||
pub(crate) fn has_continue_in_loop_body(loop_body: &[serde_json::Value]) -> bool {
|
||||
loop_body.iter().any(|stmt| {
|
||||
if stmt["type"].as_str() == Some("If") {
|
||||
if let Some(then_body) = stmt["then"].as_array() {
|
||||
then_body
|
||||
.iter()
|
||||
.any(|s| s["type"].as_str() == Some("Continue"))
|
||||
} else {
|
||||
false
|
||||
}
|
||||
} else {
|
||||
false
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Phase 34-7: Loop pattern の lowering(Case-A: tiny while loop)
|
||||
///
|
||||
/// 対象: `LoopFrontendTest.simple(n)` 相当
|
||||
/// - local i = 0; local acc = 0;
|
||||
/// - loop(i < n) { acc = acc + 1; i = i + 1; }
|
||||
/// - return acc
|
||||
///
|
||||
/// 目標 JoinIR: Phase 31 の LoopToJoinLowerer と同型
|
||||
/// - entry: 初期化 → loop_step 呼び出し
|
||||
/// - loop_step: 条件チェック → 再帰 or k_exit
|
||||
/// - k_exit: 結果を return
|
||||
///
|
||||
/// Phase 34-7.4b: Local ノード処理(同名再宣言 = 再代入)
|
||||
/// ループ本体lowering(Phase 34実装)
|
||||
///
|
||||
/// # Phase 40拡張予定
|
||||
///
|
||||
/// この関数に以下を追加:
|
||||
/// 1. `extract_if_in_loop_modified_vars()`呼び出し
|
||||
/// 2. 検出された変数をloop exit PHI生成に使用
|
||||
///
|
||||
/// ```rust,ignore
|
||||
/// // TODO(Phase 40-1): Add if-in-loop variable tracking
|
||||
/// // let loop_vars = self.get_loop_carried_vars(&loop_ctx);
|
||||
/// // let modified_in_if = self.extract_if_in_loop_modified_vars(&loop_body, &loop_vars);
|
||||
/// // Pass modified_in_if to create_exit_function() for PHI generation
|
||||
/// ```
|
||||
pub(super) fn lower_loop_case_a_simple(
|
||||
&mut self,
|
||||
program_json: &serde_json::Value,
|
||||
) -> JoinModule {
|
||||
// TODO(Phase 40-1): Add if-in-loop variable tracking here
|
||||
// Integration point for extract_if_in_loop_modified_vars()
|
||||
|
||||
// 1. Program(JSON) から defs を取得
|
||||
let defs = program_json["defs"]
|
||||
.as_array()
|
||||
.expect("Program(JSON v0) must have 'defs' array");
|
||||
|
||||
let func_def = defs
|
||||
.get(0)
|
||||
.expect("At least one function definition required");
|
||||
|
||||
let func_name = func_def["name"]
|
||||
.as_str()
|
||||
.expect("Function must have 'name'");
|
||||
|
||||
let params = func_def["params"]
|
||||
.as_array()
|
||||
.expect("Function must have 'params' array");
|
||||
|
||||
// 2. ExtractCtx 作成とパラメータ登録
|
||||
let mut ctx = ExtractCtx::new(params.len() as u32);
|
||||
for (i, param) in params.iter().enumerate() {
|
||||
let param_name = param
|
||||
.as_str()
|
||||
.expect("Parameter must be string")
|
||||
.to_string();
|
||||
ctx.register_param(param_name, crate::mir::ValueId(i as u32));
|
||||
}
|
||||
|
||||
// 3. body を解析: Local 初期化 + Loop + Return
|
||||
let body = &func_def["body"]["body"];
|
||||
let stmts = body.as_array().expect("Function body must be array");
|
||||
|
||||
// Phase 34-7.4b: Local ノード処理(初期化)
|
||||
// stmts[0]: Local i = 0
|
||||
// stmts[1]: Local acc = 0
|
||||
// stmts[2]: Loop { cond, body }
|
||||
// stmts[3]: Return acc
|
||||
|
||||
let mut init_insts = Vec::new();
|
||||
let mut loop_node_idx = None;
|
||||
|
||||
for (idx, stmt) in stmts.iter().enumerate() {
|
||||
let stmt_type = stmt["type"].as_str().expect("Statement must have type");
|
||||
|
||||
match stmt_type {
|
||||
"Local" => {
|
||||
// Phase 34-7.4b: Local ノード処理
|
||||
let var_name = stmt["name"]
|
||||
.as_str()
|
||||
.expect("Local must have 'name'")
|
||||
.to_string();
|
||||
let expr = &stmt["expr"];
|
||||
|
||||
// extract_value で式を評価
|
||||
let (var_id, insts) = self.extract_value(expr, &mut ctx);
|
||||
init_insts.extend(insts);
|
||||
|
||||
// 同名再宣言 = var_map を更新(再代入の意味論)
|
||||
ctx.register_param(var_name, var_id);
|
||||
}
|
||||
"Loop" => {
|
||||
loop_node_idx = Some(idx);
|
||||
break; // Loop 以降は別処理
|
||||
}
|
||||
_ => panic!("Unexpected statement type before Loop: {}", stmt_type),
|
||||
}
|
||||
}
|
||||
|
||||
let loop_node_idx = loop_node_idx.expect("Loop node not found");
|
||||
let loop_node = &stmts[loop_node_idx];
|
||||
|
||||
// 4. Loop の cond と body を抽出
|
||||
let loop_cond_expr = &loop_node["cond"];
|
||||
let loop_body_stmts = loop_node["body"]
|
||||
.as_array()
|
||||
.expect("Loop must have 'body' array");
|
||||
|
||||
// 5. Return 文を抽出(Loop の次)
|
||||
let return_stmt = &stmts[loop_node_idx + 1];
|
||||
assert_eq!(
|
||||
return_stmt["type"].as_str(),
|
||||
Some("Return"),
|
||||
"Expected Return after Loop"
|
||||
);
|
||||
|
||||
// 6. JoinIR 生成: entry / loop_step / k_exit(Phase 31 と同じパターン)
|
||||
//
|
||||
// Phase 34-7: Jump = 早期 return、Call = 末尾再帰
|
||||
// - entry: 初期化 → Call(loop_step)
|
||||
// - loop_step:
|
||||
// Jump(k_exit, cond=!(i<n)) // 条件が true なら抜ける
|
||||
// body 処理
|
||||
// Call(loop_step) 末尾再帰
|
||||
// - k_exit: 結果を返す
|
||||
|
||||
let entry_id = self.next_func_id();
|
||||
let loop_step_id = self.next_func_id();
|
||||
let k_exit_id = self.next_func_id();
|
||||
|
||||
// entry 関数: 初期化命令 + Call(loop_step)
|
||||
let i_init = ctx.get_var("i").expect("i must be initialized");
|
||||
let acc_init = ctx.get_var("acc").expect("acc must be initialized");
|
||||
let n_param = ctx.get_var("n").expect("n must be parameter");
|
||||
|
||||
let loop_result = ctx.alloc_var();
|
||||
|
||||
let mut entry_body = init_insts;
|
||||
entry_body.push(JoinInst::Call {
|
||||
func: loop_step_id,
|
||||
args: vec![i_init, acc_init, n_param],
|
||||
k_next: None,
|
||||
dst: Some(loop_result),
|
||||
});
|
||||
entry_body.push(JoinInst::Ret {
|
||||
value: Some(loop_result),
|
||||
});
|
||||
|
||||
let entry_func = JoinFunction {
|
||||
id: entry_id,
|
||||
name: func_name.to_string(),
|
||||
params: (0..params.len())
|
||||
.map(|i| crate::mir::ValueId(i as u32))
|
||||
.collect(),
|
||||
body: entry_body,
|
||||
exit_cont: None,
|
||||
};
|
||||
|
||||
// loop_step 関数: (i, acc, n) → Jump(k_exit, cond=!(i<n)) → body → Call(loop_step)
|
||||
let step_i = crate::mir::ValueId(0);
|
||||
let step_acc = crate::mir::ValueId(1);
|
||||
let step_n = crate::mir::ValueId(2);
|
||||
|
||||
let mut step_ctx = ExtractCtx::new(3);
|
||||
step_ctx.register_param("i".to_string(), step_i);
|
||||
step_ctx.register_param("acc".to_string(), step_acc);
|
||||
step_ctx.register_param("n".to_string(), step_n);
|
||||
|
||||
// 条件式を評価(i < n)
|
||||
let (cond_var, cond_insts) = self.extract_value(loop_cond_expr, &mut step_ctx);
|
||||
|
||||
// !cond を計算(i >= n なら抜ける)
|
||||
let false_const = step_ctx.alloc_var();
|
||||
let exit_cond = step_ctx.alloc_var();
|
||||
|
||||
let mut loop_step_body = cond_insts;
|
||||
loop_step_body.push(JoinInst::Compute(crate::mir::join_ir::MirLikeInst::Const {
|
||||
dst: false_const,
|
||||
value: ConstValue::Bool(false),
|
||||
}));
|
||||
loop_step_body.push(JoinInst::Compute(
|
||||
crate::mir::join_ir::MirLikeInst::Compare {
|
||||
dst: exit_cond,
|
||||
op: crate::mir::join_ir::CompareOp::Eq,
|
||||
lhs: cond_var,
|
||||
rhs: false_const,
|
||||
},
|
||||
));
|
||||
|
||||
// 早期 return: exit_cond が true(i >= n)なら k_exit へ Jump
|
||||
loop_step_body.push(JoinInst::Jump {
|
||||
cont: k_exit_id.as_cont(), // Phase 34-7.5: 型変換ヘルパー使用
|
||||
args: vec![step_acc],
|
||||
cond: Some(exit_cond),
|
||||
});
|
||||
|
||||
// loop body を処理(Jump で抜けなかった場合のみ実行される)
|
||||
for body_stmt in loop_body_stmts {
|
||||
assert_eq!(
|
||||
body_stmt["type"].as_str(),
|
||||
Some("Local"),
|
||||
"Loop body must contain only Local statements"
|
||||
);
|
||||
|
||||
let var_name = body_stmt["name"]
|
||||
.as_str()
|
||||
.expect("Local must have 'name'")
|
||||
.to_string();
|
||||
let expr = &body_stmt["expr"];
|
||||
|
||||
let (var_id, insts) = self.extract_value(expr, &mut step_ctx);
|
||||
loop_step_body.extend(insts);
|
||||
step_ctx.register_param(var_name, var_id);
|
||||
}
|
||||
|
||||
// body 処理後の i_next, acc_next を取得
|
||||
let i_next = step_ctx
|
||||
.get_var("i")
|
||||
.expect("i must be updated in loop body");
|
||||
let acc_next = step_ctx
|
||||
.get_var("acc")
|
||||
.expect("acc must be updated in loop body");
|
||||
|
||||
// loop_step を再帰的に Call(末尾再帰)
|
||||
let recurse_result = step_ctx.alloc_var();
|
||||
loop_step_body.push(JoinInst::Call {
|
||||
func: loop_step_id,
|
||||
args: vec![i_next, acc_next, step_n],
|
||||
k_next: None,
|
||||
dst: Some(recurse_result),
|
||||
});
|
||||
|
||||
loop_step_body.push(JoinInst::Ret {
|
||||
value: Some(recurse_result),
|
||||
});
|
||||
|
||||
let loop_step_func = JoinFunction {
|
||||
id: loop_step_id,
|
||||
name: format!("{}_loop_step", func_name),
|
||||
params: vec![step_i, step_acc, step_n],
|
||||
body: loop_step_body,
|
||||
exit_cont: None,
|
||||
};
|
||||
|
||||
// k_exit 関数: (acc) → Ret acc
|
||||
let k_exit_acc = crate::mir::ValueId(0);
|
||||
|
||||
let k_exit_func = JoinFunction {
|
||||
id: k_exit_id,
|
||||
name: format!("{}_k_exit", func_name),
|
||||
params: vec![k_exit_acc],
|
||||
body: vec![JoinInst::Ret {
|
||||
value: Some(k_exit_acc),
|
||||
}],
|
||||
exit_cont: None,
|
||||
};
|
||||
|
||||
// JoinModule を構築
|
||||
let mut functions = BTreeMap::new();
|
||||
functions.insert(entry_id, entry_func);
|
||||
functions.insert(loop_step_id, loop_step_func);
|
||||
functions.insert(k_exit_id, k_exit_func);
|
||||
|
||||
JoinModule {
|
||||
functions,
|
||||
entry: Some(entry_id),
|
||||
}
|
||||
}
|
||||
|
||||
/// Phase 34-8: Break pattern の lowering
|
||||
///
|
||||
/// パターン: `loop { if i >= n { break }; acc = acc + i; i = i + 1 }`
|
||||
/// 目標: Jump(k_exit, cond=i>=n) で早期 return を実現
|
||||
pub(super) fn lower_loop_break_pattern(
|
||||
&mut self,
|
||||
program_json: &serde_json::Value,
|
||||
) -> JoinModule {
|
||||
// 1. Program(JSON) から基本情報を取得(Phase 34-7 と同じ)
|
||||
let defs = program_json["defs"]
|
||||
.as_array()
|
||||
.expect("Program(JSON v0) must have 'defs' array");
|
||||
|
||||
let func_def = defs
|
||||
.get(0)
|
||||
.expect("At least one function definition required");
|
||||
|
||||
let func_name = func_def["name"]
|
||||
.as_str()
|
||||
.expect("Function must have 'name'");
|
||||
|
||||
let params = func_def["params"]
|
||||
.as_array()
|
||||
.expect("Function must have 'params' array");
|
||||
|
||||
// 2. ExtractCtx 作成とパラメータ登録
|
||||
let mut ctx = ExtractCtx::new(params.len() as u32);
|
||||
for (i, param) in params.iter().enumerate() {
|
||||
let param_name = param
|
||||
.as_str()
|
||||
.expect("Parameter must be string")
|
||||
.to_string();
|
||||
ctx.register_param(param_name, crate::mir::ValueId(i as u32));
|
||||
}
|
||||
|
||||
// 3. body を解析: Local 初期化 + Loop + Return
|
||||
let body = &func_def["body"]["body"];
|
||||
let stmts = body.as_array().expect("Function body must be array");
|
||||
|
||||
// Local ノード処理(初期化)
|
||||
let mut init_insts = Vec::new();
|
||||
let mut loop_node_idx = None;
|
||||
|
||||
for (idx, stmt) in stmts.iter().enumerate() {
|
||||
let stmt_type = stmt["type"].as_str().expect("Statement must have type");
|
||||
|
||||
match stmt_type {
|
||||
"Local" => {
|
||||
let var_name = stmt["name"]
|
||||
.as_str()
|
||||
.expect("Local must have 'name'")
|
||||
.to_string();
|
||||
let expr = &stmt["expr"];
|
||||
|
||||
let (var_id, insts) = self.extract_value(expr, &mut ctx);
|
||||
init_insts.extend(insts);
|
||||
ctx.register_param(var_name, var_id);
|
||||
}
|
||||
"Loop" => {
|
||||
loop_node_idx = Some(idx);
|
||||
break;
|
||||
}
|
||||
_ => panic!("Unexpected statement type before Loop: {}", stmt_type),
|
||||
}
|
||||
}
|
||||
|
||||
let loop_node_idx = loop_node_idx.expect("Loop node not found");
|
||||
let loop_node = &stmts[loop_node_idx];
|
||||
|
||||
// 4. Loop body から Break If を探す
|
||||
let loop_body = loop_node["body"]
|
||||
.as_array()
|
||||
.expect("Loop must have 'body' array");
|
||||
|
||||
let break_if_stmt = loop_body
|
||||
.iter()
|
||||
.find(|stmt| {
|
||||
stmt["type"].as_str() == Some("If")
|
||||
&& stmt["then"].as_array().map_or(false, |then| {
|
||||
then.iter().any(|s| s["type"].as_str() == Some("Break"))
|
||||
})
|
||||
})
|
||||
.expect("Break pattern must have If + Break");
|
||||
|
||||
let break_cond_expr = &break_if_stmt["cond"];
|
||||
|
||||
// 5. JoinIR 生成: entry / loop_step / k_exit(3関数構造)
|
||||
let entry_id = self.next_func_id();
|
||||
let loop_step_id = self.next_func_id();
|
||||
let k_exit_id = self.next_func_id();
|
||||
|
||||
// entry 関数
|
||||
let i_init = ctx.get_var("i").expect("i must be initialized");
|
||||
let acc_init = ctx.get_var("acc").expect("acc must be initialized");
|
||||
let n_param = ctx.get_var("n").expect("n must be parameter");
|
||||
|
||||
let loop_result = ctx.alloc_var();
|
||||
|
||||
let mut entry_body = init_insts;
|
||||
entry_body.push(JoinInst::Call {
|
||||
func: loop_step_id,
|
||||
args: vec![i_init, acc_init, n_param],
|
||||
k_next: None,
|
||||
dst: Some(loop_result),
|
||||
});
|
||||
entry_body.push(JoinInst::Ret {
|
||||
value: Some(loop_result),
|
||||
});
|
||||
|
||||
let entry_func = JoinFunction {
|
||||
id: entry_id,
|
||||
name: func_name.to_string(),
|
||||
params: (0..params.len())
|
||||
.map(|i| crate::mir::ValueId(i as u32))
|
||||
.collect(),
|
||||
body: entry_body,
|
||||
exit_cont: None,
|
||||
};
|
||||
|
||||
// loop_step 関数: (i, acc, n) → Jump(k_exit, cond=break_cond) → body → Call(loop_step)
|
||||
let step_i = crate::mir::ValueId(0);
|
||||
let step_acc = crate::mir::ValueId(1);
|
||||
let step_n = crate::mir::ValueId(2);
|
||||
|
||||
let mut step_ctx = ExtractCtx::new(3);
|
||||
step_ctx.register_param("i".to_string(), step_i);
|
||||
step_ctx.register_param("acc".to_string(), step_acc);
|
||||
step_ctx.register_param("n".to_string(), step_n);
|
||||
|
||||
// Break 条件を評価
|
||||
let (break_cond_var, break_cond_insts) = self.extract_value(break_cond_expr, &mut step_ctx);
|
||||
|
||||
let mut loop_step_body = break_cond_insts;
|
||||
|
||||
// 早期 return: break_cond が true なら k_exit へ Jump
|
||||
loop_step_body.push(JoinInst::Jump {
|
||||
cont: k_exit_id.as_cont(),
|
||||
args: vec![step_acc],
|
||||
cond: Some(break_cond_var),
|
||||
});
|
||||
|
||||
// loop body を処理(Break の後の Local 命令群)
|
||||
for body_stmt in loop_body {
|
||||
// If + Break はスキップ(Jump で処理済み)
|
||||
if body_stmt["type"].as_str() == Some("If") {
|
||||
continue;
|
||||
}
|
||||
|
||||
assert_eq!(
|
||||
body_stmt["type"].as_str(),
|
||||
Some("Local"),
|
||||
"Loop body must contain only Local statements after If"
|
||||
);
|
||||
|
||||
let var_name = body_stmt["name"]
|
||||
.as_str()
|
||||
.expect("Local must have 'name'")
|
||||
.to_string();
|
||||
let expr = &body_stmt["expr"];
|
||||
|
||||
let (var_id, insts) = self.extract_value(expr, &mut step_ctx);
|
||||
loop_step_body.extend(insts);
|
||||
step_ctx.register_param(var_name, var_id);
|
||||
}
|
||||
|
||||
// body 処理後の i_next, acc_next を取得
|
||||
let i_next = step_ctx
|
||||
.get_var("i")
|
||||
.expect("i must be updated in loop body");
|
||||
let acc_next = step_ctx
|
||||
.get_var("acc")
|
||||
.expect("acc must be updated in loop body");
|
||||
|
||||
// loop_step を再帰的に Call(末尾再帰)
|
||||
let recurse_result = step_ctx.alloc_var();
|
||||
loop_step_body.push(JoinInst::Call {
|
||||
func: loop_step_id,
|
||||
args: vec![i_next, acc_next, step_n],
|
||||
k_next: None,
|
||||
dst: Some(recurse_result),
|
||||
});
|
||||
|
||||
loop_step_body.push(JoinInst::Ret {
|
||||
value: Some(recurse_result),
|
||||
});
|
||||
|
||||
let loop_step_func = JoinFunction {
|
||||
id: loop_step_id,
|
||||
name: format!("{}_loop_step", func_name),
|
||||
params: vec![step_i, step_acc, step_n],
|
||||
body: loop_step_body,
|
||||
exit_cont: None,
|
||||
};
|
||||
|
||||
// k_exit 関数
|
||||
let k_exit_acc = crate::mir::ValueId(0);
|
||||
|
||||
let k_exit_func = JoinFunction {
|
||||
id: k_exit_id,
|
||||
name: format!("{}_k_exit", func_name),
|
||||
params: vec![k_exit_acc],
|
||||
body: vec![JoinInst::Ret {
|
||||
value: Some(k_exit_acc),
|
||||
}],
|
||||
exit_cont: None,
|
||||
};
|
||||
|
||||
// JoinModule を構築
|
||||
let mut functions = BTreeMap::new();
|
||||
functions.insert(entry_id, entry_func);
|
||||
functions.insert(loop_step_id, loop_step_func);
|
||||
functions.insert(k_exit_id, k_exit_func);
|
||||
|
||||
JoinModule {
|
||||
functions,
|
||||
entry: Some(entry_id),
|
||||
}
|
||||
}
|
||||
|
||||
/// Phase 34-8: Continue pattern の lowering
|
||||
///
|
||||
/// パターン: `loop { i = i + 1; if i == 3 { continue }; acc = acc + i }`
|
||||
/// 目標: Select(条件付き値更新)+ Call(末尾再帰)で実現
|
||||
pub(super) fn lower_loop_continue_pattern(
|
||||
&mut self,
|
||||
program_json: &serde_json::Value,
|
||||
) -> JoinModule {
|
||||
// 1. Program(JSON) から基本情報を取得
|
||||
let defs = program_json["defs"]
|
||||
.as_array()
|
||||
.expect("Program(JSON v0) must have 'defs' array");
|
||||
|
||||
let func_def = defs
|
||||
.get(0)
|
||||
.expect("At least one function definition required");
|
||||
|
||||
let func_name = func_def["name"]
|
||||
.as_str()
|
||||
.expect("Function must have 'name'");
|
||||
|
||||
let params = func_def["params"]
|
||||
.as_array()
|
||||
.expect("Function must have 'params' array");
|
||||
|
||||
// 2. ExtractCtx 作成とパラメータ登録
|
||||
let mut ctx = ExtractCtx::new(params.len() as u32);
|
||||
for (i, param) in params.iter().enumerate() {
|
||||
let param_name = param
|
||||
.as_str()
|
||||
.expect("Parameter must be string")
|
||||
.to_string();
|
||||
ctx.register_param(param_name, crate::mir::ValueId(i as u32));
|
||||
}
|
||||
|
||||
// 3. body を解析: Local 初期化 + Loop + Return
|
||||
let body = &func_def["body"]["body"];
|
||||
let stmts = body.as_array().expect("Function body must be array");
|
||||
|
||||
// Local ノード処理(初期化)
|
||||
let mut init_insts = Vec::new();
|
||||
let mut loop_node_idx = None;
|
||||
|
||||
for (idx, stmt) in stmts.iter().enumerate() {
|
||||
let stmt_type = stmt["type"].as_str().expect("Statement must have type");
|
||||
|
||||
match stmt_type {
|
||||
"Local" => {
|
||||
let var_name = stmt["name"]
|
||||
.as_str()
|
||||
.expect("Local must have 'name'")
|
||||
.to_string();
|
||||
let expr = &stmt["expr"];
|
||||
|
||||
let (var_id, insts) = self.extract_value(expr, &mut ctx);
|
||||
init_insts.extend(insts);
|
||||
ctx.register_param(var_name, var_id);
|
||||
}
|
||||
"Loop" => {
|
||||
loop_node_idx = Some(idx);
|
||||
break;
|
||||
}
|
||||
_ => panic!("Unexpected statement type before Loop: {}", stmt_type),
|
||||
}
|
||||
}
|
||||
|
||||
let loop_node_idx = loop_node_idx.expect("Loop node not found");
|
||||
let loop_node = &stmts[loop_node_idx];
|
||||
|
||||
// 4. Loop の cond を取得
|
||||
let loop_cond_expr = &loop_node["cond"];
|
||||
|
||||
// 5. Loop body から Continue If を探す
|
||||
let loop_body = loop_node["body"]
|
||||
.as_array()
|
||||
.expect("Loop must have 'body' array");
|
||||
|
||||
let continue_if_stmt = loop_body
|
||||
.iter()
|
||||
.find(|stmt| {
|
||||
stmt["type"].as_str() == Some("If")
|
||||
&& stmt["then"].as_array().map_or(false, |then| {
|
||||
then.iter().any(|s| s["type"].as_str() == Some("Continue"))
|
||||
})
|
||||
})
|
||||
.expect("Continue pattern must have If + Continue");
|
||||
|
||||
let continue_cond_expr = &continue_if_stmt["cond"];
|
||||
|
||||
// 6. JoinIR 生成: entry / loop_step / k_exit(3関数構造)
|
||||
let entry_id = self.next_func_id();
|
||||
let loop_step_id = self.next_func_id();
|
||||
let k_exit_id = self.next_func_id();
|
||||
|
||||
// entry 関数
|
||||
let i_init = ctx.get_var("i").expect("i must be initialized");
|
||||
let acc_init = ctx.get_var("acc").expect("acc must be initialized");
|
||||
let n_param = ctx.get_var("n").expect("n must be parameter");
|
||||
|
||||
let loop_result = ctx.alloc_var();
|
||||
|
||||
let mut entry_body = init_insts;
|
||||
entry_body.push(JoinInst::Call {
|
||||
func: loop_step_id,
|
||||
args: vec![i_init, acc_init, n_param],
|
||||
k_next: None,
|
||||
dst: Some(loop_result),
|
||||
});
|
||||
entry_body.push(JoinInst::Ret {
|
||||
value: Some(loop_result),
|
||||
});
|
||||
|
||||
let entry_func = JoinFunction {
|
||||
id: entry_id,
|
||||
name: func_name.to_string(),
|
||||
params: (0..params.len())
|
||||
.map(|i| crate::mir::ValueId(i as u32))
|
||||
.collect(),
|
||||
body: entry_body,
|
||||
exit_cont: None,
|
||||
};
|
||||
|
||||
// loop_step 関数: (i, acc, n) → exit check → i++ → Select → Call(loop_step)
|
||||
let step_i = crate::mir::ValueId(0);
|
||||
let step_acc = crate::mir::ValueId(1);
|
||||
let step_n = crate::mir::ValueId(2);
|
||||
|
||||
let mut step_ctx = ExtractCtx::new(3);
|
||||
step_ctx.register_param("i".to_string(), step_i);
|
||||
step_ctx.register_param("acc".to_string(), step_acc);
|
||||
step_ctx.register_param("n".to_string(), step_n);
|
||||
|
||||
let mut loop_step_body = Vec::new();
|
||||
|
||||
// 1. exit 条件チェック(!(i < n) = i >= n で抜ける)
|
||||
let (cond_var, cond_insts) = self.extract_value(loop_cond_expr, &mut step_ctx);
|
||||
loop_step_body.extend(cond_insts);
|
||||
|
||||
let false_const = step_ctx.alloc_var();
|
||||
let exit_cond = step_ctx.alloc_var();
|
||||
|
||||
loop_step_body.push(JoinInst::Compute(crate::mir::join_ir::MirLikeInst::Const {
|
||||
dst: false_const,
|
||||
value: ConstValue::Bool(false),
|
||||
}));
|
||||
loop_step_body.push(JoinInst::Compute(
|
||||
crate::mir::join_ir::MirLikeInst::Compare {
|
||||
dst: exit_cond,
|
||||
op: crate::mir::join_ir::CompareOp::Eq,
|
||||
lhs: cond_var,
|
||||
rhs: false_const,
|
||||
},
|
||||
));
|
||||
|
||||
loop_step_body.push(JoinInst::Jump {
|
||||
cont: k_exit_id.as_cont(),
|
||||
args: vec![step_acc],
|
||||
cond: Some(exit_cond),
|
||||
});
|
||||
|
||||
// 2. Continue pattern 特有の処理: i のインクリメントが先
|
||||
// Loop body の最初の Local(i) を処理
|
||||
let first_local = loop_body
|
||||
.iter()
|
||||
.find(|stmt| {
|
||||
stmt["type"].as_str() == Some("Local") && stmt["name"].as_str() == Some("i")
|
||||
})
|
||||
.expect("Continue pattern must have i increment as first Local");
|
||||
|
||||
let i_expr = &first_local["expr"];
|
||||
let (i_next, i_insts) = self.extract_value(i_expr, &mut step_ctx);
|
||||
loop_step_body.extend(i_insts);
|
||||
step_ctx.register_param("i".to_string(), i_next);
|
||||
|
||||
// 3. Continue 条件を評価
|
||||
let (continue_cond_var, continue_cond_insts) =
|
||||
self.extract_value(continue_cond_expr, &mut step_ctx);
|
||||
loop_step_body.extend(continue_cond_insts);
|
||||
|
||||
// 4. acc の更新値を計算(If の後の Local(acc) から)
|
||||
let acc_update_local = loop_body
|
||||
.iter()
|
||||
.find(|stmt| {
|
||||
stmt["type"].as_str() == Some("Local") && stmt["name"].as_str() == Some("acc")
|
||||
})
|
||||
.expect("Continue pattern must have acc update Local");
|
||||
|
||||
let acc_expr = &acc_update_local["expr"];
|
||||
let (acc_increment, acc_insts) = self.extract_value(acc_expr, &mut step_ctx);
|
||||
loop_step_body.extend(acc_insts);
|
||||
|
||||
// 5. Select: Continue なら acc そのまま、そうでなければ acc の更新値
|
||||
let acc_next = step_ctx.alloc_var();
|
||||
loop_step_body.push(JoinInst::Select {
|
||||
dst: acc_next,
|
||||
cond: continue_cond_var,
|
||||
then_val: step_acc, // Continue: 更新しない
|
||||
else_val: acc_increment, // 通常: 更新
|
||||
});
|
||||
|
||||
// 6. 末尾再帰
|
||||
let recurse_result = step_ctx.alloc_var();
|
||||
loop_step_body.push(JoinInst::Call {
|
||||
func: loop_step_id,
|
||||
args: vec![i_next, acc_next, step_n],
|
||||
k_next: None,
|
||||
dst: Some(recurse_result),
|
||||
});
|
||||
|
||||
loop_step_body.push(JoinInst::Ret {
|
||||
value: Some(recurse_result),
|
||||
});
|
||||
|
||||
let loop_step_func = JoinFunction {
|
||||
id: loop_step_id,
|
||||
name: format!("{}_loop_step", func_name),
|
||||
params: vec![step_i, step_acc, step_n],
|
||||
body: loop_step_body,
|
||||
exit_cont: None,
|
||||
};
|
||||
|
||||
// k_exit 関数
|
||||
let k_exit_acc = crate::mir::ValueId(0);
|
||||
|
||||
let k_exit_func = JoinFunction {
|
||||
id: k_exit_id,
|
||||
name: format!("{}_k_exit", func_name),
|
||||
params: vec![k_exit_acc],
|
||||
body: vec![JoinInst::Ret {
|
||||
value: Some(k_exit_acc),
|
||||
}],
|
||||
exit_cont: None,
|
||||
};
|
||||
|
||||
// JoinModule を構築
|
||||
let mut functions = BTreeMap::new();
|
||||
functions.insert(entry_id, entry_func);
|
||||
functions.insert(loop_step_id, loop_step_func);
|
||||
functions.insert(k_exit_id, k_exit_func);
|
||||
|
||||
JoinModule {
|
||||
functions,
|
||||
entry: Some(entry_id),
|
||||
}
|
||||
}
|
||||
}
|
||||
137
src/mir/join_ir/frontend/ast_lowerer/mod.rs
Normal file
137
src/mir/join_ir/frontend/ast_lowerer/mod.rs
Normal file
@ -0,0 +1,137 @@
|
||||
//! AST/CFG → JoinIR Lowering
|
||||
//!
|
||||
//! このモジュールは AST/CFG ノードを JoinIR 命令に変換する。
|
||||
//!
|
||||
//! ## 責務
|
||||
//!
|
||||
//! - **If 文→Select/IfMerge 変換**: 条件分岐を JoinIR の継続渡しスタイルに変換
|
||||
//! - **Loop 文→loop_step/k_exit 変換**: ループを関数呼び出しと継続に正規化
|
||||
//! - **Break/Continue/Return→k_* 変換**: 制御フローを継続 ID として表現
|
||||
//!
|
||||
//! ## Phase 34-2 での実装スコープ
|
||||
//!
|
||||
//! 最初は `IfSelectTest.*` 相当の tiny ケースのみ対応:
|
||||
//! - Simple pattern: `if cond { return 1 } else { return 2 }`
|
||||
//!
|
||||
//! ## 設計原則
|
||||
//!
|
||||
//! - **JoinIR = PHI 生成器**: 既存 PHI の変換器にはしない(Phase 33-10 原則)
|
||||
//! - **段階的移行**: 既存 MIR Builder 経路は保持、新経路はデフォルト OFF
|
||||
//! - **A/B テスト可能**: 既存経路と新経路の両方で実行して比較検証
|
||||
|
||||
pub(crate) use crate::mir::join_ir::{
|
||||
BinOpKind, CompareOp, ConstValue, JoinFuncId, JoinFunction, JoinInst, JoinModule, MergePair,
|
||||
VarId,
|
||||
};
|
||||
pub(crate) use std::collections::{BTreeMap, HashSet};
|
||||
|
||||
mod analysis;
|
||||
mod context;
|
||||
mod expr;
|
||||
mod if_return;
|
||||
mod loop_patterns;
|
||||
mod nested_if;
|
||||
mod read_quoted;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
||||
pub(crate) use context::ExtractCtx;
|
||||
|
||||
/// AST/CFG → JoinIR 変換器
|
||||
///
|
||||
/// Phase 34-2: Program(JSON v0) から tiny IfSelect ケースを JoinIR に変換
|
||||
pub struct AstToJoinIrLowerer {
|
||||
pub(crate) next_func_id: u32,
|
||||
#[allow(dead_code)]
|
||||
pub(crate) next_var_id: u32,
|
||||
}
|
||||
|
||||
impl AstToJoinIrLowerer {
|
||||
/// 新しい lowerer を作成
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
next_func_id: 0,
|
||||
next_var_id: 0,
|
||||
}
|
||||
}
|
||||
|
||||
/// Program(JSON v0) → JoinModule
|
||||
///
|
||||
/// Phase 34-2/34-3/34-4: simple/local/json_shape pattern に対応
|
||||
/// Phase 34-5: extract_value 統一化(Int/Var/Method 構造まで)
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// - パターンに合わない Program(JSON) が来た場合(Phase 34 は tiny テスト専用)
|
||||
/// - ループ・複数変数・副作用付き if(Phase 34-6 以降で対応予定)
|
||||
pub fn lower_program_json(&mut self, program_json: &serde_json::Value) -> JoinModule {
|
||||
// 1. Program(JSON) から defs を取得
|
||||
let defs = program_json["defs"]
|
||||
.as_array()
|
||||
.expect("Program(JSON v0) must have 'defs' array");
|
||||
|
||||
// 2. 最初の関数定義を取得
|
||||
let func_def = defs
|
||||
.get(0)
|
||||
.expect("At least one function definition required");
|
||||
|
||||
let func_name = func_def["name"]
|
||||
.as_str()
|
||||
.expect("Function must have 'name'");
|
||||
|
||||
// 3. 関数名で分岐(Phase 34-2/34-3/34-4/34-5/34-7/34-8/41-4/45)
|
||||
// test/local/_read_value_from_pair: If Return pattern
|
||||
// simple: Loop pattern (Phase 34-7/34-8)
|
||||
// parse_loop: Phase 41-4 NestedIfMerge pattern
|
||||
// read_quoted_from: Phase 45 Guard if + Loop with break + accumulator
|
||||
match func_name {
|
||||
"test" | "local" | "_read_value_from_pair" => {
|
||||
self.lower_if_return_pattern(program_json)
|
||||
}
|
||||
"simple" => {
|
||||
// Phase 34-8: Loop パターンの詳細分析(break/continue 検出)
|
||||
self.lower_loop_with_break_continue(program_json)
|
||||
}
|
||||
"parse_loop" => {
|
||||
// Phase 41-4: NestedIfMerge pattern (dev flag gated)
|
||||
// Guard: HAKO_JOINIR_NESTED_IF=1 を要求
|
||||
if std::env::var("HAKO_JOINIR_NESTED_IF").ok().as_deref() == Some("1") {
|
||||
self.lower_nested_if_pattern(program_json)
|
||||
} else {
|
||||
// Dev flag が OFF の場合は panic(旧ルートにフォールバックするため)
|
||||
panic!(
|
||||
"parse_loop NestedIfMerge requires HAKO_JOINIR_NESTED_IF=1. \
|
||||
Set env var to enable Phase 41-4 route."
|
||||
);
|
||||
}
|
||||
}
|
||||
"read_quoted_from" => {
|
||||
// Phase 45: read_quoted_from pattern (dev flag gated)
|
||||
// Guard if + Loop with break + accumulator
|
||||
if std::env::var("HAKO_JOINIR_READ_QUOTED").ok().as_deref() == Some("1") {
|
||||
self.lower_read_quoted_pattern(program_json)
|
||||
} else {
|
||||
panic!(
|
||||
"read_quoted_from JoinIR requires HAKO_JOINIR_READ_QUOTED=1. \
|
||||
Set env var to enable Phase 45 route."
|
||||
);
|
||||
}
|
||||
}
|
||||
_ => panic!("Unsupported function: {}", func_name),
|
||||
}
|
||||
}
|
||||
|
||||
/// 次の関数 ID を生成
|
||||
pub(crate) fn next_func_id(&mut self) -> JoinFuncId {
|
||||
let id = JoinFuncId::new(self.next_func_id);
|
||||
self.next_func_id += 1;
|
||||
id
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for AstToJoinIrLowerer {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
297
src/mir/join_ir/frontend/ast_lowerer/nested_if.rs
Normal file
297
src/mir/join_ir/frontend/ast_lowerer/nested_if.rs
Normal file
@ -0,0 +1,297 @@
|
||||
use super::BTreeMap;
|
||||
use super::{AstToJoinIrLowerer, ExtractCtx, JoinFunction, JoinInst, JoinModule};
|
||||
|
||||
impl AstToJoinIrLowerer {
|
||||
/// Phase 41-4.2: ネスト if パターンの lowering
|
||||
///
|
||||
/// # Purpose
|
||||
///
|
||||
/// 深いネスト if(3-4レベル)を `NestedIfMerge` 命令に変換する。
|
||||
/// 対象: `ParserControlBox.parse_loop()` 関数
|
||||
///
|
||||
/// # Pattern
|
||||
///
|
||||
/// ```nyash,ignore
|
||||
/// // Level 0 (outer)
|
||||
/// if cond0 {
|
||||
/// // Level 1
|
||||
/// if cond1 {
|
||||
/// // Level 2
|
||||
/// if cond2 {
|
||||
/// // Level 3 (deepest)
|
||||
/// x = new_value
|
||||
/// }
|
||||
/// }
|
||||
/// }
|
||||
/// // At merge point: x has PHI semantics
|
||||
/// ```
|
||||
///
|
||||
/// # Output JoinIR
|
||||
///
|
||||
/// - `NestedIfMerge { conds: [cond0, cond1, cond2], merges: [(x, new_value, old_x)], k_next }`
|
||||
///
|
||||
/// # Dev Flag
|
||||
///
|
||||
/// 環境変数 `HAKO_JOINIR_NESTED_IF=1` が必須。
|
||||
pub fn lower_nested_if_pattern(&mut self, program_json: &serde_json::Value) -> JoinModule {
|
||||
// 1. Program(JSON) から基本情報を取得
|
||||
let defs = program_json["defs"]
|
||||
.as_array()
|
||||
.expect("Program(JSON v0) must have 'defs' array");
|
||||
|
||||
let func_def = defs
|
||||
.get(0)
|
||||
.expect("At least one function definition required");
|
||||
|
||||
let func_name = func_def["name"]
|
||||
.as_str()
|
||||
.expect("Function must have 'name'");
|
||||
|
||||
let params = func_def["params"]
|
||||
.as_array()
|
||||
.expect("Function must have 'params' array");
|
||||
|
||||
// 2. ExtractCtx 作成とパラメータ登録
|
||||
let mut ctx = ExtractCtx::new(params.len() as u32);
|
||||
for (i, param) in params.iter().enumerate() {
|
||||
let param_name = param
|
||||
.as_str()
|
||||
.expect("Parameter must be string")
|
||||
.to_string();
|
||||
ctx.register_param(param_name, crate::mir::ValueId(i as u32));
|
||||
}
|
||||
|
||||
// 3. body を解析
|
||||
let body = &func_def["body"]["body"];
|
||||
let stmts = body.as_array().expect("Function body must be array");
|
||||
|
||||
// 4. nested if パターンを検出
|
||||
let nested_pattern = self.try_match_nested_if_pattern(stmts, &mut ctx);
|
||||
|
||||
// 5. JoinIR 生成
|
||||
let func_id = self.next_func_id();
|
||||
let mut insts = Vec::new();
|
||||
|
||||
// 5.1. Local 初期化を処理(ネスト if の前にある Local 文)
|
||||
for stmt in stmts {
|
||||
let stmt_type = stmt["type"].as_str().unwrap_or("");
|
||||
if stmt_type == "Local" {
|
||||
let var_name = stmt["name"]
|
||||
.as_str()
|
||||
.expect("Local must have 'name'")
|
||||
.to_string();
|
||||
let expr = &stmt["expr"];
|
||||
let (var_id, local_insts) = self.extract_value(expr, &mut ctx);
|
||||
insts.extend(local_insts);
|
||||
ctx.register_param(var_name, var_id);
|
||||
} else if stmt_type == "If" {
|
||||
// If 文でパターン開始
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// 5.2. NestedIfMerge パターンがマッチした場合
|
||||
if let Some(pattern) = nested_pattern {
|
||||
// 条件を評価
|
||||
let mut cond_vars = Vec::new();
|
||||
for cond_expr in &pattern.conds {
|
||||
let (cond_var, cond_insts) = self.extract_value(cond_expr, &mut ctx);
|
||||
insts.extend(cond_insts);
|
||||
cond_vars.push(cond_var);
|
||||
}
|
||||
|
||||
// merges を構築
|
||||
let mut merges = Vec::new();
|
||||
for (var_name, then_expr, else_expr) in &pattern.merges {
|
||||
let (then_var, then_insts) = self.extract_value(then_expr, &mut ctx);
|
||||
insts.extend(then_insts);
|
||||
|
||||
let else_var = if let Some(else_e) = else_expr {
|
||||
let (e_var, e_insts) = self.extract_value(else_e, &mut ctx);
|
||||
insts.extend(e_insts);
|
||||
e_var
|
||||
} else {
|
||||
// else がない場合は既存の変数値を使用
|
||||
ctx.get_var(var_name)
|
||||
.unwrap_or_else(|| panic!("Undefined variable in merge: {}", var_name))
|
||||
};
|
||||
|
||||
// 新しい dst を割り当て、merge 後の値として ctx に登録
|
||||
let dst = ctx.alloc_var();
|
||||
ctx.register_param(var_name.clone(), dst);
|
||||
|
||||
merges.push(crate::mir::join_ir::MergePair {
|
||||
dst,
|
||||
then_val: then_var,
|
||||
else_val: else_var,
|
||||
});
|
||||
}
|
||||
|
||||
// NestedIfMerge 命令を追加
|
||||
insts.push(JoinInst::NestedIfMerge {
|
||||
conds: cond_vars,
|
||||
merges,
|
||||
k_next: None, // 関数末尾なので継続なし
|
||||
});
|
||||
} else {
|
||||
// パターンがマッチしない場合は panic(dev フラグ ON の時のみ到達)
|
||||
panic!(
|
||||
"lower_nested_if_pattern: No nested if pattern found in parse_loop. \
|
||||
Expected 2-4 level nested if structure."
|
||||
);
|
||||
}
|
||||
|
||||
// 5.3. Return 文を処理
|
||||
let return_stmt = stmts.iter().find(|s| s["type"].as_str() == Some("Return"));
|
||||
if let Some(ret) = return_stmt {
|
||||
let (ret_var, ret_insts) = self.extract_value(&ret["expr"], &mut ctx);
|
||||
insts.extend(ret_insts);
|
||||
insts.push(JoinInst::Ret {
|
||||
value: Some(ret_var),
|
||||
});
|
||||
} else {
|
||||
// Return がない場合は void return
|
||||
insts.push(JoinInst::Ret { value: None });
|
||||
}
|
||||
|
||||
// 6. JoinFunction 構築
|
||||
let func = JoinFunction {
|
||||
id: func_id,
|
||||
name: func_name.to_string(),
|
||||
params: (0..params.len())
|
||||
.map(|i| crate::mir::ValueId(i as u32))
|
||||
.collect(),
|
||||
body: insts,
|
||||
exit_cont: None,
|
||||
};
|
||||
|
||||
let mut functions = BTreeMap::new();
|
||||
functions.insert(func_id, func);
|
||||
|
||||
JoinModule {
|
||||
functions,
|
||||
entry: Some(func_id),
|
||||
}
|
||||
}
|
||||
|
||||
/// Phase 41-4.2: ネスト if パターンの検出
|
||||
///
|
||||
/// # Purpose
|
||||
///
|
||||
/// AST から 2-4 レベルのネスト if を検出し、`NestedIfPattern` として返す。
|
||||
///
|
||||
/// # Algorithm
|
||||
///
|
||||
/// 1. 最初の If 文を探す
|
||||
/// 2. then 分岐を再帰的に解析し、ネストレベルをカウント
|
||||
/// 3. 各レベルの条件式と変数代入を収集
|
||||
/// 4. 最大 4 レベルまで対応(それ以上は未サポート)
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// - `Some(NestedIfPattern)`: パターンがマッチした場合
|
||||
/// - `None`: パターンがマッチしない場合
|
||||
pub(crate) fn try_match_nested_if_pattern(
|
||||
&self,
|
||||
stmts: &[serde_json::Value],
|
||||
_ctx: &mut ExtractCtx,
|
||||
) -> Option<NestedIfPattern> {
|
||||
// 1. 最初の If 文を探す
|
||||
let first_if = stmts.iter().find(|s| s["type"].as_str() == Some("If"))?;
|
||||
|
||||
// 2. ネスト構造を再帰的に解析
|
||||
let mut conds = Vec::new();
|
||||
let mut merges = Vec::new();
|
||||
|
||||
self.collect_nested_if_structure(first_if, &mut conds, &mut merges, 0);
|
||||
|
||||
// 3. 少なくとも 2 レベル以上のネストが必要
|
||||
if conds.len() < 2 {
|
||||
return None;
|
||||
}
|
||||
|
||||
Some(NestedIfPattern { conds, merges })
|
||||
}
|
||||
|
||||
/// Phase 41-4.2: ネスト if 構造の再帰収集
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// - `if_stmt`: 現在の If ノード
|
||||
/// - `conds`: 条件式リスト(外側から内側へ)
|
||||
/// - `merges`: 変数代入リスト
|
||||
/// - `depth`: 現在のネストレベル(0から開始)
|
||||
pub(crate) fn collect_nested_if_structure(
|
||||
&self,
|
||||
if_stmt: &serde_json::Value,
|
||||
conds: &mut Vec<serde_json::Value>,
|
||||
merges: &mut Vec<(String, serde_json::Value, Option<serde_json::Value>)>,
|
||||
depth: usize,
|
||||
) {
|
||||
// 最大 4 レベルまで
|
||||
if depth >= 4 {
|
||||
return;
|
||||
}
|
||||
|
||||
// 条件式を追加
|
||||
if let Some(cond) = if_stmt.get("cond") {
|
||||
conds.push(cond.clone());
|
||||
}
|
||||
|
||||
// then 分岐を解析
|
||||
if let Some(then_body) = if_stmt.get("then").and_then(|t| t.as_array()) {
|
||||
for stmt in then_body {
|
||||
let stmt_type = stmt["type"].as_str().unwrap_or("");
|
||||
|
||||
match stmt_type {
|
||||
"If" => {
|
||||
// ネスト if: 再帰処理
|
||||
self.collect_nested_if_structure(stmt, conds, merges, depth + 1);
|
||||
}
|
||||
"Local" => {
|
||||
// 変数代入を記録
|
||||
if let Some(var_name) = stmt["name"].as_str() {
|
||||
let expr = stmt.get("expr").cloned().unwrap_or(serde_json::Value::Null);
|
||||
// then 値のみ記録(else はパターン解析時に決定)
|
||||
merges.push((var_name.to_string(), expr, None));
|
||||
}
|
||||
}
|
||||
"Return" => {
|
||||
// 早期 return は無視(NestedIfMerge では扱わない)
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// else 分岐の代入も収集(存在する場合)
|
||||
if let Some(else_body) = if_stmt.get("else").and_then(|e| e.as_array()) {
|
||||
for stmt in else_body {
|
||||
if stmt["type"].as_str() == Some("Local") {
|
||||
if let Some(var_name) = stmt["name"].as_str() {
|
||||
// 既存の merge エントリを探して else 値を更新
|
||||
for (name, _, else_val) in merges.iter_mut() {
|
||||
if name == var_name {
|
||||
let expr =
|
||||
stmt.get("expr").cloned().unwrap_or(serde_json::Value::Null);
|
||||
*else_val = Some(expr);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Phase 41-4.2: ネスト if パターン構造
|
||||
///
|
||||
/// AST から検出されたネスト if パターンを表現する。
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct NestedIfPattern {
|
||||
/// 条件式リスト(外側から内側へ)
|
||||
pub(crate) conds: Vec<serde_json::Value>,
|
||||
/// 変数代入リスト: (変数名, then値, else値)
|
||||
pub(crate) merges: Vec<(String, serde_json::Value, Option<serde_json::Value>)>,
|
||||
}
|
||||
470
src/mir/join_ir/frontend/ast_lowerer/read_quoted.rs
Normal file
470
src/mir/join_ir/frontend/ast_lowerer/read_quoted.rs
Normal file
@ -0,0 +1,470 @@
|
||||
use super::BTreeMap;
|
||||
use super::{
|
||||
AstToJoinIrLowerer, ConstValue, ExtractCtx, JoinFunction, JoinInst, JoinModule, MergePair,
|
||||
};
|
||||
|
||||
impl AstToJoinIrLowerer {
|
||||
// ========================================
|
||||
// Phase 45: read_quoted_from Pattern Lowering
|
||||
// ========================================
|
||||
|
||||
/// Phase 45: read_quoted_from パターンの lowering
|
||||
///
|
||||
/// # Pattern
|
||||
///
|
||||
/// ```nyash,ignore
|
||||
/// read_quoted_from(s, pos) {
|
||||
/// local i = pos
|
||||
/// if s.substring(i, i+1) != "\"" { return "" } // Guard if
|
||||
/// i = i + 1
|
||||
/// local out = ""
|
||||
/// local n = s.length()
|
||||
/// loop (i < n) {
|
||||
/// local ch = s.substring(i, i+1)
|
||||
/// if ch == "\"" { break } // Found closing quote
|
||||
/// // NOTE: Escape handling (if ch == "\\") has known PHI issue
|
||||
/// // Variable reassignment inside if-block doesn't generate PHI
|
||||
/// // This will be addressed by JoinIR IfMerge improvements
|
||||
/// out = out + ch
|
||||
/// i = i + 1
|
||||
/// }
|
||||
/// return out
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// # JoinIR Output
|
||||
///
|
||||
/// - entry: Guard if check → Select(guard_passed ? Call(loop_step) : Return(""))
|
||||
/// - loop_step: (i, out, n, s) → exit check → break check → body → Call(loop_step)
|
||||
/// - k_exit: (out) → Return(out)
|
||||
///
|
||||
/// # Dev Flag
|
||||
///
|
||||
/// 環境変数 `HAKO_JOINIR_READ_QUOTED=1` が必須。
|
||||
pub(crate) fn lower_read_quoted_pattern(
|
||||
&mut self,
|
||||
program_json: &serde_json::Value,
|
||||
) -> JoinModule {
|
||||
// 1. Program(JSON) から基本情報を取得
|
||||
let defs = program_json["defs"]
|
||||
.as_array()
|
||||
.expect("Program(JSON v0) must have 'defs' array");
|
||||
|
||||
let func_def = defs
|
||||
.get(0)
|
||||
.expect("At least one function definition required");
|
||||
|
||||
let func_name = func_def["name"]
|
||||
.as_str()
|
||||
.expect("Function must have 'name'");
|
||||
|
||||
let params = func_def["params"]
|
||||
.as_array()
|
||||
.expect("Function must have 'params' array");
|
||||
|
||||
// 2. ExtractCtx 作成とパラメータ登録 (s, pos)
|
||||
let mut ctx = ExtractCtx::new(params.len() as u32);
|
||||
for (i, param) in params.iter().enumerate() {
|
||||
let param_name = param
|
||||
.as_str()
|
||||
.expect("Parameter must be string")
|
||||
.to_string();
|
||||
ctx.register_param(param_name, crate::mir::ValueId(i as u32));
|
||||
}
|
||||
|
||||
// 3. body を解析
|
||||
let body = &func_def["body"]["body"];
|
||||
let _stmts = body.as_array().expect("Function body must be array");
|
||||
|
||||
// 4. AST 構造を解析:
|
||||
// - Local i = pos
|
||||
// - If guard { return "" }
|
||||
// - i = i + 1
|
||||
// - local out = ""
|
||||
// - local n = s.length()
|
||||
// - Loop { ... }
|
||||
// - Return out
|
||||
|
||||
// 5. JoinIR 生成: entry / loop_step / k_exit(3関数構造)
|
||||
let entry_id = self.next_func_id();
|
||||
let loop_step_id = self.next_func_id();
|
||||
let k_exit_id = self.next_func_id();
|
||||
|
||||
// ========================================
|
||||
// Entry 関数の構築
|
||||
// ========================================
|
||||
let mut entry_body = Vec::new();
|
||||
|
||||
// パラメータ取得 (s=0, pos=1)
|
||||
let s_param = ctx.get_var("s").expect("s must be parameter");
|
||||
let pos_param = ctx.get_var("pos").expect("pos must be parameter");
|
||||
|
||||
// local i = pos
|
||||
ctx.register_param("i".to_string(), pos_param);
|
||||
let i_var = pos_param;
|
||||
|
||||
// Guard: s.substring(i, i+1) を計算
|
||||
// i+1 を計算
|
||||
let one_const = ctx.alloc_var();
|
||||
entry_body.push(JoinInst::Compute(crate::mir::join_ir::MirLikeInst::Const {
|
||||
dst: one_const,
|
||||
value: ConstValue::Integer(1),
|
||||
}));
|
||||
|
||||
let i_plus_1 = ctx.alloc_var();
|
||||
entry_body.push(JoinInst::Compute(crate::mir::join_ir::MirLikeInst::BinOp {
|
||||
dst: i_plus_1,
|
||||
op: crate::mir::join_ir::BinOpKind::Add,
|
||||
lhs: i_var,
|
||||
rhs: one_const,
|
||||
}));
|
||||
|
||||
// s.substring(i, i+1) を呼び出し
|
||||
let first_char = ctx.alloc_var();
|
||||
entry_body.push(JoinInst::MethodCall {
|
||||
dst: first_char,
|
||||
receiver: s_param,
|
||||
method: "substring".to_string(),
|
||||
args: vec![i_var, i_plus_1],
|
||||
});
|
||||
|
||||
// Guard 条件: first_char != '"'
|
||||
let quote_const = ctx.alloc_var();
|
||||
entry_body.push(JoinInst::Compute(crate::mir::join_ir::MirLikeInst::Const {
|
||||
dst: quote_const,
|
||||
value: ConstValue::String("\"".to_string()),
|
||||
}));
|
||||
|
||||
let guard_cond = ctx.alloc_var();
|
||||
entry_body.push(JoinInst::Compute(
|
||||
crate::mir::join_ir::MirLikeInst::Compare {
|
||||
dst: guard_cond,
|
||||
op: crate::mir::join_ir::CompareOp::Ne,
|
||||
lhs: first_char,
|
||||
rhs: quote_const,
|
||||
},
|
||||
));
|
||||
|
||||
// Guard 失敗時の戻り値: ""
|
||||
let empty_string = ctx.alloc_var();
|
||||
entry_body.push(JoinInst::Compute(crate::mir::join_ir::MirLikeInst::Const {
|
||||
dst: empty_string,
|
||||
value: ConstValue::String("".to_string()),
|
||||
}));
|
||||
|
||||
// Guard 成功時: i = i + 1
|
||||
let i_after_guard = i_plus_1; // 既に計算済み
|
||||
ctx.register_param("i".to_string(), i_after_guard);
|
||||
|
||||
// local out = ""
|
||||
let out_init = ctx.alloc_var();
|
||||
entry_body.push(JoinInst::Compute(crate::mir::join_ir::MirLikeInst::Const {
|
||||
dst: out_init,
|
||||
value: ConstValue::String("".to_string()),
|
||||
}));
|
||||
ctx.register_param("out".to_string(), out_init);
|
||||
|
||||
// local n = s.length()
|
||||
let n_var = ctx.alloc_var();
|
||||
entry_body.push(JoinInst::MethodCall {
|
||||
dst: n_var,
|
||||
receiver: s_param,
|
||||
method: "length".to_string(),
|
||||
args: vec![],
|
||||
});
|
||||
ctx.register_param("n".to_string(), n_var);
|
||||
|
||||
// Guard check → Jump to early return if guard fails
|
||||
// 逆条件で Jump(guard_cond == true なら early return)
|
||||
let k_guard_fail_id = self.next_func_id();
|
||||
|
||||
entry_body.push(JoinInst::Jump {
|
||||
cont: k_guard_fail_id.as_cont(),
|
||||
args: vec![empty_string],
|
||||
cond: Some(guard_cond),
|
||||
});
|
||||
|
||||
// Guard 成功: loop_step を呼び出し
|
||||
let loop_result = ctx.alloc_var();
|
||||
entry_body.push(JoinInst::Call {
|
||||
func: loop_step_id,
|
||||
args: vec![i_after_guard, out_init, n_var, s_param],
|
||||
k_next: None,
|
||||
dst: Some(loop_result),
|
||||
});
|
||||
|
||||
entry_body.push(JoinInst::Ret {
|
||||
value: Some(loop_result),
|
||||
});
|
||||
|
||||
let entry_func = JoinFunction {
|
||||
id: entry_id,
|
||||
name: func_name.to_string(),
|
||||
params: (0..params.len())
|
||||
.map(|i| crate::mir::ValueId(i as u32))
|
||||
.collect(),
|
||||
body: entry_body,
|
||||
exit_cont: None,
|
||||
};
|
||||
|
||||
// ========================================
|
||||
// k_guard_fail 関数(Guard 失敗時 early return)
|
||||
// ========================================
|
||||
let k_guard_fail_result = crate::mir::ValueId(0);
|
||||
let k_guard_fail_func = JoinFunction {
|
||||
id: k_guard_fail_id,
|
||||
name: format!("{}_k_guard_fail", func_name),
|
||||
params: vec![k_guard_fail_result],
|
||||
body: vec![JoinInst::Ret {
|
||||
value: Some(k_guard_fail_result),
|
||||
}],
|
||||
exit_cont: None,
|
||||
};
|
||||
|
||||
// ========================================
|
||||
// loop_step 関数の構築
|
||||
// ========================================
|
||||
// params: (i, out, n, s)
|
||||
let step_i = crate::mir::ValueId(0);
|
||||
let step_out = crate::mir::ValueId(1);
|
||||
let step_n = crate::mir::ValueId(2);
|
||||
let step_s = crate::mir::ValueId(3);
|
||||
|
||||
let mut step_ctx = ExtractCtx::new(4);
|
||||
step_ctx.register_param("i".to_string(), step_i);
|
||||
step_ctx.register_param("out".to_string(), step_out);
|
||||
step_ctx.register_param("n".to_string(), step_n);
|
||||
step_ctx.register_param("s".to_string(), step_s);
|
||||
|
||||
let mut loop_step_body = Vec::new();
|
||||
|
||||
// 1. Exit 条件チェック: !(i < n) = i >= n で抜ける
|
||||
let i_lt_n = step_ctx.alloc_var();
|
||||
loop_step_body.push(JoinInst::Compute(
|
||||
crate::mir::join_ir::MirLikeInst::Compare {
|
||||
dst: i_lt_n,
|
||||
op: crate::mir::join_ir::CompareOp::Lt,
|
||||
lhs: step_i,
|
||||
rhs: step_n,
|
||||
},
|
||||
));
|
||||
|
||||
let false_const = step_ctx.alloc_var();
|
||||
let exit_cond = step_ctx.alloc_var();
|
||||
loop_step_body.push(JoinInst::Compute(crate::mir::join_ir::MirLikeInst::Const {
|
||||
dst: false_const,
|
||||
value: ConstValue::Bool(false),
|
||||
}));
|
||||
loop_step_body.push(JoinInst::Compute(
|
||||
crate::mir::join_ir::MirLikeInst::Compare {
|
||||
dst: exit_cond,
|
||||
op: crate::mir::join_ir::CompareOp::Eq,
|
||||
lhs: i_lt_n,
|
||||
rhs: false_const,
|
||||
},
|
||||
));
|
||||
|
||||
// i >= n なら k_exit へ Jump
|
||||
loop_step_body.push(JoinInst::Jump {
|
||||
cont: k_exit_id.as_cont(),
|
||||
args: vec![step_out],
|
||||
cond: Some(exit_cond),
|
||||
});
|
||||
|
||||
// 2. ch = s.substring(i, i+1)
|
||||
let step_one = step_ctx.alloc_var();
|
||||
loop_step_body.push(JoinInst::Compute(crate::mir::join_ir::MirLikeInst::Const {
|
||||
dst: step_one,
|
||||
value: ConstValue::Integer(1),
|
||||
}));
|
||||
|
||||
let step_i_plus_1 = step_ctx.alloc_var();
|
||||
loop_step_body.push(JoinInst::Compute(crate::mir::join_ir::MirLikeInst::BinOp {
|
||||
dst: step_i_plus_1,
|
||||
op: crate::mir::join_ir::BinOpKind::Add,
|
||||
lhs: step_i,
|
||||
rhs: step_one,
|
||||
}));
|
||||
|
||||
let step_ch = step_ctx.alloc_var();
|
||||
loop_step_body.push(JoinInst::MethodCall {
|
||||
dst: step_ch,
|
||||
receiver: step_s,
|
||||
method: "substring".to_string(),
|
||||
args: vec![step_i, step_i_plus_1],
|
||||
});
|
||||
|
||||
// 3. Break 条件: ch == '"'
|
||||
let step_quote = step_ctx.alloc_var();
|
||||
loop_step_body.push(JoinInst::Compute(crate::mir::join_ir::MirLikeInst::Const {
|
||||
dst: step_quote,
|
||||
value: ConstValue::String("\"".to_string()),
|
||||
}));
|
||||
|
||||
let break_cond = step_ctx.alloc_var();
|
||||
loop_step_body.push(JoinInst::Compute(
|
||||
crate::mir::join_ir::MirLikeInst::Compare {
|
||||
dst: break_cond,
|
||||
op: crate::mir::join_ir::CompareOp::Eq,
|
||||
lhs: step_ch,
|
||||
rhs: step_quote,
|
||||
},
|
||||
));
|
||||
|
||||
// ch == '"' なら k_exit へ Jump (break)
|
||||
loop_step_body.push(JoinInst::Jump {
|
||||
cont: k_exit_id.as_cont(),
|
||||
args: vec![step_out],
|
||||
cond: Some(break_cond),
|
||||
});
|
||||
|
||||
// ========================================
|
||||
// 4. Escape 処理: if ch == "\\" { i = i + 1; ch = s.substring(i, i+1) }
|
||||
// ========================================
|
||||
// Phase 46: IfMerge で if-body 後の値をマージ
|
||||
let enable_escape = crate::mir::join_ir::env_flag_is_1("HAKO_JOINIR_READ_QUOTED_IFMERGE");
|
||||
|
||||
// 条件と then 側の値を事前計算(投機的実行)
|
||||
let step_backslash = step_ctx.alloc_var();
|
||||
loop_step_body.push(JoinInst::Compute(crate::mir::join_ir::MirLikeInst::Const {
|
||||
dst: step_backslash,
|
||||
value: ConstValue::String("\\".to_string()),
|
||||
}));
|
||||
|
||||
let esc_cond = step_ctx.alloc_var();
|
||||
loop_step_body.push(JoinInst::Compute(
|
||||
crate::mir::join_ir::MirLikeInst::Compare {
|
||||
dst: esc_cond,
|
||||
op: crate::mir::join_ir::CompareOp::Eq,
|
||||
lhs: step_ch,
|
||||
rhs: step_backslash,
|
||||
},
|
||||
));
|
||||
|
||||
// i_esc = i + 1(then 側の i 値)
|
||||
let i_esc = step_ctx.alloc_var();
|
||||
loop_step_body.push(JoinInst::Compute(crate::mir::join_ir::MirLikeInst::BinOp {
|
||||
dst: i_esc,
|
||||
op: crate::mir::join_ir::BinOpKind::Add,
|
||||
lhs: step_i,
|
||||
rhs: step_one,
|
||||
}));
|
||||
|
||||
// i_esc_plus_1 = i_esc + 1(substring の end 引数用)
|
||||
let i_esc_plus_1 = step_ctx.alloc_var();
|
||||
loop_step_body.push(JoinInst::Compute(crate::mir::join_ir::MirLikeInst::BinOp {
|
||||
dst: i_esc_plus_1,
|
||||
op: crate::mir::join_ir::BinOpKind::Add,
|
||||
lhs: i_esc,
|
||||
rhs: step_one,
|
||||
}));
|
||||
|
||||
// ch_esc = s.substring(i_esc, i_esc+1)(then 側の ch 値)
|
||||
let ch_esc = step_ctx.alloc_var();
|
||||
loop_step_body.push(JoinInst::MethodCall {
|
||||
dst: ch_esc,
|
||||
receiver: step_s,
|
||||
method: "substring".to_string(),
|
||||
args: vec![i_esc, i_esc_plus_1],
|
||||
});
|
||||
|
||||
// IfMerge: if-body 後の i と ch をマージ
|
||||
let (i_after_esc, ch_merged) = if enable_escape {
|
||||
let i_after_esc = step_ctx.alloc_var();
|
||||
let ch_merged = step_ctx.alloc_var();
|
||||
|
||||
loop_step_body.push(JoinInst::IfMerge {
|
||||
cond: esc_cond,
|
||||
merges: vec![
|
||||
MergePair {
|
||||
dst: i_after_esc,
|
||||
then_val: i_esc,
|
||||
else_val: step_i,
|
||||
},
|
||||
MergePair {
|
||||
dst: ch_merged,
|
||||
then_val: ch_esc,
|
||||
else_val: step_ch,
|
||||
},
|
||||
],
|
||||
k_next: None,
|
||||
});
|
||||
|
||||
(i_after_esc, ch_merged)
|
||||
} else {
|
||||
// 旧パス: escape 未対応(step_i と step_ch をそのまま使う)
|
||||
(step_i, step_ch)
|
||||
};
|
||||
|
||||
// ========================================
|
||||
// 5. Accumulator: out = out + ch_merged
|
||||
// ========================================
|
||||
let out_next = step_ctx.alloc_var();
|
||||
loop_step_body.push(JoinInst::Compute(crate::mir::join_ir::MirLikeInst::BinOp {
|
||||
dst: out_next,
|
||||
op: crate::mir::join_ir::BinOpKind::Add, // String concatenation
|
||||
lhs: step_out,
|
||||
rhs: ch_merged, // ← ch_merged を使う!
|
||||
}));
|
||||
|
||||
// ========================================
|
||||
// 6. i_next = i_after_esc + 1
|
||||
// ========================================
|
||||
let i_next = step_ctx.alloc_var();
|
||||
loop_step_body.push(JoinInst::Compute(crate::mir::join_ir::MirLikeInst::BinOp {
|
||||
dst: i_next,
|
||||
op: crate::mir::join_ir::BinOpKind::Add,
|
||||
lhs: i_after_esc, // ← i_after_esc を使う!
|
||||
rhs: step_one,
|
||||
}));
|
||||
|
||||
// 7. 末尾再帰: Call(loop_step)
|
||||
let recurse_result = step_ctx.alloc_var();
|
||||
loop_step_body.push(JoinInst::Call {
|
||||
func: loop_step_id,
|
||||
args: vec![i_next, out_next, step_n, step_s],
|
||||
k_next: None,
|
||||
dst: Some(recurse_result),
|
||||
});
|
||||
|
||||
loop_step_body.push(JoinInst::Ret {
|
||||
value: Some(recurse_result),
|
||||
});
|
||||
|
||||
let loop_step_func = JoinFunction {
|
||||
id: loop_step_id,
|
||||
name: format!("{}_loop_step", func_name),
|
||||
params: vec![step_i, step_out, step_n, step_s],
|
||||
body: loop_step_body,
|
||||
exit_cont: None,
|
||||
};
|
||||
|
||||
// ========================================
|
||||
// k_exit 関数の構築
|
||||
// ========================================
|
||||
let k_exit_out = crate::mir::ValueId(0);
|
||||
let k_exit_func = JoinFunction {
|
||||
id: k_exit_id,
|
||||
name: format!("{}_k_exit", func_name),
|
||||
params: vec![k_exit_out],
|
||||
body: vec![JoinInst::Ret {
|
||||
value: Some(k_exit_out),
|
||||
}],
|
||||
exit_cont: None,
|
||||
};
|
||||
|
||||
// ========================================
|
||||
// JoinModule の構築
|
||||
// ========================================
|
||||
let mut functions = BTreeMap::new();
|
||||
functions.insert(entry_id, entry_func);
|
||||
functions.insert(k_guard_fail_id, k_guard_fail_func);
|
||||
functions.insert(loop_step_id, loop_step_func);
|
||||
functions.insert(k_exit_id, k_exit_func);
|
||||
|
||||
JoinModule {
|
||||
functions,
|
||||
entry: Some(entry_id),
|
||||
}
|
||||
}
|
||||
}
|
||||
416
src/mir/join_ir/frontend/ast_lowerer/tests.rs
Normal file
416
src/mir/join_ir/frontend/ast_lowerer/tests.rs
Normal file
@ -0,0 +1,416 @@
|
||||
use super::*;
|
||||
use crate::mir::join_ir::JoinInst;
|
||||
|
||||
/// Phase 41-4.4: NestedIfMerge パターン検出のテスト
|
||||
///
|
||||
/// 2レベル以上のネスト if が検出されることを確認する。
|
||||
#[test]
|
||||
fn test_nested_if_pattern_detection_two_levels() {
|
||||
// 2レベルのネスト if: if a { if b { x = 1 } }
|
||||
let program_json = serde_json::json!({
|
||||
"defs": [{
|
||||
"name": "parse_loop",
|
||||
"params": ["src", "i"],
|
||||
"body": {
|
||||
"body": [
|
||||
{
|
||||
"type": "Local",
|
||||
"name": "x",
|
||||
"expr": {"type": "Int", "value": 0}
|
||||
},
|
||||
{
|
||||
"type": "If",
|
||||
"cond": {"type": "Var", "name": "a_cond"},
|
||||
"then": [
|
||||
{
|
||||
"type": "If",
|
||||
"cond": {"type": "Var", "name": "b_cond"},
|
||||
"then": [
|
||||
{
|
||||
"type": "Local",
|
||||
"name": "x",
|
||||
"expr": {"type": "Int", "value": 42}
|
||||
}
|
||||
],
|
||||
"else": []
|
||||
}
|
||||
],
|
||||
"else": []
|
||||
},
|
||||
{
|
||||
"type": "Return",
|
||||
"expr": {"type": "Var", "name": "x"}
|
||||
}
|
||||
]
|
||||
}
|
||||
}]
|
||||
});
|
||||
|
||||
// ExtractCtx を用意
|
||||
let mut ctx = ExtractCtx::new(2);
|
||||
ctx.register_param("src".to_string(), crate::mir::ValueId(0));
|
||||
ctx.register_param("i".to_string(), crate::mir::ValueId(1));
|
||||
ctx.register_param("a_cond".to_string(), crate::mir::ValueId(2));
|
||||
ctx.register_param("b_cond".to_string(), crate::mir::ValueId(3));
|
||||
|
||||
// AST から body を取得
|
||||
let stmts = program_json["defs"][0]["body"]["body"]
|
||||
.as_array()
|
||||
.expect("body must be array");
|
||||
|
||||
// パターン検出
|
||||
let lowerer = AstToJoinIrLowerer::new();
|
||||
let pattern = lowerer.try_match_nested_if_pattern(stmts, &mut ctx);
|
||||
|
||||
assert!(pattern.is_some(), "2-level nested if should be detected");
|
||||
let pattern = pattern.unwrap();
|
||||
|
||||
// 2つの条件が検出される
|
||||
assert_eq!(pattern.conds.len(), 2, "Should have 2 conditions");
|
||||
|
||||
// 1つの変数代入が検出される
|
||||
assert_eq!(pattern.merges.len(), 1, "Should have 1 merge");
|
||||
assert_eq!(pattern.merges[0].0, "x", "Merged variable should be 'x'");
|
||||
}
|
||||
|
||||
/// Phase 41-4.4: NestedIfMerge lowering のテスト(dev flag 必要)
|
||||
///
|
||||
/// HAKO_JOINIR_NESTED_IF=1 が設定されている場合のみ実行。
|
||||
/// 設定されていない場合はスキップ。
|
||||
#[test]
|
||||
fn test_nested_if_merge_lowering() {
|
||||
// Dev flag がない場合はスキップ
|
||||
if std::env::var("HAKO_JOINIR_NESTED_IF").ok().as_deref() != Some("1") {
|
||||
eprintln!(
|
||||
"[Phase 41-4] Skipping test_nested_if_merge_lowering: \
|
||||
Set HAKO_JOINIR_NESTED_IF=1 to enable"
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// 2レベルのネスト if
|
||||
let program_json = serde_json::json!({
|
||||
"defs": [{
|
||||
"name": "parse_loop",
|
||||
"params": ["src", "i"],
|
||||
"body": {
|
||||
"body": [
|
||||
{
|
||||
"type": "Local",
|
||||
"name": "x",
|
||||
"expr": {"type": "Int", "value": 0}
|
||||
},
|
||||
{
|
||||
"type": "If",
|
||||
"cond": {"type": "Compare", "op": ">", "lhs": {"type": "Var", "name": "i"}, "rhs": {"type": "Int", "value": 0}},
|
||||
"then": [
|
||||
{
|
||||
"type": "If",
|
||||
"cond": {"type": "Compare", "op": "<", "lhs": {"type": "Var", "name": "i"}, "rhs": {"type": "Int", "value": 100}},
|
||||
"then": [
|
||||
{
|
||||
"type": "Local",
|
||||
"name": "x",
|
||||
"expr": {"type": "Int", "value": 42}
|
||||
}
|
||||
],
|
||||
"else": []
|
||||
}
|
||||
],
|
||||
"else": []
|
||||
},
|
||||
{
|
||||
"type": "Return",
|
||||
"expr": {"type": "Var", "name": "x"}
|
||||
}
|
||||
]
|
||||
}
|
||||
}]
|
||||
});
|
||||
|
||||
let mut lowerer = AstToJoinIrLowerer::new();
|
||||
let join_module = lowerer.lower_nested_if_pattern(&program_json);
|
||||
|
||||
// JoinModule に 1 つの関数がある
|
||||
assert_eq!(join_module.functions.len(), 1, "Should have 1 function");
|
||||
|
||||
// entry が設定されている
|
||||
assert!(join_module.entry.is_some(), "Entry should be set");
|
||||
|
||||
// NestedIfMerge 命令が含まれている
|
||||
let entry_id = join_module.entry.unwrap();
|
||||
let entry_func = join_module
|
||||
.functions
|
||||
.get(&entry_id)
|
||||
.expect("Entry function");
|
||||
|
||||
let has_nested_if_merge = entry_func
|
||||
.body
|
||||
.iter()
|
||||
.any(|inst| matches!(inst, JoinInst::NestedIfMerge { .. }));
|
||||
|
||||
assert!(
|
||||
has_nested_if_merge,
|
||||
"JoinFunction should contain NestedIfMerge instruction"
|
||||
);
|
||||
|
||||
eprintln!("[Phase 41-4] test_nested_if_merge_lowering PASSED");
|
||||
}
|
||||
|
||||
/// Phase 41-4.4: 単一レベル if はマッチしないことを確認
|
||||
#[test]
|
||||
fn test_nested_if_pattern_single_level_does_not_match() {
|
||||
// 1レベルのif: if a { x = 1 }
|
||||
let program_json = serde_json::json!({
|
||||
"defs": [{
|
||||
"name": "test",
|
||||
"params": [],
|
||||
"body": {
|
||||
"body": [
|
||||
{
|
||||
"type": "If",
|
||||
"cond": {"type": "Var", "name": "a"},
|
||||
"then": [
|
||||
{
|
||||
"type": "Local",
|
||||
"name": "x",
|
||||
"expr": {"type": "Int", "value": 1}
|
||||
}
|
||||
],
|
||||
"else": []
|
||||
}
|
||||
]
|
||||
}
|
||||
}]
|
||||
});
|
||||
|
||||
let mut ctx = ExtractCtx::new(0);
|
||||
let stmts = program_json["defs"][0]["body"]["body"]
|
||||
.as_array()
|
||||
.expect("body");
|
||||
|
||||
let lowerer = AstToJoinIrLowerer::new();
|
||||
let pattern = lowerer.try_match_nested_if_pattern(stmts, &mut ctx);
|
||||
|
||||
// 1レベルはマッチしない
|
||||
assert!(
|
||||
pattern.is_none(),
|
||||
"Single-level if should NOT match NestedIfMerge pattern"
|
||||
);
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// Phase 45: read_quoted_from Pattern Tests
|
||||
// ========================================
|
||||
|
||||
/// Phase 45: read_quoted_from lowering のテスト(dev flag 必要)
|
||||
///
|
||||
/// HAKO_JOINIR_READ_QUOTED=1 が設定されている場合のみ実行。
|
||||
/// 設定されていない場合はスキップ。
|
||||
#[test]
|
||||
fn test_read_quoted_from_lowering() {
|
||||
// Dev flag がない場合はスキップ
|
||||
if std::env::var("HAKO_JOINIR_READ_QUOTED").ok().as_deref() != Some("1") {
|
||||
eprintln!(
|
||||
"[Phase 45] Skipping test_read_quoted_from_lowering: \
|
||||
Set HAKO_JOINIR_READ_QUOTED=1 to enable"
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// read_quoted_from パターンの JSON 表現
|
||||
let program_json = serde_json::json!({
|
||||
"defs": [{
|
||||
"name": "read_quoted_from",
|
||||
"params": ["s", "pos"],
|
||||
"body": {
|
||||
"body": [
|
||||
// local i = pos
|
||||
{
|
||||
"type": "Local",
|
||||
"name": "i",
|
||||
"expr": {"type": "Var", "name": "pos"}
|
||||
},
|
||||
// if s.substring(i, i+1) != '"' { return "" }
|
||||
{
|
||||
"type": "If",
|
||||
"cond": {
|
||||
"type": "Compare",
|
||||
"op": "!=",
|
||||
"lhs": {
|
||||
"type": "Method",
|
||||
"receiver": {"type": "Var", "name": "s"},
|
||||
"method": "substring",
|
||||
"args": [
|
||||
{"type": "Var", "name": "i"},
|
||||
{"type": "Binary", "op": "+", "lhs": {"type": "Var", "name": "i"}, "rhs": {"type": "Int", "value": 1}}
|
||||
]
|
||||
},
|
||||
"rhs": {"type": "String", "value": "\""}
|
||||
},
|
||||
"then": [
|
||||
{"type": "Return", "expr": {"type": "String", "value": ""}}
|
||||
],
|
||||
"else": []
|
||||
},
|
||||
// i = i + 1
|
||||
{
|
||||
"type": "Local",
|
||||
"name": "i",
|
||||
"expr": {"type": "Binary", "op": "+", "lhs": {"type": "Var", "name": "i"}, "rhs": {"type": "Int", "value": 1}}
|
||||
},
|
||||
// local out = ""
|
||||
{
|
||||
"type": "Local",
|
||||
"name": "out",
|
||||
"expr": {"type": "String", "value": ""}
|
||||
},
|
||||
// local n = s.length()
|
||||
{
|
||||
"type": "Local",
|
||||
"name": "n",
|
||||
"expr": {
|
||||
"type": "Method",
|
||||
"receiver": {"type": "Var", "name": "s"},
|
||||
"method": "length",
|
||||
"args": []
|
||||
}
|
||||
},
|
||||
// Loop (simplified, loop body handled by lower_read_quoted_pattern)
|
||||
{
|
||||
"type": "Loop",
|
||||
"cond": {
|
||||
"type": "Compare",
|
||||
"op": "<",
|
||||
"lhs": {"type": "Var", "name": "i"},
|
||||
"rhs": {"type": "Var", "name": "n"}
|
||||
},
|
||||
"body": []
|
||||
},
|
||||
// return out
|
||||
{
|
||||
"type": "Return",
|
||||
"expr": {"type": "Var", "name": "out"}
|
||||
}
|
||||
]
|
||||
}
|
||||
}]
|
||||
});
|
||||
|
||||
let mut lowerer = AstToJoinIrLowerer::new();
|
||||
let join_module = lowerer.lower_read_quoted_pattern(&program_json);
|
||||
|
||||
// JoinModule に 4 つの関数がある(entry, k_guard_fail, loop_step, k_exit)
|
||||
assert_eq!(join_module.functions.len(), 4, "Should have 4 functions");
|
||||
|
||||
// entry が設定されている
|
||||
assert!(join_module.entry.is_some(), "Entry should be set");
|
||||
|
||||
// 関数名を確認
|
||||
let func_names: Vec<&str> = join_module
|
||||
.functions
|
||||
.values()
|
||||
.map(|f| f.name.as_str())
|
||||
.collect();
|
||||
|
||||
assert!(
|
||||
func_names.iter().any(|n| *n == "read_quoted_from"),
|
||||
"Should have entry function"
|
||||
);
|
||||
assert!(
|
||||
func_names.iter().any(|n| n.contains("loop_step")),
|
||||
"Should have loop_step function"
|
||||
);
|
||||
assert!(
|
||||
func_names.iter().any(|n| n.contains("k_exit")),
|
||||
"Should have k_exit function"
|
||||
);
|
||||
assert!(
|
||||
func_names.iter().any(|n| n.contains("k_guard_fail")),
|
||||
"Should have k_guard_fail function"
|
||||
);
|
||||
|
||||
eprintln!("[Phase 45] test_read_quoted_from_lowering PASSED");
|
||||
eprintln!("[Phase 45] Functions: {:?}", func_names);
|
||||
}
|
||||
|
||||
/// Phase 45: lowering で生成される JoinInst の種類確認
|
||||
#[test]
|
||||
fn test_read_quoted_from_lowering_instructions() {
|
||||
// Dev flag がない場合はスキップ
|
||||
if std::env::var("HAKO_JOINIR_READ_QUOTED").ok().as_deref() != Some("1") {
|
||||
return;
|
||||
}
|
||||
|
||||
// 簡易的な program_json(実際の AST 構造は不要、パターンが認識されればOK)
|
||||
let program_json = serde_json::json!({
|
||||
"defs": [{
|
||||
"name": "read_quoted_from",
|
||||
"params": ["s", "pos"],
|
||||
"body": { "body": [] }
|
||||
}]
|
||||
});
|
||||
|
||||
let mut lowerer = AstToJoinIrLowerer::new();
|
||||
let join_module = lowerer.lower_read_quoted_pattern(&program_json);
|
||||
|
||||
// entry 関数の命令を確認
|
||||
let entry_id = join_module.entry.unwrap();
|
||||
let entry_func = join_module
|
||||
.functions
|
||||
.get(&entry_id)
|
||||
.expect("Entry function");
|
||||
|
||||
// entry には以下が含まれる:
|
||||
// - Compute (Const, BinOp, Compare)
|
||||
// - MethodCall (substring, length)
|
||||
// - Jump (guard check)
|
||||
// - Call (loop_step)
|
||||
// - Ret
|
||||
|
||||
let has_method_call = entry_func
|
||||
.body
|
||||
.iter()
|
||||
.any(|inst| matches!(inst, JoinInst::MethodCall { .. }));
|
||||
let has_jump = entry_func
|
||||
.body
|
||||
.iter()
|
||||
.any(|inst| matches!(inst, JoinInst::Jump { .. }));
|
||||
let has_call = entry_func
|
||||
.body
|
||||
.iter()
|
||||
.any(|inst| matches!(inst, JoinInst::Call { .. }));
|
||||
let has_ret = entry_func
|
||||
.body
|
||||
.iter()
|
||||
.any(|inst| matches!(inst, JoinInst::Ret { .. }));
|
||||
|
||||
assert!(
|
||||
has_method_call,
|
||||
"Entry should have MethodCall for substring/length"
|
||||
);
|
||||
assert!(has_jump, "Entry should have Jump for guard check");
|
||||
assert!(has_call, "Entry should have Call for loop_step");
|
||||
assert!(has_ret, "Entry should have Ret");
|
||||
|
||||
// loop_step 関数の命令を確認
|
||||
let loop_step_func = join_module
|
||||
.functions
|
||||
.values()
|
||||
.find(|f| f.name.contains("loop_step"))
|
||||
.expect("loop_step function");
|
||||
|
||||
let loop_has_jump = loop_step_func
|
||||
.body
|
||||
.iter()
|
||||
.filter(|inst| matches!(inst, JoinInst::Jump { .. }))
|
||||
.count();
|
||||
|
||||
// loop_step には 2 つの Jump がある: exit check と break check
|
||||
assert_eq!(
|
||||
loop_has_jump, 2,
|
||||
"loop_step should have 2 Jumps (exit check, break check)"
|
||||
);
|
||||
|
||||
eprintln!("[Phase 45] test_read_quoted_from_lowering_instructions PASSED");
|
||||
}
|
||||
@ -8,8 +8,8 @@
|
||||
//! - Phase 40-2: conservative_vars(後で追加)
|
||||
//! - Phase 40-3: reset_vars(後で追加)
|
||||
|
||||
use std::collections::{BTreeMap, HashSet};
|
||||
use super::super::JoinFuncId;
|
||||
use std::collections::{BTreeMap, HashSet};
|
||||
|
||||
/// Phase 40-1実験用メタデータ
|
||||
#[derive(Debug, Default, Clone)]
|
||||
|
||||
@ -7,9 +7,7 @@
|
||||
|
||||
use std::io::Write;
|
||||
|
||||
use super::{
|
||||
BinOpKind, CompareOp, ConstValue, JoinFunction, JoinInst, JoinModule, MirLikeInst,
|
||||
};
|
||||
use super::{BinOpKind, CompareOp, ConstValue, JoinFunction, JoinInst, JoinModule, MirLikeInst};
|
||||
|
||||
/// JoinModule を JSON としてシリアライズする
|
||||
///
|
||||
@ -19,7 +17,10 @@ use super::{
|
||||
/// write_join_module_as_json(&module, &mut output)?;
|
||||
/// let json_str = String::from_utf8(output)?;
|
||||
/// ```
|
||||
pub fn write_join_module_as_json<W: Write>(module: &JoinModule, out: &mut W) -> std::io::Result<()> {
|
||||
pub fn write_join_module_as_json<W: Write>(
|
||||
module: &JoinModule,
|
||||
out: &mut W,
|
||||
) -> std::io::Result<()> {
|
||||
write!(out, "{{")?;
|
||||
write!(out, "\"version\":0")?;
|
||||
|
||||
@ -48,7 +49,7 @@ pub fn write_join_module_as_json<W: Write>(module: &JoinModule, out: &mut W) ->
|
||||
fn write_function<W: Write>(func: &JoinFunction, out: &mut W) -> std::io::Result<()> {
|
||||
write!(out, "{{")?;
|
||||
write!(out, "\"id\":{}", func.id.0)?;
|
||||
write!(out, ",\"name\":\"{}\"" , escape_json_string(&func.name))?;
|
||||
write!(out, ",\"name\":\"{}\"", escape_json_string(&func.name))?;
|
||||
|
||||
// params
|
||||
write!(out, ",\"params\":[")?;
|
||||
@ -134,7 +135,12 @@ fn write_inst<W: Write>(inst: &JoinInst, out: &mut W) -> std::io::Result<()> {
|
||||
write!(out, "}}")?;
|
||||
}
|
||||
// Phase 33: Select instruction JSON serialization
|
||||
JoinInst::Select { dst, cond, then_val, else_val } => {
|
||||
JoinInst::Select {
|
||||
dst,
|
||||
cond,
|
||||
then_val,
|
||||
else_val,
|
||||
} => {
|
||||
write!(out, "{{\"type\":\"select\"")?;
|
||||
write!(out, ",\"dst\":{}", dst.0)?;
|
||||
write!(out, ",\"cond\":{}", cond.0)?;
|
||||
@ -143,7 +149,11 @@ fn write_inst<W: Write>(inst: &JoinInst, out: &mut W) -> std::io::Result<()> {
|
||||
write!(out, "}}")?;
|
||||
}
|
||||
// Phase 33-6: IfMerge instruction JSON serialization
|
||||
JoinInst::IfMerge { cond, merges, k_next } => {
|
||||
JoinInst::IfMerge {
|
||||
cond,
|
||||
merges,
|
||||
k_next,
|
||||
} => {
|
||||
write!(out, "{{\"type\":\"if_merge\"")?;
|
||||
write!(out, ",\"cond\":{}", cond.0)?;
|
||||
write!(out, ",\"merges\":[")?;
|
||||
@ -165,7 +175,12 @@ fn write_inst<W: Write>(inst: &JoinInst, out: &mut W) -> std::io::Result<()> {
|
||||
write!(out, "}}")?;
|
||||
}
|
||||
// Phase 34-6: MethodCall instruction JSON serialization
|
||||
JoinInst::MethodCall { dst, receiver, method, args } => {
|
||||
JoinInst::MethodCall {
|
||||
dst,
|
||||
receiver,
|
||||
method,
|
||||
args,
|
||||
} => {
|
||||
write!(out, "{{\"type\":\"method_call\"")?;
|
||||
write!(out, ",\"dst\":{}", dst.0)?;
|
||||
write!(out, ",\"receiver\":{}", receiver.0)?;
|
||||
@ -181,7 +196,11 @@ fn write_inst<W: Write>(inst: &JoinInst, out: &mut W) -> std::io::Result<()> {
|
||||
write!(out, "}}")?;
|
||||
}
|
||||
// Phase 41-4: NestedIfMerge instruction JSON serialization
|
||||
JoinInst::NestedIfMerge { conds, merges, k_next } => {
|
||||
JoinInst::NestedIfMerge {
|
||||
conds,
|
||||
merges,
|
||||
k_next,
|
||||
} => {
|
||||
write!(out, "{{\"type\":\"nested_if_merge\"")?;
|
||||
// conds array
|
||||
write!(out, ",\"conds\":[")?;
|
||||
@ -355,7 +374,8 @@ mod tests {
|
||||
#[test]
|
||||
fn test_simple_function() {
|
||||
let mut module = JoinModule::new();
|
||||
let mut func = JoinFunction::new(JoinFuncId::new(0), "test".to_string(), vec![ValueId(100)]);
|
||||
let mut func =
|
||||
JoinFunction::new(JoinFuncId::new(0), "test".to_string(), vec![ValueId(100)]);
|
||||
func.body.push(JoinInst::Ret {
|
||||
value: Some(ValueId(100)),
|
||||
});
|
||||
|
||||
@ -629,12 +629,12 @@ fn lower_trim_from_mir(module: &crate::mir::MirModule) -> Option<JoinModule> {
|
||||
return build_funcscanner_trim_joinir(module);
|
||||
};
|
||||
if crate::mir::join_ir::lowering::common::case_a::is_simple_case_a_loop(&loop_form) {
|
||||
eprintln!(
|
||||
"[joinir/trim/generic-hook] simple Case A loop detected (LoopToJoinLowerer)"
|
||||
);
|
||||
eprintln!("[joinir/trim/generic-hook] simple Case A loop detected (LoopToJoinLowerer)");
|
||||
let lowerer = LoopToJoinLowerer::new();
|
||||
if let Some(jm) = lowerer.lower_case_a_for_trim(target_func, &loop_form) {
|
||||
eprintln!("[joinir/trim/generic-hook] LoopToJoinLowerer produced JoinIR, returning early");
|
||||
eprintln!(
|
||||
"[joinir/trim/generic-hook] LoopToJoinLowerer produced JoinIR, returning early"
|
||||
);
|
||||
return Some(jm);
|
||||
}
|
||||
eprintln!(
|
||||
|
||||
@ -46,7 +46,9 @@ pub(crate) fn lower_case_a_skip_ws_with_scope(
|
||||
/// `_for_minimal_skip_ws` と `_with_scope` の両方から呼ばれる。
|
||||
fn lower_case_a_skip_ws_core(ctx: &CaseAContext) -> Option<JoinModule> {
|
||||
let string_key = ctx.pinned_name_or_first(0)?;
|
||||
let len_key = ctx.pinned_name_or_first(1).unwrap_or_else(|| string_key.clone());
|
||||
let len_key = ctx
|
||||
.pinned_name_or_first(1)
|
||||
.unwrap_or_else(|| string_key.clone());
|
||||
let index_key = ctx.carrier_name_or_first(0)?;
|
||||
|
||||
let s_loop = ctx.get_loop_id(&string_key)?;
|
||||
@ -246,7 +248,9 @@ pub(crate) fn lower_case_a_trim_with_scope(
|
||||
/// `_for_trim_minimal` と `_with_scope` の両方から呼ばれる。
|
||||
fn lower_case_a_trim_core(ctx: &CaseAContext) -> Option<JoinModule> {
|
||||
let string_key = ctx.pinned_name_or_first(0)?;
|
||||
let base_key = ctx.pinned_name_or_first(1).unwrap_or_else(|| string_key.clone());
|
||||
let base_key = ctx
|
||||
.pinned_name_or_first(1)
|
||||
.unwrap_or_else(|| string_key.clone());
|
||||
let carrier_key = ctx.carrier_name_or_first(0)?;
|
||||
|
||||
let s_loop = ctx.get_loop_id(&string_key)?;
|
||||
@ -722,8 +726,12 @@ pub(crate) fn lower_case_a_append_defs_with_scope(
|
||||
/// `_for_append_defs_minimal` と `_with_scope` の両方から呼ばれる。
|
||||
fn lower_case_a_append_defs_core(ctx: &CaseAContext) -> Option<JoinModule> {
|
||||
let dst_key = ctx.pinned_name_or_first(0)?;
|
||||
let defs_key = ctx.pinned_name_or_first(1).unwrap_or_else(|| dst_key.clone());
|
||||
let n_key = ctx.pinned_name_or_first(2).unwrap_or_else(|| defs_key.clone());
|
||||
let defs_key = ctx
|
||||
.pinned_name_or_first(1)
|
||||
.unwrap_or_else(|| dst_key.clone());
|
||||
let n_key = ctx
|
||||
.pinned_name_or_first(2)
|
||||
.unwrap_or_else(|| defs_key.clone());
|
||||
let i_key = ctx.carrier_name_or_first(0)?;
|
||||
|
||||
let dst_loop = ctx.get_loop_id(&dst_key)?;
|
||||
@ -863,8 +871,7 @@ fn lower_case_a_append_defs_core(ctx: &CaseAContext) -> Option<JoinModule> {
|
||||
pub(crate) fn lower_case_a_stage1_usingresolver_with_scope(
|
||||
scope: crate::mir::join_ir::lowering::loop_scope_shape::LoopScopeShape,
|
||||
) -> Option<JoinModule> {
|
||||
let ctx =
|
||||
CaseAContext::from_scope(scope, "stage1", |offset| stage1_vid::loop_step(offset))?;
|
||||
let ctx = CaseAContext::from_scope(scope, "stage1", |offset| stage1_vid::loop_step(offset))?;
|
||||
lower_case_a_stage1_usingresolver_core(&ctx)
|
||||
}
|
||||
|
||||
@ -874,11 +881,19 @@ pub(crate) fn lower_case_a_stage1_usingresolver_with_scope(
|
||||
/// `_for_stage1_usingresolver_minimal` と `_with_scope` の両方から呼ばれる。
|
||||
fn lower_case_a_stage1_usingresolver_core(ctx: &CaseAContext) -> Option<JoinModule> {
|
||||
let entries_key = ctx.pinned_name_or_first(0)?;
|
||||
let n_key = ctx.pinned_name_or_first(1).unwrap_or_else(|| entries_key.clone());
|
||||
let modules_key = ctx.pinned_name_or_first(2).unwrap_or_else(|| entries_key.clone());
|
||||
let seen_key = ctx.pinned_name_or_first(3).unwrap_or_else(|| entries_key.clone());
|
||||
let n_key = ctx
|
||||
.pinned_name_or_first(1)
|
||||
.unwrap_or_else(|| entries_key.clone());
|
||||
let modules_key = ctx
|
||||
.pinned_name_or_first(2)
|
||||
.unwrap_or_else(|| entries_key.clone());
|
||||
let seen_key = ctx
|
||||
.pinned_name_or_first(3)
|
||||
.unwrap_or_else(|| entries_key.clone());
|
||||
let prefix_key = ctx.carrier_name_or_first(0)?;
|
||||
let i_key = ctx.carrier_name_or_first(1).unwrap_or_else(|| prefix_key.clone());
|
||||
let i_key = ctx
|
||||
.carrier_name_or_first(1)
|
||||
.unwrap_or_else(|| prefix_key.clone());
|
||||
|
||||
let entries_loop = ctx.get_loop_id(&entries_key)?;
|
||||
let n_loop = ctx.get_loop_id(&n_key)?;
|
||||
|
||||
@ -42,10 +42,7 @@ impl IfLoweringDryRunner {
|
||||
/// - 各 Branch ブロックで try_lower_if_to_joinir() 試行
|
||||
/// - パフォーマンス計測(マイクロ秒レベル)
|
||||
/// - 統計情報収集(Select/IfMerge分類)
|
||||
pub fn scan_module(
|
||||
&self,
|
||||
functions: &HashMap<String, MirFunction>,
|
||||
) -> DryRunStats {
|
||||
pub fn scan_module(&self, functions: &HashMap<String, MirFunction>) -> DryRunStats {
|
||||
let mut total_branches = 0;
|
||||
let mut lowered_count = 0;
|
||||
let mut select_count = 0;
|
||||
@ -60,10 +57,7 @@ impl IfLoweringDryRunner {
|
||||
|
||||
// 各Branchブロックに対してtry_lower_if_to_joinir()試行
|
||||
for (block_id, block) in &func.blocks {
|
||||
if matches!(
|
||||
block.terminator,
|
||||
Some(MirInstruction::Branch { .. })
|
||||
) {
|
||||
if matches!(block.terminator, Some(MirInstruction::Branch { .. })) {
|
||||
total_branches += 1;
|
||||
let start = Instant::now();
|
||||
|
||||
|
||||
@ -44,7 +44,9 @@ impl IfMergeLowerer {
|
||||
|
||||
/// Phase 33-8: debug-level backward compat wrapper
|
||||
pub fn with_debug(debug: bool) -> Self {
|
||||
Self { debug_level: if debug { 1 } else { 0 } }
|
||||
Self {
|
||||
debug_level: if debug { 1 } else { 0 },
|
||||
}
|
||||
}
|
||||
|
||||
/// if/else が IfMerge に lowering できるかチェック
|
||||
@ -210,29 +212,16 @@ impl IfMergeLowerer {
|
||||
}
|
||||
|
||||
/// 命令列から dst に書き込まれる値を探す(最後の書き込み)
|
||||
fn find_written_value(
|
||||
&self,
|
||||
instructions: &[MirInstruction],
|
||||
dst: ValueId,
|
||||
) -> Option<ValueId> {
|
||||
fn find_written_value(&self, instructions: &[MirInstruction], dst: ValueId) -> Option<ValueId> {
|
||||
// 逆順で探索して最後の書き込みを見つける
|
||||
for inst in instructions.iter().rev() {
|
||||
match inst {
|
||||
MirInstruction::Copy {
|
||||
dst: inst_dst,
|
||||
src,
|
||||
} if *inst_dst == dst => {
|
||||
MirInstruction::Copy { dst: inst_dst, src } if *inst_dst == dst => {
|
||||
return Some(*src);
|
||||
}
|
||||
MirInstruction::Const {
|
||||
dst: inst_dst, ..
|
||||
}
|
||||
| MirInstruction::BinOp {
|
||||
dst: inst_dst, ..
|
||||
}
|
||||
| MirInstruction::Compare {
|
||||
dst: inst_dst, ..
|
||||
}
|
||||
MirInstruction::Const { dst: inst_dst, .. }
|
||||
| MirInstruction::BinOp { dst: inst_dst, .. }
|
||||
| MirInstruction::Compare { dst: inst_dst, .. }
|
||||
| MirInstruction::Call {
|
||||
dst: Some(inst_dst),
|
||||
..
|
||||
|
||||
@ -51,7 +51,9 @@ impl IfSelectLowerer {
|
||||
|
||||
/// Phase 33-8: debug-level backward compat wrapper
|
||||
pub fn with_debug(debug: bool) -> Self {
|
||||
Self { debug_level: if debug { 1 } else { 0 } }
|
||||
Self {
|
||||
debug_level: if debug { 1 } else { 0 },
|
||||
}
|
||||
}
|
||||
|
||||
/// if/else が Select に lowering できるかチェック
|
||||
@ -95,11 +97,7 @@ impl IfSelectLowerer {
|
||||
}
|
||||
|
||||
/// MIR 関数から if/else パターンを探す
|
||||
fn find_if_pattern(
|
||||
&self,
|
||||
func: &MirFunction,
|
||||
block_id: BasicBlockId,
|
||||
) -> Option<IfPattern> {
|
||||
fn find_if_pattern(&self, func: &MirFunction, block_id: BasicBlockId) -> Option<IfPattern> {
|
||||
// 1. Block が Branch 命令で終わっているか確認
|
||||
let block = func.blocks.get(&block_id)?;
|
||||
let branch = match block.terminator.as_ref()? {
|
||||
@ -122,11 +120,15 @@ impl IfSelectLowerer {
|
||||
// Phase 33-10: PHI早期チェック(パターンマッチング前)
|
||||
// JoinIRは「PHI生成器」であり「PHI変換器」ではない
|
||||
// then/elseがJumpで終わる場合、merge blockにPHI命令があるか早期確認
|
||||
if let Some(merge_block_id) = self.get_merge_block_if_jump_pattern(&branch, then_block, else_block) {
|
||||
if let Some(merge_block_id) =
|
||||
self.get_merge_block_if_jump_pattern(&branch, then_block, else_block)
|
||||
{
|
||||
let merge_block = func.blocks.get(&merge_block_id)?;
|
||||
if merge_block.instructions.iter().any(|inst| {
|
||||
matches!(inst, MirInstruction::Phi { .. })
|
||||
}) {
|
||||
if merge_block
|
||||
.instructions
|
||||
.iter()
|
||||
.any(|inst| matches!(inst, MirInstruction::Phi { .. }))
|
||||
{
|
||||
if self.debug_level >= 2 {
|
||||
eprintln!("[IfSelectLowerer] ⏭️ PHI already exists in merge block, skipping");
|
||||
}
|
||||
@ -144,8 +146,7 @@ impl IfSelectLowerer {
|
||||
}
|
||||
|
||||
// 4. local パターンのチェック
|
||||
if let Some(pattern) = self.try_match_local_pattern(func, &branch, then_block, else_block)
|
||||
{
|
||||
if let Some(pattern) = self.try_match_local_pattern(func, &branch, then_block, else_block) {
|
||||
// Phase 33-8: Level 2 - Pattern matching details
|
||||
if self.debug_level >= 2 {
|
||||
eprintln!("[IfSelectLowerer] ✅ matched local pattern");
|
||||
@ -314,9 +315,7 @@ impl IfSelectLowerer {
|
||||
// Phase 33-10: PHIチェックは find_if_pattern() で早期実行済み
|
||||
|
||||
match merge_block.terminator.as_ref()? {
|
||||
MirInstruction::Return {
|
||||
value: Some(v),
|
||||
} if *v == dst_then => {
|
||||
MirInstruction::Return { value: Some(v) } if *v == dst_then => {
|
||||
// OK
|
||||
}
|
||||
_ => return None,
|
||||
|
||||
@ -150,7 +150,6 @@ pub(crate) fn is_case_a_minimal_target(func_name: &str) -> bool {
|
||||
#[allow(dead_code)] // Phase 30: block IDs and progress_carrier are for future F-3/F-4 use
|
||||
pub(crate) struct LoopScopeShape {
|
||||
// === Block IDs (Phase 30: from LoopForm) ===
|
||||
|
||||
/// Loop header block (condition check)
|
||||
pub header: BasicBlockId,
|
||||
|
||||
@ -164,7 +163,6 @@ pub(crate) struct LoopScopeShape {
|
||||
pub exit: BasicBlockId,
|
||||
|
||||
// === Variable Classification ===
|
||||
|
||||
/// Loop-crossing parameters (always need header/exit PHI)
|
||||
pub pinned: BTreeSet<String>,
|
||||
|
||||
@ -241,7 +239,12 @@ impl LoopScopeShape {
|
||||
if let Some(name) = func_name {
|
||||
if is_case_a_minimal_target(name) {
|
||||
return Self::analyze_case_a(
|
||||
loop_form, intake, var_classes, exit_live_box, query, name,
|
||||
loop_form,
|
||||
intake,
|
||||
var_classes,
|
||||
exit_live_box,
|
||||
query,
|
||||
name,
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -316,9 +319,8 @@ impl LoopScopeShape {
|
||||
) -> Option<Self> {
|
||||
// Phase 30 F-3.1: 現在は legacy と同じ実装
|
||||
// 将来は MIR から独立して pinned/carriers/body_locals/exit_live を計算
|
||||
let result = Self::from_existing_boxes_legacy(
|
||||
loop_form, intake, var_classes, exit_live_box, query,
|
||||
)?;
|
||||
let result =
|
||||
Self::from_existing_boxes_legacy(loop_form, intake, var_classes, exit_live_box, query)?;
|
||||
|
||||
// Debug: Case-A minimal path が使われていることをログ
|
||||
if std::env::var("NYASH_LOOPSCOPE_DEBUG").is_ok() {
|
||||
@ -523,7 +525,6 @@ impl LoopScopeShape {
|
||||
#[derive(Debug, Clone)]
|
||||
pub(crate) struct CaseAContext {
|
||||
// Phase 30: scope フィールド削除(ordered_pinned/carriers/exit_args に情報コピー済みで重複)
|
||||
|
||||
/// 順序付き pinned 変数名
|
||||
pub ordered_pinned: Vec<String>,
|
||||
|
||||
@ -822,7 +823,10 @@ mod tests {
|
||||
|
||||
// from_scope は None を返すべき
|
||||
let ctx = CaseAContext::from_scope(scope, "test", |offset| vid::loop_step(offset));
|
||||
assert!(ctx.is_none(), "from_scope should return None when header == exit");
|
||||
assert!(
|
||||
ctx.is_none(),
|
||||
"from_scope should return None when header == exit"
|
||||
);
|
||||
}
|
||||
|
||||
/// block IDs が LoopForm から正しく伝播されるテスト
|
||||
@ -879,7 +883,10 @@ mod tests {
|
||||
let vec2: Vec<_> = set2.iter().cloned().collect();
|
||||
|
||||
assert_eq!(vec1, vec2);
|
||||
assert_eq!(vec1, vec!["a".to_string(), "m".to_string(), "z".to_string()]);
|
||||
assert_eq!(
|
||||
vec1,
|
||||
vec!["a".to_string(), "m".to_string(), "z".to_string()]
|
||||
);
|
||||
}
|
||||
|
||||
/// needs_header_phi と needs_exit_phi の一貫性テスト
|
||||
@ -950,7 +957,9 @@ mod tests {
|
||||
exit: BasicBlockId::new(100),
|
||||
pinned: vec!["s".to_string(), "n".to_string()].into_iter().collect(),
|
||||
carriers: vec!["i".to_string()].into_iter().collect(),
|
||||
body_locals: vec!["x".to_string(), "ch".to_string()].into_iter().collect(),
|
||||
body_locals: vec!["x".to_string(), "ch".to_string()]
|
||||
.into_iter()
|
||||
.collect(),
|
||||
exit_live: vec![
|
||||
"s".to_string(),
|
||||
"n".to_string(),
|
||||
@ -989,7 +998,9 @@ mod tests {
|
||||
exit: BasicBlockId::new(100),
|
||||
pinned: vec!["s".to_string()].into_iter().collect(),
|
||||
carriers: vec!["i".to_string()].into_iter().collect(),
|
||||
body_locals: vec!["x".to_string(), "ch".to_string()].into_iter().collect(),
|
||||
body_locals: vec!["x".to_string(), "ch".to_string()]
|
||||
.into_iter()
|
||||
.collect(),
|
||||
exit_live: vec!["s".to_string(), "i".to_string(), "x".to_string()]
|
||||
.into_iter()
|
||||
.collect(),
|
||||
|
||||
@ -106,8 +106,14 @@ impl LoopToJoinLowerer {
|
||||
let intake = intake_loop_form(loop_form, &var_classes, &query, func)?;
|
||||
|
||||
// Step 4: LoopScopeShape を構築
|
||||
let scope =
|
||||
LoopScopeShape::from_existing_boxes(loop_form, &intake, &var_classes, &exit_live, &query, func_name)?;
|
||||
let scope = LoopScopeShape::from_existing_boxes(
|
||||
loop_form,
|
||||
&intake,
|
||||
&var_classes,
|
||||
&exit_live,
|
||||
&query,
|
||||
func_name,
|
||||
)?;
|
||||
|
||||
if self.debug {
|
||||
eprintln!(
|
||||
@ -274,8 +280,7 @@ impl LoopToJoinLowerer {
|
||||
if self.debug {
|
||||
eprintln!(
|
||||
"[LoopToJoinLowerer] rejected: header {:?} has {} successors (expected 2)",
|
||||
region.header,
|
||||
succ_count
|
||||
region.header, succ_count
|
||||
);
|
||||
}
|
||||
return false;
|
||||
@ -412,7 +417,11 @@ impl LoopToJoinLowerer {
|
||||
func: &MirFunction,
|
||||
loop_form: &LoopForm,
|
||||
) -> Option<JoinModule> {
|
||||
self.lower(func, loop_form, Some("Stage1UsingResolverBox.resolve_for_source/5"))
|
||||
self.lower(
|
||||
func,
|
||||
loop_form,
|
||||
Some("Stage1UsingResolverBox.resolve_for_source/5"),
|
||||
)
|
||||
}
|
||||
|
||||
/// Case-A 汎用 lowerer の「StageBBodyExtractorBox.build_body_src/2 用」薄いラッパー。
|
||||
@ -422,7 +431,11 @@ impl LoopToJoinLowerer {
|
||||
func: &MirFunction,
|
||||
loop_form: &LoopForm,
|
||||
) -> Option<JoinModule> {
|
||||
self.lower(func, loop_form, Some("StageBBodyExtractorBox.build_body_src/2"))
|
||||
self.lower(
|
||||
func,
|
||||
loop_form,
|
||||
Some("StageBBodyExtractorBox.build_body_src/2"),
|
||||
)
|
||||
}
|
||||
|
||||
/// Case-A 汎用 lowerer の「StageBFuncScannerBox.scan_all_boxes/1 用」薄いラッパー。
|
||||
@ -432,7 +445,11 @@ impl LoopToJoinLowerer {
|
||||
func: &MirFunction,
|
||||
loop_form: &LoopForm,
|
||||
) -> Option<JoinModule> {
|
||||
self.lower(func, loop_form, Some("StageBFuncScannerBox.scan_all_boxes/1"))
|
||||
self.lower(
|
||||
func,
|
||||
loop_form,
|
||||
Some("StageBFuncScannerBox.scan_all_boxes/1"),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -143,7 +143,10 @@ pub fn try_lower_if_to_joinir(
|
||||
}
|
||||
|
||||
if debug_level >= 1 {
|
||||
eprintln!("[try_lower_if_to_joinir] trying to lower {}", func.signature.name);
|
||||
eprintln!(
|
||||
"[try_lower_if_to_joinir] trying to lower {}",
|
||||
func.signature.name
|
||||
);
|
||||
}
|
||||
|
||||
// 3. Phase 33-7: IfMerge を優先的に試行(複数変数パターン)
|
||||
|
||||
@ -352,7 +352,9 @@ fn lower_from_mir(module: &crate::mir::MirModule) -> Option<JoinModule> {
|
||||
// Stage-1: entry_is_preheader=false (entry の succ が preheader)
|
||||
// has_break=false (このループに break はない)
|
||||
let Some(loop_form) = construct_simple_while_loopform(entry, &query, false, false) else {
|
||||
eprintln!("[joinir/stage1_using_resolver/generic-hook] failed to construct LoopForm from CFG");
|
||||
eprintln!(
|
||||
"[joinir/stage1_using_resolver/generic-hook] failed to construct LoopForm from CFG"
|
||||
);
|
||||
return lower_handwritten(module);
|
||||
};
|
||||
|
||||
@ -368,7 +370,8 @@ fn lower_from_mir(module: &crate::mir::MirModule) -> Option<JoinModule> {
|
||||
let params_len = target_func.params.len();
|
||||
if params_len == 5 {
|
||||
let lowerer = LoopToJoinLowerer::new();
|
||||
if let Some(jm) = lowerer.lower_case_a_for_stage1_resolver(target_func, &loop_form) {
|
||||
if let Some(jm) = lowerer.lower_case_a_for_stage1_resolver(target_func, &loop_form)
|
||||
{
|
||||
eprintln!(
|
||||
"[joinir/stage1_using_resolver/generic-hook] LoopToJoinLowerer produced JoinIR, returning early"
|
||||
);
|
||||
|
||||
@ -170,7 +170,9 @@ fn lower_from_mir(module: &crate::mir::MirModule) -> Option<JoinModule> {
|
||||
|
||||
// stageb_funcscanner: entry_is_preheader=true, has_break=true
|
||||
let Some(loop_form) = construct_simple_while_loopform(entry, &query, true, true) else {
|
||||
eprintln!("[joinir/stageb_funcscanner/generic-hook] failed to construct LoopForm from CFG");
|
||||
eprintln!(
|
||||
"[joinir/stageb_funcscanner/generic-hook] failed to construct LoopForm from CFG"
|
||||
);
|
||||
return build_stageb_funcscanner_joinir(module);
|
||||
};
|
||||
|
||||
|
||||
@ -36,9 +36,7 @@ pub enum ProgressError {
|
||||
call_index: usize,
|
||||
},
|
||||
/// No recursive call found in loop function
|
||||
NoRecursiveCall {
|
||||
loop_func_id: JoinFuncId,
|
||||
},
|
||||
NoRecursiveCall { loop_func_id: JoinFuncId },
|
||||
/// Progress carrier not found
|
||||
ProgressCarrierNotFound {
|
||||
expected_param_index: usize,
|
||||
|
||||
@ -128,34 +128,54 @@ fn execute_function(
|
||||
return Ok(ret);
|
||||
}
|
||||
// Phase 33: Select instruction execution
|
||||
JoinInst::Select { dst, cond, then_val, else_val } => {
|
||||
JoinInst::Select {
|
||||
dst,
|
||||
cond,
|
||||
then_val,
|
||||
else_val,
|
||||
} => {
|
||||
// 1. Evaluate cond (Bool or Int)
|
||||
let cond_value = read_var(&locals, *cond)?;
|
||||
eprintln!("[SELECT DEBUG] cond={:?}, cond_value={:?}", cond, cond_value);
|
||||
eprintln!(
|
||||
"[SELECT DEBUG] cond={:?}, cond_value={:?}",
|
||||
cond, cond_value
|
||||
);
|
||||
let cond_bool = match cond_value {
|
||||
JoinValue::Bool(b) => b,
|
||||
JoinValue::Int(i) => i != 0, // Int も許す(0=false, それ以外=true)
|
||||
_ => return Err(JoinRuntimeError::new(format!(
|
||||
"Select: cond must be Bool or Int, got {:?}", cond_value
|
||||
))),
|
||||
_ => {
|
||||
return Err(JoinRuntimeError::new(format!(
|
||||
"Select: cond must be Bool or Int, got {:?}",
|
||||
cond_value
|
||||
)))
|
||||
}
|
||||
};
|
||||
|
||||
// 2. Select then_val or else_val
|
||||
let then_value = read_var(&locals, *then_val)?;
|
||||
let else_value = read_var(&locals, *else_val)?;
|
||||
eprintln!("[SELECT DEBUG] cond_bool={}, then_val={:?}={:?}, else_val={:?}={:?}",
|
||||
cond_bool, then_val, then_value, else_val, else_value);
|
||||
eprintln!(
|
||||
"[SELECT DEBUG] cond_bool={}, then_val={:?}={:?}, else_val={:?}={:?}",
|
||||
cond_bool, then_val, then_value, else_val, else_value
|
||||
);
|
||||
|
||||
let selected_id = if cond_bool { *then_val } else { *else_val };
|
||||
let selected_value = read_var(&locals, selected_id)?;
|
||||
eprintln!("[SELECT DEBUG] selected_id={:?}, selected_value={:?}", selected_id, selected_value);
|
||||
eprintln!(
|
||||
"[SELECT DEBUG] selected_id={:?}, selected_value={:?}",
|
||||
selected_id, selected_value
|
||||
);
|
||||
|
||||
// 3. Write to dst
|
||||
locals.insert(*dst, selected_value);
|
||||
ip += 1;
|
||||
}
|
||||
// Phase 33-6: IfMerge instruction execution (複数変数 PHI)
|
||||
JoinInst::IfMerge { cond, merges, k_next } => {
|
||||
JoinInst::IfMerge {
|
||||
cond,
|
||||
merges,
|
||||
k_next,
|
||||
} => {
|
||||
// Phase 33-6 最小実装: k_next は None のみサポート
|
||||
if k_next.is_some() {
|
||||
return Err(JoinRuntimeError::new(
|
||||
@ -168,14 +188,21 @@ fn execute_function(
|
||||
let cond_bool = match cond_value {
|
||||
JoinValue::Bool(b) => b,
|
||||
JoinValue::Int(i) => i != 0,
|
||||
_ => return Err(JoinRuntimeError::new(format!(
|
||||
"IfMerge: cond must be Bool or Int, got {:?}", cond_value
|
||||
))),
|
||||
_ => {
|
||||
return Err(JoinRuntimeError::new(format!(
|
||||
"IfMerge: cond must be Bool or Int, got {:?}",
|
||||
cond_value
|
||||
)))
|
||||
}
|
||||
};
|
||||
|
||||
// 2. 各 merge ペアについて、cond に応じて値を選択して代入
|
||||
for merge in merges {
|
||||
let selected_id = if cond_bool { merge.then_val } else { merge.else_val };
|
||||
let selected_id = if cond_bool {
|
||||
merge.then_val
|
||||
} else {
|
||||
merge.else_val
|
||||
};
|
||||
let selected_value = read_var(&locals, selected_id)?;
|
||||
locals.insert(merge.dst, selected_value);
|
||||
}
|
||||
@ -313,9 +340,9 @@ fn as_bool(value: &JoinValue) -> Result<bool, JoinRuntimeError> {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::backend::mir_interpreter::MirInterpreter;
|
||||
use crate::mir::join_ir::{ConstValue, JoinFunction, JoinModule};
|
||||
use crate::mir::ValueId;
|
||||
use crate::backend::mir_interpreter::MirInterpreter;
|
||||
|
||||
#[test]
|
||||
fn test_select_true() {
|
||||
@ -356,7 +383,9 @@ mod tests {
|
||||
});
|
||||
|
||||
// return v4
|
||||
func.body.push(JoinInst::Ret { value: Some(v_result) });
|
||||
func.body.push(JoinInst::Ret {
|
||||
value: Some(v_result),
|
||||
});
|
||||
|
||||
module.add_function(func);
|
||||
|
||||
@ -405,7 +434,9 @@ mod tests {
|
||||
});
|
||||
|
||||
// return v4
|
||||
func.body.push(JoinInst::Ret { value: Some(v_result) });
|
||||
func.body.push(JoinInst::Ret {
|
||||
value: Some(v_result),
|
||||
});
|
||||
|
||||
module.add_function(func);
|
||||
|
||||
@ -453,7 +484,9 @@ mod tests {
|
||||
});
|
||||
|
||||
// return v4
|
||||
func.body.push(JoinInst::Ret { value: Some(v_result) });
|
||||
func.body.push(JoinInst::Ret {
|
||||
value: Some(v_result),
|
||||
});
|
||||
|
||||
module.add_function(func);
|
||||
|
||||
@ -720,7 +753,9 @@ mod tests {
|
||||
rhs: v_result_z,
|
||||
}));
|
||||
|
||||
func.body.push(JoinInst::Ret { value: Some(v_sum_xyz) });
|
||||
func.body.push(JoinInst::Ret {
|
||||
value: Some(v_sum_xyz),
|
||||
});
|
||||
|
||||
module.add_function(func);
|
||||
|
||||
|
||||
18
src/mir/join_ir_vm_bridge/README.md
Normal file
18
src/mir/join_ir_vm_bridge/README.md
Normal file
@ -0,0 +1,18 @@
|
||||
JoinIR → VM bridge layer
|
||||
|
||||
Responsibilities:
|
||||
- Convert normalized JoinIR modules into MIR for the Rust VM without changing semantics.
|
||||
- Provide a thin runner helper that executes a JoinIR entry via the VM.
|
||||
- Host experimental metadata-aware paths (Phase 40-1) behind clearly marked helpers.
|
||||
|
||||
Boundaries:
|
||||
- No new control-flow semantics or heuristics here; this layer only maps structures already normalized by JoinIR.
|
||||
- Keep type information minimal (MirType::Unknown) and avoid adding inference or guessing.
|
||||
- Debug/diagnostic output must stay behind `NYASH_JOINIR_VM_BRIDGE_DEBUG=1`.
|
||||
|
||||
File layout:
|
||||
- `mod.rs`: public surface + shared helpers (naming, error, logging)
|
||||
- `convert.rs`: JoinIR→MIR lowering (functions/blocks/instructions)
|
||||
- `runner.rs`: VM execution entry (`run_joinir_via_vm`)
|
||||
- `meta.rs`: experimental metadata-aware conversion hooks
|
||||
- `tests.rs`: bridge-specific unit tests (kept local to avoid cross-layer leakage)
|
||||
@ -1,67 +1,13 @@
|
||||
//! Phase 27-shortterm S-4: JoinIR → Rust VM Bridge
|
||||
//!
|
||||
//! 目的: JoinIR(正規化された IR)を Rust VM で実行するブリッジ層
|
||||
//!
|
||||
//! ## Architecture
|
||||
//! ```text
|
||||
//! JoinIR (normalized) → MirModule → Rust VM → Result
|
||||
//! ↑ ↑ ↑
|
||||
//! PHI bugs VM input Execution
|
||||
//! eliminated format (GC, plugins)
|
||||
//! ```
|
||||
//!
|
||||
//! ## Design Principles
|
||||
//! - JoinIR の正規化構造を保持したまま VM に渡す
|
||||
//! - マッピングだけで済ませる(JoinIR でやった正規化は消えない)
|
||||
//! - VM の機能(GC、プラグイン、エラーハンドリング)を活用
|
||||
//!
|
||||
//! ## Minimal Instruction Set (S-4.3)
|
||||
//! - **Compute**: Const, BinOp, Compare
|
||||
//! - **BoxCall**: StringBox メソッド呼び出し
|
||||
//! - **Call/Jump/Ret**: 制御フロー
|
||||
//!
|
||||
//! Phase 27-shortterm scope: skip_ws で green 化できれば成功
|
||||
|
||||
use crate::backend::{MirInterpreter, VMError, VMValue};
|
||||
use crate::config::env::joinir_vm_bridge_debug;
|
||||
use crate::mir::join_ir::{
|
||||
BinOpKind, CompareOp, ConstValue, JoinFuncId, JoinInst, JoinModule, MirLikeInst,
|
||||
};
|
||||
use crate::mir::join_ir_ops::JoinValue;
|
||||
use crate::ast::Span;
|
||||
use crate::mir::join_ir::{
|
||||
BinOpKind, CompareOp, ConstValue, JoinFunction, JoinInst, JoinModule, MirLikeInst,
|
||||
};
|
||||
use crate::mir::{
|
||||
BasicBlockId, BinaryOp, CompareOp as MirCompareOp, ConstValue as MirConstValue, EffectMask,
|
||||
FunctionSignature, MirFunction, MirInstruction, MirModule, MirType, ValueId,
|
||||
};
|
||||
|
||||
/// 条件付きデバッグ出力マクロ(NYASH_JOINIR_VM_BRIDGE_DEBUG=1 で有効)
|
||||
macro_rules! debug_log {
|
||||
($($arg:tt)*) => {
|
||||
if joinir_vm_bridge_debug() {
|
||||
eprintln!($($arg)*);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// Phase 27-shortterm S-4 エラー型
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct JoinIrVmBridgeError {
|
||||
pub message: String,
|
||||
}
|
||||
|
||||
impl JoinIrVmBridgeError {
|
||||
pub fn new(msg: impl Into<String>) -> Self {
|
||||
Self {
|
||||
message: msg.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<VMError> for JoinIrVmBridgeError {
|
||||
fn from(err: VMError) -> Self {
|
||||
JoinIrVmBridgeError::new(format!("VM error: {:?}", err))
|
||||
}
|
||||
}
|
||||
use super::{join_func_name, JoinIrVmBridgeError};
|
||||
|
||||
/// ブロックを確定する(instructions + spans + terminator を設定)
|
||||
fn finalize_block(
|
||||
@ -78,88 +24,28 @@ fn finalize_block(
|
||||
}
|
||||
}
|
||||
|
||||
/// JoinFuncId から MIR 用の関数名を生成
|
||||
fn join_func_name(id: JoinFuncId) -> String {
|
||||
format!("join_func_{}", id.0)
|
||||
}
|
||||
|
||||
/// Phase 27-shortterm S-4.3: JoinIR → VM 実行のエントリーポイント
|
||||
///
|
||||
/// ## Arguments
|
||||
/// - `join_module`: JoinIR モジュール(正規化済み)
|
||||
/// - `entry_func`: エントリーポイント関数ID
|
||||
/// - `args`: 初期引数(JoinValue 形式)
|
||||
///
|
||||
/// ## Returns
|
||||
/// - `Ok(JoinValue)`: 実行結果
|
||||
/// - `Err(JoinIrVmBridgeError)`: 変換エラーまたは実行エラー
|
||||
///
|
||||
/// ## Example
|
||||
/// ```ignore
|
||||
/// let join_module = lower_skip_ws_to_joinir(&mir_module)?;
|
||||
/// let result = run_joinir_via_vm(
|
||||
/// &join_module,
|
||||
/// JoinFuncId::new(0),
|
||||
/// &[JoinValue::Str(" hello".to_string()), JoinValue::Int(7)]
|
||||
/// )?;
|
||||
/// assert_eq!(result, JoinValue::Int(2));
|
||||
/// ```
|
||||
pub fn run_joinir_via_vm(
|
||||
join_module: &JoinModule,
|
||||
entry_func: JoinFuncId,
|
||||
args: &[JoinValue],
|
||||
) -> Result<JoinValue, JoinIrVmBridgeError> {
|
||||
debug_log!("[joinir_vm_bridge] Phase 27-shortterm S-4.3");
|
||||
debug_log!("[joinir_vm_bridge] Converting JoinIR to MIR for VM execution");
|
||||
|
||||
// Step 1: JoinIR → MIR 変換
|
||||
let mir_module = convert_joinir_to_mir(join_module)?;
|
||||
|
||||
debug_log!(
|
||||
"[joinir_vm_bridge] Converted {} JoinIR functions to MIR",
|
||||
join_module.functions.len()
|
||||
);
|
||||
|
||||
// Step 2: VM 実行
|
||||
let mut vm = MirInterpreter::new();
|
||||
|
||||
debug_log!(
|
||||
"[joinir_vm_bridge] Executing via VM with {} arguments",
|
||||
args.len()
|
||||
);
|
||||
|
||||
// Convert JoinValue → VMValue (BoxRef 含む)
|
||||
let vm_args: Vec<VMValue> = args.iter().cloned().map(|v| v.into_vm_value()).collect();
|
||||
|
||||
let entry_name = join_func_name(entry_func);
|
||||
let result = vm.execute_function_with_args(&mir_module, &entry_name, &vm_args)?;
|
||||
|
||||
// Step 3: VMValue → JoinValue 変換
|
||||
let join_result = JoinValue::from_vm_value(&result)
|
||||
.map_err(|e| JoinIrVmBridgeError::new(format!("Result conversion error: {}", e.message)))?;
|
||||
|
||||
debug_log!("[joinir_vm_bridge] Execution succeeded: {:?}", join_result);
|
||||
|
||||
Ok(join_result)
|
||||
}
|
||||
|
||||
/// Phase 30.x: JoinIR → MIR 変換器
|
||||
///
|
||||
/// Phase 32 L-2.2 Step-3: テストから呼び出し可能に `pub(crate)` 化
|
||||
pub(crate) fn convert_joinir_to_mir(join_module: &JoinModule) -> Result<MirModule, JoinIrVmBridgeError> {
|
||||
pub(crate) fn convert_joinir_to_mir(
|
||||
join_module: &JoinModule,
|
||||
) -> Result<MirModule, JoinIrVmBridgeError> {
|
||||
let mut mir_module = MirModule::new("joinir_bridge".to_string());
|
||||
|
||||
// Convert all JoinIR functions to MIR (entry function becomes "skip" or similar)
|
||||
for (func_id, join_func) in &join_module.functions {
|
||||
debug_log!(
|
||||
"[joinir_vm_bridge] Converting JoinFunction {} ({})",
|
||||
func_id.0, join_func.name
|
||||
func_id.0,
|
||||
join_func.name
|
||||
);
|
||||
|
||||
let mir_func = convert_join_function_to_mir(join_func)?;
|
||||
|
||||
// Use actual function name (not "main") since we'll create a wrapper
|
||||
mir_module.functions.insert(join_func_name(*func_id), mir_func);
|
||||
mir_module
|
||||
.functions
|
||||
.insert(join_func_name(*func_id), mir_func);
|
||||
}
|
||||
|
||||
Ok(mir_module)
|
||||
@ -265,8 +151,8 @@ pub(crate) fn convert_joinir_to_mir(join_module: &JoinModule) -> Result<MirModul
|
||||
///
|
||||
/// - AST lowering: `ast_lowerer.rs::extract_if_in_loop_modified_vars()`
|
||||
/// - Design: `docs/.../phase-39-if-phi-level2/joinir_extension_design.md`
|
||||
fn convert_join_function_to_mir(
|
||||
join_func: &crate::mir::join_ir::JoinFunction,
|
||||
pub(crate) fn convert_join_function_to_mir(
|
||||
join_func: &JoinFunction,
|
||||
) -> Result<MirFunction, JoinIrVmBridgeError> {
|
||||
// TODO(Phase 40-1): Generate loop exit PHI for if-in-loop modified variables
|
||||
// See comment above for implementation details
|
||||
@ -313,7 +199,12 @@ fn convert_join_function_to_mir(
|
||||
let mir_inst = convert_mir_like_inst(mir_like)?;
|
||||
current_instructions.push(mir_inst);
|
||||
}
|
||||
JoinInst::MethodCall { dst, receiver, method, args } => {
|
||||
JoinInst::MethodCall {
|
||||
dst,
|
||||
receiver,
|
||||
method,
|
||||
args,
|
||||
} => {
|
||||
// Phase 34-6: MethodCall → MIR BoxCall 変換
|
||||
// receiver.method(args...) を BoxCall(receiver, method, args) に変換
|
||||
let mir_inst = MirInstruction::BoxCall {
|
||||
@ -402,19 +293,31 @@ Call {{\n\
|
||||
});
|
||||
|
||||
// Return the result of the tail call
|
||||
let terminator = MirInstruction::Return { value: Some(call_result_id) };
|
||||
finalize_block(&mut mir_func, current_block_id, current_instructions, terminator);
|
||||
let terminator = MirInstruction::Return {
|
||||
value: Some(call_result_id),
|
||||
};
|
||||
finalize_block(
|
||||
&mut mir_func,
|
||||
current_block_id,
|
||||
current_instructions,
|
||||
terminator,
|
||||
);
|
||||
current_instructions = Vec::new();
|
||||
}
|
||||
}
|
||||
}
|
||||
JoinInst::Jump { cont, args, cond } => {
|
||||
JoinInst::Jump {
|
||||
cont: _,
|
||||
args,
|
||||
cond,
|
||||
} => {
|
||||
// Phase 27-shortterm S-4.4-A: Jump with condition → Branch + Return
|
||||
// Jump represents an exit continuation (k_exit) in skip_ws pattern
|
||||
|
||||
debug_log!(
|
||||
"[joinir_vm_bridge] Converting Jump to cont={:?}, args={:?}, cond={:?}",
|
||||
cont, args, cond
|
||||
"[joinir_vm_bridge] Converting Jump args={:?}, cond={:?}",
|
||||
args,
|
||||
cond
|
||||
);
|
||||
|
||||
match cond {
|
||||
@ -433,7 +336,12 @@ Call {{\n\
|
||||
};
|
||||
|
||||
// Finalize current block with Branch terminator
|
||||
finalize_block(&mut mir_func, current_block_id, current_instructions, branch_terminator);
|
||||
finalize_block(
|
||||
&mut mir_func,
|
||||
current_block_id,
|
||||
current_instructions,
|
||||
branch_terminator,
|
||||
);
|
||||
|
||||
// Create exit block with Return terminator
|
||||
let exit_value = args.first().copied();
|
||||
@ -455,7 +363,12 @@ Call {{\n\
|
||||
let return_terminator = MirInstruction::Return { value: exit_value };
|
||||
|
||||
// Finalize current block with Return terminator
|
||||
finalize_block(&mut mir_func, current_block_id, current_instructions, return_terminator);
|
||||
finalize_block(
|
||||
&mut mir_func,
|
||||
current_block_id,
|
||||
current_instructions,
|
||||
return_terminator,
|
||||
);
|
||||
|
||||
// No continuation after unconditional return
|
||||
current_instructions = Vec::new();
|
||||
@ -463,13 +376,21 @@ Call {{\n\
|
||||
}
|
||||
}
|
||||
// Phase 33: Select instruction conversion to MIR
|
||||
JoinInst::Select { dst, cond, then_val, else_val } => {
|
||||
JoinInst::Select {
|
||||
dst,
|
||||
cond,
|
||||
then_val,
|
||||
else_val,
|
||||
} => {
|
||||
// Phase 33-2: Select を MIR の if/phi に変換
|
||||
// 最小実装: cond/then/else/merge の 4 ブロック構造
|
||||
|
||||
debug_log!(
|
||||
"[joinir_vm_bridge] Converting Select: dst={:?}, cond={:?}, then={:?}, else={:?}",
|
||||
dst, cond, then_val, else_val
|
||||
dst,
|
||||
cond,
|
||||
then_val,
|
||||
else_val
|
||||
);
|
||||
|
||||
// 1. cond ブロック(現在のブロック)
|
||||
@ -493,7 +414,12 @@ Call {{\n\
|
||||
then_bb: then_block,
|
||||
else_bb: else_block,
|
||||
};
|
||||
finalize_block(&mut mir_func, cond_block, current_instructions, branch_terminator);
|
||||
finalize_block(
|
||||
&mut mir_func,
|
||||
cond_block,
|
||||
current_instructions,
|
||||
branch_terminator,
|
||||
);
|
||||
|
||||
// 6. then ブロック: dst = then_val; jump merge
|
||||
let mut then_block_obj = crate::mir::BasicBlock::new(then_block);
|
||||
@ -502,7 +428,9 @@ Call {{\n\
|
||||
src: *then_val,
|
||||
});
|
||||
then_block_obj.instruction_spans.push(Span::unknown());
|
||||
then_block_obj.terminator = Some(MirInstruction::Jump { target: merge_block });
|
||||
then_block_obj.terminator = Some(MirInstruction::Jump {
|
||||
target: merge_block,
|
||||
});
|
||||
mir_func.blocks.insert(then_block, then_block_obj);
|
||||
|
||||
// 7. else ブロック: dst = else_val; jump merge
|
||||
@ -512,7 +440,9 @@ Call {{\n\
|
||||
src: *else_val,
|
||||
});
|
||||
else_block_obj.instruction_spans.push(Span::unknown());
|
||||
else_block_obj.terminator = Some(MirInstruction::Jump { target: merge_block });
|
||||
else_block_obj.terminator = Some(MirInstruction::Jump {
|
||||
target: merge_block,
|
||||
});
|
||||
mir_func.blocks.insert(else_block, else_block_obj);
|
||||
|
||||
// 8. merge ブロック作成(空)
|
||||
@ -524,20 +454,26 @@ Call {{\n\
|
||||
current_instructions = Vec::new();
|
||||
}
|
||||
// Phase 33-6: IfMerge instruction conversion to MIR
|
||||
JoinInst::IfMerge { cond, merges, k_next } => {
|
||||
JoinInst::IfMerge {
|
||||
cond,
|
||||
merges,
|
||||
k_next,
|
||||
} => {
|
||||
// Phase 33-6: IfMerge を MIR の if/phi に変換
|
||||
// Select と同じ 4 ブロック構造だが、複数の Copy を生成
|
||||
|
||||
// Phase 33-6 最小実装: k_next は None のみサポート
|
||||
if k_next.is_some() {
|
||||
return Err(JoinIrVmBridgeError::new(
|
||||
"IfMerge: k_next continuation is not yet supported (Phase 33-6 minimal)".to_string(),
|
||||
"IfMerge: k_next continuation is not yet supported (Phase 33-6 minimal)"
|
||||
.to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
debug_log!(
|
||||
"[joinir_vm_bridge] Converting IfMerge: cond={:?}, merges.len()={}",
|
||||
cond, merges.len()
|
||||
cond,
|
||||
merges.len()
|
||||
);
|
||||
|
||||
// 1. cond ブロック(現在のブロック)
|
||||
@ -561,7 +497,12 @@ Call {{\n\
|
||||
then_bb: then_block,
|
||||
else_bb: else_block,
|
||||
};
|
||||
finalize_block(&mut mir_func, cond_block, current_instructions, branch_terminator);
|
||||
finalize_block(
|
||||
&mut mir_func,
|
||||
cond_block,
|
||||
current_instructions,
|
||||
branch_terminator,
|
||||
);
|
||||
|
||||
// 6. then ブロック: 各 merge について dst = then_val; jump merge
|
||||
let mut then_block_obj = crate::mir::BasicBlock::new(then_block);
|
||||
@ -572,7 +513,9 @@ Call {{\n\
|
||||
});
|
||||
then_block_obj.instruction_spans.push(Span::unknown());
|
||||
}
|
||||
then_block_obj.terminator = Some(MirInstruction::Jump { target: merge_block });
|
||||
then_block_obj.terminator = Some(MirInstruction::Jump {
|
||||
target: merge_block,
|
||||
});
|
||||
mir_func.blocks.insert(then_block, then_block_obj);
|
||||
|
||||
// 7. else ブロック: 各 merge について dst = else_val; jump merge
|
||||
@ -584,7 +527,9 @@ Call {{\n\
|
||||
});
|
||||
else_block_obj.instruction_spans.push(Span::unknown());
|
||||
}
|
||||
else_block_obj.terminator = Some(MirInstruction::Jump { target: merge_block });
|
||||
else_block_obj.terminator = Some(MirInstruction::Jump {
|
||||
target: merge_block,
|
||||
});
|
||||
mir_func.blocks.insert(else_block, else_block_obj);
|
||||
|
||||
// 8. merge ブロック作成(空)
|
||||
@ -600,12 +545,21 @@ Call {{\n\
|
||||
let return_terminator = MirInstruction::Return { value: *value };
|
||||
|
||||
// Finalize current block with Return terminator
|
||||
finalize_block(&mut mir_func, current_block_id, current_instructions, return_terminator);
|
||||
finalize_block(
|
||||
&mut mir_func,
|
||||
current_block_id,
|
||||
current_instructions,
|
||||
return_terminator,
|
||||
);
|
||||
|
||||
current_instructions = Vec::new();
|
||||
}
|
||||
// Phase 41-4: NestedIfMerge instruction
|
||||
JoinInst::NestedIfMerge { conds, merges, k_next } => {
|
||||
JoinInst::NestedIfMerge {
|
||||
conds,
|
||||
merges,
|
||||
k_next,
|
||||
} => {
|
||||
// Phase 41-4.3: 多段 Branch + PHI を生成
|
||||
//
|
||||
// 構造:
|
||||
@ -632,7 +586,8 @@ Call {{\n\
|
||||
|
||||
debug_log!(
|
||||
"[joinir_vm_bridge] Converting NestedIfMerge: conds.len()={}, merges.len()={}",
|
||||
conds.len(), merges.len()
|
||||
conds.len(),
|
||||
merges.len()
|
||||
);
|
||||
|
||||
// 1. ブロックを事前確保
|
||||
@ -663,7 +618,9 @@ Call {{\n\
|
||||
// 2. level 1+ ブロックを事前作成(finalize_block は既存ブロックのみ更新)
|
||||
for level in 1..num_conds {
|
||||
let level_block = level_blocks[level];
|
||||
mir_func.blocks.insert(level_block, crate::mir::BasicBlock::new(level_block));
|
||||
mir_func
|
||||
.blocks
|
||||
.insert(level_block, crate::mir::BasicBlock::new(level_block));
|
||||
}
|
||||
|
||||
// 3. 各レベルで分岐を生成
|
||||
@ -686,7 +643,12 @@ Call {{\n\
|
||||
|
||||
if level == 0 {
|
||||
// level 0 は current_block、current_instructions を使う
|
||||
finalize_block(&mut mir_func, this_block, current_instructions.clone(), branch_terminator);
|
||||
finalize_block(
|
||||
&mut mir_func,
|
||||
this_block,
|
||||
current_instructions.clone(),
|
||||
branch_terminator,
|
||||
);
|
||||
current_instructions.clear();
|
||||
} else {
|
||||
// level 1+ は事前作成済みブロックを更新
|
||||
@ -703,7 +665,9 @@ Call {{\n\
|
||||
});
|
||||
then_block_obj.instruction_spans.push(Span::unknown());
|
||||
}
|
||||
then_block_obj.terminator = Some(MirInstruction::Jump { target: merge_block });
|
||||
then_block_obj.terminator = Some(MirInstruction::Jump {
|
||||
target: merge_block,
|
||||
});
|
||||
mir_func.blocks.insert(then_block, then_block_obj);
|
||||
|
||||
// 4. final_else_block: いずれかの条件 false の場合 → else_val を Copy
|
||||
@ -715,7 +679,9 @@ Call {{\n\
|
||||
});
|
||||
else_block_obj.instruction_spans.push(Span::unknown());
|
||||
}
|
||||
else_block_obj.terminator = Some(MirInstruction::Jump { target: merge_block });
|
||||
else_block_obj.terminator = Some(MirInstruction::Jump {
|
||||
target: merge_block,
|
||||
});
|
||||
mir_func.blocks.insert(final_else_block, else_block_obj);
|
||||
|
||||
// 5. merge_block(空)
|
||||
@ -763,7 +729,9 @@ Call {{\n\
|
||||
}
|
||||
|
||||
/// MirLikeInst → MirInstruction 変換
|
||||
fn convert_mir_like_inst(mir_like: &MirLikeInst) -> Result<MirInstruction, JoinIrVmBridgeError> {
|
||||
pub(crate) fn convert_mir_like_inst(
|
||||
mir_like: &MirLikeInst,
|
||||
) -> Result<MirInstruction, JoinIrVmBridgeError> {
|
||||
match mir_like {
|
||||
MirLikeInst::Const { dst, value } => {
|
||||
let mir_const = match value {
|
||||
@ -839,422 +807,3 @@ fn convert_mir_like_inst(mir_like: &MirLikeInst) -> Result<MirInstruction, JoinI
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// Phase 40-1 Experimental API
|
||||
// ========================================
|
||||
|
||||
use crate::mir::join_ir::frontend::{JoinFuncMeta, JoinFuncMetaMap};
|
||||
use crate::mir::builder::MirBuilder;
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
/// Phase 40-1実験用: JoinFuncMetaを使ったMIR変換
|
||||
///
|
||||
/// 既存の run_joinir_via_vm() を拡張し、
|
||||
/// if_modified_varsがあればloop exit PHIを生成する。
|
||||
///
|
||||
/// # Phase 40-1専用
|
||||
/// この関数はPhase 40-1 A/Bテスト専用。
|
||||
/// 本番パスでは使わない(従来のrun_joinir_via_vm()を使う)。
|
||||
///
|
||||
/// # Architecture
|
||||
/// JoinModule → MirModule変換において、JoinFuncMetaを参照してPHI生成を拡張
|
||||
///
|
||||
/// # Returns
|
||||
/// - `Ok(MirModule)`: 変換済みMIRモジュール(PHI拡張版)
|
||||
pub fn convert_join_module_to_mir_with_meta(
|
||||
module: &JoinModule,
|
||||
meta: &JoinFuncMetaMap,
|
||||
) -> Result<MirModule, JoinIrVmBridgeError> {
|
||||
debug_log!("[Phase 40-1] convert_join_module_to_mir_with_meta");
|
||||
|
||||
let mut mir_module = MirModule::new("joinir_bridge_with_meta".to_string());
|
||||
|
||||
// 1. 各関数を変換
|
||||
for (func_id, join_func) in &module.functions {
|
||||
debug_log!(
|
||||
"[Phase 40-1] Converting JoinFunction {} ({})",
|
||||
func_id.0, join_func.name
|
||||
);
|
||||
|
||||
// 2. 基本のMIR変換(既存ロジック)
|
||||
let mut mir_func = convert_join_function_to_mir(join_func)?;
|
||||
|
||||
// 3. Phase 40-1: if_modified_varsがあればloop exit PHI生成
|
||||
if let Some(m) = meta.get(func_id) {
|
||||
if let Some(if_vars) = &m.if_modified_vars {
|
||||
debug_log!(
|
||||
"[Phase 40-1] Found if_modified_vars for func {:?}: {:?}",
|
||||
func_id,
|
||||
if_vars
|
||||
);
|
||||
|
||||
// TODO(Phase 40-1.2): emit_loop_exit_phi_for_if_modified()実装後に有効化
|
||||
// emit_loop_exit_phi_for_if_modified(&mut mir_func, join_func, if_vars)?;
|
||||
}
|
||||
}
|
||||
|
||||
mir_module.functions.insert(join_func_name(*func_id), mir_func);
|
||||
}
|
||||
|
||||
Ok(mir_module)
|
||||
}
|
||||
|
||||
/// if-in-loop modified varsに対するloop exit PHI生成
|
||||
///
|
||||
/// # Purpose
|
||||
/// JoinIR Frontendで検出されたif-in-loop修正変数に対して、
|
||||
/// loop exit blockにPHI命令を追加する。
|
||||
///
|
||||
/// # Arguments
|
||||
/// - `mir_func`: 変換済みMIR関数(ミュータブル)
|
||||
/// - `join_func`: 元のJoinIR関数(メタデータ参照用)
|
||||
/// - `if_modified_vars`: if-in-loop修正変数名のセット
|
||||
///
|
||||
/// # Implementation Note
|
||||
/// 現在の実装では、JoinIRのloop_step関数は単一ブロックベースであり、
|
||||
/// exit blockの特定が困難。Phase 40-1では**ログ出力のみ**を行い、
|
||||
/// 実際のPHI生成はPhase 40-2以降で実装する。
|
||||
///
|
||||
/// # TODO(Phase 40-2)
|
||||
/// - exit block特定ロジック実装
|
||||
/// - PHI incoming value特定(header vs loop body)
|
||||
/// - PHI命令生成とブロックへの挿入
|
||||
#[allow(dead_code)]
|
||||
fn emit_loop_exit_phi_for_if_modified(
|
||||
_mir_func: &mut MirFunction,
|
||||
join_func: &crate::mir::join_ir::JoinFunction,
|
||||
if_modified_vars: &std::collections::HashSet<String>,
|
||||
) -> Result<(), JoinIrVmBridgeError> {
|
||||
debug_log!(
|
||||
"[Phase 40-1] emit_loop_exit_phi_for_if_modified: func={}, vars={:?}",
|
||||
join_func.name,
|
||||
if_modified_vars
|
||||
);
|
||||
|
||||
// Phase 40-1 minimal implementation: ログ出力のみ
|
||||
// 理由: JoinIRのloop_step関数はtail-recursiveで、exit blockが明示的でない
|
||||
// TODO(Phase 40-2): JoinIR構造を拡張してexit block情報を保持
|
||||
|
||||
if !if_modified_vars.is_empty() {
|
||||
debug_log!(
|
||||
"[Phase 40-1] Would generate {} loop exit PHIs for: {:?}",
|
||||
if_modified_vars.len(),
|
||||
if_modified_vars
|
||||
);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_convert_const_inst() {
|
||||
let join_const = MirLikeInst::Const {
|
||||
dst: ValueId(10),
|
||||
value: ConstValue::Integer(42),
|
||||
};
|
||||
|
||||
let mir_inst = convert_mir_like_inst(&join_const).unwrap();
|
||||
|
||||
match mir_inst {
|
||||
MirInstruction::Const { dst, value } => {
|
||||
assert_eq!(dst, ValueId(10));
|
||||
assert!(matches!(value, MirConstValue::Integer(42)));
|
||||
}
|
||||
_ => panic!("Expected Const instruction"),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_convert_binop_inst() {
|
||||
let join_binop = MirLikeInst::BinOp {
|
||||
dst: ValueId(20),
|
||||
op: BinOpKind::Add,
|
||||
lhs: ValueId(10),
|
||||
rhs: ValueId(11),
|
||||
};
|
||||
|
||||
let mir_inst = convert_mir_like_inst(&join_binop).unwrap();
|
||||
|
||||
match mir_inst {
|
||||
MirInstruction::BinOp { dst, op, lhs, rhs } => {
|
||||
assert_eq!(dst, ValueId(20));
|
||||
assert_eq!(op, BinaryOp::Add);
|
||||
assert_eq!(lhs, ValueId(10));
|
||||
assert_eq!(rhs, ValueId(11));
|
||||
}
|
||||
_ => panic!("Expected BinOp instruction"),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_convert_compare_inst() {
|
||||
let join_cmp = MirLikeInst::Compare {
|
||||
dst: ValueId(30),
|
||||
op: CompareOp::Ge,
|
||||
lhs: ValueId(10),
|
||||
rhs: ValueId(11),
|
||||
};
|
||||
|
||||
let mir_inst = convert_mir_like_inst(&join_cmp).unwrap();
|
||||
|
||||
match mir_inst {
|
||||
MirInstruction::Compare { dst, op, lhs, rhs } => {
|
||||
assert_eq!(dst, ValueId(30));
|
||||
assert_eq!(op, MirCompareOp::Ge);
|
||||
assert_eq!(lhs, ValueId(10));
|
||||
assert_eq!(rhs, ValueId(11));
|
||||
}
|
||||
_ => panic!("Expected Compare instruction"),
|
||||
}
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// Phase 45: read_quoted_from Bridge Tests
|
||||
// ========================================
|
||||
|
||||
/// Phase 45: read_quoted_from JoinIR → MIR 変換テスト
|
||||
///
|
||||
/// HAKO_JOINIR_READ_QUOTED=1 が設定されている場合のみ実行。
|
||||
#[test]
|
||||
fn test_read_quoted_from_joinir_to_mir_conversion() {
|
||||
// Dev flag がない場合はスキップ
|
||||
if std::env::var("HAKO_JOINIR_READ_QUOTED").ok().as_deref() != Some("1") {
|
||||
eprintln!(
|
||||
"[Phase 45] Skipping test_read_quoted_from_joinir_to_mir_conversion: \
|
||||
Set HAKO_JOINIR_READ_QUOTED=1 to enable"
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
use crate::mir::join_ir::frontend::AstToJoinIrLowerer;
|
||||
|
||||
// 1. JoinModule を生成(lower_read_quoted_pattern を使用)
|
||||
let program_json = serde_json::json!({
|
||||
"defs": [{
|
||||
"name": "read_quoted_from",
|
||||
"params": ["s", "pos"],
|
||||
"body": { "body": [] }
|
||||
}]
|
||||
});
|
||||
|
||||
let mut lowerer = AstToJoinIrLowerer::new();
|
||||
let join_module = lowerer.lower_read_quoted_pattern(&program_json);
|
||||
|
||||
// 2. JoinIR → MIR 変換
|
||||
let mir_module = convert_joinir_to_mir(&join_module);
|
||||
|
||||
assert!(
|
||||
mir_module.is_ok(),
|
||||
"JoinIR → MIR conversion should succeed: {:?}",
|
||||
mir_module.err()
|
||||
);
|
||||
|
||||
let mir_module = mir_module.unwrap();
|
||||
|
||||
// 3. MIR 構造の検証
|
||||
// 4 つの関数がある: entry, k_guard_fail, loop_step, k_exit
|
||||
assert_eq!(
|
||||
mir_module.functions.len(),
|
||||
4,
|
||||
"MIR should have 4 functions"
|
||||
);
|
||||
|
||||
// 関数名を確認
|
||||
let func_names: Vec<&str> = mir_module.functions.keys().map(|s| s.as_str()).collect();
|
||||
eprintln!("[Phase 45] MIR function names: {:?}", func_names);
|
||||
|
||||
// join_func_0 (entry), join_func_1 (loop_step), join_func_2 (k_exit), join_func_3 (k_guard_fail)
|
||||
assert!(
|
||||
func_names.contains(&"join_func_0"),
|
||||
"Should have entry function join_func_0"
|
||||
);
|
||||
assert!(
|
||||
func_names.contains(&"join_func_1"),
|
||||
"Should have loop_step function join_func_1"
|
||||
);
|
||||
assert!(
|
||||
func_names.contains(&"join_func_2"),
|
||||
"Should have k_exit function join_func_2"
|
||||
);
|
||||
assert!(
|
||||
func_names.contains(&"join_func_3"),
|
||||
"Should have k_guard_fail function join_func_3"
|
||||
);
|
||||
|
||||
eprintln!("[Phase 45] test_read_quoted_from_joinir_to_mir_conversion PASSED");
|
||||
}
|
||||
|
||||
/// Phase 45: String 定数の MIR 変換テスト
|
||||
#[test]
|
||||
fn test_convert_string_const_inst() {
|
||||
let join_const = MirLikeInst::Const {
|
||||
dst: ValueId(50),
|
||||
value: ConstValue::String("\"".to_string()),
|
||||
};
|
||||
|
||||
let mir_inst = convert_mir_like_inst(&join_const).unwrap();
|
||||
|
||||
match mir_inst {
|
||||
MirInstruction::Const { dst, value } => {
|
||||
assert_eq!(dst, ValueId(50));
|
||||
match value {
|
||||
MirConstValue::String(s) => assert_eq!(s, "\""),
|
||||
_ => panic!("Expected String value"),
|
||||
}
|
||||
}
|
||||
_ => panic!("Expected Const instruction"),
|
||||
}
|
||||
}
|
||||
|
||||
/// Phase 45: A/B テスト - Route B (JoinIR) E2E 実行テスト
|
||||
///
|
||||
/// HAKO_JOINIR_READ_QUOTED=1 が設定されている場合のみ実行。
|
||||
///
|
||||
/// # Test Cases (from Phase 45 fixture)
|
||||
///
|
||||
/// - T1: `"abc"` at pos 0 → `abc`
|
||||
/// - T2: `""` at pos 0 → `` (empty)
|
||||
/// - T3: `abc` at pos 0 → `` (guard fail, no quote)
|
||||
/// - T4: `xx"def"` at pos 2 → `def`
|
||||
///
|
||||
/// # Known Limitation
|
||||
///
|
||||
/// T5 (escape handling) is skipped due to known PHI issue
|
||||
/// with variable reassignment inside if-blocks.
|
||||
#[test]
|
||||
fn test_read_quoted_from_route_b_e2e() {
|
||||
// Dev flag がない場合はスキップ
|
||||
if std::env::var("HAKO_JOINIR_READ_QUOTED").ok().as_deref() != Some("1") {
|
||||
eprintln!(
|
||||
"[Phase 45] Skipping test_read_quoted_from_route_b_e2e: \
|
||||
Set HAKO_JOINIR_READ_QUOTED=1 to enable"
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
use crate::mir::join_ir::frontend::AstToJoinIrLowerer;
|
||||
use crate::mir::join_ir_ops::JoinValue;
|
||||
|
||||
// 1. JoinModule を生成
|
||||
let program_json = serde_json::json!({
|
||||
"defs": [{
|
||||
"name": "read_quoted_from",
|
||||
"params": ["s", "pos"],
|
||||
"body": { "body": [] }
|
||||
}]
|
||||
});
|
||||
|
||||
let mut lowerer = AstToJoinIrLowerer::new();
|
||||
let join_module = lowerer.lower_read_quoted_pattern(&program_json);
|
||||
|
||||
let entry_func = join_module.entry.expect("Entry function should exist");
|
||||
|
||||
// 2. A/B テスト実行
|
||||
// Note: Route B (JoinIR) は run_joinir_via_vm で実行
|
||||
// Route A (既存) は別途フィクスチャで検証済み
|
||||
|
||||
// T1: "abc" at pos 0 → "abc"
|
||||
let t1_result = run_joinir_via_vm(
|
||||
&join_module,
|
||||
entry_func,
|
||||
&[JoinValue::Str("\"abc\"".to_string()), JoinValue::Int(0)],
|
||||
);
|
||||
match &t1_result {
|
||||
Ok(JoinValue::Str(s)) => {
|
||||
assert_eq!(s, "abc", "T1: Expected 'abc', got '{}'", s);
|
||||
eprintln!("[Phase 45] T1 PASS: \"abc\" at pos 0 → '{}'", s);
|
||||
}
|
||||
Ok(v) => panic!("T1: Expected Str, got {:?}", v),
|
||||
Err(e) => eprintln!("[Phase 45] T1 SKIP (execution not supported): {:?}", e),
|
||||
}
|
||||
|
||||
// T2: "" at pos 0 → "" (empty)
|
||||
let t2_result = run_joinir_via_vm(
|
||||
&join_module,
|
||||
entry_func,
|
||||
&[JoinValue::Str("\"\"".to_string()), JoinValue::Int(0)],
|
||||
);
|
||||
match &t2_result {
|
||||
Ok(JoinValue::Str(s)) => {
|
||||
assert_eq!(s, "", "T2: Expected '', got '{}'", s);
|
||||
eprintln!("[Phase 45] T2 PASS: \"\" at pos 0 → '{}'", s);
|
||||
}
|
||||
Ok(v) => panic!("T2: Expected Str, got {:?}", v),
|
||||
Err(e) => eprintln!("[Phase 45] T2 SKIP (execution not supported): {:?}", e),
|
||||
}
|
||||
|
||||
// T3: abc at pos 0 → "" (guard fail)
|
||||
let t3_result = run_joinir_via_vm(
|
||||
&join_module,
|
||||
entry_func,
|
||||
&[JoinValue::Str("abc".to_string()), JoinValue::Int(0)],
|
||||
);
|
||||
match &t3_result {
|
||||
Ok(JoinValue::Str(s)) => {
|
||||
assert_eq!(s, "", "T3: Expected '', got '{}'", s);
|
||||
eprintln!("[Phase 45] T3 PASS: abc at pos 0 → '{}'", s);
|
||||
}
|
||||
Ok(v) => panic!("T3: Expected Str, got {:?}", v),
|
||||
Err(e) => eprintln!("[Phase 45] T3 SKIP (execution not supported): {:?}", e),
|
||||
}
|
||||
|
||||
// T4: xx"def" at pos 2 → "def"
|
||||
let t4_result = run_joinir_via_vm(
|
||||
&join_module,
|
||||
entry_func,
|
||||
&[JoinValue::Str("xx\"def\"".to_string()), JoinValue::Int(2)],
|
||||
);
|
||||
match &t4_result {
|
||||
Ok(JoinValue::Str(s)) => {
|
||||
assert_eq!(s, "def", "T4: Expected 'def', got '{}'", s);
|
||||
eprintln!("[Phase 45] T4 PASS: xx\"def\" at pos 2 → '{}'", s);
|
||||
}
|
||||
Ok(v) => panic!("T4: Expected Str, got {:?}", v),
|
||||
Err(e) => eprintln!("[Phase 45] T4 SKIP (execution not supported): {:?}", e),
|
||||
}
|
||||
|
||||
// T5: Escape handling - "a\"b" at pos 0 → "a"b" (escaped quote)
|
||||
// Phase 46: IfMerge で if-body 後の i と ch をマージ
|
||||
let enable_escape_ifmerge =
|
||||
std::env::var("HAKO_JOINIR_READ_QUOTED_IFMERGE").ok().as_deref() == Some("1");
|
||||
|
||||
if enable_escape_ifmerge {
|
||||
// 入力: "a\"b" → 「"」で始まり、a, \", b, 「"」で終わる
|
||||
// 期待出力: a"b(エスケープされた引用符を含む)
|
||||
let t5_input = "\"a\\\"b\""; // Rust エスケープ: "a\"b" → JSON "a\"b"
|
||||
let t5_result = run_joinir_via_vm(
|
||||
&join_module,
|
||||
entry_func,
|
||||
&[JoinValue::Str(t5_input.to_string()), JoinValue::Int(0)],
|
||||
);
|
||||
match &t5_result {
|
||||
Ok(JoinValue::Str(s)) => {
|
||||
let expected = "a\"b"; // エスケープ後: a"b
|
||||
assert_eq!(
|
||||
s, expected,
|
||||
"T5: Expected '{}', got '{}'",
|
||||
expected, s
|
||||
);
|
||||
eprintln!(
|
||||
"[Phase 46] T5 PASS: \"a\\\"b\" at pos 0 → '{}' (escape handling works!)",
|
||||
s
|
||||
);
|
||||
}
|
||||
Ok(v) => panic!("T5: Expected Str, got {:?}", v),
|
||||
Err(e) => eprintln!("[Phase 46] T5 SKIP (execution not supported): {:?}", e),
|
||||
}
|
||||
} else {
|
||||
eprintln!(
|
||||
"[Phase 45] T5 SKIP: Set HAKO_JOINIR_READ_QUOTED_IFMERGE=1 to enable \
|
||||
escape handling (Phase 46)"
|
||||
);
|
||||
}
|
||||
|
||||
eprintln!("[Phase 45] test_read_quoted_from_route_b_e2e completed");
|
||||
}
|
||||
}
|
||||
106
src/mir/join_ir_vm_bridge/meta.rs
Normal file
106
src/mir/join_ir_vm_bridge/meta.rs
Normal file
@ -0,0 +1,106 @@
|
||||
use super::{convert_join_function_to_mir, join_func_name, JoinIrVmBridgeError};
|
||||
use crate::mir::join_ir::frontend::JoinFuncMetaMap;
|
||||
use crate::mir::join_ir::JoinModule;
|
||||
use crate::mir::{MirFunction, MirModule};
|
||||
|
||||
/// Phase 40-1実験用: JoinFuncMetaを使ったMIR変換
|
||||
///
|
||||
/// 既存の run_joinir_via_vm() を拡張し、
|
||||
/// if_modified_varsがあればloop exit PHIを生成する。
|
||||
///
|
||||
/// # Phase 40-1専用
|
||||
/// この関数はPhase 40-1 A/Bテスト専用。
|
||||
/// 本番パスでは使わない(従来のrun_joinir_via_vm()を使う)。
|
||||
///
|
||||
/// # Architecture
|
||||
/// JoinModule → MirModule変換において、JoinFuncMetaを参照してPHI生成を拡張
|
||||
///
|
||||
/// # Returns
|
||||
/// - `Ok(MirModule)`: 変換済みMIRモジュール(PHI拡張版)
|
||||
pub fn convert_join_module_to_mir_with_meta(
|
||||
module: &JoinModule,
|
||||
meta: &JoinFuncMetaMap,
|
||||
) -> Result<MirModule, JoinIrVmBridgeError> {
|
||||
debug_log!("[Phase 40-1] convert_join_module_to_mir_with_meta");
|
||||
|
||||
let mut mir_module = MirModule::new("joinir_bridge_with_meta".to_string());
|
||||
|
||||
// 1. 各関数を変換
|
||||
for (func_id, join_func) in &module.functions {
|
||||
debug_log!(
|
||||
"[Phase 40-1] Converting JoinFunction {} ({})",
|
||||
func_id.0,
|
||||
join_func.name
|
||||
);
|
||||
|
||||
// 2. 基本のMIR変換(既存ロジック)
|
||||
let mir_func = convert_join_function_to_mir(join_func)?;
|
||||
|
||||
// 3. Phase 40-1: if_modified_varsがあればloop exit PHI生成
|
||||
if let Some(m) = meta.get(func_id) {
|
||||
if let Some(if_vars) = &m.if_modified_vars {
|
||||
debug_log!(
|
||||
"[Phase 40-1] Found if_modified_vars for func {:?}: {:?}",
|
||||
func_id,
|
||||
if_vars
|
||||
);
|
||||
|
||||
// TODO(Phase 40-1.2): emit_loop_exit_phi_for_if_modified()実装後に有効化
|
||||
// emit_loop_exit_phi_for_if_modified(&mut mir_func, join_func, if_vars)?;
|
||||
}
|
||||
}
|
||||
|
||||
mir_module
|
||||
.functions
|
||||
.insert(join_func_name(*func_id), mir_func);
|
||||
}
|
||||
|
||||
Ok(mir_module)
|
||||
}
|
||||
|
||||
/// if-in-loop modified varsに対するloop exit PHI生成
|
||||
///
|
||||
/// # Purpose
|
||||
/// JoinIR Frontendで検出されたif-in-loop修正変数に対して、
|
||||
/// loop exit blockにPHI命令を追加する。
|
||||
///
|
||||
/// # Arguments
|
||||
/// - `mir_func`: 変換済みMIR関数(ミュータブル)
|
||||
/// - `join_func`: 元のJoinIR関数(メタデータ参照用)
|
||||
/// - `if_modified_vars`: if-in-loop修正変数名のセット
|
||||
///
|
||||
/// # Implementation Note
|
||||
/// 現在の実装では、JoinIRのloop_step関数は単一ブロックベースであり、
|
||||
/// exit blockの特定が困難。Phase 40-1では**ログ出力のみ**を行い、
|
||||
/// 実際のPHI生成はPhase 40-2以降で実装する。
|
||||
///
|
||||
/// # TODO(Phase 40-2)
|
||||
/// - exit block特定ロジック実装
|
||||
/// - PHI incoming value特定(header vs loop body)
|
||||
/// - PHI命令生成とブロックへの挿入
|
||||
#[allow(dead_code)]
|
||||
pub(crate) fn emit_loop_exit_phi_for_if_modified(
|
||||
_mir_func: &mut MirFunction,
|
||||
join_func: &crate::mir::join_ir::JoinFunction,
|
||||
if_modified_vars: &std::collections::HashSet<String>,
|
||||
) -> Result<(), JoinIrVmBridgeError> {
|
||||
debug_log!(
|
||||
"[Phase 40-1] emit_loop_exit_phi_for_if_modified: func={}, vars={:?}",
|
||||
join_func.name,
|
||||
if_modified_vars
|
||||
);
|
||||
|
||||
// Phase 40-1 minimal implementation: ログ出力のみ
|
||||
// 理由: JoinIRのloop_step関数はtail-recursiveで、exit blockが明示的でない
|
||||
// TODO(Phase 40-2): JoinIR構造を拡張してexit block情報を保持
|
||||
|
||||
if !if_modified_vars.is_empty() {
|
||||
debug_log!(
|
||||
"[Phase 40-1] Would generate {} loop exit PHIs for: {:?}",
|
||||
if_modified_vars.len(),
|
||||
if_modified_vars
|
||||
);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
73
src/mir/join_ir_vm_bridge/mod.rs
Normal file
73
src/mir/join_ir_vm_bridge/mod.rs
Normal file
@ -0,0 +1,73 @@
|
||||
//! Phase 27-shortterm S-4: JoinIR → Rust VM Bridge
|
||||
//!
|
||||
//! 目的: JoinIR(正規化された IR)を Rust VM で実行するブリッジ層
|
||||
//!
|
||||
//! ## Architecture
|
||||
//! ```text
|
||||
//! JoinIR (normalized) → MirModule → Rust VM → Result
|
||||
//! ↑ ↑ ↑
|
||||
//! PHI bugs VM input Execution
|
||||
//! eliminated format (GC, plugins)
|
||||
//! ```
|
||||
//!
|
||||
//! ## Design Principles
|
||||
//! - JoinIR の正規化構造を保持したまま VM に渡す
|
||||
//! - マッピングだけで済ませる(JoinIR でやった正規化は消えない)
|
||||
//! - VM の機能(GC、プラグイン、エラーハンドリング)を活用
|
||||
//!
|
||||
//! ## Minimal Instruction Set (S-4.3)
|
||||
//! - **Compute**: Const, BinOp, Compare
|
||||
//! - **BoxCall**: StringBox メソッド呼び出し
|
||||
//! - **Call/Jump/Ret**: 制御フロー
|
||||
//!
|
||||
//! Phase 27-shortterm scope: skip_ws で green 化できれば成功
|
||||
|
||||
use crate::backend::VMError;
|
||||
use crate::mir::join_ir::JoinFuncId;
|
||||
|
||||
#[macro_use]
|
||||
mod logging {
|
||||
macro_rules! debug_log {
|
||||
($($arg:tt)*) => {
|
||||
if crate::config::env::joinir_vm_bridge_debug() {
|
||||
eprintln!($($arg)*);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
mod convert;
|
||||
mod meta;
|
||||
mod runner;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
||||
pub(crate) use convert::{convert_join_function_to_mir, convert_joinir_to_mir};
|
||||
pub use meta::convert_join_module_to_mir_with_meta;
|
||||
pub use runner::run_joinir_via_vm;
|
||||
|
||||
/// Phase 27-shortterm S-4 エラー型
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct JoinIrVmBridgeError {
|
||||
pub message: String,
|
||||
}
|
||||
|
||||
impl JoinIrVmBridgeError {
|
||||
pub fn new(msg: impl Into<String>) -> Self {
|
||||
Self {
|
||||
message: msg.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<VMError> for JoinIrVmBridgeError {
|
||||
fn from(err: VMError) -> Self {
|
||||
JoinIrVmBridgeError::new(format!("VM error: {:?}", err))
|
||||
}
|
||||
}
|
||||
|
||||
/// JoinFuncId から MIR 用の関数名を生成
|
||||
pub(crate) fn join_func_name(id: JoinFuncId) -> String {
|
||||
format!("join_func_{}", id.0)
|
||||
}
|
||||
65
src/mir/join_ir_vm_bridge/runner.rs
Normal file
65
src/mir/join_ir_vm_bridge/runner.rs
Normal file
@ -0,0 +1,65 @@
|
||||
use super::{convert_joinir_to_mir, join_func_name, JoinIrVmBridgeError};
|
||||
use crate::backend::{MirInterpreter, VMValue};
|
||||
use crate::mir::join_ir::JoinFuncId;
|
||||
use crate::mir::join_ir::JoinModule;
|
||||
use crate::mir::join_ir_ops::JoinValue;
|
||||
|
||||
/// Phase 27-shortterm S-4.3: JoinIR → VM 実行のエントリーポイント
|
||||
///
|
||||
/// ## Arguments
|
||||
/// - `join_module`: JoinIR モジュール(正規化済み)
|
||||
/// - `entry_func`: エントリーポイント関数ID
|
||||
/// - `args`: 初期引数(JoinValue 形式)
|
||||
///
|
||||
/// ## Returns
|
||||
/// - `Ok(JoinValue)`: 実行結果
|
||||
/// - `Err(JoinIrVmBridgeError)`: 変換エラーまたは実行エラー
|
||||
///
|
||||
/// ## Example
|
||||
/// ```ignore
|
||||
/// let join_module = lower_skip_ws_to_joinir(&mir_module)?;
|
||||
/// let result = run_joinir_via_vm(
|
||||
/// &join_module,
|
||||
/// JoinFuncId::new(0),
|
||||
/// &[JoinValue::Str(" hello".to_string()), JoinValue::Int(7)]
|
||||
/// )?;
|
||||
/// assert_eq!(result, JoinValue::Int(2));
|
||||
/// ```
|
||||
pub fn run_joinir_via_vm(
|
||||
join_module: &JoinModule,
|
||||
entry_func: JoinFuncId,
|
||||
args: &[JoinValue],
|
||||
) -> Result<JoinValue, JoinIrVmBridgeError> {
|
||||
debug_log!("[joinir_vm_bridge] Phase 27-shortterm S-4.3");
|
||||
debug_log!("[joinir_vm_bridge] Converting JoinIR to MIR for VM execution");
|
||||
|
||||
// Step 1: JoinIR → MIR 変換
|
||||
let mir_module = convert_joinir_to_mir(join_module)?;
|
||||
|
||||
debug_log!(
|
||||
"[joinir_vm_bridge] Converted {} JoinIR functions to MIR",
|
||||
join_module.functions.len()
|
||||
);
|
||||
|
||||
// Step 2: VM 実行
|
||||
let mut vm = MirInterpreter::new();
|
||||
|
||||
debug_log!(
|
||||
"[joinir_vm_bridge] Executing via VM with {} arguments",
|
||||
args.len()
|
||||
);
|
||||
|
||||
// Convert JoinValue → VMValue (BoxRef 含む)
|
||||
let vm_args: Vec<VMValue> = args.iter().cloned().map(|v| v.into_vm_value()).collect();
|
||||
|
||||
let entry_name = join_func_name(entry_func);
|
||||
let result = vm.execute_function_with_args(&mir_module, &entry_name, &vm_args)?;
|
||||
|
||||
// Step 3: VMValue → JoinValue 変換
|
||||
let join_result = JoinValue::from_vm_value(&result)
|
||||
.map_err(|e| JoinIrVmBridgeError::new(format!("Result conversion error: {}", e.message)))?;
|
||||
|
||||
debug_log!("[joinir_vm_bridge] Execution succeeded: {:?}", join_result);
|
||||
|
||||
Ok(join_result)
|
||||
}
|
||||
301
src/mir/join_ir_vm_bridge/tests.rs
Normal file
301
src/mir/join_ir_vm_bridge/tests.rs
Normal file
@ -0,0 +1,301 @@
|
||||
use super::convert::convert_mir_like_inst;
|
||||
use super::*;
|
||||
use crate::mir::join_ir::frontend::AstToJoinIrLowerer;
|
||||
use crate::mir::join_ir_ops::JoinValue;
|
||||
use crate::mir::{BinaryOp, CompareOp as MirCompareOp, MirInstruction, ValueId};
|
||||
|
||||
#[test]
|
||||
fn test_convert_const_inst() {
|
||||
let join_const = crate::mir::join_ir::MirLikeInst::Const {
|
||||
dst: ValueId(10),
|
||||
value: crate::mir::join_ir::ConstValue::Integer(42),
|
||||
};
|
||||
|
||||
let mir_inst = convert_mir_like_inst(&join_const).unwrap();
|
||||
|
||||
match mir_inst {
|
||||
MirInstruction::Const { dst, value } => {
|
||||
assert_eq!(dst, ValueId(10));
|
||||
assert!(matches!(value, crate::mir::ConstValue::Integer(42)));
|
||||
}
|
||||
_ => panic!("Expected Const instruction"),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_convert_binop_inst() {
|
||||
let join_binop = crate::mir::join_ir::MirLikeInst::BinOp {
|
||||
dst: ValueId(20),
|
||||
op: crate::mir::join_ir::BinOpKind::Add,
|
||||
lhs: ValueId(10),
|
||||
rhs: ValueId(11),
|
||||
};
|
||||
|
||||
let mir_inst = convert_mir_like_inst(&join_binop).unwrap();
|
||||
|
||||
match mir_inst {
|
||||
MirInstruction::BinOp { dst, op, lhs, rhs } => {
|
||||
assert_eq!(dst, ValueId(20));
|
||||
assert_eq!(op, BinaryOp::Add);
|
||||
assert_eq!(lhs, ValueId(10));
|
||||
assert_eq!(rhs, ValueId(11));
|
||||
}
|
||||
_ => panic!("Expected BinOp instruction"),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_convert_compare_inst() {
|
||||
let join_cmp = crate::mir::join_ir::MirLikeInst::Compare {
|
||||
dst: ValueId(30),
|
||||
op: crate::mir::join_ir::CompareOp::Ge,
|
||||
lhs: ValueId(10),
|
||||
rhs: ValueId(11),
|
||||
};
|
||||
|
||||
let mir_inst = convert_mir_like_inst(&join_cmp).unwrap();
|
||||
|
||||
match mir_inst {
|
||||
MirInstruction::Compare { dst, op, lhs, rhs } => {
|
||||
assert_eq!(dst, ValueId(30));
|
||||
assert_eq!(op, MirCompareOp::Ge);
|
||||
assert_eq!(lhs, ValueId(10));
|
||||
assert_eq!(rhs, ValueId(11));
|
||||
}
|
||||
_ => panic!("Expected Compare instruction"),
|
||||
}
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// Phase 45: read_quoted_from Bridge Tests
|
||||
// ========================================
|
||||
|
||||
/// Phase 45: read_quoted_from JoinIR → MIR 変換テスト
|
||||
///
|
||||
/// HAKO_JOINIR_READ_QUOTED=1 が設定されている場合のみ実行。
|
||||
#[test]
|
||||
fn test_read_quoted_from_joinir_to_mir_conversion() {
|
||||
// Dev flag がない場合はスキップ
|
||||
if std::env::var("HAKO_JOINIR_READ_QUOTED").ok().as_deref() != Some("1") {
|
||||
eprintln!(
|
||||
"[Phase 45] Skipping test_read_quoted_from_joinir_to_mir_conversion: \
|
||||
Set HAKO_JOINIR_READ_QUOTED=1 to enable"
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// 1. JoinModule を生成(lower_read_quoted_pattern を使用)
|
||||
let program_json = serde_json::json!({
|
||||
"defs": [{
|
||||
"name": "read_quoted_from",
|
||||
"params": ["s", "pos"],
|
||||
"body": { "body": [] }
|
||||
}]
|
||||
});
|
||||
|
||||
let mut lowerer = AstToJoinIrLowerer::new();
|
||||
let join_module = lowerer.lower_read_quoted_pattern(&program_json);
|
||||
|
||||
// 2. JoinIR → MIR 変換
|
||||
let mir_module = convert_joinir_to_mir(&join_module);
|
||||
|
||||
assert!(
|
||||
mir_module.is_ok(),
|
||||
"JoinIR → MIR conversion should succeed: {:?}",
|
||||
mir_module.err()
|
||||
);
|
||||
|
||||
let mir_module = mir_module.unwrap();
|
||||
|
||||
// 3. MIR 構造の検証
|
||||
// 4 つの関数がある: entry, k_guard_fail, loop_step, k_exit
|
||||
assert_eq!(mir_module.functions.len(), 4, "MIR should have 4 functions");
|
||||
|
||||
// 関数名を確認
|
||||
let func_names: Vec<&str> = mir_module.functions.keys().map(|s| s.as_str()).collect();
|
||||
eprintln!("[Phase 45] MIR function names: {:?}", func_names);
|
||||
|
||||
// join_func_0 (entry), join_func_1 (loop_step), join_func_2 (k_exit), join_func_3 (k_guard_fail)
|
||||
assert!(
|
||||
func_names.contains(&"join_func_0"),
|
||||
"Should have entry function join_func_0"
|
||||
);
|
||||
assert!(
|
||||
func_names.contains(&"join_func_1"),
|
||||
"Should have loop_step function join_func_1"
|
||||
);
|
||||
assert!(
|
||||
func_names.contains(&"join_func_2"),
|
||||
"Should have k_exit function join_func_2"
|
||||
);
|
||||
assert!(
|
||||
func_names.contains(&"join_func_3"),
|
||||
"Should have k_guard_fail function join_func_3"
|
||||
);
|
||||
|
||||
eprintln!("[Phase 45] test_read_quoted_from_joinir_to_mir_conversion PASSED");
|
||||
}
|
||||
|
||||
/// Phase 45: String 定数の MIR 変換テスト
|
||||
#[test]
|
||||
fn test_convert_string_const_inst() {
|
||||
let join_const = crate::mir::join_ir::MirLikeInst::Const {
|
||||
dst: ValueId(50),
|
||||
value: crate::mir::join_ir::ConstValue::String("\"".to_string()),
|
||||
};
|
||||
|
||||
let mir_inst = convert_mir_like_inst(&join_const).unwrap();
|
||||
|
||||
match mir_inst {
|
||||
MirInstruction::Const { dst, value } => {
|
||||
assert_eq!(dst, ValueId(50));
|
||||
match value {
|
||||
crate::mir::ConstValue::String(s) => assert_eq!(s, "\""),
|
||||
_ => panic!("Expected String value"),
|
||||
}
|
||||
}
|
||||
_ => panic!("Expected Const instruction"),
|
||||
}
|
||||
}
|
||||
|
||||
/// Phase 45: A/B テスト - Route B (JoinIR) E2E 実行テスト
|
||||
///
|
||||
/// HAKO_JOINIR_READ_QUOTED=1 が設定されている場合のみ実行。
|
||||
///
|
||||
/// # Test Cases (from Phase 45 fixture)
|
||||
///
|
||||
/// - T1: `"abc"` at pos 0 → `abc`
|
||||
/// - T2: `""` at pos 0 → `` (empty)
|
||||
/// - T3: `abc` at pos 0 → `` (guard fail, no quote)
|
||||
/// - T4: `xx"def"` at pos 2 → `def`
|
||||
///
|
||||
/// # Known Limitation
|
||||
///
|
||||
/// T5 (escape handling) is skipped due to known PHI issue
|
||||
/// with variable reassignment inside if-blocks.
|
||||
#[test]
|
||||
fn test_read_quoted_from_route_b_e2e() {
|
||||
// Dev flag がない場合はスキップ
|
||||
if std::env::var("HAKO_JOINIR_READ_QUOTED").ok().as_deref() != Some("1") {
|
||||
eprintln!(
|
||||
"[Phase 45] Skipping test_read_quoted_from_route_b_e2e: \
|
||||
Set HAKO_JOINIR_READ_QUOTED=1 to enable"
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// 1. JoinModule を生成
|
||||
let program_json = serde_json::json!({
|
||||
"defs": [{
|
||||
"name": "read_quoted_from",
|
||||
"params": ["s", "pos"],
|
||||
"body": { "body": [] }
|
||||
}]
|
||||
});
|
||||
|
||||
let mut lowerer = AstToJoinIrLowerer::new();
|
||||
let join_module = lowerer.lower_read_quoted_pattern(&program_json);
|
||||
|
||||
let entry_func = join_module.entry.expect("Entry function should exist");
|
||||
|
||||
// 2. A/B テスト実行
|
||||
// Note: Route B (JoinIR) は run_joinir_via_vm で実行
|
||||
// Route A (既存) は別途フィクスチャで検証済み
|
||||
|
||||
// T1: "abc" at pos 0 → "abc"
|
||||
let t1_result = run_joinir_via_vm(
|
||||
&join_module,
|
||||
entry_func,
|
||||
&[JoinValue::Str("\"abc\"".to_string()), JoinValue::Int(0)],
|
||||
);
|
||||
match &t1_result {
|
||||
Ok(JoinValue::Str(s)) => {
|
||||
assert_eq!(s, "abc", "T1: Expected 'abc', got '{}'", s);
|
||||
eprintln!("[Phase 45] T1 PASS: \"abc\" at pos 0 → '{}'", s);
|
||||
}
|
||||
Ok(v) => panic!("T1: Expected Str, got {:?}", v),
|
||||
Err(e) => eprintln!("[Phase 45] T1 SKIP (execution not supported): {:?}", e),
|
||||
}
|
||||
|
||||
// T2: "" at pos 0 → "" (empty)
|
||||
let t2_result = run_joinir_via_vm(
|
||||
&join_module,
|
||||
entry_func,
|
||||
&[JoinValue::Str("\"\"".to_string()), JoinValue::Int(0)],
|
||||
);
|
||||
match &t2_result {
|
||||
Ok(JoinValue::Str(s)) => {
|
||||
assert_eq!(s, "", "T2: Expected '', got '{}'", s);
|
||||
eprintln!("[Phase 45] T2 PASS: \"\" at pos 0 → '{}'", s);
|
||||
}
|
||||
Ok(v) => panic!("T2: Expected Str, got {:?}", v),
|
||||
Err(e) => eprintln!("[Phase 45] T2 SKIP (execution not supported): {:?}", e),
|
||||
}
|
||||
|
||||
// T3: abc at pos 0 → "" (guard fail)
|
||||
let t3_result = run_joinir_via_vm(
|
||||
&join_module,
|
||||
entry_func,
|
||||
&[JoinValue::Str("abc".to_string()), JoinValue::Int(0)],
|
||||
);
|
||||
match &t3_result {
|
||||
Ok(JoinValue::Str(s)) => {
|
||||
assert_eq!(s, "", "T3: Expected '', got '{}'", s);
|
||||
eprintln!("[Phase 45] T3 PASS: abc at pos 0 → '{}'", s);
|
||||
}
|
||||
Ok(v) => panic!("T3: Expected Str, got {:?}", v),
|
||||
Err(e) => eprintln!("[Phase 45] T3 SKIP (execution not supported): {:?}", e),
|
||||
}
|
||||
|
||||
// T4: xx"def" at pos 2 → "def"
|
||||
let t4_result = run_joinir_via_vm(
|
||||
&join_module,
|
||||
entry_func,
|
||||
&[JoinValue::Str("xx\"def\"".to_string()), JoinValue::Int(2)],
|
||||
);
|
||||
match &t4_result {
|
||||
Ok(JoinValue::Str(s)) => {
|
||||
assert_eq!(s, "def", "T4: Expected 'def', got '{}'", s);
|
||||
eprintln!("[Phase 45] T4 PASS: xx\"def\" at pos 2 → '{}'", s);
|
||||
}
|
||||
Ok(v) => panic!("T4: Expected Str, got {:?}", v),
|
||||
Err(e) => eprintln!("[Phase 45] T4 SKIP (execution not supported): {:?}", e),
|
||||
}
|
||||
|
||||
// T5: Escape handling - "a\"b" at pos 0 → "a"b" (escaped quote)
|
||||
// Phase 46: IfMerge で if-body 後の i と ch をマージ
|
||||
let enable_escape_ifmerge = std::env::var("HAKO_JOINIR_READ_QUOTED_IFMERGE")
|
||||
.ok()
|
||||
.as_deref()
|
||||
== Some("1");
|
||||
|
||||
if enable_escape_ifmerge {
|
||||
// 入力: "a\"b" → 「"」で始まり、a, \", b, 「"」で終わる
|
||||
// 期待出力: a"b(エスケープされた引用符を含む)
|
||||
let t5_input = "\"a\\\"b\""; // Rust エスケープ: "a\"b" → JSON "a\"b"
|
||||
let t5_result = run_joinir_via_vm(
|
||||
&join_module,
|
||||
entry_func,
|
||||
&[JoinValue::Str(t5_input.to_string()), JoinValue::Int(0)],
|
||||
);
|
||||
match &t5_result {
|
||||
Ok(JoinValue::Str(s)) => {
|
||||
let expected = "a\"b"; // エスケープ後: a"b
|
||||
assert_eq!(s, expected, "T5: Expected '{}', got '{}'", expected, s);
|
||||
eprintln!(
|
||||
"[Phase 46] T5 PASS: \"a\\\"b\" at pos 0 → '{}' (escape handling works!)",
|
||||
s
|
||||
);
|
||||
}
|
||||
Ok(v) => panic!("T5: Expected Str, got {:?}", v),
|
||||
Err(e) => eprintln!("[Phase 46] T5 SKIP (execution not supported): {:?}", e),
|
||||
}
|
||||
} else {
|
||||
eprintln!(
|
||||
"[Phase 45] T5 SKIP: Set HAKO_JOINIR_READ_QUOTED_IFMERGE=1 to enable \
|
||||
escape handling (Phase 46)"
|
||||
);
|
||||
}
|
||||
|
||||
eprintln!("[Phase 45] test_read_quoted_from_route_b_e2e completed");
|
||||
}
|
||||
@ -10,9 +10,9 @@
|
||||
//! 将来は LoopScopeShape / ExitAnalysis ベースの構造判定に差し替え予定。
|
||||
|
||||
use crate::config::env::{joinir_experiment_enabled, joinir_vm_bridge_enabled};
|
||||
use crate::mir::join_ir::lowering::stage1_using_resolver::lower_stage1_usingresolver_to_joinir;
|
||||
use crate::mir::join_ir::lowering::stageb_body::lower_stageb_body_to_joinir;
|
||||
use crate::mir::join_ir::lowering::stageb_funcscanner::lower_stageb_funcscanner_to_joinir;
|
||||
use crate::mir::join_ir::lowering::stage1_using_resolver::lower_stage1_usingresolver_to_joinir;
|
||||
use crate::mir::join_ir::{lower_funcscanner_trim_to_joinir, lower_skip_ws_to_joinir, JoinFuncId};
|
||||
use crate::mir::join_ir_ops::JoinValue;
|
||||
use crate::mir::join_ir_vm_bridge::run_joinir_via_vm;
|
||||
@ -300,9 +300,7 @@ fn try_run_stageb_body(module: &MirModule) -> bool {
|
||||
/// ArrayBox/MapBox 引数がまだ JoinValue でサポートされていないため、
|
||||
/// JoinIR lowering / Bridge 構造検証のみ行い、実行は VM Route A にフォールバック。
|
||||
fn try_run_stageb_funcscanner(module: &MirModule) -> bool {
|
||||
eprintln!(
|
||||
"[joinir/vm_bridge] Attempting JoinIR path for StageBFuncScannerBox.scan_all_boxes"
|
||||
);
|
||||
eprintln!("[joinir/vm_bridge] Attempting JoinIR path for StageBFuncScannerBox.scan_all_boxes");
|
||||
|
||||
match lower_stageb_funcscanner_to_joinir(module) {
|
||||
Some(join_module) => {
|
||||
|
||||
@ -300,8 +300,7 @@ impl<'a> LoopBuilder<'a> {
|
||||
.map(|f| f.signature.name.clone())
|
||||
.unwrap_or_default();
|
||||
|
||||
let bypass_flags =
|
||||
crate::mir::phi_core::loopform_builder::get_loop_bypass_flags(&fn_name);
|
||||
let bypass_flags = crate::mir::phi_core::loopform_builder::get_loop_bypass_flags(&fn_name);
|
||||
|
||||
if bypass_flags.header {
|
||||
// Phase 27.4-C: JoinIR 実験経路では Header φ を生成しない。
|
||||
|
||||
@ -34,7 +34,6 @@ pub mod join_ir_vm_bridge_dispatch; // Phase 30 F-4.4: JoinIR VM ブリッジ di
|
||||
pub mod loop_form; // ControlForm::LoopShape の薄いエイリアス
|
||||
pub mod optimizer_passes; // optimizer passes (normalize/diagnostics)
|
||||
pub mod optimizer_stats; // extracted stats struct
|
||||
mod spanned_instruction;
|
||||
pub mod passes;
|
||||
pub mod phi_core; // Phase 1 scaffold: unified PHI entry (re-exports only)
|
||||
pub mod printer;
|
||||
@ -42,6 +41,7 @@ mod printer_helpers; // internal helpers extracted from printer.rs
|
||||
pub mod query; // Phase 26-G: MIR read/write/CFGビュー (MirQuery)
|
||||
pub mod region; // Phase 25.1l: Region/GC観測レイヤ(LoopForm v2 × RefKind)
|
||||
pub mod slot_registry; // Phase 9.79b.1: method slot resolution (IDs)
|
||||
mod spanned_instruction;
|
||||
pub mod value_id;
|
||||
pub mod value_kind; // Phase 26-A: ValueId型安全化
|
||||
pub mod verification;
|
||||
@ -57,9 +57,9 @@ pub use instruction::MirInstruction;
|
||||
pub use join_ir_runner::{run_joinir_function, JoinRuntimeError, JoinValue};
|
||||
pub use optimizer::MirOptimizer;
|
||||
pub use printer::MirPrinter;
|
||||
pub use spanned_instruction::{SpannedInstRef, SpannedInstruction};
|
||||
pub use query::{MirQuery, MirQueryBox};
|
||||
pub use slot_registry::{BoxTypeId, MethodSlot};
|
||||
pub use spanned_instruction::{SpannedInstRef, SpannedInstruction};
|
||||
pub use types::{
|
||||
BarrierOp, BinaryOp, CompareOp, ConstValue, MirType, TypeOpKind, UnaryOp, WeakRefOp,
|
||||
};
|
||||
|
||||
@ -1,9 +1,9 @@
|
||||
use crate::ast::Span;
|
||||
use crate::mir::optimizer::MirOptimizer;
|
||||
use crate::mir::optimizer_stats::OptimizationStats;
|
||||
use crate::mir::{
|
||||
BarrierOp, EffectMask, MirModule, SpannedInstruction, TypeOpKind, ValueId, WeakRefOp,
|
||||
};
|
||||
use crate::ast::Span;
|
||||
|
||||
fn idemp_enabled() -> bool {
|
||||
std::env::var("NYASH_MIR_DEV_IDEMP").ok().as_deref() == Some("1")
|
||||
@ -67,10 +67,8 @@ pub fn force_plugin_invoke(_opt: &mut MirOptimizer, module: &mut MirModule) -> O
|
||||
});
|
||||
}
|
||||
|
||||
let (insts, spans): (Vec<_>, Vec<_>) = new_spanned
|
||||
.into_iter()
|
||||
.map(|sp| (sp.inst, sp.span))
|
||||
.unzip();
|
||||
let (insts, spans): (Vec<_>, Vec<_>) =
|
||||
new_spanned.into_iter().map(|sp| (sp.inst, sp.span)).unzip();
|
||||
block.instructions = insts;
|
||||
block.instruction_spans = spans;
|
||||
block.effects = block
|
||||
@ -132,10 +130,8 @@ pub fn normalize_python_helper_calls(
|
||||
new_spanned.push(SpannedInstruction { inst, span });
|
||||
}
|
||||
|
||||
let (insts, spans): (Vec<_>, Vec<_>) = new_spanned
|
||||
.into_iter()
|
||||
.map(|sp| (sp.inst, sp.span))
|
||||
.unzip();
|
||||
let (insts, spans): (Vec<_>, Vec<_>) =
|
||||
new_spanned.into_iter().map(|sp| (sp.inst, sp.span)).unzip();
|
||||
block.instructions = insts;
|
||||
block.instruction_spans = spans;
|
||||
block.effects = block
|
||||
@ -407,10 +403,8 @@ pub fn normalize_legacy_instructions(
|
||||
block.terminator_span = Some(span);
|
||||
}
|
||||
|
||||
let (insts, spans): (Vec<_>, Vec<_>) = new_spanned
|
||||
.into_iter()
|
||||
.map(|sp| (sp.inst, sp.span))
|
||||
.unzip();
|
||||
let (insts, spans): (Vec<_>, Vec<_>) =
|
||||
new_spanned.into_iter().map(|sp| (sp.inst, sp.span)).unzip();
|
||||
block.instructions = insts;
|
||||
block.instruction_spans = spans;
|
||||
block.effects = block
|
||||
|
||||
@ -80,7 +80,6 @@ pub fn infer_type_from_phi(
|
||||
None
|
||||
}
|
||||
|
||||
|
||||
// ========================================
|
||||
// Phase 40-4.1: collect_assigned_vars削除完了
|
||||
// ========================================
|
||||
@ -309,4 +308,3 @@ pub trait PhiMergeOps {
|
||||
// ========================================
|
||||
// - merge_modified_at_merge_with (70行) - PhiBuilderBox::generate_if_phis()に置き換え済み
|
||||
// - merge_with_reset_at_merge_with (29行) - 上記のwrapper、同様にデッドコード
|
||||
|
||||
|
||||
@ -313,7 +313,11 @@ mod tests {
|
||||
.unwrap();
|
||||
|
||||
// "ch" should NOT have exit PHI (BodyLocalInternal - not in all exit preds)
|
||||
assert_eq!(result.len(), 0, "Expected no exit PHI for BodyLocalInternal variable 'ch'");
|
||||
assert_eq!(
|
||||
result.len(),
|
||||
0,
|
||||
"Expected no exit PHI for BodyLocalInternal variable 'ch'"
|
||||
);
|
||||
}
|
||||
|
||||
/// Test merge_exit_with_classification with header not in exit_preds (break-only loop)
|
||||
@ -357,7 +361,9 @@ mod tests {
|
||||
let i_inputs = result.get("i").unwrap();
|
||||
assert_eq!(i_inputs.len(), 1);
|
||||
assert!(i_inputs.contains(&(break_bb, ValueId::new(10))));
|
||||
assert!(!i_inputs.contains(&(header_id, ValueId::new(1))),
|
||||
"Header input should be excluded when header not in exit_preds");
|
||||
assert!(
|
||||
!i_inputs.contains(&(header_id, ValueId::new(1))),
|
||||
"Header input should be excluded when header not in exit_preds"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -111,9 +111,7 @@ pub struct IfPhiContext {
|
||||
impl PhiBuilderBox {
|
||||
/// 新しいPhiBuilderBoxを作成
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
if_context: None,
|
||||
}
|
||||
Self { if_context: None }
|
||||
}
|
||||
|
||||
/// Phase 26-F-3: ループ内if-mergeコンテキストを設定
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
use crate::ast::Span;
|
||||
use crate::mir::{
|
||||
BasicBlockId, CompareOp, MirFunction, MirInstruction, SpannedInstruction, ValueId,
|
||||
};
|
||||
use crate::ast::Span;
|
||||
|
||||
/// Emit a MIR Compare instruction into the current block (function-level SSOT helper)
|
||||
pub fn emit_compare_func(
|
||||
|
||||
@ -93,8 +93,9 @@ impl MirVerifier {
|
||||
instruction_index
|
||||
);
|
||||
if let Some(bb) = function.blocks.get(block) {
|
||||
let inst_opt =
|
||||
bb.all_spanned_instructions_enumerated().nth(*instruction_index);
|
||||
let inst_opt = bb
|
||||
.all_spanned_instructions_enumerated()
|
||||
.nth(*instruction_index);
|
||||
if let Some((_idx, sp)) = inst_opt {
|
||||
eprintln!("[mir-ssa-debug-inst] inst={:?}", sp.inst);
|
||||
}
|
||||
|
||||
@ -1,11 +1,11 @@
|
||||
use super::globals::resolve_bridge_global;
|
||||
use super::match_expr;
|
||||
use super::merge::new_block;
|
||||
use super::globals::resolve_bridge_global;
|
||||
use super::ternary;
|
||||
use super::BridgeEnv;
|
||||
use super::throw_lower::lower_throw;
|
||||
use crate::mir::{BasicBlockId, ConstValue, EffectMask, MirFunction, MirInstruction, ValueId};
|
||||
use super::BridgeEnv;
|
||||
use crate::ast::Span;
|
||||
use crate::mir::{BasicBlockId, ConstValue, EffectMask, MirFunction, MirInstruction, ValueId};
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use super::super::ast::ExprV0;
|
||||
@ -54,9 +54,7 @@ impl<'a> VarScope for MapVars<'a> {
|
||||
}
|
||||
|
||||
// Bridge 固有のグローバル解決(imports/hostbridge/env/me dummy)は専用モジュールに委譲
|
||||
if let Some(vid) =
|
||||
resolve_bridge_global(name, env, f, cur_bb, self.vars)?
|
||||
{
|
||||
if let Some(vid) = resolve_bridge_global(name, env, f, cur_bb, self.vars)? {
|
||||
return Ok(Some(vid));
|
||||
}
|
||||
|
||||
|
||||
@ -74,4 +74,3 @@ pub(super) fn resolve_bridge_global(
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -23,10 +23,10 @@
|
||||
use super::super::ast::ExprV0;
|
||||
use super::super::ast::StmtV0;
|
||||
use super::{lower_stmt_list_with_vars, new_block, BridgeEnv, LoopContext};
|
||||
use crate::ast::Span;
|
||||
use crate::mir::phi_core::loopform_builder::{LoopFormBuilder, LoopFormOps};
|
||||
use crate::mir::phi_core::phi_input_collector::PhiInputCollector;
|
||||
use crate::mir::{BasicBlockId, MirFunction, MirInstruction, ValueId};
|
||||
use crate::ast::Span;
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
/// LoopForm v2 用の JSON bridge 実装。
|
||||
|
||||
@ -4,8 +4,8 @@ use super::super::ast::{ExprV0, MatchArmV0};
|
||||
use super::expr::{lower_expr_with_scope, VarScope};
|
||||
use super::merge::new_block;
|
||||
use super::BridgeEnv;
|
||||
use crate::mir::{BasicBlockId, CompareOp, ConstValue, MirFunction, MirInstruction, ValueId};
|
||||
use crate::ast::Span;
|
||||
use crate::mir::{BasicBlockId, CompareOp, ConstValue, MirFunction, MirInstruction, ValueId};
|
||||
|
||||
pub(super) fn lower_match_expr_with_scope<S: VarScope>(
|
||||
env: &BridgeEnv,
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
use crate::mir::{BasicBlock, BasicBlockId, MirFunction, MirInstruction, ValueId};
|
||||
use crate::ast::Span;
|
||||
use crate::mir::{BasicBlock, BasicBlockId, MirFunction, MirInstruction, ValueId};
|
||||
|
||||
fn next_block_id(f: &MirFunction) -> BasicBlockId {
|
||||
let mut mx = 0u32;
|
||||
|
||||
@ -6,8 +6,8 @@
|
||||
use super::super::ast::ExprV0;
|
||||
use super::merge::new_block;
|
||||
use super::BridgeEnv;
|
||||
use crate::mir::{BasicBlockId, MirFunction, ValueId};
|
||||
use crate::ast::Span;
|
||||
use crate::mir::{BasicBlockId, MirFunction, ValueId};
|
||||
|
||||
use super::expr::{lower_expr_with_scope, VarScope};
|
||||
|
||||
|
||||
@ -42,4 +42,3 @@ pub(super) fn lower_throw(
|
||||
(dst, cur_bb)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
use super::super::ast::{CatchV0, StmtV0};
|
||||
use super::{lower_stmt_list_with_vars, new_block, BridgeEnv, LoopContext};
|
||||
use crate::mir::{BasicBlockId, MirFunction, MirInstruction, ValueId};
|
||||
use crate::ast::Span;
|
||||
use crate::mir::{BasicBlockId, MirFunction, MirInstruction, ValueId};
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
pub(super) fn lower_try_stmt(
|
||||
|
||||
@ -13,7 +13,7 @@ pub mod io;
|
||||
pub mod plugin_guard;
|
||||
pub mod provider_registry;
|
||||
pub mod pyvm;
|
||||
pub mod source_hint;
|
||||
pub mod resolve;
|
||||
pub mod selfhost;
|
||||
pub mod selfhost_exe;
|
||||
pub mod source_hint;
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
use crate::mir::{MirCompileResult, MirCompiler};
|
||||
use crate::ast::ASTNode;
|
||||
use crate::mir::{MirCompileResult, MirCompiler};
|
||||
|
||||
/// Compile AST with a source filename hint, reducing call-site duplication.
|
||||
/// Falls back to regular compile when filename is None or empty.
|
||||
|
||||
@ -105,7 +105,8 @@ impl NyashRunner {
|
||||
|
||||
// Compile to MIR
|
||||
let mut mir_compiler = MirCompiler::new();
|
||||
let compile_result = match crate::runner::modes::common_util::source_hint::compile_with_source_hint(
|
||||
let compile_result =
|
||||
match crate::runner::modes::common_util::source_hint::compile_with_source_hint(
|
||||
&mut mir_compiler,
|
||||
ast,
|
||||
Some(filename),
|
||||
|
||||
@ -32,7 +32,8 @@ impl NyashRunner {
|
||||
|
||||
// Compile to MIR (opt passes configurable)
|
||||
let mut mir_compiler = MirCompiler::with_options(!self.config.no_optimize);
|
||||
let compile_result = match crate::runner::modes::common_util::source_hint::compile_with_source_hint(
|
||||
let compile_result =
|
||||
match crate::runner::modes::common_util::source_hint::compile_with_source_hint(
|
||||
&mut mir_compiler,
|
||||
ast,
|
||||
Some(filename),
|
||||
|
||||
@ -147,7 +147,8 @@ pub fn execute_pyvm_only(runner: &NyashRunner, filename: &str) {
|
||||
|
||||
// Compile to MIR (respect default optimizer setting)
|
||||
let mut mir_compiler = MirCompiler::with_options(true);
|
||||
let mut compile_result = match crate::runner::modes::common_util::source_hint::compile_with_source_hint(
|
||||
let mut compile_result =
|
||||
match crate::runner::modes::common_util::source_hint::compile_with_source_hint(
|
||||
&mut mir_compiler,
|
||||
ast,
|
||||
Some(filename),
|
||||
|
||||
@ -503,9 +503,8 @@ impl NyashRunner {
|
||||
// HAKO_JOINIR_IF_SELECT=1 で有効化、IfLoweringDryRunner を使用
|
||||
if crate::config::env::joinir_if_select_enabled() {
|
||||
let debug_level = crate::config::env::joinir_debug_level();
|
||||
let runner = crate::mir::join_ir::lowering::if_dry_runner::IfLoweringDryRunner::new(
|
||||
debug_level,
|
||||
);
|
||||
let runner =
|
||||
crate::mir::join_ir::lowering::if_dry_runner::IfLoweringDryRunner::new(debug_level);
|
||||
let stats = runner.scan_module(&module_vm.functions);
|
||||
runner.print_stats(&stats);
|
||||
}
|
||||
|
||||
@ -219,7 +219,10 @@ impl NyashRunner {
|
||||
if parser_prog.exists() {
|
||||
// Phase 28.2: observation log (NYASH_CLI_VERBOSE>=2)
|
||||
if verbose_level >= 2 {
|
||||
eprintln!("[selfhost/ny] spawning Ny compiler child process: {}", parser_prog.display());
|
||||
eprintln!(
|
||||
"[selfhost/ny] spawning Ny compiler child process: {}",
|
||||
parser_prog.display()
|
||||
);
|
||||
}
|
||||
// Build extra args forwarded to child program
|
||||
let mut extra_owned: Vec<String> = Vec::new();
|
||||
@ -277,7 +280,10 @@ impl NyashRunner {
|
||||
) {
|
||||
// Phase 28.2: observation log - JSON received
|
||||
if verbose_level >= 2 {
|
||||
eprintln!("[selfhost/ny] received Program(JSON v0), size={} bytes", line.len());
|
||||
eprintln!(
|
||||
"[selfhost/ny] received Program(JSON v0), size={} bytes",
|
||||
line.len()
|
||||
);
|
||||
}
|
||||
match json::parse_json_v0_line(&line) {
|
||||
Ok(module) => {
|
||||
@ -297,11 +303,19 @@ impl NyashRunner {
|
||||
super::json_v0_bridge::maybe_dump_mir(&module);
|
||||
// Phase 28.2: observation log - after maybe_dump_mir
|
||||
if verbose_level >= 2 {
|
||||
if let Some(ref path) = crate::config::env::dump::rust_mir_dump_path() {
|
||||
if let Some(ref path) =
|
||||
crate::config::env::dump::rust_mir_dump_path()
|
||||
{
|
||||
if std::path::Path::new(path).exists() {
|
||||
eprintln!("[selfhost/ny] ✅ MIR dump file created: {}", path);
|
||||
eprintln!(
|
||||
"[selfhost/ny] ✅ MIR dump file created: {}",
|
||||
path
|
||||
);
|
||||
} else {
|
||||
eprintln!("[selfhost/ny] ⚠️ MIR dump file NOT created: {}", path);
|
||||
eprintln!(
|
||||
"[selfhost/ny] ⚠️ MIR dump file NOT created: {}",
|
||||
path
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -4,8 +4,8 @@
|
||||
* Constructs stage1_args based on execution mode (emit_program / emit_mir / run).
|
||||
*/
|
||||
|
||||
use crate::config::env::stage1;
|
||||
use crate::cli::CliGroups;
|
||||
use crate::config::env::stage1;
|
||||
use serde_json;
|
||||
use std::process;
|
||||
|
||||
@ -27,8 +27,7 @@ pub(super) struct Stage1Args {
|
||||
/// - run: run --backend <backend> <source.hako>
|
||||
pub(super) fn build_stage1_args(groups: &CliGroups) -> Stage1Args {
|
||||
// Prefer new env (NYASH_STAGE1_*) and fall back to legacy names to keep compatibility.
|
||||
let source = stage1::input_path()
|
||||
.or_else(|| groups.input.file.as_ref().cloned());
|
||||
let source = stage1::input_path().or_else(|| groups.input.file.as_ref().cloned());
|
||||
|
||||
let emit_program = stage1::emit_program_json();
|
||||
let emit_mir = stage1::emit_mir_json();
|
||||
@ -69,8 +68,7 @@ pub(super) fn build_stage1_args(groups: &CliGroups) -> Stage1Args {
|
||||
process::exit(97);
|
||||
});
|
||||
args.push("run".into());
|
||||
let backend = stage1::backend_hint()
|
||||
.unwrap_or_else(|| groups.backend.backend.clone());
|
||||
let backend = stage1::backend_hint().unwrap_or_else(|| groups.backend.backend.clone());
|
||||
args.push("--backend".into());
|
||||
args.push(backend);
|
||||
args.push(src);
|
||||
|
||||
@ -76,16 +76,28 @@ pub(super) fn configure_stage1_env(
|
||||
|
||||
// Parser toggles
|
||||
if std::env::var("NYASH_ENABLE_USING").is_err() {
|
||||
cmd.env("NYASH_ENABLE_USING", if env::enable_using() { "1" } else { "0" });
|
||||
cmd.env(
|
||||
"NYASH_ENABLE_USING",
|
||||
if env::enable_using() { "1" } else { "0" },
|
||||
);
|
||||
}
|
||||
if std::env::var("HAKO_ENABLE_USING").is_err() {
|
||||
cmd.env("HAKO_ENABLE_USING", if env::enable_using() { "1" } else { "0" });
|
||||
cmd.env(
|
||||
"HAKO_ENABLE_USING",
|
||||
if env::enable_using() { "1" } else { "0" },
|
||||
);
|
||||
}
|
||||
if std::env::var("NYASH_PARSER_STAGE3").is_err() {
|
||||
cmd.env("NYASH_PARSER_STAGE3", if env::parser_stage3() { "1" } else { "0" });
|
||||
cmd.env(
|
||||
"NYASH_PARSER_STAGE3",
|
||||
if env::parser_stage3() { "1" } else { "0" },
|
||||
);
|
||||
}
|
||||
if std::env::var("HAKO_PARSER_STAGE3").is_err() {
|
||||
cmd.env("HAKO_PARSER_STAGE3", if env::parser_stage3() { "1" } else { "0" });
|
||||
cmd.env(
|
||||
"HAKO_PARSER_STAGE3",
|
||||
if env::parser_stage3() { "1" } else { "0" },
|
||||
);
|
||||
}
|
||||
|
||||
// Modules list
|
||||
|
||||
@ -20,17 +20,21 @@ mod env;
|
||||
mod modules;
|
||||
|
||||
use super::NyashRunner;
|
||||
use crate::runner::stage1_bridge::args::Stage1Args;
|
||||
use crate::cli::CliGroups;
|
||||
use crate::config;
|
||||
use crate::config::env::stage1;
|
||||
use crate::cli::CliGroups;
|
||||
use crate::mir::MirPrinter;
|
||||
use crate::runner::stage1_bridge::args::Stage1Args;
|
||||
use std::io::Write;
|
||||
use std::path::Path;
|
||||
|
||||
impl NyashRunner {
|
||||
/// Emit Program(JSON v0) using Stage-1 stub and write to a file.
|
||||
pub(crate) fn emit_program_json_v0(&self, groups: &CliGroups, out_path: &str) -> Result<(), String> {
|
||||
pub(crate) fn emit_program_json_v0(
|
||||
&self,
|
||||
groups: &CliGroups,
|
||||
out_path: &str,
|
||||
) -> Result<(), String> {
|
||||
// Resolve source path from CLI groups or env
|
||||
let source = stage1::input_path()
|
||||
.or_else(|| groups.input.file.as_ref().cloned())
|
||||
@ -49,9 +53,8 @@ impl NyashRunner {
|
||||
let modules_list = modules::collect_modules_list();
|
||||
|
||||
// Prepare command
|
||||
let exe = std::env::current_exe().unwrap_or_else(|_| {
|
||||
std::path::PathBuf::from("target/release/nyash")
|
||||
});
|
||||
let exe = std::env::current_exe()
|
||||
.unwrap_or_else(|_| std::path::PathBuf::from("target/release/nyash"));
|
||||
let mut cmd = std::process::Command::new(exe);
|
||||
let entry_fn =
|
||||
std::env::var("NYASH_ENTRY").unwrap_or_else(|_| "Stage1CliMain.main/0".to_string());
|
||||
@ -77,7 +80,9 @@ impl NyashRunner {
|
||||
let output = cmd
|
||||
.output()
|
||||
.map_err(|e| format!("stage1 emit program-json spawn failed: {}", e))?;
|
||||
if !output.stderr.is_empty() && std::env::var("STAGE1_CLI_DEBUG").ok().as_deref() == Some("1") {
|
||||
if !output.stderr.is_empty()
|
||||
&& std::env::var("STAGE1_CLI_DEBUG").ok().as_deref() == Some("1")
|
||||
{
|
||||
let _ = std::io::stderr().write_all(&output.stderr);
|
||||
}
|
||||
if !output.status.success() {
|
||||
@ -94,9 +99,10 @@ impl NyashRunner {
|
||||
}
|
||||
let stdout = String::from_utf8_lossy(&output.stdout).to_string();
|
||||
let line = crate::runner::modes::common_util::selfhost::json::first_json_v0_line(&stdout)
|
||||
.ok_or_else(|| "stage1 emit program-json did not produce Program(JSON v0)".to_string())?;
|
||||
std::fs::write(out_path, line)
|
||||
.map_err(|e| format!("write {} failed: {}", out_path, e))?;
|
||||
.ok_or_else(|| {
|
||||
"stage1 emit program-json did not produce Program(JSON v0)".to_string()
|
||||
})?;
|
||||
std::fs::write(out_path, line).map_err(|e| format!("write {} failed: {}", out_path, e))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@ -199,7 +205,9 @@ impl NyashRunner {
|
||||
return Some(code);
|
||||
}
|
||||
let stdout = String::from_utf8_lossy(&output.stdout).to_string();
|
||||
let line = match crate::runner::modes::common_util::selfhost::json::first_json_v0_line(&stdout) {
|
||||
let line = match crate::runner::modes::common_util::selfhost::json::first_json_v0_line(
|
||||
&stdout,
|
||||
) {
|
||||
Some(l) => l,
|
||||
None => {
|
||||
eprintln!("[stage1-cli] emit-mir: no Program(JSON v0) found in stub output");
|
||||
|
||||
@ -3,7 +3,7 @@
|
||||
//! 目的: フィクスチャベースの AST→JoinIR テストを簡潔に書けるようにする
|
||||
|
||||
use crate::mir::join_ir::frontend::AstToJoinIrLowerer;
|
||||
use crate::mir::join_ir::{JoinModule, JoinFuncId};
|
||||
use crate::mir::join_ir::{JoinFuncId, JoinModule};
|
||||
use crate::mir::join_ir_ops::JoinValue;
|
||||
use crate::mir::join_ir_vm_bridge::run_joinir_via_vm;
|
||||
|
||||
@ -66,19 +66,13 @@ impl JoinIrFrontendTestRunner {
|
||||
}
|
||||
|
||||
/// テストケースを実行(単一入力・単一出力)
|
||||
pub fn run_case(
|
||||
&self,
|
||||
inputs: &[JoinValue],
|
||||
expected: JoinValue,
|
||||
) -> Result<(), String> {
|
||||
let module = self.join_module.as_ref()
|
||||
pub fn run_case(&self, inputs: &[JoinValue], expected: JoinValue) -> Result<(), String> {
|
||||
let module = self
|
||||
.join_module
|
||||
.as_ref()
|
||||
.ok_or("Module not lowered. Call .lower() first")?;
|
||||
|
||||
let result = run_joinir_via_vm(
|
||||
module,
|
||||
module.entry.unwrap(),
|
||||
inputs,
|
||||
).map_err(|e| {
|
||||
let result = run_joinir_via_vm(module, module.entry.unwrap(), inputs).map_err(|e| {
|
||||
format!(
|
||||
"JoinIR execution failed\n\
|
||||
Inputs: {:?}\n\
|
||||
@ -107,10 +101,7 @@ impl JoinIrFrontendTestRunner {
|
||||
}
|
||||
|
||||
/// 複数テストケースを一括実行
|
||||
pub fn run_cases(
|
||||
&self,
|
||||
cases: &[(Vec<JoinValue>, JoinValue)],
|
||||
) -> Result<(), String> {
|
||||
pub fn run_cases(&self, cases: &[(Vec<JoinValue>, JoinValue)]) -> Result<(), String> {
|
||||
for (inputs, expected) in cases {
|
||||
self.run_case(inputs, expected.clone())?;
|
||||
}
|
||||
|
||||
@ -38,8 +38,14 @@ fn test_extract_assigned_vars_from_body() {
|
||||
let result = lowerer.extract_assigned_vars_from_body(&body);
|
||||
|
||||
assert!(result.contains("x"), "Should detect x assignment");
|
||||
assert!(result.contains("y"), "Should detect y assignment in if-branch");
|
||||
assert!(result.contains("z"), "Should detect z assignment in loop-body");
|
||||
assert!(
|
||||
result.contains("y"),
|
||||
"Should detect y assignment in if-branch"
|
||||
);
|
||||
assert!(
|
||||
result.contains("z"),
|
||||
"Should detect z assignment in loop-body"
|
||||
);
|
||||
assert_eq!(result.len(), 3, "Should detect exactly 3 assignments");
|
||||
}
|
||||
|
||||
@ -62,7 +68,10 @@ fn test_extract_if_assigned_vars() {
|
||||
let mut lowerer = crate::mir::join_ir::frontend::ast_lowerer::AstToJoinIrLowerer::new();
|
||||
let result = lowerer.extract_if_assigned_vars(&body);
|
||||
|
||||
assert!(!result.contains("x"), "Should NOT include top-level assignment");
|
||||
assert!(
|
||||
!result.contains("x"),
|
||||
"Should NOT include top-level assignment"
|
||||
);
|
||||
assert!(result.contains("y"), "Should include if-then assignment");
|
||||
assert!(result.contains("z"), "Should include if-else assignment");
|
||||
assert_eq!(result.len(), 2, "Should detect exactly 2 if-assignments");
|
||||
@ -90,7 +99,17 @@ fn test_extract_if_in_loop_modified_vars() {
|
||||
let mut lowerer = crate::mir::join_ir::frontend::ast_lowerer::AstToJoinIrLowerer::new();
|
||||
let result = lowerer.extract_if_in_loop_modified_vars(&loop_body, &loop_vars);
|
||||
|
||||
assert!(!result.contains("i"), "Should NOT include non-if assignment");
|
||||
assert!(result.contains("out"), "Should include if-in-loop modification");
|
||||
assert_eq!(result.len(), 1, "Should detect exactly 1 if-in-loop variable");
|
||||
assert!(
|
||||
!result.contains("i"),
|
||||
"Should NOT include non-if assignment"
|
||||
);
|
||||
assert!(
|
||||
result.contains("out"),
|
||||
"Should include if-in-loop modification"
|
||||
);
|
||||
assert_eq!(
|
||||
result.len(),
|
||||
1,
|
||||
"Should detect exactly 1 if-in-loop variable"
|
||||
);
|
||||
}
|
||||
|
||||
@ -3,8 +3,8 @@
|
||||
//! Route A: 既存経路(AST→MIR→PHI→VM)
|
||||
//! Route B: 新経路(AST→JoinIR→MIR'→VM)
|
||||
|
||||
use crate::tests::helpers::joinir_frontend::JoinIrFrontendTestRunner;
|
||||
use crate::mir::join_ir_ops::JoinValue;
|
||||
use crate::tests::helpers::joinir_frontend::JoinIrFrontendTestRunner;
|
||||
|
||||
/// Phase 34-2: IfSelect simple pattern の A/B テスト
|
||||
///
|
||||
|
||||
@ -367,7 +367,11 @@ fn run_snapshot_test(case: SnapshotCase) {
|
||||
};
|
||||
|
||||
// フィクスチャ生成モード
|
||||
if std::env::var("NYASH_JOINIR_SNAPSHOT_GENERATE").ok().as_deref() == Some("1") {
|
||||
if std::env::var("NYASH_JOINIR_SNAPSHOT_GENERATE")
|
||||
.ok()
|
||||
.as_deref()
|
||||
== Some("1")
|
||||
{
|
||||
std::fs::write(&fixture_path, &json).expect("Failed to write fixture");
|
||||
eprintln!(
|
||||
"[joinir/snapshot] Generated fixture: {} ({} bytes)",
|
||||
@ -405,10 +409,7 @@ fn run_snapshot_test(case: SnapshotCase) {
|
||||
panic!("jsonir v0 snapshot mismatch for {}", case.name());
|
||||
}
|
||||
|
||||
eprintln!(
|
||||
"[joinir/snapshot] {} matches fixture ✓",
|
||||
case.name()
|
||||
);
|
||||
eprintln!("[joinir/snapshot] {} matches fixture ✓", case.name());
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
@ -538,7 +539,10 @@ fn joinir_stageb_body_structure_test() {
|
||||
"[joinir/stageb_body] Function '{}': {} blocks",
|
||||
name, block_count
|
||||
);
|
||||
assert!(block_count >= 1, "Each function should have at least 1 block");
|
||||
assert!(
|
||||
block_count >= 1,
|
||||
"Each function should have at least 1 block"
|
||||
);
|
||||
}
|
||||
|
||||
eprintln!("[joinir/stageb_body] ✅ Structure test passed");
|
||||
@ -551,7 +555,8 @@ fn joinir_stageb_funcscanner_structure_test() {
|
||||
std::env::set_var("NYASH_PARSER_STAGE3", "1");
|
||||
std::env::set_var("HAKO_PARSER_STAGE3", "1");
|
||||
|
||||
let src = match std::fs::read_to_string("apps/tests/stageb_funcscanner_scan_boxes_minimal.hako") {
|
||||
let src = match std::fs::read_to_string("apps/tests/stageb_funcscanner_scan_boxes_minimal.hako")
|
||||
{
|
||||
Ok(s) => s,
|
||||
Err(_) => {
|
||||
eprintln!("[joinir/stageb_funcscanner] Source file not found, skipping");
|
||||
@ -600,7 +605,10 @@ fn joinir_stageb_funcscanner_structure_test() {
|
||||
let mir_module = match convert_joinir_to_mir(&join_module) {
|
||||
Ok(m) => m,
|
||||
Err(e) => {
|
||||
eprintln!("[joinir/stageb_funcscanner] JoinIR→MIR conversion failed: {:?}", e);
|
||||
eprintln!(
|
||||
"[joinir/stageb_funcscanner] JoinIR→MIR conversion failed: {:?}",
|
||||
e
|
||||
);
|
||||
panic!("JoinIR→MIR conversion should succeed");
|
||||
}
|
||||
};
|
||||
@ -624,7 +632,10 @@ fn joinir_stageb_funcscanner_structure_test() {
|
||||
"[joinir/stageb_funcscanner] Function '{}': {} blocks",
|
||||
name, block_count
|
||||
);
|
||||
assert!(block_count >= 1, "Each function should have at least 1 block");
|
||||
assert!(
|
||||
block_count >= 1,
|
||||
"Each function should have at least 1 block"
|
||||
);
|
||||
}
|
||||
|
||||
eprintln!("[joinir/stageb_funcscanner] ✅ Structure test passed");
|
||||
|
||||
@ -10,8 +10,8 @@
|
||||
// - Runner サポート BoxCall: StringBox.length, StringBox.substring のみ
|
||||
// - ArrayBox/MapBox は未実装 (joinir_coverage.md A-2.5 参照)
|
||||
|
||||
use crate::mir::join_ir::*;
|
||||
use crate::mir::join_ir::verify::verify_progress_for_skip_ws;
|
||||
use crate::mir::join_ir::*;
|
||||
use crate::mir::join_ir_runner::{run_joinir_function, JoinValue};
|
||||
|
||||
fn require_experiment_toggle() -> bool {
|
||||
|
||||
@ -12,7 +12,7 @@
|
||||
// - Phase 30.x: Stage-1 UsingResolver minimal A/B test
|
||||
|
||||
use crate::ast::ASTNode;
|
||||
use crate::backend::{VM, VMValue};
|
||||
use crate::backend::{VMValue, VM};
|
||||
use crate::boxes::array::ArrayBox;
|
||||
use crate::boxes::basic::StringBox;
|
||||
use crate::boxes::map_box::MapBox;
|
||||
@ -133,7 +133,10 @@ fn joinir_vm_bridge_stage1_usingresolver_empty_entries() {
|
||||
let vm_result = vm_out.to_string_box().value.clone();
|
||||
std::env::remove_var("NYASH_ENTRY");
|
||||
|
||||
eprintln!("[joinir_vm_bridge_test/stage1] Route A result: {:?}", vm_result);
|
||||
eprintln!(
|
||||
"[joinir_vm_bridge_test/stage1] Route A result: {:?}",
|
||||
vm_result
|
||||
);
|
||||
|
||||
// Route C: AST → MIR → JoinIR → MIR' → VM (via bridge)
|
||||
eprintln!("[joinir_vm_bridge_test/stage1] Route C: JoinIR → VM bridge execution");
|
||||
@ -143,8 +146,12 @@ fn joinir_vm_bridge_stage1_usingresolver_empty_entries() {
|
||||
// 空配列、n=0、空Map、空Map、prefix="init" を引数として渡す
|
||||
// JoinValue で表現可能な引数のみ(ArrayBox/MapBox は現状未サポート)
|
||||
// このテストは JoinIR lowering の構造検証が主目的
|
||||
eprintln!("[joinir_vm_bridge_test/stage1] Note: JoinIR bridge with ArrayBox requires VM-side support");
|
||||
eprintln!("[joinir_vm_bridge_test/stage1] Skipping direct bridge call - structure verification only");
|
||||
eprintln!(
|
||||
"[joinir_vm_bridge_test/stage1] Note: JoinIR bridge with ArrayBox requires VM-side support"
|
||||
);
|
||||
eprintln!(
|
||||
"[joinir_vm_bridge_test/stage1] Skipping direct bridge call - structure verification only"
|
||||
);
|
||||
|
||||
// 構造検証:JoinModule が正しく生成されているか
|
||||
assert_eq!(
|
||||
@ -158,12 +165,20 @@ fn joinir_vm_bridge_stage1_usingresolver_empty_entries() {
|
||||
if vm_result == "init" {
|
||||
eprintln!("[joinir_vm_bridge_test/stage1] ✅ VM returned expected 'init'");
|
||||
} else {
|
||||
eprintln!("[joinir_vm_bridge_test/stage1] ⚠️ VM returned '{}' (PHI bug - expected 'init')", vm_result);
|
||||
eprintln!(
|
||||
"[joinir_vm_bridge_test/stage1] ⚠️ VM returned '{}' (PHI bug - expected 'init')",
|
||||
vm_result
|
||||
);
|
||||
eprintln!("[joinir_vm_bridge_test/stage1] JoinIR would fix this by design");
|
||||
}
|
||||
|
||||
eprintln!("[joinir_vm_bridge_test/stage1] ✅ Empty entries test passed (structure verification)");
|
||||
eprintln!("[joinir_vm_bridge_test/stage1] VM result: {:?}, JoinIR structure: 2 functions", vm_result);
|
||||
eprintln!(
|
||||
"[joinir_vm_bridge_test/stage1] ✅ Empty entries test passed (structure verification)"
|
||||
);
|
||||
eprintln!(
|
||||
"[joinir_vm_bridge_test/stage1] VM result: {:?}, JoinIR structure: 2 functions",
|
||||
vm_result
|
||||
);
|
||||
|
||||
// クリーンアップ
|
||||
std::env::remove_var("NYASH_PARSER_STAGE3");
|
||||
@ -204,7 +219,10 @@ fn joinir_vm_bridge_stage1_usingresolver_with_entries() {
|
||||
let vm_result = vm_out.to_string_box().value.clone();
|
||||
std::env::remove_var("NYASH_ENTRY");
|
||||
|
||||
eprintln!("[joinir_vm_bridge_test/stage1] Route A result: {:?}", vm_result);
|
||||
eprintln!(
|
||||
"[joinir_vm_bridge_test/stage1] Route A result: {:?}",
|
||||
vm_result
|
||||
);
|
||||
|
||||
// Route C: JoinIR 構造検証
|
||||
eprintln!("[joinir_vm_bridge_test/stage1] Route C: JoinIR structure verification");
|
||||
@ -224,7 +242,10 @@ fn joinir_vm_bridge_stage1_usingresolver_with_entries() {
|
||||
if vm_result == "ABC" {
|
||||
eprintln!("[joinir_vm_bridge_test/stage1] ✅ VM returned expected 'ABC'");
|
||||
} else {
|
||||
eprintln!("[joinir_vm_bridge_test/stage1] ⚠️ VM returned '{}' (possible PHI bug)", vm_result);
|
||||
eprintln!(
|
||||
"[joinir_vm_bridge_test/stage1] ⚠️ VM returned '{}' (possible PHI bug)",
|
||||
vm_result
|
||||
);
|
||||
eprintln!("[joinir_vm_bridge_test/stage1] JoinIR would fix this by design");
|
||||
}
|
||||
|
||||
@ -255,16 +276,17 @@ fn joinir_vm_bridge_stage1_usingresolver_route_b_execution() {
|
||||
let ast: ASTNode =
|
||||
NyashParser::parse_from_string(&full_src).expect("stage1 route_b: parse failed");
|
||||
let mut mc = MirCompiler::with_options(false);
|
||||
let compiled = mc
|
||||
.compile(ast)
|
||||
.expect("stage1 route_b: MIR compile failed");
|
||||
let compiled = mc.compile(ast).expect("stage1 route_b: MIR compile failed");
|
||||
|
||||
eprintln!("[joinir_vm_bridge_test/stage1/route_b] Starting Route B execution test");
|
||||
|
||||
let join_module = lower_stage1_usingresolver_to_joinir(&compiled.module)
|
||||
.expect("lower_stage1_usingresolver_to_joinir failed");
|
||||
|
||||
eprintln!("[joinir_vm_bridge_test/stage1/route_b] JoinIR module created: {} functions", join_module.functions.len());
|
||||
eprintln!(
|
||||
"[joinir_vm_bridge_test/stage1/route_b] JoinIR module created: {} functions",
|
||||
join_module.functions.len()
|
||||
);
|
||||
|
||||
eprintln!("[joinir_vm_bridge_test/stage1/route_b] Attempting run_joinir_via_vm with n=0 (Array/Map supported)");
|
||||
|
||||
@ -282,7 +304,10 @@ fn joinir_vm_bridge_stage1_usingresolver_route_b_execution() {
|
||||
|
||||
match result {
|
||||
Ok(value) => {
|
||||
eprintln!("[joinir_vm_bridge_test/stage1/route_b] ✅ Execution succeeded: {:?}", value);
|
||||
eprintln!(
|
||||
"[joinir_vm_bridge_test/stage1/route_b] ✅ Execution succeeded: {:?}",
|
||||
value
|
||||
);
|
||||
// n=0 の場合、ループは実行されず prefix_init がそのまま返るはず
|
||||
match &value {
|
||||
JoinValue::Str(s) => {
|
||||
@ -299,7 +324,10 @@ fn joinir_vm_bridge_stage1_usingresolver_route_b_execution() {
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!("[joinir_vm_bridge_test/stage1/route_b] ❌ Execution failed: {:?}", e);
|
||||
eprintln!(
|
||||
"[joinir_vm_bridge_test/stage1/route_b] ❌ Execution failed: {:?}",
|
||||
e
|
||||
);
|
||||
eprintln!("[joinir_vm_bridge_test/stage1/route_b] This error shows where VM bridge needs extension");
|
||||
panic!("JoinIR bridge failed: {:?}", e);
|
||||
}
|
||||
@ -325,8 +353,8 @@ fn joinir_vm_bridge_stage1_usingresolver_route_b_with_entries() {
|
||||
|
||||
let full_src = format!("{STAGE1_USINGRESOLVER_SOURCE}\n{RUNNER_WITH_ENTRIES_SOURCE}");
|
||||
|
||||
let ast: ASTNode =
|
||||
NyashParser::parse_from_string(&full_src).expect("stage1 route_b_with_entries: parse failed");
|
||||
let ast: ASTNode = NyashParser::parse_from_string(&full_src)
|
||||
.expect("stage1 route_b_with_entries: parse failed");
|
||||
let mut mc = MirCompiler::with_options(false);
|
||||
let compiled = mc
|
||||
.compile(ast)
|
||||
@ -337,11 +365,16 @@ fn joinir_vm_bridge_stage1_usingresolver_route_b_with_entries() {
|
||||
let join_module = lower_stage1_usingresolver_to_joinir(&compiled.module)
|
||||
.expect("lower_stage1_usingresolver_to_joinir failed");
|
||||
|
||||
eprintln!("[joinir_vm_bridge_test/stage1/route_b_with_entries] JoinIR module created: {} functions", join_module.functions.len());
|
||||
eprintln!(
|
||||
"[joinir_vm_bridge_test/stage1/route_b_with_entries] JoinIR module created: {} functions",
|
||||
join_module.functions.len()
|
||||
);
|
||||
|
||||
// n=3 の場合、ループが実行され、entries.get(i) が呼ばれる
|
||||
// JoinValue::Int(0) を entries に渡すと、get メソッド呼び出しで失敗するはず
|
||||
eprintln!("[joinir_vm_bridge_test/stage1/route_b_with_entries] Attempting run_joinir_via_vm with n=3");
|
||||
eprintln!(
|
||||
"[joinir_vm_bridge_test/stage1/route_b_with_entries] Attempting run_joinir_via_vm with n=3"
|
||||
);
|
||||
eprintln!("[joinir_vm_bridge_test/stage1/route_b_with_entries] Note: ArrayBox passed as Int(0) - expecting failure at get() call");
|
||||
|
||||
let result = run_joinir_via_vm(
|
||||
@ -358,10 +391,16 @@ fn joinir_vm_bridge_stage1_usingresolver_route_b_with_entries() {
|
||||
|
||||
match result {
|
||||
Ok(value) => {
|
||||
eprintln!("[joinir_vm_bridge_test/stage1/route_b_with_entries] ✅ Execution succeeded: {:?}", value);
|
||||
eprintln!(
|
||||
"[joinir_vm_bridge_test/stage1/route_b_with_entries] ✅ Execution succeeded: {:?}",
|
||||
value
|
||||
);
|
||||
match value {
|
||||
JoinValue::Str(s) => {
|
||||
eprintln!("[joinir_vm_bridge_test/stage1/route_b_with_entries] Result: {:?}", s);
|
||||
eprintln!(
|
||||
"[joinir_vm_bridge_test/stage1/route_b_with_entries] Result: {:?}",
|
||||
s
|
||||
);
|
||||
if s == "ABC" {
|
||||
eprintln!("[joinir_vm_bridge_test/stage1/route_b_with_entries] ✅ JoinIR returned correct 'ABC'!");
|
||||
} else {
|
||||
@ -374,7 +413,10 @@ fn joinir_vm_bridge_stage1_usingresolver_route_b_with_entries() {
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!("[joinir_vm_bridge_test/stage1/route_b_with_entries] ❌ Execution failed: {:?}", e);
|
||||
eprintln!(
|
||||
"[joinir_vm_bridge_test/stage1/route_b_with_entries] ❌ Execution failed: {:?}",
|
||||
e
|
||||
);
|
||||
panic!("JoinIR bridge failed: {:?}", e);
|
||||
}
|
||||
}
|
||||
@ -394,8 +436,8 @@ fn joinir_vm_bridge_stage1_usingresolver_lowering_sanity() {
|
||||
|
||||
let full_src = format!("{STAGE1_USINGRESOLVER_SOURCE}\n{RUNNER_SOURCE}");
|
||||
|
||||
let ast: ASTNode =
|
||||
NyashParser::parse_from_string(&full_src).expect("stage1_usingresolver sanity: parse failed");
|
||||
let ast: ASTNode = NyashParser::parse_from_string(&full_src)
|
||||
.expect("stage1_usingresolver sanity: parse failed");
|
||||
let mut mc = MirCompiler::with_options(false);
|
||||
let compiled = mc
|
||||
.compile(ast)
|
||||
|
||||
@ -126,7 +126,9 @@ static box Runner {
|
||||
JoinValue::Str(s) => {
|
||||
assert_eq!(s, "abc", "Route C (JoinIR→VM bridge) trim result mismatch");
|
||||
if vm_result == "abc" {
|
||||
eprintln!("[joinir_vm_bridge_test/trim] ✅ A/B test passed: both routes returned 'abc'");
|
||||
eprintln!(
|
||||
"[joinir_vm_bridge_test/trim] ✅ A/B test passed: both routes returned 'abc'"
|
||||
);
|
||||
} else {
|
||||
eprintln!("[joinir_vm_bridge_test/trim] ⚠️ Route A (VM) returned '{}' (PHI bug), Route C (JoinIR) returned 'abc' (correct)", vm_result);
|
||||
eprintln!("[joinir_vm_bridge_test/trim] ✅ JoinIR correctly handles PHI issues that affect direct VM path");
|
||||
@ -209,10 +211,7 @@ static box Runner {{
|
||||
input, s
|
||||
);
|
||||
}
|
||||
other => panic!(
|
||||
"trim({:?}) returned non-string value: {:?}",
|
||||
input, other
|
||||
),
|
||||
other => panic!("trim({:?}) returned non-string value: {:?}", input, other),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -6,9 +6,7 @@
|
||||
mod tests {
|
||||
use crate::mir::join_ir::lowering::try_lower_if_to_joinir;
|
||||
use crate::mir::join_ir::JoinInst;
|
||||
use crate::mir::{
|
||||
BasicBlock, BasicBlockId, MirFunction, MirInstruction, MirModule, ValueId,
|
||||
};
|
||||
use crate::mir::{BasicBlock, BasicBlockId, MirFunction, MirInstruction, MirModule, ValueId};
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
/// Helper to create a simple if/else function matching the "simple" pattern
|
||||
@ -40,8 +38,8 @@ mod tests {
|
||||
});
|
||||
blocks.insert(BasicBlockId::new(2), else_block);
|
||||
|
||||
use crate::mir::{EffectMask, MirType};
|
||||
use crate::mir::function::FunctionMetadata;
|
||||
use crate::mir::{EffectMask, MirType};
|
||||
use std::collections::HashMap;
|
||||
|
||||
MirFunction {
|
||||
@ -104,8 +102,8 @@ mod tests {
|
||||
});
|
||||
blocks.insert(BasicBlockId::new(3), merge_block);
|
||||
|
||||
use crate::mir::{EffectMask, MirType};
|
||||
use crate::mir::function::FunctionMetadata;
|
||||
use crate::mir::{EffectMask, MirType};
|
||||
use std::collections::HashMap;
|
||||
|
||||
MirFunction {
|
||||
@ -150,7 +148,10 @@ mod tests {
|
||||
}) = result
|
||||
{
|
||||
eprintln!("✅ Simple pattern successfully lowered to Select");
|
||||
eprintln!(" dst: {:?}, cond: {:?}, then: {:?}, else: {:?}", dst, cond, then_val, else_val);
|
||||
eprintln!(
|
||||
" dst: {:?}, cond: {:?}, then: {:?}, else: {:?}",
|
||||
dst, cond, then_val, else_val
|
||||
);
|
||||
} else {
|
||||
panic!("Expected JoinInst::Select, got {:?}", result);
|
||||
}
|
||||
@ -173,7 +174,10 @@ mod tests {
|
||||
}) = result
|
||||
{
|
||||
eprintln!("✅ Local pattern successfully lowered to Select");
|
||||
eprintln!(" dst: {:?}, cond: {:?}, then: {:?}, else: {:?}", dst, cond, then_val, else_val);
|
||||
eprintln!(
|
||||
" dst: {:?}, cond: {:?}, then: {:?}, else: {:?}",
|
||||
dst, cond, then_val, else_val
|
||||
);
|
||||
} else {
|
||||
panic!("Expected JoinInst::Select, got {:?}", result);
|
||||
}
|
||||
@ -257,11 +261,8 @@ mod tests {
|
||||
use crate::mir::join_ir::{ConstValue, JoinFuncId, JoinFunction, JoinInst, MirLikeInst};
|
||||
|
||||
let func_id = JoinFuncId::new(0);
|
||||
let mut join_func = JoinFunction::new(
|
||||
func_id,
|
||||
"IfSelectTest.test/1".to_string(),
|
||||
vec![ValueId(0)],
|
||||
);
|
||||
let mut join_func =
|
||||
JoinFunction::new(func_id, "IfSelectTest.test/1".to_string(), vec![ValueId(0)]);
|
||||
|
||||
// First Select
|
||||
join_func.body.push(JoinInst::Select {
|
||||
@ -331,10 +332,7 @@ mod tests {
|
||||
|
||||
// Verifier should reject
|
||||
let result = verify_select_minimal(&join_func, true);
|
||||
assert!(
|
||||
result.is_err(),
|
||||
"Verify should reject multiple Selects"
|
||||
);
|
||||
assert!(result.is_err(), "Verify should reject multiple Selects");
|
||||
|
||||
match result {
|
||||
Err(e) => {
|
||||
@ -421,8 +419,8 @@ mod tests {
|
||||
});
|
||||
blocks.insert(BasicBlockId::new(2), else_block);
|
||||
|
||||
use crate::mir::{EffectMask, MirType};
|
||||
use crate::mir::function::FunctionMetadata;
|
||||
use crate::mir::{EffectMask, MirType};
|
||||
use std::collections::HashMap;
|
||||
|
||||
MirFunction {
|
||||
@ -492,8 +490,8 @@ mod tests {
|
||||
});
|
||||
blocks.insert(BasicBlockId::new(2), else_block);
|
||||
|
||||
use crate::mir::{EffectMask, MirType};
|
||||
use crate::mir::function::FunctionMetadata;
|
||||
use crate::mir::{EffectMask, MirType};
|
||||
use std::collections::HashMap;
|
||||
|
||||
MirFunction {
|
||||
@ -528,11 +526,24 @@ mod tests {
|
||||
"Expected simple 2-variable pattern to be lowered to IfMerge"
|
||||
);
|
||||
|
||||
if let Some(JoinInst::IfMerge { cond, merges, k_next }) = result {
|
||||
if let Some(JoinInst::IfMerge {
|
||||
cond,
|
||||
merges,
|
||||
k_next,
|
||||
}) = result
|
||||
{
|
||||
eprintln!("✅ Simple pattern (2 vars) successfully lowered to IfMerge");
|
||||
eprintln!(" cond: {:?}, merges: {} pairs, k_next: {:?}", cond, merges.len(), k_next);
|
||||
eprintln!(
|
||||
" cond: {:?}, merges: {} pairs, k_next: {:?}",
|
||||
cond,
|
||||
merges.len(),
|
||||
k_next
|
||||
);
|
||||
assert_eq!(merges.len(), 2, "Expected 2 MergePairs for x and y");
|
||||
assert!(k_next.is_none(), "Phase 33-7 constraint: k_next should be None");
|
||||
assert!(
|
||||
k_next.is_none(),
|
||||
"Phase 33-7 constraint: k_next should be None"
|
||||
);
|
||||
} else {
|
||||
panic!("Expected JoinInst::IfMerge, got {:?}", result);
|
||||
}
|
||||
@ -556,11 +567,24 @@ mod tests {
|
||||
"Expected multiple 3-variable pattern to be lowered to IfMerge"
|
||||
);
|
||||
|
||||
if let Some(JoinInst::IfMerge { cond, merges, k_next }) = result {
|
||||
if let Some(JoinInst::IfMerge {
|
||||
cond,
|
||||
merges,
|
||||
k_next,
|
||||
}) = result
|
||||
{
|
||||
eprintln!("✅ Multiple pattern (3 vars) successfully lowered to IfMerge");
|
||||
eprintln!(" cond: {:?}, merges: {} pairs, k_next: {:?}", cond, merges.len(), k_next);
|
||||
eprintln!(
|
||||
" cond: {:?}, merges: {} pairs, k_next: {:?}",
|
||||
cond,
|
||||
merges.len(),
|
||||
k_next
|
||||
);
|
||||
assert_eq!(merges.len(), 3, "Expected 3 MergePairs for x, y, and z");
|
||||
assert!(k_next.is_none(), "Phase 33-7 constraint: k_next should be None");
|
||||
assert!(
|
||||
k_next.is_none(),
|
||||
"Phase 33-7 constraint: k_next should be None"
|
||||
);
|
||||
} else {
|
||||
panic!("Expected JoinInst::IfMerge, got {:?}", result);
|
||||
}
|
||||
|
||||
@ -12,8 +12,8 @@ pub mod joinir_json_min; // Phase 30.x: JoinIR JSON シリアライズテスト
|
||||
pub mod joinir_runner_min; // Phase 27.2: JoinIR 実行器 A/B 比較テスト
|
||||
pub mod joinir_runner_standalone; // Phase 27-shortterm S-3.2: JoinIR Runner 単体テスト
|
||||
pub mod joinir_vm_bridge_skip_ws; // Phase 27-shortterm S-4.4: JoinIR → Rust VM Bridge A/B Test
|
||||
pub mod joinir_vm_bridge_trim; // Phase 30.x: JoinIR → Rust VM Bridge A/B Test for trim
|
||||
pub mod joinir_vm_bridge_stage1_usingresolver; // Phase 30.x: JoinIR → Rust VM Bridge A/B Test for Stage-1
|
||||
pub mod joinir_vm_bridge_trim; // Phase 30.x: JoinIR → Rust VM Bridge A/B Test for trim
|
||||
pub mod json_lint_stringutils_min_vm; // Phase 21.7++: using StringUtils alias resolution fix
|
||||
pub mod mir_breakfinder_ssa;
|
||||
pub mod mir_funcscanner_parse_params_trim_min;
|
||||
@ -58,7 +58,7 @@ pub mod vtable_strict;
|
||||
pub mod vtable_string;
|
||||
|
||||
// Phase 34-2: JoinIR Frontend (AST→JoinIR)
|
||||
pub mod joinir_frontend_if_select;
|
||||
pub mod joinir_frontend_if_in_loop_test; // Phase 40-1: If-in-loop variable tracking A/B test
|
||||
pub mod joinir_frontend_if_select;
|
||||
pub mod phase40_array_ext_filter_test; // Phase 40-1.1: array_ext.filter A/B test (collect_assigned_vars deletion)
|
||||
pub mod phase41_nested_if_merge_test; // Phase 41-4: NestedIfMerge A/B test (parse_loop)
|
||||
|
||||
@ -44,7 +44,10 @@ fn phase40_joinir_meta_helpers_work() {
|
||||
|
||||
// Verify: out is detected, i is not
|
||||
assert!(!result.contains("i"), "Loop counter should NOT be included");
|
||||
assert!(result.contains("out"), "If-in-loop modified var should be included");
|
||||
assert!(
|
||||
result.contains("out"),
|
||||
"If-in-loop modified var should be included"
|
||||
);
|
||||
assert_eq!(result.len(), 1, "Exactly 1 if-in-loop variable");
|
||||
}
|
||||
|
||||
@ -55,8 +58,8 @@ fn phase40_joinir_meta_helpers_work() {
|
||||
/// 2. No panic or error
|
||||
#[test]
|
||||
fn phase40_mir_conversion_with_empty_meta() {
|
||||
use crate::mir::join_ir::{JoinFunction, JoinFuncId, JoinModule};
|
||||
use crate::mir::join_ir::frontend::JoinFuncMetaMap;
|
||||
use crate::mir::join_ir::{JoinFuncId, JoinFunction, JoinModule};
|
||||
use crate::mir::ValueId;
|
||||
|
||||
// Create minimal JoinModule
|
||||
@ -65,7 +68,9 @@ fn phase40_mir_conversion_with_empty_meta() {
|
||||
|
||||
let mut func = JoinFunction::new(func_id, "test_func".to_string(), vec![]);
|
||||
// Add a simple return instruction
|
||||
func.body.push(crate::mir::join_ir::JoinInst::Ret { value: Some(ValueId(0)) });
|
||||
func.body.push(crate::mir::join_ir::JoinInst::Ret {
|
||||
value: Some(ValueId(0)),
|
||||
});
|
||||
|
||||
module.functions.insert(func_id, func);
|
||||
|
||||
@ -87,8 +92,8 @@ fn phase40_mir_conversion_with_empty_meta() {
|
||||
/// 2. No panic even with metadata present
|
||||
#[test]
|
||||
fn phase40_mir_conversion_with_meta() {
|
||||
use crate::mir::join_ir::{JoinFunction, JoinFuncId, JoinModule};
|
||||
use crate::mir::join_ir::frontend::{JoinFuncMeta, JoinFuncMetaMap};
|
||||
use crate::mir::join_ir::{JoinFuncId, JoinFunction, JoinModule};
|
||||
use crate::mir::ValueId;
|
||||
|
||||
// Create minimal JoinModule
|
||||
@ -96,7 +101,9 @@ fn phase40_mir_conversion_with_meta() {
|
||||
let func_id = JoinFuncId::new(0);
|
||||
|
||||
let mut func = JoinFunction::new(func_id, "loop_step".to_string(), vec![]);
|
||||
func.body.push(crate::mir::join_ir::JoinInst::Ret { value: Some(ValueId(0)) });
|
||||
func.body.push(crate::mir::join_ir::JoinInst::Ret {
|
||||
value: Some(ValueId(0)),
|
||||
});
|
||||
|
||||
module.functions.insert(func_id, func);
|
||||
|
||||
@ -105,10 +112,13 @@ fn phase40_mir_conversion_with_meta() {
|
||||
let mut if_modified = HashSet::new();
|
||||
if_modified.insert("out".to_string());
|
||||
|
||||
meta.insert(func_id, JoinFuncMeta {
|
||||
meta.insert(
|
||||
func_id,
|
||||
JoinFuncMeta {
|
||||
if_modified_vars: Some(if_modified),
|
||||
..Default::default()
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
// Should not panic, metadata is logged but not used for PHI generation yet
|
||||
let result = convert_join_module_to_mir_with_meta(&module, &meta);
|
||||
@ -152,8 +162,7 @@ fn phase40_joinir_detects_local_declarations() {
|
||||
];
|
||||
|
||||
// JoinIR経由(メイン経路)
|
||||
let vars =
|
||||
crate::mir::phi_core::if_phi::collect_assigned_vars_via_joinir(&then_body, None);
|
||||
let vars = crate::mir::phi_core::if_phi::collect_assigned_vars_via_joinir(&then_body, None);
|
||||
|
||||
// Verify JoinIR detects Local declarations
|
||||
assert!(vars.contains("x"), "JoinIR should detect x declaration");
|
||||
@ -201,11 +210,13 @@ fn phase40_joinir_nested_if_local() {
|
||||
];
|
||||
|
||||
// JoinIR経由
|
||||
let vars =
|
||||
crate::mir::phi_core::if_phi::collect_assigned_vars_via_joinir(&then_body, None);
|
||||
let vars = crate::mir::phi_core::if_phi::collect_assigned_vars_via_joinir(&then_body, None);
|
||||
|
||||
// Verify: inner (nested in if) and outer (top-level) detected
|
||||
assert!(vars.contains("inner"), "JoinIR: should detect inner in nested if");
|
||||
assert!(
|
||||
vars.contains("inner"),
|
||||
"JoinIR: should detect inner in nested if"
|
||||
);
|
||||
assert!(vars.contains("outer"), "JoinIR: should detect outer");
|
||||
assert_eq!(vars.len(), 2, "Should detect exactly 2 declarations");
|
||||
}
|
||||
|
||||
@ -70,9 +70,14 @@ fn phase41_nested_if_merge_path_activation() {
|
||||
|
||||
// NestedIfMerge 命令が含まれていることを確認
|
||||
let entry_id = join_module.entry.expect("Entry should be set");
|
||||
let entry_func = join_module.functions.get(&entry_id).expect("Entry function");
|
||||
let entry_func = join_module
|
||||
.functions
|
||||
.get(&entry_id)
|
||||
.expect("Entry function");
|
||||
|
||||
let nested_if_merge_count = entry_func.body.iter()
|
||||
let nested_if_merge_count = entry_func
|
||||
.body
|
||||
.iter()
|
||||
.filter(|inst| matches!(inst, JoinInst::NestedIfMerge { .. }))
|
||||
.count();
|
||||
|
||||
@ -173,11 +178,17 @@ fn phase41_nested_if_merge_route_b_execution() {
|
||||
result,
|
||||
JoinValue::Int(expected),
|
||||
"[Phase 41-4.6] Route B execution failed for ({}, {}): expected {}, got {:?}",
|
||||
a, b, expected, result
|
||||
a,
|
||||
b,
|
||||
expected,
|
||||
result
|
||||
);
|
||||
}
|
||||
|
||||
eprintln!("[Phase 41-4.6] Route B execution test PASSED: {} test cases", test_cases.len());
|
||||
eprintln!(
|
||||
"[Phase 41-4.6] Route B execution test PASSED: {} test cases",
|
||||
test_cases.len()
|
||||
);
|
||||
}
|
||||
|
||||
/// Phase 41-4.6: Route A/B 結果比較テスト
|
||||
@ -246,8 +257,9 @@ fn phase41_nested_if_merge_route_ab_comparison() {
|
||||
let route_b_result = run_joinir_via_vm(
|
||||
&join_module,
|
||||
entry_id,
|
||||
&[JoinValue::Int(5), JoinValue::Int(5)]
|
||||
).expect("Route B execution failed");
|
||||
&[JoinValue::Int(5), JoinValue::Int(5)],
|
||||
)
|
||||
.expect("Route B execution failed");
|
||||
|
||||
// 期待結果: a=5, b=5 -> 両方 > 0 -> result = 42
|
||||
let expected = JoinValue::Int(42);
|
||||
|
||||
Reference in New Issue
Block a user