RaspbianでClang 3.8を野良ビルドする

2016年3月に日本でもRaspberry Pi 3 Model Bが販売開始されました。しかし、オフィシャルから提供されているRaspbianのC/C++コンパイル環境では依然gcc 3.8または、clang 3.0までしか提供されていないようです(自分はRaspberry Pi 2 Model Bまでしか確認していませんが、OSイメージは同じなため多分状況変わらず)。そのため、C++11の正規表現C++14の利用が難しい状況です。そこで、Raspbian上にLLVM, clang, libc++ 3.8を適用し、ビルド環境と標準ライブラリをバージョンアップします。

前提

  • Raspberry Pi 2 Model B以上(多分3 Model Bでも行けると思う)
  • 空きストレージ容量4GByte以上
  • Raspbian(update, upgrade済み)
  • オフィシャル提供のclang, libc++との共存は考慮していません。

作業前に

LLVMらのコンパイル作業はクロスコンパイラではなく、Raspberry Pi実機上で行いました。 コンパイルには大量のメモリが必要になるため、作業前に使っていないサービスを停止しておいてください。 Xも起動せず、フレームバッファ・コンソールかSSHでの作業をオススメします。

<インストール先>パスについて

何も設定しない場合、デフォルトのインストール先は/usr/local以下となります。 大体の場合、そのままでOKだと思いますが、cmakeは野良ビルドしたものと既存のものが混在するため、パスを明示しています。

cmakeのコンパイル & インストール

Raspbianのaptでインストールできるcmakeではバージョン不足でコンパイルができません。 先にcmakeを用意します。

$ sudo apt-get install pkg-config automake libtool libffi-dev libatomic1 gcc-4.8 g++-4.8 zlib-bin
$ mkdir <作業ディレクトリ>
$ cd <作業ディレクトリ>
$ wget https://cmake.org/files/v3.5/cmake-3.5.0.tar.gz
$ tar vzxf cmake-3.5.0.tar.gz
$ cd cmake-3.5.0
$ ./configure --prefix=<インストール先>
$ make -j2
$ sudo make install

LLVM, clang, libc++のコンパイル & インストール

コンパイル作業はオフィシャルのマニュアルに従い進めます。一部オプションをRaspbian用にカスタマイズします。 Getting Started with the LLVM System — LLVM 3.8 documentation

LLVM, clang, libc++のコンパイル & インストールはいっぺんに行います。 libc++abiはコンパイルに失敗したので外しています。

$ cd <作業ディレクトリ>
$ wget http://llvm.org/releases/3.8.0/llvm-3.8.0.src.tar.xz  http://llvm.org/releases/3.8.0/cfe-3.8.0.src.tar.xz http://llvm.org/releases/3.8.0/compiler-rt-3.8.0.src.tar.xz http://llvm.org/releases/3.8.0/libcxx-3.8.0.src.tar.xz http://llvm.org/releases/3.8.0/openmp-3.8.0.src.tar.xz
$ tar -Jxvf llvm-3.8.0.src.tar.xz
$ tar -Jxvf cfe-3.8.0.src.tar.xz
$ tar -Jxvf compiler-rt-3.8.0.src.tar.xz
$ tar -Jxvf libcxx-3.8.0.src.tar.xz
$ tar -Jxvf openmp-3.8.0.src.tar.xz
$ $ mv cfe-3.8.0.src llvm-3.8.0.src/tools/clang
$ mv compiler-rt-3.8.0.src llvm-3.8.0.src/projects/compiler-rt
$ mv libcxx-3.8.0.src llvm-3.8.0.src/projects/libcxx
$ mv openmp-3.8.0.src llvm-3.8.0.src/projects/openmp
$ cd llvm-3.8.0.src/
$ mkdir build
$ cd build
$ <インストール先>/cmake -G "Unix Makefiles" -DCMAKE_INSTALL_PREFIX=<インストール先> -DCMAKE_BUILD_TYPE=Release -DCMAKE_C_COMPILER=gcc-4.8 -DCMAKE_CXX_COMPILER=g++-4.8 -DLIBCXX_CXX_ABI=libstdc++ -DLLVM_TARGETS_TO_BUILD="ARM;X86;AArch64" ..
$ make -j2
$ sudo make install

