Files
hakorune/src/mir/builder/stmts.rs

351 lines
15 KiB
Rust
Raw Normal View History

use super::{Effect, EffectMask, MirInstruction, ValueId};
use crate::ast::{ASTNode, CallExpr};
use crate::mir::TypeOpKind;
use crate::mir::utils::is_current_block_terminated;
impl super::MirBuilder {
// Print statement: env.console.log(value) with early TypeOp handling
pub(super) fn build_print_statement(&mut self, expression: ASTNode) -> Result<ValueId, String> {
super::utils::builder_debug_log("enter build_print_statement");
// Prefer wrapper for simple function-call pattern (non-breaking refactor)
if let Ok(call) = CallExpr::try_from(expression.clone()) {
if (call.name == "isType" || call.name == "asType") && call.arguments.len() == 2 {
super::utils::builder_debug_log("pattern: print(FunctionCall isType|asType) [via wrapper]");
if let Some(type_name) = super::MirBuilder::extract_string_literal(&call.arguments[1]) {
super::utils::builder_debug_log(&format!("extract_string_literal OK: {}", type_name));
let val = self.build_expression(call.arguments[0].clone())?;
let ty = super::MirBuilder::parse_type_name_to_mir(&type_name);
let dst = self.next_value_id();
let op = if call.name == "isType" { TypeOpKind::Check } else { TypeOpKind::Cast };
super::utils::builder_debug_log(&format!("emit TypeOp {:?} value={} dst= {}", op, val, dst));
self.emit_instruction(MirInstruction::TypeOp { dst, op, value: val, ty })?;
self.emit_instruction(MirInstruction::ExternCall {
dst: None,
iface_name: "env.console".to_string(),
method_name: "log".to_string(),
args: vec![dst],
effects: EffectMask::PURE.add(Effect::Io),
})?;
return Ok(dst);
} else {
super::utils::builder_debug_log("extract_string_literal FAIL [via wrapper]");
}
}
}
match &expression {
// print(isType(val, "Type")) / print(asType(...))
ASTNode::FunctionCall {
name, arguments, ..
} if (name == "isType" || name == "asType") && arguments.len() == 2 => {
super::utils::builder_debug_log("pattern: print(FunctionCall isType|asType)");
if let Some(type_name) = super::MirBuilder::extract_string_literal(&arguments[1]) {
super::utils::builder_debug_log(&format!(
"extract_string_literal OK: {}",
type_name
));
let val = self.build_expression(arguments[0].clone())?;
let ty = super::MirBuilder::parse_type_name_to_mir(&type_name);
let dst = self.next_value_id();
let op = if name == "isType" {
TypeOpKind::Check
} else {
TypeOpKind::Cast
};
super::utils::builder_debug_log(&format!(
"emit TypeOp {:?} value={} dst= {}",
op, val, dst
));
self.emit_instruction(MirInstruction::TypeOp {
dst,
op,
value: val,
ty,
})?;
self.emit_instruction(MirInstruction::ExternCall {
dst: None,
iface_name: "env.console".to_string(),
method_name: "log".to_string(),
args: vec![dst],
effects: EffectMask::PURE.add(Effect::Io),
})?;
return Ok(dst);
} else {
super::utils::builder_debug_log("extract_string_literal FAIL");
}
}
// print(obj.is("Type")) / print(obj.as("Type"))
ASTNode::MethodCall {
object,
method,
arguments,
..
} if (method == "is" || method == "as") && arguments.len() == 1 => {
super::utils::builder_debug_log("pattern: print(MethodCall is|as)");
if let Some(type_name) = super::MirBuilder::extract_string_literal(&arguments[0]) {
super::utils::builder_debug_log(&format!(
"extract_string_literal OK: {}",
type_name
));
let obj_val = self.build_expression(*object.clone())?;
let ty = super::MirBuilder::parse_type_name_to_mir(&type_name);
let dst = self.next_value_id();
let op = if method == "is" {
TypeOpKind::Check
} else {
TypeOpKind::Cast
};
super::utils::builder_debug_log(&format!(
"emit TypeOp {:?} obj={} dst= {}",
op, obj_val, dst
));
self.emit_instruction(MirInstruction::TypeOp {
dst,
op,
value: obj_val,
ty,
})?;
self.emit_instruction(MirInstruction::ExternCall {
dst: None,
iface_name: "env.console".to_string(),
method_name: "log".to_string(),
args: vec![dst],
effects: EffectMask::PURE.add(Effect::Io),
})?;
return Ok(dst);
} else {
super::utils::builder_debug_log("extract_string_literal FAIL");
}
}
_ => {}
}
let value = self.build_expression(expression)?;
super::utils::builder_debug_log(&format!("fallback print value={}", value));
// Phase 3.2: Use unified call for print statements
let use_unified = super::calls::call_unified::is_unified_call_enabled();
if use_unified {
// New unified path - treat print as global function
self.emit_unified_call(
None, // print returns nothing
super::builder_calls::CallTarget::Global("print".to_string()),
vec![value],
)?;
} else {
// Legacy path - use ExternCall
self.emit_instruction(MirInstruction::ExternCall {
dst: None,
iface_name: "env.console".to_string(),
method_name: "log".to_string(),
args: vec![value],
effects: EffectMask::PURE.add(Effect::Io),
})?;
}
Ok(value)
}
// Block: sequentially build statements and return last value or Void
pub(super) fn build_block(&mut self, statements: Vec<ASTNode>) -> Result<ValueId, String> {
// Scope hint for bare block (Program)
let scope_id = self.current_block.map(|bb| bb.as_u32()).unwrap_or(0);
self.hint_scope_enter(scope_id);
let mut last_value = None;
for statement in statements {
last_value = Some(self.build_statement(statement)?);
// If the current block was terminated by this statement (e.g., return/throw),
// do not emit any further instructions for this block.
if is_current_block_terminated(self)? {
break;
}
}
let out = last_value.unwrap_or_else(|| {
// Use ConstantEmissionBox for Void
crate::mir::builder::emission::constant::emit_void(self)
});
// Scope leave only if block not already terminated
if !self.is_current_block_terminated() {
self.hint_scope_leave(scope_id);
}
Ok(out)
}
/// Build a single statement node.
///
/// Note:
/// - While/ForRange は将来 Loop lowering へ委譲する拡張ポイントとして扱い、
/// 現状は他の専用ビルダ/既存パスと同様に build_expression に委譲する。
pub(super) fn build_statement(&mut self, node: ASTNode) -> Result<ValueId, String> {
match node {
// 将来ここに While / ForRange / Match / Using など statement 専用分岐を追加する。
other => self.build_expression(other),
}
}
// Local declarations with optional initializers
pub(super) fn build_local_statement(
&mut self,
variables: Vec<String>,
initial_values: Vec<Option<Box<ASTNode>>>,
) -> Result<ValueId, String> {
fix(mir): LoopForm v2完全緑化 - ValueId(0)予約 & unreachable block許容 ## 🎯 完了タスク ✅ Task 1: LoopForm v2 最小ユニットテスト全緑化(4/4パス) ✅ Task 2: program_v0 PHI trace スクリプト全緑化(5/5パス) ✅ Task 3: Stage-B 風ループ Rust テスト全緑化(2/2パス) 🔧 Task 4: Stage-1 using resolver (1/3パス、UsingStatement対応完了) ## 📝 主要修正 ### 1. ValueId(0)を無効値として予約 - **src/mir/function.rs**: MirFunction::new() で next_value_id を1から開始 - **src/mir/builder/stmts.rs**: build_local_statement で next_value_id() 使用 - **理由**: LoopForm v2 が ValueId(0) を無効値の sentinel として使用 - **効果**: SSA 構築時の ValueId 衝突を完全に防止 ### 2. Unreachable block 許容をデフォルト化 - **src/mir/verification/cfg.rs**: 到達可能性チェック削除 - **src/config/env.rs**: NYASH_VERIFY_ALLOW_UNREACHABLE 環境変数削除 - **src/tests/mir_loopform_exit_phi.rs**: 環境変数設定削除 - **理由**: break/continue/return の後の unreachable block は正当 - switch_to_unreachable_block_with_void() で意図的に作成 - LLVM IR の `unreachable` 命令と同じ標準的手法 - 削除は DCE (Dead Code Elimination) パスの仕事 - **効果**: 環境変数を減らしてシンプル化 ### 3. UsingStatement の MIR Builder 対応 - **src/mir/builder/exprs.rs**: UsingStatement → void 変換を追加 - **理由**: namespace 解決は parser/runner レベルで完了済み - **効果**: using 文を含むコードが MIR コンパイル可能に ### 4. スモークテストスクリプト修正 - **tools/smokes/v2/profiles/quick/core/phase2034/*.sh**: 5ファイル - **修正内容**: 二重コマンド置換のシンタックスエラー修正 - 誤: `out="$(out="$(COMMAND)"; rc=$?` - 正: `out="$(COMMAND)"; rc=$?` ## 🧪 テスト結果 - mir_loopform_exit_phi: 4/4パス ✅ - program_v0_*_phi_trace_vm: 5/5パス ✅ - mir_stageb_loop_break_continue: 2/2パス ✅ - mir_stage1_using_resolver: 1/3パス (残り2つは dominator violation) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-18 06:11:17 +09:00
if std::env::var("NYASH_LOOPFORM_DEBUG").is_ok() {
eprintln!("[build_local_statement] ENTRY: variables={:?}, initial_values.len()={}", variables, initial_values.len());
}
let mut last_value = None;
for (i, var_name) in variables.iter().enumerate() {
let var_id = if i < initial_values.len() && initial_values[i].is_some() {
// Evaluate the initializer expression
let init_expr = initial_values[i].as_ref().unwrap();
let init_val = self.build_expression(*init_expr.clone())?;
// FIX: Allocate a new ValueId for this local variable
fix(mir): LoopForm v2完全緑化 - ValueId(0)予約 & unreachable block許容 ## 🎯 完了タスク ✅ Task 1: LoopForm v2 最小ユニットテスト全緑化(4/4パス) ✅ Task 2: program_v0 PHI trace スクリプト全緑化(5/5パス) ✅ Task 3: Stage-B 風ループ Rust テスト全緑化(2/2パス) 🔧 Task 4: Stage-1 using resolver (1/3パス、UsingStatement対応完了) ## 📝 主要修正 ### 1. ValueId(0)を無効値として予約 - **src/mir/function.rs**: MirFunction::new() で next_value_id を1から開始 - **src/mir/builder/stmts.rs**: build_local_statement で next_value_id() 使用 - **理由**: LoopForm v2 が ValueId(0) を無効値の sentinel として使用 - **効果**: SSA 構築時の ValueId 衝突を完全に防止 ### 2. Unreachable block 許容をデフォルト化 - **src/mir/verification/cfg.rs**: 到達可能性チェック削除 - **src/config/env.rs**: NYASH_VERIFY_ALLOW_UNREACHABLE 環境変数削除 - **src/tests/mir_loopform_exit_phi.rs**: 環境変数設定削除 - **理由**: break/continue/return の後の unreachable block は正当 - switch_to_unreachable_block_with_void() で意図的に作成 - LLVM IR の `unreachable` 命令と同じ標準的手法 - 削除は DCE (Dead Code Elimination) パスの仕事 - **効果**: 環境変数を減らしてシンプル化 ### 3. UsingStatement の MIR Builder 対応 - **src/mir/builder/exprs.rs**: UsingStatement → void 変換を追加 - **理由**: namespace 解決は parser/runner レベルで完了済み - **効果**: using 文を含むコードが MIR コンパイル可能に ### 4. スモークテストスクリプト修正 - **tools/smokes/v2/profiles/quick/core/phase2034/*.sh**: 5ファイル - **修正内容**: 二重コマンド置換のシンタックスエラー修正 - 誤: `out="$(out="$(COMMAND)"; rc=$?` - 正: `out="$(COMMAND)"; rc=$?` ## 🧪 テスト結果 - mir_loopform_exit_phi: 4/4パス ✅ - program_v0_*_phi_trace_vm: 5/5パス ✅ - mir_stageb_loop_break_continue: 2/2パス ✅ - mir_stage1_using_resolver: 1/3パス (残り2つは dominator violation) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-18 06:11:17 +09:00
// Use next_value_id() which respects function context
let var_id = self.next_value_id();
if std::env::var("NYASH_LOOPFORM_DEBUG").is_ok() {
eprintln!("[build_local_statement] '{}': init_val={:?}, allocated var_id={:?}", var_name, init_val, var_id);
}
self.emit_instruction(crate::mir::MirInstruction::Copy {
dst: var_id,
src: init_val
})?;
// Propagate metadata (type/origin) from initializer to variable
crate::mir::builder::metadata::propagate::propagate(self, init_val, var_id);
var_id
} else {
// Create a concrete register for uninitialized locals (Void)
fix(mir): LoopForm v2完全緑化 - ValueId(0)予約 & unreachable block許容 ## 🎯 完了タスク ✅ Task 1: LoopForm v2 最小ユニットテスト全緑化(4/4パス) ✅ Task 2: program_v0 PHI trace スクリプト全緑化(5/5パス) ✅ Task 3: Stage-B 風ループ Rust テスト全緑化(2/2パス) 🔧 Task 4: Stage-1 using resolver (1/3パス、UsingStatement対応完了) ## 📝 主要修正 ### 1. ValueId(0)を無効値として予約 - **src/mir/function.rs**: MirFunction::new() で next_value_id を1から開始 - **src/mir/builder/stmts.rs**: build_local_statement で next_value_id() 使用 - **理由**: LoopForm v2 が ValueId(0) を無効値の sentinel として使用 - **効果**: SSA 構築時の ValueId 衝突を完全に防止 ### 2. Unreachable block 許容をデフォルト化 - **src/mir/verification/cfg.rs**: 到達可能性チェック削除 - **src/config/env.rs**: NYASH_VERIFY_ALLOW_UNREACHABLE 環境変数削除 - **src/tests/mir_loopform_exit_phi.rs**: 環境変数設定削除 - **理由**: break/continue/return の後の unreachable block は正当 - switch_to_unreachable_block_with_void() で意図的に作成 - LLVM IR の `unreachable` 命令と同じ標準的手法 - 削除は DCE (Dead Code Elimination) パスの仕事 - **効果**: 環境変数を減らしてシンプル化 ### 3. UsingStatement の MIR Builder 対応 - **src/mir/builder/exprs.rs**: UsingStatement → void 変換を追加 - **理由**: namespace 解決は parser/runner レベルで完了済み - **効果**: using 文を含むコードが MIR コンパイル可能に ### 4. スモークテストスクリプト修正 - **tools/smokes/v2/profiles/quick/core/phase2034/*.sh**: 5ファイル - **修正内容**: 二重コマンド置換のシンタックスエラー修正 - 誤: `out="$(out="$(COMMAND)"; rc=$?` - 正: `out="$(COMMAND)"; rc=$?` ## 🧪 テスト結果 - mir_loopform_exit_phi: 4/4パス ✅ - program_v0_*_phi_trace_vm: 5/5パス ✅ - mir_stageb_loop_break_continue: 2/2パス ✅ - mir_stage1_using_resolver: 1/3パス (残り2つは dominator violation) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-18 06:11:17 +09:00
let void_id = crate::mir::builder::emission::constant::emit_void(self);
if std::env::var("NYASH_LOOPFORM_DEBUG").is_ok() {
eprintln!("[build_local_statement] '{}': uninitialized, void_id={:?}", var_name, void_id);
}
void_id
};
fix(mir): LoopForm v2完全緑化 - ValueId(0)予約 & unreachable block許容 ## 🎯 完了タスク ✅ Task 1: LoopForm v2 最小ユニットテスト全緑化(4/4パス) ✅ Task 2: program_v0 PHI trace スクリプト全緑化(5/5パス) ✅ Task 3: Stage-B 風ループ Rust テスト全緑化(2/2パス) 🔧 Task 4: Stage-1 using resolver (1/3パス、UsingStatement対応完了) ## 📝 主要修正 ### 1. ValueId(0)を無効値として予約 - **src/mir/function.rs**: MirFunction::new() で next_value_id を1から開始 - **src/mir/builder/stmts.rs**: build_local_statement で next_value_id() 使用 - **理由**: LoopForm v2 が ValueId(0) を無効値の sentinel として使用 - **効果**: SSA 構築時の ValueId 衝突を完全に防止 ### 2. Unreachable block 許容をデフォルト化 - **src/mir/verification/cfg.rs**: 到達可能性チェック削除 - **src/config/env.rs**: NYASH_VERIFY_ALLOW_UNREACHABLE 環境変数削除 - **src/tests/mir_loopform_exit_phi.rs**: 環境変数設定削除 - **理由**: break/continue/return の後の unreachable block は正当 - switch_to_unreachable_block_with_void() で意図的に作成 - LLVM IR の `unreachable` 命令と同じ標準的手法 - 削除は DCE (Dead Code Elimination) パスの仕事 - **効果**: 環境変数を減らしてシンプル化 ### 3. UsingStatement の MIR Builder 対応 - **src/mir/builder/exprs.rs**: UsingStatement → void 変換を追加 - **理由**: namespace 解決は parser/runner レベルで完了済み - **効果**: using 文を含むコードが MIR コンパイル可能に ### 4. スモークテストスクリプト修正 - **tools/smokes/v2/profiles/quick/core/phase2034/*.sh**: 5ファイル - **修正内容**: 二重コマンド置換のシンタックスエラー修正 - 誤: `out="$(out="$(COMMAND)"; rc=$?` - 正: `out="$(COMMAND)"; rc=$?` ## 🧪 テスト結果 - mir_loopform_exit_phi: 4/4パス ✅ - program_v0_*_phi_trace_vm: 5/5パス ✅ - mir_stageb_loop_break_continue: 2/2パス ✅ - mir_stage1_using_resolver: 1/3パス (残り2つは dominator violation) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-18 06:11:17 +09:00
if std::env::var("NYASH_LOOPFORM_DEBUG").is_ok() {
eprintln!("[build_local_statement] Inserting '{}' -> {:?} into variable_map", var_name, var_id);
}
self.variable_map.insert(var_name.clone(), var_id);
last_value = Some(var_id);
}
Ok(last_value.unwrap_or_else(|| self.value_gen.next()))
}
// Return statement
pub(super) fn build_return_statement(
&mut self,
value: Option<Box<ASTNode>>,
) -> Result<ValueId, String> {
// Enforce cleanup policy
if self.in_cleanup_block && !self.cleanup_allow_return {
return Err("return is not allowed inside cleanup block (enable NYASH_CLEANUP_ALLOW_RETURN=1 to permit)".to_string());
}
let return_value = if let Some(expr) = value {
self.build_expression(*expr)?
} else {
crate::mir::builder::emission::constant::emit_void(self)
};
if self.return_defer_active {
// Defer: copy into slot and jump to target
if let (Some(slot), Some(target)) = (self.return_defer_slot, self.return_defer_target) {
self.return_deferred_emitted = true;
self.emit_instruction(MirInstruction::Copy { dst: slot, src: return_value })?;
crate::mir::builder::metadata::propagate::propagate(self, return_value, slot);
if !self.is_current_block_terminated() {
crate::mir::builder::emission::branch::emit_jump(self, target)?;
}
Ok(return_value)
} else {
// Fallback: no configured slot/target; emit a real return
self.emit_instruction(MirInstruction::Return { value: Some(return_value) })?;
Ok(return_value)
}
} else {
// Normal return
self.emit_instruction(MirInstruction::Return { value: Some(return_value) })?;
Ok(return_value)
}
}
// Nowait: prefer env.future.spawn_instance if method call; else FutureNew
pub(super) fn build_nowait_statement(
&mut self,
variable: String,
expression: ASTNode,
) -> Result<ValueId, String> {
if let ASTNode::MethodCall {
object,
method,
arguments,
..
} = expression.clone()
{
let recv_val = self.build_expression(*object)?;
let mname_id = crate::mir::builder::emission::constant::emit_string(self, method.clone());
let mut arg_vals: Vec<ValueId> = Vec::with_capacity(2 + arguments.len());
arg_vals.push(recv_val);
arg_vals.push(mname_id);
for a in arguments.into_iter() {
arg_vals.push(self.build_expression(a)?);
}
let future_id = self.next_value_id();
self.emit_instruction(MirInstruction::ExternCall {
dst: Some(future_id),
iface_name: "env.future".to_string(),
method_name: "spawn_instance".to_string(),
args: arg_vals,
effects: crate::mir::effect::EffectMask::PURE.add(crate::mir::effect::Effect::Io),
})?;
self.variable_map.insert(variable.clone(), future_id);
return Ok(future_id);
}
let expression_value = self.build_expression(expression)?;
let future_id = self.next_value_id();
self.emit_instruction(MirInstruction::FutureNew {
dst: future_id,
value: expression_value,
})?;
self.variable_map.insert(variable.clone(), future_id);
Ok(future_id)
}
// Await: insert Safepoint before/after and emit Await
pub(super) fn build_await_expression(
&mut self,
expression: ASTNode,
) -> Result<ValueId, String> {
let future_value = self.build_expression(expression)?;
self.emit_instruction(MirInstruction::Safepoint)?;
let result_id = self.next_value_id();
self.emit_instruction(MirInstruction::Await {
dst: result_id,
future: future_value,
})?;
self.emit_instruction(MirInstruction::Safepoint)?;
Ok(result_id)
}
// me: resolve to param if present; else symbolic const (stable mapping)
pub(super) fn build_me_expression(&mut self) -> Result<ValueId, String> {
if let Some(id) = self.variable_map.get("me").cloned() {
return Ok(id);
}
let me_tag = if let Some(ref cls) = self.current_static_box {
cls.clone()
} else {
"__me__".to_string()
};
let me_value = crate::mir::builder::emission::constant::emit_string(self, me_tag);
self.variable_map.insert("me".to_string(), me_value);
// P0: Known 化 — 分かる範囲で me の起源クラスを付与(挙動不変)。
super::origin::infer::annotate_me_origin(self, me_value);
Ok(me_value)
}
}