TableGen 编程规范与最佳实践

一、文件组织规范

1.1 文件命名与结构

tablegen 复制代码
// ❌ 错误命名
foo.td          // 含义不明确
instr.td        // 过于通用

// ✅ 正确命名
MipsInstructions.td     // 目标明确
ARMRegisterInfo.td      // 功能清晰
RISCVSchedules.td       // 范围明确

// 文件结构示例
//===------------------------------------------------------------===//
// MipsInstructions.td
//===------------------------------------------------------------===//
//
// 文件头注释(必须)
// MIPS 指令集定义
// 作者:xxx
// 创建日期:2023-10-01
// 修改记录:
//   - 2023-10-10: 添加MIPS64指令支持
//   - 2023-10-15: 修复浮点指令编码问题
//
//===------------------------------------------------------------===//

// 1. 包含文件(按顺序)
include "MipsRegisterInfo.td"
include "MipsInstrFormats.td"
include "MipsSchedule.td"

// 2. 类型定义 (deftype)
deftype MipsOpcode = bits<6>;
deftype MipsFunct = bits<6>;

// 3. 类定义 (class)
class MipsInst<dag pattern, string asmstr, list<dag> pattern> {
  // ...
}

// 4. 多类定义 (multiclass)
multiclass MipsArithInst<bits<6> funct> {
  // ...
}

// 5. 记录定义 (def)
def ADD : MipsInst<...>;

// 6. let 语句
let hasSideEffects = 0 in {
  def SUB : MipsInst<...>;
}

1.2 目录结构建议

复制代码
llvm/lib/Target/MyTarget/
├── MyTarget.td                    # 主文件,包含其他文件
├── MyTargetRegisterInfo.td       # 寄存器定义
├── MyTargetInstrInfo.td          # 指令信息
├── MyTargetInstrFormats.td       # 指令格式
├── MyTargetSchedule.td           # 调度模型
├── MyTargetCallingConv.td        # 调用约定
└── MyTarget.td                   # 目标描述主文件

二、命名规范

2.1 标识符命名规则

tablegen 复制代码
// 1. 类 (Class) - PascalCase,前缀为目标名
class MipsInstruction<...> { }    // ✅ 好
class InstructionMips<...> { }    // ❌ 差
class mips_inst<...> { }          // ❌ 差

// 2. 记录 (def) - UPPER_SNAKE_CASE
def ADD_RR : MipsInstruction<...>;    // ✅ 好
def AddRr : MipsInstruction<...>;     // ❌ 差
def add_rr : MipsInstruction<...>;    // ❌ 差

// 3. 多类 (multiclass) - PascalCase
multiclass MipsArithInstructions { }  // ✅ 好

// 4. 字段 (field) - camelCase
let mayLoad = 1;          // ✅ 好
let may_load = 1;         // ❌ 差
let MayLoad = 1;          // ❌ 差

// 5. 类型别名 (deftype) - PascalCase
deftype MipsOpcode = bits<6>;     // ✅ 好
deftype mips_opcode = bits<6>;    // ❌ 差

// 6. 模板参数 - camelCase 或单字母
class Instruction<dag pattern> { }    // ✅ 好
class Instruction<DagPattern> { }     // ✅ 可接受

2.2 特殊命名约定

tablegen 复制代码
// 寄存器命名
def R0 : Register<"r0">;          // 通用寄存器
def F0 : Register<"f0">;          // 浮点寄存器
def SP : Register<"sp">;          // 栈指针
def PC : Register<"pc">;          // 程序计数器

// 指令后缀
def ADD_RR : ...;     // 寄存器-寄存器
def ADD_RI : ...;     // 寄存器-立即数
def ADD_RM : ...;     // 寄存器-内存

// 谓词命名
def FeatureMIPS32 : SubtargetFeature<...>;
def HasDSP : SubtargetFeature<...>;
def IsLittleEndian : SubtargetFeature<...>;

三、代码格式规范

3.1 缩进与对齐

