Build forkOS API using pthread.

Posted on June 20, 2013 / Tags: jhc, ajhc, thread, pthread

Table of contents


再入可能にしようとして、まずは forkOS を作ろうとしたらハマったでゲソ。 このページはforkOSをpthread_createを使って作るメモ書きでゲソ。

作成方法を考えよう

作る前にまずは作戦をねるでゲソー。

(1) GHC baseパッケージの設計をまねる

GHCでのforkOSの実装 を見ると、StablePtrにIO関数を包んで、C言語のforkOS_createThread関数に渡しているでゲソ。 pthread_createは

どうやらGHCではStablePtrはFFIを越えることができて、その型はHsStablePtrのようでゲソ。 このようなコードをAjhcでも実現できなイカ? ところが それぽいコード を書いてみたところエラーになるじゃなイカ。 これはStablePtrは単なるスマートポインタになるはずでゲソが、 どうもこのスマートポインタを直接C言語コードに渡すのが禁止されているようでゲソ。

$ make
--snip--
Compiling...
[1 of 1] Jhc.Conc         
jhc-conc-pthread/Jhc/Conc.hs:28  - Error: caught error processing decl: user error (createFunc: attempt to pass a void argument)
jhc-conc-pthread/Jhc/Conc.hs:28  - Error: Type 'Foreign.StablePtr.StablePtr (IO ())' cannot be used in a foreign declaration

このスマートポインタを直接C言語コードに見せる行為をはたして許して良いものか考えどころでゲソ。。。 イカはforeignで使って良い要素を判定している箇所の抜き出しでゲソ。

もう一つ気になるのはdeRefStablePtrを使ってIOを元に戻しても実行不能という点でゲソ。

上のコードをコンパイルすると、イカのようなエラーになるでゲソ。

$ ajhc --tdir=tmp -o Main Main.hs
--snip--
Typechecking...
Compiling...
Collected Compilation...
-- TypeAnalyzeMethods
-- BoxifyProgram
-- Boxy WorkWrap
-- LambdaLift
Converting to Grin...
Updatable CAFS: 0
Constant CAFS:  0
Recursive CAFS: 0
Exiting abnormally. Work directory is 'tmp'
ajhc: Grin.FromE.compile'.ce in function: theMain
can't grok expression: <fromBang_ x128471745∷IO ()> x62470114

(2) foreign import ccall “wrapper”で関数ポインタを作る

wrapperというforeign import宣言 1 があり、これを使えば任意のIOを関数ポインタに変換することができるようでゲソ。 wrapperを使えば簡単にpthread_createにHaskellのIOを呼び出してもらえるじゃなイカ? やってみるでゲソ!

…と、 ものすごくいい加減なコード を書いてみたでゲソ。 ところが今度はFunPtrというC言語の型がみつからないとGCCに怒られるでゲソ。

Running: gcc tmp/rts/profile.c tmp/rts/rts_support.c tmp/rts/gc_none.c tmp/rts/jhc_rts.c tmp/lib/lib_cbits.c tmp/rts/gc_jgc.c tmp/rts/stableptr.c -Itmp/cbits -Itmp tmp/main_code.c -o Main '-std=gnu99' -D_GNU_SOURCE '-falign-functions=4' -ffast-math -Wextra -Wall -Wno-unused-parameter -fno-strict-aliasing -DNDEBUG -O3 '-D_JHC_GC=_JHC_GC_JGC'
tmp/main_code.c: In function ‘fFE$__CCall_testThread’:
tmp/main_code.c:659:17: warning: statement with no effect [-Wunused-value]
tmp/main_code.c: In function ‘ftheMain’:
tmp/main_code.c:1146:68: error: ‘FunPtr’ undeclared (first use in this function)
tmp/main_code.c:1146:68: note: each undeclared identifier is reported only once for each function it appears in
Exiting abnormally. Work directory is 'tmp'
ajhc: user error (C code did not compile.)

これはイカのようなC言語コードをAjhcが吐き出すためでゲソ。 もうちょっと小細工すれば関数ポインタが使えるようになりそうじゃなイカ?

