feat(llvm): Phase 285LLVM-1.4 - print Handle Resolution (type tag propagation)
Fix LLVM print to output 42 instead of 4 (handle value) for field access. Root cause: Type tags lost through MIR copy instruction chains - getField tagged ValueId 16 as handle - MIR copy chain: 16 → 17 → 18 - print used ValueId 18 (not tagged) → treated as raw integer Solution: Type-tag based handle detection with copy propagation - boxcall.py: Tag getField results as handles - global_call.py: Skip boxing for handles in print - copy.py: Propagate value_types tags through copy chains Test coverage: - apps/tests/phase285_print_raw_int.hako: Raw int regression check - apps/tests/phase285_userbox_field_basic.hako: Field access parity Result: VM/LLVM parity achieved (both output 42) ✅ 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
@ -293,9 +293,20 @@ def lower_boxcall(
|
||||
result = builder.call(callee, [recv_h, mptr, argc, a1, a2], name="pinvoke_by_name")
|
||||
if dst_vid is not None:
|
||||
vmap[dst_vid] = result
|
||||
# Heuristic tagging: common plugin methods returning strings
|
||||
# Type tagging: mark handles for downstream consumers (e.g., print)
|
||||
try:
|
||||
if resolver is not None and hasattr(resolver, 'mark_string') and method_name in ("read", "dirname", "join"):
|
||||
resolver.mark_string(dst_vid)
|
||||
if resolver is not None and hasattr(resolver, 'value_types'):
|
||||
# String-returning plugin methods
|
||||
if hasattr(resolver, 'mark_string') and method_name in ("read", "dirname", "join"):
|
||||
resolver.mark_string(dst_vid)
|
||||
|
||||
# Phase 285LLVM-1.4: Tag getField results as handles
|
||||
# getField returns a handle to the field value (e.g., handle to IntegerBox(42))
|
||||
# This prevents print from boxing the handle itself
|
||||
elif method_name == "getField":
|
||||
if not isinstance(resolver.value_types, dict):
|
||||
resolver.value_types = {}
|
||||
# Mark as generic handle (box_type unknown - could be IntegerBox, StringBox, etc.)
|
||||
resolver.value_types[dst_vid] = {'kind': 'handle'}
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
@ -49,12 +49,21 @@ def lower_copy(
|
||||
print(f"[vmap/id] copy dst={dst} src={src} vmap id={id(vmap)} before_write", file=sys.stderr)
|
||||
safe_vmap_write(vmap, dst, val, "copy", resolver=resolver)
|
||||
|
||||
# TypeFacts propagation (SSOT): preserve "stringish" tagging across Copy.
|
||||
# TypeFacts propagation (SSOT): preserve type tags across Copy.
|
||||
# Many MIR patterns materialize a temp then Copy into a local; without this,
|
||||
# string equality/concat may incorrectly fall back to integer/handle ops.
|
||||
try:
|
||||
if resolver is not None and hasattr(resolver, "is_stringish") and resolver.is_stringish(src):
|
||||
if hasattr(resolver, "mark_string"):
|
||||
resolver.mark_string(dst)
|
||||
if resolver is not None:
|
||||
# Preserve stringish tagging (legacy path)
|
||||
if hasattr(resolver, "is_stringish") and resolver.is_stringish(src):
|
||||
if hasattr(resolver, "mark_string"):
|
||||
resolver.mark_string(dst)
|
||||
|
||||
# Phase 285LLVM-1.4: Propagate general value_types tags (including 'kind': 'handle')
|
||||
# This ensures getField results maintain their handle tag through copy chains
|
||||
if hasattr(resolver, 'value_types') and isinstance(resolver.value_types, dict):
|
||||
src_type = resolver.value_types.get(src)
|
||||
if src_type is not None and isinstance(src_type, dict):
|
||||
resolver.value_types[dst] = src_type.copy() # Copy dict to avoid aliasing
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
@ -100,14 +100,27 @@ def lower_global_call(builder, module, func_name, args, dst_vid, vmap, resolver,
|
||||
to_i8p = ir.Function(module, to_i8p_type, name="nyash.string.to_i8p_h")
|
||||
|
||||
is_stringish = False
|
||||
is_handle = False # Phase 285LLVM-1.4: Track if arg is already a handle
|
||||
|
||||
try:
|
||||
if resolver is not None and hasattr(resolver, "is_stringish") and resolver.is_stringish(int(arg_id)):
|
||||
is_stringish = True
|
||||
except Exception:
|
||||
is_stringish = False
|
||||
|
||||
# Phase 285LLVM-1.4: Check if arg is a handle (don't re-box handles!)
|
||||
try:
|
||||
if resolver is not None and hasattr(resolver, 'value_types') and isinstance(resolver.value_types, dict):
|
||||
arg_type_info = resolver.value_types.get(int(arg_id))
|
||||
if isinstance(arg_type_info, dict) and arg_type_info.get('kind') == 'handle':
|
||||
is_handle = True
|
||||
except Exception:
|
||||
is_handle = False
|
||||
|
||||
v_to_print = arg_val
|
||||
if func_name == "print" and not is_stringish:
|
||||
# Phase 285LLVM-1.4: Only box if NOT stringish AND NOT already a handle
|
||||
if func_name == "print" and not is_stringish and not is_handle:
|
||||
# Raw i64 value: box it before printing
|
||||
boxer = None
|
||||
for f in module.functions:
|
||||
if f.name == "nyash.box.from_i64":
|
||||
|
||||
Reference in New Issue
Block a user