1418 lines
118 KiB
Plaintext
1418 lines
118 KiB
Plaintext
import lib.core.Gen.GenTypes as GenTypes
|
||
import lib.core.Gen.Generator as Generator
|
||
import lib.core.Gen.Expr.ExprGenCls as ExprGenCls
|
||
import lib.core.Gen.Expr.ExprGenName as ExprGenName
|
||
from stdint import *
|
||
import ast
|
||
import mlirlite
|
||
import memhub
|
||
import string
|
||
import stdlib
|
||
import sys
|
||
import viperlib
|
||
import win32.fileio as fileio
|
||
import lib.core.VLogger as VLogger
|
||
import lib.core.Gen.Translator as HT
|
||
import lib.core.Gen.Type.TypeSpec as TSpec
|
||
import lib.Stub.StubMerger as StubMerger
|
||
import lib.core.CompilerFactory as CompilerFactory
|
||
from . import CallGenBase
|
||
import lib.core.Gen.GenApi as GenApi
|
||
|
||
|
||
# ============================================================
|
||
# CallGenDispatch - CallGenMixin split (Phase C): translate_call detection stage
|
||
novirtual class CallGenDispatch(CallGenBase.CallGenBase):
|
||
"""Pre-dispatch helpers for translate_call (extends Base)."""
|
||
|
||
# _tc_h0_generic_func - generic function call: infer/specialize type args, then emit the call.
|
||
# Must run before normal function/constructor dispatch; hit_box marks handled void calls.
|
||
def _tc_h0_generic_func(self, pool: memhub.MemBuddy*, builder: mlirlite.IRBuilder*, mod: mlirlite.LLVMModule*, cl: ast.Call*, funcs_ptr: rawptr, func_count: int, trans: HT.Translator*, func_name: str, cargs: list[ast.AST*]*, can: sizet, hit_box: rawptr = None) -> mlirlite.Value*:
|
||
if cl is None or trans is None: return None
|
||
if self.Gen.ClassGenGeneric.GenericFuncKeys is None: return None
|
||
if cl.func is None: return None
|
||
# ---- Classify the generic call shape + template name / explicit type args ----
|
||
tpl_name: str = None
|
||
explicit_slice: ast.AST* = None
|
||
tpl_mod_sha1: str = None
|
||
is_attr_call: int = 0
|
||
fk: int = cl.func.kind()
|
||
if fk == ast.ASTKind.Subscript:
|
||
sub: ast.Subscript* = ast.Subscript*(cl.func)
|
||
if sub.value is not None and sub.value.kind() == ast.ASTKind.Name:
|
||
sn: ast.Name* = ast.Name*(sub.value)
|
||
tpl_name = sn.id
|
||
explicit_slice = sub.slice
|
||
elif fk == ast.ASTKind.Name: tpl_name = func_name
|
||
elif fk == ast.ASTKind.Attribute:
|
||
at: ast.Attribute* = ast.Attribute*(cl.func)
|
||
if at.attr is not None and at.value is not None and at.value.kind() == ast.ASTKind.Name:
|
||
mn: ast.Name* = ast.Name*(at.value)
|
||
if mn.id is not None and trans._imported_modules is not None:
|
||
if self.Gen.ImportsGen.is_module_imported_or_known(trans._imported_modules, mn.id) != 0: tpl_mod_sha1 = self._lookup_module_sha1(mn.id)
|
||
# Only treat the base as a module-qualified generic function when it is an imported
|
||
# module; otherwise `obj.add(x)` must not be hijacked by a same-named module-level generic.
|
||
if tpl_mod_sha1 is not None:
|
||
is_attr_call = 1
|
||
tpl_name = at.attr
|
||
if tpl_name is None: return None
|
||
# ---- Resolve template key "{sha1}.{fname}": module-qualified > local > from_imports ----
|
||
tpl_key: str = None
|
||
if is_attr_call != 0:
|
||
# Module-qualified call: only accept the key under that module's SHA1
|
||
if tpl_mod_sha1 is not None:
|
||
cand_q: str = self._generic_func_key(pool, tpl_mod_sha1, tpl_name)
|
||
if self.Gen.ClassGenGeneric._find_generic_func_template(cand_q) is not None: tpl_key = cand_q
|
||
else:
|
||
if trans.ModuleSha1 is not None:
|
||
cand_l: str = self._generic_func_key(pool, trans.ModuleSha1, tpl_name)
|
||
if self.Gen.ClassGenGeneric._find_generic_func_template(cand_l) is not None: tpl_key = cand_l
|
||
if tpl_key is None and trans._from_imports is not None:
|
||
imp_sha: str = self._lookup_mod_sha1(pool, trans._from_imports, tpl_name, 0)
|
||
if imp_sha is not None:
|
||
cand_f: str = self._generic_func_key(pool, imp_sha, tpl_name)
|
||
if self.Gen.ClassGenGeneric._find_generic_func_template(cand_f) is not None: tpl_key = cand_f
|
||
if tpl_key is None: return None
|
||
template_fd: ast.FunctionDef* = self.Gen.ClassGenGeneric._find_generic_func_template(tpl_key)
|
||
if template_fd is None: return None
|
||
tp_names: list[str]* = self.Gen.ClassGenGeneric._find_generic_func_tp_params(pool, tpl_key)
|
||
if tp_names is None: tp_names = template_fd.type_params
|
||
if tp_names is None: return None
|
||
tp_count: sizet = len(tp_names)
|
||
if tp_count == 0: return None
|
||
# ---- Collect type arguments ----
|
||
type_args: list[str]* = None
|
||
if explicit_slice is not None:
|
||
type_args = self.Gen.CallGenCtor._extract_type_args_from_slice(pool, explicit_slice, trans._from_imports)
|
||
if type_args is None or len(type_args) != tp_count:
|
||
self.Gen.TypeGenErr.fatal_error(cl, f"Generic function '{tpl_name}': explicit type argument count mismatch (expected {tp_count}); write {tpl_name}[Type1, Type2](...)", "TypeError")
|
||
return None
|
||
else:
|
||
# Implicit inference: align by the template signature's param annotated with T
|
||
ctx_args: list[str]* = self.Gen.CallGenCtor._infer_ctor_type_args_from_ctx(pool, trans, tpl_name, int(tp_count), cl)
|
||
type_args = list[str](pool, 8)
|
||
if type_args is None: return None
|
||
tpi: sizet
|
||
for tpi in range(int(tp_count)):
|
||
tp_name: str = tp_names.get(tpi)
|
||
ta: str = None
|
||
slot: int = self.Gen.ClassGenGeneric._find_generic_func_param_slot(template_fd, tp_name)
|
||
if slot >= 0 and sizet(slot) < can: ta = self.Gen.ClassGenGeneric._infer_generic_func_type_arg(pool, cargs.get(sizet(slot)), tpl_name, trans)
|
||
if ta is None and slot >= 0:
|
||
# Keyword arg: locate by template param name (positional wins; -1 if slot taken)
|
||
kwi: int = self._generic_func_kw_arg_index(template_fd, slot, cl.keywords, int(can))
|
||
if kwi >= 0:
|
||
kwn: ast.Keyword* = ast.Keyword*(cl.keywords.get(sizet(kwi)))
|
||
if kwn is not None and kwn.value is not None: ta = self.Gen.ClassGenGeneric._infer_generic_func_type_arg(pool, kwn.value, tpl_name, trans)
|
||
if ta is None and ctx_args is not None and sizet(tpi) < len(ctx_args): ta = ctx_args.get(tpi)
|
||
if ta is None:
|
||
self.Gen.TypeGenErr.fatal_error(cl, f"Cannot infer type argument {tpi + 1} of generic function '{tpl_name}' from the arguments (type parameter {tp_name});" f"silent default to int is forbidden. Annotate explicitly, e.g. {tpl_name}[Type](...), or annotate the corresponding argument", "TypeError")
|
||
return None
|
||
type_args.append(ta)
|
||
# ---- Specialize ----
|
||
spec_name: str = self.Gen.ClassGenGeneric._specialize_generic_function(trans, tpl_key, type_args)
|
||
if spec_name is None:
|
||
self.Gen.TypeGenErr.fatal_error(cl, f"Generic function '{tpl_name}': specialization failed", "TypeError")
|
||
return None
|
||
# ---- Emit call ----
|
||
return self._emit_generic_func_call(pool, builder, mod, trans, cl, spec_name, cargs, can, hit_box)
|
||
|
||
def _generic_func_key(self, pool: memhub.MemBuddy*, sha1: str, name: str) -> str:
|
||
"""Build the generic function template key "{sha1}.{name}" in a new buffer."""
|
||
if sha1 is None or name is None: return None
|
||
klen: sizet = string.strlen(sha1) + string.strlen(name) + 2
|
||
kbuf: str = pool.alloc(klen)
|
||
if kbuf is None: return None
|
||
viperlib.snprintf(kbuf, klen, "%s.%s", sha1, name)
|
||
return kbuf
|
||
|
||
def _generic_func_kw_arg_index(self, template_fd: ast.FunctionDef*, slot: int, keywords: list[ast.AST*]*, pos_count: int) -> int:
|
||
"""Return the keywords index for a type-parameter slot passed by keyword, else -1."""
|
||
if template_fd is None or keywords is None: return -1
|
||
if slot < pos_count: return -1
|
||
if template_fd.args is None or template_fd.args.kind() != ast.ASTKind.Arguments: return -1
|
||
ags: ast.Arguments* = ast.Arguments*(template_fd.args)
|
||
if ags.args is None: return -1
|
||
alist: list[ast.AST*]* = ags.args
|
||
if sizet(slot) >= len(alist): return -1
|
||
pa: ast.AST* = alist.get(sizet(slot))
|
||
if pa is None or pa.kind() != ast.ASTKind.Arg: return -1
|
||
ag: ast.Arg* = ast.Arg*(pa)
|
||
if ag.arg is None: return -1
|
||
kn: sizet = len(keywords)
|
||
ki: sizet
|
||
for ki in range(int(kn)):
|
||
kw: ast.Keyword* = ast.Keyword*(keywords.get(ki))
|
||
if kw is None or kw.arg is None: continue
|
||
if string.strcmp(kw.arg, ag.arg) == 0: return int(ki)
|
||
return -1
|
||
|
||
def _emit_generic_func_call(self, pool: memhub.MemBuddy*, builder: mlirlite.IRBuilder*, mod: mlirlite.LLVMModule*, trans: HT.Translator*, cl: ast.Call*, spec_name: str, cargs: list[ast.AST*]*, can: sizet, hit_box: rawptr = None) -> mlirlite.Value*:
|
||
"""Emit the specialized-function call, coercing args to the specialized param types."""
|
||
func: mlirlite.Function* = self.Gen.CallGenBase.find_func_in_module(mod, spec_name)
|
||
if func is None:
|
||
self.Gen.TypeGenErr.fatal_error(cl, f"Generic function specialization symbol not registered in the current module: {spec_name}", "InternalCompilerError")
|
||
return None
|
||
kw: list[ast.AST*]* = cl.keywords
|
||
if kw is not None and len(kw) > 0:
|
||
self.Gen.TypeGenErr.fatal_error(cl, "Generic function calls do not support keyword arguments yet; use positional arguments", "TypeError")
|
||
return None
|
||
if hit_box is not None: hit_box[0] = 1
|
||
param_count: int = 0
|
||
pp: mlirlite.Param* = mlirlite.function_get_param_head(func)
|
||
while pp is not None:
|
||
param_count += 1
|
||
pp = mlirlite.param_get_next(pp)
|
||
if int(can) < param_count:
|
||
self.Gen.TypeGenErr.fatal_error(cl, "Generic function call has fewer arguments than parameters (default parameters are not supported yet); supply the missing arguments", "TypeError")
|
||
return None
|
||
ret_ty: mlirlite.LLVMType* = mlirlite.function_get_ret_ty(func)
|
||
head: mlirlite.Value* = None
|
||
tail: mlirlite.Value* = None
|
||
fp: mlirlite.Param* = mlirlite.function_get_param_head(func)
|
||
for ai in range(int(can)):
|
||
arg: ast.AST* = cargs.get(ai)
|
||
fpty: mlirlite.LLVMType* = None
|
||
if fp is not None: fpty = mlirlite.param_get_ty(fp)
|
||
if arg is not None:
|
||
# Value-reference params (&T / &temp T): auto-address the argument (specialized callee).
|
||
av: mlirlite.Value* = self.Gen.CallGenBase.translate_call_arg(builder, pool, mod, trans, spec_name, int(ai), arg, fpty, "param")
|
||
if av is not None:
|
||
if fpty is not None: av = self.Gen.ExprGenBase.coerce_to_type(builder, av, fpty, arg, trans)
|
||
av.Next = None
|
||
if head is None: head = av
|
||
else: tail.Next = av
|
||
tail = av
|
||
if fp is not None: fp = mlirlite.param_get_next(fp)
|
||
# build_call's second arg is the callee name string (not Function*); passing a Function*
|
||
# reads its first field as a string and emits illegal IR. Use the looked-up symbol name.
|
||
return mlirlite.build_call(builder, spec_name, head, int(can), ret_ty, 0)
|
||
|
||
def _tc_h1_super_ctraits(self, pool: memhub.MemBuddy*, builder: mlirlite.IRBuilder*, mod: mlirlite.LLVMModule*, cl: ast.Call*, funcs_ptr: rawptr, func_count: int, trans: HT.Translator*, func_name: str, cargs: list[ast.AST*]*, can: sizet, hit_box: rawptr = None) -> mlirlite.Value*:
|
||
"""Compile-time super()/ctraits detection; sets hit_box on interception, else returns None."""
|
||
# super().method(args) -> static call to the parent method. Must intercept before attribute
|
||
# dispatch, else Call(super) is treated as a plain function call and lookup hits self/vtable.
|
||
if func_name is not None and cl.func is not None and cl.func.kind() == ast.ASTKind.Attribute:
|
||
sup_at: ast.Attribute* = ast.Attribute*(cl.func)
|
||
if sup_at.value is not None and sup_at.value.kind() == ast.ASTKind.Call:
|
||
sup_cl: ast.Call* = ast.Call*(sup_at.value)
|
||
if sup_cl.func is not None and sup_cl.func.kind() == ast.ASTKind.Name:
|
||
sup_nm: ast.Name* = ast.Name*(sup_cl.func)
|
||
if sup_nm.id is not None and string.strcmp(sup_nm.id, "super") == 0:
|
||
if hit_box is not None: hit_box[0] = 1
|
||
return self.Gen.CallGenCall._translate_super_call(pool, builder, mod, cl, func_name, trans)
|
||
|
||
# ctraits.isptr(x) is a compile-time predicate: return a constant from the variable's storage
|
||
# type instead of emitting a cross-module call (which would misreport an unregistered SHA1).
|
||
if func_name is not None and cl.func is not None and cl.func.kind() == ast.ASTKind.Attribute:
|
||
ct_at: ast.Attribute* = ast.Attribute*(cl.func)
|
||
if ct_at.value is not None and ct_at.value.kind() == ast.ASTKind.Name:
|
||
ct_nm: ast.Name* = ast.Name*(ct_at.value)
|
||
if ct_nm.id is not None and string.strcmp(ct_nm.id, "ctraits") == 0 and string.strcmp(func_name, "isptr") == 0:
|
||
ct_args: list[ast.AST*]* = cl.args
|
||
if ct_args is not None and len(ct_args) >= 1:
|
||
ct_arg0: ast.AST* = ct_args.get(0)
|
||
if ct_arg0 is not None and ct_arg0.kind() == ast.ASTKind.Name:
|
||
ct_nm0: ast.Name* = ast.Name*(ct_arg0)
|
||
if ct_nm0.id is not None and trans is not None:
|
||
ct_r: int = self.Gen.StmtGenCif._eval_ctraits_isptr(trans.SymTab, ct_nm0.id)
|
||
if ct_r > 0: return mlirlite.const_int32(pool, 1)
|
||
return mlirlite.const_int32(pool, 0)
|
||
return None
|
||
|
||
def _tc_h2_c_special(self, pool: memhub.MemBuddy*, builder: mlirlite.IRBuilder*, mod: mlirlite.LLVMModule*, cl: ast.Call*, funcs_ptr: rawptr, func_count: int, trans: HT.Translator*, func_name: str, cargs: list[ast.AST*]*, can: sizet, hit_box: rawptr = None) -> mlirlite.Value*:
|
||
"""Special c.Deref/Asm/LLVMIR calls; marks hit_box for void calls, else returns None."""
|
||
# __asm__(...) / __llvmir__(...) sugar: bare calls equal c.Asm(...) / c.LLVMIR(...). Must
|
||
# intercept before the c.* attribute branch, since the bare cl.func is a Name.
|
||
if func_name is not None and cl.func is not None and cl.func.kind() == ast.ASTKind.Name:
|
||
bare_nm: ast.Name* = ast.Name*(cl.func)
|
||
if bare_nm.id is not None and string.strcmp(bare_nm.id, "defined") == 0:
|
||
# defined(NAME) is a compile-time predicate valid only inside a cif/celif condition;
|
||
# it is evaluated there and never lowered as a runtime call.
|
||
self.Gen.TypeGenErr.fatal_error(cl, "defined(NAME) is only valid inside a cif/celif condition; it cannot be used in a normal expression", "NameError")
|
||
return None
|
||
if bare_nm.id is not None and (string.strcmp(bare_nm.id, "CIf") == 0 or string.strcmp(bare_nm.id, "CIfdef") == 0 or string.strcmp(bare_nm.id, "CIfndef") == 0 or string.strcmp(bare_nm.id, "CElif") == 0 or string.strcmp(bare_nm.id, "CElse") == 0 or string.strcmp(bare_nm.id, "CEndif") == 0):
|
||
# removed spellings (also covered for the bare-name form of the c.CIf family)
|
||
self.Gen.TypeGenErr.fatal_error(cl, "c.CIf/c.CIfdef/c.CIfndef/c.CElif/c.CElse/c.CEndif were removed; use the cif/celif/celse statements and the defined(NAME) predicate instead (e.g. `cif defined(NAME):` ... `celse:` ...)", "NameError")
|
||
return None
|
||
if bare_nm.id is not None and string.strcmp(bare_nm.id, "__asm__") == 0:
|
||
# translate_c_asm returns None for void output: set the hit flag so translate_call
|
||
# does not misread it as a miss and fall through to the external function path.
|
||
if hit_box is not None: hit_box[0] = 1
|
||
return self.Gen.CallGenCBuiltin.translate_c_asm(pool, builder, mod, cl, trans)
|
||
if bare_nm.id is not None and string.strcmp(bare_nm.id, "__llvmir__") == 0:
|
||
# c.LLVMIR always returns void (None): set the hit flag
|
||
if hit_box is not None: hit_box[0] = 1
|
||
return self.Gen.CallGenCBuiltin.translate_c_llvmir(pool, builder, mod, cl, trans)
|
||
if bare_nm.id is not None and (string.strcmp(bare_nm.id, "irinp") == 0 or string.strcmp(bare_nm.id, "irout") == 0):
|
||
# irinp/irout standalone calls return 0 (meaningful only inside __llvmir__ f-strings)
|
||
return mlirlite.ConstInt(pool, mlirlite.Int32(pool), 0, "0")
|
||
|
||
# Detect coro.* coroutine interception (inline translation, VPC-M1).
|
||
if cl.func is not None and cl.func.kind() == ast.ASTKind.Attribute:
|
||
at_node: ast.Attribute* = ast.Attribute*(cl.func)
|
||
if at_node.value is not None and at_node.value.kind() == ast.ASTKind.Name:
|
||
mod_nm: ast.Name* = ast.Name*(at_node.value)
|
||
is_coro_mod: int = 0
|
||
if mod_nm.id is not None and string.strcmp(mod_nm.id, "coro") == 0: is_coro_mod = 1
|
||
if is_coro_mod != 0:
|
||
if string.strcmp(func_name, "coro_run") == 0:
|
||
# coro.coro_run(frame): inline translation (resume loop until done, returns the ret slot).
|
||
# Note: translate_coro_* take (builder, pool, mod, cl, trans).
|
||
return self.Gen.AsyncGen.translate_coro_run(builder, pool, mod, cl, trans)
|
||
elif string.strcmp(func_name, "coro_destroy") == 0:
|
||
# coro.coro_destroy(frame): return the frame to the pool (M7 free-list)
|
||
return self.Gen.AsyncGen.translate_coro_destroy(builder, pool, mod, cl, trans)
|
||
elif string.strcmp(func_name, "coro_exception") == 0:
|
||
# coro.coro_exception(frame): return the frame exc slot (M4)
|
||
return self.Gen.AsyncGen.translate_coro_exception(builder, pool, mod, cl, trans)
|
||
elif string.strcmp(func_name, "coro_exception_msg") == 0:
|
||
# coro.coro_exception_msg(frame): return the frame msg slot (M7)
|
||
return self.Gen.AsyncGen.translate_coro_exception_msg(builder, pool, mod, cl, trans)
|
||
elif string.strcmp(func_name, "coro_gather") == 0:
|
||
# coro.coro_gather(frames...): advance all coroutines concurrently (M4)
|
||
return self.Gen.AsyncGen.translate_coro_gather(builder, pool, mod, cl, trans)
|
||
return None
|
||
|
||
def _tc_h3_stdarg_cast(self, pool: memhub.MemBuddy*, builder: mlirlite.IRBuilder*, mod: mlirlite.LLVMModule*, cl: ast.Call*, funcs_ptr: rawptr, func_count: int, trans: HT.Translator*, func_name: str, cargs: list[ast.AST*]*, can: sizet) -> mlirlite.Value*:
|
||
"""stdarg sugar and str/int/bool built-in casts; returns None when unmatched."""
|
||
# stdarg sugar (va_arg / arg / va_start / va_end): these are declared as pass bodies in
|
||
# stdarg.vp and handled here at translation time by emitting LLVM va_arg / intrinsic calls.
|
||
if func_name is not None and trans is not None:
|
||
if string.strcmp(func_name, "va_arg") == 0:
|
||
return self.Gen.CallGenVa._translate_va_arg_sugar(pool, builder, mod, cl, cargs, can, funcs_ptr, func_count, trans)
|
||
elif string.strcmp(func_name, "arg") == 0:
|
||
return self.Gen.CallGenVa._translate_arg_sugar(pool, builder, mod, cl, cargs, can, funcs_ptr, func_count, trans)
|
||
elif string.strcmp(func_name, "va_start") == 0:
|
||
return self.Gen.CallGenVa._translate_va_start_sugar(pool, builder, mod, cargs, can, funcs_ptr, func_count, trans)
|
||
elif string.strcmp(func_name, "va_end") == 0:
|
||
return self.Gen.CallGenVa._translate_va_end_sugar(pool, builder, mod, cargs, can, funcs_ptr, func_count, trans)
|
||
|
||
# Builtin casts: str(x)/bytes(x) -> i8*, int(x) -> i32, equivalent to (str)(x)/(int32)(x).
|
||
# In generic contexts T(x) first resolves T to a concrete type, then follows this path.
|
||
if func_name is not None and can == 1:
|
||
cast_fn_name: str = self.Gen.TypeGenBase.resolve_generic_type_name(func_name)
|
||
if string.strcmp(cast_fn_name, "str") == 0 or string.strcmp(cast_fn_name, "bytes") == 0:
|
||
# str(x) / bytes(x) → i8* (bitcast)
|
||
cast_arg: mlirlite.Value* = self.Gen.ExprGen.translate_value(builder, pool, mod, cargs.get(0), funcs_ptr, func_count, trans)
|
||
if cast_arg is not None:
|
||
# If the object defines __str__, dispatch __str__ (mirrors TPC HandlesExprBuiltin)
|
||
if string.strcmp(cast_fn_name, "str") == 0:
|
||
str_ov: mlirlite.Value* = self.Gen.ExprGenOps._try_named_dunder(pool, builder, mod, cast_arg, "__str__", trans)
|
||
if str_ov is not None: return str_ov
|
||
i8_ptr_ty_builtin: mlirlite.LLVMType* = mlirlite.Ptr(pool, mlirlite.Int8(pool))
|
||
return self.Gen.CallGenCtor._translate_t_type_cast(pool, builder, cast_arg, i8_ptr_ty_builtin)
|
||
return None
|
||
elif string.strcmp(cast_fn_name, "int") == 0:
|
||
# int(x) → i32
|
||
cast_arg_int: mlirlite.Value* = self.Gen.ExprGen.translate_value(builder, pool, mod, cargs.get(0), funcs_ptr, func_count, trans)
|
||
if cast_arg_int is not None:
|
||
i32_ty_builtin: mlirlite.LLVMType* = mlirlite.Int32(pool)
|
||
return self.Gen.CallGenCtor._translate_t_type_cast(pool, builder, cast_arg_int, i32_ty_builtin)
|
||
return None
|
||
elif string.strcmp(cast_fn_name, "bool") == 0:
|
||
# bool(x) -> i1 (strict != 0 normalization): integer icmp ne 0, float fcmp one 0.0,
|
||
# pointer icmp ne null.
|
||
cast_arg_bool: mlirlite.Value* = self.Gen.ExprGen.translate_value(builder, pool, mod, cargs.get(0), funcs_ptr, func_count, trans)
|
||
if cast_arg_bool is not None:
|
||
arg_bits: int = self.Gen.ExprGenBase.get_llvm_type_bits(cast_arg_bool.Ty)
|
||
arg_fbits: int = self.Gen.ExprGenBase.get_llvm_float_bits(cast_arg_bool.Ty)
|
||
# Integer: icmp ne x, 0
|
||
if arg_bits != 0:
|
||
zero_int: mlirlite.Value* = mlirlite.const_int(pool, arg_bits, 0)
|
||
if zero_int is not None: return mlirlite.build_icmp(builder, mlirlite.ICMP_NE, cast_arg_bool, zero_int)
|
||
return None
|
||
# Float: fcmp one x, 0.0
|
||
if arg_fbits != 0:
|
||
zero_ty: mlirlite.LLVMType* = cast_arg_bool.Ty
|
||
zero_flt: mlirlite.Value* = mlirlite.ConstFloat(pool, zero_ty, 0.0)
|
||
if zero_flt is not None: return mlirlite.build_fcmp(builder, mlirlite.FCMP_ONE, cast_arg_bool, zero_flt)
|
||
return None
|
||
# Pointer: icmp ne x, null
|
||
ptr_null: mlirlite.Value* = mlirlite.ConstNull(pool, cast_arg_bool.Ty, "null")
|
||
if ptr_null is not None: return mlirlite.build_icmp(builder, mlirlite.ICMP_NE, cast_arg_bool, ptr_null)
|
||
return None
|
||
return None
|
||
return None
|
||
|
||
def _tc_h4_type_casts(self, pool: memhub.MemBuddy*, builder: mlirlite.IRBuilder*, mod: mlirlite.LLVMModule*, cl: ast.Call*, funcs_ptr: rawptr, func_count: int, trans: HT.Translator*, func_name: str, cargs: list[ast.AST*]*, can: sizet) -> mlirlite.Value*:
|
||
"""Builtin / typedef type casts; returns None when unmatched."""
|
||
# Module-qualified typedef cast (posix.mode_t(mode), win32.win32base.LPCWSTR(v), ...): the
|
||
# callee is Attribute(<module path>, typedef); non-typedef names still go to the module call.
|
||
if cl.func is not None and cl.func.kind() == ast.ASTKind.Attribute:
|
||
mt_at: ast.Attribute* = ast.Attribute*(cl.func)
|
||
if mt_at.attr is not None:
|
||
# Module path supports both `mod.T` and nested `pkg.mod.T`.
|
||
mt_mod_path: str = None
|
||
if mt_at.value is not None and mt_at.value.kind() == ast.ASTKind.Name:
|
||
mt_mod_nm: ast.Name* = ast.Name*(mt_at.value)
|
||
mt_mod_path = mt_mod_nm.id
|
||
elif mt_at.value is not None and mt_at.value.kind() == ast.ASTKind.Attribute:
|
||
mt_mod_path = self.Gen.ExprGenAttr._attr_module_path_str(pool, mt_at.value)
|
||
if mt_mod_path is not None:
|
||
mod_td_ty: mlirlite.LLVMType* = self.Gen.TypeGenResolve.map_builtin_type(pool, func_name)
|
||
if mod_td_ty is None:
|
||
mod_td_str: str = self.Gen.TypeGenCType.lookup_ctypedef(func_name)
|
||
if mod_td_str is None or self.Gen.TypeGenCType.is_ctypedef_found() == 0:
|
||
# Library typedef closure may not be injected yet: scan the module source.
|
||
mod_td_str = self.Gen.TypeGenResolve._lookup_library_typedef(pool, mt_mod_path, func_name)
|
||
if mod_td_str is not None:
|
||
mod_td_ast: ast.AST* = self.Gen.TypeGenResolve._parse_type_string(pool, mod_td_str, 0)
|
||
if mod_td_ast is not None:
|
||
mod_imp_td: str = None
|
||
mod_fi_td: str = None
|
||
if trans is not None:
|
||
mod_imp_td = trans._imported_modules
|
||
mod_fi_td = trans._from_imports
|
||
mod_td_ty = self.Gen.TypeGenResolve.resolve_annotation_type(pool, mod_td_ast, mod_imp_td, mod_fi_td, trans, 0)
|
||
if mod_td_ty is None:
|
||
# Final fallback: the shared typedef resolver also scans stdint/win32base.
|
||
mod_imp_fb: str = None
|
||
mod_fi_fb: str = None
|
||
if trans is not None:
|
||
mod_imp_fb = trans._imported_modules
|
||
mod_fi_fb = trans._from_imports
|
||
mod_td_ty = self.Gen.TypeGenResolve.resolve_typedef_or_map(pool, func_name, mod_imp_fb, mod_fi_fb, trans)
|
||
if mod_td_ty is not None and can == 1:
|
||
mt_arg_val: mlirlite.Value* = self.Gen.ExprGen.translate_value(builder, pool, mod, cargs.get(0), funcs_ptr, func_count, trans, mod_td_ty)
|
||
if mt_arg_val is not None: return self.Gen.CallGenCtor._translate_t_type_cast(pool, builder, mt_arg_val, mod_td_ty, cargs.get(0), trans)
|
||
|
||
# Bare typedef casts (BYTEPTR, HANDLE, CHARPTR, ...): all CTypedef names and builtin primitives
|
||
# are castable. In generic contexts T(x) first resolves T, then goes through resolve_typedef_or_map.
|
||
if cl.func is not None and cl.func.kind() == ast.ASTKind.Name:
|
||
bare_fn_name: str = self.Gen.TypeGenBase.resolve_generic_type_name(func_name)
|
||
bare_ty: mlirlite.LLVMType* = None
|
||
# Builtin names resolved from generic context (T->"double"/"float"): float must map to
|
||
# f32, not f64.
|
||
if string.strcmp(bare_fn_name, "double") == 0: bare_ty = mlirlite.Double(pool)
|
||
elif string.strcmp(bare_fn_name, "float") == 0: bare_ty = mlirlite.Float(pool)
|
||
else:
|
||
# Prefer the CTypedef dynamic table (library typedef casts); on miss fall back to
|
||
# map_builtin_type.
|
||
bare_imp_c: str = None
|
||
bare_fi_c: str = None
|
||
if trans is not None:
|
||
bare_imp_c = trans._imported_modules
|
||
bare_fi_c = trans._from_imports
|
||
bare_ty = self.Gen.TypeGenResolve.resolve_typedef_or_map(pool, bare_fn_name, bare_imp_c, bare_fi_c)
|
||
# Names substituted from generic context (T->"int") are unknown to map_builtin_type, so fall
|
||
# back to resolve_annotation_type (supports builtin names / struct class names / aliases).
|
||
if bare_ty is None and string.strcmp(bare_fn_name, func_name) != 0:
|
||
gp_ast: ast.AST* = self.Gen.TypeGenResolve._parse_type_string(pool, bare_fn_name, 0)
|
||
if gp_ast is not None:
|
||
imp_gp: str = None
|
||
fi_gp: str = None
|
||
if trans is not None:
|
||
imp_gp = trans._imported_modules
|
||
fi_gp = trans._from_imports
|
||
bare_ty = self.Gen.TypeGenResolve.resolve_annotation_type(pool, gp_ast, imp_gp, fi_gp, trans, 0)
|
||
# Generic two-arg form T(val, rawptr) -> Ptr(T), aligned with int(val, rawptr) -> Ptr(i32);
|
||
# without it the two-arg call misses the can==1 branch and misreports an undefined bare call.
|
||
if bare_ty is not None and can == 2:
|
||
bare_arg2_node: ast.AST* = cargs.get(1)
|
||
bare_arg2_is_cptr: int = 0
|
||
if bare_arg2_node is not None:
|
||
# Builtin pointer marker: CPtr / rawptr
|
||
if bare_arg2_node.kind() == ast.ASTKind.Name:
|
||
bare_arg2_bn: ast.Name* = ast.Name*(bare_arg2_node)
|
||
if bare_arg2_bn.id is not None and (string.strcmp(bare_arg2_bn.id, "CPtr") == 0 or string.strcmp(bare_arg2_bn.id, "rawptr") == 0): bare_arg2_is_cptr = 1
|
||
if bare_arg2_is_cptr != 0:
|
||
# CVoid* -> i8* (void* is represented as i8* in LLVM)
|
||
match bare_ty:
|
||
case mlirlite.LLVMType.Void():
|
||
bare_ty = mlirlite.Int8(pool)
|
||
case _:
|
||
pass
|
||
bare_ty = mlirlite.Ptr(pool, bare_ty)
|
||
bare_arg_node: ast.AST* = cargs.get(0)
|
||
if bare_arg_node is not None:
|
||
bare_arg_val: mlirlite.Value* = self.Gen.ExprGen.translate_value(builder, pool, mod, bare_arg_node, None, 0, trans, bare_ty)
|
||
if bare_arg_val is not None: return self.Gen.CallGenCtor._translate_t_type_cast(pool, builder, bare_arg_val, bare_ty, bare_arg_node, trans)
|
||
if bare_ty is not None and can == 1:
|
||
bare_arg_node: ast.AST* = cargs.get(0)
|
||
if bare_arg_node is not None:
|
||
# Pass target_ty as the 8th arg so the target width participates in constant
|
||
# materialization for bare typedef casts like uint64(x) (N1).
|
||
bare_arg_val: mlirlite.Value* = self.Gen.ExprGen.translate_value(builder, pool, mod, bare_arg_node, None, 0, trans, bare_ty)
|
||
if bare_arg_val is not None: return self.Gen.CallGenCtor._translate_t_type_cast(pool, builder, bare_arg_val, bare_ty, bare_arg_node, trans)
|
||
|
||
return None
|
||
|
||
def _tc_h5_sizeof_len_isinst(self, pool: memhub.MemBuddy*, builder: mlirlite.IRBuilder*, mod: mlirlite.LLVMModule*, cl: ast.Call*, funcs_ptr: rawptr, func_count: int, trans: HT.Translator*, func_name: str, cargs: list[ast.AST*]*, can: sizet) -> mlirlite.Value*:
|
||
"""__sizeof__/len/isinstance built-ins; returns None when unmatched."""
|
||
# Detect ClassName.__sizeof__() and return the struct size constant (i64); it is a builtin,
|
||
# not a cross-module call. Supports e.g. Argument.__sizeof__() and module-qualified classes.
|
||
if cl.func is not None and cl.func.kind() == ast.ASTKind.Attribute:
|
||
sz_at: ast.Attribute* = ast.Attribute*(cl.func)
|
||
if sz_at.attr is not None and string.strcmp(sz_at.attr, "__sizeof__") == 0:
|
||
if sz_at.value is not None and can == 0:
|
||
sz_cls_name: str = None
|
||
sz_vk: int = sz_at.value.kind()
|
||
if sz_vk == ast.ASTKind.Name:
|
||
sz_cls_nm2: ast.Name* = ast.Name*(sz_at.value)
|
||
sz_cls_name = sz_cls_nm2.id
|
||
elif sz_vk == ast.ASTKind.Attribute:
|
||
sz_inner_at: ast.Attribute* = ast.Attribute*(sz_at.value)
|
||
sz_cls_name = sz_inner_at.attr
|
||
# Keep the full module path (win32.win32base.SECURITY_ATTRIBUTES): using only attr
|
||
# would let get_struct_type globally match a same-named class from another module.
|
||
if sz_inner_at.value is not None and sz_inner_at.attr is not None:
|
||
if sz_inner_at.value.kind() == ast.ASTKind.Name:
|
||
sz_inner_nm: ast.Name* = ast.Name*(sz_inner_at.value)
|
||
if sz_inner_nm.id is not None:
|
||
sz_full_len: sizet = string.strlen(sz_inner_nm.id) + 1 + string.strlen(sz_inner_at.attr) + 1
|
||
sz_full_buf: str = pool.alloc(sz_full_len)
|
||
if sz_full_buf is not None:
|
||
sz_full_buf[0] = '\0'
|
||
string.strcat(sz_full_buf, sz_inner_nm.id)
|
||
string.strcat(sz_full_buf, ".")
|
||
string.strcat(sz_full_buf, sz_inner_at.attr)
|
||
sz_cls_name = sz_full_buf
|
||
elif sz_inner_at.value.kind() == ast.ASTKind.Attribute:
|
||
sz_mod_path: str = self.Gen.ExprGenAttr._attr_module_path_str(pool, sz_inner_at.value)
|
||
if sz_mod_path is not None:
|
||
sz_full_len2: sizet = string.strlen(sz_mod_path) + 1 + string.strlen(sz_inner_at.attr) + 1
|
||
sz_full_buf2: str = pool.alloc(sz_full_len2)
|
||
if sz_full_buf2 is not None:
|
||
sz_full_buf2[0] = '\0'
|
||
string.strcat(sz_full_buf2, sz_mod_path)
|
||
string.strcat(sz_full_buf2, ".")
|
||
string.strcat(sz_full_buf2, sz_inner_at.attr)
|
||
sz_cls_name = sz_full_buf2
|
||
# Resolve the struct type: type-name hit -> generic specialization (Stack[T]) -> receiver type (self/obj)
|
||
sz_resolved_ty: mlirlite.LLVMType* = None
|
||
sz_resolved_name: str = sz_cls_name
|
||
if sz_cls_name is not None:
|
||
# Replace generic type parameter T: T.__sizeof__() -> int.__sizeof__()
|
||
sz_orig: str = sz_cls_name
|
||
sz_cls_name = self.Gen.TypeGenBase.resolve_generic_type_name(sz_cls_name)
|
||
# Fallback when the TypeGenBase context is empty (template tp_names expired
|
||
# across phases): replace T using trans's generic mapping.
|
||
if string.strcmp(sz_cls_name, sz_orig) == 0 and trans is not None and trans.GenericTypeParamNames is not None and trans.GenericTypeArgs is not None:
|
||
gp_n: sizet = len(trans.GenericTypeParamNames)
|
||
gp_i: sizet
|
||
for gp_i in range(int(gp_n)):
|
||
gpn: str = trans.GenericTypeParamNames.get(gp_i)
|
||
if gpn is not None and string.strcmp(sz_orig, gpn) == 0:
|
||
gta: str = trans.GenericTypeArgs.get(gp_i)
|
||
if gta is not None: sz_cls_name = gta
|
||
break
|
||
sz_resolved_name = sz_cls_name
|
||
# Pointer-wrapper checks must run before dot-stripping: type args may embed a
|
||
# dot after a sha1 prefix. Union types (X*) and rawptr[X] both have pointer size 8.
|
||
if string.strstr(sz_cls_name, "|") is not None: return mlirlite.const_int64(pool, 8)
|
||
if string.strncmp(sz_cls_name, "rawptr[", 7) == 0: return mlirlite.const_int64(pool, 8)
|
||
# A trailing '*' means pointer size 8 (rawptr[T] == rawptr[T] == T*). Must check
|
||
# trailing, not strrchr, since containers like list[{sha}.AST*] also contain '*'.
|
||
_sz_len_s: sizet = string.strlen(sz_cls_name)
|
||
while _sz_len_s > 0 and sz_cls_name[_sz_len_s - 1] == ' ': _sz_len_s -= 1
|
||
if _sz_len_s > 0 and sz_cls_name[_sz_len_s - 1] == '*': return mlirlite.const_int64(pool, 8)
|
||
# Save the full name (with module prefix) for exact module matching: stripping it
|
||
# lets get_struct_type globally match a same-named class from another module.
|
||
sz_full_name: str = sz_cls_name
|
||
# Strip the module prefix: the Viper parser yields Name(id="int") rather than
|
||
# Attribute, so manually take the last class-name component.
|
||
dot_pos: str = string.strrchr(sz_cls_name, '.')
|
||
if dot_pos is not None: sz_cls_name = dot_pos + 1
|
||
# [Phase 3] Unified size convention: resolve library typedefs first, only then
|
||
# use the hardcoded table. Pass trans's imports so from-import alias chains resolve.
|
||
dyn_sz_c: int = self.Gen.TypeGenResolve.get_type_size_bytes(pool, sz_cls_name, trans._imported_modules if trans is not None else None, trans._from_imports if trans is not None else None)
|
||
if dyn_sz_c > 0: return mlirlite.const_int64(pool, dyn_sz_c)
|
||
# Builtin type sizeof (int=4, str/bytes=8 pointer, double=8, ...)
|
||
sz_builtin: int = -1
|
||
if string.strcmp(sz_cls_name, "int") == 0: sz_builtin = 4
|
||
elif string.strcmp(sz_cls_name, "str") == 0: sz_builtin = 8
|
||
elif string.strcmp(sz_cls_name, "bytes") == 0: sz_builtin = 8
|
||
elif string.strcmp(sz_cls_name, "CInt") == 0: sz_builtin = 4
|
||
elif string.strcmp(sz_cls_name, "CInt8T") == 0: sz_builtin = 1
|
||
elif string.strcmp(sz_cls_name, "CInt16T") == 0: sz_builtin = 2
|
||
elif string.strcmp(sz_cls_name, "CInt32T") == 0: sz_builtin = 4
|
||
elif string.strcmp(sz_cls_name, "CInt64T") == 0: sz_builtin = 8
|
||
elif string.strcmp(sz_cls_name, "CChar") == 0: sz_builtin = 1
|
||
elif string.strcmp(sz_cls_name, "CDouble") == 0: sz_builtin = 8
|
||
elif string.strcmp(sz_cls_name, "CFloat") == 0: sz_builtin = 4
|
||
elif string.strcmp(sz_cls_name, "CPtr") == 0: sz_builtin = 8
|
||
elif string.strcmp(sz_cls_name, "CSizeT") == 0: sz_builtin = 8
|
||
elif string.strcmp(sz_cls_name, "CBool") == 0: sz_builtin = 1
|
||
elif string.strcmp(sz_cls_name, "bool") == 0: sz_builtin = 1
|
||
# Bare builtin names resolved from generic T (e.g. T->"double"/"float"): without
|
||
# them sz_builtin stays -1 and Name(T) is translated as a value, causing NameError.
|
||
elif string.strcmp(sz_cls_name, "double") == 0: sz_builtin = 8
|
||
elif string.strcmp(sz_cls_name, "float") == 0: sz_builtin = 4
|
||
elif string.strcmp(sz_cls_name, "char") == 0: sz_builtin = 1
|
||
if sz_builtin >= 0:
|
||
# Must use const_int64, not ConstInt(...,"0"): ConstInt's name is IR text used
|
||
# by ValuePrint, so a hardcoded "0" emits i64 0 instead of the actual value.
|
||
return mlirlite.const_int64(pool, sz_builtin)
|
||
sz_resolved_ty = None
|
||
# Module-qualified classes match the defining module exactly, preventing a
|
||
# same-named class from another module from being matched globally.
|
||
if sz_full_name is not None:
|
||
szf_dot: str = string.strrchr(sz_full_name, '.')
|
||
if szf_dot is not None:
|
||
szf_mod_len: sizet = sizet(uint64(szf_dot) - uint64(sz_full_name))
|
||
szf_mod: str = pool.alloc(szf_mod_len + 1)
|
||
if szf_mod is not None:
|
||
string.strncpy(szf_mod, sz_full_name, szf_mod_len)
|
||
szf_mod[szf_mod_len] = '\0'
|
||
# Module-qualified builtin names are already handled by the builtin/size table.
|
||
if szf_mod is not None:
|
||
szf_sha1: str = self.Gen.StructGen._resolve_module_sha1(szf_mod, trans._from_imports if trans is not None else None)
|
||
if szf_sha1 is not None:
|
||
szf_entry: GenTypes.StructEntry* = self.Gen.StructGen.find_struct_by_module(sz_cls_name, szf_sha1)
|
||
if szf_entry is not None and szf_entry.Ty is not None: sz_resolved_ty = szf_entry.Ty
|
||
if sz_resolved_ty is None: sz_resolved_ty = self.Gen.StructGen.get_struct_type(sz_cls_name)
|
||
# Fallback 1: generic subscript receiver Stack[T].__sizeof__() -> specialize, then size.
|
||
# (GenericTest2 Stack.type_size depends on this path.)
|
||
if sz_resolved_ty is None and trans is not None and sz_vk == ast.ASTKind.Subscript:
|
||
sub_sz: ast.Subscript* = ast.Subscript*(sz_at.value)
|
||
if sub_sz.value is not None and sub_sz.value.kind() == ast.ASTKind.Name:
|
||
gen_nm_sz: ast.Name* = ast.Name*(sub_sz.value)
|
||
if gen_nm_sz.id is not None:
|
||
gen_args_sz: list[str]* = self.Gen.CallGenCtor._extract_type_args_from_slice(pool, sub_sz.slice, trans._from_imports)
|
||
if gen_args_sz is not None and len(gen_args_sz) > 0:
|
||
gargs_resolved: list[str]* = list[str](pool, 8)
|
||
if gargs_resolved is not None:
|
||
gai_sz: sizet = len(gen_args_sz)
|
||
gai2_sz: sizet = 0
|
||
for gai2_sz in range(int(gai_sz)):
|
||
gres_sz: str = self.Gen.TypeGenBase.resolve_generic_type_name(gen_args_sz.get(gai2_sz))
|
||
gargs_resolved.append(gres_sz)
|
||
spec_sz: str = self.Gen.ClassGenGeneric._specialize_generic_class(trans, gen_nm_sz.id, gargs_resolved)
|
||
if spec_sz is not None:
|
||
sz_resolved_name = spec_sz
|
||
sz_resolved_ty = self.Gen.StructGen.get_struct_type(spec_sz)
|
||
# Fallback 2: instance receiver self.__sizeof__() / obj.__sizeof__() uses the actual
|
||
# value type; array receivers (t.CArray[T,N]) size as N*elem_size.
|
||
if sz_resolved_ty is None:
|
||
inst_sz_val: mlirlite.Value* = self.Gen.ExprGen.translate_value(builder, pool, mod, sz_at.value, None, 0, trans)
|
||
if inst_sz_val is not None and inst_sz_val.Ty is not None:
|
||
match inst_sz_val.Ty:
|
||
case mlirlite.LLVMType.Ptr(inst_inner_sz):
|
||
match inst_inner_sz:
|
||
case mlirlite.LLVMType.Struct(_, _, _):
|
||
sz_resolved_ty = inst_inner_sz
|
||
case mlirlite.LLVMType.Array(_, _):
|
||
sz_resolved_ty = inst_inner_sz
|
||
case _:
|
||
pass
|
||
case mlirlite.LLVMType.Struct(_, _, _):
|
||
sz_resolved_ty = inst_sz_val.Ty
|
||
case mlirlite.LLVMType.Array(_, _):
|
||
# Global array variables return the array value [N x T] (not a pointer);
|
||
# local arrays return an alloca pointer and take the Ptr(Array) branch above.
|
||
sz_resolved_ty = inst_sz_val.Ty
|
||
case _:
|
||
pass
|
||
if sz_resolved_ty is not None:
|
||
sz_val: int = self.Gen.ClassGenEnum._get_type_size(sz_resolved_ty)
|
||
if sz_val > 0: return mlirlite.const_int64(pool, sz_val)
|
||
# Type registered but size 0 (opaque struct or unfilled field list): fail with a Python-style error
|
||
self.Gen.TypeGenErr.fatal_error(sz_at, f"Type '{sz_resolved_name}' is registered but its size is 0 (field list not filled); cannot compute size", "TypeError")
|
||
# Type unregistered: fail with a Python-style error instead of returning 0 (would cause malloc(0)/alloc(0))
|
||
if sz_resolved_name is None: sz_resolved_name = "(unknown)"
|
||
self.Gen.TypeGenErr.fatal_error(sz_at, f"Type '{sz_resolved_name}' is not registered in the struct table; cannot compute size", "TypeError")
|
||
|
||
# Detect len(obj) on a list object: return its __count__ field (offset 8, i64). __len__ is a
|
||
# builtin of list[T]. Use translate_value, since len_at.value may be an Attribute (not a Name).
|
||
if cl.func is not None and cl.func.kind() == ast.ASTKind.Attribute:
|
||
len_at: ast.Attribute* = ast.Attribute*(cl.func)
|
||
if len_at.attr is not None and string.strcmp(len_at.attr, "__len__") == 0:
|
||
if len_at.value is not None and can == 0:
|
||
obj_val: mlirlite.Value* = self.Gen.ExprGen.translate_value(builder, pool, mod, len_at.value, funcs_ptr, func_count, trans)
|
||
if obj_val is not None and obj_val.Ty is not None:
|
||
# Defensive check: obj_val must be a pointer for GEP. If translate_value returns
|
||
# i32 (misparsed CDefine constant, module-attr fallback), llc reports a non-pointer GEP base.
|
||
if self.Gen.ExprGenBase.is_ptr_type(obj_val.Ty) == 0:
|
||
# Type inference failed (not a pointer/struct): fail fast rather than return 0
|
||
self.Gen.TypeGenErr.fatal_error(len_at.value, "len() argument type inference failed (not a pointer/struct)", "TypeError")
|
||
i64_ty_len: mlirlite.LLVMType* = mlirlite.Int64(pool)
|
||
# Must use const_int64, not ConstInt(...,"0"): ConstInt's name is emitted as IR text,
|
||
# so GEP would index 0 (__data__) instead of 1 (__count__).
|
||
idx_one: mlirlite.Value* = mlirlite.const_int64(pool, 1)
|
||
count_ptr: mlirlite.Value* = mlirlite.build_gep(builder, i64_ty_len, obj_val, idx_one)
|
||
if count_ptr is not None: return mlirlite.build_load(builder, i64_ty_len, count_ptr)
|
||
|
||
# Detect the len() builtin: struct objects use __len__, strings use strlen (returns i64).
|
||
if cl.func is not None and cl.func.kind() == ast.ASTKind.Name:
|
||
if func_name is not None and string.strcmp(func_name, "len") == 0:
|
||
if can == 1:
|
||
len_arg_node: ast.AST* = cargs.get(0)
|
||
if len_arg_node is not None:
|
||
len_val: mlirlite.Value* = self.Gen.ExprGen.translate_value(builder, pool, mod, len_arg_node, None, 0, trans)
|
||
if len_val is not None:
|
||
# len(array): t.CArray[T, N] -> constant N (TPC _HandleLenLlvm ArrayType.count).
|
||
# Array variables come back as Ptr([N x T]) or [N x T]; recognize both.
|
||
len_is_arr: int = 0
|
||
len_arr_n: int = 0
|
||
if len_val.Ty is not None:
|
||
if self.Gen.ExprGenBase.is_ptr_type(len_val.Ty) != 0:
|
||
len_ap: mlirlite.LLVMType* = len_val.Ty.Pointee
|
||
if len_ap is not None:
|
||
match len_ap:
|
||
case mlirlite.LLVMType.Array(_, len_arr_cnt):
|
||
len_is_arr = 1
|
||
len_arr_n = len_arr_cnt
|
||
case _:
|
||
pass
|
||
else:
|
||
match len_val.Ty:
|
||
case mlirlite.LLVMType.Array(_, len_arr_cnt):
|
||
len_is_arr = 1
|
||
len_arr_n = len_arr_cnt
|
||
case _:
|
||
pass
|
||
if len_is_arr != 0: return mlirlite.const_int64(pool, len_arr_n)
|
||
# len(object): call __len__ if present, otherwise fail at compile time (no silent
|
||
# strlen on the first field). list containers inline-read __count__; plain classes call {Class}.__len__.
|
||
len_struct_ty: mlirlite.LLVMType* = self.Gen.StructGen.get_struct_type_from_value(len_val)
|
||
if len_struct_ty is not None:
|
||
len_cls_nm: str = self.Gen.StructGen.get_class_name_by_type(pool, len_struct_ty)
|
||
if len_cls_nm is None: self.Gen.TypeGenErr.fatal_error(len_arg_node, "len() argument type inference failed (unknown struct class name)", "TypeError")
|
||
# list builtin __len__: read __count__ (GEP offset 1, i64). Narrow len_arg_node
|
||
# to ast.Call* explicitly, since passing a base pointer to a derived param is banned.
|
||
len_arg_cl: ast.Call* = ast.Call*(len_arg_node)
|
||
if self.Gen.CallGenProp._is_list_builtin_receiver(pool, len_val, len_cls_nm, "__len__", len_arg_cl) != 0:
|
||
i64_ty_lb: mlirlite.LLVMType* = mlirlite.Int64(pool)
|
||
idx_one_lb: mlirlite.Value* = mlirlite.const_int64(pool, 1)
|
||
cnt_ptr_lb: mlirlite.Value* = mlirlite.build_gep(builder, i64_ty_lb, len_val, idx_one_lb)
|
||
if cnt_ptr_lb is not None: return mlirlite.build_load(builder, i64_ty_lb, cnt_ptr_lb)
|
||
len_lookup: str = pool.alloc(128)
|
||
if len_lookup is not None:
|
||
viperlib.snprintf(len_lookup, 128, "%s.__len__", len_cls_nm)
|
||
len_fn2: mlirlite.Function* = self.find_func_in_module(mod, len_lookup)
|
||
if len_fn2 is not None:
|
||
len_ret_ty2: mlirlite.LLVMType* = mlirlite.function_get_ret_ty(len_fn2)
|
||
len_fn_name2: str = mlirlite.function_get_name(len_fn2)
|
||
if len_ret_ty2 is not None and len_fn_name2 is not None:
|
||
# Align the self parameter type (bitcast to the method's first param)
|
||
len_self_arg2: mlirlite.Value* = len_val
|
||
len_first_param2: mlirlite.Param* = mlirlite.function_get_param_head(len_fn2)
|
||
if len_first_param2 is not None:
|
||
len_self_ty2: mlirlite.LLVMType* = mlirlite.param_get_ty(len_first_param2)
|
||
if len_self_ty2 is not None and len_val.Ty is not None: len_self_arg2 = mlirlite.build_bitcast(builder, len_val, len_self_ty2)
|
||
len_self_arg2.Next = None
|
||
return mlirlite.build_call(builder, len_fn_name2, len_self_arg2, 1, len_ret_ty2, 0)
|
||
# len_fn2 miss (cross-module method): build the exact name {sha1}.{cls}.__len__
|
||
# from the struct's module sha1, resolve the return type from text.ll/vpi, then call.
|
||
len_cls_sha1_x: str = self.Gen.StructGen.get_struct_sha1(len_cls_nm)
|
||
if len_cls_sha1_x is not None:
|
||
# Buffer must fit "{sha1}.{cls}.__len__": 2 dots + "__len__"(7) + NUL = 10;
|
||
# +8 truncates the last two chars and breaks the link.
|
||
len_ml_x: sizet = string.strlen(len_cls_sha1_x) + string.strlen(len_cls_nm) + 10
|
||
len_mangled_x: str = pool.alloc(len_ml_x)
|
||
if len_mangled_x is not None:
|
||
viperlib.snprintf(len_mangled_x, len_ml_x, "%s.%s.__len__", len_cls_sha1_x, len_cls_nm)
|
||
len_ret_x: mlirlite.LLVMType* = self._lookup_method_ret_ty_via_vpi(pool, len_cls_sha1_x, len_cls_nm, "__len__")
|
||
if len_ret_x is None: len_ret_x = self._LookupMethodRetTyFromTextLL(pool, len_cls_sha1_x, len_cls_nm, "__len__")
|
||
if len_ret_x is not None:
|
||
# self is passed as Ptr(len_struct_ty) to match the definition's type;
|
||
# len_val is already the loaded object pointer; use the value chain as param_head.
|
||
len_self_x: mlirlite.Value* = len_val
|
||
len_self_x.Next = None
|
||
self._ensure_method_declare(pool, mod, len_mangled_x, len_ret_x, len_self_x, 1)
|
||
return mlirlite.build_call(builder, len_mangled_x, len_self_x, 1, len_ret_x, 0)
|
||
# fail-fast: the class has no __len__ (compile-time error, matching TPC
|
||
# HandlesExprBuiltin._FailFastLenUnsupported wording).
|
||
self.Gen.TypeGenErr.fatal_error(len_arg_node, f"len() argument type '{len_cls_nm}' has no __len__ method. len() only supports fixed-size arrays/array pointers (t.CArray[T, N]), i8* (str/bytes), or structs with __len__; non-i8 bare pointers/scalar pointers have no length semantics: use str/bytes for strings, record the length yourself when the element count is needed, or use rawptr[Element] and maintain the element count explicitly", "TypeError")
|
||
# Non-struct: str (Ptr(Int8)) uses strlen; other types fail fast
|
||
len_arg_ty: mlirlite.LLVMType* = mlirlite.value_get_ty(len_val)
|
||
len_is_str: int = 0
|
||
if len_arg_ty is not None and self.Gen.ExprGenBase.is_ptr_type(len_arg_ty) != 0:
|
||
match len_arg_ty:
|
||
case mlirlite.LLVMType.Ptr(len_pt_ty):
|
||
if len_pt_ty is not None:
|
||
match len_pt_ty:
|
||
case mlirlite.LLVMType.Int(len_pt_bits):
|
||
if len_pt_bits == 8: len_is_str = 1
|
||
case _:
|
||
pass
|
||
case _:
|
||
pass
|
||
if len_is_str != 0:
|
||
# Ensure strlen is declared in the module
|
||
strlen_fn: mlirlite.Function* = self.find_func_in_module(mod, "strlen")
|
||
if strlen_fn is None:
|
||
i64_ty_ln: mlirlite.LLVMType* = mlirlite.Int64(pool)
|
||
i8_ptr_ty_ln: mlirlite.LLVMType* = mlirlite.Ptr(pool, mlirlite.Int8(pool))
|
||
strlen_fn = mlirlite.create_declare(pool, mod, "strlen", i64_ty_ln)
|
||
if strlen_fn is not None: mlirlite.add_param(pool, strlen_fn, i8_ptr_ty_ln, "s")
|
||
# Coerce the argument type to i8*
|
||
i8_ptr_ty_ln2: mlirlite.LLVMType* = mlirlite.Ptr(pool, mlirlite.Int8(pool))
|
||
len_coerced: mlirlite.Value* = len_val
|
||
if self.Gen.ExprGenBase.is_ptr_type(len_arg_ty) != 0: len_coerced = mlirlite.build_bitcast(builder, len_val, i8_ptr_ty_ln2)
|
||
# Build the single-argument chain and call strlen
|
||
len_coerced.Next = None
|
||
i64_ty_ln2: mlirlite.LLVMType* = mlirlite.Int64(pool)
|
||
return mlirlite.build_call(builder, "strlen", len_coerced, 1, i64_ty_ln2, 0)
|
||
# fail-fast: not a string/array/object, so length is unavailable (matching TPC
|
||
# HandlesExprBuiltin._FailFastLenUnsupported wording).
|
||
self.Gen.TypeGenErr.fatal_error(len_arg_node, "len() argument type not supported (not a string/array/object). len() only supports fixed-size arrays/array pointers (t.CArray[T, N]), i8* (str/bytes), or structs with __len__; non-i8 bare pointers/scalar pointers have no length semantics: use str/bytes for strings, record the length yourself when the element count is needed, or use rawptr[Element] and maintain the element count explicitly", "TypeError")
|
||
|
||
# Detect isinstance(): runtime vtable subclass-set comparison. obj's head vtable pointer must
|
||
# be in {Cls and all subclass vtables}; NoVTable targets fall back to a compile-time check.
|
||
if cl.func is not None and cl.func.kind() == ast.ASTKind.Name:
|
||
if func_name is not None and string.strcmp(func_name, "isinstance") == 0:
|
||
if can >= 2:
|
||
is_res: mlirlite.Value* = self.Gen.CallGenLeafKit._translate_isinstance(pool, builder, mod, cargs.get(0), cargs.get(1), trans)
|
||
if is_res is not None: return is_res
|
||
|
||
return None
|
||
|
||
def _tc_h6_method_dispatch(self, pool: memhub.MemBuddy*, builder: mlirlite.IRBuilder*, mod: mlirlite.LLVMModule*, cl: ast.Call*, funcs_ptr: rawptr, func_count: int, trans: HT.Translator*, func_name: str, cargs: list[ast.AST*]*, can: sizet, hit_box: rawptr = None) -> mlirlite.Value*:
|
||
"""Static class/method/receiver/self.field/list built-ins; sets hit_box, else returns None."""
|
||
# REnum.kind() compiles to a __tag read (offset 0, type from the field declaration): bitcast obj
|
||
# to __tag* + load. Four criteria detect REnum, incl. pointee __tag and X.field annotations.
|
||
if cl.func is not None and cl.func.kind() == ast.ASTKind.Attribute:
|
||
rk_at: ast.Attribute* = ast.Attribute*(cl.func)
|
||
if rk_at is not None and rk_at.attr is not None and string.strcmp(rk_at.attr, "kind") == 0:
|
||
rk_obj: mlirlite.Value* = self.Gen.ExprGen.translate_value(builder, pool, mod, rk_at.value, None, 0, trans)
|
||
if rk_obj is not None and rk_obj.Ty is not None:
|
||
rk_ty: mlirlite.LLVMType* = rk_obj.Ty
|
||
rk_load: int = 0
|
||
rk_cls: str = None
|
||
rk_pointee: mlirlite.LLVMType* = None
|
||
# rk_is_rn: whether already determined to be a REnum, decoupled from rk_cls.
|
||
rk_is_rn: int = 0
|
||
# rk_tag_ty: the true source of kind()'s return type is __tag's field declaration type
|
||
# (FieldEntry.Ty). Default i32 is equivalent when the field entry is unavailable.
|
||
rk_tag_ty: mlirlite.LLVMType* = mlirlite.Int32(pool)
|
||
if self.Gen.ExprGenBase.is_ptr_type(rk_ty) != 0:
|
||
rk_pointee = rk_ty.Pointee
|
||
# Union-simplified double pointer (Ptr(Ptr(Struct))) -> deref once + load
|
||
if rk_pointee is not None and self.Gen.ExprGenBase.is_ptr_type(rk_pointee) != 0:
|
||
rk_pointee = rk_pointee.Pointee
|
||
rk_load = 1
|
||
rk_cls = self.Gen.StructGen.get_class_name_by_type(pool, rk_pointee)
|
||
# Three parallel criteria: variant table (_is_renum_class_name), struct with __tag
|
||
# (_is_renum_by_entry), or an unnamed pointee with __tag. Relying on the table alone would skip the whole branch.
|
||
if rk_cls is not None:
|
||
if self.Gen.ClassGenEnum._is_renum_class_name(rk_cls) != 0 or self.Gen.ClassGenEnum._is_renum_by_entry(rk_cls) != 0: rk_is_rn = 1
|
||
else: rk_cls = None
|
||
# (3) Even without a class name, check whether the pointee struct has __tag, and
|
||
# record __tag's field declaration type as kind()'s return type (layer 2).
|
||
rk_tag_fe: GenTypes.FieldEntry* = None
|
||
if rk_pointee is not None: rk_tag_fe = self.Gen.StructGen.lookup_field(rk_pointee, "__tag")
|
||
if rk_is_rn == 0 and rk_tag_fe is not None: rk_is_rn = 1
|
||
if rk_tag_fe is not None and rk_tag_fe.Ty is not None: rk_tag_ty = rk_tag_fe.Ty
|
||
# Annotation fallback: when the runtime type is lost after union simplification (e.g.
|
||
# resolved: LLVMType* stored as i8*), identify the REnum class from the variable annotation.
|
||
if rk_is_rn == 0 and rk_at.value is not None and rk_at.value.kind() == ast.ASTKind.Name and trans is not None:
|
||
rk_nm: ast.Name* = ast.Name*(rk_at.value)
|
||
if rk_nm.id is not None:
|
||
rk_ve: GenTypes.VarEntry* = self.Gen.VarGen.lookup_var_entry(trans.SymTab, rk_nm.id)
|
||
rk_annot_raw: str = self.Gen.VarGen.var_annot_class_name(rk_ve)
|
||
if rk_annot_raw is not None:
|
||
# Truncate the union/generic suffix (" | rawptr" / "[...]")
|
||
rk_ae: sizet = 0
|
||
while rk_annot_raw[rk_ae] != '\0' and rk_annot_raw[rk_ae] != ' ' and rk_annot_raw[rk_ae] != '|' and rk_annot_raw[rk_ae] != '[': rk_ae += 1
|
||
rk_ab: str = pool.alloc(rk_ae + 1)
|
||
if rk_ab is not None:
|
||
string.strncpy(rk_ab, rk_annot_raw, rk_ae)
|
||
rk_ab[rk_ae] = '\0'
|
||
# Strip the module prefix ("mlirlite.LLVMType" -> "LLVMType")
|
||
rk_ad: str = string.strrchr(rk_ab, '.')
|
||
if rk_ad is not None and rk_ad[1] != '\0': rk_cls = rk_ad + 1
|
||
else: rk_cls = rk_ab
|
||
# Same criteria as above (table or struct with __tag), not just the variant table
|
||
if self.Gen.ClassGenEnum._is_renum_class_name(rk_cls) != 0 or self.Gen.ClassGenEnum._is_renum_by_entry(rk_cls) != 0:
|
||
rk_is_rn = 1
|
||
# Layer 2: get the __tag declaration type from the class's struct
|
||
rk_st_b: mlirlite.LLVMType* = self.Gen.StructGen.get_struct_type(rk_cls)
|
||
if rk_st_b is not None:
|
||
rk_fe_b: GenTypes.FieldEntry* = self.Gen.StructGen.lookup_field(rk_st_b, "__tag")
|
||
if rk_fe_b is not None and rk_fe_b.Ty is not None: rk_tag_ty = rk_fe_b.Ty
|
||
else: rk_cls = None
|
||
# Layer 4 fallback for `X.field` receivers whose runtime type degraded: use declaration
|
||
# info (X's annotation -> struct -> field type -> pointee class) and re-run the table/__tag criteria.
|
||
if rk_is_rn == 0 and rk_at.value is not None and rk_at.value.kind() == ast.ASTKind.Attribute and trans is not None:
|
||
rk_bat: ast.Attribute* = ast.Attribute*(rk_at.value)
|
||
if rk_bat is not None and rk_bat.attr is not None and rk_bat.value is not None and rk_bat.value.kind() == ast.ASTKind.Name:
|
||
rk_bnm: ast.Name* = ast.Name*(rk_bat.value)
|
||
if rk_bnm.id is not None:
|
||
rk_bve: GenTypes.VarEntry* = self.Gen.VarGen.lookup_var_entry(trans.SymTab, rk_bnm.id)
|
||
rk_bannot: str = self.Gen.VarGen.var_annot_class_name(rk_bve)
|
||
if rk_bannot is not None:
|
||
rk_blen: sizet = 0
|
||
while rk_bannot[rk_blen] != '\0' and rk_bannot[rk_blen] != ' ' and rk_bannot[rk_blen] != '|' and rk_bannot[rk_blen] != '[': rk_blen += 1
|
||
rk_bbuf: str = pool.alloc(rk_blen + 1)
|
||
if rk_bbuf is not None:
|
||
string.strncpy(rk_bbuf, rk_bannot, rk_blen)
|
||
rk_bbuf[rk_blen] = '\0'
|
||
# Strip the module prefix ("mlirlite.Value" -> "Value")
|
||
rk_bdot: str = string.strrchr(rk_bbuf, '.')
|
||
rk_bcls: str = rk_bbuf
|
||
if rk_bdot is not None and rk_bdot[1] != '\0': rk_bcls = rk_bdot + 1
|
||
rk_bst: mlirlite.LLVMType* = self.Gen.StructGen.get_struct_type(rk_bcls)
|
||
if rk_bst is not None:
|
||
rk_bfe: GenTypes.FieldEntry* = self.Gen.StructGen.lookup_field(rk_bst, rk_bat.attr)
|
||
if rk_bfe is not None and rk_bfe.Ty is not None:
|
||
rk_fpt: mlirlite.LLVMType* = rk_bfe.Ty
|
||
if self.Gen.ExprGenBase.is_ptr_type(rk_fpt) != 0:
|
||
rk_fpt = rk_fpt.Pointee
|
||
if rk_fpt is not None and self.Gen.ExprGenBase.is_ptr_type(rk_fpt) != 0: rk_fpt = rk_fpt.Pointee
|
||
if rk_fpt is not None:
|
||
rk_fcls: str = self.Gen.StructGen.get_class_name_by_type(pool, rk_fpt)
|
||
if rk_fcls is not None:
|
||
if self.Gen.ClassGenEnum._is_renum_class_name(rk_fcls) != 0 or self.Gen.ClassGenEnum._is_renum_by_entry(rk_fcls) != 0:
|
||
rk_is_rn = 1
|
||
rk_cls = rk_fcls
|
||
rk_ffe: GenTypes.FieldEntry* = self.Gen.StructGen.lookup_field(rk_fpt, "__tag")
|
||
if rk_ffe is not None:
|
||
if rk_is_rn == 0: rk_is_rn = 1
|
||
if rk_ffe.Ty is not None: rk_tag_ty = rk_ffe.Ty
|
||
if rk_is_rn != 0:
|
||
rk_val: mlirlite.Value* = rk_obj
|
||
if rk_load != 0:
|
||
# build_load(builder, ty, ptr): ty is the loaded value's type. Since rk_ty is
|
||
# Ptr(Ptr(X)), pass rk_ty.Pointee; passing rk_ty would load a pointer and keep Ty a pointer.
|
||
rk_val = mlirlite.build_load(builder, rk_ty.Pointee, rk_obj)
|
||
if rk_val is None:
|
||
# fail-fast: REnum was determined but the one-level load of a double pointer
|
||
# produced no value; never skip silently, or .kind() falls into the generic call path.
|
||
self.Gen.TypeGenErr.fatal_error(cl, f"REnum.kind() translation failed: the receiver was determined to be a REnum (variant table/__tag criterion hit), " f"but loading one level from the receiver pointer failed (rk_load={rk_load})", "TypeError")
|
||
if hit_box is not None: hit_box[0] = 1
|
||
# Layer 2: address and load using __tag's field declaration type, no hardcoded i32.
|
||
# Width coercion is left to coerce_to_type on the assignment side.
|
||
rk_tag_p: mlirlite.LLVMType* = mlirlite.Ptr(pool, rk_tag_ty)
|
||
rk_cast: mlirlite.Value* = mlirlite.build_bitcast(builder, rk_val, rk_tag_p)
|
||
# Pass the loaded value type rk_tag_ty (not the pointer rk_tag_p): the latter yields
|
||
# `load i32*, i32*` and triggers "implicit pointer-to-int forbidden" at the assignment.
|
||
return mlirlite.build_load(builder, rk_tag_ty, rk_cast)
|
||
# Detect class-name static/class method calls: ClassName.method(args) where cl.func is an
|
||
# Attribute, value is a registered struct class (not a variable), and the method is static/class.
|
||
if cl.func is not None and cl.func.kind() == ast.ASTKind.Attribute:
|
||
st_at: ast.Attribute* = ast.Attribute*(cl.func)
|
||
if st_at is not None and st_at.value is not None and trans is not None:
|
||
if st_at.value.kind() == ast.ASTKind.Name:
|
||
st_nm: ast.Name* = ast.Name*(st_at.value)
|
||
if st_nm.id is not None:
|
||
st_var: mlirlite.Value* = self.Gen.VarGen.lookup_var(trans.SymTab, st_nm.id)
|
||
if st_var is None and self.Gen.StructGen.get_struct_type(st_nm.id) is not None and self.Gen.ImportsGen.is_module_imported(trans._imported_modules, st_nm.id) == 0:
|
||
# REnum variant construction takes priority over static/class method calls:
|
||
if self.Gen.ClassGenEnum._is_renum_class_name(st_nm.id) != 0:
|
||
rc_tag_sc: int = self.Gen.ClassGenEnum._lookup_renum_variant_in_class(st_nm.id, func_name)
|
||
if rc_tag_sc >= 0: return self.Gen.CallGenCtor._translate_renum_construct(pool, builder, mod, st_nm.id, func_name, rc_tag_sc, cargs, can, trans, cl.IsVolatile)
|
||
st_res: mlirlite.Value* = self.Gen.CallGenProp._translate_static_class_call(pool, builder, mod, st_nm.id, func_name, cl, trans)
|
||
if st_res is not None: return st_res
|
||
# Instance method via class name: ClassName.method(obj, args) -> obj.method(args). But when the class name equals the method name (e.g. Ctor.Ctor(...)) it is a constructor call, so skip this branch and let the ctor detection handle it.
|
||
if can >= 1 and cargs is not None and string.strcmp(st_nm.id, func_name) != 0:
|
||
im_self: mlirlite.Value* = self.Gen.ExprGen.translate_value(builder, pool, mod, cargs.get(0), None, 0, trans)
|
||
if im_self is not None:
|
||
im_rem: sizet* = None
|
||
if can >= 2:
|
||
im_rem = pool.alloc(sizet(can - 1) * 8)
|
||
if im_rem is not None:
|
||
ri_im: sizet
|
||
for ri_im in range(int(can - 1)):
|
||
im_rv: mlirlite.Value* = self.Gen.ExprGen.translate_value(builder, pool, mod, cargs.get(ri_im + 1), None, 0, trans)
|
||
if im_rv is not None: im_rem[ri_im] = sizet(im_rv)
|
||
im_res: mlirlite.Value* = self.Gen.CallGenCall._call_method_on_ptr(pool, builder, mod, st_nm.id, func_name, im_self, im_rem, can - 1, trans)
|
||
if im_res is not None: return im_res
|
||
elif st_at.value.kind() == ast.ASTKind.Attribute:
|
||
# Module-qualified static call: module.ClassName.static_method(args). Without this,
|
||
# method_name is treated as a bare function and links as an undefined reference.
|
||
st_at2: ast.Attribute* = ast.Attribute*(st_at.value)
|
||
if st_at2 is not None and st_at2.attr is not None and st_at2.value is not None:
|
||
if st_at2.value.kind() == ast.ASTKind.Name:
|
||
# When ClassName is a registered struct class, try static/class methods
|
||
if self.Gen.StructGen.get_struct_type(st_at2.attr) is not None:
|
||
st_res2: mlirlite.Value* = self.Gen.CallGenProp._translate_static_class_call(pool, builder, mod, st_at2.attr, func_name, cl, trans)
|
||
if st_res2 is not None: return st_res2
|
||
|
||
# [M4 REPL-only branch] Method calls on session-variable receivers: their values live in ctx
|
||
# slots, not SymTab, so the lookup_var branch never hits. Guarded by trans.ReplCtx is not None.
|
||
if cl.func is not None and cl.func.kind() == ast.ASTKind.Attribute and trans is not None and trans.ReplCtx is not None and pool is not None:
|
||
rp_at: ast.Attribute* = ast.Attribute*(cl.func)
|
||
if rp_at is not None and rp_at.value is not None and rp_at.attr is not None:
|
||
if rp_at.value.kind() == ast.ASTKind.Name:
|
||
rp_nm: ast.Name* = ast.Name*(rp_at.value)
|
||
if rp_nm.id is not None:
|
||
rp_ent: GenTypes.ReplVarEntry* = self.Gen.VarGen.repl_find_entry(trans.ReplCtx, rp_nm.id)
|
||
if rp_ent is not None:
|
||
rp_val: mlirlite.Value* = None
|
||
if rp_ent.TyTag == GenTypes.REPL_TY_STRUCT:
|
||
# The struct value is inline in the slot: the slot address is the object address (self)
|
||
rp_val = self.Gen.VarGen.repl_try_addr(trans, rp_nm.id, cl)
|
||
else: rp_val = self.Gen.VarGen.repl_try_read(trans, rp_nm.id, cl)
|
||
rp_cls: str = None
|
||
if rp_val is not None and rp_val.Ty is not None:
|
||
rp_sty: mlirlite.LLVMType* = self.Gen.StructGen.get_struct_type_from_value(rp_val)
|
||
if rp_sty is not None: rp_cls = self.Gen.StructGen.get_class_name_by_type(pool, rp_sty)
|
||
if rp_cls is not None:
|
||
rp_obj: mlirlite.Value* = rp_val
|
||
if self.Gen.ExprGenBase.is_ptr_type(rp_val.Ty) != 0 and rp_val.Ty.Pointee != 0 and self.Gen.ExprGenBase.is_ptr_type(rp_val.Ty.Pointee) != 0:
|
||
rp_obj = mlirlite.build_load(builder, rp_val.Ty, rp_val)
|
||
if rp_obj is not None:
|
||
if hit_box is not None: hit_box[0] = 1
|
||
return self.Gen.CallGenCall._translate_method_call(pool, builder, mod, rp_cls, rp_at.attr, rp_obj, cl, trans)
|
||
# Session variable hit but the receiver is not a struct class (int/str/other
|
||
# pointer): do not intercept, let the branches below (list builtin / FakeDuck) handle it.
|
||
|
||
# Detect method calls obj.method(args): when cl.func is an Attribute and obj is a registered
|
||
# struct variable, translate as a method call.
|
||
if cl.func is not None and cl.func.kind() == ast.ASTKind.Attribute:
|
||
method_at: ast.Attribute* = ast.Attribute*(cl.func)
|
||
if method_at is not None and method_at.value is not None:
|
||
# Chained method call a.b().c(): value is an inner Call. Translate the inner call to get
|
||
# the receiver, then dispatch the outer method (otherwise it is treated as an attribute).
|
||
if method_at.value.kind() == ast.ASTKind.Call and trans is not None:
|
||
recv_val_c: mlirlite.Value* = self.Gen.ExprGen.translate_value(builder, pool, mod, method_at.value, None, 0, trans)
|
||
if recv_val_c is not None and recv_val_c.Ty is not None:
|
||
recv_ty_c: mlirlite.LLVMType* = recv_val_c.Ty
|
||
if self.Gen.ExprGenBase.is_ptr_type(recv_ty_c) != 0:
|
||
recv_ty_c = recv_ty_c.Pointee
|
||
# Heap objects (@t.Object) are double pointers: deref once more to get the struct
|
||
if self.Gen.ExprGenBase.is_ptr_type(recv_ty_c) != 0: recv_ty_c = recv_ty_c.Pointee
|
||
recv_cls_c: str = self.Gen.StructGen.get_class_name_by_type(pool, recv_ty_c)
|
||
if recv_cls_c is not None:
|
||
if self.Gen.CallGenProp._is_list_builtin_receiver(pool, recv_val_c, recv_cls_c, method_at.attr, cl) == 0:
|
||
obj_ptr_c: mlirlite.Value* = recv_val_c
|
||
if self.Gen.ExprGenBase.is_ptr_type(recv_val_c.Ty) != 0 and recv_val_c.Ty.Pointee != 0 and self.Gen.ExprGenBase.is_ptr_type(recv_val_c.Ty.Pointee) != 0:
|
||
obj_ptr_c = mlirlite.build_load(builder, recv_val_c.Ty, recv_val_c)
|
||
if obj_ptr_c is not None:
|
||
if hit_box is not None: hit_box[0] = 1
|
||
return self.Gen.CallGenCall._translate_method_call(pool, builder, mod, recv_cls_c, method_at.attr, obj_ptr_c, cl, trans)
|
||
if method_at.value.kind() == ast.ASTKind.Name and trans is not None:
|
||
method_obj_nm: ast.Name* = ast.Name*(method_at.value)
|
||
if method_obj_nm.id is not None:
|
||
# nonlocal objects read the pointer from env (load_nonlocal_var); lookup_var would
|
||
# cross functions and corrupt SSA. get_struct_type_from_value recursively unwraps Ptr.
|
||
if GenApi.is_nonlocal_name(trans, method_obj_nm.id) != 0:
|
||
nl_obj: mlirlite.Value* = self.Gen.StmtGenDecl.load_nonlocal_var(trans, method_obj_nm.id)
|
||
if nl_obj is not None and nl_obj.Ty is not None:
|
||
nl_deref: mlirlite.Value* = self.Gen.ExprGenBase._deref_if_ptr_ptr(builder, nl_obj)
|
||
if nl_deref is None: nl_deref = nl_obj
|
||
nl_struct: mlirlite.LLVMType* = self.Gen.StructGen.get_struct_type_from_value(nl_deref)
|
||
if nl_struct is not None:
|
||
nl_cls: str = self.Gen.StructGen.get_class_name_by_type(pool, nl_struct)
|
||
if nl_cls is not None:
|
||
if self.Gen.CallGenProp._is_list_builtin_receiver(pool, nl_deref, nl_cls, func_name, cl) == 0:
|
||
if hit_box is not None: hit_box[0] = 1
|
||
return self.Gen.CallGenCall._translate_method_call(pool, builder, mod, nl_cls, func_name, nl_deref, cl, trans)
|
||
# Non-local object but dispatch failed: never fall back to lookup_var (its
|
||
# Alloca belongs to the parent function); return None so the caller fails fast.
|
||
return None
|
||
# Look up the object variable
|
||
obj_var: mlirlite.Value* = self.Gen.VarGen.lookup_var(trans.SymTab, method_obj_nm.id)
|
||
if obj_var is not None and obj_var.Ty is not None:
|
||
# Identify list generic container annotations early (shared by the class branches below): rawptr[list[rawptr[AST]]] is Ptr(Ptr(Struct('list[...]'))), and the list specialty struct is not registered as a normal class, so unwrap one rawptr and flag is_list_annot_builtin for the inlined list branch.
|
||
var_entry: GenTypes.VarEntry* = self.Gen.VarGen.lookup_var_entry(trans.SymTab, method_obj_nm.id)
|
||
is_list_annot_builtin: int = 0
|
||
la_buf: str = self.Gen.VarGen.var_annot_class_name(var_entry)
|
||
if la_buf is not None:
|
||
la_tail: str = la_buf
|
||
if string.strncmp(la_buf, "rawptr[", 7) == 0: la_tail = la_buf + 7
|
||
if string.strncmp(la_tail, "list", 4) == 0:
|
||
if self.Gen.CallGenProp._is_list_builtin_receiver(pool, obj_var, la_tail, func_name, cl) != 0: is_list_annot_builtin = 1
|
||
match obj_var.Ty:
|
||
case mlirlite.LLVMType.Ptr(obj_struct_ty):
|
||
class_nm: str = self.Gen.StructGen.get_class_name_by_type(pool, obj_struct_ty)
|
||
if class_nm is not None:
|
||
# list builtin container methods (append/get/set/pop/clear/__iter__/...) are
|
||
# inlined by the list builtin branch below; skip _translate_method_call for them.
|
||
if self.Gen.CallGenProp._is_list_builtin_receiver(pool, obj_var, class_nm, func_name, cl) == 0 and is_list_annot_builtin == 0:
|
||
if hit_box is not None: hit_box[0] = 1
|
||
return self.Gen.CallGenCall._translate_method_call(pool, builder, mod, class_nm, func_name, obj_var, cl, trans)
|
||
# Pointer variable: alloca is Ptr(Ptr(Struct)); load to get the actual
|
||
# Ptr(Struct) before lookup.
|
||
match obj_struct_ty:
|
||
case mlirlite.LLVMType.Ptr(inner_struct_ty):
|
||
inner_nm: str = self.Gen.StructGen.get_class_name_by_type(pool, inner_struct_ty)
|
||
if inner_nm is not None:
|
||
# Skip list builtin methods here too (same as the Ptr(Struct) branch);
|
||
# inner_nm may also be misread as the inner element class.
|
||
if self.Gen.CallGenProp._is_list_builtin_receiver(pool, obj_var, inner_nm, func_name, cl) == 0 and is_list_annot_builtin == 0:
|
||
obj_ptr: mlirlite.Value* = mlirlite.build_load(builder, obj_struct_ty, obj_var)
|
||
if obj_ptr is not None:
|
||
if hit_box is not None: hit_box[0] = 1
|
||
return self.Gen.CallGenCall._translate_method_call(pool, builder, mod, inner_nm, func_name,obj_ptr, cl, trans)
|
||
# FakeDuck: str/bytes variables (Ptr(i8), empty AnnotClassName) dispatch to the
|
||
# extension classes _Type/_str. Skip when is_list_annot_builtin is set.
|
||
if is_list_annot_builtin == 0:
|
||
fd_name_res: mlirlite.Value* = self.Gen.CallGenFd._translate_fakeduck_call(pool, builder, mod, None, func_name, cl, trans)
|
||
if fd_name_res is not None: return fd_name_res
|
||
# AnnotClassName fallback: for unions simplified to Ptr(i8), look up the struct by the
|
||
# original annotation's class name. Skip when is_list_annot_builtin is set.
|
||
annot_val: str = self.Gen.VarGen.var_annot_class_name(var_entry)
|
||
if annot_val is not None and is_list_annot_builtin == 0:
|
||
# Parse the module-qualified name "memhub.MemBuddy" -> mod_nm="memhub", cls_nm="MemBuddy"
|
||
dot_ptr: str = string.strrchr(annot_val, '.')
|
||
cls_nm2: str = annot_val
|
||
mod_nm2: str = None
|
||
if dot_ptr is not None:
|
||
cls_nm2 = dot_ptr + 1
|
||
total_len_av: sizet = string.strlen(annot_val)
|
||
cls_len_av: sizet = string.strlen(cls_nm2)
|
||
mod_len2: sizet = total_len_av - cls_len_av - 1
|
||
mod_nm2 = pool.alloc(mod_len2 + 1)
|
||
if mod_nm2 is not None:
|
||
string.strncpy(mod_nm2, annot_val, mod_len2)
|
||
mod_nm2[mod_len2] = '\0'
|
||
# Check whether the struct is registered
|
||
if self.Gen.StructGen.get_struct_type(cls_nm2) is not None:
|
||
# Skip list builtin methods (same as the Ptr(Struct) branch); fall through
|
||
# to the list builtin branch below.
|
||
if self.Gen.CallGenProp._is_list_builtin_receiver(pool, obj_var, cls_nm2, func_name, cl) == 0:
|
||
# Struct registered: load obj_ptr and dispatch the method call
|
||
i8_ptr_ty2: mlirlite.LLVMType* = mlirlite.Ptr(pool, mlirlite.Int8(pool))
|
||
obj_ptr2: mlirlite.Value* = mlirlite.build_load(builder, i8_ptr_ty2, obj_var)
|
||
if obj_ptr2 is not None:
|
||
if hit_box is not None: hit_box[0] = 1
|
||
return self.Gen.CallGenCall._translate_method_call(pool, builder, mod, cls_nm2, func_name,obj_ptr2, cl, trans)
|
||
else:
|
||
# Skip list builtin methods: the unregistered-struct path must not build a
|
||
# {sha1}.{cls}.append name either, since list methods have no such function.
|
||
if self.Gen.CallGenProp._is_list_builtin_receiver(pool, obj_var, cls_nm2, func_name, cl) == 0:
|
||
# FakeDuck: str variable methods dispatch to _Type/_str (type_id, upper,
|
||
# isspace, ...) when AnnotClassName=="str"; self uses a shadow struct.
|
||
fd_res: mlirlite.Value* = self.Gen.CallGenFd._translate_fakeduck_call(pool, builder, mod, cls_nm2, func_name, cl, trans)
|
||
if fd_res is not None: return fd_res
|
||
# Struct unregistered (cross-module dependency not translated): look up
|
||
# the module SHA1 to build the method call name.
|
||
sha1_fc: str = None
|
||
if mod_nm2 is not None: sha1_fc = self._lookup_module_sha1(mod_nm2)
|
||
if sha1_fc is not None:
|
||
# Build the "{sha1}.{cls_nm2}.{func_name}" call name
|
||
mlen3: sizet = string.strlen(sha1_fc) + string.strlen(cls_nm2) + string.strlen(func_name) + 3
|
||
mangled3: str = pool.alloc(mlen3)
|
||
if mangled3 is not None:
|
||
viperlib.snprintf(mangled3, mlen3, "%s.%s.%s", sha1_fc, cls_nm2, func_name)
|
||
# load obj_ptr (self)
|
||
i8_ptr_ty3: mlirlite.LLVMType* = mlirlite.Ptr(pool, mlirlite.Int8(pool))
|
||
obj_ptr3: mlirlite.Value* = mlirlite.build_load(builder, i8_ptr_ty3, obj_var)
|
||
if obj_ptr3 is not None:
|
||
# Build the argument chain: obj_ptr3 (self) -> other args
|
||
mlirlite.value_set_next(obj_ptr3, None)
|
||
head3: mlirlite.Value* = obj_ptr3
|
||
tail3: mlirlite.Value* = obj_ptr3
|
||
for ai3 in range(int(can)):
|
||
arg3: ast.AST* = cargs.get(ai3)
|
||
if arg3 is not None:
|
||
arg_val3: mlirlite.Value* = self.Gen.ExprGen.translate_value(builder, pool, mod, arg3, None, 0, trans)
|
||
if arg_val3 is not None:
|
||
mlirlite.value_set_next(arg_val3, None)
|
||
mlirlite.value_set_next(tail3, arg_val3)
|
||
tail3 = arg_val3
|
||
total3: int = int(1 + can)
|
||
ret_ty3: mlirlite.LLVMType* = self._special_method_ret_ty(pool, cls_nm2, func_name)
|
||
if ret_ty3 is None:
|
||
# Authoritative source: vpi stub return-annotation cache (from the method definition)
|
||
ret_ty3 = self._lookup_method_ret_ty_via_vpi(pool, sha1_fc, cls_nm2, func_name)
|
||
if ret_ty3 is None: ret_ty3 = self._LookupMethodRetTyFromTextLL(pool, sha1_fc, cls_nm2, func_name)
|
||
if ret_ty3 is None:
|
||
# Reached only when the annotated class's struct is unregistered, so MRO cannot
|
||
# find the Mixin's defining module; keep the default i32 instead of failing here.
|
||
ret_ty3 = mlirlite.Int32(pool)
|
||
# Coerce args to the vpi definition's params (width alignment), then declare
|
||
head3 = self.Gen.CallGenLib._coerce_args_to_def_params(pool, builder, mangled3, head3, total3, cargs, 1, trans)
|
||
self._ensure_method_declare(pool, mod, mangled3, ret_ty3, head3, total3)
|
||
return mlirlite.build_call(builder, mangled3, head3, total3, ret_ty3, 0)
|
||
else:
|
||
# Compiler-generated special methods (__before_init__/__init__): their return
|
||
# type is defined deterministically, so route through _translate_method_call.
|
||
sp_ty4: mlirlite.LLVMType* = self._special_method_ret_ty(pool, cls_nm2, func_name)
|
||
if sp_ty4 is not None:
|
||
i8_ptr_ty4: mlirlite.LLVMType* = mlirlite.Ptr(pool, mlirlite.Int8(pool))
|
||
obj_ptr4: mlirlite.Value* = mlirlite.build_load(builder, i8_ptr_ty4, obj_var)
|
||
if obj_ptr4 is not None:
|
||
if hit_box is not None: hit_box[0] = 1
|
||
return self.Gen.CallGenCall._translate_method_call(pool, builder, mod, cls_nm2, func_name, obj_ptr4, cl, trans)
|
||
|
||
# Detect expression-receiver method calls: (expr).method(args). When method_at.value is a
|
||
# BinOp/Subscript/Call, evaluate it first, then resolve the class name from its LLVM type.
|
||
if cl.func is not None and cl.func.kind() == ast.ASTKind.Attribute:
|
||
expr_method_at: ast.Attribute* = ast.Attribute*(cl.func)
|
||
if expr_method_at is not None and expr_method_at.value is not None and trans is not None:
|
||
evk: int = expr_method_at.value.kind()
|
||
# Attribute receivers come in two forms: (1) module-qualified paths, handled below;
|
||
# (2) field-chain receivers (self.writer.write_bits), which must use this method-call path.
|
||
is_mod_path_expr: int = 0
|
||
if evk == ast.ASTKind.Attribute:
|
||
expr_root: ast.AST* = expr_method_at.value
|
||
while expr_root is not None and expr_root.kind() == ast.ASTKind.Attribute: expr_root = ast.Attribute*(expr_root).value
|
||
if expr_root is not None and expr_root.kind() == ast.ASTKind.Name:
|
||
expr_root_nm: str = ast.Name*(expr_root).id
|
||
if expr_root_nm is not None:
|
||
if trans._imported_modules is not None:
|
||
if self.Gen.ImportsGen.is_module_imported(trans._imported_modules, expr_root_nm) != 0: is_mod_path_expr = 1
|
||
if evk != ast.ASTKind.Name and is_mod_path_expr == 0:
|
||
# Translate the receiver expression (the result should be Ptr(Struct))
|
||
expr_obj: mlirlite.Value* = self.Gen.ExprGen.translate_value(builder, pool, mod, expr_method_at.value, funcs_ptr, func_count, trans)
|
||
if expr_obj is not None and expr_obj.Ty is not None:
|
||
expr_cls_nm: str = None
|
||
# Recursively unwrap Ptr to handle the Ptr(Ptr(Struct)) of X* unions (e.g. Vec3**);
|
||
# otherwise the method call is treated as a bare @dot and links undefined.
|
||
expr_struct_ty: mlirlite.LLVMType* = self.Gen.StructGen.get_struct_type_from_value(expr_obj)
|
||
if expr_struct_ty is not None: expr_cls_nm = self.Gen.StructGen.get_class_name_by_type(pool, expr_struct_ty)
|
||
# Fallback when the LLVM type is Ptr(i8) (method returns X* with no type info): infer
|
||
# the class from the receiver expression via the global ExprGenCls singleton.
|
||
if expr_cls_nm is None: expr_cls_nm = self.Gen.ExprGenCls.infer_expr_class(pool, mod, expr_method_at.value, trans)
|
||
if expr_cls_nm is not None:
|
||
# Value-type receivers (embedded struct fields) must pass the field address (GEP),
|
||
# not the loaded struct value, or the ABI mismatches and the callee dereferences it as this.
|
||
# **只有 Attribute 接收者才是"字段"**:本分支的接收者按 L1236 的设计可以是
|
||
# BinOp/Subscript/Call(如 `(p - q).method()`),它们绝不能进 get_attribute_ptr
|
||
# —— 该 API 只接受 Attribute,内部直接强转成 ast.Attribute 并读 `.attr`;
|
||
# 对别的节点就会命中偏移 9 的那个字段(BinOp 的 op=1 / Name 的 ctx=1 …),
|
||
# 把整数 1 当字符串传给 strcmp → ASan 在 0x1 上 access-violation。
|
||
expr_is_ptr: int = 0
|
||
match expr_obj.Ty:
|
||
case mlirlite.LLVMType.Ptr(expr_ptr_inner):
|
||
expr_is_ptr = 1
|
||
case _:
|
||
expr_is_ptr = 0
|
||
if expr_is_ptr == 0 and expr_method_at.value.kind() == ast.ASTKind.Attribute:
|
||
expr_addr: mlirlite.Value* = self.Gen.ExprGenAttr.get_attribute_ptr(builder, pool, mod, expr_method_at.value, trans)
|
||
if expr_addr is not None: expr_obj = expr_addr
|
||
if hit_box is not None: hit_box[0] = 1
|
||
return self.Gen.CallGenCall._translate_method_call(pool, builder, mod, expr_cls_nm, func_name, expr_obj, cl, trans)
|
||
# FakeDuck: when the expression receiver fails to resolve and is a char* (e.g.
|
||
# " ".isspace()), try the _Type/_str extension methods (temp shadow, TPC L2858-2873).
|
||
fd_expr_res: mlirlite.Value* = self.Gen.CallGenFd._translate_fakeduck_call(pool, builder, mod, None, func_name, cl, trans)
|
||
if fd_expr_res is not None: return fd_expr_res
|
||
|
||
# Detect self.field.method() calls: translate the field value to an object pointer, then
|
||
# dispatch. Path 2 falls back to the field's AnnotClassName when its type became Ptr(i8).
|
||
if cl.func is not None and cl.func.kind() == ast.ASTKind.Attribute:
|
||
attr_method_at: ast.Attribute* = ast.Attribute*(cl.func)
|
||
if attr_method_at is not None and attr_method_at.value is not None:
|
||
if attr_method_at.value.kind() == ast.ASTKind.Attribute and trans is not None:
|
||
inner_attr: ast.Attribute* = ast.Attribute*(attr_method_at.value)
|
||
if inner_attr is not None and inner_attr.attr is not None:
|
||
# Translate self.__pool__ to get the field value (an already-loaded object pointer)
|
||
field_obj: mlirlite.Value* = self.Gen.ExprGen.translate_value(builder, pool, mod, attr_method_at.value, funcs_ptr, func_count, trans)
|
||
if field_obj is not None and field_obj.Ty is not None:
|
||
field_cls_nm: str = None
|
||
# Path 1: get the class name from the field value's LLVM type
|
||
match field_obj.Ty:
|
||
case mlirlite.LLVMType.Ptr(field_struct_ty):
|
||
field_cls_nm = self.Gen.StructGen.get_class_name_by_type(pool, field_struct_ty)
|
||
# Path 2: fall back to the field's annotation class name
|
||
if field_cls_nm is None:
|
||
if inner_attr.value is not None and inner_attr.value.kind() == ast.ASTKind.Name:
|
||
inner_name_node: ast.Name* = ast.Name*(inner_attr.value)
|
||
if inner_name_node is not None and inner_name_node.id is not None:
|
||
inner_var: mlirlite.Value* = self.Gen.VarGen.lookup_var(trans.SymTab, inner_name_node.id)
|
||
if inner_var is not None and inner_var.Ty is not None:
|
||
match inner_var.Ty:
|
||
case mlirlite.LLVMType.Ptr(field_owner_struct_ty):
|
||
field_info: GenTypes.FieldEntry* = self.Gen.StructGen.lookup_field(field_owner_struct_ty, inner_attr.attr)
|
||
if field_info is not None:
|
||
field_annot: str = self.Gen.StructGen.get_field_annot_class_name(field_info)
|
||
if field_annot is not None:
|
||
dot_p: str = string.strrchr(field_annot, 46)
|
||
if dot_p is not None: field_cls_nm = dot_p + 1
|
||
else: field_cls_nm = field_annot
|
||
if field_cls_nm is not None:
|
||
# Value-type fields (embedded structs): pass the field address (GEP), not the
|
||
# loaded struct value, or the ABI mismatches and the callee dereferences it as this.
|
||
is_val_field: int = 0
|
||
match field_obj.Ty:
|
||
case mlirlite.LLVMType.Ptr(field_ptr_inner):
|
||
is_val_field = 0
|
||
case _:
|
||
is_val_field = 1
|
||
if is_val_field != 0:
|
||
field_addr: mlirlite.Value* = self.Gen.ExprGenAttr.get_attribute_ptr(builder, pool, mod, attr_method_at.value, trans)
|
||
if field_addr is not None: field_obj = field_addr
|
||
if hit_box is not None: hit_box[0] = 1
|
||
return self.Gen.CallGenCall._translate_method_call(pool, builder, mod, field_cls_nm, func_name, field_obj, cl, trans)
|
||
|
||
# Detect list builtin method calls (get/set/pop/clear/__iter__/append). list[T] is a generic
|
||
# class that does not register a struct, so the method-call handler above cannot process it.
|
||
if cl.func is not None and cl.func.kind() == ast.ASTKind.Attribute:
|
||
lm_at: ast.Attribute* = ast.Attribute*(cl.func)
|
||
if lm_at is not None and lm_at.attr is not None and lm_at.value is not None:
|
||
lm_name: str = lm_at.attr
|
||
# Check whether this is a list method name
|
||
lm_is_list: int = 0
|
||
if string.strcmp(lm_name, "get") == 0: lm_is_list = 1
|
||
elif string.strcmp(lm_name, "__getitem__") == 0: lm_is_list = 1
|
||
elif string.strcmp(lm_name, "set") == 0: lm_is_list = 1
|
||
elif string.strcmp(lm_name, "__setitem__") == 0: lm_is_list = 1
|
||
elif string.strcmp(lm_name, "pop") == 0: lm_is_list = 1
|
||
elif string.strcmp(lm_name, "clear") == 0: lm_is_list = 1
|
||
elif string.strcmp(lm_name, "__iter__") == 0: lm_is_list = 1
|
||
elif string.strcmp(lm_name, "append") == 0: lm_is_list = 1
|
||
|
||
if lm_is_list == 1 and trans is not None:
|
||
# Translate the list object
|
||
lm_obj: mlirlite.Value* = self.Gen.ExprGen.translate_value(builder, pool, mod, lm_at.value, funcs_ptr, func_count, trans)
|
||
if lm_obj is not None:
|
||
# Defensive check: lm_obj must be a pointer for GEP. translate_value may return
|
||
# i32 0 for some Attribute nodes, which would make the GEP base non-pointer.
|
||
if lm_obj.Ty is None or self.Gen.ExprGenBase.is_ptr_type(lm_obj.Ty) == 0:
|
||
# translate_value type inference failed (target is not a pointer): fail fast,
|
||
# never swallow it silently.
|
||
self.Gen.TypeGenErr.fatal_error(lm_at.value, f"list method '{lm_name}': target object is not a pointer", "TypeError")
|
||
# Confirm the object really is a list (the branch matches by method name only, and
|
||
# get/set are not list-specific); skip otherwise to avoid wrong GEP offsets.
|
||
lm_conf_ty: mlirlite.LLVMType* = self.Gen.StructGen.get_struct_type_from_value(lm_obj)
|
||
lm_conf_nm: str = None
|
||
if lm_conf_ty is not None: lm_conf_nm = mlirlite.get_struct_name(lm_conf_ty)
|
||
# [S4] Compare the container base name exactly: the LLVM name carries a module sha1
|
||
# prefix; SpecTypeNameBaseIs strips/truncates before comparing, avoiding substring false hits.
|
||
if lm_conf_nm is not None and TSpec.SpecTypeNameBaseIs(lm_conf_nm, "list") == 0: return None
|
||
# Set hit_box for the list builtin branch: void methods (set/clear) return None,
|
||
# so without the flag translate_call would fall through to a cross-module lookup.
|
||
if hit_box is not None: hit_box[0] = 1
|
||
i64_ty: mlirlite.LLVMType* = mlirlite.Int64(pool)
|
||
i8_ty: mlirlite.LLVMType* = mlirlite.Int8(pool)
|
||
i8_ptr_ty: mlirlite.LLVMType* = mlirlite.Ptr(pool, i8_ty)
|
||
# Element type T is resolved from the list specialization name (list[int]->i32,
|
||
# list[str]->i8*); using a fixed i8* would corrupt adjacent elements in set/pop.
|
||
lm_esty: mlirlite.LLVMType* = self.Gen.StructGen.get_struct_type_from_value(lm_obj)
|
||
lm_ename: str = None
|
||
if lm_esty is not None: lm_ename = mlirlite.get_struct_name(lm_esty)
|
||
lm_elem_ty: mlirlite.LLVMType* = None
|
||
if lm_ename is not None: lm_elem_ty = self.Gen.ExprGenName._list_elem_type_from_name(pool, lm_ename)
|
||
if lm_elem_ty is None: lm_elem_ty = i8_ptr_ty
|
||
|
||
# get / __getitem__: return the element at index
|
||
if string.strcmp(lm_name, "get") == 0 or string.strcmp(lm_name, "__getitem__") == 0:
|
||
if can >= 1:
|
||
# Load __data__ (offset 0)
|
||
g_idx0: mlirlite.Value* = mlirlite.const_int64(pool, 0)
|
||
g_dpp: mlirlite.Value* = mlirlite.build_gep(builder, i64_ty, lm_obj, g_idx0)
|
||
g_data: mlirlite.Value* = mlirlite.build_load(builder, i8_ptr_ty, g_dpp)
|
||
# Load __elem_size__ (offset 32, index 4)
|
||
g_idx4: mlirlite.Value* = mlirlite.const_int64(pool, 4)
|
||
g_epp: mlirlite.Value* = mlirlite.build_gep(builder, i64_ty, lm_obj, g_idx4)
|
||
g_esize: mlirlite.Value* = mlirlite.build_load(builder, i64_ty, g_epp)
|
||
# Translate the index
|
||
g_idx_node: ast.AST* = cargs.get(0)
|
||
g_idx_val: mlirlite.Value* = self.Gen.ExprGen.translate_value(builder, pool, mod, g_idx_node, None, 0, trans)
|
||
if g_data is not None and g_esize is not None and g_idx_val is not None:
|
||
g_idx_i64: mlirlite.Value* = self.Gen.ExprGenBase.coerce_to_type(builder, g_idx_val, i64_ty, g_idx_node)
|
||
g_offset: mlirlite.Value* = mlirlite.build_mul(builder, g_idx_i64, g_esize)
|
||
g_addr: mlirlite.Value* = mlirlite.build_gep(builder, i8_ty, g_data, g_offset)
|
||
if g_addr is not None:
|
||
g_ep: mlirlite.Value* = mlirlite.build_bitcast(builder, g_addr, mlirlite.Ptr(pool, lm_elem_ty))
|
||
if g_ep is not None: return mlirlite.build_load(builder, lm_elem_ty, g_ep)
|
||
|
||
# set / __setitem__: set the element at index
|
||
elif string.strcmp(lm_name, "set") == 0 or string.strcmp(lm_name, "__setitem__") == 0:
|
||
if can >= 2:
|
||
s_idx0: mlirlite.Value* = mlirlite.const_int64(pool, 0)
|
||
s_dpp: mlirlite.Value* = mlirlite.build_gep(builder, i64_ty, lm_obj, s_idx0)
|
||
s_data: mlirlite.Value* = mlirlite.build_load(builder, i8_ptr_ty, s_dpp)
|
||
s_idx4: mlirlite.Value* = mlirlite.const_int64(pool, 4)
|
||
s_epp: mlirlite.Value* = mlirlite.build_gep(builder, i64_ty, lm_obj, s_idx4)
|
||
s_esize: mlirlite.Value* = mlirlite.build_load(builder, i64_ty, s_epp)
|
||
s_idx_node: ast.AST* = cargs.get(0)
|
||
s_val_node: ast.AST* = cargs.get(1)
|
||
s_idx_val: mlirlite.Value* = self.Gen.ExprGen.translate_value(builder, pool, mod, s_idx_node, None, 0, trans)
|
||
s_val_val: mlirlite.Value* = self.Gen.ExprGen.translate_value(builder, pool, mod, s_val_node, None, 0, trans)
|
||
if s_data is not None and s_esize is not None and s_idx_val is not None and s_val_val is not None:
|
||
s_idx_i64: mlirlite.Value* = self.Gen.ExprGenBase.coerce_to_type(builder, s_idx_val, i64_ty, s_idx_node)
|
||
s_offset: mlirlite.Value* = mlirlite.build_mul(builder, s_idx_i64, s_esize)
|
||
s_addr: mlirlite.Value* = mlirlite.build_gep(builder, i8_ty, s_data, s_offset)
|
||
if s_addr is not None:
|
||
s_val_elem: mlirlite.Value* = self.Gen.ExprGenBase.coerce_to_type(builder, s_val_val, lm_elem_ty, s_val_node)
|
||
s_ep: mlirlite.Value* = mlirlite.build_bitcast(builder, s_addr, mlirlite.Ptr(pool, lm_elem_ty))
|
||
if s_ep is not None:
|
||
mlirlite.build_store(builder, s_val_elem, s_ep)
|
||
return None
|
||
|
||
# pop: return the last element and decrement count
|
||
elif string.strcmp(lm_name, "pop") == 0:
|
||
p_idx1: mlirlite.Value* = mlirlite.const_int64(pool, 1)
|
||
p_cpp: mlirlite.Value* = mlirlite.build_gep(builder, i64_ty, lm_obj, p_idx1)
|
||
p_count: mlirlite.Value* = mlirlite.build_load(builder, i64_ty, p_cpp)
|
||
p_nc: mlirlite.Value* = mlirlite.build_sub(builder, p_count, mlirlite.const_int64(pool, 1))
|
||
mlirlite.build_store(builder, p_nc, p_cpp)
|
||
p_idx0: mlirlite.Value* = mlirlite.const_int64(pool, 0)
|
||
p_dpp: mlirlite.Value* = mlirlite.build_gep(builder, i64_ty, lm_obj, p_idx0)
|
||
p_data: mlirlite.Value* = mlirlite.build_load(builder, i8_ptr_ty, p_dpp)
|
||
p_idx4: mlirlite.Value* = mlirlite.const_int64(pool, 4)
|
||
p_epp: mlirlite.Value* = mlirlite.build_gep(builder, i64_ty, lm_obj, p_idx4)
|
||
p_esize: mlirlite.Value* = mlirlite.build_load(builder, i64_ty, p_epp)
|
||
if p_data is not None and p_esize is not None and p_nc is not None:
|
||
p_offset: mlirlite.Value* = mlirlite.build_mul(builder, p_nc, p_esize)
|
||
p_addr: mlirlite.Value* = mlirlite.build_gep(builder, i8_ty, p_data, p_offset)
|
||
if p_addr is not None:
|
||
p_ep: mlirlite.Value* = mlirlite.build_bitcast(builder, p_addr, mlirlite.Ptr(pool, lm_elem_ty))
|
||
if p_ep is not None: return mlirlite.build_load(builder, lm_elem_ty, p_ep)
|
||
|
||
# clear: count = 0
|
||
elif string.strcmp(lm_name, "clear") == 0:
|
||
c_idx1: mlirlite.Value* = mlirlite.const_int64(pool, 1)
|
||
c_cpp: mlirlite.Value* = mlirlite.build_gep(builder, i64_ty, lm_obj, c_idx1)
|
||
mlirlite.build_store(builder, mlirlite.const_int64(pool, 0), c_cpp)
|
||
return None
|
||
|
||
# __iter__: iter_index = 0, return self
|
||
elif string.strcmp(lm_name, "__iter__") == 0:
|
||
it_idx5: mlirlite.Value* = mlirlite.const_int64(pool, 5)
|
||
it_ipp: mlirlite.Value* = mlirlite.build_gep(builder, i64_ty, lm_obj, it_idx5)
|
||
mlirlite.build_store(builder, mlirlite.const_int64(pool, 0), it_ipp)
|
||
return lm_obj
|
||
|
||
# append: call list.append(self, item) directly, since it needs capacity checks and
|
||
# memory reallocation handled in the _list implementation.
|
||
elif string.strcmp(lm_name, "append") == 0:
|
||
if can >= 1:
|
||
ap_sha1: str = self._lookup_module_sha1("_list")
|
||
if ap_sha1 is None:
|
||
# Dependency module not compiled (_list not in the chain): fail fast, never
|
||
# silently skip append (elements would never be added).
|
||
self.Gen.TypeGenErr.fatal_error(cl, "list.append: _list module SHA1 not found (dependency module not compiled)", "InternalCompilerError")
|
||
if ap_sha1 is not None:
|
||
# The append symbol is {sha1}.list[AST*].append (specialized name), not
|
||
# {sha1}.list.append; derive the specialized name from lm_obj's type.
|
||
ap_spec_nm: str = None
|
||
ap_struct_ty: mlirlite.LLVMType* = self.Gen.StructGen.get_struct_type_from_value(lm_obj)
|
||
if ap_struct_ty is not None: ap_spec_nm = self.Gen.StructGen.get_class_name_by_type(pool, ap_struct_ty)
|
||
if ap_spec_nm is None: ap_spec_nm = "list"
|
||
# Trigger generic specialization translation (idempotent): the append define is
|
||
# emitted when the specialty class is translated, otherwise the symbol is missing.
|
||
if trans is not None and ap_spec_nm is not None:
|
||
lbkt_ap: str = string.strchr(ap_spec_nm, '[')
|
||
if lbkt_ap is not None:
|
||
rbkt_ap: str = string.strrchr(ap_spec_nm, ']')
|
||
if rbkt_ap is not None and rbkt_ap > lbkt_ap:
|
||
base_len_ap: sizet = sizet(uint64(lbkt_ap) - uint64(ap_spec_nm))
|
||
arg_len_ap: sizet = sizet(uint64(rbkt_ap) - uint64(lbkt_ap) - 1)
|
||
if base_len_ap > 0 and arg_len_ap > 0:
|
||
base_buf_ap: str = pool.alloc(base_len_ap + 1)
|
||
arg_buf_ap: str = pool.alloc(arg_len_ap + 1)
|
||
if base_buf_ap is not None and arg_buf_ap is not None:
|
||
string.strncpy(base_buf_ap, ap_spec_nm, base_len_ap)
|
||
base_buf_ap[base_len_ap] = '\0'
|
||
string.strncpy(arg_buf_ap, lbkt_ap + 1, arg_len_ap)
|
||
arg_buf_ap[arg_len_ap] = '\0'
|
||
args_ap: list[str]* = list[str](pool, 8)
|
||
if args_ap is not None:
|
||
# Comma-separated multiple type args (list[X] / dict[K, V])
|
||
cur_ap: str = arg_buf_ap
|
||
while cur_ap is not None:
|
||
comma_ap: str = string.strchr(cur_ap, ',')
|
||
if comma_ap is None:
|
||
args_ap.append(cur_ap)
|
||
break
|
||
seg_len_ap: sizet = sizet(uint64(comma_ap) - uint64(cur_ap))
|
||
seg_buf_ap: str = pool.alloc(seg_len_ap + 1)
|
||
if seg_buf_ap is not None:
|
||
string.strncpy(seg_buf_ap, cur_ap, seg_len_ap)
|
||
seg_buf_ap[seg_len_ap] = '\0'
|
||
args_ap.append(seg_buf_ap)
|
||
cur_ap = comma_ap + 1
|
||
self.Gen.ClassGenGeneric._specialize_generic_class(trans, base_buf_ap, args_ap)
|
||
# Call name "{sha1}.{spec}.append": length is spec + 25 (16+1+7+1), so the old +8
|
||
# truncated the trailing 'd' and produced an undefined symbol.
|
||
ap_mlen: sizet = string.strlen(ap_sha1) + string.strlen(ap_spec_nm) + 26
|
||
ap_mangled: str = pool.alloc(ap_mlen)
|
||
if ap_mangled is not None:
|
||
viperlib.snprintf(ap_mangled, ap_mlen, "%s.%s.append", ap_sha1, ap_spec_nm)
|
||
ap_item_node: ast.AST* = cargs.get(0)
|
||
ap_item_val: mlirlite.Value* = self.Gen.ExprGen.translate_value(builder, pool, mod, ap_item_node, None, 0, trans)
|
||
if ap_item_val is not None:
|
||
mlirlite.value_set_next(lm_obj, None)
|
||
mlirlite.value_set_next(ap_item_val, None)
|
||
mlirlite.value_set_next(lm_obj, ap_item_val)
|
||
ap_ret_ty: mlirlite.LLVMType* = mlirlite.Int32(pool)
|
||
self._ensure_method_declare(pool, mod, ap_mangled, ap_ret_ty, lm_obj, 2)
|
||
return mlirlite.build_call(builder, ap_mangled, lm_obj, 2, ap_ret_ty, 0)
|
||
|
||
return None |