日々

link permalink

link 編集する

memset のベンチマーク(AArch64, Cortex-A53 編)

(参考)コード一式は GitHub に置きました(GitHub へのリンク

AArch64 その 2 です。Cortex-A53 で memset をやってみました。環境は RK3328 Cotex-A53 1.4GHz です。メモリはおそらく LPDDR3-1600 です。

Cortex-A72 と似ている点としては、

  • musl memset 関数が非常に優秀
  • ベクトル化は性能向上に効くが、他も有効な要素がありそう

違う点としては、

  • アセンブラ実装と musl memset 関数の差が開く
  • O3 の最適化がかなり効く(※)
  • glibc memset 関数の不安定さが減る

こんなところでしょうか。A72 の glibc memset 関数はグラフが上がったり下がったりグチャグチャしていましたが、A53 だと割と素直になっています。


gcc -O3 -fno-builtin の測定結果(Cortex-A53 編)


gcc -O2 -ftree-vectorize -fno-builtin の測定結果(Cortex-A53 編)


gcc -O2 -fno-builtin の測定結果(Cortex-A53 編)

(※)A72 では単純な memset 関数は musl memset 関数にほぼ勝てない(16〜22バイトのみ勝つ)が、A53 では割と良い勝負(16〜22、32〜38、48〜52バイトで勝つ)をしている。

[編集者: すずき]
[更新: 2020年 1月 12日 02:34]

コメント一覧

  • コメントはありません。
open/close この記事にコメントする



link permalink

link 編集する

memset に一番効く最適化

Cortex-A72 での memset は O2 に -ftree-vectorize と -fpeel-loops を足すと、O3 の性能とほぼイコールになることがわかりました。


gcc -O2 -ftree-vectorize -fpeel-loops -fno-builtin の測定結果(Cortex-A72)

元の処理が非常に単純なループ処理のためか、ループ系の最適化がメチャクチャ効くっぽいです。

何が効くのか?

GCC の GIMPLE を出力させ(-fdump-tree-all)眺めてみると、

オリジナル
1バイトごとにデータ処理するループが生成される。
ベクタライズ(161t.vect)
16バイトごとにデータ処理するループと、1バイトごとに残りデータを処理するループに分割される。
アンローリング(164t.cunroll, 169t.loopdone)
残りデータを処理するループが展開される。

こんな感じに見えます。正直言って、ループアンローリングなんて大したことないと思っていましたが、これほど効くとは思いませんでした。

メモ: 技術系の話は Facebook から転記しておくことにした。大幅に追記。

[編集者: すずき]
[更新: 2020年 1月 13日 00:42]

コメント一覧

  • コメントはありません。
open/close この記事にコメントする



link permalink

link 編集する

ぼくの考えた最強の memset

目次です。

NEON intrinsic を使って自分で memset を実装してみました。ざっくりした設計方針としては、

  • NEON store (128bit) x 2 で 32バイトずつ書く
  • 端数 25〜バイトは NEON store x 2
  • 端数 16〜バイトは NEON store + uint64 store

相手は汎用実装ですし、Cortex-A72 に特化した実装なら楽勝だろう、などと考えて始めましたが、甘かった。glibc のフルアセンブラ版はかなり手ごわいです。


自作 memset の測定結果(Cortex-A72)

グラフの赤い線が、自作した memset の性能です。

最適化レベル O3 の simple memset にはほぼ全域で勝てますが、サイズが小さいときの musl は強い(サイズが小さい場合から判定しているから?)です。glibc のフルアセンブラもかなり強いです。測定によって勝ったり負けたりな程度です。

全然最強じゃなかった……

設計が甘すぎたことがわかったので、下記のように見直しました。

  • 少ないバイト数の条件から判定
  • NEON store (128bit) x 2 で 32バイトずつ書く
  • 端数バイトは NEON store(分岐を減らした)

序盤で musl memset に負けていたのは、バイト数の条件判定の順序が良くなかった(大きいサイズから判定していた)ためなので、1番目で対策しています。2番目と 3番目の方針は良いとも悪いとも一概に言えませんが、RK3399 だとこれが一番性能が出ました。


自作 memset 改善後の測定結果(Cortex-A72)

設計意図通りに musl の序盤(特に高速な 1〜8バイト付近)と、glibc フルアセンブラの序盤(1〜32バイト)には勝てたものの、glibc フルアセンブラ版は中盤以降が強く、33バイト以降は全く勝てません。

私の作った memset は 32バイトまでは専用処理で、33バイトからループで処理するようになるので、33バイトから性能がかなり落ちます。

おそらく glibc フルアセンブラ版も同様に 16バイトから性能が落ちるので、ループ処理していると思うんですが、それ以降の巻き返しが凄くて、33バイト以降はまったく勝てないですね……。どうやってんだろうね、これ?

コンパイラが変な and とか sub を出力しているのを見つけたので、アセンブラでも実装してみましたが、性能はほぼ変わりませんでした。設計の根底が違うんでしょうね。

Cortex-A53 だと全く勝ち目無し

RK3328(Cortex-A53)で測ってみると、musl には勝てますが、glibc フルアセンブラ版には勝ち目無しで、ほぼ全域に渡ってボコボコにされます。


自作 memset 改善後の測定結果(Cortex-A53)

基本設計が「余計な write をしてでも、とにかく速く終われ」なので、write を正直に実行してしまうようなヘボいプロセッサになればなるほど勝ち目が薄いです。

[編集者: すずき]
[更新: 2020年 1月 26日 17:19]

コメント一覧

  • コメントはありません。
open/close この記事にコメントする



link permalink

link 編集する

バイトをコピーする SIMD 命令

最近、見かける SIMD 命令セット(AVX も NEON も)には、レジスタ下位 [7:0] の 1バイトを、レジスタ上位 ... [31:24] [23:16] [15:8] の各バイトに配る命令が用意されています。

  • AVX: vpbroadcastb
  • NEON: dup

この命令はどういう需要があるんだろうか……?memset の実装では超役に立ちましたが、他の使い道が良くわかりません。

Facebook で上記の話をしていたところ、

  • 8bit 行列演算: 8bit 行列演算ってそんな頻出かな、って思ったら、画像使えば 8bit なので十分有り得そう。
  • バイト暗号: ブロック毎に空間変換する時とか雑に言えばスカラとベクトルの演算。

と教えてもらいました。なるほど、スカラベクトル積のスカラ側を配るときに便利ですね。

SIMD 命令のない世界

ちなみに SIMD のない処理系はどうしているのか見てみると、


int a = (何かの数字);

としたときに、


a &= 0xff;
a *= 0x01010101;

のように and, mov, mul を使っていました。もちろん、


a &= 0xff;
a |= a << 8;
a |= a << 16;

のように and, shift, or, shift, or でもできますが、今日日のプロセッサだと整数乗算の方が速そうですね。

[編集者: すずき]
[更新: 2020年 1月 26日 16:59]

コメント一覧

  • コメントはありません。
open/close この記事にコメントする



link permalink

link 編集する

glibc の memset のクセ

先日 memset を書いていたとき(2020年 1月 12日の日記参照)に気づいたのですが、glibc のフルアセンブラ版 memset の性能が 2通り(遅い、速い)あることに気づきました。だいたい 1割くらい性能が変わります。

遅いときと比較すると、自作の memset の方が速いですが、速いときと比較するとボロ負けします。割と性能が迫っているためか、影響が大きいです。

何が違うんでしょうね?コードは当然同じですから、違いは memset 関数のロードされるアドレスくらいです。まさかなと思って、スタティックリンクしたら安定して速くなりました。

ダイナミックリンクだと、アプリ側は 0xaaaac4fba560 で、glibc だけ 0xffffbf2dce00 のような遠いアドレスに飛ばされます。ベンチマーク中は、アプリのコード ←→ glibc のコードを頻繁に行き来することになるので、TLB ミスヒットの影響が出ているんですかね……??

真因はわかりませんが、アドレスが関係している可能性は高いです。今後、似たようなことをやるときは、スタティックリンクで測った方が良さそうです。

[編集者: すずき]
[更新: 2020年 1月 26日 17:09]

コメント一覧

  • コメントはありません。
open/close この記事にコメントする



link permalink

link 編集する

glibc の memset は強かった

目次です。

先日(2020年 1月 12日の日記参照)の続きです。

あまりにも glibc フルアセンブラ版 memset の実装が速くて勝てないので、観念して実装を見たのですが、序盤(1バイト〜32バイト)が弱い理由と、以降(33バイト〜)で勝てない理由がわかりました。

他の実装と違って glibc はサイズの大きい方から条件を見ています。どうしても条件分岐命令を通る回数が増えるため、序盤に弱いです。

中盤は 96 バイトまでは NEON store x 4 と分岐で捌いていて、ループを使いません。分岐も cmp して branch ではなく、ビットセットされていたら分岐する命令(tbz, tbnz)を使っています(※)。

つまり私が書いた memset はループで処理している時点で、ほぼ勝ち目がなかったということです。

グラフでは 63バイトまでしか測っていなかったから気づかなかったのですが、ループの 2週目に入る 65バイトから、さらにボロ負けです。いやはや、これは勝てないですね……。

(※)cmp, branch の 2命令を tbz 1命令にする辺り、AArch64 アセンブラならではの実装に見えますが、実は C でも if (a & 0x10) とか書くとコンパイラが tbz 命令を使います。コンパイラ侮りがたし。

[編集者: すずき]
[更新: 2020年 1月 26日 17:19]

コメント一覧

  • コメントはありません。
open/close この記事にコメントする



link permalink

link 編集する

C 言語の未定義動作と最適化

くそ長いですが、C 言語の未定義動作怖いね、printf でタイミング以外も動き変えられるよ、という話です。

環境ですが x86_64 向け Debian GNU/Linux 9.2 で実行しています。また GCC のバージョンは gcc (Debian 9.2.1-22) 9.2.1 20200104 です。

未定義動作のため、コンパイラの種類や、GCC のバージョンにより結果が変わると思われます。お家のマシンで試すならご留意ください。

1番目の実験

この日記の最後に貼ったプログラム(このプログラムをコンパイルすると、激しい警告が出ます)を gcc -Wall -O2 a.c && ./a.out のように実行すると、

1番目の実験: あれ?バッファオーバーランは…?
0: 0 0 0
1: 0 1 0
2: 0 2 0
3: 0 3 0
4: 0 4 0
...
47: 0 47 0
48: 0 48 0
49: 0 49 0
1770: 1770

こうなります。0〜59 の和は 1770 です。あってます。良かったですね。

なに?そういう問題じゃない?「なぜ array 終端を超えて guard2 にバッファオーバーランしない?」と考えた方、するどいです。しかし世の中そう単純ではありません。

2番目の実験

10行目の printf のコメントを外してローカル変数のアドレスを表示させると、

2番目の実験: 10行目の printf を有効、突然のバッファオーバーラン
0x7ffd9b348a10 0x7ffd9b348ae0 0x7ffd9b348bb0
0: 0 0 52
1: 0 1 53
2: 0 2 54
3: 0 3 55
4: 0 4 56
...
45: 0 45 0
46: 0 46 0
47: 0 47 0
48: 0 48 0
49: 0 49 0
1770: 1770

こうなります。突然オーバーランするようになりました。printf が何かしたんでしょうか、不思議ですね?

3番目の実験

どうして for ループを無意味に 2分割したのか?くっつけてみたらわかります。Segmentation Fault します。

3番目の実験: ループを 1つにするとクラッシュ
0: 0 0 0
1: 0 1 0
2: 0 2 0
3: 0 3 0
4: 0 4 0
...
45: 0 45 0
46: 0 46 0
47: 0 47 0
48: 0 48 0
49: 0 49 0
Segmentation fault

もう意味不明ですよね。何が起こっているんでしょう?

タネ明かし

この 60回の for ループは「配列の終端を超えたアクセス」が C 言語仕様上の未定義動作なので、何が起きても正しい、つまりどの結果も正しいです。

これだけだと、何言ってんのか意味不明だと思うので「printf 有効/無効」「for ループ 1つ/2つ」に着目して説明します。

1番目の実験(printf 無効、for ループ 2つ)
プログラムを見ると guard1, guard2 に対して memset 0 した後、参照のみで代入しません。コンパイラは guard1, guard2 をスタックに配置せず、配列への参照(guard1[i], guard2[i])は全て「定数の 0」に置換します。
(GIMPLE を見たら 033t.fre1 で 0 に置換されるようです)
このとき array のバッファオーバーランはスタックに退避されているレジスタ値などを書きつぶしますが、ギリギリ続行できています。
2番目の実験(printf 有効、for ループ 2つ)
1番目と変わりないと思いきや、printf が guard1, guard2 のアドレス参照をするため、定数の 0 に置換すると返せるアドレスがなくなり結果が変わってしまいます。このため、guard1, guard2 はスタックに配置されます。
このとき array のバッファーオーバーランは隣に配置された guard2 を書きつぶします。
3番目の実験(printf 無効、for ループ 1つ
いわゆる偶然の結果です。1番目と同様に guard1, guard2 はスタックに配置されず、array のバッファオーバーランによりスタックに退避したレジスタ値などが壊れます。for ループが 1つ減ったことでスタックに退避されるレジスタが 1つ減って(8バイト分余裕がなくなる)、1番目の実験でギリギリリターンアドレスを壊されずに耐えていたものが、耐えられなくなります。

3番目の実験の裏打ちとして、試しにループ回数を 80回くらいにすると for ループが 1つだろうが 2つだろうが、リターンアドレスがぶっ壊れて Segmentation Fault します。10行目の printf を有効にすると guard1, guard2 がスタックに配置されて、受け止めてくれるので、80回でも耐えます。

難解な C 言語仕様、曖昧な利用者の理解、過激なコンパイラの最適化、が招く結末

バッファオーバーランを期待していた向きには残念(?)かもしれませんが、guard1, guard2 はメモリ上に置いても置かなくても、C 言語仕様に矛盾しないなら、どっちでも良いです。もっというと C 言語仕様に矛盾しないなら、コンパイラの最適化は何をやっても OK です。

この「C 言語仕様に矛盾しないなら」はおそらくコンパイラ開発者には常識なのでしょうけども、C 言語の仕様は人間に優しくないのと、大多数の C 言語プログラマは言語仕様(特に未定義動作)を理解しておらず、何となく使っています。

難解な仕様、曖昧な理解、過激な最適化の相乗効果により、今日も世界のどこかで
「最適化で動きが変になっちゃったよ……。どうして…どうして……?」
とコンパイラとすれ違ったプログラマが泣いているでしょう。。。

参考

大したものではありませんが、ソースコードを載せておきます。

実験用ソースコード

#include <stdio.h>
#include <string.h>

int undefined()
{
	int guard1[50];
	int array[50];
	int guard2[50];
	int sum = 0, i;

	memset(guard1, 0, sizeof(guard1));
	memset(guard2, 0, sizeof(guard2));
	//printf("%p %p %p\n", &guard1[0], &array[0], &guard2[0]);

	for (i = 0; i < 60; i++) {
		array[i] = i;
	}

	for (i = 0; i < 60; i++) {
		sum += array[i];
	}

	for (i = 0; i < 50; i++) {
		printf("%2d: %d %d %d\n", i, guard1[i], array[i], guard2[i]);
	}

	return sum;
}

int main(int argc, char *argv[])
{
	int sum1 = 0, sum2 = 0, i;

	sum1 = undefined();

	for (i = 0; i < 60; i++) {
		sum2 += i;
	}

	printf("%d: %d\n", sum1, sum2);

	return 0;
}
[編集者: すずき]
[更新: 2020年 1月 26日 19:04]

コメント一覧

  • コメントはありません。
open/close この記事にコメントする



link permalink

link 編集する

クロスビルド用ツールチェーン - GCC 10.0 にしたらハマった

新し目の AArch64 のクロスコンパイル用ツールチェーンを作ろうとして、かなりハマったのでメモしておきます。

基本的には前回(2019年 4月 29日の日記参照)ご紹介した手順でビルドします。GCC と glibc のコードを変えなくて良い組み合わせは下記の通りです。特に新しいバージョンを使う理由がなければ、この組み合わせが無難です。

  • gcc: 8.3.0 (tag: releases/gcc-8.3.0)
  • glibc: 2.28 (tag: glibc-2.28)

私は新しい GCC が使いたかったので、HEAD にしました(バージョン的には 10.0 相当)。どうやら GCC のエラーチェックが厳しくなるらしく、glibc のビルドが通らなくなります。たくさんエラーが出ますが、一例を挙げると、下記のようなエラーです。

GCC HEAD (GCC 10.0 相当) で glibc 2.28 をビルドするとコンパイルエラー
./../include/libc-symbols.h:534:26: error: '__EI___errno_location' specifies less restrictive attributes than its target '__errno_location': 'const', 'nothrow' [-Werror=missing-attributes]
  534 |   extern __typeof (name) __EI_##name \
      |                          ^~~~~

エラーは glibc を新しくすると解決されるかと思いきや、よりおかしなことになります。例えば GCC 8.3 のまま glibc 2.30(おそらく 2.29 でも同じ症状が出る)にすると、下記のような変なエラーが出ます。

GCC 8.3 で glibc 2.30 をビルドするとリンクエラー
crosstool-builder-new-aarch64/buildroot/lib/gcc/aarch64-unknown-linux-gnu/8.3.0/../../../../aarch64-unknown-linux-gnu/bin/ld: crosstool-builder-new-aarch64/build/glibc/support/links-dso-program.o: Relocations in generic ELF (EM: 62)
crosstool-builder-new-aarch64/buildroot/lib/gcc/aarch64-unknown-linux-gnu/8.3.0/../../../../aarch64-unknown-linux-gnu/bin/ld: crosstool-builder-new-aarch64/build/glibc/support/links-dso-program.o: Relocations in generic ELF (EM: 62)
...

エラーの原因となっているオブジェクト links-dso-program.o を調べると、AArch64 向けにビルドしているにも関わらず、なぜか x86_64 用のオブジェクトが生成されています。

glibc/support 下に生成されたオブジェクト
build/glibc/support$ file *.o

echo-container.o:    ELF 64-bit LSB relocatable, ARM aarch64, version 1 (SYSV), with debug_info, not stripped
links-dso-program.o: ELF 64-bit LSB relocatable, x86-64, version 1 (SYSV), with debug_info, not stripped    ★★★★これ★★★★
shell-container.o:   ELF 64-bit LSB relocatable, ARM aarch64, version 1 (SYSV), with debug_info, not stripped
stamp.o:             empty
test-container.o:    ELF 64-bit LSB relocatable, ARM aarch64, version 1 (SYSV), with debug_info, not stripped
true-container.o:    ELF 64-bit LSB relocatable, ARM aarch64, version 1 (SYSV), with debug_info, not stripped

いったい何ですかね、これ。バグなのか、仕様なのかわかりません……。

GCC 10.0 と glibc 2.30 の組み合わせでビルドする方法

GCC と glibc をお互い最新にした組み合わせ、すなわち下記の組み合わせにしたとき、

  • gcc: 10.0 (HEAD)
  • glibc: 2.30 (tag: glibc-2.30)

先ほど説明した、両方のエラーに遭遇してビルドできませんので、glibc にパッチを当ててビルドエラーを回避します。

まずはコンパイルエラーを無視するパッチです。本来はエラーを無視するのではなく、エラーが指摘している事項を直すべきですけど、今回の主眼ではないのと、いずれ glibc 本家が直るだろうことを期待しておきます。

glibc のビルドエラーをあえて無視するパッチ

diff --git a/Makeconfig b/Makeconfig
index fd36c58c04..106688e210 100644
--- a/Makeconfig
+++ b/Makeconfig
@@ -916,7 +916,8 @@ ifeq	"$(strip $(+cflags))" ""
 endif	# $(+cflags) == ""
 
 +cflags += $(cflags-cpu) $(+gccwarn) $(+merge-constants) $(+math-flags) \
-	   $(+stack-protector)
+	   $(+stack-protector) \
+	   -Wno-zero-length-bounds -Wno-array-bounds -Wno-maybe-uninitialized
 +gcc-nowarn := -w
 
 # Each sysdeps directory can contain header files that both will be

次の links-dso-program はコミットログを見る限り、テストのサポート用ライブラリなので、とりあえず無くても動くはずです。ビルド自体をやめるパッチをあてます。

glibc の support/links-dso-program をビルドさせないパッチ

diff --git a/support/Makefile b/support/Makefile
index ab66913a02..19c3de2043 100644
--- a/support/Makefile
+++ b/support/Makefile
@@ -184,12 +184,12 @@ CFLAGS-support_paths.c = \
 		-DSBINDIR_PATH=\"$(sbindir)\" \
 		-DROOTSBINDIR_PATH=\"$(rootsbindir)\"
 
-ifeq (,$(CXX))
-LINKS_DSO_PROGRAM = links-dso-program-c
-else
-LINKS_DSO_PROGRAM = links-dso-program
-LDLIBS-links-dso-program = -lstdc++ -lgcc -lgcc_s $(libunwind)
-endif
+#ifeq (,$(CXX))
+#LINKS_DSO_PROGRAM = links-dso-program-c
+#else
+#LINKS_DSO_PROGRAM = links-dso-program
+#LDLIBS-links-dso-program = -lstdc++ -lgcc -lgcc_s $(libunwind)
+#endif
 
 ifeq (yes,$(have-selinux))
 LDLIBS-$(LINKS_DSO_PROGRAM) += -lselinux

クロスコンパイル環境は、各モジュールのバージョンアップですぐ壊れてしまって辛いです。ARM がこれだけ覇権を握っているにも関わらず、gcc も glibc もあまりチェックしてないんですかね……??

[編集者: すずき]
[更新: 2020年 1月 28日 00:12]

コメント一覧

  • superzeros 
    ARM64 target (cortexa53)と x86_64 host (Ryzen7)のクロスコンパイラのビルドで、当方にもご指摘のエラーがありました。で、そのパッチを拝借したところ、見事GLIBCのコンパイルが通りました。

    ありがとうございました!

    なんと申しましょうか、とても柔軟かつ大胆に繊細な対応をなされますね。

    補足:当方の各パッケージのバージョンは latest(2020-04-15)
    linux-5.6.4 (headers)
    gcc-9.3.0
    glibc-2.31
    binutils-2.34
    configure やBUILD手法は archlinux PKGBUILD と Cross LFS BOOK 等のWEB情報を参考にして適当にやっております(笑  
    (2020年04月15日 19:59:41)
  • すずき 
    コメントありがとうございます。お役に立ったようで良かったです。

    動作に支障無さそうなところだったので、かなり適当に変えてしまいました。

    大胆というか適当というか……。 
    (2020年04月15日 23:13:54)
  • superzeros 
    少し気になったのでglibcの履歴を調べてみたら・・・

    glibc-2.31/support/links-dso-program-c.c
    glibc-2.31/support/links-dso-program.cc

    このファイルは、2.29で最初に追加され

    \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\

    #include <iostream>
    using namespace std;
    int
    main (int argc, char **argv)
    /* Complexity to keep gcc from optimizing this away. */
    cout << (argc > 1 ? argv[1] : "null");
    return 0;


    \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\

    で、続いて2.30では

    \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\

    #include <iostream>
    /* makedb needs selinux dso's. */
    #ifdef HAVE_SELINUX
    # include <selinux/selinux.h>
    #endif
    using namespace std;
    /* The purpose of this file is to indicate to the build system which
    shared objects need to be copied into the testroot, such as gcc or
    selinux support libraries. This program is never executed, only
    scanned for dependencies on shared objects, so the code below may
    seem weird - it's written to survive gcc optimization and force
    such dependencies.
    */
    int
    main (int argc, char **argv)
    /* Complexity to keep gcc from optimizing this away. */
    cout << (argc > 1 ? argv[1] : "null");
    #ifdef HAVE_SELINUX
    /* This exists to force libselinux.so to be required. */
    cout << "selinux " << is_selinux_enabled ();
    #endif
    return 0;


    \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\

    2.29では、どういう経緯で誰によって導入されたのか、知りませんけど
    2.30では、SElinux(国家安全保障局)がどうたらこうたらの追加です
    誰のためになるんでしょうか ・・・

    自分の所有するハードウエアを自分で使いこなしたいだけのわたしからすれば
    バージョンアップに紛れ込んだオープンソースへの破壊活動みたいなもんですよ
    貴重な時間を奪われるところでしたが、おかげさまですぐに解決できてよかったです
    貴方に感謝いたします

      \(^o^)/
     
    (2020年04月16日 21:07:51)
  • すずき 
    詳細は調べていないので、コード中のコメントからの想像ですが……破壊活動というほどではないかなと思います。

    SELinux が有効なときは、SELinux のライブラリがないと動かないのでしょう。このヘルパープログラムは SELinux のライブラリを使っているから、ビルドシステムはテスト用の rootfs に SELinux のライブラリをコピーしてくれ、ということを示すための変更だと思います。 
    (2020年04月18日 23:05:39)
open/close この記事にコメントする



link permalink

link 編集する

Hello! Zephyr OS!! (RISC-V 32bit 版)

以前 Zephyr OS を実行しました(2019年 1月 12日の日記参照)が、今回は RISC-V 32bit 版で試してみようと思います。

この例では build をビルドディレクトリとします。どこに置いても動くはずですが、私はとりあえず zephyr の下に置いています。

クロスコンパイラの準備

前回同様 Zephyr SDK もしくは Crosstool-NG が使えます。参考までに Crosstool-NG のコンフィグを載せておきます。

crosstool-NG RISC-V 版のビルド
$ ./ct-ng menuconfig

Target options  --->
  Target Architecture (riscv)  --->
  [*] Build a multilib toolchain (READ HELP!!!)
  Bitness: (64-bit)  --->
  (rv32ima) Architecture level
  (ilp32) Generate code for the specific ABI

Toolchain options  --->
  (zephyr) Tuple's vendor string

Debug facilities  --->
  [*] gdb  --->
    [*]     Build a static cross gdb

一見すると 32bit CPU のコードをビルドする予定なのに、Bitness: 64bit にしており、不思議なコンフィグに見えるかもしれませんが、Zephyr OS のビルドはクセがあって、この設定が必要です。

Architecture level, Generate code for the specific ABI は GCC が生成するバイナリの命令セットを指定しており、それぞれ -march=rv32ima, -mabi=ilp32 に対応します。特に何も指定しないと rv32gc, ilp32f になるようです。

Zephyr の CMake スクリプト
zephyr/cmake/toolchain/xtools/target.cmake

set(CROSS_COMPILE_TARGET_riscv   riscv64-zephyr-elf)

set(CROSS_COMPILE_TARGET ${CROSS_COMPILE_TARGET_${ARCH}})

Zephyr の CMakefile を見るとわかるんですが、riscv64 も riscv32 も区別せず同じコンパイラでビルドし、しかもコンパイラ名は常に riscv64-zephyr-elf-gcc だと思っています。したがって 64bit 版をビルドする必要があり、multilib を有効にしています。

(余談)riscv 向けの -mabi, -march オプションの値を決めているのは zephyr/compiler/gcc/target.cmake で、rv32ima 固定になっています


-    list(APPEND TOOLCHAIN_C_FLAGS -mabi=ilp32 -march=rv32ima)
+    list(APPEND TOOLCHAIN_C_FLAGS -mabi=ilp32f -march=rv32gc)

上記のように変えると、Crosstool-NG で Architecture level, Generate code for the specific ABI の設定をしなくても良くなりますが、動くかどうかは試していません。

ビルドの方法

実ボードを持っていないので QEMU で試します。SiFive HiFive1 をエミュレートしているそうです。

環境変数のセットアップと cmake の実行
$ cat ~/.zephyrrc 

export ZEPHYR_TOOLCHAIN_VARIANT=xtools
export XTOOLS_TOOLCHAIN_PATH=/home/katsuhiro/x-tools

$ cd zephyr
$ source zephyr-env.sh

$ mkdir build
$ cd build

$ cmake -G Ninja -DBOARD=qemu_riscv32 ../samples/hello_world/

-- Zephyr version: 2.1.99
-- Found PythonInterp: /usr/bin/python3 (found suitable version "3.7.6", minimum required is "3.6")
-- Selected BOARD qemu_riscv32
-- Loading /home/katsuhiro/share/projects/oss/zephyr/boards/riscv/qemu_riscv32/qemu_riscv32.dts as base
Devicetree configuration written to /home/katsuhiro/share/projects/oss/zephyr/build/zephyr/include/generated/devicetree.conf
Parsing /home/katsuhiro/share/projects/oss/zephyr/Kconfig
Loaded configuration '/home/katsuhiro/share/projects/oss/zephyr/boards/riscv/qemu_riscv32/qemu_riscv32_defconfig'
Merged configuration '/home/katsuhiro/share/projects/oss/zephyr/samples/hello_world/prj.conf'
Configuration saved to '/home/katsuhiro/share/projects/oss/zephyr/build/zephyr/.config'
-- The C compiler identification is GNU 8.3.0
-- The CXX compiler identification is GNU 8.3.0
-- The ASM compiler identification is GNU
-- Found assembler: /home/katsuhiro/x-tools/riscv64-zephyr-elf/bin/riscv64-zephyr-elf-gcc
-- Cache files will be written to: /home/katsuhiro/.cache/zephyr
-- Configuring done
-- Generating done
-- Build files have been written to: /home/katsuhiro/share/projects/oss/zephyr/build

実行するときは ninja run で良いんですが、前回同様に ninja が何を起動しているか調べてみます。

Zephyr OS 実行
$ /usr/bin/qemu-system-riscv32 -nographic -machine sifive_e -net none -chardev stdio,id=con,mux=on -serial chardev:con -mon chardev=con,mode=readline -kernel /home/katsuhiro/share/projects/oss/zephyr/build/zephyr/zephyr.elf -s -S

(gdb から continue をすると、下記が出力される)

*** Booting Zephyr OS build zephyr-v2.1.0-1471-g7e7a4426d835  ***
Hello World! qemu_riscv32

オプション -s は GDB の接続を localhost:1234 で受け付けます、という意味です。オプション -S はエミュレータを Halted 状態で起動します。もし GDB をつなぐ必要がなければ -s や -S を削って起動してください。

GDB 接続
$ riscv64-zephyr-elf-gdb

GNU gdb (crosstool-NG 1.24.0.60-a152d61) 8.3.1
Copyright (C) 2019 Free Software Foundation, Inc.

...

(gdb) set arch riscv:rv32
The target architecture is assumed to be riscv:rv32

(gdb) target remote localhost:1234

Remote debugging using localhost:1234
warning: No executable has been specified and target does not support
determining executable automatically.  Try using the "file" command.
0x00001000 in ?? ()

(gdb) continue

Continuing.

実行できました。おなじみの Hello World! です。

[編集者: すずき]
[更新: 2020年 2月 16日 18:21]

コメント一覧

  • コメントはありません。
open/close この記事にコメントする



こんてんつ

open/close wiki
open/close Java API

過去の日記

open/close 2002年
open/close 2003年
open/close 2004年
open/close 2005年
open/close 2006年
open/close 2007年
open/close 2008年
open/close 2009年
open/close 2010年
open/close 2011年
open/close 2012年
open/close 2013年
open/close 2014年
open/close 2015年
open/close 2016年
open/close 2017年
open/close 2018年
open/close 2019年
open/close 2020年
open/close 過去日記について

その他の情報

open/close アクセス統計
open/close サーバ一覧
open/close サイトの情報