Phase 21.2 Complete: VM Adapter正規実装 + devブリッジ完全撤去

## 🎉 Phase 21.2完全達成

###  実装完了
- VM static box 永続化(singleton infrastructure)
- devブリッジ完全撤去(adapter_dev.rs削除、by-name dispatch削除)
- .hako正規実装(MirCallV1Handler, AbiAdapterRegistry等)
- text-merge経路完全動作
- 全phase2120 adapter reps PASS(7テスト)

### 🐛 バグ修正
1. strip_local_decl修正
   - トップレベルのみlocal削除、メソッド内は保持
   - src/runner/modes/common_util/hako.rs:29

2. static box フィールド永続化
   - MirInterpreter singleton storage実装
   - me parameter binding修正(1:1マッピング)
   - getField/setField string→singleton解決
   - src/backend/mir_interpreter/{mod,exec,handlers/boxes_object_fields}.rs

3. Map.len alias rc=0修正
   - [map/missing]パターン検出でnull扱い(4箇所)
   - lang/src/vm/boxes/mir_call_v1_handler.hako:91-93,131-133,151-153,199-201

### 📁 主要変更ファイル

#### Rust(VM Runtime)
- src/backend/mir_interpreter/mod.rs - static box singleton storage
- src/backend/mir_interpreter/exec.rs - parameter binding fix
- src/backend/mir_interpreter/handlers/boxes_object_fields.rs - singleton resolution
- src/backend/mir_interpreter/handlers/calls.rs - dev bridge removal
- src/backend/mir_interpreter/utils/mod.rs - adapter_dev module removal
- src/backend/mir_interpreter/utils/adapter_dev.rs - DELETED (7555 bytes)
- src/runner/modes/vm.rs - static box declaration collection
- src/runner/modes/common_util/hako.rs - strip_local_decl fix
- src/instance_v2.rs - Clone implementation

#### Hako (.hako実装)
- lang/src/vm/boxes/mir_call_v1_handler.hako - [map/missing] detection
- lang/src/vm/boxes/abi_adapter_registry.hako - NEW (adapter registry)
- lang/src/vm/helpers/method_alias_policy.hako - method alias support

#### テスト
- tools/smokes/v2/profiles/quick/core/phase2120/s3_vm_adapter_*.sh - 7 new tests

### 🎯 テスト結果
```
 s3_vm_adapter_array_len_canary_vm.sh
 s3_vm_adapter_array_len_per_recv_canary_vm.sh
 s3_vm_adapter_array_length_alias_canary_vm.sh
 s3_vm_adapter_array_size_alias_canary_vm.sh
 s3_vm_adapter_map_len_alias_state_canary_vm.sh
 s3_vm_adapter_map_length_alias_state_canary_vm.sh
 s3_vm_adapter_map_size_struct_canary_vm.sh
```

環境フラグ: HAKO_ABI_ADAPTER=1 HAKO_ABI_ADAPTER_DEV=0

### 🏆 設計品質
-  ハードコード禁止(AGENTS.md 5.1)完全準拠
-  構造的・一般化設計(特定Box名のif分岐なし)
-  後方互換性保持(既存コード破壊ゼロ)
-  text-merge経路(.hako依存関係正しくマージ)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
nyash-codex
2025-11-07 19:32:44 +09:00
parent 8d1e580ab4
commit 301b1d212a
62 changed files with 3867 additions and 462 deletions

View File

