近&況

Recent Posts
Edit

スノボ納め 16-17

2017-03-26

表大山まで滑りに行ってきた。今季はこれでおしまい。

http://www.daisen-resort.jp/

松江に住んでいると大山が近くて、冬場はほぼ毎週スノボに出かけている。数えてみると今季は9回行けたらしい。会社の人と一緒に行くんだけど、今季は社外の人とも一緒に行ったりした

今季は雪の降り始めが遅くてどうなることかと思ったけど、1月と2月に大量に降った日が一度ずつあり、3月入ってからも降ったりしたので、それなりに長く滑れるシーズンになって良かった。

今年は試乗会で板を予約したので、来季は新しい板になる。試乗会ってその場で板を買えるのかと思ってたんだけど、そうではなくて、この夏に製作するモデル(つまり17-18モデル)のサンプルが試乗できて、気に入ったら予約すると9月くらいに手に入るということだった。とても楽しみ。

今使っている板は新古品を2万で買ったやつで、スノボショップに持っていたら保存状態が悪くて変な風に反っていると言われて、一応滑れるようにエッジを調整してもらったという経緯がある。気づいたら6年これに乗っていたらしい。後悔はしてないけど、もう少し早く買い替えても良かったかなという気はする。新しい板、試乗してみて、めちゃめちゃ乗りやすいと思ったからね。

Edit

LLVMで動くオブジェクト指向言語を作ってレイトレした

2017-03-07

先月のエントリでEsquisというプログラミング言語を作っていると書いたが、あれからいろいろ機能を足して、レイトレーシングのプログラムが動くようになった。

できたもの

https://github.com/yhara/esquis/releases/tag/v0.0.1

実行結果

レイトレ結果

(前エントリと全く同じ絵なのもアレなので、T=10にしてみた)

ソース

https://github.com/yhara/esquis/blob/v0.0.1/examples/ray.es

見てもらえばわかるけどほぼRubyで、ただし引数に型指定があって、コンパイル前に型がチェックされる。(それって最高じゃん?と思ったあなた、気が合いますね) 今日はついに、「引数の型の変更漏れを処理系が教えてくれる」という幸福な体験をした。

言語仕様はこれからいろいろ変わると思うので詳細は書かない。

経過

足したもの

など。やることが多かったけど、ゴール(動くべきプログラム)が明確だったので、少し動かしては足らないものを足して、という感じでやっていたらあっという間に機能が増えていった。毎日のように出来ることが増えていくのでとても楽しかった。

今後の予定

まだまだやることはたくさんある。文字列、配列、継承、ジェネリクスなど。

一方で今年は別のこともやる予定なので今月の残りはそっちの作業をする。その間に次の良い課題となるプログラムを考えよう。

参考図書

Edit

牛乳を泡立てるやつ買った

2017-03-05

百均でミルクフォーマー買った。150円だった。ミルクフォーマーはスタバの店舗で売ってるのを見たことあるけど、2000円以上してたので、百均にあると聞いてびっくりした。

商品の写真

作りがちゃっちくて、グリコのおまけ感ある。とりあえず使う前に洗おうと思ったが、本体は防水じゃないので、棒の部分だけ取り外す必要があるんだけど、これがまためちゃめちゃ固い。しかし隙間にスプーンの柄を突っ込んだりして頑張ってるうちになんとか取り外すことに成功。その後は常識の範囲内の硬さになった。

使い方だけど、カップに牛乳を入れて、こいつを突っ込んでスイッチを入れるとモーターがすごい勢いで回転し、牛乳に空気が入るという寸法。液体全体が泡になるのではなく、上部に泡が発生するという感じ。牛乳はホットでもアイスでもよい。

成果物としてどういうものが得られるのかよく分かってなかったんだけど、要するにお店のカプチーノとかの上に乗ってる泡状のやつ、あれが手に入ります。150円にしては意外なほどふわふわになる。なんとなく専用の機械とかでやってるのかなぁと思ってたけどこんな単純な機構で再現できるのか。

ということであとは発生させた泡をどのように活用するかという話になるが、スタバのサイトによると泡立った牛乳の上からコーヒーを注ぐということらしい。なるほど。

Edit

弁当はじめました

2017-03-05

