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

@ -459,6 +459,7 @@ Notes
- `cargo build --release -p nyash-llvm-compiler` (ny-llvmc builder)
- `cargo build --release --features llvm`
- Run via harness: `NYASH_LLVM_USE_HARNESS=1 ./target/release/hakorune --backend llvm apps/APP/main.hako`
- LLVM harness 導線 SSOT: `CLAUDE.md`(重複手順はここに増やさない)。入口は `tools/run_llvm_harness.sh <program.hako>`
- Quick VM run: `./target/release/hakorune --backend vm apps/APP/main.hako`
- Emit + link (LLVM): `tools/build_llvm.sh apps/APP/main.hako -o app`
- Smokes (v2):

View File

@ -36,6 +36,28 @@ NYASH_VM_DUMP_MIR=1 ./target/release/hakorune --backend vm --mir-verbose --mir-v
./target/release/hakorune --emit-mir-json mir.json program.hako
jq '.functions[0].blocks' mir.json # ブロック構造確認
#### 🧭 **LLVM harness 実行導線SSOT**
LLVM harness は **3つのビルドが必須**。これを満たさないと失敗する。
```bash
# 入口SSOTビルド+実行を一気通貫)
tools/run_llvm_harness.sh <program.hako>
# 失敗時の復旧(不足物のビルド)
cargo build --release -p nyash-rust --features llvm --bin hakorune
cargo build --release -p nyash-llvm-compiler
cargo build --release -p nyash_kernel
# IR を見る場合harness
NYASH_LLVM_USE_HARNESS=1 NYASH_LLVM_DUMP_IR=/tmp/phase285.ll \
./target/release/hakorune --backend llvm program.hako
```
**MIR確認の優先順位**:
- 実行経路のSSOT: `NYASH_VM_DUMP_MIR=1 ... --backend vm`
- JSON確認LLVM経路の入力確認: `--emit-mir-json`
- `--dump-mir` は compile-only実行時差分の主導線にはしない
# Option C デバッグPHI関連
NYASH_OPTION_C_DEBUG=1 cargo test --release TEST_NAME 2>&1 | grep "Option C"

View File

@ -9,4 +9,5 @@ crate-type = ["staticlib", "rlib"]
[dependencies]
nyash-rust = { path = "../../" }
serde_json = "1.0" # Phase 285LLVM-1.1: For parsing fields JSON

View File

