ここ2ヶ月ばかりのコーディング活動

近況報告ネタ(?)

毎日仕事もせずにのんべんだらりと暮らしています。ではなくて。前回の音楽技術アドベントカレンダーの後、特に表に出せるアウトプットがあったわけではないのですが、ここ1.5ヶ月くらいの間(履歴上は2ヶ月追ってるけど12月は旅行とACに吸い込まれがち)、自作のMIDI関連ツールを割とがしがし更新していました。おかげで(と自分で書くのも何ですが)MMLの打ち込みはかなり快適になりました。

これ全部まとめたら結構な新機能紹介リストになるな…?と思ったので、git上の履歴からリストアップしてみました:

  • managed-midi
    • GeneralMidiクラスに楽器カテゴリと楽器名のリストを追加
    • MidiModuleDatabase
      • ポート名からのマッチング処理を追加
      • ドラムマップのサポートを追加(データをdomino楽器定義リストから追加インポート)
    • Xamarin.MacなしでもCoreMIDIを使えるようなIMidiAccess実装を追加(ongoing)
      • 動作確認に使っているSC-8820がMojaveで使えないので先に進めない
    • MidiPlayerの大改修
      • ロードしたSMFをいったんformat 0に変換してから処理するようにした
      • 任意の位置にジャンプして再生する機能を追加(マーカージャンプ用)
      • チャンネルマスク機能の追加(ミュート再生、ソロ再生)
  • xmmk
    • プログラムチェンジが全く機能していなかったのを修正(そう、このレベルからの改善)
    • ピアノモードとクロマトーンモードを実行時に切り替えられるようにした(以前は#if ... #endifだった)
    • トランスポーズ機能を追加(カーソル左右でキーを変更)
    • keydown/keyupでちゃんと鳴らしている音が分かるようにボタンUIに反映(これまでしていなかった)
    • バイスセレクタをメニューからComboBoxへ
    • MMLパッドの追加。演奏した内容がMMLとして記録されるようになった。
      • 同時押しはc0e0gのように和音表記になり、アルペジオc&e&gのようになる(ざっくりmugene互換)
    • クロマトーンモードでオクターブがカーソル上下でいい感じに上げ下げできるよう修正(ピアノと同じ挙動だとイマイチだった)
    • PCキー配列でJP106を選択したら記号キーまで演奏できる範囲にした(開発当初はそうなっていたけどUSキーボードでgdgdだったので消していた)
      • Xwtでキーボード種別を検出できないので自動化はしていない…
    • プログラム・チェンジとバンク・セレクトのメニューで音色番号を表示するようにした(していなかった)
    • MiidiModuleDatabaseの楽器マップを自分で選択できるようにした(GS互換サウンドフォントでtimidityなどを使っていても自動判別できないため)
    • チャンネルを選択できるようにして、ch.10の場合はドラムの音色セットを表示するようにした(リズムパッド的に使えるようになった)
    • MML「入力」テキストボックスを追加。任意のmugene MMLを実行できるようになった。演奏を試しながらCCやSysexでサウンドを調整できる。
  • xmdsp
    • drag/dropでSMFをロードできるようにした
    • チャンネルパラメーターでピッチベンド値を表示するようにした
    • 演奏中のファイルの更新を検出したら自動的に最初から再生するオプションを追加した(デフォルト有効)
      • 打ち込み中に有効だと便利きわまりない機能
    • 倍速再生のスピードを2x, 4x, 8xから選択可能に(以前はボタン上でmousedown時のみ2倍→メニューで選択)
    • 最後に演奏したSMFの情報と最後に選択したMIDIバイスの情報を保存するようにした
    • MIDIバイスリストを毎回メニュー表示時に生成するようになったので、デバイスリストの変更があったときに再起動する必要がなくなった(もともとXwtの問題でそうできなかった)
    • 演奏時間表示まわりが不正確だったのをいろいろ直した
    • マーカージャンプ機能の追加。META情報を取得してその位置までジャンプできるようになった
      • managed-midiにジャンプ機能が追加されたのはこの目的
    • 複数SMF同時再生のためのコードを追加(まだメニューには登場していない. mugeneの機能追加待ち / 複数デバイスへのマッピング機能が必要)
    • チャンネルマスク(ミュート/ソロ)の実装(これもmanaged-midiの機能追加と一対)
    • Xwt.Gtk3のTextLayoutがメモリを永久に食い続ける問題を回避
    • GUICanvas)のOnDraw()処理の部分的な最適化
  • mugene
    • VSCode拡張にmugene:compileコマンドを追加。VSCodeからいつでもコンパイルできるようになった
      • 再生はxmdspの「ファイル変更検出時に自動的に再生」が有用
    • MMLチートシートのドキュメントを追加(VSCode拡張で表示しても良いのだけど未対応)
    • VSCode拡張でMMLコンパイルエラーをエラーパネルに表示できるようになった
    • 最初のコンパイルエラーでコンパイルを終了しないようにしたので、複数エラーが出るようになった
    • #conditional track ディレクティブで複数トラック範囲が指定可能に(1,3-5,11-13など)
    • METAイベントの引数が1文字だけだった場合にクラッシュしていたのを修正(常にテンポ変更命令として処理されていた)
    • トラック番号に小数値を指定可能になった(和音を1トラックにしていたのを分解したいけどトラック番号は連続的にしたい、みたいな時に有用)
    • LEGATO命令のCC番号がおかしかったのを修正(めったに使わない命令で気付かなかった)
  • notium
    • mugeneと同様の機能を、MMLを習得しなくてもAPIだけで使えるようにするために独立して作ってみたもの
    • まだ全然使っていない(公開する予定も無かったけど、xmmkにMML入力padを追加する時に、当初これを使おうとしていて、submoduleにするなら公開しなきゃ…となって公開になった)

これで全部のコーディング活動というわけではないのですが、いま成果として実現しているのはこれくらいでしょうか。個人的にはxmmkがこんなかたちで発展することになるとは思っていなかったので、割と面白いなーという気持ちで眺めています。どれも未完成感が強いのですが、打ち込み作業者としての自分に必要な機能を肉付けできたのは良かったかなと思っています。

どのモジュールも、構想としてはいろいろ「これやったら面白いんじゃないか」「これが必要になるんじゃないか」というアイディアレベルではToDoがたくさんあったのですが、実際にMML打ち込み作業に必要なものを実装していくと、全然違うことばかりやっていたという、自分で振り返ってみると面白いことになっていました。

今はMIDI音源(ハードウェアとかfluidsynthとか)とSMFがターゲットになっているのですが、21世紀にもなってそればかりというわけにもいかないので、もう少しモダンなターゲットも狙おうかなーと思っています。実のところ、果たしてそんなに遊んでいられるのかな?とも思わなくもなく、特にここ半年くらいは雑多な活動で時間を浪費してしまった気がしているところですが…これからはもう少し自分のやりたいことに集中しないとですね(そっちか)。

最近の音楽活動(?)

父さんな、音楽で食っていこうと思うんだ。

MMLコンパイラmugeneによる音楽制作ガイド」の無償公開

技術書典4で20部だけ販売したMMLコンパイラのガイドブックですが、そろそろ無償公開しても良いだろうと考えて、ソースをCC-BY-SA 4.0でgithub上にうpしました。

github.com

原稿を公開しているだけなので、読みたい方は各自ビルドしてください。Re:VIEWなのでソースで読めなくもないと思います。あとビルドしたPDFは引き続きboothで買えるようになっています(1000円だったのですが500円に値下げしました)。

xamaritans.booth.pm

もちろんCC-BY-SA 4.0なので独自にビルドして公開してもらってもかまいません。

音楽ツール・ライブラリ・技術 Advent Calendar 2018

最終日なのでお知らせではなく報告という感じですが、12/1から音楽ツール・ライブラリ・技術 Advent Calendar 2018というものを立てて書いていました。

adventar.org

