Files

315 lines
20 KiB
Plaintext

import lib.core.Gen.GenTypes as GenTypes
import lib.core.Gen.GenApi as GenApi
from stdint import *
import ast
import mlirlite
import memhub
import string
import stdio
import viperlib
import lib.core.Gen.Translator as HT
from . import CallGenBase
# CallGenProp - split from CallGenMixin (stage B): property access (getter/setter/deleter / static class calls / list builtins)
# ============================================================
novirtual class CallGenProp(CallGenBase.CallGenBase):
"""Property access (getter/setter/deleter / static class calls / list builtins)"""
# Property methods (@property/@x.setter/@x.deleter) and static/class call helpers.
# Translate obj.prop / obj.prop=v / del obj.prop / Class.method() into the matching {sha1}.ClassName.prop call.
# _translate_property_call - translate a property/static method call.
# Resolve the call name like _translate_method_call; call_head/call_count is the prebuilt arg list.
def _translate_property_call(self, pool: memhub.MemBuddy*, builder: mlirlite.IRBuilder*, mod: mlirlite.LLVMModule*, class_name: str, method_name: str, call_head: mlirlite.Value*, call_count: int, trans: HT.Translator*, arg_nodes: list[ast.AST*]* = None, start_idx: int = 0) -> mlirlite.Value*:
"""Translate a property/static method call, return the call result Value"""
if pool is None or builder is None or class_name is None or method_name is None: return None
# Build the lookup name: ClassName.method_name
lookup_name: str = pool.alloc(128)
if lookup_name is None: return None
viperlib.snprintf(lookup_name, 128, "%s.%s", class_name, method_name)
# Look up the function (exact/suffix match)
found_func: mlirlite.Function* = self.find_func_in_module(mod, lookup_name)
# Determine the call name
call_name: str = lookup_name
if found_func is not None:
found_name: str = mlirlite.function_get_name(found_func)
if found_name is not None: call_name = found_name
else:
# Not found: build the call name from the class's owning module SHA1
cls_sha1_pc: str = self.Gen.StructGen.get_struct_sha1(class_name)
if cls_sha1_pc is not None:
mlen_pc: sizet = string.strlen(cls_sha1_pc) + string.strlen(lookup_name) + 2
mangled_pc: str = pool.alloc(mlen_pc)
if mangled_pc is not None:
viperlib.snprintf(mangled_pc, mlen_pc, "%s.%s", cls_sha1_pc, lookup_name)
call_name = mangled_pc
# Determine the return type
i32_ty: mlirlite.LLVMType* = mlirlite.Int32(pool)
call_ret_ty: mlirlite.LLVMType* = i32_ty
if found_func is not None:
frt: mlirlite.LLVMType* = mlirlite.function_get_ret_ty(found_func)
if frt is not None: call_ret_ty = frt
else:
sp_ty1: mlirlite.LLVMType* = self._special_method_ret_ty(pool, class_name, method_name)
if sp_ty1 is not None: call_ret_ty = sp_ty1
else:
cls_sha1_rt: str = self.Gen.StructGen.get_struct_sha1(class_name)
# Authoritative source: vpi stub return-annotation cache (in place, no hardcoded whitelist)
vpi_ret_ty: mlirlite.LLVMType* = self._lookup_method_ret_ty_via_vpi(pool, cls_sha1_rt, class_name, method_name)
if vpi_ret_ty is not None: call_ret_ty = vpi_ret_ty
else:
# Fallback: parse the module text.ll define line (legacy form not covered by vpi)
looked_up_ty: mlirlite.LLVMType* = self._LookupMethodRetTyFromTextLL(pool, cls_sha1_rt, class_name, method_name)
if looked_up_ty is not None: call_ret_ty = looked_up_ty
else:
# Phase A deps not ready (cls_sha1_rt None / the defining module's text.ll missing):
# keep the i32 default (build_call(Void) returning None would break expression translation).
if cls_sha1_rt is None or self._dep_text_ll_exists(pool, cls_sha1_rt) == 0: call_ret_ty = mlirlite.Int32(pool)
else: call_ret_ty = self._fail_method_ret_ty(method_name, None, class_name, None, cls_sha1_rt)
# Value-reference (&T / &temp T) params: auto-address the user args before param-type
# coercion (self/obj or the classmethod cls occupies index 0, skipped via start_idx).
call_head = self._apply_auto_addr_to_call_args(builder, pool, mod, trans, call_name, arg_nodes, call_head, call_count, start_idx)
# Coerce args to the declared param types (same as _translate_method_call); target func: found_func,
# else the mangled module declare. Otherwise call(i32) vs declare(i64) mismatches and llc errors.
tgt_func_pc: mlirlite.Function* = found_func
if tgt_func_pc is None: tgt_func_pc = self.find_func_in_module(mod, call_name)
if tgt_func_pc is not None:
prev_pc: mlirlite.Value* = None
cur_arg_pc: mlirlite.Value* = call_head
cur_param_pc: mlirlite.Param* = mlirlite.function_get_param_head(tgt_func_pc)
while cur_arg_pc is not None and cur_param_pc is not None:
param_ty_pc: mlirlite.LLVMType* = mlirlite.param_get_ty(cur_param_pc)
if param_ty_pc is not None and cur_arg_pc.Ty is not None:
coerced_pc: mlirlite.Value* = self.Gen.ExprGenBase.coerce_to_type(builder, cur_arg_pc, param_ty_pc)
if coerced_pc is not None and coerced_pc != cur_arg_pc:
# Keep the list intact: the new Value inherits the node's Next; predecessor points to it
mlirlite.value_set_next(coerced_pc, cur_arg_pc.Next)
if prev_pc is None: call_head = coerced_pc
else: mlirlite.value_set_next(prev_pc, coerced_pc)
cur_arg_pc = coerced_pc
prev_pc = cur_arg_pc
cur_arg_pc = cur_arg_pc.Next
cur_param_pc = mlirlite.param_get_next(cur_param_pc)
# Ensure the declare exists, then call
self._ensure_method_declare(pool, mod, call_name, call_ret_ty, call_head, call_count)
return mlirlite.build_call(builder, call_name, call_head, call_count, call_ret_ty, 0)
# _resolve_obj_class_name - resolve an object expression's class short name.
# Prefer the variable annotation (AnnotClassName); fall back to the LLVM type (Ptr(Struct)).
def _resolve_obj_class_name(self, pool: memhub.MemBuddy*, trans: HT.Translator*, obj_node: ast.AST*) -> str:
"""Resolve an object expression's class short name, None on failure"""
if pool is None or trans is None or obj_node is None: return None
if obj_node.kind() == ast.ASTKind.Name:
obj_nm: ast.Name* = ast.Name*(obj_node)
if obj_nm.id is not None:
var_entry: GenTypes.VarEntry* = self.Gen.VarGen.lookup_var_entry(trans.SymTab, obj_nm.id)
annot_val: str = self.Gen.VarGen.var_annot_class_name(var_entry)
if annot_val is not None:
# [D32] Split the qualified name only on '.' outside brackets: the old strrchr over the
# whole string picked the '.' inside "list[sha1.AST*]", yielding a bogus short name.
n_av: sizet = string.strlen(annot_val)
last_dot_av: sizet = n_av
depth_av: int = 0
for ai_av in range(int(n_av)):
ch_av: int = annot_val[ai_av]
if ch_av == '[': depth_av += 1
elif ch_av == ']': depth_av -= 1
elif ch_av == '.' and depth_av == 0: last_dot_av = ai_av
if last_dot_av < n_av: return annot_val + last_dot_av + 1
return annot_val
return None
# _try_property_getter_attr - try to translate obj.attr into a property getter call.
# If the class has an @property/@x.getter for attr, emit call Class.attr(obj); else return None.
def _try_property_getter_attr(self, pool: memhub.MemBuddy*, builder: mlirlite.IRBuilder*, mod: mlirlite.LLVMModule*, at_node: ast.Attribute*, obj_ptr: mlirlite.Value*, trans: HT.Translator*, fallback_class_name: str = None) -> mlirlite.Value*:
"""Try a property getter access, return the call result Value; None if not a getter."""
if at_node is None or at_node.attr is None or obj_ptr is None or trans is None: return None
class_name: str = self._resolve_obj_class_name(pool, trans, at_node.value)
if class_name is None: class_name = fallback_class_name
if class_name is None: return None
meta: int = self.Gen.ClassGenBase.lookup_method_meta(class_name, at_node.attr)
if meta & GenTypes.META_PROP_GETTER == 0: return None
# Single-arg call: obj -> getter (no user args to auto-address; obj is slot 0)
mlirlite.value_set_next(obj_ptr, None)
return self._translate_property_call(pool, builder, mod, class_name, at_node.attr, obj_ptr, 1, trans, None, 1)
# _try_property_setter - try to translate obj.attr = v into a property setter call.
# If the class has an @x.setter for attr, emit call Class.attr$set(obj, v) and return 1; else 0.
def _try_property_setter(self, pool: memhub.MemBuddy*, builder: mlirlite.IRBuilder*, mod: mlirlite.LLVMModule*, target_attr: ast.Attribute*, rhs_val: mlirlite.Value*, trans: HT.Translator*, rhs_node: ast.AST* = None) -> int:
"""Try a property setter assignment, return 1=handled (call emitted)"""
if pool is None or builder is None or mod is None or trans is None: return 0
if target_attr is None or target_attr.attr is None or rhs_val is None: return 0
class_name: str = self._resolve_obj_class_name(pool, trans, target_attr.value)
if class_name is None: return 0
meta: int = self.Gen.ClassGenBase.lookup_method_meta(class_name, target_attr.attr)
if meta & GenTypes.META_PROP_SETTER == 0: return 0
# Translate the object value (self argument)
obj_val: mlirlite.Value* = self.Gen.ExprGen.translate_value(builder, pool, mod, target_attr.value, None, 0, trans)
if obj_val is None: return 0
# Build the argument list: obj -> rhs
mlirlite.value_set_next(obj_val, None)
mlirlite.value_set_next(rhs_val, None)
mlirlite.value_set_next(obj_val, rhs_val)
# The setter function name carries the $set suffix
setter_name_buf: str = pool.alloc(128)
if setter_name_buf is None: return 0
viperlib.snprintf(setter_name_buf, 128, "%s$set", target_attr.attr)
# Value-reference (&T / &temp T) setter value param: expose the rhs AST as user arg 0
# (obj is slot 0) so the helper can take the lvalue address / build a temp copy.
set_arg_nodes: list[ast.AST*]* = None
if rhs_node is not None:
set_arg_nodes = list[ast.AST*](pool, 1)
if set_arg_nodes is not None: set_arg_nodes.append(rhs_node)
self._translate_property_call(pool, builder, mod, class_name, setter_name_buf, obj_val, 2, trans, set_arg_nodes, 1)
return 1
# _try_property_deleter - try to translate del obj.attr into a property deleter call.
# If the class has an @x.deleter for attr, emit call Class.attr$del(obj) and return 1; else 0.
def _try_property_deleter(self, pool: memhub.MemBuddy*, builder: mlirlite.IRBuilder*, mod: mlirlite.LLVMModule*, target_attr: ast.Attribute*, trans: HT.Translator*) -> int:
"""Try a property deleter, return 1=handled (call emitted)"""
if pool is None or builder is None or mod is None or trans is None: return 0
if target_attr is None or target_attr.attr is None: return 0
class_name: str = self._resolve_obj_class_name(pool, trans, target_attr.value)
if class_name is None: return 0
meta: int = self.Gen.ClassGenBase.lookup_method_meta(class_name, target_attr.attr)
if meta & GenTypes.META_PROP_DELETER == 0: return 0
# Translate the object value (self argument)
obj_val: mlirlite.Value* = self.Gen.ExprGen.translate_value(builder, pool, mod, target_attr.value, None, 0, trans)
if obj_val is None: return 0
mlirlite.value_set_next(obj_val, None)
# The deleter function name carries the $del suffix
del_name_buf: str = pool.alloc(128)
if del_name_buf is None: return 0
viperlib.snprintf(del_name_buf, 128, "%s$del", target_attr.attr)
self._translate_property_call(pool, builder, mod, class_name, del_name_buf, obj_val, 1, trans, None, 1)
return 1
# _translate_static_class_call - translate ClassName.method(args) for static/class methods.
# @staticmethod: call Class.method(args...). @classmethod: prepend a stack-allocated class pointer.
def _translate_static_class_call(self, pool: memhub.MemBuddy*, builder: mlirlite.IRBuilder*, mod: mlirlite.LLVMModule*, class_name: str, method_name: str, cl: ast.Call*, trans: HT.Translator*) -> mlirlite.Value*:
"""Translate a ClassName static/class method call, return the call result Value; None otherwise"""
if pool is None or builder is None or mod is None or trans is None: return None
if class_name is None or method_name is None or cl is None: return None
meta: int = self.Gen.ClassGenBase.lookup_method_meta(class_name, method_name)
is_static: int = 0
is_class: int = 0
if meta & GenTypes.META_STATIC != 0: is_static = 1
if meta & GenTypes.META_CLASS != 0: is_class = 1
if is_static == 0 and is_class == 0: return None
# Build the argument list
head: mlirlite.Value* = None
total_count: int = 0
# @classmethod: prepend a stack-allocated class pointer as the cls argument
if is_class != 0:
struct_ty_cc: mlirlite.LLVMType* = self.Gen.StructGen.get_struct_type(class_name)
if struct_ty_cc is None: return None
cls_ptr: mlirlite.Value* = self.Gen.VarGen._alloca_at_entry(builder, struct_ty_cc)
if cls_ptr is None: return None
mlirlite.value_set_next(cls_ptr, None)
head = cls_ptr
total_count = 1
# Translate the call arguments
cargs: list[ast.AST*]* = cl.args
can: sizet = 0
if cargs is not None: can = len(cargs)
tail: mlirlite.Value* = head
for ai in range(int(can)):
arg_node: ast.AST* = cargs.get(ai)
if arg_node is None: continue
arg_val: mlirlite.Value* = self.Gen.ExprGen.translate_value(builder, pool, mod, arg_node, None, 0, trans)
if arg_val is None: continue
mlirlite.value_set_next(arg_val, None)
if head is None:
head = arg_val
tail = arg_val
else:
mlirlite.value_set_next(tail, arg_val)
tail = arg_val
total_count += 1
# Zero-arg @staticmethod (e.g. PyListObj.new()): head=None is a valid no-arg call, so still route
# through _translate_property_call; returning None would fall back to bare-name lookup and fail.
# Value-reference (&T / &temp T) user args: static methods have no self slot (start_idx=0),
# classmethods carry the cls pointer at slot 0 (start_idx=1).
sc_start_idx: int = 0
if is_class != 0: sc_start_idx = 1
return self._translate_property_call(pool, builder, mod, class_name, method_name, head, total_count, trans, cargs, sc_start_idx)
# _is_list_builtin_method - test whether (class_name, method_name) is a list generic builtin.
# list[T] methods are inlined by translate_call's list branch, not registered as functions.
def _is_list_builtin_method(self, class_name: str, method_name: str) -> int:
"""Test whether (class_name, method_name) is a list generic builtin method"""
if class_name is None or method_name is None: return 0
# Class name must start with "list" (list / list[AST*] specializations)
if string.strncmp(class_name, "list", 4) != 0: return 0
# Method name must be one of the list builtin methods
if string.strcmp(method_name, "append") == 0: return 1
if string.strcmp(method_name, "get") == 0: return 1
if string.strcmp(method_name, "set") == 0: return 1
if string.strcmp(method_name, "pop") == 0: return 1
if string.strcmp(method_name, "clear") == 0: return 1
if string.strcmp(method_name, "__iter__") == 0: return 1
if string.strcmp(method_name, "__len__") == 0: return 1
if string.strcmp(method_name, "__getitem__") == 0: return 1
if string.strcmp(method_name, "__setitem__") == 0: return 1
return 0
# _strip_module_sha1_prefix - strip the cross-module specialization sha1 prefix.
# Receiver struct names often carry a "{16 hex}." prefix; use GenApi.sha1_prefix_len (C10).
def _strip_module_sha1_prefix(self, name: str) -> str:
"""Strip the "{16hex}." prefix (return the input unchanged if absent; returns a substring of the input)"""
if name is None: return None
pfx: int = GenApi.sha1_prefix_len(name)
return name + pfx if pfx != 0 else name
# _resolve_generic_receiver_name - resolve the receiver's actual generic specialization name (IR truth).
# Strip the sha1 prefix, take the base before '['; bare generic base => fail-fast (D25), else None.
def _resolve_generic_receiver_name(self, pool: memhub.MemBuddy*, struct_ty: mlirlite.LLVMType*, method_name: str, cl: ast.Call*) -> str:
"""Return the actual generic specialization name (sha1 stripped) or None; a bare generic base fails fast"""
if struct_ty is None: return None
ty_nm: str = mlirlite.get_struct_name(struct_ty)
if ty_nm is None or ty_nm[0] == '\0': return None
short_nm: str = self._strip_module_sha1_prefix(ty_nm)
lb: str = string.strchr(short_nm, '[')
bare_len: sizet = 0
if lb is not None: bare_len = sizet(uint64(lb) - uint64(short_nm))
else: bare_len = string.strlen(short_nm)
if bare_len == 0: return None
base_nm: str = pool.alloc(bare_len + 1)
if base_nm is None: return None
string.strncpy(base_nm, short_nm, bare_len)
base_nm[bare_len] = '\0'
# Base is not a registered generic template (plain class receiver) => not generic; return None
if self.Gen.ClassGenGeneric._find_generic_template(base_nm) is None: return None
if lb is not None: return short_nm
# Bare generic base (no type args): element type unknown -> fail-fast (D25)
self.Gen.TypeGenErr.fatal_error(cl, f"the actual struct type of the receiver of method '{method_name}' is a bare generic base " f"'{short_nm}*' (type arguments lost), so the element type is unknown; " f"silently using the default element type is forbidden (treating elements as i32 would truncate pointers); " f"make the variable annotation/type carry type arguments (e.g. list[ast.AST*])", "TypeError")
return None
# _is_list_builtin_receiver - test list builtin methods from the receiver's actual type.
# Prefer the actual LLVM struct name (sha1 stripped); fall back to the annotation/registered name.
def _is_list_builtin_receiver(self, pool: memhub.MemBuddy*, recv_val: mlirlite.Value*, fallback_name: str, method_name: str, cl: ast.Call*) -> int:
"""Test list builtin methods from the receiver's actual type (fall back to the annotation name if unavailable)"""
sty: mlirlite.LLVMType* = self.Gen.StructGen.get_struct_type_from_value(recv_val)
if sty is not None:
ty_nm: str = mlirlite.get_struct_name(sty)
if ty_nm is not None and ty_nm[0] != '\0':
# Generic template receiver: bare base fail-fast; with type args use the actual specialization
act_nm: str = self._resolve_generic_receiver_name(pool, sty, method_name, cl)
if act_nm is not None: return self._is_list_builtin_method(act_nm, method_name)
# Non-generic template receiver: judge by the actual short name (sha1 stripped)
return self._is_list_builtin_method(self._strip_module_sha1_prefix(ty_nm), method_name)
# Actual type unavailable -> fall back to the annotation/registered name
return self._is_list_builtin_method(fallback_name, method_name)