Files
TransPyC/lib/core/LLVMCG/ExprGen.py
T
TermiNexus d7158bd336 feat(cast): 指针隐式转换新规则 + None 可隐式转任意类型 + 诊断错位修复(9 文件)
MemoryOps._ClassifyPtrImplicitConvert:同类型放行;层数不同禁;任一侧通用指针(i8*)放行;皆非通用按被指类型(非基本类型禁、基本标量按位宽无损);指针与非基本值互换禁

_IsNoneValue 收敛:AST None 常量 或 指针型 null 常量 → 放行任意目标(指针目标给 null,非指针目标给零值);整数 0 不误命中;短路口覆盖 9 处禁令

诊断:新增 BaseGen._DescribeValueNode(子表达式下沉 + 有符号列号 + 源码片段),9 处禁令统一使用,不再打印外层 Assign 与 4294967295 列号

注:TPC 侧规则③比 TPV 更严(指针→非基本值恒禁),可能与既有按值回读冲突;待实机编译确认后再统一两侧口径
2026-09-28 15:10:39 +08:00

450 lines
26 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
from __future__ import annotations
from typing import Any
import logging
import llvmlite.ir as ir
from lib.constants.config import mode as _config_mode
class ExprGenMixin:
def emit_constant(self, value: Any, type_name: str = 'int') -> ir.Value:
if type_name == 'int':
return ir.Constant(ir.IntType(32), int(value))
elif type_name == 'uint64_t' or type_name == 'unsigned long long':
return ir.Constant(ir.IntType(64), int(value))
elif type_name == 'uint32_t' or type_name == 'unsigned int':
return ir.Constant(ir.IntType(32), int(value))
elif type_name == 'uint16_t' or type_name == 'unsigned short':
return ir.Constant(ir.IntType(16), int(value))
elif type_name == 'uint8_t' or type_name == 'unsigned char':
return ir.Constant(ir.IntType(8), int(value))
elif type_name == 'int64_t' or type_name == 'long long':
return ir.Constant(ir.IntType(64), int(value))
elif type_name == 'int32_t' or type_name == 'int':
return ir.Constant(ir.IntType(32), int(value))
elif type_name == 'int16_t' or type_name == 'short':
return ir.Constant(ir.IntType(16), int(value))
elif type_name == 'int8_t' or type_name == 'char':
return ir.Constant(ir.IntType(8), int(value))
elif type_name == 'float' or type_name == 'float32_t' or type_name == 'FLOAT32':
return ir.Constant(ir.FloatType(), float(value))
elif type_name == 'double' or type_name == 'float64_t' or type_name == 'FLOAT64':
return ir.Constant(ir.DoubleType(), float(value))
elif type_name == 'float16_t' or type_name == 'FLOAT16':
return ir.Constant(ir.IntType(16), int(value))
elif type_name == 'float8_t' or type_name == 'FLOAT8':
return ir.Constant(ir.IntType(8), int(value))
elif type_name == 'float128_t' or type_name == 'FLOAT128':
return ir.Constant(ir.IntType(128), int(value))
elif type_name == 'string':
encoded: bytes = value.encode('utf-8') + b'\x00'
str_type: ir.ArrayType = ir.ArrayType(ir.IntType(8), len(encoded))
str_const: ir.Constant = ir.Constant(str_type, bytearray(encoded))
global_var: ir.GlobalVariable = ir.GlobalVariable(self.module, str_type, name=f"str_const_{self.string_const_counter}")
self.string_const_counter += 1
global_var.initializer = str_const
global_var.linkage = 'internal'
return self.builder.bitcast(global_var, ir.PointerType(ir.IntType(8)), name="str")
return ir.Constant(ir.IntType(32), int(value))
def emit_binary_op(self, op: str, left: ir.Value, right: ir.Value, is_unsigned: bool = False, is_aug: bool = False) -> ir.Value:
if isinstance(left.type, ir.PointerType) and isinstance(right.type, ir.IntType):
# 数组指针需要先 decay 为元素指针,否则 GEP 会以整个数组为步长
# 例如 [64 x i8]* + 1 会前进 64 字节而非 1 字节
if isinstance(left.type.pointee, ir.ArrayType):
zero: ir.Constant = ir.Constant(ir.IntType(32), 0)
left = self.builder.gep(left, [zero, zero], name="array_decay")
right = self.builder.zext(right, ir.IntType(64), name="int2ptrint")
if op == '+' or op == 'Add':
return self.builder.gep(left, [right], name="ptr_add")
elif op == '-' or op == 'Sub':
return self.builder.gep(left, [self.builder.neg(right, name="neg")], name="ptr_sub")
if isinstance(left.type, ir.IntType) and isinstance(right.type, ir.PointerType):
if isinstance(right.type.pointee, ir.ArrayType):
zero: ir.Constant = ir.Constant(ir.IntType(32), 0)
right = self.builder.gep(right, [zero, zero], name="array_decay_rev")
left = self.builder.zext(left, ir.IntType(64), name="int2ptrint2")
if op == '+' or op == 'Add':
return self.builder.gep(right, [left], name="ptr_add_rev")
# ============================================================
# 双指针算术全域 fail-fast(**单一收敛点**)
#
# BinOp(HandlesExprOps._HandleBinOpLlvm)与 AugAssign
# (HandlesAugAssign._HandleAugAssignLlvm,+=、-=、*= …)都经本函数,
# 故在此一次收口,避免多点各写一份判据/报文。
# 判据:运算符属二元算术/位运算/移位集合,且**两操作数都是 LLVM 指针**。
# 本语言的 str 是 i8*,没有 `+` 拼接语义;指针 ± 指针也不是 GEP 语义
# (GEP 只接受「一指针一整数」,已在上方两个 ptr/int 分支处理并返回)。
# 落到下方 ops/shift_ops/bit_ops 会直出非法 IR(`add i8*, i8*` ⇒ opt 报
# invalid operand type for instruction),故在 IR 生成前 fail-fast。
# 判据与报文逐句对齐 TPV ExprGenOps.translate_binop / AssignGenMixin._apply_aug_op。
# ============================================================
if op in ('+', 'Add', '-', 'Sub', '*', 'Mult', '/', 'Div', '//', 'FloorDiv',
'%', 'Mod', '**', 'Pow', '&', 'BitAnd', '|', 'BitOr', '^', 'BitXor',
'<<', 'LShift', '>>', 'RShift') \
and isinstance(left.type, ir.PointerType) \
and isinstance(right.type, ir.PointerType):
LeftTy: str = str(left.type)
RightTy: str = str(right.type)
if LeftTy == 'i8*':
LeftTy = 'str(i8*)'
if RightTy == 'i8*':
RightTy = 'str(i8*)'
OpCh: str = op
AugNote: str = ''
if is_aug:
# 增量赋值(+= 等)要能自报家门,便于定位(不写成裸 '+')
OpCh = f'{op}='
AugNote = '(增量赋值)'
raise TypeError(
f"[TransPyC] 双指针运算类型非法: op = '{OpCh}'{AugNote},"
f'左操作数类型 = {LeftTy},右操作数类型 = {RightTy}。'
f'本语言的 str 是 i8*,没有 `+` 拼接语义(`str + str` 与指针 ± 指针'
f'都会退化成裸指针 add/sub ⇒ 非法 IR: opt 报 '
f'invalid operand type for instruction)。'
f'禁止静默降级,已终止编译。'
f'修法: 用 f-string 插值、string.strcat 或 viperlib.snprintf 构建字符串。'
+ self._get_node_info()
)
is_float: bool = isinstance(left.type, (ir.FloatType, ir.DoubleType)) or \
isinstance(right.type, (ir.FloatType, ir.DoubleType))
if is_float:
ftype: ir.Type
if isinstance(left.type, (ir.FloatType, ir.DoubleType)):
ftype = left.type
elif isinstance(right.type, (ir.FloatType, ir.DoubleType)):
ftype = right.type
else:
ftype = ir.DoubleType()
if not isinstance(left.type, (ir.FloatType, ir.DoubleType)):
left = self._to_float(left, ftype)
elif left.type != ftype:
if isinstance(left.type, ir.FloatType) and isinstance(ftype, ir.DoubleType):
left = self.builder.fpext(left, ftype, name="fpext_l")
else:
left = self.builder.fptrunc(left, ftype, name="fptrunc_l")
if not isinstance(right.type, (ir.FloatType, ir.DoubleType)):
right = self._to_float(right, ftype)
elif right.type != ftype:
if isinstance(right.type, ir.FloatType) and isinstance(ftype, ir.DoubleType):
right = self.builder.fpext(right, ftype, name="fpext_r")
else:
right = self.builder.fptrunc(right, ftype, name="fptrunc_r")
float_ops: dict[str, Any] = {
'+': self.builder.fadd, 'Add': self.builder.fadd,
'-': self.builder.fsub, 'Sub': self.builder.fsub,
'*': self.builder.fmul, 'Mult': self.builder.fmul,
'/': self.builder.fdiv, 'Div': self.builder.fdiv,
'%': self.builder.frem, 'Mod': self.builder.frem,
}
float_cmp_ops: dict[str, str] = {
'==': '==', 'Eq': '==', '!=': '!=', 'NotEq': '!=',
'<': '<', 'Lt': '<', '<=': '<=', 'LtE': '<=',
'>': '>', 'Gt': '>', '>=': '>=', 'GtE': '>=',
}
if op in float_ops:
return float_ops[op](left, right, name=op)
if op in float_cmp_ops:
return self.builder.fcmp_ordered(float_cmp_ops[op], left, right, name=op)
ops: dict[str, Any]
if is_unsigned:
ops = {
'+': self.builder.add, 'Add': self.builder.add,
'-': self.builder.sub, 'Sub': self.builder.sub,
'*': self.builder.mul, 'Mult': self.builder.mul,
'/': self.builder.udiv, 'Div': self.builder.udiv,
'%': self.builder.urem, 'Mod': self.builder.urem,
'//': self.builder.udiv, 'FloorDiv': self.builder.udiv,
}
else:
ops = {
'+': self.builder.add, 'Add': self.builder.add,
'-': self.builder.sub, 'Sub': self.builder.sub,
'*': self.builder.mul, 'Mult': self.builder.mul,
'/': self.builder.sdiv, 'Div': self.builder.sdiv,
'%': self.builder.srem, 'Mod': self.builder.srem,
'//': self.builder.sdiv, 'FloorDiv': self.builder.sdiv,
}
cmp_ops: dict[str, str] = {
'==': '==', 'Eq': '==', '!=': '!=', 'NotEq': '!=',
'<': '<', 'Lt': '<', '<=': '<=', 'LtE': '<=',
'>': '>', 'Gt': '>', '>=': '>=', 'GtE': '>=',
}
logic_ops: dict[str, Any] = {
'&&': self.builder.and_, 'And': self.builder.and_,
'||': self.builder.or_, 'Or': self.builder.or_,
}
if op in ('>>', 'RShift') and not is_unsigned:
if isinstance(left.type, ir.IntType):
if left.type.width == 8:
is_unsigned = True
elif left.type.width == 16:
is_unsigned = True
elif left.type.width == 32:
if self._last_var_name:
is_unsigned = self._is_var_unsigned(self._last_var_name)
elif left.type.width == 64:
if self._last_var_name:
is_unsigned = self._is_var_unsigned(self._last_var_name)
shift_ops: dict[str, Any] = {
'>>': self.builder.lshr if is_unsigned else self.builder.ashr,
'RShift': self.builder.lshr if is_unsigned else self.builder.ashr,
'<<': self.builder.shl, 'LShift': self.builder.shl,
}
bit_ops: dict[str, Any] = {
'&': self.builder.and_,
'|': self.builder.or_,
'^': self.builder.xor,
}
# 整数操作数宽度对齐:两个操作数都是整数但位宽不同时,
# 统一扩展到较大位宽(低位宽操作数 zext/sext)。
# 触发场景:`_g_alloc_count: t.CSizeT = 0`(i64 全局)在函数内
# `_g_alloc_count += 1`,RHS 整数字面量是 i32,不扩展会报
# "Operands must be the same type, got (i64, i32)"。
if (op in ops or op in shift_ops or op in bit_ops or op in cmp_ops):
if (isinstance(left.type, ir.IntType) and isinstance(right.type, ir.IntType)
and left.type != right.type and left.type.width != 1 and right.type.width != 1):
if left.type.width > right.type.width:
if is_unsigned:
right = self.builder.zext(right, left.type, name="zext_rhs_op")
else:
right = self.builder.sext(right, left.type, name="sext_rhs_op")
elif right.type.width > left.type.width:
if is_unsigned:
left = self.builder.zext(left, right.type, name="zext_lhs_op")
else:
left = self.builder.sext(left, right.type, name="sext_lhs_op")
if op == '**' or op == 'Pow':
if isinstance(left, ir.Constant) and isinstance(right, ir.Constant):
try:
lv: Any = left.constant
rv: Any = right.constant
if isinstance(lv, int) and isinstance(rv, int) and rv >= 0:
result: int = lv ** rv
return ir.Constant(left.type, result)
except Exception as _e:
if _config_mode == "strict":
logging.warning(f"异常被忽略: {_e}")
pass
ftype: ir.Type
if isinstance(left.type, (ir.FloatType, ir.DoubleType)):
ftype = left.type
elif isinstance(right.type, (ir.FloatType, ir.DoubleType)):
ftype = right.type
else:
ftype = ir.DoubleType()
lf: ir.Value = self._to_float(left, ftype)
rf: ir.Value = self._to_float(right, ftype)
powf: ir.Function | None = self.module.globals.get('llvm.pow.f64')
if not powf:
fnty: ir.FunctionType = ir.FunctionType(ir.DoubleType(), [ir.DoubleType(), ir.DoubleType()])
powf = ir.Function(self.module, fnty, name='llvm.pow.f64')
if lf.type != ir.DoubleType():
lf = self.builder.fpext(lf, ir.DoubleType(), name="ext_f64")
if rf.type != ir.DoubleType():
rf = self.builder.fpext(rf, ir.DoubleType(), name="ext_f64")
result: ir.Value = self.builder.call(powf, [lf, rf], name="pow")
both_int: bool = isinstance(left.type, ir.IntType) and isinstance(right.type, ir.IntType)
right_is_pos_int_const: bool = isinstance(right, ir.Constant) and isinstance(right.constant, int) and right.constant >= 0
if both_int and right_is_pos_int_const:
return self.builder.fptosi(result, left.type, name="pow2int")
if isinstance(left.type, ir.FloatType):
return self.builder.fptrunc(result, ir.FloatType(), name="pow2f32")
return result
if op in ops:
try:
return ops[op](left, right, name=op)
except Exception as e:
raise ValueError(f"{e}{self._get_node_info()}")
if op in shift_ops:
try:
return shift_ops[op](left, right, name=op)
except Exception as e:
raise ValueError(f"{e}{self._get_node_info()}")
if op in bit_ops:
try:
return bit_ops[op](left, right, name=op)
except Exception as e:
raise ValueError(f"{e}{self._get_node_info()}")
if op in cmp_ops:
try:
if is_unsigned:
return self.builder.icmp_unsigned(cmp_ops[op], left, right, name=op)
return self.builder.icmp_signed(cmp_ops[op], left, right, name=op)
except Exception as e:
raise ValueError(f"{e}{self._get_node_info()}")
if op in logic_ops:
try:
return logic_ops[op](left, right, name=op)
except Exception as e:
raise ValueError(f"{e}{self._get_node_info()}")
return left
def _to_float(self, val: ir.Value, ftype: ir.Type | None = None) -> ir.Value:
if ftype is None:
ftype = ir.DoubleType()
if isinstance(val.type, (ir.FloatType, ir.DoubleType)):
if val.type == ftype:
return val
if isinstance(val.type, ir.FloatType) and isinstance(ftype, ir.DoubleType):
return self.builder.fpext(val, ftype, name="fpext")
return self.builder.fptrunc(val, ftype, name="fptrunc")
if isinstance(val.type, ir.IntType):
if val.type.width == 1:
val = self.builder.zext(val, ir.IntType(32), name="bool2i32")
return self.builder.sitofp(val, ftype, name="int2float")
return val
def emit_return(self, value: ir.Value | None = None, Node: Any = None) -> None:
if not self.builder or self.builder.block.is_terminated:
return
if value is not None:
self._unregister_local_heap_ptr(value)
self._emit_local_heap_frees()
if self._variadic_info and self._variadic_info.get('va_start_called'):
va_list_ptr: ir.Value = self._variadic_info['va_list_ptr']
self.emit_va_end(va_list_ptr)
if value is None:
if self.func and hasattr(self.func, 'ftype'):
ret_type: ir.Type = self.func.ftype.return_type
if isinstance(ret_type, ir.IntType):
self.builder.ret(ir.Constant(ret_type, 0))
elif isinstance(ret_type, ir.PointerType):
self.builder.ret(ir.Constant(ret_type, None))
elif isinstance(ret_type, (ir.FloatType, ir.DoubleType)):
self.builder.ret(ir.Constant(ret_type, 0.0))
elif isinstance(ret_type, ir.BaseStructType):
if ret_type.elements:
zero_val: ir.Constant = ir.Constant(ret_type, [ir.Constant(et, None) if isinstance(et, (ir.PointerType, ir.IdentifiedStructType, ir.LiteralStructType, ir.ArrayType)) else ir.Constant(et, 0) if isinstance(et, (ir.IntType, ir.FloatType, ir.DoubleType)) else ir.Constant(et, ir.Undefined) for et in ret_type.elements])
else:
zero_val = ir.Constant(ret_type, None)
self.builder.ret(zero_val)
elif isinstance(ret_type, ir.ArrayType):
self.builder.ret(ir.Constant(ret_type, None))
else:
self.builder.ret_void()
else:
self.builder.ret_void()
else:
if self.func and hasattr(self.func, 'ftype'):
ret_type: ir.Type = self.func.ftype.return_type
# None(空值常量)允许隐式转换为任意返回类型:指针目标→空指针,
# 其余目标→目标类型零值(整数 0 / 浮点 0.0 / 聚合 zeroinitializer)。
if not isinstance(ret_type, ir.VoidType) and self._IsNoneValue(value, Node):
value = self._ZeroConst(ret_type)
if ret_type != value.type:
if isinstance(ret_type, ir.PointerType) and isinstance(value.type, ir.PointerType):
Allowed: bool
Reason: str
Allowed, Reason = self._ClassifyPtrImplicitConvert(value.type, ret_type)
if not Allowed:
from lib.core.LLVMCG.FuncGen import FailFastError
raise FailFastError(
f"指针隐式转换已禁止({Reason}):函数 {self.func.name} 声明返回 {ret_type},"
f"但 return 的是指针类型 {value.type};如需转换请显式 cast(如 t.CPtr(...))"
f"{self._DescribeValueNode(Node)}"
)
value = self.builder.bitcast(value, ret_type, name="ret_cast")
elif isinstance(value.type, ir.PointerType) and not isinstance(ret_type, ir.PointerType):
if isinstance(ret_type, ir.IntType):
# 指针 → 整数:隐式转换一律禁止(与 TransPyV coerce_to_type 同口径)。
# 原先"同宽 load 所指物 / 其余 ptrtoint"都是静默转换。
from lib.core.LLVMCG.FuncGen import FailFastError
raise FailFastError(
f"指针隐式转整数已禁止:函数 {self.func.name} 声明返回 {ret_type},"
f"但 return 的是指针类型 {value.type};如需转换请显式 cast(如 t.CUInt64T(p) / t.CInt(p))"
f"{self._DescribeValueNode(Node)}"
)
elif isinstance(value.type.pointee, ret_type.__class__) and not isinstance(value.type.pointee, ir.IntType):
value = self._load(value, name="ret_Load")
elif isinstance(value.type.pointee, ir.PointerType):
Loaded: ir.Value = self._load(value, name="ret_deref")
if Loaded.type == ret_type:
value = Loaded
elif isinstance(ret_type, ir.IntType):
from lib.core.LLVMCG.FuncGen import FailFastError
raise FailFastError(
f"指针隐式转整数已禁止:函数 {self.func.name} 声明返回 {ret_type},"
f"但 return 的是指针类型 {Loaded.type};如需转换请显式 cast(如 t.CUInt64T(p) / t.CInt(p))"
f"{self._DescribeValueNode(Node)}"
)
elif isinstance(ret_type, ir.IntType) and isinstance(value.type, ir.IntType):
if value.type.width < ret_type.width:
if value.type.width == 1:
# i1 是布尔值(来自 icmp/fcmp/__contains__),无符号语义,必须 zext
value = self.builder.zext(value, ret_type, name="ret_zext_bool")
else:
is_unsigned: bool = id(value) in self._unsigned_results
if is_unsigned:
value = self.builder.zext(value, ret_type, name="ret_zext")
else:
value = self.builder.sext(value, ret_type, name="ret_sext")
elif value.type.width > ret_type.width:
value = self.builder.trunc(value, ret_type, name="ret_trunc")
elif isinstance(ret_type, ir.PointerType) and isinstance(value.type, ir.IntType):
value = self.builder.inttoptr(value, ret_type, name="ret_inttoptr")
elif isinstance(ret_type, (ir.FloatType, ir.DoubleType)) and isinstance(value.type, ir.IntType):
value = self.builder.sitofp(value, ret_type, name="ret_int2float")
elif isinstance(ret_type, ir.IntType) and isinstance(value.type, (ir.FloatType, ir.DoubleType)):
value = self.builder.fptosi(value, ret_type, name="ret_float2int")
elif isinstance(ret_type, (ir.FloatType, ir.DoubleType)) and isinstance(value.type, (ir.FloatType, ir.DoubleType)):
if ret_type != value.type:
if isinstance(value.type, ir.FloatType) and isinstance(ret_type, ir.DoubleType):
value = self.builder.fpext(value, ret_type, name="ret_fpext")
elif isinstance(value.type, ir.DoubleType) and isinstance(ret_type, ir.FloatType):
value = self.builder.fptrunc(value, ret_type, name="ret_fptrunc")
self.builder.ret(value)
def _emit_printf(self, args: list[ir.Value], unsigned_flags: list[bool] | None = None, sep: str = " ", end: str = "\n") -> ir.Value | None:
if not args:
return None
if unsigned_flags is None:
unsigned_flags = []
format_str: str = ""
cast_args: list[ir.Value] = []
for i, arg in enumerate(args):
is_u: bool = i < len(unsigned_flags) and unsigned_flags[i]
if isinstance(arg.type, ir.IntType):
if arg.type.width == 1:
# i1 是布尔值(来自 icmp/fcmp/__contains__),zext 到 i32 后用 %d
format_str += "%d"
ext: ir.Value = self.builder.zext(arg, ir.IntType(32), name="bool2int")
cast_args.append(ext)
elif arg.type.width == 8:
format_str += "%c"
ext: ir.Value = self.builder.zext(arg, ir.IntType(32), name="char2int")
cast_args.append(ext)
elif arg.type.width == 64:
format_str += "%llu" if is_u else "%lld"
cast_args.append(arg)
else:
format_str += "%u" if is_u else "%d"
cast_args.append(arg)
elif isinstance(arg.type, (ir.FloatType, ir.DoubleType)):
format_str += "%f"
cast_args.append(arg)
elif isinstance(arg.type, ir.PointerType):
if isinstance(arg.type.pointee, ir.IntType) and arg.type.pointee.width == 8:
format_str += "%s"
cast_args.append(arg)
else:
format_str += "%d"
int_val: ir.Value = self.builder.ptrtoint(arg, ir.IntType(64), name="ptr2int")
trunc_val: ir.Value = self.builder.trunc(int_val, ir.IntType(32), name="trunc")
cast_args.append(trunc_val)
else:
format_str += "%d"
cast_args.append(arg)
if sep is not None and i < len(args) - 1:
format_str += sep
if end is not None:
format_str += end
string_type: ir.ArrayType = ir.ArrayType(ir.IntType(8), len(format_str.encode('utf-8')) + 1)
string_const: ir.Constant = ir.Constant(string_type, bytearray(format_str + '\x00', 'utf-8'))
global_var: ir.GlobalVariable = ir.GlobalVariable(self.module, string_type, name=f"str_const_{self.string_const_counter}")
self.string_const_counter += 1
global_var.initializer = string_const
global_var.linkage = 'internal'
format_ptr: ir.Value = self.builder.bitcast(global_var, ir.PointerType(ir.IntType(8)))
call_args: list[ir.Value] = [format_ptr] + cast_args
printf_func: ir.Function | None = self.get_or_declare_c_func('printf', ir.FunctionType(ir.IntType(32), [ir.PointerType(ir.IntType(8))], var_arg=True))
return self.builder.call(printf_func, call_args, name="call_printf")