tablegen 复制代码
// ✅ 正确:2空格缩进
class MipsInstruction<dag outs, dag ins, string asmstr> {
  dag OutOperandList = outs;
  dag InOperandList = ins;
  string AsmString = asmstr;
  
  bits<32> Inst;
  bits<6> opcode = 0;
}

// ❌ 错误:制表符或不对齐
class BadInstruction{
dag OutOperandList;
string AsmString; // 没有对齐
}

// 多字段对齐
let isReturn = 1,
    isTerminator = 1,
    hasDelaySlot = 0,
    isBarrier = 1 in {
  def RET : MipsInstruction<...>;
}

3.2 空格与换行

tablegen 复制代码
// ✅ 正确:运算符前后有空格
let Value = !add(A, B);           // 好
let Size = !size(List);           // 好

// ❌ 错误:无空格
let Value=!add(A,B);              // 差

// ✅ 正确:逗号后有空格
list<dag> Patterns = [
  (set GPR:$rd, (add GPR:$rs, GPR:$rt)),
  (set GPR:$rd, (sub GPR:$rs, GPR:$rt))
];

// 长表达式换行
let Predicates = [
  FeatureMIPS32,
  HasDSP,
  IsLittleEndian,
  !not(FeatureMIPS64)
] in {
  // ...
}

3.3 注释规范

tablegen 复制代码
// 单行注释
// 这是一个添加指令
def ADD : Instruction<...>;

// 多行注释
/// 这个类用于定义R型指令格式
/// R型指令包含以下字段:
/// - opcode: 6位操作码
/// - rs: 5位源寄存器1
/// - rt: 5位源寄存器2
/// - rd: 5位目标寄存器
/// - shamt: 5位移位量
/// - funct: 6位功能码
class RFormat<bits<6> op> : InstructionFormat {
  // ...
}

// 字段注释
class Instruction {
  bits<32> Inst;            // 32位指令编码
  bit isLoad = 0;           // 是否加载指令
  bit isStore = 0;          // 是否存储指令
  
  // 延迟槽相关标志
  bit hasDelaySlot = 0;     // 是否有延迟槽
  bit inDelaySlot = 0;      // 是否在延迟槽中
}

// TODO/FIXME注释(必须包含作者和日期)
// TODO: 需要支持MIPS64的64位操作
// 作者:张三 | 日期:2023-10-01
// FIXME: 这个编码在分支指令中有问题
// 作者:李四 | 日期:2023-10-02

四、类与记录设计规范

4.1 类层次设计

tablegen 复制代码
// ✅ 正确:清晰的继承层次
// 基础指令类
class Instruction<dag pattern> {
  dag Pattern = pattern;
  bit isPseudo = 0;
}

// 格式特定的基类
class RFormatInst<bits<6> op, bits<6> funct> : Instruction {
  bits<6> Opcode = op;
  bits<6> Funct = funct;
  bits<5> rd;
  bits<5> rs;
  bits<5> rt;
}

// 具体指令类别
class ArithInst<bits<6> funct> : RFormatInst<0b000000, funct> {
  bit isArithmetic = 1;
}

// 最终指令定义
def ADD : ArithInst<0b100000> {
  let AsmString = "add $rd, $rs, $rt";
}

// ❌ 错误:过度复杂的层次
class A : B { ... }
class B : C { ... }
class C : D { ... }
// 超过3层继承通常有问题

4.2 多类使用规范

tablegen 复制代码
// ✅ 正确:使用多类简化重复模式
multiclass ArithRRInst<bits<6> funct, string opstr> {
  def _RR : RFormatInst<0b000000, funct> {
    let AsmString = !strconcat(opstr, " $rd, $rs, $rt");
    let Pattern = (set GPR:$rd, (!cast<SDNode>(opstr) GPR:$rs, GPR:$rt));
  }
  
  def _RI : IFormatInst<...> {
    // 变体2
  }
}

// 实例化多类
defm ADD : ArithRRInst<0b100000, "add">;
defm SUB : ArithRRInst<0b100010, "sub">;
defm AND : ArithRRInst<0b100100, "and">;