メモリ不足でmakeに失敗する場合、-j2オプションを外してください(-j2で2並列にしています。)

インストール先として/usr/local以外を指定している場合、環境変数PATH, LD_LIBRARY_PATHなどにインストール先を追加します。 .bashrcなどに以下のように追記します。

export PATH=$PATH:<インストール先>/bin
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:<インストール先>/lib

記載内容を以下のとおり反映します。

$ source ~/.bashrc

まだ、ldconfigのパスも追加しておきます。 /etc/ld.so.conf.d/clang.confなどのファイルを作成し、以下のようにパスを追加します

<インストール先>/lib

記載内容を以下のとおり反映します。

$ sudo ldconfig

確認

最後にインストールしたclangが有効になっているか確認します。

$ clang --version

Boost.勉強会 #19 東京 に参加してきました

C++で開発する割に、C++系の人と滅多に会わないので初めて参加してきました。

connpass.com

厳つい老齢の開発者にコテンパンにのされるのかとビクビクしていましたが、July Tech Festaより若い人が多い印象です。 内容は低レイヤーや規格、Boostの実装についての物が多いです。

Boostライブラリ一周の旅 1.59.0-1.60.0 / Boost.Configについて

  • BoostライブラリのコミッタやC++の規格策定に関与している方が何人か参加している(っぽい)。
  • C++でも一時期のWebブラウザ程ではないが、コンパイラ間の差異に悩まされる。Boostのような幅広いライブラリの開発においてはクリティカルな問題。

EMC++とCppCoreGuidelinesについて

512bit SIMD (AVX-512)

  • SIMDコンパイラにお任せ。人力実装でコンパイラより早くするのは至難の業。
  • 環境依存になりやすく、他の手もやりつくし、残るはSIMDの人力実装のみ且つ失敗(性能向上しない)としても試すほかない環境以外では手を出すべきではない。

「女性のためのC++コミュニティ Ladies++ meetup #1」の紹介

  • 女性のためのC++コミュニティ Ladies++ ってのがあるそうだ。
  • Boost.勉強会の会場を見た感じ、女性5%いるかいないか……そりゃLadies作りますよね。

クロスプラットフォームマルチメディアライブラリSDL2の紹介

  • SDL2。Emscripten, iOS, Android上でも動く。SteamのマルチプラットフォームタイトルはSDLを使っているものがある。
  • どうもEmscripten上でSDLOpenGLを使う人がそこそこ居る(ゲーム関係?)。
  • SDL2からはIMEが使えるようになったと聞いたものの、調べてみたら完全対応という訳ではないっぽい。Electronなどと合わせて、GUI部品はwebで作り、エンジン部分がSDLのような住み分けはどうだろう?とおもったが、V8のasm.js対応ってどうなっていたっけ(パフォーマンス的意味で)?

クソザコ鳥頭が非順序連想コンテナに入門してみた

  • 非順序連想コンテナの実装について、概念図はあくまで概念図で実装はパフォーマンスやメモリ重視でかな〜〜り工夫されている。
  • libstdc++の実装には計算オーダー的にグレーな実装がされている場合がある。
  • どうも、この界隈のクソザコとは、ライブラリの実装を追い、幾つかの実装の比較ができるようになってから名乗ることが許される称号のようだ。

expectedによるエラーハンドリング

  • 毎回頭を悩ませるエラーハンドリングの新しい仕組みについて。
  • まだ制式採用されたわけではないので利用できないが、やりたいことは理解できた。

メモリモデル再入門

  • 変数とは何?マルチスレッドでのロックの話。
  • バグを出したくなければData Race Freeなコードを書け。
  • 2スレッドでtryLockに失敗したからといって、スレッド1でロックが成功しているわけではない。
  • 静的解析で指摘してくれる(google/sanitizers · GitHub)。

