lua-users home
lua-l archive

[Date Prev][Date Next][Thread Prev][Thread Next] [Date Index] [Thread Index]


I have added library preloading support to luac (Lua compiler) with the 
attached patch.  It works like luac.lua [1], except it works on systems with 
different bit sizes then x86 (it hacks the bytecode and has the sizes 
hardcoded).

Also this patch uses 'require' to find & preload Lua modules (not C-modules 
only pure Lua modules).

The command line options are a bit different from luac.lua [1], here is an 
example:
luac -L a.lua -L b -L sub-folder/c.lua x.lua y.lua

That command will compile all 5 lua scripts into a single Lua bytecode 
file 'luac.out'.
a.lua, lua module "b" & sub-folder/c.lua are compiled & preloaded 
into "package.preload", which can then be required as a, b, or c from x.lua & 
y.lua.  Module "b" would need to be somewhere in the search path for Lua 
modules.

Note that the "-L" option accepts Lua module names or file names.  The patch 
adds support to the require() function to allow preloading Lua modules 
without running them.  luac uses this preload feature to search for Lua 
modules that it needs to preload.

The patch is only needed for luac, the compiled bytecode file can still run on 
a normal lua interpreter that doesn't have this patch.

Here is what the compiled Lua code from the above command would look like:
local t=package.preload
t[a] = function(...) print("hello from a") end -- preload of a.lua
t[b] = function(...) print("hello from b") end -- preload of b.lua
t[c] = function(...) print("hello from c") end -- preload of c.lua
(function(...) require("a"); require("b") end) () -- execute x.lua
(function(...) require("c") end) () -- execute y.lua

That is basically the same as what luac.lua [1] generates.

The change to require() adds a second boolean parameter 'preload'.  if it is 
missing or false, then require() will work like normal.  If it is true, then 
require will try to find and load the module like normal but instead of 
executing the module (i.e. fully loading it), it will place the module 
into "package.preload" and return the module as a function.

Lets say we have module a.lua:
print("hello from a")

and b.lua:
print("hello from b")

then you run main.lua:
print(require("a"))
print(require("a"))
print(require("b",true))
print(require("b",true))
require("b",true) ()
require("b",true) ()
print(require("b"))
print(require("b",true))

you would get the follow output:
hello from a
true
true
function: 0x62a580
function: 0x62a580
hello from b
hello from b
hello from b
true
function: 0x62a580


[1] http://lua-users.org/lists/lua-l/2008-08/msg00092.html

