feat(types): 禁止隐式窄化(整数 高→低);_tb_read_whole 容量改 sizet

规则收紧(2026-10-10):整数位宽 高→低 隐式窄化一律 fail-fast(CastError),仅两类豁免:① 目标 i1 的真值归一化;② 源是编译期常量且值落进目标位宽。_scalar_convert_reason 的 int↔int 同步改为'目标位宽≥源位宽才 PC_OK'。BuildPipeline._tb_read_whole 去掉 ULONG(sz)+ULONG(2) 的窄化,改 sizet(sz)+2。
This commit is contained in:
TermiNexus committed 2026-10-10 21:57:18 +08:00
1 parent 9db3748f68
commit 5bb6538703
2 files changed
+43 -35

No files matched your search

+26 -25
View File
@@ -7,11 +7,11 @@ import stdlib
import memhub
import viperlib
import vthreading
import w32.fileio as fileio
import w32.win32file
import w32.win32base
import w32.win32process as win32process
import w32.win32sync as win32sync
import win32.fileio as fileio
import win32.win32file
import win32.win32base
import win32.win32process as win32process
import win32.win32sync as win32sync
import subprocess
import viperlib
import ast
@@ -280,8 +280,8 @@ def ensure_dir(path: str) -> int:
# Hot path (PROF: ensure_dir was 4.5%): if the final dir exists, return success early,
# saving an alloc + N+1 CreateDirectoryA calls. Semantics unchanged (exists == success).
attr: ULONG = w32.win32file.GetFileAttributesA(path)
if attr != 0xFFFFFFFF and (attr & w32.win32file.FILE_ATTRIBUTE_DIRECTORY) != 0: return 0
attr: ULONG = win32.win32file.GetFileAttributesA(path)
if attr != 0xFFFFFFFF and (attr & win32.win32file.FILE_ATTRIBUTE_DIRECTORY) != 0: return 0
# copy the path into a writable buffer (for stepwise truncation)
buf: bytes = _mbuddy.alloc(path_len + 1)
@@ -296,11 +296,11 @@ def ensure_dir(path: str) -> int:
if ch == 47 or ch == 92: # '/' = 47, '\\' = 92
saved: int = ch
buf[i] = '\0'
w32.win32file.CreateDirectoryA(buf, None)
win32.win32file.CreateDirectoryA(buf, None)
buf[i] = saved
# create the final directory
w32.win32file.CreateDirectoryA(buf, None)
win32.win32file.CreateDirectoryA(buf, None)
return 0
@@ -567,15 +567,15 @@ def _run_cmd_wait(cmd: str) -> int:
cmd_line: bytes = stdlib.malloc(cl_len + 1)
if cmd_line is None: return -1
string.strcpy(cmd_line, cmd)
sa: w32.win32base.SECURITY_ATTRIBUTES = w32.win32base.SECURITY_ATTRIBUTES()
sa: win32.win32base.SECURITY_ATTRIBUTES = win32.win32base.SECURITY_ATTRIBUTES()
si: win32process.STARTUPINFOA = win32process.STARTUPINFOA()
pi: win32process.PROCESS_INFORMATION = win32process.PROCESS_INFORMATION()
# T() only allocas, no memset: unset STARTUPINFOA fields are stack garbage, so concurrent
# threads get random dwFlags and CreateProcessA uses junk stdio handles. Clear si/sa/pi.
string.memset(&si, 0, win32process.STARTUPINFOA.__sizeof__())
string.memset(&sa, 0, w32.win32base.SECURITY_ATTRIBUTES.__sizeof__())
string.memset(&sa, 0, win32.win32base.SECURITY_ATTRIBUTES.__sizeof__())
string.memset(&pi, 0, win32process.PROCESS_INFORMATION.__sizeof__())
sa.nLength = w32.win32base.SECURITY_ATTRIBUTES.__sizeof__()
sa.nLength = win32.win32base.SECURITY_ATTRIBUTES.__sizeof__()
sa.lpSecurityDescriptor = t.CPtr(0)
sa.bInheritHandle = 1
si.cb = win32process.STARTUPINFOA.__sizeof__()
@@ -585,15 +585,15 @@ def _run_cmd_wait(cmd: str) -> int:
# CreateProcessA failure diagnostics: read GetLastError immediately (later calls overwrite
# it). Use warning, not error (error would sys.exit in a worker thread).
if ok == 0:
cp_err: uint32 = w32.win32base.GetLastError()
cp_err: uint32 = win32.win32base.GetLastError()
VLogger.warning(f"CreateProcessA failed (GetLastError={cp_err}) external command not started: cmd={cmd}", "LLC")
stdlib.free(cmd_line)
if ok == 0: return -1
win32sync.WaitForSingleObject(pi.hProcess, w32.win32base.INFINITE)
win32sync.WaitForSingleObject(pi.hProcess, win32.win32base.INFINITE)
exit_code: uint32 = 0
win32process.GetExitCodeProcess(pi.hProcess, &exit_code)
w32.win32base.CloseHandle(pi.hProcess)
w32.win32base.CloseHandle(pi.hThread)
win32.win32base.CloseHandle(pi.hProcess)
win32.win32base.CloseHandle(pi.hThread)
return int(exit_code)
@@ -673,7 +673,7 @@ def run_llc_parallel(tasks: bytes, task_count: int, max_threads: int) -> int:
h_j: t.CPtr = t.CPtr(handles.get(batch_start + j))
wr: uint32 = win32sync.WaitForSingleObject(h_j, 600000)
if wr != 0: VLogger.error("llc thread join timeout/failure (not finished within 600 seconds)", "PhaseB")
w32.win32base.CloseHandle(h_j)
win32.win32base.CloseHandle(h_j)
# check this batch's results
for j in range(batch_n):
tsk2: LlcTask* = (LlcTask*)(uint64(tasks) + sizet(batch_start + j) * slot_size)
@@ -702,11 +702,11 @@ def run_llc_parallel(tasks: bytes, task_count: int, max_threads: int) -> int:
def _CollectObjFilesImpl(base_dir: str, bd_len: sizet, out_buf: bytes, out_pos: sizet, out_size: sizet) -> sizet:
"""Recursively scan base_dir, collect .obj paths into out_buf, return the new out_pos."""
find_data: w32.win32file.WIN32_FIND_DATAA* = _mbuddy.alloc(w32.win32file.WIN32_FIND_DATAA.__sizeof__())
find_data: win32.win32file.WIN32_FIND_DATAA* = _mbuddy.alloc(win32.win32file.WIN32_FIND_DATAA.__sizeof__())
if find_data is None:
VLogger.warning("_CollectObjFilesImpl failed to allocate find_data, fewer .obj collected", "collect")
return out_pos
string.memset(find_data, 0, w32.win32file.WIN32_FIND_DATAA.__sizeof__())
string.memset(find_data, 0, win32.win32file.WIN32_FIND_DATAA.__sizeof__())
pattern: bytes = _mbuddy.alloc(bd_len + 8)
if pattern is None:
@@ -714,9 +714,9 @@ def _CollectObjFilesImpl(base_dir: str, bd_len: sizet, out_buf: bytes, out_pos:
return out_pos
viperlib.snprintf(pattern, bd_len + 8, "%s/*", base_dir)
handle: w32.win32base.HANDLE = w32.win32file.FindFirstFileA(pattern, find_data)
handle: win32.win32base.HANDLE = win32.win32file.FindFirstFileA(pattern, find_data)
_mbuddy.free(pattern)
if handle == w32.win32base.INVALID_HANDLE_VALUE:
if handle == win32.win32base.INVALID_HANDLE_VALUE:
VLogger.warning("_CollectObjFilesImpl cannot open directory, fewer .obj collected", "collect")
return out_pos
@@ -732,7 +732,7 @@ def _CollectObjFilesImpl(base_dir: str, bd_len: sizet, out_buf: bytes, out_pos:
if is_dot == 0:
attrs: ULONG = find_data.dwFileAttributes
is_dir: int = 0
if (attrs & w32.win32file.FILE_ATTRIBUTE_DIRECTORY) != 0: is_dir = 1
if (attrs & win32.win32file.FILE_ATTRIBUTE_DIRECTORY) != 0: is_dir = 1
if is_dir != 0:
# recurse into the subdirectory
sub_dir: bytes = _mbuddy.alloc(bd_len + fname_len + 2)
@@ -752,9 +752,9 @@ def _CollectObjFilesImpl(base_dir: str, bd_len: sizet, out_buf: bytes, out_pos:
viperlib.snprintf(out_buf + out_pos, need, "%s/%s", base_dir, fname)
out_pos += bd_len + 1 + fname_len
if w32.win32file.FindNextFileA(handle, find_data) == 0: break
if win32.win32file.FindNextFileA(handle, find_data) == 0: break
w32.win32file.FindClose(handle)
win32.win32file.FindClose(handle)
return out_pos
@@ -1823,7 +1823,8 @@ def _tb_read_whole(mb: memhub.MemBuddy*, path: str) -> str:
if buf is None:
f.close()
return None
got: LONG = f.read_all(buf, ULONG(sz) + ULONG(2))
# 容量全程用 sizet(与 sz 同宽):旧写法 ULONG(sz) + ULONG(2) 会在 sz >= 4GiB 时静默回绕
got: LONG = f.read_all(buf, sizet(sz) + 2)
f.close()
if got < 0: return None
return buf
+17 -10
View File
@@ -322,8 +322,10 @@ novirtual class ExprGenBase:
d_int: int = self.get_llvm_type_bits(dst_pt)
s_flt: int = self.get_llvm_float_bits(src_pt)
d_flt: int = self.get_llvm_float_bits(dst_pt)
# int <-> int: narrowing and widening are both allowed.
if s_int != 0 and d_int != 0: return PC_OK
# int <-> int: widening is lossless; narrowing (high->low) is lossy -> forbidden.
if s_int != 0 and d_int != 0:
if d_int >= s_int: return PC_OK
return PC_LOSSY
# float <-> float: allowed.
if s_flt != 0 and d_flt != 0: return PC_OK
# int -> float: lossless only when float width >= int width.
@@ -534,14 +536,19 @@ novirtual class ExprGenBase:
# otherwise sext. trans is required so unsigned annotations resolve.
if self._zext_or_sext_val(builder.Pool, val, node, trans) != 0: return mlirlite.build_zext(builder, val, target_ty)
return mlirlite.build_sext(builder, val, target_ty)
# Narrowing diagnostic (_Type.md issue 8): a known constant exceeding the
# target unsigned range fails fast; negative constants wrap (mod 2^bits).
if val.IsConst == 1 and val.IntVal > 0:
target_max_u: uint64 = (uint64(1) << target_bits) - 1
if uint64(val.IntVal) > target_max_u:
max_print: int64 = int64(target_max_u)
self.Gen.TypeGenErr.fatal_error(node, f"narrowing truncation overflow: constant {val.IntVal} exceeds the {target_bits}-bit range [0, {max_print}] of the target type, the assignment would be silently truncated", "OverflowError")
return mlirlite.build_trunc(builder, val, target_ty)
# 隐式窄化**禁止**(规则 2026-10-10 收紧,取代"整数↔整数 所有方向都允许"):
# 位宽高→低会丢高位,必须显式 cast 或加宽目标类型。仅两类豁免:
# ① 目标 i1:真值归一化(x != 0),语义明确,不是位截断(已在上方返回);
# ② 源是**编译期常量且值能落进目标位宽**(如字面量 5 → i32),无信息损失。
if val.IsConst == 1:
iv: int64 = val.IntVal
lo_c: int64 = -(int64(1) << (target_bits - 1))
hi_c: int64 = (int64(1) << target_bits) - 1
if iv < lo_c or iv > hi_c:
self.Gen.TypeGenErr.fatal_error(node, f"narrowing truncation overflow: constant {iv} is outside the {target_bits}-bit representable range [{lo_c}, {hi_c}]; use an explicit cast or widen the target type", "OverflowError")
return mlirlite.build_trunc(builder, val, target_ty)
self.Gen.TypeGenErr.fatal_error(node, f"implicit narrowing is forbidden: a {val_bits}-bit value cannot be implicitly assigned to a {target_bits}-bit type (the high bits would be lost); use an explicit cast (e.g. int32(x) / t.CInt32T(x)) or widen the target type", "CastError")
return val
# Float conversion.
val_fbits: int = self.get_llvm_float_bits(val.Ty)
target_fbits: int = self.get_llvm_float_bits(target_ty)