docs(tpv): R11 注释与 docstring 英文化并压缩到 2 行内
按 wiki/新规范:注释只讲作用、不掺杂历史原因、避免超长、尽可能英文(含三引号 __doc__)。全文 79 个 .vp:# 注释 16269 行(中文 13808)与 docstring(中文 1381 处)全部改英文;超 2 行的注释块(2093)与 docstring(397)全部压到 2 行。运行时可见文本未动(fatal_error/VLogger 的中文报错文案、stdio.printf 的横幅格式串)。校验:屏蔽注释与三引号后逐行比对各文件代码骨架,0 漂移。验证:自举重建 153.48s 通过(跨模块内联完成);TPV 顺序回归 77/77 全通过。
This commit is contained in:
1 parent
a2874a14e6
commit
0f4aeeceff
79 files changed
+10090
-20352
No files matched your search
+88
-180
@@ -11,57 +11,42 @@ from json import JsonValue, JSON_OBJECT, JSON_INT
|
||||
import w32.fileio as fileio
|
||||
|
||||
|
||||
# ============================================================
|
||||
# Config - 工程配置
|
||||
# ============================================================
|
||||
# Config - project configuration
|
||||
|
||||
# 全局 mbuddy 指针
|
||||
# Global mbuddy pointer
|
||||
_mbuddy: rawptr[memhub.MemManager]
|
||||
|
||||
# 配置值(从 project.vpj 加载);默认 None/0:未加载时字段为 null,调用方须 is not None 检查兜底(否则垃圾指针 → 崩溃)
|
||||
# Config values (loaded from project.vpj); default None/0: callers must guard with `is not None` (else garbage pointer crash)
|
||||
SourceDir: str = None
|
||||
BuildDir: str = None
|
||||
TempDir: str = None # = {BuildDir}/temp(自动计算)
|
||||
OutputDir: str = None # = {BuildDir}/output(自动计算)
|
||||
TempDir: str = None # = {BuildDir}/temp (auto-computed)
|
||||
OutputDir: str = None # = {BuildDir}/output (auto-computed)
|
||||
ProjectName: str = None
|
||||
ProjectVersion: str = None
|
||||
CompilerCmd: str = None
|
||||
CompilerFlags: str = None # 编译器参数(空格分隔,从 compiler.flags 数组拼接)
|
||||
CompilerFlags: str = None # compiler flags (space-separated, joined from compiler.flags array)
|
||||
LinkerCmd: str = None
|
||||
LinkerFlags: str = None # 链接器参数(空格分隔,从 linker.flags 数组拼接)
|
||||
LinkerFlags: str = None # linker flags (space-separated, joined from linker.flags array)
|
||||
LinkerOutput: str = None
|
||||
LinkerStaticLibs: str = None # 显式静态链接文件(空格分隔绝对路径,从 linker.static_libs 数组)
|
||||
LinkerDynamicLibs: str = None # 显式动态链接文件(空格分隔绝对路径,从 linker.dynamic_libs 数组,.dll/.so)
|
||||
LinkerStaticLibs: str = None # explicit static libs (space-separated abs paths, from linker.static_libs array)
|
||||
LinkerDynamicLibs: str = None # explicit dynamic libs (space-separated abs paths, from linker.dynamic_libs array, .dll/.so)
|
||||
TargetTriple: str = None
|
||||
TargetDataLayout: str = None
|
||||
ProjectDir: str = None # project.vpj 所在目录(resolve_paths 设置)
|
||||
Sha1SliceLevel: int = 0 # SHA1 切片层数(目录分散等级)
|
||||
ProjectDir: str = None # directory containing project.vpj (set by resolve_paths)
|
||||
Sha1SliceLevel: int = 0 # SHA1 slice level (directory dispersion depth)
|
||||
StrictMode: int = 0
|
||||
IncludesDir: str = None # includes 目录路径(从 includes 数组首元素,兼容单目录场景)
|
||||
IncludesDirs: str = None # 全部 includes 目录(空格分隔,多 includes 目录支持)
|
||||
IncludesDir: str = None # includes dir path (first element of includes array, single-dir compat)
|
||||
IncludesDirs: str = None # all includes dirs (space-separated, multi-dir support)
|
||||
|
||||
# includes 预编译缓存目录后缀(asan 模式用 .binary_asan 隔离 asan 插桩 obj,避免与非 asan obj 混链)
|
||||
# includes prebuilt cache dir suffix (asan mode uses .binary_asan to isolate instrumented obj)
|
||||
_inc_bin_suffix: str = ".binary"
|
||||
|
||||
# ============================================================
|
||||
# 发射后端:**单端 MLIR**(2026-10-03 起)
|
||||
#
|
||||
# 全树发射器已由 includes/llvmlite 改为 includes/mlirlite(`import mlirlite`),
|
||||
# 不再有 `--backend` 开关、不再有模块别名:mlirlite 发射的是 MLIR 文本,
|
||||
# 故 BuildPipeline 恒在 opt/llc 之前插入
|
||||
# mlir-opt(校验)→ mlir-translate --mlir-to-llvmir(降级回 LLVM IR)。
|
||||
# ============================================================
|
||||
# Emission backend: single-track MLIR (since 2026-10-03).
|
||||
# The tree now uses includes/mlirlite; BuildPipeline always runs mlir-opt -> mlir-translate to lower back to LLVM IR.
|
||||
|
||||
|
||||
def Load_project_config(path: str) -> int:
|
||||
"""加载工程配置文件(project.vpj)
|
||||
|
||||
Args:
|
||||
path: 配置文件路径
|
||||
|
||||
Returns:
|
||||
0 表示成功,非 0 表示失败
|
||||
"""
|
||||
"""Load the project config file (project.vpj); 0=success, non-zero=failure."""
|
||||
global SourceDir, BuildDir, TempDir, OutputDir, ProjectName, ProjectVersion
|
||||
global CompilerCmd, CompilerFlags, LinkerCmd, LinkerFlags, LinkerOutput, LinkerStaticLibs, LinkerDynamicLibs
|
||||
global TargetTriple, TargetDataLayout, Sha1SliceLevel, StrictMode
|
||||
@@ -70,12 +55,12 @@ def Load_project_config(path: str) -> int:
|
||||
if path is None: return 1
|
||||
if _mbuddy is None: return 1
|
||||
|
||||
# 打开文件
|
||||
# Open file
|
||||
f: rawptr[fileio.File] = fileio.File(path, fileio.MODE.R)
|
||||
if f is None: return 1
|
||||
if f.closed: return 1
|
||||
|
||||
# 分配读取缓冲区
|
||||
# Allocate read buffer
|
||||
CFG_BUF_SIZE: sizet = 8192
|
||||
buf: bytes = _mbuddy.alloc(CFG_BUF_SIZE)
|
||||
if buf is None:
|
||||
@@ -87,15 +72,15 @@ def Load_project_config(path: str) -> int:
|
||||
|
||||
if bytes_read <= 0: return 1
|
||||
|
||||
# 解析 JSON
|
||||
# Parse JSON
|
||||
root: JsonValue* = json_parse(_mbuddy, buf)
|
||||
if root is None: return 1
|
||||
if not root.is_object(): return 1
|
||||
|
||||
# 读取顶层字段(使用显式 __getitem__ 调用,兼容旧编译器二进制)
|
||||
# Read top-level fields (explicit __getitem__ calls for old compiler binaries)
|
||||
sd_val: JsonValue* = root["source_dir"]
|
||||
SourceDir = sd_val.as_string() if sd_val is not None else None
|
||||
# build_dir: 统一构建目录(默认 ./.tpv_build),temp 和 output 作为其子目录
|
||||
# build_dir: unified build dir (default ./.tpv_build); temp and output are subdirs
|
||||
bd_val: JsonValue* = root["build_dir"]
|
||||
if bd_val is not None and bd_val.is_string(): BuildDir = bd_val.as_string()
|
||||
else: BuildDir = "./.tpv_build"
|
||||
@@ -104,35 +89,35 @@ def Load_project_config(path: str) -> int:
|
||||
ver_val: JsonValue* = root["version"]
|
||||
ProjectVersion = ver_val.as_string() if ver_val is not None else None
|
||||
|
||||
# 读取 compiler 子对象
|
||||
# Read compiler sub-object
|
||||
compiler: JsonValue* = root["compiler"]
|
||||
if compiler is not None and compiler.is_object():
|
||||
cc_cmd_val: JsonValue* = compiler["cmd"]
|
||||
CompilerCmd = cc_cmd_val.as_string() if cc_cmd_val is not None else None
|
||||
# 读取 compiler.flags 数组,拼接成空格分隔的字符串
|
||||
# Read compiler.flags array, join into a space-separated string
|
||||
cc_flags_val: JsonValue* = compiler["flags"]
|
||||
CompilerFlags = _join_flags_array(cc_flags_val)
|
||||
|
||||
# 读取 linker 子对象
|
||||
# Read linker sub-object
|
||||
linker: JsonValue* = root["linker"]
|
||||
if linker is not None and linker.is_object():
|
||||
ld_cmd_val: JsonValue* = linker["cmd"]
|
||||
LinkerCmd = ld_cmd_val.as_string() if ld_cmd_val is not None else None
|
||||
ld_out_val: JsonValue* = linker["output"]
|
||||
LinkerOutput = ld_out_val.as_string() if ld_out_val is not None else None
|
||||
# 读取 linker.flags 数组,拼接成空格分隔的字符串
|
||||
# Read linker.flags array, join into a space-separated string
|
||||
ld_flags_val: JsonValue* = linker["flags"]
|
||||
LinkerFlags = _join_flags_array(ld_flags_val)
|
||||
# 读取 linker.static_libs 数组:显式静态链接文件(.a/.obj),
|
||||
# 先拼接原样字符串,resolve_paths 中再基于 project_dir 解析绝对路径
|
||||
# Read linker.static_libs array: explicit static libs (.a/.obj); joined raw here,
|
||||
# then resolved to abs paths against project_dir in resolve_paths.
|
||||
sl_val: JsonValue* = linker["static_libs"]
|
||||
LinkerStaticLibs = _join_flags_array(sl_val)
|
||||
# 读取 linker.dynamic_libs 数组:显式动态链接文件(.dll/.so),
|
||||
# 同样先拼接原样字符串,resolve_paths 中基于 project_dir 解析绝对路径
|
||||
# Read linker.dynamic_libs array: explicit dynamic libs (.dll/.so); joined raw here,
|
||||
# then resolved to abs paths against project_dir in resolve_paths.
|
||||
dl_val: JsonValue* = linker["dynamic_libs"]
|
||||
LinkerDynamicLibs = _join_flags_array(dl_val)
|
||||
|
||||
# 读取 target 子对象
|
||||
# Read target sub-object
|
||||
target: JsonValue* = root["target"]
|
||||
if target is not None and target.is_object():
|
||||
tgt_triple_val: JsonValue* = target["triple"]
|
||||
@@ -140,8 +125,8 @@ def Load_project_config(path: str) -> int:
|
||||
tgt_dl_val: JsonValue* = target["datalayout"]
|
||||
TargetDataLayout = tgt_dl_val.as_string() if tgt_dl_val is not None else None
|
||||
|
||||
# 读取 options 子对象
|
||||
# Sha1SliceLevel: SHA1 切片层数(默认 1,每层取 SHA1 前 2 字符作为子目录:0=不切片, 1=b7/, 2=b7/90/)
|
||||
# Read options sub-object
|
||||
# Sha1SliceLevel: SHA1 slice level (default 1; each level uses 2 SHA1 chars: 0=none, 1=b7/, 2=b7/90/)
|
||||
options: JsonValue* = root["options"]
|
||||
Sha1SliceLevel = 1
|
||||
StrictMode = 1
|
||||
@@ -151,7 +136,7 @@ def Load_project_config(path: str) -> int:
|
||||
sm: JsonValue* = options["strict_mode"]
|
||||
if sm is not None and sm.is_bool(): StrictMode = 1 if sm.as_bool() else 0
|
||||
|
||||
# 读取 includes 数组(支持多目录:首元素存 IncludesDir,全部拼接存 IncludesDirs)
|
||||
# Read includes array (multi-dir: first element -> IncludesDir, all joined -> IncludesDirs)
|
||||
includes_val: JsonValue* = root["includes"]
|
||||
if includes_val is not None and includes_val.is_array():
|
||||
arr_len: sizet = len(includes_val)
|
||||
@@ -165,7 +150,7 @@ def Load_project_config(path: str) -> int:
|
||||
inc_str: str = inc_item.as_string()
|
||||
inc_slen: sizet = string.strlen(inc_str)
|
||||
if inc_i == 0: IncludesDir = inc_str
|
||||
# 拼接进 IncludesDirs(空格分隔)
|
||||
# Append into IncludesDirs (space-separated)
|
||||
if inc_pos + inc_slen + 1 < 2048:
|
||||
if inc_pos > 0:
|
||||
inc_buf[inc_pos] = ' '
|
||||
@@ -180,24 +165,14 @@ def Load_project_config(path: str) -> int:
|
||||
|
||||
|
||||
def _join_flags_array(flags_val: JsonValue*) -> str:
|
||||
"""将 JSON 字符串数组拼接成空格分隔的字符串
|
||||
|
||||
用于 linker.flags 和 compiler.flags 数组。
|
||||
用 while + 索引遍历,避免 list 迭代器 bug。
|
||||
|
||||
Args:
|
||||
flags_val: JsonValue,应为字符串数组
|
||||
|
||||
Returns:
|
||||
空格分隔的字符串,或 None 如果 flags_val 为 None 或非数组或空数组
|
||||
"""
|
||||
"""Join a JSON string array into a space-separated string; None if empty/invalid."""
|
||||
if flags_val is None: return None
|
||||
if not flags_val.is_array(): return None
|
||||
|
||||
arr_len: sizet = len(flags_val)
|
||||
if arr_len == 0: return None
|
||||
|
||||
# 第一遍:计算总长度(所有 flag 长度 + 空格分隔符)
|
||||
# First pass: compute total length (all flags + space separators)
|
||||
total_len: sizet = 0
|
||||
for i in range(arr_len):
|
||||
item: JsonValue* = flags_val.get_item(i)
|
||||
@@ -208,11 +183,11 @@ def _join_flags_array(flags_val: JsonValue*) -> str:
|
||||
|
||||
if total_len == 0: return None
|
||||
|
||||
# 分配缓冲区(total_len 已含每个 flag 后的空格,最后一个是 '\0')
|
||||
# Allocate buffer (total_len includes a trailing space per flag; last one becomes '\0')
|
||||
buf: str = _mbuddy.alloc(total_len + 1)
|
||||
if buf is None: return None
|
||||
|
||||
# 第二遍:拼接(flag 之间用空格分隔)
|
||||
# Second pass: join (flags separated by spaces)
|
||||
pos: sizet = 0
|
||||
for i in range(arr_len):
|
||||
item = flags_val.get_item(i)
|
||||
@@ -230,31 +205,15 @@ def _join_flags_array(flags_val: JsonValue*) -> str:
|
||||
|
||||
|
||||
def _join_path(rel: str, project_dir: str) -> str:
|
||||
"""将相对路径与 project_dir 拼接为绝对路径
|
||||
|
||||
- ./App + Test → Test/App
|
||||
- ./temp + Test → Test/temp
|
||||
- ../includes + Test → Test/../includes(OS 解析为 includes)
|
||||
- 绝对路径保持不变
|
||||
- None 返回 None
|
||||
|
||||
拼接结果经 _normalize_path 规范化:./Test/../includes → ./includes,
|
||||
消除 .. 段,保证路径显示干净且相对寻址正确。
|
||||
|
||||
含 %VAR% 的路径(如 repl.vpj 的 build_dir = %temp%/viperepl/.tpv_build)
|
||||
先经 fileio 路径层展开为实路径:TempDir/OutputDir 等由 BuildDir 派生,
|
||||
且后续消费者不止文件打开(ensure_dir 直接调 CreateDirectoryA、
|
||||
collect_obj_files 调 FindFirstFileA、命令串走 CreateProcessA),
|
||||
故展开必须发生在**配置路径设置层**而不是逐个调用点。
|
||||
"""
|
||||
"""Join a relative path with project_dir, expanding env vars and normalizing it."""
|
||||
if rel is None: return None
|
||||
# 环境变量展开:仅在含 '%' 时生效(fileio.ExpandEnvironmentPath 自带
|
||||
# "失败/无 '%' 返回 None" 契约),展开失败时保持原串不变
|
||||
# Env var expansion: only when '%' present (fileio.ExpandEnvironmentPath returns None on
|
||||
# failure/no '%'); on failure keep the original string unchanged.
|
||||
ep: str = fileio.ExpandEnvironmentPath(rel)
|
||||
if ep is not None: rel = ep
|
||||
# 绝对路径(以 / 或 \ 开头)保持不变
|
||||
# Absolute paths (starting with / or \) stay unchanged
|
||||
if rel[0] == '/' or rel[0] == '\\': return rel
|
||||
# 路径已含 project_dir 前缀时不再拼接,避免双份路径(./TransPyV/TransPyV/.tpv_build2);边界取到目录分隔符或串尾
|
||||
# Skip joining when rel already has the project_dir prefix (avoids double paths); boundary at separator or end
|
||||
if project_dir is not None:
|
||||
pdl: sizet = string.strlen(project_dir)
|
||||
if pdl > 0 and string.strncmp(rel, project_dir, pdl) == 0:
|
||||
@@ -263,7 +222,7 @@ def _join_path(rel: str, project_dir: str) -> str:
|
||||
rel_len: sizet = string.strlen(rel)
|
||||
dir_len: sizet = string.strlen(project_dir)
|
||||
|
||||
# 处理 ../ 或 ..\\ 前缀:拼接 project_dir/../...
|
||||
# Handle ../ or ..\ prefix: join project_dir/../...
|
||||
if rel_len >= 3 and rel[0] == '.' and rel[1] == '.':
|
||||
if rel[2] == '/' or rel[2] == '\\':
|
||||
if dir_len == 0: return rel
|
||||
@@ -273,42 +232,36 @@ def _join_path(rel: str, project_dir: str) -> str:
|
||||
viperlib.snprintf(buf, total_len, "%s/%s", project_dir, rel)
|
||||
return _normalize_path(buf)
|
||||
|
||||
# 处理 ./ 或 .\\ 前缀:拼接 project_dir + rel[2:]
|
||||
# Handle ./ or .\ prefix: join project_dir + rel[2:]
|
||||
rel_off: sizet = 0
|
||||
if rel_len >= 2 and rel[0] == '.':
|
||||
if rel[1] == '/' or rel[1] == '\\': rel_off = 2
|
||||
else:
|
||||
return rel # 不是 ./ 开头,保持不变
|
||||
return rel # not starting with ./: keep unchanged
|
||||
else:
|
||||
return rel # 不是 . 开头,保持不变
|
||||
return rel # not starting with .: keep unchanged
|
||||
|
||||
if dir_len == 0:
|
||||
# project_dir 为空(当前目录),去掉 ./ 前缀即可
|
||||
# project_dir empty (current dir): just strip the ./ prefix
|
||||
return rel + rel_off
|
||||
# 拼接: project_dir + "/" + rel[rel_off:]
|
||||
# Join: project_dir + "/" + rel[rel_off:]
|
||||
total_len = dir_len + 1 + (rel_len - rel_off) + 1
|
||||
buf = _mbuddy.alloc(total_len)
|
||||
if buf is None:
|
||||
return rel # 分配失败,返回原路径
|
||||
return rel # allocation failed: return original path
|
||||
viperlib.snprintf(buf, total_len, "%s/%s", project_dir, rel + rel_off)
|
||||
return _normalize_path(buf)
|
||||
|
||||
|
||||
def _normalize_path(path: str) -> str:
|
||||
"""规范化路径:解析 . 和 .. 段,合并多余分隔符
|
||||
|
||||
例: ./test/BuddyTest/../../includes → ./includes
|
||||
./test/BuddyTest/App → ./test/BuddyTest/App
|
||||
绝对路径(/ 或 \\ 开头,含盘符 C:)保持不变。
|
||||
返回 _mbuddy 分配的字符串;失败时返回原路径。
|
||||
"""
|
||||
"""Normalize a path by resolving . and .. segments; absolute paths pass through."""
|
||||
if path is None: return None
|
||||
plen: sizet = string.strlen(path)
|
||||
if plen == 0: return path
|
||||
# 绝对路径(/ 或 \ 开头,或盘符如 C:)不规范化
|
||||
# Absolute paths (/ or \ start, or drive letter like C:) are not normalized
|
||||
if path[0] == '/' or path[0] == '\\': return path
|
||||
if plen >= 2 and path[1] == ':': return path
|
||||
# 输出缓冲(最坏情况 = 输入长度 + 可能的前缀 ./)
|
||||
# Output buffer (worst case = input length + possible ./ prefix)
|
||||
out: str = _mbuddy.alloc(plen + 3)
|
||||
if out is None: return path
|
||||
seg_start: sizet = 0
|
||||
@@ -321,26 +274,26 @@ def _normalize_path(path: str) -> str:
|
||||
if ch == '/' or ch == '\\' or ch == '\0':
|
||||
seg_len: sizet = i - seg_start
|
||||
if seg_len > 0:
|
||||
# 单段 ".":跳过
|
||||
# Single "." segment: skip
|
||||
if seg_len == 1 and path[seg_start] == '.': pass
|
||||
# 单段 "..":弹出上一段;前导 ".." 无段可弹时必须保留(对齐 Python os.path.normpath)
|
||||
# Single ".." segment: pop previous; leading ".." with nothing to pop must be kept (like Python normpath)
|
||||
elif seg_len == 2 and path[seg_start] == '.' and path[seg_start + 1] == '.':
|
||||
# 末段是否已经是前导 ".."(独立一段:要么是开头,要么前面是分隔符)
|
||||
# Whether the last segment is already a leading ".." (standalone: at start or preceded by a separator)
|
||||
last_is_dotdot: int = 0
|
||||
if out_len >= 2 and out[out_len - 1] == '.' and out[out_len - 2] == '.':
|
||||
if out_len == 2: last_is_dotdot = 1
|
||||
elif out[out_len - 3] == '/' or out[out_len - 3] == '\\': last_is_dotdot = 1
|
||||
if out_len > 0 and last_is_dotdot == 0:
|
||||
# 回退到上一个分隔符
|
||||
# Back up to the previous separator
|
||||
k: sizet = out_len
|
||||
while k > 0 and out[k - 1] != '/' and out[k - 1] != '\\': k -= 1
|
||||
if k > 0: out_len = k - 1
|
||||
else: out_len = 0
|
||||
# 仍有剩余段时保持分隔符逻辑
|
||||
# Keep separator logic when segments remain
|
||||
if out_len > 0: has_seg = 1
|
||||
else: has_seg = 0
|
||||
else:
|
||||
# 没有普通段可弹:把 ".." 当普通段追加(前导 .. 累积保留)
|
||||
# No normal segment to pop: append ".." as a normal segment (keep leading ..)
|
||||
if has_seg > 0:
|
||||
out[out_len] = '/'
|
||||
out_len += 1
|
||||
@@ -349,7 +302,7 @@ def _normalize_path(path: str) -> str:
|
||||
out_len += 2
|
||||
has_seg = 1
|
||||
else:
|
||||
# 普通段:追加分隔符 + 段
|
||||
# Normal segment: append separator + segment
|
||||
if has_seg > 0:
|
||||
out[out_len] = '/'
|
||||
out_len += 1
|
||||
@@ -362,11 +315,11 @@ def _normalize_path(path: str) -> str:
|
||||
seg_start = i + 1
|
||||
i += 1
|
||||
if has_seg == 0:
|
||||
# 全部段被消掉(如 ./..):返回当前目录
|
||||
# All segments consumed (e.g. ./..): return current dir
|
||||
return "./"
|
||||
# 相对路径补回 ./ 前缀(与原路径风格一致;以 .. 开头的除外)
|
||||
# Re-add ./ prefix for relative paths (matches original style; except those starting with ..)
|
||||
if out[0] != '.' and out[0] != '/':
|
||||
# 右移 2 位留出 "./"(从后往前拷贝,避免覆盖源字符)
|
||||
# Shift right by 2 to make room for "./" (copy back-to-front to avoid overwriting)
|
||||
mv: sizet = out_len
|
||||
while mv > 0:
|
||||
mv -= 1
|
||||
@@ -379,17 +332,7 @@ def _normalize_path(path: str) -> str:
|
||||
|
||||
|
||||
def resolve_paths(project_dir: str) -> int:
|
||||
"""解析并规范化工程路径(src/build/includes)
|
||||
|
||||
将 project.vpj 中的相对路径(以 ./ 开头)转换为基于 project_dir 的路径。
|
||||
BuildDir 解析后,TempDir = {BuildDir}/temp,OutputDir = {BuildDir}/output。
|
||||
|
||||
Args:
|
||||
project_dir: 工程根目录(project.vpj 所在目录)
|
||||
|
||||
Returns:
|
||||
0 表示成功,非 0 表示失败
|
||||
"""
|
||||
"""Resolve and normalize project paths (src/build/includes); 0=success."""
|
||||
global SourceDir, BuildDir, TempDir, OutputDir, IncludesDir, IncludesDirs
|
||||
global ProjectDir, LinkerStaticLibs, LinkerDynamicLibs
|
||||
if project_dir is None: return 1
|
||||
@@ -400,7 +343,7 @@ def resolve_paths(project_dir: str) -> int:
|
||||
SourceDir = _join_path(SourceDir, project_dir)
|
||||
BuildDir = _join_path(BuildDir, project_dir)
|
||||
|
||||
# TempDir = {BuildDir}/temp(单端 MLIR 恒追加 _mlir 隔离后缀)
|
||||
# TempDir = {BuildDir}/temp (single-track MLIR always appends the _mlir isolation suffix)
|
||||
if BuildDir is not None:
|
||||
bd_len: sizet = string.strlen(BuildDir)
|
||||
suf_dir: str = mlir_dir_suffix()
|
||||
@@ -415,8 +358,8 @@ def resolve_paths(project_dir: str) -> int:
|
||||
viperlib.snprintf(output_buf, bd_len + suf_dir_len + 8, "%s/output%s", BuildDir, suf_dir)
|
||||
OutputDir = output_buf
|
||||
|
||||
# includes 路径可能是 ../includes 形式,需要基于 project_dir 解析
|
||||
# IncludesDir(首元素)与 IncludesDirs(全部,空格分隔)逐项规范化
|
||||
# includes paths may be of the form ../includes, resolved against project_dir.
|
||||
# Normalize IncludesDir (first element) and IncludesDirs (all, space-separated) item by item.
|
||||
if IncludesDirs is not None:
|
||||
id_total: sizet = string.strlen(IncludesDirs)
|
||||
id_out: str = _mbuddy.alloc(id_total * 3 + 64)
|
||||
@@ -446,7 +389,7 @@ def resolve_paths(project_dir: str) -> int:
|
||||
id_out[id_pos] = '\0'
|
||||
IncludesDirs = id_out
|
||||
if IncludesDir is not None: IncludesDir = _join_path(IncludesDir, project_dir)
|
||||
# linker.static_libs 显式静态链接文件路径基于 project_dir 逐段解析为绝对路径
|
||||
# linker.static_libs explicit paths resolved segment-by-segment to abs paths against project_dir
|
||||
if LinkerStaticLibs is not None:
|
||||
sl_total: sizet = string.strlen(LinkerStaticLibs)
|
||||
sl_out: str = _mbuddy.alloc(sl_total * 3 + 64)
|
||||
@@ -475,8 +418,7 @@ def resolve_paths(project_dir: str) -> int:
|
||||
sl_pos += jl
|
||||
sl_out[sl_pos] = '\0'
|
||||
LinkerStaticLibs = sl_out
|
||||
# linker.dynamic_libs 显式动态链接文件(.dll/.so)同样基于 project_dir
|
||||
# 逐段解析为绝对路径
|
||||
# linker.dynamic_libs explicit files (.dll/.so) likewise resolved to abs paths against project_dir.
|
||||
if LinkerDynamicLibs is not None:
|
||||
dl_total: sizet = string.strlen(LinkerDynamicLibs)
|
||||
dl_out: str = _mbuddy.alloc(dl_total * 3 + 64)
|
||||
@@ -505,16 +447,13 @@ def resolve_paths(project_dir: str) -> int:
|
||||
dl_pos += jl
|
||||
dl_out[dl_pos] = '\0'
|
||||
LinkerDynamicLibs = dl_out
|
||||
# linker.flags 中 -T <脚本> 的路径同样基于 project_dir 解析,否则 ld.lld 按进程 cwd 找不到脚本
|
||||
# -T <script> paths in linker.flags also resolved against project_dir, else ld.lld misses them
|
||||
_resolve_linker_t_paths(project_dir)
|
||||
return 0
|
||||
|
||||
|
||||
def _join_path_abs(rel: str, project_dir: str) -> str:
|
||||
"""将相对路径基于 project_dir 拼接为绝对路径(用于 -T 链接脚本等)
|
||||
|
||||
已为绝对路径(/ \\ 开头)或盘符形式(x:)时原样返回。
|
||||
"""
|
||||
"""Join a relative path under project_dir into an absolute path; absolutes unchanged."""
|
||||
if rel is None: return None
|
||||
rel_len: sizet = string.strlen(rel)
|
||||
if rel_len == 0: return rel
|
||||
@@ -530,12 +469,7 @@ def _join_path_abs(rel: str, project_dir: str) -> str:
|
||||
|
||||
|
||||
def _resolve_linker_t_paths(project_dir: str) -> None:
|
||||
"""将 LinkerFlags 中 `-T <相对路径>` 的链接脚本路径解析为绝对路径
|
||||
|
||||
ld.lld 的 -T 参数若是相对路径,将按 subprocess 进程 cwd 解析而找不到
|
||||
脚本。逐 token 扫描 LinkerFlags,遇 -T 其后 token 若非绝对路径则基于
|
||||
project_dir 拼接(与 resolve_paths 对其他相对路径的处理保持一致)。
|
||||
"""
|
||||
"""Resolve relative `-T <script>` paths in LinkerFlags against project_dir."""
|
||||
global LinkerFlags
|
||||
if LinkerFlags is None or project_dir is None: return
|
||||
fl_total: sizet = string.strlen(LinkerFlags)
|
||||
@@ -545,9 +479,9 @@ def _resolve_linker_t_paths(project_dir: str) -> None:
|
||||
out[0] = '\0'
|
||||
out_pos: sizet = 0
|
||||
i: sizet = 0
|
||||
prev_is_t: int = 0 # 前一 token 是否为 -T
|
||||
prev_is_t: int = 0 # whether the previous token is -T
|
||||
while i < fl_total:
|
||||
# 跳过前导空格
|
||||
# Skip leading spaces
|
||||
while i < fl_total and LinkerFlags[i] == ' ': i += 1
|
||||
if i >= fl_total: break
|
||||
start: sizet = i
|
||||
@@ -555,7 +489,7 @@ def _resolve_linker_t_paths(project_dir: str) -> None:
|
||||
tok_len: sizet = i - start
|
||||
is_t_flag: int = 0
|
||||
if tok_len == 2 and LinkerFlags[start] == '-' and LinkerFlags[start + 1] == 'T': is_t_flag = 1
|
||||
# 输出分隔符(前一 token 之后)
|
||||
# Output separator (after the previous token)
|
||||
if out_pos > 0:
|
||||
out[out_pos] = ' '
|
||||
out_pos += 1
|
||||
@@ -563,7 +497,7 @@ def _resolve_linker_t_paths(project_dir: str) -> None:
|
||||
string.strcpy(out + out_pos, "-T")
|
||||
out_pos += 2
|
||||
elif prev_is_t != 0:
|
||||
# -T 后的脚本路径:非绝对路径则基于 project_dir 拼接
|
||||
# Script path after -T: join against project_dir if not absolute
|
||||
tok_buf: str = _mbuddy.alloc(tok_len + 1)
|
||||
if tok_buf is not None:
|
||||
string.memcpy(tok_buf, LinkerFlags + start, tok_len)
|
||||
@@ -588,13 +522,7 @@ def _resolve_linker_t_paths(project_dir: str) -> None:
|
||||
|
||||
|
||||
def get_includes_binary_dir() -> str:
|
||||
"""获取 includes.binary 目录路径(includes 目录 + 缓存后缀)
|
||||
|
||||
includes 目录如 ../includes,默认返回 ../includes.binary;
|
||||
asan 模式(set_inc_bin_suffix(".binary_asan"))返回 ../includes.binary_asan,
|
||||
隔离 asan 插桩 obj,避免与非 asan obj 混链。
|
||||
再追加单端 MLIR 后缀(如 .binary_asan_mlir):隔离旧 LLVM 后端留下的缓存 obj。
|
||||
"""
|
||||
"""Return the includes binary cache dir (includes dir + cache and MLIR suffixes)."""
|
||||
if IncludesDir is None: return None
|
||||
inc_len: sizet = string.strlen(IncludesDir)
|
||||
suf_len: sizet = string.strlen(_inc_bin_suffix)
|
||||
@@ -606,32 +534,15 @@ def get_includes_binary_dir() -> str:
|
||||
return buf
|
||||
|
||||
|
||||
# ============================================================
|
||||
# 单端 MLIR 的目录隔离后缀
|
||||
#
|
||||
# 发射器固定为 mlirlite(MLIR 文本),故后缀恒为 "_mlir":TempDir/OutputDir/
|
||||
# includes 缓存都落在 *_mlir 目录,与旧 LLVM 后端留下的 temp/output/.binary
|
||||
# 缓存彻底分离(否则增量构建会复用旧的 LLVM IR .ll / obj,喂给 mlir-opt 必炸)。
|
||||
# ============================================================
|
||||
# Directory isolation suffix for single-track MLIR.
|
||||
# Emitter is always mlirlite, so the suffix is "_mlir": Temp/Output/includes caches live under *_mlir, separate from old LLVM caches.
|
||||
def mlir_dir_suffix() -> str:
|
||||
"""单端 MLIR 的目录隔离后缀(恒为 "_mlir")"""
|
||||
"""Directory isolation suffix for the MLIR backend (always "_mlir")."""
|
||||
return "_mlir"
|
||||
|
||||
|
||||
def asan_runtime_declared() -> int:
|
||||
"""project.vpj 是否已声明 ASan 运行库(1 = 声明过)
|
||||
|
||||
TPV 不再硬编码 clang64 的 libclang_rt 路径:clang 没有给
|
||||
x86_64-w64-windows-gnu 目标出 ASan 运行库,只能借用 clang64(MSVC ABI)
|
||||
的那两份(会牵进 MSVC 专有的 .drectve 链接指令),这是工程级选择,
|
||||
必须由工程自己声明。
|
||||
|
||||
三种写法都认(和 openssl 那种「写在 flags 里」的写法兼容):
|
||||
* linker.static_libs 里的 .a / .dll.a
|
||||
* linker.dynamic_libs 里的 .dll / .so
|
||||
* linker.flags 里的 -L… -l:…(如 openssl 的 -lssl -lcrypto)
|
||||
判定方式:以上任一处出现 "asan" 即视为已声明。
|
||||
"""
|
||||
"""Whether project.vpj declares an ASan runtime; 1=declared."""
|
||||
if LinkerStaticLibs is not None:
|
||||
if string.strstr(LinkerStaticLibs, "asan") is not None: return 1
|
||||
if LinkerDynamicLibs is not None:
|
||||
@@ -642,7 +553,7 @@ def asan_runtime_declared() -> int:
|
||||
|
||||
|
||||
def print_config():
|
||||
"""打印当前配置"""
|
||||
"""Print the current configuration."""
|
||||
if ProjectName is not None: VLogger.info(f"project: {ProjectName} v{ProjectVersion if ProjectVersion is not None else "?"}", "config")
|
||||
if SourceDir is not None: VLogger.info(f"source_dir: {SourceDir}", "config")
|
||||
if BuildDir is not None: VLogger.info(f"build_dir: {BuildDir}", "config")
|
||||
@@ -654,17 +565,14 @@ def print_config():
|
||||
VLogger.info(f"sha1_slice_level: {Sha1SliceLevel}", "config")
|
||||
|
||||
|
||||
# ============================================================
|
||||
# slice_subdir - 每层取 SHA1 的 2 字符作为子目录名(level=0 不切片, 1→"b7", 2→"b7/90")
|
||||
# Args: sha1(至少 2*level 字符)/ level(切片层数)
|
||||
# Returns: 子目录路径字符串(mbuddy 分配,无需释放),level<=0 时返回 None
|
||||
# ============================================================
|
||||
# slice_subdir - use 2 SHA1 chars per level as subdir names (level=0 none, 1->"b7", 2->"b7/90").
|
||||
# Args: sha1 (>= 2*level chars) / level. Returns mbuddy-allocated subdir path, or None if level<=0.
|
||||
def slice_subdir(sha1: str, level: int) -> str:
|
||||
"""根据 SHA1 和切片层数返回子目录路径"""
|
||||
"""Return the SHA1 slice subdir path for the given level."""
|
||||
if level <= 0 or sha1 is None: return None
|
||||
if _mbuddy is None: return None
|
||||
# 每层 2 字符 + 分隔符,最后无分隔符 + null
|
||||
# buf 大小: level * 2 + (level - 1) + 1 = level * 3
|
||||
# 2 chars per level + separators; no trailing separator, then null
|
||||
# buf size: level * 2 + (level - 1) + 1 = level * 3
|
||||
buf_len: sizet = sizet(level * 3)
|
||||
buf: str = _mbuddy.alloc(buf_len)
|
||||
if buf is None: return None
|
||||
|
||||
@@ -10,33 +10,23 @@ import w32.win32base
|
||||
import w32.fileio as fileio
|
||||
|
||||
|
||||
# ============================================================
|
||||
# Utils - 工程工具函数(SHA1 计算、目录清理等)
|
||||
# ============================================================
|
||||
# Utils - project utility functions (SHA1 hashing, directory cleanup)
|
||||
|
||||
# 全局 mbuddy 指针
|
||||
# Global mbuddy pointer
|
||||
_mbuddy: memhub.MemBuddy*
|
||||
|
||||
|
||||
# ============================================================
|
||||
# nibble_to_hex_char - 半字节转十六进制字符
|
||||
# ============================================================
|
||||
# nibble_to_hex_char - convert a nibble to a hex character
|
||||
def nibble_to_hex_char(n: int, uppercase: int) -> char:
|
||||
"""0-15 → '0'-'9'/'a'-'f'(uppercase=1 时用 'A'-'F')"""
|
||||
"""Convert a 0-15 nibble to a hex character (uppercase=1 uses 'A'-'F')."""
|
||||
if n < 10: return '0' + n
|
||||
if uppercase != 0: return 'A' + (n - 10)
|
||||
return 'a' + (n - 10)
|
||||
|
||||
|
||||
# ============================================================
|
||||
# bytes_to_hex - 字节序列转十六进制字符串
|
||||
# ============================================================
|
||||
# bytes_to_hex - convert a byte sequence to a hex string
|
||||
def bytes_to_hex(pool: memhub.MemBuddy*, digest: bytes, nbytes: sizet, uppercase: int, use_malloc: int) -> str:
|
||||
"""将 digest 前 nbytes 字节转为 2*nbytes 个十六进制字符(含 null 终止)
|
||||
|
||||
use_malloc=1 时用 stdlib.malloc(调用方负责 free),否则 pool.alloc。
|
||||
合并自 Utils.compute_sha1 / IncludesScanner.compute_file_sha1 的重复 hex 转换。
|
||||
"""
|
||||
"""Convert the first nbytes of digest to a null-terminated hex string."""
|
||||
if digest is None or nbytes <= 0: return None
|
||||
hex_len: sizet = nbytes * 2 + 1
|
||||
hex_buf: str = None
|
||||
@@ -52,47 +42,37 @@ def bytes_to_hex(pool: memhub.MemBuddy*, digest: bytes, nbytes: sizet, uppercase
|
||||
return hex_buf
|
||||
|
||||
|
||||
# ============================================================
|
||||
# compute_sha1 - 计算字符串的 SHA1,返回前 16 个十六进制字符(与 Projectrans.py 一致)
|
||||
# ============================================================
|
||||
# compute_sha1 - compute a string SHA1, return the first 16 hex chars
|
||||
def compute_sha1(pool: memhub.MemBuddy*, content: str) -> str:
|
||||
"""计算字符串的 SHA1,返回前 16 个十六进制字符(用于文件命名)"""
|
||||
"""Compute the SHA1 of a string, returning its first 16 hex chars."""
|
||||
if content is None or pool is None: return None
|
||||
|
||||
# 构造 sha1 对象(__init__ 会初始化 state/count/buf)
|
||||
# ctx 在栈上分配(alloca),函数内使用完即丢弃,无需堆分配
|
||||
# ctx is stack-allocated (alloca); no heap allocation needed
|
||||
ctx: rawptr[hashlib.sha1] = hashlib.sha1()
|
||||
if ctx is None: return None
|
||||
|
||||
# 计算摘要
|
||||
# Compute the digest
|
||||
ctx.update(content)
|
||||
digest: bytes = pool.alloc(hashlib.SHA1_DIGEST_LEN)
|
||||
if digest is None: return None
|
||||
ctx.final(digest)
|
||||
|
||||
# 转为十六进制字符串(取前 8 字节 = 16 个十六进制字符)
|
||||
# Convert to a hex string (first 8 bytes = 16 hex chars)
|
||||
return bytes_to_hex(pool, digest, 8, 0, 0)
|
||||
|
||||
|
||||
# ============================================================
|
||||
# is_vp_at - 检查路径段是否以 .vp 后缀结尾
|
||||
# ============================================================
|
||||
# is_vp_at - check whether a path segment ends with .vp
|
||||
def is_vp_at(p: str, start: sizet, plen: sizet) -> int:
|
||||
"""检查 p[start, start+plen) 是否以 .vp 后缀结尾(1=是,0=否)。
|
||||
|
||||
不再识别 .py(移除 vp -> py 后缀识别回退:Viper 源文件仅 .vp)。
|
||||
"""
|
||||
"""Return 1 if p[start, start+plen) ends with the .vp suffix."""
|
||||
if p is None or plen <= 3: return 0
|
||||
base: sizet = start + plen - 3
|
||||
if p[base] == '.' and p[base + 1] == 'v' and p[base + 2] == 'p': return 1
|
||||
return 0
|
||||
|
||||
|
||||
# ============================================================
|
||||
# JoinPath - 拼接两个路径段为 a/b
|
||||
# ============================================================
|
||||
# JoinPath - join two path segments as a/b
|
||||
def JoinPath(a: str, b: str) -> str:
|
||||
"""拼接路径段为 "a/b"(stdlib.malloc 分配,调用方负责 free),失败返回 None"""
|
||||
"""Join two path segments as "a/b"; caller frees the malloc'd result."""
|
||||
if a is None or b is None: return None
|
||||
a_len: sizet = string.strlen(a)
|
||||
b_len: sizet = string.strlen(b)
|
||||
@@ -103,15 +83,9 @@ def JoinPath(a: str, b: str) -> str:
|
||||
return buf
|
||||
|
||||
|
||||
# ============================================================
|
||||
# read_file_to_buf - 读取整个文件到缓冲区(null 终止)
|
||||
# ============================================================
|
||||
# read_file_to_buf - read a whole file into a null-terminated buffer
|
||||
def read_file_to_buf(path: str, buf_size: sizet) -> bytes:
|
||||
"""读取文件到 stdlib.malloc 缓冲区并保证 null 终止
|
||||
|
||||
返回缓冲区(调用方负责 free);打开失败/空文件/分配失败返回 None。
|
||||
合并自 Phase2/StubMerger/IncludesScanner 的重复"打开+读取+终止"序列。
|
||||
"""
|
||||
"""Read a whole file into a null-terminated malloc buffer, or None."""
|
||||
if path is None or buf_size <= 0: return None
|
||||
sf: rawptr[fileio.File] = fileio.File(path, fileio.MODE.R)
|
||||
if sf.closed: return None
|
||||
@@ -128,19 +102,12 @@ def read_file_to_buf(path: str, buf_size: sizet) -> bytes:
|
||||
return buf
|
||||
|
||||
|
||||
# ============================================================
|
||||
# PathToModuleName - 文件路径转模块名
|
||||
# ============================================================
|
||||
# PathToModuleName - convert a file path to a module name
|
||||
def PathToModuleName(path: str, strip_includes_prefix: int) -> str:
|
||||
"""将文件路径转换为模块名(stdlib.malloc 分配,调用方负责 free)
|
||||
|
||||
strip_includes_prefix=1 时跳过 includes/ 前缀(如 includes/ast/__init__.vp → ast)。
|
||||
特殊处理 __init__.vp:pkg/__init__.vp → pkg(而非 pkg.__init__)。
|
||||
合并自 StubMerger._PathToModuleName / IncludesScanner 内联模块名计算。
|
||||
"""
|
||||
"""Convert a file path to a dotted module name; caller frees the result."""
|
||||
if path is None: return None
|
||||
plen: sizet = string.strlen(path)
|
||||
# 跳过 includes/ 或 includes\ 前缀(Windows 反斜杠兼容)
|
||||
# Skip the includes/ or includes\ prefix
|
||||
prefix_len: sizet = 9
|
||||
path_start: sizet = 0
|
||||
if strip_includes_prefix != 0 and plen > prefix_len and string.strncmp(path, "includes", 8) == 0:
|
||||
@@ -148,19 +115,18 @@ def PathToModuleName(path: str, strip_includes_prefix: int) -> str:
|
||||
if psep_ch == '/' or psep_ch == '\\':
|
||||
path_start = prefix_len
|
||||
plen = plen - prefix_len
|
||||
# 去掉 .vp 后缀(Viper 源文件仅 .vp,不再识别 .py)
|
||||
# Strip the .vp suffix
|
||||
if is_vp_at(path, path_start, plen) != 0: plen -= 3
|
||||
if plen == 0: return None
|
||||
# 检查是否以 /__init__ 或 \__init__ 结尾(包的 __init__.vp)
|
||||
# 同时支持 / 和 \ 两种分隔符(TPC 写入的路径可能混合使用)
|
||||
# Check for a trailing /__init__ or \__init__ (package __init__.vp)
|
||||
if plen > 9:
|
||||
tail_off: sizet = path_start + plen - 9
|
||||
sep_char: char = path[tail_off]
|
||||
if (sep_char == '/' or sep_char == '\\') and path[tail_off + 1] == '_' and path[tail_off + 2] == '_' and path[tail_off + 3] == 'i' and path[tail_off + 4] == 'n' and path[tail_off + 5] == 'i' and path[tail_off + 6] == 't' and path[tail_off + 7] == '_' and path[tail_off + 8] == '_':
|
||||
# 去掉 分隔符+__init__ 后缀,plen 就是包名部分长度
|
||||
# Drop the separator + __init__ suffix
|
||||
plen = tail_off - path_start
|
||||
if plen == 0: return None
|
||||
# 复制并替换 / 为 .
|
||||
# Copy and replace / with .
|
||||
result: str = stdlib.malloc(plen + 1)
|
||||
if result is None: return None
|
||||
for ri in range(plen):
|
||||
@@ -171,11 +137,9 @@ def PathToModuleName(path: str, strip_includes_prefix: int) -> str:
|
||||
return result
|
||||
|
||||
|
||||
# ============================================================
|
||||
# CleanDir - 递归删除目录中所有文件和子目录(保留 dir_path 本身;用于 --clean)
|
||||
# ============================================================
|
||||
# CleanDir - recursively delete all files/subdirs under dir_path (for --clean)
|
||||
def CleanDir(dir_path: str) -> int:
|
||||
"""递归删除目录中所有文件和子目录,保留 dir_path 本身,返回删除的文件数,-1 表示错误"""
|
||||
"""Recursively delete files/subdirs under dir_path, returning the count."""
|
||||
if dir_path is None: return -1
|
||||
dir_len: sizet = string.strlen(dir_path)
|
||||
pattern: bytes = stdlib.malloc(dir_len + 8)
|
||||
@@ -198,23 +162,23 @@ def CleanDir(dir_path: str) -> int:
|
||||
while True:
|
||||
fname: str = find_data.cFileName
|
||||
if fname is not None:
|
||||
# 跳过 . 和 ..
|
||||
# Skip . and ..
|
||||
if fname[0] == '.':
|
||||
if fname[1] == '\0': fname = None
|
||||
elif fname[1] == '.' and fname[2] == '\0': fname = None
|
||||
if fname is not None:
|
||||
full_path: bytes = JoinPath(dir_path, fname)
|
||||
if full_path is not None:
|
||||
# 检查是否为目录(dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
|
||||
# Check whether this is a directory
|
||||
is_dir: uint32 = find_data.dwFileAttributes & w32.win32file.FILE_ATTRIBUTE_DIRECTORY
|
||||
if is_dir != 0:
|
||||
# 递归清理子目录中的文件
|
||||
# Recurse into the subdirectory
|
||||
sub_deleted: int = CleanDir(full_path)
|
||||
if sub_deleted > 0: deleted += sub_deleted
|
||||
# 删除空子目录(RemoveDirectoryA 要求目录为空)
|
||||
# Remove the now-empty subdirectory
|
||||
w32.win32file.RemoveDirectoryA(full_path)
|
||||
else:
|
||||
# 删除文件
|
||||
# Delete the file
|
||||
if w32.win32file.DeleteFileA(full_path) != 0: deleted += 1
|
||||
stdlib.free(full_path)
|
||||
|
||||
|
||||
Reference in new issue
Block a user