野菜を買いすぎたので、夕食の野菜炒めを多めに作って、半分を翌日の弁当にしてみた。普段は会社に来られるお弁当屋さんの弁当を買ってて、それと比べるとボリュームも彩りもないけど、意外とこれで良いじゃんっていう感じだった。コンビニが近いので味噌汁だけカップのを買うとかもできるし。一人暮らしだとどの野菜を買ってもたいてい大きすぎるので、こうやって消費先が増えるのは良い。

弁当を作ること自体は興味がありつつ手を付けてなかったんだけど、最近ちょうどいい大きさのランチボックスを見かけて、とりあえず買うだけ買っておいたのが役に立った。(市販の弁当箱はたくさんあるんだけど、米は冷凍して持っていくので、おかずだけ入れるやつが欲しかったのだった) 二段になってるので、慣れてきたら二段目を冷凍食品で埋めたりするのもいいかも。

Edit

CapybaraでSinatraアプリをテストする

2017-02-28

このブログはSinatraで作っていて、テストはRack::Testで書いてたのだけど、思い立ってCapybaraで書き直した

Rack::Test vs Capybara?

というのはある種のバグがRack::Testだと漏れちゃうんですよね。例えばフォームをsubmitしたときにビューのtypoで正しいリクエストが飛ばない、みたいなやつ。Rack::TestだとこういうPOSTリクエストに対しこういう挙動をする、は書けるんだけど、ボタンを押したときにどういうPOSTが飛ぶか、はCapybaraでないと書けない。

もっと規模が大きければRack::Testでコントローラのテストを書いて、Capybaraでintegraiton testを書いて…みたいな構成もあり得るけど、このブログの場合は規模が小さいのでCapybaraによるE2Eテストだけあれば良いかなと思う。

以下、Rack::Testから移行するに当たって「これどうするんだろ?」ってなったことをメモしておく。

Basic認証したい

Driverによって指定方法が違うみたい。Rack::TestドライバはBasic認証に対応してなさそう。しかしヘッダを直接指定するという方法があった(参考)。

  # https://github.com/yhara/nlog2/blob/a63294a2a1b514e76ac130b2f6400fda8a8436b5/spec/app_spec.rb#L8-L11
  def login(username='jhon', password='passw0rd')
    encoded_login = ["#{username}:#{password}"].pack('m*')
    page.driver.header 'Authorization', "Basic #{encoded_login}"
  end

フォームが表示されることを確認したい

have_content("form")…かと思いきや、これはformというテキストが画面に表示されている、という意味なのでタグには反応しない。have_selector("form")が正解。

フォームにデフォルトで入力されていることを確認したい

これもhave_selectorでいける。こんな感じ。

      expect(page).to have_selector("input[name='title'][value='#{@valid_posted[:title]}']")

POSTしたい

postメソッドがない、と思ったがCapybaraはE2Eテスト用のライブラリなので、POSTリクエストを直接送るような手段はないのだった。そりゃそうか。その代わりにclick_linkやclick_buttonといったメソッドがあるので、まずvisitで編集画面にアクセスして、フォームに記入してclick_buttonする、みたいな書き方になる。

今回はこんな感じにした。

        login
        visit '/_edit/'
        fill_editor @valid_params
        click_button "Save"

fill_editorはcheckメソッドでチェックボックスをオンにしたり、fill_inでテキストを入力したりする。定義は以下。

  def fill_editor(params)
    check "permanent" if params[:permanent]
    fill_in "title", with: params[:title]
    fill_in "slug", with: params[:slug]
    fill_in "body", with: params[:body]
    fill_in "datetime", with: params[:datetime]
  end
Edit

Rubyでレイトレーシングした

2017-02-21

生成された画像

これをRubyに移植した。aobenchが自作言語のターゲットとしてまだちょっと大きいなと思ってより小さいサンプルを探していたのだけど、最後のステップまで移植したら結局aobenchとあんまり変わらないようなものになった。ただaobenchのコードはちゃんと理解していなかったので、少し理解が深まった気がして面白かった。あと画像が出るプログラムはやっぱり楽しい。

ソースはgistに上げた。ray5.rbまであり、それぞれ以下のようになっている。

  1. 画像を生成する
  2. 球を出す
  3. 陰影を付ける
  4. 床を出す
  5. 反射させる

