import lib.core.Gen.GenTypes as GenTypes from stdint import * import ast import mlirlite import memhub import string import viperlib import lib.core.Gen.Translator as HT import lib.core.Gen.GenApi as GenApi from . import CallGenBase # CallGenFd - split from CallGenMixin (stage B): FakeDuck / requires field injection / provider stack novirtual class CallGenFd(CallGenBase.CallGenBase): """FakeDuck / requires field injection / provider stack""" def _translate_fakeduck_call(self, pool: memhub.MemBuddy*, builder: mlirlite.IRBuilder*, mod: mlirlite.LLVMModule*, cls_name: str, method_name: str, cl: ast.Call*, trans: HT.Translator*) -> mlirlite.Value*: """FakeDuck: resolve a str variable's method call to extension class _Type/_str, return the call result or None""" if pool is None or builder is None or cl is None or trans is None: return None if method_name is None: return None # Only trigger for str (AnnotClassName == "str", object is char*); # also try when cls_name is None (receiver resolution failed). if cls_name is not None and string.strcmp(cls_name, "str") != 0: return None if cl.func is None or cl.func.kind() != ast.ASTKind.Attribute: return None fd_at: ast.Attribute* = ast.Attribute*(cl.func) if fd_at is None or fd_at.value is None or fd_at.attr is None: return None if string.strcmp(fd_at.attr, method_name) != 0: return None # Candidate extension classes: _Type first (a.type_id()), then _str (a.upper() etc.) fd_func: mlirlite.Function* = None fd_cls: str = None fd_key: str = pool.alloc(160) if fd_key is not None: for fd_ci in range(2): fd_cn: str = "_Type" if fd_ci == 1: fd_cn = "_str" viperlib.snprintf(fd_key, 160, "%s.%s", fd_cn, method_name) fd_func = self.find_func_in_module(mod, fd_key) if fd_func is not None: fd_cls = fd_cn break if fd_func is None or fd_cls is None: return None # The extension class struct must be registered (GEP/temp shadow needs a type) fd_sty: mlirlite.LLVMType* = self.Gen.StructGen.get_struct_type(fd_cls) if fd_sty is None: return None # Extension class field count: with fields (_str) self uses a shadow struct pointer; # without fields (_Type) self bitcasts the object value to the extension pointer (matches TPC L2856-2876). fd_entry_fd: GenTypes.StructEntry* = self.Gen.StructGen.find_struct_by_type(fd_sty) fd_has_fields: int = 0 if fd_entry_fd is not None and fd_entry_fd.FieldCount > 0: fd_has_fields = 1 # self arg: prefer an existing shadow {var}__meta__ (created by fd2, only when the extension class has fields) fd_self: mlirlite.Value* = None if fd_at.value.kind() == ast.ASTKind.Name: fd_onm: ast.Name* = ast.Name*(fd_at.value) if fd_onm.id is not None: # Pool-persistent buffer (an f-string stack buffer would expire after return) fd_mn: str = pool.alloc(256) if fd_mn is not None: viperlib.snprintf(fd_mn, 256, "%s__meta__", fd_onm.id) fd_meta: mlirlite.Value* = self.Gen.VarGen.lookup_current(trans.SymTab, fd_mn) if fd_meta is not None and fd_has_fields != 0: fd_self = fd_meta if fd_self is None: if fd_has_fields != 0: # Fielded extension class with no shadow (string literal etc.): create and init a temp # shadow at entry; first ensure full _str definition (cross-module structs default to opaque and alloca would report unsized). self.Gen.StructGen.ensure_struct_def_in_module(pool, mod, fd_sty) fd_self = self.Gen.VarGen._alloca_at_entry(builder, fd_sty) if fd_self is None: return None fd_obj_val: mlirlite.Value* = self.Gen.ExprGen.translate_value(builder, pool, mod, fd_at.value, None, 0, trans) if fd_obj_val is not None: fd_dfe: GenTypes.FieldEntry* = self.Gen.StructGen.lookup_field(fd_sty, "__data__") if fd_dfe is not None and fd_dfe.Ty is not None: fd_dp: mlirlite.Value* = mlirlite.build_gep_struct(builder, fd_sty, fd_dfe.Ty, fd_self, fd_dfe.Index) if fd_dp is not None: mlirlite.build_store(builder, fd_obj_val, fd_dp) fd_bfe: GenTypes.FieldEntry* = self.Gen.StructGen.lookup_field(fd_sty, "__mbuddy__") if fd_bfe is not None and fd_bfe.Ty is not None: fd_bp: mlirlite.Value* = mlirlite.build_gep_struct(builder, fd_sty, fd_bfe.Ty, fd_self, fd_bfe.Index) fd_null: mlirlite.Value* = mlirlite.ConstNull(pool, fd_bfe.Ty, "null") if fd_bp is not None and fd_null is not None: mlirlite.build_store(builder, fd_null, fd_bp) # FakeDuck: auto-inject requires fields from the with context (e.g. __mbuddy__) self._inject_requires_fields(trans, pool, builder, mod, fd_self, fd_cls) else: # Fieldless extension class (_Type): bitcast the object value to the extension pointer fd_obj_v: mlirlite.Value* = self.Gen.ExprGen.translate_value(builder, pool, mod, fd_at.value, None, 0, trans) if fd_obj_v is None: return None fd_self = mlirlite.build_bitcast(builder, fd_obj_v, mlirlite.Ptr(pool, fd_sty)) if fd_self is None: return None # Build the argument list: self -> remaining args mlirlite.value_set_next(fd_self, None) fd_head: mlirlite.Value* = fd_self fd_tail: mlirlite.Value* = fd_self fd_args: list[ast.AST*]* = cl.args fd_can: int = 0 if fd_args is not None: fd_can = int(len(fd_args)) for fd_ai in range(int(fd_can)): fd_arg: ast.AST* = fd_args.get(fd_ai) if fd_arg is not None: fd_av: mlirlite.Value* = self.Gen.ExprGen.translate_value(builder, pool, mod, fd_arg, None, 0, trans) # char constant arg -> single-char string (implicit char->str, e.g. s1.split(',') where # ',' is i32 44); passing it raw to a str param makes strlen/strncmp deref 44 and crash. if fd_arg.kind() == ast.ASTKind.Constant: fd_ac: ast.Constant* = ast.Constant*(fd_arg) if fd_ac.const_kind == ast.CONST_CHAR: ch_buf: str = pool.alloc(2) if ch_buf is not None: ch_buf[0] = char(fd_ac.int_val) ch_buf[1] = '\0' fd_av = self.Gen.ExprGenConst.create_global_string(builder, pool, mod, ch_buf, trans) if fd_av is not None: mlirlite.value_set_next(fd_av, None) mlirlite.value_set_next(fd_tail, fd_av) fd_tail = fd_av # Return type and call name (from the function definition hit by find_func_in_module) fd_ret_ty: mlirlite.LLVMType* = mlirlite.function_get_ret_ty(fd_func) if fd_ret_ty is None: fd_ret_ty = mlirlite.Int32(pool) fd_fname: str = mlirlite.function_get_name(fd_func) if fd_fname is None: return None # Refine return type: cross-module declare may simplify a list[T] specialization return type to Ptr(i8), degrading the caller's alloca type -> parts[0] takes the char-index path instead of list inline __getitem__ (printf %s crash). Authority is the defining module's vpi (fallback text.ll); extract the module sha1 only when the name is "{16hex}.{func}" (via GenApi.sha1_prefix_len) if GenApi.sha1_prefix_len(fd_fname) != 0: fd_sha1: str = pool.alloc(17) if fd_sha1 is not None: string.strncpy(fd_sha1, fd_fname, 16) fd_sha1[16] = '\0' fd_exact: mlirlite.LLVMType* = self._lookup_method_ret_ty_via_vpi(pool, fd_sha1, fd_cls, method_name) if fd_exact is None: fd_exact = self._LookupMethodRetTyFromTextLL(pool, fd_sha1, fd_cls, method_name) if fd_exact is not None: fd_ret_ty = fd_exact fd_total: int = 1 + fd_can # Value-reference (&T / &temp T) params of the resolved extension method: auto-address the # user args (the shadow self pointer occupies slot 0). fd_head = self._apply_auto_addr_to_call_args(builder, pool, mod, trans, fd_fname, fd_args, fd_head, fd_total, 1) return mlirlite.build_call(builder, fd_fname, fd_head, fd_total, fd_ret_ty, 0) # _inject_requires_fields - FakeDuck: inject requires fields from the with context. # For each class __requires__ field (registered by fd1), call _find_provider(field) at runtime; if non-NULL bitcast and store it (conditional inject); skip when _find_provider is unregistered. # ============================================================ def _inject_requires_fields(self, trans: HT.Translator*, pool: memhub.MemBuddy*, builder: mlirlite.IRBuilder*, mod: mlirlite.LLVMModule*, meta_ptr: mlirlite.Value*, class_name: str) -> int: """Inject requires fields from the with context, return the injected field count""" if trans is None or pool is None or builder is None or mod is None: return 0 if meta_ptr is None or class_name is None: return 0 # _withcontent._find_provider (cross-module): inject only when visible in this module # (matches TPC L4048-4050); otherwise non-with projects would emit undefined {sha1}._find_provider calls. fd_fn_probe: mlirlite.Function* = self.find_func_in_module(mod, "_find_provider") if fd_fn_probe is None: return 0 fd_fname: str = mlirlite.function_get_name(fd_fn_probe) if fd_fname is None: return 0 cnt: int = self.Gen.ClassGenBase.class_meta_count(GenTypes.CLASS_META_REQUIRES, class_name) if cnt <= 0: return 0 sty: mlirlite.LLVMType* = self.Gen.StructGen.get_struct_type(class_name) if sty is None: return 0 i8_ty_j: mlirlite.LLVMType* = mlirlite.Int8(pool) if i8_ty_j is None: return 0 i8_ptr_ty_j: mlirlite.LLVMType* = mlirlite.Ptr(pool, i8_ty_j) if i8_ptr_ty_j is None: return 0 cur_func: mlirlite.Function* = trans._cur_func if cur_func is None: return 0 injected: int = 0 for ji in range(int(cnt)): fname: str = self.Gen.ClassGenBase.class_meta_field_at(GenTypes.CLASS_META_REQUIRES, class_name, ji) if fname is None: continue fe: GenTypes.FieldEntry* = self.Gen.StructGen.lookup_field(sty, fname) if fe is None or fe.Ty is None: continue # Arg: field name string (i8*) fd_fstr: mlirlite.Value* = self.Gen.ExprGenConst.create_global_string(builder, pool, mod, fname, trans) if fd_fstr is None: continue mlirlite.value_set_next(fd_fstr, None) self._ensure_method_declare(pool, mod, fd_fname, i8_ptr_ty_j, fd_fstr, 1) fd_ptr: mlirlite.Value* = mlirlite.build_call(builder, fd_fname, fd_fstr, 1, i8_ptr_ty_j, 0) if fd_ptr is None: continue # Conditional inject: store only when ptr != NULL (preserve user-set values) fd_null: mlirlite.Value* = mlirlite.ConstNull(pool, i8_ptr_ty_j, "null") if fd_null is None: continue fd_cmp: mlirlite.Value* = mlirlite.build_icmp(builder, mlirlite.ICMP_NE, fd_ptr, fd_null) if fd_cmp is None: continue # Unique block names (create_block does not auto-number; duplicate labels in textual IR # conflict -> llc "Terminator found in the middle of a basic block"). vtc2: int = trans._label_counter trans._label_counter = vtc2 + 1 fd_bb_buf2: str = pool.alloc(64) if fd_bb_buf2 is None: continue viperlib.snprintf(fd_bb_buf2, 64, "fd.skip.%d", vtc2) fd_skip_bb: mlirlite.BasicBlock* = mlirlite.create_block(pool, cur_func, fd_bb_buf2) viperlib.snprintf(fd_bb_buf2, 64, "fd.inject.%d", vtc2) fd_inject_bb: mlirlite.BasicBlock* = mlirlite.create_block(pool, cur_func, fd_bb_buf2) if fd_skip_bb is None or fd_inject_bb is None: continue mlirlite.build_cond_br(builder, fd_cmp, fd_inject_bb, fd_skip_bb) # inject block: bitcast + GEP + store, then jump back to skip mlirlite.position_at_end(builder, fd_inject_bb) fd_cast: mlirlite.Value* = mlirlite.build_bitcast(builder, fd_ptr, fe.Ty) if fd_cast is not None: fd_fp: mlirlite.Value* = mlirlite.build_gep_struct(builder, sty, fe.Ty, meta_ptr, fe.Index) if fd_fp is not None: mlirlite.build_store(builder, fd_cast, fd_fp) mlirlite.build_br(builder, fd_skip_bb) # skip block: restore CurBlock and continue codegen mlirlite.position_at_end(builder, fd_skip_bb) injected += 1 return injected # _emit_provider_push / _emit_provider_pop - FakeDuck: with-context provider push/pop (__provides__ metadata). # Matches TPC HandlesWith.py L138-174: push _push_provider(field, ctx_i8) on entry and equal _pop_provider() on exit. # ============================================================ def _emit_provider_push(self, trans: HT.Translator*, pool: memhub.MemBuddy*, builder: mlirlite.IRBuilder*, mod: mlirlite.LLVMModule*, cls_name: str, ctx_val: mlirlite.Value*) -> int: """Emit a _push_provider(field, ctx_i8) call per provided field, return the field count""" if trans is None or pool is None or builder is None or mod is None: return 0 if cls_name is None or ctx_val is None: return 0 fd_pc: int = self.Gen.ClassGenBase.class_meta_count(GenTypes.CLASS_META_PROVIDES, cls_name) if fd_pc <= 0: return 0 # _withcontent._push_provider (cross-module, same visibility guard as _find_provider: # skip when this module does not import _withcontent, to avoid undefined symbol). fd_pfn_probe: mlirlite.Function* = self.find_func_in_module(mod, "_push_provider") if fd_pfn_probe is None: return 0 fd_pfn: str = mlirlite.function_get_name(fd_pfn_probe) if fd_pfn is None: return 0 i8_ty_p: mlirlite.LLVMType* = mlirlite.Int8(pool) if i8_ty_p is None: return 0 i8_ptr_ty_p: mlirlite.LLVMType* = mlirlite.Ptr(pool, i8_ty_p) if i8_ptr_ty_p is None: return 0 ret_i32_p: mlirlite.LLVMType* = mlirlite.Int32(pool) if ret_i32_p is None: return 0 # Bitcast the ctx value to i8* (provider stack stores void*) fd_ctx_i8: mlirlite.Value* = mlirlite.build_bitcast(builder, ctx_val, i8_ptr_ty_p) if fd_ctx_i8 is None: return 0 for pi in range(int(fd_pc)): fname: str = self.Gen.ClassGenBase.class_meta_field_at(GenTypes.CLASS_META_PROVIDES, cls_name, pi) if fname is None: continue fd_fstr: mlirlite.Value* = self.Gen.ExprGenConst.create_global_string(builder, pool, mod, fname, trans) if fd_fstr is None: continue mlirlite.value_set_next(fd_fstr, fd_ctx_i8) mlirlite.value_set_next(fd_ctx_i8, None) self._ensure_method_declare(pool, mod, fd_pfn, ret_i32_p, fd_fstr, 2) mlirlite.build_call(builder, fd_pfn, fd_fstr, 2, ret_i32_p, 0) return fd_pc def _emit_provider_pop(self, trans: HT.Translator*, pool: memhub.MemBuddy*, builder: mlirlite.IRBuilder*, mod: mlirlite.LLVMModule*, cls_name: str) -> int: """Emit a _pop_provider() call per provided field, return the field count""" if trans is None or pool is None or builder is None or mod is None: return 0 if cls_name is None: return 0 fd_pc: int = self.Gen.ClassGenBase.class_meta_count(GenTypes.CLASS_META_PROVIDES, cls_name) if fd_pc <= 0: return 0 # _withcontent._pop_provider (cross-module, same visibility guard as _find_provider: # skip when this module does not import _withcontent, to avoid undefined symbol). fd_pon_probe: mlirlite.Function* = self.find_func_in_module(mod, "_pop_provider") if fd_pon_probe is None: return 0 fd_pon: str = mlirlite.function_get_name(fd_pon_probe) if fd_pon is None: return 0 ret_i32_q: mlirlite.LLVMType* = mlirlite.Int32(pool) if ret_i32_q is None: return 0 self._ensure_method_declare(pool, mod, fd_pon, ret_i32_q, None, 0) for _pi in range(int(fd_pc)): mlirlite.build_call(builder, fd_pon, None, 0, ret_i32_q, 0) return fd_pc