Haskellとインラインアセンブリ(導入編)
(以下では前提として実行するCPUをx86とします。SPARCとかの人はごめんなさい)
Haskellから任意の機械語のコードを実行するにはどうすればよいのだろう。
Foreign.PtrにFunPtrという型が定義してあり、これは機械語コードの入っているメモリへのポインタを示す。
さらに、
type IntFunction = CInt -> IO () foreign import ccall "dynamic" mkFun :: FunPtr IntFunction -> IntFunction
などとすることによりFunPtrの指すコードを呼び出すためのラッパを生成できる。
型ごとに別個のラッパが必要になり、必要に応じて自動的に定義されるわけでもないので、必要なものは個別に書いてやる必要がある。
これらを用いれば、Haskellからmallocを用いてメモリを確保し、そこにデータを書き込み、それをラッパにかけてやることによって、Haskellから任意の機械語コードを実行することができるだろう。
まず、利便性のために、ラッパを自動的に選択できるよう、FunPtr a から a に変換できる型のクラスを作っておく。
class MkFun a where mkFun :: FunPtr a -> a foreign import ccall "dynamic" mkFunInt :: FunPtr (IO Int) -> IO Int foreign import ccall "dynamic" mkFunInt2Int :: FunPtr (Int -> IO Int) -> (Int -> IO Int) instance MkFun (IO Int) where mkFun = mkFunInt instance MkFun (Int -> IO Int) where mkFun = mkFunInt2Int
当面はIntを返す関数とIntを取ってIntを返す関数のみをサポートしておく。
機械語列からはその関数の型を推論することなどできないので、関数の型はこちらが与えてやる。その際に、コードに対して型が付与できるように型を作る。
newtype CodeBlock a = CodeBlock (Ptr Word8)
newtypeでなくtypeでやると、typeは単なるエイリアスになるため、うまく型が付かない。
メモリを確保してコードを書き込むのと機械語列を実行する関数を作る。
writeCode :: [Word8] -> CodeBlock a writeCode code = unsafePerformIO $ do let len = length code q <- mallocArray len pokeArray q code return $ CodeBlock q exec :: MkFun a => CodeBlock a -> a exec (CodeBlock p) = mkFun $ castPtrToFunPtr p
これで次のようにして望みのコードを実行させることができる。
foo :: CodeBlock (IO Int) foo = writeCode [ 0xb8, 0x09, 0x03, 0x00, 0x00 -- mov eax,777 , 0xc3 -- ret ] main = do ret <- exec foo print ret
fooは単に777を返す関数で、mainはそれを呼び出してprintしている。
$ ghc --make AsmTest.hs
$ ./AsmTest
777
さて、これでインラインアセンブリの可能性が見えてきた。当然のことながら次は機械語列をHaskellから生成したいという要求が出てくるので、そちら方面に話を進めることにする。