diff --git a/lib/core/Handles/HandlesExprBuiltin.py b/lib/core/Handles/HandlesExprBuiltin.py index a603bd9..5825804 100644 --- a/lib/core/Handles/HandlesExprBuiltin.py +++ b/lib/core/Handles/HandlesExprBuiltin.py @@ -13,6 +13,15 @@ from lib.includes import t as _t from lib.core.SymbolUtils import IsTModule +# len() 参数形态不支持时的共享提示语(与 TransPyV App/lib/core/Gen/Call/ +# CallGenDispatch.vp 的 len() 分支同口径,措辞逐字一致,便于用户对照两侧报文)。 +_LEN_SUPPORTED_HINT: str = ( + "len() 只支持定长数组/数组指针(t.CArray[T, N])、i8*(str/bytes)或带 __len__ 的结构体;" + "非 i8 裸指针/标量指针没有长度语义:字符串请用 str/bytes,需要元素个数请自行记录长度," + "或改用 t.CPtr[元素] 并显式维护元素计数" +) + + class ExprBuiltinHandle(BaseHandle): _C_LIB_FUNCS: dict[str, object] = { 'strlen': lambda: (ir.IntType(64), [ir.PointerType(ir.IntType(8))]), @@ -571,6 +580,41 @@ class ExprBuiltinHandle(BaseHandle): Gen.functions[func_name] = func return func + def _FailFastLenUnsupported(self, arg_node: ast.AST, Gen: LlvmGeneratorMixin, + val: ir.Value | None, reason: str) -> None: + """len(x) 参数形态不支持时的编译期 fail-fast 出口(TPC 与 TransPyV 同口径)。 + + 规则(对齐 TransPyV App/lib/core/Gen/Call/CallGenDispatch.vp 的 len() 分支): + 定长数组/数组指针 → 常量 N;i8*(str/bytes)→ strlen; + 带 __len__ 的结构体 → 调用 __len__;其余一律编译期报错。 + 非 i8 裸指针(如 `[]T` 形参实为 `T*`)与标量指针**没有长度语义**,旧实现会 + 静默返回常量 0(或回落 strlen)从而掩盖错误,这里改为响亮失败。 + 报文含 行:列 + 源码片段 + 期望形态 + 正确做法提示。 + """ + raw_lineno: int = getattr(arg_node, 'lineno', 0) or 0 + lineno: int = raw_lineno or getattr(Gen, '_current_lineno', 0) or 0 + col: int = getattr(arg_node, 'col_offset', -1) + src_file: str = getattr(Gen, '_current_source_file', '') or '' + _line_offset: int = int(getattr(self.Trans, '_line_offset', 0) or 0) + if _line_offset > 0 and lineno > _line_offset: + lineno -= _line_offset + code_line: str = '' + if lineno > 0 and hasattr(Gen, '_get_source_line'): + try: + code_line = (Gen._get_source_line(lineno) or '').strip() + except Exception: + code_line = '' + loc: str = src_file + if lineno > 0: + loc += f":{lineno}" + if col >= 0: + loc += f":{col}" + actual: str = str(val.type) if val is not None else '<未知>' + msg: str = (f"[fast-fail] {reason}。{_LEN_SUPPORTED_HINT}。" + f"实际类型 '{actual}'。位置 {loc}" + + (f",源码: {code_line}" if code_line else '')) + raise TypeError(msg) + def _HandleLenLlvm(self, arg_node: ast.AST) -> ir.Value | None: Gen: LlvmGeneratorMixin = self.Trans.LlvmGen # len(obj.attr):obj 可为 self 或任意局部变量(VpiReader 的 @@ -657,37 +701,51 @@ class ExprBuiltinHandle(BaseHandle): val = Gen._load(val, name="len_deref") pointee = val.type.pointee if isinstance(pointee, (ir.IdentifiedStructType, ir.LiteralStructType)): + # struct(或任何非基本类型)→ 仍先走既有 __len__ 分派(保持不变); + # 没有 __len__ 时由旧实现的「回落 strlen」改为 fail-fast(与 TPV 同口径)。 + _matched_cn: str | None = None for CN, ST in Gen.structs.items(): if pointee == ST: - result = self.Trans.ExprUtils._try_operator_overLoad(CN, '__len__', val, Gen) - if result is not None: - return result + _matched_cn = CN break + if _matched_cn is not None: + result = self.Trans.ExprUtils._try_operator_overLoad(_matched_cn, '__len__', val, Gen) + if result is not None: + return result + self._FailFastLenUnsupported(arg_node, Gen, val, f"len() 参数类型 '{_matched_cn}' 没有 __len__ 方法") + else: + # 未在 Gen.structs 登记的 struct(opaque/匿名)→ fail-fast。 + self._FailFastLenUnsupported(arg_node, Gen, val, "len() 参数类型不支持(非字符串/数组/对象)") if isinstance(pointee, ir.ArrayType): return ir.Constant(ir.IntType(64), pointee.count) - if isinstance(pointee, (ir.FloatType, ir.DoubleType, ir.IntType)) and pointee.width != 8: - return ir.Constant(ir.IntType(64), 0) - if val.type != ir.PointerType(ir.IntType(8)): - val = Gen.builder.bitcast(val, ir.PointerType(ir.IntType(8)), name="str_ptr_cast") - correct_strlen_type = ir.FunctionType(ir.IntType(64), [ir.PointerType(ir.IntType(8))]) - strlen_func = None - if 'strlen' in Gen.functions: - existing = Gen.functions['strlen'] - if existing.ftype == correct_strlen_type: - strlen_func = existing - if not strlen_func: - try: - strlen_func = ir.Function(Gen.module, correct_strlen_type, name='strlen') - Gen.functions['strlen'] = strlen_func - except Exception as _e: - if _config_mode == "strict": - self.Trans.LogWarning(f"异常被忽略: {_e}") - if strlen_func: - result = Gen.builder.call(strlen_func, [val], name="strlen_call") - return result + # i8*(含 str/bytes 指针形态)→ strlen(保持不变)。 + if isinstance(pointee, ir.IntType) and pointee.width == 8: + if val.type != ir.PointerType(ir.IntType(8)): + val = Gen.builder.bitcast(val, ir.PointerType(ir.IntType(8)), name="str_ptr_cast") + correct_strlen_type = ir.FunctionType(ir.IntType(64), [ir.PointerType(ir.IntType(8))]) + strlen_func = None + if 'strlen' in Gen.functions: + existing = Gen.functions['strlen'] + if existing.ftype == correct_strlen_type: + strlen_func = existing + if not strlen_func: + try: + strlen_func = ir.Function(Gen.module, correct_strlen_type, name='strlen') + Gen.functions['strlen'] = strlen_func + except Exception as _e: + if _config_mode == "strict": + self.Trans.LogWarning(f"异常被忽略: {_e}") + if strlen_func: + result = Gen.builder.call(strlen_func, [val], name="strlen_call") + return result + return None + # 非 i8 裸指针 / 标量指针(i32*/i64*/float*/double*)→ fail-fast + # (旧实现静默返回常量 0 或回落 strlen,与 TPV 口径不符)。 + self._FailFastLenUnsupported(arg_node, Gen, val, "len() 参数类型不支持(非字符串/数组/对象)") elif isinstance(val.type, ir.ArrayType): return ir.Constant(ir.IntType(64), val.type.count) - return ir.Constant(ir.IntType(64), 0) + # 其它不支持形态(标量值 / 结构体值等)→ fail-fast(旧实现静默返回常量 0)。 + self._FailFastLenUnsupported(arg_node, Gen, val, "len() 参数类型不支持(非字符串/数组/对象)") def _resolve_nested_opaque_structs(self, struct_type: ir.Type, Gen: LlvmGeneratorMixin, visited: set | None = None) -> None: """递归解析嵌套的 opaque struct,从 stub 文件中加载其定义""" diff --git a/lib/core/Handles/HandlesExprCall.py b/lib/core/Handles/HandlesExprCall.py index 9477d24..c6b72dd 100644 --- a/lib/core/Handles/HandlesExprCall.py +++ b/lib/core/Handles/HandlesExprCall.py @@ -19,6 +19,15 @@ from lib.core.SymbolUtils import ( from lib.core.Handles.HandlesExpr import DefineUseWidthFromLLVMType +# len() 参数形态不支持时的共享提示语(与 TransPyV App/lib/core/Gen/Call/ +# CallGenDispatch.vp 的 len() 分支同口径,措辞逐字一致,便于用户对照两侧报文)。 +_LEN_SUPPORTED_HINT: str = ( + "len() 只支持定长数组/数组指针(t.CArray[T, N])、i8*(str/bytes)或带 __len__ 的结构体;" + "非 i8 裸指针/标量指针没有长度语义:字符串请用 str/bytes,需要元素个数请自行记录长度," + "或改用 t.CPtr[元素] 并显式维护元素计数" +) + + def _safe_ptr_type(ty: ir.Type) -> ir.Type: """LLVM IR 中不允许 void* 指针,Ptr(VoidType) 统一替换为 i8*(对应 C 的 void* 语义)""" if isinstance(ty, ir.PointerType): @@ -3888,6 +3897,41 @@ class ExprCallHandle(BaseHandle): Gen.functions[func_name] = func return func + def _FailFastLenUnsupported(self, arg_node: ast.AST, Gen: LlvmGeneratorMixin, + val: ir.Value | None, reason: str) -> None: + """len(x) 参数形态不支持时的编译期 fail-fast 出口(TPC 与 TransPyV 同口径)。 + + 规则(对齐 TransPyV App/lib/core/Gen/Call/CallGenDispatch.vp 的 len() 分支): + 定长数组/数组指针 → 常量 N;i8*(str/bytes)→ strlen; + 带 __len__ 的结构体 → 调用 __len__;其余一律编译期报错。 + 非 i8 裸指针(如 `[]T` 形参实为 `T*`)与标量指针**没有长度语义**,旧实现会 + 静默返回常量 0(或回落 strlen)从而掩盖错误,这里改为响亮失败。 + 报文含 行:列 + 源码片段 + 期望形态 + 正确做法提示。 + """ + raw_lineno: int = getattr(arg_node, 'lineno', 0) or 0 + lineno: int = raw_lineno or getattr(Gen, '_current_lineno', 0) or 0 + col: int = getattr(arg_node, 'col_offset', -1) + src_file: str = getattr(Gen, '_current_source_file', '') or '' + _line_offset: int = int(getattr(self.Trans, '_line_offset', 0) or 0) + if _line_offset > 0 and lineno > _line_offset: + lineno -= _line_offset + code_line: str = '' + if lineno > 0 and hasattr(Gen, '_get_source_line'): + try: + code_line = (Gen._get_source_line(lineno) or '').strip() + except Exception: + code_line = '' + loc: str = src_file + if lineno > 0: + loc += f":{lineno}" + if col >= 0: + loc += f":{col}" + actual: str = str(val.type) if val is not None else '<未知>' + msg: str = (f"[fast-fail] {reason}。{_LEN_SUPPORTED_HINT}。" + f"实际类型 '{actual}'。位置 {loc}" + + (f",源码: {code_line}" if code_line else '')) + raise TypeError(msg) + def _HandleLenLlvm(self, arg_node: ast.AST) -> ir.Value | None: Gen = self.Trans.LlvmGen if isinstance(arg_node, ast.Attribute) and isinstance(arg_node.value, ast.Name) and arg_node.value.id == 'self': @@ -3949,37 +3993,51 @@ class ExprCallHandle(BaseHandle): val = Gen._load(val, name="len_deref") pointee = val.type.pointee if isinstance(pointee, (ir.IdentifiedStructType, ir.LiteralStructType)): + # struct(或任何非基本类型)→ 仍先走既有 __len__ 分派(保持不变); + # 没有 __len__ 时由旧实现的「回落 strlen」改为 fail-fast(与 TPV 同口径)。 + _matched_cn: str | None = None for CN, ST in Gen.structs.items(): if pointee == ST: - result = self.Trans.ExprUtils._try_operator_overLoad(CN, '__len__', val, Gen) - if result is not None: - return result + _matched_cn = CN break + if _matched_cn is not None: + result = self.Trans.ExprUtils._try_operator_overLoad(_matched_cn, '__len__', val, Gen) + if result is not None: + return result + self._FailFastLenUnsupported(arg_node, Gen, val, f"len() 参数类型 '{_matched_cn}' 没有 __len__ 方法") + else: + # 未在 Gen.structs 登记的 struct(opaque/匿名)→ fail-fast。 + self._FailFastLenUnsupported(arg_node, Gen, val, "len() 参数类型不支持(非字符串/数组/对象)") if isinstance(pointee, ir.ArrayType): return ir.Constant(ir.IntType(64), pointee.count) - if isinstance(pointee, (ir.FloatType, ir.DoubleType, ir.IntType)) and pointee.width != 8: - return ir.Constant(ir.IntType(64), 0) - if val.type != ir.PointerType(ir.IntType(8)): - val = Gen.builder.bitcast(val, ir.PointerType(ir.IntType(8)), name="str_ptr_cast") - correct_strlen_type = ir.FunctionType(ir.IntType(64), [ir.PointerType(ir.IntType(8))]) - strlen_func = None - if 'strlen' in Gen.functions: - existing = Gen.functions['strlen'] - if existing.ftype == correct_strlen_type: - strlen_func = existing - if not strlen_func: - try: - strlen_func = ir.Function(Gen.module, correct_strlen_type, name='strlen') - Gen.functions['strlen'] = strlen_func - except Exception as _e: - if _config_mode == "strict": - self.Trans.LogWarning(f"异常被忽略: {_e}") - if strlen_func: - result = Gen.builder.call(strlen_func, [val], name="strlen_call") - return result + # i8*(含 str/bytes 指针形态)→ strlen(保持不变)。 + if isinstance(pointee, ir.IntType) and pointee.width == 8: + if val.type != ir.PointerType(ir.IntType(8)): + val = Gen.builder.bitcast(val, ir.PointerType(ir.IntType(8)), name="str_ptr_cast") + correct_strlen_type = ir.FunctionType(ir.IntType(64), [ir.PointerType(ir.IntType(8))]) + strlen_func = None + if 'strlen' in Gen.functions: + existing = Gen.functions['strlen'] + if existing.ftype == correct_strlen_type: + strlen_func = existing + if not strlen_func: + try: + strlen_func = ir.Function(Gen.module, correct_strlen_type, name='strlen') + Gen.functions['strlen'] = strlen_func + except Exception as _e: + if _config_mode == "strict": + self.Trans.LogWarning(f"异常被忽略: {_e}") + if strlen_func: + result = Gen.builder.call(strlen_func, [val], name="strlen_call") + return result + return None + # 非 i8 裸指针 / 标量指针(i32*/i64*/float*/double*)→ fail-fast + # (旧实现静默返回常量 0 或回落 strlen,与 TPV 口径不符)。 + self._FailFastLenUnsupported(arg_node, Gen, val, "len() 参数类型不支持(非字符串/数组/对象)") elif isinstance(val.type, ir.ArrayType): return ir.Constant(ir.IntType(64), val.type.count) - return ir.Constant(ir.IntType(64), 0) + # 其它不支持形态(标量值 / 结构体值等)→ fail-fast(旧实现静默返回常量 0)。 + self._FailFastLenUnsupported(arg_node, Gen, val, "len() 参数类型不支持(非字符串/数组/对象)") def _resolve_opaque_struct_members(self, struct_type: ir.Type, Gen: LlvmGeneratorMixin, _visited: set | None = None) -> None: """Recursively resolve opaque struct members by loading from stub files."""