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

@ -3,6 +3,72 @@
//! Consolidates NYASH_* environment variables across subsystems and
//! optionally applies overrides from `nyash.toml`.
//!
//! # Global Environment Configuration (管理棟)
//!
//! ## 環境変数の集約と直読み禁止
//!
//! **Phase 286A/B** で、全システムの環境変数を `src/config/env/` 以下のモジュールに集約しました。
//!
//! - **直読み禁止**: `std::env::var()` / `std::env::set_var()` の直接呼び出しは禁止です。
//! - **必ず `src/config/env/*` 経由でアクセス**: 各サブシステムのフラグモジュール (`macro_flags`, `box_factory_flags`, etc.) を使用してください。
//!
//! ## モジュール構成
//!
//! | モジュール | 担当環境変数 | 用途 |
//! | --- | --- | --- |
//! | `macro_flags` | `NYASH_MACRO_*` | Macro システム設定 |
//! | `box_factory_flags` | `NYASH_BOX_FACTORY_*`, `NYASH_DISABLE_PLUGINS` | Box Factory / プラグイン設定 |
//! | `joinir_flags` | `NYASH_JOINIR_*` | JoinIR 設定 |
//! | `mir_flags` | `NYASH_MIR_*` | MIR 設定 |
//! | `vm_backend_flags` | `NYASH_VM_*` | VM / Backend 設定 |
//! | `parser_flags` | `NYASH_PARSER_*` | Parser 設定 |
//! | `using_flags` | `NYASH_USING_*` | Using / Namespace 設定 |
//! | `verification_flags` | `NYASH_VERIFY_*` | Verification 設定 |
//! | `selfhost_flags` | `NYASH_NY_COMPILER_*` | Selfhost compiler 設定 |
//!
//! ## 新規環境変数追加の手順
//!
//! 1. **モジュール選択**: 上記のモジュールから適切なものを選択。
//! 2. **関数定義**: 選択したモジュールに `fn env_var_name() -> type` 形式で関数を定義。
//! 3. **再export**: `src/config/env.rs` で `pub use module::*;` を確認(既に集約済み)。
//! 4. **ドキュメント追記**: `docs/reference/environment-variables.md` に必ず追記してください。
//!
//! ## 直読み禁止のチェック
//!
//! 置換漏れを確認するには、以下のコマンドを使用してください:
//!
//! ```bash
//! # std::env::var() の直接呼び出しを検索src/config/env/ 以外は禁止)
//! rg -n "std::env::(var|set_var|remove_var)\(" src | rg -v "src/config/env/"
//!
//! # NYASH_* 環境変数の直読みを検索src/config/env/ 以外は禁止)
//! rg -n "NYASH_(MACRO|BOX_FACTORY|DISABLE_PLUGINS)" src | rg -v "src/config/env/"
//! ```
//!
//! ## 再export ポリシー
//!
//! 全ての環境変数関数は `src/config/env.rs` で再exportされています
//!
//! ```rust
//! // Backward-compatible re-exports (NO BREAKING CHANGES!)
//! pub use box_factory_flags::*;
//! pub use joinir_flags::*;
//! pub use macro_flags::*;
//! pub use mir_flags::*;
//! pub use parser_flags::*;
//! pub use selfhost_flags::*;
//! pub use using_flags::*;
//! pub use verification_flags::*;
//! pub use vm_backend_flags::*;
//! ```
//!
//! 使用時は `crate::config::env::function_name()` でアクセスしてください。
//!
//! ## 参照
//!
//! - SSOT ドキュメント: `docs/reference/environment-variables.md`
//! - AGENTS.md 5.3: 環境変数スパロー防止ポリシー
//!
//! # Modular Organization
//!
//! Environment flags are now organized into focused Box modules:

View File

