跳到主要内容
TileLang 系统教程

tilelang-tutorial.huandu.me

第 29 章 编译器工程:写 pass、测试与回归

/code/29-compiler-engineering/

03-verify-pass-template/extract_verify_skeleton.py4456 字节
#!/usr/bin/env python3
"""把锁定版本里两个 verify pass 的「骨架」抽出来,带真实行号。

不 import tilelang,不需要 GPU/NPU,只用标准库解析两个 C++ 文件:

    _vendor/tilelang-v0.1.15/src/transform/verify_buffer_init.cc
    _vendor/tilelang-v0.1.15/src/transform/verify_parallel_loop.cc

对每个文件打印:

  * 文件头注释的起止行;
  * 所有 ``VisitStmt_`` / ``VisitExpr_`` 重载(行号 + 签名);
  * ``EmitReport`` 的定义行;
  * ``Pass X()`` 定义行与 ``CreatePrimFuncPass`` 调用行;
  * ``TVM_FFI_STATIC_INIT_BLOCK`` 块与其中的注册名。

用途:写自己的 verify pass 时,先把这份骨架打印出来对着填,比通读一遍
几百行实现更快定位「哪几块是必须抄的」。

验证级别:**CPU 逻辑验证**(本机 macOS、无 tilelang,2026-10-01 运行通过)。
"""

from __future__ import annotations

import argparse
import re
from pathlib import Path

BOOK_ROOT = Path(__file__).resolve().parents[4]
VENDOR = BOOK_ROOT / "_vendor" / "tilelang-v0.1.15"
TARGETS = {
    "verify_buffer_init": VENDOR / "src" / "transform" / "verify_buffer_init.cc",
    "verify_parallel_loop": VENDOR / "src" / "transform" / "verify_parallel_loop.cc",
}

# 注意用 [ \t]* 而不是 \s*:\s 会跨行匹配,导致 ^ 锚点落在前一行的换行上,
# 行号就会凭空少 1——这类 off-by-one 正是写 IR 分析工具时最烦的 bug。
VISIT_RE = re.compile(r"^[ \t]*(void|Stmt|PrimExpr|bool)[ \t]+Visit(Stmt|Expr)_\(", re.M)
EMIT_RE = re.compile(r"^[ \t]*void[ \t]+EmitReport[ \t]*\(", re.M)
PASS_RE = re.compile(r"^[ \t]*(?:tvm::transform::Pass|Pass)[ \t]+(\w+)[ \t]*\(", re.M)
CREATE_RE = re.compile(r"CreatePrimFuncPass\([^;]*\)", re.M)
FFI_RE = re.compile(r"TVM_FFI_STATIC_INIT_BLOCK\(\)\s*\{(?P<body>.*?)\n\}", re.S)
REGISTER_RE = re.compile(r'refl::GlobalDef\(\)\.def\(\s*"([^"]+)"')


def line_of(text: str, offset: int) -> int:
    return text.count("\n", 0, offset) + 1


def header_span(lines: list[str]) -> tuple[int, int]:
    """返回文件顶部 ``/*! ... */`` 注释的行号区间;没有就返回 (0, 0)。"""
    if not lines or not lines[0].lstrip().startswith("/*!"):
        return (0, 0)
    for index, line in enumerate(lines):
        if line.strip().endswith("*/"):
            return (1, index + 1)
    return (1, len(lines))


def describe(path: Path) -> list[str]:
    if not path.is_file():
        return [f"  [MISSING] {path}"]

    text = path.read_text(encoding="utf-8")
    lines = text.splitlines()
    out: list[str] = []

    start, end = header_span(lines)
    if start == 0:
        out.append("  header comment       : (none)")
    else:
        out.append(f"  header comment       : lines {start}-{end} ({end - start + 1} lines)")
    out.append(f"  total lines          : {len(lines)}")

    out.append("  visitor overrides:")
    found_visits = False
    for match in VISIT_RE.finditer(text):
        found_visits = True
        snippet = lines[line_of(text, match.start()) - 1].strip()
        out.append(f"    L{line_of(text, match.start()):>4}  {snippet}")
    if not found_visits:
        out.append("    (none)")

    emit = EMIT_RE.search(text)
    out.append(f"  EmitReport           : L{line_of(text, emit.start()) if emit else '-'}")

    for match in PASS_RE.finditer(text):
        name = match.group(1)
        if name in {"VerifyBufferInit", "VerifyParallelLoop"}:
            out.append(f"  pass factory         : L{line_of(text, match.start())}  Pass {name}()")

    for match in CREATE_RE.finditer(text):
        snippet = " ".join(match.group(0).split())
        out.append(f"  CreatePrimFuncPass   : L{line_of(text, match.start())}  {snippet}")

    ffi = FFI_RE.search(text)
    if ffi:
        out.append(f"  FFI block            : lines {line_of(text, ffi.start())}-"
                   f"{line_of(text, ffi.end())}")
        for registered in REGISTER_RE.finditer(ffi.group("body")):
            out.append(f"    registered name    : {registered.group(1)}")
    else:
        out.append("  FFI block            : (none)")

    return out


def main() -> int:
    parser = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
    parser.add_argument("--only", choices=sorted(TARGETS), help="只打印其中一个")
    args = parser.parse_args()

    names = [args.only] if args.only else sorted(TARGETS)
    for name in names:
        print(f"=== {name} ===")
        print(f"    source: {TARGETS[name].relative_to(BOOK_ROOT)}")
        for line in describe(TARGETS[name]):
            print(line)
        print()

    print("对照要点:两个 pass 的骨架完全相同,只有三处不同——")
    print("  1) 遍历时关心的节点(BufferStore/BufferLoad 还是 For(parallel)+BufferStore);")
    print("  2) 判定「有问题」的规则(无写入就报,还是并行写同一地址就报);")
    print("  3) EmitReport 给出的补救建议。")
    return 0


if __name__ == "__main__":
    raise SystemExit(main())

← 浏览本章全部代码