この方面でここ1、2年くらい自習してきた話題をちょいちょいまとめた感じです。1人で書く覚悟で始めたのですが、幸い何日か他の方に埋めていただけました。(参加してくださった皆さんありがとうございます…!) 25日間続くように、内容があまり偏らないようにと思って「音楽ツール・ライブラリ・技術」という表題にして、話題もある程度分散させたつもりですが、どうでしょうね…ライブラリから技術仕様、オープンなサウンドライブラリなどまで含めたので…。あと当初全く意図していなかったのですが、最終的にこの話題のほとんどがOSSと呼べる範囲の内容になりました。

本当は、無難に完結させるために、毎日2,3段落くらいでまとめるつもりだったのですが、そこまで短いと逆にまとめるのが無理…という感じになって、結局毎回そこそこの分量の内容を用意することになってしまいました。前半はまだ書きやすい話題を選びながらのびのびと書けたのですが、旅行中に書いていたこともあるので、いろいろと調べられなかったこともあります(主に「ギガが足りない」的な意味で)。後半はネタ切れでだいぶ苦しくなって、実のところ未だに埋まっていない日があるのですが、今年中に終わらせられるといいな…という感じです(!)

来年もこの方面の技術をいろいろ追いかけていければと思います。本職にするかもしれないし。

Xamarin.Androidで躍動感のある生態系を構築するにはどうしたらいいのか

https://qiita.com/advent-calendar/2018/xamarin の24日目エントリーです。

近況

たぶんアドベントカレンダーに近況報告なんて書いているのはわたしだけではないかと思いますが()、Xamarinを卒業してから真性無職エンジョイ勢です。10月くらいまでは無職だけど技術書典5の裏方やったりXamarin本の最新刊を1人で書いたりしていて割と忙しかったんだぜ…あ、紙の書籍版はComic ZIN秋葉原店にあると思います(技術書典会場で在庫をそのまま預けられるのでとてもありがたい)。boothは倉庫代が値上がりしてしまったので(それまでが安すぎた)だいぶ前に泣く泣く引き払っていて、今は電子版しかありません。

11月の半ばからはしばらく日本を離れて欧州旅行…なのかな…していました。本当は北欧とかに足を伸ばしたかったのですが、寒そうだったので未踏だった仏・伊のあたりを無難にフラフラしていました。建前としては、11/20-21にロンドンで開催されていたAudio Developers Conference (ADC) 2018に参加するための訪欧でした。ADC自体はJUCEを開発しているROLI社のイベントというのが実態に近いのですが、GoogleAppleMicrosoftも来てオーディオ関連のセッションを行う程度にはポピュラーやイベントです。日本からもクリプトンがスポンサーをやっていたりしましたね。

しかし音楽ツールやフレームワークの開発だけ調べていても心もとないので、しばらくは音楽制作とかを勉強してみたいと思っています。ADCに来ていた人たちも大半が自分で何かしら創作していたようだったので、地に足のついたことをしたいなあというお気持ちです。

Xamarinを卒業してからもしばらくはmonoには協力するつもりでいるのですが、そんなわけで他にやることが割とあったのでコードをcontributeしたりは特にしていません。最近だとdotnet/wpfが出たので、いよいよ昔やっていたSystem.Xamlの実装を置き換えられそうかなあとか期待していますが、先にpull requestが作られていたのでこれも他の人に任せておこう…というお気持ちです。まあ先の同人誌で世界で他に誰も書いていないであろうMono Compiler APIまで含めたJITの解説とか書いて出しているし、十分お釣りが来るレベルでしょ…(何)

本題について

そういうわけで今日はXamarin.Androidチーム在籍時代に作って放置していたツールをいくつか紹介しつつ、Xamarin.Androidで「躍動感のある生態系」を構築するにはどうしたらいいか、という大味な話を書きます。仕事になるなら最後までもっていってもよかったんですが、そこまで優先度上げられませんでした。すまんやで、という気持ちも多少は無くはないのですが、まあ自分が悪いわけでもないしな…ということで現状有姿です。こういうのを恥も外聞もなく出せるタイプのキャラクターでよかった…(!?)

xamarin-android-apitools

最初の数段落はどうでもいい話なのですが、前フリとして書いておきます。

api-merge

Xamarin.Androidでは、Android APIバインディングの実体であるところのMono.Android.dllについて、複数のAndroid API Levelのandroid.jarのAPI情報を吸い出してから、無理やり結合して、古いAndroidバージョンのAPIに対応するMono.Android.dllとの互換性を維持するMono.Android.dllを生成しています。

Android APIの実体であるandroid.jarは、意図的な古いメンバーの削除などでは快適変更が加えられたりはしていますが、それを除けば基本的にはJavaレベルでのABI(APIのバイナリ互換性)が保たれています。

これを単純にMono.Android.dllとしてバインドすればABIが維持されるかというと、そういうことにはなりません。これはJavaと.NETの違いによるもの(たとえば派生クラスのメソッドの引数型がcovariant/contravariantになる)と、Xamarin.Androidバインディング生成ツール(generator)の仕様によるもの(たとえばsetterしかなくてSetXxx()メソッドだったものが、getterも加わってXxxプロパティになる)があります。いずれにしろ、これをそのままにしておくと、Xamarin.Androidでは新しいAPIバージョンを追加するたびにAPIに破壊的変更が生じるということになり、Xamarin.AndroidのプロジェクトではTarget API Levelを変更する度にアプリケーションがビルドしなくなったりすることになります。これはJava/Kotlinを使っていると生じない問題です。

そういうわけで、Mono.Android.dllをビルドするときは、前述の「APIを無理やり結合」するステップが組み込まれており、通常のバインディング ライブラリのビルドとは異なる複雑な手順になっています。Mono.Android.dllのビルドが単純なバインディング ライブラリ プロジェクトになっていない主な理由もこれです。この結合ステップはapi-mergeと呼ばれるツールで実現しています。api-mergeは、全てのMono.Android.dllのビルドの過程で呼び出されるもので、API Level 28にもなると、マージ処理にもそれなりの時間がかかります…が、それは今回の主題ではないのでおいといて。

このapi-mergeによってAPI互換性が保たれ、Target API Levelを変更してもコードはそのままビルドできる、というわけです。これはxamarin-androidのunit testingにも組み込まれており、xamarin-androidmake run-api-compatibility-tests を実行してチェックできます(新しいAPIがstable APIとして登録されている場合のみです)。

api-merge everywhere...?

このapi-mergeの仕組みですが、Mono.Android.dllの他にも、複数のバージョンのライブラリの間で互換性を維持したほうがいいんじゃないかなあと思われるようなライブラリはあったわけですね。たとえばsupport-v4。どんどんバージョンが上がるので、nugetでパッケージのバージョンを上げたらビルドできなくなる、ということがあっても不思議ではないです。

そういうわけで、これらのライブラリについてもapi-mergeを利用できるように、api-mergeをバインディング ライブラリ プロジェクトの一部として組み込もうという動きが一時期あったのですが、「そこまでやっても得られるメリット無くない?」という感じの流れになって、計画は中断となりました。本家がAPIの破壊的変更を絶対に行わないポリシーで更新しているわけでもないし、基本的にnugetパッケージのバージョンを上げなければ良いだけですし、そもそもXamarinのAndroid Componentsが本家の更新に全然オンタイムで追いつけていないので「1つ前のバージョンでDeprecatedになってた」みたいなAPIがあってもすっ飛ばしていたら意味ないですよね…

xamarin-android-apitools...?

いずれにせよ、こういう背景から、複数バージョンのライブラリ間でAPI互換性をチェックできる仕組みがほしい、ということでわたしが手にかけていたのがこのxamarin-android-apitoolsです(長い前フリだった…!) ただ、↑のような流れもあって、無理にAPIの比較をすることもないかな…それよりむしろJavaAPIにあってバインディング側に無いものを探したり出来たほうがよいかな…みたいな気持ちで途中まで作って放置してあります。

実際には、バインディングの生成過程のどこかでAPIが欠落している可能性もあるので、API定義のXMLもチェックできるようにしたいと思って、それらもロードできるようにしてあります。

