fix(codegen): widen calls by the declared return signedness

This commit is contained in:
TermiNexus committed 2026-10-27 19:51:46 +08:00
1 parent 425d3f2120
commit 7cbe4e93dc
9 files changed
+83 -15

No files matched your search

+2 -2
View File
@@ -4099,7 +4099,7 @@ def _inject_function_declare(pool: memhub.MemBuddy*, mod: mlirlite.LLVMModule*,
trans._func_count = cur_count + 1
# Complete the global function-table Spec (params all added, matching FuncGenDef): without it
# find_func_entry_global() sees an empty Spec and vtable generation fails to find the type.
GenApi.update_global_func_spec(fd.name, func)
GenApi.update_global_func_spec(fd.name, func, GenApi.annot_ret_sign(fd.returns))
# _inject_method_declare - create a declare for a ClassDef method. Mangled name is
@@ -4255,7 +4255,7 @@ def _inject_method_declare(pool: memhub.MemBuddy*, mod: mlirlite.LLVMModule*, m_
trans._func_count = cur_count + 1
# Complete the global function-table Spec (matching ClassGenVTable): otherwise
# find_func_entry_global().Spec is empty and vtable generation fails to find the method type.
GenApi.update_global_func_spec(lookup_name, func)
GenApi.update_global_func_spec(lookup_name, func, GenApi.annot_ret_sign(m_fd.returns))
# _register_ctypedef_from_annassign - register a TypedefT from an AnnAssign node.
+8 -2
View File
@@ -971,8 +971,13 @@ novirtual class CallGenLeafKit(CallGenCBuiltin.CallGenCBuiltin):
return 0
def update_global_func_spec(self, name: str, func: mlirlite.Function*) -> int:
"""Update a global table entry's Spec after all its params are added"""
def update_global_func_spec(self, name: str, func: mlirlite.Function*, ret_sign: int = 0) -> int:
"""Update a global table entry's Spec after all its params are added.
ret_sign: GenTypes.FUNC_RET_SIGN_* of the declared return annotation (0 = keep the
previously recorded value). Needed because Spec/Ret comes from the LLVM type, which
cannot express unsignedness.
"""
if name is None or func is None: return 1
# 重载:同名多条时 Spec 必须写到**这个 Function 对应**的条目上,否则第二个重载会
# 覆盖第一个的签名,决议时所有候选看起来都是同一个签名。
@@ -989,6 +994,7 @@ novirtual class CallGenLeafKit(CallGenCBuiltin.CallGenCBuiltin):
if entry is None: entry = self.find_func_entry_global(name)
if entry is None: return 1
entry.Spec = self._build_func_spec(func)
if ret_sign != 0: entry.RetSign = ret_sign
return 0
+1 -1
View File
@@ -768,7 +768,7 @@ novirtual class ClassGenVTable(ClassGenBase.ClassGenBase):
if i1_ptr_ty_si is not None: mlirlite.add_param(pool, func, i1_ptr_ty_si, "%__stop_iter_flag__")
# Params done: finalize the global function-table Spec (vtable derives type text from it after reset).
self.Gen.CallGenLeafKit.update_global_func_spec(full_name, func)
self.Gen.CallGenLeafKit.update_global_func_spec(full_name, func, self.Gen.TypeGenResolve.annot_ret_sign(fd.returns))
# Forward-declaration mode: signature and table registration done, do not emit the body.
if sig_only != 0: return 0
+8 -2
View File
@@ -227,8 +227,12 @@ novirtual class ExprGenOps(ExprGenBase.ExprGenBase):
# generated IR), so a u32 with bit 31 set grew 0xFFFFFFFF in the high half — a real
# value 0x88691F61 became 0xFFFFFFFF88691F61 (stackmap pointers were mangled this way).
fe_fn_u: GenTypes.FuncEntry* = self.Gen.CallGenLeafKit.find_func_entry_global(func_nm_u.id)
if fe_fn_u is not None and fe_fn_u.Spec is not None:
if TSpec.SpecIsUnsigned(fe_fn_u.Spec.Ret) != 0: return 1
if fe_fn_u is not None:
# Declared return annotation first: the LLVM type cannot express sign, and
# SpecFromLLVMType marks every non-i1 int as SIGNED (so the Spec path below
# is only a fallback for i1/bool returns).
if fe_fn_u.RetSign == GenTypes.FUNC_RET_SIGN_UNSIGNED: return 1
if fe_fn_u.Spec is not None and TSpec.SpecIsUnsigned(fe_fn_u.Spec.Ret) != 0: return 1
elif fk_u == ast.ASTKind.Attribute:
# Qualified type cast: t.CUInt16T(x) / mod.ULONG(x).
at_u: ast.Attribute* = ast.Attribute*(cl.func)
@@ -384,6 +388,8 @@ novirtual class ExprGenOps(ExprGenBase.ExprGenBase):
nm_c: ast.Name* = ast.Name*(cl.func)
if nm_c.id is not None:
if self._spec_sign_from_annot(nm_c.id) == TSpec.TS_SIGN_SIGNED: return 1
fe_fn_s: GenTypes.FuncEntry* = self.Gen.CallGenLeafKit.find_func_entry_global(nm_c.id)
if fe_fn_s is not None and fe_fn_s.RetSign == GenTypes.FUNC_RET_SIGN_SIGNED: return 1
elif fk_s == ast.ASTKind.Attribute:
# Qualified signed cast: t.CInt16T(x) / mod.LONG(x).
at_c: ast.Attribute* = ast.Attribute*(cl.func)
+5 -5
View File
@@ -567,7 +567,7 @@ novirtual class FuncGenDef(CallGenBase.CallGenBase):
thunk_cnt: int = trans._func_count
if self.Gen.CallGenLeafKit.add_func_to_table(funcs_ptr, thunk_cnt, fd.name, thunk_f, thunk_max, None, 0, 0, pool) == 0:
trans._func_count = thunk_cnt + 1
self.Gen.CallGenLeafKit.update_global_func_spec(fd.name, thunk_f)
self.Gen.CallGenLeafKit.update_global_func_spec(fd.name, thunk_f, self.Gen.TypeGenResolve.annot_ret_sign(fd.returns))
continue
# Compute the mangled name. Overload groups (same name >=2 in this module body) get the
@@ -681,7 +681,7 @@ novirtual class FuncGenDef(CallGenBase.CallGenBase):
if ags.vararg is not None: func.IsVarArg = 1
# Params added; fill the global func-table Spec (lets vtable derive type text after func is reset)
self.Gen.CallGenLeafKit.update_global_func_spec(fd.name, func)
self.Gen.CallGenLeafKit.update_global_func_spec(fd.name, func, self.Gen.TypeGenResolve.annot_ret_sign(fd.returns))
# Clear the generic-func context once used (prevents leaking into later functions' annotation resolution)
if tp_names_fd is not None: self.Gen.TypeGenBase.clear_generic_context()
@@ -886,7 +886,7 @@ novirtual class FuncGenDef(CallGenBase.CallGenBase):
if thunk_d is None: return 0
thunk_max_d: int = 256
if self.Gen.CallGenLeafKit.add_func_to_table(funcs_ptr, func_count, fd.name, thunk_d, thunk_max_d, None, 0, 0, pool) == 0: trans._func_count = func_count + 1
self.Gen.CallGenLeafKit.update_global_func_spec(fd.name, thunk_d)
self.Gen.CallGenLeafKit.update_global_func_spec(fd.name, thunk_d, self.Gen.TypeGenResolve.annot_ret_sign(fd.returns))
return 0
# Extern declaration function: emit a declare (declare only, no definition). First check whether
# forward_declare_functions already created it to avoid a duplicate declare.
@@ -948,7 +948,7 @@ novirtual class FuncGenDef(CallGenBase.CallGenBase):
if func_section_e is not None: mlirlite.function_set_attr_group(func, func_section_e)
# Params added; fill the global func-table Spec
self.Gen.CallGenLeafKit.update_global_func_spec(fd.name, func)
self.Gen.CallGenLeafKit.update_global_func_spec(fd.name, func, self.Gen.TypeGenResolve.annot_ret_sign(fd.returns))
return 0
@@ -1084,7 +1084,7 @@ novirtual class FuncGenDef(CallGenBase.CallGenBase):
mlirlite.add_param(pool, func, i32_ptr_ty_eh, "%__eh_code_out__")
# Params added; fill the global func-table Spec
self.Gen.CallGenLeafKit.update_global_func_spec(fd.name, func)
self.Gen.CallGenLeafKit.update_global_func_spec(fd.name, func, self.Gen.TypeGenResolve.annot_ret_sign(fd.returns))
# Create the entry block
entry_blk: mlirlite.BasicBlock* = mlirlite.create_block(pool, func, "entry")
+10 -3
View File
@@ -163,6 +163,13 @@ def _get_type_name_from_annotation(pool: memhub.MemBuddy*, node: ast.AST*) -> st
return None
# ---------------- annot_ret_sign (delegates to TypeGenResolve.annot_ret_sign) ----------------
def annot_ret_sign(node: ast.AST*) -> int:
"""Signedness code of a return annotation (FUNC_RET_SIGN_*; 0=unknown)"""
if _g_gen is not None: return _g_gen.TypeGenResolve.annot_ret_sign(node)
return 0
# ---------------- register_ctypedef (delegates to TypeGenCType.register_ctypedef) ----------------
def register_ctypedef(pool: memhub.MemBuddy*, name: str, type_str: str):
"""Delegate to gen.register_ctypedef"""
@@ -1158,7 +1165,7 @@ def clear_scope_names(trans: HT.Translator*):
# ---------------- update_global_func_spec (delegates to CallGenLeafKit.update_global_func_spec) ----------------
def update_global_func_spec(name: str, func: mlirlite.Function*) -> int:
"""Delegate to gen.CallGenLeafKit.update_global_func_spec."""
if _g_gen is not None: return _g_gen.CallGenLeafKit.update_global_func_spec(name, func)
def update_global_func_spec(name: str, func: mlirlite.Function*, ret_sign: int = 0) -> int:
"""Delegate to gen.CallGenLeafKit.update_global_func_spec (ret_sign: FUNC_RET_SIGN_*, 0=keep)."""
if _g_gen is not None: return _g_gen.CallGenLeafKit.update_global_func_spec(name, func, ret_sign)
return 0
+11
View File
@@ -201,6 +201,17 @@ novirtual class FuncEntry:
# TypeSpec (dual-write since S3): the full signature of Kind=TS_FUNC (Ret/Params/...)
# built by update_global_func_spec after add_param; PermPool-resident, safe across 1b reset.
Spec: TSpec.TypeSpec*
# Signedness of the declared return annotation (FUNC_RET_SIGN_*): LLVM types carry no
# sign, and SpecFromLLVMType marks every int as signed, so this is the only source of truth
# for zext/sext at a call site.
RetSign: int
# FuncEntry.RetSign encoding. 0 must be "unset" because FuncEntry arrays are zero-initialized;
# TSpec.TS_SIGN_UNSIGNED is 0, so the mapping is explicit.
define FUNC_RET_SIGN_UNSET = 0
define FUNC_RET_SIGN_UNSIGNED = 1
define FUNC_RET_SIGN_SIGNED = 2
# ============================================================
+24
View File
@@ -1,5 +1,6 @@
import lib.core.Gen.GenTypes as GenTypes
import lib.core.Gen.GenApi as GenApi
import lib.core.Gen.Type.TypeSpec as TSpec
import lib.core.Gen.Type.TypeGenMixin as TypeGenMixin
from stdint import *
import ast
@@ -1521,6 +1522,29 @@ novirtual class TypeGenResolve(TypeGenBase.TypeGenBase):
return "<unknown>"
def annot_ret_sign(self, annot: ast.AST*) -> int:
"""Signedness code (GenTypes.FUNC_RET_SIGN_*) of a return annotation; 0 = unknown.
LLVM function types carry no signedness, so this must be recorded at declaration time.
Marker unions (`ExportT | uint16`) are scanned operand by operand; wrappers such as
`ConstT[uint16]` / `PtrT[uint16]` go through annotation_to_str + spec_sign_of_annot.
"""
if annot is None: return 0
k: int = annot.kind()
if k == ast.ASTKind.BinOp:
bop: ast.BinOp* = ast.BinOp*(annot)
if bop.op == ast.OpKind.BitOr:
r: int = self.annot_ret_sign(bop.left)
if r != 0: return r
return self.annot_ret_sign(bop.right)
return 0
txt: str = self.annotation_to_str(annot)
if txt is None: return 0
s: int = self.Gen.TypeGenCType.spec_sign_of_annot(txt)
if TSpec.SpecSignIsUnsigned(s) != 0: return GenTypes.FUNC_RET_SIGN_UNSIGNED
if TSpec.SpecSignIsSigned(s) != 0: return GenTypes.FUNC_RET_SIGN_SIGNED
return 0
def _is_storage_class_qualifier(self, name: str) -> int:
"""Whether a name is a storage-class qualifier macro (not a type): 1=yes / 0=no."""
if name is None: return 0
+14
View File
@@ -889,6 +889,20 @@ def SpecIsUnsigned(sp: TypeSpec*) -> int:
return 0
# Sign-code predicates. Reading TS_SIGN_UNSIGNED from another module fails: its value is 0 and
# the cross-module DefineT cache uses 0 as "absent", so callers must use these helpers instead.
def SpecSignIsUnsigned(sign: int) -> int:
"""Whether a TS_SIGN_* code is unsigned."""
if sign == TS_SIGN_UNSIGNED: return 1
return 0
def SpecSignIsSigned(sign: int) -> int:
"""Whether a TS_SIGN_* code is signed."""
if sign == TS_SIGN_SIGNED: return 1
return 0
def SpecBitWidth(sp: TypeSpec*) -> int:
"""Bit width; 0 for non-integer/float"""
if sp is None: return 0