// 生成的记录:
// ADD_RR, ADD_RI, SUB_RR, SUB_RI, ...

4.3 字段默认值

tablegen 复制代码
// ✅ 正确:在基类设置合理默认值
class BaseInstruction {
  bit hasSideEffects = 0;     // 默认无副作用
  bit mayLoad = 0;            // 默认不是加载
  bit mayStore = 0;           // 默认不是存储
  bit isTerminator = 0;       // 默认不是终止指令
  bit isReturn = 0;           // 默认不是返回
  bit isBarrier = 0;          // 默认不是屏障
}

// 在具体指令中覆盖
def LOAD : BaseInstruction {
  let mayLoad = 1;            // 覆盖为加载指令
  let hasSideEffects = ?;     // 保持默认,需要时覆盖
}

// ❌ 错误:让字段保持未定义
class BadInstruction {
  bit mayLoad;                // 差:没有默认值
}

五、DAG 模式规范

5.1 DAG 模式格式化

tablegen 复制代码
// ✅ 正确:清晰格式化的DAG
def ADD : Instruction {
  // 简单的DAG,单行
  dag Pattern = (set GPR:$rd, (add GPR:$rs, GPR:$rt));
  
  // 复杂的DAG,多行格式化
  dag ComplexPattern = (set 
    (i32 GPR:$rd),
    (add 
      (i32 GPR:$rs),
      (mul 
        (i32 GPR:$rt),
        (i32 4)
      )
    )
  );
  
  // 匹配模式列表
  list<dag> Patterns = [
    // 模式1
    (set GPR:$rd, (add GPR:$rs, GPR:$rt)),
    // 模式2:带立即数
    (set GPR:$rd, (add GPR:$rs, imm:$imm16))
  ];
}

// ❌ 错误:难以阅读的DAG
dag BadPattern=(set GPR:$rd,(add GPR:$rs,(mul GPR:$rt,(i32 4))));

5.2 操作数命名约定

tablegen 复制代码
// 寄存器操作数
def GPR : RegisterClass<...>;
def FPR : RegisterClass<...>;

// ✅ 正确:有意义的操作数名
dag Pattern = (set 
  GPR:$dst,        // 目标寄存器
  (add 
    GPR:$src1,     // 源寄存器1
    GPR:$src2      // 源寄存器2
  )
);

// 立即数操作数
dag Pattern = (set 
  GPR:$rd,
  (add 
    GPR:$rs,
    simm16:$imm    // 16位有符号立即数
  )
);

// 内存操作数
dag Pattern = (set 
  GPR:$rd,
  (load 
    (add 
      GPR:$base,
      simm16:$offset
    )
  )
);

// ❌ 错误:无意义的名称
dag BadPattern = (set GPR:$a, (add GPR:$b, GPR:$c));

六、Bang Operator 使用规范

6.1 常用操作符的最佳实践

tablegen 复制代码
// ✅ 正确:使用适当的操作符
// 列表操作
let Insts = !listconcat(ArithInsts, LogicInsts);
let InstCount = !size(InstList);
let IsEmpty = !empty(InstList);

// 字符串操作
let AsmString = !strconcat(Mnemonic, " $rd, $rs, $rt");
let LowerCase = !tolower("ADD");
let UpperCase = !toupper("sub");

// 条件判断
let Value = !if(!eq(Opcode, 0b000000), "R-Type", "I-Type");
let IsValid = !and(!gt(Value, 0), !lt(Value, 100));

// DAG操作
let NewDAG = !setdagarg(OldDAG, 0, GPR:$newrd);
let OpName = !getdagname(Pattern);

// ❌ 错误:过度复杂的表达式
let BadValue = !add(!mul(A, B), !div(!sub(C, D), !add(E, F)));
// 应该拆分成多个步骤

6.2 复杂表达式分解

