LuaのGo言語実装を公開しました

LuaをGo言語のみで実装した GopherLua を公開しました。

詳しくはGithubのREADMEを見ていただくとして、特徴としては以下になります。

  • Lua5.1ベース

    • 5.1の機能はほぼ実装済み
  • Compiler, VMともに完全にGo言語のみで実装

  • 引数の受け渡し以外でのスタック操作が不要で使いやすいAPI

なぜ作ろうと思ったか

もともとC言語でものを作るときにはLuaを設定ファイルの代わりとして取り入れていました。Goではあまり拡張言語実装がないので、jsonだったりiniだったりを設定ファイルとして使っていましたが、やっぱり微妙にめんどくさい。変数くらい欲しいなあ・・・とか。

結局、固い言語だけでモノを作るのは難しく、やわらかさが必要になるポイントがあります。そういうところはまず、「設定ファイル」として外だしされます。そしてその設定ファイルがどんどん肥大化したり複雑化したりして・・・(XML地獄とか)。Cに対するLua、PythonやRuby、Javaに対するGroovyなど「固い言語」+「拡張言語」は自分的に一番しっくりくる構成です。それをGoで実現するために実装してみました。

もうひとつは単純にLuaに興味があったこと。正確にはLuaというよりレジスタ型VMに。スタック型VMは実装したことがあるのですが、レジスタ型で実装してみたかったのです。

最後に、Go言語で処理系を実装してみたかったこと。実行ファイルが吐けるし、速度はそれなりに速いし、Go自体がGCを持っているのでかなり処理系作るのが楽だと思われるのでどんなものかな、と。

簡単な使い方

こうして

import (
    "github.com/yuin/gopher-lua"
)

こんな感じですね。

L := lua.NewState()
defer L.Close()
if err := L.DoString(`print("hello")`); err != nil {
    panic(err)
}

Go関数をLuaで呼ぶ場合は以下のような感じ。LuaとGoの間の引数と戻り値の受け渡しのみにスタックを使います。

func Double(L lua.LState) int {
    lv := L.ToInt(1)             // get argument
    L.Push(lua.LNumber(lv * 2))  // push result
    return 1                     // number of results
}

func main() {
    L := lua.NewState()
    defer L.Close()
    L.SetGlobal("double", L.NewFunction(Double)) // Original lua_setglobal uses stack... 
}

あとは README をみていただければ大体分かるかと思います。

実装について

全般の話

まず、そんなにLua自体のコードは読んでません(おい)。Luaは1パスでコード生成までできる文法で本家はそういう実装ですが、GopherLuaでは

  1. トークナイズ(Lexer, 手書き)
  2. パース(go-yacc)
  3. コード生成
  4. 最適化

まったく違うパス構成なので実装は完全に独自実装です。またyaccなのでユーザが文法を簡単に変えられます。

現状最適化はほとんどやっていません。複数JMPをまとめるくらい。

あと、やっぱ三項演算子欲しい・・・。

データモデルの話

こういう言語を実装する場合はいわゆる共用体のような、1つの型で複数の型を判別できるものが必要となります。Cでは共用体やポインタの下位ビットを使いますが、Goでは以下の選択肢があります。

  • reflect.Value
  • interface
  • unsafe.Pointer

GopherLuaでは interface を使っています。 interface

  • Go側のAPIを考えたとき一番分かりやすい

という利点がある一方

  • ネイティブ型をラップした interface の場合、 interface への変換が発生して速度低下を招く

という欠点があります。そこで簡単なベンチマークをしたのですが

  • 単純な例(フィボナッチ計算)では確かに reflect.Value などが速い。
  • 一方、複雑な例になればそれほど差が無いように見えた

ため interface を採用しました。ただ、プロファイルをとるとかなりの部分が interface への変換に取られているので、ここが(Goが進化して)速くなればGopherLuaも速くなると思います。

速度の話

それほどパフォーマンスチューニングはしていませんが、フィボナッチではperlと同じくらいの性能は出ているようです。メモリ確保はそもそも気をつけて減らしているので後は

  • 関数をベタ書きする

くらいかなあ。Goは短い関数をinline化してくれますが、そのinline化はそんなに賢くないのでやっぱりダメですね。ためしにVMの関数コール部分などをベタ書きすると1.2倍くらいの速度になりました。今のところそこまで速度を求めていないので元に戻しましたけど。

やっぱマクロ欲しいなあ・・・

とりあえず、使えるはずです

Lua5.1のテストは主要なものは通っているので使えるものになっているはずです。設定ファイルのかわりに使うもよし、プラグインの仕組みに使うのもよし。今後はGopherLuaのTableをGoのstructにマッピングするような( jsonxml パッケージみたいな)ライブラリとか作りたいなと思っていたりします。

comments powered by Disqus