feat(phase285): Complete weak reference implementation (VM + LLVM harness)

Phase 285LLVM-1.1 to 1.4 + weak reference infrastructure:

**LLVM Harness** (Phase 285LLVM-1.x):
- 285LLVM-1.1: User Box registration & debug output
- 285LLVM-1.2: WeakRef basic operations (identity deferred)
- 285LLVM-1.3: InstanceBox field access (getField/setField)
- 285LLVM-1.4: print Handle resolution (type tag propagation)

**VM Runtime** (nyash_kernel):
- FFI functions: nyrt_weak_new, nyrt_weak_to_strong, nyrt_weak_drop
  (crates/nyash_kernel/src/lib.rs: +209 lines)
- WeakRef plugin invoke support
  (crates/nyash_kernel/src/plugin/invoke.rs: +250 lines)
- weak_handles.rs: WeakRef handle registry (NEW)

**LLVM Python Backend**:
- WeakRef instruction lowering (weak.py: NEW)
- Entry point integration (entry.py: +93 lines)
- Instruction lowering (instruction_lower.py: +13 lines)
- LLVM harness runner script (tools/run_llvm_harness.sh: NEW)

**MIR & Runtime**:
- WeakRef emission & validation
- MIR JSON export for weak instructions
- Environment variable support (NYASH_WEAK_*, HAKO_WEAK_*)

**Documentation**:
- CLAUDE.md: Phase 285 completion notes
- LANGUAGE_REFERENCE_2025.md: Weak reference syntax
- 10-Now.md & 30-Backlog.md: Phase 285 status updates

Total: +864 lines, 24 files changed

SSOT: docs/reference/language/lifecycle.md
Related: Phase 285W-Syntax-0, Phase 285W-Syntax-0.1
This commit is contained in:
2025-12-25 00:11:34 +09:00
parent cc05c37ae3
commit f740e6542f
27 changed files with 1176 additions and 41 deletions

View File

@ -523,6 +523,26 @@ pub fn emit_mir_json_for_harness(
I::Return { value } => {
insts.push(json!({"op":"ret","value": value.map(|v| v.as_u32())}));
}
// Phase 285LLVM-1: WeakRef support (unified form after normalization)
I::WeakRef { dst, op, value } => {
use crate::mir::WeakRefOp;
let op_name = match op {
WeakRefOp::New => "weak_new",
WeakRefOp::Load => "weak_load",
};
let value_field = match op {
WeakRefOp::New => "box_val",
WeakRefOp::Load => "weak_ref",
};
insts.push(json!({"op": op_name, "dst": dst.as_u32(), value_field: value.as_u32()}));
}
// Legacy WeakNew/WeakLoad (before normalization)
I::WeakNew { dst, box_val } => {
insts.push(json!({"op":"weak_new","dst": dst.as_u32(), "box_val": box_val.as_u32()}));
}
I::WeakLoad { dst, weak_ref } => {
insts.push(json!({"op":"weak_load","dst": dst.as_u32(), "weak_ref": weak_ref.as_u32()}));
}
_ => { /* skip non-essential ops for initial harness */ }
}
}
@ -580,16 +600,32 @@ pub fn emit_mir_json_for_harness(
// Phase 155: Extract CFG information for hako_check
let cfg_info = nyash_rust::mir::extract_cfg_info(module);
// Phase 285LLVM-1.1: Extract user box declarations for LLVM harness
let user_box_decls: Vec<serde_json::Value> = module.metadata.user_box_decls
.iter()
.map(|(name, fields)| {
json!({
"name": name,
"fields": fields
})
})
.collect();
let root = if use_v1_schema {
let mut root = create_json_v1_root(json!(funs));
// Add CFG data to v1 schema
// Add CFG data and user box declarations to v1 schema
if let Some(obj) = root.as_object_mut() {
obj.insert("cfg".to_string(), cfg_info);
obj.insert("user_box_decls".to_string(), json!(user_box_decls)); // Phase 285LLVM-1.1
}
root
} else {
// v0 legacy format - also add CFG
json!({"functions": funs, "cfg": cfg_info})
// v0 legacy format - also add CFG and user_box_decls
json!({
"functions": funs,
"cfg": cfg_info,
"user_box_decls": user_box_decls // Phase 285LLVM-1.1
})
};
// NOTE: numeric_core strict validation is applied on the AotPrep output
@ -910,6 +946,26 @@ pub fn emit_mir_json_for_harness_bin(
I::Return { value } => {
insts.push(json!({"op":"ret","value": value.map(|v| v.as_u32())}));
}
// Phase 285LLVM-1: WeakRef support (unified form after normalization)
I::WeakRef { dst, op, value } => {
use crate::mir::WeakRefOp;
let op_name = match op {
WeakRefOp::New => "weak_new",
WeakRefOp::Load => "weak_load",
};
let value_field = match op {
WeakRefOp::New => "box_val",
WeakRefOp::Load => "weak_ref",
};
insts.push(json!({"op": op_name, "dst": dst.as_u32(), value_field: value.as_u32()}));
}
// Legacy WeakNew/WeakLoad (before normalization)
I::WeakNew { dst, box_val } => {
insts.push(json!({"op":"weak_new","dst": dst.as_u32(), "box_val": box_val.as_u32()}));
}
I::WeakLoad { dst, weak_ref } => {
insts.push(json!({"op":"weak_load","dst": dst.as_u32(), "weak_ref": weak_ref.as_u32()}));
}
_ => {}
}
}
@ -954,7 +1010,22 @@ pub fn emit_mir_json_for_harness_bin(
// Phase 155: Extract CFG information for hako_check
let cfg_info = crate::mir::extract_cfg_info(module);
let root = json!({"functions": funs, "cfg": cfg_info});
// Phase 285LLVM-1.1: Extract user box declarations for LLVM harness
let user_box_decls: Vec<serde_json::Value> = module.metadata.user_box_decls
.iter()
.map(|(name, fields)| {
json!({
"name": name,
"fields": fields
})
})
.collect();
let root = json!({
"functions": funs,
"cfg": cfg_info,
"user_box_decls": user_box_decls // Phase 285LLVM-1.1
});
// NOTE: numeric_core strict validation is applied on the AotPrep output
// (tools/hakorune_emit_mir.sh) rather than at raw MIR emit time. This keeps

View File

@ -92,6 +92,22 @@ fn hint_ny_llvmc_missing(path: &std::path::Path) -> String {
)
}
fn hint_nyrt_missing(dir: &str) -> String {
let lib = Path::new(dir).join("libnyash_kernel.a");
format!(
"nyrt runtime not found (missing: {}).\nHints:\n - Build it: cargo build -p nyash_kernel --release\n - Or set env NYASH_EMIT_EXE_NYRT=/path/to/nyash_kernel/target/release\n",
lib.display()
)
}
fn verify_nyrt_dir(dir: &str) -> Result<(), String> {
let lib = Path::new(dir).join("libnyash_kernel.a");
if lib.exists() {
return Ok(());
}
Err(hint_nyrt_missing(dir))
}
/// Emit native executable via ny-llvmc (lib-side MIR)
#[allow(dead_code)]
pub fn ny_llvmc_emit_exe_lib(
@ -124,11 +140,9 @@ pub fn ny_llvmc_emit_exe_lib(
.map(|r| format!("{}/target/release", r))
})
.unwrap_or_else(|| "target/release".to_string());
if let Some(dir) = nyrt_dir {
cmd.arg("--nyrt").arg(dir);
} else {
cmd.arg("--nyrt").arg(default_nyrt);
}
let nyrt_dir_final = nyrt_dir.unwrap_or(&default_nyrt);
verify_nyrt_dir(nyrt_dir_final)?;
cmd.arg("--nyrt").arg(nyrt_dir_final);
if let Some(flags) = extra_libs {
if !flags.trim().is_empty() {
cmd.arg("--libs").arg(flags);
@ -183,11 +197,9 @@ pub fn ny_llvmc_emit_exe_bin(
.map(|r| format!("{}/target/release", r))
})
.unwrap_or_else(|| "target/release".to_string());
if let Some(dir) = nyrt_dir {
cmd.arg("--nyrt").arg(dir);
} else {
cmd.arg("--nyrt").arg(default_nyrt);
}
let nyrt_dir_final = nyrt_dir.unwrap_or(&default_nyrt);
verify_nyrt_dir(nyrt_dir_final)?;
cmd.arg("--nyrt").arg(nyrt_dir_final);
if let Some(flags) = extra_libs {
if !flags.trim().is_empty() {
cmd.arg("--libs").arg(flags);

View File

@ -27,7 +27,10 @@ impl FallbackExecutorBox {
// do not silently fall back to mock.
if crate::config::env::env_bool("NYASH_LLVM_USE_HARNESS") {
return Err(LlvmRunError::fatal(
"LLVM harness requested (NYASH_LLVM_USE_HARNESS=1), but this binary was built without `--features llvm` (llvm-harness). Fix: cargo build --release --features llvm"
"LLVM harness requested (NYASH_LLVM_USE_HARNESS=1), but this binary was built without `--features llvm` (llvm-harness).\n\
Fix:\n cargo build --release -p nyash-rust --features llvm --bin hakorune\n\
Then ensure prerequisites:\n cargo build --release -p nyash-llvm-compiler\n cargo build --release -p nyash_kernel\n\
Tip: tools/run_llvm_harness.sh <program.hako>"
));
}

View File

@ -23,7 +23,11 @@ impl HarnessExecutorBox {
/// Returns Ok(exit_code) on success, Err(LlvmRunError) on failure.
#[cfg(feature = "llvm-harness")]
pub fn try_execute(module: &MirModule) -> Result<i32, LlvmRunError> {
if !crate::config::env::llvm_use_harness() {
eprintln!("🎯 [DEBUG] llvm-harness feature IS ENABLED at compile time");
let harness_enabled = crate::config::env::llvm_use_harness();
eprintln!("🎯 [DEBUG] llvm_use_harness() = {}", harness_enabled);
eprintln!("🎯 [DEBUG] NYASH_LLVM_USE_HARNESS env var = {:?}", std::env::var("NYASH_LLVM_USE_HARNESS"));
if !harness_enabled {
return Err(LlvmRunError::fatal("LLVM harness not enabled (NYASH_LLVM_USE_HARNESS not set)"));
}
@ -62,6 +66,8 @@ impl HarnessExecutorBox {
#[cfg(not(feature = "llvm-harness"))]
pub fn try_execute(_module: &MirModule) -> Result<i32, LlvmRunError> {
eprintln!("❌ [DEBUG] llvm-harness feature IS NOT ENABLED at compile time");
eprintln!("❌ [DEBUG] You need to rebuild with: cargo build --release --features llvm");
Err(LlvmRunError::fatal("LLVM harness feature not enabled (built without --features llvm)"))
}
}