fix(codegen): arg 糖支持 vprintf 式转发的 va_list,补 stdarg 模块限定分派

_HandleArgLlvm 的回退分支原先把 Gen.variables 里的形参 alloca 直接当 va_list 用,转发形态(vsnprintf(buf,size,fmt,args))因此多一层间接、读错内存;现改为先 load 出槽里的 va_list 指针(自身变参形态仍走 _direct_values 缓冲地址、不 load)。另新增 ModulePath == 'stdarg' 分派,使 stdarg.arg/va_arg/va_start/va_end 命中与裸名相同的内建(点号形式此前会落到外部调用产生未定义符号 @arg)。同时删除 ExprCallHandle 里重复且无调用者的 _HandleArgLlvm 死代码。
This commit is contained in:
TermiNexus committed 2026-10-07 20:15:32 +08:00
1 parent 9656bb161a
commit 75aae26881
2 files changed
+28 -86

No files matched your search

+15 -1
View File
@@ -1155,10 +1155,24 @@ class ExprBuiltinHandle(BaseHandle):
va_list_ptr = variadic_info.get('va_list_ptr')
if not va_list_ptr:
vararg_name: str = variadic_info.get('vararg_name', 'args') if variadic_info else 'args'
# va_list 的两种形态(回退分支只会命中其中之一,处理方式**不同**):
# 1) 自身变参形态(`def f(a, *args)` 内部调用 arg(T)):HandlesFunctions
# 在函数入口自动发射 llvm.va_start,并把 **va_list 缓冲的地址** 以
# args 为名登记进 Gen._direct_values。此处的值本身就是 va_arg 需要的
# 那个 va_list 指针,**绝不能 load**——load 会把缓冲首字节当成指针再
# 解引用,读到垃圾。
# 2) 转发形态(如 `vsnprintf(buf, size, fmt, args)`,被调函数自身**非**
# 变参):第 4 个形参 args 在 LLVM 里是 alloca 出来的 i8** 槽,槽里
# 存着**调用方传下来的 va_list 指针值**(sprintf/snprintf 把自己的
# *args 指针转发下来)。va_arg 需要的是槽里存的那个 i8* 指针值,所以
# 必须 load 形参槽把指针取出来;直接传 alloca(i8**)会多一层间接,
# 把"指向指针的槽"当成 va_list 缓冲,读到的位置完全错误。
# 两种形态的区别是"登记进 _direct_values 的是缓冲地址 / 形参槽的 alloca":
# 前者直接用,后者必须 load 出槽内容。
if vararg_name in Gen._direct_values:
va_list_ptr = Gen._direct_values[vararg_name]
elif vararg_name in Gen.variables:
va_list_ptr = Gen.variables[vararg_name]
va_list_ptr = Gen._load(Gen.variables[vararg_name], name="varg_forward_load")
if not va_list_ptr:
return ir.Constant(ir.IntType(32), 0)
# 类型参数解析与 va_arg(ap, T) 共用同一入口:显式类型走 _resolve_vararg_type
+13 -85
View File
@@ -967,6 +967,19 @@ class ExprCallHandle(BaseHandle):
return self._HandleLLVMIRLlvm(Node)
elif FuncAttr in ('LInp', 'LOut'):
return ir.Constant(ir.IntType(32), 0)
# stdarg 模块的变参糖:stdarg.arg / stdarg.va_arg / stdarg.va_start /
# stdarg.va_end 命中与裸名同名内建**完全相同**的处理(stdarg 是编译器
# 拦截的糖模块,不是真符号,与 c 模块的语法设施同一口径)。仅当
# ModulePath 恰为 'stdarg' 时进入,不会抢掉其它模块/用户模块的调用。
if ModulePath == 'stdarg':
if FuncAttr == 'arg':
return self.Trans.ExprBuiltinHandle._HandleArgLlvm(Node.args)
elif FuncAttr == 'va_arg':
return self.Trans.ExprBuiltinHandle._HandleVaArgLlvm(Node.args)
elif FuncAttr == 'va_start':
return self.Trans.ExprBuiltinHandle._HandleVaStartLlvm(Node.args)
elif FuncAttr == 'va_end':
return self.Trans.ExprBuiltinHandle._HandleVaEndLlvm(Node.args)
if FuncAttr in Gen.structs:
result = self._HandleClassNewLlvm(Node, FuncAttr, module_sha1=_ctor_module_sha1)
if result is not None:
@@ -4906,91 +4919,6 @@ class ExprCallHandle(BaseHandle):
Gen = self.Trans.LlvmGen
return None
def _HandleArgLlvm(self, args: list[ast.AST], CallNode: ast.Call | None = None) -> ir.Value | None:
Gen = self.Trans.LlvmGen
va_list_ptr = None
variadic_info = getattr(Gen, '_va_arg_info', None)
if not variadic_info:
variadic_info = getattr(Gen, '_variadic_info', None)
if variadic_info:
va_list_ptr = variadic_info.get('va_list_ptr')
if not va_list_ptr:
vararg_name = variadic_info.get('vararg_name', 'args') if variadic_info else 'args'
if vararg_name in Gen._direct_values:
va_list_ptr = Gen._direct_values[vararg_name]
elif vararg_name in Gen.variables:
va_list_ptr = Gen.variables[vararg_name]
if not va_list_ptr:
return ir.Constant(ir.IntType(32), 0)
target_type = ir.IntType(32)
if args:
type_arg = args[0]
if isinstance(type_arg, ast.Name):
type_name = 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 = CTypeRegistry.GetClassByName(type_name)
if ctype_cls is not None:
llvm_str = CTypeRegistry.CTypeToLLVM(ctype_cls)
resolved = Gen._type_str_to_llvm(llvm_str)
if resolved:
target_type = resolved
else:
resolved = CTypeRegistry.ResolveName(type_name)
if resolved is not None:
ctype_cls, ptr_level = resolved
llvm_str = CTypeRegistry.CTypeToLLVM(ctype_cls)
if llvm_str:
base = 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 = type_arg.attr
ctype_cls = CTypeRegistry.GetClassByName(attr_name)
if ctype_cls is not None:
llvm_str = CTypeRegistry.CTypeToLLVM(ctype_cls)
resolved = Gen._type_str_to_llvm(llvm_str)
if resolved:
target_type = resolved
else:
assign_node = getattr(self.Trans, '_current_assign_node', None)
if assign_node:
ann = getattr(assign_node, 'annotation', None)
if ann:
ann_name = 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 = CTypeRegistry.GetClassByName(ann_name)
if ctype_cls is not None:
llvm_str = CTypeRegistry.CTypeToLLVM(ctype_cls)
resolved = 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_counter = getattr(Gen, '_va_arg_counter', 0)
Gen._va_arg_counter = va_arg_counter + 1
result = Gen.emit_va_arg(va_list_ptr, target_type)
if result is not None and getattr(result, 'name', ''):
result.name = f"va_arg_result_{Gen._va_arg_counter}"
return result
def _HandleVaArgLlvm(self, args: list[ast.AST]) -> ir.Constant:
Gen: LlvmGeneratorMixin = self.Trans.LlvmGen
return ir.Constant(ir.IntType(32), 0)