fix(mir): PHI検証panic修正 - update_cfg()を検証前に呼び出し

A案実装: debug_verify_phi_inputs呼び出し前にCFG predecessorを更新

修正箇所(7箇所):
- src/mir/builder/phi.rs:50, 73, 132, 143
- src/mir/builder/ops.rs:273, 328, 351

根本原因:
- Branch/Jump命令でsuccessorは即座に更新
- predecessorはupdate_cfg()で遅延再構築
- PHI検証が先に実行されてpredecessor未更新でpanic

解決策:
- 各debug_verify_phi_inputs呼び出し前に
  if let Some(func) = self.current_function.as_mut() {
      func.update_cfg();
  }
  を挿入してCFGを同期

影響: if/else文、論理演算子(&&/||)のPHI生成が正常動作
This commit is contained in:
nyash-codex
2025-11-01 13:28:56 +09:00
parent 149ec61d4d
commit 6a452b2dca
174 changed files with 2432 additions and 1014 deletions

View File

@ -6,31 +6,31 @@
static box GcBox {
// Return JSON string with counters {safepoints, barrier_reads, barrier_writes}
stats() {
return call("env.gc.stats/0")
return gc.stats()
}
// Return total roots count (host handles + modules), besteffort integer
roots_snapshot() {
return call("env.gc.roots_snapshot/0")
return gc.roots_snapshot()
}
// Request collection (noop until host supports it)
collect() {
// Host may ignore; keep FailFast if extern missing
call("env.gc.collect/0")
gc.collect()
}
// Optional lifecycle hooks (noop unless host implements)
start() { call("env.gc.start/0"); }
stop() { call("env.gc.stop/0"); }
start() { gc.start(); }
stop() { gc.stop(); }
// Example (dev): a tiny cadence policy, OFF by default.
// Enable with: HAKO_GC_POLICY_TICK=1 (docs only; call manually from scripts)
policy_tick() {
if (call("env.local.get/1", "HAKO_GC_POLICY_TICK") == "1") {
if (env.get("HAKO_GC_POLICY_TICK") == "1") {
// Read metrics and print a compact line for observation
local s = me.stats()
call("env.console.log/1", "[GcBox] stats=" + s)
console.log("[GcBox] stats=" + s)
// Example threshold (no-op unless host implements collect):
// me.collect()
}
@ -41,12 +41,12 @@ static box GcBox {
// HAKO_GC_POLICY_FORCE=1 → call collect() each tick (may be no-op; guard expected)
// HAKO_GC_POLICY_EVERY_N=K → best-effort modulus trigger (tick_count % K == 0)
tick_with_policy(tick_count) {
local log = call("env.local.get/1", "HAKO_GC_POLICY_LOG")
local every = call("env.local.get/1", "HAKO_GC_POLICY_EVERY_N")
local force = call("env.local.get/1", "HAKO_GC_POLICY_FORCE")
local log = env.get("HAKO_GC_POLICY_LOG")
local every = env.get("HAKO_GC_POLICY_EVERY_N")
local force = env.get("HAKO_GC_POLICY_FORCE")
if (log == "1") {
call("env.console.log/1", "[GcBox] stats=" + me.stats())
console.log("[GcBox] stats=" + me.stats())
}
if (force == "1") {
// Gate: host may not implement collect() yet

View File

@ -11,44 +11,44 @@ static box ArcBox {
_key(ptr) { return "ARC_" + ("" + ptr) }
_debug_on() {
local d = call("env.local.get/1", "HAKO_DEBUG_ARC")
if d == null || d == "" { d = call("env.local.get/1", "NYASH_DEBUG_ARC") }
local d = env.get("HAKO_DEBUG_ARC")
if d == null || d == "" { d = env.get("NYASH_DEBUG_ARC") }
return d == "1"
}
_log(msg) { if me._debug_on() { call("env.console.log/1", "[ArcBox] " + msg) } }
_log(msg) { if me._debug_on() { console.log("[ArcBox] " + msg) } }
// Read current count (>=0) or -1 if unset/unknown)
_get_count(ptr) { return call("env.arc.count/1", ptr) }
_get_count(ptr) { return arc.count(ptr) }
// Create a new ARC handle with count=1. Fail if already exists.
arc_birth(ptr) {
local c = me._get_count(ptr)
if c >= 0 { call("env.console.error/1", "[arc/already_exists]") return -1 }
call("env.arc.birth/1", ptr)
if c >= 0 { console.error("[arc/already_exists]") return -1 }
arc.birth(ptr)
me._log("birth ptr=" + ("" + ptr) + " -> 1")
return 1
}
// Increment; Fail if unknown.
arc_retain(ptr) {
local c = call("env.arc.retain/1", ptr)
if c < 0 { call("env.console.error/1", "[arc/unknown]") return -1 }
local c = arc.retain(ptr)
if c < 0 { console.error("[arc/unknown]") return -1 }
me._log("retain ptr=" + ("" + ptr) + " -> " + StringHelpers.int_to_str(c))
return c
}
// Decrement; Fail on unknown or underflow. When reaches 0, optionally free via env.mem.free/1
arc_release(ptr) {
local n = call("env.arc.release/1", ptr)
local n = arc.release(ptr)
if n < 0 { return -1 }
me._log("release ptr=" + ("" + ptr) + " -> " + StringHelpers.int_to_str(n))
if n == 0 {
local fz = call("env.local.get/1", "HAKO_ARC_FREE_ON_ZERO")
if fz == null || fz == "" { fz = call("env.local.get/1", "NYASH_ARC_FREE_ON_ZERO") }
local fz = env.get("HAKO_ARC_FREE_ON_ZERO")
if fz == null || fz == "" { fz = env.get("NYASH_ARC_FREE_ON_ZERO") }
if fz == "1" {
// Best-effort free; ignore missing handler
call("env.mem.free/1", ptr)
mem.free(ptr)
}
}
return n

View File

@ -9,18 +9,18 @@ static box RefCellBox {
_key(ptr) { return "ref:" + ("" + ptr) }
_debug_on() {
local d = call("env.local.get/1", "HAKO_DEBUG_REFCELL")
if d == null || d == "" { d = call("env.local.get/1", "NYASH_DEBUG_REFCELL") }
local d = env.get("HAKO_DEBUG_REFCELL")
if d == null || d == "" { d = env.get("NYASH_DEBUG_REFCELL") }
return d == "1"
}
_log(msg) { if me._debug_on() { call("env.console.log/1", "[RefCellBox] " + msg) } }
_log(msg) { if me._debug_on() { console.log("[RefCellBox] " + msg) } }
_get(ptr) {
local s = call("env.local.get/1", me._key(ptr))
local s = env.get(me._key(ptr))
if s == null || s == "" { return 0 }
return StringHelpers.to_i64(s)
}
_set(ptr, n) { call("env.local.set/2", me._key(ptr), StringHelpers.int_to_str(n)) }
_set(ptr, n) { env.set(me._key(ptr), StringHelpers.int_to_str(n)) }
// Initialize state to 0 (idempotent)
init(ptr) { me._set(ptr, 0) return 0 }
@ -28,7 +28,7 @@ static box RefCellBox {
// Shared borrow: allow when state >= 0; increments shared counter.
try_borrow(ptr) {
local st = me._get(ptr)
if st < 0 { call("env.console.error/1", "[refcell/conflict_shared]") return -1 }
if st < 0 { console.error("[refcell/conflict_shared]") return -1 }
me._set(ptr, st + 1)
me._log("borrow ptr=" + ("" + ptr) + " -> " + StringHelpers.int_to_str(st + 1))
return 1
@ -37,7 +37,7 @@ static box RefCellBox {
// Mutable borrow: allow only when state == 0.
try_borrow_mut(ptr) {
local st = me._get(ptr)
if st != 0 { call("env.console.error/1", "[refcell/conflict_mut]") return -1 }
if st != 0 { console.error("[refcell/conflict_mut]") return -1 }
me._set(ptr, -1)
me._log("borrow_mut ptr=" + ("" + ptr) + " -> -1")
return 1
@ -46,7 +46,7 @@ static box RefCellBox {
// Release one shared borrow; Fail if not in shared state
release_shared(ptr) {
local st = me._get(ptr)
if st <= 0 { call("env.console.error/1", "[refcell/release_shared_invalid]") return -1 }
if st <= 0 { console.error("[refcell/release_shared_invalid]") return -1 }
me._set(ptr, st - 1)
me._log("release_shared ptr=" + ("" + ptr) + " -> " + StringHelpers.int_to_str(st - 1))
return st - 1
@ -55,7 +55,7 @@ static box RefCellBox {
// Release mutable borrow; Fail if not in mut state
release_mut(ptr) {
local st = me._get(ptr)
if st != -1 { call("env.console.error/1", "[refcell/release_mut_invalid]") return -1 }
if st != -1 { console.error("[refcell/release_mut_invalid]") return -1 }
me._set(ptr, 0)
me._log("release_mut ptr=" + ("" + ptr) + " -> 0")
return 0

View File

@ -6,17 +6,17 @@ using "lang/src/shared/json/json_utils.hako" as JsonUtilsBox
using "lang/src/shared/common/string_helpers.hako" as StringHelpers
static box JsonShapeToMap {
_empty(){ return map({ using_paths: new ArrayBox(), modules: new ArrayBox(), aliases: map({}), packages: map({}) }) }
_empty(){ return { using_paths: new ArrayBox(), modules: new ArrayBox(), aliases: {}, packages: {} } }
_parse_array_of_strings(arr_json){
local out = new ArrayBox()
if !arr_json || arr_json.size() < 2 { return out }
if !arr_json || arr_json.length() < 2 { return out }
local parts = JsonUtilsBox.split_top_level(arr_json)
local i = 0
loop(i < parts.size()){
loop(i < parts.length()){
local v = StringHelpers.trim(parts.get(i))
if v.size() >= 2 && v.substring(0,1) == "\"" && v.substring(v.size()-1, v.size()) == "\"" {
out.push(JsonUtilsBox.unescape_string(v.substring(1, v.size()-1)))
if v.length() >= 2 && v.substring(0,1) == "\"" && v.substring(v.length()-1, v.length()) == "\"" {
out.push(JsonUtilsBox.unescape_string(v.substring(1, v.length()-1)))
}
i = i + 1
}
@ -26,8 +26,8 @@ static box JsonShapeToMap {
_split_object_pairs(obj_json){
// Like split_top_level for arrays, but for object key:value pairs inside {...}
local out = new ArrayBox()
if !obj_json || obj_json.size() < 2 { return out }
local n = obj_json.size()
if !obj_json || obj_json.length() < 2 { return out }
local n = obj_json.length()
local i = 1
local start = 1
local depth = 0
@ -56,12 +56,12 @@ static box JsonShapeToMap {
_read_key_from_pair(pair_json){
// pair_json: '"key" : value'
local s = StringHelpers.trim(pair_json)
if s.size() < 3 || s.substring(0,1) != "\"" { return null }
if s.length() < 3 || s.substring(0,1) != "\"" { return null }
local end = JsonUtilsBox.skip_string(s, 0)
local raw = s.substring(0, end)
// raw like "\"key\"@pos" from read_string; we used skip_string, so raw lacks marker
// Remove quotes
local key = JsonUtilsBox.unescape_string(raw.substring(1, raw.size()-1))
local key = JsonUtilsBox.unescape_string(raw.substring(1, raw.length()-1))
return key
}
@ -80,15 +80,15 @@ static box JsonShapeToMap {
_parse_modules(mods_json){
local out = new ArrayBox()
if !mods_json || mods_json.size() < 2 { return out }
if !mods_json || mods_json.length() < 2 { return out }
local parts = JsonUtilsBox.split_top_level(mods_json)
local i = 0
loop(i < parts.size()){
loop(i < parts.length()){
local obj = StringHelpers.trim(parts.get(i))
if obj.size() >= 2 && obj.substring(0,1) == "{" {
if obj.length() >= 2 && obj.substring(0,1) == "{" {
local ns = JsonUtilsBox.extract_string_value(obj, "ns", "")
local path = JsonUtilsBox.extract_string_value(obj, "path", "")
out.push(map({ ns: ns, path: path }))
out.push({ ns: ns, path: path })
}
i = i + 1
}
@ -96,18 +96,18 @@ static box JsonShapeToMap {
}
_parse_aliases(obj_json){
local out = map({})
if !obj_json || obj_json.size() < 2 { return out }
local out = {}
if !obj_json || obj_json.length() < 2 { return out }
local pairs = me._split_object_pairs(obj_json)
local i = 0
loop(i < pairs.size()){
loop(i < pairs.length()){
local p = pairs.get(i)
local key = me._read_key_from_pair(p)
local val_raw = me._read_value_from_pair(p)
if key != null && val_raw != null {
local v = StringHelpers.trim(val_raw)
if v.size() >= 2 && v.substring(0,1) == "\"" && v.substring(v.size()-1, v.size()) == "\"" {
out.set(key, JsonUtilsBox.unescape_string(v.substring(1, v.size()-1)))
if v.length() >= 2 && v.substring(0,1) == "\"" && v.substring(v.length()-1, v.length()) == "\"" {
out.set(key, JsonUtilsBox.unescape_string(v.substring(1, v.length()-1)))
}
}
i = i + 1
@ -116,11 +116,11 @@ static box JsonShapeToMap {
}
_parse_packages(obj_json){
local out = map({})
if !obj_json || obj_json.size() < 2 { return out }
local out = {}
if !obj_json || obj_json.length() < 2 { return out }
local pairs = me._split_object_pairs(obj_json)
local i = 0
loop(i < pairs.size()){
loop(i < pairs.length()){
local p = pairs.get(i)
local key = me._read_key_from_pair(p)
local val_raw = me._read_value_from_pair(p)
@ -128,7 +128,7 @@ static box JsonShapeToMap {
local kind = JsonUtilsBox.extract_string_value(val_raw, "kind", "")
local path = JsonUtilsBox.extract_string_value(val_raw, "path", "")
local main = JsonUtilsBox.extract_string_value(val_raw, "main", "")
out.set(key, map({ kind: kind, path: path, main: main }))
out.set(key, { kind: kind, path: path, main: main })
}
i = i + 1
}
@ -148,6 +148,6 @@ static box JsonShapeToMap {
local modules = me._parse_modules(mods_arr)
local aliases = me._parse_aliases(aliases_obj)
local packages = me._parse_packages(packages_obj)
return map({ using_paths: using_paths, modules: modules, aliases: aliases, packages: packages })
return { using_paths: using_paths, modules: modules, aliases: aliases, packages: packages }
}
}

View File

@ -6,13 +6,13 @@ static box UsingResolver {
// Shape: { using_paths: [], modules: [], aliases: {}, packages: {}, policy: {} }
// Behavior-invariant: no I/O, no host-slot dependence.
resolve(_token){
return map({
return {
using_paths: new ArrayBox(),
modules: new ArrayBox(),
aliases: map({}),
packages: map({}),
policy: map({})
});
aliases: {},
packages: {},
policy: {}
};
}
// stats/1 — Return minimal JSON counts (lang-side observability; behavior-invariant)