そんなわけでこのxamarin-android-apitoolsでは、複数のAPIデータソースからAPI定義をロードしてツリー表示するだけのツールとなりました(!?)。ロードできるデータは次のとおりです。

実のところ、現状ではAPI生成のどの段階で型やメンバーが欠落したかを調べるためのツールとしてのみ便利です(API比較機能が完成していればさらに便利なのですが)。表示もXwtで作ったショボいやつなので、あんまり便利ではありません。

xamarin-android-binding-automator

Androidエコシステムはsupport/jetpackを含む膨大なサードパーティ ライブラリによって成り立っており、これをいかに適切なかたちでXamarin.Android用にバインドできるかというのは製品の大きな課題です。

わたしにはかつて密かな野望(?)がありました。binding generatorをゼロから作り直して、Mono.Android.dllをゼロから生成し直して、サポートライブラリ(現在ではjetpackというべきでしょう)のバインディングを全部刷新して、GoogleAndroidリリースに即日とは言わないまでもすぐに対応できるようにしたい、というものです。

これは技術的な制約よりは政治的な制約と技術的負債(MicrosoftVisual StudioチームがAndroidのリリースサイクルを根本的に考慮しない、古臭いAPI Level 10のバインディングをいつまでも維持しないといけない、APIに破壊的変更は1ミリも加えられない、バインディングのあるべき姿をまともに検討する前にAPIをフリーズした、等)があって今後も一生実現しないのではないかと思いますが、今後類似の開発フレームワークがXamarin.Androidの失敗を繰り返さないために、総括しつつ、何をすればあるべき姿に近づけられたのかを模索しておくことは意義があるでしょう。

何でgeneratorやMono.Android.dllを書き換えたいの?

generatorを書き換えたいという野望は、実のところわたしに限らず多くのメンバーが口にして、一部のメンバーは開発にまで着手して、結局誰も実現できていないものです。ユーザーとしてのわれわれのフラストレーションをいくつか列挙して、それぞれについて検討してみましょう。

(1) jar/aarを渡したらそれだけでバインドできる範囲だけでDLLを生成してほしい。バインドできないメンバーは削る

実のところこれが大方のデフォルトの挙動であり、何もmetadata fixupを記述せずにビルドするとエラーになるものの大半は「これを無視するようにしたら結局ビルドが通らなくなる」類のものです(たとえばnon-abstractクラスでabstractメソッドのオーバーライドを自動的にバインドできないので無視するわけにはいかない)。

実のところ、現状でも「インターフェースのメンバーに問題があるだけでインターフェース全体が生成されない」と「指定された型がメソッドの引数や戻り値に使われていて生成できない」の連鎖的な組み合わせで、膨大な型やメンバーがバインドされないことはよくあります。デフォルトでバインドされない結果生じる問題を解決するのも、かなりの困難を伴います。

(2) 名前の衝突などは衝突しないように生成してほしい

FOOフィールドとgetFoo()メソッドがあるときに、どちらもFooプロパティになるので、このままでは生成できないのですが、一方が消えます。わたしはこれは両方生成した上でmetadata fixupを追加するよう促したほうがいいんじゃないかと思いますが、今さらこのgeneratorの挙動は変えられないだろうなあ…この挙動に依存してビルドが通っているバインディング、多分それなりにあると思うので。

これをFoo, Foo1みたいに生成するようにすると、ビルドエラーはなくなりますが、それはバインディングのユーザーが本当に求めるべきものとはとても思えないので(ちなみにこれをやっちゃったのがxsd.exeというかSystem.Xml.SerializationSystem.Web.Services.Descriptionですね)、「ビルドエラーが出ないようにしてほしい」という漠然とした要求に対して、盲目的にこういうアプローチを採用しなかったのは正解だったと思います。

(3) 我々は愚直なバインディングを求めている。StreamやXmlReaderはいらない

これはXamarin社内に古くからあった問題ですが、「われわれはJavaよりもうまくやれる資産がある」という耳ざわりだけは良いスタンスが強く、generatorはjava.io.InputStreamなどをわざわざSystem.IO.Streamにマッピングするような設計になっています。もっと設計的にビミョいのはXmlReaderで(というか当時の設計方針に沿ってわたしが実現したのですが…)、SAX APIXmlPullParserからXmlReaderを返すようになっています。

この辺は実のところ「じゃあどこまでJava APIでやるとビミョくないの?」という反対側からの視点があり、たとえばjava.util.ListをそのままJava.Util.Listとして返すのは適切なのか、現状のようにSystem.Collections.IListにしたほうが適切ではないか、みたいな話はあります。System.Stringで表されているjava.lang.StringをJava.Lang.Stringのインスタンスで返すようにしたら、アプリケーション開発は多分かなり面倒なことになるでしょう(implicit conversionが可能かどうかによる、かもしれませんし、Xamarin.Androidアセンブリ固有の変換処理に依存するとなると共有ライブラリのビルドが面倒になったかもしれません)。

しかしStreamやXmlReaderは明らかに「やり過ぎ」であり、これらはそのままJava APIで返しておいて、必要な場合のみこれらを相互変換できるラッパーを被せられるようにすればよかったのです(実際、内部的にはうっすらとしたラッパーが使われています)。Streamは特にinputとoutputの境界が曖昧になって美しくありません。XmlReaderにはさらに問題があり、これさえなければMono.Android.dllがSystem.Xml.dllに依存することは無かったのです。余計なアセンブリが挟まって貴重なスペースを無駄にしています。

少し弁解というかフォローを入れるとしたら、Mono.Android.dllもgeneratorも元々は全Java APIをカバーするようなことは意図しておらず、特に.NETのSystem.* APIが代替として機能する範囲はむしろ「優れているほう(われわれのAPI)を使う」「いらないものはバインドしない」という設計思想になっていたのです。

しかし、この考え方はgeneratorやバインディングプロジェクトを一般化させる上で、ただの邪魔者にしかなりませんでした。「いらないもの」としてバインドされなかったJava APIに依存するAndroidライブラリの機能は、generatorにとっては「存在しないAPIを使おうとするライブラリ」でしかなく、単にバインドされないという結果に終わるのです。特Java.Util.CollectionなどコアなAPIバインディング不在は、さまざまなバインディングのビルドに大きな悪影響を及ぼしました。

後方互換性という技術的負債

generatorやその生成物であるところのMono.Android.dllにいろいろ問題があることは分かりました。問題が分かっているなら改善すれば良いのではないでしょうか?

問題はそんなに単純ではありません。Mono.Android.dllはXamarin.Androidフレームワーク アセンブリであり、このAPIに全てのXamarin.Androidの生態系とXamarin.Formsの生態系が影響を受けます。

Xamarin.Androidは十分にバインディング生成機構が成熟していない時点で、十分な検討が行われない中でMono.Android.dllのAPIが断行されてしまい、結果的に中途半端に問題をかかえたAPIが残りました。しかし固定されたことに変わりはなく、これを破壊することはできないのです。

これは従来の.NET Frameworkが死に体になった状態と似ています。.NET Frameworkと異なるのは、Xamarin.Androidには.NET Coreに相当する「新しい部分」が無いという点です。

しかし古いものはいつか使用に耐えなくなります。それであれば、現在の生態系を切り捨ててでも、新しいバインディングの生態系を構築すべきではないでしょうか。これを目指したのが(ようやく本題に入った)このxamarin-android-binding-automatorを中心とする構想でした。

AndroidSupportComponentsの問題

Xamarin.Androidの現在の生態系を支えている重要な要素のひとつがAndroidSupportComponentsです。現在のXamarin.Androidがかかえている「Android本家の開発スタイルにXamarin.Androidが全く追従できていない」という大きな問題の根源がここにあります。Xamarinは「正式版がまだ出ていないから」などと悠長なことを言わずに、もう少し真剣にJetpackサポートなどを「新パッケージ = 即日対応」くらいのレベルで追及すべきなのですが、互換性問題などから完全に後手後手に回っている状態であると評価せざるを得ないでしょう。

