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<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>

模式匹配过程:

  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<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 应该作为立即数、寄存器还是内存操作数来编码。

相关推荐
archi-dreamer1 天前
SlotIndex机制--以AMDGPU为例
gpu·llvm·编译器与工具链
archi-dreamer1 天前
LiveInterval分析–以AMDGPU为例
gpu·llvm·编译器与工具链
archi-dreamer6 天前
AMDGPU后端RegMask使用介绍
gpu·llvm·编译器与工具链
kevinli6 天前
帮Apple修Bug
ios·llvm
archi-dreamer12 天前
AMDGPU 后端 ABI 总览
gpu·llvm·编译器与工具链
lbaihao17 天前
LLVM Cpu0 调用规则解析
开发语言·前端·python·llvm
lbaihao20 天前
LLVM 后端中 Cpu 目标机器的 SelectionDAG 节点定义
llvm
weixin_421725261 个月前
C语言已逐渐落伍 什么样的语言能取代C语言?
c语言·编程语言·llvm·替代方案·go和rust
十五年专注C++开发1 个月前
浅谈LLVM
开发语言·c++·qt·clang·llvm