feat(llvm): Phase 97 - Call/PHI/Plugin強化 + リファクタリング準備
## 概要 LLVM backend のCall処理、PHI wiring、Plugin loader を強化。 次のリファクタリング(箱化モジュール化)のための準備も含む。 ## 変更内容 ### LLVM Call処理強化 - `mir_call/__init__.py`: Call ルーティングロジック改善 - `mir_call/global_call.py`: print処理の marshal強化 - `mir_call/method_call.py`: メソッド呼び出し処理改善 - `boxcall.py`: BoxCall処理改善 ### PHI処理強化 - `phi_manager.py`: PHI管理改善 - `phi_wiring/wiring.py`: PHI配線ロジック強化(+17行) - `phi_wiring/tagging.py`: Type tagging改善 - `resolver.py`: Value解決ロジック強化(+34行) ### Copy伝播 - `copy.py`: Copy命令のType tag伝播追加(+10行) ### Plugin loader強化 - `library.rs`: エラー出力改善、[plugin/missing]ログ追加(+34行) - fail-fast強化 ### テスト - `phase97_json_loader_escape_llvm_exe.sh`: Phase 97 E2Eテスト追加 - `phase97_next_non_ws_llvm_exe.sh`: Phase 97 E2Eテスト追加 ### その他 - `nyash_kernel/lib.rs`: Kernel側の改善(+23行) ## 統計 - 14ファイル変更 - +256行 / -53行 = +203 net ## 次のリファクタリング準備 以下の箇所がリファクタリング対象として識別済み: 1. Call ルーティング箱の明文化 2. print の marshal 箱 3. TypeFacts/Tagging 箱の一本化 4. PHI Snapshot 契約のSSOT 5. Plugin loader のエラー出力統合 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
@ -779,10 +779,29 @@ pub extern "C" fn main() -> i32 {
|
||||
}
|
||||
// SAFETY: if not linked, calling will be an unresolved symbol at link-time; we rely on link step to include ny_main.
|
||||
let v = ny_main();
|
||||
let exit_code: i64 = {
|
||||
use nyash_rust::{
|
||||
box_trait::{IntegerBox, NyashBox},
|
||||
runtime::host_handles as handles,
|
||||
};
|
||||
if v > 0 {
|
||||
if let Some(obj) = handles::get(v as u64) {
|
||||
if let Some(ib) = obj.as_any().downcast_ref::<IntegerBox>() {
|
||||
ib.value as i64
|
||||
} else {
|
||||
0
|
||||
}
|
||||
} else {
|
||||
v
|
||||
}
|
||||
} else {
|
||||
v
|
||||
}
|
||||
};
|
||||
// Print standardized result line for golden comparisons (can be silenced for tests)
|
||||
let silent = std::env::var("NYASH_NYRT_SILENT_RESULT").ok().as_deref() == Some("1");
|
||||
if !silent {
|
||||
println!("Result: {}", v);
|
||||
println!("Result: {}", exit_code);
|
||||
}
|
||||
// Optional GC metrics after program completes
|
||||
let want_json = std::env::var("NYASH_GC_METRICS_JSON").ok().as_deref() == Some("1");
|
||||
@ -857,7 +876,7 @@ pub extern "C" fn main() -> i32 {
|
||||
// ✂️ REMOVED: Legacy JIT leak diagnostics - part of 42% deletable functions
|
||||
// Leak diagnostics functionality removed with JIT archival
|
||||
// handles::type_tally() no longer available in Plugin-First architecture
|
||||
v as i32
|
||||
exit_code as i32
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -181,23 +181,38 @@ def lower_function(builder, func_data: Dict[str, Any]):
|
||||
if entry_bid is None and blocks:
|
||||
entry_bid = blocks[0].get("id", 0)
|
||||
|
||||
# Compute approx preds-first order
|
||||
visited = set()
|
||||
order: List[int] = []
|
||||
# Compute reverse-postorder over successors (SSOT):
|
||||
# - Ensures a stable, mostly-forward lowering order (preds before succs) even with loops.
|
||||
# - Avoids lowering a block before its dominating setup/copies when possible.
|
||||
visited: set[int] = set()
|
||||
post: List[int] = []
|
||||
try:
|
||||
from cfg.utils import build_preds_succs as _build_preds_succs
|
||||
_preds2, succs2 = _build_preds_succs(block_by_id)
|
||||
except Exception:
|
||||
succs2 = {}
|
||||
|
||||
def visit(bid: int):
|
||||
def dfs(bid: int):
|
||||
if bid in visited:
|
||||
return
|
||||
visited.add(bid)
|
||||
for p in builder.preds.get(bid, []):
|
||||
visit(p)
|
||||
order.append(bid)
|
||||
try:
|
||||
succ_list = list(succs2.get(bid, []) or [])
|
||||
succ_list = [int(x) for x in succ_list]
|
||||
succ_list.sort()
|
||||
except Exception:
|
||||
succ_list = []
|
||||
for s in succ_list:
|
||||
dfs(s)
|
||||
post.append(bid)
|
||||
|
||||
if entry_bid is not None:
|
||||
visit(entry_bid)
|
||||
for bid in block_by_id.keys():
|
||||
dfs(int(entry_bid))
|
||||
# Include unreachable blocks deterministically
|
||||
for bid in sorted(block_by_id.keys()):
|
||||
if bid not in visited:
|
||||
visit(bid)
|
||||
dfs(int(bid))
|
||||
order: List[int] = list(reversed(post))
|
||||
|
||||
# Prepass: collect PHI metadata and placeholders
|
||||
_setup_phi_placeholders(builder, blocks)
|
||||
|
||||
@ -9,6 +9,7 @@ from instructions.safepoint import insert_automatic_safepoint
|
||||
from naming_helper import encode_static_method
|
||||
from console_bridge import emit_console_call # Phase 133: Console 箱化モジュール
|
||||
from instructions.stringbox import emit_stringbox_call # Phase 134-B: StringBox 箱化モジュール
|
||||
from utils.values import resolve_i64_strict
|
||||
|
||||
def _declare(module: ir.Module, name: str, ret, args):
|
||||
for f in module.functions:
|
||||
@ -102,9 +103,10 @@ def lower_boxcall(
|
||||
except Exception:
|
||||
pass
|
||||
def _res_i64(vid: int):
|
||||
if r is not None and p is not None and bev is not None and bbm is not None:
|
||||
# SSOT: Use the common resolver policy (prefer local SSA, then global vmap, then PHI-localize).
|
||||
if r is not None and p is not None and bev is not None:
|
||||
try:
|
||||
return r.resolve_i64(vid, builder.block, p, bev, vmap, bbm)
|
||||
return resolve_i64_strict(r, vid, builder.block, p, bev, vmap, bbm)
|
||||
except Exception:
|
||||
return None
|
||||
return vmap.get(vid)
|
||||
|
||||
@ -48,3 +48,13 @@ def lower_copy(
|
||||
if os.environ.get('NYASH_LLVM_VMAP_TRACE') == '1':
|
||||
print(f"[vmap/id] copy dst={dst} src={src} vmap id={id(vmap)} before_write", file=sys.stderr)
|
||||
safe_vmap_write(vmap, dst, val, "copy", resolver=resolver)
|
||||
|
||||
# TypeFacts propagation (SSOT): preserve "stringish" tagging across Copy.
|
||||
# Many MIR patterns materialize a temp then Copy into a local; without this,
|
||||
# string equality/concat may incorrectly fall back to integer/handle ops.
|
||||
try:
|
||||
if resolver is not None and hasattr(resolver, "is_stringish") and resolver.is_stringish(src):
|
||||
if hasattr(resolver, "mark_string"):
|
||||
resolver.mark_string(dst)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
@ -117,10 +117,20 @@ def lower_mir_call(owner, builder: ir.IRBuilder, mir_call: Dict[str, Any], dst_v
|
||||
method = callee.get("name")
|
||||
box_name = callee.get("box_name")
|
||||
receiver = callee.get("receiver")
|
||||
# v1 JSON: receiver is implicit as first arg if missing
|
||||
if receiver is None and args:
|
||||
certainty = callee.get("certainty")
|
||||
|
||||
# SSOT: Method calls split into two routes:
|
||||
# - Static method (receiver=null, certainty=Known): lower as direct function call `Box.method/$arity`
|
||||
# - Instance method (receiver omitted in v1 JSON): receiver is implicit as first arg
|
||||
if receiver is None:
|
||||
if certainty == "Known" and box_name and method:
|
||||
func_name = f"{box_name}.{method}/{len(args)}"
|
||||
lower_global_call(builder, owner.module, func_name, args, dst_vid, vmap, resolver, owner)
|
||||
return
|
||||
if args:
|
||||
receiver = args[0]
|
||||
args = args[1:] # Remove receiver from args
|
||||
|
||||
lower_method_call(builder, owner.module, box_name, method, receiver, args, dst_vid, vmap, resolver, owner)
|
||||
|
||||
elif callee_type == "Constructor":
|
||||
|
||||
@ -82,19 +82,42 @@ def lower_global_call(builder, module, func_name, args, dst_vid, vmap, resolver,
|
||||
if i < len(func.args):
|
||||
expected_type = func.args[i].type
|
||||
if expected_type.is_pointer and isinstance(arg_val.type, ir.IntType):
|
||||
# Phase 131-15-P1: Convert i64 handle to i8* for C ABI functions
|
||||
# Use nyash.string.to_i8p_h for proper handle-to-pointer conversion
|
||||
# Convert i64 to i8* for C ABI-style functions (print/panic/error).
|
||||
#
|
||||
# IMPORTANT: `print()` in AOT must not interpret unboxed integers as handles
|
||||
# (handle collision prints wrong strings). Use type facts to decide:
|
||||
# - stringish -> treat as handle and bridge via nyash.string.to_i8p_h
|
||||
# - otherwise -> box integer (nyash.box.from_i64) then bridge
|
||||
if arg_val.type.width == 64:
|
||||
i8p = ir.IntType(8).as_pointer()
|
||||
to_i8p = None
|
||||
for f in module.functions:
|
||||
if f.name == "nyash.string.to_i8p_h":
|
||||
to_i8p = f
|
||||
break
|
||||
if not to_i8p:
|
||||
i8p = ir.IntType(8).as_pointer()
|
||||
to_i8p_type = ir.FunctionType(i8p, [ir.IntType(64)])
|
||||
to_i8p = ir.Function(module, to_i8p_type, name="nyash.string.to_i8p_h")
|
||||
arg_val = builder.call(to_i8p, [arg_val], name=f"global_h2p_{i}")
|
||||
|
||||
is_stringish = False
|
||||
try:
|
||||
if resolver is not None and hasattr(resolver, "is_stringish") and resolver.is_stringish(int(arg_id)):
|
||||
is_stringish = True
|
||||
except Exception:
|
||||
is_stringish = False
|
||||
|
||||
v_to_print = arg_val
|
||||
if func_name == "print" and not is_stringish:
|
||||
boxer = None
|
||||
for f in module.functions:
|
||||
if f.name == "nyash.box.from_i64":
|
||||
boxer = f
|
||||
break
|
||||
if boxer is None:
|
||||
boxer = ir.Function(module, ir.FunctionType(ir.IntType(64), [ir.IntType(64)]), name="nyash.box.from_i64")
|
||||
v_to_print = builder.call(boxer, [arg_val], name=f"global_box_i64_{i}")
|
||||
|
||||
arg_val = builder.call(to_i8p, [v_to_print], name=f"global_h2p_{i}")
|
||||
else:
|
||||
arg_val = builder.inttoptr(arg_val, expected_type, name=f"global_i2p_{i}")
|
||||
elif isinstance(expected_type, ir.IntType) and arg_val.type.is_pointer:
|
||||
|
||||
@ -64,13 +64,20 @@ def lower_method_call(builder, module, box_name, method, receiver, args, dst_vid
|
||||
|
||||
# Resolve receiver and arguments
|
||||
def _resolve_arg(vid: int):
|
||||
# Prefer same-block SSA (including function params) from vmap.
|
||||
try:
|
||||
v = vmap.get(vid)
|
||||
if v is not None:
|
||||
return v
|
||||
except Exception:
|
||||
pass
|
||||
if resolver and hasattr(resolver, 'resolve_i64'):
|
||||
try:
|
||||
return resolver.resolve_i64(vid, builder.block, owner.preds,
|
||||
owner.block_end_values, vmap, owner.bb_map)
|
||||
except:
|
||||
pass
|
||||
return vmap.get(vid)
|
||||
return None
|
||||
|
||||
recv_val = _resolve_arg(receiver)
|
||||
if recv_val is None:
|
||||
@ -154,8 +161,16 @@ def lower_method_call(builder, module, box_name, method, receiver, args, dst_vid
|
||||
else:
|
||||
# Generic plugin method invocation
|
||||
method_str = method.encode('utf-8') + b'\0'
|
||||
method_global = ir.GlobalVariable(module, ir.ArrayType(i8, len(method_str)), name=f"unified_method_{method}")
|
||||
method_global.initializer = ir.Constant(ir.ArrayType(i8, len(method_str)), bytearray(method_str))
|
||||
method_gname = f"unified_method_{method}"
|
||||
if method_gname in module.globals:
|
||||
method_global = module.get_global(method_gname)
|
||||
else:
|
||||
method_global = ir.GlobalVariable(
|
||||
module, ir.ArrayType(i8, len(method_str)), name=method_gname
|
||||
)
|
||||
method_global.initializer = ir.Constant(
|
||||
ir.ArrayType(i8, len(method_str)), bytearray(method_str)
|
||||
)
|
||||
method_global.global_constant = True
|
||||
mptr = builder.gep(method_global, [ir.Constant(ir.IntType(32), 0), ir.Constant(ir.IntType(32), 0)])
|
||||
|
||||
|
||||
@ -29,10 +29,7 @@ class PhiManager:
|
||||
"""
|
||||
result = {}
|
||||
for vid, val in vmap.items():
|
||||
if hasattr(val, 'add_incoming'): # Is PHI?
|
||||
if self.is_phi_owned(target_bid, vid):
|
||||
result[vid] = val
|
||||
else:
|
||||
# PHIs are valid SSA values across dominated blocks; keep them in the per-block view.
|
||||
result[vid] = val
|
||||
|
||||
# Phase 132-P0: Add PHIs from predeclared that aren't in vmap yet
|
||||
|
||||
@ -103,9 +103,14 @@ def setup_phi_placeholders(builder, blocks: List[Dict[str, Any]]):
|
||||
dst_type0 = inst.get("dst_type")
|
||||
mark_str = (
|
||||
isinstance(dst_type0, dict)
|
||||
and dst_type0.get("kind") == "handle"
|
||||
and (
|
||||
dst_type0.get("kind") == "string"
|
||||
or (
|
||||
dst_type0.get("kind") == "handle"
|
||||
and dst_type0.get("box_type") == "StringBox"
|
||||
)
|
||||
)
|
||||
)
|
||||
if not mark_str:
|
||||
# JSON v0 incoming pairs are (value, block)
|
||||
for (v_src_i, _b_decl_i) in incoming0:
|
||||
|
||||
@ -267,5 +267,22 @@ def finalize_phis(builder, context):
|
||||
total_dsts += 1
|
||||
wired = wire_incomings(builder, int(block_id), int(dst_vid), incoming, context=context)
|
||||
total_wired += int(wired or 0)
|
||||
# TypeFacts propagation (SSOT): if any incoming source is stringish, mark dst stringish.
|
||||
# This prevents string PHIs from falling back to integer/handle paths (e.g., out += ch).
|
||||
try:
|
||||
if (
|
||||
hasattr(builder, "resolver")
|
||||
and hasattr(builder.resolver, "is_stringish")
|
||||
and hasattr(builder.resolver, "mark_string")
|
||||
):
|
||||
for (_decl_b, v_src) in (incoming or []):
|
||||
try:
|
||||
if builder.resolver.is_stringish(int(v_src)):
|
||||
builder.resolver.mark_string(int(dst_vid))
|
||||
break
|
||||
except Exception:
|
||||
continue
|
||||
except Exception:
|
||||
pass
|
||||
trace({"phi": "finalize", "block": int(block_id), "dst": int(dst_vid), "wired": int(wired or 0)})
|
||||
trace({"phi": "finalize_summary", "blocks": int(total_blocks), "dsts": int(total_dsts), "incoming_wired": int(total_wired)})
|
||||
|
||||
@ -97,7 +97,24 @@ class Resolver:
|
||||
|
||||
def mark_string(self, value_id: int) -> None:
|
||||
try:
|
||||
self.string_ids.add(int(value_id))
|
||||
vid = int(value_id)
|
||||
self.string_ids.add(vid)
|
||||
# TypeFacts SSOT: keep value_types in sync so downstream decisions
|
||||
# (e.g., '+' concat tag checks) can treat this as a StringBox handle.
|
||||
try:
|
||||
if not hasattr(self, 'value_types') or self.value_types is None:
|
||||
self.value_types = {}
|
||||
cur = self.value_types.get(vid) if isinstance(self.value_types, dict) else None
|
||||
is_already_string = False
|
||||
if isinstance(cur, dict):
|
||||
if cur.get('kind') == 'string':
|
||||
is_already_string = True
|
||||
if cur.get('kind') == 'handle' and cur.get('box_type') == 'StringBox':
|
||||
is_already_string = True
|
||||
if not is_already_string and isinstance(self.value_types, dict):
|
||||
self.value_types[vid] = {'kind': 'handle', 'box_type': 'StringBox'}
|
||||
except Exception:
|
||||
pass
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
@ -478,16 +495,11 @@ class Resolver:
|
||||
except Exception:
|
||||
pass
|
||||
if is_phi_val:
|
||||
# Accept PHI only when it belongs to the same block (dominates end-of-block).
|
||||
try:
|
||||
belongs_here = (getattr(getattr(val, 'basic_block', None), 'name', b'').decode() if hasattr(getattr(val, 'basic_block', None), 'name') else str(getattr(getattr(val, 'basic_block', None), 'name', ''))) == f"bb{block_id}"
|
||||
except Exception:
|
||||
belongs_here = False
|
||||
if belongs_here:
|
||||
# PHIs are valid SSA values to carry through snapshots: a PHI defined at a
|
||||
# dominating block head can be used at the end of successor blocks.
|
||||
coerced = self._coerce_in_block_to_i64(val, block_id, bb_map)
|
||||
self._end_i64_cache[key] = coerced
|
||||
return coerced
|
||||
# Otherwise, PHI from wrong block -> treat as miss
|
||||
else:
|
||||
coerced = self._coerce_in_block_to_i64(val, block_id, bb_map)
|
||||
self._end_i64_cache[key] = coerced
|
||||
|
||||
@ -2,6 +2,7 @@ use super::specs;
|
||||
use super::util::dbg_on;
|
||||
use super::PluginLoaderV2;
|
||||
use crate::bid::{BidError, BidResult};
|
||||
use crate::config::env::env_bool;
|
||||
use crate::config::nyash_toml_v2::LibraryDefinition;
|
||||
use crate::runtime::get_global_ring0;
|
||||
use libloading::{Library, Symbol};
|
||||
@ -11,6 +12,10 @@ use std::sync::Arc;
|
||||
|
||||
pub(super) fn load_all_plugins(loader: &PluginLoaderV2) -> BidResult<()> {
|
||||
let config = loader.config.as_ref().ok_or(BidError::PluginError)?;
|
||||
// Strict mode policy (SSOT): reuse JoinIR strict flag to avoid env-var sprawl.
|
||||
// - strict=0: Phase 134 best-effort load (continue on failure)
|
||||
// - strict=1: Fail-Fast on first plugin/library load error
|
||||
let strict = env_bool("HAKO_JOINIR_STRICT");
|
||||
|
||||
// Phase 134 P0: Best-effort loading
|
||||
// Failures don't stop the entire load process
|
||||
@ -24,8 +29,11 @@ pub(super) fn load_all_plugins(loader: &PluginLoaderV2) -> BidResult<()> {
|
||||
for (lib_name, lib_def) in lib_items {
|
||||
match load_plugin(loader, lib_name, lib_def) {
|
||||
Ok(()) => loaded_count += 1,
|
||||
Err(_) => {
|
||||
Err(e) => {
|
||||
failed_count += 1;
|
||||
if strict {
|
||||
return Err(e);
|
||||
}
|
||||
// Log already printed by load_plugin, continue
|
||||
}
|
||||
}
|
||||
@ -38,8 +46,11 @@ pub(super) fn load_all_plugins(loader: &PluginLoaderV2) -> BidResult<()> {
|
||||
for (plugin_name, root) in plugin_items {
|
||||
match load_plugin_from_root(loader, plugin_name, root) {
|
||||
Ok(()) => loaded_count += 1,
|
||||
Err(_) => {
|
||||
Err(e) => {
|
||||
failed_count += 1;
|
||||
if strict {
|
||||
return Err(e);
|
||||
}
|
||||
// Log already printed by load_plugin_from_root, continue
|
||||
}
|
||||
}
|
||||
@ -82,7 +93,24 @@ pub(super) fn load_plugin(
|
||||
}
|
||||
}
|
||||
}
|
||||
let lib_path = lib_path.unwrap_or_else(|| base.to_path_buf());
|
||||
let lib_path = match lib_path {
|
||||
Some(path) => path,
|
||||
None => {
|
||||
let mut attempted = candidates
|
||||
.iter()
|
||||
.map(|p| p.display().to_string())
|
||||
.collect::<Vec<_>>();
|
||||
attempted.sort();
|
||||
attempted.dedup();
|
||||
get_global_ring0().log.error(&format!(
|
||||
"[plugin/missing] {}: no existing file for configured path='{}' (attempted={})",
|
||||
lib_name,
|
||||
base.display(),
|
||||
attempted.join(", ")
|
||||
));
|
||||
return Err(BidError::PluginError);
|
||||
}
|
||||
};
|
||||
if dbg_on() {
|
||||
get_global_ring0().log.debug(&format!(
|
||||
"[PluginLoaderV2] load_plugin: lib='{}' path='{}'",
|
||||
|
||||
@ -20,8 +20,33 @@ fi
|
||||
|
||||
FILEBOX_SO="$NYASH_ROOT/plugins/nyash-filebox-plugin/libnyash_filebox_plugin.so"
|
||||
MAPBOX_SO="$NYASH_ROOT/plugins/nyash-map-plugin/libnyash_map_plugin.so"
|
||||
|
||||
# Phase 98 P0: Ensure required dynamic plugin artifacts exist and are loadable.
|
||||
echo "[INFO] Ensuring plugin artifacts (FileBox/MapBox)"
|
||||
if ! bash "$NYASH_ROOT/tools/plugins/build-all.sh" nyash-filebox-plugin nyash-map-plugin >/dev/null; then
|
||||
echo "[FAIL] tools/plugins/build-all.sh failed for FileBox/MapBox"
|
||||
exit 1
|
||||
fi
|
||||
if [ ! -f "$FILEBOX_SO" ] || [ ! -f "$MAPBOX_SO" ]; then
|
||||
test_skip "Required plugins not found (FileBox/MapBox)"; exit 0
|
||||
echo "[FAIL] Required plugin artifacts still missing after build-all (FileBox/MapBox)"
|
||||
echo "[INFO] FileBox: $FILEBOX_SO"
|
||||
echo "[INFO] MapBox: $MAPBOX_SO"
|
||||
exit 1
|
||||
fi
|
||||
if ! python3 - <<PY 2>/dev/null; then
|
||||
import ctypes
|
||||
ctypes.CDLL(r"$FILEBOX_SO")
|
||||
ctypes.CDLL(r"$MAPBOX_SO")
|
||||
print("OK")
|
||||
PY
|
||||
echo "[FAIL] Plugin dlopen check failed for FileBox/MapBox"
|
||||
python3 - <<PY 2>&1 | tail -n 80
|
||||
import ctypes
|
||||
ctypes.CDLL(r"$FILEBOX_SO")
|
||||
ctypes.CDLL(r"$MAPBOX_SO")
|
||||
print("OK")
|
||||
PY
|
||||
exit 1
|
||||
fi
|
||||
|
||||
mkdir -p "$NYASH_ROOT/tmp"
|
||||
@ -32,7 +57,7 @@ OUTPUT_EXE="$NYASH_ROOT/tmp/phase97_json_loader_escape_llvm_exe"
|
||||
echo "[INFO] Building: $INPUT_HAKO → $OUTPUT_EXE"
|
||||
|
||||
BUILD_LOG="/tmp/phase97_json_loader_escape_build.log"
|
||||
if ! env NYASH_DISABLE_PLUGINS=1 "$NYASH_ROOT/tools/build_llvm.sh" "$INPUT_HAKO" -o "$OUTPUT_EXE" 2>&1 | tee "$BUILD_LOG"; then
|
||||
if ! env NYASH_DISABLE_PLUGINS=0 "$NYASH_ROOT/tools/build_llvm.sh" "$INPUT_HAKO" -o "$OUTPUT_EXE" 2>&1 | tee "$BUILD_LOG"; then
|
||||
echo "[FAIL] build_llvm.sh failed"
|
||||
tail -n 80 "$BUILD_LOG"
|
||||
exit 1
|
||||
@ -47,7 +72,7 @@ fi
|
||||
echo "[INFO] Executing: $OUTPUT_EXE"
|
||||
|
||||
set +e
|
||||
OUTPUT=$(timeout "${RUN_TIMEOUT_SECS:-10}" env NYASH_DISABLE_PLUGINS=1 "$OUTPUT_EXE" 2>&1)
|
||||
OUTPUT=$(timeout "${RUN_TIMEOUT_SECS:-10}" env NYASH_DISABLE_PLUGINS=0 "$OUTPUT_EXE" 2>&1)
|
||||
EXIT_CODE=$?
|
||||
set -e
|
||||
|
||||
|
||||
@ -20,8 +20,33 @@ fi
|
||||
|
||||
FILEBOX_SO="$NYASH_ROOT/plugins/nyash-filebox-plugin/libnyash_filebox_plugin.so"
|
||||
MAPBOX_SO="$NYASH_ROOT/plugins/nyash-map-plugin/libnyash_map_plugin.so"
|
||||
|
||||
# Phase 98 P0: Ensure required dynamic plugin artifacts exist and are loadable.
|
||||
echo "[INFO] Ensuring plugin artifacts (FileBox/MapBox)"
|
||||
if ! bash "$NYASH_ROOT/tools/plugins/build-all.sh" nyash-filebox-plugin nyash-map-plugin >/dev/null; then
|
||||
echo "[FAIL] tools/plugins/build-all.sh failed for FileBox/MapBox"
|
||||
exit 1
|
||||
fi
|
||||
if [ ! -f "$FILEBOX_SO" ] || [ ! -f "$MAPBOX_SO" ]; then
|
||||
test_skip "Required plugins not found (FileBox/MapBox)"; exit 0
|
||||
echo "[FAIL] Required plugin artifacts still missing after build-all (FileBox/MapBox)"
|
||||
echo "[INFO] FileBox: $FILEBOX_SO"
|
||||
echo "[INFO] MapBox: $MAPBOX_SO"
|
||||
exit 1
|
||||
fi
|
||||
if ! python3 - <<PY 2>/dev/null; then
|
||||
import ctypes
|
||||
ctypes.CDLL(r"$FILEBOX_SO")
|
||||
ctypes.CDLL(r"$MAPBOX_SO")
|
||||
print("OK")
|
||||
PY
|
||||
echo "[FAIL] Plugin dlopen check failed for FileBox/MapBox"
|
||||
python3 - <<PY 2>&1 | tail -n 80
|
||||
import ctypes
|
||||
ctypes.CDLL(r"$FILEBOX_SO")
|
||||
ctypes.CDLL(r"$MAPBOX_SO")
|
||||
print("OK")
|
||||
PY
|
||||
exit 1
|
||||
fi
|
||||
|
||||
mkdir -p "$NYASH_ROOT/tmp"
|
||||
@ -32,7 +57,7 @@ OUTPUT_EXE="$NYASH_ROOT/tmp/phase97_next_non_ws_llvm_exe"
|
||||
echo "[INFO] Building: $INPUT_HAKO → $OUTPUT_EXE"
|
||||
|
||||
BUILD_LOG="/tmp/phase97_next_non_ws_build.log"
|
||||
if ! env NYASH_DISABLE_PLUGINS=1 "$NYASH_ROOT/tools/build_llvm.sh" "$INPUT_HAKO" -o "$OUTPUT_EXE" 2>&1 | tee "$BUILD_LOG"; then
|
||||
if ! env NYASH_DISABLE_PLUGINS=0 "$NYASH_ROOT/tools/build_llvm.sh" "$INPUT_HAKO" -o "$OUTPUT_EXE" 2>&1 | tee "$BUILD_LOG"; then
|
||||
echo "[FAIL] build_llvm.sh failed"
|
||||
tail -n 80 "$BUILD_LOG"
|
||||
exit 1
|
||||
@ -47,7 +72,7 @@ fi
|
||||
echo "[INFO] Executing: $OUTPUT_EXE"
|
||||
|
||||
set +e
|
||||
OUTPUT=$(timeout "${RUN_TIMEOUT_SECS:-10}" env NYASH_DISABLE_PLUGINS=1 "$OUTPUT_EXE" 2>&1)
|
||||
OUTPUT=$(timeout "${RUN_TIMEOUT_SECS:-10}" env NYASH_DISABLE_PLUGINS=0 "$OUTPUT_EXE" 2>&1)
|
||||
EXIT_CODE=$?
|
||||
set -e
|
||||
|
||||
|
||||
Reference in New Issue
Block a user