Files

246 lines
17 KiB
Plaintext

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