一、文件组织规范
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 常见问题避免
- 避免魔法数字:使用命名常量
- 避免深层继承:优先使用组合
- 避免重复代码:使用多类或模板
- 避免过度复杂:保持简单直接
- 避免未定义行为:设置合理默认值
快速参考卡片
命名速查
| 元素类型 | 命名规范 | 示例 |
|---|---|---|
| 类 | 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))
];
最佳实践速查
- 保持简单:每个文件<1000行,每个类<20个字段
- 明确命名:名称应自解释
- 充分注释:解释为什么,而不仅仅是什么
- 测试驱动:为复杂逻辑添加测试用例
- 逐步验证:使用!assert确保正确性
记住:TableGen代码是LLVM后端的基石。清晰的TableGen代码意味着:
- 更少的bug
- 更容易的维护
- 更快的编译
- 更好的团队协作