From a40ee8dda53a1966609a3797d0ccb2c9dbdd3f73 Mon Sep 17 00:00:00 2001 From: tomoaki Date: Thu, 25 Dec 2025 03:53:42 +0900 Subject: [PATCH] refactor(plan): Phase 286C-4 Step 1 - plan_helpers module MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Create helper functions to support plan_rewrites() extraction: - build_local_block_map(): Build block ID mapping for a function - sync_spans(): Synchronize instruction spans after rewriting These pure functions will be used by both the current monolithic merge_and_rewrite() and the new plan_rewrites() function. Progress: Step 1/4 (helpers) complete 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- .../current/main/phases/phase-285/README.md | 4 +- docs/reference/language/EBNF.md | 16 ++- docs/reference/language/lifecycle.md | 4 +- docs/reference/language/quick-reference.md | 2 +- .../control_flow/joinir/merge/rewriter/mod.rs | 1 + .../joinir/merge/rewriter/plan_helpers.rs | 116 ++++++++++++++++++ 6 files changed, 137 insertions(+), 6 deletions(-) create mode 100644 src/mir/builder/control_flow/joinir/merge/rewriter/plan_helpers.rs diff --git a/docs/development/current/main/phases/phase-285/README.md b/docs/development/current/main/phases/phase-285/README.md index 9051622c..42ab063a 100644 --- a/docs/development/current/main/phases/phase-285/README.md +++ b/docs/development/current/main/phases/phase-285/README.md @@ -33,7 +33,7 @@ This Phase document is not the language SSOT; it tracks implementation status, b See `docs/development/current/main/phases/phase-285/phase-285a1-boxification.md`. -- WeakRef E2E (VM): `weak(x)` + `weak_to_strong()`, plus strict weak-field contract (no implicit weakification). +- WeakRef E2E (VM/LLVM harness): `weak ` + `weak_to_strong()`, plus strict weak-field contract (no implicit weakification). - Visibility support: `public { weak parent }` plus sugar `public weak parent` (same meaning). - Parser robustness: parameter type annotations (`arg: Type`) are rejected with a clear parse error (no hang). - Helper: `src/parser/common/params.rs` @@ -83,7 +83,7 @@ See `docs/development/current/main/phases/phase-285/phase-285a1-boxification.md` The runbook assumes WeakRef infrastructure exists in the VM and lowering. If any of the following are missing, treat weak smokes as **unsupported** and scope to exit-time leak report first: -- `weak(x)` parse/lower +- `weak ` parse/lower (and `weak(...)` is rejected) - VM handler for MIR WeakRef/WeakNew/WeakLoad - language-surface `weak_to_strong()` on WeakRef diff --git a/docs/reference/language/EBNF.md b/docs/reference/language/EBNF.md index 8656c214..e10aaca6 100644 --- a/docs/reference/language/EBNF.md +++ b/docs/reference/language/EBNF.md @@ -17,7 +17,21 @@ logic := compare (('&&' | '||') compare)* compare := sum (( '==' | '!=' | '<' | '>' | '<=' | '>=' ) sum)? sum := term (('+' | '-') term)* term := unary (('*' | '/') unary)* -unary := ('-' | '!' | 'not' | '~' | 'weak') unary | factor +unary := ( '-' | '!' | 'not' | '~' ) unary + | weak_unary + | factor + +; Phase 285W-Syntax-0.1: `weak()` is invalid. The operand must not be a grouped +; expression starting with `(`. (Write `weak x`, not `weak(x)`.) +weak_unary := 'weak' unary_no_group +unary_no_group := ( '-' | '!' | 'not' | '~' ) unary_no_group + | INT + | STRING + | IDENT call_tail* + | 'new' IDENT '(' args? ')' + | '[' args? ']' ; Array literal (Stage‑1 sugar, gated) + | '{' map_entries? '}' ; Map literal (Stage‑2 sugar, gated) + | match_expr ; Pattern matching (replaces legacy peek) factor := INT | STRING diff --git a/docs/reference/language/lifecycle.md b/docs/reference/language/lifecycle.md index aad8cdb7..bc61e077 100644 --- a/docs/reference/language/lifecycle.md +++ b/docs/reference/language/lifecycle.md @@ -304,7 +304,7 @@ This section documents current backend reality so we can detect drift as bugs. | Feature | VM | LLVM | WASM | |---------|-----|------|------| -| WeakRef (`weak `, `weak_to_strong()`) | ✅ | ❌ unsupported (285LLVM-1) | ❌ unsupported | +| WeakRef (`weak `, `weak_to_strong()`) | ✅ | ✅ LLVM harness (Phase 285LLVM-1.4) | ❌ unsupported | | Leak Report (`NYASH_LEAK_LOG`) | ✅ | ⚠️ Parent process roots only (285LLVM-0) | ❌ | **LLVM Leak Report の制限** (Phase 285LLVM-0): @@ -316,7 +316,7 @@ This section documents current backend reality so we can detect drift as bugs. ### Notes - **Block-scoped locals** are the language model (`local` drops at `}`), but the *observable* effects depend on where the last strong reference is held. -- **WeakRef** (Phase 285A0): VM backend fully supports `weak ` and `weak_to_strong()`. LLVM harness support is planned for Phase 285LLVM-1. +- **WeakRef** (Phase 285A0+): VM backend fully supports `weak ` and `weak_to_strong()`. LLVM harness also supports this surface as of Phase 285LLVM-1.4. - **WASM backend** currently treats MIR `WeakNew/WeakLoad` as plain copies (weak behaves like strong). This does not satisfy the SSOT weak semantics yet (see also: `docs/guides/wasm-guide/planning/unsupported_features.md`). - **Leak Report** (Phase 285): `NYASH_LEAK_LOG={1|2}` prints exit-time diagnostics showing global roots still held (modules, host_handles, plugin_boxes). See `docs/reference/environment-variables.md`. - Conformance gaps (any backend differences from this document) must be treated as bugs and tracked explicitly; do not "paper over" differences by changing this SSOT without a decision. diff --git a/docs/reference/language/quick-reference.md b/docs/reference/language/quick-reference.md index acd93a99..49a0e117 100644 --- a/docs/reference/language/quick-reference.md +++ b/docs/reference/language/quick-reference.md @@ -26,7 +26,7 @@ Display & Conversion - JSONシリアライズ: `toJson(x)`(文字列)/ `toJsonNode(x)`(構造) Operators (precedence high→low) -- Unary: `! ~ -` +- Unary: `! ~ - weak` (`weak ` produces a `WeakRef`; `weak(expr)` is invalid) - Multiplicative: `* / %` - Additive: `+ -` - Compare: `== != < <= > >=` diff --git a/src/mir/builder/control_flow/joinir/merge/rewriter/mod.rs b/src/mir/builder/control_flow/joinir/merge/rewriter/mod.rs index abcda812..c64a08c6 100644 --- a/src/mir/builder/control_flow/joinir/merge/rewriter/mod.rs +++ b/src/mir/builder/control_flow/joinir/merge/rewriter/mod.rs @@ -47,6 +47,7 @@ pub(super) mod type_propagation; // Phase 260 P0.1 Step 4: Type propagation extr pub(super) mod scan_box; // Stage 1: Read-only scanning for rewrite planning pub(super) mod plan_box; // Stage 2: Pure transformation (scan → blocks) pub(super) mod apply_box; // Stage 3: Builder mutation only +pub(super) mod plan_helpers; // Phase 286C-4 Step 1: Helper functions for plan_rewrites() // Re-export public API // Phase 260 P0.1 Step 3: From helpers ✅ diff --git a/src/mir/builder/control_flow/joinir/merge/rewriter/plan_helpers.rs b/src/mir/builder/control_flow/joinir/merge/rewriter/plan_helpers.rs new file mode 100644 index 00000000..baad1837 --- /dev/null +++ b/src/mir/builder/control_flow/joinir/merge/rewriter/plan_helpers.rs @@ -0,0 +1,116 @@ +//! Helper functions for plan_rewrites() stage +//! +//! Phase 286C-4 Step 1: Extracted from merge_and_rewrite() to support plan_rewrites() +//! These are pure helper functions that both the current monolithic code and the +//! new plan_rewrites() function can use. + +use crate::mir::{BasicBlock, BasicBlockId, MirFunction, MirInstruction, ValueId}; +use crate::mir::builder::joinir_id_remapper::JoinIrIdRemapper; +use std::collections::BTreeMap; + +/// Build a local block map for a single function +/// +/// Maps old block IDs (from JoinIR function) to new block IDs (in host MIR). +/// This is needed for remapping Branch/Jump targets within the same function. +/// +/// # Arguments +/// * `func_name` - Name of the function +/// * `func` - The function whose blocks to map +/// * `remapper` - The ID remapper containing block mappings +/// +/// # Returns +/// * BTreeMap for this function +pub fn build_local_block_map( + func_name: &str, + func: &MirFunction, + remapper: &JoinIrIdRemapper, +) -> Result, String> { + let mut local_block_map = BTreeMap::new(); + + for old_block_id in func.blocks.keys() { + let new_block_id = remapper + .get_block(func_name, *old_block_id) + .ok_or_else(|| format!("Block {:?} not found for {}", old_block_id, func_name))?; + local_block_map.insert(*old_block_id, new_block_id); + } + + Ok(local_block_map) +} + +/// Synchronize instruction spans to match instruction count +/// +/// After adding/removing instructions during rewriting, the instruction_spans +/// vector may not match the instructions vector. This function ensures they match. +/// +/// # Arguments +/// * `instructions` - The current instructions +/// * `old_block` - The original block with spans +/// +/// # Returns +/// * A spans vector that matches the instructions length +pub fn sync_spans( + instructions: &[MirInstruction], + old_block: &BasicBlock, +) -> Vec { + let inst_count = instructions.len(); + let mut spans = old_block.instruction_spans.clone(); + + if inst_count > spans.len() { + // Use a default span for the extra instructions + let default_span = spans + .last() + .copied() + .unwrap_or_else(crate::ast::Span::unknown); + spans.resize(inst_count, default_span); + } else if inst_count < spans.len() { + // Truncate spans to match instructions + spans.truncate(inst_count); + } + + spans +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::ast::Span; + + #[test] + fn test_sync_spans_exact_match() { + let instructions = vec![ + MirInstruction::Const { dst: ValueId(1), value: crate::mir::types::ConstValue::Integer(42) }, + MirInstruction::Const { dst: ValueId(2), value: crate::mir::types::ConstValue::Integer(43) }, + ]; + let mut block = BasicBlock::new(BasicBlockId(0)); + block.instruction_spans = vec![Span::unknown(), Span::unknown()]; + + let result = sync_spans(&instructions, &block); + assert_eq!(result.len(), 2); + } + + #[test] + fn test_sync_spans_more_instructions() { + let instructions = vec![ + MirInstruction::Const { dst: ValueId(1), value: crate::mir::types::ConstValue::Integer(42) }, + MirInstruction::Const { dst: ValueId(2), value: crate::mir::types::ConstValue::Integer(43) }, + MirInstruction::Const { dst: ValueId(3), value: crate::mir::types::ConstValue::Integer(44) }, + ]; + let mut block = BasicBlock::new(BasicBlockId(0)); + block.instruction_spans = vec![Span::unknown()]; + + let result = sync_spans(&instructions, &block); + assert_eq!(result.len(), 3); // Should be padded + } + + #[test] + fn test_sync_spans_fewer_instructions() { + let instructions = vec![ + MirInstruction::Const { dst: ValueId(1), value: crate::mir::types::ConstValue::Integer(42) }, + ]; + let mut block = BasicBlock::new(BasicBlockId(0)); + block.instruction_spans = vec![Span::unknown(), Span::unknown(), Span::unknown()]; + + let result = sync_spans(&instructions, &block); + assert_eq!(result.len(), 1); // Should be truncated + } +}