From eb10fc715912a24de281bb79281df0f80477ac56 Mon Sep 17 00:00:00 2001 From: nyash-codex Date: Fri, 21 Nov 2025 13:53:50 +0900 Subject: [PATCH] =?UTF-8?q?fix(mir):=20Phase=2025.1=20StaticCompiler=20rec?= =?UTF-8?q?eiver=E5=9E=8B=E6=8E=A8=E8=AB=96=E3=83=90=E3=82=B0=E6=A0=B9?= =?UTF-8?q?=E6=B2=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 問題 Stage-1ブリッジで `StringHelpers.skip_ws/2` 呼び出し時に: - ParserBox.length(receiver=ValueId未定義) でVM落ち - receiver が捏造される(型情報なし) ## 根本原因 1. CalleeResolver: receiver型なし→捏造ValueId生成 2. 順序問題: materialization → guard(逆であるべき) 3. 型注釈不足: String定数・BinOp結果に型情報なし ## 解決策(3 Phase) ### Phase 1: guard.rs Global フォールバック (lines 100-146) - receiver型なし→Global変換で捏造ValueId防止 - Phase 3-C: 文字列メソッド→StringBox正規化追加 ### Phase 2: unified_emitter.rs 順序反転 (lines 176-188) - guard FIRST → materialization (Method のみ) - Global 呼び出しの receiver 実体化を回避 ### Phase 3-A: emit_string 型注釈 (constant.rs:46-49) - String定数に value_types + value_origin_newbox 登録 ### Phase 3-B: BinOp(Add) 型注釈強化 (ops.rs:70-86,145-166,178-198) - value_origin_newbox もチェック(value_types のみ→両方) - 結果も両方のマップに登録 ### Phase 3-C: StaticCompiler 文字列メソッド正規化 (guard.rs:106-129) - length/substring等→StringBox.method に統一 - ParserBox.length → StringBox.length ## 検証 ✅ RC=0 達成(apps/tests/stage1_skip_ws_repro.hako) ✅ 正規化トレース確認(ParserBox→StringBox) ✅ receiver捏造完全防止 Co-Authored-By: ChatGPT5 --- src/mir/builder/calls/guard.rs | 54 +++++++++++++++++++----- src/mir/builder/calls/unified_emitter.rs | 16 ++++--- src/mir/builder/emission/constant.rs | 4 ++ src/mir/builder/ops.rs | 25 ++++++----- 4 files changed, 72 insertions(+), 27 deletions(-) diff --git a/src/mir/builder/calls/guard.rs b/src/mir/builder/calls/guard.rs index 0f07a5ad..3654a673 100644 --- a/src/mir/builder/calls/guard.rs +++ b/src/mir/builder/calls/guard.rs @@ -99,18 +99,50 @@ impl<'a> CalleeGuardBox<'a> { }); } else { // StaticCompiler Method but receiver has NO Box type - // → Pass through as ME-CALL (let downstream handle it) - if trace_enabled { - eprintln!("[static-runtime-guard] StaticCompiler receiver has NO Box type:"); - eprintln!( - " {}.{} receiver %{} has no type info", - box_name, method, recv.0 - ); - eprintln!(" → Passing through as ME-CALL (downstream will resolve)"); - } + // 🎯 Phase 3-C: Normalize StaticCompiler string methods to StringBox + // + // Common string methods that should be routed to StringBox: + // length, substring, charAt, indexOf, etc. + let is_string_method = matches!( + method.as_str(), + "length" | "substring" | "charAt" | "indexOf" | "lastIndexOf" + | "toUpperCase" | "toLowerCase" | "trim" | "split" + ); - // Pass through unchanged - let finalize_method_receiver() handle it - return Ok(callee); + if is_string_method { + if trace_enabled { + eprintln!("[static-runtime-guard] StaticCompiler string method normalization:"); + eprintln!( + " {}.{} receiver %{} has no type info", + box_name, method, recv.0 + ); + eprintln!(" → Normalize: StringBox.{}", method); + } + + // Normalize to StringBox method call + return Ok(Callee::Method { + box_name: "StringBox".to_string(), + method: method.clone(), + receiver: Some(recv), + certainty, + box_kind: CalleeBoxKind::RuntimeData, + }); + } else { + // Non-string methods: fallback to Global call + if trace_enabled { + eprintln!("[static-runtime-guard] StaticCompiler receiver has NO Box type:"); + eprintln!( + " {}.{} receiver %{} has no type info", + box_name, method, recv.0 + ); + eprintln!(" → Fallback: Global({}.{})", box_name, method); + } + + // receiver 型情報がない場合は Global 呼び出しに降格 + // 例: StageBArgsBox.get(receiver=%123) で receiver 型不明 + // → "StageBArgsBox.get" (receiver なし) へ変換 + return Ok(Callee::Global(format!("{}.{}", box_name, method))); + } } } } diff --git a/src/mir/builder/calls/unified_emitter.rs b/src/mir/builder/calls/unified_emitter.rs index 5db2e444..93f89cb6 100644 --- a/src/mir/builder/calls/unified_emitter.rs +++ b/src/mir/builder/calls/unified_emitter.rs @@ -173,16 +173,20 @@ impl UnifiedCallEmitterBox { } }; - // Safety: ensure receiver is materialized even after callee conversion (via CallMaterializerBox) - callee = super::materializer::CallMaterializerBox::materialize_receiver_in_callee( - builder, callee, - )?; - - // Structural guard: prevent static compiler boxes from being called with runtime receivers + // Structural guard FIRST: prevent static compiler boxes from being called with runtime receivers // 箱理論: CalleeGuardBox による構造的分離 + // (Guard may convert Method → Global, so we check BEFORE materializing receiver) let guard = super::guard::CalleeGuardBox::new(&builder.value_types); callee = guard.apply_static_runtime_guard(callee)?; + // Safety: ensure receiver is materialized ONLY for Method calls + // (Global calls don't have receivers, so skip materialization) + if matches!(callee, Callee::Method { .. }) { + callee = super::materializer::CallMaterializerBox::materialize_receiver_in_callee( + builder, callee, + )?; + } + // Emit resolve.choose for method callee (dev-only; default OFF) if let Callee::Method { box_name, diff --git a/src/mir/builder/emission/constant.rs b/src/mir/builder/emission/constant.rs index 73441110..5727ffd2 100644 --- a/src/mir/builder/emission/constant.rs +++ b/src/mir/builder/emission/constant.rs @@ -43,6 +43,10 @@ pub fn emit_string>(b: &mut MirBuilder, s: S) -> ValueId { dst, value: ConstValue::String(s.into()), }); + // 🎯 Phase 3-A: String constant type annotation + // Ensures string constants have proper Box type for method resolution + b.value_types.insert(dst, crate::mir::MirType::Box("StringBox".to_string())); + b.value_origin_newbox.insert(dst, "StringBox".to_string()); dst } diff --git a/src/mir/builder/ops.rs b/src/mir/builder/ops.rs index 4c1ba73a..942d8f2e 100644 --- a/src/mir/builder/ops.rs +++ b/src/mir/builder/ops.rs @@ -67,19 +67,20 @@ impl super::MirBuilder { super::builder_calls::CallTarget::Global(name), vec![lhs, rhs], )?; - // 型注釈(従来と同等) + // 型注釈(Phase 3-B: value_origin_newbox もチェック&両方登録) let lhs_is_str = match self.value_types.get(&lhs) { Some(MirType::String) => true, Some(MirType::Box(bt)) if bt == "StringBox" => true, - _ => false, + _ => 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, - _ => false, + _ => self.value_origin_newbox.get(&rhs).map(|s| s == "StringBox").unwrap_or(false), }; if lhs_is_str || rhs_is_str { - self.value_types.insert(dst, MirType::String); + self.value_types.insert(dst, MirType::Box("StringBox".to_string())); + self.value_origin_newbox.insert(dst, "StringBox".to_string()); } else { self.value_types.insert(dst, MirType::Integer); } @@ -142,18 +143,20 @@ impl super::MirBuilder { self.emit_instruction(MirInstruction::BinOp { dst, op, lhs, rhs })?; } if matches!(op, crate::mir::BinaryOp::Add) { + // Phase 3-B: value_origin_newbox もチェック&両方登録 let lhs_is_str = match self.value_types.get(&lhs) { Some(MirType::String) => true, Some(MirType::Box(bt)) if bt == "StringBox" => true, - _ => false, + _ => 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, - _ => false, + _ => self.value_origin_newbox.get(&rhs).map(|s| s == "StringBox").unwrap_or(false), }; if lhs_is_str || rhs_is_str { - self.value_types.insert(dst, MirType::String); + self.value_types.insert(dst, MirType::Box("StringBox".to_string())); + self.value_origin_newbox.insert(dst, "StringBox".to_string()); } else { self.value_types.insert(dst, MirType::Integer); } @@ -173,18 +176,20 @@ impl super::MirBuilder { self.emit_instruction(MirInstruction::BinOp { dst, op, lhs, rhs })?; } if matches!(op, crate::mir::BinaryOp::Add) { + // Phase 3-B: value_origin_newbox もチェック&両方登録 let lhs_is_str = match self.value_types.get(&lhs) { Some(MirType::String) => true, Some(MirType::Box(bt)) if bt == "StringBox" => true, - _ => false, + _ => 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, - _ => false, + _ => self.value_origin_newbox.get(&rhs).map(|s| s == "StringBox").unwrap_or(false), }; if lhs_is_str || rhs_is_str { - self.value_types.insert(dst, MirType::String); + self.value_types.insert(dst, MirType::Box("StringBox".to_string())); + self.value_origin_newbox.insert(dst, "StringBox".to_string()); } else { self.value_types.insert(dst, MirType::Integer); }