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:
@ -8,6 +8,7 @@
|
||||
using selfhost.shared.mir.io as MirIoBox
|
||||
using selfhost.shared.common.string_helpers as StringHelpers
|
||||
using selfhost.shared.json.utils.json_frag as JsonFragBox
|
||||
using selfhost.llvm.ir.aot_prep.helpers.common as AotPrepHelpers
|
||||
// Modular normalizers (opt-in, default OFF)
|
||||
using selfhost.llvm.ir.normalize.print as NormalizePrintBox
|
||||
using selfhost.llvm.ir.normalize.ref as NormalizeRefBox
|
||||
@ -229,7 +230,7 @@ static box AotPrepBox {
|
||||
local lhs_val = const_vals.contains(lhs) ? const_vals[lhs] : ""
|
||||
local rhs_val = const_vals.contains(rhs) ? const_vals[rhs] : ""
|
||||
if lhs_val == "" || rhs_val == "" { continue }
|
||||
local computed = AotPrepBox._evaluate_binop_constant(operation, lhs_val, rhs_val)
|
||||
local computed = AotPrepHelpers.evaluate_binop_constant(operation, lhs_val, rhs_val)
|
||||
if computed == "" { continue }
|
||||
const_defs[dst] = inst
|
||||
const_vals[dst] = computed
|
||||
@ -286,29 +287,7 @@ static box AotPrepBox {
|
||||
return out
|
||||
}
|
||||
|
||||
_evaluate_binop_constant(operation, lhs_val, rhs_val) {
|
||||
if operation == "" { return "" }
|
||||
local li = StringHelpers.to_i64(lhs_val)
|
||||
local ri = StringHelpers.to_i64(rhs_val)
|
||||
if li == null || ri == null { return "" }
|
||||
local res = null
|
||||
if operation == "add" || operation == "+" {
|
||||
res = li + ri
|
||||
} else if operation == "sub" || operation == "-" {
|
||||
res = li - ri
|
||||
} else if operation == "mul" || operation == "*" {
|
||||
res = li * ri
|
||||
} else if operation == "sdiv" || operation == "div" || operation == "/" {
|
||||
if ri == 0 { return "" }
|
||||
res = li / ri
|
||||
} else if operation == "srem" || operation == "rem" || operation == "%" {
|
||||
if ri == 0 { return "" }
|
||||
res = li % ri
|
||||
} else {
|
||||
return ""
|
||||
}
|
||||
return StringHelpers.int_to_str(res)
|
||||
}
|
||||
// evaluate_binop_constant is provided by AotPrepHelpers
|
||||
|
||||
// 内部: 最小の安全畳み込み(JSON文字列ベース)
|
||||
_try_fold_const_binop_ret(json) {
|
||||
|
||||
@ -88,19 +88,22 @@ static box AotPrepHelpers {
|
||||
local lhs = StringHelpers.read_digits(inst, lhs_pos + 6)
|
||||
local rhs = StringHelpers.read_digits(inst, rhs_pos + 6)
|
||||
local op = JsonFragBox.read_string_after(inst, op_key + 13)
|
||||
// Treat +,-,* with one const and one linear as linear
|
||||
if lhs != "" && rhs != "" && (op == "+" || op == "-" || op == "*" || op == "add" || op == "sub" || op == "mul") {
|
||||
// + / - : sum/difference of linear terms remains linear
|
||||
if lhs != "" && rhs != "" && (op == "+" || op == "-" || op == "add" || op == "sub") {
|
||||
if me._linear_expr(json, lhs, depth + 1) && me._linear_expr(json, rhs, depth + 1) { return true }
|
||||
if me.is_const_vid(json, lhs) && me._linear_expr(json, rhs, depth + 1) { return true }
|
||||
if me.is_const_vid(json, rhs) && me._linear_expr(json, lhs, depth + 1) { return true }
|
||||
}
|
||||
// Heuristic: allow div/rem with a const side as linear
|
||||
// * : const * linear は線形、linear*linear は非線形扱い
|
||||
if lhs != "" && rhs != "" && (op == "*" || op == "mul") {
|
||||
if me.is_const_vid(json, lhs) && me._linear_expr(json, rhs, depth + 1) { return true }
|
||||
if me.is_const_vid(json, rhs) && me._linear_expr(json, lhs, depth + 1) { return true }
|
||||
}
|
||||
// div/rem: linear / const, linear % const を線形扱い(ヒューリスティク)
|
||||
if lhs != "" && rhs != "" && (op == "/" || op == "div" || op == "sdiv" || op == "%" || op == "rem" || op == "srem") {
|
||||
// div: either linear/const or const/linear
|
||||
if (op == "/" || op == "div" || op == "sdiv") {
|
||||
if me._linear_expr(json, lhs, depth + 1) && me.is_const_vid(json, rhs) { return true }
|
||||
if me.is_const_vid(json, lhs) && me._linear_expr(json, rhs, depth + 1) { return true }
|
||||
} else {
|
||||
// rem: only accept linear % const (mod by const)
|
||||
if me._linear_expr(json, lhs, depth + 1) && me.is_const_vid(json, rhs) { return true }
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
// AotPrepBinopCSEBox — common subexpression elimination for binops (text-level)
|
||||
using selfhost.shared.json.utils.json_frag as JsonFragBox
|
||||
using selfhost.shared.common.string_helpers as StringHelpers
|
||||
using selfhost.llvm.ir.aot_prep.helpers.common as AotPrepHelpers
|
||||
|
||||
static box AotPrepBinopCSEBox {
|
||||
run(json) {
|
||||
@ -33,7 +34,7 @@ static box AotPrepBinopCSEBox {
|
||||
loop(true) {
|
||||
local os = body.indexOf("{", i)
|
||||
if os < 0 { break }
|
||||
local oe = me._seek_object_end(body, os)
|
||||
local oe = AotPrepHelpers._seek_object_end(body, os)
|
||||
if oe < 0 { break }
|
||||
insts.push(body.substring(os, oe+1))
|
||||
i = oe + 1
|
||||
@ -112,30 +113,5 @@ static box AotPrepBinopCSEBox {
|
||||
return out
|
||||
}
|
||||
|
||||
_seek_object_end(s, start) {
|
||||
if s == null { return -1 }
|
||||
if start < 0 || start >= s.length() { return -1 }
|
||||
if s.substring(start, start+1) != "{" { return -1 }
|
||||
local i = start
|
||||
local depth = 0
|
||||
local in_str = 0
|
||||
local esc = 0
|
||||
loop (i < s.length()) {
|
||||
local ch = s.substring(i, i+1)
|
||||
if in_str == 1 {
|
||||
if esc == 1 { esc = 0 }
|
||||
else if ch == "\\" { esc = 1 }
|
||||
else if ch == "\"" { in_str = 0 }
|
||||
} else {
|
||||
if ch == "\"" { in_str = 1 }
|
||||
else if ch == "{" { depth = depth + 1 }
|
||||
else if ch == "}" {
|
||||
depth = depth - 1
|
||||
if depth == 0 { return i }
|
||||
}
|
||||
}
|
||||
i = i + 1
|
||||
}
|
||||
return -1
|
||||
}
|
||||
// _seek_object_end moved to AotPrepHelpers
|
||||
}
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
// AotPrepCollectionsHotBox — rewrite Array/Map boxcall to externcall hot paths (AOT-only)
|
||||
using selfhost.shared.json.utils.json_frag as JsonFragBox
|
||||
using selfhost.shared.common.string_helpers as StringHelpers
|
||||
using selfhost.llvm.ir.aot_prep.helpers.common as AotPrepHelpers // for is_const_or_linear
|
||||
using selfhost.llvm.ir.aot_prep.helpers.common as AotPrepHelpers // for is_const_or_linear and _seek_object_end
|
||||
|
||||
static box AotPrepCollectionsHotBox {
|
||||
run(json) {
|
||||
@ -64,7 +64,7 @@ static box AotPrepCollectionsHotBox {
|
||||
if last < 0 { return "" }
|
||||
local os = text.lastIndexOf("{", last)
|
||||
if os < 0 { return "" }
|
||||
local oe = me._seek_object_end(text, os)
|
||||
local oe = AotPrepHelpers._seek_object_end(text, os)
|
||||
if oe < 0 || oe >= k { return "" }
|
||||
local inst = text.substring(os, oe+1)
|
||||
if inst.indexOf("\"op\":\"const\"") >= 0 {
|
||||
@ -106,7 +106,7 @@ static box AotPrepCollectionsHotBox {
|
||||
local abs = block_lb + p
|
||||
local os = text.lastIndexOf("{", abs)
|
||||
if os < 0 { break }
|
||||
local oe = me._seek_object_end(text, os)
|
||||
local oe = AotPrepHelpers._seek_object_end(text, os)
|
||||
if oe < 0 || oe >= k { break }
|
||||
local inst = text.substring(os, oe+1)
|
||||
if inst.indexOf("\"method\":\"set\"") >= 0 {
|
||||
@ -125,6 +125,33 @@ static box AotPrepCollectionsHotBox {
|
||||
}
|
||||
return ""
|
||||
}
|
||||
// helper: find last set(map,a0,*) key vid for same receiver inside block
|
||||
local find_last_set_key_in_block = fun(text, block_lb, k, recv_vid) {
|
||||
if recv_vid == "" { return "" }
|
||||
local slice = text.substring(block_lb, k)
|
||||
local p = slice.lastIndexOf("\"op\":\"boxcall\"")
|
||||
while p >= 0 {
|
||||
local abs = block_lb + p
|
||||
local os = text.lastIndexOf("{", abs)
|
||||
if os < 0 { break }
|
||||
local oe = AotPrepHelpers._seek_object_end(text, os)
|
||||
if oe < 0 || oe >= k { break }
|
||||
local inst = text.substring(os, oe+1)
|
||||
if inst.indexOf("\"method\":\"set\"") >= 0 {
|
||||
local kbox = inst.indexOf("\"box\":")
|
||||
local bid = (kbox>=0 ? StringHelpers.read_digits(inst, kbox+6) : "")
|
||||
if bid == recv_vid {
|
||||
local kargs = inst.indexOf("\"args\":[")
|
||||
if kargs >= 0 {
|
||||
local keyvid = StringHelpers.read_digits(inst, kargs+8)
|
||||
if keyvid != "" { return keyvid }
|
||||
}
|
||||
}
|
||||
}
|
||||
p = slice.lastIndexOf("\"op\":\"boxcall\"", p-1)
|
||||
}
|
||||
return ""
|
||||
}
|
||||
local pos2 = 0
|
||||
local seen_key_vid = {}
|
||||
loop(true){
|
||||
@ -162,6 +189,10 @@ static box AotPrepCollectionsHotBox {
|
||||
// Safe set->get index reuse inside same block
|
||||
local prev_idx = find_last_set_index_in_block(out, lb, k, bvid)
|
||||
if prev_idx != "" { a0 = prev_idx }
|
||||
} else if is_map && mname == "get" {
|
||||
// Fallback: reuse last set key vid inside block for same map receiver
|
||||
local prev_key = find_last_set_key_in_block(out, lb, k, bvid)
|
||||
if prev_key != "" { a0 = prev_key }
|
||||
}
|
||||
}
|
||||
} while(false)
|
||||
@ -190,7 +221,7 @@ static box AotPrepCollectionsHotBox {
|
||||
if func == "" { pos2 = k + 1; continue }
|
||||
local obj_start = out.lastIndexOf("{", k)
|
||||
if obj_start < 0 { pos2 = k + 1; continue }
|
||||
local obj_end = me._seek_object_end(out, obj_start)
|
||||
local obj_end = AotPrepHelpers._seek_object_end(out, obj_start)
|
||||
if obj_end < 0 { pos2 = k + 1; continue }
|
||||
local dst_part = (dvid != "" ? ("\"dst\":" + dvid + ",") : "")
|
||||
local repl = "{" + dst_part + "\"op\":\"externcall\",\"func\":\"" + func + "\",\"args\":[" + args + "]}"
|
||||
@ -200,27 +231,5 @@ static box AotPrepCollectionsHotBox {
|
||||
return out
|
||||
}
|
||||
|
||||
_seek_object_end(s, start) {
|
||||
if s == null { return -1 }
|
||||
if start < 0 || start >= s.length() { return -1 }
|
||||
if s.substring(start, start+1) != "{" { return -1 }
|
||||
local i = start
|
||||
local depth = 0
|
||||
local in_str = 0
|
||||
local esc = 0
|
||||
loop (i < s.length()) {
|
||||
local ch = s.substring(i, i+1)
|
||||
if in_str == 1 {
|
||||
if esc == 1 { esc = 0 }
|
||||
else if ch == "\\" { esc = 1 }
|
||||
else if ch == "\"" { in_str = 0 }
|
||||
} else {
|
||||
if ch == "\"" { in_str = 1 }
|
||||
else if ch == "{" { depth = depth + 1 }
|
||||
else if ch == "}" { depth = depth - 1 if depth == 0 { return i } }
|
||||
}
|
||||
i = i + 1
|
||||
}
|
||||
return -1
|
||||
}
|
||||
// _seek_object_end moved to AotPrepHelpers
|
||||
}
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
using selfhost.shared.json.utils.json_frag as JsonFragBox
|
||||
using selfhost.shared.common.string_helpers as StringHelpers
|
||||
using selfhost.llvm.ir.aot_prep.helpers.common as AotPrepHelpers
|
||||
|
||||
static box AotPrepConstDedupBox {
|
||||
run(json) {
|
||||
@ -24,7 +25,7 @@ static box AotPrepConstDedupBox {
|
||||
break
|
||||
}
|
||||
new_body = new_body + body.substring(i, os)
|
||||
local oe = me._seek_object_end(body, os)
|
||||
local oe = AotPrepHelpers._seek_object_end(body, os)
|
||||
if oe < 0 {
|
||||
new_body = new_body + body.substring(os, body.length())
|
||||
break
|
||||
@ -58,30 +59,5 @@ static box AotPrepConstDedupBox {
|
||||
return out
|
||||
}
|
||||
|
||||
_seek_object_end(s, start) {
|
||||
if s == null { return -1 }
|
||||
if start < 0 || start >= s.length() { return -1 }
|
||||
if s.substring(start, start+1) != "{" { return -1 }
|
||||
local i = start
|
||||
local depth = 0
|
||||
local in_str = 0
|
||||
local esc = 0
|
||||
loop (i < s.length()) {
|
||||
local ch = s.substring(i, i+1)
|
||||
if in_str == 1 {
|
||||
if esc == 1 { esc = 0 }
|
||||
else if ch == "\\" { esc = 1 }
|
||||
else if ch == "\"" { in_str = 0 }
|
||||
} else {
|
||||
if ch == "\"" { in_str = 1 }
|
||||
else if ch == "{" { depth = depth + 1 }
|
||||
else if ch == "}" {
|
||||
depth = depth - 1
|
||||
if depth == 0 { return i }
|
||||
}
|
||||
}
|
||||
i = i + 1
|
||||
}
|
||||
return -1
|
||||
}
|
||||
// _seek_object_end moved to AotPrepHelpers
|
||||
}
|
||||
|
||||
@ -14,7 +14,7 @@ static box AotPrepLoopHoistBox {
|
||||
loop(true) {
|
||||
local os = body.indexOf("{", i)
|
||||
if os < 0 { break }
|
||||
local oe = me._seek_object_end(body, os)
|
||||
local oe = AotPrepHelpers._seek_object_end(body, os)
|
||||
if oe < 0 { break }
|
||||
items.push(body.substring(os, oe+1))
|
||||
i = oe + 1
|
||||
@ -111,27 +111,5 @@ static box AotPrepLoopHoistBox {
|
||||
return out
|
||||
}
|
||||
|
||||
_seek_object_end(s, start) {
|
||||
if s == null { return -1 }
|
||||
if start < 0 || start >= s.length() { return -1 }
|
||||
if s.substring(start, start+1) != "{" { return -1 }
|
||||
local i = start
|
||||
local depth = 0
|
||||
local in_str = 0
|
||||
local esc = 0
|
||||
loop (i < s.length()) {
|
||||
local ch = s.substring(i, i+1)
|
||||
if in_str == 1 {
|
||||
if esc == 1 { esc = 0 }
|
||||
else if ch == "\\" { esc = 1 }
|
||||
else if ch == "\"" { in_str = 0 }
|
||||
} else {
|
||||
if ch == "\"" { in_str = 1 }
|
||||
else if ch == "{" { depth = depth + 1 }
|
||||
else if ch == "}" { depth = depth - 1 if depth == 0 { return i } }
|
||||
}
|
||||
i = i + 1
|
||||
}
|
||||
return -1
|
||||
}
|
||||
// _seek_object_end moved to AotPrepHelpers
|
||||
}
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
// AotPrepStrlenBox — fold length/len for known StringBox receivers (JSON text)
|
||||
using selfhost.shared.json.utils.json_frag as JsonFragBox
|
||||
using selfhost.shared.common.string_helpers as StringHelpers
|
||||
using selfhost.llvm.ir.aot_prep.helpers.common as AotPrepHelpers
|
||||
|
||||
static box AotPrepStrlenBox {
|
||||
run(json) {
|
||||
@ -78,7 +79,7 @@ static box AotPrepStrlenBox {
|
||||
if dvid == "" { pos3 = k + 1; continue }
|
||||
local obj_start = out.lastIndexOf("{", k)
|
||||
if obj_start < 0 { pos3 = k + 1; continue }
|
||||
local obj_end = me._seek_object_end(out, obj_start)
|
||||
local obj_end = AotPrepHelpers._seek_object_end(out, obj_start)
|
||||
if obj_end < 0 { pos3 = k + 1; continue }
|
||||
local blen = recv_len[bvid]
|
||||
local repl = "{\"op\":\"const\",\"dst\":" + dvid + ",\"value\":{\"type\":\"i64\",\"value\":" + StringHelpers.int_to_str(blen) + "}}"
|
||||
@ -88,27 +89,5 @@ static box AotPrepStrlenBox {
|
||||
return out
|
||||
}
|
||||
|
||||
_seek_object_end(s, start) {
|
||||
if s == null { return -1 }
|
||||
if start < 0 || start >= s.length() { return -1 }
|
||||
if s.substring(start, start+1) != "{" { return -1 }
|
||||
local i = start
|
||||
local depth = 0
|
||||
local in_str = 0
|
||||
local esc = 0
|
||||
loop (i < s.length()) {
|
||||
local ch = s.substring(i, i+1)
|
||||
if in_str == 1 {
|
||||
if esc == 1 { esc = 0 }
|
||||
else if ch == "\\" { esc = 1 }
|
||||
else if ch == "\"" { in_str = 0 }
|
||||
} else {
|
||||
if ch == "\"" { in_str = 1 }
|
||||
else if ch == "{" { depth = depth + 1 }
|
||||
else if ch == "}" { depth = depth - 1 if depth == 0 { return i } }
|
||||
}
|
||||
i = i + 1
|
||||
}
|
||||
return -1
|
||||
}
|
||||
// _seek_object_end moved to AotPrepHelpers
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user