From 349bc47ef2a88d93f86531f7d7fd898c95daad27 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Marek=20Vavru=C5=A1a?= <marek.vavrusa@nic.cz>
Date: Mon, 19 Oct 2015 19:41:03 +0200
Subject: [PATCH] modules: support for modules in Go (needs golang 1.5+)

---
 Makefile                   |  5 ++-
 doc/build.rst              |  3 +-
 help.mk                    |  2 +-
 lib/module.c               | 53 ------------------------------
 modules/README.rst         | 66 +++++++++++++++++---------------------
 modules/gostats/gostats.go | 30 ++++++++++-------
 modules/modules.mk         | 15 +++------
 platform.mk                | 14 ++++++--
 tests/mock_gomodule.c      | 44 -------------------------
 tests/test_module.c        |  8 -----
 tests/unit.mk              |  2 --
 11 files changed, 71 insertions(+), 171 deletions(-)
 delete mode 100644 tests/mock_gomodule.c

diff --git a/Makefile b/Makefile
index ea55233d7..0797fee85 100644
--- a/Makefile
+++ b/Makefile
@@ -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)
diff --git a/doc/build.rst b/doc/build.rst
index cf833cbd5..a2b02c316 100644
--- a/doc/build.rst
+++ b/doc/build.rst
@@ -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
diff --git a/help.mk b/help.mk
index 416903741..096faeaae 100644
--- a/help.mk
+++ b/help.mk
@@ -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))
diff --git a/lib/module.c b/lib/module.c
index 071394940..6b4316247 100644
--- a/lib/module.c
+++ b/lib/module.c
@@ -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);
 	}
diff --git a/modules/README.rst b/modules/README.rst
index 47b1435b9..8cfb97341 100644
--- a/modules/README.rst
+++ b/modules/README.rst
@@ -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
diff --git a/modules/gostats/gostats.go b/modules/gostats/gostats.go
index 92fd2ebd4..c52a3b82f 100644
--- a/modules/gostats/gostats.go
+++ b/modules/gostats/gostats.go
@@ -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
diff --git a/modules/modules.mk b/modules/modules.mk
index 55ddaaecc..8b7a1652f 100644
--- a/modules/modules.mk
+++ b/modules/modules.mk
@@ -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)
diff --git a/platform.mk b/platform.mk
index ef46719b2..be3ce282c 100644
--- a/platform.mk
+++ b/platform.mk
@@ -1,7 +1,7 @@
 # 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
+
diff --git a/tests/mock_gomodule.c b/tests/mock_gomodule.c
deleted file mode 100644
index c77ea93a1..000000000
--- a/tests/mock_gomodule.c
+++ /dev/null
@@ -1,44 +0,0 @@
-/*  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
diff --git a/tests/test_module.c b/tests/test_module.c
index 031000e96..cd00da3ca 100644
--- a/tests/test_module.c
+++ b/tests/test_module.c
@@ -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);
diff --git a/tests/unit.mk b/tests/unit.mk
index 6893c5b96..98967130b 100644
--- a/tests/unit.mk
+++ b/tests/unit.mk
@@ -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)
-- 
GitLab