@ -26,17 +26,72 @@ impl NyashRunner {
process::exit(1);
}
};
// Using preprocessing: 仕様維持のためテキスト・プレリュード統合を既定にASTマージは任意
// Using preprocessing: AST prelude merge.hako/Hakoライクは強制AST
let mut code2 = code.clone();
if crate::config::env::enable_using() {
match crate::runner::modes::common_util::resolve::merge_prelude_text(self, &code2, filename) {
Ok(merged) => {
if std::env::var("NYASH_RESOLVE_TRACE").ok().as_deref() == Some("1") {
eprintln!("[using/text-merge] applied (vm-fallback): {} bytes", merged.len());
let mut use_ast = crate::config::env::using_ast_enabled();
let is_hako = filename.ends_with(".hako")
|| crate::runner::modes::common_util::hako::looks_like_hako_code(&code2);
if is_hako { use_ast = true; }
if use_ast {
match crate::runner::modes::common_util::resolve::resolve_prelude_paths_profiled(self, &code2, filename) {
Ok((clean, paths)) => {
// If any prelude is .hako, prefer text-merge (Hakorune surface is not Nyash AST)
let has_hako = paths.iter().any(|p| p.ends_with(".hako"));
if has_hako {
match crate::runner::modes::common_util::resolve::merge_prelude_text(self, &code2, filename) {
Ok(merged) => {
if std::env::var("NYASH_RESOLVE_TRACE").ok().as_deref() == Some("1") {
eprintln!("[using/text-merge] preludes={} (vm-fallback)", paths.len());
}
code2 = merged;
}
Err(e) => { eprintln!("{}", e); process::exit(1); }
}
// Fall through to normal parse of merged text below
} else {
// AST prelude merge path
code2 = clean;
let preexpanded = crate::runner::modes::common_util::resolve::preexpand_at_local(&code2);
code2 = preexpanded;
if crate::runner::modes::common_util::hako::looks_like_hako_code(&code2) {
code2 = crate::runner::modes::common_util::hako::strip_local_decl(&code2);
}
let main_ast = match NyashParser::parse_from_string(&code2) {
Ok(ast) => ast,
Err(e) => { eprintln!("❌ Parse error in {}: {}", filename, e); process::exit(1); }
};
if !paths.is_empty() {
match crate::runner::modes::common_util::resolve::parse_preludes_to_asts(self, &paths) {
Ok(v) => {
if std::env::var("NYASH_RESOLVE_TRACE").ok().as_deref() == Some("1") {
eprintln!("[using/ast-merge] preludes={} (vm-fallback)", v.len());
}
let ast = crate::runner::modes::common_util::resolve::merge_prelude_asts_with_main(v, &main_ast);
self.execute_vm_fallback_from_ast(filename, ast);
return; // done
}
Err(e) => { eprintln!("{}", e); process::exit(1); }
}
} else {
self.execute_vm_fallback_from_ast(filename, main_ast);
return;
}
}
}
code2 = merged;
Err(e) => { eprintln!("{}", e); process::exit(1); }
}
} else {
// Fallback: text-prelude merge言語非依存
match crate::runner::modes::common_util::resolve::merge_prelude_text(self, &code2, filename) {
Ok(merged) => {
if std::env::var("NYASH_RESOLVE_TRACE").ok().as_deref() == Some("1") {
eprintln!("[using/text-merge] applied (vm-fallback): {} bytes", merged.len());
}
code2 = merged;
}
Err(e) => { eprintln!("❌ using text merge error: {}", e); process::exit(1); }
}
Err(e) => { eprintln!("❌ using text merge error: {}", e); process::exit(1); }
}
} else {
// using disabled: detect and fail fast if present
@ -78,7 +133,7 @@ impl NyashRunner {
process::exit(1);
}
};
// AST prelude merge is retired in favor of text-based merge for language-neutral handling
// No AST preludes (text path or no using) → use the parsed main AST as-is
let ast_combined = main_ast;
// Optional: dump AST statement kinds for quick diagnostics
if std::env::var("NYASH_AST_DUMP").ok().as_deref() == Some("1") {
@ -295,3 +350,111 @@ impl NyashRunner {
}
}
}
impl NyashRunner {
/// Small helper to continue fallback execution once AST is prepared
fn execute_vm_fallback_from_ast(&self, filename: &str, ast: nyash_rust::ast::ASTNode) {
use crate::{
backend::MirInterpreter,
box_factory::{BoxFactory, RuntimeError},
core::model::BoxDeclaration as CoreBoxDecl,
instance_v2::InstanceBox,
mir::MirCompiler,
};
use std::sync::{Arc, RwLock};
use std::process;
// Macro expand (if enabled)
let ast = crate::r#macro::maybe_expand_and_dump(&ast, false);
// Minimal user-defined Box support (inline factory)
{
use nyash_rust::ast::ASTNode;
let mut nonstatic_decls: std::collections::HashMap<String, CoreBoxDecl> = std::collections::HashMap::new();
let mut static_names: Vec<String> = Vec::new();
if let ASTNode::Program { statements, .. } = &ast {
for st in statements {
if let ASTNode::BoxDeclaration { name, fields, public_fields, private_fields, methods, constructors, init_fields, weak_fields, is_interface, extends, implements, type_parameters, is_static, .. } = st {
if *is_static { static_names.push(name.clone()); continue; }
let decl = CoreBoxDecl { name: name.clone(), fields: fields.clone(), public_fields: public_fields.clone(), private_fields: private_fields.clone(), methods: methods.clone(), constructors: constructors.clone(), init_fields: init_fields.clone(), weak_fields: weak_fields.clone(), is_interface: *is_interface, extends: extends.clone(), implements: implements.clone(), type_parameters: type_parameters.clone() };
nonstatic_decls.insert(name.clone(), decl);
}
}
}
let mut decls = nonstatic_decls.clone();
for s in static_names.into_iter() {
let inst = format!("{}Instance", s);
if let Some(d) = nonstatic_decls.get(&inst) {
decls.insert(s, d.clone());
}
}
if !decls.is_empty() {
struct InlineUserBoxFactory {
decls: Arc<RwLock<std::collections::HashMap<String, CoreBoxDecl>>>,
}
impl BoxFactory for InlineUserBoxFactory {
fn create_box(
&self,
name: &str,
args: &[Box<dyn crate::box_trait::NyashBox>],
) -> Result<Box<dyn crate::box_trait::NyashBox>, RuntimeError> {
let opt = { self.decls.read().unwrap().get(name).cloned() };
let decl = match opt {
Some(d) => d,
None => {
return Err(RuntimeError::InvalidOperation {
message: format!("Unknown Box type: {}", name),
})
}
};
let mut inst = InstanceBox::from_declaration(
decl.name.clone(),
decl.fields.clone(),
decl.methods.clone(),
);
let _ = inst.init(args);
Ok(Box::new(inst))
}
fn box_types(&self) -> Vec<&str> { vec![] }
fn is_available(&self) -> bool { true }
fn factory_type(&self) -> crate::box_factory::FactoryType {
crate::box_factory::FactoryType::User
}
}
let factory = InlineUserBoxFactory {
decls: Arc::new(RwLock::new(decls)),
};
crate::runtime::unified_registry::register_user_defined_factory(Arc::new(factory));
}
}
// Compile to MIR and execute via interpreter
let mut compiler = MirCompiler::with_options(!self.config.no_optimize);
let module = match compiler.compile(ast) {
Ok(r) => r.module,
Err(e) => { eprintln!("❌ MIR compilation error: {}", e); process::exit(1); }
};
let mut interp = MirInterpreter::new();
match interp.execute_module(&module) {
Ok(result) => {
// Normalize display (avoid nonexistent coerce_to_exit_code here)
use nyash_rust::box_trait::{BoolBox, IntegerBox};
let rc = if let Some(ib) = result.as_any().downcast_ref::<IntegerBox>() {
ib.value as i32
} else if let Some(bb) = result.as_any().downcast_ref::<BoolBox>() {
if bb.value { 1 } else { 0 }
} else {
0
};
// For CAPI pure pipeline, suppress "RC:" text to keep last line = exe path
let capi = std::env::var("NYASH_LLVM_USE_CAPI").ok().as_deref() == Some("1");
let pure = std::env::var("HAKO_CAPI_PURE").ok().as_deref() == Some("1");
if capi && pure {
process::exit(rc);
} else {
println!("RC: {}", rc);
}
}
Err(e) => { eprintln!("❌ VM fallback runtime error: {}", e); process::exit(1); }
}
}
}