上に貼ったのはray5.rbの出力結果だ。よく見ると元記事と微妙に球の位置が違うことが分かるが、オリジナルは時間tを受け取ってアニメーションするようになっていて、tの値が違うのが原因と思われる(最初、アルゴリズム部分に間違いがあるのかと思って焦った)。

Edit

printfだけで画像を生成する(.ppm)

2017-02-21

自作のプログラムで画像を出力したいとき、どのフォーマットを選ぶのが良いだろうか。PNGやJPGなどは広く普及したフォーマットだが、マイナーな言語や自作のプログラミング言語の場合、出力するためのライブラリが存在しない場合がある。

そんなときに役立つのが、aobenchでも使われているPPMという画像フォーマットだ。PPMは仕様が極めてシンプルなので、例えばprintfが使えれば出力することができる。

以下はppmファイルの例である(上記ページから拝借)。

P3
3 2
255
255 0 0
0 255 0
0 0 255
255 255 0
255 255 255
0 0 0

Netpbm形式

PPM形式はNetpbm形式とよばれるフォーマットの一種である。Netpbm形式には「白黒・グレースケール・カラー」の3種類に「テキスト・バイナリ」の2種類をかけた6種類が存在し、そのうちのカラーのものがPPM(portable pixmap format)形式なのだ。

上記の例だと、ファイル冒頭の「P3」が「カラー、テキスト」に対応する。

2行目の「3 2」は画像のサイズで、この場合は3x2ピクセルという意味になる。3行目の「255」は、色を0〜255までの256段階で表すという意味。

4行目以降は、左上から順に各ピクセルのR, G, B要素を数値にしたものが並んでいる。ここでは分かりやすく1ピクセルごとに改行してあるが、仕様的にはどこで改行しても構わないようだ。

注意点

R, G, B要素は整数で出力する必要がある。間違って小数で出力してしまった場合、ソフトによっては壊れた画像が表示されたりするので注意が必要だ(エラーにならないので気づきにくい)。

Edit

オブジェクト指向言語をLLVM IRにコンパイルするには

2017-02-07

昨年から、Esquisという言語を作っている。Rubyっぽい文法の静的型付け言語になる予定で、実行はLLVM IRを経由して行う。

ということで、(クラスベースの)オブジェクト指向言語をLLVM IRで表現する方法について考えていたので、分かったことをまとめておく。既存の実装としては主にCrystalの生成するLLVM IRを参考にした。

オブジェクトをstructに対応させる

LLVMにはstruct型というものがあり、オブジェクトはこれで表現することができる。問題はどのようなstruct型を定義するかだ。

最初に考えたのは、EsObjという型を作って、すべてのオブジェクトを表現するという方法だ。

%EsObj = type { ... }

だがすぐに、この方法では上手くいかない点が見つかった。オブジェクトはインスタンス変数の値を保持する必要があるが、オブジェクトがどのようなインスタンス変数を持つかは属するクラスによって異なるので、一つのLLVM structで全てを表現することはできない。

ということでEsquisの1クラスごとに、対応するstruct型を定義する必要がある(ちなみにCrystalの生成するLLVM IRもそのようになっている)。以下は例である(Esquisの言語仕様はまだちゃんと決めてないので雰囲気で読んで下さい)。

; 1. インスタンス変数を持たないクラス
; class A
; end
%A = type {}

; 2. 整数型のインスタンス変数をひとつ持つクラス
; class B
;   def initialize(Int @i)
;   end
; end
%B = type { i32 }

インスタンス生成

次にインスタンス生成のコンパイルを考える。メモリを確保する必要があるので、今回はBoehm GCを使う。@GC_mallocを呼べばメモリの確保(と、必要に応じてメモリの回収)が行われるので、何バイト確保するのかだけ考えればよい。structのサイズ計算については前の記事で触れた。

これを使って、クラスAのインスタンスを生成する関数を書く。名前は@"A.new"としよう。この関数は新しくメモリを確保して、struct Aのポインタ(%A*)を返す。

define %A* @"A.new"() {
  ; struct Aのサイズを計算する
  %A_size = ptrtoint %"A"* getelementptr (%"A", %"A"* null, i32 1) to i64
  ; メモリを確保する
  %raw_addr = call i8* @GC_malloc(i64 %A_size)
  ; i8*を%A*に変換する
  %addr = bitcast i8* %raw_addr to %A*
  ; 変換後のアドレスを返す
  ret %A* %addr
}

