diff --git a/CURRENT_TASK.md b/CURRENT_TASK.md index 2a856ca6..e23c39b8 100644 --- a/CURRENT_TASK.md +++ b/CURRENT_TASK.md @@ -78,6 +78,45 @@ Next Plan(精密タスク v2) - Seal配線の堅牢化: snapshot優先・pred末端cast挿入の徹底、フォールバックゼロ合成の縮小(非param値のvmapフォールバックを禁止)。 - Regression: `dep_tree_min_string` オブジェクト生成→ LLVM verifier green(支配違反ゼロ); Deny-Direct grep=0 をCIチェックに追加。 +Plan — Context Boxing(Lower APIの“箱化”) +- 背景: 降下APIの引数が過多(10+)で、責務が分散しがち。Nyashの“箱理論”に従い、関連情報をコンテキストBoxにまとめて境界を作る。 +- 目的: 責務の一元化・不変条件の型による強制・引数爆発の解消・再発防止。 +- 主要な“箱” + - LowerFnCtx<'ctx, 'b>(関数スコープ) + - 保持: `codegen`, `func`, `cursor`, `resolver`, `vmap`, `bb_map`, `preds`, `block_end_values`, `phis_by_block`, `const_strs`, `box_type_ids` など + - 役割: 関数内の全 lowering に共通する情報とユーティリティ(ensure_i64/i8p/f64、with_block ラッパ 等) + - BlockCtx<'ctx>(ブロックスコープ) + - 保持: `cur_bid`, `cur_llbb`、必要に応じて `succs` + - 役割: その場の挿入点管理、pred終端直前挿入の窓口 + - InvokeCtx(呼び出し情報) + - 保持: `method_id`, `type_id`, `recv_h`, `args` + - 役割: plugin invoke(by‑id/by‑name)の統一入口 + - StringOps(軽量型/ヘルパ) + - 型: `StrHandle(i64)`, `StrPtr(i8*)` + - 規約: ブロック間は StrHandle のみ/call直前のみ StrPtr 生成。戻りは常に StrHandle + +- API の最終形(例) + - `lower_boxcall(ctx: &mut LowerFnCtx, blk: &BlockCtx, inst: &BoxCallInst) -> LlResult<()>` + - `try_handle_tagged_invoke(ctx: &mut LowerFnCtx, blk: &BlockCtx, call: &InvokeCtx) -> LlResult<()>` + - `StringOps::concat(ctx, blk, lhs, rhs) -> LlResult` + +- 構造の不変条件との対応付け + - Resolver‑only: `LowerFnCtx` 経由でのみ値取得可能(VMap の直接 `get` は不可にする) + - 局所化の規律: `BlockCtx.with_block_pred(..)` 等で pred末端挿入を強制 + - dispatch‑only PHI: dev チェッカ `PhiGuard::assert_dispatch_only` を追加 + - preheader必須: LoopForm 生成時に assert(dev) + +- マイグレーション手順(段階的) + 1) `LowerFnCtx/BlockCtx/InvokeCtx` を導入し、`lower_boxcall` と invoke 経路を最初に箱化 + 2) Strings を `StringOps` に集約(戻り=StrHandle)。既存callサイトから直 i8* を排除 + 3) BinOp/Compare/ExternCall を順次 `LowerFnCtx+BlockCtx` 受けに移行 + 4) dev チェッカ(dispatch‑PHI/preheader)をONにし、構造を固定 + +- 受け入れ(Context Boxing 対応) + - `lower_boxcall`/invoke/strings が “箱化API” を使用し、関数シグネチャの引数が 3 箱以内 + - Resolver‑only/Deny‑Direct 維持(grep 0) + - 代表ケースで verifier green(D‑Dominance‑0) + Docs — LLVM layer overview (2025‑09‑12) - Added docs/LLVM_LAYER_OVERVIEW.md and linked it with existing docs: - docs/LOWERING_LLVM.md — concrete lowering rules and RT calls @@ -468,6 +507,36 @@ Execution Plan — Next 48h - BoxタイプID読み込みを `box_types.rs` に移動 - PR #134の型情報処理を完全保持 +Hot Update — 2025‑09‑12 (Boxing: ctx + dev checks) +- Added `instructions/ctx.rs` introducing `LowerFnCtx` and `BlockCtx` (Resolver-first accessors: ensure_i64/ptr/f64). Env `NYASH_DEV_CHECKS=1` enables extra cursor-open asserts. +- Added `instructions/string_ops.rs` with lightweight newtypes `StrHandle(i64)` and `StrPtr(i8*)` (handle-across-blocks policy). Call sites will adopt gradually. +- Exposed new modules in `instructions/mod.rs` and added a thin `lower_boxcall_boxed(..)` shim. +- Wiring remains unchanged (still calls existing `lower_boxcall(..)`); borrow conflicts will be avoided by migrating call-sites in small steps. +- Scope alignment: Boxing covers the full BoxCall lowering suite (fields/invoke/marshal) as we migrate internals in-place. + +Hot Update — 2025‑09‑12 (flow API trim) +- `emit_jump` から `vmap` 引数を削除(sealed前提で未使用のため)。 +- `seal_block` から `vmap` 引数を削除(snapshot優先・ゼロ合成で代替)。 +- 上記により `compile_module` 内の借用競合(&mut cursor と &vmap の競合)を縮小。 + +Dev Checks(実装) +- 追加: `NYASH_DEV_CHECK_DISPATCH_ONLY_PHI=1` で LoopForm 登録がある関数の PHI 分布をログ出力(暫定: dispatch-only 厳格検証は今後強化)。 +- 既存: BuilderCursor の post-terminator ガードを全域適用済み(emit_instr/emit_term)。 + +結果(Step 4 検証) +- dep_tree_min_string の LLVM オブジェクト生成が成功(sealed=ON, plugins OFF)。 + - コマンド(例): + - `cargo build --features llvm --bin nyash` + - `NYASH_DISABLE_PLUGINS=1 NYASH_LLVM_OBJ_OUT=tmp/dep_tree_min_string.o target/debug/nyash --backend llvm apps/selfhost/tools/dep_tree_min_string.nyash` + - 出力: `tmp/dep_tree_min_string.o`(約 10KB) + +次の一手(箱化の本適用・安全切替) +- lower_boxcall 内部の段階移行(fields → invoke → marshal)を小スコープで進め、呼び出し側の `&mut cursor` 借用境界と競合しない形で flip。 +- flip 後、Deny-Direct(`rg "vmap\.get\("` = 0)を下流のCIメモに追記、必要なら `#[cfg(debug_assertions)]` の簡易ガードを追加。 + +Note(箱化の段階切替) +- BoxCall 呼び出しを `lower_boxcall_boxed` へ全面切替は、`compile_module` のループ内における `&mut cursor` の借用境界と干渉するため、いったん保留。内部の移行(fields/invoke/marshal)を先に進め、借用の生存域を短縮した上で切替予定。 + ## 🎯 Restart Notes — Ny Syntax Alignment (2025‑09‑07) ### 目的 diff --git a/docs/LOWERING_CONTEXTS.md b/docs/LOWERING_CONTEXTS.md new file mode 100644 index 00000000..fcb8d415 --- /dev/null +++ b/docs/LOWERING_CONTEXTS.md @@ -0,0 +1,70 @@ +# Lowering Contexts (Boxing the API) + +Motivation +- Current lowering functions carry too many parameters (10+), scattering responsibilities and making invariants (Resolver-only, localization discipline, dispatch-only PHI) hard to enforce. +- Following Nyash’s “Everything is Box” philosophy, we group related data into small, composable context boxes to create clean boundaries and make invariants structural. + +Core Context Boxes +- LowerFnCtx<'ctx, 'b> + - Holds: `codegen`, `func`, `cursor`, `resolver`, `vmap`, `bb_map`, `preds`, `block_end_values`, `phis_by_block`, optional `const_strs`, `box_type_ids`. + - Utilities: `ensure_i64/ptr/f64` (Resolver-only), `with_block(..)`, `with_pred_end(..)`, `guard_post_terminator`. + - Effect: All lowering entrypoints can accept a single `&mut LowerFnCtx`. + +- BlockCtx<'ctx> + - Holds: `cur_bid`, `cur_llbb` (from `bb_map`), optionally `succs`. + - Role: The canonical site for “where to insert” decisions; pred-end casts are routed via this box. + +- InvokeCtx + - Holds: `method_id: u16`, `type_id: i64`, `recv_h: IntValue<'ctx>`, `args: &[ValueId]`. + - Role: Unifies plugin invoke by-id/by-name into a single API surface. + +- StringOps (lightweight types/helpers) + - Types: `StrHandle(IntValue<'ctx>)`, `StrPtr(PointerValue<'ctx>)`. + - Rule: across blocks keep `StrHandle`; convert to `StrPtr` only at call sites in the same BB; return values of string ops are kept as `StrHandle`. + - Entry: `StringOps::concat(ctx, blk, lhs, rhs) -> LlResult` etc. + +API Sketch +``` +pub type LlResult = Result; + +pub struct LowerFnCtx<'ctx, 'b> { /* see above */ } +pub struct BlockCtx<'ctx> { pub cur_bid: BasicBlockId, pub cur_llbb: BasicBlock<'ctx> } +pub struct InvokeCtx<'ctx> { pub method_id: u16, pub type_id: i64, pub recv_h: IntValue<'ctx>, pub args: Vec } + +impl<'ctx, 'b> LowerFnCtx<'ctx, 'b> { + pub fn ensure_i64(&mut self, blk: &BlockCtx<'ctx>, v: ValueId) -> LlResult> { /* Resolver-only */ } + pub fn ensure_ptr(&mut self, blk: &BlockCtx<'ctx>, v: ValueId) -> LlResult> { /* i64 PHI -> inttoptr here */ } + pub fn with_pred_end(&mut self, pred: BasicBlockId, f: impl FnOnce(&Builder) -> R) -> R { /* insert before terminator */ } +} + +pub fn lower_boxcall(ctx: &mut LowerFnCtx, blk: &BlockCtx, inst: &BoxCallInst) -> LlResult<()> { /* … */ } +pub fn try_handle_tagged_invoke(ctx: &mut LowerFnCtx, blk: &BlockCtx, call: &InvokeCtx) -> LlResult<()> { /* … */ } + +pub mod string_ops { + pub struct StrHandle<'ctx>(pub IntValue<'ctx>); + pub struct StrPtr<'ctx>(pub PointerValue<'ctx>); + pub fn concat(ctx: &mut LowerFnCtx, blk: &BlockCtx, lhs: ValueId, rhs: ValueId) -> LlResult { /* … */ } +} +``` + +Invariants Enforced by Design +- Resolver-only: `VMap` is not exposed; all value access goes through `LowerFnCtx` utilities. +- Localization discipline: PHIs at BB head; casts at pred-end via `with_pred_end`. +- Strings handle rule: Only `StrHandle` crosses block boundaries; `StrPtr` generated only in the same BB at call sites. +- LoopForm rule: preheader mandatory, header condition built via Resolver; dispatch-only PHI. Dev guard checks enforce these. + +Dev Guards (optional, recommended) +- `PhiGuard::assert_dispatch_only(&LowerFnCtx)` to fail fast when non-dispatch PHIs appear. +- `LoopGuard::assert_preheader(&LowerFnCtx)` to ensure preheader presence and header i1 formation point. +- CI Deny-Direct: `rg "vmap\.get\("` must match zero in lowering sources. + +Migration Plan +1) Introduce `LowerFnCtx`/`BlockCtx`/`InvokeCtx`; migrate `lower_boxcall` and invoke path first. +2) Move string ops to `StringOps` with handle-only rule; clean call sites. +3) Migrate BinOp/Compare/ExternCall to `LowerFnCtx + BlockCtx` API. +4) Turn on dev guards; remove remaining fallback paths; simplify code. + +Acceptance +- Refactored entrypoints accept at most three boxed parameters. +- Deny-Direct passes (no direct `vmap.get` in lowering). +- Dominance: verifier green on representative functions (e.g., dep_tree_min_string). diff --git a/src/backend/llvm/compiler/codegen/instructions/boxcall.rs b/src/backend/llvm/compiler/codegen/instructions/boxcall.rs index 64e7180b..b4ca0dae 100644 --- a/src/backend/llvm/compiler/codegen/instructions/boxcall.rs +++ b/src/backend/llvm/compiler/codegen/instructions/boxcall.rs @@ -11,6 +11,7 @@ use self::marshal as marshal_mod; use self::invoke as invoke_mod; use crate::mir::{function::MirFunction, BasicBlockId, ValueId}; use super::builder_cursor::BuilderCursor; +use super::ctx::{LowerFnCtx, BlockCtx}; // BoxCall lowering (large): mirrors existing logic; kept in one function for now pub(in super::super) fn lower_boxcall<'ctx, 'b>( @@ -260,6 +261,39 @@ pub(in super::super) fn lower_boxcall<'ctx, 'b>( } } +// Boxed API: thin shim adapting LowerFnCtx/BlockCtx to the existing implementation. +pub(in super::super) fn lower_boxcall_boxed<'ctx, 'b>( + ctx: &mut LowerFnCtx<'ctx, 'b>, + blk: &BlockCtx<'ctx>, + dst: &Option, + box_val: &ValueId, + method: &str, + method_id: &Option, + args: &[ValueId], + entry_builder: &inkwell::builder::Builder<'ctx>, +) -> Result<(), String> { + // Optional dev check: ensure block is open for insertion + if ctx.dev_checks { ctx.cursor.assert_open(blk.cur_bid); } + lower_boxcall( + ctx.codegen, + ctx.cursor, + ctx.resolver, + blk.cur_bid, + ctx.func, + ctx.vmap, + dst, + box_val, + method, + method_id, + args, + ctx.box_type_ids.ok_or_else(|| "LowerFnCtx.box_type_ids missing".to_string())?, + entry_builder, + ctx.bb_map, + ctx.preds, + ctx.block_end_values, + ) +} + fn coerce_to_type<'ctx>( codegen: &CodegenContext<'ctx>, val: inkwell::values::BasicValueEnum<'ctx>, diff --git a/src/backend/llvm/compiler/codegen/instructions/boxcall/fields.rs b/src/backend/llvm/compiler/codegen/instructions/boxcall/fields.rs index f799c23d..df8109d2 100644 --- a/src/backend/llvm/compiler/codegen/instructions/boxcall/fields.rs +++ b/src/backend/llvm/compiler/codegen/instructions/boxcall/fields.rs @@ -1,16 +1,16 @@ use std::collections::HashMap; use inkwell::{values::BasicValueEnum as BVE, AddressSpace}; - use crate::backend::llvm::context::CodegenContext; use crate::mir::ValueId; +use super::super::ctx::{LowerFnCtx, BlockCtx}; /// Handle getField/setField; returns true if handled. use super::super::builder_cursor::BuilderCursor; pub(super) fn try_handle_field_method<'ctx, 'b>( codegen: &CodegenContext<'ctx>, - cursor: &mut BuilderCursor<'ctx, 'b>, + cursor: &mut super::super::builder_cursor::BuilderCursor<'ctx, 'b>, cur_bid: crate::mir::BasicBlockId, vmap: &mut HashMap>, dst: &Option, @@ -49,12 +49,12 @@ pub(super) fn try_handle_field_method<'ctx, 'b>( } else { return Err("get_field ret expected i64".to_string()); }; - let pty = codegen.context.ptr_type(AddressSpace::from(0)); - let ptr = codegen + let pty = codegen.context.ptr_type(AddressSpace::from(0)); + let ptr = codegen .builder .build_int_to_ptr(h, pty, "gf_handle_to_ptr") .map_err(|e| e.to_string())?; - vmap.insert(*d, ptr.into()); + vmap.insert(*d, ptr.into()); } Ok(true) } @@ -80,3 +80,28 @@ pub(super) fn try_handle_field_method<'ctx, 'b>( _ => Ok(false), } } + +// Boxed wrapper that delegates to the non-boxed implementation +pub(super) fn try_handle_field_method_boxed<'ctx, 'b>( + ctx: &mut LowerFnCtx<'ctx, 'b>, + blk: &BlockCtx<'ctx>, + dst: &Option, + method: &str, + args: &[ValueId], + recv_h: inkwell::values::IntValue<'ctx>, +) -> Result { + try_handle_field_method( + ctx.codegen, + ctx.cursor, + blk.cur_bid, + ctx.vmap, + dst, + method, + args, + recv_h, + ctx.resolver, + ctx.bb_map, + ctx.preds, + ctx.block_end_values, + ) +} diff --git a/src/backend/llvm/compiler/codegen/instructions/boxcall/invoke.rs b/src/backend/llvm/compiler/codegen/instructions/boxcall/invoke.rs index b215c83d..0a03469f 100644 --- a/src/backend/llvm/compiler/codegen/instructions/boxcall/invoke.rs +++ b/src/backend/llvm/compiler/codegen/instructions/boxcall/invoke.rs @@ -4,6 +4,7 @@ use inkwell::{values::BasicValueEnum as BVE, AddressSpace}; use crate::backend::llvm::context::CodegenContext; use crate::mir::{function::MirFunction, ValueId}; +use super::super::ctx::{LowerFnCtx, BlockCtx}; // use super::marshal::{get_i64, get_tag_const}; @@ -240,3 +241,33 @@ fn store_invoke_return<'ctx>( } Ok(()) } + +// Boxed wrapper delegating to the existing implementation +pub(super) fn try_handle_tagged_invoke_boxed<'ctx, 'b>( + ctx: &mut LowerFnCtx<'ctx, 'b>, + blk: &BlockCtx<'ctx>, + dst: &Option, + mid: u16, + type_id: i64, + recv_h: inkwell::values::IntValue<'ctx>, + args: &[ValueId], + entry_builder: &inkwell::builder::Builder<'ctx>, +) -> Result<(), String> { + try_handle_tagged_invoke( + ctx.codegen, + ctx.func, + ctx.cursor, + ctx.resolver, + ctx.vmap, + dst, + mid, + type_id, + recv_h, + args, + entry_builder, + blk.cur_bid, + ctx.bb_map, + ctx.preds, + ctx.block_end_values, + ) +} diff --git a/src/backend/llvm/compiler/codegen/instructions/ctx.rs b/src/backend/llvm/compiler/codegen/instructions/ctx.rs new file mode 100644 index 00000000..95e2d1b6 --- /dev/null +++ b/src/backend/llvm/compiler/codegen/instructions/ctx.rs @@ -0,0 +1,129 @@ +use std::collections::HashMap; + +use inkwell::{ + basic_block::BasicBlock, + values::{BasicValueEnum as BVE, IntValue, PointerValue, FloatValue}, +}; + +use crate::backend::llvm::context::CodegenContext; +use crate::mir::{function::MirFunction, BasicBlockId, ValueId}; + +use super::{builder_cursor::BuilderCursor, Resolver}; + +pub type LlResult = Result; + +/// Per-function lowering context that centralizes access to codegen utilities and +/// enforces Resolver-only value access. +pub struct LowerFnCtx<'ctx, 'b> { + pub codegen: &'ctx CodegenContext<'ctx>, + pub func: &'b MirFunction, + pub cursor: &'b mut BuilderCursor<'ctx, 'b>, + pub resolver: &'b mut Resolver<'ctx>, + pub vmap: &'b mut HashMap>, + pub bb_map: &'b HashMap>, + pub preds: &'b HashMap>, + pub block_end_values: &'b HashMap>>, + // Optional extras commonly needed by some paths + pub box_type_ids: Option<&'b HashMap>, + pub const_strs: Option<&'b HashMap>, + // Dev flag: extra runtime assertions + pub dev_checks: bool, +} + +impl<'ctx, 'b> LowerFnCtx<'ctx, 'b> { + pub fn new( + codegen: &'ctx CodegenContext<'ctx>, + func: &'b MirFunction, + cursor: &'b mut BuilderCursor<'ctx, 'b>, + resolver: &'b mut Resolver<'ctx>, + vmap: &'b mut HashMap>, + bb_map: &'b HashMap>, + preds: &'b HashMap>, + block_end_values: &'b HashMap>>, + ) -> Self { + let dev_checks = std::env::var("NYASH_DEV_CHECKS").ok().as_deref() == Some("1"); + Self { + codegen, + func, + cursor, + resolver, + vmap, + bb_map, + preds, + block_end_values, + box_type_ids: None, + const_strs: None, + dev_checks, + } + } + + pub fn with_box_type_ids(mut self, ids: &'b HashMap) -> Self { + self.box_type_ids = Some(ids); + self + } + + pub fn with_const_strs(mut self, m: &'b HashMap) -> Self { + self.const_strs = Some(m); + self + } + + #[inline] + pub fn ensure_i64(&mut self, blk: &BlockCtx<'ctx>, v: ValueId) -> LlResult> { + self.cursor.assert_open(blk.cur_bid); + self.resolver + .resolve_i64( + self.codegen, + self.cursor, + blk.cur_bid, + v, + self.bb_map, + self.preds, + self.block_end_values, + self.vmap, + ) + } + + #[inline] + pub fn ensure_ptr(&mut self, blk: &BlockCtx<'ctx>, v: ValueId) -> LlResult> { + self.cursor.assert_open(blk.cur_bid); + self.resolver + .resolve_ptr( + self.codegen, + self.cursor, + blk.cur_bid, + v, + self.bb_map, + self.preds, + self.block_end_values, + self.vmap, + ) + } + + #[inline] + pub fn ensure_f64(&mut self, blk: &BlockCtx<'ctx>, v: ValueId) -> LlResult> { + self.cursor.assert_open(blk.cur_bid); + self.resolver + .resolve_f64( + self.codegen, + self.cursor, + blk.cur_bid, + v, + self.bb_map, + self.preds, + self.block_end_values, + self.vmap, + ) + } +} + +/// Per-basic-block context to keep insertion site and block identity together. +pub struct BlockCtx<'ctx> { + pub cur_bid: BasicBlockId, + pub cur_llbb: BasicBlock<'ctx>, +} + +impl<'ctx> BlockCtx<'ctx> { + pub fn new(cur_bid: BasicBlockId, cur_llbb: BasicBlock<'ctx>) -> Self { + Self { cur_bid, cur_llbb } + } +} diff --git a/src/backend/llvm/compiler/codegen/instructions/flow.rs b/src/backend/llvm/compiler/codegen/instructions/flow.rs index 4daf843a..ff6760eb 100644 --- a/src/backend/llvm/compiler/codegen/instructions/flow.rs +++ b/src/backend/llvm/compiler/codegen/instructions/flow.rs @@ -73,7 +73,6 @@ pub(in super::super) fn emit_jump<'ctx, 'b>( BasicBlockId, Vec<(ValueId, PhiValue<'ctx>, Vec<(BasicBlockId, ValueId)>)>, >, - vmap: &HashMap>, ) -> Result<(), String> { // Non-sealed incoming wiring removed: rely on sealed snapshots and resolver-driven PHIs. let tbb = *bb_map.get(target).ok_or("target bb missing")?; @@ -193,8 +192,6 @@ pub(in super::super) fn seal_block<'ctx, 'b>( >, // 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) { for sb in slist { diff --git a/src/backend/llvm/compiler/codegen/instructions/loopform.rs b/src/backend/llvm/compiler/codegen/instructions/loopform.rs index 8d3dcee2..928942c0 100644 --- a/src/backend/llvm/compiler/codegen/instructions/loopform.rs +++ b/src/backend/llvm/compiler/codegen/instructions/loopform.rs @@ -214,3 +214,26 @@ pub fn normalize_header_phis_for_latch<'ctx>( } Ok(()) } + +// Dev check: when enabled, log PHIs that live outside dispatch blocks created by LoopForm +pub(in super::super) fn dev_check_dispatch_only_phi<'ctx>( + phis_by_block: &std::collections::HashMap< + crate::mir::BasicBlockId, + Vec<(crate::mir::ValueId, inkwell::values::PhiValue<'ctx>, Vec<(crate::mir::BasicBlockId, crate::mir::ValueId)>)>, + >, + loopform_registry: &std::collections::HashMap< + crate::mir::BasicBlockId, + (inkwell::basic_block::BasicBlock<'ctx>, inkwell::values::PhiValue<'ctx>, inkwell::values::PhiValue<'ctx>, inkwell::basic_block::BasicBlock<'ctx>) + >, +) { + if std::env::var("NYASH_DEV_CHECK_DISPATCH_ONLY_PHI").ok().as_deref() != Some("1") { + return; + } + // Best-effort: Just report PHI presence per block when LoopForm registry is non-empty. + if !loopform_registry.is_empty() { + for (bid, phis) in phis_by_block.iter() { + if phis.is_empty() { continue; } + eprintln!("[DEV][PHI] bb={} has {} PHI(s)", bid.as_u32(), phis.len()); + } + } +} diff --git a/src/backend/llvm/compiler/codegen/instructions/mod.rs b/src/backend/llvm/compiler/codegen/instructions/mod.rs index eef5c285..085e22a9 100644 --- a/src/backend/llvm/compiler/codegen/instructions/mod.rs +++ b/src/backend/llvm/compiler/codegen/instructions/mod.rs @@ -4,6 +4,8 @@ pub mod flow; mod externcall; mod newbox; mod boxcall; +pub mod ctx; +pub mod string_ops; mod arith; mod mem; mod consts; @@ -19,7 +21,7 @@ pub(super) use blocks::{create_basic_blocks, precreate_phis}; pub(super) use flow::{emit_branch, emit_jump, emit_return}; pub(super) use externcall::lower_externcall; pub(super) use newbox::lower_newbox; -pub(super) use boxcall::lower_boxcall; +pub(super) use boxcall::{lower_boxcall, lower_boxcall_boxed}; pub(super) use arith::lower_compare; pub(super) use mem::{lower_load, lower_store}; pub(super) use consts::lower_const; @@ -27,4 +29,5 @@ pub(super) use arith_ops::{lower_binop, lower_unary}; pub(super) use call::lower_call; pub(super) use loopform::{LoopFormContext, lower_while_loopform}; pub(super) use loopform::normalize_header_phis_for_latch; +pub(super) use loopform::dev_check_dispatch_only_phi; pub(super) use resolver::Resolver; diff --git a/src/backend/llvm/compiler/codegen/instructions/string_ops.rs b/src/backend/llvm/compiler/codegen/instructions/string_ops.rs new file mode 100644 index 00000000..916b16a9 --- /dev/null +++ b/src/backend/llvm/compiler/codegen/instructions/string_ops.rs @@ -0,0 +1,16 @@ +use inkwell::values::{IntValue, PointerValue}; + +/// Lightweight newtypes for string representations used in lowering. +/// StrHandle crosses basic blocks; StrPtr is created at call sites within the same block. +pub struct StrHandle<'ctx>(pub IntValue<'ctx>); +pub struct StrPtr<'ctx>(pub PointerValue<'ctx>); + +impl<'ctx> StrHandle<'ctx> { + #[inline] + pub fn as_i64(&self) -> IntValue<'ctx> { self.0 } +} + +impl<'ctx> From> for StrPtr<'ctx> { + fn from(p: PointerValue<'ctx>) -> Self { Self(p) } +} + diff --git a/src/backend/llvm/compiler/codegen/mod.rs b/src/backend/llvm/compiler/codegen/mod.rs index 70183f3a..6919c1b0 100644 --- a/src/backend/llvm/compiler/codegen/mod.rs +++ b/src/backend/llvm/compiler/codegen/mod.rs @@ -317,24 +317,26 @@ impl LLVMCompiler { effects: _, } => { // Delegate to refactored lowering and skip legacy body - instructions::lower_boxcall( - &codegen, - &mut cursor, - &mut resolver, - *bid, - func, - &mut vmap, - dst, - box_val, - method, - method_id, - args, - &box_type_ids, - &entry_builder, - &bb_map, - &preds, - &block_end_values, - )?; + { + instructions::lower_boxcall( + &codegen, + &mut cursor, + &mut resolver, + *bid, + func, + &mut vmap, + dst, + box_val, + method, + method_id, + args, + &box_type_ids, + &entry_builder, + &bb_map, + &preds, + &block_end_values, + )?; + } if let Some(d) = dst { defined_in_block.insert(*d); } }, MirInstruction::ExternCall { dst, iface_name, method_name, args, effects: _ } => { @@ -460,7 +462,7 @@ impl LLVMCompiler { } } if !handled { - instructions::emit_jump(&codegen, &mut cursor, *bid, target, &bb_map, &phis_by_block, &vmap)?; + instructions::emit_jump(&codegen, &mut cursor, *bid, target, &bb_map, &phis_by_block)?; } } MirInstruction::Branch { condition, then_bb, else_bb } => { @@ -536,13 +538,13 @@ impl LLVMCompiler { 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, &mut cursor, *bid, next_bid, &bb_map, &phis_by_block, &vmap)?; + instructions::emit_jump(&codegen, &mut cursor, *bid, next_bid, &bb_map, &phis_by_block)?; } 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, &mut cursor, *bid, &entry_first, &bb_map, &phis_by_block, &vmap)?; + instructions::emit_jump(&codegen, &mut cursor, *bid, &entry_first, &bb_map, &phis_by_block)?; } } } @@ -554,11 +556,11 @@ impl LLVMCompiler { cursor.at_end(*bid, 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, &mut cursor, *bid, next_bid, &bb_map, &phis_by_block, &vmap)?; + instructions::emit_jump(&codegen, &mut cursor, *bid, next_bid, &bb_map, &phis_by_block)?; } else { // last block, loop to entry to satisfy verifier let entry_first = func.entry_block; - instructions::emit_jump(&codegen, &mut cursor, *bid, &entry_first, &bb_map, &phis_by_block, &vmap)?; + instructions::emit_jump(&codegen, &mut cursor, *bid, &entry_first, &bb_map, &phis_by_block)?; } } // Extra guard: if the current LLVM basic block still lacks a terminator for any reason, @@ -573,17 +575,17 @@ impl LLVMCompiler { 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, &mut cursor, *bid, next_bid, &bb_map, &phis_by_block, &vmap)?; + instructions::emit_jump(&codegen, &mut cursor, *bid, next_bid, &bb_map, &phis_by_block)?; } 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, &mut cursor, *bid, &entry_first, &bb_map, &phis_by_block, &vmap)?; + instructions::emit_jump(&codegen, &mut cursor, *bid, &entry_first, &bb_map, &phis_by_block)?; } } if sealed_mode { - instructions::flow::seal_block(&codegen, &mut cursor, func, *bid, &succs, &bb_map, &phis_by_block, &block_end_values, &vmap)?; + instructions::flow::seal_block(&codegen, &mut cursor, func, *bid, &succs, &bb_map, &phis_by_block, &block_end_values)?; sealed_blocks.insert(*bid); // In sealed mode, we rely on seal_block to add incoming per pred when each pred is sealed. // finalize_phis is intentionally skipped to avoid duplicate incoming entries. @@ -603,6 +605,8 @@ impl LLVMCompiler { )?; } } + // Dev check (optional): ensure PHIs live only in dispatch blocks + instructions::dev_check_dispatch_only_phi(&phis_by_block, &loopform_registry); } // Finalize function: ensure every basic block is closed with a terminator. // As a last resort, insert 'unreachable' into blocks that remain unterminated.