LLVM Cpu 后端中具体的指令定义和模式匹配规则

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 会匹配:

  • loadLD 指令

  • retRET 指令

  • 常量 42ADDiu 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\ { let DecoderMethod = "DecodeSimm16"; } // Address operand def mem : Operand\ { 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\; //===----------------------------------------------------------------------===// // Pattern fragment for load/store //===----------------------------------------------------------------------===// class AlignedLoad\ : PatFrag\<(ops node:$ptr), (Node node:$ptr), \[{ LoadSDNode \*LD = cast\(N); return LD-\>getMemoryVT().getSizeInBits()/8 \<= LD-\>getAlignment(); }\]\>; class AlignedStore\ : PatFrag\<(ops node:$val, node:$ptr), (Node node:$val, node:$ptr), \[{ StoreSDNode \*ST = cast\(N); return ST-\>getMemoryVT().getSizeInBits()/8 \<= ST-\>getAlignment(); }\]\>; // Load/Store PatFrags def load_a : AlignedLoad\; def store_a : AlignedStore\; //===----------------------------------------------------------------------===// // Instructions specific format //===----------------------------------------------------------------------===// // Arithmetic and logical instructions with 2 register operands class ArighLogicI\ op, string instrAsm, SDNode opNode, Operand od, PatLeaf immType, RegisterClass regClass\> : FL\ { let isReMaterializable = 1; } class FMem\ op, dag outs, dag ins, string asmStr, list\ pattern, InstrItinClass itin\> : FL\ { 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\ op, string instrAsm, PatFrag opNode, RegisterClass regClass, Operand od, bit pseudo\> : FMem\ { let isPseudo = pseudo; } class StoreM\ op, string instrAsm, PatFrag opNode, RegisterClass regClass, Operand od, bit pseudo\> : FMem\ { let isPseudo = pseudo; } // 32-bit load multiclass LoadM32\ op, string instrAsm, PatFrag opNode, bit pseudo=0\> { def #NAME# : LoadM\; } // 32-bit store multiclass StoreM32\ op, string instrAsm, PatFrag opNode, bit pseudo=0\> { def #NAME# : StoreM\; } // JumpFR let isBranch=1, isTerminator=1, isBarrier=1, hasDelaySlot=1, isIndirectBranch=1 in class JumpFR\ op, string instrAsm, RegisterClass regClass\> : FL\ { let rb = 0; let imm16 = 0; } // Return class RetBase\ : 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\; // 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> ``` 模式匹配过程: 1. `immSExt16` 检查 42 是否在 `-32768~32767` → ✅ 是 2. 匹配成功,变量 `$in` 绑定到 Constant\<42\> 3. 生成指令:`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`: 1. **左侧匹配** :`immSExt16:$in` 匹配到 `Constant<1234>`,`$in` 绑定到这个节点 2. **右侧构建** :`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 { 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` 应该作为立即数、寄存器还是内存操作数来编码。

相关推荐
lbaihao3 小时前
LLVM 后端中 Cpu 目标机器的 SelectionDAG 节点定义
llvm
weixin_421725266 天前
C语言已逐渐落伍 什么样的语言能取代C语言?
c语言·编程语言·llvm·替代方案·go和rust
十五年专注C++开发8 天前
浅谈LLVM
开发语言·c++·qt·clang·llvm
Gauss松鼠会2 个月前
【GaussDB】LLVM技术在GaussDB等数据库中的应用
大数据·数据库·架构·数据库开发·gaussdb·llvm
lich42 个月前
个人OLLVM项目成果展示
llvm
穷人小水滴2 个月前
编译 LLVM: 跨平台 npm 二进制包
npm·编译器·llvm
穷人小水滴3 个月前
LLVM IR 入门: 使用 LLVM 编译到 WebAssembly
webassembly·编译器·llvm