fix(gen): keep &T auto-address correct when T is itself a pointer
`f(a: &T)` lowers to `T*` and takes the argument's address at the call site. The old rule passed any pointer argument through unchanged, so `bubble_sort(nums)` with `arr: &list[int]*` (T = list[int]*) sent `list[int]*` where `list[int]**` was declared and failed with a pointer level 1 -> 2 CastError. - _apply_auto_addr_arg compares the argument's structural pointer depth with the parameter type instead of "is a pointer". For a Name whose own alloca already has the parameter depth, the alloca is passed directly: the `&obj` fold on an object-pointer variable drops one level, which is wrong for `&T` with a pointer T. - translate_call_arg falls back to the definition-side vpi annotation when the same-module table misses, so a cross-module callee that has not been body-translated yet still gets its &T modes. - _coerce_args_to_def_params uses the injected declare's parameter type when the vpi text cannot resolve it (e.g. a generic class name), and passes that type to the auto-address transform. - a null literal passed to `&T` keeps passing through (no lvalue). Probe: `bubble_sort(nums)` sorts through `&list[int]*`; `bump(x)` and `bump(p)` with `p: int*` both increment x (value addressed, pointer passed through). Self-host 110/110; GenericTest3, ListTest, PtrOpProbe pass; EscapeTest still reports its expected LifetimeError.
This commit is contained in:
1 parent
f57a7d962c
commit
94d8c0d151
2 files changed
+41
-4
No files matched your search
@@ -153,26 +153,51 @@ novirtual class CallGenBase:
|
||||
# 临时槽再传地址,被调者改的是副本(跨模块按 vpi 形参注解判定,见 helper)。
|
||||
self._fail_on_value_arg_to_ptr_param(trans, callee_name, arg_idx, arg_node, fpty)
|
||||
mode: int = self._auto_addr_mode(callee_name, arg_idx)
|
||||
if mode == 0 and trans is not None:
|
||||
# Cross-module callee whose definition has not been translated yet in this
|
||||
# process: the definition-side vpi annotation still carries &T / &temp T.
|
||||
mode = self._vpi_auto_addr_mode(callee_name, trans.ModuleSha1, arg_idx)
|
||||
if mode == 0:
|
||||
return self.Gen.ExprGen.translate_value(builder, pool, mod, arg_node, None, 0, trans, fpty, use_ctx)
|
||||
# Value-reference param: translate without the pointer target hint (a char/str literal must
|
||||
# not be turned into a string), then apply the auto-address transform.
|
||||
av: mlirlite.Value* = self.Gen.ExprGen.translate_value(builder, pool, mod, arg_node, None, 0, trans, None, use_ctx)
|
||||
return self._apply_auto_addr_arg(builder, pool, mod, trans, mode, arg_node, av)
|
||||
return self._apply_auto_addr_arg(builder, pool, mod, trans, mode, arg_node, av, fpty)
|
||||
|
||||
# _apply_auto_addr_arg - ref: take the lvalue address; temp: copy into a fresh entry-block slot.
|
||||
def _apply_auto_addr_arg(self, builder: mlirlite.IRBuilder*, pool: memhub.MemBuddy*, mod: mlirlite.LLVMModule*,
|
||||
trans: HT.Translator*, mode: int, node: ast.AST*, av: mlirlite.Value*) -> mlirlite.Value*:
|
||||
trans: HT.Translator*, mode: int, node: ast.AST*, av: mlirlite.Value*,
|
||||
fpty: mlirlite.LLVMType* = None) -> mlirlite.Value*:
|
||||
"""Apply a value-reference mode to a translated argument value."""
|
||||
if av is None: return None
|
||||
if self.Gen.ExprGenBase.is_ptr_type(av.Ty) != 0: return av # already a pointer: pass through
|
||||
# `&T` lowers to `T*`: an argument already at the parameter's pointer depth IS the
|
||||
# reference (passing `p: int*` to `&int`). Compare structural pointer depth, not type
|
||||
# text: opaque pointers print identically (`!llvm.ptr`), so text equality cannot tell
|
||||
# `list[int]*` from `list[int]**` and used to pass `nums` through unaddressed.
|
||||
if fpty is not None:
|
||||
if self.Gen.ExprGenBase.get_ptr_level(av.Ty) == self.Gen.ExprGenBase.get_ptr_level(fpty): return av
|
||||
else:
|
||||
if self.Gen.ExprGenBase.is_ptr_type(av.Ty) != 0: return av # already a pointer: pass through
|
||||
if mode == 1:
|
||||
if node is None: return av
|
||||
nk: int = node.kind()
|
||||
# `&obj` on an object-pointer variable normally folds to the object pointer
|
||||
# (one level less). When the callee expects exactly the variable's storage
|
||||
# address (`&T` with T itself a pointer, e.g. `&list[int]*` -> `list[int]**`),
|
||||
# use the alloca directly so the address-of fold does not drop a level.
|
||||
if nk == ast.ASTKind.Name and trans is not None and fpty is not None:
|
||||
nm_a: ast.Name* = ast.Name*(node)
|
||||
if nm_a.id is not None:
|
||||
v_a: mlirlite.Value* = self.Gen.VarGen.lookup_var(trans.SymTab, nm_a.id)
|
||||
if v_a is not None and self.Gen.ExprGenBase.get_ptr_level(v_a.Ty) == self.Gen.ExprGenBase.get_ptr_level(fpty): return v_a
|
||||
if nk == ast.ASTKind.Name or nk == ast.ASTKind.Attribute or nk == ast.ASTKind.Subscript:
|
||||
ao_node: ast.AST* = ast.UnaryOp(pool, ast.OpKind.AddrOf, node)
|
||||
if ao_node is None: return av
|
||||
return self.Gen.ExprGen.translate_value(builder, pool, mod, ao_node, None, 0, trans, None, None)
|
||||
# Null literal: there is no lvalue to address; pass the null value through.
|
||||
if nk == ast.ASTKind.Constant:
|
||||
cst: ast.Constant* = ast.Constant*(node)
|
||||
if cst is not None and cst.const_kind == ast.CONST_NONE: return av
|
||||
self.Gen.TypeGenErr.fatal_error(node, "value-reference parameter '&T' needs an lvalue argument (variable / field / element) or an existing pointer; a literal or expression result has no address (use '&temp T' to pass a temporary copy)", "CastError")
|
||||
return av
|
||||
if mode == 2:
|
||||
|
||||
@@ -198,6 +198,18 @@ novirtual class CallGenLib(CallGenBase.CallGenBase):
|
||||
tgt_ty: mlirlite.LLVMType* = None
|
||||
if tpl_src != 0: tgt_ty = VpiReader._resolve_vpi_param_annot_seg_template(pool, sha1_buf, cls_nm, meth_nm, idx, i32_ty, i8_ptr_ty)
|
||||
else: tgt_ty = VpiReader._resolve_vpi_param_annot_seg(pool, sha1_buf, cls_nm, meth_nm, idx, i32_ty, i8_ptr_ty)
|
||||
if tgt_ty is None and trans is not None and trans.Module is not None:
|
||||
# vpi text could not be resolved in this file's context (e.g. a generic
|
||||
# class name). The injected declare was built with the caller's context,
|
||||
# so its parameter type is the authoritative ABI type.
|
||||
dcl: mlirlite.Function* = self.Gen.CallGenBase.find_func_in_module(trans.Module, call_name)
|
||||
if dcl is not None:
|
||||
p_d: mlirlite.Param* = mlirlite.function_get_param_head(dcl)
|
||||
pi_d: int = 0
|
||||
while p_d is not None and pi_d < idx:
|
||||
p_d = mlirlite.param_get_next(p_d)
|
||||
pi_d += 1
|
||||
if p_d is not None: tgt_ty = mlirlite.param_get_ty(p_d)
|
||||
if tgt_ty is not None and cur.Ty is not None:
|
||||
# Argument AST (for source-symbolic checks): AST index = chain index idx - self offset
|
||||
arg_idx: int = idx - self_off
|
||||
@@ -210,7 +222,7 @@ novirtual class CallGenLib(CallGenBase.CallGenBase):
|
||||
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:
|
||||
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, tgt_ty)
|
||||
# "str / bytes are not generic pointer wildcards": void* / rawptr may
|
||||
# convert to any pointer, but str/bytes may not, even though both are i8* at LLVM level.
|
||||
if self.Gen.ExprGenBase._str_rejects_ptr_kind(cur) != 0:
|
||||
|
||||
Reference in new issue
Block a user