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

@ -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()