LLVM IR の alloca 命令のつかいかた
LLVM IR の alloca 命令の使い方について,リファレンスマニュアルに載ってない注意点があったのでメモがてら書きます.
alloca
命令とは
スタック上にメモリを確保し,確保した領域の先頭へのポインタを返します.スタック上にメモリを割り付けることでアドレスが必要なメモリ領域を得ることができ,自動変数などの実装に使うことができます.
http://llvm.org/docs/LangRef.html#alloca-instruction
%ptr = alloca i32 ; yields i32*:ptr %ptr = alloca i32, i32 4 ; yields i32*:ptr %ptr = alloca i32, i32 4, align 1024 ; yields i32*:ptr %ptr = alloca i32, align 1024 ; yields i32*:ptr
変数などは基本的にこの alloca
命令を使って領域を確保するお馴染みの命令です.スタック上に割り当てるので,関数を抜けると同時にその領域は解放されます.
と,ここまでならリファレンスマニュアルを読めばそれで解決なのですが,いくつか注意点があります.
alloca
によるメモリ割り当てのタイミング
例えば下記のようなループを実装したいケース
int i = 0; while (i < 10) { int j = i * 2; printf("%d\n", j); ++i; }
そのまま書き下すと
%i = alloca i32 store i32 0 i32* %i br label %loop.init loop.init: %i1 = load i32 i32* %i %0 = icmp sle i32 %i1 10 br i1 %0 label %loop.end, label %loop.body loop.body: %1 = mul i32 %i1, 2 %j = alloca i32 store i32 %1 i32* %j %2 = load i32 i32* %j call void @printf(i8* @str, i32 %2) %3 = add i32 %i1, 1 store i32 %3 i32* %i br label %loop.init loop.end:
のような IR をつくりたくなりますが,これだと10行目の %j = alloca i32
がループごとに確保されてしまうので,ループするだけで再帰でもないのにどんどんスタックを食っていってしまいます.
なので,下記のようにスタックのアロケーションは原則関数の頭でやります.
%i = alloca i32 %j = alloca i32 store i32 0 i32* %i br label %loop.init loop.init: %i1 = load i32 i32* %i %0 = icmp sle i32 %i1 10 br i1 %0 label %loop.end, label %loop.body loop.body: %1 = mul i32 %i1, 2 store i32 %1 i32* %j %2 = load i32 i32* %j call void @printf(i8* @str, i32 %2) %3 = add i32 %i1, 1 store i32 %3 i32* %i br label %loop.init loop.end:
これは割と常識らしく,Clang は明示的にブロックをつくりこそしませんが,関数の頭で alloca
を呼んでいます.Rust や Crystal は関数の最初にその関数内で必要なスタック領域をすべて alloca
する基本ブロックを置く実装になっています.どちらでも良いですが,LLVM の C++ API でなく C bindings な llvm-c を使う場合は後者の方が実装がかなり楽だと思います.
C89 までブロックの先頭でしか変数を定義できなかったのはこういった理由があるのかもしれません(未確認)
関数の頭でメモリを割り当てられない例外:動的サイズ配列
ここまでは一般的なケースですが,残念ながらこれではうまくいかないケースがあります.C99 の動的サイズ配列です.
alloca
命令はN要素分の領域確保もできるためスタック上に動的サイズでメモリを確保すること自体は問題なくできます.
int i = 0; while (i < 10) { int arr[i + 1]; arr[i] = 42; printf("%d\n", arr[i]); ++i; }
例えば上記のようなコードの場合,arr
の確保する領域は i
の値に応じて動的に変わります.なので,関数の先頭で alloca
しておく前述の方法は使えません.
しかし,ループの中で単に alloca
してしまうとループが回るごとにスタックが積み上がっていってしまい,無限ループでもしようものならスタックを使い切ってプログラムがクラッシュしてしまうでしょう.
そこで,LLVM には llvm.stacksave と llvm.stackrestore という intrinsic 関数があります.前者は現在のスタック位置をレジスタに保存し,後者はレジスタの値にスタック位置を復帰します.
%0 = call i8* @llvm.stacksave() ; 現在のスタック位置を %0 に保存 %1 = alloca i32, i64 %x ; i32 の値を %x 個分確保する ; %1 を使った処理 call void @llvm.stackrestore(i8* %0) ; スタック位置を復帰.%1 の領域は解放される
これを上記の while
ループのブロック内で行うことで一度消費したスタックを復帰してから次のループに臨むことができ,スタックを消費しっぱなしにならなくできます.
初めこれを Clang の出力で見た時は偶然関数の最初と最後に stacksave
と stackrestore
があったので,どうしてこれが必要なんだろうと思いましたが,今回説明したような理由があったからでした.
Electron に Mac タッチバー API が実装された
個人的に気になっていた Electron のタッチバーサポートがついに master にマージされました.
実装は下記の PR で行われ,@MarshallOfSound さんの初期実装と @kevinsawicki さんのブラッシュアップで実装されました.
https://github.com/electron/electron/issues/8095
まだリリースされていないですが,待てなかったので master ブランチの実装を試してみました.
追記(2017/3/8): v1.6.3 beta としてリリースされました.
Electron の master ブランチをビルドする
ビルドの仕方はドキュメントにまとめられています. 再配布せず手元で試すだけであれば面倒そうな「macOS SDK」のセクションは無視してしまって大丈夫です.
# リポジトリを取得 $ git clone https://github.com/electron/electron && cd electron/ # ビルドに必要な依存関係パッケージの取得 $ ./script/bootstrap.py -v # ビルド $ ./script/build.py
ビルドにはそれなりに時間がかかります.out/R
にリリースビルドの Electron.app
が,out/D
にデバッグビルドの Electron.app
が生成されます.
Touchbar API
タッチバーはメインプロセスから制御することができます.レンダラプロセスからは IPC 経由でのアクセスになりそうです. macOS 10.12.1 以降のタッチバー搭載 MacBook Pro で利用できるようです
タッチバーの UI パーツにはいくつかの種類があり,各パーツごとの class と,それらをまとめる class が提供されています.
TouchBarButton
: アイコンとテキストが置けるボタンですTouchBarColorPicker
: カラーピッカーですTouchBarGroup
: 複数の UI パーツを1つのグループとして配置できますTouchBarLabel
: テキストを表示できるラベルですTouchBarPopover
: アイコンとラベルとキャンセルボタンが表示できる,タッチバーに出るダイアログボックス的な UI ですTouchBarSlider
: 音量などに使えるスライダーですTouchBarSpacer
: タッチバーのアイテムとアイテムの間にスペースを開けることができます.開けるスペースはsmall
,large
,flexible
(スペースを取れるだけ取る) の中から選べますTouchBar
:TouchBar
は上記 Touchbar の UI パーツを並べてタッチバーインスタンスを生成できます
タッチバーインスタンスは BrowserWindow
の setTouchbar()
メソッドによってセットできます.セットされると即座にタッチバーが表示されます.また,setTouchbar
メソッドに null
を与えることでタッチバーを消すことができます.
サンプルアプリ
ドキュメントにのっているサンプルアプリを動かしてみます.ドキュメントにはコード片のみなので,動くコードを下記に置いてみました.
サンプルアプリではタッチバー上でスロットを回すことができます.
エントリポイントである main.js
は下記の通りです.
const path = require('path') const {app, BrowserWindow, TouchBar} = require('electron') // 必要なタッチバーの UI パーツをインポートする const {TouchBarLabel, TouchBarButton, TouchBarSpacer} = TouchBar let spinning = false // スロットの各リールの文字列を表示するラベル3つ const reel1 = new TouchBarLabel() const reel2 = new TouchBarLabel() const reel3 = new TouchBarLabel() // スロットを回した結果を表示するラベル const result = new TouchBarLabel() // 'Spin' ボタン const spin = new TouchBarButton({ label: '🎰 Spin', // ボタンのテキスト.ここでは絵文字だけどアイコンも置けるはず… backgroundColor: '#7851A9', click: () => { // スロットが回っている間はクリックを無視する if (spinning) { return } // スロットを回す処理 spinning = true result.label = '' let timeout = 10 const spinLength = 4 * 1000 // 4 seconds const startTime = Date.now() // クリックされてから4秒間スロットを回す const spinReels = () => { // スロットのリールを更新する.止まった時のリールが結果になる updateReels() if ((Date.now() - startTime) >= spinLength) { finishSpin() } else { // 1.1倍ずつスロットを遅くしていく(慣性っぽさ) timeout *= 1.1 setTimeout(spinReels, timeout) } } spinReels() } }) // ランダムに絵柄を1つ選ぶ関数 const getRandomValue = () => { const values = ['🍒', '💎', '7️⃣', '🍊', '🔔', '⭐', '🍇', '🍀'] return values[Math.floor(Math.random() * values.length)] } // 3つのリールを更新 const updateReels = () => { reel1.label = getRandomValue() reel2.label = getRandomValue() reel3.label = getRandomValue() } const finishSpin = () => { // Set を使って重複を省き,残った要素数で絵柄がいくつ合ったかをチェックする const uniqueValues = new Set([reel1.label, reel2.label, reel3.label]).size if (uniqueValues === 1) { // すべての絵柄が揃ったとき:大当たり! result.label = '💰 Jackpot!' result.textColor = '#FDFF00' } else if (uniqueValues === 2) { // 3つ中2つのリールが同じ絵柄だったとき:当たり result.label = '😍 Winner!' result.textColor = '#FDFF00' } else { // 絵柄がまったく揃わなかったとき:はずれ result.label = '🙁 Spin Again' result.textColor = null } spinning = false } // [ボタン] [r1] [r2] [r3] [result] // // のように配置.配列に入れたパーツが入れた順にタッチバーに表示される const touchBar = new TouchBar([ spin, new TouchBarSpacer({size: 'large'}), reel1, new TouchBarSpacer({size: 'small'}), reel2, new TouchBarSpacer({size: 'small'}), reel3, new TouchBarSpacer({size: 'large'}), result ]) let window app.once('ready', () => { window = new BrowserWindow({ width: 200, height: 200 }) window.loadURL(`file://${path.join(__dirname, '/index.html')}`) // ブラウザウィンドウにタッチバーをセットする. // このタイミングでタッチバーにスロットマシンが表示される window.setTouchBar(touchBar) }) // 全てのウィンドウが閉じたときアプリを閉じる app.on('window-all-closed', () => { app.quit() })
まとめ
ついに Electron アプリでもタッチバーにアクセスできるようになりました. まだ入ったところなので多少バグなどがあるかもしれませんが,面白い機能なので是非自分がつくっているアプリでも活用を考えたいと思います. 何事も無ければ次リリースに含まれると思うので,今週後半か来週あたりには v1.6 系で使えるようになるのではと思います.
GitHub のハイライトがおかしい時のなおしかた(GitHub のハイライトの仕組み)
GitHub でコードを見ていると,ハイライトがおかしいのを見つけることがたまにあります.ここではその直し方を紹介します.
次の2パターンを想定しています.
- 自分のリポジトリのあるファイルのハイライト言語がおかしい
- 構文ハイライトが間違えている,言語の新機能に対応していない
自分のリポジトリのあるファイルのハイライト言語がおかしい
コードをどの言語でハイライトするかは自動判別されますが,実はある程度ユーザ側で制御する方法があります.
Vim や Emacs のモードラインを書く
ファイル単位で Vim や Emacs の設定をマジックコメントとして書けるモードラインですが,実は GitHub もそれを参照しています
// C++ だけど拡張子は .h … C じゃなくて C++ でハイライトしてほしい // vim: set ft=cpp // // または // // -*- mode: cpp;-*-
これで上記のコードは C++ として認識されるはずです.
.gitattributes
に指定する
リポジトリの .gitattributes
ファイルにハイライトを指定することもできます.
$ cat .gitattributes *.h linguist-language=C++
またハイライト以外の設定もできて,
$ cat .gitattributes vendor/* linguist-vendored docs/*.rb linguist-documentation=false
例えば上記の設定をすると,vendor/
配下は他のプロジェクトのコードと判断してリポジトリの stat (リポジトリページのヘッダにあるコード率など)から除外できたり,docs/*.rb
はドキュメント向けのコードとして,stat カウントから除外できたりします.
構文ハイライトが間違えている,言語の新機能に対応していない
実は GitHub のコードハイライトを行うライブラリは OSS で公開されています.
https://github.com/github/linguist
このリポジトリでは
など様々なメタデータが管理されています.なので例えば新しい言語をつくって十分に普及してきたら,ここに自分の言語を追加したプルリクを出すなどもできるわけですね.
今回は一番最後の言語のハイライト方法を見ます.
GitHub のハイライトは .tmbundle
という,TextMate の構文ハイライトフォーマットまたは,Atom の cson を使った構文ハイライトフォーマットを利用してハイライトされています.これらはオープンソースで開発されているものを利用しており,一覧は下記のディレクトリにあります.
https://github.com/github/linguist/tree/master/vendor/grammars
サブモジュールでリビジョン固定されていますが,何らかのタイミングで更新されているので,対象の .tmbundle
や .cson
を管理しているリポジトリにプルリクを投げて修正してもらえばいずれ linguist の依存する構文ハイライターもアップデートされ,GitHub 上のハイライトがなおるという仕組みです.
例えば僕は以前 Vim script の try ... finally
がハイライトされていないことに気づいたので,それを追加するプルリクを投げました.
また,上記リポジトリでは各種フレームワーク対応(例えば jquery.*.js
は jQuery のファイルなど)も行われているので,各フレームワーク向けのハイライトを改善したい時は YAML の設定ファイル にプルリクを出せば良さそうです.
GitHub でコードを読んでいるとハイライトが壊れていて困ることがあるかもしれませんが,実は自力で修正できますというご紹介でした.
公式ドキュメント
https://github.com/github/linguist/blob/master/docs/overrides.md