Files

1418 lines
118 KiB
Plaintext
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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