Build instructions at the bottom. By that I mean requirements :
- having LuaJIT in
pkg-config
... - having gcc or whatever so that
Cgo
can compile C code (obvisouly I link with LuaJIT library...)
Beside that, this module only has to be import
ed, like a "normal" module.
import (
"github.com/folays/luajit-go"
)
func main() {
var L *luajit.State = luajit.NewState()
L.ReasonableDefaults()
do some stuff with L...
}
//go:embed *.lua
//go:embed subdir/*.lua
var luaFS embed.FS
L.RunEmbedFsFatal(luaFS)
L.RunEmbedFsPathFatal(luaFS, "subdir")
Note : Lots of calls have a non-Fatal()
-suffixed version, which returns an error
.
There is also some helpers to load+run some embedded
Go's string
or []byte
.
L.SetGlobalAny("myvar", data)
myvar := L.GetGlobal*("myvar")
myvar := L.GetGlobalAny("myvar").(typecast) // because it returns a `any`
myvar := L.GetGlobalBoolean("myvar") // returns a `bool`
L.RunFuncFatal("some_function") // run a func named ...
L.RunStringFatal(`print("hello world")`) // eval and run
Note : each of those above can take any number of args. (...[]any
)
L.RunCodeFatal("some_function(({...})[1])", args ...[]any)
Note : Each of the args
(...[]any
) is lua_push*()
'ed, as a suitable Lua type.
Each arg can be retrieved by ({...})[n]
with n >= 1 && < len(args)
.
Note : Above the girl function has no name, and is no-one. The function is a 1st-class variable, which is then pcall()
'ed with the ...[]any
.
You could prettify by prepending the code with some local a, b = select(1, ...)
with some length caveats (if any of the arg is nil
), or with some local args = {...}
.
A new luajit.*
Lua module is exposed to Lua, specific to my module. Do not confure with jit.*
(LuaJIT's one).
-
luajit.exists()
: relativepath black-magic fileops
. -
luajit.readfile()
: ^ same -
luajit.loadfile()
: ^ same -
luajit.stacktrace()
: return the Go stack trace. Used by0b-tools-debug.lua
. To "concat" stack of Lua+Go. -
luajit.set_error_handler()
: same as above. I mean, to set the error handler forpcall()
, includingRun.*()
from Go.
The unspoken path black-magic fileops
is the fact that those will operate relative to the caller. The magicness comes from the fact that if the Lua caller was defined :
- defined in local filesystem path (on your computer) : it will be relative to that.
- defined in a embed.FS : it will be relative to that. (I think, I don't remember)
It means that users of this module can, during development, have some local files (never pushed to git),
and luajit.*()
will mostly find them in the relative path you expect it to do.
Of course, for published modules, you should only use Lua methods exposed on embed.FS
objects.
L.FuncAdd("calls", "go_mem_usage", go_mem_usage)
func go_mem_usage() string {
var stats runtime.MemStats
runtime.ReadMemStats(&stats)
return fmt.Sprintf("%.3f MB", float64(stats.HeapAlloc)/1e6)
}
The black-magicness of the module will take care of passed-in and returned Go arguments.
main() {
ui.L.MethodsAdd("calls", &calls{})
}
type mycalls struct{}
func (mycall *mycalls) Lua_Test(a int, b int, c float32) (int, string) {
fmt.Printf("HEY TEST HAS BEEN CALLED ! %v %v %v\n", a, b, c)
return 42, "quack quack"
}
All mycall
(type struct
)'s methods prefixed with Lua_
will be available in Lua as calls.*
All return arguments will be returned to Lua... : local ret, msg = calls.test()
See here how all Go's embed.FS
are exposed usefully to Lua : https://github.com/folays/luajit-go/blob/master/embed.go
// needed to be able to "overload" embed.FS with some additional Lua_*() Go methods
type embedFS embed.FS
func (L *State) embed_prepare_bridge() {
L.BridgeAdd(embedFS{}) // register *YOUR* Go type
L.BridgeAddShared(embedFS{}, embed.FS{}) // alias *YOUR* Go type, with the Go base type
}
[...]
func (eFS embedFS) Lua_loadfile(L *State, path string) (err error) { [...] }
Here, registering embed.FS
would not be so much useful, because you can't add Go methods on it.
The above let you :
- tell Lua that
embed.FS
can be assumed to being equivalent to YOUR embedFS nearly-alias type - add a Lua
:loadfile(path)
to any exposed-to-Lua embed.FS object
ATTENTION REQUIRED : That's one of the MAIN FACILITATOR of LuaJIT-Go
.
Please re-read again : You can directly expose your Go objects, to Lua, as userdata objects, WITHOUT ANY boilerplate code.
You just need to have some Lua_()
methods on your object, which takes some Go args, and returns some Go args. You don't even have to involve Lua in any form here.
You can pass any Go's embed.FS
as an argument to Lua, which will give you an useful Lua object.
func (L *State) FuncEverywherePass(v any, type_in_index_max int) { ...
The intention here is that, if you have some Go type
that nearly 99% of your exposed Go functions will need,
it can be cumbersome to pass it everytime.
LuaJIT-Go
itself calls L.FuncEverywherePass(L, 0)
so that any Go function called from Lua
needing a *luajit.State
in the first (0
) arg, Lua code can omit it.
And as a consequence, you can also omit to expose it to Lua from Go.
That's only usefull if you can tell that in your whole Lua
's "state" (scope = lua_State *
),
you will only have ONE instance of this type ever created (somehow like a singleton).
json
: https://github.com/rxi/json.luareflect
: https://github.com/corsix/ffi-reflect / http://corsix.github.io/ffi-reflect/
You can require()
those from Lua.
See https://github.com/folays/luajit-go/tree/master/lua-helpers :
- Some
printf
/snprintf
/errorf
/assertf
log
/logf
- Lua
debug.traceback()
andpcall
errors will concat the Go backtrace after the Lua one.
I think I already implemented it.
Want to propose your own additional Go Module
exposing a C library
as a Lua module ?
package yourmodule
import (
"github.com/folays/luajit-go"
)
var (
Luaopen_yourmodule_c = luajit.LuaCFunction(C.luaopen_yourmodule)
Luaopen_yourmodule_go = _luaopen_yourmodule
)
func _luaopen_yourmodule(L *luajit.State) (err error) {
if err = L.Module_preload("yourmodule", Luaopen_yourmodule_c); err != nil {
log.Fatalln(err)
}
return
}
Here above the C function luaopen_yourmodule
is expected to behave like a regular C luaopen_*()
Lua C module.
Any of your users would use your github.com/yourname/yourmodule
as :
package main
import (
"github.com/folays/luajit-go"
"github.com/yourname/yourmodule"
)
func main() {
var L *luajit.State = L = luajit.NewState()
L.ReasonableDefaults()
yourmodule.Luaopen_yourmodule_go(L)
// ... now you *COULD* do in Lua : `local yourmodule = require("yourmodule")` and use it.
}
So it respects the semantics of how-to open Lua module in C, but the mechanism is exposed in Go.
Work has been done to ensure that the calling Go routine backtrace appears concat'ed to Lua backtraces.
I didn't need them, so I did not think of how they would be useful w.r.t. a Go env already ultra parallelized.
- TODO/maybe : expose less "dangerous" functions to the global env
- TODO/maybe : otherwise maybe expose an "easy API" in Go to run sandboxed code in a non-global less-filled table
I put some love in this module. It should work well, and I did some profiling.
I tried to do zero-copy of string
as much as I could do.
- possibly expect some ImGui stuff
- possibly expect some SQLite stuff
BSD 3-clause. I mean, at least for my original work.
Please consider the licences of included parts, including but not limited to :
- LuaJIT itself
*.lua
modules found inlua-modules/
You don't need to locally download luajit-go
. It works as a "normal" Go module.
However, it uses LuaJIT
and Cgo
, so obviously, you will need LuaJIT to be installed somehow,
preferably find'able by #cgo: pkg-config luajit
I guess, try to have pkg-config luajit
working. Do yourself a favor : use LuaJIT's latest commit (probably v2.1
branch).
I would like to advise to install LuaJIT HEAD via homebrew :
brew install luajit --HEAD
HOWEVER, on recent macOS (at least 11.6.2 for me, Intel CPU), LuaJIT needs some special compile flags to work past simple cases, when used by Go ;
See LuaJIT/LuaJIT#285 (comment) and above...
The reason seems to be :
- LuaJIT would like to reserve some "relative jump area" pool of memory (2 GB on x86_64) for JITed code
- MacOS/Intel/Go seems to tight close together everything in virtual memory
- When the Go runtime let us a chance to load LuaJIT, it's already too late : LuaJIT is left with only 3% of the wanted 2 GB pool
That's without doing any work. As soon as you do some real work, those 3% goes down very quickly.
So under those circumstances, LuaJIT will bailout JIT'ing code.
I found it can be alleviated by building LuaJIT yourself :
git clone https://github.com/LuaJIT/LuaJIT.git
git checkout v2.1
export MACOSX_DEPLOYMENT_TARGET=11.0
make -C src/ libluajit.so LDFLAGS="-Wl,-image_base,0x488800000000"
make install
I choose 0x488800000000 hasardly, no specifics here. It's near the top of the 48 bits (~47,5 bits), with enough room before the 48 bits to not constrain the mcode region.
Then, please ensure that LuaJIT is find'able by pkg-config luajit
.
All this to say that, whatever Go Module (this one or another) you would import to use LuaJIT in Go, chances are that you would need to anyway specially-build LuaJIT as shown above.
P.S. : The above was for Intel Mac. For ARM Mac, see LuaJIT/LuaJIT#285, people are also discussing patch(es).