tablegen 复制代码
// ❌ 错误:单行复杂表达式
let ComplexField = !if(
  !and(!gt(X, 0), !lt(X, 100)),
  !strconcat("Value is ", !repr(X)),
  "Out of range"
);

// ✅ 正确:分解为多步
let IsInRange = !and(!gt(X, 0), !lt(X, 100));
let RangeMessage = !strconcat("Value is ", !repr(X));
let OutOfRangeMessage = "Out of range";
let ComplexField = !if(IsInRange, RangeMessage, OutOfRangeMessage);

// 或者使用 let...in 块
let ComplexField = 
  let IsInRange = !and(!gt(X, 0), !lt(X, 100));
  let RangeMessage = !strconcat("Value is ", !repr(X));
  let OutOfRangeMessage = "Out of range";
in !if(IsInRange, RangeMessage, OutOfRangeMessage);

七、错误处理与验证

7.1 前置条件检查

tablegen 复制代码
// 使用 !assert 验证假设
def CHECKED_INST : Instruction {
  // 验证操作码范围
  !assert(!ge(Opcode, 0), "Opcode must be non-negative");
  !assert(!lt(Opcode, 64), "Opcode must be less than 64");
  
  // 验证字段一致性
  !assert(!or(isLoad, isStore, isArithmetic), 
         "Instruction must have at least one type");
  
  // 验证立即数范围
  !assert(!and(!ge(Immediate, -32768), !le(Immediate, 32767)),
         "Immediate out of 16-bit signed range");
}

7.2 调试辅助

tablegen 复制代码
// 使用 !repr 进行调试输出
let DebugInfo = !repr([
  "Opcode: ", Opcode,
  "Funct: ", Funct,
  "Rs: ", Rs,
  "Rt: ", Rt,
  "Rd: ", Rd
]);

// 条件调试
let DebugOutput = !if(EnableDebug,
  !strconcat("Debug: ", !repr(Inst)),
  ""
);

八、性能优化建议

8.1 编译时优化

tablegen 复制代码
// ✅ 正确:编译时计算
let ElementCount = !size(ElementList);          // 好:编译时计算
let IsEmpty = !empty(ElementList);              // 好:编译时计算

// 避免运行时可能的变化
class OptimizedInst {
  // 编译时已知的常量
  const bits<6> Opcode = 0b000000;
  const bits<6> Funct;
  
  // 避免复杂的运行时表达式
  // ❌ 差:可能需要运行时计算
  // bits<32> DynamicValue = !someComplexFunction();
}

8.2 模式匹配优化

tablegen 复制代码
// ✅ 正确:使用高效的模式
def EFFICIENT_LOAD : Instruction {
  // 简单直接的匹配
  dag Pattern = (set GPR:$rd, (load addr:$addr));
  
  // 避免过于复杂的嵌套
  // ❌ 差:复杂的多层嵌套
  // dag BadPattern = (set (set (set ...)))
}

// 使用多类避免重复
multiclass EfficientPatterns<string opcode> {
  def : Pat<(opcode GPR:$rs, GPR:$rt),
            (INST_RR GPR:$rs, GPR:$rt)>;
  
  def : Pat<(opcode GPR:$rs, imm:$imm),
            (INST_RI GPR:$rs, imm:$imm)>;
}

九、测试与维护

9.1 测试代码规范

tablegen 复制代码
// 测试特定的指令模式
def TEST_ADD : ADD {
  // 覆盖字段以测试边界情况
  let Rs = 0;      // 测试寄存器0
  let Rt = 31;     // 测试寄存器31
  let Rd = 1;      // 测试目标寄存器
  
  // 验证编码
  !assert(!eq(Inst{31-26}, 0b000000), "Wrong opcode");
  !assert(!eq(Inst{5-0}, 0b100000), "Wrong funct");
}

// 测试列表
def TestInstructions = [
  TEST_ADD,
  TEST_SUB,
  TEST_AND
] {
  // 所有测试指令的共同属性
  let isCodeGenOnly = 1;    // 仅用于代码生成测试
  let isPseudo = 1;         // 标记为伪指令
}

