Tarantool development patches archive
 help / color / mirror / Atom feed
* [Tarantool-patches] [PATCH v2] luajit: proxy -j and -b flags
@ 2021-09-14 17:56 Maxim Kokryashkin via Tarantool-patches
  2021-12-08  7:36 ` Sergey Kaplun via Tarantool-patches
  0 siblings, 1 reply; 2+ messages in thread
From: Maxim Kokryashkin via Tarantool-patches @ 2021-09-14 17:56 UTC (permalink / raw)
  To: tarantool-patches, imun, skaplun

There are two flags in the LuaJIT useful for debugging purposes: `-j`
and `-b`. However, if you want to check the same Lua code from the
Tarantool, you will need to make some adjustments in the script itself,
as those flags are not present in the Tarantool's CLI.

This patch introduces those flags to the Tarantool, so debugging is
much more convenient now. Flags are working the same as they do in
LuaJIT.

Closes #5541
---
GitHub branch:
https://github.com/tarantool/tarantool/tree/fckxorg/gh-5541-proxy-luajit-flags

Issues:
https://github.com/tarantool/tarantool/issues/5541

Q: Why do we need fiber_sleep here?
Please, drop a comment.

> + aux_loop_is_run = true;
> + for (argv++; *argv != NULL; narg++, argv++)
> + lua_pushstring(L, *argv);
> + int res = lua_pcall(L, narg, 0, 0);
> + if(res)
> + goto error;
> +end:
> + if (!aux_loop_is_run)
> + fiber_sleep(0.0);
A:That chunk of code is taken almost verbatim from the `run_script_f()`
as I need to achieve almost the same thing. So the first
`fiber_sleep()` is needed, because `tarantool_lua_dump_bytecode()`
needs to start auxiliary event loop and re-schedule this fiber. The
second `fiber_sleep()` is needed because event loop should be started
before the fiber calls the `ev_break()`.

Q: IINM, bcsave module don't run a user's code, just precompile it to the
bytecode. Do we need a separate fiber for it?
A: Yes, we need it since tarantool has it's own definition of os.exit().
If you call os.exit() when running lua code outside of any fiber, you will
get a panic. Since there is no easy way to redefine it temporarily and
not break anything, I decided to stick with execution inside a fiber.

 src/lua/init.c | 185 +++++++++++++++++++++++++++++++++++++++++++++++++
 src/lua/init.h |   2 +
 src/main.cc    | 113 ++++++++++++++++++------------
 3 files changed, 255 insertions(+), 45 deletions(-)

diff --git a/src/lua/init.c b/src/lua/init.c
index f9738025d..54d07926a 100644
--- a/src/lua/init.c
+++ b/src/lua/init.c
@@ -313,6 +313,154 @@ skip:		base = (base == -1 ? 10 : base);
 
 /* }}} */
 
+static void l_message(const char *msg)
+{
+        printf("%s\n", msg);
+        fflush(stdout);
+}
+
+/**
+ * This function checks if there is an error,
+ * and outputs error message corresponding to the
+ * error object on the top of the Lua stack,
+ * if there is one,
+ *
+ * This function pops the error message from the Lua stack
+ */
+static int report(lua_State *L, int status)
+{
+        if (status && !lua_isnil(L, -1)) {
+                const char *msg = lua_tostring(L, -1);
+                if (msg == NULL)
+                        msg = "(error object is not a string)";
+                l_message(msg);
+                lua_pop(L, 1);
+        }
+        return status;
+}
+
+/**
+ * Load add-on module. This function has no
+ * effects on the Lua stack.
+ */
+static int loadjitmodule(lua_State* L)
+{
+        lua_getglobal(L, "require");
+        lua_pushliteral(L, "jit.");
+        lua_pushvalue(L, -3);
+        lua_concat(L, 2);
+        if (lua_pcall(L, 1, 1, 0)) {
+                const char *msg = lua_tostring(L, -1);
+                if (msg && !strncmp(msg, "module ", 7))
+                        goto nomodule;
+                return report(L, 1);
+        }
+        lua_getfield(L, -1, "start");
+        if (lua_isnil(L, -1)) {
+        nomodule:
+                l_message("unknown luaJIT command or jit.* modules not installed");
+                return 1;
+        }
+        /* Drop module table. */
+        lua_remove(L, -2);
+        return 0;
+}
+/**
+ * This function dump bytecode for Lua script.
+ * Has no effect on the Lua stack
+ */
+int dobytecode(va_list ap)
+{
+        char **argv = va_arg(ap, char **);
+        struct diag *diag = va_arg(ap, struct diag *);
+
+        int narg = 0;
+        bool aux_loop_is_run = false;
+
+        lua_pushliteral(tarantool_L, "bcsave");
+        if (loadjitmodule(tarantool_L))
+                goto error;
+        if (argv[0][2]) {
+                narg++;
+                argv[0][1] = '-';
+                lua_pushstring(tarantool_L, argv[0] + 1);
+        }
+
+        fiber_sleep(0.0);
+        aux_loop_is_run = true;
+
+        for (argv++; *argv != NULL; narg++, argv++)
+                lua_pushstring(tarantool_L, *argv);
+        int res = lua_pcall(tarantool_L, narg, 0, 0);
+        if (res != LUA_OK)
+                goto error;
+        end:
+        if (!aux_loop_is_run)
+                fiber_sleep(0.0);
+	ev_break(loop(), EVBREAK_ALL);
+	return 0;
+
+        error:
+        diag_move(diag_get(), diag);
+        goto end;
+}
+
+/**
+ * Run command with options.
+ * Has no effect on the Lua stack.
+ */
+static int runcmdopt(lua_State *L, const char *opt)
+{
+        int narg = 0;
+        if (opt && *opt) {
+                for (;;) {
+                        const char *p = strchr(opt, ',');
+                        narg++;
+                        if (!p) break;
+                        if (p == opt)
+                                lua_pushnil(L);
+                        else
+                                lua_pushlstring(L, opt, (size_t)(p - opt));
+                        opt = p + 1;
+                }
+                if (*opt)
+                        lua_pushstring(L, opt);
+                else
+                        lua_pushnil(L);
+        }
+        return report(L, lua_pcall(L, narg, 0, 0));
+}
+
+/**
+ * JIT engine control command: try jit
+ * library first or load add-on module.
+ * Has no effect on the Lua stack.
+ */
+int dojitcmd(const char *cmd)
+{
+        const char *opt = strchr(cmd, '=');
+        lua_pushlstring(tarantool_L, cmd, opt ? (size_t)(opt - cmd) : strlen(cmd));
+        lua_getfield(tarantool_L, LUA_REGISTRYINDEX, "_LOADED");
+        /* Get jit.* module table. */
+        lua_getfield(tarantool_L, -1, "jit");
+        lua_remove(tarantool_L, -2);
+        lua_pushvalue(tarantool_L, -2);
+        /* Lookup library function. */
+        lua_gettable(tarantool_L, -2);
+        if (!lua_isfunction(tarantool_L, -1)) {
+                /* Drop non-function and jit.* table, keep module name. */
+                lua_pop(tarantool_L, 2);
+        if (loadjitmodule(tarantool_L))
+                return 1;
+        } else {
+                /* Drop jit.* table. */
+                lua_remove(tarantool_L, -2);
+        }
+        /* Drop module name. */
+        lua_remove(tarantool_L, -2);
+        return runcmdopt(tarantool_L, opt ? opt + 1 : opt);
+}
+
 /**
  * Original LuaJIT/Lua logic: <luajit/src/lib_package.c - function setpath>
  *
@@ -608,6 +756,10 @@ run_script_f(va_list ap)
 			lua_setglobal(L, optv[i + 1]);
 			lua_settop(L, 0);
 			break;
+                case 'j':
+                        if (dojitcmd(optv[i + 1]) != 0)
+                                goto error;
+                        break;
 		case 'e':
 			/*
 			 * Execute chunk
@@ -747,6 +899,39 @@ tarantool_lua_run_script(char *path, bool interactive,
 	return diag_is_empty(diag_get()) ? 0 : -1;
 }
 
+int
+tarantool_lua_dump_bytecode(char **argv) {
+	script_fiber = fiber_new("bcsave", dobytecode);
+	if (script_fiber == NULL)
+		panic("%s", diag_last_error(diag_get())->errmsg);
+	script_fiber->storage.lua.stack = tarantool_L;
+	/*
+	 * Create a new diag on the stack. Don't pass fiber's diag, because it
+	 * might be overwritten by libev callbacks invoked in the scheduler
+	 * fiber (which is this), and therefore can't be used as a sign of fail
+	 * in the script itself.
+	 */
+	struct diag bc_diag;
+	diag_create(&bc_diag);
+	fiber_start(script_fiber, argv, &bc_diag);
+	/*
+	 * Run an auxiliary event loop to re-schedule run_script fiber.
+	 * When this fiber finishes, it will call ev_break to stop the loop.
+	 */
+	if (start_loop)
+		ev_run(loop(), 0);
+	/* The fiber running the startup script has ended. */
+	script_fiber = NULL;
+	diag_move(&bc_diag, diag_get());
+	diag_destroy(&bc_diag);
+	/*
+	 * Result can't be obtained via fiber_join - script fiber
+	 * never dies if os.exit() was called. This is why diag
+	 * is checked explicitly.
+	 */
+	return diag_is_empty(diag_get()) ? 0 : -1;
+}
+
 void
 tarantool_lua_free()
 {
diff --git a/src/lua/init.h b/src/lua/init.h
index 7fc0b1a31..3c9489468 100644
--- a/src/lua/init.h
+++ b/src/lua/init.h
@@ -74,6 +74,8 @@ int
 tarantool_lua_run_script(char *path, bool force_interactive,
 			 int optc, const char **optv,
 			 int argc, char **argv);
+int
+tarantool_lua_dump_bytecode(char **argv);
 
 extern char *history;
 
diff --git a/src/main.cc b/src/main.cc
index 91ed79fab..65248e655 100644
--- a/src/main.cc
+++ b/src/main.cc
@@ -576,6 +576,8 @@ print_help(const char *program)
 	puts("  -v, --version\t\t\tprint program version and exit");
 	puts("  -e EXPR\t\t\texecute string 'EXPR'");
 	puts("  -l NAME\t\t\trequire library 'NAME'");
+	puts("  -j CMD \t\t\tperform LuaJIT control command");
+	puts("  -b[options] input output\tsave LuaJIT bytecode");
 	puts("  -i\t\t\t\tenter interactive mode after executing 'SCRIPT'");
 	puts("  --\t\t\t\tstop handling options");
 	puts("  -\t\t\t\texecute stdin and stop handling options");
@@ -584,6 +586,12 @@ print_help(const char *program)
 	puts("to see online documentation, submit bugs or contribute a patch.");
 }
 
+#define O_INTERACTIVE 0x1
+#define O_BYTECODE    0x2
+
+extern "C" void **
+export_syms(void);
+
 int
 main(int argc, char **argv)
 {
@@ -595,7 +603,7 @@ main(int argc, char **argv)
 	fpconv_check();
 
 	/* Enter interactive mode after executing 'script' */
-	bool interactive = false;
+        uint32_t opt_mask = 0;
 	/* Lua interpeter options, e.g. -e and -l */
 	int optc = 0;
 	const char **optv = NULL;
@@ -606,38 +614,47 @@ main(int argc, char **argv)
 		{"version", no_argument, 0, 'v'},
 		{NULL, 0, 0, 0},
 	};
-	static const char *opts = "+hVvie:l:";
+	static const char *opts = "+hVvbij:e:l:";
 
 	int ch;
+        bool lj_arg = false;
 	while ((ch = getopt_long(argc, argv, opts, longopts, NULL)) != -1) {
-		switch (ch) {
-		case 'V':
-		case 'v':
-			print_version();
-			return 0;
-		case 'h':
-			print_help(basename(argv[0]));
-			return 0;
-		case 'i':
-			/* Force interactive mode */
-			interactive = true;
-			break;
-		case 'l':
-		case 'e':
-			/* Save Lua interepter options to optv as is */
-			if (optc == 0) {
-				optv = (const char **) calloc(argc,
-							      sizeof(optv[0]));
-				if (optv == NULL)
-					panic_syserror("No enough memory for arguments");
-			}
-			optv[optc++] = ch == 'l' ? "-l" : "-e";
-			optv[optc++] = optarg;
-			break;
-		default:
-			/* "invalid option" is printed by getopt */
-			return EX_USAGE;
-		}
+                switch (ch) {
+                        case 'V':
+                        case 'v':
+                                print_version();
+                                return 0;
+                        case 'h':
+                                print_help(basename(argv[0]));
+                                return 0;
+                        case 'i':
+                                /* Force interactive mode */
+                                opt_mask |= O_INTERACTIVE;
+                                break;
+                        case 'b':
+                                opt_mask |= O_BYTECODE;
+                                lj_arg = true;
+                                break;
+                        case 'j':
+                        case 'l':
+                        case 'e':
+                                /* Save Lua interepter options to optv as is */
+                                if (optc == 0) {
+                                        optv = (const char **) calloc(argc, sizeof(optv[0]));
+                                        if (optv == NULL)
+                                                panic_syserror("No enough memory for arguments");
+                                }
+                                if(ch == 'l') optv[optc++] = "-l";
+                                else if(ch == 'j') optv[optc++] = "-j";
+                                else optv[optc++] = "-e";
+                                optv[optc++] = optarg;
+                                break;
+                        default:
+                                /* "invalid option" is printed by getopt */
+                                return EX_USAGE;
+                }
+
+                if(lj_arg) break;
 	}
 
 	/* Shift arguments */
@@ -645,19 +662,21 @@ main(int argc, char **argv)
 	for (int i = 1; i < argc; i++)
 		argv[i] = argv[optind + i - 1];
 
-	if (argc > 1 && strcmp(argv[1], "-") && access(argv[1], R_OK) != 0) {
-		/*
-		 * Somebody made a mistake in the file
-		 * name. Be nice: open the file to set
-		 * errno.
-		 */
-		int fd = open(argv[1], O_RDONLY);
-		int save_errno = errno;
-		if (fd >= 0)
-			close(fd);
-		printf("Can't open script %s: %s\n", argv[1], strerror(save_errno));
-		return save_errno;
-	}
+        if(!(opt_mask & O_BYTECODE)) {
+                if (argc > 1 && strcmp(argv[1], "-") && access(argv[1], R_OK) != 0) {
+                        /*
+                        * Somebody made a mistake in the file
+                        * name. Be nice: open the file to set
+                        * errno.
+                        */
+                        int fd = open(argv[1], O_RDONLY);
+                        int save_errno = errno;
+                        if (fd >= 0)
+                                close(fd);
+                        printf("Can't open script %s: %s\n", argv[1], strerror(save_errno));
+                                return save_errno;
+                }
+        }
 
 	argv = title_init(argc, argv);
 	/*
@@ -747,13 +766,17 @@ main(int argc, char **argv)
 			panic("%s", "can't init event loop");
 
 		int events = ev_activecnt(loop());
-		/*
+
+                if(opt_mask & O_BYTECODE) {
+                        return tarantool_lua_dump_bytecode(argv);
+                }
+                /*
 		 * Load user init script.  The script should have access
 		 * to Tarantool Lua API (box.cfg, box.fiber, etc...) that
 		 * is why script must run only after the server was fully
 		 * initialized.
 		 */
-		if (tarantool_lua_run_script(script, interactive, optc, optv,
+		if (tarantool_lua_run_script(script, opt_mask & O_INTERACTIVE, optc, optv,
 					     main_argc, main_argv) != 0)
 			diag_raise();
 		/*
-- 
2.33.0


^ permalink raw reply	[flat|nested] 2+ messages in thread

end of thread, other threads:[~2021-12-08  7:38 UTC | newest]

Thread overview: 2+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2021-09-14 17:56 [Tarantool-patches] [PATCH v2] luajit: proxy -j and -b flags Maxim Kokryashkin via Tarantool-patches
2021-12-08  7:36 ` Sergey Kaplun via Tarantool-patches

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox