fix(aot/numeric_core): implement PHI type propagation for copy→phi→copy chains

- Add propagate_copy_types() to track MatI64 through copy instructions
- Fix PHI detection bug: indexOf("{") → indexOf("\"op\":\"")
- Add 4-iteration loop for multi-step propagation chains
- Enhance diagnostics with MatI64 vids list and skip reasons

This fixes type propagation for complex SSA patterns where MatI64 types
flow through multiple copy and phi instructions. Small test cases now
pass successfully.

Note: microbench matmul_core still has issues - investigating separately.

🤖 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-15 01:39:13 +09:00
parent 3d082ca131
commit 8d9bbc40bd

View File

@ -22,10 +22,41 @@ static box AotPrepNumericCoreBox {
// Phase 25 MVP: Build type table and transform BoxCall → Call // Phase 25 MVP: Build type table and transform BoxCall → Call
local tmap = AotPrepNumericCoreBox.build_type_table(text, trace) local tmap = AotPrepNumericCoreBox.build_type_table(text, trace)
// Propagate MatI64 type through simple PHI chains全incomingがMatI64ならdstもMatI64とみなす
tmap = AotPrepNumericCoreBox.propagate_phi_types(text, tmap, trace)
local copy_map = AotPrepNumericCoreBox.build_copy_map(text, trace) local copy_map = AotPrepNumericCoreBox.build_copy_map(text, trace)
// Iterative type propagation: alternate copy and phi propagation to handle chains
// like: MatI64.new() → r2 → copy → r10 → phi → r15 → copy → r31
local iter = 0
loop(iter < 4) {
tmap = AotPrepNumericCoreBox.propagate_copy_types(text, tmap, trace)
tmap = AotPrepNumericCoreBox.propagate_phi_types(text, tmap, trace)
iter = iter + 1
}
// Diagnostic: show all MatI64 vids if trace enabled
if trace != null && ("" + trace) == "1" {
local mat_vids = new ArrayBox()
local keys = tmap.keys()
local ki = 0
loop(ki < keys.size()) {
local key = keys.get(ki)
if tmap.get(key) == "MatI64" {
mat_vids.push("r" + key)
}
ki = ki + 1
}
if mat_vids.size() > 0 {
local vids_str = ""
local vi = 0
loop(vi < mat_vids.size()) {
if vi > 0 { vids_str = vids_str + "," }
vids_str = vids_str + mat_vids.get(vi)
vi = vi + 1
}
print("[aot/numeric_core] MatI64 vids: " + vids_str)
}
}
local result = AotPrepNumericCoreBox.transform_boxcalls(text, tmap, copy_map, trace) local result = AotPrepNumericCoreBox.transform_boxcalls(text, tmap, copy_map, trace)
// If nothing happened and trace is ON, surface a hint // If nothing happened and trace is ON, surface a hint
@ -94,13 +125,6 @@ static box AotPrepNumericCoreBox {
} }
} }
} }
} else if op == "copy" {
// Propagate type through copy
local dst = AotPrepNumericCoreBox.read_digits_field(inst, "dst")
local src = AotPrepNumericCoreBox.read_digits_field(inst, "src")
if dst != "" && src != "" && tmap.has(src) {
tmap.set(dst, tmap.get(src))
}
} }
pos = obj_end + 1 pos = obj_end + 1
@ -149,6 +173,42 @@ static box AotPrepNumericCoreBox {
return cmap return cmap
} }
// Propagate MatI64 type through copy instructions
// Scans all copy ops and propagates type from src to dst
propagate_copy_types(text, tmap, trace) {
local pos = 0
loop(true) {
local op_marker = text.indexOf("\"op\":\"", pos)
if op_marker < 0 { break }
local obj_start = text.substring(0, op_marker).lastIndexOf("{")
if obj_start < 0 { pos = op_marker + 1 continue }
local obj_end = AotPrepHelpers._seek_object_end(text, obj_start)
if obj_end <= obj_start { pos = op_marker + 1 continue }
local inst = text.substring(obj_start, obj_end + 1)
local op = AotPrepNumericCoreBox.read_field(inst, "op")
if op == "copy" {
local dst = AotPrepNumericCoreBox.read_digits_field(inst, "dst")
local src = AotPrepNumericCoreBox.read_digits_field(inst, "src")
if dst != "" && src != "" && tmap.has(src) && !tmap.has(dst) {
local src_type = tmap.get(src)
if src_type == "MatI64" {
tmap.set(dst, "MatI64")
if trace != null && ("" + trace) == "1" {
print("[aot/numeric_core] copy-prop MatI64: r" + src + " → r" + dst)
}
}
}
}
pos = obj_end + 1
}
return tmap
}
// Propagate MatI64 type information through phi nodes. // Propagate MatI64 type information through phi nodes.
// Policy: 「少なくとも1つ MatI64 で、かつMatI64以外の型が混ざっていない」場合に dst を MatI64 とみなす。 // Policy: 「少なくとも1つ MatI64 で、かつMatI64以外の型が混ざっていない」場合に dst を MatI64 とみなす。
propagate_phi_types(text, tmap, trace) { propagate_phi_types(text, tmap, trace) {
@ -158,10 +218,17 @@ static box AotPrepNumericCoreBox {
changed = 0 changed = 0
local pos = 0 local pos = 0
loop(true) { loop(true) {
local obj_start = text.indexOf("{", pos) // Find next "op":" marker (same pattern as build_type_table/propagate_copy_types)
if obj_start < 0 { break } local op_marker = text.indexOf("\"op\":\"", pos)
if op_marker < 0 { break }
// Find the start of this instruction object (the { before "op")
local obj_start = text.substring(0, op_marker).lastIndexOf("{")
if obj_start < 0 { pos = op_marker + 1 continue }
// Find the end of this instruction object
local obj_end = AotPrepHelpers._seek_object_end(text, obj_start) local obj_end = AotPrepHelpers._seek_object_end(text, obj_start)
if obj_end <= obj_start { pos = obj_start + 1 continue } if obj_end <= obj_start { pos = op_marker + 1 continue }
local inst = text.substring(obj_start, obj_end + 1) local inst = text.substring(obj_start, obj_end + 1)
local op = AotPrepNumericCoreBox.read_field(inst, "op") local op = AotPrepNumericCoreBox.read_field(inst, "op")
@ -197,7 +264,7 @@ static box AotPrepNumericCoreBox {
tmap.set(dst, "MatI64") tmap.set(dst, "MatI64")
changed = 1 changed = 1
if trace != null && ("" + trace) == "1" { if trace != null && ("" + trace) == "1" {
print("[aot/numeric_core] phi-prop MatI64 at r" + dst) print("[aot/numeric_core] phi-prop MatI64: r" + dst)
} }
} }
} }
@ -259,6 +326,23 @@ static box AotPrepNumericCoreBox {
} }
} }
// Diagnostic logging for skipped transformations
if should_transform == 0 && method == "mul_naive" {
local reason = "unknown"
if box_vid == "" {
reason = "no_box_vid"
} else if !tmap.has(box_vid) {
reason = "type_unknown"
} else if tmap.get(box_vid) != "MatI64" {
reason = "type_conflict (expected MatI64, got " + tmap.get(box_vid) + ")"
}
if trace != null && ("" + trace) == "1" {
local dst = AotPrepNumericCoreBox.read_digits_field(inst, "dst")
print("[aot/numeric_core] skip mul_naive@r" + dst + ": box=r" + box_vid + " reason=" + reason)
}
}
if should_transform == 1 { if should_transform == 1 {
// Transform BoxCall → Call // Transform BoxCall → Call
// Copy text before this boxcall // Copy text before this boxcall