(サポートライブラリのバインディングは、かつてはXamarin.Androidチーム側で提供していたのですが、Xamarinコンポーネントストアの発足に伴ってコンポーネント開発部隊が新設され、やがてサポートライブラリもそこに移管されてしまったので、総合的なXamarin.Androidエコシステムの設計を行える組織体系ではなくなってしまったということは、一因としてあります。)

AndroidSupportComponentsは現状、MacWindowsでしかまともに動作しないCakeによってビルドされる仕組みになっていて、2016年頃にわたしが手を加えようとした時には既に手遅れでした(それでCakeにプルリクを送って修正しようとしていた時期もあるのですが、そもそもCakeをビルドするdotnetがまともにLinuxに対応しない問題があり、dotnetチームはまともにやる気が無く問題が修正されない…という状況で、わたしはほぼ匙を投げました)。

xamarin-android-binding-automator.exe

AndroidSupportComponentsはいずれにしろ後手後手なので、こうなったら自前でバインディングのビルド生成機構を作ってしまえ…というわけで作っていたのがこのリポジトリです。現状ひとつだけ存在するツールxamarin-android-binding-automator.exeは、指定されたMavenのパッケージIDから、依存関係を丸洗いして、それぞれのjar/aarをダウンロードして、それぞれについて依存関係(*.csproj中のProjectReference)を追加しつつバインディングを生成するようになっています。MavenではパッケージIDからPOMと呼ばれるパッケージ記述ファイルをリポジトリから取得できるので、それを活用しています(解析はやっつけですが)。

もともとバインディング プロジェクトの構成要素は大きくなく、jar/aarとmetadata fixup、追加C#コードがあればほぼ足りるのですが、現状metadata fixupや追加コードは考慮されていません。バインディングプロジェクトではさらにソースコードのjarからパラメーター名を、javadocのjarからドキュメンテーションを取得できるので、それらは追加されます。

究極的にはMono.Android.dll(android.jar)も生態系の一部となるようにカスタマイズできるようにしたいのですが、Mono.Android.dllのビルドにはapi-mergeなど特殊な課題もあり、まずはsupport libraryから…という感じです(でした)。Mono.Android.dllは、かつてはバインディング ランタイムとしての役割も持っていたのですが、現在ではこれはJava.Interop.dllに移管されており(後方互換のためにMono.Android.dllにもランタイム相当の部分が残されています)、Mono.Android.dllを切り捨てつつ、あるいは完全に残したままで、新しいバインディングアセンブリ(たとえばXamarin.Android.dll)を構築することは、不可能ではありません(libmonodroidの側に"Mono.Android.dll"をハードコーディングしている部分があれば、それは切替可能なかたちに書き換えないといけませんが)。

切替可能な生態系の構築

Androidサポートライブラリ(旧)のバインディングを中心とする、現状閉塞感の強いエコシステムを書き換えるには、より迅速にバインディングを提供する仕組みがあるべきです。バインドされるライブラリには、他のライブラリへの依存関係があり、これらはMavenのパッケージの依存関係として記述・処理されます。Xamarin.Androidバインディングには、現状、Mavenパッケージの相互依存関係を記述する要素はありません。

また、Mavenパッケージの依存関係は、こちらの期待を外れて非互換なかたちで差し替わる可能性があります。Javaにおける型解決には「どのjarに含まれていたか」のような情報は存在しないので彼らは自由に組み合わせを変えることがありますが(Java9のモジュールまわりでこの辺の事情は変わる可能性はありますが)、.NETにおける型情報にはアセンブリ名も含まれるので、それほど自由ではありません。非互換変更が生じたら「生態系ごと切り替える」つもりで臨むしか無いでしょう。

そのためには、ABI互換性維持といったぬるいことを言っていないで、新しい生態系で全てを瞬時に新しく構築できることが重要であろうと思います。そして生態系の切り替えは世界線の切り替えにも近い…というとピンとくる方もいるかと思いますが、そうです、gitのブランチ切り替えがこれに親和的なのではないかと思います。誰でも自由に生態系を改善しつつ、必要なものはソースからビルドできる。これが理想の生態系システムではないかと思います。

総括

…と、風呂敷だけは広げたのですが、この構想自体はXamarin.Androidチームにいるとほぼ実現不可能なんですよね。政治的な問題もあるし(チームの切り分けとか)。Microsoftがわたしの作業環境であるLinuxでもきちんと.NET/Xamarin開発環境をサポートしていくような会社であれば、これを継続していたかもしれませんが、現実は残念な感じだったので、この辺の構想をまとめたあたりで「まあもういいでしょ」という感じになりました。(まあそもそもを言えばバインディングまわりが自分の担当というわけでもなかったのですが。)

jetpackに関しては、バインディングAPIだけでも大変なのに、roomやdata-bindingなどGradleのビルドシステムを統合しないと実現できないようなコード自動生成にどう対応するかという問題もあり、この辺の課題に対してXamarin.Androidでは答えを出せていません。この辺も「今すぐにでも」取り組むべき課題なのですが、XamarinチームはXamarin.Formsのbindingとかあるからいいでしょ…くらいにしか思っていないようです。

もっとも、Xamarinのコンポーネントチームでも、最近ビルドシステムを刷新してMavenからの自動取り込みも行うようになった?ようなので、ここまで抜本的ではないにしても、何かしらの小刻みな改善は施していくかもしれません。jetpack対応は以下のツリーで行われています。Xamarin.AndroidやXamarin.Formsのエコシステムに期待する人は、彼らの動向に注目しておくとよいでしょう。

https://github.com/xamarin/AndroidSupportComponents/tree/AndroidX

.NETはどのくらいAPLに近づくことができるか

.NET, .NET Core, monoのランタイム・フレームワーク・ライブラリ Advent Calendar 2018の1日目は「.NETはどのくらいAPLに近づくことができるか」というお題でお送りします。

APLとは何か

APL(audio programming language)というのは、主として音楽を作成したり音響効果を実現したりする目的で音声を処理するためのもので、主に非プログラマー(というか、「ガチの」プログラマーではない人々)でも「簡単に」書けるようにするために開発されています。ここで言うAPLというのはひとつの言語ではなく、さまざまな言語の総称です(固有名詞であるA Programming LanguageことAPL言語は、オーディオとは一切関係ありません)。類似の概念としてVPL(visual programming language)があると言ってもよいでしょう。

APLのガラパゴス

APLの例としては、Csound、ChucK、Pure Data、Alda、Faust、Tidalといったものが挙げられます。面白いのは、これらの多くがそれぞれ独自の言語を定義しているということです。もっとも、これらの少なからぬ部分がSchemeなどのLisp系の言語の体系を利用していることを考えると、独自の文法であるとまで言えるかは何とも言えないところです。とはいえ、最終的に実行するプログラムが独自の生態系を構築しているということは言えるでしょう。

これらの言語の一般的な傾向として興味深いのは、これらはほぼ間違いなくJavaや.NETのような仮想マシン技術を用いて実装されないということです。音楽や音響効果を記述するのは、たとえばゲームの開発などにおいては有用そうですし、これらがJavaや.NETで開発されていないというのは意外なことではないでしょうか? なぜ音声処理を行うのにユーザーたるわれわれが独自言語を学習しなければならないのでしょうか? 生オーディオや生MIDIを扱うAPIが存在しているように、音声オブジェクトのライブラリとして提供してくれれば、後は自分の好きなようにどうとでも生成するのに…

しかしAPL開発者には彼らなりの理由づけがあってそうしているのです。面白いことに、それらの理由づけのいくつかは論文化されており、各APLの公式サイトや関連コミュニティなどで紹介されていたりもします。

今回は、その中からExtempore言語の作者Andrew Sorensenによる "The design, implementation and application of a cyber-physical programming language" という論文が、「なぜ.NETではダメなのか」というような疑問に対して、さまざまな実装の可能性を検討しつつ論じていて面白かったので、これを紹介しながら内容を吟味していきたいと思います。

