From 5bb6538703436cfb6def59c46b4482118e3f94bf Mon Sep 17 00:00:00 2001 From: TermiNexus Date: Sat, 10 Oct 2026 21:57:18 +0800 Subject: [PATCH] =?UTF-8?q?feat(types):=20=E7=A6=81=E6=AD=A2=E9=9A=90?= =?UTF-8?q?=E5=BC=8F=E7=AA=84=E5=8C=96=EF=BC=88=E6=95=B4=E6=95=B0=20?= =?UTF-8?q?=E9=AB=98=E2=86=92=E4=BD=8E=EF=BC=89=EF=BC=9B=5Ftb=5Fread=5Fwho?= =?UTF-8?q?le=20=E5=AE=B9=E9=87=8F=E6=94=B9=20sizet?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 规则收紧(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。 --- App/lib/Phase/BuildPipeline.vp | 51 ++++++++++++++-------------- App/lib/core/Gen/Expr/ExprGenBase.vp | 27 +++++++++------ 2 files changed, 43 insertions(+), 35 deletions(-) diff --git a/App/lib/Phase/BuildPipeline.vp b/App/lib/Phase/BuildPipeline.vp index f16224c..77a90eb 100644 --- a/App/lib/Phase/BuildPipeline.vp +++ b/App/lib/Phase/BuildPipeline.vp @@ -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 diff --git a/App/lib/core/Gen/Expr/ExprGenBase.vp b/App/lib/core/Gen/Expr/ExprGenBase.vp index 70c3581..00dac01 100644 --- a/App/lib/core/Gen/Expr/ExprGenBase.vp +++ b/App/lib/core/Gen/Expr/ExprGenBase.vp @@ -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)