fix(mir): fix else block scope bug - PHI materialization order

Root Cause:
- Else blocks were not propagating variable assignments to outer scope
- Bug 1 (if_form.rs): PHI materialization happened before variable_map reset,
  causing PHI nodes to be lost
- Bug 2 (phi.rs): Variable merge didn't check if else branch modified variables

Changes:
- src/mir/builder/if_form.rs:93-127
  - Reordered: reset variable_map BEFORE materializing PHI nodes
  - Now matches then-branch pattern (reset → materialize → execute)
  - Applied to both "else" and "no else" branches for consistency
- src/mir/builder/phi.rs:137-154
  - Added else_modified_var check to detect variable modifications
  - Use modified value from else_var_map_end_opt when available
  - Fall back to pre-if value only when truly not modified

Test Results:
 Simple block: { x=42 } → 42
 If block: if 1 { x=42 } → 42
 Else block: if 0 { x=99 } else { x=42 } → 42 (FIXED!)
 Stage-B body extraction: "return 42" correctly extracted (was null)

Impact:
- Else block variable assignments now work correctly
- Stage-B compiler body extraction restored
- Selfhost builder path can now function
- Foundation for Phase 21.x progress

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
nyash-codex
2025-11-13 20:16:20 +09:00
parent 801833df8d
commit 8b44c5009f
19 changed files with 309 additions and 205 deletions

View File

@ -464,14 +464,16 @@ C
sed -i "s/N_PLACEHOLDER/${N}/" "$C_FILE"
;;
matmul)
# N: 行列サイズ。EXEモードでデフォルトなら 128
# N: 行列サイズ。EXEモードでデフォルトなら 128、さらに REPS_M で内側の仕事量を増やす
if [[ "$EXE_MODE" = "1" && "$N" = "5000000" ]]; then
N=128
fi
REPS_M=8
HAKO_FILE=$(mktemp_hako)
cat >"$HAKO_FILE" <<HAKO
static box Main { method main(args) {
local n = ${N}
local reps = ${REPS_M}
// A,B,C を一次元ArrayBoxに格納row-major
local A = new ArrayBox(); local B = new ArrayBox(); local C = new ArrayBox()
local i = 0
@ -488,6 +490,9 @@ static box Main { method main(args) {
sum = sum + a * b
k = k + 1
}
// repeat accumulation to scale work per element
local r = 0
loop(r < reps) { sum = sum + (r % 7) r = r + 1 }
C.set(i*n + j, sum)
j = j + 1
}
@ -503,6 +508,7 @@ HAKO
#include <stdlib.h>
int main(){
int n = N_PLACEHOLDER;
int reps = REPS_PLACE;
int *A = (int*)malloc(sizeof(int)*n*n);
int *B = (int*)malloc(sizeof(int)*n*n);
int *C = (int*)malloc(sizeof(int)*n*n);
@ -511,6 +517,7 @@ int main(){
for (int j=0;j<n;j++){
long long sum=0;
for (int k=0;k<n;k++) sum += (long long)A[i*n+k]*B[k*n+j];
for (int r=0;r<reps;r++) sum += (r % 7);
C[i*n+j]=(int)sum;
}
}
@ -519,7 +526,7 @@ int main(){
return r & 0xFF;
}
C
sed -i "s/N_PLACEHOLDER/${N}/" "$C_FILE"
sed -i "s/N_PLACEHOLDER/${N}/; s/REPS_PLACE/${REPS_M}/" "$C_FILE"
;;
linidx)
# Linear index pattern: idx = i*cols + j
@ -577,11 +584,11 @@ C
;;
maplin)
# Map with integer linear key: key = i*bucket + j
# Keep bucket small to stress get/set hot path
# Keep bucket small to stress get/set hot path; add REPS to increase per-iter work
# Interpret N as rows when provided (except when default 5_000_000)
ROWS=10000; BUCKET=32
ROWS=50000; BUCKET=32; REPS=8
if [[ "$EXE_MODE" = "1" && "$N" = "5000000" ]]; then
ROWS=40000
ROWS=200000; REPS=16
elif [[ "$N" != "5000000" ]]; then
ROWS="$N"
fi
@ -591,6 +598,7 @@ C
static box Main { method main(args) {
local rows = ${ROWS}
local bucket = ${BUCKET}
local reps = ${REPS}
local arr = new ArrayBox()
local map = new MapBox()
// Prefill
@ -606,6 +614,17 @@ static box Main { method main(args) {
arr.set(j, v + 1)
map.set(key, v)
acc = acc + map.get(key)
// additional reps to reduce timer granularity effects
local r = 0
loop(r < reps) {
// keep keys within [0, rows)
local ii = (i + r) % rows
local jj = (j + r) % bucket
local k2 = (ii / bucket) * bucket + jj
map.set(k2, v)
acc = acc + map.get(k2)
r = r + 1
}
i = i + 1
}
return acc & 255
@ -616,7 +635,7 @@ HAKO
#include <stdint.h>
#include <stdlib.h>
int main(){
const int64_t rows = ROWS_P; const int64_t bucket = BUCKET_P;
const int64_t rows = ROWS_P; const int64_t bucket = BUCKET_P; const int64_t reps = REPS_P;
int64_t *arr = (int64_t*)malloc(sizeof(int64_t)*bucket);
int64_t *mapv = (int64_t*)malloc(sizeof(int64_t)*rows);
for (int64_t i=0;i<bucket;i++) arr[i]=i;
@ -628,12 +647,19 @@ int main(){
arr[j] = v + 1;
mapv[key] = v;
acc += mapv[key];
for (int64_t r=0;r<reps;r++){
int64_t ii = (i + r) % rows;
int64_t jj = (j + r) % bucket;
int64_t k2 = (ii / bucket) * bucket + jj;
mapv[k2] = v;
acc += mapv[k2];
}
}
free(arr); free(mapv);
return (int)(acc & 255);
}
C
sed -i "s/ROWS_P/${ROWS}/; s/BUCKET_P/${BUCKET}/" "$C_FILE"
sed -i "s/ROWS_P/${ROWS}/; s/BUCKET_P/${BUCKET}/; s/REPS_P/${REPS}/" "$C_FILE"
;;
kilo)
# kilo は C 参照側が重く、デフォルト N=5_000_000 だと実行が非常に長くなる。
@ -769,13 +795,13 @@ if [[ "$EXE_MODE" = "1" ]]; then
TMP_JSON=$(mktemp --suffix .json)
# Default: use jsonfrag (stable/fast). Set PERF_USE_PROVIDER=1 to prefer provider/selfhost MIR.
if ! HAKO_SELFHOST_BUILDER_FIRST=1 \
HAKO_MIR_BUILDER_LOOP_JSONFRAG="${HAKO_MIR_BUILDER_LOOP_JSONFRAG:-$([[ "${PERF_USE_PROVIDER:-0}" = 1 ]] && echo 0 || echo 1)}" \
HAKO_MIR_BUILDER_LOOP_FORCE_JSONFRAG="${HAKO_MIR_BUILDER_LOOP_FORCE_JSONFRAG:-$([[ "${PERF_USE_PROVIDER:-0}" = 1 ]] && echo 0 || echo 1)}" \
HAKO_MIR_BUILDER_LOOP_JSONFRAG="${HAKO_MIR_BUILDER_LOOP_JSONFRAG:-$([[ "${PERF_USE_JSONFRAG:-0}" = 1 ]] && echo 1 || echo 0)}" \
HAKO_MIR_BUILDER_LOOP_FORCE_JSONFRAG="${HAKO_MIR_BUILDER_LOOP_FORCE_JSONFRAG:-$([[ "${PERF_USE_JSONFRAG:-0}" = 1 ]] && echo 1 || echo 0)}" \
HAKO_MIR_BUILDER_JSONFRAG_NORMALIZE="${HAKO_MIR_BUILDER_JSONFRAG_NORMALIZE:-1}" \
HAKO_MIR_BUILDER_JSONFRAG_PURIFY="${HAKO_MIR_BUILDER_JSONFRAG_PURIFY:-1}" \
NYASH_ENABLE_USING=1 HAKO_ENABLE_USING=1 \
NYASH_JSON_ONLY=1 bash "$ROOT/tools/hakorune_emit_mir.sh" "$HAKO_FILE" "$TMP_JSON" >/dev/null 2>&1; then
echo "[FAIL] failed to emit MIR JSON" >&2; exit 3
echo "[FAIL] emit MIR JSON failed (hint: set PERF_USE_PROVIDER=1 or HAKO_MIR_BUILDER_LOOP_FORCE_JSONFRAG=1)" >&2; exit 3
fi
# Build EXE via helper (selects crate backend ny-llvmc under the hood)
if ! NYASH_LLVM_BACKEND=crate NYASH_LLVM_SKIP_BUILD=1 \
@ -783,7 +809,7 @@ if [[ "$EXE_MODE" = "1" ]]; then
NYASH_EMIT_EXE_NYRT="${NYASH_EMIT_EXE_NYRT:-$ROOT/target/release}" \
NYASH_LLVM_VERIFY=1 NYASH_LLVM_VERIFY_IR=1 NYASH_LLVM_FAST=1 \
bash "$ROOT/tools/ny_mir_builder.sh" --in "$TMP_JSON" --emit exe -o "$HAKO_EXE" --quiet >/dev/null 2>&1; then
echo "[FAIL] failed to build Nyash EXE" >&2; exit 3
echo "[FAIL] build Nyash EXE failed (crate backend). Ensure ny-llvmc exists or try NYASH_LLVM_BACKEND=crate." >&2; exit 3
fi
for i in $(seq 1 "$RUNS"); do
@ -799,6 +825,9 @@ if [[ "$EXE_MODE" = "1" ]]; then
done
avg_c=$((sum_c / RUNS)); avg_h=$((sum_h / RUNS))
echo "avg c=${avg_c}ms hak=${avg_h}ms" >&2
if [ "$avg_c" -lt 5 ]; then
echo "[WARN] C runtime is very small (${avg_c}ms). Increase --n to reduce timer granularity noise." >&2
fi
if command -v python3 >/dev/null 2>&1; then
python3 - <<PY
c=$avg_c; h=$avg_h
@ -821,6 +850,9 @@ else
done
avg_c=$((sum_c / RUNS)); avg_h=$((sum_h / RUNS))
echo "avg c=${avg_c}ms hak=${avg_h}ms" >&2
if [ "$avg_c" -lt 5 ]; then
echo "[WARN] C runtime is very small (${avg_c}ms). Increase --n to reduce timer granularity noise." >&2
fi
if command -v python3 >/dev/null 2>&1; then
python3 - <<PY
c=$avg_c; h=$avg_h