fix(gen): OOP __len__/__getitem__/methods beat pointer arithmetic
A pointer chain that reaches a class is a reference to the object, not an array of objects. `arr: &list[int]*` (inside `arr` is `list[int]**`) used to be treated as raw pointer arithmetic: `len(arr)` read the pointer slot, `arr[i]` indexed it, and `arr.append(x)` passed the slot as self -- all producing garbage or a runtime crash. - add `_oop_self_ptr`: peel extra pointer levels down to Ptr(Struct); - `len()` normalizes the receiver after resolving the class, so list inline `__count__` and class `__len__` receive the object pointer; - subscript reads dispatch list / class `__getitem__` with the peeled receiver; raw pointer traversal remains the fallback when the chain does not reach a class (or the class has no `__getitem__`); - the list builtin method branch and `_translate_method_call` peel the receiver, so `arr.append(x)` works through a reference; - `is_list_subscript` unwraps every level for the assignment path, so `arr[i] = v` and tuple swaps on elements write the real list; value-type list variables keep their old path. Probe: `&list[int]*` len/read/write/swap/append and `&Box*` custom `__len__`/`__getitem__` all produce the expected values; self-host 110/110; ListTest, GenericTest3, PtrOpProbe and HashLibTest pass.
This commit is contained in:
1 parent
83d309509b
commit
49683fc881
4 files changed
+75
-18
No files matched your search
@@ -646,6 +646,10 @@ novirtual class CallGenCall(CallGenBase.CallGenBase):
|
||||
"""Translate a method call; returns 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 obj_ptr is None or cl is None: return None
|
||||
# A pointer chain that reaches the class is a reference to the object: peel the extra
|
||||
# levels so `arr: &list[int]*` calls `list.append` on the list, not on the pointer slot.
|
||||
obj_ptr = self.Gen.StructGen._oop_self_ptr(builder, pool, obj_ptr)
|
||||
if obj_ptr is None: return None
|
||||
|
||||
# 调用点生命期检查(M2.3 覆盖面扩展):方法摘要键为 "Class.method"(含 MRO 回退)。
|
||||
# 摘要的形参下标含 self(bit 0),故用户第 ai 个实参对应 bit (ai+1)。语义与模块级
|
||||
|
||||
@@ -699,13 +699,17 @@ novirtual class CallGenDispatch(CallGenBase.CallGenBase):
|
||||
if len_struct_ty is not None:
|
||||
len_cls_nm: char* = 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")
|
||||
# A pointer chain that reaches the class is a reference to the object:
|
||||
# peel the extra levels so `len(&list[int]*)` reads the list, not the slot.
|
||||
len_obj: mlirlite.Value* = self.Gen.StructGen._oop_self_ptr(builder, pool, len_val)
|
||||
if len_obj is None: return None
|
||||
# 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:
|
||||
if self.Gen.CallGenProp._is_list_builtin_receiver(pool, len_obj, 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)
|
||||
cnt_ptr_lb: mlirlite.Value* = mlirlite.build_gep(builder, i64_ty_lb, len_obj, idx_one_lb)
|
||||
if cnt_ptr_lb is not None: return mlirlite.build_load(builder, i64_ty_lb, cnt_ptr_lb)
|
||||
len_lookup: char* = pool.alloc(128)
|
||||
if len_lookup is not None:
|
||||
@@ -716,11 +720,11 @@ novirtual class CallGenDispatch(CallGenBase.CallGenBase):
|
||||
len_fn_name2: char* = 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_self_arg2: mlirlite.Value* = len_obj
|
||||
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)
|
||||
if len_self_ty2 is not None and len_obj.Ty is not None: len_self_arg2 = mlirlite.build_bitcast(builder, len_obj, 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__
|
||||
@@ -737,8 +741,8 @@ novirtual class CallGenDispatch(CallGenBase.CallGenBase):
|
||||
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_obj is the peeled object pointer; use the value chain as param_head.
|
||||
len_self_x: mlirlite.Value* = len_obj
|
||||
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)
|
||||
@@ -1294,6 +1298,10 @@ novirtual class CallGenDispatch(CallGenBase.CallGenBase):
|
||||
# [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
|
||||
# A pointer chain that reaches the list is a reference to the list object:
|
||||
# peel the extra levels before reading __data__/__count__ (&list[T]* -> list[T]*).
|
||||
lm_obj = self.Gen.StructGen._oop_self_ptr(builder, pool, lm_obj)
|
||||
if lm_obj is None: 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
|
||||
|
||||
@@ -460,26 +460,24 @@ novirtual class ExprGenName(ExprGenBase.ExprGenBase):
|
||||
if self.is_ptr_type(alloca.Ty) == 0: return None
|
||||
pointee: mlirlite.LLVMType* = alloca.Ty.Pointee
|
||||
if pointee is None: return None
|
||||
# Check whether pointee is Ptr(Struct)
|
||||
inner_ty: mlirlite.LLVMType* = None
|
||||
match pointee:
|
||||
case mlirlite.LLVMType.Ptr(it):
|
||||
inner_ty = it
|
||||
case _:
|
||||
return None
|
||||
if inner_ty is None: return None
|
||||
# Check whether inner_ty is a Struct whose name contains "list["
|
||||
if self.is_ptr_type(pointee) == 0: return None # value-type list: handled by get_subscript_ptr
|
||||
# Unwrap every pointer level: a reference to a list pointer (`&list[T]*`) is still
|
||||
# the list object for __setitem__, not an array of list pointers.
|
||||
st_ty: mlirlite.LLVMType* = self.Gen.StructGen.get_struct_type_from_value(alloca)
|
||||
if st_ty is None: return None
|
||||
struct_name: str = None
|
||||
match inner_ty:
|
||||
match st_ty:
|
||||
case mlirlite.LLVMType.Struct(_, _, sname):
|
||||
struct_name = sname
|
||||
case _:
|
||||
return None
|
||||
if struct_name is None: return None
|
||||
if string.strstr(struct_name, "list[") is None: return None
|
||||
# Is a list type: load out the list object pointer
|
||||
# Is a list type: load out the list object pointer (peel reference levels).
|
||||
builder: mlirlite.IRBuilder* = trans._cur_builder
|
||||
return mlirlite.build_load(builder, pointee, alloca)
|
||||
obj: mlirlite.Value* = mlirlite.build_load(builder, pointee, alloca)
|
||||
if obj is None: return None
|
||||
return self.Gen.StructGen._oop_self_ptr(builder, trans.Pool, obj)
|
||||
# _nonlocal_subscript_ptr - element pointer for a nonlocal array/pointer subscript: read the
|
||||
# shared heap address from env (_load_raw_var_ptr), bitcast, then GEP (the outer alloca is unusable).
|
||||
def _nonlocal_subscript_ptr(self, builder: mlirlite.IRBuilder*, pool: memhub.MemBuddy*, trans: HT.Translator*, nm_id: str, idx_val: mlirlite.Value*) -> mlirlite.Value*:
|
||||
@@ -642,6 +640,34 @@ novirtual class ExprGenName(ExprGenBase.ExprGenBase):
|
||||
# inner_ty being Ptr means alloca.Ty is a triple pointer (e.g. T=AST*, T*=AST**),
|
||||
# so [i] is a pointer dereference (stride 8), not a struct __getitem__ call.
|
||||
if self.is_ptr_type(inner_ty) != 0:
|
||||
# OOP priority over pointer arithmetic: a pointer chain that
|
||||
# reaches a class dispatches the class __getitem__ (peeling the
|
||||
# reference levels), no matter how deep the chain is.
|
||||
oop_st: mlirlite.LLVMType* = self.Gen.StructGen.get_struct_type_from_value(alloca)
|
||||
if oop_st is not None:
|
||||
oop_cls: str = self.Gen.StructGen.get_class_name_by_type(pool, oop_st)
|
||||
if oop_cls is not None:
|
||||
oop_obj: mlirlite.Value* = mlirlite.build_load(builder, pointee, alloca)
|
||||
if oop_obj is not None: oop_obj = self.Gen.StructGen._oop_self_ptr(builder, pool, oop_obj)
|
||||
if oop_obj is not None:
|
||||
if string.strstr(oop_cls, "list[") is not None:
|
||||
oop_addr: mlirlite.Value* = self.list_getitem_inline(builder, pool, oop_obj, idx_val, node)
|
||||
if oop_addr is not None:
|
||||
oop_elem_ty: mlirlite.LLVMType* = self._list_elem_type_from_name(pool, oop_cls)
|
||||
if oop_elem_ty is not None:
|
||||
oop_ptr_ty: mlirlite.LLVMType* = mlirlite.Ptr(pool, oop_elem_ty)
|
||||
oop_cast: mlirlite.Value* = mlirlite.build_bitcast(builder, oop_addr, oop_ptr_ty)
|
||||
if oop_cast is not None: return mlirlite.build_load(builder, oop_elem_ty, oop_cast)
|
||||
return None
|
||||
gi_lookup_oop: str = pool.alloc(128)
|
||||
if gi_lookup_oop is not None:
|
||||
viperlib.snprintf(gi_lookup_oop, 128, "%s.__getitem__", oop_cls)
|
||||
if self.Gen.CallGenBase.find_func_in_module(mod, gi_lookup_oop) is not None:
|
||||
oop_args: sizet* = pool.alloc(8)
|
||||
if oop_args is not None:
|
||||
oop_args[0] = sizet(idx_val)
|
||||
oop_ret: mlirlite.Value* = self.Gen.CallGenCall._call_method_on_ptr(pool, builder, mod, oop_cls, "__getitem__", oop_obj, oop_args, 1, trans, sub)
|
||||
if oop_ret is not None: return oop_ret
|
||||
ptr_val_pp: mlirlite.Value* = mlirlite.build_load(builder, pointee, alloca)
|
||||
if ptr_val_pp is not None:
|
||||
elem_ptr_pp: mlirlite.Value* = mlirlite.build_gep(builder, inner_ty, ptr_val_pp, idx_val)
|
||||
|
||||
@@ -3097,6 +3097,25 @@ novirtual class StructGenMixin:
|
||||
ty = nxt_ty
|
||||
return None
|
||||
|
||||
# _oop_self_ptr - reduce Ptr^k(Struct) to the Ptr(Struct) self pointer.
|
||||
# A pointer chain that reaches a class is a reference to the object, not an array
|
||||
# of objects: OOP dispatch (len / []) peels every extra level instead of treating
|
||||
# the chain as raw pointer arithmetic.
|
||||
def _oop_self_ptr(self, builder: mlirlite.IRBuilder*, pool: memhub.MemBuddy*, val: mlirlite.Value*) -> mlirlite.Value*:
|
||||
"""Strip extra pointer levels so the value is the Ptr(Struct) object pointer."""
|
||||
if val is None or val.Ty is None: return val
|
||||
cur: mlirlite.Value* = val
|
||||
cur_ty: mlirlite.LLVMType* = val.Ty
|
||||
while cur_ty is not None:
|
||||
if self.Gen.ExprGenBase.is_ptr_type(cur_ty) == 0: break
|
||||
pt: mlirlite.LLVMType* = cur_ty.Pointee
|
||||
if pt is None: break
|
||||
if self.Gen.ExprGenBase.is_ptr_type(pt) == 0: break
|
||||
cur = mlirlite.build_load(builder, pt, cur)
|
||||
if cur is None: return None
|
||||
cur_ty = cur.Ty
|
||||
return cur
|
||||
|
||||
def mark_as_union(self, name: str) -> int:
|
||||
"""Mark a registered struct as a union; returns 1 on success, 0 on failure."""
|
||||
if name is None: return 0
|
||||
|
||||
Reference in new issue
Block a user