FPGA開発日記

FPGAというより、コンピュータアーキテクチャかもね! カテゴリ別記事インデックス https://msyksphinz.github.io/github_pages

Verilog-Perl を使ってRocket-Chipの階層構造を把握する

RISC-VのRocket-Chipを勉強していこうと思う。

まずはRocketがどのような階層構成になっているのか、生成されたVerilogを使って把握してみたい。

ずいぶんと古いツールだが、Verilog-Perlの中にvhierというツールが存在する。

metacpan.org

久しぶりにVerilog-Perlを使って、Rocket-Chipの階層構造を把握してみることにした。

cd rocket-chip
cd emulator
make # Verilogを生成
vhier --forest --instance generated-src/*.v ../src/main/resources/vsrc/*.v --top-module RocketTile +define+CLOCK_PERIOD=1.0 -o rocket.hier

生成されたrocket.hierを確認する。

とりあえずこんな感じで生成できた。あとはこれを掘り下げていく。

f:id:msyksphinz:20190611004930p:plain
  • rocket.hier
  RocketTile RocketTile
  |--buffer TLBuffer_11
  |  |--Queue Queue_51
  |  |--Queue_1 Queue_52
  |  |--Queue_2 Queue_53
  |  |--Queue_3 Queue_54
  |  |--Queue_4 Queue_55
  |  \--TLMonitor TLMonitor_41
  |     \--plusarg_reader plusarg_reader
  |--core Rocket
  |  |--PlusArgTimeout PlusArgTimeout
  |  |  \--plusarg_reader plusarg_reader
  |  |--alu ALU
  |  |--bpu BreakpointUnit
  |  |--csr CSRFile
  |  |--div MulDiv
  |  \--ibuf IBuf
  |     \--RVCExpander RVCExpander
  |--dcache DCache
  |  |--MaxPeriodFibonacciLFSR MaxPeriodFibonacciLFSR
  |  |--amoalu AMOALU
  |  |--data DCacheDataArray
  |  |  \--data_arrays_0 data_arrays_0
  |  |     \--data_arrays_0_ext data_arrays_0_ext
  |  |--dataArb DCacheModuleanon3
  |  |--metaArb DCacheModuleanon2
  |  |--tag_array tag_array
  |  |  \--tag_array_ext tag_array_ext
  |  \--tlb TLB
  |     |--packageanon2 packageanon2
  |     |--packageanon2_1 packageanon2
  |     |--packageanon2_10 packageanon2
  |     |--packageanon2_11 packageanon2
  |     |--packageanon2_12 packageanon2
  |     |--packageanon2_13 packageanon2
  |     |--packageanon2_14 packageanon2
  |     |--packageanon2_15 packageanon2
...
  |     |--packageanon2_8 packageanon2
  |     |--packageanon2_9 packageanon2
  |     \--pmp PMPChecker
  |--dcacheArb HellaCacheArbiter
  |--fpuOpt FPU
  |  |--dfma FPUFMAPipe_1
  |  |  \--fma MulAddRecFNPipe_1
  |  |     |--mulAddRecFNToRaw_postMul MulAddRecFNToRaw_postMul_1
  |  |     |--mulAddRecFNToRaw_preMul MulAddRecFNToRaw_preMul_1
  |  |     \--roundRawFNToRecFN RoundRawFNToRecFN_1
  |  |        \--roundAnyRawFNToRecFN RoundAnyRawFNToRecFN_4
  |  |--divSqrt DivSqrtRecFN_small
  |  |  |--divSqrtRecFNToRaw DivSqrtRecFNToRaw_small
  |  |  \--roundRawFNToRecFN RoundRawFNToRecFN
  |  |     \--roundAnyRawFNToRecFN RoundAnyRawFNToRecFN
  |  |--divSqrt_1 DivSqrtRecFN_small_1
  |  |  |--divSqrtRecFNToRaw DivSqrtRecFNToRaw_small_1
  |  |  \--roundRawFNToRecFN RoundRawFNToRecFN_1
  |  |     \--roundAnyRawFNToRecFN RoundAnyRawFNToRecFN_4
  |  |--fp_decoder FPUDecoder
  |  |--fpiu FPToInt
  |  |  |--RecFNToIN RecFNToIN
  |  |  |--RecFNToIN_1 RecFNToIN_1
  |  |  \--dcmp CompareRecFN
  |  |--fpmu FPToFP
  |  |  \--RecFNToRecFN RecFNToRecFN
  |  |     \--RoundAnyRawFNToRecFN RoundAnyRawFNToRecFN_3
  |  |--ifpu IntToFP
  |  |  |--INToRecFN INToRecFN
  |  |  |  \--roundAnyRawFNToRecFN RoundAnyRawFNToRecFN_1
  |  |  \--INToRecFN_1 INToRecFN_1
  |  |     \--roundAnyRawFNToRecFN RoundAnyRawFNToRecFN_2
  |  \--sfma FPUFMAPipe
  |     \--fma MulAddRecFNPipe
  |        |--mulAddRecFNToRaw_postMul MulAddRecFNToRaw_postMul
  |        |--mulAddRecFNToRaw_preMul MulAddRecFNToRaw_preMul
  |        \--roundRawFNToRecFN RoundRawFNToRecFN
  |           \--roundAnyRawFNToRecFN RoundAnyRawFNToRecFN
  |--frontend Frontend
  |  |--btb BTB
  |  |--fq ShiftQueue
  |  |--icache ICache
  |  |  |--MaxPeriodFibonacciLFSR MaxPeriodFibonacciLFSR
  |  |  |--data_arrays_0 data_arrays_0_0
  |  |  |  \--data_arrays_0_0_ext data_arrays_0_0_ext
  |  |  |--data_arrays_1 data_arrays_0_0
  |  |  |  \--data_arrays_0_0_ext data_arrays_0_0_ext
  |  |  \--tag_array tag_array_0
  |  |     \--tag_array_0_ext tag_array_0_ext
  |  \--tlb TLB_1
  |     |--packageanon2 packageanon2
  |     |--packageanon2_1 packageanon2
  |     |--packageanon2_10 packageanon2
...
  |     |--packageanon2_9 packageanon2
  |     \--pmp PMPChecker_1
  |--intXbar IntXbar_4
  |--intsink IntSyncCrossingSink
  |  \--SynchronizerShiftReg_w1_d3 SynchronizerShiftReg_w1_d3
  |--intsink_1 IntSyncCrossingSink_1
  |--intsink_2 IntSyncCrossingSink_2
  |--intsink_3 IntSyncCrossingSink_2
  |--ptw PTW
  |  |--arb RRArbiter
  |  |--packageanon2 packageanon2_78
  |  \--packageanon2_1 packageanon2_79
  \--tlMasterXbar TLXbar_8
     |--TLMonitor TLMonitor_39
     |  \--plusarg_reader plusarg_reader
     \--TLMonitor_1 TLMonitor_40
        \--plusarg_reader plusarg_reader

LLVMのバックエンドを作るための第一歩 (22. RISC-Vに存在しないパタンをどのように命令生成するか)

f:id:msyksphinz:20190425001356p:plain

例えば、以下のようなプログラムをコンパイルする。 このプログラムは命令を生成する段階でローテートを示すIRを生成する。

int test_rotate_left()
{
  unsigned int a = 8;
  int result = ((a << 30) | (a >> 2));

  return result;
}
./bin/clang -target mips-unknown-linux-gnu -c ../lbdex/input/ch4_1_rotate.cpp -emit-llvm
./bin/llc -debug -march=myriscvx32 -mcpu=simple32 -mattr=+64bit -relocation-model=pic -filetype=asm ch4_1_rotate.bc -o -
...
Combining: t12: i32 = FrameIndex<1>

Combining: t11: i32 = or t8, t10
Creating new node: t18: i32 = rotl t6, Constant:i32<30>
 ... into: t18: i32 = rotl t6, Constant:i32<30>

Combining: t13: ch = store<(store 4 into %ir.result)> t6:1, t18, FrameIndex:i32<1>, undef:i32
...

最終的に、rotlを命令に変換しようとするが、当然エラーとなる。

ISEL: Starting pattern match
  Initial Opcode index to 0
  Match failed at index 0
LLVM ERROR: Cannot select: t18: i32 = rotl t6, Constant:i32<30>
  t6: i32,ch = load<(dereferenceable load 4 from %ir.a)> t5, FrameIndex:i32<0>, undef:i32
    t2: i32 = FrameIndex<0>
    t4: i32 = undef
  t7: i32 = Constant<30>
In function: _Z16test_rotate_leftv

このような場合どうすればよいのかというと、MYRISCVXTargetLoweringに以下を追加する。

  • llvm-myriscvx/lib/Target/MYRISCVX/MYRISCVXSEISelLowering.cpp
//@MYRISCVXSETargetLowering {
MYRISCVXSETargetLowering::MYRISCVXSETargetLowering(const MYRISCVXTargetMachine &TM,
                                                   const MYRISCVXSubtarget &STI)
...

  setOperationAction(ISD::ROTL, MVT::i32, Expand);
  setOperationAction(ISD::ROTR, MVT::i32, Expand);
}

llvm::TargetLoweringBase::setOperationActionは、特定の命令においてターゲットアーキテクチャでのネイティブ命令が生成できない場合に指定する。これにより、一般的な命令に分解を行い命令を生成する。

./bin/llc -debug -march=myriscvx32 -mcpu=simple32 -mattr=+64bit -relocation-model=pic -filetype=asm ch4_1_rotate.bc -o - 
        addi    x2, x2, -8
        addi    x10, zero, 8
        sw      x10, 4(x2)
        lw      x10, 4(x2)
        slli    x11, x10, 30
        srli    x10, x10, 2
        or      x10, x11, x10
        sw      x10, 0(x2)
        lw      x10, 0(x2)
        addi    x2, x2, 8
        ret     x1
f:id:msyksphinz:20190608182451p:plain
Combineによるrotlが生成されるが、これを防ぎたい。

LLVMのバックエンドを作るための第一歩 (21. 演算命令の追加)

f:id:msyksphinz:20190425001356p:plain

定数が生成できるようになったので、次は演算命令を追加する。 こちらも同様にMYRISCVXInstrInfo.tdに命令パタンを追加していく。

  • llvm-myriscvx/lib/Target/MYRISCVX/MYRISCVXInstrInfo.td
def ADDI : ArithLogicI<0b0010011, 0b000, "addi", add, simm12, immSExt12, GPR>;
def XORI : ArithLogicI<0b0010011, 0b100, "xori", xor, uimm12, immZExt12, GPR>;
def ORI  : ArithLogicI<0b0010011, 0b110, "ori",  or,  uimm12, immZExt12, GPR>;
def ANDI : ArithLogicI<0b0010011, 0b111, "andi", and, uimm12, immZExt12, GPR>;

def LUI  : ArithLogicU<0b0110111, "lui", GPR, simm20, immSExt20>;
def ADD  : ArithLogicR<0b0110011, 0b000, 0b0000000, "add", add, GPR>;
def SUB  : ArithLogicR<0b0110011, 0b000, 0b0100000, "sub", sub, GPR>;

def SLL  : shift_rotate_reg<0b0110011, 0b0000000, 0b001, 0x0, "sll", shl, GPR>;
def AND  : ArithLogicR<0b0110011, 0b111, 0b0000000, "and", and, GPR>;
def SRL  : shift_rotate_reg<0b0110011, 0b0000000, 0b101, 0x0, "srl", srl, GPR>;
def SRA  : shift_rotate_reg<0b0110011, 0b0100000, 0b101, 0x0, "sra", sra, GPR>;
def OR   : ArithLogicR<0b0110011, 0b110, 0b0000000, "or",  or,  GPR>;
def XOR  : ArithLogicR<0b0110011, 0b100, 0b0000000, "xor", xor, GPR>;

ArithLogicI, ArithLogicRの定義を見直してみる。

// Arithmetic and logical instructions with 2 register operands.
class ArithLogicR<bits<7> opcode, bits<3> funct3, bits<7>funct7,
                  string instr_asm, SDNode OpNode,
                  RegisterClass RC> :
  MYRISCVX_R<opcode, funct3, funct7, (outs RC:$rd), (ins RC:$rs1, RC:$rs2),
  !strconcat(instr_asm, "\t$rd, $rs1, $rs2"),
  [(set RC:$rd, (OpNode RC:$rs1, RC:$rs2))], IIAlu> {
    let isReMaterializable = 1;
}


// Arithmetic and logical instructions with 2 register operands.
class ArithLogicI<bits<7> opcode, bits<3> funct3,
                  string instr_asm, SDNode OpNode,
                  Operand Od, PatLeaf imm_type, RegisterClass RC> :
  MYRISCVX_I<opcode, funct3, (outs RC:$rd), (ins RC:$rs1, Od:$imm12),
  !strconcat(instr_asm, "\t$rd, $rs1, $imm12"),
  [(set RC:$rd, (OpNode RC:$rs1, imm_type:$imm12))], IIAlu> {
    let isReMaterializable = 1;
}

例えば、

def ADD  : ArithLogicR<0b0110011, 0b000, 0b0000000, "add", add, GPR>;

の場合、

  • MYRISCVX_Rクラスをベースに扱う。
  • 命令ニーモニック!strconcat(instr_asm, "\t$rd, $rs1, $rs2")add\t$rd, $rs1, $rs2となる。
  • 命令の動作パタンは、(set RC:$rd, (add RC:$rs1, RC:$rs2))
f:id:msyksphinz:20190608175058p:plain
ADDI命令の定義方法
f:id:msyksphinz:20190608175215p:plain
ADD命令の定義方法

となる。これを、算術演算、論理演算について定義して行く。

シフト命令は特殊なフォーマットを定義して、即値、レジスタ指定両方のフォーマットを決める。

  • llvm-myriscvx/lib/Target/MYRISCVX/MYRISCVXInstrInfo.td
class MYRISCVX_ISHIFT<bits<7> opcode, bits<3> funct3, bit arithshift,
                      dag outs, dag ins, string asmstr, list<dag> pattern,
                      InstrItinClass itin>:
  MYRISCVXInst<outs, ins, asmstr, pattern, itin, FrmI>
{
  bits<5> rs1;
  bits<5> rd;
  bits<6> shamt;

  let Inst{31} = 0;
  let Inst{30} = arithshift;
  let Inst{29-26} = 0;
  let Inst{25-20} = shamt;
  let Inst{19-15} = rs1;
  let Inst{14-12} = funct3;
  let Inst{11-7}  = rd;
  let Inst{6-0}   = opcode;
}
  • llvm-myriscvx/lib/Target/MYRISCVX/MYRISCVXInstrInfo.td
// Shifts
class ShiftImm<bits<7> opcode, bits<3> funct3, bit arithshift, string instr_asm,
               SDNode OpNode, PatFrag PF, Operand ImmOpnd,
               RegisterClass RC>:
  MYRISCVX_ISHIFT<opcode, funct3, arithshift, (outs RC:$rd), (ins RC:$rs1, ImmOpnd:$shamt),
                  !strconcat(instr_asm, "\t$rd, $rs1, $shamt"),
                  [(set GPR:$rd, (OpNode RC:$rs1, PF:$shamt))], IIAlu> {
}


// 32-bit shift instructions.
class ShiftImm32<bits<7> opcode, bits<3> funct3, bit arithshift, string instr_asm,
                         SDNode OpNode>:
  ShiftImm<opcode, funct3, arithshift, instr_asm, OpNode, immZExt5, shamt, GPR>;


class ShiftR<bits<7> opcode, bits<7> funct7, bits<3> funct3,
             bits<4> isRotate, string instr_asm,
             SDNode OpNode, RegisterClass RC>:
  MYRISCVX_R<opcode, funct3, funct7, (outs RC:$ra), (ins RC:$rb, RC:$rc),
          !strconcat(instr_asm, "\t$ra, $rb, $rc"),
          [(set GPR:$ra, (OpNode RC:$rb, RC:$rc))], IIAlu> {
}

ShiftRShiftImm32を使って、即値とレジスタ指定のシフト命令を定義する。

  • llvm-myriscvx/lib/Target/MYRISCVX/MYRISCVXInstrInfo.td
def SLL  : ShiftR<0b0110011, 0b0000000, 0b001, 0x0, "sll", shl, GPR>;
def SRL  : ShiftR<0b0110011, 0b0000000, 0b101, 0x0, "srl", srl, GPR>;
def SRA  : ShiftR<0b0110011, 0b0100000, 0b101, 0x0, "sra", sra, GPR>;

def SRLI : ShiftImm32<0b0010011, 0b101, 0, "srli", srl>;
def SLLI : ShiftImm32<0b0010011, 0b001, 0, "slli", shl>;
def SRAI : ShiftImm32<0b0010011, 0b101, 1, "srai", sra>;

まず、LLVM IRが正しく命令に変換されるかテストしてみる。以下のch4_math.llを使用する。

; Function Attrs: nounwind
define i32 @_Z9test_mathv() #0 {
  %a = alloca i32, align 4
  %b = alloca i32, align 4
  %1 = load i32, i32* %a, align 4
  %2 = load i32, i32* %b, align 4

  %3 = add nsw i32 %1, %2
  %4 = sub nsw i32 %1, %2
  %5 = mul nsw i32 %1, %2
  %6 = shl i32 %1, 2
  %7 = ashr i32 %1, 2
  %8 = lshr i32 %1, 30
  %9 = shl i32 1, %2
  %10 = ashr i32 128, %2
  %11 = ashr i32 %1, %2

  %12 = add nsw i32 %3, %4
  %13 = add nsw i32 %12, %5
  %14 = add nsw i32 %13, %6
  %15 = add nsw i32 %14, %7
  %16 = add nsw i32 %15, %8
  %17 = add nsw i32 %16, %9
  %18 = add nsw i32 %17, %10
  %19 = add nsw i32 %18, %11

  ret i32 %19
}
./bin/llc -debug -march=myriscvx32 -mcpu=simple32 -mattr=+64bit -relocation-model=pic -filetype=asm ../lbdex/input/ch4_math.ll -o -
        .globl  _Z9test_mathv           # -- Begin function _Z9test_mathv
        .type   _Z9test_mathv,@function
        .ent    _Z9test_mathv           # @_Z9test_mathv
_Z9test_mathv:
        .cfi_startproc
        .frame  $x8,8,$x1
        .mask   0x00000000,0
        .set    noreorder
        .set    nomacro
        discovered a new reachable node %bb.0
# %bb.0:
        addi    x2, x2, -8
        .cfi_def_cfa_offset 8
        lw      x10, 0(x2)
        lw      x11, 4(x2)
        sub     x12, x11, x10
        add     x13, x11, x10
        add     x12, x13, x12
        mul     x13, x11, x10
        add     x12, x12, x13
        slli    x13, x11, 2
        add     x12, x12, x13
        srai    x13, x11, 2
        add     x12, x12, x13
        srli    x13, x11, 30
        add     x12, x12, x13
        addi    x13, zero, 1
        sll     x13, x13, x10
        add     x12, x12, x13
        sra     x11, x11, x10
        addi    x13, zero, 128
        srl     x10, x13, x10
        add     x10, x12, x10
        add     x10, x10, x11
        addi    x2, x2, 8
        ret     x1
        .set    macro
        .set    reorder
        .end    _Z9test_mathv
$func_end0:
        .size   _Z9test_mathv, ($func_end0)-_Z9test_mathv
        .cfi_endproc
                                        # -- End function

        .section        ".note.GNU-stack","",@progbits

命令が生成されていることが確認できた。なかなか良さそうだ。

さらに、乗除算命令を追加する。

  • llvm-myriscvx/lib/Target/MYRISCVX/MYRISCVXInstrInfo.td
def MUL   : ArithLogicR<0b0110011, 0b000, 0b0000001, "mul",    mul,   GPR>;
def MULH  : ArithLogicR<0b0110011, 0b001, 0b0000001, "mulh",   mulhs, GPR>;
def MULHSU: ArithLogicR<0b0110011, 0b010, 0b0000001, "mulhsu", mulhs, GPR>;
def MULHU : ArithLogicR<0b0110011, 0b011, 0b0000001, "mulhu",  mulhu, GPR>;
def DIV   : ArithLogicR<0b0110011, 0b100, 0b0000001, "div",    sdiv,  GPR>;
def DIVU  : ArithLogicR<0b0110011, 0b101, 0b0000001, "divu",   udiv,  GPR>;
def REM   : ArithLogicR<0b0110011, 0b110, 0b0000001, "rem",    srem,  GPR>;
def REMU  : ArithLogicR<0b0110011, 0b111, 0b0000001, "remu",   urem,  GPR>;

これで乗除算命令が生成できるようになる。

int test_mult()
{
  int a = 12;
  int b = 11;

  b = a * b;

  return b;
}
./bin/clang -target mips-unknown-linux-gnu -c ../lbdex/input/ch4_1_multtest.cpp -emit-llvm
./bin/llc -debug -march=myriscvx32 -mcpu=simple32 -mattr=+64bit -relocation-model=pic -filetype=asm ch4_1_multtest.bc -o -
# %bb.0:                                # %entry
        addi    x2, x2, -8
        addi    x10, zero, 12
        sw      x10, 4(x2)
        addi    x10, zero, 11
        sw      x10, 0(x2)
        lw      x10, 4(x2)
        lw      x11, 0(x2)
        mul     x10, x10, x11
        sw      x10, 0(x2)
        lw      x10, 0(x2)
        addi    x2, x2, 8
        ret     x1
        .set    macro
        .set    reorder
        .end    _Z9test_multv
$func_end0:

LLVMのバックエンドを作るための第一歩 (20. 定数を生成させるパタンの追加)

f:id:msyksphinz:20190425001356p:plain

簡単な関数をコンパイルしてアセンブリ命令が出力できるようになったので、様々な命令をサポートしていきましょう。MYRISCVXに命令を追加して、生成できるコードの量を増やしていく。

基本的な算術演算から進めるのが良いと思う。 MYRISCVXに命令を追加することで、算術演算命令が生成できるようになる。

定数を生成させるパタンの追加

まず、算術演算命令を生成させる前に、テストなどで頻繁に出てくる定数を生成させるパタンをLLVMに追加する。 例えば、0x01234567という32ビット定数をレジスタに格納したい場合、MYRISCVXはどのように命令を生成すればよいのだろうか。

RISC-Vには、lui命令というものが定義されている。 lui命令は20ビットの即値オペランドを持ち、32ビットのうち上位の20ビットにオペランドの値を設定する。 さらに、ori命令は12ビットの即値オペランドを取ることができるので、まずは上位の20ビットを設定して、ori命令で下位の12ビットを設定すれば、任意の32ビット値を生成できると考えられる。

このルールをMYRISCVXInstrInfo.tdに追加すればよいことが分かる。 ここでは、以下の3つルールを追加する。

  • 12ビット以内に収まる符号付き定数 : immSExt12 は、以下のルールで表現される。
def immSExt12 : PatLeaf<(imm), [{ return isInt<12>(N->getSExtValue()); }]>;

これは、$zeroレジスタとのaddiで生成することができます。符号付きなので、addi命令を使う。

def : Pat<(i32 immSExt12:$in),
          (ADDI ZERO, imm:$in)>;
  • 12ビット以内に収まる符号なし定数 : immZExt12は、以下のルールで表現される。
def immZExt12 : PatLeaf<(imm), [{
  if (N->getValueType(0) == MVT::i32)
    return (uint32_t)N->getZExtValue() == (unsigned short)N->getZExtValue();
  else
    return (uint64_t)N->getZExtValue() == (unsigned short)N->getZExtValue();
}], LO12>;

これは、addiの代わりにoriを使用する。

def : Pat<(i32 immZExt12:$in),
          (ORI ZERO, imm:$in)>;
  • 下位12ビットが0の定数 : immLow12Zero は、以下のルールで表現される。
// Immediate can be loaded with LUi (32-bit int with lower 16-bit cleared).
def immLow12Zero : PatLeaf<(imm), [{
  int64_t Val = N->getSExtValue();
  return isInt<32>(Val) && !(Val & 0x0fff);
}]>;

これは、単純にlui命令だけで良いだろう。

def : Pat<(i32 immLow12Zero:$in),
          (LUI (HI20 imm:$in))>;
  • 32ビット整数 : immは、以下の手順で生成する。まずはlui命令で上位の20ビットを作り、次にoriで買いの12ビットを連結する。
def : Pat<(i32 imm:$imm),
          (ORI (LUI (HI20 imm:$imm)), (LO12 imm:$imm))>;
// Transformation Function - get the lower 12 bits.
def LO12 : SDNodeXForm<imm, [{
  return getImm(N, N->getZExtValue() & 0xfff);
}]>;

// Transformation Function - get the higher 20 bits.
def HI20 : SDNodeXForm<imm, [{
  return getImm(N, (N->getZExtValue() >> 12) & 0xfffff);
}]>;
f:id:msyksphinz:20190608010144p:plain
定数の生成方法

ET-West 2019にてRISC-Vとオープンハードウェアについてテクニカルセッションを行います

2019/06/13(木)~2019/06/14(金)に開催されるET-Westと呼ばれる技術展で、テクニカルセッションとしてRISC-Vに関する講演を行います。

www.jasa.or.jp

f:id:msyksphinz:20190606224125p:plain

またしても90分です。最近90分のセッションしか喋っていない気がする。90分って本当に疲れますね。

去年のETでも似たようなタイトルで90分も話した気がしますが、タイトルは同じでも内容はかなり変えました。

デモンストレーションも含んでいますが、前回とは全く異なるデモンストレーションをします。 xSIGでもデモはしたのですが、その内容ともすこし変えようと思います。資料自体も、xSIGのものと少し変えます。

っていうか私ET-Westに行くのは初めてなんですけど、関西での組み込み業界とか、技術展の盛り上がりってどうなんでしょうね? 関西に行くのも久しぶりなので少し緊張します。やはりたこ焼きとか食べるべきなのかしら。

ちなみに、この週は京都でVLSIもやってるらしいんですよね。VLSIの最終日で、もう見るものが無くなった、という方はぜひ大阪に足を運んでみてください。

vlsisymposium.org

という訳で、関西方面のRISC-Vに興味のある方は、よろしくお願いいたします。

LLVMのバックエンドを作るための第一歩 (19. 関数のスタックフレームの作り方)

f:id:msyksphinz:20190425001356p:plain

MYRISCVXRegisterInfo::eliminateFrameIndex

これも必須の関数だ。関数のスタックフレーム内での参照について、計算できていなかったオフセットを計算するための関数だ。

このeliminateFrameIndexが呼ばれるまでは、スタックポインタをベースに参照されるデータのオフセットは計算できていない。このeliminateFrameIndexによって、最終的なオフセットを計算する。

そもそもどのようにしてスタックオフセットを計算するかというと、まず、関数のプロローグでスタックポインタをデクリメントすることでスタックを割り当てる。 スタックポインタがデクリメントされると、すべてのスタックへの参照はスタック・フレームポインタからの+方向へのオフセット参照となる。

ここでは、MIPSに倣いスタックフレームを以下のように構成する。

//  0                 ----------
//  4                 引数一覧
//  .                 PICで保存するGP
//  .                 allocaによる割り当て
//  .                 ローカル領域
//  .                 CPUのCallee Savedレジスタ
//  .                 保存されたFP
//  .                 保存されたRA
//  .                 FPUのCallee Savedレジスタ
//  StackSize         -----------
f:id:msyksphinz:20190606003013p:plain
スタックが割り当てられる領域。

全体のスタックサイズはLowerFormalArgumentsではまだ未決定で、例えば関数の引数へのアクセスのために挿入されるスタックベースのメモリアクセス(ObjectOffset)は負数が挿入されている。 これにより、eliminateFrameIndexはこの参照を検出し、実際のアクセスに変換する、というわけだ。

  • EmitPrologue, EmitEpilogue, EliminateFrameIndex実行前のLLVM IR
bb.0.entry:
  $a0 = ADDI $zero, 0
  SW $a0, %stack.0.retval, 0 :: (store 4 into %ir.retval)
  RetRA implicit $a0
  • EmitPrologue, EmitEpilogue, EliminateFrameIndex実行後のLLVM IR
bb.0.entry:
  $sp = ADDI $sp, -8
  CFI_INSTRUCTION def_cfa_offset 8
  $a0 = ADDI $zero, 0
  SW $a0, $sp, 4 :: (store 4 into %ir.retval)
  $sp = ADDI $sp, 8
  RetRA implicit $a0

SW命令のアドレスがレジスタとオフセットに置き換えられていることが分かる。

LLVMのバックエンドを作るための第一歩 (18. emitPrologue, emitEpilogue)

f:id:msyksphinz:20190425001356p:plain

最終的に関数をコンパイルして命令を出力するためには、関数のプロローグ・エピローグを生成する必要がある。 関数のプロローグは関数に入った時の最初に行う処理、関数のエピローグは関数から出るときに最後に行う処理である。

基本的には

  • Callee Saved Registerの退避
  • スタックポインタの移動

が必要になる。これらを実行するのが、LLVM

  • MYRISCVXSEFrameLowering::emitPrologue()
  • MYRISCVXSEFrameLowering::emitEpilogue()

である。それぞれについて内部の実装を確認していく。

MYRISCVXSEFrameLowering::emitPrologue()

emitPrologueは関数のエピローグのコードを生成する。

  • llvm-myriscvx/lib/Target/MYRISCVX/MYRISCVXSEFrameLowering.cpp
void MYRISCVXSEFrameLowering::emitPrologue(MachineFunction &MF,
                                           MachineBasicBlock &MBB) const {

引数としては2つ用意されている。MachineFunction MFは変換対象となる関数の実体で、関数内部のBasicBlockなどを含んでいる。 MachieBasicBlockのインスタンスのリストを含んでおり、MachineFunctionにはMachineControlPool, MachineFrameInfo, MachineFunctionInfo, MachineRegisterInfoクラスが含まれている。

MachineBasicBlock MBBは変換後のBasicBlockだ。MachineBasicBlockにはマシン命令のリストであるMachineInstrのリストが含まれている。

実際には、EmitPrologue内では関数内で使用されるスタックのサイズを計算し、そのその分だけスタックポインタの量を調整する。

  • llvm-myriscvx/lib/Target/MYRISCVX/MYRISCVXSEFrameLowering.cpp
void MYRISCVXSEFrameLowering::emitPrologue(MachineFunction &MF,
                                           MachineBasicBlock &MBB) const {
...
  // First, compute final stack size.
  uint64_t StackSize = MFI.getStackSize();

  // No need to allocate space on the stack.
  if (StackSize == 0 && !MFI.adjustsStack()) return;

  MachineModuleInfo &MMI = MF.getMMI();
  const MCRegisterInfo *MRI = MMI.getContext().getRegisterInfo();

  // Adjust stack.
  TII.adjustStackPtr(SP, -StackSize, MBB, MBBI);
...

また、EmitEpilogueではその逆で、関数内のスタックサイズの分だけスタックポインタを元に戻す。

  • llvm-myriscvx/lib/Target/MYRISCVX/MYRISCVXSEFrameLowering.cpp
//@emitEpilogue {
void MYRISCVXSEFrameLowering::emitEpilogue(MachineFunction &MF,
                                           MachineBasicBlock &MBB) const {
...
  unsigned SP = MYRISCVX::SP;

  // Get the number of bytes from FrameInfo
  uint64_t StackSize = MFI.getStackSize();

  if (!StackSize)
    return;

  // Adjust stack.
  TII.adjustStackPtr(SP, StackSize, MBB, MBBI);
...