https://openresearch-repository.anu.edu.au/handle/1885/144603

(Extemporeは自称「サイバーフィジカル言語」で、音楽だけを対象とするものではないそうで、この特徴は割と多くのAPLに共通するものなのですが、ここでは踏み込まないでおきます。Impromptuという言語処理系をもとに誕生したようです。あと、この論文自体は.NETについて議論しているものではありません。CLRへの言及などはまれに登場しますが。)

おまけ。OSCON 2014のキーノートで行われたExtemporeのライブコーディングも見られます。

https://www.youtube.com/watch?v=yY1FSsUV-8c

音声処理の用途

一般的なAPLが「仮想マシンではダメだ」としながら担っている仕事は、主に音声の「リアルタイム」に近い処理です。「リアルタイム」とはなんぞや?という疑問は今はおいておいて、音声のリアルタイム処理がどのような場面で必要になるか、いくつかの例を分かりやすい順に挙げていきましょう。

(1) バーチャルなピアノの鍵盤をソフトウェアで実現することを考えてみてください。ピアノのキーを押すとそれに反応してピアノの音が出ます。押したキーの位置に応じて音階が変わります。キーを押してから音が出るまでに時間がかかっていたら、ユーザーはこれを使いたいとは思わないでしょう。

(2) MP3プレイヤーは、ユーザーが指定したMP3ファイルをデコードしてサウンドバイスに出力します。PCMデータのデコードにかかる時間は、生PCMを再生する時間よりは短いですが、再生するタイミングが適切に管理されて守られていないと、音が飛び飛びになってしまって、ユーザーは聴くに耐えなくなります。

(3) DAW(デジタルオーディオワークステーション)は、複数のトラックで音楽を編集して制作するためのものです。場合によっては、PCMとして生成されるオーディオトラックの他に、MIDIバイスにメッセージを送信するMIDIトラックが存在するかもしれません。オーディオトラックとMIDIトラックの間で時間差が生じてしまうと、リスナーにとってはちぐはぐな音楽として聞こえることになります。

(4) デジタル楽器を使ったライブ演奏を行うことを考えてみてください(冒頭で紹介したOSCONのキーノートスピーチのように、最近ではAPLを使った Live Codingと呼ばれる実演もあります)。ライブ中にアプリケーションが固まったり処理が一時的に遅くなってしまったら致命的です。

もちろん、「リアルタイムでない」(リアルタイムであることが要求されない)音声関連の処理もいろいろあります。たとえば生PCMデータをMP3にエンコードする仕事は、何もリアルタイムで行う必要がありません。音声に対してエフェクターとして機能するソフトウェアもいくつかあります。これらは(i)再生しながらエフェクトをかける場合はリアルタイムで処理する必要がありますが、(ii)単に変換して結果を保存したり他のソフトウェアに渡すような場合はリアルタイムである必要はありません。

また、前記(1)〜(4)で求められている要件は、厳密には「リアルタイム」とは限らないものもあります(たとえば(2)や(3)はタイミングが合っていれば即応的である必要はありません)。しかし観念としては処理時間の正確さが重要であるものであり、ここで列挙しておくべきものでしょう。

リアルタイム性

いずれにせよ、音声処理ではリアルタイム性が求められる場面が多数あることがわかりました。しかし「リアルタイム」とはどのような意味なのでしょうか? ピアノの打鍵から1秒経ってから音が出たら、誰が聞いてもおかしいと思うでしょう。100ミリ秒だったら? 10ミリ秒だったら? 1ミリ秒だったら? …こうなってくるとわれわれは直感的にYES/NOで答えることが出来なくなってきます。

同じような問題がVRなどで用いられる3Dアニメーションのフレームレートについても語られます。VRも60FPSとか120FPSといった要求数値が出てきてなかなか厳しいのですが、音声も割とシビアなほうで、20ミリ秒くらいだとそれなりに気づかれてしまうようです。50FPSだと思うとなかなか厳しいことがわかります。

ここでひとつはっきりさせておくべきことがありますが、リアルタイム性とは「コンピューターの処理能力を可能な限り上げることで高度な処理も期待された時間内に終わらせる」仕組みではありません。あるタスクが一定間隔の「期待された時間」に必ず呼び出されて処理できることが、リアルタイムの要件です。

リアルタイム処理は、パフォーマンスの最大化すなわち「コンピューターの処理能力を最大限に活用して、もっともコンピューティングリソースを必要とする処理に最大限のリソースを差し向けよう」という思想とは、むしろ真っ向から対立するものであると言えます(リアルタイムに呼び出される処理はむしろ大して仕事しないかもしれず、それでも定期的に呼び出すことは重要なので、タスクマネージャーはその優先度を上げたままで維持します)。

現代のマルチタスク・コンピューティング環境において、「期待された時間」の枠は、OSのプロセスとスレッドの管理に依存するところが大きいです。前述のSorensenの論文は、そもそも「OSが自動的にタスク切り替えを処理するプリエンプティブ・マルチタスクにするか、アプリケーションが手動でタスク切り替えを管理する協調的マルチタスクにするか」というところから、リアルタイム処理の可能性を検討するのですが、協調的マルチタスクは、もはや一般的なデスクトップ環境があらかたプリエンプティブである現代において現実的ではないので、すぐに検討から外されています。

いずれにせよ、プリエンプティブなマルチタスク環境においては、リアルタイム処理を実現するためには、リアルタイムの精度で必ず呼び出しが発生するプロセス/スレッドが存在することが求められます。一般的なスレッドには、そのような保証はまったくありません。スレッドは大量に生成されて、また多くは既存のスレッドをスレッドプールから使い回されます。全てのスレッドが期待された時間に必ず呼び出されるような理想的な世界があれば問題にはなりませんが、現実はそのようにはなっていません。せいぜい、リアルタイム処理のために特権的に許されたスレッドだけが、優先度の高い割り込みを実現できるのです。それもRTLinuxのような特殊なOSカーネルによって実現してきたのです。

唐突に本題に戻りますが、.NETでこのようなリアルタイム用スレッドの作成が出来てコードが実行できるのであれば、.NETでAPLの要件を満たしたコードが書けるかもしれません。この論点は後でまた言及します。

(Sorensen論文は、さらに「時間をどう計測するか」というトピックについても一節を使って論じていて面白いのですが、さすがにそこまで紹介する意味はほぼ無いので、これだけの言及にとどめます。)

ガベージコレクション

仮にリアルタイム処理を行えるスレッドが利用できたとしても、それはあらゆるアプリケーションがリアルタイム処理に対応していると言える十分条件になるわけではありません。リアルタイム処理においては、1回の呼び出しサイクルが、1サイクル分の時間以内に処理を終えて呼び出し元(OS)に制御を戻すことが求められます。すなわち、この処理で予定外に長い時間がかかってはいけないのです。そして、これは処理時間の平均値の問題ではなく、WCETと呼ばれる「最悪の場合にかかる処理時間」 (worst case execution time)の問題なのです。

この観点で、.NETやJavaをはじめ、その他各種言語ランタイムで問題になるのがGCの処理です。GCの多くは確保されたオブジェクトの利用状態を安定的にトラッキングするために、よく"stop the world"と呼ばれる全アプリケーション・スレッド停止処理を施した上で、メモリをスキャンして、使用されなくなったオブジェクトをマークして、これをスイープします。

stop the worldはCLRのような世代別GCでは、一般的には第2世代以降のみに当てはまる話ですが、いずれにせよ、ここで重要なのはstop the worldが発生しうるということです。stop the worldは当然ながらリアルタイム処理を行っているアプリケーション・スレッドも止めなければなりません。…あれ? リアルタイム処理では、1回の呼び出しサイクルで行われる処理が1サイクル分の時間に必ず収まらないといけない、という条件はどうなるのでしょうか? はい皆さんご想像の通りです。これは満たせません。