どうも上の不具合はイカのpatchで簡単に修正できるようでゲソ。たぶんこれはイージーミスだと思うでゲソ。

しかしこれでFunPtrを使った関数ポインタを実現できたんでゲソが、 任意のIOを関数ポインタ化できることにはならないでゲソ。 具体的にはイカのような型になってしまっているforkOSの引数をIO ()にしたいでゲソ。

GHCはどんな魔法を使っているのでゲソ? GHCでforeign import ccall “wrapper”を使う例 を解析すれば何かわかるかもしれないでゲソ。 freeHaskellFunPtrの行方を探ったところ、 どうもghc/rts/Adjustor.cでwrapperが作ったコード片のラッパーを作るようでゲソ。 createAdjustorというのが主犯のようじゃなイカ。

とりあえず任意のIOをFunPtrに変換するのはキツいにしても、 定数的なIOはラベルをふるだけなのだから比較的簡単にFunPtr化できるんじゃなイカ?

Commentary/Rts/FFI – GHC を読んでみたでゲソが、具体的な実現方法については言及がないでゲソ。

(3) グローバル関数テーブルのインデックスを引数渡し

StablePtrを使わずにコンテキスト間でIOを授受する方法として無理矢理考えてみたでゲソ。 結局インデックスの意味がC言語側に漏れるので、 StablePtrを直接C言語に渡すケースと比較して危険度はほとんど変わらない気もするでゲソ…

(4) ラムダ式を使うために-std=gnu++11でコンパイル

さすがにこれは筋が悪すぎるので、困った時の隠し玉に取っておかなイカ?

結論: やはりnewStablePtrに直接IO ()を突っ込んだ際の挙動を観察するべき

ajhc: Grin.FromE.compile'.ce in function: theMain
can't grok expression: <fromBang_ x128471745∷IO ()> x62470114

このメッセージはcompile’関数が以下の型を意図せず受け取ったことをしめしているでゲソ。

ということはこのEApという型はどこかで変換されるべきで、そのしくみから漏れてきたと考えられるじゃなイカ。 しかし仮にイカのようなpatchをあてても…

ajhcのコンパイルでイカのようにエラーになってしまうでゲソ。 どうやらforeign importに渡せる型は限定されているようでゲソ。

Compiling...
[1 of 4] Foreign.StablePtr
lib/haskell-extras/Foreign/StablePtr.hs:56  - Error: Type 'Foreign.StablePtr.StablePtr (IO Foreign.StablePtr.37_a)' cannot be used in a foreign declaration
lib/haskell-extras/Foreign/StablePtr.hs:57  - Error: Type 'IO Foreign.StablePtr.39_a' cannot be used in a foreign declaration
make[2]: *** [haskell-extras-0.8.1.hl] エラー 1

じゃあUIOを経由してStablePtrを作るのはどーなんでゲソ?

エラーは変化せず、そりゃそうカー。

ちょっと立ち返って、StablePtrを経由したIO ()の授受というのは本来どのようなコードになるべきなんでゲソ? イカのようなコード、つまりIO ()をStablePtrとBang_で二重に包んだような型を作り、 この型をpthread_create()で生成されるスレッドに渡して復元してほしいでゲソ。

ところが先のエラーがなぜ起きていたかというと予期しないEApがあったからじゃなイカ。 この変なEApだけごまかせばなんとかなるんじゃなイカ? どうやらこのEApという型はfromAp関数でEVarを剥き出しにしてからパターンマッチされるようでゲソ。

そこでイカのようなfromBang_プリミティブを抹殺する関数を作ったでゲソ!

このstripBangを通してからfromApにeを食わせたところ無事エラーが出なくなったでゲソ。 ちょっと危険な気もするが、大目に見てほしいでゲソ。

実装

ここまで来ればforkOSを実装するのは簡単でゲソ。


  1. 本物のプログラマはHaskellを使う - 第22回 FFIを使って他の言語の関数を呼び出す:ITpro

blog comments powered by Disqus