CANN pyasc 项目编码规范全解:从 C++/MLIR 风格约束到 clang-format 与 clang-tidy 落地实践 CANN pyasc 项目编码规范全解从 C/MLIR 风格约束到 clang-format 与 clang-tidy 落地实践【免费下载链接】pyasc本项目为Python用户提供算子编程接口支持在昇腾AI处理器上加速计算接口与Ascend C一一对应并遵守Python原生语法。项目地址: https://gitcode.com/cann/pyasc本文档系统梳理 CANN pyasc 开源仓库的官方编码规范docs/codestyle.rst并对照仓库中真实源码、测试与工具脚本逐条解读其在 C 文件、MLIR Dialect/Pass、LIT 测试中的具体落地方式帮助你快速理解为什么这么写以及如何借助 clang-format / clang-tidy 让代码自动达标。读完本文你将能写出与项目主线代码风格一致、可直接合入的 C 与 MLIR 代码。规范总览以 LLVM 风格为基底融入项目自定义规则pyasc 的编码规范明确声明以 LLVM 编码风格为基础外加少量例外与项目级调整。这意味着仓库内所有 C 代码include/、lib/、python/src/下的实现共享一套统一的视觉与组织约定同时针对本项目特有的 MLIR 方言、Pass 与 LIT 测试目录结构给出了专属规则。整套约定由以下仓库文件承载规范正文docs/codestyle.rst格式化配置.clang-format静态检查配置.clang-tidy另有 Python 绑定目录的定制版 python/src/.clang-tidy自动化检查脚本scripts/static_check.sh 与 scripts/oat_check.shC 文件通用约定文件扩展名与 Include Guard头文件必须使用.h后缀实现文件必须使用.cpp后缀两者不可混用。所有头文件必须使用传统 include guard#ifndef HEADER_H/#define HEADER_H/#endif三段式明确禁止使用#pragma once。这一点与.clang-tidy中开启的portability-avoid-pragma-once检查项遥相呼应——该检查会在代码中出现#pragma once时直接告警从工具层面强制落实规范。仓库根目录的.clang-tidy同时开启了misc-definitions-in-headers、misc-anonymous-namespace-in-header等检查从另一个角度保证头文件只做声明、不产生定义。缩进与命名缩进使用2 个空格禁止使用 Tab。命名约定分三类PascalCase类型命名类、结构体、枚举、typedef如MyClass、MyEnumcamelCase变量局部与全局、函数、类成员如myFunction、myVariableUPPER_SNAKE_CASE宏定义如MY_CONSTANT、MAX_BUFFER_SIZEkebab-case不用于代码标识符仅允许出现在测试与资源文件名中如my-test-case.mlir。这些命名规则并非纸面约定.clang-tidy中通过readability-identifier-naming检查及其CheckOptions做了机器可执行的绑定ClassCase/EnumCase/UnionCase CamelCaseFunctionCase/MemberCase/MethodCase/ParameterCase/VariableCase camelBack。也就是说即使手写代码时命名不规范静态检查也会拦截。命名空间声明关闭命名空间时必须追加注释// namespace namespace_name匿名命名空间则省略名称只写// namespace。禁止在头文件中使用using namespace。在.cpp文件中若某类或函数仅在本文件内使用、不对外暴露应放入匿名命名空间而不应标记为static。这对应.clang-tidy中的misc-use-anonymous-namespace与readability-static-definition-in-anonymous-namespace检查。在仓库实现中可以找到完全一致的写法例如 lib/Dialect/Asc/Transforms/EraseSync.cppnamespace mlir { namespace ascendc { #define GEN_PASS_DEF_ERASESYNC #include ascir/Dialect/Asc/Transforms/Passes.h.inc } // namespace ascendc } // namespace mlir using namespace mlir; namespace { // 仅在本文件使用的辅助函数与 Pass 定义全部放入匿名命名空间 template typename OpT void eraseOps(Operation* root) { root-walk([](OpT op) { op.erase(); }); } struct EraseSyncPass : public ascendc::impl::EraseSyncBaseEraseSyncPass { ... }; } // namespace注意规范允许在.cpp中使用using namespace mlir;该文件第 24 行即如此但这一自由不适用于头文件。宏定义与全局常量不鼓励在头文件中定义宏除非绝对必要。需要定义全局常量时应使用constexpr语法并遵循camelCase命名。这与传统 C/C 中常量用全大写的习惯不同是 LLVM 风格在 pyasc 中的具体体现。Include 排序#include必须按以下分组顺序排列组间以空行分隔组内按文件名字母序排序本地项目头文件如ascir/...空行MLIR / LLVM / Clang 头文件空行标准库头文件optional、vector等。规范原文给出的示例即为标准范式#include ascir/Dialect/EmitAsc/IR/EmitAsc.h #include ascir/Target/Asc/Utils.h #include mlir/IR/Builders.h #include mlir/IR/DialectImplementation.h #include llvm/ADT/TypeSwitch.h #include optional #include unordered_map这与.clang-tidy中启用的llvm-include-order检查一一对应且.clang-format中设置了SortIncludes: false——也就是说include 排序主要由 clang-tidy 负责把关而 clang-format 不做自动重排避免两者冲突。上面引用的 EraseSync.cpp 就严格遵守了本地头文件 → 空行 → mlir 头文件 → 空行 → 标准库头文件的顺序。模板参数命名模板类型形参使用PascalCasetemplate typename AttrT非类型模板形参使用camelCasetemplate size_t size统一使用typename关键字不要使用class声明模板类型参数。同样可在源码中找到对应写法如 EraseSync.cpp 中的template typename OpT。MLIR 方言Dialect约定MLIR 方言中的 Operation、Type、Attribute、Interface 等实体在对应的 TableGen 文件.td中必须按字母序排列。这一约定直接服务于代码审查与检索效率方言定义文件通常很大仓库中 include/ascir/Dialect/Asc/IR 下就有 66 个.td文件字母序排列让新增实体该插在哪有明确的客观答案也方便工具自动 diff。从仓库结构看lib/Dialect/Asc/IR 下Ops.cpp、Types.cpp、Attributes.cpp与方言.td保持一一对应的组织方式正是为了让方言定义与实现文件的维护路径清晰可循。MLIR Pass 约定Pass 文件组织每个 MLIR Pass 必须独立放在对应方言目录下的Transforms目录中一个 Pass 一个.cpp文件。仓库中 lib/Dialect/Asc/Transforms 下 18 个.cpp文件EraseSync.cpp、UnifyPipe.cpp、VerifySync.cpp、LegalizeKernelArgs.cpp、HoistQueBind.cpp等正是这一约定的直接体现。文件命名.cpp文件名 Pass 名去掉Pass后缀。例如FoldVariablePass对应文件FoldVariable.cpp仓库中EraseSyncPass对应 EraseSync.cppDetectKernelTypePass对应 DetectKernelTypePass.cpp均严格遵循此规则。Pass 声明顺序在Passes.td、CMakeLists.txt以及构造函数头文件Passes.h中Pass 名称必须按字母序列出。这保证了同一组 Pass 在任何注册/声明点都以一致顺序出现降低合并冲突概率。LIT 测试约定目录结构所有测试位于test目录仓库根目录下的 test。测试文件名使用kebab-case例如my-test-case.mlir。仓库中 test/Dialect/AscendC/Transforms 下的canonicalize.mlir、insert-que-sync.mlir、hoist-tensor-allocation.mlir、materialize-tensor.mlir等都是此命名规范的实例。测试文件格式与归属测试文件第一行一般应包含一个或多个// RUN:命令指明测试的执行方式。例如 test/Dialect/AscendC/Transforms/canonicalize.mlir 的第一条指令// RUN: ascir-opt -canonicalize %s | FileCheck %s即用ascir-opt工具对测试文件运行-canonicalizePass再用FileCheck校验输出中// CHECK标注的期望结果。lit.cfgtest/lit.cfg将config.suffixes设为[.mlir]即仓库内所有.mlir文件都被视为 LIT 测试输入。测试归属规则新增功能时按此放置测试新增 MLIR Operation / Type / Attribute测试放在对应方言目录下的IR子目录如 test/Dialect/AscendC/IR新增 MLIR Pass在引入该 Pass 的方言Transforms目录下添加一组测试通常包含多个func.func操作文件名与 Pass 名对应如 canonicalize.mlir新增指令发射emission相关功能测试放在Target目录如 test/Target/AscendC 下 27 个 basic 测试与matmul.mlir、adv.mlir等工具端到端测试放在Tools目录如 test/tools/ascendir-opt。其他综合要求可读性优先代码应服务于清晰与可维护性而非单纯追求简短。函数、变量、类型要使用有意义的名字对复杂或不易直观理解的代码应添加注释。示例见 EraseSync.cpp 中ValueMapValue allocTensors; // maps queue to allocated tensor这类解释性注释。克制重构不要在无关区域做大范围、非必要的重构始终以最小化对代码库的扰动为目标。版本控制习惯合并merge时提交会被 squash 成单个 commit因此不强制要求详尽的 commit message但如有需要可补充变更相关说明Merge Request 标题应清晰表达变更意图尽量遵循 Conventional Commits 风格标题已足够说明问题时可省略描述复杂变更则应补充描述提交前确保代码本地编译通过、测试通过。工具链clang-format 与 clang-tidyclang-format一键格式化仓库根目录已配置 .clang-format对单文件执行格式化clang-format -i filename.clang-format的核心配置要点与文档2 空格缩进等规则配套理解配置项值说明BasedOnStyleGoogle以 Google 风格为基底ColumnLimit120单行上限 120 列IndentWidth/TabWidth4/4缩进宽度配合UseTab: NeverUseTabNever禁止 TabPointerAlignmentLeft指针星号靠左SortIncludesfalse不做 include 自动排序交由 clang-tidyBreakBeforeBracesCustom自定义大括号换行函数定义后换行、class/enum 不换行等建议将 clang-format 集成进 IDE 实现保存即格式化例如 Visual Studio Code 可安装 Clang-Format 扩展。同时可以将检查挂在 pre-commit 钩子上由 scripts/static_check.sh 统一执行。clang-tidy静态分析把关clang-tidy 按预定义/自定义检查集对代码做静态分析帮助发现代码质量、未使用变量、潜在 bug 与可性能优化点。建议在提交前查看 open merge request 中 clang-tidy 任务的输出日志并在合并前处理问题。仓库根目录 .clang-tidy 采用白名单制Checks: -*,先禁用全部再逐个启用启用的检查按族划分bugprone-*bugprone-argument-comment、bugprone-assert-side-effect、bugprone-infinite-loop、bugprone-narrowing-conversions、bugprone-macro-repeated-side-effects、bugprone-reserved-identifier、bugprone-unused-raii、bugprone-unused-return-value等约 30 项聚焦易错模式llvm-*llvm-include-order对应 include 排序规范、llvm-namespace-comment对应命名空间注释规范、llvm-prefer-isa-or-dyn-cast-in-conditionals、llvm-twine-local等承载 LLVM 风格核心misc-*misc-use-anonymous-namespace、misc-definitions-in-headers、misc-unused-parameters、misc-static-assert等modernize-*modernize-use-nullptr、modernize-use-override、modernize-use-equals-default、modernize-loop-convert、modernize-use-structured-binding等推动现代 C 写法performance-*performance-for-range-copy、performance-unnecessary-value-param、performance-inefficient-vector-operation等portability-*portability-avoid-pragma-once从检查层面杜绝#pragma oncereadability-*readability-identifier-naming配合CheckOptions强制 camelCase/CamelCase 命名、readability-else-after-return、readability-container-size-empty、readability-misleading-indentation等cppcoreguidelines-avoid-goto禁止 goto。CheckOptions段通过readability-identifier-naming.*Case系列键值将命名规范变成硬性约束FormatStyle: file表示 clang-tidy 读取FormatStyle时使用文件系统中的.clang-format。此外python/src/.clang-tidy 以InheritParentConfig: true继承根配置并针对 pybind11 的编程风格单独关闭bugprone-unused-raii检查如用匿名对象定义空类体现规范统一、例外显式的管理思路。static_check.sh提交前的自动化闸门scripts/static_check.sh 将 clang-format 与 clang-tidy 整合为一条可重复执行的检查流水线其工作流程对应文档本地构建并测试通过后再提交的要求以origin/master为基线通过git diff -U0 origin/master HEAD提取本次改动的 C/C 文件grep -E \.cpp$|\.h$run_clang_format_diff_check将 diff 管道给clang-format-diff -p1若输出非空则统计需要格式化的行数并提示修复命令git diff -U0 origin/master HEAD | clang-format-diff -p1 -irun_clang_tidy_diff_check优先查找compile_commands.json以获得精确编译参数未找到时使用自动探测 GCC 版本与架构拼装的手动编译参数-stdc17 -Iproject/include等调用clang-tidy-diff通过 awk 脚本过滤误报丢弃mlir/、llvm/路径与.inc文件的诊断、无编译数据库时丢弃clang-diagnostic-*并支持按TIDY_IGNORE_LIST白名单放行特定项例如 python/asc/lib/runtime/print_utils.cpp 中PrintWorkSpace因是extern CABI 入口、必须保持 PascalCase 以匹配 Python 运行时链接而显式豁免readability-identifier-naming检查汇总输出clang-format-diff行数与clang-tidyerror/warning 数量任何问题都会使检查状态为FAILED。对于许可证合规性仓库还提供了 scripts/oat_check.sh它基于 Python 版 oatoat-py以 pre-commit 钩子形式扫描暂存文件或在特性分支上以 merge-base 为界扫描整个 PR 改动校验非法文件类型与 License 头缺失发现问题时直接阻断提交可用git commit --no-verify跳过。这解释了仓库中所有源文件.cpp/.h/.mlir统一携带华为版权与 CANN Open Software License Agreement 声明头的原因——它们正是 OAT 检查的硬性要求例如本规范文档与上述所有源码文件均带相同格式的许可证头。小结把规范变成可执行的工程实践pyasc 的编码规范不是一份仅供参考的文档而是一套完整闭环的工程实践风格层面2 空格缩进、三段式 include guard、PascalCase/camelCase 命名、命名空间注释、匿名命名空间优先等规则由.clang-format负责机械格式化、.clang-tidy的readability-identifier-naming与llvm-include-order等检查负责语义把关结构层面MLIR 方言实体按字母序排布、每个 Pass 独立成文件且去掉Pass后缀命名、LIT 测试按IR/Transforms/Target/Tools目录归属让仓库在快速膨胀时依然保持新增代码位置可预期流程层面scripts/static_check.sh 与 scripts/oat_check.sh 分别在提交前自动执行格式、静态分析与许可证检查把人工评审的关注点从风格是否一致解放到逻辑是否正确。对贡献者而言最稳妥的提交流程是本地修改 →clang-format -i格式化 → 运行scripts/static_check.sh与scripts/oat_check.sh通过 → 本地构建并跑通 LIT 测试如ascir-optFileCheck→ 提交并保证 MR 标题清晰描述意图。遵循这套规范既能降低合入成本也能让新成员更快上手这个融合了 LLVM/MLIR 工程实践的算子编程语言项目。【免费下载链接】pyasc本项目为Python用户提供算子编程接口支持在昇腾AI处理器上加速计算接口与Ascend C一一对应并遵守Python原生语法。项目地址: https://gitcode.com/cann/pyasc创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考