実際には、ここには「力こそパワー!」が働く余地があって、極端な話、GCのstop the worldが十分に短いサイクルで実現できるのであれば、これは問題になりません。たとえばGoのGCはマイクロ秒単位で完了するらしいです。ビックリですね…! GC開発者が2016年に「10ミリ秒の停止なんてもう古い!」みたいなことを書いています。

https://groups.google.com/forum/#!msg/golang-dev/Ab1sFeoZg_8/_DaL0E8fAwAJ

あと全然関係ないところでHISEというプロジェクトがしれっと "Customized and real time safe Javascript Engine" などと書いていてたいへん気になるところです。ざっと見た感じでは、JS実装はどうやらインタープリタの独自実装で、一方でメモリ確保は自動で行わないように拡張したものであるようです。

http://hise.audio/

翻って、われらが.NETはどうでしょうか。2017年にMatt WarrenがまとめたGC停止時間に関する投稿がひとつの情報源として信頼できるでしょう。

http://mattwarren.org/2017/01/13/Analysing-Pause-times-in-the-.NET-GC/

Workstationで20ミリ秒強…これではオーディオのリアルタイム処理を任せられるとは言えなそうです。もっとも、この投稿ではGCLatency Modesに関する言及が何もなく、もしかしたら全く調整を試みずに測定しただけなのかもしれません。Low Latencyモードは.NET 3.5で追加されたもので、この記事がよくまとめています。

http://blogs.microsoft.co.il/sasha/2008/08/10/low-latency-gc-in-net-35/

もっともLowLatencyモードは「gen-2以降はGCしない」という剛毅な動作条件なので、長時間実行しっぱなしのライブパフォーマンスのような用途で使う余地はありません。

同じ2017年1月のこちらの記事では、もう少し悲観的な数値が出ています(最後のほう)。このベンチマークではbackground (concurrent) workstation GCとLowLatencyモードを明示的に対象としています。WCETが200ミリ秒を超えることがちょいちょいあるようです。これではやはり使い物にならないでしょう。

https://blogs.msdn.microsoft.com/seteplia/2017/01/05/understanding-different-gc-modes-with-concurrency-visualizer/

(少しだけGCの速度について補足すると、goはreified genericsをサポートする.NETに比べると、だいぶシンプルな要件に基づいて実装されているはずなので、実装の本気度合いの比較と考えてしまうのはフェアではないとわたしは考えています。ただし、言語の総合的な可能性を評価する要素にはもちろん含まれます。)

JITの予見できない動的コード生成

GCの話はここまでにしておいて、もうひとつ、この論文が取り上げるJITの話題に移りましょう。一般に、仮想マシンJITエンジンは、実行時にCPUネイティブの実行可能なコードを生成します。.NETプログラムを初めて起動したときにつっかかるような遅さを体感できると思いますが、その正体はJITコンパイルです。より正確を期するなら、JITコンパイル処理そのものよりは、JITコンパイラをプログラムとしてロードして実行するための遅さ、であると考えたほうがよいでしょう(コンパイル処理自体にあれほど長大な時間がかかるとまでは言えないので)。しかしそこまで極端に長い時間はかからないとしても、JITコンパイル処理自体には無視できないコストがかかります。

一般的には、ある仮想マシンコードのメソッドを実行するためには、まずJITコンパイルしてから実行することになります。ここで先般から話題にしているリアルタイム処理の要求事項の話を思い出してください。リアルタイム処理の呼び出しが行われたときに、初めて実行するコードがあったとします。このコードはまずJITコンパイルされなければなりません。このコンパイル処理はリアルタイム処理に期待される応答時間内に完了するでしょうか? …ちょっとこれは期待できそうにないですね。これがSorensenが提起するもうひとつの問題です。

ちなみに、これは.NETやJavaの問題というよりは、一般的な言語ランタイムに共通する問題です。Sorensenが論文で言及しているのはSonic PiのRuby、GibberのJavaScript、ImpromptuのScheme、OvertoneのClojureなどです。

Extemporeは、ではどのように設計され実装されているかというと、この言語はSchemeインタープリターとXTLangという独自の実行系のハイブリッド構成になっていて、XTLangではGCに依存しない、リアルタイム処理で期待されるコードを書く、それ以外はSchemeで書く、ということになります。XTLangで書かれたコードは、いったんコンパイルしてLLVM IRに変換し、それをネイティブコードに変換して実行することになります。

Extemporeは、ライブパフォーマンスなどの場面で、REPLのようにコードの断片を実行できることを、主目的としており(CやC++では実現できない課題としてこれを挙げています)、実際それがExtemporeコードの一般的な実行方法になります。ただ、XTLangのコンパイルには時間がかかるので、事前にコンパイルコマンドを実行しておくことで、実行時遅延の問題を回避します。

この論点ですが、2018年までにAOTやインタープリターのような、さまざまな.NET実行環境を見てきたわれわれとしては、「仮想マシンであればJITコンパイル」というのは、やや固定観念的であると評価せざるを得ないでしょう。古くからAOTをサポートしていたmonoランタイムだけでなく、.NET CoreでもAOT実行が可能になりつつあり(可能である、と言ったほうが良いでしょうか?)、この点での懸念は、少なくとも理論の上では無くなりつつあると考えられます。

もっとも、インタープリターで実行するのであれば、ネイティブコードのパフォーマンスは一切期待できず、何のために仮想マシン言語を使うのか、という話になるでしょう。その点AOTはまさにXTLangと同じことをやっているのであり、基本的にはリアルタイム処理を期待する場面ではAOTを適用するようにすれば良いと言えそうです。もっとも、Xamarin.iOSでも問題になるように、AOTでも不完全AOTになってしまうものは、JIT処理が発生する余地があるということで、リアルタイム処理に関する懸念をクリアできません。

いずれにせよ、ExtemporeはAOTに相当する静的コード生成をREPLのレベルで実現しているので、.NETでこれに相当するものを実現するためには、現存するC#のREPL環境では足りず、部分的に独自にAOTコンパイルを行えるようなREPL環境が必要になるでしょう(先ほど「少なくとも理論の上では」と留保した理由です)。

言語ランタイムとサウンドサーバーのプロセス内通信

Sorensen論文が本当に面白いのは、この論文はExtempore以外のさまざまな他の実装のアプローチについても、彼なりの視点で検討を加えているところにあります。そのひとつは、言語ランタイム側はリアルタイム処理を行う音声処理部分と、FFI(foreign function invocation/interface)などによってintra-process communication(interではなくintraであることに注意)を行うことで、他言語実装によるメリットを得る、というアプローチです。ChucK、Impromptu、Fluxusはこのアプローチで実現しているようです。

このアプローチの問題は、アプリケーションがクラッシュした場合に問題を解決するのがとても困難である、とまとめられています。これは確かに事実ではあるのですが、「難しいかどうか」はどうしても主観的な判断になってしまうので、その妥当性も評価が難しいところです(というかこの論文ここはスルーしてもらえたのか…)。

プロセス間通信による実装アプローチ

intra-process communicationが困難ならinter-process communicationすなわちIPCではどうでしょうか。これもSorensenは検討しており、実際にこのアプローチで実現している例としてSuperColliderを挙げています。プロセスが分離していると、クラッシュするのはクライアントかサーバーのいずれかになるので、問題の切り分けが容易になりますし、ABIではなくプロトコルによってやり取りが決まるようになります。特にSC3は仕様の安定化を図ったことで、SC1やSC2のようなクライアント・サーバー間の厳密な同一バージョン依存が無くなった、と説明されています。

しかし一方で、安定した仕様になってしまった結果プロトコルへのメッセージの追加が容易に行えなくなったことが問題である、という議論を加えてもいます。仕様を簡単に変更できることがintra-process communicationの大きなアドバンテージである、とまで書かれています。

Extemporeもクライアント・サーバー方式であり、プロセスはクライアントとサーバーで分離しているのですが、tightly-coupledとloosely-coupledの2つのアプローチの間ではフラフラしていたようです。基本的には不可分のものとしているつもりであるようで、その機能の一部をCのライブラリとして分離したりしなかったりといったことを、後方互換性を気にせずに行ってきた、というような話も書かれています。

