feat(gen): 值引用自动取址接入虚分派/构造/属性/静态等全部方法调用路径

CallGenBase 新增 _vpi_auto_addr_mode(把原先埋在 _coerce_args_to_def_params 里的 vpi 注解解析抽成唯一实现,两处共用)与 _apply_auto_addr_to_call_args(同模块先查 AutoAddrModes,跨模块回退 vpi 注解)。接入原先完全不查表的路径:虚分派(原在 vpi 取址前 return)、_call_method_on_ptr 覆盖的构造 __new__/__init__(含泛型)与 dunder 分派、属性 getter/setter/deleter、静态/类方法、super()、FakeDuck;属性 setter 由 AssignGenMixin 传入 rhs AST 以支持 &T 取左值地址。
This commit is contained in:
TermiNexus committed 2026-10-07 19:59:52 +08:00
1 parent 5c1b882169
commit c20bd4734b
7 files changed
+143 -19

No files matched your search

+2 -1
View File
@@ -2082,7 +2082,8 @@ novirtual class AssignGenMixin:
# Narrow target to ast.Attribute* explicitly (required under the strict pointer rules) # Narrow target to ast.Attribute* explicitly (required under the strict pointer rules)
at_tg: ast.Attribute* = ast.Attribute*(target) at_tg: ast.Attribute* = ast.Attribute*(target)
# property setter: when attr is a setter, emit the setter call and skip the field store # property setter: when attr is a setter, emit the setter call and skip the field store
if self.Gen.CallGenProp._try_property_setter(pool, builder, mod, at_tg, rhs_val, trans) == 1: continue # (asgn.value gives the setter value param's AST for value-reference auto-address).
if self.Gen.CallGenProp._try_property_setter(pool, builder, mod, at_tg, rhs_val, trans, asgn.value) == 1: continue
# FakeDuck: write per-instance str fields through the {var}__meta__ shadow struct # FakeDuck: write per-instance str fields through the {var}__meta__ shadow struct
fd_an: ast.Attribute* = at_tg fd_an: ast.Attribute* = at_tg
if fd_an is not None and fd_an.value is not None and fd_an.attr is not None: if fd_an is not None and fd_an.value is not None and fd_an.attr is not None:
+84
View File
@@ -176,6 +176,90 @@ novirtual class CallGenBase:
return slot return slot
return av return av
# _vpi_auto_addr_mode - value-reference mode of parameter idx for a cross-module ({sha1}-mangled)
# callee, read from its vpi definition-side param annotations. The parsing (split / ensure-load /
# lookup / segment decode) lives here only; _coerce_args_to_def_params and _apply_auto_addr_to_call_args
# both call it, so there is a single implementation. cur_sha1 is the module being compiled: its own
# functions are covered by AutoAddrModes, so the (guaranteed-miss) vpi load is skipped for them.
# Returns 0=none / 1=ref / 2=temp.
def _vpi_auto_addr_mode(self, call_name: str, cur_sha1: str, idx: int) -> int:
"""Cross-module ({sha1}.cls.method / {sha1}.func) value-reference mode from the vpi cache; 0 if none/unknown."""
if call_name is None or idx < 0: return 0
if GenApi.sha1_prefix_len(call_name) == 0: return 0
# Same-module callee: AutoAddrModes (registration during this compilation) is authoritative.
if cur_sha1 is not None and string.strncmp(call_name, cur_sha1, 16) == 0: return 0
# split {sha1}.{rest}: if rest has '.', it is {cls}.{method}, else {func}
rest: str = call_name + 17
cls_nm: str = None
meth_nm: str = rest
dot_r: str = string.strrchr(rest, 46)
if dot_r is not None:
cls_len: sizet = uint64(dot_r) - uint64(rest)
if cls_len > 0:
cls_buf: bytes = stdlib.malloc(cls_len + 1)
if cls_buf is not None:
string.strncpy(cls_buf, rest, cls_len)
cls_buf[cls_len] = '\0'
cls_nm = cls_buf
meth_nm = dot_r + 1
if meth_nm is None or meth_nm[0] == '\0':
if cls_nm is not None: stdlib.free(cls_nm)
return 0
sha1_buf: bytes = stdlib.malloc(17)
if sha1_buf is None:
if cls_nm is not None: stdlib.free(cls_nm)
return 0
string.strncpy(sha1_buf, call_name, 16)
sha1_buf[16] = '\0'
if self.TempDir is not None and self.TempDirLen > 0: VpiReader._ensure_vpi_param_annot_loaded(sha1_buf, self.TempDir)
prm_annots: str = VpiReader._lookup_vpi_param_annots(sha1_buf, cls_nm, meth_nm)
mode: int = 0
if prm_annots is not None: mode = VpiReader._vpi_param_annot_seg_mode(prm_annots, idx)
stdlib.free(sha1_buf)
if cls_nm is not None: stdlib.free(cls_nm)
return mode
# _apply_auto_addr_to_call_args - auto-address the user arguments of a call whose definition params
# are value references (&T -> take the lvalue address / &temp T -> copy into a temp slot). The
# per-parameter mode is the same-module AutoAddrModes table first, then (for a {sha1}-mangled callee)
# its vpi annotations. head/count is the emitted argument chain; the first start_idx slots are
# non-user (self/obj/cls); arg_nodes holds the source ASTs of the user arguments, indexed by
# (param index - start_idx). Returns the rebuilt chain head; nodes beyond count (e.g. defaults
# appended by the caller) are preserved. Idempotent: _apply_auto_addr_arg passes an already-pointer
# argument through, so a caller that also ran translate_call_arg does not double-address.
def _apply_auto_addr_to_call_args(self, builder: mlirlite.IRBuilder*, pool: memhub.MemBuddy*, mod: mlirlite.LLVMModule*,
trans: HT.Translator*, callee_name: str, arg_nodes: list[ast.AST*]*,
head: mlirlite.Value*, count: int, start_idx: int) -> mlirlite.Value*:
"""Apply value-reference auto-address (&T / &temp T) to a call's user args; returns the chain head."""
if head is None or count <= 0: return head
if callee_name is None or callee_name[0] == '\0': return head
if trans is None: return head
new_head: mlirlite.Value* = None
new_tail: mlirlite.Value* = None
cur: mlirlite.Value* = head
idx: int = 0
while cur is not None and idx < count:
node_v: mlirlite.Value* = cur
if idx >= start_idx:
# Redirect to the canonical CallGenBase sub-object: AutoAddrModes / TempDir live there
# (registration targets self.Gen.CallGenBase), not on the sibling Call* sub-object.
mode: int = self.Gen.CallGenBase._auto_addr_mode(callee_name, idx)
if mode == 0: mode = self.Gen.CallGenBase._vpi_auto_addr_mode(callee_name, trans.ModuleSha1, idx)
if mode != 0:
arg_i: int = idx - start_idx
arg_node: ast.AST* = None
if arg_nodes is not None and arg_i < len(arg_nodes): arg_node = arg_nodes.get(arg_i)
node_v = self.Gen.CallGenBase._apply_auto_addr_arg(builder, pool, mod, trans, mode, arg_node, cur)
if node_v is None: node_v = cur
if new_head is None: new_head = node_v
else: mlirlite.value_set_next(new_tail, node_v)
new_tail = node_v
cur = mlirlite.value_get_next(cur)
idx += 1
if new_head is None: return head
mlirlite.value_set_next(new_tail, cur)
return new_head
def _lookup_module_sha1(self, mod_name: str) -> str: def _lookup_module_sha1(self, mod_name: str) -> str:
"""Look up the SHA1 for a module name via StubMerger.LookupSha1ByModName (exact match + package fallback); None if absent.""" """Look up the SHA1 for a module name via StubMerger.LookupSha1ByModName (exact match + package fallback); None if absent."""
return StubMergerStore.LookupSha1ByModName(mod_name) return StubMergerStore.LookupSha1ByModName(mod_name)
+16 -2
View File
@@ -141,7 +141,7 @@ novirtual class CallGenCall(CallGenBase.CallGenBase):
# first argument (goes through virtual dispatch if the method is in the vtable). # first argument (goes through virtual dispatch if the method is in the vtable).
# ============================================================ # ============================================================
def _call_method_on_ptr(self, pool: memhub.MemBuddy*, builder: mlirlite.IRBuilder*, mod: mlirlite.LLVMModule*, class_name: str, method_name: str, self_ptr: mlirlite.Value*, extra_args: sizet*, extra_count: int, trans: HT.Translator*, node: ast.AST* = None, is_volatile: int = 0) -> mlirlite.Value*: def _call_method_on_ptr(self, pool: memhub.MemBuddy*, builder: mlirlite.IRBuilder*, mod: mlirlite.LLVMModule*, class_name: str, method_name: str, self_ptr: mlirlite.Value*, extra_args: sizet*, extra_count: int, trans: HT.Translator*, node: ast.AST* = None, is_volatile: int = 0, arg_nodes: list[ast.AST*]* = None) -> mlirlite.Value*:
"""Call a method on a struct pointer; adds a volatile barrier for constructors.""" """Call a method on a struct pointer; adds a volatile barrier for constructors."""
if pool is None or builder is None or class_name is None or method_name is None: return None if pool is None or builder is None or class_name is None or method_name is None: return None
if self_ptr is None: return None if self_ptr is None: return None
@@ -295,6 +295,13 @@ novirtual class CallGenCall(CallGenBase.CallGenBase):
total_count: int = 1 + extra_count total_count: int = 1 + extra_count
# Value-reference (&T / &temp T) params: auto-address the user args before default-filling and
# the param-type coercion below (a temp copy must be taken from the source-typed value). call_name
# is the resolved call symbol ({sha}.Class.method / Class.method), matching the registration key.
head = self._apply_auto_addr_to_call_args(builder, pool, mod, trans, call_name, arg_nodes, head, total_count, 1)
# Recover the tail after the (possibly rebuilt) prefix so default args append to the end.
tail = head
while tail is not None and mlirlite.value_get_next(tail) is not None: tail = mlirlite.value_get_next(tail)
# Default-argument filling: if the call site supplies fewer args than the # Default-argument filling: if the call site supplies fewer args than the
# definition, fill the missing ones (defined default, else zero). # definition, fill the missing ones (defined default, else zero).
mdf_defaults: t.CPtr = None mdf_defaults: t.CPtr = None
@@ -1202,6 +1209,9 @@ novirtual class CallGenCall(CallGenBase.CallGenBase):
if mc_has_vt == 1 and mc_method_idx >= 0: if mc_has_vt == 1 and mc_method_idx >= 0:
# Virtual dispatch must pass the adjusted self_arg (multi-inheritance # Virtual dispatch must pass the adjusted self_arg (multi-inheritance
# this-adjust) but read the vtable from the object head obj_ptr. # this-adjust) but read the vtable from the object head obj_ptr.
# Virtual dispatch returns before the cross-module vpi coercion below, so the value-reference
# (&T / &temp T) auto-address must be applied here (same-module table first, then vpi).
head = self._apply_auto_addr_to_call_args(builder, pool, mod, trans, call_name, cargs, head, total_count, 1)
return self._do_virtual_call(pool, builder, mod, class_name, method_name, self_arg, head, total_count, found_func, call_ret_ty, obj_ptr) return self._do_virtual_call(pool, builder, mod, class_name, method_name, self_arg, head, total_count, found_func, call_ret_ty, obj_ptr)
# Cross-module call: coerce args to the vpi definition-side params (width # Cross-module call: coerce args to the vpi definition-side params (width
@@ -1510,7 +1520,9 @@ novirtual class CallGenCall(CallGenBase.CallGenBase):
pp_sp = mlirlite.param_get_next(pp_sp) pp_sp = mlirlite.param_get_next(pp_sp)
pi_sp += 1 pi_sp += 1
if pp_sp is not None: pty_sp = mlirlite.param_get_ty(pp_sp) if pp_sp is not None: pty_sp = mlirlite.param_get_ty(pp_sp)
arg_val_sp: mlirlite.Value* = self.Gen.ExprGen.translate_value(builder, pool, mod, arg_node_sp, None, 0, trans, pty_sp) # Value-reference params (&T / &temp T): translate_call_arg applies the same-module auto-address
# before the coercion below (cross-module vpi addressing is applied in bulk for the whole chain).
arg_val_sp: mlirlite.Value* = self.Gen.CallGenBase.translate_call_arg(builder, pool, mod, trans, call_name, int(ai_sp) + 1, arg_node_sp, pty_sp, None)
if arg_val_sp is None: continue if arg_val_sp is None: continue
if pty_sp is not None: arg_val_sp = self.Gen.ExprGenBase.coerce_to_type(builder, arg_val_sp, pty_sp, arg_node_sp) if pty_sp is not None: arg_val_sp = self.Gen.ExprGenBase.coerce_to_type(builder, arg_val_sp, pty_sp, arg_node_sp)
else: else:
@@ -1549,6 +1561,8 @@ novirtual class CallGenCall(CallGenBase.CallGenBase):
if anc_sha1_sp is None or self._dep_text_ll_exists(pool, anc_sha1_sp) == 0: ret_ty_sp = mlirlite.Int32(pool) if anc_sha1_sp is None or self._dep_text_ll_exists(pool, anc_sha1_sp) == 0: ret_ty_sp = mlirlite.Int32(pool)
else: self._fail_method_ret_ty(method_name, cl) else: self._fail_method_ret_ty(method_name, cl)
# 7. Cross-module: create the declare; emit the call. # 7. Cross-module: create the declare; emit the call.
# Value-reference (&T / &temp T) params: auto-address the parent-method args (self is slot 0).
head = self._apply_auto_addr_to_call_args(builder, pool, mod, trans, call_name, cargs_sp, head, arg_count_sp, 1)
if found_func is None: self._ensure_method_declare(pool, mod, call_name, ret_ty_sp, head, arg_count_sp) if found_func is None: self._ensure_method_declare(pool, mod, call_name, ret_ty_sp, head, arg_count_sp)
is_variadic_sp: int = self.Gen.CallGenVa._check_and_promote_variadic_call(pool, builder, mod, call_name, head, arg_count_sp, cargs_sp, 1, trans) is_variadic_sp: int = self.Gen.CallGenVa._check_and_promote_variadic_call(pool, builder, mod, call_name, head, arg_count_sp, cargs_sp, 1, trans)
return mlirlite.build_call(builder, call_name, head, arg_count_sp, ret_ty_sp, is_variadic_sp) return mlirlite.build_call(builder, call_name, head, arg_count_sp, ret_ty_sp, is_variadic_sp)
+12 -6
View File
@@ -127,7 +127,7 @@ novirtual class CallGenCtor(CallGenBase.CallGenBase):
new_arg_vals[new_arg_count] = sizet(new_av) new_arg_vals[new_arg_count] = sizet(new_av)
new_arg_count += 1 new_arg_count += 1
new_ptr: mlirlite.Value* = self.Gen.CallGenCall._call_method_on_ptr(pool, builder, mod, class_name, "__new__", tmp, new_arg_vals, new_arg_count, trans, diag_node_ct, is_volatile) new_ptr: mlirlite.Value* = self.Gen.CallGenCall._call_method_on_ptr(pool, builder, mod, class_name, "__new__", tmp, new_arg_vals, new_arg_count, trans, diag_node_ct, is_volatile, cargs)
if new_ptr is not None: if new_ptr is not None:
storage_ptr = new_ptr storage_ptr = new_ptr
# __new__ declares `-> t.CPtr` (i8*) but the class type is Ptr(struct_ty). # __new__ declares `-> t.CPtr` (i8*) but the class type is Ptr(struct_ty).
@@ -159,7 +159,7 @@ novirtual class CallGenCtor(CallGenBase.CallGenBase):
arg_vals[real_arg_count] = sizet(av) arg_vals[real_arg_count] = sizet(av)
real_arg_count += 1 real_arg_count += 1
self.Gen.CallGenCall._call_method_on_ptr(pool, builder, mod, class_name, "__init__", storage_ptr, arg_vals, real_arg_count, trans, diag_node_ct, is_volatile) self.Gen.CallGenCall._call_method_on_ptr(pool, builder, mod, class_name, "__init__", storage_ptr, arg_vals, real_arg_count, trans, diag_node_ct, is_volatile, cargs)
elif has_new_flag == 0: elif has_new_flag == 0:
# No __init__ and no __new__: store args field by field # No __init__ and no __new__: store args field by field
# (with __new__, args were already consumed and must not be stored). # (with __new__, args were already consumed and must not be stored).
@@ -394,7 +394,7 @@ novirtual class CallGenCtor(CallGenBase.CallGenBase):
if new_arg_count < 32: if new_arg_count < 32:
new_arg_vals[new_arg_count] = sizet(nkw_av) new_arg_vals[new_arg_count] = sizet(nkw_av)
new_arg_count += 1 new_arg_count += 1
new_ptr: mlirlite.Value* = self.Gen.CallGenCall._call_method_on_ptr(pool, builder, mod, class_name, "__new__", tmp, new_arg_vals, new_arg_count, trans, diag_node_ct, is_volatile) new_ptr: mlirlite.Value* = self.Gen.CallGenCall._call_method_on_ptr(pool, builder, mod, class_name, "__new__", tmp, new_arg_vals, new_arg_count, trans, diag_node_ct, is_volatile, cargs)
if new_ptr is not None: if new_ptr is not None:
storage_ptr = new_ptr storage_ptr = new_ptr
# __new__ declares `-> t.CPtr` (i8*) but the class type is Ptr(struct_ty). # __new__ declares `-> t.CPtr` (i8*) but the class type is Ptr(struct_ty).
@@ -429,7 +429,7 @@ novirtual class CallGenCtor(CallGenBase.CallGenBase):
init_arg_count += 1 init_arg_count += 1
# call __init__(storage_ptr, *args) # call __init__(storage_ptr, *args)
self.Gen.CallGenCall._call_method_on_ptr(pool, builder, mod, class_name, "__init__", storage_ptr, init_arg_vals, init_arg_count, trans, diag_node_ct, is_volatile) self.Gen.CallGenCall._call_method_on_ptr(pool, builder, mod, class_name, "__init__", storage_ptr, init_arg_vals, init_arg_count, trans, diag_node_ct, is_volatile, cargs)
# return the pointer # return the pointer
return storage_ptr return storage_ptr
@@ -850,13 +850,19 @@ novirtual class CallGenCtor(CallGenBase.CallGenBase):
# volatile prefix: `a: T = volatile list[int](mb)` -- the ctor writes are volatile # volatile prefix: `a: T = volatile list[int](mb)` -- the ctor writes are volatile
gc_volatile: int = cl.IsVolatile gc_volatile: int = cl.IsVolatile
# AST nodes matching extra_args (positional slots) for the value-reference (&T / &temp T)
# auto-address inside _call_method_on_ptr. With keyword args the slot mapping needs the
# __init__ param table, so it is skipped there (temp copies still work; a ref lvalue is skipped).
gc_arg_nodes: list[ast.AST*]* = cargs
if kw_count > 0: gc_arg_nodes = None
# Storage pointer: alloca by default, or __new__'s return when present (__new__ is # Storage pointer: alloca by default, or __new__'s return when present (__new__ is
# optional; matches _translate_struct_ctor to avoid over-declaring for classes without it). # optional; matches _translate_struct_ctor to avoid over-declaring for classes without it).
storage_ptr: mlirlite.Value* = tmp storage_ptr: mlirlite.Value* = tmp
has_new_flag: int = 0 has_new_flag: int = 0
if struct_entry is not None: has_new_flag = struct_entry.HasNew if struct_entry is not None: has_new_flag = struct_entry.HasNew
if has_new_flag != 0: if has_new_flag != 0:
new_ptr: mlirlite.Value* = self.Gen.CallGenCall._call_method_on_ptr(pool, builder, mod, spec_name, "__new__", tmp, extra_args, arg_count, trans, None, gc_volatile) new_ptr: mlirlite.Value* = self.Gen.CallGenCall._call_method_on_ptr(pool, builder, mod, spec_name, "__new__", tmp, extra_args, arg_count, trans, None, gc_volatile, gc_arg_nodes)
if new_ptr is not None: if new_ptr is not None:
storage_ptr = new_ptr storage_ptr = new_ptr
# __new__ declares `-> t.CPtr` (i8*) but the class type is Ptr(struct_ty). # __new__ declares `-> t.CPtr` (i8*) but the class type is Ptr(struct_ty).
@@ -871,6 +877,6 @@ novirtual class CallGenCtor(CallGenBase.CallGenBase):
self.Gen.CallGenCall._call_method_on_ptr(pool, builder, mod, spec_name, "__before_init__", storage_ptr, None, 0, trans, None, gc_volatile) self.Gen.CallGenCall._call_method_on_ptr(pool, builder, mod, spec_name, "__before_init__", storage_ptr, None, 0, trans, None, gc_volatile)
# call __init__(storage_ptr, *args) (arg_count = slots 0..highest filled) # call __init__(storage_ptr, *args) (arg_count = slots 0..highest filled)
self.Gen.CallGenCall._call_method_on_ptr(pool, builder, mod, spec_name, "__init__", storage_ptr, extra_args, arg_count, trans, None, gc_volatile) self.Gen.CallGenCall._call_method_on_ptr(pool, builder, mod, spec_name, "__init__", storage_ptr, extra_args, arg_count, trans, None, gc_volatile, gc_arg_nodes)
return storage_ptr return storage_ptr
+3
View File
@@ -124,6 +124,9 @@ novirtual class CallGenFd(CallGenBase.CallGenBase):
if fd_exact is None: fd_exact = self._LookupMethodRetTyFromTextLL(pool, fd_sha1, fd_cls, method_name) if fd_exact is None: fd_exact = self._LookupMethodRetTyFromTextLL(pool, fd_sha1, fd_cls, method_name)
if fd_exact is not None: fd_ret_ty = fd_exact if fd_exact is not None: fd_ret_ty = fd_exact
fd_total: int = 1 + fd_can fd_total: int = 1 + fd_can
# Value-reference (&T / &temp T) params of the resolved extension method: auto-address the
# user args (the shadow self pointer occupies slot 0).
fd_head = self._apply_auto_addr_to_call_args(builder, pool, mod, trans, fd_fname, fd_args, fd_head, fd_total, 1)
return mlirlite.build_call(builder, fd_fname, fd_head, fd_total, fd_ret_ty, 0) return mlirlite.build_call(builder, fd_fname, fd_head, fd_total, fd_ret_ty, 0)
# _inject_requires_fields - FakeDuck: inject requires fields from the with context. # _inject_requires_fields - FakeDuck: inject requires fields from the with context.
# For each class __requires__ field (registered by fd1), call _find_provider(field) at runtime; if non-NULL bitcast and store it (conditional inject); skip when _find_provider is unregistered. # For each class __requires__ field (registered by fd1), call _find_provider(field) at runtime; if non-NULL bitcast and store it (conditional inject); skip when _find_provider is unregistered.
+5 -3
View File
@@ -209,9 +209,11 @@ novirtual class CallGenLib(CallGenBase.CallGenBase):
arg_node: ast.AST* = None arg_node: ast.AST* = None
if cargs is not None and arg_idx >= 0 and arg_idx < len(cargs): arg_node = cargs.get(arg_idx) if cargs is not None and arg_idx >= 0 and arg_idx < len(cargs): arg_node = cargs.get(arg_idx)
# Value-reference params (&T / &temp T): auto-address the argument (cross-module callee). # Value-reference params (&T / &temp T): auto-address the argument (cross-module callee).
# The mode comes from the definition-side vpi annotation segment; if the arg is not a # The mode comes from the definition-side vpi annotation segment (shared resolver); if the
# pointer it is addressed (ref: lvalue) or copied into a temp slot (temp). # arg is not a pointer it is addressed (ref: lvalue) or copied into a temp slot (temp).
mode_cm: int = VpiReader._vpi_param_annot_seg_mode(prm_annots, idx) vpi_cur_sha: str = None
if trans is not None: vpi_cur_sha = trans.ModuleSha1
mode_cm: int = self.Gen.CallGenBase._vpi_auto_addr_mode(call_name, vpi_cur_sha, idx)
if mode_cm != 0 and trans is not None and trans.Module is not None: if mode_cm != 0 and trans is not None and trans.Module is not None:
node_v = self.Gen.CallGenBase._apply_auto_addr_arg(builder, pool, trans.Module, trans, mode_cm, arg_node, node_v) node_v = self.Gen.CallGenBase._apply_auto_addr_arg(builder, pool, trans.Module, trans, mode_cm, arg_node, node_v)
# "str / bytes are not generic pointer wildcards": void* / t.CPtr / rawptr may # "str / bytes are not generic pointer wildcards": void* / t.CPtr / rawptr may
+21 -7
View File
@@ -23,7 +23,7 @@ novirtual class CallGenProp(CallGenBase.CallGenBase):
# _translate_property_call - translate a property/static method 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. # 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*) -> mlirlite.Value*: 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""" """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 if pool is None or builder is None or class_name is None or method_name is None: return None
@@ -74,6 +74,10 @@ novirtual class CallGenProp(CallGenBase.CallGenBase):
if cls_sha1_rt is None or self._dep_text_ll_exists(pool, cls_sha1_rt) == 0: call_ret_ty = mlirlite.Int32(pool) 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) else: call_ret_ty = self._fail_method_ret_ty(method_name)
# 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, # 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. # else the mangled module declare. Otherwise call(i32) vs declare(i64) mismatches and llc errors.
tgt_func_pc: mlirlite.Function* = found_func tgt_func_pc: mlirlite.Function* = found_func
@@ -135,13 +139,13 @@ novirtual class CallGenProp(CallGenBase.CallGenBase):
if class_name is None: return None if class_name is None: return None
meta: int = self.Gen.ClassGenBase.lookup_method_meta(class_name, at_node.attr) meta: int = self.Gen.ClassGenBase.lookup_method_meta(class_name, at_node.attr)
if meta & GenTypes.META_PROP_GETTER == 0: return None if meta & GenTypes.META_PROP_GETTER == 0: return None
# Single-arg call: obj -> getter # Single-arg call: obj -> getter (no user args to auto-address; obj is slot 0)
mlirlite.value_set_next(obj_ptr, None) mlirlite.value_set_next(obj_ptr, None)
return self._translate_property_call(pool, builder, mod, class_name, at_node.attr, obj_ptr, 1, trans) 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. # _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. # 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*) -> int: 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)""" """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 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 if target_attr is None or target_attr.attr is None or rhs_val is None: return 0
@@ -160,7 +164,13 @@ novirtual class CallGenProp(CallGenBase.CallGenBase):
setter_name_buf: str = pool.alloc(128) setter_name_buf: str = pool.alloc(128)
if setter_name_buf is None: return 0 if setter_name_buf is None: return 0
viperlib.snprintf(setter_name_buf, 128, "%s$set", target_attr.attr) viperlib.snprintf(setter_name_buf, 128, "%s$set", target_attr.attr)
self._translate_property_call(pool, builder, mod, class_name, setter_name_buf, obj_val, 2, trans) # 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 return 1
# _try_property_deleter - try to translate del obj.attr into a property deleter call. # _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. # If the class has an @x.deleter for attr, emit call Class.attr$del(obj) and return 1; else 0.
@@ -181,7 +191,7 @@ novirtual class CallGenProp(CallGenBase.CallGenBase):
del_name_buf: str = pool.alloc(128) del_name_buf: str = pool.alloc(128)
if del_name_buf is None: return 0 if del_name_buf is None: return 0
viperlib.snprintf(del_name_buf, 128, "%s$del", target_attr.attr) 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) self._translate_property_call(pool, builder, mod, class_name, del_name_buf, obj_val, 1, trans, None, 1)
return 1 return 1
# _translate_static_class_call - translate ClassName.method(args) for static/class methods. # _translate_static_class_call - translate ClassName.method(args) for static/class methods.
# @staticmethod: call Class.method(args...). @classmethod: prepend a stack-allocated class pointer. # @staticmethod: call Class.method(args...). @classmethod: prepend a stack-allocated class pointer.
@@ -232,7 +242,11 @@ novirtual class CallGenProp(CallGenBase.CallGenBase):
# Zero-arg @staticmethod (e.g. PyListObj.new()): head=None is a valid no-arg call, so still route # 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. # through _translate_property_call; returning None would fall back to bare-name lookup and fail.
return self._translate_property_call(pool, builder, mod, class_name, method_name, head, total_count, trans) # 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. # _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. # list[T] methods are inlined by translate_call's list branch, not registered as functions.