9.2 版本控制与变更

tablegen 复制代码
// 变更记录注释
// 修改:添加MIPS64支持
// 日期:2023-10-10
// 影响:所有64位指令
// 测试:通过所有MIPS64测试用例
class Mips64Instruction : MipsInstruction {
  // 新增字段
  bit Is64Bit = 1;
  
  // 修改字段默认值
  let OperandSize = 64;
}

// 废弃标记
class DeprecatedInstruction : Instruction {
  // 标记为废弃
  bit isDeprecated = 1;
  
  // 说明替代方案
  string Replacement = "Use NEW_INSTRUCTION instead";
  string DeprecationMessage = 
    "This instruction is deprecated and will be removed in LLVM 18";
}

十、代码审查清单

10.1 提交前检查

  • 文件名和目标名正确
  • 命名符合规范(PascalCase/UPPER_SNAKE_CASE)
  • 缩进为2个空格,没有制表符
  • 操作符前后有空格
  • 注释完整且有意义
  • 类层次不超过3层
  • 字段有合理默认值
  • DAG模式格式化清晰
  • 复杂表达式已分解
  • 使用了适当的bang operators
  • 包含必要的验证(!assert)
  • 无编译警告
  • 测试用例已更新

10.2 常见问题避免

  1. 避免魔法数字:使用命名常量
  2. 避免深层继承:优先使用组合
  3. 避免重复代码:使用多类或模板
  4. 避免过度复杂:保持简单直接
  5. 避免未定义行为:设置合理默认值

快速参考卡片

命名速查

元素类型 命名规范 示例
PascalCase MipsInstruction
记录 UPPER_SNAKE_CASE ADD_RR
多类 PascalCase MipsArithInsts
字段 camelCase hasSideEffects
类型 PascalCase MipsOpcode

格式速查

tablegen 复制代码
// 缩进:2空格
class Example {
  bit field1;
  int field2;
}

// 空格:运算符前后
let x = !add(a, b);

// 长行:在逗号后换行
list<dag> patterns = [
  (set a, (add b, c)),
  (set d, (sub e, f))
];

最佳实践速查

  1. 保持简单:每个文件<1000行,每个类<20个字段
  2. 明确命名:名称应自解释
  3. 充分注释:解释为什么,而不仅仅是什么
  4. 测试驱动:为复杂逻辑添加测试用例
  5. 逐步验证:使用!assert确保正确性

记住:TableGen代码是LLVM后端的基石。清晰的TableGen代码意味着:

  • 更少的bug
  • 更容易的维护
  • 更快的编译
  • 更好的团队协作
相关推荐
威桑8 天前
LLVM (Low Level Virtual Machine)全景机制解析
c++·gcc·llvm
Molesidy1 个月前
【VSCode】【Clangd】Win下的基于LLVM/Clangd+Clangd插件+MINGW+CMake的VSCode配置C/C++开发环境的详细教程
c++·ide·vscode·clangd·llvm
Moonbit1 个月前
MoonBit 推出 LLVM Debugger,核心用户数破十万
前端·编程语言·llvm
沢田纲吉2 个月前
《LLVM IR 学习手记(五):关系运算与循环语句的实现与解析》
前端·c++·llvm
沢田纲吉2 个月前
《LLVM IR 学习手记(六):break 语句与 continue 语句的实现与解析》
前端·c++·llvm
沢田纲吉2 个月前
《LLVM IR 学习手记(三):赋值表达式与错误处理的实现与解析》
前端·编程语言·llvm
沢田纲吉2 个月前
《LLVM IR 学习手记(二):变量表达式编译器的实现与深入解析》
前端·编程语言·llvm
沢田纲吉3 个月前
《LLVM IR 学习手记(一):无量表达式编译器的实现与实践总结》
编程语言·llvm
CYRUS_STUDIO3 个月前
一文搞懂 Frida Stalker:对抗 OLLVM 的算法还原利器
android·逆向·llvm