refactor(llvm-py): Phase 3 boxification - Strategic extraction

Extract complex logic while keeping simple parts inline:

1. StringBoxerBox (15 lines)
   - Box string pointers (i8*) to handles (i64)
   - Eliminate function declaration boilerplate
   - Clear single responsibility

2. ReturnPhiSynthesizerBox (101 lines)
   - Synthesize PHI nodes for return values
   - should_synthesize_phi(): Zero-like detection
   - synthesize_phi(): PHI creation with predecessors
   - Respects _disable_phi_synthesis flag

What was NOT boxified (good decisions):
- Fast path vmap lookup (13 lines): Too simple
- Global vmap fallback (7 lines): Too small
- Default value generation (18 lines): Clear as-is

Impact:
- lower_return(): 166→117 lines (-29% reduction)
- File size: 250→352 lines (+102 for organization)
- Testable units: 2→4 (+2 new Boxes)

Tests:
- phase286_pattern5_return_min.hako: PASS (exit 7)
- phase284_p1_return_in_loop_llvm.sh: PASS
- phase284_p2_return_in_loop_llvm.sh: PASS

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2025-12-26 17:12:34 +09:00
parent 32aa0ddf69
commit 5a88c4eb23

View File

@ -81,6 +81,158 @@ class ReturnTypeAdjusterBox:
return ret_val
class StringBoxerBox:
"""
Box-First principle: Single Responsibility - Box string pointers to handles
Converts i8* string pointers to i64 box handles via nyash.box.from_i8_string
"""
@staticmethod
def box_string_pointer(builder: ir.IRBuilder, string_ptr: ir.Value) -> ir.Value:
"""
Box a string pointer (i8*) to handle (i64)
Args:
builder: Current LLVM IR builder
string_ptr: i8* string pointer
Returns:
i64 box handle
"""
i8p = ir.IntType(8).as_pointer()
i64 = ir.IntType(64)
# Find or declare nyash.box.from_i8_string
boxer = None
for f in builder.module.functions:
if f.name == 'nyash.box.from_i8_string':
boxer = f
break
if boxer is None:
boxer = ir.Function(
builder.module,
ir.FunctionType(i64, [i8p]),
name='nyash.box.from_i8_string'
)
return builder.call(boxer, [string_ptr], name='ret_ptr2h')
class ReturnPhiSynthesizerBox:
"""
Box-First principle: Single Responsibility - Synthesize PHI nodes for return values
Creates PHI at block head when return value is zero-like and has predecessors
Phase 131-4: Respects _disable_phi_synthesis flag
"""
@staticmethod
def should_synthesize_phi(ret_val: ir.Value, return_type: ir.Type) -> bool:
"""
Check if return value is zero-like and needs PHI synthesis
Args:
ret_val: Return value to check
return_type: Expected return type
Returns:
True if PHI synthesis is needed
"""
if not isinstance(ret_val, ir.Constant):
return False
# Check if zero-like
if isinstance(return_type, ir.IntType):
return str(ret_val) == str(ir.Constant(return_type, 0))
elif isinstance(return_type, ir.DoubleType):
return str(ret_val) == str(ir.Constant(return_type, 0.0))
elif isinstance(return_type, ir.PointerType):
return str(ret_val) == str(ir.Constant(return_type, None))
return False
@staticmethod
def synthesize_phi(
builder: ir.IRBuilder,
value_id: int,
return_type: ir.Type,
preds: Dict[int, list],
block_end_values: Dict[int, Dict[int, ir.Value]],
bb_map: Dict[int, ir.Block],
resolver=None
) -> Optional[ir.Value]:
"""
Synthesize PHI node at block head for return value
Args:
builder: Current LLVM IR builder
value_id: Value ID to synthesize PHI for
return_type: Expected return type
preds: Predecessor map
block_end_values: Block end value snapshots
bb_map: Block ID to LLVM block map
resolver: Optional resolver for disable flag
Returns:
PHI node if synthesized, None otherwise
"""
# Check if PHI synthesis is disabled (Phase 131-4)
if resolver is not None and hasattr(resolver, '_disable_phi_synthesis'):
if getattr(resolver, '_disable_phi_synthesis', False):
return None
# Derive current block ID from name like 'bb3'
cur_bid = None
try:
cur_bid = int(str(builder.block.name).replace('bb', ''))
except Exception:
return None
if cur_bid is None:
return None
# Collect incoming values from predecessors
incoming = []
for p in preds.get(cur_bid, []):
if p == cur_bid:
continue
v = None
try:
v = block_end_values.get(p, {}).get(value_id)
except Exception:
v = None
if v is None:
v = ir.Constant(return_type, 0)
bblk = bb_map.get(p)
if bblk is not None:
incoming.append((v, bblk))
if not incoming:
return None
# Create PHI at block head
if _phi_at_block_head is not None:
phi = _phi_at_block_head(builder.block, return_type, name=f"ret_phi_{value_id}")
else:
# Fallback: create PHI at block head using a temporary builder
try:
_b = ir.IRBuilder(builder.block)
_b.position_at_start(builder.block)
phi = _b.phi(return_type, name=f"ret_phi_{value_id}")
except Exception:
# As a last resort, create via current builder (may still succeed)
phi = builder.phi(return_type, name=f"ret_phi_{value_id}")
# Add incoming values
for (v, bblk) in incoming:
phi.add_incoming(v, bblk)
return phi
def lower_return(
builder: ir.IRBuilder,
value_id: Optional[int],
@ -155,16 +307,9 @@ def lower_return(
except Exception:
is_stringish = False
if is_stringish and hasattr(resolver, 'string_ptrs') and int(value_id) in getattr(resolver, 'string_ptrs'):
# Delegate to StringBoxerBox (Box-First principle)
p = resolver.string_ptrs[int(value_id)]
i8p = ir.IntType(8).as_pointer()
i64 = ir.IntType(64)
boxer = None
for f in builder.module.functions:
if f.name == 'nyash.box.from_i8_string':
boxer = f; break
if boxer is None:
boxer = ir.Function(builder.module, ir.FunctionType(i64, [i8p]), name='nyash.box.from_i8_string')
ret_val = builder.call(boxer, [p], name='ret_ptr2h')
ret_val = StringBoxerBox.box_string_pointer(builder, p)
else:
ret_val = resolver.resolve_i64(value_id, builder.block, preds, block_end_values, vmap, bb_map)
@ -187,57 +332,15 @@ def lower_return(
# Pointer type - null
ret_val = ir.Constant(return_type, None)
# If still zero-like (typed zero) and we have predecessor snapshots, synthesize a minimal PHI at block head.
# Delegate PHI synthesis to ReturnPhiSynthesizerBox (Box-First principle)
# Phase 131-4: Skip PHI synthesis if disabled (e.g., during Pass C terminator lowering)
try:
disable_phi = False
if resolver is not None and hasattr(resolver, '_disable_phi_synthesis'):
disable_phi = getattr(resolver, '_disable_phi_synthesis', False)
zero_like = False
if isinstance(ret_val, ir.Constant):
if isinstance(return_type, ir.IntType):
zero_like = (str(ret_val) == str(ir.Constant(return_type, 0)))
elif isinstance(return_type, ir.DoubleType):
zero_like = (str(ret_val) == str(ir.Constant(return_type, 0.0)))
elif isinstance(return_type, ir.PointerType):
zero_like = (str(ret_val) == str(ir.Constant(return_type, None)))
# Synthesize a PHI for return at the BLOCK HEAD (grouped), not inline.
if not disable_phi and zero_like and preds is not None and block_end_values is not None and bb_map is not None and isinstance(value_id, int):
# Derive current block id from name like 'bb3'
cur_bid = None
try:
cur_bid = int(str(builder.block.name).replace('bb',''))
except Exception:
cur_bid = None
if cur_bid is not None:
incoming = []
for p in preds.get(cur_bid, []):
if p == cur_bid:
continue
v = None
try:
v = block_end_values.get(p, {}).get(value_id)
except Exception:
v = None
if v is None:
v = ir.Constant(return_type, 0)
bblk = bb_map.get(p)
if bblk is not None:
incoming.append((v, bblk))
if incoming:
if _phi_at_block_head is not None:
phi = _phi_at_block_head(builder.block, return_type, name=f"ret_phi_{value_id}")
else:
# Fallback: create PHI at block head using a temporary builder
try:
_b = ir.IRBuilder(builder.block)
_b.position_at_start(builder.block)
phi = _b.phi(return_type, name=f"ret_phi_{value_id}")
except Exception:
# As a last resort, create via current builder (may still succeed)
phi = builder.phi(return_type, name=f"ret_phi_{value_id}")
for (v, bblk) in incoming:
phi.add_incoming(v, bblk)
if ReturnPhiSynthesizerBox.should_synthesize_phi(ret_val, return_type):
if preds is not None and block_end_values is not None and bb_map is not None and isinstance(value_id, int):
phi = ReturnPhiSynthesizerBox.synthesize_phi(
builder, value_id, return_type, preds, block_end_values, bb_map, resolver
)
if phi is not None:
ret_val = phi
except Exception:
pass