このあたりの事情をどう評価するかも悩ましいところですが、わたしの個人的な意見としては、実装のアプローチがクライアント・サーバー分離モデルであることと、仕様の安定化の有無は個別に判断すればよいことであって、仕様は正直「ここは安定」「ここは未定」みたいな宣言で押し切るしか無いように思います。安定仕様がダメだったら、非互換の別の仕様として立て直すしか無いでしょう(今「たぶんSwiftはこうやって現状のようになっていったんだろうなー」という考えが浮かびましたが多分気のせいでしょう)。

.NETの文脈でこれを考えるなら、intra-process communicationの方法はあまり考えつかないのですが、CLR hostingやmono embedded APIによるホスティングでしょうか…あまりメリットが無いような気がしますが可能と言えば可能でしょう。(前述のstop the worldと向き合わなければならない事に変わりはないのですが、ここでは個別に検討します。)

inter-process communicationのアプローチは、特段論じるべきことはあまり無く、強いて言えばIPCに必要な共有メモリのサポートなどは.NET Coreでもだいぶ最近になってクロスプラットフォームで実装されてきた機能であることに注意したほうがよい、という程度でしょうか。TCPなどの通信スタックを使ってしまうと、リアルタイム処理を期待しているのに…となってしまいそうです。

ただ、そもそも根本的な問題として、クライアントとサーバーを分けて、ではサーバーは.NETやJavaで実装・拡張したくないのか、と考えると、やはりやりたくなるんじゃないかなあという気はします(個人の主観の問題でしょうか…?)。そうなると結局stop the worldなどの問題に正面から向き合うことになるでしょう。

.NETのGCに挽回の余地はあるか?

さて、ここまで長々とSorensen論文を中心に、APLを.NETで実現する場合の課題にはどんなものがありうるか、検討してきました。やはり一番問題になるのはGCのstop the worldではないでしょうか。

実のところ、この点では.NETよりはJavaのほうが可能性が広がっています。Java界隈には、リアルタイム処理を実現するための手段としてRTSJ(Real-Time Specification for Java)という仕様があり、JSR 282として標準化されています。RTSJの主な特徴をざっくり列挙するとこんな感じです:

  • RTSJでは、リアルタイム処理用のスレッドをユーザーが作成できる
  • そのスレッドではメモリの確保手段が限定されている
  • そのメモリ領域はGCの対象とならない

これらの取り決めをしておくことで、リアルタイム処理を妨げられないスレッドが実現できるというわけです。

Javaにはこのような機構があるのに.NETには無いのでしょうか? .NETにはCER (constrained execution region) という機構があって、「GCをLowLatencyモードに切り替えるときはこれを使え」と言われるものですが、これは別にメモリ管理を別途行うようにするというほどのものではないので、RTSJのようなことは出来ません。このあたりの柔軟性の無さは、やはり.NETがずっとクローズドソースで閉鎖的に開発されてきたからでしょう。CLR/CoreCLRのGCもあまり柔軟ではなく、このようなメモリ管理機構に耐えうる設計になっているのかはわかりません。

一方、.NETや.NET Coreとは異なり、MonoはGCの実装を差し替える機構がそれなりに整備されているので、RTSJ的なGCとハイブリッドに協調するGCを構築できる可能性があるかもしれません。こちらはずっとOSSだったにも関わらず、RTSJのような試みはなかったと思います。何処かではあったかもしれません(わたしは聞いたことがありません)。

実のところ、RTSJも高価な商用製品による実装がほとんどであり、オープンソースの実装というものは無いように思います。そういう意味では、気軽な音楽アプリとは住んでいる世界が違うかもしれません。

この方面で一番現実化しそうなのはUnityとくにECSかもしれません(リアルタイムオーディオのリクエストは何年も前から出ているようです)。ECSで要求されるC#コードの制約がどれくらい問題になるかはUnityを使っていない勢のわたしにはわかりませんが、機構としては一番可能性がある領域かなと思っています。

まあこれはあくまでGCに関連する部分だけの話です。REPL環境が作れないんじゃないかという気もしますし、Unityのエコシステムは.NETのそれとは根本的に異なるガラパゴスであり、これとデスクトップ環境の標準的なコントロールに基づくUIを繋ぐのはだいぶ無理がありそうなので、頑張って機構を作っても、利用する側の開発体験が悪くなるだけになりそうですし、こういうのはMono Frameworkが一番向いていますね。

考察

さて、ここまでSorensenの論文を主な肴としつつ長々と書いてきましたが、最後にこれまでの話をもとに、どんなアプローチであればAPL開発者が納得できるような仕組みが構築できるのか、考察していきます。現時点では、次の2通りのアプローチがあると思います。

  • Low Latencyモードでメモリ不足で落ちないところまで頑張ってみる
    • and/or Low Latencyモードで解放されずに増え続けるメモリ使用は「リーク」とみなしてつぶす
  • クライアントとサーバーをIPCで繋ぐ。やりとりは共有メモリ。サーバも.NETで作るなら、Low Latency GCで「リーク」しないように作り込む

後者のほうが現実的かな?という気がしています。ただ、いずれも定性的に「リーク」を検出できる機構になっておらず、これでは不安である、と言われてしまう可能性はあります(まあそれを言い出したらGCの無い言語の上にコードを構築すること自体が不安ということになりそうですが)。

わたしがAPLを使ったり作ったりしたくなるような時が来たら、このIPCもどき方式で試してみようかなと思っています(個人的にはライブパフォーマンスに走る予定は無く、停止時間はほぼ重要ではないので、作るとしてもあくまで仮想的なクライアント/サーバーを分けておく程度で、アプリケーションは単一のままになりそうですが)。

なお、これは全て「アプリケーションがリアルタイム処理用のスレッドを作成できる」OSであることを要求事項としています。Androidなど、これが実現できない環境については、また別個の考察を必要とするものです。(本当はこの辺の話をDroidKaigi 2019でしようと思って準備していたのですが、セッション不採択になってしまったので他の機会に…)

"Android ネイティブライブラリビ ルド Tips" on "Trick or Treat" (TechBooster新刊)

10/8の技術書典5が近づいてきて、例によって台風が押し寄せてきて、スタッフ業がてんやわんやしていますが、進捗どうでしょうか? 最悪でも致命傷で何とかすませたいところですネ。

さて先日のエントリではわれわれのサークルの新刊のお知らせを出しましたが、今日はTechBoosterの新刊 Trick or Treat に記事を出したのでお知らせです。

techbookfest.org

今回はAndroidネイティブライブラリビルドTipsというタイトルで、特にAndroid向けに書かれているわけでもないLinux用ネイティブライブラリを、Android NDKを使って、ちゃんとAndroid Studioからデバッグできるようにビルドする方法をまとめました。内容はほぼそれだけですが、全部で12ページくらいあります。

NDKでndk-build以外のやり方で書かれたライブラリをビルドするには、NDKのツールチェインをクロスコンパイラの流儀に沿って使わなければならないのですが、Android NDKで公式にサポートしているcmakeに基づくビルドであっても手こずったりしますし、autotoolsなどが使われているとさらに面倒です。部分的にクロスコンパイルできていなくてハマったり、x86ビルドがなぜかAndroidに無いglibc依存になっていたり、あるいは共有ライブラリで依存関係があったりとかすると沼です(Linux環境では共有ライブラリは大概Androidでサポートされないシンボリックリンクになりますし)。

さらに、ビルドだけ何とか出来ても、デバッグシンボルをちゃんと埋め込むようにビルドする、さらにはビルド中に依存ライブラリがある場合はそれらもデバッグシンボルを埋め込んでビルドする…といったふうに、さまざまなポイントでデバッグシンボルが抜け落ちることがあります。

というわけで、さまざまな落とし穴に言及しつつ、Android Studioデバッグできるような*.soをビルドできるように、12ページでまとめています。あとついでにJavaやXamarin、Kotlin/Nativeなどから呼び出す時に困りそうなAPI設計のパターンについてもちょろっと言及しました。

