FPGA開発日記

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

RISC-VでZephyr OSを動作させる (2. HiFive1でZephyr OSを動作させる)

f:id:msyksphinz:20190415004633p:plain

Zephyrとは、Linux Foundationのプロジェクトの一つでもあり、RTOS(Real Time Operating System)の一つだ。

次は、SiFive社から販売されている32bit RISC-VボードHiFive1を使ってリアルタイムOSであるZephyrを動かしてみる。

f:id:msyksphinz:20190416020805p:plain

参考にするのは、RISC-VのGetting Started GuideのZephyrの章(https://risc-v-getting-started-guide.readthedocs.io/en/latest/zephyr-hifive1.html#flashing)だ。 こちらの章に従って進めていくと、HiFive1ボードの上でZephyr OSを動作させ、アプリケーションを動かすことができる。

GDBとOpenOCDのダウンロード

まずは、HiFive1ボードへのZephyrのアップロードとデバッグに必要なGDB, OpenOCDをダウンロードする。ここでは、すべての作業を${HOME}/riscv/zephyr/ディレクトリ上で行う。 まずは、以下のように入力し、GDBとOpenOCDをダウンロードする。

curl -L https://static.dev.sifive.com/dev-tools/riscv64-unknown-elf-gcc-2018.07.0-x86_64-linux-ubuntu14.tar.gz | tar xz
curl -L https://static.dev.sifive.com/dev-tools/riscv-openocd-2018.7.0-x86_64-linux-ubuntu14.tar.gz | tar xz

これらの環境を、Freedom-E-SDKの上に配置しなければならない。 Freedom-E-SDKは、SiFive社が配布しているRISC-Vボードやデザインを開発するためのSDKで、HiFive1のサンプルプログラムや、フラッシュへのデータをアップロードする環境なども一通り揃っている。

git clone https://github.com/sifive/freedom-e-sdk
mkdir freedom-e-sdk/riscv-gnu-toolchain
mkdir freedom-e-sdk/openocd
mv riscv64-unknown-elf-gcc-2018.07.0-x86_64-linux-ubuntu14/* freedom-e-sdk/riscv-gnu-toolchain
mv riscv-openocd-2018.7.0-x86_64-linux-ubuntu14/* freedom-e-sdk/openocd

余談だがZephyrのビルドに使用するDevice-Tree-Compilerも最新のものを導入してかなければならない。 私の実験環境であるUbuntu 18.04でインストールされているdtcはバージョンが古くビルド時にエラーとなったので、dtcの最新バージョンをインストールしておく。

curl -L https://git.kernel.org/pub/scm/utils/dtc/dtc.git/snapshot/dtc-1.5.0.tar.gz | tar xz
cd dtc-1.5.0
make
sudo make install PREFIX=/usr/

サンプルプログラムのコンパイル

ここまでで、Zephyrの環境構築は完了した。 次に、サンプルプログラムのコンパイルを行う。 サンプルプログラムは、${ZEPHYR_BASE}/samples/hello_worldを使用する。

  • samples/hello_world/src/main.c
/*
 * Copyright (c) 2012-2014 Wind River Systems, Inc.
 *
 * SPDX-License-Identifier: Apache-2.0
 */

#include <zephyr.h>
#include <misc/printk.h>

void main(void)
{
        printk("Hello World! %s\n", CONFIG_BOARD);
}

たったこれだけだが、うまく実行できるとシリアルコンソール上にprintfkの結果が表示されるはずだ。

まずは作業環境を整える。

mkdir build-example
cd build-example
cmake -DBOARD=hifive1 $ZEPHYR_BASE/samples/hello_world
make -j $(nproc)
cd ..
  • cmake -DBOARD=hifive1 $ZEPHYR_BASE/samples/hello_worldの実行結果
Zephyr version: 1.14.0
-- Selected BOARD hifive1
-- Loading /home/msyksphinz/work/riscv/zephyr/boards/riscv32/hifive1/hifive1.dts as base
-- Overlaying /home/msyksphinz/work/riscv/zephyr/dts/common/common.dts
Parsing Kconfig tree in /home/msyksphinz/work/riscv/zephyr/Kconfig
Loading /home/msyksphinz/work/riscv/zephyr/hifive1/build-example/zephyr/.config as base
Configuration written to '/home/msyksphinz/work/riscv/zephyr/hifive1/build-example/zephyr/.config'
-- Cache files will be written to: /home/msyksphinz/.cache/zephyr
-- Configuring done
-- Generating done
-- Build files have been written to: /home/msyksphinz/work/riscv/zephyr/hifive1/build-example
  • make -j $(nproc)の実行結果
[ 81%] Building C object zephyr/kernel/CMakeFiles/kernel.dir/atomic_c.c.obj
[ 82%] Building C object zephyr/CMakeFiles/zephyr.dir/drivers/timer/sys_clock_init.c.obj
[ 83%] Building C object zephyr/CMakeFiles/zephyr.dir/drivers/timer/riscv_machine_timer.c.obj
[ 84%] Linking C static library libkernel.a
[ 85%] Linking C static library libzephyr.a
[ 86%] Built target kernel
[ 92%] Built target zephyr
Scanning dependencies of target zephyr_prebuilt
[ 93%] Linking C executable zephyr_prebuilt.elf
Memory region         Used Size  Region Size  %age Used
             ROM:       18068 B        12 MB      0.14%
             RAM:        4272 B        16 KB     26.07%
        IDT_LIST:         553 B         2 KB     27.00%
[ 94%] Built target zephyr_prebuilt
Scanning dependencies of target linker_pass_final_script_target
[ 95%] Generating linker_pass_final.cmd
[ 95%] Built target linker_pass_final_script_target
[ 96%] Generating isr_tables.c
Scanning dependencies of target kernel_elf
[ 97%] Building C object zephyr/CMakeFiles/kernel_elf.dir/isr_tables.c.obj
[ 98%] Linking C executable zephyr.elf
Generating files from zephyr.elf for board: hifive1
[100%] Built target kernel_elf

これでZephyrとアプリケーションのコンパイルができる。HiFive1にダウンロードして実行してみよう。

サンプルプログラムをHiFive1のフラッシュメモリに書き込む

コンパイルしたプログラムをHiFive1にダウンロードするためには、先ほどのFreedom-E-SDK環境を使用する。 まずは、HiFive1のコンフィグレーションを使用してopenocdを立ち上げ、HiFive1と接続してみる。

cd freedom-e-sdk
sudo openocd/bin/openocd -f bsp/sifive-hifive1/openocd.cfg

次に別のターミナルを開き、GDBを立ち上げる。 そして、ZephyrのバイナリをHiFive1に書き込んでみよう。 GDBを立ち上げた後、下記リストの先頭が(gdb)と記述されている行のみ入力していけばよい。

./freedom-e-sdk/riscv-gnu-toolchain/bin/riscv64-unknown-elf-gdb
(gdb) set remotetimeout 240
(gdb) target extended-remote localhost:3333
Remote debugging using localhost:3333
warning: No executable has been specified and target does not support
determining executable automatically.  Try using the "file" command.
0x20401244 in ?? ()
(gdb) monitor reset halt
JTAG tap: riscv.cpu tap/device found: 0x10e31913 (mfg: 0x489 (SiFive, Inc.), part: 0x0e31, ver: 0x1)
halted at 0x20401244 due to debug interrupt
(gdb) monitor flash protect 0 64 last off
cleared protection for sectors 64 through 255 on flash bank 0
(gdb) load /home/msyksphinz/work/riscv/zephyr/hifive1/build-example/zephyr/zephyr.elf
Loading section vector, size 0x10 lma 0x20400000
Loading section exceptions, size 0x268 lma 0x20400010
Loading section text, size 0x3ac8 lma 0x20400278
Loading section sw_isr_table, size 0x200 lma 0x20403d40
Loading section devconfig, size 0x84 lma 0x20403f40
Loading section rodata, size 0x574 lma 0x20403fc4
Loading section datas, size 0xc4 lma 0x20404538
Loading section initlevel, size 0x84 lma 0x204045fc
Loading section _k_mutex_area, size 0x14 lma 0x20404680
Start address 0x20400000, load size 18068
Transfer rate: 8 KB/sec, 2007 bytes/write.
(gdb) monitor resume
halted at 0x20400004 due to step

ここまででエラーが生じなければ、Zephyrのアップロードは完了だ。 シリアルコンソールを立ち上げて動作を確認みる。

以下を入力する。 /dev/ttyUSB1の部分は、お使いの環境でHiFive1のシリアルコンソールが認識されているデバイスファイルに適宜書き換えること。

sh sudo picocom -b 115200 /dev/ttyUSB1

HiFive1のリセットボタンを押すと以下のように表示された。

***** Booting Zephyr OS v1.14.0-rc3-246-g10327e59c622 *****
Hello World! hifive1

HiFive1ボードで、Zephyrのサンプルアプリケーションを動作せさせることができた。

組込みシステム技術協会の機関誌Bulletin JASAに寄稿しました

f:id:msyksphinz:20190417221740p:plain

私も聞いたことのなかった機関紙なのですが、組込みシステム技術協会というのがありまして。 Embedded Technologyとかを主宰しているところですね。

実物をこれまで見たことがなかったのですが、組込みシステム技術協会はBulletin JASAという4半期に1回発行される機関紙を発行しています。

今回の特集はRISC-Vだということで、4ページほど寄稿させてもらいました。ネタとしてはRISC-VをFPGAで使うためのイントロダクション的な内容です。 私がいつもよく書いている類のやつですね。

www.jasa.or.jp

っていうかPDFダウンロードしたら全部画像じゃないですか。めっちゃ重たいので注意。

私の記事は面白くないのですが、RISC-Vのエッジ向けコア一覧とか、ボード一覧とかは面白いと思いますよ。私も勉強になる。

でもやっぱりPDFが画像で汚らしいので、ちゃんと読みたい人は紙媒体を手に入れた方がいいかも。

RISC-VでZephyr OSを動作させる (1. QEMUでHello Worldを動作させる)

f:id:msyksphinz:20190415004633p:plain

Zephyrとは、Linux Foundationのプロジェクトの一つでもあり、RTOS(Real Time Operating System)の一つだ。

このZephyr OSはRTOSとしてはかなりメジャー?のなOSであり、完全にオープンソース、そして組み込みデバイスなどの小さな機器に搭載するためのOSとして適している。例えば、IoTワイヤレスアプリケーションやスマートウォッチなどに使われる想定のOSだ。

そして、ZephyrはRISC-Vをサポートしており、現在以下のボードで動作することが確認されている。

  • SiFive HiFive1
  • Microsemi’s Mi-V soft CPU running on the M2GL025 development board
  • soft Pulpino CPU running on a Zedboard

なるほど、これは面白そうだ。HiFive1も持っていることだし、Zephyrを動かして少し遊んでみよう。

QEMU上で簡易OSZephyrを動かしてみる

動作環境としては、Ubuntu 18.04 LTSをVirtua Box上にインストールし、そのうえで作業を行う。まずは、ZephyrのコンパイルQEMUを動かすために必要なパッケージをインストールする。

sudo apt-get install --no-install-recommends git cmake ninja-build gperf \
  ccache dfu-util device-tree-compiler wget \
  python3-pip python3-setuptools python3-wheel xz-utils file make gcc \
  gcc-multilib

次に、Zephyrのソースコードをダウンロードし、さらに必要な外部パッケージのダウンロードを行う。以下のようにタイプする。

git clone https://github.com/zephyrproject-rtos/zephyr
cd zephyr
sudo pip3 install --user -r scripts/requirements.txt
export ZEPHYR_TOOLCHAIN_VARIANT=zephyr
. ./zephyr-env.sh

次に、ZephyrのSDKのダウンロードとインストールを行う。<zephyr_sdk_install_dir>には、SDKをインストールしたい場所を指定する。

# 資料には下記のように記述してあるが、SDK 0.9.5ではビルドできなかった。SDK 0.10.0を使用する必要がある。
# wget https://github.com/zephyrproject-rtos/meta-zephyr-sdk/releases/download/0.9.5/zephyr-sdk-0.9.5-setup.run
# sh zephyr-sdk-0.9.5-setup.run
wget https://github.com/zephyrproject-rtos/sdk-ng/releases/download/v0.10.0/zephyr-sdk-0.10.0-setup.run
sh zephyr-sdk-0.10.0-setup.run

SDKのビルドプロセスでは、riscv32のターゲットでZephyrがビルドされていることが確認できる。

Verifying archive integrity... All good.
Uncompressing SDK for Zephyr  100%
Enter target directory for SDK (default: /opt/zephyr-sdk/): /home/msyksphinz/work/riscv/zephyr/zephyr_install_sdk
Installing SDK to /home/msyksphinz/work/riscv/zephyr/zephyr_install_sdk
Creating directory /home/msyksphinz/work/riscv/zephyr/zephyr_install_sdk
Success
 [*] Installing x86 tools...
 [*] Installing arm tools...
 [*] Installing arc tools...
 [*] Installing iamcu tools...
 [*] Installing mips tools...
 [*] Installing nios2 tools...
 [*] Installing xtensa tools...
 [*] Installing riscv32 tools...
 [*] Installing additional host tools...
Success installing SDK. SDK is ready to be used.

そして、SDKをインストールした場所を環境変数として登録する。

export ZEPHYR_SDK_INSTALL_DIR=<zephyr_sdk_install_dir>   # ここでは、/home/msyksphinz/work/riscv/zephyr/zephyr_install_sdk を指定。

では、サンプルプログラムを動かす。ここでは、チュートリアルの通りにhello_worldプログラムをビルドして、QEMUで動作させる。

mkdir build-example
cd build-example
cmake -DBOARD=qemu_riscv32 /home/msyksphinz/work/riscv/zephyr/zephyr/samples/hello_world
$ make -j $(nproc)

しばらくするとビルドが完了した。さっそくQEMU上で動作させてみる。

$ make run
[  1%] Built target kobj_types_h_target
[  2%] Built target syscall_macros_h_target
[  6%] Built target syscall_list_h_target
[  7%] Built target driver_validation_h_target
[  8%] Built target offsets
[  9%] Built target offsets_h
[ 35%] Built target kernel
[ 38%] Built target app
[ 39%] Built target linker_script_target
[ 73%] Built target zephyr
[ 86%] Built target lib__libc__minimal
[ 89%] Built target drivers__gpio
[ 91%] Built target drivers__serial
[ 93%] Built target zephyr_prebuilt
[ 94%] Built target linker_pass_final_script_target
[ 98%] Built target kernel_elf
[100%]
To exit from QEMU enter: 'CTRL+a, x'
[QEMU] CPU: riscv32
***** Booting Zephyr OS v1.14.0-rc3-246-g10327e59c622 *****
Hello World! qemu_riscv32

Hello worldプログラムが無事に動作したようだ。QEMUで、Zephyr OSを動作させ、最初のアプリケーションを動かすことができた。

注意 : チュートリアルの通りにZephyr SDK 0.9.5 をダウンロードしてビルドすると以下のようなエラーが出ました。どうやら、0.10.0を使用する必要があるようです。

Zephyr version: 1.14.0
-- Found PythonInterp: /usr/bin/python3 (found suitable version "3.6.7", minimum required is "3.4")
-- Selected BOARD qemu_riscv32
CMake Error at /home/msyksphinz/work/riscv/zephyr/zephyr/cmake/toolchain/zephyr/host-tools.cmake:38 (message):
  The SDK version you are using is too old, please update your SDK.

  You need at least SDK version 0.10.0.

  You have version 0.9.5 (/home/msyksphinz/work/riscv/zephyr/zephyr_sdk).

  The new version of the SDK can be downloaded from:


  https://github.com/zephyrproject-rtos/sdk-ng/releases/download/v0.10.0/zephyr-sdk-0.10.0-setup.run

AWS F1インスタンス上のFireSimでBOOMコアをシミュレーションする試行(5. オリジナルAFI(Amazon FPGA Image)の生成方法調査)

f:id:msyksphinz:20190405012417p:plain

AWS F1インスタンス上でRISC-Vコアを動かすことのできるFireSimは、徐々にバージョンが上がっており、現在はBOOM(Berkeley Out-of Order Machine)のLinux起動もサポートできるようになっているらしい。

fires.im

一度、F1インスタンスチュートリアルはやってみたことがあるのだが、しばらく時間もたっているし、前回はRocketコアで検証した環境を、BOOMコアを使って再検証してみたい。

チュートリアルを見ながら、再度FireSimをF1インスタンス上に構築するチュートリアルを試してみることにした。以下の資料を参考にした。

docs.fires.im

docs.fires.im

このセクションではFireSimのシミュレーションに使用する独自のAFIイメージの作成を行う。 AFIとは、Amazon FPGA Imageの略称で、AFIを作成しておけばすぐにAWS F1インスタンスにデザインをデプロイすることができる。

Amazon S3のアカウントをセットアップしていることを前提にする。Amazon S3のコンソール上で[Create bucket]をクリックして新しいバケットをセットアップする。

[Bucket name]は"firesim-(アカウント名)"とする必要がある。そうしないとfiresimがS3バケットを認識してくれない。次に[Next]をクリックした。

そのまま[Next]をクリックし続け、[Create Bucket]をクリックして新しいバケットを生成する。

f:id:msyksphinz:20190414233926p:plain
作成されたS3 firesim-msyksphinzバケット

S3上でバケットの構築が完了すると、次にマネージャインスタンス上で作業を行う。firesim/deploy/config_build.iniを編集した。

[afibuild]

s3bucketname=firesim-msyksphinz

[builds]
# this section references builds defined in config_build_recipes.ini
# if you add a build here, it will be built when you run buildafi
firesim-singlecore-no-nic-lbp
# #firesim-singlecore-nic-lbp
# #firesim-quadcore-no-nic-lbp
# #firesim-quadcore-nic-lbp
# firesim-quadcore-no-nic-ddr3-llc4mb
# firesim-quadcore-nic-ddr3-llc4mb
# #fireboom-singlecore-no-nic-lbp
# fireboom-singlecore-no-nic-ddr3-llc4mb
# #fireboom-singlecore-nic-lbp
# fireboom-singlecore-nic-ddr3-llc4mb
# #firesim-supernode-singlecore-nic-ddr3-llc4mb
# #firesim-supernode-quadcore-nic-ddr3-llc4mb
# firesim-supernode-singlecore-nic-lbp


[agfistoshare]
firesim-singlecore-no-nic-lbp
# #firesim-singlecore-nic-lbp
# #firesim-quadcore-no-nic-lbp
# #firesim-quadcore-nic-lbp
# firesim-quadcore-no-nic-ddr3-llc4mb
# firesim-quadcore-nic-ddr3-llc4mb
# #fireboom-singlecore-no-nic-lbp
# fireboom-singlecore-no-nic-ddr3-llc4mb
# #fireboom-singlecore-nic-lbp
# fireboom-singlecore-nic-ddr3-llc4mb
# #firesim-supernode-singlecore-nic-ddr3-llc4mb
# #firesim-supernode-quadcore-nic-ddr3-llc4mb
# firesim-supernode-singlecore-nic-lbp

ターゲットとなるfiresim-singlecore-no-nic-lbpのみを残し、あとはコメントアウトした。これをしっかり行わないと、ターゲットとなるイメージの数だけインスタンスが生成されてしまい、料金がめっちゃかさむことになる。 上記のように編集した後、firesim buildafiを実行します。処理が完了するのに、3~4時間程度かかる。

しかし一応ビルドが完了したと思われる段階で、Current state : pendingと表示された。これ、合っているのかなあ?

send e-mail to $env(EMAIL)";
[192.168.3.37] out: #   exec $env(AWS_FPGA_REPO_DIR)/shared/bin/scripts/notify_via_sns.py
[192.168.3.37] out: # }
[192.168.3.37] out: # puts "AWS FPGA: ([clock format [clock seconds] -format %T]) - Build complete.";
[192.168.3.37] out: AWS FPGA: (14:12:45) - Build complete.
[192.168.3.37] out: INFO: [Common 17-206] Exiting Vivado at Sun Apr 14 14:12:45 2019...
[192.168.3.37] out:
Resulting AGFI: agfi-0cf781ba8d9a177c3
Resulting AFI: afi-04cf716b0e6f8b13f
Waiting for create-fpga-image completion.
Current state: pending
Current state: pending
Current state: pending
Current state: pending
Current state: pending
Current state: pending
Current state: pending
Current state: pending
Current state: pending
Current state: pending
Current state: pending
Current state: pending
Current state: pending
Current state: pending
Current state: pending
Current state: pending
Current state: pending
Current state: pending

一応、RTLの生成と、Vivadoでの論理合成とイメージの作成が行われ、その結果がS3に書き込まれている。この中身を確認していこう。

f:id:msyksphinz:20190414234141p:plain
生成されたFireimのAFIイメージ

オリジナルLLVM Backendを追加しよう (28. C++のサポート)

https://cdn-ak.f.st-hatena.com/images/fotolife/m/msyksphinz/20181123/20181123225150.png

LLVMにはすでにRISC-Vのバックエンドサポートが追加されている。しかし、勉強のために独自のRISC-V実装をLLVMに追加している。

jonathan2251.github.io

C++のサポート

LLVMC++特殊な文法をサポートするためには、ポリフォーフィズムをサポートする必要がある。例えば、以下のようなプログラムを考える。

  • lbdex/input/ch12_inherit.cpp
class CPolygon { // _ZTVN10__cxxabiv117__class_type_infoE for parent class
  protected:
    int width, height;
  public:
    void set_values (int a, int b)
    { width=a; height=b; }
    virtual int area (void) { return 0;};
    void printarea (void)
    { printf("%d\n", this->area()); }
  };

class CRectangle: public CPolygon {
  public:
    int area (void)
    { return (width * height); }
};

class CTriangle: public CPolygon {
  public:
    int area (void)
    { return (width * height / 2); }
};

class CAngle: public CPolygon {
  public:
    int area (void)
    { return (width * height / 4); }
};

int test_cpp_polymorphism() {
  CRectangle poly1;
  CTriangle poly2;
  CAngle poly3;

  CPolygon * ppoly1 = &poly1;
  CPolygon * ppoly2 = &poly2;
  CPolygon * ppoly3 = &poly3;

  ppoly1->set_values (4,5);
  ppoly2->set_values (4,5);
  ppoly3->set_values (4,5);
  ppoly1->printarea();
  ppoly2->printarea();
  ppoly3->printarea();

上記のC++コードでは、ベースクラスCPolygonに対して3つの派生クラスCRectangle, CTriangle, CAngleが定義されている。 この時、print_area()を呼び出した場合、それぞれの派生クラスで呼び出されるarea()メソッドが異なり、異なる結果が得られる。 C++のサポートでは、このような文法を考慮する必要がある。

./bin/clang -target mips-unknown-linux-gnu -c ../lbdex/input/ch12_inherit.cpp -emit-llvm -o - | ./bin/llc -march=myriscvx32 -relocation-model=pic -filetype=asm -o -

また、C++では、try - catchのような例外動作も考慮する必要がある。

  • lbdex/input/ch12_eh.cpp
class Ex1 {};
void throw_exception(int a, int b) {
  Ex1 ex1;

  if (a > b) {
    throw ex1;
  }
}

int test_try_catch() {
  try {
    throw_exception(2, 1);
  }
  catch(...) {
    return 1;
  }
  return 0;
}

このプログラムでは例外を処理するために、try - catchの構文を使っている。これをLLVMコンパイルすると、

./bin/clang -target mips-unknown-linux-gnu -c ../lbdex/input/ch12_eh.cpp -emit-llvm -o - | ./bin/llc -march=myriscvx32 -relocation-model=pic -filetype=asm -o -
Selecting: t4: i32,ch = load<(dereferenceable load 4 from %ir.retval)> t0, FrameIndex:i32<0>, undef:i32
Selecting: t5: i32 = Register $a0
Selecting: t0: ch = EntryToken
llc: /home/msyksphinz/work/llvm/llvm-myriscvx/lib/CodeGen/LiveVariables.cpp:133: void llvm::LiveVariables::HandleVirtRegUse(unsigned int, llvm::MachineBasicBlock*, llvm::MachineInstr&): Assertion `MRI->getVRegDef(reg) && "Register use before def!"' failed.
Stack dump:
0.      Program arguments: ./bin/llc -march=myriscvx32 -relocation-model=pic -filetype=asm -o -
1.      Running pass 'Function Pass Manager' on module '<stdin>'.
2.      Running pass 'Live Variable Analysis' on function '@_Z14test_try_catchv'
^C

llcが正しくIRを処理することができなかった。LLVM IRの逆アセンブルを生成して確認してみる。

./bin/clang -target mips-unknown-linux-gnu -c ../lbdex/input/ch12_eh.cpp -emit-llvm -o - | ./bin/llvm-dis
; Function Attrs: noinline optnone
define dso_local void @_Z15throw_exceptionii(i32 signext %a, i32 signext %b) #0 {                                                                                                                                                                                              entry:
  %a.addr = alloca i32, align 4
  %b.addr = alloca i32, align 4
  %ex1 = alloca %class.Ex1, align 1
  store i32 %a, i32* %a.addr, align 4
  store i32 %b, i32* %b.addr, align 4
  %0 = load i32, i32* %a.addr, align 4
  %1 = load i32, i32* %b.addr, align 4
  %cmp = icmp sgt i32 %0, %1
  br i1 %cmp, label %if.then, label %if.end

if.then:                                          ; preds = %entry
  %exception = call i8* @__cxa_allocate_exception(i32 1) #1
  %2 = bitcast i8* %exception to %class.Ex1*
  call void @__cxa_throw(i8* %exception, i8* bitcast ({ i8*, i8* }* @_ZTI3Ex1 to i8*), i8* null) #2
  unreachable

if.end:                                           ; preds = %entry
  ret void
}

そこで、以下のコードを追加した。

  • lib/Target/MYRISCVX/MYRISCVXISelLowering.h
  /// 例外アドレスが格納されるレジスタを指定する。今回はA0レジスタ
  unsigned
  getExceptionPointerRegister(const Constant *PersonalityFn) const override {
    return MYRISCVX::A0;
  }

  /// 例外要因が格納されるレジスタを指定する。今回はA1レジスタ
  unsigned
  getExceptionSelectorRegister(const Constant *PersonalityFn) const override {
    return MYRISCVX::A1;
  }

LLVMをリビルドし、同じソースコードを試してみる。

./bin/clang -target mips-unknown-linux-gnu -c ../lbdex/input/ch12_eh.cpp -emit-llvm -o - | ./bin/llc -march=myriscvx32 -relocation-model=pic -filetype=asm -o - | less

ビルドできた。test_try_catch()を確認する。

_Z14test_try_catchv:
$tmp3:
.set $func_begin0, ($tmp3)
        .cfi_startproc
        .cfi_personality 0, __gxx_personality_v0
        .cfi_lsda 0, $exception0
        .frame  $x8,24,$x1
        .mask   0x00000100,-4
        .set    noreorder
        .cpload $t9
        .set    nomacro
# %bb.0:                                # %entry
        lui     x10, %hi(_gp_disp)
        addi    x10, x10, %lo(_gp_disp)
        addi    x2, x2, -24
        .cfi_def_cfa_offset 24
        sw      x8, 20(x2)              # 4-byte Folded Spill
        .cfi_offset 8, -4
        move    x8, x2
        .cfi_def_cfa_register 8
        .cprestore      8
$tmp0:
        lw      x3, %call16(_Z15throw_exceptionii)(x3)
        addi    x10, zero, 2
        addi    x11, zero, 1
        jalr    x3
        lw      x3, 8(x2)
$tmp1:
        jal     $BB1_1
$BB1_1:                                 # %invoke.cont
        jal     $BB1_4
$BB1_2:                                 # %lpad
$tmp2:
        lw      x3, 8(x2)
        sw      x30, 12(x2)
        sw      x31, 8(x2)
        jal     $BB1_3

スレッドローカルストレージのサポート

スレッドローカルストレージとは、C++11からサポートされているスレッド固有の変数で、スレッド毎の静的記憶領域に変数が書き込まれる。 staticに対してthread_localキーワードの場合はスレッド固有の領域に変数が用意される。

  • lbdex/input/ch12_thread_var.cpp
__thread int a = 0;
thread_local int b = 0; // need option -std=c++11
int test_thread_var()
{
    a = 2;
    return a;
}

int test_thread_var_2()
{
    b = 3;
    return b;
}

上記をコンパイルしてみると、以下のようなエラーが発生してしまった。GlobalTLSAddressLLVM IRを追加する必要がありそうだ。

./bin/clang -target mips-unknown-linux-gnu -c ../lbdex/input/ch12_thread_var.cpp -emit-llvm -o - | ./bin/llc -march=myriscvx32 -relocation-model=pic -filetype=asm -o - | less
Selecting: t9: ch = MYRISCVXISD::Ret t8, Register:i32 $a0, t8:1
Selecting: t8: ch,glue = CopyToReg t5, Register:i32 $a0, t6
Selecting: t6: i32,ch = load<(dereferenceable load 4 from @a)> t5, GlobalTLSAddress:i32<i32* @a> 0, undef:i32
Selecting: t5: ch = store<(store 4 into @a)> t0, Constant:i32<2>, GlobalTLSAddress:i32<i32* @a> 0, undef:i32
Selecting: t7: i32 = Register $a0
Selecting: t2: i32 = GlobalTLSAddress<i32* @a> 0
LLVM ERROR: Cannot select: t2: i32 = GlobalTLSAddress<i32* @a> 0
In function: _Z15test_thread_varv
  • MYRISCVXAsmParser.cpp : evaluateRelocExpr()
  • MYRISCVXMCExpr.cpp : printImpl()

を改造する必要がある。追加しなければならないLLVM IRは、tlsgd, tlsldm, dtp_hi, dtp_lo, gottp, tp_hi, tp_loです。

  • lib/Target/MYRISCVX/MCTargetDesc/MYRISCVXAsmBackend.cpp
//@getFixupKindInfo {
const MCFixupKindInfo &MYRISCVXAsmBackend::
getFixupKindInfo(MCFixupKind Kind) const {
  const static MCFixupKindInfo Infos[MYRISCVX::NumTargetFixupKinds] = {
...
    { "fixup_MYRISCVX_TLSGD"    , 0, 16, 0 },
    { "fixup_MYRISCVX_GOTTP"    , 0, 16, 0 },
    { "fixup_MYRISCVX_TP_HI"    , 0, 16, 0 },
    { "fixup_MYRISCVX_TP_LO"    , 0, 16, 0 },
    { "fixup_MYRISCVX_TLSLDM"   , 0, 16, 0 },
    { "fixup_MYRISCVX_DTP_HI"   , 0, 16, 0 },
    { "fixup_MYRISCVX_DTP_LO"   , 0, 16, 0 }
  • lib/Target/MYRISCVX/MCTargetDesc/MYRISCVXBaseInfo.h
  namespace MYRISCVXII {

    /// Target Operand Flag enum.
    enum TOF {
      //===------------------------------------------------------------------===//
      // MYRISCVX Specific MachineOperand flags.
...
      // MO_TLSGD : モジュールIDとTSLブロックオフセットが存在するグローバルオフセットテーブルのオフセットを示している。
      // グローバル動的TLS向け
      MO_TLSGD,

      // MO_TLSLDM : モジュールIDとTSLブロックオフセットが存在するグローバルオフセットテーブルのオフセットを示している。
      // ローカル動的TLS向け
      MO_TLSLDM,
      MO_DTP_HI,
      MO_DTP_LO,

      // MO_GOTTPREL : スレッドポインタのオフセットを示す
      // 初期動作TLS向け
      MO_GOTTPREL,

      // MO_TPREL_HI/LO : スレッドポインタからのオフセットのHIとLO領域を示す。
      // ローカル動作TLS向け
      MO_TP_HI,
      MO_TP_LO,
    }; // enum TOF {

さらに、target descriptionファイルに以下を追加します。

// TlsGd node is used to handle General Dynamic TLS
def MYRISCVXTlsGd : SDNode<"MYRISCVXISD::TlsGd", SDTIntUnaryOp>;

// TpHi and TpLo nodes are used to handle Local Exec TLS
def MYRISCVXTpHi  : SDNode<"MYRISCVXISD::TpHi", SDTIntUnaryOp>;
def MYRISCVXTpLo  : SDNode<"MYRISCVXISD::TpLo", SDTIntUnaryOp>;

def : Pat<(MYRISCVXHi tglobaltlsaddr:$in), (LUi tglobaltlsaddr:$in)>;

def : Pat<(MYRISCVXLo tglobaltlsaddr:$in), (ORi ZERO, tglobaltlsaddr:$in)>;

def : Pat<(add CPURegs:$hi, (MYRISCVXLo tglobaltlsaddr:$lo)),
              (ORi CPURegs:$hi, tglobaltlsaddr:$lo)>;

def : WrapperPat<tglobaltlsaddr, ORi, CPURegs>;

これで最終的にスレッドローカルストレージを含むソースコードコンパイルできるようになった。LLVMがスレッドローカルストレージのアドレスを生成する手順を以下にまとめた。

  • IR:load i32* @a, align 4;(add Cpu0ISD::Hi Cpu0ISD::Lo);ori $2, $zero, %tp_lo(a); lui $3, %tp_hi(a); addu $3, $3, $2;
  • IR: ret i32* @b;%0=(add Cpu0ISD::Hi Cpu0ISD::Lo);...ori $2, $zero, %tp_lo(a); lui $3, %tp_hi(a); addu $3, $3, $2;

オリジナルLLVM Backendを追加しよう (28. Intrinsicのサポート2)

https://cdn-ak.f.st-hatena.com/images/fotolife/m/msyksphinz/20181123/20181123225150.png

LLVMにはすでにRISC-Vのバックエンドサポートが追加されている。しかし、勉強のために独自のRISC-V実装をLLVMに追加している。

jonathan2251.github.io

第11章では、アセンブラやIntrinsicをサポートする。今度はC言語の中にIntrinsicを埋め込んだアセンブラの拡張を行う。例えば、以下のようなコードをサポートする。

  __asm__ __volatile__("addi %0,%1,%2"
                       :"=r"(foo) // 15
                       :"r"(foo), "i"(5)
                       );

  __asm__ __volatile__("ld %0,%1"
                       :"=r"(c) // c=3
                       :"m"(g[2])
                       );

インラインアセンブリである。組み込みの業界ではよく使う手法なのだが。上記のaddiでは、

これらの表現をサポートするためには、MYRISCVXのAsmPrinterを改造する必要がある。 追加するのは、以下のコードだ。以下のコードをオーバライドし、MYRISCVX用に改造する。

// インラインアセンブリの式のオペランドをプリントする。  
bool PrintAsmOperand(const MachineInstr *MI, unsigned OpNo,
                       unsigned AsmVariant, const char *ExtraCode,
                       raw_ostream &O) override;
// メモリアドレスのオペランド
bool PrintAsmMemoryOperand(const MachineInstr *MI, unsigned OpNum,
                             unsigned AsmVariant, const char *ExtraCode,
                             raw_ostream &O) override;
// オペランドのプリント
void printOperand(const MachineInstr *MI, int opNum, raw_ostream &O);
  • MYRISCVXTargetLowering::getConstraintType () : ターゲットの制約を取得する。

    • デフォルトでは、最初から組み込まれているTargetLowering::getConstraintType(Constraint)が呼ばれる。

    • しかし、それ以外に特殊なケースについてswitch文が組み込まれている。

    • これはどこから来ているかというと、MIPSの規定として以下の特殊なイントリンジックの表記が使えるとあり、ここから来ているものと思われる。 https://gcc.gnu.org/onlinedocs/gcc/Machine-Constraints.html#Machine-Constraints

    • このなかで、

      • c : A register suitable for use in an indirect jump. This will always be $25 for -mabicalls.
      • R : An address that can be used in a non-macro load or store.
      • cの場合はC_RegisterClass,Rの場合はC_Memory`を返す。
    • 例えば、以下のようにIntrinsicを使用してみる。

      int foo = 10;
      const int bar = 15;
    
      __asm__ __volatile__("add %0,%1,%2"
                           :"=r"(foo) // 5
                           :"c"(foo), "c"(bar)
                           );
            addi    10, zero, 15
            sw      10, 4(2)
            lw      5, 8(2)
            addi    5, 10, 0
            #APP
            add $11,$5,$5
            #NO_APP
  • このように、アーキテクチャ独自のIntrinsicの記法を導入することができるようだ。

  • MYRISCXVTargetLowering::getSingleConstraintMatchWeight ()` :

  • std::pair<unsigned, const TargetRegisterClass *> Cpu0TargetLowering::getRegForInlineAsmConstraint()
    • constraintに応じて、レジスタを取得しする。つまり、今回のケースだとrcの場合にMYRISCVX::GPRRegClassを返する。cの場合には、優先的にt0 (=x5)が使用されるようになる。
  • void Cpu0TargetLowering::LowerAsmOperandForConstraint()
    • オペランドの文字解析を行う。通常はTargetLowering::LowerAsmOperandForConstraintを使用するが、MIPSの特殊なレジスタ記法 :
      • I, J, K, L, N, O, P
    • の場合には特殊な処理を行う。
  • bool Cpu0TargetLowering::isLegalAddressingMode()
    • アドレッシングモードが正しく記述されているかどうかをチェックする。

上記の実装を追加した結果、以下のようにIntrisicを含んだソースコードコンパイルできるようになった。

./bin/clang -target mips-unknown-linux-gnu -c ../lbdex/input/ch11_2.cpp -emit-llvm
./bin/llc -march=myriscvx32 -relocation-model=pic -filetype=obj ch11_2.bc -o -
_Z16inlineasm_globalv:
        .frame  $8,16,$1
        .mask   0x00000100,-4
        .set    noreorder
        .cpload $t9
        .set    nomacro
# %bb.0:                                # %entry
        lui     10, %hi(_gp_disp)
        addi    10, 10, %lo(_gp_disp)
        addi    2, 2, -16
        sw      8, 12(2)                # 4-byte Folded Spill
        move    8, 2
        lui     10, %got_hi(g)
        add     10, 10, 3
        lw      10, %got_lo(g)(10)
        addi    11, 10, 8
        #APP
        lw $11,0($11)
        #NO_APP
        sw      11, 8(2)
        lw      11, 8(2)
        #APP
        addi $11,$11,1
        #NO_APP
        sw      11, 4(2)
        lw      10, 4(2)
        move    2, 8
        lw      8, 12(2)                # 4-byte Folded Reload
        addi    2, 2, 16
        jalr    1

赤羽北区ハーフマラソン大会に参加した

ハーフマラソンに参加するのは人生で3回目。 前回は暑さと音楽プレーヤーが壊れたことによりタイムが大幅にダウンしたので、今回はリベンジしたい。

場所は新荒川大橋の付近で、ハーフマラソンの参加者は250人くらい?

f:id:msyksphinz:20190413221816p:plain

コースは赤羽の荒川の河川敷を走る。一周10kmのコースを2周走る。この時期は河川敷の桜が非常にきれいなコースだ(ハーフマラソンなので最終的に桜なんてどうでも良くなるほど疲れるが...)

前回の失敗を元に対策を講じ、給水に頼らず自分のスポーツドリンクを持って走る、そして気温の上昇に耐えるためにあらかじめスポーツドリンクは凍らせておいた。 結果的にこれが当たった。自分の好きなタイミングで給水できるし、冷たいので体も冷やしやすい。

という訳で結果は大幅に向上した。前回は2時間24分くらいだったのが、今回は2時間12分まで向上。なんだかんだで自己ベストを更新した。 良かった良かった。