ほか、懇親会や休憩時間で聞けたこと

  • Emscriptenの例外対応はONにするとパフォーマンスが落ちるのでデフォルトOFFになっている(ヤベ)。
  • libuvのようなcallback登録を行うライブラリとC++の例外の扱いについて実装のヒントをもらえた。
  • armプロセッサはx86系に比べメモリバリアを厳としないとData Race時に異常値を持ってクラッシュしやすい。
  • x86上で動くarmエミュレータはホストのメモリに守られるため実機のみで再現する厄介なバグに遭遇する可能性がある。
  • Boostのテスト環境はボランティアによるもの。CPUアーキテクチャ×コンパイラバージョン×OS他の環境による組み合わせ数の爆発でかなり大変。
  • マルチスレッド、ロックフリーで使える汎用アルゴリズムlibcds
  • アムダールの法則

OSXでループバックデバイスっぽいことをする

OS Xではあまり馴染みがないかもしれませんが、ループバックデバイスっぽいことができます。

itpro.nikkeibp.co.jp

前提

今回入れたもの

ext4を内包するファイルを使ったループバック

OSXだとデフォルトでHFS(+)ですが、後からサイズ変更する方法がわからなかったので、資料があるext系のファイルシステムで試しました。 FUSE for OS Xは以下のサイトからインストーラをダウンロードしてインストールします。

Home - FUSE for OS X

途中、インストールするコンポーネントの選択画面が出ます。MacFUSE Compatibility Layerにチェックを入れます。

fuse-ext2も同様に以下のサイトからインストーラをダウンロードしてインストールします。

sourceforge.net

extを操作するためのコマンドラインツールをインストールします。

$ brew install e2fsprogs

適当なサイズの入れ物を作る。見ての通り小さすぎるとジャーナルできないらしい。 ブロックサイズやinodeの数は自動的に計算されますが、後から変更できないっぽいので、サイズやinode数は適宜調整します。 今回はとにかく作ったものです。

最初にext4ファイルシステムを内包した空のファイル(image.bin)を作ります。

$ cd <作業フォルダ, イメージ格納先>
$ gtruncate -s 1MB image.bin
$ <e2fsprosインストール先(/usr/local/Cellar/e2fspros/あたりなはず)>/sbin/mkfs.ext4 -F image.bin
mke2fs 1.42.13 (17-May-2015)

Filesystem too small for a journal
Creating filesystem with 976 1k blocks and 128 inodes

Allocating group tables: done
Writing inode tables: done
Writing superblocks and filesystem accounting information: done

作ったファイルをマウントして使います。

$ mkdir <マウント先>
$ fuse-ext2 -o rw+ image.bin <マウント先>
$ ls volume
lost+found

lost+foundは消しても良いはずです(ファイルシステムを作るときに見つかったデータとかだったと思う)。

もっとサイズが必要になったら拡張ができます。

$ umount <マウント先>
$ <e2fsprogs>/sbin/resize2fs image.bin 50M
resize2fs 1.42.13 (17-May-2015)
Resizing the filesystem on image.bin to 51200 (1k) blocks.
The filesystem on image.bin is now 51200 (1k) blocks long.
$ fuse-ext2 -o rw+ image.bin <マウント先>

圧縮ファイルをマウントする

領域サイズを調整するのが面倒だし、普段使わないので多少スピードが遅くなっても容量を節約したい場合、archivemountを使うと、圧縮ファイルtar.gzをマウントできるようになります。書き換えもできます。

osdn.jp

$ brew install homebrew/fuse/archivemount
$ touch dummy
$ tar czvf image.tar.gz dummy
$ rm dummy
$ archivemount image.tar.gz <マウント先>

使い終わったらアンマウントすると圧縮ファイルの内容が更新されます。

$ umount <マウント先>

リンク先にあるように、archivemountはアンマウント時に圧縮ファイルが2つできます。そのため十分なファイルサイズが必要になります。-o nobackupオプションでバックアップが自動削除されますが、一時的には2つ分の容量を消費するようです。

