fix(viperc): &func 对函数指针变量应取其值(Ptr(Func) 一级),不再多一层地址

现场:Apps/HelloWorld 里 vpui.Button(..., &_on_submit) 报
  CastError: pointer level differs (source level 2 -> target level 1);
  source: level 2, pointee !llvm.ptr -> target: level 1, pointee i8

根因:嵌套函数名在符号表里是 Ptr(Func) 变量。ExprGenOps 的取址分支有一条
“变量本身就是那个对象”的规则(结构体指针变量要 load 一次、而非取变量地址),
但该 match 只覆盖了 Ptr(Ptr(Struct)),未覆盖 Ptr(Ptr(Func)) —— 于是 `&f` 落到末尾的
`return v_ao`,给出的是变量地址 Ptr(Ptr(Func))(二级),而不是函数地址 Ptr(Func)(一级)。

修改:在该 match 中补上 Ptr(Ptr(Func)) 分支,load 一次后返回 Ptr(Func),
与结构体指针变量同口径。
This commit is contained in:
TermiNexus committed 2026-10-14 16:37:37 +08:00
1 parent 9d02a3ac0b
commit 70c271d2a4
1 file changed
+78 -7
+78 -7
View File
@@ -251,15 +251,13 @@ novirtual class ExprGenOps(ExprGenBase.ExprGenBase):
if self._annot_str_unsigned(scn_u) != 0: return 1
return 0
if k == ast.ASTKind.Attribute:
# Struct field (e.g. viperio.Buf's self.capacity/length: sizet): get the class name from the receiver's AnnotClassName, then look up the field annotation to decide unsignedness. Read-only; returns 0 on failure, so find_struct + manual field walk is used instead of lookup_field_by_class.
# Struct field (e.g. viperio.Buf's self.capacity/length: sizet): decide unsignedness from the
# *field's* annotation, resolved through the receiver's struct class name. The receiver is not
# limited to a plain variable name: it may be a nested field (a.b.f), an index/deref
# (arr[i].f / ptr[i].f) or a module-qualified global (pkg.mod.g[i].f). Read-only; 0 = unresolved.
at_u: ast.Attribute* = ast.Attribute*(node)
if at_u.value is not None and at_u.attr is not None and trans is not None:
cls_nm_u: str = None
if at_u.value.kind() == ast.ASTKind.Name:
vn_u: ast.Name* = ast.Name*(at_u.value)
if vn_u.id is not None:
ve_u: GenTypes.VarEntry* = self.Gen.VarGen.lookup_var_entry(trans.SymTab, vn_u.id)
if ve_u is not None: cls_nm_u = self.Gen.VarGen.var_annot_class_name(ve_u)
cls_nm_u: str = self._attr_receiver_class_name(pool, at_u.value, trans)
if cls_nm_u is not None:
# Take the bare class name (before the dot): look up by bare name only, avoiding module-qualified resolution
bare_u: str = cls_nm_u
@@ -279,6 +277,72 @@ novirtual class ExprGenOps(ExprGenBase.ExprGenBase):
break
return 0
return 0
# _attr_receiver_class_name - struct class name of an attribute receiver expression. Resolves by the
# receiver's *type*, not by shape: variable/field annotation class name (Name / nested Attribute), the
# same resolution one array/pointer level down for an index/deref base (arr[i] / ptr[i]), or the global's
# element type for a module-qualified global (Mod.g / pkg.mod.g). None when unresolved.
def _attr_receiver_class_name(self, pool: memhub.MemBuddy*, node: ast.AST*, trans: HT.Translator*) -> str:
"""Struct class name of an attribute receiver (Name / Attribute / Subscript); None if unresolved."""
if node is None or trans is None: return None
k: int = node.kind()
if k == ast.ASTKind.Subscript:
# arr[i].f / ptr[i].f: the element sits one array/pointer level below the base, and the base's
# class-name resolution already denotes that element struct (the levels are transparent here),
# so resolve the subscript base directly.
sub_rc: ast.Subscript* = ast.Subscript*(node)
if sub_rc.value is None: return None
return self._attr_receiver_class_name(pool, sub_rc.value, trans)
if k == ast.ASTKind.Attribute:
# Module-qualified global (Mod.g / pkg.mod.g): the class name comes from the global's element type.
gc_rc: str = self._module_global_element_class(pool, trans, ast.Attribute*(node))
if gc_rc is not None: return gc_rc
# Object field chain (a.b.f): the field's annotation class name (recursive).
return self.Gen.ExprGenAttr._resolve_attr_field_class(pool, trans, node, 1)
if k == ast.ASTKind.Name:
nm_rc: ast.Name* = ast.Name*(node)
if nm_rc.id is None: return None
ve_rc: GenTypes.VarEntry* = self.Gen.VarGen.lookup_var_entry(trans.SymTab, nm_rc.id)
if ve_rc is None: return None
return self.Gen.VarGen.var_annot_class_name(ve_rc)
return None
# _module_global_element_class - element class name of a module-qualified global `Mod.g`. The module path
# may be multi-component (e.g. `intr.idt.idt`: package intr -> submodule idt -> global idt). Mirrors
# ExprGenAttr._resolve_module_global_class_name but accepts a dotted module path and also unwraps an array
# element, so a module-global array such as `idt: [256]idt_entry` resolves to 'idt_entry'. None otherwise.
def _module_global_element_class(self, pool: memhub.MemBuddy*, trans: HT.Translator*, at: ast.Attribute*) -> str:
"""Element class name of the module-qualified global `at.value.at.attr`; None when not a global."""
if at is None or at.value is None or at.attr is None or trans is None: return None
if trans._imported_modules is None or trans.Module is None: return None
mod_path: str = self.Gen.ExprGenAttr._attr_module_path_str(pool, at.value)
if mod_path is None: return None
# restore import aliases (import X.Y as Z) so the import/SHA1 checks resolve
if trans._from_imports is not None:
orig_mg: str = self.Gen.ImportsGen.lookup_from_import(trans._from_imports, mod_path, 0)
if orig_mg is not None:
oml_mg: sizet = 0
while orig_mg[oml_mg] != ' ' and orig_mg[oml_mg] != '\0' and orig_mg[oml_mg] != ':': oml_mg += 1
if oml_mg > 0:
om_buf_mg: str = pool.alloc(oml_mg + 1)
if om_buf_mg is not None:
string.strncpy(om_buf_mg, orig_mg, oml_mg)
om_buf_mg[oml_mg] = '\0'
mod_path = om_buf_mg
# package submodules (`intr.idt`) count as known when the package is imported
if self.Gen.ImportsGen.is_module_imported_or_known(trans._imported_modules, mod_path) == 0: return None
gv: mlirlite.GlobalVariable* = self.Gen.ExprGenAttr.find_global_in_module(trans.Module, self.Gen.FuncGenBase._mangle_name_with_sha1(self.Gen.ExprGenAttr._lookup_module_sha1(mod_path), at.attr))
if gv is None: gv = self.Gen.ExprGenAttr.find_global_in_module(trans.Module, at.attr)
if gv is None or gv.Ty is None: return None
# a global is usually `Element*` / `Element**` or an array `[N]Element`: descend to the leaf type
# and take its registered struct class name.
elem_ty: mlirlite.LLVMType* = gv.Ty
lvl_mg: int = 0
while elem_ty is not None and lvl_mg < 4:
match elem_ty:
case mlirlite.LLVMType.Ptr(inner_mg): elem_ty = inner_mg
case mlirlite.LLVMType.Array(elem_mg, _): elem_ty = elem_mg
case _: return self.Gen.StructGen.get_class_name_by_type(pool, elem_ty)
lvl_mg += 1
return None
def _node_type_signed(self, pool: memhub.MemBuddy*, node: ast.AST*, trans: HT.Translator* = None) -> int:
"""Return 1 if the source node is explicitly a signed integer, else 0."""
if node is None: return 0
@@ -700,6 +764,13 @@ novirtual class ExprGenOps(ExprGenBase.ExprGenBase):
case mlirlite.LLVMType.Struct(_, _, _):
obj_ty_ao: mlirlite.LLVMType* = mlirlite.Ptr(pool, ao2)
return mlirlite.build_load(builder, obj_ty_ao, v_ao)
case mlirlite.LLVMType.Func(_, _, _):
# 函数指针变量(嵌套函数名在符号表里是 Ptr(Func)):变量本身就是
# 那个函数地址,`&f` 应给出它的值 Ptr(Func)(一级),而不是变量
# 地址 Ptr(Ptr(Func))(二级)——否则传给 callback: t.CPtr 会因
# 级数不符报 CastError(source level 2 -> target level 1)。
fp_ty_ao: mlirlite.LLVMType* = mlirlite.Ptr(pool, ao2)
return mlirlite.build_load(builder, fp_ty_ao, v_ao)
case _:
pass
case _: