MENU

为rvv-llvm添加vleff intrinsic支持

February 9, 2021 • 实习

什么是LLVM intrinsic

简单来说,intrinsic是一种函数的统称,这种函数是一种可以让高级语言(如C/C++)直接调用汇编指令的接口或映射。例如在RISCV V扩展中,C语言程序可以通过引入头文件,调用响应函数直接访问相应的V扩展向量指令。

为LLVM添加intrinsic函数的具体流程

要想知道添加intrinsic函数的具体流程,就要对LLVM编译生成汇编指令的流程有一个初步的了解。

llvm架构

LLVM编译代码文件时,大致涉及到三类文件,分别是,代码源文件,LLVM IR文件 以及 最后生成的汇编(.ll)文件

因此想要添加intrinsic支持,我们也需要分别对生成IR生成汇编的两个过程进行处理,这两个过程分别对应 clang 和 LLVM 两部分。

我们由前端clang开始到后端LLVM进行intrinsic的添加工作。

我们以中科院软件所PLCT实验室的RVV-LLVM为例,为其添加指令vleff的intrinsic支持。

Clang 部分

1. riscv_vector.td

添加intrinsic的工作从高级语言C/C++的调用开始,生成调用方法所需的头文件

在RVV-LLVM中,调用用到的头文件为#include<riscv_vector.h>, 因此需要先在该头文件内添加函数声明,但是RVV-LLVM已经在仓库中定义了相应生成头文件TableGen脚本。因此我们只需在脚本中添加相应的结构定义就可以了

具体TableGen脚本的使用方法和语法规范请参考 TableGen 简介及使用

生成riscv_vector.h的脚本位于clang/include/clang/Basic/riscv_vector.td路径下。

代码比较冗长,需要通过一两个例子来理解整体的生成逻辑。