ライブラリをコンパイルしてLLVM-IRを出力する方法

皆さんご存知、LLVM-IRはコンパイラ基盤であるLLVMの中間生成物であり、emscriptenやPROCESS WARPLLVM-IRを読み込んで動かすことができるプラットフォームです。 現代のプログラミングでは既存ライブラリを利用することにより開発期間の短縮や品質向上を図っています。前述のプラットフォームでプログラムを動かす場合、自分のプログラムをLLVM-IRに変換するわけですが、その過程で依存ライブラリも同様にLLVM-IRに変換する必要があります。

$ clang -emit-llvm -c hoge.c

上記コマンドでC/C++からLLVM-IRへの変換はできていましたが、autoconfなどのビルドツールでは同様の方法では対応できませんでした。このような場合には-fltoオプションを使うとうまくいくようです。

The LLVM gold plugin — LLVM 3.8 documentation

GMPというライブラリを例にLLVM-IRの塊を取り出してみます。 GMPはC/C++で任意精度の整数、小数演算を行うためのライブラリです。標準C/C++ライブラリ以外にほとんど依存しません。

The GNU MP Bignum Library

前提

  • OS X Yosemite Version 10.10.5
  • clang Apple LLVM version 7.0.0 (clang-700.1.76)
  • LLVM 3.6.2 (homebrewでインストル済み)
  • GMP 6.1.0 (.tar.bz2ファイルをダウンロード、解凍しておく)

コンパイル作業

$ cd <gmpの展開先>
$ CC="clang -flto" CXX="clang++ -flto" ./configure --disable-shared
<省略>
$ make
<省略>

GMPは通常、共有ライブラリと静的ライブラリの両方をコンパイルします。LLVM-IRを取り出す場合、静的ライブラリだけあればOKなので--disable-sharedオプションを指定しています。 GMPの場合、.libフォルダ以下にコンパイル済みライブラリが格納されています。

$ cd .lib
$ ls
libgmp.a  libgmp.la  libgmp.lai

静的ライブラリlibgmp.aにはLLVM-IRのBitCodeが含まれています。一旦arを展開し、中のBitCodeを取り出します。

$ mkdir work
$ cd work
$ llvm-ar x ../libgmp.a
$ ls
<libgmp.aに格納されていた.oファイルが展開されているはず>

展開された.oファイルはただのオブジェクトファイルではなくBitCodeです。 llvm-disコマンドでヒューマンリーダブルな.llファイルに変換できます。

$ find *.o -exec llvm-dis {} \;

あとはllvm-linkコマンドで1つにまとめて終わりです。

$ llvm-link -o ../libgmp.bc *.ll
$ cd ..
$ llvm-dis libgmp.bc
$ ls
libgmp.a  libgmp.bc  libgmp.la  libgmp.lai  libgmp.ll  work

失敗談

fltoオプションの存在に気づくまで、emscriptenのようにCCにラッパを流し込んでなんとかしようとしていました。 基本動作は以下のとおりでスクリプトを組んだのですが、autoconfの関数有無の判定はプログラムがリンクまで正常に行えるかを基準にしています。 LLVM-IRの出力までで処理を止めた場合、関数がなくともリンク相当のコマンドが正常終了し、存在しない関数が有ると判定しmakeで止まるとうい問題が発生しました。 その時の調査で-fltoオプションを見つけて無事ライブラリのLLVM-IRを出力できることが分かりました。 ここまで来るのに2日ほどかけてgcc/clang/emcc/autoconfの動作をトレースしたんですけどね… 以下は必要なくなったラッパプログラムです。

#!/usr/bin/env python
# coding:utf-8

import subprocess
import sys
import re
import os
import stat

i_name = False
o_name = False
o_name_idx = False
is_c = False
is_e = False
is_o = False
is_s = False
s_idx = False

