246 lines
17 KiB
Plaintext
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 |