// riscv_vector.td
foreach I = AllVectorType in {
    def "vle"#I.SEW#"ff_v"#I.Name : Inst<"vle"#I.SEW#"ff",
[I.Value, I.ElemType.Value#"C*"], "_v", I.Name, 0, [-1, 0]>;
    def "vle"#I.SEW#"ff_v"#I.Name#"_mask" : Inst<"vle"#I.SEW#"ff",
[I.Value, I.BoolValue, I.Value, I.ElemType.Value#"C*"], "_v", I.Name, 1, [-1, 0, 2]>;
}

以上代码是生成vleff相应头文件声明的代码,在这里,需要了解AllVectorTypeInst,对应的代码如下:

// riscv_vector.td
class Inst<string name, list<string> types, string inf, string suf,
           int is_mask, list<int> anytype_operands, code definition = ""> {
  string IntrinsicName = name;
  list<string> BuiltinStr = types;
  string Infix = inf;
  string Suffix = suf;
  // The list of indexes for the overload type. Use '-1' to refer to result
  // type, '0' to refer to the first argument type and so on. This is used
  // to generate clang builtin codegen snippet. The given indexes must be
  // consisted with llvm/include/llvm/IR/IntrinsicsRISCV.td. For an example,
  // the index list of
  //   class V_VX : Intrinsic<[ llvm_anyvector_ty ],
  //                          [ LLVMMatchType<0>, llvm_anyint_ty ],
  //                          [ IntrNoMem ]>;
  // should be:
  //   [-1, 1]
  list<int> AnyTypeOperands = anytype_operands;
  int Mask = is_mask;
  // If not empty, the emitter will use it to define the intrinsic function.
  // Otherwise, the emitter will generate intrinsic in the default way.
  code CustomDef = definition;
  // Should emit BUILTIN macro. Set to false if the intrinsic function can
  // be implemented by other existing builtin functions.
  bit ShouldEmitBuiltin = 1;
} 

AllVectorType指的就是所有RISCV V的向量类型,例如i8mf8`i8mf4`等。用来对应vleff doc文档中定义的函数名。

Inst类中,Infix应该对应v扩展intrinsic的标识_vSuffix对应相应的向量类型,BuiltinStr对应文档中定义的函数的参数。

改TableGen文件运行会生成相应的riscv_vector.h

2. RISCVVectorEmitter.cpp

上一步修改完TableGen后,需要检查riscv_vector_builtins.incriscv_vector_builtin_cg.inc两个文件。检查发现riscv_vector_builtin_cg.inc调用的响应函数有误,vload是指令vle的intrinsic使用的,因此我们需要为vleff创建新的intrinsic函数。

case RISCV::BI__builtin_riscv_vle32ff_v_u32mf2:
{
    Function *F = CGM.getIntrinsic(Intrinsic::riscv_vload, {ResultType, Ops[0]->getType()});
    return Builder.CreateCall(F, Ops);
}

于是修改clang/utils/TableGen/RISCVVectorEmitter.cpp。改文件中,主要修改的是函数Intrinsic::createStatementInCase(),这个函数用于生成riscv_vector_builtin_cg.inc中intrinsic函数的调用。

根据该函数的逻辑,添加如下代码

// case of command "vle*ff"
if(std::string(Name.begin(), NameIter-1)=="vle" 
    && std::string(Name.end()-2, Name.end())=="ff"){
     result += "_faultfirst";
}

编译之后生成riscv_vector_builtin_cg.inc中代码为:

case RISCV::BI__builtin_riscv_vle32ff_v_u32mf2:
{
    Function *F = CGM.getIntrinsic(Intrinsic::riscv_vload_faultfirst, {ResultType, Ops[0]->getType()});
    return Builder.CreateCall(F, Ops);
}

3. IntrinsicsRISCV.td

llvm/include/llvm/IR/IntrinsicsRISCV.td编译生成IntrinsicsRISCV.h,定义了LLVM支持的全部intrinsic函数,因为我们在之前使用了两个个全新的intrinsic函数因此需要新定义两条intrinsic函数(_faultfirst_faultfirst_mask)。

添加的定义如下:

def "_faultfirst" : Intrinsic<[llvm_anyvector_ty],
                                [llvm_anyptr_ty],
                                [IntrReadMem]>;
def "_faultfirst_mask" : Intrinsic<[llvm_anyvector_ty],
                                    [llvm_anyvector_ty, LLVMMatchType<0>,
                                    llvm_anyptr_ty],
                                    [IntrReadMem]>;

4. 测试Clang部分,生成IR

添加测试文件clang/test/CodeGen/riscv-vector-intrinsics/vle8ff_v.c,引入头文件<riscv_vector.h>,调用相应函数:

// RUN: %clang_cc1 -triple riscv64-unknown-linux-gnu -target-feature +experimental-v -fallow-half-arguments-and-returns -fnative-half-type -S -emit-llvm  %s -o - |  FileCheck %s

#include <riscv_vector.h>

vint8mf8_t test_vle8ff_v_i8mf8 (int8_t *base) {
  // CHECK-LABEL: test_vle8ff_v_i8mf8
  // CHECK: %{{.*}} = call <vscale x 1 x i8> @llvm.riscv.vload.faultfirst{{.*}}nxv1i8{{.*}}(i8* %{{.*}})
  return vle8ff_v_i8mf8(base);
}

重新编译LLVM,用LLVM编译vle8ff_v.c~/rvv-llvm/build/bin/clang -cc1 -internal-isystem ./build/lib/clang/12.0.0/include -triple riscv64-unknown-linux-gnu -target-feature +experimental-v -S -emit-llvm -o0 ./clang/test/CodeGen/riscv-vector-intrinsics/vle8ff_v.c

生成相应的IR,位于根目录的0文件内:

顺利生成,即证明clang部分完成。

LLVM 部分

1. RISCVPseudoInfoV.td

LLVM部分相对简单,主要涉及llvm/lib/Target/RISCV/RISCVPseudoInfoV.td的两处修改。

添加如下两段内容:

// vleff
      let Uses = [VL, VTYPE], SEWIndex = 4, MergeOpIndex = 1,
        BaseInstr = !cast<Instruction>("VLE"#sew#"FF_V") in 
        def PseudoVLE#sew#FF_V_M#lmul
          : Pseudo<(outs I.RegClass:$rd), (ins I.RegClass:$merge,
                 GPR:$rs1, VMaskOp:$mask, ixlenimm:$sew), []>,
          RISCVVectorPseudo;
          
def : Pat<(I.Type (int_riscv_vload_faultfirst i64:$rs1)),
              (I.Type (!cast<Instruction>("PseudoVLE"#I.SEW#"FF_V_M"#str)
                        (I.Type zero_reg), i64:$rs1, (I.Mask zero_reg),
                        I.SEW))>;

测试LLVM部分

添加测试文件 llvm/test/CodeGen/RISCV/rvv/vle8ff_v.ll ,将Clang部分生成的IR粘贴进去

; RUN: llc -mtriple=riscv64 -mattr=+experimental-v < %s | FileCheck -check-prefix=CHECK %s

; Function Attrs: noinline nounwind optnone
define <vscale x 1 x i8> @test_vle8ff_v_i8mf8(i8* %base) {
entry:
; CHECK-LABEL: vle8ff_v_i8mf8
; CHECK: vle8ff.v {{v[0-9]+}}, ({{a[0-9]+}})
  %call = tail call <vscale x 1 x i8> @llvm.riscv.vload.faultfirst.nxv1i8.p0i8(i8* %base)
  ret <vscale x 1 x i8> %call
}

重新编译LLVM,用LLVM编译vle8ff_v.ll :./build/bin/llc -mtriple=riscv64 -mattr=+experimental-v -verify-machineinstrs < ./llvm/test/CodeGen/RISCV/rvv/vle8ff_v.ll

顺利运行,输出正确的汇编文件,即证明LLVM部分完成。

Last Modified: September 8, 2021