# 起動引数を読み取って、-c, -o, -S関連の有無と場所を確認
for idx, arg in enumerate(sys.argv):
    if is_o == True and (not o_name):
        o_name = arg
        o_name_idx = idx

    matched = re.match(r"^(.*)\.(c|i|cpp|cxx|cc|c\+\+|ii|s)$", arg)
    if matched:
        i_name = matched.group(1)

    if arg == "-c":
        is_c = True
    if arg == "-E":
        is_e = True
    if arg == "-o":
        is_o = True
    if arg == "-S":
        is_s = True
        s_idx = idx

# 起動引数をコピーしてコマンドを作成
command = sys.argv[:]
#
command[0] = "clang"

# -Sオプションの場合は-cで上書きする
if is_s:
    command[s_idx] = "-c"

# プリプロセッサ出力の場合を除き、出力形式に合わせて出力ファイル名を決定
if not is_e:
    if is_o:
        # 実行可能形式でのコンパイルをしようとした場合は、hoge.00に出力
        if not is_c and not is_s:
            command[o_name_idx] = o_name + ".00"
    else:
        command.insert(1, "-o")
        if is_s:
            # アセンブラやオブジェクトファイルの場合は
            # .s, .oファイルを出力(中身はLLVM-IR)
            command.insert(2, i_name + ".s")
        elif is_c:
            command.insert(2, i_name + ".o")
        else:
            # 実行可能形式でファイル名が指定されない場合はa.out.00に出力
            o_name = "a.out"
            command.insert(2, o_name + ".00")

    # コンパイルオプションに-emit-llvmを付加する
    command.insert(1, "-emit-llvm")
    command.insert(2, "-c")
    command.insert(3, "-fno-vectorize")

# set target
command.insert(1, "-m64")

proc = subprocess.Popen(
    command,
    shell  = False,
    stdin  = subprocess.PIPE,
    stdout = subprocess.PIPE,
    stderr = subprocess.PIPE)

stdout_data, stderr_data = proc.communicate()
print stdout_data
print stderr_data

# 実行可能形式でのコンパイルの場合、LLVM-IRを
# 実行するクッションスクリプトを作成
# @todo リンクオプションをlliに渡すようにする
if not is_c and not is_e and not is_s:
    f = open(o_name, "w")
    f.write("#!/usr/bin/env sh\n")
    f.write("/usr/local/Cellar/llvm/3.6.2/bin/lli " +
            o_name + ".00 \"$@\"\n")
    f.close()
    os.chmod(o_name, stat.S_IRWXU)

sys.exit(proc.returncode)

デザインに凝り過ぎた名刺は名刺スキャナ通らなかった

Evernoteでは名刺をスキャンする機能(名刺を写真で取ると取り込む)があります。 チョットデザインに凝ったものは失敗することが多いのでポイントをリストアップしました。

  • コントラストは高め。
    • コントラストを補正しているけど、やっぱり限界がある
    • リバースとストレートが混在するデザインは読み取り失敗が多い。
    • 透明、半透明の材質は低コントラストになって失敗が多い。
  • ゴシック、明朝は読み取り精度に影響が少ないようだが、手書き風フォントは精度が落ちる(ただし英語名刺の場合は奇抜でなければ読み取り精度が高い)。
    • 文字サイズは、よほど小さい場合以外は問題ない。
  • 片面だけで名前、所属、連絡先の基本情報を全て記載する。
    • 会社理念などを基本情報と同じ面にデザインすると認識精度が低くなる。
  • 紙は矩形。角丸や独創的な形の名刺は台形補正が失敗する。
    • 同様の理由で高コントラストの非矩形枠線も失敗要因になる。
  • 横書き推奨。
    • 他国産アルゴリズムでは縦書きの認識精度が低い。
    • 斜めの場合も認識精度が低い。
  • 1カラム構成が望ましい。2カラムの場所は左右でフォントサイズを変更する(役職と名前とか)。本社支社で2カラム構成はNG。

気づいたことがあったら加筆します。

PIAXで作る P2Pネットワーク

CMU #33で「PIAXで作るP2Pネットワーク」という題目で発表してきました。

cmu.connpass.com

www.slideshare.net

メモ

  • やっぱJavaScriptで使えればな〜という雰囲気があった。