@ -6,6 +6,49 @@ mod plugin;
pub use plugin::*;
// Phase 285LLVM-1.1: Global registry for user box field declarations
use std::sync::RwLock;
use std::collections::HashMap;
static USER_BOX_FIELDS: RwLock<Option<HashMap<String, Vec<String>>>> = RwLock::new(None);
fn get_user_box_fields(box_name: &str) -> Option<Vec<String>> {
if let Ok(guard) = USER_BOX_FIELDS.read() {
if let Some(ref map) = *guard {
return map.get(box_name).cloned();
}
}
None
}
fn register_user_box_fields(box_name: String, fields: Vec<String>) {
if let Ok(mut guard) = USER_BOX_FIELDS.write() {
if guard.is_none() {
*guard = Some(HashMap::new());
}
if let Some(ref mut map) = *guard {
map.insert(box_name, fields);
}
}
}
// Phase 285LLVM-1.1: Factory function for user-defined boxes
fn create_user_box_from_registry(box_name: &str, _args: &[Box<dyn nyash_rust::box_trait::NyashBox>]) -> Result<Box<dyn nyash_rust::box_trait::NyashBox>, String> {
use nyash_rust::{instance_v2::InstanceBox, box_trait::NyashBox};
use std::collections::HashMap as StdHashMap;
if let Some(fields) = get_user_box_fields(box_name) {
let instance = InstanceBox::from_declaration(
box_name.to_string(),
fields,
StdHashMap::new(), // Empty methods - resolved via MIR BoxCall
);
Ok(Box::new(instance) as Box<dyn NyashBox>)
} else {
Err(format!("User box '{}' not registered in field registry", box_name))
}
}
// --- AOT ObjectModule dotted-name exports (String/Any helpers) ---
// String.len_h(handle) -> i64
#[export_name = "nyash.string.len_h"]
@ -391,13 +434,38 @@ pub extern "C" fn nyash_env_box_new_i64x(
push_val(&mut argv, a4);
}
// Phase 285LLVM-1.1: Check if this is a user-defined box in the field registry
if let Some(fields) = get_user_box_fields(ty) {
// Create InstanceBox with the registered fields
use nyash_rust::instance_v2::InstanceBox;
use std::collections::HashMap as StdHashMap;
use std::sync::Arc;
eprintln!("[DEBUG] Creating user box '{}' with fields: {:?}", ty, fields);
let instance = InstanceBox::from_declaration(
ty.to_string(),
fields.clone(), // Clone fields for proper ownership
StdHashMap::new(), // Empty methods - resolved via MIR BoxCall
);
let boxed: Box<dyn NyashBox> = Box::new(instance);
let arc: Arc<dyn NyashBox> = Arc::from(boxed);
let handle = handles::to_handle_arc(arc) as i64;
return handle;
}
let reg = get_global_registry();
match reg.create_box(ty, &argv) {
Ok(b) => {
let arc: std::sync::Arc<dyn NyashBox> = b.into();
handles::to_handle_arc(arc) as i64
}
Err(_) => 0,
Err(e) => {
// Phase 285LLVM-1.1: Improved error message
eprintln!("[nyrt_error] Failed to create box '{}': {}", ty, e);
eprintln!("[nyrt_hint] User-defined boxes must be registered via nyrt_register_user_box_decl()");
eprintln!("[nyrt_hint] Check MIR JSON user_box_decls or box declaration metadata");
0
}
}
}
@ -960,6 +1028,145 @@ pub extern "C" fn nyash_float_unbox_to_f64(float_handle: i64) -> f64 {
0.0 // Not a FloatBox or handle invalid
}
// ---- Phase 285LLVM-1: WeakRef FFI functions ----
/// nyrt_weak_new: Create weak reference from strong handle
///
/// # Arguments
/// * `strong_handle` - Strong Box handle (>0)
///
/// # Returns
/// * Weak handle (bit 63 = 1) on success
/// * 0 on failure (invalid handle)
///
/// # SSOT
/// - docs/reference/language/lifecycle.md:179
/// - docs/development/current/main/phases/phase-285/phase-285llvm-1-design.md
#[no_mangle]
#[export_name = "nyrt_weak_new"]
pub extern "C" fn nyrt_weak_new(strong_handle: i64) -> i64 {
use nyash_rust::runtime::host_handles as handles;
use nyash_rust::runtime::weak_handles;
eprintln!("[nyrt_weak_new] called with handle: {}", strong_handle);
if strong_handle <= 0 {
eprintln!("[nyrt_weak_new] invalid handle (<=0), returning 0");
return 0;
}
// Get Arc from strong handle
if let Some(arc) = handles::get(strong_handle as u64) {
// Downgrade to Weak and allocate weak handle
let weak = std::sync::Arc::downgrade(&arc);
let weak_handle = weak_handles::to_handle_weak(weak);
eprintln!("[nyrt_weak_new] success: strong {} → weak {}", strong_handle, weak_handle);
return weak_handle;
}
eprintln!("[nyrt_weak_new] handle {} not found in registry, returning 0", strong_handle);
0 // Invalid handle
}
/// nyrt_weak_to_strong: Upgrade weak reference to strong handle
///
/// # Arguments
/// * `weak_handle` - Weak handle (bit 63 = 1)
///
/// # Returns
/// * Strong handle (>0) on success (target is alive)
/// * 0 (Void/null) on failure (target is dead or invalid handle)
///
/// # SSOT
/// - docs/reference/language/lifecycle.md:179
#[no_mangle]
#[export_name = "nyrt_weak_to_strong"]
pub extern "C" fn nyrt_weak_to_strong(weak_handle: i64) -> i64 {
use nyash_rust::runtime::weak_handles;
eprintln!("[nyrt_weak_to_strong] called with weak_handle: {}", weak_handle);
// Upgrade weak handle to strong handle (0 on failure)
let result = weak_handles::upgrade_weak_handle(weak_handle);
eprintln!("[nyrt_weak_to_strong] result: {} (0=null/failed, >0=success)", result);
result
}
/// nyrt_weak_drop: Release weak reference
///
/// # Arguments
/// * `weak_handle` - Weak handle (bit 63 = 1)
///
/// # Note
/// Called when WeakRef goes out of scope (LLVM backend cleanup)
#[no_mangle]
#[export_name = "nyrt_weak_drop"]
pub extern "C" fn nyrt_weak_drop(weak_handle: i64) {
use nyash_rust::runtime::weak_handles;
if weak_handle != 0 {
weak_handles::drop_weak_handle(weak_handle);
}
}
/// Register user-defined box declaration (LLVM harness support)
/// Phase 285LLVM-1.1: Enable user box instantiation with fields in LLVM harness mode
///
/// # Arguments
/// * `type_name` - Box type name (e.g., "SomeBox")
/// * `fields_json` - JSON array of field names (e.g., "[\"x\",\"y\"]")
///
/// # Returns
/// * `0` - Success
/// * `-1` - Error: null pointer
/// * `-2` - Error: invalid UTF-8
/// * `-3` - Error: invalid JSON
#[export_name = "nyrt_register_user_box_decl"]
pub extern "C" fn nyrt_register_user_box_decl(
type_name: *const i8,
fields_json: *const i8
) -> i32 {
use std::ffi::CStr;
if type_name.is_null() || fields_json.is_null() {
eprintln!("[nyrt_register_user_box_decl] Error: null pointer");
return -1;
}
let ty = match unsafe { CStr::from_ptr(type_name) }.to_str() {
Ok(s) => s.to_string(),
Err(e) => {
eprintln!("[nyrt_register_user_box_decl] Error: invalid UTF-8 in type_name: {:?}", e);
return -2;
}
};
let fields_str = match unsafe { CStr::from_ptr(fields_json) }.to_str() {
Ok(s) => s,
Err(e) => {
eprintln!("[nyrt_register_user_box_decl] Error: invalid UTF-8 in fields_json: {:?}", e);
return -2;
}
};
// Parse JSON array of field names
let fields: Vec<String> = match serde_json::from_str(fields_str) {
Ok(f) => f,
Err(e) => {
eprintln!("[nyrt_register_user_box_decl] Error: invalid JSON in fields: {:?}", e);
return -3;
}
};
// Store fields in global registry
// The actual box creation will be handled in nyash_env_box_new_i64x
register_user_box_fields(ty.clone(), fields.clone());
eprintln!("[DEBUG] Registered user box '{}' with fields: {:?}", ty, fields);
0 // Success
}
#[cfg(test)]
mod tests {
use super::*;

View File

@ -238,6 +238,218 @@ fn nyash_plugin_invoke_name_common_i64(method: &str, argc: i64, a0: i64, a1: i64
0
}
// ========================================================================
// Phase 285LLVM-1.3: InstanceBox Field Access Helpers
// ========================================================================
/// Helper: handle → String デコード
///
/// Phase 285LLVM-1.3: Fail-Fast error logging
fn decode_handle_to_string(handle: i64) -> Result<String, String> {
if handle <= 0 {
return Err(format!("Invalid handle: {}", handle));
}
let obj = nyash_rust::runtime::host_handles::get(handle as u64)
.ok_or_else(|| format!("Handle {} not found", handle))?;
let sb = obj
.as_any()
.downcast_ref::<nyash_rust::box_trait::StringBox>()
.ok_or_else(|| format!("Handle {} is not a StringBox", handle))?;
Ok(sb.value.clone())
}
/// Helper: handle → NyashValue デコード(対応型のみ)
///
/// Phase 285LLVM-1.3: Integer/String/Bool のみ対応、未対応型は明示エラー
fn decode_handle_to_nyash_value(handle: i64) -> Result<nyash_rust::value::NyashValue, String> {
use nyash_rust::box_trait::{BoolBox, IntegerBox, StringBox};
use nyash_rust::value::NyashValue;
if handle <= 0 {
return Err(format!("Invalid handle: {}", handle));
}
let obj = nyash_rust::runtime::host_handles::get(handle as u64)
.ok_or_else(|| format!("Handle {} not found", handle))?;
// Integer
if let Some(ib) = obj.as_any().downcast_ref::<IntegerBox>() {
return Ok(NyashValue::Integer(ib.value));
}
// String
if let Some(sb) = obj.as_any().downcast_ref::<StringBox>() {
return Ok(NyashValue::String(sb.value.clone()));
}
// Bool
if let Some(bb) = obj.as_any().downcast_ref::<BoolBox>() {
return Ok(NyashValue::Bool(bb.value));
}
// 未対応型: 明示的エラー(次フェーズで対応)
Err(format!(
"Unsupported Box type for handle {}: Phase 285LLVM-1.3 supports Integer/String/Bool only",
handle
))
}
/// InstanceBox.getField(field_name) → i64 handle
///
/// Fail-Fast: エラーは明示的にログ出力して0返却
fn handle_instance_get_field(inst: &nyash_rust::instance_v2::InstanceBox, field_handle: i64) -> i64 {
use nyash_rust::box_trait::{BoolBox, IntegerBox, StringBox};
use nyash_rust::value::NyashValue;
use std::sync::Arc;
// 1. field_name デコード (既存ユーティリティ活用)
let field_name = match decode_handle_to_string(field_handle) {
Ok(s) => s,
Err(e) => {
eprintln!(
"[llvm/invoke/getField] Failed to decode field_name handle {}: {}",
field_handle, e
);
return 0;
}
};
// 2. fields_ng から値を取得 (SSOT: get_field_ng のみ使用)
let nv = match inst.get_field_ng(&field_name) {
Some(v) => v,
None => {
eprintln!(
"[llvm/invoke/getField] Field '{}' not found in InstanceBox",
field_name
);
return 0;
}
};
// 3. NyashValue → i64 handle 変換
match nv {
NyashValue::Integer(i) => {
let arc: Arc<dyn nyash_rust::box_trait::NyashBox> = Arc::new(IntegerBox::new(i));
let handle = nyash_rust::runtime::host_handles::to_handle_arc(arc) as i64;
eprintln!("[llvm/invoke/getField] Returning Integer({}) as handle {}", i, handle);
// Verify handle can be resolved back
if let Some(obj) = nyash_rust::runtime::host_handles::get(handle as u64) {
if let Some(ib) = obj.as_any().downcast_ref::<nyash_rust::box_trait::IntegerBox>() {
eprintln!("[llvm/invoke/getField] ✅ Verified: handle {} resolves to IntegerBox({})", handle, ib.value);
} else {
eprintln!("[llvm/invoke/getField] ❌ ERROR: handle {} does not resolve to IntegerBox!", handle);
}
} else {
eprintln!("[llvm/invoke/getField] ❌ ERROR: handle {} cannot be resolved!", handle);
}
handle
}
NyashValue::String(s) => {
let arc: Arc<dyn nyash_rust::box_trait::NyashBox> = Arc::new(StringBox::new(s));
nyash_rust::runtime::host_handles::to_handle_arc(arc) as i64
}
NyashValue::Bool(b) => {
let arc: Arc<dyn nyash_rust::box_trait::NyashBox> = Arc::new(BoolBox::new(b));
nyash_rust::runtime::host_handles::to_handle_arc(arc) as i64
}
NyashValue::Null | NyashValue::Void => 0,
// 未対応型: 明示的エラー(次フェーズで対応)
NyashValue::Float(_) => {
eprintln!(
"[llvm/invoke/getField] Unsupported type: Float (field: {})",
field_name
);
0
}
NyashValue::Array(_) => {
eprintln!(
"[llvm/invoke/getField] Unsupported type: Array (field: {})",
field_name
);
0
}
NyashValue::Map(_) => {
eprintln!(
"[llvm/invoke/getField] Unsupported type: Map (field: {})",
field_name
);
0
}
NyashValue::Box(_) => {
eprintln!(
"[llvm/invoke/getField] Unsupported type: Box (field: {})",
field_name
);
0
}
NyashValue::WeakBox(_) => {
eprintln!(
"[llvm/invoke/getField] Unsupported type: WeakBox (field: {})",
field_name
);
0
}
}
}
/// InstanceBox.setField(field_name, value) → 1 (success) or 0 (failure)
///
/// Fail-Fast: エラーは明示的にログ出力して0返却
fn handle_instance_set_field(
inst: &nyash_rust::instance_v2::InstanceBox,
field_handle: i64,
value_handle: i64,
) -> i64 {
use nyash_rust::value::NyashValue;
// 1. field_name デコード (既存ユーティリティ活用)
let field_name = match decode_handle_to_string(field_handle) {
Ok(s) => s,
Err(e) => {
eprintln!(
"[llvm/invoke/setField] Failed to decode field_name handle {}: {}",
field_handle, e
);
return 0;
}
};
// 2. value handle → NyashValue 変換
// LLVM backend では i64 値がそのまま渡される場合があるhandle ではなく生の値)
let nv = if value_handle == 0 {
NyashValue::Null
} else {
// まず handle として解決を試みる
match decode_handle_to_nyash_value(value_handle) {
Ok(v) => v,
Err(_) => {
// handle でない場合は、i64 値として直接扱うLLVM backend の挙動)
eprintln!(
"[llvm/invoke/setField] Handle {} not found for field '{}', treating as raw i64 value",
value_handle, field_name
);
NyashValue::Integer(value_handle)
}
}
};
// 3. fields_ng に設定 (SSOT: set_field_ng のみ使用)
match inst.set_field_ng(field_name.clone(), nv) {
Ok(_) => 1, // 成功
Err(e) => {
eprintln!(
"[llvm/invoke/setField] Failed to set field '{}': {}",
field_name, e
);
0
}
}
}
// General by-name invoke: (recv_handle, method_cstr, argc, a1, a2) -> i64
// Export name: nyash.plugin.invoke_by_name_i64
#[export_name = "nyash.plugin.invoke_by_name_i64"]
@ -255,7 +467,12 @@ pub extern "C" fn nyash_plugin_invoke_by_name_i64(
let Ok(method_str) = mname.to_str() else {
return 0;
};
// 🔍 DEBUG: Trace function entry
eprintln!("[llvm/invoke/DEBUG] Called with recv_handle={}, method={}, argc={}", recv_handle, method_str, argc);
use nyash_rust::runtime::plugin_loader_v2::PluginBoxV2;
use nyash_rust::instance_v2::InstanceBox;
let mut instance_id: u32 = 0;
let mut type_id: u32 = 0;
let mut box_type: Option<String> = None;
@ -264,13 +481,46 @@ pub extern "C" fn nyash_plugin_invoke_by_name_i64(
> = None;
if recv_handle > 0 {
if let Some(obj) = nyash_rust::runtime::host_handles::get(recv_handle as u64) {
eprintln!("[llvm/invoke/DEBUG] Handle {} resolved successfully", recv_handle);
// 🔥 Phase 285LLVM-1.3: InstanceBox 専用処理(優先)
if let Some(inst) = obj.as_any().downcast_ref::<InstanceBox>() {
eprintln!("[llvm/invoke/DEBUG] ✅ InstanceBox downcast SUCCESS, method={}", method_str);
match method_str {
"getField" => {
return handle_instance_get_field(inst, a1);
}
"setField" => {
return handle_instance_set_field(inst, a1, a2);
}
_ => {
// getField/setField 以外はエラー
eprintln!(
"[llvm/invoke] Unsupported InstanceBox method: {}",
method_str
);
return 0;
}
}
} else {
eprintln!("[llvm/invoke/DEBUG] ❌ InstanceBox downcast FAILED, trying PluginBoxV2...");
}
// 既存の PluginBoxV2 処理(変更なし)
if let Some(p) = obj.as_any().downcast_ref::<PluginBoxV2>() {
eprintln!("[llvm/invoke/DEBUG] ✅ PluginBoxV2 downcast SUCCESS");
instance_id = p.instance_id();
type_id = p.inner.type_id;
box_type = Some(p.box_type.clone());
invoke = Some(p.inner.invoke_fn);
} else {
eprintln!("[llvm/invoke/DEBUG] ❌ PluginBoxV2 downcast FAILED");
}
} else {
eprintln!("[llvm/invoke/DEBUG] ❌ Handle {} NOT found in host_handles", recv_handle);
}
} else {
eprintln!("[llvm/invoke/DEBUG] ❌ recv_handle <= 0");
}
if invoke.is_none() {
return 0;

View File

@ -9,7 +9,25 @@
- 次: Phase 285design-firstBox lifecycle SSOT`docs/development/current/main/phases/phase-285/README.md`
- 次の次: Phase 286design-firstJoinIR line absorption`docs/development/current/main/phases/phase-286/README.md`
## Recently Completed (2025-12-23)
## Recently Completed
### 2025-12-24: Phase 285LLVM-1.3LLVM InstanceBox Field Access
-**Phase 285LLVM-1.3完了**: InstanceBox field access (getField/setField) implementation
- 詳細: `docs/development/current/main/phases/phase-285/phase-285llvm-1.3-verification-report.md`
- 実装: `crates/nyash_kernel/src/plugin/invoke.rs` (~170 lines)
- 達成内容:
- ✅ getField/setField handlers 実装完了SSOT `fields_ng` 直接アクセス)
- ✅ Fail-Fast error logging 実装(`[llvm/invoke/{get,set}Field]` tags
- ✅ Raw i64 fallback 対応LLVM backend 特有の挙動)
- ✅ Handle resolution 動作確認handle 4 → IntegerBox(42)
- 検証結果:
- ✅ setField: Integer(42) を正しく保存
- ✅ getField: Integer(42) を正しく取得、handle 返却
- ⚠️ print issue: handle 解決問題により VM/LLVM parity blockedPhase 285LLVM-1.4で対応)
- 次フェーズ: Phase 285LLVM-1.4 (print Handle Resolution, 推定2-4時間)
### 2025-12-23
- Phase 283bugfix: JoinIR if-condition remap fix: `docs/development/current/main/phases/phase-283/README.md`
- Phase 282Router shrinkage + extraction-based migration + extractor refactor P0P9a: `docs/development/current/main/phases/phase-282/README.md`

View File

@ -34,6 +34,20 @@ Related:
- 参考(現状の入口候補):
- weakref 表現: `src/value.rs``NyashValue::WeakBox`
- finalization: `src/finalization.rs`
- 追加syntax cleanup, small & focused:
- `weak` の表面構文を `weak <expr>` に収束(`weak(<expr>)` を持ち込まない)
- `let weak w;` / `let weak w = e` の糖衣を検討(概念を増やさず `let w = weak e` にデシュガー)
- fixture/smoke は `apps/tests/*.hako` を SSOT にして VM/LLVM で共通化(必要なら LLVM 側は SKIP で理由を固定)
- **Phase 29xplanned, post self-host: De-Rust runtime for LLVM execution**
- 目的: LLVM 実行経路のランタイム依存を段階的に Rust から切り離す脱Rust
- 前提: self-host ラインが安定し、VM/LLVM conformancePhase 285が十分に固まっていること。
- 方針:
- 仕様SSOTstrong/weak/fini/cleanup/voidは維持し、実装だけを差し替え可能にする。
- まず ABI 境界(例: `nyrt_*`)を “将来置換する契約” として固定し、独立ランタイムに差し替える。
- 受け入れ条件(最小):
- 既存の `apps/tests/*.hako` fixture を再利用し、VM/LLVM parity のスモークが維持される。
- weak の語彙(`weak <expr>` / `weak_to_strong()`が同じ意味で動作するcycleは当面リーク仕様でも可
- **Phase 286planned, design-first: JoinIR Line AbsorptionJoinIR→CorePlan/Frag 収束)**
- 目的: 移行期間に残っている「2本の loweringPlan line / JoinIR line」を、構造で 1 本に収束させる

View File

@ -49,7 +49,7 @@ Rust製インタープリターによる高性能実行と、直感的な構文
| `pack` | 旧コンストラクタ(互換性) | `pack(param) { }` |
| `outbox` | 所有権移転変数 | `outbox result = compute()` |
| `global` | グローバル変数 | `global CONFIG = "dev"` |
| `weak` | 弱参照(生成 | `weak(x)` |
| `weak` | 弱参照(強→弱の変換 | `weak x` |
| `using` | 名前空間インポート | `using namespace` |
### **演算子・論理**
@ -83,14 +83,15 @@ box ClassName {
me.field3 = defaultValue()
}
# メソッド
methodName(arg1, arg2) {
return me.field1 + arg1
}
# デストラクタfini
fini() {
print("Cleanup: " + me.field1)
# メソッド
methodName(arg1, arg2) {
return me.field1 + arg1
}
# 注: 引数の型注釈 `arg: Type` は未対応Phase 285A1.5: 明示エラー。ハングしない)
# デストラクタfini
fini() {
print("Cleanup: " + me.field1)
}
}
```
@ -798,6 +799,7 @@ static box Main {
x = 42 # 変数未宣言 → ランタイムエラー
while condition { } # 非対応構文 → パーサーエラー
this.field # thisは使用不可 → me.fieldを使用
methodName(arg: Type) { } # 未対応Phase 285A1.5)。引数は名前だけ:`methodName(arg) { }`
# ✅ 正しい書き方Phase 12.7後)
field1: TypeBox # フィールド宣言(型は省略可)

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

55
tools/run_llvm_harness.sh Normal file
View File

@ -0,0 +1,55 @@
#!/usr/bin/env bash
set -euo pipefail
usage() {
cat << USAGE
Usage: tools/run_llvm_harness.sh <input.hako> [-- <args...>]
Builds LLVM-harness prerequisites and runs the program via:
NYASH_LLVM_USE_HARNESS=1 ./target/release/hakorune --backend llvm <input.hako>
USAGE
}
if [[ $# -lt 1 ]]; then
usage
exit 1
fi
INPUT="$1"
shift || true
if [[ "$INPUT" == "-h" || "$INPUT" == "--help" ]]; then
usage
exit 0
fi
if [[ ! -f "$INPUT" ]]; then
echo "error: input file not found: $INPUT" >&2
exit 1
fi
CARGO_TARGET_DIR_EFFECTIVE="${CARGO_TARGET_DIR:-$PWD/target}"
BIN_DEFAULT="$CARGO_TARGET_DIR_EFFECTIVE/release/hakorune"
BIN="${NYASH_BIN:-$BIN_DEFAULT}"
echo "[1/4] Building hakorune (llvm feature)..."
cargo build --release -p nyash-rust --features llvm --bin hakorune -j 24
echo "[2/4] Building ny-llvmc..."
cargo build --release -p nyash-llvm-compiler -j 24
echo "[3/4] Building nyash_kernel..."
cargo build --release -p nyash_kernel -j 24
if [[ ! -x "$BIN" ]]; then
if [[ -x "$CARGO_TARGET_DIR_EFFECTIVE/release/nyash" ]]; then
BIN="$CARGO_TARGET_DIR_EFFECTIVE/release/nyash"
else
echo "error: compiler binary not found/executable after build: $BIN" >&2
echo "hint: ensure NYASH_BIN points to an existing binary or set CARGO_TARGET_DIR correctly" >&2
exit 1
fi
fi
echo "[4/4] Running LLVM harness..."
NYASH_LLVM_USE_HARNESS=1 "$BIN" --backend llvm "$INPUT" "$@"