""" Call instruction lowering Handles regular function calls (not BoxCall or ExternCall) """ import llvmlite.ir as ir from typing import Dict, List, Optional, Any from trace import debug as trace_debug from instructions.safepoint import insert_automatic_safepoint from naming_helper import encode_static_method def lower_call( builder: ir.IRBuilder, module: ir.Module, func_name: str, args: List[int], dst_vid: Optional[int], vmap: Dict[int, ir.Value], resolver=None, preds=None, block_end_values=None, bb_map=None, ctx: Optional[Any] = None, ) -> None: """ Lower MIR Call instruction Args: builder: Current LLVM IR builder module: LLVM module func_name: Function name to call args: List of argument value IDs dst_vid: Optional destination for return value vmap: Value map resolver: Optional resolver for type handling """ # If BuildCtx is provided, prefer its maps for consistency. if ctx is not None: try: if getattr(ctx, 'resolver', None) is not None: resolver = ctx.resolver if getattr(ctx, 'preds', None) is not None and preds is None: preds = ctx.preds if getattr(ctx, 'block_end_values', None) is not None and block_end_values is None: block_end_values = ctx.block_end_values if getattr(ctx, 'bb_map', None) is not None and bb_map is None: bb_map = ctx.bb_map except Exception: pass # Insert an automatic safepoint after the function call try: import os if os.environ.get('NYASH_LLVM_AUTO_SAFEPOINT', '1') == '1': insert_automatic_safepoint(builder, module, "function_call") except Exception: pass # Short-hands with ctx (backward-compatible fallback) r = resolver p = preds bev = block_end_values bbm = bb_map if ctx is not None: try: r = getattr(ctx, 'resolver', r) p = getattr(ctx, 'preds', p) bev = getattr(ctx, 'block_end_values', bev) bbm = getattr(ctx, 'bb_map', bbm) except Exception: pass # Resolver helpers (prefer resolver when available) def _res_i64(vid: int): if r is not None and p is not None and bev is not None and bbm is not None: try: return r.resolve_i64(vid, builder.block, p, bev, vmap, bbm) except Exception: return None return vmap.get(vid) def _res_ptr(vid: int): if r is not None and p is not None and bev is not None: try: return r.resolve_ptr(vid, builder.block, p, bev, vmap) except Exception: return None return vmap.get(vid) # Resolve function: accepts string name or value-id referencing a string literal actual_name = func_name if not isinstance(func_name, str): # Try resolver.string_literals if resolver is not None and hasattr(resolver, 'string_literals'): actual_name = resolver.string_literals.get(func_name) # Look up function in module func = None if isinstance(actual_name, str): for f in module.functions: if f.name == actual_name: func = f break if not func: # Function not found - create declaration. Special-case well-known C symbols # (print/println and nyash.console.*) to use pointer-based signature i8* -> i64. i64 = ir.IntType(64) i8p = ir.IntType(8).as_pointer() name = actual_name if isinstance(actual_name, str) else "unknown_fn" is_console = False try: if isinstance(name, str): is_console = (name in ("print", "println")) or name.startswith("nyash.console.") except Exception: is_console = False if is_console: func_type = ir.FunctionType(i64, [i8p]) else: # Default i64(int64,...) prototype func_type = ir.FunctionType(i64, [i64] * len(args)) func = ir.Function(module, func_type, name=name) # If calling a Dev-only predicate name (e.g., 'condition_fn') that lacks a body, # synthesize a trivial definition that returns non-zero to satisfy linker during bring-up. if isinstance(actual_name, str) and actual_name == 'condition_fn': try: if func is not None and len(list(func.blocks)) == 0: b = ir.IRBuilder(func.append_basic_block('entry')) rty = func.function_type.return_type if isinstance(rty, ir.IntType): b.ret(ir.Constant(rty, 1)) else: b.ret_void() except Exception: pass # Prepare arguments call_args = [] for i, arg_id in enumerate(args): arg_val = None if i < len(func.args): expected_type = func.args[i].type if hasattr(expected_type, 'is_pointer') and expected_type.is_pointer: arg_val = _res_ptr(arg_id) else: arg_val = _res_i64(arg_id) if arg_val is None: arg_val = vmap.get(arg_id) if arg_val is None: if i < len(func.args): expected_type = func.args[i].type else: expected_type = ir.IntType(64) if isinstance(expected_type, ir.IntType): arg_val = ir.Constant(expected_type, 0) elif isinstance(expected_type, ir.DoubleType): arg_val = ir.Constant(expected_type, 0.0) else: arg_val = ir.Constant(expected_type, None) if i < len(func.args): expected_type = func.args[i].type if hasattr(arg_val, 'type') and arg_val.type != expected_type: if expected_type.is_pointer and isinstance(arg_val.type, ir.IntType): arg_val = builder.inttoptr(arg_val, expected_type, name=f"call_i2p_{i}") elif isinstance(expected_type, ir.IntType) and arg_val.type.is_pointer: arg_val = builder.ptrtoint(arg_val, expected_type, name=f"call_p2i_{i}") call_args.append(arg_val) # Make the call result = builder.call(func, call_args, name=f"call_{func_name}") # NamingBox SSOT: Optional trace for final debugging traced_names = ( encode_static_method("Main", "node_json", 3), encode_static_method("Main", "esc_json", 1), "main" ) if isinstance(actual_name, str) and actual_name in traced_names: trace_debug(f"[TRACE] call {actual_name} args={len(call_args)}") # Store result if needed if dst_vid is not None: vmap[dst_vid] = result # Heuristic: mark known string-producing functions as string handles try: name_for_tag = actual_name if isinstance(actual_name, str) else str(actual_name) if resolver is not None and hasattr(resolver, 'mark_string'): if any(key in name_for_tag for key in [ 'esc_json', 'node_json', 'dirname', 'join', 'read_all', 'toJson' ]): resolver.mark_string(dst_vid) # Additionally, create a pointer view via bridge for println pointer-API if resolver is not None and hasattr(resolver, 'string_ptrs'): i64 = ir.IntType(64) i8p = ir.IntType(8).as_pointer() if hasattr(result, 'type') and isinstance(result.type, ir.IntType) and result.type.width == 64: bridge = None for f in module.functions: if f.name == 'nyash.string.to_i8p_h': bridge = f; break if bridge is None: bridge = ir.Function(module, ir.FunctionType(i8p, [i64]), name='nyash.string.to_i8p_h') pv = builder.call(bridge, [result], name=f"ret_h2p_{dst_vid}") resolver.string_ptrs[int(dst_vid)] = pv except Exception: pass