diff --git a/CURRENT_TASK.md b/CURRENT_TASK.md index d7b1d185..22b975f7 100644 --- a/CURRENT_TASK.md +++ b/CURRENT_TASK.md @@ -44,6 +44,34 @@ Next Steps(Sealed SSA 段階導入) 3) 足りない型整合(String/Box/Array→i8*)があれば `coerce_to_type` を拡張。 4) グリーン後、Sealed をデフォルトONにする前にスモーク一式で回帰確認。 +TODO — Sealed SSA 段階導入(実装タスク) +- [ ] block_end_values 追加(LLVM Lower 内の per-BB 終端スナップショット) + - 追加先: `src/backend/llvm/compiler/codegen/mod.rs` + - 形式: `HashMap>` + - タイミング: 各BBの命令をすべて Lower した「直後」、終端命令を発行する「直前」に `vmap.clone()` を保存 + - 目的: `seal_block` で pred 終端時点の値を安定取得する(現在の vmap 直接参照をやめる) +- [ ] `seal_block` をスナップショット参照に切替 + - 対象: `src/backend/llvm/compiler/codegen/instructions/flow.rs::seal_block` + - 取得: `block_end_values[bid].get(in_vid)` を用いて `val` を取得 + - フォールバック: もしスナップショットが無ければ(例外ケース)従来の `vmap` を参照し、警告ログを出す + - ログ: `NYASH_LLVM_TRACE_PHI=1` 時に `[PHI] sealed add pred_bb=.. val=.. ty=.. (snapshot)` と明示 +- [ ] 非 sealed 経路の維持(回帰防止) + - `emit_jump/emit_branch` は sealed=OFF の時のみ incoming を追加(現状仕様を維持) + - sealed=ON の時は incoming 配線は一切行わず、`seal_block` のみで完結 +- [ ] 型整合(coerce)の継続強化 + - 対象: `src/backend/llvm/compiler/codegen/instructions/flow.rs::coerce_to_type` + - 方針: PHI の型は i8* 優先(String/Box/Array を含む場合)。ptr/int 混在は明示 cast で橋渡し + - 検討: i1 ブリッジ(bool)の zext/trunc の置き場所は PHI 外側に寄せる(必要時) +- [ ] 代表スモークの回帰 + - 再現対象: `apps/selfhost/tools/dep_tree_min_string.nyash` + - 実行: `NYASH_LLVM_PHI_SEALED=1 NYASH_LLVM_TRACE_PHI=1 NYASH_DISABLE_PLUGINS=1 ./target/release/nyash --backend llvm apps/selfhost/tools/dep_tree_min_string.nyash` + - 期待: `PHINode should have one entry for each predecessor` が解消し、OFF/ON で等価な結果 + +補足(実装メモ) +- `block_end_values` の寿命はコード生成のライフタイムに束縛されるため、`BasicValueEnum<'ctx>` の所有は問題なし(`Context` が生きている間は有効) +- 収集は `compile_function` の BB ループ内で行い、`phis_by_block` と同スコープで管理すると取り回しが良い +- 将来の拡張として `value_at_end_of_block(var, bb)` ヘルパを導入し、sealed/unsealed を内部で吸収する API 化を検討 + Plan — PHI/SSA Hardening (Sealed SSA) - Sealed SSA 入れ替え(安全に段階導入) - Blockごとに `sealed: bool` と `incomplete_phis: Map` を保持 diff --git a/src/backend/llvm/compiler/codegen/instructions/arith.rs b/src/backend/llvm/compiler/codegen/instructions/arith.rs index 01cf09e7..f4e0f15e 100644 --- a/src/backend/llvm/compiler/codegen/instructions/arith.rs +++ b/src/backend/llvm/compiler/codegen/instructions/arith.rs @@ -89,7 +89,23 @@ pub(in super::super) fn lower_compare<'ctx>( return Ok(b.into()); } } - let out = if let (Some(li), Some(ri)) = (as_int(lv), as_int(rv)) { + let out = if let (Some(mut li), Some(mut ri)) = (as_int(lv), as_int(rv)) { + // Normalize integer widths: extend the narrower to match the wider to satisfy LLVM + let lw = li.get_type().get_bit_width(); + let rw = ri.get_type().get_bit_width(); + if lw != rw { + if lw < rw { + li = codegen + .builder + .build_int_z_extend(li, ri.get_type(), "icmp_zext_l") + .map_err(|e| e.to_string())?; + } else { + ri = codegen + .builder + .build_int_z_extend(ri, li.get_type(), "icmp_zext_r") + .map_err(|e| e.to_string())?; + } + } use CompareOp as C; let pred = match op { C::Eq => inkwell::IntPredicate::EQ, diff --git a/src/backend/llvm/compiler/codegen/instructions/flow.rs b/src/backend/llvm/compiler/codegen/instructions/flow.rs index eaa8e158..0e5ed75d 100644 --- a/src/backend/llvm/compiler/codegen/instructions/flow.rs +++ b/src/backend/llvm/compiler/codegen/instructions/flow.rs @@ -87,6 +87,9 @@ pub(in super::super) fn emit_jump<'ctx>( } } let tbb = *bb_map.get(target).ok_or("target bb missing")?; + if std::env::var("NYASH_CLI_VERBOSE").ok().as_deref() == Some("1") { + eprintln!("[LLVM] emit_jump: {} -> {}", bid.as_u32(), target.as_u32()); + } codegen .builder .build_unconditional_branch(tbb) @@ -178,6 +181,9 @@ pub(in super::super) fn emit_branch<'ctx>( } let tbb = *bb_map.get(then_bb).ok_or("then bb missing")?; let ebb = *bb_map.get(else_bb).ok_or("else bb missing")?; + if std::env::var("NYASH_CLI_VERBOSE").ok().as_deref() == Some("1") { + eprintln!("[LLVM] emit_branch: {} -> then {} / else {}", bid.as_u32(), then_bb.as_u32(), else_bb.as_u32()); + } codegen .builder .build_conditional_branch(b, tbb, ebb) @@ -252,6 +258,9 @@ pub(in super::super) fn seal_block<'ctx>( BasicBlockId, Vec<(ValueId, PhiValue<'ctx>, Vec<(BasicBlockId, ValueId)>)>, >, + // Snapshot of value map at end of each predecessor block + block_end_values: &HashMap>>, + // Fallback: current vmap (used only if snapshot missing) vmap: &HashMap>, ) -> Result<(), String> { if let Some(slist) = succs.get(&bid) { @@ -259,7 +268,24 @@ pub(in super::super) fn seal_block<'ctx>( if let Some(pl) = phis_by_block.get(sb) { for (_dst, phi, inputs) in pl { if let Some((_, in_vid)) = inputs.iter().find(|(pred, _)| pred == &bid) { - let mut val = *vmap.get(in_vid).ok_or("phi incoming (seal) value missing")?; + // Prefer the predecessor's block-end snapshot; fall back to current vmap + let snap_opt = block_end_values + .get(&bid) + .and_then(|m| m.get(in_vid).copied()); + let mut val = if let Some(sv) = snap_opt { + sv + } else { + match vmap.get(in_vid).copied() { + Some(v) => v, + None => { + let msg = format!( + "phi incoming (seal) missing: pred={} succ_bb={} in_vid={} (no snapshot)", + bid.as_u32(), sb.as_u32(), in_vid.as_u32() + ); + return Err(msg); + } + } + }; let pred_bb = *bb_map.get(&bid).ok_or("pred bb missing")?; val = coerce_to_type(codegen, phi, val)?; if std::env::var("NYASH_CLI_VERBOSE").ok().as_deref() == Some("1") { @@ -269,10 +295,11 @@ pub(in super::super) fn seal_block<'ctx>( .print_to_string() .to_string(); eprintln!( - "[PHI] sealed add pred_bb={} val={} ty={}", + "[PHI] sealed add pred_bb={} val={} ty={}{}", bid.as_u32(), in_vid.as_u32(), - tys + tys, + if snap_opt.is_some() { " (snapshot)" } else { " (vmap)" } ); } match val { diff --git a/src/backend/llvm/compiler/codegen/mod.rs b/src/backend/llvm/compiler/codegen/mod.rs index 04482661..df70e63c 100644 --- a/src/backend/llvm/compiler/codegen/mod.rs +++ b/src/backend/llvm/compiler/codegen/mod.rs @@ -122,6 +122,8 @@ impl LLVMCompiler { crate::mir::BasicBlockId, Vec<(ValueId, PhiValue, Vec<(crate::mir::BasicBlockId, ValueId)>)>, > = HashMap::new(); + // Snapshot of values at the end of each basic block (for sealed-SSA PHI wiring) + let mut block_end_values: HashMap> = HashMap::new(); // Build successors map (for optional sealed-SSA PHI wiring) let mut succs: HashMap> = HashMap::new(); for (bid, block) in &func.blocks { @@ -204,6 +206,9 @@ impl LLVMCompiler { { codegen.builder.position_at_end(bb); } + if std::env::var("NYASH_CLI_VERBOSE").ok().as_deref() == Some("1") { + eprintln!("[LLVM] lowering bb={}", bid.as_u32()); + } let block = func.blocks.get(bid).unwrap(); for inst in &block.instructions { match inst { @@ -320,9 +325,16 @@ impl LLVMCompiler { } _ => { /* ignore other ops for 11.1 */ }, } + // Capture a snapshot of the value map at the end of this block's body + block_end_values.insert(*bid, vmap.clone()); } // Emit terminators and provide a conservative fallback when absent if let Some(term) = &block.terminator { + if std::env::var("NYASH_CLI_VERBOSE").ok().as_deref() == Some("1") { + eprintln!("[LLVM] terminator present for bb={}", bid.as_u32()); + } + // Ensure builder is positioned at current block before emitting terminator + codegen.builder.position_at_end(bb); match term { MirInstruction::Return { value } => { instructions::emit_return(&codegen, func, &vmap, value)?; @@ -333,9 +345,30 @@ impl LLVMCompiler { MirInstruction::Branch { condition, then_bb, else_bb } => { instructions::emit_branch(&codegen, *bid, condition, then_bb, else_bb, &bb_map, &phis_by_block, &vmap)?; } - _ => {} + _ => { + // Ensure builder is at this block before fallback branch + codegen.builder.position_at_end(bb); + // Unknown/unhandled terminator: conservatively branch forward + if let Some(next_bid) = block_ids.get(bi + 1) { + if std::env::var("NYASH_CLI_VERBOSE").ok().as_deref() == Some("1") { + eprintln!("[LLVM] unknown terminator fallback: bb={} -> next={}", bid.as_u32(), next_bid.as_u32()); + } + instructions::emit_jump(&codegen, *bid, next_bid, &bb_map, &phis_by_block, &vmap)?; + } else { + let entry_first = func.entry_block; + if std::env::var("NYASH_CLI_VERBOSE").ok().as_deref() == Some("1") { + eprintln!("[LLVM] unknown terminator fallback: bb={} -> entry={}", bid.as_u32(), entry_first.as_u32()); + } + instructions::emit_jump(&codegen, *bid, &entry_first, &bb_map, &phis_by_block, &vmap)?; + } + } } } else { + if std::env::var("NYASH_CLI_VERBOSE").ok().as_deref() == Some("1") { + eprintln!("[LLVM] no terminator in MIR for bb={} (fallback)", bid.as_u32()); + } + // Ensure builder is at this block before fallback branch + codegen.builder.position_at_end(bb); // Fallback: branch to the next block if any; otherwise loop to entry if let Some(next_bid) = block_ids.get(bi + 1) { instructions::emit_jump(&codegen, *bid, next_bid, &bb_map, &phis_by_block, &vmap)?; @@ -348,15 +381,26 @@ impl LLVMCompiler { // Extra guard: if the current LLVM basic block still lacks a terminator for any reason, // insert a conservative branch to the next block (or entry if last) to satisfy verifier. if unsafe { bb.get_terminator() }.is_none() { + if std::env::var("NYASH_CLI_VERBOSE").ok().as_deref() == Some("1") { + eprintln!("[LLVM] extra guard inserting fallback for bb={}", bid.as_u32()); + } + // Ensure the builder is positioned at the end of this block before inserting the fallback terminator + codegen.builder.position_at_end(bb); if let Some(next_bid) = block_ids.get(bi + 1) { + if std::env::var("NYASH_CLI_VERBOSE").ok().as_deref() == Some("1") { + eprintln!("[LLVM] fallback terminator: bb={} -> next={}", bid.as_u32(), next_bid.as_u32()); + } instructions::emit_jump(&codegen, *bid, next_bid, &bb_map, &phis_by_block, &vmap)?; } else { let entry_first = func.entry_block; + if std::env::var("NYASH_CLI_VERBOSE").ok().as_deref() == Some("1") { + eprintln!("[LLVM] fallback terminator: bb={} -> entry={}", bid.as_u32(), entry_first.as_u32()); + } instructions::emit_jump(&codegen, *bid, &entry_first, &bb_map, &phis_by_block, &vmap)?; } } if sealed_mode { - instructions::flow::seal_block(&codegen, *bid, &succs, &bb_map, &phis_by_block, &vmap)?; + instructions::flow::seal_block(&codegen, *bid, &succs, &bb_map, &phis_by_block, &block_end_values, &vmap)?; } } // Verify the fully-lowered function once, after all blocks