1. Cpu0Ret 节点定义
tablegen
def Cpu0Ret : SDNode<"Cpu0ISD::Ret", SDTNone,
[SDNPHasChain, SDNPOptInGlue, SDNPVariadic]>;
-
使用空签名
SDTNone(无输入输出) -
属性:有链式依赖、可选粘合值、可变参数
-
用于表示函数返回操作
2. 操作数和寻址模式
tablegen
def simm16 : Operand<i32> {
let DecoderMethod = "DecodeSimm16";
}
-
16位有符号立即数
-
自定义解码方法
tablegen
def mem : Operand<iPTR> {
let PrintMethod = "printMemOperand";
let MIOperandInfo = (ops CPURegs, simm16);
let EncoderMethod = "getMemEncoding";
}
-
内存操作数:
基址寄存器(CPURegs) + 16位有符号偏移(simm16) -
例:
8($sp)编码为基址$sp和偏移8
tablegen
def addr : ComplexPattern<iPTR, 2, "SelectAddr", [frameindex], [SDNPWantParent]>;
-
复杂地址模式匹配器
-
返回
iPTR类型,匹配2个操作数(基址+偏移) -
frameindex表示栈帧索引也可以匹配
tablegen
def immSExt16 : PatLeaf<(imm), [{ return isInt<16>(N->getSExtValue()); }]>;
- 匹配16位有符号立即数的叶子节点
3. 内存对齐检查片段
tablegen
class AlignedLoad<PatFrag Node> : PatFrag<(ops node:$ptr), (Node node:$ptr), [{
LoadSDNode *LD = cast<LoadSDNode>(N);
return LD->getMemoryVT().getSizeInBits()/8 <= LD->getAlignment();
}]>;
-
包装load/store操作,检查对齐要求
-
确保访问大小 ≤ 内存对齐度
4. 指令格式类
算术/逻辑立即数指令
tablegen
class ArighLogicI<bits<8> op, string instrAsm, SDNode opNode,
Operand od, PatLeaf immType, RegisterClass regClass>
: FL<op, (outs GPROut:$ra), (ins regClass:$rb, od:$imm16),
!strconcat(instrAsm, "\t$ra, $rb, $imm16"),
[(set GPROut:$ra, (opNode regClass:$rb, immType:$imm16))], IIAlu>;
-
格式:
addiu $ra, $rb, $imm16 -
模式:
ra = rb op imm16
内存访问基类
tablegen
class LoadM<bits<8> op, string instrAsm, PatFrag opNode, RegisterClass regClass,
Operand od, bit pseudo>
: FMem<op, (outs regClass:$ra), (ins od:$addr),
!strconcat(instrAsm, "\t$ra, $addr"),
[(set regClass:$ra, (opNode addr:$addr))], IILoad>;
- load模式:
ra = load(addr)
tablegen
class StoreM<bits<8> op, string instrAsm, PatFrag opNode, RegisterClass regClass,
Operand od, bit pseudo>
: FMem<op, (outs), (ins regClass:$ra, od:$addr),
!strconcat(instrAsm, "\t$ra, $addr"),
[(opNode regClass:$ra, addr:$addr)], IIStore>;
- store模式:
store(ra, addr)
5. 跳转指令类
tablegen
let isBranch=1, isTerminator=1, isBarrier=1, hasDelaySlot=1,
isIndirectBranch=1 in
class JumpFR<bits<8> op, string instrAsm, RegisterClass regClass>
: FL<op, (outs), (ins regClass:$ra),
!strconcat(instrAsm, "\t$ra"), [(brind regClass:$ra)], IIBranch>;
-
间接跳转:
jr $ra -
属性:分支、终止符、屏障、延迟槽
6. 返回指令
tablegen
class RetBase<RegisterClass regClass> : JumpFR<0x3c, "ret", regClass> {
let isReturn = 1;
let isCodeGenOnly = 1;
let hasCtrlDep = 1;
let hasExtraSrcRegAllocReq = 1;
}
-
继承自
JumpFR,操作码0x3c -
特殊属性标记为返回指令
7. 具体指令实例化
tablegen
defm LD : LoadM32<0x01, "ld", load_a>; // 加载指令
defm ST : StoreM32<0x02, "st", store_a>; // 存储指令
def ADDiu : ArighLogicI<0x09, "addiu", add, simm16, immSExt16, CPURegs>; // 加法
def JR : JumpFR<0x3c, "jr", GPROut>; // 间接跳转
def RET : RetBase<GPROut>; // 返回指令
def NOP : FJ<0, (outs), (ins), "nop", [], IIAlu>; // 空操作
8. 模式匹配规则
tablegen
def : Pat<(i32 immSExt16:$in), (ADDiu ZERO, imm:$in)>
-
将16位立即数转换为
addiu $zero, imm指令 -
用零寄存器加立即数实现加载常量
工作流程示例
对于 C 代码:
c
int a = 42;
return a;
LLVM IR:
llvm
%a = alloca i32
store i32 42, i32* %a
%1 = load i32, i32* %a
ret i32 %1
SelectionDAG 会匹配:
-
load→LD指令 -
ret→RET指令 -
常量
42→ADDiu ZERO, 42指令
这些 TableGen 定义最终会生成 Cpu0 的汇编代码。
//===-- Cpu0InstrInfo.td - Cpu0 Instruction defs -----------*- tablegen -*-===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
//
// This fiel contains the Cpu0 implementation of the TargetInstrInfo class.
//
//===----------------------------------------------------------------------===//
//===----------------------------------------------------------------------===//
// Cpu0 profiles and nodes
//===----------------------------------------------------------------------===//
def SDT_Cpu0Ret : SDTypeProfile<0, 1, SDTCisInt\<0\>>;
// Return
def Cpu0Ret : SDNode<"Cpu0ISD::Ret", SDTNone,
SDNPHasChain, SDNPOptInGlue, SDNPVariadic>;
//===----------------------------------------------------------------------===//
// Instruction format superclass
//===----------------------------------------------------------------------===//
include "Cpu0InstrFormats.td"
//===----------------------------------------------------------------------===//
// Cpu0 Operand, Complex Patterns and Transformations Definitions
//===----------------------------------------------------------------------===//
// Instruction operand types
// Signed Operand
def simm16 : Operand<i32> {
let DecoderMethod = "DecodeSimm16";
}
// Address operand
def mem : Operand<iPTR> {
let PrintMethod = "printMemOperand";
let MIOperandInfo = (ops CPURegs, simm16);
let EncoderMethod = "getMemEncoding";
}
// Node immediate fits as 16-bit sign extended on target immediate.
// e.g. addi, andi
def immSExt16 : PatLeaf<(imm), { return isInt\<16\>(N-\>getSExtValue()); }>;
// Cpu0 Address Mode! SDNode frameindex could posibily be a match since
// load and store instructions from stack used it.
def addr : ComplexPattern<iPTR, 2, "SelectAddr",
frameindex, SDNPWantParent>;
//===----------------------------------------------------------------------===//
// Pattern fragment for load/store
//===----------------------------------------------------------------------===//
class AlignedLoad<PatFrag Node>
: PatFrag<(ops node:ptr), (Node node:ptr),
[{
LoadSDNode *LD = cast<LoadSDNode>(N);
return LD->getMemoryVT().getSizeInBits()/8 <= LD->getAlignment();
}]>;
class AlignedStore<PatFrag Node>
: PatFrag<(ops node:val, node:ptr), (Node node:val, node:ptr),
[{
StoreSDNode *ST = cast<StoreSDNode>(N);
return ST->getMemoryVT().getSizeInBits()/8 <= ST->getAlignment();
}]>;
// Load/Store PatFrags
def load_a : AlignedLoad<load>;
def store_a : AlignedStore<store>;
//===----------------------------------------------------------------------===//
// Instructions specific format
//===----------------------------------------------------------------------===//
// Arithmetic and logical instructions with 2 register operands
class ArighLogicI<bits<8> op, string instrAsm, SDNode opNode,
Operand od, PatLeaf immType, RegisterClass regClass>
: FL<op, (outs GPROut:ra), (ins regClass:rb, od:$imm16),
!strconcat(instrAsm, "\tra, rb, $imm16"),
(set GPROut:$ra, (opNode regClass:$rb, immType:$imm16)), IIAlu> {
let isReMaterializable = 1;
}
class FMem<bits<8> op, dag outs, dag ins, string asmStr, list<dag> pattern,
InstrItinClass itin>
: FL<op, outs, ins, asmStr, pattern, itin> {
bits<20> addr;
let Inst{19-16} = addr{19-16};
let Inst{15-0} = addr{15-0};
let DecoderMethod = "DecodeMem";
}
// Memory Load/Store
let canFoldAsLoad = 1 in
class LoadM<bits<8> op, string instrAsm, PatFrag opNode, RegisterClass regClass,
Operand od, bit pseudo>
: FMem<op, (outs regClass:ra), (ins od:addr),
!strconcat(instrAsm, "\tra, addr"),
(set regClass:$ra, (opNode addr:$addr)), IILoad> {
let isPseudo = pseudo;
}
class StoreM<bits<8> op, string instrAsm, PatFrag opNode, RegisterClass regClass,
Operand od, bit pseudo>
: FMem<op, (outs), (ins regClass:ra, od:addr),
!strconcat(instrAsm, "\tra, addr"),
(opNode regClass:$ra, addr:$addr), IIStore> {
let isPseudo = pseudo;
}
// 32-bit load
multiclass LoadM32<bits<8> op, string instrAsm,
PatFrag opNode, bit pseudo=0> {
def #NAME# : LoadM<op, instrAsm, opNode, GPROut, mem, pseudo>;
}
// 32-bit store
multiclass StoreM32<bits<8> op, string instrAsm,
PatFrag opNode, bit pseudo=0> {
def #NAME# : StoreM<op, instrAsm, opNode, CPURegs, mem, pseudo>;
}
// JumpFR
let isBranch=1, isTerminator=1, isBarrier=1, hasDelaySlot=1,
isIndirectBranch=1 in
class JumpFR<bits<8> op, string instrAsm, RegisterClass regClass>
: FL<op, (outs), (ins regClass:$ra),
!strconcat(instrAsm, "\tra"), \[(brind regClass:ra)], IIBranch> {
let rb = 0;
let imm16 = 0;
}
// Return
class RetBase<RegisterClass regClass> : JumpFR<0x3c, "ret", regClass> {
let isReturn = 1;
let isCodeGenOnly = 1;
let hasCtrlDep = 1;
let hasExtraSrcRegAllocReq = 1;
}
//===----------------------------------------------------------------------===//
// Instruction definition
//===----------------------------------------------------------------------===//
// Load and Store Instructions
// already aligned
defm LD : LoadM32<0x01, "ld", load_a>;
defm ST : StoreM32<0x02, "st", store_a>;
// Arithmetic Instructions (ALU Immediate)
def ADDiu : ArighLogicI<0x09, "addiu", add, simm16, immSExt16, CPURegs>;
// Arithmetic Instructions (3-Operand, R-Type)
// Shift Instructions
// Jump Instructions
def JR : JumpFR<0x3c, "jr", GPROut>;
def RET : RetBase<GPROut>;
// No Operation Instructions
let addr=0 in
def NOP : FJ<0, (outs), (ins), "nop", \[\], IIAlu>;
//===----------------------------------------------------------------------===//
// Arbitrary patterns that map to one or more instructions
//===----------------------------------------------------------------------===//
// Small Immediates
def : Pat<(i32 immSExt16:in), (ADDiu ZERO, imm:in)>;
语法拆解
tablegen
def : Pat<(i32 immSExt16:$in), (ADDiu ZERO, imm:$in)>;
1. i32 - 类型约束
-
表示这个模式匹配返回 32 位整数类型的节点
-
在 SelectionDAG 中,这表示匹配一个产生
i32值的节点
2. immSExt16 - 模式匹配器
-
这是一个之前定义的
PatLeaf(模式叶子节点) -
定义如下:
tablegen
def immSExt16 : PatLeaf<(imm), [{ return isInt<16>(N->getSExtValue()); }]>; -
作用 :匹配那些在 16 位有符号整数范围内(-32768 到 32767)的立即数
3. :$in - 变量绑定
-
$in是将匹配到的 DAG 节点绑定到变量名in -
这样可以在右侧的指令构建中引用这个值
-
:是分隔符,前面是类型/模式,后面是变量名
完整含义
左侧模式 (i32 immSExt16:$in) 匹配:
-
一个产生
i32类型值的节点 -
该节点是一个立即数(
imm) -
且该立即数的值在
-32768 ~ 32767范围内 -
将匹配到的节点命名为
$in
右侧替换 (ADDiu ZERO, imm:$in):
-
生成一条
ADDiu指令 -
使用
ZERO寄存器($zero)作为第一个源操作数 -
imm:$in将左侧匹配到的立即数作为第二个源操作数
实际效果示例
假设有这样的 LLVM IR:
llvm
%1 = add i32 0, 42 ; 常量 42
SelectionDAG 中的节点:
text
t1: i32 = Constant<42>
模式匹配过程:
-
immSExt16检查 42 是否在-32768~32767→ ✅ 是 -
匹配成功,变量
$in绑定到 Constant<42> -
生成指令:
ADDiu $zero, 42
为什么要这样的模式?
Cpu0 架构没有专门的加载立即数指令 (如 ARM 的 MOV),而是通过:
asm
addiu $dest, $zero, imm
这条指令将 $zero + imm 的结果存入目标寄存器,相当于加载立即数。
变体示例
如果没有这个模式,LLVM 可能需要生成:
asm
# 假设有 lui/ori 组合(如 MIPS)
lui $dest, hi(imm)
ori $dest, $dest, lo(imm)
通过 Pat 规则,将小立即数优化为单条 addiu 指令,提高代码效率。
类似规则
tablegen
// 匹配 16 位无符号立即数 → ADDIU
def : Pat<(i32 immZExt16:$in), (ADDiu ZERO, imm:$in)>;
// 匹配 32 位大立即数 → LUI + ORI 组合
def : Pat<(i32 imm32:$in), (ORi (LUi hi16:$in), lo16:$in)>;
这个模式是典型的 RISC 架构立即数加载优化技巧。
imm:$in 表示什么意思
imm:$in 是 TableGen 中指令构建部分的参数传递语法,让我详细解释:
语法解析
tablegen
(ADDiu ZERO, imm:$in)
1. imm - 操作数类型
-
表示这是一个立即数操作数
-
对应指令定义中的
simm16类型(16位有符号立即数) -
告诉指令编码器:这个值要作为立即数字段编码到指令中
2. $in - 变量引用
-
引用左侧模式中绑定的变量
$in -
将左侧匹配到的 DAG 节点的值,传递给右侧指令的立即数操作数
完整的数据流
tablegen
def : Pat<(i32 immSExt16:$in), (ADDiu ZERO, imm:$in)>;
数据流动过程:
text
左侧 DAG 节点 右侧指令构建
┌─────────────┐ ┌──────────────────┐
│ Constant<42>│ →→→ │ ADDiu ZERO, imm │
│ ($in) │ │ ↑ │
└─────────────┘ │ └── 使用 $in 的值 │
└──────────────────┘
具体例子
假设匹配到立即数 1234:
-
左侧匹配 :
immSExt16:$in匹配到Constant<1234>,$in绑定到这个节点 -
右侧构建 :
imm:$in取出$in的值1234,作为立即数传给ADDiu
生成的指令:
asm
addiu $zero, 1234
为什么需要 imm:?
指令定义中 ADDiu 的格式:
tablegen
def ADDiu : ArighLogicI<0x09, "addiu", add, simm16, immSExt16, CPURegs>;
其中 simm16 是立即数操作数类型:
tablegen
def simm16 : Operand<i32> {
let DecoderMethod = "DecodeSimm16";
}
imm:$in 中的 imm 告诉 TableGen:
-
这个参数对应指令的
simm16操作数位置 -
需要进行立即数编码(16位有符号)
-
可能需要符号扩展等处理
对比其他类型的操作数
tablegen
// 寄存器操作数
(ADDiu GPROut:$ra, CPURegs:$rb, imm:$imm16)
// ↑ ↑
// 寄存器参数 寄存器参数
// 内存操作数
(ST CPURegs:$ra, mem:$addr)
// ↑
// 内存操作数(包含寄存器和偏移)
// 立即数操作数
(ADDiu ZERO, imm:$in)
// ↑
// 立即数操作数
常见变体
tablegen
// 直接使用常量(不绑定变量)
def : Pat<(i32 0), (ADDiu ZERO, 0)>;
// 绑定后多次使用
def : Pat<(add (i32 immSExt16:$a), (i32 immSExt16:$b)),
(ADDiu (ADDiu ZERO, imm:$a), imm:$b)>;
// 类型转换
def : Pat<(i32 imm:$in), (MOVi32 imm:$in)>; // 直接传送立即数
关键要点
-
imm:$in中的imm是操作数类型说明符 -
$in是从左侧传递过来的值 -
这种机制实现了 DAG 节点到指令操作数的映射
没有 imm: 前缀,TableGen 就无法知道这个 $in 应该作为立即数、寄存器还是内存操作数来编码。