これだけでも動くが、CrystalのLLVM IRを見るとメモリ確保後に@llvm.memsetで内容をゼロクリアしていることが分かる。ゼロクリアしないと、作っている処理系にバグがあったときに毎回挙動が違うことになりかねないので、クリアしたほうが良さそうだ。

declare void @llvm.memset.p0i8.i64(i8* nocapture, i8, i64, i32, i1)
...
  call void @llvm.memset.p0i8.i64(i8* %raw_addr, i8 0, i64 %A_size, i32 4, i1 false)

インスタンスを生成する側は以下のようになる。

define i32 @main() {
  ...
  ; a = A.new
  %a = call %A* @"A.new"()
  ...

メソッド呼び出し

次にメソッド呼び出しを考える。継承はまだ考えないものとする。オブジェクトの型が事前に分かっている(= 呼ばれるメソッドが事前に特定できる)のであれば、以下のように単なる関数呼び出しにコンパイルできる。ただしメソッドに対応する関数は、第一引数として当該オブジェクトのアドレス%selfを受け取るものとする(今回は必要ないが、インスタンス変数の値を参照したりする場合に必要となる)。

; class A
;   def foo
;     213
;   end
; end
%A = type { }
define i32 @"A#foo"(%A* %self) {
  ret i32 213
}

define i32 @main() {
  ...
  ; a = A.new
  %a = call %A* @"A.new"()
  ; a.foo
  %result = call i32 @"A#foo"(%A* %a)
  ...

多態

次に、継承について考える。継承がある場合、ある変数に代入されているオブジェクトのクラスは実行時まで確定しないことがある。

例として、「図形」を表すShapeクラスと、そのサブクラスであるCircleクラスおよびSquareクラスがあるとしよう。各クラスは面積を計算して返すareaというメソッドをもつとする。

以下のような場合、変数sがCircleなのかSquareなのかは、実行時になるまで確定しない。そのため、どちらであっても対応できるようなLLVM IRを生成する必要がある。

; sampleメソッドは、配列のいずれかの要素をランダムに返す
s = [Circle.new(10), Square.new(20)].sample
s.area

クラスID

これに対応するには、各オブジェクトに所属するクラスの情報を入れておく必要がある。例えば各クラスに番号(以下、クラスID)を振り、それをstructの最初の要素とするというのは一つの方法である。

; class A
; end
%A = type { i32 }   ; クラスID 

; class B
;   def initialize(Int @i)
;   end
; end
%B = type { i32, i32 }  ; クラスID、インスタンス変数@i

クラスIDは、クラスのインスタンスを生成したタイミングでそのインスタンスに書き込む。クラスAの場合でいえば、先ほどの@"A.new"を改修する。

多態に対応したメソッド呼び出し

areaメソッドの呼び出しを考える。Shape#areaメソッドを作り、CircleかSquareか分からないオブジェクトsはとりあえずこの関数に渡すことにする。Shape#areaはclass_idを見て、Circle#areaまたはSquare#areaをcallする。

define i32 @"Shape#area"(%"Shape"* %self) {
  %id_addr = getelementptr inbounds %"Shape", %"Shape"* %a, i32 0, i32 0
  %class_id = load i32, i32* %id_addr

  ; %class_idが1ならCircle#areaをcall
  ...
  ; %class_idが2ならSquare#areaをcall
  %sq = bitcast %Shape* %self to %Square*  ; %selfは%Shape*なので、%Square*にキャストする必要がある
  %ret = call i32 @"Square#area"(%Square* %sq)
  ret i32 %ret
}

おわりに

大したことはやってないのだが、分かったことを一つずつまとめていたら長くなってしまった。いままでクラスやオブジェクトといったものがどのように実現されるかについてはあまり深く考えて無かったので、いろいろな発見があって面白かった。

Edit

syntax-rules進捗

2017-02-06

去年の夏頃から、BiwaSchemeにsyntax-rulesを入れようと思ってぼちぼち調べてたんだけど、どうもこれは本腰入れて調べないと進捗しないなということで、年末からいろいろ文献を読むなどしていた。

その成果がこちら。

swapマクロを展開させてみたところ

orrというマクロを素朴に展開すると、マクロ内で使っているtという変数名が呼び出し側で定義しているtと衝突してしまう。これを自動でt.0とt.1にリネームしている、という図。

ソースコードはブランチにpushしてある。まだhygenic macroに対応したエクスパンダが本体とは独立に存在するという状態で、最終的にはこれで本体のエクスパンダ(Biwascheme.Interpreter.expand)を置き換えるということになる。

読んだものとか

最初は「syntax-rules 実装」とかで検索してたんだけど全然資料がなくて、上位互換であるsyntax-caseについての文献を読んでいた。

ビューティフルコード

25章がDybvig先生によるsyntax-caseの実装についての寄稿で、結局これが一番参考になった。エッセイ集にこの内容ぶち込むのすごい。

日本語版と英語版のどちらを読むかは悩ましいところで、日本語版の方が概要を掴むのは早いと思うが、多少意味が取りづらい箇所がある。

論文

これもsyntax-caseの説明で、範囲はビューティフルコードとだいたい同じ。

ビューティフルコードも論文の方も、方針が詳しめに解説されているという感じで、詳細な全体像は載っていない。実装へのリンクとか付けてほしい…。書いてない部分は想像力で補完する必要がある。

今後の予定

とりあえず最小の例がうまく動いたというだけなので、リリースにはまだたくさんの作業が残っている。

が、まあ夏〜秋頃を目標にぼちぼちとやっていきたい。というのもBiwaSchemeは僕の中ではサイドプロジェクトという位置づけなので、静的型付け言語の実装の方をメインにやっていきたいのだった(今日LLVMのエントリを書いたのはこっちの作業)。

とはいえ今年はBiwaScheme 10周年イヤー(!)なので、今年中には実装を済ませて、1.0.0をリリースしたいと考えている。規格の範囲にはまだ未実装の大物がいくつか残っているが(ライブラリ、例外、dynamic-windなど)、アプリを作って遊べる程度の能力はもうだいぶ前からあるので。

追記

書き忘れていたことを一つ。書籍・論文の他には「既存の処理系の実装を読む」という方法があって、実際にいくつか眺めてみたのだけど、これはあまりうまく行かなかった。hygenic macroというシステムは処理系全体のさまざまなもの(シンボル、マクロ展開器、ライブラリシステム等)と関係しているため、処理系のどの部分まで読めばいいかの判断が難しいのと、処理系によって実装が全然違う(syntax-rulesだけを実装しているもの、psyntax経由でsyntax-rulesとsyntax-caseを実装しているもの、独自の低レベルマクロシステムの上にexplicit renamingとsyntax-rulesを実装しているもの、etc.)というのが大変だった。

Edit

llvmのsub expressionっぽいやつの書き方

2017-02-06

メモ。

LLVM IRは基本的に以下のような構造をしている。

; (レジスタ名) = (命令名) (引数 ...)
%2 = mul i64 %0, %1

ところがコンパイラが生成した.llファイルを見ていると、引数の部分に別の命令が入っていたりする。

%4 = mul i32 ptrtoint (i1** getelementptr (i1*, i1** null, i32 1) to i32), %3

ここではmul命令の引数に、ptrtoint命令とgetelementptr命令の呼び出しが入っている…ように見える。しかしそれにしては括弧の位置が微妙におかしい。例えば「to i32」はどの命令にかかるのだろうか?

実はこれらはConstant Expressionsと呼ばれるもので、特定の命令だけ、このような書き方が許されている。上記の例では以下の2つのConstant Expressionが使われている。

ptrtoint (CST to TYPE)
getelementptr (TY, CSTPTR, IDX0, IDX1, ...)

ということで、上記の「to i32」はptrtointと対応しているのであった。

以下のような構造体Aについて、これのサイズを計算したいとする。

%A = type { i32 }

これは以下のように書ける。

; nullを%A*と見なして、1だけ進めた位置のアドレスを得る
%addr = getelementptr %A, %A* null, i32 1
; アドレス値を整数に変換する
%A_size = ptrtoint %A* %addr to i64

この2行を1行にまとめるにはどうすれば良いか。まず1行目のgetelementptrをConstant Expressionとして書き直すと以下のようになる。

getelementptr (%A, %A* null, i32 1)

これを2行目の%addrと差し替えれば出来上がり。

%A_size = ptrtoint %A* getelementptr (%A, %A* null, i32 1) to i64

(LLVM構造体のサイズ計算についてはこのエントリを参考にした)

Next »