本当はこの辺をメインに書いてうちの新刊のネタにするつもりだったわけですが、Xamarinっぽくなかったのでボツとして、ちょうどAndroidネタを募集していたTechBoosterの方で再利用したというわけです。

そんなわけで、Android NDKネタですが、割と珍しい切り口になっているのではないかと思います。興味がわいてきたという皆さんはぜひ10/8の技術書典5に買いに来てください。TechBoosterは「あ11」です

Awesome Xamarin (技術書典5新刊情報)

無職になっても生活が在職時とほとんど変わらないatsushienoです。それって単に給料が入ってこなくなっただけじゃないか…?自由な生活たのしい!!!1

戯言はさておいて、9月は多くの時間を10/8の技術書典5に向けて費やしていました。そのうちのひとつは今日お知らせするXamaritans名義の新刊です。

Xamaritansの配置は「う-78」です。詳細は技術書典Webサイト上のサークルページをチェックしてください。

タイトルは「Awesome Xamarin: この素晴らしいXamarinの世界」です。

f:id:atsushieno:20180925205124p:plain

(今回はまたイラストをお馴染みの(?)絵師に描いてもらいました。今まではアナログ絵の取り込みだったのですが今回はペンタブで描いたらしい…)

内容ですが、今回は完全に1人で書くことにしました…というと何やらそのような意図でやったように聞こえるのですが、単純にまともに原稿募集を出していなくて(もうしわけない…)、結果的に1人で全部書くことになったのでした。本当は30ページくらいのコピー本にしようと思っていたのですが、最初に出来たクロスプラットホームGUIの話がデスクトップ中心でイマイチだったので、もう1本書いてみたらあまりにもXamarinと関係ないNDKの話になってしまって、ビミョイかな…とますます困ったのでした。そうこうしているうちに、mono project blogからクッソおもしろいネタが投下されたんですね。

これはmonoランタイムのJIT相当部分をC#で実装できる、という話なんですが、これはちょうどいいと思って、ついうっかりこの話を理解するために必要そうなJITというかコード生成エンジンの話を書き始めてしまったんですね。

割といい感じにまとまったと思ったんですが、ここで出来上がった原稿は(1)x-plat GUI(しかも割とデスクトップ中心) (2)NDK (3)JITXamarin要素ほぼゼロじゃんAwesome Xamarinとは何だったのか…

…というわけで、急遽(4)「Xamarin.Androidバインディングを極める」を書いて何とかそれっぽく誤魔化し(!?)、NDKの章は削って他で再利用することにしました(こっちは告知できるようになった段階で出します)。

そういうわけで、今回のネタは次の3本です。

(1) Xamarin.Android バインディングを極める

自分が一時期主に開発していたAndroidバインディング・プロジェクトについて、実用的なプロジェクト構成から細かい説明、各種ビルドエラーの対策(この辺は去年シンガポールでやったセッションの焼き直し)などを30ページくらい書いてまとめました。本当は個人的に構想していた未来形の話も書きたかったのですが、今後それを推し進めることになる気があまりしないので、ほぼ現状の話でまとめてあります。

(2) Mono.Compiler API を理解するためのMono JITエンジン解説

去年のde:code2017でもある程度コード生成エンジンの話をしましたが、アレはJITの話としてはほんの序の口です。JITが何をしているのか、miniとは何なのか、llvm実装とそうでない実装はどう繋ぎこまれているのか、CILはどのように実際のマシンコードに置き換えられているのか(Linear IR)、コードはどう最適化されているのかといった話を「もう少しだけ」掘り下げます。SSAレジスタアロケーターのspillingに言及はするが深入りはしない、という程度です。AOTも関係あるんだけど、最終的には「JIT」って章タイトルに入っているほうが分かりやすいだろうと思って「JIT」としました。自分で言うのも何ですがこの章が一番おもしろいです。

(3) Mono ・ XamarinクロスプラットフォームGUIの軌跡

MonoチームがXamarin.FormsのようなクロスプラットフォームGUIフレームワークを実装したのは、これが初めてではなく、大昔にはWindows.FormsをLinux/Mac向けに実装したり、その後今でも現役で使われているXwtを開発したりしてきました。またGtk+も今や…というか10年以上前から…クロスプラットフォームGUIツールキットであり、実装上の知見を得てきたフレームワークです。これらをきちんと振り返った上で、今Flutterのような新しいGUIフレームワークをどのように捉えるのか、Xamarin.Formsには何があって何がないのか正しく語るために知っておくべきことは何か、といったことを考察するための起爆剤として書いた章です。一部はde:code2018でXamarin.Formsについて語った内容も含まれています。

最終的におよそ90ページという、予定の3倍くらい厚い薄い本になってしまったのですが(完全に予言されたとおりになってしまった…)、いつも通り1000円で頒布しようと思います。技術書典5、スタッフとしても作業していますんで(こっちも大変…)、ぜひ皆さん来てください。Xamaritansは「う-78」です(サークルページ)。

"language server protocol explained" at COSCUP2018

台湾でおそらく一番でかいオープンソースのカンファレンスCOSCUP 2018でlanguage server protocol (LSP) explainedというセッションをしてきたので、その辺に関係する話を軽く書きます*1。当日は英語のセッションなのに30-40人くらいの人に来てもらえたので、それなりに関心が集まるトピックだったのかなと思います。

speakerdeck.com

もはやスライドのテンプレートすら選ぶ気のない感じのGoogle Slidesですが。

LSPがざっくり何のためにあるのか、language serverが何をするものなのか、何でprotocolなのか、どんな命令があるのか…といった部分は以前にqiitaにまとめた通りですが、今回は応募したセクションの趣旨が "read the source code" だったこともあって、少しvscodeで実装するやり方を説明しました(もちろんvscodeに固有のものではないことはたびたび言及しました)。vscode自体、まだLSPはJSONCSSみたいなおもちゃレベルのものしか提供していないので*2、引用するのもvscode-languageserver-cssが中心だったのですが、まあ逆に現状使えるのはこのレベルの言語かなという雰囲気が伝わったほうがいいかなと思って(自分の中では)納得しました。

わたし自身も、C#で自作MMLコンパイラのLSを(途中まで)作ったので、一応それが動いているのを見せつつという感じでした。C#のサーバが動いているのはどうでもよくて、vscodeデバッグコンソールのようなところにJSON-RPCの生メッセージが出てくるのを見せた感じです。これが出てくると割と面白いです。

ただ、LSPの仕様自体がまだ割といいかげんで、特にlocationやrangeみたいな情報の要求が、どこまで求めるべきものなのかを記していなかったりするので(たとえばトークンのrangeのendなんて割とどうでもいいと思うのですが、無いとvscodeがまともに処理してくれなかったりします)、まだまだ真面目に相互運用性を期待して手を出せるものじゃないなあという印象です。

あとC#のLSPライブラリに関して言えば、MSが出しているライブラリは依存関係にMicrosoft.VisualStudio で始まるプロプラエタリなパッケージがあってNG、omnisharp-vscodeはまだ激しく発展途上で実際ビルドしてテストを通そうとしたら通らないレベルだったのでNG…という感じで、いのまたさん(id:matarillo)の実装を使わせてもらったりしています。将来的にはもしかしたらomnisharpの実装の完成度が上がるのかもしれませんが、現状ではいのまたさんの実装がベストです。vscode-languageserver-nodeなどはもう少し実装ヘルパー的なものも持っているので、この辺はわたしがC#版のコンパイラまわりを本気でやることがあれば貢献できるといいなあという感じです。*3

というわけで、ひと段落ついたので、いろいろ残件を片付けつつ遊びに入りたいと思います(!)

*1:本当は「後編」を書こうと思っていたのだけどなかなかまとまらなかったというのは内緒です

*2:CSSパーサーをおもちゃレベルというのはちょっとひどい気もしますが、LSPにユーザーが期待するのはこのレベルではないはずですよね

*3:いまこのコンパイラvscode拡張は優先度が高くないので後回し状態ですが…