@ -216,3 +216,23 @@ pub fn test_return() -> Option<String> {
pub fn macro_syntax_sugar_level() -> Option<String> {
std::env::var("NYASH_SYNTAX_SUGAR_LEVEL").ok()
}
/// NYASH_MACRO_CAP_IO (capability: IO allowed)
pub fn macro_cap_io() -> Option<bool> {
std::env::var("NYASH_MACRO_CAP_IO")
.ok()
.map(|v| {
let lv = v.to_ascii_lowercase();
lv == "1" || lv == "true" || lv == "on"
})
}
/// NYASH_MACRO_CAP_NET (capability: NET allowed)
pub fn macro_cap_net() -> Option<bool> {
std::env::var("NYASH_MACRO_CAP_NET")
.ok()
.map(|v| {
let lv = v.to_ascii_lowercase();
lv == "1" || lv == "true" || lv == "on"
})
}

View File

@ -7,6 +7,7 @@ from naming_helper import encode_static_method
def ensure_ny_main(builder) -> None:
"""Ensure ny_main wrapper exists by delegating to Main.main/1 or main().
Phase 285LLVM-1.1: Register user box declarations before calling main.
Modifies builder.module in place; no return value.
"""
has_ny_main = any(f.name == 'ny_main' for f in builder.module.functions)
@ -34,6 +35,11 @@ def ensure_ny_main(builder) -> None:
ny_main = ir.Function(builder.module, ny_main_ty, name='ny_main')
entry = ny_main.append_basic_block('entry')
b = ir.IRBuilder(entry)
# Phase 285LLVM-1.1: Register user box declarations before calling main
user_box_decls = getattr(builder, 'user_box_decls', [])
if user_box_decls:
_emit_user_box_registration(b, builder.module, user_box_decls)
if fn_main_box is not None:
# Build args
i64 = builder.i64
@ -84,3 +90,90 @@ def ensure_ny_main(builder) -> None:
b.ret(rv)
else:
b.ret(ir.Constant(builder.i64, 0))
def _emit_user_box_registration(b, module, user_box_decls):
"""Emit calls to nyrt_register_user_box_decl() for each user box declaration.
Phase 285LLVM-1.1: Register user-defined boxes before main execution.
Args:
b: IRBuilder instance (positioned at ny_main entry block)
module: LLVM module
user_box_decls: List[dict] with format [{"name": "SomeBox", "fields": ["x"]}, ...]
"""
from llvmlite import ir
import json
i32 = ir.IntType(32)
i8 = ir.IntType(8)
i8p = i8.as_pointer()
# Declare nyrt_register_user_box_decl if not exists
reg_func = None
for f in module.functions:
if f.name == "nyrt_register_user_box_decl":
reg_func = f
break
if not reg_func:
# i32 nyrt_register_user_box_decl(i8* name, i8* fields_json)
reg_func_type = ir.FunctionType(i32, [i8p, i8p])
reg_func = ir.Function(module, reg_func_type, name="nyrt_register_user_box_decl")
# Emit registration calls for each box declaration
for box_decl in user_box_decls:
name = box_decl.get("name", "")
fields = box_decl.get("fields", [])
if not name:
continue
# Create global string constant for name
name_bytes = (name + "\0").encode('utf-8')
name_arr_ty = ir.ArrayType(i8, len(name_bytes))
name_global = f".user_box_name_{name}"
# Check if global already exists
existing_global = None
for g in module.global_values:
if g.name == name_global:
existing_global = g
break
if existing_global is None:
g_name = ir.GlobalVariable(module, name_arr_ty, name=name_global)
g_name.linkage = 'private'
g_name.global_constant = True
g_name.initializer = ir.Constant(name_arr_ty, bytearray(name_bytes))
else:
g_name = existing_global
# Create global string constant for fields JSON
fields_json = json.dumps(fields)
fields_bytes = (fields_json + "\0").encode('utf-8')
fields_arr_ty = ir.ArrayType(i8, len(fields_bytes))
fields_global = f".user_box_fields_{name}"
# Check if global already exists
existing_fields_global = None
for g in module.global_values:
if g.name == fields_global:
existing_fields_global = g
break
if existing_fields_global is None:
g_fields = ir.GlobalVariable(module, fields_arr_ty, name=fields_global)
g_fields.linkage = 'private'
g_fields.global_constant = True
g_fields.initializer = ir.Constant(fields_arr_ty, bytearray(fields_bytes))
else:
g_fields = existing_fields_global
# Get pointers to strings
c0 = ir.Constant(ir.IntType(32), 0)
name_ptr = b.gep(g_name, [c0, c0], inbounds=True)
fields_ptr = b.gep(g_fields, [c0, c0], inbounds=True)
# Call nyrt_register_user_box_decl(name, fields_json)
b.call(reg_func, [name_ptr, fields_ptr])

View File

@ -22,6 +22,7 @@ from instructions.select import lower_select # Phase 256 P1.5: Select instructi
from instructions.loopform import lower_while_loopform
from instructions.controlflow.while_ import lower_while_regular
from instructions.mir_call import lower_mir_call # New unified handler
from instructions.weak import lower_weak_new, lower_weak_load # Phase 285LLVM-1: WeakRef
def lower_instruction(owner, builder: ir.IRBuilder, inst: Dict[str, Any], func: ir.Function):
@ -185,6 +186,18 @@ def lower_instruction(owner, builder: ir.IRBuilder, inst: Dict[str, Any], func:
vmap_ctx, owner.preds, owner.block_end_values, owner.bb_map,
ctx=getattr(owner, 'ctx', None))
elif op == "weak_new":
# Phase 285LLVM-1: WeakNew instruction (strong → weak)
dst = inst.get("dst")
box_val = inst.get("box_val")
lower_weak_new(builder, owner.module, dst, box_val, vmap_ctx, ctx=getattr(owner, 'ctx', None))
elif op == "weak_load":
# Phase 285LLVM-1: WeakLoad instruction (weak → strong or 0/Void)
dst = inst.get("dst")
weak_ref = inst.get("weak_ref")
lower_weak_load(builder, owner.module, dst, weak_ref, vmap_ctx, ctx=getattr(owner, 'ctx', None))
elif op == "while":
# Experimental LoopForm lowering inside a block
cond = inst.get("cond")

View File

@ -0,0 +1,111 @@
"""
Phase 285LLVM-1: WeakRef instruction lowering
Handles weak reference creation and upgrade (weak_new, weak_load)
SSOT: docs/reference/language/lifecycle.md:179
"""
import llvmlite.ir as ir
from typing import Dict, Optional, Any
def lower_weak_new(
builder: ir.IRBuilder,
module: ir.Module,
dst_vid: int,
box_val_vid: int,
vmap: Dict[int, ir.Value],
ctx: Optional[Any] = None
) -> None:
"""
Lower MIR WeakNew instruction
Converts strong BoxRef to WeakRef.
MIR: WeakNew { dst: ValueId(10), box_val: ValueId(5) }
LLVM IR: %10 = call i64 @nyrt_weak_new(i64 %5)
Args:
builder: Current LLVM IR builder
module: LLVM module
dst_vid: Destination value ID for weak handle
box_val_vid: Source BoxRef value ID
vmap: Value map
ctx: Optional context
"""
i64 = ir.IntType(64)
# Get or declare nyrt_weak_new function
nyrt_weak_new = None
for f in module.functions:
if f.name == "nyrt_weak_new":
nyrt_weak_new = f
break
if not nyrt_weak_new:
# Declare: i64 @nyrt_weak_new(i64 strong_handle)
func_type = ir.FunctionType(i64, [i64])
nyrt_weak_new = ir.Function(module, func_type, name="nyrt_weak_new")
# Get strong handle from vmap
strong_handle = vmap.get(box_val_vid)
if strong_handle is None:
# Fallback: treat as literal 0 (invalid)
strong_handle = ir.Constant(i64, 0)
# Call nyrt_weak_new
weak_handle = builder.call(nyrt_weak_new, [strong_handle], name=f"weak_{dst_vid}")
# Store result in vmap
vmap[dst_vid] = weak_handle
def lower_weak_load(
builder: ir.IRBuilder,
module: ir.Module,
dst_vid: int,
weak_ref_vid: int,
vmap: Dict[int, ir.Value],
ctx: Optional[Any] = None
) -> None:
"""
Lower MIR WeakLoad instruction
Upgrades WeakRef to BoxRef (returns 0/Void on failure).
MIR: WeakLoad { dst: ValueId(20), weak_ref: ValueId(10) }
LLVM IR: %20 = call i64 @nyrt_weak_to_strong(i64 %10)
Args:
builder: Current LLVM IR builder
module: LLVM module
dst_vid: Destination value ID for strong handle (or 0)
weak_ref_vid: Source WeakRef value ID
vmap: Value map
ctx: Optional context
"""
i64 = ir.IntType(64)
# Get or declare nyrt_weak_to_strong function
nyrt_weak_to_strong = None
for f in module.functions:
if f.name == "nyrt_weak_to_strong":
nyrt_weak_to_strong = f
break
if not nyrt_weak_to_strong:
# Declare: i64 @nyrt_weak_to_strong(i64 weak_handle)
func_type = ir.FunctionType(i64, [i64])
nyrt_weak_to_strong = ir.Function(module, func_type, name="nyrt_weak_to_strong")
# Get weak handle from vmap
weak_handle = vmap.get(weak_ref_vid)
if weak_handle is None:
# Fallback: treat as literal 0 (invalid)
weak_handle = ir.Constant(i64, 0)
# Call nyrt_weak_to_strong
strong_handle = builder.call(nyrt_weak_to_strong, [weak_handle], name=f"strong_{dst_vid}")
# Store result in vmap
vmap[dst_vid] = strong_handle

View File

@ -138,6 +138,9 @@ class NyashLLVMBuilder:
def build_from_mir(self, mir_json: Dict[str, Any]) -> str:
"""Build LLVM IR from MIR JSON"""
# Phase 285LLVM-1.1: Extract user box declarations for registration
self.user_box_decls = mir_json.get("user_box_decls", [])
# Parse MIR
reader = MIRReader(mir_json)
functions = reader.get_functions()

View File

@ -459,8 +459,8 @@ struct NyChildMacroBox {
}
fn caps_allow_macro_source(ast: &ASTNode) -> Result<(), String> {
let allow_io = crate::config::env::env_flag("NYASH_MACRO_CAP_IO").unwrap_or(false);
let allow_net = crate::config::env::env_flag("NYASH_MACRO_CAP_NET").unwrap_or(false);
let allow_io = crate::config::env::macro_cap_io().unwrap_or(false);
let allow_net = crate::config::env::macro_cap_net().unwrap_or(false);
use nyash_rust::ast::ASTNode as A;
fn scan(n: &A, seen: &mut Vec<String>) {
match n {

View File

@ -968,7 +968,7 @@ impl MirBuilder {
// - For user-defined boxes (no explicit constructor), do NOT emit BoxCall("birth").
// VM will treat plain NewBox as constructed; dev verify warns if needed.
// - For builtins/plugins, keep BoxCall("birth") fallback to preserve legacy init.
let is_user_box = self.comp_ctx.user_defined_boxes.contains(&class);
let is_user_box = self.comp_ctx.user_defined_boxes.contains_key(&class); // Phase 285LLVM-1.1: HashMap
// Dev safety: allow disabling birth() injection for builtins to avoid
// unified-call method dispatch issues while migrating. Off by default unless explicitly enabled.
let allow_builtin_birth = std::env::var("NYASH_DEV_BIRTH_INJECT_BUILTINS")

View File

@ -45,7 +45,10 @@ pub(crate) struct CompilationContext {
pub current_static_box: Option<String>,
/// Names of user-defined boxes declared in the current module
pub user_defined_boxes: HashSet<String>,
/// Phase 285LLVM-1.1: Extended to track fields (box name → field names)
/// For static boxes: empty Vec (no fields)
/// For instance boxes: Vec of field names
pub user_defined_boxes: HashMap<String, Vec<String>>,
/// Phase 201-A: Reserved ValueIds that must not be allocated
/// These are PHI dst ValueIds created by LoopHeaderPhiBuilder.
@ -98,7 +101,7 @@ impl CompilationContext {
Self {
compilation_context: None,
current_static_box: None,
user_defined_boxes: HashSet::new(),
user_defined_boxes: HashMap::new(), // Phase 285LLVM-1.1: HashMap for fields
reserved_value_ids: HashSet::new(),
fn_body_ast: None,
weak_fields_by_box: HashMap::new(),
@ -124,12 +127,17 @@ impl CompilationContext {
/// Check if a box is user-defined
pub fn is_user_defined_box(&self, name: &str) -> bool {
self.user_defined_boxes.contains(name)
self.user_defined_boxes.contains_key(name) // Phase 285LLVM-1.1: HashMap check
}
/// Register a user-defined box
/// Register a user-defined box (backward compatibility - no fields)
pub fn register_user_box(&mut self, name: String) {
self.user_defined_boxes.insert(name);
self.user_defined_boxes.insert(name, Vec::new()); // Phase 285LLVM-1.1: Empty fields
}
/// Phase 285LLVM-1.1: Register a user-defined box with field information
pub fn register_user_box_with_fields(&mut self, name: String, fields: Vec<String>) {
self.user_defined_boxes.insert(name, fields);
}
/// Check if a ValueId is reserved

View File

@ -82,13 +82,17 @@ impl super::MirBuilder {
}
ASTNode::BoxDeclaration {
name,
fields, // Phase 285LLVM-1.1: Extract fields
methods,
is_static,
..
} => {
if !*is_static {
self.comp_ctx.user_defined_boxes.insert(name.clone());
// Phase 285LLVM-1.1: Register instance box with field information
self.comp_ctx.register_user_box_with_fields(name.clone(), fields.clone());
} else {
// Static box: no fields
self.comp_ctx.register_user_box(name.clone());
for (mname, mast) in methods {
if let ASTNode::FunctionDeclaration { params, .. } = mast {
self.comp_ctx
@ -223,7 +227,8 @@ impl super::MirBuilder {
}
} else {
// Instance box: register type and lower instance methods/ctors as functions
self.comp_ctx.user_defined_boxes.insert(name.clone());
// Phase 285LLVM-1.1: Register with field information for LLVM harness
self.comp_ctx.register_user_box_with_fields(name.clone(), fields.clone());
self.build_box_declaration(
name.clone(),
methods.clone(),
@ -534,6 +539,9 @@ impl super::MirBuilder {
// main 関数スコープの SlotRegistry を解放するよ。
self.comp_ctx.current_slot_registry = None;
// Phase 285LLVM-1.1: Copy user box declarations to module metadata for LLVM harness
module.metadata.user_box_decls = self.comp_ctx.user_defined_boxes.clone();
Ok(module)
}

View File

@ -49,7 +49,7 @@ pub(crate) fn try_known_rewrite(
return None;
}
// Only user-defined boxes (plugin/core boxesは対象外)
if !builder.comp_ctx.user_defined_boxes.contains(cls) {
if !builder.comp_ctx.user_defined_boxes.contains_key(cls) { // Phase 285LLVM-1.1: HashMap
return None;
}
// Policy gates従来互換
@ -124,7 +124,7 @@ pub(crate) fn try_known_rewrite_to_dst(
{
return None;
}
if !builder.comp_ctx.user_defined_boxes.contains(cls) {
if !builder.comp_ctx.user_defined_boxes.contains_key(cls) { // Phase 285LLVM-1.1: HashMap
return None;
}
let allow_userbox_rewrite =
@ -204,7 +204,7 @@ pub(crate) fn try_unique_suffix_rewrite(
let fname = cands.remove(0);
// 🎯 Phase 21.7++ Phase 3: StaticMethodId SSOT 実装
let id = crate::mir::naming::StaticMethodId::parse(&fname)?;
if !builder.comp_ctx.user_defined_boxes.contains(&id.box_name) {
if !builder.comp_ctx.user_defined_boxes.contains_key(&id.box_name) { // Phase 285LLVM-1.1: HashMap
return None;
}
// unified
@ -260,7 +260,7 @@ pub(crate) fn try_unique_suffix_rewrite_to_dst(
let fname = cands.remove(0);
// 🎯 Phase 21.7++ Phase 3: StaticMethodId SSOT 実装
let id = crate::mir::naming::StaticMethodId::parse(&fname)?;
if !builder.comp_ctx.user_defined_boxes.contains(&id.box_name) {
if !builder.comp_ctx.user_defined_boxes.contains_key(&id.box_name) { // Phase 285LLVM-1.1: HashMap
return None;
}
let _name_const = match crate::mir::builder::name_const::make_name_const_result(builder, &fname)

View File

@ -414,6 +414,10 @@ pub struct ModuleMetadata {
/// Dev idempotence markers for passes (optional; default empty)
/// Key format suggestion: "pass_name:function_name"
pub dev_processed_markers: HashSet<String>,
/// Phase 285LLVM-1.1: User-defined box declarations with fields
/// HashMap: box name → field names (empty Vec for static boxes)
pub user_box_decls: std::collections::HashMap<String, Vec<String>>,
}
impl MirModule {

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)"))
}
}

View File

@ -31,6 +31,7 @@ pub mod unified_registry; // Deprecation warnings with warn-once guards
pub mod extern_registry; // ExternCall (env.*) 登録・診断用レジストリ
pub mod host_api; // C ABI: plugins -> host 逆呼び出しAPITLSでVMに橋渡し
pub mod host_handles; // C ABI(TLV) 向け HostHandle レジストリ(ユーザー/内蔵Box受け渡し
pub mod weak_handles; // Phase 285LLVM-1: WeakRef Handle レジストリbit 63 = 1
pub mod modules_registry;
pub mod type_box_abi; // Phase 12: Nyash ABI (vtable) 雛形
pub mod type_meta;

146
src/runtime/weak_handles.rs Normal file
View File

@ -0,0 +1,146 @@
/*!
* Weak Handle Registry (Phase 285LLVM-1)
*
* 目的:
* - WeakRef のための LLVM handle 管理を提供。
* - i64 handle (bit 63 = 1) → Weak<dyn NyashBox> をグローバルに保持。
* - LLVM backend から FFI 経由でアクセス可能。
*
* Runtime 表現:
* - Strong handle: 0x0000_0000_0000_0001 ~ 0x7FFF_FFFF_FFFF_FFFF (bit 63 = 0)
* - Weak handle: 0x8000_0000_0000_0001 ~ 0xFFFF_FFFF_FFFF_FFFF (bit 63 = 1)
*/
use once_cell::sync::OnceCell;
use std::collections::HashMap;
use std::sync::{
atomic::{AtomicU64, Ordering},
Arc, RwLock, Weak,
};
use crate::box_trait::NyashBox;
/// Weak handle marker (bit 63 = 1)
const WEAK_HANDLE_MARKER: u64 = 0x8000_0000_0000_0000;
/// Extract raw handle ID (clear bit 63)
#[inline]
fn extract_weak_id(handle: i64) -> u64 {
(handle as u64) & !WEAK_HANDLE_MARKER
}
/// Mark handle as weak (set bit 63)
#[inline]
fn mark_weak_handle(id: u64) -> i64 {
(id | WEAK_HANDLE_MARKER) as i64
}
/// Check if handle is weak (bit 63 = 1)
#[inline]
pub fn is_weak_handle(handle: i64) -> bool {
(handle as u64 & WEAK_HANDLE_MARKER) != 0
}
struct WeakRegistry {
next: AtomicU64,
map: RwLock<HashMap<u64, Weak<dyn NyashBox>>>,
}
impl WeakRegistry {
fn new() -> Self {
Self {
next: AtomicU64::new(1),
map: RwLock::new(HashMap::new()),
}
}
fn alloc(&self, weak: Weak<dyn NyashBox>) -> i64 {
let id = self.next.fetch_add(1, Ordering::Relaxed);
if let Ok(mut m) = self.map.write() {
m.insert(id, weak);
}
mark_weak_handle(id)
}
fn get(&self, handle: i64) -> Option<Weak<dyn NyashBox>> {
let id = extract_weak_id(handle);
self.map.read().ok().and_then(|m| m.get(&id).cloned())
}
fn drop_handle(&self, handle: i64) {
let id = extract_weak_id(handle);
if let Ok(mut m) = self.map.write() {
m.remove(&id);
}
}
}
static WEAK_REG: OnceCell<WeakRegistry> = OnceCell::new();
fn weak_reg() -> &'static WeakRegistry {
WEAK_REG.get_or_init(WeakRegistry::new)
}
/// Weak<dyn NyashBox> → Weak Handle (i64, bit 63 = 1)
pub fn to_handle_weak(weak: Weak<dyn NyashBox>) -> i64 {
weak_reg().alloc(weak)
}
/// Weak Handle (i64) → Weak<dyn NyashBox>
pub fn get_weak(handle: i64) -> Option<Weak<dyn NyashBox>> {
weak_reg().get(handle)
}
/// Drop weak handle (release from registry)
pub fn drop_weak_handle(handle: i64) {
weak_reg().drop_handle(handle)
}
/// Upgrade weak handle to strong handle
/// Returns: strong handle (>0) on success, 0 (Void) on failure
pub fn upgrade_weak_handle(weak_handle: i64) -> i64 {
if let Some(weak) = get_weak(weak_handle) {
if let Some(arc) = weak.upgrade() {
return crate::runtime::host_handles::to_handle_arc(arc) as i64;
}
}
0 // Void (null)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::box_trait::StringBox;
#[test]
fn test_weak_handle_marker() {
let strong_handle = 0x0000_0000_0000_0001i64;
let weak_handle = 0x8000_0000_0000_0001i64;
assert!(!is_weak_handle(strong_handle));
assert!(is_weak_handle(weak_handle));
}
#[test]
fn test_weak_handle_lifecycle() {
let arc: Arc<dyn NyashBox> = Arc::new(StringBox::new("test"));
let weak = Arc::downgrade(&arc);
// Allocate weak handle
let weak_handle = to_handle_weak(weak.clone());
assert!(is_weak_handle(weak_handle));
// Upgrade should succeed (arc is alive)
let strong_handle = upgrade_weak_handle(weak_handle);
assert!(strong_handle > 0);
// Drop arc
drop(arc);
// Upgrade should fail (arc is dead)
let result = upgrade_weak_handle(weak_handle);
assert_eq!(result, 0); // Void
// Cleanup
drop_weak_handle(weak_handle);
}
}