WebAssemblyのAOTコンパイラを作った
本記事はWebAssembly Advent Calendar 2018の26日目の記事である。
Advent Calendarに間に合わなかったけど、何も書かないのはもったいないので書くことにする。
概要
WebAssembly AssemblyをAOTでコンパイルするコンパイラを作った。
これによりブラウザを使わなくてもWebAssemblyを実行できる。(もちろんNode.js等も不要)
作った目的
WebAssemblyには期待しているが、ネットで見かける話題ではブラウザ上でのマイニングとか、ブラウザゲームが軽くなると言った話が多かったのでそれ以外の道を示したかった。あと前々から低レイヤには興味があったが手を出してなかったのでやってみたかったというのもある。
その名前のせいかWebAssemblyはブラウザでしか動かないと思っている人が多く悲しい。確かに実用的なアプリケーションを作ろうとすると現状ブラウザに限定されてしまうのは事実だ。
しかしWebAssemblyの規格にはブラウザで実行するように制限するようなことは自分が読んだ限りでは書いてなかったし、公式のドキュメントにはIoTやデスクトップアプリといった環境でも実行できるように設計していると書いてある。
実際すでにWebAssemblyをring0で実行できるOSや洗濯機でWebAssemblyを実行できるようにするプロジェクトが存在している。WebAssemblyの可能性はまだまだ広がっていくだろう。
作り方
まず規格を読みます。
次に規格で求められている通りにランタイムシステムとWebAssemblyの命令をLLVMで実装します。
最後にコンパイルする対象のWebAssemblyがimportしている関数で足りないものがあれば、ランタイム側で提供してあげます。
簡単ですね。
嘘です。クソ難しかったです。8ヶ月ぐらいかかった。
LLVMのAPIドキュメントに関数の説明が何も説明が書いていないのが一番つらかった。普段言語作ってる人たちはどうしてるんだろう。
WebAssemblyの規格でもExecutionの項がAOT実装者向けに書いてあるわけではなくて脳内解釈してやる必要があった。
これは実用的なの?
現状muslと一緒に吐かれたwasmファイルでHello worldするのが精一杯なので全く実用的じゃない。
rustだとターゲットをwasmにするとprintln!で実際に出力されるコードが吐かれないようなのでHello worldできなかった。
最も頑張ってimportされてる関数を実装していけばある程度のことはできるようになるとは思うが、当然importして依存している各wasmファイルで全然違うので、現実的とは言えない。
この問題を解決するにはWebAssembly用のライブラリのようなものを規格化する必要があると思う。
できればブラウザに依存しない部分のライブラリと各ブラウザで共通のライブラリは分けて規格化されてほしいが、そもそもライブラリの規格化は話に出ているのだろうか?
まとめ
実際にWebAssembly処理系を実装してみたが更に関わっていきたいという気持ちが強くなった。
将来的にはWebAssemblyの規格の仕様策定に参加していきたいが、そのためには覚えなければ行けないことがたくさんありそう。
Javaのエラー設計は何が悪かったのか
先日TwitterでJavaの検査例外ってそこまで悪くないのになといった旨の発言をしたら結構反応あったのでまとめることにする。
今回はJavaの言語機能のエラー設計の何が悪かったか書く。
ちなみに私のJava知識は十年以上前に学校で習ったきりなので何も知らないに等しい。検査例外という名称もつい先日知ったばかり。そのため妄想で書いてしまうことがあるので、もし間違っていたらプロのマサカリを希望する。
なおTwitter上では多くの方が私の疑問に答えてくださり助かりました。この場をもってお礼申し上げます。
throws指定しなくても投げることのできる例外が存在する
いわゆる非検査例外と呼ばれているものであり、catchやthrowsで指定しなくてもコンパイルが通ってしまう例外。大昔にこれがあるからJavaの検査例外は意味がない的な批判を見た気がする。
確かにせっかく例外をcatchしてなかったらコンパイルエラーにする機能があるのに、catchしなくてもコンパイルエラーにならない例外が飛んでくるかも知れないと思うと安心できない。
とはいえ、ユーザーにcatchさせたくないエラーというのは少なからず存在するので、そういったものは例外という同じくくりにせずに、GoやRustのようにpanicとし、構文からエラーの扱いを別にするべきだったのではなかろうか。
高階関数と相性が悪い
高階関数を使うようにする機能として、FunctionやBiFunctionといったインターフェースが提供されているが、これらはapplyする時に例外が投げられるため、例外を投げることができない。
対応としてcatchしてもみ消したり、RuntimeExceptionで包んで投げたりしてる記事をいくつかみたが非常にめんどくさい。
これはJavaのthrowsが複数の型を指定できること、及びJavaのメソッドがthrowsでオーバーロードできないことに起因していると思われる。
仮の話ではあるが、高階関数でもthrowsできるように型システムやJava中間言語の設計をきちんと行っていれば発生しなかった問題のように感じる。
非同期との相性が悪い
らしい。今のJavaの非同期処理がどうなっているかよくわからないので正直詳しいことはあまり書けない。
C#だとasync/awaitで非同期処理で発生した例外をcatchできるので、検査例外の機能を維持しつつ非同期処理を書くといったことはできそうではあるが、今のJavaでそれをやるにはやはり型システムを大きく変えなければ実現できないように思う。
あと今後発生するかも知れない非同期処理のパラダイム変化に対応することを考えると、エラーは戻り値ベースにしてシンプルに実装しておいたほうが後々面倒にはならなさそうに感じる。
まとめ
以上がTwitterで教えてもらったりして、私がJavaのエラー設計の悪いと思うところを挙げたものである。
個人的には検査例外自体は悪い発想ではなかったが、Javaの型システムやその他の設計が時代についていけていないのが原因でイマイチな感じになっている印象。
そのため検査例外自体も低い評価になってるように見えるのでもったいないように感じる。
聞くところによるとSwiftが検査例外を採用しているらしい。時間があれば上手く落とし込めているか確かめてみたい。
circeでJsonのすべてのvalueにアクセスする
scalaでautodoc的なものが作りたくなって、Jsonの値を列挙する必要があったので調べた。
circeのドキュメントには使い方的な記載はなく、APIドキュメントにポツンとあっただけなので書いておく
foldを使うとよさそう。 引数が多いので整理したい場合は自分でJson.Folder[X]を実装してfoldWithを使うとよい。
val jsonOption = parse( """ | { | "id": "c730433b-082c-4984-9d66-855c243266f0", | "name": "Foo", | "counts": [1, 2, 3], | "values": { | "bar": true, | "baz": 100.001, | "qux": ["a", "b"] | } | } """.stripMargin) val json:Json = jsonOption.getOrElse(Json.Null) printJson(json) def printJson(json: Json): Unit ={ json.fold( throw new RuntimeException("null"), // nullの場合 printBoolean(_), // Booleanの場合 printNumber(_), // Numberの場合 printString(_), // Stringの場合 printArray(_), // Arrayの場合 printObject(_)) // Objectの場合 }
JsonObjectにたいしてはkeyの名前も欲しい場合、toMapしてMapに変換するとよさそう
def printObject(jsonObject: JsonObject): Unit ={ jsonObject.toMap.foreach{ case (key,value) => print(key ) printJson(value) } }
しかし最近Swaggerなるドキュメントを生成できるフレームワークを見つけてしまい、今やってることが無駄に思えてきた
Xamarin.Formsでツイクラ作ってたけどモチベが下がってしまった
なんとも後ろ向きなタイトルですが、最近モチベが下がってしまってツイクラ開発が止まってしまています。
理由としてはこんな感じ
・公式のツイッタークライアントの出来が思っていた以上によかった。
・DM、投票機能など、公式クライアントにしか実装できない機能がある。
1番目の理由が結構大きくて、Xamarin.Formsで作ろうとするとものすごく高い壁を感じてます。公式でいいじゃんってなる。
やっぱりクロスプラットフォームツールキットはトレードオフなものだなあと。
それでまあ、今やることなくなって手持無沙汰になっててどうしたもんかなーってなってます。
一時はXamarin.FormsのLinuxディストリビューションへの対応でもやろうかと思ったんですが、
Linuxディストロってデスクトップ環境を好きに選べるからいろいろあってXamarin.Formsの方針であるそのOSのネイティブに合わせるっていう方針とイマイチマッチしていない感じがしてならないです。
Xamarin.Formsの方針に従うならとりあえずターゲットをUbuntuだけにしぼってGnome Sharpでするのが妥当なのかなとか、
妥協してGTK Sharpで実装するのが楽だし、幅広く提供できるよなとか考えて答えが見つからず、
そのうえLinux界隈の人がXamarinはMSのものだから使わないと言ってるのも観測してしまって、
作っても誰にも使われず徒労に終わるんじゃないかと考えてしまって最初の一歩を踏み出せない状況。一応興味はあるのですが・・・
なんにせよ何かやろうかとは思うんですがほかに作りたいアプリを思いつかないしどうしたもんかなって感じ。困った困った。
【Xamarin.Forms】XAML Compilerを使用してパフォーマンスを向上させる
Xamarin.FormsだとXAMLのコンパイルは実行時におこなわれます。
これをC#コンパイル時に行ってパフォーマンスを向上させようというのがXAML Compilerです。XAMLCとも。
使い方は簡単でコンパイルしたいXAMLがあるプロジェクトの名前空間の上に以下のようなコードを追加します。
using Xamarin.Forms.Xaml; ... [assembly: XamlCompilation (XamlCompilationOptions.Compile)] namespace PhotoApp { ... }
詳しい内容はここを参照してください。
これでXAMLがC#コンパイル時にコンパイルされます。
実行時のパフォーマンス向上以外にも、XAMLの解析がコンパイル時に行われるので自分のXAML書き間違いがコンパイル時にわかったり、XAMLが事前にコンパイルされるため最終的なアセンブリのサイズが小さくなったりとメリットがあるみたいです。
この機能デメリットなさそうだし、デフォで有効にしといてくれないかなあ。何で無効になってんだろ。('A`)
【2016/6/13 追記】この機能、どうもまだβ版のようです。
TypeScriptで疑似的な文字列enumを作る
※2015/09/24追記 やはりいろいろ問題があるようなのでこの方法は使わないほうが良いです。
知っての通り、TypeScriptのenumには文字列を設定することはできない。
下記のようにすればenumのメンバ名の文字列を取得することはできる。(TypeScript1.6を使用)
export enum Fruits { BANANA=1, APPLE=4, ORANGE=5 } export function showFluits(fruits:Fruits){ //これで文字列をとりだせる let fruitsStr = Fruits[fruits]; console.log(fruitsStr); } showFluits(Fruits.APPLE);//APPLEと表示される
しかし文字列を定数として扱うことのみを考えた場合、TypeScriptのenumではやや冗長すぎるJavaScriptが生成される。
生成されたJavaScript
export var Fruits; (function (Fruits) { Fruits[Fruits["BANANA"] = 1] = "BANANA"; Fruits[Fruits["APPLE"] = 4] = "APPLE"; Fruits[Fruits["ORANGE"] = 5] = "ORANGE"; })(Fruits || (Fruits = {})); export function showFluits(fruits) { //これで文字列をとりだせる let fruitsStr = Fruits[fruits]; console.log(fruitsStr); } showFluits(Fruits.APPLE); //APPLEと表示される
const変数やオブジェクトリテラルを使用してもよいのだが、それだとせっかくのTypeScriptの型の恩恵を受けることができない。
showFluits関数の引数fruitsがstring型になった場合を考えてみてほしい。その関数を使うユーザは何の文字列を引数にしてよいか戸惑ってしまうだろう。
それで型の恩恵を受けつつ文字列定数を定義する方法はないか考えた結果以下のようになった。
export interface Fruits extends String{} //インターフェースと同じ名前の名前空間 export namespace Fruits{ export const BANANA:Fruits="BANANA"; export const APPLE:Fruits = "APPLE"; export const ORANGE:Fruits = "ORANGE"; } export function showFruits(fruits:Fruits){ console.log(fruits); } showFruits(Fruits.APPLE);//APPLEと表示される
生成されたJavaScript
export var Fruits; (function (Fruits) { Fruits.BANANA = "BANANA"; Fruits.APPLE = "APPLE"; Fruits.ORANGE = "ORANGE"; })(Fruits || (Fruits = {})); export function showFruits(fruits) { console.log(fruits); } showFruits(Fruits.APPLE); //APPLEと表示される
まあ若干裏技感があるんだけどね。
C# for Systems Programming Part2 fail fast
C# for Systems Programmingの情報がJoe duffy氏のブログで新しいのが書かれていたので和訳してみます。例によってGoogle翻訳を使っての拙い訳です。
-----------------------------ここから和訳-----------------------------
If you’re going to fail, do it fast
私たちの言語開発チームで検討した技術の一つは"fail-fast"と呼ばれるもので、プログラムが可能な限り迅速にバグが検出された後に失敗するというアイデアだ。このアイデアは本当にフィクションなどではなく、現実的なアプリケーションなものだ。多分ね。
下記のものは私たちが開発中のシステムでfail-fastとなるものである。
・契約違反(Contract violation)
・実行時宣言違反(Runtime assertion violation)
・Null参照先のデータ取得(Null dereference)
・メモリ不足(Out of memory)
・スタックオーバーフロー(Stack overflow)
・ゼロ除算(Divide by zero)
・算術オーバーフロー(Arithmetic overflow)
もしあなたが.NETで投げられた例外の内の90%を見て、それらが上記の状況によるものである。これは面白い。
これは例で、開発中のシステムに似たものだ:
これは最終的に以下のようになる。
私の経験では開発者はふつう、以下の二つの失敗状態の反応により、一つを行うことで終了する。
1.キャッチして続行する。一般的にはバグを隠ぺいし、それが難しく後で検出するために行う。
2.プロセスをクラッシュさせる。その後スタックは戻されて実行が終了する。潜在的に重要なデバッグ状態は失われる。
私は合法的キャッチして例外を処理する3番目があると仮定したが、それは珍しいものでリストに上げたくはない。それらは通常プログラミングのエラーであり、キャッチされるべきでまた容易だ。"キャッチして無視"の規律は莫大な壊れたウィンドウとなる。
例外はその場所を持っている。しかし、入出力、データ、の残り10%のシナリオについては再スタート可能だ。(例:パーサなど)
私たちが既存のコードベースにfail-fastを適用したときには案の定多くのバグを見つけることができた。これは例外ベースの事故にはふくまれていないが、常に返値ベースのものだ。私たちが移植したのは音声サーバのプログラムだった。それは数年前からHRESULTが内包されていたルーチンで実装されていたが誰も気づいてなかった。悲しいことにこれが台湾の顧客は80%のエラーに見舞われた。fail-fastはそのエラーを私たちに対面させた。
このカテゴリに算術オーバーフロー(Arithmetic overflow)をいれることにあなたは疑問を抱くだろう。その通りで、私たちはデフォルトで計算チェックを使用している。興味深いことに、これは私たちが遭遇した障害のなかで共通して最もストレスの元となったものだった。(おかげで大部分がfail-fastとなるだけでなく、競合状態やデッドロックを排除して、安全な実行モデルとなる。・・・しかし私は脱線する)。なんて腹立たしいことだと言うかもしれない?まさか! ほとんどの時間は開発者が実際のところオーバーフローが可能であるなんて期待していなかったし、無言のラップアラウンドは偽の結果をもたらすだろう。事実、共通のセキュリティ開発の元は今日、整数オーバーフローをトリガーすることにより得られる。最善は開発者にそれをスローして対面させること、そして、その開発者にチェックをはずすかどうか決めさせることだ。
メモリ不足(Out of memory)は別のケースだから横に置いておこう。現代のアーキテクチャは障害をさける方法をとるよりかは、むしろ、障害を容認する傾向がある(例:再スタートできること、状態を書き込めることなどなど)。そう、オブジェクト指向モデリング硬化が時間とともにどんどん珍しくなる傾向になっている。それゆえ、fail-fastのデフォルトは実際ほとんどのコードにおいて正しいものだ。しかし、いくつかのサービスに--カーネル自身のような--は不適切なのかもしれない。私たちがどのようにこれに取り組んだかはブログ投稿を見てほしい。
.NETは既にfail-fastを選択的に使用している。このように"corrupted state exceptions”
私たちは積極的により広く、C#と.NETへのfail-fastの規律を適用し、調査している。だから、期待してほしい。しかしながら、幅広いプラットフォームのサポートがない場合にはその規律があなたのコードベースで簡単に採用できるのだ。
-----------------------------ここまで和訳-----------------------------
方針としてはやって当たり前なチェック処理は既定でやるようにして、必要に応じてそのチェックを外せるようにするみたいですね。このあたりがC++とは違うのでしょうか。 まあ、算術オーバーフローの項のuncheckedはC#にすでにあったと思いますが。
例にでてたrequires構文?はD言語のin契約に似ている印象(D言語まともにやったことないけど)。というかこれC#にも欲しい機能です簡潔に引数チェックできそうですし。
気になっていたメモリ管理についてはノータッチだったのでこの言語の評価はまだ何とも言えないですね。少し前の記事でクラス実装終わったみたいなこと書いてたんでぼちぼち情報が出てくるんじゃないでしょうか。とりあえずお蔵入りになってなくてよかったです。