-- 
Robert G. Jakabosky
diff --git a/src/loadlib.c b/src/loadlib.c
index 0d401eb..23020c4 100644
--- a/src/loadlib.c
+++ b/src/loadlib.c
@@ -450,9 +450,10 @@ static const int sentinel_ = 0;
 
 static int ll_require (lua_State *L) {
   const char *name = luaL_checkstring(L, 1);
+  int preload = lua_toboolean(L, 2);
   int i;
-  lua_settop(L, 1);  /* _LOADED table will be at index 2 */
-  lua_getfield(L, LUA_REGISTRYINDEX, "_LOADED");
+  lua_settop(L, 1);  /* _PRELOAD/_LOADED table will be at index 2 */
+  lua_getfield(L, LUA_REGISTRYINDEX, (preload)?"_PRELOAD":"_LOADED");
   lua_getfield(L, 2, name);
   if (lua_toboolean(L, -1)) {  /* is it there? */
     if (lua_touserdata(L, -1) == sentinel)  /* check loops */
@@ -478,6 +479,11 @@ static int ll_require (lua_State *L) {
     else
       lua_pop(L, 1);
   }
+  if (preload) { /* add library to preload list, don't run it yet. */
+    lua_pushvalue(L, -1);  /* dup module function */
+    lua_setfield(L, 2, name);  /* _PRELOAD[name] = library load function. */
+    return 1;
+  }
   lua_pushlightuserdata(L, sentinel);
   lua_setfield(L, 2, name);  /* _LOADED[name] = sentinel */
   lua_pushstring(L, name);  /* pass name as argument to module */
@@ -656,7 +662,7 @@ LUALIB_API int luaopen_package (lua_State *L) {
   luaL_findtable(L, LUA_REGISTRYINDEX, "_LOADED", 2);
   lua_setfield(L, -2, "loaded");
   /* set field `preload' */
-  lua_newtable(L);
+  luaL_findtable(L, LUA_REGISTRYINDEX, "_PRELOAD", 0);
   lua_setfield(L, -2, "preload");
   lua_pushvalue(L, LUA_GLOBALSINDEX);
   luaL_register(L, NULL, ll_funcs);  /* open lib into global table */
diff --git a/src/luac.c b/src/luac.c
index d070173..d4179d4 100644
--- a/src/luac.c
+++ b/src/luac.c
@@ -14,6 +14,7 @@
 
 #include "lua.h"
 #include "lauxlib.h"
+#include "lualib.h"
 
 #include "ldo.h"
 #include "lfunc.h"
@@ -33,6 +34,10 @@ static char Output[]={ OUTPUT };	/* default output file name */
 static const char* output=Output;	/* actual output file name */
 static const char* progname=PROGNAME;	/* actual program name */
 
+#define MAX_PRELOADS (MAXINDEXRK - 2) /* max preloads about 250, to simplify code. */
+static char* preload_libs[MAX_PRELOADS];
+static int preloads=0;
+
 static void fatal(const char* message)
 {
  fprintf(stderr,"%s: %s\n",progname,message);
@@ -56,6 +61,7 @@ static void usage(const char* message)
  "Available options are:\n"
  "  -        process stdin\n"
  "  -l       list\n"
+ "  -L name  preload lua library " LUA_QL("name") "\n"
  "  -o name  output to file " LUA_QL("name") " (default is \"%s\")\n"
  "  -p       parse only\n"
  "  -s       strip debug information\n"
@@ -84,6 +90,12 @@ static int doargs(int argc, char* argv[])
   }
   else if (IS("-"))			/* end of options; use stdin */
    break;
+  else if (IS("-L"))			/* preload library */
+  {
+   if (preloads >= MAX_PRELOADS) usage(LUA_QL("-L") " too many preloads");
+   preload_libs[preloads]=argv[++i];
+   preloads++;
+  }
   else if (IS("-l"))			/* list */
    ++listing;
   else if (IS("-o"))			/* output file */
@@ -116,28 +128,69 @@ static int doargs(int argc, char* argv[])
 
 #define toproto(L,i) (clvalue(L->top+(i))->l.p)
 
-static const Proto* combine(lua_State* L, int n)
+static const Proto* combine(lua_State* L, int scripts)
 {
- if (n==1)
+ if (scripts == 1 && preloads == 0)
   return toproto(L,-1);
  else
  {
-  int i,pc;
+  TString *s;
+  TValue *k;
+  int i,pc,n;
   Proto* f=luaF_newproto(L);
   setptvalue2s(L,L->top,f); incr_top(L);
   f->source=luaS_newliteral(L,"=(" PROGNAME ")");
   f->maxstacksize=1;
-  pc=2*n+1;
+  pc=(2*scripts) + 1;
+  if(preloads > 0)
+  {
+   pc+=(2*preloads) + 2;
+  }
   f->code=luaM_newvector(L,pc,Instruction);
   f->sizecode=pc;
+  n=(scripts + preloads);
   f->p=luaM_newvector(L,n,Proto*);
   f->sizep=n;
   pc=0;
-  for (i=0; i<n; i++)
+  n=0;
+  /* preload libraries. */
+  if (preloads > 0)
+  {
+   /* create constants array. */
+   f->k=luaM_newvector(L, (preloads + 2),TValue);
+   f->sizek=(preloads + 2);
+   /* make room for "local t" variable. */
+   f->maxstacksize=2;
+   /* add "package" & "preload" constants. */
+   k=&(f->k[0]);
+   s=luaS_newliteral(L, "package");
+   setsvalue2n(L,k,s);
+   k=&(f->k[1]);
+   s=luaS_newliteral(L, "preload");
+   setsvalue2n(L,k,s);
+   /* code: local t = package.preload */
+   f->code[pc++]=CREATE_ABx(OP_GETGLOBAL,0,0);
+   f->code[pc++]=CREATE_ABC(OP_GETTABLE,0,0,RKASK(1));
+  }
+  /* add preload libraries to "package.preload" */
+  for (i=0; i < preloads; i++)
+  {
+   /* create constant for library name. */
+   k=&(f->k[i+2]);
+   s=luaS_new(L, preload_libs[i]);
+   setsvalue2n(L,k,s);
+   /* code: t['name'] = function() --[[ lib code ]] end */
+   f->code[pc++]=CREATE_ABx(OP_CLOSURE,1,n);
+   f->code[pc++]=CREATE_ABC(OP_SETTABLE,0,RKASK(i+2),1);
+   f->p[n++]=toproto(L,i-preloads-1);
+  }
+  /* call scripts. */
+  for (i=0; i < scripts; i++)
   {
-   f->p[i]=toproto(L,i-n-1);
-   f->code[pc++]=CREATE_ABx(OP_CLOSURE,0,i);
+   /* code: (function() --[[ script code ]] end)() */
+   f->code[pc++]=CREATE_ABx(OP_CLOSURE,0,n);
    f->code[pc++]=CREATE_ABC(OP_CALL,0,1,1);
+   f->p[n++]=toproto(L,i-scripts-1-preloads);
   }
   f->code[pc++]=CREATE_ABC(OP_RETURN,0,1,0);
   return f;
@@ -161,14 +214,60 @@ static int pmain(lua_State* L)
  int argc=s->argc;
  char** argv=s->argv;
  const Proto* f;
+ int scripts=0;
  int i;
  if (!lua_checkstack(L,argc)) fatal("too many input files");
- for (i=0; i<argc; i++)
- {
+ lua_gc(L, LUA_GCSTOP, 0);  /* stop collector during initialization */
+ luaL_openlibs(L);  /* open libraries */
+ lua_gc(L, LUA_GCRESTART, 0);
+ /* compile each script from command line into a Lua function. */
+ for (i=0; i<argc; i++) {
   const char* filename=IS("-") ? NULL : argv[i];
+  if(IS("-L")) break;
   if (luaL_loadfile(L,filename)!=0) fatal(lua_tostring(L,-1));
+  scripts++;
+ }
+ /* compile each preload library from the command line into a Lua function. */
+ for (i=0; i<preloads; i++) {
+  char* filename=preload_libs[i];
+  char* p;
+  /* try loading library as if it is a normal file. */
+  if (luaL_loadfile(L,filename)!=0) {
+   /* try pre-loading library with 'require' module loading system. */
+   lua_getglobal(L, "require");
+   lua_pushstring(L, filename);
+   lua_pushboolean(L, 1);
+   lua_call(L, 2, 1);
+   if (lua_iscfunction(L, -1)) { /* make sure it is not a C-Function. */
+    lua_pop(L, 1);
+    lua_pushfstring(L, "\nCan't preload C module: '%s'\n", filename);
+    lua_concat(L, 2);  /* accumulate with error from luaL_findfile */
+    fatal(lua_tostring(L,-1));
+   }
+   if (!lua_isfunction(L, -1)) { /* did we get an error? */
+    lua_pushliteral(L, "\n");
+    lua_concat(L, 3);  /* accumulate with error from luaL_findfile */
+    fatal(lua_tostring(L,-1));
+   } else {
+    lua_remove(L, -2); /* remove error from luaL_findfile. */
+   }
+  } else {
+   /* convert filename into package name. */
+   p= filename + strlen(filename);
+   for(;p >= filename; p--) {
+    if(p[0] == '.') { /* Remove file extension. */
+     p[0] = '\0';
+     continue;
+    }
+    if(p[0] == '/') { /* Remove file path. */
+     preload_libs[i] = p+1;
+     break;
+    }
+   }
+  }
  }
- f=combine(L,argc);
+ /* generate a new Lua function to combine all of the compiled scripts. */
+ f=combine(L, scripts);
  if (listing) luaU_print(f,listing>1);
  if (dumping)
  {