Mars Studio

LLVM Backend Practices - Part 1

Word count: 12kReading time: 42 mins. min
2025/04/15 Share

LLVM Backend Practices - Part 1

PostEncoderMethod

Background

一般情况下我们对于指令encoding采取传统的在指令定义的tablegen文件里,设置好指令的field mapping即可,如果新一代指令集有新指令,则定义新的Inst和fieldmap类即可。

在实际项目中,我们遇到过这种情况:架构演进过程中,每代之间指令功能变动不大,但指令encoding变动频繁,此外encoding采取的并不是顺序编码,而是逐bit的映射,目的是为了获取一定的指令shrink机会,即可变长指令。这里先不展开讨论shrink,而是着重讨论我们是如何解决encoding问题的。

Solution

解决方案整体上可以一句话概括:自动代码生成 + LLVM基础设施中的PostencodeMethod hook

Code Auto-gen

  1. 针对每一代架构指令集,定义一张大表,可以是csv表格或其他便于非研发人员编辑与研发人员读取的格式均可,这个表格中定义每一个指令field对应encoding的比特位序列。

  2. 读取表格,针对每一类相同编码规则的指令,自动生成形似下述代码的encoder methods。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
unsigned XXXInst1PostEncoder(const MCInst &MI, unsigned EncodedValue, const MCSubtargetInfo &STI) {
// code to transform EncodedValue
...

return EncodedValue;
}

unsigned XXXInst2PostEncoder(const MCInst &MI, unsigned EncodedValue, const MCSubtargetInfo &STI) {
// code to transform EncodedValue
...

return EncodedValue;
}

// other post encoder methods

LLVM Infrastrcture - PostEncoderMethod

LLVM tablegen类Instruction中包含成员PostEncoderMethod,对需要使用postencoder的指令类绑定相应的method,即可完成绑定,例如ARM架构中的类似代码:

1
2
3
4
5
6
7
8
class NDataI<dag oops, dag iops, Format f, InstrItinClass itin,
string opc, string dt, string asm, string cstr, list<dag> pattern>
: NeonI<oops, iops, AddrModeNone, IndexModeNone, f, itin, opc, dt, asm, cstr,
pattern> {
let Inst{31-25} = 0b1111001;
let PostEncoderMethod = "NEONThumb2DataIPostEncoder";
let DecoderNamespace = "NEONData";
}

这里为NDataI这类指令,绑定了一个Post encoder method,用于在code emitting时对encoding进行修改。

Shrink

Shrink操作并不少见,很多可变长指令集都有对encoding的shrink操作,即在指令编码阶段,根据指令集编码的定义,允许按照一定规则将指令编码进一步缩短。根据指令集特点,会有不同的shrink策略。当然也有一些架构代码中定义了类似shrink的pass,会做一些target specific的指令替换或立即数优化,这与我们工作中遇到的shrink不相同,以下会称之为“某架构”。

某架构最长支持128bit,最短32bit编码,指令各个field根据一些经验和profiler数据,将编码的bit位分布在不同的dword上,并且会给这些域定义一个缺省值,这样就可以根据128bit中4个dword的u32值来判断某条指令实际是否会占据更高32bit的bit位,从而帮助编译器判断是否可以做shrink。

根据以上描述,编译器会根据某条指令初始编码中的4个u32值,是否为默认值,来判断最短可以shrink到几个dword,并且在实际占据的若干个dword的最后一个的最后一位上,设置一个endbit,即简单设为1,舍去后续的encoding,即可完成shrink。

Inlined ptx/asm Impl

llvm有支持inline对应架构的asm汇编的基础设施,具体是定义一个继承MCAsmInfo类,做一些简单的配置和注册即可初步使能inline asm,当然前提是指令定义是tablegen中要定义好每个指令对应的汇编格式。以AMDGPU为例:

  • 定义并配置
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    // definition and configurations
    AMDGPUMCAsmInfo::AMDGPUMCAsmInfo(const Triple &TT,
    const MCTargetOptions &Options) {
    CodePointerSize = (TT.getArch() == Triple::amdgcn) ? 8 : 4;
    StackGrowsUp = true;
    HasSingleParameterDotFile = false;
    //===------------------------------------------------------------------===//
    MinInstAlignment = 4;

    // This is the maximum instruction encoded size for gfx10. With a known
    // subtarget, it can be reduced to 8 bytes.
    MaxInstLength = (TT.getArch() == Triple::amdgcn) ? 20 : 16;
    SeparatorString = "\n";
    CommentString = ";";
    InlineAsmStart = ";#ASMSTART";
    InlineAsmEnd = ";#ASMEND";

    //===--- Data Emission Directives -------------------------------------===//
    UsesELFSectionDirectiveForBSS = true;

    //===--- Global Variable Emission Directives --------------------------===//
    HasAggressiveSymbolFolding = true;
    COMMDirectiveAlignmentIsInBytes = false;
    HasNoDeadStrip = true;
    //===--- Dwarf Emission Directives -----------------------------------===//
    SupportsDebugInformation = true;
    UsesCFIWithoutEH = true;
    DwarfRegNumForCFI = true;

    UseIntegratedAssembler = false;
    }
  • 注册
    1
    2
    3
    4
    5
    extern "C" LLVM_EXTERNAL_VISIBILITY void LLVMInitializeAMDGPUTargetMC() {
    ...
    RegisterMCAsmInfo<AMDGPUMCAsmInfo> X(*T);
    ...
    }

那么inline ptx怎么做呢?其实也可以利用该机制,将ptx视为某个非ptx target所能识别的汇编,但要自定义ptx汇编语句的lexer、parser,并将inlined ptx codegen成llvm ir,所以这就要求将这个特殊的inlined asm处理的pass加在llvm ir阶段。

由于ptx其实功能非常繁多,直接generate成llvm ir,ir builder的开发量会比较大,并且有一些ptx指令功能其实是比较复杂的,因此我们也可以在llvm ir生成过程中,通过将ptx指令逻辑用c语言实现,放进libdevice库中,而在ir builder时直接生成libdevice function的call inst即可一定程度上提高实现的效率。同样,这也要求我们把pass加在llvm ir阶段,并且在always inliner之前,这样可以让libdevice function call自动inline。

这个实现方案有一个限制,就是编译器不太好区分是inline ptx还是inline native asm,此时需要牺牲掉inline ptx的语法检查功能,将无法解析的inline汇编语法认为是inline native asm,交给下一步inline native asm去处理。

CATALOG
  1. 1. LLVM Backend Practices - Part 1
    1. 1.1. PostEncoderMethod
      1. 1.1.1. Background
      2. 1.1.2. Solution
        1. 1.1.2.1. Code Auto-gen
        2. 1.1.2.2. LLVM Infrastrcture - PostEncoderMethod
    2. 1.2. Shrink
    3. 1.3. Inlined ptx/asm Impl