diff --git a/App/lib/core/Gen/Call/CallGenVa.vp b/App/lib/core/Gen/Call/CallGenVa.vp index daac42f..5e02ac0 100644 --- a/App/lib/core/Gen/Call/CallGenVa.vp +++ b/App/lib/core/Gen/Call/CallGenVa.vp @@ -68,8 +68,23 @@ novirtual class CallGenVa(CallGenBase.CallGenBase): # Emit the va_arg instruction return mlirlite.build_va_arg(builder, ap_ptr, read_ty) def _translate_arg_sugar(self, pool: memhub.MemBuddy*, builder: mlirlite.IRBuilder*, mod: mlirlite.LLVMModule*, cl: ast.Call*, cargs: list[ast.AST*]*, can: sizet, funcs_ptr: t.CPtr, func_count: int, trans: HT.Translator*) -> mlirlite.Value*: - """Emit va_arg using the current function's implicit va_list (trans._va_list_ptr).""" - if trans._va_list_ptr is None: return mlirlite.const_int32(pool, 0) + """Emit va_arg reading from the enclosing variadic's own va_list or a forwarded va_list.""" + # arg() has two forms: + # (1) Own-variadic form: inside `def f(a, *args)`, trans._va_list_ptr is the i8* address of + # the function's implicit va_list buffer; read the next vararg straight from it. + # (2) Forwarding form: in a NON-variadic wrapper such as vsnprintf(buf, size, fmt, args), + # the caller's va_list pointer is passed by value through the 4th param named "args" + # (vprintf style, see includes/viperlib.vp); there is no implicit va_list here, so + # trans._va_list_ptr is None and the pointer must be recovered from that param. + va_ptr: mlirlite.Value* = trans._va_list_ptr + if va_ptr is None: + # The "args" param's alloca is an i8** slot holding the forwarded i8* va_list value, so it + # MUST be loaded once: va_arg needs the pointer value itself (i8*), not the slot address. + args_alloca: mlirlite.Value* = self.Gen.VarGen.lookup_var(trans.SymTab, "args") + if args_alloca is None: return mlirlite.const_int32(pool, 0) + if args_alloca.Ty is None: return mlirlite.const_int32(pool, 0) + va_ptr = mlirlite.build_load(builder, args_alloca.Ty.Pointee, args_alloca) + if va_ptr is None: return mlirlite.const_int32(pool, 0) # First arg: target type (optional, defaults to i32) arg_ty: mlirlite.LLVMType* = mlirlite.Int32(pool) @@ -83,12 +98,12 @@ novirtual class CallGenVa(CallGenBase.CallGenBase): abits_arg: int = self.Gen.ExprGenBase.get_llvm_type_bits(arg_ty) if abits_arg != 0 and abits_arg < 64: read_ty = mlirlite.Int64(pool) - arg_raw: mlirlite.Value* = mlirlite.build_va_arg(builder, trans._va_list_ptr, read_ty) + arg_raw: mlirlite.Value* = mlirlite.build_va_arg(builder, va_ptr, read_ty) if arg_raw is None: return mlirlite.const_int32(pool, 0) return mlirlite.build_trunc(builder, arg_raw, arg_ty) - # Emit va_arg, using trans._va_list_ptr as the va_list address - return mlirlite.build_va_arg(builder, trans._va_list_ptr, read_ty) + # Emit va_arg, using va_ptr as the va_list address (own buffer or forwarded pointer) + return mlirlite.build_va_arg(builder, va_ptr, read_ty) def _translate_va_start_sugar(self, pool: memhub.MemBuddy*, builder: mlirlite.IRBuilder*, mod: mlirlite.LLVMModule*, cargs: list[ast.AST*]*, can: sizet, funcs_ptr: t.CPtr, func_count: int, trans: HT.Translator*) -> mlirlite.Value*: """Emit llvm.va_start to initialize the va_list (the second C-ABI argument is unused).""" if can == 0 or cargs is None: return mlirlite.const_int32(pool, 0)