315 lines
20 KiB
Plaintext
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) |