feat(mir): Phase 131-11-E - TypeFacts/TypeDemands 分離(SSOT)
## 実装内容
### 1) Rust MIR Builder (ops.rs + lifecycle.rs)
- OperandTypeClass で型分類(String/Integer/Unknown)
- BinOp 型推論: Integer + Unknown → Integer
- lifecycle.rs に repropagate_binop_types() パス追加
- PHI 型解決後に BinOp 型を再計算
### 2) JSON Emission (mir_json_emit.rs)
- 結果ベースの dst_type 発行に変更
- Integer → "i64", String → {kind: handle, box_type: StringBox}
### 3) Python LLVM Backend (binop.py)
- dst_type を確実な情報として使用
- dst_type != None なら優先して処理
## 結果
- ✅ MIR: PHI/BinOp が Integer として正しく型付け
- ✅ VM: `Result: 3` (正しい出力)
- ✅ JSON: `dst_type: "i64"` を発行
- ❓ LLVM: 別の codegen 問題の可能性あり
## SSOT 設計達成
- TypeFacts(事実): 定義命令から推論
- TypeDemands(要求): 使用箇所の coercion で吸収
- 後方伝播なし: Fail-Fast に統一
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
This commit is contained in:
@ -38,7 +38,9 @@ JoinIR の箱構造と責務、ループ/if の lowering パターンを把握
|
||||
- `docs/development/current/main/loop_pattern_space.md`
|
||||
3. Boundary / ExitLine / Carrier の具体パターン
|
||||
- `docs/development/current/main/joinir-boundary-builder-pattern.md`
|
||||
4. Scope/BindingId(shadowing・束縛同一性の段階移行)
|
||||
4. JoinIR 設計地図(現役の地図)
|
||||
- `docs/development/current/main/design/joinir-design-map.md`
|
||||
5. Scope/BindingId(shadowing・束縛同一性の段階移行)
|
||||
- `docs/development/current/main/phase73-scope-manager-design.md`
|
||||
- `docs/development/current/main/PHASE_74_SUMMARY.md`
|
||||
- `docs/development/current/main/PHASE_75_SUMMARY.md`
|
||||
@ -46,16 +48,16 @@ JoinIR の箱構造と責務、ループ/if の lowering パターンを把握
|
||||
- `docs/development/current/main/phase78-bindingid-promoted-carriers.md`
|
||||
- `docs/development/current/main/phase80-bindingid-p3p4-plan.md`(P3/P4 への配線計画)
|
||||
- `docs/development/current/main/phase81-pattern2-exitline-contract.md`(promoted carriers の ExitLine 契約検証)
|
||||
5. Boxification feedback(Phase 78–85 の振り返りと Phase 86 推奨)
|
||||
6. Boxification feedback(Phase 78–85 の振り返りと Phase 86 推奨)
|
||||
- `docs/development/current/main/phase78-85-boxification-feedback.md`
|
||||
6. Phase 86: Carrier Init Builder + Error Tags ✅
|
||||
7. Phase 86: Carrier Init Builder + Error Tags ✅
|
||||
- **Status**: COMPLETE (2025-12-13)
|
||||
- **Modules**:
|
||||
- `src/mir/builder/control_flow/joinir/merge/carrier_init_builder.rs` (+8 tests)
|
||||
- `src/mir/join_ir/lowering/error_tags.rs` (+5 tests)
|
||||
- **Achievements**: SSOT 確立(CarrierInit → ValueId 生成統一、エラータグ中央化、DebugOutputBox 完全移行)
|
||||
- **Impact**: 987/987 tests PASS, +13 unit tests, Single Responsibility validated
|
||||
7. Phase 87: LLVM Exe Line SSOT ✅
|
||||
8. Phase 87: LLVM Exe Line SSOT ✅
|
||||
- **Status**: COMPLETE (2025-12-13)
|
||||
- **SSOT**: `tools/build_llvm.sh` - Single pipeline for .hako → executable
|
||||
- **Deliverables**:
|
||||
@ -64,11 +66,11 @@ JoinIR の箱構造と責務、ループ/if の lowering パターンを把握
|
||||
- Integration smoke: `tools/smokes/v2/profiles/integration/apps/phase87_llvm_exe_min.sh` (SKIP if no LLVM)
|
||||
- **Policy**: No script duplication, integration smoke only (not quick), graceful SKIP
|
||||
- **Impact**: Standard procedure established, prerequisites documented
|
||||
8. 代表的な Phase 文書(現役ラインとの接点だけ絞ったもの)
|
||||
9. 代表的な Phase 文書(現役ラインとの接点だけ絞ったもの)
|
||||
- `docs/development/current/main/phase33-16-INDEX.md`
|
||||
- `docs/development/current/main/phase33-17-joinir-modularization-analysis.md`
|
||||
- `docs/development/current/main/phase183-selfhost-depth2-joinir-status.md`
|
||||
9. Phase 86–90(Loop frontends)の要約(1枚)
|
||||
10. Phase 86–90(Loop frontends)の要約(1枚)
|
||||
- `docs/development/current/main/phase86-90-loop-frontends-summary.md`
|
||||
|
||||
Phase 文書は歴史や検証ログも含むので、「JoinIR の現役設計を確認した上で、必要なときだけ掘る」という前提で読んでね。
|
||||
|
||||
146
docs/development/current/main/design/joinir-design-map.md
Normal file
146
docs/development/current/main/design/joinir-design-map.md
Normal file
@ -0,0 +1,146 @@
|
||||
# JoinIR Design Map(現役の地図)
|
||||
|
||||
Status: Active
|
||||
Scope: JoinIR の「Loop/If を JoinIR 化して MIR に統合する」導線(検出→shape guard→lower→merge→契約検証)
|
||||
Related:
|
||||
- SSOT: [`docs/development/current/main/joinir-architecture-overview.md`](../joinir-architecture-overview.md)
|
||||
- SSOT: [`docs/development/current/main/loop_pattern_space.md`](../loop_pattern_space.md)
|
||||
- SSOT: [`docs/development/current/main/joinir-boundary-builder-pattern.md`](../joinir-boundary-builder-pattern.md)
|
||||
|
||||
このドキュメントは Phase ログではなく、「JoinIR を触る人が迷子にならず、どこを直すべきかが一発で分かる」ための設計図(地図)です。
|
||||
詳細な経緯・作業ログは `docs/development/current/main/phases/` と `docs/development/current/main/investigations/` に分離します。
|
||||
|
||||
---
|
||||
|
||||
## 1枚図: レイヤー(AST → JoinIR → MIR → Backend)
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
A[AST] -->|Frontend lowering| J1[JoinIR (Structured)]
|
||||
J1 -->|Normalize / Shape guard| J2[JoinIR (Normalized)]
|
||||
J2 -->|JoinIR → MIR bridge| M1[MIR Module]
|
||||
M1 -->|Merge into host function| M2[MIR (in builder)]
|
||||
M2 --> B[Backend\n(VM / LLVM / Cranelift)]
|
||||
|
||||
subgraph Frontend
|
||||
A
|
||||
J1
|
||||
end
|
||||
|
||||
subgraph JoinIR Core
|
||||
J2
|
||||
end
|
||||
|
||||
subgraph MIR Builder
|
||||
M1
|
||||
M2
|
||||
end
|
||||
```
|
||||
|
||||
読み方:
|
||||
- 「Loop/If の形が認識されない」: Pattern 検出(Feature/Kind)と shape guard を見る
|
||||
- 「JoinIR は生成できるが統合で壊れる」: Merge(ValueId/PHI/ExitLine/Boundary)と契約検証を見る
|
||||
- 「なぜこのエラータグが出たか」: ErrorTags(SSOT)を起点に呼び出し元へ辿る
|
||||
|
||||
---
|
||||
|
||||
## “箱”の責務マップ(担当境界)
|
||||
|
||||
| 領域 | 役割(何を決めるか) | 主な入口/箱(SSOT寄り) | 主な出力 | Fail-Fast(典型) |
|
||||
|---|---|---|---|---|
|
||||
| Pattern検出 | ループ形を分類し、どの lowerer に渡すか決める | [`src/mir/builder/control_flow/joinir/patterns/router.rs`](../../../../../src/mir/builder/control_flow/joinir/patterns/router.rs), [`src/mir/builder/control_flow/joinir/patterns/ast_feature_extractor.rs`](../../../../../src/mir/builder/control_flow/joinir/patterns/ast_feature_extractor.rs), [`src/mir/loop_pattern_detection/mod.rs`](../../../../../src/mir/loop_pattern_detection/mod.rs) | `LoopPatternKind` / Pattern 選択 | 「分類不能」→ 明示的に Err(サイレントな非JoinIR退避は禁止) |
|
||||
| shape guard | 「この shape なら lower/merge 契約が成立する」を保証する | [`src/mir/join_ir/normalized/shape_guard.rs`](../../../../../src/mir/join_ir/normalized/shape_guard.rs), `src/mir/builder/control_flow/joinir/patterns/*_validator.rs` | shape OK / 詳細診断 | shape 不一致を握りつぶさず Err |
|
||||
| lowering | JoinIR(Structured/Normalized)を生成する | [`src/mir/join_ir/lowering/mod.rs`](../../../../../src/mir/join_ir/lowering/mod.rs), `src/mir/builder/control_flow/joinir/patterns/pattern*_*.rs` | `JoinModule` | 未対応の構造は `error_tags::freeze(...)` 等で Err |
|
||||
| merge | JoinIR→MIR 変換後、ホスト関数に統合する | [`src/mir/builder/control_flow/joinir/patterns/conversion_pipeline.rs`](../../../../../src/mir/builder/control_flow/joinir/patterns/conversion_pipeline.rs), [`src/mir/builder/control_flow/joinir/merge/mod.rs`](../../../../../src/mir/builder/control_flow/joinir/merge/mod.rs) | ホスト MIR のブロック/ValueId 更新 | ValueId 競合、ExitLine 未接続、PHI 破綻を Err |
|
||||
| ExitMeta | 「出口でどの carrier をどの host slot に戻すか」のメタ | [`src/mir/join_ir/lowering/carrier_info.rs`](../../../../../src/mir/join_ir/lowering/carrier_info.rs), [`src/mir/builder/control_flow/joinir/merge/exit_line/meta_collector.rs`](../../../../../src/mir/builder/control_flow/joinir/merge/exit_line/meta_collector.rs) | `ExitMeta` / `exit_bindings` | carrier 不整合(不足/過剰)を Err |
|
||||
| CarrierInit | carrier 初期化の SSOT(FromHost/Const/LoopLocal) | [`src/mir/builder/control_flow/joinir/merge/carrier_init_builder.rs`](../../../../../src/mir/builder/control_flow/joinir/merge/carrier_init_builder.rs), [`src/mir/join_ir/lowering/carrier_info.rs`](../../../../../src/mir/join_ir/lowering/carrier_info.rs) | 初期値 `ValueId` | 初期化経路の分岐が散らばらない(SSOT を使う) |
|
||||
| ErrorTags | エラータグ整形の SSOT(検索性・一貫性) | [`src/mir/join_ir/lowering/error_tags.rs`](../../../../../src/mir/join_ir/lowering/error_tags.rs) | 文字列タグ | 文字列ハードコードを避け、タグを一元化 |
|
||||
|
||||
注:
|
||||
- Pattern 検出は「関数名 by-name 分岐」に依存しない(構造で決める)。必要なら dev/診断限定のガードに閉じ込める。
|
||||
- shape guard は「OK なら後工程が前提にできる契約」を固定する場所で、曖昧な許容をしない。
|
||||
|
||||
---
|
||||
|
||||
## 入口(コード側のエントリポイント)
|
||||
|
||||
### Loop(builder 側の導線)
|
||||
|
||||
- Router(builder 入口): [`src/mir/builder/control_flow/joinir/routing.rs`](../../../../../src/mir/builder/control_flow/joinir/routing.rs)
|
||||
- `MirBuilder::try_cf_loop_joinir(...)`(JoinIR ルートへ入る最初の関数)
|
||||
- `MirBuilder::cf_loop_joinir_impl(...)`(pattern router → legacy binding の順)
|
||||
- Pattern router(テーブル駆動): [`src/mir/builder/control_flow/joinir/patterns/router.rs`](../../../../../src/mir/builder/control_flow/joinir/patterns/router.rs)
|
||||
- Feature extraction: [`src/mir/builder/control_flow/joinir/patterns/ast_feature_extractor.rs`](../../../../../src/mir/builder/control_flow/joinir/patterns/ast_feature_extractor.rs)
|
||||
- Pattern 実体(代表):
|
||||
- [`src/mir/builder/control_flow/joinir/patterns/pattern1_minimal.rs`](../../../../../src/mir/builder/control_flow/joinir/patterns/pattern1_minimal.rs)
|
||||
- [`src/mir/builder/control_flow/joinir/patterns/pattern2_with_break.rs`](../../../../../src/mir/builder/control_flow/joinir/patterns/pattern2_with_break.rs)
|
||||
- [`src/mir/builder/control_flow/joinir/patterns/pattern3_with_if_phi.rs`](../../../../../src/mir/builder/control_flow/joinir/patterns/pattern3_with_if_phi.rs)
|
||||
- [`src/mir/builder/control_flow/joinir/patterns/pattern4_with_continue.rs`](../../../../../src/mir/builder/control_flow/joinir/patterns/pattern4_with_continue.rs)
|
||||
- [`src/mir/builder/control_flow/joinir/patterns/pattern5_infinite_early_exit.rs`](../../../../../src/mir/builder/control_flow/joinir/patterns/pattern5_infinite_early_exit.rs)
|
||||
- 変換パイプライン(JoinIR→MIR→Merge の統一導線):
|
||||
- [`src/mir/builder/control_flow/joinir/patterns/conversion_pipeline.rs`](../../../../../src/mir/builder/control_flow/joinir/patterns/conversion_pipeline.rs)
|
||||
- Merge(統合の本体): [`src/mir/builder/control_flow/joinir/merge/mod.rs`](../../../../../src/mir/builder/control_flow/joinir/merge/mod.rs)
|
||||
- ExitLine: [`src/mir/builder/control_flow/joinir/merge/exit_line/mod.rs`](../../../../../src/mir/builder/control_flow/joinir/merge/exit_line/mod.rs)
|
||||
- Merge の契約検証(debug): [`src/mir/builder/control_flow/joinir/merge/contract_checks.rs`](../../../../../src/mir/builder/control_flow/joinir/merge/contract_checks.rs)
|
||||
|
||||
### JoinIR(IR/正規化/ブリッジ)
|
||||
|
||||
- JoinIR 定義・入口: [`src/mir/join_ir/mod.rs`](../../../../../src/mir/join_ir/mod.rs)
|
||||
- Normalized / shape guard: [`src/mir/join_ir/normalized/shape_guard.rs`](../../../../../src/mir/join_ir/normalized/shape_guard.rs)
|
||||
- JoinIR → MIR bridge: [`src/mir/join_ir_vm_bridge/mod.rs`](../../../../../src/mir/join_ir_vm_bridge/mod.rs)
|
||||
|
||||
### 共通(診断とタグ)
|
||||
|
||||
- Trace(JoinIR ルートの統一トレース): [`src/mir/builder/control_flow/joinir/trace.rs`](../../../../../src/mir/builder/control_flow/joinir/trace.rs)
|
||||
- Error tags(SSOT): [`src/mir/join_ir/lowering/error_tags.rs`](../../../../../src/mir/join_ir/lowering/error_tags.rs)
|
||||
|
||||
---
|
||||
|
||||
## 不変条件(Fail-Fast)
|
||||
|
||||
JoinIR を触るときは、次を破ったら「即エラーで止める」前提で設計・実装する。
|
||||
|
||||
### 形状(shape)
|
||||
|
||||
- Pattern は「認識できる shape だけ」を通し、曖昧な許容をしない。
|
||||
- 形状が合わないときは `Ok(None)` で静かに進めない(非JoinIRへの退避を作らない)。
|
||||
- 例外: 明確な “routing” で「別 JoinIR 経路」を選ぶのは可(同一層内での選択)。
|
||||
|
||||
### ValueId / PHI / Boundary の世界
|
||||
|
||||
- JoinIR 内部(JoinValueSpace 等)と host MIR builder の ValueId を混ぜない。
|
||||
- Boundary は JoinIR↔host の橋渡し契約:
|
||||
- `join_inputs` と `host_inputs` の対応が明示される
|
||||
- Exit 側は “carrier 名” をキーにして reconnection される(ExitMeta/exit_bindings)
|
||||
- PHI は「誰が確保するか」を固定し、衝突を許さない(PHI dst の予約・再利用禁止)。
|
||||
|
||||
### ExitLine 契約
|
||||
|
||||
- ExitLine は「出口へ集約する」ための契約であり、未接続の経路を残さない。
|
||||
- carrier/slot の不足・余剰・不整合は `error_tags::exit_line_contract(...)` 等で即エラーにする。
|
||||
|
||||
---
|
||||
|
||||
## 追加手順チェックリスト(新しいループ形を飲み込む最小手順)
|
||||
|
||||
「新しいループ形」を JoinIR で扱えるようにするときの最小手順。
|
||||
|
||||
1. Fixture を追加(再現可能に固定)
|
||||
- `apps/tests/` または `apps/smokes/` に最小の `.hako` を追加(対象形が一目で分かるもの)
|
||||
2. Pattern/feature を追加(検出)
|
||||
- `src/mir/builder/control_flow/joinir/patterns/ast_feature_extractor.rs`(必要なら feature 抽出を拡張)
|
||||
- `src/mir/loop_pattern_detection/`(分類/補助解析が必要ならここに追加)
|
||||
3. shape guard を追加(契約の固定)
|
||||
- 形状・前提条件を validator として分離し、失敗は Err にする
|
||||
4. lower を追加(JoinIR を生成)
|
||||
- 既存 pattern のコピーではなく、「今回の shape が要求する最小構成」にする
|
||||
- エラーは `src/mir/join_ir/lowering/error_tags.rs` を使いタグを固定する
|
||||
5. merge/ExitLine を接続(契約が満たされるように)
|
||||
- carrier/ExitMeta/Boundary が揃っているか確認する
|
||||
6. Tests を追加(仕様固定)
|
||||
- unit: pattern/validator/merge の局所テスト
|
||||
- smoke: `tools/smokes/v2/` の profile に軽いケースを追加(quick を重くしない)
|
||||
7. Docs を更新(地図を更新)
|
||||
- `docs/development/current/main/loop_pattern_space.md`(パターン空間に追記が必要なら)
|
||||
- `docs/development/current/main/joinir-architecture-overview.md`(箱/契約が増えたなら)
|
||||
- 本ファイル(入口・責務マップの更新)
|
||||
@ -125,19 +125,28 @@ def lower_binop(
|
||||
# pointer present?
|
||||
is_ptr_side = (hasattr(lhs_raw, 'type') and isinstance(lhs_raw.type, ir.PointerType)) or \
|
||||
(hasattr(rhs_raw, 'type') and isinstance(rhs_raw.type, ir.PointerType))
|
||||
# Phase 131-6 FIX: Do NOT use dst_type hint from MIR JSON!
|
||||
# The dst_type is a forward-looking type hint that may be incorrect
|
||||
# (e.g., "i + 1" gets StringBox hint because i is later used as string in print(i))
|
||||
# We should only do string concat if operands are actually strings.
|
||||
#
|
||||
# OLD CODE (REMOVED):
|
||||
# force_string = False
|
||||
# try:
|
||||
# if isinstance(dst_type, dict) and dst_type.get('kind') == 'handle' and dst_type.get('box_type') == 'StringBox':
|
||||
# force_string = True
|
||||
# except Exception:
|
||||
# pass
|
||||
|
||||
# Phase 131-11-E: Use dst_type as authoritative hint
|
||||
# After Phase 131-11-E, dst_type is correctly set by BinOp re-propagation
|
||||
# - "i64" means integer arithmetic (even if operands are unknown)
|
||||
# - {"kind": "handle", "box_type": "StringBox"} means string concat
|
||||
# - None/missing means fallback to operand analysis
|
||||
explicit_integer = False
|
||||
explicit_string = False
|
||||
try:
|
||||
if dst_type == "i64":
|
||||
explicit_integer = True
|
||||
elif isinstance(dst_type, dict) and dst_type.get('kind') == 'handle' and dst_type.get('box_type') == 'StringBox':
|
||||
explicit_string = True
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# If explicit type hint is present, use it
|
||||
if explicit_integer:
|
||||
is_str = False
|
||||
elif explicit_string:
|
||||
is_str = True
|
||||
else:
|
||||
# Phase 196: TypeFacts SSOT - Only check for actual string types (not use-site demands)
|
||||
# Check if BOTH operands are known to be strings from their definition
|
||||
any_tagged = False
|
||||
@ -159,11 +168,11 @@ def lower_binop(
|
||||
pass
|
||||
is_str = is_ptr_side or any_tagged
|
||||
|
||||
# Phase 131-6 DEBUG
|
||||
# Phase 131-11-E DEBUG
|
||||
if os.environ.get('NYASH_BINOP_DEBUG') == '1':
|
||||
print(f"[binop +] lhs={lhs} rhs={rhs} dst={dst}")
|
||||
print(f" dst_type={dst_type} explicit_integer={explicit_integer} explicit_string={explicit_string}")
|
||||
print(f" is_ptr_side={is_ptr_side} any_tagged={any_tagged} is_str={is_str}")
|
||||
print(f" dst_type={dst_type}")
|
||||
if is_str:
|
||||
# Helper: convert raw or resolved value to string handle
|
||||
def to_handle(raw, val, tag: str, vid: int):
|
||||
|
||||
@ -385,8 +385,12 @@ impl super::MirBuilder {
|
||||
}
|
||||
}
|
||||
|
||||
// Phase 131-11-E: BinOp type re-propagation after PHI resolution
|
||||
// After PHI types are corrected, re-infer BinOp result types
|
||||
self.repropagate_binop_types(&mut function);
|
||||
|
||||
// Phase 131-9: Update function metadata with corrected types
|
||||
// MUST happen after PHI type correction above
|
||||
// MUST happen after PHI type correction above AND BinOp re-propagation
|
||||
function.metadata.value_types = self.value_types.clone();
|
||||
|
||||
// Phase 82-5: lifecycle.rs バグ修正 - terminator の Return のみをチェック
|
||||
@ -595,4 +599,83 @@ impl super::MirBuilder {
|
||||
|
||||
Ok(module)
|
||||
}
|
||||
|
||||
// Phase 131-11-E: Re-propagate BinOp result types after PHI resolution
|
||||
// This fixes cases where BinOp instructions were created before PHI types were known
|
||||
fn repropagate_binop_types(&mut self, function: &mut super::MirFunction) {
|
||||
use crate::mir::MirInstruction;
|
||||
use crate::mir::MirType;
|
||||
|
||||
let mut binop_updates: Vec<(super::ValueId, MirType)> = Vec::new();
|
||||
|
||||
for (_bid, bb) in function.blocks.iter() {
|
||||
for inst in bb.instructions.iter() {
|
||||
if let MirInstruction::BinOp { dst, op, lhs, rhs } = inst {
|
||||
// Only handle Add operations (string concat vs numeric addition)
|
||||
if matches!(op, crate::mir::BinaryOp::Add) {
|
||||
// Get current lhs/rhs types after PHI resolution
|
||||
let lhs_type = self.value_types.get(lhs);
|
||||
let rhs_type = self.value_types.get(rhs);
|
||||
|
||||
// Classify types
|
||||
let lhs_class = match lhs_type {
|
||||
Some(MirType::String) => OperandTypeClass::String,
|
||||
Some(MirType::Box(bt)) if bt == "StringBox" => OperandTypeClass::String,
|
||||
Some(MirType::Integer) => OperandTypeClass::Integer,
|
||||
Some(MirType::Bool) => OperandTypeClass::Integer,
|
||||
_ => OperandTypeClass::Unknown,
|
||||
};
|
||||
let rhs_class = match rhs_type {
|
||||
Some(MirType::String) => OperandTypeClass::String,
|
||||
Some(MirType::Box(bt)) if bt == "StringBox" => OperandTypeClass::String,
|
||||
Some(MirType::Integer) => OperandTypeClass::Integer,
|
||||
Some(MirType::Bool) => OperandTypeClass::Integer,
|
||||
_ => OperandTypeClass::Unknown,
|
||||
};
|
||||
|
||||
use OperandTypeClass::*;
|
||||
let new_type = match (lhs_class, rhs_class) {
|
||||
(String, String) => Some(MirType::Box("StringBox".to_string())),
|
||||
(Integer, Integer) | (Integer, Unknown) | (Unknown, Integer) => {
|
||||
Some(MirType::Integer)
|
||||
}
|
||||
_ => None, // Keep Unknown for mixed/unclear cases
|
||||
};
|
||||
|
||||
if let Some(new_ty) = new_type {
|
||||
// Check if type is missing or different
|
||||
let current_type = self.value_types.get(dst);
|
||||
if current_type.is_none() || current_type != Some(&new_ty) {
|
||||
binop_updates.push((*dst, new_ty));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Other arithmetic ops: always Integer
|
||||
if !self.value_types.contains_key(dst) {
|
||||
binop_updates.push((*dst, MirType::Integer));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Apply updates
|
||||
for (dst, ty) in binop_updates {
|
||||
if std::env::var("NYASH_BINOP_REPROP_DEBUG").is_ok() {
|
||||
eprintln!(
|
||||
"[binop-reprop] {} updated {:?} -> {:?}",
|
||||
function.signature.name, dst, ty
|
||||
);
|
||||
}
|
||||
self.value_types.insert(dst, ty);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Phase 131-11-E: OperandTypeClass for BinOp type inference
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
enum OperandTypeClass {
|
||||
String,
|
||||
Integer,
|
||||
Unknown,
|
||||
}
|
||||
|
||||
@ -10,7 +10,40 @@ enum BinaryOpType {
|
||||
Comparison(CompareOp),
|
||||
}
|
||||
|
||||
// Phase 131-11-E: TypeFacts - operand type classification
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
enum OperandTypeClass {
|
||||
String,
|
||||
Integer,
|
||||
Unknown,
|
||||
}
|
||||
|
||||
impl super::MirBuilder {
|
||||
// Phase 131-11-E: TypeFacts - classify operand type for BinOp inference
|
||||
fn classify_operand_type(&self, vid: ValueId) -> OperandTypeClass {
|
||||
let result = match self.value_types.get(&vid) {
|
||||
Some(MirType::String) => OperandTypeClass::String,
|
||||
Some(MirType::Box(bt)) if bt == "StringBox" => OperandTypeClass::String,
|
||||
Some(MirType::Integer) => OperandTypeClass::Integer,
|
||||
Some(MirType::Bool) => OperandTypeClass::Integer, // Bool can be used as integer
|
||||
_ => {
|
||||
// Check value_origin_newbox for StringBox
|
||||
if self
|
||||
.value_origin_newbox
|
||||
.get(&vid)
|
||||
.map(|s| s == "StringBox")
|
||||
.unwrap_or(false)
|
||||
{
|
||||
return OperandTypeClass::String;
|
||||
}
|
||||
OperandTypeClass::Unknown
|
||||
}
|
||||
};
|
||||
if std::env::var("NYASH_TYPEFACTS_DEBUG").is_ok() {
|
||||
eprintln!("[typefacts] classify {:?} -> {:?}", vid, result);
|
||||
}
|
||||
result
|
||||
}
|
||||
// Build a binary operation
|
||||
pub(super) fn build_binary_op(
|
||||
&mut self,
|
||||
@ -68,35 +101,33 @@ impl super::MirBuilder {
|
||||
vec![lhs, rhs],
|
||||
)?;
|
||||
// Phase 196: TypeFacts SSOT - AddOperator call type annotation
|
||||
let lhs_is_str = match self.value_types.get(&lhs) {
|
||||
Some(MirType::String) => true,
|
||||
Some(MirType::Box(bt)) if bt == "StringBox" => true,
|
||||
_ => self
|
||||
.value_origin_newbox
|
||||
.get(&lhs)
|
||||
.map(|s| s == "StringBox")
|
||||
.unwrap_or(false),
|
||||
};
|
||||
let rhs_is_str = match self.value_types.get(&rhs) {
|
||||
Some(MirType::String) => true,
|
||||
Some(MirType::Box(bt)) if bt == "StringBox" => true,
|
||||
_ => self
|
||||
.value_origin_newbox
|
||||
.get(&rhs)
|
||||
.map(|s| s == "StringBox")
|
||||
.unwrap_or(false),
|
||||
};
|
||||
if lhs_is_str && rhs_is_str {
|
||||
// Phase 131-11-E: TypeFacts - classify operand types
|
||||
let lhs_type = self.classify_operand_type(lhs);
|
||||
let rhs_type = self.classify_operand_type(rhs);
|
||||
|
||||
use OperandTypeClass::*;
|
||||
match (lhs_type, rhs_type) {
|
||||
(String, String) => {
|
||||
// BOTH are strings: result is string
|
||||
self.value_types
|
||||
.insert(dst, MirType::Box("StringBox".to_string()));
|
||||
self.value_origin_newbox
|
||||
.insert(dst, "StringBox".to_string());
|
||||
} else if !lhs_is_str && !rhs_is_str {
|
||||
// NEITHER is a string: numeric addition
|
||||
}
|
||||
(Integer, Integer) | (Integer, Unknown) | (Unknown, Integer) => {
|
||||
// TypeFact: Integer + anything non-String = Integer
|
||||
self.value_types.insert(dst, MirType::Integer);
|
||||
}
|
||||
// else: Mixed - leave Unknown for use-site coercion
|
||||
(String, Integer) | (Integer, String) => {
|
||||
// Mixed types: leave as Unknown for use-site coercion
|
||||
}
|
||||
(Unknown, Unknown) => {
|
||||
// Both Unknown: cannot infer
|
||||
}
|
||||
(String, Unknown) | (Unknown, String) => {
|
||||
// One side is String, other is Unknown: cannot infer safely
|
||||
}
|
||||
}
|
||||
} else if all_call {
|
||||
// Lower other arithmetic ops to operator boxes under ALL flag
|
||||
let (name, guard_prefix) = match op {
|
||||
@ -158,37 +189,36 @@ impl super::MirBuilder {
|
||||
// Phase 196: TypeFacts SSOT - BinOp type is determined by operands only
|
||||
// String concatenation is handled at use-site in LLVM lowering
|
||||
if matches!(op, crate::mir::BinaryOp::Add) {
|
||||
// Check if BOTH operands are known to be strings (TypeFacts)
|
||||
let lhs_is_str = match self.value_types.get(&lhs) {
|
||||
Some(MirType::String) => true,
|
||||
Some(MirType::Box(bt)) if bt == "StringBox" => true,
|
||||
_ => self
|
||||
.value_origin_newbox
|
||||
.get(&lhs)
|
||||
.map(|s| s == "StringBox")
|
||||
.unwrap_or(false),
|
||||
};
|
||||
let rhs_is_str = match self.value_types.get(&rhs) {
|
||||
Some(MirType::String) => true,
|
||||
Some(MirType::Box(bt)) if bt == "StringBox" => true,
|
||||
_ => self
|
||||
.value_origin_newbox
|
||||
.get(&rhs)
|
||||
.map(|s| s == "StringBox")
|
||||
.unwrap_or(false),
|
||||
};
|
||||
if lhs_is_str && rhs_is_str {
|
||||
// Phase 131-11-E: TypeFacts - classify operand types
|
||||
let lhs_type = self.classify_operand_type(lhs);
|
||||
let rhs_type = self.classify_operand_type(rhs);
|
||||
|
||||
use OperandTypeClass::*;
|
||||
match (lhs_type, rhs_type) {
|
||||
(String, String) => {
|
||||
// BOTH are strings: result is definitely a string
|
||||
self.value_types
|
||||
.insert(dst, MirType::Box("StringBox".to_string()));
|
||||
self.value_origin_newbox
|
||||
.insert(dst, "StringBox".to_string());
|
||||
} else if !lhs_is_str && !rhs_is_str {
|
||||
// NEITHER is a string: numeric addition
|
||||
}
|
||||
(Integer, Integer) | (Integer, Unknown) | (Unknown, Integer) => {
|
||||
// TypeFact: Integer + anything non-String = Integer
|
||||
// This handles `counter + 1` where counter might be Unknown
|
||||
self.value_types.insert(dst, MirType::Integer);
|
||||
}
|
||||
// else: Mixed types (string + int or int + string)
|
||||
// Leave dst type as Unknown - LLVM will handle coercion at use-site
|
||||
(String, Integer) | (Integer, String) => {
|
||||
// Mixed types: leave as Unknown for use-site coercion
|
||||
// LLVM backend will handle string concatenation
|
||||
}
|
||||
(Unknown, Unknown) => {
|
||||
// Both Unknown: cannot infer, leave as Unknown
|
||||
}
|
||||
(String, Unknown) | (Unknown, String) => {
|
||||
// One side is String, other is Unknown: cannot infer safely
|
||||
// Leave as Unknown
|
||||
}
|
||||
}
|
||||
} else {
|
||||
self.value_types.insert(dst, MirType::Integer);
|
||||
}
|
||||
|
||||
@ -343,25 +343,24 @@ pub fn emit_mir_json_for_harness(
|
||||
B::Or => "|",
|
||||
};
|
||||
let mut obj = json!({"op":"binop","operation": op_s, "lhs": lhs.as_u32(), "rhs": rhs.as_u32(), "dst": dst.as_u32()});
|
||||
// dst_type hint for string concatenation: ONLY if BOTH sides are explicitly String-ish and op is '+', mark result as String handle
|
||||
// Option C: Unknown/None types default to Integer arithmetic (conservative)
|
||||
// Phase 131-11-E: dst_type hint based on RESULT type (not operand types)
|
||||
// Use the dst type from metadata, which has been corrected by repropagate_binop_types
|
||||
if matches!(op, B::Add) {
|
||||
let lhs_is_str = match f.metadata.value_types.get(lhs) {
|
||||
Some(MirType::String) => true,
|
||||
Some(MirType::Box(bt)) if bt == "StringBox" => true,
|
||||
_ => false,
|
||||
};
|
||||
let rhs_is_str = match f.metadata.value_types.get(rhs) {
|
||||
Some(MirType::String) => true,
|
||||
Some(MirType::Box(bt)) if bt == "StringBox" => true,
|
||||
_ => false,
|
||||
};
|
||||
// Changed: require BOTH to be explicitly String (lhs_is_str && rhs_is_str)
|
||||
// Default: Unknown → Integer arithmetic
|
||||
if lhs_is_str && rhs_is_str {
|
||||
let dst_type = f.metadata.value_types.get(dst);
|
||||
match dst_type {
|
||||
Some(MirType::Box(bt)) if bt == "StringBox" => {
|
||||
obj["dst_type"] =
|
||||
json!({"kind":"handle","box_type":"StringBox"});
|
||||
}
|
||||
Some(MirType::Integer) => {
|
||||
// Explicitly mark as i64 for integer addition
|
||||
obj["dst_type"] = json!("i64");
|
||||
}
|
||||
_ => {
|
||||
// Unknown/other: default to i64 (conservative)
|
||||
obj["dst_type"] = json!("i64");
|
||||
}
|
||||
}
|
||||
}
|
||||
insts.push(obj);
|
||||
}
|
||||
@ -736,24 +735,24 @@ pub fn emit_mir_json_for_harness_bin(
|
||||
B::Or => "|",
|
||||
};
|
||||
let mut obj = json!({"op":"binop","operation": op_s, "lhs": lhs.as_u32(), "rhs": rhs.as_u32(), "dst": dst.as_u32()});
|
||||
// Option C: Unknown/None types default to Integer arithmetic (conservative)
|
||||
// Phase 131-11-E: dst_type hint based on RESULT type (not operand types)
|
||||
// Use the dst type from metadata, which has been corrected by repropagate_binop_types
|
||||
if matches!(op, B::Add) {
|
||||
let lhs_is_str = match f.metadata.value_types.get(lhs) {
|
||||
Some(MirType::String) => true,
|
||||
Some(MirType::Box(bt)) if bt == "StringBox" => true,
|
||||
_ => false,
|
||||
};
|
||||
let rhs_is_str = match f.metadata.value_types.get(rhs) {
|
||||
Some(MirType::String) => true,
|
||||
Some(MirType::Box(bt)) if bt == "StringBox" => true,
|
||||
_ => false,
|
||||
};
|
||||
// Changed: require BOTH to be explicitly String (lhs_is_str && rhs_is_str)
|
||||
// Default: Unknown → Integer arithmetic
|
||||
if lhs_is_str && rhs_is_str {
|
||||
let dst_type = f.metadata.value_types.get(dst);
|
||||
match dst_type {
|
||||
Some(MirType::Box(bt)) if bt == "StringBox" => {
|
||||
obj["dst_type"] =
|
||||
json!({"kind":"handle","box_type":"StringBox"});
|
||||
}
|
||||
Some(MirType::Integer) => {
|
||||
// Explicitly mark as i64 for integer addition
|
||||
obj["dst_type"] = json!("i64");
|
||||
}
|
||||
_ => {
|
||||
// Unknown/other: default to i64 (conservative)
|
||||
obj["dst_type"] = json!("i64");
|
||||
}
|
||||
}
|
||||
}
|
||||
insts.push(obj);
|
||||
emitted_defs.insert(dst.as_u32());
|
||||
|
||||
Reference in New Issue
Block a user