Commit 349bc47e authored by Marek Vavruša's avatar Marek Vavruša

modules: support for modules in Go (needs golang 1.5+)

parent c960c3b4
......@@ -21,12 +21,15 @@ $(eval $(call find_alt,lua,luajit))
$(eval $(call find_lib,cmocka))
$(eval $(call find_bin,doxygen))
$(eval $(call find_bin,sphinx-build))
$(eval $(call find_bin,gccgo))
$(eval $(call find_bin,python))
$(eval $(call find_lib,libmemcached,1.0))
$(eval $(call find_lib,hiredis))
$(eval $(call find_lib,socket_wrapper))
$(eval $(call find_lib,libdnssec))
# Find Go compiler version
E :=
GO_VERSION := $(subst $(E) $(E),,$(subst go,,$(wordlist 1,3,$(subst ., ,$(word 3,$(shell $(GO) version))))))
$(eval $(call find_ver,go,$(GO_VERSION),150))
# Work around luajit on OS X
ifeq ($(PLATFORM), Darwin)
......
......@@ -41,6 +41,7 @@ There are also *optional* packages that enable specific functionality in Knot DN
"libmemcached_", "``modules/memcached``", "To build memcached backend module."
"hiredis_", "``modules/redis``", "To build redis backend module."
"Go_ 1.5+", "``modules``", "Build modules written in Go."
"cmocka_", "``unit tests``", "Unit testing framework."
"Python_", "``integration tests``", "For test scripts."
"Doxygen_", "``documentation``", "Generating API documentation."
......@@ -134,7 +135,7 @@ The project can be built with code coverage tracking using the ``COVERAGE=1`` va
.. _Lua: http://www.lua.org/about.html
.. _LuaJIT: http://luajit.org/luajit.html
.. _GCCGO: https://golang.org/doc/install/gccgo
.. _Go: https://golang.org
.. _libmemcached: http://libmemcached.org/libMemcached.html
.. _hiredis: https://github.com/redis/hiredis
.. _Doxygen: http://www.stack.nl/~dimitri/doxygen/manual/index.html
......
......@@ -18,7 +18,7 @@ info:
$(info Optional)
$(info --------)
$(info [$(HAS_doxygen)] doxygen (doc))
$(info [$(HAS_gccgo)] GCCGO (modules/go))
$(info [$(HAS_go)] Go (modules/go))
$(info [$(HAS_libmemcached)] libmemcached (modules/memcached))
$(info [$(HAS_hiredis)] hiredis (modules/redis))
$(info [$(HAS_cmocka)] cmocka (tests/unit))
......
......@@ -121,54 +121,6 @@ static int load_sym_c(struct kr_module *module, uint32_t api_required)
return kr_ok();
}
/** Bootstrap Go runtime from module. */
static int bootstrap_libgo(struct kr_module *module)
{
/* Check if linked against compatible libgo */
void (*go_check)(void) = dlsym(module->lib, "runtime_check");
void (*go_args)(int, void*) = dlsym(module->lib, "runtime_args");
void (*go_init_os)(void) = dlsym(module->lib, "runtime_osinit");
void (*go_init_sched)(void) = dlsym(module->lib, "runtime_schedinit");
void (*go_init_main)(void) = dlsym(module->lib, "__go_init_main");
if ((go_check && go_args && go_init_os && go_init_sched && go_init_main) == false) {
return kr_error(EINVAL);
}
/*
* Bootstrap runtime - this is minimal runtime, we would need a running scheduler
* and gc for coroutines and memory allocation. That would require a custom "world loop",
* message passing, and either runtime sharing or module isolation.
* https://github.com/gcc-mirror/gcc/blob/gcc-4_9_2-release/libgo/runtime/proc.c#L457
*/
char *fake_argv[2] = {
getenv("_"),
NULL
};
go_check();
go_args(1, fake_argv);
go_init_os();
go_init_sched();
go_init_main();
return kr_ok();
}
/** Load Go module symbols. */
static int load_ffi_go(struct kr_module *module, uint32_t api_required)
{
/* Bootstrap libgo */
int ret = bootstrap_libgo(module);
if (ret != 0) {
return ret;
}
/* Enforced prefix for now. */
const char *module_prefix = "main.";
ABI_CHECK(module, module_prefix, "Api", api_required);
ABI_LOAD(module, module_prefix, "Init", "Deinit", "Config", "Layer", "Props");
return kr_ok();
}
int kr_module_load(struct kr_module *module, const char *name, const char *path)
{
if (module == NULL || name == NULL) {
......@@ -197,11 +149,6 @@ int kr_module_load(struct kr_module *module, const char *name, const char *path)
/* Try to load module ABI. */
int ret = load_sym_c(module, KR_MODULE_API);
if (ret != 0 && module->lib != RTLD_DEFAULT) {
ret = load_ffi_go(module, KR_MODULE_API);
}
/* Module constructor. */
if (ret == 0 && module->init) {
ret = module->init(module);
}
......
......@@ -11,11 +11,8 @@ Modules API reference
Supported languages
===================
Currently modules written in C and Lua are supported.
There is also a rudimentary support for writing modules in Go |---|
(1) the library has no native Go bindings, library is accessible using CGO_,
(2) gc doesn't support building shared libraries, GCCGO_ is required,
(3) no coroutines and no garbage collecting thread, as the Go code is called from C threads.
Currently modules written in C and LuaJIT are supported.
There is also a support for writing modules in Go 1.5+ |---| the library has no native Go bindings, library is accessible using CGO_.
The anatomy of an extension
===========================
......@@ -25,14 +22,14 @@ A module is a shared object or script defining specific functions, here's an ove
*Note* |---| the :ref:`Modules <lib_api_modules>` header documents the module loading and API.
.. csv-table::
:header: "C", "Lua", "Go", "Params", "Comment"
:header: "C/Go", "Lua", "Params", "Comment"
"``X_api()`` [#]_", "", "``Api()``", "", "API version"
"``X_init()``", "``X.init()``", "``Init()``", "``module``", "Constructor"
"``X_deinit()``", "``X.deinit()``", "``Deinit()``", "``module, key``", "Destructor"
"``X_config()``", "``X.config()``", "``Config()``", "``module``", "Configuration"
"``X_layer()``", "``X.layer``", "``Layer()``", "``module``", ":ref:`Module layer <lib-layers>`"
"``X_props()``", "", "``Props()``", "", "List of properties"
"``X_api()`` [#]_", "", "", "API version"
"``X_init()``", "``X.init()``", "``module``", "Constructor"
"``X_deinit()``", "``X.deinit()``", "``module, key``", "Destructor"
"``X_config()``", "``X.config()``", "``module``", "Configuration"
"``X_layer()``", "``X.layer``", "``module``", ":ref:`Module layer <lib-layers>`"
"``X_props()``", "", "", "List of properties"
.. [#] Mandatory symbol.
......@@ -181,9 +178,7 @@ and publish data about query resolution.
Writing a module in Go
======================
.. note:: At the moment only a limited subset of Go is supported. The reason is that the Go functions must run inside the goroutines, and *presume* the garbage collector and scheduler are running in the background. `GCCGO`_ compiler can build dynamic libraries, and also allow us to bootstrap basic Go runtime, including a trampoline to call Go functions. The problem with the ``layer()`` and callbacks is that they're called from C threads, that Go runtime has no knowledge of. Thus neither garbage collection or spawning routines can work. The solution could be to register C threads to Go runtime, or have each module to run inside its world loop and use IPC instead of callbacks |---| alas neither is implemented at the moment, but may be in the future.
The Go modules also use CGO_ to interface C resolver library, and to declare layers with function pointers, which are `not present in Go`_. Each module must be the ``main`` package, here's a minimal example:
The Go modules use CGO_ to interface C resolver library, there are no native bindings yet. Second issue is that layers are declared as a structure of function pointers, which are `not present in Go`_, the workaround is to declare them in CGO_ header. Each module must be the ``main`` package, here's a minimal example:
.. code-block:: go
......@@ -195,10 +190,16 @@ The Go modules also use CGO_ to interface C resolver library, and to declare lay
import "C"
import "unsafe"
func Api() C.uint32_t {
//export mymodule_api
func mymodule_api() C.uint32_t {
return C.KR_MODULE_API
}
// Mandatory function
func main() {}
.. warning:: Do not forget to prefix function declarations with ``//export symbol_name``, as only these will be exported in module.
In order to integrate with query processing, you have to declare a helper function with function pointers to the
the layer implementation. Since the code prefacing ``import "C"`` is expanded in headers, you need the `static inline` trick
to avoid multiple declarations. Here's how the preface looks like:
......@@ -206,45 +207,37 @@ to avoid multiple declarations. Here's how the preface looks like:
.. code-block:: go
/*
#include "lib/layer.h"
#include "lib/module.h"
#include "lib/layer.h"
//! Trampoline for Go callbacks, note that this is going to work
//! with ELF only, this is hopefully going to change in the future
extern int Begin(knot_layer_t *, void *) __asm__ ("main.Begin");
extern int Finish(knot_layer_t *) __asm__ ("main.Finish");
static inline const knot_layer_api_t *_gostats_layer(void)
// Need a forward declaration of the function signature
int finish(knot_layer_t *);
// Workaround for layers composition
static inline const knot_layer_api_t *_layer(void)
{
static const knot_layer_api_t api = {
.begin = &Begin,
.finish = &Finish
.finish = &finish
};
return &api;
}
*/
import "C"
import "unsafe"
import "fmt"
Now we can add the implementations for the ``Begin`` and ``Finish`` functions, and finalize the module:
Now we can add the implementations for the ``finish`` layer and finalize the module:
.. code-block:: go
func Begin(ctx *C.knot_layer_t, param unsafe.Pointer) C.int {
// Save the context
ctx.data = param
return 0
}
func Finish(ctx *C.knot_layer_t) C.int {
//export finish
func finish(ctx *C.knot_layer_t) C.int {
// Since the context is unsafe.Pointer, we need to cast it
var param *C.struct_kr_request = (*C.struct_kr_request)(ctx.data)
// Now we can use the C API as well
fmt.Printf("[go] resolved %d queries", C.list_size(&param.rplan.resolved))
fmt.Printf("[go] resolved %d queries\n", C.list_size(&param.rplan.resolved))
return 0
}
func Layer(module *C.struct_kr_module) *C.knot_layer_api_t {
//export mymodule_layer
func mymodule_layer(module *C.struct_kr_module) *C.knot_layer_api_t {
// Wrapping the inline trampoline function
return C._layer()
}
......@@ -330,6 +323,5 @@ regular tables.
.. _`not present in Go`: http://blog.golang.org/gos-declaration-syntax
.. _CGO: http://golang.org/cmd/cgo/
.. _GCCGO: https://golang.org/doc/install/gccgo
.. |---| unicode:: U+02014 .. em dash
\ No newline at end of file
......@@ -3,13 +3,13 @@ package main
/*
#include "lib/layer.h"
#include "lib/module.h"
extern int Begin(knot_layer_t *, void *) __asm__ ("main.Begin");
extern int Finish(knot_layer_t *) __asm__ ("main.Finish");
int begin(knot_layer_t *, void *);
int finish(knot_layer_t *);
static inline const knot_layer_api_t *_layer(void)
{
static const knot_layer_api_t api = {
.begin = &Begin,
.finish = &Finish
.begin = &begin,
.finish = &finish
};
return &api;
}
......@@ -18,29 +18,37 @@ import "C"
import "unsafe"
import "fmt"
func Api() C.uint32_t {
//export gostats_api
func gostats_api() C.uint32_t {
return C.KR_MODULE_API
}
func Init(module *C.struct_kr_module) C.int {
//export gostats_init
func gostats_init(module *C.struct_kr_module) int {
return 0
}
func Deinit(module *C.struct_kr_module) C.int {
//export gostats_deinit
func gostats_deinit(module *C.struct_kr_module) int {
return 0
}
func Begin(ctx *C.knot_layer_t, param unsafe.Pointer) C.int {
//export begin
func begin(ctx *C.knot_layer_t, param unsafe.Pointer) C.int {
ctx.data = param
return 0
}
func Finish(ctx *C.knot_layer_t) C.int {
//export finish
func finish(ctx *C.knot_layer_t) C.int {
var param *C.struct_kr_request = (*C.struct_kr_request)(ctx.data)
fmt.Printf("[gostats] resolved %d queries", C.list_size(&param.rplan.resolved))
fmt.Printf("[gostats] resolved %d queries\n", C.list_size(&param.rplan.resolved))
return 0
}
func Layer(module *C.struct_kr_module) *C.knot_layer_api_t {
//export gostats_layer
func gostats_layer(module *C.struct_kr_module) *C.knot_layer_api_t {
return C._layer()
}
func main() {}
\ No newline at end of file
......@@ -22,7 +22,7 @@ modules_TARGETS += ketcd \
endif
# List of Golang modules
ifeq ($(HAS_gccgo),yes)
ifeq ($(HAS_go),yes)
modules_TARGETS += gostats
endif
......@@ -54,17 +54,10 @@ endef
# Go target definition
define go_target
$(1) := $(2)/$(1)$(LIBEXT)
$(1)_OBJS := $(addprefix $(2)/_obj/,_cgo_defun.c _cgo_export.c $(subst /,_,$(2))_$(1).cgo2.c)
$(1)_GOBJS := $(addprefix $(2)/_obj/,_cgo_gotypes.go $(subst /,_,$(2))_$(1).cgo1.go)
$(2)/_obj/_cgo_export.h: $$($(1)_SOURCES)
@$(INSTALL) -d $(2)/_obj
$(call quiet,CGO,$$^) -gccgo=true -objdir=$(2)/_obj -- $(BUILD_CFLAGS) $$^
$(2)/$(1).o: $(2)/_obj/_cgo_export.h
$(call quiet,GCCGO,$$@) -I$(2)/_obj -c -fPIC $$($(1)_GOBJS) -o $$@
$(2)/$(1)$(LIBEXT): $(2)/$(1).o $$($(1)_DEPEND)
$(call quiet,GCCGO,$$@) -g -fPIC $(BUILD_CFLAGS) -I$(2)/_obj $(2)/$(1).o $$($(1)_OBJS) -o $$@ -$(LIBTYPE) -lgcc -lgo $$($(1)_LIBS)
$(2)/$(1)$(LIBEXT): $$($(1)_SOURCES) $$($(1)_DEPEND)
@echo " GO $(2)"; CGO_CFLAGS="$(BUILD_CFLAGS)" CGO_LDFLAGS="$$($(1)_LIBS)" $(GO) build -buildmode=c-shared -o $$@ $$($(1)_SOURCES)
$(1)-clean:
$(RM) -r $(2)/_obj $(2)/$(1)$(LIBEXT)
$(RM) -r $(2)/$(1).h $(2)/$(1)$(LIBEXT)
$(1)-install: $(2)/$(1)$(LIBEXT)
$(INSTALL) -d $(PREFIX)/$(MODULEDIR)
$(INSTALL) $$^ $(PREFIX)/$(MODULEDIR)
......
# Platform-specific
CCLD := $(CC)
CGO := go tool cgo
GCCGO := gccgo
GO := go
LIBEXT := .so
MODEXT := $(LIBEXT)
AREXT := .a
......@@ -71,7 +71,7 @@ endif
endef
# Make targets (name,path)
make_bin = $(call make_target,$(1),$(2),$(BINEXT),,$(BINDIR))
make_bin = $(call make_target,$(1),$(2),$(BINEXT),$(BINFLAGS),$(BINDIR))
make_lib = $(call make_target,$(1),$(2),$(LIBEXT),-$(LIBTYPE),$(LIBDIR))
make_module = $(call make_target,$(1),$(2),$(LIBEXT),-$(LIBTYPE),$(MODULEDIR))
make_shared = $(call make_target,$(1),$(2),$(MODEXT),-$(MODTYPE),$(LIBDIR))
......@@ -117,3 +117,13 @@ define find_bin
$(1) := $$($(1)_BIN)
endif
endef
# Find version
define find_ver
ifeq ($(shell test $(2) -gt $(3); echo $$?),0)
HAS_$(1) := yes
else
HAS_$(1) := no
endif
endef
/* Copyright (C) 2015 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "lib/module.h"
/* Fake libgo */
void runtime_check(void) {}
void runtime_args(int argc, void *argv) {}
void runtime_osinit(void) {}
void runtime_schedinit(void) {}
void __go_init_main() {}
/*
* No module implementation.
*/
/* @note Renamed to mimick Go module. */
#if defined(__APPLE__)
extern uint32_t Api(void) __asm__ ("_main.Api"); /* Mach-O */
#elif _WIN32
#error DLL format is not supported for Golang modules.
#else
extern uint32_t Api(void) __asm__ ("main.Api"); /* ELF */
#endif
uint32_t Api(void)
{
return KR_MODULE_API - 1; /* Bad version */
}
\ No newline at end of file
......@@ -39,20 +39,12 @@ static void test_module_c(void **state)
kr_module_unload(&module);
}
static void test_module_go(void **state)
{
/* Mock Go module fails on version check. */
struct kr_module module;
assert_int_equal(kr_module_load(&module, "mock_gomodule", "tests"), kr_error(ENOTSUP));
}
int main(void)
{
const UnitTest tests[] = {
unit_test(test_module_params),
unit_test(test_module_builtin),
unit_test(test_module_c),
unit_test(test_module_go)
};
return run_tests(tests);
......
......@@ -16,8 +16,6 @@ tests_BIN := \
mock_cmodule_SOURCES := tests/mock_cmodule.c
$(eval $(call make_lib,mock_cmodule,tests))
mock_gomodule_SOURCES := tests/mock_gomodule.c
$(eval $(call make_lib,mock_gomodule,tests))
# Dependencies
tests_DEPEND := $(libkres) $(mock_cmodule) $(mock_gomodule)
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment