feat(joinir): Phase 135 P0 - ConditionLoweringBox allocator SSOT (ValueId collision fix)
## Summary Root cause: ConditionLoweringBox was bypassing ConditionContext.alloc_value (SSOT allocator), causing ValueId collisions between JoinIR condition params and lowered instructions. ## Changes 1. **ConditionLoweringBox (expr_lowerer.rs)**: Must use ConditionContext.alloc_value - Pass &mut ConditionContext to lower_condition (SSOT allocator) - Eliminates internal counter usage 2. **Allocator unification (condition_lowerer.rs, method_call_lowerer.rs)**: - Accept &mut dyn FnMut() -> ValueId as allocator parameter - Ensures all lowering paths use same SSOT allocator 3. **Boundary Copy deduplication (joinir_inline_boundary_injector.rs)**: - Deduplicate condition_bindings by dst - Fail-Fast if different sources target same dst (MIR SSA violation) 4. **Trim pattern fixes (trim_loop_lowering.rs, trim_pattern_validator.rs, stmts.rs)**: - Use builder.next_value_id() instead of value_gen.next() in function context - Ensures function-level ValueId allocation respects reserved PHI dsts ## Acceptance ✅ ./target/release/hakorune --verify apps/tests/phase133_json_skip_whitespace_min.hako ✅ Smoke: phase135_trim_mir_verify.sh - MIR SSA validation PASS ✅ Regression: phase132_exit_phi_parity.sh - 3/3 PASS ✅ Regression: phase133_json_skip_whitespace_llvm_exe.sh - compile-only PASS 🤖 Generated with Claude Code Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
This commit is contained in:
@ -24,6 +24,9 @@
|
|||||||
- LLVM Python: PHI を落とす/上書きする経路を除去(PHI SSOT を保護)
|
- LLVM Python: PHI を落とす/上書きする経路を除去(PHI SSOT を保護)
|
||||||
- JoinIR merge: exit PHI dst の allocator を function-level に統一(ValueId 衝突を排除)
|
- JoinIR merge: exit PHI dst の allocator を function-level に統一(ValueId 衝突を排除)
|
||||||
- debug-only: Exit PHI collision を早期検出する verifier を追加(LLVM 実行時に壊れる前に Fail-Fast)
|
- debug-only: Exit PHI collision を早期検出する verifier を追加(LLVM 実行時に壊れる前に Fail-Fast)
|
||||||
|
- **Phase 133 完了**: Promoted carrier の `join_id` 解決(Trim)を SSOT に寄せて根治(smoke は compile-only)。
|
||||||
|
- **Phase 134 完了**: Plugin loader best-effort loading(決定的順序 + failure 集約 + 継続)を導入。
|
||||||
|
- **Phase 135 実装**: ConditionLoweringBox が allocator SSOT を無視して ValueId 衝突を起こす問題を根治(検証はローカルで実施)。
|
||||||
- **Phase 88 完了**: continue + 可変ステップ(i=i+const 差分)を dev-only fixture で固定、StepCalculator Box 抽出。
|
- **Phase 88 完了**: continue + 可変ステップ(i=i+const 差分)を dev-only fixture で固定、StepCalculator Box 抽出。
|
||||||
- **Phase 89 完了**: P0(ContinueReturn detector)+ P1(lowering 実装)完了。
|
- **Phase 89 完了**: P0(ContinueReturn detector)+ P1(lowering 実装)完了。
|
||||||
- **Phase 90 完了**: ParseStringComposite + `Null` literal + ContinueReturn(同一値の複数 return-if)を dev-only fixture で固定。
|
- **Phase 90 完了**: ParseStringComposite + `Null` literal + ContinueReturn(同一値の複数 return-if)を dev-only fixture で固定。
|
||||||
@ -48,7 +51,21 @@
|
|||||||
|
|
||||||
## 次の指示書(優先順位)
|
## 次の指示書(優先順位)
|
||||||
|
|
||||||
### P0: Docs 整備(数の増殖を止める)
|
### P0: Phase 135 検証(Trim fixture の `--verify` を緑に固定)
|
||||||
|
|
||||||
|
目的:
|
||||||
|
- `apps/tests/phase133_json_skip_whitespace_min.hako` で発生していた MIR SSA 破綻(ValueId 重複)を後戻りしない形で固定する。
|
||||||
|
|
||||||
|
やること:
|
||||||
|
1. `./target/release/hakorune --verify apps/tests/phase133_json_skip_whitespace_min.hako` が PASS することを確認
|
||||||
|
2. 必要なら integration smoke を追加(quick は増やさない)
|
||||||
|
3. Phase 文書を更新: `docs/development/current/main/phases/phase-135/README.md`
|
||||||
|
|
||||||
|
受け入れ基準:
|
||||||
|
- `--verify` が PASS
|
||||||
|
- 既存の Phase 132/133/134 の integration smoke が退行しない
|
||||||
|
|
||||||
|
### P1: Docs 整備(数の増殖を止める)
|
||||||
|
|
||||||
**目的**: SSOT への集約と導線整備(Phase 86–90 の情報が散らばらない状態にする)
|
**目的**: SSOT への集約と導線整備(Phase 86–90 の情報が散らばらない状態にする)
|
||||||
|
|
||||||
|
|||||||
@ -18,6 +18,13 @@
|
|||||||
**追加(Phase 132-P3): Exit PHI collision の早期検出(debug-only)**
|
**追加(Phase 132-P3): Exit PHI collision の早期検出(debug-only)**
|
||||||
- `verify_exit_phi_no_collision()` を `contract_checks.rs` に追加し、ValueId 衝突を JoinIR merge の段階で Fail-Fast する
|
- `verify_exit_phi_no_collision()` を `contract_checks.rs` に追加し、ValueId 衝突を JoinIR merge の段階で Fail-Fast する
|
||||||
|
|
||||||
|
## 2025‑12‑15:Phase 133–135(短報)
|
||||||
|
|
||||||
|
- Phase 133: promoted carrier(Trim)の `join_id` 解決を SSOT に寄せて修正(smoke は compile-only)。
|
||||||
|
- Phase 134: plugin loader best-effort loading を導入(決定的順序 + failure 集約 + 継続)。
|
||||||
|
- Phase 135: ConditionLoweringBox が allocator SSOT を無視して ValueId 衝突を起こす問題を根治。
|
||||||
|
- 詳細: `docs/development/current/main/phases/phase-135/README.md`
|
||||||
|
|
||||||
## 2025‑12‑14:現状サマリ
|
## 2025‑12‑14:現状サマリ
|
||||||
|
|
||||||
(補足)docs が増えて迷子になったときの「置き場所ルール(SSOT)」:
|
(補足)docs が増えて迷子になったときの「置き場所ルール(SSOT)」:
|
||||||
|
|||||||
@ -5,8 +5,9 @@
|
|||||||
## 現在の Phase
|
## 現在の Phase
|
||||||
|
|
||||||
- **Phase 132**: Exit Values Parity (VM == LLVM)
|
- **Phase 132**: Exit Values Parity (VM == LLVM)
|
||||||
- **Phase 131**: LLVM Lowering & InfiniteEarlyExit パターン実装 🚀
|
- **Phase 133**: Promoted carrier join_id(Trim)修正
|
||||||
- **Phase 33**: Box Theory Modularization
|
- **Phase 134**: Plugin loader best-effort loading
|
||||||
|
- **Phase 135**: ConditionLoweringBox allocator SSOT(ValueId 衝突の根治)
|
||||||
|
|
||||||
## Phase フォルダ構成(推奨)
|
## Phase フォルダ構成(推奨)
|
||||||
|
|
||||||
|
|||||||
27
docs/development/current/main/phases/phase-135/README.md
Normal file
27
docs/development/current/main/phases/phase-135/README.md
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
# Phase 135: ConditionLoweringBox allocator SSOT(ValueId 衝突の根治)
|
||||||
|
|
||||||
|
## Status
|
||||||
|
- 状態: ✅ 実装完了(動作確認はローカルで実施)
|
||||||
|
- スコープ: JoinIR の条件 lowering が `JoinValueSpace` と同一の allocator(SSOT)を使うことを保証する
|
||||||
|
|
||||||
|
## Problem
|
||||||
|
`apps/tests/phase133_json_skip_whitespace_min.hako` などで `--verify` が失敗し、MIR に以下の SSA 破綻が出ることがあった:
|
||||||
|
- `Value %13/%14 defined multiple times`(ループ header PHI dst が後続命令で上書きされる)
|
||||||
|
- `Value %18 defined multiple times`(同一 JoinIR ValueId への alias binding が Copy を重複注入する)
|
||||||
|
|
||||||
|
## Root Cause
|
||||||
|
1. `ExprLowerer` が `ConditionLoweringBox` 実装で `ConditionContext.alloc_value` を無視し、内部カウンタで ValueId を発行していた。
|
||||||
|
→ JoinIR 内で `main()` params(例: `ValueId(1000), ValueId(1001)`)と衝突し、merge の remap で header PHI dst に書き込む命令が生成される。
|
||||||
|
2. `JoinInlineBoundary` の `condition_bindings` に同一 `join_value` が複数名で登録される場合があり、entry block への Copy 注入が同じ `dst` に重複する。
|
||||||
|
→ MIR SSA を破壊する(`copy dst` が 2 回発生)。
|
||||||
|
|
||||||
|
## Fix
|
||||||
|
- `ConditionLoweringBox` は `ConditionContext.alloc_value`(SSOT allocator)を必ず使う。
|
||||||
|
- `ConditionLoweringBox::lower_condition` は `&mut ConditionContext` を受け取る(allocator の正当な可変借用のため)。
|
||||||
|
- `condition_lowerer::lower_condition_to_joinir` は `&mut dyn FnMut() -> ValueId` を受理する。
|
||||||
|
- `BoundaryInjector` は `condition_bindings` 注入を `dst` で重複排除し、異なる source が同一 dst に来る場合は Fail-Fast。
|
||||||
|
|
||||||
|
## Acceptance
|
||||||
|
- `./target/release/hakorune --verify apps/tests/phase133_json_skip_whitespace_min.hako` が PASS
|
||||||
|
- `./target/release/hakorune --dump-mir apps/tests/phase133_json_skip_whitespace_min.hako` のループ header で PHI dst の再定義がない
|
||||||
|
|
||||||
@ -363,7 +363,8 @@ impl TrimLoopLowerer {
|
|||||||
use crate::mir::instruction::MirInstruction;
|
use crate::mir::instruction::MirInstruction;
|
||||||
use crate::mir::types::BinaryOp;
|
use crate::mir::types::BinaryOp;
|
||||||
let one = emit_integer(builder, 1);
|
let one = emit_integer(builder, 1);
|
||||||
let start_plus_1 = builder.value_gen.next();
|
// Phase 135 P0: Use function-level ValueId (SSOT)
|
||||||
|
let start_plus_1 = builder.next_value_id();
|
||||||
builder.emit_instruction(MirInstruction::BinOp {
|
builder.emit_instruction(MirInstruction::BinOp {
|
||||||
dst: start_plus_1,
|
dst: start_plus_1,
|
||||||
op: BinaryOp::Add,
|
op: BinaryOp::Add,
|
||||||
@ -372,7 +373,8 @@ impl TrimLoopLowerer {
|
|||||||
})?;
|
})?;
|
||||||
|
|
||||||
// Generate: ch0 = s.substring(start, start+1)
|
// Generate: ch0 = s.substring(start, start+1)
|
||||||
let ch0 = builder.value_gen.next();
|
// Phase 135 P0: Use function-level ValueId (SSOT)
|
||||||
|
let ch0 = builder.next_value_id();
|
||||||
builder.emit_method_call(
|
builder.emit_method_call(
|
||||||
Some(ch0),
|
Some(ch0),
|
||||||
s_id,
|
s_id,
|
||||||
|
|||||||
@ -47,12 +47,14 @@ impl TrimPatternValidator {
|
|||||||
let ws_const = emit_string(builder, ws_char.clone());
|
let ws_const = emit_string(builder, ws_char.clone());
|
||||||
|
|
||||||
// eq_check = ch == ws_const
|
// eq_check = ch == ws_const
|
||||||
let eq_dst = builder.value_gen.next();
|
// Phase 135 P0: Use function-level ValueId (SSOT)
|
||||||
|
let eq_dst = builder.next_value_id();
|
||||||
emit_eq_to(builder, eq_dst, ch_value, ws_const)?;
|
emit_eq_to(builder, eq_dst, ch_value, ws_const)?;
|
||||||
|
|
||||||
result_opt = Some(if let Some(prev_result) = result_opt {
|
result_opt = Some(if let Some(prev_result) = result_opt {
|
||||||
// result = prev_result || eq_check
|
// result = prev_result || eq_check
|
||||||
let dst = builder.value_gen.next();
|
// Phase 135 P0: Use function-level ValueId (SSOT)
|
||||||
|
let dst = builder.next_value_id();
|
||||||
builder.emit_instruction(MirInstruction::BinOp {
|
builder.emit_instruction(MirInstruction::BinOp {
|
||||||
dst,
|
dst,
|
||||||
op: BinaryOp::Or,
|
op: BinaryOp::Or,
|
||||||
|
|||||||
@ -178,6 +178,7 @@ impl BoundaryInjector {
|
|||||||
// We inject Copy: remapped_join_value = Copy host_value
|
// We inject Copy: remapped_join_value = Copy host_value
|
||||||
//
|
//
|
||||||
// Phase 177-3 Option B: Use pre-allocated reallocations for PHI collision cases
|
// Phase 177-3 Option B: Use pre-allocated reallocations for PHI collision cases
|
||||||
|
let mut seen_dst_to_src: BTreeMap<ValueId, ValueId> = BTreeMap::new();
|
||||||
for binding in &boundary.condition_bindings {
|
for binding in &boundary.condition_bindings {
|
||||||
// Look up the remapped JoinIR ValueId from value_map
|
// Look up the remapped JoinIR ValueId from value_map
|
||||||
let remapped_join = value_map
|
let remapped_join = value_map
|
||||||
@ -191,6 +192,23 @@ impl BoundaryInjector {
|
|||||||
.copied()
|
.copied()
|
||||||
.unwrap_or(remapped_join);
|
.unwrap_or(remapped_join);
|
||||||
|
|
||||||
|
// Phase 135-P0: Deduplicate condition bindings that alias the same JoinIR ValueId.
|
||||||
|
//
|
||||||
|
// Promoters may produce multiple names that map to the same JoinIR ValueId
|
||||||
|
// (e.g. 'char' and 'is_char_match' sharing a promoted carrier slot).
|
||||||
|
// Emitting multiple `Copy` instructions to the same `dst` violates MIR SSA and
|
||||||
|
// fails `--verify` (ValueId defined multiple times).
|
||||||
|
if let Some(prev_src) = seen_dst_to_src.get(&final_dst).copied() {
|
||||||
|
if prev_src != binding.host_value {
|
||||||
|
return Err(format!(
|
||||||
|
"[BoundaryInjector] condition_bindings conflict: dst {:?} would be assigned from two different sources: {:?} and {:?} (JoinIR {:?}, name '{}')",
|
||||||
|
final_dst, prev_src, binding.host_value, binding.join_value, binding.name
|
||||||
|
));
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
seen_dst_to_src.insert(final_dst, binding.host_value);
|
||||||
|
|
||||||
// Copy instruction: final_dst = Copy host_value
|
// Copy instruction: final_dst = Copy host_value
|
||||||
let copy_inst = MirInstruction::Copy {
|
let copy_inst = MirInstruction::Copy {
|
||||||
dst: final_dst,
|
dst: final_dst,
|
||||||
|
|||||||
@ -346,7 +346,8 @@ impl super::MirBuilder {
|
|||||||
}
|
}
|
||||||
last_value = Some(var_id);
|
last_value = Some(var_id);
|
||||||
}
|
}
|
||||||
Ok(last_value.unwrap_or_else(|| self.value_gen.next()))
|
// Phase 135 P0: Use function-level ValueId (SSOT) - build_local_statement is always in function context
|
||||||
|
Ok(last_value.unwrap_or_else(|| self.next_value_id()))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return statement
|
// Return statement
|
||||||
|
|||||||
@ -59,14 +59,11 @@ use super::method_call_lowerer::MethodCallLowerer;
|
|||||||
/// &env,
|
/// &env,
|
||||||
/// )?;
|
/// )?;
|
||||||
/// ```
|
/// ```
|
||||||
pub fn lower_condition_to_joinir<F>(
|
pub fn lower_condition_to_joinir(
|
||||||
cond_ast: &ASTNode,
|
cond_ast: &ASTNode,
|
||||||
alloc_value: &mut F,
|
alloc_value: &mut dyn FnMut() -> ValueId,
|
||||||
env: &ConditionEnv,
|
env: &ConditionEnv,
|
||||||
) -> Result<(ValueId, Vec<JoinInst>), String>
|
) -> Result<(ValueId, Vec<JoinInst>), String> {
|
||||||
where
|
|
||||||
F: FnMut() -> ValueId,
|
|
||||||
{
|
|
||||||
let mut instructions = Vec::new();
|
let mut instructions = Vec::new();
|
||||||
let result_value = lower_condition_recursive(cond_ast, alloc_value, env, &mut instructions)?;
|
let result_value = lower_condition_recursive(cond_ast, alloc_value, env, &mut instructions)?;
|
||||||
Ok((result_value, instructions))
|
Ok((result_value, instructions))
|
||||||
@ -75,15 +72,12 @@ where
|
|||||||
/// Recursive helper for condition lowering
|
/// Recursive helper for condition lowering
|
||||||
///
|
///
|
||||||
/// Handles all supported AST node types and emits appropriate JoinIR instructions.
|
/// Handles all supported AST node types and emits appropriate JoinIR instructions.
|
||||||
fn lower_condition_recursive<F>(
|
fn lower_condition_recursive(
|
||||||
cond_ast: &ASTNode,
|
cond_ast: &ASTNode,
|
||||||
alloc_value: &mut F,
|
alloc_value: &mut dyn FnMut() -> ValueId,
|
||||||
env: &ConditionEnv,
|
env: &ConditionEnv,
|
||||||
instructions: &mut Vec<JoinInst>,
|
instructions: &mut Vec<JoinInst>,
|
||||||
) -> Result<ValueId, String>
|
) -> Result<ValueId, String> {
|
||||||
where
|
|
||||||
F: FnMut() -> ValueId,
|
|
||||||
{
|
|
||||||
match cond_ast {
|
match cond_ast {
|
||||||
// Comparison operations: <, ==, !=, <=, >=, >
|
// Comparison operations: <, ==, !=, <=, >=, >
|
||||||
ASTNode::BinaryOp {
|
ASTNode::BinaryOp {
|
||||||
@ -128,16 +122,14 @@ where
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Lower a comparison operation (e.g., `i < end`)
|
/// Lower a comparison operation (e.g., `i < end`)
|
||||||
fn lower_comparison<F>(
|
fn lower_comparison(
|
||||||
operator: &BinaryOperator,
|
operator: &BinaryOperator,
|
||||||
left: &ASTNode,
|
left: &ASTNode,
|
||||||
right: &ASTNode,
|
right: &ASTNode,
|
||||||
alloc_value: &mut F,
|
alloc_value: &mut dyn FnMut() -> ValueId,
|
||||||
env: &ConditionEnv,
|
env: &ConditionEnv,
|
||||||
instructions: &mut Vec<JoinInst>,
|
instructions: &mut Vec<JoinInst>,
|
||||||
) -> Result<ValueId, String>
|
) -> Result<ValueId, String>
|
||||||
where
|
|
||||||
F: FnMut() -> ValueId,
|
|
||||||
{
|
{
|
||||||
// Lower left and right sides
|
// Lower left and right sides
|
||||||
let lhs = lower_value_expression(left, alloc_value, env, instructions)?;
|
let lhs = lower_value_expression(left, alloc_value, env, instructions)?;
|
||||||
@ -166,15 +158,13 @@ where
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Lower logical AND operation (e.g., `a && b`)
|
/// Lower logical AND operation (e.g., `a && b`)
|
||||||
fn lower_logical_and<F>(
|
fn lower_logical_and(
|
||||||
left: &ASTNode,
|
left: &ASTNode,
|
||||||
right: &ASTNode,
|
right: &ASTNode,
|
||||||
alloc_value: &mut F,
|
alloc_value: &mut dyn FnMut() -> ValueId,
|
||||||
env: &ConditionEnv,
|
env: &ConditionEnv,
|
||||||
instructions: &mut Vec<JoinInst>,
|
instructions: &mut Vec<JoinInst>,
|
||||||
) -> Result<ValueId, String>
|
) -> Result<ValueId, String>
|
||||||
where
|
|
||||||
F: FnMut() -> ValueId,
|
|
||||||
{
|
{
|
||||||
// Logical AND: evaluate both sides and combine
|
// Logical AND: evaluate both sides and combine
|
||||||
let lhs = lower_condition_recursive(left, alloc_value, env, instructions)?;
|
let lhs = lower_condition_recursive(left, alloc_value, env, instructions)?;
|
||||||
@ -193,15 +183,13 @@ where
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Lower logical OR operation (e.g., `a || b`)
|
/// Lower logical OR operation (e.g., `a || b`)
|
||||||
fn lower_logical_or<F>(
|
fn lower_logical_or(
|
||||||
left: &ASTNode,
|
left: &ASTNode,
|
||||||
right: &ASTNode,
|
right: &ASTNode,
|
||||||
alloc_value: &mut F,
|
alloc_value: &mut dyn FnMut() -> ValueId,
|
||||||
env: &ConditionEnv,
|
env: &ConditionEnv,
|
||||||
instructions: &mut Vec<JoinInst>,
|
instructions: &mut Vec<JoinInst>,
|
||||||
) -> Result<ValueId, String>
|
) -> Result<ValueId, String>
|
||||||
where
|
|
||||||
F: FnMut() -> ValueId,
|
|
||||||
{
|
{
|
||||||
// Logical OR: evaluate both sides and combine
|
// Logical OR: evaluate both sides and combine
|
||||||
let lhs = lower_condition_recursive(left, alloc_value, env, instructions)?;
|
let lhs = lower_condition_recursive(left, alloc_value, env, instructions)?;
|
||||||
@ -220,14 +208,12 @@ where
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Lower NOT operator (e.g., `!cond`)
|
/// Lower NOT operator (e.g., `!cond`)
|
||||||
fn lower_not_operator<F>(
|
fn lower_not_operator(
|
||||||
operand: &ASTNode,
|
operand: &ASTNode,
|
||||||
alloc_value: &mut F,
|
alloc_value: &mut dyn FnMut() -> ValueId,
|
||||||
env: &ConditionEnv,
|
env: &ConditionEnv,
|
||||||
instructions: &mut Vec<JoinInst>,
|
instructions: &mut Vec<JoinInst>,
|
||||||
) -> Result<ValueId, String>
|
) -> Result<ValueId, String>
|
||||||
where
|
|
||||||
F: FnMut() -> ValueId,
|
|
||||||
{
|
{
|
||||||
let operand_val = lower_condition_recursive(operand, alloc_value, env, instructions)?;
|
let operand_val = lower_condition_recursive(operand, alloc_value, env, instructions)?;
|
||||||
let dst = alloc_value();
|
let dst = alloc_value();
|
||||||
@ -243,13 +229,11 @@ where
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Lower a literal value (e.g., `10`, `true`, `"text"`)
|
/// Lower a literal value (e.g., `10`, `true`, `"text"`)
|
||||||
fn lower_literal<F>(
|
fn lower_literal(
|
||||||
value: &LiteralValue,
|
value: &LiteralValue,
|
||||||
alloc_value: &mut F,
|
alloc_value: &mut dyn FnMut() -> ValueId,
|
||||||
instructions: &mut Vec<JoinInst>,
|
instructions: &mut Vec<JoinInst>,
|
||||||
) -> Result<ValueId, String>
|
) -> Result<ValueId, String>
|
||||||
where
|
|
||||||
F: FnMut() -> ValueId,
|
|
||||||
{
|
{
|
||||||
let dst = alloc_value();
|
let dst = alloc_value();
|
||||||
let const_value = match value {
|
let const_value = match value {
|
||||||
@ -279,15 +263,12 @@ where
|
|||||||
///
|
///
|
||||||
/// This handles the common case where we need to evaluate a simple value
|
/// This handles the common case where we need to evaluate a simple value
|
||||||
/// (variable or literal) as part of a comparison.
|
/// (variable or literal) as part of a comparison.
|
||||||
pub fn lower_value_expression<F>(
|
pub fn lower_value_expression(
|
||||||
expr: &ASTNode,
|
expr: &ASTNode,
|
||||||
alloc_value: &mut F,
|
alloc_value: &mut dyn FnMut() -> ValueId,
|
||||||
env: &ConditionEnv,
|
env: &ConditionEnv,
|
||||||
instructions: &mut Vec<JoinInst>,
|
instructions: &mut Vec<JoinInst>,
|
||||||
) -> Result<ValueId, String>
|
) -> Result<ValueId, String> {
|
||||||
where
|
|
||||||
F: FnMut() -> ValueId,
|
|
||||||
{
|
|
||||||
match expr {
|
match expr {
|
||||||
// Variables - look up in ConditionEnv
|
// Variables - look up in ConditionEnv
|
||||||
ASTNode::Variable { name, .. } => env
|
ASTNode::Variable { name, .. } => env
|
||||||
@ -334,16 +315,14 @@ where
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Lower an arithmetic binary operation (e.g., `i + 1`)
|
/// Lower an arithmetic binary operation (e.g., `i + 1`)
|
||||||
fn lower_arithmetic_binop<F>(
|
fn lower_arithmetic_binop(
|
||||||
operator: &BinaryOperator,
|
operator: &BinaryOperator,
|
||||||
left: &ASTNode,
|
left: &ASTNode,
|
||||||
right: &ASTNode,
|
right: &ASTNode,
|
||||||
alloc_value: &mut F,
|
alloc_value: &mut dyn FnMut() -> ValueId,
|
||||||
env: &ConditionEnv,
|
env: &ConditionEnv,
|
||||||
instructions: &mut Vec<JoinInst>,
|
instructions: &mut Vec<JoinInst>,
|
||||||
) -> Result<ValueId, String>
|
) -> Result<ValueId, String>
|
||||||
where
|
|
||||||
F: FnMut() -> ValueId,
|
|
||||||
{
|
{
|
||||||
let lhs = lower_value_expression(left, alloc_value, env, instructions)?;
|
let lhs = lower_value_expression(left, alloc_value, env, instructions)?;
|
||||||
let rhs = lower_value_expression(right, alloc_value, env, instructions)?;
|
let rhs = lower_value_expression(right, alloc_value, env, instructions)?;
|
||||||
|
|||||||
@ -125,7 +125,7 @@ pub trait ConditionLoweringBox<S: ScopeManager> {
|
|||||||
fn lower_condition(
|
fn lower_condition(
|
||||||
&mut self,
|
&mut self,
|
||||||
condition: &ASTNode,
|
condition: &ASTNode,
|
||||||
context: &ConditionContext<S>,
|
context: &mut ConditionContext<S>,
|
||||||
) -> Result<ValueId, String>;
|
) -> Result<ValueId, String>;
|
||||||
|
|
||||||
/// Check if this lowerer supports the given condition pattern
|
/// Check if this lowerer supports the given condition pattern
|
||||||
@ -225,7 +225,7 @@ mod tests {
|
|||||||
id
|
id
|
||||||
};
|
};
|
||||||
|
|
||||||
let context = ConditionContext {
|
let mut context = ConditionContext {
|
||||||
loop_var_name: "i".to_string(),
|
loop_var_name: "i".to_string(),
|
||||||
loop_var_id: ValueId(1),
|
loop_var_id: ValueId(1),
|
||||||
scope: &scope,
|
scope: &scope,
|
||||||
@ -245,7 +245,7 @@ mod tests {
|
|||||||
);
|
);
|
||||||
|
|
||||||
// Lower condition via ConditionLoweringBox trait (Step 2 implemented)
|
// Lower condition via ConditionLoweringBox trait (Step 2 implemented)
|
||||||
let result = expr_lowerer.lower_condition(&ast, &context);
|
let result = expr_lowerer.lower_condition(&ast, &mut context);
|
||||||
assert!(result.is_ok(), "i < 10 should lower successfully via trait");
|
assert!(result.is_ok(), "i < 10 should lower successfully via trait");
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -280,7 +280,7 @@ mod tests {
|
|||||||
id
|
id
|
||||||
};
|
};
|
||||||
|
|
||||||
let context = ConditionContext {
|
let mut context = ConditionContext {
|
||||||
loop_var_name: "i".to_string(),
|
loop_var_name: "i".to_string(),
|
||||||
loop_var_id: ValueId(1),
|
loop_var_id: ValueId(1),
|
||||||
scope: &scope,
|
scope: &scope,
|
||||||
|
|||||||
@ -265,11 +265,43 @@ impl<'env, 'builder, S: ScopeManager> ConditionLoweringBox<S> for ExprLowerer<'e
|
|||||||
fn lower_condition(
|
fn lower_condition(
|
||||||
&mut self,
|
&mut self,
|
||||||
condition: &ASTNode,
|
condition: &ASTNode,
|
||||||
_context: &ConditionContext<S>,
|
context: &mut ConditionContext<S>,
|
||||||
) -> Result<ValueId, String> {
|
) -> Result<ValueId, String> {
|
||||||
// Delegate to existing lower() method
|
// Phase 244+ / Phase 201 SSOT: ValueId allocation must be coordinated by the caller.
|
||||||
// ConditionContext is unused because ExprLowerer already has scope access
|
//
|
||||||
self.lower(condition).map_err(|e| e.to_string())
|
// JoinIR lowering uses JoinValueSpace as SSOT for ValueId regions.
|
||||||
|
// If we allocate locally here (e.g. starting from 1000), we can collide with
|
||||||
|
// other JoinIR value users (main params, carrier slots), and after remapping
|
||||||
|
// this becomes a MIR-level ValueId collision.
|
||||||
|
if !ast_support::is_supported_condition(condition) {
|
||||||
|
return Err(format!("Unsupported condition node: {:?}", condition));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build ConditionEnv from the provided scope (the caller controls the scope + allocator).
|
||||||
|
#[cfg(feature = "normalized_dev")]
|
||||||
|
let condition_env =
|
||||||
|
scope_resolution::build_condition_env_from_scope_with_binding(context.scope, condition, self.builder)
|
||||||
|
.map_err(|e| e.to_string())?;
|
||||||
|
|
||||||
|
#[cfg(not(feature = "normalized_dev"))]
|
||||||
|
let condition_env = scope_resolution::build_condition_env_from_scope(context.scope, condition)
|
||||||
|
.map_err(|e| e.to_string())?;
|
||||||
|
|
||||||
|
// Delegate to the well-tested lowerer, but use the caller-provided allocator (SSOT).
|
||||||
|
let (result_value, instructions) =
|
||||||
|
lower_condition_to_joinir(condition, &mut *context.alloc_value, &condition_env)
|
||||||
|
.map_err(|e| e.to_string())?;
|
||||||
|
|
||||||
|
self.last_instructions = instructions;
|
||||||
|
|
||||||
|
if self.debug {
|
||||||
|
eprintln!(
|
||||||
|
"[expr_lowerer/phase244] Lowered condition → ValueId({:?}) (context alloc)",
|
||||||
|
result_value
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(result_value)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Phase 244: Check if ExprLowerer supports a given condition pattern
|
/// Phase 244: Check if ExprLowerer supports a given condition pattern
|
||||||
|
|||||||
@ -51,14 +51,14 @@ pub(crate) fn lower_header_condition(
|
|||||||
let mut expr_lowerer =
|
let mut expr_lowerer =
|
||||||
ExprLowerer::new(&scope_manager, ExprContext::Condition, &mut dummy_builder);
|
ExprLowerer::new(&scope_manager, ExprContext::Condition, &mut dummy_builder);
|
||||||
|
|
||||||
let context = ConditionContext {
|
let mut context = ConditionContext {
|
||||||
loop_var_name: loop_var_name.to_string(),
|
loop_var_name: loop_var_name.to_string(),
|
||||||
loop_var_id,
|
loop_var_id,
|
||||||
scope: &scope_manager,
|
scope: &scope_manager,
|
||||||
alloc_value,
|
alloc_value,
|
||||||
};
|
};
|
||||||
|
|
||||||
match expr_lowerer.lower_condition(condition, &context) {
|
match expr_lowerer.lower_condition(condition, &mut context) {
|
||||||
Ok(value_id) => {
|
Ok(value_id) => {
|
||||||
let instructions = expr_lowerer.take_last_instructions();
|
let instructions = expr_lowerer.take_last_instructions();
|
||||||
eprintln!(
|
eprintln!(
|
||||||
@ -105,7 +105,7 @@ pub(crate) fn lower_break_condition(
|
|||||||
let mut expr_lowerer =
|
let mut expr_lowerer =
|
||||||
ExprLowerer::new(&scope_manager, ExprContext::Condition, &mut dummy_builder);
|
ExprLowerer::new(&scope_manager, ExprContext::Condition, &mut dummy_builder);
|
||||||
|
|
||||||
let context = ConditionContext {
|
let mut context = ConditionContext {
|
||||||
loop_var_name: loop_var_name.to_string(),
|
loop_var_name: loop_var_name.to_string(),
|
||||||
loop_var_id,
|
loop_var_id,
|
||||||
scope: &scope_manager,
|
scope: &scope_manager,
|
||||||
@ -113,7 +113,7 @@ pub(crate) fn lower_break_condition(
|
|||||||
};
|
};
|
||||||
|
|
||||||
let value_id = expr_lowerer
|
let value_id = expr_lowerer
|
||||||
.lower_condition(break_condition, &context)
|
.lower_condition(break_condition, &mut context)
|
||||||
.map_err(|e| {
|
.map_err(|e| {
|
||||||
format!(
|
format!(
|
||||||
"[joinir/pattern2/phase244] ConditionLoweringBox failed to lower break condition: {}",
|
"[joinir/pattern2/phase244] ConditionLoweringBox failed to lower break condition: {}",
|
||||||
|
|||||||
@ -259,14 +259,14 @@ pub(crate) fn lower_loop_with_continue_minimal(
|
|||||||
let mut expr_lowerer =
|
let mut expr_lowerer =
|
||||||
ExprLowerer::new(&scope_manager, ExprContext::Condition, &mut dummy_builder);
|
ExprLowerer::new(&scope_manager, ExprContext::Condition, &mut dummy_builder);
|
||||||
|
|
||||||
let context = ConditionContext {
|
let mut context = ConditionContext {
|
||||||
loop_var_name: loop_var_name.clone(),
|
loop_var_name: loop_var_name.clone(),
|
||||||
loop_var_id: i_param,
|
loop_var_id: i_param,
|
||||||
scope: &scope_manager,
|
scope: &scope_manager,
|
||||||
alloc_value: &mut alloc_value,
|
alloc_value: &mut alloc_value,
|
||||||
};
|
};
|
||||||
|
|
||||||
match expr_lowerer.lower_condition(condition, &context) {
|
match expr_lowerer.lower_condition(condition, &mut context) {
|
||||||
Ok(value_id) => {
|
Ok(value_id) => {
|
||||||
let instructions = expr_lowerer.take_last_instructions();
|
let instructions = expr_lowerer.take_last_instructions();
|
||||||
eprintln!("[joinir/pattern4/phase244] Header condition via ConditionLoweringBox: {} instructions", instructions.len());
|
eprintln!("[joinir/pattern4/phase244] Header condition via ConditionLoweringBox: {} instructions", instructions.len());
|
||||||
|
|||||||
@ -81,17 +81,14 @@ impl MethodCallLowerer {
|
|||||||
/// &mut instructions,
|
/// &mut instructions,
|
||||||
/// )?;
|
/// )?;
|
||||||
/// ```
|
/// ```
|
||||||
pub fn lower_for_condition<F>(
|
pub fn lower_for_condition(
|
||||||
recv_val: ValueId,
|
recv_val: ValueId,
|
||||||
method_name: &str,
|
method_name: &str,
|
||||||
args: &[ASTNode],
|
args: &[ASTNode],
|
||||||
alloc_value: &mut F,
|
alloc_value: &mut dyn FnMut() -> ValueId,
|
||||||
env: &ConditionEnv,
|
env: &ConditionEnv,
|
||||||
instructions: &mut Vec<JoinInst>,
|
instructions: &mut Vec<JoinInst>,
|
||||||
) -> Result<ValueId, String>
|
) -> Result<ValueId, String> {
|
||||||
where
|
|
||||||
F: FnMut() -> ValueId,
|
|
||||||
{
|
|
||||||
// Resolve method name to CoreMethodId
|
// Resolve method name to CoreMethodId
|
||||||
// Note: We don't know receiver type at this point, so we try all methods
|
// Note: We don't know receiver type at this point, so we try all methods
|
||||||
let method_id = CoreMethodId::iter()
|
let method_id = CoreMethodId::iter()
|
||||||
|
|||||||
@ -0,0 +1,33 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# Phase 135: MIR verify regression for Trim(A-3) + Pattern2 lowering
|
||||||
|
# Purpose: prevent ValueId/SSA corruption introduced by allocator mismatch or duplicated boundary copies.
|
||||||
|
#
|
||||||
|
# Expected: `--verify` PASS for the Phase 133 Trim-derived fixture.
|
||||||
|
|
||||||
|
source "$(dirname "$0")/../../../lib/test_runner.sh"
|
||||||
|
export SMOKES_USE_PYVM=0
|
||||||
|
require_env || exit 2
|
||||||
|
|
||||||
|
INPUT="$NYASH_ROOT/apps/tests/phase133_json_skip_whitespace_min.hako"
|
||||||
|
|
||||||
|
echo "[INFO] Phase 135: --verify (phase133_json_skip_whitespace_min.hako)"
|
||||||
|
OUT="$(timeout "${RUN_TIMEOUT_SECS:-60}" "$NYASH_BIN" --verify "$INPUT" 2>&1)"
|
||||||
|
RC=$?
|
||||||
|
|
||||||
|
if [ "$RC" -ne 0 ]; then
|
||||||
|
echo "[FAIL] verify: hakorune --verify failed (rc=$RC)"
|
||||||
|
echo "[INFO] output (tail):"
|
||||||
|
echo "$OUT" | tail -n 120 || true
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if echo "$OUT" | grep -q "MIR verification failed"; then
|
||||||
|
echo "[FAIL] verify: MIR verification failed"
|
||||||
|
echo "[INFO] output (tail):"
|
||||||
|
echo "$OUT" | tail -n 120 || true
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "[PASS] verify: MIR is valid (SSA/ValueId OK)"
|
||||||
|
exit 0
|
||||||
|
|
||||||
Reference in New Issue
Block a user