fix(vararg): va_arg/arg 类型解析改走注解解析同源并 fail-fast

- _resolve_vararg_type 覆盖 builtins/库/符号表 typedef 与 T* 指针后缀,不再对手抄名单之外的名字(如 uint64)静默回退 i32
- i32 回退会让 Win64 8 字节变参槽被 trunc 丢高 32 位:viperlib vsnprintf 的 %016llx 因此只打印低 32 位,ConstFloat 的浮点常量名(即常量值)退化为真值低 32 位零扩展,double 字面量全变 0.0
- _HandleArgLlvm / _infer_vararg_type_from_context 统一调同一入口,消除三份同源重复解析链
This commit is contained in:
TermiNexus committed 2026-09-24 20:41:05 +08:00
1 parent 2cc816ac13
commit f7ace9876d
1 file changed
+68 -88
+68 -88
View File
@@ -5,11 +5,12 @@ if TYPE_CHECKING:
from lib.core.Translator.LlvmGenerator import LlvmGeneratorMixin
import vast as ast
import llvmlite.ir as ir
from lib.core.Handles.HandlesBase import BaseHandle
from lib.core.Handles.HandlesBase import BaseHandle, CTypeInfo
from lib.core.VLogger import get_logger as _vlog
from lib.constants.config import mode as _config_mode
from lib.includes.t import CTypeRegistry
from lib.core.SymbolUtils import IsTModule, ExtractTypeNameFromBinOp
from lib.includes import t as _t
from lib.core.SymbolUtils import IsTModule
class ExprBuiltinHandle(BaseHandle):
@@ -631,7 +632,20 @@ class ExprBuiltinHandle(BaseHandle):
if offset is not None:
list_ptr = Gen.builder.gep(self_ptr, [ir.Constant(ir.IntType(32), 0), ir.Constant(ir.IntType(32), offset)], name=f"{obj_name}_{attr_name}_ptr")
list_val = Gen._load(list_ptr, name=f"{obj_name}_{attr_name}")
result = self.Trans.ExprUtils._try_operator_overLoad(field_cls, '__len__', list_val, Gen)
# 诊断增强(不改变任何成功路径):字段身份(class_member_element_class)
# 说是 list 容器,但按 _get_member_offset 取出的字段静态类型不是
# list 特化接收者(典型为 i32,即偏移落在 lineno/col_offset 等 i32 字段)
# 时,llvmlite 只会抛出无上下文的
# "Type of #1 arg mismatch: %"…list[…]"* != i32"。
# 此处仅在"该调用本就会抛 TypeError 终止"时生效,把类/字段/偏移/
# 接收者类型一并报出,用于判定 class_members 偏移与 struct 布局不一致。
try:
result = self.Trans.ExprUtils._try_operator_overLoad(field_cls, '__len__', list_val, Gen)
except TypeError as _e_len:
raise TypeError(
f"[fast-fail] len({obj_name}.{attr_name}) 分派 '{field_cls}.__len__' 失败:"
f"类='{bare_cls}' 偏移={offset} 接收者类型={list_val.type};"
f"(成员偏移与 struct 布局不一致)原错误: {_e_len}")
if result is not None:
return result
val = self.HandleExprLlvm(arg_node)
@@ -850,7 +864,8 @@ class ExprBuiltinHandle(BaseHandle):
if enum_member_found:
enum_type_name = enum_class_name
if enum_type_name:
type_str = f"<enum '{enum_type_name}' size=4 signed=true, ptr=false>"
_enum_bits: int = Gen._enum_int_type(enum_type_name).width
type_str = f"<enum '{enum_type_name}' size={_enum_bits // 8} signed=true, ptr=false>"
else:
llvm_type = self.Trans.ExprUtils._infer_expr_llvm_type_full(arg)
if isinstance(llvm_type, ir.PointerType):
@@ -1048,68 +1063,11 @@ class ExprBuiltinHandle(BaseHandle):
va_list_ptr = Gen.variables[vararg_name]
if not va_list_ptr:
return ir.Constant(ir.IntType(32), 0)
target_type: ir.Type = ir.IntType(32)
if args:
type_arg: ast.expr = args[0]
if isinstance(type_arg, ast.Name):
type_name: str = type_arg.id
if type_name in ('str', 'bytes'):
target_type = ir.PointerType(ir.IntType(8))
elif type_name == 'CPtr':
target_type = ir.PointerType(ir.IntType(8))
elif type_name in Gen.structs:
target_type = ir.PointerType(Gen.structs[type_name])
else:
ctype_cls: type | None = CTypeRegistry.GetClassByName(type_name)
if ctype_cls is not None:
llvm_str: str | None = CTypeRegistry.CTypeToLLVM(ctype_cls)
resolved: ir.Type | None = Gen._type_str_to_llvm(llvm_str)
if resolved:
target_type = resolved
else:
resolved: tuple | None = CTypeRegistry.ResolveName(type_name)
if resolved is not None:
ctype_cls, ptr_level = resolved
llvm_str = CTypeRegistry.CTypeToLLVM(ctype_cls)
if llvm_str:
base: ir.Type | None = Gen._type_str_to_llvm(llvm_str)
if base is not None:
for _ in range(ptr_level):
if isinstance(base, ir.VoidType):
base = ir.IntType(8).as_pointer()
else:
base = ir.PointerType(base)
target_type = base
elif isinstance(type_arg, ast.Attribute):
if isinstance(type_arg.value, ast.Name) and IsTModule(type_arg.value.id, self.Trans.SymbolTable):
attr_name: str = type_arg.attr
ctype_cls: type | None = CTypeRegistry.GetClassByName(attr_name)
if ctype_cls is not None:
llvm_str: str | None = CTypeRegistry.CTypeToLLVM(ctype_cls)
resolved: ir.Type | None = Gen._type_str_to_llvm(llvm_str)
if resolved:
target_type = resolved
else:
assign_node: ast.AST | None = getattr(self.Trans, '_current_assign_node', None)
if assign_node:
ann: ast.AST | None = getattr(assign_node, 'annotation', None)
if ann:
ann_name: str | None = None
if isinstance(ann, ast.Name):
ann_name = ann.id
elif isinstance(ann, ast.Attribute):
ann_name = ann.attr
elif isinstance(ann, ast.BinOp) and isinstance(ann.op, ast.BitOr):
ann_name = ExtractTypeNameFromBinOp(ann)
if ann_name:
ctype_cls: type | None = CTypeRegistry.GetClassByName(ann_name)
if ctype_cls is not None:
llvm_str: str | None = CTypeRegistry.CTypeToLLVM(ctype_cls)
resolved: ir.Type | None = Gen._type_str_to_llvm(llvm_str)
if resolved:
target_type = resolved
elif ann_name in Gen.structs:
target_type = ir.PointerType(Gen.structs[ann_name])
# 类型参数解析与 va_arg(ap, T) 共用同一入口:显式类型走 _resolve_vararg_type
# (typedef/T*/struct 同一规则源,解析不出即 fail-fast),无参数走
# _infer_vararg_type_from_context(无注解可推断时保持 i32 默认)。
target_type: ir.Type = self._resolve_vararg_type(args[0], Gen) if args \
else self._infer_vararg_type_from_context(Gen)
if not getattr(Gen, '_va_arg_counter', None):
Gen._va_arg_counter = 0
Gen._va_arg_counter += 1
@@ -1119,8 +1077,24 @@ class ExprBuiltinHandle(BaseHandle):
return result
def _resolve_vararg_type(self, type_arg: ast.expr, Gen: LlvmGeneratorMixin) -> ir.Type:
"""将类型表达式解析为 LLVM 类型(供 arg/va_arg 共用)。"""
target_type: ir.Type = ir.IntType(32)
"""将类型表达式解析为 LLVM 类型(供 arg/va_arg 共用)。
解析顺序(与注解解析同一规则源,避免各处手抄一份而漏项):
1) str/bytes/CPtr → i8*;类名(Gen.structs)→ 该类指针
2) CTypeRegistry.GetClassByName/ResolveName(t.* 类型类与 C 基本类型名)
3) CTypeInfo.FromNode:builtins/库/符号表 typedef(uint64/int64/sizet/
size_t…)、`T*` 指针后缀(parser 已展开为 `T | t.CPtr`)等
fail-fast:以上均解析不出 LLVM 类型时直接报错终止,**绝不回退 i32**。
回退 i32 的后果不是"精度差一点"而是**读错变参槽**:Win64 变参槽固定 8 字节,
emit_va_arg 对 <64 位整数会生成 "va_arg i64 → trunc to i32",于是
`va_arg(ap, uint64)` 只保留低 32 位、高 32 位被丢弃。viperlib vsnprintf 的
`%016llx` 分支(`u_num_l: uint64 = va_arg(ap, uint64)`)因此输出
0x00000000XXXXXXXX —— llvmlite.ConstFloat 用该文本作"浮点常量位模式名",
所有 double 字面量遂退化为「真值低 32 位零扩展」(1.0/2.5/3.0 的低 32 位
全 0 ⇒ 全变成 0.0,除法/比较结果错乱)。
"""
target_type: ir.Type | None = None
if isinstance(type_arg, ast.Name):
type_name: str = type_arg.id
if type_name in ('str', 'bytes'):
@@ -1159,32 +1133,38 @@ class ExprBuiltinHandle(BaseHandle):
resolved: ir.Type | None = Gen._type_str_to_llvm(llvm_str)
if resolved:
target_type = resolved
# 常规分支未命中:走注解解析同一规则源(typedef / `T*` 指针后缀 / t.* 组合写法)。
# 该规则源对未收录的名字会退化成 `%"{mod}.名"*` 假结构体(CStruct),故显式判掉。
if target_type is None:
type_info: CTypeInfo | None = CTypeInfo.FromNode(type_arg, self.Trans.SymbolTable)
if type_info is not None and type_info.BaseType is not None \
and not isinstance(type_info.BaseType, _t.CStruct):
target_type = Gen._ctype_to_llvm(type_info)
if target_type is None:
type_name_dbg: str = type(type_arg).__name__
if isinstance(type_arg, ast.Name):
type_name_dbg = type_arg.id
elif isinstance(type_arg, ast.Attribute):
type_name_dbg = type_arg.attr
raise TypeError(
f"[fast-fail] va_arg/arg 的类型参数 '{type_name_dbg}' 解析不出 LLVM 类型:"
f"拒绝回退 i32(Win64 变参槽 8 字节,i32 会 trunc 掉高 32 位,"
f"如 va_arg(ap, uint64) 只留低 32 位)。请检查该类型名是否已声明/import。")
return target_type
def _infer_vararg_type_from_context(self, Gen: LlvmGeneratorMixin) -> ir.Type:
"""从当前赋值上下文推断 va_arg/arg 的目标类型。"""
target_type: ir.Type = ir.IntType(32)
"""从当前赋值上下文推断 va_arg/arg 的目标类型(无显式类型参数时用)
有赋值注解时与显式类型参数走同一解析入口(含 typedef/T*/struct;解析不出即
fail-fast,避免 i32 回退读错 8 字节变参槽);无注解可推断时保持 i32 默认
(`va_arg(ap)` 读 i32 的既有语义,见 viperlib vsnprintf)。
"""
assign_node: ast.AST | None = getattr(self.Trans, '_current_assign_node', None)
if assign_node:
ann: ast.AST | None = getattr(assign_node, 'annotation', None)
if ann:
ann_name: str | None = None
if isinstance(ann, ast.Name):
ann_name = ann.id
elif isinstance(ann, ast.Attribute):
ann_name = ann.attr
elif isinstance(ann, ast.BinOp) and isinstance(ann.op, ast.BitOr):
ann_name = ExtractTypeNameFromBinOp(ann)
if ann_name:
ctype_cls: type | None = CTypeRegistry.GetClassByName(ann_name)
if ctype_cls is not None:
llvm_str: str | None = CTypeRegistry.CTypeToLLVM(ctype_cls)
resolved: ir.Type | None = Gen._type_str_to_llvm(llvm_str)
if resolved:
target_type = resolved
elif ann_name in Gen.structs:
target_type = ir.PointerType(Gen.structs[ann_name])
return target_type
if ann is not None:
return self._resolve_vararg_type(ann, Gen)
return ir.IntType(32)
def _HandleVaArgLlvm(self, args: list[ast.expr]) -> ir.Value:
Gen: LlvmGeneratorMixin = self.Trans.LlvmGen