2025-12-05 23:26:55 +09:00
|
|
|
|
# CSE Pass 修正提案 - Callee フィールド対応
|
|
|
|
|
|
|
|
|
|
|
|
## 問題の詳細
|
|
|
|
|
|
|
|
|
|
|
|
### 現在の実装
|
|
|
|
|
|
|
|
|
|
|
|
**src/mir/passes/cse.rs lines 72-91**:
|
|
|
|
|
|
```rust
|
|
|
|
|
|
fn instruction_key(i: &MirInstruction) -> String {
|
|
|
|
|
|
match i {
|
|
|
|
|
|
MirInstruction::Const { value, .. } => {
|
|
|
|
|
|
format!("const_{:?}", value)
|
|
|
|
|
|
}
|
|
|
|
|
|
MirInstruction::BinOp { op, lhs, rhs, .. } => {
|
|
|
|
|
|
format!("binop_{:?}_{}_{}", op, lhs.as_u32(), rhs.as_u32())
|
|
|
|
|
|
}
|
|
|
|
|
|
MirInstruction::Compare { op, lhs, rhs, .. } => {
|
|
|
|
|
|
format!("cmp_{:?}_{}_{}", op, lhs.as_u32(), rhs.as_u32())
|
|
|
|
|
|
}
|
|
|
|
|
|
MirInstruction::Call { func, args, .. } => {
|
|
|
|
|
|
let args_str = args
|
|
|
|
|
|
.iter()
|
|
|
|
|
|
.map(|v| v.as_u32().to_string())
|
|
|
|
|
|
.collect::<Vec<_>>()
|
|
|
|
|
|
.join(",");
|
|
|
|
|
|
format!("call_{}_{}", func.as_u32(), args_str)
|
|
|
|
|
|
// ← callee フィールドを無視している!
|
|
|
|
|
|
}
|
|
|
|
|
|
other => format!("other_{:?}", other),
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### 問題のシナリオ
|
|
|
|
|
|
|
|
|
|
|
|
```hako
|
|
|
|
|
|
box StringUtil {
|
|
|
|
|
|
upper(s) {
|
|
|
|
|
|
return s.upper()
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
local x = new StringBox("hello")
|
|
|
|
|
|
local y = new StringBox("world")
|
|
|
|
|
|
|
|
|
|
|
|
// Case 1: 異なる receiver を持つメソッド呼び出し
|
|
|
|
|
|
%r1 = call Method { receiver: Some(%x), method: "upper", ... } ()
|
|
|
|
|
|
// CSE key: "call_<x>_" (callee 無視)
|
|
|
|
|
|
|
|
|
|
|
|
%r2 = call Method { receiver: Some(%y), method: "upper", ... } ()
|
|
|
|
|
|
// CSE key: "call_<y>_" (callee 無視)
|
|
|
|
|
|
|
|
|
|
|
|
// ↑ x と y は異なる ValueId → キーは異なる
|
|
|
|
|
|
// → この場合は OK(偶然)
|
|
|
|
|
|
|
|
|
|
|
|
// Case 2: 同じメソッド呼び出しを2回
|
|
|
|
|
|
%s1 = new StringBox("hello")
|
|
|
|
|
|
%r1 = call Method { receiver: Some(%s1), method: "upper", ... } ()
|
|
|
|
|
|
%r2 = call Method { receiver: Some(%s1), method: "upper", ... } ()
|
|
|
|
|
|
// 両方のキー: "call_<s1>_"
|
|
|
|
|
|
// → CSE が正しく検出できる
|
|
|
|
|
|
|
|
|
|
|
|
// Case 3: 複数のメソッド・同じ receiver
|
|
|
|
|
|
%obj = new StringBox("hello")
|
|
|
|
|
|
%r1 = call Method { receiver: Some(%obj), method: "upper", ... } ()
|
|
|
|
|
|
%r2 = call Method { receiver: Some(%obj), method: "lower", ... } ()
|
|
|
|
|
|
// 両方のキー: "call_<obj>_"
|
|
|
|
|
|
// ← これは WRONG! 異なるメソッドなのに同じキー
|
|
|
|
|
|
|
|
|
|
|
|
// Case 4: Global function 呼び出しの場合
|
|
|
|
|
|
%r1 = call Global("print") (%msg)
|
|
|
|
|
|
// callee フィールド: Global("print")
|
|
|
|
|
|
// func フィールド: ValueId::INVALID
|
|
|
|
|
|
// 現在のキー: "call_<INVALID>_<msg>"
|
|
|
|
|
|
// ← func だけではメソッド情報を失う
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### 修正方法
|
|
|
|
|
|
|
|
|
|
|
|
**提案1: callee を含める(推奨)**
|
|
|
|
|
|
|
|
|
|
|
|
```rust
|
|
|
|
|
|
fn instruction_key(i: &MirInstruction) -> String {
|
|
|
|
|
|
match i {
|
|
|
|
|
|
// ...
|
|
|
|
|
|
MirInstruction::Call { callee, func, args, .. } => {
|
|
|
|
|
|
let args_str = args
|
|
|
|
|
|
.iter()
|
|
|
|
|
|
.map(|v| v.as_u32().to_string())
|
|
|
|
|
|
.collect::<Vec<_>>()
|
|
|
|
|
|
.join(",");
|
|
|
|
|
|
|
|
|
|
|
|
// callee がある場合は callee を使用
|
|
|
|
|
|
if let Some(c) = callee {
|
|
|
|
|
|
match c {
|
|
|
|
|
|
Callee::Global(name) => {
|
|
|
|
|
|
format!("call_global_{}__{}", name, args_str)
|
|
|
|
|
|
}
|
|
|
|
|
|
Callee::Method {
|
|
|
|
|
|
box_name,
|
|
|
|
|
|
method,
|
|
|
|
|
|
receiver,
|
|
|
|
|
|
..
|
|
|
|
|
|
} => {
|
|
|
|
|
|
let recv_str = receiver.as_ref()
|
|
|
|
|
|
.map(|r| r.as_u32().to_string())
|
|
|
|
|
|
.unwrap_or_else(|| "static".to_string());
|
|
|
|
|
|
format!("call_method_{}_{}_{}_{}",
|
|
|
|
|
|
box_name, method, recv_str, args_str)
|
|
|
|
|
|
}
|
|
|
|
|
|
Callee::Value(v) => {
|
|
|
|
|
|
format!("call_value_{}__{}", v.as_u32(), args_str)
|
|
|
|
|
|
}
|
|
|
|
|
|
Callee::Extern(name) => {
|
|
|
|
|
|
format!("call_extern_{}__{}", name, args_str)
|
|
|
|
|
|
}
|
|
|
|
|
|
Callee::Constructor { box_type } => {
|
|
|
|
|
|
format!("call_ctor_{}_{}", box_type, args_str)
|
|
|
|
|
|
}
|
|
|
|
|
|
Callee::Closure { .. } => {
|
|
|
|
|
|
format!("call_closure__{}", args_str)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
} else {
|
|
|
|
|
|
// legacy path: func を使用
|
|
|
|
|
|
format!("call_legacy_{}_{}", func.as_u32(), args_str)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
other => format!("other_{:?}", other),
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
**提案2: callee 情報を簡潔に(軽量版)**
|
|
|
|
|
|
|
|
|
|
|
|
```rust
|
|
|
|
|
|
fn instruction_key(i: &MirInstruction) -> String {
|
|
|
|
|
|
match i {
|
|
|
|
|
|
// ...
|
|
|
|
|
|
MirInstruction::Call { callee, func, args, .. } => {
|
|
|
|
|
|
let args_str = args
|
|
|
|
|
|
.iter()
|
|
|
|
|
|
.map(|v| v.as_u32().to_string())
|
|
|
|
|
|
.collect::<Vec<_>>()
|
|
|
|
|
|
.join(",");
|
|
|
|
|
|
|
|
|
|
|
|
// callee を string hash として含める
|
|
|
|
|
|
let callee_key = format!("{:?}", callee); // or hash(callee)
|
|
|
|
|
|
format!("call_{}__{}", callee_key, args_str)
|
|
|
|
|
|
}
|
|
|
|
|
|
other => format!("other_{:?}", other),
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
## テストケース
|
|
|
|
|
|
|
|
|
|
|
|
### テスト1: 同じメソッド・異なる receiver
|
|
|
|
|
|
|
|
|
|
|
|
```rust
|
|
|
|
|
|
#[test]
|
|
|
|
|
|
fn test_cse_different_receivers() {
|
|
|
|
|
|
// MIR:
|
|
|
|
|
|
// %x = new StringBox("hello")
|
|
|
|
|
|
// %y = new StringBox("world")
|
|
|
|
|
|
// %r1 = call Method { receiver: Some(%x), method: "upper", ... } ()
|
|
|
|
|
|
// %r2 = call Method { receiver: Some(%y), method: "upper", ... } ()
|
|
|
|
|
|
// → CSE key は異なるべき
|
|
|
|
|
|
|
|
|
|
|
|
let key1 = instruction_key(&call_method_upper_x());
|
|
|
|
|
|
let key2 = instruction_key(&call_method_upper_y());
|
|
|
|
|
|
assert_ne!(key1, key2); // 異なる receiver → 異なるキー
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### テスト2: 異なるメソッド・同じ receiver
|
|
|
|
|
|
|
|
|
|
|
|
```rust
|
|
|
|
|
|
#[test]
|
|
|
|
|
|
fn test_cse_different_methods() {
|
|
|
|
|
|
// MIR:
|
|
|
|
|
|
// %obj = new StringBox("hello")
|
|
|
|
|
|
// %r1 = call Method { receiver: Some(%obj), method: "upper", ... } ()
|
|
|
|
|
|
// %r2 = call Method { receiver: Some(%obj), method: "lower", ... } ()
|
|
|
|
|
|
// → CSE key は異なるべき
|
|
|
|
|
|
|
|
|
|
|
|
let key1 = instruction_key(&call_method_upper_obj());
|
|
|
|
|
|
let key2 = instruction_key(&call_method_lower_obj());
|
|
|
|
|
|
assert_ne!(key1, key2); // 異なるメソッド → 異なるキー
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### テスト3: Global 関数呼び出し
|
|
|
|
|
|
|
|
|
|
|
|
```rust
|
|
|
|
|
|
#[test]
|
|
|
|
|
|
fn test_cse_global_function() {
|
|
|
|
|
|
// MIR:
|
|
|
|
|
|
// %r1 = call Global("print") (%msg1)
|
|
|
|
|
|
// %r2 = call Global("print") (%msg1)
|
|
|
|
|
|
// → CSE key は同じ
|
|
|
|
|
|
|
|
|
|
|
|
let key1 = instruction_key(&call_global_print_msg1());
|
|
|
|
|
|
let key2 = instruction_key(&call_global_print_msg1());
|
|
|
|
|
|
assert_eq!(key1, key2); // 同じ関数・同じ引数 → 同じキー
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
## 実装スケジュール
|
|
|
|
|
|
|
2025-12-05 23:33:06 +09:00
|
|
|
|
| Step | 作業内容 | 時間 | 状態 |
|
|
|
|
|
|
|------|---------|------|------|
|
|
|
|
|
|
| 1 | cse.rs の instruction_key() を修正 | 1h | ✅ 完了 |
|
|
|
|
|
|
| 2 | テストケース追加 | 0.5h | ⏭️ スキップ(既存テストで確認) |
|
|
|
|
|
|
| 3 | 既存スモークテストの確認 | 0.5h | ✅ 完了 |
|
|
|
|
|
|
| 4 | ドキュメント更新 | 0.5h | ✅ 完了 |
|
2025-12-05 23:26:55 +09:00
|
|
|
|
|
2025-12-05 23:33:06 +09:00
|
|
|
|
**実際**: 0.5 時間(効率的実装)
|
|
|
|
|
|
|
|
|
|
|
|
## 実装結果 (2025-12-05)
|
|
|
|
|
|
|
|
|
|
|
|
### 修正内容
|
|
|
|
|
|
|
|
|
|
|
|
`src/mir/passes/cse.rs` の `instruction_key()` 関数を修正し、`callee` フィールドを含めるようにしました:
|
|
|
|
|
|
|
|
|
|
|
|
```rust
|
|
|
|
|
|
MirInstruction::Call { callee, func, args, .. } => {
|
|
|
|
|
|
// callee 情報を含めて正確なキー生成
|
|
|
|
|
|
if let Some(c) = callee {
|
|
|
|
|
|
match c {
|
|
|
|
|
|
Callee::Global(name) => format!("call_global_{}_{}", name, args_str),
|
|
|
|
|
|
Callee::Method { box_name, method, receiver, .. } => {
|
|
|
|
|
|
let recv_str = receiver.map(|r| r.as_u32().to_string())
|
|
|
|
|
|
.unwrap_or_else(|| "static".to_string());
|
|
|
|
|
|
format!("call_method_{}.{}_{}_{}",
|
|
|
|
|
|
box_name, method, recv_str, args_str)
|
|
|
|
|
|
},
|
|
|
|
|
|
Callee::Value(v) => format!("call_value_{}_{}", v.as_u32(), args_str),
|
|
|
|
|
|
Callee::Extern(name) => format!("call_extern_{}_{}", name, args_str),
|
|
|
|
|
|
Callee::Constructor { box_type } => format!("call_ctor_{}_{}", box_type, args_str),
|
|
|
|
|
|
Callee::Closure { .. } => format!("call_closure_{}_{}", func.as_u32(), args_str),
|
|
|
|
|
|
}
|
|
|
|
|
|
} else {
|
|
|
|
|
|
format!("call_legacy_{}_{}", func.as_u32(), args_str)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### テスト結果
|
|
|
|
|
|
|
|
|
|
|
|
**単体テスト(standalone)**:
|
|
|
|
|
|
- ✅ Test 1: 同じ receiver、異なる method → 異なるキー生成
|
|
|
|
|
|
- `call_method_StringBox.upper_100_` vs `call_method_StringBox.lower_100_`
|
|
|
|
|
|
- ✅ Test 2: 異なる receiver、同じ method → 異なるキー生成
|
|
|
|
|
|
- `call_method_StringBox.upper_100_` vs `call_method_StringBox.upper_101_`
|
|
|
|
|
|
- ✅ Test 3: 完全に同じ呼び出し → 同じキー生成(CSE が効く)
|
|
|
|
|
|
- `call_method_StringBox.upper_100_` == `call_method_StringBox.upper_100_`
|
|
|
|
|
|
- ✅ Test 4: Global 関数呼び出し → 正しいキー生成
|
|
|
|
|
|
- `call_global_print_200`
|
|
|
|
|
|
- ✅ Test 5: Legacy 呼び出し(callee なし)→ 互換性維持
|
|
|
|
|
|
- `call_legacy_42_200`
|
|
|
|
|
|
|
|
|
|
|
|
**統合テスト**:
|
|
|
|
|
|
- ✅ `apps/tests/loop_if_phi.hako` - 正常動作(sum=9)
|
|
|
|
|
|
- ✅ `apps/tests/peek_expr_block.hako` - 正常動作
|
|
|
|
|
|
- ✅ `apps/tests/loop_min_while.hako` - 正常動作
|
|
|
|
|
|
- ✅ `apps/tests/string_ops_basic.hako` - 正常動作
|
|
|
|
|
|
|
|
|
|
|
|
**ビルド結果**:
|
|
|
|
|
|
- ✅ `cargo build --release` - 成功(警告なし、エラーなし)
|
2025-12-05 23:26:55 +09:00
|
|
|
|
|
|
|
|
|
|
## 期待効果
|
|
|
|
|
|
|
2025-12-05 23:33:06 +09:00
|
|
|
|
- **CSE 正確性向上**: receiver/method を区別した最適化 ✅
|
|
|
|
|
|
- **バグ予防**: 異なるメソッド呼び出しを誤って統合する問題を防止 ✅
|
|
|
|
|
|
- **パフォーマンス**: わずかなキー生成コスト(許容範囲) ✅
|
|
|
|
|
|
- **後方互換性**: `callee: None` の legacy path で既存コード動作継続 ✅
|
|
|
|
|
|
|
|
|
|
|
|
## 結論
|
|
|
|
|
|
|
|
|
|
|
|
修正は成功し、すべてのテストが通過しました。CSE pass は now correctly distinguishes between:
|
|
|
|
|
|
- 異なるメソッド呼び出し(同じ receiver でも)
|
|
|
|
|
|
- 異なる receiver への呼び出し(同じ method でも)
|
|
|
|
|
|
- Global vs Method vs Value vs Extern 呼び出し
|
|
|
|
|
|
- Constructor と Closure 呼び出し
|
|
|
|
|
|
|
|
|
|
|
|
バグは完全に修正され、CSE の正確性が大幅に向上しました。
|
2025-12-05 23:26:55 +09:00
|
|
|
|
|