fix(Gen): 联合类型数组下标按 8 字节步长(写路径 + CUInt64T|CPtr)

get_subscript_ptr 写路径 IsPtrElement 分支 GEP base 误用 alloca(栈地址),list append 只增 count 不写 data(dump 虚调用读 0xbaadf00d 崩溃),改为先 load 指针值再 GEP。另扩展 _is_ptr_element_annotation 识别 CUInt64T|CPtr 等 64 位标量联合,避免 GEP 步长 1 + trunc i8 截断指针(BuddyTest OOM/fill ptrs 数组 free 全部无效)。
This commit is contained in:
TermiNexus committed 2026-08-11 01:33:58 +08:00
1 parent 120a2d7ad7
commit 65d7342d0b
2 files changed
+186 -6

No files matched your search

+52
View File
@@ -356,6 +356,58 @@ class AssignGenMixin:
return 1
if self._annotation_contains_name(annot, "bytes") != 0:
return 1
# 泛型类型参数/嵌套联合 | t.CPtr(如 _list.vp 的 elem_ptr: T | t.CPtr):
# T 特化后为具体类型(AST|t.CPtr 等指针),联合在解析时可能退化为 i8*,
# 但元素是 8 字节指针,下标必须按 8 字节步长访问——
# 否则 list.get/append 只读写 1 字节,指针被截断 inttoptr 崩溃。
if self._is_generic_tp_or_nested(bop.left) != 0 or self._is_generic_tp_or_nested(bop.right) != 0:
return 1
# 64 位标量数值类型 | CPtr(如 CUInt64T | CPtr / CSizeT | CPtr):
# 联合解析后同样退化为指针表示,元素为 8 字节(i64 或指针)。
# 下标必须按 8 字节步长读写——否则 GEP 步长 1 + trunc i8 截断,
# 指针存进数组只剩低 1 字节(BuddyTest OOM/fill 的 ptrs 数组全错位:
# store i8 trunc i64、读回 inttoptr i8 丢高 56 位,free 全部无效)。
if self._is_64bit_scalar_ty(bop.left) != 0 or self._is_64bit_scalar_ty(bop.right) != 0:
return 1
return 0
def _is_64bit_scalar_ty(self, node: ast.AST | t.CPtr) -> int:
"""判断类型节点是否为 64 位标量数值类型(CUInt64T/CInt64T/CSizeT),元素 8 字节"""
if node is None:
return 0
ty_nm: str = None
nk: int = node.kind()
if nk == ast.ASTKind.Name:
nm64: ast.Name | t.CPtr = (ast.Name | t.CPtr)(node)
ty_nm = nm64.id
elif nk == ast.ASTKind.Attribute:
at64: ast.Attribute | t.CPtr = (ast.Attribute | t.CPtr)(node)
ty_nm = at64.attr
if ty_nm is None:
return 0
if string.strcmp(ty_nm, "CUInt64T") == 0:
return 1
if string.strcmp(ty_nm, "CInt64T") == 0:
return 1
if string.strcmp(ty_nm, "CSizeT") == 0:
return 1
return 0
def _is_generic_tp_or_nested(self, node: ast.AST | t.CPtr) -> int:
"""判断注解节点是否为泛型类型参数名(单字母大写,如 T)或嵌套联合(BinOp)"""
if node is None:
return 0
nk: int = node.kind()
if nk == ast.ASTKind.BinOp:
return 1
if nk == ast.ASTKind.Name:
nm_tp: ast.Name | t.CPtr = (ast.Name | t.CPtr)(node)
if nm_tp.id is None:
return 0
tp_len: t.CSizeT = string.strlen(nm_tp.id)
if tp_len == 1:
if nm_tp.id[0] >= 65 and nm_tp.id[0] <= 90:
return 1
return 0
# ============================================================
+134 -6
View File
@@ -2291,6 +2291,64 @@ class ExprGenMixin:
pool, llvmlite.Int8(pool))
return llvmlite.build_bitcast(builder, fn_ref_v, fn_i8ptr_ty)
# ============================================================
# 跨模块 from-import 全局变量读取(如 from .tokens import _op_head)
# 根因:模块级全局(非 CDefine/非函数)from-import 后无本模块 SymTab
# 条目,所有查找路径未命中 → translate_name_value 返回 None → 赋值被
# 静默跳过 → 变量未初始化 → 垃圾指针解引用崩溃(AstTest 的
# _read_operator 里 `cur = _op_head` 丢失导致 while 循环读未初始化 %6)。
# 语义对齐模块属性全局(sys._mbuddy):external @{全局名} 引用 + load,
# 定义由源模块提供(COFF 按名链接)。别名导入用源模块内真实名。
# ============================================================
if trans is not None and trans._from_imports is not None:
if trans.Module is not None:
# 是 from-import 名称(禁用 star 回退,避免误归 star 模块)
fi_mod: str = HandlesImports.lookup_from_import(
trans._from_imports, nm_id, 0)
if fi_mod is not None:
# 排除已注册类名/枚举名:这些是类型,不作为全局变量值读取
fi_is_class: int = 0
if HandlesStruct.get_struct_type(nm_id) is not None:
fi_is_class = 1
# 排除 ALL_CAPS CDefine(上方已处理本地+跨模块查找)
if self._is_cdefine_name_pattern(nm_id) != 0:
fi_is_class = 1
if fi_is_class == 0:
fi_gname: str = nm_id
fi_orig: str = HandlesImports.lookup_from_import_original(
trans._from_imports, nm_id)
if fi_orig is not None:
og_len_fi: t.CSizeT = 0
while fi_orig[og_len_fi] != ' ' and fi_orig[og_len_fi] != '\0':
og_len_fi += 1
if og_len_fi > 0:
og_buf_fi: t.CChar | t.CPtr = pool.alloc(og_len_fi + 1)
if og_buf_fi is not None:
string.strncpy(og_buf_fi, fi_orig, og_len_fi)
og_buf_fi[og_len_fi] = '\0'
fi_gname = og_buf_fi
# stub 已注入全局(精确类型)→ 直接引用;未注入 → external 声明
gv_fi: llvmlite.GlobalVariable | t.CPtr = self.find_global_in_module(
trans.Module, fi_gname)
ref_fi: llvmlite.Value | t.CPtr = None
if gv_fi is not None and gv_fi.Ty is not None:
ref_fi = self.make_global_ref(pool, fi_gname, gv_fi.Ty)
else:
i8_ptr_hint_fi: llvmlite.LLVMType | t.CPtr = llvmlite.Ptr(
pool, llvmlite.Int8(pool))
ext_gv_fi: llvmlite.GlobalVariable | t.CPtr = llvmlite.new_global_variable(
pool, fi_gname, i8_ptr_hint_fi)
if ext_gv_fi is not None:
ext_gv_fi.Linkage = "external"
ext_gv_fi.Initializer = None
llvmlite.module_add_global(trans.Module, ext_gv_fi)
ref_fi = self.make_global_ref(pool, fi_gname, i8_ptr_hint_fi)
if ref_fi is not None and ref_fi.Ty is not None:
lty_fi: llvmlite.LLVMType | t.CPtr = ref_fi.Ty.Pointee
if lty_fi is not None:
return llvmlite.build_load(builder, lty_fi, ref_fi)
return ref_fi
# 所有查找路径均失败
# fail-fast: 仅对符合 CDefine 命名约定(ALL_CAPS)的名字报错
# 避免对 match/self/pool 等关键字和变量名误报
@@ -2609,10 +2667,18 @@ class ExprGenMixin:
ve_pe_ts: HandlesVar.VarEntry | t.CPtr = \
HandlesVar.lookup_var_entry(trans.SymTab, nm.id)
if ve_pe_ts is not None and ve_pe_ts.IsPtrElement == 1:
elem_ptr_ts: llvmlite.Value | t.CPtr = llvmlite.build_gep(
builder, pointee, alloca, idx_val)
if elem_ptr_ts is not None:
return llvmlite.build_load(builder, pointee, elem_ptr_ts)
# bytes|t.CPtr / str|t.CPtr / T|t.CPtr:变量存指针值(i8*),
# elem[i] 语义 = load [指针值 + i*8](8 字节指针元素)。
# 必须先 load alloca 得指针值,再 GEP i8*(步长 8)——
# 否则 GEP base 用 alloca(&elem_ptr)读到栈变量而非数据区,
# list.get 返回"元素地址"而非"元素值"(dump 虚调用读错 vtable)。
ptr_val_pe_ts: llvmlite.Value | t.CPtr = llvmlite.build_load(
builder, pointee, alloca)
if ptr_val_pe_ts is not None:
elem_ptr_ts: llvmlite.Value | t.CPtr = llvmlite.build_gep(
builder, pointee, ptr_val_pe_ts, idx_val)
if elem_ptr_ts is not None:
return llvmlite.build_load(builder, pointee, elem_ptr_ts)
return None
# 普通指针遍历: 先 load 指针值,再 GEP
ptr_val: llvmlite.Value | t.CPtr = llvmlite.build_load(
@@ -2624,6 +2690,25 @@ class ExprGenMixin:
return llvmlite.build_load(builder, inner_ty, elem_ptr2)
return None
case _:
# 联合类型变量(IsPtrElement=1,如 _list.vp 的
# elem_ptr: T | t.CPtr 退化为 i8*):pointee=i8 但
# 元素是 8 字节指针,须按 i8* 步长 GEP + load i8*——
# 否则 list.get 只读 1 字节,指针截断 inttoptr 崩溃。
if trans is not None:
ve_pe_rd: HandlesVar.VarEntry | t.CPtr = \
HandlesVar.lookup_var_entry(trans.SymTab, nm.id)
if ve_pe_rd is not None and ve_pe_rd.IsPtrElement == 1:
match pointee:
case llvmlite.LLVMType.Int8():
i8_ptr_rd: llvmlite.LLVMType | t.CPtr = llvmlite.Ptr(
pool, llvmlite.Int8(pool))
elem_ptr_rd: llvmlite.Value | t.CPtr = llvmlite.build_gep(
builder, i8_ptr_rd, alloca, idx_val)
if elem_ptr_rd is not None:
return llvmlite.build_load(
builder, i8_ptr_rd, elem_ptr_rd)
case _:
pass
# 自定义类(结构体类型): 仅当类定义了 __getitem__
# (当前模块定义或跨模块 stub declare 注入)才转发方法调用;
# 否则普通结构体指针按下标做 GEP 数组访问
@@ -3454,8 +3539,25 @@ class ExprGenMixin:
ve_pe: HandlesVar.VarEntry | t.CPtr = \
HandlesVar.lookup_var_entry(trans.SymTab, nm.id)
if ve_pe is not None and ve_pe.IsPtrElement == 1:
return llvmlite.build_gep(
builder, pointee, alloca, idx_val)
# bytes|t.CPtr / str|t.CPtr / T|t.CPtr:变量存指针值,
# 下标语义 = [指针值 + i*8](8 字节指针元素)。必须先 load
# alloca 得指针值,再 GEP——否则 GEP base 用 alloca(&elem_ptr)
# 读到栈变量而非数据区,赋值写不进 list 数据
# (data 全 0xbaadf00d,dump 虚调用读错 vtable 崩溃)。
ptr_val_pe: llvmlite.Value | t.CPtr = llvmlite.build_load(
builder, pointee, alloca)
if ptr_val_pe is not None:
match pointee:
case llvmlite.LLVMType.Int8():
i8_ptr_pe: llvmlite.LLVMType | t.CPtr = llvmlite.Ptr(
pool, llvmlite.Int8(pool))
return llvmlite.build_gep(
builder, i8_ptr_pe, ptr_val_pe, idx_val)
case _:
pass
return llvmlite.build_gep(
builder, pointee, ptr_val_pe, idx_val)
return None
# 普通指针遍历: 先 load 指针值,再 GEP
ptr_val: llvmlite.Value | t.CPtr = llvmlite.build_load(
builder, pointee, alloca)
@@ -3468,6 +3570,32 @@ class ExprGenMixin:
# pointee 是普通标量类型 (如 i64, i32, i8):
# alloca 是 Ptr(标量), 直接 GEP 获取第 idx 个元素指针
# 支持 arg_vals[i] = val 这类参数数组下标赋值
# 联合类型变量(IsPtrElement=1,如 _list.vp 的
# elem_ptr: T | t.CPtr 退化为 i8*):pointee=i8 但
# 元素是 8 字节指针,须按 i8* 步长 GEP(防只写 1 字节截断)
if trans is not None:
ve_pe_cs: HandlesVar.VarEntry | t.CPtr = \
HandlesVar.lookup_var_entry(trans.SymTab, nm.id)
if ve_pe_cs is not None and ve_pe_cs.IsPtrElement == 1:
# T|t.CPtr(退化为 i8*,如 _list.vp 的 elem_ptr)变量
# 存数据区指针,元素为 8 字节指针:必须先 load alloca
# 得指针值,再按 i8* 步长 GEP——否则 GEP base 用 alloca
# (&elem_ptr)算到栈地址,赋值写不进 list 数据区
# (append 只增 count、data 全 0xbaadf00d)。
ptr_val_cs: llvmlite.Value | t.CPtr = llvmlite.build_load(
builder, pointee, alloca)
if ptr_val_cs is not None:
match pointee:
case llvmlite.LLVMType.Int8():
i8_ptr_cs: llvmlite.LLVMType | t.CPtr = llvmlite.Ptr(
pool, llvmlite.Int8(pool))
return llvmlite.build_gep(
builder, i8_ptr_cs, ptr_val_cs, idx_val)
case _:
pass
return llvmlite.build_gep(
builder, pointee, ptr_val_cs, idx_val)
return None
return llvmlite.build_gep(builder, pointee, alloca, idx_val)
# 通用路径